diff --git a/components/3rd_party/omv/CMakeLists.txt b/components/3rd_party/omv/CMakeLists.txt index e04484da..92d730b2 100644 --- a/components/3rd_party/omv/CMakeLists.txt +++ b/components/3rd_party/omv/CMakeLists.txt @@ -1,6 +1,4 @@ -set(omv_version_str "${CONFIG_OMV_VERSION_MAJOR}.${CONFIG_OMV_VERSION_MINOR}.${CONFIG_OMV_VERSION_PATCH}") -set(omv_unzip_path "${DL_EXTRACTED_PATH}/omv") -set(src_path "${omv_unzip_path}/omv-${omv_version_str}") +set(src_path "${CMAKE_CURRENT_SOURCE_DIR}/omv") ############### Add include ################### set(omv_include_dir "." diff --git a/components/3rd_party/omv/component.py b/components/3rd_party/omv/component.py deleted file mode 100644 index 239fb26d..00000000 --- a/components/3rd_party/omv/component.py +++ /dev/null @@ -1,34 +0,0 @@ - -def add_file_downloads(confs : dict) -> list: - ''' - @param confs kconfig vars, dict type - @return list type, items is dict type - ''' - version = f"{confs['CONFIG_OMV_VERSION_MAJOR']}.{confs['CONFIG_OMV_VERSION_MINOR']}.{confs['CONFIG_OMV_VERSION_PATCH']}" - url = f"https://github.com/sipeed/MaixCDK/releases/download/v0.0.0/omv-{version}.zip" - if version == "1.0.10": - sha256sum = "e3b6d04a5379be52b226434ef0dfce404faf91955dfab0386a189684b99940bd" - else: - raise Exception(f"version {version} not support") - sites = ["https://github.com/sipeed/MaixCDK/releases/tag/v0.0.0"] - filename = f"omv-{version}.zip" - path = f"omv" - check_file = f'omv-{version}' - rename = {} - - return [ - { - 'url': f'{url}', - 'urls': [], - 'sites': sites, - 'sha256sum': sha256sum, - 'filename': filename, - 'path': path, - 'check_files': [ - check_file - ], - 'rename': rename - } - ] - - diff --git a/components/3rd_party/omv/omv/alloc/fb_alloc.c b/components/3rd_party/omv/omv/alloc/fb_alloc.c new file mode 100644 index 00000000..0e25683e --- /dev/null +++ b/components/3rd_party/omv/omv/alloc/fb_alloc.c @@ -0,0 +1,427 @@ +/* + * 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 + +#define USER_DEBUG (0) +// #define USE_MALLOC + +#if USER_DEBUG +#define DEBUG_PRINT printf +#define ERR_PRINT printf +#else +#define DEBUG_PRINT(...) +#define ERR_PRINT printf +#endif +#ifdef USE_MALLOC +char *fb_alloc_stack_pointer() +{ + // FIXME: framebuffer.c file used this function. but this function is not implemented. + // We should avoid using functions of framebuffer.c file. + return NULL; +} + +void fb_alloc_fail() { + ERR_PRINT("[omv] MemoryError of fb alloc!"); +} + +void fb_alloc_init0() { + // do nothing +} + +uint32_t fb_avail() { + // FIXME: framebuffer.c file used this function. but this function is not implemented. + // We should avoid using functions of framebuffer.c file. + return 0; +} + +void fb_alloc_mark() { + // do nothing +} + +void fb_alloc_free_till_mark() { + // do nothing +} + +void fb_alloc_mark_permanent() { + // do nothing +} + +void fb_alloc_free_till_mark_past_mark_permanent() { + // do nothing +} + +void *fb_alloc(uint32_t size, int hints) { + return xalloc(size); +} + +void *fb_alloc0(uint32_t size, int hints) { + return xalloc0(size); +} + +void *fb_alloc_all(uint32_t *size, int hints) { + void *alloc = xalloc(OMV_FB_ALLOC_SIZE); + if (!alloc) { + fb_alloc_fail(); + return NULL; + } + *size = OMV_FB_ALLOC_SIZE; + return alloc; +} + +void *fb_alloc0_all(uint32_t *size, int hints) { + void *alloc = xalloc0(OMV_FB_ALLOC_SIZE); + if (!alloc) { + fb_alloc_fail(); + return NULL; + } + *size = OMV_FB_ALLOC_SIZE; + return alloc; +} + +void fb_free(void *ptr) { + xfree(ptr); +} + +void fb_free_all() { + // do nothing +} +#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; + +#if USER_DEBUG +static int alloc_num = 0; +#endif + +#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!"); +} + +__attribute__((constructor)) void fb_alloc_init0() +{ + if (_fballoc_start) + return; + DEBUG_PRINT("[omv] fb alloc init\r\n"); + _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; + } +} + +__attribute__((destructor)) void fb_alloc_close0() +{ + if (!_fballoc_start) + return; + DEBUG_PRINT("[omv] fb alloc deinit\r\n"); + 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; + } + } +#if USER_DEBUG + DEBUG_PRINT("mem num:%d pointer:%p size:%d\r\n", ++ alloc_num, pointer, size); +#endif + 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\r\n", ++ 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 *ptr) +{ + 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 pointer:%p size:%d\r\n", -- alloc_num, pointer, size); + } +} + +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/components/3rd_party/omv/omv/alloc/fb_alloc.h b/components/3rd_party/omv/omv/alloc/fb_alloc.h new file mode 100644 index 00000000..f4c485f9 --- /dev/null +++ b/components/3rd_party/omv/omv/alloc/fb_alloc.h @@ -0,0 +1,81 @@ +/* + * 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 pointer to an allocated region of memory equal in size to + * the amount requested. If the memory is not available 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__ + +#if __cplusplus +extern "C" { +#endif +#include +#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(); +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 *ptr); +void fb_free_all(); + +#if __cplusplus +} +#endif +#endif /* __FF_ALLOC_H__ */ diff --git a/components/3rd_party/omv/omv/alloc/umm_malloc.c b/components/3rd_party/omv/omv/alloc/umm_malloc.c new file mode 100644 index 00000000..b0394040 --- /dev/null +++ b/components/3rd_party/omv/omv/alloc/umm_malloc.c @@ -0,0 +1,621 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2007-2017 Ralph Hempel + * Copyright (c) 2017-2021 Ibrahim Abdelkader + * Copyright (c) 2017-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" + +// /* 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) + +// NORETURN void umm_alloc_fail() { +// mp_raise_msg(&mp_type_MemoryError, MP_ERROR_TEXT("Out of fast frame buffer stack memory")); +// } + +// 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 need 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/components/3rd_party/omv/omv/alloc/umm_malloc.h b/components/3rd_party/omv/omv/alloc/umm_malloc.h new file mode 100644 index 00000000..54ce115e --- /dev/null +++ b/components/3rd_party/omv/omv/alloc/umm_malloc.h @@ -0,0 +1,48 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2007-2017 Ralph Hempel + * Copyright (c) 2017-2021 Ibrahim Abdelkader + * Copyright (c) 2017-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 "xalloc.h" +#include + +static inline void umm_alloc_fail() +{ + printf("UMM: alloc failed\n"); + exit(1); +} + +static inline void umm_init_x(size_t size) +{ + (void)size; +} + +static inline void *umm_malloc(size_t size) +{ + return xalloc(size); +} + +static inline void *umm_calloc(size_t num, size_t size) +{ + return xcalloc(num, size); +} + +static inline void *umm_realloc(void *ptr, size_t size) +{ + return xrealloc(ptr, size); +} + +static inline void umm_free(void *ptr) +{ + xfree(ptr); +} +#endif /* __UMM_MALLOC_H__ */ diff --git a/components/3rd_party/omv/omv/alloc/unaligned_memcpy.c b/components/3rd_party/omv/omv/alloc/unaligned_memcpy.c new file mode 100644 index 00000000..358e111b --- /dev/null +++ b/components/3rd_party/omv/omv/alloc/unaligned_memcpy.c @@ -0,0 +1,74 @@ +#include +#include +// #include "cmsis_gcc.h" +#include "arm_compat.h" +#include "unaligned_memcpy.h" + +// 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; +} diff --git a/components/3rd_party/omv/omv/alloc/unaligned_memcpy.h b/components/3rd_party/omv/omv/alloc/unaligned_memcpy.h new file mode 100644 index 00000000..2d026cb9 --- /dev/null +++ b/components/3rd_party/omv/omv/alloc/unaligned_memcpy.h @@ -0,0 +1,16 @@ +/* + * 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__ +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); +#endif //__UNALIGNED_MEMCPY_H__ diff --git a/components/3rd_party/omv/omv/alloc/xalloc.c b/components/3rd_party/omv/omv/alloc/xalloc.c new file mode 100644 index 00000000..6fca92fe --- /dev/null +++ b/components/3rd_party/omv/omv/alloc/xalloc.c @@ -0,0 +1,79 @@ +/* + * 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" + +static void xalloc_fail(size_t size) +{ + printf("MemoryError :memory allocation failed, allocating %ld 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); + } + 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); + return mem; +} + +// returns without error if mem==null +void xfree(void *mem) +{ + free(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); + } + return mem; +} +void *xcalloc(size_t nitems, size_t size) +{ + void *mem = calloc(nitems, size); + if (size && (mem == NULL)) { + xalloc_fail(size); + } + return mem; +} + diff --git a/components/3rd_party/omv/omv/alloc/xalloc.h b/components/3rd_party/omv/omv/alloc/xalloc.h new file mode 100644 index 00000000..df0100d6 --- /dev/null +++ b/components/3rd_party/omv/omv/alloc/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/components/3rd_party/omv/omv/common/array.c b/components/3rd_party/omv/omv/common/array.c new file mode 100644 index 00000000..d51445f3 --- /dev/null +++ b/components/3rd_party/omv/omv/common/array.c @@ -0,0 +1,170 @@ +/* + * 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; i < j; i++) { + array->dtor(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; i < j; i++) { + array->dtor(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/components/3rd_party/omv/omv/common/array.h b/components/3rd_party/omv/omv/common/array.h new file mode 100644 index 00000000..4460c118 --- /dev/null +++ b/components/3rd_party/omv/omv/common/array.h @@ -0,0 +1,37 @@ +/* + * 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__ +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); +#endif //__ARRAY_H__ diff --git a/components/3rd_party/omv/omv/common/boot_utils.c b/components/3rd_party/omv/omv/common/boot_utils.c new file mode 100644 index 00000000..d8d65063 --- /dev/null +++ b/components/3rd_party/omv/omv/common/boot_utils.c @@ -0,0 +1,95 @@ +/* + * 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. + * + * Boot util functions. + */ +#include +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" +#include "py/objstr.h" +#include "shared/runtime/pyexec.h" +#if MICROPY_HW_USB_MSC +#include "extmod/vfs.h" +#include "extmod/vfs_fat.h" +// Fresh filesystem templates. +#include "main_py.h" +#include "readme_txt.h" +#endif +#include "omv_boardconfig.h" +#include "usbdbg.h" +#if OMV_ENABLE_WIFIDBG +#include "wifidbg.h" +#endif +#include "file_utils.h" +#include "boot_utils.h" + +#if MICROPY_VFS_FAT +extern void __fatal_error(); + +int bootutils_init_filesystem(fs_user_mount_t *vfs) { + FIL fp; UINT n; + uint8_t working_buf[FF_MAX_SS]; + if (f_mkfs(&vfs->fatfs, FM_FAT, 0, working_buf, sizeof(working_buf)) != FR_OK) { + __fatal_error("Could not create LFS"); + } + + // Mark FS as OpenMV disk. + if (f_stat(&vfs->fatfs, "/.openmv_disk", NULL) != FR_OK) { + f_open(&vfs->fatfs, &fp, "/.openmv_disk", FA_WRITE | FA_CREATE_ALWAYS); + f_close(&fp); + } + + // Create default main.py + f_open(&vfs->fatfs, &fp, "/main.py", FA_WRITE | FA_CREATE_ALWAYS); + f_write(&fp, fresh_main_py, sizeof(fresh_main_py) - 1 /* don't count null terminator */, &n); + f_close(&fp); + + // Create readme file + f_open(&vfs->fatfs, &fp, "/README.txt", FA_WRITE | FA_CREATE_ALWAYS); + f_write(&fp, fresh_readme_txt, sizeof(fresh_readme_txt) - 1 /* don't count null terminator */, &n); + f_close(&fp); + + return 0; +} +#endif + +bool bootutils_exec_bootscript(const char *path, bool interruptible, bool wifidbg_enabled) { + nlr_buf_t nlr; + bool interrupted = false; + + if (nlr_push(&nlr) == 0) { + // Enable IDE interrupts if allowed. + if (interruptible) { + usbdbg_set_irq_enabled(true); + usbdbg_set_script_running(true); + #if OMV_ENABLE_WIFIDBG + wifidbg_set_irq_enabled(wifidbg_enabled); + #endif + } + + // Parse, compile and execute the script. + pyexec_file_if_exists(path, true); + nlr_pop(); + } else { + interrupted = true; + } + + // Disable IDE interrupts + usbdbg_set_irq_enabled(false); + usbdbg_set_script_running(false); + #if OMV_ENABLE_WIFIDBG + wifidbg_set_irq_enabled(false); + #endif + + if (interrupted) { + mp_obj_print_exception(&mp_plat_print, (mp_obj_t) nlr.ret_val); + } + + return interrupted; +} diff --git a/components/3rd_party/omv/omv/common/boot_utils.h b/components/3rd_party/omv/omv/common/boot_utils.h new file mode 100644 index 00000000..913b10b7 --- /dev/null +++ b/components/3rd_party/omv/omv/common/boot_utils.h @@ -0,0 +1,16 @@ +/* + * 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. + * + * Boot util functions. + */ +#ifndef __BOOT_UTILS_H__ +#define __BOOT_UTILS_H__ +typedef struct _fs_user_mount_t fs_user_mount_t; +int bootutils_init_filesystem(fs_user_mount_t *vfs); +bool bootutils_exec_bootscript(const char *path, bool interruptible, bool wifidbg_enabled); +#endif // __BOOT_UTILS_H__ diff --git a/components/3rd_party/omv/omv/common/file_utils.c b/components/3rd_party/omv/omv/common/file_utils.c new file mode 100644 index 00000000..aa1a0d61 --- /dev/null +++ b/components/3rd_party/omv/omv/common/file_utils.c @@ -0,0 +1,419 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013-2023 Kwabena W. Agyeman + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Filesystem helper functions. + * + */ +#include "imlib_config.h" +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + +#include +#include "py/runtime.h" +#include "extmod/vfs.h" +#include "extmod/vfs_fat.h" + +#include "omv_common.h" +#include "fb_alloc.h" +#include "file_utils.h" +#define FF_MIN(x, y) (((x) < (y))?(x):(y)) + +NORETURN static void ff_read_fail(FIL *fp) { + if (fp) { + f_close(fp); + } + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to read requested bytes!")); +} + +NORETURN static void ff_write_fail(FIL *fp) { + if (fp) { + f_close(fp); + } + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to write requested bytes!")); +} + +NORETURN static void ff_expect_fail(FIL *fp) { + if (fp) { + f_close(fp); + } + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unexpected value read!")); +} + +NORETURN void file_raise_format(FIL *fp) { + if (fp) { + f_close(fp); + } + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unsupported format!")); +} + +NORETURN void file_raise_corrupted(FIL *fp) { + if (fp) { + f_close(fp); + } + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("File corrupted!")); +} + +NORETURN void file_raise_error(FIL *fp, FRESULT res) { + if (fp) { + f_close(fp); + } + mp_raise_msg(&mp_type_OSError, (mp_rom_error_text_t) ffs_strerror(res)); +} + +static 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 file_ll_open(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 file_ll_close(FIL *fp) { + return f_close(fp); +} + +FRESULT file_ll_read(FIL *fp, void *buff, UINT btr, UINT *br) { + return f_read(fp, buff, btr, br); +} + +FRESULT file_ll_write(FIL *fp, const void *buff, UINT btw, UINT *bw) { + return f_write(fp, buff, btw, bw); +} + +FRESULT file_ll_opendir(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 file_ll_stat(const TCHAR *path, FILINFO *fno) { + FATFS *fs = lookup_path(&path); + if (fs == NULL) { + return FR_NO_PATH; + } + return f_stat(fs, path, fno); +} + +FRESULT file_ll_mkdir(const TCHAR *path) { + FATFS *fs = lookup_path(&path); + if (fs == NULL) { + return FR_NO_PATH; + } + return f_mkdir(fs, path); +} + +FRESULT file_ll_unlink(const TCHAR *path) { + FATFS *fs = lookup_path(&path); + if (fs == NULL) { + return FR_NO_PATH; + } + return f_unlink(fs, path); +} + +FRESULT file_ll_rename(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); +} + +FRESULT file_ll_touch(const TCHAR *path) { + FIL fp; + FATFS *fs = lookup_path(&path); + if (fs == NULL) { + return FR_NO_PATH; + } + + if (f_stat(fs, path, NULL) != FR_OK) { + f_open(fs, &fp, path, FA_WRITE | FA_CREATE_ALWAYS); + f_close(&fp); + } + + return FR_OK; +} + +// 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 significantly 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 = f_size(fp) - f_tell(fp); + uint32_t can_do = FF_MIN(file_buffer_size, file_remaining); + UINT bytes; + FRESULT res = f_read(fp, file_buffer_pointer, can_do, &bytes); + if (res != FR_OK) { + file_raise_error(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) { + UINT bytes; + FRESULT res = f_write(fp, file_buffer_pointer, file_buffer_index, &bytes); + if (res != FR_OK) { + file_raise_error(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; + } +} + +void file_buffer_on(FIL *fp) { + file_buffer_offset = f_tell(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 = f_size(fp) - f_tell(fp); + uint32_t can_do = FF_MIN(file_buffer_size, file_remaining); + UINT bytes; + FRESULT res = f_read(fp, file_buffer_pointer, can_do, &bytes); + if (res != FR_OK) { + file_raise_error(fp, res); + } + if (bytes != can_do) { + ff_read_fail(fp); + } + } +} + +void file_buffer_off(FIL *fp) { + if ((fp->flag & FA_WRITE) && file_buffer_index) { + UINT bytes; + FRESULT res = f_write(fp, file_buffer_pointer, file_buffer_index, &bytes); + if (res != FR_OK) { + file_raise_error(fp, res); + } + if (bytes != file_buffer_index) { + ff_write_fail(fp); + } + } + + if (file_buffer_pointer) { + fb_free(file_buffer_pointer); + file_buffer_pointer = 0; + } +} + +void file_open(FIL *fp, const char *path, bool buffered, uint32_t flags) { + FRESULT res = file_ll_open(fp, path, flags); + if (res != FR_OK) { + file_raise_error(fp, res); + } + if (buffered) { + file_buffer_on(fp); + } +} + +void file_close(FIL *fp) { + if (file_buffer_pointer) { + file_buffer_off(fp); + } + + FRESULT res = f_close(fp); + if (res != FR_OK) { + file_raise_error(fp, res); + } +} + +void file_seek(FIL *fp, UINT offset) { + FRESULT res = f_lseek(fp, offset); + if (res != FR_OK) { + file_raise_error(fp, res); + } +} + +void file_truncate(FIL *fp) { + FRESULT res = f_truncate(fp); + if (res != FR_OK) { + file_raise_error(fp, res); + } +} + +void file_sync(FIL *fp) { + FRESULT res = f_sync(fp); + if (res != FR_OK) { + file_raise_error(fp, res); + } +} + +uint32_t file_tell(FIL *fp) { + if (file_buffer_pointer) { + if (fp->flag & FA_READ) { + return f_tell(fp) - file_buffer_size + file_buffer_index; + } else { + return f_tell(fp) + file_buffer_index; + } + } + return f_tell(fp); +} + +uint32_t file_size(FIL *fp) { + if (file_buffer_pointer) { + if (fp->flag & FA_READ) { + return f_size(fp); + } else { + return f_size(fp) + file_buffer_index; + } + } + return f_size(fp); +} + +void file_read(FIL *fp, void *data, size_t size) { + if (data == NULL) { + uint8_t byte; + if (file_buffer_pointer) { + for (size_t i = 0; i < size; i++) { + file_fill(fp); + byte = file_buffer_pointer[file_buffer_index++]; + } + } else { + for (size_t i = 0; i < size; i++) { + UINT bytes; + FRESULT res = f_read(fp, &byte, 1, &bytes); + if (res != FR_OK) { + file_raise_error(fp, res); + } + if (bytes != 1) { + ff_read_fail(fp); + } + } + } + return; + } + + if (file_buffer_pointer) { + if (size <= 4) { + for (size_t i = 0; i < size; i++) { + file_fill(fp); + ((uint8_t *) data)[i] = file_buffer_pointer[file_buffer_index++]; + } + } else { + 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 { + UINT bytes; + FRESULT res = f_read(fp, data, size, &bytes); + if (res != FR_OK) { + file_raise_error(fp, res); + } + if (bytes != size) { + ff_read_fail(fp); + } + } +} + +void file_write(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 { + UINT bytes; + FRESULT res = f_write(fp, data, size, &bytes); + if (res != FR_OK) { + file_raise_error(fp, res); + } + if (bytes != size) { + ff_write_fail(fp); + } + } +} + +void file_write_byte(FIL *fp, uint8_t value) { + file_write(fp, &value, 1); +} + +void file_write_short(FIL *fp, uint16_t value) { + file_write(fp, &value, 2); +} + +void file_write_long(FIL *fp, uint32_t value) { + file_write(fp, &value, 4); +} + +void file_read_check(FIL *fp, const void *data, size_t size) { + uint8_t buf[16]; + while (size) { + size_t len = OMV_MIN(sizeof(buf), size); + file_read(fp, buf, len); + if (memcmp(data, buf, len)) { + ff_expect_fail(fp); + } + size -= len; + data = ((uint8_t *) data) + len; + } +} +#endif //IMLIB_ENABLE_IMAGE_FILE_IO diff --git a/components/3rd_party/omv/omv/common/file_utils.h b/components/3rd_party/omv/omv/common/file_utils.h new file mode 100644 index 00000000..2eed5bfe --- /dev/null +++ b/components/3rd_party/omv/omv/common/file_utils.h @@ -0,0 +1,52 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013-2023 Kwabena W. Agyeman + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Filesystem helper functions. + * + */ +#ifndef __FILE_UTILS_H__ +#define __FILE_UTILS_H__ +#include +#include +#include +extern const char *ffs_strerror(FRESULT res); + +void file_raise_format(FIL *fp); +void file_raise_corrupted(FIL *fp); +void file_raise_error(FIL *fp, FRESULT res); + +// These low-level functions/wrappers don't use file buffering, +// and they don't raise any exceptions, so they're safe to call +// from anywhere. +FRESULT file_ll_open(FIL *fp, const TCHAR *path, BYTE mode); +FRESULT file_ll_close(FIL *fp); +FRESULT file_ll_read(FIL *fp, void *buff, UINT btr, UINT *br); +FRESULT file_ll_write(FIL *fp, const void *buff, UINT btw, UINT *bw); +FRESULT file_ll_opendir(FF_DIR *dp, const TCHAR *path); +FRESULT file_ll_stat(const TCHAR *path, FILINFO *fno); +FRESULT file_ll_mkdir(const TCHAR *path); +FRESULT file_ll_unlink(const TCHAR *path); +FRESULT file_ll_rename(const TCHAR *path_old, const TCHAR *path_new); +FRESULT file_ll_touch(const TCHAR *path); + +// File buffer functions. +void file_buffer_init0(); +void file_buffer_on(FIL *fp); // Calls fb_alloc_all() +void file_buffer_off(FIL *fp); // Calls fb_free() + +void file_open(FIL *fp, const char *path, bool buffered, uint32_t flags); +void file_close(FIL *fp); +void file_seek(FIL *fp, UINT offset); +void file_truncate(FIL *fp); +void file_sync(FIL *fp); +uint32_t file_tell(FIL *fp); +uint32_t file_size(FIL *fp); +void file_read(FIL *fp, void *data, size_t size); +void file_write(FIL *fp, const void *data, size_t size); +void file_write_byte(FIL *fp, uint8_t value); +void file_write_short(FIL *fp, uint16_t value); +void file_write_long(FIL *fp, uint32_t value); +void file_read_check(FIL *fp, const void *data, size_t size); +#endif /* __FILE_UTILS_H__ */ diff --git a/components/3rd_party/omv/omv/common/ini.c b/components/3rd_party/omv/omv/common/ini.c new file mode 100644 index 00000000..bdf86069 --- /dev/null +++ b/components/3rd_party/omv/omv/common/ini.c @@ -0,0 +1,526 @@ +/* + * 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" +#include "file_utils.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 (file_ll_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 = ini_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 *) malloc(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 = realloc(line, max_line); + if (!new_line) { + free(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 + free(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); +} diff --git a/components/3rd_party/omv/omv/common/ini.h b/components/3rd_party/omv/omv/common/ini.h new file mode 100644 index 00000000..27fda93d --- /dev/null +++ b/components/3rd_party/omv/omv/common/ini.h @@ -0,0 +1,130 @@ +/* + * 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__ + +int ini_atoi(const char *string); +bool ini_is_true(const char *value); + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include "file_utils.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__ */ diff --git a/components/3rd_party/omv/omv/common/mutex.c b/components/3rd_party/omv/omv/common/mutex.c new file mode 100644 index 00000000..22f096f4 --- /dev/null +++ b/components/3rd_party/omv/omv/common/mutex.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. + * + * Mutex implementation. + * This is a standard implementation of mutexs on ARM processors following the ARM guide. + * http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0321a/BIHEJCHB.html + * + * Note: The Cortex-M0/M0+ does Not have the Load/Store exclusive instructions, on these + * CPUs the locking function is implemented with atomic access using disable/enable IRQs. + */ +#include "mutex.h" +#include "cmsis_gcc.h" +#include "py/mphal.h" + +void mutex_init0(omv_mutex_t *mutex) { + __DMB(); + mutex->tid = 0; + mutex->lock = 0; + mutex->last_tid = 0; +} + +static void _mutex_lock(omv_mutex_t *mutex, uint32_t tid, bool blocking) { + #if (__ARM_ARCH < 7) + do { + __disable_irq(); + if (mutex->lock == 0) { + mutex->lock = 1; + mutex->tid = tid; + } + __enable_irq(); + } while (mutex->tid != tid && blocking); + #else + do { + // Attempt to lock the mutex + if (__LDREXW(&mutex->lock) == 0) { + if (__STREXW(1, &mutex->lock) == 0) { + // Set TID if mutex is locked + mutex->tid = tid; + } + } + } while (mutex->tid != tid && blocking); + #endif + __DMB(); +} + +void mutex_lock(omv_mutex_t *mutex, uint32_t tid) { + _mutex_lock(mutex, tid, true); +} + +int mutex_try_lock(omv_mutex_t *mutex, uint32_t tid) { + // If the mutex is already locked by the current thread then + // release it and return without locking, otherwise try to lock it. + if (mutex->tid == tid) { + mutex_unlock(mutex, tid); + } else { + _mutex_lock(mutex, tid, false); + } + + return (mutex->tid == tid); +} + +int mutex_try_lock_alternate(omv_mutex_t *mutex, uint32_t tid) { + if (mutex->last_tid != tid) { + if (mutex_try_lock(mutex, tid)) { + mutex->last_tid = tid; + return 1; + } + } + + return 0; +} + +int mutex_lock_timeout(omv_mutex_t *mutex, uint32_t tid, uint32_t timeout) { + mp_uint_t tick_start = mp_hal_ticks_ms(); + while ((mp_hal_ticks_ms() - tick_start) >= timeout) { + if (mutex_try_lock(mutex, tid)) { + return 1; + } + __WFI(); + } + return 0; +} + +void mutex_unlock(omv_mutex_t *mutex, uint32_t tid) { + if (mutex->tid == tid) { + __DMB(); + mutex->tid = 0; + mutex->lock = 0; + } +} diff --git a/components/3rd_party/omv/omv/common/mutex.h b/components/3rd_party/omv/omv/common/mutex.h new file mode 100644 index 00000000..af8a2117 --- /dev/null +++ b/components/3rd_party/omv/omv/common/mutex.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. + * + * Mutex implementation. + */ +#ifndef __MUTEX_H__ +#define __MUTEX_H__ +#include +#define MUTEX_TID_IDE (1 << 0) +#define MUTEX_TID_OMV (1 << 1) + +typedef volatile struct { + uint32_t tid; + uint32_t lock; + uint32_t last_tid; +} omv_mutex_t; + +void mutex_init0(omv_mutex_t *mutex); +void mutex_lock(omv_mutex_t *mutex, uint32_t tid); +int mutex_try_lock(omv_mutex_t *mutex, uint32_t tid); +int mutex_try_lock_alternate(omv_mutex_t *mutex, uint32_t tid); +int mutex_lock_timeout(omv_mutex_t *mutex, uint32_t tid, uint32_t timeout); +void mutex_unlock(omv_mutex_t *mutex, uint32_t tid); +#endif /* __MUTEX_H__ */ diff --git a/components/3rd_party/omv/omv/common/omv_common.h b/components/3rd_party/omv/omv/common/omv_common.h new file mode 100644 index 00000000..bbd379db --- /dev/null +++ b/components/3rd_party/omv/omv/common/omv_common.h @@ -0,0 +1,49 @@ +/* + * 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. + * + * Common macros. + */ +#ifndef __OMV_COMMON_H__ + +#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 OMV_BREAK() __asm__ volatile ("BKPT") + +// Use 32-byte alignment on MCUs with no cache for DMA buffer alignment. +#ifndef __DCACHE_PRESENT +#define OMV_ALLOC_ALIGNMENT (32) +#else +#define OMV_ALLOC_ALIGNMENT (__SCB_DCACHE_LINE_SIZE) +#endif + +#define OMV_ATTR_ALIGNED_DMA(x) OMV_ATTR_ALIGNED(x, OMV_ALLOC_ALIGNMENT) + +#ifdef OMV_DEBUG_PRINTF +#define debug_printf(fmt, ...) \ + do { printf("%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; \ + }) + +#define OMV_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +#endif //__OMV_COMMON_H__ diff --git a/components/3rd_party/omv/omv/common/omv_gpio.h b/components/3rd_party/omv/omv/common/omv_gpio.h new file mode 100644 index 00000000..efc11896 --- /dev/null +++ b/components/3rd_party/omv/omv/common/omv_gpio.h @@ -0,0 +1,40 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2023 Ibrahim Abdelkader + * Copyright (c) 2023 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * GPIO abstraction layer. + */ +#ifndef __OMV_GPIO_H__ +#define __OMV_GPIO_H__ +#include +#include +#include "omv_portconfig.h" + +// Config options are defined in ports so they can be used +// directly to initialize peripherals without remapping them. + +typedef void (*omv_gpio_callback_t) (void *data); + +typedef struct _omv_gpio_irq_descr { + bool enabled; + void *data; + omv_gpio_callback_t callback; + #ifdef OMV_GPIO_IRQ_DESCR_PORT_BITS + // Additional port-specific fields used internally. + OMV_GPIO_IRQ_DESCR_PORT_BITS + #endif +} omv_gpio_irq_descr_t; + +void omv_gpio_init0(void); +void omv_gpio_config(omv_gpio_t pin, uint32_t mode, uint32_t pull, uint32_t speed, uint32_t af); +void omv_gpio_deinit(omv_gpio_t pin); +bool omv_gpio_read(omv_gpio_t pin); +void omv_gpio_write(omv_gpio_t pin, bool value); +void omv_gpio_irq_register(omv_gpio_t pin, omv_gpio_callback_t callback, void *data); +void omv_gpio_irq_enable(omv_gpio_t pin, bool enable); +void omv_gpio_clock_enable(omv_gpio_t pin, bool enable); +#endif // __OMV_GPIO_H__ diff --git a/components/3rd_party/omv/omv/common/omv_i2c.h b/components/3rd_party/omv/omv/common/omv_i2c.h new file mode 100644 index 00000000..19c564d5 --- /dev/null +++ b/components/3rd_party/omv/omv/common/omv_i2c.h @@ -0,0 +1,69 @@ +/* + * 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. + * + * I2C bus abstraction layer. + */ +#ifndef __OMV_I2C_H__ +#define __OMV_I2C_H__ +#include +#include +#include "omv_portconfig.h" + +// Transfer speeds +typedef enum _omv_i2c_speed { + OMV_I2C_SPEED_STANDARD = (0U), + OMV_I2C_SPEED_FULL = (1U), + OMV_I2C_SPEED_FAST = (2U), + OMV_I2C_SPEED_MAX = (3U) +} omv_i2c_speed_t; + +// For use with read/write_bytes +typedef enum omv_i2c_xfer_flags { + // Stop condition after the transfer. + // Normal transfer with start condition, address, data and stop condition. + OMV_I2C_XFER_NO_FLAGS = (0 << 0), + // No stop condition after the transfer. + // This flag allows the next transfer to change direction with repeated start. + OMV_I2C_XFER_NO_STOP = (1 << 0), + // No stop condition after the transfer. + // This flag allows chaining multiple writes or reads with the same direction. + OMV_I2C_XFER_SUSPEND = (1 << 1), +} omv_i2c_xfer_flags_t; + +typedef struct _omv_i2c { + uint32_t id; + uint32_t speed; + uint32_t initialized; + omv_gpio_t scl_pin; + omv_gpio_t sda_pin; + omv_i2c_dev_t inst; + #ifdef OMV_I2C_PORT_BITS + // Additional port-specific fields like device base pointer, + // dma handles, more I/Os etc... are included directly here, + // so that they can be accessible from this struct. + OMV_I2C_PORT_BITS + #endif +} omv_i2c_t; + +int omv_i2c_init(omv_i2c_t *i2c, uint32_t bus_id, uint32_t speed); +int omv_i2c_deinit(omv_i2c_t *i2c); +int omv_i2c_scan(omv_i2c_t *i2c, uint8_t *list, uint8_t size); +int omv_i2c_enable(omv_i2c_t *i2c, bool enable); +int omv_i2c_gencall(omv_i2c_t *i2c, uint8_t cmd); +int omv_i2c_pulse_scl(omv_i2c_t *i2c); +int omv_i2c_readb(omv_i2c_t *i2c, uint8_t slv_addr, uint8_t reg_addr, uint8_t *reg_data); +int omv_i2c_writeb(omv_i2c_t *i2c, uint8_t slv_addr, uint8_t reg_addr, uint8_t reg_data); +int omv_i2c_readb2(omv_i2c_t *i2c, uint8_t slv_addr, uint16_t reg_addr, uint8_t *reg_data); +int omv_i2c_writeb2(omv_i2c_t *i2c, uint8_t slv_addr, uint16_t reg_addr, uint8_t reg_data); +int omv_i2c_readw(omv_i2c_t *i2c, uint8_t slv_addr, uint8_t reg_addr, uint16_t *reg_data); +int omv_i2c_writew(omv_i2c_t *i2c, uint8_t slv_addr, uint8_t reg_addr, uint16_t reg_data); +int omv_i2c_readw2(omv_i2c_t *i2c, uint8_t slv_addr, uint16_t reg_addr, uint16_t *reg_data); +int omv_i2c_writew2(omv_i2c_t *i2c, uint8_t slv_addr, uint16_t reg_addr, uint16_t reg_data); +int omv_i2c_read_bytes(omv_i2c_t *i2c, uint8_t slv_addr, uint8_t *buf, int len, uint32_t flags); +int omv_i2c_write_bytes(omv_i2c_t *i2c, uint8_t slv_addr, uint8_t *buf, int len, uint32_t flags); +#endif // __OMV_I2C_H__ diff --git a/components/3rd_party/omv/omv/common/omv_spi.h b/components/3rd_party/omv/omv/common/omv_spi.h new file mode 100644 index 00000000..26d52073 --- /dev/null +++ b/components/3rd_party/omv/omv/common/omv_spi.h @@ -0,0 +1,89 @@ +/* + * 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. + * + * SPI bus abstraction layer. + */ +#ifndef __OMV_SPI_H__ +#define __OMV_SPI_H__ +#include +#include +#include "omv_portconfig.h" + +// Config options are defined in ports so they can be used +// directly to initialize peripherals without remapping them. + +typedef enum { + OMV_SPI_DMA_NORMAL = (1 << 0), + OMV_SPI_DMA_CIRCULAR = (1 << 1), + OMV_SPI_DMA_DOUBLE = (1 << 2) +} omv_spi_dma_flags_t; + +typedef enum { + OMV_SPI_XFER_DMA = (1 << 0), + OMV_SPI_XFER_BLOCKING = (1 << 1), + OMV_SPI_XFER_NONBLOCK = (1 << 2), + OMV_SPI_XFER_FAILED = (1 << 3), + OMV_SPI_XFER_COMPLETE = (1 << 4), + OMV_SPI_XFER_HALF = (1 << 6), // Internal for Double Buffer mode. +} omv_spi_xfer_flags_t; + +typedef struct _omv_spi_config { + uint8_t id; + uint32_t baudrate; + uint8_t datasize; + uint32_t spi_mode; + uint32_t bus_mode; + uint32_t bit_order; + uint32_t clk_pol; + uint32_t clk_pha; + uint32_t nss_pol; + bool nss_enable; + uint32_t dma_flags; + bool data_retained; +} omv_spi_config_t; + +typedef struct _omv_spi omv_spi_t; + +typedef void (*omv_spi_callback_t) (omv_spi_t *spi, void *userdata, void *buf); + +typedef struct _omv_spi_transfer { + void *txbuf; + void *rxbuf; + uint32_t size; + uint32_t timeout; + omv_spi_xfer_flags_t flags; + void *userdata; + omv_spi_callback_t callback; +} omv_spi_transfer_t; + +typedef struct _omv_spi { + uint8_t id; + bool initialized; + uint32_t dma_flags; + omv_gpio_t cs; // For soft-NSS mode. + void *userdata; + omv_spi_callback_t callback; + uint32_t xfer_error; + volatile omv_spi_xfer_flags_t xfer_flags; + #ifdef OMV_SPI_PORT_BITS + // Additional port-specific fields like device base pointer, + // dma handles, more I/Os etc... are included directly here, + // so that they can be accessible from this struct. + OMV_SPI_PORT_BITS + #endif +} omv_spi_t; + +int omv_spi_init(omv_spi_t *spi, omv_spi_config_t *config); +// Default config: MASTER | FDX | 10MHz | 8 bits | MSB FIRST | NSS HARD | NSS/CPHA/CPOL LOW. +int omv_spi_default_config(omv_spi_config_t *config, uint32_t bus_id); +int omv_spi_deinit(omv_spi_t *spi); +int omv_spi_set_baudrate(omv_spi_t *spi, uint32_t baudrate); +int omv_spi_transfer_start(omv_spi_t *spi, omv_spi_transfer_t *xfer); +int omv_spi_transfer_abort(omv_spi_t *spi); + +#endif // __OMV_SPI_H__ diff --git a/components/3rd_party/omv/omv/common/ringbuf.c b/components/3rd_party/omv/omv/common/ringbuf.c new file mode 100644 index 00000000..92309dae --- /dev/null +++ b/components/3rd_party/omv/omv/common/ringbuf.c @@ -0,0 +1,42 @@ +/* + * 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/components/3rd_party/omv/omv/common/ringbuf.h b/components/3rd_party/omv/omv/common/ringbuf.h new file mode 100644 index 00000000..8c05954f --- /dev/null +++ b/components/3rd_party/omv/omv/common/ringbuf.h @@ -0,0 +1,26 @@ +/* + * 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 +#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); +#endif /* __RING_BUFFER_H__ */ diff --git a/components/3rd_party/omv/omv/common/sensor.h b/components/3rd_party/omv/omv/common/sensor.h new file mode 100644 index 00000000..654d9b7d --- /dev/null +++ b/components/3rd_party/omv/omv/common/sensor.h @@ -0,0 +1,454 @@ +/* + * 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 abstraction layer. + */ +#ifndef __SENSOR_H__ +#define __SENSOR_H__ +#include +#include "omv_i2c.h" +#include "imlib.h" + +#define OV2640_SLV_ADDR (0x60) +#define OV5640_SLV_ADDR (0x78) +#define OV7725_SLV_ADDR (0x42) +#define MT9V0XX_SLV_ADDR (0xB8) +#define MT9M114_SLV_ADDR (0x90) +#define LEPTON_SLV_ADDR (0x54) +#define HM0XX0_SLV_ADDR (0x48) +#define GC2145_SLV_ADDR (0x78) +#define FROGEYE2020_SLV_ADDR (0x6E) + +// Chip ID Registers +#define OV5640_CHIP_ID (0x300A) +#define OV_CHIP_ID (0x0A) +#define ON_CHIP_ID (0x00) +#define HIMAX_CHIP_ID (0x0001) +#define GC_CHIP_ID (0xF0) + +// Chip ID Values +#define OV2640_ID (0x26) +#define OV5640_ID (0x56) +#define OV7670_ID (0x76) +#define OV7690_ID (0x76) +#define OV7725_ID (0x77) +#define OV9650_ID (0x96) +#define MT9V0X2_ID_V_1 (0x1311) +#define MT9V0X2_ID_V_2 (0x1312) +#define MT9V0X2_ID (0x1313) +#define MT9V0X2_C_ID (0x1413) +#define MT9V0X4_ID (0x1324) +#define MT9V0X4_C_ID (0x1424) +#define MT9M114_ID (0x2481) +#define LEPTON_ID (0x54) +#define LEPTON_1_5 (0x5415) +#define LEPTON_1_6 (0x5416) +#define LEPTON_2_0 (0x5420) +#define LEPTON_2_5 (0x5425) +#define LEPTON_3_0 (0x5430) +#define LEPTON_3_5 (0x5435) +#define HM01B0_ID (0xB0) +#define HM0360_ID (0x60) +#define GC2145_ID (0x21) +#define PAJ6100_ID (0x6100) +#define FROGEYE2020_ID (0x2020) + +typedef enum { + FRAMESIZE_INVALID = 0, + // C/SIF Resolutions + FRAMESIZE_QQCIF, // 88x72 + FRAMESIZE_QCIF, // 176x144 + FRAMESIZE_CIF, // 352x288 + FRAMESIZE_QQSIF, // 88x60 + FRAMESIZE_QSIF, // 176x120 + FRAMESIZE_SIF, // 352x240 + // VGA Resolutions + FRAMESIZE_QQQQVGA, // 40x30 + FRAMESIZE_QQQVGA, // 80x60 + FRAMESIZE_QQVGA, // 160x120 + FRAMESIZE_QVGA, // 320x240 + FRAMESIZE_VGA, // 640x480 + FRAMESIZE_HQQQQVGA, // 30x20 + FRAMESIZE_HQQQVGA, // 60x40 + FRAMESIZE_HQQVGA, // 120x80 + FRAMESIZE_HQVGA, // 240x160 + FRAMESIZE_HVGA, // 480x320 + // FFT Resolutions + FRAMESIZE_64X32, // 64x32 + FRAMESIZE_64X64, // 64x64 + FRAMESIZE_128X64, // 128x64 + FRAMESIZE_128X128, // 128x128 + // Himax Resolutions + FRAMESIZE_160X160, // 160x160 + FRAMESIZE_320X320, // 320x320 + // Other + FRAMESIZE_LCD, // 128x160 + FRAMESIZE_QQVGA2, // 128x160 + FRAMESIZE_WVGA, // 720x480 + FRAMESIZE_WVGA2, // 752x480 + FRAMESIZE_SVGA, // 800x600 + FRAMESIZE_XGA, // 1024x768 + FRAMESIZE_WXGA, // 1280x768 + FRAMESIZE_SXGA, // 1280x1024 + FRAMESIZE_SXGAM, // 1280x960 + FRAMESIZE_UXGA, // 1600x1200 + FRAMESIZE_HD, // 1280x720 + FRAMESIZE_FHD, // 1920x1080 + FRAMESIZE_QHD, // 2560x1440 + FRAMESIZE_QXGA, // 2048x1536 + FRAMESIZE_WQXGA, // 2560x1600 + FRAMESIZE_WQXGA2, // 2592x1944 +} framesize_t; + +typedef enum { + GAINCEILING_2X, + GAINCEILING_4X, + GAINCEILING_8X, + GAINCEILING_16X, + GAINCEILING_32X, + GAINCEILING_64X, + GAINCEILING_128X, +} gainceiling_t; + +typedef enum { + SDE_NORMAL, + SDE_NEGATIVE, +} sde_t; + +typedef enum { + ATTR_CONTRAST=0, + ATTR_BRIGHTNESS, + ATTR_SATURATION, + ATTR_GAINCEILING, +} sensor_attr_t; + +typedef enum { + ACTIVE_LOW, + ACTIVE_HIGH +} polarity_t; + +typedef enum { + IOCTL_SET_READOUT_WINDOW, + IOCTL_GET_READOUT_WINDOW, + IOCTL_SET_TRIGGERED_MODE, + IOCTL_GET_TRIGGERED_MODE, + IOCTL_SET_FOV_WIDE, + IOCTL_GET_FOV_WIDE, + IOCTL_TRIGGER_AUTO_FOCUS, + IOCTL_PAUSE_AUTO_FOCUS, + IOCTL_RESET_AUTO_FOCUS, + IOCTL_WAIT_ON_AUTO_FOCUS, + IOCTL_SET_NIGHT_MODE, + IOCTL_GET_NIGHT_MODE, + IOCTL_LEPTON_GET_WIDTH, + IOCTL_LEPTON_GET_HEIGHT, + IOCTL_LEPTON_GET_RADIOMETRY, + IOCTL_LEPTON_GET_REFRESH, + IOCTL_LEPTON_GET_RESOLUTION, + IOCTL_LEPTON_RUN_COMMAND, + IOCTL_LEPTON_SET_ATTRIBUTE, + IOCTL_LEPTON_GET_ATTRIBUTE, + IOCTL_LEPTON_GET_FPA_TEMPERATURE, + IOCTL_LEPTON_GET_AUX_TEMPERATURE, + IOCTL_LEPTON_SET_MEASUREMENT_MODE, + IOCTL_LEPTON_GET_MEASUREMENT_MODE, + IOCTL_LEPTON_SET_MEASUREMENT_RANGE, + IOCTL_LEPTON_GET_MEASUREMENT_RANGE, + IOCTL_HIMAX_MD_ENABLE, + IOCTL_HIMAX_MD_CLEAR, + IOCTL_HIMAX_MD_WINDOW, + IOCTL_HIMAX_MD_THRESHOLD, + IOCTL_HIMAX_OSC_ENABLE, +} ioctl_t; + +typedef enum { + SENSOR_ERROR_NO_ERROR = 0, + SENSOR_ERROR_CTL_FAILED = -1, + SENSOR_ERROR_CTL_UNSUPPORTED = -2, + SENSOR_ERROR_ISC_UNDETECTED = -3, + SENSOR_ERROR_ISC_UNSUPPORTED = -4, + SENSOR_ERROR_ISC_INIT_FAILED = -5, + SENSOR_ERROR_TIM_INIT_FAILED = -6, + SENSOR_ERROR_DMA_INIT_FAILED = -7, + SENSOR_ERROR_DCMI_INIT_FAILED = -8, + SENSOR_ERROR_IO_ERROR = -9, + SENSOR_ERROR_CAPTURE_FAILED = -10, + SENSOR_ERROR_CAPTURE_TIMEOUT = -11, + SENSOR_ERROR_INVALID_FRAMESIZE = -12, + SENSOR_ERROR_INVALID_PIXFORMAT = -13, + SENSOR_ERROR_INVALID_WINDOW = -14, + SENSOR_ERROR_INVALID_FRAMERATE = -15, + SENSOR_ERROR_INVALID_ARGUMENT = -16, + SENSOR_ERROR_PIXFORMAT_UNSUPPORTED = -17, + SENSOR_ERROR_FRAMEBUFFER_ERROR = -18, + SENSOR_ERROR_FRAMEBUFFER_OVERFLOW = -19, + SENSOR_ERROR_JPEG_OVERFLOW = -20, +} sensor_error_t; + +// Bayer patterns. +// NOTE: These must match the Bayer subformats in imlib.h +#define SENSOR_HW_FLAGS_BAYER_BGGR (SUBFORMAT_ID_BGGR) +#define SENSOR_HW_FLAGS_BAYER_GBRG (SUBFORMAT_ID_GBRG) +#define SENSOR_HW_FLAGS_BAYER_GRBG (SUBFORMAT_ID_GRBG) +#define SENSOR_HW_FLAGS_BAYER_RGGB (SUBFORMAT_ID_RGGB) +#define SENSOR_HW_FLAGS_YUV422 (SUBFORMAT_ID_YUV422) +#define SENSOR_HW_FLAGS_YVU422 (SUBFORMAT_ID_YVU422) + +typedef void (*vsync_cb_t) (uint32_t vsync); +typedef void (*frame_cb_t) (); + +typedef struct _sensor sensor_t; +typedef struct _sensor { + union { + uint8_t chip_id; // Sensor ID. + uint16_t chip_id_w; // Sensor ID 16 bits. + }; + uint8_t slv_addr; // Sensor I2C slave address. + + // Hardware flags (clock polarities, hw capabilities etc..) + struct { + uint32_t vsync : 1; // Vertical sync polarity. + uint32_t hsync : 1; // Horizontal sync polarity. + uint32_t pixck : 1; // Pixel clock edge. + uint32_t fsync : 1; // Hardware frame sync. + uint32_t jpege : 1; // Hardware jpeg encoder. + uint32_t jpeg_mode : 3; // JPEG mode. + uint32_t gs_bpp : 2; // Grayscale bytes per pixel output. + uint32_t rgb_swap : 1; // Byte-swap 2BPP RGB formats after capture. + uint32_t yuv_swap : 1; // Byte-swap 2BPP YUV formats after capture. + uint32_t bayer : 3; // Bayer/CFA pattern. + uint32_t yuv_order : 1; // YUV/YVU order. + uint32_t blc_size : 4; // Number of black level calibration registers. + } hw_flags; + + const uint16_t *color_palette; // Color palette used for color lookup. + bool disable_delays; // Set to true to disable all sensor settling time delays. + bool disable_full_flush; // Turn off default frame buffer flush policy when full. + + vsync_cb_t vsync_callback; // VSYNC callback. + frame_cb_t frame_callback; // Frame callback. + polarity_t pwdn_pol; // PWDN polarity (TODO move to hw_flags) + polarity_t reset_pol; // Reset polarity (TODO move to hw_flags) + + // Sensor state + sde_t sde; // Special digital effects + pixformat_t pixformat; // Pixel format + framesize_t framesize; // Frame size + int framerate; // Frame rate + uint32_t last_frame_ms; // Last sampled frame timestamp in milliseconds. + bool last_frame_ms_valid; // Last sampled frame timestamp in milliseconds valid. + gainceiling_t gainceiling; // AGC gainceiling + bool hmirror; // Horizontal Mirror + bool vflip; // Vertical Flip + bool transpose; // Transpose Image + bool auto_rotation; // Rotate Image Automatically + bool detected; // Set to true when the sensor is initialized. + + omv_i2c_t i2c_bus; // SCCB/I2C bus. + + // Sensor function pointers + int (*reset) (sensor_t *sensor); + int (*sleep) (sensor_t *sensor, int enable); + int (*read_reg) (sensor_t *sensor, uint16_t reg_addr); + int (*write_reg) (sensor_t *sensor, uint16_t reg_addr, uint16_t reg_data); + int (*set_pixformat) (sensor_t *sensor, pixformat_t pixformat); + int (*set_framesize) (sensor_t *sensor, framesize_t framesize); + int (*set_framerate) (sensor_t *sensor, int framerate); + int (*set_contrast) (sensor_t *sensor, int level); + int (*set_brightness) (sensor_t *sensor, int level); + int (*set_saturation) (sensor_t *sensor, int level); + int (*set_gainceiling) (sensor_t *sensor, gainceiling_t gainceiling); + int (*set_quality) (sensor_t *sensor, int quality); + int (*set_colorbar) (sensor_t *sensor, int enable); + int (*set_auto_gain) (sensor_t *sensor, int enable, float gain_db, float gain_db_ceiling); + int (*get_gain_db) (sensor_t *sensor, float *gain_db); + int (*set_auto_exposure) (sensor_t *sensor, int enable, int exposure_us); + int (*get_exposure_us) (sensor_t *sensor, int *exposure_us); + int (*set_auto_whitebal) (sensor_t *sensor, int enable, float r_gain_db, float g_gain_db, float b_gain_db); + int (*get_rgb_gain_db) (sensor_t *sensor, float *r_gain_db, float *g_gain_db, float *b_gain_db); + int (*set_auto_blc) (sensor_t *sensor, int enable, int *regs); + int (*get_blc_regs) (sensor_t *sensor, int *regs); + int (*set_hmirror) (sensor_t *sensor, int enable); + int (*set_vflip) (sensor_t *sensor, int enable); + int (*set_special_effect) (sensor_t *sensor, sde_t sde); + int (*set_lens_correction) (sensor_t *sensor, int enable, int radi, int coef); + int (*ioctl) (sensor_t *sensor, int request, va_list ap); + int (*snapshot) (sensor_t *sensor, image_t *image, uint32_t flags); +} sensor_t; + +extern sensor_t sensor; + +// Resolution table +extern const int resolution[][2]; + +// Initialize the sensor state. +void sensor_init0(); + +// Initialize the sensor hardware and probe the image sensor. +int sensor_init(); + +// Detect and initialize the image sensor. +int sensor_probe_init(uint32_t bus_id, uint32_t bus_speed); + +// Configure DCMI hardware interface. +int sensor_dcmi_config(uint32_t pixformat); + +// Abort frame capture and disable IRQs, DMA etc.. +int sensor_abort(); + +// Reset the sensor to its default state. +int sensor_reset(); + +// Return sensor PID. +int sensor_get_id(); + +// Returns the xclk freq in hz. +uint32_t sensor_get_xclk_frequency(); + +// Returns the xclk freq in hz. +int sensor_set_xclk_frequency(uint32_t frequency); + +// Return true if the sensor was detected and initialized. +bool sensor_is_detected(); + +// Sleep mode. +int sensor_sleep(int enable); + +// Shutdown mode. +int sensor_shutdown(int enable); + +// Read a sensor register. +int sensor_read_reg(uint16_t reg_addr); + +// Write a sensor register. +int sensor_write_reg(uint16_t reg_addr, uint16_t reg_data); + +// Set the sensor pixel format. +int sensor_set_pixformat(pixformat_t pixformat); + +// Set the sensor frame size. +int sensor_set_framesize(framesize_t framesize); + +// Set the sensor frame rate. +int sensor_set_framerate(int framerate); + +// Return the number of bytes per pixel to read from the image sensor. +uint32_t sensor_get_src_bpp(); + +// Return the number of bytes per pixel to write to memory. +uint32_t sensor_get_dst_bpp(); + +// Returns true if a crop is being applied to the frame buffer. +bool sensor_get_cropped(); + +// Set window size. +int sensor_set_windowing(int x, int y, int w, int h); + +// Set the sensor contrast level (from -3 to +3). +int sensor_set_contrast(int level); + +// Set the sensor brightness level (from -3 to +3). +int sensor_set_brightness(int level); + +// Set the sensor saturation level (from -3 to +3). +int sensor_set_saturation(int level); + +// Set the sensor AGC gain ceiling. +// Note: This function has no effect when AGC (Automatic Gain Control) is disabled. +int sensor_set_gainceiling(gainceiling_t gainceiling); + +// Set the quantization scale factor, controls JPEG quality (quality 0-255). +int sensor_set_quality(int qs); + +// Enable/disable the colorbar mode. +int sensor_set_colorbar(int enable); + +// Enable auto gain or set value manually. +int sensor_set_auto_gain(int enable, float gain_db, float gain_db_ceiling); + +// Get the gain value. +int sensor_get_gain_db(float *gain_db); + +// Enable auto exposure or set value manually. +int sensor_set_auto_exposure(int enable, int exposure_us); + +// Get the exposure value. +int sensor_get_exposure_us(int *get_exposure_us); + +// Enable auto white balance or set value manually. +int sensor_set_auto_whitebal(int enable, float r_gain_db, float g_gain_db, float b_gain_db); + +// Get the rgb gain values. +int sensor_get_rgb_gain_db(float *r_gain_db, float *g_gain_db, float *b_gain_db); + +// Enable auto blc (black level calibration) or set from previous calibration. +int sensor_set_auto_blc(int enable, int *regs); + +// Get black level valibration register values. +int sensor_get_blc_regs(int *regs); + +// Enable/disable the hmirror mode. +int sensor_set_hmirror(int enable); + +// Get hmirror status. +bool sensor_get_hmirror(); + +// Enable/disable the vflip mode. +int sensor_set_vflip(int enable); + +// Get vflip status. +bool sensor_get_vflip(); + +// Enable/disable the transpose mode. +int sensor_set_transpose(bool enable); + +// Get transpose mode state. +bool sensor_get_transpose(); + +// Enable/disable the auto rotation mode. +int sensor_set_auto_rotation(bool enable); + +// Get transpose mode state. +bool sensor_get_auto_rotation(); + +// Set the number of virtual frame buffers. +int sensor_set_framebuffers(int count); + +// Set special digital effects (SDE). +int sensor_set_special_effect(sde_t sde); + +// Set lens shading correction +int sensor_set_lens_correction(int enable, int radi, int coef); + +// IOCTL function +int sensor_ioctl(int request, ...); + +// Set vsync callback function. +int sensor_set_vsync_callback(vsync_cb_t vsync_cb); + +// Set frame callback function. +int sensor_set_frame_callback(frame_cb_t vsync_cb); + +// Set color palette +int sensor_set_color_palette(const uint16_t *color_palette); + +// Get color palette +const uint16_t *sensor_get_color_palette(); + +// Return true if the current frame size/format fits in RAM. +int sensor_check_framebuffer_size(); + +// Auto-crop frame buffer until it fits in RAM (may switch pixel format to BAYER). +int sensor_auto_crop_framebuffer(); + +// Default snapshot function. +int sensor_snapshot(sensor_t *sensor, image_t *image, uint32_t flags); + +// Convert sensor error codes to strings. +const char *sensor_strerror(int error); +#endif /* __SENSOR_H__ */ diff --git a/components/3rd_party/omv/omv/common/sensor_utils.c b/components/3rd_party/omv/omv/common/sensor_utils.c new file mode 100644 index 00000000..becbc6d3 --- /dev/null +++ b/components/3rd_party/omv/omv/common/sensor_utils.c @@ -0,0 +1,1270 @@ +/* + * 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 contains image sensor driver utility functions and some default (weak) + * implementations of common functions that can be replaced by port-specific drivers. + */ +#if MICROPY_PY_SENSOR +#include +#include +#include +#include "py/mphal.h" +#include "sensor.h" +#include "ov2640.h" +#include "ov5640.h" +#include "ov7725.h" +#include "ov7670.h" +#include "ov7690.h" +#include "ov9650.h" +#include "mt9v0xx.h" +#include "mt9m114.h" +#include "lepton.h" +#include "hm01b0.h" +#include "hm0360.h" +#include "paj6100.h" +#include "frogeye2020.h" +#include "gc2145.h" +#include "framebuffer.h" +#include "omv_boardconfig.h" +#include "omv_gpio.h" +#include "omv_i2c.h" + +#ifndef OMV_ISC_MAX_DEVICES +#define OMV_ISC_MAX_DEVICES (5) +#endif + +#ifndef __weak +#define __weak __attribute__((weak)) +#endif + +// Sensor frame size/resolution table. +const int resolution[][2] = { + {0, 0 }, + // C/SIF Resolutions + {88, 72 }, /* QQCIF */ + {176, 144 }, /* QCIF */ + {352, 288 }, /* CIF */ + {88, 60 }, /* QQSIF */ + {176, 120 }, /* QSIF */ + {352, 240 }, /* SIF */ + // VGA Resolutions + {40, 30 }, /* QQQQVGA */ + {80, 60 }, /* QQQVGA */ + {160, 120 }, /* QQVGA */ + {320, 240 }, /* QVGA */ + {640, 480 }, /* VGA */ + {30, 20 }, /* HQQQQVGA */ + {60, 40 }, /* HQQQVGA */ + {120, 80 }, /* HQQVGA */ + {240, 160 }, /* HQVGA */ + {480, 320 }, /* HVGA */ + // FFT Resolutions + {64, 32 }, /* 64x32 */ + {64, 64 }, /* 64x64 */ + {128, 64 }, /* 128x64 */ + {128, 128 }, /* 128x128 */ + // Himax Resolutions + {160, 160 }, /* 160x160 */ + {320, 320 }, /* 320x320 */ + // Other + {128, 160 }, /* LCD */ + {128, 160 }, /* QQVGA2 */ + {720, 480 }, /* WVGA */ + {752, 480 }, /* WVGA2 */ + {800, 600 }, /* SVGA */ + {1024, 768 }, /* XGA */ + {1280, 768 }, /* WXGA */ + {1280, 1024}, /* SXGA */ + {1280, 960 }, /* SXGAM */ + {1600, 1200}, /* UXGA */ + {1280, 720 }, /* HD */ + {1920, 1080}, /* FHD */ + {2560, 1440}, /* QHD */ + {2048, 1536}, /* QXGA */ + {2560, 1600}, /* WQXGA */ + {2592, 1944}, /* WQXGA2 */ +}; + +__weak void sensor_init0() { + // Reset the sensor state + memset(&sensor, 0, sizeof(sensor_t)); +} + +__weak int sensor_init() { + // Reset the sensor state + memset(&sensor, 0, sizeof(sensor_t)); + return SENSOR_ERROR_CTL_UNSUPPORTED; +} + +__weak int sensor_abort() { + return SENSOR_ERROR_CTL_UNSUPPORTED; +} + +__weak int sensor_reset() { + // Disable any ongoing frame capture. + sensor_abort(); + + // Reset the sensor state + sensor.sde = 0; + sensor.pixformat = 0; + sensor.framesize = 0; + sensor.framerate = 0; + sensor.last_frame_ms = 0; + sensor.last_frame_ms_valid = false; + sensor.gainceiling = 0; + sensor.hmirror = false; + sensor.vflip = false; + sensor.transpose = false; + #if MICROPY_PY_IMU + sensor.auto_rotation = (sensor.chip_id == OV7690_ID); + #else + sensor.auto_rotation = false; + #endif // MICROPY_PY_IMU + sensor.vsync_callback = NULL; + sensor.frame_callback = NULL; + + // Reset default color palette. + sensor.color_palette = rainbow_table; + + sensor.disable_full_flush = false; + + // Restore shutdown state on reset. + sensor_shutdown(false); + + // Disable the bus before reset. + omv_i2c_enable(&sensor.i2c_bus, false); + + #if defined(DCMI_RESET_PIN) + // Hard-reset the sensor + if (sensor.reset_pol == ACTIVE_HIGH) { + omv_gpio_write(DCMI_RESET_PIN, 1); + mp_hal_delay_ms(10); + omv_gpio_write(DCMI_RESET_PIN, 0); + } else { + omv_gpio_write(DCMI_RESET_PIN, 0); + mp_hal_delay_ms(10); + omv_gpio_write(DCMI_RESET_PIN, 1); + } + #endif + + mp_hal_delay_ms(20); + + // Re-enable the bus. + omv_i2c_enable(&sensor.i2c_bus, true); + + // Call sensor-specific reset function + if (sensor.reset != NULL + && sensor.reset(&sensor) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + // Reset framebuffers + framebuffer_reset_buffers(); + + return 0; +} + +static int sensor_detect() { + uint8_t devs_list[OMV_ISC_MAX_DEVICES]; + int n_devs = omv_i2c_scan(&sensor.i2c_bus, devs_list, OMV_ARRAY_SIZE(devs_list)); + + for (int i = 0; i < OMV_MIN(n_devs, OMV_ISC_MAX_DEVICES); i++) { + uint8_t slv_addr = devs_list[i]; + switch (slv_addr) { + #if (OMV_ENABLE_OV2640 == 1) + case OV2640_SLV_ADDR: // Or OV9650. + omv_i2c_readb(&sensor.i2c_bus, slv_addr, OV_CHIP_ID, &sensor.chip_id); + return slv_addr; + #endif // (OMV_ENABLE_OV2640 == 1) + + #if (OMV_ENABLE_OV5640 == 1) + case OV5640_SLV_ADDR: + omv_i2c_readb2(&sensor.i2c_bus, slv_addr, OV5640_CHIP_ID, &sensor.chip_id); + return slv_addr; + #endif // (OMV_ENABLE_OV5640 == 1) + + #if (OMV_ENABLE_OV7725 == 1) || (OMV_ENABLE_OV7670 == 1) || (OMV_ENABLE_OV7690 == 1) + case OV7725_SLV_ADDR: // Or OV7690 or OV7670. + omv_i2c_readb(&sensor.i2c_bus, slv_addr, OV_CHIP_ID, &sensor.chip_id); + return slv_addr; + #endif //(OMV_ENABLE_OV7725 == 1) || (OMV_ENABLE_OV7670 == 1) || (OMV_ENABLE_OV7690 == 1) + + #if (OMV_ENABLE_MT9V0XX == 1) + case MT9V0XX_SLV_ADDR: + omv_i2c_readw(&sensor.i2c_bus, slv_addr, ON_CHIP_ID, &sensor.chip_id_w); + return slv_addr; + #endif //(OMV_ENABLE_MT9V0XX == 1) + + #if (OMV_ENABLE_MT9M114 == 1) + case MT9M114_SLV_ADDR: + omv_i2c_readw2(&sensor.i2c_bus, slv_addr, ON_CHIP_ID, &sensor.chip_id_w); + return slv_addr; + #endif // (OMV_ENABLE_MT9M114 == 1) + + #if (OMV_ENABLE_LEPTON == 1) + case LEPTON_SLV_ADDR: + sensor.chip_id = LEPTON_ID; + return slv_addr; + #endif // (OMV_ENABLE_LEPTON == 1) + + #if (OMV_ENABLE_HM01B0 == 1) || (OMV_ENABLE_HM0360 == 1) + case HM0XX0_SLV_ADDR: + omv_i2c_readb2(&sensor.i2c_bus, slv_addr, HIMAX_CHIP_ID, &sensor.chip_id); + return slv_addr; + #endif // (OMV_ENABLE_HM01B0 == 1) || (OMV_ENABLE_HM0360 == 1) + + #if (OMV_ENABLE_GC2145 == 1) + case GC2145_SLV_ADDR: + omv_i2c_readb(&sensor.i2c_bus, slv_addr, GC_CHIP_ID, &sensor.chip_id); + return slv_addr; + #endif //(OMV_ENABLE_GC2145 == 1) + + #if (OMV_ENABLE_FROGEYE2020 == 1) + case FROGEYE2020_SLV_ADDR: + sensor.chip_id_w = FROGEYE2020_ID; + return slv_addr; + #endif // (OMV_ENABLE_FROGEYE2020 == 1) + } + } + + return 0; +} + +int sensor_probe_init(uint32_t bus_id, uint32_t bus_speed) { + int init_ret = 0; + + #if defined(DCMI_POWER_PIN) + sensor.pwdn_pol = ACTIVE_HIGH; + // Do a power cycle + omv_gpio_write(DCMI_POWER_PIN, 1); + mp_hal_delay_ms(10); + + omv_gpio_write(DCMI_POWER_PIN, 0); + mp_hal_delay_ms(10); + #endif + + #if defined(DCMI_RESET_PIN) + sensor.reset_pol = ACTIVE_HIGH; + // Reset the sensor + omv_gpio_write(DCMI_RESET_PIN, 1); + mp_hal_delay_ms(10); + + omv_gpio_write(DCMI_RESET_PIN, 0); + mp_hal_delay_ms(10); + #endif + + // Initialize the camera bus. + omv_i2c_init(&sensor.i2c_bus, bus_id, bus_speed); + mp_hal_delay_ms(10); + + // Scan the bus multiple times using different reset and power-down + // polarities, until a supported sensor is detected. + if ((sensor.slv_addr = sensor_detect()) == 0) { + // No devices were detected, try scanning the bus + // again with different reset/power-down polarities. + #if defined(DCMI_RESET_PIN) + sensor.reset_pol = ACTIVE_LOW; + omv_gpio_write(DCMI_RESET_PIN, 1); + mp_hal_delay_ms(10); + #endif + + if ((sensor.slv_addr = sensor_detect()) == 0) { + #if defined(DCMI_POWER_PIN) + sensor.pwdn_pol = ACTIVE_LOW; + omv_gpio_write(DCMI_POWER_PIN, 1); + mp_hal_delay_ms(10); + #endif + + if ((sensor.slv_addr = sensor_detect()) == 0) { + #if defined(DCMI_RESET_PIN) + sensor.reset_pol = ACTIVE_HIGH; + omv_gpio_write(DCMI_RESET_PIN, 0); + mp_hal_delay_ms(10); + #endif + sensor.slv_addr = sensor_detect(); + } + } + + // If no devices were detected on the I2C bus, try the SPI bus. + if (sensor.slv_addr == 0) { + if (0) { + #if (OMV_ENABLE_PAJ6100 == 1) + } else if (paj6100_detect(&sensor)) { + // Found PixArt PAJ6100 + sensor.chip_id_w = PAJ6100_ID; + sensor.pwdn_pol = ACTIVE_LOW; + sensor.reset_pol = ACTIVE_LOW; + #endif + } else { + return SENSOR_ERROR_ISC_UNDETECTED; + } + } + } + + // A supported sensor was detected, try to initialize it. + switch (sensor.chip_id_w) { + #if (OMV_ENABLE_OV2640 == 1) + case OV2640_ID: + if (sensor_set_xclk_frequency(OV2640_XCLK_FREQ) != 0) { + return SENSOR_ERROR_TIM_INIT_FAILED; + } + init_ret = ov2640_init(&sensor); + break; + #endif // (OMV_ENABLE_OV2640 == 1) + + #if (OMV_ENABLE_OV5640 == 1) + case OV5640_ID: { + int freq = OMV_OV5640_XCLK_FREQ; + #if (OMV_OV5640_REV_Y_CHECK == 1) + if (HAL_GetREVID() < 0x2003) { + // Is this REV Y? + freq = OMV_OV5640_REV_Y_FREQ; + } + #endif + if (sensor_set_xclk_frequency(freq) != 0) { + return SENSOR_ERROR_TIM_INIT_FAILED; + } + init_ret = ov5640_init(&sensor); + break; + } + #endif // (OMV_ENABLE_OV5640 == 1) + + #if (OMV_ENABLE_OV7670 == 1) + case OV7670_ID: + if (sensor_set_xclk_frequency(OV7670_XCLK_FREQ) != 0) { + return SENSOR_ERROR_TIM_INIT_FAILED; + } + init_ret = ov7670_init(&sensor); + break; + #endif // (OMV_ENABLE_OV7670 == 1) + + #if (OMV_ENABLE_OV7690 == 1) + case OV7690_ID: + if (sensor_set_xclk_frequency(OV7690_XCLK_FREQ) != 0) { + return SENSOR_ERROR_TIM_INIT_FAILED; + } + init_ret = ov7690_init(&sensor); + break; + #endif // (OMV_ENABLE_OV7690 == 1) + + #if (OMV_ENABLE_OV7725 == 1) + case OV7725_ID: + init_ret = ov7725_init(&sensor); + break; + #endif // (OMV_ENABLE_OV7725 == 1) + + #if (OMV_ENABLE_OV9650 == 1) + case OV9650_ID: + init_ret = ov9650_init(&sensor); + break; + #endif // (OMV_ENABLE_OV9650 == 1) + + #if (OMV_ENABLE_MT9V0XX == 1) + case MT9V0X2_ID_V_1: + case MT9V0X2_ID_V_2: + // Force old versions to the newest. + sensor.chip_id_w = MT9V0X2_ID; + case MT9V0X2_ID: + case MT9V0X4_ID: + if (sensor_set_xclk_frequency(MT9V0XX_XCLK_FREQ) != 0) { + return SENSOR_ERROR_TIM_INIT_FAILED; + } + init_ret = mt9v0xx_init(&sensor); + break; + #endif //(OMV_ENABLE_MT9V0XX == 1) + + #if (OMV_ENABLE_MT9M114 == 1) + case MT9M114_ID: + if (sensor_set_xclk_frequency(MT9M114_XCLK_FREQ) != 0) { + return SENSOR_ERROR_TIM_INIT_FAILED; + } + init_ret = mt9m114_init(&sensor); + break; + #endif //(OMV_ENABLE_MT9M114 == 1) + + #if (OMV_ENABLE_LEPTON == 1) + case LEPTON_ID: + if (sensor_set_xclk_frequency(LEPTON_XCLK_FREQ) != 0) { + return SENSOR_ERROR_TIM_INIT_FAILED; + } + init_ret = lepton_init(&sensor); + break; + #endif // (OMV_ENABLE_LEPTON == 1) + + #if (OMV_ENABLE_HM01B0 == 1) + case HM01B0_ID: + if (sensor_set_xclk_frequency(HM01B0_XCLK_FREQ) != 0) { + return SENSOR_ERROR_TIM_INIT_FAILED; + } + init_ret = hm01b0_init(&sensor); + break; + #endif //(OMV_ENABLE_HM01B0 == 1) + + #if (OMV_ENABLE_HM0360 == 1) + case HM0360_ID: + if (sensor_set_xclk_frequency(HM0360_XCLK_FREQ) != 0) { + return SENSOR_ERROR_TIM_INIT_FAILED; + } + init_ret = hm0360_init(&sensor); + break; + #endif //(OMV_ENABLE_HM0360 == 1) + + #if (OMV_ENABLE_GC2145 == 1) + case GC2145_ID: + if (sensor_set_xclk_frequency(GC2145_XCLK_FREQ) != 0) { + return SENSOR_ERROR_TIM_INIT_FAILED; + } + init_ret = gc2145_init(&sensor); + break; + #endif //(OMV_ENABLE_GC2145 == 1) + + #if (OMV_ENABLE_PAJ6100 == 1) + case PAJ6100_ID: + if (sensor_set_xclk_frequency(PAJ6100_XCLK_FREQ) != 0) { + return SENSOR_ERROR_TIM_INIT_FAILED; + } + init_ret = paj6100_init(&sensor); + break; + #endif // (OMV_ENABLE_PAJ6100 == 1) + + #if (OMV_ENABLE_FROGEYE2020 == 1) + case FROGEYE2020_ID: + if (sensor_set_xclk_frequency(FROGEYE2020_XCLK_FREQ) != 0) { + return SENSOR_ERROR_TIM_INIT_FAILED; + } + init_ret = frogeye2020_init(&sensor); + break; + #endif // (OMV_ENABLE_FROGEYE2020 == 1) + + default: + return SENSOR_ERROR_ISC_UNSUPPORTED; + break; + } + + if (init_ret != 0) { + // Sensor init failed. + return SENSOR_ERROR_ISC_INIT_FAILED; + } + + return 0; +} + +__weak int sensor_get_id() { + return sensor.chip_id_w; +} + +__weak uint32_t sensor_get_xclk_frequency() { + return SENSOR_ERROR_CTL_UNSUPPORTED; +} + +__weak int sensor_set_xclk_frequency(uint32_t frequency) { + return SENSOR_ERROR_CTL_UNSUPPORTED; +} + +__weak bool sensor_is_detected() { + return sensor.detected; +} + +__weak int sensor_sleep(int enable) { + // Disable any ongoing frame capture. + sensor_abort(); + + // Check if the control is supported. + if (sensor.sleep == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.sleep(&sensor, enable) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + return 0; +} + +__weak int sensor_shutdown(int enable) { + int ret = 0; + + // Disable any ongoing frame capture. + sensor_abort(); + + #if defined(DCMI_POWER_PIN) + if (enable) { + if (sensor.pwdn_pol == ACTIVE_HIGH) { + omv_gpio_write(DCMI_POWER_PIN, 1); + } else { + omv_gpio_write(DCMI_POWER_PIN, 0); + } + } else { + if (sensor.pwdn_pol == ACTIVE_HIGH) { + omv_gpio_write(DCMI_POWER_PIN, 0); + } else { + omv_gpio_write(DCMI_POWER_PIN, 1); + } + } + #endif + + mp_hal_delay_ms(10); + + return ret; +} + +__weak int sensor_read_reg(uint16_t reg_addr) { + int ret; + + // Check if the control is supported. + if (sensor.read_reg == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if ((ret = sensor.read_reg(&sensor, reg_addr)) == -1) { + return SENSOR_ERROR_IO_ERROR; + } + + return ret; +} + +__weak int sensor_write_reg(uint16_t reg_addr, uint16_t reg_data) { + // Check if the control is supported. + if (sensor.write_reg == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.write_reg(&sensor, reg_addr, reg_data) == -1) { + return SENSOR_ERROR_IO_ERROR; + } + + return 0; +} + +__weak int sensor_set_pixformat(pixformat_t pixformat) { + // Check if the value has changed. + if (sensor.pixformat == pixformat) { + return 0; + } + + // Some sensor drivers automatically switch to BAYER to reduce the frame size if it does not fit in RAM. + // If the current format is BAYER (1BPP), and the target format is color and (2BPP), and the frame does not + // fit in RAM it will just be switched back again to BAYER, so we keep the current format unchanged. + uint32_t size = framebuffer_get_buffer_size(); + if ((sensor.pixformat == PIXFORMAT_BAYER) + && ((pixformat == PIXFORMAT_RGB565) || (pixformat == PIXFORMAT_YUV422)) + && (MAIN_FB()->u * MAIN_FB()->v * 2 > size) + && (MAIN_FB()->u * MAIN_FB()->v * 1 <= size)) { + return 0; + } + + // Cropping and transposing (and thus auto rotation) don't work in JPEG mode. + if ((pixformat == PIXFORMAT_JPEG) + && (sensor_get_cropped() || sensor.transpose || sensor.auto_rotation)) { + return SENSOR_ERROR_PIXFORMAT_UNSUPPORTED; + } + + // Disable any ongoing frame capture. + sensor_abort(); + + // Flush previous frame. + framebuffer_update_jpeg_buffer(); + + // Check if the control is supported. + if (sensor.set_pixformat == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.set_pixformat(&sensor, pixformat) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + if (!sensor.disable_delays) { + mp_hal_delay_ms(100); // wait for the camera to settle + } + + // Set pixel format + sensor.pixformat = pixformat; + + // Skip the first frame. + MAIN_FB()->pixfmt = PIXFORMAT_INVALID; + + // Pickout a good buffer count for the user. + framebuffer_auto_adjust_buffers(); + + // Reconfigure the DCMI if needed. + return sensor_dcmi_config(pixformat); +} + +__weak int sensor_set_framesize(framesize_t framesize) { + if (sensor.framesize == framesize) { + // No change + return 0; + } + + // Disable any ongoing frame capture. + sensor_abort(); + + // Flush previous frame. + framebuffer_update_jpeg_buffer(); + + // Call the sensor specific function + if (sensor.set_framesize == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + if (sensor.set_framesize(&sensor, framesize) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + if (!sensor.disable_delays) { + mp_hal_delay_ms(100); // wait for the camera to settle + } + + // Set framebuffer size + sensor.framesize = framesize; + + // Skip the first frame. + MAIN_FB()->pixfmt = PIXFORMAT_INVALID; + + // Set MAIN FB x offset, y offset, width, height, backup width, and backup height. + MAIN_FB()->x = 0; + MAIN_FB()->y = 0; + MAIN_FB()->w = MAIN_FB()->u = resolution[framesize][0]; + MAIN_FB()->h = MAIN_FB()->v = resolution[framesize][1]; + + // Pickout a good buffer count for the user. + framebuffer_auto_adjust_buffers(); + + return 0; +} + +__weak int sensor_set_framerate(int framerate) { + if (sensor.framerate == framerate) { + // No change + return 0; + } + + if (framerate < 0) { + return SENSOR_ERROR_INVALID_ARGUMENT; + } + + // If the sensor implements framerate control use it. + if (sensor.set_framerate != NULL + && sensor.set_framerate(&sensor, framerate) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } else { + // Otherwise use software framerate control. + sensor.framerate = framerate; + } + return 0; +} + +__weak bool sensor_get_cropped() { + if (sensor.framesize != FRAMESIZE_INVALID) { + return (MAIN_FB()->x != 0) // should be zero if not cropped. + || (MAIN_FB()->y != 0) // should be zero if not cropped. + || (MAIN_FB()->u != resolution[sensor.framesize][0]) // should be equal to the resolution if not cropped. + || (MAIN_FB()->v != resolution[sensor.framesize][1]); // should be equal to the resolution if not cropped. + } + return false; +} + + +__weak uint32_t sensor_get_src_bpp() { + switch (sensor.pixformat) { + case PIXFORMAT_GRAYSCALE: + return sensor.hw_flags.gs_bpp; + case PIXFORMAT_RGB565: + case PIXFORMAT_YUV422: + return 2; + case PIXFORMAT_BAYER: + case PIXFORMAT_JPEG: + return 1; + default: + return 0; + } +} + +__weak uint32_t sensor_get_dst_bpp() { + switch (sensor.pixformat) { + case PIXFORMAT_GRAYSCALE: + case PIXFORMAT_BAYER: + return 1; + case PIXFORMAT_RGB565: + case PIXFORMAT_YUV422: + return 2; + default: + return 0; + } +} + +__weak int sensor_set_windowing(int x, int y, int w, int h) { + // Check if the value has changed. + if ((MAIN_FB()->x == x) && (MAIN_FB()->y == y) && + (MAIN_FB()->u == w) && (MAIN_FB()->v == h)) { + return 0; + } + + if (sensor.pixformat == PIXFORMAT_JPEG) { + return SENSOR_ERROR_PIXFORMAT_UNSUPPORTED; + } + + // Disable any ongoing frame capture. + sensor_abort(); + + // Flush previous frame. + framebuffer_update_jpeg_buffer(); + + // Skip the first frame. + MAIN_FB()->pixfmt = PIXFORMAT_INVALID; + + MAIN_FB()->x = x; + MAIN_FB()->y = y; + MAIN_FB()->w = MAIN_FB()->u = w; + MAIN_FB()->h = MAIN_FB()->v = h; + + // Pickout a good buffer count for the user. + framebuffer_auto_adjust_buffers(); + + return 0; +} + +__weak int sensor_set_contrast(int level) { + // Check if the control is supported. + if (sensor.set_contrast == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.set_contrast(&sensor, level) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + return 0; +} + +__weak int sensor_set_brightness(int level) { + // Check if the control is supported. + if (sensor.set_brightness == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.set_brightness(&sensor, level) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + return 0; +} + +__weak int sensor_set_saturation(int level) { + // Check if the control is supported. + if (sensor.set_saturation == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.set_saturation(&sensor, level) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + return 0; +} + +__weak int sensor_set_gainceiling(gainceiling_t gainceiling) { + // Check if the value has changed. + if (sensor.gainceiling == gainceiling) { + return 0; + } + + // Check if the control is supported. + if (sensor.set_gainceiling == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.set_gainceiling(&sensor, gainceiling) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + // Set the new control value. + sensor.gainceiling = gainceiling; + + return 0; +} + +__weak int sensor_set_quality(int qs) { + // Check if the control is supported. + if (sensor.set_quality == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.set_quality(&sensor, qs) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + return 0; +} + +__weak int sensor_set_colorbar(int enable) { + // Check if the control is supported. + if (sensor.set_colorbar == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.set_colorbar(&sensor, enable) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + return 0; +} + +__weak int sensor_set_auto_gain(int enable, float gain_db, float gain_db_ceiling) { + // Check if the control is supported. + if (sensor.set_auto_gain == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.set_auto_gain(&sensor, enable, gain_db, gain_db_ceiling) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + return 0; +} + +__weak int sensor_get_gain_db(float *gain_db) { + // Check if the control is supported. + if (sensor.get_gain_db == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.get_gain_db(&sensor, gain_db) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + return 0; +} + +__weak int sensor_set_auto_exposure(int enable, int exposure_us) { + // Check if the control is supported. + if (sensor.set_auto_exposure == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.set_auto_exposure(&sensor, enable, exposure_us) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + return 0; +} + +__weak int sensor_get_exposure_us(int *exposure_us) { + // Check if the control is supported. + if (sensor.get_exposure_us == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.get_exposure_us(&sensor, exposure_us) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + return 0; +} + +__weak int sensor_set_auto_whitebal(int enable, float r_gain_db, float g_gain_db, float b_gain_db) { + // Check if the control is supported. + if (sensor.set_auto_whitebal == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.set_auto_whitebal(&sensor, enable, r_gain_db, g_gain_db, b_gain_db) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + return 0; +} + +__weak int sensor_get_rgb_gain_db(float *r_gain_db, float *g_gain_db, float *b_gain_db) { + // Check if the control is supported. + if (sensor.get_rgb_gain_db == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.get_rgb_gain_db(&sensor, r_gain_db, g_gain_db, b_gain_db) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + return 0; +} + +__weak int sensor_set_auto_blc(int enable, int *regs) { + // Check if the control is supported. + if (sensor.set_auto_blc == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.set_auto_blc(&sensor, enable, regs) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + return 0; +} + +__weak int sensor_get_blc_regs(int *regs) { + // Check if the control is supported. + if (sensor.get_blc_regs == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.get_blc_regs(&sensor, regs) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + return 0; +} + +__weak int sensor_set_hmirror(int enable) { + // Check if the value has changed. + if (sensor.hmirror == ((bool) enable)) { + return 0; + } + + // Disable any ongoing frame capture. + sensor_abort(); + + // Check if the control is supported. + if (sensor.set_hmirror == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.set_hmirror(&sensor, enable) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + // Set the new control value. + sensor.hmirror = enable; + + // Wait for the camera to settle + if (!sensor.disable_delays) { + mp_hal_delay_ms(100); + } + + return 0; +} + +__weak bool sensor_get_hmirror() { + return sensor.hmirror; +} + +__weak int sensor_set_vflip(int enable) { + // Check if the value has changed. + if (sensor.vflip == ((bool) enable)) { + return 0; + } + + // Disable any ongoing frame capture. + sensor_abort(); + + // Check if the control is supported. + if (sensor.set_vflip == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.set_vflip(&sensor, enable) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + // Set the new control value. + sensor.vflip = enable; + + // Wait for the camera to settle + if (!sensor.disable_delays) { + mp_hal_delay_ms(100); + } + + return 0; +} + +__weak bool sensor_get_vflip() { + return sensor.vflip; +} + +__weak int sensor_set_transpose(bool enable) { + // Check if the value has changed. + if (sensor.transpose == enable) { + return 0; + } + + // Disable any ongoing frame capture. + sensor_abort(); + + if (sensor.pixformat == PIXFORMAT_JPEG) { + return SENSOR_ERROR_PIXFORMAT_UNSUPPORTED; + } + + // Set the new control value. + sensor.transpose = enable; + + return 0; +} + +__weak bool sensor_get_transpose() { + return sensor.transpose; +} + +__weak int sensor_set_auto_rotation(bool enable) { + // Check if the value has changed. + if (sensor.auto_rotation == enable) { + return 0; + } + + // Disable any ongoing frame capture. + sensor_abort(); + + // Operation not supported on JPEG images. + if (sensor.pixformat == PIXFORMAT_JPEG) { + return SENSOR_ERROR_PIXFORMAT_UNSUPPORTED; + } + + // Set the new control value. + sensor.auto_rotation = enable; + return 0; +} + +__weak bool sensor_get_auto_rotation() { + return sensor.auto_rotation; +} + +__weak int sensor_set_framebuffers(int count) { + // Disable any ongoing frame capture. + sensor_abort(); + + // Flush previous frame. + framebuffer_update_jpeg_buffer(); + + return framebuffer_set_buffers(count); +} + +__weak int sensor_set_special_effect(sde_t sde) { + // Check if the value has changed. + if (sensor.sde == sde) { + return 0; + } + + // Check if the control is supported. + if (sensor.set_special_effect == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.set_special_effect(&sensor, sde) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + // Set the new control value. + sensor.sde = sde; + + return 0; +} + +__weak int sensor_set_lens_correction(int enable, int radi, int coef) { + // Check if the control is supported. + if (sensor.set_lens_correction == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + // Call the sensor specific function. + if (sensor.set_lens_correction(&sensor, enable, radi, coef) != 0) { + return SENSOR_ERROR_CTL_FAILED; + } + + return 0; +} + +__weak int sensor_ioctl(int request, ... /* arg */) { + // Disable any ongoing frame capture. + sensor_abort(); + + // Check if the control is supported. + if (sensor.ioctl == NULL) { + return SENSOR_ERROR_CTL_UNSUPPORTED; + } + + va_list ap; + va_start(ap, request); + // Call the sensor specific function. + int ret = sensor.ioctl(&sensor, request, ap); + va_end(ap); + + return ((ret != 0) ? SENSOR_ERROR_CTL_FAILED : 0); +} + +__weak int sensor_set_vsync_callback(vsync_cb_t vsync_cb) { + sensor.vsync_callback = vsync_cb; + return 0; +} + +__weak int sensor_set_frame_callback(frame_cb_t vsync_cb) { + sensor.frame_callback = vsync_cb; + return 0; +} + +__weak int sensor_set_color_palette(const uint16_t *color_palette) { + sensor.color_palette = color_palette; + return 0; +} + +__weak const uint16_t *sensor_get_color_palette() { + return sensor.color_palette; +} + +__weak int sensor_check_framebuffer_size() { + uint32_t bpp = sensor_get_dst_bpp(); + uint32_t size = framebuffer_get_buffer_size(); + return (((MAIN_FB()->u * MAIN_FB()->v * bpp) <= size) ? 0 : -1); +} + +__weak int sensor_auto_crop_framebuffer() { + uint32_t bpp = sensor_get_dst_bpp(); + uint32_t size = framebuffer_get_buffer_size(); + + // If the pixformat is NULL/JPEG there we can't do anything to check if it fits before hand. + if (!bpp) { + return 0; + } + + // MAIN_FB() fits, we are done. + if ((MAIN_FB()->u * MAIN_FB()->v * bpp) <= size) { + return 0; + } + + if ((sensor.pixformat == PIXFORMAT_RGB565) || (sensor.pixformat == PIXFORMAT_YUV422)) { + // Switch to bayer for the quick 2x savings. + sensor_set_pixformat(PIXFORMAT_BAYER); + bpp = 1; + + // MAIN_FB() fits, we are done (bpp is 1). + if ((MAIN_FB()->u * MAIN_FB()->v) <= size) { + return 0; + } + } + + int window_w = MAIN_FB()->u; + int window_h = MAIN_FB()->v; + + // We need to shrink the frame buffer. We can do this by cropping. So, we will subtract columns + // and rows from the frame buffer until it fits within the frame buffer. + int max = IM_MAX(window_w, window_h); + int min = IM_MIN(window_w, window_h); + float aspect_ratio = max / ((float) min); + float r = aspect_ratio, best_r = r; + int c = 1, best_c = c; + float best_err = FLT_MAX; + + // Find the width/height ratio that's within 1% of the aspect ratio with a loop limit. + for (int i = 100; i; i--) { + float err = fast_fabsf(r - fast_roundf(r)); + + if (err <= best_err) { + best_err = err; + best_r = r; + best_c = c; + } + + if (best_err <= 0.01f) { + break; + } + + r += aspect_ratio; + c += 1; + } + + // Select the larger geometry to map the aspect ratio to. + int u_sub, v_sub; + + if (window_w > window_h) { + u_sub = fast_roundf(best_r); + v_sub = best_c; + } else { + u_sub = best_c; + v_sub = fast_roundf(best_r); + } + + // Crop the frame buffer while keeping the aspect ratio and keeping the width/height even. + while (((MAIN_FB()->u * MAIN_FB()->v * bpp) > size) || (MAIN_FB()->u % 2) || (MAIN_FB()->v % 2)) { + MAIN_FB()->u -= u_sub; + MAIN_FB()->v -= v_sub; + } + + // Center the new window using the previous offset and keep the offset even. + MAIN_FB()->x += (window_w - MAIN_FB()->u) / 2; + MAIN_FB()->y += (window_h - MAIN_FB()->v) / 2; + + if (MAIN_FB()->x % 2) { + MAIN_FB()->x -= 1; + } + if (MAIN_FB()->y % 2) { + MAIN_FB()->y -= 1; + } + + // Pickout a good buffer count for the user. + framebuffer_auto_adjust_buffers(); + return 0; +} + +const char *sensor_strerror(int error) { + static const char *sensor_errors[] = { + "No error.", + "Sensor control failed.", + "The requested operation is not supported by the image sensor.", + "Failed to detect the image sensor or image sensor is detached.", + "The detected image sensor is not supported.", + "Failed to initialize the image sensor.", + "Failed to initialize the image sensor clock.", + "Failed to initialize the image sensor DMA.", + "Failed to initialize the image sensor DCMI.", + "An low level I/O error has occurred.", + "Frame capture has failed.", + "Frame capture has timed out.", + "Frame size is not supported or is not set.", + "Pixel format is not supported or is not set.", + "Window is not supported or is not set.", + "Frame rate is not supported or is not set.", + "An invalid argument is used.", + "The requested operation is not supported on the current pixel format.", + "Frame buffer error.", + "Frame buffer overflow, try reducing the frame size.", + "JPEG frame buffer overflow.", + }; + + // Sensor errors are negative. + error = ((error < 0) ? (error * -1) : error); + + if (error > (sizeof(sensor_errors) / sizeof(sensor_errors[0]))) { + return "Unknown error."; + } else { + return sensor_errors[error]; + } +} + +__weak int sensor_snapshot(sensor_t *sensor, image_t *image, uint32_t flags) { + return -1; +} +#endif //MICROPY_PY_SENSOR diff --git a/components/3rd_party/omv/omv/common/tinyusb_debug.c b/components/3rd_party/omv/omv/common/tinyusb_debug.c new file mode 100644 index 00000000..058d6a7b --- /dev/null +++ b/components/3rd_party/omv/omv/common/tinyusb_debug.c @@ -0,0 +1,147 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2022 Ibrahim Abdelkader + * Copyright (c) 2022 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Tinyusb CDC debugger helper code. + */ + +#include "omv_boardconfig.h" +#if (OMV_ENABLE_TUSBDBG == 1) +#include "py/runtime.h" +#include "py/stream.h" +#include "py/mphal.h" +#include "py/ringbuf.h" +#include "pendsv.h" + +#include "tusb.h" +#include "usbdbg.h" +#include "tinyusb_debug.h" + +#define DEBUG_MAX_PACKET (OMV_TUSBDBG_PACKET) +#define DEBUG_BAUDRATE_SLOW (921600) +#define DEBUG_BAUDRATE_FAST (12000000) + +extern void __fatal_error(); + +typedef struct __attribute__((packed)) { + uint8_t cmd; + uint8_t request; + uint32_t xfer_length; +} +usbdbg_cmd_t; + +static uint8_t debug_ringbuf_array[512]; +static volatile bool tinyusb_debug_mode = false; +ringbuf_t debug_ringbuf = { debug_ringbuf_array, sizeof(debug_ringbuf_array) }; + +uint32_t usb_cdc_buf_len() { + return ringbuf_avail((ringbuf_t *) &debug_ringbuf); +} + +uint32_t usb_cdc_get_buf(uint8_t *buf, uint32_t len) { + for (int i = 0; i < len; i++) { + buf[i] = ringbuf_get((ringbuf_t *) &debug_ringbuf); + } + return len; +} + +void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const *coding) { + debug_ringbuf.iget = 0; + debug_ringbuf.iput = 0; + + if (0) { + #if defined(MICROPY_BOARD_ENTER_BOOTLOADER) + } else if (coding->bit_rate == 1200) { + MICROPY_BOARD_ENTER_BOOTLOADER(0, 0); + #endif + } else if (coding->bit_rate == DEBUG_BAUDRATE_SLOW + || coding->bit_rate == DEBUG_BAUDRATE_FAST) { + tinyusb_debug_mode = true; + } else { + tinyusb_debug_mode = false; + } +} + +bool tinyusb_debug_enabled(void) { + return tinyusb_debug_mode; +} + +void tinyusb_debug_tx_strn(const char *str, mp_uint_t len) { + // TODO can be faster. + if (tinyusb_debug_enabled() && tud_cdc_connected()) { + for (int i = 0; i < len; i++) { + NVIC_DisableIRQ(PendSV_IRQn); + ringbuf_put((ringbuf_t *) &debug_ringbuf, str[i]); + NVIC_EnableIRQ(PendSV_IRQn); + } + } +} + +static void tinyusb_debug_task(void) { + tud_task(); + + uint8_t dbg_buf[DEBUG_MAX_PACKET]; + if (tud_cdc_connected() && tud_cdc_available() >= 6) { + uint32_t count = tud_cdc_read(dbg_buf, 6); + if (count < 6 || dbg_buf[0] != 0x30) { + // Maybe we should try to recover from this state + // but for now, call __fatal_error which doesn't + // return. + __fatal_error(); + return; + } + usbdbg_cmd_t *cmd = (usbdbg_cmd_t *) dbg_buf; + uint8_t request = cmd->request; + uint32_t xfer_length = cmd->xfer_length; + usbdbg_control(NULL, request, xfer_length); + + while (xfer_length) { + // && tud_cdc_connected()) + if (tud_task_event_ready()) { + tud_task(); + } + if (request & 0x80) { + // Device-to-host data phase + int bytes = MIN(xfer_length, DEBUG_MAX_PACKET); + if (bytes <= tud_cdc_write_available()) { + xfer_length -= bytes; + usbdbg_data_in(dbg_buf, bytes); + tud_cdc_write(dbg_buf, bytes); + } + tud_cdc_write_flush(); + } else { + // Host-to-device data phase + int bytes = MIN(xfer_length, DEBUG_MAX_PACKET); + uint32_t count = tud_cdc_read(dbg_buf, bytes); + xfer_length -= count; + usbdbg_data_out(dbg_buf, count); + } + } + } +} + +// For the mimxrt, and nrf ports this replaces the weak USB IRQ handlers. +// For the RP2 port, this handler is installed in main.c +void OMV_USB1_IRQ_HANDLER(void) { + dcd_int_handler(0); + // If there are any event to process, schedule a call to cdc loop. + if (tinyusb_debug_enabled()) { + pendsv_schedule_dispatch(PENDSV_DISPATCH_CDC, tinyusb_debug_task); + } +} + +#if defined(OMV_USB2_IRQ_HANDLER) +void OMV_USB2_IRQ_HANDLER(void) { + dcd_int_handler(1); + // If there are any event to process, schedule a call to cdc loop. + if (tinyusb_debug_enabled()) { + pendsv_schedule_dispatch(PENDSV_DISPATCH_CDC, tinyusb_debug_task); + } +} +#endif + +#endif //OMV_TINYUSB_DEBUG diff --git a/components/3rd_party/omv/omv/common/tinyusb_debug.h b/components/3rd_party/omv/omv/common/tinyusb_debug.h new file mode 100644 index 00000000..4af0067b --- /dev/null +++ b/components/3rd_party/omv/omv/common/tinyusb_debug.h @@ -0,0 +1,16 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2022 Ibrahim Abdelkader + * Copyright (c) 2022 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Tinyusb CDC debugger helper code. + */ +#ifndef __TUSBDBG_H__ +void USBD_IRQHandler(void); +int tinyusb_debug_init(void); +bool tinyusb_debug_enabled(void); +void tinyusb_debug_tx_strn(const char *str, mp_uint_t len); +#endif // __TUSBDBG_H__ diff --git a/components/3rd_party/omv/omv/common/trace.c b/components/3rd_party/omv/omv/common/trace.c new file mode 100644 index 00000000..92099b0c --- /dev/null +++ b/components/3rd_party/omv/omv/common/trace.c @@ -0,0 +1,36 @@ +/* + * 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. + * + * Trace buffer. + */ +#include +#include "cmsis_gcc.h" +#include "trace.h" + +#define TRACEBUF_SIZE (256) +typedef struct _tracebuf_t { + uint8_t idx; + uint8_t buf[TRACEBUF_SIZE]; +} tracebuf_t; + +static tracebuf_t tracebuf; + +void trace_init() { + tracebuf.idx = 0; + for (int i = 0; i < TRACEBUF_SIZE; i++) { + tracebuf.buf[i] = 0; + } +} + +void trace_insert(uint32_t x) { + __disable_irq(); + if (tracebuf.idx < TRACEBUF_SIZE) { + tracebuf.buf[tracebuf.idx++] = x; + } + __enable_irq(); +} diff --git a/components/3rd_party/omv/omv/common/trace.h b/components/3rd_party/omv/omv/common/trace.h new file mode 100644 index 00000000..e0b56ec0 --- /dev/null +++ b/components/3rd_party/omv/omv/common/trace.h @@ -0,0 +1,16 @@ +/* + * 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. + * + * Trace buffer. + */ +#ifndef __TRACE_H__ +#define __TRACE_H__ +#include +void trace_init(); +void trace_insert(uint32_t x); +#endif /* __TRACE_H__ */ diff --git a/components/3rd_party/omv/omv/common/usbdbg.c b/components/3rd_party/omv/omv/common/usbdbg.c new file mode 100644 index 00000000..c21f777b --- /dev/null +++ b/components/3rd_party/omv/omv/common/usbdbg.c @@ -0,0 +1,460 @@ +/* + * 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. + * + * USB debugger. + */ +#include +#include +#include "py/nlr.h" +#include "py/gc.h" +#include "py/mphal.h" +#include "py/obj.h" +#include "py/objstr.h" +#include "py/runtime.h" +#include "pendsv.h" + +#include "imlib.h" +#if MICROPY_PY_SENSOR +#include "omv_i2c.h" +#include "sensor.h" +#endif +#include "framebuffer.h" +#include "usbdbg.h" +#include "omv_boardconfig.h" +#include "py_image.h" + +static int xfer_bytes; +static int xfer_length; +static enum usbdbg_cmd cmd; + +static volatile bool script_ready; +static volatile bool script_running; +static volatile bool irq_enabled; +static vstr_t script_buf; + +static mp_obj_exception_t ide_exception; +static const MP_DEFINE_STR_OBJ(ide_exception_msg, "IDE interrupt"); +static const mp_rom_obj_tuple_t ide_exception_args_obj = { + {&mp_type_tuple}, 1, {MP_ROM_PTR(&ide_exception_msg)} +}; + + +// These functions must be implemented in MicroPython CDC driver. +extern uint32_t usb_cdc_buf_len(); +extern uint32_t usb_cdc_get_buf(uint8_t *buf, uint32_t len); +void __attribute__((weak)) usb_cdc_reset_buffers() { + +} + +void usbdbg_init() { + cmd = USBDBG_NONE; + script_ready = false; + script_running = false; + irq_enabled = false; + + vstr_init(&script_buf, 32); + + // Initialize the IDE exception object. + ide_exception.base.type = &mp_type_Exception; + ide_exception.traceback_alloc = 0; + ide_exception.traceback_len = 0; + ide_exception.traceback_data = NULL; + ide_exception.args = (mp_obj_tuple_t *) &ide_exception_args_obj; +} + +void usbdbg_wait_for_command(uint32_t timeout) { + for (mp_uint_t ticks = mp_hal_ticks_ms(); + irq_enabled && ((mp_hal_ticks_ms() - ticks) < timeout) && (cmd != USBDBG_NONE); ) { + ; + } +} + +bool usbdbg_script_ready() { + return script_ready; +} + +vstr_t *usbdbg_get_script() { + return &script_buf; +} + +bool usbdbg_is_busy() { + return cmd != USBDBG_NONE; +} + +void usbdbg_set_script_running(bool running) { + script_running = running; +} + +inline void usbdbg_set_irq_enabled(bool enabled) { + if (enabled) { + NVIC_EnableIRQ(OMV_USB_IRQN); + } else { + NVIC_DisableIRQ(OMV_USB_IRQN); + } + __DSB(); __ISB(); + irq_enabled = enabled; +} + +static void usbdbg_interrupt_vm(bool ready) { + // Set script ready flag + script_ready = ready; + + // Set script running flag + script_running = ready; + + // Disable IDE IRQ (re-enabled by pyexec or main). + usbdbg_set_irq_enabled(false); + + // Clear interrupt traceback + mp_obj_exception_clear_traceback(&ide_exception); + + // Remove the BASEPRI masking (if any) + __set_BASEPRI(0); + + // Interrupt running REPL + // Note: setting pendsv explicitly here because the VM is probably + // waiting in REPL and the soft interrupt flag will not be checked. + pendsv_nlr_jump(&ide_exception); +} + +bool usbdbg_get_irq_enabled() { + return irq_enabled; +} + +void usbdbg_data_in(void *buffer, int length) { + switch (cmd) { + case USBDBG_FW_VERSION: { + uint32_t *ver_buf = buffer; + ver_buf[0] = FIRMWARE_VERSION_MAJOR; + ver_buf[1] = FIRMWARE_VERSION_MINOR; + ver_buf[2] = FIRMWARE_VERSION_PATCH; + cmd = USBDBG_NONE; + break; + } + + case USBDBG_TX_BUF_LEN: { + uint32_t tx_buf_len = usb_cdc_buf_len(); + memcpy(buffer, &tx_buf_len, sizeof(tx_buf_len)); + cmd = USBDBG_NONE; + break; + } + + case USBDBG_SENSOR_ID: { + int sensor_id = 0xFF; + #if MICROPY_PY_SENSOR + if (sensor_is_detected() == true) { + sensor_id = sensor_get_id(); + } + #endif + memcpy(buffer, &sensor_id, 4); + cmd = USBDBG_NONE; + break; + } + + case USBDBG_TX_BUF: { + xfer_bytes += usb_cdc_get_buf(buffer, length); + if (xfer_bytes == xfer_length) { + cmd = USBDBG_NONE; + } + break; + } + + case USBDBG_FRAME_SIZE: + // Return 0 if FB is locked or not ready. + ((uint32_t *) buffer)[0] = 0; + // Try to lock FB. If header size == 0 frame is not ready + if (mutex_try_lock_alternate(&JPEG_FB()->lock, MUTEX_TID_IDE)) { + // If header size == 0 frame is not ready + if (JPEG_FB()->size == 0) { + // unlock FB + mutex_unlock(&JPEG_FB()->lock, MUTEX_TID_IDE); + } else { + // Return header w, h and size/bpp + ((uint32_t *) buffer)[0] = JPEG_FB()->w; + ((uint32_t *) buffer)[1] = JPEG_FB()->h; + ((uint32_t *) buffer)[2] = JPEG_FB()->size; + } + } + cmd = USBDBG_NONE; + break; + + case USBDBG_FRAME_DUMP: + if (xfer_bytes < xfer_length) { + memcpy(buffer, JPEG_FB()->pixels + xfer_bytes, length); + xfer_bytes += length; + if (xfer_bytes == xfer_length) { + cmd = USBDBG_NONE; + JPEG_FB()->w = 0; JPEG_FB()->h = 0; JPEG_FB()->size = 0; + mutex_unlock(&JPEG_FB()->lock, MUTEX_TID_IDE); + } + } + break; + + case USBDBG_ARCH_STR: { + unsigned int uid[3] = { + #if (OMV_UNIQUE_ID_SIZE == 2) + 0U, + #else + *((unsigned int *) (OMV_UNIQUE_ID_ADDR + OMV_UNIQUE_ID_OFFSET * 2)), + #endif + *((unsigned int *) (OMV_UNIQUE_ID_ADDR + OMV_UNIQUE_ID_OFFSET * 1)), + *((unsigned int *) (OMV_UNIQUE_ID_ADDR + OMV_UNIQUE_ID_OFFSET * 0)), + }; + snprintf((char *) buffer, 64, "%s [%s:%08X%08X%08X]", + OMV_ARCH_STR, OMV_BOARD_TYPE, uid[0], uid[1], uid[2]); + cmd = USBDBG_NONE; + break; + } + + case USBDBG_SCRIPT_RUNNING: { + uint32_t *buf = buffer; + buf[0] = (uint32_t) script_running; + cmd = USBDBG_NONE; + break; + } + default: /* error */ + break; + } +} + +void usbdbg_data_out(void *buffer, int length) { + switch (cmd) { + case USBDBG_FB_ENABLE: { + uint32_t enable = *((int32_t *) buffer); + JPEG_FB()->enabled = enable; + if (enable == 0) { + // When disabling framebuffer, the IDE might still be holding FB lock. + // If the IDE is not the current lock owner, this operation is ignored. + mutex_unlock(&JPEG_FB()->lock, MUTEX_TID_IDE); + } + cmd = USBDBG_NONE; + break; + } + + case USBDBG_SCRIPT_EXEC: + // check if GC is locked before allocating memory for vstr. If GC was locked + // at least once before the script is fully uploaded xfer_bytes will be less + // than the total length (xfer_length) and the script will Not be executed. + if (!script_running) { + nlr_buf_t nlr; + if (!gc_is_locked() && nlr_push(&nlr) == 0) { + vstr_add_strn(&script_buf, buffer, length); + nlr_pop(); + } + xfer_bytes += length; + if (xfer_bytes == xfer_length) { + cmd = USBDBG_NONE; + // Schedule the IDE exception to interrupt the VM. + usbdbg_interrupt_vm(true); + } + } + break; + + case USBDBG_TEMPLATE_SAVE: { + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + image_t image; + framebuffer_init_image(&image); + + // null terminate the path + length = (length == 64) ? 63:length; + ((char *) buffer)[length] = 0; + + rectangle_t *roi = (rectangle_t *) buffer; + char *path = (char *) buffer + sizeof(rectangle_t); + + imlib_save_image(&image, path, roi, 50); + + // raise a flash IRQ to flush image + //NVIC->STIR = FLASH_IRQn; + #endif //IMLIB_ENABLE_IMAGE_FILE_IO + break; + } + + case USBDBG_DESCRIPTOR_SAVE: { + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) \ + && defined(IMLIB_ENABLE_KEYPOINTS) + image_t image; + framebuffer_init_image(&image); + + // null terminate the path + length = (length == 64) ? 63:length; + ((char *) buffer)[length] = 0; + + rectangle_t *roi = (rectangle_t *) buffer; + char *path = (char *) buffer + sizeof(rectangle_t); + + py_image_descriptor_from_roi(&image, path, roi); + #endif //IMLIB_ENABLE_IMAGE_FILE_IO && IMLIB_ENABLE_KEYPOINTS + cmd = USBDBG_NONE; + break; + } + + case USBDBG_ATTR_WRITE: { + #if MICROPY_PY_SENSOR + /* write sensor attribute */ + int32_t attr = *((int32_t *) buffer); + int32_t val = *((int32_t *) buffer + 1); + switch (attr) { + case ATTR_CONTRAST: + sensor_set_contrast(val); + break; + case ATTR_BRIGHTNESS: + sensor_set_brightness(val); + break; + case ATTR_SATURATION: + sensor_set_saturation(val); + break; + case ATTR_GAINCEILING: + sensor_set_gainceiling(val); + break; + default: + break; + } + #endif + cmd = USBDBG_NONE; + break; + } + + case USBDBG_SET_TIME: { + // TODO implement + #if 0 + uint32_t *timebuf = (uint32_t *) buffer; + timebuf[0]; // Year + timebuf[1]; // Month + timebuf[2]; // Day + timebuf[3]; // Day of the week + timebuf[4]; // Hour + timebuf[5]; // Minute + timebuf[6]; // Second + timebuf[7]; // Milliseconds + #endif + cmd = USBDBG_NONE; + break; + } + + case USBDBG_TX_INPUT: { + // TODO implement + #if 0 + uint32_t key = *((uint32_t *) buffer); + #endif + cmd = USBDBG_NONE; + break; + } + + default: /* error */ + break; + } +} + +void usbdbg_control(void *buffer, uint8_t request, uint32_t length) { + cmd = (enum usbdbg_cmd) request; + switch (cmd) { + case USBDBG_FW_VERSION: + xfer_bytes = 0; + xfer_length = length; + break; + + case USBDBG_FRAME_SIZE: + xfer_bytes = 0; + xfer_length = length; + break; + + case USBDBG_FRAME_DUMP: + xfer_bytes = 0; + xfer_length = length; + break; + + case USBDBG_ARCH_STR: + xfer_bytes = 0; + xfer_length = length; + break; + + case USBDBG_SCRIPT_EXEC: + xfer_bytes = 0; + xfer_length = length; + vstr_reset(&script_buf); + break; + + case USBDBG_SCRIPT_STOP: + if (script_running) { + // Reset CDC buffers. + usb_cdc_reset_buffers(); + + // Schedule the IDE exception to interrupt the VM. + usbdbg_interrupt_vm(false); + } + cmd = USBDBG_NONE; + break; + + case USBDBG_SCRIPT_SAVE: + // TODO: save running script + cmd = USBDBG_NONE; + break; + + case USBDBG_SCRIPT_RUNNING: + xfer_bytes = 0; + xfer_length = length; + break; + + case USBDBG_TEMPLATE_SAVE: + case USBDBG_DESCRIPTOR_SAVE: + /* save template */ + xfer_bytes = 0; + xfer_length = length; + break; + + case USBDBG_ATTR_WRITE: + xfer_bytes = 0; + xfer_length = length; + break; + + case USBDBG_SYS_RESET: + NVIC_SystemReset(); + break; + + case USBDBG_SYS_RESET_TO_BL: { + #if defined(MICROPY_BOARD_ENTER_BOOTLOADER) + MICROPY_BOARD_ENTER_BOOTLOADER(0, 0); + #else + NVIC_SystemReset(); + #endif + break; + } + + case USBDBG_FB_ENABLE: { + xfer_bytes = 0; + xfer_length = length; + break; + } + + case USBDBG_TX_BUF: + case USBDBG_TX_BUF_LEN: + xfer_bytes = 0; + xfer_length = length; + break; + + case USBDBG_SENSOR_ID: + xfer_bytes = 0; + xfer_length = length; + break; + + case USBDBG_SET_TIME: + xfer_bytes = 0; + xfer_length = length; + break; + + case USBDBG_TX_INPUT: + xfer_bytes = 0; + xfer_length = length; + break; + + default: /* error */ + cmd = USBDBG_NONE; + break; + } +} diff --git a/components/3rd_party/omv/omv/common/usbdbg.h b/components/3rd_party/omv/omv/common/usbdbg.h new file mode 100644 index 00000000..6bdb7254 --- /dev/null +++ b/components/3rd_party/omv/omv/common/usbdbg.h @@ -0,0 +1,66 @@ +/* + * 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. + * + * USB debug support. + */ +#ifndef __USBDBG_H__ +#define __USBDBG_H__ + +/** + * Firmware version (major, minor and patch numbers). + * + */ +#define FIRMWARE_VERSION_MAJOR (4) +#define FIRMWARE_VERSION_MINOR (5) +#define FIRMWARE_VERSION_PATCH (2) + +/** + * To add a new debugging command, increment the last command value used. + * Set the MSB of the value if the request has a device-to-host data phase. + * Add the command to usr/openmv.py using the same value. + * Handle the command control and data in/out (if any) phases in usbdbg.c. + * + * See usbdbg.c for examples. + */ +enum usbdbg_cmd { + USBDBG_NONE =0x00, + USBDBG_FW_VERSION =0x80, + USBDBG_FRAME_SIZE =0x81, + USBDBG_FRAME_DUMP =0x82, + USBDBG_ARCH_STR =0x83, + USBDBG_SCRIPT_EXEC =0x05, + USBDBG_SCRIPT_STOP =0x06, + USBDBG_SCRIPT_SAVE =0x07, + USBDBG_SCRIPT_RUNNING =0x87, + USBDBG_TEMPLATE_SAVE =0x08, + USBDBG_DESCRIPTOR_SAVE =0x09, + USBDBG_ATTR_READ =0x8A, + USBDBG_ATTR_WRITE =0x0B, + USBDBG_SYS_RESET =0x0C, + USBDBG_SYS_RESET_TO_BL =0x0E, + USBDBG_FB_ENABLE =0x0D, + USBDBG_TX_BUF_LEN =0x8E, + USBDBG_TX_BUF =0x8F, + USBDBG_SENSOR_ID =0x90, + USBDBG_TX_INPUT =0x11, + USBDBG_SET_TIME =0x12, +}; + +void usbdbg_init(); +void usbdbg_wait_for_command(uint32_t timeout); +bool usbdbg_script_ready(); +vstr_t *usbdbg_get_script(); +bool usbdbg_is_busy(); +bool usbdbg_get_irq_enabled(); +void usbdbg_set_irq_enabled(bool enabled); +void usbdbg_set_script_running(bool running); +void usbdbg_data_in(void *buffer, int length); +void usbdbg_data_out(void *buffer, int length); +void usbdbg_control(void *buffer, uint8_t brequest, uint32_t wlength); + +#endif /* __USBDBG_H__ */ diff --git a/components/3rd_party/omv/omv/common/vospi.c b/components/3rd_party/omv/omv/common/vospi.c new file mode 100644 index 00000000..c2738123 --- /dev/null +++ b/components/3rd_party/omv/omv/common/vospi.c @@ -0,0 +1,202 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2023 Ibrahim Abdelkader + * Copyright (c) 2023 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * VOSPI driver. + */ +#include "omv_boardconfig.h" +#if OMV_ENABLE_VOSPI || OMV_ENABLE_LEPTON + +#include +#include +#include "py/mphal.h" + +#include "vospi.h" +#include "crc16.h" +#include "omv_common.h" +#include "omv_spi.h" + +#define VOSPI_HEADER_WORDS (2) // 16-bits +#define VOSPI_PID_SIZE_PIXELS (80) // w, 16-bits per pixel +#define VOSPI_PIDS_PER_SID (60) // h +#define VOSPI_SIDS_PER_FRAME (4) +#define VOSPI_PACKET_SIZE (VOSPI_HEADER_WORDS + VOSPI_PID_SIZE_PIXELS) // 16-bits +#define VOSPI_SID_SIZE_PIXELS (VOSPI_PIDS_PER_SID * VOSPI_PID_SIZE_PIXELS) // 16-bits + +#define VOSPI_BUFFER_SIZE (VOSPI_PACKET_SIZE * 2) // 16-bits +#define VOSPI_CLOCK_SPEED 20000000 // hz +#define VOSPI_SYNC_MS 200 // ms + +#define VOSPI_SPECIAL_PACKET (20) +#define VOSPI_DONT_CARE_PACKET (0x0F00) +#define VOSPI_HEADER_DONT_CARE(x) (((x) & VOSPI_DONT_CARE_PACKET) == VOSPI_DONT_CARE_PACKET) +#define VOSPI_HEADER_PID(id) ((id) & 0x0FFF) +#define VOSPI_HEADER_SID(id) (((id) >> 12) & 0x7) + +typedef enum { + VOSPI_FLAGS_CAPTURE = (1 << 0), + VOSPI_FLAGS_RESYNC = (1 << 1), +} vospi_flags_t; + +typedef struct _vospi_state { + int pid; + int sid; + uint16_t *framebuffer; + bool lepton_3; + omv_spi_t spi_bus; + volatile uint32_t flags; +} vospi_state_t; + +static vospi_state_t vospi; + +static uint16_t OMV_ATTR_SECTION(OMV_ATTR_ALIGNED_DMA(vospi_buf[VOSPI_BUFFER_SIZE]), ".dma_buffer"); +static void vospi_callback(omv_spi_t *spi, void *userdata, void *buf); + +static void vospi_resync() { + omv_spi_transfer_t spi_xfer = { + .rxbuf = vospi_buf, + .size = VOSPI_BUFFER_SIZE, + .flags = OMV_SPI_XFER_DMA, + .callback = vospi_callback, + }; + + mp_hal_delay_ms(VOSPI_SYNC_MS); + omv_spi_transfer_start(&vospi.spi_bus, &spi_xfer); +} + +#if defined(OMV_ENABLE_VOSPI_CRC) +static bool vospi_check_crc(const uint16_t *base) { + int id = base[0]; + int packet_crc = base[1]; + int crc = ByteCRC16((id >> 8) & 0x0F, 0); + crc = ByteCRC16(id, crc); + crc = ByteCRC16(0, crc); + crc = ByteCRC16(0, crc); + + for (int i = VOSPI_HEADER_WORDS; i < VOSPI_PACKET_SIZE; i++) { + int value = base[i]; + crc = ByteCRC16(value >> 8, crc); + crc = ByteCRC16(value, crc); + } + + return packet_crc == crc; +} +#endif + +void vospi_callback(omv_spi_t *spi, void *userdata, void *buf) { + if (!(vospi.flags & VOSPI_FLAGS_CAPTURE)) { + return; + } + + const uint16_t *base = (uint16_t *) buf; + + int id = base[0]; + + // Ignore don't care packets. + if (VOSPI_HEADER_DONT_CARE(id)) { + return; + } + + int pid = VOSPI_HEADER_PID(id); + int sid = VOSPI_HEADER_SID(id) - 1; + + // Discard packets with a pid != 0 when waiting for the first packet. + if ((vospi.pid == 0) && (pid != 0)) { + return; + } + + // Discard sidments with a sid != 0 when waiting for the first segment. + if (vospi.lepton_3 && (pid == VOSPI_SPECIAL_PACKET) && (vospi.sid == 0) && (sid != 0)) { + vospi.pid = 0; + return; + } + + // Are we in sync with the flir lepton? + if ((pid != vospi.pid) + #if defined(OMV_ENABLE_VOSPI_CRC) + || (!vospi_check_crc(base)) + #endif + || (vospi.lepton_3 && (pid == VOSPI_SPECIAL_PACKET) && (sid != vospi.sid))) { + vospi.pid = 0; + vospi.sid = 0; + omv_spi_transfer_abort(&vospi.spi_bus); + vospi.flags |= VOSPI_FLAGS_RESYNC; + return; + } + + memcpy(vospi.framebuffer + + (vospi.pid * VOSPI_PID_SIZE_PIXELS) + + (vospi.sid * VOSPI_SID_SIZE_PIXELS), + base + VOSPI_HEADER_WORDS, VOSPI_PID_SIZE_PIXELS * sizeof(uint16_t)); + + vospi.pid += 1; + if (vospi.pid == VOSPI_PIDS_PER_SID) { + vospi.pid = 0; + + // For the FLIR Lepton 3 we have to receive all the pids in all the segments. + if (vospi.lepton_3) { + vospi.sid += 1; + if (vospi.sid == VOSPI_SIDS_PER_FRAME) { + vospi.sid = 0; + vospi.flags &= ~VOSPI_FLAGS_CAPTURE; + } + // For the FLIR Lepton 1/2 we just have to receive all the pids. + } else { + vospi.flags &= ~VOSPI_FLAGS_CAPTURE; + } + } +} + +int vospi_init(uint32_t n_packets, void *buffer) { + memset(&vospi, 0, sizeof(vospi_state_t)); + vospi.lepton_3 = n_packets > VOSPI_PIDS_PER_SID; + vospi.framebuffer = buffer; + // resync on first snapshot. + vospi.flags = VOSPI_FLAGS_RESYNC; + + omv_spi_config_t spi_config; + omv_spi_default_config(&spi_config, ISC_SPI_ID); + + spi_config.bus_mode = OMV_SPI_BUS_RX; + spi_config.datasize = 16; + spi_config.baudrate = VOSPI_CLOCK_SPEED; + spi_config.dma_flags = OMV_SPI_DMA_CIRCULAR | OMV_SPI_DMA_DOUBLE; + + if (omv_spi_init(&vospi.spi_bus, &spi_config) != 0) { + return -1; + } + return 0; +} + +int vospi_snapshot(uint32_t timeout_ms) { + // Restart counters to capture a new frame. + vospi.flags |= VOSPI_FLAGS_CAPTURE; + + // Snapshot start tick + mp_uint_t tick_start = mp_hal_ticks_ms(); + + do { + if (vospi.flags & VOSPI_FLAGS_RESYNC) { + vospi.flags &= ~VOSPI_FLAGS_RESYNC; + vospi_resync(); + } + + if ((mp_hal_ticks_ms() - tick_start) > timeout_ms) { + omv_spi_transfer_abort(&vospi.spi_bus); + vospi.pid = 0; + vospi.sid = 0; + vospi.flags = VOSPI_FLAGS_RESYNC; + return -1; + } + + MICROPY_EVENT_POLL_HOOK + } while (vospi.flags & VOSPI_FLAGS_CAPTURE); + + return 0; +} + +#endif diff --git a/components/3rd_party/omv/omv/common/vospi.h b/components/3rd_party/omv/omv/common/vospi.h new file mode 100644 index 00000000..0754cb57 --- /dev/null +++ b/components/3rd_party/omv/omv/common/vospi.h @@ -0,0 +1,15 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2023 Ibrahim Abdelkader + * Copyright (c) 2023 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * VOSPI driver. + */ +#ifndef __VOSPI_H__ +#define __VOSPI_H__ +int vospi_init(uint32_t n_packets, void *buffer); +int vospi_snapshot(uint32_t timeout_ms); +#endif // __VOSPI_H__ diff --git a/components/3rd_party/omv/omv/imlib/agast.c b/components/3rd_party/omv/omv/imlib/agast.c new file mode 100644 index 00000000..ffa86547 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/agast.c @@ -0,0 +1,1293 @@ +/* + * 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 (480u) +#define MIN_MEM (10 * 1024) +#define MAX_CORNERS (2000u) +#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; i < num_corners; i++) { + corners[i].score = agast58_score(image->pixels + (corners[i].y * image->w + corners[i].x), threshold); + } + // Non-max suppression + nonmax_suppression(corners, num_corners, keypoints); + } + // Free corners; + if (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; + 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; + row_start = fb_alloc((last_row + 1) * sizeof(uint16_t), FB_ALLOC_NO_HINT); + + for (int i = 0; i < last_row + 1; i++) { + row_start[i] = -1; + } + + for (int i = 0, prev_row = -1; i < sz; i++) { + corner_t *c = &corners[i]; + if (c->y != prev_row) { + row_start[c->y] = i; + prev_row = c->y; + } + } + + for (int i = 0; i < sz; i++) { + corner_t pos = corners[i]; + uint16_t score = pos.score; + + /*Check left */ + if (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); + + // 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; + } + } + array_push_back(keypoints, alloc_keypoint(pos.x, pos.y, pos.score)); + nonmax: + ; + } + + // Free temp rows. + if (row_start) fb_free(row_start); +} + +// *INDENT-OFF* +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 = MAX_CORNERS; + corner_t *corners = (corner_t*) fb_alloc(max_corners * sizeof(corner_t), FB_ALLOC_NO_HINT); + + 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; + } +} +// *INDENT-ON* diff --git a/components/3rd_party/omv/omv/imlib/apriltag.c b/components/3rd_party/omv/omv/imlib/apriltag.c new file mode 100644 index 00000000..aab2a4f2 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/apriltag.c @@ -0,0 +1,12671 @@ +/* + * 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" + +// *INDENT-OFF* +// Enable new code optimizations +#define OPTIMIZED + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" + +#define DEBUG_EN 0 +#if DEBUG_EN + #include +static uint64_t get_time_us() +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_usec + tv.tv_sec * 1000000; +} + +#define DEBUG_INIT() uint64_t __debug_start_us = 0; +#define DEBUG_START() __debug_start_us = get_time_us(); +#define DEBUG_PRINT() printf(">>>>>>>>>>>>>>>>> [%s][%d] %ldus\n", __func__, __LINE__, get_time_us() - __debug_start_us); +#else +#define DEBUG_INIT() +#define DEBUG_START() +#define DEBUG_PRINT() +#endif +/* 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) +{ + (void)buffer_bytes; // suppress unused parameter warning + assert(za != NULL); + assert(buffer != NULL); + assert((int)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 && (int)a->nrows == len) || ((int)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 < (int)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 < (int)m->nrows; i++) { + for (int j = 0; j < (int)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 < (int)m->ncols; j++) { + for (int i = 0; i < (int)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 < (int)m->nrows; i++) { + for (int j = 0; j < (int)m->ncols; j++) { + TYPE acc = 0; + for (int k = 0; k < (int)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 < (int)m->nrows; i++) { + for (int j = 0; j < (int)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 < (int)a->nrows; i++) { + for (int j = 0; j < (int)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 < (int)m->nrows; i++) { + for (int j = 0; j < (int)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 < (int)a->nrows; i++) { + for (int j = 0; j < (int)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 < (int)m->nrows; i++) { + for (int j = 0; j < (int)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 < (int)a->nrows; i++) { + for (int j = 0; j < (int)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 < (int)a->nrows; i++) { + for (int j = 0; j < (int)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 decomposition 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 < (int)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 < (int)a->nrows; i++) { + for (int j = 0; j < (int)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 < (int)A->nrows; hhidx++) { + + if (hhidx < (int)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 < (int)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 < (int)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 < (int)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 < (int)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 < (int)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 = 0; // 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 < (int)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 < (int)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 < (int)B->ncols; i++) { + for (int j = 0; j < (int)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 < (int)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 < (int)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 < (int)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 < (int)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 < (int)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 < (int)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 < (int)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 < (int)B->nrows; i++) { + for (int j = 0; j < (int)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 < (int)a->nrows; i++) + piv[i] = i; + + for (int j = 0; j < (int)a->ncols; j++) { + for (int i = 0; i < (int)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 < (int)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 < (int)lu->ncols && j < (int)lu->nrows && LUjj != 0) { + LUjj = 1.0 / LUjj; + for (int i = j+1; i < (int)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 < (int)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 < (int)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 < (int)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 < (int)lu->ncols; i++) { + for (int j = 0; j < (int)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 < (int)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 < (int)mlu->lu->nrows; k++) { + for (int i = k+1; i < (int)mlu->lu->nrows; i++) { + float LUik = -MATD_EL(mlu->lu, i, k); + for (int t = 0; t < (int)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 < (int)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 < (int)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 < (int)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 < (int)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 < (int)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 < (int)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 < (int)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 < (int)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 < (int)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 < (int)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; x<(int)m->nrows; x++) { + for(int y=0; y<(int)m->ncols; 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) +{ + (void)C; + (void)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 = 0; + 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 <= (int)maxid; i++) { + uf->data[i].parent = i; + } + return uf; +} + +static inline void unionfind_destroy(unionfind_t * uf) +{ + if (uf) { + if (uf->data) fb_free(uf->data); + fb_free(uf); + } +} + +/* +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)); + + if (tmp) 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; + } + + if (f) fb_free(f); // f + memcpy(errs, y, sz * sizeof(float)); + if (y) 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){ + if (maxima_errs) fb_free(maxima_errs); // maxima_errs + if (maxima) fb_free(maxima); // maxima + if (errs) fb_free(errs); // 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; + + if (maxima_errs_copy) fb_free(maxima_errs_copy); // maxima_errs_copy + } + + if (maxima_errs) fb_free(maxima_errs); // maxima_errs + // if (maxima) fb_free(maxima); // maxima + if (errs) 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 (maxima) fb_free(maxima); // maxima + + 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 preparatory + // 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: + + if (lfps) 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) +{ + (void)h; + 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_alloc0(tw*th*sizeof(uint8_t), FB_ALLOC_NO_HINT); + uint8_t *im_min_tmp = fb_alloc0(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)); + if (im_min_tmp) fb_free(im_min_tmp); // im_min_tmp + if (im_max_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; + } + } + } + + if (im_min) fb_free(im_min); // im_min + if (im_max) 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; + } + } + + if (tmp->buf) fb_free(tmp->buf); // tmp->buf + if (tmp) fb_free(tmp); // tmp + } + + return threshim; +} + +zarray_t *apriltag_quad_thresh(apriltag_detector_t *td, image_u8_t *im, bool overrideMode) +{ + DEBUG_INIT(); + //////////////////////////////////////////////////////// + // step 1. threshold the image, creating the edge image. + DEBUG_START(); + int w = im->width, h = im->height; + + image_u8_t *threshim = threshold(td, im); + int ts = threshim->stride; + DEBUG_PRINT(); + + //////////////////////////////////////////////////////// + // step 2. find connected components. + DEBUG_START(); + unionfind_t *uf = unionfind_create(w * h); + + for (int y = 0; y < h - 1; y++) { + do_unionfind_line(uf, threshim, h, w, ts, y); + } + DEBUG_PRINT(); + + DEBUG_START(); + uint32_t nclustermap = 16 * 1024; + struct uint32_zarray_entry **clustermap = fb_alloc0(nclustermap, FB_ALLOC_PREFER_SPEED); + nclustermap /= sizeof(struct uint32_zarray_entry*); + if (!nclustermap) fb_alloc_fail(); + DEBUG_PRINT(); + +#if 0 + DEBUG_START(); + static int max_need_idx = 0; + static int max_x, max_y, max_clustermap_bucket; + int dx = 0, dy = 1; + for (int y = 1; y < h-1; y++) { + for (int x = 1; x < w-1; x++) { + uint32_t rep0 = unionfind_get_representative(uf, y*w + x); + 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; + uint32_t clustermap_bucket = u64hash_2(clusterid); + uint32_t need_idx = clustermap_bucket % nclustermap; + if (clustermap_bucket > max_clustermap_bucket) { + max_need_idx = need_idx; + max_clustermap_bucket = clustermap_bucket; + max_x = x; + max_y = y; + } + } + } + DEBUG_PRINT(); +#endif + DEBUG_START(); + 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 + DEBUG_PRINT(); + + DEBUG_START(); + //////////////////////////////////////////////////////// + // step 3. process each connected component. + zarray_t *clusters = zarray_create_fail_ok(sizeof(zarray_t*)); //, uint32_zarray_hash_size(clustermap)); + DEBUG_PRINT(); + + DEBUG_START(); + if (clusters) { + for (int i = 0; i < (int)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); + } + } + } + DEBUG_PRINT(); + + DEBUG_START(); + int sz = clusters ? zarray_size(clusters) : 0; + if (1) { + for (int i = 0; i < (int)nclustermap; i++) { + struct uint32_zarray_entry *entry = clustermap[i]; + while (entry) { + struct uint32_zarray_entry *tmp = entry->next; + free(entry); + entry = tmp; + } + } + if (clustermap) fb_free(clustermap); // clustermap + } + DEBUG_PRINT(); + + DEBUG_START(); + unionfind_destroy(uf); + + if (threshim->buf) fb_free(threshim->buf); // threshim->buf + if (threshim) fb_free(threshim); // threshim + + zarray_t *quads = zarray_create_fail_ok(sizeof(struct quad)); + DEBUG_PRINT(); + + DEBUG_START(); + 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); + } + } + } + DEBUG_PRINT(); + + DEBUG_START(); + // 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)); + } + DEBUG_PRINT(); + 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 < (int32_t)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 < (int32_t)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 < (int32_t)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) +{ + (void)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 < (int)(sizeof(patterns)/(5*sizeof(float))); pattern_idx ++) { + float *pattern = &patterns[pattern_idx * 5]; + + int is_white = pattern[4]; + + for (int i = 0; i < 2*(int)family->black_border + (int)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 < (int)family->d * (int)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) +{ + (void)user; + return quad_goodness(family, im, quad); +} + +float score_decodability(apriltag_family_t *family, image_u8_t *im, struct quad *quad, void *user) +{ + (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) +{ + (void)td; + 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 = {0}; + + 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); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +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) +{ + DEBUG_INIT(); + DEBUG_START(); + // 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(); + DEBUG_PRINT(); + + DEBUG_START(); + 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); + } + + DEBUG_PRINT(); + + DEBUG_START(); + image_t img; + img.w = roi->w; + img.h = roi->h; + img.pixfmt = PIXFORMAT_GRAYSCALE; + img.data = fb_alloc(image_size(&img), FB_ALLOC_NO_HINT); + imlib_draw_image(&img, ptr, 0, 0, 1.f, 1.f, roi, -1, 256, NULL, NULL, 0, NULL, NULL, NULL); + DEBUG_PRINT(); + + DEBUG_START(); + 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); + DEBUG_PRINT(); + + DEBUG_START(); + 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 = det->c[0] + roi->x; + lnk_data.centroid_y = 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); + } + DEBUG_PRINT(); + + DEBUG_START(); + apriltag_detections_destroy(detections); + fb_free(img.data); // grayscale_image; + apriltag_detector_destroy(td); + // umm_init_x() is not implemented, so it does not need to free memory + // fb_free(); // umm_init_x(); + DEBUG_PRINT(); +} + +#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.data = fb_alloc(image_size(&img), FB_ALLOC_NO_HINT); + imlib_draw_image(&img, ptr, 0, 0, 1.f, 1.f, roi, -1, 256, NULL, NULL, 0, NULL, 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); + det1->H = NULL; + matd_destroy(det1->Hinv); + det1->Hinv = NULL; + zarray_remove_index(detections, i1, 1); + i1--; // retry the same index + goto retry1; + } else { + // keep det1, destroy det0 + matd_destroy(det0->H); + det0->H = NULL; + matd_destroy(det0->Hinv); + det0->Hinv = NULL; + zarray_remove_index(detections, i0, 1); + i0--; // retry the same index. + goto retry0; + } + } + + retry1: ; + } + + retry0: ; + } + + zarray_destroy(poly0); + zarray_destroy(poly1); + } + + 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 < (int)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); + } + + if (point_buffer) fb_free(point_buffer); // point_buffer + if (mag_buffer) fb_free(mag_buffer); // mag_buffer + if (theta_buffer) fb_free(theta_buffer); // theta_buffer + + for (int i = 0; i < zarray_size(detections); i++) { + struct quad *quad_original; + zarray_get_volatile(detections, i, &quad_original); + matd_destroy(quad_original->H); + matd_destroy(quad_original->Hinv); + } + zarray_destroy(detections); + if (img.data) fb_free(img.data); // grayscale_image; + apriltag_detector_destroy(td); + // umm_init_x() is not implemented, so it does not need to free memory + // fb_free(); // 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: { + pixel_rgb_t *tmp = (pixel_rgb_t *) data; + + for (int y = 0, yy = h; y < yy; y++) { + pixel_rgb_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)) { + pixel_rgb_t *ptr = tmp + (w * sourceY); + pixel_rgb_t pixel = IMAGE_GET_RGB888_PIXEL_FAST(ptr, sourceX); + IMAGE_PUT_RGB888_PIXEL_FAST(row_ptr, x, pixel); + } + } + } + break; + } + default: { + break; + } + } + } else { // warp perspective + 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: { + pixel_rgb_t *tmp = (pixel_rgb_t *) data; + + for (int y = 0, yy = h; y < yy; y++) { + pixel_rgb_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)) { + pixel_rgb_t *ptr = tmp + (w * sourceY); + pixel_rgb_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); + + // umm_init_x() is not implemented, so it does not need to free memory + // fb_free(); // umm_init_x(); + + if (data) fb_free(data); +} +#endif //IMLIB_ENABLE_ROTATION_CORR *INDENT-ON* +#pragma GCC diagnostic pop diff --git a/components/3rd_party/omv/omv/imlib/bayer.c b/components/3rd_party/omv/omv/imlib/bayer.c new file mode 100644 index 00000000..1a4722c6 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/bayer.c @@ -0,0 +1,890 @@ +/* + * 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; + } + 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; + } + 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; + } + } + + 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; + } + } + + 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; + } + } + } + } +} diff --git a/components/3rd_party/omv/omv/imlib/binary.c b/components/3rd_party/omv/omv/imlib/binary.c new file mode 100644 index 00000000..4269169d --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/binary.c @@ -0,0 +1,1238 @@ +/* + * 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++) { + pixel_rgb_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++) { + pixel_rgb_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++) { + pixel_rgb_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++) { + pixel_rgb_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); + pixel_rgb_t *out_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(out, y); + for (int x = 0, xx = img->w; x < xx; x++) { + pixel_rgb_t 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++) { + pixel_rgb_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); + pixel_rgb_t *out_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(out, y); + for (int x = 0, xx = img->w; x < xx; x++) { + pixel_rgb_t 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 = COLOR_R8_G8_B8_TO_RGB888(0, 0, 0); + IMAGE_PUT_RGB888_PIXEL_FAST(out_row_ptr, x, pixel); + } + } + } + } + break; + } + default: { + break; + } + } + + if (bmp.data) 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; + } + 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: { + pixel_rgb_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++) { + pixel_rgb_t src_pixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + pixel_rgb_t other_pixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_t *) other), i); + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, COLOR_R8_G8_B8_TO_RGB888( + COLOR_RGB888_TO_R8(src_pixel) & COLOR_RGB888_TO_R8(other_pixel), + COLOR_RGB888_TO_G8(src_pixel) & COLOR_RGB888_TO_G8(other_pixel), + COLOR_RGB888_TO_B8(src_pixel) & COLOR_RGB888_TO_B8(other_pixel))); + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + pixel_rgb_t src_pixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + pixel_rgb_t other_pixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_t *) other), i); + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, COLOR_R8_G8_B8_TO_RGB888( + COLOR_RGB888_TO_R8(src_pixel) & COLOR_RGB888_TO_R8(other_pixel), + COLOR_RGB888_TO_G8(src_pixel) & COLOR_RGB888_TO_G8(other_pixel), + COLOR_RGB888_TO_B8(src_pixel) & COLOR_RGB888_TO_B8(other_pixel))); + } + } + } + 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: { + pixel_rgb_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++) { + pixel_rgb_t src_pixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + pixel_rgb_t other_pixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_t *) other), i); + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, COLOR_R8_G8_B8_TO_RGB888( + COLOR_RGB888_TO_R8(src_pixel) & ~COLOR_RGB888_TO_R8(other_pixel), + COLOR_RGB888_TO_G8(src_pixel) & ~COLOR_RGB888_TO_G8(other_pixel), + COLOR_RGB888_TO_B8(src_pixel) & ~COLOR_RGB888_TO_B8(other_pixel))); + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + pixel_rgb_t src_pixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + pixel_rgb_t other_pixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_t *) other), i); + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, COLOR_R8_G8_B8_TO_RGB888( + COLOR_RGB888_TO_R8(src_pixel) & ~COLOR_RGB888_TO_R8(other_pixel), + COLOR_RGB888_TO_G8(src_pixel) & ~COLOR_RGB888_TO_G8(other_pixel), + COLOR_RGB888_TO_B8(src_pixel) & ~COLOR_RGB888_TO_B8(other_pixel))); + } + } + } + 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: { + pixel_rgb_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++) { + pixel_rgb_t src_pixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + pixel_rgb_t other_pixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_t *) other), i); + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, COLOR_R8_G8_B8_TO_RGB888( + COLOR_RGB888_TO_R8(src_pixel) | COLOR_RGB888_TO_R8(other_pixel), + COLOR_RGB888_TO_G8(src_pixel) | COLOR_RGB888_TO_G8(other_pixel), + COLOR_RGB888_TO_B8(src_pixel) | COLOR_RGB888_TO_B8(other_pixel))); + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + pixel_rgb_t src_pixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + pixel_rgb_t other_pixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_t *) other), i); + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, COLOR_R8_G8_B8_TO_RGB888( + COLOR_RGB888_TO_R8(src_pixel) | COLOR_RGB888_TO_R8(other_pixel), + COLOR_RGB888_TO_G8(src_pixel) | COLOR_RGB888_TO_G8(other_pixel), + COLOR_RGB888_TO_B8(src_pixel) | COLOR_RGB888_TO_B8(other_pixel))); + } + } + } + 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: { + pixel_rgb_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++) { + pixel_rgb_t src_pixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + pixel_rgb_t other_pixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_t *) other), i); + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, COLOR_R8_G8_B8_TO_RGB888( + COLOR_RGB888_TO_R8(src_pixel) | ~COLOR_RGB888_TO_R8(other_pixel), + COLOR_RGB888_TO_G8(src_pixel) | ~COLOR_RGB888_TO_G8(other_pixel), + COLOR_RGB888_TO_B8(src_pixel) | ~COLOR_RGB888_TO_B8(other_pixel))); + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + pixel_rgb_t src_pixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + pixel_rgb_t other_pixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_t *) other), i); + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, COLOR_R8_G8_B8_TO_RGB888( + COLOR_RGB888_TO_R8(src_pixel) | ~COLOR_RGB888_TO_R8(other_pixel), + COLOR_RGB888_TO_G8(src_pixel) | ~COLOR_RGB888_TO_G8(other_pixel), + COLOR_RGB888_TO_B8(src_pixel) | ~COLOR_RGB888_TO_B8(other_pixel))); + } + } + } + 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: { + pixel_rgb_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++) { + pixel_rgb_t src_pixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + pixel_rgb_t other_pixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_t *) other), i); + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, COLOR_R8_G8_B8_TO_RGB888( + COLOR_RGB888_TO_R8(src_pixel) ^ COLOR_RGB888_TO_R8(other_pixel), + COLOR_RGB888_TO_G8(src_pixel) ^ COLOR_RGB888_TO_G8(other_pixel), + COLOR_RGB888_TO_B8(src_pixel) ^ COLOR_RGB888_TO_B8(other_pixel))); + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + pixel_rgb_t src_pixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + pixel_rgb_t other_pixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_t *) other), i); + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, COLOR_R8_G8_B8_TO_RGB888( + COLOR_RGB888_TO_R8(src_pixel) ^ COLOR_RGB888_TO_R8(other_pixel), + COLOR_RGB888_TO_G8(src_pixel) ^ COLOR_RGB888_TO_G8(other_pixel), + COLOR_RGB888_TO_B8(src_pixel) ^ COLOR_RGB888_TO_B8(other_pixel))); + } + } + } + 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: { + pixel_rgb_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++) { + pixel_rgb_t src_pixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + pixel_rgb_t other_pixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_t *) other), i); + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, COLOR_R8_G8_B8_TO_RGB888( + COLOR_RGB888_TO_R8(src_pixel) ^ ~COLOR_RGB888_TO_R8(other_pixel), + COLOR_RGB888_TO_G8(src_pixel) ^ ~COLOR_RGB888_TO_G8(other_pixel), + COLOR_RGB888_TO_B8(src_pixel) ^ ~COLOR_RGB888_TO_B8(other_pixel))); + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + pixel_rgb_t src_pixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + pixel_rgb_t other_pixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_t *) other), i); + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, COLOR_R8_G8_B8_TO_RGB888( + COLOR_RGB888_TO_R8(src_pixel) ^ ~COLOR_RGB888_TO_R8(other_pixel), + COLOR_RGB888_TO_G8(src_pixel) ^ ~COLOR_RGB888_TO_G8(other_pixel), + COLOR_RGB888_TO_B8(src_pixel) ^ ~COLOR_RGB888_TO_B8(other_pixel))); + } + } + } + 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)); + } + + if (buf.data) 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)); + } + + if (buf.data) 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)); + } + + if (buf.data) 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++) { + pixel_rgb_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + pixel_rgb_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++) { + pixel_rgb_t pixel = IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x); + IMAGE_PUT_RGB888_PIXEL_FAST(buf_row_ptr, x, pixel); + + if (x > ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { // faster + for (int j = -ksize; j <= ksize; j++) { + pixel_rgb_t *k_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img,y+j); + // subtract old left column and add new right column + acc -= COLOR_RGB888_TO_BINARY(IMAGE_GET_RGB888_PIXEL_FAST(k_row_ptr,x-ksize-1)) > 0; + acc += COLOR_RGB888_TO_BINARY(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++) { + pixel_rgb_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 += COLOR_RGB888_TO_BINARY(IMAGE_GET_RGB888_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1)))) > 0; + } + } + } + + // Note: This code must be here because we need to calculate acc before this. + if (mask && (!image_get_mask_pixel(mask, x, y))) { + continue; // Short circuit. + } + + if (!e_or_d) { + // Preserve original pixel value... or clear it. + if (acc < threshold) { + pixel_rgb_t min = {.r = (COLOR_RGB888_BINARY_MIN >> 16) & 0xff, + .g = (COLOR_RGB888_BINARY_MIN >> 8) & 0xff, + .b = (COLOR_RGB888_BINARY_MIN) & 0xff}; + IMAGE_PUT_RGB888_PIXEL_FAST(buf_row_ptr, x, min); + } + } else { + // Preserve original pixel value... or set it. + if (acc > threshold) { + pixel_rgb_t max = {.r = (COLOR_RGB888_BINARY_MAX >> 16) & 0xff, + .g = (COLOR_RGB888_BINARY_MAX >> 8) & 0xff, + .b = (COLOR_RGB888_BINARY_MAX) & 0xff}; + IMAGE_PUT_RGB888_PIXEL_FAST(buf_row_ptr, x, 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); + if (temp.data) 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); + if (temp.data) fb_free(temp.data); +} +#endif diff --git a/components/3rd_party/omv/omv/imlib/blob.c b/components/3rd_party/omv/omv/imlib/blob.c new file mode 100644 index 00000000..646f923d --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/blob.c @@ -0,0 +1,1941 @@ +/* + * 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; + } + + uint16_t 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); + + uint16_t 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)); + + 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) { + pixel_rgb_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; + pixel_rgb_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); + } + if (bmp.data) fb_free(bmp.data); // bitmap + + if (merge) { + for (;;) { + bool merge_occured = false; + + list_t out_temp; + 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: { + pixel_rgb_t rgb_seed_threshold = COLOR_R8_G8_B8_TO_RGB888(seed_threshold & 0xff, (seed_threshold >> 8) & 0xff, (seed_threshold >> 16) & 0xff); + pixel_rgb_t rgb_floating_threshold = COLOR_R8_G8_B8_TO_RGB888(floating_threshold & 0xff, (floating_threshold >> 8) & 0xff, (floating_threshold >> 16) & 0xff); + for(pixel_rgb_t seed_pixel = IMAGE_GET_RGB888_PIXEL(img, x, y);;) { + int left = x, right = x; + pixel_rgb_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, rgb_seed_threshold) + && COLOR_BOUND_RGB888(IMAGE_GET_RGB888_PIXEL_FAST(row, left - 1), + IMAGE_GET_RGB888_PIXEL_FAST(row, left), rgb_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, rgb_seed_threshold) + && COLOR_BOUND_RGB888(IMAGE_GET_RGB888_PIXEL_FAST(row, right + 1), + IMAGE_GET_RGB888_PIXEL_FAST(row, right), rgb_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) { + pixel_rgb_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, rgb_seed_threshold) + && COLOR_BOUND_RGB888(IMAGE_GET_RGB888_PIXEL_FAST(row, i), + IMAGE_GET_RGB888_PIXEL_FAST(old_row, i), rgb_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, rgb_seed_threshold) + && COLOR_BOUND_RGB888(IMAGE_GET_RGB888_PIXEL_FAST(row, i), + IMAGE_GET_RGB888_PIXEL_FAST(old_row, i), rgb_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/components/3rd_party/omv/omv/imlib/bmp.c b/components/3rd_party/omv/omv/imlib/bmp.c new file mode 100644 index 00000000..a0c36ba8 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/bmp.c @@ -0,0 +1,332 @@ +/* + * 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 "file_utils.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) { + file_read_check(fp, "BM", 2); + + uint32_t file_size; + file_read(fp, &file_size, 4); + file_read(fp, NULL, 4); + + uint32_t header_size; + file_read(fp, &header_size, 4); + if (file_size <= header_size) { + file_raise_corrupted(fp); + } + + uint32_t data_size = file_size - header_size; + if (data_size % 4) { + file_raise_corrupted(fp); + } + + uint32_t header_type; + file_read(fp, &header_type, 4); + if ((header_type != 40) // BITMAPINFOHEADER + && (header_type != 52) // BITMAPV2INFOHEADER + && (header_type != 56) // BITMAPV3INFOHEADER + && (header_type != 108) // BITMAPV4HEADER + && (header_type != 124)) { + file_raise_format(fp); // BITMAPV5HEADER + } + file_read(fp, &rs->bmp_w, 4); + file_read(fp, &rs->bmp_h, 4); + if ((rs->bmp_w == 0) || (rs->bmp_h == 0)) { + file_raise_corrupted(fp); + } + img->w = abs(rs->bmp_w); + img->h = abs(rs->bmp_h); + + file_read_check(fp, &(uint32_t) {1}, 2); + file_read(fp, &rs->bmp_bpp, 2); + if ((rs->bmp_bpp != 8) && (rs->bmp_bpp != 16) && (rs->bmp_bpp != 24)) { + file_raise_format(fp); + } + img->pixfmt = (rs->bmp_bpp == 8) ? PIXFORMAT_GRAYSCALE : PIXFORMAT_RGB565; + + file_read(fp, &rs->bmp_fmt, 4); + if ((rs->bmp_fmt != 0) && (rs->bmp_fmt != 3)) { + file_raise_format(fp); + } + + file_read_check(fp, &data_size, 4); + file_read(fp, NULL, 16); + + if (rs->bmp_bpp == 8) { + if (rs->bmp_fmt != 0) { + file_raise_format(fp); + } + if (header_type >= 52) { + // Skip past the remaining BITMAPV2INFOHEADER bytes. + for (int i = 0; i < 3; i++) { + file_read(fp, NULL, 4); + } + } + if (header_type >= 56) { + // Skip past the remaining BITMAPV3INFOHEADER bytes. + for (int i = 0; i < 1; i++) { + file_read(fp, NULL, 4); + } + } + if (header_type >= 108) { + // Skip past the remaining BITMAPV4HEADER bytes. + for (int i = 0; i < 13; i++) { + file_read(fp, NULL, 4); + } + } + if (header_type >= 124) { + // Skip past the remaining BITMAPV5HEADER bytes. + for (int i = 0; i < 4; i++) { + file_read(fp, NULL, 4); + } + } + // Color Table (1024 bytes) + for (int i = 0; i < 256; i++) { + file_read_check(fp, &(uint32_t) {((i) << 16) | ((i) << 8) | i}, 4); + } + } else if (rs->bmp_bpp == 16) { + if (rs->bmp_fmt != 3) { + file_raise_format(fp); + } + // Bit Masks (12 bytes) + file_read_check(fp, (uint32_t [3]) {0x1F << 11, 0x3F << 5, 0x1F}, 12); + if (header_type >= 56) { + // Skip past the remaining BITMAPV3INFOHEADER bytes. + for (int i = 0; i < 1; i++) { + file_read(fp, NULL, 4); + } + } + if (header_type >= 108) { + // Skip past the remaining BITMAPV4HEADER bytes. + for (int i = 0; i < 13; i++) { + file_read(fp, NULL, 4); + } + } + if (header_type >= 124) { + // Skip past the remaining BITMAPV5HEADER bytes. + for (int i = 0; i < 4; i++) { + file_read(fp, NULL, 4); + } + } + } else if (rs->bmp_bpp == 24) { + if (rs->bmp_fmt == 3) { + // Bit Masks (12 bytes) + file_read_check(fp, (uint32_t [3]) {0xFF << 16, 0xFF << 8, 0xFF}, 12); + } else if (header_type >= 52) { + // Skip past the remaining BITMAPV2INFOHEADER bytes. + for (int i = 0; i < 3; i++) { + file_read(fp, NULL, 4); + } + } + if (header_type >= 56) { + // Skip past the remaining BITMAPV3INFOHEADER bytes. + for (int i = 0; i < 1; i++) { + file_read(fp, NULL, 4); + } + } + if (header_type >= 108) { + // Skip past the remaining BITMAPV4HEADER bytes. + for (int i = 0; i < 13; i++) { + file_read(fp, NULL, 4); + } + } + if (header_type >= 124) { + // Skip past the remaining BITMAPV5HEADER bytes. + for (int i = 0; i < 4; i++) { + file_read(fp, NULL, 4); + } + } + } + + rs->bmp_row_bytes = (((img->w * rs->bmp_bpp) + 31) / 32) * 4; + if (data_size != (rs->bmp_row_bytes * img->h)) { + file_raise_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)) { + file_read(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; + file_read(fp, &pixel, 1); + 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; + file_read(fp, &pixel, 2); + 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; + file_read(fp, &b, 1); + file_read(fp, &g, 1); + file_read(fp, &r, 1); + uint16_t pixel = COLOR_R8_G8_B8_TO_RGB565(r, g, b); + if (j < img->w) { + if (rs->bmp_h < 0) { + // vertical flip + if (rs->bmp_w < 0) { + // horizontal flip + 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); + } + } + } + } + for (int j = 0, jj = rs->bmp_row_bytes % 3; j < jj; j++) { + file_read(fp, NULL, 1); + } + } + } +} + +void bmp_read(image_t *img, const char *path) { + FIL fp; + bmp_read_settings_t rs; + file_open(&fp, path, true, FA_READ | FA_OPEN_EXISTING); + bmp_read_geometry(&fp, img, path, &rs); + if (!img->pixels) { + img->pixels = xalloc(img->w * img->h * img->bpp); + } + bmp_read_pixels(&fp, img, img->h, &rs); + file_close(&fp); +} + +static void bmp_write_header(FIL *fp, uint32_t header_size, uint32_t data_size, + uint32_t bpp, uint32_t ncomp, rectangle_t *r) { + // File Header (14 bytes) + file_write(fp, "BM", 2); + file_write_long(fp, 14 + 40 + header_size + data_size); + file_write_long(fp, 0); + file_write_long(fp, 14 + 40 + header_size); + // Info Header (40 bytes) + file_write_long(fp, 40); + file_write_long(fp, r->w); + file_write_long(fp, -r->h); // store the image flipped (correctly) + file_write_short(fp, 1); + file_write_short(fp, bpp); + file_write_long(fp, ncomp); + file_write_long(fp, data_size); + file_write(fp, (uint8_t [16]) {0}, 16); +} + +void bmp_write_subimg(image_t *img, const char *path, rectangle_t *r) { + rectangle_t rect; + if (!rectangle_subimg(img, r, &rect)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("No intersection!")); + } + + FIL fp; + file_open(&fp, path, true, FA_WRITE | FA_CREATE_ALWAYS); + + 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; + // Write BMP file header + bmp_write_header(&fp, 1024, data_size, 8, 0, &rect); + // Write color Table (1024 bytes) + for (int i = 0; i < 256; i++) { + file_write_long(&fp, ((i) << 16) | ((i) << 8) | i); + } + if ((rect.x == 0) && (rect.w == img->w) && (img->w == row_bytes)) { + file_write(&fp, img->pixels + (rect.y * img->w), rect.w * rect.h); + } else { + for (int i = 0; i < rect.h; i++) { + file_write(&fp, img->pixels + ((rect.y + i) * img->w) + rect.x, rect.w); + for (int j = 0; j < waste; j++) { + file_write_byte(&fp, 0); + } + } + } + } else { + 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; + // Write BMP file header + bmp_write_header(&fp, 12, data_size, 16, 3, &rect); + // Write Bit Masks (12 bytes) + file_write_long(&fp, 0x1F << 11); + file_write_long(&fp, 0x3F << 5); + file_write_long(&fp, 0x1F); + for (int i = 0; i < rect.h; i++) { + for (int j = 0; j < rect.w; j++) { + file_write_short(&fp, IM_GET_RGB565_PIXEL(img, (rect.x + j), (rect.y + i))); + } + for (int j = 0; j < waste; j++) { + file_write_short(&fp, 0); + } + } + } + file_close(&fp); +} +#endif //IMLIB_ENABLE_IMAGE_FILE_IO diff --git a/components/3rd_party/omv/omv/imlib/cascade.h b/components/3rd_party/omv/omv/imlib/cascade.h new file mode 100644 index 00000000..54d9d386 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/cascade.h @@ -0,0 +1,24 @@ +// *INDENT-OFF* +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}; +// *INDENT-ON* diff --git a/components/3rd_party/omv/omv/imlib/clahe.c b/components/3rd_party/omv/omv/imlib/clahe.c new file mode 100644 index 00000000..624d038b --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/clahe.c @@ -0,0 +1,469 @@ +/* + * 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 *) fb_alloc(sizeof(unsigned long) * uiNrX * uiNrY * uiNrBins, FB_ALLOC_NO_HINT); + 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; + } + if (pulMapArray) fb_free(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)), 2u); + int yTileSize = IM_MAX(uiMAX_REG_Y >> (10 - IM_MIN(IM_LOG2_32(img->h), 10)), 2u); + 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 = fb_alloc0(pImageW * pImageH * sizeof(kz_pixel_t), FB_ALLOC_NO_HINT); + + 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; + } + 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; + } + default: { + break; + } + } + + if (temp.data) fb_free(temp.data); +} diff --git a/components/3rd_party/omv/omv/imlib/collections.c b/components/3rd_party/omv/omv/imlib/collections.c new file mode 100644 index 00000000..14881830 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/collections.c @@ -0,0 +1,481 @@ +/* + * 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); + ptr->data = NULL; + } +} + +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); + ptr->data = NULL; + } +} + +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); + ptr->data = NULL; + } +} + +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 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/components/3rd_party/omv/omv/imlib/collections.h b/components/3rd_party/omv/omv/imlib/collections.h new file mode 100644 index 00000000..5b213314 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/collections.h @@ -0,0 +1,123 @@ +/* + * 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 + +//////////// +// 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 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); + +#endif /* __COLLECTIONS_H__ */ diff --git a/components/3rd_party/omv/omv/imlib/dmtx.c b/components/3rd_party/omv/omv/imlib/dmtx.c new file mode 100644 index 00000000..0c9767ac --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/dmtx.c @@ -0,0 +1,6411 @@ +/* + * 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; }) +#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 cos(x) cosf(x) +#define sin(x) sinf(x) +#define acos(x) acosf(x) +#define asin(x) asinf(x) + +// *INDENT-OFF* +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "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; +} + +/** + * receives 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)); + + /* Horizontal 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; + imlib_draw_image(&img, ptr, 0, 0, 1.f, 1.f, roi, -1, 256, NULL, NULL, 0, NULL, 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)); + + 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); + + // umm_init_x() is not implemented, so we does not need to free memory. + // fb_free(); // umm_init_x(); + if (ptr->pixfmt != PIXFORMAT_GRAYSCALE) { + if (grayscale_image) fb_free(grayscale_image); // grayscale_image; + } +} + +#pragma GCC diagnostic pop +#endif //IMLIB_ENABLE_DATAMATRICES +// *INDENT-ON* diff --git a/components/3rd_party/omv/omv/imlib/draw.c b/components/3rd_party/omv/omv/imlib/draw.c new file mode 100644 index 00000000..0b53a5a4 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/draw.c @@ -0,0 +1,5692 @@ +/* + * 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" +#include "omv_common.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); + } + default: { + // This shouldn't happen, at least we return a valid memory block + return img->data; + } + } +} + +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); + } + 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; + } + 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); + } + } + } + } +} + +// 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 + fast_floorf((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 + fast_floorf((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_CACHE_ALIGN); + + #ifdef IMLIB_ENABLE_DMA2D + data->dma2d_enabled = false; + data->dma2d_initialized = false; + + void *dst_buff = data->dst_row_override ? data->dst_row_override : data->dst_img->data; + + if (data->dma2d_request && (data->dst_img->pixfmt == PIXFORMAT_RGB565) && DMA_BUFFER(dst_buff) && + ((data->src_img_pixfmt == PIXFORMAT_GRAYSCALE) || + ((data->src_img_pixfmt == PIXFORMAT_RGB565) && (data->rgb_channel < 0) + && (data->alpha != 256) && (!data->color_palette) && (!data->alpha_palette)))) { + data->row_buffer[1] = fb_alloc(image_row_size, FB_ALLOC_CACHE_ALIGN); + data->dma2d_enabled = true; + data->dma2d_initialized = true; + + memset(&data->dma2d, 0, sizeof(data->dma2d)); + + data->dma2d.Instance = DMA2D; + data->dma2d.Init.Mode = DMA2D_M2M; + if (data->dst_img->pixfmt != data->src_img_pixfmt) { + data->dma2d.Init.Mode = DMA2D_M2M_PFC; + } + if ((data->alpha != 256) || data->alpha_palette) { + data->dma2d.Init.Mode = DMA2D_M2M_BLEND; + } + data->dma2d.Init.ColorMode = DMA2D_OUTPUT_RGB565; + data->dma2d.Init.OutputOffset = 0; + #if defined(MCU_SERIES_F7) || defined(MCU_SERIES_H7) + data->dma2d.Init.AlphaInverted = DMA2D_REGULAR_ALPHA; + data->dma2d.Init.RedBlueSwap = DMA2D_RB_REGULAR; + #endif + HAL_DMA2D_Init(&data->dma2d); + + data->dma2d.LayerCfg[0].InputOffset = 0; + data->dma2d.LayerCfg[0].InputColorMode = DMA2D_INPUT_RGB565; + data->dma2d.LayerCfg[0].AlphaMode = DMA2D_REPLACE_ALPHA; + data->dma2d.LayerCfg[0].InputAlpha = data->black_background ? 0x00 : 0xff; + #if defined(MCU_SERIES_F7) || defined(MCU_SERIES_H7) + data->dma2d.LayerCfg[0].AlphaInverted = DMA2D_REGULAR_ALPHA; + data->dma2d.LayerCfg[0].RedBlueSwap = DMA2D_RB_REGULAR; + #endif + #if defined(MCU_SERIES_H7) + data->dma2d.LayerCfg[0].ChromaSubSampling = DMA2D_NO_CSS; + #endif + HAL_DMA2D_ConfigLayer(&data->dma2d, 0); + + switch (data->src_img_pixfmt) { + case PIXFORMAT_GRAYSCALE: { + data->dma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_L8; + data->dma2d.LayerCfg[1].AlphaMode = DMA2D_COMBINE_ALPHA; + uint32_t *clut = fb_alloc(256 * sizeof(uint32_t), FB_ALLOC_CACHE_ALIGN); + + if (!data->alpha_palette) { + if (!data->color_palette) { + for (int i = 0; i < 256; i++) { + clut[i] = (0xff << 24) | COLOR_Y_TO_RGB888(i); + } + } else { + for (int i = 0; i < 256; i++) { + int pixel = data->color_palette[i]; + clut[i] = + (0xff << + 24) | + (COLOR_RGB565_TO_R8(pixel) << 16) | (COLOR_RGB565_TO_G8(pixel) << 8) | COLOR_RGB565_TO_B8( + pixel); + } + } + } else { + if (!data->color_palette) { + for (int i = 0; i < 256; i++) { + clut[i] = (data->alpha_palette[i] << 24) | COLOR_Y_TO_RGB888(i); + } + } else { + for (int i = 0; i < 256; i++) { + int pixel = data->color_palette[i]; + clut[i] = + (data->alpha_palette[i] << + 24) | + (COLOR_RGB565_TO_R8(pixel) << 16) | (COLOR_RGB565_TO_G8(pixel) << 8) | COLOR_RGB565_TO_B8( + pixel); + } + } + } + + DMA2D_CLUTCfgTypeDef cfg; + cfg.pCLUT = clut; + cfg.CLUTColorMode = DMA2D_CCM_ARGB8888; + cfg.Size = 255; + #if defined(MCU_SERIES_F7) || defined(MCU_SERIES_H7) + SCB_CleanDCache_by_Addr(clut, 256 * sizeof(uint32_t)); + #endif + HAL_DMA2D_CLUTLoad(&data->dma2d, cfg, 1); + HAL_DMA2D_PollForTransfer(&data->dma2d, 1000); + break; + } + case PIXFORMAT_RGB565: { + data->dma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565; + data->dma2d.LayerCfg[1].AlphaMode = DMA2D_REPLACE_ALPHA; + break; + } + default: { + break; + } + } + + data->dma2d.LayerCfg[1].InputOffset = 0; + data->dma2d.LayerCfg[1].InputAlpha = fast_roundf((data->alpha * 255) / 256.f); + #if defined(MCU_SERIES_F7) || defined(MCU_SERIES_H7) + data->dma2d.LayerCfg[1].AlphaInverted = DMA2D_REGULAR_ALPHA; + data->dma2d.LayerCfg[1].RedBlueSwap = DMA2D_RB_REGULAR; + #endif + #if defined(MCU_SERIES_H7) + data->dma2d.LayerCfg[1].ChromaSubSampling = DMA2D_NO_CSS; + #endif + HAL_DMA2D_ConfigLayer(&data->dma2d, 1); + } else { + data->row_buffer[1] = data->row_buffer[0]; + } + #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); + data->smuad_alpha_palette = NULL; + } + #ifdef IMLIB_ENABLE_DMA2D + if (data->dma2d_initialized) { + if (!data->callback) { + HAL_DMA2D_PollForTransfer(&data->dma2d, 1000); + } + HAL_DMA2D_DeInit(&data->dma2d); + if (data->src_img_pixfmt == PIXFORMAT_GRAYSCALE) { + #error "fb_free() is commented, but when using dma2d we need to free the memory of clut here." + // fb_free(); // clut... + } + if (data->row_buffer[1]) { + fb_free(data->row_buffer[1]); // data->row_buffer[1] + data->row_buffer[1] = NULL; + } + } + #endif + if (data->row_buffer[0]) { + fb_free(data->row_buffer[0]); // data->row_buffer[0] + data->row_buffer[0] = NULL; + } +} + +#ifdef IMLIB_ENABLE_DMA2D +void imlib_draw_row_deinit_all() { + DMA2D_HandleTypeDef dma2d = {}; + dma2d.Instance = DMA2D; + HAL_DMA2D_DeInit(&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 + if (data->dma2d_enabled && (!DMA_BUFFER(row_buffer))) { + data->dma2d_enabled = false; + } + #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 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; + } + 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; + } + 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 + // Confirm destination row starts and end on a complete cache line. + if (data->dma2d_enabled + && ((((uint32_t) (dst16 + x_start)) % OMV_ALLOC_ALIGNMENT) + || (((uint32_t) (dst16 + x_end)) % OMV_ALLOC_ALIGNMENT))) { + data->dma2d_enabled = false; + } + if (data->dma2d_enabled) { + if (!data->callback) { + HAL_DMA2D_PollForTransfer(&data->dma2d, 1000); + } + #if defined(MCU_SERIES_F7) || defined(MCU_SERIES_H7) + // Memory referenced by src8 between (x_end - x_start) may or may not be + // cache algined. However, after being flushed it shouldn't change again + // so DMA2D can safety read the line of pixels. + SCB_CleanDCache_by_Addr((uint32_t *) src8, (x_end - x_start) * sizeof(uint8_t)); + // DMA2D will overwrite this area. dst16 (x_end - x_start) must be cache + // aligned or the line of pixels will be corrutped. + SCB_InvalidateDCache_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 + // Confirm destination row starts and end on a complete cache line. + if (data->dma2d_enabled + && ((((uint32_t) (dst16 + x_start)) % OMV_ALLOC_ALIGNMENT) + || (((uint32_t) (dst16 + x_end)) % OMV_ALLOC_ALIGNMENT))) { + data->dma2d_enabled = false; + } + if (data->dma2d_enabled) { + if (!data->callback) { + HAL_DMA2D_PollForTransfer(&data->dma2d, 1000); + } + #if defined(MCU_SERIES_F7) || defined(MCU_SERIES_H7) + // Memory referenced by src16 between (x_end - x_start) may or may not be + // cache algined. However, after being flushed it shouldn't change again + // so DMA2D can safety read the line of pixels. + SCB_CleanDCache_by_Addr((uint32_t *) src16, (x_end - x_start) * sizeof(uint16_t)); + // DMA2D will overwrite this area. dst16 (x_end - x_start) must be cache + // aligned or the line of pixels will be corrutped. + SCB_InvalidateDCache_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; + } + // 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_RGB566_0 + #undef BLEND_RGB566 +} + +static void imlib_draw_image_scale_and_center_helper(image_t *dst_img, + int src_img_w, + int src_img_h, + int *src_width_scaled, + int *src_height_scaled, + int *dst_x_start, + int *dst_y_start, + float *x_scale, + float *y_scale, + image_hint_t *hint) { + if (*hint & (IMAGE_HINT_SCALE_ASPECT_KEEP | IMAGE_HINT_SCALE_ASPECT_EXPAND | IMAGE_HINT_SCALE_ASPECT_IGNORE)) { + float xs = ((*hint & IMAGE_HINT_TRANSPOSE) ? dst_img->h : dst_img->w) / ((float) src_img_w); + float ys = ((*hint & IMAGE_HINT_TRANSPOSE) ? dst_img->w : dst_img->h) / ((float) src_img_h); + if (*hint & IMAGE_HINT_SCALE_ASPECT_IGNORE) { + *x_scale *= xs; + *y_scale *= ys; + } else { + float scale = (*hint & IMAGE_HINT_SCALE_ASPECT_KEEP) ? IM_MIN(xs, ys) : IM_MAX(xs, ys); + *x_scale *= scale; + *y_scale *= scale; + } + *hint &= ~(IMAGE_HINT_SCALE_ASPECT_KEEP | IMAGE_HINT_SCALE_ASPECT_EXPAND | IMAGE_HINT_SCALE_ASPECT_IGNORE); + } + + *src_width_scaled = fast_floorf(fast_fabsf(*x_scale) * src_img_w); + *src_height_scaled = fast_floorf(fast_fabsf(*y_scale) * src_img_h); + + if (*hint & IMAGE_HINT_TRANSPOSE) { + int temp = *src_width_scaled; + *src_width_scaled = *src_height_scaled; + *src_height_scaled = temp; + } + + if (*hint & IMAGE_HINT_CENTER) { + *dst_x_start += fast_floorf((dst_img->w - *src_width_scaled) / 2.f); + *dst_y_start += fast_floorf((dst_img->h - *src_height_scaled) / 2.f); + *hint &= ~IMAGE_HINT_CENTER; + } +} + +// False == Image is black, True == rect valid +void imlib_draw_image_get_bounds(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, + point_t *p0, + point_t *p1) { + p0->x = -1; + + int src_img_w = roi ? roi->w : src_img->w; + int src_img_h = roi ? roi->h : src_img->h; + + int src_width_scaled, src_height_scaled; + imlib_draw_image_scale_and_center_helper(dst_img, src_img_w, src_img_h, &src_width_scaled, &src_height_scaled, + &dst_x_start, &dst_y_start, &x_scale, &y_scale, &hint); + + if (!alpha) { + return; + } + + if (alpha_palette) { + int i = 0; + while ((i < 256) && (!alpha_palette[i])) { + i++; + } + if (i == 256) { + // zero alpha palette + return; + } + } + + // 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; + } + + 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 because 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; + } + + p0->x = dst_x_start; + p1->x = dst_x_end; + p0->y = dst_y_start; + p1->y = dst_y_end; + + return; +} + +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 *callback_arg, + 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; + } + if (hint & IMAGE_HINT_HMIRROR) { + dst_delta_x = -dst_delta_x; + } + + int dst_delta_y = 1; // positive direction + if (y_scale < 0.f) { + // flip Y + dst_delta_y = -1; + y_scale = -y_scale; + } + if (hint & IMAGE_HINT_VFLIP) { + dst_delta_y = -dst_delta_y; + } + + int src_img_w = roi ? roi->w : src_img->w; + int w_start = roi ? roi->x : 0, w_start_p_1 = w_start + 1, w_start_p_2 = w_start_p_1 + 1; + int w_limit = w_start + src_img_w - 1; + int w_limit_m_1 = w_limit - 1; + + int src_img_h = roi ? roi->h : src_img->h; + int h_start = roi ? roi->y : 0, h_start_p_1 = h_start + 1, h_start_p_2 = h_start_p_1 + 1; + int h_limit = h_start + src_img_h - 1; + int h_limit_m_1 = h_limit - 1; + + int src_width_scaled, src_height_scaled; + imlib_draw_image_scale_and_center_helper(dst_img, src_img_w, src_img_h, &src_width_scaled, &src_height_scaled, + &dst_x_start, &dst_y_start, &x_scale, &y_scale, &hint); + + // 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 + } + } + + int dst_x_start_backup = dst_x_start; + int dst_y_start_backup = dst_y_start; + + // 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; + } + 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 because 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 + new_src_img.pixfmt = color_palette ? PIXFORMAT_RGB565 : PIXFORMAT_GRAYSCALE; + new_src_img.data = fb_alloc(image_size(&new_src_img), FB_ALLOC_CACHE_ALIGN); + imlib_draw_image(&new_src_img, src_img, 0, 0, 1.f, 1.f, NULL, + rgb_channel, 256, color_palette, NULL, 0, NULL, 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; + // 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); + + 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_CACHE_ALIGN); + + switch (new_src_img.pixfmt) { + case PIXFORMAT_BINARY: + case PIXFORMAT_GRAYSCALE: + 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_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_CACHE_ALIGN); + memcpy(new_src_img.data, src_img->data, size); + } + + src_img = &new_src_img; + } + + // To improve transpose performance we will split the operation up into chunks that fit in + // onchip RAM. These chunks will then be copied to the target buffer in an efficent manner. + // However, this doesn't work when the image is being scaled. So, we have to scale the image + // first if that is requested. + if (hint & IMAGE_HINT_TRANSPOSE) { + rectangle_t t_roi = {}; + image_t t_src_img; + t_src_img.pixfmt = src_img->pixfmt; + + // Are we scaling? + if ((src_x_frac != 65536) || (src_y_frac != 65536)) { + t_src_img.w = t_roi.w = src_height_scaled; // was transposed + t_src_img.h = t_roi.h = src_width_scaled; // was transposed + t_src_img.data = fb_alloc(image_size(&t_src_img), FB_ALLOC_CACHE_ALIGN); + imlib_draw_image(&t_src_img, src_img, 0, 0, x_scale, y_scale, roi, + -1, 256, NULL, NULL, + hint & (IMAGE_HINT_AREA | IMAGE_HINT_BILINEAR | IMAGE_HINT_BICUBIC), + NULL, NULL, NULL); + } else { + memcpy(&t_roi, roi, sizeof(rectangle_t)); + t_src_img.w = src_img->w; + t_src_img.h = src_img->h; + t_src_img.data = src_img->data; + } + + uint32_t size; + void *data = fb_alloc_all(&size, FB_ALLOC_PREFER_SPEED | FB_ALLOC_CACHE_ALIGN); + + // line_num stores how many lines we can do at a time with on-chip RAM. + image_t temp = {.w = t_roi.w, .h = t_roi.h, .pixfmt = t_src_img.pixfmt}; + int line_num = size / image_line_size(&temp); + + // Work top to bottom transposing as many lines at a time in a chunk of the image. + for (int i = t_roi.y; i < t_roi.h; i += line_num) { + line_num = IM_MIN(line_num, (t_roi.h - i)); + + // Make an image that is a slice of the input image. + image_t in = {.w = t_src_img.w, .h = line_num, .pixfmt = t_src_img.pixfmt}; + in.data = t_src_img.data + (image_line_size(&t_src_img) * + ((dst_delta_y < 0) ? (t_roi.h - i - 1) : i)); + + // Make an image that will hold the transposed output. + image_t out = in; + out.w = line_num; + out.h = t_roi.w; + out.data = data; + + switch (t_src_img.pixfmt) { + case PIXFORMAT_BINARY: { + for (int y = 0; y < in.h; y++) { + int y_2 = (dst_delta_y < 0) ? -y : y; + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR((&in), y_2); + if (dst_delta_x < 0) { + for (int x = 0; x < t_roi.w; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, (t_roi.x + (t_roi.w - x - 1))); + IMAGE_PUT_BINARY_PIXEL((&out), y, x, pixel); + } + } else { + for (int x = 0; x < t_roi.w; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, (t_roi.x + x)); + IMAGE_PUT_BINARY_PIXEL((&out), y, x, pixel); + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + for (int y = 0; y < in.h; y++) { + int y_2 = (dst_delta_y < 0) ? -y : y; + uint8_t *i_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR((&in), y_2) + t_roi.x; + uint8_t *o_row_ptr = ((uint8_t *) out.data) + y; + if (dst_delta_x < 0) { + for (int x = t_roi.w - 1; x >= 0; x--, o_row_ptr += line_num) { + *o_row_ptr = i_row_ptr[x]; + } + } else { + for (int x = 0; x < t_roi.w; x++, o_row_ptr += line_num) { + *o_row_ptr = i_row_ptr[x]; + } + } + } + break; + } + case PIXFORMAT_RGB565: { + for (int y = 0; y < in.h; y++) { + int y_2 = (dst_delta_y < 0) ? -y : y; + uint16_t *i_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR((&in), y_2) + t_roi.x; + uint16_t *o_row_ptr = ((uint16_t *) out.data) + y; + if (dst_delta_x < 0) { + for (int x = t_roi.w - 1; x >= 0; x--, o_row_ptr += line_num) { + *o_row_ptr = i_row_ptr[x]; + } + } else { + for (int x = 0; x < t_roi.w; x++, o_row_ptr += line_num) { + *o_row_ptr = i_row_ptr[x]; + } + } + } + break; + } + default: { + break; + } + } + + imlib_draw_image(dst_img, &out, dst_x_start_backup + i, dst_y_start_backup, 1.f, 1.f, NULL, + rgb_channel, alpha, color_palette, alpha_palette, + hint & IMAGE_HINT_BLACK_BACKGROUND, + callback, callback_arg, dst_row_override); + } + + if(data) fb_free(data); // fb_alloc_all + + if (t_src_img.data != src_img->data) { + if (t_src_img.data) fb_free(t_src_img.data); + } + + if (&new_src_img == src_img) { + if (new_src_img.data) fb_free(new_src_img.data); + } + + return; + } + + 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.callback_arg = callback_arg; + 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 >= h_limit) { + src_y_index_end = h_limit + 1; + } + 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 >= w_limit) { + src_x_index_end = w_limit + 1; + } + 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 >= h_limit) { + src_y_index_end = h_limit + 1; + } + 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 >= w_limit) { + src_x_index_end = w_limit + 1; + } + 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 >= h_limit) { + src_y_index_end = h_limit + 1; + } + 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 >= w_limit) { + src_x_index_end = w_limit + 1; + } + 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; + } + default: { + break; + } + } + } else { + // slow + switch (src_img->pixfmt) { + case PIXFORMAT_BINARY: { + int t_b_weight_sum = 256 + ((src_y_frac >> 8) & 0xFF); + int l_r_weight_sum = 256 + ((src_x_frac >> 8) & 0xFF); + 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 + 255) >> 8) & 0xFF); + int b_y_weight = ((src_y_accum + src_y_frac + 255) >> 8) & 0xFF; + // Since src_y_index_end is inclusive this should be 256 when there's perfect overlap. + if ((!b_y_weight) && (t_y_weight < t_b_weight_sum)) { + 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 this or 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 + } + } + + // Handle discontinuities. + if ((t_y_weight + b_y_weight) < 256) { + t_y_weight += 128; + b_y_weight += 128; + } + + // Weights must be balanced. + if ((t_y_weight + b_y_weight) > t_b_weight_sum) { + b_y_weight -= 1; // It's only ever over by 1. + } + + 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 + 255) >> 8) & 0xFF); + int r_x_weight = ((src_x_accum + src_x_frac + 255) >> 8) & 0xFF; + // Since src_x_index_end is inclusive this should be 256 when there's perfect overlap. + if ((!r_x_weight) && (l_x_weight < l_r_weight_sum)) { + 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 this or 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 + } + } + + // Handle discontinuities. + if ((l_x_weight + r_x_weight) < 256) { + l_x_weight += 128; + r_x_weight += 128; + } + + // Weights must be balanced. + if ((l_x_weight + r_x_weight) > l_r_weight_sum) { + r_x_weight -= 1; // It's only ever over by 1. + } + + 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: { + int t_b_weight_sum = 256 + ((src_y_frac >> 8) & 0xFF); + int l_r_weight_sum = 256 + ((src_x_frac >> 8) & 0xFF); + 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 + 255) >> 8) & 0xFF); + int b_y_weight = ((src_y_accum + src_y_frac + 255) >> 8) & 0xFF; + // Since src_y_index_end is inclusive this should be 256 when there's perfect overlap. + if ((!b_y_weight) && (t_y_weight < t_b_weight_sum)) { + 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 this or 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 + } + } + + // Handle discontinuities. + if ((t_y_weight + b_y_weight) < t_b_weight_sum) { + t_y_weight += 128; + b_y_weight += 128; + } + + // Weights must be balanced. + if ((t_y_weight + b_y_weight) > t_b_weight_sum) { + b_y_weight -= 1; // It's only ever over by 1. + } + + 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 + 255) >> 8) & 0xFF); + int r_x_weight = ((src_x_accum + src_x_frac + 255) >> 8) & 0xFF; + // Since src_x_index_end is inclusive this should be 256 when there's perfect overlap. + if ((!r_x_weight) && (l_x_weight < l_r_weight_sum)) { + 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 this or 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 + } + } + + // Handle discontinuities. + if ((l_x_weight + r_x_weight) < l_r_weight_sum) { + l_x_weight += 128; + r_x_weight += 128; + } + + // Weights must be balanced. + if ((l_x_weight + r_x_weight) > l_r_weight_sum) { + r_x_weight -= 1; // It's only ever over by 1. + } + + 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: { + int t_b_weight_sum = 64 + ((src_y_frac >> 10) & 0x3F); + int l_r_weight_sum = 64 + ((src_x_frac >> 10) & 0x3F); + 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 = 64 - (((src_y_accum + 63) >> 10) & 0x3F); + int b_y_weight = ((src_y_accum + src_y_frac + 63) >> 10) & 0x3F; + // Since src_y_index_end is inclusive this should be 128 when there's perfect overlap. + if ((!b_y_weight) && (t_y_weight < t_b_weight_sum)) { + b_y_weight = 64; + } + + // 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 this or we chopped off the last part. + if (src_y_index_end == src_y_index) { + b_y_weight = 0; + } else{ + b_y_weight = 64; // max out if we chopped off + } + } + + // Handle discontinuities. + if ((t_y_weight + b_y_weight) < t_b_weight_sum) { + t_y_weight += 32; + b_y_weight += 32; + } + + // Weights must be balanced. + if ((t_y_weight + b_y_weight) > t_b_weight_sum) { + b_y_weight -= 1; // It's only ever over by 1. + } + + 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 = 64 - (((src_x_accum + 63) >> 10) & 0x3F); + int r_x_weight = ((src_x_accum + src_x_frac + 63) >> 10) & 0x3F; + // Since src_x_index_end is inclusive this should be 128 when there's perfect overlap. + if ((!r_x_weight) && (l_x_weight < l_r_weight_sum)) { + r_x_weight = 64; + } + + // 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 this or we chopped off the last part. + if (src_x_index_end == src_x_index) { + r_x_weight = 0; + } else{ + r_x_weight = 64; // max out if we chopped off + } + } + + // Handle discontinuities. + if ((l_x_weight + r_x_weight) < l_r_weight_sum) { + l_x_weight += 32; + r_x_weight += 32; + } + + // Weights must be balanced. + if ((l_x_weight + r_x_weight) > l_r_weight_sum) { + r_x_weight -= 1; // It's only ever over by 1. + } + + 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_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 + 63) >> 6; + r_acc = (r_acc + 63) >> 6; + g_acc = (g_acc + 63) >> 6; + b_acc = (b_acc + 63) >> 6; + + 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 + 63) >> 6; + r_acc = (r_acc + 63) >> 6; + g_acc = (g_acc + 63) >> 6; + b_acc = (b_acc + 63) >> 6; + + 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; + } + 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 < h_start) { + src_row_ptr_0 = src_row_ptr_1 = src_row_ptr_2 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, h_start); + src_row_ptr_3 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, h_start_p_1); + } else if (src_y_index == h_start) { + 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, h_start_p_1); + src_row_ptr_3 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, h_start_p_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 < w_start) { + pixel_x_offests[0] = pixel_x_offests[1] = pixel_x_offests[2] = w_start; + pixel_x_offests[3] = w_start_p_1; + } else if (src_x_index == w_start) { + pixel_x_offests[0] = pixel_x_offests[1] = w_start; + pixel_x_offests[2] = w_start_p_1; + pixel_x_offests[3] = w_start_p_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, h_start); + src_row_ptr_3 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, h_start_p_1); + } else if (src_y_index == h_start) { + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, h_start); + src_row_ptr_2 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, h_start_p_1); + src_row_ptr_3 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, h_start_p_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 < w_start) { + pixel_row_0 = ((*(src_row_ptr_0 + w_start)) * 0x010101) | + ((*(src_row_ptr_0 + w_start_p_1)) << 24); + pixel_row_1 = ((*(src_row_ptr_1 + w_start)) * 0x010101) | + ((*(src_row_ptr_1 + w_start_p_1)) << 24); + pixel_row_2 = ((*(src_row_ptr_2 + w_start)) * 0x010101) | + ((*(src_row_ptr_2 + w_start_p_1)) << 24); + pixel_row_3 = ((*(src_row_ptr_3 + w_start)) * 0x010101) | + ((*(src_row_ptr_3 + w_start_p_1)) << 24); + } else if (src_x_index == w_start) { + pixel_row_0 = ((*(src_row_ptr_0 + w_start)) * 0x0101) | + ((*((uint16_t *) (src_row_ptr_0 + w_start_p_1))) << 16); + pixel_row_1 = ((*(src_row_ptr_1 + w_start)) * 0x0101) | + ((*((uint16_t *) (src_row_ptr_1 + w_start_p_1))) << 16); + pixel_row_2 = ((*(src_row_ptr_2 + w_start)) * 0x0101) | + ((*((uint16_t *) (src_row_ptr_2 + w_start_p_1))) << 16); + pixel_row_3 = ((*(src_row_ptr_3 + w_start)) * 0x0101) | + ((*((uint16_t *) (src_row_ptr_3 + w_start_p_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 guard 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 guard 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 guard 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 < w_start) { + pixel_x_offests[0] = pixel_x_offests[1] = pixel_x_offests[2] = w_start; + pixel_x_offests[3] = w_start_p_1; + } else if (src_x_index == w_start) { + pixel_x_offests[0] = pixel_x_offests[1] = w_start; + pixel_x_offests[2] = w_start_p_1; + pixel_x_offests[3] = w_start_p_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 < h_start) { + src_row_ptr_0 = src_row_ptr_1 = src_row_ptr_2 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, h_start); + src_row_ptr_3 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, h_start_p_1); + } else if (src_y_index == h_start) { + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, h_start); + src_row_ptr_2 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, h_start_p_1); + src_row_ptr_3 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, h_start_p_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 < w_start) { + pixel_row_0[0] = (*(src_row_ptr_0 + w_start)) * 0x10001; + pixel_row_0[1] = __PKHBT(pixel_row_0[0], *(src_row_ptr_0 + w_start_p_1), 16); + pixel_row_1[0] = (*(src_row_ptr_1 + w_start)) * 0x10001; + pixel_row_1[1] = __PKHBT(pixel_row_1[0], *(src_row_ptr_1 + w_start_p_1), 16); + pixel_row_2[0] = (*(src_row_ptr_2 + w_start)) * 0x10001; + pixel_row_2[1] = __PKHBT(pixel_row_2[0], *(src_row_ptr_2 + w_start_p_1), 16); + pixel_row_3[0] = (*(src_row_ptr_3 + w_start)) * 0x10001; + pixel_row_3[1] = __PKHBT(pixel_row_3[0], *(src_row_ptr_3 + w_start_p_1), 16); + } else if (src_x_index == w_start) { + pixel_row_0[0] = (*(src_row_ptr_0 + w_start)) * 0x10001; + pixel_row_0[1] = *((uint32_t *) (src_row_ptr_0 + w_start_p_1)); + pixel_row_1[0] = (*(src_row_ptr_1 + w_start)) * 0x10001; + pixel_row_1[1] = *((uint32_t *) (src_row_ptr_1 + w_start_p_1)); + pixel_row_2[0] = (*(src_row_ptr_2 + w_start)) * 0x10001; + pixel_row_2[1] = *((uint32_t *) (src_row_ptr_2 + w_start_p_1)); + pixel_row_3[0] = (*(src_row_ptr_3 + w_start)) * 0x10001; + pixel_row_3[1] = *((uint32_t *) (src_row_ptr_3 + w_start_p_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 < w_start) { + pixel_x_offests[0] = pixel_x_offests[1] = pixel_x_offests[2] = w_start; + pixel_x_offests[3] = w_start_p_1; + } else if (src_x_index == 0) { + pixel_x_offests[0] = pixel_x_offests[1] = w_start; + pixel_x_offests[2] = w_start_p_1; + pixel_x_offests[3] = w_start_p_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; + } + 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 < h_start) { + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, h_start); + } 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 < w_start) { + pixel_0 = pixel_1 = IMAGE_GET_BINARY_PIXEL_FAST(src_row_ptr, w_start); + } 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 < h_start) { + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, h_start); + } 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 < w_start) { + pixel_00 = pixel_10 = src_row_ptr_0[w_start]; + pixel_01 = pixel_11 = src_row_ptr_1[w_start]; + } 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 < h_start) { + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, h_start); + } 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 < w_start) { + pixel_00 = pixel_10 = src_row_ptr_0[w_start]; + pixel_01 = pixel_11 = src_row_ptr_1[w_start]; + } 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; + } + 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; + } + 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_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; + } + default: { + break; + } + } + } + + imlib_draw_row_teardown(&imlib_draw_row_data); + if (&new_src_img == src_img) { + if (new_src_img.data) 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); + + 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 = ((fast_floorf(seed_threshold * COLOR_R8_MAX)) & 0xff) + | ((fast_floorf(seed_threshold * COLOR_G8_MAX) << 8) & 0xff00) + | ((fast_floorf(seed_threshold * COLOR_B8_MAX) << 16) & 0xff0000); + color_floating_threshold = ((fast_floorf(floating_threshold * COLOR_R8_MAX)) & 0xff) + | ((fast_floorf(floating_threshold * COLOR_G8_MAX) << 8) & 0xff00) + | ((fast_floorf(floating_threshold * COLOR_B8_MAX) << 16) & 0xff0000); + 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: { + pixel_rgb_t rgb_c = COLOR_R8_G8_B8_TO_RGB888(c & 0xff, (c >> 8) & 0xff, (c >> 16) & 0xff); + pixel_rgb_t rgb_zero = COLOR_R8_G8_B8_TO_RGB888(0, 0, 0); + for (int y = 0, yy = out.h; y < yy; y++) { + pixel_rgb_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, rgb_c); + } else if (clear_background) { + IMAGE_PUT_RGB888_PIXEL_FAST(row_ptr, x, rgb_zero); + } + } + } + break; + } + default: { + break; + } + } + + if(out.data) fb_free(out.data); + } +} +#endif // IMLIB_ENABLE_FLOOD_FILL diff --git a/components/3rd_party/omv/omv/imlib/edge.c b/components/3rd_party/omv/omv/imlib/edge.c new file mode 100644 index 00000000..900ef8ec --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/edge.c @@ -0,0 +1,156 @@ +/* + * 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; + 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 = fb_alloc0(roi->w * roi->h * sizeof *gm, FB_ALLOC_NO_HINT); + + //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; y < roi->y + roi->h - 1; y++, gy++) { + for (int gx = 1, x = roi->x + 1; x < roi->x + 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; y < roi->y + roi->h; y++, gy++) { + for (int gx = 0, x = roi->x; x < roi->x + 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; + } + } + } + + if (gm) fb_free(gm); +} +#endif diff --git a/components/3rd_party/omv/omv/imlib/eye.c b/components/3rd_party/omv/omv/imlib/eye.c new file mode 100644 index 00000000..2c11c6ae --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/eye.c @@ -0,0 +1,133 @@ +/* + * 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; y < y_off + box_h - 3; y++) { + for (int x = x_off; x < x_off + box_w - 3; x++) { + int vx = 0, vy = 0, w = src->w; + // 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; i < array_length(gradients); i++) { + vec_t *v = (vec_t *) array_at(gradients, i); + total_m += v->m; + } + + float avg_m = total_m / array_length(gradients); + + for (int i = 0; i < array_length(gradients); i++) { + vec_t *v = (vec_t *) array_at(gradients, i); + float diff = (v->m - 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; y < y_off + box_h; y++) { + for (int x = x_off; x < x_off + box_w; x++) { + float sum_dot = 0.0f; + for (int i = 0; i < array_length(gradients); i++) { + // get gradient vector g + vec_t *v = (vec_t *) array_at(gradients, i); + + // get vector from gradient to centor d + vec_t d = {x - v->cx, 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/components/3rd_party/omv/omv/imlib/fast.c b/components/3rd_party/omv/omv/imlib/fast.c new file mode 100644 index 00000000..724410b6 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/fast.c @@ -0,0 +1,6078 @@ +/* + * 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 MIN_MEM (10 * 1024) +#define MAX_CORNERS (2000U) +#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; + if (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; + 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; + row_start = fb_alloc((last_row + 1) * sizeof(uint16_t), FB_ALLOC_NO_HINT); + + for (int i = 0; i < last_row + 1; i++) { + row_start[i] = -1; + } + + for (int i = 0, prev_row = -1; i < sz; i++) { + corner_t *c = &corners[i]; + if (c->y != prev_row) { + row_start[c->y] = i; + prev_row = c->y; + } + } + + for (int i = 0; i < sz; i++) { + corner_t pos = corners[i]; + uint16_t score = pos.score; + + /*Check left */ + if (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); + // 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; + } + } + array_push_back(keypoints, alloc_keypoint(pos.x, pos.y, pos.score)); + nonmax: + ; + } + + // Free temp rows. + if (row_start) fb_free(row_start); +} + +// *INDENT-OFF* +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; + } +} +// *INDENT-ON* + +static void fast9_score(image_t *image, corner_t *corners, int num_corners, int b) { + for (int i = 0; i < num_corners; i++) { + corner_t *c = &corners[i]; + c->score = fast9_corner_score(image->pixels + c->y * image->w + c->x, b); + } +} + +// *INDENT-OFF* +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 = MAX_CORNERS; + 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; +} +// *INDENT-ON* + +#endif //IMLIB_ENABLE_FAST diff --git a/components/3rd_party/omv/omv/imlib/fft.c b/components/3rd_party/omv/omv/imlib/fft.c new file mode 100644 index 00000000..9c498785 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/fft.c @@ -0,0 +1,756 @@ +/* + * 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 "file_utils.h" +#include "omv_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 efficient 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(fft1d_controller_t *controller) { + if (controller) { + if (controller->data) fb_free(controller->data); + controller->data = NULL; + } +} + +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); + if(h_buffer) 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)); + if(h_buffer) 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); + if (h_buffer) 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(fft2d_controller_t *controller) { + if (controller) { + if (controller->data) { + fb_free(controller->data); + controller->data = NULL; + } + } +} + +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); + // Free image data buffer. + if (tmp) 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]; + } + } + } + + if (tmp) 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]; + } + } + } + + if (tmp) 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/components/3rd_party/omv/omv/imlib/fft.h b/components/3rd_party/omv/omv/imlib/fft.h new file mode 100644 index 00000000..71799753 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/fft.h @@ -0,0 +1,48 @@ +/* + * 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" +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(fft2d_controller_t *controller); +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 +#endif /* __FFT_H__ */ diff --git a/components/3rd_party/omv/omv/imlib/filter.c b/components/3rd_party/omv/omv/imlib/filter.c new file mode 100644 index 00000000..ef85d958 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/filter.c @@ -0,0 +1,3096 @@ +/* + * 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)); + } + } + + if (hist) 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)); + } + } + + if (hist) 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)); + } + } + + if (hist) 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++) { + pixel_rgb_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + pixel_rgb_t value = IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x); + hist[COLOR_RGB888_TO_Y(value.r, value.g, value.b) - 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++) { + pixel_rgb_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; + pixel_rgb_t 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)); + } + + if (buf.data) 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)); + } + + if (buf.data) 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)); + } + + if (buf.data) fb_free(buf.data); + break; + } + case PIXFORMAT_RGB888: { + int r, g, b, r_acc, g_acc, b_acc; + pixel_rgb_t pixel; + 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++) { + pixel_rgb_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + pixel_rgb_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++) { + pixel_rgb_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++) { + pixel_rgb_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); + } + } + } + pixel_rgb_t 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) { + pixel_rgb_t temp = IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x); + if (((COLOR_RGB888_TO_Y(pixel.r, pixel.g, pixel.b) - offset) < COLOR_RGB888_TO_Y(temp.r, temp.g, temp.b)) ^ invert) { + pixel.r = COLOR_R8_MAX; + pixel.g = COLOR_G8_MAX; + pixel.b = COLOR_B8_MAX; + } else { + pixel.r = COLOR_R8_MIN; + pixel.g = COLOR_G8_MIN; + pixel.b = COLOR_B8_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 < len; i += 4) { + // work 4 at time with SIMD + sum32 = __USADA8(*(uint32_t *) &data[i], 0, sum32); + if (sum32 >= 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; i < len && sum < cutoff; i++) { + sum += data[i]; + } +#endif + return i - 1; +} /* hist_median() */ + +void imlib_median_filter(image_t *img, const int ksize, float percentile, 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 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 < img->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)); + } + + if (buf.data) 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)); + } + + if (data) fb_free(data); + if (buf.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)); + } + + if (b_data) fb_free(b_data); + if (g_data) fb_free(g_data); + if (r_data) fb_free(r_data); + if (buf.data) 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_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++) { + pixel_rgb_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + pixel_rgb_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++) { + pixel_rgb_t *k_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y + j); + pixel_rgb_t 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, 256); memset(g_data, 0, 256); memset(b_data, 0, 256); + for (int j = -ksize; j <= ksize; j++) { + pixel_rgb_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_rgb_t 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); + + pixel_rgb_t pixel = COLOR_R8_G8_B8_TO_RGB888(r, g, b); + + if (threshold) { + pixel_rgb_t temp = IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x); + if (((COLOR_RGB888_TO_Y(pixel.r, pixel.g, pixel.b) - offset) < COLOR_RGB888_TO_Y(temp.r, temp.g, temp.b)) ^ invert) { + pixel.r = COLOR_R8_MAX; + pixel.g = COLOR_G8_MAX; + pixel.b = COLOR_B8_MAX; + } else { + pixel.r = COLOR_R8_MIN; + pixel.g = COLOR_G8_MIN; + pixel.b = COLOR_B8_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)); + } + + if (b_data) fb_free(b_data); + if (g_data) fb_free(g_data); + if (r_data) fb_free(r_data); + if (buf.data) fb_free(buf.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 < len; i += 4) { + if (*(uint32_t *) &bins[i] == 0) { + continue; // skip empty bins quickly + } + for (j = i; j < i + 4; j++) { + if (bins[j] > 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)); + } + + if (buf.data) 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)); + } + + if (bins) fb_free(bins); + if (buf.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_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)); + } + + if (r_bins) fb_free(r_bins); + if (g_bins) fb_free(g_bins); + if (b_bins) fb_free(b_bins); + if (buf.data) 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(256, FB_ALLOC_NO_HINT); + uint8_t *g_bins = fb_alloc(256, FB_ALLOC_NO_HINT); + uint8_t *b_bins = fb_alloc(256, FB_ALLOC_NO_HINT); + int r_pixel, g_pixel, b_pixel; + + for (int y = 0, yy = img->h; y < yy; y++) { + pixel_rgb_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + pixel_rgb_t *buf_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, (y % brows)); + int r_mcount=0, g_mcount=0, b_mcount=0; + int r_mode, g_mode, b_mode; + pixel_rgb_t pixel; + 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++) { + pixel_rgb_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, 256); + memset(g_bins, 0, 256); + memset(b_bins, 0, 256); + r_mcount = g_mcount = b_mcount = 0; + for (int j = -ksize; j <= ksize; j++) { + pixel_rgb_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); + + if (threshold) { + pixel_rgb_t temp = IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x); + if (((COLOR_RGB888_TO_Y(pixel.r, pixel.g, pixel.b) - offset) < COLOR_RGB888_TO_Y(temp.r, temp.g, temp.b)) ^ invert) { + pixel.r = COLOR_R8_MAX; + pixel.g = COLOR_G8_MAX; + pixel.b = COLOR_B8_MAX; + } else { + pixel.r = COLOR_R8_MIN; + pixel.g = COLOR_G8_MIN; + pixel.b = COLOR_B8_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)); + } + + if (b_bins) fb_free(b_bins); // b_bins + if (g_bins) fb_free(g_bins); // g_bins + if (r_bins) fb_free(r_bins); // r_bins + if (buf.data) fb_free(buf.data); // 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)); + } + + if (buf.data) 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)); + } + + if (buf.data) 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)); + } + + if (buf.data) 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++) { + pixel_rgb_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + pixel_rgb_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++) { + pixel_rgb_t *k_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img,y+j); + for (int k = -ksize; k <= ksize; k++) { + pixel_rgb_t 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++) { + pixel_rgb_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_rgb_t 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]; + pixel_rgb_t pixel = COLOR_R8_G8_B8_TO_RGB888(r_min, g_min, b_min); + + if (threshold) { + pixel_rgb_t temp = IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x); + if (((COLOR_RGB888_TO_Y(pixel.r, pixel.g, pixel.b) - offset) < COLOR_RGB888_TO_Y(temp.r, temp.g, temp.b)) ^ invert) { + pixel.r = COLOR_R8_MAX; + pixel.g = COLOR_G8_MAX; + pixel.b = COLOR_B8_MAX; + } else { + pixel.r = COLOR_R8_MIN; + pixel.g = COLOR_G8_MIN; + pixel.b = COLOR_B8_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)); + } + + if (buf.data) fb_free(buf.data); + break; + } + default: { + break; + } + } + if (u8BiasTable) 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 = fast_roundf(65536 * m); + const int32_t b_int = b << 16; + invert = invert ? 1 : 0; // ensure binary + + 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; y < img->h; 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; x < img->w; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + int p = IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x); + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, p); + 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++) { + 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++) { + int y_j = IM_MIN(IM_MAX(y + j, 0), (img->h - 1)); + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y_j); + + for (int k = -ksize; k <= ksize; k++) { + int x_k = IM_MIN(IM_MAX(x + k, 0), (img->w - 1)); + acc += krn[ptr++] * IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr, x_k); + } + } + } + + int32_t tmp = (acc * m_int) + b_int; + int pixel = __USAT_ASR(tmp, 1, 16); + + if (threshold) { + pixel -= offset; + pixel = pixel < IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x); + pixel = pixel ^ invert; + } + + 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); y < img->h; 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)); + } + + if (buf.data) fb_free(buf.data); + break; + } + case PIXFORMAT_GRAYSCALE: { + buf.data = fb_alloc(IMAGE_GRAYSCALE_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + + #if defined(ARM_MATH_DSP) + int32_t krn_4, krn_2_0, krn_5_3, krn_8_6, krn_7_1, offset_int, invert_ge, invert_lt; + if (ksize == 1) { + krn_4 = krn[4]; + krn_2_0 = __PKHBT(krn[0], krn[2], 16); + krn_5_3 = __PKHBT(krn[3], krn[5], 16); + krn_8_6 = __PKHBT(krn[6], krn[8], 16); + krn_7_1 = __PKHBT(krn[1], krn[7], 16); + offset_int = __PKHBT(offset, offset, 16); + invert_ge = invert ? 0x00FF00FF : 0xFF00FF00; + invert_lt = invert ? 0xFF00FF00 : 0x00FF00FF; + } + #endif + + for (int y = 0; y < img->h; 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)); + + if (0) { + #if defined(ARM_MATH_DSP) + } else if ((ksize == 1) && (!mask)) { + uint8_t *row_ptr_m1, *row_ptr_p1; + + if (y == 0) { + row_ptr_m1 = row_ptr; + row_ptr_p1 = row_ptr + ((img->h >= 2) ? img->w : 0); + } else if (y >= (img->h - 1)) { + row_ptr_m1 = row_ptr - img->w; + row_ptr_p1 = row_ptr; + } else { + // get 2 neighboring rows + row_ptr_m1 = row_ptr - img->w; + row_ptr_p1 = row_ptr + img->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 < img->w; x += 2) { + uint32_t row_0, row_1, row_2; + + if (x == 0) { + if (img->w >= 3) { + row_0 = *((uint16_t *) row_ptr_m1) | (*(row_ptr_m1 + 2) << 16); + row_1 = *((uint16_t *) row_ptr) | (*(row_ptr + 2) << 16); + row_2 = *((uint16_t *) row_ptr_p1) | (*(row_ptr_p1 + 2) << 16); + } else if (img->w >= 2) { + row_0 = *((uint16_t *) row_ptr_m1); + row_0 = __REV(row_0) | row_0; + row_1 = *((uint16_t *) row_ptr); + row_1 = __REV(row_1) | row_1; + row_2 = *((uint16_t *) row_ptr_p1); + row_2 = __REV(row_2) | row_2; + } else { + row_0 = *row_ptr_m1 * 0x010101; + row_1 = *row_ptr * 0x010101; + row_2 = *row_ptr_p1 * 0x010101; + } + row_0 = (row_0 << 8) | (row_0 & 0xff); + row_1 = (row_1 << 8) | (row_1 & 0xff); + row_2 = (row_2 << 8) | (row_2 & 0xff); + } else if (x == (img->w - 2)) { + row_0 = *((uint32_t *) (row_ptr_m1 + x - 2)); + row_0 = (row_0 >> 8) | ((row_0 << 8) & 0xff000000); + row_1 = *((uint32_t *) (row_ptr + x - 2)); + row_1 = (row_1 >> 8) | ((row_1 << 8) & 0xff000000); + row_2 = *((uint32_t *) (row_ptr_p1 + x - 2)); + row_2 = (row_2 >> 8) | ((row_2 << 8) & 0xff000000); + } else if (x >= (img->w - 1)) { + row_0 = *((uint16_t *) (row_ptr_m1 + x - 1)); + row_0 = ((__UXTB_RORn(row_0, 8) * 0x0101) << 16) | row_0; + row_1 = *((uint16_t *) (row_ptr + x - 1)); + row_1 = ((__UXTB_RORn(row_1, 8) * 0x0101) << 16) | row_1; + row_2 = *((uint16_t *) (row_ptr_p1 + x - 1)); + row_2 = ((__UXTB_RORn(row_2, 8) * 0x0101) << 16) | row_2; + } else { + // get 3 neighboring rows + row_0 = *((uint32_t *) (row_ptr_m1 + x - 1)); + row_1 = *((uint32_t *) (row_ptr + x - 1)); + row_2 = *((uint32_t *) (row_ptr_p1 + x - 1)); + } + + int32_t p0_4 = __UXTB_RORn(row_1, 8); + int32_t p0_7_1 = __PKHBT(__UXTB_RORn(row_0, 8), __UXTB_RORn(row_2, 8), 16); + int32_t pixel0 = krn_4 * p0_4; + pixel0 = __SMLAD(__UXTB16(row_0), krn_2_0, pixel0); + pixel0 = __SMLAD(__UXTB16(row_1), krn_5_3, pixel0); + pixel0 = __SMLAD(__UXTB16(row_2), krn_8_6, pixel0); + pixel0 = __SMLAD(p0_7_1, krn_7_1, pixel0); + pixel0 = (pixel0 * m_int) + b_int; + pixel0 = __USAT_ASR(pixel0, 8, 16); + + int32_t p1_4 = __UXTB_RORn(row_1, 16); + int32_t p1_7_1 = __PKHBT(__UXTB_RORn(row_0, 16), __UXTB_RORn(row_2, 16), 16); + int32_t pixel1 = krn_4 * p1_4; + pixel1 = __SMLAD(__UXTB16_RORn(row_0, 8), krn_2_0, pixel1); + pixel1 = __SMLAD(__UXTB16_RORn(row_1, 8), krn_5_3, pixel1); + pixel1 = __SMLAD(__UXTB16_RORn(row_2, 8), krn_8_6, pixel1); + pixel1 = __SMLAD(p1_7_1, krn_7_1, pixel1); + pixel1 = (pixel1 * m_int) + b_int; + pixel1 = __USAT_ASR(pixel1, 8, 16); + + // Re-pack to make thresholding faster. + int32_t p1_p0 = __PKHBT(pixel0, pixel1, 16); + + if (threshold) { + p1_p0 = __SSUB16(__SSUB16(p1_p0, offset_int), __PKHBT(p0_4, p1_4, 16)); + p1_p0 = __SEL(invert_ge, invert_lt); + } + + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, p1_p0); + + if (x != (img->w - 1)) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x + 1, p1_p0 >> 16); + } + } + #endif + } else { + for (int x = 0; x < img->w; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + int p = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, p); + 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++) { + int y_j = IM_MIN(IM_MAX(y + j, 0), (img->h - 1)); + uint8_t *k_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y_j); + + for (int k = -ksize; k <= ksize; k++) { + int x_k = IM_MIN(IM_MAX(x + k, 0), (img->w - 1)); + acc += krn[ptr++] * IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr, x_k); + } + } + } + + int32_t tmp = (acc * m_int) + b_int; + int pixel = __USAT_ASR(tmp, 8, 16); + + if (threshold) { + pixel -= offset; + pixel = pixel < IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + pixel = (pixel ^ invert) * COLOR_GRAYSCALE_BINARY_MAX; + } + + 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); y < img->h; 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)); + } + + if (buf.data) fb_free(buf.data); + break; + } + case PIXFORMAT_RGB565: { + buf.data = fb_alloc(IMAGE_RGB565_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + + #if defined(ARM_MATH_DSP) + int32_t krn_5, krn_1_0, krn_4_3, krn_7_6, krn_8_2, offset_int, invert_ge, invert_lt; + if (ksize == 1) { + krn_5 = krn[5]; + krn_1_0 = __PKHBT(krn[0], krn[1], 16); + krn_4_3 = __PKHBT(krn[3], krn[4], 16); + krn_7_6 = __PKHBT(krn[6], krn[7], 16); + krn_8_2 = __PKHBT(krn[2], krn[8], 16); + offset_int = __PKHBT(offset, offset, 16); + invert_ge = invert ? 0xFFFFFFFF : 0x00000000; + invert_lt = invert ? 0x00000000 : 0xFFFFFFFF; + } + #endif + + for (int y = 0; y < img->h; 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)); + + if (0) { + #if defined(ARM_MATH_DSP) + } else if (0 && (ksize == 1) && (!mask)) { + uint16_t *row_ptr_m1, *row_ptr_p1; + + if (y == 0) { + row_ptr_m1 = row_ptr; + row_ptr_p1 = row_ptr + ((img->h >= 2) ? img->w : 0); + } else if (y >= (img->h - 1)) { + row_ptr_m1 = row_ptr - img->w; + row_ptr_p1 = row_ptr; + } else { + // get 2 neighboring rows + row_ptr_m1 = row_ptr - img->w; + row_ptr_p1 = row_ptr + img->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 < img->w; x += 2) { + uint32_t row_0[2], row_1[2], row_2[2]; + + if (x == 0) { + row_0[0] = *row_ptr_m1 * 0x10001; + row_1[0] = *row_ptr * 0x10001; + row_2[0] = *row_ptr_p1 * 0x10001; + if (img->w >= 3) { + row_0[1] = *((uint32_t *) (row_ptr_m1 + 1)); + row_1[1] = *((uint32_t *) (row_ptr + 1)); + row_2[1] = *((uint32_t *) (row_ptr_p1 + 1)); + } else if (img->w >= 2) { + row_0[1] = row_ptr_m1[1] * 0x10001; + row_1[1] = row_ptr[1] * 0x10001; + row_2[1] = row_ptr_p1[1] * 0x10001; + } else { + row_0[1] = row_0[0]; + row_1[1] = row_1[0]; + row_2[1] = row_2[0]; + } + } else if (x == (img->w - 2)) { + row_0[0] = *((uint32_t *) (row_ptr_m1 + x - 1)); + row_0[1] = row_ptr_m1[x + 1] * 0x10001; + row_1[0] = *((uint32_t *) (row_ptr + x - 1)); + row_1[1] = row_ptr[x + 1] * 0x10001; + row_2[0] = *((uint32_t *) (row_ptr_p1 + x - 1)); + row_2[1] = row_ptr_p1[x + 1] * 0x10001; + } else if (x >= (img->w - 1)) { + row_0[0] = *((uint32_t *) (row_ptr_m1 + x - 1)); + row_0[1] = __PKHTB(row_0[0], row_0[0], 16); + row_1[0] = *((uint32_t *) (row_ptr + x - 1)); + row_1[1] = __PKHTB(row_1[0], row_1[0], 16); + row_2[0] = *((uint32_t *) (row_ptr_p1 + x - 1)); + row_2[1] = __PKHTB(row_2[0], row_2[0], 16); + } else { + // get 3 neighboring rows + row_0[0] = *((uint32_t *) (row_ptr_m1 + x - 1)); + row_0[1] = *((uint32_t *) (row_ptr_m1 + x + 1)); + row_1[0] = *((uint32_t *) (row_ptr + x - 1)); + row_1[1] = *((uint32_t *) (row_ptr + x + 1)); + row_2[0] = *((uint32_t *) (row_ptr_p1 + x - 1)); + row_2[1] = *((uint32_t *) (row_ptr_p1 + x + 1)); + } + + int32_t p0_8_2 = __PKHBT(row_0[1], row_2[1], 16); + + int32_t p0_r_acc = ((row_1[1] >> 11) & 0x1F) * krn_5; + p0_r_acc = __SMLAD((row_0[0] >> 11) & 0x1F001F, krn_1_0, p0_r_acc); + p0_r_acc = __SMLAD((row_1[0] >> 11) & 0x1F001F, krn_4_3, p0_r_acc); + p0_r_acc = __SMLAD((row_2[0] >> 11) & 0x1F001F, krn_7_6, p0_r_acc); + p0_r_acc = __SMLAD((p0_8_2 >> 11) & 0x1F001F, krn_8_2, p0_r_acc); + p0_r_acc = (p0_r_acc * m_int) + b_int; + p0_r_acc = __USAT_ASR(p0_r_acc, 5, 16); + + int32_t p0_g_acc = ((row_1[1] >> 5) & 0x3F) * krn_5; + p0_g_acc = __SMLAD((row_0[0] >> 5) & 0x3F003F, krn_1_0, p0_g_acc); + p0_g_acc = __SMLAD((row_1[0] >> 5) & 0x3F003F, krn_4_3, p0_g_acc); + p0_g_acc = __SMLAD((row_2[0] >> 5) & 0x3F003F, krn_7_6, p0_g_acc); + p0_g_acc = __SMLAD((p0_8_2 >> 5) & 0x3F003F, krn_8_2, p0_g_acc); + p0_g_acc = (p0_g_acc * m_int) + b_int; + p0_g_acc = __USAT_ASR(p0_g_acc, 6, 16); + + int32_t p0_b_acc = (row_1[1] & 0x1F) * krn_5; + p0_b_acc = __SMLAD(row_0[0] & 0x1F001F, krn_1_0, p0_b_acc); + p0_b_acc = __SMLAD(row_1[0] & 0x1F001F, krn_4_3, p0_b_acc); + p0_b_acc = __SMLAD(row_2[0] & 0x1F001F, krn_7_6, p0_b_acc); + p0_b_acc = __SMLAD(p0_8_2 & 0x1F001F, krn_8_2, p0_b_acc); + p0_b_acc = (p0_b_acc * m_int) + b_int; + p0_b_acc = __USAT_ASR(p0_b_acc, 5, 16); + + int pixel0 = COLOR_R5_G6_B5_TO_RGB565(p0_r_acc, p0_g_acc, p0_b_acc); + + int32_t p1_8_2 = __PKHTB(row_2[1], row_0[1], 16); + int32_t p1_1_0 = (row_0[1] << 16) | (row_0[0] >> 16); + int32_t p1_4_3 = (row_1[1] << 16) | (row_1[0] >> 16); + int32_t p1_7_6 = (row_2[1] << 16) | (row_2[0] >> 16); + + int32_t p1_r_acc = (row_1[1] >> 27) * krn_5; + p1_r_acc = __SMLAD((p1_1_0 >> 11) & 0x1F001F, krn_1_0, p1_r_acc); + p1_r_acc = __SMLAD((p1_4_3 >> 11) & 0x1F001F, krn_4_3, p1_r_acc); + p1_r_acc = __SMLAD((p1_7_6 >> 11) & 0x1F001F, krn_7_6, p1_r_acc); + p1_r_acc = __SMLAD((p1_8_2 >> 11) & 0x1F001F, krn_8_2, p1_r_acc); + p1_r_acc = (p1_r_acc * m_int) + b_int; + p1_r_acc = __USAT_ASR(p1_r_acc, 5, 16); + + int32_t p1_g_acc = ((row_1[1] >> 21) & 0x3F) * krn_5; + p1_g_acc = __SMLAD((p1_1_0 >> 5) & 0x3F003F, krn_1_0, p1_g_acc); + p1_g_acc = __SMLAD((p1_4_3 >> 5) & 0x3F003F, krn_4_3, p1_g_acc); + p1_g_acc = __SMLAD((p1_7_6 >> 5) & 0x3F003F, krn_7_6, p1_g_acc); + p1_g_acc = __SMLAD((p1_8_2 >> 5) & 0x3F003F, krn_8_2, p1_g_acc); + p1_g_acc = (p1_g_acc * m_int) + b_int; + p1_g_acc = __USAT_ASR(p1_g_acc, 6, 16); + + int32_t p1_b_acc = ((row_1[1] >> 16) & 0x1F) * krn_5; + p1_b_acc = __SMLAD(p1_1_0 & 0x1F001F, krn_1_0, p1_b_acc); + p1_b_acc = __SMLAD(p1_4_3 & 0x1F001F, krn_4_3, p1_b_acc); + p1_b_acc = __SMLAD(p1_7_6 & 0x1F001F, krn_7_6, p1_b_acc); + p1_b_acc = __SMLAD(p1_8_2 & 0x1F001F, krn_8_2, p1_b_acc); + p1_b_acc = (p1_b_acc * m_int) + b_int; + p1_b_acc = __USAT_ASR(p1_b_acc, 5, 16); + + int pixel1 = COLOR_R5_G6_B5_TO_RGB565(p1_r_acc, p1_g_acc, p1_b_acc); + + // Re-pack to make thresholding faster. + int32_t p1_p0 = __PKHBT(pixel0, pixel1, 16); + + if (threshold) { + int32_t r_p = __PKHBT(p0_r_acc, p1_r_acc, 16); + int32_t g_p = __PKHBT(p0_g_acc, p1_g_acc, 16); + int32_t b_p = __PKHBT(p0_b_acc, p1_b_acc, 16); + int32_t r_l = (p1_4_3 >> 11) & 0x1F001F; + int32_t g_l = (p1_4_3 >> 5) & 0x3F003F; + int32_t b_l = p1_4_3 & 0x1F001F; + // Note, since the above values are rgb565 versus rgb888 we adjust + // the yuv transform below to account for the scale difference. + // r5 to r8 scale = (r << 3) | (r >> 2) = 8.25 ~= 255/31 + // g6 to g8 scale = (g << 2) | (g >> 4) = 4.0625 ~= 255/63 + // b5 to b8 scale = (b << 3) | (b >> 2) = 8.25 ~= 255/31 + // r -> 38 * 8.25 = 313.5 -> 313 + // g -> 75 * 4.0625 = 304.6875 -> 305 + // b -> 15 * 8.25 = 123.75 -> 124 + int y_p = __UXTB16(((r_p * 313) + (g_p * 305) + (b_p * 124)) >> 7); + int y_l = __UXTB16(((r_l * 313) + (g_l * 305) + (b_l * 124)) >> 7); + p1_p0 = __SSUB16(__SSUB16(y_p, offset_int), y_l); + p1_p0 = __SEL(invert_ge, invert_lt); + } + + if (x == (img->w - 1)) { + // just put bottom + IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, p1_p0); + } else { + // put both + *((uint32_t *) (buf_row_ptr + x)) = p1_p0; + } + } + #endif + } else { + for (int x = 0; x < img->w; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + int p = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x); + IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, p); + continue; // Short circuit. + } + + int32_t 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++) { + int y_j = IM_MIN(IM_MAX(y + j, 0), (img->h - 1)); + uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y_j); + + for (int k = -ksize; k <= ksize; k++) { + int x_k = IM_MIN(IM_MAX(x + k, 0), (img->w - 1)); + 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); + } + } + } + + int32_t r_tmp = (r_acc * m_int) + b_int; + int r_pixel = __USAT_ASR(r_tmp, 5, 16); + + int32_t g_tmp = (g_acc * m_int) + b_int; + int g_pixel = __USAT_ASR(g_tmp, 6, 16); + + int32_t b_tmp = (b_acc * m_int) + b_int; + int b_pixel = __USAT_ASR(b_tmp, 5, 16); + + int pixel = COLOR_R5_G6_B5_TO_RGB565(r_pixel, g_pixel, b_pixel); + + if (threshold) { + pixel = COLOR_RGB565_TO_Y(pixel) - offset; + pixel = pixel < COLOR_RGB565_TO_Y(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + pixel = (pixel ^ invert) * COLOR_RGB565_BINARY_MAX; + } + + 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); y < img->h; 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)); + } + + if (buf.data) fb_free(buf.data); + break; + } + case PIXFORMAT_RGB888: { + buf.data = fb_alloc(IMAGE_RGB888_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + + #if defined(ARM_MATH_DSP) + int32_t krn_5, krn_1_0, krn_4_3, krn_7_6, krn_8_2, offset_int, invert_ge, invert_lt; + if (ksize == 1) { + krn_5 = krn[5]; + krn_1_0 = __PKHBT(krn[0], krn[1], 16); + krn_4_3 = __PKHBT(krn[3], krn[4], 16); + krn_7_6 = __PKHBT(krn[6], krn[7], 16); + krn_8_2 = __PKHBT(krn[2], krn[8], 16); + offset_int = __PKHBT(offset, offset, 16); + invert_ge = invert ? 0xFFFFFFFF : 0x00000000; + invert_lt = invert ? 0x00000000 : 0xFFFFFFFF; + } + #endif + + for (int y = 0; y < img->h; y++) { + pixel_rgb_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + pixel_rgb_t *buf_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, (y % brows)); + + if (0) { + #if defined(ARM_MATH_DSP) + } else if (0 && (ksize == 1) && (!mask)) { + pixel_rgb_t *row_ptr_m1, *row_ptr_p1; + + if (y == 0) { + row_ptr_m1 = row_ptr; + row_ptr_p1 = row_ptr + ((img->h >= 2) ? img->w : 0); + } else if (y >= (img->h - 1)) { + row_ptr_m1 = row_ptr - img->w; + row_ptr_p1 = row_ptr; + } else { + // get 2 neighboring rows + row_ptr_m1 = row_ptr - img->w; + row_ptr_p1 = row_ptr + img->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 < img->w; x += 2) { + uint32_t row_0[2], row_1[2], row_2[2]; + + if (x == 0) { + row_0[0] = *row_ptr_m1 * 0x10001; + row_1[0] = *row_ptr * 0x10001; + row_2[0] = *row_ptr_p1 * 0x10001; + if (img->w >= 3) { + row_0[1] = *((uint32_t *) (row_ptr_m1 + 1)); + row_1[1] = *((uint32_t *) (row_ptr + 1)); + row_2[1] = *((uint32_t *) (row_ptr_p1 + 1)); + } else if (img->w >= 2) { + row_0[1] = row_ptr_m1[1] * 0x10001; + row_1[1] = row_ptr[1] * 0x10001; + row_2[1] = row_ptr_p1[1] * 0x10001; + } else { + row_0[1] = row_0[0]; + row_1[1] = row_1[0]; + row_2[1] = row_2[0]; + } + } else if (x == (img->w - 2)) { + row_0[0] = *((uint32_t *) (row_ptr_m1 + x - 1)); + row_0[1] = row_ptr_m1[x + 1] * 0x10001; + row_1[0] = *((uint32_t *) (row_ptr + x - 1)); + row_1[1] = row_ptr[x + 1] * 0x10001; + row_2[0] = *((uint32_t *) (row_ptr_p1 + x - 1)); + row_2[1] = row_ptr_p1[x + 1] * 0x10001; + } else if (x >= (img->w - 1)) { + row_0[0] = *((uint32_t *) (row_ptr_m1 + x - 1)); + row_0[1] = __PKHTB(row_0[0], row_0[0], 16); + row_1[0] = *((uint32_t *) (row_ptr + x - 1)); + row_1[1] = __PKHTB(row_1[0], row_1[0], 16); + row_2[0] = *((uint32_t *) (row_ptr_p1 + x - 1)); + row_2[1] = __PKHTB(row_2[0], row_2[0], 16); + } else { + // get 3 neighboring rows + row_0[0] = *((uint32_t *) (row_ptr_m1 + x - 1)); + row_0[1] = *((uint32_t *) (row_ptr_m1 + x + 1)); + row_1[0] = *((uint32_t *) (row_ptr + x - 1)); + row_1[1] = *((uint32_t *) (row_ptr + x + 1)); + row_2[0] = *((uint32_t *) (row_ptr_p1 + x - 1)); + row_2[1] = *((uint32_t *) (row_ptr_p1 + x + 1)); + } + + int32_t p0_8_2 = __PKHBT(row_0[1], row_2[1], 16); + + int32_t p0_r_acc = ((row_1[1] >> 11) & 0x1F) * krn_5; + p0_r_acc = __SMLAD((row_0[0] >> 11) & 0x1F001F, krn_1_0, p0_r_acc); + p0_r_acc = __SMLAD((row_1[0] >> 11) & 0x1F001F, krn_4_3, p0_r_acc); + p0_r_acc = __SMLAD((row_2[0] >> 11) & 0x1F001F, krn_7_6, p0_r_acc); + p0_r_acc = __SMLAD((p0_8_2 >> 11) & 0x1F001F, krn_8_2, p0_r_acc); + p0_r_acc = (p0_r_acc * m_int) + b_int; + p0_r_acc = __USAT_ASR(p0_r_acc, 5, 16); + + int32_t p0_g_acc = ((row_1[1] >> 5) & 0x3F) * krn_5; + p0_g_acc = __SMLAD((row_0[0] >> 5) & 0x3F003F, krn_1_0, p0_g_acc); + p0_g_acc = __SMLAD((row_1[0] >> 5) & 0x3F003F, krn_4_3, p0_g_acc); + p0_g_acc = __SMLAD((row_2[0] >> 5) & 0x3F003F, krn_7_6, p0_g_acc); + p0_g_acc = __SMLAD((p0_8_2 >> 5) & 0x3F003F, krn_8_2, p0_g_acc); + p0_g_acc = (p0_g_acc * m_int) + b_int; + p0_g_acc = __USAT_ASR(p0_g_acc, 6, 16); + + int32_t p0_b_acc = (row_1[1] & 0x1F) * krn_5; + p0_b_acc = __SMLAD(row_0[0] & 0x1F001F, krn_1_0, p0_b_acc); + p0_b_acc = __SMLAD(row_1[0] & 0x1F001F, krn_4_3, p0_b_acc); + p0_b_acc = __SMLAD(row_2[0] & 0x1F001F, krn_7_6, p0_b_acc); + p0_b_acc = __SMLAD(p0_8_2 & 0x1F001F, krn_8_2, p0_b_acc); + p0_b_acc = (p0_b_acc * m_int) + b_int; + p0_b_acc = __USAT_ASR(p0_b_acc, 5, 16); + + int pixel0 = COLOR_R8_G8_B8_TO_RGB888(p0_r_acc, p0_g_acc, p0_b_acc); + + int32_t p1_8_2 = __PKHTB(row_2[1], row_0[1], 16); + int32_t p1_1_0 = (row_0[1] << 16) | (row_0[0] >> 16); + int32_t p1_4_3 = (row_1[1] << 16) | (row_1[0] >> 16); + int32_t p1_7_6 = (row_2[1] << 16) | (row_2[0] >> 16); + + int32_t p1_r_acc = (row_1[1] >> 27) * krn_5; + p1_r_acc = __SMLAD((p1_1_0 >> 11) & 0x1F001F, krn_1_0, p1_r_acc); + p1_r_acc = __SMLAD((p1_4_3 >> 11) & 0x1F001F, krn_4_3, p1_r_acc); + p1_r_acc = __SMLAD((p1_7_6 >> 11) & 0x1F001F, krn_7_6, p1_r_acc); + p1_r_acc = __SMLAD((p1_8_2 >> 11) & 0x1F001F, krn_8_2, p1_r_acc); + p1_r_acc = (p1_r_acc * m_int) + b_int; + p1_r_acc = __USAT_ASR(p1_r_acc, 5, 16); + + int32_t p1_g_acc = ((row_1[1] >> 21) & 0x3F) * krn_5; + p1_g_acc = __SMLAD((p1_1_0 >> 5) & 0x3F003F, krn_1_0, p1_g_acc); + p1_g_acc = __SMLAD((p1_4_3 >> 5) & 0x3F003F, krn_4_3, p1_g_acc); + p1_g_acc = __SMLAD((p1_7_6 >> 5) & 0x3F003F, krn_7_6, p1_g_acc); + p1_g_acc = __SMLAD((p1_8_2 >> 5) & 0x3F003F, krn_8_2, p1_g_acc); + p1_g_acc = (p1_g_acc * m_int) + b_int; + p1_g_acc = __USAT_ASR(p1_g_acc, 6, 16); + + int32_t p1_b_acc = ((row_1[1] >> 16) & 0x1F) * krn_5; + p1_b_acc = __SMLAD(p1_1_0 & 0x1F001F, krn_1_0, p1_b_acc); + p1_b_acc = __SMLAD(p1_4_3 & 0x1F001F, krn_4_3, p1_b_acc); + p1_b_acc = __SMLAD(p1_7_6 & 0x1F001F, krn_7_6, p1_b_acc); + p1_b_acc = __SMLAD(p1_8_2 & 0x1F001F, krn_8_2, p1_b_acc); + p1_b_acc = (p1_b_acc * m_int) + b_int; + p1_b_acc = __USAT_ASR(p1_b_acc, 5, 16); + + int pixel1 = COLOR_R8_G8_B8_TO_RGB888(p1_r_acc, p1_g_acc, p1_b_acc); + + // Re-pack to make thresholding faster. + int32_t p1_p0 = __PKHBT(pixel0, pixel1, 16); + + if (threshold) { + int32_t r_p = __PKHBT(p0_r_acc, p1_r_acc, 16); + int32_t g_p = __PKHBT(p0_g_acc, p1_g_acc, 16); + int32_t b_p = __PKHBT(p0_b_acc, p1_b_acc, 16); + int32_t r_l = (p1_4_3 >> 11) & 0x1F001F; + int32_t g_l = (p1_4_3 >> 5) & 0x3F003F; + int32_t b_l = p1_4_3 & 0x1F001F; + // Note, since the above values are rgb565 versus rgb888 we adjust + // the yuv transform below to account for the scale difference. + // r5 to r8 scale = (r << 3) | (r >> 2) = 8.25 ~= 255/31 + // g6 to g8 scale = (g << 2) | (g >> 4) = 4.0625 ~= 255/63 + // b5 to b8 scale = (b << 3) | (b >> 2) = 8.25 ~= 255/31 + // r -> 38 * 8.25 = 313.5 -> 313 + // g -> 75 * 4.0625 = 304.6875 -> 305 + // b -> 15 * 8.25 = 123.75 -> 124 + int y_p = __UXTB16(((r_p * 313) + (g_p * 305) + (b_p * 124)) >> 7); + int y_l = __UXTB16(((r_l * 313) + (g_l * 305) + (b_l * 124)) >> 7); + p1_p0 = __SSUB16(__SSUB16(y_p, offset_int), y_l); + p1_p0 = __SEL(invert_ge, invert_lt); + } + + if (x == (img->w - 1)) { + // just put bottom + IMAGE_PUT_RGB888_PIXEL_FAST(buf_row_ptr, x, p1_p0); + } else { + // put both + *((uint32_t *) (buf_row_ptr + x)) = p1_p0; + } + } + #endif + } else { + for (int x = 0; x < img->w; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + pixel_rgb_t p = IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x); + IMAGE_PUT_RGB888_PIXEL_FAST(buf_row_ptr, x, p); + continue; // Short circuit. + } + + int32_t 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++) { + pixel_rgb_t *k_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y + j); + for (int k = -ksize; k <= ksize; k++) { + pixel_rgb_t 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++) { + int y_j = IM_MIN(IM_MAX(y + j, 0), (img->h - 1)); + pixel_rgb_t *k_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y_j); + + for (int k = -ksize; k <= ksize; k++) { + int x_k = IM_MIN(IM_MAX(x + k, 0), (img->w - 1)); + pixel_rgb_t 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); + } + } + } + + int32_t r_tmp = (r_acc * m_int) + b_int; + int r_pixel = __USAT_ASR(r_tmp, 8, 16); + + int32_t g_tmp = (g_acc * m_int) + b_int; + int g_pixel = __USAT_ASR(g_tmp, 8, 16); + + int32_t b_tmp = (b_acc * m_int) + b_int; + int b_pixel = __USAT_ASR(b_tmp, 8, 16); + + pixel_rgb_t pixel = COLOR_R8_G8_B8_TO_RGB888(r_pixel, g_pixel, b_pixel); + + if (threshold) { + int tmp_y = COLOR_RGB888_TO_Y(pixel.r, pixel.g, pixel.b) - offset; + pixel_rgb_t tmp_pixel = IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x); + tmp_y = tmp_y < COLOR_RGB888_TO_Y(tmp_pixel.r, tmp_pixel.g, tmp_pixel.r); + tmp_y = (tmp_y ^ invert) * COLOR_RGB888_BINARY_MAX; + pixel_rgb_t pixel_tmp = COLOR_YUV_TO_RGB888(tmp_y, 128, 128); + pixel = pixel_tmp; + } + + 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); y < img->h; 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)); + } + + if (buf.data) 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)); + } + + if (gs_lut) fb_free(gs_lut); + if (gi_lut_ptr) fb_free(gi_lut_ptr); + if (buf.data) 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)); + } + + if (gs_lut) fb_free(gs_lut); + if (gi_lut_ptr) fb_free(gi_lut_ptr); + if (buf.data) 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)); + } + + if (gs_lut) fb_free(gs_lut); + if (g_gi_ptr) fb_free(g_gi_ptr); + if (rb_gi_ptr) fb_free(rb_gi_ptr); + if (buf.data) 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++) { + pixel_rgb_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + pixel_rgb_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. + } + + pixel_rgb_t 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++) { + pixel_rgb_t *k_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img,y+j); + for (int k = -ksize; k <= ksize; k++) { + pixel_rgb_t 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++) { + pixel_rgb_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_rgb_t 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; + } + } + } + + pixel_rgb_t pixel = COLOR_R8_G8_B8_TO_RGB888(fast_floorf(IM_MIN(IM_DIV(r_i_acc, r_w_acc), COLOR_R8_MAX)), + fast_floorf(IM_MIN(IM_DIV(g_i_acc, g_w_acc), COLOR_G8_MAX)), + fast_floorf(IM_MIN(IM_DIV(b_i_acc, b_w_acc), COLOR_B8_MAX))); + + if (threshold) { + pixel_rgb_t temp = IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x); + if (((COLOR_RGB888_TO_Y(pixel.r, pixel.g, pixel.b) - offset) < COLOR_RGB888_TO_Y(temp.r, temp.g, temp.b)) ^ invert) { + pixel.r = COLOR_R8_MAX; + pixel.g = COLOR_G8_MAX; + pixel.b = COLOR_B8_MAX; + } else { + pixel.r = COLOR_R8_MIN; + pixel.g = COLOR_G8_MIN; + pixel.b = COLOR_B8_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)); + } + if (gs_lut) fb_free(gs_lut); + if (b_gi_ptr) fb_free(b_gi_ptr); + if (g_gi_ptr) fb_free(g_gi_ptr); + if (r_gi_ptr) fb_free(r_gi_ptr); + if (buf.data) 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; + } + 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; + } + 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; + } + 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; + } + default: { + break; + } + } + + imlib_flood_fill_int(&fill_image, img, x, y, color_seed_threshold, color_floating_threshold, + imlib_cartoon_filter_fill, &fill_state); + } + } + } + + if (fill_image.pixels) fb_free(fill_image.pixels); + if (mean_image.pixels) fb_free(mean_image.pixels); +} +#endif // IMLIB_ENABLE_CARTOON diff --git a/components/3rd_party/omv/omv/imlib/fmath.c b/components/3rd_party/omv/omv/imlib/fmath.c new file mode 100644 index 00000000..c08c34fb --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/fmath.c @@ -0,0 +1,254 @@ +/* + * 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 +#include "fmath.h" +#include "omv_common.h" + +const float __atanf_lut[4] = { + -0.0443265554792128f, //p7 + -0.3258083974640975f, //p3 + +0.1555786518463281f, //p5 + +0.9997878412794807f //p1 +}; + +#if (__ARM_ARCH < 7) +#include +float OMV_ATTR_ALWAYS_INLINE fast_sqrtf(float x) { + return sqrtf(x); +} + +int OMV_ATTR_ALWAYS_INLINE fast_floorf(float x) { + return floorf(x); +} + +int OMV_ATTR_ALWAYS_INLINE fast_ceilf(float x) { + return ceilf(x); +} + +int OMV_ATTR_ALWAYS_INLINE fast_roundf(float x) { + return 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 ( + #if (__CORTEX_M > 4) + "vcvtm.S32.f32 %[r], %[x]\n" + #else + "vcvt.S32.f32 %[r], %[x]\n" + #endif + : [r] "=t" (i) + : [x] "t" (x)); + return i; +} + +int OMV_ATTR_ALWAYS_INLINE fast_ceilf(float x) { + int i; + #if (__CORTEX_M > 4) + asm volatile ( + "vcvtp.S32.f32 %[r], %[x]\n" + #else + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wstrict-aliasing" + uint32_t max = 0x3f7fffff; + x += *((float *) &max); + #pragma GCC diagnostic pop + asm volatile ( + "vcvt.S32.f32 %[r], %[x]\n" + #endif + : [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; + +#pragma GCC push_options +#pragma GCC optimize ("O1") +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 pop_options +#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; +} diff --git a/components/3rd_party/omv/omv/imlib/fmath.h b/components/3rd_party/omv/omv/imlib/fmath.h new file mode 100644 index 00000000..71220d4b --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/fmath.h @@ -0,0 +1,31 @@ +/* + * 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 +float fast_sqrtf(float x); +int fast_floorf(float x); +int fast_ceilf(float x); +int fast_roundf(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_fabsf(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]; +#endif // __FMATH_H__ diff --git a/components/3rd_party/omv/omv/imlib/font.c b/components/3rd_party/omv/omv/imlib/font.c new file mode 100644 index 00000000..59a4b647 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/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/components/3rd_party/omv/omv/imlib/font.h b/components/3rd_party/omv/omv/imlib/font.h new file mode 100644 index 00000000..a1e94397 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/font.h @@ -0,0 +1,20 @@ +/* + * 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 +typedef struct { + int w; + int h; + uint8_t data[10]; +} glyph_t; +extern const glyph_t font[95]; +#endif // __FONT_H__ diff --git a/components/3rd_party/omv/omv/imlib/framebuffer.c b/components/3rd_party/omv/omv/imlib/framebuffer.c new file mode 100644 index 00000000..a2533eaa --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/framebuffer.c @@ -0,0 +1,475 @@ +/* + * 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. + * + * Framebuffer functions. + */ +#include +#include "mpprint.h" +#include "framebuffer.h" +#include "omv_boardconfig.h" + +#define FB_ALIGN_SIZE_ROUND_DOWN(x) (((x) / FRAMEBUFFER_ALIGNMENT) * FRAMEBUFFER_ALIGNMENT) +#define FB_ALIGN_SIZE_ROUND_UP(x) FB_ALIGN_SIZE_ROUND_DOWN(((x) + FRAMEBUFFER_ALIGNMENT - 1)) +#define CONSERVATIVE_JPEG_BUF_SIZE (OMV_JPEG_BUF_SIZE - 64) + +extern char _fb_base; +extern char _fb_end; +framebuffer_t *framebuffer = (framebuffer_t *) &_fb_base; + +extern char _jpeg_buf; +jpegbuffer_t *jpeg_framebuffer = (jpegbuffer_t *) &_jpeg_buf; + +void fb_set_streaming_enabled(bool enable) { + framebuffer->streaming_enabled = enable; +} + +bool fb_get_streaming_enabled() { + return framebuffer->streaming_enabled; +} + +int fb_encode_for_ide_new_size(image_t *img) { + return (((img->size * 8) + 5) / 6) + 2; +} + +void fb_encode_for_ide(uint8_t *ptr, image_t *img) { + *ptr++ = 0xFE; + + for (int i = 0, j = (img->size / 3) * 3; i < j; i += 3) { + int x = 0; + x |= img->data[i + 0] << 0; + x |= img->data[i + 1] << 8; + x |= img->data[i + 2] << 16; + *ptr++ = 0x80 | ((x >> 0) & 0x3F); + *ptr++ = 0x80 | ((x >> 6) & 0x3F); + *ptr++ = 0x80 | ((x >> 12) & 0x3F); + *ptr++ = 0x80 | ((x >> 18) & 0x3F); + } + + if ((img->size % 3) == 2) { + // 2 bytes -> 16-bits -> 24-bits sent + int x = 0; + x |= img->data[img->size - 2] << 0; + x |= img->data[img->size - 1] << 8; + *ptr++ = 0x80 | ((x >> 0) & 0x3F); + *ptr++ = 0x80 | ((x >> 6) & 0x3F); + *ptr++ = 0x80 | ((x >> 12) & 0x3F); + } + + if ((img->size % 3) == 1) { + // 1 byte -> 8-bits -> 16-bits sent + int x = 0; + x |= img->data[img->size - 1] << 0; + *ptr++ = 0x80 | ((x >> 0) & 0x3F); + *ptr++ = 0x80 | ((x >> 6) & 0x3F); + } + + *ptr++ = 0xFE; +} + +void framebuffer_init0() { + // Save fb_enabled flag state + int fb_enabled = JPEG_FB()->enabled; + + // Clear framebuffers + memset(MAIN_FB(), 0, sizeof(*MAIN_FB())); + memset(JPEG_FB(), 0, sizeof(*JPEG_FB())); + + mutex_init0(&JPEG_FB()->lock); + + // Enable streaming. + MAIN_FB()->streaming_enabled = true; // controlled by the OpenMV Cam. + + // Set default quality + JPEG_FB()->quality = ((JPEG_QUALITY_HIGH - JPEG_QUALITY_LOW) / 2) + JPEG_QUALITY_LOW; + + // Set fb_enabled + JPEG_FB()->enabled = fb_enabled; // controlled by the IDE. + + // Setup buffering. + framebuffer_set_buffers(1); +} + +void framebuffer_init_image(image_t *img) { + if (img != NULL) { + img->w = framebuffer->w; + img->h = framebuffer->h; + img->size = framebuffer->size; + img->pixfmt = framebuffer->pixfmt; + img->pixels = framebuffer_get_buffer(framebuffer->head)->data; + } +} + +void framebuffer_init_from_image(image_t *img) { + framebuffer->w = img->w; + framebuffer->h = img->h; + framebuffer->size = img->size; + framebuffer->pixfmt = img->pixfmt; +} + +static void jpegbuffer_init_from_image(image_t *img) { + if (img == NULL) { + jpeg_framebuffer->w = 0; + jpeg_framebuffer->h = 0; + jpeg_framebuffer->size = 0; + } else { + jpeg_framebuffer->w = img->w; + jpeg_framebuffer->h = img->h; + jpeg_framebuffer->size = img->size; + } +} + +void framebuffer_update_jpeg_buffer() { + static int overflow_count = 0; + + image_t main_fb_src; + framebuffer_init_image(&main_fb_src); + image_t *src = &main_fb_src; + + if (src->pixfmt != PIXFORMAT_INVALID && + framebuffer->streaming_enabled && jpeg_framebuffer->enabled) { + if (src->is_compressed) { + bool does_not_fit = false; + + if (mutex_try_lock_alternate(&jpeg_framebuffer->lock, MUTEX_TID_OMV)) { + if (CONSERVATIVE_JPEG_BUF_SIZE < src->size) { + jpegbuffer_init_from_image(NULL); + does_not_fit = true; + } else { + jpegbuffer_init_from_image(src); + memcpy(jpeg_framebuffer->pixels, src->pixels, src->size); + } + + mutex_unlock(&jpeg_framebuffer->lock, MUTEX_TID_OMV); + } + + if (does_not_fit) { + printf("Warning: JPEG/PNG too big! Trying framebuffer transfer using fallback method!\n"); + int new_size = fb_encode_for_ide_new_size(src); + fb_alloc_mark(); + uint8_t *temp = fb_alloc(new_size, FB_ALLOC_NO_HINT); + fb_encode_for_ide(temp, src); + (MP_PYTHON_PRINTER)->print_strn((MP_PYTHON_PRINTER)->data, (const char *) temp, new_size); + fb_alloc_free_till_mark(); + } + } else if (src->pixfmt != PIXFORMAT_INVALID) { + if (mutex_try_lock_alternate(&jpeg_framebuffer->lock, MUTEX_TID_OMV)) { + image_t dst = { + .w = src->w, + .h = src->h, + .pixfmt = PIXFORMAT_JPEG, + .size = CONSERVATIVE_JPEG_BUF_SIZE, + .pixels = jpeg_framebuffer->pixels + }; + // Note: lower quality saves USB bandwidth and results in a faster IDE FPS. + bool overflow = jpeg_compress(src, &dst, jpeg_framebuffer->quality, false); + + if (overflow) { + // JPEG buffer overflowed, reduce JPEG quality for the next frame + // and skip the current frame. The IDE doesn't receive this frame. + if (jpeg_framebuffer->quality > 1) { + // Keep this quality for the next n frames + overflow_count = 60; + jpeg_framebuffer->quality = IM_MAX(1, (jpeg_framebuffer->quality / 2)); + } + + jpegbuffer_init_from_image(NULL); + } else { + if (overflow_count) { + overflow_count--; + } + + // Dynamically adjust our quality if the image is huge. + bool big_frame_buffer = image_size(src) > JPEG_QUALITY_THRESH; + int jpeg_quality_max = big_frame_buffer ? JPEG_QUALITY_LOW : JPEG_QUALITY_HIGH; + + // No buffer overflow, increase quality up to max quality based on frame size... + if ((!overflow_count) && (jpeg_framebuffer->quality < jpeg_quality_max)) { + jpeg_framebuffer->quality++; + } + + jpegbuffer_init_from_image(&dst); + } + + mutex_unlock(&jpeg_framebuffer->lock, MUTEX_TID_OMV); + } + } + } +} + +int32_t framebuffer_get_x() { + return framebuffer->x; +} + +int32_t framebuffer_get_y() { + return framebuffer->y; +} + +int32_t framebuffer_get_u() { + return framebuffer->u; +} + +int32_t framebuffer_get_v() { + return framebuffer->v; +} + +int32_t framebuffer_get_width() { + return framebuffer->w; +} + +int32_t framebuffer_get_height() { + return framebuffer->h; +} + +int32_t framebuffer_get_depth() { + return framebuffer->bpp; +} + +// Returns the number of bytes the frame buffer could be at the current moment it time. +static uint32_t framebuffer_raw_buffer_size() { + uint32_t size = (uint32_t) (fb_alloc_stack_pointer() - ((char *) framebuffer->data)); + // We don't want to give all of the frame buffer RAM to the frame buffer. So, we will limit + // the maximum amount of RAM we return. + uint32_t raw_buf_size = (&_fb_end - &_fb_base); + return IM_MIN(size, raw_buf_size); +} + +uint32_t framebuffer_get_buffer_size() { + uint32_t size; + + if (framebuffer->n_buffers == 1) { + // With only 1 vbuffer it's fine to allow the frame buffer size to change given fb_alloc(). + size = framebuffer_raw_buffer_size(); + } else { + // Whatever the raw size was when the number of buffers were set is locked in... + size = framebuffer->raw_buffer_size; + } + + // Remove the size of the state header plus alignment padding. + size -= sizeof(vbuffer_t); + + // Do we have an estimate on the frame size with multiple buffers? If so, we can reduce the + // RAM each buffer takes up giving some space back to fb_alloc(). + if ((framebuffer->n_buffers != 1) && framebuffer->u && framebuffer->v) { + // Typically a framebuffer will not need more than u*v*2 bytes. + uint32_t size_guess = framebuffer->u * framebuffer->v * 2; + // Add in extra bytes to prevent round down from shrinking buffer too small. + size_guess += FRAMEBUFFER_ALIGNMENT - 1; + // Limit the frame buffer size. + size = IM_MIN(size, size_guess); + } + + // Needs to be a multiple of FRAMEBUFFER_ALIGNMENT for DMA transfers... + return FB_ALIGN_SIZE_ROUND_DOWN(size); +} + +// Each raw frame buffer is split into two parts. The vbuffer_t struct followed by +// padding and then the pixel array starting at the next 32-byte offset. +vbuffer_t *framebuffer_get_buffer(int32_t index) { + uint32_t offset = (sizeof(vbuffer_t) + framebuffer_get_buffer_size()) * index; + return (vbuffer_t *) (framebuffer->data + offset); +} + +void framebuffer_flush_buffers() { + // Move the tail pointer to the head which empties the virtual fifo while keeping the same + // position of the current frame for the rest of the code. + framebuffer->tail = framebuffer->head; + framebuffer->check_head = true; + framebuffer->sampled_head = 0; +} + +void framebuffer_reset_buffers() { + for (int32_t i = 0; i < framebuffer->n_buffers; i++) { + memset(framebuffer_get_buffer(i), 0, sizeof(vbuffer_t)); + } + + framebuffer_flush_buffers(); +} + +int framebuffer_set_buffers(int32_t n_buffers) { + uint32_t total_size = framebuffer_raw_buffer_size(); + uint32_t size = total_size / n_buffers; + + // Error out if frame buffers are smaller than this... + if (size < (sizeof(vbuffer_t) + FRAMEBUFFER_ALIGNMENT)) { + return -1; + } + + // Invalidate frame. + framebuffer->pixfmt = PIXFORMAT_INVALID; + + // Cache the maximum size we can allocate for the frame buffer when vbuffers are greater than 1. + framebuffer->raw_buffer_size = size; + framebuffer->n_buffers = n_buffers; + framebuffer->head = 0; + + framebuffer_reset_buffers(); + + return 0; +} + +// Returns the real size of bytes in the frame buffer. +static uint32_t framebuffer_total_buffer_size() { + if (framebuffer->n_buffers == 1) { + // Allow fb_alloc to use frame buffer space up until the image size. + image_t img; + framebuffer_init_image(&img); + return sizeof(vbuffer_t) + FB_ALIGN_SIZE_ROUND_UP(image_size(&img)); + } else { + // fb_alloc may only use up to the size of all the virtual buffers... + return (sizeof(vbuffer_t) + framebuffer_get_buffer_size()) * framebuffer->n_buffers; + } +} + +void framebuffer_auto_adjust_buffers() { + // Keep same buffer count in video fifo mode but resize buffer sizes. + if (framebuffer->n_buffers > 3) { + framebuffer_set_buffers(framebuffer->n_buffers); + return; + } + + for (int i = 3; i > 0; i--) { + framebuffer_set_buffers(i); + + // Find a buffering size automatically that doesn't use more than half. + if (fb_avail() >= framebuffer_total_buffer_size()) { + return; + } + } +} + +void framebuffer_free_current_buffer() { + vbuffer_t *buffer = framebuffer_get_buffer(framebuffer->head); + #ifdef __DCACHE_PRESENT + // Make sure all cached CPU writes are discarded before returning the buffer. + SCB_InvalidateDCache_by_Addr(buffer->data, framebuffer_get_buffer_size()); + #endif + + // Invalidate frame. + framebuffer->pixfmt = PIXFORMAT_INVALID; + + // Allow frame to be updated in single buffer mode... + if (framebuffer->n_buffers == 1) { + buffer->waiting_for_data = true; + } +} + +void framebuffer_setup_buffers() { + #ifdef __DCACHE_PRESENT + for (int32_t i = 0; i < framebuffer->n_buffers; i++) { + if (i != framebuffer->head) { + vbuffer_t *buffer = framebuffer_get_buffer(i); + // Make sure all cached CPU writes are discarded before returning the buffer. + SCB_InvalidateDCache_by_Addr(buffer->data, framebuffer_get_buffer_size()); + } + } + #endif +} + +vbuffer_t *framebuffer_get_head(framebuffer_flags_t flags) { + int32_t new_head = (framebuffer->head + 1) % framebuffer->n_buffers; + + // Single Buffer Mode. + if (framebuffer->n_buffers == 1) { + if (framebuffer_get_buffer(framebuffer->head)->waiting_for_data) { + return NULL; + } + // Double Buffer Mode. + } else if (framebuffer->n_buffers == 2) { + if (framebuffer->head == framebuffer->tail) { + return NULL; + } + // Triple Buffer Mode. + } else if (framebuffer->n_buffers == 3) { + int32_t sampled_tail = framebuffer->tail; + if (framebuffer->head == sampled_tail) { + return NULL; + } else { + new_head = sampled_tail; + } + // Video FIFO Mode. + } else { + if (framebuffer->head == framebuffer->tail) { + return NULL; + } + } + + if (!(flags & FB_PEEK)) { + framebuffer->head = new_head; + } + + return framebuffer_get_buffer(new_head); +} + +vbuffer_t *framebuffer_get_tail(framebuffer_flags_t flags) { + // Sample head on the first line of a new frame. + if (framebuffer->check_head) { + framebuffer->check_head = false; + framebuffer->sampled_head = framebuffer->head; + } + + int32_t new_tail = (framebuffer->tail + 1) % framebuffer->n_buffers; + + // Single Buffer Mode. + if (framebuffer->n_buffers == 1) { + if (!framebuffer_get_buffer(new_tail)->waiting_for_data) { + // Setup to check head again. + framebuffer->check_head = true; + return NULL; + } + // Double Buffer Mode. + } else if (framebuffer->n_buffers == 2) { + if (new_tail == framebuffer->sampled_head) { + // Setup to check head again. + framebuffer->check_head = true; + return NULL; + } + // Triple Buffer Mode. + } else if (framebuffer->n_buffers == 3) { + // For triple buffering we are never writing where tail or head + // (which may instantly update to be equal to tail) is. + if (new_tail == framebuffer->sampled_head) { + new_tail = (new_tail + 1) % framebuffer->n_buffers; + } + // Video FIFO Mode. + } else { + if (new_tail == framebuffer->sampled_head) { + // Setup to check head again. + framebuffer->check_head = true; + return NULL; + } + } + + vbuffer_t *buffer = framebuffer_get_buffer(new_tail); + + // Reset on start versus the end so offset and jpeg_buffer_overflow are valid after FB_COMMIT. + if (buffer->reset_state) { + buffer->reset_state = false; + buffer->offset = 0; + buffer->jpeg_buffer_overflow = false; + } + + if (!(flags & FB_PEEK)) { + // Trigger reset on the frame buffer the next time it is used. + buffer->reset_state = true; + + // Mark the frame buffer ready in single buffer mode. + if (framebuffer->n_buffers == 1) { + buffer->waiting_for_data = false; + } + + framebuffer->tail = new_tail; + + // Setup to check head again. + framebuffer->check_head = true; + } + return buffer; +} + +char *framebuffer_get_buffers_end() { + return (char *) (framebuffer->data + framebuffer_total_buffer_size()); +} diff --git a/components/3rd_party/omv/omv/imlib/framebuffer.h b/components/3rd_party/omv/omv/imlib/framebuffer.h new file mode 100644 index 00000000..e5d019e6 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/framebuffer.h @@ -0,0 +1,138 @@ +/* + * 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. + * + * Framebuffer functions. + */ +#ifndef __FRAMEBUFFER_H__ +#define __FRAMEBUFFER_H__ +#include +#include "imlib.h" +#include "mutex.h" +#include "omv_common.h" + +// DMA Buffers need to be aligned by cache lines or 16 bytes. +#ifndef __DCACHE_PRESENT +#define FRAMEBUFFER_ALIGNMENT 16 +#else +#define FRAMEBUFFER_ALIGNMENT __SCB_DCACHE_LINE_SIZE +#endif + +typedef struct framebuffer { + int32_t x, y; + int32_t w, h; + int32_t u, v; + PIXFORMAT_STRUCT; + int32_t streaming_enabled; + uint32_t raw_buffer_size; + int32_t n_buffers; + int32_t head; + volatile int32_t tail; + bool check_head; + int32_t sampled_head; + OMV_ATTR_ALIGNED(uint8_t data[], FRAMEBUFFER_ALIGNMENT); +} framebuffer_t; + +extern framebuffer_t *framebuffer; + +typedef enum { + FB_NO_FLAGS = (0 << 0), + FB_PEEK = (1 << 0), // If set, will not move the head/tail. +} framebuffer_flags_t; + +typedef struct vbuffer { + // Used by snapshot code to figure out the jpeg size (bpp). + int32_t offset; + bool jpeg_buffer_overflow; + // Used internally by frame buffer code. + volatile bool waiting_for_data; + bool reset_state; + // Image data array. + OMV_ATTR_ALIGNED(uint8_t data[], FRAMEBUFFER_ALIGNMENT); +} vbuffer_t; + +typedef struct jpegbuffer { + int32_t w, h; + int32_t size; + int32_t enabled; + int32_t quality; + omv_mutex_t lock; + OMV_ATTR_ALIGNED(uint8_t pixels[], FRAMEBUFFER_ALIGNMENT); +} jpegbuffer_t; + +extern jpegbuffer_t *jpeg_framebuffer; + +// Force fb streaming to the IDE off. +void fb_set_streaming_enabled(bool enable); +bool fb_get_streaming_enabled(); + +// Encode jpeg data for transmission over a text channel. +int fb_encode_for_ide_new_size(image_t *img); +void fb_encode_for_ide(uint8_t *ptr, image_t *img); + +void framebuffer_init0(); + +int32_t framebuffer_get_x(); +int32_t framebuffer_get_y(); +int32_t framebuffer_get_u(); +int32_t framebuffer_get_v(); + +int32_t framebuffer_get_width(); +int32_t framebuffer_get_height(); +int32_t framebuffer_get_depth(); + +// Return the number of bytes in the current buffer. +uint32_t framebuffer_get_buffer_size(); + +// Return the state of a buffer. +vbuffer_t *framebuffer_get_buffer(int32_t index); + +// Initializes an image from the frame buffer. +void framebuffer_init_image(image_t *img); + +// Sets the frame buffer from an image. +void framebuffer_init_from_image(image_t *img); + +// Compress src image to the JPEG buffer if src is mutable, otherwise copy src to the JPEG buffer +// if the src is JPEG and fits in the JPEG buffer, or encode and stream src image to the IDE if not. +void framebuffer_update_jpeg_buffer(); + +// Clears out all old captures frames in the framebuffer. +void framebuffer_flush_buffers(); + +// Resets all buffers (for use after aborting) +void framebuffer_reset_buffers(); + +// Controls the number of virtual buffers in the frame buffer. +int framebuffer_set_buffers(int32_t n_buffers); + +// Automatically finds the best buffering size given RAM. +void framebuffer_auto_adjust_buffers(); + +// Call when done with the current vbuffer to mark it as free. +void framebuffer_free_current_buffer(); + +// Call to do any heavy setup before frame capture. +void framebuffer_setup_buffers(); + +// Sets the current frame buffer to the latest virtual frame buffer. +// Returns the buffer if it is ready or NULL if not... +// Pass FB_PEEK to get the next buffer but not take it. +vbuffer_t *framebuffer_get_head(framebuffer_flags_t flags); + +// Return the next vbuffer to store image data to or NULL if none. +// Pass FB_PEEK to get the next buffer but not commit it. +vbuffer_t *framebuffer_get_tail(framebuffer_flags_t flags); + +// Returns a pointer to the end of the framebuffer(s). +char *framebuffer_get_buffers_end(); + +// Use these macros to get a pointer to main or JPEG framebuffer. +#define MAIN_FB() (framebuffer) +#define JPEG_FB() (jpeg_framebuffer) + +#endif /* __FRAMEBUFFER_H__ */ diff --git a/components/3rd_party/omv/omv/imlib/fsort.c b/components/3rd_party/omv/omv/imlib/fsort.c new file mode 100644 index 00000000..079da1d0 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/fsort.c @@ -0,0 +1,304 @@ +/* + * 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/components/3rd_party/omv/omv/imlib/fsort.h b/components/3rd_party/omv/omv/imlib/fsort.h new file mode 100644 index 00000000..76dbf9f4 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/fsort.h @@ -0,0 +1,13 @@ +/* + * 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 +void fsort(int *data, int n); +#endif /* __FSORT_H__ */ diff --git a/components/3rd_party/omv/omv/imlib/gif.c b/components/3rd_party/omv/omv/imlib/gif.c new file mode 100644 index 00000000..f430f241 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/gif.c @@ -0,0 +1,117 @@ +/* + * 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 "file_utils.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); + + file_write(fp, "GIF89a", 6); + file_write(fp, (uint16_t []) {width, height}, 4); + file_write(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; + file_write(fp, (uint8_t []) {red, green, blue}, 3); + } + } else { + for (int i = 0; i < 128; i++) { + int gray = ((i * 255) + 63.5) / 127; + file_write(fp, (uint8_t []) {gray, gray, gray}, 3); + } + } + + if (loop) { + file_write(fp, (uint8_t []) {'!', 0xFF, 0x0B}, 3); + file_write(fp, "NETSCAPE2.0", 11); + file_write(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) { + file_write(fp, (uint8_t []) {'!', 0xF9, 0x04, 0x04}, 4); + file_write_short(fp, delay); + file_write_short(fp, 0); // end + } + + file_write_byte(fp, 0x2C); + file_write_long(fp, 0); + file_write(fp, (uint16_t []) {img->w, img->h}, 4); + file_write(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; y < blocks; y++) { + int block_size = IM_MIN(BLOCK_SIZE, bytes - (y * BLOCK_SIZE)); + file_write_byte(fp, 1 + block_size); + file_write_byte(fp, 0x80); // clear code + for (int x = 0; x < block_size; x++) { + file_write_byte(fp, img->pixels[(y * BLOCK_SIZE) + x] >> 1); + } + } + } else if (IM_IS_RGB565(img)) { + for (int y = 0; y < blocks; y++) { + int block_size = IM_MIN(BLOCK_SIZE, bytes - (y * BLOCK_SIZE)); + file_write_byte(fp, 1 + block_size); + file_write_byte(fp, 0x80); // clear code + for (int x = 0; x < block_size; x++) { + uint16_t pixel = ((uint16_t *) img->pixels)[(y * BLOCK_SIZE) + x]; + uint16_t r = COLOR_RGB565_TO_R5(pixel) >> 3; + uint16_t g = COLOR_RGB565_TO_G6(pixel) >> 3; + uint16_t b = COLOR_RGB565_TO_B5(pixel) >> 3; + file_write_byte(fp, (r << 5) | (g << 2) | b); + } + } + } else if (img->is_bayer || img->is_yuv) { + for (int y = 0; y < blocks; y++) { + int block_size = IM_MIN(BLOCK_SIZE, bytes - (y * BLOCK_SIZE)); + file_write_byte(fp, 1 + block_size); + file_write_byte(fp, 0x80); // clear code + uint16_t pixels[block_size]; + if (img->is_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 < block_size; x++) { + uint16_t pixel = pixels[2]; + uint16_t r = COLOR_RGB565_TO_R5(pixel) >> 3; + uint16_t g = COLOR_RGB565_TO_G6(pixel) >> 3; + uint16_t b = COLOR_RGB565_TO_B5(pixel) >> 3; + file_write_byte(fp, (r << 5) | (g << 2) | b); + } + } + } + + file_write(fp, (uint8_t []) {0x01, 0x81, 0x00}, 3); // end code + + file_buffer_off(fp); +} + +void gif_close(FIL *fp) { + file_write_byte(fp, ';'); + file_close(fp); +} +#endif //IMLIB_ENABLE_IMAGE_FILE_IO diff --git a/components/3rd_party/omv/omv/imlib/haar.c b/components/3rd_party/omv/omv/imlib/haar.c new file mode 100644 index 00000000..ec462cbb --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/haar.c @@ -0,0 +1,296 @@ +/* + * 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 "xalloc.h" +#include "imlib.h" +// built-in cascades +#include "cascade.h" +#include "file_utils.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; i < cascade->num_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; i < cascade->n_stages; i++) { + int stage_sum = 0; + for (int j = 0; j < cascade->stages_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; y < y2; y += cascade->step) { + for (int x = 0; x < x2; x += cascade->step) { + 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_open(&fp, path, true, FA_READ | FA_OPEN_EXISTING); + + // Read detection window size + file_read(&fp, &cascade->window, sizeof(cascade->window)); + + // Read num stages + file_read(&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 */ + file_read(&fp, cascade->stages_array, sizeof(uint8_t) * cascade->n_stages); + + /* sum num of features in each stages*/ + for (i = 0, cascade->n_features = 0; i < cascade->n_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 */ + file_read(&fp, cascade->stages_thresh_array, sizeof(int16_t) * cascade->n_stages); + + /* read features thresholds */ + file_read(&fp, cascade->tree_thresh_array, sizeof(*cascade->tree_thresh_array) * cascade->n_features); + + /* read alpha 1 */ + file_read(&fp, cascade->alpha1_array, sizeof(*cascade->alpha1_array) * cascade->n_features); + + /* read alpha 2 */ + file_read(&fp, cascade->alpha2_array, sizeof(*cascade->alpha2_array) * cascade->n_features); + + /* read num rectangles per feature*/ + file_read(&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; i < cascade->n_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 */ + file_read(&fp, cascade->weights_array, sizeof(*cascade->weights_array) * cascade->n_rectangles); + + /* read rectangles num rectangles * 4 points */ + file_read(&fp, cascade->rectangles_array, sizeof(*cascade->rectangles_array) * cascade->n_rectangles * 4); + +error: + file_close(&fp); + return res; +} +#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; i < cascade->n_stages; i++) { + cascade->n_features += cascade->stages_array[i]; + } + + // sum the number of recatngles in all features + for (i = 0, cascade->n_rectangles = 0; i < cascade->n_features; i++) { + cascade->n_rectangles += cascade->num_rectangles_array[i]; + } + return FR_OK; +} diff --git a/components/3rd_party/omv/omv/imlib/hog.c b/components/3rd_party/omv/omv/imlib/hog.c new file mode 100644 index 00000000..0436311b --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/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; y < h; y += block_size) { + for (int x = roi->x; x < w; x += block_size) { + float k = 0.0f; + for (int cy = 0; cy < block_size; cy++) { + for (int cx = 0; cx < block_size; cx++) { + if ((y + cy) > 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 < N_BINS; i++) { + array_push_back(gds, &bins[i]); + } + + int l = cell_size / 2; + // Note cells are not ordered histograms of 4 cells + for (int by = 0, hog_index = 0; by < y_cells; by += 2) { + for (int bx = 0; bx < x_cells; bx += 2) { + for (int y = 0; y < 2; y++) { + for (int x = 0; x < 2; x++) { + // Sort and draw bins + for (int i = hog_index; i < hog_index + N_BINS; i++) { + int m = (int) (hog[i] * 255); + if (m > 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; i < N_BINS; i++) { + bin_t *bin = array_at(gds, i); + int x2 = l * cos_table[bin->d]; + 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); + if (hog) fb_free(hog); +} +#endif // IMLIB_ENABLE_HOG diff --git a/components/3rd_party/omv/omv/imlib/hough.c b/components/3rd_party/omv/omv/imlib/hough.c new file mode 100644 index 00000000..869b1fbc --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/hough.c @@ -0,0 +1,998 @@ +/* + * 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) <= image_size(ptr)) { + 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) { + pixel_rgb_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; + } + } + + 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); + } + } + } + + if (acc) fb_free(acc); // acc + + for (;;) { + // Merge overlapping. + bool merge_occured = false; + + list_t out_temp; + 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); + 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; + 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); + + if (point_buffer) fb_free(point_buffer); // point_buffer + if (mag_buffer) fb_free(mag_buffer); // mag_buffer + if (theta_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) { + pixel_rgb_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 + + 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) <= image_size(ptr)) { + 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 + } + } + } + } + + if (rsin) fb_free(rsin); // rsin + if (rcos) fb_free(rcos); // rcos + if (acc) fb_free(acc); // acc + } + + if (magnitude_acc) fb_free(magnitude_acc); // magnitude_acc + if (theta_acc) fb_free(theta_acc); // theta_acc + + for (;;) { + // Merge overlapping. + bool merge_occured = false; + + list_t out_temp; + 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/components/3rd_party/omv/omv/imlib/imlib.c b/components/3rd_party/omv/omv/imlib/imlib.c new file mode 100644 index 00000000..bbe0e2cd --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/imlib.c @@ -0,0 +1,1359 @@ +/* + * 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 "file_utils.h" +#include "imlib.h" +#include "omv_common.h" +#include "omv_boardconfig.h" + +void imlib_init_all() { + #if (OMV_HARDWARE_JPEG == 1) + imlib_jpeg_compress_init(); + #endif +} + +void imlib_deinit_all() { + #ifdef IMLIB_ENABLE_DMA2D + imlib_draw_row_deinit_all(); + #endif + #if (OMV_HARDWARE_JPEG == 1) + imlib_jpeg_compress_deinit(); + #endif +} + +///////////////// +// 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); +} + +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 chosen 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 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; +} + +void image_copy(image_t *dst, image_t *src) { + memcpy(dst, src, sizeof(image_t)); +} + +size_t image_line_size(image_t *ptr) { + switch (ptr->pixfmt) { + case PIXFORMAT_BINARY: { + return IMAGE_BINARY_LINE_LEN_BYTES(ptr); + } + case PIXFORMAT_GRAYSCALE: + case PIXFORMAT_BAYER_ANY: { + // re-use + return IMAGE_GRAYSCALE_LINE_LEN_BYTES(ptr); + } + case PIXFORMAT_RGB565: + case PIXFORMAT_YUV_ANY: { + // re-use + return IMAGE_RGB565_LINE_LEN_BYTES(ptr); + } + case PIXFORMAT_RGB888: { + return IMAGE_RGB888_LINE_LEN_BYTES(ptr); + } + default: { + return 0; + } + } +} + +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_MAX(IM_MIN(fast_floorf(116 * y) - 16, COLOR_L_MAX), COLOR_L_MIN); +} + +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_MAX(IM_MIN(fast_floorf(500 * (x - y)), COLOR_A_MAX), COLOR_A_MIN); +} + +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_MAX(IM_MIN(fast_floorf(200 * (y - z)), COLOR_B_MAX), COLOR_B_MIN); +} + +int8_t imlib_rgb888_to_l(pixel_rgb_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_MAX(IM_MIN(fast_floorf(116 * y) - 16, COLOR_L_MAX), COLOR_L_MIN); +} + +int8_t imlib_rgb888_to_a(pixel_rgb_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_MAX(IM_MIN(fast_floorf(500 * (x-y)), COLOR_A_MAX), COLOR_A_MIN); +} + +int8_t imlib_rgb888_to_b(pixel_rgb_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_MAX(IM_MIN(fast_floorf(200 * (y-z)), COLOR_B_MAX), COLOR_B_MIN); +} + +void imlib_rgb888_to_lab(pixel_rgb_t pixel, uint8_t *l, int8_t *a, int8_t *b) +{ + 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); + float z = ((r_lin * 0.0193f) + (g_lin * 0.1192f) + (b_lin * 0.9505f)) * (1.0f / 108.883f); + + x = (x > 0.008856f) ? fast_cbrtf(x) : ((x * 7.787037f) + 0.137931f); + y = (y > 0.008856f) ? fast_cbrtf(y) : ((y * 7.787037f) + 0.137931f); + z = (z > 0.008856f) ? fast_cbrtf(z) : ((z * 7.787037f) + 0.137931f); + + *l = IM_MAX(IM_MIN(fast_floorf(116 * y) - 16, COLOR_L_MAX), COLOR_L_MIN); + *a = IM_MAX(IM_MIN(fast_floorf(500 * (x - y)), COLOR_A_MAX), COLOR_A_MIN); + *b = IM_MAX(IM_MIN(fast_floorf(200 * (y - z)), COLOR_B_MAX), COLOR_B_MIN); +} +// 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); +} + +pixel_rgb_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); +} + +pixel_rgb_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)) { + 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)) { + 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)) { + 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)) { + 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) { + char magic[4]; + file_open(fp, path, false, FA_READ | FA_OPEN_EXISTING); + file_read(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_open(fp, path, true, FA_READ | FA_OPEN_EXISTING); + ppm_read_geometry(fp, img, path, &rs->ppm_rs); + } else if ((magic[0] == 'B') && (magic[1] == 'M')) { + // BMP + rs->format = FORMAT_BMP; + file_open(fp, path, true, FA_READ | FA_OPEN_EXISTING); + vflipped = bmp_read_geometry(fp, img, path, &rs->bmp_rs); + } else if ((magic[0] == 0xFF) && (magic[1] == 0xD8)) { + // JPG + rs->format = FORMAT_JPG; + file_open(fp, path, false, FA_READ | FA_OPEN_EXISTING); + jpeg_read_geometry(fp, img, path, &rs->jpg_rs); + file_buffer_on(fp); + } else if ((magic[0] == 0x89) && (magic[1] == 0x50) && (magic[2] == 0x4E) && (magic[3] == 0x47)) { + // PNG + rs->format = FORMAT_PNG; + file_open(fp, path, false, FA_READ | FA_OPEN_EXISTING); + png_read_geometry(fp, img, path, &rs->png_rs); + file_buffer_on(fp); + } else { + file_raise_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 = image_size(img); + 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)) { + file_close(&fp); + 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((uint32_t) img->h, (size / (temp.w * temp.bpp))); + // This should never happen unless someone forgot to free. + if ((!temp.pixels) || (!temp.h)) { + mp_raise_msg(&mp_type_MemoryError, MP_ERROR_TEXT("Not enough memory available!")); + } + for (int i = 0; i < img->h; 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; j < lines; j++) { + if (!vflipped) { + op(img, i + j, temp.pixels + (temp.w * temp.bpp * j), data, false); + } else { + op(img, (img->h - i - lines) + j, temp.pixels + (temp.w * temp.bpp * j), data, true); + } + } + } + file_close(&fp); + if (alloc) 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; i < ii; i++) { + op(img, i, IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(other, i), data, false); + } + break; + } + case PIXFORMAT_GRAYSCALE: { + for (int i = 0, ii = img->h; i < ii; i++) { + op(img, i, IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(other, i), data, false); + } + break; + } + case PIXFORMAT_RGB565: { + for (int i = 0, ii = img->h; i < ii; i++) { + op(img, i, IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(other, i), data, false); + } + break; + } + case PIXFORMAT_RGB888: { + for (int i=0, ii=img->h; 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; i < ii; i++) { + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, i, scalar); + } + + for (int i = 0, ii = img->h; i < ii; i++) { + op(img, i, row_ptr, data, false); + } + + if (row_ptr) fb_free(row_ptr); + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *row_ptr = fb_alloc(IMAGE_GRAYSCALE_LINE_LEN_BYTES(img), FB_ALLOC_NO_HINT); + + for (int i = 0, ii = img->w; i < ii; i++) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, i, scalar); + } + + for (int i = 0, ii = img->h; i < ii; i++) { + op(img, i, row_ptr, data, false); + } + + if (row_ptr) fb_free(row_ptr); + break; + } + case PIXFORMAT_RGB565: { + uint16_t *row_ptr = fb_alloc(IMAGE_RGB565_LINE_LEN_BYTES(img), FB_ALLOC_NO_HINT); + + for (int i = 0, ii = img->w; i < ii; i++) { + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, i, scalar); + } + + for (int i = 0, ii = img->h; i < ii; i++) { + op(img, i, row_ptr, data, false); + } + + if (row_ptr) fb_free(row_ptr); + break; + } + case PIXFORMAT_RGB888: { + pixel_rgb_t *row_ptr = fb_alloc(IMAGE_RGB888_LINE_LEN_BYTES(img), FB_ALLOC_NO_HINT); + pixel_rgb_t scalar_value = {.r = scalar, .g = scalar, .b = scalar}; + for (int i=0, ii=img->w; 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 *alloc = (char *)fb_alloc(strlen(path) + 5, FB_ALLOC_NO_HINT); + char *new_path = strcat(strcpy(alloc, path), ".jpg"); + jpeg_write(img, new_path, quality); + if (alloc) fb_free(alloc); + } else if (img->pixfmt == PIXFORMAT_PNG) { + char *alloc = (char *)fb_alloc(strlen(path) + 5, FB_ALLOC_NO_HINT); + char *new_path = strcat(strcpy(, path), ".png"); + png_write(img, new_path); + if (alloc) fb_free(alloc); + } else if (IM_IS_BAYER(img)) { + FIL fp; + char *alloc = (char *)fb_alloc(strlen(path) + 5, FB_ALLOC_NO_HINT); + char *new_path = strcat(strcpy(alloc, path), ".raw"); + file_open(&fp, new_path, false, FA_WRITE | FA_CREATE_ALWAYS); + file_write(&fp, img->pixels, img->w * img->h); + file_close(&fp); + if (alloc) fb_free(alloc); + } else { + // RGB or GS, save as BMP. + char *alloc = (char *)fb_alloc(strlen(path) + 5, FB_ALLOC_NO_HINT); + char *new_path = strcat(strcpy(alloc, path), ".bmp"); + bmp_write_subimg(img, new_path, roi); + if (alloc) fb_free(alloc); + } + 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: { + pixel_rgb_t rgb_zero = COLOR_R8_G8_B8_TO_RGB888(0, 0, 0); + for (int y = 0, yy = img->h; y < yy; y++) { + pixel_rgb_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, rgb_zero); + } + } + } + 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: { + pixel_rgb_t *tmp = (pixel_rgb_t *) data; + + for (int y = 0; y < halfHeight; y++) { + pixel_rgb_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + pixel_rgb_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) { + pixel_rgb_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) { + pixel_rgb_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; + } + } + + if (precalculated_table) fb_free(precalculated_table); // precalculated_table + if (data) 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; i < n; i++) { + r_s += src->pixels[i]; + } + *r_mean = r_s / n; + *g_mean = r_s / n; + *b_mean = r_s / n; + break; + } + case PIXFORMAT_RGB565: { + for (int i = 0; i < n; i++) { + uint16_t p = ((uint16_t *) src->pixels)[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(p); + g_s += COLOR_RGB888_TO_G8(p); + b_s += COLOR_RGB888_TO_B8(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; i < n; i += 2) { + s += data[i + 0] + data[i + 1]; + uint32_t tmp = __PKHBT(data[i + 0], data[i + 1], 16); + sq = __SMLAD(tmp, tmp, sq); + } + + if (n % 2) { + s += data[n - 1]; + sq += data[n - 1] * data[n - 1]; + } + + /* mean */ + int m = s / n; + + /* variance */ + uint32_t v = sq / n - (m * m); + + /* std */ + return fast_sqrtf(v); +} + +void imlib_sepconv3(image_t *img, const int8_t *krn, const float m, const int b) { + int ksize = 3; + // TODO: Support RGB + int *buffer = fb_alloc(img->w * 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; y < img->h - ksize; y++) { + for (int x = 0; x < img->w; 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; x < img->w - 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); + } + } + } + if (buffer) fb_free(buffer); +} diff --git a/components/3rd_party/omv/omv/imlib/imlib.h b/components/3rd_party/omv/omv/imlib/imlib.h new file mode 100644 index 00000000..87d8c308 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/imlib.h @@ -0,0 +1,1617 @@ +/* + * 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 "fb_alloc.h" +#include "file_utils.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" + +#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_IS_SIGNED(a) (__builtin_types_compatible_p(__typeof__(a), signed) || \ + __builtin_types_compatible_p(__typeof__(a), signed long)) +#define IM_IS_UNSIGNED(a) (__builtin_types_compatible_p(__typeof__(a), unsigned) || \ + __builtin_types_compatible_p(__typeof__(a), unsigned long)) +#define IM_SIGN_COMPARE(a, b) ((IM_IS_SIGNED(a) && IM_IS_UNSIGNED(b)) || \ + (IM_IS_SIGNED(b) && IM_IS_UNSIGNED(a))) + +#define IM_MAX(a, b) \ + ({__typeof__ (a) _a = (a); __typeof__ (b) _b = (b); \ + __builtin_choose_expr(IM_SIGN_COMPARE(_a, _b), (void) 0, (_a > _b ? _a : _b)); }) + +#define IM_MIN(a, b) \ + ({__typeof__ (a) _a = (a); __typeof__ (b) _b = (b); \ + __builtin_choose_expr(IM_SIGN_COMPARE(_a, _b), (void) 0, (_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 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) + +typedef struct { + uint8_t r; + uint8_t g; + uint8_t b; +} pixel_rgb_t; + +///////////////// +// 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); +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); \ + int8_t _l, _a, _b; \ + COLOR_RGB888_TO_LAB(_pixel, &_l, &_a, &_b); \ + ((_threshold->LMin <= _l) && (_l <= _threshold->LMax) && \ + (_threshold->AMin <= _a) && (_a <= _threshold->AMax) && \ + (_threshold->BMin <= _b) && (_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); \ + }) + +// RGB888 Stuff // +#define COLOR_RGB888_TO_R8(pixel) (pixel.r) +#define COLOR_RGB888_TO_G8(pixel) (pixel.g) +#define COLOR_RGB888_TO_B8(pixel) (pixel.b) + +#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) ({pixel_rgb_t __rgb = { .r = (r8), .g = (g8), .b = (b8) }; __rgb;}) + +#ifdef IMLIB_ENABLE_YUV_LUT +extern const int8_t yuv_table[196608]; +#define COLOR_RGB888_TO_Y(r8, g8, b8) ((uint8_t)yuv_table[(COLOR_R8_G8_B8_TO_RGB565(r8, g8, b8) * 3) + 0]) +#define COLOR_RGB888_TO_U(r8, g8, b8) yuv_table[(COLOR_R8_G8_B8_TO_RGB565(r8, g8, b8) * 3) + 1] +#define COLOR_RGB888_TO_V(r8, g8, b8) yuv_table[(COLOR_R8_G8_B8_TO_RGB565(r8, g8, b8) * 3) + 2] +#define COLOR_RGB565_TO_Y(rgb565) yuv_table[((rgb565) * 3) + 0] +#define COLOR_RGB565_TO_U(rgb565) yuv_table[((rgb565) * 3) + 1] +#define COLOR_RGB565_TO_V(rgb565) yuv_table[((rgb565) * 3) + 2] +#else +#define COLOR_RGB888_TO_Y(r8, g8, b8) ((((r8) * 38) + ((g8) * 75) + ((b8) * 15)) >> 7) // 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_RGB888_TO_U(r8, g8, b8) ((((r8) * -21) - ((g8) * 43) + ((b8) * 64)) >> 7) // -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_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); \ + }) +#endif + +#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); \ + }) + + + +extern const int8_t lab_table[196608 / 2]; +extern const int8_t lab_table2[196608]; + +#ifdef IMLIB_ENABLE_LAB_LUT +#if 0 +#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] +#define COLOR_RGB888_TO_L(pixel) lab_table[(((COLOR_R8_G8_B8_TO_RGB565(COLOR_RGB888_TO_R8(pixel), COLOR_RGB888_TO_G8(pixel), COLOR_RGB888_TO_B8(pixel))) >> 1) * 3) + 0] +#define COLOR_RGB888_TO_A(pixel) lab_table[(((COLOR_R8_G8_B8_TO_RGB565(COLOR_RGB888_TO_R8(pixel), COLOR_RGB888_TO_G8(pixel), COLOR_RGB888_TO_B8(pixel))) >> 1) * 3) + 1] +#define COLOR_RGB888_TO_B(pixel) lab_table[(((COLOR_R8_G8_B8_TO_RGB565(COLOR_RGB888_TO_R8(pixel), COLOR_RGB888_TO_G8(pixel), COLOR_RGB888_TO_B8(pixel))) >> 1) * 3) + 2] +#define COLOR_RGB888_TO_LAB(pixel, l, a, b) \ + ({\ + uint16_t rgb565_idx = (COLOR_R8_G8_B8_TO_RGB565(COLOR_RGB888_TO_R8(pixel), COLOR_RGB888_TO_G8(pixel), COLOR_RGB888_TO_B8(pixel)) >> 1) * 3;\ + (*l) = lab_table[rgb565_idx + 0];\ + (*a) = lab_table[rgb565_idx + 1];\ + (*b) = lab_table[rgb565_idx + 2];\ + }) +#else +#define COLOR_RGB565_TO_L(pixel) lab_table2[((pixel) * 3) + 0] +#define COLOR_RGB565_TO_A(pixel) lab_table2[((pixel) * 3) + 1] +#define COLOR_RGB565_TO_B(pixel) lab_table2[((pixel) * 3) + 2] +#define COLOR_RGB888_TO_L(pixel) lab_table2[((uint32_t)((COLOR_R8_G8_B8_TO_RGB565(COLOR_RGB888_TO_R8(pixel), COLOR_RGB888_TO_G8(pixel), COLOR_RGB888_TO_B8(pixel)))) * 3) + 0] +#define COLOR_RGB888_TO_A(pixel) lab_table2[((uint32_t)((COLOR_R8_G8_B8_TO_RGB565(COLOR_RGB888_TO_R8(pixel), COLOR_RGB888_TO_G8(pixel), COLOR_RGB888_TO_B8(pixel)))) * 3) + 1] +#define COLOR_RGB888_TO_B(pixel) lab_table2[((uint32_t)((COLOR_R8_G8_B8_TO_RGB565(COLOR_RGB888_TO_R8(pixel), COLOR_RGB888_TO_G8(pixel), COLOR_RGB888_TO_B8(pixel)))) * 3) + 2] +#define COLOR_RGB888_TO_LAB(pixel, l, a, b) \ + ({\ + uint32_t rgb565_idx = (COLOR_R8_G8_B8_TO_RGB565(COLOR_RGB888_TO_R8(pixel), COLOR_RGB888_TO_G8(pixel), COLOR_RGB888_TO_B8(pixel))) * 3;\ + (*l) = lab_table2[rgb565_idx + 0];\ + (*a) = lab_table2[rgb565_idx + 1];\ + (*b) = lab_table2[rgb565_idx + 2];\ + }) +#endif +#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) +#define COLOR_RGB888_TO_LAB(pixel, l, a, b) imlib_rgb888_to_lab(pixel, l, a, b) +#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_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_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_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_RGB888_TO_GRAYSCALE(pixel) COLOR_RGB888_TO_Y(pixel.r, pixel.g, pixel.b) +#define COLOR_RGB888_TO_BINARY(pixel) (COLOR_RGB888_TO_Y(pixel.r, pixel.g, pixel.b) > (((COLOR_Y_MAX - COLOR_Y_MIN) / 2) + COLOR_Y_MIN)) +#define COLOR_BINARY_TO_RGB888(pixel) COLOR_YUV_TO_RGB888(((pixel) ? 127 : -128), 0, 0) +#define COLOR_YUV_TO_R8(y, u, v) (IM_MAX(IM_MIN(y + ((91881 * v) >> 16), COLOR_R8_MAX), COLOR_R8_MIN)) +#define COLOR_YUV_TO_G8(y, u, v) (IM_MAX(IM_MIN(y - ((22554 * u + 46802 * v) >> 16), COLOR_G8_MAX), COLOR_G8_MIN)) +#define COLOR_YUV_TO_B8(y, u, v) (IM_MAX(IM_MIN(y + ((116130 * u) >> 16), COLOR_B8_MAX), COLOR_B8_MIN)) +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_PNG = 7, + PIXFORMAT_ID_ARGB8 = 8, + PIXFORMAT_ID_RGB888 = 9, + /* 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_ARGB8 = 4, + PIXFORMAT_BPP_RGB888 = 5, + /* 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) + +// *INDENT-OFF* +// 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; +// *INDENT-ON* + +#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) \ + +// *INDENT-OFF* +#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 */ \ +} +#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 */ \ +} +#else +#error "Byte order is not defined." +#endif +// *INDENT-ON* + +typedef struct image { + int32_t w; + int32_t h; + PIXFORMAT_STRUCT; + union { + uint8_t *pixels; + uint8_t *data; + }; +} image_t; + +void 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_line_size(image_t *ptr); +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(pixel_rgb_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_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; \ + }) + +#define IMAGE_GET_RGB888_PIXEL(image, x, y) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + ((pixel_rgb_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); \ + ((pixel_rgb_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_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); \ + }) + +#define IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(image, y) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (y) _y = (y); \ + ((pixel_rgb_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_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; \ +}) + +// 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_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_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_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; + +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; + float centroid_x, centroid_y; + 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_HMIRROR = (1 << 4), + IMAGE_HINT_VFLIP = (1 << 5), + IMAGE_HINT_TRANSPOSE = (1 << 6), + IMAGE_HINT_CENTER = (1 << 7), + IMAGE_HINT_EXTRACT_RGB_CHANNEL_FIRST = (1 << 8), + IMAGE_HINT_APPLY_COLOR_PALETTE_FIRST = (1 << 9), + IMAGE_HINT_SCALE_ASPECT_KEEP = (1 << 10), + IMAGE_HINT_SCALE_ASPECT_EXPAND = (1 << 11), + IMAGE_HINT_SCALE_ASPECT_IGNORE = (1 << 12), + 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 *callback_arg; // 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(); + +// 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(pixel_rgb_t pixel); +int8_t imlib_rgb888_to_a(pixel_rgb_t pixel); +int8_t imlib_rgb888_to_b(pixel_rgb_t pixel); +void imlib_rgb888_to_lab(pixel_rgb_t pixel, uint8_t *l, int8_t *a, int8_t *b); +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); +pixel_rgb_t imlib_yuv_to_rgb888(uint8_t y, int8_t u, int8_t v); +pixel_rgb_t imlib_lab_to_rgb888(uint8_t l, int8_t a, int8_t b); + +/* 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_write(FIL *fp, int width, int height, uint32_t *frames, uint32_t *bytes, +// image_t *img, int quality, rectangle_t *roi, int rgb_channel, int alpha, +// const uint16_t *color_palette, const uint8_t *alpha_palette, image_hint_t hint); +// void mjpeg_sync(FIL *fp, uint32_t *frames, uint32_t *bytes, float fps); +// 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 *t, rectangle_t *r); +float imlib_template_match_ex(image_t *image, image_t *t, 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); +void imlib_draw_image_get_bounds(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, + point_t *p0, + point_t *p1); +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(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 *callback_arg, + 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); +// 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_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 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); +// Stereo Imaging +void imlib_stereo_disparity(image_t *img, bool reversed, int max_disparity, int threshold); + +array_t *imlib_selective_search(image_t *src, float t, int min_size, float a1, float a2, float a3); +#endif //__IMLIB_H__ diff --git a/components/3rd_party/omv/omv/imlib/integral.c b/components/3rd_party/omv/omv/imlib/integral.c new file mode 100644 index 00000000..e58c0864 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/integral.c @@ -0,0 +1,117 @@ +/* + * 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 + if (sum) { + if (sum->data) { + fb_free(sum->data); + sum->data = NULL; + } + } +} + +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; x < src->w; x++) { + /* sum of the current row (integer) */ + s += img_data[x]; + sum_data[x] = s; + } + + for (int y = 1; y < src->h; y++) { + /* loop over the number of columns */ + for (int s = 0, x = 0; x < src->w; 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; x < sum->w; 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; y < sum->h; y++) { + int sy = (y * y_ratio) >> 16; + /* loop over the number of columns */ + for (int s = 0, x = 0; x < sum->w; 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; x < src->w; x++) { + /* sum of the current row (integer) */ + s += img_data[x] * img_data[x]; + sum_data[x] = s; + } + + for (uint32_t y = 1; y < src->h; y++) { + /* loop over the number of columns */ + for (uint32_t s = 0, x = 0; x < src->w; 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/components/3rd_party/omv/omv/imlib/integral_mw.c b/components/3rd_party/omv/omv/imlib/integral_mw.c new file mode 100644 index 00000000..53dc20c0 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/integral_mw.c @@ -0,0 +1,316 @@ +/* + * 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 = fb_alloc(h * sizeof(*sum->data), FB_ALLOC_NO_HINT); + // swap is used when shifting the image pointers + // to avoid overwriting the image rows in sum->data + sum->swap = fb_alloc(h * sizeof(*sum->data), FB_ALLOC_NO_HINT); + + for (int i = 0; i < h; i++) { + sum->data[i] = fb_alloc(w * sizeof(**sum->data), FB_ALLOC_NO_HINT); + } +} + +void imlib_integral_mw_free(mw_image_t *sum) { + if (sum) { + for (int i = 0; i < sum->h; i++) { + if (sum->data[i]) { + fb_free(sum->data[i]); // Free h lines + sum->data[i] = NULL; + } + } + + if (sum->swap) { + fb_free(sum->swap); + sum->swap = NULL; + } + + if (sum->data) { + fb_free(sum->data); + sum->data = NULL; + } + } +} + +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; x < sum->w; 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; y < sum->h; y++) { + // Y offset + sy = (y * sum->y_ratio) >> 16; + + // Sum the current row + for (int sx, s = 0, x = 0; x < sum->w; 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; x < sum->w; 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; y < sum->h; y++) { + // Y offset + sy = (y * sum->y_ratio) >> 16; + + // Sum the current row + for (int sx, s = 0, x = 0; x < sum->w; 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; y < sum->h; 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); y < sum->h; 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; x < sum->w; 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; y < sum->h; 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); y < sum->h; 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; x < sum->w; 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; x < sum->w; 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; y < sum->h; y++) { + // Y offset + sy = roi->y + ((y * sum->y_ratio) >> 16); + + // Sum the current row + for (int sx, s = 0, sq = 0, x = 0; x < sum->w; 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; y < sum->h; 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); y < sum->h; 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; x < sum->w; 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/components/3rd_party/omv/omv/imlib/isp.c b/components/3rd_party/omv/omv/imlib/isp.c new file mode 100644 index 00000000..3536fd7a --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/isp.c @@ -0,0 +1,1263 @@ +/* + * 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]; + } + + 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 + + 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 + + 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; + } + case PIXFORMAT_RGB888: { + pixel_rgb_t *ptr = (pixel_rgb_t *) img->data; + long n = img->w * img->h; // must be signed for count down loop + + int i_rr = IM_MIN(fast_roundf(rr * 256), 4096); + int i_rg = IM_MIN(fast_roundf(rg * 256), 4096); + int i_rb = IM_MIN(fast_roundf(rb * 256), 4096); + + int i_gr = IM_MIN(fast_roundf(gr * 256), 4096); + int i_gg = IM_MIN(fast_roundf(gg * 256), 4096); + int i_gb = IM_MIN(fast_roundf(gb * 256), 4096); + + int i_br = IM_MIN(fast_roundf(br * 256), 4096); + int i_bg = IM_MIN(fast_roundf(bg * 256), 4096); + int i_bb = IM_MIN(fast_roundf(bb * 256), 4096); + + int i_ro = IM_MIN(fast_roundf(ro * 256), 4096); + int i_go = IM_MIN(fast_roundf(go * 256), 4096); + int i_bo = IM_MIN(fast_roundf(bo * 256), 4096); + + #if defined(ARM_MATH_DSP) + #error "RGB888 format not support this operation" + 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 + + if (offset) { + for (; n > 0; n -= 1) { + pixel_rgb_t pixel = *ptr; + int g = COLOR_RGB888_TO_G8(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), 8, 8); + int new_g = __USAT_ASR(__SMLAD(r_b, smuad_gr_gb, (i_gg * g) + i_go), 8, 8); + int new_b = __USAT_ASR(__SMLAD(r_b, smuad_br_bb, (i_bg * g) + i_bo), 8, 8); + #else + int r = COLOR_RGB888_TO_R8(pixel); + int b = COLOR_RGB888_TO_B8(pixel); + int new_r = __USAT_ASR((i_rr * r) + (i_rg * g) + (i_rb * b) + i_ro, 8, 8); + int new_g = __USAT_ASR((i_gr * r) + (i_gg * g) + (i_gb * b) + i_go, 8, 8); + int new_b = __USAT_ASR((i_br * r) + (i_bg * g) + (i_bb * b) + i_bo, 8, 8); + #endif + *ptr++ = COLOR_R8_G8_B8_TO_RGB888(new_r, new_g, new_b); + } + } else { + for (; n > 0; n -= 1) { + pixel_rgb_t pixel = *ptr; + int g = COLOR_RGB888_TO_G8(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), 8, 8); + int new_g = __USAT_ASR(__SMLAD(r_b, smuad_gr_gb, i_gg * g), 8, 8); + int new_b = __USAT_ASR(__SMLAD(r_b, smuad_br_bb, i_bg * g), 8, 8); + #else + int r = COLOR_RGB888_TO_R8(pixel); + int b = COLOR_RGB888_TO_B8(pixel); + int new_r = __USAT_ASR((i_rr * r) + (i_rg * g) + (i_rb * b), 8, 8); + int new_g = __USAT_ASR((i_gr * r) + (i_gg * g) + (i_gb * b), 8, 8); + int new_b = __USAT_ASR((i_br * r) + (i_bg * g) + (i_bb * b), 8, 8); + #endif + *ptr++ = COLOR_R8_G8_B8_TO_RGB888(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); + } + } + + if (p_lut) fb_free(p_lut); + 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]; + } + } + + if (p_lut) 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); + } + + 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); + } + + if (b_lut) fb_free(b_lut); + if (g_lut) fb_free(g_lut); + if (r_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++) { + pixel_rgb_t *data = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + pixel_rgb_t 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)); + } + } + + if (b_lut) fb_free(b_lut); + if (g_lut) fb_free(g_lut); + if (r_lut) fb_free(r_lut); + break; + } + default: { + break; + } + } +} + +#endif // IMLIB_ENABLE_ISP_OPS diff --git a/components/3rd_party/omv/omv/imlib/jpeg.c b/components/3rd_party/omv/omv/imlib/jpeg.c new file mode 100644 index 00000000..938d7163 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/jpeg.c @@ -0,0 +1,2285 @@ +/* + * 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 "file_utils.h" +#include "imlib.h" +#include "omv_boardconfig.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] = { + 0x00000000, 0x000000ff, 0x0000ff00, 0x0000ffff, + 0x00ff0000, 0x00ff00ff, 0x00ffff00, 0x00ffffff, + 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: { + 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++; + 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_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) +#include STM32_HAL_H +#include "irq.h" +#include "dma_utils.h" + +#define FB_ALLOC_PADDING ((__SCB_DCACHE_LINE_SIZE) * 4) +#define OUTPUT_CHUNK_SIZE (512) // The minimum output buffer size is 2x this - so 1KB. +#define JPEG_INPUT_FIFO_BYTES (32) +#define JPEG_OUTPUT_FIFO_BYTES (32) + +static JPEG_HandleTypeDef JPEG_Handle = {}; +static JPEG_ConfTypeDef JPEG_Config = {}; +static MDMA_HandleTypeDef JPEG_MDMA_Handle_In = {}; +static MDMA_HandleTypeDef JPEG_MDMA_Handle_Out = {}; + +static int JPEG_out_data_length_max = 0; +static volatile int JPEG_out_data_length = 0; +static volatile bool JPEG_input_paused = false; +static volatile bool JPEG_output_paused = false; + +// JIFF-APP0 header designed to be injected at the start of the JPEG byte stream. +// Contains a variable sized COM header at the end for cache alignment. +static const uint8_t JPEG_APP0[] = { + 0xFF, 0xE0, // JIFF-APP0 + 0x00, 0x10, // 16 + 0x4A, 0x46, 0x49, 0x46, 0x00, // JIFF + 0x01, 0x01, // V1.01 + 0x01, // DPI + 0x00, 0x00, // Xdensity 0 + 0x00, 0x00, // Ydensity 0 + 0x00, // Xthumbnail 0 + 0x00, // Ythumbnail 0 + 0xFF, 0xFE // COM +}; + +void JPEG_IRQHandler() { + IRQ_ENTER(JPEG_IRQn); + HAL_JPEG_IRQHandler(&JPEG_Handle); + IRQ_EXIT(JPEG_IRQn); +} + +void jpeg_mdma_irq_handler() { + if (MDMA->GISR0 & (1 << OMV_MDMA_CHANNEL_JPEG_IN)) { + HAL_MDMA_IRQHandler(&JPEG_MDMA_Handle_In); + } + if (MDMA->GISR0 & (1 << OMV_MDMA_CHANNEL_JPEG_OUT)) { + HAL_MDMA_IRQHandler(&JPEG_MDMA_Handle_Out); + } +} + +static void jpeg_get_data_callback(JPEG_HandleTypeDef *hjpeg, uint32_t NbDecodedData) { + HAL_JPEG_Pause(hjpeg, JPEG_PAUSE_RESUME_INPUT); + JPEG_input_paused = true; +} + +static void jpeg_data_ready_callback(JPEG_HandleTypeDef *hjpeg, uint8_t *pDataOut, uint32_t OutDataLength) { + // We have received this much data. + JPEG_out_data_length += OutDataLength; + + if ((JPEG_out_data_length + OUTPUT_CHUNK_SIZE) > JPEG_out_data_length_max) { + // We will overflow if we receive anymore data. + HAL_JPEG_Pause(hjpeg, JPEG_PAUSE_RESUME_OUTPUT); + JPEG_output_paused = true; + } else { + uint8_t *new_pDataOut = pDataOut + OutDataLength; + + // DMA will write data to the output buffer in __SCB_DCACHE_LINE_SIZE aligned chunks. At the + // end of JPEG compression the processor will manually transfer the remaining parts of the + // image in randomly aligned chunks. We only want to invalidate the cache of the output + // buffer for the initial DMA chunks. So, this code below will do that and then only + // invalidate aligned regions when the processor is moving the final parts of the image. + if (!(((uint32_t) new_pDataOut) % __SCB_DCACHE_LINE_SIZE)) { + SCB_InvalidateDCache_by_Addr((uint32_t *) new_pDataOut, OUTPUT_CHUNK_SIZE); + } + + // We are ok to receive more data. + HAL_JPEG_ConfigOutputBuffer(hjpeg, new_pDataOut, OUTPUT_CHUNK_SIZE); + } +} + +bool jpeg_compress(image_t *src, image_t *dst, int quality, bool realloc) { +#if (TIME_JPEG == 1) + mp_uint_t start = mp_hal_ticks_ms(); +#endif + + int mcu_size = 0; + JPEG_ConfTypeDef JPEG_Info; + JPEG_Info.ImageWidth = src->w; + JPEG_Info.ImageHeight = src->h; + JPEG_Info.ImageQuality = quality; + + switch (src->pixfmt) { + case PIXFORMAT_BINARY: + case PIXFORMAT_GRAYSCALE: + mcu_size = JPEG_444_GS_MCU_SIZE; + JPEG_Info.ColorSpace = JPEG_GRAYSCALE_COLORSPACE; + JPEG_Info.ChromaSubsampling = JPEG_444_SUBSAMPLING; + break; + case PIXFORMAT_RGB565: + case PIXFORMAT_BAYER_ANY: + case PIXFORMAT_YUV_ANY: + mcu_size = JPEG_444_YCBCR_MCU_SIZE; + JPEG_Info.ColorSpace = JPEG_YCBCR_COLORSPACE; + JPEG_Info.ChromaSubsampling = JPEG_444_SUBSAMPLING; + break; + default: + break; + } + + if (memcmp(&JPEG_Config, &JPEG_Info, sizeof(JPEG_ConfTypeDef))) { + HAL_JPEG_ConfigEncoding(&JPEG_Handle, &JPEG_Info); + memcpy(&JPEG_Config, &JPEG_Info, sizeof(JPEG_ConfTypeDef)); + } + + int src_w_mcus = (src->w + MCU_W - 1) / MCU_W; + int src_w_mcus_bytes = src_w_mcus * mcu_size; + int src_w_mcus_bytes_2 = src_w_mcus_bytes * 2; + + // If dst->data == NULL then we need to fb_alloc() space for the payload which will be fb_free()'d + // by the caller. We have to alloc this memory for all cases if we return from the method. + if (!dst->data) { + uint32_t avail = image_size(src); + uint32_t space = src_w_mcus_bytes_2 + FB_ALLOC_PADDING; + + if (avail < space) { + fb_alloc_fail(); + } + + dst->size = IMLIB_IMAGE_MAX_SIZE(avail - space); + dst->data = fb_alloc(dst->size, FB_ALLOC_PREFER_SIZE | FB_ALLOC_CACHE_ALIGN); + } + + if (src->is_compressed) { + return true; + } + + // Compute size of the APP0 header with cache alignment padding. + int app0_size = sizeof(JPEG_APP0); + int app0_unalign_size = app0_size % __SCB_DCACHE_LINE_SIZE; + int app0_padding_size = app0_unalign_size ? (__SCB_DCACHE_LINE_SIZE - app0_unalign_size) : 0; + int app0_total_size = app0_size + app0_padding_size; + + if (dst->size < app0_total_size) { + return true; // overflow + } + + // Adjust JPEG size and address by app0 header size. + dst->size -= app0_total_size; + uint8_t *dma_buffer = dst->data + app0_total_size; + + // Destination is too small. + if (dst->size < (OUTPUT_CHUNK_SIZE * 2)) { + return true; // overflow + } + + JPEG_out_data_length_max = dst->size; + JPEG_out_data_length = 0; + JPEG_input_paused = false; + JPEG_output_paused = false; + + uint8_t *mcu_row_buffer = fb_alloc(src_w_mcus_bytes_2, FB_ALLOC_PREFER_SPEED | FB_ALLOC_CACHE_ALIGN); + + for (int y_offset = 0; y_offset < src->h; y_offset += MCU_H) { + uint8_t *mcu_row_buffer_ptr = mcu_row_buffer + (src_w_mcus_bytes * ((y_offset / MCU_H) % 2)); + + 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) { + int8_t *Y0 = (int8_t *) (mcu_row_buffer_ptr + (mcu_size * (x_offset / MCU_W))); + int8_t *CB = Y0 + JPEG_444_GS_MCU_SIZE; + int8_t *CR = CB + JPEG_444_GS_MCU_SIZE; + + int dx = src->w - x_offset; + if (dx > MCU_W) { + dx = MCU_W; + } + + // Copy 8x8 MCUs. + jpeg_get_mcu(src, x_offset, y_offset, dx, dy, Y0, CB, CR); + } + + // Flush the MCU row for DMA... + SCB_CleanDCache_by_Addr((uint32_t *) mcu_row_buffer_ptr, src_w_mcus_bytes); + + if (!y_offset) { + // Invalidate the output buffer. + SCB_InvalidateDCache_by_Addr(dma_buffer, OUTPUT_CHUNK_SIZE); + // Start the DMA process off on the first row of MCUs. + HAL_JPEG_Encode_DMA(&JPEG_Handle, mcu_row_buffer_ptr, src_w_mcus_bytes, dma_buffer, OUTPUT_CHUNK_SIZE); + } else { + + // Wait for the last row MCUs to be processed before starting the next row. + while (!JPEG_input_paused) { + __WFI(); + + if (JPEG_output_paused) { + memset(&JPEG_Config, 0, sizeof(JPEG_ConfTypeDef)); + HAL_JPEG_Abort(&JPEG_Handle); + if (mcu_row_buffer) fb_free(mcu_row_buffer); // mcu_row_buffer (after DMA is aborted) + return true; // overflow + } + } + + // Reset the lock. + JPEG_input_paused = false; + + // Restart the DMA process on the next row of MCUs (that were already prepared). + HAL_JPEG_ConfigInputBuffer(&JPEG_Handle, mcu_row_buffer_ptr, src_w_mcus_bytes); + HAL_JPEG_Resume(&JPEG_Handle, JPEG_PAUSE_RESUME_INPUT); + } + } + + // After writing the last MCU to the JPEG core it will eventually generate an end-of-conversion + // interrupt which will finish the JPEG encoding process and clear the busy flag. + + while (HAL_JPEG_GetState(&JPEG_Handle) == HAL_JPEG_STATE_BUSY_ENCODING) { + __WFI(); + + if (JPEG_output_paused) { + memset(&JPEG_Config, 0, sizeof(JPEG_ConfTypeDef)); + HAL_JPEG_Abort(&JPEG_Handle); + if (mcu_row_buffer) fb_free(mcu_row_buffer); // mcu_row_buffer (after DMA is aborted) + return true; // overflow + } + } + + if (mcu_row_buffer) fb_free(mcu_row_buffer); // mcu_row_buffer + + // Set output size. + dst->size = JPEG_out_data_length; + + // STM32H7 BUG FIX! The JPEG Encoder will occasionally trigger the EOCF interrupt before writing + // a final 0x000000D9 long into the output fifo as the end of the JPEG image. When this occurs + // the output fifo will have a single 0 value in it after the encoding process finishes. + if (__HAL_JPEG_GET_FLAG(&JPEG_Handle, JPEG_FLAG_OFNEF) && (!JPEG_Handle.Instance->DOR)) { + // The encoding output process always aborts before writing OUTPUT_CHUNK_SIZE bytes + // to the end of the dma_buffer. So, it is always safe to add one extra byte. + dma_buffer[dst->size++] = 0xD9; + } + + // Update the JPEG image size by the new APP0 header and it's padding. However, we have to move + // the SOI header to the front of the image first... + dst->size += app0_total_size; + memcpy(dst->data, dma_buffer, sizeof(uint16_t)); // move SOI + memcpy(dst->data + sizeof(uint16_t), JPEG_APP0, sizeof(JPEG_APP0)); // inject APP0 + + // Add on a comment header with 0 padding to ensure cache alignment after the APP0 header. + *((uint16_t *) (dst->data + sizeof(uint16_t) + sizeof(JPEG_APP0))) = __REV16(app0_padding_size); // size + memset(dst->data + sizeof(uint32_t) + sizeof(JPEG_APP0), 0, app0_padding_size - sizeof(uint16_t)); // data + + // Clean trailing data after 0xFFD9 at the end of the jpeg byte stream. + dst->size = jpeg_clean_trailing_bytes(dst->size, dst->data); + + #if (TIME_JPEG == 1) + printf("time: %u ms\n", mp_hal_ticks_ms() - start); + #endif + + return false; +} + +void imlib_jpeg_compress_init() { + JPEG_Handle.Instance = JPEG; + HAL_JPEG_Init(&JPEG_Handle); + // Register JPEG callbacks. + HAL_JPEG_RegisterGetDataCallback(&JPEG_Handle, jpeg_get_data_callback); + HAL_JPEG_RegisterDataReadyCallback(&JPEG_Handle, jpeg_data_ready_callback); + + NVIC_SetPriority(JPEG_IRQn, IRQ_PRI_JPEG); + HAL_NVIC_EnableIRQ(JPEG_IRQn); + + JPEG_MDMA_Handle_In.Instance = MDMA_CHAN_TO_INSTANCE(OMV_MDMA_CHANNEL_JPEG_IN); + JPEG_MDMA_Handle_In.Init.Request = MDMA_REQUEST_JPEG_INFIFO_TH; + JPEG_MDMA_Handle_In.Init.TransferTriggerMode = MDMA_BUFFER_TRANSFER; + JPEG_MDMA_Handle_In.Init.Priority = MDMA_PRIORITY_LOW; + JPEG_MDMA_Handle_In.Init.Endianness = MDMA_LITTLE_ENDIANNESS_PRESERVE; + JPEG_MDMA_Handle_In.Init.SourceInc = MDMA_SRC_INC_DOUBLEWORD; + JPEG_MDMA_Handle_In.Init.DestinationInc = MDMA_DEST_INC_DISABLE; + JPEG_MDMA_Handle_In.Init.SourceDataSize = MDMA_SRC_DATASIZE_DOUBLEWORD; + JPEG_MDMA_Handle_In.Init.DestDataSize = MDMA_DEST_DATASIZE_WORD; + JPEG_MDMA_Handle_In.Init.DataAlignment = MDMA_DATAALIGN_PACKENABLE; + JPEG_MDMA_Handle_In.Init.BufferTransferLength = JPEG_INPUT_FIFO_BYTES; + JPEG_MDMA_Handle_In.Init.SourceBurst = MDMA_SOURCE_BURST_4BEATS; + JPEG_MDMA_Handle_In.Init.DestBurst = MDMA_DEST_BURST_8BEATS; + JPEG_MDMA_Handle_In.Init.SourceBlockAddressOffset = 0; + JPEG_MDMA_Handle_In.Init.DestBlockAddressOffset = 0; + + HAL_MDMA_Init(&JPEG_MDMA_Handle_In); + __HAL_LINKDMA(&JPEG_Handle, hdmain, JPEG_MDMA_Handle_In); + + JPEG_MDMA_Handle_Out.Instance = MDMA_CHAN_TO_INSTANCE(OMV_MDMA_CHANNEL_JPEG_OUT); + JPEG_MDMA_Handle_Out.Init.Request = MDMA_REQUEST_JPEG_OUTFIFO_TH; + JPEG_MDMA_Handle_Out.Init.TransferTriggerMode = MDMA_BUFFER_TRANSFER; + JPEG_MDMA_Handle_Out.Init.Priority = MDMA_PRIORITY_LOW; + JPEG_MDMA_Handle_Out.Init.Endianness = MDMA_LITTLE_ENDIANNESS_PRESERVE; + JPEG_MDMA_Handle_Out.Init.SourceInc = MDMA_SRC_INC_DISABLE; + JPEG_MDMA_Handle_Out.Init.DestinationInc = MDMA_DEST_INC_DOUBLEWORD; + JPEG_MDMA_Handle_Out.Init.SourceDataSize = MDMA_SRC_DATASIZE_WORD; + JPEG_MDMA_Handle_Out.Init.DestDataSize = MDMA_DEST_DATASIZE_DOUBLEWORD; + JPEG_MDMA_Handle_Out.Init.DataAlignment = MDMA_DATAALIGN_PACKENABLE; + JPEG_MDMA_Handle_Out.Init.BufferTransferLength = JPEG_OUTPUT_FIFO_BYTES; + JPEG_MDMA_Handle_Out.Init.SourceBurst = MDMA_SOURCE_BURST_8BEATS; + JPEG_MDMA_Handle_Out.Init.DestBurst = MDMA_DEST_BURST_4BEATS; + JPEG_MDMA_Handle_Out.Init.SourceBlockAddressOffset = 0; + JPEG_MDMA_Handle_Out.Init.DestBlockAddressOffset = 0; + + HAL_MDMA_Init(&JPEG_MDMA_Handle_Out); + __HAL_LINKDMA(&JPEG_Handle, hdmaout, JPEG_MDMA_Handle_Out); +} + +void imlib_jpeg_compress_deinit() { + memset(&JPEG_Config, 0, sizeof(JPEG_ConfTypeDef)); + HAL_JPEG_Abort(&JPEG_Handle); + HAL_MDMA_DeInit(&JPEG_MDMA_Handle_Out); + HAL_MDMA_DeInit(&JPEG_MDMA_Handle_In); + HAL_NVIC_DisableIRQ(JPEG_IRQn); + HAL_JPEG_DeInit(&JPEG_Handle); +} + +#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 << bits[1]) - 1); +} + +static int jpeg_processDU(jpeg_buf_t *jpeg_buf, int8_t *CDU, float *fdtbl, int DC, const uint16_t (*HTDC)[2], + const uint16_t (*HTAC)[2]) { + int DU[64]; + int DUQ[64]; + int z1, z2, z3, z4, z5, z11, z13; + int t0, t1, t2, t3, t4, t5, t6, t7, t10, t11, t12, t13; + const uint16_t EOB[2] = { HTAC[0x00][0], HTAC[0x00][1] }; + const uint16_t M16zeroes[2] = { HTAC[0xF0][0], HTAC[0xF0][1] }; + + // DCT rows + for (int i = 8, *p = DU; i > 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; i < nr_comp; i++) { + // Component ID, HV sampling, q table idx + jpeg_put_bytes(jpeg_buf, (uint8_t [3]) {i + 1, (i == 0 && bpp == 2)? jpeg_subsample:0x11, (i > 0)}, 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; i < nr_comp; i++) { + jpeg_put_bytes(jpeg_buf, (uint8_t [2]) {i + 1, (i == 0)? 0x00:0x11}, 2); + } + + // Spectral selection + jpeg_put_bytes(jpeg_buf, (uint8_t [3]) {0x00, 0x3F, 0x0}, 3); +} + +bool jpeg_compress(image_t *src, image_t *dst, int quality, bool realloc) { + #if (TIME_JPEG == 1) + mp_uint_t start = mp_hal_ticks_ms(); + #endif + + if (!dst->data) { + uint32_t size = 0; + dst->data = fb_alloc_all(&size, FB_ALLOC_PREFER_SIZE | FB_ALLOC_CACHE_ALIGN); + dst->size = IMLIB_IMAGE_MAX_SIZE(size); + } + + 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; + file_read(fp, &header, 2); + 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; + file_read(fp, &size, 2); + size = __REV16(size); + if (((0xFFC0 <= header) && (header <= 0xFFC3)) + || ((0xFFC5 <= header) && (header <= 0xFFC7)) + || ((0xFFC9 <= header) && (header <= 0xFFCB)) + || ((0xFFCD <= header) && (header <= 0xFFCF))) { + file_read(fp, NULL, 1); + uint16_t height; + file_read(fp, &height, 2); + height = __REV16(height); + + uint16_t width; + file_read(fp, &width, 2); + width = __REV16(width); + + rs->jpg_w = width; + rs->jpg_h = height; + rs->jpg_size = IMLIB_IMAGE_MAX_SIZE(f_size(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, f_tell(fp) + size - 2); + } + } else { + file_raise_corrupted(fp); + } + } +} + +// This function reads the pixel values of an image. +void jpeg_read_pixels(FIL *fp, image_t *img) { + file_seek(fp, 0); + file_read(fp, img->pixels, img->size); +} + +void jpeg_read(image_t *img, const char *path) { + FIL fp; + jpg_read_settings_t rs; + + // Do not use file buffering here. + file_open(&fp, path, false, FA_READ | FA_OPEN_EXISTING); + jpeg_read_geometry(&fp, img, path, &rs); + + if (!img->pixels) { + img->pixels = xalloc(img->size); + } + + jpeg_read_pixels(&fp, img); + file_close(&fp); +} + +void jpeg_write(image_t *img, const char *path, int quality) { + FIL fp; + file_open(&fp, path, false, FA_WRITE | FA_CREATE_ALWAYS); + if (IM_IS_JPEG(img)) { + file_write(&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); + file_write(&fp, out.pixels, out.size); + if (out.pixels) fb_free(out.pixels); // frees alloc in jpeg_compress() + } + file_close(&fp); +} +#endif //IMLIB_ENABLE_IMAGE_FILE_IO) diff --git a/components/3rd_party/omv/omv/imlib/jpegd.c b/components/3rd_party/omv/omv/imlib/jpegd.c new file mode 100644 index 00000000..6413731c --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/jpegd.c @@ -0,0 +1,3109 @@ +/* + * 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 "imlib_config.h" +#include "omv_boardconfig.h" +#include "py/obj.h" +#include "py/nlr.h" +#include "py/runtime.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; i < img->size;) { + 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) +/* Hardware JPEG decoder */ +#include STM32_HAL_H +#define JPEG_MCU_SIZE_444 (192) +#define JPEG_MCU_SIZE_422 (256) +#define JPEG_MCU_SIZE_420 (384) +#define JPEG_MCU_SIZE_GRAY (64) +#define debug_printf(...) //printf(__VA_ARGS__) + +typedef struct { + uint32_t width; + uint32_t height; + uint8_t *pixels; + uint32_t bpp; + uint32_t error; + pixformat_t pixfmt; + uint32_t mcu_count; + uint32_t mcu_width; + uint32_t mcu_height; + uint32_t mcu_per_line; + uint8_t *mcu_buffer[2]; + uint32_t mcu_buffer_size; +} jpeg_state_t; + +static jpeg_state_t *jpeg_state = NULL; +static JPEG_HandleTypeDef hjpeg = {0}; +static DMA2D_HandleTypeDef hdma2d = {0}; + +static int dma2d_config(uint32_t pixfmt, uint32_t colorspace, uint32_t subsampling) { + // Initial DMA2D configuration. + hdma2d.Instance = DMA2D; + HAL_DMA2D_DeInit(&hdma2d); + hdma2d.XferCpltCallback = NULL; + hdma2d.XferErrorCallback = NULL; + + // Configure DMA2D output. + hdma2d.Init.OutputOffset = 0; + hdma2d.Init.ColorMode = DMA2D_OUTPUT_RGB565; // Ignored in M2M mode. + hdma2d.Init.RedBlueSwap = DMA2D_RB_REGULAR; + hdma2d.Init.AlphaInverted = DMA2D_REGULAR_ALPHA; + hdma2d.Init.BytesSwap = DMA2D_BYTES_REGULAR; + hdma2d.Init.LineOffsetMode = DMA2D_LOM_PIXELS; + + // Configure DMA2D input. + hdma2d.LayerCfg[1].AlphaMode = DMA2D_NO_MODIF_ALPHA; + hdma2d.LayerCfg[1].InputAlpha = 0xFF; + hdma2d.LayerCfg[1].InputOffset = 0; + hdma2d.LayerCfg[1].RedBlueSwap = DMA2D_RB_REGULAR; + hdma2d.LayerCfg[1].AlphaInverted = DMA2D_REGULAR_ALPHA; + hdma2d.LayerCfg[1].ChromaSubSampling = subsampling; + + if (pixfmt == PIXFORMAT_GRAYSCALE && colorspace == JPEG_GRAYSCALE_COLORSPACE) { + hdma2d.Init.Mode = DMA2D_M2M; // GS -> GS, no PFC. + hdma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_L8; + } else if (pixfmt == PIXFORMAT_RGB565 && colorspace == JPEG_YCBCR_COLORSPACE) { + hdma2d.Init.Mode = DMA2D_M2M_PFC; + hdma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_YCBCR; + } else { + // Unsupported pixel format conversion + return -1; + } + + // DMA2D initialization. + HAL_DMA2D_Init(&hdma2d); + HAL_DMA2D_ConfigLayer(&hdma2d, 1); + return 0; +} + +static void dma2d_copy_mcu(uint32_t x, uint32_t y, uint32_t mcu_width, uint32_t mcu_height, uint8_t *mcu_buffer) { + // Wait for previous transfer. + if (HAL_DMA2D_PollForTransfer(&hdma2d, 1000) != HAL_OK) { + // TODO abort transfer and decoding, and set error flag. + } + + // Configure DMA2D output. + hdma2d.Init.OutputOffset = jpeg_state->width - mcu_width; + hdma2d.LayerCfg[1].InputOffset = jpeg_state->mcu_width - mcu_width; + + // DMA2D initialization. + HAL_DMA2D_Init(&hdma2d); + HAL_DMA2D_ConfigLayer(&hdma2d, 1); + + uint8_t *dst = jpeg_state->pixels + (y * jpeg_state->width + x) * jpeg_state->bpp; + + // Output size in pixels per line and number of lines + HAL_DMA2D_Start(&hdma2d, (uint32_t) mcu_buffer, (uint32_t) dst, mcu_width, mcu_height); +} + +static void jpeg_info_ready_callback(JPEG_HandleTypeDef *hjpeg, JPEG_ConfTypeDef *jpeg_info) { + uint32_t subsampling = DMA2D_NO_CSS; + debug_printf("colorspace %ld subsampling %ld width %ld height %ld quality %ld\n", + jpeg_info->ColorSpace, jpeg_info->ChromaSubsampling, + jpeg_info->ImageWidth, jpeg_info->ImageHeight, jpeg_info->ImageQuality); + + switch (jpeg_info->ChromaSubsampling) { + case JPEG_420_SUBSAMPLING: + subsampling = DMA2D_CSS_420; + jpeg_state->mcu_width = 16; + jpeg_state->mcu_height = 16; + jpeg_state->mcu_buffer_size = JPEG_MCU_SIZE_420; + break; + case JPEG_422_SUBSAMPLING: + subsampling = DMA2D_CSS_422; + jpeg_state->mcu_width = 16; + jpeg_state->mcu_height = 8; + jpeg_state->mcu_buffer_size = JPEG_MCU_SIZE_422; + break; + case JPEG_444_SUBSAMPLING: + subsampling = DMA2D_NO_CSS; + jpeg_state->mcu_width = 8; + jpeg_state->mcu_height = 8; + if (jpeg_info->ColorSpace == JPEG_YCBCR_COLORSPACE) { + jpeg_state->mcu_buffer_size = JPEG_MCU_SIZE_444; + } else { + // Grayscale + jpeg_state->mcu_buffer_size = JPEG_MCU_SIZE_GRAY; + } + break; + default: // unknown subsampling. + goto config_error; + } + + if (dma2d_config(jpeg_state->pixfmt, jpeg_info->ColorSpace, subsampling) != 0) { + goto config_error; + } + + // Allocate MCU buffer(s). + jpeg_state->mcu_buffer[0] = fb_alloc(jpeg_state->mcu_buffer_size, FB_ALLOC_PREFER_SPEED | FB_ALLOC_CACHE_ALIGN); + jpeg_state->mcu_buffer[1] = fb_alloc(jpeg_state->mcu_buffer_size, FB_ALLOC_PREFER_SPEED | FB_ALLOC_CACHE_ALIGN); + jpeg_state->mcu_per_line = (jpeg_state->width / jpeg_state->mcu_width) + !!(jpeg_state->width % jpeg_state->mcu_width); + + // Set JPEG output buffer. + HAL_JPEG_ConfigOutputBuffer(hjpeg, jpeg_state->mcu_buffer[0], jpeg_state->mcu_buffer_size); + return; + +config_error: + HAL_JPEG_Abort(hjpeg); + jpeg_state->error = true; + return; +} + +static void jpeg_data_ready_callback(JPEG_HandleTypeDef *hjpeg, uint8_t *mcu_buffer, uint32_t length) { + uint32_t mcu_width = jpeg_state->mcu_width; + uint32_t mcu_height = jpeg_state->mcu_height; + uint32_t x = (jpeg_state->mcu_count % jpeg_state->mcu_per_line) * mcu_width; + uint32_t y = (jpeg_state->mcu_count / jpeg_state->mcu_per_line) * mcu_height; + + if ((x + mcu_width) > jpeg_state->width) { + mcu_width = jpeg_state->width - x; + debug_printf("mcu: %lu mcu_perline %lu x:%lu y:%lu mcu_w:%lu mcu_cropped_w:%lu\n", + jpeg_state->mcu_count % jpeg_state->mcu_per_line, jpeg_state->mcu_per_line, + x, y, jpeg_state->mcu_width, mcu_width); + } + + if ((y + mcu_height) > jpeg_state->height) { + mcu_height = jpeg_state->height - y; + debug_printf("mcu: %ld mcu_perline %lu x:%lu y:%lu mcu_h:%lu mcu_cropped_h:%lu\n", + jpeg_state->mcu_count % jpeg_state->mcu_per_line, jpeg_state->mcu_per_line, + x, y, jpeg_state->mcu_height, mcu_height); + } + + // Flush MCU buffer cache for DMA2D. + SCB_CleanDCache_by_Addr((uint32_t *) mcu_buffer, jpeg_state->mcu_buffer_size); + + // Copy MCU to target frame buffer. + dma2d_copy_mcu(x, y, mcu_width, mcu_height, mcu_buffer); + + if (mcu_buffer == jpeg_state->mcu_buffer[0]) { + HAL_JPEG_ConfigOutputBuffer(hjpeg, jpeg_state->mcu_buffer[1], jpeg_state->mcu_buffer_size); + } else { + HAL_JPEG_ConfigOutputBuffer(hjpeg, jpeg_state->mcu_buffer[0], jpeg_state->mcu_buffer_size); + } + jpeg_state->mcu_count++; +} + +void jpeg_decompress(image_t *dst, image_t *src) { + hjpeg.Instance = JPEG; + HAL_JPEG_DeInit(&hjpeg); + HAL_JPEG_Init(&hjpeg); + + // Register JPEG callbacks. + HAL_JPEG_RegisterDataReadyCallback(&hjpeg, jpeg_data_ready_callback); + HAL_JPEG_RegisterInfoReadyCallback(&hjpeg, jpeg_info_ready_callback); + + // Supports decoding to Grayscale or RGB565 only. + if (dst->pixfmt != PIXFORMAT_GRAYSCALE && dst->pixfmt != PIXFORMAT_RGB565) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unsupported format.")); + } + + // Supports decoding baseline JPEGs only. + if (jpeg_is_progressive(src)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Progressive JPEG is not supported.")); + } + + // Set/reset JPEG state. + jpeg_state_t jpeg_state_stk = { + .width = dst->w, + .height = dst->h, + .pixels = dst->data, + .bpp = dst->bpp, + .error = false, + .pixfmt = dst->pixfmt, + .mcu_count = 0, + .mcu_width = 0, + .mcu_height = 0, + .mcu_per_line = 0, + .mcu_buffer = {NULL, NULL}, + .mcu_buffer_size = 0, + }; + + jpeg_state = &jpeg_state_stk; + + #if (TIME_JPEG == 1) + mp_uint_t start = mp_hal_ticks_ms(); + #endif + + uint8_t buf[1024]; + // Start decoding JPEG headers. + if (HAL_JPEG_Decode(&hjpeg, src->data, src->size, buf, sizeof(buf), 3000) != HAL_OK || jpeg_state->error) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("JPEG decoder failed")); + } + + if (jpeg_state->mcu_buffer[0] != NULL) { + #error "fb_free() is commented, but we need to free the memory of MCU buffer here." + // Free MCU buffer(s). + // fb_free(); + // fb_free(); + } + + // Invalidate the dest image cache. + SCB_InvalidateDCache_by_Addr(dst->pixels, image_size(dst)); + + #if (TIME_JPEG == 1) + printf("time: %u ms\n", mp_hal_ticks_ms() - start); + #endif +} +#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 +}; + +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 + +// 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++) { + // calculate 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; i < ycount; i++) { + // do up to 8 rows + for (j = 0; j < xcount; j++) { + *usDest++ = usGrayTo565[*pSrc++]; + } + pSrc += (8 - xcount); + usDest -= xcount; + usDest += iPitch; // next line + } + } // RGB565 +} + +static void JPEGPutMCU8BitGray(JPEGIMAGE *pJPEG, int x, int y) { + int i, j, xcount, ycount; + const int iPitch = pJPEG->iWidth; + 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.")); + } + + 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.")); + } + + 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; + default: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("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.")); + } + + #if (TIME_JPEG == 1) + printf("time: %u ms\n", mp_hal_ticks_ms() - start); + #endif +} +#endif diff --git a/components/3rd_party/omv/omv/imlib/kmeans.c b/components/3rd_party/omv/omv/imlib/kmeans.c new file mode 100644 index 00000000..dc4f9b5d --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/kmeans.c @@ -0,0 +1,135 @@ +/* + * 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; j < array_length(clusters); j++) { + cluster_t *cl = array_at(clusters, j); + while (array_length(cl->points)) { + 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; j < array_length(clusters); j++) { + cluster_t *cl = array_at(clusters, j); + int cx = cl->x, cy = cl->y; + int cl_size = array_length(cl->points); + + // Sum all points in this cluster + for (int i = 0; i < cl_size; i++) { + kp_t *p = array_at(cl->points, 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; j < array_length(clusters); j++) { + cluster_t *cl = array_at(clusters, j); + float d = dist_func(cl->x, 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; i < k; i++) { + int pidx = rng_randint(0, array_length(points) - 1); + kp_t *p = array_at(points, pidx); + array_push_back(clusters, cluster_alloc(p->x, 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/components/3rd_party/omv/omv/imlib/lab_tab.c b/components/3rd_party/omv/omv/imlib/lab_tab.c new file mode 100644 index 00000000..47423826 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/lab_tab.c @@ -0,0 +1,24582 @@ +#include +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 +}; + +const int8_t lab_table2[196608] = { + 0, 0, 0, 0, 1, -3, 0, 2, -6, 1, 4, -12, + 1, 7, -18, 1, 10, -24, 2, 14, -29, 3, 19, -34, + 4, 25, -39, 4, 29, -43, 6, 33, -47, 7, 37, -50, + 8, 40, -54, 9, 42, -57, 11, 44, -60, 12, 46, -63, + 14, 49, -66, 15, 51, -69, 16, 53, -72, 17, 55, -75, + 19, 57, -78, 20, 59, -81, 21, 61, -83, 23, 63, -86, + 24, 65, -89, 25, 67, -92, 26, 69, -94, 28, 71, -97, + 29, 73, -100, 30, 75, -103, 31, 77, -105, 32, 79, -108, + 1, -2, 1, 1, 0, -2, 1, 1, -5, 1, 3, -11, + 2, 5, -17, 2, 9, -22, 3, 12, -28, 4, 18, -33, + 4, 23, -37, 5, 27, -42, 6, 31, -45, 7, 34, -49, + 9, 37, -53, 10, 40, -56, 11, 42, -59, 13, 44, -62, + 14, 47, -65, 15, 49, -68, 17, 51, -71, 18, 54, -74, + 19, 56, -77, 20, 58, -80, 22, 60, -83, 23, 62, -86, + 24, 64, -88, 25, 67, -91, 27, 69, -94, 28, 71, -97, + 29, 73, -99, 30, 75, -102, 31, 77, -105, 32, 79, -108, + 2, -3, 2, 2, -2, -1, 2, -1, -4, 2, 1, -10, + 3, 4, -16, 3, 7, -21, 4, 11, -26, 4, 16, -32, + 5, 21, -36, 6, 25, -40, 7, 29, -44, 8, 32, -48, + 10, 35, -51, 11, 38, -55, 12, 40, -58, 13, 43, -61, + 15, 46, -64, 16, 48, -68, 17, 50, -70, 18, 52, -73, + 20, 55, -77, 21, 57, -80, 22, 59, -82, 23, 61, -85, + 24, 63, -88, 26, 66, -91, 27, 68, -94, 28, 70, -96, + 29, 72, -99, 30, 74, -102, 32, 76, -105, 33, 78, -107, + 2, -5, 3, 3, -4, 0, 3, -2, -3, 3, 0, -9, + 3, 2, -14, 4, 5, -20, 4, 9, -25, 5, 14, -30, + 6, 19, -35, 7, 23, -39, 8, 27, -43, 9, 30, -46, + 10, 33, -50, 11, 36, -54, 13, 39, -57, 14, 41, -60, + 15, 44, -64, 16, 46, -67, 17, 49, -70, 19, 51, -73, + 20, 54, -76, 21, 56, -79, 22, 58, -82, 23, 60, -85, + 25, 63, -87, 26, 65, -91, 27, 67, -93, 28, 69, -96, + 29, 71, -99, 31, 73, -102, 32, 75, -104, 33, 77, -107, + 3, -7, 5, 4, -6, 2, 4, -4, -2, 4, -2, -7, + 4, 0, -13, 5, 3, -18, 5, 7, -23, 6, 12, -29, + 7, 17, -33, 8, 21, -37, 9, 24, -41, 10, 27, -45, + 11, 31, -49, 12, 34, -52, 13, 37, -56, 14, 39, -59, + 16, 42, -63, 17, 45, -66, 18, 47, -69, 19, 50, -72, + 20, 52, -75, 22, 55, -78, 23, 57, -81, 24, 59, -84, + 25, 62, -87, 26, 64, -90, 27, 66, -93, 28, 68, -95, + 30, 70, -98, 31, 73, -101, 32, 75, -104, 33, 77, -107, + 5, -9, 7, 5, -8, 4, 5, -7, 0, 5, -5, -6, + 6, -2, -11, 6, 1, -17, 7, 5, -22, 7, 10, -27, + 8, 14, -31, 9, 17, -36, 10, 21, -40, 11, 25, -43, + 12, 28, -48, 13, 31, -51, 14, 34, -55, 15, 37, -58, + 16, 40, -62, 17, 43, -65, 19, 46, -68, 20, 48, -71, + 21, 51, -74, 22, 53, -77, 23, 56, -80, 24, 58, -83, + 25, 60, -86, 27, 63, -89, 28, 65, -92, 29, 67, -95, + 30, 69, -98, 31, 72, -101, 32, 74, -103, 33, 76, -106, + 6, -12, 9, 6, -11, 6, 6, -10, 2, 7, -8, -3, + 7, -5, -9, 7, -2, -14, 8, 2, -19, 9, 7, -25, + 9, 11, -29, 10, 14, -34, 11, 18, -38, 12, 22, -42, + 13, 25, -46, 14, 29, -50, 15, 32, -53, 16, 35, -57, + 17, 38, -60, 18, 41, -64, 19, 44, -67, 20, 46, -70, + 21, 49, -74, 23, 52, -77, 24, 54, -80, 25, 57, -83, + 26, 59, -85, 27, 62, -89, 28, 64, -91, 29, 66, -94, + 30, 68, -97, 32, 71, -100, 33, 73, -103, 34, 75, -105, + 8, -15, 11, 8, -14, 8, 8, -13, 4, 8, -11, -1, + 8, -8, -7, 9, -5, -12, 9, -1, -17, 10, 3, -22, + 11, 7, -27, 11, 11, -31, 12, 15, -36, 13, 19, -40, + 14, 23, -44, 15, 26, -48, 16, 29, -52, 17, 32, -55, + 18, 36, -59, 19, 39, -62, 20, 42, -66, 21, 44, -69, + 22, 47, -72, 23, 50, -76, 24, 52, -79, 25, 55, -82, + 26, 57, -85, 28, 60, -88, 29, 62, -91, 30, 65, -94, + 31, 67, -96, 32, 69, -99, 33, 72, -102, 34, 74, -105, + 9, -19, 14, 9, -18, 10, 10, -16, 7, 10, -14, 1, + 10, -11, -4, 11, -8, -9, 11, -4, -15, 12, 0, -20, + 12, 4, -25, 13, 8, -29, 13, 12, -34, 14, 16, -38, + 15, 20, -42, 16, 23, -46, 17, 26, -50, 18, 30, -54, + 19, 33, -58, 20, 36, -61, 21, 39, -64, 22, 42, -68, + 23, 45, -71, 24, 48, -74, 25, 51, -78, 26, 53, -81, + 27, 56, -84, 28, 58, -87, 29, 61, -90, 30, 63, -93, + 31, 65, -96, 32, 68, -99, 33, 70, -101, 35, 72, -104, + 11, -22, 16, 11, -20, 13, 11, -19, 9, 11, -17, 4, + 12, -14, -2, 12, -10, -7, 13, -7, -12, 13, -3, -18, + 14, 1, -22, 14, 5, -27, 15, 9, -31, 15, 13, -36, + 16, 17, -40, 17, 20, -44, 18, 24, -48, 19, 27, -52, + 20, 31, -56, 21, 34, -59, 22, 37, -63, 23, 40, -66, + 24, 43, -70, 25, 46, -73, 26, 49, -76, 27, 51, -79, + 28, 54, -83, 29, 57, -86, 30, 59, -89, 31, 61, -92, + 32, 64, -95, 33, 66, -98, 34, 69, -101, 35, 71, -103, + 13, -24, 18, 13, -22, 15, 13, -21, 12, 13, -19, 6, + 13, -16, 1, 14, -13, -5, 14, -10, -10, 15, -6, -15, + 15, -2, -20, 16, 2, -25, 16, 6, -29, 17, 9, -34, + 18, 14, -38, 18, 17, -42, 19, 21, -46, 20, 24, -50, + 21, 28, -54, 22, 31, -58, 22, 34, -61, 23, 37, -65, + 24, 41, -69, 25, 44, -72, 26, 46, -75, 27, 49, -78, + 28, 52, -81, 29, 55, -85, 30, 57, -88, 31, 60, -91, + 32, 62, -94, 33, 65, -97, 34, 67, -100, 35, 69, -103, + 15, -26, 21, 15, -24, 18, 15, -23, 14, 15, -21, 9, + 15, -19, 4, 16, -16, -2, 16, -13, -7, 16, -9, -12, + 17, -5, -17, 17, -2, -22, 18, 2, -27, 18, 6, -31, + 19, 10, -36, 20, 14, -40, 20, 17, -44, 21, 21, -48, + 22, 24, -52, 23, 28, -56, 24, 31, -59, 25, 34, -63, + 26, 38, -67, 26, 41, -70, 27, 43, -73, 28, 46, -77, + 29, 49, -80, 30, 52, -83, 31, 55, -86, 32, 57, -89, + 33, 60, -92, 34, 63, -96, 35, 65, -99, 36, 67, -101, + 16, -27, 23, 17, -26, 20, 17, -25, 16, 17, -23, 11, + 17, -21, 6, 17, -18, 1, 18, -15, -4, 18, -11, -10, + 18, -8, -15, 19, -4, -20, 19, -1, -24, 20, 3, -29, + 20, 7, -34, 21, 11, -38, 22, 14, -42, 22, 18, -46, + 23, 22, -50, 24, 25, -54, 25, 28, -58, 26, 31, -61, + 26, 35, -65, 27, 38, -69, 28, 41, -72, 29, 44, -75, + 30, 47, -78, 31, 50, -82, 32, 53, -85, 33, 55, -88, + 34, 58, -91, 35, 61, -95, 36, 63, -98, 37, 66, -100, + 18, -28, 25, 18, -27, 22, 18, -26, 18, 18, -24, 13, + 19, -22, 8, 19, -20, 3, 19, -17, -2, 19, -14, -8, + 20, -11, -13, 20, -7, -17, 21, -4, -22, 21, 0, -27, + 22, 4, -32, 22, 8, -36, 23, 11, -40, 24, 15, -44, + 24, 19, -48, 25, 22, -52, 26, 25, -56, 27, 29, -60, + 27, 32, -64, 28, 35, -67, 29, 39, -70, 30, 41, -74, + 31, 44, -77, 32, 48, -81, 33, 50, -84, 34, 53, -87, + 35, 56, -90, 36, 59, -93, 36, 61, -96, 37, 64, -99, + 20, -30, 27, 20, -29, 24, 20, -28, 20, 20, -26, 15, + 20, -24, 10, 20, -22, 5, 21, -19, 0, 21, -16, -5, + 21, -13, -10, 22, -10, -15, 22, -6, -20, 23, -3, -24, + 23, 1, -29, 24, 5, -34, 24, 8, -38, 25, 12, -42, + 26, 16, -46, 26, 19, -50, 27, 23, -54, 28, 26, -58, + 28, 30, -62, 29, 33, -65, 30, 36, -69, 31, 39, -72, + 32, 42, -76, 33, 45, -79, 34, 48, -83, 34, 51, -86, + 35, 54, -89, 36, 57, -92, 37, 59, -95, 38, 62, -98, + 21, -31, 29, 21, -30, 26, 21, -29, 22, 22, -28, 17, + 22, -26, 12, 22, -24, 7, 22, -21, 2, 22, -18, -3, + 23, -15, -8, 23, -12, -13, 23, -9, -18, 24, -5, -22, + 24, -2, -27, 25, 2, -32, 25, 6, -36, 26, 9, -40, + 27, 13, -45, 27, 16, -48, 28, 20, -52, 29, 23, -56, + 30, 27, -60, 30, 30, -64, 31, 33, -67, 32, 36, -71, + 33, 39, -74, 34, 43, -78, 34, 46, -81, 35, 49, -84, + 36, 51, -87, 37, 54, -91, 38, 57, -94, 39, 60, -97, + 23, -32, 31, 23, -32, 27, 23, -31, 24, 23, -29, 19, + 23, -28, 14, 23, -26, 9, 24, -23, 5, 24, -21, -1, + 24, -18, -6, 25, -15, -11, 25, -11, -15, 25, -8, -20, + 26, -4, -25, 26, -1, -29, 27, 3, -34, 27, 6, -38, + 28, 10, -43, 29, 14, -47, 29, 17, -50, 30, 20, -54, + 31, 24, -58, 31, 27, -62, 32, 31, -66, 33, 34, -69, + 34, 37, -73, 34, 40, -76, 35, 43, -80, 36, 46, -83, + 37, 49, -86, 38, 52, -90, 39, 55, -93, 40, 58, -96, + 25, -34, 32, 25, -33, 29, 25, -32, 26, 25, -31, 21, + 25, -29, 16, 25, -27, 12, 25, -25, 7, 25, -23, 1, + 26, -20, -4, 26, -17, -9, 26, -14, -13, 27, -11, -18, + 27, -7, -23, 28, -3, -27, 28, 0, -32, 29, 3, -36, + 29, 7, -41, 30, 11, -45, 30, 14, -48, 31, 18, -52, + 32, 21, -57, 32, 25, -60, 33, 28, -64, 34, 31, -67, + 35, 34, -71, 35, 38, -75, 36, 41, -78, 37, 44, -81, + 38, 47, -85, 39, 50, -88, 40, 53, -91, 40, 55, -94, + 26, -35, 34, 26, -34, 30, 26, -34, 27, 26, -32, 23, + 26, -31, 18, 27, -29, 14, 27, -27, 9, 27, -25, 3, + 27, -22, -2, 27, -19, -6, 28, -16, -11, 28, -13, -16, + 29, -9, -21, 29, -6, -25, 29, -3, -30, 30, 1, -34, + 30, 5, -39, 31, 8, -43, 32, 11, -47, 32, 15, -50, + 33, 19, -55, 34, 22, -58, 34, 25, -62, 35, 28, -66, + 36, 32, -69, 36, 35, -73, 37, 38, -77, 38, 41, -80, + 39, 44, -83, 40, 47, -87, 40, 50, -90, 41, 53, -93, + 28, -36, 35, 28, -36, 32, 28, -35, 29, 28, -34, 24, + 28, -32, 20, 28, -31, 16, 28, -29, 11, 28, -26, 5, + 29, -24, 1, 29, -21, -4, 29, -18, -9, 30, -15, -14, + 30, -12, -19, 30, -9, -23, 31, -5, -27, 31, -2, -32, + 32, 2, -36, 32, 5, -41, 33, 9, -45, 33, 12, -49, + 34, 16, -53, 35, 19, -57, 35, 23, -60, 36, 26, -64, + 37, 29, -68, 37, 33, -71, 38, 36, -75, 39, 39, -78, + 40, 42, -82, 40, 45, -85, 41, 48, -88, 42, 51, -92, + 29, -37, 36, 29, -37, 33, 29, -36, 30, 29, -35, 26, + 29, -34, 22, 30, -32, 17, 30, -31, 13, 30, -28, 7, + 30, -26, 3, 30, -23, -2, 31, -21, -7, 31, -18, -11, + 31, -14, -17, 32, -11, -21, 32, -8, -25, 33, -4, -30, + 33, -1, -34, 34, 3, -39, 34, 6, -43, 35, 9, -47, + 35, 13, -51, 36, 17, -55, 36, 20, -59, 37, 23, -62, + 38, 26, -66, 38, 30, -70, 39, 33, -73, 40, 36, -77, + 41, 39, -80, 41, 42, -84, 42, 45, -87, 43, 48, -90, + 31, -39, 37, 31, -38, 35, 31, -38, 32, 31, -37, 28, + 31, -35, 24, 31, -34, 19, 31, -32, 15, 31, -30, 10, + 32, -28, 5, 32, -25, 0, 32, -23, -5, 32, -20, -9, + 33, -16, -14, 33, -13, -19, 33, -10, -23, 34, -7, -28, + 34, -3, -32, 35, 0, -37, 35, 3, -41, 36, 7, -45, + 36, 11, -49, 37, 14, -53, 38, 17, -57, 38, 20, -60, + 39, 24, -64, 40, 27, -68, 40, 30, -72, 41, 34, -75, + 42, 37, -78, 42, 40, -82, 43, 43, -85, 44, 46, -89, + 32, -40, 39, 32, -39, 36, 32, -39, 33, 32, -38, 30, + 32, -37, 26, 32, -35, 21, 33, -34, 17, 33, -32, 12, + 33, -30, 7, 33, -27, 2, 33, -25, -3, 34, -22, -7, + 34, -19, -12, 34, -16, -17, 35, -13, -21, 35, -9, -26, + 36, -6, -30, 36, -3, -35, 37, 1, -39, 37, 4, -43, + 38, 8, -47, 38, 11, -51, 39, 14, -55, 39, 18, -59, + 40, 21, -62, 41, 25, -66, 41, 28, -70, 42, 31, -73, + 43, 34, -77, 43, 37, -81, 44, 40, -84, 45, 43, -87, + 34, -41, 40, 34, -41, 37, 34, -40, 35, 34, -39, 31, + 34, -38, 27, 34, -37, 23, 34, -35, 19, 34, -33, 14, + 34, -31, 9, 35, -29, 4, 35, -27, 0, 35, -24, -5, + 35, -21, -10, 36, -18, -15, 36, -15, -19, 36, -12, -23, + 37, -8, -28, 37, -5, -32, 38, -2, -37, 38, 2, -41, + 39, 5, -45, 39, 9, -49, 40, 12, -53, 40, 15, -57, + 41, 18, -60, 42, 22, -65, 42, 25, -68, 43, 28, -72, + 44, 31, -75, 44, 35, -79, 45, 38, -82, 46, 41, -86, + 35, -42, 41, 35, -42, 39, 35, -41, 36, 35, -41, 33, + 35, -40, 29, 35, -38, 25, 36, -37, 21, 36, -35, 15, + 36, -33, 11, 36, -31, 6, 36, -29, 2, 37, -26, -3, + 37, -23, -8, 37, -20, -13, 37, -17, -17, 38, -14, -21, + 38, -11, -26, 39, -7, -30, 39, -4, -35, 39, -1, -39, + 40, 3, -43, 41, 6, -47, 41, 9, -51, 42, 12, -55, + 42, 16, -59, 43, 19, -63, 43, 23, -66, 44, 26, -70, + 45, 29, -73, 45, 32, -77, 46, 35, -81, 47, 38, -84, + 37, -44, 42, 37, -43, 40, 37, -43, 38, 37, -42, 34, + 37, -41, 31, 37, -40, 27, 37, -38, 22, 37, -37, 17, + 37, -35, 13, 37, -33, 8, 38, -30, 4, 38, -28, -1, + 38, -25, -6, 38, -22, -11, 39, -19, -15, 39, -16, -19, + 40, -13, -24, 40, -10, -28, 40, -7, -33, 41, -4, -37, + 41, 0, -41, 42, 3, -45, 42, 7, -49, 43, 10, -53, + 43, 13, -57, 44, 17, -61, 44, 20, -65, 45, 23, -68, + 46, 26, -72, 46, 30, -76, 47, 33, -79, 48, 36, -82, + 38, -45, 43, 38, -44, 41, 38, -44, 39, 38, -43, 36, + 38, -42, 32, 38, -41, 28, 38, -40, 24, 39, -38, 19, + 39, -36, 15, 39, -34, 10, 39, -32, 6, 39, -30, 1, + 40, -27, -4, 40, -24, -8, 40, -22, -13, 40, -19, -17, + 41, -15, -22, 41, -12, -26, 42, -9, -31, 42, -6, -35, + 42, -2, -39, 43, 1, -43, 43, 4, -47, 44, 7, -51, + 44, 11, -55, 45, 14, -59, 46, 17, -63, 46, 21, -66, + 47, 24, -70, 47, 27, -74, 48, 30, -77, 49, 33, -81, + 39, -46, 44, 40, -46, 42, 40, -45, 40, 40, -45, 37, + 40, -44, 34, 40, -43, 30, 40, -41, 26, 40, -40, 21, + 40, -38, 17, 40, -36, 12, 40, -34, 8, 41, -32, 3, + 41, -29, -2, 41, -26, -6, 41, -24, -11, 42, -21, -15, + 42, -18, -20, 43, -15, -24, 43, -11, -29, 43, -8, -33, + 44, -5, -37, 44, -2, -41, 45, 2, -45, 45, 5, -49, + 46, 8, -53, 46, 12, -57, 47, 15, -61, 47, 18, -65, + 48, 21, -68, 48, 25, -72, 49, 28, -76, 50, 31, -79, + 41, -47, 46, 41, -47, 44, 41, -47, 42, 41, -46, 39, + 41, -45, 35, 41, -44, 32, 41, -43, 28, 41, -41, 23, + 42, -40, 19, 42, -38, 14, 42, -36, 10, 42, -34, 5, + 42, -31, 0, 43, -28, -4, 43, -26, -9, 43, -23, -13, + 43, -20, -18, 44, -17, -22, 44, -14, -27, 45, -11, -31, + 45, -7, -35, 45, -4, -39, 46, -1, -43, 46, 2, -47, + 47, 5, -51, 47, 9, -55, 48, 12, -59, 48, 15, -63, + 49, 19, -66, 50, 22, -70, 50, 25, -74, 51, 28, -77, + 42, -48, 47, 42, -48, 45, 42, -48, 43, 42, -47, 40, + 43, -46, 37, 43, -45, 33, 43, -44, 29, 43, -43, 25, + 43, -41, 20, 43, -39, 16, 43, -37, 12, 43, -35, 7, + 44, -33, 2, 44, -30, -2, 44, -28, -7, 44, -25, -11, + 45, -22, -16, 45, -19, -20, 45, -16, -25, 46, -13, -29, + 46, -10, -33, 47, -7, -37, 47, -3, -41, 47, 0, -45, + 48, 3, -49, 48, 7, -54, 49, 10, -57, 49, 13, -61, + 50, 16, -65, 51, 20, -69, 51, 23, -72, 52, 26, -76, + 44, -50, 48, 44, -49, 46, 44, -49, 44, 44, -48, 41, + 44, -48, 38, 44, -47, 35, 44, -46, 31, 44, -44, 26, + 44, -43, 22, 44, -41, 18, 45, -39, 13, 45, -37, 9, + 45, -34, 4, 45, -32, 0, 46, -30, -5, 46, -27, -9, + 46, -24, -14, 46, -21, -18, 47, -18, -23, 47, -15, -27, + 47, -12, -31, 48, -9, -35, 48, -6, -39, 49, -3, -43, + 49, 0, -47, 50, 4, -52, 50, 7, -55, 51, 10, -59, + 51, 13, -63, 52, 17, -67, 52, 20, -70, 53, 23, -74, + 45, -51, 49, 45, -51, 47, 45, -50, 46, 45, -50, 43, + 45, -49, 40, 45, -48, 36, 45, -47, 33, 46, -45, 28, + 46, -44, 24, 46, -42, 20, 46, -41, 15, 46, -39, 11, + 46, -36, 6, 47, -34, 2, 47, -32, -3, 47, -29, -7, + 47, -26, -12, 48, -23, -16, 48, -20, -21, 48, -18, -25, + 49, -14, -29, 49, -11, -34, 50, -8, -38, 50, -5, -41, + 50, -2, -45, 51, 2, -50, 51, 5, -54, 52, 8, -57, + 52, 11, -61, 53, 14, -65, 53, 18, -69, 54, 21, -72, + 47, -52, 50, 47, -52, 49, 47, -52, 47, 47, -51, 45, + 47, -50, 42, 47, -50, 38, 47, -49, 35, 47, -47, 30, + 47, -46, 26, 48, -44, 22, 48, -43, 18, 48, -41, 13, + 48, -38, 8, 48, -36, 4, 49, -34, 0, 49, -31, -5, + 49, -29, -10, 49, -26, -14, 50, -23, -18, 50, -20, -22, + 50, -17, -27, 51, -14, -31, 51, -11, -35, 51, -8, -39, + 52, -5, -43, 52, -1, -47, 53, 2, -51, 53, 5, -55, + 54, 8, -59, 54, 11, -63, 55, 14, -66, 55, 18, -70, + 48, -53, 52, 48, -53, 50, 48, -53, 48, 48, -52, 46, + 48, -52, 43, 49, -51, 40, 49, -50, 36, 49, -49, 32, + 49, -47, 28, 49, -46, 24, 49, -44, 19, 49, -42, 15, + 49, -40, 10, 50, -38, 6, 50, -36, 1, 50, -33, -3, + 50, -30, -8, 51, -28, -12, 51, -25, -16, 51, -22, -20, + 52, -19, -25, 52, -16, -29, 52, -13, -33, 53, -10, -37, + 53, -7, -41, 53, -4, -46, 54, -1, -49, 54, 2, -53, + 55, 5, -57, 55, 9, -61, 56, 12, -65, 56, 15, -68, + 50, -55, 53, 50, -54, 51, 50, -54, 50, 50, -53, 47, + 50, -53, 45, 50, -52, 41, 50, -51, 38, 50, -50, 34, + 50, -49, 30, 50, -47, 25, 50, -46, 21, 51, -44, 17, + 51, -42, 12, 51, -40, 8, 51, -37, 3, 51, -35, -1, + 52, -32, -6, 52, -30, -10, 52, -27, -14, 52, -24, -18, + 53, -21, -23, 53, -18, -27, 54, -16, -31, 54, -13, -35, + 54, -10, -39, 55, -6, -44, 55, -3, -47, 56, 0, -51, + 56, 3, -55, 56, 6, -59, 57, 10, -63, 57, 13, -66, + 51, -56, 54, 51, -55, 52, 51, -55, 51, 51, -55, 49, + 51, -54, 46, 51, -53, 43, 51, -52, 39, 51, -51, 35, + 52, -50, 31, 52, -49, 27, 52, -47, 23, 52, -45, 19, + 52, -43, 14, 52, -41, 10, 52, -39, 5, 53, -37, 1, + 53, -34, -4, 53, -32, -8, 53, -29, -12, 54, -26, -17, + 54, -23, -21, 54, -21, -25, 55, -18, -29, 55, -15, -33, + 55, -12, -37, 56, -8, -42, 56, -5, -46, 57, -2, -49, + 57, 1, -53, 58, 4, -57, 58, 7, -61, 59, 10, -65, + 52, -57, 55, 52, -57, 54, 53, -56, 52, 53, -56, 50, + 53, -55, 47, 53, -55, 44, 53, -54, 41, 53, -53, 37, + 53, -51, 33, 53, -50, 29, 53, -49, 25, 53, -47, 21, + 53, -45, 16, 54, -43, 12, 54, -41, 7, 54, -39, 3, + 54, -36, -2, 55, -34, -6, 55, -31, -10, 55, -28, -15, + 55, -25, -19, 56, -23, -23, 56, -20, -27, 56, -17, -31, + 57, -14, -35, 57, -11, -40, 57, -8, -44, 58, -5, -48, + 58, -2, -51, 59, 2, -56, 59, 5, -59, 60, 8, -63, + 54, -58, 56, 54, -58, 55, 54, -57, 53, 54, -57, 51, + 54, -56, 49, 54, -56, 46, 54, -55, 42, 54, -54, 38, + 54, -53, 35, 54, -51, 31, 54, -50, 27, 55, -48, 22, + 55, -46, 18, 55, -45, 13, 55, -43, 9, 55, -40, 5, + 56, -38, 0, 56, -35, -4, 56, -33, -8, 56, -30, -13, + 57, -27, -17, 57, -25, -21, 57, -22, -26, 58, -19, -30, + 58, -16, -34, 58, -13, -38, 59, -10, -42, 59, -7, -46, + 59, -4, -50, 60, -1, -54, 60, 2, -57, 61, 5, -61, + 55, -59, 57, 55, -59, 56, 55, -59, 54, 55, -58, 52, + 55, -58, 50, 55, -57, 47, 55, -56, 44, 55, -55, 40, + 56, -54, 36, 56, -53, 32, 56, -51, 28, 56, -50, 24, + 56, -48, 19, 56, -46, 15, 56, -44, 11, 57, -42, 7, + 57, -40, 2, 57, -37, -2, 57, -35, -7, 58, -32, -11, + 58, -29, -15, 58, -27, -20, 58, -24, -24, 59, -21, -28, + 59, -18, -32, 59, -15, -36, 60, -12, -40, 60, -9, -44, + 61, -6, -48, 61, -3, -52, 62, 0, -56, 62, 3, -59, + 57, -60, 58, 57, -60, 57, 57, -60, 56, 57, -59, 54, + 57, -59, 51, 57, -58, 48, 57, -57, 45, 57, -56, 41, + 57, -55, 38, 57, -54, 34, 57, -53, 30, 57, -51, 26, + 57, -49, 21, 58, -48, 17, 58, -46, 13, 58, -44, 9, + 58, -41, 4, 58, -39, 0, 59, -37, -5, 59, -34, -9, + 59, -31, -14, 59, -29, -18, 60, -26, -22, 60, -23, -26, + 60, -21, -30, 61, -17, -34, 61, -15, -38, 61, -12, -42, + 62, -9, -46, 62, -5, -50, 63, -2, -54, 63, 1, -58, + 58, -61, 59, 58, -61, 58, 58, -61, 57, 58, -61, 55, + 58, -60, 53, 58, -59, 50, 58, -59, 47, 58, -58, 43, + 58, -57, 39, 58, -55, 36, 58, -54, 32, 59, -53, 28, + 59, -51, 23, 59, -49, 19, 59, -47, 15, 59, -45, 10, + 59, -43, 6, 60, -41, 1, 60, -38, -3, 60, -36, -7, + 60, -33, -12, 61, -31, -16, 61, -28, -20, 61, -25, -24, + 62, -23, -28, 62, -20, -32, 62, -17, -36, 63, -14, -40, + 63, -11, -44, 63, -8, -48, 64, -5, -52, 64, -2, -56, + 59, -62, 60, 59, -62, 59, 59, -62, 58, 59, -62, 56, + 59, -61, 54, 59, -61, 51, 59, -60, 48, 60, -59, 44, + 60, -58, 41, 60, -57, 37, 60, -55, 33, 60, -54, 29, + 60, -52, 25, 60, -51, 21, 60, -49, 16, 61, -47, 12, + 61, -45, 7, 61, -43, 3, 61, -40, -1, 61, -38, -5, + 62, -35, -10, 62, -33, -14, 62, -30, -18, 62, -27, -22, + 63, -25, -26, 63, -22, -30, 63, -19, -34, 64, -16, -38, + 64, -13, -42, 65, -10, -46, 65, -7, -50, 65, -4, -54, + 61, -64, 61, 61, -63, 60, 61, -63, 59, 61, -63, 57, + 61, -62, 55, 61, -62, 53, 61, -61, 50, 61, -60, 46, + 61, -59, 42, 61, -58, 39, 61, -57, 35, 61, -55, 31, + 61, -54, 26, 61, -52, 22, 62, -50, 18, 62, -49, 14, + 62, -46, 9, 62, -44, 5, 62, -42, 1, 63, -40, -3, + 63, -37, -8, 63, -35, -12, 63, -32, -16, 64, -29, -20, + 64, -27, -24, 64, -24, -29, 65, -21, -32, 65, -18, -36, + 65, -15, -40, 66, -12, -45, 66, -9, -48, 66, -6, -52, + 62, -65, 62, 62, -65, 61, 62, -64, 60, 62, -64, 58, + 62, -63, 56, 62, -63, 54, 62, -62, 51, 62, -61, 47, + 62, -60, 44, 62, -59, 40, 62, -58, 37, 63, -57, 33, + 63, -55, 28, 63, -54, 24, 63, -52, 20, 63, -50, 16, + 63, -48, 11, 63, -46, 7, 64, -44, 3, 64, -41, -1, + 64, -39, -6, 64, -36, -10, 65, -34, -14, 65, -31, -18, + 65, -29, -22, 66, -26, -27, 66, -23, -31, 66, -20, -35, + 67, -17, -38, 67, -14, -43, 67, -11, -47, 68, -8, -50, + 63, -66, 64, 63, -66, 62, 63, -65, 61, 63, -65, 60, + 63, -65, 58, 63, -64, 55, 63, -63, 52, 63, -63, 49, + 64, -62, 45, 64, -61, 42, 64, -59, 38, 64, -58, 34, + 64, -57, 30, 64, -55, 26, 64, -53, 22, 64, -52, 18, + 65, -49, 13, 65, -47, 9, 65, -45, 5, 65, -43, 0, + 65, -41, -4, 66, -38, -8, 66, -36, -12, 66, -33, -16, + 66, -31, -20, 67, -28, -25, 67, -25, -29, 67, -22, -33, + 68, -20, -37, 68, -16, -41, 68, -14, -45, 69, -11, -48, + 65, -67, 65, 65, -67, 64, 65, -67, 62, 65, -66, 61, + 65, -66, 59, 65, -65, 56, 65, -65, 54, 65, -64, 50, + 65, -63, 47, 65, -62, 43, 65, -61, 40, 65, -59, 36, + 65, -58, 31, 65, -56, 27, 66, -55, 23, 66, -53, 19, + 66, -51, 15, 66, -49, 11, 66, -47, 6, 66, -45, 2, + 67, -42, -2, 67, -40, -6, 67, -38, -10, 67, -35, -15, + 68, -33, -19, 68, -30, -23, 68, -27, -27, 69, -24, -31, + 69, -22, -35, 69, -18, -39, 70, -16, -43, 70, -13, -47, + 66, -68, 66, 66, -68, 65, 66, -68, 64, 66, -67, 62, + 66, -67, 60, 66, -66, 58, 66, -66, 55, 66, -65, 52, + 66, -64, 48, 66, -63, 45, 66, -62, 41, 66, -61, 37, + 67, -59, 33, 67, -58, 29, 67, -56, 25, 67, -55, 21, + 67, -53, 16, 67, -51, 12, 67, -49, 8, 68, -47, 4, + 68, -44, 0, 68, -42, -5, 68, -39, -9, 69, -37, -13, + 69, -35, -17, 69, -32, -21, 69, -29, -25, 70, -26, -29, + 70, -24, -33, 70, -21, -37, 71, -18, -41, 71, -15, -45, + 67, -69, 67, 67, -69, 66, 67, -69, 65, 67, -68, 63, + 67, -68, 61, 67, -68, 59, 67, -67, 56, 67, -66, 53, + 67, -65, 50, 68, -64, 46, 68, -63, 43, 68, -62, 39, + 68, -61, 35, 68, -59, 31, 68, -58, 27, 68, -56, 23, + 68, -54, 18, 69, -52, 14, 69, -50, 10, 69, -48, 6, + 69, -46, 1, 69, -44, -3, 70, -41, -7, 70, -39, -11, + 70, -36, -15, 70, -34, -19, 71, -31, -23, 71, -28, -27, + 71, -26, -31, 72, -23, -35, 72, -20, -39, 72, -17, -43, + 68, -70, 68, 68, -70, 67, 68, -70, 66, 68, -69, 64, + 69, -69, 62, 69, -69, 60, 69, -68, 58, 69, -67, 54, + 69, -66, 51, 69, -66, 48, 69, -65, 44, 69, -63, 41, + 69, -62, 36, 69, -61, 32, 69, -59, 29, 69, -57, 24, + 70, -56, 20, 70, -54, 16, 70, -52, 12, 70, -50, 8, + 70, -47, 3, 71, -45, -1, 71, -43, -5, 71, -41, -9, + 71, -38, -13, 72, -35, -17, 72, -33, -21, 72, -30, -25, + 72, -28, -29, 73, -25, -34, 73, -22, -37, 73, -19, -41, + 70, -71, 69, 70, -71, 68, 70, -71, 67, 70, -71, 65, + 70, -70, 64, 70, -70, 61, 70, -69, 59, 70, -68, 56, + 70, -68, 53, 70, -67, 49, 70, -66, 46, 70, -65, 42, + 70, -63, 38, 70, -62, 34, 71, -60, 30, 71, -59, 26, + 71, -57, 22, 71, -55, 18, 71, -53, 14, 71, -51, 9, + 72, -49, 5, 72, -47, 1, 72, -45, -3, 72, -42, -7, + 73, -40, -11, 73, -37, -16, 73, -35, -20, 73, -32, -24, + 74, -30, -27, 74, -27, -32, 74, -24, -36, 75, -21, -39, + 71, -72, 70, 71, -72, 69, 71, -72, 68, 71, -72, 67, + 71, -71, 65, 71, -71, 63, 71, -70, 60, 71, -70, 57, + 71, -69, 54, 71, -68, 51, 71, -67, 47, 72, -66, 44, + 72, -65, 40, 72, -63, 36, 72, -62, 32, 72, -60, 28, + 72, -58, 23, 72, -57, 19, 72, -55, 15, 73, -53, 11, + 73, -51, 7, 73, -49, 3, 73, -46, -1, 73, -44, -5, + 74, -42, -9, 74, -39, -14, 74, -37, -18, 75, -34, -22, + 75, -32, -26, 75, -29, -30, 75, -26, -34, 76, -23, -38, + 72, -73, 71, 72, -73, 70, 72, -73, 69, 72, -73, 68, + 72, -72, 66, 72, -72, 64, 72, -71, 61, 73, -71, 58, + 73, -70, 55, 73, -69, 52, 73, -68, 49, 73, -67, 45, + 73, -66, 41, 73, -65, 37, 73, -63, 33, 73, -62, 30, + 73, -60, 25, 74, -58, 21, 74, -56, 17, 74, -55, 13, + 74, -52, 8, 74, -50, 4, 74, -48, 0, 75, -46, -4, + 75, -44, -8, 75, -41, -12, 75, -39, -16, 76, -36, -20, + 76, -34, -24, 76, -31, -28, 77, -28, -32, 77, -25, -36, + 74, -74, 72, 74, -74, 71, 74, -74, 70, 74, -74, 69, + 74, -74, 67, 74, -73, 65, 74, -73, 63, 74, -72, 60, + 74, -71, 57, 74, -70, 54, 74, -69, 50, 74, -68, 47, + 74, -67, 43, 74, -66, 39, 74, -64, 35, 75, -63, 31, + 75, -61, 27, 75, -60, 23, 75, -58, 19, 75, -56, 15, + 75, -54, 10, 76, -52, 6, 76, -50, 2, 76, -48, -2, + 76, -45, -6, 76, -43, -10, 77, -40, -14, 77, -38, -18, + 77, -35, -22, 77, -33, -26, 78, -30, -30, 78, -27, -34, + 75, -76, 73, 75, -76, 72, 75, -75, 71, 75, -75, 70, + 75, -75, 69, 75, -74, 67, 75, -74, 64, 75, -73, 61, + 75, -73, 58, 76, -72, 55, 76, -71, 52, 76, -70, 49, + 76, -69, 45, 76, -67, 41, 76, -66, 37, 76, -65, 33, + 76, -63, 29, 76, -61, 25, 77, -60, 21, 77, -58, 17, + 77, -56, 12, 77, -54, 8, 77, -52, 4, 77, -50, 0, + 78, -47, -4, 78, -45, -8, 78, -43, -12, 78, -40, -16, + 79, -38, -20, 79, -35, -24, 79, -32, -28, 80, -30, -32, + 76, -77, 74, 76, -77, 73, 76, -77, 73, 77, -76, 71, + 77, -76, 70, 77, -76, 68, 77, -75, 66, 77, -74, 63, + 77, -74, 60, 77, -73, 57, 77, -72, 54, 77, -71, 50, + 77, -70, 46, 77, -69, 42, 77, -67, 39, 77, -66, 35, + 77, -64, 30, 78, -63, 27, 78, -61, 23, 78, -59, 19, + 78, -57, 14, 78, -55, 10, 78, -53, 6, 79, -51, 2, + 79, -49, -2, 79, -47, -6, 79, -44, -10, 80, -42, -14, + 80, -40, -18, 80, -37, -22, 80, -34, -26, 81, -32, -30, + 78, -78, 75, 78, -78, 74, 78, -78, 74, 78, -77, 72, + 78, -77, 71, 78, -77, 69, 78, -76, 67, 78, -76, 64, + 78, -75, 61, 78, -74, 58, 78, -73, 55, 78, -72, 52, + 78, -71, 48, 78, -70, 44, 78, -69, 40, 79, -67, 36, + 79, -66, 32, 79, -64, 28, 79, -63, 24, 79, -61, 20, + 79, -59, 16, 79, -57, 12, 80, -55, 8, 80, -53, 4, + 80, -51, 0, 80, -48, -4, 81, -46, -8, 81, -44, -12, + 81, -41, -16, 81, -39, -21, 82, -36, -24, 82, -34, -28, + 79, -79, 76, 79, -79, 75, 79, -79, 75, 79, -78, 73, + 79, -78, 72, 79, -78, 70, 79, -77, 68, 79, -77, 65, + 79, -76, 62, 79, -75, 59, 79, -74, 56, 79, -74, 53, + 80, -72, 49, 80, -71, 46, 80, -70, 42, 80, -69, 38, + 80, -67, 34, 80, -66, 30, 80, -64, 26, 80, -62, 22, + 81, -60, 18, 81, -58, 14, 81, -57, 10, 81, -54, 6, + 81, -52, 2, 82, -50, -3, 82, -48, -7, 82, -45, -11, + 82, -43, -14, 82, -40, -19, 83, -38, -23, 83, -36, -26, + 80, -80, 77, 80, -80, 76, 80, -80, 76, 80, -79, 74, + 80, -79, 73, 80, -79, 71, 80, -78, 69, 80, -78, 66, + 80, -77, 64, 81, -76, 61, 81, -76, 58, 81, -75, 54, + 81, -74, 51, 81, -72, 47, 81, -71, 43, 81, -70, 40, + 81, -68, 35, 81, -67, 32, 81, -65, 28, 82, -64, 24, + 82, -62, 19, 82, -60, 15, 82, -58, 11, 82, -56, 7, + 82, -54, 3, 83, -52, -1, 83, -49, -5, 83, -47, -9, + 83, -45, -13, 84, -42, -17, 84, -40, -21, 84, -37, -25, + 82, -81, 78, 82, -81, 78, 82, -81, 77, 82, -81, 76, + 82, -80, 74, 82, -80, 72, 82, -79, 70, 82, -79, 68, + 82, -78, 65, 82, -78, 62, 82, -77, 59, 82, -76, 56, + 82, -75, 52, 82, -74, 49, 82, -73, 45, 82, -71, 41, + 82, -70, 37, 83, -68, 33, 83, -67, 29, 83, -65, 25, + 83, -63, 21, 83, -61, 17, 83, -60, 13, 83, -58, 9, + 84, -56, 5, 84, -53, 1, 84, -51, -3, 84, -49, -7, + 85, -47, -11, 85, -44, -15, 85, -42, -19, 85, -39, -23, + 83, -82, 79, 83, -82, 79, 83, -82, 78, 83, -82, 77, + 83, -81, 75, 83, -81, 74, 83, -81, 71, 83, -80, 69, + 83, -79, 66, 83, -79, 63, 83, -78, 60, 83, -77, 57, + 83, -76, 53, 83, -75, 50, 83, -74, 46, 84, -73, 43, + 84, -71, 39, 84, -70, 35, 84, -68, 31, 84, -67, 27, + 84, -65, 23, 84, -63, 19, 85, -61, 15, 85, -59, 11, + 85, -57, 7, 85, -55, 3, 85, -53, -1, 86, -51, -5, + 86, -48, -9, 86, -46, -13, 86, -43, -17, 86, -41, -21, + 84, -83, 80, 84, -83, 80, 84, -83, 79, 84, -83, 78, + 84, -82, 76, 84, -82, 75, 84, -82, 73, 84, -81, 70, + 84, -80, 68, 84, -80, 65, 84, -79, 62, 84, -78, 59, + 84, -77, 55, 85, -76, 51, 85, -75, 48, 85, -74, 44, + 85, -72, 40, 85, -71, 36, 85, -69, 33, 85, -68, 29, + 85, -66, 24, 86, -64, 20, 86, -63, 16, 86, -61, 13, + 86, -59, 9, 86, -56, 4, 86, -54, 0, 87, -52, -4, + 87, -50, -7, 87, -48, -12, 87, -45, -16, 88, -43, -19, + 85, -84, 81, 85, -84, 81, 85, -84, 80, 85, -84, 79, + 85, -83, 77, 85, -83, 76, 85, -83, 74, 85, -82, 71, + 85, -82, 69, 86, -81, 66, 86, -80, 63, 86, -79, 60, + 86, -78, 56, 86, -77, 53, 86, -76, 49, 86, -75, 46, + 86, -74, 42, 86, -72, 38, 86, -71, 34, 86, -69, 30, + 87, -67, 26, 87, -66, 22, 87, -64, 18, 87, -62, 14, + 87, -60, 10, 87, -58, 6, 88, -56, 2, 88, -54, -2, + 88, -52, -6, 88, -49, -10, 89, -47, -14, 89, -45, -18, + 87, -85, 82, 87, -85, 82, 87, -85, 81, 87, -85, 80, + 87, -84, 78, 87, -84, 77, 87, -84, 75, 87, -83, 73, + 87, -83, 70, 87, -82, 67, 87, -81, 64, 87, -80, 61, + 87, -79, 58, 87, -78, 54, 87, -77, 51, 87, -76, 47, + 87, -75, 43, 87, -74, 39, 88, -72, 36, 88, -71, 32, + 88, -69, 28, 88, -67, 24, 88, -65, 20, 88, -64, 16, + 88, -62, 12, 89, -60, 8, 89, -58, 4, 89, -56, 0, + 89, -53, -4, 89, -51, -8, 90, -49, -12, 90, -46, -16, + 88, -86, 83, 88, -86, 83, 88, -86, 82, 88, -86, 81, + 88, -85, 80, 88, -85, 78, 88, -85, 76, 88, -84, 74, + 88, -84, 71, 88, -83, 69, 88, -82, 66, 88, -82, 63, + 88, -81, 59, 88, -80, 56, 88, -79, 52, 88, -77, 49, + 89, -76, 45, 89, -75, 41, 89, -73, 37, 89, -72, 34, + 89, -70, 29, 89, -69, 25, 89, -67, 22, 89, -65, 18, + 90, -63, 14, 90, -61, 9, 90, -59, 6, 90, -57, 2, + 90, -55, -2, 91, -53, -7, 91, -50, -10, 91, -48, -14, + 0, 2, 1, 1, 3, -2, 1, 4, -6, 1, 7, -11, + 1, 9, -17, 2, 12, -23, 2, 16, -28, 3, 21, -33, + 4, 26, -38, 5, 30, -42, 6, 34, -46, 7, 37, -50, + 9, 40, -53, 10, 42, -56, 11, 44, -59, 12, 46, -62, + 14, 49, -66, 15, 51, -69, 16, 53, -71, 18, 55, -74, + 19, 57, -77, 20, 59, -80, 22, 61, -83, 23, 63, -86, + 24, 65, -89, 25, 67, -92, 26, 69, -94, 28, 71, -97, + 29, 73, -100, 30, 75, -102, 31, 77, -105, 32, 79, -108, + 1, 0, 2, 1, 2, -1, 2, 3, -5, 2, 5, -10, + 2, 7, -16, 3, 11, -22, 3, 15, -27, 4, 20, -32, + 5, 24, -37, 6, 28, -41, 7, 32, -45, 8, 35, -48, + 9, 37, -52, 11, 40, -55, 12, 42, -58, 13, 45, -61, + 14, 47, -65, 16, 49, -68, 17, 52, -71, 18, 54, -74, + 19, 56, -77, 21, 58, -80, 22, 60, -83, 23, 62, -85, + 24, 64, -88, 26, 67, -91, 27, 69, -94, 28, 71, -97, + 29, 73, -99, 30, 75, -102, 31, 77, -105, 33, 79, -107, + 2, -1, 3, 2, 0, 0, 2, 1, -3, 3, 3, -9, + 3, 6, -15, 3, 9, -20, 4, 13, -26, 5, 18, -31, + 6, 22, -35, 7, 26, -40, 8, 30, -43, 9, 32, -47, + 10, 35, -51, 11, 38, -54, 12, 41, -57, 14, 43, -61, + 15, 46, -64, 16, 48, -67, 17, 50, -70, 18, 53, -73, + 20, 55, -76, 21, 57, -79, 22, 59, -82, 23, 61, -85, + 25, 64, -88, 26, 66, -91, 27, 68, -93, 28, 70, -96, + 29, 72, -99, 31, 74, -102, 32, 76, -104, 33, 78, -107, + 3, -3, 4, 3, -2, 1, 3, 0, -2, 3, 2, -8, + 4, 4, -14, 4, 7, -19, 5, 11, -24, 6, 16, -30, + 6, 20, -34, 7, 24, -38, 8, 27, -42, 9, 30, -46, + 11, 33, -50, 12, 36, -53, 13, 39, -56, 14, 41, -60, + 15, 44, -63, 17, 47, -66, 18, 49, -69, 19, 51, -72, + 20, 54, -76, 21, 56, -79, 22, 58, -81, 24, 61, -84, + 25, 63, -87, 26, 65, -90, 27, 67, -93, 28, 69, -96, + 29, 71, -98, 31, 73, -101, 32, 75, -104, 33, 77, -107, + 4, -5, 6, 4, -4, 3, 4, -2, -1, 4, 0, -6, + 5, 2, -12, 5, 5, -18, 6, 9, -23, 7, 14, -28, + 7, 18, -32, 8, 21, -37, 9, 25, -41, 10, 28, -44, + 11, 31, -48, 13, 34, -52, 14, 37, -55, 15, 40, -59, + 16, 43, -62, 17, 45, -65, 18, 48, -69, 19, 50, -72, + 21, 53, -75, 22, 55, -78, 23, 57, -81, 24, 59, -84, + 25, 62, -87, 26, 64, -90, 28, 66, -93, 29, 68, -95, + 30, 70, -98, 31, 73, -101, 32, 75, -104, 33, 77, -106, + 5, -7, 7, 5, -6, 4, 5, -5, 1, 6, -3, -5, + 6, 0, -10, 6, 3, -16, 7, 7, -21, 8, 11, -26, + 9, 15, -31, 9, 18, -35, 10, 22, -39, 11, 25, -43, + 12, 29, -47, 13, 32, -51, 14, 35, -54, 15, 38, -57, + 17, 41, -61, 18, 43, -64, 19, 46, -68, 20, 48, -71, + 21, 51, -74, 22, 54, -77, 23, 56, -80, 24, 58, -83, + 26, 60, -86, 27, 63, -89, 28, 65, -92, 29, 67, -95, + 30, 69, -97, 31, 72, -100, 32, 74, -103, 34, 76, -106, + 6, -10, 9, 7, -9, 6, 7, -8, 3, 7, -6, -3, + 7, -3, -8, 8, 0, -14, 8, 4, -19, 9, 8, -24, + 10, 12, -29, 11, 15, -33, 11, 19, -37, 12, 22, -41, + 13, 26, -45, 14, 29, -49, 15, 32, -53, 16, 35, -56, + 17, 38, -60, 18, 41, -63, 19, 44, -67, 20, 47, -70, + 22, 49, -73, 23, 52, -76, 24, 54, -79, 25, 57, -82, + 26, 59, -85, 27, 62, -88, 28, 64, -91, 29, 66, -94, + 30, 68, -97, 32, 71, -100, 33, 73, -103, 34, 75, -105, + 8, -13, 12, 8, -12, 9, 8, -11, 5, 9, -9, 0, + 9, -6, -6, 9, -3, -11, 10, 1, -16, 10, 5, -22, + 11, 9, -26, 12, 12, -31, 13, 16, -35, 13, 19, -39, + 14, 23, -44, 15, 26, -47, 16, 30, -51, 17, 33, -55, + 18, 36, -59, 19, 39, -62, 20, 42, -65, 21, 45, -69, + 22, 48, -72, 23, 50, -75, 24, 53, -78, 25, 55, -81, + 26, 57, -84, 28, 60, -88, 29, 62, -90, 30, 65, -93, + 31, 67, -96, 32, 69, -99, 33, 72, -102, 34, 74, -105, + 10, -16, 14, 10, -15, 11, 10, -14, 8, 10, -12, 2, + 11, -9, -3, 11, -6, -9, 11, -2, -14, 12, 2, -19, + 12, 5, -24, 13, 9, -29, 14, 13, -33, 15, 16, -37, + 15, 20, -42, 16, 24, -46, 17, 27, -50, 18, 30, -53, + 19, 34, -57, 20, 37, -61, 21, 40, -64, 22, 42, -67, + 23, 45, -71, 24, 48, -74, 25, 51, -77, 26, 53, -80, + 27, 56, -83, 28, 59, -87, 29, 61, -90, 30, 63, -92, + 31, 66, -95, 33, 68, -98, 34, 70, -101, 35, 72, -104, + 11, -19, 17, 11, -18, 13, 12, -16, 10, 12, -14, 4, + 12, -11, -1, 12, -8, -6, 13, -5, -12, 13, -1, -17, + 14, 2, -22, 14, 6, -27, 15, 10, -31, 16, 13, -35, + 17, 17, -40, 17, 21, -44, 18, 24, -48, 19, 27, -52, + 20, 31, -56, 21, 34, -59, 22, 37, -63, 23, 40, -66, + 24, 43, -70, 25, 46, -73, 26, 49, -76, 27, 51, -79, + 28, 54, -82, 29, 57, -86, 30, 59, -89, 31, 62, -92, + 32, 64, -94, 33, 67, -98, 34, 69, -100, 35, 71, -103, + 13, -21, 19, 13, -20, 16, 13, -18, 12, 13, -16, 7, + 14, -14, 1, 14, -11, -4, 14, -8, -9, 15, -4, -15, + 15, -1, -20, 16, 3, -24, 16, 7, -29, 17, 10, -33, + 18, 14, -38, 18, 18, -42, 19, 21, -46, 20, 25, -50, + 21, 28, -54, 22, 32, -58, 23, 35, -61, 24, 38, -65, + 25, 41, -68, 26, 44, -72, 26, 47, -75, 27, 49, -78, + 28, 52, -81, 29, 55, -85, 30, 57, -88, 31, 60, -91, + 32, 62, -93, 34, 65, -97, 35, 67, -100, 36, 70, -102, + 15, -23, 22, 15, -22, 18, 15, -21, 15, 15, -19, 9, + 16, -17, 4, 16, -14, -1, 16, -11, -6, 17, -7, -12, + 17, -4, -17, 18, -1, -22, 18, 3, -26, 19, 7, -31, + 19, 11, -35, 20, 14, -40, 21, 18, -44, 21, 21, -48, + 22, 25, -52, 23, 28, -55, 24, 31, -59, 25, 34, -63, + 26, 38, -67, 27, 41, -70, 27, 44, -73, 28, 46, -76, + 29, 49, -80, 30, 52, -83, 31, 55, -86, 32, 57, -89, + 33, 60, -92, 34, 63, -95, 35, 65, -98, 36, 68, -101, + 17, -25, 24, 17, -24, 20, 17, -23, 17, 17, -21, 11, + 17, -19, 6, 17, -16, 1, 18, -14, -4, 18, -10, -10, + 19, -7, -15, 19, -3, -19, 19, 0, -24, 20, 4, -28, + 21, 8, -33, 21, 11, -38, 22, 15, -42, 23, 18, -46, + 23, 22, -50, 24, 25, -54, 25, 29, -57, 26, 32, -61, + 27, 35, -65, 27, 38, -68, 28, 41, -72, 29, 44, -75, + 30, 47, -78, 31, 50, -82, 32, 53, -85, 33, 55, -88, + 34, 58, -91, 35, 61, -94, 36, 63, -97, 37, 66, -100, + 18, -26, 26, 18, -25, 22, 19, -24, 19, 19, -23, 13, + 19, -21, 8, 19, -18, 3, 19, -16, -2, 20, -13, -7, + 20, -9, -12, 20, -6, -17, 21, -3, -22, 21, 1, -26, + 22, 5, -31, 23, 8, -36, 23, 12, -40, 24, 15, -44, + 25, 19, -48, 25, 23, -52, 26, 26, -56, 27, 29, -59, + 28, 33, -63, 28, 36, -67, 29, 39, -70, 30, 42, -74, + 31, 45, -77, 32, 48, -81, 33, 51, -84, 34, 53, -87, + 35, 56, -90, 36, 59, -93, 37, 61, -96, 38, 64, -99, + 20, -28, 27, 20, -27, 24, 20, -26, 21, 20, -24, 15, + 20, -23, 11, 21, -21, 5, 21, -18, 0, 21, -15, -5, + 21, -12, -10, 22, -9, -15, 22, -5, -20, 23, -2, -24, + 23, 2, -29, 24, 5, -33, 24, 9, -38, 25, 12, -42, + 26, 16, -46, 26, 20, -50, 27, 23, -54, 28, 26, -58, + 29, 30, -62, 29, 33, -65, 30, 36, -69, 31, 39, -72, + 32, 42, -75, 33, 45, -79, 34, 48, -82, 35, 51, -85, + 35, 54, -89, 36, 57, -92, 37, 59, -95, 38, 62, -98, + 22, -29, 29, 22, -28, 26, 22, -28, 22, 22, -26, 17, + 22, -25, 13, 22, -22, 8, 22, -20, 3, 23, -17, -3, + 23, -14, -8, 23, -11, -13, 24, -8, -17, 24, -5, -22, + 25, -1, -27, 25, 3, -31, 26, 6, -36, 26, 10, -40, + 27, 13, -44, 28, 17, -48, 28, 20, -52, 29, 24, -56, + 30, 27, -60, 30, 30, -64, 31, 34, -67, 32, 37, -71, + 33, 40, -74, 34, 43, -78, 34, 46, -81, 35, 49, -84, + 36, 51, -87, 37, 55, -91, 38, 57, -94, 39, 60, -97, + 23, -31, 31, 23, -30, 28, 23, -29, 24, 23, -28, 19, + 23, -26, 15, 24, -24, 10, 24, -22, 5, 24, -19, -1, + 24, -17, -6, 25, -14, -10, 25, -11, -15, 25, -7, -20, + 26, -3, -25, 26, 0, -29, 27, 3, -34, 27, 7, -38, + 28, 11, -42, 29, 14, -46, 29, 17, -50, 30, 21, -54, + 31, 25, -58, 31, 28, -62, 32, 31, -65, 33, 34, -69, + 34, 37, -72, 35, 40, -76, 35, 43, -79, 36, 46, -83, + 37, 49, -86, 38, 52, -89, 39, 55, -93, 40, 58, -96, + 25, -32, 32, 25, -31, 29, 25, -31, 26, 25, -29, 21, + 25, -28, 17, 25, -26, 12, 25, -24, 7, 26, -21, 1, + 26, -19, -3, 26, -16, -8, 26, -13, -13, 27, -10, -18, + 27, -6, -23, 28, -3, -27, 28, 1, -31, 29, 4, -36, + 29, 8, -40, 30, 11, -44, 31, 15, -48, 31, 18, -52, + 32, 22, -56, 33, 25, -60, 33, 28, -64, 34, 31, -67, + 35, 35, -71, 36, 38, -75, 36, 41, -78, 37, 44, -81, + 38, 47, -84, 39, 50, -88, 40, 53, -91, 40, 55, -94, + 26, -34, 34, 26, -33, 31, 26, -32, 27, 26, -31, 23, + 27, -30, 18, 27, -28, 14, 27, -26, 9, 27, -23, 4, + 27, -21, -1, 28, -18, -6, 28, -15, -11, 28, -12, -15, + 29, -9, -21, 29, -5, -25, 30, -2, -29, 30, 1, -34, + 31, 5, -38, 31, 9, -42, 32, 12, -46, 32, 15, -50, + 33, 19, -55, 34, 22, -58, 34, 26, -62, 35, 29, -66, + 36, 32, -69, 37, 35, -73, 37, 38, -76, 38, 41, -80, + 39, 44, -83, 40, 48, -87, 40, 50, -90, 41, 53, -93, + 28, -35, 35, 28, -34, 32, 28, -34, 29, 28, -33, 25, + 28, -31, 20, 28, -30, 16, 28, -28, 11, 29, -25, 6, + 29, -23, 1, 29, -20, -4, 29, -18, -9, 30, -15, -13, + 30, -11, -18, 30, -8, -23, 31, -5, -27, 31, -1, -32, + 32, 2, -36, 32, 6, -40, 33, 9, -44, 33, 13, -48, + 34, 16, -53, 35, 20, -56, 35, 23, -60, 36, 26, -64, + 37, 29, -67, 38, 33, -71, 38, 36, -75, 39, 39, -78, + 40, 42, -81, 41, 45, -85, 41, 48, -88, 42, 51, -92, + 29, -36, 36, 29, -36, 33, 29, -35, 30, 29, -34, 26, + 30, -33, 22, 30, -31, 18, 30, -30, 13, 30, -27, 8, + 30, -25, 3, 30, -22, -2, 31, -20, -7, 31, -17, -11, + 31, -13, -16, 32, -10, -21, 32, -7, -25, 33, -4, -29, + 33, 0, -34, 34, 3, -38, 34, 6, -42, 35, 10, -46, + 35, 14, -51, 36, 17, -55, 37, 20, -58, 37, 23, -62, + 38, 27, -66, 39, 30, -70, 39, 33, -73, 40, 36, -77, + 41, 39, -80, 41, 43, -84, 42, 46, -87, 43, 48, -90, + 31, -38, 38, 31, -37, 35, 31, -36, 32, 31, -36, 28, + 31, -34, 24, 31, -33, 20, 31, -31, 15, 31, -29, 10, + 32, -27, 5, 32, -24, 0, 32, -22, -4, 32, -19, -9, + 33, -16, -14, 33, -13, -19, 34, -10, -23, 34, -6, -27, + 34, -3, -32, 35, 1, -36, 35, 4, -40, 36, 7, -44, + 36, 11, -49, 37, 14, -53, 38, 17, -57, 38, 21, -60, + 39, 24, -64, 40, 27, -68, 40, 31, -71, 41, 34, -75, + 42, 37, -78, 42, 40, -82, 43, 43, -85, 44, 46, -89, + 32, -39, 39, 32, -38, 36, 32, -38, 33, 32, -37, 30, + 32, -36, 26, 33, -35, 21, 33, -33, 17, 33, -31, 12, + 33, -29, 7, 33, -26, 2, 34, -24, -2, 34, -21, -7, + 34, -18, -12, 35, -15, -17, 35, -12, -21, 35, -9, -25, + 36, -5, -30, 36, -2, -34, 37, 1, -38, 37, 5, -42, + 38, 8, -47, 38, 12, -51, 39, 15, -55, 39, 18, -58, + 40, 21, -62, 41, 25, -66, 41, 28, -70, 42, 31, -73, + 43, 34, -77, 43, 38, -80, 44, 41, -84, 45, 44, -87, + 34, -40, 40, 34, -40, 37, 34, -39, 35, 34, -38, 31, + 34, -37, 27, 34, -36, 23, 34, -35, 19, 34, -33, 14, + 35, -31, 9, 35, -28, 4, 35, -26, 0, 35, -23, -5, + 36, -20, -10, 36, -17, -15, 36, -14, -19, 37, -11, -23, + 37, -8, -28, 37, -5, -32, 38, -1, -36, 38, 2, -41, + 39, 6, -45, 39, 9, -49, 40, 12, -53, 40, 15, -57, + 41, 19, -60, 42, 22, -64, 42, 25, -68, 43, 29, -72, + 44, 32, -75, 44, 35, -79, 45, 38, -82, 46, 41, -86, + 35, -42, 41, 35, -41, 39, 35, -41, 36, 35, -40, 33, + 35, -39, 29, 36, -38, 25, 36, -36, 21, 36, -34, 16, + 36, -32, 11, 36, -30, 6, 36, -28, 2, 37, -25, -3, + 37, -22, -8, 37, -20, -12, 38, -17, -17, 38, -14, -21, + 38, -10, -26, 39, -7, -30, 39, -4, -34, 40, -1, -39, + 40, 3, -43, 41, 6, -47, 41, 10, -51, 42, 13, -55, + 42, 16, -58, 43, 20, -63, 43, 23, -66, 44, 26, -70, + 45, 29, -73, 45, 32, -77, 46, 36, -81, 47, 39, -84, + 37, -43, 42, 37, -42, 40, 37, -42, 38, 37, -41, 34, + 37, -40, 31, 37, -39, 27, 37, -38, 22, 37, -36, 18, + 37, -34, 13, 38, -32, 8, 38, -30, 4, 38, -27, -1, + 38, -24, -6, 39, -22, -10, 39, -19, -15, 39, -16, -19, + 40, -13, -24, 40, -9, -28, 40, -6, -32, 41, -3, -37, + 41, 0, -41, 42, 4, -45, 42, 7, -49, 43, 10, -53, + 43, 13, -57, 44, 17, -61, 45, 20, -64, 45, 23, -68, + 46, 26, -72, 46, 30, -75, 47, 33, -79, 48, 36, -82, + 38, -44, 43, 38, -44, 41, 38, -43, 39, 38, -42, 36, + 38, -42, 32, 38, -40, 28, 39, -39, 24, 39, -37, 19, + 39, -36, 15, 39, -34, 10, 39, -32, 6, 39, -29, 1, + 40, -26, -4, 40, -24, -8, 40, -21, -13, 41, -18, -17, + 41, -15, -22, 41, -12, -26, 42, -9, -30, 42, -6, -35, + 43, -2, -39, 43, 1, -43, 43, 4, -47, 44, 8, -51, + 44, 11, -55, 45, 14, -59, 46, 18, -63, 46, 21, -66, + 47, 24, -70, 47, 27, -74, 48, 30, -77, 49, 33, -81, + 40, -45, 45, 40, -45, 43, 40, -44, 40, 40, -44, 37, + 40, -43, 34, 40, -42, 30, 40, -41, 26, 40, -39, 21, + 40, -37, 17, 40, -35, 12, 41, -33, 8, 41, -31, 3, + 41, -28, -2, 41, -26, -6, 42, -23, -11, 42, -20, -15, + 42, -17, -20, 43, -14, -24, 43, -11, -28, 43, -8, -33, + 44, -4, -37, 44, -1, -41, 45, 2, -45, 45, 5, -49, + 46, 8, -53, 46, 12, -57, 47, 15, -61, 47, 18, -64, + 48, 21, -68, 49, 25, -72, 49, 28, -76, 50, 31, -79, + 41, -47, 46, 41, -46, 44, 41, -46, 42, 41, -45, 39, + 41, -44, 35, 41, -43, 32, 41, -42, 28, 41, -41, 23, + 42, -39, 19, 42, -37, 14, 42, -35, 10, 42, -33, 5, + 42, -30, 0, 43, -28, -4, 43, -25, -9, 43, -22, -13, + 44, -19, -18, 44, -16, -22, 44, -13, -26, 45, -10, -31, + 45, -7, -35, 45, -4, -39, 46, -1, -43, 46, 3, -47, + 47, 6, -51, 47, 9, -55, 48, 12, -59, 48, 16, -63, + 49, 19, -66, 50, 22, -70, 50, 25, -74, 51, 28, -77, + 42, -48, 47, 42, -47, 45, 42, -47, 43, 43, -46, 40, + 43, -46, 37, 43, -45, 33, 43, -44, 29, 43, -42, 25, + 43, -40, 20, 43, -39, 16, 43, -37, 12, 44, -35, 7, + 44, -32, 2, 44, -30, -2, 44, -27, -7, 45, -25, -11, + 45, -21, -16, 45, -19, -20, 46, -16, -24, 46, -13, -29, + 46, -9, -33, 47, -6, -37, 47, -3, -41, 48, 0, -45, + 48, 3, -49, 49, 7, -53, 49, 10, -57, 50, 13, -61, + 50, 16, -65, 51, 20, -69, 51, 23, -72, 52, 26, -76, + 44, -49, 48, 44, -49, 46, 44, -48, 44, 44, -48, 42, + 44, -47, 38, 44, -46, 35, 44, -45, 31, 44, -43, 27, + 44, -42, 22, 45, -40, 18, 45, -38, 14, 45, -36, 9, + 45, -34, 4, 45, -32, 0, 46, -29, -5, 46, -27, -9, + 46, -24, -14, 46, -21, -18, 47, -18, -22, 47, -15, -27, + 48, -12, -31, 48, -9, -35, 48, -5, -39, 49, -2, -43, + 49, 1, -47, 50, 4, -52, 50, 7, -55, 51, 11, -59, + 51, 14, -63, 52, 17, -67, 52, 20, -70, 53, 23, -74, + 45, -50, 49, 45, -50, 47, 45, -49, 46, 45, -49, 43, + 45, -48, 40, 45, -47, 37, 46, -46, 33, 46, -45, 28, + 46, -43, 24, 46, -42, 20, 46, -40, 15, 46, -38, 11, + 46, -36, 6, 47, -33, 2, 47, -31, -3, 47, -29, -7, + 47, -26, -12, 48, -23, -16, 48, -20, -21, 48, -17, -25, + 49, -14, -29, 49, -11, -33, 50, -8, -37, 50, -5, -41, + 50, -2, -45, 51, 2, -50, 51, 5, -53, 52, 8, -57, + 52, 11, -61, 53, 15, -65, 53, 18, -69, 54, 21, -72, + 47, -52, 51, 47, -51, 49, 47, -51, 47, 47, -50, 45, + 47, -50, 42, 47, -49, 38, 47, -48, 35, 47, -47, 30, + 48, -45, 26, 48, -44, 22, 48, -42, 18, 48, -40, 13, + 48, -38, 8, 48, -36, 4, 49, -33, 0, 49, -31, -5, + 49, -28, -10, 49, -25, -14, 50, -23, -18, 50, -20, -22, + 50, -17, -27, 51, -14, -31, 51, -11, -35, 51, -8, -39, + 52, -5, -43, 52, -1, -47, 53, 2, -51, 53, 5, -55, + 54, 8, -59, 54, 12, -63, 55, 15, -66, 55, 18, -70, + 48, -53, 52, 48, -53, 50, 48, -52, 48, 48, -52, 46, + 49, -51, 43, 49, -50, 40, 49, -49, 36, 49, -48, 32, + 49, -47, 28, 49, -45, 24, 49, -44, 20, 49, -42, 15, + 49, -40, 10, 50, -37, 6, 50, -35, 2, 50, -33, -3, + 50, -30, -8, 51, -27, -12, 51, -25, -16, 51, -22, -20, + 52, -19, -25, 52, -16, -29, 52, -13, -33, 53, -10, -37, + 53, -7, -41, 54, -4, -45, 54, -1, -49, 54, 3, -53, + 55, 6, -57, 55, 9, -61, 56, 12, -65, 56, 15, -68, + 50, -54, 53, 50, -54, 51, 50, -53, 50, 50, -53, 47, + 50, -52, 45, 50, -52, 41, 50, -51, 38, 50, -49, 34, + 50, -48, 30, 50, -47, 26, 50, -45, 21, 51, -43, 17, + 51, -41, 12, 51, -39, 8, 51, -37, 4, 51, -35, -1, + 52, -32, -6, 52, -29, -10, 52, -27, -14, 53, -24, -18, + 53, -21, -23, 53, -18, -27, 54, -15, -31, 54, -12, -35, + 54, -9, -39, 55, -6, -44, 55, -3, -47, 56, 0, -51, + 56, 3, -55, 57, 7, -59, 57, 10, -63, 58, 13, -66, + 51, -55, 54, 51, -55, 52, 51, -55, 51, 51, -54, 49, + 51, -54, 46, 51, -53, 43, 51, -52, 39, 51, -51, 35, + 52, -50, 31, 52, -48, 27, 52, -47, 23, 52, -45, 19, + 52, -43, 14, 52, -41, 10, 53, -39, 5, 53, -36, 1, + 53, -34, -4, 53, -31, -8, 54, -29, -12, 54, -26, -16, + 54, -23, -21, 54, -20, -25, 55, -17, -29, 55, -15, -33, + 56, -12, -37, 56, -8, -42, 56, -5, -46, 57, -2, -49, + 57, 1, -53, 58, 4, -57, 58, 7, -61, 59, 10, -65, + 53, -56, 55, 53, -56, 54, 53, -56, 52, 53, -55, 50, + 53, -55, 47, 53, -54, 44, 53, -53, 41, 53, -52, 37, + 53, -51, 33, 53, -50, 29, 53, -48, 25, 53, -46, 21, + 54, -44, 16, 54, -42, 12, 54, -40, 7, 54, -38, 3, + 54, -36, -2, 55, -33, -6, 55, -31, -10, 55, -28, -14, + 55, -25, -19, 56, -22, -23, 56, -20, -27, 56, -17, -31, + 57, -14, -35, 57, -11, -40, 58, -8, -44, 58, -5, -47, + 58, -2, -51, 59, 2, -55, 59, 5, -59, 60, 8, -63, + 54, -58, 56, 54, -57, 55, 54, -57, 53, 54, -57, 51, + 54, -56, 49, 54, -55, 46, 54, -54, 42, 54, -53, 38, + 54, -52, 35, 54, -51, 31, 55, -50, 27, 55, -48, 22, + 55, -46, 18, 55, -44, 13, 55, -42, 9, 55, -40, 5, + 56, -37, 0, 56, -35, -4, 56, -33, -8, 56, -30, -13, + 57, -27, -17, 57, -24, -21, 57, -22, -25, 58, -19, -29, + 58, -16, -33, 58, -13, -38, 59, -10, -42, 59, -7, -46, + 60, -4, -49, 60, 0, -54, 60, 3, -57, 61, 6, -61, + 55, -59, 57, 55, -58, 56, 55, -58, 55, 55, -58, 52, + 55, -57, 50, 55, -57, 47, 55, -56, 44, 56, -55, 40, + 56, -54, 36, 56, -52, 32, 56, -51, 28, 56, -49, 24, + 56, -48, 19, 56, -46, 15, 56, -44, 11, 57, -42, 7, + 57, -39, 2, 57, -37, -2, 57, -35, -6, 58, -32, -11, + 58, -29, -15, 58, -26, -19, 59, -24, -24, 59, -21, -28, + 59, -18, -32, 60, -15, -36, 60, -12, -40, 60, -9, -44, + 61, -6, -48, 61, -3, -52, 62, 0, -56, 62, 3, -59, + 57, -60, 58, 57, -60, 57, 57, -59, 56, 57, -59, 54, + 57, -58, 51, 57, -58, 49, 57, -57, 45, 57, -56, 42, + 57, -55, 38, 57, -54, 34, 57, -52, 30, 57, -51, 26, + 57, -49, 21, 58, -47, 17, 58, -45, 13, 58, -43, 9, + 58, -41, 4, 58, -39, 0, 59, -36, -5, 59, -34, -9, + 59, -31, -13, 59, -28, -18, 60, -26, -22, 60, -23, -26, + 60, -20, -30, 61, -17, -34, 61, -14, -38, 61, -11, -42, + 62, -8, -46, 62, -5, -50, 63, -2, -54, 63, 1, -57, + 58, -61, 59, 58, -61, 58, 58, -60, 57, 58, -60, 55, + 58, -60, 53, 58, -59, 50, 58, -58, 47, 58, -57, 43, + 58, -56, 39, 58, -55, 36, 59, -54, 32, 59, -52, 28, + 59, -51, 23, 59, -49, 19, 59, -47, 15, 59, -45, 10, + 59, -43, 6, 60, -40, 1, 60, -38, -3, 60, -36, -7, + 60, -33, -12, 61, -30, -16, 61, -28, -20, 61, -25, -24, + 62, -22, -28, 62, -19, -32, 62, -16, -36, 63, -14, -40, + 63, -11, -44, 63, -7, -48, 64, -4, -52, 64, -1, -56, + 59, -62, 60, 59, -62, 59, 59, -62, 58, 59, -61, 56, + 59, -61, 54, 59, -60, 51, 59, -59, 48, 60, -58, 45, + 60, -57, 41, 60, -56, 37, 60, -55, 33, 60, -54, 29, + 60, -52, 25, 60, -50, 21, 60, -49, 16, 61, -47, 12, + 61, -44, 8, 61, -42, 3, 61, -40, -1, 61, -38, -5, + 62, -35, -10, 62, -32, -14, 62, -30, -18, 63, -27, -22, + 63, -24, -26, 63, -21, -30, 63, -19, -34, 64, -16, -38, + 64, -13, -42, 65, -10, -46, 65, -7, -50, 65, -4, -54, + 61, -63, 61, 61, -63, 60, 61, -63, 59, 61, -62, 57, + 61, -62, 55, 61, -61, 53, 61, -61, 50, 61, -60, 46, + 61, -59, 43, 61, -58, 39, 61, -56, 35, 61, -55, 31, + 61, -53, 26, 62, -52, 22, 62, -50, 18, 62, -48, 14, + 62, -46, 9, 62, -44, 5, 62, -42, 1, 63, -39, -3, + 63, -37, -8, 63, -34, -12, 63, -32, -16, 64, -29, -20, + 64, -26, -24, 64, -23, -28, 65, -21, -32, 65, -18, -36, + 65, -15, -40, 66, -12, -44, 66, -9, -48, 67, -6, -52, + 62, -64, 63, 62, -64, 61, 62, -64, 60, 62, -64, 58, + 62, -63, 56, 62, -63, 54, 62, -62, 51, 62, -61, 47, + 62, -60, 44, 62, -59, 40, 62, -58, 37, 63, -56, 33, + 63, -55, 28, 63, -53, 24, 63, -52, 20, 63, -50, 16, + 63, -48, 11, 64, -46, 7, 64, -43, 3, 64, -41, -1, + 64, -39, -6, 64, -36, -10, 65, -34, -14, 65, -31, -18, + 65, -28, -22, 66, -25, -27, 66, -23, -31, 66, -20, -34, + 67, -17, -38, 67, -14, -43, 67, -11, -46, 68, -8, -50, + 63, -65, 64, 63, -65, 63, 63, -65, 61, 63, -65, 60, + 63, -64, 58, 63, -64, 55, 63, -63, 52, 64, -62, 49, + 64, -61, 46, 64, -60, 42, 64, -59, 38, 64, -58, 34, + 64, -56, 30, 64, -55, 26, 64, -53, 22, 64, -51, 18, + 65, -49, 13, 65, -47, 9, 65, -45, 5, 65, -43, 1, + 65, -40, -4, 66, -38, -8, 66, -36, -12, 66, -33, -16, + 66, -30, -20, 67, -27, -25, 67, -25, -29, 67, -22, -33, + 68, -19, -37, 68, -16, -41, 68, -13, -45, 69, -10, -48, + 65, -67, 65, 65, -66, 64, 65, -66, 63, 65, -66, 61, + 65, -65, 59, 65, -65, 56, 65, -64, 54, 65, -63, 50, + 65, -63, 47, 65, -62, 43, 65, -60, 40, 65, -59, 36, + 65, -58, 32, 65, -56, 28, 66, -54, 23, 66, -53, 19, + 66, -51, 15, 66, -49, 11, 66, -47, 6, 66, -45, 2, + 67, -42, -2, 67, -40, -6, 67, -37, -10, 67, -35, -14, + 68, -32, -18, 68, -29, -23, 68, -27, -27, 69, -24, -31, + 69, -21, -35, 69, -18, -39, 70, -15, -43, 70, -13, -47, + 66, -68, 66, 66, -67, 65, 66, -67, 64, 66, -67, 62, + 66, -67, 60, 66, -66, 58, 66, -65, 55, 66, -65, 52, + 66, -64, 48, 66, -63, 45, 66, -62, 41, 66, -60, 38, + 67, -59, 33, 67, -58, 29, 67, -56, 25, 67, -54, 21, + 67, -52, 17, 67, -50, 12, 68, -48, 8, 68, -46, 4, + 68, -44, 0, 68, -42, -5, 68, -39, -9, 69, -37, -13, + 69, -34, -17, 69, -31, -21, 69, -29, -25, 70, -26, -29, + 70, -23, -33, 70, -20, -37, 71, -18, -41, 71, -15, -45, + 67, -69, 67, 67, -69, 66, 67, -68, 65, 67, -68, 63, + 67, -68, 61, 67, -67, 59, 67, -67, 56, 67, -66, 53, + 67, -65, 50, 68, -64, 46, 68, -63, 43, 68, -62, 39, + 68, -60, 35, 68, -59, 31, 68, -57, 27, 68, -56, 23, + 68, -54, 18, 69, -52, 14, 69, -50, 10, 69, -48, 6, + 69, -45, 1, 69, -43, -3, 70, -41, -7, 70, -39, -11, + 70, -36, -15, 70, -33, -19, 71, -31, -23, 71, -28, -27, + 71, -25, -31, 72, -22, -35, 72, -20, -39, 72, -17, -43, + 68, -70, 68, 68, -70, 67, 69, -69, 66, 69, -69, 64, + 69, -69, 62, 69, -68, 60, 69, -68, 58, 69, -67, 54, + 69, -66, 51, 69, -65, 48, 69, -64, 44, 69, -63, 41, + 69, -62, 36, 69, -60, 33, 69, -59, 29, 70, -57, 25, + 70, -55, 20, 70, -53, 16, 70, -52, 12, 70, -50, 8, + 70, -47, 3, 71, -45, -1, 71, -43, -5, 71, -40, -9, + 71, -38, -13, 72, -35, -17, 72, -33, -21, 72, -30, -25, + 72, -27, -29, 73, -24, -34, 73, -22, -37, 73, -19, -41, + 70, -71, 69, 70, -71, 68, 70, -71, 67, 70, -70, 65, + 70, -70, 64, 70, -69, 61, 70, -69, 59, 70, -68, 56, + 70, -67, 53, 70, -66, 49, 70, -65, 46, 70, -64, 42, + 70, -63, 38, 71, -62, 34, 71, -60, 30, 71, -59, 26, + 71, -57, 22, 71, -55, 18, 71, -53, 14, 71, -51, 10, + 72, -49, 5, 72, -47, 1, 72, -44, -3, 72, -42, -7, + 73, -40, -11, 73, -37, -16, 73, -35, -20, 73, -32, -23, + 74, -29, -27, 74, -27, -32, 74, -24, -36, 75, -21, -39, + 71, -72, 70, 71, -72, 69, 71, -72, 68, 71, -71, 67, + 71, -71, 65, 71, -71, 63, 71, -70, 60, 71, -69, 57, + 71, -69, 54, 71, -68, 51, 71, -67, 47, 72, -66, 44, + 72, -64, 40, 72, -63, 36, 72, -62, 32, 72, -60, 28, + 72, -58, 23, 72, -56, 19, 73, -55, 15, 73, -53, 11, + 73, -50, 7, 73, -48, 3, 73, -46, -1, 74, -44, -5, + 74, -42, -9, 74, -39, -14, 74, -36, -18, 75, -34, -22, + 75, -31, -26, 75, -28, -30, 75, -26, -34, 76, -23, -38, + 72, -73, 71, 72, -73, 70, 72, -73, 69, 72, -72, 68, + 72, -72, 66, 72, -72, 64, 73, -71, 62, 73, -70, 58, + 73, -70, 55, 73, -69, 52, 73, -68, 49, 73, -67, 45, + 73, -66, 41, 73, -64, 37, 73, -63, 34, 73, -61, 30, + 73, -60, 25, 74, -58, 21, 74, -56, 17, 74, -54, 13, + 74, -52, 9, 74, -50, 4, 75, -48, 0, 75, -46, -4, + 75, -43, -8, 75, -41, -12, 75, -38, -16, 76, -36, -20, + 76, -33, -24, 76, -30, -28, 77, -28, -32, 77, -25, -36, + 74, -74, 72, 74, -74, 71, 74, -74, 70, 74, -74, 69, + 74, -73, 67, 74, -73, 65, 74, -72, 63, 74, -72, 60, + 74, -71, 57, 74, -70, 54, 74, -69, 50, 74, -68, 47, + 74, -67, 43, 74, -66, 39, 74, -64, 35, 75, -63, 31, + 75, -61, 27, 75, -59, 23, 75, -58, 19, 75, -56, 15, + 75, -54, 10, 76, -52, 6, 76, -50, 2, 76, -47, -2, + 76, -45, -6, 76, -43, -10, 77, -40, -14, 77, -38, -18, + 77, -35, -22, 78, -32, -26, 78, -30, -30, 78, -27, -34, + 75, -75, 73, 75, -75, 72, 75, -75, 72, 75, -75, 70, + 75, -75, 69, 75, -74, 67, 75, -74, 64, 75, -73, 61, + 75, -72, 59, 76, -72, 55, 76, -71, 52, 76, -70, 49, + 76, -68, 45, 76, -67, 41, 76, -66, 37, 76, -64, 33, + 76, -63, 29, 76, -61, 25, 77, -59, 21, 77, -58, 17, + 77, -56, 12, 77, -54, 8, 77, -52, 4, 77, -49, 0, + 78, -47, -4, 78, -45, -8, 78, -42, -12, 78, -40, -16, + 79, -38, -20, 79, -35, -24, 79, -32, -28, 80, -30, -32, + 77, -77, 74, 77, -76, 73, 77, -76, 73, 77, -76, 71, + 77, -76, 70, 77, -75, 68, 77, -75, 66, 77, -74, 63, + 77, -73, 60, 77, -73, 57, 77, -72, 54, 77, -71, 50, + 77, -70, 46, 77, -68, 42, 77, -67, 39, 77, -66, 35, + 78, -64, 31, 78, -63, 27, 78, -61, 23, 78, -59, 19, + 78, -57, 14, 78, -55, 10, 78, -53, 6, 79, -51, 2, + 79, -49, -2, 79, -46, -6, 79, -44, -10, 80, -42, -14, + 80, -39, -18, 80, -37, -22, 80, -34, -26, 81, -32, -30, + 78, -78, 75, 78, -77, 74, 78, -77, 74, 78, -77, 72, + 78, -77, 71, 78, -76, 69, 78, -76, 67, 78, -75, 64, + 78, -75, 61, 78, -74, 58, 78, -73, 55, 78, -72, 52, + 78, -71, 48, 78, -70, 44, 78, -68, 40, 79, -67, 37, + 79, -66, 32, 79, -64, 28, 79, -62, 24, 79, -61, 20, + 79, -59, 16, 80, -57, 12, 80, -55, 8, 80, -53, 4, + 80, -51, 0, 80, -48, -4, 81, -46, -8, 81, -44, -12, + 81, -41, -16, 81, -38, -20, 82, -36, -24, 82, -33, -28, + 79, -79, 76, 79, -79, 76, 79, -78, 75, 79, -78, 73, + 79, -78, 72, 79, -77, 70, 79, -77, 68, 79, -76, 65, + 79, -76, 62, 79, -75, 60, 79, -74, 56, 79, -73, 53, + 80, -72, 49, 80, -71, 46, 80, -70, 42, 80, -68, 38, + 80, -67, 34, 80, -65, 30, 80, -64, 26, 80, -62, 22, + 81, -60, 18, 81, -58, 14, 81, -56, 10, 81, -54, 6, + 81, -52, 2, 82, -50, -3, 82, -48, -7, 82, -45, -10, + 82, -43, -14, 82, -40, -19, 83, -38, -23, 83, -35, -26, + 80, -80, 77, 80, -80, 77, 80, -79, 76, 80, -79, 75, + 80, -79, 73, 80, -79, 71, 80, -78, 69, 80, -77, 66, + 81, -77, 64, 81, -76, 61, 81, -75, 58, 81, -74, 54, + 81, -73, 51, 81, -72, 47, 81, -71, 43, 81, -70, 40, + 81, -68, 35, 81, -67, 32, 81, -65, 28, 82, -64, 24, + 82, -62, 19, 82, -60, 15, 82, -58, 11, 82, -56, 7, + 83, -54, 4, 83, -51, -1, 83, -49, -5, 83, -47, -9, + 83, -45, -13, 84, -42, -17, 84, -40, -21, 84, -37, -25, + 82, -81, 78, 82, -81, 78, 82, -80, 77, 82, -80, 76, + 82, -80, 74, 82, -80, 72, 82, -79, 70, 82, -79, 68, + 82, -78, 65, 82, -77, 62, 82, -77, 59, 82, -76, 56, + 82, -75, 52, 82, -73, 49, 82, -72, 45, 82, -71, 41, + 82, -70, 37, 83, -68, 33, 83, -67, 29, 83, -65, 25, + 83, -63, 21, 83, -61, 17, 83, -59, 13, 84, -57, 9, + 84, -55, 5, 84, -53, 1, 84, -51, -3, 84, -49, -7, + 85, -46, -11, 85, -44, -15, 85, -41, -19, 85, -39, -23, + 83, -82, 79, 83, -82, 79, 83, -82, 78, 83, -81, 77, + 83, -81, 75, 83, -81, 74, 83, -80, 72, 83, -80, 69, + 83, -79, 66, 83, -78, 64, 83, -78, 61, 83, -77, 57, + 83, -76, 54, 83, -75, 50, 83, -74, 46, 84, -72, 43, + 84, -71, 39, 84, -69, 35, 84, -68, 31, 84, -66, 27, + 84, -64, 23, 84, -63, 19, 85, -61, 15, 85, -59, 11, + 85, -57, 7, 85, -55, 3, 85, -53, -1, 86, -50, -5, + 86, -48, -9, 86, -46, -13, 86, -43, -17, 87, -41, -21, + 84, -83, 80, 84, -83, 80, 84, -83, 79, 84, -82, 78, + 84, -82, 76, 84, -82, 75, 84, -81, 73, 84, -81, 70, + 84, -80, 68, 84, -80, 65, 84, -79, 62, 84, -78, 59, + 85, -77, 55, 85, -76, 52, 85, -75, 48, 85, -74, 44, + 85, -72, 40, 85, -71, 36, 85, -69, 33, 85, -68, 29, + 85, -66, 24, 86, -64, 20, 86, -62, 17, 86, -60, 13, + 86, -59, 9, 86, -56, 4, 87, -54, 0, 87, -52, -3, + 87, -50, -7, 87, -47, -12, 87, -45, -16, 88, -43, -19, + 85, -84, 81, 85, -84, 81, 85, -84, 80, 85, -83, 79, + 85, -83, 77, 85, -83, 76, 85, -82, 74, 85, -82, 71, + 85, -81, 69, 86, -81, 66, 86, -80, 63, 86, -79, 60, + 86, -78, 56, 86, -77, 53, 86, -76, 49, 86, -75, 46, + 86, -73, 42, 86, -72, 38, 86, -71, 34, 86, -69, 30, + 87, -67, 26, 87, -66, 22, 87, -64, 18, 87, -62, 14, + 87, -60, 10, 87, -58, 6, 88, -56, 2, 88, -54, -2, + 88, -52, -6, 88, -49, -10, 89, -47, -14, 89, -44, -18, + 87, -85, 82, 87, -85, 82, 87, -85, 81, 87, -84, 80, + 87, -84, 79, 87, -84, 77, 87, -83, 75, 87, -83, 73, + 87, -82, 70, 87, -82, 67, 87, -81, 65, 87, -80, 61, + 87, -79, 58, 87, -78, 54, 87, -77, 51, 87, -76, 47, + 87, -75, 43, 87, -73, 40, 88, -72, 36, 88, -70, 32, + 88, -69, 28, 88, -67, 24, 88, -65, 20, 88, -63, 16, + 88, -62, 12, 89, -59, 8, 89, -57, 4, 89, -55, 0, + 89, -53, -4, 90, -51, -8, 90, -48, -12, 90, -46, -16, + 88, -86, 83, 88, -86, 83, 88, -86, 82, 88, -86, 81, + 88, -85, 80, 88, -85, 78, 88, -85, 76, 88, -84, 74, + 88, -84, 71, 88, -83, 69, 88, -82, 66, 88, -81, 63, + 88, -80, 59, 88, -79, 56, 88, -78, 52, 88, -77, 49, + 89, -76, 45, 89, -75, 41, 89, -73, 37, 89, -72, 34, + 89, -70, 29, 89, -68, 25, 89, -67, 22, 90, -65, 18, + 90, -63, 14, 90, -61, 9, 90, -59, 6, 90, -57, 2, + 90, -55, -2, 91, -52, -6, 91, -50, -10, 91, -48, -14, + 1, 4, 2, 1, 6, -1, 1, 7, -5, 2, 9, -11, + 2, 11, -16, 2, 15, -22, 3, 19, -27, 4, 24, -33, + 5, 28, -37, 5, 31, -41, 6, 35, -45, 8, 37, -49, + 9, 40, -52, 10, 42, -55, 12, 44, -59, 13, 46, -62, + 14, 49, -65, 16, 51, -68, 17, 53, -71, 18, 55, -74, + 19, 57, -77, 21, 59, -80, 22, 61, -83, 23, 63, -85, + 24, 65, -88, 25, 68, -91, 27, 69, -94, 28, 71, -97, + 29, 73, -99, 30, 75, -102, 31, 77, -105, 33, 79, -107, + 2, 3, 3, 2, 4, 0, 2, 5, -4, 2, 7, -9, + 3, 10, -15, 3, 13, -21, 4, 17, -26, 5, 22, -31, + 5, 26, -36, 6, 29, -40, 7, 32, -44, 8, 35, -47, + 10, 38, -51, 11, 40, -54, 12, 42, -58, 13, 45, -61, + 15, 47, -64, 16, 49, -67, 17, 52, -70, 18, 54, -73, + 20, 56, -76, 21, 58, -79, 22, 60, -82, 23, 62, -85, + 24, 64, -88, 26, 67, -91, 27, 69, -94, 28, 71, -96, + 29, 73, -99, 30, 75, -102, 32, 77, -105, 33, 79, -107, + 3, 1, 4, 3, 2, 1, 3, 4, -3, 3, 6, -8, + 4, 8, -14, 4, 11, -20, 5, 15, -25, 5, 20, -30, + 6, 24, -35, 7, 27, -39, 8, 30, -42, 9, 33, -46, + 10, 36, -50, 12, 38, -53, 13, 41, -57, 14, 43, -60, + 15, 46, -63, 16, 48, -67, 18, 50, -70, 19, 53, -73, + 20, 55, -76, 21, 57, -79, 22, 59, -82, 24, 62, -84, + 25, 64, -87, 26, 66, -90, 27, 68, -93, 28, 70, -96, + 29, 72, -99, 31, 74, -102, 32, 76, -104, 33, 78, -107, + 3, 0, 5, 4, 1, 2, 4, 2, -1, 4, 4, -7, + 4, 7, -13, 5, 10, -18, 5, 14, -23, 6, 18, -29, + 7, 22, -33, 8, 25, -37, 9, 28, -41, 10, 31, -45, + 11, 34, -49, 12, 37, -52, 13, 39, -56, 14, 42, -59, + 16, 44, -63, 17, 47, -66, 18, 49, -69, 19, 51, -72, + 20, 54, -75, 22, 56, -78, 23, 58, -81, 24, 61, -84, + 25, 63, -87, 26, 65, -90, 27, 67, -93, 29, 69, -95, + 30, 71, -98, 31, 74, -101, 32, 76, -104, 33, 77, -106, + 4, -2, 6, 5, -1, 3, 5, 0, 0, 5, 2, -6, + 5, 5, -11, 6, 8, -17, 6, 12, -22, 7, 16, -27, + 8, 19, -32, 9, 22, -36, 10, 25, -40, 11, 28, -44, + 12, 32, -48, 13, 34, -51, 14, 37, -55, 15, 40, -58, + 16, 43, -62, 17, 45, -65, 18, 48, -68, 20, 50, -71, + 21, 53, -75, 22, 55, -78, 23, 57, -81, 24, 60, -83, + 25, 62, -86, 27, 64, -89, 28, 66, -92, 29, 68, -95, + 30, 70, -98, 31, 73, -101, 32, 75, -103, 33, 77, -106, + 6, -5, 8, 6, -4, 5, 6, -2, 2, 6, 0, -4, + 7, 2, -10, 7, 5, -15, 8, 9, -20, 8, 13, -25, + 9, 16, -30, 10, 19, -34, 11, 23, -38, 12, 26, -42, + 13, 29, -46, 14, 32, -50, 15, 35, -54, 16, 38, -57, + 17, 41, -61, 18, 44, -64, 19, 46, -67, 20, 49, -70, + 21, 51, -74, 22, 54, -77, 23, 56, -80, 25, 58, -83, + 26, 61, -86, 27, 63, -89, 28, 65, -92, 29, 67, -94, + 30, 69, -97, 31, 72, -100, 33, 74, -103, 34, 76, -106, + 7, -8, 10, 7, -6, 7, 7, -5, 4, 8, -3, -2, + 8, -1, -7, 8, 2, -13, 9, 6, -18, 10, 10, -23, + 10, 13, -28, 11, 16, -32, 12, 20, -36, 13, 23, -40, + 14, 27, -45, 15, 30, -49, 15, 33, -52, 16, 36, -56, + 18, 39, -60, 19, 42, -63, 20, 44, -66, 21, 47, -69, + 22, 50, -73, 23, 52, -76, 24, 54, -79, 25, 57, -82, + 26, 59, -85, 27, 62, -88, 28, 64, -91, 30, 66, -94, + 31, 68, -97, 32, 71, -100, 33, 73, -102, 34, 75, -105, + 8, -11, 13, 9, -10, 9, 9, -8, 6, 9, -6, 0, + 9, -4, -5, 10, -1, -10, 10, 3, -16, 11, 6, -21, + 12, 10, -26, 12, 13, -30, 13, 17, -34, 14, 20, -39, + 15, 24, -43, 15, 27, -47, 16, 30, -51, 17, 33, -54, + 18, 36, -58, 19, 39, -62, 20, 42, -65, 21, 45, -68, + 23, 48, -72, 24, 50, -75, 25, 53, -78, 26, 55, -81, + 27, 58, -84, 28, 60, -87, 29, 63, -90, 30, 65, -93, + 31, 67, -96, 32, 70, -99, 33, 72, -102, 34, 74, -104, + 10, -14, 15, 10, -13, 12, 10, -11, 8, 11, -9, 3, + 11, -6, -3, 11, -4, -8, 12, 0, -13, 12, 3, -19, + 13, 7, -24, 13, 10, -28, 14, 14, -33, 15, 17, -37, + 16, 21, -41, 17, 24, -45, 17, 27, -49, 18, 31, -53, + 19, 34, -57, 20, 37, -60, 21, 40, -64, 22, 43, -67, + 23, 46, -71, 24, 48, -74, 25, 51, -77, 26, 53, -80, + 27, 56, -83, 28, 59, -86, 29, 61, -89, 30, 63, -92, + 31, 66, -95, 33, 68, -98, 34, 70, -101, 35, 73, -104, + 12, -16, 17, 12, -15, 14, 12, -14, 11, 12, -11, 5, + 12, -9, -1, 13, -6, -6, 13, -3, -11, 14, 0, -17, + 14, 4, -21, 15, 7, -26, 15, 11, -30, 16, 14, -35, + 17, 18, -39, 18, 21, -43, 18, 25, -47, 19, 28, -51, + 20, 31, -55, 21, 34, -59, 22, 37, -62, 23, 40, -66, + 24, 43, -69, 25, 46, -73, 26, 49, -76, 27, 52, -79, + 28, 54, -82, 29, 57, -85, 30, 59, -88, 31, 62, -91, + 32, 64, -94, 33, 67, -97, 34, 69, -100, 35, 71, -103, + 13, -18, 19, 14, -17, 16, 14, -16, 13, 14, -14, 7, + 14, -12, 2, 14, -9, -4, 15, -6, -9, 15, -3, -14, + 16, 1, -19, 16, 4, -24, 17, 8, -28, 17, 11, -33, + 18, 15, -38, 19, 19, -42, 19, 22, -46, 20, 25, -49, + 21, 29, -54, 22, 32, -57, 23, 35, -61, 24, 38, -64, + 25, 41, -68, 26, 44, -71, 27, 47, -75, 28, 49, -78, + 29, 52, -81, 30, 55, -84, 31, 57, -87, 32, 60, -90, + 33, 62, -93, 34, 65, -97, 35, 67, -99, 36, 70, -102, + 15, -21, 22, 16, -20, 19, 16, -18, 15, 16, -17, 10, + 16, -15, 5, 16, -12, -1, 17, -9, -6, 17, -6, -12, + 17, -3, -16, 18, 1, -21, 18, 4, -26, 19, 8, -30, + 20, 11, -35, 20, 15, -39, 21, 18, -43, 22, 22, -47, + 22, 25, -51, 23, 29, -55, 24, 32, -59, 25, 35, -62, + 26, 38, -66, 27, 41, -70, 28, 44, -73, 29, 47, -76, + 29, 49, -79, 31, 52, -83, 31, 55, -86, 32, 58, -89, + 33, 60, -92, 34, 63, -95, 35, 65, -98, 36, 68, -101, + 17, -22, 24, 17, -21, 21, 17, -20, 17, 17, -19, 12, + 18, -17, 7, 18, -14, 1, 18, -12, -4, 18, -9, -9, + 19, -5, -14, 19, -2, -19, 20, 1, -24, 20, 5, -28, + 21, 8, -33, 21, 12, -37, 22, 15, -41, 23, 19, -45, + 24, 23, -50, 24, 26, -53, 25, 29, -57, 26, 32, -61, + 27, 36, -65, 28, 39, -68, 28, 41, -71, 29, 44, -75, + 30, 47, -78, 31, 50, -82, 32, 53, -85, 33, 56, -88, + 34, 58, -91, 35, 61, -94, 36, 63, -97, 37, 66, -100, + 19, -24, 26, 19, -23, 23, 19, -22, 19, 19, -21, 14, + 19, -19, 9, 19, -17, 4, 20, -14, -1, 20, -11, -7, + 20, -8, -12, 21, -5, -17, 21, -2, -21, 22, 2, -26, + 22, 6, -31, 23, 9, -35, 23, 12, -39, 24, 16, -43, + 25, 20, -48, 25, 23, -52, 26, 26, -55, 27, 29, -59, + 28, 33, -63, 29, 36, -67, 29, 39, -70, 30, 42, -73, + 31, 45, -77, 32, 48, -80, 33, 51, -83, 34, 53, -87, + 35, 56, -90, 36, 59, -93, 37, 61, -96, 38, 64, -99, + 20, -26, 28, 20, -25, 25, 20, -24, 21, 20, -23, 16, + 21, -21, 11, 21, -19, 6, 21, -17, 1, 21, -14, -5, + 22, -11, -10, 22, -8, -15, 22, -4, -19, 23, -1, -24, + 23, 3, -29, 24, 6, -33, 25, 10, -37, 25, 13, -41, + 26, 17, -46, 27, 20, -50, 27, 23, -54, 28, 27, -57, + 29, 30, -61, 30, 33, -65, 30, 36, -68, 31, 39, -72, + 32, 42, -75, 33, 46, -79, 34, 48, -82, 35, 51, -85, + 35, 54, -88, 36, 57, -92, 37, 59, -95, 38, 62, -98, + 22, -27, 29, 22, -27, 26, 22, -26, 23, 22, -24, 18, + 22, -23, 13, 22, -21, 8, 23, -19, 3, 23, -16, -3, + 23, -13, -8, 23, -10, -12, 24, -7, -17, 24, -4, -22, + 25, 0, -27, 25, 3, -31, 26, 7, -35, 26, 10, -39, + 27, 14, -44, 28, 17, -48, 28, 21, -52, 29, 24, -56, + 30, 28, -60, 31, 31, -63, 31, 34, -67, 32, 37, -70, + 33, 40, -74, 34, 43, -77, 35, 46, -81, 35, 49, -84, + 36, 52, -87, 37, 55, -91, 38, 57, -94, 39, 60, -97, + 23, -29, 31, 23, -28, 28, 23, -28, 24, 24, -26, 20, + 24, -25, 15, 24, -23, 10, 24, -21, 5, 24, -18, 0, + 25, -15, -5, 25, -13, -10, 25, -10, -15, 26, -6, -19, + 26, -3, -25, 27, 1, -29, 27, 4, -33, 28, 7, -37, + 28, 11, -42, 29, 15, -46, 29, 18, -50, 30, 21, -54, + 31, 25, -58, 32, 28, -62, 32, 31, -65, 33, 34, -69, + 34, 37, -72, 35, 41, -76, 35, 44, -79, 36, 46, -83, + 37, 49, -86, 38, 52, -89, 39, 55, -92, 40, 58, -95, + 25, -31, 33, 25, -30, 29, 25, -29, 26, 25, -28, 21, + 25, -27, 17, 25, -25, 12, 26, -23, 7, 26, -20, 2, + 26, -18, -3, 26, -15, -8, 27, -12, -13, 27, -9, -17, + 27, -5, -22, 28, -2, -27, 28, 1, -31, 29, 5, -35, + 29, 8, -40, 30, 12, -44, 31, 15, -48, 31, 18, -52, + 32, 22, -56, 33, 25, -60, 33, 29, -63, 34, 32, -67, + 35, 35, -71, 36, 38, -74, 36, 41, -78, 37, 44, -81, + 38, 47, -84, 39, 50, -88, 40, 53, -91, 41, 56, -94, + 26, -32, 34, 26, -31, 31, 26, -31, 28, 27, -30, 23, + 27, -28, 19, 27, -27, 14, 27, -25, 9, 27, -22, 4, + 27, -20, -1, 28, -17, -6, 28, -14, -11, 28, -11, -15, + 29, -8, -20, 29, -5, -25, 30, -1, -29, 30, 2, -33, + 31, 6, -38, 31, 9, -42, 32, 12, -46, 32, 16, -50, + 33, 19, -54, 34, 23, -58, 34, 26, -62, 35, 29, -65, + 36, 32, -69, 37, 36, -73, 37, 39, -76, 38, 42, -80, + 39, 44, -83, 40, 48, -86, 41, 51, -90, 41, 53, -93, + 28, -34, 35, 28, -33, 32, 28, -32, 29, 28, -31, 25, + 28, -30, 21, 28, -28, 16, 28, -27, 11, 29, -24, 6, + 29, -22, 1, 29, -19, -4, 29, -17, -8, 30, -14, -13, + 30, -10, -18, 31, -7, -23, 31, -4, -27, 31, -1, -31, + 32, 3, -36, 33, 6, -40, 33, 10, -44, 34, 13, -48, + 34, 17, -52, 35, 20, -56, 36, 23, -60, 36, 26, -64, + 37, 30, -67, 38, 33, -71, 38, 36, -75, 39, 39, -78, + 40, 42, -81, 41, 45, -85, 41, 48, -88, 42, 51, -91, + 29, -35, 37, 29, -34, 34, 29, -34, 31, 30, -33, 27, + 30, -32, 22, 30, -30, 18, 30, -28, 13, 30, -26, 8, + 30, -24, 3, 31, -22, -2, 31, -19, -6, 31, -16, -11, + 32, -13, -16, 32, -10, -21, 32, -7, -25, 33, -3, -29, + 33, 0, -34, 34, 4, -38, 34, 7, -42, 35, 10, -46, + 35, 14, -51, 36, 17, -54, 37, 20, -58, 37, 24, -62, + 38, 27, -65, 39, 30, -69, 39, 33, -73, 40, 37, -76, + 41, 40, -80, 42, 43, -83, 42, 46, -87, 43, 49, -90, + 31, -36, 38, 31, -36, 35, 31, -35, 32, 31, -34, 28, + 31, -33, 24, 31, -32, 20, 31, -30, 15, 32, -28, 10, + 32, -26, 5, 32, -24, 0, 32, -21, -4, 33, -18, -9, + 33, -15, -14, 33, -12, -18, 34, -9, -23, 34, -6, -27, + 35, -2, -32, 35, 1, -36, 35, 4, -40, 36, 8, -44, + 37, 11, -49, 37, 15, -53, 38, 18, -56, 38, 21, -60, + 39, 24, -64, 40, 28, -68, 40, 31, -71, 41, 34, -75, + 42, 37, -78, 43, 40, -82, 43, 43, -85, 44, 46, -88, + 32, -38, 39, 32, -37, 36, 32, -37, 34, 33, -36, 30, + 33, -35, 26, 33, -33, 22, 33, -32, 17, 33, -30, 12, + 33, -28, 7, 33, -26, 3, 34, -23, -2, 34, -20, -7, + 34, -17, -12, 35, -14, -16, 35, -11, -21, 35, -8, -25, + 36, -5, -30, 36, -2, -34, 37, 2, -38, 37, 5, -42, + 38, 9, -47, 38, 12, -51, 39, 15, -54, 39, 18, -58, + 40, 22, -62, 41, 25, -66, 41, 28, -70, 42, 31, -73, + 43, 34, -76, 43, 38, -80, 44, 41, -84, 45, 44, -87, + 34, -39, 40, 34, -39, 38, 34, -38, 35, 34, -37, 31, + 34, -36, 28, 34, -35, 23, 34, -34, 19, 34, -32, 14, + 35, -30, 9, 35, -27, 5, 35, -25, 0, 35, -23, -5, + 36, -20, -10, 36, -17, -14, 36, -14, -19, 37, -11, -23, + 37, -7, -28, 38, -4, -32, 38, -1, -36, 38, 2, -40, + 39, 6, -45, 40, 9, -49, 40, 13, -53, 41, 16, -56, + 41, 19, -60, 42, 23, -64, 42, 26, -68, 43, 29, -71, + 44, 32, -75, 44, 35, -79, 45, 38, -82, 46, 41, -85, + 35, -40, 41, 35, -40, 39, 35, -40, 36, 35, -39, 33, + 36, -38, 29, 36, -37, 25, 36, -35, 21, 36, -33, 16, + 36, -31, 11, 36, -29, 7, 36, -27, 2, 37, -25, -3, + 37, -22, -8, 37, -19, -12, 38, -16, -17, 38, -13, -21, + 38, -10, -26, 39, -7, -30, 39, -3, -34, 40, 0, -38, + 40, 3, -43, 41, 7, -47, 41, 10, -51, 42, 13, -55, + 42, 16, -58, 43, 20, -62, 44, 23, -66, 44, 26, -70, + 45, 29, -73, 45, 33, -77, 46, 36, -80, 47, 39, -84, + 37, -42, 42, 37, -41, 40, 37, -41, 38, 37, -40, 34, + 37, -39, 31, 37, -38, 27, 37, -37, 23, 37, -35, 18, + 38, -33, 13, 38, -31, 9, 38, -29, 4, 38, -27, -1, + 38, -24, -6, 39, -21, -10, 39, -18, -15, 39, -15, -19, + 40, -12, -24, 40, -9, -28, 41, -6, -32, 41, -3, -36, + 41, 1, -41, 42, 4, -45, 42, 7, -49, 43, 11, -53, + 43, 14, -56, 44, 17, -61, 45, 20, -64, 45, 24, -68, + 46, 27, -71, 47, 30, -75, 47, 33, -79, 48, 36, -82, + 38, -43, 44, 38, -43, 41, 38, -42, 39, 38, -42, 36, + 38, -41, 32, 39, -40, 29, 39, -38, 24, 39, -37, 20, + 39, -35, 15, 39, -33, 11, 39, -31, 6, 39, -29, 1, + 40, -26, -4, 40, -23, -8, 40, -20, -13, 41, -18, -17, + 41, -14, -22, 41, -11, -26, 42, -8, -30, 42, -5, -34, + 43, -2, -39, 43, 2, -43, 44, 5, -47, 44, 8, -51, + 45, 11, -55, 45, 15, -59, 46, 18, -62, 46, 21, -66, + 47, 24, -70, 48, 28, -74, 48, 31, -77, 49, 34, -81, + 40, -44, 45, 40, -44, 43, 40, -44, 41, 40, -43, 37, + 40, -42, 34, 40, -41, 30, 40, -40, 26, 40, -38, 21, + 40, -37, 17, 40, -35, 12, 41, -33, 8, 41, -30, 3, + 41, -28, -2, 41, -25, -6, 42, -23, -11, 42, -20, -15, + 42, -17, -20, 43, -14, -24, 43, -11, -28, 43, -8, -32, + 44, -4, -37, 44, -1, -41, 45, 2, -45, 45, 5, -49, + 46, 9, -53, 46, 12, -57, 47, 15, -61, 47, 18, -64, + 48, 22, -68, 49, 25, -72, 49, 28, -75, 50, 31, -79, + 41, -46, 46, 41, -45, 44, 41, -45, 42, 41, -44, 39, + 41, -43, 36, 41, -43, 32, 41, -41, 28, 42, -40, 23, + 42, -38, 19, 42, -36, 14, 42, -34, 10, 42, -32, 5, + 42, -30, 0, 43, -27, -4, 43, -25, -9, 43, -22, -13, + 44, -19, -18, 44, -16, -22, 44, -13, -26, 45, -10, -30, + 45, -6, -35, 46, -3, -39, 46, 0, -43, 46, 3, -47, + 47, 6, -51, 47, 10, -55, 48, 13, -59, 49, 16, -63, + 49, 19, -66, 50, 22, -70, 50, 26, -74, 51, 29, -77, + 43, -47, 47, 43, -47, 45, 43, -46, 43, 43, -46, 40, + 43, -45, 37, 43, -44, 34, 43, -43, 30, 43, -41, 25, + 43, -40, 21, 43, -38, 16, 43, -36, 12, 44, -34, 7, + 44, -32, 2, 44, -29, -2, 44, -27, -7, 45, -24, -11, + 45, -21, -16, 45, -18, -20, 46, -15, -24, 46, -12, -29, + 46, -9, -33, 47, -6, -37, 47, -3, -41, 48, 0, -45, + 48, 4, -49, 49, 7, -53, 49, 10, -57, 50, 13, -61, + 50, 16, -64, 51, 20, -68, 51, 23, -72, 52, 26, -75, + 44, -48, 48, 44, -48, 46, 44, -48, 45, 44, -47, 42, + 44, -46, 39, 44, -45, 35, 44, -44, 31, 44, -43, 27, + 44, -41, 22, 45, -40, 18, 45, -38, 14, 45, -36, 9, + 45, -33, 4, 45, -31, 0, 46, -29, -5, 46, -26, -9, + 46, -23, -14, 47, -20, -18, 47, -17, -22, 47, -15, -27, + 48, -11, -31, 48, -8, -35, 48, -5, -39, 49, -2, -43, + 49, 1, -47, 50, 5, -51, 50, 8, -55, 51, 11, -59, + 51, 14, -63, 52, 17, -67, 52, 21, -70, 53, 24, -74, + 45, -49, 49, 45, -49, 48, 45, -49, 46, 45, -48, 43, + 45, -48, 40, 46, -47, 37, 46, -46, 33, 46, -44, 28, + 46, -43, 24, 46, -41, 20, 46, -39, 16, 46, -38, 11, + 47, -35, 6, 47, -33, 2, 47, -31, -3, 47, -28, -7, + 48, -25, -12, 48, -22, -16, 48, -20, -20, 48, -17, -25, + 49, -13, -29, 49, -10, -33, 50, -7, -37, 50, -4, -41, + 50, -1, -45, 51, 2, -50, 51, 5, -53, 52, 8, -57, + 52, 11, -61, 53, 15, -65, 53, 18, -68, 54, 21, -72, + 47, -51, 51, 47, -51, 49, 47, -50, 47, 47, -50, 45, + 47, -49, 42, 47, -48, 39, 47, -47, 35, 47, -46, 31, + 48, -45, 26, 48, -43, 22, 48, -41, 18, 48, -40, 13, + 48, -37, 9, 48, -35, 4, 49, -33, 0, 49, -30, -5, + 49, -28, -9, 49, -25, -14, 50, -22, -18, 50, -19, -22, + 50, -16, -27, 51, -13, -31, 51, -10, -35, 52, -7, -39, + 52, -4, -43, 52, -1, -47, 53, 2, -51, 53, 5, -55, + 54, 8, -59, 54, 12, -63, 55, 15, -66, 55, 18, -70, + 48, -52, 52, 48, -52, 50, 49, -52, 49, 49, -51, 46, + 49, -50, 43, 49, -50, 40, 49, -49, 36, 49, -47, 32, + 49, -46, 28, 49, -45, 24, 49, -43, 20, 49, -41, 15, + 50, -39, 10, 50, -37, 6, 50, -35, 2, 50, -32, -3, + 50, -30, -7, 51, -27, -12, 51, -24, -16, 51, -22, -20, + 52, -18, -25, 52, -16, -29, 52, -13, -33, 53, -10, -37, + 53, -7, -41, 54, -3, -45, 54, 0, -49, 54, 3, -53, + 55, 6, -57, 55, 9, -61, 56, 12, -64, 56, 15, -68, + 50, -53, 53, 50, -53, 51, 50, -53, 50, 50, -52, 47, + 50, -52, 45, 50, -51, 42, 50, -50, 38, 50, -49, 34, + 50, -48, 30, 50, -46, 26, 51, -45, 21, 51, -43, 17, + 51, -41, 12, 51, -39, 8, 51, -37, 4, 52, -34, -1, + 52, -32, -6, 52, -29, -10, 52, -26, -14, 53, -24, -18, + 53, -21, -23, 53, -18, -27, 54, -15, -31, 54, -12, -35, + 54, -9, -39, 55, -6, -43, 55, -3, -47, 56, 0, -51, + 56, 3, -55, 57, 7, -59, 57, 10, -63, 58, 13, -66, + 51, -55, 54, 51, -54, 53, 51, -54, 51, 51, -54, 49, + 51, -53, 46, 51, -52, 43, 51, -51, 40, 52, -50, 35, + 52, -49, 31, 52, -48, 27, 52, -46, 23, 52, -44, 19, + 52, -42, 14, 52, -40, 10, 53, -38, 6, 53, -36, 1, + 53, -33, -4, 53, -31, -8, 54, -28, -12, 54, -26, -16, + 54, -23, -21, 55, -20, -25, 55, -17, -29, 55, -14, -33, + 56, -11, -37, 56, -8, -42, 56, -5, -45, 57, -2, -49, + 57, 1, -53, 58, 4, -57, 58, 8, -61, 59, 11, -65, + 53, -56, 55, 53, -56, 54, 53, -55, 52, 53, -55, 50, + 53, -54, 47, 53, -54, 44, 53, -53, 41, 53, -52, 37, + 53, -50, 33, 53, -49, 29, 53, -48, 25, 53, -46, 21, + 54, -44, 16, 54, -42, 12, 54, -40, 7, 54, -38, 3, + 54, -35, -2, 55, -33, -6, 55, -30, -10, 55, -28, -14, + 55, -25, -19, 56, -22, -23, 56, -19, -27, 56, -16, -31, + 57, -14, -35, 57, -10, -40, 58, -7, -44, 58, -4, -47, + 58, -1, -51, 59, 2, -55, 59, 5, -59, 60, 8, -63, + 54, -57, 56, 54, -57, 55, 54, -56, 53, 54, -56, 51, + 54, -55, 49, 54, -55, 46, 54, -54, 43, 54, -53, 39, + 54, -52, 35, 54, -50, 31, 55, -49, 27, 55, -47, 23, + 55, -46, 18, 55, -44, 14, 55, -42, 9, 55, -40, 5, + 56, -37, 0, 56, -35, -4, 56, -32, -8, 56, -30, -12, + 57, -27, -17, 57, -24, -21, 57, -21, -25, 58, -19, -29, + 58, -16, -33, 58, -12, -38, 59, -10, -42, 59, -7, -46, + 60, -4, -49, 60, 0, -54, 60, 3, -57, 61, 6, -61, + 55, -58, 57, 55, -58, 56, 55, -58, 55, 55, -57, 52, + 55, -57, 50, 55, -56, 47, 56, -55, 44, 56, -54, 40, + 56, -53, 36, 56, -52, 32, 56, -50, 28, 56, -49, 24, + 56, -47, 20, 56, -45, 15, 57, -43, 11, 57, -41, 7, + 57, -39, 2, 57, -37, -2, 57, -34, -6, 58, -32, -11, + 58, -29, -15, 58, -26, -19, 59, -23, -23, 59, -21, -27, + 59, -18, -31, 60, -15, -36, 60, -12, -40, 60, -9, -44, + 61, -6, -47, 61, -3, -52, 62, 0, -55, 62, 3, -59, + 57, -59, 58, 57, -59, 57, 57, -59, 56, 57, -58, 54, + 57, -58, 51, 57, -57, 49, 57, -56, 45, 57, -55, 42, + 57, -54, 38, 57, -53, 34, 57, -52, 30, 57, -50, 26, + 58, -49, 21, 58, -47, 17, 58, -45, 13, 58, -43, 9, + 58, -41, 4, 58, -38, 0, 59, -36, -5, 59, -34, -9, + 59, -31, -13, 60, -28, -17, 60, -25, -22, 60, -23, -26, + 60, -20, -30, 61, -17, -34, 61, -14, -38, 62, -11, -42, + 62, -8, -46, 62, -5, -50, 63, -2, -54, 63, 1, -57, + 58, -60, 59, 58, -60, 58, 58, -60, 57, 58, -60, 55, + 58, -59, 53, 58, -58, 50, 58, -58, 47, 58, -57, 43, + 58, -56, 40, 58, -55, 36, 59, -53, 32, 59, -52, 28, + 59, -50, 23, 59, -48, 19, 59, -47, 15, 59, -45, 11, + 60, -42, 6, 60, -40, 2, 60, -38, -3, 60, -35, -7, + 60, -33, -11, 61, -30, -16, 61, -28, -20, 61, -25, -24, + 62, -22, -28, 62, -19, -32, 62, -16, -36, 63, -13, -40, + 63, -10, -44, 63, -7, -48, 64, -4, -52, 64, -1, -56, + 59, -62, 60, 59, -61, 59, 59, -61, 58, 59, -61, 56, + 59, -60, 54, 59, -60, 51, 60, -59, 48, 60, -58, 45, + 60, -57, 41, 60, -56, 37, 60, -55, 33, 60, -53, 29, + 60, -52, 25, 60, -50, 21, 60, -48, 17, 61, -46, 12, + 61, -44, 8, 61, -42, 3, 61, -40, -1, 61, -37, -5, + 62, -35, -10, 62, -32, -14, 62, -29, -18, 63, -27, -22, + 63, -24, -26, 63, -21, -30, 64, -18, -34, 64, -15, -38, + 64, -13, -42, 65, -9, -46, 65, -6, -50, 65, -4, -54, + 61, -63, 62, 61, -63, 60, 61, -62, 59, 61, -62, 57, + 61, -61, 55, 61, -61, 53, 61, -60, 50, 61, -59, 46, + 61, -58, 43, 61, -57, 39, 61, -56, 35, 61, -55, 31, + 61, -53, 27, 62, -51, 22, 62, -50, 18, 62, -48, 14, + 62, -46, 9, 62, -43, 5, 63, -41, 1, 63, -39, -3, + 63, -36, -8, 63, -34, -12, 64, -31, -16, 64, -29, -20, + 64, -26, -24, 64, -23, -28, 65, -20, -32, 65, -18, -36, + 65, -15, -40, 66, -12, -44, 66, -9, -48, 67, -6, -52, + 62, -64, 63, 62, -64, 62, 62, -63, 60, 62, -63, 59, + 62, -63, 56, 62, -62, 54, 62, -61, 51, 62, -61, 48, + 62, -60, 44, 62, -59, 40, 63, -57, 37, 63, -56, 33, + 63, -54, 28, 63, -53, 24, 63, -51, 20, 63, -49, 16, + 63, -47, 11, 64, -45, 7, 64, -43, 3, 64, -41, -1, + 64, -38, -6, 64, -36, -10, 65, -33, -14, 65, -31, -18, + 65, -28, -22, 66, -25, -27, 66, -23, -31, 66, -20, -34, + 67, -17, -38, 67, -14, -43, 67, -11, -46, 68, -8, -50, + 63, -65, 64, 63, -65, 63, 63, -65, 61, 63, -64, 60, + 63, -64, 58, 63, -63, 55, 63, -63, 52, 64, -62, 49, + 64, -61, 46, 64, -60, 42, 64, -59, 38, 64, -57, 34, + 64, -56, 30, 64, -54, 26, 64, -53, 22, 64, -51, 18, + 65, -49, 13, 65, -47, 9, 65, -45, 5, 65, -43, 1, + 65, -40, -4, 66, -38, -8, 66, -35, -12, 66, -33, -16, + 67, -30, -20, 67, -27, -25, 67, -25, -29, 67, -22, -33, + 68, -19, -36, 68, -16, -41, 69, -13, -45, 69, -10, -48, + 65, -66, 65, 65, -66, 64, 65, -66, 63, 65, -65, 61, + 65, -65, 59, 65, -64, 57, 65, -64, 54, 65, -63, 50, + 65, -62, 47, 65, -61, 44, 65, -60, 40, 65, -59, 36, + 65, -57, 32, 65, -56, 28, 66, -54, 24, 66, -52, 19, + 66, -50, 15, 66, -48, 11, 66, -46, 7, 66, -44, 2, + 67, -42, -2, 67, -39, -6, 67, -37, -10, 67, -35, -14, + 68, -32, -18, 68, -29, -23, 68, -27, -27, 69, -24, -31, + 69, -21, -35, 69, -18, -39, 70, -15, -43, 70, -12, -46, + 66, -67, 66, 66, -67, 65, 66, -67, 64, 66, -67, 62, + 66, -66, 60, 66, -66, 58, 66, -65, 55, 66, -64, 52, + 66, -63, 49, 66, -62, 45, 66, -61, 41, 66, -60, 38, + 67, -59, 33, 67, -57, 29, 67, -56, 25, 67, -54, 21, + 67, -52, 17, 67, -50, 12, 68, -48, 8, 68, -46, 4, + 68, -43, 0, 68, -41, -4, 68, -39, -9, 69, -36, -13, + 69, -34, -17, 69, -31, -21, 70, -29, -25, 70, -26, -29, + 70, -23, -33, 70, -20, -37, 71, -17, -41, 71, -15, -45, + 67, -68, 67, 67, -68, 66, 67, -68, 65, 67, -68, 63, + 67, -67, 61, 67, -67, 59, 67, -66, 56, 67, -65, 53, + 68, -65, 50, 68, -64, 47, 68, -63, 43, 68, -61, 39, + 68, -60, 35, 68, -59, 31, 68, -57, 27, 68, -55, 23, + 68, -53, 18, 69, -52, 14, 69, -50, 10, 69, -48, 6, + 69, -45, 1, 69, -43, -3, 70, -41, -7, 70, -38, -11, + 70, -36, -15, 70, -33, -19, 71, -31, -23, 71, -28, -27, + 71, -25, -31, 72, -22, -35, 72, -19, -39, 72, -17, -43, + 69, -69, 68, 69, -69, 67, 69, -69, 66, 69, -69, 64, + 69, -68, 63, 69, -68, 60, 69, -67, 58, 69, -67, 55, + 69, -66, 51, 69, -65, 48, 69, -64, 44, 69, -63, 41, + 69, -61, 36, 69, -60, 33, 69, -58, 29, 70, -57, 25, + 70, -55, 20, 70, -53, 16, 70, -51, 12, 70, -49, 8, + 70, -47, 3, 71, -45, -1, 71, -42, -5, 71, -40, -9, + 71, -38, -13, 72, -35, -17, 72, -32, -21, 72, -30, -25, + 72, -27, -29, 73, -24, -33, 73, -22, -37, 73, -19, -41, + 70, -71, 69, 70, -70, 68, 70, -70, 67, 70, -70, 65, + 70, -70, 64, 70, -69, 62, 70, -69, 59, 70, -68, 56, + 70, -67, 53, 70, -66, 49, 70, -65, 46, 70, -64, 42, + 70, -63, 38, 71, -61, 34, 71, -60, 30, 71, -58, 26, + 71, -56, 22, 71, -55, 18, 71, -53, 14, 71, -51, 10, + 72, -49, 5, 72, -46, 1, 72, -44, -3, 72, -42, -7, + 73, -40, -11, 73, -37, -16, 73, -34, -19, 73, -32, -23, + 74, -29, -27, 74, -26, -32, 74, -24, -35, 75, -21, -39, + 71, -72, 70, 71, -71, 69, 71, -71, 68, 71, -71, 67, + 71, -71, 65, 71, -70, 63, 71, -70, 60, 71, -69, 57, + 71, -68, 54, 71, -67, 51, 72, -66, 47, 72, -65, 44, + 72, -64, 40, 72, -63, 36, 72, -61, 32, 72, -60, 28, + 72, -58, 23, 72, -56, 19, 73, -54, 15, 73, -52, 11, + 73, -50, 7, 73, -48, 3, 73, -46, -1, 74, -44, -5, + 74, -41, -9, 74, -39, -14, 74, -36, -18, 75, -34, -22, + 75, -31, -25, 75, -28, -30, 75, -26, -34, 76, -23, -37, + 72, -73, 71, 72, -73, 70, 72, -72, 69, 72, -72, 68, + 72, -72, 66, 73, -71, 64, 73, -71, 62, 73, -70, 59, + 73, -69, 56, 73, -69, 52, 73, -68, 49, 73, -67, 45, + 73, -65, 41, 73, -64, 37, 73, -63, 34, 73, -61, 30, + 73, -59, 25, 74, -58, 21, 74, -56, 17, 74, -54, 13, + 74, -52, 9, 74, -50, 5, 75, -48, 1, 75, -45, -3, + 75, -43, -7, 75, -40, -12, 76, -38, -16, 76, -36, -20, + 76, -33, -24, 76, -30, -28, 77, -28, -32, 77, -25, -36, + 74, -74, 72, 74, -74, 71, 74, -73, 70, 74, -73, 69, + 74, -73, 67, 74, -72, 65, 74, -72, 63, 74, -71, 60, + 74, -71, 57, 74, -70, 54, 74, -69, 50, 74, -68, 47, + 74, -67, 43, 74, -65, 39, 74, -64, 35, 75, -62, 31, + 75, -61, 27, 75, -59, 23, 75, -57, 19, 75, -56, 15, + 75, -53, 10, 76, -51, 6, 76, -49, 2, 76, -47, -2, + 76, -45, -6, 76, -42, -10, 77, -40, -14, 77, -37, -18, + 77, -35, -22, 78, -32, -26, 78, -30, -30, 78, -27, -34, + 75, -75, 73, 75, -75, 72, 75, -75, 72, 75, -75, 70, + 75, -74, 69, 75, -74, 67, 75, -73, 64, 75, -73, 61, + 76, -72, 59, 76, -71, 55, 76, -70, 52, 76, -69, 49, + 76, -68, 45, 76, -67, 41, 76, -66, 37, 76, -64, 33, + 76, -62, 29, 76, -61, 25, 77, -59, 21, 77, -57, 17, + 77, -55, 13, 77, -53, 9, 77, -51, 5, 78, -49, 1, + 78, -47, -3, 78, -44, -8, 78, -42, -12, 78, -40, -16, + 79, -37, -20, 79, -35, -24, 79, -32, -28, 80, -29, -32, + 77, -76, 74, 77, -76, 73, 77, -76, 73, 77, -76, 71, + 77, -75, 70, 77, -75, 68, 77, -74, 66, 77, -74, 63, + 77, -73, 60, 77, -72, 57, 77, -72, 54, 77, -71, 50, + 77, -69, 46, 77, -68, 43, 77, -67, 39, 77, -66, 35, + 78, -64, 31, 78, -62, 27, 78, -61, 23, 78, -59, 19, + 78, -57, 14, 78, -55, 10, 79, -53, 6, 79, -51, 2, + 79, -49, -2, 79, -46, -6, 79, -44, -10, 80, -42, -14, + 80, -39, -18, 80, -36, -22, 80, -34, -26, 81, -31, -30, + 78, -77, 75, 78, -77, 75, 78, -77, 74, 78, -77, 72, + 78, -76, 71, 78, -76, 69, 78, -76, 67, 78, -75, 64, + 78, -74, 61, 78, -74, 58, 78, -73, 55, 78, -72, 52, + 78, -71, 48, 78, -69, 44, 79, -68, 40, 79, -67, 37, + 79, -65, 32, 79, -64, 28, 79, -62, 24, 79, -60, 20, + 79, -58, 16, 80, -56, 12, 80, -54, 8, 80, -52, 4, + 80, -50, 0, 80, -48, -4, 81, -46, -8, 81, -43, -12, + 81, -41, -16, 81, -38, -20, 82, -36, -24, 82, -33, -28, + 79, -78, 76, 79, -78, 76, 79, -78, 75, 79, -78, 73, + 79, -78, 72, 79, -77, 70, 79, -77, 68, 79, -76, 65, + 79, -75, 63, 79, -75, 60, 79, -74, 56, 79, -73, 53, + 80, -72, 49, 80, -71, 46, 80, -69, 42, 80, -68, 38, + 80, -67, 34, 80, -65, 30, 80, -63, 26, 80, -62, 22, + 81, -60, 18, 81, -58, 14, 81, -56, 10, 81, -54, 6, + 81, -52, 2, 82, -50, -3, 82, -47, -7, 82, -45, -10, + 82, -43, -14, 83, -40, -19, 83, -38, -22, 83, -35, -26, + 80, -79, 77, 80, -79, 77, 80, -79, 76, 80, -79, 75, + 80, -79, 73, 80, -78, 71, 80, -78, 69, 80, -77, 67, + 81, -77, 64, 81, -76, 61, 81, -75, 58, 81, -74, 55, + 81, -73, 51, 81, -72, 47, 81, -71, 43, 81, -69, 40, + 81, -68, 35, 81, -66, 32, 82, -65, 28, 82, -63, 24, + 82, -61, 19, 82, -59, 15, 82, -58, 11, 82, -56, 8, + 83, -54, 4, 83, -51, -1, 83, -49, -5, 83, -47, -9, + 83, -45, -13, 84, -42, -17, 84, -39, -21, 84, -37, -25, + 82, -80, 78, 82, -80, 78, 82, -80, 77, 82, -80, 76, + 82, -80, 74, 82, -79, 72, 82, -79, 70, 82, -78, 68, + 82, -78, 65, 82, -77, 62, 82, -76, 59, 82, -75, 56, + 82, -74, 52, 82, -73, 49, 82, -72, 45, 82, -71, 41, + 82, -69, 37, 83, -68, 33, 83, -66, 29, 83, -65, 25, + 83, -63, 21, 83, -61, 17, 83, -59, 13, 84, -57, 9, + 84, -55, 5, 84, -53, 1, 84, -51, -3, 84, -49, -7, + 85, -46, -11, 85, -44, -15, 85, -41, -19, 85, -39, -23, + 83, -82, 79, 83, -81, 79, 83, -81, 78, 83, -81, 77, + 83, -81, 75, 83, -80, 74, 83, -80, 72, 83, -79, 69, + 83, -79, 66, 83, -78, 64, 83, -77, 61, 83, -77, 57, + 83, -75, 54, 83, -74, 50, 83, -73, 47, 84, -72, 43, + 84, -71, 39, 84, -69, 35, 84, -68, 31, 84, -66, 27, + 84, -64, 23, 84, -62, 19, 85, -61, 15, 85, -59, 11, + 85, -57, 7, 85, -54, 3, 85, -52, -1, 86, -50, -5, + 86, -48, -9, 86, -45, -13, 86, -43, -17, 87, -41, -21, + 84, -83, 80, 84, -82, 80, 84, -82, 79, 84, -82, 78, + 84, -82, 76, 84, -81, 75, 84, -81, 73, 84, -81, 70, + 84, -80, 68, 84, -79, 65, 84, -79, 62, 84, -78, 59, + 85, -77, 55, 85, -76, 52, 85, -75, 48, 85, -73, 44, + 85, -72, 40, 85, -70, 36, 85, -69, 33, 85, -67, 29, + 85, -66, 24, 86, -64, 20, 86, -62, 17, 86, -60, 13, + 86, -58, 9, 86, -56, 4, 87, -54, 0, 87, -52, -3, + 87, -50, -7, 87, -47, -12, 87, -45, -15, 88, -42, -19, + 85, -84, 81, 85, -84, 81, 85, -83, 80, 85, -83, 79, + 85, -83, 77, 85, -83, 76, 85, -82, 74, 85, -82, 71, + 86, -81, 69, 86, -80, 66, 86, -80, 63, 86, -79, 60, + 86, -78, 56, 86, -77, 53, 86, -76, 50, 86, -75, 46, + 86, -73, 42, 86, -72, 38, 86, -70, 34, 87, -69, 30, + 87, -67, 26, 87, -65, 22, 87, -64, 18, 87, -62, 14, + 87, -60, 10, 88, -58, 6, 88, -56, 2, 88, -54, -2, + 88, -51, -6, 88, -49, -10, 89, -47, -14, 89, -44, -18, + 87, -85, 82, 87, -85, 82, 87, -84, 81, 87, -84, 80, + 87, -84, 79, 87, -84, 77, 87, -83, 75, 87, -83, 73, + 87, -82, 70, 87, -82, 67, 87, -81, 65, 87, -80, 61, + 87, -79, 58, 87, -78, 54, 87, -77, 51, 87, -76, 47, + 87, -74, 43, 87, -73, 40, 88, -72, 36, 88, -70, 32, + 88, -68, 28, 88, -67, 24, 88, -65, 20, 88, -63, 16, + 89, -61, 12, 89, -59, 8, 89, -57, 4, 89, -55, 0, + 89, -53, -4, 90, -51, -8, 90, -48, -12, 90, -46, -16, + 88, -86, 83, 88, -86, 83, 88, -85, 82, 88, -85, 81, + 88, -85, 80, 88, -85, 78, 88, -84, 76, 88, -84, 74, + 88, -83, 71, 88, -83, 69, 88, -82, 66, 88, -81, 63, + 88, -80, 59, 88, -79, 56, 88, -78, 52, 88, -77, 49, + 89, -76, 45, 89, -74, 41, 89, -73, 37, 89, -72, 34, + 89, -70, 29, 89, -68, 25, 89, -67, 22, 90, -65, 18, + 90, -63, 14, 90, -61, 9, 90, -59, 6, 90, -57, 2, + 90, -55, -2, 91, -52, -6, 91, -50, -10, 91, -48, -14, + 2, 8, 3, 2, 9, 0, 2, 11, -4, 3, 13, -9, + 3, 15, -15, 3, 19, -21, 4, 22, -26, 5, 26, -31, + 5, 30, -36, 6, 33, -40, 7, 36, -44, 9, 38, -47, + 10, 40, -51, 11, 42, -54, 12, 45, -58, 13, 47, -61, + 15, 49, -64, 16, 51, -67, 17, 53, -70, 18, 55, -73, + 20, 57, -76, 21, 59, -79, 22, 61, -82, 23, 63, -85, + 24, 65, -88, 26, 68, -91, 27, 70, -93, 28, 72, -96, + 29, 73, -99, 30, 76, -102, 32, 77, -104, 33, 79, -107, + 3, 7, 4, 3, 8, 1, 3, 9, -2, 3, 11, -8, + 4, 14, -14, 4, 17, -19, 5, 20, -24, 5, 24, -30, + 6, 28, -34, 7, 31, -38, 8, 33, -42, 9, 36, -46, + 11, 38, -50, 12, 41, -53, 13, 43, -57, 14, 45, -60, + 15, 48, -63, 16, 50, -66, 18, 52, -70, 19, 54, -73, + 20, 56, -76, 21, 58, -79, 22, 61, -82, 24, 63, -84, + 25, 65, -87, 26, 67, -90, 27, 69, -93, 28, 71, -96, + 29, 73, -98, 31, 75, -101, 32, 77, -104, 33, 79, -107, + 3, 5, 5, 4, 6, 2, 4, 8, -1, 4, 10, -7, + 4, 12, -13, 5, 15, -18, 5, 19, -23, 6, 22, -29, + 7, 26, -33, 8, 28, -37, 9, 31, -41, 10, 33, -45, + 11, 36, -49, 12, 39, -52, 13, 41, -56, 14, 44, -59, + 16, 46, -63, 17, 48, -66, 18, 51, -69, 19, 53, -72, + 20, 55, -75, 22, 57, -78, 23, 60, -81, 24, 62, -84, + 25, 64, -87, 26, 66, -90, 27, 68, -93, 29, 70, -95, + 30, 72, -98, 31, 74, -101, 32, 76, -104, 33, 78, -106, + 4, 4, 6, 4, 5, 3, 5, 6, 0, 5, 8, -6, + 5, 10, -11, 6, 14, -17, 6, 17, -22, 7, 20, -27, + 8, 23, -32, 9, 26, -36, 10, 29, -40, 11, 32, -44, + 12, 34, -48, 13, 37, -51, 14, 40, -55, 15, 42, -58, + 16, 45, -62, 17, 47, -65, 18, 49, -68, 20, 52, -71, + 21, 54, -75, 22, 56, -78, 23, 59, -81, 24, 61, -83, + 25, 63, -86, 27, 65, -89, 28, 67, -92, 29, 69, -95, + 30, 71, -98, 31, 74, -101, 32, 76, -103, 33, 78, -106, + 5, 2, 8, 5, 3, 5, 6, 4, 1, 6, 6, -4, + 6, 9, -10, 7, 11, -15, 7, 14, -20, 8, 18, -26, + 9, 21, -30, 10, 24, -34, 10, 26, -39, 11, 29, -42, + 12, 32, -47, 13, 35, -50, 14, 38, -54, 16, 40, -57, + 17, 43, -61, 18, 46, -64, 19, 48, -67, 20, 50, -71, + 21, 53, -74, 22, 55, -77, 23, 58, -80, 24, 60, -83, + 26, 62, -86, 27, 64, -89, 28, 66, -92, 29, 68, -95, + 30, 71, -97, 31, 73, -100, 32, 75, -103, 34, 77, -106, + 6, -1, 10, 7, 0, 7, 7, 2, 3, 7, 4, -3, + 7, 6, -8, 8, 9, -13, 8, 12, -19, 9, 15, -24, + 10, 18, -28, 11, 21, -33, 11, 24, -37, 12, 27, -41, + 13, 30, -45, 14, 33, -49, 15, 36, -53, 16, 38, -56, + 17, 41, -60, 18, 44, -63, 19, 46, -67, 20, 49, -70, + 22, 52, -73, 23, 54, -76, 24, 56, -79, 25, 58, -82, + 26, 61, -85, 27, 63, -88, 28, 65, -91, 29, 67, -94, + 30, 70, -97, 32, 72, -100, 33, 74, -103, 34, 76, -105, + 8, -4, 12, 8, -3, 9, 8, -1, 5, 8, 1, -1, + 9, 3, -6, 9, 6, -11, 10, 8, -16, 10, 12, -22, + 11, 15, -27, 12, 18, -31, 12, 21, -35, 13, 24, -39, + 14, 28, -44, 15, 31, -48, 16, 33, -51, 17, 36, -55, + 18, 39, -59, 19, 42, -62, 20, 45, -65, 21, 47, -69, + 22, 50, -72, 23, 52, -75, 24, 55, -78, 25, 57, -81, + 26, 59, -84, 28, 62, -88, 29, 64, -91, 30, 66, -93, + 31, 68, -96, 32, 71, -99, 33, 73, -102, 34, 75, -105, + 9, -7, 14, 9, -5, 11, 10, -4, 7, 10, -2, 2, + 10, 0, -4, 11, 3, -9, 11, 5, -14, 12, 9, -20, + 12, 12, -25, 13, 15, -29, 14, 18, -34, 14, 21, -38, + 15, 25, -42, 16, 28, -46, 17, 31, -50, 18, 34, -54, + 19, 37, -57, 20, 40, -61, 21, 43, -64, 22, 45, -68, + 23, 48, -71, 24, 51, -74, 25, 53, -77, 26, 55, -81, + 27, 58, -84, 28, 60, -87, 29, 63, -90, 30, 65, -93, + 31, 67, -95, 32, 70, -99, 33, 72, -101, 35, 74, -104, + 11, -9, 16, 11, -8, 13, 11, -7, 9, 11, -5, 4, + 12, -3, -2, 12, 0, -7, 12, 2, -12, 13, 6, -18, + 13, 9, -23, 14, 12, -27, 15, 15, -32, 15, 19, -36, + 16, 22, -40, 17, 25, -44, 18, 28, -48, 19, 31, -52, + 20, 35, -56, 21, 38, -60, 21, 40, -63, 22, 43, -66, + 24, 46, -70, 25, 49, -73, 25, 51, -76, 26, 54, -80, + 27, 56, -83, 29, 59, -86, 30, 61, -89, 31, 64, -92, + 32, 66, -95, 33, 68, -98, 34, 71, -101, 35, 73, -103, + 12, -12, 18, 13, -11, 15, 13, -10, 11, 13, -8, 6, + 13, -6, 0, 13, -3, -5, 14, -1, -10, 14, 3, -16, + 15, 6, -21, 15, 9, -25, 16, 12, -30, 17, 16, -34, + 17, 19, -39, 18, 22, -43, 19, 26, -47, 20, 29, -50, + 21, 32, -55, 21, 35, -58, 22, 38, -62, 23, 41, -65, + 24, 44, -69, 25, 47, -72, 26, 49, -75, 27, 52, -78, + 28, 54, -82, 29, 57, -85, 30, 60, -88, 31, 62, -91, + 32, 64, -94, 33, 67, -97, 34, 69, -100, 35, 71, -103, + 14, -14, 20, 14, -13, 17, 14, -12, 14, 14, -10, 8, + 15, -8, 3, 15, -6, -3, 15, -3, -8, 16, 0, -14, + 16, 3, -18, 17, 6, -23, 17, 9, -28, 18, 13, -32, + 18, 16, -37, 19, 20, -41, 20, 23, -45, 21, 26, -49, + 22, 29, -53, 22, 33, -57, 23, 36, -60, 24, 38, -64, + 25, 42, -68, 26, 44, -71, 27, 47, -74, 28, 50, -77, + 29, 52, -80, 30, 55, -84, 31, 58, -87, 32, 60, -90, + 33, 63, -93, 34, 65, -96, 35, 68, -99, 36, 70, -102, + 16, -17, 23, 16, -16, 20, 16, -15, 16, 16, -13, 10, + 16, -11, 5, 17, -9, 0, 17, -7, -5, 17, -4, -11, + 18, -1, -16, 18, 2, -20, 19, 6, -25, 19, 9, -29, + 20, 13, -34, 21, 16, -39, 21, 19, -43, 22, 23, -47, + 23, 26, -51, 24, 29, -55, 24, 32, -58, 25, 35, -62, + 26, 39, -66, 27, 41, -69, 28, 44, -72, 29, 47, -76, + 30, 50, -79, 31, 53, -82, 32, 55, -86, 33, 58, -89, + 34, 60, -92, 35, 63, -95, 36, 65, -98, 37, 68, -101, + 17, -19, 25, 18, -18, 21, 18, -17, 18, 18, -16, 12, + 18, -14, 7, 18, -12, 2, 18, -9, -3, 19, -6, -9, + 19, -3, -14, 20, 0, -18, 20, 3, -23, 21, 6, -27, + 21, 10, -32, 22, 13, -37, 22, 16, -41, 23, 20, -45, + 24, 23, -49, 25, 27, -53, 25, 30, -57, 26, 33, -60, + 27, 36, -64, 28, 39, -68, 29, 42, -71, 30, 45, -74, + 30, 47, -78, 31, 51, -81, 32, 53, -84, 33, 56, -87, + 34, 58, -91, 35, 61, -94, 36, 64, -97, 37, 66, -100, + 19, -21, 26, 19, -20, 23, 19, -19, 20, 19, -18, 14, + 19, -16, 9, 20, -14, 4, 20, -12, -1, 20, -9, -6, + 21, -6, -11, 21, -3, -16, 21, 0, -21, 22, 3, -25, + 22, 7, -30, 23, 10, -35, 24, 14, -39, 24, 17, -43, + 25, 21, -47, 26, 24, -51, 26, 27, -55, 27, 30, -59, + 28, 33, -63, 29, 37, -66, 30, 39, -70, 30, 42, -73, + 31, 45, -76, 32, 48, -80, 33, 51, -83, 34, 54, -86, + 35, 56, -89, 36, 59, -93, 37, 62, -96, 38, 64, -99, + 21, -23, 28, 21, -22, 25, 21, -21, 21, 21, -20, 16, + 21, -18, 11, 21, -16, 6, 21, -14, 1, 22, -11, -4, + 22, -9, -9, 22, -6, -14, 23, -3, -19, 23, 0, -23, + 24, 4, -28, 24, 7, -33, 25, 11, -37, 25, 14, -41, + 26, 18, -45, 27, 21, -49, 27, 24, -53, 28, 27, -57, + 29, 31, -61, 30, 34, -65, 31, 37, -68, 31, 40, -71, + 32, 43, -75, 33, 46, -79, 34, 49, -82, 35, 51, -85, + 36, 54, -88, 37, 57, -92, 38, 60, -95, 38, 62, -98, + 22, -25, 30, 22, -24, 27, 22, -23, 23, 22, -22, 18, + 22, -20, 13, 23, -19, 8, 23, -16, 3, 23, -14, -2, + 23, -11, -7, 24, -8, -12, 24, -5, -17, 25, -2, -21, + 25, 1, -26, 26, 5, -31, 26, 8, -35, 27, 11, -39, + 27, 15, -44, 28, 18, -48, 29, 21, -51, 29, 25, -55, + 30, 28, -59, 31, 31, -63, 32, 34, -66, 32, 37, -70, + 33, 40, -73, 34, 44, -77, 35, 46, -80, 36, 49, -84, + 36, 52, -87, 37, 55, -90, 38, 58, -93, 39, 60, -96, + 24, -26, 31, 24, -26, 28, 24, -25, 25, 24, -24, 20, + 24, -22, 15, 24, -21, 11, 24, -19, 6, 25, -16, 0, + 25, -14, -5, 25, -11, -10, 26, -8, -14, 26, -5, -19, + 26, -1, -24, 27, 2, -29, 27, 5, -33, 28, 8, -37, + 29, 12, -42, 29, 15, -46, 30, 19, -50, 30, 22, -53, + 31, 25, -58, 32, 29, -61, 33, 32, -65, 33, 35, -68, + 34, 38, -72, 35, 41, -76, 36, 44, -79, 36, 47, -82, + 37, 50, -85, 38, 53, -89, 39, 55, -92, 40, 58, -95, + 25, -28, 33, 25, -27, 30, 25, -27, 26, 25, -26, 22, + 25, -24, 17, 26, -23, 12, 26, -21, 8, 26, -18, 2, + 26, -16, -3, 27, -13, -8, 27, -10, -12, 27, -7, -17, + 28, -4, -22, 28, -1, -26, 29, 2, -31, 29, 6, -35, + 30, 9, -40, 30, 13, -44, 31, 16, -48, 31, 19, -52, + 32, 23, -56, 33, 26, -60, 34, 29, -63, 34, 32, -67, + 35, 35, -70, 36, 39, -74, 37, 42, -77, 37, 44, -81, + 38, 47, -84, 39, 50, -88, 40, 53, -91, 41, 56, -94, + 27, -30, 34, 27, -29, 31, 27, -28, 28, 27, -27, 24, + 27, -26, 19, 27, -25, 14, 27, -23, 10, 28, -20, 4, + 28, -18, -1, 28, -16, -5, 28, -13, -10, 29, -10, -15, + 29, -7, -20, 29, -3, -24, 30, 0, -29, 30, 3, -33, + 31, 7, -38, 31, 10, -42, 32, 13, -46, 33, 16, -50, + 33, 20, -54, 34, 23, -58, 35, 26, -61, 35, 30, -65, + 36, 33, -69, 37, 36, -72, 38, 39, -76, 38, 42, -79, + 39, 45, -83, 40, 48, -86, 41, 51, -89, 42, 54, -93, + 28, -31, 36, 28, -31, 33, 28, -30, 29, 28, -29, 25, + 28, -28, 21, 29, -27, 16, 29, -25, 12, 29, -23, 6, + 29, -20, 1, 29, -18, -3, 30, -15, -8, 30, -12, -13, + 30, -9, -18, 31, -6, -22, 31, -3, -27, 32, 0, -31, + 32, 4, -36, 33, 7, -40, 33, 10, -44, 34, 14, -48, + 34, 17, -52, 35, 21, -56, 36, 24, -60, 36, 27, -63, + 37, 30, -67, 38, 33, -71, 38, 36, -74, 39, 39, -78, + 40, 42, -81, 41, 46, -85, 42, 48, -88, 42, 51, -91, + 30, -33, 37, 30, -32, 34, 30, -32, 31, 30, -31, 27, + 30, -30, 23, 30, -28, 18, 30, -27, 14, 30, -25, 8, + 31, -22, 3, 31, -20, -1, 31, -17, -6, 31, -15, -11, + 32, -11, -16, 32, -9, -20, 33, -5, -25, 33, -2, -29, + 33, 1, -34, 34, 5, -38, 34, 8, -42, 35, 11, -46, + 36, 15, -50, 36, 18, -54, 37, 21, -58, 37, 24, -62, + 38, 27, -65, 39, 31, -69, 39, 34, -73, 40, 37, -76, + 41, 40, -79, 42, 43, -83, 42, 46, -86, 43, 49, -90, + 31, -34, 38, 31, -34, 35, 31, -33, 32, 31, -32, 29, + 31, -31, 24, 31, -30, 20, 32, -29, 16, 32, -26, 10, + 32, -24, 6, 32, -22, 1, 33, -20, -4, 33, -17, -9, + 33, -14, -14, 33, -11, -18, 34, -8, -23, 34, -5, -27, + 35, -1, -32, 35, 2, -36, 36, 5, -40, 36, 8, -44, + 37, 12, -48, 37, 15, -52, 38, 18, -56, 39, 22, -60, + 39, 25, -63, 40, 28, -67, 41, 31, -71, 41, 34, -74, + 42, 37, -78, 43, 41, -82, 43, 44, -85, 44, 46, -88, + 33, -36, 39, 33, -35, 37, 33, -35, 34, 33, -34, 30, + 33, -33, 26, 33, -32, 22, 33, -30, 17, 33, -28, 12, + 33, -26, 8, 34, -24, 3, 34, -22, -2, 34, -19, -6, + 35, -16, -12, 35, -13, -16, 35, -10, -21, 36, -7, -25, + 36, -4, -30, 36, -1, -34, 37, 2, -38, 37, 6, -42, + 38, 9, -46, 38, 13, -50, 39, 16, -54, 40, 19, -58, + 40, 22, -62, 41, 26, -66, 42, 29, -69, 42, 32, -73, + 43, 35, -76, 44, 38, -80, 44, 41, -83, 45, 44, -87, + 34, -37, 40, 34, -37, 38, 34, -36, 35, 34, -36, 32, + 34, -35, 28, 34, -33, 24, 35, -32, 19, 35, -30, 14, + 35, -28, 10, 35, -26, 5, 35, -24, 0, 36, -21, -4, + 36, -18, -10, 36, -16, -14, 37, -13, -18, 37, -10, -23, + 37, -6, -28, 38, -3, -32, 38, 0, -36, 39, 3, -40, + 39, 7, -45, 40, 10, -48, 40, 13, -52, 41, 16, -56, + 41, 19, -60, 42, 23, -64, 43, 26, -68, 43, 29, -71, + 44, 32, -75, 45, 36, -78, 45, 39, -82, 46, 42, -85, + 36, -39, 41, 36, -38, 39, 36, -38, 37, 36, -37, 33, + 36, -36, 30, 36, -35, 25, 36, -34, 21, 36, -32, 16, + 36, -30, 11, 36, -28, 7, 37, -26, 2, 37, -23, -2, + 37, -21, -7, 38, -18, -12, 38, -15, -16, 38, -12, -21, + 39, -9, -26, 39, -6, -30, 39, -3, -34, 40, 1, -38, + 40, 4, -43, 41, 7, -47, 41, 10, -50, 42, 14, -54, + 42, 17, -58, 43, 20, -62, 44, 23, -66, 44, 27, -69, + 45, 30, -73, 46, 33, -77, 46, 36, -80, 47, 39, -84, + 37, -40, 43, 37, -40, 40, 37, -39, 38, 37, -39, 35, + 37, -38, 31, 37, -37, 27, 37, -35, 23, 38, -34, 18, + 38, -32, 13, 38, -30, 9, 38, -28, 4, 38, -25, 0, + 39, -23, -5, 39, -20, -10, 39, -17, -14, 39, -14, -19, + 40, -11, -24, 40, -8, -28, 41, -5, -32, 41, -2, -36, + 42, 2, -41, 42, 5, -45, 43, 8, -49, 43, 11, -52, + 44, 14, -56, 44, 18, -60, 45, 21, -64, 45, 24, -68, + 46, 27, -71, 47, 31, -75, 47, 34, -79, 48, 37, -82, + 38, -42, 44, 38, -41, 42, 38, -41, 39, 39, -40, 36, + 39, -39, 33, 39, -38, 29, 39, -37, 25, 39, -35, 20, + 39, -34, 15, 39, -32, 11, 39, -30, 6, 40, -27, 2, + 40, -25, -3, 40, -22, -8, 40, -19, -12, 41, -17, -17, + 41, -13, -22, 42, -11, -26, 42, -7, -30, 42, -4, -34, + 43, -1, -39, 43, 2, -43, 44, 5, -47, 44, 8, -51, + 45, 12, -54, 45, 15, -59, 46, 18, -62, 46, 21, -66, + 47, 25, -69, 48, 28, -73, 48, 31, -77, 49, 34, -80, + 40, -43, 45, 40, -43, 43, 40, -42, 41, 40, -42, 38, + 40, -41, 34, 40, -40, 30, 40, -39, 26, 40, -37, 22, + 40, -35, 17, 41, -33, 13, 41, -31, 8, 41, -29, 4, + 41, -27, -1, 42, -24, -6, 42, -22, -10, 42, -19, -15, + 42, -16, -20, 43, -13, -24, 43, -10, -28, 44, -7, -32, + 44, -3, -37, 44, 0, -41, 45, 3, -45, 45, 6, -49, + 46, 9, -53, 46, 13, -57, 47, 16, -60, 48, 19, -64, + 48, 22, -68, 49, 25, -72, 49, 28, -75, 50, 31, -79, + 41, -44, 46, 41, -44, 44, 41, -44, 42, 41, -43, 39, + 41, -42, 36, 41, -41, 32, 42, -40, 28, 42, -39, 23, + 42, -37, 19, 42, -35, 15, 42, -33, 10, 42, -31, 6, + 43, -29, 1, 43, -26, -4, 43, -24, -8, 43, -21, -13, + 44, -18, -18, 44, -15, -22, 44, -12, -26, 45, -9, -30, + 45, -6, -35, 46, -3, -39, 46, 0, -43, 47, 3, -47, + 47, 7, -51, 48, 10, -55, 48, 13, -59, 49, 16, -62, + 49, 19, -66, 50, 23, -70, 50, 26, -74, 51, 29, -77, + 43, -46, 47, 43, -45, 45, 43, -45, 43, 43, -44, 40, + 43, -44, 37, 43, -43, 34, 43, -42, 30, 43, -40, 25, + 43, -39, 21, 43, -37, 16, 44, -35, 12, 44, -33, 8, + 44, -31, 2, 44, -28, -2, 44, -26, -6, 45, -23, -11, + 45, -20, -16, 45, -17, -20, 46, -14, -24, 46, -12, -28, + 47, -8, -33, 47, -5, -37, 47, -2, -41, 48, 1, -45, + 48, 4, -49, 49, 8, -53, 49, 11, -57, 50, 14, -61, + 50, 17, -64, 51, 20, -68, 51, 23, -72, 52, 26, -75, + 44, -47, 48, 44, -47, 47, 44, -46, 45, 44, -46, 42, + 44, -45, 39, 44, -44, 35, 44, -43, 31, 44, -42, 27, + 45, -40, 23, 45, -39, 18, 45, -37, 14, 45, -35, 9, + 45, -32, 4, 46, -30, 0, 46, -28, -4, 46, -25, -9, + 46, -22, -14, 47, -20, -18, 47, -17, -22, 47, -14, -26, + 48, -11, -31, 48, -8, -35, 49, -5, -39, 49, -1, -43, + 49, 2, -47, 50, 5, -51, 50, 8, -55, 51, 11, -59, + 51, 14, -62, 52, 18, -66, 53, 21, -70, 53, 24, -74, + 45, -48, 49, 45, -48, 48, 46, -48, 46, 46, -47, 43, + 46, -46, 40, 46, -46, 37, 46, -44, 33, 46, -43, 29, + 46, -42, 24, 46, -40, 20, 46, -38, 16, 46, -37, 11, + 47, -34, 6, 47, -32, 2, 47, -30, -2, 47, -27, -7, + 48, -24, -12, 48, -22, -16, 48, -19, -20, 49, -16, -24, + 49, -13, -29, 49, -10, -33, 50, -7, -37, 50, -4, -41, + 51, -1, -45, 51, 3, -49, 52, 6, -53, 52, 9, -57, + 52, 12, -61, 53, 15, -65, 54, 18, -68, 54, 21, -72, + 47, -50, 51, 47, -50, 49, 47, -49, 48, 47, -49, 45, + 47, -48, 42, 47, -47, 39, 47, -46, 35, 48, -45, 31, + 48, -44, 27, 48, -42, 22, 48, -40, 18, 48, -39, 14, + 48, -36, 9, 49, -34, 4, 49, -32, 0, 49, -30, -4, + 49, -27, -9, 50, -24, -14, 50, -22, -18, 50, -19, -22, + 51, -16, -27, 51, -13, -31, 51, -10, -35, 52, -7, -39, + 52, -4, -43, 53, 0, -47, 53, 3, -51, 53, 6, -55, + 54, 9, -58, 54, 12, -62, 55, 15, -66, 55, 18, -70, + 49, -51, 52, 49, -51, 50, 49, -51, 49, 49, -50, 46, + 49, -49, 43, 49, -49, 40, 49, -48, 37, 49, -46, 32, + 49, -45, 28, 49, -44, 24, 49, -42, 20, 49, -40, 16, + 50, -38, 11, 50, -36, 6, 50, -34, 2, 50, -32, -2, + 51, -29, -7, 51, -26, -12, 51, -24, -16, 51, -21, -20, + 52, -18, -25, 52, -15, -29, 53, -12, -33, 53, -9, -37, + 53, -6, -41, 54, -3, -45, 54, 0, -49, 55, 3, -53, + 55, 6, -57, 56, 10, -61, 56, 13, -64, 57, 16, -68, + 50, -52, 53, 50, -52, 52, 50, -52, 50, 50, -51, 48, + 50, -51, 45, 50, -50, 42, 50, -49, 38, 50, -48, 34, + 50, -47, 30, 51, -45, 26, 51, -44, 22, 51, -42, 17, + 51, -40, 12, 51, -38, 8, 51, -36, 4, 52, -33, -1, + 52, -31, -5, 52, -28, -10, 52, -26, -14, 53, -23, -18, + 53, -20, -23, 53, -17, -27, 54, -14, -31, 54, -11, -35, + 54, -8, -39, 55, -5, -43, 55, -2, -47, 56, 1, -51, + 56, 4, -55, 57, 7, -59, 57, 10, -63, 58, 13, -66, + 51, -54, 54, 51, -53, 53, 51, -53, 51, 51, -53, 49, + 51, -52, 46, 52, -51, 43, 52, -50, 40, 52, -49, 36, + 52, -48, 32, 52, -47, 28, 52, -45, 23, 52, -44, 19, + 52, -42, 14, 53, -40, 10, 53, -37, 6, 53, -35, 1, + 53, -33, -3, 53, -30, -8, 54, -28, -12, 54, -25, -16, + 54, -22, -21, 55, -19, -25, 55, -17, -29, 55, -14, -33, + 56, -11, -37, 56, -7, -41, 56, -4, -45, 57, -2, -49, + 57, 1, -53, 58, 5, -57, 58, 8, -61, 59, 11, -64, + 53, -55, 55, 53, -55, 54, 53, -54, 52, 53, -54, 50, + 53, -53, 48, 53, -53, 45, 53, -52, 41, 53, -51, 37, + 53, -49, 33, 53, -48, 29, 53, -47, 25, 53, -45, 21, + 54, -43, 16, 54, -41, 12, 54, -39, 8, 54, -37, 3, + 54, -35, -2, 55, -32, -6, 55, -30, -10, 55, -27, -14, + 56, -24, -19, 56, -21, -23, 56, -19, -27, 57, -16, -31, + 57, -13, -35, 57, -10, -40, 58, -7, -43, 58, -4, -47, + 58, -1, -51, 59, 3, -55, 59, 6, -59, 60, 9, -63, + 54, -56, 56, 54, -56, 55, 54, -56, 54, 54, -55, 51, + 54, -55, 49, 54, -54, 46, 54, -53, 43, 54, -52, 39, + 54, -51, 35, 55, -50, 31, 55, -48, 27, 55, -47, 23, + 55, -45, 18, 55, -43, 14, 55, -41, 9, 56, -39, 5, + 56, -36, 0, 56, -34, -4, 56, -32, -8, 57, -29, -12, + 57, -26, -17, 57, -24, -21, 57, -21, -25, 58, -18, -29, + 58, -15, -33, 58, -12, -38, 59, -9, -42, 59, -6, -45, + 60, -3, -49, 60, 0, -53, 61, 3, -57, 61, 6, -61, + 55, -57, 57, 55, -57, 56, 55, -57, 55, 55, -56, 53, + 56, -56, 50, 56, -55, 47, 56, -54, 44, 56, -53, 40, + 56, -52, 37, 56, -51, 33, 56, -50, 29, 56, -48, 24, + 56, -46, 20, 56, -45, 15, 57, -43, 11, 57, -41, 7, + 57, -38, 2, 57, -36, -2, 58, -34, -6, 58, -31, -10, + 58, -28, -15, 58, -26, -19, 59, -23, -23, 59, -20, -27, + 59, -17, -31, 60, -14, -36, 60, -11, -40, 60, -8, -44, + 61, -6, -47, 61, -2, -52, 62, 1, -55, 62, 4, -59, + 57, -58, 58, 57, -58, 57, 57, -58, 56, 57, -58, 54, + 57, -57, 51, 57, -56, 49, 57, -56, 46, 57, -55, 42, + 57, -54, 38, 57, -52, 34, 57, -51, 30, 57, -50, 26, + 58, -48, 21, 58, -46, 17, 58, -44, 13, 58, -42, 9, + 58, -40, 4, 59, -38, 0, 59, -35, -4, 59, -33, -9, + 59, -30, -13, 60, -28, -17, 60, -25, -21, 60, -22, -25, + 61, -20, -29, 61, -16, -34, 61, -14, -38, 62, -11, -42, + 62, -8, -46, 62, -4, -50, 63, -2, -54, 63, 1, -57, + 58, -60, 59, 58, -59, 58, 58, -59, 57, 58, -59, 55, + 58, -58, 53, 58, -58, 50, 58, -57, 47, 58, -56, 43, + 58, -55, 40, 59, -54, 36, 59, -53, 32, 59, -51, 28, + 59, -49, 23, 59, -48, 19, 59, -46, 15, 59, -44, 11, + 60, -42, 6, 60, -39, 2, 60, -37, -2, 60, -35, -7, + 61, -32, -11, 61, -30, -15, 61, -27, -20, 61, -24, -24, + 62, -22, -28, 62, -19, -32, 62, -16, -36, 63, -13, -40, + 63, -10, -44, 64, -7, -48, 64, -4, -52, 64, -1, -55, + 59, -61, 61, 59, -61, 59, 59, -60, 58, 59, -60, 56, + 60, -59, 54, 60, -59, 51, 60, -58, 48, 60, -57, 45, + 60, -56, 41, 60, -55, 37, 60, -54, 34, 60, -53, 30, + 60, -51, 25, 60, -49, 21, 61, -47, 17, 61, -46, 12, + 61, -43, 8, 61, -41, 4, 61, -39, -1, 62, -37, -5, + 62, -34, -9, 62, -32, -14, 62, -29, -18, 63, -26, -22, + 63, -24, -26, 63, -21, -30, 64, -18, -34, 64, -15, -38, + 64, -12, -42, 65, -9, -46, 65, -6, -50, 66, -3, -54, + 61, -62, 62, 61, -62, 61, 61, -62, 59, 61, -61, 57, + 61, -61, 55, 61, -60, 53, 61, -59, 50, 61, -59, 46, + 61, -58, 43, 61, -57, 39, 61, -55, 35, 61, -54, 31, + 62, -52, 27, 62, -51, 23, 62, -49, 18, 62, -47, 14, + 62, -45, 10, 62, -43, 5, 63, -41, 1, 63, -38, -3, + 63, -36, -8, 63, -33, -12, 64, -31, -16, 64, -28, -20, + 64, -26, -24, 64, -23, -28, 65, -20, -32, 65, -17, -36, + 65, -14, -40, 66, -11, -44, 66, -8, -48, 67, -5, -52, + 62, -63, 63, 62, -63, 62, 62, -63, 60, 62, -62, 59, + 62, -62, 57, 62, -61, 54, 62, -61, 51, 62, -60, 48, + 62, -59, 44, 62, -58, 41, 63, -57, 37, 63, -55, 33, + 63, -54, 28, 63, -52, 24, 63, -51, 20, 63, -49, 16, + 63, -47, 11, 64, -45, 7, 64, -42, 3, 64, -40, -1, + 64, -38, -6, 65, -35, -10, 65, -33, -14, 65, -30, -18, + 65, -28, -22, 66, -25, -26, 66, -22, -30, 66, -19, -34, + 67, -17, -38, 67, -13, -42, 67, -11, -46, 68, -8, -50, + 63, -64, 64, 63, -64, 63, 63, -64, 62, 63, -64, 60, + 63, -63, 58, 64, -63, 55, 64, -62, 53, 64, -61, 49, + 64, -60, 46, 64, -59, 42, 64, -58, 38, 64, -57, 35, + 64, -55, 30, 64, -54, 26, 64, -52, 22, 65, -50, 18, + 65, -48, 13, 65, -46, 9, 65, -44, 5, 65, -42, 1, + 66, -39, -4, 66, -37, -8, 66, -35, -12, 66, -32, -16, + 67, -30, -20, 67, -27, -25, 67, -24, -29, 68, -21, -32, + 68, -19, -36, 68, -16, -41, 69, -13, -44, 69, -10, -48, + 65, -65, 65, 65, -65, 64, 65, -65, 63, 65, -65, 61, + 65, -64, 59, 65, -64, 57, 65, -63, 54, 65, -62, 50, + 65, -61, 47, 65, -60, 44, 65, -59, 40, 65, -58, 36, + 65, -57, 32, 66, -55, 28, 66, -54, 24, 66, -52, 20, + 66, -50, 15, 66, -48, 11, 66, -46, 7, 67, -44, 3, + 67, -41, -2, 67, -39, -6, 67, -37, -10, 68, -34, -14, + 68, -32, -18, 68, -29, -23, 68, -26, -27, 69, -23, -31, + 69, -21, -34, 69, -18, -39, 70, -15, -43, 70, -12, -46, + 66, -67, 66, 66, -66, 65, 66, -66, 64, 66, -66, 62, + 66, -65, 60, 66, -65, 58, 66, -64, 55, 66, -64, 52, + 66, -63, 49, 66, -62, 45, 66, -61, 42, 67, -59, 38, + 67, -58, 33, 67, -57, 29, 67, -55, 25, 67, -53, 21, + 67, -51, 17, 67, -49, 13, 68, -47, 8, 68, -45, 4, + 68, -43, 0, 68, -41, -4, 69, -38, -8, 69, -36, -12, + 69, -34, -16, 69, -31, -21, 70, -28, -25, 70, -26, -29, + 70, -23, -33, 71, -20, -37, 71, -17, -41, 71, -14, -45, + 67, -68, 67, 67, -67, 66, 67, -67, 65, 67, -67, 63, + 67, -67, 61, 67, -66, 59, 67, -66, 57, 68, -65, 53, + 68, -64, 50, 68, -63, 47, 68, -62, 43, 68, -61, 39, + 68, -59, 35, 68, -58, 31, 68, -56, 27, 68, -55, 23, + 69, -53, 18, 69, -51, 14, 69, -49, 10, 69, -47, 6, + 69, -45, 2, 70, -43, -3, 70, -40, -7, 70, -38, -11, + 70, -35, -15, 71, -33, -19, 71, -30, -23, 71, -28, -27, + 71, -25, -31, 72, -22, -35, 72, -19, -39, 72, -16, -43, + 69, -69, 68, 69, -69, 67, 69, -68, 66, 69, -68, 64, + 69, -68, 63, 69, -67, 60, 69, -67, 58, 69, -66, 55, + 69, -65, 51, 69, -64, 48, 69, -63, 45, 69, -62, 41, + 69, -61, 37, 69, -59, 33, 69, -58, 29, 70, -56, 25, + 70, -54, 20, 70, -53, 16, 70, -51, 12, 70, -49, 8, + 71, -46, 3, 71, -44, -1, 71, -42, -5, 71, -40, -9, + 71, -37, -13, 72, -35, -17, 72, -32, -21, 72, -29, -25, + 73, -27, -29, 73, -24, -33, 73, -21, -37, 74, -18, -41, + 70, -70, 69, 70, -70, 68, 70, -70, 67, 70, -69, 66, + 70, -69, 64, 70, -68, 62, 70, -68, 59, 70, -67, 56, + 70, -66, 53, 70, -66, 50, 70, -65, 46, 70, -63, 42, + 71, -62, 38, 71, -61, 34, 71, -59, 30, 71, -58, 26, + 71, -56, 22, 71, -54, 18, 71, -52, 14, 72, -50, 10, + 72, -48, 5, 72, -46, 1, 72, -44, -3, 72, -41, -7, + 73, -39, -11, 73, -36, -15, 73, -34, -19, 73, -31, -23, + 74, -29, -27, 74, -26, -32, 74, -23, -35, 75, -21, -39, + 71, -71, 70, 71, -71, 69, 71, -71, 68, 71, -70, 67, + 71, -70, 65, 71, -70, 63, 71, -69, 60, 71, -68, 57, + 71, -68, 54, 72, -67, 51, 72, -66, 48, 72, -65, 44, + 72, -63, 40, 72, -62, 36, 72, -61, 32, 72, -59, 28, + 72, -57, 24, 72, -56, 20, 73, -54, 16, 73, -52, 11, + 73, -50, 7, 73, -48, 3, 73, -45, -1, 74, -43, -5, + 74, -41, -9, 74, -38, -14, 74, -36, -18, 75, -33, -21, + 75, -31, -25, 75, -28, -30, 76, -25, -34, 76, -23, -37, + 72, -72, 71, 72, -72, 70, 72, -72, 69, 73, -72, 68, + 73, -71, 66, 73, -71, 64, 73, -70, 62, 73, -70, 59, + 73, -69, 56, 73, -68, 52, 73, -67, 49, 73, -66, 45, + 73, -65, 41, 73, -63, 38, 73, -62, 34, 73, -61, 30, + 74, -59, 25, 74, -57, 21, 74, -55, 17, 74, -54, 13, + 74, -51, 9, 74, -49, 5, 75, -47, 1, 75, -45, -3, + 75, -43, -7, 75, -40, -12, 76, -38, -16, 76, -35, -20, + 76, -33, -24, 76, -30, -28, 77, -27, -32, 77, -25, -36, + 74, -73, 72, 74, -73, 71, 74, -73, 70, 74, -73, 69, + 74, -72, 67, 74, -72, 65, 74, -71, 63, 74, -71, 60, + 74, -70, 57, 74, -69, 54, 74, -68, 50, 74, -67, 47, + 74, -66, 43, 74, -65, 39, 75, -63, 35, 75, -62, 31, + 75, -60, 27, 75, -59, 23, 75, -57, 19, 75, -55, 15, + 75, -53, 10, 76, -51, 6, 76, -49, 2, 76, -47, -2, + 76, -44, -6, 77, -42, -10, 77, -40, -14, 77, -37, -18, + 77, -35, -22, 78, -32, -26, 78, -29, -30, 78, -27, -34, + 75, -75, 73, 75, -74, 73, 75, -74, 72, 75, -74, 70, + 75, -74, 69, 75, -73, 67, 75, -73, 64, 76, -72, 62, + 76, -71, 59, 76, -71, 56, 76, -70, 52, 76, -69, 49, + 76, -68, 45, 76, -66, 41, 76, -65, 37, 76, -64, 33, + 76, -62, 29, 76, -60, 25, 77, -59, 21, 77, -57, 17, + 77, -55, 13, 77, -53, 9, 77, -51, 5, 78, -49, 1, + 78, -47, -3, 78, -44, -8, 78, -42, -12, 79, -39, -16, + 79, -37, -20, 79, -34, -24, 79, -32, -28, 80, -29, -32, + 77, -76, 74, 77, -76, 74, 77, -75, 73, 77, -75, 71, + 77, -75, 70, 77, -74, 68, 77, -74, 66, 77, -73, 63, + 77, -73, 60, 77, -72, 57, 77, -71, 54, 77, -70, 50, + 77, -69, 46, 77, -68, 43, 77, -66, 39, 77, -65, 35, + 78, -63, 31, 78, -62, 27, 78, -60, 23, 78, -58, 19, + 78, -56, 14, 78, -54, 10, 79, -52, 6, 79, -50, 2, + 79, -48, -2, 79, -46, -6, 79, -44, -10, 80, -41, -14, + 80, -39, -18, 80, -36, -22, 81, -34, -26, 81, -31, -30, + 78, -77, 75, 78, -77, 75, 78, -76, 74, 78, -76, 72, + 78, -76, 71, 78, -76, 69, 78, -75, 67, 78, -74, 64, + 78, -74, 61, 78, -73, 58, 78, -72, 55, 78, -71, 52, + 78, -70, 48, 78, -69, 44, 79, -68, 40, 79, -66, 37, + 79, -65, 32, 79, -63, 28, 79, -62, 24, 79, -60, 21, + 79, -58, 16, 80, -56, 12, 80, -54, 8, 80, -52, 4, + 80, -50, 0, 80, -48, -4, 81, -45, -8, 81, -43, -12, + 81, -41, -16, 81, -38, -20, 82, -35, -24, 82, -33, -28, + 79, -78, 76, 79, -78, 76, 79, -78, 75, 79, -77, 74, + 79, -77, 72, 79, -77, 70, 79, -76, 68, 79, -76, 65, + 79, -75, 63, 79, -74, 60, 79, -73, 57, 80, -72, 53, + 80, -71, 49, 80, -70, 46, 80, -69, 42, 80, -68, 38, + 80, -66, 34, 80, -65, 30, 80, -63, 26, 80, -61, 22, + 81, -59, 18, 81, -58, 14, 81, -56, 10, 81, -54, 6, + 81, -52, 2, 82, -49, -3, 82, -47, -6, 82, -45, -10, + 82, -42, -14, 83, -40, -19, 83, -37, -22, 83, -35, -26, + 80, -79, 77, 80, -79, 77, 80, -79, 76, 80, -78, 75, + 80, -78, 73, 80, -78, 71, 81, -77, 69, 81, -77, 67, + 81, -76, 64, 81, -75, 61, 81, -75, 58, 81, -74, 55, + 81, -73, 51, 81, -71, 47, 81, -70, 44, 81, -69, 40, + 81, -67, 36, 81, -66, 32, 82, -64, 28, 82, -63, 24, + 82, -61, 19, 82, -59, 15, 82, -57, 12, 82, -55, 8, + 83, -53, 4, 83, -51, -1, 83, -49, -5, 83, -46, -9, + 83, -44, -12, 84, -42, -17, 84, -39, -21, 84, -37, -24, + 82, -80, 78, 82, -80, 78, 82, -80, 77, 82, -79, 76, + 82, -79, 74, 82, -79, 73, 82, -78, 70, 82, -78, 68, + 82, -77, 65, 82, -77, 62, 82, -76, 59, 82, -75, 56, + 82, -74, 52, 82, -73, 49, 82, -72, 45, 82, -70, 41, + 83, -69, 37, 83, -67, 33, 83, -66, 29, 83, -64, 26, + 83, -62, 21, 83, -61, 17, 83, -59, 13, 84, -57, 9, + 84, -55, 5, 84, -53, 1, 84, -50, -3, 84, -48, -7, + 85, -46, -11, 85, -43, -15, 85, -41, -19, 85, -39, -23, + 83, -81, 79, 83, -81, 79, 83, -81, 78, 83, -81, 77, + 83, -80, 75, 83, -80, 74, 83, -80, 72, 83, -79, 69, + 83, -78, 66, 83, -78, 64, 83, -77, 61, 83, -76, 57, + 83, -75, 54, 83, -74, 50, 84, -73, 47, 84, -72, 43, + 84, -70, 39, 84, -69, 35, 84, -67, 31, 84, -66, 27, + 84, -64, 23, 84, -62, 19, 85, -60, 15, 85, -58, 11, + 85, -56, 7, 85, -54, 3, 85, -52, -1, 86, -50, -5, + 86, -48, -9, 86, -45, -13, 86, -43, -17, 87, -40, -21, + 84, -82, 80, 84, -82, 80, 84, -82, 79, 84, -82, 78, + 84, -81, 76, 84, -81, 75, 84, -81, 73, 84, -80, 70, + 84, -79, 68, 84, -79, 65, 84, -78, 62, 85, -77, 59, + 85, -76, 55, 85, -75, 52, 85, -74, 48, 85, -73, 44, + 85, -71, 40, 85, -70, 36, 85, -69, 33, 85, -67, 29, + 86, -65, 24, 86, -64, 21, 86, -62, 17, 86, -60, 13, + 86, -58, 9, 86, -56, 4, 87, -54, 1, 87, -52, -3, + 87, -49, -7, 87, -47, -12, 87, -45, -15, 88, -42, -19, + 85, -83, 81, 85, -83, 81, 85, -83, 80, 85, -83, 79, + 85, -82, 78, 85, -82, 76, 85, -82, 74, 86, -81, 71, + 86, -81, 69, 86, -80, 66, 86, -79, 63, 86, -78, 60, + 86, -77, 57, 86, -76, 53, 86, -75, 50, 86, -74, 46, + 86, -73, 42, 86, -71, 38, 86, -70, 34, 87, -68, 30, + 87, -67, 26, 87, -65, 22, 87, -63, 18, 87, -61, 14, + 87, -60, 11, 88, -57, 6, 88, -55, 2, 88, -53, -2, + 88, -51, -5, 88, -49, -10, 89, -46, -14, 89, -44, -17, + 87, -84, 82, 87, -84, 82, 87, -84, 81, 87, -84, 80, + 87, -84, 79, 87, -83, 77, 87, -83, 75, 87, -82, 73, + 87, -82, 70, 87, -81, 68, 87, -80, 65, 87, -80, 62, + 87, -79, 58, 87, -78, 55, 87, -77, 51, 87, -75, 47, + 87, -74, 43, 88, -73, 40, 88, -71, 36, 88, -70, 32, + 88, -68, 28, 88, -66, 24, 88, -65, 20, 88, -63, 16, + 89, -61, 12, 89, -59, 8, 89, -57, 4, 89, -55, 0, + 89, -53, -4, 90, -50, -8, 90, -48, -12, 90, -46, -16, + 88, -85, 83, 88, -85, 83, 88, -85, 82, 88, -85, 81, + 88, -85, 80, 88, -84, 78, 88, -84, 76, 88, -83, 74, + 88, -83, 71, 88, -82, 69, 88, -82, 66, 88, -81, 63, + 88, -80, 59, 88, -79, 56, 88, -78, 53, 89, -77, 49, + 89, -75, 45, 89, -74, 41, 89, -73, 37, 89, -71, 34, + 89, -69, 29, 89, -68, 26, 89, -66, 22, 90, -64, 18, + 90, -63, 14, 90, -60, 10, 90, -58, 6, 90, -56, 2, + 91, -54, -2, 91, -52, -6, 91, -50, -10, 91, -47, -14, + 3, 13, 5, 3, 14, 2, 3, 15, -2, 4, 18, -8, + 4, 20, -13, 4, 23, -19, 5, 26, -24, 6, 29, -29, + 6, 32, -34, 7, 34, -38, 8, 36, -42, 9, 38, -46, + 11, 41, -49, 12, 43, -53, 13, 45, -56, 14, 47, -60, + 15, 49, -63, 17, 51, -66, 18, 53, -69, 19, 55, -72, + 20, 58, -76, 21, 60, -79, 23, 62, -81, 24, 64, -84, + 25, 66, -87, 26, 68, -90, 27, 70, -93, 28, 72, -96, + 30, 74, -98, 31, 76, -101, 32, 78, -104, 33, 79, -107, + 4, 12, 6, 4, 13, 3, 4, 14, -1, 4, 16, -6, + 5, 18, -12, 5, 21, -18, 6, 24, -23, 6, 27, -28, + 7, 29, -33, 8, 32, -37, 9, 34, -41, 10, 36, -44, + 11, 39, -48, 12, 41, -52, 14, 43, -55, 15, 45, -59, + 16, 48, -62, 17, 50, -66, 18, 52, -69, 19, 54, -72, + 21, 57, -75, 22, 59, -78, 23, 61, -81, 24, 63, -84, + 25, 65, -87, 26, 67, -90, 27, 69, -93, 29, 71, -95, + 30, 73, -98, 31, 75, -101, 32, 77, -104, 33, 79, -106, + 4, 10, 7, 5, 11, 4, 5, 12, 0, 5, 14, -5, + 5, 17, -11, 6, 19, -16, 6, 22, -21, 7, 25, -27, + 8, 27, -31, 9, 29, -35, 10, 32, -39, 11, 34, -43, + 12, 37, -47, 13, 39, -51, 14, 42, -55, 15, 44, -58, + 16, 47, -62, 17, 49, -65, 19, 51, -68, 20, 53, -71, + 21, 56, -74, 22, 58, -77, 23, 60, -80, 24, 62, -83, + 25, 64, -86, 27, 66, -89, 28, 68, -92, 29, 70, -95, + 30, 72, -98, 31, 74, -101, 32, 76, -103, 33, 78, -106, + 5, 8, 8, 5, 9, 5, 6, 11, 2, 6, 13, -4, + 6, 15, -10, 7, 17, -15, 7, 20, -20, 8, 22, -26, + 9, 25, -30, 10, 27, -34, 11, 30, -38, 11, 32, -42, + 13, 35, -47, 14, 38, -50, 15, 40, -54, 16, 43, -57, + 17, 45, -61, 18, 47, -64, 19, 50, -67, 20, 52, -70, + 21, 55, -74, 22, 57, -77, 23, 59, -80, 25, 61, -83, + 26, 63, -86, 27, 65, -89, 28, 67, -92, 29, 69, -95, + 30, 72, -97, 31, 74, -100, 32, 76, -103, 34, 78, -106, + 6, 6, 10, 6, 7, 6, 7, 9, 3, 7, 10, -3, + 7, 12, -8, 8, 15, -14, 8, 17, -19, 9, 20, -24, + 10, 22, -29, 10, 25, -33, 11, 28, -37, 12, 30, -41, + 13, 33, -45, 14, 36, -49, 15, 38, -53, 16, 41, -56, + 17, 44, -60, 18, 46, -63, 19, 48, -67, 20, 51, -70, + 22, 53, -73, 23, 56, -76, 24, 58, -79, 25, 60, -82, + 26, 62, -85, 27, 64, -88, 28, 67, -91, 29, 69, -94, + 30, 71, -97, 32, 73, -100, 33, 75, -103, 34, 77, -105, + 7, 4, 11, 8, 5, 8, 8, 6, 5, 8, 8, -1, + 8, 10, -6, 9, 12, -12, 9, 14, -17, 10, 17, -22, + 11, 20, -27, 11, 22, -31, 12, 25, -36, 13, 28, -40, + 14, 31, -44, 15, 34, -48, 16, 36, -52, 17, 39, -55, + 18, 42, -59, 19, 44, -62, 20, 47, -66, 21, 49, -69, + 22, 52, -72, 23, 54, -76, 24, 56, -79, 25, 59, -82, + 26, 61, -85, 28, 63, -88, 29, 66, -91, 30, 68, -94, + 31, 70, -96, 32, 72, -99, 33, 74, -102, 34, 76, -105, + 9, 1, 13, 9, 2, 10, 9, 3, 7, 9, 5, 1, + 10, 7, -5, 10, 9, -10, 11, 11, -15, 11, 14, -21, + 12, 17, -25, 12, 20, -30, 13, 23, -34, 14, 25, -38, + 15, 29, -43, 16, 31, -47, 17, 34, -50, 17, 37, -54, + 19, 40, -58, 20, 42, -61, 20, 45, -65, 21, 48, -68, + 23, 50, -72, 24, 53, -75, 25, 55, -78, 26, 57, -81, + 27, 60, -84, 28, 62, -87, 29, 64, -90, 30, 66, -93, + 31, 69, -96, 32, 71, -99, 33, 73, -102, 34, 75, -104, + 10, -2, 15, 10, -1, 12, 10, 0, 9, 11, 2, 3, + 11, 4, -3, 11, 6, -8, 12, 8, -13, 12, 11, -19, + 13, 14, -23, 14, 17, -28, 14, 20, -32, 15, 23, -37, + 16, 26, -41, 17, 29, -45, 17, 32, -49, 18, 35, -53, + 19, 38, -57, 20, 40, -60, 21, 43, -64, 22, 46, -67, + 23, 48, -71, 24, 51, -74, 25, 53, -77, 26, 56, -80, + 27, 58, -83, 28, 61, -86, 29, 63, -89, 30, 65, -92, + 32, 67, -95, 33, 70, -98, 34, 72, -101, 35, 74, -104, + 12, -5, 17, 12, -4, 14, 12, -3, 11, 12, -1, 5, + 12, 1, -1, 13, 3, -6, 13, 5, -11, 14, 8, -17, + 14, 11, -21, 15, 14, -26, 15, 17, -31, 16, 20, -35, + 17, 23, -40, 18, 26, -44, 18, 29, -47, 19, 32, -51, + 20, 35, -55, 21, 38, -59, 22, 41, -62, 23, 44, -66, + 24, 46, -69, 25, 49, -73, 26, 52, -76, 27, 54, -79, + 28, 56, -82, 29, 59, -85, 30, 61, -88, 31, 64, -91, + 32, 66, -94, 33, 68, -97, 34, 71, -100, 35, 73, -103, + 13, -7, 19, 13, -6, 16, 13, -5, 13, 14, -4, 7, + 14, -2, 2, 14, 0, -4, 14, 2, -9, 15, 5, -15, + 15, 8, -19, 16, 11, -24, 16, 14, -29, 17, 17, -33, + 18, 21, -38, 19, 24, -42, 19, 27, -46, 20, 30, -50, + 21, 33, -54, 22, 36, -57, 23, 39, -61, 24, 41, -64, + 25, 44, -68, 26, 47, -72, 27, 50, -75, 27, 52, -78, + 28, 55, -81, 30, 57, -84, 30, 60, -88, 31, 62, -90, + 32, 64, -93, 34, 67, -97, 35, 69, -100, 36, 71, -102, + 15, -10, 21, 15, -9, 18, 15, -8, 14, 15, -7, 9, + 15, -5, 4, 15, -3, -2, 16, 0, -7, 16, 2, -13, + 17, 5, -17, 17, 8, -22, 18, 11, -27, 18, 14, -31, + 19, 18, -36, 20, 21, -40, 20, 24, -44, 21, 27, -48, + 22, 30, -52, 23, 33, -56, 24, 36, -60, 24, 39, -63, + 25, 42, -67, 26, 45, -70, 27, 48, -74, 28, 50, -77, + 29, 53, -80, 30, 56, -83, 31, 58, -86, 32, 60, -90, + 33, 63, -92, 34, 65, -96, 35, 68, -99, 36, 70, -102, + 17, -13, 24, 17, -12, 20, 17, -11, 17, 17, -10, 11, + 17, -8, 6, 17, -6, 1, 18, -4, -4, 18, -1, -10, + 18, 2, -15, 19, 5, -20, 19, 8, -24, 20, 11, -29, + 20, 14, -34, 21, 17, -38, 22, 20, -42, 22, 24, -46, + 23, 27, -50, 24, 30, -54, 25, 33, -58, 26, 36, -61, + 26, 39, -65, 27, 42, -69, 28, 45, -72, 29, 47, -75, + 30, 50, -78, 31, 53, -82, 32, 56, -85, 33, 58, -88, + 34, 61, -91, 35, 63, -95, 36, 66, -97, 37, 68, -100, + 18, -15, 25, 18, -14, 22, 18, -14, 19, 18, -12, 13, + 18, -11, 8, 19, -9, 3, 19, -7, -2, 19, -4, -8, + 20, -1, -13, 20, 2, -18, 21, 5, -22, 21, 8, -27, + 22, 11, -32, 22, 14, -36, 23, 18, -40, 24, 21, -44, + 24, 24, -48, 25, 27, -52, 26, 30, -56, 27, 33, -60, + 27, 37, -64, 28, 40, -67, 29, 42, -71, 30, 45, -74, + 31, 48, -77, 32, 51, -81, 33, 54, -84, 34, 56, -87, + 34, 59, -90, 36, 61, -93, 36, 64, -96, 37, 66, -99, + 20, -17, 27, 20, -17, 24, 20, -16, 20, 20, -15, 15, + 20, -13, 10, 20, -11, 5, 20, -9, 0, 21, -6, -6, + 21, -4, -11, 21, -1, -15, 22, 2, -20, 22, 5, -25, + 23, 8, -30, 23, 12, -34, 24, 15, -38, 25, 18, -42, + 25, 21, -47, 26, 25, -51, 27, 28, -54, 27, 31, -58, + 28, 34, -62, 29, 37, -66, 30, 40, -69, 31, 43, -72, + 32, 46, -76, 33, 49, -79, 33, 51, -83, 34, 54, -86, + 35, 57, -89, 36, 59, -92, 37, 62, -95, 38, 64, -98, + 21, -19, 29, 21, -19, 26, 21, -18, 22, 21, -17, 17, + 21, -15, 12, 22, -14, 7, 22, -12, 2, 22, -9, -4, + 22, -6, -8, 23, -4, -13, 23, -1, -18, 24, 2, -23, + 24, 6, -28, 25, 9, -32, 25, 12, -36, 26, 15, -40, + 26, 19, -45, 27, 22, -49, 28, 25, -53, 29, 28, -56, + 29, 32, -61, 30, 35, -64, 31, 37, -68, 32, 40, -71, + 32, 43, -74, 33, 46, -78, 34, 49, -81, 35, 52, -85, + 36, 54, -88, 37, 57, -91, 38, 60, -94, 39, 62, -97, + 23, -21, 30, 23, -21, 27, 23, -20, 24, 23, -19, 19, + 23, -18, 14, 23, -16, 9, 23, -14, 4, 24, -11, -1, + 24, -9, -6, 24, -6, -11, 25, -4, -16, 25, -1, -21, + 25, 3, -26, 26, 6, -30, 26, 9, -34, 27, 12, -38, + 28, 16, -43, 28, 19, -47, 29, 22, -51, 30, 25, -55, + 30, 29, -59, 31, 32, -62, 32, 35, -66, 33, 38, -69, + 33, 41, -73, 34, 44, -77, 35, 47, -80, 36, 50, -83, + 37, 52, -86, 38, 55, -90, 39, 58, -93, 39, 60, -96, + 24, -23, 32, 24, -23, 29, 24, -22, 25, 24, -21, 21, + 24, -20, 16, 25, -18, 11, 25, -16, 6, 25, -14, 1, + 25, -11, -4, 26, -9, -9, 26, -6, -14, 26, -3, -18, + 27, 0, -24, 27, 3, -28, 28, 6, -32, 28, 10, -36, + 29, 13, -41, 29, 16, -45, 30, 20, -49, 31, 23, -53, + 31, 26, -57, 32, 29, -61, 33, 32, -64, 34, 35, -68, + 34, 38, -71, 35, 42, -75, 36, 44, -79, 37, 47, -82, + 38, 50, -85, 38, 53, -89, 39, 56, -92, 40, 58, -95, + 26, -25, 33, 26, -25, 30, 26, -24, 27, 26, -23, 22, + 26, -22, 18, 26, -20, 13, 26, -18, 8, 26, -16, 3, + 27, -14, -2, 27, -11, -7, 27, -9, -12, 28, -6, -16, + 28, -2, -21, 28, 1, -26, 29, 4, -30, 29, 7, -35, + 30, 10, -39, 31, 14, -43, 31, 17, -47, 32, 20, -51, + 32, 23, -55, 33, 27, -59, 34, 30, -63, 35, 33, -66, + 35, 36, -70, 36, 39, -74, 37, 42, -77, 38, 45, -80, + 38, 48, -84, 39, 51, -87, 40, 53, -90, 41, 56, -94, + 27, -27, 35, 27, -27, 32, 27, -26, 28, 27, -25, 24, + 27, -24, 20, 27, -22, 15, 28, -21, 10, 28, -18, 5, + 28, -16, 0, 28, -14, -5, 29, -11, -10, 29, -8, -14, + 29, -5, -19, 30, -2, -24, 30, 1, -28, 31, 4, -33, + 31, 8, -37, 32, 11, -41, 32, 14, -45, 33, 17, -49, + 34, 21, -54, 34, 24, -57, 35, 27, -61, 36, 30, -65, + 36, 33, -68, 37, 36, -72, 38, 39, -76, 38, 42, -79, + 39, 45, -82, 40, 48, -86, 41, 51, -89, 42, 54, -92, + 28, -29, 36, 29, -28, 33, 29, -28, 30, 29, -27, 26, + 29, -26, 21, 29, -24, 17, 29, -23, 12, 29, -20, 7, + 29, -18, 2, 30, -16, -3, 30, -13, -8, 30, -11, -12, + 31, -8, -17, 31, -5, -22, 32, -2, -26, 32, 1, -31, + 32, 5, -35, 33, 8, -39, 33, 11, -43, 34, 15, -47, + 35, 18, -52, 35, 21, -56, 36, 24, -59, 37, 27, -63, + 37, 31, -67, 38, 34, -70, 39, 37, -74, 39, 40, -77, + 40, 43, -81, 41, 46, -84, 42, 49, -88, 43, 52, -91, + 30, -31, 37, 30, -30, 34, 30, -29, 31, 30, -29, 27, + 30, -28, 23, 30, -26, 19, 30, -25, 14, 31, -23, 9, + 31, -20, 4, 31, -18, -1, 31, -16, -6, 32, -13, -10, + 32, -10, -15, 32, -7, -20, 33, -4, -24, 33, -1, -29, + 34, 2, -33, 34, 6, -37, 35, 9, -42, 35, 12, -46, + 36, 15, -50, 36, 19, -54, 37, 22, -57, 38, 25, -61, + 38, 28, -65, 39, 31, -69, 40, 34, -72, 40, 37, -76, + 41, 40, -79, 42, 44, -83, 43, 46, -86, 43, 49, -89, + 31, -32, 38, 31, -32, 36, 31, -31, 33, 32, -30, 29, + 32, -29, 25, 32, -28, 21, 32, -27, 16, 32, -25, 11, + 32, -23, 6, 33, -20, 1, 33, -18, -3, 33, -15, -8, + 33, -12, -13, 34, -10, -18, 34, -7, -22, 34, -4, -27, + 35, 0, -31, 35, 3, -35, 36, 6, -40, 36, 9, -44, + 37, 13, -48, 38, 16, -52, 38, 19, -56, 39, 22, -59, + 39, 25, -63, 40, 29, -67, 41, 32, -71, 41, 35, -74, + 42, 38, -78, 43, 41, -81, 44, 44, -85, 44, 47, -88, + 33, -34, 39, 33, -33, 37, 33, -33, 34, 33, -32, 30, + 33, -31, 27, 33, -30, 22, 33, -28, 18, 34, -27, 13, + 34, -25, 8, 34, -22, 3, 34, -20, -1, 34, -18, -6, + 35, -15, -11, 35, -12, -16, 35, -9, -20, 36, -6, -24, + 36, -3, -29, 37, 0, -34, 37, 3, -38, 38, 7, -42, + 38, 10, -46, 39, 13, -50, 39, 16, -54, 40, 20, -58, + 40, 23, -61, 41, 26, -65, 42, 29, -69, 42, 32, -73, + 43, 35, -76, 44, 39, -80, 44, 41, -83, 45, 44, -86, + 34, -35, 41, 34, -35, 38, 34, -34, 36, 34, -34, 32, + 35, -33, 28, 35, -32, 24, 35, -30, 20, 35, -28, 15, + 35, -27, 10, 35, -25, 5, 36, -22, 1, 36, -20, -4, + 36, -17, -9, 36, -14, -14, 37, -12, -18, 37, -9, -22, + 38, -5, -27, 38, -2, -32, 38, 1, -36, 39, 4, -40, + 39, 7, -44, 40, 11, -48, 40, 14, -52, 41, 17, -56, + 42, 20, -60, 42, 24, -64, 43, 27, -67, 43, 30, -71, + 44, 33, -74, 45, 36, -78, 45, 39, -82, 46, 42, -85, + 36, -37, 42, 36, -36, 39, 36, -36, 37, 36, -35, 34, + 36, -34, 30, 36, -33, 26, 36, -32, 21, 36, -30, 16, + 36, -28, 12, 37, -26, 7, 37, -24, 3, 37, -22, -2, + 37, -19, -7, 38, -17, -12, 38, -14, -16, 38, -11, -20, + 39, -8, -25, 39, -5, -30, 40, -2, -34, 40, 1, -38, + 41, 5, -42, 41, 8, -46, 42, 11, -50, 42, 14, -54, + 43, 17, -58, 43, 21, -62, 44, 24, -66, 44, 27, -69, + 45, 30, -73, 46, 33, -77, 46, 36, -80, 47, 39, -83, + 37, -38, 43, 37, -38, 41, 37, -38, 38, 37, -37, 35, + 37, -36, 31, 37, -35, 27, 38, -34, 23, 38, -32, 18, + 38, -30, 14, 38, -28, 9, 38, -26, 5, 38, -24, 0, + 39, -21, -5, 39, -19, -10, 39, -16, -14, 40, -13, -18, + 40, -10, -23, 40, -7, -28, 41, -4, -32, 41, -1, -36, + 42, 2, -40, 42, 5, -44, 43, 9, -48, 43, 12, -52, + 44, 15, -56, 44, 18, -60, 45, 21, -64, 46, 24, -67, + 46, 28, -71, 47, 31, -75, 47, 34, -78, 48, 37, -82, + 39, -40, 44, 39, -39, 42, 39, -39, 40, 39, -38, 36, + 39, -38, 33, 39, -37, 29, 39, -35, 25, 39, -34, 20, + 39, -32, 16, 39, -30, 11, 40, -28, 7, 40, -26, 2, + 40, -23, -3, 40, -21, -8, 41, -18, -12, 41, -16, -16, + 41, -12, -21, 42, -10, -26, 42, -7, -30, 43, -4, -34, + 43, 0, -38, 43, 3, -42, 44, 6, -46, 44, 9, -50, + 45, 12, -54, 45, 16, -58, 46, 19, -62, 47, 22, -66, + 47, 25, -69, 48, 28, -73, 48, 31, -77, 49, 34, -80, + 40, -41, 45, 40, -41, 43, 40, -41, 41, 40, -40, 38, + 40, -39, 35, 40, -38, 31, 40, -37, 27, 41, -35, 22, + 41, -34, 17, 41, -32, 13, 41, -30, 8, 41, -28, 4, + 41, -25, -1, 42, -23, -6, 42, -20, -10, 42, -18, -14, + 43, -15, -19, 43, -12, -24, 43, -9, -28, 44, -6, -32, + 44, -3, -37, 45, 0, -41, 45, 4, -45, 46, 7, -48, + 46, 10, -52, 47, 13, -57, 47, 16, -60, 48, 19, -64, + 48, 22, -68, 49, 26, -72, 49, 29, -75, 50, 32, -78, + 41, -43, 46, 41, -42, 44, 41, -42, 42, 42, -41, 39, + 42, -41, 36, 42, -40, 32, 42, -39, 28, 42, -37, 24, + 42, -36, 19, 42, -34, 15, 42, -32, 10, 43, -30, 6, + 43, -27, 1, 43, -25, -4, 43, -23, -8, 44, -20, -12, + 44, -17, -17, 44, -14, -22, 45, -11, -26, 45, -8, -30, + 45, -5, -35, 46, -2, -39, 46, 1, -43, 47, 4, -47, + 47, 7, -50, 48, 11, -55, 48, 14, -58, 49, 17, -62, + 49, 20, -66, 50, 23, -70, 51, 26, -73, 51, 29, -77, + 43, -44, 47, 43, -44, 46, 43, -43, 44, 43, -43, 41, + 43, -42, 38, 43, -41, 34, 43, -40, 30, 43, -39, 25, + 43, -37, 21, 44, -36, 17, 44, -34, 12, 44, -32, 8, + 44, -29, 3, 44, -27, -2, 45, -25, -6, 45, -22, -10, + 45, -19, -15, 46, -16, -20, 46, -14, -24, 46, -11, -28, + 47, -7, -33, 47, -4, -37, 47, -1, -41, 48, 2, -45, + 48, 5, -49, 49, 8, -53, 49, 11, -57, 50, 14, -60, + 50, 17, -64, 51, 21, -68, 52, 24, -72, 52, 27, -75, + 44, -45, 48, 44, -45, 47, 44, -45, 45, 44, -44, 42, + 44, -44, 39, 44, -43, 36, 45, -42, 32, 45, -40, 27, + 45, -39, 23, 45, -37, 19, 45, -36, 14, 45, -34, 10, + 45, -31, 5, 46, -29, 0, 46, -27, -4, 46, -24, -9, + 47, -21, -13, 47, -19, -18, 47, -16, -22, 47, -13, -26, + 48, -10, -31, 48, -7, -35, 49, -4, -39, 49, -1, -43, + 50, 2, -47, 50, 6, -51, 51, 9, -55, 51, 12, -59, + 52, 15, -62, 52, 18, -66, 53, 21, -70, 53, 24, -73, + 46, -47, 50, 46, -47, 48, 46, -46, 46, 46, -46, 43, + 46, -45, 40, 46, -44, 37, 46, -43, 33, 46, -42, 29, + 46, -40, 25, 46, -39, 20, 46, -37, 16, 47, -35, 12, + 47, -33, 7, 47, -31, 2, 47, -29, -2, 48, -26, -7, + 48, -23, -11, 48, -21, -16, 48, -18, -20, 49, -15, -24, + 49, -12, -29, 50, -9, -33, 50, -6, -37, 50, -3, -41, + 51, 0, -45, 51, 3, -49, 52, 6, -53, 52, 9, -57, + 53, 12, -60, 53, 16, -65, 54, 19, -68, 54, 22, -72, + 47, -49, 51, 47, -48, 49, 47, -48, 48, 47, -47, 45, + 47, -47, 42, 48, -46, 39, 48, -45, 35, 48, -44, 31, + 48, -42, 27, 48, -41, 23, 48, -39, 18, 48, -38, 14, + 48, -35, 9, 49, -33, 5, 49, -31, 0, 49, -29, -4, + 49, -26, -9, 50, -23, -13, 50, -21, -18, 50, -18, -22, + 51, -15, -26, 51, -12, -30, 51, -9, -35, 52, -6, -38, + 52, -3, -42, 53, 0, -47, 53, 3, -51, 54, 6, -54, + 54, 9, -58, 55, 13, -62, 55, 16, -66, 56, 19, -69, + 49, -50, 52, 49, -50, 51, 49, -49, 49, 49, -49, 46, + 49, -48, 44, 49, -47, 40, 49, -46, 37, 49, -45, 33, + 49, -44, 28, 49, -43, 24, 49, -41, 20, 50, -39, 16, + 50, -37, 11, 50, -35, 6, 50, -33, 2, 50, -31, -2, + 51, -28, -7, 51, -25, -11, 51, -23, -16, 52, -20, -20, + 52, -17, -24, 52, -14, -29, 53, -11, -33, 53, -9, -37, + 53, -6, -41, 54, -2, -45, 54, 1, -49, 55, 4, -53, + 55, 7, -56, 56, 10, -60, 56, 13, -64, 57, 16, -68, + 50, -51, 53, 50, -51, 52, 50, -51, 50, 50, -50, 48, + 50, -49, 45, 50, -49, 42, 50, -48, 38, 50, -47, 34, + 51, -45, 30, 51, -44, 26, 51, -43, 22, 51, -41, 18, + 51, -39, 13, 51, -37, 8, 52, -35, 4, 52, -33, 0, + 52, -30, -5, 52, -27, -9, 53, -25, -14, 53, -22, -18, + 53, -19, -23, 54, -16, -27, 54, -14, -31, 54, -11, -35, + 55, -8, -39, 55, -5, -43, 55, -2, -47, 56, 1, -51, + 56, 4, -55, 57, 8, -59, 57, 11, -62, 58, 14, -66, + 51, -52, 54, 51, -52, 53, 52, -52, 51, 52, -51, 49, + 52, -51, 46, 52, -50, 43, 52, -49, 40, 52, -48, 36, + 52, -47, 32, 52, -46, 28, 52, -44, 24, 52, -43, 19, + 52, -41, 15, 53, -39, 10, 53, -37, 6, 53, -34, 2, + 53, -32, -3, 54, -29, -8, 54, -27, -12, 54, -24, -16, + 54, -21, -21, 55, -19, -25, 55, -16, -29, 55, -13, -33, + 56, -10, -37, 56, -7, -41, 57, -4, -45, 57, -1, -49, + 57, 2, -53, 58, 5, -57, 58, 8, -61, 59, 11, -64, + 53, -54, 55, 53, -53, 54, 53, -53, 53, 53, -53, 50, + 53, -52, 48, 53, -51, 45, 53, -51, 41, 53, -50, 37, + 53, -48, 33, 53, -47, 29, 53, -46, 25, 54, -44, 21, + 54, -42, 16, 54, -40, 12, 54, -38, 8, 54, -36, 3, + 55, -34, -1, 55, -31, -6, 55, -29, -10, 55, -26, -14, + 56, -23, -19, 56, -21, -23, 56, -18, -27, 57, -15, -31, + 57, -12, -35, 57, -9, -39, 58, -6, -43, 58, -3, -47, + 59, 0, -51, 59, 3, -55, 60, 6, -59, 60, 9, -62, + 54, -55, 56, 54, -55, 55, 54, -54, 54, 54, -54, 52, + 54, -53, 49, 54, -53, 46, 54, -52, 43, 54, -51, 39, + 55, -50, 35, 55, -49, 31, 55, -47, 27, 55, -46, 23, + 55, -44, 18, 55, -42, 14, 55, -40, 10, 56, -38, 5, + 56, -36, 1, 56, -33, -4, 56, -31, -8, 57, -28, -12, + 57, -26, -17, 57, -23, -21, 58, -20, -25, 58, -17, -29, + 58, -15, -33, 59, -11, -37, 59, -9, -41, 59, -6, -45, + 60, -3, -49, 60, 1, -53, 61, 4, -57, 61, 7, -61, + 56, -56, 57, 56, -56, 56, 56, -56, 55, 56, -55, 53, + 56, -55, 50, 56, -54, 48, 56, -53, 44, 56, -52, 40, + 56, -51, 37, 56, -50, 33, 56, -49, 29, 56, -47, 25, + 56, -45, 20, 57, -44, 16, 57, -42, 11, 57, -40, 7, + 57, -37, 2, 57, -35, -2, 58, -33, -6, 58, -30, -10, + 58, -28, -15, 58, -25, -19, 59, -22, -23, 59, -20, -27, + 59, -17, -31, 60, -14, -36, 60, -11, -39, 61, -8, -43, + 61, -5, -47, 61, -2, -51, 62, 1, -55, 62, 4, -59, + 57, -57, 59, 57, -57, 57, 57, -57, 56, 57, -57, 54, + 57, -56, 52, 57, -55, 49, 57, -55, 46, 57, -54, 42, + 57, -53, 38, 57, -51, 34, 57, -50, 30, 58, -49, 26, + 58, -47, 22, 58, -45, 17, 58, -43, 13, 58, -41, 9, + 58, -39, 4, 59, -37, 0, 59, -35, -4, 59, -32, -8, + 59, -29, -13, 60, -27, -17, 60, -24, -21, 60, -22, -25, + 61, -19, -29, 61, -16, -34, 61, -13, -38, 62, -10, -42, + 62, -7, -45, 63, -4, -50, 63, -1, -53, 63, 2, -57, + 58, -59, 60, 58, -58, 58, 58, -58, 57, 58, -58, 55, + 58, -57, 53, 58, -57, 50, 58, -56, 47, 58, -55, 43, + 59, -54, 40, 59, -53, 36, 59, -52, 32, 59, -50, 28, + 59, -49, 23, 59, -47, 19, 59, -45, 15, 60, -43, 11, + 60, -41, 6, 60, -39, 2, 60, -36, -2, 60, -34, -6, + 61, -31, -11, 61, -29, -15, 61, -26, -19, 62, -24, -23, + 62, -21, -27, 62, -18, -32, 63, -15, -36, 63, -12, -40, + 63, -10, -44, 64, -6, -48, 64, -3, -52, 64, 0, -55, + 60, -60, 61, 60, -60, 60, 60, -59, 58, 60, -59, 56, + 60, -59, 54, 60, -58, 52, 60, -57, 49, 60, -56, 45, + 60, -55, 41, 60, -54, 38, 60, -53, 34, 60, -52, 30, + 60, -50, 25, 60, -48, 21, 61, -47, 17, 61, -45, 13, + 61, -43, 8, 61, -40, 4, 61, -38, 0, 62, -36, -5, + 62, -33, -9, 62, -31, -13, 62, -28, -17, 63, -26, -22, + 63, -23, -26, 63, -20, -30, 64, -17, -34, 64, -15, -38, + 64, -12, -42, 65, -9, -46, 65, -6, -50, 66, -3, -53, + 61, -61, 62, 61, -61, 61, 61, -61, 59, 61, -60, 58, + 61, -60, 55, 61, -59, 53, 61, -59, 50, 61, -58, 46, + 61, -57, 43, 61, -56, 39, 61, -54, 35, 61, -53, 31, + 62, -52, 27, 62, -50, 23, 62, -48, 19, 62, -46, 14, + 62, -44, 10, 62, -42, 6, 63, -40, 1, 63, -38, -3, + 63, -35, -7, 63, -33, -12, 64, -30, -16, 64, -28, -20, + 64, -25, -24, 65, -22, -28, 65, -19, -32, 65, -17, -36, + 66, -14, -40, 66, -11, -44, 66, -8, -48, 67, -5, -52, + 62, -62, 63, 62, -62, 62, 62, -62, 61, 62, -61, 59, + 62, -61, 57, 62, -60, 54, 62, -60, 51, 62, -59, 48, + 62, -58, 44, 63, -57, 41, 63, -56, 37, 63, -55, 33, + 63, -53, 29, 63, -51, 24, 63, -50, 20, 63, -48, 16, + 64, -46, 12, 64, -44, 7, 64, -42, 3, 64, -40, -1, + 64, -37, -6, 65, -35, -10, 65, -32, -14, 65, -30, -18, + 65, -27, -22, 66, -24, -26, 66, -22, -30, 66, -19, -34, + 67, -16, -38, 67, -13, -42, 68, -10, -46, 68, -7, -50, + 63, -63, 64, 63, -63, 63, 64, -63, 62, 64, -63, 60, + 64, -62, 58, 64, -62, 55, 64, -61, 53, 64, -60, 49, + 64, -59, 46, 64, -58, 42, 64, -57, 39, 64, -56, 35, + 64, -54, 30, 64, -53, 26, 64, -51, 22, 65, -50, 18, + 65, -48, 13, 65, -46, 9, 65, -44, 5, 65, -41, 1, + 66, -39, -4, 66, -37, -8, 66, -34, -12, 66, -32, -16, + 67, -29, -20, 67, -26, -24, 67, -24, -28, 68, -21, -32, + 68, -18, -36, 68, -15, -40, 69, -12, -44, 69, -9, -48, + 65, -65, 65, 65, -64, 64, 65, -64, 63, 65, -64, 61, + 65, -63, 59, 65, -63, 57, 65, -62, 54, 65, -62, 51, + 65, -61, 47, 65, -60, 44, 65, -59, 40, 65, -57, 36, + 65, -56, 32, 66, -54, 28, 66, -53, 24, 66, -51, 20, + 66, -49, 15, 66, -47, 11, 66, -45, 7, 67, -43, 3, + 67, -41, -2, 67, -38, -6, 67, -36, -10, 68, -34, -14, + 68, -31, -18, 68, -28, -23, 68, -26, -27, 69, -23, -30, + 69, -20, -34, 69, -17, -39, 70, -14, -42, 70, -12, -46, + 66, -66, 66, 66, -66, 65, 66, -65, 64, 66, -65, 62, + 66, -65, 60, 66, -64, 58, 66, -64, 55, 66, -63, 52, + 66, -62, 49, 66, -61, 45, 67, -60, 42, 67, -59, 38, + 67, -57, 33, 67, -56, 30, 67, -54, 25, 67, -53, 21, + 67, -51, 17, 68, -49, 13, 68, -47, 9, 68, -45, 4, + 68, -42, 0, 68, -40, -4, 69, -38, -8, 69, -35, -12, + 69, -33, -16, 69, -30, -21, 70, -28, -25, 70, -25, -29, + 70, -22, -33, 71, -19, -37, 71, -17, -41, 71, -14, -44, + 67, -67, 67, 67, -67, 66, 67, -67, 65, 67, -66, 63, + 67, -66, 62, 68, -65, 59, 68, -65, 57, 68, -64, 53, + 68, -63, 50, 68, -62, 47, 68, -61, 43, 68, -60, 39, + 68, -59, 35, 68, -57, 31, 68, -56, 27, 68, -54, 23, + 69, -52, 19, 69, -50, 14, 69, -48, 10, 69, -46, 6, + 69, -44, 2, 70, -42, -2, 70, -40, -6, 70, -37, -10, + 70, -35, -14, 71, -32, -19, 71, -30, -23, 71, -27, -27, + 71, -24, -31, 72, -21, -35, 72, -19, -39, 72, -16, -43, + 69, -68, 68, 69, -68, 67, 69, -68, 66, 69, -67, 65, + 69, -67, 63, 69, -67, 61, 69, -66, 58, 69, -65, 55, + 69, -64, 52, 69, -64, 48, 69, -63, 45, 69, -61, 41, + 69, -60, 37, 69, -59, 33, 70, -57, 29, 70, -56, 25, + 70, -54, 20, 70, -52, 16, 70, -50, 12, 70, -48, 8, + 71, -46, 3, 71, -44, -1, 71, -41, -5, 71, -39, -9, + 72, -37, -13, 72, -34, -17, 72, -32, -21, 72, -29, -25, + 73, -26, -29, 73, -23, -33, 73, -21, -37, 74, -18, -41, + 70, -69, 69, 70, -69, 68, 70, -69, 67, 70, -69, 66, + 70, -68, 64, 70, -68, 62, 70, -67, 59, 70, -66, 56, + 70, -66, 53, 70, -65, 50, 70, -64, 46, 70, -63, 43, + 71, -61, 38, 71, -60, 34, 71, -59, 31, 71, -57, 27, + 71, -55, 22, 71, -54, 18, 71, -52, 14, 72, -50, 10, + 72, -47, 5, 72, -45, 1, 72, -43, -3, 72, -41, -7, + 73, -39, -11, 73, -36, -15, 73, -33, -19, 74, -31, -23, + 74, -28, -27, 74, -25, -31, 74, -23, -35, 75, -20, -39, + 71, -70, 70, 71, -70, 69, 71, -70, 68, 71, -70, 67, + 71, -69, 65, 71, -69, 63, 71, -68, 61, 71, -68, 57, + 72, -67, 54, 72, -66, 51, 72, -65, 48, 72, -64, 44, + 72, -63, 40, 72, -61, 36, 72, -60, 32, 72, -59, 28, + 72, -57, 24, 73, -55, 20, 73, -53, 16, 73, -51, 12, + 73, -49, 7, 73, -47, 3, 73, -45, -1, 74, -43, -5, + 74, -40, -9, 74, -38, -14, 74, -35, -17, 75, -33, -21, + 75, -30, -25, 75, -27, -30, 76, -25, -33, 76, -22, -37, + 73, -71, 71, 73, -71, 70, 73, -71, 69, 73, -71, 68, + 73, -70, 66, 73, -70, 64, 73, -70, 62, 73, -69, 59, + 73, -68, 56, 73, -67, 53, 73, -66, 49, 73, -65, 46, + 73, -64, 41, 73, -63, 38, 73, -61, 34, 73, -60, 30, + 74, -58, 25, 74, -57, 21, 74, -55, 17, 74, -53, 13, + 74, -51, 9, 74, -49, 5, 75, -47, 1, 75, -44, -3, + 75, -42, -7, 75, -40, -12, 76, -37, -16, 76, -35, -20, + 76, -32, -23, 76, -29, -28, 77, -27, -32, 77, -24, -35, + 74, -73, 72, 74, -72, 71, 74, -72, 70, 74, -72, 69, + 74, -72, 67, 74, -71, 65, 74, -71, 63, 74, -70, 60, + 74, -69, 57, 74, -69, 54, 74, -68, 51, 74, -67, 47, + 74, -65, 43, 74, -64, 39, 75, -63, 35, 75, -61, 31, + 75, -60, 27, 75, -58, 23, 75, -56, 19, 75, -54, 15, + 76, -52, 11, 76, -50, 7, 76, -48, 3, 76, -46, -1, + 76, -44, -5, 77, -41, -10, 77, -39, -14, 77, -37, -18, + 77, -34, -22, 78, -31, -26, 78, -29, -30, 78, -26, -34, + 75, -74, 73, 75, -74, 73, 75, -74, 72, 75, -73, 70, + 75, -73, 69, 76, -73, 67, 76, -72, 65, 76, -72, 62, + 76, -71, 59, 76, -70, 56, 76, -69, 52, 76, -68, 49, + 76, -67, 45, 76, -66, 41, 76, -64, 37, 76, -63, 34, + 76, -61, 29, 77, -60, 25, 77, -58, 21, 77, -56, 17, + 77, -54, 13, 77, -52, 9, 77, -50, 5, 78, -48, 1, + 78, -46, -3, 78, -44, -8, 78, -41, -12, 79, -39, -16, + 79, -37, -19, 79, -34, -24, 79, -31, -28, 80, -29, -31, + 77, -75, 74, 77, -75, 74, 77, -75, 73, 77, -74, 71, + 77, -74, 70, 77, -74, 68, 77, -73, 66, 77, -73, 63, + 77, -72, 60, 77, -71, 57, 77, -70, 54, 77, -69, 50, + 77, -68, 46, 77, -67, 43, 77, -66, 39, 78, -64, 35, + 78, -63, 31, 78, -61, 27, 78, -60, 23, 78, -58, 19, + 78, -56, 14, 78, -54, 10, 79, -52, 6, 79, -50, 2, + 79, -48, -1, 79, -45, -6, 80, -43, -10, 80, -41, -14, + 80, -38, -18, 80, -36, -22, 81, -33, -26, 81, -31, -30, + 78, -76, 75, 78, -76, 75, 78, -76, 74, 78, -76, 73, + 78, -75, 71, 78, -75, 69, 78, -74, 67, 78, -74, 64, + 78, -73, 61, 78, -72, 58, 78, -72, 55, 78, -71, 52, + 78, -70, 48, 79, -68, 44, 79, -67, 41, 79, -66, 37, + 79, -64, 32, 79, -63, 29, 79, -61, 25, 79, -59, 21, + 80, -57, 16, 80, -56, 12, 80, -54, 8, 80, -52, 4, + 80, -49, 0, 80, -47, -4, 81, -45, -8, 81, -43, -12, + 81, -40, -16, 81, -38, -20, 82, -35, -24, 82, -33, -28, + 79, -77, 76, 79, -77, 76, 79, -77, 75, 79, -77, 74, + 79, -76, 72, 79, -76, 70, 79, -76, 68, 79, -75, 65, + 79, -74, 63, 79, -74, 60, 80, -73, 57, 80, -72, 53, + 80, -71, 49, 80, -70, 46, 80, -68, 42, 80, -67, 38, + 80, -66, 34, 80, -64, 30, 80, -63, 26, 81, -61, 22, + 81, -59, 18, 81, -57, 14, 81, -55, 10, 81, -53, 6, + 81, -51, 2, 82, -49, -2, 82, -47, -6, 82, -44, -10, + 82, -42, -14, 83, -39, -18, 83, -37, -22, 83, -34, -26, + 80, -78, 77, 80, -78, 77, 80, -78, 76, 80, -78, 75, + 81, -78, 73, 81, -77, 71, 81, -77, 69, 81, -76, 67, + 81, -76, 64, 81, -75, 61, 81, -74, 58, 81, -73, 55, + 81, -72, 51, 81, -71, 47, 81, -70, 44, 81, -69, 40, + 81, -67, 36, 81, -66, 32, 82, -64, 28, 82, -62, 24, + 82, -60, 20, 82, -59, 16, 82, -57, 12, 82, -55, 8, + 83, -53, 4, 83, -50, -1, 83, -48, -5, 83, -46, -8, + 84, -44, -12, 84, -41, -17, 84, -39, -21, 84, -36, -24, + 82, -79, 78, 82, -79, 78, 82, -79, 77, 82, -79, 76, + 82, -79, 74, 82, -78, 73, 82, -78, 71, 82, -77, 68, + 82, -77, 65, 82, -76, 62, 82, -75, 59, 82, -74, 56, + 82, -73, 52, 82, -72, 49, 82, -71, 45, 82, -70, 41, + 83, -68, 37, 83, -67, 33, 83, -65, 30, 83, -64, 26, + 83, -62, 21, 83, -60, 17, 83, -58, 13, 84, -56, 9, + 84, -54, 5, 84, -52, 1, 84, -50, -3, 84, -48, -7, + 85, -46, -11, 85, -43, -15, 85, -41, -19, 85, -38, -23, + 83, -80, 79, 83, -80, 79, 83, -80, 78, 83, -80, 77, + 83, -80, 75, 83, -79, 74, 83, -79, 72, 83, -78, 69, + 83, -78, 67, 83, -77, 64, 83, -76, 61, 83, -76, 58, + 83, -74, 54, 84, -73, 50, 84, -72, 47, 84, -71, 43, + 84, -70, 39, 84, -68, 35, 84, -67, 31, 84, -65, 27, + 84, -63, 23, 85, -62, 19, 85, -60, 15, 85, -58, 11, + 85, -56, 7, 85, -54, 3, 85, -52, -1, 86, -49, -5, + 86, -47, -9, 86, -45, -13, 86, -42, -17, 87, -40, -21, + 84, -82, 80, 84, -81, 80, 84, -81, 79, 84, -81, 78, + 84, -81, 77, 84, -80, 75, 84, -80, 73, 84, -80, 70, + 84, -79, 68, 84, -78, 65, 85, -78, 62, 85, -77, 59, + 85, -76, 55, 85, -75, 52, 85, -74, 48, 85, -72, 45, + 85, -71, 40, 85, -70, 37, 85, -68, 33, 85, -67, 29, + 86, -65, 25, 86, -63, 21, 86, -61, 17, 86, -59, 13, + 86, -58, 9, 86, -55, 5, 87, -53, 1, 87, -51, -3, + 87, -49, -7, 87, -46, -11, 88, -44, -15, 88, -42, -19, + 85, -83, 81, 85, -83, 81, 85, -82, 80, 85, -82, 79, + 85, -82, 78, 86, -82, 76, 86, -81, 74, 86, -81, 72, + 86, -80, 69, 86, -79, 66, 86, -79, 63, 86, -78, 60, + 86, -77, 57, 86, -76, 53, 86, -75, 50, 86, -74, 46, + 86, -72, 42, 86, -71, 38, 86, -70, 34, 87, -68, 31, + 87, -66, 26, 87, -65, 22, 87, -63, 18, 87, -61, 15, + 87, -59, 11, 88, -57, 6, 88, -55, 2, 88, -53, -2, + 88, -51, -5, 88, -48, -10, 89, -46, -14, 89, -44, -17, + 87, -84, 82, 87, -84, 82, 87, -83, 81, 87, -83, 80, + 87, -83, 79, 87, -83, 77, 87, -82, 75, 87, -82, 73, + 87, -81, 70, 87, -81, 68, 87, -80, 65, 87, -79, 62, + 87, -78, 58, 87, -77, 55, 87, -76, 51, 87, -75, 48, + 87, -74, 43, 88, -72, 40, 88, -71, 36, 88, -69, 32, + 88, -68, 28, 88, -66, 24, 88, -64, 20, 88, -62, 16, + 89, -61, 12, 89, -58, 8, 89, -56, 4, 89, -54, 0, + 89, -52, -4, 90, -50, -8, 90, -48, -12, 90, -45, -16, + 88, -85, 83, 88, -85, 83, 88, -85, 82, 88, -84, 81, + 88, -84, 80, 88, -84, 78, 88, -83, 76, 88, -83, 74, + 88, -82, 71, 88, -82, 69, 88, -81, 66, 88, -80, 63, + 88, -79, 59, 88, -78, 56, 88, -77, 53, 89, -76, 49, + 89, -75, 45, 89, -74, 41, 89, -72, 38, 89, -71, 34, + 89, -69, 29, 89, -67, 26, 89, -66, 22, 90, -64, 18, + 90, -62, 14, 90, -60, 10, 90, -58, 6, 90, -56, 2, + 91, -54, -2, 91, -52, -6, 91, -49, -10, 91, -47, -14, + 4, 19, 7, 4, 20, 4, 5, 21, 0, 5, 23, -5, + 5, 24, -11, 6, 26, -17, 6, 29, -22, 7, 31, -27, + 8, 33, -32, 9, 35, -36, 10, 37, -40, 11, 39, -44, + 12, 41, -48, 13, 43, -51, 14, 45, -55, 15, 47, -58, + 16, 50, -62, 17, 52, -65, 18, 54, -68, 20, 56, -71, + 21, 58, -75, 22, 60, -78, 23, 62, -81, 24, 64, -83, + 25, 66, -86, 27, 68, -89, 28, 70, -92, 29, 72, -95, + 30, 74, -98, 31, 76, -101, 32, 78, -103, 33, 80, -106, + 5, 17, 8, 5, 18, 5, 5, 19, 1, 6, 21, -4, + 6, 22, -10, 6, 24, -15, 7, 26, -21, 8, 29, -26, + 9, 31, -30, 9, 33, -35, 10, 35, -39, 11, 37, -43, + 12, 39, -47, 13, 42, -50, 14, 44, -54, 15, 46, -57, + 17, 48, -61, 18, 50, -64, 19, 52, -68, 20, 55, -71, + 21, 57, -74, 22, 59, -77, 23, 61, -80, 24, 63, -83, + 26, 65, -86, 27, 67, -89, 28, 69, -92, 29, 71, -95, + 30, 73, -97, 31, 75, -100, 32, 77, -103, 34, 79, -106, + 6, 16, 9, 6, 16, 6, 6, 17, 3, 6, 19, -3, + 7, 20, -9, 7, 22, -14, 8, 24, -19, 9, 26, -25, + 9, 29, -29, 10, 31, -33, 11, 33, -38, 12, 35, -42, + 13, 38, -46, 14, 40, -50, 15, 42, -53, 16, 45, -57, + 17, 47, -60, 18, 49, -64, 19, 51, -67, 20, 53, -70, + 21, 56, -74, 23, 58, -77, 24, 60, -80, 25, 62, -83, + 26, 64, -85, 27, 66, -89, 28, 68, -91, 29, 70, -94, + 30, 72, -97, 32, 75, -100, 33, 76, -103, 34, 78, -106, + 7, 14, 10, 7, 15, 7, 7, 15, 4, 7, 17, -2, + 8, 18, -8, 8, 20, -13, 9, 22, -18, 9, 24, -23, + 10, 27, -28, 11, 29, -32, 12, 31, -37, 12, 34, -41, + 13, 36, -45, 14, 39, -49, 15, 41, -52, 16, 43, -56, + 17, 46, -60, 18, 48, -63, 20, 50, -66, 21, 52, -69, + 22, 55, -73, 23, 57, -76, 24, 59, -79, 25, 61, -82, + 26, 63, -85, 27, 66, -88, 28, 68, -91, 29, 70, -94, + 31, 72, -97, 32, 74, -100, 33, 76, -102, 34, 78, -105, + 8, 11, 12, 8, 12, 9, 8, 13, 5, 8, 14, 0, + 9, 16, -6, 9, 18, -11, 10, 20, -17, 10, 22, -22, + 11, 24, -27, 12, 27, -31, 12, 29, -35, 13, 32, -40, + 14, 34, -44, 15, 37, -48, 16, 39, -51, 17, 42, -55, + 18, 44, -59, 19, 47, -62, 20, 49, -66, 21, 51, -69, + 22, 54, -72, 23, 56, -75, 24, 58, -78, 25, 60, -81, + 26, 62, -84, 28, 65, -88, 29, 67, -91, 30, 69, -93, + 31, 71, -96, 32, 73, -99, 33, 75, -102, 34, 77, -105, + 9, 9, 13, 9, 9, 10, 9, 10, 7, 9, 12, 1, + 10, 13, -4, 10, 15, -10, 11, 17, -15, 11, 19, -21, + 12, 22, -25, 12, 24, -30, 13, 27, -34, 14, 29, -38, + 15, 32, -43, 16, 35, -47, 17, 37, -50, 17, 40, -54, + 19, 43, -58, 19, 45, -61, 20, 47, -65, 21, 50, -68, + 23, 52, -72, 24, 55, -75, 25, 57, -78, 26, 59, -81, + 27, 61, -84, 28, 64, -87, 29, 66, -90, 30, 68, -93, + 31, 70, -96, 32, 72, -99, 33, 74, -102, 34, 76, -104, + 10, 6, 15, 10, 7, 12, 10, 7, 9, 11, 9, 3, + 11, 10, -3, 11, 12, -8, 12, 14, -13, 12, 17, -19, + 13, 19, -24, 13, 22, -28, 14, 24, -33, 15, 27, -37, + 16, 30, -41, 16, 33, -45, 17, 35, -49, 18, 38, -53, + 19, 41, -57, 20, 43, -60, 21, 46, -64, 22, 48, -67, + 23, 51, -71, 24, 53, -74, 25, 55, -77, 26, 58, -80, + 27, 60, -83, 28, 62, -86, 29, 65, -89, 30, 67, -92, + 31, 69, -95, 33, 71, -98, 34, 73, -101, 35, 75, -104, + 11, 3, 17, 11, 4, 14, 12, 4, 10, 12, 6, 5, + 12, 7, -1, 12, 9, -6, 13, 11, -11, 13, 14, -17, + 14, 16, -22, 14, 19, -27, 15, 22, -31, 16, 24, -35, + 17, 27, -40, 17, 30, -44, 18, 33, -48, 19, 36, -52, + 20, 39, -56, 21, 41, -59, 22, 44, -63, 23, 46, -66, + 24, 49, -70, 25, 51, -73, 26, 54, -76, 27, 56, -79, + 28, 58, -82, 29, 61, -86, 30, 63, -89, 31, 65, -92, + 32, 68, -94, 33, 70, -98, 34, 72, -100, 35, 74, -103, + 13, 0, 19, 13, 1, 16, 13, 2, 12, 13, 3, 6, + 13, 5, 1, 14, 6, -4, 14, 8, -10, 14, 11, -15, + 15, 14, -20, 15, 16, -25, 16, 19, -29, 17, 22, -34, + 17, 25, -38, 18, 28, -42, 19, 30, -46, 20, 33, -50, + 21, 36, -54, 22, 39, -58, 22, 42, -61, 23, 44, -65, + 24, 47, -69, 25, 50, -72, 26, 52, -75, 27, 54, -78, + 28, 57, -81, 29, 59, -85, 30, 62, -88, 31, 64, -91, + 32, 66, -94, 33, 69, -97, 34, 71, -100, 35, 73, -103, + 14, -3, 21, 14, -2, 17, 14, -1, 14, 14, 0, 8, + 15, 2, 3, 15, 4, -2, 15, 6, -8, 16, 8, -13, + 16, 11, -18, 17, 13, -23, 17, 16, -27, 18, 19, -32, + 19, 22, -37, 19, 25, -41, 20, 28, -45, 21, 31, -49, + 22, 34, -53, 22, 37, -57, 23, 39, -60, 24, 42, -64, + 25, 45, -67, 26, 48, -71, 27, 50, -74, 28, 53, -77, + 29, 55, -80, 30, 58, -84, 31, 60, -87, 32, 62, -90, + 33, 65, -93, 34, 67, -96, 35, 69, -99, 36, 72, -102, + 15, -5, 22, 16, -5, 19, 16, -4, 16, 16, -3, 10, + 16, -1, 5, 16, 1, 0, 17, 3, -6, 17, 5, -11, + 17, 8, -16, 18, 11, -21, 18, 13, -26, 19, 16, -30, + 20, 19, -35, 20, 22, -39, 21, 25, -43, 22, 28, -47, + 22, 31, -51, 23, 34, -55, 24, 37, -59, 25, 40, -62, + 26, 43, -66, 27, 45, -70, 28, 48, -73, 29, 51, -76, + 29, 53, -79, 31, 56, -83, 31, 58, -86, 32, 61, -89, + 33, 63, -92, 34, 66, -95, 35, 68, -98, 36, 70, -101, + 17, -9, 25, 17, -8, 21, 17, -7, 18, 18, -6, 12, + 18, -4, 7, 18, -3, 2, 18, -1, -3, 19, 2, -9, + 19, 4, -14, 19, 7, -19, 20, 10, -23, 20, 13, -28, + 21, 16, -33, 22, 19, -37, 22, 22, -41, 23, 25, -45, + 24, 28, -49, 24, 31, -53, 25, 34, -57, 26, 37, -60, + 27, 40, -64, 28, 43, -68, 29, 45, -71, 29, 48, -75, + 30, 51, -78, 31, 53, -81, 32, 56, -85, 33, 58, -88, + 34, 61, -91, 35, 64, -94, 36, 66, -97, 37, 68, -100, + 19, -11, 26, 19, -10, 23, 19, -10, 20, 19, -8, 14, + 19, -7, 9, 19, -5, 4, 20, -3, -1, 20, -1, -7, + 20, 2, -12, 21, 4, -17, 21, 7, -21, 22, 10, -26, + 22, 13, -31, 23, 16, -35, 23, 19, -39, 24, 22, -43, + 25, 25, -48, 25, 28, -52, 26, 31, -55, 27, 34, -59, + 28, 37, -63, 29, 40, -66, 29, 43, -70, 30, 46, -73, + 31, 48, -77, 32, 51, -80, 33, 54, -83, 34, 56, -87, + 35, 59, -90, 36, 62, -93, 37, 64, -96, 38, 66, -99, + 20, -13, 28, 20, -13, 25, 20, -12, 21, 20, -11, 16, + 21, -10, 11, 21, -8, 6, 21, -6, 1, 21, -4, -5, + 22, -1, -10, 22, 1, -15, 22, 4, -19, 23, 7, -24, + 23, 10, -29, 24, 13, -33, 24, 16, -37, 25, 19, -41, + 26, 23, -46, 26, 26, -50, 27, 29, -54, 28, 32, -57, + 29, 35, -61, 29, 38, -65, 30, 41, -68, 31, 43, -72, + 32, 46, -75, 33, 49, -79, 34, 52, -82, 35, 54, -85, + 35, 57, -88, 36, 60, -92, 37, 62, -95, 38, 65, -98, + 22, -16, 30, 22, -15, 26, 22, -14, 23, 22, -13, 18, + 22, -12, 13, 22, -10, 8, 22, -9, 3, 23, -6, -3, + 23, -4, -8, 23, -1, -12, 24, 1, -17, 24, 4, -22, + 25, 7, -27, 25, 10, -31, 26, 13, -35, 26, 17, -40, + 27, 20, -44, 28, 23, -48, 28, 26, -52, 29, 29, -56, + 30, 32, -60, 30, 35, -63, 31, 38, -67, 32, 41, -70, + 33, 44, -74, 34, 47, -78, 35, 50, -81, 35, 52, -84, + 36, 55, -87, 37, 58, -91, 38, 60, -94, 39, 63, -97, + 23, -18, 31, 23, -17, 28, 23, -17, 24, 23, -16, 20, + 23, -14, 15, 24, -13, 10, 24, -11, 5, 24, -9, -1, + 24, -6, -6, 25, -4, -10, 25, -1, -15, 25, 1, -20, + 26, 5, -25, 26, 8, -29, 27, 11, -34, 27, 14, -38, + 28, 17, -42, 29, 20, -46, 29, 23, -50, 30, 26, -54, + 31, 30, -58, 31, 33, -62, 32, 36, -65, 33, 39, -69, + 34, 41, -72, 35, 45, -76, 35, 47, -79, 36, 50, -83, + 37, 53, -86, 38, 56, -89, 39, 58, -93, 40, 61, -96, + 24, -20, 33, 25, -19, 29, 25, -19, 26, 25, -18, 21, + 25, -17, 17, 25, -15, 12, 25, -13, 7, 25, -11, 1, + 26, -9, -4, 26, -7, -8, 26, -4, -13, 27, -1, -18, + 27, 2, -23, 28, 5, -27, 28, 8, -32, 29, 11, -36, + 29, 14, -40, 30, 18, -45, 30, 21, -48, 31, 24, -52, + 32, 27, -57, 32, 30, -60, 33, 33, -64, 34, 36, -67, + 35, 39, -71, 35, 42, -75, 36, 45, -78, 37, 48, -81, + 38, 50, -85, 39, 53, -88, 40, 56, -91, 40, 59, -94, + 26, -22, 34, 26, -22, 31, 26, -21, 27, 26, -20, 23, + 26, -19, 18, 26, -17, 14, 27, -16, 9, 27, -14, 3, + 27, -11, -2, 27, -9, -6, 28, -6, -11, 28, -4, -16, + 28, -1, -21, 29, 2, -25, 29, 5, -30, 30, 8, -34, + 30, 12, -39, 31, 15, -43, 31, 18, -47, 32, 21, -51, + 33, 24, -55, 33, 27, -59, 34, 30, -62, 35, 33, -66, + 36, 36, -69, 36, 40, -73, 37, 42, -77, 38, 45, -80, + 39, 48, -83, 40, 51, -87, 40, 54, -90, 41, 56, -93, + 27, -24, 35, 27, -23, 32, 28, -23, 29, 28, -22, 25, + 28, -21, 20, 28, -20, 16, 28, -18, 11, 28, -16, 5, + 28, -14, 0, 29, -11, -4, 29, -9, -9, 29, -6, -14, + 30, -3, -19, 30, 0, -23, 31, 3, -28, 31, 6, -32, + 32, 9, -37, 32, 12, -41, 33, 15, -45, 33, 18, -49, + 34, 22, -53, 35, 25, -57, 35, 28, -61, 36, 31, -64, + 36, 34, -68, 37, 37, -72, 38, 40, -75, 39, 43, -78, + 39, 46, -82, 40, 49, -85, 41, 52, -89, 42, 54, -92, + 29, -26, 36, 29, -25, 33, 29, -25, 30, 29, -24, 26, + 29, -23, 22, 29, -22, 17, 29, -20, 13, 30, -18, 7, + 30, -16, 2, 30, -14, -2, 30, -11, -7, 31, -9, -12, + 31, -6, -17, 31, -3, -21, 32, 0, -26, 32, 3, -30, + 33, 6, -35, 33, 9, -39, 34, 12, -43, 34, 16, -47, + 35, 19, -51, 36, 22, -55, 36, 25, -59, 37, 28, -62, + 37, 31, -66, 38, 35, -70, 39, 37, -74, 40, 40, -77, + 40, 43, -80, 41, 46, -84, 42, 49, -87, 43, 52, -90, + 30, -28, 38, 30, -27, 35, 30, -27, 32, 30, -26, 28, + 31, -25, 24, 31, -24, 19, 31, -22, 15, 31, -20, 9, + 31, -18, 4, 31, -16, 0, 32, -14, -5, 32, -11, -10, + 32, -8, -15, 33, -6, -19, 33, -3, -24, 34, 0, -28, + 34, 4, -33, 34, 7, -37, 35, 10, -41, 36, 13, -45, + 36, 16, -49, 37, 19, -53, 37, 23, -57, 38, 26, -61, + 39, 29, -64, 39, 32, -68, 40, 35, -72, 41, 38, -75, + 41, 41, -79, 42, 44, -83, 43, 47, -86, 44, 50, -89, + 32, -30, 39, 32, -29, 36, 32, -29, 33, 32, -28, 29, + 32, -27, 25, 32, -26, 21, 32, -24, 16, 32, -22, 11, + 33, -20, 6, 33, -18, 2, 33, -16, -3, 33, -14, -8, + 34, -11, -13, 34, -8, -17, 34, -5, -22, 35, -2, -26, + 35, 1, -31, 36, 4, -35, 36, 7, -39, 37, 10, -43, + 37, 14, -48, 38, 17, -51, 38, 20, -55, 39, 23, -59, + 40, 26, -63, 40, 29, -67, 41, 32, -70, 42, 35, -74, + 42, 38, -77, 43, 42, -81, 44, 44, -84, 45, 47, -88, + 33, -31, 40, 33, -31, 37, 33, -30, 35, 33, -30, 31, + 33, -29, 27, 34, -27, 23, 34, -26, 18, 34, -24, 13, + 34, -22, 8, 34, -20, 4, 34, -18, -1, 35, -16, -6, + 35, -13, -11, 35, -10, -15, 36, -8, -20, 36, -5, -24, + 37, -1, -29, 37, 2, -33, 37, 5, -37, 38, 8, -41, + 38, 11, -46, 39, 14, -50, 40, 17, -53, 40, 20, -57, + 41, 23, -61, 41, 27, -65, 42, 30, -69, 43, 33, -72, + 43, 36, -76, 44, 39, -79, 45, 42, -83, 45, 45, -86, + 35, -33, 41, 35, -33, 39, 35, -32, 36, 35, -31, 32, + 35, -30, 29, 35, -29, 24, 35, -28, 20, 35, -26, 15, + 35, -25, 10, 36, -23, 6, 36, -20, 1, 36, -18, -4, + 36, -15, -9, 37, -13, -13, 37, -10, -18, 37, -7, -22, + 38, -4, -27, 38, -1, -31, 39, 2, -35, 39, 5, -39, + 40, 8, -44, 40, 12, -48, 41, 15, -52, 41, 18, -55, + 42, 21, -59, 42, 24, -63, 43, 27, -67, 44, 30, -70, + 44, 33, -74, 45, 37, -78, 46, 39, -81, 46, 42, -85, + 36, -35, 42, 36, -34, 40, 36, -34, 37, 36, -33, 34, + 36, -32, 30, 36, -31, 26, 36, -30, 22, 37, -28, 17, + 37, -26, 12, 37, -25, 8, 37, -23, 3, 37, -20, -2, + 38, -18, -7, 38, -15, -11, 38, -12, -16, 39, -10, -20, + 39, -6, -25, 39, -4, -29, 40, -1, -33, 40, 2, -37, + 41, 6, -42, 41, 9, -46, 42, 12, -50, 42, 15, -54, + 43, 18, -57, 43, 22, -62, 44, 25, -65, 45, 28, -69, + 45, 31, -72, 46, 34, -76, 47, 37, -80, 47, 40, -83, + 37, -36, 43, 37, -36, 41, 38, -35, 39, 38, -35, 35, + 38, -34, 32, 38, -33, 28, 38, -32, 24, 38, -30, 19, + 38, -28, 14, 38, -27, 10, 39, -25, 5, 39, -22, 0, + 39, -20, -5, 39, -17, -9, 40, -15, -14, 40, -12, -18, + 40, -9, -23, 41, -6, -27, 41, -3, -31, 41, 0, -35, + 42, 3, -40, 42, 6, -44, 43, 9, -48, 43, 13, -52, + 44, 16, -56, 45, 19, -60, 45, 22, -63, 46, 25, -67, + 46, 28, -71, 47, 31, -75, 48, 34, -78, 48, 37, -81, + 39, -38, 44, 39, -37, 42, 39, -37, 40, 39, -36, 37, + 39, -36, 33, 39, -35, 29, 39, -33, 25, 39, -32, 20, + 40, -30, 16, 40, -28, 11, 40, -27, 7, 40, -24, 2, + 40, -22, -3, 41, -19, -7, 41, -17, -12, 41, -14, -16, + 42, -11, -21, 42, -8, -25, 42, -5, -29, 43, -3, -34, + 43, 1, -38, 44, 4, -42, 44, 7, -46, 45, 10, -50, + 45, 13, -54, 46, 16, -58, 46, 20, -62, 47, 23, -65, + 47, 26, -69, 48, 29, -73, 49, 32, -76, 49, 35, -80, + 40, -39, 45, 40, -39, 43, 40, -39, 41, 40, -38, 38, + 40, -37, 35, 41, -36, 31, 41, -35, 27, 41, -34, 22, + 41, -32, 18, 41, -30, 13, 41, -28, 9, 41, -26, 4, + 42, -24, -1, 42, -22, -5, 42, -19, -10, 43, -17, -14, + 43, -13, -19, 43, -11, -23, 44, -8, -27, 44, -5, -32, + 44, -2, -36, 45, 1, -40, 45, 4, -44, 46, 7, -48, + 46, 10, -52, 47, 14, -56, 47, 17, -60, 48, 20, -64, + 48, 23, -67, 49, 26, -71, 50, 29, -75, 50, 32, -78, + 42, -41, 47, 42, -40, 45, 42, -40, 43, 42, -39, 40, + 42, -39, 36, 42, -38, 33, 42, -37, 29, 42, -35, 24, + 42, -34, 20, 42, -32, 15, 43, -30, 11, 43, -28, 6, + 43, -26, 1, 43, -24, -3, 44, -21, -8, 44, -19, -12, + 44, -16, -17, 44, -13, -21, 45, -10, -25, 45, -7, -30, + 46, -4, -34, 46, -1, -38, 46, 2, -42, 47, 5, -46, + 47, 8, -50, 48, 11, -54, 48, 14, -58, 49, 17, -62, + 49, 20, -65, 50, 24, -69, 51, 27, -73, 51, 30, -77, + 43, -42, 48, 43, -42, 46, 43, -42, 44, 43, -41, 41, + 43, -40, 38, 43, -39, 34, 43, -38, 30, 44, -37, 26, + 44, -36, 21, 44, -34, 17, 44, -32, 13, 44, -30, 8, + 44, -28, 3, 45, -26, -1, 45, -23, -6, 45, -21, -10, + 45, -18, -15, 46, -15, -19, 46, -13, -24, 46, -10, -28, + 47, -6, -32, 47, -4, -36, 48, -1, -40, 48, 2, -44, + 49, 5, -48, 49, 9, -53, 50, 12, -56, 50, 15, -60, + 51, 18, -64, 51, 21, -68, 52, 24, -71, 52, 27, -75, + 44, -44, 49, 44, -43, 47, 44, -43, 45, 45, -42, 42, + 45, -42, 39, 45, -41, 36, 45, -40, 32, 45, -39, 27, + 45, -37, 23, 45, -36, 19, 45, -34, 14, 45, -32, 10, + 46, -30, 5, 46, -28, 1, 46, -25, -4, 46, -23, -8, + 47, -20, -13, 47, -17, -17, 47, -15, -22, 48, -12, -26, + 48, -9, -30, 48, -6, -34, 49, -3, -39, 49, 0, -42, + 50, 3, -46, 50, 6, -51, 51, 9, -54, 51, 12, -58, + 52, 15, -62, 52, 19, -66, 53, 22, -70, 53, 25, -73, + 46, -45, 50, 46, -45, 48, 46, -45, 46, 46, -44, 44, + 46, -43, 41, 46, -43, 37, 46, -42, 34, 46, -40, 29, + 46, -39, 25, 46, -37, 21, 47, -36, 16, 47, -34, 12, + 47, -32, 7, 47, -30, 2, 47, -27, -2, 48, -25, -6, + 48, -22, -11, 48, -20, -15, 49, -17, -20, 49, -14, -24, + 49, -11, -28, 50, -8, -33, 50, -5, -37, 50, -2, -41, + 51, 1, -44, 51, 4, -49, 52, 7, -53, 52, 10, -56, + 53, 13, -60, 53, 16, -64, 54, 19, -68, 54, 22, -71, + 48, -47, 51, 48, -47, 50, 48, -46, 48, 48, -46, 45, + 48, -45, 42, 48, -44, 39, 48, -43, 36, 48, -42, 31, + 48, -41, 27, 48, -40, 23, 48, -38, 19, 48, -36, 14, + 49, -34, 9, 49, -32, 5, 49, -30, 0, 49, -28, -4, + 50, -25, -9, 50, -22, -13, 50, -20, -17, 50, -17, -21, + 51, -14, -26, 51, -11, -30, 52, -8, -34, 52, -5, -38, + 52, -2, -42, 53, 1, -47, 53, 4, -50, 54, 7, -54, + 54, 10, -58, 55, 13, -62, 55, 16, -66, 56, 19, -69, + 49, -48, 52, 49, -48, 51, 49, -48, 49, 49, -47, 47, + 49, -47, 44, 49, -46, 41, 49, -45, 37, 49, -44, 33, + 49, -43, 29, 50, -41, 25, 50, -40, 20, 50, -38, 16, + 50, -36, 11, 50, -34, 7, 50, -32, 2, 51, -29, -2, + 51, -27, -7, 51, -24, -11, 51, -22, -15, 52, -19, -20, + 52, -16, -24, 52, -13, -28, 53, -11, -32, 53, -8, -36, + 54, -5, -40, 54, -1, -45, 54, 1, -49, 55, 4, -52, + 55, 7, -56, 56, 11, -60, 56, 14, -64, 57, 17, -68, + 50, -50, 53, 50, -49, 52, 50, -49, 50, 50, -49, 48, + 50, -48, 45, 50, -47, 42, 51, -46, 39, 51, -45, 34, + 51, -44, 30, 51, -43, 26, 51, -41, 22, 51, -40, 18, + 51, -38, 13, 52, -36, 9, 52, -34, 4, 52, -31, 0, + 52, -29, -5, 52, -26, -9, 53, -24, -13, 53, -21, -18, + 53, -18, -22, 54, -16, -26, 54, -13, -30, 54, -10, -34, + 55, -7, -38, 55, -4, -43, 56, -1, -47, 56, 2, -50, + 56, 5, -54, 57, 8, -58, 57, 11, -62, 58, 14, -66, + 52, -51, 54, 52, -51, 53, 52, -50, 52, 52, -50, 49, + 52, -49, 47, 52, -49, 44, 52, -48, 40, 52, -47, 36, + 52, -46, 32, 52, -44, 28, 52, -43, 24, 52, -41, 20, + 53, -39, 15, 53, -37, 10, 53, -35, 6, 53, -33, 2, + 53, -31, -3, 54, -28, -7, 54, -26, -12, 54, -23, -16, + 55, -20, -20, 55, -18, -24, 55, -15, -29, 56, -12, -33, + 56, -9, -37, 56, -6, -41, 57, -3, -45, 57, 0, -49, + 58, 3, -52, 58, 6, -57, 59, 9, -60, 59, 12, -64, + 53, -52, 56, 53, -52, 54, 53, -52, 53, 53, -51, 50, + 53, -51, 48, 53, -50, 45, 53, -49, 42, 53, -48, 38, + 53, -47, 34, 54, -46, 30, 54, -44, 26, 54, -43, 21, + 54, -41, 17, 54, -39, 12, 54, -37, 8, 55, -35, 4, + 55, -33, -1, 55, -30, -5, 55, -28, -10, 56, -25, -14, + 56, -23, -18, 56, -20, -23, 56, -17, -27, 57, -14, -31, + 57, -12, -35, 58, -8, -39, 58, -6, -43, 58, -3, -47, + 59, 0, -51, 59, 4, -55, 60, 7, -59, 60, 9, -62, + 54, -54, 57, 54, -53, 55, 54, -53, 54, 54, -53, 52, + 54, -52, 49, 54, -51, 46, 55, -51, 43, 55, -50, 39, + 55, -49, 35, 55, -47, 31, 55, -46, 27, 55, -45, 23, + 55, -43, 18, 55, -41, 14, 56, -39, 10, 56, -37, 6, + 56, -35, 1, 56, -32, -3, 57, -30, -8, 57, -27, -12, + 57, -25, -17, 57, -22, -21, 58, -19, -25, 58, -17, -29, + 58, -14, -33, 59, -11, -37, 59, -8, -41, 59, -5, -45, + 60, -2, -49, 60, 1, -53, 61, 4, -57, 61, 7, -60, + 56, -55, 58, 56, -55, 56, 56, -54, 55, 56, -54, 53, + 56, -53, 51, 56, -53, 48, 56, -52, 45, 56, -51, 41, + 56, -50, 37, 56, -49, 33, 56, -48, 29, 56, -46, 25, + 57, -44, 20, 57, -43, 16, 57, -41, 12, 57, -39, 7, + 57, -36, 3, 58, -34, -2, 58, -32, -6, 58, -29, -10, + 58, -27, -15, 59, -24, -19, 59, -21, -23, 59, -19, -27, + 60, -16, -31, 60, -13, -35, 60, -10, -39, 61, -7, -43, + 61, -4, -47, 61, -1, -51, 62, 2, -55, 62, 5, -59, + 57, -56, 59, 57, -56, 58, 57, -56, 56, 57, -55, 54, + 57, -55, 52, 57, -54, 49, 57, -53, 46, 57, -52, 42, + 57, -51, 38, 57, -50, 35, 58, -49, 31, 58, -48, 27, + 58, -46, 22, 58, -44, 18, 58, -42, 13, 58, -40, 9, + 59, -38, 4, 59, -36, 0, 59, -34, -4, 59, -31, -8, + 60, -29, -13, 60, -26, -17, 60, -24, -21, 60, -21, -25, + 61, -18, -29, 61, -15, -34, 61, -12, -37, 62, -10, -41, + 62, -7, -45, 63, -3, -49, 63, -1, -53, 63, 2, -57, + 58, -57, 60, 58, -57, 59, 58, -57, 57, 58, -57, 55, + 58, -56, 53, 58, -56, 50, 59, -55, 47, 59, -54, 44, + 59, -53, 40, 59, -52, 36, 59, -51, 32, 59, -49, 28, + 59, -47, 24, 59, -46, 19, 59, -44, 15, 60, -42, 11, + 60, -40, 6, 60, -38, 2, 60, -36, -2, 61, -33, -6, + 61, -31, -11, 61, -28, -15, 61, -26, -19, 62, -23, -23, + 62, -20, -27, 62, -17, -32, 63, -15, -36, 63, -12, -39, + 63, -9, -43, 64, -6, -48, 64, -3, -51, 65, 0, -55, + 60, -59, 61, 60, -58, 60, 60, -58, 58, 60, -58, 57, + 60, -57, 54, 60, -57, 52, 60, -56, 49, 60, -55, 45, + 60, -54, 42, 60, -53, 38, 60, -52, 34, 60, -51, 30, + 60, -49, 25, 61, -47, 21, 61, -46, 17, 61, -44, 13, + 61, -42, 8, 61, -40, 4, 62, -37, 0, 62, -35, -4, + 62, -33, -9, 62, -30, -13, 63, -28, -17, 63, -25, -21, + 63, -22, -25, 64, -19, -30, 64, -17, -34, 64, -14, -38, + 65, -11, -41, 65, -8, -46, 65, -5, -50, 66, -2, -53, + 61, -60, 62, 61, -60, 61, 61, -59, 60, 61, -59, 58, + 61, -59, 56, 61, -58, 53, 61, -57, 50, 61, -57, 47, + 61, -56, 43, 61, -55, 39, 62, -53, 36, 62, -52, 32, + 62, -50, 27, 62, -49, 23, 62, -47, 19, 62, -45, 15, + 62, -43, 10, 63, -41, 6, 63, -39, 2, 63, -37, -3, + 63, -34, -7, 64, -32, -11, 64, -30, -15, 64, -27, -19, + 64, -24, -23, 65, -22, -28, 65, -19, -32, 65, -16, -36, + 66, -13, -40, 66, -10, -44, 66, -7, -48, 67, -5, -51, + 62, -61, 63, 62, -61, 62, 62, -61, 61, 62, -60, 59, + 62, -60, 57, 62, -59, 54, 62, -59, 52, 63, -58, 48, + 63, -57, 45, 63, -56, 41, 63, -55, 37, 63, -54, 33, + 63, -52, 29, 63, -50, 25, 63, -49, 21, 63, -47, 16, + 64, -45, 12, 64, -43, 8, 64, -41, 3, 64, -39, -1, + 65, -36, -5, 65, -34, -10, 65, -32, -14, 65, -29, -18, + 66, -26, -22, 66, -24, -26, 66, -21, -30, 67, -18, -34, + 67, -15, -38, 67, -12, -42, 68, -10, -46, 68, -7, -50, + 64, -62, 64, 64, -62, 63, 64, -62, 62, 64, -62, 60, + 64, -61, 58, 64, -61, 56, 64, -60, 53, 64, -59, 49, + 64, -58, 46, 64, -57, 42, 64, -56, 39, 64, -55, 35, + 64, -53, 30, 64, -52, 26, 65, -50, 22, 65, -49, 18, + 65, -47, 13, 65, -45, 9, 65, -43, 5, 66, -41, 1, + 66, -38, -4, 66, -36, -8, 66, -33, -12, 67, -31, -16, + 67, -28, -20, 67, -26, -24, 67, -23, -28, 68, -20, -32, + 68, -18, -36, 68, -15, -40, 69, -12, -44, 69, -9, -48, + 65, -64, 65, 65, -63, 64, 65, -63, 63, 65, -63, 61, + 65, -62, 59, 65, -62, 57, 65, -61, 54, 65, -60, 51, + 65, -60, 47, 65, -59, 44, 65, -58, 40, 65, -56, 36, + 66, -55, 32, 66, -53, 28, 66, -52, 24, 66, -50, 20, + 66, -48, 15, 66, -46, 11, 67, -44, 7, 67, -42, 3, + 67, -40, -2, 67, -38, -6, 67, -35, -10, 68, -33, -14, + 68, -30, -18, 68, -28, -22, 69, -25, -26, 69, -22, -30, + 69, -20, -34, 70, -17, -39, 70, -14, -42, 70, -11, -46, + 66, -65, 66, 66, -65, 65, 66, -64, 64, 66, -64, 62, + 66, -64, 60, 66, -63, 58, 66, -63, 56, 66, -62, 52, + 67, -61, 49, 67, -60, 45, 67, -59, 42, 67, -58, 38, + 67, -56, 34, 67, -55, 30, 67, -53, 26, 67, -52, 22, + 67, -50, 17, 68, -48, 13, 68, -46, 9, 68, -44, 5, + 68, -42, 0, 68, -39, -4, 69, -37, -8, 69, -35, -12, + 69, -32, -16, 70, -30, -21, 70, -27, -25, 70, -24, -28, + 70, -22, -32, 71, -19, -37, 71, -16, -41, 71, -13, -44, + 68, -66, 67, 68, -66, 66, 68, -66, 65, 68, -65, 64, + 68, -65, 62, 68, -64, 59, 68, -64, 57, 68, -63, 54, + 68, -62, 50, 68, -61, 47, 68, -60, 43, 68, -59, 40, + 68, -58, 35, 68, -56, 31, 68, -55, 27, 69, -53, 23, + 69, -51, 19, 69, -50, 15, 69, -48, 11, 69, -46, 6, + 69, -43, 2, 70, -41, -2, 70, -39, -6, 70, -37, -10, + 70, -34, -14, 71, -32, -19, 71, -29, -23, 71, -26, -27, + 72, -24, -31, 72, -21, -35, 72, -18, -39, 73, -15, -42, + 69, -67, 68, 69, -67, 67, 69, -67, 66, 69, -66, 65, + 69, -66, 63, 69, -66, 61, 69, -65, 58, 69, -64, 55, + 69, -64, 52, 69, -63, 48, 69, -62, 45, 69, -61, 41, + 69, -59, 37, 70, -58, 33, 70, -56, 29, 70, -55, 25, + 70, -53, 20, 70, -51, 16, 70, -49, 12, 70, -47, 8, + 71, -45, 4, 71, -43, 0, 71, -41, -4, 71, -38, -8, + 72, -36, -12, 72, -33, -17, 72, -31, -21, 72, -28, -25, + 73, -26, -29, 73, -23, -33, 73, -20, -37, 74, -18, -41, + 70, -68, 69, 70, -68, 68, 70, -68, 67, 70, -68, 66, + 70, -67, 64, 70, -67, 62, 70, -66, 59, 70, -66, 56, + 70, -65, 53, 70, -64, 50, 71, -63, 46, 71, -62, 43, + 71, -61, 38, 71, -59, 35, 71, -58, 31, 71, -56, 27, + 71, -54, 22, 71, -53, 18, 72, -51, 14, 72, -49, 10, + 72, -47, 5, 72, -45, 1, 72, -43, -3, 73, -40, -7, + 73, -38, -11, 73, -35, -15, 73, -33, -19, 74, -30, -23, + 74, -28, -27, 74, -25, -31, 75, -22, -35, 75, -20, -39, + 71, -69, 70, 71, -69, 69, 71, -69, 68, 71, -69, 67, + 71, -68, 65, 71, -68, 63, 72, -67, 61, 72, -67, 58, + 72, -66, 55, 72, -65, 51, 72, -64, 48, 72, -63, 44, + 72, -62, 40, 72, -61, 36, 72, -59, 32, 72, -58, 28, + 72, -56, 24, 73, -54, 20, 73, -53, 16, 73, -51, 12, + 73, -48, 7, 73, -46, 3, 74, -44, -1, 74, -42, -5, + 74, -40, -9, 74, -37, -13, 75, -35, -17, 75, -32, -21, + 75, -30, -25, 75, -27, -29, 76, -24, -33, 76, -22, -37, + 73, -71, 71, 73, -70, 70, 73, -70, 69, 73, -70, 68, + 73, -70, 66, 73, -69, 64, 73, -69, 62, 73, -68, 59, + 73, -67, 56, 73, -66, 53, 73, -66, 49, 73, -65, 46, + 73, -63, 42, 73, -62, 38, 73, -61, 34, 74, -59, 30, + 74, -57, 26, 74, -56, 22, 74, -54, 18, 74, -52, 13, + 74, -50, 9, 75, -48, 5, 75, -46, 1, 75, -44, -3, + 75, -42, -7, 75, -39, -12, 76, -37, -16, 76, -34, -19, + 76, -32, -23, 77, -29, -28, 77, -26, -32, 77, -24, -35, + 74, -72, 72, 74, -72, 71, 74, -71, 71, 74, -71, 69, + 74, -71, 67, 74, -70, 65, 74, -70, 63, 74, -69, 60, + 74, -69, 57, 74, -68, 54, 74, -67, 51, 74, -66, 47, + 74, -65, 43, 75, -63, 39, 75, -62, 36, 75, -61, 32, + 75, -59, 27, 75, -57, 23, 75, -56, 19, 75, -54, 15, + 76, -52, 11, 76, -50, 7, 76, -48, 3, 76, -46, -1, + 76, -43, -5, 77, -41, -10, 77, -38, -14, 77, -36, -18, + 77, -34, -22, 78, -31, -26, 78, -28, -30, 78, -26, -34, + 75, -73, 73, 76, -73, 73, 76, -73, 72, 76, -73, 70, + 76, -72, 69, 76, -72, 67, 76, -71, 65, 76, -71, 62, + 76, -70, 59, 76, -69, 56, 76, -68, 53, 76, -67, 49, + 76, -66, 45, 76, -65, 41, 76, -64, 38, 76, -62, 34, + 77, -61, 29, 77, -59, 25, 77, -57, 21, 77, -56, 17, + 77, -54, 13, 77, -52, 9, 78, -50, 5, 78, -48, 1, + 78, -46, -3, 78, -43, -8, 78, -41, -11, 79, -38, -15, + 79, -36, -19, 79, -33, -24, 79, -31, -28, 80, -28, -31, + 77, -74, 74, 77, -74, 74, 77, -74, 73, 77, -74, 72, + 77, -73, 70, 77, -73, 68, 77, -73, 66, 77, -72, 63, + 77, -71, 60, 77, -70, 57, 77, -70, 54, 77, -69, 51, + 77, -68, 47, 77, -66, 43, 78, -65, 39, 78, -64, 35, + 78, -62, 31, 78, -61, 27, 78, -59, 23, 78, -57, 19, + 78, -55, 15, 79, -53, 11, 79, -51, 7, 79, -49, 3, + 79, -47, -1, 79, -45, -6, 80, -43, -10, 80, -40, -14, + 80, -38, -18, 80, -35, -22, 81, -33, -26, 81, -30, -30, + 78, -75, 76, 78, -75, 75, 78, -75, 74, 78, -75, 73, + 78, -75, 71, 78, -74, 69, 78, -74, 67, 78, -73, 64, + 78, -72, 62, 78, -72, 59, 78, -71, 55, 78, -70, 52, + 79, -69, 48, 79, -68, 44, 79, -66, 41, 79, -65, 37, + 79, -64, 33, 79, -62, 29, 79, -60, 25, 79, -59, 21, + 80, -57, 16, 80, -55, 12, 80, -53, 8, 80, -51, 4, + 80, -49, 0, 81, -46, -4, 81, -44, -8, 81, -42, -12, + 81, -40, -16, 82, -37, -20, 82, -35, -24, 82, -32, -28, + 79, -76, 77, 79, -76, 76, 79, -76, 75, 79, -76, 74, + 79, -76, 72, 79, -75, 70, 79, -75, 68, 79, -74, 66, + 80, -74, 63, 80, -73, 60, 80, -72, 57, 80, -71, 53, + 80, -70, 49, 80, -69, 46, 80, -68, 42, 80, -66, 38, + 80, -65, 34, 80, -63, 30, 80, -62, 26, 81, -60, 22, + 81, -58, 18, 81, -56, 14, 81, -55, 10, 81, -53, 6, + 82, -51, 2, 82, -48, -2, 82, -46, -6, 82, -44, -10, + 82, -41, -14, 83, -39, -18, 83, -36, -22, 83, -34, -26, + 81, -78, 78, 81, -77, 77, 81, -77, 76, 81, -77, 75, + 81, -77, 73, 81, -76, 72, 81, -76, 69, 81, -75, 67, + 81, -75, 64, 81, -74, 61, 81, -73, 58, 81, -72, 55, + 81, -71, 51, 81, -70, 47, 81, -69, 44, 81, -68, 40, + 81, -66, 36, 82, -65, 32, 82, -63, 28, 82, -62, 24, + 82, -60, 20, 82, -58, 16, 82, -56, 12, 83, -54, 8, + 83, -52, 4, 83, -50, -1, 83, -48, -4, 83, -46, -8, + 84, -43, -12, 84, -41, -17, 84, -38, -20, 84, -36, -24, + 82, -79, 79, 82, -79, 78, 82, -78, 77, 82, -78, 76, + 82, -78, 74, 82, -78, 73, 82, -77, 71, 82, -77, 68, + 82, -76, 65, 82, -75, 63, 82, -75, 59, 82, -74, 56, + 82, -73, 52, 82, -72, 49, 82, -70, 45, 83, -69, 42, + 83, -68, 37, 83, -66, 34, 83, -65, 30, 83, -63, 26, + 83, -61, 21, 83, -60, 17, 84, -58, 13, 84, -56, 10, + 84, -54, 6, 84, -52, 1, 84, -49, -3, 85, -47, -7, + 85, -45, -10, 85, -42, -15, 85, -40, -19, 86, -38, -22, + 83, -80, 80, 83, -80, 79, 83, -80, 78, 83, -79, 77, + 83, -79, 76, 83, -79, 74, 83, -78, 72, 83, -78, 69, + 83, -77, 67, 83, -76, 64, 83, -76, 61, 83, -75, 58, + 83, -74, 54, 84, -73, 50, 84, -72, 47, 84, -70, 43, + 84, -69, 39, 84, -68, 35, 84, -66, 31, 84, -65, 27, + 84, -63, 23, 85, -61, 19, 85, -59, 15, 85, -57, 11, + 85, -55, 7, 85, -53, 3, 86, -51, -1, 86, -49, -5, + 86, -47, -9, 86, -44, -13, 86, -42, -17, 87, -40, -21, + 84, -81, 80, 84, -81, 80, 84, -81, 79, 84, -80, 78, + 84, -80, 77, 84, -80, 75, 84, -79, 73, 84, -79, 70, + 84, -78, 68, 85, -78, 65, 85, -77, 62, 85, -76, 59, + 85, -75, 55, 85, -74, 52, 85, -73, 48, 85, -72, 45, + 85, -70, 40, 85, -69, 37, 85, -68, 33, 85, -66, 29, + 86, -64, 25, 86, -63, 21, 86, -61, 17, 86, -59, 13, + 86, -57, 9, 87, -55, 5, 87, -53, 1, 87, -51, -3, + 87, -48, -7, 87, -46, -11, 88, -44, -15, 88, -41, -19, + 86, -82, 81, 86, -82, 81, 86, -82, 80, 86, -82, 79, + 86, -81, 78, 86, -81, 76, 86, -81, 74, 86, -80, 72, + 86, -79, 69, 86, -79, 66, 86, -78, 63, 86, -77, 60, + 86, -76, 57, 86, -75, 53, 86, -74, 50, 86, -73, 46, + 86, -72, 42, 86, -70, 38, 87, -69, 34, 87, -67, 31, + 87, -66, 26, 87, -64, 22, 87, -62, 19, 87, -60, 15, + 87, -59, 11, 88, -56, 6, 88, -54, 2, 88, -52, -1, + 88, -50, -5, 89, -48, -10, 89, -45, -13, 89, -43, -17, + 87, -83, 82, 87, -83, 82, 87, -83, 81, 87, -83, 80, + 87, -82, 79, 87, -82, 77, 87, -82, 75, 87, -81, 73, + 87, -81, 70, 87, -80, 68, 87, -79, 65, 87, -78, 62, + 87, -77, 58, 87, -77, 55, 87, -75, 51, 87, -74, 48, + 88, -73, 44, 88, -72, 40, 88, -70, 36, 88, -69, 32, + 88, -67, 28, 88, -65, 24, 88, -64, 20, 89, -62, 16, + 89, -60, 12, 89, -58, 8, 89, -56, 4, 89, -54, 0, + 89, -52, -4, 90, -49, -8, 90, -47, -12, 90, -45, -16, + 88, -84, 83, 88, -84, 83, 88, -84, 82, 88, -84, 81, + 88, -83, 80, 88, -83, 78, 88, -83, 76, 88, -82, 74, + 88, -82, 72, 88, -81, 69, 88, -80, 66, 88, -80, 63, + 88, -79, 59, 88, -78, 56, 89, -77, 53, 89, -76, 49, + 89, -74, 45, 89, -73, 41, 89, -72, 38, 89, -70, 34, + 89, -68, 30, 89, -67, 26, 90, -65, 22, 90, -63, 18, + 90, -62, 14, 90, -60, 10, 90, -58, 6, 90, -56, 2, + 91, -53, -2, 91, -51, -6, 91, -49, -10, 91, -47, -14, + 6, 24, 9, 6, 25, 6, 6, 26, 3, 7, 27, -3, + 7, 28, -9, 7, 29, -14, 8, 31, -19, 9, 33, -24, + 9, 34, -29, 10, 36, -33, 11, 38, -37, 12, 40, -41, + 13, 42, -46, 14, 44, -49, 15, 46, -53, 16, 48, -57, + 17, 50, -60, 18, 52, -64, 19, 54, -67, 20, 56, -70, + 21, 58, -73, 23, 60, -77, 24, 62, -80, 25, 64, -82, + 26, 66, -85, 27, 68, -89, 28, 70, -91, 29, 72, -94, + 30, 74, -97, 32, 76, -100, 33, 78, -103, 34, 80, -105, + 7, 22, 10, 7, 23, 7, 7, 23, 4, 7, 25, -2, + 8, 26, -7, 8, 27, -13, 9, 29, -18, 9, 30, -23, + 10, 32, -28, 11, 34, -32, 12, 36, -36, 12, 38, -41, + 13, 40, -45, 14, 42, -49, 15, 44, -52, 16, 46, -56, + 17, 49, -60, 19, 51, -63, 20, 53, -66, 21, 55, -69, + 22, 57, -73, 23, 59, -76, 24, 61, -79, 25, 63, -82, + 26, 65, -85, 27, 67, -88, 28, 69, -91, 29, 71, -94, + 31, 73, -97, 32, 75, -100, 33, 77, -102, 34, 79, -105, + 7, 20, 12, 8, 21, 9, 8, 21, 5, 8, 22, -1, + 8, 23, -6, 9, 25, -12, 9, 26, -17, 10, 28, -22, + 11, 30, -27, 11, 32, -31, 12, 34, -36, 13, 36, -40, + 14, 39, -44, 15, 41, -48, 16, 43, -52, 17, 45, -55, + 18, 48, -59, 19, 50, -62, 20, 52, -66, 21, 54, -69, + 22, 56, -72, 23, 58, -76, 24, 60, -79, 25, 62, -82, + 26, 64, -85, 28, 67, -88, 29, 69, -91, 30, 71, -93, + 31, 73, -96, 32, 75, -99, 33, 77, -102, 34, 79, -105, + 8, 18, 13, 8, 18, 10, 9, 19, 6, 9, 20, 1, + 9, 21, -5, 10, 23, -10, 10, 24, -16, 11, 27, -21, + 11, 28, -26, 12, 31, -30, 13, 33, -35, 14, 35, -39, + 14, 37, -43, 15, 39, -47, 16, 42, -51, 17, 44, -54, + 18, 46, -58, 19, 49, -62, 20, 51, -65, 21, 53, -68, + 22, 55, -72, 23, 57, -75, 24, 59, -78, 26, 62, -81, + 27, 64, -84, 28, 66, -87, 29, 68, -90, 30, 70, -93, + 31, 72, -96, 32, 74, -99, 33, 76, -102, 34, 78, -105, + 9, 16, 14, 9, 16, 11, 9, 17, 8, 10, 18, 2, + 10, 19, -4, 10, 21, -9, 11, 22, -14, 11, 24, -20, + 12, 26, -25, 13, 29, -29, 13, 31, -34, 14, 33, -38, + 15, 36, -42, 16, 38, -46, 17, 40, -50, 18, 42, -54, + 19, 45, -58, 20, 47, -61, 21, 49, -64, 22, 52, -68, + 23, 54, -71, 24, 56, -74, 25, 58, -78, 26, 61, -81, + 27, 63, -84, 28, 65, -87, 29, 67, -90, 30, 69, -93, + 31, 71, -96, 32, 73, -99, 33, 75, -101, 34, 77, -104, + 10, 13, 16, 10, 14, 12, 10, 14, 9, 11, 15, 3, + 11, 17, -2, 11, 18, -8, 12, 20, -13, 12, 22, -19, + 13, 24, -23, 14, 26, -28, 14, 29, -32, 15, 31, -37, + 16, 34, -41, 17, 36, -45, 17, 38, -49, 18, 41, -53, + 19, 43, -57, 20, 46, -60, 21, 48, -64, 22, 50, -67, + 23, 53, -71, 24, 55, -74, 25, 57, -77, 26, 59, -80, + 27, 62, -83, 28, 64, -86, 29, 66, -89, 30, 68, -92, + 32, 70, -95, 33, 72, -98, 34, 74, -101, 35, 76, -104, + 11, 10, 17, 11, 11, 14, 12, 12, 11, 12, 13, 5, + 12, 14, -1, 12, 16, -6, 13, 17, -11, 13, 20, -17, + 14, 22, -22, 14, 24, -26, 15, 26, -31, 16, 29, -35, + 17, 31, -40, 17, 34, -44, 18, 36, -48, 19, 39, -51, + 20, 41, -56, 21, 44, -59, 22, 46, -63, 23, 49, -66, + 24, 51, -70, 25, 54, -73, 26, 56, -76, 27, 58, -79, + 28, 60, -82, 29, 63, -86, 30, 65, -89, 31, 67, -92, + 32, 69, -94, 33, 71, -98, 34, 74, -100, 35, 76, -103, + 13, 7, 19, 13, 8, 16, 13, 9, 12, 13, 10, 6, + 13, 11, 1, 13, 13, -4, 14, 15, -10, 14, 17, -15, + 15, 19, -20, 15, 21, -25, 16, 24, -29, 17, 26, -34, + 17, 29, -38, 18, 32, -43, 19, 34, -46, 20, 37, -50, + 21, 39, -54, 21, 42, -58, 22, 44, -62, 23, 47, -65, + 24, 50, -69, 25, 52, -72, 26, 54, -75, 27, 57, -78, + 28, 59, -81, 29, 61, -85, 30, 64, -88, 31, 66, -91, + 32, 68, -94, 33, 70, -97, 34, 72, -100, 35, 74, -103, + 14, 5, 20, 14, 5, 17, 14, 6, 14, 14, 7, 8, + 14, 8, 3, 15, 10, -3, 15, 12, -8, 15, 14, -14, + 16, 16, -19, 16, 19, -23, 17, 21, -28, 18, 24, -32, + 18, 27, -37, 19, 29, -41, 20, 32, -45, 21, 34, -49, + 21, 37, -53, 22, 40, -57, 23, 42, -60, 24, 45, -64, + 25, 48, -68, 26, 50, -71, 27, 53, -74, 28, 55, -77, + 29, 57, -81, 30, 60, -84, 31, 62, -87, 32, 64, -90, + 33, 67, -93, 34, 69, -96, 35, 71, -99, 36, 73, -102, + 15, 2, 22, 15, 3, 19, 15, 3, 15, 15, 4, 10, + 16, 6, 4, 16, 7, -1, 16, 9, -6, 17, 11, -12, + 17, 14, -17, 18, 16, -22, 18, 18, -26, 19, 21, -31, + 19, 24, -35, 20, 27, -40, 21, 29, -44, 21, 32, -48, + 22, 35, -52, 23, 38, -56, 24, 40, -59, 25, 43, -63, + 26, 46, -67, 27, 48, -70, 27, 51, -73, 28, 53, -76, + 29, 56, -80, 30, 58, -83, 31, 60, -86, 32, 63, -89, + 33, 65, -92, 34, 68, -95, 35, 70, -98, 36, 72, -101, + 16, -1, 24, 16, 0, 21, 17, 0, 17, 17, 2, 11, + 17, 3, 6, 17, 4, 1, 17, 6, -4, 18, 8, -10, + 18, 11, -15, 19, 13, -20, 19, 16, -24, 20, 18, -29, + 20, 21, -34, 21, 24, -38, 22, 27, -42, 22, 29, -46, + 23, 33, -50, 24, 35, -54, 25, 38, -58, 25, 41, -61, + 26, 44, -65, 27, 46, -69, 28, 49, -72, 29, 51, -75, + 30, 54, -79, 31, 56, -82, 32, 59, -85, 33, 61, -88, + 34, 63, -91, 35, 66, -95, 36, 68, -98, 37, 71, -100, + 18, -4, 26, 18, -4, 23, 18, -3, 19, 18, -2, 14, + 19, -1, 8, 19, 1, 3, 19, 3, -2, 19, 5, -8, + 20, 7, -13, 20, 10, -17, 21, 12, -22, 21, 15, -27, + 22, 18, -32, 22, 21, -36, 23, 23, -40, 24, 26, -44, + 24, 29, -48, 25, 32, -52, 26, 35, -56, 27, 38, -60, + 27, 41, -64, 28, 43, -67, 29, 46, -70, 30, 49, -74, + 31, 51, -77, 32, 54, -81, 33, 56, -84, 34, 59, -87, + 34, 61, -90, 36, 64, -93, 36, 66, -96, 37, 69, -99, + 19, -7, 27, 20, -6, 24, 20, -6, 21, 20, -4, 15, + 20, -3, 10, 20, -2, 5, 20, 0, 0, 21, 2, -6, + 21, 4, -11, 21, 7, -15, 22, 9, -20, 22, 12, -25, + 23, 15, -30, 23, 18, -34, 24, 21, -38, 25, 24, -42, + 25, 27, -47, 26, 30, -51, 27, 32, -54, 27, 35, -58, + 28, 38, -62, 29, 41, -66, 30, 44, -69, 31, 46, -73, + 32, 49, -76, 32, 52, -80, 33, 54, -83, 34, 57, -86, + 35, 59, -89, 36, 62, -92, 37, 64, -95, 38, 67, -98, + 21, -9, 29, 21, -9, 26, 21, -8, 22, 21, -7, 17, + 21, -6, 12, 21, -4, 7, 22, -3, 2, 22, 0, -4, + 22, 2, -9, 23, 4, -13, 23, 7, -18, 23, 9, -23, + 24, 12, -28, 25, 15, -32, 25, 18, -36, 26, 21, -41, + 26, 24, -45, 27, 27, -49, 28, 30, -53, 28, 33, -57, + 29, 36, -61, 30, 39, -64, 31, 41, -68, 32, 44, -71, + 32, 47, -75, 33, 50, -78, 34, 52, -82, 35, 55, -85, + 36, 57, -88, 37, 60, -91, 38, 63, -94, 39, 65, -97, + 22, -12, 31, 22, -11, 27, 22, -11, 24, 22, -10, 19, + 23, -8, 14, 23, -7, 9, 23, -5, 4, 23, -3, -2, + 24, -1, -7, 24, 1, -12, 24, 4, -16, 25, 6, -21, + 25, 10, -26, 26, 12, -30, 26, 15, -35, 27, 18, -39, + 27, 21, -43, 28, 24, -47, 29, 27, -51, 29, 30, -55, + 30, 33, -59, 31, 36, -63, 32, 39, -66, 32, 42, -70, + 33, 44, -73, 34, 47, -77, 35, 50, -80, 36, 53, -83, + 37, 55, -87, 38, 58, -90, 38, 61, -93, 39, 63, -96, + 24, -14, 32, 24, -13, 29, 24, -13, 25, 24, -12, 20, + 24, -11, 16, 24, -9, 11, 24, -8, 6, 25, -6, 0, + 25, -4, -5, 25, -1, -10, 26, 1, -14, 26, 4, -19, + 26, 7, -24, 27, 10, -28, 27, 12, -33, 28, 15, -37, + 29, 19, -42, 29, 22, -46, 30, 25, -49, 30, 27, -53, + 31, 31, -58, 32, 34, -61, 33, 36, -65, 33, 39, -68, + 34, 42, -72, 35, 45, -76, 36, 48, -79, 36, 51, -82, + 37, 53, -85, 38, 56, -89, 39, 59, -92, 40, 61, -95, + 25, -16, 33, 25, -16, 30, 25, -15, 27, 25, -14, 22, + 25, -13, 17, 26, -12, 13, 26, -10, 8, 26, -8, 2, + 26, -6, -3, 27, -4, -8, 27, -1, -12, 27, 1, -17, + 28, 4, -22, 28, 7, -26, 29, 10, -31, 29, 13, -35, + 30, 16, -40, 30, 19, -44, 31, 22, -48, 31, 25, -52, + 32, 28, -56, 33, 31, -60, 33, 34, -63, 34, 37, -67, + 35, 40, -70, 36, 43, -74, 37, 46, -77, 37, 48, -81, + 38, 51, -84, 39, 54, -88, 40, 56, -91, 41, 59, -94, + 26, -18, 35, 27, -18, 31, 27, -17, 28, 27, -16, 24, + 27, -15, 19, 27, -14, 14, 27, -13, 10, 27, -11, 4, + 28, -9, -1, 28, -6, -6, 28, -4, -10, 29, -2, -15, + 29, 1, -20, 29, 4, -25, 30, 7, -29, 30, 10, -33, + 31, 13, -38, 31, 16, -42, 32, 19, -46, 33, 22, -50, + 33, 25, -54, 34, 28, -58, 34, 31, -62, 35, 34, -65, + 36, 37, -69, 37, 40, -73, 37, 43, -76, 38, 46, -79, + 39, 49, -83, 40, 52, -86, 41, 54, -90, 41, 57, -93, + 28, -21, 36, 28, -20, 33, 28, -20, 30, 28, -19, 25, + 28, -18, 21, 28, -16, 16, 28, -15, 11, 29, -13, 6, + 29, -11, 1, 29, -9, -4, 29, -7, -8, 30, -4, -13, + 30, -1, -18, 31, 2, -23, 31, 4, -27, 31, 7, -31, + 32, 11, -36, 33, 13, -40, 33, 16, -44, 34, 19, -48, + 34, 23, -52, 35, 26, -56, 36, 29, -60, 36, 32, -64, + 37, 35, -67, 38, 38, -71, 38, 41, -75, 39, 43, -78, + 40, 46, -81, 41, 49, -85, 41, 52, -88, 42, 55, -91, + 29, -23, 37, 29, -22, 34, 29, -22, 31, 29, -21, 27, + 30, -20, 23, 30, -19, 18, 30, -17, 13, 30, -15, 8, + 30, -13, 3, 31, -11, -2, 31, -9, -6, 31, -7, -11, + 31, -4, -16, 32, -1, -21, 32, 2, -25, 33, 5, -29, + 33, 8, -34, 34, 11, -38, 34, 14, -42, 35, 17, -46, + 35, 20, -51, 36, 23, -54, 37, 26, -58, 37, 29, -62, + 38, 32, -66, 39, 35, -70, 39, 38, -73, 40, 41, -76, + 41, 44, -80, 42, 47, -84, 42, 50, -87, 43, 52, -90, + 31, -25, 38, 31, -24, 35, 31, -24, 32, 31, -23, 28, + 31, -22, 24, 31, -21, 20, 31, -19, 15, 31, -18, 10, + 32, -16, 5, 32, -14, 0, 32, -11, -4, 32, -9, -9, + 33, -6, -14, 33, -4, -19, 34, -1, -23, 34, 2, -27, + 34, 5, -32, 35, 8, -36, 35, 11, -40, 36, 14, -44, + 36, 18, -49, 37, 21, -53, 38, 24, -56, 38, 26, -60, + 39, 29, -64, 40, 33, -68, 40, 36, -71, 41, 39, -75, + 42, 41, -78, 42, 45, -82, 43, 47, -85, 44, 50, -89, + 32, -26, 39, 32, -26, 37, 32, -26, 34, 32, -25, 30, + 32, -24, 26, 33, -23, 22, 33, -21, 17, 33, -20, 12, + 33, -18, 7, 33, -16, 2, 33, -14, -2, 34, -11, -7, + 34, -9, -12, 34, -6, -17, 35, -3, -21, 35, -1, -25, + 36, 3, -30, 36, 6, -34, 37, 9, -39, 37, 11, -43, + 38, 15, -47, 38, 18, -51, 39, 21, -55, 39, 24, -58, + 40, 27, -62, 41, 30, -66, 41, 33, -70, 42, 36, -73, + 43, 39, -77, 43, 42, -81, 44, 45, -84, 45, 48, -87, + 34, -28, 40, 34, -28, 38, 34, -27, 35, 34, -27, 31, + 34, -26, 28, 34, -25, 23, 34, -23, 19, 34, -22, 14, + 34, -20, 9, 35, -18, 4, 35, -16, 0, 35, -14, -5, + 35, -11, -10, 36, -9, -15, 36, -6, -19, 36, -3, -23, + 37, 0, -28, 37, 3, -32, 38, 6, -37, 38, 9, -41, + 39, 12, -45, 39, 15, -49, 40, 18, -53, 40, 21, -57, + 41, 24, -60, 42, 28, -65, 42, 31, -68, 43, 34, -72, + 44, 36, -75, 44, 40, -79, 45, 42, -82, 46, 45, -86, + 35, -30, 41, 35, -30, 39, 35, -29, 37, 35, -29, 33, + 35, -28, 29, 35, -27, 25, 35, -25, 21, 36, -24, 15, + 36, -22, 11, 36, -20, 6, 36, -18, 2, 36, -16, -3, + 37, -13, -8, 37, -11, -13, 37, -8, -17, 38, -6, -21, + 38, -2, -26, 39, 0, -31, 39, 3, -35, 39, 6, -39, + 40, 10, -43, 40, 13, -47, 41, 16, -51, 41, 19, -55, + 42, 22, -59, 43, 25, -63, 43, 28, -66, 44, 31, -70, + 45, 34, -74, 45, 37, -77, 46, 40, -81, 47, 43, -84, + 36, -32, 43, 36, -31, 40, 36, -31, 38, 37, -30, 34, + 37, -30, 31, 37, -29, 27, 37, -27, 22, 37, -26, 17, + 37, -24, 13, 37, -22, 8, 38, -20, 4, 38, -18, -1, + 38, -16, -6, 38, -13, -11, 39, -11, -15, 39, -8, -20, + 39, -5, -24, 40, -2, -29, 40, 1, -33, 41, 4, -37, + 41, 7, -41, 42, 10, -45, 42, 13, -49, 43, 16, -53, + 43, 19, -57, 44, 22, -61, 44, 25, -65, 45, 28, -68, + 46, 31, -72, 46, 35, -76, 47, 38, -79, 48, 40, -83, + 38, -34, 44, 38, -33, 41, 38, -33, 39, 38, -32, 36, + 38, -31, 32, 38, -30, 28, 38, -29, 24, 38, -28, 19, + 38, -26, 15, 39, -24, 10, 39, -22, 5, 39, -20, 1, + 39, -18, -4, 40, -15, -9, 40, -13, -13, 40, -10, -18, + 41, -7, -22, 41, -5, -27, 41, -2, -31, 42, 1, -35, + 42, 5, -40, 43, 7, -44, 43, 10, -47, 44, 13, -51, + 44, 16, -55, 45, 20, -59, 45, 23, -63, 46, 26, -67, + 47, 29, -70, 47, 32, -74, 48, 35, -78, 48, 38, -81, + 39, -35, 45, 39, -35, 43, 39, -35, 40, 39, -34, 37, + 39, -33, 34, 39, -32, 30, 40, -31, 26, 40, -30, 21, + 40, -28, 16, 40, -26, 12, 40, -25, 7, 40, -23, 3, + 41, -20, -2, 41, -18, -7, 41, -15, -11, 42, -13, -16, + 42, -10, -20, 42, -7, -25, 43, -4, -29, 43, -1, -33, + 43, 2, -38, 44, 5, -42, 44, 8, -46, 45, 11, -50, + 45, 14, -53, 46, 17, -58, 46, 20, -61, 47, 23, -65, + 48, 26, -69, 48, 30, -73, 49, 33, -76, 49, 35, -79, + 41, -37, 46, 41, -37, 44, 41, -36, 42, 41, -36, 39, + 41, -35, 35, 41, -34, 32, 41, -33, 27, 41, -31, 23, + 41, -30, 18, 41, -28, 14, 42, -27, 9, 42, -25, 5, + 42, -22, 0, 42, -20, -5, 43, -17, -9, 43, -15, -14, + 43, -12, -19, 43, -9, -23, 44, -7, -27, 44, -4, -31, + 45, 0, -36, 45, 2, -40, 46, 5, -44, 46, 8, -48, + 46, 11, -52, 47, 15, -56, 48, 18, -60, 48, 21, -63, + 49, 24, -67, 49, 27, -71, 50, 30, -74, 50, 33, -78, + 42, -38, 47, 42, -38, 45, 42, -38, 43, 42, -37, 40, + 42, -37, 37, 42, -36, 33, 42, -35, 29, 42, -33, 24, + 43, -32, 20, 43, -30, 16, 43, -28, 11, 43, -27, 7, + 43, -24, 2, 44, -22, -3, 44, -20, -7, 44, -17, -12, + 44, -14, -17, 45, -12, -21, 45, -9, -25, 45, -6, -29, + 46, -3, -34, 46, 0, -38, 47, 3, -42, 47, 6, -46, + 48, 9, -50, 48, 12, -54, 49, 15, -58, 49, 18, -61, + 50, 21, -65, 50, 25, -69, 51, 28, -73, 51, 30, -76, + 43, -40, 48, 43, -40, 46, 43, -39, 44, 43, -39, 41, + 43, -38, 38, 44, -37, 35, 44, -36, 31, 44, -35, 26, + 44, -34, 22, 44, -32, 17, 44, -30, 13, 44, -29, 9, + 45, -26, 3, 45, -24, -1, 45, -22, -5, 45, -19, -10, + 46, -17, -15, 46, -14, -19, 46, -11, -23, 47, -8, -27, + 47, -5, -32, 47, -2, -36, 48, 0, -40, 48, 3, -44, + 49, 6, -48, 49, 10, -52, 50, 13, -56, 50, 16, -60, + 51, 19, -63, 51, 22, -67, 52, 25, -71, 53, 28, -74, + 45, -42, 49, 45, -41, 47, 45, -41, 45, 45, -40, 43, + 45, -40, 40, 45, -39, 36, 45, -38, 32, 45, -37, 28, + 45, -35, 24, 45, -34, 19, 46, -32, 15, 46, -30, 10, + 46, -28, 5, 46, -26, 1, 46, -24, -3, 47, -21, -8, + 47, -19, -13, 47, -16, -17, 48, -14, -21, 48, -11, -25, + 48, -8, -30, 49, -5, -34, 49, -2, -38, 49, 1, -42, + 50, 4, -46, 50, 7, -50, 51, 10, -54, 51, 13, -58, + 52, 16, -62, 52, 20, -66, 53, 23, -69, 54, 25, -73, + 46, -43, 50, 46, -43, 48, 46, -42, 47, 46, -42, 44, + 46, -41, 41, 46, -41, 38, 46, -40, 34, 46, -38, 29, + 47, -37, 25, 47, -36, 21, 47, -34, 17, 47, -32, 12, + 47, -30, 7, 47, -28, 3, 48, -26, -2, 48, -24, -6, + 48, -21, -11, 49, -18, -15, 49, -16, -19, 49, -13, -23, + 50, -10, -28, 50, -7, -32, 50, -4, -36, 51, -1, -40, + 51, 1, -44, 52, 5, -49, 52, 8, -52, 53, 11, -56, + 53, 14, -60, 54, 17, -64, 54, 20, -68, 55, 23, -71, + 48, -45, 51, 48, -45, 50, 48, -44, 48, 48, -44, 46, + 48, -43, 43, 48, -43, 40, 48, -42, 36, 48, -40, 32, + 48, -39, 27, 48, -38, 23, 49, -36, 19, 49, -35, 15, + 49, -32, 10, 49, -30, 5, 49, -28, 1, 50, -26, -4, + 50, -23, -8, 50, -21, -13, 50, -18, -17, 51, -16, -21, + 51, -13, -26, 51, -10, -30, 52, -7, -34, 52, -4, -38, + 53, -2, -42, 53, 2, -46, 53, 5, -50, 54, 8, -54, + 54, 11, -58, 55, 14, -62, 55, 17, -65, 56, 20, -69, + 49, -46, 53, 49, -46, 51, 49, -46, 49, 49, -45, 47, + 49, -45, 44, 49, -44, 41, 49, -43, 37, 50, -42, 33, + 50, -41, 29, 50, -39, 25, 50, -38, 21, 50, -36, 16, + 50, -34, 11, 50, -32, 7, 51, -30, 3, 51, -28, -2, + 51, -26, -6, 51, -23, -11, 52, -21, -15, 52, -18, -19, + 52, -15, -24, 53, -12, -28, 53, -10, -32, 53, -7, -36, + 54, -4, -40, 54, -1, -44, 55, 2, -48, 55, 5, -52, + 55, 8, -56, 56, 11, -60, 57, 14, -64, 57, 17, -67, + 50, -48, 54, 51, -48, 52, 51, -47, 51, 51, -47, 48, + 51, -46, 46, 51, -46, 42, 51, -45, 39, 51, -44, 35, + 51, -42, 31, 51, -41, 27, 51, -40, 22, 51, -38, 18, + 52, -36, 13, 52, -34, 9, 52, -32, 5, 52, -30, 0, + 52, -28, -5, 53, -25, -9, 53, -23, -13, 53, -20, -17, + 54, -17, -22, 54, -15, -26, 54, -12, -30, 55, -9, -34, + 55, -6, -38, 55, -3, -43, 56, 0, -46, 56, 3, -50, + 57, 6, -54, 57, 9, -58, 58, 12, -62, 58, 15, -65, + 52, -49, 55, 52, -49, 53, 52, -49, 52, 52, -48, 49, + 52, -48, 47, 52, -47, 44, 52, -46, 40, 52, -45, 36, + 52, -44, 32, 52, -43, 28, 53, -41, 24, 53, -40, 20, + 53, -38, 15, 53, -36, 11, 53, -34, 6, 53, -32, 2, + 54, -29, -3, 54, -27, -7, 54, -25, -11, 54, -22, -15, + 55, -19, -20, 55, -17, -24, 55, -14, -28, 56, -11, -32, + 56, -8, -36, 57, -5, -41, 57, -2, -45, 57, 0, -48, + 58, 3, -52, 58, 7, -56, 59, 10, -60, 59, 13, -64, + 53, -51, 56, 53, -50, 54, 53, -50, 53, 53, -50, 51, + 53, -49, 48, 53, -48, 45, 53, -48, 42, 54, -47, 38, + 54, -46, 34, 54, -44, 30, 54, -43, 26, 54, -41, 22, + 54, -40, 17, 54, -38, 13, 54, -36, 8, 55, -34, 4, + 55, -31, -1, 55, -29, -5, 55, -27, -9, 56, -24, -14, + 56, -21, -18, 56, -19, -22, 57, -16, -26, 57, -14, -30, + 57, -11, -34, 58, -8, -39, 58, -5, -43, 58, -2, -47, + 59, 1, -50, 59, 4, -55, 60, 7, -58, 60, 10, -62, + 55, -52, 57, 55, -52, 56, 55, -51, 54, 55, -51, 52, + 55, -51, 49, 55, -50, 47, 55, -49, 43, 55, -48, 39, + 55, -47, 36, 55, -46, 32, 55, -45, 28, 55, -43, 23, + 55, -41, 19, 56, -40, 14, 56, -38, 10, 56, -36, 6, + 56, -33, 1, 56, -31, -3, 57, -29, -7, 57, -26, -12, + 57, -24, -16, 58, -21, -20, 58, -18, -24, 58, -16, -29, + 59, -13, -33, 59, -10, -37, 59, -7, -41, 60, -4, -45, + 60, -1, -49, 61, 2, -53, 61, 5, -56, 61, 8, -60, + 56, -53, 58, 56, -53, 57, 56, -53, 55, 56, -52, 53, + 56, -52, 51, 56, -51, 48, 56, -51, 45, 56, -50, 41, + 56, -49, 37, 56, -47, 33, 56, -46, 29, 57, -45, 25, + 57, -43, 20, 57, -41, 16, 57, -39, 12, 57, -37, 8, + 57, -35, 3, 58, -33, -1, 58, -31, -6, 58, -28, -10, + 58, -26, -14, 59, -23, -19, 59, -21, -23, 59, -18, -27, + 60, -15, -31, 60, -12, -35, 60, -9, -39, 61, -6, -43, + 61, -4, -47, 62, 0, -51, 62, 2, -55, 62, 5, -58, + 57, -55, 59, 57, -54, 58, 57, -54, 56, 57, -54, 54, + 57, -53, 52, 57, -53, 49, 57, -52, 46, 57, -51, 42, + 58, -50, 39, 58, -49, 35, 58, -48, 31, 58, -46, 27, + 58, -45, 22, 58, -43, 18, 58, -41, 14, 59, -39, 9, + 59, -37, 5, 59, -35, 0, 59, -33, -4, 59, -30, -8, + 60, -28, -13, 60, -25, -17, 60, -23, -21, 61, -20, -25, + 61, -17, -29, 61, -14, -33, 62, -12, -37, 62, -9, -41, + 62, -6, -45, 63, -3, -49, 63, 0, -53, 64, 3, -57, + 59, -56, 60, 59, -56, 59, 59, -55, 58, 59, -55, 56, + 59, -55, 53, 59, -54, 51, 59, -53, 48, 59, -52, 44, + 59, -51, 40, 59, -50, 36, 59, -49, 33, 59, -48, 28, + 59, -46, 24, 59, -45, 20, 60, -43, 15, 60, -41, 11, + 60, -39, 7, 60, -37, 2, 60, -34, -2, 61, -32, -6, + 61, -30, -11, 61, -27, -15, 62, -25, -19, 62, -22, -23, + 62, -19, -27, 62, -16, -31, 63, -14, -35, 63, -11, -39, + 64, -8, -43, 64, -5, -47, 64, -2, -51, 65, 1, -55, + 60, -57, 61, 60, -57, 60, 60, -57, 59, 60, -56, 57, + 60, -56, 55, 60, -55, 52, 60, -55, 49, 60, -54, 45, + 60, -53, 42, 60, -52, 38, 60, -51, 34, 60, -49, 30, + 61, -48, 26, 61, -46, 21, 61, -44, 17, 61, -43, 13, + 61, -40, 8, 61, -38, 4, 62, -36, 0, 62, -34, -4, + 62, -32, -9, 62, -29, -13, 63, -27, -17, 63, -24, -21, + 63, -22, -25, 64, -19, -30, 64, -16, -33, 64, -13, -37, + 65, -10, -41, 65, -7, -46, 65, -4, -49, 66, -2, -53, + 61, -58, 62, 61, -58, 61, 61, -58, 60, 61, -58, 58, + 61, -57, 56, 61, -57, 53, 61, -56, 50, 61, -55, 47, + 61, -54, 43, 62, -53, 40, 62, -52, 36, 62, -51, 32, + 62, -49, 27, 62, -48, 23, 62, -46, 19, 62, -44, 15, + 63, -42, 10, 63, -40, 6, 63, -38, 2, 63, -36, -2, + 63, -33, -7, 64, -31, -11, 64, -29, -15, 64, -26, -19, + 65, -24, -23, 65, -21, -28, 65, -18, -32, 65, -15, -36, + 66, -13, -39, 66, -9, -44, 67, -7, -48, 67, -4, -51, + 62, -60, 63, 62, -60, 62, 62, -59, 61, 63, -59, 59, + 63, -59, 57, 63, -58, 55, 63, -57, 52, 63, -57, 48, + 63, -56, 45, 63, -55, 41, 63, -54, 37, 63, -52, 33, + 63, -51, 29, 63, -49, 25, 63, -48, 21, 64, -46, 17, + 64, -44, 12, 64, -42, 8, 64, -40, 4, 64, -38, -1, + 65, -35, -5, 65, -33, -9, 65, -31, -13, 65, -28, -17, + 66, -26, -21, 66, -23, -26, 66, -20, -30, 67, -17, -34, + 67, -15, -38, 67, -12, -42, 68, -9, -46, 68, -6, -49, + 64, -61, 64, 64, -61, 63, 64, -61, 62, 64, -60, 60, + 64, -60, 58, 64, -59, 56, 64, -59, 53, 64, -58, 50, + 64, -57, 46, 64, -56, 43, 64, -55, 39, 64, -54, 35, + 64, -52, 31, 65, -51, 27, 65, -49, 22, 65, -48, 18, + 65, -46, 14, 65, -44, 10, 65, -42, 5, 66, -40, 1, + 66, -37, -3, 66, -35, -7, 66, -33, -12, 67, -30, -16, + 67, -28, -20, 67, -25, -24, 68, -22, -28, 68, -20, -32, + 68, -17, -36, 69, -14, -40, 69, -11, -44, 69, -8, -48, + 65, -62, 65, 65, -62, 64, 65, -62, 63, 65, -62, 61, + 65, -61, 59, 65, -61, 57, 65, -60, 54, 65, -59, 51, + 65, -58, 48, 65, -57, 44, 66, -56, 40, 66, -55, 37, + 66, -54, 32, 66, -52, 28, 66, -51, 24, 66, -49, 20, + 66, -47, 15, 67, -45, 11, 67, -43, 7, 67, -41, 3, + 67, -39, -2, 67, -37, -6, 68, -34, -10, 68, -32, -14, + 68, -30, -18, 68, -27, -22, 69, -24, -26, 69, -22, -30, + 69, -19, -34, 70, -16, -38, 70, -13, -42, 70, -11, -46, + 66, -63, 66, 66, -63, 65, 66, -63, 64, 66, -63, 63, + 66, -62, 61, 66, -62, 58, 67, -61, 56, 67, -61, 52, + 67, -60, 49, 67, -59, 46, 67, -58, 42, 67, -57, 38, + 67, -55, 34, 67, -54, 30, 67, -52, 26, 67, -51, 22, + 68, -49, 17, 68, -47, 13, 68, -45, 9, 68, -43, 5, + 68, -41, 0, 69, -39, -4, 69, -36, -8, 69, -34, -12, + 69, -32, -16, 70, -29, -20, 70, -26, -24, 70, -24, -28, + 71, -21, -32, 71, -18, -36, 71, -15, -40, 72, -13, -44, + 68, -65, 67, 68, -65, 66, 68, -64, 65, 68, -64, 64, + 68, -64, 62, 68, -63, 60, 68, -63, 57, 68, -62, 54, + 68, -61, 51, 68, -60, 47, 68, -59, 44, 68, -58, 40, + 68, -57, 35, 68, -55, 32, 69, -54, 28, 69, -52, 24, + 69, -50, 19, 69, -49, 15, 69, -47, 11, 69, -45, 7, + 70, -42, 2, 70, -40, -2, 70, -38, -6, 70, -36, -10, + 71, -33, -14, 71, -31, -19, 71, -28, -23, 71, -26, -26, + 72, -23, -30, 72, -20, -35, 72, -18, -39, 73, -15, -42, + 69, -66, 68, 69, -66, 67, 69, -66, 66, 69, -65, 65, + 69, -65, 63, 69, -64, 61, 69, -64, 58, 69, -63, 55, + 69, -62, 52, 69, -62, 49, 69, -61, 45, 69, -59, 41, + 70, -58, 37, 70, -57, 33, 70, -55, 29, 70, -54, 25, + 70, -52, 21, 70, -50, 17, 70, -48, 13, 71, -46, 8, + 71, -44, 4, 71, -42, 0, 71, -40, -4, 71, -38, -8, + 72, -35, -12, 72, -33, -17, 72, -30, -21, 73, -28, -25, + 73, -25, -29, 73, -22, -33, 74, -20, -37, 74, -17, -41, + 70, -67, 69, 70, -67, 68, 70, -67, 67, 70, -66, 66, + 70, -66, 64, 70, -66, 62, 70, -65, 60, 70, -64, 56, + 70, -64, 53, 71, -63, 50, 71, -62, 47, 71, -61, 43, + 71, -60, 39, 71, -58, 35, 71, -57, 31, 71, -55, 27, + 71, -54, 22, 72, -52, 18, 72, -50, 14, 72, -48, 10, + 72, -46, 6, 72, -44, 2, 72, -42, -2, 73, -39, -6, + 73, -37, -10, 73, -35, -15, 73, -32, -19, 74, -30, -23, + 74, -27, -27, 74, -24, -31, 75, -22, -35, 75, -19, -39, + 71, -68, 70, 71, -68, 69, 72, -68, 69, 72, -68, 67, + 72, -67, 65, 72, -67, 63, 72, -66, 61, 72, -66, 58, + 72, -65, 55, 72, -64, 51, 72, -63, 48, 72, -62, 44, + 72, -61, 40, 72, -60, 36, 72, -58, 32, 72, -57, 29, + 73, -55, 24, 73, -53, 20, 73, -52, 16, 73, -50, 12, + 73, -48, 7, 73, -46, 3, 74, -43, -1, 74, -41, -5, + 74, -39, -9, 74, -36, -13, 75, -34, -17, 75, -32, -21, + 75, -29, -25, 76, -26, -29, 76, -24, -33, 76, -21, -37, + 73, -69, 71, 73, -69, 71, 73, -69, 70, 73, -69, 68, + 73, -69, 66, 73, -68, 64, 73, -68, 62, 73, -67, 59, + 73, -66, 56, 73, -65, 53, 73, -65, 49, 73, -64, 46, + 73, -62, 42, 73, -61, 38, 74, -60, 34, 74, -58, 30, + 74, -57, 26, 74, -55, 22, 74, -53, 18, 74, -51, 14, + 75, -49, 9, 75, -47, 5, 75, -45, 1, 75, -43, -3, + 75, -41, -7, 76, -38, -11, 76, -36, -15, 76, -34, -19, + 76, -31, -23, 77, -28, -28, 77, -26, -31, 77, -23, -35, + 74, -71, 72, 74, -71, 72, 74, -70, 71, 74, -70, 69, + 74, -70, 68, 74, -69, 66, 74, -69, 63, 74, -68, 60, + 74, -68, 57, 74, -67, 54, 74, -66, 51, 74, -65, 47, + 75, -64, 43, 75, -62, 40, 75, -61, 36, 75, -60, 32, + 75, -58, 27, 75, -56, 23, 75, -55, 19, 76, -53, 15, + 76, -51, 11, 76, -49, 7, 76, -47, 3, 76, -45, -1, + 77, -43, -5, 77, -40, -10, 77, -38, -14, 77, -35, -17, + 78, -33, -21, 78, -30, -26, 78, -28, -30, 78, -25, -33, + 76, -72, 74, 76, -72, 73, 76, -72, 72, 76, -72, 71, + 76, -71, 69, 76, -71, 67, 76, -70, 65, 76, -70, 62, + 76, -69, 59, 76, -68, 56, 76, -67, 53, 76, -66, 49, + 76, -65, 45, 76, -64, 41, 76, -63, 38, 76, -61, 34, + 77, -60, 29, 77, -58, 25, 77, -57, 22, 77, -55, 18, + 77, -53, 13, 77, -51, 9, 78, -49, 5, 78, -47, 1, + 78, -45, -3, 78, -42, -7, 79, -40, -11, 79, -38, -15, + 79, -35, -19, 79, -33, -23, 80, -30, -27, 80, -28, -31, + 77, -73, 75, 77, -73, 74, 77, -73, 73, 77, -73, 72, + 77, -72, 70, 77, -72, 68, 77, -72, 66, 77, -71, 63, + 77, -70, 60, 77, -70, 57, 77, -69, 54, 77, -68, 51, + 77, -67, 47, 78, -65, 43, 78, -64, 39, 78, -63, 35, + 78, -61, 31, 78, -60, 27, 78, -58, 23, 78, -56, 19, + 78, -54, 15, 79, -53, 11, 79, -51, 7, 79, -49, 3, + 79, -47, -1, 79, -44, -6, 80, -42, -10, 80, -40, -13, + 80, -37, -17, 80, -35, -22, 81, -32, -26, 81, -30, -29, + 78, -74, 76, 78, -74, 75, 78, -74, 74, 78, -74, 73, + 78, -74, 71, 78, -73, 69, 78, -73, 67, 78, -72, 64, + 78, -72, 62, 78, -71, 59, 78, -70, 55, 79, -69, 52, + 79, -68, 48, 79, -67, 45, 79, -66, 41, 79, -64, 37, + 79, -63, 33, 79, -61, 29, 79, -60, 25, 80, -58, 21, + 80, -56, 16, 80, -54, 12, 80, -52, 8, 80, -50, 5, + 80, -48, 1, 81, -46, -4, 81, -44, -8, 81, -41, -12, + 81, -39, -16, 82, -36, -20, 82, -34, -24, 82, -32, -28, + 79, -76, 77, 79, -75, 76, 79, -75, 75, 79, -75, 74, + 79, -75, 72, 79, -74, 71, 80, -74, 68, 80, -73, 66, + 80, -73, 63, 80, -72, 60, 80, -71, 57, 80, -70, 54, + 80, -69, 50, 80, -68, 46, 80, -67, 42, 80, -66, 39, + 80, -64, 34, 80, -63, 30, 81, -61, 27, 81, -59, 23, + 81, -58, 18, 81, -56, 14, 81, -54, 10, 81, -52, 6, + 82, -50, 2, 82, -48, -2, 82, -45, -6, 82, -43, -10, + 83, -41, -14, 83, -38, -18, 83, -36, -22, 83, -33, -26, + 81, -77, 78, 81, -77, 77, 81, -76, 76, 81, -76, 75, + 81, -76, 73, 81, -76, 72, 81, -75, 70, 81, -75, 67, + 81, -74, 64, 81, -73, 61, 81, -72, 58, 81, -72, 55, + 81, -70, 51, 81, -69, 48, 81, -68, 44, 81, -67, 40, + 82, -66, 36, 82, -64, 32, 82, -63, 28, 82, -61, 24, + 82, -59, 20, 82, -57, 16, 82, -55, 12, 83, -54, 8, + 83, -52, 4, 83, -49, 0, 83, -47, -4, 83, -45, -8, + 84, -43, -12, 84, -40, -16, 84, -38, -20, 84, -35, -24, + 82, -78, 79, 82, -78, 78, 82, -78, 77, 82, -77, 76, + 82, -77, 75, 82, -77, 73, 82, -76, 71, 82, -76, 68, + 82, -75, 66, 82, -74, 63, 82, -74, 60, 82, -73, 56, + 82, -72, 53, 82, -71, 49, 83, -70, 45, 83, -68, 42, + 83, -67, 37, 83, -65, 34, 83, -64, 30, 83, -62, 26, + 83, -61, 21, 83, -59, 18, 84, -57, 14, 84, -55, 10, + 84, -53, 6, 84, -51, 1, 84, -49, -3, 85, -47, -6, + 85, -44, -10, 85, -42, -15, 85, -40, -19, 86, -37, -22, + 83, -79, 80, 83, -79, 79, 83, -79, 78, 83, -78, 77, + 83, -78, 76, 83, -78, 74, 83, -77, 72, 83, -77, 69, + 83, -76, 67, 83, -76, 64, 83, -75, 61, 84, -74, 58, + 84, -73, 54, 84, -72, 51, 84, -71, 47, 84, -70, 43, + 84, -68, 39, 84, -67, 35, 84, -65, 31, 84, -64, 28, + 85, -62, 23, 85, -60, 19, 85, -59, 15, 85, -57, 11, + 85, -55, 7, 85, -53, 3, 86, -50, -1, 86, -48, -5, + 86, -46, -9, 86, -44, -13, 87, -41, -17, 87, -39, -21, + 84, -80, 81, 84, -80, 80, 84, -80, 79, 84, -80, 78, + 84, -79, 77, 84, -79, 75, 84, -79, 73, 85, -78, 71, + 85, -77, 68, 85, -77, 65, 85, -76, 62, 85, -75, 59, + 85, -74, 55, 85, -73, 52, 85, -72, 48, 85, -71, 45, + 85, -70, 41, 85, -68, 37, 85, -67, 33, 86, -65, 29, + 86, -64, 25, 86, -62, 21, 86, -60, 17, 86, -58, 13, + 86, -56, 9, 87, -54, 5, 87, -52, 1, 87, -50, -3, + 87, -48, -7, 87, -45, -11, 88, -43, -15, 88, -41, -19, + 86, -81, 82, 86, -81, 81, 86, -81, 80, 86, -81, 79, + 86, -80, 78, 86, -80, 76, 86, -80, 74, 86, -79, 72, + 86, -79, 69, 86, -78, 67, 86, -77, 64, 86, -76, 60, + 86, -75, 57, 86, -75, 53, 86, -73, 50, 86, -72, 46, + 86, -71, 42, 87, -70, 38, 87, -68, 35, 87, -67, 31, + 87, -65, 26, 87, -63, 23, 87, -62, 19, 87, -60, 15, + 88, -58, 11, 88, -56, 6, 88, -54, 3, 88, -52, -1, + 88, -50, -5, 89, -47, -9, 89, -45, -13, 89, -43, -17, + 87, -82, 83, 87, -82, 82, 87, -82, 81, 87, -82, 80, + 87, -82, 79, 87, -81, 77, 87, -81, 75, 87, -80, 73, + 87, -80, 70, 87, -79, 68, 87, -78, 65, 87, -78, 62, + 87, -77, 58, 87, -76, 55, 87, -75, 51, 88, -74, 48, + 88, -72, 44, 88, -71, 40, 88, -70, 36, 88, -68, 32, + 88, -66, 28, 88, -65, 24, 88, -63, 20, 89, -61, 16, + 89, -59, 13, 89, -57, 8, 89, -55, 4, 89, -53, 0, + 90, -51, -3, 90, -49, -8, 90, -47, -12, 90, -44, -15, + 88, -83, 84, 88, -83, 83, 88, -83, 82, 88, -83, 81, + 88, -83, 80, 88, -82, 78, 88, -82, 77, 88, -81, 74, + 88, -81, 72, 88, -80, 69, 88, -80, 66, 88, -79, 63, + 88, -78, 60, 89, -77, 56, 89, -76, 53, 89, -75, 49, + 89, -74, 45, 89, -72, 42, 89, -71, 38, 89, -69, 34, + 89, -68, 30, 89, -66, 26, 90, -65, 22, 90, -63, 18, + 90, -61, 14, 90, -59, 10, 90, -57, 6, 91, -55, 2, + 91, -53, -2, 91, -51, -6, 91, -48, -10, 91, -46, -14, + 8, 28, 13, 8, 28, 10, 8, 29, 6, 9, 30, 1, + 9, 30, -5, 9, 31, -10, 10, 33, -16, 11, 34, -21, + 11, 36, -26, 12, 37, -30, 13, 39, -35, 13, 41, -39, + 14, 43, -43, 15, 45, -47, 16, 47, -51, 17, 48, -54, + 18, 51, -58, 19, 53, -62, 20, 54, -65, 21, 56, -68, + 22, 59, -72, 23, 60, -75, 24, 62, -78, 25, 64, -81, + 27, 66, -84, 28, 68, -87, 29, 70, -90, 30, 72, -93, + 31, 74, -96, 32, 76, -99, 33, 78, -102, 34, 80, -105, + 9, 26, 14, 9, 26, 11, 9, 27, 7, 9, 27, 2, + 10, 28, -4, 10, 30, -9, 11, 31, -15, 11, 32, -20, + 12, 34, -25, 12, 36, -29, 13, 37, -34, 14, 39, -38, + 15, 41, -43, 16, 43, -46, 17, 45, -50, 18, 47, -54, + 19, 49, -58, 20, 51, -61, 21, 53, -65, 22, 55, -68, + 23, 58, -71, 24, 60, -75, 25, 62, -78, 26, 64, -81, + 27, 65, -84, 28, 68, -87, 29, 70, -90, 30, 72, -93, + 31, 73, -96, 32, 76, -99, 33, 77, -102, 34, 79, -104, + 10, 24, 15, 10, 24, 12, 10, 25, 8, 10, 26, 3, + 10, 27, -3, 11, 28, -8, 11, 29, -14, 12, 31, -19, + 12, 32, -24, 13, 34, -29, 14, 36, -33, 14, 38, -37, + 15, 40, -42, 16, 42, -46, 17, 44, -49, 18, 46, -53, + 19, 48, -57, 20, 50, -61, 21, 52, -64, 22, 54, -67, + 23, 57, -71, 24, 59, -74, 25, 61, -77, 26, 63, -80, + 27, 65, -83, 28, 67, -87, 29, 69, -90, 30, 71, -92, + 31, 73, -95, 32, 75, -98, 34, 77, -101, 35, 79, -104, + 10, 22, 16, 10, 22, 13, 11, 23, 9, 11, 24, 4, + 11, 25, -2, 11, 26, -7, 12, 27, -13, 12, 29, -18, + 13, 31, -23, 14, 32, -28, 14, 34, -32, 15, 36, -36, + 16, 39, -41, 17, 41, -45, 17, 43, -49, 18, 45, -52, + 19, 47, -57, 20, 49, -60, 21, 51, -63, 22, 53, -67, + 23, 56, -70, 24, 58, -74, 25, 60, -77, 26, 62, -80, + 27, 64, -83, 28, 66, -86, 29, 68, -89, 30, 70, -92, + 32, 72, -95, 33, 74, -98, 34, 76, -101, 35, 78, -104, + 11, 20, 17, 11, 20, 14, 11, 21, 10, 12, 22, 5, + 12, 23, -1, 12, 24, -6, 13, 25, -12, 13, 27, -17, + 14, 29, -22, 14, 31, -27, 15, 33, -31, 16, 35, -35, + 16, 37, -40, 17, 39, -44, 18, 41, -48, 19, 43, -52, + 20, 46, -56, 21, 48, -59, 22, 50, -63, 23, 52, -66, + 24, 55, -70, 25, 57, -73, 26, 59, -76, 27, 61, -79, + 28, 63, -82, 29, 65, -86, 30, 67, -89, 31, 69, -92, + 32, 71, -95, 33, 74, -98, 34, 76, -101, 35, 78, -103, + 12, 17, 18, 12, 18, 15, 12, 18, 12, 12, 19, 6, + 13, 20, 0, 13, 22, -5, 13, 23, -10, 14, 25, -16, + 14, 27, -21, 15, 29, -26, 16, 31, -30, 16, 33, -34, + 17, 35, -39, 18, 37, -43, 19, 40, -47, 19, 42, -51, + 20, 44, -55, 21, 47, -59, 22, 49, -62, 23, 51, -65, + 24, 53, -69, 25, 56, -72, 26, 58, -76, 27, 60, -79, + 28, 62, -82, 29, 64, -85, 30, 66, -88, 31, 68, -91, + 32, 70, -94, 33, 73, -97, 34, 75, -100, 35, 77, -103, + 13, 15, 20, 13, 15, 16, 13, 16, 13, 13, 17, 7, + 14, 18, 2, 14, 19, -4, 14, 21, -9, 15, 23, -15, + 15, 24, -20, 16, 26, -24, 16, 29, -29, 17, 31, -33, + 18, 33, -38, 18, 35, -42, 19, 38, -46, 20, 40, -50, + 21, 43, -54, 22, 45, -58, 23, 47, -61, 24, 49, -65, + 25, 52, -68, 25, 54, -72, 26, 56, -75, 27, 59, -78, + 28, 61, -81, 29, 63, -85, 30, 65, -88, 31, 67, -91, + 32, 69, -93, 34, 72, -97, 35, 74, -100, 36, 76, -102, + 14, 12, 21, 14, 13, 18, 14, 13, 14, 14, 14, 9, + 15, 15, 3, 15, 17, -2, 15, 18, -7, 16, 20, -13, + 16, 22, -18, 17, 24, -23, 17, 26, -27, 18, 28, -32, + 19, 31, -37, 19, 33, -41, 20, 36, -45, 21, 38, -49, + 22, 41, -53, 22, 43, -57, 23, 45, -60, 24, 48, -64, + 25, 50, -67, 26, 53, -71, 27, 55, -74, 28, 57, -77, + 29, 59, -80, 30, 62, -84, 31, 64, -87, 32, 66, -90, + 33, 68, -93, 34, 71, -96, 35, 73, -99, 36, 75, -102, + 15, 10, 22, 15, 10, 19, 15, 11, 16, 16, 12, 10, + 16, 13, 5, 16, 14, -1, 16, 16, -6, 17, 18, -12, + 17, 19, -17, 18, 22, -21, 18, 24, -26, 19, 26, -30, + 19, 29, -35, 20, 31, -39, 21, 33, -43, 21, 36, -47, + 22, 39, -52, 23, 41, -55, 24, 43, -59, 25, 46, -63, + 26, 49, -66, 27, 51, -70, 27, 53, -73, 28, 56, -76, + 29, 58, -80, 30, 60, -83, 31, 63, -86, 32, 65, -89, + 33, 67, -92, 34, 69, -95, 35, 71, -98, 36, 74, -101, + 16, 7, 24, 16, 7, 21, 16, 8, 17, 17, 9, 12, + 17, 10, 6, 17, 11, 1, 17, 13, -4, 18, 15, -10, + 18, 17, -15, 19, 19, -20, 19, 21, -24, 20, 23, -29, + 20, 26, -34, 21, 29, -38, 22, 31, -42, 22, 34, -46, + 23, 36, -50, 24, 39, -54, 25, 41, -58, 25, 44, -61, + 26, 47, -65, 27, 49, -69, 28, 51, -72, 29, 54, -75, + 30, 56, -79, 31, 59, -82, 32, 61, -85, 33, 63, -88, + 34, 66, -91, 35, 68, -95, 36, 70, -98, 37, 72, -100, + 18, 4, 25, 18, 5, 22, 18, 5, 19, 18, 6, 13, + 18, 7, 8, 18, 9, 3, 19, 10, -3, 19, 12, -8, + 19, 14, -13, 20, 16, -18, 20, 18, -23, 21, 21, -27, + 21, 24, -32, 22, 26, -36, 22, 29, -41, 23, 31, -45, + 24, 34, -49, 25, 37, -53, 25, 39, -56, 26, 42, -60, + 27, 45, -64, 28, 47, -68, 29, 49, -71, 30, 52, -74, + 31, 54, -78, 32, 57, -81, 32, 59, -84, 33, 62, -87, + 34, 64, -90, 35, 66, -94, 36, 69, -97, 37, 71, -100, + 19, 1, 27, 19, 1, 24, 19, 2, 21, 19, 3, 15, + 20, 4, 10, 20, 5, 5, 20, 7, 0, 20, 9, -6, + 21, 11, -11, 21, 13, -16, 22, 15, -20, 22, 17, -25, + 23, 20, -30, 23, 23, -34, 24, 25, -39, 24, 28, -43, + 25, 31, -47, 26, 34, -51, 26, 36, -55, 27, 39, -58, + 28, 42, -62, 29, 44, -66, 30, 47, -69, 31, 49, -73, + 31, 52, -76, 32, 55, -80, 33, 57, -83, 34, 59, -86, + 35, 62, -89, 36, 64, -93, 37, 67, -96, 38, 69, -99, + 20, -2, 29, 20, -1, 26, 21, -1, 22, 21, 0, 17, + 21, 1, 12, 21, 3, 7, 21, 4, 1, 22, 6, -4, + 22, 8, -9, 22, 10, -14, 23, 12, -19, 23, 15, -23, + 24, 18, -28, 24, 20, -33, 25, 23, -37, 25, 25, -41, + 26, 28, -46, 27, 31, -49, 27, 34, -53, 28, 36, -57, + 29, 39, -61, 30, 42, -65, 30, 45, -68, 31, 47, -72, + 32, 50, -75, 33, 53, -79, 34, 55, -82, 35, 58, -85, + 36, 60, -88, 37, 63, -92, 38, 65, -95, 38, 67, -98, + 22, -4, 30, 22, -4, 27, 22, -3, 23, 22, -2, 18, + 22, -1, 13, 22, 0, 8, 23, 1, 3, 23, 3, -2, + 23, 5, -7, 23, 7, -12, 24, 10, -17, 24, 12, -21, + 25, 15, -27, 25, 17, -31, 26, 20, -35, 26, 23, -39, + 27, 26, -44, 28, 29, -48, 28, 31, -52, 29, 34, -55, + 30, 37, -60, 31, 40, -63, 31, 42, -67, 32, 45, -70, + 33, 48, -74, 34, 50, -77, 35, 53, -81, 35, 56, -84, + 36, 58, -87, 37, 61, -91, 38, 63, -94, 39, 65, -97, + 23, -7, 32, 23, -6, 28, 23, -6, 25, 23, -5, 20, + 23, -4, 15, 24, -3, 10, 24, -1, 5, 24, 1, 0, + 24, 3, -5, 25, 5, -10, 25, 7, -15, 25, 9, -20, + 26, 12, -25, 26, 15, -29, 27, 17, -33, 27, 20, -38, + 28, 23, -42, 29, 26, -46, 29, 29, -50, 30, 31, -54, + 31, 35, -58, 31, 37, -62, 32, 40, -65, 33, 43, -69, + 34, 45, -72, 35, 48, -76, 35, 51, -79, 36, 53, -83, + 37, 56, -86, 38, 59, -89, 39, 61, -93, 40, 64, -96, + 24, -9, 33, 24, -9, 30, 25, -8, 26, 25, -8, 22, + 25, -7, 17, 25, -5, 12, 25, -4, 7, 25, -2, 1, + 26, 0, -4, 26, 2, -8, 26, 4, -13, 27, 7, -18, + 27, 9, -23, 28, 12, -27, 28, 15, -32, 29, 17, -36, + 29, 21, -40, 30, 23, -45, 30, 26, -48, 31, 29, -52, + 32, 32, -57, 32, 35, -60, 33, 38, -64, 34, 40, -67, + 35, 43, -71, 35, 46, -75, 36, 49, -78, 37, 51, -81, + 38, 54, -85, 39, 57, -88, 40, 59, -91, 40, 62, -94, + 26, -12, 34, 26, -11, 31, 26, -11, 28, 26, -10, 23, + 26, -9, 19, 26, -8, 14, 26, -6, 9, 27, -5, 3, + 27, -3, -2, 27, -1, -6, 28, 2, -11, 28, 4, -16, + 28, 7, -21, 29, 9, -25, 29, 12, -30, 30, 15, -34, + 30, 18, -39, 31, 21, -43, 31, 23, -47, 32, 26, -51, + 33, 29, -55, 33, 32, -59, 34, 35, -62, 35, 38, -66, + 35, 41, -69, 36, 44, -73, 37, 46, -77, 38, 49, -80, + 39, 52, -83, 39, 55, -87, 40, 57, -90, 41, 60, -93, + 27, -14, 36, 27, -14, 32, 27, -13, 29, 27, -12, 25, + 27, -11, 20, 28, -10, 15, 28, -9, 11, 28, -7, 5, + 28, -5, 0, 29, -3, -5, 29, -1, -9, 29, 1, -14, + 30, 4, -19, 30, 7, -24, 30, 9, -28, 31, 12, -32, + 31, 15, -37, 32, 18, -41, 32, 21, -45, 33, 24, -49, + 34, 27, -53, 34, 30, -57, 35, 33, -61, 36, 35, -64, + 36, 38, -68, 37, 41, -72, 38, 44, -75, 39, 47, -79, + 39, 49, -82, 40, 52, -86, 41, 55, -89, 42, 57, -92, + 29, -16, 37, 29, -16, 34, 29, -15, 30, 29, -15, 26, + 29, -14, 22, 29, -13, 17, 29, -11, 12, 29, -9, 7, + 30, -8, 2, 30, -6, -3, 30, -4, -7, 30, -1, -12, + 31, 1, -17, 31, 4, -22, 32, 7, -26, 32, 9, -30, + 33, 13, -35, 33, 15, -39, 34, 18, -43, 34, 21, -47, + 35, 24, -52, 35, 27, -55, 36, 30, -59, 37, 33, -63, + 37, 36, -66, 38, 39, -70, 39, 42, -74, 39, 44, -77, + 40, 47, -81, 41, 50, -84, 42, 53, -88, 43, 55, -91, + 30, -18, 38, 30, -18, 35, 30, -18, 32, 30, -17, 28, + 30, -16, 23, 30, -15, 19, 30, -14, 14, 31, -12, 9, + 31, -10, 4, 31, -8, -1, 31, -6, -5, 32, -4, -10, + 32, -1, -15, 32, 1, -20, 33, 4, -24, 33, 7, -28, + 34, 10, -33, 34, 13, -37, 35, 16, -41, 35, 18, -45, + 36, 22, -50, 36, 25, -54, 37, 27, -57, 38, 30, -61, + 38, 33, -65, 39, 36, -69, 40, 39, -72, 40, 42, -76, + 41, 45, -79, 42, 48, -83, 43, 50, -86, 43, 53, -89, + 31, -21, 39, 31, -20, 36, 31, -20, 33, 31, -19, 29, + 32, -18, 25, 32, -17, 21, 32, -16, 16, 32, -14, 11, + 32, -12, 6, 32, -11, 1, 33, -8, -3, 33, -6, -8, + 33, -4, -13, 34, -1, -18, 34, 1, -22, 34, 4, -27, + 35, 7, -31, 35, 10, -36, 36, 13, -40, 36, 16, -44, + 37, 19, -48, 37, 22, -52, 38, 25, -56, 39, 28, -59, + 39, 31, -63, 40, 34, -67, 41, 37, -71, 41, 39, -74, + 42, 42, -78, 43, 45, -81, 44, 48, -85, 44, 51, -88, + 33, -23, 40, 33, -22, 37, 33, -22, 35, 33, -21, 31, + 33, -20, 27, 33, -19, 22, 33, -18, 18, 33, -16, 13, + 34, -15, 8, 34, -13, 3, 34, -11, -2, 34, -9, -6, + 35, -6, -11, 35, -4, -16, 35, -1, -20, 36, 2, -25, + 36, 5, -29, 37, 7, -34, 37, 10, -38, 37, 13, -42, + 38, 16, -46, 39, 19, -50, 39, 22, -54, 40, 25, -58, + 40, 28, -61, 41, 31, -66, 42, 34, -69, 42, 37, -73, + 43, 40, -76, 44, 43, -80, 44, 46, -83, 45, 48, -87, + 34, -25, 41, 34, -24, 38, 34, -24, 36, 34, -23, 32, + 34, -22, 28, 34, -21, 24, 35, -20, 20, 35, -19, 14, + 35, -17, 10, 35, -15, 5, 35, -13, 0, 36, -11, -4, + 36, -8, -9, 36, -6, -14, 37, -4, -18, 37, -1, -23, + 37, 2, -28, 38, 5, -32, 38, 8, -36, 39, 11, -40, + 39, 14, -44, 40, 17, -48, 40, 20, -52, 41, 23, -56, + 41, 25, -60, 42, 29, -64, 43, 32, -68, 43, 34, -71, + 44, 37, -75, 45, 40, -78, 45, 43, -82, 46, 46, -85, + 35, -27, 42, 35, -26, 40, 36, -26, 37, 36, -25, 34, + 36, -24, 30, 36, -23, 26, 36, -22, 21, 36, -21, 16, + 36, -19, 12, 36, -17, 7, 37, -15, 2, 37, -13, -2, + 37, -11, -7, 37, -8, -12, 38, -6, -16, 38, -3, -21, + 39, 0, -26, 39, 2, -30, 39, 5, -34, 40, 8, -38, + 40, 11, -43, 41, 14, -47, 41, 17, -50, 42, 20, -54, + 42, 23, -58, 43, 26, -62, 44, 29, -66, 44, 32, -69, + 45, 35, -73, 46, 38, -77, 46, 41, -80, 47, 44, -84, + 37, -28, 43, 37, -28, 41, 37, -28, 38, 37, -27, 35, + 37, -26, 31, 37, -25, 27, 37, -24, 23, 37, -23, 18, + 38, -21, 13, 38, -20, 9, 38, -18, 4, 38, -16, 0, + 38, -13, -5, 39, -11, -10, 39, -8, -14, 39, -6, -19, + 40, -3, -24, 40, 0, -28, 41, 3, -32, 41, 5, -36, + 41, 9, -41, 42, 12, -45, 42, 14, -49, 43, 17, -53, + 43, 20, -56, 44, 24, -61, 45, 26, -64, 45, 29, -68, + 46, 32, -71, 47, 35, -75, 47, 38, -79, 48, 41, -82, + 38, -30, 44, 38, -30, 42, 38, -30, 40, 38, -29, 36, + 38, -28, 33, 38, -27, 29, 39, -26, 25, 39, -25, 20, + 39, -23, 15, 39, -22, 11, 39, -20, 6, 39, -18, 2, + 40, -15, -4, 40, -13, -8, 40, -11, -13, 41, -8, -17, + 41, -5, -22, 41, -3, -26, 42, 0, -30, 42, 3, -34, + 43, 6, -39, 43, 9, -43, 44, 12, -47, 44, 15, -51, + 45, 18, -55, 45, 21, -59, 46, 24, -62, 46, 27, -66, + 47, 30, -70, 48, 33, -74, 48, 36, -77, 49, 39, -81, + 40, -32, 45, 40, -32, 43, 40, -31, 41, 40, -31, 38, + 40, -30, 34, 40, -29, 31, 40, -28, 26, 40, -27, 22, + 40, -25, 17, 40, -24, 13, 41, -22, 8, 41, -20, 3, + 41, -18, -2, 41, -15, -6, 42, -13, -11, 42, -11, -15, + 42, -8, -20, 43, -5, -24, 43, -2, -28, 43, 0, -32, + 44, 4, -37, 44, 6, -41, 45, 9, -45, 45, 12, -49, + 46, 15, -53, 46, 18, -57, 47, 21, -61, 47, 24, -64, + 48, 27, -68, 49, 30, -72, 49, 33, -75, 50, 36, -79, + 41, -34, 46, 41, -33, 44, 41, -33, 42, 41, -33, 39, + 41, -32, 36, 41, -31, 32, 41, -30, 28, 41, -29, 23, + 42, -27, 19, 42, -26, 14, 42, -24, 10, 42, -22, 5, + 42, -20, 0, 43, -18, -4, 43, -15, -9, 43, -13, -13, + 44, -10, -18, 44, -7, -22, 44, -5, -26, 45, -2, -31, + 45, 1, -35, 45, 4, -39, 46, 7, -43, 46, 10, -47, + 47, 13, -51, 47, 16, -55, 48, 19, -59, 48, 22, -63, + 49, 25, -66, 50, 28, -70, 50, 31, -74, 51, 34, -77, + 42, -36, 47, 42, -35, 45, 42, -35, 43, 42, -34, 41, + 42, -34, 37, 43, -33, 34, 43, -32, 30, 43, -31, 25, + 43, -29, 21, 43, -28, 16, 43, -26, 12, 43, -24, 7, + 44, -22, 2, 44, -20, -2, 44, -18, -7, 44, -15, -11, + 45, -12, -16, 45, -10, -20, 45, -7, -25, 46, -4, -29, + 46, -1, -33, 47, 1, -37, 47, 4, -41, 47, 7, -45, + 48, 10, -49, 48, 13, -53, 49, 16, -57, 49, 19, -61, + 50, 22, -65, 51, 25, -69, 51, 28, -72, 52, 31, -76, + 44, -37, 48, 44, -37, 47, 44, -37, 45, 44, -36, 42, + 44, -35, 39, 44, -35, 35, 44, -34, 31, 44, -32, 27, + 44, -31, 22, 44, -30, 18, 45, -28, 14, 45, -26, 9, + 45, -24, 4, 45, -22, 0, 45, -20, -5, 46, -17, -9, + 46, -15, -14, 46, -12, -18, 47, -10, -23, 47, -7, -27, + 47, -4, -31, 48, -1, -35, 48, 2, -40, 49, 5, -43, + 49, 8, -47, 50, 11, -52, 50, 14, -55, 51, 17, -59, + 51, 20, -63, 52, 23, -67, 52, 26, -71, 53, 29, -74, + 45, -39, 49, 45, -39, 48, 45, -38, 46, 45, -38, 43, + 45, -37, 40, 45, -36, 37, 45, -35, 33, 45, -34, 28, + 46, -33, 24, 46, -31, 20, 46, -30, 15, 46, -28, 11, + 46, -26, 6, 46, -24, 1, 47, -22, -3, 47, -20, -7, + 47, -17, -12, 48, -14, -16, 48, -12, -21, 48, -9, -25, + 49, -6, -30, 49, -3, -34, 49, -1, -38, 50, 2, -42, + 50, 5, -46, 51, 8, -50, 51, 11, -54, 52, 14, -57, + 52, 17, -61, 53, 20, -65, 53, 23, -69, 54, 26, -72, + 46, -40, 51, 46, -40, 49, 46, -40, 47, 46, -39, 44, + 47, -39, 42, 47, -38, 38, 47, -37, 34, 47, -36, 30, + 47, -35, 26, 47, -33, 21, 47, -32, 17, 47, -30, 13, + 48, -28, 8, 48, -26, 3, 48, -24, -1, 48, -22, -5, + 49, -19, -10, 49, -17, -15, 49, -14, -19, 49, -11, -23, + 50, -8, -28, 50, -6, -32, 51, -3, -36, 51, 0, -40, + 51, 3, -44, 52, 6, -48, 52, 9, -52, 53, 12, -56, + 53, 15, -59, 54, 18, -63, 54, 21, -67, 55, 24, -71, + 48, -42, 52, 48, -42, 50, 48, -42, 49, 48, -41, 46, + 48, -41, 43, 48, -40, 40, 48, -39, 36, 48, -38, 32, + 49, -37, 28, 49, -36, 24, 49, -34, 19, 49, -32, 15, + 49, -30, 10, 49, -28, 6, 50, -26, 1, 50, -24, -3, + 50, -22, -8, 50, -19, -12, 51, -17, -16, 51, -14, -21, + 51, -11, -25, 52, -9, -29, 52, -6, -33, 52, -3, -37, + 53, 0, -41, 53, 3, -46, 54, 6, -50, 54, 9, -53, + 55, 12, -57, 55, 15, -61, 56, 18, -65, 56, 21, -69, + 49, -44, 53, 49, -44, 51, 49, -43, 50, 50, -43, 47, + 50, -42, 45, 50, -42, 41, 50, -41, 38, 50, -40, 34, + 50, -39, 30, 50, -37, 25, 50, -36, 21, 50, -34, 17, + 51, -32, 12, 51, -30, 8, 51, -28, 3, 51, -26, -1, + 51, -24, -6, 52, -21, -10, 52, -19, -15, 52, -16, -19, + 53, -14, -23, 53, -11, -28, 53, -8, -32, 54, -5, -36, + 54, -3, -40, 54, 1, -44, 55, 3, -48, 55, 6, -52, + 56, 9, -55, 56, 12, -60, 57, 15, -63, 57, 18, -67, + 51, -45, 54, 51, -45, 53, 51, -45, 51, 51, -44, 49, + 51, -44, 46, 51, -43, 43, 51, -42, 39, 51, -41, 35, + 51, -40, 31, 51, -39, 27, 51, -38, 23, 52, -36, 19, + 52, -34, 14, 52, -32, 9, 52, -30, 5, 52, -28, 1, + 53, -26, -4, 53, -23, -8, 53, -21, -13, 53, -19, -17, + 54, -16, -22, 54, -13, -26, 54, -10, -30, 55, -8, -34, + 55, -5, -38, 56, -2, -42, 56, 1, -46, 56, 4, -50, + 57, 7, -54, 57, 10, -58, 58, 13, -61, 58, 16, -65, + 52, -47, 55, 52, -47, 54, 52, -46, 52, 52, -46, 50, + 52, -45, 47, 52, -45, 44, 52, -44, 41, 52, -43, 37, + 53, -42, 33, 53, -41, 29, 53, -39, 25, 53, -38, 20, + 53, -36, 15, 53, -34, 11, 53, -32, 7, 54, -30, 3, + 54, -28, -2, 54, -26, -7, 54, -23, -11, 55, -21, -15, + 55, -18, -20, 55, -15, -24, 56, -13, -28, 56, -10, -32, + 56, -7, -36, 57, -4, -40, 57, -1, -44, 58, 2, -48, + 58, 4, -52, 58, 8, -56, 59, 10, -60, 59, 13, -63, + 53, -48, 56, 53, -48, 55, 53, -48, 53, 54, -47, 51, + 54, -47, 49, 54, -46, 46, 54, -46, 42, 54, -45, 38, + 54, -43, 34, 54, -42, 30, 54, -41, 26, 54, -39, 22, + 54, -38, 17, 55, -36, 13, 55, -34, 9, 55, -32, 4, + 55, -30, 0, 55, -28, -5, 56, -25, -9, 56, -23, -13, + 56, -20, -18, 57, -18, -22, 57, -15, -26, 57, -12, -30, + 58, -10, -34, 58, -6, -38, 58, -4, -42, 59, -1, -46, + 59, 2, -50, 60, 5, -54, 60, 8, -58, 60, 11, -62, + 55, -50, 57, 55, -50, 56, 55, -49, 54, 55, -49, 52, + 55, -48, 50, 55, -48, 47, 55, -47, 44, 55, -46, 40, + 55, -45, 36, 55, -44, 32, 55, -43, 28, 56, -41, 24, + 56, -39, 19, 56, -38, 15, 56, -36, 10, 56, -34, 6, + 56, -32, 1, 57, -29, -3, 57, -27, -7, 57, -25, -11, + 57, -22, -16, 58, -20, -20, 58, -17, -24, 58, -14, -28, + 59, -12, -32, 59, -9, -37, 59, -6, -40, 60, -3, -44, + 60, 0, -48, 61, 3, -52, 61, 6, -56, 62, 9, -60, + 56, -51, 58, 56, -51, 57, 56, -51, 56, 56, -50, 53, + 56, -50, 51, 56, -49, 48, 56, -49, 45, 56, -48, 41, + 56, -47, 38, 57, -45, 34, 57, -44, 30, 57, -43, 25, + 57, -41, 21, 57, -39, 17, 57, -38, 12, 58, -36, 8, + 58, -34, 3, 58, -31, -1, 58, -29, -5, 58, -27, -9, + 59, -24, -14, 59, -22, -18, 59, -19, -22, 60, -17, -26, + 60, -14, -30, 60, -11, -35, 61, -8, -39, 61, -5, -43, + 61, -3, -46, 62, 1, -51, 62, 3, -54, 63, 6, -58, + 57, -53, 59, 57, -52, 58, 57, -52, 57, 57, -52, 55, + 58, -51, 52, 58, -51, 50, 58, -50, 47, 58, -49, 43, + 58, -48, 39, 58, -47, 35, 58, -46, 31, 58, -44, 27, + 58, -43, 22, 58, -41, 18, 59, -39, 14, 59, -38, 10, + 59, -35, 5, 59, -33, 1, 59, -31, -3, 60, -29, -8, + 60, -26, -12, 60, -24, -16, 61, -21, -20, 61, -19, -24, + 61, -16, -28, 61, -13, -33, 62, -10, -37, 62, -8, -41, + 63, -5, -45, 63, -2, -49, 63, 1, -53, 64, 4, -56, + 59, -54, 60, 59, -54, 59, 59, -54, 58, 59, -53, 56, + 59, -53, 54, 59, -52, 51, 59, -51, 48, 59, -51, 44, + 59, -50, 41, 59, -49, 37, 59, -47, 33, 59, -46, 29, + 60, -44, 24, 60, -43, 20, 60, -41, 16, 60, -39, 12, + 60, -37, 7, 60, -35, 3, 61, -33, -2, 61, -31, -6, + 61, -28, -10, 61, -26, -14, 62, -23, -19, 62, -21, -23, + 62, -18, -27, 63, -15, -31, 63, -13, -35, 63, -10, -39, + 64, -7, -43, 64, -4, -47, 65, -1, -51, 65, 2, -55, + 60, -55, 61, 60, -55, 60, 60, -55, 59, 60, -55, 57, + 60, -54, 55, 60, -54, 52, 60, -53, 49, 60, -52, 46, + 60, -51, 42, 60, -50, 38, 61, -49, 34, 61, -48, 30, + 61, -46, 26, 61, -45, 22, 61, -43, 18, 61, -41, 13, + 62, -39, 9, 62, -37, 4, 62, -35, 0, 62, -33, -4, + 62, -30, -9, 63, -28, -13, 63, -25, -17, 63, -23, -21, + 64, -20, -25, 64, -17, -29, 64, -15, -33, 65, -12, -37, + 65, -9, -41, 65, -6, -45, 66, -4, -49, 66, -1, -53, + 61, -57, 62, 61, -56, 61, 61, -56, 60, 61, -56, 58, + 61, -55, 56, 61, -55, 54, 62, -54, 51, 62, -53, 47, + 62, -53, 44, 62, -52, 40, 62, -50, 36, 62, -49, 32, + 62, -48, 28, 62, -46, 23, 62, -45, 19, 63, -43, 15, + 63, -41, 10, 63, -39, 6, 63, -37, 2, 63, -35, -2, + 64, -32, -7, 64, -30, -11, 64, -27, -15, 64, -25, -19, + 65, -22, -23, 65, -20, -27, 65, -17, -31, 66, -14, -35, + 66, -12, -39, 66, -9, -43, 67, -6, -47, 67, -3, -51, + 63, -58, 63, 63, -58, 62, 63, -58, 61, 63, -57, 59, + 63, -57, 57, 63, -56, 55, 63, -56, 52, 63, -55, 48, + 63, -54, 45, 63, -53, 41, 63, -52, 38, 63, -51, 34, + 63, -49, 29, 64, -48, 25, 64, -46, 21, 64, -44, 17, + 64, -42, 12, 64, -41, 8, 64, -39, 4, 65, -36, 0, + 65, -34, -5, 65, -32, -9, 65, -29, -13, 66, -27, -17, + 66, -25, -21, 66, -22, -26, 67, -19, -30, 67, -16, -33, + 67, -14, -37, 68, -11, -42, 68, -8, -45, 68, -5, -49, + 64, -59, 64, 64, -59, 63, 64, -59, 62, 64, -59, 61, + 64, -58, 59, 64, -58, 56, 64, -57, 53, 64, -56, 50, + 64, -55, 46, 64, -54, 43, 64, -53, 39, 65, -52, 35, + 65, -51, 31, 65, -49, 27, 65, -48, 23, 65, -46, 19, + 65, -44, 14, 65, -42, 10, 66, -40, 6, 66, -38, 2, + 66, -36, -3, 66, -34, -7, 67, -31, -11, 67, -29, -15, + 67, -27, -19, 67, -24, -24, 68, -21, -28, 68, -19, -32, + 68, -16, -35, 69, -13, -40, 69, -10, -44, 69, -7, -47, + 65, -61, 65, 65, -60, 64, 65, -60, 63, 65, -60, 62, + 65, -60, 60, 65, -59, 57, 65, -58, 55, 65, -58, 51, + 66, -57, 48, 66, -56, 44, 66, -55, 41, 66, -54, 37, + 66, -52, 33, 66, -51, 29, 66, -49, 24, 66, -48, 20, + 67, -46, 16, 67, -44, 12, 67, -42, 7, 67, -40, 3, + 67, -38, -1, 68, -36, -5, 68, -33, -9, 68, -31, -13, + 68, -29, -17, 69, -26, -22, 69, -23, -26, 69, -21, -30, + 70, -18, -34, 70, -15, -38, 70, -12, -42, 71, -10, -46, + 67, -62, 66, 67, -62, 66, 67, -61, 64, 67, -61, 63, + 67, -61, 61, 67, -60, 59, 67, -60, 56, 67, -59, 53, + 67, -58, 49, 67, -57, 46, 67, -56, 42, 67, -55, 38, + 67, -54, 34, 67, -52, 30, 67, -51, 26, 68, -49, 22, + 68, -47, 17, 68, -46, 13, 68, -44, 9, 68, -42, 5, + 69, -40, 1, 69, -37, -4, 69, -35, -8, 69, -33, -12, + 70, -30, -16, 70, -28, -20, 70, -25, -24, 70, -23, -28, + 71, -20, -32, 71, -17, -36, 71, -15, -40, 72, -12, -44, + 68, -63, 67, 68, -63, 67, 68, -63, 66, 68, -62, 64, + 68, -62, 62, 68, -62, 60, 68, -61, 57, 68, -60, 54, + 68, -60, 51, 68, -59, 47, 68, -58, 44, 68, -57, 40, + 68, -55, 36, 69, -54, 32, 69, -52, 28, 69, -51, 24, + 69, -49, 19, 69, -47, 15, 69, -45, 11, 70, -44, 7, + 70, -41, 2, 70, -39, -2, 70, -37, -6, 70, -35, -10, + 71, -32, -14, 71, -30, -18, 71, -27, -22, 72, -25, -26, + 72, -22, -30, 72, -19, -34, 73, -17, -38, 73, -14, -42, + 69, -64, 69, 69, -64, 68, 69, -64, 67, 69, -64, 65, + 69, -63, 63, 69, -63, 61, 69, -62, 59, 69, -62, 55, + 69, -61, 52, 69, -60, 49, 70, -59, 45, 70, -58, 42, + 70, -57, 37, 70, -55, 33, 70, -54, 29, 70, -52, 25, + 70, -51, 21, 70, -49, 17, 71, -47, 13, 71, -45, 9, + 71, -43, 4, 71, -41, 0, 71, -39, -4, 72, -37, -8, + 72, -34, -12, 72, -32, -17, 72, -29, -20, 73, -27, -24, + 73, -24, -28, 73, -21, -33, 74, -19, -36, 74, -16, -40, + 70, -66, 70, 70, -65, 69, 70, -65, 68, 70, -65, 66, + 70, -65, 64, 70, -64, 62, 71, -64, 60, 71, -63, 57, + 71, -62, 54, 71, -61, 50, 71, -60, 47, 71, -59, 43, + 71, -58, 39, 71, -57, 35, 71, -55, 31, 71, -54, 27, + 72, -52, 23, 72, -51, 19, 72, -49, 15, 72, -47, 10, + 72, -45, 6, 72, -43, 2, 73, -41, -2, 73, -38, -6, + 73, -36, -10, 73, -34, -15, 74, -31, -19, 74, -29, -23, + 74, -26, -26, 75, -23, -31, 75, -21, -35, 75, -18, -38, + 72, -67, 71, 72, -67, 70, 72, -67, 69, 72, -66, 67, + 72, -66, 66, 72, -65, 63, 72, -65, 61, 72, -64, 58, + 72, -64, 55, 72, -63, 52, 72, -62, 48, 72, -61, 45, + 72, -60, 40, 72, -58, 37, 72, -57, 33, 73, -56, 29, + 73, -54, 24, 73, -52, 20, 73, -50, 16, 73, -49, 12, + 73, -46, 8, 74, -44, 4, 74, -42, 0, 74, -40, -4, + 74, -38, -8, 75, -35, -13, 75, -33, -17, 75, -31, -21, + 75, -28, -25, 76, -25, -29, 76, -23, -33, 76, -20, -37, + 73, -68, 72, 73, -68, 71, 73, -68, 70, 73, -67, 68, + 73, -67, 67, 73, -67, 65, 73, -66, 62, 73, -66, 59, + 73, -65, 56, 73, -64, 53, 73, -63, 50, 73, -62, 46, + 74, -61, 42, 74, -60, 38, 74, -58, 34, 74, -57, 30, + 74, -55, 26, 74, -54, 22, 74, -52, 18, 74, -50, 14, + 75, -48, 9, 75, -46, 5, 75, -44, 1, 75, -42, -3, + 75, -40, -7, 76, -37, -11, 76, -35, -15, 76, -33, -19, + 77, -30, -23, 77, -27, -27, 77, -25, -31, 77, -22, -35, + 74, -69, 73, 74, -69, 72, 74, -69, 71, 74, -69, 69, + 74, -68, 68, 74, -68, 66, 74, -68, 63, 74, -67, 61, + 74, -66, 58, 75, -65, 54, 75, -65, 51, 75, -64, 48, + 75, -62, 44, 75, -61, 40, 75, -60, 36, 75, -58, 32, + 75, -57, 28, 75, -55, 24, 76, -54, 20, 76, -52, 16, + 76, -50, 11, 76, -48, 7, 76, -46, 3, 76, -44, -1, + 77, -42, -5, 77, -39, -9, 77, -37, -13, 77, -34, -17, + 78, -32, -21, 78, -29, -25, 78, -27, -29, 79, -24, -33, + 76, -71, 74, 76, -71, 73, 76, -70, 72, 76, -70, 71, + 76, -70, 69, 76, -70, 67, 76, -69, 65, 76, -68, 62, + 76, -68, 59, 76, -67, 56, 76, -66, 53, 76, -65, 49, + 76, -64, 45, 76, -63, 42, 77, -62, 38, 77, -60, 34, + 77, -59, 30, 77, -57, 26, 77, -56, 22, 77, -54, 18, + 77, -52, 13, 78, -50, 9, 78, -48, 5, 78, -46, 1, + 78, -44, -3, 78, -41, -7, 79, -39, -11, 79, -37, -15, + 79, -34, -19, 79, -32, -23, 80, -29, -27, 80, -27, -31, + 77, -72, 75, 77, -72, 74, 77, -72, 73, 77, -71, 72, + 77, -71, 70, 77, -71, 68, 77, -70, 66, 77, -70, 63, + 77, -69, 61, 77, -68, 58, 77, -67, 54, 77, -67, 51, + 78, -65, 47, 78, -64, 43, 78, -63, 39, 78, -62, 36, + 78, -60, 31, 78, -59, 27, 78, -57, 23, 78, -55, 19, + 79, -53, 15, 79, -52, 11, 79, -50, 7, 79, -48, 3, + 79, -46, -1, 80, -43, -5, 80, -41, -9, 80, -39, -13, + 80, -36, -17, 81, -34, -21, 81, -31, -25, 81, -29, -29, + 78, -73, 76, 78, -73, 75, 78, -73, 74, 78, -73, 73, + 78, -72, 71, 78, -72, 70, 78, -72, 67, 78, -71, 65, + 79, -70, 62, 79, -70, 59, 79, -69, 56, 79, -68, 52, + 79, -67, 48, 79, -66, 45, 79, -64, 41, 79, -63, 37, + 79, -62, 33, 79, -60, 29, 80, -59, 25, 80, -57, 21, + 80, -55, 17, 80, -53, 13, 80, -51, 9, 80, -49, 5, + 81, -47, 1, 81, -45, -4, 81, -43, -8, 81, -40, -11, + 82, -38, -15, 82, -36, -20, 82, -33, -24, 82, -31, -27, + 80, -74, 77, 80, -74, 76, 80, -74, 75, 80, -74, 74, + 80, -74, 73, 80, -73, 71, 80, -73, 69, 80, -72, 66, + 80, -72, 63, 80, -71, 60, 80, -70, 57, 80, -69, 54, + 80, -68, 50, 80, -67, 46, 80, -66, 43, 80, -65, 39, + 80, -63, 35, 81, -62, 31, 81, -60, 27, 81, -58, 23, + 81, -57, 18, 81, -55, 14, 81, -53, 10, 82, -51, 6, + 82, -49, 3, 82, -47, -2, 82, -44, -6, 82, -42, -10, + 83, -40, -14, 83, -37, -18, 83, -35, -22, 83, -33, -26, + 81, -75, 78, 81, -75, 77, 81, -75, 76, 81, -75, 75, + 81, -75, 74, 81, -74, 72, 81, -74, 70, 81, -73, 67, + 81, -73, 64, 81, -72, 62, 81, -71, 58, 81, -70, 55, + 81, -69, 51, 81, -68, 48, 81, -67, 44, 82, -66, 40, + 82, -64, 36, 82, -63, 32, 82, -62, 28, 82, -60, 24, + 82, -58, 20, 82, -56, 16, 83, -54, 12, 83, -53, 8, + 83, -51, 4, 83, -48, 0, 83, -46, -4, 84, -44, -8, + 84, -42, -12, 84, -39, -16, 84, -37, -20, 85, -35, -24, + 82, -77, 79, 82, -76, 78, 82, -76, 77, 82, -76, 76, + 82, -76, 75, 82, -76, 73, 82, -75, 71, 82, -75, 68, + 82, -74, 66, 82, -73, 63, 82, -73, 60, 82, -72, 57, + 82, -71, 53, 83, -70, 49, 83, -68, 46, 83, -67, 42, + 83, -66, 38, 83, -64, 34, 83, -63, 30, 83, -61, 26, + 83, -60, 22, 84, -58, 18, 84, -56, 14, 84, -54, 10, + 84, -52, 6, 84, -50, 2, 85, -48, -2, 85, -46, -6, + 85, -44, -10, 85, -41, -14, 86, -39, -18, 86, -36, -22, + 83, -78, 80, 83, -78, 79, 83, -78, 78, 83, -77, 77, + 83, -77, 76, 83, -77, 74, 83, -76, 72, 83, -76, 70, + 83, -75, 67, 84, -75, 64, 84, -74, 61, 84, -73, 58, + 84, -72, 54, 84, -71, 51, 84, -70, 47, 84, -69, 43, + 84, -67, 39, 84, -66, 35, 84, -64, 32, 85, -63, 28, + 85, -61, 23, 85, -59, 19, 85, -58, 16, 85, -56, 12, + 85, -54, 8, 86, -52, 3, 86, -50, -1, 86, -47, -5, + 86, -45, -8, 86, -43, -13, 87, -41, -17, 87, -38, -20, + 84, -79, 81, 84, -79, 80, 85, -79, 79, 85, -78, 78, + 85, -78, 77, 85, -78, 75, 85, -77, 73, 85, -77, 71, + 85, -76, 68, 85, -76, 65, 85, -75, 62, 85, -74, 59, + 85, -73, 56, 85, -72, 52, 85, -71, 49, 85, -70, 45, + 85, -69, 41, 85, -67, 37, 86, -66, 33, 86, -64, 29, + 86, -63, 25, 86, -61, 21, 86, -59, 17, 86, -57, 13, + 87, -55, 9, 87, -53, 5, 87, -51, 1, 87, -49, -3, + 87, -47, -7, 88, -45, -11, 88, -42, -15, 88, -40, -19, + 86, -80, 82, 86, -80, 81, 86, -80, 80, 86, -80, 79, + 86, -79, 78, 86, -79, 76, 86, -79, 74, 86, -78, 72, + 86, -78, 69, 86, -77, 67, 86, -76, 64, 86, -75, 61, + 86, -74, 57, 86, -73, 54, 86, -72, 50, 86, -71, 46, + 87, -70, 42, 87, -69, 39, 87, -67, 35, 87, -66, 31, + 87, -64, 27, 87, -62, 23, 87, -61, 19, 88, -59, 15, + 88, -57, 11, 88, -55, 7, 88, -53, 3, 88, -51, -1, + 89, -49, -5, 89, -46, -9, 89, -44, -13, 89, -42, -17, + 87, -81, 83, 87, -81, 82, 87, -81, 81, 87, -81, 80, + 87, -80, 79, 87, -80, 77, 87, -80, 76, 87, -79, 73, + 87, -79, 71, 87, -78, 68, 87, -77, 65, 87, -77, 62, + 87, -76, 58, 87, -75, 55, 88, -74, 52, 88, -73, 48, + 88, -71, 44, 88, -70, 40, 88, -69, 36, 88, -67, 33, + 88, -65, 28, 88, -64, 24, 89, -62, 21, 89, -60, 17, + 89, -59, 13, 89, -57, 8, 89, -55, 4, 89, -53, 1, + 90, -50, -3, 90, -48, -8, 90, -46, -11, 90, -44, -15, + 88, -82, 84, 88, -82, 83, 88, -82, 82, 88, -82, 81, + 88, -82, 80, 88, -81, 79, 88, -81, 77, 88, -80, 74, + 88, -80, 72, 88, -79, 69, 88, -79, 66, 89, -78, 63, + 89, -77, 60, 89, -76, 56, 89, -75, 53, 89, -74, 49, + 89, -73, 45, 89, -71, 42, 89, -70, 38, 89, -69, 34, + 89, -67, 30, 90, -65, 26, 90, -64, 22, 90, -62, 18, + 90, -60, 14, 90, -58, 10, 90, -56, 6, 91, -54, 2, + 91, -52, -2, 91, -50, -6, 91, -48, -10, 92, -45, -13, + 10, 30, 16, 10, 31, 13, 11, 31, 10, 11, 32, 4, + 11, 33, -2, 11, 33, -7, 12, 35, -13, 12, 36, -18, + 13, 37, -23, 14, 39, -28, 14, 40, -32, 15, 42, -36, + 16, 44, -41, 17, 46, -45, 17, 47, -49, 18, 49, -52, + 19, 51, -57, 20, 53, -60, 21, 55, -63, 22, 57, -67, + 23, 59, -70, 24, 61, -74, 25, 63, -77, 26, 65, -80, + 27, 67, -83, 28, 69, -86, 29, 71, -89, 30, 72, -92, + 32, 74, -95, 33, 76, -98, 34, 78, -101, 35, 80, -104, + 11, 29, 17, 11, 29, 14, 11, 29, 10, 11, 30, 5, + 12, 31, -1, 12, 32, -6, 12, 33, -12, 13, 34, -17, + 13, 36, -22, 14, 37, -27, 15, 39, -31, 15, 41, -36, + 16, 43, -40, 17, 44, -44, 18, 46, -48, 19, 48, -52, + 20, 50, -56, 21, 52, -59, 22, 54, -63, 22, 56, -66, + 24, 58, -70, 25, 60, -73, 26, 62, -76, 27, 64, -79, + 28, 66, -83, 29, 68, -86, 30, 70, -89, 31, 72, -92, + 32, 74, -95, 33, 76, -98, 34, 78, -101, 35, 80, -103, + 11, 27, 18, 12, 27, 15, 12, 28, 11, 12, 28, 6, + 12, 29, 0, 13, 30, -6, 13, 31, -11, 13, 33, -17, + 14, 34, -21, 15, 36, -26, 15, 37, -31, 16, 39, -35, + 17, 41, -40, 17, 43, -44, 18, 45, -47, 19, 47, -51, + 20, 49, -55, 21, 51, -59, 22, 53, -62, 23, 55, -66, + 24, 57, -69, 25, 59, -73, 26, 61, -76, 27, 63, -79, + 28, 65, -82, 29, 67, -86, 30, 69, -88, 31, 71, -91, + 32, 73, -94, 33, 75, -98, 34, 77, -100, 35, 79, -103, + 12, 25, 19, 12, 26, 16, 12, 26, 12, 13, 27, 6, + 13, 28, 1, 13, 29, -5, 13, 30, -10, 14, 31, -16, + 14, 33, -21, 15, 34, -25, 16, 36, -30, 16, 38, -34, + 17, 40, -39, 18, 42, -43, 19, 44, -47, 19, 46, -51, + 20, 48, -55, 21, 50, -58, 22, 52, -62, 23, 54, -65, + 24, 56, -69, 25, 58, -72, 26, 60, -75, 27, 62, -79, + 28, 64, -82, 29, 67, -85, 30, 69, -88, 31, 71, -91, + 32, 72, -94, 33, 75, -97, 34, 77, -100, 35, 78, -103, + 13, 23, 20, 13, 24, 17, 13, 24, 13, 13, 25, 7, + 13, 26, 2, 14, 27, -4, 14, 28, -9, 15, 30, -15, + 15, 31, -20, 16, 33, -24, 16, 34, -29, 17, 36, -33, + 18, 38, -38, 18, 40, -42, 19, 42, -46, 20, 45, -50, + 21, 47, -54, 22, 49, -58, 23, 51, -61, 23, 53, -65, + 24, 55, -68, 25, 57, -72, 26, 59, -75, 27, 61, -78, + 28, 64, -81, 29, 66, -85, 30, 68, -88, 31, 70, -91, + 32, 72, -94, 33, 74, -97, 34, 76, -100, 35, 78, -102, + 14, 21, 21, 14, 22, 18, 14, 22, 14, 14, 23, 8, + 14, 24, 3, 15, 25, -3, 15, 26, -8, 15, 28, -14, + 16, 29, -19, 16, 31, -23, 17, 33, -28, 17, 35, -32, + 18, 37, -37, 19, 39, -41, 20, 41, -45, 20, 43, -49, + 21, 45, -53, 22, 47, -57, 23, 50, -60, 24, 52, -64, + 25, 54, -68, 26, 56, -71, 27, 58, -74, 28, 60, -78, + 29, 62, -81, 30, 65, -84, 31, 67, -87, 32, 69, -90, + 33, 71, -93, 34, 73, -96, 35, 75, -99, 36, 77, -102, + 14, 19, 22, 15, 19, 19, 15, 20, 15, 15, 20, 9, + 15, 21, 4, 15, 22, -1, 16, 24, -7, 16, 25, -12, + 17, 27, -17, 17, 29, -22, 18, 31, -27, 18, 33, -31, + 19, 35, -36, 20, 37, -40, 20, 39, -44, 21, 41, -48, + 22, 44, -52, 23, 46, -56, 24, 48, -60, 24, 50, -63, + 25, 53, -67, 26, 55, -70, 27, 57, -74, 28, 59, -77, + 29, 61, -80, 30, 64, -83, 31, 66, -87, 32, 68, -90, + 33, 70, -93, 34, 72, -96, 35, 74, -99, 36, 76, -102, + 15, 16, 23, 16, 17, 20, 16, 17, 16, 16, 18, 11, + 16, 19, 5, 16, 20, 0, 17, 21, -5, 17, 23, -11, + 17, 25, -16, 18, 27, -21, 18, 28, -25, 19, 30, -30, + 20, 33, -35, 20, 35, -39, 21, 37, -43, 22, 39, -47, + 22, 42, -51, 23, 44, -55, 24, 46, -59, 25, 49, -62, + 26, 51, -66, 27, 53, -69, 28, 56, -73, 29, 58, -76, + 29, 60, -79, 31, 62, -83, 31, 64, -86, 32, 67, -89, + 33, 69, -92, 34, 71, -95, 35, 73, -98, 36, 75, -101, + 16, 14, 24, 17, 14, 21, 17, 15, 18, 17, 16, 12, + 17, 16, 7, 17, 18, 1, 18, 19, -4, 18, 21, -10, + 18, 22, -15, 19, 24, -19, 19, 26, -24, 20, 28, -29, + 20, 31, -33, 21, 33, -38, 22, 35, -42, 22, 37, -46, + 23, 40, -50, 24, 42, -54, 25, 45, -58, 26, 47, -61, + 26, 49, -65, 27, 52, -69, 28, 54, -72, 29, 56, -75, + 30, 58, -78, 31, 61, -82, 32, 63, -85, 33, 65, -88, + 34, 67, -91, 35, 70, -95, 36, 72, -97, 37, 74, -100, + 18, 11, 26, 18, 12, 23, 18, 12, 19, 18, 13, 13, + 18, 14, 8, 18, 15, 3, 19, 16, -2, 19, 18, -8, + 19, 20, -13, 20, 22, -18, 20, 24, -23, 21, 26, -27, + 21, 28, -32, 22, 30, -36, 23, 33, -40, 23, 35, -45, + 24, 38, -49, 25, 40, -53, 25, 43, -56, 26, 45, -60, + 27, 48, -64, 28, 50, -68, 29, 52, -71, 30, 55, -74, + 31, 57, -77, 32, 59, -81, 32, 62, -84, 33, 64, -87, + 34, 66, -90, 35, 68, -94, 36, 71, -97, 37, 73, -100, + 19, 9, 27, 19, 9, 24, 19, 9, 20, 19, 10, 15, + 19, 11, 10, 19, 12, 4, 20, 14, -1, 20, 15, -7, + 20, 17, -11, 21, 19, -16, 21, 21, -21, 22, 23, -26, + 22, 26, -31, 23, 28, -35, 23, 30, -39, 24, 33, -43, + 25, 36, -48, 25, 38, -51, 26, 40, -55, 27, 43, -59, + 28, 46, -63, 29, 48, -66, 29, 50, -70, 30, 53, -73, + 31, 55, -76, 32, 58, -80, 33, 60, -83, 34, 62, -86, + 35, 64, -90, 36, 67, -93, 37, 69, -96, 38, 71, -99, + 20, 5, 29, 20, 6, 26, 20, 6, 22, 20, 7, 17, + 21, 8, 12, 21, 9, 6, 21, 10, 1, 21, 12, -4, + 22, 14, -9, 22, 16, -14, 22, 18, -19, 23, 20, -24, + 23, 23, -29, 24, 25, -33, 25, 27, -37, 25, 30, -41, + 26, 33, -46, 27, 35, -50, 27, 38, -54, 28, 40, -57, + 29, 43, -61, 30, 45, -65, 30, 48, -68, 31, 50, -72, + 32, 53, -75, 33, 55, -79, 34, 58, -82, 35, 60, -85, + 35, 62, -88, 36, 65, -92, 37, 67, -95, 38, 69, -98, + 21, 3, 30, 21, 3, 27, 22, 4, 23, 22, 4, 18, + 22, 5, 13, 22, 6, 8, 22, 8, 3, 23, 9, -3, + 23, 11, -8, 23, 13, -13, 24, 15, -17, 24, 17, -22, + 24, 20, -27, 25, 22, -31, 26, 25, -36, 26, 27, -40, + 27, 30, -44, 27, 33, -48, 28, 35, -52, 29, 38, -56, + 30, 41, -60, 30, 43, -64, 31, 46, -67, 32, 48, -71, + 33, 51, -74, 34, 53, -78, 34, 56, -81, 35, 58, -84, + 36, 61, -87, 37, 63, -91, 38, 65, -94, 39, 68, -97, + 23, 0, 32, 23, 0, 28, 23, 1, 25, 23, 2, 20, + 23, 3, 15, 23, 4, 10, 23, 5, 5, 24, 7, -1, + 24, 9, -6, 24, 10, -11, 25, 13, -16, 25, 15, -20, + 26, 17, -25, 26, 20, -30, 27, 22, -34, 27, 25, -38, + 28, 28, -43, 28, 30, -47, 29, 33, -51, 30, 35, -54, + 30, 38, -59, 31, 41, -62, 32, 43, -66, 33, 46, -69, + 33, 48, -73, 34, 51, -76, 35, 54, -80, 36, 56, -83, + 37, 59, -86, 38, 61, -90, 39, 64, -93, 39, 66, -96, + 24, -3, 33, 24, -2, 30, 24, -2, 26, 24, -1, 21, + 24, 0, 16, 24, 1, 11, 25, 2, 6, 25, 4, 1, + 25, 6, -4, 25, 8, -9, 26, 10, -14, 26, 12, -18, + 27, 15, -23, 27, 17, -28, 28, 20, -32, 28, 22, -36, + 29, 25, -41, 29, 28, -45, 30, 30, -49, 31, 33, -53, + 31, 36, -57, 32, 38, -61, 33, 41, -64, 33, 44, -68, + 34, 46, -71, 35, 49, -75, 36, 52, -79, 37, 54, -82, + 37, 57, -85, 38, 59, -89, 39, 62, -92, 40, 64, -95, + 25, -5, 34, 25, -5, 31, 25, -4, 27, 25, -3, 23, + 26, -3, 18, 26, -1, 13, 26, 0, 8, 26, 2, 3, + 26, 3, -2, 27, 5, -7, 27, 7, -12, 27, 9, -17, + 28, 12, -22, 28, 14, -26, 29, 17, -30, 29, 19, -35, + 30, 22, -39, 30, 25, -43, 31, 28, -47, 32, 30, -51, + 32, 33, -56, 33, 36, -59, 34, 39, -63, 34, 41, -67, + 35, 44, -70, 36, 47, -74, 37, 49, -77, 37, 52, -81, + 38, 54, -84, 39, 57, -87, 40, 60, -91, 41, 62, -94, + 27, -8, 35, 27, -7, 32, 27, -7, 29, 27, -6, 24, + 27, -5, 20, 27, -4, 15, 27, -3, 10, 27, -1, 4, + 28, 1, 0, 28, 3, -5, 28, 5, -10, 29, 7, -15, + 29, 9, -20, 29, 12, -24, 30, 14, -29, 30, 17, -33, + 31, 20, -38, 31, 22, -42, 32, 25, -46, 33, 28, -50, + 33, 31, -54, 34, 34, -58, 35, 36, -61, 35, 39, -65, + 36, 42, -69, 37, 45, -72, 37, 47, -76, 38, 50, -79, + 39, 52, -83, 40, 55, -86, 41, 58, -89, 41, 60, -93, + 28, -10, 36, 28, -10, 33, 28, -9, 30, 28, -8, 26, + 28, -8, 21, 28, -7, 17, 28, -5, 12, 29, -4, 6, + 29, -2, 1, 29, 0, -3, 29, 2, -8, 30, 4, -13, + 30, 7, -18, 31, 9, -22, 31, 12, -27, 31, 14, -31, + 32, 17, -36, 32, 20, -40, 33, 22, -44, 34, 25, -48, + 34, 28, -52, 35, 31, -56, 35, 34, -60, 36, 36, -64, + 37, 39, -67, 38, 42, -71, 38, 45, -75, 39, 47, -78, + 40, 50, -81, 41, 53, -85, 41, 56, -88, 42, 58, -91, + 29, -12, 38, 29, -12, 34, 29, -11, 31, 29, -11, 27, + 29, -10, 23, 30, -9, 18, 30, -8, 13, 30, -6, 8, + 30, -4, 3, 30, -3, -2, 31, -1, -6, 31, 2, -11, + 31, 4, -16, 32, 6, -21, 32, 9, -25, 33, 11, -29, + 33, 14, -34, 34, 17, -38, 34, 20, -42, 35, 23, -46, + 35, 26, -51, 36, 28, -55, 36, 31, -58, 37, 34, -62, + 38, 37, -66, 39, 40, -70, 39, 42, -73, 40, 45, -76, + 41, 48, -80, 41, 51, -84, 42, 53, -87, 43, 56, -90, + 31, -15, 39, 31, -14, 36, 31, -14, 33, 31, -13, 29, + 31, -12, 24, 31, -11, 20, 31, -10, 15, 31, -8, 10, + 31, -7, 5, 32, -5, 0, 32, -3, -4, 32, -1, -9, + 33, 1, -14, 33, 4, -19, 33, 6, -23, 34, 9, -28, + 34, 12, -32, 35, 15, -36, 35, 17, -41, 36, 20, -45, + 36, 23, -49, 37, 26, -53, 37, 29, -57, 38, 31, -60, + 39, 34, -64, 39, 37, -68, 40, 40, -72, 41, 43, -75, + 41, 45, -78, 42, 48, -82, 43, 51, -85, 44, 54, -89, + 32, -17, 40, 32, -16, 37, 32, -16, 34, 32, -15, 30, + 32, -15, 26, 32, -14, 22, 32, -12, 17, 33, -11, 12, + 33, -9, 7, 33, -8, 2, 33, -6, -3, 34, -4, -7, + 34, -1, -12, 34, 1, -17, 35, 4, -21, 35, 6, -26, + 35, 9, -30, 36, 12, -35, 36, 15, -39, 37, 17, -43, + 37, 20, -47, 38, 23, -51, 39, 26, -55, 39, 29, -59, + 40, 32, -62, 40, 35, -66, 41, 38, -70, 42, 40, -74, + 42, 43, -77, 43, 46, -81, 44, 49, -84, 45, 51, -87, + 33, -19, 41, 33, -19, 38, 33, -18, 35, 33, -18, 31, + 33, -17, 27, 34, -16, 23, 34, -15, 19, 34, -13, 13, + 34, -12, 9, 34, -10, 4, 35, -8, -1, 35, -6, -5, + 35, -4, -10, 35, -1, -15, 36, 1, -19, 36, 4, -24, + 37, 7, -29, 37, 9, -33, 37, 12, -37, 38, 15, -41, + 39, 18, -46, 39, 21, -49, 40, 24, -53, 40, 26, -57, + 41, 29, -61, 41, 32, -65, 42, 35, -68, 43, 38, -72, + 43, 41, -75, 44, 44, -79, 45, 46, -83, 45, 49, -86, + 35, -21, 42, 35, -21, 39, 35, -20, 37, 35, -20, 33, + 35, -19, 29, 35, -18, 25, 35, -17, 20, 35, -15, 15, + 35, -14, 11, 36, -12, 6, 36, -10, 1, 36, -8, -3, + 36, -6, -9, 37, -4, -13, 37, -1, -18, 37, 1, -22, + 38, 4, -27, 38, 7, -31, 39, 9, -35, 39, 12, -39, + 40, 15, -44, 40, 18, -48, 41, 21, -52, 41, 24, -55, + 42, 27, -59, 42, 30, -63, 43, 33, -67, 44, 35, -70, + 44, 38, -74, 45, 41, -78, 46, 44, -81, 46, 47, -85, + 36, -23, 43, 36, -23, 40, 36, -22, 38, 36, -22, 34, + 36, -21, 31, 36, -20, 26, 36, -19, 22, 37, -18, 17, + 37, -16, 12, 37, -15, 8, 37, -13, 3, 37, -11, -2, + 38, -8, -7, 38, -6, -11, 38, -4, -16, 39, -1, -20, + 39, 2, -25, 39, 4, -29, 40, 7, -33, 40, 10, -37, + 41, 13, -42, 41, 16, -46, 42, 18, -50, 42, 21, -54, + 43, 24, -57, 43, 27, -62, 44, 30, -65, 45, 33, -69, + 45, 36, -72, 46, 39, -76, 47, 42, -80, 47, 44, -83, + 37, -25, 44, 37, -25, 41, 37, -24, 39, 37, -24, 36, + 38, -23, 32, 38, -22, 28, 38, -21, 24, 38, -20, 19, + 38, -18, 14, 38, -17, 10, 38, -15, 5, 39, -13, 0, + 39, -11, -5, 39, -9, -9, 39, -6, -14, 40, -4, -18, + 40, -1, -23, 41, 2, -27, 41, 4, -31, 41, 7, -36, + 42, 10, -40, 42, 13, -44, 43, 16, -48, 43, 19, -52, + 44, 21, -56, 44, 25, -60, 45, 28, -64, 46, 30, -67, + 46, 33, -71, 47, 36, -75, 48, 39, -78, 48, 42, -82, + 39, -27, 45, 39, -27, 43, 39, -26, 40, 39, -26, 37, + 39, -25, 33, 39, -24, 30, 39, -23, 25, 39, -22, 20, + 39, -20, 16, 40, -19, 11, 40, -17, 7, 40, -15, 2, + 40, -13, -3, 40, -11, -7, 41, -9, -12, 41, -6, -16, + 41, -3, -21, 42, -1, -25, 42, 2, -30, 43, 5, -34, + 43, 8, -38, 43, 10, -42, 44, 13, -46, 44, 16, -50, + 45, 19, -54, 46, 22, -58, 46, 25, -62, 47, 28, -66, + 47, 31, -69, 48, 34, -73, 48, 37, -77, 49, 39, -80, + 40, -29, 46, 40, -29, 44, 40, -28, 42, 40, -28, 38, + 40, -27, 35, 40, -26, 31, 40, -25, 27, 41, -24, 22, + 41, -23, 18, 41, -21, 13, 41, -19, 9, 41, -18, 4, + 41, -15, -1, 42, -13, -5, 42, -11, -10, 42, -9, -14, + 43, -6, -19, 43, -3, -23, 43, -1, -28, 44, 2, -32, + 44, 5, -36, 45, 8, -40, 45, 11, -44, 46, 14, -48, + 46, 16, -52, 47, 20, -56, 47, 22, -60, 48, 25, -64, + 48, 28, -67, 49, 31, -71, 49, 34, -75, 50, 37, -78, + 41, -31, 47, 41, -30, 45, 41, -30, 43, 41, -30, 40, + 42, -29, 36, 42, -28, 33, 42, -27, 29, 42, -26, 24, + 42, -25, 19, 42, -23, 15, 42, -21, 10, 43, -20, 6, + 43, -17, 1, 43, -15, -4, 43, -13, -8, 44, -11, -12, + 44, -8, -17, 44, -6, -22, 45, -3, -26, 45, 0, -30, + 45, 3, -35, 46, 5, -39, 46, 8, -43, 47, 11, -47, + 47, 14, -50, 48, 17, -55, 48, 20, -58, 49, 23, -62, + 49, 26, -66, 50, 29, -70, 50, 32, -73, 51, 35, -77, + 43, -33, 48, 43, -32, 46, 43, -32, 44, 43, -31, 41, + 43, -31, 38, 43, -30, 34, 43, -29, 30, 43, -28, 26, + 43, -27, 21, 43, -25, 17, 44, -24, 12, 44, -22, 8, + 44, -20, 3, 44, -18, -2, 45, -15, -6, 45, -13, -11, + 45, -10, -15, 45, -8, -20, 46, -5, -24, 46, -3, -28, + 47, 0, -33, 47, 3, -37, 47, 6, -41, 48, 9, -45, + 48, 11, -49, 49, 15, -53, 49, 17, -57, 50, 20, -60, + 50, 23, -64, 51, 26, -68, 51, 29, -72, 52, 32, -75, + 44, -34, 49, 44, -34, 47, 44, -34, 45, 44, -33, 42, + 44, -33, 39, 44, -32, 36, 44, -31, 32, 44, -30, 27, + 45, -29, 23, 45, -27, 19, 45, -26, 14, 45, -24, 10, + 45, -22, 5, 46, -20, 0, 46, -18, -4, 46, -15, -9, + 46, -13, -14, 47, -10, -18, 47, -8, -22, 47, -5, -26, + 48, -2, -31, 48, 1, -35, 49, 3, -39, 49, 6, -43, + 49, 9, -47, 50, 12, -51, 50, 15, -55, 51, 18, -59, + 51, 21, -62, 52, 24, -66, 53, 27, -70, 53, 30, -74, + 45, -36, 50, 45, -36, 48, 45, -36, 46, 46, -35, 44, + 46, -34, 41, 46, -34, 37, 46, -33, 33, 46, -32, 29, + 46, -30, 25, 46, -29, 20, 46, -28, 16, 46, -26, 11, + 47, -24, 6, 47, -22, 2, 47, -20, -2, 47, -18, -7, + 48, -15, -12, 48, -13, -16, 48, -10, -20, 49, -8, -24, + 49, -5, -29, 49, -2, -33, 50, 1, -37, 50, 4, -41, + 51, 6, -45, 51, 10, -49, 51, 12, -53, 52, 15, -57, + 52, 18, -61, 53, 21, -65, 54, 24, -68, 54, 27, -72, + 47, -38, 51, 47, -38, 49, 47, -37, 48, 47, -37, 45, + 47, -36, 42, 47, -35, 39, 47, -35, 35, 47, -33, 30, + 47, -32, 26, 47, -31, 22, 48, -29, 18, 48, -28, 13, + 48, -26, 8, 48, -24, 4, 48, -22, -1, 49, -20, -5, + 49, -17, -10, 49, -15, -14, 49, -12, -18, 50, -10, -22, + 50, -7, -27, 51, -4, -31, 51, -2, -35, 51, 1, -39, + 52, 4, -43, 52, 7, -48, 53, 10, -51, 53, 13, -55, + 54, 16, -59, 54, 19, -63, 55, 22, -67, 55, 25, -70, + 48, -40, 52, 48, -40, 51, 48, -39, 49, 49, -39, 46, + 49, -38, 44, 49, -38, 40, 49, -37, 37, 49, -36, 32, + 49, -35, 28, 49, -33, 24, 49, -32, 20, 49, -30, 15, + 50, -28, 11, 50, -26, 6, 50, -24, 2, 50, -22, -3, + 50, -20, -7, 51, -18, -12, 51, -15, -16, 51, -13, -20, + 52, -10, -25, 52, -7, -29, 52, -4, -33, 53, -2, -37, + 53, 1, -41, 54, 4, -45, 54, 7, -49, 54, 10, -53, + 55, 13, -57, 55, 16, -61, 56, 19, -64, 56, 22, -68, + 50, -41, 53, 50, -41, 52, 50, -41, 50, 50, -40, 48, + 50, -40, 45, 50, -39, 42, 50, -38, 38, 50, -37, 34, + 50, -36, 30, 50, -35, 26, 50, -34, 22, 51, -32, 17, + 51, -30, 12, 51, -28, 8, 51, -26, 4, 51, -24, -1, + 52, -22, -6, 52, -20, -10, 52, -17, -14, 53, -15, -18, + 53, -12, -23, 53, -9, -27, 54, -7, -31, 54, -4, -35, + 54, -1, -39, 55, 2, -43, 55, 5, -47, 56, 7, -51, + 56, 10, -55, 57, 13, -59, 57, 16, -63, 57, 19, -66, + 51, -43, 54, 51, -43, 53, 51, -43, 51, 51, -42, 49, + 51, -42, 46, 51, -41, 43, 51, -40, 40, 51, -39, 36, + 52, -38, 32, 52, -37, 27, 52, -35, 23, 52, -34, 19, + 52, -32, 14, 52, -30, 10, 52, -28, 5, 53, -26, 1, + 53, -24, -4, 53, -22, -8, 53, -19, -12, 54, -17, -16, + 54, -14, -21, 54, -12, -25, 55, -9, -29, 55, -6, -33, + 55, -4, -37, 56, -1, -42, 56, 2, -46, 57, 5, -49, + 57, 8, -53, 58, 11, -57, 58, 14, -61, 59, 17, -65, + 52, -45, 55, 52, -44, 54, 52, -44, 53, 52, -44, 50, + 53, -43, 48, 53, -43, 45, 53, -42, 41, 53, -41, 37, + 53, -40, 33, 53, -39, 29, 53, -37, 25, 53, -36, 21, + 53, -34, 16, 54, -32, 12, 54, -30, 7, 54, -28, 3, + 54, -26, -2, 54, -24, -6, 55, -22, -10, 55, -19, -15, + 55, -16, -19, 56, -14, -23, 56, -11, -27, 56, -9, -31, + 57, -6, -35, 57, -3, -40, 57, 0, -44, 58, 3, -48, + 58, 5, -51, 59, 9, -56, 59, 11, -59, 60, 14, -63, + 54, -46, 56, 54, -46, 55, 54, -46, 54, 54, -45, 51, + 54, -45, 49, 54, -44, 46, 54, -43, 43, 54, -42, 39, + 54, -41, 35, 54, -40, 31, 54, -39, 27, 55, -38, 22, + 55, -36, 18, 55, -34, 13, 55, -32, 9, 55, -30, 5, + 55, -28, 0, 56, -26, -4, 56, -24, -8, 56, -21, -13, + 57, -19, -17, 57, -16, -21, 57, -14, -26, 57, -11, -30, + 58, -8, -34, 58, -5, -38, 59, -3, -42, 59, 0, -46, + 59, 3, -50, 60, 6, -54, 60, 9, -58, 61, 12, -61, + 55, -48, 57, 55, -47, 56, 55, -47, 55, 55, -47, 53, + 55, -46, 50, 55, -46, 47, 55, -45, 44, 55, -44, 40, + 55, -43, 36, 56, -42, 32, 56, -41, 28, 56, -39, 24, + 56, -38, 19, 56, -36, 15, 56, -34, 11, 56, -32, 7, + 57, -30, 2, 57, -28, -2, 57, -26, -7, 57, -23, -11, + 58, -21, -16, 58, -18, -20, 58, -16, -24, 59, -13, -28, + 59, -11, -32, 59, -8, -36, 60, -5, -40, 60, -2, -44, + 61, 1, -48, 61, 4, -52, 61, 7, -56, 62, 9, -59, + 56, -49, 59, 56, -49, 57, 56, -49, 56, 56, -48, 54, + 56, -48, 51, 57, -47, 49, 57, -47, 45, 57, -46, 42, + 57, -45, 38, 57, -44, 34, 57, -42, 30, 57, -41, 26, + 57, -39, 21, 57, -38, 17, 58, -36, 13, 58, -34, 8, + 58, -32, 4, 58, -30, -1, 58, -28, -5, 59, -25, -9, + 59, -23, -14, 59, -20, -18, 60, -18, -22, 60, -15, -26, + 60, -13, -30, 61, -10, -34, 61, -7, -38, 61, -4, -42, + 62, -2, -46, 62, 2, -50, 62, 4, -54, 63, 7, -58, + 58, -51, 60, 58, -50, 58, 58, -50, 57, 58, -50, 55, + 58, -49, 53, 58, -49, 50, 58, -48, 47, 58, -47, 43, + 58, -46, 39, 58, -45, 36, 58, -44, 32, 58, -43, 28, + 59, -41, 23, 59, -39, 19, 59, -38, 14, 59, -36, 10, + 59, -34, 5, 59, -32, 1, 60, -30, -3, 60, -27, -7, + 60, -25, -12, 60, -22, -16, 61, -20, -20, 61, -18, -24, + 61, -15, -28, 62, -12, -33, 62, -9, -36, 62, -7, -40, + 63, -4, -44, 63, -1, -48, 64, 2, -52, 64, 5, -56, + 59, -52, 61, 59, -52, 59, 59, -52, 58, 59, -51, 56, + 59, -51, 54, 59, -50, 51, 59, -50, 48, 59, -49, 44, + 59, -48, 41, 59, -47, 37, 60, -46, 33, 60, -44, 29, + 60, -43, 25, 60, -41, 20, 60, -40, 16, 60, -38, 12, + 60, -36, 7, 61, -34, 3, 61, -32, -1, 61, -29, -5, + 61, -27, -10, 62, -25, -14, 62, -22, -18, 62, -20, -22, + 63, -17, -26, 63, -14, -31, 63, -12, -35, 64, -9, -39, + 64, -6, -42, 64, -3, -47, 65, 0, -50, 65, 2, -54, + 60, -53, 62, 60, -53, 60, 60, -53, 59, 60, -53, 57, + 60, -52, 55, 60, -52, 53, 60, -51, 50, 61, -50, 46, + 61, -49, 42, 61, -48, 39, 61, -47, 35, 61, -46, 31, + 61, -44, 26, 61, -43, 22, 61, -41, 18, 62, -40, 14, + 62, -37, 9, 62, -35, 5, 62, -33, 1, 62, -31, -4, + 63, -29, -8, 63, -27, -12, 63, -24, -16, 63, -22, -20, + 64, -19, -24, 64, -16, -29, 64, -14, -33, 65, -11, -37, + 65, -8, -41, 65, -5, -45, 66, -3, -49, 66, 0, -52, + 62, -55, 63, 62, -55, 62, 62, -54, 60, 62, -54, 59, + 62, -54, 56, 62, -53, 54, 62, -53, 51, 62, -52, 47, + 62, -51, 44, 62, -50, 40, 62, -49, 36, 62, -48, 32, + 62, -46, 28, 62, -45, 24, 63, -43, 20, 63, -41, 16, + 63, -39, 11, 63, -37, 7, 63, -35, 2, 64, -33, -2, + 64, -31, -6, 64, -29, -10, 64, -26, -15, 65, -24, -19, + 65, -21, -23, 65, -18, -27, 66, -16, -31, 66, -13, -35, + 66, -11, -39, 67, -8, -43, 67, -5, -47, 67, -2, -51, + 63, -56, 64, 63, -56, 63, 63, -56, 61, 63, -55, 60, + 63, -55, 58, 63, -55, 55, 63, -54, 52, 63, -53, 49, + 63, -52, 45, 63, -51, 42, 63, -50, 38, 63, -49, 34, + 64, -48, 30, 64, -46, 25, 64, -45, 21, 64, -43, 17, + 64, -41, 13, 64, -39, 8, 65, -37, 4, 65, -35, 0, + 65, -33, -5, 65, -30, -9, 66, -28, -13, 66, -26, -17, + 66, -23, -21, 66, -21, -25, 67, -18, -29, 67, -15, -33, + 67, -13, -37, 68, -10, -41, 68, -7, -45, 68, -4, -49, + 64, -57, 65, 64, -57, 64, 64, -57, 63, 64, -57, 61, + 64, -56, 59, 64, -56, 56, 64, -55, 54, 64, -55, 50, + 64, -54, 47, 65, -53, 43, 65, -52, 39, 65, -51, 36, + 65, -49, 31, 65, -48, 27, 65, -46, 23, 65, -45, 19, + 65, -43, 14, 66, -41, 10, 66, -39, 6, 66, -37, 2, + 66, -35, -3, 67, -32, -7, 67, -30, -11, 67, -28, -15, + 67, -25, -19, 68, -23, -23, 68, -20, -27, 68, -18, -31, + 69, -15, -35, 69, -12, -40, 69, -9, -43, 70, -7, -47, + 65, -59, 66, 65, -59, 65, 65, -58, 64, 66, -58, 62, + 66, -58, 60, 66, -57, 58, 66, -57, 55, 66, -56, 52, + 66, -55, 48, 66, -54, 45, 66, -53, 41, 66, -52, 37, + 66, -51, 33, 66, -49, 29, 66, -48, 25, 67, -46, 21, + 67, -44, 16, 67, -43, 12, 67, -41, 8, 67, -39, 4, + 68, -36, -1, 68, -34, -5, 68, -32, -9, 68, -30, -13, + 68, -27, -17, 69, -25, -22, 69, -22, -26, 69, -20, -29, + 70, -17, -33, 70, -14, -38, 70, -11, -42, 71, -9, -45, + 67, -60, 67, 67, -60, 66, 67, -60, 65, 67, -60, 63, + 67, -59, 61, 67, -59, 59, 67, -58, 56, 67, -57, 53, + 67, -57, 50, 67, -56, 46, 67, -55, 43, 67, -54, 39, + 67, -52, 34, 68, -51, 30, 68, -49, 26, 68, -48, 22, + 68, -46, 18, 68, -44, 14, 68, -42, 10, 69, -41, 5, + 69, -38, 1, 69, -36, -3, 69, -34, -7, 69, -32, -11, + 70, -29, -15, 70, -27, -20, 70, -24, -24, 71, -22, -28, + 71, -19, -32, 71, -16, -36, 72, -14, -40, 72, -11, -44, + 68, -61, 68, 68, -61, 67, 68, -61, 66, 68, -61, 64, + 68, -61, 62, 68, -60, 60, 68, -60, 57, 68, -59, 54, + 68, -58, 51, 68, -57, 48, 68, -56, 44, 69, -55, 40, + 69, -54, 36, 69, -52, 32, 69, -51, 28, 69, -50, 24, + 69, -48, 19, 69, -46, 15, 70, -44, 11, 70, -42, 7, + 70, -40, 3, 70, -38, -1, 70, -36, -6, 71, -34, -10, + 71, -31, -14, 71, -29, -18, 71, -26, -22, 72, -24, -26, + 72, -21, -30, 72, -18, -34, 73, -16, -38, 73, -13, -42, + 69, -63, 69, 69, -63, 68, 69, -62, 67, 69, -62, 65, + 69, -62, 63, 69, -61, 61, 69, -61, 59, 70, -60, 56, + 70, -59, 52, 70, -59, 49, 70, -58, 46, 70, -57, 42, + 70, -55, 38, 70, -54, 34, 70, -53, 30, 70, -51, 26, + 70, -49, 21, 71, -48, 17, 71, -46, 13, 71, -44, 9, + 71, -42, 4, 71, -40, 0, 72, -38, -4, 72, -35, -8, + 72, -33, -12, 72, -31, -16, 73, -28, -20, 73, -26, -24, + 73, -23, -28, 74, -20, -32, 74, -18, -36, 74, -15, -40, + 71, -64, 70, 71, -64, 69, 71, -64, 68, 71, -63, 66, + 71, -63, 65, 71, -63, 62, 71, -62, 60, 71, -62, 57, + 71, -61, 54, 71, -60, 50, 71, -59, 47, 71, -58, 43, + 71, -57, 39, 71, -55, 35, 71, -54, 31, 72, -53, 27, + 72, -51, 23, 72, -49, 19, 72, -48, 15, 72, -46, 11, + 72, -44, 6, 73, -42, 2, 73, -39, -2, 73, -37, -6, + 73, -35, -10, 74, -33, -14, 74, -30, -18, 74, -28, -22, + 74, -25, -26, 75, -22, -31, 75, -20, -34, 75, -17, -38, + 72, -65, 71, 72, -65, 70, 72, -65, 69, 72, -65, 67, + 72, -64, 66, 72, -64, 64, 72, -64, 61, 72, -63, 58, + 72, -62, 55, 72, -61, 52, 72, -60, 48, 72, -59, 45, + 72, -58, 41, 73, -57, 37, 73, -56, 33, 73, -54, 29, + 73, -53, 25, 73, -51, 21, 73, -49, 16, 73, -47, 12, + 74, -45, 8, 74, -43, 4, 74, -41, 0, 74, -39, -4, + 74, -37, -8, 75, -34, -13, 75, -32, -17, 75, -30, -21, + 76, -27, -24, 76, -24, -29, 76, -22, -33, 76, -19, -36, + 73, -67, 72, 73, -66, 71, 73, -66, 70, 73, -66, 69, + 73, -66, 67, 73, -65, 65, 73, -65, 62, 73, -64, 59, + 73, -63, 56, 73, -63, 53, 73, -62, 50, 74, -61, 46, + 74, -60, 42, 74, -58, 38, 74, -57, 35, 74, -56, 31, + 74, -54, 26, 74, -52, 22, 74, -51, 18, 75, -49, 14, + 75, -47, 10, 75, -45, 6, 75, -43, 2, 75, -41, -2, + 76, -39, -6, 76, -36, -11, 76, -34, -15, 76, -32, -19, + 77, -29, -23, 77, -26, -27, 77, -24, -31, 78, -21, -35, + 74, -68, 73, 74, -68, 72, 74, -68, 71, 74, -67, 70, + 74, -67, 68, 74, -67, 66, 74, -66, 64, 75, -65, 61, + 75, -65, 58, 75, -64, 55, 75, -63, 51, 75, -62, 48, + 75, -61, 44, 75, -60, 40, 75, -59, 36, 75, -57, 32, + 75, -56, 28, 76, -54, 24, 76, -52, 20, 76, -51, 16, + 76, -49, 11, 76, -47, 7, 76, -45, 3, 77, -43, -1, + 77, -41, -5, 77, -38, -9, 77, -36, -13, 78, -34, -17, + 78, -31, -21, 78, -28, -25, 78, -26, -29, 79, -23, -33, + 76, -69, 74, 76, -69, 73, 76, -69, 72, 76, -69, 71, + 76, -69, 69, 76, -68, 67, 76, -68, 65, 76, -67, 62, + 76, -66, 59, 76, -66, 56, 76, -65, 53, 76, -64, 50, + 76, -63, 46, 77, -62, 42, 77, -60, 38, 77, -59, 34, + 77, -57, 30, 77, -56, 26, 77, -54, 22, 77, -53, 18, + 78, -51, 13, 78, -49, 9, 78, -47, 5, 78, -45, 2, + 78, -43, -2, 79, -40, -7, 79, -38, -11, 79, -36, -15, + 79, -34, -19, 80, -31, -23, 80, -28, -27, 80, -26, -31, + 77, -71, 75, 77, -70, 74, 77, -70, 73, 77, -70, 72, + 77, -70, 71, 77, -69, 69, 77, -69, 66, 77, -68, 64, + 77, -68, 61, 77, -67, 58, 78, -66, 54, 78, -65, 51, + 78, -64, 47, 78, -63, 43, 78, -62, 40, 78, -61, 36, + 78, -59, 32, 78, -57, 28, 78, -56, 24, 79, -54, 20, + 79, -52, 15, 79, -50, 11, 79, -49, 7, 79, -47, 3, + 80, -45, -1, 80, -42, -5, 80, -40, -9, 80, -38, -13, + 80, -35, -17, 81, -33, -21, 81, -30, -25, 81, -28, -29, + 78, -72, 76, 78, -72, 75, 78, -72, 74, 78, -71, 73, + 78, -71, 72, 79, -71, 70, 79, -70, 68, 79, -70, 65, + 79, -69, 62, 79, -68, 59, 79, -67, 56, 79, -67, 53, + 79, -65, 49, 79, -64, 45, 79, -63, 41, 79, -62, 37, + 79, -60, 33, 80, -59, 29, 80, -57, 25, 80, -56, 21, + 80, -54, 17, 80, -52, 13, 80, -50, 9, 81, -48, 5, + 81, -46, 1, 81, -44, -3, 81, -42, -7, 81, -40, -11, + 82, -37, -15, 82, -35, -20, 82, -32, -23, 82, -30, -27, + 80, -73, 77, 80, -73, 76, 80, -73, 75, 80, -73, 74, + 80, -72, 73, 80, -72, 71, 80, -71, 69, 80, -71, 66, + 80, -70, 63, 80, -70, 60, 80, -69, 57, 80, -68, 54, + 80, -67, 50, 80, -66, 46, 80, -65, 43, 80, -63, 39, + 81, -62, 35, 81, -60, 31, 81, -59, 27, 81, -57, 23, + 81, -55, 19, 81, -54, 15, 82, -52, 11, 82, -50, 7, + 82, -48, 3, 82, -46, -2, 82, -44, -6, 83, -41, -10, + 83, -39, -13, 83, -37, -18, 83, -34, -22, 84, -32, -25, + 81, -74, 78, 81, -74, 77, 81, -74, 77, 81, -74, 75, + 81, -73, 74, 81, -73, 72, 81, -73, 70, 81, -72, 67, + 81, -72, 65, 81, -71, 62, 81, -70, 59, 81, -69, 55, + 81, -68, 51, 81, -67, 48, 82, -66, 44, 82, -65, 41, + 82, -63, 36, 82, -62, 32, 82, -60, 29, 82, -59, 25, + 82, -57, 20, 83, -55, 16, 83, -53, 12, 83, -52, 8, + 83, -50, 4, 83, -47, 0, 84, -45, -4, 84, -43, -8, + 84, -41, -12, 84, -38, -16, 84, -36, -20, 85, -34, -24, + 82, -75, 79, 82, -75, 78, 82, -75, 78, 82, -75, 76, + 82, -75, 75, 82, -74, 73, 82, -74, 71, 82, -73, 68, + 82, -73, 66, 82, -72, 63, 82, -71, 60, 83, -71, 57, + 83, -69, 53, 83, -68, 49, 83, -67, 46, 83, -66, 42, + 83, -65, 38, 83, -63, 34, 83, -62, 30, 83, -60, 26, + 84, -59, 22, 84, -57, 18, 84, -55, 14, 84, -53, 10, + 84, -51, 6, 84, -49, 2, 85, -47, -2, 85, -45, -6, + 85, -43, -10, 85, -40, -14, 86, -38, -18, 86, -36, -22, + 83, -77, 80, 83, -76, 79, 83, -76, 79, 83, -76, 77, + 83, -76, 76, 83, -76, 74, 84, -75, 72, 84, -75, 70, + 84, -74, 67, 84, -73, 64, 84, -73, 61, 84, -72, 58, + 84, -71, 54, 84, -70, 51, 84, -69, 47, 84, -68, 44, + 84, -66, 39, 84, -65, 36, 84, -63, 32, 85, -62, 28, + 85, -60, 24, 85, -58, 20, 85, -57, 16, 85, -55, 12, + 85, -53, 8, 86, -51, 3, 86, -49, 0, 86, -47, -4, + 86, -44, -8, 87, -42, -13, 87, -40, -16, 87, -37, -20, + 85, -78, 81, 85, -78, 80, 85, -77, 80, 85, -77, 78, + 85, -77, 77, 85, -77, 75, 85, -76, 73, 85, -76, 71, + 85, -75, 68, 85, -75, 66, 85, -74, 63, 85, -73, 59, + 85, -72, 56, 85, -71, 52, 85, -70, 49, 85, -69, 45, + 85, -68, 41, 86, -66, 37, 86, -65, 33, 86, -63, 30, + 86, -62, 25, 86, -60, 21, 86, -58, 17, 86, -56, 13, + 87, -55, 10, 87, -52, 5, 87, -50, 1, 87, -48, -3, + 87, -46, -6, 88, -44, -11, 88, -42, -15, 88, -39, -18, + 86, -79, 82, 86, -79, 81, 86, -79, 81, 86, -78, 79, + 86, -78, 78, 86, -78, 77, 86, -77, 75, 86, -77, 72, + 86, -76, 70, 86, -76, 67, 86, -75, 64, 86, -74, 61, + 86, -73, 57, 86, -72, 54, 86, -71, 50, 87, -70, 47, + 87, -69, 43, 87, -68, 39, 87, -66, 35, 87, -65, 31, + 87, -63, 27, 87, -61, 23, 87, -60, 19, 88, -58, 15, + 88, -56, 11, 88, -54, 7, 88, -52, 3, 88, -50, -1, + 89, -48, -5, 89, -46, -9, 89, -43, -13, 89, -41, -17, + 87, -80, 83, 87, -80, 82, 87, -80, 82, 87, -80, 80, + 87, -79, 79, 87, -79, 78, 87, -79, 76, 87, -78, 73, + 87, -78, 71, 87, -77, 68, 87, -76, 65, 87, -76, 62, + 88, -75, 59, 88, -74, 55, 88, -73, 52, 88, -72, 48, + 88, -70, 44, 88, -69, 40, 88, -68, 37, 88, -66, 33, + 88, -65, 28, 89, -63, 25, 89, -61, 21, 89, -60, 17, + 89, -58, 13, 89, -56, 9, 89, -54, 5, 90, -52, 1, + 90, -50, -3, 90, -47, -7, 90, -45, -11, 90, -43, -15, + 88, -81, 84, 88, -81, 83, 88, -81, 83, 88, -81, 82, + 88, -81, 80, 88, -80, 79, 88, -80, 77, 88, -79, 74, + 88, -79, 72, 89, -78, 69, 89, -78, 67, 89, -77, 64, + 89, -76, 60, 89, -75, 57, 89, -74, 53, 89, -73, 50, + 89, -72, 46, 89, -70, 42, 89, -69, 38, 89, -68, 34, + 90, -66, 30, 90, -64, 26, 90, -63, 22, 90, -61, 18, + 90, -59, 15, 90, -57, 10, 91, -55, 6, 91, -53, 3, + 91, -51, -1, 91, -49, -6, 91, -47, -9, 92, -45, -13, + 12, 33, 19, 12, 33, 16, 13, 33, 13, 13, 34, 7, + 13, 35, 1, 13, 35, -4, 14, 36, -10, 14, 38, -15, + 15, 39, -20, 15, 40, -25, 16, 42, -29, 16, 43, -34, + 17, 45, -39, 18, 47, -43, 19, 48, -47, 20, 50, -50, + 21, 52, -54, 21, 54, -58, 22, 56, -62, 23, 57, -65, + 24, 60, -69, 25, 61, -72, 26, 63, -75, 27, 65, -78, + 28, 67, -82, 29, 69, -85, 30, 71, -88, 31, 73, -91, + 32, 75, -94, 33, 77, -97, 34, 78, -100, 35, 80, -103, + 13, 31, 20, 13, 32, 17, 13, 32, 13, 13, 32, 8, + 14, 33, 2, 14, 34, -3, 14, 35, -9, 15, 36, -15, + 15, 37, -19, 16, 39, -24, 16, 40, -29, 17, 42, -33, + 18, 44, -38, 18, 45, -42, 19, 47, -46, 20, 49, -50, + 21, 51, -54, 22, 53, -58, 23, 55, -61, 23, 57, -65, + 25, 59, -68, 25, 61, -72, 26, 62, -75, 27, 64, -78, + 28, 66, -81, 29, 68, -85, 30, 70, -88, 31, 72, -91, + 32, 74, -93, 34, 76, -97, 35, 78, -100, 36, 80, -102, + 13, 30, 21, 14, 30, 18, 14, 30, 14, 14, 31, 8, + 14, 32, 3, 14, 33, -3, 15, 34, -8, 15, 35, -14, + 16, 36, -19, 16, 38, -23, 17, 39, -28, 17, 41, -32, + 18, 43, -37, 19, 44, -41, 20, 46, -45, 20, 48, -49, + 21, 50, -53, 22, 52, -57, 23, 54, -61, 24, 56, -64, + 25, 58, -68, 26, 60, -71, 27, 62, -74, 28, 64, -78, + 29, 66, -81, 30, 68, -84, 31, 70, -87, 32, 72, -90, + 33, 73, -93, 34, 76, -96, 35, 77, -99, 36, 79, -102, + 14, 28, 22, 14, 29, 18, 14, 29, 15, 14, 30, 9, + 15, 30, 3, 15, 31, -2, 15, 32, -7, 16, 33, -13, + 16, 35, -18, 17, 36, -23, 17, 38, -27, 18, 39, -32, + 18, 41, -37, 19, 43, -41, 20, 45, -45, 21, 47, -49, + 22, 49, -53, 22, 51, -57, 23, 53, -60, 24, 55, -64, + 25, 57, -67, 26, 59, -71, 27, 61, -74, 28, 63, -77, + 29, 65, -80, 30, 67, -84, 31, 69, -87, 32, 71, -90, + 33, 73, -93, 34, 75, -96, 35, 77, -99, 36, 79, -102, + 15, 27, 22, 15, 27, 19, 15, 27, 16, 15, 28, 10, + 15, 29, 4, 15, 29, -1, 16, 31, -6, 16, 32, -12, + 17, 33, -17, 17, 35, -22, 18, 36, -27, 18, 38, -31, + 19, 40, -36, 20, 42, -40, 20, 44, -44, 21, 46, -48, + 22, 48, -52, 23, 50, -56, 24, 52, -60, 24, 54, -63, + 25, 56, -67, 26, 58, -70, 27, 60, -74, 28, 62, -77, + 29, 64, -80, 30, 66, -83, 31, 68, -86, 32, 70, -89, + 33, 72, -92, 34, 74, -96, 35, 76, -99, 36, 78, -101, + 15, 25, 23, 15, 25, 20, 15, 25, 17, 16, 26, 11, + 16, 27, 5, 16, 28, 0, 16, 29, -5, 17, 30, -11, + 17, 32, -16, 18, 33, -21, 18, 35, -26, 19, 36, -30, + 19, 38, -35, 20, 40, -39, 21, 42, -43, 22, 44, -47, + 22, 47, -51, 23, 49, -55, 24, 51, -59, 25, 53, -62, + 26, 55, -66, 27, 57, -70, 28, 59, -73, 28, 61, -76, + 29, 63, -79, 30, 65, -83, 31, 67, -86, 32, 69, -89, + 33, 71, -92, 34, 73, -95, 35, 75, -98, 36, 77, -101, + 16, 22, 24, 16, 23, 21, 16, 23, 18, 16, 24, 12, + 17, 25, 6, 17, 26, 1, 17, 27, -4, 18, 28, -10, + 18, 30, -15, 18, 31, -20, 19, 33, -25, 19, 35, -29, + 20, 37, -34, 21, 39, -38, 21, 41, -42, 22, 43, -46, + 23, 45, -51, 24, 47, -54, 24, 49, -58, 25, 51, -62, + 26, 54, -65, 27, 56, -69, 28, 58, -72, 29, 60, -76, + 30, 62, -79, 31, 64, -82, 32, 66, -85, 33, 68, -88, + 34, 70, -91, 35, 73, -95, 36, 74, -98, 37, 76, -101, + 17, 20, 25, 17, 21, 22, 17, 21, 19, 17, 22, 13, + 17, 22, 8, 18, 23, 2, 18, 25, -3, 18, 26, -9, + 19, 27, -14, 19, 29, -19, 20, 31, -23, 20, 33, -28, + 21, 35, -33, 21, 37, -37, 22, 39, -41, 23, 41, -45, + 24, 43, -50, 24, 45, -53, 25, 47, -57, 26, 50, -61, + 27, 52, -65, 28, 54, -68, 28, 56, -71, 29, 58, -75, + 30, 61, -78, 31, 63, -82, 32, 65, -85, 33, 67, -88, + 34, 69, -91, 35, 71, -94, 36, 73, -97, 37, 75, -100, + 18, 18, 26, 18, 18, 23, 18, 19, 20, 18, 19, 14, + 18, 20, 9, 19, 21, 3, 19, 22, -2, 19, 24, -8, + 20, 25, -13, 20, 27, -17, 20, 29, -22, 21, 30, -27, + 22, 33, -32, 22, 35, -36, 23, 37, -40, 23, 39, -44, + 24, 41, -49, 25, 44, -52, 26, 46, -56, 26, 48, -60, + 27, 50, -64, 28, 53, -67, 29, 55, -71, 30, 57, -74, + 31, 59, -77, 32, 62, -81, 33, 64, -84, 33, 66, -87, + 34, 68, -90, 35, 70, -94, 36, 72, -96, 37, 74, -99, + 19, 15, 28, 19, 16, 24, 19, 16, 21, 19, 17, 15, + 19, 18, 10, 20, 19, 5, 20, 20, 0, 20, 21, -6, + 20, 23, -11, 21, 24, -16, 21, 26, -21, 22, 28, -25, + 22, 30, -30, 23, 32, -35, 24, 35, -39, 24, 37, -43, + 25, 39, -47, 26, 42, -51, 26, 44, -55, 27, 46, -59, + 28, 49, -63, 29, 51, -66, 30, 53, -70, 30, 55, -73, + 31, 58, -76, 32, 60, -80, 33, 62, -83, 34, 64, -86, + 35, 66, -89, 36, 69, -93, 37, 71, -96, 38, 73, -99, + 20, 13, 29, 20, 13, 26, 20, 14, 22, 20, 14, 17, + 20, 15, 11, 21, 16, 6, 21, 17, 1, 21, 19, -5, + 21, 20, -10, 22, 22, -14, 22, 24, -19, 23, 26, -24, + 23, 28, -29, 24, 30, -33, 24, 32, -37, 25, 35, -42, + 26, 37, -46, 26, 39, -50, 27, 42, -54, 28, 44, -57, + 29, 47, -62, 29, 49, -65, 30, 51, -69, 31, 54, -72, + 32, 56, -75, 33, 58, -79, 34, 61, -82, 34, 63, -85, + 35, 65, -89, 36, 67, -92, 37, 70, -95, 38, 72, -98, + 21, 10, 30, 21, 10, 27, 21, 10, 24, 22, 11, 18, + 22, 12, 13, 22, 13, 8, 22, 14, 3, 22, 16, -3, + 23, 17, -8, 23, 19, -13, 23, 21, -17, 24, 23, -22, + 24, 25, -27, 25, 27, -31, 25, 29, -36, 26, 32, -40, + 27, 34, -44, 27, 37, -48, 28, 39, -52, 29, 41, -56, + 30, 44, -60, 30, 46, -64, 31, 49, -67, 32, 51, -71, + 33, 53, -74, 34, 56, -78, 34, 58, -81, 35, 61, -84, + 36, 63, -87, 37, 65, -91, 38, 68, -94, 39, 70, -97, + 22, 7, 32, 23, 7, 28, 23, 8, 25, 23, 8, 20, + 23, 9, 15, 23, 10, 10, 23, 11, 5, 24, 13, -1, + 24, 15, -6, 24, 16, -11, 25, 18, -16, 25, 20, -20, + 25, 22, -25, 26, 25, -30, 26, 27, -34, 27, 29, -38, + 28, 32, -43, 28, 34, -47, 29, 37, -51, 30, 39, -55, + 30, 42, -59, 31, 44, -62, 32, 47, -66, 33, 49, -69, + 33, 51, -73, 34, 54, -77, 35, 57, -80, 36, 59, -83, + 37, 61, -86, 38, 64, -90, 38, 66, -93, 39, 68, -96, + 24, 4, 33, 24, 5, 30, 24, 5, 26, 24, 6, 21, + 24, 7, 16, 24, 8, 11, 24, 9, 6, 25, 10, 1, + 25, 12, -4, 25, 14, -9, 26, 15, -14, 26, 17, -19, + 26, 20, -24, 27, 22, -28, 27, 24, -33, 28, 27, -37, + 29, 29, -41, 29, 32, -45, 30, 34, -49, 30, 37, -53, + 31, 40, -57, 32, 42, -61, 33, 45, -65, 33, 47, -68, + 34, 49, -72, 35, 52, -75, 36, 55, -79, 37, 57, -82, + 37, 59, -85, 38, 62, -89, 39, 64, -92, 40, 67, -95, + 25, 2, 34, 25, 2, 31, 25, 2, 27, 25, 3, 22, + 25, 4, 18, 25, 5, 13, 26, 6, 8, 26, 8, 2, + 26, 9, -3, 26, 11, -8, 27, 13, -12, 27, 15, -17, + 28, 17, -22, 28, 19, -27, 28, 22, -31, 29, 24, -35, + 30, 27, -40, 30, 29, -44, 31, 32, -48, 31, 34, -52, + 32, 37, -56, 33, 40, -60, 33, 42, -63, 34, 45, -67, + 35, 47, -70, 36, 50, -74, 36, 52, -78, 37, 55, -81, + 38, 57, -84, 39, 60, -88, 40, 62, -91, 41, 65, -94, + 26, -1, 35, 26, 0, 32, 26, 0, 29, 26, 1, 24, + 26, 1, 19, 27, 2, 14, 27, 4, 10, 27, 5, 4, + 27, 7, -1, 28, 8, -6, 28, 10, -11, 28, 12, -15, + 29, 15, -20, 29, 17, -25, 29, 19, -29, 30, 22, -34, + 31, 24, -38, 31, 27, -42, 32, 29, -46, 32, 32, -50, + 33, 35, -54, 34, 37, -58, 34, 40, -62, 35, 42, -66, + 36, 45, -69, 36, 48, -73, 37, 50, -76, 38, 53, -80, + 39, 55, -83, 40, 58, -87, 40, 60, -90, 41, 63, -93, + 27, -3, 36, 27, -3, 33, 28, -3, 30, 28, -2, 25, + 28, -1, 21, 28, 0, 16, 28, 1, 11, 28, 3, 6, + 28, 4, 1, 29, 6, -4, 29, 8, -9, 29, 10, -13, + 30, 12, -19, 30, 14, -23, 31, 17, -28, 31, 19, -32, + 32, 22, -37, 32, 24, -41, 33, 27, -45, 33, 29, -49, + 34, 32, -53, 35, 35, -57, 35, 38, -60, 36, 40, -64, + 36, 43, -68, 37, 46, -72, 38, 48, -75, 39, 51, -78, + 39, 53, -82, 40, 56, -85, 41, 58, -89, 42, 61, -92, + 29, -6, 37, 29, -5, 34, 29, -5, 31, 29, -4, 27, + 29, -4, 22, 29, -3, 18, 29, -2, 13, 29, 0, 7, + 30, 1, 3, 30, 3, -2, 30, 5, -7, 31, 7, -12, + 31, 9, -17, 31, 12, -21, 32, 14, -26, 32, 16, -30, + 33, 19, -35, 33, 22, -39, 34, 24, -43, 34, 27, -47, + 35, 30, -51, 35, 32, -55, 36, 35, -59, 37, 38, -63, + 37, 40, -66, 38, 43, -70, 39, 46, -74, 40, 48, -77, + 40, 51, -80, 41, 54, -84, 42, 56, -87, 43, 59, -91, + 30, -8, 38, 30, -8, 35, 30, -7, 32, 30, -7, 28, + 30, -6, 24, 30, -5, 19, 31, -4, 15, 31, -3, 9, + 31, -1, 4, 31, 1, 0, 31, 2, -5, 32, 4, -10, + 32, 7, -15, 32, 9, -20, 33, 11, -24, 33, 14, -28, + 34, 17, -33, 34, 19, -37, 35, 22, -41, 35, 24, -45, + 36, 27, -50, 36, 30, -54, 37, 33, -57, 38, 35, -61, + 38, 38, -65, 39, 41, -69, 40, 43, -72, 40, 46, -76, + 41, 49, -79, 42, 52, -83, 43, 54, -86, 43, 57, -89, + 31, -11, 39, 31, -10, 36, 31, -10, 34, 31, -9, 30, + 32, -9, 25, 32, -8, 21, 32, -6, 16, 32, -5, 11, + 32, -4, 6, 32, -2, 1, 33, 0, -3, 33, 2, -8, + 33, 4, -13, 34, 6, -18, 34, 9, -22, 34, 11, -27, + 35, 14, -31, 35, 17, -36, 36, 19, -40, 36, 22, -44, + 37, 25, -48, 37, 27, -52, 38, 30, -56, 39, 33, -59, + 39, 35, -63, 40, 38, -67, 41, 41, -71, 41, 44, -74, + 42, 46, -78, 43, 49, -81, 43, 52, -85, 44, 54, -88, + 33, -13, 40, 33, -13, 38, 33, -12, 35, 33, -12, 31, + 33, -11, 27, 33, -10, 22, 33, -9, 18, 33, -7, 13, + 33, -6, 8, 34, -4, 3, 34, -3, -2, 34, -1, -6, + 34, 2, -11, 35, 4, -16, 35, 6, -20, 36, 9, -25, + 36, 11, -30, 36, 14, -34, 37, 17, -38, 37, 19, -42, + 38, 22, -46, 38, 25, -50, 39, 28, -54, 40, 30, -58, + 40, 33, -62, 41, 36, -66, 42, 39, -69, 42, 41, -73, + 43, 44, -76, 44, 47, -80, 44, 50, -83, 45, 52, -87, + 34, -15, 41, 34, -15, 39, 34, -14, 36, 34, -14, 32, + 34, -13, 28, 34, -12, 24, 34, -11, 20, 35, -10, 14, + 35, -8, 10, 35, -7, 5, 35, -5, 0, 35, -3, -4, + 36, -1, -10, 36, 1, -14, 36, 4, -19, 37, 6, -23, + 37, 9, -28, 38, 11, -32, 38, 14, -36, 38, 17, -40, + 39, 20, -45, 40, 22, -49, 40, 25, -52, 41, 28, -56, + 41, 30, -60, 42, 33, -64, 42, 36, -68, 43, 39, -71, + 44, 42, -75, 44, 45, -79, 45, 47, -82, 46, 50, -85, + 35, -17, 42, 35, -17, 40, 35, -17, 37, 35, -16, 34, + 35, -15, 30, 36, -15, 26, 36, -13, 21, 36, -12, 16, + 36, -11, 11, 36, -9, 7, 36, -7, 2, 37, -6, -3, + 37, -3, -8, 37, -1, -12, 38, 1, -17, 38, 4, -21, + 38, 6, -26, 39, 9, -30, 39, 11, -34, 40, 14, -38, + 40, 17, -43, 41, 20, -47, 41, 22, -51, 42, 25, -55, + 42, 28, -58, 43, 31, -62, 43, 34, -66, 44, 36, -70, + 45, 39, -73, 45, 42, -77, 46, 45, -81, 47, 48, -84, + 37, -19, 43, 37, -19, 41, 37, -19, 39, 37, -18, 35, + 37, -18, 31, 37, -17, 27, 37, -16, 23, 37, -14, 18, + 37, -13, 13, 37, -11, 9, 38, -10, 4, 38, -8, -1, + 38, -6, -6, 38, -4, -10, 39, -1, -15, 39, 1, -19, + 39, 4, -24, 40, 6, -28, 40, 9, -32, 41, 11, -37, + 41, 14, -41, 42, 17, -45, 42, 20, -49, 43, 23, -53, + 43, 25, -57, 44, 28, -61, 44, 31, -65, 45, 34, -68, + 46, 37, -72, 46, 40, -76, 47, 42, -79, 48, 45, -82, + 38, -22, 44, 38, -21, 42, 38, -21, 40, 38, -20, 36, + 38, -20, 33, 38, -19, 29, 38, -18, 25, 38, -17, 20, + 39, -15, 15, 39, -14, 10, 39, -12, 6, 39, -10, 1, + 39, -8, -4, 40, -6, -8, 40, -4, -13, 40, -1, -17, + 41, 1, -22, 41, 4, -26, 41, 6, -31, 42, 9, -35, + 42, 12, -39, 43, 15, -43, 43, 17, -47, 44, 20, -51, + 44, 23, -55, 45, 26, -59, 45, 29, -63, 46, 31, -67, + 47, 34, -70, 47, 37, -74, 48, 40, -77, 49, 43, -81, + 39, -24, 45, 39, -23, 43, 39, -23, 41, 39, -22, 38, + 39, -22, 34, 39, -21, 30, 40, -20, 26, 40, -19, 21, + 40, -17, 17, 40, -16, 12, 40, -14, 8, 40, -13, 3, + 41, -10, -2, 41, -8, -7, 41, -6, -11, 42, -4, -15, + 42, -1, -20, 42, 1, -25, 43, 4, -29, 43, 6, -33, + 43, 9, -38, 44, 12, -42, 44, 15, -46, 45, 18, -49, + 45, 20, -53, 46, 23, -58, 46, 26, -61, 47, 29, -65, + 48, 32, -68, 48, 35, -72, 49, 38, -76, 49, 40, -79, + 41, -26, 46, 41, -25, 44, 41, -25, 42, 41, -24, 39, + 41, -24, 36, 41, -23, 32, 41, -22, 28, 41, -21, 23, + 41, -20, 18, 41, -18, 14, 41, -17, 9, 42, -15, 5, + 42, -13, 0, 42, -11, -5, 42, -9, -9, 43, -6, -14, + 43, -4, -18, 43, -1, -23, 44, 1, -27, 44, 4, -31, + 45, 7, -36, 45, 10, -40, 45, 12, -44, 46, 15, -48, + 46, 18, -52, 47, 21, -56, 48, 24, -60, 48, 26, -63, + 49, 29, -67, 49, 32, -71, 50, 35, -74, 50, 38, -78, + 42, -28, 47, 42, -27, 45, 42, -27, 43, 42, -26, 40, + 42, -26, 37, 42, -25, 33, 42, -24, 29, 42, -23, 25, + 42, -22, 20, 43, -20, 16, 43, -19, 11, 43, -17, 7, + 43, -15, 2, 43, -13, -3, 44, -11, -7, 44, -9, -12, + 44, -6, -17, 45, -4, -21, 45, -1, -25, 45, 1, -29, + 46, 4, -34, 46, 7, -38, 47, 10, -42, 47, 13, -46, + 48, 15, -50, 48, 18, -54, 49, 21, -58, 49, 24, -62, + 50, 27, -65, 50, 30, -69, 51, 33, -73, 51, 35, -76, + 43, -29, 48, 43, -29, 47, 43, -29, 45, 43, -28, 42, + 43, -28, 38, 43, -27, 35, 43, -26, 31, 44, -25, 26, + 44, -24, 22, 44, -22, 17, 44, -21, 13, 44, -19, 8, + 44, -17, 3, 45, -15, -1, 45, -13, -5, 45, -11, -10, + 46, -8, -15, 46, -6, -19, 46, -4, -23, 47, -1, -27, + 47, 2, -32, 47, 5, -36, 48, 7, -40, 48, 10, -44, + 49, 13, -48, 49, 16, -52, 50, 19, -56, 50, 21, -60, + 51, 24, -63, 51, 27, -68, 52, 30, -71, 52, 33, -75, + 44, -31, 49, 45, -31, 48, 45, -31, 46, 45, -30, 43, + 45, -30, 40, 45, -29, 36, 45, -28, 32, 45, -27, 28, + 45, -26, 24, 45, -24, 19, 45, -23, 15, 46, -21, 10, + 46, -19, 5, 46, -17, 1, 46, -15, -4, 46, -13, -8, + 47, -11, -13, 47, -8, -17, 47, -6, -21, 48, -3, -26, + 48, 0, -30, 49, 2, -34, 49, 5, -38, 49, 8, -42, + 50, 10, -46, 50, 13, -51, 51, 16, -54, 51, 19, -58, + 52, 22, -62, 52, 25, -66, 53, 28, -69, 53, 31, -73, + 46, -33, 50, 46, -33, 49, 46, -33, 47, 46, -32, 44, + 46, -32, 41, 46, -31, 38, 46, -30, 34, 46, -29, 29, + 46, -28, 25, 46, -26, 21, 47, -25, 16, 47, -23, 12, + 47, -21, 7, 47, -20, 3, 47, -18, -2, 48, -15, -6, + 48, -13, -11, 48, -11, -15, 49, -8, -20, 49, -6, -24, + 49, -3, -28, 50, 0, -33, 50, 2, -37, 50, 5, -41, + 51, 8, -44, 51, 11, -49, 52, 14, -53, 52, 17, -56, + 53, 19, -60, 53, 22, -64, 54, 25, -68, 54, 28, -71, + 47, -35, 51, 47, -35, 50, 47, -34, 48, 47, -34, 45, + 47, -33, 42, 47, -33, 39, 47, -32, 35, 48, -31, 31, + 48, -30, 27, 48, -28, 23, 48, -27, 18, 48, -25, 14, + 48, -23, 9, 48, -22, 4, 49, -20, 0, 49, -18, -4, + 49, -15, -9, 50, -13, -13, 50, -10, -18, 50, -8, -22, + 50, -5, -27, 51, -3, -31, 51, 0, -35, 52, 3, -39, + 52, 5, -43, 52, 8, -47, 53, 11, -51, 53, 14, -55, + 54, 17, -58, 54, 20, -62, 55, 23, -66, 55, 26, -70, + 49, -37, 53, 49, -37, 51, 49, -37, 50, 49, -36, 47, + 49, -36, 44, 49, -35, 41, 49, -34, 37, 49, -33, 33, + 49, -32, 29, 49, -31, 25, 50, -29, 20, 50, -28, 16, + 50, -26, 11, 50, -24, 7, 50, -22, 2, 51, -20, -2, + 51, -18, -7, 51, -16, -11, 51, -13, -15, 52, -11, -20, + 52, -8, -24, 52, -6, -28, 53, -3, -32, 53, 0, -36, + 53, 2, -40, 54, 5, -45, 54, 8, -49, 55, 11, -52, + 55, 14, -56, 56, 17, -60, 56, 20, -64, 57, 23, -68, + 50, -39, 54, 50, -38, 52, 50, -38, 51, 50, -38, 48, + 50, -37, 45, 50, -37, 42, 50, -36, 39, 50, -35, 34, + 51, -34, 30, 51, -33, 26, 51, -31, 22, 51, -30, 18, + 51, -28, 13, 51, -26, 9, 52, -24, 4, 52, -22, 0, + 52, -20, -5, 52, -18, -9, 53, -15, -14, 53, -13, -18, + 53, -10, -22, 54, -8, -27, 54, -5, -31, 54, -3, -35, + 55, 0, -39, 55, 3, -43, 55, 6, -47, 56, 9, -51, + 56, 11, -54, 57, 15, -59, 57, 17, -62, 58, 20, -66, + 51, -40, 55, 51, -40, 53, 51, -40, 52, 51, -40, 49, + 52, -39, 47, 52, -38, 44, 52, -38, 40, 52, -37, 36, + 52, -36, 32, 52, -34, 28, 52, -33, 24, 52, -32, 20, + 52, -30, 15, 53, -28, 10, 53, -26, 6, 53, -24, 2, + 53, -22, -3, 54, -20, -7, 54, -18, -12, 54, -15, -16, + 54, -13, -21, 55, -10, -25, 55, -8, -29, 55, -5, -33, + 56, -2, -37, 56, 1, -41, 57, 3, -45, 57, 6, -49, + 57, 9, -53, 58, 12, -57, 58, 15, -61, 59, 18, -64, + 53, -42, 56, 53, -42, 54, 53, -42, 53, 53, -41, 51, + 53, -41, 48, 53, -40, 45, 53, -39, 42, 53, -38, 38, + 53, -37, 34, 53, -36, 30, 53, -35, 25, 54, -34, 21, + 54, -32, 16, 54, -30, 12, 54, -28, 8, 54, -26, 3, + 55, -24, -1, 55, -22, -6, 55, -20, -10, 55, -17, -14, + 56, -15, -19, 56, -12, -23, 56, -10, -27, 57, -7, -31, + 57, -5, -35, 57, -2, -39, 58, 1, -43, 58, 4, -47, + 59, 7, -51, 59, 10, -55, 59, 12, -59, 60, 15, -62, + 54, -44, 57, 54, -43, 55, 54, -43, 54, 54, -43, 52, + 54, -42, 49, 54, -42, 46, 54, -41, 43, 54, -40, 39, + 54, -39, 35, 55, -38, 31, 55, -37, 27, 55, -35, 23, + 55, -34, 18, 55, -32, 14, 55, -30, 10, 56, -28, 5, + 56, -26, 0, 56, -24, -4, 56, -22, -8, 56, -20, -12, + 57, -17, -17, 57, -15, -21, 57, -12, -25, 58, -10, -29, + 58, -7, -33, 58, -4, -38, 59, -1, -41, 59, 1, -45, + 60, 4, -49, 60, 7, -53, 61, 10, -57, 61, 13, -61, + 55, -45, 58, 55, -45, 57, 55, -45, 55, 55, -44, 53, + 55, -44, 51, 56, -43, 48, 56, -43, 44, 56, -42, 41, + 56, -41, 37, 56, -40, 33, 56, -38, 29, 56, -37, 25, + 56, -35, 20, 56, -34, 16, 57, -32, 11, 57, -30, 7, + 57, -28, 2, 57, -26, -2, 57, -24, -6, 58, -22, -10, + 58, -19, -15, 58, -17, -19, 59, -14, -23, 59, -12, -27, + 59, -9, -31, 60, -6, -36, 60, -4, -40, 60, -1, -44, + 61, 2, -47, 61, 5, -52, 62, 8, -55, 62, 11, -59, + 57, -47, 59, 57, -47, 58, 57, -46, 56, 57, -46, 54, + 57, -46, 52, 57, -45, 49, 57, -44, 46, 57, -43, 42, + 57, -42, 38, 57, -41, 34, 57, -40, 30, 57, -39, 26, + 58, -37, 22, 58, -36, 17, 58, -34, 13, 58, -32, 9, + 58, -30, 4, 58, -28, 0, 59, -26, -4, 59, -24, -9, + 59, -21, -13, 60, -19, -17, 60, -16, -21, 60, -14, -25, + 60, -11, -29, 61, -8, -34, 61, -6, -38, 62, -3, -42, + 62, 0, -46, 62, 3, -50, 63, 5, -54, 63, 8, -57, + 58, -48, 60, 58, -48, 59, 58, -48, 57, 58, -47, 55, + 58, -47, 53, 58, -47, 50, 58, -46, 47, 58, -45, 43, + 58, -44, 40, 58, -43, 36, 59, -42, 32, 59, -41, 28, + 59, -39, 23, 59, -37, 19, 59, -36, 15, 59, -34, 11, + 60, -32, 6, 60, -30, 2, 60, -28, -3, 60, -26, -7, + 60, -23, -11, 61, -21, -16, 61, -19, -20, 61, -16, -24, + 62, -14, -28, 62, -11, -32, 62, -8, -36, 63, -5, -40, + 63, -3, -44, 63, 0, -48, 64, 3, -52, 64, 6, -56, + 59, -50, 61, 59, -50, 60, 59, -49, 59, 59, -49, 57, + 59, -49, 54, 59, -48, 52, 59, -47, 49, 60, -47, 45, + 60, -46, 41, 60, -45, 38, 60, -44, 34, 60, -42, 30, + 60, -41, 25, 60, -39, 21, 60, -38, 17, 61, -36, 12, + 61, -34, 8, 61, -32, 3, 61, -30, -1, 61, -28, -5, + 62, -25, -10, 62, -23, -14, 62, -21, -18, 62, -18, -22, + 63, -16, -26, 63, -13, -30, 63, -10, -34, 64, -8, -38, + 64, -5, -42, 65, -2, -46, 65, 1, -50, 65, 3, -54, + 61, -51, 62, 61, -51, 61, 61, -51, 60, 61, -51, 58, + 61, -50, 56, 61, -50, 53, 61, -49, 50, 61, -48, 46, + 61, -47, 43, 61, -46, 39, 61, -45, 35, 61, -44, 31, + 61, -42, 27, 61, -41, 22, 62, -39, 18, 62, -38, 14, + 62, -36, 9, 62, -34, 5, 62, -32, 1, 63, -30, -3, + 63, -27, -8, 63, -25, -12, 63, -23, -16, 64, -20, -20, + 64, -18, -24, 64, -15, -28, 65, -13, -32, 65, -10, -36, + 65, -7, -40, 66, -4, -44, 66, -2, -48, 66, 1, -52, + 62, -53, 63, 62, -52, 62, 62, -52, 61, 62, -52, 59, + 62, -52, 57, 62, -51, 54, 62, -50, 51, 62, -50, 48, + 62, -49, 44, 62, -48, 41, 62, -47, 37, 62, -46, 33, + 63, -44, 28, 63, -43, 24, 63, -41, 20, 63, -40, 16, + 63, -38, 11, 63, -36, 7, 64, -34, 3, 64, -32, -1, + 64, -29, -6, 64, -27, -10, 65, -25, -14, 65, -22, -18, + 65, -20, -22, 65, -17, -27, 66, -15, -31, 66, -12, -35, + 66, -9, -38, 67, -6, -43, 67, -4, -47, 68, -1, -50, + 63, -54, 64, 63, -54, 63, 63, -54, 62, 63, -53, 60, + 63, -53, 58, 63, -53, 55, 63, -52, 53, 63, -51, 49, + 63, -50, 46, 64, -49, 42, 64, -48, 38, 64, -47, 34, + 64, -46, 30, 64, -44, 26, 64, -43, 22, 64, -41, 18, + 64, -39, 13, 65, -37, 9, 65, -36, 5, 65, -34, 0, + 65, -31, -4, 66, -29, -8, 66, -27, -12, 66, -24, -16, + 66, -22, -20, 67, -19, -25, 67, -17, -29, 67, -14, -33, + 68, -12, -37, 68, -9, -41, 68, -6, -45, 69, -3, -48, + 64, -56, 65, 64, -55, 64, 64, -55, 63, 64, -55, 61, + 64, -54, 59, 65, -54, 57, 65, -53, 54, 65, -53, 50, + 65, -52, 47, 65, -51, 44, 65, -50, 40, 65, -49, 36, + 65, -47, 32, 65, -46, 28, 65, -45, 23, 66, -43, 19, + 66, -41, 15, 66, -39, 11, 66, -37, 6, 66, -35, 2, + 67, -33, -2, 67, -31, -6, 67, -29, -11, 67, -26, -15, + 68, -24, -19, 68, -21, -23, 68, -19, -27, 68, -16, -31, + 69, -14, -35, 69, -11, -39, 69, -8, -43, 70, -6, -47, + 66, -57, 66, 66, -57, 65, 66, -57, 64, 66, -56, 62, + 66, -56, 60, 66, -55, 58, 66, -55, 55, 66, -54, 52, + 66, -53, 49, 66, -52, 45, 66, -51, 41, 66, -50, 38, + 66, -49, 33, 66, -48, 29, 67, -46, 25, 67, -45, 21, + 67, -43, 16, 67, -41, 12, 67, -39, 8, 68, -37, 4, + 68, -35, -1, 68, -33, -5, 68, -31, -9, 68, -28, -13, + 69, -26, -17, 69, -23, -21, 69, -21, -25, 70, -18, -29, + 70, -16, -33, 70, -13, -37, 71, -10, -41, 71, -8, -45, + 67, -58, 67, 67, -58, 66, 67, -58, 65, 67, -58, 63, + 67, -57, 61, 67, -57, 59, 67, -56, 57, 67, -56, 53, + 67, -55, 50, 67, -54, 46, 67, -53, 43, 68, -52, 39, + 68, -51, 35, 68, -49, 31, 68, -48, 27, 68, -46, 23, + 68, -45, 18, 68, -43, 14, 69, -41, 10, 69, -39, 6, + 69, -37, 1, 69, -35, -3, 69, -33, -7, 70, -30, -11, + 70, -28, -15, 70, -25, -19, 70, -23, -23, 71, -21, -27, + 71, -18, -31, 71, -15, -36, 72, -13, -39, 72, -10, -43, + 68, -60, 68, 68, -59, 67, 68, -59, 66, 68, -59, 64, + 68, -59, 63, 68, -58, 60, 68, -58, 58, 68, -57, 55, + 69, -56, 51, 69, -55, 48, 69, -54, 44, 69, -53, 41, + 69, -52, 36, 69, -51, 32, 69, -49, 28, 69, -48, 24, + 69, -46, 20, 70, -44, 16, 70, -43, 12, 70, -41, 8, + 70, -39, 3, 70, -37, -1, 71, -34, -5, 71, -32, -9, + 71, -30, -13, 71, -27, -18, 72, -25, -22, 72, -23, -26, + 72, -20, -29, 73, -17, -34, 73, -15, -38, 73, -12, -41, + 69, -61, 69, 70, -61, 68, 70, -61, 67, 70, -60, 66, + 70, -60, 64, 70, -60, 62, 70, -59, 59, 70, -58, 56, + 70, -58, 53, 70, -57, 49, 70, -56, 46, 70, -55, 42, + 70, -54, 38, 70, -52, 34, 70, -51, 30, 71, -50, 26, + 71, -48, 21, 71, -46, 17, 71, -44, 13, 71, -43, 9, + 71, -40, 5, 72, -38, 1, 72, -36, -3, 72, -34, -7, + 72, -32, -11, 73, -29, -16, 73, -27, -20, 73, -25, -24, + 73, -22, -28, 74, -19, -32, 74, -17, -36, 74, -14, -40, + 71, -62, 70, 71, -62, 69, 71, -62, 68, 71, -62, 67, + 71, -61, 65, 71, -61, 63, 71, -60, 60, 71, -60, 57, + 71, -59, 54, 71, -58, 51, 71, -57, 47, 71, -56, 44, + 71, -55, 39, 71, -54, 36, 72, -53, 32, 72, -51, 28, + 72, -49, 23, 72, -48, 19, 72, -46, 15, 72, -44, 11, + 73, -42, 6, 73, -40, 2, 73, -38, -2, 73, -36, -6, + 73, -34, -10, 74, -31, -14, 74, -29, -18, 74, -27, -22, + 75, -24, -26, 75, -21, -30, 75, -19, -34, 75, -16, -38, + 72, -64, 71, 72, -63, 70, 72, -63, 69, 72, -63, 68, + 72, -63, 66, 72, -62, 64, 72, -62, 62, 72, -61, 58, + 72, -61, 55, 72, -60, 52, 72, -59, 49, 73, -58, 45, + 73, -57, 41, 73, -55, 37, 73, -54, 33, 73, -53, 29, + 73, -51, 25, 73, -49, 21, 73, -48, 17, 74, -46, 13, + 74, -44, 8, 74, -42, 4, 74, -40, 0, 74, -38, -4, + 75, -36, -8, 75, -33, -12, 75, -31, -16, 75, -29, -20, + 76, -26, -24, 76, -23, -28, 76, -21, -32, 77, -18, -36, + 73, -65, 72, 73, -65, 71, 73, -65, 70, 73, -64, 69, + 73, -64, 67, 73, -64, 65, 73, -63, 63, 73, -63, 60, + 74, -62, 57, 74, -61, 54, 74, -60, 50, 74, -59, 47, + 74, -58, 43, 74, -57, 39, 74, -56, 35, 74, -54, 31, + 74, -53, 26, 75, -51, 22, 75, -49, 18, 75, -48, 14, + 75, -46, 10, 75, -44, 6, 75, -42, 2, 76, -40, -2, + 76, -38, -6, 76, -35, -11, 76, -33, -15, 77, -31, -18, + 77, -28, -22, 77, -25, -27, 77, -23, -31, 78, -20, -34, + 75, -66, 73, 75, -66, 72, 75, -66, 71, 75, -66, 70, + 75, -65, 68, 75, -65, 66, 75, -65, 64, 75, -64, 61, + 75, -63, 58, 75, -62, 55, 75, -62, 52, 75, -61, 48, + 75, -60, 44, 75, -58, 40, 75, -57, 36, 75, -56, 33, + 76, -54, 28, 76, -53, 24, 76, -51, 20, 76, -49, 16, + 76, -47, 12, 76, -45, 8, 77, -44, 4, 77, -41, 0, + 77, -39, -4, 77, -37, -9, 78, -35, -13, 78, -32, -17, + 78, -30, -21, 78, -27, -25, 79, -25, -29, 79, -23, -33, + 76, -68, 74, 76, -68, 73, 76, -68, 73, 76, -67, 71, + 76, -67, 70, 76, -67, 68, 76, -66, 65, 76, -66, 63, + 76, -65, 60, 76, -64, 57, 76, -63, 53, 77, -62, 50, + 77, -61, 46, 77, -60, 42, 77, -59, 38, 77, -58, 35, + 77, -56, 30, 77, -55, 26, 77, -53, 22, 78, -51, 18, + 78, -49, 14, 78, -48, 10, 78, -46, 6, 78, -44, 2, + 79, -42, -2, 79, -39, -7, 79, -37, -11, 79, -35, -15, + 79, -33, -18, 80, -30, -23, 80, -27, -27, 80, -25, -30, + 77, -69, 75, 77, -69, 74, 77, -69, 74, 77, -69, 72, + 77, -68, 71, 77, -68, 69, 77, -67, 67, 78, -67, 64, + 78, -66, 61, 78, -66, 58, 78, -65, 55, 78, -64, 51, + 78, -63, 47, 78, -62, 44, 78, -60, 40, 78, -59, 36, + 78, -58, 32, 78, -56, 28, 79, -55, 24, 79, -53, 20, + 79, -51, 15, 79, -49, 11, 79, -47, 7, 79, -45, 4, + 80, -43, 0, 80, -41, -5, 80, -39, -9, 80, -37, -13, + 81, -34, -17, 81, -32, -21, 81, -29, -25, 81, -27, -29, + 79, -70, 76, 79, -70, 75, 79, -70, 75, 79, -70, 73, + 79, -70, 72, 79, -69, 70, 79, -69, 68, 79, -68, 65, + 79, -68, 62, 79, -67, 59, 79, -66, 56, 79, -65, 53, + 79, -64, 49, 79, -63, 45, 79, -62, 41, 79, -61, 38, + 80, -59, 33, 80, -58, 29, 80, -56, 26, 80, -55, 22, + 80, -53, 17, 80, -51, 13, 80, -49, 9, 81, -47, 5, + 81, -45, 1, 81, -43, -3, 81, -41, -7, 82, -39, -11, + 82, -36, -15, 82, -34, -19, 82, -31, -23, 83, -29, -27, + 80, -72, 77, 80, -71, 76, 80, -71, 76, 80, -71, 74, + 80, -71, 73, 80, -70, 71, 80, -70, 69, 80, -69, 66, + 80, -69, 64, 80, -68, 61, 80, -67, 57, 80, -67, 54, + 80, -65, 50, 80, -64, 47, 81, -63, 43, 81, -62, 39, + 81, -61, 35, 81, -59, 31, 81, -58, 27, 81, -56, 23, + 81, -54, 19, 82, -53, 15, 82, -51, 11, 82, -49, 7, + 82, -47, 3, 82, -45, -1, 83, -42, -5, 83, -40, -9, + 83, -38, -13, 83, -36, -18, 83, -33, -21, 84, -31, -25, + 81, -73, 78, 81, -73, 77, 81, -73, 77, 81, -72, 75, + 81, -72, 74, 81, -72, 72, 81, -71, 70, 81, -71, 67, + 81, -70, 65, 81, -69, 62, 81, -69, 59, 81, -68, 56, + 82, -67, 52, 82, -66, 48, 82, -65, 45, 82, -64, 41, + 82, -62, 37, 82, -61, 33, 82, -59, 29, 82, -58, 25, + 83, -56, 20, 83, -54, 17, 83, -52, 13, 83, -50, 9, + 83, -49, 5, 83, -46, 0, 84, -44, -4, 84, -42, -8, + 84, -40, -11, 84, -37, -16, 85, -35, -20, 85, -33, -23, + 82, -74, 79, 82, -74, 78, 82, -74, 78, 82, -74, 77, + 82, -73, 75, 82, -73, 73, 82, -73, 71, 82, -72, 69, + 83, -71, 66, 83, -71, 63, 83, -70, 60, 83, -69, 57, + 83, -68, 53, 83, -67, 50, 83, -66, 46, 83, -65, 42, + 83, -63, 38, 83, -62, 34, 83, -61, 30, 84, -59, 27, + 84, -57, 22, 84, -56, 18, 84, -54, 14, 84, -52, 10, + 84, -50, 6, 85, -48, 2, 85, -46, -2, 85, -44, -6, + 85, -42, -10, 86, -39, -14, 86, -37, -18, 86, -35, -22, + 84, -75, 80, 84, -75, 79, 84, -75, 79, 84, -75, 78, + 84, -74, 76, 84, -74, 74, 84, -74, 72, 84, -73, 70, + 84, -73, 67, 84, -72, 65, 84, -71, 62, 84, -71, 58, + 84, -70, 55, 84, -69, 51, 84, -67, 48, 84, -66, 44, + 84, -65, 40, 85, -64, 36, 85, -62, 32, 85, -61, 28, + 85, -59, 24, 85, -57, 20, 85, -56, 16, 85, -54, 12, + 86, -52, 8, 86, -50, 4, 86, -48, 0, 86, -46, -4, + 86, -44, -8, 87, -41, -12, 87, -39, -16, 87, -37, -20, + 85, -76, 81, 85, -76, 80, 85, -76, 80, 85, -76, 79, + 85, -76, 77, 85, -75, 76, 85, -75, 74, 85, -74, 71, + 85, -74, 69, 85, -73, 66, 85, -73, 63, 85, -72, 60, + 85, -71, 56, 85, -70, 53, 85, -69, 49, 85, -68, 45, + 86, -66, 41, 86, -65, 37, 86, -64, 34, 86, -62, 30, + 86, -60, 25, 86, -59, 22, 86, -57, 18, 87, -55, 14, + 87, -54, 10, 87, -51, 5, 87, -49, 2, 87, -47, -2, + 88, -45, -6, 88, -43, -11, 88, -41, -14, 88, -38, -18, + 86, -78, 82, 86, -77, 81, 86, -77, 81, 86, -77, 80, + 86, -77, 78, 86, -77, 77, 86, -76, 75, 86, -76, 72, + 86, -75, 70, 86, -75, 67, 86, -74, 64, 86, -73, 61, + 86, -72, 57, 87, -71, 54, 87, -70, 50, 87, -69, 47, + 87, -68, 43, 87, -66, 39, 87, -65, 35, 87, -64, 31, + 87, -62, 27, 87, -60, 23, 88, -59, 19, 88, -57, 15, + 88, -55, 11, 88, -53, 7, 88, -51, 3, 89, -49, -1, + 89, -47, -5, 89, -45, -9, 89, -42, -13, 89, -40, -17, + 87, -79, 83, 87, -79, 82, 87, -79, 82, 87, -78, 81, + 87, -78, 79, 87, -78, 78, 87, -77, 76, 87, -77, 73, + 87, -76, 71, 87, -76, 68, 88, -75, 65, 88, -74, 62, + 88, -73, 59, 88, -73, 55, 88, -72, 52, 88, -70, 48, + 88, -69, 44, 88, -68, 41, 88, -67, 37, 88, -65, 33, + 89, -63, 29, 89, -62, 25, 89, -60, 21, 89, -59, 17, + 89, -57, 13, 89, -55, 9, 90, -53, 5, 90, -51, 1, + 90, -49, -3, 90, -46, -7, 90, -44, -11, 91, -42, -15, + 88, -80, 84, 88, -80, 83, 88, -80, 83, 88, -80, 82, + 88, -79, 80, 89, -79, 79, 89, -79, 77, 89, -78, 75, + 89, -78, 72, 89, -77, 70, 89, -76, 67, 89, -76, 64, + 89, -75, 60, 89, -74, 57, 89, -73, 53, 89, -72, 50, + 89, -70, 46, 89, -69, 42, 89, -68, 38, 90, -67, 35, + 90, -65, 30, 90, -63, 26, 90, -62, 23, 90, -60, 19, + 90, -58, 15, 91, -56, 10, 91, -54, 7, 91, -52, 3, + 91, -50, -1, 91, -48, -5, 92, -46, -9, 92, -44, -13, + 14, 35, 22, 14, 35, 19, 15, 36, 16, 15, 36, 10, + 15, 37, 4, 15, 37, -1, 16, 38, -7, 16, 39, -12, + 16, 41, -17, 17, 42, -22, 17, 43, -27, 18, 45, -31, + 19, 46, -36, 19, 48, -40, 20, 49, -44, 21, 51, -48, + 22, 53, -52, 23, 55, -56, 23, 56, -60, 24, 58, -63, + 25, 60, -67, 26, 62, -70, 27, 64, -74, 28, 66, -77, + 29, 67, -80, 30, 69, -84, 31, 71, -87, 32, 73, -90, + 33, 75, -93, 34, 77, -96, 35, 79, -99, 36, 81, -102, + 15, 34, 23, 15, 34, 20, 15, 34, 16, 15, 35, 11, + 15, 35, 5, 16, 36, -1, 16, 37, -6, 16, 38, -12, + 17, 39, -17, 17, 41, -21, 18, 42, -26, 18, 43, -31, + 19, 45, -35, 20, 47, -40, 21, 48, -44, 21, 50, -48, + 22, 52, -52, 23, 54, -56, 24, 56, -59, 25, 57, -63, + 26, 59, -67, 26, 61, -70, 27, 63, -73, 28, 65, -76, + 29, 67, -80, 30, 69, -83, 31, 71, -86, 32, 73, -89, + 33, 74, -92, 34, 76, -96, 35, 78, -98, 36, 80, -101, + 15, 32, 24, 15, 33, 21, 16, 33, 17, 16, 34, 11, + 16, 34, 6, 16, 35, 0, 16, 36, -5, 17, 37, -11, + 17, 38, -16, 18, 39, -21, 18, 41, -25, 19, 42, -30, + 20, 44, -35, 20, 46, -39, 21, 47, -43, 22, 49, -47, + 22, 51, -51, 23, 53, -55, 24, 55, -59, 25, 57, -62, + 26, 59, -66, 27, 60, -70, 28, 62, -73, 28, 64, -76, + 29, 66, -79, 30, 68, -83, 31, 70, -86, 32, 72, -89, + 33, 74, -92, 34, 76, -95, 35, 78, -98, 36, 80, -101, + 16, 31, 24, 16, 31, 21, 16, 32, 18, 16, 32, 12, + 16, 33, 6, 17, 34, 1, 17, 35, -5, 17, 36, -10, + 18, 37, -15, 18, 38, -20, 19, 40, -25, 19, 41, -29, + 20, 43, -34, 21, 45, -38, 21, 46, -43, 22, 48, -46, + 23, 50, -51, 24, 52, -55, 24, 54, -58, 25, 56, -62, + 26, 58, -66, 27, 60, -69, 28, 62, -72, 29, 63, -76, + 30, 65, -79, 31, 68, -82, 32, 69, -86, 33, 71, -89, + 33, 73, -92, 35, 75, -95, 36, 77, -98, 37, 79, -101, + 16, 30, 25, 16, 30, 22, 17, 30, 18, 17, 31, 12, + 17, 31, 7, 17, 32, 2, 17, 33, -4, 18, 34, -10, + 18, 36, -15, 19, 37, -19, 19, 38, -24, 20, 40, -29, + 20, 42, -34, 21, 43, -38, 22, 45, -42, 22, 47, -46, + 23, 49, -50, 24, 51, -54, 25, 53, -58, 25, 55, -61, + 26, 57, -65, 27, 59, -69, 28, 61, -72, 29, 63, -75, + 30, 65, -78, 31, 67, -82, 32, 69, -85, 33, 71, -88, + 34, 73, -91, 35, 75, -95, 36, 77, -98, 37, 78, -100, + 17, 28, 26, 17, 28, 23, 17, 28, 19, 17, 29, 13, + 17, 30, 8, 18, 30, 2, 18, 31, -3, 18, 33, -9, + 19, 34, -14, 19, 35, -19, 20, 37, -23, 20, 38, -28, + 21, 40, -33, 21, 42, -37, 22, 44, -41, 23, 46, -45, + 24, 48, -50, 24, 50, -53, 25, 52, -57, 26, 54, -61, + 27, 56, -65, 28, 58, -68, 28, 60, -71, 29, 62, -75, + 30, 64, -78, 31, 66, -82, 32, 68, -85, 33, 70, -88, + 34, 72, -91, 35, 74, -94, 36, 76, -97, 37, 78, -100, + 18, 26, 27, 18, 26, 23, 18, 27, 20, 18, 27, 14, + 18, 28, 9, 18, 29, 3, 19, 30, -2, 19, 31, -8, + 19, 32, -13, 20, 34, -18, 20, 35, -22, 21, 37, -27, + 21, 39, -32, 22, 40, -36, 23, 42, -40, 23, 44, -44, + 24, 46, -49, 25, 48, -53, 26, 50, -56, 26, 52, -60, + 27, 54, -64, 28, 56, -67, 29, 58, -71, 30, 61, -74, + 31, 63, -77, 32, 65, -81, 32, 67, -84, 33, 69, -87, + 34, 71, -90, 35, 73, -94, 36, 75, -97, 37, 77, -100, + 18, 24, 28, 19, 24, 24, 19, 24, 21, 19, 25, 15, + 19, 26, 10, 19, 27, 4, 19, 28, -1, 20, 29, -7, + 20, 30, -12, 21, 32, -16, 21, 33, -21, 21, 35, -26, + 22, 37, -31, 23, 39, -35, 23, 40, -39, 24, 42, -43, + 25, 45, -48, 25, 47, -52, 26, 49, -55, 27, 51, -59, + 28, 53, -63, 28, 55, -67, 29, 57, -70, 30, 59, -73, + 31, 61, -77, 32, 64, -80, 33, 66, -83, 34, 68, -87, + 35, 70, -90, 36, 72, -93, 37, 74, -96, 38, 76, -99, + 19, 22, 29, 19, 22, 25, 19, 22, 22, 20, 23, 16, + 20, 23, 11, 20, 24, 6, 20, 25, 0, 21, 27, -5, + 21, 28, -10, 21, 29, -15, 22, 31, -20, 22, 33, -25, + 23, 35, -30, 23, 37, -34, 24, 39, -38, 25, 41, -42, + 25, 43, -47, 26, 45, -51, 27, 47, -54, 27, 49, -58, + 28, 51, -62, 29, 54, -66, 30, 56, -69, 31, 58, -73, + 31, 60, -76, 32, 62, -80, 33, 64, -83, 34, 66, -86, + 35, 68, -89, 36, 71, -92, 37, 73, -95, 38, 75, -98, + 20, 19, 30, 20, 20, 26, 20, 20, 23, 21, 20, 17, + 21, 21, 12, 21, 22, 7, 21, 23, 2, 21, 24, -4, + 22, 26, -9, 22, 27, -14, 23, 29, -19, 23, 31, -23, + 24, 33, -28, 24, 35, -33, 25, 37, -37, 25, 39, -41, + 26, 41, -46, 27, 43, -50, 27, 45, -53, 28, 47, -57, + 29, 50, -61, 30, 52, -65, 30, 54, -68, 31, 56, -72, + 32, 58, -75, 33, 61, -79, 34, 63, -82, 35, 65, -85, + 36, 67, -88, 37, 69, -92, 37, 71, -95, 38, 74, -98, + 21, 17, 31, 21, 17, 28, 21, 17, 24, 22, 18, 19, + 22, 19, 13, 22, 20, 8, 22, 21, 3, 22, 22, -3, + 23, 23, -8, 23, 25, -13, 23, 27, -17, 24, 28, -22, + 24, 30, -27, 25, 32, -31, 25, 34, -36, 26, 36, -40, + 27, 39, -44, 27, 41, -48, 28, 43, -52, 29, 45, -56, + 29, 48, -60, 30, 50, -64, 31, 52, -67, 32, 54, -71, + 33, 57, -74, 33, 59, -78, 34, 61, -81, 35, 63, -84, + 36, 66, -87, 37, 68, -91, 38, 70, -94, 39, 72, -97, + 23, 14, 32, 23, 14, 29, 23, 14, 25, 23, 15, 20, + 23, 16, 15, 23, 16, 10, 23, 18, 5, 24, 19, -1, + 24, 20, -6, 24, 22, -11, 25, 24, -16, 25, 25, -20, + 25, 27, -25, 26, 29, -30, 26, 32, -34, 27, 34, -38, + 28, 36, -43, 28, 38, -47, 29, 41, -51, 30, 43, -54, + 30, 45, -59, 31, 48, -62, 32, 50, -66, 33, 52, -69, + 33, 54, -73, 34, 57, -77, 35, 59, -80, 36, 61, -83, + 37, 64, -86, 38, 66, -90, 39, 68, -93, 39, 70, -96, + 24, 11, 33, 24, 11, 30, 24, 12, 26, 24, 12, 21, + 24, 13, 16, 24, 14, 11, 24, 15, 6, 25, 16, 1, + 25, 18, -4, 25, 19, -9, 26, 21, -14, 26, 23, -19, + 26, 25, -24, 27, 27, -28, 27, 29, -33, 28, 31, -37, + 29, 34, -41, 29, 36, -45, 30, 38, -49, 30, 41, -53, + 31, 43, -57, 32, 46, -61, 33, 48, -65, 33, 50, -68, + 34, 52, -72, 35, 55, -75, 36, 57, -79, 36, 60, -82, + 37, 62, -85, 38, 64, -89, 39, 67, -92, 40, 69, -95, + 25, 8, 34, 25, 9, 31, 25, 9, 28, 25, 10, 23, + 25, 11, 18, 25, 11, 13, 25, 12, 8, 26, 14, 2, + 26, 15, -3, 26, 17, -8, 27, 19, -12, 27, 20, -17, + 27, 23, -22, 28, 25, -27, 28, 27, -31, 29, 29, -35, + 29, 31, -40, 30, 34, -44, 31, 36, -48, 31, 38, -52, + 32, 41, -56, 33, 43, -60, 33, 46, -63, 34, 48, -67, + 35, 50, -70, 36, 53, -74, 36, 55, -78, 37, 58, -81, + 38, 60, -84, 39, 63, -88, 40, 65, -91, 41, 67, -94, + 26, 6, 35, 26, 6, 32, 26, 7, 29, 26, 7, 24, + 26, 8, 19, 26, 9, 14, 27, 10, 9, 27, 11, 4, + 27, 13, -1, 27, 14, -6, 28, 16, -11, 28, 18, -15, + 28, 20, -21, 29, 22, -25, 29, 24, -29, 30, 26, -34, + 30, 29, -38, 31, 31, -43, 31, 34, -47, 32, 36, -50, + 33, 39, -55, 33, 41, -58, 34, 44, -62, 35, 46, -66, + 36, 48, -69, 36, 51, -73, 37, 53, -77, 38, 56, -80, + 39, 58, -83, 39, 61, -87, 40, 63, -90, 41, 65, -93, + 27, 3, 36, 27, 4, 33, 27, 4, 30, 27, 5, 25, + 27, 5, 21, 28, 6, 16, 28, 7, 11, 28, 9, 5, + 28, 10, 1, 28, 12, -4, 29, 13, -9, 29, 15, -14, + 29, 17, -19, 30, 19, -23, 30, 22, -28, 31, 24, -32, + 31, 26, -37, 32, 29, -41, 32, 31, -45, 33, 34, -49, + 34, 36, -53, 34, 39, -57, 35, 41, -61, 36, 44, -64, + 36, 46, -68, 37, 49, -72, 38, 51, -75, 39, 54, -79, + 39, 56, -82, 40, 59, -86, 41, 61, -89, 42, 63, -92, + 28, 1, 37, 28, 1, 34, 28, 2, 31, 28, 2, 27, + 29, 3, 22, 29, 4, 17, 29, 5, 13, 29, 6, 7, + 29, 8, 2, 30, 9, -3, 30, 11, -7, 30, 13, -12, + 31, 15, -17, 31, 17, -22, 31, 19, -26, 32, 21, -31, + 32, 24, -35, 33, 26, -39, 33, 29, -43, 34, 31, -47, + 35, 34, -52, 35, 36, -56, 36, 39, -59, 36, 41, -63, + 37, 44, -67, 38, 47, -71, 39, 49, -74, 39, 52, -77, + 40, 54, -81, 41, 57, -84, 42, 59, -88, 42, 62, -91, + 30, -2, 38, 30, -1, 35, 30, -1, 32, 30, 0, 28, + 30, 0, 24, 30, 1, 19, 30, 2, 14, 30, 4, 9, + 31, 5, 4, 31, 7, -1, 31, 8, -6, 31, 10, -10, + 32, 12, -16, 32, 14, -20, 32, 17, -25, 33, 19, -29, + 33, 21, -34, 34, 24, -38, 34, 26, -42, 35, 29, -46, + 36, 31, -50, 36, 34, -54, 37, 37, -58, 37, 39, -62, + 38, 42, -65, 39, 44, -69, 39, 47, -73, 40, 49, -76, + 41, 52, -79, 42, 55, -83, 42, 57, -86, 43, 59, -90, + 31, -4, 39, 31, -4, 36, 31, -3, 33, 31, -3, 29, + 31, -2, 25, 31, -1, 20, 31, 0, 16, 31, 1, 10, + 32, 2, 6, 32, 4, 1, 32, 6, -4, 32, 7, -9, + 33, 10, -14, 33, 12, -18, 34, 14, -23, 34, 16, -27, + 34, 19, -32, 35, 21, -36, 35, 24, -40, 36, 26, -44, + 37, 29, -49, 37, 32, -53, 38, 34, -56, 38, 37, -60, + 39, 39, -64, 40, 42, -68, 40, 45, -71, 41, 47, -75, + 42, 50, -78, 42, 52, -82, 43, 55, -85, 44, 57, -88, + 32, -7, 40, 32, -6, 37, 32, -6, 35, 32, -5, 31, + 32, -5, 26, 32, -4, 22, 33, -3, 17, 33, -1, 12, + 33, 0, 7, 33, 1, 3, 33, 3, -2, 34, 5, -7, + 34, 7, -12, 34, 9, -17, 35, 11, -21, 35, 14, -25, + 36, 16, -30, 36, 19, -34, 36, 21, -39, 37, 24, -43, + 38, 26, -47, 38, 29, -51, 39, 32, -55, 39, 34, -59, + 40, 37, -62, 41, 40, -66, 41, 42, -70, 42, 45, -73, + 42, 47, -77, 43, 50, -81, 44, 53, -84, 45, 55, -87, + 33, -9, 41, 33, -9, 39, 33, -8, 36, 33, -8, 32, + 34, -7, 28, 34, -6, 24, 34, -5, 19, 34, -4, 14, + 34, -3, 9, 34, -1, 4, 35, 1, 0, 35, 2, -5, + 35, 5, -10, 35, 7, -15, 36, 9, -19, 36, 11, -24, + 37, 14, -28, 37, 16, -33, 38, 19, -37, 38, 21, -41, + 39, 24, -45, 39, 26, -49, 40, 29, -53, 40, 32, -57, + 41, 34, -61, 41, 37, -65, 42, 40, -68, 43, 42, -72, + 43, 45, -75, 44, 48, -79, 45, 50, -83, 45, 53, -86, + 35, -11, 42, 35, -11, 40, 35, -11, 37, 35, -10, 33, + 35, -9, 29, 35, -9, 25, 35, -8, 21, 35, -6, 15, + 35, -5, 11, 36, -3, 6, 36, -2, 1, 36, 0, -3, + 36, 2, -8, 37, 4, -13, 37, 6, -17, 37, 9, -22, + 38, 11, -27, 38, 14, -31, 39, 16, -35, 39, 19, -39, + 40, 21, -44, 40, 24, -48, 41, 27, -52, 41, 29, -55, + 42, 32, -59, 42, 35, -63, 43, 37, -67, 44, 40, -70, + 44, 43, -74, 45, 46, -78, 46, 48, -81, 46, 51, -85, + 36, -13, 43, 36, -13, 41, 36, -13, 38, 36, -12, 35, + 36, -12, 31, 36, -11, 27, 36, -10, 22, 36, -9, 17, + 37, -7, 12, 37, -6, 8, 37, -4, 3, 37, -3, -2, + 38, 0, -7, 38, 2, -11, 38, 4, -16, 38, 6, -20, + 39, 9, -25, 39, 11, -29, 40, 13, -33, 40, 16, -37, + 41, 19, -42, 41, 21, -46, 42, 24, -50, 42, 27, -54, + 43, 29, -57, 43, 32, -62, 44, 35, -65, 45, 38, -69, + 45, 40, -72, 46, 43, -76, 47, 46, -80, 47, 48, -83, + 37, -16, 44, 37, -15, 42, 37, -15, 39, 37, -15, 36, + 37, -14, 32, 37, -13, 28, 38, -12, 24, 38, -11, 19, + 38, -10, 14, 38, -8, 10, 38, -7, 5, 38, -5, 0, + 39, -3, -5, 39, -1, -9, 39, 1, -14, 40, 3, -18, + 40, 6, -23, 40, 8, -27, 41, 11, -32, 41, 13, -36, + 42, 16, -40, 42, 19, -44, 43, 22, -48, 43, 24, -52, + 44, 27, -56, 44, 30, -60, 45, 33, -64, 45, 35, -67, + 46, 38, -71, 47, 41, -75, 47, 43, -78, 48, 46, -82, + 38, -18, 45, 38, -18, 43, 39, -17, 41, 39, -17, 37, + 39, -16, 34, 39, -15, 30, 39, -14, 25, 39, -13, 20, + 39, -12, 16, 39, -11, 11, 40, -9, 7, 40, -7, 2, + 40, -5, -3, 40, -3, -8, 41, -1, -12, 41, 1, -16, + 41, 4, -21, 42, 6, -26, 42, 8, -30, 42, 11, -34, + 43, 14, -39, 43, 16, -43, 44, 19, -46, 44, 22, -50, + 45, 24, -54, 45, 27, -58, 46, 30, -62, 46, 33, -66, + 47, 35, -69, 48, 38, -73, 48, 41, -77, 49, 44, -80, + 40, -20, 46, 40, -20, 44, 40, -19, 42, 40, -19, 38, + 40, -18, 35, 40, -18, 31, 40, -17, 27, 40, -15, 22, + 40, -14, 18, 41, -13, 13, 41, -11, 8, 41, -10, 4, + 41, -8, -1, 41, -6, -6, 42, -4, -10, 42, -1, -15, + 42, 1, -20, 43, 3, -24, 43, 6, -28, 44, 8, -32, + 44, 11, -37, 44, 14, -41, 45, 16, -45, 45, 19, -49, + 46, 22, -52, 46, 25, -57, 47, 28, -60, 47, 30, -64, + 48, 33, -68, 49, 36, -72, 49, 39, -75, 50, 41, -79, + 41, -22, 47, 41, -22, 45, 41, -21, 43, 41, -21, 40, + 41, -20, 36, 41, -20, 33, 41, -19, 28, 42, -18, 24, + 42, -16, 19, 42, -15, 15, 42, -14, 10, 42, -12, 6, + 42, -10, 1, 43, -8, -4, 43, -6, -8, 43, -4, -13, + 44, -1, -18, 44, 1, -22, 44, 3, -26, 45, 6, -30, + 45, 9, -35, 46, 11, -39, 46, 14, -43, 46, 17, -47, + 47, 19, -51, 47, 22, -55, 48, 25, -59, 48, 28, -62, + 49, 30, -66, 50, 34, -70, 50, 36, -74, 51, 39, -77, + 42, -24, 48, 42, -24, 46, 42, -24, 44, 42, -23, 41, + 43, -22, 38, 43, -22, 34, 43, -21, 30, 43, -20, 25, + 43, -19, 21, 43, -17, 16, 43, -16, 12, 43, -14, 7, + 44, -12, 2, 44, -10, -2, 44, -8, -7, 44, -6, -11, + 45, -4, -16, 45, -1, -20, 45, 1, -24, 46, 3, -29, + 46, 6, -33, 47, 9, -37, 47, 11, -41, 48, 14, -45, + 48, 17, -49, 48, 20, -53, 49, 23, -57, 50, 25, -61, + 50, 28, -64, 51, 31, -69, 51, 34, -72, 52, 37, -76, + 44, -26, 49, 44, -26, 47, 44, -26, 45, 44, -25, 42, + 44, -25, 39, 44, -24, 35, 44, -23, 32, 44, -22, 27, + 44, -21, 23, 44, -19, 18, 45, -18, 14, 45, -16, 9, + 45, -14, 4, 45, -13, 0, 45, -11, -5, 46, -9, -9, + 46, -6, -14, 46, -4, -18, 47, -1, -23, 47, 1, -27, + 47, 4, -31, 48, 6, -35, 48, 9, -39, 49, 12, -43, + 49, 14, -47, 50, 17, -52, 50, 20, -55, 51, 23, -59, + 51, 26, -63, 52, 29, -67, 52, 31, -70, 53, 34, -74, + 45, -28, 50, 45, -28, 48, 45, -27, 46, 45, -27, 44, + 45, -26, 40, 45, -26, 37, 45, -25, 33, 45, -24, 28, + 46, -23, 24, 46, -21, 20, 46, -20, 15, 46, -19, 11, + 46, -17, 6, 46, -15, 2, 47, -13, -3, 47, -11, -7, + 47, -8, -12, 48, -6, -16, 48, -4, -21, 48, -1, -25, + 49, 1, -30, 49, 4, -34, 49, 7, -38, 50, 9, -42, + 50, 12, -46, 51, 15, -50, 51, 18, -54, 52, 20, -57, + 52, 23, -61, 53, 26, -65, 53, 29, -69, 54, 32, -72, + 46, -30, 51, 46, -30, 49, 46, -29, 47, 46, -29, 45, + 46, -28, 42, 46, -28, 38, 47, -27, 35, 47, -26, 30, + 47, -25, 26, 47, -24, 22, 47, -22, 17, 47, -21, 13, + 47, -19, 8, 48, -17, 3, 48, -15, -1, 48, -13, -5, + 48, -11, -10, 49, -8, -15, 49, -6, -19, 49, -4, -23, + 50, -1, -28, 50, 2, -32, 50, 4, -36, 51, 7, -40, + 51, 9, -44, 52, 12, -48, 52, 15, -52, 53, 18, -56, + 53, 21, -59, 54, 24, -64, 54, 26, -67, 55, 29, -71, + 48, -32, 52, 48, -32, 50, 48, -31, 49, 48, -31, 46, + 48, -30, 43, 48, -30, 40, 48, -29, 36, 48, -28, 32, + 48, -27, 27, 48, -26, 23, 48, -24, 19, 49, -23, 14, + 49, -21, 10, 49, -19, 5, 49, -17, 1, 49, -15, -4, + 50, -13, -9, 50, -11, -13, 50, -8, -17, 51, -6, -21, + 51, -3, -26, 51, -1, -30, 52, 2, -34, 52, 4, -38, + 52, 7, -42, 53, 10, -46, 53, 13, -50, 54, 15, -54, + 54, 18, -58, 55, 21, -62, 55, 24, -66, 56, 27, -69, + 49, -34, 53, 49, -34, 52, 49, -34, 50, 49, -33, 48, + 49, -33, 45, 49, -32, 41, 49, -31, 38, 50, -30, 34, + 50, -29, 29, 50, -28, 25, 50, -27, 21, 50, -25, 17, + 50, -23, 12, 50, -22, 7, 51, -20, 3, 51, -18, -1, + 51, -16, -6, 51, -13, -11, 52, -11, -15, 52, -9, -19, + 52, -6, -24, 53, -4, -28, 53, -1, -32, 53, 1, -36, + 54, 4, -40, 54, 7, -44, 55, 10, -48, 55, 12, -52, + 56, 15, -56, 56, 18, -60, 57, 21, -63, 57, 24, -67, + 51, -36, 54, 51, -36, 53, 51, -35, 51, 51, -35, 49, + 51, -34, 46, 51, -34, 43, 51, -33, 39, 51, -32, 35, + 51, -31, 31, 51, -30, 27, 51, -29, 23, 51, -27, 18, + 52, -25, 13, 52, -24, 9, 52, -22, 5, 52, -20, 0, + 52, -18, -4, 53, -16, -9, 53, -13, -13, 53, -11, -17, + 54, -9, -22, 54, -6, -26, 54, -4, -30, 55, -1, -34, + 55, 2, -38, 55, 5, -42, 56, 7, -46, 56, 10, -50, + 57, 13, -54, 57, 16, -58, 58, 18, -62, 58, 21, -65, + 52, -38, 55, 52, -37, 54, 52, -37, 52, 52, -37, 50, + 52, -36, 47, 52, -36, 44, 52, -35, 41, 52, -34, 37, + 52, -33, 33, 52, -32, 29, 52, -31, 24, 53, -29, 20, + 53, -27, 15, 53, -26, 11, 53, -24, 7, 53, -22, 2, + 54, -20, -3, 54, -18, -7, 54, -16, -11, 54, -13, -15, + 55, -11, -20, 55, -8, -24, 55, -6, -28, 56, -3, -32, + 56, -1, -36, 57, 2, -41, 57, 5, -45, 57, 8, -48, + 58, 10, -52, 58, 13, -56, 59, 16, -60, 59, 19, -64, + 53, -39, 56, 53, -39, 55, 53, -39, 53, 53, -38, 51, + 53, -38, 49, 53, -37, 46, 53, -37, 42, 53, -36, 38, + 54, -35, 34, 54, -34, 30, 54, -32, 26, 54, -31, 22, + 54, -29, 17, 54, -28, 13, 54, -26, 8, 55, -24, 4, + 55, -22, -1, 55, -20, -5, 55, -18, -9, 56, -16, -13, + 56, -13, -18, 56, -11, -22, 57, -8, -26, 57, -6, -30, + 57, -3, -34, 58, 0, -39, 58, 2, -43, 58, 5, -47, + 59, 8, -50, 59, 11, -55, 60, 14, -58, 60, 16, -62, + 54, -41, 57, 54, -41, 56, 54, -40, 55, 54, -40, 52, + 55, -40, 50, 55, -39, 47, 55, -38, 44, 55, -38, 40, + 55, -37, 36, 55, -35, 32, 55, -34, 28, 55, -33, 23, + 55, -31, 19, 55, -30, 14, 56, -28, 10, 56, -26, 6, + 56, -24, 1, 56, -22, -3, 57, -20, -7, 57, -18, -12, + 57, -15, -16, 57, -13, -20, 58, -10, -25, 58, -8, -29, + 58, -5, -33, 59, -2, -37, 59, 0, -41, 60, 3, -45, + 60, 5, -49, 60, 9, -53, 61, 11, -57, 61, 14, -60, + 56, -43, 58, 56, -42, 57, 56, -42, 56, 56, -42, 53, + 56, -41, 51, 56, -41, 48, 56, -40, 45, 56, -39, 41, + 56, -38, 37, 56, -37, 33, 56, -36, 29, 56, -35, 25, + 57, -33, 20, 57, -32, 16, 57, -30, 12, 57, -28, 8, + 57, -26, 3, 58, -24, -1, 58, -22, -6, 58, -20, -10, + 58, -17, -15, 59, -15, -19, 59, -13, -23, 59, -10, -27, + 60, -8, -31, 60, -5, -35, 60, -2, -39, 61, 0, -43, + 61, 3, -47, 62, 6, -51, 62, 9, -55, 62, 12, -59, + 57, -44, 59, 57, -44, 58, 57, -44, 57, 57, -43, 55, + 57, -43, 52, 57, -42, 49, 57, -42, 46, 57, -41, 42, + 57, -40, 39, 57, -39, 35, 58, -38, 31, 58, -37, 27, + 58, -35, 22, 58, -34, 18, 58, -32, 14, 58, -30, 9, + 59, -28, 5, 59, -26, 0, 59, -24, -4, 59, -22, -8, + 60, -19, -13, 60, -17, -17, 60, -15, -21, 60, -12, -25, + 61, -10, -29, 61, -7, -33, 61, -4, -37, 62, -2, -41, + 62, 1, -45, 63, 4, -49, 63, 7, -53, 63, 9, -57, + 58, -46, 60, 58, -46, 59, 58, -45, 58, 58, -45, 56, + 58, -45, 53, 58, -44, 51, 58, -43, 48, 59, -43, 44, + 59, -42, 40, 59, -41, 36, 59, -40, 32, 59, -38, 28, + 59, -37, 24, 59, -35, 20, 59, -34, 15, 60, -32, 11, + 60, -30, 6, 60, -28, 2, 60, -26, -2, 60, -24, -6, + 61, -22, -11, 61, -19, -15, 61, -17, -19, 62, -15, -23, + 62, -12, -27, 62, -9, -32, 63, -7, -36, 63, -4, -39, + 63, -1, -43, 64, 2, -48, 64, 4, -51, 65, 7, -55, + 60, -47, 61, 60, -47, 60, 60, -47, 59, 60, -47, 57, + 60, -46, 55, 60, -46, 52, 60, -45, 49, 60, -44, 45, + 60, -43, 42, 60, -42, 38, 60, -41, 34, 60, -40, 30, + 60, -39, 25, 61, -37, 21, 61, -36, 17, 61, -34, 13, + 61, -32, 8, 61, -30, 4, 61, -28, 0, 62, -26, -4, + 62, -24, -9, 62, -21, -13, 62, -19, -17, 63, -17, -21, + 63, -14, -25, 63, -11, -30, 64, -9, -34, 64, -6, -38, + 64, -4, -42, 65, -1, -46, 65, 2, -50, 66, 5, -53, + 61, -49, 62, 61, -49, 61, 61, -48, 60, 61, -48, 58, + 61, -48, 56, 61, -47, 53, 61, -47, 50, 61, -46, 47, + 61, -45, 43, 61, -44, 39, 61, -43, 36, 61, -42, 32, + 62, -40, 27, 62, -39, 23, 62, -37, 19, 62, -36, 15, + 62, -34, 10, 62, -32, 6, 63, -30, 2, 63, -28, -3, + 63, -26, -7, 63, -23, -11, 64, -21, -16, 64, -19, -20, + 64, -16, -24, 65, -14, -28, 65, -11, -32, 65, -9, -36, + 66, -6, -40, 66, -3, -44, 66, 0, -48, 67, 2, -52, + 62, -50, 63, 62, -50, 62, 62, -50, 61, 62, -50, 59, + 62, -49, 57, 62, -49, 55, 62, -48, 52, 62, -47, 48, + 62, -47, 45, 63, -46, 41, 63, -45, 37, 63, -44, 33, + 63, -42, 29, 63, -41, 25, 63, -39, 20, 63, -38, 16, + 64, -36, 12, 64, -34, 7, 64, -32, 3, 64, -30, -1, + 64, -28, -6, 65, -25, -10, 65, -23, -14, 65, -21, -18, + 65, -19, -22, 66, -16, -26, 66, -13, -30, 66, -11, -34, + 67, -8, -38, 67, -5, -42, 67, -3, -46, 68, 0, -50, + 63, -52, 64, 63, -52, 63, 63, -52, 62, 63, -51, 60, + 63, -51, 58, 64, -50, 56, 64, -50, 53, 64, -49, 49, + 64, -48, 46, 64, -47, 42, 64, -46, 39, 64, -45, 35, + 64, -44, 30, 64, -42, 26, 64, -41, 22, 65, -39, 18, + 65, -37, 13, 65, -36, 9, 65, -34, 5, 65, -32, 1, + 66, -30, -4, 66, -27, -8, 66, -25, -12, 66, -23, -16, + 67, -21, -20, 67, -18, -24, 67, -15, -28, 68, -13, -32, + 68, -10, -36, 68, -7, -41, 69, -5, -44, 69, -2, -48, + 65, -53, 65, 65, -53, 64, 65, -53, 63, 65, -53, 61, + 65, -52, 59, 65, -52, 57, 65, -51, 54, 65, -51, 51, + 65, -50, 47, 65, -49, 44, 65, -48, 40, 65, -47, 36, + 65, -45, 32, 66, -44, 28, 66, -43, 24, 66, -41, 20, + 66, -39, 15, 66, -38, 11, 66, -36, 7, 67, -34, 3, + 67, -31, -2, 67, -29, -6, 67, -27, -10, 68, -25, -14, + 68, -23, -18, 68, -20, -23, 68, -18, -27, 69, -15, -31, + 69, -13, -34, 69, -10, -39, 70, -7, -43, 70, -4, -46, + 66, -55, 66, 66, -55, 65, 66, -54, 64, 66, -54, 63, + 66, -54, 61, 66, -53, 58, 66, -53, 56, 66, -52, 52, + 66, -51, 49, 66, -50, 45, 66, -49, 42, 67, -48, 38, + 67, -47, 34, 67, -46, 30, 67, -44, 25, 67, -43, 21, + 67, -41, 17, 67, -39, 13, 68, -37, 9, 68, -36, 4, + 68, -33, 0, 68, -31, -4, 68, -29, -8, 69, -27, -12, + 69, -25, -16, 69, -22, -21, 70, -20, -25, 70, -17, -29, + 70, -15, -33, 71, -12, -37, 71, -9, -41, 71, -7, -45, + 67, -56, 67, 67, -56, 66, 67, -56, 65, 67, -56, 64, + 67, -55, 62, 67, -55, 59, 67, -54, 57, 67, -54, 54, + 68, -53, 50, 68, -52, 47, 68, -51, 43, 68, -50, 39, + 68, -49, 35, 68, -47, 31, 68, -46, 27, 68, -45, 23, + 68, -43, 18, 69, -41, 14, 69, -39, 10, 69, -37, 6, + 69, -35, 2, 69, -33, -3, 70, -31, -7, 70, -29, -11, + 70, -27, -15, 70, -24, -19, 71, -22, -23, 71, -19, -27, + 71, -17, -31, 72, -14, -35, 72, -11, -39, 72, -9, -43, + 68, -58, 68, 68, -57, 67, 69, -57, 66, 69, -57, 65, + 69, -57, 63, 69, -56, 61, 69, -56, 58, 69, -55, 55, + 69, -54, 52, 69, -54, 48, 69, -53, 45, 69, -52, 41, + 69, -50, 37, 69, -49, 33, 69, -48, 29, 70, -46, 25, + 70, -44, 20, 70, -43, 16, 70, -41, 12, 70, -39, 8, + 70, -37, 3, 71, -35, -1, 71, -33, -5, 71, -31, -9, + 71, -29, -13, 72, -26, -17, 72, -24, -21, 72, -21, -25, + 72, -19, -29, 73, -16, -33, 73, -14, -37, 73, -11, -41, + 70, -59, 69, 70, -59, 68, 70, -59, 67, 70, -58, 66, + 70, -58, 64, 70, -58, 62, 70, -57, 59, 70, -57, 56, + 70, -56, 53, 70, -55, 50, 70, -54, 46, 70, -53, 42, + 70, -52, 38, 70, -51, 34, 71, -49, 30, 71, -48, 26, + 71, -46, 22, 71, -45, 18, 71, -43, 14, 71, -41, 10, + 72, -39, 5, 72, -37, 1, 72, -35, -3, 72, -33, -7, + 73, -31, -11, 73, -28, -16, 73, -26, -20, 73, -23, -23, + 74, -21, -27, 74, -18, -32, 74, -16, -36, 75, -13, -39, + 71, -60, 70, 71, -60, 69, 71, -60, 68, 71, -60, 67, + 71, -60, 65, 71, -59, 63, 71, -59, 61, 71, -58, 57, + 71, -57, 54, 71, -56, 51, 71, -56, 48, 72, -55, 44, + 72, -53, 40, 72, -52, 36, 72, -51, 32, 72, -49, 28, + 72, -48, 24, 72, -46, 19, 72, -45, 15, 73, -43, 11, + 73, -41, 7, 73, -39, 3, 73, -37, -1, 73, -35, -5, + 74, -32, -9, 74, -30, -14, 74, -28, -18, 74, -25, -22, + 75, -23, -26, 75, -20, -30, 75, -18, -34, 76, -15, -38, + 72, -62, 71, 72, -62, 70, 72, -61, 70, 72, -61, 68, + 72, -61, 66, 72, -60, 64, 72, -60, 62, 72, -59, 59, + 73, -59, 56, 73, -58, 52, 73, -57, 49, 73, -56, 45, + 73, -55, 41, 73, -54, 37, 73, -52, 34, 73, -51, 30, + 73, -49, 25, 73, -48, 21, 74, -46, 17, 74, -44, 13, + 74, -42, 9, 74, -41, 4, 74, -39, 0, 75, -37, -4, + 75, -34, -8, 75, -32, -12, 75, -30, -16, 76, -27, -20, + 76, -25, -24, 76, -22, -28, 77, -20, -32, 77, -17, -36, + 74, -63, 72, 74, -63, 71, 74, -63, 71, 74, -63, 69, + 74, -62, 67, 74, -62, 65, 74, -61, 63, 74, -61, 60, + 74, -60, 57, 74, -59, 54, 74, -59, 50, 74, -58, 47, + 74, -56, 43, 74, -55, 39, 74, -54, 35, 74, -53, 31, + 75, -51, 27, 75, -50, 23, 75, -48, 19, 75, -46, 15, + 75, -44, 10, 75, -42, 6, 76, -40, 2, 76, -38, -2, + 76, -36, -6, 76, -34, -10, 77, -32, -14, 77, -29, -18, + 77, -27, -22, 77, -24, -26, 78, -22, -30, 78, -19, -34, + 75, -64, 73, 75, -64, 72, 75, -64, 72, 75, -64, 70, + 75, -64, 69, 75, -63, 67, 75, -63, 64, 75, -62, 61, + 75, -62, 58, 75, -61, 55, 75, -60, 52, 75, -59, 48, + 75, -58, 44, 75, -57, 41, 76, -56, 37, 76, -54, 33, + 76, -53, 28, 76, -51, 24, 76, -50, 20, 76, -48, 16, + 76, -46, 12, 77, -44, 8, 77, -42, 4, 77, -40, 0, + 77, -38, -4, 77, -36, -9, 78, -34, -12, 78, -31, -16, + 78, -29, -20, 79, -26, -25, 79, -24, -28, 79, -21, -32, + 76, -66, 74, 76, -66, 74, 76, -66, 73, 76, -66, 72, + 76, -65, 70, 76, -65, 68, 76, -64, 66, 76, -64, 63, + 77, -63, 60, 77, -63, 57, 77, -62, 54, 77, -61, 50, + 77, -60, 46, 77, -59, 42, 77, -57, 39, 77, -56, 35, + 77, -55, 30, 77, -53, 27, 78, -52, 23, 78, -50, 19, + 78, -48, 14, 78, -46, 10, 78, -44, 6, 79, -42, 2, + 79, -40, -2, 79, -38, -6, 79, -36, -10, 79, -34, -14, + 80, -31, -18, 80, -29, -22, 80, -26, -26, 81, -24, -30, + 78, -67, 75, 78, -67, 75, 78, -67, 74, 78, -67, 73, + 78, -67, 71, 78, -66, 69, 78, -66, 67, 78, -65, 64, + 78, -65, 61, 78, -64, 58, 78, -63, 55, 78, -62, 52, + 78, -61, 48, 78, -60, 44, 78, -59, 40, 78, -58, 36, + 79, -56, 32, 79, -55, 28, 79, -53, 24, 79, -52, 20, + 79, -50, 16, 79, -48, 12, 79, -46, 8, 80, -44, 4, + 80, -42, 0, 80, -40, -5, 80, -38, -9, 81, -36, -12, + 81, -33, -16, 81, -31, -21, 81, -28, -25, 82, -26, -28, + 79, -69, 76, 79, -69, 76, 79, -68, 75, 79, -68, 74, + 79, -68, 72, 79, -68, 70, 79, -67, 68, 79, -67, 65, + 79, -66, 63, 79, -65, 60, 79, -64, 56, 79, -64, 53, + 79, -63, 49, 79, -62, 45, 80, -60, 42, 80, -59, 38, + 80, -58, 34, 80, -56, 30, 80, -55, 26, 80, -53, 22, + 80, -51, 17, 81, -50, 13, 81, -48, 9, 81, -46, 6, + 81, -44, 2, 81, -42, -3, 82, -40, -7, 82, -37, -11, + 82, -35, -15, 82, -33, -19, 83, -30, -23, 83, -28, -27, + 80, -70, 77, 80, -70, 77, 80, -70, 76, 80, -69, 75, + 80, -69, 73, 80, -69, 71, 80, -68, 69, 80, -68, 67, + 80, -67, 64, 80, -67, 61, 80, -66, 58, 80, -65, 54, + 81, -64, 51, 81, -63, 47, 81, -62, 43, 81, -61, 40, + 81, -59, 35, 81, -58, 31, 81, -56, 27, 81, -55, 24, + 82, -53, 19, 82, -51, 15, 82, -49, 11, 82, -48, 7, + 82, -46, 3, 82, -43, -1, 83, -41, -5, 83, -39, -9, + 83, -37, -13, 83, -34, -17, 84, -32, -21, 84, -30, -25, + 81, -71, 78, 81, -71, 78, 81, -71, 77, 81, -71, 76, + 81, -70, 74, 81, -70, 73, 81, -70, 70, 81, -69, 68, + 81, -69, 65, 82, -68, 62, 82, -67, 59, 82, -66, 56, + 82, -65, 52, 82, -64, 48, 82, -63, 45, 82, -62, 41, + 82, -61, 37, 82, -59, 33, 82, -58, 29, 83, -56, 25, + 83, -55, 21, 83, -53, 17, 83, -51, 13, 83, -49, 9, + 83, -47, 5, 84, -45, 1, 84, -43, -3, 84, -41, -7, + 84, -39, -11, 85, -36, -15, 85, -34, -19, 85, -32, -23, + 82, -72, 79, 83, -72, 79, 83, -72, 78, 83, -72, 77, + 83, -72, 75, 83, -71, 74, 83, -71, 72, 83, -70, 69, + 83, -70, 66, 83, -69, 63, 83, -69, 60, 83, -68, 57, + 83, -67, 53, 83, -66, 50, 83, -65, 46, 83, -64, 43, + 83, -62, 38, 83, -61, 35, 84, -59, 31, 84, -58, 27, + 84, -56, 22, 84, -54, 18, 84, -53, 15, 84, -51, 11, + 85, -49, 7, 85, -47, 2, 85, -45, -2, 85, -43, -6, + 85, -41, -9, 86, -38, -14, 86, -36, -18, 86, -34, -21, + 84, -74, 80, 84, -74, 80, 84, -73, 79, 84, -73, 78, + 84, -73, 76, 84, -73, 75, 84, -72, 73, 84, -72, 70, + 84, -71, 68, 84, -71, 65, 84, -70, 62, 84, -69, 59, + 84, -68, 55, 84, -67, 51, 84, -66, 48, 84, -65, 44, + 85, -64, 40, 85, -62, 36, 85, -61, 32, 85, -59, 28, + 85, -58, 24, 85, -56, 20, 85, -54, 16, 86, -53, 12, + 86, -51, 8, 86, -49, 4, 86, -47, 0, 86, -45, -4, + 87, -42, -8, 87, -40, -12, 87, -38, -16, 87, -36, -20, + 85, -75, 81, 85, -75, 81, 85, -75, 80, 85, -74, 79, + 85, -74, 77, 85, -74, 76, 85, -74, 74, 85, -73, 71, + 85, -72, 69, 85, -72, 66, 85, -71, 63, 85, -70, 60, + 85, -69, 56, 85, -68, 53, 86, -67, 49, 86, -66, 46, + 86, -65, 41, 86, -64, 38, 86, -62, 34, 86, -61, 30, + 86, -59, 26, 86, -58, 22, 87, -56, 18, 87, -54, 14, + 87, -52, 10, 87, -50, 6, 87, -48, 2, 88, -46, -2, + 88, -44, -6, 88, -42, -10, 88, -40, -14, 89, -37, -18, + 86, -76, 82, 86, -76, 82, 86, -76, 81, 86, -76, 80, + 86, -75, 79, 86, -75, 77, 86, -75, 75, 86, -74, 72, + 86, -74, 70, 86, -73, 67, 86, -72, 64, 87, -72, 61, + 87, -71, 58, 87, -70, 54, 87, -69, 51, 87, -68, 47, + 87, -66, 43, 87, -65, 39, 87, -64, 35, 87, -62, 32, + 88, -61, 27, 88, -59, 23, 88, -57, 20, 88, -56, 16, + 88, -54, 12, 88, -52, 7, 89, -50, 3, 89, -48, 0, + 89, -46, -4, 89, -44, -9, 89, -41, -12, 90, -39, -16, + 87, -77, 83, 87, -77, 83, 87, -77, 82, 87, -77, 81, + 87, -77, 80, 87, -76, 78, 87, -76, 76, 88, -76, 74, + 88, -75, 71, 88, -74, 69, 88, -74, 66, 88, -73, 63, + 88, -72, 59, 88, -71, 56, 88, -70, 52, 88, -69, 49, + 88, -68, 44, 88, -67, 41, 88, -65, 37, 89, -64, 33, + 89, -62, 29, 89, -61, 25, 89, -59, 21, 89, -57, 17, + 89, -56, 13, 90, -54, 9, 90, -52, 5, 90, -50, 1, + 90, -48, -3, 90, -45, -7, 91, -43, -11, 91, -41, -15, + 89, -79, 84, 89, -78, 84, 89, -78, 83, 89, -78, 82, + 89, -78, 81, 89, -78, 79, 89, -77, 77, 89, -77, 75, + 89, -76, 72, 89, -76, 70, 89, -75, 67, 89, -74, 64, + 89, -73, 60, 89, -72, 57, 89, -72, 54, 89, -70, 50, + 89, -69, 46, 89, -68, 42, 90, -67, 39, 90, -65, 35, + 90, -64, 30, 90, -62, 27, 90, -61, 23, 90, -59, 19, + 90, -57, 15, 91, -55, 11, 91, -53, 7, 91, -51, 3, + 91, -49, -1, 91, -47, -5, 92, -45, -9, 92, -43, -13, + 16, 37, 25, 16, 38, 22, 17, 38, 19, 17, 38, 13, + 17, 39, 7, 17, 40, 2, 17, 40, -4, 18, 41, -9, + 18, 42, -14, 19, 43, -19, 19, 45, -24, 20, 46, -28, + 20, 48, -33, 21, 49, -38, 22, 50, -42, 22, 52, -46, + 23, 54, -50, 24, 56, -54, 25, 57, -58, 25, 59, -61, + 26, 61, -65, 27, 63, -69, 28, 64, -72, 29, 66, -75, + 30, 68, -78, 31, 70, -82, 32, 72, -85, 33, 74, -88, + 34, 75, -91, 35, 77, -95, 36, 79, -97, 37, 81, -100, + 17, 36, 26, 17, 36, 23, 17, 37, 19, 17, 37, 13, + 17, 38, 8, 18, 38, 2, 18, 39, -3, 18, 40, -9, + 19, 41, -14, 19, 42, -19, 20, 44, -23, 20, 45, -28, + 21, 46, -33, 21, 48, -37, 22, 50, -41, 23, 51, -45, + 23, 53, -50, 24, 55, -53, 25, 56, -57, 26, 58, -61, + 27, 60, -65, 28, 62, -68, 28, 64, -72, 29, 65, -75, + 30, 67, -78, 31, 69, -82, 32, 71, -85, 33, 73, -88, + 34, 75, -91, 35, 77, -94, 36, 79, -97, 37, 80, -100, + 17, 35, 26, 17, 35, 23, 17, 36, 20, 18, 36, 14, + 18, 37, 8, 18, 37, 3, 18, 38, -2, 19, 39, -8, + 19, 40, -13, 19, 41, -18, 20, 43, -23, 20, 44, -27, + 21, 46, -32, 22, 47, -37, 22, 49, -41, 23, 50, -45, + 24, 52, -49, 24, 54, -53, 25, 56, -57, 26, 57, -60, + 27, 59, -64, 28, 61, -68, 29, 63, -71, 29, 65, -74, + 30, 67, -78, 31, 69, -81, 32, 71, -84, 33, 72, -88, + 34, 74, -91, 35, 76, -94, 36, 78, -97, 37, 80, -100, + 18, 34, 27, 18, 34, 24, 18, 34, 20, 18, 35, 14, + 18, 35, 9, 18, 36, 4, 19, 37, -2, 19, 38, -8, + 19, 39, -13, 20, 40, -17, 20, 41, -22, 21, 43, -27, + 21, 45, -32, 22, 46, -36, 23, 48, -40, 23, 49, -44, + 24, 51, -49, 25, 53, -53, 25, 55, -56, 26, 57, -60, + 27, 59, -64, 28, 60, -67, 29, 62, -71, 30, 64, -74, + 31, 66, -77, 32, 68, -81, 32, 70, -84, 33, 72, -87, + 34, 74, -90, 35, 76, -94, 36, 78, -97, 37, 79, -100, + 18, 32, 28, 18, 33, 24, 18, 33, 21, 18, 34, 15, + 19, 34, 10, 19, 35, 4, 19, 36, -1, 19, 37, -7, + 20, 38, -12, 20, 39, -17, 21, 40, -22, 21, 42, -26, + 22, 43, -31, 22, 45, -35, 23, 47, -40, 24, 48, -44, + 24, 50, -48, 25, 52, -52, 26, 54, -56, 27, 56, -59, + 27, 58, -63, 28, 60, -67, 29, 61, -70, 30, 63, -74, + 31, 65, -77, 32, 67, -81, 33, 69, -84, 34, 71, -87, + 35, 73, -90, 36, 75, -93, 36, 77, -96, 37, 79, -99, + 19, 31, 28, 19, 31, 25, 19, 31, 21, 19, 32, 16, + 19, 33, 10, 19, 33, 5, 20, 34, 0, 20, 35, -6, + 20, 36, -11, 21, 38, -16, 21, 39, -21, 22, 40, -25, + 22, 42, -30, 23, 44, -35, 23, 45, -39, 24, 47, -43, + 25, 49, -47, 25, 51, -51, 26, 53, -55, 27, 55, -59, + 28, 57, -63, 29, 59, -66, 29, 60, -70, 30, 62, -73, + 31, 64, -76, 32, 66, -80, 33, 68, -83, 34, 70, -86, + 35, 72, -89, 36, 74, -93, 37, 76, -96, 38, 78, -99, + 19, 29, 29, 19, 29, 26, 19, 30, 22, 20, 30, 17, + 20, 31, 11, 20, 31, 6, 20, 32, 1, 21, 33, -5, + 21, 35, -10, 21, 36, -15, 22, 37, -20, 22, 39, -24, + 23, 40, -30, 23, 42, -34, 24, 44, -38, 25, 46, -42, + 25, 48, -47, 26, 50, -51, 27, 51, -54, 27, 53, -58, + 28, 55, -62, 29, 57, -66, 30, 59, -69, 31, 61, -73, + 31, 63, -76, 32, 65, -80, 33, 67, -83, 34, 69, -86, + 35, 71, -89, 36, 73, -92, 37, 75, -95, 38, 77, -98, + 20, 27, 30, 20, 27, 27, 20, 28, 23, 20, 28, 17, + 20, 29, 12, 21, 30, 7, 21, 30, 2, 21, 32, -4, + 22, 33, -9, 22, 34, -14, 22, 35, -19, 23, 37, -23, + 23, 39, -29, 24, 40, -33, 24, 42, -37, 25, 44, -41, + 26, 46, -46, 26, 48, -50, 27, 50, -54, 28, 52, -57, + 29, 54, -61, 29, 56, -65, 30, 58, -68, 31, 60, -72, + 32, 62, -75, 33, 64, -79, 34, 66, -82, 35, 68, -85, + 35, 70, -88, 36, 72, -92, 37, 74, -95, 38, 76, -98, + 21, 25, 31, 21, 25, 27, 21, 26, 24, 21, 26, 18, + 21, 27, 13, 21, 28, 8, 22, 28, 3, 22, 30, -3, + 22, 31, -8, 23, 32, -13, 23, 34, -18, 23, 35, -22, + 24, 37, -27, 25, 39, -32, 25, 40, -36, 26, 42, -40, + 26, 44, -45, 27, 46, -49, 28, 48, -53, 28, 50, -56, + 29, 53, -61, 30, 55, -64, 31, 57, -68, 32, 59, -71, + 32, 61, -74, 33, 63, -78, 34, 65, -81, 35, 67, -85, + 36, 69, -88, 37, 71, -91, 38, 73, -94, 39, 75, -97, + 22, 23, 32, 22, 23, 28, 22, 23, 25, 22, 24, 19, + 22, 25, 14, 22, 25, 9, 23, 26, 4, 23, 27, -2, + 23, 29, -7, 23, 30, -12, 24, 31, -17, 24, 33, -21, + 25, 35, -26, 25, 37, -31, 26, 38, -35, 26, 40, -39, + 27, 43, -44, 28, 45, -48, 28, 47, -52, 29, 49, -55, + 30, 51, -60, 31, 53, -63, 31, 55, -67, 32, 57, -70, + 33, 59, -74, 34, 62, -77, 35, 64, -81, 35, 66, -84, + 36, 68, -87, 37, 70, -91, 38, 72, -94, 39, 74, -97, + 23, 21, 33, 23, 21, 29, 23, 21, 26, 23, 22, 20, + 23, 22, 15, 23, 23, 10, 23, 24, 5, 24, 25, -1, + 24, 26, -6, 24, 28, -11, 25, 29, -15, 25, 31, -20, + 26, 33, -25, 26, 35, -30, 27, 36, -34, 27, 38, -38, + 28, 41, -43, 28, 43, -47, 29, 45, -51, 30, 47, -54, + 30, 49, -59, 31, 51, -62, 32, 53, -66, 33, 55, -69, + 33, 58, -73, 34, 60, -76, 35, 62, -80, 36, 64, -83, + 37, 66, -86, 38, 69, -90, 39, 71, -93, 39, 73, -96, + 24, 18, 34, 24, 18, 31, 24, 18, 27, 24, 19, 22, + 24, 19, 17, 24, 20, 12, 25, 21, 7, 25, 22, 1, + 25, 24, -4, 25, 25, -9, 26, 26, -14, 26, 28, -18, + 27, 30, -23, 27, 32, -28, 28, 34, -32, 28, 36, -36, + 29, 38, -41, 29, 40, -45, 30, 42, -49, 31, 44, -53, + 31, 47, -57, 32, 49, -61, 33, 51, -64, 33, 53, -68, + 34, 55, -71, 35, 58, -75, 36, 60, -79, 37, 62, -82, + 37, 64, -85, 38, 67, -89, 39, 69, -92, 40, 71, -95, + 25, 15, 35, 25, 15, 32, 25, 16, 28, 25, 16, 23, + 25, 17, 18, 25, 18, 13, 26, 19, 8, 26, 20, 2, + 26, 21, -2, 26, 22, -7, 27, 24, -12, 27, 26, -17, + 27, 28, -22, 28, 29, -26, 28, 31, -31, 29, 33, -35, + 30, 36, -40, 30, 38, -44, 31, 40, -48, 31, 42, -52, + 32, 45, -56, 33, 47, -60, 33, 49, -63, 34, 51, -67, + 35, 54, -70, 36, 56, -74, 36, 58, -78, 37, 60, -81, + 38, 63, -84, 39, 65, -88, 40, 67, -91, 41, 69, -94, + 26, 13, 36, 26, 13, 33, 26, 13, 29, 26, 14, 24, + 26, 14, 19, 26, 15, 15, 27, 16, 10, 27, 17, 4, + 27, 19, -1, 27, 20, -6, 28, 22, -11, 28, 23, -15, + 28, 25, -21, 29, 27, -25, 29, 29, -29, 30, 31, -34, + 30, 33, -38, 31, 36, -42, 31, 38, -46, 32, 40, -50, + 33, 43, -55, 33, 45, -58, 34, 47, -62, 35, 49, -66, + 36, 52, -69, 36, 54, -73, 37, 56, -77, 38, 59, -80, + 39, 61, -83, 39, 63, -87, 40, 66, -90, 41, 68, -93, + 27, 10, 37, 27, 10, 34, 27, 11, 30, 27, 11, 25, + 27, 12, 21, 27, 13, 16, 28, 14, 11, 28, 15, 5, + 28, 16, 1, 28, 18, -4, 29, 19, -9, 29, 21, -14, + 29, 23, -19, 30, 25, -24, 30, 27, -28, 31, 29, -32, + 31, 31, -37, 32, 33, -41, 32, 35, -45, 33, 38, -49, + 34, 40, -53, 34, 43, -57, 35, 45, -61, 36, 47, -64, + 36, 50, -68, 37, 52, -72, 38, 54, -75, 39, 57, -79, + 39, 59, -82, 40, 62, -86, 41, 64, -89, 42, 66, -92, + 28, 8, 38, 28, 8, 35, 28, 8, 31, 28, 9, 27, + 28, 9, 22, 29, 10, 17, 29, 11, 12, 29, 12, 7, + 29, 14, 2, 29, 15, -3, 30, 17, -8, 30, 18, -12, + 30, 20, -17, 31, 22, -22, 31, 24, -26, 32, 26, -31, + 32, 29, -35, 33, 31, -40, 33, 33, -44, 34, 35, -48, + 34, 38, -52, 35, 40, -56, 36, 43, -59, 36, 45, -63, + 37, 47, -67, 38, 50, -71, 38, 52, -74, 39, 55, -78, + 40, 57, -81, 41, 60, -85, 42, 62, -88, 42, 64, -91, + 29, 5, 39, 29, 5, 35, 29, 6, 32, 29, 6, 28, + 30, 7, 24, 30, 8, 19, 30, 9, 14, 30, 10, 9, + 30, 11, 4, 31, 12, -1, 31, 14, -6, 31, 16, -11, + 31, 18, -16, 32, 20, -20, 32, 22, -25, 33, 24, -29, + 33, 26, -34, 34, 28, -38, 34, 31, -42, 35, 33, -46, + 35, 36, -51, 36, 38, -54, 37, 40, -58, 37, 43, -62, + 38, 45, -65, 39, 48, -69, 39, 50, -73, 40, 53, -76, + 41, 55, -80, 42, 58, -83, 42, 60, -87, 43, 62, -90, + 30, 3, 40, 31, 3, 36, 31, 3, 33, 31, 4, 29, + 31, 4, 25, 31, 5, 20, 31, 6, 16, 31, 7, 10, + 31, 8, 5, 32, 10, 0, 32, 11, -4, 32, 13, -9, + 33, 15, -14, 33, 17, -19, 33, 19, -23, 34, 21, -28, + 34, 24, -32, 35, 26, -36, 35, 28, -41, 36, 31, -45, + 36, 33, -49, 37, 36, -53, 37, 38, -57, 38, 40, -60, + 39, 43, -64, 39, 46, -68, 40, 48, -72, 41, 50, -75, + 41, 53, -78, 42, 56, -82, 43, 58, -86, 44, 60, -89, + 32, 0, 40, 32, 0, 37, 32, 1, 35, 32, 1, 30, + 32, 2, 26, 32, 3, 22, 32, 3, 17, 32, 5, 12, + 33, 6, 7, 33, 7, 2, 33, 9, -3, 33, 11, -7, + 34, 13, -13, 34, 15, -17, 34, 17, -21, 35, 19, -26, + 35, 21, -31, 36, 23, -35, 36, 26, -39, 37, 28, -43, + 37, 31, -48, 38, 33, -51, 38, 36, -55, 39, 38, -59, + 40, 41, -63, 40, 43, -67, 41, 46, -70, 42, 48, -74, + 42, 51, -77, 43, 53, -81, 44, 56, -84, 44, 58, -88, + 33, -2, 41, 33, -2, 38, 33, -2, 36, 33, -1, 32, + 33, -1, 28, 33, 0, 23, 33, 1, 19, 34, 2, 13, + 34, 3, 9, 34, 5, 4, 34, 6, -1, 34, 8, -6, + 35, 10, -11, 35, 12, -15, 35, 14, -20, 36, 16, -24, + 36, 19, -29, 37, 21, -33, 37, 23, -37, 38, 26, -41, + 38, 28, -46, 39, 31, -50, 39, 33, -54, 40, 36, -57, + 40, 38, -61, 41, 41, -65, 42, 43, -69, 42, 46, -72, + 43, 48, -76, 44, 51, -80, 45, 54, -83, 45, 56, -86, + 34, -5, 42, 34, -5, 39, 34, -4, 37, 34, -4, 33, + 34, -3, 29, 34, -2, 25, 35, -1, 20, 35, 0, 15, + 35, 1, 10, 35, 2, 5, 35, 4, 1, 36, 6, -4, + 36, 8, -9, 36, 9, -14, 37, 12, -18, 37, 14, -22, + 37, 16, -27, 38, 18, -32, 38, 21, -36, 39, 23, -40, + 39, 26, -44, 40, 28, -48, 40, 31, -52, 41, 33, -56, + 41, 36, -60, 42, 39, -64, 43, 41, -67, 43, 44, -71, + 44, 46, -74, 45, 49, -78, 45, 51, -82, 46, 54, -85, + 35, -7, 43, 35, -7, 41, 35, -7, 38, 35, -6, 34, + 36, -6, 30, 36, -5, 26, 36, -4, 22, 36, -3, 17, + 36, -1, 12, 36, 0, 7, 37, 1, 2, 37, 3, -2, + 37, 5, -7, 37, 7, -12, 38, 9, -16, 38, 11, -21, + 38, 14, -26, 39, 16, -30, 39, 18, -34, 40, 21, -38, + 40, 23, -43, 41, 26, -47, 41, 28, -51, 42, 31, -54, + 42, 33, -58, 43, 36, -62, 44, 39, -66, 44, 41, -69, + 45, 44, -73, 46, 47, -77, 46, 49, -80, 47, 52, -84, + 37, -10, 44, 37, -9, 42, 37, -9, 39, 37, -8, 36, + 37, -8, 32, 37, -7, 28, 37, -6, 23, 37, -5, 18, + 37, -4, 14, 38, -3, 9, 38, -1, 4, 38, 1, 0, + 38, 3, -6, 39, 4, -10, 39, 6, -15, 39, 9, -19, + 40, 11, -24, 40, 13, -28, 40, 16, -32, 41, 18, -36, + 41, 21, -41, 42, 23, -45, 42, 26, -49, 43, 28, -53, + 43, 31, -57, 44, 34, -61, 44, 36, -64, 45, 39, -68, + 46, 41, -72, 46, 44, -75, 47, 47, -79, 48, 49, -82, + 38, -12, 45, 38, -12, 43, 38, -11, 40, 38, -11, 37, + 38, -10, 33, 38, -9, 29, 38, -9, 25, 38, -7, 20, + 39, -6, 15, 39, -5, 11, 39, -3, 6, 39, -2, 1, + 39, 0, -4, 40, 2, -8, 40, 4, -13, 40, 6, -17, + 41, 9, -22, 41, 11, -26, 41, 13, -31, 42, 16, -35, + 42, 18, -39, 43, 21, -43, 43, 23, -47, 44, 26, -51, + 44, 28, -55, 45, 31, -59, 45, 34, -63, 46, 36, -66, + 47, 39, -70, 47, 42, -74, 48, 45, -77, 49, 47, -81, + 39, -14, 46, 39, -14, 44, 39, -13, 41, 39, -13, 38, + 39, -12, 34, 39, -12, 31, 39, -11, 26, 40, -10, 21, + 40, -9, 17, 40, -7, 12, 40, -6, 8, 40, -4, 3, + 41, -2, -2, 41, 0, -7, 41, 2, -11, 41, 4, -15, + 42, 6, -20, 42, 8, -25, 43, 11, -29, 43, 13, -33, + 43, 16, -38, 44, 18, -42, 44, 21, -46, 45, 23, -49, + 45, 26, -53, 46, 29, -58, 46, 31, -61, 47, 34, -65, + 48, 37, -69, 48, 40, -72, 49, 42, -76, 49, 45, -79, + 40, -16, 47, 40, -16, 45, 40, -16, 43, 40, -15, 39, + 41, -15, 36, 41, -14, 32, 41, -13, 28, 41, -12, 23, + 41, -11, 18, 41, -10, 14, 41, -8, 9, 42, -7, 5, + 42, -5, 0, 42, -3, -5, 42, -1, -9, 43, 1, -14, + 43, 4, -19, 43, 6, -23, 44, 8, -27, 44, 11, -31, + 45, 13, -36, 45, 16, -40, 45, 18, -44, 46, 21, -48, + 46, 23, -52, 47, 26, -56, 47, 29, -60, 48, 32, -63, + 49, 34, -67, 49, 37, -71, 50, 40, -74, 50, 42, -78, + 42, -18, 48, 42, -18, 46, 42, -18, 44, 42, -17, 41, + 42, -17, 37, 42, -16, 33, 42, -15, 29, 42, -14, 25, + 42, -13, 20, 42, -12, 16, 43, -10, 11, 43, -9, 7, + 43, -7, 1, 43, -5, -3, 44, -3, -7, 44, -1, -12, + 44, 1, -17, 44, 3, -21, 45, 6, -25, 45, 8, -29, + 46, 11, -34, 46, 13, -38, 46, 16, -42, 47, 18, -46, + 47, 21, -50, 48, 24, -54, 48, 27, -58, 49, 29, -62, + 49, 32, -65, 50, 35, -69, 51, 37, -73, 51, 40, -76, + 43, -20, 49, 43, -20, 47, 43, -20, 45, 43, -20, 42, + 43, -19, 39, 43, -18, 35, 43, -18, 31, 43, -16, 26, + 44, -15, 22, 44, -14, 17, 44, -13, 13, 44, -11, 8, + 44, -9, 3, 44, -8, -1, 45, -6, -6, 45, -4, -10, + 45, -1, -15, 46, 1, -19, 46, 3, -24, 46, 6, -28, + 47, 8, -32, 47, 11, -36, 48, 13, -40, 48, 16, -44, + 48, 18, -48, 49, 21, -53, 49, 24, -56, 50, 27, -60, + 50, 29, -64, 51, 32, -68, 52, 35, -71, 52, 38, -75, + 44, -23, 50, 44, -22, 48, 44, -22, 46, 44, -22, 43, + 44, -21, 40, 44, -20, 36, 45, -20, 32, 45, -19, 28, + 45, -18, 23, 45, -16, 19, 45, -15, 15, 45, -13, 10, + 45, -12, 5, 46, -10, 1, 46, -8, -4, 46, -6, -8, + 47, -4, -13, 47, -1, -17, 47, 1, -22, 47, 3, -26, + 48, 6, -31, 48, 8, -35, 49, 11, -39, 49, 13, -43, + 50, 16, -47, 50, 19, -51, 50, 22, -55, 51, 24, -58, + 51, 27, -62, 52, 30, -66, 53, 33, -70, 53, 35, -73, + 45, -25, 51, 46, -24, 49, 46, -24, 47, 46, -24, 44, + 46, -23, 41, 46, -22, 38, 46, -22, 34, 46, -21, 29, + 46, -20, 25, 46, -18, 21, 46, -17, 16, 46, -16, 12, + 47, -14, 7, 47, -12, 2, 47, -10, -2, 47, -8, -7, + 48, -6, -11, 48, -4, -16, 48, -2, -20, 49, 1, -24, + 49, 3, -29, 49, 6, -33, 50, 8, -37, 50, 11, -41, + 51, 14, -45, 51, 16, -49, 52, 19, -53, 52, 22, -57, + 53, 24, -60, 53, 27, -65, 54, 30, -68, 54, 33, -72, + 47, -27, 52, 47, -26, 50, 47, -26, 48, 47, -26, 45, + 47, -25, 42, 47, -25, 39, 47, -24, 35, 47, -23, 31, + 47, -22, 27, 47, -21, 22, 48, -19, 18, 48, -18, 13, + 48, -16, 8, 48, -14, 4, 48, -12, 0, 49, -11, -5, + 49, -8, -10, 49, -6, -14, 49, -4, -18, 50, -2, -22, + 50, 1, -27, 51, 4, -31, 51, 6, -35, 51, 9, -39, + 52, 11, -43, 52, 14, -47, 53, 17, -51, 53, 19, -55, + 54, 22, -59, 54, 25, -63, 55, 28, -67, 55, 30, -70, + 48, -28, 53, 48, -28, 51, 48, -28, 49, 48, -28, 47, + 48, -27, 44, 48, -27, 40, 48, -26, 37, 48, -25, 32, + 49, -24, 28, 49, -23, 24, 49, -21, 20, 49, -20, 15, + 49, -18, 10, 49, -16, 6, 50, -15, 1, 50, -13, -3, + 50, -11, -8, 50, -8, -12, 51, -6, -16, 51, -4, -21, + 51, -1, -25, 52, 1, -29, 52, 4, -33, 52, 6, -37, + 53, 9, -41, 53, 12, -46, 54, 14, -50, 54, 17, -53, + 55, 20, -57, 55, 23, -61, 56, 25, -65, 56, 28, -69, + 50, -31, 54, 50, -31, 52, 50, -30, 51, 50, -30, 48, + 50, -30, 45, 50, -29, 42, 50, -28, 39, 50, -27, 34, + 50, -26, 30, 50, -25, 26, 50, -24, 22, 51, -22, 17, + 51, -21, 12, 51, -19, 8, 51, -17, 4, 51, -16, -1, + 52, -13, -6, 52, -11, -10, 52, -9, -14, 52, -7, -18, + 53, -4, -23, 53, -2, -27, 53, 1, -31, 54, 3, -35, + 54, 6, -39, 55, 9, -44, 55, 11, -47, 55, 14, -51, + 56, 16, -55, 56, 19, -59, 57, 22, -63, 57, 25, -66, + 51, -33, 55, 51, -32, 53, 51, -32, 52, 51, -32, 49, + 51, -31, 47, 51, -31, 43, 51, -30, 40, 51, -29, 36, + 51, -28, 32, 52, -27, 28, 52, -26, 23, 52, -25, 19, + 52, -23, 14, 52, -21, 10, 52, -20, 5, 53, -18, 1, + 53, -15, -4, 53, -13, -8, 53, -11, -12, 54, -9, -16, + 54, -7, -21, 54, -4, -25, 55, -2, -29, 55, 1, -33, + 55, 3, -37, 56, 6, -42, 56, 9, -46, 57, 11, -49, + 57, 14, -53, 58, 17, -57, 58, 20, -61, 58, 22, -65, + 52, -34, 56, 52, -34, 54, 52, -34, 53, 52, -34, 50, + 52, -33, 48, 52, -33, 45, 52, -32, 41, 53, -31, 37, + 53, -30, 33, 53, -29, 29, 53, -28, 25, 53, -27, 21, + 53, -25, 16, 53, -23, 12, 54, -22, 7, 54, -20, 3, + 54, -18, -2, 54, -16, -6, 55, -14, -10, 55, -11, -15, + 55, -9, -19, 55, -6, -23, 56, -4, -28, 56, -2, -32, + 56, 1, -36, 57, 4, -40, 57, 6, -44, 58, 9, -48, + 58, 12, -52, 59, 15, -56, 59, 17, -59, 59, 20, -63, + 54, -36, 57, 54, -36, 55, 54, -36, 54, 54, -35, 52, + 54, -35, 49, 54, -35, 46, 54, -34, 43, 54, -33, 39, + 54, -32, 35, 54, -31, 31, 54, -30, 27, 54, -28, 22, + 54, -27, 18, 55, -25, 13, 55, -24, 9, 55, -22, 5, + 55, -20, 0, 55, -18, -4, 56, -16, -9, 56, -14, -13, + 56, -11, -18, 57, -9, -22, 57, -6, -26, 57, -4, -30, + 58, -1, -34, 58, 1, -38, 58, 4, -42, 59, 7, -46, + 59, 9, -50, 60, 12, -54, 60, 15, -58, 61, 18, -61, + 55, -38, 58, 55, -38, 56, 55, -38, 55, 55, -37, 53, + 55, -37, 50, 55, -36, 47, 55, -36, 44, 55, -35, 40, + 55, -34, 36, 55, -33, 32, 55, -32, 28, 56, -30, 24, + 56, -29, 19, 56, -27, 15, 56, -26, 11, 56, -24, 6, + 56, -22, 2, 57, -20, -3, 57, -18, -7, 57, -16, -11, + 58, -13, -16, 58, -11, -20, 58, -9, -24, 58, -6, -28, + 59, -4, -32, 59, -1, -36, 60, 2, -40, 60, 4, -44, + 60, 7, -48, 61, 10, -52, 61, 13, -56, 62, 15, -60, + 56, -40, 59, 56, -40, 57, 56, -39, 56, 56, -39, 54, + 56, -39, 52, 56, -38, 49, 56, -37, 45, 56, -37, 42, + 56, -36, 38, 57, -35, 34, 57, -34, 30, 57, -32, 26, + 57, -31, 21, 57, -29, 17, 57, -28, 12, 57, -26, 8, + 58, -24, 3, 58, -22, -1, 58, -20, -5, 58, -18, -9, + 59, -15, -14, 59, -13, -18, 59, -11, -22, 60, -8, -26, + 60, -6, -30, 60, -3, -35, 61, -1, -39, 61, 2, -42, + 61, 5, -46, 62, 8, -51, 62, 10, -54, 63, 13, -58, + 57, -41, 60, 57, -41, 58, 57, -41, 57, 57, -41, 55, + 57, -40, 53, 58, -40, 50, 58, -39, 47, 58, -38, 43, + 58, -37, 39, 58, -36, 35, 58, -35, 31, 58, -34, 27, + 58, -33, 23, 58, -31, 18, 59, -30, 14, 59, -28, 10, + 59, -26, 5, 59, -24, 1, 59, -22, -3, 60, -20, -7, + 60, -18, -12, 60, -15, -16, 60, -13, -20, 61, -11, -24, + 61, -8, -28, 61, -5, -33, 62, -3, -37, 62, 0, -41, + 63, 2, -45, 63, 5, -49, 63, 8, -53, 64, 11, -56, + 59, -43, 61, 59, -43, 60, 59, -43, 58, 59, -42, 56, + 59, -42, 54, 59, -41, 51, 59, -41, 48, 59, -40, 44, + 59, -39, 41, 59, -38, 37, 59, -37, 33, 59, -36, 29, + 59, -34, 24, 60, -33, 20, 60, -31, 16, 60, -30, 12, + 60, -28, 7, 60, -26, 3, 61, -24, -2, 61, -22, -6, + 61, -20, -10, 61, -17, -14, 62, -15, -19, 62, -13, -23, + 62, -10, -27, 63, -8, -31, 63, -5, -35, 63, -3, -39, + 64, 0, -43, 64, 3, -47, 64, 6, -51, 65, 8, -55, + 60, -45, 62, 60, -45, 61, 60, -44, 59, 60, -44, 57, + 60, -44, 55, 60, -43, 53, 60, -43, 50, 60, -42, 46, + 60, -41, 42, 60, -40, 38, 60, -39, 35, 61, -38, 31, + 61, -36, 26, 61, -35, 22, 61, -33, 18, 61, -32, 13, + 61, -30, 9, 62, -28, 4, 62, -26, 0, 62, -24, -4, + 62, -22, -9, 63, -20, -13, 63, -17, -17, 63, -15, -21, + 63, -13, -25, 64, -10, -29, 64, -7, -33, 64, -5, -37, + 65, -2, -41, 65, 1, -45, 66, 3, -49, 66, 6, -53, + 61, -46, 63, 61, -46, 62, 61, -46, 60, 61, -46, 58, + 61, -45, 56, 61, -45, 54, 61, -44, 51, 61, -43, 47, + 62, -43, 44, 62, -42, 40, 62, -41, 36, 62, -40, 32, + 62, -38, 28, 62, -37, 23, 62, -35, 19, 62, -34, 15, + 63, -32, 10, 63, -30, 6, 63, -28, 2, 63, -26, -2, + 63, -24, -7, 64, -22, -11, 64, -19, -15, 64, -17, -19, + 65, -15, -23, 65, -12, -28, 65, -10, -32, 66, -7, -35, + 66, -5, -39, 66, -2, -44, 67, 1, -47, 67, 4, -51, + 62, -48, 64, 62, -48, 63, 62, -48, 61, 63, -47, 60, + 63, -47, 58, 63, -46, 55, 63, -46, 52, 63, -45, 49, + 63, -44, 45, 63, -43, 41, 63, -42, 38, 63, -41, 34, + 63, -40, 29, 63, -38, 25, 63, -37, 21, 64, -35, 17, + 64, -34, 12, 64, -32, 8, 64, -30, 4, 64, -28, 0, + 65, -26, -5, 65, -24, -9, 65, -21, -13, 65, -19, -17, + 66, -17, -21, 66, -14, -26, 66, -12, -30, 67, -9, -34, + 67, -7, -38, 67, -4, -42, 68, -1, -46, 68, 1, -49, + 64, -49, 65, 64, -49, 64, 64, -49, 63, 64, -49, 61, + 64, -48, 59, 64, -48, 56, 64, -47, 53, 64, -47, 50, + 64, -46, 46, 64, -45, 43, 64, -44, 39, 64, -43, 35, + 64, -42, 31, 65, -40, 27, 65, -39, 23, 65, -37, 18, + 65, -35, 14, 65, -34, 10, 65, -32, 6, 66, -30, 1, + 66, -28, -3, 66, -26, -7, 66, -23, -11, 67, -21, -15, + 67, -19, -20, 67, -16, -24, 68, -14, -28, 68, -11, -32, + 68, -9, -36, 69, -6, -40, 69, -4, -44, 69, -1, -48, + 65, -51, 66, 65, -51, 65, 65, -51, 64, 65, -50, 62, + 65, -50, 60, 65, -50, 57, 65, -49, 55, 65, -48, 51, + 65, -48, 48, 65, -47, 44, 65, -46, 41, 66, -45, 37, + 66, -43, 32, 66, -42, 28, 66, -41, 24, 66, -39, 20, + 66, -37, 16, 66, -36, 11, 67, -34, 7, 67, -32, 3, + 67, -30, -2, 67, -28, -6, 68, -26, -10, 68, -23, -14, + 68, -21, -18, 68, -18, -22, 69, -16, -26, 69, -14, -30, + 69, -11, -34, 70, -8, -38, 70, -6, -42, 70, -3, -46, + 66, -52, 67, 66, -52, 66, 66, -52, 65, 66, -52, 63, + 66, -52, 61, 66, -51, 59, 66, -51, 56, 66, -50, 53, + 67, -49, 49, 67, -48, 46, 67, -47, 42, 67, -46, 38, + 67, -45, 34, 67, -44, 30, 67, -42, 26, 67, -41, 22, + 67, -39, 17, 68, -37, 13, 68, -36, 9, 68, -34, 5, + 68, -32, 0, 68, -30, -4, 69, -27, -8, 69, -25, -12, + 69, -23, -16, 70, -21, -20, 70, -18, -24, 70, -16, -28, + 70, -13, -32, 71, -11, -37, 71, -8, -40, 71, -5, -44, + 67, -54, 68, 68, -54, 67, 68, -54, 66, 68, -53, 64, + 68, -53, 62, 68, -53, 60, 68, -52, 57, 68, -51, 54, + 68, -51, 51, 68, -50, 47, 68, -49, 44, 68, -48, 40, + 68, -47, 36, 68, -45, 32, 68, -44, 28, 69, -43, 24, + 69, -41, 19, 69, -39, 15, 69, -37, 11, 69, -36, 7, + 69, -34, 2, 70, -32, -2, 70, -29, -6, 70, -27, -10, + 70, -25, -14, 71, -23, -19, 71, -20, -23, 71, -18, -27, + 72, -15, -30, 72, -13, -35, 72, -10, -39, 73, -8, -42, + 69, -55, 69, 69, -55, 68, 69, -55, 67, 69, -55, 65, + 69, -55, 63, 69, -54, 61, 69, -54, 58, 69, -53, 55, + 69, -52, 52, 69, -51, 49, 69, -51, 45, 69, -49, 41, + 69, -48, 37, 70, -47, 33, 70, -46, 29, 70, -44, 25, + 70, -43, 21, 70, -41, 17, 70, -39, 12, 70, -37, 8, + 71, -35, 4, 71, -33, 0, 71, -31, -4, 71, -29, -8, + 72, -27, -12, 72, -25, -17, 72, -22, -21, 72, -20, -25, + 73, -18, -29, 73, -15, -33, 73, -12, -37, 74, -10, -41, + 70, -57, 70, 70, -57, 69, 70, -57, 68, 70, -56, 66, + 70, -56, 64, 70, -56, 62, 70, -55, 60, 70, -54, 57, + 70, -54, 53, 70, -53, 50, 70, -52, 47, 71, -51, 43, + 71, -50, 39, 71, -49, 35, 71, -47, 31, 71, -46, 27, + 71, -44, 22, 71, -43, 18, 71, -41, 14, 72, -39, 10, + 72, -37, 5, 72, -35, 1, 72, -33, -3, 73, -31, -7, + 73, -29, -11, 73, -27, -15, 73, -24, -19, 74, -22, -23, + 74, -20, -27, 74, -17, -31, 74, -14, -35, 75, -12, -39, + 71, -58, 71, 71, -58, 70, 71, -58, 69, 71, -58, 67, + 71, -57, 66, 71, -57, 63, 71, -57, 61, 71, -56, 58, + 72, -55, 55, 72, -54, 51, 72, -54, 48, 72, -53, 44, + 72, -51, 40, 72, -50, 36, 72, -49, 32, 72, -48, 28, + 72, -46, 24, 73, -44, 20, 73, -43, 16, 73, -41, 12, + 73, -39, 7, 73, -37, 3, 73, -35, -1, 74, -33, -5, + 74, -31, -9, 74, -29, -13, 74, -26, -17, 75, -24, -21, + 75, -22, -25, 75, -19, -30, 76, -17, -33, 76, -14, -37, + 73, -60, 72, 73, -60, 71, 73, -59, 70, 73, -59, 68, + 73, -59, 67, 73, -58, 65, 73, -58, 62, 73, -57, 59, + 73, -57, 56, 73, -56, 53, 73, -55, 49, 73, -54, 46, + 73, -53, 42, 73, -52, 38, 73, -51, 34, 73, -49, 30, + 74, -48, 26, 74, -46, 22, 74, -45, 17, 74, -43, 13, + 74, -41, 9, 74, -39, 5, 75, -37, 1, 75, -35, -3, + 75, -33, -7, 75, -30, -12, 76, -28, -16, 76, -26, -20, + 76, -24, -23, 76, -21, -28, 77, -19, -32, 77, -16, -35, + 74, -61, 73, 74, -61, 72, 74, -61, 71, 74, -61, 69, + 74, -60, 68, 74, -60, 66, 74, -59, 63, 74, -59, 60, + 74, -58, 57, 74, -57, 54, 74, -57, 51, 74, -56, 47, + 74, -55, 43, 74, -53, 39, 75, -52, 36, 75, -51, 32, + 75, -49, 27, 75, -48, 23, 75, -46, 19, 75, -45, 15, + 75, -43, 11, 76, -41, 7, 76, -39, 3, 76, -37, -1, + 76, -35, -5, 77, -32, -10, 77, -30, -14, 77, -28, -18, + 77, -26, -22, 78, -23, -26, 78, -21, -30, 78, -18, -34, + 75, -62, 74, 75, -62, 73, 75, -62, 72, 75, -62, 70, + 75, -62, 69, 75, -61, 67, 75, -61, 65, 75, -60, 62, + 75, -60, 59, 75, -59, 56, 75, -58, 52, 75, -57, 49, + 76, -56, 45, 76, -55, 41, 76, -54, 37, 76, -52, 33, + 76, -51, 29, 76, -49, 25, 76, -48, 21, 76, -46, 17, + 77, -44, 12, 77, -43, 8, 77, -41, 4, 77, -39, 0, + 77, -37, -4, 78, -34, -8, 78, -32, -12, 78, -30, -16, + 78, -28, -20, 79, -25, -24, 79, -23, -28, 79, -20, -32, + 77, -64, 75, 77, -64, 74, 77, -64, 73, 77, -64, 72, + 77, -63, 70, 77, -63, 68, 77, -63, 66, 77, -62, 63, + 77, -61, 60, 77, -61, 57, 77, -60, 54, 77, -59, 51, + 77, -58, 47, 77, -57, 43, 77, -56, 39, 77, -54, 35, + 78, -53, 31, 78, -51, 27, 78, -50, 23, 78, -48, 19, + 78, -46, 14, 78, -45, 10, 79, -43, 6, 79, -41, 2, + 79, -39, -2, 79, -37, -6, 79, -34, -10, 80, -32, -14, + 80, -30, -18, 80, -28, -22, 80, -25, -26, 81, -23, -30, + 78, -66, 76, 78, -65, 75, 78, -65, 74, 78, -65, 73, + 78, -65, 71, 78, -64, 69, 78, -64, 67, 78, -63, 64, + 78, -63, 62, 78, -62, 59, 78, -61, 55, 78, -60, 52, + 78, -59, 48, 78, -58, 44, 79, -57, 41, 79, -56, 37, + 79, -54, 32, 79, -53, 28, 79, -52, 25, 79, -50, 21, + 79, -48, 16, 80, -46, 12, 80, -45, 8, 80, -43, 4, + 80, -41, 0, 80, -38, -4, 81, -36, -8, 81, -34, -12, + 81, -32, -16, 81, -29, -20, 82, -27, -24, 82, -25, -28, + 79, -67, 77, 79, -67, 76, 79, -67, 75, 79, -66, 74, + 79, -66, 72, 79, -66, 71, 79, -65, 68, 79, -65, 66, + 79, -64, 63, 79, -64, 60, 79, -63, 57, 79, -62, 53, + 80, -61, 49, 80, -60, 46, 80, -59, 42, 80, -57, 38, + 80, -56, 34, 80, -55, 30, 80, -53, 26, 80, -52, 22, + 81, -50, 18, 81, -48, 14, 81, -46, 10, 81, -44, 6, + 81, -43, 2, 82, -40, -3, 82, -38, -6, 82, -36, -10, + 82, -34, -14, 82, -31, -19, 83, -29, -22, 83, -27, -26, + 80, -68, 78, 80, -68, 77, 80, -68, 76, 80, -68, 75, + 80, -67, 73, 80, -67, 72, 80, -67, 70, 80, -66, 67, + 80, -66, 64, 81, -65, 61, 81, -64, 58, 81, -63, 55, + 81, -62, 51, 81, -61, 47, 81, -60, 44, 81, -59, 40, + 81, -58, 36, 81, -56, 32, 81, -55, 28, 82, -53, 24, + 82, -51, 19, 82, -50, 15, 82, -48, 12, 82, -46, 8, + 82, -44, 4, 83, -42, -1, 83, -40, -5, 83, -38, -9, + 83, -36, -13, 84, -33, -17, 84, -31, -21, 84, -29, -25, + 81, -69, 79, 81, -69, 78, 81, -69, 77, 82, -69, 76, + 82, -69, 75, 82, -68, 73, 82, -68, 71, 82, -67, 68, + 82, -67, 65, 82, -66, 62, 82, -66, 59, 82, -65, 56, + 82, -64, 52, 82, -63, 49, 82, -62, 45, 82, -60, 41, + 82, -59, 37, 82, -58, 33, 83, -56, 29, 83, -55, 25, + 83, -53, 21, 83, -51, 17, 83, -50, 13, 83, -48, 9, + 84, -46, 5, 84, -44, 1, 84, -42, -3, 84, -40, -7, + 85, -38, -11, 85, -35, -15, 85, -33, -19, 85, -31, -23, + 83, -71, 80, 83, -71, 79, 83, -70, 78, 83, -70, 77, + 83, -70, 76, 83, -70, 74, 83, -69, 72, 83, -69, 69, + 83, -68, 67, 83, -68, 64, 83, -67, 61, 83, -66, 57, + 83, -65, 54, 83, -64, 50, 83, -63, 47, 83, -62, 43, + 84, -61, 39, 84, -59, 35, 84, -58, 31, 84, -56, 27, + 84, -55, 23, 84, -53, 19, 84, -51, 15, 85, -50, 11, + 85, -48, 7, 85, -46, 3, 85, -44, -1, 85, -42, -5, + 86, -39, -9, 86, -37, -13, 86, -35, -17, 86, -33, -21, + 84, -72, 81, 84, -72, 80, 84, -72, 79, 84, -72, 78, + 84, -71, 77, 84, -71, 75, 84, -71, 73, 84, -70, 70, + 84, -70, 68, 84, -69, 65, 84, -68, 62, 84, -67, 59, + 84, -67, 55, 84, -66, 52, 85, -65, 48, 85, -63, 44, + 85, -62, 40, 85, -61, 36, 85, -59, 33, 85, -58, 29, + 85, -56, 24, 85, -55, 20, 86, -53, 17, 86, -51, 13, + 86, -49, 9, 86, -47, 4, 86, -45, 0, 87, -43, -4, + 87, -41, -7, 87, -39, -12, 87, -37, -16, 88, -34, -19, + 85, -73, 82, 85, -73, 81, 85, -73, 80, 85, -73, 79, + 85, -73, 78, 85, -72, 76, 85, -72, 74, 85, -71, 72, + 85, -71, 69, 85, -70, 66, 85, -70, 63, 86, -69, 60, + 86, -68, 56, 86, -67, 53, 86, -66, 50, 86, -65, 46, + 86, -64, 42, 86, -62, 38, 86, -61, 34, 86, -59, 30, + 86, -58, 26, 87, -56, 22, 87, -55, 18, 87, -53, 14, + 87, -51, 10, 87, -49, 6, 88, -47, 2, 88, -45, -2, + 88, -43, -6, 88, -41, -10, 88, -38, -14, 89, -36, -18, + 86, -75, 83, 86, -74, 82, 86, -74, 81, 86, -74, 80, + 86, -74, 79, 86, -74, 77, 86, -73, 75, 87, -73, 73, + 87, -72, 70, 87, -72, 68, 87, -71, 65, 87, -70, 62, + 87, -69, 58, 87, -68, 54, 87, -67, 51, 87, -66, 47, + 87, -65, 43, 87, -64, 40, 87, -62, 36, 88, -61, 32, + 88, -59, 28, 88, -58, 24, 88, -56, 20, 88, -54, 16, + 88, -53, 12, 89, -51, 8, 89, -49, 4, 89, -47, 0, + 89, -45, -4, 89, -42, -8, 90, -40, -12, 90, -38, -16, + 88, -76, 83, 88, -76, 83, 88, -76, 82, 88, -75, 81, + 88, -75, 80, 88, -75, 78, 88, -74, 76, 88, -74, 74, + 88, -73, 71, 88, -73, 69, 88, -72, 66, 88, -72, 63, + 88, -71, 59, 88, -70, 56, 88, -69, 52, 88, -68, 49, + 88, -66, 45, 88, -65, 41, 89, -64, 37, 89, -62, 33, + 89, -61, 29, 89, -59, 25, 89, -58, 21, 89, -56, 18, + 89, -54, 14, 90, -52, 9, 90, -50, 5, 90, -49, 2, + 90, -47, -2, 91, -44, -7, 91, -42, -10, 91, -40, -14, + 89, -77, 84, 89, -77, 84, 89, -77, 83, 89, -77, 82, + 89, -76, 81, 89, -76, 79, 89, -76, 77, 89, -75, 75, + 89, -75, 73, 89, -74, 70, 89, -74, 67, 89, -73, 64, + 89, -72, 61, 89, -71, 57, 89, -70, 54, 89, -69, 50, + 90, -68, 46, 90, -67, 43, 90, -65, 39, 90, -64, 35, + 90, -62, 31, 90, -61, 27, 90, -59, 23, 90, -58, 19, + 91, -56, 15, 91, -54, 11, 91, -52, 7, 91, -50, 3, + 91, -48, -1, 92, -46, -5, 92, -44, -9, 92, -42, -13, + 19, 40, 29, 19, 40, 25, 19, 40, 22, 19, 41, 16, + 19, 41, 11, 19, 42, 5, 20, 43, 0, 20, 43, -6, + 20, 44, -11, 21, 45, -16, 21, 46, -21, 22, 48, -25, + 22, 49, -30, 23, 50, -35, 23, 52, -39, 24, 53, -43, + 25, 55, -47, 25, 57, -51, 26, 58, -55, 27, 60, -59, + 28, 62, -63, 29, 63, -66, 29, 65, -70, 30, 67, -73, + 31, 69, -76, 32, 71, -80, 33, 72, -83, 34, 74, -86, + 35, 76, -90, 36, 78, -93, 37, 80, -96, 38, 81, -99, + 19, 39, 29, 19, 39, 26, 19, 39, 22, 19, 40, 17, + 19, 40, 11, 20, 41, 6, 20, 42, 0, 20, 42, -6, + 21, 43, -11, 21, 44, -15, 21, 46, -20, 22, 47, -25, + 22, 48, -30, 23, 50, -34, 24, 51, -38, 24, 53, -43, + 25, 54, -47, 26, 56, -51, 26, 57, -55, 27, 59, -58, + 28, 61, -62, 29, 63, -66, 30, 65, -69, 30, 66, -73, + 31, 68, -76, 32, 70, -80, 33, 72, -83, 34, 74, -86, + 35, 75, -89, 36, 77, -93, 37, 79, -96, 38, 81, -99, + 19, 38, 29, 19, 38, 26, 19, 38, 23, 20, 39, 17, + 20, 39, 12, 20, 40, 6, 20, 41, 1, 21, 41, -5, + 21, 42, -10, 21, 43, -15, 22, 45, -20, 22, 46, -24, + 23, 47, -29, 23, 49, -34, 24, 50, -38, 25, 52, -42, + 25, 53, -47, 26, 55, -51, 27, 57, -54, 27, 58, -58, + 28, 60, -62, 29, 62, -66, 30, 64, -69, 31, 66, -72, + 31, 67, -76, 32, 69, -79, 33, 71, -83, 34, 73, -86, + 35, 75, -89, 36, 77, -92, 37, 79, -95, 38, 80, -98, + 20, 37, 30, 20, 37, 27, 20, 37, 23, 20, 38, 17, + 20, 38, 12, 20, 39, 7, 21, 40, 1, 21, 40, -5, + 21, 41, -10, 22, 42, -14, 22, 44, -19, 22, 45, -24, + 23, 46, -29, 24, 48, -33, 24, 49, -38, 25, 51, -42, + 26, 53, -46, 26, 54, -50, 27, 56, -54, 28, 58, -58, + 28, 60, -62, 29, 61, -65, 30, 63, -69, 31, 65, -72, + 32, 67, -75, 33, 69, -79, 34, 71, -82, 34, 72, -86, + 35, 74, -89, 36, 76, -92, 37, 78, -95, 38, 80, -98, + 20, 36, 30, 20, 36, 27, 20, 36, 24, 20, 37, 18, + 21, 37, 13, 21, 38, 7, 21, 38, 2, 21, 39, -4, + 22, 40, -9, 22, 41, -14, 22, 43, -19, 23, 44, -23, + 23, 45, -28, 24, 47, -33, 25, 48, -37, 25, 50, -41, + 26, 52, -46, 26, 53, -50, 27, 55, -53, 28, 57, -57, + 29, 59, -61, 30, 61, -65, 30, 62, -68, 31, 64, -72, + 32, 66, -75, 33, 68, -79, 34, 70, -82, 35, 72, -85, + 35, 74, -88, 36, 76, -92, 37, 78, -95, 38, 79, -98, + 21, 34, 31, 21, 34, 28, 21, 35, 24, 21, 35, 19, + 21, 36, 13, 21, 36, 8, 21, 37, 3, 22, 38, -3, + 22, 39, -8, 22, 40, -13, 23, 41, -18, 23, 43, -23, + 24, 44, -28, 24, 46, -32, 25, 47, -36, 26, 49, -40, + 26, 51, -45, 27, 52, -49, 28, 54, -53, 28, 56, -57, + 29, 58, -61, 30, 60, -64, 31, 61, -68, 31, 63, -71, + 32, 65, -75, 33, 67, -78, 34, 69, -82, 35, 71, -85, + 36, 73, -88, 37, 75, -91, 38, 77, -94, 39, 79, -97, + 21, 33, 32, 21, 33, 28, 21, 33, 25, 21, 34, 19, + 22, 34, 14, 22, 35, 9, 22, 35, 3, 22, 36, -2, + 23, 37, -7, 23, 39, -12, 23, 40, -17, 24, 41, -22, + 24, 43, -27, 25, 44, -31, 25, 46, -36, 26, 47, -40, + 27, 49, -44, 27, 51, -48, 28, 53, -52, 29, 55, -56, + 29, 57, -60, 30, 59, -64, 31, 60, -67, 32, 62, -71, + 33, 64, -74, 33, 66, -78, 34, 68, -81, 35, 70, -84, + 36, 72, -87, 37, 74, -91, 38, 76, -94, 39, 78, -97, + 22, 31, 32, 22, 31, 29, 22, 31, 25, 22, 32, 20, + 22, 32, 15, 22, 33, 10, 23, 34, 4, 23, 35, -1, + 23, 36, -7, 24, 37, -11, 24, 38, -16, 24, 39, -21, + 25, 41, -26, 25, 43, -30, 26, 44, -35, 26, 46, -39, + 27, 48, -44, 28, 50, -47, 28, 51, -51, 29, 53, -55, + 30, 55, -59, 31, 57, -63, 31, 59, -66, 32, 61, -70, + 33, 63, -73, 34, 65, -77, 35, 67, -80, 36, 69, -84, + 36, 71, -87, 37, 73, -90, 38, 75, -93, 39, 77, -96, + 23, 29, 33, 23, 29, 30, 23, 29, 26, 23, 30, 21, + 23, 30, 16, 23, 31, 11, 23, 32, 5, 24, 33, 0, + 24, 34, -5, 24, 35, -10, 25, 36, -15, 25, 38, -20, + 26, 39, -25, 26, 41, -29, 27, 43, -34, 27, 44, -38, + 28, 46, -43, 28, 48, -47, 29, 50, -51, 30, 52, -54, + 30, 54, -59, 31, 56, -62, 32, 58, -66, 33, 60, -69, + 33, 62, -73, 34, 64, -76, 35, 66, -80, 36, 68, -83, + 37, 70, -86, 38, 72, -90, 39, 74, -93, 39, 76, -96, + 23, 27, 34, 23, 27, 31, 24, 27, 27, 24, 28, 22, + 24, 28, 17, 24, 29, 12, 24, 30, 6, 24, 31, 1, + 25, 32, -4, 25, 33, -9, 25, 34, -14, 26, 36, -19, + 26, 37, -24, 27, 39, -28, 27, 41, -33, 28, 42, -37, + 28, 45, -42, 29, 46, -46, 30, 48, -50, 30, 50, -53, + 31, 52, -58, 32, 54, -61, 32, 56, -65, 33, 58, -68, + 34, 60, -72, 35, 63, -76, 36, 65, -79, 36, 67, -82, + 37, 69, -85, 38, 71, -89, 39, 73, -92, 40, 75, -95, + 24, 25, 35, 24, 25, 32, 24, 25, 28, 24, 26, 23, + 25, 26, 18, 25, 27, 13, 25, 28, 8, 25, 29, 2, + 25, 30, -3, 26, 31, -8, 26, 32, -13, 26, 34, -18, + 27, 35, -23, 27, 37, -27, 28, 39, -32, 28, 41, -36, + 29, 43, -41, 30, 45, -45, 30, 46, -49, 31, 48, -52, + 32, 51, -57, 32, 53, -60, 33, 55, -64, 34, 57, -68, + 34, 59, -71, 35, 61, -75, 36, 63, -78, 37, 65, -81, + 38, 67, -85, 39, 69, -88, 39, 71, -91, 40, 73, -95, + 25, 22, 36, 25, 22, 33, 25, 22, 29, 26, 23, 24, + 26, 23, 19, 26, 24, 14, 26, 25, 9, 26, 26, 3, + 27, 27, -2, 27, 28, -7, 27, 30, -11, 27, 31, -16, + 28, 33, -21, 28, 34, -26, 29, 36, -30, 29, 38, -34, + 30, 40, -39, 30, 42, -43, 31, 44, -47, 32, 46, -51, + 32, 48, -55, 33, 50, -59, 34, 52, -63, 34, 55, -66, + 35, 57, -70, 36, 59, -74, 37, 61, -77, 37, 63, -80, + 38, 65, -84, 39, 68, -87, 40, 70, -90, 41, 72, -94, + 26, 19, 37, 26, 20, 33, 26, 20, 30, 27, 20, 25, + 27, 21, 20, 27, 22, 15, 27, 22, 10, 27, 24, 5, + 27, 25, 0, 28, 26, -5, 28, 27, -10, 28, 29, -15, + 29, 31, -20, 29, 32, -24, 30, 34, -29, 30, 36, -33, + 31, 38, -38, 31, 40, -42, 32, 42, -46, 32, 44, -50, + 33, 46, -54, 34, 48, -58, 34, 51, -62, 35, 53, -65, + 36, 55, -69, 37, 57, -73, 37, 59, -76, 38, 62, -79, + 39, 64, -83, 40, 66, -86, 41, 68, -90, 41, 70, -93, + 27, 17, 38, 27, 17, 34, 27, 18, 31, 27, 18, 26, + 28, 19, 21, 28, 19, 17, 28, 20, 12, 28, 21, 6, + 28, 22, 1, 29, 24, -4, 29, 25, -9, 29, 26, -13, + 30, 28, -19, 30, 30, -23, 30, 32, -27, 31, 34, -32, + 31, 36, -37, 32, 38, -41, 33, 40, -45, 33, 42, -49, + 34, 44, -53, 34, 46, -57, 35, 49, -60, 36, 51, -64, + 36, 53, -68, 37, 55, -72, 38, 58, -75, 39, 60, -78, + 39, 62, -82, 40, 64, -85, 41, 66, -89, 42, 69, -92, + 28, 15, 38, 28, 15, 35, 28, 15, 32, 29, 16, 27, + 29, 16, 23, 29, 17, 18, 29, 18, 13, 29, 19, 7, + 29, 20, 2, 30, 21, -2, 30, 23, -7, 30, 24, -12, + 31, 26, -17, 31, 28, -22, 31, 29, -26, 32, 31, -30, + 32, 34, -35, 33, 36, -39, 33, 38, -43, 34, 40, -47, + 35, 42, -52, 35, 44, -56, 36, 47, -59, 36, 49, -63, + 37, 51, -67, 38, 53, -70, 39, 56, -74, 39, 58, -77, + 40, 60, -81, 41, 63, -84, 42, 65, -88, 42, 67, -91, + 29, 12, 39, 29, 12, 36, 29, 13, 33, 30, 13, 28, + 30, 14, 24, 30, 14, 19, 30, 15, 14, 30, 16, 9, + 30, 17, 4, 31, 19, -1, 31, 20, -6, 31, 22, -10, + 32, 23, -16, 32, 25, -20, 32, 27, -25, 33, 29, -29, + 33, 31, -34, 34, 33, -38, 34, 35, -42, 35, 37, -46, + 35, 40, -50, 36, 42, -54, 37, 44, -58, 37, 47, -62, + 38, 49, -65, 39, 51, -69, 39, 54, -73, 40, 56, -76, + 41, 58, -80, 42, 61, -83, 42, 63, -87, 43, 65, -90, + 30, 10, 40, 31, 10, 37, 31, 10, 34, 31, 11, 30, + 31, 11, 25, 31, 12, 21, 31, 13, 16, 31, 14, 10, + 31, 15, 5, 32, 16, 1, 32, 18, -4, 32, 19, -9, + 33, 21, -14, 33, 23, -19, 33, 25, -23, 34, 27, -27, + 34, 29, -32, 35, 31, -36, 35, 33, -41, 36, 35, -45, + 36, 38, -49, 37, 40, -53, 37, 42, -57, 38, 44, -60, + 39, 47, -64, 39, 49, -68, 40, 52, -72, 41, 54, -75, + 41, 56, -78, 42, 59, -82, 43, 61, -85, 44, 63, -89, + 32, 7, 41, 32, 7, 38, 32, 8, 35, 32, 8, 31, + 32, 9, 26, 32, 9, 22, 32, 10, 17, 32, 11, 12, + 32, 12, 7, 33, 14, 2, 33, 15, -3, 33, 17, -7, + 34, 19, -13, 34, 20, -17, 34, 22, -22, 35, 24, -26, + 35, 26, -31, 36, 28, -35, 36, 31, -39, 37, 33, -43, + 37, 35, -48, 38, 38, -51, 38, 40, -55, 39, 42, -59, + 39, 45, -63, 40, 47, -67, 41, 49, -70, 42, 52, -74, + 42, 54, -77, 43, 57, -81, 44, 59, -84, 44, 61, -88, + 33, 5, 42, 33, 5, 39, 33, 5, 36, 33, 6, 32, + 33, 6, 28, 33, 7, 23, 33, 8, 19, 33, 9, 13, + 34, 10, 8, 34, 11, 4, 34, 13, -1, 34, 14, -6, + 35, 16, -11, 35, 18, -15, 35, 20, -20, 36, 22, -24, + 36, 24, -29, 37, 26, -33, 37, 28, -38, 38, 30, -42, + 38, 33, -46, 39, 35, -50, 39, 38, -54, 40, 40, -58, + 40, 42, -61, 41, 45, -65, 42, 47, -69, 42, 50, -72, + 43, 52, -76, 44, 55, -80, 44, 57, -83, 45, 59, -86, + 34, 2, 42, 34, 2, 40, 34, 3, 37, 34, 3, 33, + 34, 4, 29, 34, 4, 25, 34, 5, 20, 35, 6, 15, + 35, 7, 10, 35, 9, 5, 35, 10, 1, 35, 12, -4, + 36, 14, -9, 36, 15, -14, 36, 17, -18, 37, 19, -23, + 37, 21, -28, 38, 24, -32, 38, 26, -36, 38, 28, -40, + 39, 31, -45, 40, 33, -48, 40, 35, -52, 41, 38, -56, + 41, 40, -60, 42, 43, -64, 42, 45, -68, 43, 47, -71, + 44, 50, -75, 45, 52, -78, 45, 55, -82, 46, 57, -85, + 35, 0, 43, 35, 0, 41, 35, 0, 38, 35, 1, 34, + 35, 1, 30, 35, 2, 26, 36, 3, 22, 36, 4, 16, + 36, 5, 12, 36, 6, 7, 36, 8, 2, 37, 9, -2, + 37, 11, -8, 37, 13, -12, 37, 15, -17, 38, 17, -21, + 38, 19, -26, 39, 21, -30, 39, 23, -34, 39, 25, -38, + 40, 28, -43, 40, 30, -47, 41, 33, -51, 42, 35, -55, + 42, 38, -58, 43, 40, -63, 43, 43, -66, 44, 45, -70, + 45, 48, -73, 45, 50, -77, 46, 53, -81, 47, 55, -84, + 36, -3, 44, 36, -2, 42, 36, -2, 39, 36, -2, 35, + 36, -1, 32, 37, 0, 27, 37, 0, 23, 37, 1, 18, + 37, 3, 13, 37, 4, 9, 37, 5, 4, 38, 7, -1, + 38, 9, -6, 38, 10, -10, 39, 12, -15, 39, 14, -19, + 39, 16, -24, 40, 19, -29, 40, 21, -33, 40, 23, -37, + 41, 26, -41, 41, 28, -45, 42, 30, -49, 42, 33, -53, + 43, 35, -57, 44, 38, -61, 44, 40, -65, 45, 43, -68, + 45, 45, -72, 46, 48, -76, 47, 50, -79, 47, 53, -83, + 37, -5, 45, 38, -5, 43, 38, -5, 40, 38, -4, 37, + 38, -4, 33, 38, -3, 29, 38, -2, 25, 38, -1, 19, + 38, 0, 15, 38, 1, 10, 39, 3, 6, 39, 4, 1, + 39, 6, -4, 39, 8, -9, 40, 10, -13, 40, 12, -18, + 40, 14, -23, 41, 16, -27, 41, 18, -31, 42, 21, -35, + 42, 23, -40, 42, 25, -44, 43, 28, -48, 43, 30, -52, + 44, 33, -55, 45, 36, -60, 45, 38, -63, 46, 40, -67, + 46, 43, -70, 47, 46, -74, 48, 48, -78, 48, 51, -81, + 39, -7, 46, 39, -7, 44, 39, -7, 41, 39, -6, 38, + 39, -6, 34, 39, -5, 30, 39, -4, 26, 39, -3, 21, + 39, -2, 16, 40, -1, 12, 40, 0, 7, 40, 2, 3, + 40, 4, -3, 40, 5, -7, 41, 7, -12, 41, 9, -16, + 41, 11, -21, 42, 14, -25, 42, 16, -29, 43, 18, -34, + 43, 21, -38, 44, 23, -42, 44, 25, -46, 44, 28, -50, + 45, 30, -54, 46, 33, -58, 46, 36, -62, 47, 38, -65, + 47, 41, -69, 48, 43, -73, 49, 46, -76, 49, 48, -80, + 40, -10, 47, 40, -9, 45, 40, -9, 42, 40, -9, 39, + 40, -8, 36, 40, -8, 32, 40, -7, 27, 40, -6, 23, + 41, -5, 18, 41, -3, 13, 41, -2, 9, 41, -1, 4, + 41, 1, -1, 42, 3, -5, 42, 5, -10, 42, 7, -14, + 43, 9, -19, 43, 11, -23, 43, 13, -28, 44, 16, -32, + 44, 18, -36, 45, 21, -40, 45, 23, -44, 45, 25, -48, + 46, 28, -52, 47, 31, -56, 47, 33, -60, 48, 36, -64, + 48, 38, -67, 49, 41, -71, 49, 44, -75, 50, 46, -78, + 41, -12, 48, 41, -12, 46, 41, -11, 43, 41, -11, 40, + 41, -10, 37, 41, -10, 33, 42, -9, 29, 42, -8, 24, + 42, -7, 20, 42, -6, 15, 42, -4, 11, 42, -3, 6, + 43, -1, 1, 43, 1, -4, 43, 2, -8, 43, 4, -13, + 44, 7, -17, 44, 9, -22, 44, 11, -26, 45, 13, -30, + 45, 16, -35, 46, 18, -39, 46, 20, -43, 46, 23, -47, + 47, 25, -51, 48, 28, -55, 48, 31, -59, 49, 33, -62, + 49, 36, -66, 50, 39, -70, 50, 41, -74, 51, 44, -77, + 42, -14, 49, 42, -14, 47, 42, -14, 45, 42, -13, 41, + 43, -13, 38, 43, -12, 34, 43, -11, 30, 43, -10, 26, + 43, -9, 21, 43, -8, 17, 43, -7, 12, 43, -5, 8, + 44, -4, 3, 44, -2, -2, 44, 0, -6, 44, 2, -11, + 45, 4, -16, 45, 6, -20, 45, 8, -24, 46, 11, -28, + 46, 13, -33, 47, 16, -37, 47, 18, -41, 48, 20, -45, + 48, 23, -49, 49, 26, -53, 49, 28, -57, 50, 31, -61, + 50, 33, -64, 51, 36, -68, 51, 39, -72, 52, 41, -76, + 44, -16, 50, 44, -16, 48, 44, -16, 46, 44, -15, 43, + 44, -15, 39, 44, -14, 36, 44, -14, 32, 44, -13, 27, + 44, -12, 23, 44, -10, 18, 45, -9, 14, 45, -8, 9, + 45, -6, 4, 45, -4, 0, 45, -2, -5, 46, -1, -9, + 46, 2, -14, 46, 4, -18, 47, 6, -22, 47, 8, -27, + 47, 11, -31, 48, 13, -35, 48, 16, -39, 49, 18, -43, + 49, 20, -47, 50, 23, -52, 50, 26, -55, 51, 28, -59, + 51, 31, -63, 52, 34, -67, 52, 36, -70, 53, 39, -74, + 45, -18, 50, 45, -18, 49, 45, -18, 47, 45, -18, 44, + 45, -17, 41, 45, -16, 37, 45, -16, 33, 45, -15, 29, + 45, -14, 24, 46, -13, 20, 46, -11, 16, 46, -10, 11, + 46, -8, 6, 46, -7, 2, 47, -5, -3, 47, -3, -7, + 47, -1, -12, 47, 1, -16, 48, 4, -21, 48, 6, -25, + 48, 8, -30, 49, 11, -34, 49, 13, -38, 50, 16, -42, + 50, 18, -46, 51, 21, -50, 51, 23, -54, 52, 26, -58, + 52, 29, -61, 53, 31, -65, 53, 34, -69, 54, 37, -72, + 46, -21, 51, 46, -20, 50, 46, -20, 48, 46, -20, 45, + 46, -19, 42, 46, -19, 39, 46, -18, 35, 47, -17, 30, + 47, -16, 26, 47, -15, 22, 47, -14, 17, 47, -12, 13, + 47, -10, 8, 48, -9, 3, 48, -7, -1, 48, -5, -6, + 48, -3, -10, 49, -1, -15, 49, 1, -19, 49, 3, -23, + 50, 6, -28, 50, 8, -32, 50, 11, -36, 51, 13, -40, + 51, 16, -44, 52, 18, -48, 52, 21, -52, 53, 24, -56, + 53, 26, -60, 54, 29, -64, 54, 32, -67, 55, 34, -71, + 47, -23, 52, 47, -22, 51, 47, -22, 49, 47, -22, 46, + 48, -21, 43, 48, -21, 40, 48, -20, 36, 48, -19, 32, + 48, -18, 27, 48, -17, 23, 48, -16, 19, 48, -14, 14, + 49, -13, 9, 49, -11, 5, 49, -9, 1, 49, -8, -4, + 49, -5, -9, 50, -3, -13, 50, -1, -17, 50, 1, -21, + 51, 4, -26, 51, 6, -30, 51, 8, -34, 52, 11, -38, + 52, 13, -42, 53, 16, -47, 53, 19, -50, 54, 21, -54, + 54, 24, -58, 55, 27, -62, 55, 29, -66, 56, 32, -69, + 49, -25, 53, 49, -24, 52, 49, -24, 50, 49, -24, 47, + 49, -23, 45, 49, -23, 41, 49, -22, 38, 49, -21, 33, + 49, -20, 29, 49, -19, 25, 49, -18, 20, 50, -17, 16, + 50, -15, 11, 50, -13, 7, 50, -12, 2, 50, -10, -2, + 51, -8, -7, 51, -6, -11, 51, -4, -15, 52, -1, -20, + 52, 1, -24, 52, 3, -28, 53, 6, -33, 53, 8, -37, + 53, 11, -40, 54, 14, -45, 54, 16, -49, 55, 19, -53, + 55, 21, -56, 56, 24, -60, 56, 27, -64, 57, 29, -68, + 50, -27, 54, 50, -27, 53, 50, -27, 51, 50, -26, 49, + 50, -26, 46, 50, -25, 43, 50, -25, 39, 51, -24, 35, + 51, -23, 31, 51, -22, 27, 51, -20, 23, 51, -19, 18, + 51, -18, 13, 51, -16, 9, 52, -14, 5, 52, -13, 0, + 52, -10, -5, 52, -8, -9, 53, -6, -13, 53, -4, -17, + 53, -2, -22, 54, 1, -26, 54, 3, -30, 54, 5, -34, + 55, 8, -38, 55, 11, -43, 56, 13, -47, 56, 16, -50, + 56, 18, -54, 57, 21, -58, 57, 24, -62, 58, 26, -66, + 51, -29, 55, 52, -29, 54, 52, -29, 52, 52, -28, 50, + 52, -28, 47, 52, -27, 44, 52, -27, 41, 52, -26, 36, + 52, -25, 32, 52, -24, 28, 52, -23, 24, 52, -21, 20, + 53, -20, 15, 53, -18, 11, 53, -17, 6, 53, -15, 2, + 53, -13, -3, 54, -11, -7, 54, -9, -11, 54, -7, -16, + 54, -4, -20, 55, -2, -24, 55, 1, -29, 55, 3, -33, + 56, 5, -37, 56, 8, -41, 57, 11, -45, 57, 13, -49, + 57, 16, -52, 58, 19, -57, 58, 21, -60, 59, 24, -64, + 53, -31, 56, 53, -31, 55, 53, -30, 53, 53, -30, 51, + 53, -30, 49, 53, -29, 45, 53, -29, 42, 53, -28, 38, + 53, -27, 34, 53, -26, 30, 53, -25, 26, 54, -23, 21, + 54, -22, 17, 54, -20, 12, 54, -19, 8, 54, -17, 4, + 55, -15, -1, 55, -13, -5, 55, -11, -10, 55, -9, -14, + 56, -6, -19, 56, -4, -23, 56, -2, -27, 57, 1, -31, + 57, 3, -35, 57, 6, -39, 58, 8, -43, 58, 11, -47, + 59, 13, -51, 59, 16, -55, 59, 19, -59, 60, 22, -62, + 54, -33, 57, 54, -33, 56, 54, -32, 55, 54, -32, 52, + 54, -32, 50, 54, -31, 47, 54, -30, 43, 54, -30, 39, + 54, -29, 35, 55, -28, 31, 55, -27, 27, 55, -25, 23, + 55, -24, 18, 55, -22, 14, 55, -21, 10, 56, -19, 5, + 56, -17, 1, 56, -15, -4, 56, -13, -8, 56, -11, -12, + 57, -9, -17, 57, -6, -21, 57, -4, -25, 58, -2, -29, + 58, 1, -33, 58, 3, -37, 59, 6, -41, 59, 8, -45, + 60, 11, -49, 60, 14, -53, 61, 17, -57, 61, 19, -61, + 55, -35, 58, 55, -34, 57, 55, -34, 56, 55, -34, 53, + 55, -33, 51, 55, -33, 48, 56, -32, 45, 56, -31, 41, + 56, -31, 37, 56, -30, 33, 56, -29, 29, 56, -27, 25, + 56, -26, 20, 56, -24, 16, 57, -23, 11, 57, -21, 7, + 57, -19, 2, 57, -17, -2, 57, -15, -6, 58, -13, -10, + 58, -11, -15, 58, -9, -19, 59, -6, -23, 59, -4, -27, + 59, -2, -31, 60, 1, -36, 60, 4, -40, 60, 6, -44, + 61, 9, -47, 61, 12, -52, 62, 14, -55, 62, 17, -59, + 57, -36, 59, 57, -36, 58, 57, -36, 57, 57, -36, 55, + 57, -35, 52, 57, -35, 49, 57, -34, 46, 57, -33, 42, + 57, -32, 38, 57, -32, 35, 57, -30, 31, 57, -29, 26, + 57, -28, 22, 58, -26, 17, 58, -25, 13, 58, -23, 9, + 58, -21, 4, 58, -19, 0, 59, -17, -4, 59, -15, -9, + 59, -13, -13, 59, -11, -17, 60, -9, -21, 60, -6, -26, + 60, -4, -30, 61, -1, -34, 61, 1, -38, 61, 4, -42, + 62, 6, -46, 62, 9, -50, 63, 12, -54, 63, 14, -57, + 58, -38, 60, 58, -38, 59, 58, -38, 58, 58, -37, 56, + 58, -37, 53, 58, -37, 51, 58, -36, 47, 58, -35, 44, + 58, -34, 40, 58, -33, 36, 58, -32, 32, 59, -31, 28, + 59, -30, 23, 59, -28, 19, 59, -27, 15, 59, -25, 11, + 59, -23, 6, 60, -21, 2, 60, -20, -3, 60, -18, -7, + 60, -15, -11, 61, -13, -16, 61, -11, -20, 61, -9, -24, + 61, -6, -28, 62, -3, -32, 62, -1, -36, 63, 1, -40, + 63, 4, -44, 63, 7, -48, 64, 9, -52, 64, 12, -56, + 59, -40, 61, 59, -40, 60, 59, -39, 59, 59, -39, 57, + 59, -39, 55, 59, -38, 52, 59, -38, 49, 59, -37, 45, + 59, -36, 41, 60, -35, 38, 60, -34, 34, 60, -33, 30, + 60, -32, 25, 60, -30, 21, 60, -29, 17, 60, -27, 12, + 61, -25, 8, 61, -23, 3, 61, -22, -1, 61, -20, -5, + 62, -17, -10, 62, -15, -14, 62, -13, -18, 62, -11, -22, + 63, -8, -26, 63, -6, -30, 63, -3, -34, 64, -1, -38, + 64, 2, -42, 64, 5, -46, 65, 7, -50, 65, 10, -54, + 60, -42, 62, 60, -41, 61, 60, -41, 60, 60, -41, 58, + 60, -41, 56, 60, -40, 53, 61, -39, 50, 61, -39, 46, + 61, -38, 43, 61, -37, 39, 61, -36, 35, 61, -35, 31, + 61, -33, 27, 61, -32, 22, 61, -31, 18, 62, -29, 14, + 62, -27, 9, 62, -25, 5, 62, -24, 1, 62, -22, -3, + 63, -19, -8, 63, -17, -12, 63, -15, -16, 63, -13, -20, + 64, -11, -24, 64, -8, -29, 64, -6, -33, 65, -3, -37, + 65, -1, -40, 66, 2, -45, 66, 5, -49, 66, 7, -52, + 62, -43, 63, 62, -43, 62, 62, -43, 61, 62, -43, 59, + 62, -42, 57, 62, -42, 54, 62, -41, 51, 62, -40, 48, + 62, -40, 44, 62, -39, 41, 62, -38, 37, 62, -37, 33, + 62, -35, 28, 62, -34, 24, 63, -33, 20, 63, -31, 16, + 63, -29, 11, 63, -27, 7, 63, -26, 3, 64, -24, -2, + 64, -21, -6, 64, -19, -10, 64, -17, -14, 65, -15, -18, + 65, -13, -22, 65, -10, -27, 66, -8, -31, 66, -5, -35, + 66, -3, -39, 67, 0, -43, 67, 3, -47, 67, 5, -51, + 63, -45, 64, 63, -45, 63, 63, -45, 62, 63, -44, 60, + 63, -44, 58, 63, -43, 56, 63, -43, 53, 63, -42, 49, + 63, -41, 46, 63, -41, 42, 63, -40, 38, 63, -38, 34, + 64, -37, 30, 64, -36, 26, 64, -34, 22, 64, -33, 17, + 64, -31, 13, 64, -29, 9, 65, -28, 4, 65, -26, 0, + 65, -24, -4, 65, -21, -9, 66, -19, -13, 66, -17, -17, + 66, -15, -21, 66, -12, -25, 67, -10, -29, 67, -8, -33, + 67, -5, -37, 68, -2, -41, 68, 0, -45, 68, 3, -49, + 64, -46, 65, 64, -46, 64, 64, -46, 63, 64, -46, 61, + 64, -46, 59, 64, -45, 57, 64, -45, 54, 64, -44, 50, + 64, -43, 47, 64, -42, 43, 65, -41, 40, 65, -40, 36, + 65, -39, 31, 65, -38, 27, 65, -36, 23, 65, -35, 19, + 65, -33, 14, 66, -31, 10, 66, -30, 6, 66, -28, 2, + 66, -26, -3, 66, -24, -7, 67, -21, -11, 67, -19, -15, + 67, -17, -19, 68, -14, -23, 68, -12, -27, 68, -10, -31, + 68, -7, -35, 69, -4, -40, 69, -2, -43, 70, 1, -47, + 65, -48, 66, 65, -48, 65, 65, -48, 64, 65, -47, 62, + 65, -47, 60, 65, -47, 58, 66, -46, 55, 66, -45, 52, + 66, -45, 48, 66, -44, 45, 66, -43, 41, 66, -42, 37, + 66, -41, 33, 66, -39, 29, 66, -38, 25, 66, -37, 21, + 67, -35, 16, 67, -33, 12, 67, -31, 8, 67, -30, 4, + 67, -28, -1, 68, -26, -5, 68, -23, -9, 68, -21, -13, + 68, -19, -17, 69, -17, -22, 69, -14, -26, 69, -12, -30, + 70, -9, -33, 70, -7, -38, 70, -4, -42, 71, -2, -45, + 67, -50, 67, 67, -49, 66, 67, -49, 65, 67, -49, 63, + 67, -49, 61, 67, -48, 59, 67, -48, 56, 67, -47, 53, + 67, -46, 50, 67, -46, 46, 67, -45, 43, 67, -44, 39, + 67, -42, 34, 67, -41, 31, 68, -40, 26, 68, -38, 22, + 68, -37, 18, 68, -35, 14, 68, -33, 10, 68, -32, 5, + 69, -29, 1, 69, -27, -3, 69, -25, -7, 69, -23, -11, + 70, -21, -15, 70, -19, -20, 70, -16, -24, 70, -14, -28, + 71, -12, -32, 71, -9, -36, 71, -6, -40, 72, -4, -44, + 68, -51, 68, 68, -51, 67, 68, -51, 66, 68, -51, 64, + 68, -50, 63, 68, -50, 60, 68, -49, 58, 68, -49, 54, + 68, -48, 51, 68, -47, 48, 68, -46, 44, 68, -45, 40, + 69, -44, 36, 69, -43, 32, 69, -42, 28, 69, -40, 24, + 69, -39, 19, 69, -37, 15, 69, -35, 11, 70, -33, 7, + 70, -31, 3, 70, -29, -2, 70, -27, -6, 70, -25, -10, + 71, -23, -14, 71, -21, -18, 71, -18, -22, 72, -16, -26, + 72, -14, -30, 72, -11, -34, 73, -9, -38, 73, -6, -42, + 69, -53, 69, 69, -53, 68, 69, -52, 67, 69, -52, 66, + 69, -52, 64, 69, -51, 61, 69, -51, 59, 69, -50, 56, + 69, -50, 53, 69, -49, 49, 70, -48, 46, 70, -47, 42, + 70, -46, 38, 70, -45, 34, 70, -43, 30, 70, -42, 26, + 70, -40, 21, 70, -39, 17, 71, -37, 13, 71, -35, 9, + 71, -33, 4, 71, -31, 0, 71, -29, -4, 72, -27, -8, + 72, -25, -12, 72, -23, -16, 72, -21, -20, 73, -18, -24, + 73, -16, -28, 73, -13, -33, 74, -11, -36, 74, -8, -40, + 70, -54, 70, 70, -54, 69, 70, -54, 68, 70, -54, 67, + 70, -53, 65, 70, -53, 63, 70, -53, 60, 71, -52, 57, + 71, -51, 54, 71, -50, 51, 71, -50, 47, 71, -49, 43, + 71, -47, 39, 71, -46, 35, 71, -45, 31, 71, -44, 27, + 71, -42, 23, 72, -41, 19, 72, -39, 15, 72, -37, 11, + 72, -35, 6, 72, -33, 2, 73, -31, -2, 73, -29, -6, + 73, -27, -10, 73, -25, -15, 74, -23, -19, 74, -20, -23, + 74, -18, -26, 74, -15, -31, 75, -13, -35, 75, -10, -38, + 72, -56, 71, 72, -56, 70, 72, -55, 69, 72, -55, 68, + 72, -55, 66, 72, -54, 64, 72, -54, 61, 72, -53, 58, + 72, -53, 55, 72, -52, 52, 72, -51, 48, 72, -50, 45, + 72, -49, 41, 72, -48, 37, 72, -47, 33, 73, -45, 29, + 73, -44, 24, 73, -42, 20, 73, -41, 16, 73, -39, 12, + 73, -37, 8, 74, -35, 4, 74, -33, 0, 74, -31, -4, + 74, -29, -8, 75, -27, -13, 75, -25, -17, 75, -22, -21, + 75, -20, -25, 76, -17, -29, 76, -15, -33, 76, -13, -37, + 73, -57, 72, 73, -57, 71, 73, -57, 70, 73, -57, 69, + 73, -56, 67, 73, -56, 65, 73, -56, 63, 73, -55, 59, + 73, -54, 56, 73, -54, 53, 73, -53, 50, 73, -52, 46, + 73, -51, 42, 74, -50, 38, 74, -48, 34, 74, -47, 30, + 74, -45, 26, 74, -44, 22, 74, -42, 18, 74, -41, 14, + 75, -39, 9, 75, -37, 5, 75, -35, 1, 75, -33, -3, + 75, -31, -7, 76, -29, -11, 76, -27, -15, 76, -24, -19, + 76, -22, -23, 77, -19, -27, 77, -17, -31, 77, -15, -35, + 74, -59, 73, 74, -58, 72, 74, -58, 71, 74, -58, 70, + 74, -58, 68, 74, -57, 66, 74, -57, 64, 74, -56, 61, + 74, -56, 58, 74, -55, 55, 74, -54, 51, 75, -53, 48, + 75, -52, 44, 75, -51, 40, 75, -50, 36, 75, -49, 32, + 75, -47, 28, 75, -46, 24, 75, -44, 20, 76, -43, 16, + 76, -41, 11, 76, -39, 7, 76, -37, 3, 76, -35, -1, + 77, -33, -5, 77, -31, -9, 77, -29, -13, 77, -26, -17, + 78, -24, -21, 78, -21, -26, 78, -19, -29, 78, -17, -33, + 75, -60, 74, 75, -60, 73, 75, -60, 72, 75, -60, 71, + 75, -59, 69, 75, -59, 67, 75, -58, 65, 75, -58, 62, + 76, -57, 59, 76, -57, 56, 76, -56, 53, 76, -55, 49, + 76, -54, 45, 76, -53, 41, 76, -52, 38, 76, -50, 34, + 76, -49, 29, 76, -47, 25, 77, -46, 21, 77, -44, 17, + 77, -42, 13, 77, -41, 9, 77, -39, 5, 78, -37, 1, + 78, -35, -3, 78, -33, -8, 78, -30, -12, 78, -28, -16, + 79, -26, -19, 79, -23, -24, 79, -21, -28, 80, -19, -31, + 77, -62, 75, 77, -62, 74, 77, -62, 74, 77, -61, 72, + 77, -61, 71, 77, -61, 69, 77, -60, 66, 77, -60, 64, + 77, -59, 61, 77, -58, 58, 77, -58, 54, 77, -57, 51, + 77, -56, 47, 77, -55, 43, 78, -54, 39, 78, -52, 36, + 78, -51, 31, 78, -49, 27, 78, -48, 23, 78, -46, 19, + 78, -45, 15, 79, -43, 11, 79, -41, 7, 79, -39, 3, + 79, -37, -1, 79, -35, -6, 80, -33, -9, 80, -31, -13, + 80, -28, -17, 80, -26, -22, 81, -24, -26, 81, -21, -29, + 78, -63, 76, 78, -63, 75, 78, -63, 75, 78, -63, 73, + 78, -62, 72, 78, -62, 70, 78, -62, 68, 78, -61, 65, + 78, -61, 62, 78, -60, 59, 78, -59, 56, 78, -58, 52, + 79, -57, 48, 79, -56, 45, 79, -55, 41, 79, -54, 37, + 79, -52, 33, 79, -51, 29, 79, -50, 25, 79, -48, 21, + 80, -46, 17, 80, -45, 13, 80, -43, 9, 80, -41, 5, + 80, -39, 1, 81, -37, -4, 81, -35, -8, 81, -33, -12, + 81, -30, -16, 82, -28, -20, 82, -26, -24, 82, -23, -28, + 79, -65, 77, 79, -64, 76, 79, -64, 76, 79, -64, 74, + 79, -64, 73, 79, -64, 71, 79, -63, 69, 79, -63, 66, + 80, -62, 63, 80, -61, 60, 80, -61, 57, 80, -60, 54, + 80, -59, 50, 80, -58, 46, 80, -57, 42, 80, -55, 39, + 80, -54, 34, 80, -53, 31, 80, -51, 27, 81, -50, 23, + 81, -48, 18, 81, -46, 14, 81, -45, 10, 81, -43, 6, + 82, -41, 2, 82, -39, -2, 82, -37, -6, 82, -34, -10, + 82, -32, -14, 83, -30, -18, 83, -28, -22, 83, -25, -26, + 81, -66, 78, 81, -66, 77, 81, -66, 77, 81, -65, 75, + 81, -65, 74, 81, -65, 72, 81, -65, 70, 81, -64, 67, + 81, -63, 64, 81, -63, 62, 81, -62, 58, 81, -61, 55, + 81, -60, 51, 81, -59, 48, 81, -58, 44, 81, -57, 40, + 81, -56, 36, 82, -54, 32, 82, -53, 28, 82, -51, 24, + 82, -50, 20, 82, -48, 16, 82, -46, 12, 83, -44, 8, + 83, -43, 4, 83, -40, 0, 83, -38, -4, 83, -36, -8, + 84, -34, -12, 84, -32, -16, 84, -30, -20, 84, -27, -24, + 82, -67, 79, 82, -67, 78, 82, -67, 78, 82, -67, 76, + 82, -67, 75, 82, -66, 73, 82, -66, 71, 82, -65, 68, + 82, -65, 66, 82, -64, 63, 82, -63, 60, 82, -63, 56, + 82, -62, 53, 82, -61, 49, 82, -60, 45, 83, -59, 42, + 83, -57, 38, 83, -56, 34, 83, -54, 30, 83, -53, 26, + 83, -51, 21, 83, -50, 18, 84, -48, 14, 84, -46, 10, + 84, -44, 6, 84, -42, 1, 84, -40, -3, 85, -38, -7, + 85, -36, -10, 85, -34, -15, 85, -31, -19, 86, -29, -22, + 83, -69, 80, 83, -69, 79, 83, -68, 79, 83, -68, 77, + 83, -68, 76, 83, -68, 74, 83, -67, 72, 83, -67, 70, + 83, -66, 67, 83, -66, 64, 83, -65, 61, 83, -64, 58, + 83, -63, 54, 84, -62, 51, 84, -61, 47, 84, -60, 43, + 84, -59, 39, 84, -57, 35, 84, -56, 31, 84, -55, 27, + 84, -53, 23, 85, -51, 19, 85, -50, 15, 85, -48, 11, + 85, -46, 7, 85, -44, 3, 85, -42, -1, 86, -40, -5, + 86, -38, -9, 86, -36, -13, 86, -33, -17, 87, -31, -21, + 84, -70, 81, 84, -70, 80, 84, -70, 80, 84, -70, 78, + 84, -69, 77, 84, -69, 75, 84, -69, 73, 84, -68, 71, + 84, -68, 68, 84, -67, 65, 84, -66, 62, 85, -66, 59, + 85, -65, 55, 85, -64, 52, 85, -63, 48, 85, -62, 45, + 85, -60, 41, 85, -59, 37, 85, -58, 33, 85, -56, 29, + 86, -54, 25, 86, -53, 21, 86, -51, 17, 86, -50, 13, + 86, -48, 9, 86, -46, 5, 87, -44, 1, 87, -42, -3, + 87, -40, -7, 87, -37, -11, 88, -35, -15, 88, -33, -19, + 85, -71, 82, 85, -71, 81, 85, -71, 81, 85, -71, 79, + 85, -71, 78, 85, -70, 76, 85, -70, 74, 86, -69, 72, + 86, -69, 69, 86, -68, 67, 86, -68, 64, 86, -67, 61, + 86, -66, 57, 86, -65, 53, 86, -64, 50, 86, -63, 46, + 86, -62, 42, 86, -60, 38, 86, -59, 35, 87, -58, 31, + 87, -56, 26, 87, -55, 22, 87, -53, 19, 87, -51, 15, + 87, -49, 11, 88, -47, 6, 88, -46, 2, 88, -44, -1, + 88, -42, -5, 88, -39, -10, 89, -37, -14, 89, -35, -17, + 87, -73, 83, 87, -72, 82, 87, -72, 82, 87, -72, 80, + 87, -72, 79, 87, -72, 77, 87, -71, 76, 87, -71, 73, + 87, -70, 71, 87, -70, 68, 87, -69, 65, 87, -68, 62, + 87, -67, 58, 87, -66, 55, 87, -65, 51, 87, -64, 48, + 87, -63, 44, 88, -62, 40, 88, -61, 36, 88, -59, 32, + 88, -58, 28, 88, -56, 24, 88, -55, 20, 88, -53, 16, + 89, -51, 12, 89, -49, 8, 89, -47, 4, 89, -45, 0, + 89, -43, -4, 90, -41, -8, 90, -39, -12, 90, -37, -16, + 88, -74, 84, 88, -74, 83, 88, -74, 82, 88, -73, 81, + 88, -73, 80, 88, -73, 79, 88, -73, 77, 88, -72, 74, + 88, -72, 72, 88, -71, 69, 88, -70, 66, 88, -70, 63, + 88, -69, 60, 88, -68, 56, 88, -67, 53, 88, -66, 49, + 89, -65, 45, 89, -63, 41, 89, -62, 38, 89, -61, 34, + 89, -59, 30, 89, -58, 26, 89, -56, 22, 90, -54, 18, + 90, -53, 14, 90, -51, 10, 90, -49, 6, 90, -47, 2, + 90, -45, -2, 91, -43, -6, 91, -41, -10, 91, -39, -14, + 89, -75, 85, 89, -75, 84, 89, -75, 83, 89, -75, 82, + 89, -74, 81, 89, -74, 80, 89, -74, 78, 89, -73, 75, + 89, -73, 73, 89, -72, 70, 89, -72, 68, 89, -71, 64, + 89, -70, 61, 90, -69, 58, 90, -68, 54, 90, -67, 51, + 90, -66, 47, 90, -65, 43, 90, -64, 39, 90, -62, 35, + 90, -61, 31, 90, -59, 27, 91, -58, 23, 91, -56, 20, + 91, -54, 16, 91, -52, 11, 91, -51, 7, 91, -49, 4, + 92, -47, 0, 92, -45, -5, 92, -43, -8, 92, -40, -12, + 21, 42, 31, 21, 42, 28, 21, 43, 25, 21, 43, 19, + 21, 43, 13, 21, 44, 8, 21, 45, 3, 22, 45, -3, + 22, 46, -8, 22, 47, -13, 23, 48, -18, 23, 49, -23, + 24, 51, -28, 24, 52, -32, 25, 53, -36, 25, 55, -41, + 26, 56, -45, 27, 58, -49, 27, 59, -53, 28, 61, -57, + 29, 63, -61, 30, 64, -64, 31, 66, -68, 31, 68, -71, + 32, 69, -75, 33, 71, -78, 34, 73, -82, 35, 75, -85, + 36, 76, -88, 37, 78, -91, 38, 80, -94, 38, 82, -97, + 21, 41, 32, 21, 42, 29, 21, 42, 25, 21, 42, 19, + 21, 42, 14, 21, 43, 8, 22, 44, 3, 22, 44, -3, + 22, 45, -8, 23, 46, -13, 23, 47, -17, 24, 48, -22, + 24, 50, -27, 25, 51, -32, 25, 52, -36, 26, 54, -40, + 26, 55, -45, 27, 57, -49, 28, 59, -52, 28, 60, -56, + 29, 62, -60, 30, 64, -64, 31, 65, -67, 32, 67, -71, + 32, 69, -74, 33, 71, -78, 34, 72, -81, 35, 74, -85, + 36, 76, -88, 37, 78, -91, 38, 80, -94, 39, 81, -97, + 21, 40, 32, 21, 41, 29, 21, 41, 25, 21, 41, 20, + 22, 42, 14, 22, 42, 9, 22, 43, 4, 22, 44, -2, + 23, 44, -7, 23, 45, -12, 23, 46, -17, 24, 48, -22, + 24, 49, -27, 25, 50, -31, 25, 52, -36, 26, 53, -40, + 27, 55, -44, 27, 56, -48, 28, 58, -52, 29, 59, -56, + 29, 61, -60, 30, 63, -64, 31, 65, -67, 32, 66, -71, + 33, 68, -74, 33, 70, -78, 34, 72, -81, 35, 74, -84, + 36, 75, -87, 37, 77, -91, 38, 79, -94, 39, 81, -97, + 22, 39, 32, 22, 40, 29, 22, 40, 26, 22, 40, 20, + 22, 41, 15, 22, 41, 9, 22, 42, 4, 23, 43, -2, + 23, 44, -7, 23, 44, -12, 24, 46, -17, 24, 47, -21, + 25, 48, -26, 25, 49, -31, 26, 51, -35, 26, 52, -39, + 27, 54, -44, 28, 56, -48, 28, 57, -52, 29, 59, -56, + 30, 61, -60, 30, 62, -63, 31, 64, -67, 32, 66, -70, + 33, 68, -74, 34, 70, -77, 34, 71, -81, 35, 73, -84, + 36, 75, -87, 37, 77, -91, 38, 79, -94, 39, 80, -97, + 22, 38, 33, 22, 39, 30, 22, 39, 26, 22, 39, 20, + 22, 40, 15, 23, 40, 10, 23, 41, 5, 23, 42, -1, + 23, 42, -6, 24, 43, -11, 24, 45, -16, 24, 46, -21, + 25, 47, -26, 25, 48, -30, 26, 50, -35, 27, 51, -39, + 27, 53, -43, 28, 55, -47, 28, 56, -51, 29, 58, -55, + 30, 60, -59, 31, 62, -63, 31, 63, -66, 32, 65, -70, + 33, 67, -73, 34, 69, -77, 35, 71, -80, 36, 72, -84, + 36, 74, -87, 37, 76, -90, 38, 78, -93, 39, 80, -96, + 22, 37, 33, 22, 37, 30, 23, 37, 27, 23, 38, 21, + 23, 38, 16, 23, 39, 11, 23, 39, 5, 23, 40, -1, + 24, 41, -6, 24, 42, -11, 24, 43, -15, 25, 45, -20, + 25, 46, -25, 26, 47, -30, 26, 49, -34, 27, 50, -38, + 28, 52, -43, 28, 54, -47, 29, 55, -51, 29, 57, -55, + 30, 59, -59, 31, 61, -62, 32, 62, -66, 32, 64, -69, + 33, 66, -73, 34, 68, -77, 35, 70, -80, 36, 72, -83, + 37, 73, -86, 38, 76, -90, 38, 77, -93, 39, 79, -96, + 23, 36, 34, 23, 36, 31, 23, 36, 27, 23, 36, 22, + 23, 37, 16, 23, 37, 11, 24, 38, 6, 24, 39, 0, + 24, 40, -5, 25, 41, -10, 25, 42, -15, 25, 43, -19, + 26, 45, -24, 26, 46, -29, 27, 48, -33, 27, 49, -37, + 28, 51, -42, 29, 52, -46, 29, 54, -50, 30, 56, -54, + 31, 58, -58, 31, 60, -62, 32, 61, -65, 33, 63, -69, + 34, 65, -72, 34, 67, -76, 35, 69, -79, 36, 71, -83, + 37, 73, -86, 38, 75, -89, 39, 77, -93, 40, 78, -96, + 24, 34, 35, 24, 34, 31, 24, 34, 28, 24, 35, 22, + 24, 35, 17, 24, 36, 12, 24, 36, 7, 25, 37, 1, + 25, 38, -4, 25, 39, -9, 25, 40, -14, 26, 42, -19, + 26, 43, -24, 27, 45, -28, 27, 46, -32, 28, 48, -37, + 28, 49, -41, 29, 51, -45, 30, 53, -49, 30, 55, -53, + 31, 57, -57, 32, 58, -61, 32, 60, -65, 33, 62, -68, + 34, 64, -72, 35, 66, -75, 36, 68, -79, 36, 70, -82, + 37, 72, -85, 38, 74, -89, 39, 76, -92, 40, 77, -95, + 24, 32, 35, 24, 32, 32, 24, 33, 28, 24, 33, 23, + 25, 33, 18, 25, 34, 13, 25, 35, 8, 25, 36, 2, + 25, 37, -3, 26, 38, -8, 26, 39, -13, 26, 40, -18, + 27, 42, -23, 27, 43, -27, 28, 45, -32, 28, 46, -36, + 29, 48, -41, 30, 50, -45, 30, 51, -49, 31, 53, -52, + 32, 55, -57, 32, 57, -60, 33, 59, -64, 34, 61, -68, + 34, 63, -71, 35, 65, -75, 36, 67, -78, 37, 69, -81, + 38, 71, -85, 39, 73, -88, 39, 75, -91, 40, 76, -95, + 25, 30, 36, 25, 30, 33, 25, 31, 29, 25, 31, 24, + 25, 31, 19, 25, 32, 14, 26, 33, 9, 26, 34, 3, + 26, 35, -2, 26, 36, -7, 27, 37, -12, 27, 38, -17, + 28, 40, -22, 28, 41, -26, 28, 43, -31, 29, 44, -35, + 30, 46, -40, 30, 48, -44, 31, 50, -48, 31, 52, -52, + 32, 54, -56, 33, 56, -60, 33, 57, -63, 34, 59, -67, + 35, 61, -70, 36, 63, -74, 36, 65, -77, 37, 67, -81, + 38, 69, -84, 39, 71, -88, 40, 73, -91, 41, 75, -94, + 26, 28, 37, 26, 28, 33, 26, 29, 30, 26, 29, 25, + 26, 29, 20, 26, 30, 15, 26, 31, 10, 27, 32, 4, + 27, 33, -1, 27, 34, -6, 27, 35, -11, 28, 36, -15, + 28, 38, -21, 29, 39, -25, 29, 41, -30, 30, 43, -34, + 30, 45, -39, 31, 46, -43, 31, 48, -47, 32, 50, -51, + 33, 52, -55, 33, 54, -59, 34, 56, -62, 35, 58, -66, + 35, 60, -69, 36, 62, -73, 37, 64, -77, 38, 66, -80, + 38, 68, -83, 39, 70, -87, 40, 72, -90, 41, 74, -93, + 27, 25, 38, 27, 26, 34, 27, 26, 31, 27, 26, 26, + 27, 27, 21, 27, 27, 16, 27, 28, 11, 28, 29, 5, + 28, 30, 0, 28, 31, -4, 28, 32, -9, 29, 34, -14, + 29, 35, -19, 30, 37, -24, 30, 38, -28, 30, 40, -32, + 31, 42, -37, 32, 44, -41, 32, 46, -45, 33, 48, -49, + 33, 50, -54, 34, 52, -57, 35, 54, -61, 35, 56, -65, + 36, 58, -68, 37, 60, -72, 38, 62, -76, 38, 64, -79, + 39, 66, -82, 40, 68, -86, 41, 70, -89, 42, 72, -92, + 28, 23, 38, 28, 23, 35, 28, 24, 32, 28, 24, 27, + 28, 24, 22, 28, 25, 17, 28, 26, 12, 28, 27, 7, + 29, 28, 2, 29, 29, -3, 29, 30, -8, 30, 32, -13, + 30, 33, -18, 30, 35, -22, 31, 36, -27, 31, 38, -31, + 32, 40, -36, 32, 42, -40, 33, 44, -44, 33, 46, -48, + 34, 48, -53, 35, 50, -56, 35, 52, -60, 36, 54, -64, + 37, 56, -67, 37, 58, -71, 38, 60, -75, 39, 62, -78, + 40, 65, -81, 40, 67, -85, 41, 69, -88, 42, 71, -91, + 29, 21, 39, 29, 21, 36, 29, 21, 32, 29, 22, 28, + 29, 22, 23, 29, 23, 18, 29, 24, 13, 29, 25, 8, + 30, 26, 3, 30, 27, -2, 30, 28, -7, 30, 29, -11, + 31, 31, -17, 31, 33, -21, 32, 34, -26, 32, 36, -30, + 33, 38, -35, 33, 40, -39, 34, 42, -43, 34, 44, -47, + 35, 46, -51, 35, 48, -55, 36, 50, -59, 37, 52, -63, + 37, 54, -66, 38, 57, -70, 39, 59, -74, 39, 61, -77, + 40, 63, -80, 41, 65, -84, 42, 67, -87, 43, 69, -91, + 30, 18, 40, 30, 19, 37, 30, 19, 33, 30, 19, 29, + 30, 20, 24, 30, 20, 20, 30, 21, 15, 30, 22, 9, + 31, 23, 4, 31, 24, -1, 31, 26, -5, 31, 27, -10, + 32, 29, -15, 32, 30, -20, 32, 32, -24, 33, 34, -29, + 33, 36, -33, 34, 38, -38, 34, 40, -42, 35, 42, -46, + 36, 44, -50, 36, 46, -54, 37, 48, -58, 37, 50, -61, + 38, 52, -65, 39, 55, -69, 39, 57, -73, 40, 59, -76, + 41, 61, -79, 42, 64, -83, 42, 66, -86, 43, 68, -90, + 31, 16, 41, 31, 16, 37, 31, 16, 34, 31, 17, 30, + 31, 17, 26, 31, 18, 21, 31, 19, 16, 31, 20, 11, + 31, 21, 6, 32, 22, 1, 32, 23, -4, 32, 25, -9, + 33, 26, -14, 33, 28, -18, 33, 30, -23, 34, 31, -27, + 34, 34, -32, 35, 35, -36, 35, 37, -40, 36, 39, -44, + 36, 42, -49, 37, 44, -53, 37, 46, -57, 38, 48, -60, + 39, 50, -64, 39, 53, -68, 40, 55, -71, 41, 57, -75, + 41, 59, -78, 42, 62, -82, 43, 64, -85, 44, 66, -89, + 32, 14, 41, 32, 14, 38, 32, 14, 35, 32, 14, 31, + 32, 15, 27, 32, 16, 22, 32, 16, 17, 32, 17, 12, + 32, 18, 7, 33, 20, 2, 33, 21, -3, 33, 22, -7, + 34, 24, -12, 34, 26, -17, 34, 27, -21, 35, 29, -26, + 35, 31, -31, 36, 33, -35, 36, 35, -39, 37, 37, -43, + 37, 40, -48, 38, 42, -51, 38, 44, -55, 39, 46, -59, + 39, 48, -63, 40, 51, -67, 41, 53, -70, 42, 55, -74, + 42, 57, -77, 43, 60, -81, 44, 62, -84, 44, 64, -88, + 33, 11, 42, 33, 11, 39, 33, 12, 36, 33, 12, 32, + 33, 13, 28, 33, 13, 23, 33, 14, 19, 33, 15, 13, + 34, 16, 9, 34, 17, 4, 34, 18, -1, 34, 20, -6, + 35, 22, -11, 35, 23, -15, 35, 25, -20, 36, 27, -24, + 36, 29, -29, 37, 31, -33, 37, 33, -38, 37, 35, -42, + 38, 37, -46, 39, 39, -50, 39, 42, -54, 40, 44, -58, + 40, 46, -61, 41, 49, -65, 42, 51, -69, 42, 53, -73, + 43, 55, -76, 44, 58, -80, 44, 60, -83, 45, 62, -86, + 34, 9, 43, 34, 9, 40, 34, 9, 37, 34, 10, 33, + 34, 10, 29, 34, 11, 25, 34, 11, 20, 34, 12, 15, + 35, 13, 10, 35, 15, 5, 35, 16, 1, 35, 17, -4, + 36, 19, -9, 36, 21, -14, 36, 22, -18, 37, 24, -23, + 37, 26, -28, 37, 28, -32, 38, 30, -36, 38, 33, -40, + 39, 35, -45, 39, 37, -49, 40, 39, -52, 41, 42, -56, + 41, 44, -60, 42, 46, -64, 42, 49, -68, 43, 51, -71, + 44, 53, -75, 44, 56, -79, 45, 58, -82, 46, 60, -85, + 35, 6, 44, 35, 6, 41, 35, 7, 38, 35, 7, 34, + 35, 8, 30, 35, 8, 26, 35, 9, 22, 35, 10, 16, + 36, 11, 12, 36, 12, 7, 36, 13, 2, 36, 15, -3, + 37, 17, -8, 37, 18, -12, 37, 20, -17, 38, 22, -21, + 38, 24, -26, 38, 26, -30, 39, 28, -35, 39, 30, -39, + 40, 33, -43, 40, 35, -47, 41, 37, -51, 41, 39, -55, + 42, 42, -59, 43, 44, -63, 43, 46, -66, 44, 49, -70, + 44, 51, -73, 45, 54, -77, 46, 56, -81, 47, 58, -84, + 36, 4, 44, 36, 4, 42, 36, 4, 39, 36, 5, 36, + 36, 5, 32, 36, 6, 27, 36, 7, 23, 37, 8, 18, + 37, 9, 13, 37, 10, 8, 37, 11, 4, 37, 12, -1, + 38, 14, -6, 38, 16, -11, 38, 18, -15, 39, 19, -20, + 39, 22, -25, 39, 24, -29, 40, 26, -33, 40, 28, -37, + 41, 30, -42, 41, 32, -46, 42, 35, -50, 42, 37, -53, + 43, 39, -57, 43, 42, -61, 44, 44, -65, 45, 47, -69, + 45, 49, -72, 46, 51, -76, 47, 54, -80, 47, 56, -83, + 37, 1, 45, 37, 2, 43, 37, 2, 40, 37, 2, 37, + 37, 3, 33, 37, 3, 29, 38, 4, 24, 38, 5, 19, + 38, 6, 15, 38, 7, 10, 38, 9, 5, 38, 10, 1, + 39, 12, -5, 39, 13, -9, 39, 15, -14, 40, 17, -18, + 40, 19, -23, 40, 21, -27, 41, 23, -31, 41, 25, -36, + 42, 28, -40, 42, 30, -44, 43, 32, -48, 43, 35, -52, + 44, 37, -56, 44, 40, -60, 45, 42, -64, 45, 44, -67, + 46, 47, -71, 47, 49, -75, 47, 52, -78, 48, 54, -82, + 38, -1, 46, 38, -1, 44, 38, -1, 41, 38, 0, 38, + 39, 0, 34, 39, 1, 30, 39, 2, 26, 39, 3, 21, + 39, 4, 16, 39, 5, 11, 39, 6, 7, 40, 8, 2, + 40, 9, -3, 40, 11, -8, 40, 13, -12, 41, 14, -16, + 41, 17, -21, 41, 19, -26, 42, 21, -30, 42, 23, -34, + 43, 25, -39, 43, 28, -43, 44, 30, -47, 44, 32, -50, + 45, 34, -54, 45, 37, -58, 46, 40, -62, 46, 42, -66, + 47, 44, -69, 48, 47, -73, 48, 49, -77, 49, 52, -80, + 40, -3, 47, 40, -3, 45, 40, -3, 42, 40, -3, 39, + 40, -2, 35, 40, -1, 31, 40, -1, 27, 40, 0, 22, + 40, 1, 18, 40, 2, 13, 41, 4, 8, 41, 5, 4, + 41, 7, -1, 41, 8, -6, 42, 10, -10, 42, 12, -15, + 42, 14, -20, 43, 16, -24, 43, 18, -28, 43, 20, -32, + 44, 23, -37, 44, 25, -41, 45, 27, -45, 45, 30, -49, + 46, 32, -53, 46, 35, -57, 47, 37, -61, 47, 40, -64, + 48, 42, -68, 48, 45, -72, 49, 47, -75, 50, 50, -79, + 41, -6, 48, 41, -5, 46, 41, -5, 43, 41, -5, 40, + 41, -4, 37, 41, -4, 33, 41, -3, 29, 41, -2, 24, + 41, -1, 19, 41, 0, 15, 42, 1, 10, 42, 3, 5, + 42, 4, 0, 42, 6, -4, 43, 8, -9, 43, 10, -13, + 43, 12, -18, 44, 14, -22, 44, 16, -27, 44, 18, -31, + 45, 20, -35, 45, 23, -39, 46, 25, -43, 46, 27, -47, + 47, 30, -51, 47, 32, -55, 48, 35, -59, 48, 37, -63, + 49, 40, -66, 49, 42, -71, 50, 45, -74, 51, 47, -78, + 42, -8, 49, 42, -8, 47, 42, -8, 44, 42, -7, 41, + 42, -7, 38, 42, -6, 34, 42, -5, 30, 42, -4, 25, + 43, -3, 21, 43, -2, 16, 43, -1, 12, 43, 0, 7, + 43, 2, 2, 43, 4, -3, 44, 5, -7, 44, 7, -11, + 44, 9, -16, 45, 11, -21, 45, 13, -25, 45, 15, -29, + 46, 18, -34, 46, 20, -38, 47, 23, -42, 47, 25, -46, + 48, 27, -50, 48, 30, -54, 49, 32, -58, 49, 35, -61, + 50, 37, -65, 50, 40, -69, 51, 43, -73, 51, 45, -76, + 43, -10, 49, 43, -10, 47, 43, -10, 45, 43, -9, 42, + 43, -9, 39, 43, -8, 35, 43, -8, 31, 44, -7, 27, + 44, -6, 22, 44, -5, 18, 44, -3, 13, 44, -2, 9, + 44, 0, 4, 45, 1, -1, 45, 3, -5, 45, 5, -10, + 45, 7, -15, 46, 9, -19, 46, 11, -23, 46, 13, -27, + 47, 16, -32, 47, 18, -36, 48, 20, -40, 48, 22, -44, + 49, 25, -48, 49, 28, -52, 50, 30, -56, 50, 32, -60, + 51, 35, -63, 51, 38, -68, 52, 40, -71, 52, 43, -75, + 44, -12, 50, 44, -12, 48, 44, -12, 47, 44, -12, 44, + 44, -11, 40, 45, -11, 37, 45, -10, 33, 45, -9, 28, + 45, -8, 24, 45, -7, 19, 45, -6, 15, 45, -4, 10, + 46, -3, 5, 46, -1, 1, 46, 1, -4, 46, 2, -8, + 47, 4, -13, 47, 6, -17, 47, 8, -21, 48, 11, -26, + 48, 13, -30, 48, 15, -34, 49, 18, -38, 49, 20, -42, + 50, 22, -46, 50, 25, -51, 51, 28, -54, 51, 30, -58, + 52, 32, -62, 52, 35, -66, 53, 38, -70, 53, 40, -73, + 46, -15, 51, 46, -14, 49, 46, -14, 48, 46, -14, 45, + 46, -13, 42, 46, -13, 38, 46, -12, 34, 46, -11, 30, + 46, -10, 25, 46, -9, 21, 46, -8, 16, 47, -7, 12, + 47, -5, 7, 47, -4, 3, 47, -2, -2, 47, 0, -6, + 48, 2, -11, 48, 4, -16, 48, 6, -20, 49, 8, -24, + 49, 11, -29, 49, 13, -33, 50, 15, -37, 50, 18, -41, + 51, 20, -45, 51, 23, -49, 52, 25, -53, 52, 28, -57, + 53, 30, -60, 53, 33, -64, 54, 35, -68, 54, 38, -72, + 47, -17, 52, 47, -17, 50, 47, -16, 49, 47, -16, 46, + 47, -16, 43, 47, -15, 39, 47, -14, 36, 47, -13, 31, + 47, -13, 27, 47, -11, 23, 48, -10, 18, 48, -9, 14, + 48, -7, 9, 48, -6, 4, 48, -4, 0, 49, -2, -5, + 49, 0, -9, 49, 2, -14, 49, 4, -18, 50, 6, -22, + 50, 8, -27, 51, 10, -31, 51, 13, -35, 51, 15, -39, + 52, 18, -43, 52, 20, -47, 53, 23, -51, 53, 25, -55, + 54, 28, -59, 54, 31, -63, 55, 33, -67, 55, 36, -70, + 48, -19, 53, 48, -19, 51, 48, -19, 50, 48, -18, 47, + 48, -18, 44, 48, -17, 41, 48, -17, 37, 48, -16, 33, + 49, -15, 28, 49, -14, 24, 49, -12, 20, 49, -11, 15, + 49, -10, 10, 49, -8, 6, 50, -6, 2, 50, -5, -3, + 50, -3, -8, 50, -1, -12, 51, 1, -16, 51, 3, -21, + 51, 6, -25, 52, 8, -29, 52, 10, -33, 52, 13, -37, + 53, 15, -41, 53, 18, -46, 54, 20, -50, 54, 23, -53, + 55, 25, -57, 55, 28, -61, 56, 31, -65, 56, 33, -69, + 49, -21, 54, 49, -21, 52, 49, -21, 51, 49, -20, 48, + 49, -20, 45, 49, -19, 42, 50, -19, 38, 50, -18, 34, + 50, -17, 30, 50, -16, 26, 50, -15, 21, 50, -13, 17, + 50, -12, 12, 51, -10, 8, 51, -9, 3, 51, -7, -1, + 51, -5, -6, 51, -3, -10, 52, -1, -15, 52, 1, -19, + 52, 3, -23, 53, 6, -28, 53, 8, -32, 53, 10, -36, + 54, 13, -40, 54, 15, -44, 55, 18, -48, 55, 20, -52, + 56, 23, -55, 56, 26, -60, 57, 28, -63, 57, 31, -67, + 51, -24, 55, 51, -23, 54, 51, -23, 52, 51, -23, 50, + 51, -22, 47, 51, -22, 44, 51, -21, 40, 51, -20, 36, + 51, -19, 32, 51, -18, 28, 51, -17, 23, 52, -16, 19, + 52, -15, 14, 52, -13, 10, 52, -12, 5, 52, -10, 1, + 53, -8, -4, 53, -6, -8, 53, -4, -12, 53, -2, -17, + 54, 1, -21, 54, 3, -25, 54, 5, -29, 55, 7, -34, + 55, 10, -37, 56, 12, -42, 56, 15, -46, 56, 17, -50, + 57, 20, -53, 57, 23, -58, 58, 25, -61, 58, 28, -65, + 52, -26, 56, 52, -25, 55, 52, -25, 53, 52, -25, 51, + 52, -24, 48, 52, -24, 45, 52, -23, 41, 52, -22, 37, + 52, -22, 33, 53, -21, 29, 53, -19, 25, 53, -18, 21, + 53, -17, 16, 53, -15, 11, 53, -14, 7, 54, -12, 3, + 54, -10, -2, 54, -8, -6, 54, -6, -11, 55, -4, -15, + 55, -2, -20, 55, 0, -24, 56, 3, -28, 56, 5, -32, + 56, 7, -36, 57, 10, -40, 57, 12, -44, 58, 15, -48, + 58, 17, -52, 58, 20, -56, 59, 23, -60, 59, 25, -63, + 53, -28, 57, 53, -27, 56, 53, -27, 54, 53, -27, 52, + 53, -26, 49, 53, -26, 46, 54, -25, 43, 54, -24, 39, + 54, -24, 35, 54, -23, 31, 54, -22, 26, 54, -20, 22, + 54, -19, 17, 54, -17, 13, 55, -16, 9, 55, -14, 4, + 55, -12, 0, 55, -10, -5, 56, -8, -9, 56, -6, -13, + 56, -4, -18, 56, -2, -22, 57, 0, -26, 57, 3, -30, + 57, 5, -34, 58, 8, -38, 58, 10, -42, 59, 13, -46, + 59, 15, -50, 59, 18, -54, 60, 20, -58, 60, 23, -62, + 55, -29, 58, 55, -29, 57, 55, -29, 55, 55, -29, 53, + 55, -28, 50, 55, -28, 47, 55, -27, 44, 55, -26, 40, + 55, -26, 36, 55, -25, 32, 55, -24, 28, 55, -22, 24, + 55, -21, 19, 56, -20, 15, 56, -18, 10, 56, -16, 6, + 56, -14, 1, 56, -13, -3, 57, -11, -7, 57, -9, -11, + 57, -6, -16, 58, -4, -20, 58, -2, -24, 58, 0, -28, + 59, 3, -32, 59, 5, -37, 59, 8, -41, 60, 10, -45, + 60, 13, -48, 61, 16, -53, 61, 18, -56, 61, 21, -60, + 56, -31, 59, 56, -31, 58, 56, -31, 56, 56, -31, 54, + 56, -30, 52, 56, -30, 49, 56, -29, 45, 56, -28, 41, + 56, -28, 38, 56, -27, 34, 56, -26, 30, 57, -24, 25, + 57, -23, 21, 57, -22, 16, 57, -20, 12, 57, -18, 8, + 57, -17, 3, 58, -15, -1, 58, -13, -5, 58, -11, -10, + 58, -9, -14, 59, -6, -18, 59, -4, -23, 59, -2, -27, + 60, 0, -31, 60, 3, -35, 60, 5, -39, 61, 8, -43, + 61, 10, -47, 62, 13, -51, 62, 16, -55, 62, 18, -58, + 57, -33, 60, 57, -33, 59, 57, -33, 57, 57, -33, 55, + 57, -32, 53, 57, -32, 50, 57, -31, 47, 57, -30, 43, + 57, -29, 39, 57, -29, 35, 58, -28, 31, 58, -26, 27, + 58, -25, 22, 58, -24, 18, 58, -22, 14, 58, -21, 10, + 59, -19, 5, 59, -17, 1, 59, -15, -4, 59, -13, -8, + 60, -11, -13, 60, -9, -17, 60, -6, -21, 60, -4, -25, + 61, -2, -29, 61, 1, -33, 61, 3, -37, 62, 6, -41, + 62, 8, -45, 63, 11, -49, 63, 13, -53, 63, 16, -57, + 58, -35, 61, 58, -35, 60, 58, -35, 58, 58, -34, 56, + 58, -34, 54, 58, -34, 51, 58, -33, 48, 59, -32, 44, + 59, -31, 41, 59, -30, 37, 59, -29, 33, 59, -28, 29, + 59, -27, 24, 59, -26, 20, 59, -24, 16, 60, -23, 11, + 60, -21, 7, 60, -19, 2, 60, -17, -2, 60, -15, -6, + 61, -13, -11, 61, -11, -15, 61, -9, -19, 62, -6, -23, + 62, -4, -27, 62, -2, -32, 63, 1, -36, 63, 3, -39, + 63, 6, -43, 64, 8, -48, 64, 11, -51, 65, 14, -55, + 60, -37, 62, 60, -37, 61, 60, -36, 59, 60, -36, 57, + 60, -36, 55, 60, -35, 52, 60, -35, 49, 60, -34, 46, + 60, -33, 42, 60, -32, 38, 60, -31, 34, 60, -30, 30, + 60, -29, 26, 60, -28, 21, 61, -26, 17, 61, -25, 13, + 61, -23, 8, 61, -21, 4, 61, -19, 0, 62, -17, -4, + 62, -15, -9, 62, -13, -13, 62, -11, -17, 63, -9, -21, + 63, -6, -25, 63, -4, -30, 64, -1, -34, 64, 1, -38, + 64, 3, -42, 65, 6, -46, 65, 9, -50, 66, 11, -53, + 61, -39, 63, 61, -38, 62, 61, -38, 60, 61, -38, 58, + 61, -38, 56, 61, -37, 54, 61, -37, 51, 61, -36, 47, + 61, -35, 43, 61, -34, 40, 61, -33, 36, 61, -32, 32, + 62, -31, 27, 62, -30, 23, 62, -28, 19, 62, -27, 15, + 62, -25, 10, 62, -23, 6, 63, -21, 2, 63, -19, -3, + 63, -17, -7, 63, -15, -11, 64, -13, -16, 64, -11, -20, + 64, -9, -24, 65, -6, -28, 65, -4, -32, 65, -1, -36, + 65, 1, -40, 66, 4, -44, 66, 6, -48, 67, 9, -52, + 62, -40, 64, 62, -40, 63, 62, -40, 61, 62, -40, 60, + 62, -39, 57, 62, -39, 55, 62, -38, 52, 62, -38, 48, + 62, -37, 45, 62, -36, 41, 63, -35, 37, 63, -34, 33, + 63, -33, 29, 63, -31, 25, 63, -30, 20, 63, -29, 16, + 63, -27, 12, 64, -25, 7, 64, -23, 3, 64, -21, -1, + 64, -19, -6, 64, -17, -10, 65, -15, -14, 65, -13, -18, + 65, -11, -22, 66, -8, -26, 66, -6, -30, 66, -4, -34, + 67, -1, -38, 67, 2, -42, 67, 4, -46, 68, 7, -50, + 63, -42, 65, 63, -42, 64, 63, -42, 62, 63, -41, 61, + 63, -41, 59, 63, -41, 56, 63, -40, 53, 63, -39, 50, + 64, -39, 46, 64, -38, 43, 64, -37, 39, 64, -36, 35, + 64, -35, 30, 64, -33, 26, 64, -32, 22, 64, -30, 18, + 65, -29, 13, 65, -27, 9, 65, -25, 5, 65, -24, 1, + 65, -21, -4, 66, -19, -8, 66, -17, -12, 66, -15, -16, + 66, -13, -20, 67, -10, -25, 67, -8, -29, 67, -6, -32, + 68, -3, -36, 68, -1, -41, 68, 2, -44, 69, 4, -48, + 64, -44, 66, 64, -44, 65, 65, -43, 63, 65, -43, 62, + 65, -43, 60, 65, -42, 57, 65, -42, 54, 65, -41, 51, + 65, -40, 48, 65, -40, 44, 65, -39, 40, 65, -38, 36, + 65, -36, 32, 65, -35, 28, 65, -34, 24, 66, -32, 20, + 66, -31, 15, 66, -29, 11, 66, -27, 7, 66, -26, 3, + 67, -23, -2, 67, -21, -6, 67, -19, -10, 67, -17, -14, + 68, -15, -18, 68, -13, -23, 68, -10, -27, 69, -8, -31, + 69, -6, -35, 69, -3, -39, 70, 0, -43, 70, 2, -47, + 66, -45, 67, 66, -45, 66, 66, -45, 64, 66, -45, 63, + 66, -44, 61, 66, -44, 58, 66, -44, 56, 66, -43, 52, + 66, -42, 49, 66, -41, 45, 66, -40, 42, 66, -39, 38, + 66, -38, 33, 67, -37, 29, 67, -36, 25, 67, -34, 21, + 67, -33, 17, 67, -31, 13, 67, -29, 8, 68, -28, 4, + 68, -25, 0, 68, -23, -4, 68, -21, -9, 68, -19, -13, + 69, -17, -17, 69, -15, -21, 69, -12, -25, 70, -10, -29, + 70, -8, -33, 70, -5, -37, 71, -3, -41, 71, 0, -45, + 67, -47, 68, 67, -47, 67, 67, -47, 66, 67, -46, 64, + 67, -46, 62, 67, -46, 60, 67, -45, 57, 67, -45, 54, + 67, -44, 50, 67, -43, 47, 67, -42, 43, 68, -41, 39, + 68, -40, 35, 68, -39, 31, 68, -37, 27, 68, -36, 23, + 68, -34, 18, 68, -33, 14, 69, -31, 10, 69, -29, 6, + 69, -27, 1, 69, -25, -3, 69, -23, -7, 70, -21, -11, + 70, -19, -15, 70, -17, -19, 70, -15, -23, 71, -12, -27, + 71, -10, -31, 71, -7, -35, 72, -5, -39, 72, -2, -43, + 68, -49, 68, 68, -48, 68, 68, -48, 67, 68, -48, 65, + 68, -48, 63, 68, -47, 61, 68, -47, 58, 68, -46, 55, + 68, -46, 52, 69, -45, 48, 69, -44, 45, 69, -43, 41, + 69, -42, 37, 69, -41, 33, 69, -39, 29, 69, -38, 25, + 69, -36, 20, 70, -35, 16, 70, -33, 12, 70, -31, 8, + 70, -29, 3, 70, -27, -1, 71, -25, -5, 71, -23, -9, + 71, -21, -13, 71, -19, -18, 72, -17, -22, 72, -14, -26, + 72, -12, -29, 73, -9, -34, 73, -7, -38, 73, -5, -41, + 69, -50, 69, 69, -50, 69, 69, -50, 68, 69, -50, 66, + 70, -49, 64, 70, -49, 62, 70, -48, 59, 70, -48, 56, + 70, -47, 53, 70, -46, 50, 70, -46, 46, 70, -45, 42, + 70, -43, 38, 70, -42, 34, 70, -41, 30, 70, -40, 26, + 71, -38, 22, 71, -37, 18, 71, -35, 13, 71, -33, 9, + 71, -31, 5, 72, -29, 1, 72, -27, -3, 72, -25, -7, + 72, -23, -11, 73, -21, -16, 73, -19, -20, 73, -17, -24, + 73, -14, -28, 74, -12, -32, 74, -9, -36, 74, -7, -40, + 71, -52, 70, 71, -52, 70, 71, -51, 69, 71, -51, 67, + 71, -51, 65, 71, -51, 63, 71, -50, 61, 71, -49, 57, + 71, -49, 54, 71, -48, 51, 71, -47, 47, 71, -46, 44, + 71, -45, 40, 71, -44, 36, 72, -43, 32, 72, -41, 28, + 72, -40, 23, 72, -38, 19, 72, -37, 15, 72, -35, 11, + 73, -33, 6, 73, -31, 2, 73, -29, -2, 73, -27, -6, + 73, -25, -10, 74, -23, -14, 74, -21, -18, 74, -19, -22, + 74, -16, -26, 75, -14, -30, 75, -11, -34, 75, -9, -38, + 72, -53, 71, 72, -53, 71, 72, -53, 70, 72, -53, 68, + 72, -52, 66, 72, -52, 64, 72, -52, 62, 72, -51, 59, + 72, -50, 56, 72, -50, 52, 72, -49, 49, 72, -48, 45, + 73, -47, 41, 73, -46, 37, 73, -44, 33, 73, -43, 29, + 73, -42, 25, 73, -40, 21, 73, -39, 17, 73, -37, 13, + 74, -35, 8, 74, -33, 4, 74, -31, 0, 74, -29, -4, + 75, -27, -8, 75, -25, -12, 75, -23, -16, 75, -21, -20, + 76, -18, -24, 76, -16, -29, 76, -13, -32, 77, -11, -36, + 73, -55, 72, 73, -55, 72, 73, -54, 71, 73, -54, 69, + 73, -54, 67, 73, -54, 65, 73, -53, 63, 73, -53, 60, + 73, -52, 57, 73, -51, 54, 74, -50, 50, 74, -50, 47, + 74, -48, 43, 74, -47, 39, 74, -46, 35, 74, -45, 31, + 74, -43, 26, 74, -42, 22, 75, -40, 18, 75, -39, 14, + 75, -37, 10, 75, -35, 6, 75, -33, 2, 75, -31, -2, + 76, -29, -6, 76, -27, -11, 76, -25, -15, 76, -23, -19, + 77, -20, -22, 77, -18, -27, 77, -16, -31, 78, -13, -35, + 74, -56, 73, 74, -56, 72, 74, -56, 72, 74, -56, 70, + 74, -55, 69, 74, -55, 66, 75, -55, 64, 75, -54, 61, + 75, -54, 58, 75, -53, 55, 75, -52, 52, 75, -51, 48, + 75, -50, 44, 75, -49, 40, 75, -48, 36, 75, -47, 33, + 75, -45, 28, 76, -44, 24, 76, -42, 20, 76, -41, 16, + 76, -39, 12, 76, -37, 8, 76, -35, 3, 77, -33, -1, + 77, -31, -5, 77, -29, -9, 77, -27, -13, 78, -25, -17, + 78, -22, -21, 78, -20, -25, 78, -18, -29, 79, -15, -33, + 76, -58, 74, 76, -58, 73, 76, -57, 73, 76, -57, 71, + 76, -57, 70, 76, -57, 68, 76, -56, 65, 76, -56, 62, + 76, -55, 59, 76, -54, 56, 76, -54, 53, 76, -53, 50, + 76, -52, 46, 76, -51, 42, 76, -49, 38, 76, -48, 34, + 77, -47, 30, 77, -45, 26, 77, -44, 22, 77, -42, 18, + 77, -40, 13, 77, -39, 9, 78, -37, 5, 78, -35, 1, + 78, -33, -3, 78, -31, -7, 79, -29, -11, 79, -27, -15, + 79, -24, -19, 79, -22, -23, 80, -20, -27, 80, -17, -31, + 77, -60, 75, 77, -59, 75, 77, -59, 74, 77, -59, 73, + 77, -59, 71, 77, -58, 69, 77, -58, 67, 77, -58, 64, + 77, -57, 61, 77, -56, 58, 77, -56, 55, 78, -55, 51, + 78, -54, 47, 78, -53, 44, 78, -52, 40, 78, -50, 36, + 78, -49, 32, 78, -47, 28, 78, -46, 24, 79, -45, 20, + 79, -43, 15, 79, -41, 11, 79, -39, 7, 79, -37, 3, + 79, -35, -1, 80, -33, -5, 80, -31, -9, 80, -29, -13, + 80, -27, -17, 81, -24, -21, 81, -22, -25, 81, -20, -29, + 78, -61, 76, 78, -61, 76, 78, -61, 75, 78, -61, 74, + 78, -60, 72, 78, -60, 70, 78, -60, 68, 79, -59, 65, + 79, -58, 62, 79, -58, 59, 79, -57, 56, 79, -56, 53, + 79, -55, 49, 79, -54, 45, 79, -53, 41, 79, -52, 38, + 79, -51, 33, 79, -49, 29, 80, -48, 25, 80, -46, 21, + 80, -44, 17, 80, -43, 13, 80, -41, 9, 80, -39, 5, + 81, -37, 1, 81, -35, -3, 81, -33, -7, 81, -31, -11, + 82, -29, -15, 82, -26, -20, 82, -24, -23, 82, -22, -27, + 80, -62, 77, 80, -62, 77, 80, -62, 76, 80, -62, 75, + 80, -62, 73, 80, -61, 71, 80, -61, 69, 80, -60, 66, + 80, -60, 64, 80, -59, 61, 80, -59, 57, 80, -58, 54, + 80, -57, 50, 80, -56, 47, 80, -55, 43, 80, -54, 39, + 80, -52, 35, 81, -51, 31, 81, -49, 27, 81, -48, 23, + 81, -46, 19, 81, -44, 15, 81, -43, 11, 82, -41, 7, + 82, -39, 3, 82, -37, -2, 82, -35, -6, 82, -33, -10, + 83, -31, -13, 83, -28, -18, 83, -26, -22, 84, -24, -25, + 81, -64, 78, 81, -64, 78, 81, -64, 77, 81, -63, 76, + 81, -63, 74, 81, -63, 72, 81, -62, 70, 81, -62, 68, + 81, -61, 65, 81, -61, 62, 81, -60, 59, 81, -59, 55, + 81, -58, 52, 81, -57, 48, 81, -56, 44, 82, -55, 41, + 82, -54, 36, 82, -52, 32, 82, -51, 29, 82, -50, 25, + 82, -48, 20, 82, -46, 16, 83, -45, 12, 83, -43, 8, + 83, -41, 4, 83, -39, 0, 83, -37, -4, 84, -35, -8, + 84, -33, -12, 84, -30, -16, 84, -28, -20, 85, -26, -24, + 82, -65, 79, 82, -65, 79, 82, -65, 78, 82, -65, 77, + 82, -65, 75, 82, -64, 73, 82, -64, 71, 82, -63, 69, + 82, -63, 66, 82, -62, 63, 82, -61, 60, 82, -61, 57, + 82, -60, 53, 83, -59, 49, 83, -58, 46, 83, -57, 42, + 83, -55, 38, 83, -54, 34, 83, -53, 30, 83, -51, 26, + 83, -50, 22, 84, -48, 18, 84, -46, 14, 84, -45, 10, + 84, -43, 6, 84, -41, 2, 85, -39, -2, 85, -37, -6, + 85, -35, -10, 85, -32, -14, 86, -30, -18, 86, -28, -22, + 83, -67, 80, 83, -66, 80, 83, -66, 79, 83, -66, 78, + 83, -66, 76, 83, -66, 75, 83, -65, 72, 83, -65, 70, + 83, -64, 67, 83, -64, 64, 84, -63, 61, 84, -62, 58, + 84, -61, 54, 84, -60, 51, 84, -59, 47, 84, -58, 44, + 84, -57, 39, 84, -56, 36, 84, -54, 32, 84, -53, 28, + 85, -51, 23, 85, -50, 20, 85, -48, 16, 85, -46, 12, + 85, -44, 8, 86, -42, 3, 86, -40, -1, 86, -38, -4, + 86, -36, -8, 86, -34, -13, 87, -32, -17, 87, -30, -20, + 84, -68, 81, 84, -68, 81, 84, -68, 80, 84, -68, 79, + 84, -67, 77, 84, -67, 76, 85, -67, 74, 85, -66, 71, + 85, -66, 68, 85, -65, 66, 85, -64, 63, 85, -64, 60, + 85, -63, 56, 85, -62, 52, 85, -61, 49, 85, -60, 45, + 85, -58, 41, 85, -57, 37, 86, -56, 33, 86, -54, 29, + 86, -53, 25, 86, -51, 21, 86, -50, 17, 86, -48, 13, + 86, -46, 9, 87, -44, 5, 87, -42, 1, 87, -40, -3, + 87, -38, -7, 88, -36, -11, 88, -34, -15, 88, -32, -19, + 86, -69, 82, 86, -69, 82, 86, -69, 81, 86, -69, 80, + 86, -69, 78, 86, -68, 77, 86, -68, 75, 86, -68, 72, + 86, -67, 70, 86, -66, 67, 86, -66, 64, 86, -65, 61, + 86, -64, 57, 86, -63, 54, 86, -62, 50, 86, -61, 47, + 86, -60, 42, 87, -59, 39, 87, -57, 35, 87, -56, 31, + 87, -54, 27, 87, -53, 23, 87, -51, 19, 87, -50, 15, + 88, -48, 11, 88, -46, 7, 88, -44, 3, 88, -42, -1, + 88, -40, -5, 89, -38, -9, 89, -36, -13, 89, -34, -17, + 87, -71, 83, 87, -71, 82, 87, -70, 82, 87, -70, 81, + 87, -70, 79, 87, -70, 78, 87, -69, 76, 87, -69, 73, + 87, -68, 71, 87, -68, 68, 87, -67, 65, 87, -66, 62, + 87, -66, 59, 87, -65, 55, 87, -64, 52, 88, -63, 48, + 88, -61, 44, 88, -60, 40, 88, -59, 36, 88, -58, 33, + 88, -56, 28, 88, -55, 24, 88, -53, 21, 89, -51, 17, + 89, -50, 13, 89, -48, 8, 89, -46, 4, 89, -44, 1, + 90, -42, -3, 90, -40, -8, 90, -38, -11, 90, -35, -15, + 88, -72, 84, 88, -72, 83, 88, -72, 83, 88, -72, 82, + 88, -71, 80, 88, -71, 79, 88, -71, 77, 88, -70, 75, + 88, -70, 72, 88, -69, 69, 88, -69, 67, 88, -68, 64, + 88, -67, 60, 89, -66, 57, 89, -65, 53, 89, -64, 50, + 89, -63, 45, 89, -62, 42, 89, -60, 38, 89, -59, 34, + 89, -58, 30, 89, -56, 26, 90, -55, 22, 90, -53, 18, + 90, -51, 14, 90, -49, 10, 90, -47, 6, 91, -46, 2, + 91, -44, -2, 91, -41, -6, 91, -39, -10, 91, -37, -14, + 89, -73, 85, 89, -73, 84, 89, -73, 84, 89, -73, 83, + 89, -73, 81, 89, -72, 80, 89, -72, 78, 89, -72, 76, + 89, -71, 73, 89, -71, 71, 90, -70, 68, 90, -69, 65, + 90, -68, 61, 90, -68, 58, 90, -67, 54, 90, -66, 51, + 90, -64, 47, 90, -63, 43, 90, -62, 39, 90, -61, 36, + 90, -59, 31, 91, -58, 28, 91, -56, 24, 91, -55, 20, + 91, -53, 16, 91, -51, 12, 91, -49, 8, 92, -47, 4, + 92, -45, 0, 92, -43, -4, 92, -41, -8, 93, -39, -12, + 22, 45, 34, 23, 45, 31, 23, 45, 27, 23, 45, 22, + 23, 46, 16, 23, 46, 11, 23, 47, 6, 24, 47, 0, + 24, 48, -5, 24, 49, -10, 24, 50, -15, 25, 51, -20, + 25, 52, -25, 26, 53, -29, 26, 55, -34, 27, 56, -38, + 28, 57, -43, 28, 59, -47, 29, 60, -51, 30, 62, -54, + 30, 64, -59, 31, 65, -62, 32, 67, -66, 33, 68, -69, + 33, 70, -73, 34, 72, -77, 35, 74, -80, 36, 75, -83, + 37, 77, -86, 38, 79, -90, 38, 81, -93, 39, 82, -96, + 23, 44, 34, 23, 44, 31, 23, 44, 27, 23, 44, 22, + 23, 45, 17, 23, 45, 11, 24, 46, 6, 24, 46, 0, + 24, 47, -5, 24, 48, -10, 25, 49, -15, 25, 50, -19, + 26, 51, -25, 26, 53, -29, 27, 54, -33, 27, 55, -38, + 28, 57, -42, 28, 58, -46, 29, 60, -50, 30, 61, -54, + 31, 63, -58, 31, 65, -62, 32, 66, -65, 33, 68, -69, + 33, 69, -72, 34, 71, -76, 35, 73, -80, 36, 75, -83, + 37, 76, -86, 38, 78, -90, 39, 80, -93, 39, 82, -96, + 23, 43, 35, 23, 43, 31, 23, 43, 28, 23, 43, 22, + 23, 44, 17, 24, 44, 12, 24, 45, 6, 24, 46, 1, + 24, 46, -5, 25, 47, -9, 25, 48, -14, 25, 49, -19, + 26, 51, -24, 26, 52, -29, 27, 53, -33, 27, 54, -37, + 28, 56, -42, 29, 58, -46, 29, 59, -50, 30, 61, -54, + 31, 62, -58, 31, 64, -62, 32, 66, -65, 33, 67, -69, + 34, 69, -72, 35, 71, -76, 35, 73, -79, 36, 74, -83, + 37, 76, -86, 38, 78, -89, 39, 80, -92, 40, 81, -95, + 23, 42, 35, 23, 42, 32, 23, 42, 28, 24, 43, 23, + 24, 43, 17, 24, 43, 12, 24, 44, 7, 24, 45, 1, + 25, 46, -4, 25, 47, -9, 25, 47, -14, 26, 49, -19, + 26, 50, -24, 27, 51, -28, 27, 52, -33, 28, 54, -37, + 28, 55, -42, 29, 57, -46, 30, 58, -49, 30, 60, -53, + 31, 62, -58, 32, 63, -61, 32, 65, -65, 33, 67, -68, + 34, 68, -72, 35, 70, -76, 36, 72, -79, 36, 74, -82, + 37, 75, -85, 38, 77, -89, 39, 79, -92, 40, 81, -95, + 24, 41, 35, 24, 41, 32, 24, 41, 28, 24, 42, 23, + 24, 42, 18, 24, 42, 13, 24, 43, 7, 25, 44, 1, + 25, 45, -4, 25, 46, -9, 26, 47, -13, 26, 48, -18, + 26, 49, -23, 27, 50, -28, 27, 52, -32, 28, 53, -36, + 29, 55, -41, 29, 56, -45, 30, 58, -49, 30, 59, -53, + 31, 61, -57, 32, 63, -61, 33, 64, -64, 33, 66, -68, + 34, 68, -71, 35, 70, -75, 36, 71, -79, 37, 73, -82, + 37, 75, -85, 38, 77, -89, 39, 79, -92, 40, 80, -95, + 24, 40, 36, 24, 40, 33, 24, 40, 29, 24, 40, 23, + 24, 41, 18, 25, 41, 13, 25, 42, 8, 25, 43, 2, + 25, 44, -3, 26, 44, -8, 26, 45, -13, 26, 47, -18, + 27, 48, -23, 27, 49, -27, 28, 50, -32, 28, 52, -36, + 29, 54, -41, 30, 55, -45, 30, 57, -49, 31, 58, -52, + 31, 60, -57, 32, 62, -60, 33, 63, -64, 34, 65, -68, + 34, 67, -71, 35, 69, -75, 36, 71, -78, 37, 72, -81, + 38, 74, -85, 39, 76, -88, 39, 78, -91, 40, 80, -95, + 25, 38, 36, 25, 39, 33, 25, 39, 29, 25, 39, 24, + 25, 39, 19, 25, 40, 14, 25, 41, 9, 26, 41, 3, + 26, 42, -2, 26, 43, -7, 26, 44, -12, 27, 45, -17, + 27, 47, -22, 28, 48, -27, 28, 49, -31, 29, 51, -35, + 29, 52, -40, 30, 54, -44, 30, 56, -48, 31, 57, -52, + 32, 59, -56, 33, 61, -60, 33, 62, -63, 34, 64, -67, + 35, 66, -70, 36, 68, -74, 36, 70, -78, 37, 72, -81, + 38, 73, -84, 39, 75, -88, 40, 77, -91, 40, 79, -94, + 25, 37, 37, 25, 37, 34, 25, 37, 30, 25, 38, 25, + 26, 38, 20, 26, 38, 14, 26, 39, 9, 26, 40, 3, + 26, 41, -2, 27, 42, -7, 27, 43, -11, 27, 44, -16, + 28, 45, -21, 28, 47, -26, 29, 48, -30, 29, 49, -34, + 30, 51, -39, 30, 53, -43, 31, 54, -47, 32, 56, -51, + 32, 58, -55, 33, 60, -59, 34, 61, -63, 34, 63, -66, + 35, 65, -70, 36, 67, -74, 37, 69, -77, 37, 71, -80, + 38, 72, -84, 39, 74, -87, 40, 76, -91, 41, 78, -94, + 26, 35, 37, 26, 35, 34, 26, 36, 30, 26, 36, 25, + 26, 36, 20, 26, 37, 15, 26, 37, 10, 27, 38, 4, + 27, 39, -1, 27, 40, -6, 28, 41, -10, 28, 42, -15, + 28, 44, -20, 29, 45, -25, 29, 46, -29, 30, 48, -34, + 30, 50, -38, 31, 51, -42, 31, 53, -46, 32, 55, -50, + 33, 57, -55, 33, 58, -58, 34, 60, -62, 35, 62, -66, + 35, 64, -69, 36, 66, -73, 37, 68, -77, 38, 69, -80, + 39, 71, -83, 39, 73, -87, 40, 75, -90, 41, 77, -93, + 26, 33, 38, 27, 34, 35, 27, 34, 31, 27, 34, 26, + 27, 34, 21, 27, 35, 16, 27, 36, 11, 27, 37, 5, + 28, 37, 0, 28, 38, -5, 28, 39, -10, 28, 41, -14, + 29, 42, -19, 29, 43, -24, 30, 45, -28, 30, 46, -33, + 31, 48, -37, 31, 50, -42, 32, 51, -46, 32, 53, -50, + 33, 55, -54, 34, 57, -58, 34, 59, -61, 35, 61, -65, + 36, 62, -69, 37, 64, -72, 37, 66, -76, 38, 68, -79, + 39, 70, -83, 40, 72, -86, 41, 74, -89, 41, 76, -93, + 27, 31, 39, 27, 32, 35, 27, 32, 32, 27, 32, 27, + 27, 33, 22, 28, 33, 17, 28, 34, 12, 28, 35, 6, + 28, 36, 1, 29, 37, -4, 29, 38, -9, 29, 39, -13, + 30, 40, -18, 30, 42, -23, 30, 43, -27, 31, 45, -32, + 31, 46, -37, 32, 48, -41, 32, 50, -45, 33, 52, -49, + 34, 54, -53, 34, 55, -57, 35, 57, -60, 36, 59, -64, + 36, 61, -68, 37, 63, -72, 38, 65, -75, 39, 67, -78, + 39, 69, -82, 40, 71, -85, 41, 73, -89, 42, 75, -92, + 28, 29, 39, 28, 29, 36, 28, 29, 33, 28, 30, 28, + 28, 30, 23, 29, 31, 18, 29, 31, 13, 29, 32, 8, + 29, 33, 3, 29, 34, -2, 30, 35, -7, 30, 36, -12, + 30, 38, -17, 31, 39, -22, 31, 41, -26, 32, 42, -30, + 32, 44, -35, 33, 46, -39, 33, 48, -43, 34, 49, -47, + 34, 51, -52, 35, 53, -56, 36, 55, -59, 36, 57, -63, + 37, 59, -67, 38, 61, -71, 39, 63, -74, 39, 65, -77, + 40, 67, -81, 41, 69, -85, 42, 71, -88, 42, 73, -91, + 29, 27, 40, 29, 27, 37, 29, 27, 33, 29, 27, 29, + 29, 28, 24, 29, 28, 19, 30, 29, 14, 30, 30, 9, + 30, 31, 4, 30, 32, -1, 31, 33, -6, 31, 34, -11, + 31, 36, -16, 32, 37, -20, 32, 39, -25, 32, 40, -29, + 33, 42, -34, 33, 44, -38, 34, 46, -42, 34, 48, -46, + 35, 50, -51, 36, 52, -55, 36, 53, -58, 37, 55, -62, + 38, 57, -66, 38, 60, -70, 39, 62, -73, 40, 64, -77, + 40, 66, -80, 41, 68, -84, 42, 70, -87, 43, 72, -90, + 30, 24, 41, 30, 25, 38, 30, 25, 34, 30, 25, 30, + 30, 26, 25, 30, 26, 20, 30, 27, 15, 31, 28, 10, + 31, 29, 5, 31, 30, 0, 31, 31, -5, 32, 32, -9, + 32, 34, -15, 32, 35, -19, 33, 37, -24, 33, 38, -28, + 34, 40, -33, 34, 42, -37, 35, 44, -41, 35, 46, -45, + 36, 48, -50, 36, 50, -53, 37, 52, -57, 38, 54, -61, + 38, 56, -65, 39, 58, -69, 40, 60, -72, 40, 62, -76, + 41, 64, -79, 42, 66, -83, 43, 68, -86, 43, 70, -89, + 31, 22, 41, 31, 22, 38, 31, 23, 35, 31, 23, 31, + 31, 23, 26, 31, 24, 21, 31, 25, 17, 32, 26, 11, + 32, 26, 6, 32, 28, 1, 32, 29, -3, 33, 30, -8, + 33, 31, -13, 33, 33, -18, 34, 34, -22, 34, 36, -27, + 34, 38, -32, 35, 40, -36, 35, 42, -40, 36, 44, -44, + 37, 46, -48, 37, 48, -52, 38, 50, -56, 38, 52, -60, + 39, 54, -64, 40, 56, -68, 40, 58, -71, 41, 60, -75, + 42, 62, -78, 42, 65, -82, 43, 67, -85, 44, 69, -88, + 32, 20, 42, 32, 20, 39, 32, 20, 36, 32, 21, 32, + 32, 21, 27, 32, 22, 23, 32, 22, 18, 32, 23, 12, + 33, 24, 7, 33, 25, 3, 33, 26, -2, 33, 28, -7, + 34, 29, -12, 34, 31, -17, 34, 32, -21, 35, 34, -26, + 35, 36, -30, 36, 38, -35, 36, 40, -39, 37, 41, -43, + 37, 44, -47, 38, 46, -51, 38, 48, -55, 39, 50, -59, + 40, 52, -62, 40, 54, -66, 41, 56, -70, 42, 58, -74, + 42, 60, -77, 43, 63, -81, 44, 65, -84, 45, 67, -87, + 33, 17, 43, 33, 18, 40, 33, 18, 37, 33, 18, 33, + 33, 19, 28, 33, 19, 24, 33, 20, 19, 33, 21, 14, + 34, 22, 9, 34, 23, 4, 34, 24, -1, 34, 25, -5, + 35, 27, -11, 35, 28, -15, 35, 30, -20, 36, 32, -24, + 36, 34, -29, 37, 35, -33, 37, 37, -37, 38, 39, -41, + 38, 41, -46, 39, 43, -50, 39, 46, -54, 40, 48, -57, + 40, 50, -61, 41, 52, -65, 42, 54, -69, 42, 56, -72, + 43, 58, -76, 44, 61, -80, 44, 63, -83, 45, 65, -86, + 34, 15, 43, 34, 15, 41, 34, 15, 38, 34, 16, 34, + 34, 16, 29, 34, 17, 25, 34, 18, 20, 34, 18, 15, + 35, 19, 10, 35, 20, 5, 35, 22, 1, 35, 23, -4, + 36, 25, -9, 36, 26, -14, 36, 28, -18, 37, 29, -23, + 37, 31, -28, 37, 33, -32, 38, 35, -36, 38, 37, -40, + 39, 39, -45, 39, 41, -49, 40, 43, -52, 41, 45, -56, + 41, 48, -60, 42, 50, -64, 42, 52, -68, 43, 54, -71, + 44, 57, -75, 44, 59, -79, 45, 61, -82, 46, 63, -85, + 35, 13, 44, 35, 13, 41, 35, 13, 39, 35, 13, 35, + 35, 14, 31, 35, 14, 26, 35, 15, 22, 35, 16, 16, + 36, 17, 12, 36, 18, 7, 36, 19, 2, 36, 21, -3, + 37, 22, -8, 37, 24, -12, 37, 25, -17, 38, 27, -21, + 38, 29, -26, 38, 31, -30, 39, 33, -35, 39, 35, -39, + 40, 37, -43, 40, 39, -47, 41, 41, -51, 41, 43, -55, + 42, 45, -59, 43, 48, -63, 43, 50, -66, 44, 52, -70, + 44, 54, -74, 45, 57, -77, 46, 59, -81, 46, 61, -84, + 36, 10, 45, 36, 10, 42, 36, 11, 40, 36, 11, 36, + 36, 11, 32, 36, 12, 27, 36, 13, 23, 36, 14, 18, + 37, 15, 13, 37, 16, 8, 37, 17, 4, 37, 18, -1, + 38, 20, -6, 38, 21, -11, 38, 23, -15, 39, 25, -20, + 39, 27, -25, 39, 28, -29, 40, 30, -33, 40, 32, -37, + 41, 35, -42, 41, 37, -46, 42, 39, -50, 42, 41, -54, + 43, 43, -57, 43, 46, -61, 44, 48, -65, 45, 50, -69, + 45, 52, -72, 46, 55, -76, 47, 57, -80, 47, 59, -83, + 37, 8, 46, 37, 8, 43, 37, 8, 40, 37, 9, 37, + 37, 9, 33, 37, 10, 29, 37, 10, 24, 38, 11, 19, + 38, 12, 14, 38, 13, 10, 38, 14, 5, 38, 16, 0, + 39, 17, -5, 39, 19, -9, 39, 20, -14, 40, 22, -18, + 40, 24, -23, 40, 26, -27, 41, 28, -32, 41, 30, -36, + 42, 32, -40, 42, 34, -44, 43, 37, -48, 43, 39, -52, + 44, 41, -56, 44, 43, -60, 45, 46, -64, 45, 48, -67, + 46, 50, -71, 47, 53, -75, 47, 55, -78, 48, 57, -82, + 38, 5, 46, 38, 6, 44, 38, 6, 41, 38, 6, 38, + 38, 7, 34, 38, 7, 30, 38, 8, 26, 39, 9, 21, + 39, 10, 16, 39, 11, 11, 39, 12, 7, 39, 13, 2, + 40, 15, -3, 40, 16, -8, 40, 18, -12, 41, 20, -17, + 41, 22, -22, 41, 24, -26, 42, 26, -30, 42, 28, -34, + 43, 30, -39, 43, 32, -43, 43, 34, -47, 44, 36, -51, + 44, 39, -55, 45, 41, -59, 46, 43, -62, 46, 46, -66, + 47, 48, -70, 47, 51, -74, 48, 53, -77, 49, 55, -81, + 39, 3, 47, 39, 3, 45, 39, 3, 42, 39, 4, 39, + 39, 4, 35, 39, 5, 31, 40, 5, 27, 40, 6, 22, + 40, 7, 17, 40, 8, 13, 40, 10, 8, 40, 11, 4, + 41, 12, -2, 41, 14, -6, 41, 16, -11, 42, 17, -15, + 42, 19, -20, 42, 21, -24, 43, 23, -29, 43, 25, -33, + 44, 28, -37, 44, 30, -41, 44, 32, -45, 45, 34, -49, + 45, 36, -53, 46, 39, -57, 47, 41, -61, 47, 43, -65, + 48, 46, -68, 48, 48, -72, 49, 51, -76, 50, 53, -79, + 40, 1, 48, 40, 1, 46, 40, 1, 43, 40, 1, 40, + 41, 2, 37, 41, 2, 33, 41, 3, 28, 41, 4, 23, + 41, 5, 19, 41, 6, 14, 41, 7, 10, 42, 8, 5, + 42, 10, 0, 42, 12, -5, 42, 13, -9, 43, 15, -13, + 43, 17, -18, 43, 19, -23, 44, 21, -27, 44, 23, -31, + 45, 25, -36, 45, 27, -40, 45, 29, -44, 46, 32, -48, + 46, 34, -52, 47, 37, -56, 47, 39, -60, 48, 41, -63, + 48, 44, -67, 49, 46, -71, 50, 48, -74, 50, 51, -78, + 42, -2, 49, 42, -2, 47, 42, -1, 44, 42, -1, 41, + 42, -1, 38, 42, 0, 34, 42, 1, 30, 42, 2, 25, + 42, 3, 20, 42, 4, 16, 42, 5, 11, 43, 6, 7, + 43, 8, 2, 43, 9, -3, 43, 11, -7, 44, 12, -12, + 44, 14, -17, 44, 16, -21, 45, 18, -25, 45, 20, -30, + 46, 23, -34, 46, 25, -38, 46, 27, -42, 47, 29, -46, + 47, 32, -50, 48, 34, -54, 48, 36, -58, 49, 39, -62, + 49, 41, -65, 50, 44, -69, 51, 46, -73, 51, 49, -77, + 43, -4, 49, 43, -4, 47, 43, -4, 45, 43, -3, 42, + 43, -3, 39, 43, -2, 35, 43, -2, 31, 43, -1, 26, + 43, 0, 22, 43, 1, 17, 44, 2, 13, 44, 4, 8, + 44, 5, 3, 44, 7, -1, 45, 8, -6, 45, 10, -10, + 45, 12, -15, 45, 14, -19, 46, 16, -24, 46, 18, -28, + 47, 20, -33, 47, 22, -37, 47, 25, -41, 48, 27, -45, + 48, 29, -48, 49, 32, -53, 49, 34, -57, 50, 36, -60, + 50, 39, -64, 51, 42, -68, 51, 44, -72, 52, 46, -75, + 44, -6, 50, 44, -6, 48, 44, -6, 46, 44, -6, 43, + 44, -5, 40, 44, -5, 36, 44, -4, 32, 44, -3, 28, + 44, -2, 23, 45, -1, 19, 45, 0, 14, 45, 1, 10, + 45, 3, 5, 45, 4, 0, 46, 6, -4, 46, 8, -9, + 46, 10, -13, 47, 12, -18, 47, 13, -22, 47, 16, -26, + 48, 18, -31, 48, 20, -35, 48, 22, -39, 49, 24, -43, + 49, 27, -47, 50, 29, -51, 50, 32, -55, 51, 34, -59, + 51, 36, -62, 52, 39, -67, 52, 42, -70, 53, 44, -74, + 45, -9, 51, 45, -8, 49, 45, -8, 47, 45, -8, 45, + 45, -7, 41, 45, -7, 38, 45, -6, 34, 45, -5, 29, + 46, -4, 25, 46, -3, 20, 46, -2, 16, 46, -1, 11, + 46, 0, 6, 47, 2, 2, 47, 4, -3, 47, 5, -7, + 47, 7, -12, 48, 9, -16, 48, 11, -20, 48, 13, -25, + 49, 15, -29, 49, 18, -33, 49, 20, -37, 50, 22, -41, + 50, 24, -45, 51, 27, -50, 51, 29, -53, 52, 32, -57, + 52, 34, -61, 53, 37, -65, 53, 39, -69, 54, 42, -72, + 46, -11, 52, 46, -11, 50, 46, -10, 48, 46, -10, 46, + 46, -10, 43, 46, -9, 39, 47, -9, 35, 47, -8, 31, + 47, -7, 26, 47, -6, 22, 47, -5, 18, 47, -3, 13, + 47, -2, 8, 48, 0, 4, 48, 1, -1, 48, 3, -5, + 48, 5, -10, 49, 7, -14, 49, 9, -19, 49, 11, -23, + 50, 13, -28, 50, 15, -32, 50, 17, -36, 51, 20, -40, + 51, 22, -44, 52, 25, -48, 52, 27, -52, 53, 29, -56, + 53, 32, -59, 54, 34, -64, 54, 37, -67, 55, 39, -71, + 47, -13, 53, 47, -13, 51, 48, -13, 49, 48, -12, 47, + 48, -12, 44, 48, -11, 40, 48, -11, 37, 48, -10, 32, + 48, -9, 28, 48, -8, 23, 48, -7, 19, 48, -6, 15, + 49, -4, 10, 49, -3, 5, 49, -1, 1, 49, 1, -4, + 50, 2, -8, 50, 4, -13, 50, 6, -17, 50, 8, -21, + 51, 11, -26, 51, 13, -30, 52, 15, -34, 52, 17, -38, + 52, 20, -42, 53, 22, -46, 53, 25, -50, 54, 27, -54, + 54, 29, -58, 55, 32, -62, 55, 35, -66, 56, 37, -69, + 49, -15, 54, 49, -15, 52, 49, -15, 50, 49, -15, 48, + 49, -14, 45, 49, -14, 42, 49, -13, 38, 49, -12, 33, + 49, -11, 29, 49, -10, 25, 49, -9, 21, 50, -8, 16, + 50, -6, 11, 50, -5, 7, 50, -3, 3, 50, -2, -2, + 51, 0, -7, 51, 2, -11, 51, 4, -15, 52, 6, -20, + 52, 8, -24, 52, 10, -28, 53, 13, -32, 53, 15, -36, + 53, 17, -40, 54, 20, -45, 54, 22, -49, 55, 25, -52, + 55, 27, -56, 56, 30, -60, 56, 32, -64, 57, 35, -68, + 50, -17, 55, 50, -17, 53, 50, -17, 51, 50, -17, 49, + 50, -16, 46, 50, -16, 43, 50, -15, 39, 50, -14, 35, + 50, -13, 31, 50, -12, 27, 51, -11, 22, 51, -10, 18, + 51, -9, 13, 51, -7, 9, 51, -6, 4, 52, -4, 0, + 52, -2, -5, 52, 0, -9, 52, 2, -14, 53, 4, -18, + 53, 6, -23, 53, 8, -27, 54, 10, -31, 54, 12, -35, + 54, 15, -39, 55, 17, -43, 55, 20, -47, 56, 22, -51, + 56, 25, -55, 57, 27, -59, 57, 30, -62, 58, 32, -66, + 51, -20, 56, 51, -20, 54, 51, -20, 53, 51, -19, 50, + 52, -19, 48, 52, -18, 44, 52, -18, 41, 52, -17, 37, + 52, -16, 33, 52, -15, 28, 52, -14, 24, 52, -13, 20, + 52, -11, 15, 53, -10, 11, 53, -9, 6, 53, -7, 2, + 53, -5, -3, 53, -3, -7, 54, -1, -11, 54, 1, -16, + 54, 3, -20, 55, 5, -25, 55, 7, -29, 55, 9, -33, + 56, 12, -37, 56, 14, -41, 57, 17, -45, 57, 19, -49, + 57, 22, -53, 58, 24, -57, 58, 27, -60, 59, 29, -64, + 53, -22, 57, 53, -22, 55, 53, -22, 54, 53, -21, 51, + 53, -21, 49, 53, -20, 46, 53, -20, 42, 53, -19, 38, + 53, -18, 34, 53, -17, 30, 53, -16, 26, 53, -15, 21, + 54, -14, 17, 54, -12, 12, 54, -11, 8, 54, -9, 4, + 54, -7, -1, 55, -5, -6, 55, -4, -10, 55, -2, -14, + 55, 1, -19, 56, 3, -23, 56, 5, -27, 56, 7, -31, + 57, 9, -35, 57, 12, -39, 58, 14, -43, 58, 17, -47, + 58, 19, -51, 59, 22, -55, 59, 24, -59, 60, 27, -63, + 54, -24, 58, 54, -24, 56, 54, -24, 55, 54, -23, 53, + 54, -23, 50, 54, -23, 47, 54, -22, 44, 54, -21, 39, + 54, -20, 36, 54, -19, 31, 54, -18, 27, 55, -17, 23, + 55, -16, 18, 55, -14, 14, 55, -13, 10, 55, -11, 5, + 56, -10, 0, 56, -8, -4, 56, -6, -8, 56, -4, -12, + 57, -2, -17, 57, 0, -21, 57, 3, -25, 58, 5, -29, + 58, 7, -33, 58, 10, -38, 59, 12, -42, 59, 14, -45, + 59, 17, -49, 60, 20, -54, 60, 22, -57, 61, 25, -61, + 55, -26, 59, 55, -26, 57, 55, -26, 56, 55, -25, 54, + 55, -25, 51, 55, -25, 48, 55, -24, 45, 55, -23, 41, + 55, -22, 37, 56, -21, 33, 56, -20, 29, 56, -19, 25, + 56, -18, 20, 56, -17, 16, 56, -15, 11, 57, -14, 7, + 57, -12, 2, 57, -10, -2, 57, -8, -6, 57, -6, -11, + 58, -4, -15, 58, -2, -19, 58, 0, -23, 59, 2, -28, + 59, 5, -32, 59, 7, -36, 60, 10, -40, 60, 12, -44, + 61, 14, -48, 61, 17, -52, 61, 20, -56, 62, 22, -59, + 56, -28, 60, 56, -28, 58, 56, -28, 57, 56, -27, 55, + 56, -27, 52, 56, -26, 49, 57, -26, 46, 57, -25, 42, + 57, -24, 38, 57, -23, 34, 57, -22, 30, 57, -21, 26, + 57, -20, 21, 57, -19, 17, 58, -17, 13, 58, -16, 9, + 58, -14, 4, 58, -12, 0, 58, -10, -5, 59, -8, -9, + 59, -6, -14, 59, -4, -18, 59, -2, -22, 60, 0, -26, + 60, 2, -30, 60, 5, -34, 61, 7, -38, 61, 10, -42, + 62, 12, -46, 62, 15, -50, 62, 17, -54, 63, 20, -58, + 58, -30, 60, 58, -30, 59, 58, -30, 58, 58, -29, 56, + 58, -29, 53, 58, -28, 51, 58, -28, 47, 58, -27, 44, + 58, -26, 40, 58, -25, 36, 58, -25, 32, 58, -23, 28, + 58, -22, 23, 59, -21, 19, 59, -19, 15, 59, -18, 10, + 59, -16, 6, 59, -14, 1, 60, -12, -3, 60, -11, -7, + 60, -8, -12, 60, -6, -16, 61, -4, -20, 61, -2, -24, + 61, 0, -28, 62, 3, -33, 62, 5, -37, 62, 7, -40, + 63, 10, -44, 63, 13, -49, 63, 15, -52, 64, 17, -56, + 59, -32, 61, 59, -32, 60, 59, -31, 59, 59, -31, 57, + 59, -31, 55, 59, -30, 52, 59, -30, 49, 59, -29, 45, + 59, -28, 41, 59, -27, 37, 59, -26, 33, 59, -25, 29, + 60, -24, 25, 60, -23, 20, 60, -21, 16, 60, -20, 12, + 60, -18, 7, 60, -16, 3, 61, -15, -1, 61, -13, -5, + 61, -11, -10, 61, -9, -14, 62, -7, -18, 62, -4, -22, + 62, -2, -26, 63, 0, -31, 63, 3, -35, 63, 5, -39, + 64, 7, -43, 64, 10, -47, 65, 13, -51, 65, 15, -54, + 60, -34, 62, 60, -33, 61, 60, -33, 60, 60, -33, 58, + 60, -33, 56, 60, -32, 53, 60, -32, 50, 60, -31, 46, + 60, -30, 43, 60, -29, 39, 61, -28, 35, 61, -27, 31, + 61, -26, 26, 61, -25, 22, 61, -23, 18, 61, -22, 14, + 61, -20, 9, 62, -19, 5, 62, -17, 1, 62, -15, -4, + 62, -13, -8, 63, -11, -12, 63, -9, -17, 63, -7, -21, + 63, -4, -25, 64, -2, -29, 64, 0, -33, 64, 3, -37, + 65, 5, -41, 65, 8, -45, 66, 10, -49, 66, 13, -53, + 61, -35, 63, 61, -35, 62, 61, -35, 61, 61, -35, 59, + 61, -35, 57, 61, -34, 54, 61, -34, 51, 61, -33, 48, + 62, -32, 44, 62, -31, 40, 62, -30, 36, 62, -29, 32, + 62, -28, 28, 62, -27, 24, 62, -25, 20, 62, -24, 15, + 63, -22, 11, 63, -21, 6, 63, -19, 2, 63, -17, -2, + 64, -15, -7, 64, -13, -11, 64, -11, -15, 64, -9, -19, + 65, -7, -23, 65, -4, -27, 65, -2, -31, 66, 0, -35, + 66, 3, -39, 66, 6, -43, 67, 8, -47, 67, 10, -51, + 62, -37, 64, 62, -37, 63, 62, -37, 62, 62, -37, 60, + 63, -36, 58, 63, -36, 55, 63, -35, 53, 63, -35, 49, + 63, -34, 45, 63, -33, 42, 63, -32, 38, 63, -31, 34, + 63, -30, 29, 63, -29, 25, 63, -27, 21, 64, -26, 17, + 64, -24, 12, 64, -23, 8, 64, -21, 4, 64, -19, 0, + 65, -17, -5, 65, -15, -9, 65, -13, -13, 65, -11, -17, + 66, -9, -21, 66, -6, -26, 66, -4, -30, 67, -2, -34, + 67, 1, -37, 67, 3, -42, 68, 6, -46, 68, 8, -49, + 64, -39, 65, 64, -39, 64, 64, -39, 63, 64, -38, 61, + 64, -38, 59, 64, -38, 57, 64, -37, 54, 64, -37, 50, + 64, -36, 47, 64, -35, 43, 64, -34, 39, 64, -33, 35, + 64, -32, 31, 65, -31, 27, 65, -29, 23, 65, -28, 19, + 65, -26, 14, 65, -25, 10, 65, -23, 6, 66, -21, 1, + 66, -19, -3, 66, -17, -7, 66, -15, -11, 67, -13, -15, + 67, -11, -19, 67, -9, -24, 67, -6, -28, 68, -4, -32, + 68, -2, -36, 68, 1, -40, 69, 3, -44, 69, 6, -48, + 65, -41, 66, 65, -41, 65, 65, -40, 64, 65, -40, 62, + 65, -40, 60, 65, -39, 58, 65, -39, 55, 65, -38, 52, + 65, -38, 48, 65, -37, 45, 65, -36, 41, 65, -35, 37, + 66, -34, 33, 66, -33, 28, 66, -31, 24, 66, -30, 20, + 66, -28, 16, 66, -27, 11, 67, -25, 7, 67, -23, 3, + 67, -21, -1, 67, -19, -6, 67, -17, -10, 68, -15, -14, + 68, -13, -18, 68, -11, -22, 69, -8, -26, 69, -6, -30, + 69, -4, -34, 70, -1, -38, 70, 1, -42, 70, 4, -46, + 66, -42, 67, 66, -42, 66, 66, -42, 65, 66, -42, 63, + 66, -42, 61, 66, -41, 59, 66, -41, 56, 66, -40, 53, + 66, -39, 49, 67, -39, 46, 67, -38, 42, 67, -37, 38, + 67, -36, 34, 67, -34, 30, 67, -33, 26, 67, -32, 22, + 67, -30, 17, 68, -29, 13, 68, -27, 9, 68, -25, 5, + 68, -23, 0, 68, -21, -4, 69, -19, -8, 69, -17, -12, + 69, -15, -16, 69, -13, -21, 70, -11, -24, 70, -8, -28, + 70, -6, -32, 71, -3, -37, 71, -1, -40, 71, 1, -44, + 67, -44, 68, 67, -44, 67, 67, -44, 66, 67, -44, 64, + 67, -43, 62, 67, -43, 60, 68, -42, 57, 68, -42, 54, + 68, -41, 51, 68, -40, 47, 68, -40, 44, 68, -39, 40, + 68, -37, 36, 68, -36, 32, 68, -35, 28, 68, -34, 24, + 69, -32, 19, 69, -31, 15, 69, -29, 11, 69, -27, 7, + 69, -25, 2, 70, -23, -2, 70, -21, -6, 70, -19, -10, + 70, -17, -14, 71, -15, -19, 71, -13, -23, 71, -11, -27, + 71, -8, -31, 72, -6, -35, 72, -3, -39, 72, -1, -43, + 69, -46, 69, 69, -46, 68, 69, -46, 67, 69, -45, 65, + 69, -45, 64, 69, -45, 61, 69, -44, 59, 69, -43, 55, + 69, -43, 52, 69, -42, 49, 69, -41, 45, 69, -40, 41, + 69, -39, 37, 69, -38, 33, 69, -37, 29, 70, -35, 25, + 70, -34, 21, 70, -32, 16, 70, -31, 12, 70, -29, 8, + 71, -27, 4, 71, -25, 0, 71, -23, -5, 71, -21, -9, + 71, -19, -13, 72, -17, -17, 72, -15, -21, 72, -13, -25, + 73, -10, -29, 73, -8, -33, 73, -5, -37, 74, -3, -41, + 70, -47, 70, 70, -47, 69, 70, -47, 68, 70, -47, 66, + 70, -47, 65, 70, -46, 62, 70, -46, 60, 70, -45, 57, + 70, -45, 53, 70, -44, 50, 70, -43, 47, 70, -42, 43, + 70, -41, 39, 71, -40, 35, 71, -39, 31, 71, -37, 27, + 71, -36, 22, 71, -34, 18, 71, -33, 14, 71, -31, 10, + 72, -29, 5, 72, -27, 1, 72, -25, -3, 72, -23, -7, + 73, -21, -11, 73, -19, -15, 73, -17, -19, 73, -15, -23, + 74, -13, -27, 74, -10, -31, 74, -8, -35, 75, -5, -39, + 71, -49, 71, 71, -49, 70, 71, -49, 69, 71, -49, 67, + 71, -48, 66, 71, -48, 64, 71, -47, 61, 71, -47, 58, + 71, -46, 55, 71, -45, 51, 71, -45, 48, 72, -44, 44, + 72, -43, 40, 72, -42, 36, 72, -40, 32, 72, -39, 28, + 72, -38, 24, 72, -36, 20, 72, -35, 16, 73, -33, 12, + 73, -31, 7, 73, -29, 3, 73, -27, -1, 73, -25, -5, + 74, -23, -9, 74, -21, -14, 74, -19, -18, 75, -17, -22, + 75, -15, -25, 75, -12, -30, 75, -10, -34, 76, -7, -37, + 72, -51, 72, 72, -51, 71, 72, -50, 70, 72, -50, 69, + 72, -50, 67, 72, -49, 65, 72, -49, 62, 72, -48, 59, + 73, -48, 56, 73, -47, 53, 73, -46, 49, 73, -45, 46, + 73, -44, 42, 73, -43, 38, 73, -42, 34, 73, -41, 30, + 73, -39, 25, 73, -38, 21, 74, -36, 17, 74, -35, 13, + 74, -33, 9, 74, -31, 5, 74, -29, 1, 75, -27, -3, + 75, -25, -7, 75, -23, -12, 75, -21, -16, 76, -19, -20, + 76, -17, -24, 76, -14, -28, 77, -12, -32, 77, -10, -36, + 73, -52, 73, 73, -52, 72, 73, -52, 71, 74, -52, 70, + 74, -51, 68, 74, -51, 66, 74, -51, 63, 74, -50, 60, + 74, -49, 57, 74, -49, 54, 74, -48, 51, 74, -47, 47, + 74, -46, 43, 74, -45, 39, 74, -44, 35, 74, -43, 31, + 75, -41, 27, 75, -40, 23, 75, -38, 19, 75, -37, 15, + 75, -35, 10, 75, -33, 6, 76, -31, 2, 76, -29, -2, + 76, -27, -6, 76, -25, -10, 77, -23, -14, 77, -21, -18, + 77, -19, -22, 77, -16, -26, 78, -14, -30, 78, -12, -34, + 75, -54, 74, 75, -54, 73, 75, -53, 72, 75, -53, 71, + 75, -53, 69, 75, -53, 67, 75, -52, 65, 75, -52, 62, + 75, -51, 59, 75, -50, 55, 75, -50, 52, 75, -49, 49, + 75, -48, 45, 75, -47, 41, 75, -46, 37, 76, -44, 33, + 76, -43, 29, 76, -42, 25, 76, -40, 21, 76, -38, 17, + 76, -37, 12, 77, -35, 8, 77, -33, 4, 77, -31, 0, + 77, -29, -4, 77, -27, -8, 78, -25, -12, 78, -23, -16, + 78, -21, -20, 78, -18, -25, 79, -16, -28, 79, -14, -32, + 76, -55, 75, 76, -55, 74, 76, -55, 73, 76, -55, 72, + 76, -55, 70, 76, -54, 68, 76, -54, 66, 76, -53, 63, + 76, -53, 60, 76, -52, 57, 76, -51, 53, 76, -50, 50, + 76, -49, 46, 77, -48, 42, 77, -47, 38, 77, -46, 35, + 77, -45, 30, 77, -43, 26, 77, -42, 22, 77, -40, 18, + 78, -38, 14, 78, -37, 10, 78, -35, 6, 78, -33, 2, + 78, -31, -2, 79, -29, -7, 79, -27, -11, 79, -25, -15, + 79, -23, -19, 80, -20, -23, 80, -18, -27, 80, -16, -31, + 77, -57, 76, 77, -57, 75, 77, -57, 74, 77, -57, 73, + 77, -56, 71, 78, -56, 69, 78, -56, 67, 78, -55, 64, + 78, -55, 61, 78, -54, 58, 78, -53, 55, 78, -52, 52, + 78, -51, 48, 78, -50, 44, 78, -49, 40, 78, -48, 36, + 78, -47, 32, 79, -45, 28, 79, -44, 24, 79, -42, 20, + 79, -41, 16, 79, -39, 12, 79, -37, 8, 80, -36, 4, + 80, -34, 0, 80, -31, -5, 80, -29, -9, 80, -27, -13, + 81, -25, -16, 81, -23, -21, 81, -21, -25, 82, -18, -28, + 79, -59, 77, 79, -59, 76, 79, -58, 75, 79, -58, 74, + 79, -58, 72, 79, -58, 71, 79, -57, 68, 79, -57, 66, + 79, -56, 63, 79, -55, 60, 79, -55, 57, 79, -54, 53, + 79, -53, 49, 79, -52, 46, 79, -51, 42, 79, -50, 38, + 80, -48, 34, 80, -47, 30, 80, -46, 26, 80, -44, 22, + 80, -42, 17, 80, -41, 13, 81, -39, 9, 81, -37, 5, + 81, -36, 1, 81, -33, -3, 81, -31, -7, 82, -29, -11, + 82, -27, -15, 82, -25, -19, 82, -23, -23, 83, -20, -27, + 80, -60, 78, 80, -60, 77, 80, -60, 76, 80, -60, 75, + 80, -59, 73, 80, -59, 72, 80, -59, 69, 80, -58, 67, + 80, -58, 64, 80, -57, 61, 80, -56, 58, 80, -56, 55, + 80, -55, 51, 80, -54, 47, 81, -53, 43, 81, -51, 40, + 81, -50, 35, 81, -49, 31, 81, -47, 27, 81, -46, 23, + 81, -44, 19, 82, -43, 15, 82, -41, 11, 82, -39, 7, + 82, -37, 3, 82, -35, -1, 83, -33, -5, 83, -31, -9, + 83, -29, -13, 83, -27, -17, 84, -25, -21, 84, -22, -25, + 81, -62, 79, 81, -61, 78, 81, -61, 77, 81, -61, 76, + 81, -61, 74, 81, -61, 73, 81, -60, 71, 81, -60, 68, + 81, -59, 65, 81, -59, 62, 81, -58, 59, 81, -57, 56, + 82, -56, 52, 82, -55, 48, 82, -54, 45, 82, -53, 41, + 82, -52, 37, 82, -50, 33, 82, -49, 29, 82, -48, 25, + 83, -46, 21, 83, -44, 17, 83, -43, 13, 83, -41, 9, + 83, -39, 5, 83, -37, 0, 84, -35, -4, 84, -33, -7, + 84, -31, -11, 84, -29, -16, 85, -27, -20, 85, -24, -23, + 82, -63, 80, 82, -63, 79, 82, -63, 78, 82, -63, 77, + 82, -62, 76, 82, -62, 74, 82, -62, 72, 82, -61, 69, + 82, -61, 66, 83, -60, 64, 83, -59, 60, 83, -59, 57, + 83, -58, 53, 83, -57, 50, 83, -56, 46, 83, -55, 43, + 83, -53, 38, 83, -52, 34, 83, -51, 31, 84, -49, 27, + 84, -48, 22, 84, -46, 18, 84, -44, 14, 84, -43, 10, + 84, -41, 7, 85, -39, 2, 85, -37, -2, 85, -35, -6, + 85, -33, -10, 86, -31, -14, 86, -29, -18, 86, -26, -22, + 83, -64, 81, 83, -64, 80, 83, -64, 79, 84, -64, 78, + 84, -64, 77, 84, -63, 75, 84, -63, 73, 84, -63, 70, + 84, -62, 68, 84, -61, 65, 84, -61, 62, 84, -60, 59, + 84, -59, 55, 84, -58, 51, 84, -57, 48, 84, -56, 44, + 84, -55, 40, 84, -54, 36, 85, -52, 32, 85, -51, 28, + 85, -49, 24, 85, -48, 20, 85, -46, 16, 85, -44, 12, + 86, -43, 8, 86, -41, 4, 86, -39, 0, 86, -37, -4, + 86, -35, -8, 87, -33, -12, 87, -30, -16, 87, -28, -20, + 85, -66, 82, 85, -66, 81, 85, -66, 80, 85, -65, 79, + 85, -65, 78, 85, -65, 76, 85, -65, 74, 85, -64, 71, + 85, -64, 69, 85, -63, 66, 85, -62, 63, 85, -62, 60, + 85, -61, 56, 85, -60, 53, 85, -59, 49, 85, -58, 46, + 86, -56, 41, 86, -55, 38, 86, -54, 34, 86, -53, 30, + 86, -51, 25, 86, -49, 22, 86, -48, 18, 87, -46, 14, + 87, -44, 10, 87, -42, 5, 87, -41, 2, 87, -39, -2, + 88, -37, -6, 88, -34, -11, 88, -32, -14, 88, -30, -18, + 86, -67, 82, 86, -67, 82, 86, -67, 81, 86, -67, 80, + 86, -67, 79, 86, -66, 77, 86, -66, 75, 86, -65, 73, + 86, -65, 70, 86, -64, 67, 86, -64, 64, 86, -63, 61, + 86, -62, 58, 86, -61, 54, 86, -60, 51, 87, -59, 47, + 87, -58, 43, 87, -57, 39, 87, -56, 35, 87, -54, 31, + 87, -53, 27, 87, -51, 23, 88, -50, 19, 88, -48, 15, + 88, -46, 11, 88, -44, 7, 88, -42, 3, 88, -41, -1, + 89, -39, -5, 89, -36, -9, 89, -34, -13, 89, -32, -17, + 87, -69, 83, 87, -68, 83, 87, -68, 82, 87, -68, 81, + 87, -68, 80, 87, -68, 78, 87, -67, 76, 87, -67, 74, + 87, -66, 71, 87, -66, 69, 87, -65, 66, 87, -64, 63, + 88, -64, 59, 88, -63, 56, 88, -62, 52, 88, -61, 48, + 88, -60, 44, 88, -58, 41, 88, -57, 37, 88, -56, 33, + 88, -54, 29, 89, -53, 25, 89, -51, 21, 89, -50, 17, + 89, -48, 13, 89, -46, 9, 89, -44, 5, 90, -42, 1, + 90, -40, -3, 90, -38, -7, 90, -36, -11, 90, -34, -15, + 88, -70, 84, 88, -70, 84, 88, -70, 83, 88, -70, 82, + 88, -69, 81, 88, -69, 79, 88, -69, 77, 88, -68, 75, + 88, -68, 72, 89, -67, 70, 89, -67, 67, 89, -66, 64, + 89, -65, 60, 89, -64, 57, 89, -63, 53, 89, -62, 50, + 89, -61, 46, 89, -60, 42, 89, -59, 38, 89, -57, 35, + 90, -56, 30, 90, -54, 26, 90, -53, 22, 90, -51, 19, + 90, -50, 15, 90, -48, 10, 91, -46, 6, 91, -44, 3, + 91, -42, -1, 91, -40, -6, 91, -38, -9, 92, -36, -13, + 89, -71, 85, 89, -71, 85, 89, -71, 84, 90, -71, 83, + 90, -71, 82, 90, -70, 80, 90, -70, 78, 90, -70, 76, + 90, -69, 74, 90, -69, 71, 90, -68, 68, 90, -67, 65, + 90, -66, 62, 90, -66, 58, 90, -65, 55, 90, -64, 51, + 90, -63, 47, 90, -61, 44, 90, -60, 40, 91, -59, 36, + 91, -57, 32, 91, -56, 28, 91, -54, 24, 91, -53, 20, + 91, -51, 16, 92, -49, 12, 92, -48, 8, 92, -46, 4, + 92, -44, 0, 92, -42, -4, 93, -40, -8, 93, -38, -12, + 24, 47, 37, 24, 47, 33, 24, 47, 30, 25, 47, 24, + 25, 48, 19, 25, 48, 14, 25, 49, 8, 25, 49, 3, + 26, 50, -3, 26, 51, -7, 26, 52, -12, 27, 53, -17, + 27, 54, -22, 27, 55, -27, 28, 56, -31, 28, 57, -35, + 29, 59, -40, 30, 60, -44, 30, 61, -48, 31, 63, -52, + 32, 65, -56, 32, 66, -60, 33, 68, -64, 34, 69, -67, + 34, 71, -71, 35, 73, -75, 36, 74, -78, 37, 76, -81, + 38, 78, -84, 39, 79, -88, 39, 81, -91, 40, 83, -94, + 25, 46, 37, 25, 46, 34, 25, 46, 30, 25, 47, 24, + 25, 47, 19, 25, 47, 14, 25, 48, 9, 26, 48, 3, + 26, 49, -2, 26, 50, -7, 26, 51, -12, 27, 52, -17, + 27, 53, -22, 28, 54, -26, 28, 55, -31, 29, 57, -35, + 29, 58, -40, 30, 59, -44, 30, 61, -48, 31, 62, -52, + 32, 64, -56, 33, 66, -60, 33, 67, -63, 34, 69, -67, + 35, 70, -70, 36, 72, -74, 36, 74, -78, 37, 75, -81, + 38, 77, -84, 39, 79, -88, 40, 81, -91, 40, 82, -94, + 25, 45, 37, 25, 45, 34, 25, 45, 30, 25, 46, 25, + 25, 46, 20, 25, 47, 14, 26, 47, 9, 26, 48, 3, + 26, 48, -2, 26, 49, -7, 27, 50, -12, 27, 51, -16, + 28, 52, -22, 28, 53, -26, 28, 55, -30, 29, 56, -35, + 30, 57, -39, 30, 59, -44, 31, 60, -48, 31, 62, -51, + 32, 63, -56, 33, 65, -59, 33, 67, -63, 34, 68, -67, + 35, 70, -70, 36, 72, -74, 36, 73, -77, 37, 75, -81, + 38, 77, -84, 39, 79, -88, 40, 80, -91, 41, 82, -94, + 25, 44, 37, 25, 44, 34, 25, 45, 30, 25, 45, 25, + 26, 45, 20, 26, 46, 15, 26, 46, 10, 26, 47, 4, + 26, 48, -1, 27, 49, -6, 27, 49, -11, 27, 50, -16, + 28, 52, -21, 28, 53, -26, 29, 54, -30, 29, 55, -34, + 30, 57, -39, 30, 58, -43, 31, 60, -47, 32, 61, -51, + 32, 63, -55, 33, 64, -59, 34, 66, -63, 34, 68, -66, + 35, 69, -70, 36, 71, -74, 37, 73, -77, 37, 74, -80, + 38, 76, -84, 39, 78, -87, 40, 80, -91, 41, 82, -94, + 26, 43, 38, 26, 44, 34, 26, 44, 31, 26, 44, 25, + 26, 44, 20, 26, 45, 15, 26, 45, 10, 26, 46, 4, + 27, 47, -1, 27, 48, -6, 27, 49, -11, 28, 50, -16, + 28, 51, -21, 28, 52, -25, 29, 53, -30, 29, 54, -34, + 30, 56, -39, 31, 57, -43, 31, 59, -47, 32, 60, -51, + 32, 62, -55, 33, 64, -59, 34, 65, -62, 35, 67, -66, + 35, 69, -70, 36, 70, -73, 37, 72, -77, 38, 74, -80, + 38, 76, -83, 39, 78, -87, 40, 79, -90, 41, 81, -93, + 26, 42, 38, 26, 42, 35, 26, 43, 31, 26, 43, 26, + 26, 43, 21, 26, 44, 16, 27, 44, 10, 27, 45, 5, + 27, 46, 0, 27, 47, -5, 28, 48, -10, 28, 49, -15, + 28, 50, -20, 29, 51, -25, 29, 52, -29, 30, 54, -33, + 30, 55, -38, 31, 57, -42, 31, 58, -46, 32, 60, -50, + 33, 61, -55, 33, 63, -58, 34, 65, -62, 35, 66, -66, + 35, 68, -69, 36, 70, -73, 37, 71, -76, 38, 73, -80, + 39, 75, -83, 39, 77, -87, 40, 79, -90, 41, 80, -93, + 26, 41, 38, 26, 41, 35, 26, 41, 32, 27, 42, 26, + 27, 42, 21, 27, 43, 16, 27, 43, 11, 27, 44, 5, + 27, 45, 0, 28, 45, -5, 28, 46, -10, 28, 47, -14, + 29, 49, -20, 29, 50, -24, 30, 51, -29, 30, 52, -33, + 31, 54, -38, 31, 55, -42, 32, 57, -46, 32, 59, -50, + 33, 60, -54, 34, 62, -58, 34, 64, -61, 35, 65, -65, + 36, 67, -69, 37, 69, -73, 37, 71, -76, 38, 72, -79, + 39, 74, -83, 40, 76, -86, 41, 78, -89, 41, 80, -93, + 27, 40, 39, 27, 40, 36, 27, 40, 32, 27, 40, 27, + 27, 41, 22, 27, 41, 17, 27, 42, 12, 28, 42, 6, + 28, 43, 1, 28, 44, -4, 29, 45, -9, 29, 46, -14, + 29, 47, -19, 30, 49, -23, 30, 50, -28, 31, 51, -32, + 31, 53, -37, 32, 54, -41, 32, 56, -45, 33, 57, -49, + 33, 59, -53, 34, 61, -57, 35, 63, -61, 35, 64, -64, + 36, 66, -68, 37, 68, -72, 38, 70, -75, 38, 71, -79, + 39, 73, -82, 40, 75, -86, 41, 77, -89, 42, 79, -92, + 27, 38, 39, 27, 38, 36, 28, 38, 33, 28, 39, 27, + 28, 39, 23, 28, 40, 18, 28, 40, 12, 28, 41, 7, + 28, 42, 2, 29, 43, -3, 29, 44, -8, 29, 45, -13, + 30, 46, -18, 30, 47, -23, 31, 48, -27, 31, 50, -31, + 32, 51, -36, 32, 53, -40, 33, 55, -44, 33, 56, -48, + 34, 58, -53, 35, 60, -56, 35, 61, -60, 36, 63, -64, + 37, 65, -67, 37, 67, -71, 38, 69, -75, 39, 70, -78, + 40, 72, -82, 40, 74, -85, 41, 76, -88, 42, 78, -92, + 28, 36, 40, 28, 37, 37, 28, 37, 33, 28, 37, 28, + 28, 37, 23, 28, 38, 18, 29, 38, 13, 29, 39, 8, + 29, 40, 3, 29, 41, -2, 30, 42, -7, 30, 43, -12, + 30, 44, -17, 31, 46, -22, 31, 47, -26, 32, 48, -31, + 32, 50, -35, 33, 51, -39, 33, 53, -44, 34, 55, -48, + 34, 57, -52, 35, 58, -56, 36, 60, -59, 36, 62, -63, + 37, 64, -67, 38, 66, -71, 38, 67, -74, 39, 69, -78, + 40, 71, -81, 41, 73, -85, 42, 75, -88, 42, 77, -91, + 29, 35, 40, 29, 35, 37, 29, 35, 34, 29, 35, 29, + 29, 36, 24, 29, 36, 19, 29, 37, 14, 29, 37, 9, + 30, 38, 4, 30, 39, -1, 30, 40, -6, 31, 41, -11, + 31, 43, -16, 31, 44, -21, 32, 45, -25, 32, 47, -30, + 33, 48, -34, 33, 50, -39, 34, 52, -43, 34, 53, -47, + 35, 55, -51, 35, 57, -55, 36, 59, -59, 37, 60, -62, + 37, 62, -66, 38, 64, -70, 39, 66, -73, 40, 68, -77, + 40, 70, -80, 41, 72, -84, 42, 74, -87, 43, 76, -90, + 30, 32, 41, 30, 32, 38, 30, 32, 34, 30, 33, 30, + 30, 33, 25, 30, 34, 20, 30, 34, 15, 30, 35, 10, + 31, 36, 5, 31, 37, 0, 31, 38, -5, 31, 39, -10, + 32, 40, -15, 32, 42, -20, 33, 43, -24, 33, 44, -28, + 33, 46, -33, 34, 48, -37, 34, 49, -41, 35, 51, -45, + 36, 53, -50, 36, 55, -54, 37, 57, -58, 37, 58, -61, + 38, 60, -65, 39, 62, -69, 39, 64, -72, 40, 66, -76, + 41, 68, -79, 42, 70, -83, 42, 72, -86, 43, 74, -89, + 30, 30, 42, 30, 30, 39, 31, 30, 35, 31, 31, 31, + 31, 31, 26, 31, 32, 21, 31, 32, 16, 31, 33, 11, + 31, 34, 6, 32, 35, 1, 32, 36, -4, 32, 37, -9, + 32, 38, -14, 33, 40, -18, 33, 41, -23, 34, 43, -27, + 34, 44, -32, 35, 46, -36, 35, 48, -40, 36, 49, -44, + 36, 51, -49, 37, 53, -53, 37, 55, -57, 38, 57, -60, + 39, 59, -64, 39, 61, -68, 40, 63, -72, 41, 65, -75, + 41, 67, -78, 42, 69, -82, 43, 71, -85, 44, 73, -89, + 31, 28, 42, 31, 28, 39, 31, 28, 36, 31, 29, 31, + 32, 29, 27, 32, 30, 22, 32, 30, 17, 32, 31, 12, + 32, 32, 7, 32, 33, 2, 33, 34, -3, 33, 35, -7, + 33, 36, -13, 34, 38, -17, 34, 39, -22, 34, 41, -26, + 35, 42, -31, 35, 44, -35, 36, 46, -39, 36, 47, -43, + 37, 49, -48, 37, 51, -52, 38, 53, -56, 39, 55, -59, + 39, 57, -63, 40, 59, -67, 41, 61, -71, 41, 63, -74, + 42, 65, -78, 43, 67, -81, 43, 69, -85, 44, 71, -88, + 32, 26, 43, 32, 26, 40, 32, 26, 37, 32, 26, 32, + 32, 27, 28, 32, 27, 23, 33, 28, 19, 33, 29, 13, + 33, 30, 8, 33, 31, 3, 33, 32, -2, 34, 33, -6, + 34, 34, -11, 34, 36, -16, 35, 37, -21, 35, 39, -25, + 36, 40, -30, 36, 42, -34, 37, 44, -38, 37, 46, -42, + 38, 48, -47, 38, 49, -51, 39, 51, -54, 39, 53, -58, + 40, 55, -62, 41, 57, -66, 41, 59, -70, 42, 61, -73, + 43, 63, -77, 43, 66, -80, 44, 68, -84, 45, 70, -87, + 33, 23, 44, 33, 24, 40, 33, 24, 37, 33, 24, 33, + 33, 25, 29, 33, 25, 24, 33, 26, 20, 34, 27, 14, + 34, 27, 9, 34, 28, 5, 34, 29, 0, 35, 31, -5, + 35, 32, -10, 35, 33, -15, 36, 35, -19, 36, 36, -24, + 36, 38, -29, 37, 40, -33, 37, 42, -37, 38, 43, -41, + 38, 46, -46, 39, 47, -49, 39, 49, -53, 40, 51, -57, + 41, 53, -61, 41, 56, -65, 42, 58, -69, 42, 60, -72, + 43, 62, -76, 44, 64, -79, 45, 66, -83, 45, 68, -86, + 34, 21, 44, 34, 21, 41, 34, 22, 38, 34, 22, 34, + 34, 22, 30, 34, 23, 26, 34, 23, 21, 35, 24, 15, + 35, 25, 11, 35, 26, 6, 35, 27, 1, 35, 28, -4, + 36, 30, -9, 36, 31, -13, 36, 33, -18, 37, 34, -22, + 37, 36, -27, 38, 38, -32, 38, 40, -36, 39, 41, -40, + 39, 43, -44, 40, 45, -48, 40, 47, -52, 41, 49, -56, + 41, 51, -60, 42, 54, -64, 43, 56, -67, 43, 58, -71, + 44, 60, -74, 45, 62, -78, 45, 64, -82, 46, 66, -85, + 35, 19, 45, 35, 19, 42, 35, 19, 39, 35, 20, 35, + 35, 20, 31, 35, 20, 27, 35, 21, 22, 36, 22, 17, + 36, 23, 12, 36, 24, 7, 36, 25, 2, 36, 26, -2, + 37, 28, -8, 37, 29, -12, 37, 30, -17, 38, 32, -21, + 38, 34, -26, 38, 36, -30, 39, 37, -34, 39, 39, -38, + 40, 41, -43, 40, 43, -47, 41, 45, -51, 41, 47, -55, + 42, 49, -58, 43, 52, -63, 43, 54, -66, 44, 56, -70, + 44, 58, -73, 45, 60, -77, 46, 62, -81, 47, 64, -84, + 36, 16, 45, 36, 17, 43, 36, 17, 40, 36, 17, 36, + 36, 18, 32, 36, 18, 28, 36, 19, 23, 37, 20, 18, + 37, 20, 13, 37, 21, 9, 37, 23, 4, 37, 24, -1, + 38, 25, -6, 38, 27, -11, 38, 28, -15, 39, 30, -20, + 39, 32, -25, 39, 33, -29, 40, 35, -33, 40, 37, -37, + 41, 39, -42, 41, 41, -46, 42, 43, -50, 42, 45, -53, + 43, 47, -57, 43, 49, -61, 44, 52, -65, 45, 54, -69, + 45, 56, -72, 46, 58, -76, 47, 60, -80, 47, 63, -83, + 37, 14, 46, 37, 14, 43, 37, 14, 41, 37, 15, 37, + 37, 15, 33, 37, 16, 29, 37, 16, 25, 38, 17, 19, + 38, 18, 15, 38, 19, 10, 38, 20, 5, 38, 21, 1, + 39, 23, -5, 39, 24, -9, 39, 26, -14, 39, 27, -18, + 40, 29, -23, 40, 31, -27, 41, 33, -32, 41, 35, -36, + 42, 37, -40, 42, 39, -44, 43, 41, -48, 43, 43, -52, + 44, 45, -56, 44, 47, -60, 45, 49, -64, 45, 52, -67, + 46, 54, -71, 47, 56, -75, 47, 58, -78, 48, 61, -82, + 38, 12, 47, 38, 12, 44, 38, 12, 42, 38, 12, 38, + 38, 13, 34, 38, 13, 30, 38, 14, 26, 39, 15, 21, + 39, 16, 16, 39, 17, 11, 39, 18, 7, 39, 19, 2, + 40, 20, -3, 40, 22, -8, 40, 23, -12, 40, 25, -17, + 41, 27, -22, 41, 29, -26, 42, 30, -30, 42, 32, -34, + 42, 35, -39, 43, 37, -43, 43, 39, -47, 44, 41, -51, + 44, 43, -55, 45, 45, -59, 46, 47, -62, 46, 49, -66, + 47, 52, -70, 47, 54, -74, 48, 56, -77, 49, 59, -81, + 39, 9, 47, 39, 9, 45, 39, 10, 43, 39, 10, 39, + 39, 10, 35, 39, 11, 31, 39, 12, 27, 40, 12, 22, + 40, 13, 17, 40, 14, 13, 40, 15, 8, 40, 17, 3, + 41, 18, -2, 41, 19, -6, 41, 21, -11, 41, 23, -15, + 42, 24, -20, 42, 26, -24, 43, 28, -29, 43, 30, -33, + 43, 32, -37, 44, 34, -41, 44, 36, -45, 45, 38, -49, + 45, 41, -53, 46, 43, -57, 46, 45, -61, 47, 47, -65, + 48, 50, -68, 48, 52, -72, 49, 54, -76, 49, 56, -79, + 40, 7, 48, 40, 7, 46, 40, 7, 44, 40, 8, 40, + 40, 8, 37, 40, 9, 33, 41, 9, 28, 41, 10, 23, + 41, 11, 19, 41, 12, 14, 41, 13, 10, 41, 14, 5, + 42, 16, 0, 42, 17, -5, 42, 19, -9, 42, 20, -14, + 43, 22, -19, 43, 24, -23, 43, 26, -27, 44, 28, -31, + 44, 30, -36, 45, 32, -40, 45, 34, -44, 46, 36, -48, + 46, 38, -52, 47, 41, -56, 47, 43, -60, 48, 45, -63, + 48, 47, -67, 49, 50, -71, 50, 52, -75, 50, 54, -78, + 41, 5, 49, 41, 5, 47, 41, 5, 45, 41, 5, 41, + 41, 6, 38, 42, 6, 34, 42, 7, 30, 42, 8, 25, + 42, 8, 20, 42, 9, 16, 42, 11, 11, 42, 12, 6, + 43, 13, 1, 43, 15, -3, 43, 16, -8, 43, 18, -12, + 44, 20, -17, 44, 21, -21, 44, 23, -26, 45, 25, -30, + 45, 27, -34, 46, 30, -39, 46, 32, -42, 47, 34, -46, + 47, 36, -50, 48, 38, -55, 48, 41, -58, 49, 43, -62, + 49, 45, -66, 50, 48, -70, 50, 50, -73, 51, 52, -77, + 42, 2, 50, 42, 2, 48, 42, 3, 46, 43, 3, 42, + 43, 3, 39, 43, 4, 35, 43, 4, 31, 43, 5, 26, + 43, 6, 22, 43, 7, 17, 43, 8, 13, 44, 9, 8, + 44, 11, 3, 44, 12, -2, 44, 14, -6, 45, 15, -11, + 45, 17, -15, 45, 19, -20, 45, 21, -24, 46, 23, -28, + 46, 25, -33, 47, 27, -37, 47, 29, -41, 48, 31, -45, + 48, 34, -49, 49, 36, -53, 49, 38, -57, 50, 41, -61, + 50, 43, -64, 51, 45, -68, 51, 48, -72, 52, 50, -75, + 44, 0, 50, 44, 0, 49, 44, 0, 46, 44, 1, 43, + 44, 1, 40, 44, 1, 36, 44, 2, 32, 44, 3, 27, + 44, 4, 23, 44, 5, 19, 44, 6, 14, 45, 7, 10, + 45, 8, 4, 45, 10, 0, 45, 11, -5, 46, 13, -9, + 46, 15, -14, 46, 17, -18, 47, 19, -22, 47, 20, -27, + 47, 23, -31, 48, 25, -35, 48, 27, -39, 49, 29, -43, + 49, 31, -47, 49, 34, -52, 50, 36, -55, 50, 38, -59, + 51, 41, -63, 52, 43, -67, 52, 45, -71, 53, 48, -74, + 45, -2, 51, 45, -2, 49, 45, -2, 47, 45, -2, 44, + 45, -1, 41, 45, -1, 38, 45, 0, 34, 45, 1, 29, + 45, 1, 25, 45, 2, 20, 46, 3, 16, 46, 5, 11, + 46, 6, 6, 46, 7, 2, 46, 9, -3, 47, 11, -7, + 47, 12, -12, 47, 14, -17, 48, 16, -21, 48, 18, -25, + 48, 20, -30, 49, 22, -34, 49, 24, -38, 49, 27, -42, + 50, 29, -46, 50, 31, -50, 51, 34, -54, 51, 36, -58, + 52, 38, -61, 52, 41, -65, 53, 43, -69, 54, 45, -73, + 46, -5, 52, 46, -5, 50, 46, -4, 48, 46, -4, 46, + 46, -4, 42, 46, -3, 39, 46, -3, 35, 46, -2, 30, + 46, -1, 26, 47, 0, 22, 47, 1, 17, 47, 2, 13, + 47, 4, 8, 47, 5, 3, 47, 7, -1, 48, 8, -6, + 48, 10, -11, 48, 12, -15, 49, 14, -19, 49, 16, -23, + 49, 18, -28, 50, 20, -32, 50, 22, -36, 50, 24, -40, + 51, 26, -44, 51, 29, -49, 52, 31, -52, 52, 34, -56, + 53, 36, -60, 53, 38, -64, 54, 41, -68, 54, 43, -71, + 47, -7, 53, 47, -7, 51, 47, -7, 49, 47, -6, 47, + 47, -6, 44, 47, -5, 40, 47, -5, 36, 47, -4, 32, + 48, -3, 27, 48, -2, 23, 48, -1, 19, 48, 0, 14, + 48, 1, 9, 48, 3, 5, 49, 4, 0, 49, 6, -4, + 49, 8, -9, 49, 9, -13, 50, 11, -18, 50, 13, -22, + 50, 16, -27, 51, 18, -31, 51, 20, -35, 51, 22, -39, + 52, 24, -43, 52, 27, -47, 53, 29, -51, 53, 31, -55, + 54, 33, -58, 54, 36, -63, 55, 38, -66, 55, 41, -70, + 48, -9, 54, 48, -9, 52, 48, -9, 50, 48, -9, 48, + 48, -8, 45, 48, -8, 41, 48, -7, 38, 49, -6, 33, + 49, -5, 29, 49, -5, 25, 49, -4, 20, 49, -2, 16, + 49, -1, 11, 49, 0, 6, 50, 2, 2, 50, 3, -2, + 50, 5, -7, 50, 7, -12, 51, 9, -16, 51, 11, -20, + 51, 13, -25, 52, 15, -29, 52, 17, -33, 53, 19, -37, + 53, 22, -41, 53, 24, -45, 54, 26, -49, 54, 29, -53, + 55, 31, -57, 55, 34, -61, 56, 36, -65, 56, 39, -68, + 49, -11, 55, 49, -11, 53, 49, -11, 51, 49, -11, 49, + 49, -10, 46, 50, -10, 43, 50, -9, 39, 50, -9, 34, + 50, -8, 30, 50, -7, 26, 50, -6, 22, 50, -5, 17, + 50, -3, 12, 51, -2, 8, 51, 0, 4, 51, 1, -1, + 51, 3, -6, 52, 5, -10, 52, 7, -14, 52, 9, -19, + 53, 11, -23, 53, 13, -27, 53, 15, -31, 54, 17, -35, + 54, 19, -39, 54, 22, -44, 55, 24, -48, 55, 26, -51, + 56, 29, -55, 56, 31, -59, 57, 34, -63, 57, 36, -67, + 51, -14, 56, 51, -13, 54, 51, -13, 52, 51, -13, 50, + 51, -13, 47, 51, -12, 44, 51, -12, 40, 51, -11, 36, + 51, -10, 32, 51, -9, 28, 51, -8, 23, 51, -7, 19, + 52, -6, 14, 52, -4, 10, 52, -3, 5, 52, -1, 1, + 52, 1, -4, 53, 2, -8, 53, 4, -13, 53, 6, -17, + 54, 8, -22, 54, 10, -26, 54, 13, -30, 55, 15, -34, + 55, 17, -38, 55, 19, -42, 56, 22, -46, 56, 24, -50, + 57, 26, -54, 57, 29, -58, 58, 31, -62, 58, 34, -65, + 52, -16, 57, 52, -16, 55, 52, -16, 54, 52, -16, 51, + 52, -15, 48, 52, -15, 45, 52, -14, 42, 52, -13, 38, + 52, -13, 34, 53, -12, 29, 53, -11, 25, 53, -10, 21, + 53, -8, 16, 53, -7, 12, 53, -6, 7, 54, -4, 3, + 54, -2, -2, 54, 0, -6, 54, 1, -11, 55, 3, -15, + 55, 5, -19, 55, 7, -24, 56, 10, -28, 56, 12, -32, + 56, 14, -36, 57, 16, -40, 57, 19, -44, 58, 21, -48, + 58, 23, -52, 58, 26, -56, 59, 28, -60, 59, 31, -63, + 53, -18, 57, 53, -18, 56, 53, -18, 55, 53, -18, 52, + 53, -17, 50, 53, -17, 46, 53, -16, 43, 54, -16, 39, + 54, -15, 35, 54, -14, 31, 54, -13, 27, 54, -12, 22, + 54, -11, 18, 54, -9, 13, 55, -8, 9, 55, -6, 5, + 55, -4, 0, 55, -3, -5, 55, -1, -9, 56, 1, -13, + 56, 3, -18, 56, 5, -22, 57, 7, -26, 57, 9, -30, + 57, 12, -34, 58, 14, -38, 58, 16, -42, 59, 19, -46, + 59, 21, -50, 59, 24, -54, 60, 26, -58, 60, 29, -62, + 54, -20, 58, 54, -20, 57, 54, -20, 56, 55, -20, 53, + 55, -19, 51, 55, -19, 48, 55, -18, 44, 55, -18, 40, + 55, -17, 36, 55, -16, 32, 55, -15, 28, 55, -14, 24, + 55, -13, 19, 56, -11, 15, 56, -10, 11, 56, -8, 6, + 56, -7, 1, 56, -5, -3, 57, -3, -7, 57, -1, -11, + 57, 1, -16, 57, 3, -20, 58, 5, -24, 58, 7, -28, + 58, 9, -32, 59, 12, -37, 59, 14, -41, 60, 16, -45, + 60, 19, -48, 60, 21, -53, 61, 24, -56, 61, 26, -60, + 56, -23, 59, 56, -22, 58, 56, -22, 57, 56, -22, 54, + 56, -22, 52, 56, -21, 49, 56, -21, 46, 56, -20, 42, + 56, -19, 38, 56, -18, 34, 56, -17, 30, 56, -16, 26, + 57, -15, 21, 57, -14, 16, 57, -12, 12, 57, -11, 8, + 57, -9, 3, 58, -7, -1, 58, -5, -5, 58, -4, -10, + 58, -1, -14, 59, 1, -19, 59, 3, -23, 59, 5, -27, + 60, 7, -31, 60, 9, -35, 60, 12, -39, 61, 14, -43, + 61, 16, -47, 61, 19, -51, 62, 21, -55, 62, 24, -58, + 57, -25, 60, 57, -24, 59, 57, -24, 58, 57, -24, 55, + 57, -24, 53, 57, -23, 50, 57, -23, 47, 57, -22, 43, + 57, -21, 39, 57, -20, 35, 57, -19, 31, 58, -18, 27, + 58, -17, 22, 58, -16, 18, 58, -14, 14, 58, -13, 9, + 58, -11, 5, 59, -9, 0, 59, -8, -4, 59, -6, -8, + 59, -4, -13, 60, -2, -17, 60, 0, -21, 60, 2, -25, + 61, 5, -29, 61, 7, -33, 61, 9, -37, 62, 12, -41, + 62, 14, -45, 63, 17, -49, 63, 19, -53, 63, 21, -57, + 58, -26, 61, 58, -26, 60, 58, -26, 59, 58, -26, 56, + 58, -26, 54, 58, -25, 51, 58, -25, 48, 58, -24, 44, + 58, -23, 41, 59, -22, 37, 59, -21, 33, 59, -20, 29, + 59, -19, 24, 59, -18, 20, 59, -16, 15, 59, -15, 11, + 60, -13, 6, 60, -12, 2, 60, -10, -2, 60, -8, -6, + 61, -6, -11, 61, -4, -15, 61, -2, -19, 61, 0, -23, + 62, 2, -27, 62, 5, -32, 62, 7, -36, 63, 9, -40, + 63, 12, -43, 64, 14, -48, 64, 17, -52, 64, 19, -55, + 59, -28, 62, 59, -28, 61, 59, -28, 60, 59, -28, 58, + 59, -27, 55, 59, -27, 53, 59, -27, 49, 60, -26, 46, + 60, -25, 42, 60, -24, 38, 60, -23, 34, 60, -22, 30, + 60, -21, 25, 60, -20, 21, 60, -19, 17, 61, -17, 13, + 61, -15, 8, 61, -14, 4, 61, -12, 0, 61, -10, -5, + 62, -8, -9, 62, -6, -13, 62, -4, -18, 63, -2, -22, + 63, 0, -26, 63, 2, -30, 63, 5, -34, 64, 7, -38, + 64, 9, -42, 65, 12, -46, 65, 14, -50, 65, 17, -54, + 60, -30, 63, 61, -30, 62, 61, -30, 61, 61, -30, 59, + 61, -29, 56, 61, -29, 54, 61, -28, 51, 61, -28, 47, + 61, -27, 43, 61, -26, 40, 61, -25, 36, 61, -24, 32, + 61, -23, 27, 61, -22, 23, 62, -21, 19, 62, -19, 14, + 62, -17, 10, 62, -16, 5, 62, -14, 1, 63, -12, -3, + 63, -10, -8, 63, -8, -12, 63, -6, -16, 64, -4, -20, + 64, -2, -24, 64, 0, -28, 65, 2, -32, 65, 5, -36, + 65, 7, -40, 66, 10, -44, 66, 12, -48, 66, 14, -52, + 62, -32, 64, 62, -32, 63, 62, -32, 62, 62, -32, 60, + 62, -31, 57, 62, -31, 55, 62, -30, 52, 62, -30, 48, + 62, -29, 45, 62, -28, 41, 62, -27, 37, 62, -26, 33, + 62, -25, 29, 63, -24, 24, 63, -23, 20, 63, -21, 16, + 63, -20, 11, 63, -18, 7, 63, -16, 3, 64, -15, -1, + 64, -12, -6, 64, -11, -10, 64, -9, -14, 65, -7, -18, + 65, -4, -22, 65, -2, -27, 66, 0, -31, 66, 2, -35, + 66, 5, -38, 67, 7, -43, 67, 10, -47, 67, 12, -50, + 63, -34, 65, 63, -34, 64, 63, -34, 63, 63, -33, 61, + 63, -33, 59, 63, -33, 56, 63, -32, 53, 63, -32, 50, + 63, -31, 46, 63, -30, 42, 63, -29, 39, 64, -28, 35, + 64, -27, 30, 64, -26, 26, 64, -25, 22, 64, -23, 18, + 64, -22, 13, 64, -20, 9, 65, -18, 5, 65, -17, 0, + 65, -15, -4, 65, -13, -8, 66, -11, -12, 66, -9, -16, + 66, -7, -20, 66, -4, -25, 67, -2, -29, 67, 0, -33, + 67, 2, -37, 68, 5, -41, 68, 7, -45, 69, 10, -49, + 64, -36, 66, 64, -36, 65, 64, -36, 64, 64, -35, 62, + 64, -35, 60, 64, -35, 57, 64, -34, 54, 64, -33, 51, + 64, -33, 47, 65, -32, 44, 65, -31, 40, 65, -30, 36, + 65, -29, 32, 65, -28, 28, 65, -27, 23, 65, -25, 19, + 65, -24, 15, 66, -22, 10, 66, -20, 6, 66, -19, 2, + 66, -17, -3, 67, -15, -7, 67, -13, -11, 67, -11, -15, + 67, -9, -19, 68, -6, -23, 68, -4, -27, 68, -2, -31, + 69, 0, -35, 69, 3, -39, 69, 5, -43, 70, 8, -47, + 65, -38, 67, 65, -38, 66, 65, -37, 65, 65, -37, 63, + 65, -37, 61, 65, -36, 58, 66, -36, 56, 66, -35, 52, + 66, -35, 49, 66, -34, 45, 66, -33, 41, 66, -32, 38, + 66, -31, 33, 66, -30, 29, 66, -29, 25, 66, -27, 21, + 67, -26, 16, 67, -24, 12, 67, -22, 8, 67, -21, 4, + 67, -19, -1, 68, -17, -5, 68, -15, -9, 68, -13, -13, + 68, -11, -17, 69, -9, -22, 69, -6, -26, 69, -4, -29, + 70, -2, -33, 70, 1, -38, 70, 3, -42, 71, 5, -45, + 67, -39, 68, 67, -39, 67, 67, -39, 66, 67, -39, 64, + 67, -39, 62, 67, -38, 60, 67, -38, 57, 67, -37, 53, + 67, -36, 50, 67, -36, 47, 67, -35, 43, 67, -34, 39, + 67, -33, 35, 67, -32, 31, 67, -30, 27, 68, -29, 23, + 68, -28, 18, 68, -26, 14, 68, -24, 10, 68, -23, 6, + 69, -21, 1, 69, -19, -3, 69, -17, -7, 69, -15, -11, + 70, -13, -15, 70, -11, -20, 70, -9, -24, 70, -6, -28, + 71, -4, -32, 71, -2, -36, 71, 1, -40, 72, 3, -44, + 68, -41, 69, 68, -41, 68, 68, -41, 67, 68, -41, 65, + 68, -40, 63, 68, -40, 61, 68, -40, 58, 68, -39, 55, + 68, -38, 51, 68, -38, 48, 68, -37, 44, 68, -36, 41, + 68, -35, 36, 69, -34, 32, 69, -32, 28, 69, -31, 24, + 69, -29, 20, 69, -28, 15, 69, -26, 11, 70, -25, 7, + 70, -23, 3, 70, -21, -2, 70, -19, -6, 70, -17, -10, + 71, -15, -14, 71, -13, -18, 71, -11, -22, 72, -9, -26, + 72, -6, -30, 72, -4, -34, 72, -2, -38, 73, 1, -42, + 69, -43, 69, 69, -43, 69, 69, -43, 68, 69, -42, 66, + 69, -42, 64, 69, -42, 62, 69, -41, 59, 69, -41, 56, + 69, -40, 53, 69, -39, 49, 69, -39, 46, 70, -38, 42, + 70, -36, 38, 70, -35, 34, 70, -34, 30, 70, -33, 26, + 70, -31, 21, 70, -30, 17, 71, -28, 13, 71, -27, 9, + 71, -25, 4, 71, -23, 0, 71, -21, -4, 72, -19, -8, + 72, -17, -12, 72, -15, -16, 72, -13, -20, 73, -11, -24, + 73, -9, -28, 73, -6, -33, 74, -4, -36, 74, -1, -40, + 70, -45, 70, 70, -44, 69, 70, -44, 69, 70, -44, 67, + 70, -44, 65, 70, -43, 63, 70, -43, 60, 70, -42, 57, + 70, -42, 54, 71, -41, 51, 71, -40, 47, 71, -39, 43, + 71, -38, 39, 71, -37, 35, 71, -36, 31, 71, -35, 27, + 71, -33, 23, 72, -32, 19, 72, -30, 15, 72, -29, 11, + 72, -27, 6, 72, -25, 2, 72, -23, -2, 73, -21, -6, + 73, -19, -10, 73, -17, -15, 73, -15, -19, 74, -13, -23, + 74, -11, -27, 74, -8, -31, 75, -6, -35, 75, -4, -39, + 71, -46, 71, 71, -46, 70, 71, -46, 69, 71, -46, 68, + 71, -45, 66, 72, -45, 64, 72, -45, 62, 72, -44, 58, + 72, -44, 55, 72, -43, 52, 72, -42, 49, 72, -41, 45, + 72, -40, 41, 72, -39, 37, 72, -38, 33, 72, -37, 29, + 73, -35, 24, 73, -34, 20, 73, -32, 16, 73, -31, 12, + 73, -29, 8, 73, -27, 4, 74, -25, -1, 74, -23, -5, + 74, -21, -9, 74, -19, -13, 75, -17, -17, 75, -15, -21, + 75, -13, -25, 75, -10, -29, 76, -8, -33, 76, -6, -37, + 73, -48, 72, 73, -48, 71, 73, -48, 70, 73, -47, 69, + 73, -47, 67, 73, -47, 65, 73, -46, 63, 73, -46, 60, + 73, -45, 57, 73, -45, 53, 73, -44, 50, 73, -43, 46, + 73, -42, 42, 73, -41, 38, 73, -40, 34, 74, -38, 30, + 74, -37, 26, 74, -36, 22, 74, -34, 18, 74, -33, 14, + 74, -31, 9, 75, -29, 5, 75, -27, 1, 75, -25, -3, + 75, -23, -7, 75, -21, -11, 76, -19, -15, 76, -17, -19, + 76, -15, -23, 77, -12, -27, 77, -10, -31, 77, -8, -35, + 74, -50, 73, 74, -49, 72, 74, -49, 71, 74, -49, 70, + 74, -49, 68, 74, -48, 66, 74, -48, 64, 74, -47, 61, + 74, -47, 58, 74, -46, 55, 74, -45, 51, 74, -45, 48, + 74, -44, 44, 75, -43, 40, 75, -41, 36, 75, -40, 32, + 75, -39, 28, 75, -37, 24, 75, -36, 19, 75, -34, 15, + 76, -33, 11, 76, -31, 7, 76, -29, 3, 76, -27, -1, + 76, -25, -5, 77, -23, -10, 77, -21, -14, 77, -19, -18, + 77, -17, -21, 78, -15, -26, 78, -12, -30, 78, -10, -33, + 75, -51, 74, 75, -51, 73, 75, -51, 72, 75, -51, 71, + 75, -50, 69, 75, -50, 67, 75, -50, 65, 75, -49, 62, + 75, -49, 59, 75, -48, 56, 75, -47, 53, 76, -46, 49, + 76, -45, 45, 76, -44, 41, 76, -43, 37, 76, -42, 34, + 76, -41, 29, 76, -39, 25, 76, -38, 21, 77, -36, 17, + 77, -34, 13, 77, -33, 9, 77, -31, 5, 77, -29, 1, + 78, -27, -3, 78, -25, -8, 78, -23, -12, 78, -21, -16, + 79, -19, -20, 79, -17, -24, 79, -14, -28, 79, -12, -32, + 76, -53, 75, 76, -53, 74, 76, -52, 73, 76, -52, 72, + 76, -52, 70, 76, -52, 68, 76, -51, 66, 76, -51, 63, + 76, -50, 60, 77, -49, 57, 77, -49, 54, 77, -48, 51, + 77, -47, 46, 77, -46, 43, 77, -45, 39, 77, -44, 35, + 77, -42, 31, 77, -41, 27, 78, -40, 23, 78, -38, 19, + 78, -36, 14, 78, -35, 10, 78, -33, 6, 78, -31, 2, + 79, -29, -2, 79, -27, -6, 79, -25, -10, 79, -23, -14, + 80, -21, -18, 80, -19, -22, 80, -16, -26, 80, -14, -30, + 78, -55, 76, 78, -55, 75, 78, -54, 75, 78, -54, 73, + 78, -54, 72, 78, -54, 70, 78, -53, 68, 78, -53, 65, + 78, -52, 62, 78, -52, 59, 78, -51, 56, 78, -50, 52, + 78, -49, 48, 78, -48, 45, 78, -47, 41, 79, -46, 37, + 79, -44, 33, 79, -43, 29, 79, -42, 25, 79, -40, 21, + 79, -39, 16, 80, -37, 12, 80, -35, 8, 80, -34, 4, + 80, -32, 0, 80, -30, -4, 81, -28, -8, 81, -26, -12, + 81, -24, -16, 81, -21, -20, 82, -19, -24, 82, -17, -28, + 79, -56, 77, 79, -56, 76, 79, -56, 76, 79, -56, 74, + 79, -55, 73, 79, -55, 71, 79, -55, 69, 79, -54, 66, + 79, -54, 63, 79, -53, 60, 79, -52, 57, 79, -52, 54, + 79, -51, 50, 80, -50, 46, 80, -49, 42, 80, -48, 38, + 80, -46, 34, 80, -45, 30, 80, -44, 26, 80, -42, 22, + 81, -40, 18, 81, -39, 14, 81, -37, 10, 81, -35, 6, + 81, -34, 2, 81, -31, -2, 82, -30, -6, 82, -28, -10, + 82, -26, -14, 82, -23, -19, 83, -21, -22, 83, -19, -26, + 80, -58, 78, 80, -58, 77, 80, -57, 77, 80, -57, 75, + 80, -57, 74, 80, -57, 72, 80, -56, 70, 80, -56, 67, + 80, -55, 64, 80, -55, 61, 81, -54, 58, 81, -53, 55, + 81, -52, 51, 81, -51, 47, 81, -50, 44, 81, -49, 40, + 81, -48, 36, 81, -47, 32, 81, -45, 28, 82, -44, 24, + 82, -42, 19, 82, -41, 16, 82, -39, 12, 82, -37, 8, + 82, -35, 4, 83, -33, -1, 83, -31, -5, 83, -29, -9, + 83, -27, -13, 84, -25, -17, 84, -23, -21, 84, -21, -25, + 81, -59, 79, 81, -59, 78, 81, -59, 78, 81, -59, 76, + 81, -59, 75, 81, -58, 73, 81, -58, 71, 82, -57, 68, + 82, -57, 66, 82, -56, 63, 82, -56, 60, 82, -55, 56, + 82, -54, 52, 82, -53, 49, 82, -52, 45, 82, -51, 41, + 82, -50, 37, 82, -48, 33, 83, -47, 29, 83, -46, 26, + 83, -44, 21, 83, -42, 17, 83, -41, 13, 83, -39, 9, + 84, -37, 5, 84, -35, 1, 84, -33, -3, 84, -31, -7, + 84, -29, -11, 85, -27, -15, 85, -25, -19, 85, -23, -23, + 83, -61, 80, 83, -61, 79, 83, -60, 79, 83, -60, 77, + 83, -60, 76, 83, -60, 74, 83, -59, 72, 83, -59, 69, + 83, -58, 67, 83, -58, 64, 83, -57, 61, 83, -56, 58, + 83, -55, 54, 83, -55, 50, 83, -54, 47, 83, -52, 43, + 83, -51, 39, 84, -50, 35, 84, -49, 31, 84, -47, 27, + 84, -46, 23, 84, -44, 19, 84, -43, 15, 85, -41, 11, + 85, -39, 7, 85, -37, 3, 85, -35, -1, 85, -33, -5, + 86, -31, -9, 86, -29, -14, 86, -27, -17, 86, -25, -21, + 84, -62, 81, 84, -62, 80, 84, -62, 80, 84, -62, 78, + 84, -61, 77, 84, -61, 75, 84, -61, 73, 84, -60, 71, + 84, -60, 68, 84, -59, 65, 84, -59, 62, 84, -58, 59, + 84, -57, 55, 84, -56, 52, 84, -55, 48, 85, -54, 44, + 85, -53, 40, 85, -52, 36, 85, -50, 33, 85, -49, 29, + 85, -47, 24, 85, -46, 20, 85, -44, 16, 86, -43, 13, + 86, -41, 9, 86, -39, 4, 86, -37, 0, 86, -35, -4, + 87, -33, -7, 87, -31, -12, 87, -29, -16, 87, -27, -20, + 85, -64, 82, 85, -63, 81, 85, -63, 81, 85, -63, 79, + 85, -63, 78, 85, -63, 76, 85, -62, 74, 85, -62, 72, + 85, -61, 69, 85, -61, 66, 85, -60, 63, 85, -59, 60, + 85, -59, 57, 86, -58, 53, 86, -57, 50, 86, -56, 46, + 86, -54, 42, 86, -53, 38, 86, -52, 34, 86, -51, 30, + 86, -49, 26, 86, -48, 22, 87, -46, 18, 87, -44, 14, + 87, -43, 10, 87, -41, 6, 87, -39, 2, 88, -37, -2, + 88, -35, -6, 88, -33, -10, 88, -31, -14, 89, -29, -18, + 86, -65, 83, 86, -65, 82, 86, -65, 81, 86, -65, 80, + 86, -64, 79, 86, -64, 77, 86, -64, 75, 86, -63, 73, + 86, -63, 70, 86, -62, 68, 86, -62, 65, 87, -61, 62, + 87, -60, 58, 87, -59, 55, 87, -58, 51, 87, -57, 47, + 87, -56, 43, 87, -55, 39, 87, -54, 36, 87, -52, 32, + 87, -51, 27, 88, -49, 24, 88, -48, 20, 88, -46, 16, + 88, -44, 12, 88, -42, 7, 89, -41, 4, 89, -39, 0, + 89, -37, -4, 89, -35, -8, 89, -33, -12, 90, -31, -16, + 87, -66, 84, 87, -66, 83, 87, -66, 82, 87, -66, 81, + 87, -66, 80, 87, -66, 78, 87, -65, 77, 88, -65, 74, + 88, -64, 72, 88, -64, 69, 88, -63, 66, 88, -62, 63, + 88, -62, 59, 88, -61, 56, 88, -60, 52, 88, -59, 49, + 88, -58, 45, 88, -56, 41, 88, -55, 37, 89, -54, 33, + 89, -52, 29, 89, -51, 25, 89, -49, 21, 89, -48, 17, + 89, -46, 14, 89, -44, 9, 90, -42, 5, 90, -41, 1, + 90, -39, -2, 90, -37, -7, 91, -35, -11, 91, -32, -14, + 89, -68, 85, 89, -68, 84, 89, -68, 83, 89, -67, 82, + 89, -67, 81, 89, -67, 79, 89, -67, 78, 89, -66, 75, + 89, -66, 73, 89, -65, 70, 89, -65, 67, 89, -64, 64, + 89, -63, 61, 89, -62, 57, 89, -61, 54, 89, -60, 50, + 89, -59, 46, 89, -58, 42, 90, -57, 39, 90, -55, 35, + 90, -54, 31, 90, -53, 27, 90, -51, 23, 90, -49, 19, + 90, -48, 15, 91, -46, 11, 91, -44, 7, 91, -42, 3, + 91, -41, -1, 91, -38, -5, 92, -36, -9, 92, -34, -13, + 90, -69, 86, 90, -69, 85, 90, -69, 84, 90, -69, 83, + 90, -69, 82, 90, -68, 81, 90, -68, 79, 90, -68, 76, + 90, -67, 74, 90, -67, 71, 90, -66, 68, 90, -65, 65, + 90, -64, 62, 90, -64, 59, 90, -63, 55, 90, -62, 52, + 91, -61, 48, 91, -60, 44, 91, -58, 40, 91, -57, 36, + 91, -56, 32, 91, -54, 28, 91, -53, 24, 91, -51, 21, + 92, -50, 17, 92, -48, 12, 92, -46, 9, 92, -44, 5, + 92, -42, 1, 93, -40, -4, 93, -38, -7, 93, -36, -11, + 26, 49, 39, 27, 49, 36, 27, 49, 32, 27, 50, 27, + 27, 50, 22, 27, 50, 17, 27, 51, 12, 27, 51, 6, + 28, 52, 1, 28, 53, -4, 28, 54, -9, 28, 55, -14, + 29, 56, -19, 29, 57, -24, 30, 58, -28, 30, 59, -33, + 31, 60, -37, 31, 62, -41, 32, 63, -45, 32, 64, -49, + 33, 66, -54, 34, 67, -58, 34, 69, -61, 35, 70, -65, + 36, 72, -68, 37, 74, -72, 37, 75, -76, 38, 77, -79, + 39, 78, -82, 40, 80, -86, 41, 82, -89, 41, 83, -92, + 27, 48, 39, 27, 49, 36, 27, 49, 33, 27, 49, 27, + 27, 49, 22, 27, 50, 17, 27, 50, 12, 28, 51, 6, + 28, 51, 1, 28, 52, -4, 28, 53, -9, 29, 54, -14, + 29, 55, -19, 30, 56, -23, 30, 57, -28, 30, 58, -32, + 31, 60, -37, 32, 61, -41, 32, 62, -45, 33, 64, -49, + 33, 65, -53, 34, 67, -57, 35, 68, -61, 35, 70, -65, + 36, 71, -68, 37, 73, -72, 38, 75, -76, 38, 76, -79, + 39, 78, -82, 40, 80, -86, 41, 81, -89, 42, 83, -92, + 27, 48, 40, 27, 48, 36, 27, 48, 33, 27, 48, 28, + 27, 49, 23, 27, 49, 17, 28, 49, 12, 28, 50, 6, + 28, 51, 1, 28, 51, -4, 29, 52, -9, 29, 53, -13, + 29, 54, -19, 30, 55, -23, 30, 56, -28, 31, 58, -32, + 31, 59, -37, 32, 60, -41, 32, 62, -45, 33, 63, -49, + 34, 65, -53, 34, 66, -57, 35, 68, -61, 36, 69, -64, + 36, 71, -68, 37, 73, -72, 38, 74, -75, 38, 76, -79, + 39, 77, -82, 40, 79, -86, 41, 81, -89, 42, 83, -92, + 27, 47, 40, 27, 47, 37, 27, 47, 33, 27, 48, 28, + 28, 48, 23, 28, 48, 18, 28, 49, 13, 28, 49, 7, + 28, 50, 2, 29, 51, -3, 29, 52, -8, 29, 53, -13, + 30, 54, -18, 30, 55, -23, 30, 56, -27, 31, 57, -32, + 31, 58, -36, 32, 60, -40, 32, 61, -45, 33, 63, -48, + 34, 64, -53, 34, 66, -57, 35, 67, -60, 36, 69, -64, + 36, 70, -68, 37, 72, -72, 38, 74, -75, 39, 75, -78, + 39, 77, -82, 40, 79, -85, 41, 81, -89, 42, 82, -92, + 28, 46, 40, 28, 46, 37, 28, 46, 33, 28, 47, 28, + 28, 47, 23, 28, 47, 18, 28, 48, 13, 28, 49, 7, + 29, 49, 2, 29, 50, -3, 29, 51, -8, 29, 52, -13, + 30, 53, -18, 30, 54, -22, 31, 55, -27, 31, 56, -31, + 32, 58, -36, 32, 59, -40, 33, 60, -44, 33, 62, -48, + 34, 64, -52, 35, 65, -56, 35, 67, -60, 36, 68, -64, + 37, 70, -67, 37, 72, -71, 38, 73, -75, 39, 75, -78, + 40, 76, -81, 40, 78, -85, 41, 80, -88, 42, 82, -91, + 28, 45, 40, 28, 45, 37, 28, 45, 34, 28, 46, 29, + 28, 46, 24, 28, 46, 19, 28, 47, 13, 29, 48, 8, + 29, 48, 3, 29, 49, -2, 29, 50, -7, 30, 51, -12, + 30, 52, -17, 31, 53, -22, 31, 54, -26, 31, 55, -31, + 32, 57, -35, 32, 58, -40, 33, 60, -44, 34, 61, -48, + 34, 63, -52, 35, 64, -56, 35, 66, -60, 36, 67, -63, + 37, 69, -67, 38, 71, -71, 38, 72, -74, 39, 74, -78, + 40, 76, -81, 41, 78, -85, 41, 79, -88, 42, 81, -91, + 28, 44, 41, 28, 44, 38, 28, 44, 34, 28, 45, 29, + 29, 45, 24, 29, 45, 19, 29, 46, 14, 29, 46, 8, + 29, 47, 3, 30, 48, -2, 30, 49, -7, 30, 50, -11, + 31, 51, -17, 31, 52, -21, 31, 53, -26, 32, 54, -30, + 32, 56, -35, 33, 57, -39, 33, 59, -43, 34, 60, -47, + 35, 62, -52, 35, 63, -55, 36, 65, -59, 36, 67, -63, + 37, 68, -66, 38, 70, -70, 39, 72, -74, 39, 73, -77, + 40, 75, -81, 41, 77, -84, 42, 79, -88, 42, 80, -91, + 29, 43, 41, 29, 43, 38, 29, 43, 34, 29, 43, 29, + 29, 44, 25, 29, 44, 20, 29, 45, 15, 30, 45, 9, + 30, 46, 4, 30, 47, -1, 30, 48, -6, 31, 48, -11, + 31, 50, -16, 31, 51, -21, 32, 52, -25, 32, 53, -29, + 33, 55, -34, 33, 56, -38, 34, 58, -43, 34, 59, -47, + 35, 61, -51, 36, 62, -55, 36, 64, -59, 37, 66, -62, + 37, 67, -66, 38, 69, -70, 39, 71, -73, 40, 72, -77, + 40, 74, -80, 41, 76, -84, 42, 78, -87, 43, 80, -90, + 29, 41, 42, 29, 41, 38, 29, 42, 35, 29, 42, 30, + 30, 42, 25, 30, 43, 20, 30, 43, 15, 30, 44, 9, + 30, 44, 4, 30, 45, 0, 31, 46, -5, 31, 47, -10, + 31, 48, -15, 32, 49, -20, 32, 51, -24, 33, 52, -29, + 33, 53, -34, 34, 55, -38, 34, 56, -42, 35, 58, -46, + 35, 60, -50, 36, 61, -54, 37, 63, -58, 37, 64, -62, + 38, 66, -65, 39, 68, -69, 39, 70, -73, 40, 71, -76, + 41, 73, -80, 41, 75, -83, 42, 77, -87, 43, 79, -90, + 30, 40, 42, 30, 40, 39, 30, 40, 35, 30, 40, 31, + 30, 41, 26, 30, 41, 21, 30, 42, 16, 31, 42, 10, + 31, 43, 5, 31, 44, 0, 31, 45, -5, 32, 46, -9, + 32, 47, -15, 32, 48, -19, 33, 49, -24, 33, 51, -28, + 34, 52, -33, 34, 53, -37, 35, 55, -41, 35, 56, -45, + 36, 58, -50, 36, 60, -53, 37, 62, -57, 38, 63, -61, + 38, 65, -65, 39, 67, -69, 40, 69, -72, 40, 70, -76, + 41, 72, -79, 42, 74, -83, 43, 76, -86, 43, 78, -89, + 30, 38, 43, 30, 38, 39, 31, 38, 36, 31, 39, 31, + 31, 39, 27, 31, 39, 22, 31, 40, 17, 31, 41, 11, + 31, 41, 6, 32, 42, 1, 32, 43, -4, 32, 44, -8, + 33, 45, -14, 33, 46, -18, 33, 48, -23, 34, 49, -27, + 34, 51, -32, 35, 52, -36, 35, 54, -40, 36, 55, -44, + 36, 57, -49, 37, 58, -53, 37, 60, -56, 38, 62, -60, + 39, 64, -64, 39, 66, -68, 40, 67, -71, 41, 69, -75, + 41, 71, -78, 42, 73, -82, 43, 75, -85, 44, 77, -89, + 31, 36, 43, 31, 36, 40, 31, 36, 37, 31, 36, 32, + 32, 37, 27, 32, 37, 23, 32, 38, 18, 32, 38, 12, + 32, 39, 7, 32, 40, 2, 33, 41, -2, 33, 42, -7, + 33, 43, -12, 34, 44, -17, 34, 46, -22, 34, 47, -26, + 35, 48, -31, 35, 50, -35, 36, 52, -39, 36, 53, -43, + 37, 55, -48, 37, 57, -52, 38, 58, -55, 39, 60, -59, + 39, 62, -63, 40, 64, -67, 41, 66, -70, 41, 67, -74, + 42, 69, -77, 43, 71, -81, 44, 73, -85, 44, 75, -88, + 32, 34, 44, 32, 34, 40, 32, 34, 37, 32, 34, 33, + 32, 35, 28, 32, 35, 24, 33, 36, 19, 33, 36, 13, + 33, 37, 8, 33, 38, 3, 33, 39, -1, 34, 40, -6, + 34, 41, -11, 34, 42, -16, 35, 44, -21, 35, 45, -25, + 36, 47, -30, 36, 48, -34, 36, 50, -38, 37, 51, -42, + 38, 53, -47, 38, 55, -51, 39, 57, -55, 39, 58, -58, + 40, 60, -62, 41, 62, -66, 41, 64, -70, 42, 66, -73, + 42, 68, -77, 43, 70, -80, 44, 72, -84, 45, 74, -87, + 33, 32, 44, 33, 32, 41, 33, 32, 38, 33, 32, 34, + 33, 33, 29, 33, 33, 25, 33, 34, 20, 33, 34, 14, + 34, 35, 9, 34, 36, 4, 34, 37, 0, 34, 38, -5, + 35, 39, -10, 35, 41, -15, 35, 42, -19, 36, 43, -24, + 36, 45, -29, 37, 46, -33, 37, 48, -37, 38, 50, -41, + 38, 52, -46, 39, 53, -50, 39, 55, -54, 40, 57, -57, + 40, 59, -61, 41, 61, -65, 42, 63, -69, 42, 64, -72, + 43, 66, -76, 44, 68, -80, 44, 70, -83, 45, 72, -86, + 34, 30, 45, 34, 30, 42, 34, 30, 39, 34, 30, 34, + 34, 31, 30, 34, 31, 25, 34, 32, 21, 34, 32, 15, + 34, 33, 10, 35, 34, 6, 35, 35, 1, 35, 36, -4, + 35, 37, -9, 36, 39, -14, 36, 40, -18, 36, 41, -23, + 37, 43, -28, 37, 44, -32, 38, 46, -36, 38, 48, -40, + 39, 50, -45, 39, 51, -49, 40, 53, -52, 40, 55, -56, + 41, 57, -60, 42, 59, -64, 42, 61, -68, 43, 63, -71, + 44, 65, -75, 44, 67, -79, 45, 69, -82, 46, 71, -85, + 34, 27, 45, 35, 28, 42, 35, 28, 39, 35, 28, 35, + 35, 28, 31, 35, 29, 26, 35, 29, 22, 35, 30, 16, + 35, 31, 12, 35, 32, 7, 36, 33, 2, 36, 34, -3, + 36, 35, -8, 37, 36, -13, 37, 38, -17, 37, 39, -22, + 38, 41, -26, 38, 42, -31, 39, 44, -35, 39, 46, -39, + 40, 48, -44, 40, 50, -48, 41, 51, -51, 41, 53, -55, + 42, 55, -59, 42, 57, -63, 43, 59, -67, 44, 61, -70, + 44, 63, -74, 45, 65, -78, 46, 67, -81, 46, 69, -84, + 35, 25, 46, 35, 25, 43, 35, 26, 40, 36, 26, 36, + 36, 26, 32, 36, 27, 28, 36, 27, 23, 36, 28, 18, + 36, 29, 13, 36, 30, 8, 37, 31, 3, 37, 32, -2, + 37, 33, -7, 37, 34, -11, 38, 36, -16, 38, 37, -20, + 38, 39, -25, 39, 40, -29, 39, 42, -34, 40, 44, -38, + 40, 46, -42, 41, 48, -46, 41, 49, -50, 42, 51, -54, + 42, 53, -58, 43, 55, -62, 44, 57, -66, 44, 59, -69, + 45, 61, -73, 46, 63, -77, 46, 65, -80, 47, 67, -83, + 36, 23, 46, 36, 23, 44, 36, 23, 41, 36, 24, 37, + 37, 24, 33, 37, 24, 29, 37, 25, 24, 37, 26, 19, + 37, 27, 14, 37, 27, 9, 37, 28, 4, 38, 30, 0, + 38, 31, -5, 38, 32, -10, 39, 33, -15, 39, 35, -19, + 39, 37, -24, 40, 38, -28, 40, 40, -32, 41, 42, -37, + 41, 44, -41, 41, 45, -45, 42, 47, -49, 43, 49, -53, + 43, 51, -57, 44, 53, -61, 44, 55, -65, 45, 57, -68, + 45, 59, -72, 46, 62, -76, 47, 64, -79, 47, 66, -82, + 37, 21, 47, 37, 21, 44, 37, 21, 42, 37, 21, 38, + 37, 22, 34, 38, 22, 30, 38, 23, 25, 38, 23, 20, + 38, 24, 15, 38, 25, 10, 38, 26, 6, 39, 27, 1, + 39, 29, -4, 39, 30, -9, 39, 31, -13, 40, 33, -18, + 40, 34, -23, 41, 36, -27, 41, 38, -31, 41, 39, -35, + 42, 42, -40, 42, 43, -44, 43, 45, -48, 43, 47, -52, + 44, 49, -56, 44, 51, -60, 45, 53, -63, 46, 55, -67, + 46, 57, -71, 47, 60, -75, 47, 62, -78, 48, 64, -81, + 38, 18, 47, 38, 18, 45, 38, 19, 42, 38, 19, 39, + 38, 19, 35, 39, 20, 31, 39, 20, 26, 39, 21, 21, + 39, 22, 16, 39, 23, 12, 39, 24, 7, 39, 25, 2, + 40, 26, -3, 40, 28, -7, 40, 29, -12, 41, 30, -16, + 41, 32, -21, 41, 34, -26, 42, 36, -30, 42, 37, -34, + 43, 39, -39, 43, 41, -43, 44, 43, -47, 44, 45, -50, + 45, 47, -54, 45, 49, -58, 46, 51, -62, 46, 53, -66, + 47, 55, -69, 48, 58, -73, 48, 60, -77, 49, 62, -80, + 39, 16, 48, 39, 16, 46, 39, 16, 43, 39, 17, 40, + 39, 17, 36, 39, 17, 32, 40, 18, 28, 40, 19, 22, + 40, 20, 18, 40, 21, 13, 40, 22, 8, 40, 23, 4, + 41, 24, -1, 41, 25, -6, 41, 27, -10, 42, 28, -15, + 42, 30, -20, 42, 32, -24, 43, 33, -28, 43, 35, -33, + 44, 37, -37, 44, 39, -41, 44, 41, -45, 45, 43, -49, + 45, 45, -53, 46, 47, -57, 47, 49, -61, 47, 51, -65, + 48, 53, -68, 48, 56, -72, 49, 58, -76, 50, 60, -79, + 40, 14, 49, 40, 14, 46, 40, 14, 44, 40, 14, 41, + 40, 15, 37, 41, 15, 33, 41, 16, 29, 41, 16, 24, + 41, 17, 19, 41, 18, 14, 41, 19, 10, 41, 20, 5, + 42, 22, 0, 42, 23, -5, 42, 24, -9, 43, 26, -13, + 43, 28, -18, 43, 29, -23, 44, 31, -27, 44, 33, -31, + 44, 35, -36, 45, 37, -40, 45, 39, -44, 46, 41, -48, + 46, 43, -52, 47, 45, -56, 47, 47, -60, 48, 49, -63, + 48, 51, -67, 49, 54, -71, 50, 56, -74, 50, 58, -78, + 41, 11, 49, 41, 11, 47, 41, 12, 45, 41, 12, 42, + 41, 12, 38, 42, 13, 34, 42, 13, 30, 42, 14, 25, + 42, 15, 20, 42, 16, 16, 42, 17, 11, 42, 18, 7, + 43, 19, 1, 43, 21, -3, 43, 22, -8, 43, 23, -12, + 44, 25, -17, 44, 27, -21, 45, 29, -26, 45, 30, -30, + 45, 33, -34, 46, 34, -38, 46, 36, -42, 47, 38, -46, + 47, 40, -50, 48, 43, -55, 48, 45, -58, 49, 47, -62, + 49, 49, -66, 50, 52, -70, 50, 54, -73, 51, 56, -77, + 42, 9, 50, 42, 9, 48, 42, 9, 46, 42, 10, 43, + 43, 10, 39, 43, 10, 35, 43, 11, 31, 43, 12, 26, + 43, 13, 22, 43, 13, 17, 43, 14, 13, 43, 16, 8, + 44, 17, 3, 44, 18, -2, 44, 20, -6, 44, 21, -11, + 45, 23, -15, 45, 25, -20, 45, 26, -24, 46, 28, -28, + 46, 30, -33, 47, 32, -37, 47, 34, -41, 48, 36, -45, + 48, 38, -49, 48, 41, -53, 49, 43, -57, 50, 45, -61, + 50, 47, -64, 51, 49, -68, 51, 52, -72, 52, 54, -75, + 43, 7, 51, 43, 7, 49, 43, 7, 47, 44, 7, 44, + 44, 8, 40, 44, 8, 37, 44, 9, 32, 44, 9, 28, + 44, 10, 23, 44, 11, 19, 44, 12, 14, 45, 13, 10, + 45, 15, 4, 45, 16, 0, 45, 17, -5, 45, 19, -9, + 46, 21, -14, 46, 22, -18, 46, 24, -23, 47, 26, -27, + 47, 28, -31, 48, 30, -35, 48, 32, -40, 48, 34, -43, + 49, 36, -47, 49, 38, -52, 50, 40, -56, 50, 43, -59, + 51, 45, -63, 51, 47, -67, 52, 49, -71, 53, 52, -74, + 45, 4, 52, 45, 4, 50, 45, 5, 48, 45, 5, 45, + 45, 5, 41, 45, 6, 38, 45, 6, 34, 45, 7, 29, + 45, 8, 25, 45, 9, 20, 45, 10, 16, 46, 11, 11, + 46, 12, 6, 46, 13, 1, 46, 15, -3, 47, 16, -7, + 47, 18, -12, 47, 20, -17, 47, 22, -21, 48, 23, -25, + 48, 25, -30, 49, 27, -34, 49, 29, -38, 49, 31, -42, + 50, 34, -46, 50, 36, -50, 51, 38, -54, 51, 40, -58, + 52, 42, -62, 52, 45, -66, 53, 47, -69, 53, 49, -73, + 46, 2, 52, 46, 2, 51, 46, 2, 49, 46, 3, 46, + 46, 3, 43, 46, 3, 39, 46, 4, 35, 46, 5, 30, + 46, 5, 26, 46, 6, 22, 46, 7, 17, 47, 8, 13, + 47, 10, 7, 47, 11, 3, 47, 12, -1, 48, 14, -6, + 48, 16, -11, 48, 17, -15, 48, 19, -19, 49, 21, -24, + 49, 23, -28, 50, 25, -32, 50, 27, -36, 50, 29, -40, + 51, 31, -44, 51, 34, -49, 52, 36, -53, 52, 38, -56, + 53, 40, -60, 53, 43, -64, 54, 45, -68, 54, 47, -71, + 47, 0, 53, 47, 0, 51, 47, 0, 50, 47, 0, 47, + 47, 1, 44, 47, 1, 40, 47, 2, 36, 47, 2, 32, + 47, 3, 27, 47, 4, 23, 48, 5, 18, 48, 6, 14, + 48, 7, 9, 48, 9, 5, 48, 10, 0, 49, 12, -4, + 49, 13, -9, 49, 15, -14, 49, 17, -18, 50, 19, -22, + 50, 21, -27, 51, 23, -31, 51, 25, -35, 51, 27, -39, + 52, 29, -43, 52, 31, -47, 53, 33, -51, 53, 36, -55, + 54, 38, -59, 54, 40, -63, 55, 43, -66, 55, 45, -70, + 48, -3, 54, 48, -3, 52, 48, -2, 50, 48, -2, 48, + 48, -2, 45, 48, -1, 41, 48, -1, 37, 48, 0, 33, + 48, 1, 29, 49, 2, 24, 49, 3, 20, 49, 4, 16, + 49, 5, 11, 49, 6, 6, 49, 8, 2, 50, 9, -3, + 50, 11, -8, 50, 13, -12, 51, 14, -16, 51, 16, -20, + 51, 18, -25, 52, 20, -29, 52, 22, -33, 52, 24, -37, + 53, 26, -41, 53, 29, -46, 54, 31, -50, 54, 33, -53, + 54, 36, -57, 55, 38, -61, 56, 40, -65, 56, 43, -69, + 49, -5, 55, 49, -5, 53, 49, -5, 51, 49, -4, 49, + 49, -4, 46, 49, -4, 42, 49, -3, 39, 49, -2, 34, + 50, -1, 30, 50, -1, 26, 50, 0, 21, 50, 1, 17, + 50, 3, 12, 50, 4, 8, 51, 5, 3, 51, 7, -1, + 51, 9, -6, 51, 10, -10, 52, 12, -15, 52, 14, -19, + 52, 16, -24, 53, 18, -28, 53, 20, -32, 53, 22, -36, + 54, 24, -40, 54, 27, -44, 55, 29, -48, 55, 31, -52, + 55, 33, -56, 56, 36, -60, 56, 38, -63, 57, 40, -67, + 50, -7, 56, 50, -7, 54, 50, -7, 52, 50, -7, 50, + 50, -6, 47, 50, -6, 44, 50, -5, 40, 51, -5, 36, + 51, -4, 31, 51, -3, 27, 51, -2, 23, 51, -1, 19, + 51, 0, 14, 51, 2, 9, 52, 3, 5, 52, 5, 0, + 52, 6, -4, 52, 8, -9, 53, 10, -13, 53, 12, -17, + 53, 14, -22, 54, 16, -26, 54, 18, -30, 54, 20, -34, + 55, 22, -38, 55, 24, -43, 56, 26, -47, 56, 29, -50, + 56, 31, -54, 57, 33, -58, 57, 36, -62, 58, 38, -66, + 51, -9, 56, 51, -9, 55, 51, -9, 53, 51, -9, 51, + 51, -8, 48, 52, -8, 45, 52, -7, 41, 52, -7, 37, + 52, -6, 33, 52, -5, 29, 52, -4, 24, 52, -3, 20, + 52, -2, 15, 53, -1, 11, 53, 1, 6, 53, 2, 2, + 53, 4, -3, 53, 6, -7, 54, 7, -11, 54, 9, -16, + 54, 11, -20, 55, 13, -24, 55, 15, -29, 55, 17, -33, + 56, 19, -37, 56, 22, -41, 57, 24, -45, 57, 26, -49, + 57, 28, -53, 58, 31, -57, 58, 33, -60, 59, 36, -64, + 53, -12, 58, 53, -12, 56, 53, -12, 55, 53, -12, 52, + 53, -11, 49, 53, -11, 46, 53, -10, 43, 53, -10, 39, + 53, -9, 35, 53, -8, 31, 53, -7, 26, 54, -6, 22, + 54, -5, 17, 54, -3, 13, 54, -2, 8, 54, -1, 4, + 55, 1, -1, 55, 3, -5, 55, 4, -9, 55, 6, -14, + 56, 8, -18, 56, 10, -22, 56, 12, -27, 57, 14, -31, + 57, 16, -35, 57, 19, -39, 58, 21, -43, 58, 23, -47, + 59, 26, -51, 59, 28, -55, 60, 30, -59, 60, 33, -62, + 54, -14, 58, 54, -14, 57, 54, -14, 55, 54, -14, 53, + 54, -13, 51, 54, -13, 47, 54, -12, 44, 54, -12, 40, + 54, -11, 36, 55, -10, 32, 55, -9, 28, 55, -8, 23, + 55, -7, 19, 55, -6, 14, 55, -4, 10, 55, -3, 6, + 56, -1, 1, 56, 0, -3, 56, 2, -8, 56, 4, -12, + 57, 6, -17, 57, 8, -21, 57, 10, -25, 58, 12, -29, + 58, 14, -33, 58, 17, -37, 59, 19, -41, 59, 21, -45, + 60, 23, -49, 60, 26, -53, 60, 28, -57, 61, 30, -61, + 55, -16, 59, 55, -16, 58, 55, -16, 56, 55, -16, 54, + 55, -15, 52, 55, -15, 49, 55, -15, 45, 55, -14, 41, + 56, -13, 37, 56, -12, 33, 56, -11, 29, 56, -10, 25, + 56, -9, 20, 56, -8, 16, 56, -7, 12, 57, -5, 7, + 57, -3, 2, 57, -2, -2, 57, 0, -6, 58, 2, -10, + 58, 4, -15, 58, 6, -19, 58, 8, -23, 59, 10, -27, + 59, 12, -31, 59, 14, -36, 60, 16, -40, 60, 19, -44, + 61, 21, -47, 61, 23, -52, 61, 26, -55, 62, 28, -59, + 56, -18, 60, 56, -18, 59, 56, -18, 57, 56, -18, 55, + 56, -18, 53, 56, -17, 50, 57, -17, 47, 57, -16, 43, + 57, -15, 39, 57, -14, 35, 57, -14, 31, 57, -13, 27, + 57, -11, 22, 57, -10, 17, 58, -9, 13, 58, -7, 9, + 58, -6, 4, 58, -4, 0, 58, -2, -4, 59, -1, -9, + 59, 1, -13, 59, 3, -17, 60, 5, -22, 60, 7, -26, + 60, 9, -30, 61, 12, -34, 61, 14, -38, 61, 16, -42, + 62, 19, -46, 62, 21, -50, 62, 23, -54, 63, 26, -58, + 58, -21, 61, 58, -20, 60, 58, -20, 58, 58, -20, 56, + 58, -20, 54, 58, -19, 51, 58, -19, 48, 58, -18, 44, + 58, -17, 40, 58, -17, 36, 58, -16, 32, 58, -15, 28, + 58, -13, 23, 59, -12, 19, 59, -11, 15, 59, -10, 11, + 59, -8, 6, 59, -6, 1, 60, -5, -3, 60, -3, -7, + 60, -1, -12, 60, 1, -16, 61, 3, -20, 61, 5, -24, + 61, 7, -28, 62, 10, -32, 62, 12, -36, 62, 14, -40, + 63, 16, -44, 63, 19, -48, 63, 21, -52, 64, 23, -56, + 59, -23, 62, 59, -22, 61, 59, -22, 59, 59, -22, 57, + 59, -22, 55, 59, -21, 52, 59, -21, 49, 59, -20, 45, + 59, -19, 41, 59, -19, 38, 59, -18, 34, 59, -17, 30, + 60, -16, 25, 60, -14, 21, 60, -13, 16, 60, -12, 12, + 60, -10, 7, 60, -8, 3, 61, -7, -1, 61, -5, -5, + 61, -3, -10, 61, -1, -14, 62, 1, -18, 62, 3, -22, + 62, 5, -26, 63, 7, -31, 63, 9, -35, 63, 12, -39, + 64, 14, -43, 64, 16, -47, 65, 19, -51, 65, 21, -54, + 60, -25, 63, 60, -24, 62, 60, -24, 60, 60, -24, 58, + 60, -24, 56, 60, -23, 53, 60, -23, 50, 60, -22, 46, + 60, -21, 43, 60, -21, 39, 60, -20, 35, 61, -19, 31, + 61, -18, 26, 61, -16, 22, 61, -15, 18, 61, -14, 14, + 61, -12, 9, 62, -11, 5, 62, -9, 1, 62, -7, -4, + 62, -5, -8, 63, -3, -12, 63, -1, -17, 63, 1, -21, + 63, 3, -25, 64, 5, -29, 64, 7, -33, 64, 9, -37, + 65, 12, -41, 65, 14, -45, 66, 16, -49, 66, 19, -53, + 61, -27, 64, 61, -26, 63, 61, -26, 61, 61, -26, 59, + 61, -26, 57, 61, -25, 54, 61, -25, 51, 61, -24, 48, + 61, -23, 44, 62, -23, 40, 62, -22, 37, 62, -21, 33, + 62, -20, 28, 62, -19, 24, 62, -17, 20, 62, -16, 15, + 63, -14, 11, 63, -13, 6, 63, -11, 2, 63, -9, -2, + 63, -7, -7, 64, -6, -11, 64, -4, -15, 64, -2, -19, + 64, 0, -23, 65, 3, -27, 65, 5, -31, 65, 7, -35, + 66, 9, -39, 66, 12, -44, 67, 14, -47, 67, 16, -51, + 62, -28, 65, 62, -28, 63, 62, -28, 62, 62, -28, 60, + 62, -28, 58, 62, -27, 56, 62, -27, 53, 63, -26, 49, + 63, -25, 46, 63, -25, 42, 63, -24, 38, 63, -23, 34, + 63, -22, 29, 63, -21, 25, 63, -19, 21, 63, -18, 17, + 64, -16, 12, 64, -15, 8, 64, -13, 4, 64, -12, 0, + 65, -10, -5, 65, -8, -9, 65, -6, -13, 65, -4, -17, + 66, -2, -21, 66, 0, -26, 66, 3, -30, 67, 5, -34, + 67, 7, -38, 67, 10, -42, 68, 12, -46, 68, 14, -49, + 63, -30, 65, 64, -30, 64, 64, -30, 63, 64, -30, 61, + 64, -30, 59, 64, -29, 57, 64, -29, 54, 64, -28, 50, + 64, -27, 47, 64, -27, 43, 64, -26, 39, 64, -25, 35, + 64, -24, 31, 64, -23, 27, 64, -21, 23, 65, -20, 19, + 65, -18, 14, 65, -17, 10, 65, -15, 5, 65, -14, 1, + 66, -12, -3, 66, -10, -7, 66, -8, -12, 66, -6, -16, + 67, -4, -20, 67, -2, -24, 67, 0, -28, 68, 2, -32, + 68, 5, -36, 68, 7, -40, 69, 10, -44, 69, 12, -48, + 65, -32, 66, 65, -32, 65, 65, -32, 64, 65, -32, 62, + 65, -31, 60, 65, -31, 58, 65, -31, 55, 65, -30, 52, + 65, -29, 48, 65, -29, 45, 65, -28, 41, 65, -27, 37, + 65, -26, 32, 66, -25, 28, 66, -23, 24, 66, -22, 20, + 66, -21, 15, 66, -19, 11, 66, -17, 7, 67, -16, 3, + 67, -14, -2, 67, -12, -6, 67, -10, -10, 68, -8, -14, + 68, -6, -18, 68, -4, -22, 68, -2, -26, 69, 0, -30, + 69, 2, -34, 69, 5, -39, 70, 7, -42, 70, 10, -46, + 66, -34, 67, 66, -34, 66, 66, -34, 65, 66, -34, 63, + 66, -33, 61, 66, -33, 59, 66, -32, 56, 66, -32, 53, + 66, -31, 50, 66, -30, 46, 66, -30, 42, 66, -29, 38, + 67, -28, 34, 67, -27, 30, 67, -25, 26, 67, -24, 22, + 67, -23, 17, 67, -21, 13, 68, -20, 9, 68, -18, 5, + 68, -16, 0, 68, -14, -4, 68, -12, -8, 69, -10, -12, + 69, -9, -16, 69, -6, -21, 69, -4, -25, 70, -2, -29, + 70, 0, -33, 70, 3, -37, 71, 5, -41, 71, 7, -45, + 67, -36, 68, 67, -36, 67, 67, -36, 66, 67, -35, 64, + 67, -35, 63, 67, -35, 60, 67, -34, 57, 67, -34, 54, + 67, -33, 51, 67, -32, 47, 68, -32, 44, 68, -31, 40, + 68, -30, 35, 68, -28, 31, 68, -27, 27, 68, -26, 23, + 68, -25, 19, 68, -23, 15, 69, -22, 10, 69, -20, 6, + 69, -18, 2, 69, -16, -2, 70, -14, -7, 70, -13, -11, + 70, -11, -15, 70, -8, -19, 71, -6, -23, 71, -4, -27, + 71, -2, -31, 72, 1, -35, 72, 3, -39, 72, 5, -43, + 68, -38, 69, 68, -38, 68, 68, -37, 67, 68, -37, 65, + 68, -37, 64, 68, -37, 61, 68, -36, 59, 69, -36, 55, + 69, -35, 52, 69, -34, 49, 69, -33, 45, 69, -33, 41, + 69, -31, 37, 69, -30, 33, 69, -29, 29, 69, -28, 25, + 69, -27, 20, 70, -25, 16, 70, -24, 12, 70, -22, 8, + 70, -20, 3, 70, -18, -1, 71, -17, -5, 71, -15, -9, + 71, -13, -13, 71, -11, -17, 72, -8, -21, 72, -6, -25, + 72, -4, -29, 73, -2, -34, 73, 1, -37, 73, 3, -41, + 69, -39, 70, 69, -39, 69, 70, -39, 68, 70, -39, 67, + 70, -39, 65, 70, -38, 62, 70, -38, 60, 70, -37, 57, + 70, -37, 53, 70, -36, 50, 70, -35, 46, 70, -34, 43, + 70, -33, 38, 70, -32, 34, 70, -31, 30, 70, -30, 26, + 71, -28, 22, 71, -27, 18, 71, -26, 14, 71, -24, 10, + 71, -22, 5, 72, -20, 1, 72, -19, -3, 72, -17, -7, + 72, -15, -11, 73, -13, -16, 73, -11, -20, 73, -8, -24, + 73, -6, -28, 74, -4, -32, 74, -2, -36, 74, 1, -40, + 71, -41, 71, 71, -41, 70, 71, -41, 69, 71, -41, 68, + 71, -40, 66, 71, -40, 64, 71, -40, 61, 71, -39, 58, + 71, -39, 55, 71, -38, 51, 71, -37, 48, 71, -36, 44, + 71, -35, 40, 71, -34, 36, 72, -33, 32, 72, -32, 28, + 72, -30, 23, 72, -29, 19, 72, -28, 15, 72, -26, 11, + 73, -24, 7, 73, -22, 3, 73, -21, -1, 73, -19, -6, + 73, -17, -10, 74, -15, -14, 74, -13, -18, 74, -11, -22, + 74, -8, -26, 75, -6, -30, 75, -4, -34, 75, -2, -38, + 72, -43, 72, 72, -43, 71, 72, -43, 70, 72, -42, 69, + 72, -42, 67, 72, -42, 65, 72, -41, 62, 72, -41, 59, + 72, -40, 56, 72, -40, 53, 72, -39, 49, 72, -38, 46, + 72, -37, 41, 73, -36, 37, 73, -35, 34, 73, -34, 30, + 73, -32, 25, 73, -31, 21, 73, -29, 17, 73, -28, 13, + 74, -26, 8, 74, -24, 4, 74, -23, 0, 74, -21, -4, + 75, -19, -8, 75, -17, -12, 75, -15, -16, 75, -13, -20, + 76, -11, -24, 76, -8, -28, 76, -6, -32, 77, -4, -36, + 73, -45, 73, 73, -45, 72, 73, -44, 71, 73, -44, 70, + 73, -44, 68, 73, -44, 66, 73, -43, 63, 73, -43, 60, + 73, -42, 57, 73, -41, 54, 73, -41, 51, 74, -40, 47, + 74, -39, 43, 74, -38, 39, 74, -37, 35, 74, -36, 31, + 74, -34, 27, 74, -33, 23, 74, -31, 19, 75, -30, 14, + 75, -28, 10, 75, -26, 6, 75, -25, 2, 75, -23, -2, + 76, -21, -6, 76, -19, -11, 76, -17, -15, 76, -15, -19, + 77, -13, -22, 77, -10, -27, 77, -8, -31, 78, -6, -34, + 74, -46, 74, 74, -46, 73, 74, -46, 72, 74, -46, 71, + 74, -46, 69, 74, -45, 67, 74, -45, 64, 74, -44, 61, + 75, -44, 58, 75, -43, 55, 75, -42, 52, 75, -42, 48, + 75, -41, 44, 75, -40, 40, 75, -39, 37, 75, -37, 33, + 75, -36, 28, 75, -35, 24, 76, -33, 20, 76, -32, 16, + 76, -30, 12, 76, -28, 8, 76, -27, 4, 77, -25, 0, + 77, -23, -4, 77, -21, -9, 77, -19, -13, 78, -17, -17, + 78, -15, -21, 78, -12, -25, 78, -10, -29, 79, -8, -33, + 75, -48, 75, 75, -48, 74, 76, -48, 73, 76, -48, 72, + 76, -47, 70, 76, -47, 68, 76, -47, 66, 76, -46, 63, + 76, -45, 60, 76, -45, 57, 76, -44, 53, 76, -43, 50, + 76, -42, 46, 76, -41, 42, 76, -40, 38, 76, -39, 34, + 77, -38, 30, 77, -36, 26, 77, -35, 22, 77, -34, 18, + 77, -32, 13, 77, -30, 9, 78, -29, 5, 78, -27, 1, + 78, -25, -3, 78, -23, -7, 78, -21, -11, 79, -19, -15, + 79, -17, -19, 79, -14, -23, 79, -12, -27, 80, -10, -31, + 77, -50, 76, 77, -50, 75, 77, -49, 74, 77, -49, 73, + 77, -49, 71, 77, -49, 69, 77, -48, 67, 77, -48, 64, + 77, -47, 61, 77, -47, 58, 77, -46, 55, 77, -45, 51, + 77, -44, 47, 77, -43, 43, 77, -42, 40, 78, -41, 36, + 78, -40, 31, 78, -38, 27, 78, -37, 23, 78, -35, 19, + 78, -34, 15, 78, -32, 11, 79, -31, 7, 79, -29, 3, + 79, -27, -1, 79, -25, -6, 80, -23, -10, 80, -21, -13, + 80, -19, -17, 80, -17, -22, 81, -14, -26, 81, -12, -29, + 78, -52, 77, 78, -52, 76, 78, -51, 75, 78, -51, 74, + 78, -51, 72, 78, -51, 70, 78, -50, 68, 78, -50, 65, + 78, -49, 62, 78, -49, 59, 79, -48, 56, 79, -47, 53, + 79, -46, 49, 79, -45, 45, 79, -44, 41, 79, -43, 38, + 79, -42, 33, 79, -41, 29, 79, -39, 25, 80, -38, 21, + 80, -36, 17, 80, -35, 13, 80, -33, 9, 80, -31, 5, + 80, -29, 1, 81, -27, -4, 81, -25, -7, 81, -23, -11, + 81, -21, -15, 82, -19, -20, 82, -17, -24, 82, -15, -27, + 79, -53, 78, 79, -53, 77, 79, -53, 76, 79, -53, 75, + 79, -53, 73, 79, -52, 71, 79, -52, 69, 80, -51, 66, + 80, -51, 64, 80, -50, 61, 80, -50, 58, 80, -49, 54, + 80, -48, 50, 80, -47, 47, 80, -46, 43, 80, -45, 39, + 80, -44, 35, 80, -42, 31, 81, -41, 27, 81, -40, 23, + 81, -38, 18, 81, -36, 14, 81, -35, 11, 81, -33, 7, + 82, -31, 3, 82, -29, -2, 82, -27, -6, 82, -25, -10, + 83, -23, -14, 83, -21, -18, 83, -19, -22, 83, -17, -26, + 81, -55, 79, 81, -55, 78, 81, -55, 77, 81, -54, 76, + 81, -54, 74, 81, -54, 73, 81, -53, 70, 81, -53, 68, + 81, -52, 65, 81, -52, 62, 81, -51, 59, 81, -50, 56, + 81, -50, 52, 81, -49, 48, 81, -48, 44, 81, -47, 41, + 81, -45, 36, 82, -44, 32, 82, -43, 28, 82, -41, 25, + 82, -40, 20, 82, -38, 16, 82, -37, 12, 83, -35, 8, + 83, -33, 4, 83, -31, 0, 83, -29, -4, 83, -27, -8, + 84, -25, -12, 84, -23, -16, 84, -21, -20, 84, -19, -24, + 82, -56, 80, 82, -56, 79, 82, -56, 78, 82, -56, 77, + 82, -56, 75, 82, -55, 74, 82, -55, 71, 82, -55, 69, + 82, -54, 66, 82, -53, 63, 82, -53, 60, 82, -52, 57, + 82, -51, 53, 82, -50, 49, 82, -49, 46, 83, -48, 42, + 83, -47, 38, 83, -46, 34, 83, -44, 30, 83, -43, 26, + 83, -41, 22, 83, -40, 18, 84, -38, 14, 84, -37, 10, + 84, -35, 6, 84, -33, 1, 84, -31, -2, 85, -29, -6, + 85, -27, -10, 85, -25, -15, 85, -23, -18, 86, -21, -22, + 83, -58, 80, 83, -58, 80, 83, -58, 79, 83, -57, 78, + 83, -57, 76, 83, -57, 75, 83, -57, 73, 83, -56, 70, + 83, -56, 67, 83, -55, 64, 83, -54, 61, 83, -54, 58, + 83, -53, 54, 84, -52, 51, 84, -51, 47, 84, -50, 44, + 84, -49, 39, 84, -47, 35, 84, -46, 32, 84, -45, 28, + 84, -43, 23, 85, -42, 19, 85, -40, 15, 85, -39, 11, + 85, -37, 8, 85, -35, 3, 85, -33, -1, 86, -31, -5, + 86, -29, -9, 86, -27, -13, 86, -25, -17, 87, -23, -21, + 84, -59, 81, 84, -59, 81, 84, -59, 80, 84, -59, 79, + 84, -59, 77, 84, -58, 76, 84, -58, 74, 84, -58, 71, + 84, -57, 69, 84, -57, 66, 84, -56, 63, 85, -55, 59, + 85, -54, 56, 85, -54, 52, 85, -53, 49, 85, -52, 45, + 85, -50, 41, 85, -49, 37, 85, -48, 33, 85, -47, 29, + 86, -45, 25, 86, -44, 21, 86, -42, 17, 86, -40, 13, + 86, -39, 9, 86, -37, 5, 87, -35, 1, 87, -33, -3, + 87, -31, -7, 87, -29, -11, 88, -27, -15, 88, -25, -19, + 85, -61, 82, 85, -61, 82, 85, -61, 81, 85, -60, 80, + 85, -60, 78, 85, -60, 77, 85, -60, 75, 85, -59, 72, + 86, -59, 70, 86, -58, 67, 86, -58, 64, 86, -57, 61, + 86, -56, 57, 86, -55, 54, 86, -54, 50, 86, -53, 46, + 86, -52, 42, 86, -51, 38, 86, -50, 35, 87, -48, 31, + 87, -47, 26, 87, -45, 23, 87, -44, 19, 87, -42, 15, + 87, -41, 11, 88, -39, 6, 88, -37, 2, 88, -35, -1, + 88, -33, -5, 88, -31, -10, 89, -29, -13, 89, -27, -17, + 87, -62, 83, 87, -62, 83, 87, -62, 82, 87, -62, 81, + 87, -62, 79, 87, -61, 78, 87, -61, 76, 87, -61, 73, + 87, -60, 71, 87, -60, 68, 87, -59, 65, 87, -58, 62, + 87, -57, 58, 87, -57, 55, 87, -56, 51, 87, -55, 48, + 87, -54, 44, 87, -52, 40, 88, -51, 36, 88, -50, 32, + 88, -48, 28, 88, -47, 24, 88, -45, 20, 88, -44, 16, + 88, -42, 12, 89, -40, 8, 89, -39, 4, 89, -37, 0, + 89, -35, -4, 90, -33, -8, 90, -31, -12, 90, -29, -16, + 88, -64, 84, 88, -64, 84, 88, -64, 83, 88, -63, 82, + 88, -63, 80, 88, -63, 79, 88, -63, 77, 88, -62, 74, + 88, -62, 72, 88, -61, 69, 88, -61, 66, 88, -60, 63, + 88, -59, 60, 88, -58, 56, 88, -57, 53, 88, -56, 49, + 88, -55, 45, 89, -54, 41, 89, -53, 38, 89, -52, 34, + 89, -50, 30, 89, -49, 26, 89, -47, 22, 89, -46, 18, + 90, -44, 14, 90, -42, 10, 90, -40, 6, 90, -39, 2, + 90, -37, -2, 91, -35, -6, 91, -33, -10, 91, -31, -14, + 89, -65, 85, 89, -65, 84, 89, -65, 84, 89, -65, 83, + 89, -65, 81, 89, -64, 80, 89, -64, 78, 89, -64, 76, + 89, -63, 73, 89, -63, 71, 89, -62, 68, 89, -61, 65, + 89, -61, 61, 89, -60, 58, 89, -59, 54, 90, -58, 51, + 90, -57, 47, 90, -56, 43, 90, -54, 39, 90, -53, 35, + 90, -52, 31, 90, -50, 27, 90, -49, 23, 91, -47, 20, + 91, -46, 16, 91, -44, 11, 91, -42, 7, 91, -40, 4, + 92, -39, 0, 92, -36, -5, 92, -35, -8, 92, -33, -12, + 90, -67, 86, 90, -67, 85, 90, -66, 85, 90, -66, 84, + 90, -66, 82, 90, -66, 81, 90, -66, 79, 90, -65, 77, + 90, -65, 74, 90, -64, 72, 90, -64, 69, 90, -63, 66, + 90, -62, 62, 91, -61, 59, 91, -60, 56, 91, -59, 52, + 91, -58, 48, 91, -57, 44, 91, -56, 41, 91, -55, 37, + 91, -53, 33, 91, -52, 29, 92, -51, 25, 92, -49, 21, + 92, -47, 17, 92, -46, 13, 92, -44, 9, 92, -42, 5, + 93, -40, 1, 93, -38, -3, 93, -36, -7, 93, -34, -11, + 28, 51, 42, 28, 51, 38, 28, 52, 35, 29, 52, 30, + 29, 52, 25, 29, 52, 19, 29, 53, 14, 29, 53, 8, + 29, 54, 3, 30, 55, -2, 30, 55, -6, 30, 56, -11, + 31, 57, -17, 31, 58, -21, 31, 59, -26, 32, 60, -30, + 32, 62, -35, 33, 63, -39, 33, 64, -43, 34, 65, -47, + 35, 67, -51, 35, 68, -55, 36, 70, -59, 36, 71, -63, + 37, 73, -66, 38, 74, -70, 39, 76, -74, 39, 78, -77, + 40, 79, -81, 41, 81, -84, 42, 83, -87, 42, 84, -91, + 29, 51, 42, 29, 51, 39, 29, 51, 35, 29, 51, 30, + 29, 51, 25, 29, 52, 20, 29, 52, 15, 29, 53, 9, + 30, 53, 4, 30, 54, -1, 30, 55, -6, 30, 56, -11, + 31, 57, -16, 31, 58, -21, 32, 59, -25, 32, 60, -30, + 33, 61, -34, 33, 62, -39, 34, 64, -43, 34, 65, -47, + 35, 66, -51, 35, 68, -55, 36, 69, -59, 37, 71, -62, + 37, 72, -66, 38, 74, -70, 39, 76, -74, 39, 77, -77, + 40, 79, -80, 41, 80, -84, 42, 82, -87, 43, 84, -90, + 29, 50, 42, 29, 50, 39, 29, 50, 35, 29, 50, 30, + 29, 51, 25, 29, 51, 20, 29, 52, 15, 30, 52, 9, + 30, 53, 4, 30, 53, -1, 30, 54, -6, 31, 55, -11, + 31, 56, -16, 31, 57, -20, 32, 58, -25, 32, 59, -29, + 33, 61, -34, 33, 62, -38, 34, 63, -42, 34, 64, -46, + 35, 66, -51, 36, 67, -55, 36, 69, -58, 37, 70, -62, + 37, 72, -66, 38, 74, -70, 39, 75, -73, 40, 77, -77, + 40, 78, -80, 41, 80, -84, 42, 82, -87, 43, 83, -90, + 29, 49, 42, 29, 49, 39, 29, 50, 35, 29, 50, 30, + 29, 50, 25, 29, 50, 20, 30, 51, 15, 30, 52, 9, + 30, 52, 4, 30, 53, -1, 31, 54, -6, 31, 54, -10, + 31, 55, -16, 32, 56, -20, 32, 58, -25, 32, 59, -29, + 33, 60, -34, 33, 61, -38, 34, 63, -42, 34, 64, -46, + 35, 65, -51, 36, 67, -54, 36, 68, -58, 37, 70, -62, + 38, 71, -66, 38, 73, -70, 39, 75, -73, 40, 76, -76, + 40, 78, -80, 41, 80, -84, 42, 81, -87, 43, 83, -90, + 29, 49, 42, 29, 49, 39, 29, 49, 36, 29, 49, 31, + 30, 49, 26, 30, 50, 21, 30, 50, 16, 30, 51, 10, + 30, 51, 5, 30, 52, 0, 31, 53, -5, 31, 54, -10, + 31, 55, -15, 32, 56, -20, 32, 57, -24, 33, 58, -29, + 33, 59, -33, 34, 61, -38, 34, 62, -42, 35, 63, -46, + 35, 65, -50, 36, 66, -54, 37, 68, -58, 37, 69, -62, + 38, 71, -65, 39, 72, -69, 39, 74, -73, 40, 76, -76, + 41, 77, -80, 41, 79, -83, 42, 81, -87, 43, 82, -90, + 30, 48, 43, 30, 48, 39, 30, 48, 36, 30, 48, 31, + 30, 48, 26, 30, 49, 21, 30, 49, 16, 30, 50, 10, + 31, 50, 5, 31, 51, 0, 31, 52, -5, 31, 53, -9, + 32, 54, -15, 32, 55, -19, 33, 56, -24, 33, 57, -28, + 33, 58, -33, 34, 60, -37, 34, 61, -41, 35, 62, -45, + 36, 64, -50, 36, 65, -54, 37, 67, -57, 37, 69, -61, + 38, 70, -65, 39, 72, -69, 39, 73, -72, 40, 75, -76, + 41, 77, -79, 42, 79, -83, 42, 80, -86, 43, 82, -89, + 30, 47, 43, 30, 47, 40, 30, 47, 36, 30, 47, 31, + 30, 47, 26, 30, 48, 21, 31, 48, 16, 31, 49, 11, + 31, 49, 6, 31, 50, 1, 31, 51, -4, 32, 52, -9, + 32, 53, -14, 32, 54, -19, 33, 55, -23, 33, 56, -28, + 34, 58, -33, 34, 59, -37, 35, 60, -41, 35, 62, -45, + 36, 63, -49, 36, 65, -53, 37, 66, -57, 38, 68, -61, + 38, 69, -64, 39, 71, -68, 40, 73, -72, 40, 74, -75, + 41, 76, -79, 42, 78, -83, 43, 79, -86, 43, 81, -89, + 30, 45, 43, 30, 45, 40, 31, 46, 36, 31, 46, 32, + 31, 46, 27, 31, 47, 22, 31, 47, 17, 31, 48, 11, + 31, 48, 6, 32, 49, 1, 32, 50, -4, 32, 51, -8, + 32, 52, -14, 33, 53, -18, 33, 54, -23, 34, 55, -27, + 34, 56, -32, 35, 58, -36, 35, 59, -40, 36, 61, -44, + 36, 62, -49, 37, 64, -53, 37, 65, -56, 38, 67, -60, + 39, 68, -64, 39, 70, -68, 40, 72, -71, 41, 73, -75, + 41, 75, -78, 42, 77, -82, 43, 79, -85, 44, 80, -89, + 31, 44, 44, 31, 44, 40, 31, 44, 37, 31, 44, 32, + 31, 45, 27, 31, 45, 23, 31, 46, 18, 32, 46, 12, + 32, 47, 7, 32, 48, 2, 32, 48, -3, 33, 49, -8, + 33, 50, -13, 33, 52, -18, 34, 53, -22, 34, 54, -26, + 35, 55, -31, 35, 57, -36, 35, 58, -40, 36, 59, -44, + 37, 61, -48, 37, 63, -52, 38, 64, -56, 38, 66, -60, + 39, 67, -63, 40, 69, -67, 40, 71, -71, 41, 72, -74, + 42, 74, -78, 43, 76, -82, 43, 78, -85, 44, 80, -88, + 31, 42, 44, 31, 43, 41, 32, 43, 37, 32, 43, 33, + 32, 43, 28, 32, 44, 23, 32, 44, 18, 32, 45, 13, + 32, 45, 8, 33, 46, 3, 33, 47, -2, 33, 48, -7, + 33, 49, -12, 34, 50, -17, 34, 51, -21, 35, 53, -26, + 35, 54, -31, 35, 55, -35, 36, 57, -39, 36, 58, -43, + 37, 60, -47, 38, 61, -51, 38, 63, -55, 39, 65, -59, + 39, 66, -63, 40, 68, -67, 41, 70, -70, 41, 71, -74, + 42, 73, -77, 43, 75, -81, 44, 77, -84, 44, 79, -88, + 32, 41, 44, 32, 41, 41, 32, 41, 38, 32, 41, 33, + 32, 42, 29, 32, 42, 24, 33, 43, 19, 33, 43, 13, + 33, 44, 8, 33, 45, 4, 33, 46, -1, 34, 46, -6, + 34, 48, -11, 34, 49, -16, 35, 50, -20, 35, 51, -25, + 36, 53, -30, 36, 54, -34, 36, 55, -38, 37, 57, -42, + 37, 58, -47, 38, 60, -51, 39, 62, -54, 39, 63, -58, + 40, 65, -62, 40, 67, -66, 41, 68, -70, 42, 70, -73, + 42, 72, -77, 43, 74, -80, 44, 76, -84, 45, 77, -87, + 33, 39, 45, 33, 39, 42, 33, 39, 38, 33, 39, 34, + 33, 40, 30, 33, 40, 25, 33, 40, 20, 33, 41, 14, + 34, 42, 10, 34, 43, 5, 34, 43, 0, 34, 44, -5, + 35, 46, -10, 35, 47, -15, 35, 48, -19, 36, 49, -24, + 36, 51, -29, 37, 52, -33, 37, 53, -37, 38, 55, -41, + 38, 57, -46, 39, 58, -50, 39, 60, -53, 40, 61, -57, + 40, 63, -61, 41, 65, -65, 42, 67, -69, 42, 69, -72, + 43, 70, -76, 44, 72, -80, 44, 74, -83, 45, 76, -86, + 34, 37, 45, 34, 37, 42, 34, 37, 39, 34, 37, 35, + 34, 38, 30, 34, 38, 26, 34, 39, 21, 34, 39, 15, + 34, 40, 10, 35, 41, 6, 35, 42, 1, 35, 43, -4, + 35, 44, -9, 36, 45, -14, 36, 46, -18, 36, 47, -23, + 37, 49, -28, 37, 50, -32, 38, 52, -36, 38, 53, -40, + 39, 55, -45, 39, 57, -49, 40, 58, -53, 40, 60, -56, + 41, 62, -60, 42, 64, -64, 42, 65, -68, 43, 67, -71, + 43, 69, -75, 44, 71, -79, 45, 73, -82, 46, 75, -85, + 34, 35, 46, 34, 35, 43, 34, 35, 40, 34, 36, 35, + 34, 36, 31, 35, 36, 27, 35, 37, 22, 35, 37, 16, + 35, 38, 11, 35, 39, 7, 35, 40, 2, 36, 41, -3, + 36, 42, -8, 36, 43, -13, 37, 44, -17, 37, 46, -22, + 37, 47, -27, 38, 49, -31, 38, 50, -35, 39, 52, -39, + 39, 53, -44, 40, 55, -48, 40, 57, -52, 41, 58, -55, + 41, 60, -59, 42, 62, -63, 43, 64, -67, 43, 66, -71, + 44, 68, -74, 45, 70, -78, 45, 71, -81, 46, 73, -85, + 35, 33, 46, 35, 33, 43, 35, 33, 40, 35, 33, 36, + 35, 34, 32, 35, 34, 27, 35, 35, 23, 36, 35, 17, + 36, 36, 12, 36, 37, 8, 36, 38, 3, 36, 39, -2, + 37, 40, -7, 37, 41, -12, 37, 42, -16, 38, 44, -21, + 38, 45, -26, 39, 47, -30, 39, 48, -34, 39, 50, -38, + 40, 52, -43, 40, 53, -47, 41, 55, -51, 41, 57, -55, + 42, 58, -58, 43, 60, -62, 43, 62, -66, 44, 64, -70, + 45, 66, -73, 45, 68, -77, 46, 70, -81, 47, 72, -84, + 36, 31, 47, 36, 31, 44, 36, 31, 41, 36, 31, 37, + 36, 32, 33, 36, 32, 28, 36, 33, 24, 36, 33, 18, + 37, 34, 14, 37, 35, 9, 37, 36, 4, 37, 37, -1, + 38, 38, -6, 38, 39, -11, 38, 40, -15, 38, 42, -20, + 39, 43, -25, 39, 45, -29, 40, 46, -33, 40, 48, -37, + 41, 50, -42, 41, 51, -46, 42, 53, -50, 42, 55, -53, + 43, 57, -57, 43, 59, -61, 44, 61, -65, 44, 62, -69, + 45, 64, -72, 46, 66, -76, 46, 68, -80, 47, 70, -83, + 37, 29, 47, 37, 29, 44, 37, 29, 42, 37, 29, 38, + 37, 30, 34, 37, 30, 29, 37, 31, 25, 37, 31, 19, + 37, 32, 15, 38, 33, 10, 38, 34, 5, 38, 35, 0, + 38, 36, -5, 39, 37, -9, 39, 38, -14, 39, 40, -18, + 40, 41, -23, 40, 43, -28, 40, 44, -32, 41, 46, -36, + 41, 48, -41, 42, 49, -45, 42, 51, -49, 43, 53, -52, + 43, 55, -56, 44, 57, -60, 45, 59, -64, 45, 61, -68, + 46, 63, -71, 46, 65, -75, 47, 67, -79, 48, 69, -82, + 38, 26, 48, 38, 27, 45, 38, 27, 42, 38, 27, 39, + 38, 27, 35, 38, 28, 30, 38, 28, 26, 38, 29, 21, + 38, 30, 16, 38, 31, 11, 39, 32, 6, 39, 33, 2, + 39, 34, -4, 39, 35, -8, 40, 36, -13, 40, 38, -17, + 40, 39, -22, 41, 41, -26, 41, 42, -31, 42, 44, -35, + 42, 46, -39, 43, 47, -43, 43, 49, -47, 44, 51, -51, + 44, 53, -55, 45, 55, -59, 45, 57, -63, 46, 59, -67, + 46, 61, -70, 47, 63, -74, 48, 65, -78, 48, 67, -81, + 38, 24, 48, 38, 24, 46, 39, 25, 43, 39, 25, 39, + 39, 25, 36, 39, 26, 31, 39, 26, 27, 39, 27, 22, + 39, 28, 17, 39, 28, 12, 39, 29, 8, 40, 30, 3, + 40, 32, -2, 40, 33, -7, 41, 34, -11, 41, 35, -16, + 41, 37, -21, 42, 39, -25, 42, 40, -29, 42, 42, -34, + 43, 44, -38, 43, 45, -42, 44, 47, -46, 44, 49, -50, + 45, 51, -54, 45, 53, -58, 46, 55, -62, 46, 57, -66, + 47, 59, -69, 48, 61, -73, 48, 63, -77, 49, 65, -80, + 39, 22, 49, 39, 22, 46, 39, 22, 44, 40, 23, 40, + 40, 23, 37, 40, 23, 32, 40, 24, 28, 40, 25, 23, + 40, 25, 18, 40, 26, 13, 40, 27, 9, 41, 28, 4, + 41, 29, -1, 41, 31, -6, 41, 32, -10, 42, 33, -15, + 42, 35, -20, 42, 36, -24, 43, 38, -28, 43, 40, -32, + 44, 42, -37, 44, 43, -41, 45, 45, -45, 45, 47, -49, + 46, 49, -53, 46, 51, -57, 47, 53, -61, 47, 55, -64, + 48, 57, -68, 48, 59, -72, 49, 61, -75, 50, 63, -79, + 40, 20, 49, 40, 20, 47, 40, 20, 45, 40, 20, 41, + 41, 21, 38, 41, 21, 33, 41, 22, 29, 41, 22, 24, + 41, 23, 19, 41, 24, 15, 41, 25, 10, 42, 26, 5, + 42, 27, 0, 42, 28, -4, 42, 30, -9, 43, 31, -13, + 43, 33, -18, 43, 34, -23, 44, 36, -27, 44, 37, -31, + 44, 39, -36, 45, 41, -40, 45, 43, -44, 46, 45, -48, + 46, 47, -51, 47, 49, -56, 47, 51, -59, 48, 53, -63, + 48, 55, -67, 49, 57, -71, 50, 59, -74, 50, 61, -78, + 41, 17, 50, 41, 17, 48, 41, 18, 45, 41, 18, 42, + 42, 18, 39, 42, 19, 35, 42, 19, 30, 42, 20, 25, + 42, 21, 21, 42, 22, 16, 42, 22, 11, 42, 23, 7, + 43, 25, 2, 43, 26, -3, 43, 27, -7, 44, 29, -12, + 44, 30, -17, 44, 32, -21, 45, 34, -25, 45, 35, -30, + 45, 37, -34, 46, 39, -38, 46, 41, -42, 47, 43, -46, + 47, 45, -50, 48, 47, -54, 48, 49, -58, 49, 51, -62, + 49, 53, -66, 50, 55, -70, 50, 57, -73, 51, 59, -77, + 42, 15, 51, 42, 15, 49, 42, 15, 46, 42, 16, 43, + 43, 16, 40, 43, 16, 36, 43, 17, 31, 43, 18, 26, + 43, 18, 22, 43, 19, 17, 43, 20, 13, 43, 21, 8, + 44, 22, 3, 44, 24, -2, 44, 25, -6, 44, 26, -10, + 45, 28, -15, 45, 30, -20, 45, 31, -24, 46, 33, -28, + 46, 35, -33, 47, 37, -37, 47, 39, -41, 47, 40, -45, + 48, 42, -49, 48, 45, -53, 49, 47, -57, 49, 49, -61, + 50, 51, -64, 51, 53, -68, 51, 55, -72, 52, 57, -75, + 43, 13, 51, 43, 13, 49, 43, 13, 47, 43, 13, 44, + 44, 14, 41, 44, 14, 37, 44, 15, 33, 44, 15, 28, + 44, 16, 23, 44, 17, 19, 44, 18, 14, 44, 19, 10, + 45, 20, 4, 45, 21, 0, 45, 23, -5, 45, 24, -9, + 46, 26, -14, 46, 27, -18, 46, 29, -23, 47, 31, -27, + 47, 33, -31, 48, 34, -36, 48, 36, -40, 48, 38, -44, + 49, 40, -47, 49, 42, -52, 50, 44, -56, 50, 47, -59, + 51, 49, -63, 51, 51, -67, 52, 53, -71, 53, 55, -74, + 44, 10, 52, 44, 11, 50, 44, 11, 48, 45, 11, 45, + 45, 11, 42, 45, 12, 38, 45, 12, 34, 45, 13, 29, + 45, 14, 25, 45, 15, 20, 45, 15, 16, 45, 17, 11, + 46, 18, 6, 46, 19, 1, 46, 20, -3, 46, 22, -8, + 47, 23, -12, 47, 25, -17, 47, 27, -21, 48, 28, -25, + 48, 30, -30, 48, 32, -34, 49, 34, -38, 49, 36, -42, + 50, 38, -46, 50, 40, -50, 51, 42, -54, 51, 44, -58, + 52, 46, -62, 52, 49, -66, 53, 51, -69, 53, 53, -73, + 45, 8, 53, 45, 8, 51, 46, 8, 49, 46, 9, 46, + 46, 9, 43, 46, 9, 39, 46, 10, 35, 46, 11, 30, + 46, 11, 26, 46, 12, 21, 46, 13, 17, 46, 14, 12, + 47, 15, 7, 47, 17, 3, 47, 18, -2, 47, 19, -6, + 48, 21, -11, 48, 23, -15, 48, 24, -20, 49, 26, -24, + 49, 28, -29, 49, 30, -33, 50, 32, -37, 50, 34, -41, + 51, 36, -45, 51, 38, -49, 52, 40, -53, 52, 42, -57, + 53, 44, -60, 53, 47, -64, 54, 49, -68, 54, 51, -72, + 47, 6, 53, 47, 6, 52, 47, 6, 50, 47, 6, 47, + 47, 7, 44, 47, 7, 40, 47, 8, 36, 47, 8, 32, + 47, 9, 27, 47, 10, 23, 47, 11, 18, 48, 12, 14, + 48, 13, 9, 48, 14, 4, 48, 16, 0, 48, 17, -5, + 49, 19, -9, 49, 20, -14, 49, 22, -18, 50, 24, -22, + 50, 26, -27, 50, 28, -31, 51, 29, -35, 51, 31, -39, + 52, 33, -43, 52, 36, -48, 52, 38, -51, 53, 40, -55, + 53, 42, -59, 54, 44, -63, 54, 47, -67, 55, 49, -70, + 48, 3, 54, 48, 4, 52, 48, 4, 51, 48, 4, 48, + 48, 4, 45, 48, 5, 41, 48, 5, 37, 48, 6, 33, + 48, 7, 29, 48, 8, 24, 48, 8, 20, 49, 9, 15, + 49, 11, 10, 49, 12, 6, 49, 13, 1, 49, 15, -3, + 50, 16, -8, 50, 18, -12, 50, 20, -17, 51, 21, -21, + 51, 23, -25, 51, 25, -30, 52, 27, -34, 52, 29, -38, + 52, 31, -42, 53, 33, -46, 53, 35, -50, 54, 38, -54, + 54, 40, -57, 55, 42, -62, 55, 44, -65, 56, 47, -69, + 49, 1, 55, 49, 1, 53, 49, 1, 52, 49, 2, 49, + 49, 2, 46, 49, 2, 42, 49, 3, 39, 49, 4, 34, + 49, 4, 30, 49, 5, 26, 49, 6, 21, 50, 7, 17, + 50, 8, 12, 50, 10, 7, 50, 11, 3, 50, 12, -1, + 51, 14, -6, 51, 16, -11, 51, 17, -15, 52, 19, -19, + 52, 21, -24, 52, 23, -28, 53, 25, -32, 53, 27, -36, + 53, 29, -40, 54, 31, -45, 54, 33, -48, 55, 35, -52, + 55, 37, -56, 56, 40, -60, 56, 42, -64, 57, 44, -67, + 50, -1, 56, 50, -1, 54, 50, -1, 52, 50, -1, 50, + 50, 0, 47, 50, 0, 44, 50, 1, 40, 50, 1, 35, + 50, 2, 31, 50, 3, 27, 51, 4, 23, 51, 5, 18, + 51, 6, 13, 51, 7, 9, 51, 9, 4, 52, 10, 0, + 52, 12, -5, 52, 13, -9, 52, 15, -13, 53, 17, -18, + 53, 19, -22, 53, 20, -27, 54, 22, -31, 54, 24, -35, + 54, 26, -39, 55, 29, -43, 55, 31, -47, 56, 33, -51, + 56, 35, -55, 57, 38, -59, 57, 40, -62, 58, 42, -66, + 51, -3, 57, 51, -3, 55, 51, -3, 53, 51, -3, 51, + 51, -3, 48, 51, -2, 45, 51, -2, 41, 51, -1, 37, + 51, 0, 33, 52, 1, 28, 52, 2, 24, 52, 3, 20, + 52, 4, 15, 52, 5, 10, 52, 6, 6, 53, 8, 2, + 53, 9, -3, 53, 11, -8, 53, 13, -12, 54, 14, -16, + 54, 16, -21, 54, 18, -25, 55, 20, -29, 55, 22, -33, + 55, 24, -37, 56, 26, -41, 56, 28, -45, 57, 31, -49, + 57, 33, -53, 58, 35, -57, 58, 37, -61, 58, 40, -65, + 52, -6, 57, 52, -6, 56, 52, -5, 54, 52, -5, 52, + 52, -5, 49, 52, -4, 46, 52, -4, 42, 52, -3, 38, + 53, -2, 34, 53, -2, 30, 53, -1, 26, 53, 0, 21, + 53, 1, 16, 53, 3, 12, 53, 4, 8, 54, 5, 3, + 54, 7, -2, 54, 9, -6, 54, 10, -10, 55, 12, -15, + 55, 14, -19, 55, 16, -23, 56, 18, -27, 56, 20, -32, + 56, 22, -36, 57, 24, -40, 57, 26, -44, 58, 28, -48, + 58, 30, -52, 58, 33, -56, 59, 35, -59, 59, 37, -63, + 54, -8, 58, 54, -8, 57, 54, -8, 55, 54, -8, 53, + 54, -8, 50, 54, -7, 47, 54, -7, 44, 54, -6, 40, + 54, -5, 36, 54, -4, 32, 54, -4, 27, 54, -3, 23, + 54, -1, 18, 55, 0, 14, 55, 1, 9, 55, 3, 5, + 55, 4, 0, 56, 6, -4, 56, 7, -8, 56, 9, -13, + 56, 11, -17, 57, 13, -21, 57, 15, -25, 57, 17, -30, + 58, 19, -34, 58, 21, -38, 58, 23, -42, 59, 25, -46, + 59, 28, -50, 60, 30, -54, 60, 32, -58, 61, 35, -61, + 55, -11, 59, 55, -10, 58, 55, -10, 56, 55, -10, 54, + 55, -10, 51, 55, -9, 48, 55, -9, 45, 55, -8, 41, + 55, -7, 37, 55, -7, 33, 55, -6, 29, 55, -5, 25, + 56, -4, 20, 56, -2, 15, 56, -1, 11, 56, 0, 7, + 56, 2, 2, 57, 3, -2, 57, 5, -7, 57, 7, -11, + 57, 9, -16, 58, 11, -20, 58, 12, -24, 58, 14, -28, + 59, 16, -32, 59, 19, -36, 59, 21, -40, 60, 23, -44, + 60, 25, -48, 61, 28, -52, 61, 30, -56, 62, 32, -60, + 56, -13, 60, 56, -13, 59, 56, -12, 57, 56, -12, 55, + 56, -12, 53, 56, -11, 50, 56, -11, 46, 56, -10, 42, + 56, -10, 38, 56, -9, 34, 56, -8, 30, 57, -7, 26, + 57, -6, 21, 57, -5, 17, 57, -3, 13, 57, -2, 8, + 57, 0, 3, 58, 1, -1, 58, 3, -5, 58, 4, -9, + 58, 6, -14, 59, 8, -18, 59, 10, -22, 59, 12, -26, + 60, 14, -30, 60, 16, -35, 60, 19, -39, 61, 21, -43, + 61, 23, -46, 62, 25, -51, 62, 28, -54, 62, 30, -58, + 57, -15, 61, 57, -15, 60, 57, -15, 58, 57, -14, 56, + 57, -14, 54, 57, -14, 51, 57, -13, 47, 57, -12, 43, + 57, -12, 40, 57, -11, 36, 58, -10, 32, 58, -9, 27, + 58, -8, 23, 58, -7, 18, 58, -6, 14, 58, -4, 10, + 59, -3, 5, 59, -1, 1, 59, 0, -3, 59, 2, -8, + 60, 4, -12, 60, 6, -16, 60, 8, -21, 60, 10, -25, + 61, 12, -29, 61, 14, -33, 61, 16, -37, 62, 18, -41, + 62, 21, -45, 63, 23, -49, 63, 25, -53, 63, 28, -57, + 58, -17, 62, 58, -17, 61, 58, -17, 59, 58, -16, 57, + 58, -16, 55, 58, -16, 52, 58, -15, 49, 58, -15, 45, + 59, -14, 41, 59, -13, 37, 59, -12, 33, 59, -11, 29, + 59, -10, 24, 59, -9, 20, 59, -8, 16, 59, -6, 11, + 60, -5, 7, 60, -3, 2, 60, -2, -2, 60, 0, -6, + 61, 2, -11, 61, 4, -15, 61, 6, -19, 61, 7, -23, + 62, 9, -27, 62, 12, -32, 62, 14, -35, 63, 16, -39, + 63, 18, -43, 64, 21, -48, 64, 23, -51, 64, 25, -55, + 59, -19, 63, 59, -19, 61, 59, -19, 60, 59, -18, 58, + 59, -18, 56, 59, -18, 53, 60, -17, 50, 60, -17, 46, + 60, -16, 42, 60, -15, 39, 60, -14, 35, 60, -13, 30, + 60, -12, 26, 60, -11, 22, 60, -10, 17, 61, -9, 13, + 61, -7, 8, 61, -6, 4, 61, -4, 0, 61, -2, -4, + 62, 0, -9, 62, 1, -13, 62, 3, -17, 63, 5, -21, + 63, 7, -25, 63, 9, -30, 64, 12, -34, 64, 14, -38, + 64, 16, -42, 65, 18, -46, 65, 21, -50, 65, 23, -53, + 61, -21, 63, 61, -21, 62, 61, -21, 61, 61, -21, 59, + 61, -20, 57, 61, -20, 54, 61, -19, 51, 61, -19, 47, + 61, -18, 44, 61, -17, 40, 61, -17, 36, 61, -16, 32, + 61, -14, 27, 61, -13, 23, 62, -12, 19, 62, -11, 15, + 62, -9, 10, 62, -8, 6, 62, -6, 1, 63, -5, -3, + 63, -3, -7, 63, -1, -12, 63, 1, -16, 64, 3, -20, + 64, 5, -24, 64, 7, -28, 65, 9, -32, 65, 11, -36, + 65, 14, -40, 66, 16, -44, 66, 18, -48, 66, 21, -52, + 62, -23, 64, 62, -23, 63, 62, -23, 62, 62, -23, 60, + 62, -22, 58, 62, -22, 55, 62, -21, 52, 62, -21, 49, + 62, -20, 45, 62, -19, 41, 62, -19, 37, 62, -18, 33, + 62, -17, 29, 63, -15, 25, 63, -14, 20, 63, -13, 16, + 63, -11, 11, 63, -10, 7, 63, -8, 3, 64, -7, -1, + 64, -5, -6, 64, -3, -10, 64, -1, -14, 65, 1, -18, + 65, 3, -22, 65, 5, -27, 66, 7, -31, 66, 9, -34, + 66, 11, -38, 67, 14, -43, 67, 16, -47, 67, 18, -50, + 63, -25, 65, 63, -25, 64, 63, -25, 63, 63, -25, 61, + 63, -24, 59, 63, -24, 56, 63, -23, 53, 63, -23, 50, + 63, -22, 46, 63, -21, 43, 63, -21, 39, 63, -20, 35, + 64, -19, 30, 64, -18, 26, 64, -16, 22, 64, -15, 18, + 64, -14, 13, 64, -12, 9, 65, -11, 5, 65, -9, 1, + 65, -7, -4, 65, -5, -8, 66, -3, -12, 66, -2, -16, + 66, 0, -20, 66, 3, -25, 67, 5, -29, 67, 7, -33, + 67, 9, -37, 68, 12, -41, 68, 14, -45, 68, 16, -49, + 64, -27, 66, 64, -27, 65, 64, -27, 64, 64, -26, 62, + 64, -26, 60, 64, -26, 57, 64, -25, 55, 64, -25, 51, + 64, -24, 48, 64, -23, 44, 65, -23, 40, 65, -22, 36, + 65, -21, 32, 65, -20, 28, 65, -18, 24, 65, -17, 19, + 65, -16, 15, 66, -14, 10, 66, -13, 6, 66, -11, 2, + 66, -9, -3, 66, -7, -7, 67, -6, -11, 67, -4, -15, + 67, -2, -19, 68, 0, -23, 68, 3, -27, 68, 5, -31, + 68, 7, -35, 69, 9, -39, 69, 11, -43, 70, 14, -47, + 65, -29, 67, 65, -29, 66, 65, -29, 65, 65, -28, 63, + 65, -28, 61, 65, -28, 59, 65, -27, 56, 65, -27, 52, + 66, -26, 49, 66, -25, 45, 66, -25, 42, 66, -24, 38, + 66, -23, 33, 66, -22, 29, 66, -20, 25, 66, -19, 21, + 67, -18, 16, 67, -16, 12, 67, -15, 8, 67, -13, 4, + 67, -11, -1, 68, -10, -5, 68, -8, -9, 68, -6, -13, + 68, -4, -17, 69, -2, -22, 69, 0, -26, 69, 2, -30, + 69, 5, -33, 70, 7, -38, 70, 9, -42, 71, 11, -45, + 66, -31, 68, 66, -31, 67, 66, -31, 66, 66, -30, 64, + 66, -30, 62, 67, -30, 60, 67, -29, 57, 67, -29, 54, + 67, -28, 50, 67, -27, 47, 67, -27, 43, 67, -26, 39, + 67, -25, 35, 67, -24, 31, 67, -22, 27, 67, -21, 22, + 68, -20, 18, 68, -18, 14, 68, -17, 10, 68, -15, 5, + 68, -13, 1, 69, -12, -3, 69, -10, -7, 69, -8, -11, + 69, -6, -15, 70, -4, -20, 70, -2, -24, 70, 0, -28, + 71, 2, -32, 71, 5, -36, 71, 7, -40, 72, 9, -44, + 68, -33, 69, 68, -33, 68, 68, -32, 67, 68, -32, 65, + 68, -32, 63, 68, -32, 61, 68, -31, 58, 68, -31, 55, + 68, -30, 51, 68, -29, 48, 68, -29, 44, 68, -28, 41, + 68, -27, 36, 68, -26, 32, 68, -24, 28, 69, -23, 24, + 69, -22, 19, 69, -20, 15, 69, -19, 11, 69, -17, 7, + 70, -16, 2, 70, -14, -2, 70, -12, -6, 70, -10, -10, + 70, -8, -14, 71, -6, -18, 71, -4, -22, 71, -2, -26, + 72, 0, -30, 72, 3, -34, 72, 5, -38, 73, 7, -42, + 69, -35, 70, 69, -34, 69, 69, -34, 68, 69, -34, 66, + 69, -34, 64, 69, -33, 62, 69, -33, 59, 69, -32, 56, + 69, -32, 53, 69, -31, 49, 69, -30, 46, 69, -30, 42, + 69, -29, 38, 70, -28, 34, 70, -26, 30, 70, -25, 26, + 70, -24, 21, 70, -22, 17, 70, -21, 13, 70, -19, 9, + 71, -18, 4, 71, -16, 0, 71, -14, -4, 71, -12, -8, + 72, -10, -12, 72, -8, -17, 72, -6, -21, 72, -4, -25, + 73, -2, -28, 73, 0, -33, 73, 2, -37, 74, 5, -40, + 70, -36, 71, 70, -36, 70, 70, -36, 69, 70, -36, 67, + 70, -36, 65, 70, -35, 63, 70, -35, 60, 70, -34, 57, + 70, -34, 54, 70, -33, 51, 70, -32, 47, 70, -31, 43, + 71, -30, 39, 71, -29, 35, 71, -28, 31, 71, -27, 27, + 71, -26, 23, 71, -24, 18, 71, -23, 14, 72, -21, 10, + 72, -20, 6, 72, -18, 2, 72, -16, -2, 72, -14, -6, + 73, -13, -10, 73, -10, -15, 73, -8, -19, 74, -6, -23, + 74, -4, -27, 74, -2, -31, 74, 0, -35, 75, 3, -39, + 71, -38, 72, 71, -38, 71, 71, -38, 70, 71, -38, 68, + 71, -37, 66, 71, -37, 64, 71, -37, 62, 71, -36, 58, + 71, -36, 55, 71, -35, 52, 72, -34, 48, 72, -33, 45, + 72, -32, 41, 72, -31, 37, 72, -30, 33, 72, -29, 29, + 72, -28, 24, 72, -26, 20, 73, -25, 16, 73, -23, 12, + 73, -22, 7, 73, -20, 3, 73, -18, -1, 74, -17, -5, + 74, -15, -9, 74, -13, -13, 74, -11, -17, 75, -9, -21, + 75, -6, -25, 75, -4, -29, 76, -2, -33, 76, 0, -37, + 72, -40, 72, 72, -40, 72, 72, -40, 71, 72, -39, 69, + 72, -39, 67, 72, -39, 65, 72, -38, 63, 73, -38, 60, + 73, -37, 57, 73, -37, 53, 73, -36, 50, 73, -35, 46, + 73, -34, 42, 73, -33, 38, 73, -32, 34, 73, -31, 30, + 73, -30, 26, 74, -28, 22, 74, -27, 18, 74, -25, 14, + 74, -24, 9, 74, -22, 5, 75, -20, 1, 75, -19, -3, + 75, -17, -7, 75, -15, -12, 75, -13, -16, 76, -11, -20, + 76, -9, -23, 76, -6, -28, 77, -4, -32, 77, -2, -35, + 74, -42, 73, 74, -42, 72, 74, -41, 72, 74, -41, 70, + 74, -41, 68, 74, -41, 66, 74, -40, 64, 74, -40, 61, + 74, -39, 58, 74, -39, 55, 74, -38, 51, 74, -37, 48, + 74, -36, 43, 74, -35, 40, 74, -34, 36, 74, -33, 32, + 75, -32, 27, 75, -30, 23, 75, -29, 19, 75, -27, 15, + 75, -26, 11, 75, -24, 7, 76, -22, 3, 76, -21, -2, + 76, -19, -6, 76, -17, -10, 77, -15, -14, 77, -13, -18, + 77, -11, -22, 77, -8, -26, 78, -6, -30, 78, -4, -34, + 75, -43, 74, 75, -43, 73, 75, -43, 73, 75, -43, 71, + 75, -43, 69, 75, -42, 67, 75, -42, 65, 75, -41, 62, + 75, -41, 59, 75, -40, 56, 75, -40, 52, 75, -39, 49, + 75, -38, 45, 75, -37, 41, 75, -36, 37, 76, -35, 33, + 76, -33, 29, 76, -32, 25, 76, -31, 21, 76, -29, 17, + 76, -28, 12, 77, -26, 8, 77, -24, 4, 77, -23, 0, + 77, -21, -4, 77, -19, -8, 78, -17, -12, 78, -15, -16, + 78, -13, -20, 78, -10, -24, 79, -8, -28, 79, -6, -32, + 76, -45, 75, 76, -45, 74, 76, -45, 73, 76, -45, 72, + 76, -44, 70, 76, -44, 68, 76, -44, 66, 76, -43, 63, + 76, -43, 60, 76, -42, 57, 76, -41, 54, 76, -41, 50, + 76, -40, 46, 77, -39, 42, 77, -38, 39, 77, -37, 35, + 77, -35, 30, 77, -34, 26, 77, -33, 22, 77, -31, 18, + 78, -30, 14, 78, -28, 10, 78, -26, 6, 78, -25, 2, + 78, -23, -2, 79, -21, -7, 79, -19, -11, 79, -17, -15, + 79, -15, -18, 80, -13, -23, 80, -10, -27, 80, -8, -31, + 77, -47, 76, 77, -47, 75, 77, -47, 74, 77, -46, 73, + 77, -46, 71, 77, -46, 70, 77, -45, 67, 77, -45, 64, + 77, -44, 61, 77, -44, 58, 77, -43, 55, 78, -42, 52, + 78, -41, 48, 78, -40, 44, 78, -39, 40, 78, -38, 36, + 78, -37, 32, 78, -36, 28, 78, -34, 24, 79, -33, 20, + 79, -31, 15, 79, -30, 11, 79, -28, 7, 79, -27, 3, + 79, -25, -1, 80, -23, -5, 80, -21, -9, 80, -19, -13, + 80, -17, -17, 81, -15, -21, 81, -13, -25, 81, -10, -29, + 79, -49, 77, 79, -49, 76, 79, -49, 76, 79, -48, 74, + 79, -48, 73, 79, -48, 71, 79, -48, 69, 79, -47, 66, + 79, -46, 63, 79, -46, 60, 79, -45, 57, 79, -44, 53, + 79, -44, 49, 79, -43, 46, 79, -42, 42, 79, -41, 38, + 80, -39, 34, 80, -38, 30, 80, -37, 26, 80, -35, 22, + 80, -34, 17, 80, -32, 13, 80, -31, 9, 81, -29, 5, + 81, -27, 2, 81, -25, -3, 81, -23, -7, 82, -21, -11, + 82, -19, -15, 82, -17, -19, 82, -15, -23, 83, -13, -27, + 80, -50, 78, 80, -50, 77, 80, -50, 77, 80, -50, 75, + 80, -50, 74, 80, -50, 72, 80, -49, 70, 80, -49, 67, + 80, -48, 64, 80, -48, 61, 80, -47, 58, 80, -46, 55, + 80, -45, 51, 80, -44, 47, 80, -43, 43, 81, -42, 40, + 81, -41, 35, 81, -40, 31, 81, -39, 27, 81, -37, 24, + 81, -36, 19, 81, -34, 15, 82, -33, 11, 82, -31, 7, + 82, -29, 3, 82, -27, -1, 82, -25, -5, 83, -23, -9, + 83, -21, -13, 83, -19, -17, 83, -17, -21, 84, -15, -25, + 81, -52, 79, 81, -52, 78, 81, -52, 78, 81, -52, 76, + 81, -51, 75, 81, -51, 73, 81, -51, 71, 81, -50, 68, + 81, -50, 65, 81, -49, 62, 81, -49, 59, 81, -48, 56, + 81, -47, 52, 82, -46, 49, 82, -45, 45, 82, -44, 41, + 82, -43, 37, 82, -42, 33, 82, -40, 29, 82, -39, 25, + 82, -37, 21, 83, -36, 17, 83, -34, 13, 83, -33, 9, + 83, -31, 5, 83, -29, 0, 84, -27, -4, 84, -25, -7, + 84, -23, -11, 84, -21, -16, 85, -19, -20, 85, -17, -23, + 82, -54, 80, 82, -54, 79, 82, -53, 79, 82, -53, 77, + 82, -53, 76, 82, -53, 74, 82, -52, 72, 82, -52, 69, + 82, -51, 67, 82, -51, 64, 82, -50, 61, 83, -50, 57, + 83, -49, 53, 83, -48, 50, 83, -47, 46, 83, -46, 43, + 83, -45, 38, 83, -43, 34, 83, -42, 31, 83, -41, 27, + 84, -39, 22, 84, -38, 18, 84, -36, 14, 84, -35, 10, + 84, -33, 6, 84, -31, 2, 85, -29, -2, 85, -27, -6, + 85, -25, -10, 85, -23, -14, 86, -21, -18, 86, -19, -22, + 83, -55, 81, 83, -55, 80, 83, -55, 79, 83, -55, 78, + 83, -55, 77, 83, -54, 75, 83, -54, 73, 83, -54, 70, + 84, -53, 68, 84, -52, 65, 84, -52, 62, 84, -51, 59, + 84, -50, 55, 84, -49, 51, 84, -49, 48, 84, -48, 44, + 84, -46, 40, 84, -45, 36, 84, -44, 32, 85, -43, 28, + 85, -41, 24, 85, -40, 20, 85, -38, 16, 85, -36, 12, + 85, -35, 8, 86, -33, 4, 86, -31, 0, 86, -29, -4, + 86, -27, -8, 86, -25, -12, 87, -23, -16, 87, -21, -20, + 84, -57, 82, 84, -57, 81, 84, -57, 80, 85, -56, 79, + 85, -56, 78, 85, -56, 76, 85, -56, 74, 85, -55, 72, + 85, -55, 69, 85, -54, 66, 85, -53, 63, 85, -53, 60, + 85, -52, 56, 85, -51, 53, 85, -50, 49, 85, -49, 45, + 85, -48, 41, 85, -47, 37, 86, -46, 34, 86, -44, 30, + 86, -43, 25, 86, -41, 21, 86, -40, 18, 86, -38, 14, + 87, -37, 10, 87, -35, 5, 87, -33, 1, 87, -31, -3, + 87, -29, -6, 88, -27, -11, 88, -25, -15, 88, -23, -18, + 86, -58, 83, 86, -58, 82, 86, -58, 81, 86, -58, 80, + 86, -58, 79, 86, -57, 77, 86, -57, 75, 86, -57, 73, + 86, -56, 70, 86, -56, 67, 86, -55, 64, 86, -54, 61, + 86, -54, 58, 86, -53, 54, 86, -52, 51, 86, -51, 47, + 86, -50, 43, 87, -49, 39, 87, -47, 35, 87, -46, 31, + 87, -45, 27, 87, -43, 23, 87, -42, 19, 87, -40, 15, + 88, -38, 11, 88, -37, 7, 88, -35, 3, 88, -33, -1, + 88, -31, -5, 89, -29, -9, 89, -27, -13, 89, -25, -17, + 87, -60, 84, 87, -60, 83, 87, -60, 82, 87, -59, 81, + 87, -59, 80, 87, -59, 78, 87, -59, 76, 87, -58, 74, + 87, -58, 71, 87, -57, 69, 87, -57, 66, 87, -56, 63, + 87, -55, 59, 87, -54, 55, 87, -53, 52, 88, -52, 48, + 88, -51, 44, 88, -50, 40, 88, -49, 37, 88, -48, 33, + 88, -46, 28, 88, -45, 25, 88, -43, 21, 89, -42, 17, + 89, -40, 13, 89, -38, 9, 89, -37, 5, 89, -35, 1, + 90, -33, -3, 90, -31, -7, 90, -29, -11, 90, -27, -15, + 88, -61, 85, 88, -61, 84, 88, -61, 83, 88, -61, 82, + 88, -61, 81, 88, -60, 79, 88, -60, 77, 88, -60, 75, + 88, -59, 72, 88, -59, 70, 88, -58, 67, 88, -58, 64, + 88, -57, 60, 89, -56, 57, 89, -55, 53, 89, -54, 50, + 89, -53, 46, 89, -52, 42, 89, -51, 38, 89, -49, 34, + 89, -48, 30, 89, -47, 26, 90, -45, 22, 90, -44, 18, + 90, -42, 15, 90, -40, 10, 90, -38, 6, 90, -37, 2, + 91, -35, -1, 91, -33, -6, 91, -31, -10, 91, -29, -13, + 89, -63, 85, 89, -63, 85, 89, -63, 84, 89, -62, 83, + 89, -62, 82, 89, -62, 80, 89, -62, 78, 89, -61, 76, + 89, -61, 74, 89, -60, 71, 89, -60, 68, 90, -59, 65, + 90, -58, 62, 90, -57, 58, 90, -57, 55, 90, -56, 51, + 90, -55, 47, 90, -53, 43, 90, -52, 40, 90, -51, 36, + 90, -50, 32, 91, -48, 28, 91, -47, 24, 91, -45, 20, + 91, -44, 16, 91, -42, 12, 91, -40, 8, 92, -39, 4, + 92, -37, 0, 92, -35, -4, 92, -33, -8, 92, -31, -12, + 90, -64, 86, 90, -64, 86, 90, -64, 85, 90, -64, 84, + 90, -64, 83, 90, -63, 81, 90, -63, 79, 91, -63, 77, + 91, -62, 75, 91, -62, 72, 91, -61, 69, 91, -61, 66, + 91, -60, 63, 91, -59, 60, 91, -58, 56, 91, -57, 53, + 91, -56, 49, 91, -55, 45, 91, -54, 41, 91, -53, 37, + 92, -51, 33, 92, -50, 29, 92, -49, 25, 92, -47, 22, + 92, -46, 18, 92, -44, 13, 93, -42, 9, 93, -40, 6, + 93, -39, 2, 93, -37, -3, 93, -35, -6, 94, -33, -10, + 30, 53, 44, 30, 54, 41, 30, 54, 37, 30, 54, 32, + 30, 54, 27, 31, 54, 22, 31, 55, 17, 31, 55, 11, + 31, 56, 6, 31, 57, 1, 32, 57, -4, 32, 58, -9, + 32, 59, -14, 33, 60, -18, 33, 61, -23, 33, 62, -27, + 34, 63, -32, 34, 64, -36, 35, 65, -41, 35, 67, -45, + 36, 68, -49, 37, 70, -53, 37, 71, -57, 38, 72, -60, + 38, 74, -64, 39, 75, -68, 40, 77, -72, 41, 78, -75, + 41, 80, -79, 42, 82, -82, 43, 83, -86, 44, 85, -89, + 30, 53, 44, 30, 53, 41, 30, 53, 37, 31, 53, 32, + 31, 54, 27, 31, 54, 22, 31, 54, 17, 31, 55, 11, + 31, 55, 6, 32, 56, 1, 32, 57, -3, 32, 57, -8, + 32, 58, -14, 33, 59, -18, 33, 60, -23, 34, 61, -27, + 34, 63, -32, 35, 64, -36, 35, 65, -40, 36, 66, -44, + 36, 68, -49, 37, 69, -53, 37, 70, -56, 38, 72, -60, + 39, 73, -64, 39, 75, -68, 40, 76, -71, 41, 78, -75, + 41, 80, -78, 42, 81, -82, 43, 83, -85, 44, 84, -89, + 31, 52, 44, 31, 52, 41, 31, 52, 37, 31, 53, 32, + 31, 53, 28, 31, 53, 23, 31, 54, 17, 31, 54, 12, + 31, 55, 7, 32, 55, 2, 32, 56, -3, 32, 57, -8, + 33, 58, -13, 33, 59, -18, 33, 60, -22, 34, 61, -27, + 34, 62, -32, 35, 63, -36, 35, 64, -40, 36, 66, -44, + 36, 67, -49, 37, 69, -52, 37, 70, -56, 38, 71, -60, + 39, 73, -64, 39, 75, -68, 40, 76, -71, 41, 78, -75, + 42, 79, -78, 42, 81, -82, 43, 82, -85, 44, 84, -88, + 31, 52, 44, 31, 52, 41, 31, 52, 37, 31, 52, 33, + 31, 52, 28, 31, 53, 23, 31, 53, 18, 31, 54, 12, + 32, 54, 7, 32, 55, 2, 32, 56, -3, 32, 56, -8, + 33, 57, -13, 33, 58, -18, 34, 59, -22, 34, 60, -26, + 34, 62, -31, 35, 63, -36, 35, 64, -40, 36, 65, -44, + 37, 67, -48, 37, 68, -52, 38, 69, -56, 38, 71, -60, + 39, 72, -63, 40, 74, -67, 40, 76, -71, 41, 77, -74, + 42, 79, -78, 42, 80, -82, 43, 82, -85, 44, 84, -88, + 31, 51, 44, 31, 51, 41, 31, 51, 38, 31, 51, 33, + 31, 52, 28, 31, 52, 23, 32, 52, 18, 32, 53, 12, + 32, 53, 7, 32, 54, 2, 32, 55, -2, 33, 56, -7, + 33, 57, -13, 33, 58, -17, 34, 59, -22, 34, 60, -26, + 35, 61, -31, 35, 62, -35, 36, 63, -39, 36, 65, -43, + 37, 66, -48, 37, 67, -52, 38, 69, -56, 38, 70, -59, + 39, 72, -63, 40, 74, -67, 40, 75, -71, 41, 77, -74, + 42, 78, -78, 43, 80, -81, 43, 82, -85, 44, 83, -88, + 31, 50, 45, 31, 50, 41, 31, 50, 38, 32, 50, 33, + 32, 51, 28, 32, 51, 23, 32, 51, 18, 32, 52, 13, + 32, 53, 8, 32, 53, 3, 33, 54, -2, 33, 55, -7, + 33, 56, -12, 34, 57, -17, 34, 58, -21, 34, 59, -26, + 35, 60, -31, 35, 61, -35, 36, 63, -39, 36, 64, -43, + 37, 65, -48, 38, 67, -51, 38, 68, -55, 39, 70, -59, + 39, 71, -63, 40, 73, -67, 41, 74, -70, 41, 76, -74, + 42, 78, -77, 43, 79, -81, 44, 81, -84, 44, 83, -88, + 32, 49, 45, 32, 49, 42, 32, 49, 38, 32, 49, 33, + 32, 50, 29, 32, 50, 24, 32, 51, 19, 32, 51, 13, + 33, 52, 8, 33, 52, 3, 33, 53, -2, 33, 54, -6, + 34, 55, -12, 34, 56, -16, 34, 57, -21, 35, 58, -25, + 35, 59, -30, 36, 60, -34, 36, 62, -39, 37, 63, -43, + 37, 65, -47, 38, 66, -51, 38, 67, -55, 39, 69, -59, + 40, 70, -62, 40, 72, -66, 41, 74, -70, 42, 75, -73, + 42, 77, -77, 43, 79, -81, 44, 80, -84, 44, 82, -87, + 32, 48, 45, 32, 48, 42, 32, 48, 39, 32, 48, 34, + 32, 49, 29, 32, 49, 24, 33, 49, 19, 33, 50, 14, + 33, 51, 9, 33, 51, 4, 33, 52, -1, 34, 53, -6, + 34, 54, -11, 34, 55, -16, 35, 56, -20, 35, 57, -25, + 36, 58, -30, 36, 59, -34, 36, 61, -38, 37, 62, -42, + 38, 64, -47, 38, 65, -50, 39, 67, -54, 39, 68, -58, + 40, 70, -62, 41, 71, -66, 41, 73, -69, 42, 74, -73, + 43, 76, -76, 43, 78, -80, 44, 80, -84, 45, 81, -87, + 33, 47, 45, 33, 47, 42, 33, 47, 39, 33, 47, 34, + 33, 47, 30, 33, 48, 25, 33, 48, 20, 33, 49, 14, + 33, 49, 9, 34, 50, 4, 34, 51, 0, 34, 52, -5, + 34, 53, -10, 35, 54, -15, 35, 55, -20, 36, 56, -24, + 36, 57, -29, 36, 58, -33, 37, 60, -37, 37, 61, -41, + 38, 63, -46, 38, 64, -50, 39, 65, -54, 40, 67, -58, + 40, 69, -61, 41, 70, -65, 42, 72, -69, 42, 74, -72, + 43, 75, -76, 44, 77, -80, 44, 79, -83, 45, 80, -86, + 33, 45, 46, 33, 45, 43, 33, 45, 39, 33, 46, 35, + 33, 46, 30, 33, 46, 25, 34, 47, 21, 34, 47, 15, + 34, 48, 10, 34, 49, 5, 34, 49, 0, 35, 50, -4, + 35, 51, -10, 35, 52, -14, 36, 53, -19, 36, 54, -23, + 36, 56, -28, 37, 57, -33, 37, 58, -37, 38, 60, -41, + 38, 61, -45, 39, 63, -49, 39, 64, -53, 40, 66, -57, + 41, 67, -61, 41, 69, -65, 42, 71, -68, 43, 73, -72, + 43, 74, -75, 44, 76, -79, 45, 78, -83, 45, 79, -86, + 34, 44, 46, 34, 44, 43, 34, 44, 40, 34, 44, 35, + 34, 44, 31, 34, 45, 26, 34, 45, 21, 34, 46, 16, + 34, 46, 11, 35, 47, 6, 35, 48, 1, 35, 49, -4, + 35, 50, -9, 36, 51, -14, 36, 52, -18, 36, 53, -23, + 37, 54, -27, 37, 56, -32, 38, 57, -36, 38, 58, -40, + 39, 60, -45, 39, 62, -49, 40, 63, -52, 40, 65, -56, + 41, 66, -60, 42, 68, -64, 42, 70, -68, 43, 71, -71, + 44, 73, -75, 44, 75, -79, 45, 77, -82, 46, 78, -85, + 34, 42, 47, 34, 42, 43, 34, 42, 40, 34, 42, 36, + 35, 42, 32, 35, 43, 27, 35, 43, 22, 35, 44, 17, + 35, 44, 12, 35, 45, 7, 36, 46, 2, 36, 47, -3, + 36, 48, -8, 36, 49, -13, 37, 50, -17, 37, 51, -22, + 38, 53, -26, 38, 54, -31, 38, 55, -35, 39, 57, -39, + 39, 58, -44, 40, 60, -48, 40, 61, -51, 41, 63, -55, + 42, 65, -59, 42, 66, -63, 43, 68, -67, 43, 70, -70, + 44, 72, -74, 45, 73, -78, 45, 75, -81, 46, 77, -85, + 35, 40, 47, 35, 40, 44, 35, 40, 41, 35, 40, 37, + 35, 41, 32, 35, 41, 28, 35, 42, 23, 36, 42, 18, + 36, 43, 13, 36, 43, 8, 36, 44, 3, 36, 45, -2, + 37, 46, -7, 37, 47, -12, 37, 48, -16, 38, 50, -21, + 38, 51, -26, 39, 52, -30, 39, 54, -34, 39, 55, -38, + 40, 57, -43, 40, 58, -47, 41, 60, -51, 41, 62, -54, + 42, 63, -58, 43, 65, -62, 43, 67, -66, 44, 68, -70, + 45, 70, -73, 45, 72, -77, 46, 74, -80, 47, 76, -84, + 36, 38, 47, 36, 38, 44, 36, 38, 41, 36, 39, 37, + 36, 39, 33, 36, 39, 29, 36, 40, 24, 36, 40, 18, + 36, 41, 14, 37, 42, 9, 37, 42, 4, 37, 43, -1, + 37, 44, -6, 38, 46, -11, 38, 47, -15, 38, 48, -20, + 39, 49, -25, 39, 51, -29, 40, 52, -33, 40, 54, -37, + 41, 55, -42, 41, 57, -46, 41, 58, -50, 42, 60, -54, + 43, 62, -57, 43, 64, -62, 44, 65, -65, 44, 67, -69, + 45, 69, -72, 46, 71, -76, 46, 73, -80, 47, 74, -83, + 36, 36, 48, 36, 36, 45, 36, 36, 42, 37, 37, 38, + 37, 37, 34, 37, 37, 29, 37, 38, 25, 37, 38, 19, + 37, 39, 15, 37, 40, 10, 38, 41, 5, 38, 42, 0, + 38, 43, -5, 38, 44, -10, 39, 45, -14, 39, 46, -19, + 39, 48, -24, 40, 49, -28, 40, 50, -32, 41, 52, -36, + 41, 53, -41, 42, 55, -45, 42, 57, -49, 43, 58, -53, + 43, 60, -56, 44, 62, -61, 44, 64, -64, 45, 65, -68, + 46, 67, -72, 46, 69, -75, 47, 71, -79, 48, 73, -82, + 37, 34, 48, 37, 34, 45, 37, 34, 43, 37, 35, 39, + 37, 35, 35, 37, 35, 30, 38, 36, 26, 38, 36, 20, + 38, 37, 16, 38, 38, 11, 38, 39, 6, 39, 40, 1, + 39, 41, -4, 39, 42, -9, 39, 43, -13, 40, 44, -18, + 40, 46, -23, 40, 47, -27, 41, 48, -31, 41, 50, -35, + 42, 52, -40, 42, 53, -44, 43, 55, -48, 43, 57, -52, + 44, 58, -55, 44, 60, -60, 45, 62, -63, 46, 64, -67, + 46, 66, -71, 47, 68, -75, 47, 70, -78, 48, 71, -81, + 38, 32, 49, 38, 32, 46, 38, 32, 43, 38, 33, 39, + 38, 33, 35, 38, 33, 31, 38, 34, 27, 39, 34, 21, + 39, 35, 17, 39, 36, 12, 39, 37, 7, 39, 38, 2, + 40, 39, -3, 40, 40, -7, 40, 41, -12, 40, 42, -16, + 41, 44, -21, 41, 45, -26, 42, 47, -30, 42, 48, -34, + 42, 50, -39, 43, 51, -43, 43, 53, -47, 44, 55, -51, + 44, 57, -54, 45, 59, -59, 46, 60, -62, 46, 62, -66, + 47, 64, -70, 47, 66, -74, 48, 68, -77, 49, 70, -81, + 39, 30, 49, 39, 30, 46, 39, 30, 44, 39, 30, 40, + 39, 31, 36, 39, 31, 32, 39, 32, 28, 39, 32, 22, + 39, 33, 18, 40, 34, 13, 40, 35, 8, 40, 35, 4, + 40, 37, -2, 41, 38, -6, 41, 39, -11, 41, 40, -15, + 42, 42, -20, 42, 43, -25, 42, 45, -29, 43, 46, -33, + 43, 48, -38, 44, 50, -42, 44, 51, -46, 45, 53, -50, + 45, 55, -53, 46, 57, -58, 46, 59, -61, 47, 60, -65, + 47, 62, -69, 48, 64, -73, 49, 66, -76, 49, 68, -80, + 40, 28, 50, 40, 28, 47, 40, 28, 45, 40, 28, 41, + 40, 29, 37, 40, 29, 33, 40, 29, 29, 40, 30, 23, + 40, 31, 19, 41, 32, 14, 41, 32, 9, 41, 33, 5, + 41, 35, -1, 41, 36, -5, 42, 37, -10, 42, 38, -14, + 42, 40, -19, 43, 41, -23, 43, 43, -28, 43, 44, -32, + 44, 46, -36, 44, 48, -40, 45, 49, -44, 45, 51, -48, + 46, 53, -52, 46, 55, -57, 47, 57, -60, 47, 58, -64, + 48, 60, -68, 49, 63, -72, 49, 64, -75, 50, 66, -79, + 41, 26, 50, 41, 26, 48, 41, 26, 45, 41, 26, 42, + 41, 26, 38, 41, 27, 34, 41, 27, 30, 41, 28, 25, + 41, 29, 20, 41, 29, 15, 42, 30, 11, 42, 31, 6, + 42, 32, 1, 42, 33, -4, 43, 35, -8, 43, 36, -13, + 43, 37, -18, 44, 39, -22, 44, 40, -26, 44, 42, -31, + 45, 44, -35, 45, 45, -39, 46, 47, -43, 46, 49, -47, + 46, 51, -51, 47, 53, -55, 48, 55, -59, 48, 57, -63, + 49, 58, -66, 49, 61, -70, 50, 63, -74, 50, 65, -77, + 42, 23, 51, 42, 23, 48, 42, 24, 46, 42, 24, 43, + 42, 24, 39, 42, 25, 35, 42, 25, 31, 42, 26, 26, + 42, 26, 21, 42, 27, 16, 42, 28, 12, 43, 29, 7, + 43, 30, 2, 43, 31, -3, 43, 32, -7, 44, 34, -12, + 44, 35, -16, 44, 37, -21, 45, 38, -25, 45, 40, -29, + 45, 42, -34, 46, 43, -38, 46, 45, -42, 47, 47, -46, + 47, 49, -50, 48, 51, -54, 48, 53, -58, 49, 55, -62, + 49, 57, -65, 50, 59, -69, 51, 61, -73, 51, 63, -76, + 42, 21, 51, 42, 21, 49, 43, 21, 47, 43, 22, 44, + 43, 22, 40, 43, 22, 36, 43, 23, 32, 43, 23, 27, + 43, 24, 22, 43, 25, 18, 43, 26, 13, 44, 27, 8, + 44, 28, 3, 44, 29, -1, 44, 30, -6, 45, 32, -10, + 45, 33, -15, 45, 35, -19, 46, 36, -24, 46, 38, -28, + 46, 40, -33, 47, 41, -37, 47, 43, -41, 48, 45, -45, + 48, 47, -49, 49, 49, -53, 49, 51, -57, 50, 53, -60, + 50, 55, -64, 51, 57, -68, 51, 59, -72, 52, 61, -75, + 43, 19, 52, 43, 19, 50, 43, 19, 48, 44, 19, 44, + 44, 20, 41, 44, 20, 37, 44, 20, 33, 44, 21, 28, + 44, 22, 24, 44, 23, 19, 44, 23, 14, 44, 24, 10, + 45, 26, 5, 45, 27, 0, 45, 28, -4, 45, 29, -9, + 46, 31, -14, 46, 32, -18, 46, 34, -22, 47, 35, -27, + 47, 37, -31, 48, 39, -35, 48, 41, -39, 48, 43, -43, + 49, 44, -47, 49, 47, -52, 50, 49, -55, 50, 50, -59, + 51, 52, -63, 51, 55, -67, 52, 57, -71, 53, 59, -74, + 44, 16, 52, 44, 17, 51, 44, 17, 48, 45, 17, 45, + 45, 17, 42, 45, 18, 38, 45, 18, 34, 45, 19, 29, + 45, 20, 25, 45, 20, 20, 45, 21, 16, 45, 22, 11, + 46, 23, 6, 46, 24, 1, 46, 26, -3, 46, 27, -7, + 47, 29, -12, 47, 30, -17, 47, 32, -21, 48, 33, -25, + 48, 35, -30, 48, 37, -34, 49, 39, -38, 49, 40, -42, + 50, 42, -46, 50, 44, -50, 51, 46, -54, 51, 48, -58, + 52, 50, -62, 52, 53, -66, 53, 55, -69, 53, 57, -73, + 45, 14, 53, 45, 14, 51, 45, 14, 49, 46, 15, 46, + 46, 15, 43, 46, 15, 39, 46, 16, 35, 46, 17, 30, + 46, 17, 26, 46, 18, 22, 46, 19, 17, 46, 20, 13, + 47, 21, 7, 47, 22, 3, 47, 23, -2, 47, 25, -6, + 48, 26, -11, 48, 28, -15, 48, 29, -20, 49, 31, -24, + 49, 33, -29, 49, 35, -33, 50, 36, -37, 50, 38, -41, + 51, 40, -45, 51, 42, -49, 51, 44, -53, 52, 46, -57, + 52, 48, -60, 53, 51, -64, 54, 53, -68, 54, 55, -72, + 46, 12, 54, 46, 12, 52, 46, 12, 50, 47, 12, 47, + 47, 13, 44, 47, 13, 40, 47, 14, 36, 47, 14, 32, + 47, 15, 27, 47, 16, 23, 47, 17, 18, 47, 18, 14, + 48, 19, 9, 48, 20, 4, 48, 21, 0, 48, 22, -5, + 49, 24, -10, 49, 25, -14, 49, 27, -18, 50, 29, -22, + 50, 31, -27, 50, 32, -31, 51, 34, -35, 51, 36, -39, + 51, 38, -43, 52, 40, -48, 52, 42, -51, 53, 44, -55, + 53, 46, -59, 54, 48, -63, 54, 50, -67, 55, 53, -70, + 47, 9, 55, 48, 10, 53, 48, 10, 51, 48, 10, 48, + 48, 10, 45, 48, 11, 41, 48, 11, 38, 48, 12, 33, + 48, 13, 29, 48, 13, 24, 48, 14, 20, 48, 15, 15, + 49, 16, 10, 49, 18, 6, 49, 19, 1, 49, 20, -3, + 50, 22, -8, 50, 23, -12, 50, 25, -17, 50, 26, -21, + 51, 28, -26, 51, 30, -30, 52, 32, -34, 52, 34, -38, + 52, 36, -42, 53, 38, -46, 53, 40, -50, 54, 42, -54, + 54, 44, -58, 55, 46, -62, 55, 48, -65, 56, 50, -69, + 49, 7, 55, 49, 7, 54, 49, 7, 52, 49, 8, 49, + 49, 8, 46, 49, 8, 42, 49, 9, 39, 49, 10, 34, + 49, 10, 30, 49, 11, 26, 49, 12, 21, 49, 13, 17, + 50, 14, 12, 50, 15, 7, 50, 16, 3, 50, 18, -2, + 51, 19, -7, 51, 21, -11, 51, 22, -15, 51, 24, -19, + 52, 26, -24, 52, 28, -28, 52, 30, -32, 53, 31, -36, + 53, 33, -40, 54, 36, -45, 54, 38, -49, 55, 40, -52, + 55, 42, -56, 56, 44, -60, 56, 46, -64, 57, 48, -68, + 50, 5, 56, 50, 5, 54, 50, 5, 53, 50, 5, 50, + 50, 6, 47, 50, 6, 44, 50, 7, 40, 50, 7, 35, + 50, 8, 31, 50, 9, 27, 50, 10, 23, 51, 11, 18, + 51, 12, 13, 51, 13, 9, 51, 14, 4, 51, 15, 0, + 52, 17, -5, 52, 19, -9, 52, 20, -14, 52, 22, -18, + 53, 24, -23, 53, 25, -27, 53, 27, -31, 54, 29, -35, + 54, 31, -39, 55, 33, -43, 55, 35, -47, 55, 37, -51, + 56, 39, -55, 56, 42, -59, 57, 44, -63, 57, 46, -66, + 51, 3, 57, 51, 3, 55, 51, 3, 53, 51, 3, 51, + 51, 3, 48, 51, 4, 45, 51, 4, 41, 51, 5, 37, + 51, 6, 32, 51, 6, 28, 51, 7, 24, 52, 8, 20, + 52, 9, 15, 52, 11, 10, 52, 12, 6, 52, 13, 1, + 53, 15, -4, 53, 16, -8, 53, 18, -12, 53, 19, -16, + 54, 21, -21, 54, 23, -25, 54, 25, -29, 55, 27, -33, + 55, 29, -37, 56, 31, -42, 56, 33, -46, 56, 35, -50, + 57, 37, -53, 57, 39, -58, 58, 42, -61, 58, 44, -65, + 52, 0, 58, 52, 0, 56, 52, 1, 54, 52, 1, 52, + 52, 1, 49, 52, 2, 46, 52, 2, 42, 52, 3, 38, + 52, 3, 34, 52, 4, 30, 52, 5, 25, 53, 6, 21, + 53, 7, 16, 53, 8, 12, 53, 9, 7, 53, 11, 3, + 54, 12, -2, 54, 14, -6, 54, 15, -11, 54, 17, -15, + 55, 19, -20, 55, 21, -24, 55, 23, -28, 56, 24, -32, + 56, 26, -36, 57, 29, -40, 57, 31, -44, 57, 33, -48, + 58, 35, -52, 58, 37, -56, 59, 39, -60, 59, 41, -63, + 53, -2, 58, 53, -2, 57, 53, -2, 55, 53, -1, 53, + 53, -1, 50, 53, -1, 47, 53, 0, 43, 53, 0, 39, + 53, 1, 35, 53, 2, 31, 54, 3, 27, 54, 4, 22, + 54, 5, 17, 54, 6, 13, 54, 7, 9, 54, 8, 4, + 55, 10, -1, 55, 12, -5, 55, 13, -9, 55, 15, -13, + 56, 17, -18, 56, 18, -22, 56, 20, -26, 57, 22, -30, + 57, 24, -34, 57, 26, -39, 58, 28, -43, 58, 30, -47, + 59, 32, -50, 59, 35, -55, 60, 37, -58, 60, 39, -62, + 54, -5, 59, 54, -5, 58, 54, -4, 56, 54, -4, 54, + 54, -4, 51, 54, -3, 48, 55, -3, 45, 55, -2, 41, + 55, -2, 37, 55, -1, 33, 55, 0, 28, 55, 1, 24, + 55, 2, 19, 55, 3, 15, 56, 4, 11, 56, 6, 6, + 56, 7, 1, 56, 9, -3, 56, 10, -7, 57, 12, -11, + 57, 14, -16, 57, 16, -20, 58, 17, -24, 58, 19, -28, + 58, 21, -32, 59, 23, -37, 59, 25, -41, 59, 27, -45, + 60, 30, -48, 60, 32, -53, 61, 34, -56, 61, 36, -60, + 55, -7, 60, 55, -7, 59, 55, -7, 57, 55, -6, 55, + 56, -6, 52, 56, -6, 49, 56, -5, 46, 56, -5, 42, + 56, -4, 38, 56, -3, 34, 56, -2, 30, 56, -1, 26, + 56, 0, 21, 56, 1, 16, 57, 2, 12, 57, 3, 8, + 57, 5, 3, 57, 6, -1, 58, 8, -6, 58, 10, -10, + 58, 11, -14, 58, 13, -19, 59, 15, -23, 59, 17, -27, + 59, 19, -31, 60, 21, -35, 60, 23, -39, 60, 25, -43, + 61, 27, -47, 61, 30, -51, 62, 32, -55, 62, 34, -59, + 57, -9, 61, 57, -9, 60, 57, -9, 58, 57, -9, 56, + 57, -8, 53, 57, -8, 51, 57, -7, 47, 57, -7, 43, + 57, -6, 39, 57, -5, 35, 57, -5, 31, 57, -4, 27, + 57, -2, 22, 58, -1, 18, 58, 0, 14, 58, 1, 9, + 58, 3, 5, 58, 4, 0, 59, 6, -4, 59, 7, -8, + 59, 9, -13, 59, 11, -17, 60, 13, -21, 60, 15, -25, + 60, 17, -29, 61, 19, -34, 61, 21, -38, 61, 23, -42, + 62, 25, -45, 62, 27, -50, 63, 30, -53, 63, 32, -57, + 58, -11, 62, 58, -11, 60, 58, -11, 59, 58, -11, 57, + 58, -10, 54, 58, -10, 52, 58, -10, 48, 58, -9, 44, + 58, -8, 41, 58, -8, 37, 58, -7, 33, 58, -6, 29, + 59, -5, 24, 59, -4, 19, 59, -2, 15, 59, -1, 11, + 59, 0, 6, 59, 2, 2, 60, 3, -2, 60, 5, -7, + 60, 7, -11, 60, 9, -15, 61, 10, -20, 61, 12, -24, + 61, 14, -28, 62, 16, -32, 62, 18, -36, 62, 21, -40, + 63, 23, -44, 63, 25, -48, 64, 27, -52, 64, 29, -56, + 59, -13, 63, 59, -13, 61, 59, -13, 60, 59, -13, 58, + 59, -13, 56, 59, -12, 53, 59, -12, 50, 59, -11, 46, + 59, -10, 42, 59, -10, 38, 59, -9, 34, 60, -8, 30, + 60, -7, 25, 60, -6, 21, 60, -5, 17, 60, -3, 12, + 60, -2, 8, 61, 0, 3, 61, 1, -1, 61, 3, -5, + 61, 5, -10, 62, 6, -14, 62, 8, -18, 62, 10, -22, + 62, 12, -26, 63, 14, -31, 63, 16, -34, 63, 18, -38, + 64, 20, -42, 64, 23, -47, 65, 25, -50, 65, 27, -54, + 60, -15, 63, 60, -15, 62, 60, -15, 61, 60, -15, 59, + 60, -15, 57, 60, -14, 54, 60, -14, 51, 60, -13, 47, + 60, -13, 43, 60, -12, 39, 61, -11, 35, 61, -10, 31, + 61, -9, 27, 61, -8, 23, 61, -7, 18, 61, -6, 14, + 61, -4, 9, 62, -3, 5, 62, -1, 1, 62, 0, -3, + 62, 2, -8, 63, 4, -12, 63, 6, -16, 63, 8, -20, + 63, 10, -24, 64, 12, -29, 64, 14, -33, 64, 16, -37, + 65, 18, -41, 65, 20, -45, 66, 23, -49, 66, 25, -53, + 61, -17, 64, 61, -17, 63, 61, -17, 62, 61, -17, 60, + 61, -17, 58, 61, -16, 55, 61, -16, 52, 61, -15, 48, + 61, -15, 45, 62, -14, 41, 62, -13, 37, 62, -12, 33, + 62, -11, 28, 62, -10, 24, 62, -9, 20, 62, -8, 16, + 63, -6, 11, 63, -5, 7, 63, -3, 2, 63, -2, -2, + 63, 0, -6, 64, 2, -11, 64, 4, -15, 64, 5, -19, + 65, 7, -23, 65, 10, -27, 65, 12, -31, 65, 14, -35, + 66, 16, -39, 66, 18, -43, 67, 20, -47, 67, 23, -51, + 62, -20, 65, 62, -19, 64, 62, -19, 63, 62, -19, 61, + 62, -19, 59, 62, -18, 56, 62, -18, 53, 63, -17, 49, + 63, -17, 46, 63, -16, 42, 63, -15, 38, 63, -14, 34, + 63, -13, 30, 63, -12, 26, 63, -11, 21, 63, -10, 17, + 64, -8, 12, 64, -7, 8, 64, -6, 4, 64, -4, 0, + 65, -2, -5, 65, 0, -9, 65, 1, -13, 65, 3, -17, + 66, 5, -21, 66, 7, -26, 66, 9, -30, 67, 11, -34, + 67, 13, -37, 67, 16, -42, 68, 18, -46, 68, 20, -49, + 63, -22, 66, 63, -21, 65, 63, -21, 64, 64, -21, 62, + 64, -21, 60, 64, -20, 57, 64, -20, 54, 64, -19, 51, + 64, -19, 47, 64, -18, 43, 64, -17, 40, 64, -16, 36, + 64, -15, 31, 64, -14, 27, 64, -13, 23, 65, -12, 19, + 65, -11, 14, 65, -9, 10, 65, -8, 6, 65, -6, 1, + 66, -4, -3, 66, -3, -7, 66, -1, -11, 66, 1, -16, + 67, 3, -20, 67, 5, -24, 67, 7, -28, 68, 9, -32, + 68, 11, -36, 68, 14, -40, 69, 16, -44, 69, 18, -48, + 65, -24, 67, 65, -23, 66, 65, -23, 65, 65, -23, 63, + 65, -23, 61, 65, -22, 58, 65, -22, 55, 65, -21, 52, + 65, -21, 48, 65, -20, 45, 65, -19, 41, 65, -19, 37, + 65, -17, 33, 65, -16, 29, 66, -15, 24, 66, -14, 20, + 66, -13, 16, 66, -11, 11, 66, -10, 7, 66, -8, 3, + 67, -6, -2, 67, -5, -6, 67, -3, -10, 67, -1, -14, + 68, 1, -18, 68, 3, -22, 68, 5, -26, 69, 7, -30, + 69, 9, -34, 69, 11, -39, 70, 13, -42, 70, 16, -46, + 66, -26, 68, 66, -25, 67, 66, -25, 66, 66, -25, 64, + 66, -25, 62, 66, -24, 59, 66, -24, 57, 66, -23, 53, + 66, -23, 50, 66, -22, 46, 66, -21, 42, 66, -21, 39, + 66, -20, 34, 67, -19, 30, 67, -17, 26, 67, -16, 22, + 67, -15, 17, 67, -13, 13, 67, -12, 9, 68, -10, 5, + 68, -9, 0, 68, -7, -4, 68, -5, -8, 69, -3, -12, + 69, -2, -16, 69, 1, -21, 69, 3, -25, 70, 5, -29, + 70, 7, -33, 70, 9, -37, 71, 11, -41, 71, 13, -45, + 67, -27, 69, 67, -27, 68, 67, -27, 67, 67, -27, 65, + 67, -27, 63, 67, -26, 60, 67, -26, 58, 67, -25, 54, + 67, -25, 51, 67, -24, 47, 67, -23, 44, 68, -23, 40, + 68, -22, 35, 68, -21, 31, 68, -19, 27, 68, -18, 23, + 68, -17, 19, 68, -15, 15, 69, -14, 10, 69, -13, 6, + 69, -11, 2, 69, -9, -2, 69, -7, -7, 70, -6, -11, + 70, -4, -15, 70, -2, -19, 70, 0, -23, 71, 2, -27, + 71, 4, -31, 71, 7, -35, 72, 9, -39, 72, 11, -43, + 68, -29, 69, 68, -29, 68, 68, -29, 67, 68, -29, 66, + 68, -29, 64, 68, -28, 62, 68, -28, 59, 68, -27, 55, + 68, -27, 52, 68, -26, 49, 69, -25, 45, 69, -25, 41, + 69, -24, 37, 69, -23, 33, 69, -21, 29, 69, -20, 25, + 69, -19, 20, 69, -18, 16, 70, -16, 12, 70, -15, 8, + 70, -13, 3, 70, -11, -1, 71, -10, -5, 71, -8, -9, + 71, -6, -13, 71, -4, -18, 72, -2, -21, 72, 0, -25, + 72, 2, -29, 72, 5, -34, 73, 7, -38, 73, 9, -41, + 69, -31, 70, 69, -31, 69, 69, -31, 68, 69, -31, 67, + 69, -31, 65, 69, -30, 63, 69, -30, 60, 70, -29, 57, + 70, -29, 53, 70, -28, 50, 70, -27, 46, 70, -27, 43, + 70, -26, 38, 70, -25, 34, 70, -23, 30, 70, -22, 26, + 70, -21, 22, 71, -20, 18, 71, -18, 14, 71, -17, 9, + 71, -15, 5, 71, -13, 1, 72, -12, -3, 72, -10, -7, + 72, -8, -11, 72, -6, -16, 73, -4, -20, 73, -2, -24, + 73, 0, -28, 74, 2, -32, 74, 5, -36, 74, 7, -40, + 70, -33, 71, 70, -33, 70, 70, -33, 69, 71, -33, 68, + 71, -32, 66, 71, -32, 64, 71, -32, 61, 71, -31, 58, + 71, -31, 55, 71, -30, 51, 71, -29, 48, 71, -28, 44, + 71, -27, 40, 71, -26, 36, 71, -25, 32, 71, -24, 28, + 72, -23, 23, 72, -22, 19, 72, -20, 15, 72, -19, 11, + 72, -17, 6, 73, -15, 2, 73, -14, -2, 73, -12, -6, + 73, -10, -10, 73, -8, -14, 74, -6, -18, 74, -4, -22, + 74, -2, -26, 75, 0, -30, 75, 2, -34, 75, 4, -38, + 72, -35, 72, 72, -35, 71, 72, -35, 70, 72, -35, 69, + 72, -34, 67, 72, -34, 65, 72, -34, 62, 72, -33, 59, + 72, -32, 56, 72, -32, 53, 72, -31, 49, 72, -30, 45, + 72, -29, 41, 72, -28, 37, 72, -27, 33, 73, -26, 29, + 73, -25, 25, 73, -24, 21, 73, -22, 17, 73, -21, 13, + 73, -19, 8, 74, -17, 4, 74, -16, 0, 74, -14, -4, + 74, -12, -8, 75, -10, -13, 75, -8, -17, 75, -6, -20, + 75, -4, -24, 76, -2, -29, 76, 0, -33, 76, 2, -36, + 73, -37, 73, 73, -37, 72, 73, -37, 71, 73, -36, 70, + 73, -36, 68, 73, -36, 66, 73, -35, 63, 73, -35, 60, + 73, -34, 57, 73, -34, 54, 73, -33, 50, 73, -32, 47, + 73, -31, 43, 74, -30, 39, 74, -29, 35, 74, -28, 31, + 74, -27, 26, 74, -26, 22, 74, -24, 18, 74, -23, 14, + 75, -21, 10, 75, -20, 6, 75, -18, 2, 75, -16, -2, + 75, -14, -6, 76, -12, -11, 76, -10, -15, 76, -8, -19, + 76, -6, -23, 77, -4, -27, 77, -2, -31, 77, 0, -35, + 74, -39, 74, 74, -38, 73, 74, -38, 72, 74, -38, 71, + 74, -38, 69, 74, -38, 67, 74, -37, 64, 74, -37, 61, + 74, -36, 58, 74, -36, 55, 74, -35, 52, 74, -34, 48, + 75, -33, 44, 75, -32, 40, 75, -31, 36, 75, -30, 32, + 75, -29, 28, 75, -28, 24, 75, -26, 20, 76, -25, 16, + 76, -23, 11, 76, -22, 7, 76, -20, 3, 76, -18, -1, + 77, -16, -5, 77, -14, -9, 77, -13, -13, 77, -11, -17, + 78, -9, -21, 78, -6, -25, 78, -4, -29, 78, -2, -33, + 75, -40, 75, 75, -40, 74, 75, -40, 73, 75, -40, 72, + 75, -40, 70, 75, -39, 68, 75, -39, 66, 75, -38, 63, + 75, -38, 60, 75, -37, 56, 76, -37, 53, 76, -36, 50, + 76, -35, 45, 76, -34, 42, 76, -33, 38, 76, -32, 34, + 76, -31, 29, 76, -29, 25, 76, -28, 21, 77, -27, 17, + 77, -25, 13, 77, -24, 9, 77, -22, 5, 77, -20, 1, + 78, -18, -3, 78, -16, -8, 78, -15, -12, 78, -13, -16, + 79, -11, -19, 79, -8, -24, 79, -6, -28, 79, -4, -32, + 76, -42, 76, 76, -42, 75, 76, -42, 74, 76, -42, 73, + 76, -41, 71, 76, -41, 69, 76, -41, 67, 77, -40, 64, + 77, -40, 61, 77, -39, 58, 77, -38, 54, 77, -38, 51, + 77, -37, 47, 77, -36, 43, 77, -35, 39, 77, -34, 35, + 77, -33, 31, 77, -31, 27, 78, -30, 23, 78, -29, 19, + 78, -27, 14, 78, -25, 10, 78, -24, 6, 79, -22, 2, + 79, -20, -2, 79, -18, -6, 79, -17, -10, 79, -15, -14, + 80, -13, -18, 80, -11, -22, 80, -8, -26, 81, -6, -30, + 78, -44, 77, 78, -44, 76, 78, -44, 75, 78, -43, 74, + 78, -43, 72, 78, -43, 70, 78, -43, 68, 78, -42, 65, + 78, -42, 62, 78, -41, 59, 78, -40, 56, 78, -40, 52, + 78, -39, 48, 78, -38, 45, 78, -37, 41, 78, -36, 37, + 78, -34, 33, 79, -33, 29, 79, -32, 25, 79, -31, 21, + 79, -29, 16, 79, -27, 12, 79, -26, 8, 80, -24, 4, + 80, -22, 0, 80, -20, -4, 80, -19, -8, 81, -17, -12, + 81, -15, -16, 81, -13, -21, 81, -11, -24, 82, -8, -28, + 79, -46, 78, 79, -46, 77, 79, -46, 76, 79, -46, 75, + 79, -45, 73, 79, -45, 71, 79, -45, 69, 79, -44, 66, + 79, -44, 64, 79, -43, 61, 79, -42, 57, 79, -42, 54, + 79, -41, 50, 80, -40, 46, 80, -39, 43, 80, -38, 39, + 80, -37, 34, 80, -36, 30, 80, -34, 27, 80, -33, 23, + 81, -31, 18, 81, -30, 14, 81, -28, 10, 81, -27, 6, + 81, -25, 2, 81, -23, -2, 82, -21, -6, 82, -19, -10, + 82, -17, -14, 82, -15, -18, 83, -13, -22, 83, -11, -26, + 80, -48, 79, 80, -48, 78, 80, -47, 77, 80, -47, 76, + 80, -47, 74, 80, -47, 72, 80, -46, 70, 80, -46, 68, + 80, -45, 65, 80, -45, 62, 80, -44, 59, 81, -43, 55, + 81, -43, 51, 81, -42, 48, 81, -41, 44, 81, -40, 40, + 81, -39, 36, 81, -37, 32, 81, -36, 28, 81, -35, 24, + 82, -33, 20, 82, -32, 16, 82, -30, 12, 82, -29, 8, + 82, -27, 4, 83, -25, -1, 83, -23, -5, 83, -21, -9, + 83, -19, -12, 84, -17, -17, 84, -15, -21, 84, -13, -24, + 81, -49, 80, 81, -49, 79, 81, -49, 78, 81, -49, 77, + 81, -49, 75, 81, -48, 73, 81, -48, 71, 81, -48, 69, + 82, -47, 66, 82, -47, 63, 82, -46, 60, 82, -45, 57, + 82, -44, 53, 82, -43, 49, 82, -43, 45, 82, -42, 42, + 82, -40, 37, 82, -39, 34, 82, -38, 30, 83, -37, 26, + 83, -35, 21, 83, -34, 17, 83, -32, 13, 83, -30, 9, + 83, -29, 5, 84, -27, 1, 84, -25, -3, 84, -23, -7, + 84, -21, -11, 85, -19, -15, 85, -17, -19, 85, -15, -23, + 83, -51, 80, 83, -51, 80, 83, -51, 79, 83, -51, 78, + 83, -50, 76, 83, -50, 75, 83, -50, 72, 83, -49, 70, + 83, -49, 67, 83, -48, 64, 83, -48, 61, 83, -47, 58, + 83, -46, 54, 83, -45, 50, 83, -44, 47, 83, -43, 43, + 83, -42, 39, 84, -41, 35, 84, -40, 31, 84, -38, 27, + 84, -37, 23, 84, -35, 19, 84, -34, 15, 84, -32, 11, + 85, -31, 7, 85, -29, 3, 85, -27, -1, 85, -25, -5, + 85, -23, -9, 86, -21, -14, 86, -19, -17, 86, -17, -21, + 84, -53, 81, 84, -52, 81, 84, -52, 80, 84, -52, 79, + 84, -52, 77, 84, -52, 76, 84, -51, 74, 84, -51, 71, + 84, -50, 68, 84, -50, 65, 84, -49, 62, 84, -49, 59, + 84, -48, 55, 84, -47, 52, 84, -46, 48, 84, -45, 45, + 85, -44, 40, 85, -43, 37, 85, -41, 33, 85, -40, 29, + 85, -39, 24, 85, -37, 20, 85, -36, 16, 86, -34, 13, + 86, -33, 9, 86, -31, 4, 86, -29, 0, 86, -27, -4, + 87, -25, -8, 87, -23, -12, 87, -21, -16, 87, -19, -20, + 85, -54, 82, 85, -54, 82, 85, -54, 81, 85, -54, 80, + 85, -54, 78, 85, -53, 77, 85, -53, 75, 85, -52, 72, + 85, -52, 69, 85, -51, 67, 85, -51, 64, 85, -50, 60, + 85, -49, 57, 85, -49, 53, 85, -48, 50, 86, -47, 46, + 86, -46, 42, 86, -44, 38, 86, -43, 34, 86, -42, 30, + 86, -40, 26, 86, -39, 22, 87, -38, 18, 87, -36, 14, + 87, -34, 10, 87, -33, 6, 87, -31, 2, 87, -29, -2, + 88, -27, -6, 88, -25, -10, 88, -23, -14, 88, -21, -18, + 86, -56, 83, 86, -56, 83, 86, -55, 82, 86, -55, 81, + 86, -55, 79, 86, -55, 78, 86, -55, 76, 86, -54, 73, + 86, -54, 71, 86, -53, 68, 86, -53, 65, 86, -52, 62, + 86, -51, 58, 87, -50, 55, 87, -49, 51, 87, -48, 47, + 87, -47, 43, 87, -46, 39, 87, -45, 36, 87, -44, 32, + 87, -42, 27, 88, -41, 24, 88, -39, 20, 88, -38, 16, + 88, -36, 12, 88, -34, 7, 88, -33, 4, 89, -31, 0, + 89, -29, -4, 89, -27, -9, 89, -25, -12, 90, -23, -16, + 87, -57, 84, 87, -57, 83, 87, -57, 83, 87, -57, 82, + 87, -57, 80, 87, -56, 79, 87, -56, 77, 87, -56, 74, + 87, -55, 72, 87, -55, 69, 87, -54, 66, 88, -53, 63, + 88, -53, 59, 88, -52, 56, 88, -51, 52, 88, -50, 49, + 88, -49, 45, 88, -48, 41, 88, -47, 37, 88, -45, 33, + 89, -44, 29, 89, -43, 25, 89, -41, 21, 89, -40, 17, + 89, -38, 13, 89, -36, 9, 90, -35, 5, 90, -33, 1, + 90, -31, -3, 90, -29, -7, 90, -27, -11, 91, -25, -15, + 88, -59, 85, 88, -59, 84, 88, -59, 84, 88, -58, 83, + 88, -58, 81, 88, -58, 80, 88, -58, 78, 89, -57, 75, + 89, -57, 73, 89, -56, 70, 89, -56, 67, 89, -55, 64, + 89, -54, 61, 89, -53, 57, 89, -53, 54, 89, -52, 50, + 89, -51, 46, 89, -50, 42, 89, -48, 39, 89, -47, 35, + 90, -46, 31, 90, -44, 27, 90, -43, 23, 90, -41, 19, + 90, -40, 15, 90, -38, 11, 91, -36, 7, 91, -35, 3, + 91, -33, -1, 91, -31, -5, 91, -29, -9, 92, -27, -13, + 90, -60, 86, 90, -60, 85, 90, -60, 85, 90, -60, 84, + 90, -60, 82, 90, -59, 81, 90, -59, 79, 90, -59, 76, + 90, -58, 74, 90, -58, 71, 90, -57, 69, 90, -57, 66, + 90, -56, 62, 90, -55, 59, 90, -54, 55, 90, -53, 52, + 90, -52, 48, 90, -51, 44, 91, -50, 40, 91, -49, 36, + 91, -47, 32, 91, -46, 28, 91, -45, 24, 91, -43, 20, + 91, -42, 17, 92, -40, 12, 92, -38, 8, 92, -37, 5, + 92, -35, 1, 92, -33, -4, 93, -31, -8, 93, -29, -11, + 91, -62, 87, 91, -62, 86, 91, -62, 86, 91, -61, 84, + 91, -61, 83, 91, -61, 82, 91, -61, 80, 91, -60, 78, + 91, -60, 75, 91, -59, 73, 91, -59, 70, 91, -58, 67, + 91, -57, 63, 91, -57, 60, 91, -56, 57, 91, -55, 53, + 91, -54, 49, 92, -53, 45, 92, -52, 42, 92, -51, 38, + 92, -49, 34, 92, -48, 30, 92, -46, 26, 92, -45, 22, + 93, -43, 18, 93, -42, 14, 93, -40, 10, 93, -38, 6, + 93, -37, 2, 93, -35, -2, 94, -33, -6, 94, -31, -10, + 32, 55, 46, 32, 56, 43, 32, 56, 39, 32, 56, 34, + 32, 56, 30, 32, 56, 25, 32, 57, 20, 33, 57, 14, + 33, 58, 9, 33, 58, 4, 33, 59, -1, 34, 60, -6, + 34, 61, -11, 34, 62, -16, 35, 62, -20, 35, 63, -25, + 35, 65, -30, 36, 66, -34, 36, 67, -38, 37, 68, -42, + 37, 69, -47, 38, 71, -51, 39, 72, -54, 39, 73, -58, + 40, 75, -62, 40, 76, -66, 41, 78, -70, 42, 79, -73, + 42, 81, -77, 43, 83, -80, 44, 84, -84, 45, 86, -87, + 32, 55, 46, 32, 55, 43, 32, 55, 39, 32, 55, 35, + 32, 56, 30, 33, 56, 25, 33, 56, 20, 33, 57, 14, + 33, 57, 9, 33, 58, 4, 33, 59, -1, 34, 59, -6, + 34, 60, -11, 34, 61, -15, 35, 62, -20, 35, 63, -24, + 36, 64, -29, 36, 65, -34, 37, 66, -38, 37, 68, -42, + 38, 69, -46, 38, 70, -50, 39, 72, -54, 39, 73, -58, + 40, 74, -62, 41, 76, -66, 41, 77, -69, 42, 79, -73, + 43, 80, -76, 43, 82, -80, 44, 84, -84, 45, 85, -87, + 32, 54, 46, 32, 54, 43, 32, 55, 39, 33, 55, 35, + 33, 55, 30, 33, 55, 25, 33, 56, 20, 33, 56, 14, + 33, 57, 9, 33, 57, 4, 34, 58, 0, 34, 59, -5, + 34, 60, -11, 35, 61, -15, 35, 61, -20, 35, 62, -24, + 36, 64, -29, 36, 65, -33, 37, 66, -38, 37, 67, -42, + 38, 69, -46, 38, 70, -50, 39, 71, -54, 39, 73, -58, + 40, 74, -61, 41, 76, -66, 41, 77, -69, 42, 79, -73, + 43, 80, -76, 43, 82, -80, 44, 83, -83, 45, 85, -87, + 33, 54, 46, 33, 54, 43, 33, 54, 40, 33, 54, 35, + 33, 54, 30, 33, 55, 25, 33, 55, 20, 33, 56, 15, + 33, 56, 10, 34, 57, 5, 34, 57, 0, 34, 58, -5, + 34, 59, -10, 35, 60, -15, 35, 61, -19, 36, 62, -24, + 36, 63, -29, 36, 64, -33, 37, 65, -37, 37, 67, -41, + 38, 68, -46, 38, 69, -50, 39, 71, -54, 40, 72, -57, + 40, 74, -61, 41, 75, -65, 42, 77, -69, 42, 78, -72, + 43, 80, -76, 44, 81, -80, 44, 83, -83, 45, 84, -86, + 33, 53, 46, 33, 53, 43, 33, 53, 40, 33, 54, 35, + 33, 54, 30, 33, 54, 26, 33, 55, 21, 33, 55, 15, + 34, 56, 10, 34, 56, 5, 34, 57, 0, 34, 58, -5, + 35, 58, -10, 35, 59, -15, 35, 60, -19, 36, 61, -24, + 36, 63, -29, 37, 64, -33, 37, 65, -37, 38, 66, -41, + 38, 67, -46, 39, 69, -50, 39, 70, -53, 40, 72, -57, + 40, 73, -61, 41, 75, -65, 42, 76, -69, 42, 78, -72, + 43, 79, -76, 44, 81, -79, 44, 82, -83, 45, 84, -86, + 33, 52, 47, 33, 52, 43, 33, 53, 40, 33, 53, 35, + 33, 53, 31, 33, 53, 26, 34, 54, 21, 34, 54, 15, + 34, 55, 10, 34, 55, 5, 34, 56, 1, 35, 57, -4, + 35, 58, -10, 35, 59, -14, 36, 60, -19, 36, 61, -23, + 36, 62, -28, 37, 63, -32, 37, 64, -37, 38, 65, -41, + 38, 67, -45, 39, 68, -49, 39, 70, -53, 40, 71, -57, + 41, 72, -61, 41, 74, -65, 42, 75, -68, 43, 77, -72, + 43, 79, -75, 44, 80, -79, 45, 82, -83, 45, 83, -86, + 33, 51, 47, 33, 51, 44, 33, 52, 40, 34, 52, 36, + 34, 52, 31, 34, 52, 26, 34, 53, 21, 34, 53, 16, + 34, 54, 11, 34, 54, 6, 35, 55, 1, 35, 56, -4, + 35, 57, -9, 36, 58, -14, 36, 59, -18, 36, 60, -23, + 37, 61, -28, 37, 62, -32, 38, 63, -36, 38, 65, -40, + 39, 66, -45, 39, 67, -49, 40, 69, -53, 40, 70, -56, + 41, 72, -60, 41, 73, -64, 42, 75, -68, 43, 76, -71, + 43, 78, -75, 44, 80, -79, 45, 81, -82, 46, 83, -86, + 34, 50, 47, 34, 50, 44, 34, 51, 41, 34, 51, 36, + 34, 51, 32, 34, 51, 27, 34, 52, 22, 34, 52, 16, + 35, 53, 11, 35, 53, 6, 35, 54, 1, 35, 55, -3, + 36, 56, -9, 36, 57, -13, 36, 58, -18, 37, 59, -22, + 37, 60, -27, 37, 61, -31, 38, 62, -36, 38, 64, -40, + 39, 65, -44, 39, 66, -48, 40, 68, -52, 41, 69, -56, + 41, 71, -60, 42, 72, -64, 42, 74, -67, 43, 76, -71, + 44, 77, -75, 44, 79, -78, 45, 80, -82, 46, 82, -85, + 34, 49, 47, 34, 49, 44, 34, 49, 41, 34, 50, 36, + 34, 50, 32, 35, 50, 27, 35, 51, 22, 35, 51, 17, + 35, 52, 12, 35, 52, 7, 35, 53, 2, 36, 54, -3, + 36, 55, -8, 36, 56, -13, 37, 57, -17, 37, 58, -22, + 37, 59, -27, 38, 60, -31, 38, 61, -35, 39, 63, -39, + 39, 64, -44, 40, 65, -48, 40, 67, -52, 41, 68, -55, + 41, 70, -59, 42, 72, -63, 43, 73, -67, 43, 75, -71, + 44, 76, -74, 45, 78, -78, 45, 80, -81, 46, 81, -85, + 35, 48, 48, 35, 48, 44, 35, 48, 41, 35, 48, 37, + 35, 49, 32, 35, 49, 28, 35, 49, 23, 35, 50, 17, + 35, 50, 12, 36, 51, 8, 36, 52, 3, 36, 53, -2, + 36, 53, -7, 37, 54, -12, 37, 55, -17, 37, 56, -21, + 38, 58, -26, 38, 59, -30, 39, 60, -34, 39, 61, -39, + 40, 63, -43, 40, 64, -47, 41, 66, -51, 41, 67, -55, + 42, 69, -59, 42, 71, -63, 43, 72, -66, 44, 74, -70, + 44, 75, -74, 45, 77, -77, 46, 79, -81, 46, 80, -84, + 35, 46, 48, 35, 47, 45, 35, 47, 42, 35, 47, 37, + 35, 47, 33, 35, 47, 28, 36, 48, 24, 36, 48, 18, + 36, 49, 13, 36, 50, 8, 36, 50, 3, 37, 51, -1, + 37, 52, -7, 37, 53, -11, 38, 54, -16, 38, 55, -20, + 38, 56, -25, 39, 58, -30, 39, 59, -34, 40, 60, -38, + 40, 62, -42, 41, 63, -46, 41, 65, -50, 42, 66, -54, + 42, 68, -58, 43, 69, -62, 43, 71, -66, 44, 73, -69, + 45, 74, -73, 45, 76, -77, 46, 78, -80, 47, 79, -84, + 36, 44, 48, 36, 45, 45, 36, 45, 42, 36, 45, 38, + 36, 45, 34, 36, 46, 29, 36, 46, 24, 36, 47, 19, + 37, 47, 14, 37, 48, 9, 37, 48, 4, 37, 49, 0, + 38, 50, -6, 38, 51, -10, 38, 52, -15, 39, 53, -19, + 39, 55, -24, 39, 56, -29, 40, 57, -33, 40, 59, -37, + 41, 60, -41, 41, 62, -45, 42, 63, -49, 42, 65, -53, + 43, 66, -57, 43, 68, -61, 44, 69, -65, 45, 71, -69, + 45, 73, -72, 46, 75, -76, 47, 76, -79, 47, 78, -83, + 36, 43, 49, 37, 43, 46, 37, 43, 43, 37, 43, 39, + 37, 44, 34, 37, 44, 30, 37, 44, 25, 37, 45, 20, + 37, 45, 15, 37, 46, 10, 38, 47, 5, 38, 48, 0, + 38, 49, -5, 38, 50, -9, 39, 51, -14, 39, 52, -18, + 39, 53, -23, 40, 54, -28, 40, 56, -32, 41, 57, -36, + 41, 59, -41, 42, 60, -45, 42, 62, -49, 43, 63, -53, + 43, 65, -56, 44, 66, -61, 44, 68, -64, 45, 70, -68, + 46, 71, -71, 46, 73, -75, 47, 75, -79, 48, 77, -82, + 37, 41, 49, 37, 41, 46, 37, 41, 43, 37, 42, 39, + 37, 42, 35, 37, 42, 31, 38, 43, 26, 38, 43, 21, + 38, 44, 16, 38, 44, 11, 38, 45, 6, 38, 46, 1, + 39, 47, -4, 39, 48, -9, 39, 49, -13, 40, 50, -18, + 40, 52, -22, 40, 53, -27, 41, 54, -31, 41, 55, -35, + 42, 57, -40, 42, 59, -44, 43, 60, -48, 43, 62, -52, + 44, 63, -55, 44, 65, -60, 45, 67, -63, 45, 68, -67, + 46, 70, -71, 47, 72, -75, 47, 74, -78, 48, 75, -81, + 38, 39, 49, 38, 39, 46, 38, 39, 44, 38, 40, 40, + 38, 40, 36, 38, 40, 31, 38, 41, 27, 38, 41, 21, + 39, 42, 17, 39, 43, 12, 39, 43, 7, 39, 44, 2, + 39, 45, -3, 40, 46, -8, 40, 47, -12, 40, 48, -17, + 41, 50, -22, 41, 51, -26, 41, 52, -30, 42, 54, -34, + 42, 55, -39, 43, 57, -43, 43, 58, -47, 44, 60, -51, + 44, 62, -55, 45, 63, -59, 45, 65, -63, 46, 67, -66, + 47, 69, -70, 47, 71, -74, 48, 72, -77, 49, 74, -81, + 39, 37, 50, 39, 37, 47, 39, 38, 44, 39, 38, 40, + 39, 38, 36, 39, 38, 32, 39, 39, 28, 39, 39, 22, + 39, 40, 18, 39, 41, 13, 40, 41, 8, 40, 42, 3, + 40, 43, -2, 40, 44, -7, 41, 45, -11, 41, 47, -16, + 41, 48, -20, 42, 49, -25, 42, 51, -29, 42, 52, -33, + 43, 54, -38, 43, 55, -42, 44, 57, -46, 44, 58, -50, + 45, 60, -54, 45, 62, -58, 46, 64, -62, 47, 65, -65, + 47, 67, -69, 48, 69, -73, 48, 71, -76, 49, 73, -80, + 39, 35, 50, 39, 35, 47, 39, 36, 45, 39, 36, 41, + 40, 36, 37, 40, 36, 33, 40, 37, 29, 40, 37, 23, + 40, 38, 19, 40, 39, 14, 40, 40, 9, 41, 40, 4, + 41, 41, -1, 41, 42, -5, 41, 44, -10, 42, 45, -14, + 42, 46, -19, 42, 47, -24, 43, 49, -28, 43, 50, -32, + 44, 52, -37, 44, 53, -41, 44, 55, -45, 45, 57, -49, + 45, 58, -53, 46, 60, -57, 47, 62, -61, 47, 64, -64, + 48, 65, -68, 48, 67, -72, 49, 69, -75, 50, 71, -79, + 40, 33, 50, 40, 33, 48, 40, 34, 46, 40, 34, 42, + 40, 34, 38, 40, 34, 34, 40, 35, 29, 41, 35, 24, + 41, 36, 20, 41, 37, 15, 41, 38, 10, 41, 38, 5, + 42, 39, 0, 42, 40, -4, 42, 42, -9, 42, 43, -13, + 43, 44, -18, 43, 46, -23, 43, 47, -27, 44, 48, -31, + 44, 50, -36, 45, 52, -40, 45, 53, -44, 46, 55, -48, + 46, 56, -52, 47, 58, -56, 47, 60, -60, 48, 62, -63, + 48, 64, -67, 49, 66, -71, 50, 68, -75, 50, 69, -78, + 41, 31, 51, 41, 31, 49, 41, 31, 46, 41, 32, 43, + 41, 32, 39, 41, 32, 35, 41, 33, 30, 41, 33, 25, + 42, 34, 21, 42, 35, 16, 42, 35, 11, 42, 36, 7, + 42, 37, 1, 43, 38, -3, 43, 40, -8, 43, 41, -12, + 44, 42, -17, 44, 44, -21, 44, 45, -26, 45, 46, -30, + 45, 48, -35, 45, 50, -39, 46, 51, -43, 46, 53, -47, + 47, 55, -51, 47, 57, -55, 48, 58, -59, 48, 60, -62, + 49, 62, -66, 50, 64, -70, 50, 66, -74, 51, 68, -77, + 42, 29, 51, 42, 29, 49, 42, 29, 47, 42, 30, 43, + 42, 30, 40, 42, 30, 36, 42, 31, 31, 42, 31, 26, + 42, 32, 22, 43, 33, 17, 43, 33, 12, 43, 34, 8, + 43, 35, 3, 43, 36, -2, 44, 37, -7, 44, 39, -11, + 44, 40, -16, 45, 41, -20, 45, 43, -25, 45, 44, -29, + 46, 46, -33, 46, 48, -38, 47, 49, -42, 47, 51, -46, + 48, 53, -49, 48, 55, -54, 49, 56, -57, 49, 58, -61, + 50, 60, -65, 50, 62, -69, 51, 64, -72, 51, 66, -76, + 43, 27, 52, 43, 27, 50, 43, 27, 48, 43, 27, 44, + 43, 28, 41, 43, 28, 37, 43, 28, 32, 43, 29, 27, + 43, 30, 23, 43, 30, 18, 44, 31, 14, 44, 32, 9, + 44, 33, 4, 44, 34, -1, 45, 35, -5, 45, 37, -10, + 45, 38, -15, 45, 39, -19, 46, 41, -23, 46, 42, -28, + 47, 44, -32, 47, 46, -36, 47, 47, -40, 48, 49, -44, + 48, 51, -48, 49, 53, -53, 49, 54, -56, 50, 56, -60, + 50, 58, -64, 51, 60, -68, 51, 62, -71, 52, 64, -75, + 44, 25, 53, 44, 25, 50, 44, 25, 48, 44, 25, 45, + 44, 25, 42, 44, 26, 38, 44, 26, 33, 44, 27, 29, + 44, 27, 24, 44, 28, 19, 44, 29, 15, 45, 30, 10, + 45, 31, 5, 45, 32, 1, 45, 33, -4, 46, 34, -8, + 46, 36, -13, 46, 37, -18, 47, 39, -22, 47, 40, -26, + 47, 42, -31, 48, 44, -35, 48, 45, -39, 49, 47, -43, + 49, 49, -47, 50, 51, -51, 50, 52, -55, 50, 54, -59, + 51, 56, -63, 52, 58, -67, 52, 60, -70, 53, 62, -74, + 45, 22, 53, 45, 22, 51, 45, 23, 49, 45, 23, 46, + 45, 23, 42, 45, 24, 39, 45, 24, 34, 45, 25, 30, + 45, 25, 25, 45, 26, 21, 45, 27, 16, 46, 28, 11, + 46, 29, 6, 46, 30, 2, 46, 31, -3, 47, 32, -7, + 47, 34, -12, 47, 35, -16, 47, 36, -21, 48, 38, -25, + 48, 40, -30, 49, 41, -34, 49, 43, -38, 49, 45, -42, + 50, 47, -46, 50, 49, -50, 51, 50, -54, 51, 52, -58, + 52, 54, -61, 52, 56, -66, 53, 58, -69, 53, 60, -73, + 45, 20, 54, 46, 20, 52, 46, 20, 50, 46, 21, 47, + 46, 21, 43, 46, 21, 40, 46, 22, 36, 46, 22, 31, + 46, 23, 26, 46, 24, 22, 46, 24, 17, 46, 25, 13, + 47, 26, 8, 47, 28, 3, 47, 29, -1, 47, 30, -6, + 48, 31, -11, 48, 33, -15, 48, 34, -19, 49, 36, -24, + 49, 38, -28, 49, 39, -32, 50, 41, -37, 50, 43, -41, + 51, 44, -44, 51, 46, -49, 52, 48, -53, 52, 50, -56, + 53, 52, -60, 53, 54, -64, 54, 56, -68, 54, 58, -71, + 46, 18, 54, 46, 18, 52, 47, 18, 51, 47, 18, 48, + 47, 19, 44, 47, 19, 41, 47, 19, 37, 47, 20, 32, + 47, 21, 28, 47, 21, 23, 47, 22, 19, 47, 23, 14, + 48, 24, 9, 48, 25, 4, 48, 26, 0, 48, 28, -4, + 49, 29, -9, 49, 31, -14, 49, 32, -18, 50, 34, -22, + 50, 35, -27, 50, 37, -31, 51, 39, -35, 51, 40, -39, + 51, 42, -43, 52, 44, -48, 52, 46, -51, 53, 48, -55, + 53, 50, -59, 54, 52, -63, 54, 54, -67, 55, 56, -70, + 47, 16, 55, 47, 16, 53, 48, 16, 51, 48, 16, 48, + 48, 16, 45, 48, 17, 42, 48, 17, 38, 48, 18, 33, + 48, 18, 29, 48, 19, 24, 48, 20, 20, 48, 21, 15, + 49, 22, 10, 49, 23, 6, 49, 24, 1, 49, 25, -3, + 50, 27, -8, 50, 28, -12, 50, 30, -17, 50, 31, -21, + 51, 33, -26, 51, 35, -30, 52, 36, -34, 52, 38, -38, + 52, 40, -42, 53, 42, -46, 53, 44, -50, 54, 46, -54, + 54, 48, -58, 55, 50, -62, 55, 52, -65, 56, 54, -69, + 48, 13, 56, 48, 13, 54, 49, 13, 52, 49, 14, 49, + 49, 14, 46, 49, 14, 43, 49, 15, 39, 49, 15, 34, + 49, 16, 30, 49, 17, 26, 49, 18, 21, 49, 19, 17, + 50, 20, 12, 50, 21, 7, 50, 22, 3, 50, 23, -2, + 50, 25, -7, 51, 26, -11, 51, 28, -15, 51, 29, -19, + 52, 31, -24, 52, 33, -28, 52, 34, -32, 53, 36, -36, + 53, 38, -40, 54, 40, -45, 54, 42, -49, 54, 44, -53, + 55, 46, -56, 55, 48, -60, 56, 50, -64, 56, 52, -68, + 50, 11, 56, 50, 11, 55, 50, 11, 53, 50, 11, 50, + 50, 12, 47, 50, 12, 44, 50, 13, 40, 50, 13, 35, + 50, 14, 31, 50, 15, 27, 50, 15, 23, 50, 16, 18, + 51, 17, 13, 51, 18, 9, 51, 20, 4, 51, 21, 0, + 51, 22, -5, 52, 24, -9, 52, 25, -14, 52, 27, -18, + 53, 29, -23, 53, 30, -27, 53, 32, -31, 54, 34, -35, + 54, 36, -39, 55, 38, -43, 55, 40, -47, 55, 42, -51, + 56, 44, -55, 56, 46, -59, 57, 48, -63, 57, 50, -66, + 51, 9, 57, 51, 9, 55, 51, 9, 54, 51, 9, 51, + 51, 9, 48, 51, 10, 45, 51, 10, 41, 51, 11, 37, + 51, 12, 33, 51, 12, 28, 51, 13, 24, 51, 14, 19, + 52, 15, 14, 52, 16, 10, 52, 17, 6, 52, 19, 1, + 52, 20, -4, 53, 21, -8, 53, 23, -12, 53, 24, -17, + 54, 26, -21, 54, 28, -25, 54, 30, -30, 55, 31, -34, + 55, 33, -38, 55, 35, -42, 56, 37, -46, 56, 39, -50, + 57, 41, -54, 57, 44, -58, 58, 46, -61, 58, 48, -65, + 52, 6, 58, 52, 7, 56, 52, 7, 55, 52, 7, 52, + 52, 7, 49, 52, 8, 46, 52, 8, 42, 52, 9, 38, + 52, 9, 34, 52, 10, 30, 52, 11, 25, 52, 12, 21, + 53, 13, 16, 53, 14, 11, 53, 15, 7, 53, 16, 3, + 53, 18, -2, 54, 19, -7, 54, 21, -11, 54, 22, -15, + 55, 24, -20, 55, 26, -24, 55, 27, -28, 56, 29, -32, + 56, 31, -36, 56, 33, -41, 57, 35, -44, 57, 37, -48, + 58, 39, -52, 58, 41, -56, 59, 43, -60, 59, 46, -64, + 53, 4, 59, 53, 4, 57, 53, 4, 55, 53, 5, 53, + 53, 5, 50, 53, 5, 47, 53, 6, 43, 53, 6, 39, + 53, 7, 35, 53, 8, 31, 53, 8, 27, 53, 9, 22, + 54, 10, 17, 54, 12, 13, 54, 13, 9, 54, 14, 4, + 54, 15, -1, 55, 17, -5, 55, 18, -9, 55, 20, -14, + 56, 22, -18, 56, 23, -22, 56, 25, -27, 57, 27, -31, + 57, 29, -35, 57, 31, -39, 58, 33, -43, 58, 35, -47, + 58, 37, -51, 59, 39, -55, 59, 41, -59, 60, 43, -62, + 54, 2, 59, 54, 2, 58, 54, 2, 56, 54, 2, 54, + 54, 3, 51, 54, 3, 48, 54, 3, 45, 54, 4, 40, + 54, 5, 36, 54, 5, 32, 54, 6, 28, 55, 7, 24, + 55, 8, 19, 55, 9, 14, 55, 10, 10, 55, 12, 6, + 55, 13, 1, 56, 15, -4, 56, 16, -8, 56, 18, -12, + 57, 19, -17, 57, 21, -21, 57, 23, -25, 57, 25, -29, + 58, 26, -33, 58, 29, -38, 59, 31, -42, 59, 33, -45, + 59, 35, -49, 60, 37, -53, 60, 39, -57, 61, 41, -61, + 55, -1, 60, 55, -1, 59, 55, -1, 57, 55, 0, 55, + 55, 0, 52, 55, 0, 49, 55, 1, 46, 55, 1, 42, + 56, 2, 38, 56, 3, 34, 56, 3, 30, 56, 4, 25, + 56, 5, 21, 56, 6, 16, 56, 8, 12, 57, 9, 7, + 57, 10, 3, 57, 12, -2, 57, 13, -6, 57, 15, -10, + 58, 17, -15, 58, 18, -19, 58, 20, -23, 59, 22, -27, + 59, 24, -31, 59, 26, -36, 60, 28, -40, 60, 30, -44, + 61, 32, -47, 61, 34, -52, 61, 36, -55, 62, 38, -59, + 56, -3, 61, 56, -3, 60, 56, -3, 58, 56, -3, 56, + 56, -2, 53, 56, -2, 50, 56, -2, 47, 57, -1, 43, + 57, 0, 39, 57, 0, 35, 57, 1, 31, 57, 2, 27, + 57, 3, 22, 57, 4, 18, 57, 5, 13, 58, 7, 9, + 58, 8, 4, 58, 9, 0, 58, 11, -4, 59, 12, -9, + 59, 14, -13, 59, 16, -17, 59, 18, -22, 60, 19, -26, + 60, 21, -30, 60, 23, -34, 61, 25, -38, 61, 27, -42, + 62, 29, -46, 62, 32, -50, 62, 34, -54, 63, 36, -58, + 57, -5, 62, 57, -5, 60, 57, -5, 59, 57, -5, 57, + 57, -5, 54, 57, -4, 52, 58, -4, 48, 58, -3, 44, + 58, -3, 40, 58, -2, 36, 58, -1, 32, 58, 0, 28, + 58, 1, 23, 58, 2, 19, 58, 3, 15, 59, 4, 11, + 59, 6, 6, 59, 7, 1, 59, 9, -3, 60, 10, -7, + 60, 12, -12, 60, 14, -16, 60, 15, -20, 61, 17, -24, + 61, 19, -28, 61, 21, -33, 62, 23, -37, 62, 25, -40, + 62, 27, -44, 63, 29, -49, 63, 32, -52, 64, 34, -56, + 58, -8, 63, 58, -7, 61, 58, -7, 60, 58, -7, 58, + 59, -7, 55, 59, -6, 53, 59, -6, 49, 59, -5, 45, + 59, -5, 42, 59, -4, 38, 59, -3, 34, 59, -2, 30, + 59, -1, 25, 59, 0, 21, 60, 1, 16, 60, 2, 12, + 60, 3, 7, 60, 5, 3, 60, 6, -1, 61, 8, -6, + 61, 10, -10, 61, 11, -14, 61, 13, -18, 62, 15, -23, + 62, 17, -27, 62, 19, -31, 63, 21, -35, 63, 23, -39, + 63, 25, -43, 64, 27, -47, 64, 29, -51, 65, 31, -55, + 60, -10, 63, 60, -10, 62, 60, -9, 61, 60, -9, 59, + 60, -9, 56, 60, -9, 54, 60, -8, 51, 60, -8, 47, + 60, -7, 43, 60, -6, 39, 60, -5, 35, 60, -5, 31, + 60, -4, 26, 61, -3, 22, 61, -1, 18, 61, 0, 14, + 61, 1, 9, 61, 3, 4, 61, 4, 0, 62, 6, -4, + 62, 7, -9, 62, 9, -13, 62, 11, -17, 63, 13, -21, + 63, 14, -25, 63, 17, -30, 64, 18, -33, 64, 20, -37, + 64, 23, -41, 65, 25, -46, 65, 27, -49, 66, 29, -53, + 61, -12, 64, 61, -12, 63, 61, -12, 62, 61, -11, 60, + 61, -11, 57, 61, -11, 55, 61, -10, 52, 61, -10, 48, + 61, -9, 44, 61, -8, 40, 61, -8, 36, 61, -7, 32, + 61, -6, 28, 62, -5, 24, 62, -4, 19, 62, -2, 15, + 62, -1, 10, 62, 0, 6, 63, 2, 2, 63, 3, -2, + 63, 5, -7, 63, 7, -11, 64, 9, -15, 64, 10, -19, + 64, 12, -23, 64, 14, -28, 65, 16, -32, 65, 18, -36, + 65, 20, -40, 66, 23, -44, 66, 25, -48, 67, 27, -52, + 62, -14, 65, 62, -14, 64, 62, -14, 63, 62, -13, 61, + 62, -13, 58, 62, -13, 56, 62, -12, 53, 62, -12, 49, + 62, -11, 45, 62, -11, 42, 62, -10, 38, 62, -9, 34, + 63, -8, 29, 63, -7, 25, 63, -6, 21, 63, -5, 17, + 63, -3, 12, 63, -2, 8, 64, 0, 3, 64, 1, -1, + 64, 3, -5, 64, 5, -10, 65, 6, -14, 65, 8, -18, + 65, 10, -22, 65, 12, -26, 66, 14, -30, 66, 16, -34, + 66, 18, -38, 67, 20, -42, 67, 22, -46, 68, 25, -50, + 63, -16, 66, 63, -16, 65, 63, -16, 64, 63, -15, 62, + 63, -15, 59, 63, -15, 57, 63, -14, 54, 63, -14, 50, + 63, -13, 47, 63, -13, 43, 63, -12, 39, 64, -11, 35, + 64, -10, 31, 64, -9, 26, 64, -8, 22, 64, -7, 18, + 64, -5, 13, 64, -4, 9, 65, -3, 5, 65, -1, 1, + 65, 1, -4, 65, 2, -8, 66, 4, -12, 66, 6, -16, + 66, 8, -20, 67, 10, -25, 67, 12, -29, 67, 14, -33, + 67, 16, -37, 68, 18, -41, 68, 20, -45, 69, 22, -48, + 64, -18, 67, 64, -18, 66, 64, -18, 64, 64, -18, 63, + 64, -17, 60, 64, -17, 58, 64, -17, 55, 64, -16, 51, + 64, -15, 48, 64, -15, 44, 65, -14, 41, 65, -13, 37, + 65, -12, 32, 65, -11, 28, 65, -10, 24, 65, -9, 20, + 65, -8, 15, 66, -6, 11, 66, -5, 7, 66, -3, 2, + 66, -2, -2, 66, 0, -6, 67, 2, -11, 67, 4, -15, + 67, 5, -19, 68, 7, -23, 68, 9, -27, 68, 11, -31, + 68, 13, -35, 69, 16, -39, 69, 18, -43, 70, 20, -47, + 65, -20, 68, 65, -20, 67, 65, -20, 65, 65, -20, 64, + 65, -19, 62, 65, -19, 59, 65, -19, 56, 65, -18, 53, + 66, -17, 49, 66, -17, 46, 66, -16, 42, 66, -15, 38, + 66, -14, 33, 66, -13, 29, 66, -12, 25, 66, -11, 21, + 67, -10, 16, 67, -8, 12, 67, -7, 8, 67, -5, 4, + 67, -4, -1, 68, -2, -5, 68, 0, -9, 68, 1, -13, + 68, 3, -17, 69, 5, -22, 69, 7, -26, 69, 9, -29, + 70, 11, -33, 70, 13, -38, 70, 16, -42, 71, 18, -45, + 66, -22, 68, 66, -22, 67, 66, -22, 66, 66, -22, 65, + 66, -21, 63, 67, -21, 60, 67, -21, 57, 67, -20, 54, + 67, -20, 51, 67, -19, 47, 67, -18, 43, 67, -17, 39, + 67, -16, 35, 67, -15, 31, 67, -14, 27, 67, -13, 23, + 68, -12, 18, 68, -10, 14, 68, -9, 10, 68, -8, 6, + 68, -6, 1, 69, -4, -3, 69, -3, -7, 69, -1, -11, + 69, 1, -15, 70, 3, -20, 70, 5, -24, 70, 7, -28, + 71, 9, -32, 71, 11, -36, 71, 13, -40, 72, 15, -44, + 68, -24, 69, 68, -24, 68, 68, -24, 67, 68, -24, 66, + 68, -23, 64, 68, -23, 61, 68, -23, 58, 68, -22, 55, + 68, -22, 52, 68, -21, 48, 68, -20, 45, 68, -19, 41, + 68, -18, 36, 68, -17, 32, 68, -16, 28, 69, -15, 24, + 69, -14, 20, 69, -13, 15, 69, -11, 11, 69, -10, 7, + 70, -8, 2, 70, -6, -2, 70, -5, -6, 70, -3, -10, + 70, -1, -14, 71, 1, -18, 71, 3, -22, 71, 5, -26, + 72, 7, -30, 72, 9, -34, 72, 11, -38, 73, 13, -42, + 69, -26, 70, 69, -26, 69, 69, -26, 68, 69, -26, 66, + 69, -25, 65, 69, -25, 62, 69, -25, 60, 69, -24, 56, + 69, -24, 53, 69, -23, 50, 69, -22, 46, 69, -21, 42, + 69, -20, 38, 69, -19, 34, 70, -18, 30, 70, -17, 26, + 70, -16, 21, 70, -15, 17, 70, -13, 13, 70, -12, 9, + 71, -10, 4, 71, -9, 0, 71, -7, -4, 71, -5, -8, + 72, -3, -12, 72, -1, -17, 72, 1, -21, 72, 2, -25, + 73, 4, -29, 73, 7, -33, 73, 9, -37, 74, 11, -41, + 70, -28, 71, 70, -28, 70, 70, -28, 69, 70, -27, 67, + 70, -27, 66, 70, -27, 63, 70, -27, 61, 70, -26, 57, + 70, -25, 54, 70, -25, 51, 70, -24, 47, 70, -23, 43, + 70, -22, 39, 71, -21, 35, 71, -20, 31, 71, -19, 27, + 71, -18, 23, 71, -17, 18, 71, -15, 14, 72, -14, 10, + 72, -12, 6, 72, -11, 2, 72, -9, -2, 72, -7, -7, + 73, -6, -11, 73, -4, -15, 73, -2, -19, 73, 0, -23, + 74, 2, -27, 74, 5, -31, 74, 7, -35, 75, 9, -39, + 71, -30, 72, 71, -30, 71, 71, -30, 70, 71, -29, 68, + 71, -29, 67, 71, -29, 64, 71, -28, 62, 71, -28, 59, + 71, -27, 55, 71, -27, 52, 71, -26, 49, 72, -25, 45, + 72, -24, 41, 72, -23, 37, 72, -22, 33, 72, -21, 29, + 72, -20, 24, 72, -19, 20, 72, -17, 16, 73, -16, 12, + 73, -14, 7, 73, -13, 3, 73, -11, -1, 73, -9, -5, + 74, -8, -9, 74, -6, -13, 74, -4, -17, 74, -2, -21, + 75, 0, -25, 75, 2, -30, 75, 4, -33, 76, 7, -37, + 72, -32, 73, 72, -32, 72, 72, -31, 71, 72, -31, 69, + 72, -31, 68, 72, -31, 65, 72, -30, 63, 72, -30, 60, + 72, -29, 57, 72, -29, 53, 73, -28, 50, 73, -27, 46, + 73, -26, 42, 73, -25, 38, 73, -24, 34, 73, -23, 30, + 73, -22, 26, 73, -21, 22, 74, -19, 18, 74, -18, 13, + 74, -16, 9, 74, -15, 5, 74, -13, 1, 75, -12, -3, + 75, -10, -7, 75, -8, -12, 75, -6, -16, 76, -4, -20, + 76, -2, -24, 76, 0, -28, 76, 2, -32, 77, 4, -36, + 73, -34, 74, 73, -33, 73, 73, -33, 72, 73, -33, 70, + 73, -33, 69, 73, -33, 66, 73, -32, 64, 74, -32, 61, + 74, -31, 58, 74, -31, 55, 74, -30, 51, 74, -29, 48, + 74, -28, 43, 74, -27, 40, 74, -26, 36, 74, -25, 32, + 74, -24, 27, 75, -23, 23, 75, -21, 19, 75, -20, 15, + 75, -18, 10, 75, -17, 6, 75, -15, 2, 76, -14, -2, + 76, -12, -6, 76, -10, -10, 76, -8, -14, 77, -6, -18, + 77, -4, -22, 77, -2, -26, 77, 0, -30, 78, 2, -34, + 74, -35, 74, 74, -35, 74, 75, -35, 73, 75, -35, 71, + 75, -35, 70, 75, -34, 68, 75, -34, 65, 75, -34, 62, + 75, -33, 59, 75, -32, 56, 75, -32, 52, 75, -31, 49, + 75, -30, 45, 75, -29, 41, 75, -28, 37, 75, -27, 33, + 76, -26, 29, 76, -25, 25, 76, -23, 21, 76, -22, 17, + 76, -20, 12, 76, -19, 8, 77, -17, 4, 77, -16, 0, + 77, -14, -4, 77, -12, -9, 77, -10, -13, 78, -8, -16, + 78, -6, -20, 78, -4, -25, 79, -2, -29, 79, 0, -32, + 76, -37, 75, 76, -37, 75, 76, -37, 74, 76, -37, 72, + 76, -37, 71, 76, -36, 69, 76, -36, 66, 76, -35, 63, + 76, -35, 60, 76, -34, 57, 76, -34, 54, 76, -33, 50, + 76, -32, 46, 76, -31, 42, 76, -30, 39, 77, -29, 35, + 77, -28, 30, 77, -27, 26, 77, -25, 22, 77, -24, 18, + 77, -22, 14, 77, -21, 10, 78, -19, 6, 78, -18, 2, + 78, -16, -2, 78, -14, -7, 79, -12, -11, 79, -10, -15, + 79, -8, -19, 79, -6, -23, 80, -4, -27, 80, -2, -31, + 77, -39, 76, 77, -39, 75, 77, -39, 75, 77, -39, 73, + 77, -38, 72, 77, -38, 70, 77, -38, 67, 77, -37, 64, + 77, -37, 61, 77, -36, 58, 77, -36, 55, 77, -35, 52, + 77, -34, 48, 77, -33, 44, 78, -32, 40, 78, -31, 36, + 78, -30, 32, 78, -29, 28, 78, -27, 24, 78, -26, 20, + 78, -24, 15, 79, -23, 11, 79, -21, 7, 79, -20, 3, + 79, -18, -1, 79, -16, -5, 80, -14, -9, 80, -12, -13, + 80, -11, -17, 80, -8, -21, 81, -6, -25, 81, -4, -29, + 78, -41, 77, 78, -41, 76, 78, -41, 76, 78, -40, 74, + 78, -40, 73, 78, -40, 71, 78, -40, 68, 78, -39, 66, + 78, -39, 63, 78, -38, 60, 78, -37, 56, 78, -37, 53, + 78, -36, 49, 79, -35, 45, 79, -34, 41, 79, -33, 38, + 79, -32, 33, 79, -31, 29, 79, -29, 25, 79, -28, 21, + 80, -26, 17, 80, -25, 13, 80, -23, 9, 80, -22, 5, + 80, -20, 1, 81, -18, -4, 81, -16, -8, 81, -15, -12, + 81, -13, -15, 82, -10, -20, 82, -8, -24, 82, -6, -28, + 79, -43, 78, 79, -43, 78, 79, -43, 77, 79, -43, 75, + 79, -42, 74, 80, -42, 72, 80, -42, 70, 80, -41, 67, + 80, -41, 64, 80, -40, 61, 80, -40, 58, 80, -39, 55, + 80, -38, 51, 80, -37, 47, 80, -36, 43, 80, -35, 39, + 80, -34, 35, 80, -33, 31, 81, -32, 27, 81, -30, 23, + 81, -29, 19, 81, -27, 15, 81, -26, 11, 81, -24, 7, + 82, -23, 3, 82, -21, -2, 82, -19, -6, 82, -17, -10, + 83, -15, -13, 83, -13, -18, 83, -11, -22, 83, -9, -25, + 81, -45, 79, 81, -45, 78, 81, -44, 78, 81, -44, 76, + 81, -44, 75, 81, -44, 73, 81, -43, 71, 81, -43, 68, + 81, -43, 65, 81, -42, 62, 81, -41, 59, 81, -41, 56, + 81, -40, 52, 81, -39, 48, 81, -38, 45, 81, -37, 41, + 82, -36, 37, 82, -35, 33, 82, -33, 29, 82, -32, 25, + 82, -31, 20, 82, -29, 16, 82, -28, 12, 83, -26, 8, + 83, -25, 4, 83, -23, 0, 83, -21, -4, 83, -19, -8, + 84, -17, -12, 84, -15, -16, 84, -13, -20, 84, -11, -24, + 82, -46, 80, 82, -46, 79, 82, -46, 79, 82, -46, 77, + 82, -46, 76, 82, -45, 74, 82, -45, 72, 82, -45, 69, + 82, -44, 67, 82, -44, 64, 82, -43, 60, 82, -42, 57, + 82, -42, 53, 82, -41, 50, 82, -40, 46, 83, -39, 42, + 83, -38, 38, 83, -37, 34, 83, -35, 30, 83, -34, 26, + 83, -33, 22, 83, -31, 18, 84, -30, 14, 84, -28, 10, + 84, -27, 6, 84, -25, 2, 84, -23, -2, 85, -21, -6, + 85, -19, -10, 85, -17, -15, 85, -15, -18, 86, -13, -22, + 83, -48, 81, 83, -48, 80, 83, -48, 79, 83, -48, 78, + 83, -47, 77, 83, -47, 75, 83, -47, 73, 83, -46, 70, + 83, -46, 68, 83, -45, 65, 83, -45, 62, 83, -44, 58, + 83, -43, 55, 83, -42, 51, 84, -42, 47, 84, -41, 44, + 84, -39, 39, 84, -38, 36, 84, -37, 32, 84, -36, 28, + 84, -34, 23, 84, -33, 19, 85, -32, 16, 85, -30, 12, + 85, -28, 8, 85, -27, 3, 85, -25, -1, 86, -23, -5, + 86, -21, -9, 86, -19, -13, 86, -17, -17, 87, -15, -21, + 84, -50, 82, 84, -50, 81, 84, -49, 80, 84, -49, 79, + 84, -49, 78, 84, -49, 76, 84, -49, 74, 84, -48, 71, + 84, -48, 69, 84, -47, 66, 84, -47, 63, 84, -46, 60, + 85, -45, 56, 85, -44, 52, 85, -43, 49, 85, -42, 45, + 85, -41, 41, 85, -40, 37, 85, -39, 33, 85, -38, 29, + 85, -36, 25, 86, -35, 21, 86, -33, 17, 86, -32, 13, + 86, -30, 9, 86, -28, 5, 87, -27, 1, 87, -25, -3, + 87, -23, -7, 87, -21, -11, 87, -19, -15, 88, -17, -19, + 85, -51, 83, 85, -51, 82, 85, -51, 81, 85, -51, 80, + 85, -51, 79, 85, -50, 77, 85, -50, 75, 85, -50, 73, + 85, -49, 70, 85, -49, 67, 86, -48, 64, 86, -48, 61, + 86, -47, 57, 86, -46, 54, 86, -45, 50, 86, -44, 47, + 86, -43, 42, 86, -42, 39, 86, -41, 35, 86, -40, 31, + 87, -38, 26, 87, -37, 23, 87, -35, 19, 87, -34, 15, + 87, -32, 11, 87, -30, 6, 88, -29, 3, 88, -27, -1, + 88, -25, -5, 88, -23, -10, 89, -21, -13, 89, -19, -17, + 86, -53, 84, 86, -53, 83, 86, -53, 82, 86, -53, 81, + 86, -52, 80, 86, -52, 78, 87, -52, 76, 87, -51, 74, + 87, -51, 71, 87, -50, 68, 87, -50, 65, 87, -49, 62, + 87, -48, 59, 87, -48, 55, 87, -47, 52, 87, -46, 48, + 87, -45, 44, 87, -44, 40, 87, -43, 36, 88, -41, 32, + 88, -40, 28, 88, -39, 24, 88, -37, 20, 88, -36, 16, + 88, -34, 12, 89, -32, 8, 89, -31, 4, 89, -29, 0, + 89, -27, -4, 89, -25, -8, 90, -23, -12, 90, -21, -16, + 88, -55, 85, 88, -54, 84, 88, -54, 83, 88, -54, 82, + 88, -54, 81, 88, -54, 79, 88, -53, 77, 88, -53, 75, + 88, -53, 72, 88, -52, 70, 88, -51, 67, 88, -51, 64, + 88, -50, 60, 88, -49, 56, 88, -48, 53, 88, -48, 49, + 88, -46, 45, 88, -45, 42, 89, -44, 38, 89, -43, 34, + 89, -42, 30, 89, -40, 26, 89, -39, 22, 89, -37, 18, + 89, -36, 14, 90, -34, 10, 90, -32, 6, 90, -31, 2, + 90, -29, -2, 91, -27, -6, 91, -25, -10, 91, -23, -14, + 89, -56, 85, 89, -56, 85, 89, -56, 84, 89, -56, 83, + 89, -56, 82, 89, -55, 80, 89, -55, 78, 89, -55, 76, + 89, -54, 73, 89, -54, 71, 89, -53, 68, 89, -53, 65, + 89, -52, 61, 89, -51, 58, 89, -50, 54, 89, -49, 51, + 90, -48, 47, 90, -47, 43, 90, -46, 39, 90, -45, 35, + 90, -43, 31, 90, -42, 27, 90, -41, 23, 90, -39, 19, + 91, -38, 16, 91, -36, 11, 91, -34, 7, 91, -33, 3, + 91, -31, 0, 92, -29, -5, 92, -27, -9, 92, -25, -12, + 90, -58, 86, 90, -58, 86, 90, -57, 85, 90, -57, 84, + 90, -57, 83, 90, -57, 81, 90, -57, 79, 90, -56, 77, + 90, -56, 75, 90, -55, 72, 90, -55, 69, 90, -54, 66, + 90, -53, 62, 90, -53, 59, 90, -52, 56, 91, -51, 52, + 91, -50, 48, 91, -49, 44, 91, -48, 41, 91, -47, 37, + 91, -45, 33, 91, -44, 29, 91, -42, 25, 92, -41, 21, + 92, -40, 17, 92, -38, 13, 92, -36, 9, 92, -35, 5, + 92, -33, 1, 93, -31, -3, 93, -29, -7, 93, -27, -11, + 91, -59, 87, 91, -59, 87, 91, -59, 86, 91, -59, 85, + 91, -59, 84, 91, -58, 82, 91, -58, 80, 91, -58, 78, + 91, -57, 76, 91, -57, 73, 91, -56, 70, 91, -56, 67, + 91, -55, 64, 92, -54, 60, 92, -53, 57, 92, -53, 54, + 92, -51, 50, 92, -50, 46, 92, -49, 42, 92, -48, 38, + 92, -47, 34, 92, -46, 30, 93, -44, 26, 93, -43, 23, + 93, -41, 19, 93, -40, 14, 93, -38, 11, 93, -36, 7, + 94, -35, 3, 94, -33, -2, 94, -31, -5, 94, -29, -9, + 34, 58, 48, 34, 58, 45, 34, 58, 42, 34, 58, 37, + 34, 58, 32, 34, 59, 27, 34, 59, 23, 35, 60, 17, + 35, 60, 12, 35, 61, 7, 35, 61, 2, 35, 62, -3, + 36, 63, -8, 36, 63, -13, 36, 64, -17, 37, 65, -22, + 37, 66, -27, 38, 67, -31, 38, 68, -35, 39, 70, -39, + 39, 71, -44, 40, 72, -48, 40, 73, -52, 41, 75, -56, + 41, 76, -59, 42, 78, -64, 43, 79, -67, 43, 80, -71, + 44, 82, -74, 45, 83, -78, 45, 85, -82, 46, 86, -85, + 34, 57, 48, 34, 57, 45, 34, 58, 42, 34, 58, 37, + 34, 58, 33, 34, 58, 28, 35, 59, 23, 35, 59, 17, + 35, 60, 12, 35, 60, 7, 35, 61, 2, 36, 61, -3, + 36, 62, -8, 36, 63, -13, 37, 64, -17, 37, 65, -22, + 37, 66, -26, 38, 67, -31, 38, 68, -35, 39, 69, -39, + 39, 70, -44, 40, 72, -48, 40, 73, -52, 41, 74, -55, + 41, 76, -59, 42, 77, -63, 43, 79, -67, 43, 80, -71, + 44, 81, -74, 45, 83, -78, 45, 85, -81, 46, 86, -85, + 34, 57, 48, 34, 57, 45, 34, 57, 42, 34, 57, 37, + 35, 57, 33, 35, 58, 28, 35, 58, 23, 35, 59, 17, + 35, 59, 12, 35, 60, 7, 36, 60, 3, 36, 61, -2, + 36, 62, -8, 36, 62, -12, 37, 63, -17, 37, 64, -21, + 38, 65, -26, 38, 66, -31, 38, 68, -35, 39, 69, -39, + 39, 70, -43, 40, 71, -47, 40, 73, -51, 41, 74, -55, + 42, 75, -59, 42, 77, -63, 43, 78, -67, 43, 80, -70, + 44, 81, -74, 45, 83, -78, 45, 84, -81, 46, 86, -84, + 35, 56, 49, 35, 56, 45, 35, 56, 42, 35, 57, 37, + 35, 57, 33, 35, 57, 28, 35, 58, 23, 35, 58, 18, + 35, 58, 13, 36, 59, 8, 36, 60, 3, 36, 60, -2, + 36, 61, -7, 37, 62, -12, 37, 63, -17, 37, 64, -21, + 38, 65, -26, 38, 66, -30, 39, 67, -35, 39, 68, -39, + 40, 70, -43, 40, 71, -47, 41, 72, -51, 41, 73, -55, + 42, 75, -59, 42, 76, -63, 43, 78, -67, 44, 79, -70, + 44, 81, -74, 45, 82, -78, 46, 84, -81, 46, 85, -84, + 35, 56, 49, 35, 56, 45, 35, 56, 42, 35, 56, 38, + 35, 56, 33, 35, 57, 28, 35, 57, 23, 35, 57, 18, + 36, 58, 13, 36, 58, 8, 36, 59, 3, 36, 60, -2, + 37, 61, -7, 37, 61, -12, 37, 62, -16, 37, 63, -21, + 38, 64, -26, 38, 65, -30, 39, 67, -34, 39, 68, -38, + 40, 69, -43, 40, 70, -47, 41, 72, -51, 41, 73, -55, + 42, 74, -58, 43, 76, -63, 43, 77, -66, 44, 79, -70, + 44, 80, -73, 45, 82, -77, 46, 83, -81, 46, 85, -84, + 35, 55, 49, 35, 55, 46, 35, 55, 42, 35, 55, 38, + 35, 56, 33, 35, 56, 29, 35, 56, 24, 36, 57, 18, + 36, 57, 13, 36, 58, 8, 36, 58, 3, 36, 59, -1, + 37, 60, -7, 37, 61, -11, 37, 62, -16, 38, 63, -20, + 38, 64, -25, 39, 65, -30, 39, 66, -34, 39, 67, -38, + 40, 68, -43, 40, 70, -47, 41, 71, -50, 41, 72, -54, + 42, 74, -58, 43, 75, -62, 43, 77, -66, 44, 78, -70, + 45, 80, -73, 45, 81, -77, 46, 83, -80, 47, 84, -84, + 35, 54, 49, 35, 54, 46, 35, 54, 43, 35, 54, 38, + 36, 55, 34, 36, 55, 29, 36, 55, 24, 36, 56, 19, + 36, 56, 14, 36, 57, 9, 36, 58, 4, 37, 58, -1, + 37, 59, -6, 37, 60, -11, 38, 61, -15, 38, 62, -20, + 38, 63, -25, 39, 64, -29, 39, 65, -33, 40, 66, -38, + 40, 68, -42, 41, 69, -46, 41, 70, -50, 42, 72, -54, + 42, 73, -58, 43, 75, -62, 44, 76, -66, 44, 78, -69, + 45, 79, -73, 45, 81, -77, 46, 82, -80, 47, 84, -83, + 36, 53, 49, 36, 53, 46, 36, 53, 43, 36, 53, 38, + 36, 54, 34, 36, 54, 29, 36, 54, 25, 36, 55, 19, + 36, 55, 14, 37, 56, 9, 37, 57, 4, 37, 57, 0, + 37, 58, -6, 38, 59, -10, 38, 60, -15, 38, 61, -19, + 39, 62, -24, 39, 63, -29, 40, 64, -33, 40, 65, -37, + 40, 67, -42, 41, 68, -46, 41, 69, -50, 42, 71, -53, + 43, 72, -57, 43, 74, -61, 44, 75, -65, 44, 77, -69, + 45, 78, -72, 46, 80, -76, 46, 82, -80, 47, 83, -83, + 36, 52, 49, 36, 52, 46, 36, 52, 43, 36, 52, 39, + 36, 53, 34, 36, 53, 30, 36, 53, 25, 37, 54, 19, + 37, 54, 15, 37, 55, 10, 37, 55, 5, 37, 56, 0, + 38, 57, -5, 38, 58, -10, 38, 59, -14, 39, 60, -19, + 39, 61, -24, 39, 62, -28, 40, 63, -32, 40, 64, -37, + 41, 66, -41, 41, 67, -45, 42, 69, -49, 42, 70, -53, + 43, 71, -57, 43, 73, -61, 44, 74, -65, 45, 76, -68, + 45, 77, -72, 46, 79, -76, 47, 81, -79, 47, 82, -83, + 36, 51, 50, 37, 51, 46, 37, 51, 43, 37, 51, 39, + 37, 51, 35, 37, 52, 30, 37, 52, 26, 37, 53, 20, + 37, 53, 15, 37, 54, 10, 38, 54, 5, 38, 55, 1, + 38, 56, -5, 38, 57, -9, 39, 58, -14, 39, 59, -18, + 39, 60, -23, 40, 61, -28, 40, 62, -32, 41, 63, -36, + 41, 65, -41, 42, 66, -45, 42, 68, -49, 43, 69, -52, + 43, 70, -56, 44, 72, -60, 44, 73, -64, 45, 75, -68, + 46, 77, -71, 46, 78, -75, 47, 80, -79, 48, 81, -82, + 37, 49, 50, 37, 49, 47, 37, 50, 44, 37, 50, 40, + 37, 50, 35, 37, 50, 31, 37, 51, 26, 38, 51, 21, + 38, 52, 16, 38, 52, 11, 38, 53, 6, 38, 54, 1, + 39, 55, -4, 39, 56, -9, 39, 56, -13, 39, 57, -18, + 40, 59, -23, 40, 60, -27, 41, 61, -31, 41, 62, -35, + 42, 64, -40, 42, 65, -44, 43, 66, -48, 43, 68, -52, + 44, 69, -56, 44, 71, -60, 45, 72, -64, 45, 74, -67, + 46, 76, -71, 47, 77, -75, 47, 79, -78, 48, 81, -82, + 38, 48, 50, 38, 48, 47, 38, 48, 44, 38, 48, 40, + 38, 48, 36, 38, 49, 32, 38, 49, 27, 38, 49, 21, + 38, 50, 17, 38, 51, 12, 39, 51, 7, 39, 52, 2, + 39, 53, -3, 39, 54, -8, 40, 55, -12, 40, 56, -17, + 40, 57, -22, 41, 58, -26, 41, 59, -30, 42, 61, -34, + 42, 62, -39, 43, 63, -43, 43, 65, -47, 44, 66, -51, + 44, 68, -55, 45, 69, -59, 45, 71, -63, 46, 73, -66, + 46, 74, -70, 47, 76, -74, 48, 78, -77, 48, 79, -81, + 38, 46, 50, 38, 46, 48, 38, 46, 45, 38, 46, 41, + 38, 47, 37, 38, 47, 32, 39, 47, 28, 39, 48, 22, + 39, 48, 17, 39, 49, 13, 39, 50, 8, 39, 50, 3, + 40, 51, -2, 40, 52, -7, 40, 53, -11, 41, 54, -16, + 41, 56, -21, 41, 57, -25, 42, 58, -29, 42, 59, -34, + 43, 61, -38, 43, 62, -42, 44, 63, -46, 44, 65, -50, + 45, 66, -54, 45, 68, -58, 46, 70, -62, 46, 71, -66, + 47, 73, -69, 48, 75, -73, 48, 76, -77, 49, 78, -80, + 39, 44, 51, 39, 44, 48, 39, 45, 45, 39, 45, 41, + 39, 45, 37, 39, 45, 33, 39, 46, 28, 39, 46, 23, + 39, 47, 18, 40, 47, 13, 40, 48, 9, 40, 49, 4, + 40, 50, -1, 41, 51, -6, 41, 52, -11, 41, 53, -15, + 42, 54, -20, 42, 55, -24, 42, 56, -29, 43, 58, -33, + 43, 59, -37, 44, 61, -42, 44, 62, -45, 45, 63, -49, + 45, 65, -53, 46, 67, -58, 46, 68, -61, 47, 70, -65, + 47, 72, -69, 48, 73, -73, 49, 75, -76, 49, 77, -79, + 39, 43, 51, 39, 43, 48, 40, 43, 46, 40, 43, 42, + 40, 43, 38, 40, 44, 34, 40, 44, 29, 40, 45, 24, + 40, 45, 19, 40, 46, 14, 40, 46, 9, 41, 47, 5, + 41, 48, 0, 41, 49, -5, 41, 50, -10, 42, 51, -14, + 42, 52, -19, 42, 54, -23, 43, 55, -28, 43, 56, -32, + 44, 58, -37, 44, 59, -41, 45, 60, -45, 45, 62, -49, + 46, 64, -52, 46, 65, -57, 47, 67, -60, 47, 68, -64, + 48, 70, -68, 48, 72, -72, 49, 74, -75, 50, 75, -79, + 40, 41, 51, 40, 41, 49, 40, 41, 46, 40, 41, 42, + 40, 41, 39, 40, 42, 34, 41, 42, 30, 41, 43, 25, + 41, 43, 20, 41, 44, 15, 41, 45, 10, 41, 45, 6, + 42, 46, 0, 42, 47, -4, 42, 48, -9, 42, 49, -13, + 43, 51, -18, 43, 52, -23, 43, 53, -27, 44, 54, -31, + 44, 56, -36, 45, 57, -40, 45, 59, -44, 46, 60, -48, + 46, 62, -52, 47, 64, -56, 47, 65, -60, 48, 67, -63, + 48, 69, -67, 49, 71, -71, 50, 72, -74, 50, 74, -78, + 41, 39, 52, 41, 39, 49, 41, 39, 47, 41, 39, 43, + 41, 40, 39, 41, 40, 35, 41, 40, 31, 41, 41, 26, + 41, 41, 21, 42, 42, 16, 42, 43, 11, 42, 44, 7, + 42, 44, 1, 43, 45, -3, 43, 46, -8, 43, 48, -12, + 43, 49, -17, 44, 50, -21, 44, 51, -26, 44, 53, -30, + 45, 54, -35, 45, 56, -39, 46, 57, -43, 46, 59, -47, + 47, 60, -51, 47, 62, -55, 48, 64, -59, 48, 65, -62, + 49, 67, -66, 50, 69, -70, 50, 71, -74, 51, 72, -77, + 42, 37, 52, 42, 37, 50, 42, 37, 47, 42, 37, 44, + 42, 38, 40, 42, 38, 36, 42, 38, 32, 42, 39, 26, + 42, 39, 22, 42, 40, 17, 43, 41, 12, 43, 42, 8, + 43, 43, 2, 43, 44, -2, 43, 45, -7, 44, 46, -11, + 44, 47, -16, 44, 48, -20, 45, 49, -25, 45, 51, -29, + 46, 52, -34, 46, 54, -38, 46, 55, -42, 47, 57, -46, + 47, 59, -50, 48, 60, -54, 48, 62, -58, 49, 64, -61, + 49, 65, -65, 50, 67, -69, 51, 69, -73, 51, 71, -76, + 42, 35, 53, 42, 35, 50, 42, 35, 48, 43, 35, 44, + 43, 36, 41, 43, 36, 37, 43, 36, 32, 43, 37, 27, + 43, 37, 23, 43, 38, 18, 43, 39, 13, 44, 40, 9, + 44, 41, 4, 44, 42, -1, 44, 43, -6, 45, 44, -10, + 45, 45, -15, 45, 46, -19, 46, 48, -24, 46, 49, -28, + 46, 51, -33, 47, 52, -37, 47, 54, -41, 48, 55, -45, + 48, 57, -49, 49, 59, -53, 49, 60, -57, 50, 62, -60, + 50, 64, -64, 51, 66, -68, 51, 67, -72, 52, 69, -75, + 43, 33, 53, 43, 33, 51, 43, 33, 49, 43, 33, 45, + 43, 34, 42, 43, 34, 38, 44, 34, 33, 44, 35, 28, + 44, 35, 24, 44, 36, 19, 44, 37, 15, 44, 38, 10, + 45, 39, 5, 45, 40, 0, 45, 41, -4, 45, 42, -9, + 46, 43, -14, 46, 44, -18, 46, 46, -22, 47, 47, -27, + 47, 49, -31, 47, 50, -36, 48, 52, -40, 48, 53, -44, + 49, 55, -47, 49, 57, -52, 50, 58, -56, 50, 60, -59, + 51, 62, -63, 51, 64, -67, 52, 66, -71, 52, 68, -74, + 44, 31, 54, 44, 31, 51, 44, 31, 49, 44, 31, 46, + 44, 31, 42, 44, 32, 39, 44, 32, 34, 45, 33, 29, + 45, 33, 25, 45, 34, 20, 45, 35, 16, 45, 35, 11, + 45, 36, 6, 46, 37, 1, 46, 39, -3, 46, 40, -8, + 46, 41, -13, 47, 42, -17, 47, 44, -21, 47, 45, -26, + 48, 47, -30, 48, 48, -34, 49, 50, -38, 49, 51, -42, + 49, 53, -46, 50, 55, -51, 50, 57, -54, 51, 58, -58, + 51, 60, -62, 52, 62, -66, 53, 64, -70, 53, 66, -73, + 45, 28, 54, 45, 29, 52, 45, 29, 50, 45, 29, 47, + 45, 29, 43, 45, 30, 39, 45, 30, 35, 45, 31, 30, + 45, 31, 26, 46, 32, 21, 46, 33, 17, 46, 33, 12, + 46, 34, 7, 46, 35, 3, 47, 36, -2, 47, 38, -6, + 47, 39, -11, 47, 40, -16, 48, 42, -20, 48, 43, -24, + 49, 45, -29, 49, 46, -33, 49, 48, -37, 50, 49, -41, + 50, 51, -45, 51, 53, -50, 51, 55, -53, 52, 56, -57, + 52, 58, -61, 53, 60, -65, 53, 62, -69, 54, 64, -72, + 46, 26, 55, 46, 26, 53, 46, 27, 51, 46, 27, 47, + 46, 27, 44, 46, 27, 40, 46, 28, 36, 46, 28, 31, + 46, 29, 27, 46, 30, 23, 47, 30, 18, 47, 31, 13, + 47, 32, 8, 47, 33, 4, 47, 34, -1, 48, 35, -5, + 48, 37, -10, 48, 38, -15, 49, 39, -19, 49, 41, -23, + 49, 42, -28, 50, 44, -32, 50, 46, -36, 50, 47, -40, + 51, 49, -44, 51, 51, -48, 52, 53, -52, 52, 54, -56, + 53, 56, -60, 53, 58, -64, 54, 60, -67, 54, 62, -71, + 47, 24, 55, 47, 24, 53, 47, 24, 51, 47, 25, 48, + 47, 25, 45, 47, 25, 41, 47, 26, 37, 47, 26, 33, + 47, 27, 28, 47, 27, 24, 48, 28, 19, 48, 29, 15, + 48, 30, 10, 48, 31, 5, 48, 32, 1, 49, 33, -4, + 49, 35, -9, 49, 36, -13, 49, 37, -18, 50, 39, -22, + 50, 40, -27, 51, 42, -31, 51, 44, -35, 51, 45, -39, + 52, 47, -43, 52, 49, -47, 53, 51, -51, 53, 52, -55, + 54, 54, -58, 54, 56, -63, 55, 58, -66, 55, 60, -70, + 48, 22, 56, 48, 22, 54, 48, 22, 52, 48, 22, 49, + 48, 23, 46, 48, 23, 42, 48, 23, 38, 48, 24, 34, + 48, 25, 29, 48, 25, 25, 48, 26, 20, 49, 27, 16, + 49, 28, 11, 49, 29, 6, 49, 30, 2, 49, 31, -3, + 50, 32, -8, 50, 34, -12, 50, 35, -16, 51, 37, -20, + 51, 38, -25, 51, 40, -29, 52, 41, -33, 52, 43, -37, + 52, 45, -41, 53, 47, -46, 53, 48, -50, 54, 50, -53, + 54, 52, -57, 55, 54, -61, 55, 56, -65, 56, 58, -69, + 49, 20, 56, 49, 20, 55, 49, 20, 53, 49, 20, 50, + 49, 20, 47, 49, 21, 43, 49, 21, 39, 49, 22, 35, + 49, 22, 30, 49, 23, 26, 49, 24, 22, 50, 25, 17, + 50, 26, 12, 50, 27, 8, 50, 28, 3, 50, 29, -1, + 51, 30, -6, 51, 31, -11, 51, 33, -15, 52, 34, -19, + 52, 36, -24, 52, 38, -28, 53, 39, -32, 53, 41, -36, + 53, 43, -40, 54, 45, -45, 54, 46, -48, 55, 48, -52, + 55, 50, -56, 56, 52, -60, 56, 54, -64, 57, 56, -67, + 50, 17, 57, 50, 17, 55, 50, 18, 53, 50, 18, 51, + 50, 18, 48, 50, 18, 44, 50, 19, 40, 50, 19, 36, + 50, 20, 32, 50, 21, 27, 50, 21, 23, 51, 22, 18, + 51, 23, 13, 51, 24, 9, 51, 25, 4, 51, 27, 0, + 52, 28, -5, 52, 29, -9, 52, 31, -14, 52, 32, -18, + 53, 34, -22, 53, 35, -27, 53, 37, -31, 54, 39, -35, + 54, 40, -39, 55, 42, -43, 55, 44, -47, 55, 46, -51, + 56, 48, -55, 56, 50, -59, 57, 52, -63, 57, 54, -66, + 51, 15, 58, 51, 15, 56, 51, 15, 54, 51, 16, 52, + 51, 16, 49, 51, 16, 45, 51, 17, 42, 51, 17, 37, + 51, 18, 33, 51, 18, 29, 51, 19, 24, 51, 20, 20, + 52, 21, 15, 52, 22, 10, 52, 23, 6, 52, 24, 1, + 53, 26, -3, 53, 27, -8, 53, 28, -12, 53, 30, -16, + 54, 32, -21, 54, 33, -25, 54, 35, -29, 55, 36, -33, + 55, 38, -37, 55, 40, -42, 56, 42, -46, 56, 44, -50, + 57, 46, -53, 57, 48, -58, 58, 50, -61, 58, 52, -65, + 52, 13, 58, 52, 13, 57, 52, 13, 55, 52, 13, 52, + 52, 14, 50, 52, 14, 46, 52, 14, 43, 52, 15, 38, + 52, 15, 34, 52, 16, 30, 52, 17, 25, 52, 18, 21, + 53, 19, 16, 53, 20, 12, 53, 21, 7, 53, 22, 3, + 53, 23, -2, 54, 25, -6, 54, 26, -11, 54, 28, -15, + 55, 29, -20, 55, 31, -24, 55, 33, -28, 56, 34, -32, + 56, 36, -36, 56, 38, -40, 57, 40, -44, 57, 42, -48, + 58, 44, -52, 58, 46, -56, 59, 48, -60, 59, 50, -64, + 53, 11, 59, 53, 11, 57, 53, 11, 56, 53, 11, 53, + 53, 11, 50, 53, 12, 47, 53, 12, 44, 53, 13, 39, + 53, 13, 35, 53, 14, 31, 53, 15, 27, 53, 15, 22, + 54, 16, 17, 54, 18, 13, 54, 19, 9, 54, 20, 4, + 54, 21, -1, 55, 22, -5, 55, 24, -9, 55, 25, -14, + 56, 27, -18, 56, 29, -22, 56, 30, -27, 56, 32, -31, + 57, 34, -35, 57, 36, -39, 58, 38, -43, 58, 39, -47, + 58, 41, -51, 59, 44, -55, 59, 46, -59, 60, 48, -62, + 54, 8, 60, 54, 8, 58, 54, 9, 57, 54, 9, 54, + 54, 9, 51, 54, 9, 48, 54, 10, 45, 54, 10, 40, + 54, 11, 36, 54, 12, 32, 54, 12, 28, 54, 13, 24, + 55, 14, 19, 55, 15, 14, 55, 16, 10, 55, 17, 6, + 55, 19, 1, 56, 20, -4, 56, 22, -8, 56, 23, -12, + 56, 25, -17, 57, 26, -21, 57, 28, -25, 57, 30, -29, + 58, 31, -33, 58, 34, -38, 59, 35, -42, 59, 37, -45, + 59, 39, -49, 60, 41, -53, 60, 43, -57, 61, 45, -61, + 55, 6, 60, 55, 6, 59, 55, 6, 57, 55, 7, 55, + 55, 7, 52, 55, 7, 49, 55, 8, 46, 55, 8, 42, + 55, 9, 38, 55, 9, 34, 55, 10, 29, 55, 11, 25, + 56, 12, 20, 56, 13, 16, 56, 14, 11, 56, 15, 7, + 56, 17, 2, 57, 18, -2, 57, 19, -6, 57, 21, -11, + 57, 22, -15, 58, 24, -19, 58, 26, -24, 58, 27, -28, + 59, 29, -32, 59, 31, -36, 59, 33, -40, 60, 35, -44, + 60, 37, -48, 61, 39, -52, 61, 41, -56, 62, 43, -60, + 56, 3, 61, 56, 3, 60, 56, 4, 58, 56, 4, 56, + 56, 4, 54, 56, 4, 51, 56, 5, 47, 56, 5, 43, + 56, 6, 39, 57, 7, 35, 57, 7, 31, 57, 8, 27, + 57, 9, 22, 57, 10, 18, 57, 11, 13, 57, 12, 9, + 58, 14, 4, 58, 15, 0, 58, 16, -5, 58, 18, -9, + 59, 20, -13, 59, 21, -18, 59, 23, -22, 60, 25, -26, + 60, 26, -30, 60, 28, -34, 61, 30, -38, 61, 32, -42, + 61, 34, -46, 62, 36, -50, 62, 38, -54, 63, 40, -58, + 57, 1, 62, 57, 1, 61, 57, 1, 59, 57, 1, 57, + 57, 2, 55, 57, 2, 52, 57, 3, 48, 57, 3, 44, + 58, 4, 40, 58, 4, 36, 58, 5, 32, 58, 6, 28, + 58, 7, 23, 58, 8, 19, 58, 9, 15, 58, 10, 10, + 59, 11, 6, 59, 13, 1, 59, 14, -3, 59, 16, -7, + 60, 17, -12, 60, 19, -16, 60, 21, -20, 61, 22, -24, + 61, 24, -28, 61, 26, -33, 62, 28, -37, 62, 30, -41, + 62, 32, -45, 63, 34, -49, 63, 36, -53, 64, 38, -56, + 58, -1, 63, 58, -1, 62, 58, -1, 60, 58, -1, 58, + 58, 0, 56, 58, 0, 53, 58, 0, 49, 58, 1, 45, + 59, 1, 42, 59, 2, 38, 59, 3, 34, 59, 4, 29, + 59, 5, 25, 59, 6, 20, 59, 7, 16, 60, 8, 12, + 60, 9, 7, 60, 11, 3, 60, 12, -2, 60, 13, -6, + 61, 15, -10, 61, 17, -15, 61, 18, -19, 62, 20, -23, + 62, 22, -27, 62, 24, -31, 63, 26, -35, 63, 28, -39, + 63, 30, -43, 64, 32, -47, 64, 34, -51, 64, 36, -55, + 59, -3, 64, 59, -3, 62, 59, -3, 61, 59, -3, 59, + 59, -3, 57, 59, -2, 54, 59, -2, 51, 60, -1, 47, + 60, -1, 43, 60, 0, 39, 60, 1, 35, 60, 1, 31, + 60, 2, 26, 60, 3, 22, 60, 4, 18, 61, 6, 13, + 61, 7, 9, 61, 8, 4, 61, 10, 0, 61, 11, -4, + 62, 13, -9, 62, 14, -13, 62, 16, -17, 63, 18, -21, + 63, 20, -25, 63, 22, -30, 64, 23, -34, 64, 25, -38, + 64, 27, -42, 65, 30, -46, 65, 32, -50, 65, 34, -53, + 60, -6, 64, 60, -5, 63, 60, -5, 62, 60, -5, 60, + 60, -5, 58, 61, -4, 55, 61, -4, 52, 61, -4, 48, + 61, -3, 44, 61, -2, 40, 61, -2, 36, 61, -1, 32, + 61, 0, 28, 61, 1, 23, 61, 2, 19, 62, 3, 15, + 62, 5, 10, 62, 6, 6, 62, 7, 2, 62, 9, -3, + 63, 11, -7, 63, 12, -12, 63, 14, -16, 64, 16, -20, + 64, 17, -24, 64, 19, -28, 64, 21, -32, 65, 23, -36, + 65, 25, -40, 66, 27, -44, 66, 29, -48, 66, 31, -52, + 61, -8, 65, 62, -8, 64, 62, -7, 63, 62, -7, 61, + 62, -7, 58, 62, -7, 56, 62, -6, 53, 62, -6, 49, + 62, -5, 45, 62, -5, 42, 62, -4, 38, 62, -3, 34, + 62, -2, 29, 62, -1, 25, 63, 0, 21, 63, 1, 16, + 63, 3, 12, 63, 4, 7, 63, 5, 3, 64, 7, -1, + 64, 8, -6, 64, 10, -10, 64, 12, -14, 65, 13, -18, + 65, 15, -22, 65, 17, -27, 65, 19, -31, 66, 21, -35, + 66, 23, -39, 67, 25, -43, 67, 27, -47, 67, 29, -50, + 63, -10, 66, 63, -10, 65, 63, -10, 64, 63, -9, 62, + 63, -9, 59, 63, -9, 57, 63, -8, 54, 63, -8, 50, + 63, -7, 47, 63, -7, 43, 63, -6, 39, 63, -5, 35, + 63, -4, 30, 63, -3, 26, 64, -2, 22, 64, -1, 18, + 64, 0, 13, 64, 2, 9, 64, 3, 5, 65, 4, 0, + 65, 6, -4, 65, 8, -8, 65, 9, -13, 66, 11, -17, + 66, 13, -21, 66, 15, -25, 66, 17, -29, 67, 19, -33, + 67, 20, -37, 68, 23, -41, 68, 25, -45, 68, 27, -49, + 64, -12, 67, 64, -12, 66, 64, -12, 64, 64, -11, 63, + 64, -11, 60, 64, -11, 58, 64, -11, 55, 64, -10, 51, + 64, -9, 48, 64, -9, 44, 64, -8, 40, 64, -7, 36, + 64, -6, 32, 65, -5, 28, 65, -4, 23, 65, -3, 19, + 65, -2, 15, 65, -1, 10, 65, 1, 6, 66, 2, 2, + 66, 4, -3, 66, 5, -7, 66, 7, -11, 67, 9, -15, + 67, 10, -19, 67, 13, -24, 68, 14, -28, 68, 16, -32, + 68, 18, -35, 69, 20, -40, 69, 23, -44, 69, 25, -47, + 65, -14, 68, 65, -14, 67, 65, -14, 65, 65, -14, 64, + 65, -13, 61, 65, -13, 59, 65, -13, 56, 65, -12, 52, + 65, -12, 49, 65, -11, 45, 65, -10, 42, 65, -9, 38, + 66, -8, 33, 66, -8, 29, 66, -6, 25, 66, -5, 21, + 66, -4, 16, 66, -3, 12, 67, -1, 8, 67, 0, 3, + 67, 2, -1, 67, 3, -5, 67, 5, -9, 68, 7, -13, + 68, 8, -18, 68, 10, -22, 69, 12, -26, 69, 14, -30, + 69, 16, -34, 70, 18, -38, 70, 20, -42, 70, 22, -46, + 66, -16, 68, 66, -16, 67, 66, -16, 66, 66, -16, 64, + 66, -15, 62, 66, -15, 60, 66, -15, 57, 66, -14, 54, + 66, -14, 50, 66, -13, 47, 66, -12, 43, 67, -12, 39, + 67, -11, 35, 67, -10, 30, 67, -9, 26, 67, -8, 22, + 67, -6, 18, 67, -5, 13, 68, -4, 9, 68, -2, 5, + 68, -1, 0, 68, 1, -4, 68, 3, -8, 69, 4, -12, + 69, 6, -16, 69, 8, -20, 70, 10, -24, 70, 12, -28, + 70, 14, -32, 71, 16, -37, 71, 18, -40, 71, 20, -44, + 67, -18, 69, 67, -18, 68, 67, -18, 67, 67, -18, 65, + 67, -17, 63, 67, -17, 61, 67, -17, 58, 67, -16, 55, + 67, -16, 51, 67, -15, 48, 68, -14, 44, 68, -14, 40, + 68, -13, 36, 68, -12, 32, 68, -11, 28, 68, -10, 24, + 68, -8, 19, 68, -7, 15, 69, -6, 11, 69, -4, 7, + 69, -3, 2, 69, -1, -2, 70, 0, -6, 70, 2, -10, + 70, 4, -14, 70, 6, -19, 71, 8, -23, 71, 10, -27, + 71, 11, -31, 72, 14, -35, 72, 16, -39, 72, 18, -43, + 68, -20, 70, 68, -20, 69, 68, -20, 68, 68, -20, 66, + 68, -19, 64, 68, -19, 62, 68, -19, 59, 68, -18, 56, + 69, -18, 53, 69, -17, 49, 69, -16, 46, 69, -16, 42, + 69, -15, 37, 69, -14, 33, 69, -13, 29, 69, -12, 25, + 69, -10, 21, 70, -9, 16, 70, -8, 12, 70, -7, 8, + 70, -5, 4, 70, -3, -1, 71, -2, -5, 71, 0, -9, + 71, 2, -13, 71, 4, -17, 72, 5, -21, 72, 7, -25, + 72, 9, -29, 73, 11, -33, 73, 14, -37, 73, 16, -41, + 69, -22, 71, 69, -22, 70, 69, -22, 69, 69, -22, 67, + 69, -21, 65, 69, -21, 63, 70, -21, 60, 70, -20, 57, + 70, -20, 54, 70, -19, 50, 70, -18, 47, 70, -18, 43, + 70, -17, 39, 70, -16, 35, 70, -15, 31, 70, -14, 27, + 71, -13, 22, 71, -11, 18, 71, -10, 14, 71, -9, 10, + 71, -7, 5, 71, -5, 1, 72, -4, -3, 72, -2, -7, + 72, -1, -11, 72, 1, -16, 73, 3, -20, 73, 5, -24, + 73, 7, -28, 74, 9, -32, 74, 11, -36, 74, 13, -40, + 70, -24, 72, 71, -24, 71, 71, -24, 70, 71, -24, 68, + 71, -23, 66, 71, -23, 64, 71, -23, 62, 71, -22, 58, + 71, -22, 55, 71, -21, 52, 71, -21, 48, 71, -20, 44, + 71, -19, 40, 71, -18, 36, 71, -17, 32, 71, -16, 28, + 72, -15, 24, 72, -13, 19, 72, -12, 15, 72, -11, 11, + 72, -9, 7, 73, -8, 3, 73, -6, -2, 73, -4, -6, + 73, -3, -10, 73, -1, -14, 74, 1, -18, 74, 3, -22, + 74, 5, -26, 75, 7, -30, 75, 9, -34, 75, 11, -38, + 72, -26, 73, 72, -26, 72, 72, -26, 71, 72, -26, 69, + 72, -25, 67, 72, -25, 65, 72, -25, 63, 72, -24, 59, + 72, -24, 56, 72, -23, 53, 72, -22, 49, 72, -22, 46, + 72, -21, 41, 72, -20, 38, 72, -19, 34, 73, -18, 30, + 73, -17, 25, 73, -15, 21, 73, -14, 17, 73, -13, 13, + 73, -11, 8, 74, -10, 4, 74, -8, 0, 74, -7, -4, + 74, -5, -8, 75, -3, -12, 75, -1, -16, 75, 1, -20, + 75, 3, -24, 76, 5, -29, 76, 7, -33, 76, 9, -36, + 73, -28, 73, 73, -28, 73, 73, -28, 72, 73, -28, 70, + 73, -27, 68, 73, -27, 66, 73, -27, 64, 73, -26, 61, + 73, -26, 58, 73, -25, 54, 73, -24, 51, 73, -24, 47, + 73, -23, 43, 73, -22, 39, 74, -21, 35, 74, -20, 31, + 74, -19, 27, 74, -17, 23, 74, -16, 18, 74, -15, 14, + 75, -13, 10, 75, -12, 6, 75, -10, 2, 75, -9, -2, + 75, -7, -6, 76, -5, -11, 76, -3, -15, 76, -1, -19, + 76, 0, -23, 77, 3, -27, 77, 5, -31, 77, 7, -35, + 74, -30, 74, 74, -30, 73, 74, -30, 73, 74, -29, 71, + 74, -29, 69, 74, -29, 67, 74, -29, 65, 74, -28, 62, + 74, -28, 59, 74, -27, 55, 74, -26, 52, 74, -26, 48, + 74, -25, 44, 75, -24, 40, 75, -23, 37, 75, -22, 33, + 75, -21, 28, 75, -20, 24, 75, -18, 20, 75, -17, 16, + 76, -15, 11, 76, -14, 7, 76, -12, 3, 76, -11, -1, + 76, -9, -5, 77, -7, -9, 77, -5, -13, 77, -4, -17, + 77, -2, -21, 78, 1, -25, 78, 2, -29, 78, 5, -33, + 75, -32, 75, 75, -32, 74, 75, -32, 73, 75, -31, 72, + 75, -31, 70, 75, -31, 68, 75, -30, 66, 75, -30, 63, + 75, -30, 60, 75, -29, 57, 75, -28, 53, 76, -28, 50, + 76, -27, 46, 76, -26, 42, 76, -25, 38, 76, -24, 34, + 76, -23, 30, 76, -22, 26, 76, -20, 22, 77, -19, 17, + 77, -17, 13, 77, -16, 9, 77, -14, 5, 77, -13, 1, + 78, -11, -3, 78, -9, -8, 78, -7, -12, 78, -6, -16, + 79, -4, -19, 79, -2, -24, 79, 0, -28, 79, 2, -32, + 76, -34, 76, 76, -33, 75, 76, -33, 74, 76, -33, 73, + 76, -33, 71, 76, -33, 69, 76, -32, 67, 76, -32, 64, + 76, -31, 61, 77, -31, 58, 77, -30, 55, 77, -30, 51, + 77, -29, 47, 77, -28, 43, 77, -27, 39, 77, -26, 35, + 77, -25, 31, 77, -23, 27, 78, -22, 23, 78, -21, 19, + 78, -19, 14, 78, -18, 10, 78, -16, 6, 78, -15, 2, + 79, -13, -2, 79, -11, -6, 79, -10, -10, 79, -8, -14, + 80, -6, -18, 80, -4, -22, 80, -2, -26, 80, 0, -30, + 77, -35, 77, 77, -35, 76, 77, -35, 75, 77, -35, 74, + 77, -35, 72, 77, -35, 70, 77, -34, 68, 78, -34, 65, + 78, -33, 62, 78, -33, 59, 78, -32, 56, 78, -31, 52, + 78, -31, 48, 78, -30, 45, 78, -29, 41, 78, -28, 37, + 78, -27, 33, 78, -25, 29, 79, -24, 25, 79, -23, 21, + 79, -21, 16, 79, -20, 12, 79, -18, 8, 80, -17, 4, + 80, -15, 0, 80, -13, -4, 80, -12, -8, 80, -10, -12, + 81, -8, -16, 81, -6, -21, 81, -4, -24, 81, -2, -28, + 79, -37, 78, 79, -37, 77, 79, -37, 76, 79, -37, 75, + 79, -37, 73, 79, -36, 71, 79, -36, 69, 79, -36, 66, + 79, -35, 63, 79, -35, 60, 79, -34, 57, 79, -33, 54, + 79, -32, 50, 79, -32, 46, 79, -31, 42, 79, -30, 38, + 79, -29, 34, 80, -27, 30, 80, -26, 26, 80, -25, 22, + 80, -23, 18, 80, -22, 14, 80, -20, 10, 81, -19, 6, + 81, -17, 2, 81, -15, -3, 81, -14, -7, 81, -12, -11, + 82, -10, -15, 82, -8, -19, 82, -6, -23, 83, -4, -27, + 80, -39, 79, 80, -39, 78, 80, -39, 77, 80, -39, 76, + 80, -39, 74, 80, -39, 73, 80, -38, 70, 80, -38, 68, + 80, -37, 65, 80, -37, 62, 80, -36, 59, 80, -36, 55, + 80, -35, 51, 81, -34, 48, 81, -33, 44, 81, -32, 40, + 81, -31, 36, 81, -30, 32, 81, -29, 28, 81, -27, 24, + 81, -26, 20, 82, -24, 16, 82, -23, 12, 82, -21, 8, + 82, -20, 4, 82, -18, -1, 83, -16, -5, 83, -15, -9, + 83, -13, -13, 83, -11, -17, 84, -9, -21, 84, -7, -25, + 81, -41, 80, 81, -41, 79, 81, -41, 78, 81, -41, 77, + 81, -41, 75, 81, -40, 74, 81, -40, 72, 81, -40, 69, + 81, -39, 66, 81, -39, 63, 81, -38, 60, 82, -37, 57, + 82, -37, 53, 82, -36, 49, 82, -35, 45, 82, -34, 42, + 82, -33, 37, 82, -32, 33, 82, -30, 29, 82, -29, 26, + 83, -28, 21, 83, -26, 17, 83, -25, 13, 83, -23, 9, + 83, -22, 5, 84, -20, 1, 84, -18, -3, 84, -17, -7, + 84, -15, -11, 84, -13, -15, 85, -11, -19, 85, -9, -23, + 82, -43, 81, 82, -43, 80, 82, -43, 79, 82, -43, 78, + 82, -42, 76, 82, -42, 75, 82, -42, 73, 82, -41, 70, + 82, -41, 67, 83, -40, 64, 83, -40, 61, 83, -39, 58, + 83, -38, 54, 83, -38, 50, 83, -37, 47, 83, -36, 43, + 83, -35, 39, 83, -34, 35, 83, -32, 31, 84, -31, 27, + 84, -30, 23, 84, -28, 19, 84, -27, 15, 84, -25, 11, + 84, -24, 7, 85, -22, 2, 85, -20, -2, 85, -19, -6, + 85, -17, -9, 86, -15, -14, 86, -13, -18, 86, -11, -21, + 83, -45, 82, 83, -45, 81, 83, -45, 80, 83, -44, 79, + 83, -44, 77, 83, -44, 76, 84, -44, 74, 84, -43, 71, + 84, -43, 68, 84, -42, 65, 84, -42, 62, 84, -41, 59, + 84, -40, 55, 84, -39, 52, 84, -38, 48, 84, -38, 44, + 84, -36, 40, 84, -35, 36, 85, -34, 32, 85, -33, 29, + 85, -32, 24, 85, -30, 20, 85, -29, 16, 85, -27, 12, + 85, -26, 8, 86, -24, 4, 86, -22, 0, 86, -21, -4, + 86, -19, -8, 87, -17, -12, 87, -15, -16, 87, -13, -20, + 85, -46, 82, 85, -46, 82, 85, -46, 81, 85, -46, 80, + 85, -46, 78, 85, -46, 77, 85, -45, 75, 85, -45, 72, + 85, -44, 69, 85, -44, 67, 85, -43, 64, 85, -43, 60, + 85, -42, 57, 85, -41, 53, 85, -40, 50, 85, -39, 46, + 85, -38, 42, 86, -37, 38, 86, -36, 34, 86, -35, 30, + 86, -33, 26, 86, -32, 22, 86, -31, 18, 86, -29, 14, + 87, -28, 10, 87, -26, 6, 87, -24, 2, 87, -23, -2, + 87, -21, -6, 88, -19, -11, 88, -17, -14, 88, -15, -18, + 86, -48, 83, 86, -48, 83, 86, -48, 82, 86, -48, 81, + 86, -48, 79, 86, -47, 78, 86, -47, 76, 86, -47, 73, + 86, -46, 71, 86, -46, 68, 86, -45, 65, 86, -44, 62, + 86, -44, 58, 86, -43, 54, 86, -42, 51, 86, -41, 47, + 87, -40, 43, 87, -39, 39, 87, -38, 35, 87, -37, 32, + 87, -35, 27, 87, -34, 23, 87, -33, 19, 88, -31, 15, + 88, -30, 12, 88, -28, 7, 88, -26, 3, 88, -24, -1, + 89, -23, -5, 89, -21, -9, 89, -19, -13, 89, -17, -17, + 87, -50, 84, 87, -50, 84, 87, -50, 83, 87, -49, 82, + 87, -49, 80, 87, -49, 79, 87, -49, 77, 87, -48, 74, + 87, -48, 72, 87, -47, 69, 87, -47, 66, 87, -46, 63, + 87, -45, 59, 87, -45, 56, 87, -44, 52, 88, -43, 49, + 88, -42, 44, 88, -41, 41, 88, -40, 37, 88, -38, 33, + 88, -37, 29, 88, -36, 25, 88, -34, 21, 89, -33, 17, + 89, -31, 13, 89, -30, 9, 89, -28, 5, 89, -26, 1, + 90, -25, -3, 90, -23, -7, 90, -21, -11, 90, -19, -15, + 88, -51, 85, 88, -51, 84, 88, -51, 84, 88, -51, 83, + 88, -51, 81, 88, -51, 80, 88, -50, 78, 88, -50, 75, + 88, -49, 73, 88, -49, 70, 88, -48, 67, 88, -48, 64, + 88, -47, 61, 89, -46, 57, 89, -45, 54, 89, -45, 50, + 89, -44, 46, 89, -43, 42, 89, -41, 38, 89, -40, 35, + 89, -39, 30, 89, -38, 26, 90, -36, 22, 90, -35, 19, + 90, -33, 15, 90, -32, 10, 90, -30, 6, 90, -28, 3, + 91, -27, -1, 91, -25, -6, 91, -23, -10, 91, -21, -13, + 89, -53, 86, 89, -53, 85, 89, -53, 85, 89, -53, 84, + 89, -52, 82, 89, -52, 81, 89, -52, 79, 89, -52, 76, + 89, -51, 74, 89, -51, 71, 89, -50, 68, 90, -50, 65, + 90, -49, 62, 90, -48, 58, 90, -47, 55, 90, -46, 51, + 90, -45, 47, 90, -44, 44, 90, -43, 40, 90, -42, 36, + 90, -41, 32, 91, -39, 28, 91, -38, 24, 91, -37, 20, + 91, -35, 16, 91, -33, 12, 91, -32, 8, 92, -30, 4, + 92, -29, 0, 92, -27, -4, 92, -25, -8, 92, -23, -12, + 90, -55, 87, 90, -55, 86, 90, -54, 86, 90, -54, 85, + 90, -54, 83, 90, -54, 82, 90, -54, 80, 90, -53, 77, + 90, -53, 75, 91, -52, 72, 91, -52, 70, 91, -51, 67, + 91, -50, 63, 91, -50, 60, 91, -49, 56, 91, -48, 53, + 91, -47, 49, 91, -46, 45, 91, -45, 41, 91, -44, 38, + 92, -42, 33, 92, -41, 29, 92, -40, 26, 92, -38, 22, + 92, -37, 18, 92, -35, 13, 93, -34, 10, 93, -32, 6, + 93, -30, 2, 93, -28, -2, 93, -27, -6, 94, -25, -10, + 91, -56, 88, 91, -56, 87, 91, -56, 86, 92, -56, 85, + 92, -56, 84, 92, -55, 83, 92, -55, 81, 92, -55, 79, + 92, -54, 76, 92, -54, 74, 92, -53, 71, 92, -53, 68, + 92, -52, 64, 92, -51, 61, 92, -51, 58, 92, -50, 54, + 92, -49, 50, 92, -48, 46, 92, -47, 43, 93, -46, 39, + 93, -44, 35, 93, -43, 31, 93, -42, 27, 93, -40, 23, + 93, -39, 19, 93, -37, 15, 94, -36, 11, 94, -34, 7, + 94, -32, 3, 94, -30, -1, 94, -29, -5, 95, -27, -9, + 36, 60, 50, 36, 60, 47, 36, 60, 44, 36, 60, 39, + 36, 60, 35, 36, 61, 30, 36, 61, 25, 36, 61, 19, + 37, 62, 14, 37, 62, 10, 37, 63, 5, 37, 64, 0, + 37, 64, -5, 38, 65, -10, 38, 66, -15, 38, 67, -19, + 39, 68, -24, 39, 69, -28, 40, 70, -33, 40, 71, -37, + 41, 72, -41, 41, 73, -45, 42, 75, -49, 42, 76, -53, + 43, 77, -57, 43, 79, -61, 44, 80, -65, 44, 81, -69, + 45, 83, -72, 46, 84, -76, 46, 86, -79, 47, 87, -83, + 36, 59, 50, 36, 59, 47, 36, 60, 44, 36, 60, 39, + 36, 60, 35, 36, 60, 30, 36, 61, 25, 37, 61, 20, + 37, 61, 15, 37, 62, 10, 37, 63, 5, 37, 63, 0, + 38, 64, -5, 38, 65, -10, 38, 66, -14, 39, 66, -19, + 39, 67, -24, 39, 68, -28, 40, 70, -32, 40, 71, -37, + 41, 72, -41, 41, 73, -45, 42, 74, -49, 42, 76, -53, + 43, 77, -57, 43, 78, -61, 44, 80, -65, 45, 81, -68, + 45, 82, -72, 46, 84, -76, 47, 86, -79, 47, 87, -83, + 36, 59, 50, 36, 59, 47, 36, 59, 44, 36, 59, 39, + 36, 60, 35, 36, 60, 30, 37, 60, 25, 37, 61, 20, + 37, 61, 15, 37, 61, 10, 37, 62, 5, 37, 63, 0, + 38, 64, -5, 38, 64, -10, 38, 65, -14, 39, 66, -19, + 39, 67, -24, 39, 68, -28, 40, 69, -32, 40, 70, -36, + 41, 71, -41, 41, 73, -45, 42, 74, -49, 42, 75, -53, + 43, 76, -57, 44, 78, -61, 44, 79, -65, 45, 81, -68, + 45, 82, -72, 46, 84, -76, 47, 85, -79, 47, 87, -83, + 36, 58, 50, 36, 59, 47, 36, 59, 44, 36, 59, 40, + 36, 59, 35, 37, 59, 31, 37, 60, 26, 37, 60, 20, + 37, 61, 15, 37, 61, 10, 37, 62, 5, 38, 62, 1, + 38, 63, -5, 38, 64, -9, 39, 65, -14, 39, 66, -18, + 39, 67, -23, 40, 68, -28, 40, 69, -32, 40, 70, -36, + 41, 71, -41, 41, 72, -45, 42, 73, -49, 42, 75, -53, + 43, 76, -56, 44, 78, -61, 44, 79, -64, 45, 80, -68, + 45, 82, -72, 46, 83, -75, 47, 85, -79, 47, 86, -82, + 36, 58, 50, 37, 58, 47, 37, 58, 44, 37, 58, 40, + 37, 58, 35, 37, 59, 31, 37, 59, 26, 37, 59, 20, + 37, 60, 15, 37, 60, 11, 38, 61, 6, 38, 62, 1, + 38, 62, -4, 38, 63, -9, 39, 64, -14, 39, 65, -18, + 39, 66, -23, 40, 67, -27, 40, 68, -32, 41, 69, -36, + 41, 71, -41, 42, 72, -45, 42, 73, -48, 43, 74, -52, + 43, 76, -56, 44, 77, -60, 44, 78, -64, 45, 80, -68, + 46, 81, -71, 46, 83, -75, 47, 84, -79, 48, 86, -82, + 37, 57, 51, 37, 57, 47, 37, 57, 44, 37, 58, 40, + 37, 58, 36, 37, 58, 31, 37, 58, 26, 37, 59, 21, + 37, 59, 16, 38, 60, 11, 38, 60, 6, 38, 61, 1, + 38, 62, -4, 39, 63, -9, 39, 63, -13, 39, 64, -18, + 40, 65, -23, 40, 66, -27, 40, 67, -31, 41, 69, -36, + 41, 70, -40, 42, 71, -44, 42, 72, -48, 43, 74, -52, + 43, 75, -56, 44, 77, -60, 45, 78, -64, 45, 79, -67, + 46, 81, -71, 46, 82, -75, 47, 84, -78, 48, 85, -82, + 37, 56, 51, 37, 56, 48, 37, 57, 45, 37, 57, 40, + 37, 57, 36, 37, 57, 31, 37, 58, 27, 38, 58, 21, + 38, 58, 16, 38, 59, 11, 38, 60, 6, 38, 60, 2, + 39, 61, -4, 39, 62, -8, 39, 63, -13, 40, 64, -17, + 40, 65, -22, 40, 66, -27, 41, 67, -31, 41, 68, -35, + 42, 69, -40, 42, 70, -44, 43, 72, -48, 43, 73, -52, + 44, 74, -56, 44, 76, -60, 45, 77, -63, 45, 79, -67, + 46, 80, -71, 47, 82, -75, 47, 83, -78, 48, 85, -81, + 37, 55, 51, 37, 55, 48, 37, 56, 45, 37, 56, 41, + 38, 56, 36, 38, 56, 32, 38, 57, 27, 38, 57, 21, + 38, 58, 17, 38, 58, 12, 38, 59, 7, 39, 59, 2, + 39, 60, -3, 39, 61, -8, 40, 62, -13, 40, 63, -17, + 40, 64, -22, 41, 65, -26, 41, 66, -31, 41, 67, -35, + 42, 68, -39, 42, 70, -43, 43, 71, -47, 43, 72, -51, + 44, 74, -55, 44, 75, -59, 45, 77, -63, 46, 78, -67, + 46, 79, -70, 47, 81, -74, 48, 83, -78, 48, 84, -81, + 38, 54, 51, 38, 54, 48, 38, 55, 45, 38, 55, 41, + 38, 55, 37, 38, 55, 32, 38, 56, 27, 38, 56, 22, + 38, 57, 17, 39, 57, 12, 39, 58, 7, 39, 58, 3, + 39, 59, -3, 40, 60, -7, 40, 61, -12, 40, 62, -16, + 41, 63, -21, 41, 64, -26, 41, 65, -30, 42, 66, -34, + 42, 67, -39, 43, 69, -43, 43, 70, -47, 44, 71, -51, + 44, 73, -55, 45, 74, -59, 45, 76, -63, 46, 77, -66, + 46, 79, -70, 47, 80, -74, 48, 82, -77, 48, 83, -81, + 38, 53, 51, 38, 53, 48, 38, 53, 45, 38, 54, 41, + 38, 54, 37, 38, 54, 33, 38, 54, 28, 39, 55, 22, + 39, 55, 18, 39, 56, 13, 39, 57, 8, 39, 57, 3, + 40, 58, -2, 40, 59, -7, 40, 60, -11, 41, 61, -16, + 41, 62, -21, 41, 63, -25, 42, 64, -30, 42, 65, -34, + 43, 66, -38, 43, 68, -42, 43, 69, -46, 44, 70, -50, + 44, 72, -54, 45, 73, -58, 46, 75, -62, 46, 76, -66, + 47, 78, -69, 47, 79, -73, 48, 81, -77, 49, 83, -80, + 39, 52, 51, 39, 52, 49, 39, 52, 46, 39, 52, 42, + 39, 53, 38, 39, 53, 33, 39, 53, 28, 39, 54, 23, + 39, 54, 18, 39, 55, 13, 40, 55, 8, 40, 56, 4, + 40, 57, -2, 40, 58, -6, 41, 59, -11, 41, 60, -15, + 41, 61, -20, 42, 62, -25, 42, 63, -29, 42, 64, -33, + 43, 65, -38, 43, 67, -42, 44, 68, -46, 44, 69, -50, + 45, 71, -54, 45, 72, -58, 46, 74, -62, 47, 75, -65, + 47, 77, -69, 48, 78, -73, 48, 80, -76, 49, 82, -80, + 39, 50, 52, 39, 50, 49, 39, 50, 46, 39, 51, 42, + 39, 51, 38, 39, 51, 34, 40, 51, 29, 40, 52, 24, + 40, 52, 19, 40, 53, 14, 40, 54, 9, 40, 54, 5, + 41, 55, -1, 41, 56, -5, 41, 57, -10, 42, 58, -14, + 42, 59, -19, 42, 60, -24, 43, 61, -28, 43, 62, -32, + 43, 64, -37, 44, 65, -41, 44, 66, -45, 45, 68, -49, + 45, 69, -53, 46, 71, -57, 46, 72, -61, 47, 74, -64, + 48, 75, -68, 48, 77, -72, 49, 79, -76, 49, 80, -79, + 40, 49, 52, 40, 49, 49, 40, 49, 47, 40, 49, 43, + 40, 49, 39, 40, 50, 34, 40, 50, 30, 40, 50, 24, + 40, 51, 20, 41, 52, 15, 41, 52, 10, 41, 53, 5, + 41, 54, 0, 41, 55, -5, 42, 56, -9, 42, 56, -14, + 42, 58, -19, 43, 59, -23, 43, 60, -27, 43, 61, -31, + 44, 63, -36, 44, 64, -40, 45, 65, -44, 45, 67, -48, + 46, 68, -52, 46, 70, -56, 47, 71, -60, 47, 73, -64, + 48, 74, -67, 49, 76, -71, 49, 78, -75, 50, 79, -78, + 40, 47, 52, 40, 47, 50, 40, 47, 47, 40, 48, 43, + 40, 48, 39, 41, 48, 35, 41, 48, 30, 41, 49, 25, + 41, 49, 20, 41, 50, 16, 41, 51, 11, 41, 51, 6, + 42, 52, 1, 42, 53, -4, 42, 54, -8, 43, 55, -13, + 43, 56, -18, 43, 57, -22, 44, 58, -26, 44, 60, -31, + 44, 61, -35, 45, 62, -39, 45, 64, -43, 46, 65, -47, + 46, 67, -51, 47, 68, -56, 47, 70, -59, 48, 71, -63, + 48, 73, -67, 49, 75, -71, 50, 76, -74, 50, 78, -78, + 41, 45, 52, 41, 46, 50, 41, 46, 47, 41, 46, 44, + 41, 46, 40, 41, 46, 36, 41, 47, 31, 41, 47, 26, + 42, 48, 21, 42, 48, 16, 42, 49, 12, 42, 50, 7, + 42, 51, 2, 43, 51, -3, 43, 52, -7, 43, 53, -12, + 43, 55, -17, 44, 56, -21, 44, 57, -26, 45, 58, -30, + 45, 60, -35, 45, 61, -39, 46, 62, -43, 46, 64, -47, + 47, 65, -50, 47, 67, -55, 48, 68, -59, 48, 70, -62, + 49, 72, -66, 50, 73, -70, 50, 75, -73, 51, 77, -77, + 42, 44, 53, 42, 44, 50, 42, 44, 48, 42, 44, 44, + 42, 44, 40, 42, 45, 36, 42, 45, 32, 42, 46, 27, + 42, 46, 22, 42, 47, 17, 43, 47, 13, 43, 48, 8, + 43, 49, 3, 43, 50, -2, 43, 51, -7, 44, 52, -11, + 44, 53, -16, 44, 54, -20, 45, 55, -25, 45, 57, -29, + 46, 58, -34, 46, 59, -38, 46, 61, -42, 47, 62, -46, + 47, 64, -50, 48, 65, -54, 48, 67, -58, 49, 69, -61, + 49, 70, -65, 50, 72, -69, 51, 74, -73, 51, 75, -76, + 42, 42, 53, 42, 42, 51, 42, 42, 48, 42, 42, 45, + 42, 43, 41, 42, 43, 37, 43, 43, 33, 43, 44, 27, + 43, 44, 23, 43, 45, 18, 43, 46, 13, 43, 46, 9, + 44, 47, 4, 44, 48, -1, 44, 49, -6, 44, 50, -10, + 45, 51, -15, 45, 52, -19, 45, 54, -24, 46, 55, -28, + 46, 56, -33, 47, 58, -37, 47, 59, -41, 47, 61, -45, + 48, 62, -49, 48, 64, -53, 49, 65, -57, 49, 67, -61, + 50, 69, -64, 51, 70, -68, 51, 72, -72, 52, 74, -75, + 43, 40, 54, 43, 40, 51, 43, 40, 49, 43, 40, 45, + 43, 41, 42, 43, 41, 38, 43, 41, 33, 43, 42, 28, + 44, 42, 24, 44, 43, 19, 44, 44, 14, 44, 44, 10, + 44, 45, 5, 45, 46, 0, 45, 47, -5, 45, 48, -9, + 45, 49, -14, 46, 51, -18, 46, 52, -23, 46, 53, -27, + 47, 55, -32, 47, 56, -36, 48, 57, -40, 48, 59, -44, + 48, 60, -48, 49, 62, -52, 49, 64, -56, 50, 65, -60, + 51, 67, -63, 51, 69, -67, 52, 71, -71, 52, 72, -74, + 44, 38, 54, 44, 38, 52, 44, 38, 49, 44, 38, 46, + 44, 39, 43, 44, 39, 39, 44, 39, 34, 44, 40, 29, + 44, 40, 25, 44, 41, 20, 45, 42, 15, 45, 42, 11, + 45, 43, 6, 45, 44, 1, 45, 45, -4, 46, 46, -8, + 46, 48, -13, 46, 49, -17, 47, 50, -22, 47, 51, -26, + 47, 53, -31, 48, 54, -35, 48, 56, -39, 49, 57, -43, + 49, 59, -47, 50, 60, -51, 50, 62, -55, 51, 64, -59, + 51, 65, -62, 52, 67, -66, 52, 69, -70, 53, 71, -74, + 45, 36, 54, 45, 36, 52, 45, 36, 50, 45, 36, 47, + 45, 37, 43, 45, 37, 39, 45, 37, 35, 45, 38, 30, + 45, 38, 26, 45, 39, 21, 45, 40, 16, 46, 40, 12, + 46, 41, 7, 46, 42, 2, 46, 43, -2, 46, 44, -7, + 47, 46, -12, 47, 47, -16, 47, 48, -21, 48, 49, -25, + 48, 51, -30, 49, 52, -34, 49, 54, -38, 49, 55, -42, + 50, 57, -46, 50, 59, -50, 51, 60, -54, 51, 62, -58, + 52, 64, -61, 52, 66, -65, 53, 67, -69, 53, 69, -73, + 45, 34, 55, 45, 34, 53, 45, 34, 51, 45, 34, 47, + 45, 35, 44, 46, 35, 40, 46, 35, 36, 46, 36, 31, + 46, 36, 27, 46, 37, 22, 46, 38, 17, 46, 38, 13, + 47, 39, 8, 47, 40, 3, 47, 41, -1, 47, 42, -6, + 48, 44, -11, 48, 45, -15, 48, 46, -19, 48, 47, -24, + 49, 49, -28, 49, 50, -33, 50, 52, -37, 50, 53, -41, + 50, 55, -45, 51, 57, -49, 51, 58, -53, 52, 60, -57, + 52, 62, -60, 53, 64, -64, 53, 66, -68, 54, 67, -72, + 46, 32, 55, 46, 32, 53, 46, 32, 51, 46, 32, 48, + 46, 33, 45, 46, 33, 41, 46, 33, 37, 47, 34, 32, + 47, 34, 28, 47, 35, 23, 47, 36, 19, 47, 36, 14, + 47, 37, 9, 48, 38, 4, 48, 39, 0, 48, 40, -5, + 48, 42, -10, 49, 43, -14, 49, 44, -18, 49, 45, -23, + 50, 47, -27, 50, 48, -31, 50, 50, -35, 51, 51, -39, + 51, 53, -43, 52, 55, -48, 52, 57, -52, 53, 58, -55, + 53, 60, -59, 54, 62, -63, 54, 64, -67, 55, 65, -71, + 47, 30, 56, 47, 30, 54, 47, 30, 52, 47, 30, 49, + 47, 30, 46, 47, 31, 42, 47, 31, 38, 47, 32, 33, + 48, 32, 29, 48, 33, 24, 48, 34, 20, 48, 34, 15, + 48, 35, 10, 48, 36, 6, 49, 37, 1, 49, 38, -3, + 49, 40, -8, 49, 41, -13, 50, 42, -17, 50, 43, -21, + 50, 45, -26, 51, 46, -30, 51, 48, -34, 51, 49, -38, + 52, 51, -42, 52, 53, -47, 53, 55, -51, 53, 56, -54, + 54, 58, -58, 54, 60, -62, 55, 62, -66, 55, 64, -69, + 48, 28, 56, 48, 28, 55, 48, 28, 53, 48, 28, 50, + 48, 28, 46, 48, 29, 43, 48, 29, 39, 48, 30, 34, + 48, 30, 30, 49, 31, 25, 49, 31, 21, 49, 32, 16, + 49, 33, 11, 49, 34, 7, 49, 35, 2, 50, 36, -2, + 50, 37, -7, 50, 39, -12, 51, 40, -16, 51, 41, -20, + 51, 43, -25, 52, 44, -29, 52, 46, -33, 52, 47, -37, + 53, 49, -41, 53, 51, -45, 54, 53, -49, 54, 54, -53, + 54, 56, -57, 55, 58, -61, 56, 60, -65, 56, 62, -68, + 49, 25, 57, 49, 25, 55, 49, 26, 53, 49, 26, 50, + 49, 26, 47, 49, 26, 44, 49, 27, 40, 49, 27, 35, + 49, 28, 31, 49, 28, 26, 50, 29, 22, 50, 30, 18, + 50, 31, 12, 50, 32, 8, 50, 33, 3, 51, 34, -1, + 51, 35, -6, 51, 36, -10, 51, 38, -15, 52, 39, -19, + 52, 41, -24, 52, 42, -28, 53, 44, -32, 53, 45, -36, + 53, 47, -40, 54, 49, -44, 54, 51, -48, 55, 52, -52, + 55, 54, -56, 56, 56, -60, 56, 58, -64, 57, 60, -67, + 50, 23, 58, 50, 23, 56, 50, 23, 54, 50, 24, 51, + 50, 24, 48, 50, 24, 45, 50, 25, 41, 50, 25, 36, + 50, 26, 32, 50, 26, 28, 50, 27, 23, 51, 28, 19, + 51, 29, 14, 51, 30, 9, 51, 31, 5, 51, 32, 0, + 52, 33, -5, 52, 34, -9, 52, 36, -13, 52, 37, -18, + 53, 39, -22, 53, 40, -26, 54, 42, -31, 54, 43, -35, + 54, 45, -39, 55, 47, -43, 55, 48, -47, 56, 50, -51, + 56, 52, -55, 57, 54, -59, 57, 56, -62, 57, 58, -66, + 51, 21, 58, 51, 21, 57, 51, 21, 55, 51, 21, 52, + 51, 22, 49, 51, 22, 46, 51, 22, 42, 51, 23, 37, + 51, 23, 33, 51, 24, 29, 51, 25, 24, 52, 26, 20, + 52, 27, 15, 52, 27, 11, 52, 28, 6, 52, 30, 2, + 53, 31, -3, 53, 32, -8, 53, 33, -12, 53, 35, -16, + 54, 36, -21, 54, 38, -25, 54, 39, -29, 55, 41, -33, + 55, 43, -37, 56, 45, -42, 56, 46, -46, 56, 48, -49, + 57, 50, -53, 57, 52, -57, 58, 54, -61, 58, 56, -65, + 52, 19, 59, 52, 19, 57, 52, 19, 55, 52, 19, 53, + 52, 19, 50, 52, 20, 47, 52, 20, 43, 52, 21, 38, + 52, 21, 34, 52, 22, 30, 52, 23, 26, 52, 23, 21, + 53, 24, 16, 53, 25, 12, 53, 26, 7, 53, 27, 3, + 53, 29, -2, 54, 30, -6, 54, 31, -11, 54, 33, -15, + 55, 34, -20, 55, 36, -24, 55, 37, -28, 56, 39, -32, + 56, 41, -36, 56, 42, -40, 57, 44, -44, 57, 46, -48, + 58, 48, -52, 58, 50, -56, 59, 52, -60, 59, 54, -64, + 53, 16, 59, 53, 17, 58, 53, 17, 56, 53, 17, 54, + 53, 17, 51, 53, 17, 48, 53, 18, 44, 53, 18, 40, + 53, 19, 35, 53, 20, 31, 53, 20, 27, 53, 21, 23, + 54, 22, 18, 54, 23, 13, 54, 24, 9, 54, 25, 4, + 54, 26, -1, 55, 28, -5, 55, 29, -9, 55, 30, -13, + 55, 32, -18, 56, 34, -22, 56, 35, -26, 56, 37, -31, + 57, 38, -35, 57, 40, -39, 58, 42, -43, 58, 44, -47, + 58, 46, -51, 59, 48, -55, 59, 50, -59, 60, 52, -62, + 54, 14, 60, 54, 14, 59, 54, 14, 57, 54, 15, 54, + 54, 15, 52, 54, 15, 49, 54, 16, 45, 54, 16, 41, + 54, 17, 37, 54, 17, 32, 54, 18, 28, 54, 19, 24, + 55, 20, 19, 55, 21, 14, 55, 22, 10, 55, 23, 6, + 55, 24, 1, 56, 25, -4, 56, 27, -8, 56, 28, -12, + 56, 30, -17, 57, 31, -21, 57, 33, -25, 57, 34, -29, + 58, 36, -33, 58, 38, -38, 58, 40, -42, 59, 42, -45, + 59, 44, -49, 60, 46, -54, 60, 48, -57, 61, 49, -61, + 55, 12, 61, 55, 12, 59, 55, 12, 58, 55, 12, 55, + 55, 13, 53, 55, 13, 50, 55, 13, 46, 55, 14, 42, + 55, 14, 38, 55, 15, 34, 55, 16, 29, 55, 17, 25, + 56, 18, 20, 56, 18, 16, 56, 20, 11, 56, 21, 7, + 56, 22, 2, 57, 23, -2, 57, 25, -6, 57, 26, -11, + 57, 28, -15, 58, 29, -20, 58, 31, -24, 58, 32, -28, + 59, 34, -32, 59, 36, -36, 59, 38, -40, 60, 39, -44, + 60, 41, -48, 61, 43, -52, 61, 45, -56, 61, 47, -60, + 56, 10, 61, 56, 10, 60, 56, 10, 59, 56, 10, 56, + 56, 10, 54, 56, 11, 50, 56, 11, 47, 56, 12, 43, + 56, 12, 39, 56, 13, 35, 56, 14, 31, 56, 14, 26, + 57, 15, 22, 57, 16, 17, 57, 17, 13, 57, 18, 8, + 57, 20, 4, 58, 21, -1, 58, 22, -5, 58, 24, -9, + 58, 25, -14, 59, 27, -18, 59, 28, -22, 59, 30, -26, + 60, 32, -30, 60, 34, -35, 60, 35, -39, 61, 37, -43, + 61, 39, -47, 61, 41, -51, 62, 43, -55, 62, 45, -58, + 57, 7, 62, 57, 7, 61, 57, 7, 60, 57, 7, 57, + 57, 8, 55, 57, 8, 52, 57, 8, 48, 57, 9, 44, + 57, 9, 40, 57, 10, 36, 58, 11, 32, 58, 12, 28, + 58, 12, 23, 58, 13, 19, 58, 14, 15, 58, 16, 10, + 59, 17, 5, 59, 18, 1, 59, 19, -3, 59, 21, -7, + 59, 22, -12, 60, 24, -16, 60, 26, -20, 60, 27, -25, + 61, 29, -29, 61, 31, -33, 61, 33, -37, 62, 34, -41, + 62, 36, -45, 63, 38, -49, 63, 40, -53, 63, 42, -57, + 58, 5, 63, 58, 5, 62, 58, 5, 60, 58, 5, 58, + 58, 5, 56, 58, 6, 53, 58, 6, 49, 58, 7, 45, + 58, 7, 42, 58, 8, 38, 59, 8, 34, 59, 9, 29, + 59, 10, 25, 59, 11, 20, 59, 12, 16, 59, 13, 12, + 60, 15, 7, 60, 16, 3, 60, 17, -2, 60, 19, -6, + 60, 20, -11, 61, 22, -15, 61, 23, -19, 61, 25, -23, + 62, 27, -27, 62, 29, -32, 62, 30, -36, 63, 32, -39, + 63, 34, -43, 63, 36, -48, 64, 38, -51, 64, 40, -55, + 59, 3, 64, 59, 3, 63, 59, 3, 61, 59, 3, 59, + 59, 3, 57, 59, 4, 54, 59, 4, 51, 59, 4, 47, + 59, 5, 43, 59, 6, 39, 60, 6, 35, 60, 7, 31, + 60, 8, 26, 60, 9, 22, 60, 10, 17, 60, 11, 13, + 61, 12, 8, 61, 14, 4, 61, 15, 0, 61, 16, -4, + 61, 18, -9, 62, 19, -13, 62, 21, -17, 62, 23, -22, + 63, 24, -26, 63, 26, -30, 63, 28, -34, 64, 30, -38, + 64, 32, -42, 64, 34, -46, 65, 36, -50, 65, 38, -54, + 60, 0, 65, 60, 0, 63, 60, 1, 62, 60, 1, 60, + 60, 1, 58, 60, 1, 55, 60, 2, 52, 60, 2, 48, + 60, 3, 44, 61, 3, 40, 61, 4, 36, 61, 5, 32, + 61, 6, 27, 61, 7, 23, 61, 8, 19, 61, 9, 15, + 62, 10, 10, 62, 11, 5, 62, 13, 1, 62, 14, -3, + 62, 16, -8, 63, 17, -12, 63, 19, -16, 63, 20, -20, + 64, 22, -24, 64, 24, -29, 64, 26, -33, 65, 28, -37, + 65, 30, -40, 65, 32, -45, 66, 34, -49, 66, 36, -52, + 61, -2, 65, 61, -2, 64, 61, -2, 63, 61, -1, 61, + 61, -1, 58, 61, -1, 56, 61, -1, 53, 61, 0, 49, + 62, 1, 45, 62, 1, 41, 62, 2, 37, 62, 3, 33, + 62, 4, 29, 62, 4, 24, 62, 5, 20, 62, 7, 16, + 63, 8, 11, 63, 9, 7, 63, 10, 3, 63, 12, -2, + 63, 13, -6, 64, 15, -10, 64, 17, -14, 64, 18, -19, + 65, 20, -23, 65, 22, -27, 65, 24, -31, 66, 25, -35, + 66, 27, -39, 66, 29, -43, 67, 31, -47, 67, 33, -51, + 62, -4, 66, 62, -4, 65, 62, -4, 64, 62, -4, 62, + 62, -3, 59, 62, -3, 57, 62, -3, 54, 63, -2, 50, + 63, -2, 46, 63, -1, 43, 63, 0, 39, 63, 0, 35, + 63, 1, 30, 63, 2, 26, 63, 3, 22, 63, 4, 17, + 64, 6, 13, 64, 7, 8, 64, 8, 4, 64, 10, 0, + 64, 11, -5, 65, 13, -9, 65, 14, -13, 65, 16, -17, + 66, 18, -21, 66, 20, -26, 66, 21, -30, 66, 23, -34, + 67, 25, -37, 67, 27, -42, 68, 29, -46, 68, 31, -49, + 63, -6, 67, 63, -6, 66, 63, -6, 65, 63, -6, 63, + 63, -6, 60, 63, -5, 58, 64, -5, 55, 64, -4, 51, + 64, -4, 48, 64, -3, 44, 64, -3, 40, 64, -2, 36, + 64, -1, 31, 64, 0, 27, 64, 1, 23, 65, 2, 19, + 65, 3, 14, 65, 5, 10, 65, 6, 6, 65, 7, 2, + 66, 9, -3, 66, 10, -7, 66, 12, -11, 66, 14, -16, + 67, 15, -20, 67, 17, -24, 67, 19, -28, 67, 21, -32, + 68, 23, -36, 68, 25, -40, 69, 27, -44, 69, 29, -48, + 64, -8, 68, 64, -8, 67, 64, -8, 65, 64, -8, 63, + 65, -8, 61, 65, -7, 59, 65, -7, 56, 65, -6, 52, + 65, -6, 49, 65, -5, 45, 65, -5, 41, 65, -4, 37, + 65, -3, 33, 65, -2, 29, 65, -1, 24, 66, 0, 20, + 66, 1, 16, 66, 2, 11, 66, 4, 7, 66, 5, 3, + 67, 7, -2, 67, 8, -6, 67, 10, -10, 67, 11, -14, + 68, 13, -18, 68, 15, -23, 68, 17, -27, 68, 19, -30, + 69, 21, -34, 69, 23, -39, 70, 25, -43, 70, 27, -46, + 66, -10, 68, 66, -10, 67, 66, -10, 66, 66, -10, 64, + 66, -10, 62, 66, -9, 60, 66, -9, 57, 66, -9, 53, + 66, -8, 50, 66, -7, 46, 66, -7, 43, 66, -6, 39, + 66, -5, 34, 66, -4, 30, 66, -3, 26, 67, -2, 22, + 67, -1, 17, 67, 0, 13, 67, 2, 9, 67, 3, 5, + 68, 5, 0, 68, 6, -4, 68, 8, -8, 68, 9, -12, + 69, 11, -16, 69, 13, -21, 69, 15, -25, 69, 16, -29, + 70, 18, -33, 70, 20, -37, 70, 22, -41, 71, 24, -45, + 67, -13, 69, 67, -12, 68, 67, -12, 67, 67, -12, 65, + 67, -12, 63, 67, -12, 61, 67, -11, 58, 67, -11, 55, + 67, -10, 51, 67, -10, 48, 67, -9, 44, 67, -8, 40, + 67, -7, 36, 67, -6, 31, 68, -5, 27, 68, -4, 23, + 68, -3, 19, 68, -2, 14, 68, -1, 10, 68, 1, 6, + 69, 2, 1, 69, 4, -3, 69, 5, -7, 69, 7, -11, + 70, 9, -15, 70, 11, -19, 70, 12, -23, 70, 14, -27, + 71, 16, -31, 71, 18, -36, 71, 20, -39, 72, 22, -43, + 68, -15, 70, 68, -14, 69, 68, -14, 68, 68, -14, 66, + 68, -14, 64, 68, -14, 62, 68, -13, 59, 68, -13, 56, + 68, -12, 52, 68, -12, 49, 68, -11, 45, 68, -10, 41, + 68, -9, 37, 69, -9, 33, 69, -8, 29, 69, -6, 25, + 69, -5, 20, 69, -4, 16, 69, -3, 12, 69, -1, 8, + 70, 0, 3, 70, 2, -1, 70, 3, -5, 70, 5, -9, + 71, 6, -13, 71, 8, -18, 71, 10, -22, 71, 12, -26, + 72, 14, -30, 72, 16, -34, 72, 18, -38, 73, 20, -42, + 69, -17, 71, 69, -17, 70, 69, -16, 69, 69, -16, 67, + 69, -16, 65, 69, -16, 63, 69, -15, 60, 69, -15, 57, + 69, -14, 54, 69, -14, 50, 69, -13, 46, 69, -12, 43, + 70, -11, 38, 70, -11, 34, 70, -10, 30, 70, -9, 26, + 70, -7, 22, 70, -6, 17, 70, -5, 13, 71, -4, 9, + 71, -2, 4, 71, -1, 0, 71, 1, -4, 71, 3, -8, + 72, 4, -12, 72, 6, -16, 72, 8, -20, 73, 10, -24, + 73, 12, -28, 73, 14, -33, 73, 16, -36, 74, 18, -40, + 70, -19, 72, 70, -19, 71, 70, -18, 70, 70, -18, 68, + 70, -18, 66, 70, -18, 64, 70, -17, 61, 70, -17, 58, + 70, -16, 55, 70, -16, 51, 70, -15, 48, 70, -14, 44, + 71, -14, 40, 71, -13, 36, 71, -12, 32, 71, -11, 28, + 71, -9, 23, 71, -8, 19, 71, -7, 15, 72, -6, 11, + 72, -4, 6, 72, -3, 2, 72, -1, -2, 72, 0, -6, + 73, 2, -10, 73, 4, -15, 73, 6, -19, 74, 8, -23, + 74, 9, -27, 74, 12, -31, 74, 14, -35, 75, 16, -39, + 71, -21, 72, 71, -21, 72, 71, -20, 71, 71, -20, 69, + 71, -20, 67, 71, -20, 65, 71, -19, 62, 71, -19, 59, + 71, -18, 56, 71, -18, 53, 72, -17, 49, 72, -17, 45, + 72, -16, 41, 72, -15, 37, 72, -14, 33, 72, -13, 29, + 72, -12, 24, 72, -10, 20, 73, -9, 16, 73, -8, 12, + 73, -6, 8, 73, -5, 3, 73, -3, -1, 74, -2, -5, + 74, 0, -9, 74, 2, -13, 74, 4, -17, 75, 5, -21, + 75, 7, -25, 75, 9, -29, 75, 11, -33, 76, 13, -37, + 72, -23, 73, 72, -23, 72, 72, -22, 71, 72, -22, 70, + 72, -22, 68, 72, -22, 66, 72, -21, 63, 72, -21, 60, + 72, -20, 57, 73, -20, 54, 73, -19, 50, 73, -19, 47, + 73, -18, 42, 73, -17, 38, 73, -16, 35, 73, -15, 30, + 73, -14, 26, 73, -12, 22, 74, -11, 18, 74, -10, 14, + 74, -8, 9, 74, -7, 5, 74, -5, 1, 75, -4, -3, + 75, -2, -7, 75, 0, -12, 75, 1, -16, 76, 3, -20, + 76, 5, -23, 76, 7, -28, 77, 9, -32, 77, 11, -35, + 73, -25, 74, 73, -24, 73, 73, -24, 72, 73, -24, 71, + 73, -24, 69, 73, -24, 67, 74, -23, 64, 74, -23, 61, + 74, -22, 58, 74, -22, 55, 74, -21, 52, 74, -21, 48, + 74, -20, 44, 74, -19, 40, 74, -18, 36, 74, -17, 32, + 74, -16, 27, 75, -15, 23, 75, -13, 19, 75, -12, 15, + 75, -10, 11, 75, -9, 7, 75, -8, 3, 76, -6, -1, + 76, -4, -6, 76, -3, -10, 76, -1, -14, 77, 1, -18, + 77, 3, -22, 77, 5, -26, 78, 7, -30, 78, 9, -34, + 74, -27, 75, 74, -26, 74, 75, -26, 73, 75, -26, 72, + 75, -26, 70, 75, -26, 68, 75, -25, 66, 75, -25, 62, + 75, -24, 59, 75, -24, 56, 75, -23, 53, 75, -23, 49, + 75, -22, 45, 75, -21, 41, 75, -20, 37, 75, -19, 33, + 76, -18, 29, 76, -17, 25, 76, -15, 21, 76, -14, 17, + 76, -13, 12, 76, -11, 8, 77, -10, 4, 77, -8, 0, + 77, -7, -4, 77, -5, -8, 77, -3, -12, 78, -1, -16, + 78, 1, -20, 78, 3, -25, 79, 5, -28, 79, 7, -32, + 76, -28, 76, 76, -28, 75, 76, -28, 74, 76, -28, 73, + 76, -28, 71, 76, -28, 69, 76, -27, 67, 76, -27, 64, + 76, -26, 61, 76, -26, 57, 76, -25, 54, 76, -24, 51, + 76, -24, 46, 76, -23, 43, 76, -22, 39, 76, -21, 35, + 77, -20, 30, 77, -19, 26, 77, -17, 22, 77, -16, 18, + 77, -15, 14, 77, -13, 10, 78, -12, 6, 78, -10, 2, + 78, -9, -2, 78, -7, -7, 79, -5, -11, 79, -3, -15, + 79, -1, -19, 79, 1, -23, 80, 3, -27, 80, 5, -31, + 77, -30, 77, 77, -30, 76, 77, -30, 75, 77, -30, 74, + 77, -30, 72, 77, -29, 70, 77, -29, 68, 77, -29, 65, + 77, -28, 62, 77, -28, 59, 77, -27, 55, 77, -26, 52, + 77, -26, 48, 77, -25, 44, 77, -24, 40, 78, -23, 36, + 78, -22, 32, 78, -21, 28, 78, -19, 24, 78, -18, 20, + 78, -17, 15, 79, -15, 11, 79, -14, 7, 79, -12, 3, + 79, -11, -1, 79, -9, -5, 80, -7, -9, 80, -5, -13, + 80, -4, -17, 80, -1, -21, 81, 0, -25, 81, 2, -29, + 78, -32, 78, 78, -32, 77, 78, -32, 76, 78, -32, 75, + 78, -32, 73, 78, -31, 71, 78, -31, 69, 78, -31, 66, + 78, -30, 63, 78, -30, 60, 78, -29, 57, 78, -28, 53, + 78, -27, 49, 79, -27, 45, 79, -26, 42, 79, -25, 38, + 79, -24, 33, 79, -23, 29, 79, -21, 25, 79, -20, 21, + 79, -19, 17, 80, -17, 13, 80, -16, 9, 80, -14, 5, + 80, -13, 1, 80, -11, -4, 81, -9, -8, 81, -7, -12, + 81, -6, -15, 81, -4, -20, 82, -2, -24, 82, 0, -28, + 79, -34, 78, 79, -34, 78, 79, -34, 77, 79, -34, 75, + 79, -33, 74, 79, -33, 72, 79, -33, 70, 79, -32, 67, + 79, -32, 64, 79, -31, 61, 79, -31, 58, 79, -30, 54, + 80, -29, 50, 80, -29, 47, 80, -28, 43, 80, -27, 39, + 80, -26, 35, 80, -25, 31, 80, -23, 27, 80, -22, 23, + 81, -21, 18, 81, -19, 14, 81, -18, 10, 81, -16, 6, + 81, -15, 2, 82, -13, -2, 82, -11, -6, 82, -10, -10, + 82, -8, -14, 82, -6, -18, 83, -4, -22, 83, -2, -26, + 80, -36, 79, 80, -36, 79, 80, -36, 78, 80, -36, 77, + 81, -36, 75, 81, -35, 73, 81, -35, 71, 81, -35, 68, + 81, -34, 66, 81, -34, 63, 81, -33, 59, 81, -33, 56, + 81, -32, 52, 81, -31, 48, 81, -30, 45, 81, -29, 41, + 81, -28, 37, 81, -27, 33, 82, -26, 29, 82, -25, 25, + 82, -23, 20, 82, -22, 16, 82, -20, 12, 82, -19, 8, + 83, -17, 4, 83, -16, 0, 83, -14, -4, 83, -12, -8, + 84, -10, -12, 84, -8, -16, 84, -6, -20, 84, -5, -24, + 82, -38, 80, 82, -38, 80, 82, -38, 79, 82, -38, 78, + 82, -38, 76, 82, -37, 74, 82, -37, 72, 82, -37, 69, + 82, -36, 67, 82, -36, 64, 82, -35, 61, 82, -34, 57, + 82, -34, 53, 82, -33, 50, 82, -32, 46, 82, -31, 42, + 82, -30, 38, 83, -29, 34, 83, -28, 30, 83, -27, 26, + 83, -25, 22, 83, -24, 18, 83, -22, 14, 84, -21, 10, + 84, -19, 6, 84, -18, 1, 84, -16, -2, 84, -14, -6, + 85, -12, -10, 85, -10, -15, 85, -9, -19, 85, -7, -22, + 83, -40, 81, 83, -40, 81, 83, -40, 80, 83, -40, 79, + 83, -39, 77, 83, -39, 75, 83, -39, 73, 83, -38, 70, + 83, -38, 68, 83, -37, 65, 83, -37, 62, 83, -36, 59, + 83, -35, 55, 83, -35, 51, 83, -34, 47, 83, -33, 44, + 84, -32, 39, 84, -31, 36, 84, -30, 32, 84, -28, 28, + 84, -27, 23, 84, -26, 19, 84, -24, 15, 85, -23, 11, + 85, -21, 7, 85, -20, 3, 85, -18, -1, 85, -16, -5, + 86, -14, -9, 86, -12, -13, 86, -11, -17, 86, -9, -21, + 84, -42, 82, 84, -42, 81, 84, -41, 81, 84, -41, 79, + 84, -41, 78, 84, -41, 76, 84, -41, 74, 84, -40, 72, + 84, -40, 69, 84, -39, 66, 84, -39, 63, 84, -38, 60, + 84, -37, 56, 84, -36, 52, 85, -36, 49, 85, -35, 45, + 85, -34, 41, 85, -33, 37, 85, -31, 33, 85, -30, 29, + 85, -29, 25, 85, -28, 21, 86, -26, 17, 86, -25, 13, + 86, -23, 9, 86, -22, 5, 86, -20, 1, 87, -18, -3, + 87, -16, -7, 87, -14, -11, 87, -13, -15, 88, -11, -19, + 85, -43, 83, 85, -43, 82, 85, -43, 82, 85, -43, 80, + 85, -43, 79, 85, -43, 77, 85, -42, 75, 85, -42, 73, + 85, -41, 70, 85, -41, 67, 85, -40, 64, 85, -40, 61, + 85, -39, 57, 86, -38, 54, 86, -37, 50, 86, -37, 46, + 86, -35, 42, 86, -34, 38, 86, -33, 35, 86, -32, 31, + 86, -31, 26, 87, -29, 22, 87, -28, 18, 87, -27, 15, + 87, -25, 11, 87, -23, 6, 87, -22, 2, 88, -20, -2, + 88, -18, -5, 88, -16, -10, 88, -15, -14, 89, -13, -18, + 86, -45, 84, 86, -45, 83, 86, -45, 82, 86, -45, 81, + 86, -45, 80, 86, -44, 78, 86, -44, 76, 86, -44, 74, + 86, -43, 71, 86, -43, 68, 86, -42, 65, 87, -42, 62, + 87, -41, 59, 87, -40, 55, 87, -39, 52, 87, -38, 48, + 87, -37, 44, 87, -36, 40, 87, -35, 36, 87, -34, 32, + 87, -33, 28, 88, -31, 24, 88, -30, 20, 88, -29, 16, + 88, -27, 12, 88, -25, 8, 89, -24, 4, 89, -22, 0, + 89, -20, -4, 89, -18, -8, 89, -17, -12, 90, -15, -16, + 87, -47, 85, 87, -47, 84, 87, -47, 83, 87, -46, 82, + 87, -46, 81, 87, -46, 79, 87, -46, 77, 87, -45, 75, + 87, -45, 72, 88, -44, 70, 88, -44, 67, 88, -43, 64, + 88, -43, 60, 88, -42, 56, 88, -41, 53, 88, -40, 49, + 88, -39, 45, 88, -38, 41, 88, -37, 38, 88, -36, 34, + 89, -35, 29, 89, -33, 25, 89, -32, 22, 89, -30, 18, + 89, -29, 14, 89, -27, 9, 90, -26, 5, 90, -24, 2, + 90, -22, -2, 90, -20, -7, 90, -19, -11, 91, -17, -14, + 88, -48, 86, 88, -48, 85, 88, -48, 84, 88, -48, 83, + 88, -48, 82, 89, -48, 80, 89, -47, 78, 89, -47, 76, + 89, -47, 73, 89, -46, 71, 89, -46, 68, 89, -45, 65, + 89, -44, 61, 89, -44, 58, 89, -43, 54, 89, -42, 51, + 89, -41, 47, 89, -40, 43, 89, -39, 39, 90, -38, 35, + 90, -36, 31, 90, -35, 27, 90, -34, 23, 90, -32, 19, + 90, -31, 15, 91, -29, 11, 91, -28, 7, 91, -26, 3, + 91, -24, -1, 91, -22, -5, 92, -21, -9, 92, -19, -13, + 90, -50, 86, 90, -50, 86, 90, -50, 85, 90, -50, 84, + 90, -50, 83, 90, -49, 81, 90, -49, 79, 90, -49, 77, + 90, -48, 75, 90, -48, 72, 90, -47, 69, 90, -47, 66, + 90, -46, 62, 90, -45, 59, 90, -45, 56, 90, -44, 52, + 90, -43, 48, 90, -42, 44, 91, -41, 40, 91, -39, 37, + 91, -38, 32, 91, -37, 28, 91, -36, 25, 91, -34, 21, + 91, -33, 17, 92, -31, 12, 92, -30, 9, 92, -28, 5, + 92, -26, 1, 92, -24, -3, 93, -23, -7, 93, -21, -11, + 91, -52, 87, 91, -52, 87, 91, -52, 86, 91, -51, 85, + 91, -51, 84, 91, -51, 82, 91, -51, 80, 91, -50, 78, + 91, -50, 76, 91, -50, 73, 91, -49, 70, 91, -48, 67, + 91, -48, 64, 91, -47, 60, 91, -46, 57, 91, -45, 53, + 91, -44, 49, 92, -43, 46, 92, -42, 42, 92, -41, 38, + 92, -40, 34, 92, -39, 30, 92, -37, 26, 92, -36, 22, + 93, -35, 18, 93, -33, 14, 93, -31, 10, 93, -30, 6, + 93, -28, 2, 93, -26, -2, 94, -25, -6, 94, -23, -10, + 92, -53, 88, 92, -53, 88, 92, -53, 87, 92, -53, 86, + 92, -53, 85, 92, -53, 83, 92, -52, 81, 92, -52, 79, + 92, -52, 77, 92, -51, 74, 92, -51, 71, 92, -50, 68, + 92, -49, 65, 92, -49, 62, 92, -48, 58, 93, -47, 55, + 93, -46, 51, 93, -45, 47, 93, -44, 43, 93, -43, 40, + 93, -42, 35, 93, -40, 31, 93, -39, 28, 93, -38, 24, + 94, -36, 20, 94, -35, 16, 94, -33, 12, 94, -32, 8, + 94, -30, 4, 95, -28, 0, 95, -26, -4, 95, -25, -8, + 38, 62, 52, 38, 62, 49, 38, 62, 46, 38, 62, 41, + 38, 62, 37, 38, 63, 32, 38, 63, 28, 38, 63, 22, + 38, 64, 17, 38, 64, 12, 39, 65, 7, 39, 65, 3, + 39, 66, -3, 39, 67, -7, 40, 68, -12, 40, 69, -17, + 40, 69, -22, 41, 70, -26, 41, 71, -30, 42, 72, -34, + 42, 74, -39, 43, 75, -43, 43, 76, -47, 44, 77, -51, + 44, 78, -55, 45, 80, -59, 45, 81, -63, 46, 82, -66, + 46, 84, -70, 47, 85, -74, 48, 87, -77, 48, 88, -81, + 38, 61, 52, 38, 62, 49, 38, 62, 46, 38, 62, 42, + 38, 62, 37, 38, 62, 33, 38, 63, 28, 38, 63, 22, + 38, 63, 17, 39, 64, 12, 39, 64, 8, 39, 65, 3, + 39, 66, -3, 40, 66, -7, 40, 67, -12, 40, 68, -16, + 41, 69, -21, 41, 70, -26, 41, 71, -30, 42, 72, -34, + 42, 73, -39, 43, 74, -43, 43, 76, -47, 44, 77, -51, + 44, 78, -55, 45, 79, -59, 45, 81, -63, 46, 82, -66, + 46, 83, -70, 47, 85, -74, 48, 86, -77, 48, 88, -81, + 38, 61, 52, 38, 61, 49, 38, 61, 46, 38, 61, 42, + 38, 62, 37, 38, 62, 33, 38, 62, 28, 38, 63, 22, + 39, 63, 18, 39, 63, 13, 39, 64, 8, 39, 65, 3, + 39, 65, -2, 40, 66, -7, 40, 67, -12, 40, 68, -16, + 41, 69, -21, 41, 70, -25, 41, 71, -30, 42, 72, -34, + 42, 73, -39, 43, 74, -43, 43, 75, -47, 44, 76, -51, + 44, 78, -54, 45, 79, -59, 45, 80, -62, 46, 82, -66, + 47, 83, -70, 47, 85, -74, 48, 86, -77, 49, 88, -80, + 38, 61, 52, 38, 61, 49, 38, 61, 46, 38, 61, 42, + 38, 61, 38, 38, 61, 33, 38, 62, 28, 39, 62, 23, + 39, 63, 18, 39, 63, 13, 39, 64, 8, 39, 64, 3, + 40, 65, -2, 40, 66, -7, 40, 66, -11, 40, 67, -16, + 41, 68, -21, 41, 69, -25, 42, 70, -30, 42, 71, -34, + 42, 72, -38, 43, 74, -42, 43, 75, -46, 44, 76, -50, + 44, 77, -54, 45, 79, -58, 46, 80, -62, 46, 81, -66, + 47, 83, -69, 47, 84, -73, 48, 86, -77, 49, 87, -80, + 38, 60, 52, 38, 60, 49, 38, 60, 46, 38, 60, 42, + 38, 61, 38, 38, 61, 33, 39, 61, 28, 39, 62, 23, + 39, 62, 18, 39, 62, 13, 39, 63, 8, 39, 64, 3, + 40, 64, -2, 40, 65, -7, 40, 66, -11, 41, 67, -16, + 41, 68, -21, 41, 69, -25, 42, 70, -29, 42, 71, -33, + 43, 72, -38, 43, 73, -42, 44, 74, -46, 44, 76, -50, + 45, 77, -54, 45, 78, -58, 46, 80, -62, 46, 81, -66, + 47, 82, -69, 48, 84, -73, 48, 85, -77, 49, 87, -80, + 38, 59, 52, 38, 59, 49, 38, 60, 46, 39, 60, 42, + 39, 60, 38, 39, 60, 33, 39, 60, 29, 39, 61, 23, + 39, 61, 18, 39, 62, 13, 39, 62, 9, 40, 63, 4, + 40, 64, -2, 40, 64, -6, 41, 65, -11, 41, 66, -15, + 41, 67, -20, 42, 68, -25, 42, 69, -29, 42, 70, -33, + 43, 71, -38, 43, 73, -42, 44, 74, -46, 44, 75, -50, + 45, 76, -54, 45, 78, -58, 46, 79, -62, 46, 80, -65, + 47, 82, -69, 48, 83, -73, 48, 85, -76, 49, 86, -80, + 39, 59, 52, 39, 59, 49, 39, 59, 47, 39, 59, 42, + 39, 59, 38, 39, 59, 34, 39, 60, 29, 39, 60, 23, + 39, 61, 19, 40, 61, 14, 40, 62, 9, 40, 62, 4, + 40, 63, -1, 40, 64, -6, 41, 65, -10, 41, 65, -15, + 41, 66, -20, 42, 67, -24, 42, 68, -29, 43, 69, -33, + 43, 71, -37, 43, 72, -42, 44, 73, -46, 44, 74, -49, + 45, 76, -53, 46, 77, -58, 46, 78, -61, 47, 80, -65, + 47, 81, -69, 48, 83, -73, 49, 84, -76, 49, 86, -80, + 39, 58, 53, 39, 58, 50, 39, 58, 47, 39, 58, 43, + 39, 58, 38, 39, 59, 34, 39, 59, 29, 40, 59, 24, + 40, 60, 19, 40, 60, 14, 40, 61, 9, 40, 61, 5, + 41, 62, -1, 41, 63, -5, 41, 64, -10, 41, 65, -15, + 42, 66, -20, 42, 67, -24, 42, 68, -28, 43, 69, -32, + 43, 70, -37, 44, 71, -41, 44, 72, -45, 45, 74, -49, + 45, 75, -53, 46, 76, -57, 46, 78, -61, 47, 79, -65, + 47, 81, -68, 48, 82, -72, 49, 84, -76, 49, 85, -79, + 39, 57, 53, 39, 57, 50, 39, 57, 47, 39, 57, 43, + 40, 57, 39, 40, 58, 34, 40, 58, 30, 40, 58, 24, + 40, 59, 19, 40, 59, 15, 40, 60, 10, 41, 60, 5, + 41, 61, 0, 41, 62, -5, 41, 63, -10, 42, 64, -14, + 42, 65, -19, 42, 66, -23, 43, 67, -28, 43, 68, -32, + 44, 69, -37, 44, 70, -41, 45, 72, -45, 45, 73, -49, + 45, 74, -52, 46, 76, -57, 47, 77, -60, 47, 78, -64, + 48, 80, -68, 48, 81, -72, 49, 83, -75, 50, 84, -79, + 40, 56, 53, 40, 56, 50, 40, 56, 47, 40, 56, 43, + 40, 56, 39, 40, 56, 35, 40, 57, 30, 40, 57, 25, + 40, 58, 20, 41, 58, 15, 41, 59, 10, 41, 59, 6, + 41, 60, 0, 41, 61, -4, 42, 62, -9, 42, 63, -14, + 42, 64, -19, 43, 65, -23, 43, 66, -27, 43, 67, -31, + 44, 68, -36, 44, 69, -40, 45, 71, -44, 45, 72, -48, + 46, 73, -52, 46, 75, -56, 47, 76, -60, 47, 78, -64, + 48, 79, -67, 49, 81, -71, 49, 82, -75, 50, 84, -78, + 40, 54, 53, 40, 55, 50, 40, 55, 48, 40, 55, 44, + 40, 55, 40, 40, 55, 35, 41, 56, 31, 41, 56, 25, + 41, 57, 20, 41, 57, 16, 41, 58, 11, 41, 58, 6, + 42, 59, 1, 42, 60, -4, 42, 61, -8, 42, 62, -13, + 43, 63, -18, 43, 64, -22, 43, 65, -27, 44, 66, -31, + 44, 67, -35, 45, 68, -40, 45, 70, -44, 46, 71, -47, + 46, 72, -51, 47, 74, -56, 47, 75, -59, 48, 77, -63, + 48, 78, -67, 49, 80, -71, 50, 81, -74, 50, 83, -78, + 41, 53, 53, 41, 53, 51, 41, 53, 48, 41, 53, 44, + 41, 53, 40, 41, 54, 36, 41, 54, 31, 41, 54, 26, + 41, 55, 21, 42, 55, 16, 42, 56, 12, 42, 57, 7, + 42, 57, 2, 42, 58, -3, 43, 59, -8, 43, 60, -12, + 43, 61, -17, 44, 62, -21, 44, 63, -26, 44, 64, -30, + 45, 66, -35, 45, 67, -39, 46, 68, -43, 46, 69, -47, + 47, 71, -51, 47, 72, -55, 48, 74, -59, 48, 75, -62, + 49, 77, -66, 49, 78, -70, 50, 80, -74, 51, 81, -77, + 41, 51, 53, 41, 51, 51, 41, 52, 48, 41, 52, 45, + 41, 52, 41, 42, 52, 36, 42, 53, 32, 42, 53, 27, + 42, 54, 22, 42, 54, 17, 42, 55, 12, 42, 55, 8, + 43, 56, 2, 43, 57, -2, 43, 58, -7, 43, 59, -11, + 44, 60, -16, 44, 61, -21, 44, 62, -25, 45, 63, -29, + 45, 64, -34, 46, 66, -38, 46, 67, -42, 47, 68, -46, + 47, 70, -50, 48, 71, -54, 48, 73, -58, 49, 74, -62, + 49, 76, -65, 50, 77, -69, 50, 79, -73, 51, 80, -77, + 42, 50, 54, 42, 50, 51, 42, 50, 49, 42, 50, 45, + 42, 50, 41, 42, 51, 37, 42, 51, 32, 42, 52, 27, + 42, 52, 23, 43, 53, 18, 43, 53, 13, 43, 54, 8, + 43, 55, 3, 43, 55, -2, 44, 56, -6, 44, 57, -11, + 44, 58, -16, 45, 59, -20, 45, 60, -24, 45, 62, -29, + 46, 63, -33, 46, 64, -37, 47, 66, -41, 47, 67, -45, + 47, 68, -49, 48, 70, -54, 49, 71, -57, 49, 73, -61, + 50, 74, -65, 50, 76, -69, 51, 78, -72, 51, 79, -76, + 42, 48, 54, 42, 48, 52, 42, 48, 49, 42, 49, 46, + 43, 49, 42, 43, 49, 38, 43, 50, 33, 43, 50, 28, + 43, 50, 23, 43, 51, 19, 43, 52, 14, 43, 52, 9, + 44, 53, 4, 44, 54, -1, 44, 55, -5, 44, 56, -10, + 45, 57, -15, 45, 58, -19, 45, 59, -23, 46, 60, -28, + 46, 62, -32, 47, 63, -36, 47, 64, -41, 48, 65, -45, + 48, 67, -48, 49, 68, -53, 49, 70, -57, 50, 71, -60, + 50, 73, -64, 51, 75, -68, 51, 76, -72, 52, 78, -75, + 43, 47, 54, 43, 47, 52, 43, 47, 50, 43, 47, 46, + 43, 47, 42, 43, 48, 38, 43, 48, 34, 43, 48, 29, + 44, 49, 24, 44, 49, 19, 44, 50, 15, 44, 51, 10, + 44, 51, 5, 45, 52, 0, 45, 53, -4, 45, 54, -9, + 45, 55, -14, 46, 56, -18, 46, 57, -23, 46, 59, -27, + 47, 60, -32, 47, 61, -36, 48, 63, -40, 48, 64, -44, + 49, 65, -48, 49, 67, -52, 50, 69, -56, 50, 70, -60, + 51, 72, -63, 51, 73, -67, 52, 75, -71, 52, 77, -74, + 44, 45, 55, 44, 45, 52, 44, 45, 50, 44, 45, 47, + 44, 45, 43, 44, 46, 39, 44, 46, 35, 44, 47, 29, + 44, 47, 25, 44, 48, 20, 45, 48, 16, 45, 49, 11, + 45, 50, 6, 45, 51, 1, 45, 51, -4, 46, 52, -8, + 46, 54, -13, 46, 55, -17, 47, 56, -22, 47, 57, -26, + 47, 58, -31, 48, 60, -35, 48, 61, -39, 49, 62, -43, + 49, 64, -47, 50, 66, -51, 50, 67, -55, 51, 69, -59, + 51, 70, -62, 52, 72, -66, 52, 74, -70, 53, 75, -74, + 44, 43, 55, 44, 43, 53, 44, 43, 51, 44, 43, 47, + 45, 44, 44, 45, 44, 40, 45, 44, 35, 45, 45, 30, + 45, 45, 26, 45, 46, 21, 45, 46, 16, 45, 47, 12, + 46, 48, 7, 46, 49, 2, 46, 50, -3, 46, 51, -7, + 47, 52, -12, 47, 53, -16, 47, 54, -21, 48, 55, -25, + 48, 57, -30, 48, 58, -34, 49, 59, -38, 49, 61, -42, + 50, 62, -46, 50, 64, -50, 51, 65, -54, 51, 67, -58, + 52, 69, -61, 52, 70, -66, 53, 72, -69, 53, 74, -73, + 45, 41, 55, 45, 41, 53, 45, 41, 51, 45, 42, 48, + 45, 42, 44, 45, 42, 40, 45, 42, 36, 45, 43, 31, + 46, 43, 27, 46, 44, 22, 46, 45, 17, 46, 45, 13, + 46, 46, 8, 47, 47, 3, 47, 48, -2, 47, 49, -6, + 47, 50, -11, 48, 51, -15, 48, 52, -20, 48, 54, -24, + 49, 55, -29, 49, 56, -33, 49, 58, -37, 50, 59, -41, + 50, 61, -45, 51, 62, -49, 51, 64, -53, 52, 65, -57, + 52, 67, -61, 53, 69, -65, 53, 70, -68, 54, 72, -72, + 46, 39, 56, 46, 39, 54, 46, 39, 52, 46, 40, 48, + 46, 40, 45, 46, 40, 41, 46, 40, 37, 46, 41, 32, + 46, 41, 28, 46, 42, 23, 47, 43, 18, 47, 43, 14, + 47, 44, 9, 47, 45, 4, 47, 46, -1, 48, 47, -5, + 48, 48, -10, 48, 49, -14, 49, 50, -19, 49, 52, -23, + 49, 53, -28, 50, 54, -32, 50, 56, -36, 50, 57, -40, + 51, 59, -44, 51, 61, -48, 52, 62, -52, 52, 64, -56, + 53, 65, -60, 53, 67, -64, 54, 69, -67, 54, 71, -71, + 47, 37, 56, 47, 37, 54, 47, 37, 52, 47, 38, 49, + 47, 38, 46, 47, 38, 42, 47, 38, 38, 47, 39, 33, + 47, 39, 28, 47, 40, 24, 47, 41, 19, 48, 41, 15, + 48, 42, 10, 48, 43, 5, 48, 44, 1, 48, 45, -4, + 49, 46, -9, 49, 47, -13, 49, 49, -18, 50, 50, -22, + 50, 51, -27, 50, 53, -31, 51, 54, -35, 51, 55, -39, + 52, 57, -43, 52, 59, -47, 52, 60, -51, 53, 62, -55, + 53, 64, -59, 54, 65, -63, 54, 67, -66, 55, 69, -70, + 47, 35, 57, 47, 35, 55, 47, 35, 53, 47, 36, 50, + 48, 36, 46, 48, 36, 43, 48, 36, 39, 48, 37, 34, + 48, 37, 29, 48, 38, 25, 48, 39, 20, 48, 39, 16, + 49, 40, 11, 49, 41, 6, 49, 42, 2, 49, 43, -3, + 49, 44, -8, 50, 45, -12, 50, 47, -16, 50, 48, -21, + 51, 49, -25, 51, 51, -30, 51, 52, -34, 52, 54, -38, + 52, 55, -42, 53, 57, -46, 53, 58, -50, 54, 60, -54, + 54, 62, -58, 55, 64, -62, 55, 65, -65, 56, 67, -69, + 48, 33, 57, 48, 33, 55, 48, 33, 53, 48, 33, 50, + 48, 34, 47, 48, 34, 44, 48, 34, 40, 49, 35, 35, + 49, 35, 30, 49, 36, 26, 49, 37, 21, 49, 37, 17, + 49, 38, 12, 50, 39, 7, 50, 40, 3, 50, 41, -2, + 50, 42, -7, 51, 43, -11, 51, 45, -15, 51, 46, -20, + 51, 47, -24, 52, 49, -28, 52, 50, -33, 53, 52, -37, + 53, 53, -41, 53, 55, -45, 54, 57, -49, 54, 58, -53, + 55, 60, -56, 55, 62, -61, 56, 64, -64, 56, 65, -68, + 49, 31, 58, 49, 31, 56, 49, 31, 54, 49, 31, 51, + 49, 32, 48, 49, 32, 44, 49, 32, 40, 49, 33, 36, + 50, 33, 31, 50, 34, 27, 50, 35, 23, 50, 35, 18, + 50, 36, 13, 50, 37, 8, 51, 38, 4, 51, 39, 0, + 51, 40, -5, 51, 41, -10, 52, 43, -14, 52, 44, -18, + 52, 45, -23, 53, 47, -27, 53, 48, -31, 53, 50, -35, + 54, 51, -39, 54, 53, -44, 55, 55, -48, 55, 56, -52, + 55, 58, -55, 56, 60, -60, 56, 62, -63, 57, 63, -67, + 50, 29, 58, 50, 29, 57, 50, 29, 55, 50, 29, 52, + 50, 29, 49, 50, 30, 45, 50, 30, 41, 50, 31, 37, + 50, 31, 33, 51, 32, 28, 51, 32, 24, 51, 33, 19, + 51, 34, 14, 51, 35, 10, 51, 36, 5, 52, 37, 1, + 52, 38, -4, 52, 39, -9, 52, 40, -13, 53, 42, -17, + 53, 43, -22, 53, 45, -26, 54, 46, -30, 54, 48, -34, + 54, 49, -38, 55, 51, -43, 55, 53, -47, 56, 54, -50, + 56, 56, -54, 57, 58, -58, 57, 60, -62, 58, 62, -66, + 51, 27, 59, 51, 27, 57, 51, 27, 55, 51, 27, 53, + 51, 27, 50, 51, 28, 46, 51, 28, 42, 51, 28, 38, + 51, 29, 34, 51, 30, 29, 52, 30, 25, 52, 31, 20, + 52, 32, 15, 52, 33, 11, 52, 34, 6, 52, 35, 2, + 53, 36, -3, 53, 37, -7, 53, 38, -12, 54, 40, -16, + 54, 41, -21, 54, 43, -25, 55, 44, -29, 55, 46, -33, + 55, 47, -37, 56, 49, -41, 56, 51, -45, 56, 52, -49, + 57, 54, -53, 57, 56, -57, 58, 58, -61, 58, 60, -65, + 52, 24, 59, 52, 25, 58, 52, 25, 56, 52, 25, 53, + 52, 25, 50, 52, 25, 47, 52, 26, 43, 52, 26, 39, + 52, 27, 35, 52, 27, 30, 52, 28, 26, 53, 29, 22, + 53, 30, 17, 53, 31, 12, 53, 32, 8, 53, 33, 3, + 54, 34, -2, 54, 35, -6, 54, 36, -10, 54, 38, -15, + 55, 39, -19, 55, 40, -23, 55, 42, -28, 56, 43, -32, + 56, 45, -36, 56, 47, -40, 57, 49, -44, 57, 50, -48, + 58, 52, -52, 58, 54, -56, 59, 56, -60, 59, 58, -63, + 53, 22, 60, 53, 22, 58, 53, 22, 57, 53, 23, 54, + 53, 23, 51, 53, 23, 48, 53, 24, 44, 53, 24, 40, + 53, 25, 36, 53, 25, 32, 53, 26, 27, 53, 27, 23, + 54, 27, 18, 54, 28, 13, 54, 29, 9, 54, 30, 5, + 54, 32, 0, 55, 33, -5, 55, 34, -9, 55, 35, -13, + 56, 37, -18, 56, 38, -22, 56, 40, -26, 57, 41, -30, + 57, 43, -34, 57, 45, -39, 58, 46, -43, 58, 48, -47, + 58, 50, -50, 59, 52, -55, 59, 54, -58, 60, 56, -62, + 54, 20, 61, 54, 20, 59, 54, 20, 57, 54, 20, 55, + 54, 21, 52, 54, 21, 49, 54, 21, 45, 54, 22, 41, + 54, 22, 37, 54, 23, 33, 54, 24, 28, 54, 24, 24, + 55, 25, 19, 55, 26, 15, 55, 27, 10, 55, 28, 6, + 55, 29, 1, 56, 31, -3, 56, 32, -8, 56, 33, -12, + 56, 35, -17, 57, 36, -21, 57, 38, -25, 57, 39, -29, + 58, 41, -33, 58, 43, -38, 58, 44, -41, 59, 46, -45, + 59, 48, -49, 60, 50, -53, 60, 52, -57, 61, 53, -61, + 55, 18, 61, 55, 18, 60, 55, 18, 58, 55, 18, 56, + 55, 18, 53, 55, 19, 50, 55, 19, 46, 55, 20, 42, + 55, 20, 38, 55, 21, 34, 55, 21, 30, 55, 22, 25, + 56, 23, 20, 56, 24, 16, 56, 25, 12, 56, 26, 7, + 56, 27, 2, 57, 28, -2, 57, 30, -6, 57, 31, -11, + 57, 33, -15, 58, 34, -19, 58, 35, -24, 58, 37, -28, + 59, 39, -32, 59, 40, -36, 59, 42, -40, 60, 44, -44, + 60, 46, -48, 61, 48, -52, 61, 50, -56, 61, 51, -60, + 56, 16, 62, 56, 16, 60, 56, 16, 59, 56, 16, 57, + 56, 16, 54, 56, 17, 51, 56, 17, 47, 56, 17, 43, + 56, 18, 39, 56, 19, 35, 56, 19, 31, 56, 20, 26, + 56, 21, 22, 57, 22, 17, 57, 23, 13, 57, 24, 9, + 57, 25, 4, 57, 26, -1, 58, 27, -5, 58, 29, -9, + 58, 30, -14, 59, 32, -18, 59, 33, -22, 59, 35, -26, + 59, 36, -30, 60, 38, -35, 60, 40, -39, 61, 42, -43, + 61, 43, -47, 61, 46, -51, 62, 47, -55, 62, 49, -58, + 57, 13, 63, 57, 13, 61, 57, 14, 60, 57, 14, 57, + 57, 14, 55, 57, 14, 52, 57, 15, 48, 57, 15, 44, + 57, 16, 40, 57, 16, 36, 57, 17, 32, 57, 18, 28, + 57, 19, 23, 58, 19, 19, 58, 20, 14, 58, 21, 10, + 58, 23, 5, 58, 24, 1, 59, 25, -4, 59, 27, -8, + 59, 28, -13, 59, 30, -17, 60, 31, -21, 60, 33, -25, + 60, 34, -29, 61, 36, -33, 61, 38, -37, 61, 40, -41, + 62, 41, -45, 62, 43, -49, 63, 45, -53, 63, 47, -57, + 58, 11, 63, 58, 11, 62, 58, 11, 61, 58, 11, 58, + 58, 11, 56, 58, 12, 53, 58, 12, 50, 58, 12, 46, + 58, 13, 42, 58, 13, 38, 58, 14, 34, 59, 15, 29, + 59, 16, 25, 59, 17, 20, 59, 18, 16, 59, 19, 12, + 59, 20, 7, 60, 21, 2, 60, 22, -2, 60, 24, -6, + 60, 25, -11, 61, 27, -15, 61, 28, -19, 61, 30, -23, + 61, 31, -27, 62, 33, -32, 62, 35, -36, 63, 37, -40, + 63, 39, -43, 63, 41, -48, 64, 42, -52, 64, 44, -55, + 59, 8, 64, 59, 8, 63, 59, 9, 61, 59, 9, 59, + 59, 9, 57, 59, 9, 54, 59, 10, 51, 59, 10, 47, + 59, 11, 43, 59, 11, 39, 59, 12, 35, 60, 13, 31, + 60, 14, 26, 60, 14, 22, 60, 15, 17, 60, 16, 13, + 60, 18, 8, 61, 19, 4, 61, 20, 0, 61, 21, -5, + 61, 23, -9, 62, 24, -14, 62, 26, -18, 62, 28, -22, + 62, 29, -26, 63, 31, -30, 63, 33, -34, 63, 35, -38, + 64, 36, -42, 64, 38, -46, 65, 40, -50, 65, 42, -54, + 60, 6, 65, 60, 6, 64, 60, 6, 62, 60, 7, 60, + 60, 7, 58, 60, 7, 55, 60, 7, 52, 60, 8, 48, + 60, 8, 44, 60, 9, 40, 60, 10, 36, 61, 10, 32, + 61, 11, 27, 61, 12, 23, 61, 13, 19, 61, 14, 14, + 61, 15, 10, 62, 17, 5, 62, 18, 1, 62, 19, -3, + 62, 21, -8, 63, 22, -12, 63, 24, -16, 63, 25, -20, + 63, 27, -24, 64, 29, -29, 64, 31, -33, 64, 32, -37, + 65, 34, -41, 65, 36, -45, 66, 38, -49, 66, 40, -52, + 61, 4, 66, 61, 4, 64, 61, 4, 63, 61, 4, 61, + 61, 5, 59, 61, 5, 56, 61, 5, 53, 61, 6, 49, + 61, 6, 45, 61, 7, 41, 61, 7, 37, 62, 8, 33, + 62, 9, 29, 62, 10, 24, 62, 11, 20, 62, 12, 16, + 62, 13, 11, 63, 14, 7, 63, 16, 2, 63, 17, -2, + 63, 19, -6, 63, 20, -11, 64, 22, -15, 64, 23, -19, + 64, 25, -23, 65, 27, -27, 65, 28, -31, 65, 30, -35, + 66, 32, -39, 66, 34, -44, 66, 36, -47, 67, 38, -51, + 62, 2, 66, 62, 2, 65, 62, 2, 64, 62, 2, 62, + 62, 2, 60, 62, 3, 57, 62, 3, 54, 62, 4, 50, + 62, 4, 46, 62, 5, 43, 62, 5, 39, 63, 6, 34, + 63, 7, 30, 63, 8, 26, 63, 9, 21, 63, 10, 17, + 63, 11, 12, 64, 12, 8, 64, 13, 4, 64, 15, 0, + 64, 16, -5, 64, 18, -9, 65, 19, -13, 65, 21, -17, + 65, 22, -21, 66, 24, -26, 66, 26, -30, 66, 28, -34, + 67, 30, -38, 67, 32, -42, 67, 34, -46, 68, 36, -50, + 63, 0, 67, 63, 0, 66, 63, 0, 65, 63, 0, 63, + 63, 0, 60, 63, 1, 58, 63, 1, 55, 63, 1, 51, + 63, 2, 47, 63, 2, 44, 64, 3, 40, 64, 4, 36, + 64, 5, 31, 64, 6, 27, 64, 7, 23, 64, 8, 19, + 64, 9, 14, 65, 10, 10, 65, 11, 5, 65, 13, 1, + 65, 14, -4, 65, 16, -8, 66, 17, -12, 66, 19, -16, + 66, 20, -20, 67, 22, -24, 67, 24, -28, 67, 26, -32, + 68, 27, -36, 68, 30, -41, 68, 31, -44, 69, 33, -48, + 64, -3, 68, 64, -2, 67, 64, -2, 65, 64, -2, 64, + 64, -2, 61, 64, -2, 59, 64, -1, 56, 64, -1, 52, + 64, 0, 49, 65, 0, 45, 65, 1, 41, 65, 2, 37, + 65, 3, 33, 65, 3, 28, 65, 4, 24, 65, 5, 20, + 65, 7, 15, 66, 8, 11, 66, 9, 7, 66, 10, 3, + 66, 12, -2, 66, 13, -6, 67, 15, -10, 67, 16, -14, + 67, 18, -18, 68, 20, -23, 68, 22, -27, 68, 23, -31, + 68, 25, -35, 69, 27, -39, 69, 29, -43, 70, 31, -47, + 65, -5, 69, 65, -5, 67, 65, -4, 66, 65, -4, 64, + 65, -4, 62, 65, -4, 60, 65, -3, 57, 65, -3, 53, + 65, -2, 50, 66, -2, 46, 66, -1, 42, 66, -1, 38, + 66, 0, 34, 66, 1, 30, 66, 2, 26, 66, 3, 21, + 66, 4, 17, 67, 6, 13, 67, 7, 8, 67, 8, 4, + 67, 10, -1, 67, 11, -5, 68, 13, -9, 68, 14, -13, + 68, 16, -17, 69, 18, -21, 69, 19, -25, 69, 21, -29, + 69, 23, -33, 70, 25, -38, 70, 27, -41, 71, 29, -45, + 66, -7, 69, 66, -7, 68, 66, -7, 67, 66, -6, 65, + 66, -6, 63, 66, -6, 61, 66, -6, 58, 66, -5, 54, + 67, -5, 51, 67, -4, 47, 67, -3, 44, 67, -3, 40, + 67, -2, 35, 67, -1, 31, 67, 0, 27, 67, 1, 23, + 68, 2, 18, 68, 3, 14, 68, 5, 10, 68, 6, 6, + 68, 7, 1, 69, 9, -3, 69, 10, -7, 69, 12, -11, + 69, 14, -15, 70, 15, -20, 70, 17, -24, 70, 19, -28, + 70, 21, -32, 71, 23, -36, 71, 25, -40, 71, 27, -44, + 67, -9, 70, 67, -9, 69, 67, -9, 68, 67, -9, 66, + 67, -8, 64, 67, -8, 62, 68, -8, 59, 68, -7, 56, + 68, -7, 52, 68, -6, 49, 68, -6, 45, 68, -5, 41, + 68, -4, 37, 68, -3, 32, 68, -2, 28, 68, -1, 24, + 69, 0, 20, 69, 1, 15, 69, 2, 11, 69, 4, 7, + 69, 5, 2, 70, 7, -2, 70, 8, -6, 70, 10, -10, + 70, 11, -14, 71, 13, -18, 71, 15, -22, 71, 17, -26, + 71, 18, -30, 72, 21, -35, 72, 22, -38, 72, 24, -42, + 68, -11, 71, 68, -11, 70, 68, -11, 69, 68, -11, 67, + 69, -10, 65, 69, -10, 63, 69, -10, 60, 69, -9, 57, + 69, -9, 53, 69, -8, 50, 69, -8, 46, 69, -7, 42, + 69, -6, 38, 69, -5, 34, 69, -4, 30, 69, -3, 26, + 70, -2, 21, 70, -1, 17, 70, 0, 13, 70, 2, 9, + 70, 3, 4, 71, 4, 0, 71, 6, -4, 71, 8, -8, + 71, 9, -12, 72, 11, -17, 72, 13, -21, 72, 14, -25, + 72, 16, -29, 73, 18, -33, 73, 20, -37, 73, 22, -41, + 70, -13, 72, 70, -13, 71, 70, -13, 70, 70, -13, 68, + 70, -12, 66, 70, -12, 64, 70, -12, 61, 70, -11, 58, + 70, -11, 54, 70, -10, 51, 70, -10, 47, 70, -9, 44, + 70, -8, 39, 70, -7, 35, 70, -6, 31, 71, -5, 27, + 71, -4, 23, 71, -3, 18, 71, -2, 14, 71, -1, 10, + 71, 1, 5, 72, 2, 1, 72, 4, -3, 72, 5, -7, + 72, 7, -11, 73, 9, -15, 73, 10, -19, 73, 12, -23, + 73, 14, -27, 74, 16, -32, 74, 18, -35, 74, 20, -39, + 71, -15, 72, 71, -15, 72, 71, -15, 70, 71, -15, 69, + 71, -15, 67, 71, -14, 65, 71, -14, 62, 71, -13, 59, + 71, -13, 56, 71, -12, 52, 71, -12, 49, 71, -11, 45, + 71, -10, 41, 71, -9, 37, 71, -9, 33, 72, -8, 29, + 72, -6, 24, 72, -5, 20, 72, -4, 16, 72, -3, 12, + 72, -1, 7, 73, 0, 3, 73, 2, -1, 73, 3, -5, + 73, 5, -9, 74, 7, -14, 74, 8, -18, 74, 10, -22, + 74, 12, -26, 75, 14, -30, 75, 16, -34, 75, 18, -38, + 72, -17, 73, 72, -17, 72, 72, -17, 71, 72, -17, 70, + 72, -17, 68, 72, -16, 66, 72, -16, 63, 72, -16, 60, + 72, -15, 57, 72, -14, 53, 72, -14, 50, 72, -13, 46, + 72, -12, 42, 72, -12, 38, 73, -11, 34, 73, -10, 30, + 73, -8, 25, 73, -7, 21, 73, -6, 17, 73, -5, 13, + 74, -3, 9, 74, -2, 4, 74, -1, 0, 74, 1, -4, + 74, 3, -8, 75, 4, -12, 75, 6, -16, 75, 8, -20, + 75, 10, -24, 76, 12, -28, 76, 14, -32, 76, 16, -36, + 73, -19, 74, 73, -19, 73, 73, -19, 72, 73, -19, 71, + 73, -19, 69, 73, -18, 67, 73, -18, 64, 73, -18, 61, + 73, -17, 58, 73, -17, 55, 73, -16, 51, 73, -15, 47, + 73, -14, 43, 74, -14, 39, 74, -13, 35, 74, -12, 31, + 74, -11, 27, 74, -9, 23, 74, -8, 19, 74, -7, 15, + 75, -6, 10, 75, -4, 6, 75, -3, 2, 75, -1, -2, + 75, 0, -6, 76, 2, -11, 76, 4, -15, 76, 6, -19, + 76, 7, -23, 77, 10, -27, 77, 11, -31, 77, 13, -35, + 74, -21, 75, 74, -21, 74, 74, -21, 73, 74, -21, 72, + 74, -21, 70, 74, -20, 68, 74, -20, 65, 74, -20, 62, + 74, -19, 59, 74, -19, 56, 74, -18, 52, 74, -17, 49, + 75, -16, 45, 75, -16, 41, 75, -15, 37, 75, -14, 33, + 75, -13, 28, 75, -12, 24, 75, -10, 20, 75, -9, 16, + 76, -8, 12, 76, -6, 8, 76, -5, 3, 76, -3, -1, + 76, -2, -5, 77, 0, -9, 77, 2, -13, 77, 4, -17, + 77, 5, -21, 78, 7, -25, 78, 9, -29, 78, 11, -33, + 75, -23, 76, 75, -23, 75, 75, -23, 74, 75, -23, 72, + 75, -23, 71, 75, -22, 69, 75, -22, 66, 75, -22, 63, + 75, -21, 60, 75, -21, 57, 75, -20, 54, 76, -19, 50, + 76, -18, 46, 76, -18, 42, 76, -17, 38, 76, -16, 34, + 76, -15, 30, 76, -14, 26, 76, -12, 22, 77, -11, 18, + 77, -10, 13, 77, -8, 9, 77, -7, 5, 77, -5, 1, + 78, -4, -3, 78, -2, -8, 78, 0, -12, 78, 1, -15, + 79, 3, -19, 79, 5, -24, 79, 7, -28, 79, 9, -31, + 76, -25, 77, 76, -25, 76, 76, -25, 75, 76, -25, 73, + 76, -24, 72, 76, -24, 70, 76, -24, 67, 76, -23, 64, + 76, -23, 61, 77, -23, 58, 77, -22, 55, 77, -21, 51, + 77, -20, 47, 77, -20, 43, 77, -19, 40, 77, -18, 36, + 77, -17, 31, 77, -16, 27, 77, -14, 23, 78, -13, 19, + 78, -12, 15, 78, -10, 11, 78, -9, 7, 78, -8, 3, + 79, -6, -1, 79, -4, -6, 79, -2, -10, 79, -1, -14, + 80, 1, -18, 80, 3, -22, 80, 5, -26, 80, 7, -30, + 77, -27, 77, 77, -27, 77, 77, -27, 76, 77, -27, 74, + 77, -26, 73, 77, -26, 71, 77, -26, 68, 78, -25, 65, + 78, -25, 63, 78, -24, 59, 78, -24, 56, 78, -23, 53, + 78, -22, 49, 78, -22, 45, 78, -21, 41, 78, -20, 37, + 78, -19, 33, 78, -18, 29, 79, -16, 25, 79, -15, 21, + 79, -14, 16, 79, -12, 12, 79, -11, 8, 79, -10, 4, + 80, -8, 0, 80, -6, -4, 80, -5, -8, 80, -3, -12, + 81, -1, -16, 81, 1, -21, 81, 3, -24, 81, 5, -28, + 78, -29, 78, 78, -29, 77, 78, -29, 77, 78, -29, 75, + 79, -28, 74, 79, -28, 72, 79, -28, 69, 79, -27, 67, + 79, -27, 64, 79, -26, 61, 79, -26, 57, 79, -25, 54, + 79, -24, 50, 79, -24, 46, 79, -23, 42, 79, -22, 38, + 79, -21, 34, 80, -20, 30, 80, -18, 26, 80, -17, 22, + 80, -16, 18, 80, -15, 14, 80, -13, 10, 81, -12, 6, + 81, -10, 2, 81, -8, -3, 81, -7, -7, 81, -5, -11, + 82, -3, -15, 82, -1, -19, 82, 1, -23, 82, 2, -27, + 80, -31, 79, 80, -31, 78, 80, -31, 77, 80, -30, 76, + 80, -30, 75, 80, -30, 73, 80, -30, 70, 80, -29, 68, + 80, -29, 65, 80, -28, 62, 80, -28, 59, 80, -27, 55, + 80, -26, 51, 80, -26, 47, 80, -25, 44, 80, -24, 40, + 81, -23, 36, 81, -22, 32, 81, -20, 28, 81, -19, 24, + 81, -18, 19, 81, -17, 15, 81, -15, 11, 82, -14, 7, + 82, -12, 3, 82, -10, -1, 82, -9, -5, 82, -7, -9, + 83, -5, -13, 83, -3, -17, 83, -2, -21, 84, 0, -25, + 81, -33, 80, 81, -33, 79, 81, -33, 79, 81, -33, 77, + 81, -33, 76, 81, -32, 74, 81, -32, 72, 81, -32, 69, + 81, -31, 66, 81, -31, 63, 81, -30, 60, 81, -29, 57, + 81, -29, 53, 82, -28, 49, 82, -27, 45, 82, -26, 42, + 82, -25, 37, 82, -24, 33, 82, -23, 29, 82, -22, 26, + 82, -20, 21, 83, -19, 17, 83, -18, 13, 83, -16, 9, + 83, -15, 5, 83, -13, 1, 84, -11, -3, 84, -10, -7, + 84, -8, -11, 84, -6, -15, 85, -4, -19, 85, -2, -23, + 82, -35, 81, 82, -35, 80, 82, -35, 79, 82, -35, 78, + 82, -34, 77, 82, -34, 75, 82, -34, 73, 82, -33, 70, + 82, -33, 67, 82, -33, 64, 82, -32, 61, 82, -31, 58, + 83, -31, 54, 83, -30, 50, 83, -29, 47, 83, -28, 43, + 83, -27, 39, 83, -26, 35, 83, -25, 31, 83, -24, 27, + 84, -22, 23, 84, -21, 19, 84, -20, 15, 84, -18, 11, + 84, -17, 7, 84, -15, 2, 85, -13, -2, 85, -12, -6, + 85, -10, -10, 85, -8, -14, 86, -6, -18, 86, -4, -22, + 83, -37, 82, 83, -37, 81, 83, -37, 80, 83, -36, 79, + 83, -36, 78, 83, -36, 76, 83, -36, 74, 83, -35, 71, + 83, -35, 68, 83, -34, 66, 84, -34, 62, 84, -33, 59, + 84, -32, 55, 84, -32, 52, 84, -31, 48, 84, -30, 44, + 84, -29, 40, 84, -28, 36, 84, -27, 32, 84, -26, 28, + 85, -24, 24, 85, -23, 20, 85, -22, 16, 85, -20, 12, + 85, -19, 8, 86, -17, 4, 86, -15, 0, 86, -14, -4, + 86, -12, -8, 86, -10, -12, 87, -8, -16, 87, -6, -20, + 84, -39, 83, 84, -38, 82, 84, -38, 81, 84, -38, 80, + 84, -38, 79, 84, -38, 77, 84, -37, 75, 85, -37, 72, + 85, -37, 70, 85, -36, 67, 85, -36, 64, 85, -35, 60, + 85, -34, 57, 85, -34, 53, 85, -33, 50, 85, -32, 46, + 85, -31, 42, 85, -30, 38, 85, -29, 34, 86, -28, 30, + 86, -26, 26, 86, -25, 22, 86, -24, 18, 86, -22, 14, + 86, -21, 10, 87, -19, 5, 87, -17, 1, 87, -16, -2, + 87, -14, -6, 87, -12, -11, 88, -10, -15, 88, -9, -18, + 85, -40, 84, 86, -40, 83, 86, -40, 82, 86, -40, 81, + 86, -40, 80, 86, -40, 78, 86, -39, 76, 86, -39, 73, + 86, -38, 71, 86, -38, 68, 86, -37, 65, 86, -37, 62, + 86, -36, 58, 86, -35, 54, 86, -35, 51, 86, -34, 47, + 86, -33, 43, 86, -32, 39, 87, -31, 35, 87, -29, 31, + 87, -28, 27, 87, -27, 23, 87, -26, 19, 87, -24, 15, + 87, -23, 11, 88, -21, 7, 88, -19, 3, 88, -18, -1, + 88, -16, -5, 89, -14, -9, 89, -12, -13, 89, -11, -17, + 87, -42, 84, 87, -42, 84, 87, -42, 83, 87, -42, 82, + 87, -42, 81, 87, -41, 79, 87, -41, 77, 87, -41, 74, + 87, -40, 72, 87, -40, 69, 87, -39, 66, 87, -39, 63, + 87, -38, 59, 87, -37, 56, 87, -36, 52, 87, -36, 49, + 87, -34, 44, 88, -34, 41, 88, -32, 37, 88, -31, 33, + 88, -30, 29, 88, -29, 25, 88, -27, 21, 88, -26, 17, + 89, -25, 13, 89, -23, 8, 89, -21, 5, 89, -20, 1, + 89, -18, -3, 90, -16, -8, 90, -14, -11, 90, -13, -15, + 88, -44, 85, 88, -44, 85, 88, -44, 84, 88, -43, 83, + 88, -43, 81, 88, -43, 80, 88, -43, 78, 88, -42, 75, + 88, -42, 73, 88, -42, 70, 88, -41, 67, 88, -40, 64, + 88, -40, 60, 88, -39, 57, 88, -38, 54, 88, -37, 50, + 89, -36, 46, 89, -35, 42, 89, -34, 38, 89, -33, 34, + 89, -32, 30, 89, -31, 26, 89, -29, 22, 90, -28, 18, + 90, -27, 14, 90, -25, 10, 90, -23, 6, 90, -22, 2, + 90, -20, -2, 91, -18, -6, 91, -16, -10, 91, -15, -14, + 89, -46, 86, 89, -45, 85, 89, -45, 85, 89, -45, 84, + 89, -45, 82, 89, -45, 81, 89, -45, 79, 89, -44, 76, + 89, -44, 74, 89, -43, 71, 89, -43, 68, 89, -42, 65, + 89, -41, 62, 89, -41, 58, 89, -40, 55, 90, -39, 51, + 90, -38, 47, 90, -37, 43, 90, -36, 40, 90, -35, 36, + 90, -34, 32, 90, -32, 28, 90, -31, 24, 91, -30, 20, + 91, -28, 16, 91, -27, 12, 91, -25, 8, 91, -24, 4, + 92, -22, 0, 92, -20, -4, 92, -18, -8, 92, -17, -12, + 90, -47, 87, 90, -47, 86, 90, -47, 86, 90, -47, 85, + 90, -47, 83, 90, -46, 82, 90, -46, 80, 90, -46, 77, + 90, -45, 75, 90, -45, 72, 90, -44, 70, 90, -44, 67, + 90, -43, 63, 91, -43, 60, 91, -42, 56, 91, -41, 53, + 91, -40, 49, 91, -39, 45, 91, -38, 41, 91, -37, 37, + 91, -36, 33, 91, -34, 29, 92, -33, 25, 92, -32, 21, + 92, -30, 18, 92, -29, 13, 92, -27, 9, 92, -26, 5, + 93, -24, 2, 93, -22, -3, 93, -20, -7, 93, -19, -10, + 91, -49, 88, 91, -49, 87, 91, -49, 87, 91, -49, 86, + 91, -48, 84, 91, -48, 83, 91, -48, 81, 91, -48, 79, + 91, -47, 76, 91, -47, 74, 91, -46, 71, 91, -46, 68, + 92, -45, 64, 92, -44, 61, 92, -43, 57, 92, -43, 54, + 92, -42, 50, 92, -41, 46, 92, -40, 43, 92, -39, 39, + 92, -37, 34, 93, -36, 31, 93, -35, 27, 93, -34, 23, + 93, -32, 19, 93, -31, 15, 93, -29, 11, 93, -28, 7, + 94, -26, 3, 94, -24, -1, 94, -22, -5, 94, -21, -9, + 92, -51, 89, 92, -50, 88, 92, -50, 87, 92, -50, 86, + 92, -50, 85, 92, -50, 84, 92, -50, 82, 92, -49, 80, + 92, -49, 77, 93, -48, 75, 93, -48, 72, 93, -47, 69, + 93, -47, 65, 93, -46, 62, 93, -45, 59, 93, -44, 55, + 93, -43, 51, 93, -42, 48, 93, -41, 44, 93, -40, 40, + 93, -39, 36, 94, -38, 32, 94, -37, 28, 94, -35, 24, + 94, -34, 21, 94, -32, 16, 94, -31, 12, 95, -29, 9, + 95, -28, 5, 95, -26, 0, 95, -24, -3, 95, -23, -7, + 39, 64, 54, 39, 64, 51, 39, 64, 48, 39, 64, 44, + 39, 64, 39, 40, 65, 35, 40, 65, 30, 40, 65, 25, + 40, 66, 20, 40, 66, 15, 40, 67, 10, 41, 67, 5, + 41, 68, 0, 41, 69, -5, 41, 69, -9, 42, 70, -14, + 42, 71, -19, 42, 72, -23, 43, 73, -28, 43, 74, -32, + 44, 75, -37, 44, 76, -41, 44, 77, -45, 45, 78, -49, + 45, 80, -52, 46, 81, -57, 47, 82, -60, 47, 84, -64, + 48, 85, -68, 48, 86, -72, 49, 88, -75, 50, 89, -79, + 39, 64, 54, 39, 64, 51, 39, 64, 48, 40, 64, 44, + 40, 64, 39, 40, 64, 35, 40, 65, 30, 40, 65, 25, + 40, 65, 20, 40, 66, 15, 40, 66, 10, 41, 67, 5, + 41, 68, 0, 41, 68, -5, 41, 69, -9, 42, 70, -14, + 42, 71, -19, 42, 72, -23, 43, 73, -27, 43, 74, -32, + 44, 75, -36, 44, 76, -40, 45, 77, -44, 45, 78, -48, + 46, 79, -52, 46, 81, -57, 47, 82, -60, 47, 83, -64, + 48, 85, -68, 48, 86, -72, 49, 87, -75, 50, 89, -79, + 40, 63, 54, 40, 63, 51, 40, 63, 48, 40, 63, 44, + 40, 64, 40, 40, 64, 35, 40, 64, 30, 40, 65, 25, + 40, 65, 20, 40, 65, 15, 41, 66, 10, 41, 66, 6, + 41, 67, 0, 41, 68, -4, 42, 69, -9, 42, 69, -14, + 42, 70, -19, 43, 71, -23, 43, 72, -27, 43, 73, -31, + 44, 74, -36, 44, 75, -40, 45, 77, -44, 45, 78, -48, + 46, 79, -52, 46, 80, -56, 47, 82, -60, 47, 83, -64, + 48, 84, -67, 49, 86, -71, 49, 87, -75, 50, 89, -78, + 40, 63, 54, 40, 63, 51, 40, 63, 48, 40, 63, 44, + 40, 63, 40, 40, 63, 35, 40, 64, 31, 40, 64, 25, + 40, 64, 20, 41, 65, 15, 41, 65, 11, 41, 66, 6, + 41, 67, 0, 41, 67, -4, 42, 68, -9, 42, 69, -13, + 42, 70, -18, 43, 71, -23, 43, 72, -27, 43, 73, -31, + 44, 74, -36, 44, 75, -40, 45, 76, -44, 45, 77, -48, + 46, 79, -52, 46, 80, -56, 47, 81, -60, 47, 83, -64, + 48, 84, -67, 49, 85, -71, 49, 87, -75, 50, 88, -78, + 40, 62, 54, 40, 62, 51, 40, 62, 48, 40, 62, 44, + 40, 63, 40, 40, 63, 35, 40, 63, 31, 40, 64, 25, + 41, 64, 20, 41, 64, 16, 41, 65, 11, 41, 66, 6, + 41, 66, 1, 42, 67, -4, 42, 68, -9, 42, 68, -13, + 43, 69, -18, 43, 70, -23, 43, 71, -27, 44, 72, -31, + 44, 74, -36, 45, 75, -40, 45, 76, -44, 45, 77, -48, + 46, 78, -52, 47, 80, -56, 47, 81, -60, 48, 82, -63, + 48, 83, -67, 49, 85, -71, 49, 86, -75, 50, 88, -78, + 40, 62, 54, 40, 62, 51, 40, 62, 48, 40, 62, 44, + 40, 62, 40, 40, 62, 36, 40, 63, 31, 41, 63, 26, + 41, 63, 21, 41, 64, 16, 41, 64, 11, 41, 65, 6, + 42, 66, 1, 42, 66, -4, 42, 67, -8, 42, 68, -13, + 43, 69, -18, 43, 70, -22, 43, 71, -27, 44, 72, -31, + 44, 73, -35, 45, 74, -39, 45, 75, -44, 46, 76, -47, + 46, 78, -51, 47, 79, -56, 47, 80, -59, 48, 82, -63, + 48, 83, -67, 49, 85, -71, 50, 86, -74, 50, 87, -78, + 40, 61, 54, 40, 61, 51, 40, 61, 48, 40, 61, 44, + 41, 61, 40, 41, 62, 36, 41, 62, 31, 41, 62, 26, + 41, 63, 21, 41, 63, 16, 41, 64, 11, 42, 64, 7, + 42, 65, 1, 42, 66, -3, 42, 66, -8, 43, 67, -12, + 43, 68, -17, 43, 69, -22, 44, 70, -26, 44, 71, -30, + 45, 72, -35, 45, 73, -39, 45, 75, -43, 46, 76, -47, + 46, 77, -51, 47, 78, -55, 47, 80, -59, 48, 81, -63, + 48, 82, -66, 49, 84, -70, 50, 85, -74, 50, 87, -78, + 41, 60, 54, 41, 60, 51, 41, 60, 49, 41, 60, 45, + 41, 61, 41, 41, 61, 36, 41, 61, 32, 41, 61, 26, + 41, 62, 21, 41, 62, 17, 42, 63, 12, 42, 63, 7, + 42, 64, 2, 42, 65, -3, 43, 66, -8, 43, 66, -12, + 43, 67, -17, 44, 68, -21, 44, 69, -26, 44, 70, -30, + 45, 72, -35, 45, 73, -39, 46, 74, -43, 46, 75, -47, + 47, 76, -51, 47, 78, -55, 48, 79, -59, 48, 80, -62, + 49, 82, -66, 49, 83, -70, 50, 85, -74, 51, 86, -77, + 41, 59, 54, 41, 59, 52, 41, 59, 49, 41, 59, 45, + 41, 60, 41, 41, 60, 37, 41, 60, 32, 41, 61, 27, + 42, 61, 22, 42, 61, 17, 42, 62, 12, 42, 63, 7, + 42, 63, 2, 43, 64, -2, 43, 65, -7, 43, 66, -12, + 44, 67, -17, 44, 68, -21, 44, 69, -25, 45, 70, -30, + 45, 71, -34, 45, 72, -38, 46, 73, -42, 46, 74, -46, + 47, 76, -50, 47, 77, -55, 48, 78, -58, 48, 80, -62, + 49, 81, -66, 50, 83, -70, 50, 84, -73, 51, 85, -77, + 41, 58, 54, 41, 58, 52, 41, 58, 49, 41, 58, 45, + 42, 59, 41, 42, 59, 37, 42, 59, 32, 42, 60, 27, + 42, 60, 22, 42, 60, 17, 42, 61, 13, 42, 62, 8, + 43, 62, 3, 43, 63, -2, 43, 64, -7, 44, 65, -11, + 44, 66, -16, 44, 67, -20, 45, 68, -25, 45, 69, -29, + 45, 70, -34, 46, 71, -38, 46, 72, -42, 47, 73, -46, + 47, 75, -50, 48, 76, -54, 48, 77, -58, 49, 79, -62, + 49, 80, -65, 50, 82, -69, 50, 83, -73, 51, 85, -76, + 42, 57, 55, 42, 57, 52, 42, 57, 49, 42, 57, 46, + 42, 57, 42, 42, 58, 37, 42, 58, 33, 42, 58, 28, + 42, 59, 23, 43, 59, 18, 43, 60, 13, 43, 60, 8, + 43, 61, 3, 43, 62, -1, 44, 63, -6, 44, 64, -11, + 44, 65, -16, 45, 66, -20, 45, 67, -24, 45, 68, -28, + 46, 69, -33, 46, 70, -37, 47, 71, -41, 47, 72, -45, + 47, 74, -49, 48, 75, -54, 49, 77, -57, 49, 78, -61, + 50, 79, -65, 50, 81, -69, 51, 82, -72, 51, 84, -76, + 42, 55, 55, 42, 55, 52, 42, 56, 50, 42, 56, 46, + 42, 56, 42, 43, 56, 38, 43, 56, 33, 43, 57, 28, + 43, 57, 23, 43, 58, 19, 43, 58, 14, 43, 59, 9, + 44, 60, 4, 44, 60, -1, 44, 61, -5, 44, 62, -10, + 45, 63, -15, 45, 64, -19, 45, 65, -23, 46, 66, -28, + 46, 67, -32, 47, 69, -37, 47, 70, -41, 47, 71, -45, + 48, 72, -48, 48, 74, -53, 49, 75, -57, 49, 77, -60, + 50, 78, -64, 51, 80, -68, 51, 81, -72, 52, 83, -75, + 43, 54, 55, 43, 54, 53, 43, 54, 50, 43, 54, 46, + 43, 55, 43, 43, 55, 38, 43, 55, 34, 43, 56, 29, + 43, 56, 24, 44, 56, 19, 44, 57, 15, 44, 58, 10, + 44, 58, 5, 44, 59, 0, 45, 60, -5, 45, 61, -9, + 45, 62, -14, 46, 63, -18, 46, 64, -23, 46, 65, -27, + 47, 66, -32, 47, 67, -36, 47, 69, -40, 48, 70, -44, + 48, 71, -48, 49, 73, -52, 49, 74, -56, 50, 75, -60, + 50, 77, -63, 51, 79, -68, 52, 80, -71, 52, 82, -75, + 43, 53, 55, 43, 53, 53, 43, 53, 50, 43, 53, 47, + 43, 53, 43, 44, 53, 39, 44, 54, 35, 44, 54, 29, + 44, 55, 25, 44, 55, 20, 44, 56, 15, 44, 56, 11, + 45, 57, 5, 45, 58, 1, 45, 59, -4, 45, 59, -8, + 46, 60, -13, 46, 61, -18, 46, 63, -22, 47, 64, -26, + 47, 65, -31, 47, 66, -35, 48, 67, -39, 48, 69, -43, + 49, 70, -47, 49, 71, -51, 50, 73, -55, 50, 74, -59, + 51, 76, -63, 51, 77, -67, 52, 79, -70, 52, 80, -74, + 44, 51, 56, 44, 51, 53, 44, 51, 51, 44, 51, 47, + 44, 52, 44, 44, 52, 40, 44, 52, 35, 44, 53, 30, + 44, 53, 25, 45, 54, 21, 45, 54, 16, 45, 55, 11, + 45, 56, 6, 45, 56, 2, 46, 57, -3, 46, 58, -8, + 46, 59, -13, 46, 60, -17, 47, 61, -21, 47, 62, -26, + 48, 64, -30, 48, 65, -34, 48, 66, -38, 49, 67, -42, + 49, 69, -46, 50, 70, -51, 50, 72, -55, 51, 73, -58, + 51, 74, -62, 52, 76, -66, 52, 78, -70, 53, 79, -73, + 44, 49, 56, 44, 50, 54, 45, 50, 51, 45, 50, 48, + 45, 50, 44, 45, 50, 40, 45, 51, 36, 45, 51, 31, + 45, 51, 26, 45, 52, 22, 45, 53, 17, 45, 53, 12, + 46, 54, 7, 46, 55, 2, 46, 56, -2, 46, 56, -7, + 47, 58, -12, 47, 59, -16, 47, 60, -20, 48, 61, -25, + 48, 62, -29, 48, 63, -34, 49, 65, -38, 49, 66, -42, + 50, 67, -46, 50, 69, -50, 51, 70, -54, 51, 72, -58, + 52, 73, -61, 52, 75, -65, 53, 76, -69, 53, 78, -73, + 45, 48, 56, 45, 48, 54, 45, 48, 52, 45, 48, 48, + 45, 48, 45, 45, 49, 41, 45, 49, 37, 46, 49, 32, + 46, 50, 27, 46, 50, 22, 46, 51, 18, 46, 52, 13, + 46, 52, 8, 47, 53, 3, 47, 54, -1, 47, 55, -6, + 47, 56, -11, 48, 57, -15, 48, 58, -20, 48, 59, -24, + 49, 60, -29, 49, 62, -33, 49, 63, -37, 50, 64, -41, + 50, 66, -45, 51, 67, -49, 51, 69, -53, 52, 70, -57, + 52, 72, -60, 53, 73, -65, 53, 75, -68, 54, 77, -72, + 46, 46, 57, 46, 46, 54, 46, 46, 52, 46, 46, 49, + 46, 47, 45, 46, 47, 41, 46, 47, 37, 46, 48, 32, + 46, 48, 28, 46, 49, 23, 47, 49, 18, 47, 50, 14, + 47, 51, 9, 47, 51, 4, 47, 52, 0, 48, 53, -5, + 48, 54, -10, 48, 55, -14, 49, 56, -19, 49, 58, -23, + 49, 59, -28, 50, 60, -32, 50, 61, -36, 50, 63, -40, + 51, 64, -44, 51, 66, -48, 52, 67, -52, 52, 69, -56, + 53, 70, -60, 53, 72, -64, 54, 74, -67, 54, 75, -71, + 46, 44, 57, 46, 44, 55, 46, 44, 53, 47, 45, 49, + 47, 45, 46, 47, 45, 42, 47, 45, 38, 47, 46, 33, + 47, 46, 29, 47, 47, 24, 47, 47, 19, 47, 48, 15, + 48, 49, 10, 48, 50, 5, 48, 50, 0, 48, 51, -4, + 49, 53, -9, 49, 54, -13, 49, 55, -18, 49, 56, -22, + 50, 57, -27, 50, 58, -31, 51, 60, -35, 51, 61, -39, + 51, 62, -43, 52, 64, -47, 52, 66, -51, 53, 67, -55, + 53, 69, -59, 54, 70, -63, 54, 72, -67, 55, 74, -70, + 47, 42, 57, 47, 42, 55, 47, 42, 53, 47, 43, 50, + 47, 43, 47, 47, 43, 43, 47, 43, 39, 48, 44, 34, + 48, 44, 29, 48, 45, 25, 48, 46, 20, 48, 46, 16, + 48, 47, 11, 48, 48, 6, 49, 49, 1, 49, 50, -3, + 49, 51, -8, 50, 52, -12, 50, 53, -17, 50, 54, -21, + 51, 55, -26, 51, 57, -30, 51, 58, -34, 52, 59, -38, + 52, 61, -42, 52, 62, -46, 53, 64, -50, 53, 65, -54, + 54, 67, -58, 54, 69, -62, 55, 70, -66, 55, 72, -69, + 48, 40, 58, 48, 40, 56, 48, 41, 54, 48, 41, 51, + 48, 41, 47, 48, 41, 44, 48, 42, 40, 48, 42, 35, + 48, 42, 30, 49, 43, 26, 49, 44, 21, 49, 44, 17, + 49, 45, 12, 49, 46, 7, 49, 47, 3, 50, 48, -2, + 50, 49, -7, 50, 50, -11, 51, 51, -16, 51, 52, -20, + 51, 54, -25, 52, 55, -29, 52, 56, -33, 52, 58, -37, + 53, 59, -41, 53, 61, -45, 54, 62, -49, 54, 64, -53, + 54, 65, -57, 55, 67, -61, 56, 69, -65, 56, 70, -68, + 49, 38, 58, 49, 38, 56, 49, 39, 54, 49, 39, 51, + 49, 39, 48, 49, 39, 44, 49, 40, 40, 49, 40, 36, + 49, 41, 31, 49, 41, 27, 49, 42, 22, 50, 42, 18, + 50, 43, 13, 50, 44, 8, 50, 45, 4, 50, 46, -1, + 51, 47, -6, 51, 48, -10, 51, 49, -15, 52, 50, -19, + 52, 52, -24, 52, 53, -28, 53, 54, -32, 53, 56, -36, + 53, 57, -40, 54, 59, -44, 54, 60, -48, 55, 62, -52, + 55, 64, -56, 56, 65, -60, 56, 67, -64, 57, 69, -67, + 49, 36, 59, 49, 36, 57, 49, 37, 55, 50, 37, 52, + 50, 37, 49, 50, 37, 45, 50, 38, 41, 50, 38, 37, + 50, 38, 32, 50, 39, 28, 50, 40, 23, 50, 40, 19, + 51, 41, 14, 51, 42, 9, 51, 43, 5, 51, 44, 0, + 51, 45, -5, 52, 46, -9, 52, 47, -13, 52, 48, -18, + 53, 50, -22, 53, 51, -27, 53, 52, -31, 54, 54, -35, + 54, 55, -39, 54, 57, -43, 55, 59, -47, 55, 60, -51, + 56, 62, -55, 56, 64, -59, 57, 65, -63, 57, 67, -66, + 50, 34, 59, 50, 34, 57, 50, 34, 55, 50, 35, 53, + 50, 35, 50, 50, 35, 46, 51, 36, 42, 51, 36, 37, + 51, 36, 33, 51, 37, 29, 51, 38, 24, 51, 38, 20, + 51, 39, 15, 52, 40, 10, 52, 41, 6, 52, 42, 1, + 52, 43, -4, 52, 44, -8, 53, 45, -12, 53, 46, -17, + 53, 48, -21, 54, 49, -25, 54, 51, -30, 54, 52, -34, + 55, 53, -38, 55, 55, -42, 56, 57, -46, 56, 58, -50, + 56, 60, -54, 57, 62, -58, 57, 63, -62, 58, 65, -65, + 51, 32, 60, 51, 32, 58, 51, 32, 56, 51, 33, 53, + 51, 33, 50, 51, 33, 47, 51, 33, 43, 51, 34, 38, + 52, 34, 34, 52, 35, 30, 52, 36, 25, 52, 36, 21, + 52, 37, 16, 52, 38, 11, 53, 39, 7, 53, 40, 3, + 53, 41, -2, 53, 42, -7, 53, 43, -11, 54, 44, -15, + 54, 46, -20, 54, 47, -24, 55, 49, -28, 55, 50, -32, + 55, 51, -36, 56, 53, -41, 56, 55, -45, 57, 56, -49, + 57, 58, -53, 58, 60, -57, 58, 62, -60, 59, 63, -64, + 52, 30, 60, 52, 30, 58, 52, 30, 57, 52, 30, 54, + 52, 31, 51, 52, 31, 48, 52, 31, 44, 52, 32, 39, + 52, 32, 35, 53, 33, 31, 53, 33, 27, 53, 34, 22, + 53, 35, 17, 53, 36, 13, 53, 37, 8, 54, 38, 4, + 54, 39, -1, 54, 40, -6, 54, 41, -10, 55, 42, -14, + 55, 44, -19, 55, 45, -23, 56, 46, -27, 56, 48, -31, + 56, 49, -35, 57, 51, -40, 57, 53, -44, 57, 54, -48, + 58, 56, -51, 58, 58, -56, 59, 60, -59, 59, 61, -63, + 53, 28, 61, 53, 28, 59, 53, 28, 57, 53, 28, 55, + 53, 29, 52, 53, 29, 49, 53, 29, 45, 53, 30, 40, + 53, 30, 36, 53, 31, 32, 54, 31, 28, 54, 32, 23, + 54, 33, 18, 54, 34, 14, 54, 35, 9, 54, 36, 5, + 55, 37, 0, 55, 38, -4, 55, 39, -9, 55, 40, -13, + 56, 42, -18, 56, 43, -22, 56, 44, -26, 57, 46, -30, + 57, 47, -34, 57, 49, -39, 58, 51, -42, 58, 52, -46, + 59, 54, -50, 59, 56, -54, 60, 58, -58, 60, 59, -62, + 54, 26, 61, 54, 26, 60, 54, 26, 58, 54, 26, 55, + 54, 26, 53, 54, 27, 49, 54, 27, 46, 54, 27, 41, + 54, 28, 37, 54, 28, 33, 54, 29, 29, 55, 30, 24, + 55, 31, 19, 55, 31, 15, 55, 32, 11, 55, 33, 6, + 56, 35, 1, 56, 36, -3, 56, 37, -7, 56, 38, -12, + 57, 40, -16, 57, 41, -21, 57, 42, -25, 57, 44, -29, + 58, 45, -33, 58, 47, -37, 59, 49, -41, 59, 50, -45, + 59, 52, -49, 60, 54, -53, 60, 56, -57, 61, 57, -61, + 55, 24, 62, 55, 24, 60, 55, 24, 59, 55, 24, 56, + 55, 24, 53, 55, 24, 50, 55, 25, 47, 55, 25, 42, + 55, 26, 38, 55, 26, 34, 55, 27, 30, 55, 28, 26, + 56, 28, 21, 56, 29, 16, 56, 30, 12, 56, 31, 7, + 56, 32, 3, 57, 34, -2, 57, 35, -6, 57, 36, -10, + 57, 37, -15, 58, 39, -19, 58, 40, -23, 58, 42, -28, + 59, 43, -32, 59, 45, -36, 59, 47, -40, 60, 48, -44, + 60, 50, -48, 61, 52, -52, 61, 54, -56, 62, 55, -59, + 56, 21, 62, 56, 21, 61, 56, 22, 59, 56, 22, 57, + 56, 22, 54, 56, 22, 51, 56, 23, 48, 56, 23, 43, + 56, 24, 39, 56, 24, 35, 56, 25, 31, 56, 25, 27, + 57, 26, 22, 57, 27, 17, 57, 28, 13, 57, 29, 9, + 57, 30, 4, 57, 31, -1, 58, 33, -5, 58, 34, -9, + 58, 35, -14, 59, 37, -18, 59, 38, -22, 59, 40, -26, + 60, 41, -30, 60, 43, -35, 60, 44, -39, 61, 46, -43, + 61, 48, -46, 61, 50, -51, 62, 52, -54, 62, 53, -58, + 57, 19, 63, 57, 19, 62, 57, 19, 60, 57, 20, 58, + 57, 20, 55, 57, 20, 52, 57, 20, 49, 57, 21, 44, + 57, 21, 41, 57, 22, 36, 57, 23, 32, 57, 23, 28, + 57, 24, 23, 58, 25, 19, 58, 26, 14, 58, 27, 10, + 58, 28, 5, 58, 29, 1, 59, 30, -3, 59, 32, -8, + 59, 33, -12, 59, 34, -17, 60, 36, -21, 60, 37, -25, + 60, 39, -29, 61, 41, -33, 61, 42, -37, 61, 44, -41, + 62, 46, -45, 62, 48, -49, 63, 49, -53, 63, 51, -57, + 58, 17, 64, 58, 17, 62, 58, 17, 61, 58, 17, 59, + 58, 18, 56, 58, 18, 53, 58, 18, 50, 58, 19, 46, + 58, 19, 42, 58, 20, 38, 58, 20, 33, 58, 21, 29, + 58, 22, 24, 59, 23, 20, 59, 24, 16, 59, 25, 11, + 59, 26, 6, 59, 27, 2, 60, 28, -2, 60, 29, -6, + 60, 31, -11, 60, 32, -15, 61, 34, -19, 61, 35, -24, + 61, 37, -28, 62, 39, -32, 62, 40, -36, 62, 42, -40, + 63, 44, -44, 63, 46, -48, 64, 47, -52, 64, 49, -56, + 59, 14, 64, 59, 14, 63, 59, 14, 62, 59, 15, 60, + 59, 15, 57, 59, 15, 54, 59, 15, 51, 59, 16, 47, + 59, 16, 43, 59, 17, 39, 59, 18, 35, 59, 18, 31, + 60, 19, 26, 60, 20, 22, 60, 21, 17, 60, 22, 13, + 60, 23, 8, 60, 24, 4, 61, 25, 0, 61, 27, -5, + 61, 28, -9, 61, 29, -14, 62, 31, -18, 62, 32, -22, + 62, 34, -26, 63, 36, -30, 63, 37, -34, 63, 39, -38, + 64, 41, -42, 64, 43, -46, 65, 45, -50, 65, 46, -54, + 60, 12, 65, 60, 12, 64, 60, 12, 63, 60, 12, 60, + 60, 13, 58, 60, 13, 55, 60, 13, 52, 60, 14, 48, + 60, 14, 44, 60, 15, 40, 60, 15, 36, 60, 16, 32, + 61, 17, 27, 61, 18, 23, 61, 19, 19, 61, 20, 14, + 61, 21, 10, 61, 22, 5, 62, 23, 1, 62, 24, -3, + 62, 26, -8, 62, 27, -12, 63, 29, -16, 63, 30, -20, + 63, 32, -24, 64, 34, -29, 64, 35, -33, 64, 37, -37, + 65, 39, -41, 65, 41, -45, 65, 42, -49, 66, 44, -53, + 61, 10, 66, 61, 10, 65, 61, 10, 63, 61, 10, 61, + 61, 10, 59, 61, 11, 56, 61, 11, 53, 61, 11, 49, + 61, 12, 45, 61, 13, 41, 61, 13, 37, 61, 14, 33, + 62, 15, 28, 62, 16, 24, 62, 16, 20, 62, 17, 16, + 62, 19, 11, 62, 20, 7, 63, 21, 2, 63, 22, -2, + 63, 24, -7, 63, 25, -11, 64, 27, -15, 64, 28, -19, + 64, 30, -23, 65, 31, -28, 65, 33, -32, 65, 35, -35, + 66, 36, -39, 66, 38, -44, 66, 40, -47, 67, 42, -51, + 62, 8, 67, 62, 8, 65, 62, 8, 64, 62, 8, 62, + 62, 8, 60, 62, 8, 57, 62, 9, 54, 62, 9, 50, + 62, 10, 46, 62, 10, 43, 62, 11, 39, 62, 12, 34, + 63, 12, 30, 63, 13, 26, 63, 14, 21, 63, 15, 17, + 63, 16, 12, 63, 18, 8, 64, 19, 4, 64, 20, 0, + 64, 21, -5, 64, 23, -9, 65, 24, -13, 65, 26, -18, + 65, 27, -22, 65, 29, -26, 66, 31, -30, 66, 33, -34, + 66, 34, -38, 67, 36, -42, 67, 38, -46, 68, 40, -50, + 63, 5, 67, 63, 5, 66, 63, 6, 65, 63, 6, 63, + 63, 6, 61, 63, 6, 58, 63, 7, 55, 63, 7, 51, + 63, 8, 47, 63, 8, 44, 63, 9, 40, 63, 9, 36, + 64, 10, 31, 64, 11, 27, 64, 12, 23, 64, 13, 18, + 64, 14, 14, 64, 15, 9, 65, 16, 5, 65, 18, 1, + 65, 19, -4, 65, 21, -8, 66, 22, -12, 66, 24, -16, + 66, 25, -20, 66, 27, -25, 67, 29, -29, 67, 30, -33, + 67, 32, -36, 68, 34, -41, 68, 36, -45, 68, 38, -48, + 64, 3, 68, 64, 3, 67, 64, 3, 66, 64, 4, 64, + 64, 4, 61, 64, 4, 59, 64, 4, 56, 64, 5, 52, + 64, 5, 49, 64, 6, 45, 64, 7, 41, 64, 7, 37, + 65, 8, 32, 65, 9, 28, 65, 10, 24, 65, 11, 20, + 65, 12, 15, 65, 13, 11, 66, 14, 7, 66, 16, 2, + 66, 17, -2, 66, 18, -6, 66, 20, -11, 67, 21, -15, + 67, 23, -19, 67, 25, -23, 68, 26, -27, 68, 28, -31, + 68, 30, -35, 69, 32, -39, 69, 34, -43, 69, 36, -47, + 65, 1, 69, 65, 1, 68, 65, 1, 66, 65, 1, 65, + 65, 2, 62, 65, 2, 60, 65, 2, 57, 65, 3, 53, + 65, 3, 50, 65, 4, 46, 65, 4, 42, 65, 5, 38, + 66, 6, 34, 66, 7, 30, 66, 8, 25, 66, 9, 21, + 66, 10, 16, 66, 11, 12, 67, 12, 8, 67, 13, 4, + 67, 15, -1, 67, 16, -5, 67, 18, -9, 68, 19, -13, + 68, 21, -17, 68, 22, -22, 69, 24, -26, 69, 26, -30, + 69, 28, -34, 70, 30, -38, 70, 31, -42, 70, 33, -46, + 66, -1, 69, 66, -1, 68, 66, -1, 67, 66, -1, 65, + 66, 0, 63, 66, 0, 61, 66, 0, 58, 66, 1, 54, + 66, 1, 51, 66, 2, 47, 66, 2, 43, 67, 3, 40, + 67, 4, 35, 67, 5, 31, 67, 5, 27, 67, 6, 23, + 67, 8, 18, 67, 9, 14, 68, 10, 9, 68, 11, 5, + 68, 13, 1, 68, 14, -3, 68, 15, -8, 69, 17, -12, + 69, 18, -16, 69, 20, -20, 70, 22, -24, 70, 24, -28, + 70, 25, -32, 71, 27, -36, 71, 29, -40, 71, 31, -44, + 67, -3, 70, 67, -3, 69, 67, -3, 68, 67, -3, 66, + 67, -3, 64, 67, -2, 62, 67, -2, 59, 67, -2, 55, + 67, -1, 52, 67, -1, 48, 67, 0, 45, 68, 1, 41, + 68, 2, 36, 68, 2, 32, 68, 3, 28, 68, 4, 24, + 68, 5, 19, 68, 6, 15, 69, 8, 11, 69, 9, 7, + 69, 10, 2, 69, 12, -2, 69, 13, -6, 70, 15, -10, + 70, 16, -14, 70, 18, -19, 71, 20, -23, 71, 21, -27, + 71, 23, -31, 71, 25, -35, 72, 27, -39, 72, 29, -43, + 68, -5, 71, 68, -5, 70, 68, -5, 69, 68, -5, 67, + 68, -5, 65, 68, -4, 63, 68, -4, 60, 68, -4, 57, + 68, -3, 53, 68, -3, 50, 69, -2, 46, 69, -1, 42, + 69, -1, 38, 69, 0, 34, 69, 1, 29, 69, 2, 25, + 69, 3, 21, 69, 4, 17, 70, 5, 12, 70, 7, 8, + 70, 8, 4, 70, 10, -1, 70, 11, -5, 71, 12, -9, + 71, 14, -13, 71, 16, -17, 72, 17, -21, 72, 19, -25, + 72, 21, -29, 72, 23, -34, 73, 25, -37, 73, 27, -41, + 69, -7, 72, 69, -7, 71, 69, -7, 70, 69, -7, 68, + 69, -7, 66, 69, -7, 64, 69, -6, 61, 69, -6, 58, + 69, -5, 54, 70, -5, 51, 70, -4, 47, 70, -4, 43, + 70, -3, 39, 70, -2, 35, 70, -1, 31, 70, 0, 27, + 70, 1, 22, 70, 2, 18, 71, 3, 14, 71, 5, 10, + 71, 6, 5, 71, 7, 1, 71, 9, -3, 72, 10, -7, + 72, 12, -11, 72, 14, -16, 73, 15, -20, 73, 17, -24, + 73, 19, -28, 73, 21, -32, 74, 23, -36, 74, 24, -40, + 70, -10, 72, 70, -9, 72, 70, -9, 70, 70, -9, 69, + 70, -9, 67, 70, -9, 65, 70, -8, 62, 70, -8, 59, + 71, -7, 55, 71, -7, 52, 71, -6, 48, 71, -6, 45, + 71, -5, 40, 71, -4, 36, 71, -3, 32, 71, -2, 28, + 71, -1, 24, 72, 0, 19, 72, 1, 15, 72, 2, 11, + 72, 4, 7, 72, 5, 2, 73, 7, -2, 73, 8, -6, + 73, 10, -10, 73, 11, -14, 74, 13, -18, 74, 15, -22, + 74, 17, -26, 74, 19, -30, 75, 20, -34, 75, 22, -38, + 71, -12, 73, 71, -12, 72, 71, -11, 71, 71, -11, 70, + 71, -11, 68, 71, -11, 66, 71, -10, 63, 72, -10, 60, + 72, -10, 57, 72, -9, 53, 72, -8, 50, 72, -8, 46, + 72, -7, 42, 72, -6, 38, 72, -5, 34, 72, -4, 30, + 72, -3, 25, 73, -2, 21, 73, -1, 17, 73, 0, 13, + 73, 2, 8, 73, 3, 4, 74, 4, 0, 74, 6, -4, + 74, 7, -8, 74, 9, -13, 75, 11, -17, 75, 13, -21, + 75, 14, -25, 75, 16, -29, 76, 18, -33, 76, 20, -37, + 72, -14, 74, 72, -14, 73, 72, -13, 72, 72, -13, 71, + 72, -13, 69, 73, -13, 67, 73, -13, 64, 73, -12, 61, + 73, -12, 58, 73, -11, 54, 73, -11, 51, 73, -10, 47, + 73, -9, 43, 73, -8, 39, 73, -7, 35, 73, -6, 31, + 74, -5, 26, 74, -4, 22, 74, -3, 18, 74, -2, 14, + 74, 0, 10, 74, 1, 5, 75, 2, 1, 75, 4, -3, + 75, 5, -7, 75, 7, -11, 76, 9, -15, 76, 10, -19, + 76, 12, -23, 76, 14, -27, 77, 16, -31, 77, 18, -35, + 74, -16, 75, 74, -16, 74, 74, -16, 73, 74, -15, 71, + 74, -15, 70, 74, -15, 68, 74, -15, 65, 74, -14, 62, + 74, -14, 59, 74, -13, 56, 74, -13, 52, 74, -12, 48, + 74, -11, 44, 74, -10, 40, 74, -9, 36, 74, -9, 32, + 75, -7, 28, 75, -6, 24, 75, -5, 20, 75, -4, 16, + 75, -3, 11, 75, -1, 7, 76, 0, 3, 76, 2, -1, + 76, 3, -5, 76, 5, -10, 77, 7, -14, 77, 8, -18, + 77, 10, -22, 77, 12, -26, 78, 14, -30, 78, 16, -34, + 75, -18, 76, 75, -18, 75, 75, -18, 74, 75, -17, 72, + 75, -17, 71, 75, -17, 69, 75, -17, 66, 75, -16, 63, + 75, -16, 60, 75, -15, 57, 75, -15, 53, 75, -14, 50, + 75, -13, 45, 75, -12, 42, 75, -12, 38, 75, -11, 34, + 76, -10, 29, 76, -8, 25, 76, -7, 21, 76, -6, 17, + 76, -5, 13, 76, -3, 8, 77, -2, 4, 77, -1, 0, + 77, 1, -4, 77, 3, -8, 78, 4, -12, 78, 6, -16, + 78, 8, -20, 78, 10, -24, 79, 12, -28, 79, 13, -32, + 76, -20, 76, 76, -20, 76, 76, -20, 75, 76, -19, 73, + 76, -19, 72, 76, -19, 70, 76, -19, 67, 76, -18, 64, + 76, -18, 61, 76, -17, 58, 76, -17, 54, 76, -16, 51, + 76, -15, 47, 76, -14, 43, 76, -14, 39, 77, -13, 35, + 77, -12, 31, 77, -11, 27, 77, -9, 23, 77, -8, 19, + 77, -7, 14, 78, -5, 10, 78, -4, 6, 78, -3, 2, + 78, -1, -2, 78, 1, -7, 79, 2, -11, 79, 4, -15, + 79, 6, -18, 79, 8, -23, 80, 9, -27, 80, 11, -31, + 77, -22, 77, 77, -22, 76, 77, -21, 76, 77, -21, 74, + 77, -21, 72, 77, -21, 70, 77, -21, 68, 77, -20, 65, + 77, -20, 62, 77, -19, 59, 77, -19, 56, 77, -18, 52, + 77, -17, 48, 77, -16, 44, 78, -16, 40, 78, -15, 37, + 78, -14, 32, 78, -13, 28, 78, -11, 24, 78, -10, 20, + 78, -9, 16, 79, -8, 11, 79, -6, 7, 79, -5, 3, + 79, -3, -1, 79, -2, -5, 80, 0, -9, 80, 2, -13, + 80, 3, -17, 80, 5, -21, 81, 7, -25, 81, 9, -29, + 78, -24, 78, 78, -24, 77, 78, -23, 76, 78, -23, 75, + 78, -23, 73, 78, -23, 71, 78, -23, 69, 78, -22, 66, + 78, -22, 63, 78, -21, 60, 78, -21, 57, 78, -20, 53, + 78, -19, 49, 79, -18, 46, 79, -18, 42, 79, -17, 38, + 79, -16, 34, 79, -15, 30, 79, -14, 26, 79, -12, 22, + 79, -11, 17, 80, -10, 13, 80, -8, 9, 80, -7, 5, + 80, -5, 1, 80, -4, -4, 81, -2, -7, 81, 0, -11, + 81, 1, -15, 81, 3, -20, 82, 5, -24, 82, 7, -27, + 79, -26, 79, 79, -25, 78, 79, -25, 77, 79, -25, 76, + 79, -25, 74, 79, -25, 72, 79, -24, 70, 79, -24, 67, + 79, -24, 64, 79, -23, 61, 79, -23, 58, 79, -22, 55, + 80, -21, 51, 80, -20, 47, 80, -20, 43, 80, -19, 39, + 80, -18, 35, 80, -17, 31, 80, -16, 27, 80, -14, 23, + 81, -13, 19, 81, -12, 14, 81, -10, 10, 81, -9, 6, + 81, -7, 2, 82, -6, -2, 82, -4, -6, 82, -2, -10, + 82, -1, -14, 82, 1, -18, 83, 3, -22, 83, 5, -26, + 80, -27, 80, 80, -27, 79, 80, -27, 78, 80, -27, 77, + 80, -27, 75, 80, -27, 73, 80, -26, 71, 80, -26, 68, + 80, -26, 66, 80, -25, 63, 80, -25, 59, 81, -24, 56, + 81, -23, 52, 81, -22, 48, 81, -22, 45, 81, -21, 41, + 81, -20, 36, 81, -19, 32, 81, -18, 28, 81, -16, 24, + 82, -15, 20, 82, -14, 16, 82, -12, 12, 82, -11, 8, + 82, -10, 4, 83, -8, 0, 83, -6, -4, 83, -5, -8, + 83, -3, -12, 84, -1, -17, 84, 1, -20, 84, 3, -24, + 82, -30, 81, 82, -30, 80, 82, -30, 79, 82, -29, 78, + 82, -29, 76, 82, -29, 75, 82, -29, 72, 82, -28, 70, + 82, -28, 67, 82, -27, 64, 82, -27, 61, 82, -26, 57, + 82, -26, 54, 82, -25, 50, 82, -24, 46, 82, -23, 42, + 82, -22, 38, 83, -21, 34, 83, -20, 30, 83, -19, 26, + 83, -18, 22, 83, -16, 18, 83, -15, 14, 83, -14, 10, + 84, -12, 6, 84, -10, 2, 84, -9, -2, 84, -7, -6, + 85, -6, -10, 85, -4, -15, 85, -2, -19, 85, 0, -22, + 83, -32, 82, 83, -32, 81, 83, -32, 80, 83, -31, 79, + 83, -31, 77, 83, -31, 76, 83, -31, 73, 83, -30, 71, + 83, -30, 68, 83, -29, 65, 83, -29, 62, 83, -28, 59, + 83, -27, 55, 83, -27, 51, 83, -26, 48, 83, -25, 44, + 84, -24, 40, 84, -23, 36, 84, -22, 32, 84, -21, 28, + 84, -20, 23, 84, -18, 19, 84, -17, 15, 85, -16, 11, + 85, -14, 7, 85, -12, 3, 85, -11, -1, 85, -9, -5, + 86, -8, -9, 86, -6, -13, 86, -4, -17, 86, -2, -21, + 84, -34, 82, 84, -33, 82, 84, -33, 81, 84, -33, 80, + 84, -33, 78, 84, -33, 77, 84, -32, 74, 84, -32, 72, + 84, -32, 69, 84, -31, 66, 84, -31, 63, 84, -30, 60, + 84, -29, 56, 84, -29, 53, 84, -28, 49, 84, -27, 45, + 85, -26, 41, 85, -25, 37, 85, -24, 33, 85, -23, 29, + 85, -21, 25, 85, -20, 21, 85, -19, 17, 86, -18, 13, + 86, -16, 9, 86, -14, 5, 86, -13, 1, 86, -11, -3, + 87, -10, -7, 87, -8, -12, 87, -6, -15, 87, -4, -19, + 85, -35, 83, 85, -35, 83, 85, -35, 82, 85, -35, 81, + 85, -35, 79, 85, -35, 78, 85, -34, 75, 85, -34, 73, + 85, -34, 70, 85, -33, 67, 85, -33, 64, 85, -32, 61, + 85, -31, 57, 85, -31, 54, 86, -30, 50, 86, -29, 47, + 86, -28, 42, 86, -27, 38, 86, -26, 35, 86, -25, 31, + 86, -23, 26, 86, -22, 22, 87, -21, 18, 87, -20, 15, + 87, -18, 11, 87, -16, 6, 87, -15, 2, 88, -13, -2, + 88, -12, -6, 88, -10, -10, 88, -8, -14, 88, -6, -18, + 86, -37, 84, 86, -37, 83, 86, -37, 83, 86, -37, 82, + 86, -37, 80, 86, -36, 78, 86, -36, 76, 86, -36, 74, + 86, -35, 71, 86, -35, 69, 86, -34, 66, 86, -34, 62, + 86, -33, 59, 87, -32, 55, 87, -32, 52, 87, -31, 48, + 87, -30, 44, 87, -29, 40, 87, -28, 36, 87, -27, 32, + 87, -25, 28, 87, -24, 24, 88, -23, 20, 88, -21, 16, + 88, -20, 12, 88, -18, 8, 88, -17, 4, 89, -15, 0, + 89, -14, -4, 89, -12, -8, 89, -10, -12, 89, -8, -16, + 87, -39, 85, 87, -39, 84, 87, -39, 84, 87, -39, 82, + 87, -38, 81, 87, -38, 79, 87, -38, 77, 87, -38, 75, + 87, -37, 72, 87, -37, 70, 87, -36, 67, 87, -36, 64, + 88, -35, 60, 88, -34, 56, 88, -33, 53, 88, -33, 49, + 88, -32, 45, 88, -31, 41, 88, -30, 37, 88, -29, 34, + 88, -27, 29, 89, -26, 25, 89, -25, 21, 89, -23, 18, + 89, -22, 14, 89, -20, 9, 89, -19, 5, 90, -17, 1, + 90, -16, -2, 90, -14, -7, 90, -12, -11, 91, -10, -15, + 88, -41, 86, 88, -41, 85, 88, -41, 84, 88, -40, 83, + 88, -40, 82, 88, -40, 80, 88, -40, 78, 88, -39, 76, + 88, -39, 74, 88, -39, 71, 89, -38, 68, 89, -37, 65, + 89, -37, 61, 89, -36, 58, 89, -35, 54, 89, -34, 51, + 89, -33, 46, 89, -33, 43, 89, -32, 39, 89, -30, 35, + 90, -29, 31, 90, -28, 27, 90, -27, 23, 90, -25, 19, + 90, -24, 15, 90, -22, 11, 91, -21, 7, 91, -19, 3, + 91, -18, -1, 91, -16, -5, 91, -14, -9, 92, -12, -13, + 89, -42, 87, 89, -42, 86, 89, -42, 85, 89, -42, 84, + 89, -42, 83, 89, -42, 81, 89, -41, 79, 89, -41, 77, + 90, -41, 75, 90, -40, 72, 90, -40, 69, 90, -39, 66, + 90, -39, 62, 90, -38, 59, 90, -37, 56, 90, -36, 52, + 90, -35, 48, 90, -34, 44, 90, -33, 40, 90, -32, 37, + 91, -31, 32, 91, -30, 28, 91, -29, 24, 91, -27, 21, + 91, -26, 17, 91, -24, 12, 92, -23, 8, 92, -21, 5, + 92, -20, 1, 92, -18, -4, 92, -16, -8, 93, -14, -11, + 90, -44, 88, 90, -44, 87, 90, -44, 86, 91, -44, 85, + 91, -44, 84, 91, -44, 82, 91, -43, 80, 91, -43, 78, + 91, -42, 76, 91, -42, 73, 91, -42, 70, 91, -41, 67, + 91, -40, 64, 91, -40, 60, 91, -39, 57, 91, -38, 53, + 91, -37, 49, 91, -36, 46, 91, -35, 42, 92, -34, 38, + 92, -33, 34, 92, -32, 30, 92, -30, 26, 92, -29, 22, + 92, -28, 18, 92, -26, 14, 93, -25, 10, 93, -23, 6, + 93, -22, 2, 93, -20, -2, 93, -18, -6, 94, -16, -10, + 92, -46, 88, 92, -46, 88, 92, -46, 87, 92, -46, 86, + 92, -45, 85, 92, -45, 83, 92, -45, 81, 92, -45, 79, + 92, -44, 77, 92, -44, 74, 92, -43, 71, 92, -43, 68, + 92, -42, 65, 92, -41, 62, 92, -41, 58, 92, -40, 55, + 92, -39, 51, 92, -38, 47, 93, -37, 43, 93, -36, 39, + 93, -35, 35, 93, -34, 31, 93, -32, 27, 93, -31, 24, + 93, -30, 20, 94, -28, 15, 94, -27, 11, 94, -25, 8, + 94, -24, 4, 94, -22, -1, 95, -20, -4, 95, -18, -8, + 93, -48, 89, 93, -48, 89, 93, -47, 88, 93, -47, 87, + 93, -47, 86, 93, -47, 84, 93, -47, 82, 93, -46, 80, + 93, -46, 78, 93, -46, 75, 93, -45, 73, 93, -44, 70, + 93, -44, 66, 93, -43, 63, 93, -42, 59, 93, -42, 56, + 93, -41, 52, 94, -40, 48, 94, -39, 45, 94, -38, 41, + 94, -37, 37, 94, -35, 33, 94, -34, 29, 94, -33, 25, + 94, -32, 21, 95, -30, 17, 95, -29, 13, 95, -27, 9, + 95, -26, 5, 95, -24, 1, 96, -22, -3, 96, -20, -7, + 41, 66, 55, 41, 66, 53, 41, 66, 50, 41, 66, 46, + 41, 66, 42, 41, 67, 37, 41, 67, 32, 41, 67, 27, + 42, 68, 22, 42, 68, 17, 42, 69, 13, 42, 69, 8, + 42, 70, 2, 43, 70, -2, 43, 71, -7, 43, 72, -11, + 44, 73, -16, 44, 74, -21, 44, 74, -25, 45, 75, -29, + 45, 77, -34, 45, 78, -38, 46, 79, -42, 46, 80, -46, + 47, 81, -50, 47, 82, -54, 48, 83, -58, 48, 85, -62, + 49, 86, -66, 50, 87, -70, 50, 89, -73, 51, 90, -77, + 41, 66, 55, 41, 66, 53, 41, 66, 50, 41, 66, 46, + 41, 66, 42, 41, 66, 37, 41, 67, 33, 42, 67, 27, + 42, 67, 22, 42, 68, 18, 42, 68, 13, 42, 69, 8, + 43, 69, 3, 43, 70, -2, 43, 71, -7, 43, 71, -11, + 44, 72, -16, 44, 73, -21, 44, 74, -25, 45, 75, -29, + 45, 76, -34, 46, 77, -38, 46, 78, -42, 46, 79, -46, + 47, 81, -50, 48, 82, -54, 48, 83, -58, 49, 84, -62, + 49, 86, -65, 50, 87, -69, 50, 88, -73, 51, 90, -77, + 41, 65, 55, 41, 65, 53, 41, 65, 50, 41, 65, 46, + 41, 66, 42, 42, 66, 37, 42, 66, 33, 42, 66, 27, + 42, 67, 23, 42, 67, 18, 42, 68, 13, 42, 68, 8, + 43, 69, 3, 43, 70, -2, 43, 70, -6, 43, 71, -11, + 44, 72, -16, 44, 73, -20, 44, 74, -25, 45, 75, -29, + 45, 76, -34, 46, 77, -38, 46, 78, -42, 47, 79, -46, + 47, 80, -50, 48, 82, -54, 48, 83, -58, 49, 84, -62, + 49, 85, -65, 50, 87, -69, 50, 88, -73, 51, 90, -76, + 41, 65, 55, 41, 65, 53, 41, 65, 50, 42, 65, 46, + 42, 65, 42, 42, 65, 38, 42, 66, 33, 42, 66, 28, + 42, 66, 23, 42, 67, 18, 42, 67, 13, 43, 68, 8, + 43, 69, 3, 43, 69, -2, 43, 70, -6, 44, 71, -11, + 44, 72, -16, 44, 72, -20, 45, 73, -25, 45, 74, -29, + 45, 75, -34, 46, 77, -38, 46, 78, -42, 47, 79, -46, + 47, 80, -50, 48, 81, -54, 48, 82, -58, 49, 84, -61, + 49, 85, -65, 50, 87, -69, 51, 88, -73, 51, 89, -76, + 42, 64, 56, 42, 64, 53, 42, 64, 50, 42, 65, 46, + 42, 65, 42, 42, 65, 38, 42, 65, 33, 42, 66, 28, + 42, 66, 23, 42, 66, 18, 43, 67, 13, 43, 67, 9, + 43, 68, 3, 43, 69, -1, 43, 69, -6, 44, 70, -11, + 44, 71, -16, 44, 72, -20, 45, 73, -24, 45, 74, -29, + 46, 75, -33, 46, 76, -37, 46, 77, -41, 47, 78, -45, + 47, 79, -49, 48, 81, -54, 48, 82, -57, 49, 83, -61, + 49, 85, -65, 50, 86, -69, 51, 87, -72, 51, 89, -76, + 42, 64, 56, 42, 64, 53, 42, 64, 50, 42, 64, 46, + 42, 64, 42, 42, 64, 38, 42, 65, 33, 42, 65, 28, + 42, 65, 23, 43, 66, 18, 43, 66, 14, 43, 67, 9, + 43, 68, 3, 43, 68, -1, 44, 69, -6, 44, 70, -10, + 44, 71, -15, 45, 71, -20, 45, 72, -24, 45, 73, -28, + 46, 75, -33, 46, 76, -37, 47, 77, -41, 47, 78, -45, + 47, 79, -49, 48, 80, -53, 49, 82, -57, 49, 83, -61, + 50, 84, -65, 50, 86, -69, 51, 87, -72, 51, 88, -76, + 42, 63, 56, 42, 63, 53, 42, 63, 50, 42, 63, 47, + 42, 63, 43, 42, 64, 38, 42, 64, 34, 43, 64, 28, + 43, 65, 23, 43, 65, 19, 43, 66, 14, 43, 66, 9, + 43, 67, 4, 44, 68, -1, 44, 68, -5, 44, 69, -10, + 45, 70, -15, 45, 71, -19, 45, 72, -24, 46, 73, -28, + 46, 74, -33, 46, 75, -37, 47, 76, -41, 47, 77, -45, + 48, 78, -49, 48, 80, -53, 49, 81, -57, 49, 82, -61, + 50, 84, -64, 50, 85, -68, 51, 86, -72, 52, 88, -75, + 42, 62, 56, 42, 62, 53, 42, 62, 51, 42, 63, 47, + 42, 63, 43, 43, 63, 38, 43, 63, 34, 43, 64, 29, + 43, 64, 24, 43, 64, 19, 43, 65, 14, 43, 65, 9, + 44, 66, 4, 44, 67, 0, 44, 68, -5, 44, 68, -10, + 45, 69, -15, 45, 70, -19, 45, 71, -23, 46, 72, -28, + 46, 73, -32, 47, 74, -36, 47, 75, -40, 47, 77, -44, + 48, 78, -48, 48, 79, -53, 49, 80, -57, 49, 82, -60, + 50, 83, -64, 51, 85, -68, 51, 86, -72, 52, 87, -75, + 43, 61, 56, 43, 61, 53, 43, 62, 51, 43, 62, 47, + 43, 62, 43, 43, 62, 39, 43, 62, 34, 43, 63, 29, + 43, 63, 24, 43, 64, 19, 44, 64, 15, 44, 65, 10, + 44, 65, 5, 44, 66, 0, 44, 67, -5, 45, 67, -9, + 45, 68, -14, 45, 69, -19, 46, 70, -23, 46, 71, -27, + 46, 72, -32, 47, 73, -36, 47, 75, -40, 48, 76, -44, + 48, 77, -48, 49, 78, -52, 49, 80, -56, 50, 81, -60, + 50, 82, -64, 51, 84, -68, 51, 85, -71, 52, 87, -75, + 43, 60, 56, 43, 60, 54, 43, 61, 51, 43, 61, 47, + 43, 61, 43, 43, 61, 39, 43, 61, 35, 43, 62, 29, + 44, 62, 25, 44, 63, 20, 44, 63, 15, 44, 64, 10, + 44, 64, 5, 45, 65, 0, 45, 66, -4, 45, 67, -9, + 45, 68, -14, 46, 68, -18, 46, 69, -22, 46, 70, -27, + 47, 72, -31, 47, 73, -36, 48, 74, -40, 48, 75, -44, + 48, 76, -48, 49, 78, -52, 49, 79, -56, 50, 80, -59, + 51, 81, -63, 51, 83, -67, 52, 84, -71, 52, 86, -74, + 43, 59, 56, 43, 59, 54, 43, 59, 51, 43, 60, 48, + 44, 60, 44, 44, 60, 40, 44, 60, 35, 44, 61, 30, + 44, 61, 25, 44, 62, 20, 44, 62, 16, 44, 63, 11, + 45, 63, 6, 45, 64, 1, 45, 65, -4, 45, 66, -8, + 46, 67, -13, 46, 67, -18, 46, 68, -22, 47, 69, -26, + 47, 71, -31, 48, 72, -35, 48, 73, -39, 48, 74, -43, + 49, 75, -47, 49, 77, -51, 50, 78, -55, 50, 79, -59, + 51, 81, -63, 51, 82, -67, 52, 84, -70, 53, 85, -74, + 44, 58, 56, 44, 58, 54, 44, 58, 52, 44, 58, 48, + 44, 58, 44, 44, 59, 40, 44, 59, 36, 44, 59, 30, + 44, 60, 26, 45, 60, 21, 45, 61, 16, 45, 61, 12, + 45, 62, 6, 45, 63, 2, 46, 63, -3, 46, 64, -7, + 46, 65, -12, 46, 66, -17, 47, 67, -21, 47, 68, -25, + 48, 69, -30, 48, 70, -34, 48, 72, -38, 49, 73, -42, + 49, 74, -46, 50, 75, -51, 50, 77, -54, 51, 78, -58, + 51, 79, -62, 52, 81, -66, 52, 82, -70, 53, 84, -73, + 44, 57, 57, 44, 57, 54, 44, 57, 52, 44, 57, 48, + 44, 57, 45, 45, 57, 41, 45, 58, 36, 45, 58, 31, + 45, 58, 26, 45, 59, 22, 45, 59, 17, 45, 60, 12, + 46, 61, 7, 46, 61, 2, 46, 62, -2, 46, 63, -7, + 47, 64, -12, 47, 65, -16, 47, 66, -21, 48, 67, -25, + 48, 68, -30, 48, 69, -34, 49, 70, -38, 49, 72, -42, + 50, 73, -46, 50, 74, -50, 51, 76, -54, 51, 77, -58, + 52, 78, -61, 52, 80, -66, 53, 81, -69, 53, 83, -73, + 45, 55, 57, 45, 55, 55, 45, 55, 52, 45, 55, 49, + 45, 56, 45, 45, 56, 41, 45, 56, 37, 45, 57, 32, + 45, 57, 27, 46, 57, 22, 46, 58, 18, 46, 59, 13, + 46, 59, 8, 46, 60, 3, 47, 61, -2, 47, 62, -6, + 47, 63, -11, 47, 64, -15, 48, 65, -20, 48, 66, -24, + 48, 67, -29, 49, 68, -33, 49, 69, -37, 50, 70, -41, + 50, 72, -45, 51, 73, -49, 51, 74, -53, 51, 76, -57, + 52, 77, -61, 53, 79, -65, 53, 80, -69, 54, 82, -72, + 45, 54, 57, 45, 54, 55, 45, 54, 53, 45, 54, 49, + 46, 54, 46, 46, 54, 42, 46, 55, 37, 46, 55, 32, + 46, 56, 28, 46, 56, 23, 46, 57, 18, 46, 57, 14, + 47, 58, 8, 47, 59, 4, 47, 59, -1, 47, 60, -5, + 48, 61, -10, 48, 62, -15, 48, 63, -19, 49, 64, -23, + 49, 65, -28, 49, 67, -32, 50, 68, -36, 50, 69, -40, + 50, 70, -44, 51, 72, -49, 51, 73, -53, 52, 75, -56, + 52, 76, -60, 53, 78, -64, 54, 79, -68, 54, 81, -71, + 46, 52, 57, 46, 52, 55, 46, 52, 53, 46, 53, 50, + 46, 53, 46, 46, 53, 42, 46, 53, 38, 46, 54, 33, + 46, 54, 28, 47, 55, 24, 47, 55, 19, 47, 56, 14, + 47, 56, 9, 47, 57, 5, 48, 58, 0, 48, 59, -5, + 48, 60, -10, 48, 61, -14, 49, 62, -18, 49, 63, -23, + 49, 64, -27, 50, 65, -31, 50, 66, -36, 51, 68, -40, + 51, 69, -44, 51, 70, -48, 52, 72, -52, 52, 73, -56, + 53, 75, -59, 53, 76, -63, 54, 78, -67, 54, 79, -71, + 47, 51, 58, 47, 51, 56, 47, 51, 53, 47, 51, 50, + 47, 51, 47, 47, 51, 43, 47, 52, 38, 47, 52, 34, + 47, 52, 29, 47, 53, 24, 47, 53, 20, 47, 54, 15, + 48, 55, 10, 48, 56, 5, 48, 56, 1, 48, 57, -4, + 49, 58, -9, 49, 59, -13, 49, 60, -17, 50, 61, -22, + 50, 63, -26, 50, 64, -31, 51, 65, -35, 51, 66, -39, + 51, 68, -43, 52, 69, -47, 52, 70, -51, 53, 72, -55, + 53, 73, -59, 54, 75, -63, 54, 76, -66, 55, 78, -70, + 47, 49, 58, 47, 49, 56, 47, 49, 54, 47, 49, 51, + 47, 49, 47, 47, 50, 43, 47, 50, 39, 48, 50, 34, + 48, 51, 30, 48, 51, 25, 48, 52, 21, 48, 52, 16, + 48, 53, 11, 48, 54, 6, 49, 55, 2, 49, 56, -3, + 49, 57, -8, 50, 58, -12, 50, 59, -17, 50, 60, -21, + 51, 61, -26, 51, 62, -30, 51, 63, -34, 52, 65, -38, + 52, 66, -42, 52, 68, -46, 53, 69, -50, 53, 70, -54, + 54, 72, -58, 54, 74, -62, 55, 75, -66, 55, 77, -69, + 48, 47, 58, 48, 47, 56, 48, 47, 54, 48, 47, 51, + 48, 48, 48, 48, 48, 44, 48, 48, 40, 48, 49, 35, + 48, 49, 31, 48, 50, 26, 49, 50, 21, 49, 51, 17, + 49, 51, 12, 49, 52, 7, 49, 53, 3, 50, 54, -2, + 50, 55, -7, 50, 56, -11, 50, 57, -16, 51, 58, -20, + 51, 59, -25, 51, 61, -29, 52, 62, -33, 52, 63, -37, + 53, 64, -41, 53, 66, -45, 53, 67, -49, 54, 69, -53, + 54, 70, -57, 55, 72, -61, 55, 74, -65, 56, 75, -68, + 48, 45, 59, 49, 45, 57, 49, 45, 55, 49, 46, 52, + 49, 46, 48, 49, 46, 45, 49, 46, 41, 49, 47, 36, + 49, 47, 31, 49, 48, 27, 49, 48, 22, 49, 49, 18, + 50, 50, 13, 50, 50, 8, 50, 51, 4, 50, 52, -1, + 51, 53, -6, 51, 54, -10, 51, 55, -15, 51, 56, -19, + 52, 58, -24, 52, 59, -28, 52, 60, -32, 53, 61, -36, + 53, 63, -40, 54, 64, -44, 54, 66, -48, 55, 67, -52, + 55, 69, -56, 55, 70, -60, 56, 72, -64, 56, 74, -67, + 49, 43, 59, 49, 44, 57, 49, 44, 55, 49, 44, 52, + 49, 44, 49, 49, 44, 45, 49, 45, 41, 50, 45, 37, + 50, 45, 32, 50, 46, 28, 50, 46, 23, 50, 47, 19, + 50, 48, 14, 50, 49, 9, 51, 49, 4, 51, 50, 0, + 51, 51, -5, 51, 52, -9, 52, 53, -14, 52, 55, -18, + 52, 56, -23, 53, 57, -27, 53, 58, -31, 53, 60, -35, + 54, 61, -39, 54, 63, -44, 55, 64, -47, 55, 66, -51, + 56, 67, -55, 56, 69, -59, 57, 70, -63, 57, 72, -67, + 50, 41, 60, 50, 42, 58, 50, 42, 56, 50, 42, 53, + 50, 42, 50, 50, 42, 46, 50, 43, 42, 50, 43, 37, + 50, 43, 33, 51, 44, 29, 51, 45, 24, 51, 45, 20, + 51, 46, 15, 51, 47, 10, 51, 48, 6, 52, 48, 1, + 52, 50, -4, 52, 51, -8, 52, 52, -13, 53, 53, -17, + 53, 54, -22, 53, 55, -26, 54, 57, -30, 54, 58, -34, + 54, 59, -38, 55, 61, -42, 55, 62, -46, 56, 64, -50, + 56, 65, -54, 57, 67, -58, 57, 69, -62, 58, 70, -66, + 51, 40, 60, 51, 40, 58, 51, 40, 56, 51, 40, 53, + 51, 40, 50, 51, 40, 47, 51, 41, 43, 51, 41, 38, + 51, 42, 34, 51, 42, 30, 51, 43, 25, 52, 43, 21, + 52, 44, 16, 52, 45, 11, 52, 46, 7, 52, 47, 2, + 53, 48, -3, 53, 49, -7, 53, 50, -12, 53, 51, -16, + 54, 52, -21, 54, 53, -25, 54, 55, -29, 55, 56, -33, + 55, 57, -37, 56, 59, -41, 56, 61, -45, 56, 62, -49, + 57, 64, -53, 57, 65, -57, 58, 67, -61, 58, 69, -65, + 51, 38, 60, 52, 38, 59, 52, 38, 57, 52, 38, 54, + 52, 38, 51, 52, 38, 48, 52, 39, 44, 52, 39, 39, + 52, 40, 35, 52, 40, 31, 52, 41, 26, 52, 41, 22, + 53, 42, 17, 53, 43, 12, 53, 44, 8, 53, 45, 3, + 53, 46, -2, 54, 47, -6, 54, 48, -10, 54, 49, -15, + 54, 50, -19, 55, 52, -24, 55, 53, -28, 55, 54, -32, + 56, 56, -36, 56, 57, -40, 57, 59, -44, 57, 60, -48, + 57, 62, -52, 58, 64, -56, 58, 65, -60, 59, 67, -64, + 52, 35, 61, 52, 36, 59, 52, 36, 57, 52, 36, 55, + 52, 36, 52, 52, 36, 48, 53, 37, 45, 53, 37, 40, + 53, 38, 36, 53, 38, 32, 53, 39, 27, 53, 39, 23, + 53, 40, 18, 53, 41, 13, 54, 42, 9, 54, 43, 4, + 54, 44, -1, 54, 45, -5, 55, 46, -9, 55, 47, -14, + 55, 48, -18, 56, 50, -23, 56, 51, -27, 56, 52, -31, + 57, 54, -35, 57, 55, -39, 57, 57, -43, 58, 58, -47, + 58, 60, -51, 59, 62, -55, 59, 63, -59, 60, 65, -63, + 53, 33, 61, 53, 33, 60, 53, 34, 58, 53, 34, 55, + 53, 34, 53, 53, 34, 49, 53, 35, 45, 53, 35, 41, + 54, 35, 37, 54, 36, 33, 54, 37, 28, 54, 37, 24, + 54, 38, 19, 54, 39, 14, 54, 40, 10, 55, 41, 5, + 55, 42, 1, 55, 43, -4, 55, 44, -8, 56, 45, -12, + 56, 46, -17, 56, 48, -21, 57, 49, -25, 57, 50, -30, + 57, 52, -34, 58, 53, -38, 58, 55, -42, 58, 56, -46, + 59, 58, -50, 59, 60, -54, 60, 61, -58, 60, 63, -61, + 54, 31, 62, 54, 31, 60, 54, 31, 59, 54, 32, 56, + 54, 32, 53, 54, 32, 50, 54, 32, 46, 54, 33, 42, + 54, 33, 38, 55, 34, 34, 55, 34, 29, 55, 35, 25, + 55, 36, 20, 55, 37, 15, 55, 38, 11, 55, 38, 7, + 56, 40, 2, 56, 41, -3, 56, 42, -7, 56, 43, -11, + 57, 44, -16, 57, 46, -20, 57, 47, -24, 58, 48, -28, + 58, 50, -32, 58, 51, -37, 59, 53, -41, 59, 54, -45, + 60, 56, -49, 60, 58, -53, 61, 60, -57, 61, 61, -60, + 55, 29, 62, 55, 29, 61, 55, 29, 59, 55, 30, 57, + 55, 30, 54, 55, 30, 51, 55, 30, 47, 55, 31, 43, + 55, 31, 39, 55, 32, 35, 55, 32, 30, 56, 33, 26, + 56, 34, 21, 56, 35, 17, 56, 35, 12, 56, 36, 8, + 57, 37, 3, 57, 39, -1, 57, 40, -6, 57, 41, -10, + 58, 42, -15, 58, 43, -19, 58, 45, -23, 58, 46, -27, + 59, 48, -31, 59, 49, -36, 60, 51, -40, 60, 52, -44, + 60, 54, -47, 61, 56, -52, 61, 58, -55, 62, 59, -59, + 56, 27, 63, 56, 27, 62, 56, 27, 60, 56, 27, 58, + 56, 28, 55, 56, 28, 52, 56, 28, 48, 56, 29, 44, + 56, 29, 40, 56, 30, 36, 56, 30, 31, 57, 31, 27, + 57, 32, 22, 57, 32, 18, 57, 33, 13, 57, 34, 9, + 57, 35, 4, 58, 36, 0, 58, 38, -4, 58, 39, -9, + 58, 40, -14, 59, 41, -18, 59, 43, -22, 59, 44, -26, + 60, 46, -30, 60, 47, -34, 60, 49, -38, 61, 50, -42, + 61, 52, -46, 62, 54, -50, 62, 56, -54, 62, 57, -58, + 57, 25, 64, 57, 25, 62, 57, 25, 61, 57, 25, 58, + 57, 25, 56, 57, 26, 53, 57, 26, 49, 57, 26, 45, + 57, 27, 41, 57, 27, 37, 57, 28, 33, 57, 29, 28, + 58, 29, 23, 58, 30, 19, 58, 31, 15, 58, 32, 10, + 58, 33, 5, 58, 34, 1, 59, 35, -3, 59, 37, -7, + 59, 38, -12, 60, 39, -16, 60, 41, -21, 60, 42, -25, + 60, 43, -29, 61, 45, -33, 61, 47, -37, 62, 48, -41, + 62, 50, -45, 62, 52, -49, 63, 54, -53, 63, 55, -57, + 58, 23, 64, 58, 23, 63, 58, 23, 61, 58, 23, 59, + 58, 23, 56, 58, 24, 53, 58, 24, 50, 58, 24, 46, + 58, 25, 42, 58, 25, 38, 58, 26, 34, 58, 26, 29, + 58, 27, 25, 59, 28, 20, 59, 29, 16, 59, 30, 12, + 59, 31, 7, 59, 32, 2, 60, 33, -2, 60, 34, -6, + 60, 36, -11, 60, 37, -15, 61, 38, -19, 61, 40, -23, + 61, 41, -27, 62, 43, -32, 62, 45, -36, 62, 46, -40, + 63, 48, -44, 63, 50, -48, 64, 51, -52, 64, 53, -55, + 59, 20, 65, 59, 21, 63, 59, 21, 62, 59, 21, 60, + 59, 21, 57, 59, 21, 54, 59, 22, 51, 59, 22, 47, + 59, 23, 43, 59, 23, 39, 59, 24, 35, 59, 24, 31, + 59, 25, 26, 60, 26, 21, 60, 27, 17, 60, 28, 13, + 60, 29, 8, 60, 30, 4, 60, 31, -1, 61, 32, -5, + 61, 34, -10, 61, 35, -14, 62, 36, -18, 62, 38, -22, + 62, 39, -26, 63, 41, -31, 63, 43, -35, 63, 44, -38, + 64, 46, -42, 64, 48, -47, 64, 49, -50, 65, 51, -54, + 60, 18, 66, 60, 18, 64, 60, 18, 63, 60, 18, 61, + 60, 18, 58, 60, 19, 55, 60, 19, 52, 60, 19, 48, + 60, 20, 44, 60, 20, 40, 60, 21, 36, 60, 22, 32, + 61, 22, 27, 61, 23, 23, 61, 24, 19, 61, 25, 14, + 61, 26, 10, 61, 27, 5, 62, 28, 1, 62, 30, -3, + 62, 31, -8, 62, 32, -12, 63, 34, -16, 63, 35, -20, + 63, 37, -24, 64, 38, -29, 64, 40, -33, 64, 41, -37, + 65, 43, -41, 65, 45, -45, 65, 47, -49, 66, 49, -53, + 61, 16, 66, 61, 16, 65, 61, 16, 64, 61, 16, 62, + 61, 16, 59, 61, 16, 56, 61, 17, 53, 61, 17, 49, + 61, 18, 45, 61, 18, 42, 61, 19, 37, 61, 19, 33, + 61, 20, 29, 62, 21, 24, 62, 22, 20, 62, 23, 16, + 62, 24, 11, 62, 25, 7, 63, 26, 2, 63, 27, -2, + 63, 29, -7, 63, 30, -11, 64, 31, -15, 64, 33, -19, + 64, 34, -23, 64, 36, -28, 65, 38, -32, 65, 39, -35, + 65, 41, -39, 66, 43, -44, 66, 45, -48, 67, 46, -51, + 62, 13, 67, 62, 13, 66, 62, 14, 64, 62, 14, 62, + 62, 14, 60, 62, 14, 57, 62, 15, 54, 62, 15, 50, + 62, 15, 47, 62, 16, 43, 62, 17, 39, 62, 17, 35, + 62, 18, 30, 63, 19, 26, 63, 20, 21, 63, 21, 17, + 63, 22, 12, 63, 23, 8, 63, 24, 4, 64, 25, 0, + 64, 27, -5, 64, 28, -9, 64, 29, -14, 65, 31, -18, + 65, 32, -22, 65, 34, -26, 66, 35, -30, 66, 37, -34, + 66, 39, -38, 67, 41, -42, 67, 42, -46, 67, 44, -50, + 63, 11, 68, 63, 11, 66, 63, 11, 65, 63, 12, 63, + 63, 12, 61, 63, 12, 58, 63, 12, 55, 63, 13, 51, + 63, 13, 48, 63, 14, 44, 63, 14, 40, 63, 15, 36, + 63, 16, 31, 64, 17, 27, 64, 17, 23, 64, 18, 18, + 64, 20, 14, 64, 21, 9, 64, 22, 5, 65, 23, 1, + 65, 24, -4, 65, 26, -8, 65, 27, -12, 66, 28, -16, + 66, 30, -20, 66, 32, -25, 67, 33, -29, 67, 35, -33, + 67, 37, -37, 68, 39, -41, 68, 40, -45, 68, 42, -49, + 64, 9, 68, 64, 9, 67, 64, 9, 66, 64, 9, 64, + 64, 10, 62, 64, 10, 59, 64, 10, 56, 64, 11, 52, + 64, 11, 49, 64, 12, 45, 64, 12, 41, 64, 13, 37, + 64, 14, 32, 65, 14, 28, 65, 15, 24, 65, 16, 20, + 65, 17, 15, 65, 18, 11, 65, 20, 6, 66, 21, 2, + 66, 22, -2, 66, 23, -7, 66, 25, -11, 67, 26, -15, + 67, 28, -19, 67, 30, -23, 67, 31, -27, 68, 33, -31, + 68, 34, -35, 69, 36, -40, 69, 38, -43, 69, 40, -47, + 65, 7, 69, 65, 7, 68, 65, 7, 67, 65, 7, 65, + 65, 7, 63, 65, 8, 60, 65, 8, 57, 65, 8, 53, + 65, 9, 50, 65, 9, 46, 65, 10, 42, 65, 11, 38, + 65, 11, 34, 66, 12, 29, 66, 13, 25, 66, 14, 21, + 66, 15, 16, 66, 16, 12, 66, 17, 8, 67, 19, 4, + 67, 20, -1, 67, 21, -5, 67, 23, -9, 68, 24, -13, + 68, 26, -17, 68, 27, -22, 68, 29, -26, 69, 31, -30, + 69, 32, -34, 69, 34, -38, 70, 36, -42, 70, 38, -46, + 66, 5, 70, 66, 5, 69, 66, 5, 67, 66, 5, 66, + 66, 5, 63, 66, 5, 61, 66, 6, 58, 66, 6, 54, + 66, 7, 51, 66, 7, 47, 66, 8, 43, 66, 8, 39, + 66, 9, 35, 67, 10, 31, 67, 11, 27, 67, 12, 22, + 67, 13, 18, 67, 14, 13, 67, 15, 9, 68, 16, 5, + 68, 18, 0, 68, 19, -4, 68, 20, -8, 69, 22, -12, + 69, 23, -16, 69, 25, -21, 69, 27, -24, 70, 28, -28, + 70, 30, -32, 70, 32, -37, 71, 34, -41, 71, 36, -44, + 67, 2, 70, 67, 3, 69, 67, 3, 68, 67, 3, 66, + 67, 3, 64, 67, 3, 62, 67, 4, 59, 67, 4, 55, + 67, 5, 52, 67, 5, 48, 67, 6, 45, 67, 6, 41, + 67, 7, 36, 68, 8, 32, 68, 9, 28, 68, 10, 24, + 68, 11, 19, 68, 12, 15, 68, 13, 11, 69, 14, 7, + 69, 16, 2, 69, 17, -2, 69, 18, -6, 69, 20, -10, + 70, 21, -15, 70, 23, -19, 70, 24, -23, 71, 26, -27, + 71, 28, -31, 71, 30, -35, 72, 32, -39, 72, 33, -43, + 68, 0, 71, 68, 0, 70, 68, 1, 69, 68, 1, 67, + 68, 1, 65, 68, 1, 63, 68, 1, 60, 68, 2, 56, + 68, 2, 53, 68, 3, 50, 68, 3, 46, 68, 4, 42, + 68, 5, 37, 69, 6, 33, 69, 7, 29, 69, 7, 25, + 69, 9, 20, 69, 10, 16, 69, 11, 12, 70, 12, 8, + 70, 13, 3, 70, 15, -1, 70, 16, -5, 70, 17, -9, + 71, 19, -13, 71, 21, -18, 71, 22, -22, 72, 24, -26, + 72, 26, -29, 72, 28, -34, 73, 29, -38, 73, 31, -42, + 69, -2, 72, 69, -2, 71, 69, -2, 70, 69, -1, 68, + 69, -1, 66, 69, -1, 64, 69, -1, 61, 69, 0, 58, + 69, 0, 54, 69, 1, 51, 69, 1, 47, 69, 2, 43, + 70, 3, 39, 70, 4, 35, 70, 4, 31, 70, 5, 26, + 70, 6, 22, 70, 7, 18, 70, 9, 14, 71, 10, 9, + 71, 11, 5, 71, 12, 1, 71, 14, -3, 71, 15, -8, + 72, 17, -12, 72, 18, -16, 72, 20, -20, 73, 22, -24, + 73, 23, -28, 73, 25, -32, 73, 27, -36, 74, 29, -40, + 70, -4, 73, 70, -4, 72, 70, -4, 71, 70, -4, 69, + 70, -3, 67, 70, -3, 65, 70, -3, 62, 70, -2, 59, + 70, -2, 55, 70, -1, 52, 70, -1, 48, 70, 0, 44, + 71, 1, 40, 71, 1, 36, 71, 2, 32, 71, 3, 28, + 71, 4, 23, 71, 5, 19, 71, 6, 15, 72, 8, 11, + 72, 9, 6, 72, 10, 2, 72, 12, -2, 72, 13, -6, + 73, 15, -10, 73, 16, -15, 73, 18, -19, 73, 20, -23, + 74, 21, -27, 74, 23, -31, 74, 25, -35, 75, 27, -39, + 71, -6, 73, 71, -6, 72, 71, -6, 71, 71, -6, 70, + 71, -5, 68, 71, -5, 66, 71, -5, 63, 71, -4, 60, + 71, -4, 56, 71, -4, 53, 71, -3, 49, 71, -2, 46, + 72, -2, 41, 72, -1, 37, 72, 0, 33, 72, 1, 29, + 72, 2, 25, 72, 3, 21, 72, 4, 16, 73, 5, 12, + 73, 7, 8, 73, 8, 4, 73, 9, -1, 73, 11, -5, + 74, 12, -9, 74, 14, -13, 74, 16, -17, 74, 17, -21, + 75, 19, -25, 75, 21, -29, 75, 23, -33, 76, 25, -37, + 72, -8, 74, 72, -8, 73, 72, -8, 72, 72, -8, 71, + 72, -8, 69, 72, -7, 67, 72, -7, 64, 72, -7, 61, + 72, -6, 58, 72, -6, 54, 72, -5, 51, 73, -4, 47, + 73, -4, 43, 73, -3, 39, 73, -2, 35, 73, -1, 31, + 73, 0, 26, 73, 1, 22, 73, 2, 18, 74, 3, 14, + 74, 5, 9, 74, 6, 5, 74, 7, 1, 74, 9, -3, + 75, 10, -7, 75, 12, -12, 75, 14, -16, 75, 15, -20, + 76, 17, -24, 76, 19, -28, 76, 21, -32, 77, 22, -36, + 73, -10, 75, 73, -10, 74, 73, -10, 73, 73, -10, 71, + 73, -10, 70, 73, -9, 67, 73, -9, 65, 73, -9, 62, + 73, -8, 59, 73, -8, 55, 74, -7, 52, 74, -7, 48, + 74, -6, 44, 74, -5, 40, 74, -4, 36, 74, -3, 32, + 74, -2, 27, 74, -1, 23, 74, 0, 19, 75, 1, 15, + 75, 3, 11, 75, 4, 6, 75, 5, 2, 75, 7, -2, + 76, 8, -6, 76, 10, -10, 76, 11, -14, 76, 13, -18, + 77, 15, -22, 77, 17, -26, 77, 18, -30, 78, 20, -34, + 74, -12, 76, 74, -12, 75, 74, -12, 74, 74, -12, 72, + 74, -12, 71, 74, -11, 68, 74, -11, 66, 74, -11, 63, + 74, -10, 60, 74, -10, 56, 75, -9, 53, 75, -9, 49, + 75, -8, 45, 75, -7, 41, 75, -6, 37, 75, -5, 33, + 75, -4, 29, 75, -3, 25, 76, -2, 21, 76, -1, 17, + 76, 0, 12, 76, 2, 8, 76, 3, 4, 76, 4, 0, + 77, 6, -4, 77, 8, -9, 77, 9, -13, 77, 11, -17, + 78, 12, -21, 78, 14, -25, 78, 16, -29, 79, 18, -33, + 75, -14, 76, 75, -14, 76, 75, -14, 75, 75, -14, 73, + 75, -14, 71, 75, -13, 69, 75, -13, 67, 75, -13, 64, + 76, -12, 61, 76, -12, 58, 76, -11, 54, 76, -11, 51, + 76, -10, 46, 76, -9, 43, 76, -8, 39, 76, -7, 35, + 76, -6, 30, 76, -5, 26, 77, -4, 22, 77, -3, 18, + 77, -2, 14, 77, 0, 9, 77, 1, 5, 78, 2, 1, + 78, 4, -3, 78, 5, -7, 78, 7, -11, 78, 9, -15, + 79, 10, -19, 79, 12, -23, 79, 14, -27, 80, 16, -31, + 76, -16, 77, 76, -16, 76, 76, -16, 75, 76, -16, 74, + 76, -16, 72, 76, -15, 70, 76, -15, 68, 77, -15, 65, + 77, -14, 62, 77, -14, 59, 77, -13, 55, 77, -13, 52, + 77, -12, 48, 77, -11, 44, 77, -10, 40, 77, -10, 36, + 77, -8, 32, 77, -7, 28, 78, -6, 24, 78, -5, 20, + 78, -4, 15, 78, -3, 11, 78, -1, 7, 79, 0, 3, + 79, 2, -1, 79, 3, -6, 79, 5, -10, 79, 6, -14, + 80, 8, -17, 80, 10, -22, 80, 12, -26, 81, 14, -30, + 77, -18, 78, 77, -18, 77, 77, -18, 76, 77, -18, 75, + 78, -18, 73, 78, -17, 71, 78, -17, 69, 78, -17, 66, + 78, -16, 63, 78, -16, 60, 78, -15, 57, 78, -15, 53, + 78, -14, 49, 78, -13, 45, 78, -12, 41, 78, -12, 37, + 78, -11, 33, 79, -10, 29, 79, -8, 25, 79, -7, 21, + 79, -6, 16, 79, -5, 12, 79, -3, 8, 80, -2, 4, + 80, -1, 0, 80, 1, -4, 80, 3, -8, 80, 4, -12, + 81, 6, -16, 81, 8, -20, 81, 10, -24, 82, 11, -28, + 79, -20, 79, 79, -20, 78, 79, -20, 77, 79, -20, 76, + 79, -20, 74, 79, -19, 72, 79, -19, 70, 79, -19, 67, + 79, -18, 64, 79, -18, 61, 79, -17, 58, 79, -17, 54, + 79, -16, 50, 79, -15, 47, 79, -14, 43, 79, -14, 39, + 79, -13, 34, 80, -12, 30, 80, -10, 26, 80, -9, 22, + 80, -8, 18, 80, -7, 14, 80, -5, 10, 81, -4, 6, + 81, -3, 2, 81, -1, -3, 81, 1, -7, 82, 2, -10, + 82, 4, -14, 82, 6, -19, 82, 7, -23, 83, 9, -27, + 80, -22, 80, 80, -22, 79, 80, -22, 78, 80, -22, 77, + 80, -22, 75, 80, -21, 73, 80, -21, 71, 80, -21, 68, + 80, -20, 65, 80, -20, 62, 80, -19, 59, 80, -19, 56, + 80, -18, 51, 80, -17, 48, 80, -16, 44, 80, -16, 40, + 81, -15, 36, 81, -14, 32, 81, -13, 28, 81, -11, 24, + 81, -10, 19, 81, -9, 15, 81, -8, 11, 82, -6, 7, + 82, -5, 3, 82, -3, -1, 82, -2, -5, 83, 0, -9, + 83, 2, -13, 83, 4, -17, 83, 5, -21, 84, 7, -25, + 81, -24, 80, 81, -24, 80, 81, -24, 79, 81, -24, 78, + 81, -24, 76, 81, -23, 74, 81, -23, 72, 81, -23, 69, + 81, -22, 66, 81, -22, 63, 81, -21, 60, 81, -21, 57, + 81, -20, 53, 81, -19, 49, 81, -18, 45, 82, -18, 42, + 82, -17, 37, 82, -16, 33, 82, -15, 29, 82, -13, 25, + 82, -12, 21, 82, -11, 17, 83, -10, 13, 83, -8, 9, + 83, -7, 5, 83, -5, 0, 83, -4, -3, 84, -2, -7, + 84, 0, -11, 84, 1, -16, 84, 3, -20, 85, 5, -23, + 82, -27, 81, 82, -26, 81, 82, -26, 80, 82, -26, 79, + 82, -26, 77, 82, -26, 75, 82, -25, 73, 82, -25, 70, + 82, -25, 68, 82, -24, 65, 82, -24, 62, 82, -23, 58, + 83, -22, 54, 83, -22, 51, 83, -21, 47, 83, -20, 43, + 83, -19, 39, 83, -18, 35, 83, -17, 31, 83, -16, 27, + 84, -15, 23, 84, -13, 19, 84, -12, 15, 84, -11, 11, + 84, -9, 7, 84, -8, 2, 85, -6, -2, 85, -5, -6, + 85, -3, -9, 85, -1, -14, 86, 1, -18, 86, 2, -22, + 83, -28, 82, 83, -28, 82, 83, -28, 81, 83, -28, 80, + 83, -28, 78, 83, -28, 76, 83, -27, 74, 83, -27, 71, + 83, -27, 69, 83, -26, 66, 84, -26, 63, 84, -25, 59, + 84, -24, 56, 84, -24, 52, 84, -23, 48, 84, -22, 45, + 84, -21, 40, 84, -20, 36, 84, -19, 33, 84, -18, 29, + 85, -17, 24, 85, -15, 20, 85, -14, 16, 85, -13, 12, + 85, -11, 8, 85, -10, 4, 86, -8, 0, 86, -7, -4, + 86, -5, -8, 86, -3, -12, 87, -2, -16, 87, 0, -20, + 84, -30, 83, 84, -30, 82, 84, -30, 82, 84, -30, 80, + 84, -30, 79, 84, -30, 77, 84, -29, 75, 84, -29, 73, + 85, -29, 70, 85, -28, 67, 85, -28, 64, 85, -27, 61, + 85, -26, 57, 85, -26, 53, 85, -25, 50, 85, -24, 46, + 85, -23, 42, 85, -22, 38, 85, -21, 34, 86, -20, 30, + 86, -19, 26, 86, -17, 22, 86, -16, 18, 86, -15, 14, + 86, -13, 10, 87, -12, 5, 87, -10, 1, 87, -9, -2, + 87, -7, -6, 87, -5, -11, 88, -4, -15, 88, -2, -18, + 85, -32, 84, 85, -32, 83, 85, -32, 83, 85, -32, 81, + 85, -32, 80, 85, -31, 78, 86, -31, 76, 86, -31, 74, + 86, -30, 71, 86, -30, 68, 86, -29, 65, 86, -29, 62, + 86, -28, 58, 86, -27, 55, 86, -27, 51, 86, -26, 47, + 86, -25, 43, 86, -24, 39, 86, -23, 35, 87, -22, 32, + 87, -21, 27, 87, -19, 23, 87, -18, 19, 87, -17, 15, + 87, -15, 11, 88, -14, 7, 88, -12, 3, 88, -11, -1, + 88, -9, -5, 88, -7, -9, 89, -6, -13, 89, -4, -17, + 87, -34, 85, 87, -34, 84, 87, -34, 83, 87, -34, 82, + 87, -33, 81, 87, -33, 79, 87, -33, 77, 87, -33, 75, + 87, -32, 72, 87, -32, 69, 87, -31, 66, 87, -31, 63, + 87, -30, 59, 87, -29, 56, 87, -29, 52, 87, -28, 49, + 87, -27, 44, 87, -26, 41, 88, -25, 37, 88, -24, 33, + 88, -23, 29, 88, -21, 25, 88, -20, 21, 88, -19, 17, + 88, -17, 13, 89, -16, 8, 89, -14, 5, 89, -13, 1, + 89, -11, -3, 90, -9, -8, 90, -8, -11, 90, -6, -15, + 88, -36, 86, 88, -36, 85, 88, -36, 84, 88, -35, 83, + 88, -35, 82, 88, -35, 80, 88, -35, 78, 88, -34, 76, + 88, -34, 73, 88, -34, 70, 88, -33, 67, 88, -33, 64, + 88, -32, 61, 88, -31, 57, 88, -30, 54, 88, -30, 50, + 88, -29, 46, 89, -28, 42, 89, -27, 38, 89, -26, 34, + 89, -24, 30, 89, -23, 26, 89, -22, 22, 89, -21, 18, + 90, -19, 14, 90, -18, 10, 90, -16, 6, 90, -15, 2, + 90, -13, -2, 91, -11, -6, 91, -10, -10, 91, -8, -14, + 89, -38, 86, 89, -38, 86, 89, -37, 85, 89, -37, 84, + 89, -37, 83, 89, -37, 81, 89, -37, 79, 89, -36, 77, + 89, -36, 74, 89, -35, 71, 89, -35, 69, 89, -34, 65, + 89, -34, 62, 89, -33, 58, 89, -32, 55, 89, -32, 51, + 90, -31, 47, 90, -30, 43, 90, -29, 40, 90, -28, 36, + 90, -26, 31, 90, -25, 28, 90, -24, 24, 90, -23, 20, + 91, -21, 16, 91, -20, 12, 91, -18, 8, 91, -17, 4, + 91, -15, 0, 92, -13, -5, 92, -12, -8, 92, -10, -12, + 90, -39, 87, 90, -39, 87, 90, -39, 86, 90, -39, 85, + 90, -39, 84, 90, -39, 82, 90, -38, 80, 90, -38, 78, + 90, -38, 75, 90, -37, 73, 90, -37, 70, 90, -36, 67, + 90, -36, 63, 90, -35, 60, 90, -34, 56, 91, -33, 53, + 91, -32, 49, 91, -32, 45, 91, -31, 41, 91, -30, 37, + 91, -28, 33, 91, -27, 29, 91, -26, 25, 92, -25, 21, + 92, -23, 17, 92, -22, 13, 92, -20, 9, 92, -19, 5, + 92, -17, 1, 93, -15, -3, 93, -14, -7, 93, -12, -11, + 91, -41, 88, 91, -41, 87, 91, -41, 87, 91, -41, 86, + 91, -41, 84, 91, -40, 83, 91, -40, 81, 91, -40, 79, + 91, -39, 76, 91, -39, 74, 91, -39, 71, 91, -38, 68, + 91, -37, 64, 91, -37, 61, 92, -36, 58, 92, -35, 54, + 92, -34, 50, 92, -33, 46, 92, -32, 42, 92, -31, 39, + 92, -30, 34, 92, -29, 31, 92, -28, 27, 93, -27, 23, + 93, -25, 19, 93, -24, 15, 93, -22, 11, 93, -21, 7, + 93, -19, 3, 94, -17, -1, 94, -16, -5, 94, -14, -9, + 92, -43, 89, 92, -43, 88, 92, -43, 88, 92, -43, 87, + 92, -42, 85, 92, -42, 84, 92, -42, 82, 92, -42, 80, + 92, -41, 77, 92, -41, 75, 92, -40, 72, 92, -40, 69, + 92, -39, 65, 93, -39, 62, 93, -38, 59, 93, -37, 55, + 93, -36, 51, 93, -35, 48, 93, -34, 44, 93, -33, 40, + 93, -32, 36, 93, -31, 32, 94, -30, 28, 94, -28, 24, + 94, -27, 20, 94, -26, 16, 94, -24, 12, 94, -23, 8, + 95, -21, 4, 95, -19, 0, 95, -18, -4, 95, -16, -8, + 93, -45, 90, 93, -45, 89, 93, -44, 89, 93, -44, 88, + 93, -44, 86, 93, -44, 85, 93, -44, 83, 93, -43, 81, + 93, -43, 78, 93, -43, 76, 93, -42, 73, 94, -42, 70, + 94, -41, 67, 94, -40, 63, 94, -40, 60, 94, -39, 57, + 94, -38, 53, 94, -37, 49, 94, -36, 45, 94, -35, 42, + 94, -34, 37, 94, -33, 33, 95, -32, 30, 95, -30, 26, + 95, -29, 22, 95, -27, 18, 95, -26, 14, 95, -25, 10, + 96, -23, 6, 96, -21, 2, 96, -20, -2, 96, -18, -6, + 43, 68, 57, 43, 68, 55, 43, 68, 52, 43, 68, 48, + 43, 69, 44, 43, 69, 40, 43, 69, 35, 43, 69, 30, + 44, 70, 25, 44, 70, 20, 44, 71, 15, 44, 71, 11, + 44, 72, 5, 44, 72, 1, 45, 73, -4, 45, 74, -9, + 45, 75, -14, 46, 75, -18, 46, 76, -22, 46, 77, -27, + 47, 78, -31, 47, 79, -35, 48, 80, -39, 48, 81, -43, + 48, 82, -47, 49, 84, -52, 49, 85, -56, 50, 86, -59, + 50, 87, -63, 51, 89, -67, 52, 90, -71, 52, 91, -74, + 43, 68, 57, 43, 68, 55, 43, 68, 52, 43, 68, 48, + 43, 68, 44, 43, 68, 40, 43, 69, 35, 44, 69, 30, + 44, 69, 25, 44, 70, 20, 44, 70, 16, 44, 71, 11, + 44, 71, 5, 45, 72, 1, 45, 73, -4, 45, 73, -8, + 45, 74, -13, 46, 75, -18, 46, 76, -22, 46, 77, -26, + 47, 78, -31, 47, 79, -35, 48, 80, -39, 48, 81, -43, + 49, 82, -47, 49, 83, -52, 50, 85, -55, 50, 86, -59, + 51, 87, -63, 51, 88, -67, 52, 90, -71, 52, 91, -74, + 43, 67, 57, 43, 68, 55, 43, 68, 52, 43, 68, 48, + 43, 68, 44, 43, 68, 40, 44, 68, 35, 44, 69, 30, + 44, 69, 25, 44, 69, 21, 44, 70, 16, 44, 70, 11, + 45, 71, 6, 45, 72, 1, 45, 72, -4, 45, 73, -8, + 46, 74, -13, 46, 75, -18, 46, 76, -22, 47, 76, -26, + 47, 78, -31, 47, 79, -35, 48, 80, -39, 48, 81, -43, + 49, 82, -47, 49, 83, -51, 50, 84, -55, 50, 85, -59, + 51, 87, -63, 51, 88, -67, 52, 89, -70, 52, 91, -74, + 43, 67, 57, 43, 67, 55, 43, 67, 52, 43, 67, 48, + 43, 68, 44, 44, 68, 40, 44, 68, 36, 44, 68, 30, + 44, 69, 25, 44, 69, 21, 44, 70, 16, 44, 70, 11, + 45, 71, 6, 45, 71, 1, 45, 72, -3, 45, 73, -8, + 46, 74, -13, 46, 74, -17, 46, 75, -22, 47, 76, -26, + 47, 77, -31, 47, 78, -35, 48, 79, -39, 48, 80, -43, + 49, 81, -47, 49, 83, -51, 50, 84, -55, 50, 85, -59, + 51, 86, -63, 51, 88, -67, 52, 89, -70, 53, 90, -74, + 43, 67, 57, 44, 67, 55, 44, 67, 52, 44, 67, 48, + 44, 67, 45, 44, 67, 40, 44, 68, 36, 44, 68, 30, + 44, 68, 26, 44, 69, 21, 44, 69, 16, 45, 70, 11, + 45, 70, 6, 45, 71, 1, 45, 72, -3, 46, 72, -8, + 46, 73, -13, 46, 74, -17, 46, 75, -22, 47, 76, -26, + 47, 77, -31, 48, 78, -35, 48, 79, -39, 48, 80, -43, + 49, 81, -47, 49, 82, -51, 50, 84, -55, 50, 85, -59, + 51, 86, -62, 52, 87, -66, 52, 89, -70, 53, 90, -74, + 44, 66, 57, 44, 66, 55, 44, 66, 52, 44, 66, 49, + 44, 67, 45, 44, 67, 40, 44, 67, 36, 44, 67, 31, + 44, 68, 26, 44, 68, 21, 45, 69, 16, 45, 69, 12, + 45, 70, 6, 45, 70, 2, 45, 71, -3, 46, 72, -7, + 46, 73, -13, 46, 73, -17, 47, 74, -21, 47, 75, -26, + 47, 76, -30, 48, 77, -34, 48, 78, -38, 49, 79, -42, + 49, 81, -46, 50, 82, -51, 50, 83, -55, 51, 84, -58, + 51, 86, -62, 52, 87, -66, 52, 88, -70, 53, 90, -73, + 44, 65, 58, 44, 66, 55, 44, 66, 53, 44, 66, 49, + 44, 66, 45, 44, 66, 41, 44, 66, 36, 44, 67, 31, + 44, 67, 26, 45, 67, 21, 45, 68, 17, 45, 68, 12, + 45, 69, 7, 45, 70, 2, 46, 70, -3, 46, 71, -7, + 46, 72, -12, 47, 73, -17, 47, 74, -21, 47, 75, -25, + 48, 76, -30, 48, 77, -34, 48, 78, -38, 49, 79, -42, + 49, 80, -46, 50, 81, -51, 50, 83, -54, 51, 84, -58, + 51, 85, -62, 52, 86, -66, 52, 88, -70, 53, 89, -73, + 44, 65, 58, 44, 65, 55, 44, 65, 53, 44, 65, 49, + 44, 65, 45, 44, 65, 41, 44, 66, 36, 45, 66, 31, + 45, 66, 27, 45, 67, 22, 45, 67, 17, 45, 68, 12, + 45, 68, 7, 46, 69, 2, 46, 70, -2, 46, 70, -7, + 46, 71, -12, 47, 72, -16, 47, 73, -21, 47, 74, -25, + 48, 75, -30, 48, 76, -34, 49, 77, -38, 49, 78, -42, + 49, 79, -46, 50, 81, -50, 50, 82, -54, 51, 83, -58, + 51, 84, -62, 52, 86, -66, 53, 87, -69, 53, 89, -73, + 44, 64, 58, 44, 64, 55, 45, 64, 53, 45, 64, 49, + 45, 64, 45, 45, 65, 41, 45, 65, 37, 45, 65, 32, + 45, 66, 27, 45, 66, 22, 45, 66, 17, 45, 67, 13, + 46, 68, 7, 46, 68, 3, 46, 69, -2, 46, 70, -6, + 47, 71, -11, 47, 71, -16, 47, 72, -20, 48, 73, -24, + 48, 74, -29, 48, 75, -33, 49, 76, -37, 49, 77, -41, + 50, 79, -45, 50, 80, -50, 51, 81, -54, 51, 82, -57, + 52, 84, -61, 52, 85, -65, 53, 87, -69, 53, 88, -72, + 45, 63, 58, 45, 63, 56, 45, 63, 53, 45, 63, 49, + 45, 63, 46, 45, 64, 42, 45, 64, 37, 45, 64, 32, + 45, 65, 27, 45, 65, 23, 46, 66, 18, 46, 66, 13, + 46, 67, 8, 46, 67, 3, 46, 68, -1, 47, 69, -6, + 47, 70, -11, 47, 70, -15, 48, 71, -20, 48, 72, -24, + 48, 73, -29, 49, 74, -33, 49, 76, -37, 50, 77, -41, + 50, 78, -45, 50, 79, -49, 51, 80, -53, 51, 82, -57, + 52, 83, -61, 53, 84, -65, 53, 86, -68, 54, 87, -72, + 45, 62, 58, 45, 62, 56, 45, 62, 53, 45, 62, 50, + 45, 62, 46, 45, 63, 42, 45, 63, 37, 46, 63, 32, + 46, 64, 28, 46, 64, 23, 46, 65, 18, 46, 65, 14, + 46, 66, 8, 47, 66, 4, 47, 67, -1, 47, 68, -5, + 47, 69, -10, 48, 70, -15, 48, 70, -19, 48, 71, -24, + 49, 73, -28, 49, 74, -32, 49, 75, -37, 50, 76, -41, + 50, 77, -45, 51, 78, -49, 51, 80, -53, 52, 81, -57, + 52, 82, -60, 53, 84, -64, 53, 85, -68, 54, 86, -72, + 46, 61, 58, 46, 61, 56, 46, 61, 54, 46, 61, 50, + 46, 61, 46, 46, 61, 42, 46, 62, 38, 46, 62, 33, + 46, 62, 28, 46, 63, 24, 46, 63, 19, 47, 64, 14, + 47, 64, 9, 47, 65, 4, 47, 66, 0, 48, 66, -5, + 48, 67, -10, 48, 68, -14, 48, 69, -19, 49, 70, -23, + 49, 71, -28, 50, 72, -32, 50, 73, -36, 50, 75, -40, + 51, 76, -44, 51, 77, -48, 52, 78, -52, 52, 80, -56, + 53, 81, -60, 53, 82, -64, 54, 84, -67, 54, 85, -71, + 46, 59, 58, 46, 59, 56, 46, 59, 54, 46, 60, 50, + 46, 60, 47, 46, 60, 43, 46, 60, 38, 46, 61, 33, + 47, 61, 29, 47, 61, 24, 47, 62, 19, 47, 62, 15, + 47, 63, 10, 47, 64, 5, 48, 65, 0, 48, 65, -4, + 48, 66, -9, 49, 67, -14, 49, 68, -18, 49, 69, -22, + 50, 70, -27, 50, 71, -31, 50, 72, -35, 51, 73, -39, + 51, 75, -43, 52, 76, -48, 52, 77, -52, 53, 79, -55, + 53, 80, -59, 54, 81, -63, 54, 83, -67, 55, 84, -70, + 47, 58, 59, 47, 58, 56, 47, 58, 54, 47, 58, 51, + 47, 59, 47, 47, 59, 43, 47, 59, 39, 47, 59, 34, + 47, 60, 29, 47, 60, 25, 47, 61, 20, 48, 61, 15, + 48, 62, 10, 48, 63, 6, 48, 63, 1, 48, 64, -4, + 49, 65, -9, 49, 66, -13, 49, 67, -17, 50, 68, -22, + 50, 69, -26, 50, 70, -30, 51, 71, -35, 51, 72, -39, + 51, 74, -43, 52, 75, -47, 52, 76, -51, 53, 77, -55, + 53, 79, -58, 54, 80, -63, 54, 82, -66, 55, 83, -70, + 47, 57, 59, 47, 57, 57, 47, 57, 55, 47, 57, 51, + 47, 57, 48, 47, 57, 44, 47, 58, 40, 47, 58, 35, + 48, 58, 30, 48, 59, 25, 48, 59, 21, 48, 60, 16, + 48, 61, 11, 48, 61, 6, 49, 62, 2, 49, 63, -3, + 49, 64, -8, 49, 65, -12, 50, 66, -17, 50, 66, -21, + 50, 68, -26, 51, 69, -30, 51, 70, -34, 52, 71, -38, + 52, 72, -42, 52, 74, -46, 53, 75, -50, 53, 76, -54, + 54, 78, -58, 54, 79, -62, 55, 81, -66, 55, 82, -69, + 48, 55, 59, 48, 55, 57, 48, 55, 55, 48, 55, 52, + 48, 56, 48, 48, 56, 44, 48, 56, 40, 48, 57, 35, + 48, 57, 31, 48, 57, 26, 48, 58, 21, 49, 58, 17, + 49, 59, 12, 49, 60, 7, 49, 61, 2, 49, 61, -2, + 50, 62, -7, 50, 63, -11, 50, 64, -16, 51, 65, -20, + 51, 66, -25, 51, 67, -29, 52, 69, -33, 52, 70, -37, + 52, 71, -41, 53, 72, -46, 53, 74, -50, 54, 75, -53, + 54, 76, -57, 55, 78, -61, 55, 79, -65, 56, 81, -69, + 48, 54, 59, 48, 54, 57, 48, 54, 55, 48, 54, 52, + 48, 54, 49, 48, 54, 45, 48, 55, 41, 49, 55, 36, + 49, 55, 31, 49, 56, 27, 49, 56, 22, 49, 57, 18, + 49, 58, 12, 49, 58, 8, 50, 59, 3, 50, 60, -1, + 50, 61, -6, 50, 62, -11, 51, 63, -15, 51, 64, -19, + 51, 65, -24, 52, 66, -28, 52, 67, -32, 52, 68, -36, + 53, 70, -40, 53, 71, -45, 54, 72, -49, 54, 74, -53, + 55, 75, -56, 55, 77, -61, 56, 78, -64, 56, 80, -68, + 49, 52, 60, 49, 52, 58, 49, 52, 56, 49, 52, 53, + 49, 53, 49, 49, 53, 45, 49, 53, 41, 49, 53, 36, + 49, 54, 32, 49, 54, 27, 49, 55, 23, 50, 55, 18, + 50, 56, 13, 50, 57, 9, 50, 57, 4, 50, 58, 0, + 51, 59, -5, 51, 60, -10, 51, 61, -14, 52, 62, -18, + 52, 63, -23, 52, 64, -27, 53, 66, -32, 53, 67, -36, + 53, 68, -40, 54, 70, -44, 54, 71, -48, 55, 72, -52, + 55, 74, -56, 56, 75, -60, 56, 77, -63, 57, 78, -67, + 49, 50, 60, 49, 50, 58, 49, 50, 56, 49, 51, 53, + 49, 51, 50, 50, 51, 46, 50, 51, 42, 50, 52, 37, + 50, 52, 33, 50, 53, 28, 50, 53, 24, 50, 54, 19, + 50, 54, 14, 51, 55, 9, 51, 56, 5, 51, 57, 0, + 51, 58, -5, 52, 59, -9, 52, 60, -13, 52, 61, -18, + 53, 62, -22, 53, 63, -27, 53, 64, -31, 54, 65, -35, + 54, 67, -39, 54, 68, -43, 55, 69, -47, 55, 71, -51, + 56, 72, -55, 56, 74, -59, 57, 75, -63, 57, 77, -66, + 50, 49, 60, 50, 49, 59, 50, 49, 57, 50, 49, 54, + 50, 49, 50, 50, 49, 47, 50, 50, 43, 50, 50, 38, + 50, 50, 34, 51, 51, 29, 51, 51, 25, 51, 52, 20, + 51, 53, 15, 51, 53, 10, 51, 54, 6, 52, 55, 1, + 52, 56, -4, 52, 57, -8, 52, 58, -12, 53, 59, -17, + 53, 60, -21, 53, 61, -26, 54, 62, -30, 54, 64, -34, + 55, 65, -38, 55, 66, -42, 55, 68, -46, 56, 69, -50, + 56, 71, -54, 57, 72, -58, 57, 74, -62, 58, 75, -65, + 51, 47, 61, 51, 47, 59, 51, 47, 57, 51, 47, 54, + 51, 47, 51, 51, 48, 47, 51, 48, 43, 51, 48, 39, + 51, 49, 34, 51, 49, 30, 51, 50, 25, 52, 50, 21, + 52, 51, 16, 52, 52, 11, 52, 52, 7, 52, 53, 2, + 53, 54, -3, 53, 55, -7, 53, 56, -11, 53, 57, -16, + 54, 58, -21, 54, 60, -25, 54, 61, -29, 55, 62, -33, + 55, 63, -37, 56, 65, -41, 56, 66, -45, 56, 68, -49, + 57, 69, -53, 57, 71, -57, 58, 72, -61, 58, 74, -65, + 51, 45, 61, 51, 45, 59, 51, 45, 57, 51, 45, 55, + 52, 45, 52, 52, 46, 48, 52, 46, 44, 52, 46, 39, + 52, 47, 35, 52, 47, 31, 52, 48, 26, 52, 48, 22, + 52, 49, 17, 53, 50, 12, 53, 51, 8, 53, 51, 3, + 53, 52, -2, 54, 53, -6, 54, 54, -10, 54, 55, -15, + 54, 57, -20, 55, 58, -24, 55, 59, -28, 55, 60, -32, + 56, 62, -36, 56, 63, -40, 57, 65, -44, 57, 66, -48, + 57, 67, -52, 58, 69, -56, 58, 71, -60, 59, 72, -64, + 52, 43, 62, 52, 43, 60, 52, 43, 58, 52, 43, 55, + 52, 44, 52, 52, 44, 49, 52, 44, 45, 52, 44, 40, + 53, 45, 36, 53, 45, 32, 53, 46, 27, 53, 46, 23, + 53, 47, 18, 53, 48, 13, 53, 49, 9, 54, 50, 4, + 54, 51, -1, 54, 52, -5, 54, 53, -9, 55, 54, -14, + 55, 55, -18, 55, 56, -23, 56, 57, -27, 56, 59, -31, + 56, 60, -35, 57, 61, -39, 57, 63, -43, 58, 64, -47, + 58, 66, -51, 59, 67, -55, 59, 69, -59, 59, 71, -63, + 53, 41, 62, 53, 41, 60, 53, 41, 59, 53, 41, 56, + 53, 42, 53, 53, 42, 49, 53, 42, 46, 53, 43, 41, + 53, 43, 37, 53, 43, 33, 54, 44, 28, 54, 45, 24, + 54, 45, 19, 54, 46, 14, 54, 47, 10, 54, 48, 5, + 55, 49, 0, 55, 50, -4, 55, 51, -8, 55, 52, -13, + 56, 53, -17, 56, 54, -22, 56, 55, -26, 57, 57, -30, + 57, 58, -34, 57, 60, -38, 58, 61, -42, 58, 62, -46, + 59, 64, -50, 59, 66, -54, 60, 67, -58, 60, 69, -62, + 54, 39, 62, 54, 39, 61, 54, 39, 59, 54, 39, 56, + 54, 40, 54, 54, 40, 50, 54, 40, 46, 54, 41, 42, + 54, 41, 38, 54, 41, 34, 54, 42, 29, 54, 43, 25, + 55, 43, 20, 55, 44, 15, 55, 45, 11, 55, 46, 6, + 55, 47, 1, 56, 48, -3, 56, 49, -7, 56, 50, -12, + 56, 51, -16, 57, 52, -21, 57, 54, -25, 57, 55, -29, + 58, 56, -33, 58, 58, -37, 59, 59, -41, 59, 61, -45, + 59, 62, -49, 60, 64, -53, 60, 65, -57, 61, 67, -61, + 54, 37, 63, 54, 37, 61, 55, 37, 60, 55, 37, 57, + 55, 38, 54, 55, 38, 51, 55, 38, 47, 55, 39, 43, + 55, 39, 39, 55, 39, 34, 55, 40, 30, 55, 41, 26, + 55, 41, 21, 56, 42, 16, 56, 43, 12, 56, 44, 7, + 56, 45, 3, 56, 46, -2, 57, 47, -6, 57, 48, -10, + 57, 49, -15, 58, 50, -19, 58, 52, -24, 58, 53, -28, + 58, 54, -32, 59, 56, -36, 59, 57, -40, 60, 59, -44, + 60, 60, -48, 60, 62, -52, 61, 64, -56, 61, 65, -60, + 55, 35, 63, 55, 35, 62, 55, 35, 60, 55, 35, 58, + 55, 36, 55, 55, 36, 52, 56, 36, 48, 56, 36, 44, + 56, 37, 40, 56, 37, 35, 56, 38, 31, 56, 39, 27, + 56, 39, 22, 56, 40, 17, 57, 41, 13, 57, 42, 9, + 57, 43, 4, 57, 44, -1, 57, 45, -5, 58, 46, -9, + 58, 47, -14, 58, 48, -18, 59, 50, -22, 59, 51, -26, + 59, 52, -31, 60, 54, -35, 60, 55, -39, 60, 57, -43, + 61, 58, -47, 61, 60, -51, 62, 62, -55, 62, 63, -59, + 56, 33, 64, 56, 33, 62, 56, 33, 61, 56, 33, 58, + 56, 33, 56, 56, 34, 52, 56, 34, 49, 56, 34, 45, + 57, 35, 41, 57, 35, 36, 57, 36, 32, 57, 36, 28, + 57, 37, 23, 57, 38, 19, 57, 39, 14, 58, 40, 10, + 58, 41, 5, 58, 42, 0, 58, 43, -4, 58, 44, -8, + 59, 45, -13, 59, 46, -17, 59, 48, -21, 60, 49, -25, + 60, 50, -29, 60, 52, -34, 61, 53, -38, 61, 55, -42, + 61, 56, -46, 62, 58, -50, 62, 60, -54, 63, 61, -57, + 57, 31, 64, 57, 31, 63, 57, 31, 61, 57, 31, 59, + 57, 31, 56, 57, 32, 53, 57, 32, 50, 57, 32, 46, + 57, 33, 42, 57, 33, 37, 58, 34, 33, 58, 34, 29, + 58, 35, 24, 58, 36, 20, 58, 37, 15, 58, 38, 11, + 59, 39, 6, 59, 40, 2, 59, 41, -3, 59, 42, -7, + 60, 43, -12, 60, 44, -16, 60, 46, -20, 60, 47, -24, + 61, 48, -28, 61, 50, -33, 61, 51, -37, 62, 53, -41, + 62, 54, -44, 63, 56, -49, 63, 58, -52, 63, 59, -56, + 58, 29, 65, 58, 29, 64, 58, 29, 62, 58, 29, 60, + 58, 29, 57, 58, 29, 54, 58, 30, 51, 58, 30, 47, + 58, 31, 43, 58, 31, 39, 58, 32, 34, 59, 32, 30, + 59, 33, 25, 59, 34, 21, 59, 35, 16, 59, 35, 12, + 59, 37, 7, 60, 38, 3, 60, 39, -1, 60, 40, -6, + 60, 41, -10, 61, 42, -15, 61, 44, -19, 61, 45, -23, + 62, 46, -27, 62, 48, -31, 62, 49, -35, 63, 51, -39, + 63, 52, -43, 63, 54, -48, 64, 56, -51, 64, 58, -55, + 59, 27, 66, 59, 27, 64, 59, 27, 63, 59, 27, 60, + 59, 27, 58, 59, 27, 55, 59, 28, 52, 59, 28, 47, + 59, 28, 44, 59, 29, 40, 59, 30, 35, 59, 30, 31, + 60, 31, 26, 60, 32, 22, 60, 32, 18, 60, 33, 13, + 60, 34, 8, 61, 35, 4, 61, 36, 0, 61, 38, -4, + 61, 39, -9, 61, 40, -13, 62, 41, -17, 62, 43, -22, + 62, 44, -26, 63, 46, -30, 63, 47, -34, 63, 49, -38, + 64, 50, -42, 64, 52, -46, 65, 54, -50, 65, 56, -54, + 60, 24, 66, 60, 24, 65, 60, 25, 63, 60, 25, 61, + 60, 25, 59, 60, 25, 56, 60, 25, 52, 60, 26, 48, + 60, 26, 45, 60, 27, 41, 60, 27, 36, 60, 28, 32, + 61, 29, 27, 61, 29, 23, 61, 30, 19, 61, 31, 15, + 61, 32, 10, 61, 33, 5, 62, 34, 1, 62, 35, -3, + 62, 37, -8, 62, 38, -12, 63, 39, -16, 63, 41, -20, + 63, 42, -24, 64, 44, -29, 64, 45, -33, 64, 47, -37, + 65, 48, -41, 65, 50, -45, 65, 52, -49, 66, 53, -53, + 61, 22, 67, 61, 22, 66, 61, 22, 64, 61, 22, 62, + 61, 22, 60, 61, 22, 57, 61, 23, 54, 61, 23, 50, + 61, 24, 46, 61, 24, 42, 61, 25, 38, 62, 25, 34, + 62, 26, 29, 62, 27, 25, 62, 28, 20, 62, 28, 16, + 62, 30, 11, 62, 31, 7, 63, 32, 3, 63, 33, -2, + 63, 34, -6, 63, 35, -10, 64, 37, -15, 64, 38, -19, + 64, 39, -23, 65, 41, -27, 65, 43, -31, 65, 44, -35, + 66, 46, -39, 66, 48, -43, 66, 49, -47, 67, 51, -51, + 62, 20, 67, 62, 20, 66, 62, 20, 65, 62, 20, 63, + 62, 20, 60, 62, 20, 58, 62, 21, 55, 62, 21, 51, + 62, 21, 47, 62, 22, 43, 62, 22, 39, 62, 23, 35, + 63, 24, 30, 63, 25, 26, 63, 25, 22, 63, 26, 17, + 63, 27, 13, 63, 28, 8, 64, 29, 4, 64, 31, 0, + 64, 32, -5, 64, 33, -9, 65, 35, -13, 65, 36, -17, + 65, 37, -21, 65, 39, -26, 66, 40, -30, 66, 42, -34, + 66, 44, -38, 67, 45, -42, 67, 47, -46, 68, 49, -50, + 63, 17, 68, 63, 17, 67, 63, 18, 66, 63, 18, 64, + 63, 18, 61, 63, 18, 59, 63, 18, 55, 63, 19, 52, + 63, 19, 48, 63, 20, 44, 63, 20, 40, 63, 21, 36, + 64, 22, 31, 64, 22, 27, 64, 23, 23, 64, 24, 19, + 64, 25, 14, 64, 26, 10, 65, 27, 5, 65, 28, 1, + 65, 30, -4, 65, 31, -8, 65, 32, -12, 66, 34, -16, + 66, 35, -20, 66, 37, -25, 67, 38, -29, 67, 40, -33, + 67, 41, -36, 68, 43, -41, 68, 45, -45, 68, 47, -48, + 64, 15, 69, 64, 15, 68, 64, 15, 66, 64, 16, 64, + 64, 16, 62, 64, 16, 59, 64, 16, 56, 64, 17, 53, + 64, 17, 49, 64, 18, 45, 64, 18, 41, 64, 19, 37, + 64, 20, 33, 65, 20, 28, 65, 21, 24, 65, 22, 20, + 65, 23, 15, 65, 24, 11, 65, 25, 7, 66, 26, 2, + 66, 28, -2, 66, 29, -6, 66, 30, -11, 67, 32, -15, + 67, 33, -19, 67, 35, -23, 68, 36, -27, 68, 38, -31, + 68, 39, -35, 69, 41, -39, 69, 43, -43, 69, 45, -47, + 65, 13, 69, 65, 13, 68, 65, 13, 67, 65, 13, 65, + 65, 14, 63, 65, 14, 60, 65, 14, 57, 65, 14, 54, + 65, 15, 50, 65, 15, 46, 65, 16, 42, 65, 17, 38, + 65, 17, 34, 66, 18, 30, 66, 19, 25, 66, 20, 21, + 66, 21, 16, 66, 22, 12, 66, 23, 8, 67, 24, 4, + 67, 25, -1, 67, 27, -5, 67, 28, -9, 68, 29, -13, + 68, 31, -17, 68, 32, -22, 68, 34, -26, 69, 36, -30, + 69, 37, -34, 69, 39, -38, 70, 41, -42, 70, 42, -46, + 66, 11, 70, 66, 11, 69, 66, 11, 68, 66, 11, 66, + 66, 11, 64, 66, 12, 61, 66, 12, 58, 66, 12, 55, + 66, 13, 51, 66, 13, 47, 66, 14, 44, 66, 14, 40, + 66, 15, 35, 67, 16, 31, 67, 17, 27, 67, 18, 23, + 67, 19, 18, 67, 20, 14, 67, 21, 9, 68, 22, 5, + 68, 23, 0, 68, 25, -4, 68, 26, -8, 68, 27, -12, + 69, 29, -16, 69, 30, -20, 69, 32, -24, 70, 33, -28, + 70, 35, -32, 70, 37, -37, 71, 39, -41, 71, 40, -44, + 67, 9, 71, 67, 9, 70, 67, 9, 69, 67, 9, 67, + 67, 9, 65, 67, 9, 62, 67, 10, 59, 67, 10, 56, + 67, 11, 52, 67, 11, 49, 67, 12, 45, 67, 12, 41, + 67, 13, 36, 67, 14, 32, 68, 15, 28, 68, 15, 24, + 68, 16, 19, 68, 17, 15, 68, 19, 11, 68, 20, 7, + 69, 21, 2, 69, 22, -2, 69, 24, -6, 69, 25, -10, + 70, 26, -15, 70, 28, -19, 70, 30, -23, 71, 31, -27, + 71, 33, -31, 71, 35, -35, 72, 36, -39, 72, 38, -43, + 68, 7, 71, 68, 7, 70, 68, 7, 69, 68, 7, 68, + 68, 7, 65, 68, 7, 63, 68, 8, 60, 68, 8, 57, + 68, 8, 53, 68, 9, 50, 68, 9, 46, 68, 10, 42, + 68, 11, 38, 68, 12, 33, 69, 12, 29, 69, 13, 25, + 69, 14, 20, 69, 15, 16, 69, 16, 12, 69, 18, 8, + 70, 19, 3, 70, 20, -1, 70, 21, -5, 70, 23, -9, + 71, 24, -13, 71, 26, -18, 71, 27, -22, 71, 29, -26, + 72, 31, -30, 72, 32, -34, 72, 34, -38, 73, 36, -42, + 69, 4, 72, 69, 4, 71, 69, 5, 70, 69, 5, 68, + 69, 5, 66, 69, 5, 64, 69, 5, 61, 69, 6, 58, + 69, 6, 54, 69, 7, 51, 69, 7, 47, 69, 8, 43, + 69, 9, 39, 69, 9, 35, 70, 10, 31, 70, 11, 26, + 70, 12, 22, 70, 13, 18, 70, 14, 13, 70, 15, 9, + 71, 17, 5, 71, 18, 1, 71, 19, -4, 71, 21, -8, + 72, 22, -12, 72, 24, -16, 72, 25, -20, 72, 27, -24, + 73, 28, -28, 73, 30, -32, 73, 32, -36, 74, 34, -40, + 70, 2, 73, 70, 2, 72, 70, 2, 71, 70, 3, 69, + 70, 3, 67, 70, 3, 65, 70, 3, 62, 70, 4, 59, + 70, 4, 55, 70, 5, 52, 70, 5, 48, 70, 6, 44, + 70, 7, 40, 70, 7, 36, 71, 8, 32, 71, 9, 28, + 71, 10, 23, 71, 11, 19, 71, 12, 15, 71, 13, 11, + 72, 15, 6, 72, 16, 2, 72, 17, -2, 72, 18, -6, + 73, 20, -10, 73, 22, -15, 73, 23, -19, 73, 25, -23, + 74, 26, -27, 74, 28, -31, 74, 30, -35, 75, 32, -39, + 71, 0, 74, 71, 0, 73, 71, 0, 72, 71, 0, 70, + 71, 1, 68, 71, 1, 66, 71, 1, 63, 71, 2, 60, + 71, 2, 56, 71, 2, 53, 71, 3, 49, 71, 4, 46, + 71, 4, 41, 72, 5, 37, 72, 6, 33, 72, 7, 29, + 72, 8, 25, 72, 9, 20, 72, 10, 16, 72, 11, 12, + 73, 12, 7, 73, 14, 3, 73, 15, -1, 73, 16, -5, + 73, 18, -9, 74, 19, -13, 74, 21, -17, 74, 22, -21, + 75, 24, -25, 75, 26, -30, 75, 28, -33, 76, 29, -37, + 72, -2, 74, 72, -2, 73, 72, -2, 72, 72, -2, 71, + 72, -1, 69, 72, -1, 67, 72, -1, 64, 72, -1, 61, + 72, 0, 58, 72, 0, 54, 72, 1, 51, 72, 1, 47, + 72, 2, 42, 73, 3, 39, 73, 4, 35, 73, 5, 30, + 73, 6, 26, 73, 7, 22, 73, 8, 18, 73, 9, 14, + 74, 10, 9, 74, 11, 5, 74, 13, 1, 74, 14, -3, + 74, 15, -7, 75, 17, -12, 75, 19, -16, 75, 20, -20, + 76, 22, -24, 76, 24, -28, 76, 25, -32, 76, 27, -36, + 73, -4, 75, 73, -4, 74, 73, -4, 73, 73, -4, 72, + 73, -4, 70, 73, -3, 68, 73, -3, 65, 73, -3, 62, + 73, -2, 59, 73, -2, 55, 73, -1, 52, 73, -1, 48, + 73, 0, 44, 74, 1, 40, 74, 2, 36, 74, 2, 32, + 74, 4, 27, 74, 5, 23, 74, 6, 19, 74, 7, 15, + 75, 8, 10, 75, 9, 6, 75, 11, 2, 75, 12, -2, + 75, 13, -6, 76, 15, -10, 76, 16, -14, 76, 18, -18, + 76, 20, -22, 77, 22, -27, 77, 23, -31, 77, 25, -34, + 74, -6, 76, 74, -6, 75, 74, -6, 74, 74, -6, 72, + 74, -6, 71, 74, -5, 68, 74, -5, 66, 74, -5, 63, + 74, -4, 60, 74, -4, 56, 74, -3, 53, 74, -3, 49, + 74, -2, 45, 75, -1, 41, 75, 0, 37, 75, 0, 33, + 75, 1, 29, 75, 2, 25, 75, 3, 20, 75, 5, 16, + 76, 6, 12, 76, 7, 8, 76, 8, 4, 76, 10, 0, + 76, 11, -4, 77, 13, -9, 77, 14, -13, 77, 16, -17, + 77, 18, -21, 78, 19, -25, 78, 21, -29, 78, 23, -33, + 75, -8, 77, 75, -8, 76, 75, -8, 75, 75, -8, 73, + 75, -8, 72, 75, -8, 69, 75, -7, 67, 75, -7, 64, + 75, -6, 61, 75, -6, 58, 75, -5, 54, 75, -5, 50, + 76, -4, 46, 76, -3, 42, 76, -3, 38, 76, -2, 35, + 76, -1, 30, 76, 0, 26, 76, 1, 22, 76, 2, 18, + 77, 4, 13, 77, 5, 9, 77, 6, 5, 77, 8, 1, + 77, 9, -3, 78, 11, -7, 78, 12, -11, 78, 14, -15, + 78, 15, -19, 79, 17, -24, 79, 19, -28, 79, 21, -31, + 76, -10, 77, 76, -10, 77, 76, -10, 76, 76, -10, 74, + 76, -10, 72, 76, -10, 70, 76, -9, 68, 76, -9, 65, + 76, -8, 62, 76, -8, 59, 76, -8, 55, 76, -7, 52, + 77, -6, 48, 77, -5, 44, 77, -5, 40, 77, -4, 36, + 77, -3, 31, 77, -2, 27, 77, -1, 23, 77, 0, 19, + 78, 2, 15, 78, 3, 11, 78, 4, 7, 78, 5, 3, + 78, 7, -1, 79, 8, -6, 79, 10, -10, 79, 12, -14, + 79, 13, -18, 80, 15, -22, 80, 17, -26, 80, 18, -30, + 77, -12, 78, 77, -12, 77, 77, -12, 76, 77, -12, 75, + 77, -12, 73, 77, -12, 71, 77, -11, 69, 77, -11, 66, + 77, -11, 63, 77, -10, 60, 77, -10, 56, 78, -9, 53, + 78, -8, 49, 78, -8, 45, 78, -7, 41, 78, -6, 37, + 78, -5, 33, 78, -4, 29, 78, -3, 25, 79, -2, 21, + 79, 0, 16, 79, 1, 12, 79, 2, 8, 79, 3, 4, + 79, 5, 0, 80, 6, -4, 80, 8, -8, 80, 9, -12, + 80, 11, -16, 81, 13, -21, 81, 15, -25, 81, 16, -28, + 78, -14, 79, 78, -14, 78, 78, -14, 77, 78, -14, 76, + 78, -14, 74, 78, -14, 72, 78, -13, 70, 78, -13, 67, + 78, -13, 64, 78, -12, 61, 79, -12, 58, 79, -11, 54, + 79, -10, 50, 79, -10, 46, 79, -9, 42, 79, -8, 39, + 79, -7, 34, 79, -6, 30, 79, -5, 26, 80, -4, 22, + 80, -3, 18, 80, -1, 14, 80, 0, 10, 80, 1, 5, + 80, 3, 1, 81, 4, -3, 81, 6, -7, 81, 7, -11, + 81, 9, -15, 82, 11, -19, 82, 12, -23, 82, 14, -27, + 79, -16, 80, 79, -16, 79, 79, -16, 78, 79, -16, 77, + 79, -16, 75, 79, -16, 73, 79, -15, 71, 79, -15, 68, + 79, -15, 65, 80, -14, 62, 80, -14, 59, 80, -13, 55, + 80, -12, 51, 80, -12, 48, 80, -11, 44, 80, -10, 40, + 80, -9, 35, 80, -8, 32, 80, -7, 28, 81, -6, 24, + 81, -5, 19, 81, -3, 15, 81, -2, 11, 81, -1, 7, + 82, 0, 3, 82, 2, -2, 82, 4, -5, 82, 5, -9, + 82, 7, -13, 83, 9, -18, 83, 10, -22, 83, 12, -25, + 80, -18, 80, 80, -18, 80, 80, -18, 79, 80, -18, 78, + 80, -18, 76, 80, -18, 74, 80, -17, 72, 80, -17, 69, + 81, -17, 66, 81, -16, 63, 81, -16, 60, 81, -15, 56, + 81, -14, 52, 81, -14, 49, 81, -13, 45, 81, -12, 41, + 81, -11, 37, 81, -10, 33, 82, -9, 29, 82, -8, 25, + 82, -7, 20, 82, -6, 16, 82, -4, 12, 82, -3, 8, + 83, -2, 4, 83, 0, 0, 83, 1, -4, 83, 3, -8, + 83, 5, -12, 84, 6, -16, 84, 8, -20, 84, 10, -24, + 81, -20, 81, 81, -20, 81, 81, -20, 80, 81, -20, 78, + 81, -20, 77, 81, -20, 75, 82, -19, 73, 82, -19, 70, + 82, -19, 67, 82, -18, 64, 82, -18, 61, 82, -17, 58, + 82, -16, 54, 82, -16, 50, 82, -15, 46, 82, -14, 43, + 82, -13, 38, 82, -12, 34, 83, -11, 30, 83, -10, 26, + 83, -9, 22, 83, -8, 18, 83, -6, 14, 83, -5, 10, + 84, -4, 6, 84, -2, 1, 84, -1, -2, 84, 1, -6, + 84, 2, -10, 85, 4, -15, 85, 6, -19, 85, 8, -22, + 83, -23, 82, 83, -23, 82, 83, -23, 81, 83, -22, 79, + 83, -22, 78, 83, -22, 76, 83, -22, 74, 83, -21, 71, + 83, -21, 69, 83, -21, 66, 83, -20, 62, 83, -20, 59, + 83, -19, 55, 83, -18, 52, 83, -17, 48, 84, -17, 44, + 84, -16, 40, 84, -15, 36, 84, -14, 32, 84, -13, 28, + 84, -11, 24, 84, -10, 20, 85, -9, 16, 85, -8, 12, + 85, -6, 8, 85, -5, 3, 85, -3, -1, 86, -2, -5, + 86, 0, -8, 86, 2, -13, 86, 3, -17, 86, 5, -20, + 84, -25, 83, 84, -25, 82, 84, -25, 82, 84, -24, 80, + 84, -24, 79, 84, -24, 77, 84, -24, 75, 84, -23, 72, + 84, -23, 70, 84, -23, 67, 84, -22, 64, 84, -21, 60, + 84, -21, 57, 84, -20, 53, 84, -19, 49, 85, -19, 46, + 85, -18, 41, 85, -17, 37, 85, -16, 34, 85, -15, 30, + 85, -13, 25, 85, -12, 21, 86, -11, 17, 86, -10, 13, + 86, -8, 9, 86, -7, 5, 86, -5, 1, 87, -4, -3, + 87, -2, -7, 87, 0, -11, 87, 1, -15, 87, 3, -19, + 85, -27, 84, 85, -27, 83, 85, -26, 82, 85, -26, 81, + 85, -26, 80, 85, -26, 78, 85, -26, 76, 85, -25, 73, + 85, -25, 71, 85, -24, 68, 85, -24, 65, 85, -23, 62, + 85, -23, 58, 85, -22, 54, 86, -21, 51, 86, -21, 47, + 86, -20, 43, 86, -19, 39, 86, -18, 35, 86, -17, 31, + 86, -15, 27, 86, -14, 23, 87, -13, 19, 87, -12, 15, + 87, -10, 11, 87, -9, 6, 87, -7, 2, 88, -6, -1, + 88, -4, -5, 88, -2, -10, 88, -1, -14, 89, 1, -17, + 86, -28, 85, 86, -28, 84, 86, -28, 83, 86, -28, 82, + 86, -28, 81, 86, -28, 79, 86, -28, 77, 86, -27, 74, + 86, -27, 72, 86, -26, 69, 86, -26, 66, 86, -25, 63, + 86, -25, 59, 87, -24, 55, 87, -23, 52, 87, -22, 48, + 87, -22, 44, 87, -21, 40, 87, -20, 36, 87, -19, 32, + 87, -17, 28, 88, -16, 24, 88, -15, 20, 88, -14, 16, + 88, -12, 12, 88, -11, 8, 88, -9, 4, 89, -8, 0, + 89, -6, -4, 89, -5, -8, 89, -3, -12, 90, -1, -16, + 87, -30, 85, 87, -30, 85, 87, -30, 84, 87, -30, 83, + 87, -30, 82, 87, -30, 80, 87, -29, 78, 87, -29, 75, + 87, -29, 73, 87, -28, 70, 87, -28, 67, 87, -27, 64, + 88, -27, 60, 88, -26, 57, 88, -25, 53, 88, -24, 50, + 88, -23, 45, 88, -23, 42, 88, -22, 38, 88, -21, 34, + 88, -19, 29, 89, -18, 26, 89, -17, 22, 89, -16, 18, + 89, -14, 14, 89, -13, 9, 89, -11, 5, 90, -10, 2, + 90, -8, -2, 90, -7, -7, 90, -5, -11, 91, -3, -14, + 88, -32, 86, 88, -32, 86, 88, -32, 85, 88, -32, 84, + 88, -32, 82, 88, -32, 81, 88, -31, 79, 88, -31, 76, + 88, -31, 74, 88, -30, 71, 89, -30, 68, 89, -29, 65, + 89, -28, 61, 89, -28, 58, 89, -27, 54, 89, -26, 51, + 89, -25, 47, 89, -24, 43, 89, -24, 39, 89, -22, 35, + 90, -21, 31, 90, -20, 27, 90, -19, 23, 90, -18, 19, + 90, -16, 15, 90, -15, 11, 91, -13, 7, 91, -12, 3, + 91, -10, -1, 91, -9, -5, 91, -7, -9, 92, -5, -13, + 89, -34, 87, 89, -34, 86, 89, -34, 86, 89, -34, 85, + 89, -34, 83, 89, -33, 82, 89, -33, 80, 89, -33, 77, + 90, -32, 75, 90, -32, 72, 90, -31, 69, 90, -31, 66, + 90, -30, 63, 90, -30, 59, 90, -29, 56, 90, -28, 52, + 90, -27, 48, 90, -26, 44, 90, -25, 41, 90, -24, 37, + 91, -23, 32, 91, -22, 28, 91, -21, 25, 91, -20, 21, + 91, -18, 17, 91, -17, 12, 92, -15, 8, 92, -14, 5, + 92, -12, 1, 92, -11, -4, 92, -9, -7, 93, -7, -11, + 90, -36, 88, 90, -36, 87, 90, -36, 87, 90, -36, 86, + 90, -35, 84, 90, -35, 83, 91, -35, 81, 91, -35, 78, + 91, -34, 76, 91, -34, 73, 91, -33, 70, 91, -33, 67, + 91, -32, 64, 91, -32, 60, 91, -31, 57, 91, -30, 53, + 91, -29, 49, 91, -28, 46, 91, -27, 42, 92, -26, 38, + 92, -25, 34, 92, -24, 30, 92, -23, 26, 92, -22, 22, + 92, -20, 18, 92, -19, 14, 93, -17, 10, 93, -16, 6, + 93, -14, 2, 93, -13, -2, 93, -11, -6, 94, -9, -10, + 92, -38, 89, 92, -38, 88, 92, -37, 88, 92, -37, 86, + 92, -37, 85, 92, -37, 84, 92, -37, 82, 92, -36, 79, + 92, -36, 77, 92, -36, 74, 92, -35, 72, 92, -35, 69, + 92, -34, 65, 92, -33, 62, 92, -33, 58, 92, -32, 55, + 92, -31, 51, 92, -30, 47, 92, -29, 43, 93, -28, 40, + 93, -27, 35, 93, -26, 31, 93, -25, 28, 93, -24, 24, + 93, -22, 20, 93, -21, 15, 94, -19, 12, 94, -18, 8, + 94, -16, 4, 94, -15, -1, 94, -13, -4, 95, -11, -8, + 93, -39, 90, 93, -39, 89, 93, -39, 88, 93, -39, 87, + 93, -39, 86, 93, -39, 85, 93, -39, 83, 93, -38, 80, + 93, -38, 78, 93, -37, 76, 93, -37, 73, 93, -36, 70, + 93, -36, 66, 93, -35, 63, 93, -35, 60, 93, -34, 56, + 93, -33, 52, 93, -32, 48, 94, -31, 45, 94, -30, 41, + 94, -29, 37, 94, -28, 33, 94, -27, 29, 94, -25, 25, + 94, -24, 21, 95, -23, 17, 95, -21, 13, 95, -20, 9, + 95, -18, 5, 95, -17, 1, 96, -15, -3, 96, -13, -7, + 94, -41, 90, 94, -41, 90, 94, -41, 89, 94, -41, 88, + 94, -41, 87, 94, -41, 86, 94, -40, 84, 94, -40, 81, + 94, -40, 79, 94, -39, 77, 94, -39, 74, 94, -38, 71, + 94, -38, 67, 94, -37, 64, 94, -36, 61, 94, -36, 57, + 94, -35, 53, 95, -34, 50, 95, -33, 46, 95, -32, 42, + 95, -31, 38, 95, -30, 34, 95, -29, 30, 95, -27, 27, + 95, -26, 23, 96, -25, 18, 96, -23, 15, 96, -22, 11, + 96, -20, 7, 96, -19, 3, 97, -17, -1, 97, -15, -5, + 45, 70, 59, 45, 70, 56, 45, 70, 54, 45, 70, 50, + 45, 71, 46, 45, 71, 42, 45, 71, 37, 45, 71, 32, + 45, 72, 27, 45, 72, 23, 46, 72, 18, 46, 73, 13, + 46, 74, 8, 46, 74, 3, 46, 75, -1, 47, 75, -6, + 47, 76, -11, 47, 77, -15, 48, 78, -20, 48, 79, -24, + 48, 80, -29, 49, 81, -33, 49, 82, -37, 49, 83, -41, + 50, 84, -45, 50, 85, -49, 51, 86, -53, 51, 87, -57, + 52, 88, -61, 52, 90, -65, 53, 91, -69, 54, 92, -72, + 45, 70, 59, 45, 70, 56, 45, 70, 54, 45, 70, 50, + 45, 70, 46, 45, 70, 42, 45, 71, 38, 45, 71, 32, + 45, 71, 28, 45, 72, 23, 46, 72, 18, 46, 73, 13, + 46, 73, 8, 46, 74, 3, 46, 74, -1, 47, 75, -6, + 47, 76, -11, 47, 77, -15, 48, 77, -20, 48, 78, -24, + 48, 79, -29, 49, 80, -33, 49, 81, -37, 50, 82, -41, + 50, 83, -45, 50, 85, -49, 51, 86, -53, 51, 87, -57, + 52, 88, -61, 53, 90, -65, 53, 91, -68, 54, 92, -72, + 45, 69, 59, 45, 70, 56, 45, 70, 54, 45, 70, 50, + 45, 70, 46, 45, 70, 42, 45, 70, 38, 45, 71, 32, + 45, 71, 28, 46, 71, 23, 46, 72, 18, 46, 72, 13, + 46, 73, 8, 46, 73, 4, 47, 74, -1, 47, 75, -6, + 47, 76, -11, 47, 76, -15, 48, 77, -19, 48, 78, -24, + 48, 79, -28, 49, 80, -33, 49, 81, -37, 50, 82, -41, + 50, 83, -45, 51, 84, -49, 51, 85, -53, 52, 87, -57, + 52, 88, -61, 53, 89, -65, 53, 90, -68, 54, 92, -72, + 45, 69, 59, 45, 69, 57, 45, 69, 54, 45, 69, 50, + 45, 70, 47, 45, 70, 42, 45, 70, 38, 45, 70, 33, + 46, 71, 28, 46, 71, 23, 46, 71, 18, 46, 72, 14, + 46, 73, 8, 46, 73, 4, 47, 74, -1, 47, 74, -5, + 47, 75, -10, 48, 76, -15, 48, 77, -19, 48, 78, -24, + 49, 79, -28, 49, 80, -32, 49, 81, -37, 50, 82, -41, + 50, 83, -45, 51, 84, -49, 51, 85, -53, 52, 86, -57, + 52, 88, -60, 53, 89, -64, 53, 90, -68, 54, 91, -72, + 45, 69, 59, 45, 69, 57, 45, 69, 54, 45, 69, 50, + 45, 69, 47, 45, 69, 42, 45, 70, 38, 46, 70, 33, + 46, 70, 28, 46, 71, 23, 46, 71, 19, 46, 71, 14, + 46, 72, 9, 47, 73, 4, 47, 73, -1, 47, 74, -5, + 47, 75, -10, 48, 76, -15, 48, 76, -19, 48, 77, -23, + 49, 78, -28, 49, 79, -32, 49, 80, -36, 50, 81, -40, + 50, 82, -44, 51, 84, -49, 51, 85, -53, 52, 86, -56, + 52, 87, -60, 53, 89, -64, 53, 90, -68, 54, 91, -72, + 45, 68, 59, 45, 68, 57, 45, 68, 54, 45, 68, 51, + 46, 69, 47, 46, 69, 43, 46, 69, 38, 46, 69, 33, + 46, 70, 28, 46, 70, 24, 46, 71, 19, 46, 71, 14, + 47, 72, 9, 47, 72, 4, 47, 73, 0, 47, 74, -5, + 48, 74, -10, 48, 75, -14, 48, 76, -19, 49, 77, -23, + 49, 78, -28, 49, 79, -32, 50, 80, -36, 50, 81, -40, + 50, 82, -44, 51, 83, -49, 51, 84, -52, 52, 86, -56, + 52, 87, -60, 53, 88, -64, 54, 89, -68, 54, 91, -71, + 46, 68, 59, 46, 68, 57, 46, 68, 54, 46, 68, 51, + 46, 68, 47, 46, 68, 43, 46, 68, 38, 46, 69, 33, + 46, 69, 29, 46, 69, 24, 46, 70, 19, 47, 70, 14, + 47, 71, 9, 47, 72, 4, 47, 72, 0, 47, 73, -5, + 48, 74, -10, 48, 75, -14, 48, 75, -19, 49, 76, -23, + 49, 77, -28, 49, 78, -32, 50, 79, -36, 50, 80, -40, + 51, 81, -44, 51, 83, -48, 52, 84, -52, 52, 85, -56, + 53, 86, -60, 53, 88, -64, 54, 89, -67, 54, 90, -71, + 46, 67, 59, 46, 67, 57, 46, 67, 55, 46, 67, 51, + 46, 67, 47, 46, 67, 43, 46, 68, 39, 46, 68, 34, + 46, 68, 29, 46, 69, 24, 47, 69, 19, 47, 70, 15, + 47, 70, 9, 47, 71, 5, 47, 72, 0, 48, 72, -4, + 48, 73, -9, 48, 74, -14, 49, 75, -18, 49, 76, -22, + 49, 77, -27, 50, 78, -31, 50, 79, -35, 50, 80, -40, + 51, 81, -44, 51, 82, -48, 52, 83, -52, 52, 84, -56, + 53, 86, -59, 53, 87, -63, 54, 88, -67, 54, 90, -71, + 46, 66, 59, 46, 66, 57, 46, 66, 55, 46, 66, 51, + 46, 67, 47, 46, 67, 43, 46, 67, 39, 46, 67, 34, + 47, 68, 29, 47, 68, 24, 47, 68, 20, 47, 69, 15, + 47, 70, 10, 47, 70, 5, 48, 71, 1, 48, 72, -4, + 48, 72, -9, 49, 73, -13, 49, 74, -18, 49, 75, -22, + 50, 76, -27, 50, 77, -31, 50, 78, -35, 51, 79, -39, + 51, 80, -43, 52, 81, -48, 52, 83, -51, 53, 84, -55, + 53, 85, -59, 54, 86, -63, 54, 88, -67, 55, 89, -70, + 46, 65, 60, 46, 65, 57, 46, 65, 55, 46, 65, 51, + 47, 66, 48, 47, 66, 44, 47, 66, 39, 47, 66, 34, + 47, 67, 30, 47, 67, 25, 47, 68, 20, 47, 68, 15, + 48, 69, 10, 48, 69, 6, 48, 70, 1, 48, 71, -4, + 49, 72, -9, 49, 72, -13, 49, 73, -17, 49, 74, -22, + 50, 75, -26, 50, 76, -31, 51, 77, -35, 51, 78, -39, + 51, 79, -43, 52, 81, -47, 52, 82, -51, 53, 83, -55, + 53, 84, -59, 54, 86, -63, 54, 87, -66, 55, 88, -70, + 47, 64, 60, 47, 64, 57, 47, 64, 55, 47, 65, 52, + 47, 65, 48, 47, 65, 44, 47, 65, 40, 47, 65, 35, + 47, 66, 30, 47, 66, 25, 48, 67, 21, 48, 67, 16, + 48, 68, 11, 48, 68, 6, 48, 69, 1, 49, 70, -3, + 49, 71, -8, 49, 71, -13, 49, 72, -17, 50, 73, -21, + 50, 74, -26, 50, 75, -30, 51, 76, -34, 51, 77, -38, + 52, 79, -42, 52, 80, -47, 53, 81, -51, 53, 82, -54, + 54, 83, -58, 54, 85, -62, 55, 86, -66, 55, 88, -70, + 47, 63, 60, 47, 63, 58, 47, 63, 55, 47, 63, 52, + 47, 63, 48, 47, 64, 44, 47, 64, 40, 48, 64, 35, + 48, 65, 31, 48, 65, 26, 48, 65, 21, 48, 66, 17, + 48, 67, 11, 49, 67, 7, 49, 68, 2, 49, 69, -2, + 49, 69, -7, 50, 70, -12, 50, 71, -16, 50, 72, -21, + 51, 73, -25, 51, 74, -29, 51, 75, -34, 52, 76, -38, + 52, 77, -42, 53, 79, -46, 53, 80, -50, 53, 81, -54, + 54, 82, -58, 54, 84, -62, 55, 85, -65, 55, 87, -69, + 48, 62, 60, 48, 62, 58, 48, 62, 56, 48, 62, 52, + 48, 62, 49, 48, 62, 45, 48, 63, 41, 48, 63, 36, + 48, 63, 31, 48, 64, 26, 48, 64, 22, 49, 65, 17, + 49, 65, 12, 49, 66, 7, 49, 67, 3, 49, 67, -2, + 50, 68, -7, 50, 69, -11, 50, 70, -16, 51, 71, -20, + 51, 72, -25, 51, 73, -29, 52, 74, -33, 52, 75, -37, + 52, 76, -41, 53, 78, -45, 53, 79, -49, 54, 80, -53, + 54, 81, -57, 55, 83, -61, 55, 84, -65, 56, 86, -68, + 48, 61, 60, 48, 61, 58, 48, 61, 56, 48, 61, 53, + 48, 61, 49, 48, 61, 45, 48, 61, 41, 48, 62, 36, + 49, 62, 32, 49, 63, 27, 49, 63, 22, 49, 64, 18, + 49, 64, 12, 49, 65, 8, 50, 65, 3, 50, 66, -1, + 50, 67, -6, 50, 68, -11, 51, 69, -15, 51, 70, -19, + 51, 71, -24, 52, 72, -28, 52, 73, -32, 52, 74, -36, + 53, 75, -40, 53, 77, -45, 54, 78, -49, 54, 79, -53, + 55, 80, -56, 55, 82, -61, 56, 83, -64, 56, 85, -68, + 49, 59, 60, 49, 59, 58, 49, 59, 56, 49, 59, 53, + 49, 60, 50, 49, 60, 46, 49, 60, 42, 49, 60, 37, + 49, 61, 32, 49, 61, 28, 49, 62, 23, 49, 62, 18, + 50, 63, 13, 50, 63, 9, 50, 64, 4, 50, 65, -1, + 51, 66, -6, 51, 67, -10, 51, 68, -14, 51, 68, -19, + 52, 70, -23, 52, 71, -28, 52, 72, -32, 53, 73, -36, + 53, 74, -40, 54, 75, -44, 54, 77, -48, 55, 78, -52, + 55, 79, -56, 56, 81, -60, 56, 82, -64, 57, 83, -67, + 49, 58, 61, 49, 58, 59, 49, 58, 57, 49, 58, 53, + 49, 58, 50, 49, 58, 46, 49, 59, 42, 49, 59, 37, + 50, 59, 33, 50, 60, 28, 50, 60, 24, 50, 61, 19, + 50, 61, 14, 50, 62, 9, 51, 63, 5, 51, 64, 0, + 51, 64, -5, 51, 65, -9, 52, 66, -14, 52, 67, -18, + 52, 68, -23, 53, 69, -27, 53, 70, -31, 53, 72, -35, + 54, 73, -39, 54, 74, -44, 55, 75, -47, 55, 77, -51, + 55, 78, -55, 56, 79, -59, 56, 81, -63, 57, 82, -67, + 50, 56, 61, 50, 56, 59, 50, 56, 57, 50, 57, 54, + 50, 57, 50, 50, 57, 47, 50, 57, 43, 50, 58, 38, + 50, 58, 33, 50, 58, 29, 50, 59, 24, 50, 59, 20, + 51, 60, 15, 51, 61, 10, 51, 61, 5, 51, 62, 1, + 52, 63, -4, 52, 64, -9, 52, 65, -13, 52, 66, -17, + 53, 67, -22, 53, 68, -26, 53, 69, -30, 54, 70, -34, + 54, 71, -38, 55, 73, -43, 55, 74, -47, 55, 75, -51, + 56, 77, -54, 56, 78, -59, 57, 80, -62, 57, 81, -66, + 50, 55, 61, 50, 55, 59, 50, 55, 57, 50, 55, 54, + 50, 55, 51, 50, 55, 47, 50, 56, 43, 51, 56, 38, + 51, 56, 34, 51, 57, 30, 51, 57, 25, 51, 58, 20, + 51, 59, 15, 51, 59, 11, 52, 60, 6, 52, 61, 2, + 52, 62, -3, 52, 62, -8, 53, 63, -12, 53, 64, -16, + 53, 65, -21, 54, 67, -25, 54, 68, -29, 54, 69, -34, + 55, 70, -38, 55, 71, -42, 55, 73, -46, 56, 74, -50, + 56, 75, -54, 57, 77, -58, 57, 78, -62, 58, 80, -65, + 51, 53, 62, 51, 53, 60, 51, 53, 58, 51, 53, 55, + 51, 54, 52, 51, 54, 48, 51, 54, 44, 51, 54, 39, + 51, 55, 35, 51, 55, 30, 51, 56, 26, 52, 56, 21, + 52, 57, 16, 52, 58, 12, 52, 58, 7, 52, 59, 3, + 53, 60, -2, 53, 61, -7, 53, 62, -11, 53, 63, -16, + 54, 64, -20, 54, 65, -24, 54, 66, -29, 55, 67, -33, + 55, 69, -37, 56, 70, -41, 56, 71, -45, 56, 73, -49, + 57, 74, -53, 57, 75, -57, 58, 77, -61, 58, 78, -64, + 51, 51, 62, 51, 52, 60, 51, 52, 58, 51, 52, 55, + 52, 52, 52, 52, 52, 48, 52, 52, 45, 52, 53, 40, + 52, 53, 35, 52, 54, 31, 52, 54, 27, 52, 55, 22, + 52, 55, 17, 53, 56, 12, 53, 57, 8, 53, 57, 3, + 53, 58, -2, 53, 59, -6, 54, 60, -10, 54, 61, -15, + 54, 62, -19, 55, 63, -24, 55, 65, -28, 55, 66, -32, + 56, 67, -36, 56, 68, -40, 57, 70, -44, 57, 71, -48, + 57, 72, -52, 58, 74, -56, 58, 75, -60, 59, 77, -64, + 52, 50, 62, 52, 50, 60, 52, 50, 59, 52, 50, 56, + 52, 50, 53, 52, 50, 49, 52, 51, 45, 52, 51, 41, + 52, 51, 36, 53, 52, 32, 53, 52, 27, 53, 53, 23, + 53, 54, 18, 53, 54, 13, 53, 55, 9, 54, 56, 4, + 54, 57, -1, 54, 58, -5, 54, 59, -9, 55, 60, -14, + 55, 61, -19, 55, 62, -23, 56, 63, -27, 56, 64, -31, + 56, 65, -35, 57, 67, -39, 57, 68, -43, 58, 69, -47, + 58, 71, -51, 58, 72, -55, 59, 74, -59, 59, 75, -63, + 53, 48, 63, 53, 48, 61, 53, 48, 59, 53, 48, 56, + 53, 48, 53, 53, 49, 50, 53, 49, 46, 53, 49, 41, + 53, 50, 37, 53, 50, 33, 53, 51, 28, 54, 51, 24, + 54, 52, 19, 54, 52, 14, 54, 53, 10, 54, 54, 5, + 55, 55, 0, 55, 56, -4, 55, 57, -8, 55, 58, -13, + 56, 59, -18, 56, 60, -22, 56, 61, -26, 57, 62, -30, + 57, 64, -34, 57, 65, -39, 58, 67, -42, 58, 68, -46, + 59, 69, -50, 59, 71, -54, 59, 72, -58, 60, 74, -62, + 53, 46, 63, 53, 46, 61, 53, 46, 59, 54, 46, 57, + 54, 47, 54, 54, 47, 50, 54, 47, 47, 54, 47, 42, + 54, 48, 38, 54, 48, 34, 54, 49, 29, 54, 49, 25, + 54, 50, 20, 55, 51, 15, 55, 51, 11, 55, 52, 6, + 55, 53, 1, 55, 54, -3, 56, 55, -7, 56, 56, -12, + 56, 57, -17, 57, 58, -21, 57, 60, -25, 57, 61, -29, + 58, 62, -33, 58, 63, -38, 58, 65, -41, 59, 66, -45, + 59, 68, -49, 60, 69, -53, 60, 71, -57, 60, 72, -61, + 54, 44, 63, 54, 44, 62, 54, 44, 60, 54, 44, 57, + 54, 45, 54, 54, 45, 51, 54, 45, 47, 54, 46, 43, + 55, 46, 39, 55, 46, 34, 55, 47, 30, 55, 47, 26, + 55, 48, 21, 55, 49, 16, 55, 50, 12, 56, 50, 7, + 56, 51, 2, 56, 52, -2, 56, 53, -6, 57, 54, -11, + 57, 55, -16, 57, 57, -20, 58, 58, -24, 58, 59, -28, + 58, 60, -32, 59, 62, -37, 59, 63, -40, 59, 64, -44, + 60, 66, -48, 60, 68, -53, 61, 69, -56, 61, 71, -60, + 55, 42, 64, 55, 42, 62, 55, 42, 61, 55, 43, 58, + 55, 43, 55, 55, 43, 52, 55, 43, 48, 55, 44, 44, + 55, 44, 40, 55, 44, 35, 56, 45, 31, 56, 46, 27, + 56, 46, 22, 56, 47, 17, 56, 48, 13, 56, 48, 8, + 57, 49, 3, 57, 50, -1, 57, 51, -5, 57, 52, -10, + 58, 54, -14, 58, 55, -19, 58, 56, -23, 59, 57, -27, + 59, 58, -31, 59, 60, -35, 60, 61, -39, 60, 63, -43, + 60, 64, -47, 61, 66, -51, 61, 67, -55, 62, 69, -59, + 56, 40, 64, 56, 40, 63, 56, 40, 61, 56, 41, 59, + 56, 41, 56, 56, 41, 52, 56, 41, 49, 56, 42, 45, + 56, 42, 40, 56, 42, 36, 56, 43, 32, 56, 44, 27, + 57, 44, 23, 57, 45, 18, 57, 46, 14, 57, 47, 9, + 57, 47, 4, 58, 48, 0, 58, 49, -4, 58, 50, -9, + 58, 52, -13, 59, 53, -18, 59, 54, -22, 59, 55, -26, + 60, 57, -30, 60, 58, -34, 60, 59, -38, 61, 61, -42, + 61, 62, -46, 62, 64, -50, 62, 65, -54, 62, 67, -58, + 56, 38, 65, 57, 38, 63, 57, 38, 62, 57, 39, 59, + 57, 39, 56, 57, 39, 53, 57, 39, 50, 57, 40, 45, + 57, 40, 41, 57, 40, 37, 57, 41, 33, 57, 42, 29, + 57, 42, 24, 58, 43, 19, 58, 44, 15, 58, 45, 10, + 58, 46, 5, 58, 46, 1, 59, 47, -3, 59, 48, -8, + 59, 50, -12, 59, 51, -16, 60, 52, -21, 60, 53, -25, + 60, 55, -29, 61, 56, -33, 61, 58, -37, 61, 59, -41, + 62, 60, -45, 62, 62, -49, 63, 64, -53, 63, 65, -57, + 57, 36, 65, 57, 36, 64, 57, 36, 62, 57, 36, 60, + 57, 37, 57, 57, 37, 54, 58, 37, 50, 58, 38, 46, + 58, 38, 42, 58, 38, 38, 58, 39, 34, 58, 40, 30, + 58, 40, 25, 58, 41, 20, 58, 42, 16, 59, 43, 11, + 59, 44, 7, 59, 44, 2, 59, 45, -2, 60, 47, -6, + 60, 48, -11, 60, 49, -15, 60, 50, -19, 61, 51, -24, + 61, 53, -28, 61, 54, -32, 62, 56, -36, 62, 57, -40, + 62, 59, -44, 63, 60, -48, 63, 62, -52, 64, 63, -56, + 58, 34, 66, 58, 34, 64, 58, 34, 63, 58, 34, 60, + 58, 35, 58, 58, 35, 55, 58, 35, 51, 58, 36, 47, + 59, 36, 43, 59, 36, 39, 59, 37, 35, 59, 37, 31, + 59, 38, 26, 59, 39, 21, 59, 40, 17, 59, 40, 13, + 60, 41, 8, 60, 42, 3, 60, 43, -1, 60, 44, -5, + 61, 46, -10, 61, 47, -14, 61, 48, -18, 61, 49, -22, + 62, 51, -26, 62, 52, -31, 62, 54, -35, 63, 55, -39, + 63, 57, -43, 64, 58, -47, 64, 60, -51, 64, 61, -55, + 59, 32, 66, 59, 32, 65, 59, 32, 63, 59, 32, 61, + 59, 33, 59, 59, 33, 56, 59, 33, 52, 59, 33, 48, + 59, 34, 44, 59, 34, 40, 60, 35, 36, 60, 35, 32, + 60, 36, 27, 60, 37, 22, 60, 38, 18, 60, 38, 14, + 61, 39, 9, 61, 40, 5, 61, 41, 0, 61, 42, -4, + 61, 44, -9, 62, 45, -13, 62, 46, -17, 62, 47, -21, + 63, 49, -25, 63, 50, -30, 63, 52, -34, 64, 53, -38, + 64, 55, -42, 64, 56, -46, 65, 58, -50, 65, 60, -53, + 60, 30, 67, 60, 30, 65, 60, 30, 64, 60, 30, 62, + 60, 30, 59, 60, 31, 56, 60, 31, 53, 60, 31, 49, + 60, 32, 45, 60, 32, 41, 60, 33, 37, 61, 33, 33, + 61, 34, 28, 61, 35, 24, 61, 36, 19, 61, 36, 15, + 61, 37, 10, 62, 38, 6, 62, 39, 1, 62, 40, -3, + 62, 42, -8, 62, 43, -12, 63, 44, -16, 63, 45, -20, + 63, 47, -24, 64, 48, -29, 64, 50, -33, 64, 51, -37, + 65, 53, -40, 65, 54, -45, 66, 56, -49, 66, 58, -52, + 61, 28, 67, 61, 28, 66, 61, 28, 65, 61, 28, 62, + 61, 28, 60, 61, 29, 57, 61, 29, 54, 61, 29, 50, + 61, 30, 46, 61, 30, 42, 61, 31, 38, 61, 31, 34, + 62, 32, 29, 62, 33, 25, 62, 33, 20, 62, 34, 16, + 62, 35, 11, 62, 36, 7, 63, 37, 3, 63, 38, -2, + 63, 40, -6, 63, 41, -10, 64, 42, -15, 64, 43, -19, + 64, 45, -23, 64, 46, -27, 65, 48, -31, 65, 49, -35, + 65, 51, -39, 66, 52, -44, 66, 54, -47, 67, 56, -51, + 62, 25, 68, 62, 25, 67, 62, 25, 65, 62, 25, 63, + 62, 26, 61, 62, 26, 58, 62, 26, 55, 62, 27, 51, + 62, 27, 47, 62, 27, 43, 62, 28, 39, 63, 29, 35, + 63, 29, 30, 63, 30, 26, 63, 31, 22, 63, 32, 18, + 63, 33, 13, 63, 34, 9, 64, 35, 4, 64, 36, 0, + 64, 37, -5, 64, 38, -9, 65, 39, -13, 65, 41, -17, + 65, 42, -21, 66, 44, -26, 66, 45, -30, 66, 46, -34, + 67, 48, -38, 67, 50, -42, 67, 51, -46, 68, 53, -50, + 63, 23, 69, 63, 23, 67, 63, 23, 66, 63, 23, 64, + 63, 23, 62, 63, 24, 59, 63, 24, 56, 63, 24, 52, + 63, 25, 48, 63, 25, 44, 63, 26, 40, 63, 26, 36, + 64, 27, 32, 64, 28, 27, 64, 29, 23, 64, 29, 19, + 64, 30, 14, 64, 31, 10, 65, 32, 6, 65, 34, 1, + 65, 35, -3, 65, 36, -8, 66, 37, -12, 66, 39, -16, + 66, 40, -20, 66, 41, -24, 67, 43, -28, 67, 44, -32, + 67, 46, -36, 68, 48, -41, 68, 49, -45, 68, 51, -48, + 64, 21, 69, 64, 21, 68, 64, 21, 67, 64, 21, 65, + 64, 21, 62, 64, 22, 60, 64, 22, 57, 64, 22, 53, + 64, 23, 49, 64, 23, 45, 64, 24, 42, 64, 24, 37, + 64, 25, 33, 65, 26, 29, 65, 26, 24, 65, 27, 20, + 65, 28, 15, 65, 29, 11, 65, 30, 7, 66, 31, 3, + 66, 33, -2, 66, 34, -6, 66, 35, -10, 67, 36, -15, + 67, 38, -19, 67, 39, -23, 68, 41, -27, 68, 42, -31, + 68, 44, -35, 69, 46, -39, 69, 47, -43, 69, 49, -47, + 65, 19, 70, 65, 19, 69, 65, 19, 67, 65, 19, 66, + 65, 19, 63, 65, 19, 61, 65, 20, 58, 65, 20, 54, + 65, 20, 50, 65, 21, 47, 65, 21, 43, 65, 22, 39, + 65, 23, 34, 66, 24, 30, 66, 24, 26, 66, 25, 21, + 66, 26, 17, 66, 27, 12, 66, 28, 8, 67, 29, 4, + 67, 30, -1, 67, 32, -5, 67, 33, -9, 68, 34, -13, + 68, 36, -17, 68, 37, -22, 68, 39, -26, 69, 40, -30, + 69, 42, -34, 69, 43, -38, 70, 45, -42, 70, 47, -46, + 66, 17, 70, 66, 17, 69, 66, 17, 68, 66, 17, 66, + 66, 17, 64, 66, 17, 61, 66, 18, 59, 66, 18, 55, + 66, 18, 51, 66, 19, 48, 66, 19, 44, 66, 20, 40, + 66, 21, 35, 66, 21, 31, 67, 22, 27, 67, 23, 23, + 67, 24, 18, 67, 25, 14, 67, 26, 9, 67, 27, 5, + 68, 28, 0, 68, 30, -4, 68, 31, -8, 68, 32, -12, + 69, 33, -16, 69, 35, -20, 69, 37, -24, 70, 38, -28, + 70, 40, -32, 70, 41, -37, 71, 43, -41, 71, 45, -44, + 67, 14, 71, 67, 14, 70, 67, 15, 69, 67, 15, 67, + 67, 15, 65, 67, 15, 62, 67, 15, 59, 67, 16, 56, + 67, 16, 52, 67, 17, 49, 67, 17, 45, 67, 18, 41, + 67, 18, 36, 67, 19, 32, 68, 20, 28, 68, 21, 24, + 68, 22, 19, 68, 23, 15, 68, 24, 11, 68, 25, 7, + 69, 26, 2, 69, 27, -2, 69, 29, -6, 69, 30, -11, + 70, 31, -15, 70, 33, -19, 70, 34, -23, 70, 36, -27, + 71, 37, -31, 71, 39, -35, 71, 41, -39, 72, 43, -43, + 68, 12, 72, 68, 12, 71, 68, 12, 70, 68, 13, 68, + 68, 13, 66, 68, 13, 63, 68, 13, 60, 68, 14, 57, + 68, 14, 53, 68, 14, 50, 68, 15, 46, 68, 16, 42, + 68, 16, 38, 68, 17, 33, 68, 18, 29, 69, 19, 25, + 69, 20, 20, 69, 21, 16, 69, 22, 12, 69, 23, 8, + 70, 24, 3, 70, 25, -1, 70, 26, -5, 70, 28, -9, + 70, 29, -13, 71, 31, -18, 71, 32, -22, 71, 34, -26, + 72, 35, -30, 72, 37, -34, 72, 39, -38, 73, 40, -42, + 69, 10, 72, 69, 10, 71, 69, 10, 70, 69, 10, 69, + 69, 11, 67, 69, 11, 64, 69, 11, 61, 69, 11, 58, + 69, 12, 54, 69, 12, 51, 69, 13, 47, 69, 13, 43, + 69, 14, 39, 69, 15, 35, 69, 16, 31, 70, 16, 26, + 70, 17, 22, 70, 18, 18, 70, 19, 13, 70, 21, 9, + 71, 22, 5, 71, 23, 0, 71, 24, -4, 71, 26, -8, + 71, 27, -12, 72, 29, -16, 72, 30, -20, 72, 32, -24, + 73, 33, -28, 73, 35, -33, 73, 37, -37, 74, 38, -40, + 70, 8, 73, 70, 8, 72, 70, 8, 71, 70, 8, 69, + 70, 8, 67, 70, 9, 65, 70, 9, 62, 70, 9, 59, + 70, 10, 55, 70, 10, 52, 70, 11, 48, 70, 11, 44, + 70, 12, 40, 70, 13, 36, 70, 13, 32, 71, 14, 28, + 71, 15, 23, 71, 16, 19, 71, 17, 15, 71, 18, 11, + 71, 20, 6, 72, 21, 2, 72, 22, -2, 72, 23, -6, + 72, 25, -10, 73, 26, -15, 73, 28, -19, 73, 29, -23, + 73, 31, -27, 74, 33, -31, 74, 34, -35, 74, 36, -39, + 71, 6, 74, 71, 6, 73, 71, 6, 72, 71, 6, 70, + 71, 6, 68, 71, 6, 66, 71, 7, 63, 71, 7, 60, + 71, 8, 57, 71, 8, 53, 71, 9, 49, 71, 9, 46, + 71, 10, 41, 71, 11, 37, 71, 11, 33, 72, 12, 29, + 72, 13, 24, 72, 14, 20, 72, 15, 16, 72, 16, 12, + 72, 17, 7, 73, 19, 3, 73, 20, -1, 73, 21, -5, + 73, 23, -9, 74, 24, -14, 74, 26, -18, 74, 27, -22, + 74, 29, -25, 75, 31, -30, 75, 32, -34, 75, 34, -38, + 72, 4, 75, 72, 4, 74, 72, 4, 73, 72, 4, 71, + 72, 4, 69, 72, 4, 67, 72, 5, 64, 72, 5, 61, + 72, 5, 58, 72, 6, 54, 72, 6, 51, 72, 7, 47, + 72, 8, 42, 72, 8, 38, 72, 9, 34, 73, 10, 30, + 73, 11, 26, 73, 12, 22, 73, 13, 17, 73, 14, 13, + 73, 15, 9, 74, 17, 5, 74, 18, 1, 74, 19, -4, + 74, 20, -8, 75, 22, -12, 75, 24, -16, 75, 25, -20, + 75, 27, -24, 76, 28, -28, 76, 30, -32, 76, 32, -36, + 73, 2, 75, 73, 2, 74, 73, 2, 73, 73, 2, 72, + 73, 2, 70, 73, 2, 68, 73, 3, 65, 73, 3, 62, + 73, 3, 59, 73, 4, 55, 73, 4, 52, 73, 5, 48, + 73, 6, 44, 73, 6, 40, 73, 7, 36, 74, 8, 32, + 74, 9, 27, 74, 10, 23, 74, 11, 19, 74, 12, 15, + 74, 13, 10, 75, 14, 6, 75, 16, 2, 75, 17, -2, + 75, 18, -6, 75, 20, -11, 76, 21, -15, 76, 23, -19, + 76, 24, -23, 77, 26, -27, 77, 28, -31, 77, 30, -35, + 74, -1, 76, 74, -1, 75, 74, 0, 74, 74, 0, 73, + 74, 0, 71, 74, 0, 69, 74, 0, 66, 74, 1, 63, + 74, 1, 60, 74, 2, 56, 74, 2, 53, 74, 3, 49, + 74, 3, 45, 74, 4, 41, 74, 5, 37, 75, 6, 33, + 75, 7, 28, 75, 8, 24, 75, 9, 20, 75, 10, 16, + 75, 11, 12, 76, 12, 7, 76, 13, 3, 76, 15, -1, + 76, 16, -5, 76, 18, -9, 77, 19, -13, 77, 21, -17, + 77, 22, -21, 78, 24, -26, 78, 26, -29, 78, 27, -33, + 75, -3, 77, 75, -3, 76, 75, -3, 75, 75, -2, 73, + 75, -2, 72, 75, -2, 69, 75, -2, 67, 75, -1, 64, + 75, -1, 61, 75, 0, 57, 75, 0, 54, 75, 1, 50, + 75, 1, 46, 75, 2, 42, 75, 3, 38, 76, 4, 34, + 76, 5, 30, 76, 6, 26, 76, 7, 22, 76, 8, 18, + 76, 9, 13, 77, 10, 9, 77, 11, 5, 77, 13, 1, + 77, 14, -3, 77, 16, -8, 78, 17, -12, 78, 19, -16, + 78, 20, -20, 78, 22, -24, 79, 24, -28, 79, 25, -32, + 76, -5, 77, 76, -5, 77, 76, -5, 76, 76, -4, 74, + 76, -4, 72, 76, -4, 70, 76, -4, 68, 76, -3, 65, + 76, -3, 62, 76, -3, 59, 76, -2, 55, 76, -1, 51, + 76, -1, 47, 76, 0, 43, 76, 1, 40, 77, 1, 36, + 77, 2, 31, 77, 3, 27, 77, 4, 23, 77, 5, 19, + 77, 7, 14, 78, 8, 10, 78, 9, 6, 78, 10, 2, + 78, 12, -2, 78, 13, -6, 79, 15, -10, 79, 16, -14, + 79, 18, -18, 79, 20, -23, 80, 21, -27, 80, 23, -30, + 77, -7, 78, 77, -7, 77, 77, -7, 76, 77, -7, 75, + 77, -6, 73, 77, -6, 71, 77, -6, 69, 77, -5, 66, + 77, -5, 63, 77, -5, 60, 77, -4, 56, 77, -4, 53, + 77, -3, 49, 77, -2, 45, 77, -1, 41, 78, -1, 37, + 78, 0, 32, 78, 1, 28, 78, 2, 24, 78, 3, 20, + 78, 5, 16, 79, 6, 12, 79, 7, 8, 79, 8, 4, + 79, 10, 0, 79, 11, -5, 80, 13, -9, 80, 14, -13, + 80, 16, -17, 80, 18, -21, 81, 19, -25, 81, 21, -29, + 78, -9, 79, 78, -9, 78, 78, -9, 77, 78, -9, 76, + 78, -8, 74, 78, -8, 72, 78, -8, 70, 78, -8, 67, + 78, -7, 64, 78, -7, 61, 78, -6, 57, 78, -6, 54, + 78, -5, 50, 78, -4, 46, 79, -4, 42, 79, -3, 38, + 79, -2, 34, 79, -1, 30, 79, 0, 26, 79, 1, 22, + 79, 3, 17, 80, 4, 13, 80, 5, 9, 80, 6, 5, + 80, 8, 1, 80, 9, -3, 81, 11, -7, 81, 12, -11, + 81, 14, -15, 81, 15, -20, 82, 17, -24, 82, 19, -27, + 79, -11, 80, 79, -11, 79, 79, -11, 78, 79, -11, 77, + 79, -10, 75, 79, -10, 73, 79, -10, 71, 79, -10, 68, + 79, -9, 65, 79, -9, 62, 79, -8, 59, 79, -8, 55, + 79, -7, 51, 79, -6, 47, 80, -6, 43, 80, -5, 40, + 80, -4, 35, 80, -3, 31, 80, -2, 27, 80, -1, 23, + 80, 0, 19, 81, 2, 15, 81, 3, 11, 81, 4, 7, + 81, 5, 3, 81, 7, -2, 82, 8, -6, 82, 10, -10, + 82, 11, -14, 82, 13, -18, 83, 15, -22, 83, 17, -26, + 80, -13, 80, 80, -13, 80, 80, -13, 79, 80, -13, 77, + 80, -12, 76, 80, -12, 74, 80, -12, 72, 80, -12, 69, + 80, -11, 66, 80, -11, 63, 80, -10, 60, 80, -10, 56, + 80, -9, 52, 81, -8, 48, 81, -8, 45, 81, -7, 41, + 81, -6, 36, 81, -5, 33, 81, -4, 29, 81, -3, 25, + 81, -2, 20, 82, -1, 16, 82, 1, 12, 82, 2, 8, + 82, 3, 4, 82, 5, -1, 83, 6, -4, 83, 8, -8, + 83, 9, -12, 83, 11, -17, 84, 13, -21, 84, 14, -24, + 81, -15, 81, 81, -15, 80, 81, -15, 80, 81, -15, 78, + 81, -14, 77, 81, -14, 75, 81, -14, 73, 81, -14, 70, + 81, -13, 67, 81, -13, 64, 81, -12, 61, 81, -12, 57, + 81, -11, 53, 82, -10, 50, 82, -10, 46, 82, -9, 42, + 82, -8, 38, 82, -7, 34, 82, -6, 30, 82, -5, 26, + 82, -4, 21, 83, -3, 17, 83, -1, 13, 83, 0, 9, + 83, 1, 5, 83, 3, 1, 84, 4, -3, 84, 6, -7, + 84, 7, -11, 84, 9, -15, 85, 11, -19, 85, 12, -23, + 82, -17, 82, 82, -17, 81, 82, -17, 80, 82, -17, 79, + 82, -16, 78, 82, -16, 76, 82, -16, 74, 82, -16, 71, + 82, -15, 68, 82, -15, 65, 82, -14, 62, 82, -14, 59, + 83, -13, 55, 83, -12, 51, 83, -12, 47, 83, -11, 43, + 83, -10, 39, 83, -9, 35, 83, -8, 31, 83, -7, 27, + 83, -6, 23, 84, -5, 19, 84, -3, 15, 84, -2, 11, + 84, -1, 7, 84, 1, 2, 85, 2, -1, 85, 4, -5, + 85, 5, -9, 85, 7, -14, 86, 8, -18, 86, 10, -21, + 83, -19, 83, 83, -19, 82, 83, -19, 81, 83, -19, 80, + 83, -19, 79, 83, -19, 77, 84, -18, 75, 84, -18, 72, + 84, -18, 69, 84, -17, 66, 84, -17, 63, 84, -16, 60, + 84, -16, 56, 84, -15, 53, 84, -14, 49, 84, -13, 45, + 84, -12, 41, 84, -12, 37, 85, -11, 33, 85, -10, 29, + 85, -8, 25, 85, -7, 21, 85, -6, 17, 85, -5, 13, + 85, -3, 9, 86, -2, 4, 86, -1, 0, 86, 1, -4, + 86, 2, -7, 87, 4, -12, 87, 6, -16, 87, 8, -20, + 84, -21, 84, 84, -21, 83, 84, -21, 82, 84, -21, 81, + 85, -21, 80, 85, -21, 78, 85, -20, 76, 85, -20, 73, + 85, -20, 70, 85, -19, 68, 85, -19, 64, 85, -18, 61, + 85, -18, 57, 85, -17, 54, 85, -16, 50, 85, -15, 46, + 85, -14, 42, 85, -14, 38, 86, -13, 34, 86, -12, 30, + 86, -10, 26, 86, -9, 22, 86, -8, 18, 86, -7, 14, + 86, -6, 10, 87, -4, 6, 87, -3, 2, 87, -1, -2, + 87, 0, -6, 88, 2, -10, 88, 4, -14, 88, 5, -18, + 86, -23, 85, 86, -23, 84, 86, -23, 83, 86, -23, 82, + 86, -23, 81, 86, -23, 79, 86, -22, 77, 86, -22, 74, + 86, -22, 71, 86, -21, 69, 86, -21, 66, 86, -20, 62, + 86, -20, 59, 86, -19, 55, 86, -18, 51, 86, -17, 48, + 86, -16, 43, 86, -16, 40, 87, -15, 36, 87, -14, 32, + 87, -12, 27, 87, -11, 24, 87, -10, 20, 87, -9, 16, + 88, -8, 12, 88, -6, 7, 88, -5, 3, 88, -3, -1, + 88, -2, -4, 89, 0, -9, 89, 2, -13, 89, 3, -17, + 87, -25, 85, 87, -25, 85, 87, -25, 84, 87, -25, 83, + 87, -25, 81, 87, -24, 80, 87, -24, 78, 87, -24, 75, + 87, -24, 73, 87, -23, 70, 87, -23, 67, 87, -22, 64, + 87, -21, 60, 87, -21, 56, 87, -20, 53, 87, -19, 49, + 87, -18, 45, 88, -18, 41, 88, -17, 37, 88, -16, 33, + 88, -14, 29, 88, -13, 25, 88, -12, 21, 88, -11, 17, + 89, -10, 13, 89, -8, 9, 89, -7, 5, 89, -5, 1, + 89, -4, -3, 90, -2, -7, 90, 0, -11, 90, 1, -15, + 88, -27, 86, 88, -27, 86, 88, -27, 85, 88, -27, 84, + 88, -27, 82, 88, -26, 81, 88, -26, 79, 88, -26, 76, + 88, -25, 74, 88, -25, 71, 88, -25, 68, 88, -24, 65, + 88, -23, 61, 88, -23, 58, 88, -22, 54, 88, -21, 50, + 89, -20, 46, 89, -20, 42, 89, -19, 39, 89, -18, 35, + 89, -16, 30, 89, -15, 26, 89, -14, 23, 89, -13, 19, + 90, -12, 15, 90, -10, 10, 90, -9, 6, 90, -7, 2, + 90, -6, -1, 91, -4, -6, 91, -2, -10, 91, -1, -14, + 89, -29, 87, 89, -29, 86, 89, -29, 86, 89, -29, 85, + 89, -28, 83, 89, -28, 82, 89, -28, 80, 89, -28, 77, + 89, -27, 75, 89, -27, 72, 89, -26, 69, 89, -26, 66, + 89, -25, 62, 89, -25, 59, 89, -24, 55, 89, -23, 52, + 90, -22, 47, 90, -21, 44, 90, -21, 40, 90, -20, 36, + 90, -18, 32, 90, -17, 28, 90, -16, 24, 90, -15, 20, + 91, -14, 16, 91, -12, 12, 91, -11, 8, 91, -9, 4, + 91, -8, 0, 92, -6, -4, 92, -5, -8, 92, -3, -12, + 90, -31, 88, 90, -31, 87, 90, -31, 86, 90, -31, 85, + 90, -30, 84, 90, -30, 82, 90, -30, 81, 90, -30, 78, + 90, -29, 76, 90, -29, 73, 90, -28, 70, 90, -28, 67, + 90, -27, 63, 90, -27, 60, 90, -26, 57, 91, -25, 53, + 91, -24, 49, 91, -23, 45, 91, -22, 41, 91, -21, 37, + 91, -20, 33, 91, -19, 29, 91, -18, 25, 92, -17, 22, + 92, -16, 18, 92, -14, 13, 92, -13, 9, 92, -11, 5, + 92, -10, 2, 93, -8, -3, 93, -7, -7, 93, -5, -10, + 91, -33, 89, 91, -33, 88, 91, -32, 87, 91, -32, 86, + 91, -32, 85, 91, -32, 83, 91, -32, 82, 91, -31, 79, + 91, -31, 77, 91, -31, 74, 91, -30, 71, 91, -30, 68, + 91, -29, 65, 91, -28, 61, 92, -28, 58, 92, -27, 54, + 92, -26, 50, 92, -25, 46, 92, -24, 43, 92, -23, 39, + 92, -22, 35, 92, -21, 31, 92, -20, 27, 93, -19, 23, + 93, -18, 19, 93, -16, 15, 93, -15, 11, 93, -13, 7, + 93, -12, 3, 94, -10, -1, 94, -9, -5, 94, -7, -9, + 92, -34, 89, 92, -34, 89, 92, -34, 88, 92, -34, 87, + 92, -34, 86, 92, -34, 84, 92, -34, 82, 92, -33, 80, + 92, -33, 78, 92, -33, 75, 92, -32, 72, 92, -32, 69, + 92, -31, 66, 93, -30, 62, 93, -30, 59, 93, -29, 56, + 93, -28, 51, 93, -27, 48, 93, -26, 44, 93, -25, 40, + 93, -24, 36, 93, -23, 32, 94, -22, 28, 94, -21, 24, + 94, -20, 21, 94, -18, 16, 94, -17, 12, 94, -15, 8, + 95, -14, 5, 95, -12, 0, 95, -11, -4, 95, -9, -7, + 93, -36, 90, 93, -36, 90, 93, -36, 89, 93, -36, 88, + 93, -36, 87, 93, -36, 85, 93, -35, 83, 93, -35, 81, + 93, -35, 79, 93, -34, 76, 93, -34, 73, 93, -33, 70, + 94, -33, 67, 94, -32, 64, 94, -32, 60, 94, -31, 57, + 94, -30, 53, 94, -29, 49, 94, -28, 45, 94, -27, 42, + 94, -26, 37, 94, -25, 34, 95, -24, 30, 95, -23, 26, + 95, -21, 22, 95, -20, 18, 95, -19, 14, 95, -17, 10, + 96, -16, 6, 96, -14, 2, 96, -13, -2, 96, -11, -6, + 94, -38, 91, 94, -38, 90, 94, -38, 90, 94, -38, 89, + 94, -38, 88, 94, -37, 86, 94, -37, 84, 94, -37, 82, + 94, -37, 80, 94, -36, 77, 94, -36, 75, 95, -35, 72, + 95, -35, 68, 95, -34, 65, 95, -33, 62, 95, -33, 58, + 95, -32, 54, 95, -31, 50, 95, -30, 47, 95, -29, 43, + 95, -28, 39, 96, -27, 35, 96, -26, 31, 96, -25, 27, + 96, -23, 23, 96, -22, 19, 96, -21, 15, 96, -19, 11, + 97, -18, 8, 97, -16, 3, 97, -15, -1, 97, -13, -4, + 46, 72, 61, 46, 72, 58, 46, 72, 56, 46, 72, 52, + 46, 73, 48, 47, 73, 44, 47, 73, 40, 47, 73, 35, + 47, 74, 30, 47, 74, 25, 47, 74, 20, 47, 75, 16, + 48, 75, 10, 48, 76, 6, 48, 76, 1, 48, 77, -3, + 49, 78, -8, 49, 79, -13, 49, 79, -17, 49, 80, -22, + 50, 81, -26, 50, 82, -31, 51, 83, -35, 51, 84, -39, + 51, 85, -43, 52, 86, -47, 52, 87, -51, 53, 88, -55, + 53, 90, -59, 54, 91, -63, 54, 92, -66, 55, 93, -70, + 46, 72, 61, 46, 72, 58, 47, 72, 56, 47, 72, 52, + 47, 72, 48, 47, 72, 44, 47, 73, 40, 47, 73, 35, + 47, 73, 30, 47, 74, 25, 47, 74, 21, 47, 74, 16, + 48, 75, 11, 48, 76, 6, 48, 76, 1, 48, 77, -3, + 49, 78, -8, 49, 78, -13, 49, 79, -17, 50, 80, -21, + 50, 81, -26, 50, 82, -30, 51, 83, -34, 51, 84, -39, + 51, 85, -43, 52, 86, -47, 52, 87, -51, 53, 88, -55, + 53, 89, -58, 54, 91, -63, 54, 92, -66, 55, 93, -70, + 47, 71, 61, 47, 72, 58, 47, 72, 56, 47, 72, 52, + 47, 72, 49, 47, 72, 44, 47, 72, 40, 47, 73, 35, + 47, 73, 30, 47, 73, 25, 47, 74, 21, 48, 74, 16, + 48, 75, 11, 48, 75, 6, 48, 76, 1, 48, 76, -3, + 49, 77, -8, 49, 78, -13, 49, 79, -17, 50, 80, -21, + 50, 81, -26, 50, 82, -30, 51, 82, -34, 51, 83, -38, + 52, 84, -42, 52, 86, -47, 52, 87, -51, 53, 88, -54, + 53, 89, -58, 54, 90, -62, 54, 92, -66, 55, 93, -70, + 47, 71, 61, 47, 71, 58, 47, 71, 56, 47, 71, 52, + 47, 72, 49, 47, 72, 45, 47, 72, 40, 47, 72, 35, + 47, 73, 30, 47, 73, 26, 47, 73, 21, 48, 74, 16, + 48, 74, 11, 48, 75, 6, 48, 76, 2, 49, 76, -3, + 49, 77, -8, 49, 78, -12, 49, 78, -17, 50, 79, -21, + 50, 80, -26, 50, 81, -30, 51, 82, -34, 51, 83, -38, + 52, 84, -42, 52, 85, -47, 53, 86, -51, 53, 88, -54, + 53, 89, -58, 54, 90, -62, 55, 91, -66, 55, 93, -70, + 47, 71, 61, 47, 71, 58, 47, 71, 56, 47, 71, 52, + 47, 71, 49, 47, 71, 45, 47, 72, 40, 47, 72, 35, + 47, 72, 30, 47, 73, 26, 48, 73, 21, 48, 73, 16, + 48, 74, 11, 48, 75, 6, 48, 75, 2, 49, 76, -3, + 49, 77, -8, 49, 77, -12, 50, 78, -17, 50, 79, -21, + 50, 80, -26, 51, 81, -30, 51, 82, -34, 51, 83, -38, + 52, 84, -42, 52, 85, -46, 53, 86, -50, 53, 87, -54, + 54, 88, -58, 54, 90, -62, 55, 91, -66, 55, 92, -69, + 47, 70, 61, 47, 70, 58, 47, 70, 56, 47, 70, 53, + 47, 71, 49, 47, 71, 45, 47, 71, 40, 47, 71, 35, + 48, 72, 31, 48, 72, 26, 48, 72, 21, 48, 73, 17, + 48, 73, 11, 48, 74, 7, 49, 75, 2, 49, 75, -3, + 49, 76, -8, 49, 77, -12, 50, 78, -16, 50, 78, -21, + 50, 79, -25, 51, 80, -30, 51, 81, -34, 52, 82, -38, + 52, 83, -42, 52, 85, -46, 53, 86, -50, 53, 87, -54, + 54, 88, -58, 54, 89, -62, 55, 91, -66, 55, 92, -69, + 47, 70, 61, 47, 70, 59, 47, 70, 56, 47, 70, 53, + 47, 70, 49, 47, 70, 45, 48, 70, 41, 48, 71, 36, + 48, 71, 31, 48, 71, 26, 48, 72, 22, 48, 72, 17, + 48, 73, 12, 49, 73, 7, 49, 74, 2, 49, 75, -2, + 49, 76, -7, 50, 76, -12, 50, 77, -16, 50, 78, -20, + 51, 79, -25, 51, 80, -29, 51, 81, -33, 52, 82, -38, + 52, 83, -42, 53, 84, -46, 53, 85, -50, 53, 86, -54, + 54, 88, -57, 54, 89, -62, 55, 90, -65, 55, 91, -69, + 47, 69, 61, 47, 69, 59, 47, 69, 56, 48, 69, 53, + 48, 69, 49, 48, 70, 45, 48, 70, 41, 48, 70, 36, + 48, 70, 31, 48, 71, 27, 48, 71, 22, 48, 72, 17, + 49, 72, 12, 49, 73, 7, 49, 73, 3, 49, 74, -2, + 50, 75, -7, 50, 76, -11, 50, 76, -16, 50, 77, -20, + 51, 78, -25, 51, 79, -29, 51, 80, -33, 52, 81, -37, + 52, 82, -41, 53, 84, -46, 53, 85, -50, 54, 86, -53, + 54, 87, -57, 55, 88, -61, 55, 90, -65, 56, 91, -69, + 48, 68, 61, 48, 68, 59, 48, 68, 57, 48, 69, 53, + 48, 69, 49, 48, 69, 45, 48, 69, 41, 48, 69, 36, + 48, 70, 32, 48, 70, 27, 48, 71, 22, 49, 71, 17, + 49, 72, 12, 49, 72, 8, 49, 73, 3, 49, 73, -2, + 50, 74, -7, 50, 75, -11, 50, 76, -15, 51, 77, -20, + 51, 78, -24, 51, 79, -29, 52, 80, -33, 52, 81, -37, + 53, 82, -41, 53, 83, -45, 53, 84, -49, 54, 85, -53, + 54, 86, -57, 55, 88, -61, 55, 89, -65, 56, 90, -68, + 48, 67, 61, 48, 67, 59, 48, 68, 57, 48, 68, 53, + 48, 68, 50, 48, 68, 46, 48, 68, 41, 48, 69, 36, + 48, 69, 32, 49, 69, 27, 49, 70, 23, 49, 70, 18, + 49, 71, 13, 49, 71, 8, 50, 72, 3, 50, 73, -1, + 50, 73, -6, 50, 74, -11, 51, 75, -15, 51, 76, -19, + 51, 77, -24, 52, 78, -28, 52, 79, -32, 52, 80, -36, + 53, 81, -40, 53, 82, -45, 54, 83, -49, 54, 84, -53, + 55, 86, -56, 55, 87, -61, 56, 88, -64, 56, 90, -68, + 48, 67, 61, 48, 67, 59, 48, 67, 57, 48, 67, 54, + 48, 67, 50, 49, 67, 46, 49, 67, 42, 49, 68, 37, + 49, 68, 32, 49, 68, 28, 49, 69, 23, 49, 69, 18, + 49, 70, 13, 50, 70, 8, 50, 71, 4, 50, 72, -1, + 50, 73, -6, 51, 73, -10, 51, 74, -15, 51, 75, -19, + 52, 76, -24, 52, 77, -28, 52, 78, -32, 53, 79, -36, + 53, 80, -40, 53, 81, -44, 54, 83, -48, 54, 84, -52, + 55, 85, -56, 55, 86, -60, 56, 88, -64, 56, 89, -68, + 49, 65, 61, 49, 65, 59, 49, 65, 57, 49, 66, 54, + 49, 66, 50, 49, 66, 46, 49, 66, 42, 49, 66, 37, + 49, 67, 33, 49, 67, 28, 50, 68, 24, 50, 68, 19, + 50, 69, 14, 50, 69, 9, 50, 70, 4, 50, 71, 0, + 51, 71, -5, 51, 72, -10, 51, 73, -14, 52, 74, -18, + 52, 75, -23, 52, 76, -27, 53, 77, -31, 53, 78, -35, + 53, 79, -39, 54, 80, -44, 54, 81, -48, 55, 83, -52, + 55, 84, -55, 56, 85, -60, 56, 87, -63, 57, 88, -67, + 49, 64, 62, 49, 64, 60, 49, 64, 57, 49, 64, 54, + 49, 65, 51, 49, 65, 47, 49, 65, 43, 50, 65, 38, + 50, 66, 33, 50, 66, 29, 50, 66, 24, 50, 67, 19, + 50, 68, 14, 50, 68, 10, 51, 69, 5, 51, 69, 0, + 51, 70, -5, 51, 71, -9, 52, 72, -13, 52, 73, -18, + 52, 74, -22, 53, 75, -27, 53, 76, -31, 53, 77, -35, + 54, 78, -39, 54, 79, -43, 55, 80, -47, 55, 82, -51, + 56, 83, -55, 56, 84, -59, 57, 86, -63, 57, 87, -66, + 50, 63, 62, 50, 63, 60, 50, 63, 58, 50, 63, 54, + 50, 63, 51, 50, 64, 47, 50, 64, 43, 50, 64, 38, + 50, 64, 34, 50, 65, 29, 50, 65, 25, 50, 66, 20, + 51, 66, 15, 51, 67, 10, 51, 68, 6, 51, 68, 1, + 52, 69, -4, 52, 70, -8, 52, 71, -13, 52, 72, -17, + 53, 73, -22, 53, 74, -26, 53, 75, -30, 54, 76, -34, + 54, 77, -38, 55, 78, -43, 55, 79, -47, 55, 81, -50, + 56, 82, -54, 56, 83, -59, 57, 85, -62, 57, 86, -66, + 50, 62, 62, 50, 62, 60, 50, 62, 58, 50, 62, 55, + 50, 62, 51, 50, 62, 48, 50, 63, 44, 50, 63, 39, + 51, 63, 34, 51, 64, 30, 51, 64, 25, 51, 65, 21, + 51, 65, 15, 51, 66, 11, 51, 66, 6, 52, 67, 2, + 52, 68, -3, 52, 69, -8, 53, 70, -12, 53, 70, -16, + 53, 72, -21, 53, 73, -25, 54, 74, -30, 54, 75, -34, + 55, 76, -38, 55, 77, -42, 55, 78, -46, 56, 79, -50, + 56, 81, -54, 57, 82, -58, 57, 84, -62, 58, 85, -65, + 51, 60, 62, 51, 60, 60, 51, 60, 58, 51, 61, 55, + 51, 61, 52, 51, 61, 48, 51, 61, 44, 51, 62, 39, + 51, 62, 35, 51, 62, 30, 51, 63, 26, 51, 63, 21, + 52, 64, 16, 52, 64, 11, 52, 65, 7, 52, 66, 2, + 52, 67, -3, 53, 67, -7, 53, 68, -11, 53, 69, -16, + 54, 70, -21, 54, 71, -25, 54, 72, -29, 55, 73, -33, + 55, 75, -37, 55, 76, -41, 56, 77, -45, 56, 78, -49, + 57, 80, -53, 57, 81, -57, 58, 82, -61, 58, 84, -65, + 51, 59, 62, 51, 59, 61, 51, 59, 59, 51, 59, 56, + 51, 59, 52, 51, 60, 49, 51, 60, 45, 51, 60, 40, + 52, 60, 35, 52, 61, 31, 52, 61, 26, 52, 62, 22, + 52, 62, 17, 52, 63, 12, 52, 64, 8, 53, 64, 3, + 53, 65, -2, 53, 66, -6, 53, 67, -11, 54, 68, -15, + 54, 69, -20, 54, 70, -24, 55, 71, -28, 55, 72, -32, + 55, 73, -36, 56, 75, -41, 56, 76, -45, 57, 77, -49, + 57, 78, -52, 58, 80, -57, 58, 81, -60, 59, 83, -64, + 52, 57, 63, 52, 57, 61, 52, 58, 59, 52, 58, 56, + 52, 58, 53, 52, 58, 49, 52, 58, 45, 52, 59, 40, + 52, 59, 36, 52, 59, 32, 52, 60, 27, 52, 60, 23, + 53, 61, 17, 53, 62, 13, 53, 62, 8, 53, 63, 4, + 53, 64, -1, 54, 65, -6, 54, 66, -10, 54, 66, -14, + 55, 68, -19, 55, 69, -23, 55, 70, -27, 56, 71, -31, + 56, 72, -35, 56, 73, -40, 57, 74, -44, 57, 76, -48, + 58, 77, -52, 58, 79, -56, 59, 80, -60, 59, 81, -63, + 52, 56, 63, 52, 56, 61, 52, 56, 59, 52, 56, 56, + 52, 56, 53, 52, 56, 50, 52, 57, 46, 53, 57, 41, + 53, 57, 37, 53, 58, 32, 53, 58, 28, 53, 59, 23, + 53, 59, 18, 53, 60, 14, 54, 61, 9, 54, 61, 5, + 54, 62, 0, 54, 63, -5, 54, 64, -9, 55, 65, -13, + 55, 66, -18, 55, 67, -22, 56, 68, -27, 56, 69, -31, + 56, 70, -35, 57, 72, -39, 57, 73, -43, 58, 74, -47, + 58, 76, -51, 59, 77, -55, 59, 79, -59, 59, 80, -63, + 53, 54, 63, 53, 54, 62, 53, 54, 60, 53, 54, 57, + 53, 55, 54, 53, 55, 50, 53, 55, 46, 53, 55, 42, + 53, 56, 37, 53, 56, 33, 53, 57, 29, 54, 57, 24, + 54, 58, 19, 54, 58, 14, 54, 59, 10, 54, 60, 5, + 55, 61, 0, 55, 62, -4, 55, 63, -8, 55, 63, -13, + 56, 65, -17, 56, 66, -22, 56, 67, -26, 57, 68, -30, + 57, 69, -34, 57, 70, -38, 58, 72, -42, 58, 73, -46, + 59, 74, -50, 59, 76, -54, 59, 77, -58, 60, 79, -62, + 53, 53, 64, 53, 53, 62, 53, 53, 60, 53, 53, 57, + 54, 53, 54, 54, 53, 51, 54, 53, 47, 54, 54, 42, + 54, 54, 38, 54, 55, 34, 54, 55, 29, 54, 56, 25, + 54, 56, 20, 55, 57, 15, 55, 58, 11, 55, 58, 6, + 55, 59, 1, 55, 60, -3, 56, 61, -7, 56, 62, -12, + 56, 63, -16, 57, 64, -21, 57, 65, -25, 57, 66, -29, + 58, 67, -33, 58, 69, -37, 58, 70, -41, 59, 71, -45, + 59, 73, -49, 60, 74, -53, 60, 76, -57, 60, 77, -61, + 54, 51, 64, 54, 51, 62, 54, 51, 61, 54, 51, 58, + 54, 51, 55, 54, 51, 51, 54, 52, 48, 54, 52, 43, + 54, 52, 39, 55, 53, 35, 55, 53, 30, 55, 54, 26, + 55, 54, 21, 55, 55, 16, 55, 56, 12, 56, 57, 7, + 56, 57, 2, 56, 58, -2, 56, 59, -6, 57, 60, -11, + 57, 61, -16, 57, 62, -20, 57, 63, -24, 58, 65, -28, + 58, 66, -32, 58, 67, -37, 59, 68, -41, 59, 70, -44, + 60, 71, -48, 60, 73, -53, 61, 74, -56, 61, 76, -60, + 55, 49, 64, 55, 49, 63, 55, 49, 61, 55, 49, 58, + 55, 49, 55, 55, 50, 52, 55, 50, 48, 55, 50, 44, + 55, 51, 40, 55, 51, 35, 55, 52, 31, 55, 52, 27, + 56, 53, 22, 56, 53, 17, 56, 54, 13, 56, 55, 8, + 56, 56, 3, 57, 57, -1, 57, 57, -6, 57, 58, -10, + 57, 60, -15, 58, 61, -19, 58, 62, -23, 58, 63, -27, + 59, 64, -31, 59, 66, -36, 59, 67, -40, 60, 68, -44, + 60, 70, -47, 61, 71, -52, 61, 72, -55, 62, 74, -59, + 55, 47, 65, 55, 47, 63, 55, 47, 61, 56, 47, 59, + 56, 48, 56, 56, 48, 53, 56, 48, 49, 56, 48, 45, + 56, 49, 40, 56, 49, 36, 56, 50, 32, 56, 50, 27, + 56, 51, 22, 57, 52, 18, 57, 52, 14, 57, 53, 9, + 57, 54, 4, 57, 55, 0, 58, 56, -5, 58, 57, -9, + 58, 58, -14, 58, 59, -18, 59, 60, -22, 59, 61, -26, + 59, 62, -30, 60, 64, -35, 60, 65, -39, 60, 66, -43, + 61, 68, -46, 61, 69, -51, 62, 71, -54, 62, 72, -58, + 56, 45, 65, 56, 45, 64, 56, 45, 62, 56, 46, 59, + 56, 46, 57, 56, 46, 53, 56, 46, 50, 56, 47, 45, + 57, 47, 41, 57, 47, 37, 57, 48, 33, 57, 48, 28, + 57, 49, 23, 57, 50, 19, 57, 50, 15, 58, 51, 10, + 58, 52, 5, 58, 53, 1, 58, 54, -4, 58, 55, -8, + 59, 56, -13, 59, 57, -17, 59, 58, -21, 60, 59, -25, + 60, 61, -29, 60, 62, -34, 61, 63, -38, 61, 65, -42, + 61, 66, -45, 62, 68, -50, 62, 69, -53, 63, 71, -57, + 57, 43, 66, 57, 43, 64, 57, 43, 62, 57, 44, 60, + 57, 44, 57, 57, 44, 54, 57, 44, 50, 57, 45, 46, + 57, 45, 42, 57, 45, 38, 58, 46, 34, 58, 46, 29, + 58, 47, 24, 58, 48, 20, 58, 48, 16, 58, 49, 11, + 59, 50, 6, 59, 51, 2, 59, 52, -2, 59, 53, -7, + 59, 54, -12, 60, 55, -16, 60, 56, -20, 60, 58, -24, + 61, 59, -28, 61, 60, -33, 61, 62, -37, 62, 63, -41, + 62, 64, -44, 63, 66, -49, 63, 67, -52, 63, 69, -56, + 58, 41, 66, 58, 41, 65, 58, 42, 63, 58, 42, 61, + 58, 42, 58, 58, 42, 55, 58, 42, 51, 58, 43, 47, + 58, 43, 43, 58, 43, 39, 58, 44, 35, 58, 45, 30, + 59, 45, 25, 59, 46, 21, 59, 47, 17, 59, 47, 12, + 59, 48, 7, 59, 49, 3, 60, 50, -1, 60, 51, -6, + 60, 52, -10, 60, 53, -15, 61, 55, -19, 61, 56, -23, + 61, 57, -27, 62, 58, -32, 62, 60, -36, 62, 61, -39, + 63, 63, -43, 63, 64, -48, 64, 66, -51, 64, 67, -55, + 58, 39, 66, 58, 39, 65, 59, 40, 64, 59, 40, 61, + 59, 40, 59, 59, 40, 55, 59, 40, 52, 59, 41, 48, + 59, 41, 44, 59, 42, 40, 59, 42, 36, 59, 43, 31, + 59, 43, 26, 59, 44, 22, 60, 45, 18, 60, 45, 13, + 60, 46, 8, 60, 47, 4, 60, 48, 0, 61, 49, -5, + 61, 50, -9, 61, 51, -14, 61, 53, -18, 62, 54, -22, + 62, 55, -26, 62, 57, -30, 63, 58, -34, 63, 59, -38, + 63, 61, -42, 64, 62, -47, 64, 64, -50, 65, 65, -54, + 59, 37, 67, 59, 37, 66, 59, 38, 64, 59, 38, 62, + 59, 38, 59, 59, 38, 56, 59, 38, 53, 60, 39, 49, + 60, 39, 45, 60, 39, 41, 60, 40, 37, 60, 41, 32, + 60, 41, 27, 60, 42, 23, 60, 43, 19, 61, 43, 14, + 61, 44, 9, 61, 45, 5, 61, 46, 1, 61, 47, -3, + 62, 48, -8, 62, 49, -12, 62, 51, -17, 63, 52, -21, + 63, 53, -25, 63, 55, -29, 64, 56, -33, 64, 57, -37, + 64, 59, -41, 65, 60, -45, 65, 62, -49, 65, 63, -53, + 60, 35, 67, 60, 35, 66, 60, 35, 65, 60, 36, 62, + 60, 36, 60, 60, 36, 57, 60, 36, 54, 60, 37, 50, + 60, 37, 46, 61, 37, 42, 61, 38, 37, 61, 38, 33, + 61, 39, 28, 61, 40, 24, 61, 41, 20, 61, 41, 15, + 62, 42, 11, 62, 43, 6, 62, 44, 2, 62, 45, -2, + 62, 46, -7, 63, 48, -11, 63, 49, -15, 63, 50, -20, + 64, 51, -24, 64, 53, -28, 64, 54, -32, 65, 55, -36, + 65, 57, -40, 65, 58, -44, 66, 60, -48, 66, 62, -52, + 61, 33, 68, 61, 33, 67, 61, 33, 65, 61, 34, 63, + 61, 34, 61, 61, 34, 58, 61, 34, 54, 61, 35, 50, + 61, 35, 47, 61, 35, 43, 61, 36, 39, 62, 36, 34, + 62, 37, 30, 62, 38, 25, 62, 39, 21, 62, 39, 17, + 62, 40, 12, 63, 41, 7, 63, 42, 3, 63, 43, -1, + 63, 44, -6, 64, 45, -10, 64, 47, -14, 64, 48, -18, + 64, 49, -22, 65, 51, -27, 65, 52, -31, 65, 53, -35, + 66, 55, -39, 66, 57, -43, 66, 58, -47, 67, 60, -51, + 62, 31, 69, 62, 31, 67, 62, 31, 66, 62, 31, 64, + 62, 32, 61, 62, 32, 58, 62, 32, 55, 62, 32, 51, + 62, 33, 48, 62, 33, 44, 62, 34, 40, 62, 34, 35, + 63, 35, 31, 63, 36, 26, 63, 36, 22, 63, 37, 18, + 63, 38, 13, 63, 39, 9, 64, 40, 4, 64, 41, 0, + 64, 42, -5, 64, 43, -9, 65, 45, -13, 65, 46, -17, + 65, 47, -21, 65, 49, -26, 66, 50, -30, 66, 51, -34, + 66, 53, -38, 67, 55, -42, 67, 56, -46, 68, 58, -50, + 63, 29, 69, 63, 29, 68, 63, 29, 67, 63, 29, 65, + 63, 29, 62, 63, 29, 59, 63, 29, 56, 63, 30, 52, + 63, 30, 49, 63, 31, 45, 63, 31, 41, 64, 32, 37, + 64, 32, 32, 64, 33, 28, 64, 34, 23, 64, 35, 19, + 64, 36, 14, 64, 36, 10, 65, 37, 6, 65, 38, 2, + 65, 40, -3, 65, 41, -7, 66, 42, -11, 66, 43, -16, + 66, 45, -20, 66, 46, -24, 67, 47, -28, 67, 49, -32, + 67, 50, -36, 68, 52, -40, 68, 54, -44, 69, 55, -48, + 64, 26, 70, 64, 26, 69, 64, 27, 67, 64, 27, 65, + 64, 27, 63, 64, 27, 60, 64, 27, 57, 64, 28, 53, + 64, 28, 50, 64, 29, 46, 64, 29, 42, 64, 30, 38, + 65, 30, 33, 65, 31, 29, 65, 32, 25, 65, 33, 20, + 65, 33, 16, 65, 34, 11, 66, 35, 7, 66, 36, 3, + 66, 38, -2, 66, 39, -6, 66, 40, -10, 67, 41, -14, + 67, 42, -18, 67, 44, -23, 68, 45, -27, 68, 47, -31, + 68, 48, -35, 69, 50, -39, 69, 52, -43, 69, 53, -47, + 65, 24, 70, 65, 24, 69, 65, 24, 68, 65, 25, 66, + 65, 25, 64, 65, 25, 61, 65, 25, 58, 65, 26, 54, + 65, 26, 51, 65, 26, 47, 65, 27, 43, 65, 27, 39, + 65, 28, 34, 66, 29, 30, 66, 30, 26, 66, 30, 22, + 66, 31, 17, 66, 32, 13, 66, 33, 8, 67, 34, 4, + 67, 35, -1, 67, 37, -5, 67, 38, -9, 68, 39, -13, + 68, 40, -17, 68, 42, -22, 68, 43, -26, 69, 45, -30, + 69, 46, -34, 69, 48, -38, 70, 49, -42, 70, 51, -46, + 66, 22, 71, 66, 22, 70, 66, 22, 69, 66, 22, 67, + 66, 23, 64, 66, 23, 62, 66, 23, 59, 66, 23, 55, + 66, 24, 52, 66, 24, 48, 66, 25, 44, 66, 25, 40, + 66, 26, 35, 66, 27, 31, 67, 27, 27, 67, 28, 23, + 67, 29, 18, 67, 30, 14, 67, 31, 10, 67, 32, 5, + 68, 33, 1, 68, 35, -4, 68, 36, -8, 68, 37, -12, + 69, 38, -16, 69, 40, -20, 69, 41, -24, 70, 43, -28, + 70, 44, -32, 70, 46, -37, 71, 47, -40, 71, 49, -44, + 67, 20, 72, 67, 20, 70, 67, 20, 69, 67, 20, 67, + 67, 20, 65, 67, 21, 63, 67, 21, 60, 67, 21, 56, + 67, 22, 53, 67, 22, 49, 67, 23, 45, 67, 23, 41, + 67, 24, 37, 67, 25, 32, 68, 25, 28, 68, 26, 24, + 68, 27, 19, 68, 28, 15, 68, 29, 11, 68, 30, 7, + 69, 31, 2, 69, 32, -2, 69, 34, -6, 69, 35, -10, + 70, 36, -15, 70, 38, -19, 70, 39, -23, 70, 41, -27, + 71, 42, -31, 71, 44, -35, 71, 45, -39, 72, 47, -43, + 68, 18, 72, 68, 18, 71, 68, 18, 70, 68, 18, 68, + 68, 18, 66, 68, 19, 64, 68, 19, 61, 68, 19, 57, + 68, 20, 54, 68, 20, 50, 68, 21, 46, 68, 21, 42, + 68, 22, 38, 68, 22, 34, 68, 23, 29, 69, 24, 25, + 69, 25, 21, 69, 26, 16, 69, 27, 12, 69, 28, 8, + 70, 29, 3, 70, 30, -1, 70, 31, -5, 70, 33, -9, + 70, 34, -13, 71, 36, -18, 71, 37, -22, 71, 38, -26, + 72, 40, -30, 72, 42, -34, 72, 43, -38, 73, 45, -42, + 68, 16, 73, 69, 16, 72, 69, 16, 71, 69, 16, 69, + 69, 16, 67, 69, 16, 64, 69, 17, 62, 69, 17, 58, + 69, 17, 55, 69, 18, 51, 69, 18, 47, 69, 19, 43, + 69, 20, 39, 69, 20, 35, 69, 21, 31, 70, 22, 26, + 70, 23, 22, 70, 24, 18, 70, 25, 13, 70, 26, 9, + 70, 27, 5, 71, 28, 0, 71, 29, -4, 71, 31, -8, + 71, 32, -12, 72, 33, -16, 72, 35, -20, 72, 36, -24, + 72, 38, -28, 73, 39, -33, 73, 41, -37, 73, 43, -40, + 69, 14, 73, 69, 14, 72, 69, 14, 71, 70, 14, 70, + 70, 14, 68, 70, 14, 65, 70, 15, 62, 70, 15, 59, + 70, 15, 56, 70, 16, 52, 70, 16, 48, 70, 17, 44, + 70, 17, 40, 70, 18, 36, 70, 19, 32, 70, 20, 28, + 71, 21, 23, 71, 22, 19, 71, 23, 15, 71, 24, 11, + 71, 25, 6, 72, 26, 2, 72, 27, -2, 72, 28, -6, + 72, 30, -11, 73, 31, -15, 73, 33, -19, 73, 34, -23, + 73, 36, -27, 74, 37, -31, 74, 39, -35, 74, 41, -39, + 70, 11, 74, 70, 11, 73, 70, 12, 72, 70, 12, 70, + 71, 12, 68, 71, 12, 66, 71, 12, 63, 71, 13, 60, + 71, 13, 57, 71, 14, 53, 71, 14, 49, 71, 15, 46, + 71, 15, 41, 71, 16, 37, 71, 17, 33, 71, 18, 29, + 72, 18, 24, 72, 19, 20, 72, 20, 16, 72, 21, 12, + 72, 23, 7, 72, 24, 3, 73, 25, -1, 73, 26, -5, + 73, 28, -9, 73, 29, -14, 74, 31, -18, 74, 32, -22, + 74, 33, -26, 75, 35, -30, 75, 37, -34, 75, 38, -38, + 71, 9, 75, 71, 9, 74, 71, 9, 73, 71, 10, 71, + 71, 10, 69, 72, 10, 67, 72, 10, 64, 72, 11, 61, + 72, 11, 58, 72, 11, 54, 72, 12, 51, 72, 12, 47, + 72, 13, 42, 72, 14, 38, 72, 15, 34, 72, 15, 30, + 73, 16, 26, 73, 17, 22, 73, 18, 17, 73, 19, 13, + 73, 20, 9, 73, 22, 4, 74, 23, 0, 74, 24, -4, + 74, 25, -8, 74, 27, -12, 75, 28, -16, 75, 30, -20, + 75, 31, -24, 75, 33, -29, 76, 35, -32, 76, 36, -36, + 72, 7, 75, 72, 7, 75, 72, 7, 74, 72, 7, 72, + 72, 8, 70, 73, 8, 68, 73, 8, 65, 73, 8, 62, + 73, 9, 59, 73, 9, 55, 73, 10, 52, 73, 10, 48, + 73, 11, 44, 73, 12, 40, 73, 12, 36, 73, 13, 32, + 74, 14, 27, 74, 15, 23, 74, 16, 19, 74, 17, 15, + 74, 18, 10, 74, 19, 6, 75, 21, 2, 75, 22, -2, + 75, 23, -6, 75, 25, -11, 76, 26, -15, 76, 28, -19, + 76, 29, -23, 76, 31, -27, 77, 33, -31, 77, 34, -35, + 73, 5, 76, 73, 5, 75, 73, 5, 74, 73, 5, 73, + 73, 6, 71, 74, 6, 69, 74, 6, 66, 74, 6, 63, + 74, 7, 60, 74, 7, 56, 74, 8, 53, 74, 8, 49, + 74, 9, 45, 74, 10, 41, 74, 10, 37, 74, 11, 33, + 74, 12, 28, 75, 13, 24, 75, 14, 20, 75, 15, 16, + 75, 16, 11, 75, 17, 7, 76, 19, 3, 76, 20, -1, + 76, 21, -5, 76, 23, -9, 76, 24, -13, 77, 26, -17, + 77, 27, -21, 77, 29, -26, 78, 30, -30, 78, 32, -34, + 74, 3, 77, 74, 3, 76, 74, 3, 75, 74, 3, 73, + 74, 3, 72, 75, 4, 70, 75, 4, 67, 75, 4, 64, + 75, 5, 61, 75, 5, 57, 75, 6, 54, 75, 6, 50, + 75, 7, 46, 75, 7, 42, 75, 8, 38, 75, 9, 34, + 75, 10, 30, 76, 11, 25, 76, 12, 21, 76, 13, 17, + 76, 14, 13, 76, 15, 9, 77, 16, 5, 77, 18, 0, + 77, 19, -4, 77, 20, -8, 77, 22, -12, 78, 23, -16, + 78, 25, -20, 78, 27, -24, 79, 28, -28, 79, 30, -32, + 75, 1, 78, 75, 1, 77, 75, 1, 76, 75, 1, 74, + 76, 1, 73, 76, 2, 70, 76, 2, 68, 76, 2, 65, + 76, 3, 62, 76, 3, 58, 76, 3, 55, 76, 4, 51, + 76, 5, 47, 76, 5, 43, 76, 6, 39, 76, 7, 35, + 76, 8, 31, 77, 9, 27, 77, 10, 23, 77, 11, 19, + 77, 12, 14, 77, 13, 10, 77, 14, 6, 78, 15, 2, + 78, 17, -2, 78, 18, -7, 78, 20, -11, 79, 21, -15, + 79, 23, -19, 79, 24, -23, 79, 26, -27, 80, 28, -31, + 76, -1, 78, 76, -1, 77, 76, -1, 77, 77, -1, 75, + 77, -1, 73, 77, -1, 71, 77, 0, 69, 77, 0, 66, + 77, 0, 63, 77, 1, 60, 77, 1, 56, 77, 2, 53, + 77, 3, 48, 77, 3, 45, 77, 4, 41, 77, 5, 37, + 77, 6, 32, 78, 7, 28, 78, 8, 24, 78, 9, 20, + 78, 10, 15, 78, 11, 11, 78, 12, 7, 79, 13, 3, + 79, 15, -1, 79, 16, -5, 79, 18, -9, 80, 19, -13, + 80, 21, -17, 80, 22, -22, 80, 24, -25, 81, 26, -29, + 77, -3, 79, 78, -3, 78, 78, -3, 77, 78, -3, 76, + 78, -3, 74, 78, -3, 72, 78, -2, 70, 78, -2, 67, + 78, -2, 64, 78, -1, 61, 78, -1, 57, 78, 0, 54, + 78, 0, 50, 78, 1, 46, 78, 2, 42, 78, 3, 38, + 78, 4, 34, 79, 4, 29, 79, 5, 25, 79, 6, 21, + 79, 8, 17, 79, 9, 13, 79, 10, 9, 80, 11, 5, + 80, 13, 1, 80, 14, -4, 80, 15, -8, 81, 17, -12, + 81, 18, -16, 81, 20, -20, 81, 22, -24, 82, 23, -28, + 79, -5, 80, 79, -5, 79, 79, -5, 78, 79, -5, 77, + 79, -5, 75, 79, -5, 73, 79, -4, 71, 79, -4, 68, + 79, -4, 65, 79, -3, 62, 79, -3, 58, 79, -2, 55, + 79, -2, 51, 79, -1, 47, 79, 0, 43, 79, 1, 39, + 79, 1, 35, 80, 2, 31, 80, 3, 27, 80, 4, 23, + 80, 6, 18, 80, 7, 14, 80, 8, 10, 81, 9, 6, + 81, 10, 2, 81, 12, -2, 81, 13, -6, 82, 15, -10, + 82, 16, -14, 82, 18, -19, 82, 20, -22, 83, 21, -26, + 80, -7, 81, 80, -7, 80, 80, -7, 79, 80, -7, 78, + 80, -7, 76, 80, -7, 74, 80, -7, 72, 80, -6, 69, + 80, -6, 66, 80, -5, 63, 80, -5, 59, 80, -4, 56, + 80, -4, 52, 80, -3, 48, 80, -2, 44, 80, -2, 41, + 81, -1, 36, 81, 0, 32, 81, 1, 28, 81, 2, 24, + 81, 3, 20, 81, 5, 16, 81, 6, 12, 82, 7, 8, + 82, 8, 4, 82, 10, -1, 82, 11, -5, 82, 13, -9, + 83, 14, -13, 83, 16, -17, 83, 17, -21, 84, 19, -25, + 81, -9, 81, 81, -9, 81, 81, -9, 80, 81, -9, 78, + 81, -9, 77, 81, -9, 75, 81, -9, 73, 81, -8, 70, + 81, -8, 67, 81, -7, 64, 81, -7, 61, 81, -6, 57, + 81, -6, 53, 81, -5, 49, 81, -4, 46, 81, -4, 42, + 82, -3, 37, 82, -2, 34, 82, -1, 30, 82, 0, 26, + 82, 1, 21, 82, 2, 17, 82, 4, 13, 83, 5, 9, + 83, 6, 5, 83, 8, 1, 83, 9, -3, 83, 10, -7, + 84, 12, -11, 84, 14, -16, 84, 15, -20, 84, 17, -23, + 82, -11, 82, 82, -11, 81, 82, -11, 80, 82, -11, 79, + 82, -11, 78, 82, -11, 76, 82, -11, 74, 82, -10, 71, + 82, -10, 68, 82, -9, 65, 82, -9, 62, 82, -8, 58, + 82, -8, 54, 82, -7, 51, 82, -6, 47, 82, -6, 43, + 83, -5, 39, 83, -4, 35, 83, -3, 31, 83, -2, 27, + 83, -1, 22, 83, 0, 18, 83, 2, 14, 84, 3, 10, + 84, 4, 6, 84, 6, 2, 84, 7, -2, 84, 8, -6, + 85, 10, -10, 85, 12, -14, 85, 13, -18, 85, 15, -22, + 83, -13, 83, 83, -13, 82, 83, -13, 81, 83, -13, 80, + 83, -13, 78, 83, -13, 77, 83, -13, 74, 83, -12, 72, + 83, -12, 69, 83, -11, 66, 83, -11, 63, 83, -10, 59, + 83, -10, 56, 83, -9, 52, 83, -8, 48, 83, -8, 44, + 84, -7, 40, 84, -6, 36, 84, -5, 32, 84, -4, 28, + 84, -3, 24, 84, -2, 20, 84, -1, 16, 85, 1, 12, + 85, 2, 8, 85, 3, 3, 85, 5, -1, 85, 6, -4, + 86, 8, -8, 86, 9, -13, 86, 11, -17, 86, 13, -20, + 84, -16, 84, 84, -16, 83, 84, -16, 82, 84, -16, 81, + 84, -16, 80, 84, -15, 78, 84, -15, 76, 84, -15, 73, + 84, -14, 70, 84, -14, 67, 84, -14, 64, 84, -13, 61, + 84, -12, 57, 85, -12, 53, 85, -11, 50, 85, -10, 46, + 85, -9, 42, 85, -8, 38, 85, -8, 34, 85, -7, 30, + 85, -5, 26, 86, -4, 22, 86, -3, 18, 86, -2, 14, + 86, -1, 10, 86, 1, 5, 86, 2, 1, 87, 4, -3, + 87, 5, -7, 87, 7, -11, 87, 8, -15, 88, 10, -19, + 85, -18, 85, 85, -18, 84, 85, -18, 83, 85, -18, 82, + 85, -18, 80, 85, -17, 79, 85, -17, 77, 85, -17, 74, + 85, -16, 71, 85, -16, 68, 85, -15, 65, 85, -15, 62, + 86, -14, 58, 86, -14, 55, 86, -13, 51, 86, -12, 47, + 86, -11, 43, 86, -10, 39, 86, -10, 35, 86, -9, 31, + 86, -7, 27, 87, -6, 23, 87, -5, 19, 87, -4, 15, + 87, -3, 11, 87, -1, 7, 88, 0, 3, 88, 2, -1, + 88, 3, -5, 88, 5, -9, 88, 6, -13, 89, 8, -17, + 86, -20, 85, 86, -20, 85, 86, -20, 84, 86, -20, 83, + 86, -19, 81, 86, -19, 80, 86, -19, 78, 86, -19, 75, + 86, -18, 72, 86, -18, 69, 86, -17, 66, 87, -17, 63, + 87, -16, 59, 87, -16, 56, 87, -15, 52, 87, -14, 49, + 87, -13, 44, 87, -13, 41, 87, -12, 37, 87, -11, 33, + 87, -9, 28, 88, -8, 24, 88, -7, 21, 88, -6, 17, + 88, -5, 13, 88, -3, 8, 89, -2, 4, 89, -1, 0, + 89, 1, -4, 89, 3, -8, 89, 4, -12, 90, 6, -16, + 87, -22, 86, 87, -22, 85, 87, -22, 85, 87, -22, 84, + 87, -21, 82, 87, -21, 80, 87, -21, 78, 87, -21, 76, + 87, -20, 73, 87, -20, 71, 88, -19, 68, 88, -19, 64, + 88, -18, 61, 88, -18, 57, 88, -17, 54, 88, -16, 50, + 88, -15, 46, 88, -14, 42, 88, -14, 38, 88, -13, 34, + 89, -11, 30, 89, -10, 26, 89, -9, 22, 89, -8, 18, + 89, -7, 14, 89, -5, 10, 90, -4, 6, 90, -3, 2, + 90, -1, -2, 90, 1, -6, 90, 2, -10, 91, 4, -14, + 88, -24, 87, 88, -24, 86, 88, -24, 86, 88, -23, 84, + 88, -23, 83, 88, -23, 81, 88, -23, 79, 88, -23, 77, + 88, -22, 74, 89, -22, 72, 89, -21, 69, 89, -21, 66, + 89, -20, 62, 89, -20, 58, 89, -19, 55, 89, -18, 51, + 89, -17, 47, 89, -16, 43, 89, -16, 39, 89, -15, 36, + 90, -13, 31, 90, -12, 27, 90, -11, 23, 90, -10, 19, + 90, -9, 16, 90, -7, 11, 91, -6, 7, 91, -5, 3, + 91, -3, -1, 91, -1, -5, 91, 0, -9, 92, 2, -13, + 89, -26, 88, 89, -26, 87, 89, -26, 86, 89, -25, 85, + 89, -25, 84, 89, -25, 82, 89, -25, 80, 90, -24, 78, + 90, -24, 75, 90, -24, 73, 90, -23, 70, 90, -23, 67, + 90, -22, 63, 90, -22, 60, 90, -21, 56, 90, -20, 52, + 90, -19, 48, 90, -18, 45, 90, -18, 41, 90, -17, 37, + 91, -15, 33, 91, -14, 29, 91, -13, 25, 91, -12, 21, + 91, -11, 17, 91, -9, 13, 92, -8, 9, 92, -7, 5, + 92, -5, 1, 92, -4, -3, 92, -2, -7, 93, 0, -11, + 90, -28, 88, 90, -27, 88, 90, -27, 87, 90, -27, 86, + 90, -27, 85, 91, -27, 83, 91, -27, 81, 91, -26, 79, + 91, -26, 76, 91, -26, 74, 91, -25, 71, 91, -25, 68, + 91, -24, 64, 91, -23, 61, 91, -23, 57, 91, -22, 54, + 91, -21, 50, 91, -20, 46, 91, -19, 42, 92, -19, 38, + 92, -17, 34, 92, -16, 30, 92, -15, 26, 92, -14, 22, + 92, -13, 18, 92, -11, 14, 93, -10, 10, 93, -9, 6, + 93, -7, 2, 93, -6, -2, 93, -4, -6, 94, -2, -10, + 92, -29, 89, 92, -29, 89, 92, -29, 88, 92, -29, 87, + 92, -29, 86, 92, -29, 84, 92, -29, 82, 92, -28, 80, + 92, -28, 77, 92, -28, 75, 92, -27, 72, 92, -27, 69, + 92, -26, 65, 92, -25, 62, 92, -25, 59, 92, -24, 55, + 92, -23, 51, 92, -22, 47, 92, -21, 43, 93, -20, 40, + 93, -19, 35, 93, -18, 32, 93, -17, 28, 93, -16, 24, + 93, -15, 20, 93, -13, 16, 94, -12, 12, 94, -11, 8, + 94, -9, 4, 94, -8, 0, 94, -6, -4, 95, -5, -8, + 93, -31, 90, 93, -31, 89, 93, -31, 89, 93, -31, 88, + 93, -31, 87, 93, -31, 85, 93, -30, 83, 93, -30, 81, + 93, -30, 78, 93, -29, 76, 93, -29, 73, 93, -28, 70, + 93, -28, 67, 93, -27, 63, 93, -27, 60, 93, -26, 56, + 93, -25, 52, 93, -24, 49, 94, -23, 45, 94, -22, 41, + 94, -21, 37, 94, -20, 33, 94, -19, 29, 94, -18, 25, + 94, -17, 21, 95, -15, 17, 95, -14, 13, 95, -13, 9, + 95, -11, 5, 95, -10, 1, 95, -8, -3, 96, -7, -7, + 94, -33, 91, 94, -33, 90, 94, -33, 90, 94, -33, 89, + 94, -33, 87, 94, -32, 86, 94, -32, 84, 94, -32, 82, + 94, -32, 79, 94, -31, 77, 94, -31, 74, 94, -30, 71, + 94, -30, 68, 94, -29, 64, 94, -28, 61, 94, -28, 58, + 94, -27, 54, 94, -26, 50, 95, -25, 46, 95, -24, 42, + 95, -23, 38, 95, -22, 34, 95, -21, 31, 95, -20, 27, + 95, -19, 23, 96, -17, 18, 96, -16, 15, 96, -15, 11, + 96, -13, 7, 96, -12, 3, 97, -10, -1, 97, -9, -5, + 95, -35, 92, 95, -35, 91, 95, -35, 90, 95, -35, 89, + 95, -34, 88, 95, -34, 87, 95, -34, 85, 95, -34, 83, + 95, -33, 80, 95, -33, 78, 95, -33, 75, 95, -32, 72, + 95, -32, 69, 95, -31, 66, 95, -30, 62, 95, -30, 59, + 95, -29, 55, 96, -28, 51, 96, -27, 48, 96, -26, 44, + 96, -25, 40, 96, -24, 36, 96, -23, 32, 96, -22, 28, + 96, -21, 24, 97, -19, 20, 97, -18, 16, 97, -17, 12, + 97, -15, 8, 97, -14, 4, 98, -12, 0, 98, -11, -4, + 48, 74, 62, 48, 74, 60, 48, 74, 58, 48, 74, 54, + 48, 74, 50, 48, 75, 46, 48, 75, 42, 48, 75, 37, + 49, 75, 32, 49, 76, 28, 49, 76, 23, 49, 77, 18, + 49, 77, 13, 49, 78, 8, 50, 78, 4, 50, 79, -1, + 50, 80, -6, 50, 80, -10, 51, 81, -15, 51, 82, -19, + 51, 83, -24, 52, 84, -28, 52, 85, -32, 52, 85, -36, + 53, 86, -40, 53, 88, -45, 54, 89, -49, 54, 90, -52, + 55, 91, -56, 55, 92, -60, 56, 93, -64, 56, 95, -68, + 48, 74, 62, 48, 74, 60, 48, 74, 58, 48, 74, 54, + 48, 74, 51, 48, 74, 46, 48, 75, 42, 49, 75, 37, + 49, 75, 32, 49, 75, 28, 49, 76, 23, 49, 76, 18, + 49, 77, 13, 49, 77, 8, 50, 78, 4, 50, 79, -1, + 50, 79, -6, 50, 80, -10, 51, 81, -15, 51, 82, -19, + 51, 82, -24, 52, 83, -28, 52, 84, -32, 52, 85, -36, + 53, 86, -40, 53, 87, -45, 54, 88, -48, 54, 89, -52, + 55, 91, -56, 55, 92, -60, 56, 93, -64, 56, 94, -68, + 48, 73, 62, 48, 74, 60, 48, 74, 58, 48, 74, 54, + 48, 74, 51, 48, 74, 47, 49, 74, 42, 49, 75, 37, + 49, 75, 33, 49, 75, 28, 49, 76, 23, 49, 76, 18, + 49, 77, 13, 50, 77, 9, 50, 78, 4, 50, 78, -1, + 50, 79, -6, 51, 80, -10, 51, 80, -14, 51, 81, -19, + 52, 82, -24, 52, 83, -28, 52, 84, -32, 53, 85, -36, + 53, 86, -40, 53, 87, -44, 54, 88, -48, 54, 89, -52, + 55, 90, -56, 55, 92, -60, 56, 93, -64, 56, 94, -68, + 48, 73, 62, 48, 73, 60, 48, 73, 58, 48, 73, 54, + 49, 74, 51, 49, 74, 47, 49, 74, 42, 49, 74, 37, + 49, 74, 33, 49, 75, 28, 49, 75, 23, 49, 76, 19, + 49, 76, 13, 50, 77, 9, 50, 77, 4, 50, 78, 0, + 50, 79, -5, 51, 79, -10, 51, 80, -14, 51, 81, -19, + 52, 82, -23, 52, 83, -28, 52, 84, -32, 53, 85, -36, + 53, 86, -40, 54, 87, -44, 54, 88, -48, 54, 89, -52, + 55, 90, -56, 55, 91, -60, 56, 93, -64, 56, 94, -67, + 49, 73, 62, 49, 73, 60, 49, 73, 58, 49, 73, 54, + 49, 73, 51, 49, 73, 47, 49, 74, 42, 49, 74, 37, + 49, 74, 33, 49, 74, 28, 49, 75, 23, 49, 75, 19, + 50, 76, 14, 50, 76, 9, 50, 77, 4, 50, 78, 0, + 51, 78, -5, 51, 79, -10, 51, 80, -14, 51, 81, -18, + 52, 82, -23, 52, 82, -27, 52, 83, -32, 53, 84, -36, + 53, 85, -40, 54, 86, -44, 54, 88, -48, 55, 89, -52, + 55, 90, -56, 56, 91, -60, 56, 92, -64, 56, 93, -67, + 49, 72, 62, 49, 72, 60, 49, 72, 58, 49, 73, 55, + 49, 73, 51, 49, 73, 47, 49, 73, 43, 49, 73, 38, + 49, 74, 33, 49, 74, 28, 49, 74, 24, 50, 75, 19, + 50, 75, 14, 50, 76, 9, 50, 76, 5, 50, 77, 0, + 51, 78, -5, 51, 79, -10, 51, 79, -14, 52, 80, -18, + 52, 81, -23, 52, 82, -27, 53, 83, -31, 53, 84, -35, + 53, 85, -39, 54, 86, -44, 54, 87, -48, 55, 88, -52, + 55, 89, -55, 56, 91, -60, 56, 92, -63, 57, 93, -67, + 49, 72, 62, 49, 72, 60, 49, 72, 58, 49, 72, 55, + 49, 72, 51, 49, 72, 47, 49, 73, 43, 49, 73, 38, + 49, 73, 33, 49, 73, 29, 50, 74, 24, 50, 74, 19, + 50, 75, 14, 50, 75, 9, 50, 76, 5, 51, 77, 0, + 51, 77, -5, 51, 78, -9, 51, 79, -14, 52, 80, -18, + 52, 81, -23, 52, 81, -27, 53, 82, -31, 53, 83, -35, + 54, 84, -39, 54, 86, -44, 54, 87, -48, 55, 88, -51, + 55, 89, -55, 56, 90, -59, 56, 91, -63, 57, 93, -67, + 49, 71, 63, 49, 71, 60, 49, 71, 58, 49, 71, 55, + 49, 71, 51, 49, 72, 47, 49, 72, 43, 49, 72, 38, + 50, 72, 34, 50, 73, 29, 50, 73, 24, 50, 74, 20, + 50, 74, 14, 50, 75, 10, 51, 75, 5, 51, 76, 1, + 51, 77, -5, 51, 77, -9, 52, 78, -13, 52, 79, -18, + 52, 80, -22, 53, 81, -27, 53, 82, -31, 53, 83, -35, + 54, 84, -39, 54, 85, -43, 55, 86, -47, 55, 87, -51, + 55, 88, -55, 56, 90, -59, 56, 91, -63, 57, 92, -66, + 49, 70, 63, 49, 70, 61, 49, 71, 58, 49, 71, 55, + 49, 71, 51, 50, 71, 48, 50, 71, 43, 50, 71, 38, + 50, 72, 34, 50, 72, 29, 50, 73, 25, 50, 73, 20, + 50, 74, 15, 51, 74, 10, 51, 75, 5, 51, 75, 1, + 51, 76, -4, 52, 77, -9, 52, 78, -13, 52, 78, -17, + 52, 79, -22, 53, 80, -26, 53, 81, -30, 54, 82, -35, + 54, 83, -39, 54, 84, -43, 55, 85, -47, 55, 87, -51, + 56, 88, -55, 56, 89, -59, 57, 90, -63, 57, 92, -66, + 50, 70, 63, 50, 70, 61, 50, 70, 58, 50, 70, 55, + 50, 70, 52, 50, 70, 48, 50, 70, 44, 50, 71, 39, + 50, 71, 34, 50, 71, 30, 50, 72, 25, 50, 72, 20, + 51, 73, 15, 51, 73, 10, 51, 74, 6, 51, 75, 1, + 52, 75, -4, 52, 76, -8, 52, 77, -13, 52, 78, -17, + 53, 79, -22, 53, 80, -26, 53, 80, -30, 54, 81, -34, + 54, 82, -38, 55, 84, -43, 55, 85, -47, 55, 86, -50, + 56, 87, -54, 56, 88, -58, 57, 90, -62, 57, 91, -66, + 50, 69, 63, 50, 69, 61, 50, 69, 59, 50, 69, 55, + 50, 69, 52, 50, 69, 48, 50, 70, 44, 50, 70, 39, + 50, 70, 34, 50, 70, 30, 51, 71, 25, 51, 71, 21, + 51, 72, 15, 51, 72, 11, 51, 73, 6, 52, 74, 2, + 52, 74, -3, 52, 75, -8, 52, 76, -12, 53, 77, -16, + 53, 78, -21, 53, 79, -25, 54, 80, -30, 54, 81, -34, + 54, 82, -38, 55, 83, -42, 55, 84, -46, 56, 85, -50, + 56, 86, -54, 57, 88, -58, 57, 89, -62, 58, 90, -65, + 50, 68, 63, 50, 68, 61, 50, 68, 59, 50, 68, 56, + 50, 68, 52, 51, 68, 49, 51, 68, 44, 51, 69, 39, + 51, 69, 35, 51, 69, 30, 51, 70, 26, 51, 70, 21, + 51, 71, 16, 52, 71, 11, 52, 72, 7, 52, 73, 2, + 52, 73, -3, 53, 74, -7, 53, 75, -12, 53, 76, -16, + 53, 77, -21, 54, 78, -25, 54, 79, -29, 54, 80, -33, + 55, 81, -37, 55, 82, -42, 56, 83, -46, 56, 84, -49, + 56, 85, -53, 57, 87, -57, 57, 88, -61, 58, 89, -65, + 51, 66, 63, 51, 67, 61, 51, 67, 59, 51, 67, 56, + 51, 67, 53, 51, 67, 49, 51, 67, 45, 51, 68, 40, + 51, 68, 35, 51, 68, 31, 51, 69, 26, 52, 69, 22, + 52, 70, 16, 52, 70, 12, 52, 71, 7, 52, 72, 3, + 53, 72, -2, 53, 73, -7, 53, 74, -11, 53, 75, -15, + 54, 76, -20, 54, 77, -24, 54, 78, -28, 55, 79, -33, + 55, 80, -37, 56, 81, -41, 56, 82, -45, 56, 83, -49, + 57, 84, -53, 57, 86, -57, 58, 87, -61, 58, 88, -64, + 51, 65, 63, 51, 65, 61, 51, 65, 59, 51, 66, 56, + 51, 66, 53, 51, 66, 49, 51, 66, 45, 51, 66, 40, + 52, 67, 36, 52, 67, 31, 52, 68, 27, 52, 68, 22, + 52, 69, 17, 52, 69, 12, 53, 70, 8, 53, 70, 3, + 53, 71, -2, 53, 72, -6, 54, 73, -10, 54, 74, -15, + 54, 75, -20, 54, 76, -24, 55, 77, -28, 55, 78, -32, + 55, 79, -36, 56, 80, -41, 56, 81, -44, 57, 82, -48, + 57, 83, -52, 58, 85, -56, 58, 86, -60, 59, 87, -64, + 52, 64, 64, 52, 64, 62, 52, 64, 60, 52, 64, 57, + 52, 65, 53, 52, 65, 50, 52, 65, 46, 52, 65, 41, + 52, 66, 36, 52, 66, 32, 52, 66, 27, 52, 67, 23, + 53, 67, 18, 53, 68, 13, 53, 69, 8, 53, 69, 4, + 53, 70, -1, 54, 71, -5, 54, 72, -10, 54, 72, -14, + 55, 73, -19, 55, 74, -23, 55, 75, -27, 56, 76, -31, + 56, 78, -35, 56, 79, -40, 57, 80, -44, 57, 81, -48, + 58, 82, -52, 58, 84, -56, 58, 85, -60, 59, 86, -63, + 52, 63, 64, 52, 63, 62, 52, 63, 60, 52, 63, 57, + 52, 63, 54, 52, 63, 50, 52, 64, 46, 52, 64, 41, + 52, 64, 37, 53, 65, 32, 53, 65, 28, 53, 66, 23, + 53, 66, 18, 53, 67, 14, 53, 67, 9, 54, 68, 5, + 54, 69, 0, 54, 70, -5, 54, 70, -9, 55, 71, -14, + 55, 72, -18, 55, 73, -23, 56, 74, -27, 56, 75, -31, + 56, 76, -35, 57, 78, -39, 57, 79, -43, 57, 80, -47, + 58, 81, -51, 58, 83, -55, 59, 84, -59, 59, 85, -63, + 53, 61, 64, 53, 62, 62, 53, 62, 60, 53, 62, 57, + 53, 62, 54, 53, 62, 51, 53, 62, 47, 53, 63, 42, + 53, 63, 37, 53, 63, 33, 53, 64, 29, 53, 64, 24, + 53, 65, 19, 54, 65, 14, 54, 66, 10, 54, 67, 5, + 54, 67, 0, 55, 68, -4, 55, 69, -9, 55, 70, -13, + 55, 71, -18, 56, 72, -22, 56, 73, -26, 56, 74, -30, + 57, 75, -34, 57, 76, -39, 58, 78, -43, 58, 79, -46, + 58, 80, -50, 59, 81, -55, 59, 83, -58, 60, 84, -62, + 53, 60, 64, 53, 60, 62, 53, 60, 61, 53, 60, 58, + 53, 60, 55, 53, 61, 51, 53, 61, 47, 53, 61, 42, + 53, 61, 38, 54, 62, 34, 54, 62, 29, 54, 63, 25, + 54, 63, 20, 54, 64, 15, 54, 65, 10, 55, 65, 6, + 55, 66, 1, 55, 67, -3, 55, 68, -8, 56, 69, -12, + 56, 70, -17, 56, 71, -21, 57, 72, -25, 57, 73, -29, + 57, 74, -33, 58, 75, -38, 58, 76, -42, 58, 77, -46, + 59, 79, -50, 59, 80, -54, 60, 81, -58, 60, 83, -61, + 54, 58, 65, 54, 59, 63, 54, 59, 61, 54, 59, 58, + 54, 59, 55, 54, 59, 51, 54, 59, 48, 54, 60, 43, + 54, 60, 39, 54, 60, 34, 54, 61, 30, 54, 61, 25, + 55, 62, 20, 55, 62, 16, 55, 63, 11, 55, 64, 7, + 55, 65, 2, 56, 65, -3, 56, 66, -7, 56, 67, -11, + 56, 68, -16, 57, 69, -20, 57, 70, -24, 57, 71, -29, + 58, 72, -33, 58, 74, -37, 58, 75, -41, 59, 76, -45, + 59, 77, -49, 60, 79, -53, 60, 80, -57, 61, 81, -61, + 54, 57, 65, 54, 57, 63, 54, 57, 61, 54, 57, 59, + 54, 57, 55, 54, 58, 52, 54, 58, 48, 54, 58, 44, + 55, 58, 39, 55, 59, 35, 55, 59, 31, 55, 60, 26, + 55, 60, 21, 55, 61, 16, 55, 62, 12, 56, 62, 8, + 56, 63, 3, 56, 64, -2, 56, 65, -6, 57, 66, -11, + 57, 67, -15, 57, 68, -20, 58, 69, -24, 58, 70, -28, + 58, 71, -32, 59, 72, -36, 59, 73, -40, 59, 75, -44, + 60, 76, -48, 60, 77, -52, 61, 79, -56, 61, 80, -60, + 55, 55, 65, 55, 55, 63, 55, 55, 62, 55, 56, 59, + 55, 56, 56, 55, 56, 53, 55, 56, 49, 55, 56, 44, + 55, 57, 40, 55, 57, 36, 55, 58, 31, 56, 58, 27, + 56, 59, 22, 56, 59, 17, 56, 60, 13, 56, 61, 8, + 56, 62, 3, 57, 62, -1, 57, 63, -5, 57, 64, -10, + 57, 65, -14, 58, 66, -19, 58, 67, -23, 58, 68, -27, + 59, 69, -31, 59, 71, -36, 59, 72, -39, 60, 73, -43, + 60, 75, -47, 61, 76, -52, 61, 77, -55, 62, 79, -59, + 55, 54, 65, 55, 54, 64, 55, 54, 62, 55, 54, 59, + 56, 54, 57, 56, 54, 53, 56, 54, 49, 56, 55, 45, + 56, 55, 41, 56, 56, 36, 56, 56, 32, 56, 56, 28, + 56, 57, 23, 56, 58, 18, 57, 58, 14, 57, 59, 9, + 57, 60, 4, 57, 61, 0, 58, 62, -4, 58, 63, -9, + 58, 64, -14, 58, 65, -18, 59, 66, -22, 59, 67, -26, + 59, 68, -30, 60, 69, -35, 60, 70, -39, 60, 72, -43, + 61, 73, -46, 61, 74, -51, 62, 76, -54, 62, 77, -58, + 56, 52, 66, 56, 52, 64, 56, 52, 63, 56, 52, 60, + 56, 52, 57, 56, 53, 54, 56, 53, 50, 56, 53, 46, + 56, 53, 42, 57, 54, 37, 57, 54, 33, 57, 55, 28, + 57, 55, 23, 57, 56, 19, 57, 57, 15, 57, 57, 10, + 58, 58, 5, 58, 59, 1, 58, 60, -4, 58, 61, -8, + 59, 62, -13, 59, 63, -17, 59, 64, -21, 60, 65, -25, + 60, 66, -29, 60, 68, -34, 61, 69, -38, 61, 70, -42, + 61, 71, -45, 62, 73, -50, 62, 74, -54, 63, 76, -57, + 57, 50, 66, 57, 50, 65, 57, 50, 63, 57, 50, 60, + 57, 51, 58, 57, 51, 54, 57, 51, 51, 57, 51, 46, + 57, 52, 42, 57, 52, 38, 57, 52, 34, 57, 53, 29, + 58, 54, 24, 58, 54, 20, 58, 55, 15, 58, 56, 11, + 58, 56, 6, 59, 57, 2, 59, 58, -3, 59, 59, -7, + 59, 60, -12, 60, 61, -16, 60, 62, -20, 60, 63, -24, + 61, 65, -28, 61, 66, -33, 61, 67, -37, 62, 68, -41, + 62, 70, -45, 62, 71, -49, 63, 73, -53, 63, 74, -56, + 57, 48, 67, 57, 48, 65, 57, 48, 63, 58, 49, 61, + 58, 49, 58, 58, 49, 55, 58, 49, 51, 58, 49, 47, + 58, 50, 43, 58, 50, 39, 58, 51, 35, 58, 51, 30, + 58, 52, 25, 58, 52, 21, 59, 53, 16, 59, 54, 12, + 59, 55, 7, 59, 56, 3, 59, 56, -2, 60, 57, -6, + 60, 58, -11, 60, 59, -15, 61, 61, -19, 61, 62, -23, + 61, 63, -27, 62, 64, -32, 62, 65, -36, 62, 67, -40, + 63, 68, -44, 63, 70, -48, 63, 71, -52, 64, 72, -55, + 58, 46, 67, 58, 46, 65, 58, 47, 64, 58, 47, 61, + 58, 47, 59, 58, 47, 56, 58, 47, 52, 58, 48, 48, + 59, 48, 44, 59, 48, 40, 59, 49, 35, 59, 49, 31, + 59, 50, 26, 59, 51, 22, 59, 51, 17, 59, 52, 13, + 60, 53, 8, 60, 54, 4, 60, 55, -1, 60, 56, -5, + 61, 57, -10, 61, 58, -14, 61, 59, -18, 61, 60, -22, + 62, 61, -26, 62, 62, -31, 62, 64, -35, 63, 65, -39, + 63, 66, -43, 64, 68, -47, 64, 69, -51, 64, 71, -54, + 59, 44, 67, 59, 45, 66, 59, 45, 64, 59, 45, 62, + 59, 45, 59, 59, 45, 56, 59, 45, 53, 59, 46, 49, + 59, 46, 45, 59, 46, 41, 59, 47, 36, 60, 47, 32, + 60, 48, 27, 60, 49, 23, 60, 49, 18, 60, 50, 14, + 60, 51, 9, 61, 52, 5, 61, 53, 0, 61, 54, -4, + 61, 55, -9, 62, 56, -13, 62, 57, -17, 62, 58, -21, + 62, 59, -25, 63, 61, -30, 63, 62, -34, 64, 63, -38, + 64, 65, -42, 64, 66, -46, 65, 68, -50, 65, 69, -53, + 60, 43, 68, 60, 43, 66, 60, 43, 65, 60, 43, 63, + 60, 43, 60, 60, 43, 57, 60, 43, 54, 60, 44, 49, + 60, 44, 46, 60, 45, 41, 60, 45, 37, 60, 45, 33, + 60, 46, 28, 61, 47, 24, 61, 47, 19, 61, 48, 15, + 61, 49, 10, 61, 50, 6, 62, 51, 1, 62, 52, -3, + 62, 53, -8, 62, 54, -12, 63, 55, -16, 63, 56, -20, + 63, 57, -24, 64, 59, -29, 64, 60, -33, 64, 61, -37, + 65, 63, -41, 65, 64, -45, 65, 66, -49, 66, 67, -52, + 60, 41, 68, 60, 41, 67, 60, 41, 65, 61, 41, 63, + 61, 41, 61, 61, 41, 58, 61, 41, 54, 61, 42, 50, + 61, 42, 46, 61, 43, 42, 61, 43, 38, 61, 44, 34, + 61, 44, 29, 61, 45, 25, 62, 45, 20, 62, 46, 16, + 62, 47, 11, 62, 48, 7, 62, 49, 3, 63, 50, -2, + 63, 51, -6, 63, 52, -11, 63, 53, -15, 64, 54, -19, + 64, 56, -23, 64, 57, -28, 65, 58, -32, 65, 60, -36, + 65, 61, -39, 66, 63, -44, 66, 64, -48, 66, 65, -51, + 61, 39, 69, 61, 39, 67, 61, 39, 66, 61, 39, 64, + 61, 39, 61, 61, 39, 58, 61, 39, 55, 62, 40, 51, + 62, 40, 47, 62, 41, 43, 62, 41, 39, 62, 42, 35, + 62, 42, 30, 62, 43, 26, 62, 44, 21, 62, 44, 17, + 63, 45, 12, 63, 46, 8, 63, 47, 4, 63, 48, -1, + 64, 49, -5, 64, 50, -10, 64, 51, -14, 64, 52, -18, + 65, 54, -22, 65, 55, -26, 65, 56, -30, 66, 58, -34, + 66, 59, -38, 66, 61, -43, 67, 62, -46, 67, 64, -50, + 62, 36, 69, 62, 37, 68, 62, 37, 67, 62, 37, 64, + 62, 37, 62, 62, 37, 59, 62, 37, 56, 62, 38, 52, + 62, 38, 48, 63, 39, 44, 63, 39, 40, 63, 40, 36, + 63, 40, 31, 63, 41, 27, 63, 41, 23, 63, 42, 18, + 63, 43, 13, 64, 44, 9, 64, 45, 5, 64, 46, 1, + 64, 47, -4, 65, 48, -8, 65, 49, -13, 65, 50, -17, + 65, 52, -21, 66, 53, -25, 66, 54, -29, 66, 56, -33, + 67, 57, -37, 67, 59, -42, 67, 60, -45, 68, 62, -49, + 63, 34, 70, 63, 34, 69, 63, 35, 67, 63, 35, 65, + 63, 35, 63, 63, 35, 60, 63, 35, 57, 63, 36, 53, + 63, 36, 49, 63, 36, 45, 63, 37, 41, 64, 37, 37, + 64, 38, 32, 64, 39, 28, 64, 39, 24, 64, 40, 19, + 64, 41, 14, 64, 42, 10, 65, 43, 6, 65, 44, 2, + 65, 45, -3, 65, 46, -7, 66, 47, -11, 66, 48, -16, + 66, 50, -20, 66, 51, -24, 67, 52, -28, 67, 54, -32, + 67, 55, -36, 68, 57, -40, 68, 58, -44, 69, 60, -48, + 64, 32, 70, 64, 32, 69, 64, 32, 68, 64, 32, 66, + 64, 32, 64, 64, 32, 61, 64, 33, 58, 64, 33, 54, + 64, 33, 50, 64, 34, 46, 64, 34, 42, 65, 35, 38, + 65, 36, 34, 65, 36, 29, 65, 37, 25, 65, 38, 21, + 65, 39, 16, 66, 39, 12, 66, 40, 7, 66, 41, 3, + 66, 42, -2, 66, 44, -6, 67, 45, -10, 67, 46, -14, + 67, 47, -18, 67, 49, -23, 68, 50, -27, 68, 51, -31, + 68, 53, -35, 69, 54, -39, 69, 56, -43, 69, 57, -47, + 65, 30, 71, 65, 30, 70, 65, 30, 69, 65, 30, 67, + 65, 30, 64, 65, 30, 62, 65, 31, 59, 65, 31, 55, + 65, 31, 51, 65, 32, 47, 65, 32, 43, 65, 33, 39, + 66, 33, 35, 66, 34, 30, 66, 35, 26, 66, 36, 22, + 66, 36, 17, 66, 37, 13, 67, 38, 9, 67, 39, 4, + 67, 40, 0, 67, 42, -5, 67, 43, -9, 68, 44, -13, + 68, 45, -17, 68, 47, -21, 69, 48, -25, 69, 49, -29, + 69, 51, -33, 70, 52, -38, 70, 54, -42, 70, 55, -45, + 66, 28, 72, 66, 28, 70, 66, 28, 69, 66, 28, 67, + 66, 28, 65, 66, 28, 62, 66, 29, 59, 66, 29, 56, + 66, 29, 52, 66, 30, 48, 66, 30, 44, 66, 31, 40, + 66, 31, 36, 67, 32, 32, 67, 33, 27, 67, 33, 23, + 67, 34, 18, 67, 35, 14, 67, 36, 10, 68, 37, 6, + 68, 38, 1, 68, 39, -3, 68, 41, -7, 69, 42, -12, + 69, 43, -16, 69, 44, -20, 69, 46, -24, 70, 47, -28, + 70, 49, -32, 70, 50, -36, 71, 52, -40, 71, 53, -44, + 67, 26, 72, 67, 26, 71, 67, 26, 70, 67, 26, 68, + 67, 26, 66, 67, 26, 63, 67, 26, 60, 67, 27, 57, + 67, 27, 53, 67, 28, 49, 67, 28, 45, 67, 29, 41, + 67, 29, 37, 67, 30, 33, 68, 31, 28, 68, 31, 24, + 68, 32, 19, 68, 33, 15, 68, 34, 11, 68, 35, 7, + 69, 36, 2, 69, 37, -2, 69, 38, -6, 69, 40, -10, + 70, 41, -14, 70, 42, -19, 70, 44, -23, 70, 45, -27, + 71, 47, -31, 71, 48, -35, 71, 50, -39, 72, 51, -43, + 68, 23, 73, 68, 23, 72, 68, 24, 70, 68, 24, 69, + 68, 24, 66, 68, 24, 64, 68, 24, 61, 68, 25, 57, + 68, 25, 54, 68, 25, 50, 68, 26, 46, 68, 26, 42, + 68, 27, 38, 68, 28, 34, 68, 28, 30, 69, 29, 25, + 69, 30, 21, 69, 31, 16, 69, 32, 12, 69, 33, 8, + 70, 34, 3, 70, 35, -1, 70, 36, -5, 70, 38, -9, + 70, 39, -13, 71, 40, -18, 71, 42, -22, 71, 43, -26, + 72, 44, -30, 72, 46, -34, 72, 48, -38, 73, 49, -42, + 68, 21, 73, 68, 21, 72, 69, 21, 71, 69, 22, 69, + 69, 22, 67, 69, 22, 65, 69, 22, 62, 69, 23, 58, + 69, 23, 55, 69, 23, 51, 69, 24, 47, 69, 24, 44, + 69, 25, 39, 69, 26, 35, 69, 26, 31, 70, 27, 27, + 70, 28, 22, 70, 29, 18, 70, 30, 14, 70, 31, 9, + 70, 32, 5, 71, 33, 1, 71, 34, -4, 71, 35, -8, + 71, 37, -12, 72, 38, -16, 72, 40, -20, 72, 41, -24, + 72, 42, -28, 73, 44, -33, 73, 46, -36, 73, 47, -40, + 69, 19, 74, 69, 19, 73, 69, 19, 72, 69, 19, 70, + 70, 20, 68, 70, 20, 66, 70, 20, 63, 70, 20, 59, + 70, 21, 56, 70, 21, 52, 70, 22, 49, 70, 22, 45, + 70, 23, 40, 70, 24, 36, 70, 24, 32, 70, 25, 28, + 71, 26, 23, 71, 27, 19, 71, 28, 15, 71, 29, 11, + 71, 30, 6, 72, 31, 2, 72, 32, -2, 72, 33, -6, + 72, 35, -10, 72, 36, -15, 73, 37, -19, 73, 39, -23, + 73, 40, -27, 74, 42, -31, 74, 43, -35, 74, 45, -39, + 70, 17, 75, 70, 17, 74, 70, 17, 72, 70, 17, 71, + 70, 17, 69, 70, 18, 66, 71, 18, 64, 71, 18, 60, + 71, 19, 57, 71, 19, 53, 71, 20, 50, 71, 20, 46, + 71, 21, 41, 71, 21, 37, 71, 22, 33, 71, 23, 29, + 72, 24, 24, 72, 25, 20, 72, 26, 16, 72, 27, 12, + 72, 28, 7, 72, 29, 3, 73, 30, -1, 73, 31, -5, + 73, 32, -9, 73, 34, -14, 74, 35, -18, 74, 37, -22, + 74, 38, -26, 75, 40, -30, 75, 41, -34, 75, 43, -38, + 71, 15, 75, 71, 15, 74, 71, 15, 73, 71, 15, 71, + 71, 15, 70, 71, 16, 67, 71, 16, 65, 72, 16, 61, + 72, 17, 58, 72, 17, 54, 72, 17, 51, 72, 18, 47, + 72, 19, 42, 72, 19, 38, 72, 20, 34, 72, 21, 30, + 72, 22, 26, 73, 23, 22, 73, 23, 17, 73, 24, 13, + 73, 26, 9, 73, 27, 4, 74, 28, 0, 74, 29, -4, + 74, 30, -8, 74, 32, -12, 75, 33, -16, 75, 35, -20, + 75, 36, -24, 75, 38, -29, 76, 39, -33, 76, 41, -36, + 72, 13, 76, 72, 13, 75, 72, 13, 74, 72, 13, 72, + 72, 13, 70, 72, 13, 68, 72, 14, 65, 72, 14, 62, + 73, 14, 59, 73, 15, 55, 73, 15, 52, 73, 16, 48, + 73, 16, 44, 73, 17, 40, 73, 18, 36, 73, 19, 32, + 73, 19, 27, 74, 20, 23, 74, 21, 19, 74, 22, 15, + 74, 23, 10, 74, 25, 6, 74, 26, 2, 75, 27, -2, + 75, 28, -6, 75, 30, -11, 75, 31, -15, 76, 32, -19, + 76, 34, -23, 76, 36, -27, 77, 37, -31, 77, 39, -35, + 73, 11, 76, 73, 11, 76, 73, 11, 75, 73, 11, 73, + 73, 11, 71, 73, 11, 69, 73, 12, 66, 73, 12, 63, + 74, 12, 60, 74, 13, 56, 74, 13, 53, 74, 14, 49, + 74, 14, 45, 74, 15, 41, 74, 16, 37, 74, 16, 33, + 74, 17, 28, 74, 18, 24, 75, 19, 20, 75, 20, 16, + 75, 21, 11, 75, 22, 7, 75, 24, 3, 76, 25, -1, + 76, 26, -5, 76, 28, -10, 76, 29, -14, 77, 30, -18, + 77, 32, -22, 77, 33, -26, 77, 35, -30, 78, 37, -34, + 74, 9, 77, 74, 9, 76, 74, 9, 75, 74, 9, 74, + 74, 9, 72, 74, 9, 70, 74, 9, 67, 74, 10, 64, + 75, 10, 61, 75, 11, 57, 75, 11, 54, 75, 12, 50, + 75, 12, 46, 75, 13, 42, 75, 14, 38, 75, 14, 34, + 75, 15, 29, 75, 16, 25, 76, 17, 21, 76, 18, 17, + 76, 19, 13, 76, 20, 8, 76, 21, 4, 77, 23, 0, + 77, 24, -4, 77, 25, -8, 77, 27, -12, 78, 28, -16, + 78, 30, -20, 78, 31, -25, 78, 33, -28, 79, 34, -32, + 75, 6, 78, 75, 6, 77, 75, 7, 76, 75, 7, 74, + 75, 7, 73, 75, 7, 71, 75, 7, 68, 75, 8, 65, + 75, 8, 62, 76, 8, 59, 76, 9, 55, 76, 9, 51, + 76, 10, 47, 76, 11, 43, 76, 11, 39, 76, 12, 35, + 76, 13, 31, 76, 14, 27, 77, 15, 23, 77, 16, 19, + 77, 17, 14, 77, 18, 10, 77, 19, 6, 77, 21, 2, + 78, 22, -2, 78, 23, -7, 78, 25, -11, 78, 26, -15, + 79, 27, -19, 79, 29, -23, 79, 31, -27, 80, 32, -31, + 76, 4, 79, 76, 4, 78, 76, 4, 77, 76, 5, 75, + 76, 5, 74, 76, 5, 71, 76, 5, 69, 76, 6, 66, + 76, 6, 63, 77, 6, 60, 77, 7, 56, 77, 7, 52, + 77, 8, 48, 77, 9, 44, 77, 9, 41, 77, 10, 37, + 77, 11, 32, 77, 12, 28, 78, 13, 24, 78, 14, 20, + 78, 15, 15, 78, 16, 11, 78, 17, 7, 78, 18, 3, + 79, 20, -1, 79, 21, -5, 79, 22, -9, 79, 24, -13, + 80, 25, -17, 80, 27, -22, 80, 29, -26, 80, 30, -29, + 77, 2, 79, 77, 2, 78, 77, 2, 77, 77, 3, 76, + 77, 3, 74, 77, 3, 72, 77, 3, 70, 77, 3, 67, + 77, 4, 64, 78, 4, 61, 78, 5, 57, 78, 5, 54, + 78, 6, 49, 78, 7, 46, 78, 7, 42, 78, 8, 38, + 78, 9, 33, 78, 10, 29, 79, 11, 25, 79, 12, 21, + 79, 13, 17, 79, 14, 13, 79, 15, 9, 79, 16, 4, + 80, 18, 0, 80, 19, -4, 80, 20, -8, 80, 22, -12, + 81, 23, -16, 81, 25, -20, 81, 26, -24, 81, 28, -28, + 78, 0, 80, 78, 0, 79, 78, 0, 78, 78, 0, 77, + 78, 1, 75, 78, 1, 73, 78, 1, 71, 78, 1, 68, + 78, 2, 65, 79, 2, 62, 79, 3, 58, 79, 3, 55, + 79, 4, 51, 79, 4, 47, 79, 5, 43, 79, 6, 39, + 79, 7, 35, 79, 8, 31, 79, 9, 27, 80, 10, 23, + 80, 11, 18, 80, 12, 14, 80, 13, 10, 80, 14, 6, + 81, 15, 2, 81, 17, -3, 81, 18, -7, 81, 20, -11, + 81, 21, -15, 82, 23, -19, 82, 24, -23, 82, 26, -27, + 79, -2, 81, 79, -2, 80, 79, -2, 79, 79, -2, 78, + 79, -1, 76, 79, -1, 74, 79, -1, 72, 79, -1, 69, + 79, 0, 66, 80, 0, 63, 80, 1, 59, 80, 1, 56, + 80, 2, 52, 80, 2, 48, 80, 3, 44, 80, 4, 40, + 80, 5, 36, 80, 6, 32, 80, 6, 28, 81, 7, 24, + 81, 9, 19, 81, 10, 15, 81, 11, 11, 81, 12, 7, + 82, 13, 3, 82, 15, -1, 82, 16, -5, 82, 17, -9, + 82, 19, -13, 83, 21, -17, 83, 22, -21, 83, 24, -25, + 80, -4, 81, 80, -4, 81, 80, -4, 80, 80, -4, 78, + 80, -4, 77, 80, -3, 75, 80, -3, 73, 80, -3, 70, + 81, -2, 67, 81, -2, 64, 81, -2, 60, 81, -1, 57, + 81, 0, 53, 81, 0, 49, 81, 1, 45, 81, 2, 42, + 81, 3, 37, 81, 3, 33, 81, 4, 29, 82, 5, 25, + 82, 6, 21, 82, 8, 17, 82, 9, 13, 82, 10, 9, + 83, 11, 5, 83, 13, 0, 83, 14, -4, 83, 15, -8, + 83, 17, -12, 84, 18, -16, 84, 20, -20, 84, 22, -24, + 81, -6, 82, 81, -6, 81, 81, -6, 81, 81, -6, 79, + 81, -6, 78, 81, -5, 76, 81, -5, 74, 81, -5, 71, + 82, -4, 68, 82, -4, 65, 82, -4, 62, 82, -3, 58, + 82, -2, 54, 82, -2, 50, 82, -1, 47, 82, 0, 43, + 82, 0, 39, 82, 1, 35, 82, 2, 31, 83, 3, 27, + 83, 4, 22, 83, 5, 18, 83, 7, 14, 83, 8, 10, + 83, 9, 6, 84, 10, 2, 84, 12, -2, 84, 13, -6, + 84, 15, -10, 85, 16, -15, 85, 18, -18, 85, 19, -22, + 82, -8, 83, 82, -8, 82, 82, -8, 81, 82, -8, 80, + 82, -8, 78, 82, -7, 77, 82, -7, 74, 83, -7, 72, + 83, -6, 69, 83, -6, 66, 83, -6, 63, 83, -5, 59, + 83, -5, 55, 83, -4, 52, 83, -3, 48, 83, -2, 44, + 83, -2, 40, 83, -1, 36, 83, 0, 32, 84, 1, 28, + 84, 2, 23, 84, 3, 19, 84, 4, 15, 84, 6, 11, + 84, 7, 8, 85, 8, 3, 85, 10, -1, 85, 11, -5, + 85, 13, -9, 86, 14, -13, 86, 16, -17, 86, 17, -21, + 83, -10, 84, 83, -10, 83, 83, -10, 82, 83, -10, 81, + 83, -10, 79, 83, -9, 77, 84, -9, 75, 84, -9, 73, + 84, -9, 70, 84, -8, 67, 84, -8, 64, 84, -7, 60, + 84, -7, 57, 84, -6, 53, 84, -5, 49, 84, -5, 45, + 84, -4, 41, 84, -3, 37, 85, -2, 33, 85, -1, 29, + 85, 0, 25, 85, 1, 21, 85, 2, 17, 85, 4, 13, + 85, 5, 9, 86, 6, 4, 86, 8, 1, 86, 9, -3, + 86, 10, -7, 87, 12, -12, 87, 14, -16, 87, 15, -19, + 85, -13, 85, 85, -13, 84, 85, -12, 83, 85, -12, 82, + 85, -12, 80, 85, -12, 79, 85, -12, 76, 85, -11, 74, + 85, -11, 71, 85, -11, 68, 85, -10, 65, 85, -10, 62, + 85, -9, 58, 85, -8, 54, 85, -8, 51, 85, -7, 47, + 86, -6, 43, 86, -5, 39, 86, -4, 35, 86, -4, 31, + 86, -2, 27, 86, -1, 23, 86, 0, 19, 87, 1, 15, + 87, 2, 11, 87, 4, 6, 87, 5, 2, 87, 6, -2, + 88, 8, -6, 88, 9, -10, 88, 11, -14, 88, 13, -18, + 86, -15, 85, 86, -14, 85, 86, -14, 84, 86, -14, 83, + 86, -14, 81, 86, -14, 79, 86, -14, 77, 86, -13, 75, + 86, -13, 72, 86, -13, 69, 86, -12, 66, 86, -12, 63, + 86, -11, 59, 86, -10, 56, 86, -10, 52, 86, -9, 48, + 87, -8, 44, 87, -7, 40, 87, -6, 36, 87, -6, 32, + 87, -4, 28, 87, -3, 24, 87, -2, 20, 88, -1, 16, + 88, 0, 12, 88, 2, 8, 88, 3, 4, 88, 4, 0, + 89, 6, -4, 89, 7, -8, 89, 9, -12, 89, 10, -16, + 87, -17, 86, 87, -16, 85, 87, -16, 85, 87, -16, 83, + 87, -16, 82, 87, -16, 80, 87, -16, 78, 87, -15, 76, + 87, -15, 73, 87, -15, 70, 87, -14, 67, 87, -14, 64, + 87, -13, 60, 87, -12, 57, 87, -12, 53, 87, -11, 50, + 88, -10, 45, 88, -9, 42, 88, -9, 38, 88, -8, 34, + 88, -6, 29, 88, -5, 25, 88, -4, 21, 89, -3, 18, + 89, -2, 14, 89, 0, 9, 89, 1, 5, 89, 2, 1, + 90, 4, -3, 90, 5, -7, 90, 7, -11, 90, 8, -15, + 88, -18, 87, 88, -18, 86, 88, -18, 85, 88, -18, 84, + 88, -18, 83, 88, -18, 81, 88, -18, 79, 88, -17, 77, + 88, -17, 74, 88, -17, 71, 88, -16, 68, 88, -16, 65, + 88, -15, 61, 88, -14, 58, 88, -14, 54, 89, -13, 51, + 89, -12, 47, 89, -11, 43, 89, -11, 39, 89, -10, 35, + 89, -8, 31, 89, -7, 27, 89, -6, 23, 90, -5, 19, + 90, -4, 15, 90, -3, 11, 90, -1, 7, 90, 0, 3, + 91, 2, -1, 91, 3, -5, 91, 5, -9, 91, 6, -13, + 89, -20, 88, 89, -20, 87, 89, -20, 86, 89, -20, 85, + 89, -20, 84, 89, -20, 82, 89, -20, 80, 89, -19, 78, + 89, -19, 75, 89, -19, 72, 89, -18, 69, 89, -18, 66, + 89, -17, 63, 89, -16, 59, 89, -16, 56, 90, -15, 52, + 90, -14, 48, 90, -13, 44, 90, -13, 40, 90, -12, 36, + 90, -10, 32, 90, -9, 28, 90, -8, 24, 91, -7, 20, + 91, -6, 16, 91, -5, 12, 91, -3, 8, 91, -2, 4, + 92, -1, 0, 92, 1, -4, 92, 3, -8, 92, 4, -12, + 90, -22, 88, 90, -22, 88, 90, -22, 87, 90, -22, 86, + 90, -22, 85, 90, -22, 83, 90, -22, 81, 90, -21, 79, + 90, -21, 76, 90, -20, 73, 90, -20, 71, 90, -20, 67, + 90, -19, 64, 90, -18, 60, 91, -18, 57, 91, -17, 53, + 91, -16, 49, 91, -15, 45, 91, -14, 42, 91, -14, 38, + 91, -12, 33, 91, -11, 30, 91, -10, 26, 92, -9, 22, + 92, -8, 18, 92, -7, 14, 92, -5, 10, 92, -4, 6, + 93, -3, 2, 93, -1, -3, 93, 1, -6, 93, 2, -10, + 91, -24, 89, 91, -24, 89, 91, -24, 88, 91, -24, 87, + 91, -24, 85, 91, -24, 84, 91, -23, 82, 91, -23, 80, + 91, -23, 77, 91, -22, 75, 91, -22, 72, 91, -21, 69, + 91, -21, 65, 92, -20, 62, 92, -20, 58, 92, -19, 55, + 92, -18, 50, 92, -17, 47, 92, -16, 43, 92, -16, 39, + 92, -14, 35, 92, -13, 31, 93, -12, 27, 93, -11, 23, + 93, -10, 19, 93, -9, 15, 93, -7, 11, 93, -6, 7, + 94, -5, 3, 94, -3, -1, 94, -1, -5, 94, 0, -9, + 92, -26, 90, 92, -26, 89, 92, -26, 89, 92, -26, 88, + 92, -26, 86, 92, -26, 85, 92, -25, 83, 92, -25, 81, + 92, -25, 78, 92, -24, 76, 92, -24, 73, 92, -23, 70, + 92, -23, 66, 93, -22, 63, 93, -22, 59, 93, -21, 56, + 93, -20, 52, 93, -19, 48, 93, -18, 44, 93, -18, 41, + 93, -16, 36, 93, -15, 32, 94, -14, 29, 94, -13, 25, + 94, -12, 21, 94, -11, 16, 94, -9, 13, 94, -8, 9, + 95, -7, 5, 95, -5, 0, 95, -4, -3, 95, -2, -7, + 93, -28, 91, 93, -28, 90, 93, -28, 89, 93, -28, 88, + 93, -28, 87, 93, -27, 86, 93, -27, 84, 93, -27, 82, + 93, -27, 79, 93, -26, 77, 93, -26, 74, 93, -25, 71, + 94, -25, 67, 94, -24, 64, 94, -24, 61, 94, -23, 57, + 94, -22, 53, 94, -21, 49, 94, -20, 46, 94, -19, 42, + 94, -18, 38, 94, -17, 34, 95, -16, 30, 95, -15, 26, + 95, -14, 22, 95, -13, 18, 95, -11, 14, 95, -10, 10, + 96, -9, 6, 96, -7, 2, 96, -6, -2, 96, -4, -6, + 94, -30, 92, 94, -30, 91, 94, -30, 90, 94, -30, 89, + 94, -29, 88, 94, -29, 87, 94, -29, 85, 94, -29, 82, + 94, -28, 80, 94, -28, 78, 94, -28, 75, 95, -27, 72, + 95, -27, 68, 95, -26, 65, 95, -25, 62, 95, -25, 58, + 95, -24, 54, 95, -23, 51, 95, -22, 47, 95, -21, 43, + 95, -20, 39, 96, -19, 35, 96, -18, 31, 96, -17, 28, + 96, -16, 24, 96, -15, 19, 96, -13, 15, 96, -12, 12, + 97, -11, 8, 97, -9, 3, 97, -8, 0, 97, -6, -4, + 95, -32, 92, 95, -32, 92, 95, -32, 91, 95, -31, 90, + 95, -31, 89, 95, -31, 87, 95, -31, 86, 95, -31, 83, + 95, -30, 81, 96, -30, 79, 96, -30, 76, 96, -29, 73, + 96, -28, 70, 96, -28, 66, 96, -27, 63, 96, -27, 60, + 96, -26, 56, 96, -25, 52, 96, -24, 48, 96, -23, 45, + 96, -22, 40, 97, -21, 37, 97, -20, 33, 97, -19, 29, + 97, -18, 25, 97, -17, 21, 97, -15, 17, 97, -14, 13, + 98, -13, 9, 98, -11, 5, 98, -10, 1, 98, -8, -3, + 50, 76, 64, 50, 76, 62, 50, 76, 60, 50, 77, 56, + 50, 77, 53, 50, 77, 49, 50, 77, 45, 50, 77, 40, + 50, 78, 35, 51, 78, 30, 51, 78, 26, 51, 79, 21, + 51, 79, 16, 51, 80, 11, 51, 80, 6, 52, 81, 2, + 52, 81, -3, 52, 82, -8, 52, 83, -12, 53, 84, -16, + 53, 84, -21, 53, 85, -25, 54, 86, -29, 54, 87, -34, + 54, 88, -38, 55, 89, -42, 55, 90, -46, 56, 91, -50, + 56, 92, -54, 57, 94, -58, 57, 95, -62, 58, 96, -65, + 50, 76, 64, 50, 76, 62, 50, 76, 60, 50, 76, 56, + 50, 76, 53, 50, 76, 49, 50, 77, 45, 50, 77, 40, + 50, 77, 35, 51, 78, 30, 51, 78, 26, 51, 78, 21, + 51, 79, 16, 51, 79, 11, 51, 80, 7, 52, 80, 2, + 52, 81, -3, 52, 82, -7, 52, 83, -12, 53, 83, -16, + 53, 84, -21, 53, 85, -25, 54, 86, -29, 54, 87, -33, + 55, 88, -37, 55, 89, -42, 55, 90, -46, 56, 91, -50, + 56, 92, -54, 57, 93, -58, 57, 94, -62, 58, 96, -65, + 50, 76, 64, 50, 76, 62, 50, 76, 60, 50, 76, 56, + 50, 76, 53, 50, 76, 49, 50, 76, 45, 50, 77, 40, + 51, 77, 35, 51, 77, 31, 51, 78, 26, 51, 78, 21, + 51, 79, 16, 51, 79, 11, 52, 80, 7, 52, 80, 2, + 52, 81, -3, 52, 82, -7, 53, 82, -12, 53, 83, -16, + 53, 84, -21, 54, 85, -25, 54, 86, -29, 54, 87, -33, + 55, 88, -37, 55, 89, -42, 55, 90, -46, 56, 91, -50, + 56, 92, -53, 57, 93, -58, 57, 94, -61, 58, 95, -65, + 50, 75, 64, 50, 75, 62, 50, 75, 60, 50, 76, 56, + 50, 76, 53, 50, 76, 49, 50, 76, 45, 51, 76, 40, + 51, 77, 35, 51, 77, 31, 51, 77, 26, 51, 78, 21, + 51, 78, 16, 51, 79, 11, 52, 79, 7, 52, 80, 2, + 52, 81, -3, 52, 81, -7, 53, 82, -12, 53, 83, -16, + 53, 84, -21, 54, 84, -25, 54, 85, -29, 54, 86, -33, + 55, 87, -37, 55, 88, -42, 56, 89, -46, 56, 90, -49, + 56, 92, -53, 57, 93, -58, 57, 94, -61, 58, 95, -65, + 50, 75, 64, 50, 75, 62, 50, 75, 60, 50, 75, 57, + 50, 75, 53, 51, 76, 49, 51, 76, 45, 51, 76, 40, + 51, 76, 35, 51, 77, 31, 51, 77, 26, 51, 77, 22, + 51, 78, 16, 52, 78, 12, 52, 79, 7, 52, 80, 3, + 52, 80, -3, 53, 81, -7, 53, 82, -11, 53, 82, -16, + 53, 83, -20, 54, 84, -25, 54, 85, -29, 54, 86, -33, + 55, 87, -37, 55, 88, -41, 56, 89, -45, 56, 90, -49, + 57, 91, -53, 57, 92, -57, 57, 94, -61, 58, 95, -65, + 51, 75, 64, 51, 75, 62, 51, 75, 60, 51, 75, 57, + 51, 75, 53, 51, 75, 49, 51, 75, 45, 51, 76, 40, + 51, 76, 36, 51, 76, 31, 51, 77, 26, 51, 77, 22, + 52, 77, 16, 52, 78, 12, 52, 79, 7, 52, 79, 3, + 52, 80, -2, 53, 81, -7, 53, 81, -11, 53, 82, -15, + 54, 83, -20, 54, 84, -24, 54, 85, -29, 55, 86, -33, + 55, 87, -37, 55, 88, -41, 56, 89, -45, 56, 90, -49, + 57, 91, -53, 57, 92, -57, 58, 93, -61, 58, 94, -65, + 51, 74, 64, 51, 74, 62, 51, 74, 60, 51, 74, 57, + 51, 74, 53, 51, 75, 49, 51, 75, 45, 51, 75, 40, + 51, 75, 36, 51, 76, 31, 51, 76, 27, 52, 76, 22, + 52, 77, 17, 52, 77, 12, 52, 78, 8, 52, 79, 3, + 53, 79, -2, 53, 80, -7, 53, 81, -11, 53, 82, -15, + 54, 82, -20, 54, 83, -24, 54, 84, -28, 55, 85, -32, + 55, 86, -37, 56, 87, -41, 56, 88, -45, 56, 89, -49, + 57, 90, -53, 57, 92, -57, 58, 93, -61, 58, 94, -64, + 51, 73, 64, 51, 74, 62, 51, 74, 60, 51, 74, 57, + 51, 74, 54, 51, 74, 50, 51, 74, 46, 51, 74, 41, + 51, 75, 36, 51, 75, 31, 52, 75, 27, 52, 76, 22, + 52, 76, 17, 52, 77, 12, 52, 77, 8, 53, 78, 3, + 53, 79, -2, 53, 79, -6, 53, 80, -11, 54, 81, -15, + 54, 82, -20, 54, 83, -24, 55, 84, -28, 55, 85, -32, + 55, 86, -36, 56, 87, -41, 56, 88, -45, 57, 89, -49, + 57, 90, -52, 57, 91, -57, 58, 92, -60, 58, 94, -64, + 51, 73, 64, 51, 73, 62, 51, 73, 60, 51, 73, 57, + 51, 73, 54, 51, 73, 50, 51, 74, 46, 51, 74, 41, + 52, 74, 36, 52, 74, 32, 52, 75, 27, 52, 75, 23, + 52, 76, 17, 52, 76, 13, 53, 77, 8, 53, 77, 4, + 53, 78, -1, 53, 79, -6, 54, 80, -10, 54, 80, -15, + 54, 81, -19, 54, 82, -24, 55, 83, -28, 55, 84, -32, + 55, 85, -36, 56, 86, -40, 56, 87, -44, 57, 88, -48, + 57, 89, -52, 58, 91, -56, 58, 92, -60, 59, 93, -64, + 51, 72, 65, 51, 72, 63, 51, 72, 60, 51, 72, 57, + 52, 72, 54, 52, 73, 50, 52, 73, 46, 52, 73, 41, + 52, 73, 37, 52, 74, 32, 52, 74, 27, 52, 74, 23, + 52, 75, 18, 53, 76, 13, 53, 76, 8, 53, 77, 4, + 53, 77, -1, 53, 78, -6, 54, 79, -10, 54, 80, -14, + 54, 81, -19, 55, 81, -23, 55, 82, -27, 55, 83, -32, + 56, 84, -36, 56, 85, -40, 57, 86, -44, 57, 88, -48, + 57, 89, -52, 58, 90, -56, 58, 91, -60, 59, 92, -63, + 52, 71, 65, 52, 71, 63, 52, 71, 61, 52, 71, 58, + 52, 72, 54, 52, 72, 50, 52, 72, 46, 52, 72, 41, + 52, 73, 37, 52, 73, 32, 52, 73, 28, 53, 74, 23, + 53, 74, 18, 53, 75, 13, 53, 75, 9, 53, 76, 4, + 54, 77, -1, 54, 77, -5, 54, 78, -9, 54, 79, -14, + 55, 80, -19, 55, 81, -23, 55, 82, -27, 56, 83, -31, + 56, 84, -35, 56, 85, -40, 57, 86, -44, 57, 87, -47, + 58, 88, -51, 58, 89, -56, 59, 90, -59, 59, 92, -63, + 52, 70, 65, 52, 70, 63, 52, 70, 61, 52, 70, 58, + 52, 70, 54, 52, 71, 51, 52, 71, 47, 52, 71, 42, + 53, 71, 37, 53, 72, 33, 53, 72, 28, 53, 73, 24, + 53, 73, 19, 53, 74, 14, 53, 74, 9, 54, 75, 5, + 54, 76, 0, 54, 76, -5, 54, 77, -9, 55, 78, -13, + 55, 79, -18, 55, 80, -22, 56, 81, -26, 56, 81, -31, + 56, 82, -35, 57, 84, -39, 57, 85, -43, 58, 86, -47, + 58, 87, -51, 58, 88, -55, 59, 89, -59, 59, 91, -62, + 52, 69, 65, 52, 69, 63, 52, 69, 61, 53, 69, 58, + 53, 69, 55, 53, 70, 51, 53, 70, 47, 53, 70, 42, + 53, 70, 38, 53, 71, 33, 53, 71, 29, 53, 72, 24, + 53, 72, 19, 54, 73, 14, 54, 73, 10, 54, 74, 5, + 54, 75, 0, 54, 75, -4, 55, 76, -8, 55, 77, -13, + 55, 78, -18, 56, 79, -22, 56, 80, -26, 56, 81, -30, + 57, 82, -34, 57, 83, -39, 57, 84, -43, 58, 85, -46, + 58, 86, -50, 59, 87, -55, 59, 89, -58, 60, 90, -62, + 53, 68, 65, 53, 68, 63, 53, 68, 61, 53, 68, 58, + 53, 68, 55, 53, 69, 51, 53, 69, 47, 53, 69, 43, + 53, 69, 38, 53, 70, 34, 53, 70, 29, 54, 70, 25, + 54, 71, 20, 54, 72, 15, 54, 72, 10, 54, 73, 6, + 55, 74, 1, 55, 74, -4, 55, 75, -8, 55, 76, -12, + 56, 77, -17, 56, 78, -21, 56, 79, -25, 57, 80, -30, + 57, 81, -34, 57, 82, -38, 58, 83, -42, 58, 84, -46, + 59, 85, -50, 59, 86, -54, 60, 88, -58, 60, 89, -62, + 53, 67, 65, 53, 67, 63, 53, 67, 62, 53, 67, 59, + 53, 67, 55, 53, 67, 52, 53, 68, 48, 54, 68, 43, + 54, 68, 39, 54, 69, 34, 54, 69, 30, 54, 69, 25, + 54, 70, 20, 54, 70, 16, 55, 71, 11, 55, 72, 6, + 55, 72, 1, 55, 73, -3, 55, 74, -7, 56, 75, -12, + 56, 76, -16, 56, 77, -21, 57, 78, -25, 57, 78, -29, + 57, 80, -33, 58, 81, -37, 58, 82, -41, 59, 83, -45, + 59, 84, -49, 59, 85, -53, 60, 87, -57, 60, 88, -61, + 54, 66, 66, 54, 66, 64, 54, 66, 62, 54, 66, 59, + 54, 66, 56, 54, 66, 52, 54, 66, 48, 54, 67, 44, + 54, 67, 39, 54, 67, 35, 54, 68, 30, 54, 68, 26, + 55, 69, 21, 55, 69, 16, 55, 70, 12, 55, 70, 7, + 55, 71, 2, 56, 72, -2, 56, 73, -7, 56, 74, -11, + 56, 74, -16, 57, 75, -20, 57, 76, -24, 57, 77, -28, + 58, 78, -32, 58, 80, -37, 59, 81, -41, 59, 82, -45, + 59, 83, -49, 60, 84, -53, 60, 86, -57, 61, 87, -60, + 54, 64, 66, 54, 64, 64, 54, 64, 62, 54, 64, 59, + 54, 65, 56, 54, 65, 53, 54, 65, 49, 54, 65, 44, + 55, 66, 40, 55, 66, 35, 55, 66, 31, 55, 67, 26, + 55, 67, 21, 55, 68, 17, 55, 69, 12, 56, 69, 8, + 56, 70, 3, 56, 71, -2, 56, 71, -6, 57, 72, -10, + 57, 73, -15, 57, 74, -19, 58, 75, -24, 58, 76, -28, + 58, 77, -32, 59, 78, -36, 59, 80, -40, 59, 81, -44, + 60, 82, -48, 60, 83, -52, 61, 84, -56, 61, 86, -60, + 55, 63, 66, 55, 63, 64, 55, 63, 62, 55, 63, 60, + 55, 63, 57, 55, 63, 53, 55, 64, 49, 55, 64, 45, + 55, 64, 40, 55, 65, 36, 55, 65, 32, 55, 65, 27, + 56, 66, 22, 56, 67, 17, 56, 67, 13, 56, 68, 8, + 56, 69, 3, 57, 69, -1, 57, 70, -5, 57, 71, -10, + 57, 72, -14, 58, 73, -19, 58, 74, -23, 58, 75, -27, + 59, 76, -31, 59, 77, -36, 59, 78, -40, 60, 79, -43, + 60, 81, -47, 61, 82, -52, 61, 83, -55, 62, 85, -59, + 55, 61, 66, 55, 61, 65, 55, 62, 63, 55, 62, 60, + 55, 62, 57, 55, 62, 54, 55, 62, 50, 56, 63, 45, + 56, 63, 41, 56, 63, 37, 56, 64, 32, 56, 64, 28, + 56, 65, 23, 56, 65, 18, 56, 66, 14, 57, 66, 9, + 57, 67, 4, 57, 68, 0, 57, 69, -5, 58, 70, -9, + 58, 71, -14, 58, 72, -18, 58, 73, -22, 59, 74, -26, + 59, 75, -30, 60, 76, -35, 60, 77, -39, 60, 78, -43, + 61, 79, -47, 61, 81, -51, 62, 82, -55, 62, 83, -58, + 56, 60, 66, 56, 60, 65, 56, 60, 63, 56, 60, 60, + 56, 60, 57, 56, 60, 54, 56, 61, 50, 56, 61, 46, + 56, 61, 42, 56, 62, 37, 56, 62, 33, 56, 63, 28, + 57, 63, 23, 57, 64, 19, 57, 64, 14, 57, 65, 10, + 57, 66, 5, 58, 67, 1, 58, 67, -4, 58, 68, -8, + 58, 69, -13, 59, 70, -17, 59, 71, -21, 59, 72, -25, + 60, 73, -30, 60, 74, -34, 60, 76, -38, 61, 77, -42, + 61, 78, -46, 62, 79, -50, 62, 81, -54, 62, 82, -58, + 56, 58, 67, 56, 58, 65, 56, 58, 63, 56, 59, 61, + 56, 59, 58, 56, 59, 55, 57, 59, 51, 57, 59, 46, + 57, 60, 42, 57, 60, 38, 57, 61, 34, 57, 61, 29, + 57, 62, 24, 57, 62, 20, 58, 63, 15, 58, 63, 11, + 58, 64, 6, 58, 65, 1, 58, 66, -3, 59, 67, -7, + 59, 68, -12, 59, 69, -16, 59, 70, -21, 60, 71, -25, + 60, 72, -29, 61, 73, -33, 61, 74, -37, 61, 75, -41, + 62, 77, -45, 62, 78, -49, 62, 79, -53, 63, 81, -57, + 57, 57, 67, 57, 57, 65, 57, 57, 64, 57, 57, 61, + 57, 57, 58, 57, 57, 55, 57, 58, 51, 57, 58, 47, + 57, 58, 43, 57, 59, 39, 58, 59, 34, 58, 59, 30, + 58, 60, 25, 58, 61, 20, 58, 61, 16, 58, 62, 12, + 59, 63, 7, 59, 63, 2, 59, 64, -2, 59, 65, -6, + 59, 66, -11, 60, 67, -15, 60, 68, -20, 60, 69, -24, + 61, 70, -28, 61, 71, -32, 61, 73, -36, 62, 74, -40, + 62, 75, -44, 63, 77, -49, 63, 78, -52, 63, 79, -56, + 58, 55, 67, 58, 55, 66, 58, 55, 64, 58, 55, 62, + 58, 55, 59, 58, 56, 56, 58, 56, 52, 58, 56, 48, + 58, 56, 44, 58, 57, 39, 58, 57, 35, 58, 58, 31, + 58, 58, 26, 59, 59, 21, 59, 60, 17, 59, 60, 12, + 59, 61, 7, 59, 62, 3, 60, 63, -1, 60, 63, -6, + 60, 65, -10, 60, 65, -15, 61, 66, -19, 61, 68, -23, + 61, 69, -27, 62, 70, -32, 62, 71, -36, 62, 72, -39, + 63, 74, -43, 63, 75, -48, 64, 76, -51, 64, 78, -55, + 58, 53, 68, 58, 53, 66, 58, 53, 65, 58, 54, 62, + 58, 54, 59, 58, 54, 56, 58, 54, 53, 58, 54, 48, + 59, 55, 44, 59, 55, 40, 59, 56, 36, 59, 56, 31, + 59, 57, 26, 59, 57, 22, 59, 58, 18, 60, 59, 13, + 60, 59, 8, 60, 60, 4, 60, 61, 0, 60, 62, -5, + 61, 63, -9, 61, 64, -14, 61, 65, -18, 62, 66, -22, + 62, 67, -26, 62, 68, -31, 63, 69, -35, 63, 71, -39, + 63, 72, -42, 64, 73, -47, 64, 75, -51, 64, 76, -54, + 59, 52, 68, 59, 52, 67, 59, 52, 65, 59, 52, 63, + 59, 52, 60, 59, 52, 57, 59, 52, 53, 59, 53, 49, + 59, 53, 45, 59, 53, 41, 59, 54, 37, 60, 54, 32, + 60, 55, 27, 60, 55, 23, 60, 56, 19, 60, 57, 14, + 60, 58, 9, 61, 58, 5, 61, 59, 1, 61, 60, -4, + 61, 61, -9, 62, 62, -13, 62, 63, -17, 62, 64, -21, + 62, 65, -25, 63, 67, -30, 63, 68, -34, 63, 69, -38, + 64, 70, -42, 64, 72, -46, 65, 73, -50, 65, 75, -53, + 60, 50, 68, 60, 50, 67, 60, 50, 66, 60, 50, 63, + 60, 50, 61, 60, 50, 57, 60, 51, 54, 60, 51, 50, + 60, 51, 46, 60, 52, 42, 60, 52, 37, 60, 52, 33, + 60, 53, 28, 61, 54, 24, 61, 54, 19, 61, 55, 15, + 61, 56, 10, 61, 57, 6, 61, 57, 1, 62, 58, -3, + 62, 59, -8, 62, 60, -12, 63, 61, -16, 63, 63, -20, + 63, 64, -24, 63, 65, -29, 64, 66, -33, 64, 67, -37, + 64, 69, -41, 65, 70, -45, 65, 71, -49, 66, 73, -52, + 60, 48, 69, 60, 48, 67, 60, 48, 66, 60, 48, 64, + 60, 48, 61, 60, 48, 58, 60, 49, 55, 61, 49, 51, + 61, 49, 47, 61, 50, 42, 61, 50, 38, 61, 51, 34, + 61, 51, 29, 61, 52, 25, 61, 52, 20, 62, 53, 16, + 62, 54, 11, 62, 55, 7, 62, 56, 2, 62, 57, -2, + 63, 58, -7, 63, 59, -11, 63, 60, -15, 63, 61, -19, + 64, 62, -23, 64, 63, -28, 64, 64, -32, 65, 66, -36, + 65, 67, -40, 66, 68, -44, 66, 70, -48, 66, 71, -52, + 61, 46, 69, 61, 46, 68, 61, 46, 66, 61, 46, 64, + 61, 46, 62, 61, 47, 59, 61, 47, 55, 61, 47, 51, + 61, 47, 47, 61, 48, 43, 62, 48, 39, 62, 49, 35, + 62, 49, 30, 62, 50, 26, 62, 51, 21, 62, 51, 17, + 62, 52, 12, 63, 53, 8, 63, 54, 4, 63, 55, -1, + 63, 56, -6, 64, 57, -10, 64, 58, -14, 64, 59, -18, + 64, 60, -22, 65, 61, -27, 65, 63, -31, 65, 64, -35, + 66, 65, -39, 66, 67, -43, 67, 68, -47, 67, 69, -51, + 62, 44, 70, 62, 44, 68, 62, 44, 67, 62, 44, 65, + 62, 44, 62, 62, 45, 59, 62, 45, 56, 62, 45, 52, + 62, 46, 48, 62, 46, 44, 62, 46, 40, 62, 47, 36, + 63, 47, 31, 63, 48, 27, 63, 49, 22, 63, 49, 18, + 63, 50, 13, 63, 51, 9, 64, 52, 5, 64, 53, 0, + 64, 54, -4, 64, 55, -9, 65, 56, -13, 65, 57, -17, + 65, 58, -21, 65, 60, -26, 66, 61, -30, 66, 62, -34, + 66, 63, -37, 67, 65, -42, 67, 66, -46, 68, 68, -49, + 63, 42, 70, 63, 42, 69, 63, 42, 68, 63, 42, 65, + 63, 42, 63, 63, 43, 60, 63, 43, 57, 63, 43, 53, + 63, 44, 49, 63, 44, 45, 63, 44, 41, 63, 45, 37, + 63, 46, 32, 63, 46, 28, 64, 47, 23, 64, 47, 19, + 64, 48, 14, 64, 49, 10, 64, 50, 6, 65, 51, 1, + 65, 52, -3, 65, 53, -8, 65, 54, -12, 66, 55, -16, + 66, 56, -20, 66, 58, -25, 66, 59, -29, 67, 60, -32, + 67, 62, -36, 68, 63, -41, 68, 64, -45, 68, 66, -48, + 63, 40, 71, 63, 40, 69, 63, 40, 68, 63, 40, 66, + 63, 40, 64, 63, 41, 61, 64, 41, 58, 64, 41, 54, + 64, 42, 50, 64, 42, 46, 64, 42, 42, 64, 43, 38, + 64, 44, 33, 64, 44, 29, 64, 45, 24, 65, 45, 20, + 65, 46, 15, 65, 47, 11, 65, 48, 7, 65, 49, 2, + 66, 50, -2, 66, 51, -6, 66, 52, -11, 66, 53, -15, + 67, 54, -19, 67, 56, -23, 67, 57, -27, 68, 58, -31, + 68, 60, -35, 68, 61, -40, 69, 63, -44, 69, 64, -47, + 64, 38, 71, 64, 38, 70, 64, 38, 69, 64, 38, 67, + 64, 38, 64, 64, 39, 61, 64, 39, 58, 64, 39, 54, + 64, 40, 51, 65, 40, 47, 65, 40, 43, 65, 41, 39, + 65, 42, 34, 65, 42, 30, 65, 43, 25, 65, 43, 21, + 66, 44, 16, 66, 45, 12, 66, 46, 8, 66, 47, 4, + 66, 48, -1, 67, 49, -5, 67, 50, -10, 67, 51, -14, + 67, 52, -18, 68, 54, -22, 68, 55, -26, 68, 56, -30, + 69, 58, -34, 69, 59, -39, 69, 61, -42, 70, 62, -46, + 65, 35, 72, 65, 36, 71, 65, 36, 69, 65, 36, 67, + 65, 36, 65, 65, 36, 62, 65, 36, 59, 65, 37, 55, + 66, 37, 52, 66, 37, 48, 66, 38, 44, 66, 38, 40, + 66, 39, 35, 66, 40, 31, 66, 40, 27, 66, 41, 23, + 67, 42, 18, 67, 43, 13, 67, 44, 9, 67, 44, 5, + 67, 46, 0, 68, 47, -4, 68, 48, -8, 68, 49, -12, + 68, 50, -16, 69, 51, -21, 69, 53, -25, 69, 54, -29, + 69, 55, -33, 70, 57, -37, 70, 58, -41, 71, 60, -45, + 66, 33, 72, 66, 34, 71, 66, 34, 70, 66, 34, 68, + 66, 34, 66, 66, 34, 63, 66, 34, 60, 66, 35, 56, + 66, 35, 53, 66, 35, 49, 67, 36, 45, 67, 36, 41, + 67, 37, 36, 67, 38, 32, 67, 38, 28, 67, 39, 24, + 67, 40, 19, 67, 41, 15, 68, 42, 10, 68, 42, 6, + 68, 44, 1, 68, 45, -3, 69, 46, -7, 69, 47, -11, + 69, 48, -15, 69, 49, -20, 70, 51, -24, 70, 52, -28, + 70, 53, -32, 71, 55, -36, 71, 56, -40, 71, 58, -44, + 67, 31, 73, 67, 31, 72, 67, 32, 71, 67, 32, 69, + 67, 32, 66, 67, 32, 64, 67, 32, 61, 67, 33, 57, + 67, 33, 54, 67, 33, 50, 67, 34, 46, 67, 34, 42, + 68, 35, 37, 68, 35, 33, 68, 36, 29, 68, 37, 25, + 68, 38, 20, 68, 39, 16, 69, 39, 12, 69, 40, 7, + 69, 41, 3, 69, 43, -2, 69, 44, -6, 70, 45, -10, + 70, 46, -14, 70, 47, -18, 70, 49, -22, 71, 50, -26, + 71, 51, -30, 71, 53, -35, 72, 54, -39, 72, 56, -42, + 68, 29, 73, 68, 29, 72, 68, 29, 71, 68, 30, 69, + 68, 30, 67, 68, 30, 65, 68, 30, 62, 68, 30, 58, + 68, 31, 55, 68, 31, 51, 68, 32, 47, 68, 32, 43, + 68, 33, 38, 69, 33, 34, 69, 34, 30, 69, 35, 26, + 69, 36, 21, 69, 36, 17, 69, 37, 13, 70, 38, 9, + 70, 39, 4, 70, 40, 0, 70, 42, -4, 70, 43, -9, + 71, 44, -13, 71, 45, -17, 71, 47, -21, 72, 48, -25, + 72, 49, -29, 72, 51, -34, 73, 52, -37, 73, 54, -41, + 69, 27, 74, 69, 27, 73, 69, 27, 72, 69, 27, 70, + 69, 28, 68, 69, 28, 65, 69, 28, 62, 69, 28, 59, + 69, 29, 55, 69, 29, 52, 69, 30, 48, 69, 30, 44, + 69, 31, 40, 69, 31, 35, 70, 32, 31, 70, 33, 27, + 70, 34, 22, 70, 34, 18, 70, 35, 14, 70, 36, 10, + 71, 37, 5, 71, 38, 1, 71, 39, -3, 71, 41, -7, + 72, 42, -11, 72, 43, -16, 72, 45, -20, 72, 46, -24, + 73, 47, -28, 73, 49, -32, 73, 50, -36, 74, 52, -40, + 70, 25, 75, 70, 25, 74, 70, 25, 72, 70, 25, 71, + 70, 26, 69, 70, 26, 66, 70, 26, 63, 70, 26, 60, + 70, 27, 56, 70, 27, 53, 70, 27, 49, 70, 28, 45, + 70, 29, 41, 70, 29, 37, 70, 30, 32, 71, 31, 28, + 71, 31, 24, 71, 32, 19, 71, 33, 15, 71, 34, 11, + 71, 35, 6, 72, 36, 2, 72, 37, -2, 72, 39, -6, + 72, 40, -10, 73, 41, -15, 73, 42, -19, 73, 44, -23, + 73, 45, -27, 74, 47, -31, 74, 48, -35, 74, 50, -39, + 70, 23, 75, 71, 23, 74, 71, 23, 73, 71, 23, 71, + 71, 23, 69, 71, 24, 67, 71, 24, 64, 71, 24, 61, + 71, 25, 57, 71, 25, 54, 71, 25, 50, 71, 26, 46, + 71, 26, 42, 71, 27, 38, 71, 28, 34, 71, 28, 29, + 72, 29, 25, 72, 30, 21, 72, 31, 16, 72, 32, 12, + 72, 33, 8, 73, 34, 3, 73, 35, -1, 73, 36, -5, + 73, 38, -9, 73, 39, -13, 74, 40, -17, 74, 42, -21, + 74, 43, -25, 75, 45, -30, 75, 46, -34, 75, 48, -37, + 71, 21, 76, 71, 21, 75, 71, 21, 74, 71, 21, 72, + 71, 21, 70, 72, 22, 68, 72, 22, 65, 72, 22, 62, + 72, 22, 58, 72, 23, 55, 72, 23, 51, 72, 24, 47, + 72, 24, 43, 72, 25, 39, 72, 26, 35, 72, 26, 31, + 73, 27, 26, 73, 28, 22, 73, 29, 18, 73, 30, 14, + 73, 31, 9, 73, 32, 5, 74, 33, 1, 74, 34, -3, + 74, 36, -8, 74, 37, -12, 75, 38, -16, 75, 40, -20, + 75, 41, -24, 75, 43, -28, 76, 44, -32, 76, 46, -36, + 72, 19, 76, 72, 19, 75, 72, 19, 74, 72, 19, 73, + 72, 19, 71, 72, 19, 68, 73, 20, 66, 73, 20, 62, + 73, 20, 59, 73, 21, 56, 73, 21, 52, 73, 22, 48, + 73, 22, 44, 73, 23, 40, 73, 24, 36, 73, 24, 32, + 73, 25, 27, 74, 26, 23, 74, 27, 19, 74, 28, 15, + 74, 29, 10, 74, 30, 6, 75, 31, 2, 75, 32, -2, + 75, 33, -6, 75, 35, -11, 75, 36, -15, 76, 38, -19, + 76, 39, -23, 76, 40, -27, 77, 42, -31, 77, 43, -35, + 73, 17, 77, 73, 17, 76, 73, 17, 75, 73, 17, 73, + 73, 17, 72, 73, 17, 69, 73, 18, 67, 73, 18, 63, + 74, 18, 60, 74, 19, 57, 74, 19, 53, 74, 20, 49, + 74, 20, 45, 74, 21, 41, 74, 21, 37, 74, 22, 33, + 74, 23, 28, 75, 24, 24, 75, 25, 20, 75, 26, 16, + 75, 27, 11, 75, 28, 7, 75, 29, 3, 76, 30, -1, + 76, 31, -5, 76, 33, -9, 76, 34, -13, 77, 35, -17, + 77, 37, -21, 77, 38, -26, 77, 40, -30, 78, 41, -34, + 74, 15, 78, 74, 15, 77, 74, 15, 76, 74, 15, 74, + 74, 15, 72, 74, 15, 70, 74, 15, 68, 74, 16, 64, + 75, 16, 61, 75, 16, 58, 75, 17, 54, 75, 17, 50, + 75, 18, 46, 75, 19, 42, 75, 19, 38, 75, 20, 34, + 75, 21, 30, 75, 22, 26, 76, 23, 21, 76, 24, 17, + 76, 25, 13, 76, 26, 9, 76, 27, 5, 77, 28, 0, + 77, 29, -4, 77, 31, -8, 77, 32, -12, 78, 33, -16, + 78, 35, -20, 78, 36, -24, 78, 38, -28, 79, 39, -32, + 75, 12, 78, 75, 12, 77, 75, 13, 76, 75, 13, 75, + 75, 13, 73, 75, 13, 71, 75, 13, 68, 75, 14, 65, + 75, 14, 62, 76, 14, 59, 76, 15, 55, 76, 15, 52, + 76, 16, 47, 76, 17, 43, 76, 17, 39, 76, 18, 35, + 76, 19, 31, 76, 20, 27, 77, 21, 23, 77, 21, 19, + 77, 23, 14, 77, 24, 10, 77, 25, 6, 77, 26, 2, + 78, 27, -2, 78, 29, -7, 78, 30, -11, 78, 31, -15, + 79, 33, -19, 79, 34, -23, 79, 36, -27, 80, 37, -31, + 76, 10, 79, 76, 10, 78, 76, 10, 77, 76, 11, 76, + 76, 11, 74, 76, 11, 72, 76, 11, 69, 76, 12, 66, + 76, 12, 63, 76, 12, 60, 77, 13, 56, 77, 13, 53, + 77, 14, 48, 77, 14, 45, 77, 15, 41, 77, 16, 37, + 77, 17, 32, 77, 17, 28, 77, 18, 24, 78, 19, 20, + 78, 20, 15, 78, 22, 11, 78, 23, 7, 78, 24, 3, + 79, 25, -1, 79, 26, -5, 79, 28, -9, 79, 29, -13, + 80, 30, -17, 80, 32, -22, 80, 34, -26, 80, 35, -29, + 77, 8, 80, 77, 8, 79, 77, 8, 78, 77, 8, 76, + 77, 9, 75, 77, 9, 73, 77, 9, 70, 77, 9, 67, + 77, 10, 64, 77, 10, 61, 78, 11, 57, 78, 11, 54, + 78, 12, 50, 78, 12, 46, 78, 13, 42, 78, 14, 38, + 78, 15, 33, 78, 15, 29, 78, 16, 25, 79, 17, 21, + 79, 18, 17, 79, 19, 13, 79, 20, 9, 79, 22, 4, + 80, 23, 0, 80, 24, -4, 80, 26, -8, 80, 27, -12, + 80, 28, -16, 81, 30, -20, 81, 31, -24, 81, 33, -28, + 78, 6, 80, 78, 6, 79, 78, 6, 79, 78, 6, 77, + 78, 7, 75, 78, 7, 73, 78, 7, 71, 78, 7, 68, + 78, 8, 65, 78, 8, 62, 79, 8, 58, 79, 9, 55, + 79, 10, 51, 79, 10, 47, 79, 11, 43, 79, 12, 39, + 79, 12, 35, 79, 13, 31, 79, 14, 27, 80, 15, 23, + 80, 16, 18, 80, 17, 14, 80, 18, 10, 80, 20, 6, + 80, 21, 2, 81, 22, -3, 81, 23, -7, 81, 25, -11, + 81, 26, -15, 82, 28, -19, 82, 29, -23, 82, 31, -27, + 79, 4, 81, 79, 4, 80, 79, 4, 79, 79, 4, 78, + 79, 4, 76, 79, 5, 74, 79, 5, 72, 79, 5, 69, + 79, 6, 66, 79, 6, 63, 79, 6, 59, 80, 7, 56, + 80, 7, 52, 80, 8, 48, 80, 9, 44, 80, 9, 40, + 80, 10, 36, 80, 11, 32, 80, 12, 28, 81, 13, 24, + 81, 14, 19, 81, 15, 15, 81, 16, 11, 81, 17, 7, + 81, 19, 3, 82, 20, -1, 82, 21, -5, 82, 23, -9, + 82, 24, -13, 83, 26, -18, 83, 27, -21, 83, 29, -25, + 80, 2, 82, 80, 2, 81, 80, 2, 80, 80, 2, 79, + 80, 2, 77, 80, 3, 75, 80, 3, 73, 80, 3, 70, + 80, 3, 67, 80, 4, 64, 80, 4, 61, 81, 5, 57, + 81, 5, 53, 81, 6, 49, 81, 7, 45, 81, 7, 42, + 81, 8, 37, 81, 9, 33, 81, 10, 29, 81, 11, 25, + 82, 12, 21, 82, 13, 17, 82, 14, 13, 82, 15, 9, + 82, 16, 5, 83, 18, 0, 83, 19, -4, 83, 21, -8, + 83, 22, -12, 84, 24, -16, 84, 25, -20, 84, 27, -24, + 81, 0, 82, 81, 0, 82, 81, 0, 81, 81, 0, 79, + 81, 0, 78, 81, 1, 76, 81, 1, 74, 81, 1, 71, + 81, 1, 68, 81, 2, 65, 81, 2, 62, 82, 3, 58, + 82, 3, 54, 82, 4, 50, 82, 5, 47, 82, 5, 43, + 82, 6, 38, 82, 7, 34, 82, 8, 31, 82, 9, 27, + 83, 10, 22, 83, 11, 18, 83, 12, 14, 83, 13, 10, + 83, 14, 6, 84, 16, 1, 84, 17, -3, 84, 18, -6, + 84, 20, -10, 84, 21, -15, 85, 23, -19, 85, 24, -23, + 82, -2, 83, 82, -2, 82, 82, -2, 82, 82, -2, 80, + 82, -2, 79, 82, -2, 77, 82, -1, 75, 82, -1, 72, + 82, -1, 69, 82, 0, 66, 82, 0, 63, 83, 1, 59, + 83, 1, 55, 83, 2, 52, 83, 3, 48, 83, 3, 44, + 83, 4, 40, 83, 5, 36, 83, 6, 32, 83, 7, 28, + 84, 8, 23, 84, 9, 19, 84, 10, 15, 84, 11, 11, + 84, 12, 7, 85, 14, 3, 85, 15, -1, 85, 16, -5, + 85, 18, -9, 85, 19, -13, 86, 21, -17, 86, 22, -21, + 83, -4, 84, 83, -4, 83, 83, -4, 82, 83, -4, 81, + 83, -4, 79, 83, -4, 78, 83, -3, 75, 83, -3, 73, + 83, -3, 70, 83, -2, 67, 83, -2, 64, 84, -1, 60, + 84, -1, 56, 84, 0, 53, 84, 0, 49, 84, 1, 45, + 84, 2, 41, 84, 3, 37, 84, 4, 33, 84, 5, 29, + 85, 6, 25, 85, 7, 21, 85, 8, 17, 85, 9, 13, + 85, 10, 9, 85, 12, 4, 86, 13, 0, 86, 14, -4, + 86, 16, -8, 86, 17, -12, 87, 19, -16, 87, 20, -20, + 84, -6, 85, 84, -6, 84, 84, -6, 83, 84, -6, 82, + 84, -6, 80, 84, -6, 78, 84, -5, 76, 84, -5, 74, + 84, -5, 71, 84, -4, 68, 85, -4, 65, 85, -3, 62, + 85, -3, 58, 85, -2, 54, 85, -2, 50, 85, -1, 47, + 85, 0, 42, 85, 1, 38, 85, 2, 34, 85, 3, 31, + 86, 4, 26, 86, 5, 22, 86, 6, 18, 86, 7, 14, + 86, 8, 10, 86, 9, 6, 87, 11, 2, 87, 12, -2, + 87, 13, -6, 87, 15, -11, 88, 17, -14, 88, 18, -18, + 85, -9, 85, 85, -9, 85, 85, -9, 84, 86, -8, 83, + 86, -8, 81, 86, -8, 80, 86, -8, 77, 86, -8, 75, + 86, -7, 72, 86, -7, 69, 86, -6, 66, 86, -6, 63, + 86, -5, 59, 86, -5, 56, 86, -4, 52, 86, -3, 48, + 86, -3, 44, 86, -2, 40, 87, -1, 36, 87, 0, 32, + 87, 1, 28, 87, 2, 24, 87, 3, 20, 87, 4, 16, + 87, 5, 12, 88, 7, 7, 88, 8, 4, 88, 9, 0, + 88, 11, -4, 89, 12, -9, 89, 14, -13, 89, 15, -16, + 87, -11, 86, 87, -11, 86, 87, -11, 85, 87, -10, 84, + 87, -10, 82, 87, -10, 80, 87, -10, 78, 87, -10, 76, + 87, -9, 73, 87, -9, 70, 87, -8, 67, 87, -8, 64, + 87, -7, 60, 87, -7, 57, 87, -6, 53, 87, -5, 49, + 87, -5, 45, 87, -4, 41, 88, -3, 37, 88, -2, 34, + 88, -1, 29, 88, 0, 25, 88, 1, 21, 88, 2, 17, + 88, 3, 13, 89, 5, 9, 89, 6, 5, 89, 7, 1, + 89, 9, -3, 90, 10, -7, 90, 12, -11, 90, 13, -15, + 88, -13, 87, 88, -13, 86, 88, -13, 86, 88, -12, 84, + 88, -12, 83, 88, -12, 81, 88, -12, 79, 88, -12, 77, + 88, -11, 74, 88, -11, 71, 88, -10, 68, 88, -10, 65, + 88, -9, 61, 88, -9, 58, 88, -8, 54, 88, -8, 51, + 88, -7, 46, 88, -6, 43, 89, -5, 39, 89, -4, 35, + 89, -3, 30, 89, -2, 27, 89, -1, 23, 89, 0, 19, + 89, 1, 15, 90, 3, 10, 90, 4, 6, 90, 5, 2, + 90, 7, -1, 90, 8, -6, 91, 10, -10, 91, 11, -14, + 89, -15, 88, 89, -15, 87, 89, -15, 86, 89, -14, 85, + 89, -14, 84, 89, -14, 82, 89, -14, 80, 89, -14, 78, + 89, -13, 75, 89, -13, 72, 89, -12, 69, 89, -12, 66, + 89, -11, 62, 89, -11, 59, 89, -10, 55, 89, -10, 52, + 89, -9, 48, 89, -8, 44, 90, -7, 40, 90, -6, 36, + 90, -5, 32, 90, -4, 28, 90, -3, 24, 90, -2, 20, + 90, -1, 16, 91, 1, 12, 91, 2, 8, 91, 3, 4, + 91, 5, 0, 91, 6, -4, 92, 8, -8, 92, 9, -12, + 90, -17, 88, 90, -17, 88, 90, -17, 87, 90, -16, 86, + 90, -16, 85, 90, -16, 83, 90, -16, 81, 90, -16, 79, + 90, -15, 76, 90, -15, 73, 90, -14, 70, 90, -14, 67, + 90, -13, 64, 90, -13, 60, 90, -12, 57, 90, -12, 53, + 90, -11, 49, 91, -10, 45, 91, -9, 41, 91, -8, 38, + 91, -7, 33, 91, -6, 29, 91, -5, 25, 91, -4, 21, + 91, -3, 18, 92, -1, 13, 92, 0, 9, 92, 1, 5, + 92, 3, 1, 92, 4, -3, 93, 6, -7, 93, 7, -11, + 91, -19, 89, 91, -19, 89, 91, -18, 88, 91, -18, 87, + 91, -18, 85, 91, -18, 84, 91, -18, 82, 91, -17, 80, + 91, -17, 77, 91, -17, 74, 91, -16, 71, 91, -16, 68, + 91, -15, 65, 91, -15, 61, 91, -14, 58, 91, -13, 54, + 91, -13, 50, 92, -12, 46, 92, -11, 43, 92, -10, 39, + 92, -9, 35, 92, -8, 31, 92, -7, 27, 92, -6, 23, + 92, -5, 19, 93, -3, 15, 93, -2, 11, 93, -1, 7, + 93, 0, 3, 93, 2, -1, 94, 3, -5, 94, 5, -9, + 92, -21, 90, 92, -20, 89, 92, -20, 89, 92, -20, 88, + 92, -20, 86, 92, -20, 85, 92, -20, 83, 92, -19, 80, + 92, -19, 78, 92, -19, 75, 92, -18, 73, 92, -18, 70, + 92, -17, 66, 92, -17, 63, 92, -16, 59, 92, -15, 56, + 92, -15, 51, 93, -14, 48, 93, -13, 44, 93, -12, 40, + 93, -11, 36, 93, -10, 32, 93, -9, 28, 93, -8, 24, + 94, -7, 20, 94, -5, 16, 94, -4, 12, 94, -3, 8, + 94, -2, 4, 94, 0, 0, 95, 1, -4, 95, 3, -8, + 93, -22, 91, 93, -22, 90, 93, -22, 89, 93, -22, 88, + 93, -22, 87, 93, -22, 86, 93, -22, 84, 93, -21, 81, + 93, -21, 79, 93, -21, 76, 93, -20, 74, 93, -20, 71, + 93, -19, 67, 93, -19, 64, 93, -18, 60, 93, -17, 57, + 94, -17, 53, 94, -16, 49, 94, -15, 45, 94, -14, 42, + 94, -13, 37, 94, -12, 33, 94, -11, 30, 94, -10, 26, + 95, -9, 22, 95, -7, 17, 95, -6, 14, 95, -5, 10, + 95, -4, 6, 95, -2, 1, 96, -1, -2, 96, 1, -6, + 94, -24, 92, 94, -24, 91, 94, -24, 90, 94, -24, 89, + 94, -24, 88, 94, -24, 87, 94, -24, 85, 94, -23, 82, + 94, -23, 80, 94, -23, 77, 94, -22, 75, 94, -22, 72, + 94, -21, 68, 94, -21, 65, 94, -20, 62, 94, -19, 58, + 95, -19, 54, 95, -18, 50, 95, -17, 47, 95, -16, 43, + 95, -15, 39, 95, -14, 35, 95, -13, 31, 95, -12, 27, + 96, -11, 23, 96, -9, 19, 96, -8, 15, 96, -7, 11, + 96, -6, 7, 96, -4, 3, 97, -3, -1, 97, -1, -5, + 95, -26, 92, 95, -26, 92, 95, -26, 91, 95, -26, 90, + 95, -26, 89, 95, -26, 87, 95, -25, 86, 95, -25, 83, + 95, -25, 81, 95, -24, 78, 95, -24, 76, 95, -24, 73, + 95, -23, 69, 95, -23, 66, 95, -22, 63, 95, -21, 59, + 96, -20, 55, 96, -20, 52, 96, -19, 48, 96, -18, 44, + 96, -17, 40, 96, -16, 36, 96, -15, 32, 96, -14, 28, + 97, -13, 25, 97, -11, 20, 97, -10, 16, 97, -9, 13, + 97, -8, 9, 97, -6, 4, 98, -5, 1, 98, -3, -3, + 96, -28, 93, 96, -28, 93, 96, -28, 92, 96, -28, 91, + 96, -28, 90, 96, -28, 88, 96, -27, 87, 96, -27, 84, + 96, -27, 82, 96, -26, 79, 96, -26, 77, 96, -26, 74, + 96, -25, 70, 96, -24, 67, 96, -24, 64, 97, -23, 60, + 97, -22, 57, 97, -22, 53, 97, -21, 49, 97, -20, 46, + 97, -19, 41, 97, -18, 38, 97, -17, 34, 97, -16, 30, + 98, -15, 26, 98, -13, 22, 98, -12, 18, 98, -11, 14, + 98, -10, 10, 98, -8, 6, 99, -7, 2, 99, -5, -2, + 52, 78, 66, 52, 78, 64, 52, 78, 61, 52, 78, 58, + 52, 79, 55, 52, 79, 51, 52, 79, 47, 52, 79, 42, + 52, 79, 37, 52, 80, 33, 52, 80, 28, 52, 80, 23, + 53, 81, 18, 53, 81, 14, 53, 82, 9, 53, 82, 4, + 53, 83, -1, 54, 84, -5, 54, 84, -9, 54, 85, -14, + 55, 86, -19, 55, 87, -23, 55, 88, -27, 56, 89, -31, + 56, 89, -35, 56, 91, -40, 57, 92, -44, 57, 93, -48, + 58, 94, -51, 58, 95, -56, 58, 96, -59, 59, 97, -63, + 52, 78, 66, 52, 78, 64, 52, 78, 61, 52, 78, 58, + 52, 78, 55, 52, 78, 51, 52, 79, 47, 52, 79, 42, + 52, 79, 37, 52, 79, 33, 52, 80, 28, 53, 80, 24, + 53, 81, 18, 53, 81, 14, 53, 82, 9, 53, 82, 5, + 54, 83, -1, 54, 84, -5, 54, 84, -9, 54, 85, -14, + 55, 86, -18, 55, 87, -23, 55, 87, -27, 56, 88, -31, + 56, 89, -35, 56, 90, -40, 57, 91, -44, 57, 92, -47, + 58, 93, -51, 58, 95, -56, 59, 96, -59, 59, 97, -63, + 52, 78, 66, 52, 78, 64, 52, 78, 62, 52, 78, 58, + 52, 78, 55, 52, 78, 51, 52, 78, 47, 52, 79, 42, + 52, 79, 37, 52, 79, 33, 52, 80, 28, 53, 80, 24, + 53, 80, 18, 53, 81, 14, 53, 81, 9, 53, 82, 5, + 54, 83, 0, 54, 83, -5, 54, 84, -9, 54, 85, -14, + 55, 86, -18, 55, 86, -23, 55, 87, -27, 56, 88, -31, + 56, 89, -35, 56, 90, -39, 57, 91, -43, 57, 92, -47, + 58, 93, -51, 58, 94, -55, 59, 95, -59, 59, 97, -63, + 52, 77, 66, 52, 77, 64, 52, 77, 62, 52, 78, 58, + 52, 78, 55, 52, 78, 51, 52, 78, 47, 52, 78, 42, + 52, 79, 38, 52, 79, 33, 53, 79, 28, 53, 80, 24, + 53, 80, 19, 53, 81, 14, 53, 81, 9, 53, 82, 5, + 54, 82, 0, 54, 83, -5, 54, 84, -9, 54, 84, -13, + 55, 85, -18, 55, 86, -22, 55, 87, -27, 56, 88, -31, + 56, 89, -35, 57, 90, -39, 57, 91, -43, 57, 92, -47, + 58, 93, -51, 58, 94, -55, 59, 95, -59, 59, 96, -63, + 52, 77, 66, 52, 77, 64, 52, 77, 62, 52, 77, 58, + 52, 77, 55, 52, 77, 51, 52, 78, 47, 52, 78, 42, + 52, 78, 38, 53, 79, 33, 53, 79, 29, 53, 79, 24, + 53, 80, 19, 53, 80, 14, 53, 81, 10, 54, 81, 5, + 54, 82, 0, 54, 83, -5, 54, 83, -9, 55, 84, -13, + 55, 85, -18, 55, 86, -22, 56, 87, -26, 56, 87, -31, + 56, 88, -35, 57, 89, -39, 57, 90, -43, 57, 91, -47, + 58, 93, -51, 58, 94, -55, 59, 95, -59, 59, 96, -63, + 52, 77, 66, 52, 77, 64, 52, 77, 62, 52, 77, 59, + 52, 77, 55, 52, 77, 51, 52, 77, 47, 52, 78, 42, + 53, 78, 38, 53, 78, 33, 53, 78, 29, 53, 79, 24, + 53, 79, 19, 53, 80, 14, 54, 80, 10, 54, 81, 5, + 54, 82, 0, 54, 82, -4, 54, 83, -9, 55, 84, -13, + 55, 85, -18, 55, 85, -22, 56, 86, -26, 56, 87, -30, + 56, 88, -34, 57, 89, -39, 57, 90, -43, 58, 91, -47, + 58, 92, -51, 59, 93, -55, 59, 95, -59, 59, 96, -62, + 52, 76, 66, 52, 76, 64, 52, 76, 62, 52, 76, 59, + 52, 76, 55, 52, 77, 52, 53, 77, 47, 53, 77, 43, + 53, 77, 38, 53, 78, 34, 53, 78, 29, 53, 78, 24, + 53, 79, 19, 53, 79, 15, 54, 80, 10, 54, 80, 5, + 54, 81, 0, 54, 82, -4, 55, 82, -8, 55, 83, -13, + 55, 84, -18, 56, 85, -22, 56, 86, -26, 56, 87, -30, + 57, 88, -34, 57, 89, -39, 57, 90, -43, 58, 91, -47, + 58, 92, -50, 59, 93, -55, 59, 94, -58, 60, 95, -62, + 53, 76, 66, 53, 76, 64, 53, 76, 62, 53, 76, 59, + 53, 76, 55, 53, 76, 52, 53, 76, 48, 53, 76, 43, + 53, 77, 38, 53, 77, 34, 53, 77, 29, 53, 78, 25, + 53, 78, 19, 54, 79, 15, 54, 79, 10, 54, 80, 6, + 54, 81, 1, 55, 81, -4, 55, 82, -8, 55, 83, -13, + 55, 84, -17, 56, 84, -22, 56, 85, -26, 56, 86, -30, + 57, 87, -34, 57, 88, -38, 58, 89, -42, 58, 90, -46, + 58, 91, -50, 59, 93, -54, 59, 94, -58, 60, 95, -62, + 53, 75, 66, 53, 75, 64, 53, 75, 62, 53, 75, 59, + 53, 75, 56, 53, 75, 52, 53, 76, 48, 53, 76, 43, + 53, 76, 39, 53, 76, 34, 53, 77, 29, 54, 77, 25, + 54, 78, 20, 54, 78, 15, 54, 79, 11, 54, 79, 6, + 55, 80, 1, 55, 81, -3, 55, 81, -8, 55, 82, -12, + 56, 83, -17, 56, 84, -21, 56, 85, -25, 57, 86, -30, + 57, 86, -34, 57, 88, -38, 58, 89, -42, 58, 90, -46, + 59, 91, -50, 59, 92, -54, 59, 93, -58, 60, 94, -62, + 53, 74, 66, 53, 74, 64, 53, 74, 62, 53, 74, 59, + 53, 74, 56, 53, 75, 52, 53, 75, 48, 53, 75, 43, + 53, 75, 39, 54, 76, 34, 54, 76, 30, 54, 76, 25, + 54, 77, 20, 54, 77, 15, 54, 78, 11, 55, 79, 6, + 55, 79, 1, 55, 80, -3, 55, 81, -8, 56, 81, -12, + 56, 82, -17, 56, 83, -21, 56, 84, -25, 57, 85, -29, + 57, 86, -33, 58, 87, -38, 58, 88, -42, 58, 89, -46, + 59, 90, -49, 59, 91, -54, 60, 92, -58, 60, 94, -61, + 53, 73, 66, 53, 73, 64, 53, 73, 62, 53, 74, 59, + 53, 74, 56, 53, 74, 52, 54, 74, 48, 54, 74, 44, + 54, 75, 39, 54, 75, 35, 54, 75, 30, 54, 76, 26, + 54, 76, 20, 54, 77, 16, 55, 77, 11, 55, 78, 7, + 55, 79, 2, 55, 79, -3, 56, 80, -7, 56, 81, -11, + 56, 82, -16, 56, 82, -20, 57, 83, -25, 57, 84, -29, + 57, 85, -33, 58, 86, -37, 58, 87, -41, 59, 88, -45, + 59, 89, -49, 59, 91, -53, 60, 92, -57, 60, 93, -61, + 54, 72, 66, 54, 72, 65, 54, 72, 63, 54, 72, 60, + 54, 73, 56, 54, 73, 53, 54, 73, 49, 54, 73, 44, + 54, 74, 40, 54, 74, 35, 54, 74, 31, 54, 75, 26, + 55, 75, 21, 55, 76, 16, 55, 76, 12, 55, 77, 7, + 55, 77, 2, 56, 78, -2, 56, 79, -7, 56, 80, -11, + 56, 81, -16, 57, 81, -20, 57, 82, -24, 57, 83, -28, + 58, 84, -32, 58, 85, -37, 59, 86, -41, 59, 87, -45, + 59, 88, -49, 60, 90, -53, 60, 91, -57, 61, 92, -60, + 54, 71, 67, 54, 71, 65, 54, 71, 63, 54, 72, 60, + 54, 72, 57, 54, 72, 53, 54, 72, 49, 54, 72, 44, + 54, 73, 40, 55, 73, 36, 55, 73, 31, 55, 74, 26, + 55, 74, 21, 55, 75, 17, 55, 75, 12, 55, 76, 8, + 56, 77, 3, 56, 77, -2, 56, 78, -6, 56, 79, -10, + 57, 80, -15, 57, 80, -19, 57, 81, -24, 58, 82, -28, + 58, 83, -32, 58, 84, -36, 59, 85, -40, 59, 86, -44, + 60, 88, -48, 60, 89, -52, 61, 90, -56, 61, 91, -60, + 54, 70, 67, 54, 70, 65, 54, 70, 63, 54, 70, 60, + 54, 71, 57, 55, 71, 53, 55, 71, 49, 55, 71, 45, + 55, 72, 40, 55, 72, 36, 55, 72, 31, 55, 73, 27, + 55, 73, 22, 55, 74, 17, 56, 74, 13, 56, 75, 8, + 56, 76, 3, 56, 76, -1, 57, 77, -6, 57, 78, -10, + 57, 79, -15, 57, 79, -19, 58, 80, -23, 58, 81, -27, + 58, 82, -31, 59, 83, -36, 59, 84, -40, 60, 86, -44, + 60, 87, -48, 60, 88, -52, 61, 89, -56, 61, 90, -59, + 55, 69, 67, 55, 69, 65, 55, 69, 63, 55, 69, 60, + 55, 69, 57, 55, 70, 54, 55, 70, 50, 55, 70, 45, + 55, 70, 41, 55, 71, 36, 55, 71, 32, 56, 72, 27, + 56, 72, 22, 56, 73, 18, 56, 73, 13, 56, 74, 9, + 56, 74, 4, 57, 75, -1, 57, 76, -5, 57, 77, -9, + 57, 78, -14, 58, 78, -18, 58, 79, -23, 58, 80, -27, + 59, 81, -31, 59, 82, -35, 59, 83, -39, 60, 85, -43, + 60, 86, -47, 61, 87, -51, 61, 88, -55, 62, 89, -59, + 55, 68, 67, 55, 68, 65, 55, 68, 63, 55, 68, 61, + 55, 68, 58, 55, 68, 54, 55, 69, 50, 55, 69, 46, + 56, 69, 41, 56, 70, 37, 56, 70, 32, 56, 70, 28, + 56, 71, 23, 56, 71, 18, 56, 72, 14, 57, 73, 9, + 57, 73, 4, 57, 74, 0, 57, 75, -4, 58, 76, -9, + 58, 76, -14, 58, 77, -18, 58, 78, -22, 59, 79, -26, + 59, 80, -30, 59, 81, -35, 60, 82, -39, 60, 83, -43, + 61, 85, -47, 61, 86, -51, 62, 87, -55, 62, 88, -58, + 56, 67, 67, 56, 67, 66, 56, 67, 64, 56, 67, 61, + 56, 67, 58, 56, 67, 54, 56, 67, 51, 56, 68, 46, + 56, 68, 42, 56, 68, 37, 56, 69, 33, 56, 69, 29, + 57, 70, 23, 57, 70, 19, 57, 71, 14, 57, 71, 10, + 57, 72, 5, 58, 73, 1, 58, 74, -4, 58, 74, -8, + 58, 75, -13, 59, 76, -17, 59, 77, -21, 59, 78, -25, + 60, 79, -30, 60, 80, -34, 60, 81, -38, 61, 82, -42, + 61, 83, -46, 61, 85, -50, 62, 86, -54, 62, 87, -58, + 56, 65, 67, 56, 65, 66, 56, 65, 64, 56, 66, 61, + 56, 66, 58, 56, 66, 55, 56, 66, 51, 56, 66, 47, + 57, 67, 42, 57, 67, 38, 57, 67, 34, 57, 68, 29, + 57, 68, 24, 57, 69, 20, 57, 69, 15, 58, 70, 11, + 58, 71, 6, 58, 72, 1, 58, 72, -3, 58, 73, -7, + 59, 74, -12, 59, 75, -17, 59, 76, -21, 60, 77, -25, + 60, 78, -29, 60, 79, -33, 61, 80, -37, 61, 81, -41, + 61, 82, -45, 62, 84, -50, 62, 85, -53, 63, 86, -57, + 57, 64, 68, 57, 64, 66, 57, 64, 64, 57, 64, 62, + 57, 64, 59, 57, 65, 55, 57, 65, 52, 57, 65, 47, + 57, 65, 43, 57, 66, 39, 57, 66, 34, 57, 66, 30, + 58, 67, 25, 58, 68, 20, 58, 68, 16, 58, 69, 11, + 58, 69, 6, 58, 70, 2, 59, 71, -2, 59, 72, -7, + 59, 73, -12, 59, 74, -16, 60, 75, -20, 60, 75, -24, + 60, 77, -28, 61, 78, -33, 61, 79, -37, 61, 80, -41, + 62, 81, -45, 62, 82, -49, 63, 84, -53, 63, 85, -56, + 57, 63, 68, 57, 63, 66, 57, 63, 65, 57, 63, 62, + 57, 63, 59, 57, 63, 56, 57, 63, 52, 57, 64, 48, + 58, 64, 44, 58, 64, 39, 58, 65, 35, 58, 65, 30, + 58, 66, 25, 58, 66, 21, 58, 67, 16, 59, 67, 12, + 59, 68, 7, 59, 69, 3, 59, 70, -2, 59, 70, -6, + 60, 71, -11, 60, 72, -15, 60, 73, -19, 61, 74, -23, + 61, 75, -27, 61, 76, -32, 62, 77, -36, 62, 79, -40, + 62, 80, -44, 63, 81, -48, 63, 82, -52, 64, 84, -56, + 58, 61, 68, 58, 61, 67, 58, 61, 65, 58, 61, 62, + 58, 61, 60, 58, 62, 56, 58, 62, 53, 58, 62, 48, + 58, 62, 44, 58, 63, 40, 58, 63, 36, 58, 64, 31, + 59, 64, 26, 59, 65, 22, 59, 65, 17, 59, 66, 13, + 59, 67, 8, 59, 67, 3, 60, 68, -1, 60, 69, -5, + 60, 70, -10, 60, 71, -14, 61, 72, -18, 61, 73, -23, + 61, 74, -27, 62, 75, -31, 62, 76, -35, 62, 77, -39, + 63, 78, -43, 63, 80, -47, 64, 81, -51, 64, 82, -55, + 58, 59, 69, 58, 59, 67, 58, 60, 65, 58, 60, 63, + 58, 60, 60, 58, 60, 57, 59, 60, 53, 59, 60, 49, + 59, 61, 45, 59, 61, 41, 59, 62, 36, 59, 62, 32, + 59, 63, 27, 59, 63, 22, 59, 64, 18, 60, 64, 14, + 60, 65, 9, 60, 66, 4, 60, 67, 0, 60, 67, -4, + 61, 68, -9, 61, 69, -13, 61, 70, -18, 62, 71, -22, + 62, 72, -26, 62, 73, -30, 63, 75, -34, 63, 76, -38, + 63, 77, -42, 64, 78, -47, 64, 80, -50, 65, 81, -54, + 59, 58, 69, 59, 58, 67, 59, 58, 66, 59, 58, 63, + 59, 58, 61, 59, 58, 57, 59, 59, 54, 59, 59, 49, + 59, 59, 45, 59, 60, 41, 59, 60, 37, 60, 60, 33, + 60, 61, 28, 60, 61, 23, 60, 62, 19, 60, 63, 14, + 60, 63, 9, 61, 64, 5, 61, 65, 1, 61, 66, -4, + 61, 67, -8, 62, 68, -13, 62, 69, -17, 62, 70, -21, + 62, 71, -25, 63, 72, -30, 63, 73, -34, 64, 74, -38, + 64, 75, -41, 64, 77, -46, 65, 78, -50, 65, 79, -53, + 60, 56, 69, 60, 56, 68, 60, 56, 66, 60, 56, 64, + 60, 56, 61, 60, 57, 58, 60, 57, 54, 60, 57, 50, + 60, 58, 46, 60, 58, 42, 60, 58, 38, 60, 59, 33, + 60, 59, 28, 60, 60, 24, 61, 60, 20, 61, 61, 15, + 61, 62, 10, 61, 63, 6, 61, 63, 2, 62, 64, -3, + 62, 65, -8, 62, 66, -12, 62, 67, -16, 63, 68, -20, + 63, 69, -24, 63, 70, -29, 64, 72, -33, 64, 73, -37, + 64, 74, -41, 65, 75, -45, 65, 77, -49, 66, 78, -52, + 60, 54, 69, 60, 54, 68, 60, 55, 67, 60, 55, 64, + 60, 55, 62, 60, 55, 58, 60, 55, 55, 60, 55, 51, + 61, 56, 47, 61, 56, 43, 61, 57, 38, 61, 57, 34, + 61, 58, 29, 61, 58, 25, 61, 59, 20, 61, 59, 16, + 62, 60, 11, 62, 61, 7, 62, 62, 2, 62, 63, -2, + 63, 64, -7, 63, 64, -11, 63, 65, -15, 63, 66, -19, + 64, 68, -23, 64, 69, -28, 64, 70, -32, 65, 71, -36, + 65, 72, -40, 65, 74, -44, 66, 75, -48, 66, 76, -52, + 61, 53, 70, 61, 53, 68, 61, 53, 67, 61, 53, 65, + 61, 53, 62, 61, 53, 59, 61, 53, 56, 61, 54, 51, + 61, 54, 48, 61, 54, 43, 61, 55, 39, 61, 55, 35, + 62, 56, 30, 62, 56, 26, 62, 57, 21, 62, 58, 17, + 62, 58, 12, 62, 59, 8, 63, 60, 3, 63, 61, -1, + 63, 62, -6, 63, 63, -10, 64, 64, -14, 64, 65, -18, + 64, 66, -22, 65, 67, -27, 65, 68, -31, 65, 69, -35, + 66, 71, -39, 66, 72, -43, 66, 73, -47, 67, 75, -51, + 62, 51, 70, 62, 51, 69, 62, 51, 67, 62, 51, 65, + 62, 51, 63, 62, 51, 60, 62, 52, 56, 62, 52, 52, + 62, 52, 48, 62, 53, 44, 62, 53, 40, 62, 53, 36, + 62, 54, 31, 62, 55, 27, 63, 55, 22, 63, 56, 18, + 63, 57, 13, 63, 57, 9, 63, 58, 4, 64, 59, 0, + 64, 60, -5, 64, 61, -9, 64, 62, -13, 65, 63, -17, + 65, 64, -21, 65, 65, -26, 66, 67, -30, 66, 68, -34, + 66, 69, -38, 67, 70, -42, 67, 72, -46, 67, 73, -50, + 62, 49, 71, 62, 49, 69, 62, 49, 68, 62, 49, 66, + 62, 49, 63, 62, 50, 60, 62, 50, 57, 63, 50, 53, + 63, 50, 49, 63, 51, 45, 63, 51, 41, 63, 52, 37, + 63, 52, 32, 63, 53, 28, 63, 53, 23, 63, 54, 19, + 64, 55, 14, 64, 56, 10, 64, 56, 5, 64, 57, 1, + 65, 58, -4, 65, 59, -8, 65, 60, -12, 65, 61, -16, + 66, 62, -20, 66, 64, -25, 66, 65, -29, 67, 66, -33, + 67, 67, -37, 67, 69, -41, 68, 70, -45, 68, 71, -49, + 63, 47, 71, 63, 47, 70, 63, 47, 68, 63, 47, 66, + 63, 47, 64, 63, 48, 61, 63, 48, 58, 63, 48, 54, + 63, 48, 50, 63, 49, 46, 64, 49, 42, 64, 50, 38, + 64, 50, 33, 64, 51, 28, 64, 51, 24, 64, 52, 20, + 64, 53, 15, 65, 54, 11, 65, 55, 6, 65, 55, 2, + 65, 56, -3, 65, 57, -7, 66, 58, -11, 66, 59, -15, + 66, 61, -19, 67, 62, -24, 67, 63, -28, 67, 64, -32, + 67, 66, -36, 68, 67, -40, 68, 68, -44, 69, 70, -48, + 64, 45, 71, 64, 45, 70, 64, 45, 69, 64, 45, 67, + 64, 46, 64, 64, 46, 62, 64, 46, 58, 64, 46, 54, + 64, 47, 51, 64, 47, 47, 64, 47, 43, 64, 48, 38, + 64, 48, 34, 65, 49, 29, 65, 50, 25, 65, 50, 21, + 65, 51, 16, 65, 52, 12, 65, 53, 7, 66, 54, 3, + 66, 55, -2, 66, 56, -6, 66, 57, -10, 67, 58, -14, + 67, 59, -18, 67, 60, -23, 68, 61, -27, 68, 62, -31, + 68, 64, -35, 69, 65, -39, 69, 67, -43, 69, 68, -47, + 65, 43, 72, 65, 43, 71, 65, 43, 69, 65, 43, 67, + 65, 44, 65, 65, 44, 62, 65, 44, 59, 65, 44, 55, + 65, 45, 51, 65, 45, 48, 65, 45, 44, 65, 46, 39, + 65, 46, 35, 65, 47, 30, 66, 48, 26, 66, 48, 22, + 66, 49, 17, 66, 50, 13, 66, 51, 8, 66, 52, 4, + 67, 53, -1, 67, 54, -5, 67, 55, -9, 67, 56, -13, + 68, 57, -17, 68, 58, -22, 68, 59, -26, 69, 61, -30, + 69, 62, -34, 69, 63, -38, 70, 65, -42, 70, 66, -46, + 65, 41, 72, 65, 41, 71, 65, 41, 70, 65, 41, 68, + 65, 42, 66, 65, 42, 63, 65, 42, 60, 66, 42, 56, + 66, 43, 52, 66, 43, 48, 66, 43, 44, 66, 44, 40, + 66, 45, 36, 66, 45, 31, 66, 46, 27, 66, 46, 23, + 67, 47, 18, 67, 48, 14, 67, 49, 10, 67, 50, 5, + 67, 51, 1, 68, 52, -4, 68, 53, -8, 68, 54, -12, + 68, 55, -16, 69, 56, -21, 69, 57, -25, 69, 59, -29, + 70, 60, -33, 70, 61, -37, 70, 63, -41, 71, 64, -45, + 66, 39, 73, 66, 39, 72, 66, 39, 71, 66, 39, 69, + 66, 39, 66, 66, 39, 64, 66, 40, 61, 67, 40, 57, + 67, 40, 53, 67, 41, 50, 67, 41, 46, 67, 41, 42, + 67, 42, 37, 67, 43, 33, 67, 43, 28, 67, 44, 24, + 68, 45, 19, 68, 46, 15, 68, 46, 11, 68, 47, 7, + 68, 48, 2, 69, 49, -2, 69, 50, -6, 69, 51, -11, + 69, 53, -15, 70, 54, -19, 70, 55, -23, 70, 56, -27, + 70, 58, -31, 71, 59, -35, 71, 60, -39, 72, 62, -43, + 67, 37, 74, 67, 37, 72, 67, 37, 71, 67, 37, 69, + 67, 37, 67, 67, 37, 64, 67, 38, 61, 67, 38, 58, + 67, 38, 54, 68, 39, 50, 68, 39, 47, 68, 39, 43, + 68, 40, 38, 68, 41, 34, 68, 41, 30, 68, 42, 25, + 68, 43, 21, 69, 44, 16, 69, 44, 12, 69, 45, 8, + 69, 46, 3, 69, 47, -1, 70, 48, -5, 70, 49, -9, + 70, 51, -13, 70, 52, -18, 71, 53, -22, 71, 54, -26, + 71, 56, -30, 72, 57, -34, 72, 59, -38, 72, 60, -42, + 68, 35, 74, 68, 35, 73, 68, 35, 72, 68, 35, 70, + 68, 35, 68, 68, 35, 65, 68, 35, 62, 68, 36, 59, + 68, 36, 55, 68, 36, 51, 68, 37, 47, 69, 37, 44, + 69, 38, 39, 69, 39, 35, 69, 39, 31, 69, 40, 26, + 69, 41, 22, 69, 41, 17, 70, 42, 13, 70, 43, 9, + 70, 44, 4, 70, 45, 0, 70, 46, -4, 71, 47, -8, + 71, 49, -12, 71, 50, -17, 71, 51, -21, 72, 52, -25, + 72, 54, -29, 72, 55, -33, 73, 57, -37, 73, 58, -41, + 69, 33, 75, 69, 33, 73, 69, 33, 72, 69, 33, 70, + 69, 33, 68, 69, 33, 66, 69, 33, 63, 69, 34, 59, + 69, 34, 56, 69, 34, 52, 69, 35, 48, 69, 35, 44, + 69, 36, 40, 70, 37, 36, 70, 37, 32, 70, 38, 27, + 70, 39, 23, 70, 39, 19, 70, 40, 14, 71, 41, 10, + 71, 42, 5, 71, 43, 1, 71, 44, -3, 71, 45, -7, + 72, 47, -11, 72, 48, -16, 72, 49, -20, 72, 50, -24, + 73, 52, -28, 73, 53, -32, 73, 55, -36, 74, 56, -40, + 70, 31, 75, 70, 31, 74, 70, 31, 73, 70, 31, 71, + 70, 31, 69, 70, 31, 67, 70, 31, 64, 70, 32, 60, + 70, 32, 57, 70, 32, 53, 70, 33, 49, 70, 33, 46, + 70, 34, 41, 70, 34, 37, 71, 35, 33, 71, 36, 29, + 71, 37, 24, 71, 37, 20, 71, 38, 15, 71, 39, 11, + 72, 40, 7, 72, 41, 2, 72, 42, -2, 72, 43, -6, + 72, 44, -10, 73, 46, -14, 73, 47, -18, 73, 48, -22, + 74, 50, -26, 74, 51, -31, 74, 53, -35, 75, 54, -38, + 71, 28, 76, 71, 28, 75, 71, 29, 74, 71, 29, 72, + 71, 29, 70, 71, 29, 67, 71, 29, 65, 71, 30, 61, + 71, 30, 58, 71, 30, 54, 71, 31, 50, 71, 31, 47, + 71, 32, 42, 71, 32, 38, 71, 33, 34, 72, 34, 30, + 72, 35, 25, 72, 35, 21, 72, 36, 17, 72, 37, 13, + 72, 38, 8, 73, 39, 4, 73, 40, 0, 73, 41, -5, + 73, 42, -9, 74, 44, -13, 74, 45, -17, 74, 46, -21, + 74, 48, -25, 75, 49, -30, 75, 51, -33, 75, 52, -37, + 71, 26, 76, 71, 26, 75, 72, 26, 74, 72, 27, 72, + 72, 27, 70, 72, 27, 68, 72, 27, 65, 72, 27, 62, + 72, 28, 59, 72, 28, 55, 72, 29, 51, 72, 29, 48, + 72, 30, 43, 72, 30, 39, 72, 31, 35, 72, 32, 31, + 73, 32, 26, 73, 33, 22, 73, 34, 18, 73, 35, 14, + 73, 36, 9, 73, 37, 5, 74, 38, 1, 74, 39, -3, + 74, 40, -7, 74, 42, -12, 75, 43, -16, 75, 44, -20, + 75, 46, -24, 76, 47, -28, 76, 49, -32, 76, 50, -36, + 72, 24, 77, 72, 24, 76, 72, 24, 75, 72, 25, 73, + 72, 25, 71, 72, 25, 69, 73, 25, 66, 73, 25, 63, + 73, 26, 60, 73, 26, 56, 73, 27, 52, 73, 27, 49, + 73, 28, 44, 73, 28, 40, 73, 29, 36, 73, 30, 32, + 73, 30, 27, 74, 31, 23, 74, 32, 19, 74, 33, 15, + 74, 34, 10, 74, 35, 6, 75, 36, 2, 75, 37, -2, + 75, 38, -6, 75, 40, -11, 76, 41, -15, 76, 42, -19, + 76, 44, -23, 76, 45, -27, 77, 46, -31, 77, 48, -35, + 73, 22, 77, 73, 22, 76, 73, 22, 75, 73, 22, 74, + 73, 23, 72, 73, 23, 70, 73, 23, 67, 73, 23, 64, + 74, 24, 60, 74, 24, 57, 74, 24, 53, 74, 25, 50, + 74, 26, 45, 74, 26, 41, 74, 27, 37, 74, 27, 33, + 74, 28, 29, 75, 29, 24, 75, 30, 20, 75, 31, 16, + 75, 32, 12, 75, 33, 7, 75, 34, 3, 76, 35, -1, + 76, 36, -5, 76, 38, -9, 76, 39, -13, 77, 40, -17, + 77, 41, -21, 77, 43, -26, 77, 44, -30, 78, 46, -33, + 74, 20, 78, 74, 20, 77, 74, 20, 76, 74, 20, 75, + 74, 20, 73, 74, 21, 70, 74, 21, 68, 74, 21, 65, + 74, 22, 61, 75, 22, 58, 75, 22, 54, 75, 23, 51, + 75, 23, 46, 75, 24, 42, 75, 25, 38, 75, 25, 34, + 75, 26, 30, 75, 27, 26, 76, 28, 22, 76, 29, 17, + 76, 30, 13, 76, 31, 9, 76, 32, 5, 77, 33, 0, + 77, 34, -4, 77, 35, -8, 77, 37, -12, 77, 38, -16, + 78, 39, -20, 78, 41, -24, 78, 42, -28, 79, 44, -32, + 75, 18, 79, 75, 18, 78, 75, 18, 77, 75, 18, 75, + 75, 18, 73, 75, 19, 71, 75, 19, 69, 75, 19, 65, + 75, 19, 62, 75, 20, 59, 76, 20, 55, 76, 21, 52, + 76, 21, 47, 76, 22, 44, 76, 23, 40, 76, 23, 36, + 76, 24, 31, 76, 25, 27, 76, 26, 23, 77, 27, 19, + 77, 28, 14, 77, 29, 10, 77, 30, 6, 77, 31, 2, + 78, 32, -2, 78, 33, -7, 78, 35, -11, 78, 36, -15, + 79, 37, -19, 79, 39, -23, 79, 40, -27, 79, 42, -31, + 76, 16, 79, 76, 16, 78, 76, 16, 77, 76, 16, 76, + 76, 16, 74, 76, 16, 72, 76, 17, 70, 76, 17, 66, + 76, 17, 63, 76, 18, 60, 76, 18, 56, 77, 19, 53, + 77, 19, 49, 77, 20, 45, 77, 20, 41, 77, 21, 37, + 77, 22, 32, 77, 23, 28, 77, 24, 24, 78, 24, 20, + 78, 26, 15, 78, 27, 11, 78, 28, 7, 78, 29, 3, + 79, 30, -1, 79, 31, -5, 79, 33, -9, 79, 34, -13, + 79, 35, -17, 80, 37, -22, 80, 38, -26, 80, 40, -30, + 77, 14, 80, 77, 14, 79, 77, 14, 78, 77, 14, 77, + 77, 14, 75, 77, 14, 73, 77, 15, 70, 77, 15, 67, + 77, 15, 64, 77, 16, 61, 77, 16, 57, 77, 16, 54, + 78, 17, 50, 78, 18, 46, 78, 18, 42, 78, 19, 38, + 78, 20, 33, 78, 21, 29, 78, 21, 25, 78, 22, 21, + 79, 23, 17, 79, 24, 13, 79, 26, 8, 79, 27, 4, + 79, 28, 0, 80, 29, -4, 80, 30, -8, 80, 32, -12, + 80, 33, -16, 81, 35, -20, 81, 36, -24, 81, 38, -28, + 78, 12, 81, 78, 12, 80, 78, 12, 79, 78, 12, 77, + 78, 12, 76, 78, 12, 74, 78, 12, 71, 78, 13, 68, + 78, 13, 65, 78, 13, 62, 78, 14, 59, 78, 14, 55, + 79, 15, 51, 79, 16, 47, 79, 16, 43, 79, 17, 39, + 79, 18, 35, 79, 19, 31, 79, 19, 27, 79, 20, 22, + 80, 21, 18, 80, 22, 14, 80, 23, 10, 80, 25, 6, + 80, 26, 2, 81, 27, -3, 81, 28, -7, 81, 30, -11, + 81, 31, -15, 82, 33, -19, 82, 34, -23, 82, 35, -27, + 79, 10, 81, 79, 10, 80, 79, 10, 80, 79, 10, 78, + 79, 10, 76, 79, 10, 74, 79, 10, 72, 79, 11, 69, + 79, 11, 66, 79, 11, 63, 79, 12, 60, 79, 12, 56, + 79, 13, 52, 80, 13, 48, 80, 14, 44, 80, 15, 40, + 80, 16, 36, 80, 16, 32, 80, 17, 28, 80, 18, 24, + 81, 19, 19, 81, 20, 15, 81, 21, 11, 81, 22, 7, + 81, 24, 3, 81, 25, -1, 82, 26, -5, 82, 27, -9, + 82, 29, -13, 82, 30, -18, 83, 32, -22, 83, 33, -25, + 80, 7, 82, 80, 8, 81, 80, 8, 80, 80, 8, 79, + 80, 8, 77, 80, 8, 75, 80, 8, 73, 80, 9, 70, + 80, 9, 67, 80, 9, 64, 80, 10, 61, 80, 10, 57, + 80, 11, 53, 81, 11, 49, 81, 12, 45, 81, 13, 42, + 81, 14, 37, 81, 14, 33, 81, 15, 29, 81, 16, 25, + 81, 17, 21, 82, 18, 16, 82, 19, 12, 82, 20, 8, + 82, 21, 4, 82, 23, 0, 83, 24, -4, 83, 25, -8, + 83, 27, -12, 83, 28, -16, 84, 30, -20, 84, 31, -24, + 81, 5, 83, 81, 5, 82, 81, 6, 81, 81, 6, 80, + 81, 6, 78, 81, 6, 76, 81, 6, 74, 81, 7, 71, + 81, 7, 68, 81, 7, 65, 81, 8, 62, 81, 8, 58, + 81, 9, 54, 82, 9, 50, 82, 10, 47, 82, 11, 43, + 82, 11, 38, 82, 12, 34, 82, 13, 30, 82, 14, 26, + 82, 15, 22, 83, 16, 18, 83, 17, 14, 83, 18, 10, + 83, 19, 6, 83, 21, 1, 84, 22, -3, 84, 23, -7, + 84, 25, -11, 84, 26, -15, 85, 28, -19, 85, 29, -23, + 82, 3, 83, 82, 3, 83, 82, 3, 82, 82, 4, 80, + 82, 4, 79, 82, 4, 77, 82, 4, 75, 82, 4, 72, + 82, 5, 69, 82, 5, 66, 82, 6, 63, 82, 6, 59, + 82, 7, 55, 82, 7, 52, 83, 8, 48, 83, 9, 44, + 83, 9, 40, 83, 10, 36, 83, 11, 32, 83, 12, 28, + 83, 13, 23, 84, 14, 19, 84, 15, 15, 84, 16, 11, + 84, 17, 7, 84, 19, 3, 84, 20, -1, 85, 21, -5, + 85, 22, -9, 85, 24, -14, 85, 25, -17, 86, 27, -21, + 83, 1, 84, 83, 1, 83, 83, 1, 82, 83, 2, 81, + 83, 2, 80, 83, 2, 78, 83, 2, 76, 83, 2, 73, + 83, 3, 70, 83, 3, 67, 83, 4, 64, 83, 4, 60, + 83, 5, 56, 83, 5, 53, 84, 6, 49, 84, 6, 45, + 84, 7, 41, 84, 8, 37, 84, 9, 33, 84, 10, 29, + 84, 11, 24, 85, 12, 20, 85, 13, 16, 85, 14, 12, + 85, 15, 8, 85, 16, 4, 85, 18, 0, 86, 19, -4, + 86, 20, -8, 86, 22, -12, 86, 23, -16, 87, 25, -20, + 84, -1, 85, 84, -1, 84, 84, -1, 83, 84, -1, 82, + 84, 0, 80, 84, 0, 79, 84, 0, 76, 84, 0, 74, + 84, 1, 71, 84, 1, 68, 84, 1, 65, 84, 2, 61, + 84, 3, 58, 84, 3, 54, 85, 4, 50, 85, 4, 46, + 85, 5, 42, 85, 6, 38, 85, 7, 34, 85, 8, 30, + 85, 9, 26, 85, 10, 22, 86, 11, 18, 86, 12, 14, + 86, 13, 10, 86, 14, 5, 86, 16, 1, 87, 17, -3, + 87, 18, -6, 87, 20, -11, 87, 21, -15, 88, 23, -19, + 85, -3, 85, 85, -3, 85, 85, -3, 84, 85, -3, 83, + 85, -2, 81, 85, -2, 79, 85, -2, 77, 85, -2, 75, + 85, -1, 72, 85, -1, 69, 85, -1, 66, 85, 0, 63, + 85, 0, 59, 85, 1, 55, 86, 2, 51, 86, 2, 48, + 86, 3, 43, 86, 4, 39, 86, 5, 36, 86, 6, 32, + 86, 7, 27, 86, 8, 23, 87, 9, 19, 87, 10, 15, + 87, 11, 11, 87, 12, 7, 87, 14, 3, 88, 15, -1, + 88, 16, -5, 88, 18, -9, 88, 19, -13, 88, 21, -17, + 86, -5, 86, 86, -5, 86, 86, -5, 85, 86, -5, 84, + 86, -5, 82, 86, -5, 80, 86, -5, 78, 86, -4, 76, + 86, -4, 73, 86, -4, 70, 86, -3, 67, 87, -3, 64, + 87, -2, 60, 87, -2, 57, 87, -1, 53, 87, 0, 49, + 87, 1, 45, 87, 1, 41, 87, 2, 37, 87, 3, 33, + 88, 4, 29, 88, 5, 25, 88, 6, 21, 88, 7, 17, + 88, 8, 13, 88, 10, 9, 89, 11, 5, 89, 12, 1, + 89, 14, -3, 89, 15, -8, 89, 17, -12, 90, 18, -15, + 87, -7, 87, 87, -7, 86, 87, -7, 86, 87, -7, 84, + 87, -7, 83, 87, -7, 81, 87, -7, 79, 87, -6, 77, + 87, -6, 74, 87, -6, 71, 88, -5, 68, 88, -5, 65, + 88, -4, 61, 88, -4, 58, 88, -3, 54, 88, -2, 50, + 88, -1, 46, 88, -1, 42, 88, 0, 38, 88, 1, 35, + 89, 2, 30, 89, 3, 26, 89, 4, 22, 89, 5, 18, + 89, 6, 14, 89, 8, 10, 90, 9, 6, 90, 10, 2, + 90, 11, -2, 90, 13, -6, 90, 14, -10, 91, 16, -14, + 88, -9, 88, 88, -9, 87, 88, -9, 86, 88, -9, 85, + 88, -9, 84, 88, -9, 82, 88, -9, 80, 88, -8, 78, + 88, -8, 75, 88, -8, 72, 89, -7, 69, 89, -7, 66, + 89, -6, 62, 89, -6, 59, 89, -5, 55, 89, -4, 52, + 89, -4, 47, 89, -3, 44, 89, -2, 40, 89, -1, 36, + 90, 0, 31, 90, 1, 28, 90, 2, 24, 90, 3, 20, + 90, 4, 16, 90, 6, 11, 91, 7, 7, 91, 8, 3, + 91, 9, 0, 91, 11, -5, 91, 12, -9, 92, 14, -13, + 89, -11, 88, 89, -11, 88, 89, -11, 87, 89, -11, 86, + 89, -11, 85, 89, -11, 83, 89, -11, 81, 89, -10, 78, + 89, -10, 76, 89, -10, 73, 90, -9, 70, 90, -9, 67, + 90, -8, 63, 90, -8, 60, 90, -7, 56, 90, -6, 53, + 90, -6, 49, 90, -5, 45, 90, -4, 41, 90, -3, 37, + 91, -2, 33, 91, -1, 29, 91, 0, 25, 91, 1, 21, + 91, 2, 17, 91, 3, 13, 91, 5, 9, 92, 6, 5, + 92, 7, 1, 92, 9, -3, 92, 10, -7, 93, 12, -11, + 90, -13, 89, 90, -13, 89, 90, -13, 88, 90, -13, 87, + 90, -13, 85, 90, -13, 84, 90, -13, 82, 90, -12, 79, + 90, -12, 77, 91, -12, 74, 91, -11, 71, 91, -11, 68, + 91, -10, 65, 91, -10, 61, 91, -9, 58, 91, -8, 54, + 91, -8, 50, 91, -7, 46, 91, -6, 42, 91, -5, 38, + 92, -4, 34, 92, -3, 30, 92, -2, 26, 92, -1, 22, + 92, 0, 19, 92, 1, 14, 92, 3, 10, 93, 4, 6, + 93, 5, 2, 93, 7, -2, 93, 8, -6, 94, 10, -10, + 91, -15, 90, 91, -15, 89, 91, -15, 89, 91, -15, 88, + 91, -15, 86, 91, -15, 85, 91, -14, 83, 91, -14, 80, + 91, -14, 78, 92, -14, 75, 92, -13, 72, 92, -13, 69, + 92, -12, 66, 92, -12, 62, 92, -11, 59, 92, -10, 55, + 92, -10, 51, 92, -9, 47, 92, -8, 44, 92, -7, 40, + 93, -6, 35, 93, -5, 32, 93, -4, 28, 93, -3, 24, + 93, -2, 20, 93, -1, 16, 93, 1, 12, 94, 2, 8, + 94, 3, 4, 94, 5, 0, 94, 6, -4, 94, 8, -8, + 92, -17, 91, 92, -17, 90, 92, -17, 89, 92, -17, 88, + 92, -17, 87, 92, -17, 86, 92, -16, 84, 92, -16, 81, + 93, -16, 79, 93, -15, 76, 93, -15, 73, 93, -15, 70, + 93, -14, 67, 93, -14, 63, 93, -13, 60, 93, -12, 56, + 93, -12, 52, 93, -11, 49, 93, -10, 45, 93, -9, 41, + 94, -8, 37, 94, -7, 33, 94, -6, 29, 94, -5, 25, + 94, -4, 21, 94, -3, 17, 94, -1, 13, 95, 0, 9, + 95, 1, 5, 95, 3, 1, 95, 4, -3, 95, 6, -7, + 93, -19, 91, 93, -19, 91, 93, -19, 90, 93, -19, 89, + 93, -19, 88, 93, -19, 86, 93, -18, 85, 94, -18, 82, + 94, -18, 80, 94, -17, 77, 94, -17, 74, 94, -17, 71, + 94, -16, 68, 94, -15, 65, 94, -15, 61, 94, -14, 58, + 94, -13, 54, 94, -13, 50, 94, -12, 46, 94, -11, 42, + 95, -10, 38, 95, -9, 34, 95, -8, 30, 95, -7, 27, + 95, -6, 23, 95, -5, 18, 95, -3, 14, 96, -2, 11, + 96, -1, 7, 96, 1, 2, 96, 2, -1, 96, 3, -5, + 94, -21, 92, 94, -21, 92, 94, -21, 91, 94, -21, 90, + 94, -21, 89, 95, -20, 87, 95, -20, 85, 95, -20, 83, + 95, -20, 81, 95, -19, 78, 95, -19, 75, 95, -19, 73, + 95, -18, 69, 95, -17, 66, 95, -17, 62, 95, -16, 59, + 95, -15, 55, 95, -15, 51, 95, -14, 48, 95, -13, 44, + 96, -12, 39, 96, -11, 36, 96, -10, 32, 96, -9, 28, + 96, -8, 24, 96, -7, 20, 96, -5, 16, 97, -4, 12, + 97, -3, 8, 97, -1, 4, 97, 0, 0, 97, 1, -4, + 95, -23, 93, 95, -23, 92, 95, -23, 92, 96, -23, 91, + 96, -23, 90, 96, -22, 88, 96, -22, 86, 96, -22, 84, + 96, -22, 82, 96, -21, 79, 96, -21, 77, 96, -20, 74, + 96, -20, 70, 96, -19, 67, 96, -19, 64, 96, -18, 60, + 96, -17, 56, 96, -17, 52, 96, -16, 49, 96, -15, 45, + 97, -14, 41, 97, -13, 37, 97, -12, 33, 97, -11, 29, + 97, -10, 26, 97, -9, 21, 97, -7, 17, 98, -6, 13, + 98, -5, 10, 98, -3, 5, 98, -2, 1, 98, -1, -2, + 97, -25, 94, 97, -25, 93, 97, -25, 93, 97, -25, 92, + 97, -24, 90, 97, -24, 89, 97, -24, 87, 97, -24, 85, + 97, -23, 83, 97, -23, 80, 97, -23, 78, 97, -22, 75, + 97, -22, 71, 97, -21, 68, 97, -21, 65, 97, -20, 61, + 97, -19, 57, 97, -19, 54, 97, -18, 50, 98, -17, 46, + 98, -16, 42, 98, -15, 38, 98, -14, 35, 98, -13, 31, + 98, -12, 27, 98, -11, 23, 98, -9, 19, 99, -8, 15, + 99, -7, 11, 99, -5, 7, 99, -4, 3, 99, -3, -1, + 53, 80, 67, 53, 80, 65, 53, 80, 63, 53, 80, 60, + 53, 80, 57, 53, 81, 53, 53, 81, 49, 54, 81, 44, + 54, 81, 40, 54, 82, 35, 54, 82, 30, 54, 82, 26, + 54, 83, 21, 54, 83, 16, 55, 84, 11, 55, 84, 7, + 55, 85, 2, 55, 85, -3, 55, 86, -7, 56, 87, -11, + 56, 88, -16, 56, 88, -20, 57, 89, -25, 57, 90, -29, + 57, 91, -33, 58, 92, -37, 58, 93, -41, 59, 94, -45, + 59, 95, -49, 59, 96, -53, 60, 97, -57, 60, 98, -61, + 53, 80, 67, 53, 80, 65, 53, 80, 63, 53, 80, 60, + 53, 80, 57, 53, 80, 53, 54, 80, 49, 54, 81, 44, + 54, 81, 40, 54, 81, 35, 54, 82, 31, 54, 82, 26, + 54, 82, 21, 54, 83, 16, 55, 83, 12, 55, 84, 7, + 55, 85, 2, 55, 85, -2, 56, 86, -7, 56, 87, -11, + 56, 87, -16, 56, 88, -20, 57, 89, -24, 57, 90, -29, + 57, 91, -33, 58, 92, -37, 58, 93, -41, 59, 94, -45, + 59, 95, -49, 60, 96, -53, 60, 97, -57, 60, 98, -61, + 53, 80, 67, 53, 80, 65, 53, 80, 63, 53, 80, 60, + 54, 80, 57, 54, 80, 53, 54, 80, 49, 54, 80, 44, + 54, 81, 40, 54, 81, 35, 54, 81, 31, 54, 82, 26, + 54, 82, 21, 55, 83, 16, 55, 83, 12, 55, 84, 7, + 55, 84, 2, 55, 85, -2, 56, 86, -7, 56, 86, -11, + 56, 87, -16, 57, 88, -20, 57, 89, -24, 57, 90, -28, + 58, 90, -33, 58, 91, -37, 58, 92, -41, 59, 93, -45, + 59, 94, -49, 60, 96, -53, 60, 97, -57, 60, 98, -61, + 54, 79, 67, 54, 79, 65, 54, 79, 63, 54, 79, 60, + 54, 80, 57, 54, 80, 53, 54, 80, 49, 54, 80, 44, + 54, 80, 40, 54, 81, 35, 54, 81, 31, 54, 81, 26, + 54, 82, 21, 55, 82, 16, 55, 83, 12, 55, 83, 7, + 55, 84, 2, 55, 85, -2, 56, 85, -7, 56, 86, -11, + 56, 87, -16, 57, 88, -20, 57, 88, -24, 57, 89, -28, + 58, 90, -32, 58, 91, -37, 58, 92, -41, 59, 93, -45, + 59, 94, -49, 60, 95, -53, 60, 96, -57, 61, 98, -60, + 54, 79, 67, 54, 79, 65, 54, 79, 63, 54, 79, 60, + 54, 79, 57, 54, 79, 53, 54, 80, 49, 54, 80, 44, + 54, 80, 40, 54, 80, 35, 54, 81, 31, 54, 81, 26, + 55, 82, 21, 55, 82, 17, 55, 83, 12, 55, 83, 7, + 55, 84, 2, 56, 84, -2, 56, 85, -6, 56, 86, -11, + 56, 87, -16, 57, 87, -20, 57, 88, -24, 57, 89, -28, + 58, 90, -32, 58, 91, -37, 58, 92, -41, 59, 93, -45, + 59, 94, -49, 60, 95, -53, 60, 96, -57, 61, 97, -60, + 54, 79, 67, 54, 79, 65, 54, 79, 63, 54, 79, 60, + 54, 79, 57, 54, 79, 53, 54, 79, 49, 54, 79, 45, + 54, 80, 40, 54, 80, 36, 54, 80, 31, 55, 81, 26, + 55, 81, 21, 55, 82, 17, 55, 82, 12, 55, 83, 8, + 56, 83, 3, 56, 84, -2, 56, 85, -6, 56, 85, -11, + 57, 86, -15, 57, 87, -20, 57, 88, -24, 57, 89, -28, + 58, 90, -32, 58, 91, -37, 59, 92, -41, 59, 93, -44, + 59, 94, -48, 60, 95, -53, 60, 96, -56, 61, 97, -60, + 54, 78, 67, 54, 78, 66, 54, 78, 64, 54, 78, 61, + 54, 78, 57, 54, 79, 54, 54, 79, 50, 54, 79, 45, + 54, 79, 40, 54, 80, 36, 55, 80, 31, 55, 80, 27, + 55, 81, 22, 55, 81, 17, 55, 82, 12, 55, 82, 8, + 56, 83, 3, 56, 84, -2, 56, 84, -6, 56, 85, -10, + 57, 86, -15, 57, 87, -19, 57, 87, -24, 58, 88, -28, + 58, 89, -32, 58, 90, -36, 59, 91, -40, 59, 92, -44, + 60, 93, -48, 60, 94, -52, 60, 95, -56, 61, 97, -60, + 54, 78, 68, 54, 78, 66, 54, 78, 64, 54, 78, 61, + 54, 78, 57, 54, 78, 54, 54, 78, 50, 54, 78, 45, + 55, 79, 41, 55, 79, 36, 55, 79, 32, 55, 80, 27, + 55, 80, 22, 55, 81, 17, 55, 81, 13, 56, 82, 8, + 56, 82, 3, 56, 83, -1, 56, 84, -6, 57, 84, -10, + 57, 85, -15, 57, 86, -19, 57, 87, -23, 58, 88, -27, + 58, 89, -32, 59, 90, -36, 59, 91, -40, 59, 92, -44, + 60, 93, -48, 60, 94, -52, 61, 95, -56, 61, 96, -60, + 54, 77, 68, 54, 77, 66, 54, 77, 64, 54, 77, 61, + 54, 77, 58, 55, 77, 54, 55, 78, 50, 55, 78, 45, + 55, 78, 41, 55, 78, 36, 55, 79, 32, 55, 79, 27, + 55, 80, 22, 55, 80, 17, 56, 81, 13, 56, 81, 8, + 56, 82, 3, 56, 82, -1, 57, 83, -5, 57, 84, -10, + 57, 85, -15, 57, 85, -19, 58, 86, -23, 58, 87, -27, + 58, 88, -31, 59, 89, -36, 59, 90, -40, 59, 91, -44, + 60, 92, -48, 60, 93, -52, 61, 94, -56, 61, 96, -59, + 55, 76, 68, 55, 76, 66, 55, 76, 64, 55, 76, 61, + 55, 77, 58, 55, 77, 54, 55, 77, 50, 55, 77, 45, + 55, 77, 41, 55, 78, 37, 55, 78, 32, 55, 78, 27, + 56, 79, 22, 56, 79, 18, 56, 80, 13, 56, 80, 9, + 56, 81, 4, 57, 82, -1, 57, 82, -5, 57, 83, -9, + 57, 84, -14, 58, 85, -19, 58, 86, -23, 58, 87, -27, + 59, 87, -31, 59, 88, -35, 59, 89, -39, 60, 90, -43, + 60, 92, -47, 61, 93, -52, 61, 94, -55, 61, 95, -59, + 55, 75, 68, 55, 76, 66, 55, 76, 64, 55, 76, 61, + 55, 76, 58, 55, 76, 54, 55, 76, 50, 55, 76, 46, + 55, 77, 41, 55, 77, 37, 55, 77, 32, 56, 78, 28, + 56, 78, 23, 56, 79, 18, 56, 79, 14, 56, 80, 9, + 57, 80, 4, 57, 81, 0, 57, 82, -5, 57, 82, -9, + 58, 83, -14, 58, 84, -18, 58, 85, -22, 58, 86, -26, + 59, 87, -31, 59, 88, -35, 60, 89, -39, 60, 90, -43, + 60, 91, -47, 61, 92, -51, 61, 93, -55, 62, 94, -59, + 55, 74, 68, 55, 74, 66, 55, 75, 64, 55, 75, 61, + 55, 75, 58, 55, 75, 55, 55, 75, 51, 56, 75, 46, + 56, 76, 42, 56, 76, 37, 56, 76, 33, 56, 77, 28, + 56, 77, 23, 56, 78, 19, 56, 78, 14, 57, 79, 10, + 57, 79, 5, 57, 80, 0, 57, 81, -4, 58, 81, -9, + 58, 82, -13, 58, 83, -18, 58, 84, -22, 59, 85, -26, + 59, 86, -30, 60, 87, -35, 60, 88, -39, 60, 89, -42, + 61, 90, -46, 61, 91, -51, 62, 92, -54, 62, 93, -58, + 56, 73, 68, 56, 74, 66, 56, 74, 64, 56, 74, 62, + 56, 74, 59, 56, 74, 55, 56, 74, 51, 56, 74, 46, + 56, 75, 42, 56, 75, 38, 56, 75, 33, 56, 76, 29, + 56, 76, 24, 57, 77, 19, 57, 77, 15, 57, 78, 10, + 57, 78, 5, 57, 79, 1, 58, 80, -4, 58, 81, -8, + 58, 81, -13, 58, 82, -17, 59, 83, -21, 59, 84, -26, + 59, 85, -30, 60, 86, -34, 60, 87, -38, 61, 88, -42, + 61, 89, -46, 61, 90, -50, 62, 91, -54, 62, 93, -58, + 56, 72, 68, 56, 73, 67, 56, 73, 65, 56, 73, 62, + 56, 73, 59, 56, 73, 55, 56, 73, 51, 56, 73, 47, + 56, 74, 43, 56, 74, 38, 57, 74, 34, 57, 75, 29, + 57, 75, 24, 57, 76, 20, 57, 76, 15, 57, 77, 11, + 58, 78, 5, 58, 78, 1, 58, 79, -3, 58, 80, -8, + 59, 80, -12, 59, 81, -17, 59, 82, -21, 59, 83, -25, + 60, 84, -29, 60, 85, -34, 60, 86, -38, 61, 87, -42, + 61, 88, -45, 62, 89, -50, 62, 91, -54, 63, 92, -57, + 56, 71, 68, 56, 71, 67, 56, 72, 65, 56, 72, 62, + 56, 72, 59, 56, 72, 56, 56, 72, 52, 57, 72, 47, + 57, 73, 43, 57, 73, 39, 57, 73, 34, 57, 74, 30, + 57, 74, 25, 57, 75, 20, 57, 75, 16, 58, 76, 11, + 58, 76, 6, 58, 77, 2, 58, 78, -3, 59, 79, -7, + 59, 79, -12, 59, 80, -16, 59, 81, -20, 60, 82, -24, + 60, 83, -29, 60, 84, -33, 61, 85, -37, 61, 86, -41, + 62, 87, -45, 62, 89, -49, 62, 90, -53, 63, 91, -57, + 57, 70, 69, 57, 70, 67, 57, 70, 65, 57, 71, 62, + 57, 71, 59, 57, 71, 56, 57, 71, 52, 57, 71, 48, + 57, 72, 43, 57, 72, 39, 57, 72, 35, 57, 73, 30, + 58, 73, 25, 58, 74, 21, 58, 74, 16, 58, 75, 12, + 58, 75, 7, 59, 76, 2, 59, 77, -2, 59, 78, -7, + 59, 78, -11, 60, 79, -16, 60, 80, -20, 60, 81, -24, + 60, 82, -28, 61, 83, -33, 61, 84, -37, 62, 85, -40, + 62, 86, -44, 62, 88, -49, 63, 89, -53, 63, 90, -56, + 57, 69, 69, 57, 69, 67, 57, 69, 65, 57, 69, 63, + 57, 69, 60, 57, 70, 56, 57, 70, 53, 57, 70, 48, + 57, 70, 44, 58, 71, 40, 58, 71, 35, 58, 71, 31, + 58, 72, 26, 58, 72, 21, 58, 73, 17, 58, 74, 12, + 59, 74, 7, 59, 75, 3, 59, 76, -2, 59, 76, -6, + 60, 77, -11, 60, 78, -15, 60, 79, -19, 61, 80, -23, + 61, 81, -27, 61, 82, -32, 62, 83, -36, 62, 84, -40, + 62, 85, -44, 63, 86, -48, 63, 88, -52, 64, 89, -56, + 58, 68, 69, 58, 68, 67, 58, 68, 66, 58, 68, 63, + 58, 68, 60, 58, 68, 57, 58, 69, 53, 58, 69, 49, + 58, 69, 44, 58, 69, 40, 58, 70, 36, 58, 70, 31, + 58, 71, 26, 59, 71, 22, 59, 72, 17, 59, 72, 13, + 59, 73, 8, 59, 74, 3, 60, 74, -1, 60, 75, -5, + 60, 76, -10, 60, 77, -14, 61, 78, -19, 61, 79, -23, + 61, 80, -27, 62, 81, -31, 62, 82, -35, 62, 83, -39, + 63, 84, -43, 63, 85, -47, 64, 86, -51, 64, 88, -55, + 58, 66, 69, 58, 67, 68, 58, 67, 66, 58, 67, 63, + 58, 67, 60, 58, 67, 57, 58, 67, 53, 58, 67, 49, + 58, 68, 45, 59, 68, 41, 59, 68, 36, 59, 69, 32, + 59, 69, 27, 59, 70, 22, 59, 70, 18, 59, 71, 13, + 60, 72, 8, 60, 72, 4, 60, 73, 0, 60, 74, -5, + 61, 75, -9, 61, 76, -14, 61, 77, -18, 61, 77, -22, + 62, 78, -26, 62, 80, -31, 62, 81, -35, 63, 82, -39, + 63, 83, -42, 64, 84, -47, 64, 85, -51, 64, 86, -54, + 59, 65, 69, 59, 65, 68, 59, 65, 66, 59, 65, 64, + 59, 65, 61, 59, 66, 58, 59, 66, 54, 59, 66, 50, + 59, 66, 45, 59, 67, 41, 59, 67, 37, 59, 67, 32, + 59, 68, 27, 60, 68, 23, 60, 69, 19, 60, 70, 14, + 60, 70, 9, 60, 71, 5, 61, 72, 0, 61, 73, -4, + 61, 73, -9, 61, 74, -13, 62, 75, -17, 62, 76, -21, + 62, 77, -25, 63, 78, -30, 63, 79, -34, 63, 80, -38, + 64, 82, -42, 64, 83, -46, 64, 84, -50, 65, 85, -54, + 59, 64, 70, 59, 64, 68, 59, 64, 67, 59, 64, 64, + 59, 64, 61, 59, 64, 58, 59, 64, 54, 59, 65, 50, + 59, 65, 46, 60, 65, 42, 60, 66, 38, 60, 66, 33, + 60, 67, 28, 60, 67, 24, 60, 68, 19, 60, 68, 15, + 61, 69, 10, 61, 70, 6, 61, 70, 1, 61, 71, -3, + 62, 72, -8, 62, 73, -12, 62, 74, -16, 62, 75, -21, + 63, 76, -25, 63, 77, -29, 63, 78, -33, 64, 79, -37, + 64, 80, -41, 64, 82, -45, 65, 83, -49, 65, 84, -53, + 60, 62, 70, 60, 62, 68, 60, 62, 67, 60, 62, 64, + 60, 62, 62, 60, 63, 59, 60, 63, 55, 60, 63, 51, + 60, 63, 47, 60, 64, 42, 60, 64, 38, 60, 64, 34, + 60, 65, 29, 61, 66, 24, 61, 66, 20, 61, 67, 16, + 61, 67, 11, 61, 68, 6, 62, 69, 2, 62, 70, -2, + 62, 71, -7, 62, 71, -11, 63, 72, -16, 63, 73, -20, + 63, 74, -24, 64, 75, -28, 64, 77, -32, 64, 78, -36, + 65, 79, -40, 65, 80, -45, 65, 81, -48, 66, 83, -52, + 60, 60, 70, 60, 61, 69, 60, 61, 67, 60, 61, 65, + 60, 61, 62, 60, 61, 59, 60, 61, 56, 61, 62, 51, + 61, 62, 47, 61, 62, 43, 61, 63, 39, 61, 63, 35, + 61, 63, 30, 61, 64, 25, 61, 65, 21, 62, 65, 16, + 62, 66, 11, 62, 67, 7, 62, 67, 3, 62, 68, -2, + 63, 69, -6, 63, 70, -11, 63, 71, -15, 63, 72, -19, + 64, 73, -23, 64, 74, -28, 64, 75, -32, 65, 76, -36, + 65, 77, -39, 65, 79, -44, 66, 80, -48, 66, 81, -51, + 61, 59, 71, 61, 59, 69, 61, 59, 68, 61, 59, 65, + 61, 59, 63, 61, 59, 60, 61, 60, 56, 61, 60, 52, + 61, 60, 48, 61, 61, 44, 61, 61, 40, 62, 61, 35, + 62, 62, 30, 62, 62, 26, 62, 63, 22, 62, 64, 17, + 62, 64, 12, 62, 65, 8, 63, 66, 4, 63, 67, -1, + 63, 67, -6, 63, 68, -10, 64, 69, -14, 64, 70, -18, + 64, 71, -22, 65, 72, -27, 65, 74, -31, 65, 75, -35, + 66, 76, -39, 66, 77, -43, 66, 78, -47, 67, 80, -51, + 61, 57, 71, 62, 57, 70, 62, 57, 68, 62, 57, 66, + 62, 58, 63, 62, 58, 60, 62, 58, 57, 62, 58, 53, + 62, 59, 49, 62, 59, 45, 62, 59, 40, 62, 60, 36, + 62, 60, 31, 62, 61, 27, 63, 61, 22, 63, 62, 18, + 63, 63, 13, 63, 63, 9, 63, 64, 4, 64, 65, 0, + 64, 66, -5, 64, 67, -9, 64, 68, -13, 65, 69, -17, + 65, 70, -21, 65, 71, -26, 65, 72, -30, 66, 73, -34, + 66, 74, -38, 67, 76, -42, 67, 77, -46, 67, 78, -50, + 62, 55, 71, 62, 56, 70, 62, 56, 68, 62, 56, 66, + 62, 56, 64, 62, 56, 61, 62, 56, 57, 62, 56, 53, + 62, 57, 49, 63, 57, 45, 63, 58, 41, 63, 58, 37, + 63, 58, 32, 63, 59, 28, 63, 60, 23, 63, 60, 19, + 64, 61, 14, 64, 62, 10, 64, 62, 5, 64, 63, 1, + 64, 64, -4, 65, 65, -8, 65, 66, -12, 65, 67, -16, + 65, 68, -20, 66, 69, -25, 66, 70, -29, 66, 72, -33, + 67, 73, -37, 67, 74, -41, 67, 75, -45, 68, 77, -49, + 63, 54, 72, 63, 54, 70, 63, 54, 69, 63, 54, 67, + 63, 54, 64, 63, 54, 61, 63, 54, 58, 63, 55, 54, + 63, 55, 50, 63, 55, 46, 63, 56, 42, 63, 56, 38, + 64, 57, 33, 64, 57, 28, 64, 58, 24, 64, 58, 20, + 64, 59, 15, 64, 60, 11, 65, 61, 6, 65, 62, 2, + 65, 63, -3, 65, 63, -7, 66, 64, -11, 66, 65, -15, + 66, 66, -19, 66, 68, -24, 67, 69, -28, 67, 70, -32, + 67, 71, -36, 68, 72, -40, 68, 74, -44, 68, 75, -48, + 64, 52, 72, 64, 52, 71, 64, 52, 69, 64, 52, 67, + 64, 52, 65, 64, 52, 62, 64, 53, 59, 64, 53, 55, + 64, 53, 51, 64, 54, 47, 64, 54, 43, 64, 54, 38, + 64, 55, 34, 64, 55, 29, 65, 56, 25, 65, 57, 21, + 65, 57, 16, 65, 58, 12, 65, 59, 7, 65, 60, 3, + 66, 61, -2, 66, 62, -6, 66, 63, -10, 66, 64, -14, + 67, 65, -18, 67, 66, -23, 67, 67, -27, 68, 68, -31, + 68, 69, -35, 68, 71, -39, 69, 72, -43, 69, 73, -47, + 64, 50, 72, 64, 50, 71, 64, 50, 70, 64, 50, 68, + 64, 50, 65, 64, 51, 62, 64, 51, 59, 64, 51, 55, + 65, 51, 51, 65, 52, 48, 65, 52, 43, 65, 53, 39, + 65, 53, 35, 65, 54, 30, 65, 54, 26, 65, 55, 22, + 66, 56, 17, 66, 56, 12, 66, 57, 8, 66, 58, 4, + 66, 59, -1, 67, 60, -5, 67, 61, -9, 67, 62, -13, + 67, 63, -17, 68, 64, -22, 68, 65, -26, 68, 66, -30, + 69, 68, -34, 69, 69, -38, 69, 70, -42, 70, 72, -46, + 65, 48, 73, 65, 48, 72, 65, 48, 70, 65, 48, 68, + 65, 49, 66, 65, 49, 63, 65, 49, 60, 65, 49, 56, + 65, 50, 52, 65, 50, 48, 65, 50, 44, 66, 51, 40, + 66, 51, 35, 66, 52, 31, 66, 52, 27, 66, 53, 23, + 66, 54, 18, 66, 55, 13, 67, 55, 9, 67, 56, 5, + 67, 57, 0, 67, 58, -4, 68, 59, -8, 68, 60, -12, + 68, 61, -16, 68, 62, -21, 69, 63, -25, 69, 65, -29, + 69, 66, -33, 70, 67, -37, 70, 69, -41, 70, 70, -45, + 66, 46, 73, 66, 46, 72, 66, 46, 71, 66, 46, 69, + 66, 47, 66, 66, 47, 64, 66, 47, 61, 66, 47, 57, + 66, 48, 53, 66, 48, 49, 66, 48, 45, 66, 49, 41, + 66, 49, 36, 67, 50, 32, 67, 50, 28, 67, 51, 24, + 67, 52, 19, 67, 53, 14, 67, 53, 10, 68, 54, 6, + 68, 55, 1, 68, 56, -3, 68, 57, -7, 68, 58, -11, + 69, 59, -15, 69, 61, -20, 69, 62, -24, 70, 63, -28, + 70, 64, -32, 70, 66, -36, 71, 67, -40, 71, 68, -44, + 66, 44, 74, 66, 44, 73, 66, 44, 71, 67, 45, 69, + 67, 45, 67, 67, 45, 64, 67, 45, 61, 67, 45, 57, + 67, 46, 54, 67, 46, 50, 67, 46, 46, 67, 47, 42, + 67, 47, 37, 67, 48, 33, 67, 49, 29, 68, 49, 25, + 68, 50, 20, 68, 51, 16, 68, 52, 11, 68, 52, 7, + 68, 53, 2, 69, 54, -2, 69, 55, -6, 69, 56, -10, + 69, 57, -14, 70, 59, -19, 70, 60, -23, 70, 61, -27, + 71, 62, -31, 71, 64, -35, 71, 65, -39, 72, 66, -43, + 67, 42, 74, 67, 42, 73, 67, 42, 72, 67, 42, 70, + 68, 42, 68, 68, 42, 65, 68, 43, 62, 68, 43, 58, + 68, 43, 55, 68, 44, 51, 68, 44, 47, 68, 44, 43, + 68, 45, 39, 68, 46, 34, 68, 46, 30, 69, 47, 26, + 69, 48, 21, 69, 48, 17, 69, 49, 13, 69, 50, 8, + 69, 51, 4, 70, 52, -1, 70, 53, -5, 70, 54, -9, + 70, 55, -13, 71, 56, -17, 71, 57, -22, 71, 59, -25, + 72, 60, -29, 72, 61, -34, 72, 63, -38, 73, 64, -42, + 68, 40, 75, 68, 40, 74, 68, 40, 72, 68, 40, 71, + 68, 40, 68, 68, 40, 66, 68, 41, 63, 68, 41, 59, + 69, 41, 56, 69, 42, 52, 69, 42, 48, 69, 42, 44, + 69, 43, 39, 69, 44, 35, 69, 44, 31, 69, 45, 27, + 69, 46, 22, 70, 46, 18, 70, 47, 14, 70, 48, 9, + 70, 49, 5, 70, 50, 1, 71, 51, -4, 71, 52, -8, + 71, 53, -12, 71, 54, -16, 72, 56, -20, 72, 57, -24, + 72, 58, -28, 73, 59, -33, 73, 61, -37, 73, 62, -40, + 69, 38, 75, 69, 38, 74, 69, 38, 73, 69, 38, 71, + 69, 38, 69, 69, 38, 66, 69, 39, 64, 69, 39, 60, + 69, 39, 57, 69, 40, 53, 70, 40, 49, 70, 40, 45, + 70, 41, 40, 70, 42, 36, 70, 42, 32, 70, 43, 28, + 70, 44, 23, 70, 44, 19, 71, 45, 15, 71, 46, 11, + 71, 47, 6, 71, 48, 2, 71, 49, -2, 72, 50, -7, + 72, 51, -11, 72, 52, -15, 72, 54, -19, 73, 55, -23, + 73, 56, -27, 73, 58, -32, 74, 59, -35, 74, 60, -39, + 70, 36, 76, 70, 36, 75, 70, 36, 74, 70, 36, 72, + 70, 36, 70, 70, 36, 67, 70, 37, 64, 70, 37, 61, + 70, 37, 57, 70, 38, 54, 70, 38, 50, 70, 38, 46, + 71, 39, 41, 71, 40, 37, 71, 40, 33, 71, 41, 29, + 71, 42, 24, 71, 42, 20, 71, 43, 16, 72, 44, 12, + 72, 45, 7, 72, 46, 3, 72, 47, -1, 72, 48, -5, + 73, 49, -9, 73, 50, -14, 73, 52, -18, 73, 53, -22, + 74, 54, -26, 74, 56, -30, 74, 57, -34, 75, 58, -38, + 71, 34, 76, 71, 34, 75, 71, 34, 74, 71, 34, 72, + 71, 34, 70, 71, 34, 68, 71, 35, 65, 71, 35, 62, + 71, 35, 58, 71, 36, 55, 71, 36, 51, 71, 36, 47, + 71, 37, 42, 71, 38, 38, 72, 38, 34, 72, 39, 30, + 72, 40, 25, 72, 40, 21, 72, 41, 17, 72, 42, 13, + 73, 43, 8, 73, 44, 4, 73, 45, 0, 73, 46, -4, + 73, 47, -8, 74, 48, -13, 74, 50, -17, 74, 51, -21, + 75, 52, -25, 75, 54, -29, 75, 55, -33, 75, 56, -37, + 72, 32, 77, 72, 32, 76, 72, 32, 75, 72, 32, 73, + 72, 32, 71, 72, 32, 69, 72, 33, 66, 72, 33, 62, + 72, 33, 59, 72, 33, 56, 72, 34, 52, 72, 34, 48, + 72, 35, 44, 72, 35, 39, 72, 36, 35, 73, 37, 31, + 73, 38, 27, 73, 38, 22, 73, 39, 18, 73, 40, 14, + 73, 41, 9, 74, 42, 5, 74, 43, 1, 74, 44, -3, + 74, 45, -7, 75, 46, -12, 75, 48, -16, 75, 49, -20, + 75, 50, -24, 76, 52, -28, 76, 53, -32, 76, 54, -36, + 72, 30, 77, 72, 30, 76, 73, 30, 75, 73, 30, 74, + 73, 30, 72, 73, 30, 69, 73, 30, 67, 73, 31, 63, + 73, 31, 60, 73, 31, 56, 73, 32, 53, 73, 32, 49, + 73, 33, 45, 73, 33, 41, 73, 34, 36, 73, 35, 32, + 74, 35, 28, 74, 36, 24, 74, 37, 19, 74, 38, 15, + 74, 39, 11, 74, 40, 6, 75, 41, 2, 75, 42, -2, + 75, 43, -6, 75, 44, -10, 76, 46, -14, 76, 47, -18, + 76, 48, -22, 76, 50, -27, 77, 51, -31, 77, 52, -35, + 73, 28, 78, 73, 28, 77, 73, 28, 76, 73, 28, 74, + 73, 28, 72, 73, 28, 70, 74, 28, 67, 74, 29, 64, + 74, 29, 61, 74, 29, 57, 74, 30, 54, 74, 30, 50, + 74, 31, 46, 74, 31, 42, 74, 32, 38, 74, 33, 33, + 74, 33, 29, 75, 34, 25, 75, 35, 21, 75, 36, 16, + 75, 37, 12, 75, 38, 8, 75, 39, 4, 76, 40, -1, + 76, 41, -5, 76, 42, -9, 76, 44, -13, 77, 45, -17, + 77, 46, -21, 77, 48, -26, 78, 49, -29, 78, 50, -33, + 74, 25, 79, 74, 26, 78, 74, 26, 77, 74, 26, 75, + 74, 26, 73, 74, 26, 71, 74, 26, 68, 74, 27, 65, + 75, 27, 62, 75, 27, 58, 75, 28, 55, 75, 28, 51, + 75, 29, 47, 75, 29, 43, 75, 30, 39, 75, 31, 35, + 75, 31, 30, 75, 32, 26, 76, 33, 22, 76, 34, 18, + 76, 35, 13, 76, 36, 9, 76, 37, 5, 77, 38, 1, + 77, 39, -3, 77, 40, -8, 77, 41, -12, 78, 43, -16, + 78, 44, -20, 78, 45, -24, 78, 47, -28, 79, 48, -32, + 75, 23, 79, 75, 23, 78, 75, 24, 77, 75, 24, 76, + 75, 24, 74, 75, 24, 72, 75, 24, 69, 75, 25, 66, + 75, 25, 63, 75, 25, 59, 76, 26, 56, 76, 26, 52, + 76, 27, 48, 76, 27, 44, 76, 28, 40, 76, 28, 36, + 76, 29, 31, 76, 30, 27, 76, 31, 23, 77, 32, 19, + 77, 33, 14, 77, 34, 10, 77, 35, 6, 77, 36, 2, + 78, 37, -2, 78, 38, -7, 78, 39, -11, 78, 41, -15, + 79, 42, -19, 79, 43, -23, 79, 45, -27, 79, 46, -31, + 76, 21, 80, 76, 21, 79, 76, 21, 78, 76, 22, 76, + 76, 22, 75, 76, 22, 72, 76, 22, 70, 76, 22, 67, + 76, 23, 64, 76, 23, 60, 76, 24, 57, 77, 24, 53, + 77, 25, 49, 77, 25, 45, 77, 26, 41, 77, 26, 37, + 77, 27, 32, 77, 28, 28, 77, 29, 24, 78, 30, 20, + 78, 31, 15, 78, 32, 11, 78, 33, 7, 78, 34, 3, + 78, 35, -1, 79, 36, -5, 79, 37, -9, 79, 39, -13, + 79, 40, -17, 80, 41, -22, 80, 43, -26, 80, 44, -29, + 77, 19, 80, 77, 19, 79, 77, 19, 79, 77, 19, 77, + 77, 20, 75, 77, 20, 73, 77, 20, 71, 77, 20, 68, + 77, 21, 64, 77, 21, 61, 77, 21, 58, 77, 22, 54, + 78, 22, 50, 78, 23, 46, 78, 24, 42, 78, 24, 38, + 78, 25, 34, 78, 26, 29, 78, 27, 25, 78, 28, 21, + 79, 29, 17, 79, 30, 13, 79, 31, 9, 79, 32, 4, + 79, 33, 0, 80, 34, -4, 80, 35, -8, 80, 36, -12, + 80, 38, -16, 81, 39, -20, 81, 41, -24, 81, 42, -28, + 78, 17, 81, 78, 17, 80, 78, 17, 79, 78, 17, 78, + 78, 18, 76, 78, 18, 74, 78, 18, 71, 78, 18, 68, + 78, 19, 65, 78, 19, 62, 78, 19, 59, 78, 20, 55, + 78, 20, 51, 79, 21, 47, 79, 22, 43, 79, 22, 39, + 79, 23, 35, 79, 24, 31, 79, 25, 27, 79, 25, 23, + 80, 26, 18, 80, 27, 14, 80, 28, 10, 80, 30, 6, + 80, 31, 2, 80, 32, -3, 81, 33, -7, 81, 34, -11, + 81, 36, -15, 81, 37, -19, 82, 39, -23, 82, 40, -27, + 79, 15, 82, 79, 15, 81, 79, 15, 80, 79, 15, 78, + 79, 15, 77, 79, 16, 75, 79, 16, 72, 79, 16, 69, + 79, 16, 66, 79, 17, 63, 79, 17, 60, 79, 18, 56, + 79, 18, 52, 79, 19, 48, 80, 19, 44, 80, 20, 40, + 80, 21, 36, 80, 22, 32, 80, 22, 28, 80, 23, 24, + 80, 24, 19, 81, 25, 15, 81, 26, 11, 81, 27, 7, + 81, 29, 3, 81, 30, -2, 82, 31, -5, 82, 32, -9, + 82, 34, -13, 82, 35, -18, 83, 36, -22, 83, 38, -26, + 80, 13, 82, 80, 13, 81, 80, 13, 81, 80, 13, 79, + 80, 13, 77, 80, 14, 75, 80, 14, 73, 80, 14, 70, + 80, 14, 67, 80, 15, 64, 80, 15, 61, 80, 16, 57, + 80, 16, 53, 80, 17, 49, 81, 17, 45, 81, 18, 42, + 81, 19, 37, 81, 20, 33, 81, 20, 29, 81, 21, 25, + 81, 22, 20, 82, 23, 16, 82, 24, 12, 82, 25, 8, + 82, 26, 4, 82, 28, 0, 83, 29, -4, 83, 30, -8, + 83, 32, -12, 83, 33, -16, 83, 34, -20, 84, 36, -24, + 81, 11, 83, 81, 11, 82, 81, 11, 81, 81, 11, 80, + 81, 11, 78, 81, 11, 76, 81, 12, 74, 81, 12, 71, + 81, 12, 68, 81, 13, 65, 81, 13, 62, 81, 14, 58, + 81, 14, 54, 81, 15, 50, 81, 15, 47, 82, 16, 43, + 82, 17, 38, 82, 17, 34, 82, 18, 30, 82, 19, 26, + 82, 20, 22, 82, 21, 18, 83, 22, 14, 83, 23, 10, + 83, 24, 6, 83, 26, 1, 83, 27, -3, 84, 28, -7, + 84, 29, -11, 84, 31, -15, 84, 32, -19, 85, 34, -23, + 82, 9, 84, 82, 9, 83, 82, 9, 82, 82, 9, 81, + 82, 9, 79, 82, 9, 77, 82, 10, 75, 82, 10, 72, + 82, 10, 69, 82, 11, 66, 82, 11, 63, 82, 11, 59, + 82, 12, 55, 82, 13, 52, 82, 13, 48, 83, 14, 44, + 83, 15, 40, 83, 15, 36, 83, 16, 32, 83, 17, 28, + 83, 18, 23, 83, 19, 19, 84, 20, 15, 84, 21, 11, + 84, 22, 7, 84, 24, 2, 84, 25, -1, 85, 26, -5, + 85, 27, -9, 85, 29, -14, 85, 30, -18, 86, 32, -22, + 83, 7, 84, 83, 7, 83, 83, 7, 83, 83, 7, 81, + 83, 7, 80, 83, 7, 78, 83, 8, 76, 83, 8, 73, + 83, 8, 70, 83, 9, 67, 83, 9, 64, 83, 9, 60, + 83, 10, 56, 83, 10, 53, 83, 11, 49, 83, 12, 45, + 84, 13, 41, 84, 13, 37, 84, 14, 33, 84, 15, 29, + 84, 16, 24, 84, 17, 20, 84, 18, 16, 85, 19, 12, + 85, 20, 8, 85, 21, 4, 85, 23, 0, 85, 24, -4, + 86, 25, -8, 86, 27, -12, 86, 28, -16, 86, 30, -20, + 84, 5, 85, 84, 5, 84, 84, 5, 83, 84, 5, 82, + 84, 5, 81, 84, 5, 79, 84, 5, 77, 84, 6, 74, + 84, 6, 71, 84, 6, 68, 84, 7, 65, 84, 7, 61, + 84, 8, 57, 84, 8, 54, 84, 9, 50, 84, 10, 46, + 85, 10, 42, 85, 11, 38, 85, 12, 34, 85, 13, 30, + 85, 14, 26, 85, 15, 22, 85, 16, 18, 86, 17, 14, + 86, 18, 10, 86, 19, 5, 86, 21, 1, 86, 22, -3, + 87, 23, -7, 87, 25, -11, 87, 26, -15, 87, 27, -19, + 85, 3, 86, 85, 3, 85, 85, 3, 84, 85, 3, 83, + 85, 3, 81, 85, 3, 79, 85, 3, 77, 85, 4, 75, + 85, 4, 72, 85, 4, 69, 85, 5, 66, 85, 5, 62, + 85, 6, 59, 85, 6, 55, 85, 7, 51, 85, 8, 48, + 86, 8, 43, 86, 9, 39, 86, 10, 35, 86, 11, 31, + 86, 12, 27, 86, 13, 23, 86, 14, 19, 87, 15, 15, + 87, 16, 11, 87, 17, 7, 87, 18, 3, 87, 20, -1, + 88, 21, -5, 88, 23, -10, 88, 24, -14, 88, 25, -17, + 86, 1, 86, 86, 1, 86, 86, 1, 85, 86, 1, 84, + 86, 1, 82, 86, 1, 80, 86, 1, 78, 86, 2, 76, + 86, 2, 73, 86, 2, 70, 86, 3, 67, 86, 3, 64, + 86, 4, 60, 86, 4, 56, 86, 5, 52, 86, 6, 49, + 87, 6, 44, 87, 7, 41, 87, 8, 37, 87, 9, 33, + 87, 10, 28, 87, 11, 24, 87, 12, 20, 87, 13, 16, + 88, 14, 12, 88, 15, 8, 88, 16, 4, 88, 18, 0, + 88, 19, -4, 89, 20, -8, 89, 22, -12, 89, 23, -16, + 87, -2, 87, 87, -2, 86, 87, -2, 86, 87, -2, 85, + 87, -2, 83, 87, -1, 81, 87, -1, 79, 87, -1, 77, + 87, -1, 74, 87, 0, 71, 87, 0, 68, 87, 1, 65, + 87, 1, 61, 87, 2, 58, 88, 2, 54, 88, 3, 50, + 88, 4, 46, 88, 4, 42, 88, 5, 38, 88, 6, 34, + 88, 7, 30, 88, 8, 26, 89, 9, 22, 89, 10, 18, + 89, 11, 14, 89, 13, 10, 89, 14, 6, 89, 15, 2, + 90, 16, -2, 90, 18, -7, 90, 19, -10, 90, 21, -14, + 88, -4, 88, 88, -4, 87, 88, -4, 86, 88, -4, 85, + 88, -4, 84, 88, -3, 82, 88, -3, 80, 88, -3, 78, + 88, -3, 75, 88, -2, 72, 88, -2, 69, 88, -1, 66, + 88, -1, 62, 88, 0, 59, 89, 0, 55, 89, 1, 51, + 89, 2, 47, 89, 2, 43, 89, 3, 39, 89, 4, 36, + 89, 5, 31, 89, 6, 27, 90, 7, 23, 90, 8, 19, + 90, 9, 15, 90, 10, 11, 90, 12, 7, 90, 13, 3, + 91, 14, -1, 91, 16, -5, 91, 17, -9, 91, 19, -13, + 89, -6, 89, 89, -6, 88, 89, -6, 87, 89, -6, 86, + 89, -6, 85, 89, -5, 83, 89, -5, 81, 89, -5, 78, + 89, -5, 76, 89, -4, 73, 89, -4, 70, 89, -3, 67, + 89, -3, 63, 89, -2, 60, 90, -2, 56, 90, -1, 53, + 90, 0, 48, 90, 0, 45, 90, 1, 41, 90, 2, 37, + 90, 3, 33, 90, 4, 29, 90, 5, 25, 91, 6, 21, + 91, 7, 17, 91, 8, 12, 91, 10, 8, 91, 11, 5, + 92, 12, 1, 92, 14, -4, 92, 15, -8, 92, 16, -11, + 90, -8, 89, 90, -8, 89, 90, -8, 88, 90, -8, 87, + 90, -8, 85, 90, -7, 84, 90, -7, 82, 90, -7, 79, + 90, -7, 77, 90, -6, 74, 90, -6, 71, 90, -5, 68, + 90, -5, 64, 90, -4, 61, 91, -4, 57, 91, -3, 54, + 91, -2, 50, 91, -2, 46, 91, -1, 42, 91, 0, 38, + 91, 1, 34, 91, 2, 30, 91, 3, 26, 92, 4, 22, + 92, 5, 18, 92, 6, 14, 92, 8, 10, 92, 9, 6, + 93, 10, 2, 93, 12, -2, 93, 13, -6, 93, 14, -10, + 91, -10, 90, 91, -10, 89, 91, -10, 89, 91, -10, 88, + 91, -10, 86, 91, -9, 85, 91, -9, 83, 91, -9, 80, + 91, -9, 78, 91, -8, 75, 91, -8, 72, 91, -7, 69, + 91, -7, 65, 91, -6, 62, 92, -6, 59, 92, -5, 55, + 92, -4, 51, 92, -4, 47, 92, -3, 43, 92, -2, 39, + 92, -1, 35, 92, 0, 31, 92, 1, 27, 93, 2, 23, + 93, 3, 20, 93, 4, 15, 93, 6, 11, 93, 7, 7, + 94, 8, 3, 94, 9, -1, 94, 11, -5, 94, 12, -9, + 92, -12, 91, 92, -12, 90, 92, -12, 89, 92, -12, 88, + 92, -12, 87, 92, -11, 86, 92, -11, 84, 92, -11, 81, + 92, -11, 79, 92, -10, 76, 92, -10, 73, 92, -9, 70, + 92, -9, 67, 92, -8, 63, 93, -8, 60, 93, -7, 56, + 93, -6, 52, 93, -6, 48, 93, -5, 45, 93, -4, 41, + 93, -3, 36, 93, -2, 33, 93, -1, 29, 94, 0, 25, + 94, 1, 21, 94, 2, 17, 94, 3, 13, 94, 5, 9, + 94, 6, 5, 95, 7, 1, 95, 9, -3, 95, 10, -7, + 93, -14, 91, 93, -14, 91, 93, -14, 90, 93, -14, 89, + 93, -13, 88, 93, -13, 86, 93, -13, 85, 93, -13, 82, + 93, -13, 80, 93, -12, 77, 93, -12, 74, 93, -11, 71, + 93, -11, 68, 93, -10, 64, 94, -10, 61, 94, -9, 57, + 94, -8, 53, 94, -8, 50, 94, -7, 46, 94, -6, 42, + 94, -5, 38, 94, -4, 34, 94, -3, 30, 95, -2, 26, + 95, -1, 22, 95, 0, 18, 95, 1, 14, 95, 3, 10, + 95, 4, 6, 96, 5, 2, 96, 7, -2, 96, 8, -6, + 94, -16, 92, 94, -16, 92, 94, -16, 91, 94, -16, 90, + 94, -15, 89, 94, -15, 87, 94, -15, 85, 94, -15, 83, + 94, -14, 81, 94, -14, 78, 94, -14, 75, 94, -13, 72, + 94, -13, 69, 94, -12, 65, 95, -12, 62, 95, -11, 59, + 95, -10, 55, 95, -10, 51, 95, -9, 47, 95, -8, 43, + 95, -7, 39, 95, -6, 35, 95, -5, 31, 96, -4, 28, + 96, -3, 24, 96, -2, 19, 96, -1, 15, 96, 1, 12, + 96, 2, 8, 97, 3, 3, 97, 5, 0, 97, 6, -4, + 95, -18, 93, 95, -18, 92, 95, -18, 92, 95, -18, 91, + 95, -17, 90, 95, -17, 88, 95, -17, 86, 95, -17, 84, + 95, -16, 82, 95, -16, 79, 95, -16, 76, 95, -15, 73, + 95, -15, 70, 96, -14, 67, 96, -14, 63, 96, -13, 60, + 96, -12, 56, 96, -12, 52, 96, -11, 48, 96, -10, 45, + 96, -9, 40, 96, -8, 37, 96, -7, 33, 97, -6, 29, + 97, -5, 25, 97, -4, 21, 97, -3, 17, 97, -1, 13, + 97, 0, 9, 98, 1, 5, 98, 3, 1, 98, 4, -3, + 96, -20, 94, 96, -20, 93, 96, -20, 93, 96, -19, 92, + 96, -19, 90, 96, -19, 89, 96, -19, 87, 96, -19, 85, + 96, -18, 83, 96, -18, 80, 96, -18, 77, 96, -17, 74, + 96, -17, 71, 97, -16, 68, 97, -16, 64, 97, -15, 61, + 97, -14, 57, 97, -14, 53, 97, -13, 50, 97, -12, 46, + 97, -11, 42, 97, -10, 38, 97, -9, 34, 98, -8, 30, + 98, -7, 26, 98, -6, 22, 98, -5, 18, 98, -3, 14, + 98, -2, 11, 99, -1, 6, 99, 1, 2, 99, 2, -1, + 97, -22, 94, 97, -21, 94, 97, -21, 93, 97, -21, 92, + 97, -21, 91, 97, -21, 90, 97, -21, 88, 97, -21, 86, + 97, -20, 84, 97, -20, 81, 97, -20, 78, 97, -19, 75, + 98, -19, 72, 98, -18, 69, 98, -18, 66, 98, -17, 62, + 98, -16, 58, 98, -15, 55, 98, -15, 51, 98, -14, 47, + 98, -13, 43, 98, -12, 39, 98, -11, 35, 99, -10, 32, + 99, -9, 28, 99, -8, 23, 99, -7, 20, 99, -5, 16, + 99, -4, 12, 100, -3, 8, 100, -1, 4, 100, 0, 0 +}; \ No newline at end of file diff --git a/components/3rd_party/omv/omv/imlib/lbp.c b/components/3rd_party/omv/omv/imlib/lbp.c new file mode 100644 index 00000000..edc5ea0f --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/lbp.c @@ -0,0 +1,127 @@ +/* + * 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 "file_utils.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 < LBP_DESC_SIZE; i++) { + int w = lbp_weights[i / LBP_HIST_SIZE]; + sum += w * ((((d0[i] - d1[i]) * (d0[i] - d1[i])) / IM_MAX((d0[i] + d1[i]), 1))); + } + return sum; +} + +int imlib_lbp_desc_save(FIL *fp, uint8_t *desc) { +#if 0 + UINT bytes; + // Write descriptor + return file_ll_write(fp, desc, LBP_DESC_SIZE, &bytes); +#else + // #warning "imlib_lbp_desc_save() not implemented" + return 0; +#endif +} + +int imlib_lbp_desc_load(FIL *fp, uint8_t **desc) { +#if 0 + UINT bytes; + FRESULT res = FR_OK; + + *desc = NULL; + uint8_t *hist = xalloc(LBP_DESC_SIZE); + + // Read descriptor + res = file_ll_read(fp, hist, LBP_DESC_SIZE, &bytes); + if (res != FR_OK || bytes != LBP_DESC_SIZE) { + *desc = NULL; + xfree(hist); + } else { + *desc = hist; + } + + return res; +#else + // #warning "imlib_lbp_desc_save() not implemented" + return 0; +#endif +} +#endif //IMLIB_ENABLE_FIND_LBP diff --git a/components/3rd_party/omv/omv/imlib/line.c b/components/3rd_party/omv/omv/imlib/line.c new file mode 100644 index 00000000..9293402b --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/line.c @@ -0,0 +1,482 @@ +/* + * 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 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; + } + 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; + 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/components/3rd_party/omv/omv/imlib/lodepng.c b/components/3rd_party/omv/omv/imlib/lodepng.c new file mode 100644 index 00000000..13e4c71d --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/lodepng.c @@ -0,0 +1,7518 @@ +/* + 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 < unsigned char >& 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 < unsigned char >& 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 < unsigned char >& 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 < unsigned char >& out, const std::vector < unsigned char >& 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 < unsigned char >& 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 < unsigned char >& out, const std::vector < unsigned char >& 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 < unsigned char >& 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 < unsigned char >& out, unsigned& w, unsigned& h, + const std::vector < unsigned char >& in, LodePNGColorType colortype, unsigned bitdepth) { + return decode(out, w, h, in.empty() ? 0 : &in[0], (unsigned) in.size(), colortype, bitdepth); + } + + unsigned decode(std::vector < unsigned char >& 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 < unsigned char >& out, unsigned& w, unsigned& h, + State& state, + const std::vector < unsigned char >& in) { + return decode(out, w, h, state, in.empty() ? 0 : &in[0], in.size()); + } + +#ifdef LODEPNG_COMPILE_DISK + unsigned decode(std::vector < unsigned char >& out, unsigned& w, unsigned& h, const std::string& filename, + LodePNGColorType colortype, unsigned bitdepth) { + std::vector < unsigned char > 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 < unsigned char >& 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 < unsigned char >& out, + const std::vector < unsigned char >& 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 < unsigned char >& 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 < unsigned char >& out, + const std::vector < unsigned char >& 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 < unsigned char > 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 < unsigned char >& 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/components/3rd_party/omv/omv/imlib/lodepng.h b/components/3rd_party/omv/omv/imlib/lodepng.h new file mode 100644 index 00000000..3f9089fd --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/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 < unsigned char >& out, unsigned& w, unsigned& h, + const unsigned char *in, size_t insize, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned decode(std::vector < unsigned char >& out, unsigned& w, unsigned& h, + const std::vector < unsigned char >& 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 < unsigned char >& 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 < unsigned char >& out, + const unsigned char *in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(std::vector < unsigned char >& out, + const std::vector < unsigned char >& 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 < unsigned char >& 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. + Return 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 < unsigned char >& out, unsigned& w, unsigned& h, + State& state, + const unsigned char *in, size_t insize); +unsigned decode(std::vector < unsigned char >& out, unsigned& w, unsigned& h, + State& state, + const std::vector < unsigned char >& 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 < unsigned char >& out, + const unsigned char *in, unsigned w, unsigned h, + State& state); +unsigned encode(std::vector < unsigned char >& out, + const std::vector < unsigned char >& 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 < unsigned char >& 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 < unsigned char >& 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 < unsigned char >& out, const unsigned char *in, size_t insize, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); + +/* Zlib-decompress an std::vector */ +unsigned decompress(std::vector < unsigned char >& out, const std::vector < unsigned char >& 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 < unsigned char >& out, const unsigned char *in, size_t insize, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); + +/* Zlib-compress an std::vector */ +unsigned compress(std::vector < unsigned char >& out, const std::vector < unsigned char >& 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 output, 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/components/3rd_party/omv/omv/imlib/lsd.c b/components/3rd_party/omv/omv/imlib/lsd.c new file mode 100644 index 00000000..4bd5fabf --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/lsd.c @@ -0,0 +1,2764 @@ +/* + * This file is part of the OpenMV project. + * + * 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 . + */ +#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 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 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); + +/** 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; +}; + + +/*----------------------------------------------------------------------------*/ +/** 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 + + 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++; +} + + +/** 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; i < N; i++) { + image->data[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; i < N; i++) { + image->data[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; +} + + +/** 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; i < kernel->dim; 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; i < kernel->dim; 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; x < aux->xsize; 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; y < aux->ysize; y++) { + sum = 0.0; + for (i = 0; i < kernel->dim; 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; y < out->ysize; 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; x < out->xsize; x++) { + sum = 0.0; + for (i = 0; i < kernel->dim; 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; +} + +/** 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; i < n_bins; i++) { + range_l_s[i] = range_l_e[i] = NULL; + } + + /* 'undefined' on the down and right boundaries */ + for (x = 0; x < p; x++) { + g->data[(n - 1) * p + x] = NOTDEF; + } + for (y = 0; y < n; y++) { + g->data[p * y + p - 1] = NOTDEF; + } + + /* compute gradient on the remaining pixels */ + for (x = 0; x < p - 1; x++) { + for (y = 0; y < n - 1; y++) { + adr = y * p + x; + + /* + Norm 2 computation using 2x2 pixel window: + A B + C D + and + com1 = D-A, com2 = B-C. + Then + gx = B+D - (A+C) horizontal difference + gy = C+D - (A+B) vertical difference + com1 and com2 are just to avoid 2 additions. + */ + com1 = in->data[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; x < p - 1; x++) { + for (y = 0; y < n - 1; y++) { + norm = (*modgrad)->data[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; +} + +/** 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)) + +/** 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: 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) { + /* interpolation */ + if (double_equal(x1, x2) && y1 < y2) { + return y1; + } + if (double_equal(x1, x2) && y1 > y2) { + return y2; + } + + float result = y1 + (x - x1) * (y2 - y1) / (x2 - x1); + if (isnan(result) || isinf(result)) { + return (y1 < y2) ? y1 : ((y1 > y2) ? 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) { + /* interpolation */ + if (double_equal(x1, x2) && y1 < y2) { + return y2; + } + if (double_equal(x1, x2) && y1 > y2) { + 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 (y1 < y2) ? y2 : ((y1 > y2) ? 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) { + /* 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 iterator 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; + + /* 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; i < reg_size; i++) { + weight = modgrad->data[ 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; i < reg_size; i++) { + iweight = modgrad->data[ 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 < reg_size; i++) { + l = ( (float) reg[i].x - x) * dx + ( (float) reg[i].y - y) * dy; + w = -( (float) reg[i].x - x) * dy + ( (float) reg[i].y - y) * dx; + + if (l > 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."); + } + + /* 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 < l_size; i++) { + int dx = 3, dy = 3; // assume 3x3 region to try + int ty = reg[i].y - 1; + int tx = reg[i].x - 1; + if (tx < 0) { + tx = 0; dx--; + } else if (tx + dx >= xsize) { + dx--; + } + if (ty < 0) { + ty = 0; dy--; + } else if (ty + dy >= used->ysize) { + dy--; + } + for (xx = tx; xx < tx + dx; xx++) { + for (yy = ty; yy < ty + dy; yy++) { + if (used->data[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); + 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 */ + 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) { + /* 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; + imlib_draw_image(&img, ptr, 0, 0, 1.f, 1.f, roi, -1, 256, NULL, NULL, 0, NULL, 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); + 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); + } + + // umm_init_x() is not implemented, so it does not need to free memory + // fb_free(); // umm_init_x(); + if (grayscale_image) fb_free(grayscale_image); // grayscale_image; + if (ls) fb_free(ls); // ls; +} + +#pragma GCC diagnostic pop +#endif //IMLIB_ENABLE_FIND_LINE_SEGMENTS diff --git a/components/3rd_party/omv/omv/imlib/mathop.c b/components/3rd_party/omv/omv/imlib/mathop.c new file mode 100644 index 00000000..4d17350c --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/mathop.c @@ -0,0 +1,919 @@ +/* + * 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_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++) { + pixel_rgb_t *data = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + pixel_rgb_t dataPixel = IMAGE_GET_RGB888_PIXEL_FAST(data, x); + COLOR_RGB888_TO_R8(dataPixel) = ~COLOR_RGB888_TO_R8(dataPixel); + COLOR_RGB888_TO_G8(dataPixel) = ~COLOR_RGB888_TO_G8(dataPixel); + COLOR_RGB888_TO_B8(dataPixel) = ~COLOR_RGB888_TO_B8(dataPixel); + 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); + } else { + int pixel = 0; + 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); + } else { + int pixel = 0; + 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); + } else { + int pixel = 0; + 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)) { + pixel_rgb_t pixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_t *) other), h_i); + IMAGE_PUT_RGB888_PIXEL(&target, transpose ? v_line : i, transpose ? i : v_line, pixel); + } else { + pixel_rgb_t pixel = {0}; + 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; + } + + // To improve transpose performance we will split the operation up into chunks that fit in + // onchip RAM. These chunks will then be copied to the target buffer in an efficent manner. + if (path == NULL && other && transpose) { + uint32_t size; + void *data = fb_alloc_all(&size, FB_ALLOC_PREFER_SPEED); + // line_num stores how many lines we can do at a time with on-chip RAM. + int line_num = size / image_line_size(other); + // Transposed chunks will be copied to the output image... + uint8_t *img_data = img->data; + int t_line_size = (image_line_size(img) * img->h) / img->w; + // Work top to bottom transposing as many lines at a time in a chunk of the image. + for (int i = 0, ii = other->h; i < ii; i += line_num) { + line_num = IM_MIN(line_num, (ii - i)); + // Make an image that is a slice of the input image. + image_t in = {.w = other->w, .h = line_num, .pixfmt = other->pixfmt}; + in.data = other->data + (image_line_size(other) * i); + // Make an image that will hold the transposed output. + image_t out = in; + out.data = data; + // Transpose the slice of the input image. + imlib_replace_line_op_state_t state; + state.hmirror = hmirror; + state.vflip = vflip; + state.mask = mask; + state.transpose = true; + imlib_image_operation(&out, NULL, &in, 0, imlib_replace_line_op, &state); + out.w = line_num; + out.h = other->w; + // Copy lines of the chunk to the target image. + int out_line_size = image_line_size(&out); + for (int j = 0, jj = out.h; j < jj; j++) { + memcpy(img_data + (t_line_size * j), out.data + (out_line_size * j), out_line_size); + } + // Slide the offset for the first line over by the size of the slice we transposed. + img_data += out_line_size; + } + if (data) fb_free(data); // fb_alloc_all + } else { + 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) { + if (temp.data) 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: { + pixel_rgb_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)) { + pixel_rgb_t dataPixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + pixel_rgb_t otherPixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_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: { + pixel_rgb_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)) { + pixel_rgb_t dataPixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + pixel_rgb_t otherPixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_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: { + pixel_rgb_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)) { + pixel_rgb_t dataPixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + pixel_rgb_t otherPixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_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: { + pixel_rgb_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)) { + pixel_rgb_t dataPixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + pixel_rgb_t otherPixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_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: { + pixel_rgb_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)) { + pixel_rgb_t dataPixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + pixel_rgb_t otherPixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_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: { + pixel_rgb_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)) { + pixel_rgb_t dataPixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + pixel_rgb_t otherPixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_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: { + pixel_rgb_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)) { + pixel_rgb_t dataPixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + pixel_rgb_t otherPixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_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: { + pixel_rgb_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)) { + pixel_rgb_t dataPixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + pixel_rgb_t otherPixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel_rgb_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/components/3rd_party/omv/omv/imlib/mjpeg.c b/components/3rd_party/omv/omv/imlib/mjpeg.c new file mode 100644 index 00000000..7dafafcb --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/mjpeg.c @@ -0,0 +1,239 @@ +/* + * 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. + * + * A simple MJPEG encoder. + */ +#include "imlib.h" +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + +#include "file_utils.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) { + file_write(fp, "RIFF", 4); // FOURCC fcc; - 0 + file_write_long(fp, 0); // DWORD cb; size - updated on close - 1 + file_write(fp, "AVI ", 4); // FOURCC fcc; - 2 + + file_write(fp, "LIST", 4); // FOURCC fcc; - 3 + file_write_long(fp, 192); // DWORD cb; - 4 + file_write(fp, "hdrl", 4); // FOURCC fcc; - 5 + + file_write(fp, "avih", 4); // FOURCC fcc; - 6 + file_write_long(fp, 56); // DWORD cb; - 7 + file_write_long(fp, 0); // DWORD dwMicroSecPerFrame; micros - updated on close - 8 + file_write_long(fp, 0); // DWORD dwMaxBytesPerSec; updated on close - 9 + file_write_long(fp, 4); // DWORD dwPaddingGranularity; - 10 + file_write_long(fp, 0); // DWORD dwFlags; - 11 + file_write_long(fp, 0); // DWORD dwTotalFrames; frames - updated on close - 12 + file_write_long(fp, 0); // DWORD dwInitialFrames; - 13 + file_write_long(fp, 1); // DWORD dwStreams; - 14 + file_write_long(fp, 0); // DWORD dwSuggestedBufferSize; - 15 + file_write_long(fp, width); // DWORD dwWidth; - 16 + file_write_long(fp, height); // DWORD dwHeight; - 17 + file_write_long(fp, 1000); // DWORD dwScale; - 18 + file_write_long(fp, 0); // DWORD dwRate; rate - updated on close - 19 + file_write_long(fp, 0); // DWORD dwStart; - 20 + file_write_long(fp, 0); // DWORD dwLength; length - updated on close - 21 + + file_write(fp, "LIST", 4); // FOURCC fcc; - 22 + file_write_long(fp, 116); // DWORD cb; - 23 + file_write(fp, "strl", 4); // FOURCC fcc; - 24 + + file_write(fp, "strh", 4); // FOURCC fcc; - 25 + file_write_long(fp, 56); // DWORD cb; - 26 + file_write(fp, "vids", 4); // FOURCC fccType; - 27 + file_write(fp, "MJPG", 4); // FOURCC fccHandler; - 28 + file_write_long(fp, 0); // DWORD dwFlags; - 29 + file_write_short(fp, 0); // WORD wPriority; - 30 + file_write_short(fp, 0); // WORD wLanguage; - 30.5 + file_write_long(fp, 0); // DWORD dwInitialFrames; - 31 + file_write_long(fp, 1000); // DWORD dwScale; - 32 + file_write_long(fp, 0); // DWORD dwRate; rate - updated on close - 33 + file_write_long(fp, 0); // DWORD dwStart; - 34 + file_write_long(fp, 0); // DWORD dwLength; length - updated on close - 35 + file_write_long(fp, 0); // DWORD dwSuggestedBufferSize; - 36 + file_write_long(fp, 10000); // DWORD dwQuality; - 37 + file_write_long(fp, 0); // DWORD dwSampleSize; - 38 + file_write_short(fp, 0); // short int left; - 39 + file_write_short(fp, 0); // short int top; - 39.5 + file_write_short(fp, 0); // short int right; - 40 + file_write_short(fp, 0); // short int bottom; - 40.5 + + file_write(fp, "strf", 4); // FOURCC fcc; - 41 + file_write_long(fp, 40); // DWORD cb; - 42 + file_write_long(fp, 40); // DWORD biSize; - 43 + file_write_long(fp, width); // DWORD biWidth; - 44 + file_write_long(fp, height); // DWORD biHeight; - 45 + file_write_short(fp, 1); // WORD biPlanes; - 46 + file_write_short(fp, 24); // WORD biBitCount; - 46.5 + file_write(fp, "MJPG", 4); // DWORD biCompression; - 47 + file_write_long(fp, 0); // DWORD biSizeImage; - 48 + file_write_long(fp, 0); // DWORD biXPelsPerMeter; - 49 + file_write_long(fp, 0); // DWORD biYPelsPerMeter; - 50 + file_write_long(fp, 0); // DWORD biClrUsed; - 51 + file_write_long(fp, 0); // DWORD biClrImportant; - 52 + + file_write(fp, "LIST", 4); // FOURCC fcc; - 53 + file_write_long(fp, 0); // DWORD cb; movi - updated on close - 54 + file_write(fp, "movi", 4); // FOURCC fcc; - 55 +} + +void mjpeg_write(FIL *fp, int width, int height, uint32_t *frames, uint32_t *bytes, + image_t *img, int quality, rectangle_t *roi, int rgb_channel, int alpha, + const uint16_t *color_palette, const uint8_t *alpha_palette, image_hint_t hint) { + float xscale = width / ((float) roi->w); + float yscale = height / ((float) roi->h); + // MAX == KeepAspectRationByExpanding - MIN == KeepAspectRatio + float scale = IM_MIN(xscale, yscale); + + image_t dst_img = { + .w = width, + .h = height, + .pixfmt = PIXFORMAT_JPEG, + .size = 0, + .data = NULL + }; + + bool simple = (xscale == 1) && + (yscale == 1) && + (roi->x == 0) && + (roi->y == 0) && + (roi->w == img->w) && + (roi->h == img->h) && + (rgb_channel == -1) && + (alpha == 256) && + (color_palette == NULL) && + (alpha_palette == NULL); + + fb_alloc_mark(); + + if ((dst_img.pixfmt != img->pixfmt) || (!simple)) { + image_t temp; + memcpy(&temp, img, sizeof(image_t)); + + if (img->is_compressed || (!simple)) { + temp.w = dst_img.w; + temp.h = dst_img.h; + temp.pixfmt = PIXFORMAT_RGB565; // TODO PIXFORMAT_ARGB8888 + temp.size = 0; + temp.data = fb_alloc(image_size(&temp), FB_ALLOC_NO_HINT); + + int center_x = fast_floorf((width - (roi->w * scale)) / 2); + int center_y = fast_floorf((height - (roi->h * scale)) / 2); + + point_t p0, p1; + imlib_draw_image_get_bounds(&temp, img, center_x, center_y, scale, scale, roi, + alpha, alpha_palette, (hint & (~IMAGE_HINT_CENTER)), &p0, &p1); + bool black = p0.x == -1; + + if (black) { + // zero the whole image + memset(temp.data, 0, temp.w * temp.h * sizeof(uint16_t)); + } else { + // Zero the top rows + if (p0.y) { + memset(temp.data, 0, temp.w * p0.y * sizeof(uint16_t)); + } + + if (p0.x) { + for (int i = p0.y; i < p1.y; i++) { + // Zero left + memset(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&temp, i), 0, p0.x * sizeof(uint16_t)); + } + } + + imlib_draw_image(&temp, img, center_x, center_y, scale, scale, roi, + rgb_channel, alpha, color_palette, alpha_palette, + (hint & (~IMAGE_HINT_CENTER)) | IMAGE_HINT_BLACK_BACKGROUND, + NULL, NULL, NULL); + + if (temp.w - p1.x) { + for (int i = p0.y; i < p1.y; i++) { + // Zero right + memset(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&temp, i) + p1.x, + 0, (temp.w - p1.x) * sizeof(uint16_t)); + } + } + + // Zero the bottom rows + if (temp.h - p1.y) { + memset(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&temp, p1.y), + 0, temp.w * (temp.h - p1.y) * sizeof(uint16_t)); + } + } + } + + // 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(&temp, &dst_img, quality, true); + } else { + dst_img.size = img->size; + dst_img.data = img->data; + } + + uint32_t size_padded = (((dst_img.size + 3) / 4) * 4); + file_write(fp, "00dc", 4); // FOURCC fcc; + file_write_long(fp, size_padded); // DWORD cb; + file_write(fp, dst_img.data, size_padded); // reading past okay + + *frames += 1; + *bytes += size_padded; + + fb_alloc_free_till_mark(); +} + +void mjpeg_sync(FIL *fp, uint32_t *frames, uint32_t *bytes, float fps) { + uint32_t position = f_tell(fp); + // Needed + file_seek(fp, SIZE_OFFSET); + file_write_long(fp, 216 + (*frames * 8) + *bytes); + // Needed + file_seek(fp, MICROS_OFFSET); + file_write_long(fp, (!fast_roundf(fps)) ? 0 : + fast_roundf(1000000 / fps)); + file_write_long(fp, (!(*frames)) ? 0 : + fast_roundf((((*frames * 8) + *bytes) * fps) / *frames)); + // Needed + file_seek(fp, FRAMES_OFFSET); + file_write_long(fp, *frames); + // Probably not needed but writing it just in case. + file_seek(fp, RATE_0_OFFSET); + file_write_long(fp, fast_roundf(fps * 1000)); + // Probably not needed but writing it just in case. + file_seek(fp, LENGTH_0_OFFSET); + file_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); + file_write_long(fp, fast_roundf(fps * 1000)); + // Probably not needed but writing it just in case. + file_seek(fp, LENGTH_1_OFFSET); + file_write_long(fp, (!fast_roundf(fps)) ? 0 : + fast_roundf((*frames * 1000) / fps)); + // Needed + file_seek(fp, MOVI_OFFSET); + file_write_long(fp, 4 + (*frames * 8) + *bytes); + file_sync(fp); + file_seek(fp, position); +} + +void mjpeg_close(FIL *fp, uint32_t *frames, uint32_t *bytes, float fps) { + mjpeg_sync(fp, frames, bytes, fps); + file_close(fp); +} + +#endif // IMLIB_ENABLE_IMAGE_FILE_IO diff --git a/components/3rd_party/omv/omv/imlib/orb.c b/components/3rd_party/omv/omv/imlib/orb.c new file mode 100644 index 00000000..84daac34 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/orb.c @@ -0,0 +1,842 @@ +/* + * 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 "imlib.h" +#include "xalloc.h" +#include "fb_alloc.h" +#include "file_utils.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; y < dst->h; y++) { + int sy = (y * y_ratio) >> 16; + for (int x = 0; x < dst->w; 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; k < array_length(kpts); k++, kpts_index++) { + // Set keypoint octave/scale + kp_t *kpt = array_at(kpts, k); + kpt->octave = 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 < KDESC_SIZE; ++i, pattern += 16) { + int t0, t1, t2, t3, u, v, k, val; + t0 = GET_VALUE(0); t1 = GET_VALUE(1); + t2 = GET_VALUE(2); t3 = GET_VALUE(3); + 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; + + 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 + if (img_scaled.pixels) 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 modified 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; i < kpts_size; i++) { + int dist = 0; + kp_t *kp2 = array_at(kpts, i); + + if (kp2->matched == 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 < kpts1_size; i++) { + int kp_index1 = 0; + int kp_index2 = 0; + int min_dist1 = 0; + int min_dist2 = 0; + kp_t *min_kp = NULL; + kp_t *kp1 = array_at(kpts1, i); + + // Find the best match in second set + min_kp = find_best_match(kp1, kpts2, &min_dist1, &min_dist2, &kp_index2); + // Test the distance ratio between the best two matches + if ((min_dist1 * 100 / min_dist2) > 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; i < kpts_size; i++) { + kp_t *kp = array_at(kpts, i); + if (kp->matched) { + 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; i < kpts_size; i++) { + kp_t *kp = array_at(kpts, i); + if (kp->matched) { + 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; i < kpts_size; i++) { + kp_t *kp = array_at(kpts, i); + if (kp->matched) { + 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; i < kpts_size; i++) { + kp_t *kp = array_at(kpts, i); + if (kp->matched) { + 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 + if (kpts_dist) fb_free(kpts_dist); + return matches; +} + +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) +int orb_save_descriptor(FIL *fp, array_t *kpts) { + UINT bytes; + FRESULT res; + + int kpts_size = array_length(kpts); + + // Write the number of keypoints + res = file_ll_write(fp, &kpts_size, sizeof(kpts_size), &bytes); + if (res != FR_OK || bytes != sizeof(kpts_size)) { + goto error; + } + + // Write keypoints + for (int i = 0; i < kpts_size; i++) { + kp_t *kp = array_at(kpts, i); + + // Write X + res = file_ll_write(fp, &kp->x, sizeof(kp->x), &bytes); + if (res != FR_OK || bytes != sizeof(kp->x)) { + goto error; + } + + // Write Y + res = file_ll_write(fp, &kp->y, sizeof(kp->y), &bytes); + if (res != FR_OK || bytes != sizeof(kp->y)) { + goto error; + } + + // Write Score + res = file_ll_write(fp, &kp->score, sizeof(kp->score), &bytes); + if (res != FR_OK || bytes != sizeof(kp->score)) { + goto error; + } + + // Write Octave + res = file_ll_write(fp, &kp->octave, sizeof(kp->octave), &bytes); + if (res != FR_OK || bytes != sizeof(kp->octave)) { + goto error; + } + + // Write Angle + res = file_ll_write(fp, &kp->angle, sizeof(kp->angle), &bytes); + if (res != FR_OK || bytes != sizeof(kp->angle)) { + goto error; + } + + // Write descriptor + res = file_ll_write(fp, kp->desc, KDESC_SIZE, &bytes); + if (res != FR_OK || bytes != KDESC_SIZE) { + goto error; + } + } + +error: + return res; +} + +int orb_load_descriptor(FIL *fp, array_t *kpts) { + UINT bytes; + FRESULT res = FR_OK; + + int kpts_size = 0; + + // Read number of keypoints + res = file_ll_read(fp, &kpts_size, sizeof(kpts_size), &bytes); + if (res != FR_OK || bytes != sizeof(kpts_size)) { + goto error; + } + + // Read keypoints + for (int i = 0; i < kpts_size; i++) { + kp_t *kp = xalloc(sizeof(*kp)); + kp->matched = 0; + + // Read X + res = file_ll_read(fp, &kp->x, sizeof(kp->x), &bytes); + if (res != FR_OK || bytes != sizeof(kp->x)) { + goto error; + } + + // Read Y + res = file_ll_read(fp, &kp->y, sizeof(kp->y), &bytes); + if (res != FR_OK || bytes != sizeof(kp->y)) { + goto error; + } + + // Read Score + res = file_ll_read(fp, &kp->score, sizeof(kp->score), &bytes); + if (res != FR_OK || bytes != sizeof(kp->score)) { + goto error; + } + + // Read Octave + res = file_ll_read(fp, &kp->octave, sizeof(kp->octave), &bytes); + if (res != FR_OK || bytes != sizeof(kp->octave)) { + goto error; + } + + // Read Angle + res = file_ll_read(fp, &kp->angle, sizeof(kp->angle), &bytes); + if (res != FR_OK || bytes != sizeof(kp->angle)) { + goto error; + } + + // Read descriptor + res = file_ll_read(fp, kp->desc, KDESC_SIZE, &bytes); + if (res != FR_OK || bytes != 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/components/3rd_party/omv/omv/imlib/phasecorrelation.c b/components/3rd_party/omv/omv/imlib/phasecorrelation.c new file mode 100644 index 00000000..1b442e41 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/phasecorrelation.c @@ -0,0 +1,704 @@ +/* + * 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: { + pixel_rgb_t *tmp = (pixel_rgb_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++) { + pixel_rgb_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 + pixel_rgb_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: { + pixel_rgb_t *tmp = (pixel_rgb_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++) { + pixel_rgb_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 + pixel_rgb_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); + + if (img_2.data) 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++) { + pixel_rgb_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) { + if (img1alt.data) fb_free(img1alt.data); // img1alt + if (img0alt.data) 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) { + if (img0_fixed.pixels) fb_free(img0_fixed.pixels); + } +} +#endif //IMLIB_ENABLE_FIND_DISPLACEMENT diff --git a/components/3rd_party/omv/omv/imlib/png.c b/components/3rd_party/omv/omv/imlib/png.c new file mode 100644 index 00000000..e5c53be9 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/png.c @@ -0,0 +1,328 @@ +/* + * 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 "file_utils.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 umm_malloc(size); +} + +void *lodepng_realloc(void *ptr, size_t new_size) { + return umm_realloc(ptr, new_size); +} + +void lodepng_free(void *ptr) { + return umm_free(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 + 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; + } + 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_YUV_ANY: + mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("Input format is not supported")); + break; + case PIXFORMAT_BAYER_ANY: + 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)); + } + + 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 { + 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(). + // umm_init_x() is not implemented, so it does not need to free memory + // 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; + } + + 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)); + } + + uint32_t new_img_size = image_size(dst); + if (new_img_size <= img_size) { + memcpy(dst->data, png_data, new_img_size); + } else { + 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(). + // umm_init_x() is not implemented, so it does not need to free memory + // 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) { + 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) { + 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 + file_read(fp, &header, 4); + if (header == 0x52444849) { + // IHDR + uint32_t width, height; + file_read(fp, &width, 4); + file_read(fp, &height, 4); + width = __builtin_bswap32(width); + height = __builtin_bswap32(height); + + rs->png_w = width; + rs->png_h = height; + rs->png_size = IMLIB_IMAGE_MAX_SIZE(f_size(fp)); + + img->w = rs->png_w; + img->h = rs->png_h; + img->size = rs->png_size; + img->pixfmt = PIXFORMAT_PNG; + } else { + file_raise_corrupted(fp); + } +} + +// This function reads the pixel values of an image. +void png_read_pixels(FIL *fp, image_t *img) { + file_seek(fp, 0); + file_read(fp, img->pixels, img->size); +} + +void png_read(image_t *img, const char *path) { + FIL fp; + png_read_settings_t rs; + + // Do not use file buferring here. + file_open(&fp, path, false, FA_READ | FA_OPEN_EXISTING); + + 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_open(&fp, path, false, FA_WRITE | FA_CREATE_ALWAYS); + if (img->pixfmt == PIXFORMAT_PNG) { + file_write(&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); + file_write(&fp, out.pixels, out.size); + if (out.data) fb_free(out.data); // frees alloc in png_compress() + } + file_close(&fp); +} +#endif //IMLIB_ENABLE_IMAGE_FILE_IO) diff --git a/components/3rd_party/omv/omv/imlib/point.c b/components/3rd_party/omv/omv/imlib/point.c new file mode 100644 index 00000000..ecf0223a --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/point.c @@ -0,0 +1,30 @@ +/* + * 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/components/3rd_party/omv/omv/imlib/pool.c b/components/3rd_party/omv/omv/imlib/pool.c new file mode 100644 index 00000000..63f56483 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/pool.c @@ -0,0 +1,169 @@ +/* + * 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; + } + 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++) { + pixel_rgb_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++) { + pixel_rgb_t 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/components/3rd_party/omv/omv/imlib/ppm.c b/components/3rd_party/omv/omv/imlib/ppm.c new file mode 100644 index 00000000..c60793d2 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/ppm.c @@ -0,0 +1,174 @@ +/* + * 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 "file_utils.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(fp) == file_size(fp)) { + return; + } + file_read(fp, &rs->read_int_c, 1); + 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); + file_read_check(fp, "P", 1); + file_read(fp, &rs->ppm_fmt, 1); + + if ((rs->ppm_fmt != '2') && (rs->ppm_fmt != '3') && (rs->ppm_fmt != '5') && (rs->ppm_fmt != '6')) { + file_raise_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)) { + file_raise_corrupted(fp); + } + + uint32_t max; + read_int(fp, &max, rs); + if (max != 255) { + file_raise_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') { + file_read(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; + file_read(fp, &r, 1); + file_read(fp, &g, 1); + file_read(fp, &b, 1); + 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_open(&fp, path, true, FA_READ | FA_OPEN_EXISTING); + 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_close(&fp); +} + +void ppm_write_subimg(image_t *img, const char *path, rectangle_t *r) { + rectangle_t rect; + if (!rectangle_subimg(img, r, &rect)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("No intersection!")); + } + + FIL fp; + file_open(&fp, path, true, FA_WRITE | FA_CREATE_ALWAYS); + + 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); + file_write(&fp, buffer, len); + if ((rect.x == 0) && (rect.w == img->w)) { + file_write(&fp, img->pixels + (rect.y * img->w), rect.w * rect.h); + } else { + for (int i = 0; i < rect.h; i++) { + file_write(&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); + file_write(&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); + file_write(&fp, buff, 3); + } + } + } + file_close(&fp); +} +#endif //IMLIB_ENABLE_IMAGE_FILE_IO diff --git a/components/3rd_party/omv/omv/imlib/qrcode.c b/components/3rd_party/omv/omv/imlib/qrcode.c new file mode 100644 index 00000000..1b8afd06 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/qrcode.c @@ -0,0 +1,3044 @@ +/* + * 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 + +// *INDENT-OFF* +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "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; + +#pragma GCC push_options +#pragma GCC optimize ("O1") +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; +} +#pragma GCC pop_options + +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 multiply 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) + { if (ds) 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) + { if (ds) 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) + { if (ds) fb_free(ds); return err; } + + read_data(code, data, ds); + err = codestream_ecc(data, ds); + if (err) + { if (ds) fb_free(ds); return err; } + + err = decode_payload(data, ds); + if (err) + { if (ds) fb_free(ds); return err; } + + if (ds) 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(q->pixels); + quirc_pixel_t *new_pixels = fb_alloc(new_size, FB_ALLOC_NO_HINT); + if (!new_pixels) { + fb_free(new_image); + 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; + int need_free_controller_image = 1; + if (roi->w == ptr->w && roi->h == ptr->h && ptr->pixfmt == PIXFORMAT_GRAYSCALE) { + controller->image = ptr->data; + need_free_controller_image = 0; + } else { + img.w = roi->w; + img.h = roi->h; + img.pixfmt = PIXFORMAT_GRAYSCALE; + img.data = grayscale_image; + imlib_draw_image(&img, ptr, 0, 0, 1.f, 1.f, roi, -1, 256, NULL, NULL, 0, NULL, NULL, NULL); + } + + quirc_end(controller); + 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); + } + + if (data) fb_free(data); + if (code) fb_free(code); + } + + if (!need_free_controller_image) { + controller->image = NULL; + } + quirc_destroy(controller); +} +#endif //IMLIB_ENABLE_QRCODES *INDENT-ON* diff --git a/components/3rd_party/omv/omv/imlib/qsort.c b/components/3rd_party/omv/omv/imlib/qsort.c new file mode 100644 index 00000000..b2def63b --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/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/components/3rd_party/omv/omv/imlib/rainbow_tab.c b/components/3rd_party/omv/omv/imlib/rainbow_tab.c new file mode 100644 index 00000000..0593131e --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/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/components/3rd_party/omv/omv/imlib/rectangle.c b/components/3rd_party/omv/omv/imlib/rectangle.c new file mode 100644 index 00000000..98ebd7fb --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/rectangle.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. + * + * 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 overlapping detections */ + while (array_length(rectangles)) { + /* check for overlapping detections */ + rectangle_t *rect = (rectangle_t *) array_take(rectangles, 0); + for (int j = 0; j < array_length(rectangles); j++) { + // do not cache bound + if (rectangle_intersects(rect, (rectangle_t *) array_at(rectangles, j))) { + array_push_back(overlap, array_take(rectangles, j--)); + } + } + /* add the overlapping detections */ + int count = array_length(overlap); + for (int i = 0; i < count; i++) { + rectangle_t *overlap_rect = (rectangle_t *) array_pop_back(overlap); + rectangle_add(rect, overlap_rect); + xfree(overlap_rect); + } + /* average the overlapping detections */ + rectangle_div(rect, count + 1); + array_push_back(objects, rect); + } + array_free(rectangles); + array_free(overlap); + return objects; +} + +// Expands a bounding box with a point. +// After adding all points sub x from w and y from h. +void rectangle_expand(rectangle_t *r, int x, int y) { + if (x < r->x) { + 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/components/3rd_party/omv/omv/imlib/selective_search.c b/components/3rd_party/omv/omv/imlib/selective_search.c new file mode 100644 index 00000000..f005c608 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/selective_search.c @@ -0,0 +1,451 @@ +/* + * 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; i < elements; ++i) { + uni->elts[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)); +} + +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; i < num_vertices; i++) { + threshold[i] = THRESHOLD(1, c); + } + + for (int i = 0; i < num_edges; i++) { + edge *pedge = edges + i; + int a = universe_find(u, pedge->a); + 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. + if (threshold) 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; y < dst->h; y++) { + int sy = (y * y_ratio) >> 16; + for (int x = 0; x < dst->w; 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 < height; y++) { + for (int x = 0; x < width; x++) { + if (x < width - 1) { + edges[num].a = y * width + x; + edges[num].b = y * width + (x + 1); + edges[num].w = diff(img, x, y, x + 1, y); + num++; + } + + if (y < height - 1) { + edges[num].a = y * width + x; + edges[num].b = (y + 1) * width + x; + edges[num].w = diff(img, x, y, x, y + 1); + num++; + } + + if ((x < width - 1) && (y < height - 1)) { + 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++; + } + + if ((x < width - 1) && (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; i < num; i++) { + int a = universe_find(u, edges[i].a); + int b = universe_find(u, edges[i].b); + if ((a != b) && ((universe_size(u, a) < min_size) || (universe_size(u, b) < min_size))) { + universe_join(u, a, b); + } + } + + // Free graph edges + if (edges) fb_free(edges); + + int num_ccs = universe_num_sets(u); + region *regions = (region *) fb_alloc(num_ccs * sizeof(region), FB_ALLOC_NO_HINT); + for (i = 0; i < num_ccs; i++) { + regions[i].x = width; + regions[i].w = 0; + regions[i].y = height; + regions[i].h = 0; + } + + int next_component = 0; + int *counts = (int *) fb_alloc0(num_ccs * sizeof(int), FB_ALLOC_NO_HINT); + int *components = (int *) fb_alloc(num_ccs * sizeof(int), FB_ALLOC_NO_HINT); + float *histogram = (float *) fb_alloc0(num_ccs * sizeof(float) * 75, FB_ALLOC_NO_HINT); + + // Calc histograms + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int component_id = -1; + int comp = universe_find(u, y * width + x); + for (i = 0; i < next_component; i++) { + if (components[i] == comp) { + component_id = i; + break; + } + } + if (i == next_component) { + components[next_component] = comp; + component_id = next_component; + ++next_component; + } + universe_set_id(u, y * width + x, component_id); + region *r = regions + component_id; + r->y = 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 < num_ccs; i++) { + float max_val = 0; + for (j = 0; j < 75; j++) { + max_val = max(max_val, histogram[75 * i + j]); + } + for (j = 0; j < 75; j++) { + histogram[75 * i + j] /= max_val; + } + } + + uint8_t *adjacency = (uint8_t *) fb_alloc0(num_ccs * num_ccs * sizeof(uint8_t), FB_ALLOC_NO_HINT); + for (int y = 0; y < height - 1; ++y) { + for (int x = 0; x < width - 1; ++x) { + int component1 = universe_get_id(u, y * width + x); + int component2 = universe_get_id(u, y * width + x + 1); + int component3 = universe_get_id(u, y * width + x + width); + + if (component1 != component2) { + adjacency[component1 * num_ccs + component2] = 1; + adjacency[component2 * num_ccs + component1] = 1; + } + + if (component1 != component3) { + adjacency[component1 * num_ccs + component3] = 1; + adjacency[component3 * num_ccs + component1] = 1; + } + } + } + + int size = height * width; + float *similarity_table = (float *) fb_alloc(num_ccs * num_ccs * sizeof(float), FB_ALLOC_NO_HINT); + for (i = 0; i < num_ccs; ++i) { + for (j = i + 1; j < num_ccs; ++j) { + float color_sim = a1 * color_similarity(histogram + 75 * i, histogram + 75 * j); + float size_sim = a2 * size_similarity(counts[i], counts[j], size); + float fill_sim = a3 * fill_similarity(regions + i, regions + j, counts[i], counts[j], size); + float similarity = color_sim + size_sim + fill_sim; + similarity_table[i * num_ccs + j] = similarity; + similarity_table[j * num_ccs + i] = similarity; + } + } + + int remaining = num_ccs; + while (remaining > 1) { + int best_i = -1; + int best_j = -1; + float best_similarity = 0; + for (i = 0; i < num_ccs; i++) { + for (j = i + 1; j < num_ccs; j++) { + if (adjacency[i * num_ccs + j] == 0) { + continue; + } + float similarity = similarity_table[i * num_ccs + j]; + if (similarity > 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; i < array_length(proposals); i++) { + rectangle_t *r = array_at(proposals, i); + if (regions[best_i].x == r->x && 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; i < num_ccs; i++) { + adjacency[best_i * num_ccs + i] |= adjacency[best_j * num_ccs + i]; + adjacency[i * num_ccs + best_i] |= adjacency[i * num_ccs + best_j]; + adjacency[best_j * num_ccs + i] = adjacency[i * num_ccs + best_j] = 0; + } + adjacency[best_i * num_ccs + best_i] = 0; + + for (i = 0; i < num_ccs; i++) { + if (adjacency[best_i * num_ccs + i] == 0) { + continue; + } + float color_sim = a1 * color_similarity(histogram + 75 * i, histogram + 75 * best_i); + float size_sim = a2 * size_similarity(counts[i], counts[best_i], size); + float fill_sim = a3 * fill_similarity(regions + i, regions + best_i, counts[i], counts[best_i], size); + float similarity = color_sim + size_sim + fill_sim; + similarity_table[i * num_ccs + best_i] = similarity; + similarity_table[best_i * num_ccs + i] = similarity; + } + --remaining; + } + + for (int i = 0; i < array_length(proposals); i++) { + rectangle_t *r = array_at(proposals, i); + r->w = 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/components/3rd_party/omv/omv/imlib/sincos_tab.c b/components/3rd_party/omv/omv/imlib/sincos_tab.c new file mode 100644 index 00000000..918e913b --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/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/components/3rd_party/omv/omv/imlib/stats.c b/components/3rd_party/omv/omv/imlib/stats.c new file mode 100644 index 00000000..646b1162 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/stats.c @@ -0,0 +1,1460 @@ +/* + * 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; + } + 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; + + if (state.sum2Buckets) fb_free(state.sum2Buckets); + if (state.sum2BucketsOfY) fb_free(state.sum2BucketsOfY); + if (state.sum2BucketsOfX) fb_free(state.sum2BucketsOfX); + if (state.sumBucketsOfY) fb_free(state.sumBucketsOfY); + if (state.sumBucketsOfX) 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)]++; + } + } + } 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)]++; + } + } + } + } 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)]++; + 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)]++; + 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)]++; + } + } + } 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)]++; + } + } + } + } 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)]++; + 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)]++; + 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)]++; + ((uint32_t *) out->ABins)[fast_roundf((COLOR_RGB565_TO_A(pixel) - COLOR_A_MIN) * a_mult)]++; + ((uint32_t *) out->BBins)[fast_roundf((COLOR_RGB565_TO_B(pixel) - COLOR_B_MIN) * b_mult)]++; + } + } + } 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); + int 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)]++; + ((uint32_t *) out->ABins)[fast_roundf((COLOR_RGB565_TO_A(pixel) - COLOR_A_MIN) * a_mult)]++; + ((uint32_t *) out->BBins)[fast_roundf((COLOR_RGB565_TO_B(pixel) - COLOR_B_MIN) * b_mult)]++; + } + } + } + } 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)]++; + ((uint32_t *) out->ABins)[fast_roundf((COLOR_RGB565_TO_A(pixel) - COLOR_A_MIN) * a_mult)]++; + ((uint32_t *) out->BBins)[fast_roundf((COLOR_RGB565_TO_B(pixel) - COLOR_B_MIN) * b_mult)]++; + 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); + int 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)]++; + ((uint32_t *) out->ABins)[fast_roundf((COLOR_RGB565_TO_A(pixel) - COLOR_A_MIN) * a_mult)]++; + ((uint32_t *) out->BBins)[fast_roundf((COLOR_RGB565_TO_B(pixel) - COLOR_B_MIN) * b_mult)]++; + 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++) { + pixel_rgb_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + pixel_rgb_t 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++) { + pixel_rgb_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++) { + pixel_rgb_t 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++) { + pixel_rgb_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + pixel_rgb_t 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++) { + pixel_rgb_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++) { + pixel_rgb_t 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; + } + 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; + } + 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) { + pixel_rgb_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); + int *y_histogram = fb_alloc0(ptr->h * sizeof(int), FB_ALLOC_NO_HINT); + long long *x_delta_histogram = fb_alloc0((2 * ptr->w) * sizeof(long long), FB_ALLOC_NO_HINT); + long long *y_delta_histogram = fb_alloc0((2 * ptr->h) * sizeof(long long), FB_ALLOC_NO_HINT); + + 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) { + pixel_rgb_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]; + // Note we allocated 1 extra above so we can do ptr->w instead of (ptr->w-1). + x_delta_histogram[p0->x - p1->x + ptr->w]++; + // Note we allocated 1 extra above so we can do ptr->h instead of (ptr->h-1). + y_delta_histogram[p0->y - p1->y + ptr->h]++; + } + } + + 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)); + } + } + } + } + + if (points) fb_free(points); // points + if (y_delta_histogram) fb_free(y_delta_histogram); // y_delta_histogram + if (x_delta_histogram) fb_free(x_delta_histogram); // x_delta_histogram + if (y_histogram) fb_free(y_histogram); // y_histogram + if (x_histogram) fb_free(x_histogram); // x_histogram + } + + return result; +} diff --git a/components/3rd_party/omv/omv/imlib/stereo.c b/components/3rd_party/omv/omv/imlib/stereo.c new file mode 100644 index 00000000..d6c995bc --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/stereo.c @@ -0,0 +1,237 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2022 Ibrahim Abdelkader + * Copyright (c) 2013-2022 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Stero Image Disparity + */ +#include "imlib.h" + +#ifdef IMLIB_ENABLE_STEREO_DISPARITY + +#define BLOCK_W 4 +#define BLOCK_H 4 + +#define BLOCK_W_L (((BLOCK_W) / 2) - 1) +#define BLOCK_W_R ((BLOCK_W) / 2) +#define BLOCK_H_U (((BLOCK_H) / 2) - 1) +#define BLOCK_H_D ((BLOCK_H) / 2) + +#define BLOCK_SIZE ((BLOCK_W) *(BLOCK_H)) + +static inline void shift(uint8_t *data, image_t *img, int x, int y) { +#if (defined(ARM_MATH_CM7) || defined(ARM_MATH_CM4)) && (!(BLOCK_SIZE % 4)) + int x_2 = x + BLOCK_W_R; + uint32_t *data_32 = (uint32_t *) data; + + #if BLOCK_SIZE == 4 + uint32_t temp = __UXTB16_RORn(*data_32, 8); + temp |= IMAGE_GET_GRAYSCALE_PIXEL(img, x_2, y) << 8; + temp |= IMAGE_GET_GRAYSCALE_PIXEL(img, x_2, y + 1) << 24; + *data_32 = temp; + #elif BLOCK_SIZE == 16 + data_32[0] = (data_32[0] >> 8) | (IMAGE_GET_GRAYSCALE_PIXEL(img, x_2, y - 1) << 24); + data_32[1] = (data_32[1] >> 8) | (IMAGE_GET_GRAYSCALE_PIXEL(img, x_2, y) << 24); + data_32[2] = (data_32[2] >> 8) | (IMAGE_GET_GRAYSCALE_PIXEL(img, x_2, y + 1) << 24); + data_32[3] = (data_32[3] >> 8) | (IMAGE_GET_GRAYSCALE_PIXEL(img, x_2, y + 2) << 24); + #elif BLOCK_SIZE == 64 + data_32[0] = (data_32[0] >> 8) | (data_32[1] << 24); + data_32[1] = (data_32[1] >> 8) | (IMAGE_GET_GRAYSCALE_PIXEL(img, x_2, y - 3) << 24); + data_32[2] = (data_32[2] >> 8) | (data_32[3] << 24); + data_32[3] = (data_32[3] >> 8) | (IMAGE_GET_GRAYSCALE_PIXEL(img, x_2, y - 2) << 24); + data_32[4] = (data_32[4] >> 8) | (data_32[5] << 24); + data_32[5] = (data_32[5] >> 8) | (IMAGE_GET_GRAYSCALE_PIXEL(img, x_2, y - 1) << 24); + data_32[6] = (data_32[6] >> 8) | (data_32[7] << 24); + data_32[7] = (data_32[7] >> 8) | (IMAGE_GET_GRAYSCALE_PIXEL(img, x_2, y) << 24); + data_32[8] = (data_32[8] >> 8) | (data_32[9] << 24); + data_32[9] = (data_32[9] >> 8) | (IMAGE_GET_GRAYSCALE_PIXEL(img, x_2, y + 1) << 24); + data_32[10] = (data_32[10] >> 8) | (data_32[11] << 24); + data_32[11] = (data_32[11] >> 8) | (IMAGE_GET_GRAYSCALE_PIXEL(img, x_2, y + 2) << 24); + data_32[12] = (data_32[12] >> 8) | (data_32[13] << 24); + data_32[13] = (data_32[13] >> 8) | (IMAGE_GET_GRAYSCALE_PIXEL(img, x_2, y + 3) << 24); + data_32[14] = (data_32[14] >> 8) | (data_32[15] << 24); + data_32[15] = (data_32[15] >> 8) | (IMAGE_GET_GRAYSCALE_PIXEL(img, x_2, y + 4) << 24); + #else + for (int j = -BLOCK_H_U, last = (BLOCK_W / sizeof(uint32_t)) - 1; j <= BLOCK_H_D; j++, data_32 += last + 1) { + for (int i = 0; i < last; i++) { + data_32[i] = (data_32[i] >> 8) | (data_32[i + 1] << 24); + } + data_32[last] = (data_32[last] >> 8) | (IMAGE_GET_GRAYSCALE_PIXEL(img, x_2, y + j) << 24); + } + #endif +#else + for (int i = 0, j = -BLOCK_H_U; j <= BLOCK_H_D; j++) { + int y_p = y + j; + uint8_t *k_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y_p); + for (int k = -BLOCK_W_L; k <= BLOCK_W_R; k++) { + int x_p = x + k; + data[i++] = IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr, x_p); + } + } +#endif +} + +static inline uint32_t sad(uint8_t *data_l, uint8_t *data_r) { + uint32_t diff = 0; + +#if (defined(ARM_MATH_CM7) || defined(ARM_MATH_CM4)) && (!(BLOCK_SIZE % 4)) + uint32_t *data_l_32 = (uint32_t *) data_l; + uint32_t *data_r_32 = (uint32_t *) data_r; + + #if BLOCK_SIZE == 4 + diff = __USADA8(*data_l_32, *data_r_32, diff); + #elif BLOCK_SIZE == 16 + diff = __USADA8(data_l_32[0], data_r_32[0], diff); + diff = __USADA8(data_l_32[1], data_r_32[1], diff); + diff = __USADA8(data_l_32[2], data_r_32[2], diff); + diff = __USADA8(data_l_32[3], data_r_32[3], diff); + #elif BLOCK_SIZE == 64 + diff = __USADA8(data_l_32[0], data_r_32[0], diff); + diff = __USADA8(data_l_32[1], data_r_32[1], diff); + diff = __USADA8(data_l_32[2], data_r_32[2], diff); + diff = __USADA8(data_l_32[3], data_r_32[3], diff); + diff = __USADA8(data_l_32[4], data_r_32[4], diff); + diff = __USADA8(data_l_32[5], data_r_32[5], diff); + diff = __USADA8(data_l_32[6], data_r_32[6], diff); + diff = __USADA8(data_l_32[7], data_r_32[7], diff); + diff = __USADA8(data_l_32[8], data_r_32[8], diff); + diff = __USADA8(data_l_32[9], data_r_32[9], diff); + diff = __USADA8(data_l_32[10], data_r_32[10], diff); + diff = __USADA8(data_l_32[11], data_r_32[11], diff); + diff = __USADA8(data_l_32[12], data_r_32[12], diff); + diff = __USADA8(data_l_32[13], data_r_32[13], diff); + diff = __USADA8(data_l_32[14], data_r_32[14], diff); + diff = __USADA8(data_l_32[15], data_r_32[15], diff); + #else + for (int i = 0; i < (BLOCK_SIZE / sizeof(uint32_t)); i++) { + diff = __USADA8(data_l_32[i], data_r_32[i], diff); + } + #endif +#else + for (int i = 0; i < BLOCK_SIZE; i++) { + int d = data_l[i] - data_r[i]; + diff += abs(d); + } +#endif + + return diff; +} + +void imlib_stereo_disparity(image_t *img, bool reversed, int max_disparity, int threshold) { + int width_2 = img->w / 2, width_2_m_1 = width_2 - 1; + int height_1 = img->h, height_1_m_1 = height_1 - 1; + + int xl_offset = 0; + int xr_offset = width_2; + + if (reversed) { + xl_offset = xr_offset; + xr_offset = 0; + } + + float disparity_scale = COLOR_GRAYSCALE_MAX / max_disparity; + + image_t buf; + buf.w = width_2; + buf.h = BLOCK_H_D; + buf.pixfmt = img->pixfmt; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + break; + } + case PIXFORMAT_GRAYSCALE: { + buf.data = fb_alloc(IMAGE_GRAYSCALE_LINE_LEN_BYTES(&buf) * BLOCK_H_D, FB_ALLOC_NO_HINT); + uint8_t *data_l = fb_alloc(BLOCK_SIZE, FB_ALLOC_NO_HINT); + uint8_t *data_r = fb_alloc(BLOCK_SIZE, FB_ALLOC_NO_HINT); + + for (int y = 0, yy = height_1 - BLOCK_H_D; y < height_1; y++) { + for (int xl = 0, xx = width_2 - BLOCK_W_R; xl < width_2; xl++) { + if ((xl >= BLOCK_W_L) && (xl < xx) && (y >= BLOCK_H_U) && (y < yy)) { + // fast way + shift(data_l, img, xl + xl_offset, y); + } else { + // slow way + for (int i = 0, j = -BLOCK_H_U; j <= BLOCK_H_D; j++) { + int y_p = y + j; + int y = IM_MIN(IM_MAX(y_p, 0), height_1_m_1); + uint8_t *k_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + for (int k = -BLOCK_W_L; k <= BLOCK_W_R; k++) { + int x_p = xl + k; + int x = IM_MIN(IM_MAX(x_p, 0), width_2_m_1) + xl_offset; + data_l[i++] = IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr, x); + } + } + } + + uint32_t min_diff = UINT32_MAX; + int min_disparity = 0; + + for (int xr = xl, disparity = 0; + (xr < width_2) && (disparity <= max_disparity); xr++, disparity++) { + if (disparity && (xr >= BLOCK_W_L) && (xr < xx) && (y >= BLOCK_H_U) && (y < yy)) { + // fast way + shift(data_r, img, xr + xr_offset, y); + } else { + // slow way + for (int i = 0, j = -BLOCK_H_U; j <= BLOCK_H_D; j++) { + int y_p = y + j; + int y = IM_MIN(IM_MAX(y_p, 0), height_1_m_1); + uint8_t *k_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + for (int k = -BLOCK_W_L; k <= BLOCK_W_R; k++) { + int x_p = xr + k; + int x = IM_MIN(IM_MAX(x_p, 0), width_2_m_1) + xr_offset; + data_r[i++] = IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr, x); + } + } + } + + // Record the closest matching block in the scan line. + uint32_t diff = sad(data_l, data_r); + if (diff < min_diff) { + min_diff = diff; + min_disparity = disparity; + } + + if (min_diff <= threshold) { + break; + } + } + + uint8_t *buf_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, (y % BLOCK_H_D)); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, xl, fast_floorf(min_disparity * disparity_scale)); + } + + if (y >= BLOCK_H_U) { + // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, (y - BLOCK_H_U)) + xr_offset, + IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, ((y - BLOCK_H_U) % BLOCK_H_D)), + IMAGE_GRAYSCALE_LINE_LEN_BYTES(&buf)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(height_1 - BLOCK_H_U, 0); y < height_1; y++) { + memcpy(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y) + xr_offset, + IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, (y % BLOCK_H_D)), + IMAGE_GRAYSCALE_LINE_LEN_BYTES(&buf)); + } + + if (data_r) fb_free(data_r); // data_r + if (data_l) fb_free(data_l); // data_l + if (buf) fb_free(buf); // buf + break; + } + case PIXFORMAT_RGB565: { + break; + } + default: { + break; + } + } +} + +#endif // IMLIB_ENABLE_STEREO_DISPARITY diff --git a/components/3rd_party/omv/omv/imlib/template.c b/components/3rd_party/omv/omv/imlib/template.c new file mode 100644 index 00000000..7f54a74e --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/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; y < v + h; y++) { + for (int x = u; x < u + 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; + 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 < num_pts; i++) { + if (pts[i].x >= 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/components/3rd_party/omv/omv/imlib/xyz_tab.c b/components/3rd_party/omv/omv/imlib/xyz_tab.c new file mode 100644 index 00000000..bd45c5d5 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/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/components/3rd_party/omv/omv/imlib/yuv.c b/components/3rd_party/omv/omv/imlib/yuv.c new file mode 100644 index 00000000..3d83806d --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/yuv.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. + * + * 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; + } + 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; + } + } + + imlib_deyuv_line(0, src_w, y, row_ptr, dst->pixfmt, src); + } +} diff --git a/components/3rd_party/omv/omv/imlib/yuv_tab.c b/components/3rd_party/omv/omv/imlib/yuv_tab.c new file mode 100644 index 00000000..d5264801 --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/yuv_tab.c @@ -0,0 +1,16387 @@ +#include +const int8_t yuv_table[196608] = { + 0, 0, 0, 0, 4, 0, 1, 8, -1, 2, 12, -2, + 3, 16, -2, 4, 20, -3, 5, 24, -3, 6, 29, -4, + 7, 33, -5, 8, 37, -6, 9, 41, -6, 10, 45, -7, + 11, 49, -8, 12, 53, -8, 13, 57, -9, 14, 61, -10, + 15, 66, -10, 15, 70, -11, 16, 74, -12, 17, 78, -12, + 18, 82, -13, 19, 86, -14, 20, 90, -14, 21, 94, -15, + 22, 98, -16, 23, 103, -16, 24, 107, -17, 25, 111, -18, + 26, 115, -18, 27, 119, -19, 28, 123, -20, 29, 127, -20, + 2, -1, -1, 3, 2, -2, 4, 6, -2, 5, 11, -3, + 6, 15, -4, 7, 19, -5, 7, 23, -5, 8, 27, -6, + 9, 31, -7, 10, 35, -7, 11, 39, -8, 12, 43, -8, + 13, 48, -9, 14, 52, -10, 15, 56, -11, 16, 60, -11, + 17, 64, -12, 18, 68, -13, 19, 72, -13, 20, 76, -14, + 21, 81, -15, 22, 85, -15, 22, 89, -16, 23, 93, -17, + 24, 97, -17, 25, 101, -18, 26, 105, -19, 27, 109, -19, + 28, 113, -20, 29, 118, -21, 30, 122, -21, 31, 126, -22, + 4, -2, -3, 5, 1, -4, 6, 5, -4, 7, 9, -5, + 8, 13, -6, 9, 17, -6, 10, 21, -7, 11, 26, -8, + 12, 30, -8, 13, 34, -9, 14, 38, -10, 14, 42, -10, + 15, 46, -11, 16, 50, -12, 17, 54, -12, 18, 58, -13, + 19, 63, -14, 20, 67, -14, 21, 71, -15, 22, 75, -16, + 23, 79, -16, 24, 83, -17, 25, 87, -18, 26, 91, -18, + 27, 95, -19, 28, 100, -20, 29, 104, -20, 30, 108, -21, + 30, 112, -22, 31, 116, -22, 32, 120, -23, 33, 124, -24, + 7, -3, -5, 7, 0, -5, 8, 4, -6, 9, 8, -7, + 10, 12, -7, 11, 16, -8, 12, 20, -9, 13, 25, -9, + 14, 29, -10, 15, 33, -11, 16, 37, -11, 17, 41, -12, + 18, 45, -13, 19, 49, -13, 20, 53, -14, 21, 57, -15, + 22, 62, -15, 23, 66, -16, 23, 70, -17, 24, 74, -17, + 25, 78, -18, 26, 82, -19, 27, 86, -19, 28, 90, -20, + 29, 94, -21, 30, 99, -21, 31, 103, -22, 32, 107, -23, + 33, 111, -23, 34, 115, -24, 35, 119, -25, 36, 123, -25, + 9, -5, -6, 10, -1, -7, 11, 2, -8, 12, 7, -8, + 13, 11, -9, 14, 15, -10, 14, 19, -10, 16, 23, -11, + 16, 27, -12, 17, 31, -12, 18, 35, -13, 19, 39, -14, + 20, 44, -14, 21, 48, -15, 22, 52, -16, 23, 56, -16, + 24, 60, -17, 25, 64, -18, 26, 68, -18, 27, 72, -19, + 28, 77, -20, 29, 81, -20, 30, 85, -21, 30, 89, -22, + 31, 93, -22, 32, 97, -23, 33, 101, -24, 34, 105, -24, + 35, 109, -25, 36, 114, -26, 37, 118, -26, 38, 122, -27, + 11, -6, -8, 12, -2, -9, 13, 1, -9, 14, 5, -10, + 15, 9, -11, 16, 13, -11, 17, 17, -12, 18, 22, -13, + 19, 26, -13, 20, 30, -14, 21, 34, -15, 22, 38, -15, + 23, 42, -16, 23, 46, -17, 24, 50, -17, 25, 54, -18, + 26, 59, -19, 27, 63, -19, 28, 67, -20, 29, 71, -21, + 30, 75, -21, 31, 79, -22, 32, 83, -23, 33, 87, -23, + 34, 91, -24, 35, 96, -25, 36, 100, -25, 37, 104, -26, + 37, 108, -27, 38, 112, -27, 39, 116, -28, 40, 120, -29, + 14, -7, -10, 15, -3, -10, 15, 0, -11, 16, 4, -12, + 17, 8, -12, 18, 12, -13, 19, 16, -14, 20, 21, -14, + 21, 25, -15, 22, 29, -16, 23, 33, -16, 24, 37, -17, + 25, 41, -18, 26, 45, -18, 27, 49, -19, 28, 53, -20, + 29, 58, -20, 30, 62, -21, 30, 66, -22, 31, 70, -22, + 32, 74, -23, 33, 78, -24, 34, 82, -24, 35, 86, -25, + 36, 90, -26, 37, 95, -26, 38, 99, -27, 39, 103, -28, + 40, 107, -28, 41, 111, -29, 42, 115, -30, 43, 119, -30, + 16, -9, -11, 17, -5, -12, 18, -1, -13, 19, 3, -13, + 20, 7, -14, 21, 11, -15, 22, 15, -15, 23, 19, -16, + 23, 23, -17, 24, 27, -17, 25, 31, -18, 26, 35, -19, + 27, 40, -19, 28, 44, -20, 29, 48, -21, 30, 52, -21, + 31, 56, -22, 32, 60, -23, 33, 64, -23, 34, 68, -24, + 35, 73, -25, 36, 77, -25, 37, 81, -26, 37, 85, -27, + 38, 89, -27, 39, 93, -28, 40, 97, -29, 41, 101, -29, + 42, 105, -30, 43, 110, -31, 44, 114, -31, 45, 118, -32, + 18, -10, -13, 19, -6, -14, 20, -2, -14, 21, 1, -15, + 22, 5, -16, 23, 9, -16, 24, 13, -17, 25, 18, -18, + 26, 22, -18, 27, 26, -19, 28, 30, -20, 29, 34, -20, + 30, 38, -21, 30, 42, -22, 31, 46, -22, 32, 50, -23, + 33, 55, -24, 34, 59, -24, 35, 63, -25, 36, 67, -26, + 37, 71, -26, 38, 75, -27, 39, 79, -28, 40, 83, -28, + 41, 87, -29, 42, 92, -30, 43, 96, -30, 44, 100, -31, + 45, 104, -32, 46, 108, -32, 46, 112, -33, 47, 116, -34, + 21, -11, -15, 22, -7, -15, 22, -3, -16, 23, 0, -17, + 24, 4, -17, 25, 8, -18, 26, 12, -19, 27, 17, -19, + 28, 21, -20, 29, 25, -21, 30, 29, -21, 31, 33, -22, + 32, 37, -23, 33, 41, -23, 34, 45, -24, 35, 49, -25, + 36, 54, -25, 37, 58, -26, 38, 62, -27, 38, 66, -27, + 39, 70, -28, 40, 74, -29, 41, 78, -29, 42, 82, -30, + 43, 86, -31, 44, 91, -31, 45, 95, -32, 46, 99, -33, + 47, 103, -33, 48, 107, -34, 49, 111, -35, 50, 115, -35, + 23, -13, -16, 24, -9, -17, 25, -5, -18, 26, 0, -18, + 27, 3, -19, 28, 7, -20, 29, 11, -20, 30, 15, -21, + 31, 19, -22, 31, 23, -22, 32, 27, -23, 33, 31, -24, + 34, 36, -24, 35, 40, -25, 36, 44, -26, 37, 48, -26, + 38, 52, -27, 39, 56, -28, 40, 60, -28, 41, 64, -29, + 42, 69, -30, 43, 73, -30, 44, 77, -31, 45, 81, -32, + 45, 85, -32, 46, 89, -33, 47, 93, -34, 48, 97, -34, + 49, 101, -35, 50, 106, -36, 51, 110, -36, 52, 114, -37, + 26, -14, -18, 27, -10, -19, 28, -6, -20, 29, -2, -20, + 30, 1, -21, 31, 5, -22, 32, 9, -22, 33, 14, -23, + 33, 18, -24, 34, 22, -24, 35, 26, -25, 36, 30, -26, + 37, 34, -26, 38, 38, -27, 39, 42, -28, 40, 46, -28, + 41, 51, -29, 42, 55, -30, 43, 59, -30, 44, 63, -31, + 45, 67, -32, 46, 71, -32, 47, 75, -33, 47, 79, -34, + 48, 83, -34, 49, 88, -35, 50, 92, -36, 51, 96, -36, + 52, 100, -37, 53, 104, -38, 54, 108, -38, 55, 112, -39, + 28, -16, -20, 29, -12, -21, 30, -8, -21, 31, -3, -22, + 32, 0, -23, 33, 4, -23, 34, 8, -24, 35, 12, -25, + 36, 16, -25, 37, 20, -26, 38, 24, -27, 39, 28, -27, + 40, 33, -28, 40, 37, -29, 41, 41, -29, 42, 45, -30, + 43, 49, -31, 44, 53, -31, 45, 57, -32, 46, 61, -33, + 47, 66, -33, 48, 70, -34, 49, 74, -35, 50, 78, -35, + 51, 82, -36, 52, 86, -37, 53, 90, -37, 54, 94, -38, + 54, 98, -39, 56, 103, -39, 56, 107, -40, 57, 111, -41, + 31, -17, -22, 32, -13, -22, 32, -9, -23, 33, -5, -24, + 34, -1, -24, 35, 2, -25, 36, 6, -26, 37, 11, -26, + 38, 15, -27, 39, 19, -28, 40, 23, -28, 41, 27, -29, + 42, 31, -30, 43, 35, -30, 44, 39, -31, 45, 43, -32, + 46, 48, -32, 47, 52, -33, 47, 56, -34, 48, 60, -34, + 49, 64, -35, 50, 68, -36, 51, 72, -36, 52, 76, -37, + 53, 80, -38, 54, 85, -38, 55, 89, -39, 56, 93, -40, + 57, 97, -40, 58, 101, -41, 59, 105, -42, 60, 109, -42, + 33, -18, -23, 34, -14, -24, 35, -10, -25, 36, -6, -25, + 37, -2, -26, 38, 1, -27, 39, 5, -27, 40, 10, -28, + 40, 14, -29, 41, 18, -29, 42, 22, -30, 43, 26, -31, + 44, 30, -31, 45, 34, -32, 46, 38, -33, 47, 42, -33, + 48, 47, -34, 49, 51, -35, 50, 55, -35, 51, 59, -36, + 52, 63, -37, 53, 67, -37, 54, 71, -38, 55, 75, -39, + 55, 79, -39, 56, 84, -40, 57, 88, -41, 58, 92, -41, + 59, 96, -42, 60, 100, -43, 61, 104, -43, 62, 108, -44, + 35, -20, -25, 36, -16, -26, 37, -12, -26, 38, -7, -27, + 39, -3, -28, 40, 0, -28, 41, 4, -29, 42, 8, -30, + 43, 12, -30, 44, 16, -31, 45, 20, -32, 46, 24, -32, + 47, 29, -33, 48, 33, -34, 48, 37, -34, 49, 41, -35, + 50, 45, -36, 51, 49, -36, 52, 53, -37, 53, 57, -38, + 54, 62, -38, 55, 66, -39, 56, 70, -40, 57, 74, -40, + 58, 78, -41, 59, 82, -42, 60, 86, -42, 61, 90, -43, + 62, 94, -44, 63, 99, -44, 63, 103, -45, 64, 107, -46, + 38, -21, -27, 39, -17, -27, 39, -13, -28, 41, -9, -29, + 41, -5, -29, 42, -1, -30, 43, 2, -31, 44, 7, -31, + 45, 11, -32, 46, 15, -33, 47, 19, -33, 48, 23, -34, + 49, 27, -35, 50, 31, -35, 51, 35, -36, 52, 39, -37, + 53, 44, -37, 54, 48, -38, 55, 52, -39, 55, 56, -39, + 56, 60, -40, 57, 64, -41, 58, 68, -41, 59, 72, -42, + 60, 76, -43, 61, 81, -43, 62, 85, -44, 63, 89, -45, + 64, 93, -45, 65, 97, -46, 66, 101, -47, 67, 105, -47, + 40, -22, -28, 41, -18, -29, 42, -14, -30, 43, -10, -30, + 44, -6, -31, 45, -2, -32, 46, 1, -32, 47, 6, -33, + 48, 10, -34, 48, 14, -34, 49, 18, -35, 50, 22, -36, + 51, 26, -36, 52, 30, -37, 53, 34, -38, 54, 38, -38, + 55, 43, -39, 56, 47, -40, 57, 51, -40, 58, 55, -41, + 59, 59, -42, 60, 63, -42, 61, 67, -43, 62, 71, -44, + 62, 75, -44, 63, 80, -45, 64, 84, -46, 65, 88, -46, + 66, 92, -47, 67, 96, -48, 68, 100, -48, 69, 104, -49, + 42, -24, -30, 43, -20, -31, 44, -16, -31, 45, -11, -32, + 46, -7, -33, 47, -3, -33, 48, 0, -34, 49, 4, -35, + 50, 8, -35, 51, 12, -36, 52, 16, -37, 53, 20, -37, + 54, 25, -38, 55, 29, -39, 55, 33, -39, 56, 37, -40, + 57, 41, -41, 58, 45, -41, 59, 49, -42, 60, 53, -43, + 61, 58, -43, 62, 62, -44, 63, 66, -45, 64, 70, -45, + 65, 74, -46, 66, 78, -47, 67, 82, -47, 68, 86, -48, + 69, 90, -49, 70, 95, -49, 71, 99, -50, 71, 103, -51, + 45, -25, -32, 46, -21, -32, 47, -17, -33, 48, -13, -34, + 48, -9, -34, 49, -5, -35, 50, -1, -36, 51, 3, -36, + 52, 7, -37, 53, 11, -38, 54, 15, -38, 55, 19, -39, + 56, 23, -40, 57, 27, -40, 58, 31, -41, 59, 35, -42, + 60, 40, -42, 61, 44, -43, 62, 48, -44, 62, 52, -44, + 64, 56, -45, 64, 60, -46, 65, 64, -46, 66, 68, -47, + 67, 72, -48, 68, 77, -48, 69, 81, -49, 70, 85, -50, + 71, 89, -50, 72, 93, -51, 73, 97, -52, 74, 101, -52, + 47, -26, -33, 48, -22, -34, 49, -18, -35, 50, -14, -35, + 51, -10, -36, 52, -6, -37, 53, -2, -37, 54, 2, -38, + 55, 6, -39, 55, 10, -39, 56, 14, -40, 57, 18, -41, + 58, 22, -41, 59, 26, -42, 60, 30, -43, 61, 34, -43, + 62, 39, -44, 63, 43, -45, 64, 47, -45, 65, 51, -46, + 66, 55, -47, 67, 59, -47, 68, 63, -48, 69, 67, -49, + 70, 71, -49, 71, 76, -50, 71, 80, -51, 72, 84, -51, + 73, 88, -52, 74, 92, -53, 75, 96, -53, 76, 100, -54, + 49, -28, -35, 50, -24, -36, 51, -20, -36, 52, -15, -37, + 53, -11, -38, 54, -7, -38, 55, -3, -39, 56, 0, -40, + 57, 4, -40, 58, 8, -41, 59, 12, -42, 60, 16, -42, + 61, 21, -43, 62, 25, -44, 63, 29, -44, 63, 33, -45, + 64, 37, -46, 65, 41, -46, 66, 45, -47, 67, 49, -48, + 68, 54, -49, 69, 58, -49, 70, 62, -50, 71, 66, -50, + 72, 70, -51, 73, 74, -52, 74, 78, -52, 75, 82, -53, + 76, 86, -54, 77, 91, -55, 78, 95, -55, 78, 99, -56, + 52, -29, -37, 53, -25, -37, 54, -21, -38, 55, -16, -39, + 56, -12, -39, 56, -8, -40, 57, -4, -41, 58, 0, -41, + 59, 3, -42, 60, 7, -43, 61, 11, -43, 62, 15, -44, + 63, 20, -45, 64, 24, -45, 65, 28, -46, 66, 32, -47, + 67, 36, -47, 68, 40, -48, 69, 44, -49, 70, 48, -49, + 71, 53, -50, 71, 57, -51, 72, 61, -51, 73, 65, -52, + 74, 69, -53, 75, 73, -54, 76, 77, -54, 77, 81, -55, + 78, 85, -55, 79, 90, -56, 80, 94, -57, 81, 98, -57, + 54, -30, -38, 55, -26, -39, 56, -22, -40, 57, -18, -40, + 58, -14, -41, 59, -10, -42, 60, -6, -42, 61, -1, -43, + 62, 2, -44, 63, 6, -44, 63, 10, -45, 64, 14, -46, + 65, 18, -46, 66, 22, -47, 67, 26, -48, 68, 30, -48, + 69, 35, -49, 70, 39, -50, 71, 43, -50, 72, 47, -51, + 73, 51, -52, 74, 55, -53, 75, 59, -53, 76, 63, -54, + 77, 67, -54, 78, 72, -55, 78, 76, -56, 79, 80, -56, + 80, 84, -57, 81, 88, -58, 82, 92, -59, 83, 96, -59, + 56, -32, -40, 57, -28, -41, 58, -24, -41, 59, -19, -42, + 60, -15, -43, 61, -11, -43, 62, -7, -44, 63, -3, -45, + 64, 0, -45, 65, 4, -46, 66, 8, -47, 67, 12, -47, + 68, 17, -48, 69, 21, -49, 70, 25, -49, 70, 29, -50, + 71, 33, -51, 72, 37, -51, 73, 41, -52, 74, 45, -53, + 75, 50, -54, 76, 54, -54, 77, 58, -55, 78, 62, -55, + 79, 66, -56, 80, 70, -57, 81, 74, -58, 82, 78, -58, + 83, 82, -59, 84, 87, -60, 85, 91, -60, 86, 95, -61, + 59, -33, -42, 60, -29, -42, 61, -25, -43, 62, -20, -44, + 63, -16, -44, 63, -12, -45, 64, -8, -46, 65, -4, -47, + 66, 0, -47, 67, 3, -48, 68, 7, -48, 69, 11, -49, + 70, 16, -50, 71, 20, -50, 72, 24, -51, 73, 28, -52, + 74, 32, -53, 75, 36, -53, 76, 40, -54, 77, 44, -54, + 78, 49, -55, 79, 53, -56, 79, 57, -57, 80, 61, -57, + 81, 65, -58, 82, 69, -59, 83, 73, -59, 84, 77, -60, + 85, 81, -60, 86, 86, -61, 87, 90, -62, 88, 94, -63, + 61, -34, -43, 62, -30, -44, 63, -26, -45, 64, -22, -45, + 65, -18, -46, 66, -14, -47, 67, -10, -47, 68, -5, -48, + 69, -1, -49, 70, 2, -49, 70, 6, -50, 71, 10, -51, + 72, 14, -52, 73, 18, -52, 74, 22, -53, 75, 26, -53, + 76, 31, -54, 77, 35, -55, 78, 39, -55, 79, 43, -56, + 80, 47, -57, 81, 51, -58, 82, 55, -58, 83, 59, -59, + 84, 63, -59, 85, 68, -60, 86, 72, -61, 86, 76, -62, + 87, 80, -62, 88, 84, -63, 89, 88, -64, 90, 92, -64, + 63, -36, -45, 64, -32, -46, 65, -28, -46, 66, -23, -47, + 67, -19, -48, 68, -15, -48, 69, -11, -49, 70, -7, -50, + 71, -3, -51, 72, 0, -51, 73, 4, -52, 74, 8, -52, + 75, 13, -53, 76, 17, -54, 77, 21, -54, 78, 25, -55, + 79, 29, -56, 79, 33, -57, 80, 37, -57, 81, 41, -58, + 82, 46, -59, 83, 50, -59, 84, 54, -60, 85, 58, -61, + 86, 62, -61, 87, 66, -62, 88, 70, -63, 89, 74, -63, + 90, 78, -64, 91, 83, -65, 92, 87, -65, 93, 91, -66, + 66, -37, -47, 67, -33, -47, 68, -29, -48, 69, -24, -49, + 70, -20, -49, 71, -16, -50, 71, -12, -51, 72, -8, -52, + 73, -4, -52, 74, 0, -53, 75, 3, -53, 76, 7, -54, + 77, 12, -55, 78, 16, -56, 79, 20, -56, 80, 24, -57, + 81, 28, -58, 82, 32, -58, 83, 36, -59, 84, 40, -59, + 85, 45, -60, 86, 49, -61, 86, 53, -62, 87, 57, -62, + 88, 61, -63, 89, 65, -64, 90, 69, -64, 91, 73, -65, + 92, 77, -66, 93, 82, -66, 94, 86, -67, 95, 90, -68, + 68, -38, -48, 69, -34, -49, 70, -30, -50, 71, -26, -51, + 72, -22, -51, 73, -18, -52, 74, -14, -52, 75, -9, -53, + 76, -5, -54, 77, -1, -55, 78, 2, -55, 78, 6, -56, + 79, 10, -57, 80, 14, -57, 81, 18, -58, 82, 22, -58, + 83, 27, -59, 84, 31, -60, 85, 35, -61, 86, 39, -61, + 87, 43, -62, 88, 47, -63, 89, 51, -63, 90, 55, -64, + 91, 59, -65, 92, 64, -65, 93, 68, -66, 93, 72, -67, + 94, 76, -67, 95, 80, -68, 96, 84, -69, 97, 88, -69, + 71, -40, -50, 71, -36, -51, 72, -32, -51, 73, -27, -52, + 74, -23, -53, 75, -19, -53, 76, -15, -54, 77, -11, -55, + 78, -7, -56, 79, -3, -56, 80, 0, -57, 81, 4, -57, + 82, 9, -58, 83, 13, -59, 84, 17, -60, 85, 21, -60, + 86, 25, -61, 86, 29, -62, 87, 33, -62, 88, 37, -63, + 89, 42, -64, 90, 46, -64, 91, 50, -65, 92, 54, -66, + 93, 58, -66, 94, 62, -67, 95, 66, -68, 96, 70, -68, + 97, 74, -69, 98, 79, -70, 99, 83, -70, 100, 87, -71, + 73, -41, -52, 74, -37, -52, 75, -33, -53, 76, -28, -54, + 77, -24, -55, 78, -20, -55, 78, -16, -56, 79, -12, -57, + 80, -8, -57, 81, -4, -58, 82, 0, -59, 83, 3, -59, + 84, 8, -60, 85, 12, -61, 86, 16, -61, 87, 20, -62, + 88, 24, -63, 89, 28, -63, 90, 32, -64, 91, 36, -65, + 92, 41, -65, 93, 45, -66, 94, 49, -67, 94, 53, -67, + 95, 57, -68, 96, 61, -69, 97, 65, -69, 98, 69, -70, + 99, 73, -71, 100, 78, -71, 101, 82, -72, 102, 86, -73, + 76, -43, -54, 77, -39, -55, 78, -35, -55, 79, -30, -56, + 80, -26, -57, 80, -22, -57, 81, -18, -58, 82, -14, -59, + 83, -10, -59, 84, -6, -60, 85, -2, -61, 86, 1, -61, + 87, 6, -62, 88, 10, -63, 89, 14, -63, 90, 18, -64, + 91, 22, -65, 92, 26, -65, 93, 30, -66, 94, 34, -67, + 95, 39, -67, 96, 43, -68, 96, 47, -69, 97, 51, -69, + 98, 55, -70, 99, 59, -71, 100, 63, -71, 101, 67, -72, + 102, 71, -73, 103, 76, -73, 104, 80, -74, 105, 84, -75, + 78, -44, -56, 79, -40, -56, 80, -36, -57, 81, -31, -58, + 82, -27, -58, 83, -23, -59, 84, -19, -60, 85, -15, -60, + 86, -11, -61, 87, -7, -62, 88, -3, -62, 88, 0, -63, + 89, 5, -64, 90, 9, -64, 91, 13, -65, 92, 17, -66, + 93, 21, -66, 94, 25, -67, 95, 29, -68, 96, 33, -68, + 97, 38, -69, 98, 42, -70, 99, 46, -70, 100, 50, -71, + 101, 54, -72, 102, 58, -72, 103, 62, -73, 103, 66, -74, + 104, 70, -74, 105, 75, -75, 106, 79, -76, 107, 83, -76, + 81, -45, -57, 81, -41, -58, 82, -37, -59, 83, -33, -59, + 84, -29, -60, 85, -25, -61, 86, -21, -61, 87, -16, -62, + 88, -12, -63, 89, -8, -63, 90, -4, -64, 91, 0, -65, + 92, 3, -65, 93, 7, -66, 94, 11, -67, 95, 15, -67, + 96, 20, -68, 96, 24, -69, 97, 28, -69, 98, 32, -70, + 99, 36, -71, 100, 40, -71, 101, 44, -72, 102, 48, -73, + 103, 52, -73, 104, 57, -74, 105, 61, -75, 106, 65, -75, + 107, 69, -76, 108, 73, -77, 109, 77, -77, 110, 81, -78, + 83, -47, -59, 84, -43, -60, 85, -39, -60, 86, -34, -61, + 87, -30, -62, 88, -26, -62, 88, -22, -63, 89, -18, -64, + 90, -14, -64, 91, -10, -65, 92, -6, -66, 93, -2, -66, + 94, 2, -67, 95, 6, -68, 96, 10, -68, 97, 14, -69, + 98, 18, -70, 99, 22, -70, 100, 26, -71, 101, 30, -72, + 102, 35, -72, 103, 39, -73, 103, 43, -74, 104, 47, -74, + 105, 51, -75, 106, 55, -76, 107, 59, -76, 108, 63, -77, + 109, 67, -78, 110, 72, -78, 111, 76, -79, 112, 80, -80, + 85, -48, -61, 86, -44, -61, 87, -40, -62, 88, -35, -63, + 89, -31, -63, 90, -27, -64, 91, -23, -65, 92, -19, -65, + 93, -15, -66, 94, -11, -67, 95, -7, -67, 95, -3, -68, + 96, 1, -69, 97, 5, -69, 98, 9, -70, 99, 13, -71, + 100, 17, -71, 101, 21, -72, 102, 25, -73, 103, 29, -73, + 104, 34, -74, 105, 38, -75, 106, 42, -75, 107, 46, -76, + 108, 50, -77, 109, 54, -77, 110, 58, -78, 111, 62, -79, + 111, 66, -79, 112, 71, -80, 113, 75, -81, 114, 79, -81, + 88, -49, -62, 88, -45, -63, 89, -41, -64, 90, -37, -64, + 91, -33, -65, 92, -29, -66, 93, -25, -66, 94, -20, -67, + 95, -16, -68, 96, -12, -68, 97, -8, -69, 98, -4, -70, + 99, 0, -70, 100, 3, -71, 101, 7, -72, 102, 11, -72, + 103, 16, -73, 104, 20, -74, 104, 24, -74, 105, 28, -75, + 106, 32, -76, 107, 36, -76, 108, 40, -77, 109, 44, -78, + 110, 48, -78, 111, 53, -79, 112, 57, -80, 113, 61, -80, + 114, 65, -81, 115, 69, -82, 116, 73, -82, 117, 77, -83, + 90, -51, -64, 91, -47, -65, 92, -43, -65, 93, -38, -66, + 94, -34, -67, 95, -30, -67, 95, -26, -68, 97, -22, -69, + 97, -18, -69, 98, -14, -70, 99, -10, -71, 100, -6, -71, + 101, -1, -72, 102, 2, -73, 103, 6, -73, 104, 10, -74, + 105, 14, -75, 106, 18, -75, 107, 22, -76, 108, 26, -77, + 109, 31, -77, 110, 35, -78, 111, 39, -79, 111, 43, -79, + 112, 47, -80, 113, 51, -81, 114, 55, -81, 115, 59, -82, + 116, 63, -83, 117, 68, -83, 118, 72, -84, 119, 76, -85, + 92, -52, -66, 93, -48, -66, 94, -44, -67, 95, -39, -68, + 96, -35, -68, 97, -31, -69, 98, -27, -70, 99, -23, -70, + 100, -19, -71, 101, -15, -72, 102, -11, -72, 103, -7, -73, + 104, -2, -74, 104, 1, -74, 105, 5, -75, 106, 9, -76, + 107, 13, -76, 108, 17, -77, 109, 21, -78, 110, 25, -78, + 111, 30, -79, 112, 34, -80, 113, 38, -80, 114, 42, -81, + 115, 46, -82, 116, 50, -82, 117, 54, -83, 118, 58, -84, + 118, 62, -84, 119, 67, -85, 120, 71, -86, 121, 75, -86, + 95, -53, -67, 96, -49, -68, 96, -45, -69, 97, -41, -69, + 98, -37, -70, 99, -33, -71, 100, -29, -71, 101, -24, -72, + 102, -20, -73, 103, -16, -73, 104, -12, -74, 105, -8, -75, + 106, -4, -75, 107, 0, -76, 108, 3, -77, 109, 7, -77, + 110, 12, -78, 111, 16, -79, 111, 20, -79, 112, 24, -80, + 113, 28, -81, 114, 32, -81, 115, 36, -82, 116, 40, -83, + 117, 44, -83, 118, 49, -84, 119, 53, -85, 120, 57, -85, + 121, 61, -86, 122, 65, -87, 123, 69, -87, 124, 73, -88, + 97, -54, -69, 98, -50, -70, 99, -46, -70, 100, -42, -71, + 101, -38, -72, 102, -34, -72, 103, -30, -73, 104, -25, -74, + 104, -21, -74, 105, -17, -75, 106, -13, -76, 107, -9, -76, + 108, -5, -77, 109, -1, -78, 110, 2, -78, 111, 6, -79, + 112, 11, -80, 113, 15, -80, 114, 19, -81, 115, 23, -82, + 116, 27, -82, 117, 31, -83, 118, 35, -84, 118, 39, -84, + 119, 43, -85, 120, 48, -86, 121, 52, -86, 122, 56, -87, + 123, 60, -88, 124, 64, -88, 125, 68, -89, 126, 72, -90, + 99, -56, -71, 100, -52, -71, 101, -48, -72, 102, -43, -73, + 103, -39, -73, 104, -35, -74, 105, -31, -75, 106, -27, -75, + 107, -23, -76, 108, -19, -77, 109, -15, -77, 110, -11, -78, + 111, -6, -79, 111, -2, -79, 112, 1, -80, 113, 5, -81, + 114, 9, -81, 115, 13, -82, 116, 17, -83, 117, 21, -83, + 118, 26, -84, 119, 30, -85, 120, 34, -85, 121, 38, -86, + 122, 42, -87, 123, 46, -87, 124, 50, -88, 125, 54, -89, + 126, 58, -89, 127, 63, -90, 127, 67, -91, 128, 71, -91, + 102, -57, -72, 103, -53, -73, 103, -49, -74, 104, -45, -74, + 105, -41, -75, 106, -37, -76, 107, -33, -76, 108, -28, -77, + 109, -24, -78, 110, -20, -78, 111, -16, -79, 112, -12, -80, + 113, -8, -80, 114, -4, -81, 115, 0, -82, 116, 3, -82, + 117, 8, -83, 118, 12, -84, 119, 16, -84, 119, 20, -85, + 120, 24, -86, 121, 28, -86, 122, 32, -87, 123, 36, -88, + 124, 40, -88, 125, 45, -89, 126, 49, -90, 127, 53, -90, + 128, 57, -91, 129, 61, -92, 130, 65, -92, 131, 69, -93, + 104, -58, -74, 105, -54, -75, 106, -50, -75, 107, -46, -76, + 108, -42, -77, 109, -38, -77, 110, -34, -78, 111, -29, -79, + 112, -25, -79, 112, -21, -80, 113, -17, -81, 114, -13, -81, + 115, -9, -82, 116, -5, -83, 117, -1, -83, 118, 2, -84, + 119, 7, -85, 120, 11, -85, 121, 15, -86, 122, 19, -87, + 123, 23, -87, 124, 27, -88, 125, 31, -89, 126, 35, -89, + 126, 39, -90, 127, 44, -91, 128, 48, -91, 129, 52, -92, + 130, 56, -93, 131, 60, -93, 132, 64, -94, 133, 68, -95, + 106, -60, -76, 107, -56, -76, 108, -52, -77, 109, -47, -78, + 110, -43, -78, 111, -39, -79, 112, -35, -80, 113, -31, -80, + 114, -27, -81, 115, -23, -82, 116, -19, -82, 117, -15, -83, + 118, -10, -84, 119, -6, -84, 119, -2, -85, 120, 1, -86, + 121, 5, -86, 122, 9, -87, 123, 13, -88, 124, 17, -88, + 125, 22, -89, 126, 26, -90, 127, 30, -90, 128, 34, -91, + 129, 38, -92, 130, 42, -92, 131, 46, -93, 132, 50, -94, + 133, 54, -94, 134, 59, -95, 134, 63, -96, 135, 67, -96, + 109, -61, -77, 110, -57, -78, 111, -53, -79, 112, -49, -79, + 112, -45, -80, 113, -41, -81, 114, -37, -81, 115, -32, -82, + 116, -28, -83, 117, -24, -83, 118, -20, -84, 119, -16, -85, + 120, -12, -85, 121, -8, -86, 122, -4, -87, 123, 0, -87, + 124, 4, -88, 125, 8, -89, 126, 12, -89, 126, 16, -90, + 127, 20, -91, 128, 24, -91, 129, 28, -92, 130, 32, -93, + 131, 36, -93, 132, 41, -94, 133, 45, -95, 134, 49, -95, + 135, 53, -96, 136, 57, -97, 137, 61, -97, 138, 65, -98, + 111, -62, -79, 112, -58, -80, 113, -54, -80, 114, -50, -81, + 115, -46, -82, 116, -42, -82, 117, -38, -83, 118, -33, -84, + 119, -29, -84, 119, -25, -85, 120, -21, -86, 121, -17, -86, + 122, -13, -87, 123, -9, -88, 124, -5, -88, 125, -1, -89, + 126, 3, -90, 127, 7, -90, 128, 11, -91, 129, 15, -92, + 130, 19, -92, 131, 23, -93, 132, 27, -94, 133, 31, -94, + 133, 35, -95, 135, 40, -96, 135, 44, -96, 136, 48, -97, + 137, 52, -98, 138, 56, -98, 139, 60, -99, 140, 64, -100, + 113, -64, -81, 114, -60, -81, 115, -56, -82, 116, -51, -83, + 117, -47, -83, 118, -43, -84, 119, -39, -85, 120, -35, -85, + 121, -31, -86, 122, -27, -87, 123, -23, -87, 124, -19, -88, + 125, -14, -89, 126, -10, -89, 126, -6, -90, 127, -2, -91, + 128, 1, -91, 129, 5, -92, 130, 9, -93, 131, 13, -93, + 132, 18, -94, 133, 22, -95, 134, 26, -95, 135, 30, -96, + 136, 34, -97, 137, 38, -97, 138, 42, -98, 139, 46, -99, + 140, 50, -99, 141, 55, -100, 142, 59, -101, 142, 63, -101, + 116, -65, -82, 117, -61, -83, 118, -57, -84, 119, -53, -84, + 119, -49, -85, 120, -45, -86, 121, -41, -86, 122, -36, -87, + 123, -32, -88, 124, -28, -88, 125, -24, -89, 126, -20, -90, + 127, -16, -90, 128, -12, -91, 129, -8, -92, 130, -4, -92, + 131, 0, -93, 132, 4, -94, 133, 8, -94, 134, 12, -95, + 135, 16, -96, 135, 20, -96, 136, 24, -97, 137, 28, -98, + 138, 32, -98, 139, 37, -99, 140, 41, -100, 141, 45, -100, + 142, 49, -101, 143, 53, -102, 144, 57, -102, 145, 61, -103, + 118, -66, -84, 119, -62, -85, 120, -58, -85, 121, -54, -86, + 122, -50, -87, 123, -46, -87, 124, -42, -88, 125, -37, -89, + 126, -33, -89, 127, -29, -90, 127, -25, -91, 128, -21, -91, + 129, -17, -92, 130, -13, -93, 131, -9, -93, 132, -5, -94, + 133, 0, -95, 134, 3, -95, 135, 7, -96, 136, 11, -97, + 137, 15, -97, 138, 19, -98, 139, 23, -99, 140, 27, -99, + 141, 31, -100, 142, 36, -101, 142, 40, -101, 143, 44, -102, + 144, 48, -103, 145, 52, -104, 146, 56, -104, 147, 60, -105, + 120, -68, -86, 121, -64, -86, 122, -60, -87, 123, -55, -88, + 124, -51, -88, 125, -47, -89, 126, -43, -90, 127, -39, -90, + 128, -35, -91, 129, -31, -92, 130, -27, -92, 131, -23, -93, + 132, -18, -94, 133, -14, -94, 134, -10, -95, 134, -6, -96, + 135, -2, -96, 136, 1, -97, 137, 5, -98, 138, 9, -98, + 139, 14, -99, 140, 18, -100, 141, 22, -100, 142, 26, -101, + 143, 30, -102, 144, 34, -103, 145, 38, -103, 146, 42, -104, + 147, 46, -104, 148, 51, -105, 149, 55, -106, 149, 59, -106, + 123, -69, -87, 124, -65, -88, 125, -61, -89, 126, -57, -89, + 127, -53, -90, 127, -49, -91, 128, -45, -91, 129, -40, -92, + 130, -36, -93, 131, -32, -93, 132, -28, -94, 133, -24, -95, + 134, -20, -95, 135, -16, -96, 136, -12, -97, 137, -8, -97, + 138, -3, -98, 139, 0, -99, 140, 4, -99, 141, 8, -100, + 142, 12, -101, 142, 16, -101, 143, 20, -102, 144, 24, -103, + 145, 28, -103, 146, 33, -104, 147, 37, -105, 148, 41, -105, + 149, 45, -106, 150, 49, -107, 151, 53, -108, 152, 57, -108, + 126, -71, -90, 127, -67, -90, 128, -63, -91, 129, -58, -92, + 129, -54, -92, 130, -50, -93, 131, -46, -94, 132, -42, -94, + 133, -38, -95, 134, -34, -96, 135, -30, -96, 136, -26, -97, + 137, -21, -98, 138, -17, -98, 139, -13, -99, 140, -9, -100, + 141, -5, -100, 142, -1, -101, 143, 2, -102, 143, 6, -102, + 145, 11, -103, 145, 15, -104, 146, 19, -104, 147, 23, -105, + 148, 27, -106, 149, 31, -106, 150, 35, -107, 151, 39, -108, + 152, 43, -108, 153, 48, -109, 154, 52, -110, 155, 56, -110, + 128, -72, -91, 129, -68, -92, 130, -64, -92, 131, -60, -93, + 132, -56, -94, 133, -52, -95, 134, -48, -95, 135, -43, -96, + 136, -39, -97, 136, -35, -97, 137, -31, -98, 138, -27, -99, + 139, -23, -99, 140, -19, -100, 141, -15, -101, 142, -11, -101, + 143, -6, -102, 144, -2, -103, 145, 1, -103, 146, 5, -104, + 147, 9, -105, 148, 13, -105, 149, 17, -106, 150, 21, -107, + 151, 25, -107, 152, 30, -108, 152, 34, -109, 153, 38, -109, + 154, 42, -110, 155, 46, -111, 156, 50, -111, 157, 54, -112, + 130, -73, -93, 131, -69, -94, 132, -65, -94, 133, -61, -95, + 134, -57, -96, 135, -53, -96, 136, -49, -97, 137, -44, -98, + 138, -40, -98, 139, -36, -99, 140, -32, -100, 141, -28, -100, + 142, -24, -101, 143, -20, -102, 144, -16, -102, 144, -12, -103, + 145, -7, -104, 146, -3, -104, 147, 0, -105, 148, 4, -106, + 149, 8, -106, 150, 12, -107, 151, 16, -108, 152, 20, -108, + 153, 24, -109, 154, 29, -110, 155, 33, -110, 156, 37, -111, + 157, 41, -112, 158, 45, -112, 159, 49, -113, 159, 53, -114, + 133, -75, -95, 134, -71, -95, 135, -67, -96, 136, -62, -97, + 137, -58, -97, 137, -54, -98, 138, -50, -99, 139, -46, -99, + 140, -42, -100, 141, -38, -101, 142, -34, -101, 143, -30, -102, + 144, -25, -103, 145, -21, -103, 146, -17, -104, 147, -13, -105, + 148, -9, -105, 149, -5, -106, 150, -1, -107, 151, 2, -107, + 152, 7, -108, 152, 11, -109, 153, 15, -109, 154, 19, -110, + 155, 23, -111, 156, 27, -111, 157, 31, -112, 158, 35, -113, + 159, 39, -113, 160, 44, -114, 161, 48, -115, 162, 52, -115, + 135, -76, -96, 136, -72, -97, 137, -68, -98, 138, -64, -98, + 139, -60, -99, 140, -56, -100, 141, -52, -100, 142, -47, -101, + 143, -43, -102, 144, -39, -102, 144, -35, -103, 145, -31, -104, + 146, -27, -104, 147, -23, -105, 148, -19, -106, 149, -15, -106, + 150, -10, -107, 151, -6, -108, 152, -2, -108, 153, 1, -109, + 154, 5, -110, 155, 9, -110, 156, 13, -111, 157, 17, -112, + 158, 21, -112, 159, 26, -113, 159, 30, -114, 160, 34, -114, + 161, 38, -115, 162, 42, -116, 163, 46, -116, 164, 50, -117, + 137, -77, -98, 138, -73, -99, 139, -69, -99, 140, -65, -100, + 141, -61, -101, 142, -57, -101, 143, -53, -102, 144, -48, -103, + 145, -44, -103, 146, -40, -104, 147, -36, -105, 148, -32, -105, + 149, -28, -106, 150, -24, -107, 151, -20, -107, 151, -16, -108, + 152, -11, -109, 153, -7, -109, 154, -3, -110, 155, 0, -111, + 156, 4, -111, 157, 8, -112, 158, 12, -113, 159, 16, -113, + 160, 20, -114, 161, 25, -115, 162, 29, -115, 163, 33, -116, + 164, 37, -117, 165, 41, -117, 166, 45, -118, 167, 49, -119, + 140, -79, -100, 141, -75, -100, 142, -71, -101, 143, -66, -102, + 144, -62, -102, 144, -58, -103, 145, -54, -104, 146, -50, -104, + 147, -46, -105, 148, -42, -106, 149, -38, -106, 150, -34, -107, + 151, -29, -108, 152, -25, -108, 153, -21, -109, 154, -17, -110, + 155, -13, -110, 156, -9, -111, 157, -5, -112, 158, -1, -112, + 159, 3, -113, 160, 7, -114, 160, 11, -114, 161, 15, -115, + 162, 19, -116, 163, 23, -116, 164, 27, -117, 165, 31, -118, + 166, 35, -118, 167, 40, -119, 168, 44, -120, 169, 48, -120, + 142, -80, -101, 143, -76, -102, 144, -72, -103, 145, -67, -103, + 146, -63, -104, 147, -59, -105, 148, -55, -105, 149, -51, -106, + 150, -47, -107, 151, -43, -107, 151, -39, -108, 152, -35, -109, + 153, -30, -109, 154, -26, -110, 155, -22, -111, 156, -18, -111, + 157, -14, -112, 158, -10, -113, 159, -6, -113, 160, -2, -114, + 161, 2, -115, 162, 6, -115, 163, 10, -116, 164, 14, -117, + 165, 18, -117, 166, 22, -118, 167, 26, -119, 167, 30, -119, + 168, 34, -120, 169, 39, -121, 170, 43, -121, 171, 47, -122, + 144, -81, -103, 145, -77, -104, 146, -73, -104, 147, -69, -105, + 148, -65, -106, 149, -61, -106, 150, -57, -107, 151, -52, -108, + 152, -48, -108, 153, -44, -109, 154, -40, -110, 155, -36, -110, + 156, -32, -111, 157, -28, -112, 158, -24, -112, 159, -20, -113, + 160, -15, -114, 160, -11, -114, 161, -7, -115, 162, -3, -116, + 163, 0, -116, 164, 4, -117, 165, 8, -118, 166, 12, -118, + 167, 16, -119, 168, 21, -120, 169, 25, -120, 170, 29, -121, + 171, 33, -122, 172, 37, -122, 173, 41, -123, 174, 45, -124, + 147, -83, -105, 148, -79, -105, 149, -75, -106, 150, -70, -107, + 151, -66, -107, 152, -62, -108, 152, -58, -109, 153, -54, -109, + 154, -50, -110, 155, -46, -111, 156, -42, -111, 157, -38, -112, + 158, -33, -113, 159, -29, -113, 160, -25, -114, 161, -21, -115, + 162, -17, -115, 163, -13, -116, 164, -9, -117, 165, -5, -117, + 166, 0, -118, 167, 3, -119, 167, 7, -119, 168, 11, -120, + 169, 15, -121, 170, 19, -121, 171, 23, -122, 172, 27, -123, + 173, 31, -123, 174, 36, -124, 175, 40, -125, 176, 44, -125, + 149, -84, -106, 150, -80, -107, 151, -76, -108, 152, -71, -108, + 153, -67, -109, 154, -63, -110, 155, -59, -110, 156, -55, -111, + 157, -51, -112, 158, -47, -112, 159, -43, -113, 159, -39, -114, + 160, -34, -114, 161, -30, -115, 162, -26, -116, 163, -22, -116, + 164, -18, -117, 165, -14, -118, 166, -10, -118, 167, -6, -119, + 168, -1, -120, 169, 2, -120, 170, 6, -121, 171, 10, -122, + 172, 14, -122, 173, 18, -123, 174, 22, -124, 174, 26, -124, + 175, 30, -125, 176, 35, -126, 177, 39, -126, 178, 43, -127, + 2, -1, 4, 3, 2, 3, 4, 6, 2, 5, 11, 1, + 6, 15, 1, 7, 19, 0, 7, 23, 0, 9, 27, 0, + 9, 31, -1, 10, 35, -2, 11, 39, -2, 12, 43, -3, + 13, 48, -4, 14, 52, -4, 15, 56, -5, 16, 60, -6, + 17, 64, -6, 18, 68, -7, 19, 72, -8, 20, 76, -8, + 21, 81, -9, 22, 85, -10, 23, 89, -10, 23, 93, -11, + 24, 97, -12, 25, 101, -12, 26, 105, -13, 27, 109, -14, + 28, 113, -14, 29, 118, -15, 30, 122, -16, 31, 126, -16, + 4, -2, 2, 5, 1, 1, 6, 5, 1, 7, 9, 0, + 8, 13, 0, 9, 17, -1, 10, 21, -1, 11, 26, -2, + 12, 30, -3, 13, 34, -3, 14, 38, -4, 15, 42, -4, + 16, 46, -5, 16, 50, -6, 17, 54, -7, 18, 58, -7, + 19, 63, -8, 20, 67, -9, 21, 71, -9, 22, 75, -10, + 23, 79, -11, 24, 83, -11, 25, 87, -12, 26, 91, -13, + 27, 95, -13, 28, 100, -14, 29, 104, -15, 30, 108, -15, + 30, 112, -16, 31, 116, -17, 32, 120, -17, 33, 124, -18, + 7, -4, 0, 7, 0, 0, 8, 4, 0, 9, 8, -1, + 10, 12, -2, 11, 16, -2, 12, 20, -3, 13, 25, -4, + 14, 29, -4, 15, 33, -5, 16, 37, -6, 17, 41, -6, + 18, 45, -7, 19, 49, -8, 20, 53, -8, 21, 57, -9, + 22, 62, -10, 23, 66, -10, 23, 70, -11, 24, 74, -12, + 25, 78, -12, 26, 82, -13, 27, 86, -14, 28, 90, -14, + 29, 94, -15, 30, 99, -16, 31, 103, -16, 32, 107, -17, + 33, 111, -18, 34, 115, -18, 35, 119, -19, 36, 123, -20, + 9, -5, -1, 10, -1, -1, 11, 2, -2, 12, 7, -3, + 13, 11, -3, 14, 15, -4, 15, 19, -5, 16, 23, -5, + 16, 27, -6, 17, 31, -7, 18, 35, -7, 19, 39, -8, + 20, 44, -9, 21, 48, -9, 22, 52, -10, 23, 56, -11, + 24, 60, -11, 25, 64, -12, 26, 68, -13, 27, 72, -13, + 28, 77, -14, 29, 81, -15, 30, 85, -15, 30, 89, -16, + 31, 93, -17, 32, 97, -17, 33, 101, -18, 34, 105, -19, + 35, 109, -19, 36, 114, -20, 37, 118, -21, 38, 122, -21, + 11, -6, -2, 12, -2, -3, 13, 1, -4, 14, 5, -4, + 15, 9, -5, 16, 13, -6, 17, 17, -6, 18, 22, -7, + 19, 26, -8, 20, 30, -8, 21, 34, -9, 22, 38, -10, + 23, 42, -10, 23, 46, -11, 24, 50, -12, 25, 54, -12, + 26, 59, -13, 27, 63, -14, 28, 67, -14, 29, 71, -15, + 30, 75, -16, 31, 79, -16, 32, 83, -17, 33, 87, -18, + 34, 91, -18, 35, 96, -19, 36, 100, -20, 37, 104, -20, + 38, 108, -21, 39, 112, -22, 39, 116, -22, 40, 120, -23, + 14, -7, -4, 15, -3, -5, 15, 0, -5, 16, 4, -6, + 17, 8, -7, 18, 12, -7, 19, 16, -8, 20, 21, -9, + 21, 25, -9, 22, 29, -10, 23, 33, -11, 24, 37, -11, + 25, 41, -12, 26, 45, -13, 27, 49, -13, 28, 53, -14, + 29, 58, -15, 30, 62, -15, 31, 66, -16, 31, 70, -17, + 32, 74, -17, 33, 78, -18, 34, 82, -19, 35, 86, -19, + 36, 90, -20, 37, 95, -21, 38, 99, -21, 39, 103, -22, + 40, 107, -23, 41, 111, -23, 42, 115, -24, 43, 119, -25, + 16, -9, -6, 17, -5, -6, 18, -1, -7, 19, 3, -8, + 20, 7, -8, 21, 11, -9, 22, 15, -10, 23, 19, -10, + 24, 23, -11, 24, 27, -12, 25, 31, -12, 26, 35, -13, + 27, 40, -14, 28, 44, -14, 29, 48, -15, 30, 52, -16, + 31, 56, -16, 32, 60, -17, 33, 64, -18, 34, 68, -18, + 35, 73, -19, 36, 77, -20, 37, 81, -20, 38, 85, -21, + 38, 89, -22, 39, 93, -22, 40, 97, -23, 41, 101, -24, + 42, 105, -24, 43, 110, -25, 44, 114, -26, 45, 118, -26, + 18, -10, -7, 19, -6, -8, 20, -2, -9, 21, 1, -9, + 22, 5, -10, 23, 9, -11, 24, 13, -11, 25, 18, -12, + 26, 22, -13, 27, 26, -13, 28, 30, -14, 29, 34, -15, + 30, 38, -15, 31, 42, -16, 31, 46, -17, 32, 50, -17, + 33, 55, -18, 34, 59, -19, 35, 63, -19, 36, 67, -20, + 37, 71, -21, 38, 75, -21, 39, 79, -22, 40, 83, -23, + 41, 87, -23, 42, 92, -24, 43, 96, -25, 44, 100, -25, + 45, 104, -26, 46, 108, -27, 46, 112, -27, 47, 116, -28, + 21, -11, -9, 22, -7, -10, 23, -3, -10, 24, 0, -11, + 24, 4, -12, 25, 8, -12, 26, 12, -13, 27, 17, -14, + 28, 21, -14, 29, 25, -15, 30, 29, -16, 31, 33, -16, + 32, 37, -17, 33, 41, -18, 34, 45, -18, 35, 49, -19, + 36, 54, -20, 37, 58, -20, 38, 62, -21, 38, 66, -22, + 39, 70, -22, 40, 74, -23, 41, 78, -24, 42, 82, -24, + 43, 86, -25, 44, 91, -26, 45, 95, -26, 46, 99, -27, + 47, 103, -28, 48, 107, -28, 49, 111, -29, 50, 115, -30, + 23, -13, -11, 24, -9, -11, 25, -5, -12, 26, 0, -13, + 27, 3, -13, 28, 7, -14, 29, 11, -15, 30, 15, -15, + 31, 19, -16, 31, 23, -17, 32, 27, -17, 33, 31, -18, + 34, 36, -19, 35, 40, -19, 36, 44, -20, 37, 48, -21, + 38, 52, -21, 39, 56, -22, 40, 60, -23, 41, 64, -23, + 42, 69, -24, 43, 73, -25, 44, 77, -25, 45, 81, -26, + 45, 85, -27, 47, 89, -27, 47, 93, -28, 48, 97, -29, + 49, 101, -29, 50, 106, -30, 51, 110, -31, 52, 114, -31, + 25, -14, -12, 26, -10, -13, 27, -6, -14, 28, -2, -14, + 29, 1, -15, 30, 5, -16, 31, 9, -16, 32, 14, -17, + 33, 18, -18, 34, 22, -18, 35, 26, -19, 36, 30, -20, + 37, 34, -20, 38, 38, -21, 38, 42, -22, 39, 46, -22, + 40, 51, -23, 41, 55, -24, 42, 59, -24, 43, 63, -25, + 44, 67, -26, 45, 71, -26, 46, 75, -27, 47, 79, -28, + 48, 83, -28, 49, 88, -29, 50, 92, -30, 51, 96, -30, + 52, 100, -31, 53, 104, -32, 54, 108, -32, 54, 112, -33, + 28, -16, -14, 29, -12, -15, 30, -8, -16, 31, -3, -16, + 32, 0, -17, 33, 4, -18, 34, 8, -18, 35, 12, -19, + 36, 16, -20, 37, 20, -20, 38, 24, -21, 39, 28, -22, + 40, 33, -22, 41, 37, -23, 41, 41, -24, 42, 45, -24, + 43, 49, -25, 44, 53, -26, 45, 57, -26, 46, 61, -27, + 47, 66, -28, 48, 70, -28, 49, 74, -29, 50, 78, -30, + 51, 82, -30, 52, 86, -31, 53, 90, -32, 54, 94, -32, + 55, 98, -33, 56, 103, -34, 56, 107, -34, 57, 111, -35, + 31, -17, -16, 32, -13, -17, 32, -9, -17, 34, -5, -18, + 34, -1, -19, 35, 2, -19, 36, 6, -20, 37, 11, -21, + 38, 15, -21, 39, 19, -22, 40, 23, -23, 41, 27, -23, + 42, 31, -24, 43, 35, -25, 44, 39, -25, 45, 43, -26, + 46, 48, -27, 47, 52, -27, 48, 56, -28, 48, 60, -29, + 49, 64, -29, 50, 68, -30, 51, 72, -31, 52, 76, -31, + 53, 80, -32, 54, 85, -33, 55, 89, -33, 56, 93, -34, + 57, 97, -35, 58, 101, -35, 59, 105, -36, 60, 109, -37, + 33, -18, -18, 34, -14, -18, 35, -10, -19, 36, -6, -20, + 37, -2, -20, 38, 1, -21, 39, 5, -22, 40, 10, -22, + 41, 14, -23, 41, 18, -24, 42, 22, -24, 43, 26, -25, + 44, 30, -26, 45, 34, -26, 46, 38, -27, 47, 42, -28, + 48, 47, -28, 49, 51, -29, 50, 55, -30, 51, 59, -30, + 52, 63, -31, 53, 67, -32, 54, 71, -32, 55, 75, -33, + 55, 79, -34, 56, 84, -34, 57, 88, -35, 58, 92, -36, + 59, 96, -36, 60, 100, -37, 61, 104, -38, 62, 108, -38, + 35, -20, -19, 36, -16, -20, 37, -12, -21, 38, -7, -21, + 39, -3, -22, 40, 0, -23, 41, 4, -23, 42, 8, -24, + 43, 12, -25, 44, 16, -25, 45, 20, -26, 46, 24, -27, + 47, 29, -27, 48, 33, -28, 48, 37, -29, 49, 41, -29, + 50, 45, -30, 51, 49, -31, 52, 53, -31, 53, 57, -32, + 54, 62, -33, 55, 66, -33, 56, 70, -34, 57, 74, -35, + 58, 78, -35, 59, 82, -36, 60, 86, -37, 61, 90, -37, + 62, 94, -38, 63, 99, -39, 64, 103, -39, 64, 107, -40, + 38, -21, -21, 39, -17, -22, 40, -13, -22, 41, -9, -23, + 41, -5, -24, 42, -1, -24, 43, 2, -25, 44, 7, -26, + 45, 11, -26, 46, 15, -27, 47, 19, -28, 48, 23, -28, + 49, 27, -29, 50, 31, -30, 51, 35, -30, 52, 39, -31, + 53, 44, -32, 54, 48, -32, 55, 52, -33, 55, 56, -34, + 57, 60, -34, 57, 64, -35, 58, 68, -36, 59, 72, -36, + 60, 76, -37, 61, 81, -38, 62, 85, -38, 63, 89, -39, + 64, 93, -40, 65, 97, -40, 66, 101, -41, 67, 105, -42, + 40, -22, -23, 41, -18, -23, 42, -14, -24, 43, -10, -25, + 44, -6, -25, 45, -2, -26, 46, 1, -27, 47, 6, -27, + 48, 10, -28, 48, 14, -29, 49, 18, -29, 50, 22, -30, + 51, 26, -31, 52, 30, -31, 53, 34, -32, 54, 38, -33, + 55, 43, -33, 56, 47, -34, 57, 51, -35, 58, 55, -35, + 59, 59, -36, 60, 63, -37, 61, 67, -37, 62, 71, -38, + 63, 75, -39, 64, 80, -39, 64, 84, -40, 65, 88, -41, + 66, 92, -41, 67, 96, -42, 68, 100, -43, 69, 104, -43, + 42, -24, -24, 43, -20, -25, 44, -16, -26, 45, -11, -26, + 46, -7, -27, 47, -3, -28, 48, 0, -28, 49, 4, -29, + 50, 8, -30, 51, 12, -30, 52, 16, -31, 53, 20, -32, + 54, 25, -32, 55, 29, -33, 56, 33, -34, 56, 37, -34, + 57, 41, -35, 58, 45, -36, 59, 49, -36, 60, 53, -37, + 61, 58, -38, 62, 62, -38, 63, 66, -39, 64, 70, -40, + 65, 74, -40, 66, 78, -41, 67, 82, -42, 68, 86, -42, + 69, 90, -43, 70, 95, -44, 71, 99, -44, 71, 103, -45, + 45, -25, -26, 46, -21, -27, 47, -17, -27, 48, -13, -28, + 49, -9, -29, 49, -5, -29, 50, -1, -30, 51, 3, -31, + 52, 7, -31, 53, 11, -32, 54, 15, -33, 55, 19, -33, + 56, 23, -34, 57, 27, -35, 58, 31, -35, 59, 35, -36, + 60, 40, -37, 61, 44, -37, 62, 48, -38, 63, 52, -39, + 64, 56, -39, 64, 60, -40, 65, 64, -41, 66, 68, -41, + 67, 72, -42, 68, 77, -43, 69, 81, -43, 70, 85, -44, + 71, 89, -45, 72, 93, -45, 73, 97, -46, 74, 101, -47, + 47, -26, -28, 48, -22, -28, 49, -18, -29, 50, -14, -30, + 51, -10, -30, 52, -6, -31, 53, -2, -32, 54, 2, -32, + 55, 6, -33, 56, 10, -34, 56, 14, -34, 57, 18, -35, + 58, 22, -36, 59, 26, -36, 60, 30, -37, 61, 34, -38, + 62, 39, -38, 63, 43, -39, 64, 47, -40, 65, 51, -40, + 66, 55, -41, 67, 59, -42, 68, 63, -42, 69, 67, -43, + 70, 71, -44, 71, 76, -44, 71, 80, -45, 72, 84, -46, + 73, 88, -46, 74, 92, -47, 75, 96, -48, 76, 100, -48, + 49, -28, -29, 50, -24, -30, 51, -20, -31, 52, -15, -31, + 53, -11, -32, 54, -7, -33, 55, -3, -33, 56, 0, -34, + 57, 4, -35, 58, 8, -35, 59, 12, -36, 60, 16, -37, + 61, 21, -37, 62, 25, -38, 63, 29, -39, 63, 33, -39, + 64, 37, -40, 65, 41, -41, 66, 45, -41, 67, 49, -42, + 68, 54, -43, 69, 58, -43, 70, 62, -44, 71, 66, -45, + 72, 70, -45, 73, 74, -46, 74, 78, -47, 75, 82, -47, + 76, 86, -48, 77, 91, -49, 78, 95, -49, 79, 99, -50, + 52, -29, -31, 53, -25, -32, 54, -21, -32, 55, -17, -33, + 56, -13, -34, 56, -9, -34, 57, -5, -35, 58, 0, -36, + 59, 3, -36, 60, 7, -37, 61, 11, -38, 62, 15, -38, + 63, 19, -39, 64, 23, -40, 65, 27, -40, 66, 31, -41, + 67, 36, -42, 68, 40, -42, 69, 44, -43, 70, 48, -44, + 71, 52, -45, 72, 56, -45, 72, 60, -46, 73, 64, -46, + 74, 68, -47, 75, 73, -48, 76, 77, -48, 77, 81, -49, + 78, 85, -50, 79, 89, -51, 80, 93, -51, 81, 97, -52, + 54, -30, -33, 55, -26, -33, 56, -22, -34, 57, -18, -35, + 58, -14, -35, 59, -10, -36, 60, -6, -37, 61, -1, -37, + 62, 2, -38, 63, 6, -39, 63, 10, -39, 64, 14, -40, + 65, 18, -41, 66, 22, -41, 67, 26, -42, 68, 30, -43, + 69, 35, -43, 70, 39, -44, 71, 43, -45, 72, 47, -45, + 73, 51, -46, 74, 55, -47, 75, 59, -47, 76, 63, -48, + 77, 67, -49, 78, 72, -50, 79, 76, -50, 79, 80, -51, + 80, 84, -51, 81, 88, -52, 82, 92, -53, 83, 96, -53, + 56, -32, -34, 57, -28, -35, 58, -24, -36, 59, -19, -36, + 60, -15, -37, 61, -11, -38, 62, -7, -38, 63, -3, -39, + 64, 0, -40, 65, 4, -40, 66, 8, -41, 67, 12, -42, + 68, 17, -42, 69, 21, -43, 70, 25, -44, 71, 29, -44, + 72, 33, -45, 72, 37, -46, 73, 41, -46, 74, 45, -47, + 75, 50, -48, 76, 54, -49, 77, 58, -49, 78, 62, -50, + 79, 66, -50, 80, 70, -51, 81, 74, -52, 82, 78, -52, + 83, 82, -53, 84, 87, -54, 85, 91, -55, 86, 95, -55, + 59, -33, -36, 60, -29, -37, 61, -25, -37, 62, -20, -38, + 63, -16, -39, 64, -12, -39, 64, -8, -40, 65, -4, -41, + 66, 0, -41, 67, 3, -42, 68, 7, -43, 69, 11, -43, + 70, 16, -44, 71, 20, -45, 72, 24, -45, 73, 28, -46, + 74, 32, -47, 75, 36, -47, 76, 40, -48, 77, 44, -49, + 78, 49, -50, 79, 53, -50, 79, 57, -51, 80, 61, -51, + 81, 65, -52, 82, 69, -53, 83, 73, -54, 84, 77, -54, + 85, 81, -55, 86, 86, -56, 87, 90, -56, 88, 94, -57, + 61, -34, -38, 62, -30, -38, 63, -26, -39, 64, -22, -40, + 65, -18, -40, 66, -14, -41, 67, -10, -42, 68, -5, -43, + 69, -1, -43, 70, 2, -44, 71, 6, -44, 71, 10, -45, + 72, 14, -46, 73, 18, -46, 74, 22, -47, 75, 26, -48, + 76, 31, -49, 77, 35, -49, 78, 39, -50, 79, 43, -50, + 80, 47, -51, 81, 51, -52, 82, 55, -53, 83, 59, -53, + 84, 63, -54, 85, 68, -55, 86, 72, -55, 86, 76, -56, + 87, 80, -56, 88, 84, -57, 89, 88, -58, 90, 92, -59, + 64, -36, -39, 64, -32, -40, 65, -28, -41, 66, -23, -41, + 67, -19, -42, 68, -15, -43, 69, -11, -43, 70, -7, -44, + 71, -3, -45, 72, 0, -45, 73, 4, -46, 74, 8, -47, + 75, 13, -48, 76, 17, -48, 77, 21, -49, 78, 25, -49, + 79, 29, -50, 79, 33, -51, 80, 37, -51, 81, 41, -52, + 82, 46, -53, 83, 50, -54, 84, 54, -54, 85, 58, -55, + 86, 62, -55, 87, 66, -56, 88, 70, -57, 89, 74, -58, + 90, 78, -58, 91, 83, -59, 92, 87, -60, 93, 91, -60, + 66, -37, -41, 67, -33, -42, 68, -29, -42, 69, -24, -43, + 70, -20, -44, 71, -16, -44, 71, -12, -45, 72, -8, -46, + 73, -4, -47, 74, 0, -47, 75, 3, -48, 76, 7, -48, + 77, 12, -49, 78, 16, -50, 79, 20, -50, 80, 24, -51, + 81, 28, -52, 82, 32, -53, 83, 36, -53, 84, 40, -54, + 85, 45, -55, 86, 49, -55, 87, 53, -56, 87, 57, -57, + 88, 61, -57, 89, 65, -58, 90, 69, -59, 91, 73, -59, + 92, 77, -60, 93, 82, -61, 94, 86, -61, 95, 90, -62, + 68, -38, -43, 69, -34, -43, 70, -30, -44, 71, -26, -45, + 72, -22, -45, 73, -18, -46, 74, -14, -47, 75, -9, -48, + 76, -5, -48, 77, -1, -49, 78, 2, -49, 78, 6, -50, + 80, 10, -51, 80, 14, -52, 81, 18, -52, 82, 22, -53, + 83, 27, -54, 84, 31, -54, 85, 35, -55, 86, 39, -55, + 87, 43, -56, 88, 47, -57, 89, 51, -58, 90, 55, -58, + 91, 59, -59, 92, 64, -60, 93, 68, -60, 94, 72, -61, + 94, 76, -62, 95, 80, -62, 96, 84, -63, 97, 88, -64, + 71, -40, -44, 71, -36, -45, 72, -32, -46, 73, -27, -47, + 74, -23, -47, 75, -19, -48, 76, -15, -48, 77, -11, -49, + 78, -7, -50, 79, -3, -51, 80, 0, -51, 81, 4, -52, + 82, 9, -53, 83, 13, -53, 84, 17, -54, 85, 21, -54, + 86, 25, -55, 87, 29, -56, 87, 33, -57, 88, 37, -57, + 89, 42, -58, 90, 46, -59, 91, 50, -59, 92, 54, -60, + 93, 58, -61, 94, 62, -61, 95, 66, -62, 96, 70, -63, + 97, 74, -63, 98, 79, -64, 99, 83, -65, 100, 87, -65, + 73, -41, -46, 74, -37, -47, 75, -33, -47, 76, -28, -48, + 77, -24, -49, 78, -20, -49, 79, -16, -50, 80, -12, -51, + 80, -8, -52, 81, -4, -52, 82, 0, -53, 83, 3, -53, + 84, 8, -54, 85, 12, -55, 86, 16, -56, 87, 20, -56, + 88, 24, -57, 89, 28, -58, 90, 32, -58, 91, 36, -59, + 92, 41, -60, 93, 45, -60, 94, 49, -61, 94, 53, -62, + 95, 57, -62, 96, 61, -63, 97, 65, -64, 98, 69, -64, + 99, 73, -65, 100, 78, -66, 101, 82, -66, 102, 86, -67, + 75, -42, -48, 76, -38, -48, 77, -34, -49, 78, -30, -50, + 79, -26, -51, 80, -22, -51, 81, -18, -52, 82, -13, -53, + 83, -9, -53, 84, -5, -54, 85, -1, -55, 86, 2, -55, + 87, 6, -56, 87, 10, -57, 88, 14, -57, 89, 18, -58, + 90, 23, -59, 91, 27, -59, 92, 31, -60, 93, 35, -61, + 94, 39, -61, 95, 43, -62, 96, 47, -63, 97, 51, -63, + 98, 55, -64, 99, 60, -65, 100, 64, -65, 101, 68, -66, + 101, 72, -67, 103, 76, -67, 103, 80, -68, 104, 84, -69, + 78, -44, -50, 79, -40, -51, 80, -36, -51, 81, -31, -52, + 82, -27, -53, 83, -23, -53, 84, -19, -54, 85, -15, -55, + 86, -11, -55, 87, -7, -56, 88, -3, -57, 88, 0, -57, + 89, 5, -58, 90, 9, -59, 91, 13, -59, 92, 17, -60, + 93, 21, -61, 94, 25, -61, 95, 29, -62, 96, 33, -63, + 97, 38, -63, 98, 42, -64, 99, 46, -65, 100, 50, -65, + 101, 54, -66, 102, 58, -67, 103, 62, -67, 104, 66, -68, + 104, 70, -69, 105, 75, -69, 106, 79, -70, 107, 83, -71, + 81, -45, -52, 81, -41, -52, 82, -37, -53, 83, -33, -54, + 84, -29, -54, 85, -25, -55, 86, -21, -56, 87, -16, -56, + 88, -12, -57, 89, -8, -58, 90, -4, -58, 91, 0, -59, + 92, 3, -60, 93, 7, -60, 94, 11, -61, 95, 15, -62, + 96, 20, -62, 97, 24, -63, 97, 28, -64, 98, 32, -64, + 99, 36, -65, 100, 40, -66, 101, 44, -66, 102, 48, -67, + 103, 52, -68, 104, 57, -68, 105, 61, -69, 106, 65, -70, + 107, 69, -70, 108, 73, -71, 109, 77, -72, 110, 81, -72, + 83, -47, -53, 84, -43, -54, 85, -39, -55, 86, -34, -55, + 87, -30, -56, 88, -26, -57, 88, -22, -57, 90, -18, -58, + 90, -14, -59, 91, -10, -59, 92, -6, -60, 93, -2, -61, + 94, 2, -61, 95, 6, -62, 96, 10, -63, 97, 14, -63, + 98, 18, -64, 99, 22, -65, 100, 26, -65, 101, 30, -66, + 102, 35, -67, 103, 39, -67, 104, 43, -68, 104, 47, -69, + 105, 51, -69, 106, 55, -70, 107, 59, -71, 108, 63, -71, + 109, 67, -72, 110, 72, -73, 111, 76, -73, 112, 80, -74, + 85, -48, -55, 86, -44, -56, 87, -40, -56, 88, -35, -57, + 89, -31, -58, 90, -27, -58, 91, -23, -59, 92, -19, -60, + 93, -15, -60, 94, -11, -61, 95, -7, -62, 96, -3, -62, + 97, 1, -63, 97, 5, -64, 98, 9, -64, 99, 13, -65, + 100, 17, -66, 101, 21, -66, 102, 25, -67, 103, 29, -68, + 104, 34, -68, 105, 38, -69, 106, 42, -70, 107, 46, -70, + 108, 50, -71, 109, 54, -72, 110, 58, -72, 111, 62, -73, + 111, 66, -74, 112, 71, -74, 113, 75, -75, 114, 79, -76, + 88, -49, -57, 89, -45, -57, 89, -41, -58, 90, -37, -59, + 91, -33, -59, 92, -29, -60, 93, -25, -61, 94, -20, -61, + 95, -16, -62, 96, -12, -63, 97, -8, -63, 98, -4, -64, + 99, 0, -65, 100, 3, -65, 101, 7, -66, 102, 11, -67, + 103, 16, -67, 104, 20, -68, 104, 24, -69, 105, 28, -69, + 106, 32, -70, 107, 36, -71, 108, 40, -71, 109, 44, -72, + 110, 48, -73, 111, 53, -73, 112, 57, -74, 113, 61, -75, + 114, 65, -75, 115, 69, -76, 116, 73, -77, 117, 77, -77, + 90, -51, -58, 91, -47, -59, 92, -43, -60, 93, -38, -60, + 94, -34, -61, 95, -30, -62, 96, -26, -62, 97, -22, -63, + 97, -18, -64, 98, -14, -64, 99, -10, -65, 100, -6, -66, + 101, -1, -66, 102, 2, -67, 103, 6, -68, 104, 10, -68, + 105, 14, -69, 106, 18, -70, 107, 22, -70, 108, 26, -71, + 109, 31, -72, 110, 35, -72, 111, 39, -73, 111, 43, -74, + 112, 47, -74, 113, 51, -75, 114, 55, -76, 115, 59, -76, + 116, 63, -77, 117, 68, -78, 118, 72, -78, 119, 76, -79, + 92, -52, -60, 93, -48, -61, 94, -44, -61, 95, -39, -62, + 96, -35, -63, 97, -31, -63, 98, -27, -64, 99, -23, -65, + 100, -19, -65, 101, -15, -66, 102, -11, -67, 103, -7, -67, + 104, -2, -68, 104, 1, -69, 105, 5, -69, 106, 9, -70, + 107, 13, -71, 108, 17, -71, 109, 21, -72, 110, 25, -73, + 111, 30, -73, 112, 34, -74, 113, 38, -75, 114, 42, -75, + 115, 46, -76, 116, 50, -77, 117, 54, -77, 118, 58, -78, + 119, 62, -79, 120, 67, -79, 120, 71, -80, 121, 75, -81, + 95, -53, -62, 96, -49, -62, 96, -45, -63, 97, -41, -64, + 98, -37, -64, 99, -33, -65, 100, -29, -66, 101, -24, -66, + 102, -20, -67, 103, -16, -68, 104, -12, -68, 105, -8, -69, + 106, -4, -70, 107, 0, -70, 108, 3, -71, 109, 7, -72, + 110, 12, -72, 111, 16, -73, 112, 20, -74, 112, 24, -74, + 113, 28, -75, 114, 32, -76, 115, 36, -76, 116, 40, -77, + 117, 44, -78, 118, 49, -78, 119, 53, -79, 120, 57, -80, + 121, 61, -80, 122, 65, -81, 123, 69, -82, 124, 73, -82, + 97, -55, -63, 98, -51, -64, 99, -47, -65, 100, -42, -65, + 101, -38, -66, 102, -34, -67, 103, -30, -67, 104, -26, -68, + 105, -22, -69, 105, -18, -69, 106, -14, -70, 107, -10, -71, + 108, -5, -71, 109, -1, -72, 110, 2, -73, 111, 6, -73, + 112, 10, -74, 113, 14, -75, 114, 18, -75, 115, 22, -76, + 116, 27, -77, 117, 31, -77, 118, 35, -78, 119, 39, -79, + 119, 43, -79, 120, 47, -80, 121, 51, -81, 122, 55, -81, + 123, 59, -82, 124, 64, -83, 125, 68, -83, 126, 72, -84, + 99, -56, -65, 100, -52, -66, 101, -48, -66, 102, -43, -67, + 103, -39, -68, 104, -35, -68, 105, -31, -69, 106, -27, -70, + 107, -23, -70, 108, -19, -71, 109, -15, -72, 110, -11, -72, + 111, -6, -73, 112, -2, -74, 112, 1, -74, 113, 5, -75, + 114, 9, -76, 115, 13, -76, 116, 17, -77, 117, 21, -78, + 118, 26, -78, 119, 30, -79, 120, 34, -80, 121, 38, -80, + 122, 42, -81, 123, 46, -82, 124, 50, -82, 125, 54, -83, + 126, 58, -84, 127, 63, -84, 127, 67, -85, 128, 71, -86, + 102, -57, -67, 103, -53, -67, 104, -49, -68, 105, -45, -69, + 105, -41, -69, 106, -37, -70, 107, -33, -71, 108, -28, -71, + 109, -24, -72, 110, -20, -73, 111, -16, -73, 112, -12, -74, + 113, -8, -75, 114, -4, -75, 115, 0, -76, 116, 3, -77, + 117, 8, -77, 118, 12, -78, 119, 16, -79, 119, 20, -79, + 120, 24, -80, 121, 28, -81, 122, 32, -81, 123, 36, -82, + 124, 40, -83, 125, 45, -83, 126, 49, -84, 127, 53, -85, + 128, 57, -85, 129, 61, -86, 130, 65, -87, 131, 69, -87, + 104, -58, -68, 105, -54, -69, 106, -50, -70, 107, -46, -70, + 108, -42, -71, 109, -38, -72, 110, -34, -72, 111, -29, -73, + 112, -25, -74, 112, -21, -74, 113, -17, -75, 114, -13, -76, + 115, -9, -76, 116, -5, -77, 117, -1, -78, 118, 2, -78, + 119, 7, -79, 120, 11, -80, 121, 15, -80, 122, 19, -81, + 123, 23, -82, 124, 27, -82, 125, 31, -83, 126, 35, -84, + 126, 39, -84, 128, 44, -85, 128, 48, -86, 129, 52, -86, + 130, 56, -87, 131, 60, -88, 132, 64, -88, 133, 68, -89, + 106, -60, -70, 107, -56, -71, 108, -52, -71, 109, -47, -72, + 110, -43, -73, 111, -39, -73, 112, -35, -74, 113, -31, -75, + 114, -27, -75, 115, -23, -76, 116, -19, -77, 117, -15, -77, + 118, -10, -78, 119, -6, -79, 119, -2, -79, 120, 1, -80, + 121, 5, -81, 122, 9, -81, 123, 13, -82, 124, 17, -83, + 125, 22, -83, 126, 26, -84, 127, 30, -85, 128, 34, -85, + 129, 38, -86, 130, 42, -87, 131, 46, -87, 132, 50, -88, + 133, 54, -89, 134, 59, -89, 135, 63, -90, 135, 67, -91, + 109, -61, -72, 110, -57, -72, 111, -53, -73, 112, -49, -74, + 112, -45, -74, 113, -41, -75, 114, -37, -76, 115, -32, -76, + 116, -28, -77, 117, -24, -78, 118, -20, -78, 119, -16, -79, + 120, -12, -80, 121, -8, -80, 122, -4, -81, 123, 0, -82, + 124, 4, -82, 125, 8, -83, 126, 12, -84, 127, 16, -84, + 128, 20, -85, 128, 24, -86, 129, 28, -86, 130, 32, -87, + 131, 36, -88, 132, 41, -88, 133, 45, -89, 134, 49, -90, + 135, 53, -90, 136, 57, -91, 137, 61, -92, 138, 65, -92, + 111, -62, -73, 112, -58, -74, 113, -54, -75, 114, -50, -75, + 115, -46, -76, 116, -42, -77, 117, -38, -77, 118, -33, -78, + 119, -29, -79, 120, -25, -79, 120, -21, -80, 121, -17, -81, + 122, -13, -81, 123, -9, -82, 124, -5, -83, 125, -1, -83, + 126, 3, -84, 127, 7, -85, 128, 11, -85, 129, 15, -86, + 130, 19, -87, 131, 23, -87, 132, 27, -88, 133, 31, -89, + 134, 35, -89, 135, 40, -90, 135, 44, -91, 136, 48, -91, + 137, 52, -92, 138, 56, -93, 139, 60, -93, 140, 64, -94, + 113, -64, -75, 114, -60, -76, 115, -56, -76, 116, -51, -77, + 117, -47, -78, 118, -43, -78, 119, -39, -79, 120, -35, -80, + 121, -31, -80, 122, -27, -81, 123, -23, -82, 124, -19, -82, + 125, -14, -83, 126, -10, -84, 127, -6, -84, 127, -2, -85, + 128, 1, -86, 129, 5, -86, 130, 9, -87, 131, 13, -88, + 132, 18, -88, 133, 22, -89, 134, 26, -90, 135, 30, -90, + 136, 34, -91, 137, 38, -92, 138, 42, -92, 139, 46, -93, + 140, 50, -94, 141, 55, -94, 142, 59, -95, 142, 63, -96, + 116, -65, -77, 117, -61, -77, 118, -57, -78, 119, -53, -79, + 120, -49, -79, 120, -45, -80, 121, -41, -81, 122, -36, -81, + 123, -32, -82, 124, -28, -83, 125, -24, -83, 126, -20, -84, + 127, -16, -85, 128, -12, -85, 129, -8, -86, 130, -4, -87, + 131, 0, -87, 132, 4, -88, 133, 8, -89, 134, 12, -89, + 135, 16, -90, 135, 20, -91, 136, 24, -91, 137, 28, -92, + 138, 32, -93, 139, 37, -93, 140, 41, -94, 141, 45, -95, + 142, 49, -95, 143, 53, -96, 144, 57, -97, 145, 61, -97, + 118, -66, -78, 119, -62, -79, 120, -58, -80, 121, -54, -80, + 122, -50, -81, 123, -46, -82, 124, -42, -82, 125, -37, -83, + 126, -33, -84, 127, -29, -84, 127, -25, -85, 128, -21, -86, + 129, -17, -86, 130, -13, -87, 131, -9, -88, 132, -5, -88, + 133, 0, -89, 134, 3, -90, 135, 7, -90, 136, 11, -91, + 137, 15, -92, 138, 19, -92, 139, 23, -93, 140, 27, -94, + 141, 31, -94, 142, 36, -95, 143, 40, -96, 143, 44, -96, + 144, 48, -97, 145, 52, -98, 146, 56, -98, 147, 60, -99, + 120, -68, -80, 121, -64, -81, 122, -60, -81, 123, -55, -82, + 124, -51, -83, 125, -47, -83, 126, -43, -84, 127, -39, -85, + 128, -35, -85, 129, -31, -86, 130, -27, -87, 131, -23, -87, + 132, -18, -88, 133, -14, -89, 134, -10, -89, 134, -6, -90, + 136, -2, -91, 136, 1, -91, 137, 5, -92, 138, 9, -93, + 139, 14, -93, 140, 18, -94, 141, 22, -95, 142, 26, -95, + 143, 30, -96, 144, 34, -97, 145, 38, -97, 146, 42, -98, + 147, 46, -99, 148, 51, -100, 149, 55, -100, 150, 59, -101, + 123, -69, -82, 124, -65, -82, 125, -61, -83, 126, -57, -84, + 127, -53, -84, 127, -49, -85, 128, -45, -86, 129, -40, -86, + 130, -36, -87, 131, -32, -88, 132, -28, -88, 133, -24, -89, + 134, -20, -90, 135, -16, -90, 136, -12, -91, 137, -8, -92, + 138, -3, -92, 139, 0, -93, 140, 4, -94, 141, 8, -94, + 142, 12, -95, 143, 16, -96, 143, 20, -96, 144, 24, -97, + 145, 28, -98, 146, 33, -99, 147, 37, -99, 148, 41, -100, + 149, 45, -100, 150, 49, -101, 151, 53, -102, 152, 57, -102, + 125, -70, -83, 126, -66, -84, 127, -62, -85, 128, -58, -85, + 129, -54, -86, 130, -50, -87, 131, -46, -87, 132, -41, -88, + 133, -37, -89, 134, -33, -89, 135, -29, -90, 135, -25, -91, + 136, -21, -91, 137, -17, -92, 138, -13, -93, 139, -9, -93, + 140, -4, -94, 141, 0, -95, 142, 3, -95, 143, 7, -96, + 144, 11, -97, 145, 15, -97, 146, 19, -98, 147, 23, -99, + 148, 27, -99, 149, 32, -100, 150, 36, -101, 150, 40, -101, + 151, 44, -102, 152, 48, -103, 153, 52, -104, 154, 56, -104, + 128, -72, -86, 129, -68, -86, 130, -64, -87, 131, -60, -88, + 132, -56, -88, 133, -52, -89, 134, -48, -90, 135, -43, -90, + 136, -39, -91, 137, -35, -92, 137, -31, -92, 138, -27, -93, + 139, -23, -94, 140, -19, -94, 141, -15, -95, 142, -11, -96, + 143, -6, -96, 144, -2, -97, 145, 1, -98, 146, 5, -98, + 147, 9, -99, 148, 13, -100, 149, 17, -100, 150, 21, -101, + 151, 25, -102, 152, 30, -102, 152, 34, -103, 153, 38, -104, + 154, 42, -104, 155, 46, -105, 156, 50, -106, 157, 54, -106, + 130, -73, -87, 131, -69, -88, 132, -65, -88, 133, -61, -89, + 134, -57, -90, 135, -53, -91, 136, -49, -91, 137, -44, -92, + 138, -40, -93, 139, -36, -93, 140, -32, -94, 141, -28, -95, + 142, -24, -95, 143, -20, -96, 144, -16, -97, 144, -12, -97, + 145, -7, -98, 146, -3, -99, 147, 0, -99, 148, 4, -100, + 149, 8, -101, 150, 12, -101, 151, 16, -102, 152, 20, -103, + 153, 24, -103, 154, 29, -104, 155, 33, -105, 156, 37, -105, + 157, 41, -106, 158, 45, -107, 159, 49, -107, 160, 53, -108, + 133, -75, -89, 134, -71, -90, 135, -67, -90, 136, -62, -91, + 137, -58, -92, 137, -54, -92, 138, -50, -93, 139, -46, -94, + 140, -42, -94, 141, -38, -95, 142, -34, -96, 143, -30, -96, + 144, -25, -97, 145, -21, -98, 146, -17, -98, 147, -13, -99, + 148, -9, -100, 149, -5, -100, 150, -1, -101, 151, 2, -102, + 152, 7, -102, 153, 11, -103, 153, 15, -104, 154, 19, -104, + 155, 23, -105, 156, 27, -106, 157, 31, -106, 158, 35, -107, + 159, 39, -108, 160, 44, -108, 161, 48, -109, 162, 52, -110, + 135, -76, -91, 136, -72, -91, 137, -68, -92, 138, -64, -93, + 139, -60, -93, 140, -56, -94, 141, -52, -95, 142, -47, -95, + 143, -43, -96, 144, -39, -97, 144, -35, -97, 145, -31, -98, + 146, -27, -99, 147, -23, -99, 148, -19, -100, 149, -15, -101, + 150, -10, -101, 151, -6, -102, 152, -2, -103, 153, 1, -103, + 154, 5, -104, 155, 9, -105, 156, 13, -105, 157, 17, -106, + 158, 21, -107, 159, 26, -107, 160, 30, -108, 160, 34, -109, + 161, 38, -109, 162, 42, -110, 163, 46, -111, 164, 50, -111, + 137, -77, -92, 138, -73, -93, 139, -69, -94, 140, -65, -94, + 141, -61, -95, 142, -57, -96, 143, -53, -96, 144, -48, -97, + 145, -44, -98, 146, -40, -98, 147, -36, -99, 148, -32, -100, + 149, -28, -100, 150, -24, -101, 151, -20, -102, 152, -16, -102, + 153, -11, -103, 153, -7, -104, 154, -3, -104, 155, 0, -105, + 156, 4, -106, 157, 8, -106, 158, 12, -107, 159, 16, -108, + 160, 20, -108, 161, 25, -109, 162, 29, -110, 163, 33, -110, + 164, 37, -111, 165, 41, -112, 166, 45, -112, 167, 49, -113, + 140, -79, -94, 141, -75, -95, 142, -71, -95, 143, -66, -96, + 144, -62, -97, 145, -58, -97, 145, -54, -98, 146, -50, -99, + 147, -46, -99, 148, -42, -100, 149, -38, -101, 150, -34, -101, + 151, -29, -102, 152, -25, -103, 153, -21, -103, 154, -17, -104, + 155, -13, -105, 156, -9, -105, 157, -5, -106, 158, -1, -107, + 159, 3, -107, 160, 7, -108, 160, 11, -109, 161, 15, -109, + 162, 19, -110, 163, 23, -111, 164, 27, -111, 165, 31, -112, + 166, 35, -113, 167, 40, -113, 168, 44, -114, 169, 48, -115, + 142, -80, -96, 143, -76, -96, 144, -72, -97, 145, -68, -98, + 146, -64, -98, 147, -60, -99, 148, -56, -100, 149, -51, -100, + 150, -47, -101, 151, -43, -102, 152, -39, -102, 152, -35, -103, + 153, -31, -104, 154, -27, -104, 155, -23, -105, 156, -19, -106, + 157, -14, -106, 158, -10, -107, 159, -6, -108, 160, -2, -108, + 161, 1, -109, 162, 5, -110, 163, 9, -110, 164, 13, -111, + 165, 17, -112, 166, 22, -112, 167, 26, -113, 167, 30, -114, + 168, 34, -114, 169, 38, -115, 170, 42, -116, 171, 46, -116, + 145, -81, -97, 145, -77, -98, 146, -73, -99, 147, -69, -99, + 148, -65, -100, 149, -61, -101, 150, -57, -101, 151, -52, -102, + 152, -48, -103, 153, -44, -103, 154, -40, -104, 155, -36, -105, + 156, -32, -105, 157, -28, -106, 158, -24, -107, 159, -20, -107, + 160, -15, -108, 160, -11, -109, 161, -7, -109, 162, -3, -110, + 163, 0, -111, 164, 4, -111, 165, 8, -112, 166, 12, -113, + 167, 16, -113, 168, 21, -114, 169, 25, -115, 170, 29, -115, + 171, 33, -116, 172, 37, -117, 173, 41, -117, 174, 45, -118, + 147, -83, -99, 148, -79, -100, 149, -75, -100, 150, -70, -101, + 151, -66, -102, 152, -62, -102, 152, -58, -103, 153, -54, -104, + 154, -50, -104, 155, -46, -105, 156, -42, -106, 157, -38, -106, + 158, -33, -107, 159, -29, -108, 160, -25, -108, 161, -21, -109, + 162, -17, -110, 163, -13, -110, 164, -9, -111, 165, -5, -112, + 166, 0, -112, 167, 3, -113, 168, 7, -114, 168, 11, -114, + 169, 15, -115, 170, 19, -116, 171, 23, -116, 172, 27, -117, + 173, 31, -118, 174, 36, -118, 175, 40, -119, 176, 44, -120, + 149, -84, -101, 150, -80, -101, 151, -76, -102, 152, -71, -103, + 153, -67, -103, 154, -63, -104, 155, -59, -105, 156, -55, -105, + 157, -51, -106, 158, -47, -107, 159, -43, -107, 159, -39, -108, + 161, -34, -109, 161, -30, -109, 162, -26, -110, 163, -22, -111, + 164, -18, -111, 165, -14, -112, 166, -10, -113, 167, -6, -113, + 168, -1, -114, 169, 2, -115, 170, 6, -115, 171, 10, -116, + 172, 14, -117, 173, 18, -117, 174, 22, -118, 175, 26, -119, + 175, 30, -119, 176, 35, -120, 177, 39, -121, 178, 43, -121, + 152, -85, -102, 152, -81, -103, 153, -77, -104, 154, -73, -104, + 155, -69, -105, 156, -65, -106, 157, -61, -106, 158, -56, -107, + 159, -52, -108, 160, -48, -108, 161, -44, -109, 162, -40, -110, + 163, -36, -110, 164, -32, -111, 165, -28, -112, 166, -24, -112, + 167, -19, -113, 168, -15, -114, 168, -11, -114, 169, -7, -115, + 170, -3, -116, 171, 0, -116, 172, 4, -117, 173, 8, -118, + 174, 12, -118, 175, 17, -119, 176, 21, -120, 177, 25, -120, + 178, 29, -121, 179, 33, -122, 180, 37, -122, 181, 41, -123, + 4, -2, 8, 5, 1, 7, 6, 5, 6, 7, 9, 5, + 8, 13, 5, 9, 17, 4, 10, 21, 4, 11, 26, 3, + 12, 30, 2, 13, 34, 1, 14, 38, 1, 15, 42, 0, + 16, 46, 0, 16, 50, 0, 17, 54, -1, 18, 58, -2, + 19, 63, -2, 20, 67, -3, 21, 71, -4, 22, 75, -4, + 23, 79, -5, 24, 83, -6, 25, 87, -6, 26, 91, -7, + 27, 95, -8, 28, 100, -8, 29, 104, -9, 30, 108, -10, + 31, 112, -10, 32, 116, -11, 32, 120, -12, 33, 124, -12, + 7, -4, 6, 8, 0, 5, 8, 3, 5, 9, 8, 4, + 10, 12, 3, 11, 16, 2, 12, 20, 2, 13, 24, 1, + 14, 28, 0, 15, 32, 0, 16, 36, 0, 17, 40, 0, + 18, 45, -1, 19, 49, -2, 20, 53, -3, 21, 57, -3, + 22, 61, -4, 23, 65, -5, 24, 69, -5, 24, 73, -6, + 25, 78, -7, 26, 82, -7, 27, 86, -8, 28, 90, -9, + 29, 94, -9, 30, 98, -10, 31, 102, -11, 32, 106, -11, + 33, 110, -12, 34, 115, -13, 35, 119, -13, 36, 123, -14, + 9, -5, 4, 10, -1, 4, 11, 2, 3, 12, 7, 2, + 13, 11, 1, 14, 15, 1, 15, 19, 0, 16, 23, 0, + 17, 27, 0, 17, 31, -1, 18, 35, -2, 19, 39, -2, + 20, 44, -3, 21, 48, -4, 22, 52, -4, 23, 56, -5, + 24, 60, -6, 25, 64, -6, 26, 68, -7, 27, 72, -8, + 28, 77, -8, 29, 81, -9, 30, 85, -10, 31, 89, -10, + 31, 93, -11, 32, 97, -12, 33, 101, -12, 34, 105, -13, + 35, 109, -14, 36, 114, -14, 37, 118, -15, 38, 122, -16, + 11, -6, 2, 12, -2, 2, 13, 1, 1, 14, 5, 0, + 15, 9, 0, 16, 13, 0, 17, 17, -1, 18, 22, -1, + 19, 26, -2, 20, 30, -3, 21, 34, -3, 22, 38, -4, + 23, 42, -5, 24, 46, -5, 24, 50, -6, 25, 54, -7, + 26, 59, -7, 27, 63, -8, 28, 67, -9, 29, 71, -9, + 30, 75, -10, 31, 79, -11, 32, 83, -11, 33, 87, -12, + 34, 91, -13, 35, 96, -13, 36, 100, -14, 37, 104, -15, + 38, 108, -15, 39, 112, -16, 39, 116, -17, 40, 120, -17, + 14, -8, 1, 15, -4, 0, 15, 0, 0, 17, 4, 0, + 17, 8, -1, 18, 12, -2, 19, 16, -2, 20, 21, -3, + 21, 25, -4, 22, 29, -4, 23, 33, -5, 24, 37, -6, + 25, 41, -6, 26, 45, -7, 27, 49, -8, 28, 53, -8, + 29, 58, -9, 30, 62, -10, 31, 66, -10, 31, 70, -11, + 32, 74, -12, 33, 78, -12, 34, 82, -13, 35, 86, -14, + 36, 90, -14, 37, 95, -15, 38, 99, -16, 39, 103, -16, + 40, 107, -17, 41, 111, -18, 42, 115, -18, 43, 119, -19, + 16, -9, 0, 17, -5, -1, 18, -1, -1, 19, 3, -2, + 20, 7, -3, 21, 11, -3, 22, 15, -4, 23, 19, -5, + 24, 23, -5, 24, 27, -6, 25, 31, -7, 26, 35, -7, + 27, 40, -8, 28, 44, -9, 29, 48, -9, 30, 52, -10, + 31, 56, -11, 32, 60, -11, 33, 64, -12, 34, 68, -13, + 35, 73, -13, 36, 77, -14, 37, 81, -15, 38, 85, -15, + 38, 89, -16, 40, 93, -17, 40, 97, -17, 41, 101, -18, + 42, 105, -19, 43, 110, -19, 44, 114, -20, 45, 118, -21, + 18, -10, -2, 19, -6, -2, 20, -2, -3, 21, 1, -4, + 22, 5, -4, 23, 9, -5, 24, 13, -6, 25, 18, -6, + 26, 22, -7, 27, 26, -8, 28, 30, -8, 29, 34, -9, + 30, 38, -10, 31, 42, -10, 31, 46, -11, 32, 50, -12, + 33, 55, -12, 34, 59, -13, 35, 63, -14, 36, 67, -14, + 37, 71, -15, 38, 75, -16, 39, 79, -16, 40, 83, -17, + 41, 87, -18, 42, 92, -18, 43, 96, -19, 44, 100, -20, + 45, 104, -20, 46, 108, -21, 47, 112, -22, 47, 116, -22, + 21, -11, -3, 22, -7, -4, 23, -3, -5, 24, 0, -5, + 24, 4, -6, 25, 8, -7, 26, 12, -7, 27, 17, -8, + 28, 21, -9, 29, 25, -9, 30, 29, -10, 31, 33, -11, + 32, 37, -11, 33, 41, -12, 34, 45, -13, 35, 49, -13, + 36, 54, -14, 37, 58, -15, 38, 62, -15, 39, 66, -16, + 40, 70, -17, 40, 74, -17, 41, 78, -18, 42, 82, -19, + 43, 86, -19, 44, 91, -20, 45, 95, -21, 46, 99, -21, + 47, 103, -22, 48, 107, -23, 49, 111, -23, 50, 115, -24, + 23, -13, -5, 24, -9, -6, 25, -5, -6, 26, 0, -7, + 27, 3, -8, 28, 7, -8, 29, 11, -9, 30, 15, -10, + 31, 19, -10, 32, 23, -11, 32, 27, -12, 33, 31, -12, + 34, 36, -13, 35, 40, -14, 36, 44, -14, 37, 48, -15, + 38, 52, -16, 39, 56, -16, 40, 60, -17, 41, 64, -18, + 42, 69, -18, 43, 73, -19, 44, 77, -20, 45, 81, -20, + 46, 85, -21, 47, 89, -22, 47, 93, -22, 48, 97, -23, + 49, 101, -24, 50, 106, -24, 51, 110, -25, 52, 114, -26, + 25, -14, -7, 26, -10, -7, 27, -6, -8, 28, -2, -9, + 29, 1, -9, 30, 5, -10, 31, 9, -11, 32, 14, -11, + 33, 18, -12, 34, 22, -13, 35, 26, -13, 36, 30, -14, + 37, 34, -15, 38, 38, -15, 39, 42, -16, 39, 46, -17, + 40, 51, -17, 41, 55, -18, 42, 59, -19, 43, 63, -19, + 44, 67, -20, 45, 71, -21, 46, 75, -21, 47, 79, -22, + 48, 83, -23, 49, 88, -23, 50, 92, -24, 51, 96, -25, + 52, 100, -25, 53, 104, -26, 54, 108, -27, 54, 112, -27, + 28, -15, -8, 29, -11, -9, 30, -7, -10, 31, -3, -10, + 32, 0, -11, 32, 4, -12, 33, 8, -12, 34, 13, -13, + 35, 17, -14, 36, 21, -14, 37, 25, -15, 38, 29, -16, + 39, 33, -16, 40, 37, -17, 41, 41, -18, 42, 45, -18, + 43, 50, -19, 44, 54, -20, 45, 58, -20, 46, 62, -21, + 47, 66, -22, 47, 70, -22, 48, 74, -23, 49, 78, -24, + 50, 82, -24, 51, 87, -25, 52, 91, -26, 53, 95, -26, + 54, 99, -27, 55, 103, -28, 56, 107, -28, 57, 111, -29, + 31, -17, -10, 32, -13, -11, 33, -9, -12, 34, -5, -12, + 34, -1, -13, 35, 2, -14, 36, 6, -14, 37, 11, -15, + 38, 15, -16, 39, 19, -16, 40, 23, -17, 41, 27, -18, + 42, 31, -18, 43, 35, -19, 44, 39, -20, 45, 43, -20, + 46, 48, -21, 47, 52, -22, 48, 56, -22, 48, 60, -23, + 50, 64, -24, 50, 68, -24, 51, 72, -25, 52, 76, -26, + 53, 80, -26, 54, 85, -27, 55, 89, -28, 56, 93, -28, + 57, 97, -29, 58, 101, -30, 59, 105, -30, 60, 109, -31, + 33, -18, -12, 34, -14, -13, 35, -10, -13, 36, -6, -14, + 37, -2, -15, 38, 1, -15, 39, 5, -16, 40, 10, -17, + 41, 14, -17, 41, 18, -18, 42, 22, -19, 43, 26, -19, + 44, 30, -20, 45, 34, -21, 46, 38, -21, 47, 42, -22, + 48, 47, -23, 49, 51, -23, 50, 55, -24, 51, 59, -25, + 52, 63, -25, 53, 67, -26, 54, 71, -27, 55, 75, -27, + 56, 79, -28, 57, 84, -29, 57, 88, -29, 58, 92, -30, + 59, 96, -31, 60, 100, -31, 61, 104, -32, 62, 108, -33, + 35, -20, -14, 36, -16, -14, 37, -12, -15, 38, -7, -16, + 39, -3, -16, 40, 0, -17, 41, 4, -18, 42, 8, -18, + 43, 12, -19, 44, 16, -20, 45, 20, -20, 46, 24, -21, + 47, 29, -22, 48, 33, -22, 49, 37, -23, 49, 41, -24, + 50, 45, -24, 51, 49, -25, 52, 53, -26, 53, 57, -26, + 54, 62, -27, 55, 66, -28, 56, 70, -28, 57, 74, -29, + 58, 78, -30, 59, 82, -30, 60, 86, -31, 61, 90, -32, + 62, 94, -32, 63, 99, -33, 64, 103, -34, 64, 107, -34, + 38, -21, -15, 39, -17, -16, 40, -13, -17, 41, -9, -17, + 42, -5, -18, 42, -1, -19, 43, 2, -19, 44, 7, -20, + 45, 11, -21, 46, 15, -21, 47, 19, -22, 48, 23, -23, + 49, 27, -23, 50, 31, -24, 51, 35, -25, 52, 39, -25, + 53, 44, -26, 54, 48, -27, 55, 52, -27, 56, 56, -28, + 57, 60, -29, 57, 64, -29, 58, 68, -30, 59, 72, -31, + 60, 76, -31, 61, 81, -32, 62, 85, -33, 63, 89, -33, + 64, 93, -34, 65, 97, -35, 66, 101, -35, 67, 105, -36, + 40, -22, -17, 41, -18, -18, 42, -14, -18, 43, -10, -19, + 44, -6, -20, 45, -2, -20, 46, 1, -21, 47, 6, -22, + 48, 10, -22, 49, 14, -23, 49, 18, -24, 50, 22, -24, + 51, 26, -25, 52, 30, -26, 53, 34, -26, 54, 38, -27, + 55, 43, -28, 56, 47, -28, 57, 51, -29, 58, 55, -30, + 59, 59, -30, 60, 63, -31, 61, 67, -32, 62, 71, -32, + 63, 75, -33, 64, 80, -34, 64, 84, -34, 65, 88, -35, + 66, 92, -36, 67, 96, -36, 68, 100, -37, 69, 104, -38, + 42, -24, -19, 43, -20, -19, 44, -16, -20, 45, -11, -21, + 46, -7, -21, 47, -3, -22, 48, 0, -23, 49, 4, -23, + 50, 8, -24, 51, 12, -25, 52, 16, -25, 53, 20, -26, + 54, 25, -27, 55, 29, -27, 56, 33, -28, 56, 37, -29, + 57, 41, -29, 58, 45, -30, 59, 49, -31, 60, 53, -31, + 61, 58, -32, 62, 62, -33, 63, 66, -33, 64, 70, -34, + 65, 74, -35, 66, 78, -35, 67, 82, -36, 68, 86, -37, + 69, 90, -37, 70, 95, -38, 71, 99, -39, 72, 103, -39, + 45, -25, -20, 46, -21, -21, 47, -17, -22, 48, -13, -22, + 49, -9, -23, 49, -5, -24, 50, -1, -24, 51, 3, -25, + 52, 7, -26, 53, 11, -26, 54, 15, -27, 55, 19, -28, + 56, 23, -28, 57, 27, -29, 58, 31, -30, 59, 35, -30, + 60, 40, -31, 61, 44, -32, 62, 48, -32, 63, 52, -33, + 64, 56, -34, 65, 60, -34, 65, 64, -35, 66, 68, -36, + 67, 72, -36, 68, 77, -37, 69, 81, -38, 70, 85, -38, + 71, 89, -39, 72, 93, -40, 73, 97, -40, 74, 101, -41, + 47, -26, -22, 48, -22, -23, 49, -18, -23, 50, -14, -24, + 51, -10, -25, 52, -6, -25, 53, -2, -26, 54, 2, -27, + 55, 6, -27, 56, 10, -28, 56, 14, -29, 57, 18, -29, + 58, 22, -30, 59, 26, -31, 60, 30, -31, 61, 34, -32, + 62, 39, -33, 63, 43, -33, 64, 47, -34, 65, 51, -35, + 66, 55, -35, 67, 59, -36, 68, 63, -37, 69, 67, -37, + 70, 71, -38, 71, 76, -39, 72, 80, -39, 72, 84, -40, + 73, 88, -41, 74, 92, -41, 75, 96, -42, 76, 100, -43, + 49, -28, -24, 50, -24, -24, 51, -20, -25, 52, -15, -26, + 53, -11, -26, 54, -7, -27, 55, -3, -28, 56, 0, -28, + 57, 4, -29, 58, 8, -30, 59, 12, -30, 60, 16, -31, + 61, 21, -32, 62, 25, -32, 63, 29, -33, 64, 33, -34, + 65, 37, -34, 65, 41, -35, 66, 45, -36, 67, 49, -36, + 68, 54, -37, 69, 58, -38, 70, 62, -38, 71, 66, -39, + 72, 70, -40, 73, 74, -40, 74, 78, -41, 75, 82, -42, + 76, 86, -42, 77, 91, -43, 78, 95, -44, 79, 99, -44, + 52, -29, -25, 53, -25, -26, 54, -21, -27, 55, -17, -27, + 56, -13, -28, 57, -9, -29, 57, -5, -29, 58, 0, -30, + 59, 3, -31, 60, 7, -31, 61, 11, -32, 62, 15, -33, + 63, 19, -33, 64, 23, -34, 65, 27, -35, 66, 31, -35, + 67, 36, -36, 68, 40, -37, 69, 44, -37, 70, 48, -38, + 71, 52, -39, 72, 56, -39, 72, 60, -40, 73, 64, -41, + 74, 68, -41, 75, 73, -42, 76, 77, -43, 77, 81, -43, + 78, 85, -44, 79, 89, -45, 80, 93, -45, 81, 97, -46, + 54, -30, -27, 55, -26, -28, 56, -22, -28, 57, -18, -29, + 58, -14, -30, 59, -10, -30, 60, -6, -31, 61, -1, -32, + 62, 2, -32, 63, 6, -33, 64, 10, -34, 64, 14, -34, + 65, 18, -35, 66, 22, -36, 67, 26, -36, 68, 30, -37, + 69, 35, -38, 70, 39, -38, 71, 43, -39, 72, 47, -40, + 73, 51, -41, 74, 55, -41, 75, 59, -42, 76, 63, -42, + 77, 67, -43, 78, 72, -44, 79, 76, -44, 79, 80, -45, + 80, 84, -46, 81, 88, -47, 82, 92, -47, 83, 96, -48, + 57, -32, -29, 57, -28, -29, 58, -24, -30, 59, -19, -31, + 60, -15, -31, 61, -11, -32, 62, -7, -33, 63, -3, -33, + 64, 0, -34, 65, 4, -35, 66, 8, -35, 67, 12, -36, + 68, 17, -37, 69, 21, -37, 70, 25, -38, 71, 29, -39, + 72, 33, -39, 72, 37, -40, 73, 41, -41, 74, 45, -41, + 75, 50, -42, 76, 54, -43, 77, 58, -43, 78, 62, -44, + 79, 66, -45, 80, 70, -46, 81, 74, -46, 82, 78, -47, + 83, 82, -47, 84, 87, -48, 85, 91, -49, 86, 95, -49, + 59, -33, -30, 60, -29, -31, 61, -25, -32, 62, -21, -32, + 63, -17, -33, 64, -13, -34, 64, -9, -34, 65, -4, -35, + 66, 0, -36, 67, 3, -36, 68, 7, -37, 69, 11, -38, + 70, 15, -38, 71, 19, -39, 72, 23, -40, 73, 27, -40, + 74, 32, -41, 75, 36, -42, 76, 40, -42, 77, 44, -43, + 78, 48, -44, 79, 52, -45, 80, 56, -45, 80, 60, -46, + 81, 64, -46, 82, 69, -47, 83, 73, -48, 84, 77, -48, + 85, 81, -49, 86, 85, -50, 87, 89, -51, 88, 93, -51, + 61, -34, -32, 62, -30, -33, 63, -26, -33, 64, -22, -34, + 65, -18, -35, 66, -14, -35, 67, -10, -36, 68, -5, -37, + 69, -1, -37, 70, 2, -38, 71, 6, -39, 71, 10, -39, + 73, 14, -40, 73, 18, -41, 74, 22, -41, 75, 26, -42, + 76, 31, -43, 77, 35, -43, 78, 39, -44, 79, 43, -45, + 80, 47, -46, 81, 51, -46, 82, 55, -47, 83, 59, -47, + 84, 63, -48, 85, 68, -49, 86, 72, -50, 87, 76, -50, + 87, 80, -51, 88, 84, -52, 89, 88, -52, 90, 92, -53, + 64, -36, -34, 64, -32, -34, 65, -28, -35, 66, -23, -36, + 67, -19, -36, 68, -15, -37, 69, -11, -38, 70, -7, -39, + 71, -3, -39, 72, 0, -40, 73, 4, -40, 74, 8, -41, + 75, 13, -42, 76, 17, -42, 77, 21, -43, 78, 25, -44, + 79, 29, -45, 80, 33, -45, 80, 37, -46, 81, 41, -46, + 82, 46, -47, 83, 50, -48, 84, 54, -49, 85, 58, -49, + 86, 62, -50, 87, 66, -51, 88, 70, -51, 89, 74, -52, + 90, 78, -52, 91, 83, -53, 92, 87, -54, 93, 91, -55, + 66, -37, -35, 67, -33, -36, 68, -29, -37, 69, -24, -37, + 70, -20, -38, 71, -16, -39, 72, -12, -39, 73, -8, -40, + 73, -4, -41, 74, 0, -41, 75, 3, -42, 76, 7, -43, + 77, 12, -44, 78, 16, -44, 79, 20, -45, 80, 24, -45, + 81, 28, -46, 82, 32, -47, 83, 36, -47, 84, 40, -48, + 85, 45, -49, 86, 49, -50, 87, 53, -50, 87, 57, -51, + 88, 61, -51, 89, 65, -52, 90, 69, -53, 91, 73, -54, + 92, 77, -54, 93, 82, -55, 94, 86, -56, 95, 90, -56, + 68, -38, -37, 69, -34, -38, 70, -30, -38, 71, -26, -39, + 72, -22, -40, 73, -18, -40, 74, -14, -41, 75, -9, -42, + 76, -5, -43, 77, -1, -43, 78, 2, -44, 79, 6, -44, + 80, 10, -45, 80, 14, -46, 81, 18, -46, 82, 22, -47, + 83, 27, -48, 84, 31, -49, 85, 35, -49, 86, 39, -50, + 87, 43, -51, 88, 47, -51, 89, 51, -52, 90, 55, -53, + 91, 59, -53, 92, 64, -54, 93, 68, -55, 94, 72, -55, + 94, 76, -56, 96, 80, -57, 96, 84, -57, 97, 88, -58, + 71, -40, -39, 72, -36, -39, 72, -32, -40, 73, -27, -41, + 74, -23, -41, 75, -19, -42, 76, -15, -43, 77, -11, -44, + 78, -7, -44, 79, -3, -45, 80, 0, -45, 81, 4, -46, + 82, 9, -47, 83, 13, -48, 84, 17, -48, 85, 21, -49, + 86, 25, -50, 87, 29, -50, 87, 33, -51, 88, 37, -51, + 89, 42, -52, 90, 46, -53, 91, 50, -54, 92, 54, -54, + 93, 58, -55, 94, 62, -56, 95, 66, -56, 96, 70, -57, + 97, 74, -58, 98, 79, -58, 99, 83, -59, 100, 87, -60, + 73, -41, -40, 74, -37, -41, 75, -33, -42, 76, -28, -43, + 77, -24, -43, 78, -20, -44, 79, -16, -44, 80, -12, -45, + 80, -8, -46, 81, -4, -47, 82, 0, -47, 83, 3, -48, + 84, 8, -49, 85, 12, -49, 86, 16, -50, 87, 20, -50, + 88, 24, -51, 89, 28, -52, 90, 32, -53, 91, 36, -53, + 92, 41, -54, 93, 45, -55, 94, 49, -55, 95, 53, -56, + 95, 57, -57, 96, 61, -57, 97, 65, -58, 98, 69, -59, + 99, 73, -59, 100, 78, -60, 101, 82, -61, 102, 86, -61, + 75, -42, -42, 76, -38, -43, 77, -34, -43, 78, -30, -44, + 79, -26, -45, 80, -22, -45, 81, -18, -46, 82, -13, -47, + 83, -9, -48, 84, -5, -48, 85, -1, -49, 86, 2, -49, + 87, 6, -50, 88, 10, -51, 88, 14, -52, 89, 18, -52, + 90, 23, -53, 91, 27, -54, 92, 31, -54, 93, 35, -55, + 94, 39, -56, 95, 43, -56, 96, 47, -57, 97, 51, -58, + 98, 55, -58, 99, 60, -59, 100, 64, -60, 101, 68, -60, + 102, 72, -61, 103, 76, -62, 103, 80, -62, 104, 84, -63, + 78, -44, -44, 79, -40, -44, 79, -36, -45, 81, -31, -46, + 81, -27, -47, 82, -23, -47, 83, -19, -48, 84, -15, -49, + 85, -11, -49, 86, -7, -50, 87, -3, -51, 88, 0, -51, + 89, 5, -52, 90, 9, -53, 91, 13, -53, 92, 17, -54, + 93, 21, -55, 94, 25, -55, 95, 29, -56, 95, 33, -57, + 96, 38, -57, 97, 42, -58, 98, 46, -59, 99, 50, -59, + 100, 54, -60, 101, 58, -61, 102, 62, -61, 103, 66, -62, + 104, 70, -63, 105, 75, -63, 106, 79, -64, 107, 83, -65, + 81, -45, -46, 82, -41, -47, 82, -37, -47, 83, -33, -48, + 84, -29, -49, 85, -25, -49, 86, -21, -50, 87, -16, -51, + 88, -12, -51, 89, -8, -52, 90, -4, -53, 91, 0, -53, + 92, 3, -54, 93, 7, -55, 94, 11, -55, 95, 15, -56, + 96, 20, -57, 97, 24, -57, 97, 28, -58, 98, 32, -59, + 99, 36, -59, 100, 40, -60, 101, 44, -61, 102, 48, -61, + 103, 52, -62, 104, 57, -63, 105, 61, -63, 106, 65, -64, + 107, 69, -65, 108, 73, -65, 109, 77, -66, 110, 81, -67, + 83, -47, -48, 84, -43, -48, 85, -39, -49, 86, -34, -50, + 87, -30, -50, 88, -26, -51, 89, -22, -52, 90, -18, -52, + 90, -14, -53, 91, -10, -54, 92, -6, -54, 93, -2, -55, + 94, 2, -56, 95, 6, -56, 96, 10, -57, 97, 14, -58, + 98, 18, -58, 99, 22, -59, 100, 26, -60, 101, 30, -60, + 102, 35, -61, 103, 39, -62, 104, 43, -62, 104, 47, -63, + 105, 51, -64, 106, 55, -64, 107, 59, -65, 108, 63, -66, + 109, 67, -66, 110, 72, -67, 111, 76, -68, 112, 80, -68, + 85, -48, -49, 86, -44, -50, 87, -40, -51, 88, -35, -51, + 89, -31, -52, 90, -27, -53, 91, -23, -53, 92, -19, -54, + 93, -15, -55, 94, -11, -55, 95, -7, -56, 96, -3, -57, + 97, 1, -57, 97, 5, -58, 98, 9, -59, 99, 13, -59, + 100, 17, -60, 101, 21, -61, 102, 25, -61, 103, 29, -62, + 104, 34, -63, 105, 38, -63, 106, 42, -64, 107, 46, -65, + 108, 50, -65, 109, 54, -66, 110, 58, -67, 111, 62, -67, + 112, 66, -68, 113, 71, -69, 113, 75, -69, 114, 79, -70, + 88, -49, -51, 89, -45, -52, 89, -41, -52, 90, -37, -53, + 91, -33, -54, 92, -29, -54, 93, -25, -55, 94, -20, -56, + 95, -16, -56, 96, -12, -57, 97, -8, -58, 98, -4, -58, + 99, 0, -59, 100, 3, -60, 101, 7, -60, 102, 11, -61, + 103, 16, -62, 104, 20, -62, 105, 24, -63, 105, 28, -64, + 106, 32, -64, 107, 36, -65, 108, 40, -66, 109, 44, -66, + 110, 48, -67, 111, 53, -68, 112, 57, -68, 113, 61, -69, + 114, 65, -70, 115, 69, -70, 116, 73, -71, 117, 77, -72, + 90, -51, -53, 91, -47, -53, 92, -43, -54, 93, -38, -55, + 94, -34, -55, 95, -30, -56, 96, -26, -57, 97, -22, -57, + 98, -18, -58, 98, -14, -59, 99, -10, -59, 100, -6, -60, + 101, -1, -61, 102, 2, -61, 103, 6, -62, 104, 10, -63, + 105, 14, -63, 106, 18, -64, 107, 22, -65, 108, 26, -65, + 109, 31, -66, 110, 35, -67, 111, 39, -67, 112, 43, -68, + 112, 47, -69, 113, 51, -69, 114, 55, -70, 115, 59, -71, + 116, 63, -71, 117, 68, -72, 118, 72, -73, 119, 76, -73, + 92, -52, -54, 93, -48, -55, 94, -44, -56, 95, -39, -56, + 96, -35, -57, 97, -31, -58, 98, -27, -58, 99, -23, -59, + 100, -19, -60, 101, -15, -60, 102, -11, -61, 103, -7, -62, + 104, -2, -62, 105, 1, -63, 105, 5, -64, 106, 9, -64, + 107, 13, -65, 108, 17, -66, 109, 21, -66, 110, 25, -67, + 111, 30, -68, 112, 34, -68, 113, 38, -69, 114, 42, -70, + 115, 46, -70, 116, 50, -71, 117, 54, -72, 118, 58, -72, + 119, 62, -73, 120, 67, -74, 120, 71, -74, 121, 75, -75, + 95, -53, -56, 96, -49, -57, 97, -45, -57, 98, -41, -58, + 98, -37, -59, 99, -33, -59, 100, -29, -60, 101, -24, -61, + 102, -20, -61, 103, -16, -62, 104, -12, -63, 105, -8, -63, + 106, -4, -64, 107, 0, -65, 108, 3, -65, 109, 7, -66, + 110, 12, -67, 111, 16, -67, 112, 20, -68, 112, 24, -69, + 113, 28, -69, 114, 32, -70, 115, 36, -71, 116, 40, -71, + 117, 44, -72, 118, 49, -73, 119, 53, -73, 120, 57, -74, + 121, 61, -75, 122, 65, -75, 123, 69, -76, 124, 73, -77, + 97, -55, -58, 98, -51, -58, 99, -47, -59, 100, -42, -60, + 101, -38, -60, 102, -34, -61, 103, -30, -62, 104, -26, -62, + 105, -22, -63, 105, -18, -64, 106, -14, -64, 107, -10, -65, + 108, -5, -66, 109, -1, -66, 110, 2, -67, 111, 6, -68, + 112, 10, -68, 113, 14, -69, 114, 18, -70, 115, 22, -70, + 116, 27, -71, 117, 31, -72, 118, 35, -72, 119, 39, -73, + 119, 43, -74, 121, 47, -74, 121, 51, -75, 122, 55, -76, + 123, 59, -76, 124, 64, -77, 125, 68, -78, 126, 72, -78, + 99, -56, -59, 100, -52, -60, 101, -48, -61, 102, -43, -61, + 103, -39, -62, 104, -35, -63, 105, -31, -63, 106, -27, -64, + 107, -23, -65, 108, -19, -65, 109, -15, -66, 110, -11, -67, + 111, -6, -67, 112, -2, -68, 112, 1, -69, 113, 5, -69, + 114, 9, -70, 115, 13, -71, 116, 17, -71, 117, 21, -72, + 118, 26, -73, 119, 30, -73, 120, 34, -74, 121, 38, -75, + 122, 42, -75, 123, 46, -76, 124, 50, -77, 125, 54, -77, + 126, 58, -78, 127, 63, -79, 128, 67, -79, 128, 71, -80, + 102, -57, -61, 103, -53, -62, 104, -49, -62, 105, -45, -63, + 105, -41, -64, 106, -37, -64, 107, -33, -65, 108, -28, -66, + 109, -24, -66, 110, -20, -67, 111, -16, -68, 112, -12, -68, + 113, -8, -69, 114, -4, -70, 115, 0, -70, 116, 3, -71, + 117, 8, -72, 118, 12, -72, 119, 16, -73, 120, 20, -74, + 121, 24, -74, 121, 28, -75, 122, 32, -76, 123, 36, -76, + 124, 40, -77, 125, 45, -78, 126, 49, -78, 127, 53, -79, + 128, 57, -80, 129, 61, -80, 130, 65, -81, 131, 69, -82, + 104, -59, -63, 105, -55, -63, 106, -51, -64, 107, -46, -65, + 108, -42, -65, 109, -38, -66, 110, -34, -67, 111, -30, -67, + 112, -26, -68, 113, -22, -69, 113, -18, -69, 114, -14, -70, + 115, -9, -71, 116, -5, -71, 117, -1, -72, 118, 2, -73, + 119, 6, -73, 120, 10, -74, 121, 14, -75, 122, 18, -75, + 123, 23, -76, 124, 27, -77, 125, 31, -77, 126, 35, -78, + 127, 39, -79, 128, 43, -79, 128, 47, -80, 129, 51, -81, + 130, 55, -81, 131, 60, -82, 132, 64, -83, 133, 68, -83, + 106, -60, -64, 107, -56, -65, 108, -52, -66, 109, -47, -66, + 110, -43, -67, 111, -39, -68, 112, -35, -68, 113, -31, -69, + 114, -27, -70, 115, -23, -70, 116, -19, -71, 117, -15, -72, + 118, -10, -72, 119, -6, -73, 120, -2, -74, 120, 1, -74, + 121, 5, -75, 122, 9, -76, 123, 13, -76, 124, 17, -77, + 125, 22, -78, 126, 26, -78, 127, 30, -79, 128, 34, -80, + 129, 38, -80, 130, 42, -81, 131, 46, -82, 132, 50, -82, + 133, 54, -83, 134, 59, -84, 135, 63, -84, 135, 67, -85, + 109, -61, -66, 110, -57, -67, 111, -53, -67, 112, -49, -68, + 113, -45, -69, 113, -41, -69, 114, -37, -70, 115, -32, -71, + 116, -28, -71, 117, -24, -72, 118, -20, -73, 119, -16, -73, + 120, -12, -74, 121, -8, -75, 122, -4, -75, 123, 0, -76, + 124, 4, -77, 125, 8, -77, 126, 12, -78, 127, 16, -79, + 128, 20, -79, 128, 24, -80, 129, 28, -81, 130, 32, -81, + 131, 36, -82, 132, 41, -83, 133, 45, -83, 134, 49, -84, + 135, 53, -85, 136, 57, -85, 137, 61, -86, 138, 65, -87, + 111, -62, -68, 112, -58, -68, 113, -54, -69, 114, -50, -70, + 115, -46, -70, 116, -42, -71, 117, -38, -72, 118, -33, -72, + 119, -29, -73, 120, -25, -74, 120, -21, -74, 121, -17, -75, + 122, -13, -76, 123, -9, -76, 124, -5, -77, 125, -1, -78, + 126, 3, -78, 127, 7, -79, 128, 11, -80, 129, 15, -80, + 130, 19, -81, 131, 23, -82, 132, 27, -82, 133, 31, -83, + 134, 35, -84, 135, 40, -84, 136, 44, -85, 136, 48, -86, + 137, 52, -86, 138, 56, -87, 139, 60, -88, 140, 64, -88, + 113, -64, -69, 114, -60, -70, 115, -56, -71, 116, -51, -71, + 117, -47, -72, 118, -43, -73, 119, -39, -73, 120, -35, -74, + 121, -31, -75, 122, -27, -75, 123, -23, -76, 124, -19, -77, + 125, -14, -77, 126, -10, -78, 127, -6, -79, 127, -2, -79, + 129, 1, -80, 129, 5, -81, 130, 9, -81, 131, 13, -82, + 132, 18, -83, 133, 22, -83, 134, 26, -84, 135, 30, -85, + 136, 34, -85, 137, 38, -86, 138, 42, -87, 139, 46, -87, + 140, 50, -88, 141, 55, -89, 142, 59, -89, 143, 63, -90, + 116, -65, -71, 117, -61, -72, 118, -57, -72, 119, -53, -73, + 120, -49, -74, 120, -45, -74, 121, -41, -75, 122, -36, -76, + 123, -32, -76, 124, -28, -77, 125, -24, -78, 126, -20, -78, + 127, -16, -79, 128, -12, -80, 129, -8, -80, 130, -4, -81, + 131, 0, -82, 132, 4, -82, 133, 8, -83, 134, 12, -84, + 135, 16, -84, 136, 20, -85, 136, 24, -86, 137, 28, -86, + 138, 32, -87, 139, 37, -88, 140, 41, -88, 141, 45, -89, + 142, 49, -90, 143, 53, -90, 144, 57, -91, 145, 61, -92, + 118, -66, -73, 119, -62, -73, 120, -58, -74, 121, -54, -75, + 122, -50, -75, 123, -46, -76, 124, -42, -77, 125, -37, -77, + 126, -33, -78, 127, -29, -79, 128, -25, -79, 128, -21, -80, + 129, -17, -81, 130, -13, -81, 131, -9, -82, 132, -5, -83, + 133, 0, -83, 134, 3, -84, 135, 7, -85, 136, 11, -85, + 137, 15, -86, 138, 19, -87, 139, 23, -87, 140, 27, -88, + 141, 31, -89, 142, 36, -89, 143, 40, -90, 143, 44, -91, + 144, 48, -91, 145, 52, -92, 146, 56, -93, 147, 60, -93, + 121, -68, -74, 121, -64, -75, 122, -60, -76, 123, -55, -76, + 124, -51, -77, 125, -47, -78, 126, -43, -78, 127, -39, -79, + 128, -35, -80, 129, -31, -80, 130, -27, -81, 131, -23, -82, + 132, -18, -82, 133, -14, -83, 134, -10, -84, 135, -6, -84, + 136, -2, -85, 136, 1, -86, 137, 5, -86, 138, 9, -87, + 139, 14, -88, 140, 18, -88, 141, 22, -89, 142, 26, -90, + 143, 30, -90, 144, 34, -91, 145, 38, -92, 146, 42, -92, + 147, 46, -93, 148, 51, -94, 149, 55, -94, 150, 59, -95, + 123, -69, -76, 124, -65, -77, 125, -61, -77, 126, -57, -78, + 127, -53, -79, 128, -49, -79, 128, -45, -80, 129, -40, -81, + 130, -36, -81, 131, -32, -82, 132, -28, -83, 133, -24, -83, + 134, -20, -84, 135, -16, -85, 136, -12, -85, 137, -8, -86, + 138, -3, -87, 139, 0, -87, 140, 4, -88, 141, 8, -89, + 142, 12, -89, 143, 16, -90, 143, 20, -91, 144, 24, -91, + 145, 28, -92, 146, 33, -93, 147, 37, -93, 148, 41, -94, + 149, 45, -95, 150, 49, -96, 151, 53, -96, 152, 57, -97, + 125, -70, -78, 126, -66, -78, 127, -62, -79, 128, -58, -80, + 129, -54, -80, 130, -50, -81, 131, -46, -82, 132, -41, -82, + 133, -37, -83, 134, -33, -84, 135, -29, -84, 135, -25, -85, + 136, -21, -86, 137, -17, -86, 138, -13, -87, 139, -9, -88, + 140, -4, -88, 141, 0, -89, 142, 3, -90, 143, 7, -90, + 144, 11, -91, 145, 15, -92, 146, 19, -92, 147, 23, -93, + 148, 27, -94, 149, 32, -95, 150, 36, -95, 151, 40, -96, + 151, 44, -96, 152, 48, -97, 153, 52, -98, 154, 56, -98, + 128, -72, -79, 128, -68, -80, 129, -64, -81, 130, -59, -81, + 131, -55, -82, 132, -51, -83, 133, -47, -83, 134, -43, -84, + 135, -39, -85, 136, -35, -85, 137, -31, -86, 138, -27, -87, + 139, -22, -87, 140, -18, -88, 141, -14, -89, 142, -10, -89, + 143, -6, -90, 144, -2, -91, 144, 1, -91, 145, 5, -92, + 146, 10, -93, 147, 14, -93, 148, 18, -94, 149, 22, -95, + 150, 26, -95, 151, 30, -96, 152, 34, -97, 153, 38, -97, + 154, 42, -98, 155, 47, -99, 156, 51, -100, 157, 55, -100, + 130, -73, -82, 131, -69, -82, 132, -65, -83, 133, -61, -84, + 134, -57, -84, 135, -53, -85, 136, -49, -86, 137, -44, -86, + 138, -40, -87, 139, -36, -88, 140, -32, -88, 141, -28, -89, + 142, -24, -90, 143, -20, -90, 144, -16, -91, 145, -12, -92, + 146, -7, -92, 146, -3, -93, 147, 0, -94, 148, 4, -94, + 149, 8, -95, 150, 12, -96, 151, 16, -96, 152, 20, -97, + 153, 24, -98, 154, 29, -98, 155, 33, -99, 156, 37, -100, + 157, 41, -100, 158, 45, -101, 159, 49, -102, 160, 53, -102, + 133, -75, -83, 134, -71, -84, 135, -67, -84, 136, -62, -85, + 137, -58, -86, 138, -54, -87, 138, -50, -87, 139, -46, -88, + 140, -42, -89, 141, -38, -89, 142, -34, -90, 143, -30, -91, + 144, -25, -91, 145, -21, -92, 146, -17, -93, 147, -13, -93, + 148, -9, -94, 149, -5, -95, 150, -1, -95, 151, 2, -96, + 152, 7, -97, 153, 11, -97, 153, 15, -98, 154, 19, -99, + 155, 23, -99, 156, 27, -100, 157, 31, -101, 158, 35, -101, + 159, 39, -102, 160, 44, -103, 161, 48, -103, 162, 52, -104, + 135, -76, -85, 136, -72, -86, 137, -68, -86, 138, -64, -87, + 139, -60, -88, 140, -56, -88, 141, -52, -89, 142, -47, -90, + 143, -43, -90, 144, -39, -91, 145, -35, -92, 145, -31, -92, + 146, -27, -93, 147, -23, -94, 148, -19, -94, 149, -15, -95, + 150, -10, -96, 151, -6, -96, 152, -2, -97, 153, 1, -98, + 154, 5, -98, 155, 9, -99, 156, 13, -100, 157, 17, -100, + 158, 21, -101, 159, 26, -102, 160, 30, -102, 160, 34, -103, + 161, 38, -104, 162, 42, -104, 163, 46, -105, 164, 50, -106, + 138, -77, -87, 138, -73, -87, 139, -69, -88, 140, -65, -89, + 141, -61, -89, 142, -57, -90, 143, -53, -91, 144, -48, -91, + 145, -44, -92, 146, -40, -93, 147, -36, -93, 148, -32, -94, + 149, -28, -95, 150, -24, -95, 151, -20, -96, 152, -16, -97, + 153, -11, -97, 153, -7, -98, 154, -3, -99, 155, 0, -99, + 156, 4, -100, 157, 8, -101, 158, 12, -101, 159, 16, -102, + 160, 20, -103, 161, 25, -103, 162, 29, -104, 163, 33, -105, + 164, 37, -105, 165, 41, -106, 166, 45, -107, 167, 49, -107, + 140, -79, -88, 141, -75, -89, 142, -71, -90, 143, -66, -90, + 144, -62, -91, 145, -58, -92, 145, -54, -92, 146, -50, -93, + 147, -46, -94, 148, -42, -94, 149, -38, -95, 150, -34, -96, + 151, -29, -96, 152, -25, -97, 153, -21, -98, 154, -17, -98, + 155, -13, -99, 156, -9, -100, 157, -5, -100, 158, -1, -101, + 159, 3, -102, 160, 7, -102, 161, 11, -103, 161, 15, -104, + 162, 19, -104, 163, 23, -105, 164, 27, -106, 165, 31, -106, + 166, 35, -107, 167, 40, -108, 168, 44, -108, 169, 48, -109, + 142, -80, -90, 143, -76, -91, 144, -72, -91, 145, -68, -92, + 146, -64, -93, 147, -60, -93, 148, -56, -94, 149, -51, -95, + 150, -47, -95, 151, -43, -96, 152, -39, -97, 152, -35, -97, + 154, -31, -98, 154, -27, -99, 155, -23, -99, 156, -19, -100, + 157, -14, -101, 158, -10, -101, 159, -6, -102, 160, -2, -103, + 161, 1, -103, 162, 5, -104, 163, 9, -105, 164, 13, -105, + 165, 17, -106, 166, 22, -107, 167, 26, -107, 168, 30, -108, + 168, 34, -109, 169, 38, -109, 170, 42, -110, 171, 46, -111, + 145, -81, -92, 145, -77, -92, 146, -73, -93, 147, -69, -94, + 148, -65, -94, 149, -61, -95, 150, -57, -96, 151, -52, -96, + 152, -48, -97, 153, -44, -98, 154, -40, -98, 155, -36, -99, + 156, -32, -100, 157, -28, -100, 158, -24, -101, 159, -20, -102, + 160, -15, -102, 161, -11, -103, 161, -7, -104, 162, -3, -104, + 163, 0, -105, 164, 4, -106, 165, 8, -106, 166, 12, -107, + 167, 16, -108, 168, 21, -108, 169, 25, -109, 170, 29, -110, + 171, 33, -110, 172, 37, -111, 173, 41, -112, 174, 45, -112, + 147, -83, -93, 148, -79, -94, 149, -75, -95, 150, -70, -95, + 151, -66, -96, 152, -62, -97, 153, -58, -97, 154, -54, -98, + 154, -50, -99, 155, -46, -99, 156, -42, -100, 157, -38, -101, + 158, -33, -101, 159, -29, -102, 160, -25, -103, 161, -21, -103, + 162, -17, -104, 163, -13, -105, 164, -9, -105, 165, -5, -106, + 166, 0, -107, 167, 3, -107, 168, 7, -108, 168, 11, -109, + 169, 15, -109, 170, 19, -110, 171, 23, -111, 172, 27, -111, + 173, 31, -112, 174, 36, -113, 175, 40, -113, 176, 44, -114, + 149, -84, -95, 150, -80, -96, 151, -76, -96, 152, -72, -97, + 153, -68, -98, 154, -64, -98, 155, -60, -99, 156, -55, -100, + 157, -51, -100, 158, -47, -101, 159, -43, -102, 160, -39, -102, + 161, -35, -103, 161, -31, -104, 162, -27, -104, 163, -23, -105, + 164, -18, -106, 165, -14, -106, 166, -10, -107, 167, -6, -108, + 168, -2, -108, 169, 1, -109, 170, 5, -110, 171, 9, -110, + 172, 13, -111, 173, 18, -112, 174, 22, -112, 175, 26, -113, + 175, 30, -114, 177, 34, -114, 177, 38, -115, 178, 42, -116, + 152, -85, -97, 153, -81, -97, 153, -77, -98, 154, -73, -99, + 155, -69, -99, 156, -65, -100, 157, -61, -101, 158, -56, -101, + 159, -52, -102, 160, -48, -103, 161, -44, -103, 162, -40, -104, + 163, -36, -105, 164, -32, -105, 165, -28, -106, 166, -24, -107, + 167, -19, -107, 168, -15, -108, 168, -11, -109, 169, -7, -109, + 170, -3, -110, 171, 0, -111, 172, 4, -111, 173, 8, -112, + 174, 12, -113, 175, 17, -113, 176, 21, -114, 177, 25, -115, + 178, 29, -115, 179, 33, -116, 180, 37, -117, 181, 41, -117, + 154, -87, -98, 155, -83, -99, 156, -79, -100, 157, -74, -100, + 158, -70, -101, 159, -66, -102, 160, -62, -102, 161, -58, -103, + 161, -54, -104, 162, -50, -104, 163, -46, -105, 164, -42, -106, + 165, -37, -106, 166, -33, -107, 167, -29, -108, 168, -25, -108, + 169, -21, -109, 170, -17, -110, 171, -13, -110, 172, -9, -111, + 173, -4, -112, 174, 0, -112, 175, 3, -113, 176, 7, -114, + 176, 11, -114, 177, 15, -115, 178, 19, -116, 179, 23, -116, + 180, 27, -117, 181, 32, -118, 182, 36, -118, 183, 40, -119, + 7, -4, 12, 8, 0, 11, 9, 3, 11, 10, 8, 10, + 11, 12, 9, 12, 16, 9, 13, 20, 8, 14, 24, 7, + 14, 28, 7, 15, 32, 6, 16, 36, 5, 17, 40, 5, + 18, 45, 4, 19, 49, 3, 20, 53, 3, 21, 57, 2, + 22, 61, 1, 23, 65, 1, 24, 69, 0, 25, 73, 0, + 26, 78, 0, 27, 82, -1, 28, 86, -2, 29, 90, -2, + 29, 94, -3, 30, 98, -4, 31, 102, -4, 32, 106, -5, + 33, 110, -6, 34, 115, -6, 35, 119, -7, 36, 123, -8, + 9, -5, 10, 10, -1, 10, 11, 2, 9, 12, 6, 8, + 13, 10, 8, 14, 14, 7, 15, 18, 6, 16, 23, 6, + 17, 27, 5, 18, 31, 4, 19, 35, 4, 20, 39, 3, + 21, 43, 2, 22, 47, 2, 22, 51, 1, 23, 55, 0, + 24, 60, 0, 25, 64, 0, 26, 68, -1, 27, 72, -1, + 28, 76, -2, 29, 80, -3, 30, 84, -3, 31, 88, -4, + 32, 92, -5, 33, 97, -5, 34, 101, -6, 35, 105, -7, + 36, 109, -7, 37, 113, -8, 37, 117, -9, 38, 121, -9, + 12, -6, 9, 13, -2, 8, 13, 1, 7, 15, 5, 7, + 15, 9, 6, 16, 13, 5, 17, 17, 5, 18, 22, 4, + 19, 26, 3, 20, 30, 3, 21, 34, 2, 22, 38, 1, + 23, 42, 1, 24, 46, 0, 25, 50, 0, 26, 54, 0, + 27, 59, -1, 28, 63, -2, 29, 67, -2, 29, 71, -3, + 30, 75, -4, 31, 79, -4, 32, 83, -5, 33, 87, -6, + 34, 91, -6, 35, 96, -7, 36, 100, -8, 37, 104, -8, + 38, 108, -9, 39, 112, -10, 40, 116, -10, 41, 120, -11, + 14, -8, 7, 15, -4, 6, 16, 0, 6, 17, 4, 5, + 18, 8, 4, 19, 12, 4, 20, 16, 3, 21, 20, 2, + 22, 24, 2, 22, 28, 1, 23, 32, 0, 24, 36, 0, + 25, 41, 0, 26, 45, -1, 27, 49, -1, 28, 53, -2, + 29, 57, -3, 30, 61, -3, 31, 65, -4, 32, 69, -5, + 33, 74, -5, 34, 78, -6, 35, 82, -7, 36, 86, -7, + 36, 90, -8, 38, 94, -9, 38, 98, -9, 39, 102, -10, + 40, 106, -11, 41, 111, -11, 42, 115, -12, 43, 119, -13, + 16, -9, 5, 17, -5, 5, 18, -1, 4, 19, 2, 3, + 20, 6, 3, 21, 10, 2, 22, 14, 1, 23, 19, 1, + 24, 23, 0, 25, 27, 0, 26, 31, 0, 27, 35, -1, + 28, 39, -2, 29, 43, -2, 29, 47, -3, 30, 51, -4, + 31, 56, -4, 32, 60, -5, 33, 64, -6, 34, 68, -6, + 35, 72, -7, 36, 76, -8, 37, 80, -8, 38, 84, -9, + 39, 88, -10, 40, 93, -10, 41, 97, -11, 42, 101, -12, + 43, 105, -12, 44, 109, -13, 45, 113, -14, 45, 117, -14, + 19, -10, 4, 20, -6, 3, 21, -2, 2, 22, 1, 2, + 22, 5, 1, 23, 9, 0, 24, 13, 0, 25, 18, 0, + 26, 22, -1, 27, 26, -1, 28, 30, -2, 29, 34, -3, + 30, 38, -3, 31, 42, -4, 32, 46, -5, 33, 50, -5, + 34, 55, -6, 35, 59, -7, 36, 63, -7, 36, 67, -8, + 38, 71, -9, 38, 75, -9, 39, 79, -10, 40, 83, -11, + 41, 87, -11, 42, 92, -12, 43, 96, -13, 44, 100, -13, + 45, 104, -14, 46, 108, -15, 47, 112, -15, 48, 116, -16, + 21, -12, 2, 22, -8, 1, 23, -4, 1, 24, 0, 0, + 25, 4, 0, 26, 8, 0, 27, 12, -1, 28, 16, -2, + 29, 20, -2, 29, 24, -3, 30, 28, -4, 31, 32, -4, + 32, 37, -5, 33, 41, -6, 34, 45, -6, 35, 49, -7, + 36, 53, -8, 37, 57, -8, 38, 61, -9, 39, 65, -10, + 40, 70, -10, 41, 74, -11, 42, 78, -12, 43, 82, -12, + 44, 86, -13, 45, 90, -14, 45, 94, -14, 46, 98, -15, + 47, 102, -16, 48, 107, -16, 49, 111, -17, 50, 115, -18, + 23, -13, 0, 24, -9, 0, 25, -5, 0, 26, 0, -1, + 27, 3, -1, 28, 7, -2, 29, 11, -3, 30, 15, -3, + 31, 19, -4, 32, 23, -5, 33, 27, -5, 34, 31, -6, + 35, 36, -7, 36, 40, -7, 37, 44, -8, 37, 48, -9, + 38, 52, -9, 39, 56, -10, 40, 60, -11, 41, 64, -11, + 42, 69, -12, 43, 73, -13, 44, 77, -13, 45, 81, -14, + 46, 85, -15, 47, 89, -15, 48, 93, -16, 49, 97, -17, + 50, 101, -17, 51, 106, -18, 52, 110, -19, 52, 114, -19, + 26, -14, 0, 27, -10, -1, 28, -6, -2, 29, -2, -2, + 30, 1, -3, 30, 5, -4, 31, 9, -4, 32, 14, -5, + 33, 18, -6, 34, 22, -6, 35, 26, -7, 36, 30, -8, + 37, 34, -8, 38, 38, -9, 39, 42, -10, 40, 46, -10, + 41, 51, -11, 42, 55, -12, 43, 59, -12, 44, 63, -13, + 45, 67, -14, 45, 71, -14, 46, 75, -15, 47, 79, -16, + 48, 83, -16, 49, 88, -17, 50, 92, -18, 51, 96, -18, + 52, 100, -19, 53, 104, -20, 54, 108, -20, 55, 112, -21, + 28, -16, -2, 29, -12, -3, 30, -8, -3, 31, -3, -4, + 32, 0, -5, 33, 4, -5, 34, 8, -6, 35, 12, -7, + 36, 16, -7, 37, 20, -8, 37, 24, -9, 38, 28, -9, + 39, 33, -10, 40, 37, -11, 41, 41, -11, 42, 45, -12, + 43, 49, -13, 44, 53, -13, 45, 57, -14, 46, 61, -15, + 47, 66, -15, 48, 70, -16, 49, 74, -17, 50, 78, -17, + 51, 82, -18, 52, 86, -19, 53, 90, -19, 53, 94, -20, + 54, 98, -21, 55, 103, -22, 56, 107, -22, 57, 111, -23, + 30, -17, -4, 31, -13, -4, 32, -9, -5, 33, -4, -6, + 34, 0, -6, 35, 3, -7, 36, 7, -8, 37, 11, -8, + 38, 15, -9, 39, 19, -10, 40, 23, -10, 41, 27, -11, + 42, 32, -12, 43, 36, -12, 44, 40, -13, 44, 44, -14, + 46, 48, -14, 46, 52, -15, 47, 56, -16, 48, 60, -16, + 49, 65, -17, 50, 69, -18, 51, 73, -18, 52, 77, -19, + 53, 81, -20, 54, 85, -20, 55, 89, -21, 56, 93, -22, + 57, 97, -22, 58, 102, -23, 59, 106, -24, 60, 110, -24, + 33, -19, -6, 34, -15, -6, 35, -11, -7, 36, -6, -8, + 37, -2, -9, 38, 1, -9, 39, 5, -10, 40, 9, -11, + 41, 13, -11, 42, 17, -12, 43, 21, -13, 44, 25, -13, + 45, 30, -14, 46, 34, -15, 47, 38, -15, 47, 42, -16, + 48, 46, -17, 49, 50, -17, 50, 54, -18, 51, 58, -19, + 52, 63, -19, 53, 67, -20, 54, 71, -21, 55, 75, -21, + 56, 79, -22, 57, 83, -23, 58, 87, -23, 59, 91, -24, + 60, 95, -25, 61, 100, -25, 62, 104, -26, 62, 108, -27, + 36, -20, -8, 37, -16, -8, 38, -12, -9, 39, -7, -10, + 40, -3, -10, 40, 0, -11, 41, 4, -12, 42, 8, -12, + 43, 12, -13, 44, 16, -14, 45, 20, -14, 46, 24, -15, + 47, 29, -16, 48, 33, -16, 49, 37, -17, 50, 41, -18, + 51, 45, -18, 52, 49, -19, 53, 53, -20, 54, 57, -20, + 55, 62, -21, 55, 66, -22, 56, 70, -22, 57, 74, -23, + 58, 78, -24, 59, 82, -24, 60, 86, -25, 61, 90, -26, + 62, 94, -26, 63, 99, -27, 64, 103, -28, 65, 107, -28, + 38, -21, -9, 39, -17, -10, 40, -13, -10, 41, -9, -11, + 42, -5, -12, 43, -1, -13, 44, 2, -13, 45, 7, -14, + 46, 11, -15, 47, 15, -15, 47, 19, -16, 48, 23, -17, + 49, 27, -17, 50, 31, -18, 51, 35, -19, 52, 39, -19, + 53, 44, -20, 54, 48, -21, 55, 52, -21, 56, 56, -22, + 57, 60, -23, 58, 64, -23, 59, 68, -24, 60, 72, -25, + 61, 76, -25, 62, 81, -26, 62, 85, -27, 63, 89, -27, + 64, 93, -28, 65, 97, -29, 66, 101, -29, 67, 105, -30, + 40, -23, -11, 41, -19, -12, 42, -15, -12, 43, -10, -13, + 44, -6, -14, 45, -2, -14, 46, 1, -15, 47, 5, -16, + 48, 9, -16, 49, 13, -17, 50, 17, -18, 51, 21, -18, + 52, 26, -19, 53, 30, -20, 54, 34, -20, 54, 38, -21, + 55, 42, -22, 56, 46, -22, 57, 50, -23, 58, 54, -24, + 59, 59, -24, 60, 63, -25, 61, 67, -26, 62, 71, -26, + 63, 75, -27, 64, 79, -28, 65, 83, -28, 66, 87, -29, + 67, 91, -30, 68, 96, -30, 69, 100, -31, 70, 104, -32, + 43, -24, -13, 44, -20, -13, 45, -16, -14, 46, -11, -15, + 47, -7, -15, 47, -3, -16, 48, 0, -17, 49, 4, -17, + 50, 8, -18, 51, 12, -19, 52, 16, -19, 53, 20, -20, + 54, 25, -21, 55, 29, -21, 56, 33, -22, 57, 37, -23, + 58, 41, -23, 59, 45, -24, 60, 49, -25, 61, 53, -25, + 62, 58, -26, 63, 62, -27, 63, 66, -27, 64, 70, -28, + 65, 74, -29, 66, 78, -29, 67, 82, -30, 68, 86, -31, + 69, 90, -31, 70, 95, -32, 71, 99, -33, 72, 103, -33, + 45, -25, -14, 46, -21, -15, 47, -17, -16, 48, -13, -16, + 49, -9, -17, 50, -5, -18, 51, -1, -18, 52, 3, -19, + 53, 7, -20, 54, 11, -20, 54, 15, -21, 55, 19, -22, + 56, 23, -22, 57, 27, -23, 58, 31, -24, 59, 35, -24, + 60, 40, -25, 61, 44, -26, 62, 48, -26, 63, 52, -27, + 64, 56, -28, 65, 60, -28, 66, 64, -29, 67, 68, -30, + 68, 72, -30, 69, 77, -31, 70, 81, -32, 70, 85, -32, + 71, 89, -33, 72, 93, -34, 73, 97, -34, 74, 101, -35, + 47, -27, -16, 48, -23, -17, 49, -19, -17, 50, -14, -18, + 51, -10, -19, 52, -6, -19, 53, -2, -20, 54, 1, -21, + 55, 5, -21, 56, 9, -22, 57, 13, -23, 58, 17, -23, + 59, 22, -24, 60, 26, -25, 61, 30, -25, 62, 34, -26, + 63, 38, -27, 63, 42, -27, 64, 46, -28, 65, 50, -29, + 66, 55, -29, 67, 59, -30, 68, 63, -31, 69, 67, -31, + 70, 71, -32, 71, 75, -33, 72, 79, -33, 73, 83, -34, + 74, 87, -35, 75, 92, -35, 76, 96, -36, 77, 100, -37, + 50, -28, -18, 51, -24, -18, 52, -20, -19, 53, -15, -20, + 54, -11, -20, 55, -7, -21, 55, -3, -22, 56, 0, -22, + 57, 4, -23, 58, 8, -24, 59, 12, -24, 60, 16, -25, + 61, 21, -26, 62, 25, -26, 63, 29, -27, 64, 33, -28, + 65, 37, -28, 66, 41, -29, 67, 45, -30, 68, 49, -30, + 69, 54, -31, 70, 58, -32, 70, 62, -32, 71, 66, -33, + 72, 70, -34, 73, 74, -34, 74, 78, -35, 75, 82, -36, + 76, 86, -36, 77, 91, -37, 78, 95, -38, 79, 99, -38, + 52, -29, -19, 53, -25, -20, 54, -21, -21, 55, -17, -21, + 56, -13, -22, 57, -9, -23, 58, -5, -23, 59, 0, -24, + 60, 3, -25, 61, 7, -25, 62, 11, -26, 62, 15, -27, + 63, 19, -27, 64, 23, -28, 65, 27, -29, 66, 31, -29, + 67, 36, -30, 68, 40, -31, 69, 44, -31, 70, 48, -32, + 71, 52, -33, 72, 56, -33, 73, 60, -34, 74, 64, -35, + 75, 68, -35, 76, 73, -36, 77, 77, -37, 77, 81, -37, + 78, 85, -38, 79, 89, -39, 80, 93, -39, 81, 97, -40, + 55, -31, -21, 55, -27, -22, 56, -23, -22, 57, -18, -23, + 58, -14, -24, 59, -10, -24, 60, -6, -25, 61, -2, -26, + 62, 1, -26, 63, 5, -27, 64, 9, -28, 65, 13, -28, + 66, 18, -29, 67, 22, -30, 68, 26, -30, 69, 30, -31, + 70, 34, -32, 70, 38, -32, 71, 42, -33, 72, 46, -34, + 73, 51, -34, 74, 55, -35, 75, 59, -36, 76, 63, -36, + 77, 67, -37, 78, 71, -38, 79, 75, -38, 80, 79, -39, + 81, 83, -40, 82, 88, -40, 83, 92, -41, 84, 96, -42, + 57, -32, -23, 58, -28, -23, 59, -24, -24, 60, -19, -25, + 61, -15, -25, 62, -11, -26, 62, -7, -27, 63, -3, -27, + 64, 0, -28, 65, 4, -29, 66, 8, -29, 67, 12, -30, + 68, 17, -31, 69, 21, -31, 70, 25, -32, 71, 29, -33, + 72, 33, -33, 73, 37, -34, 74, 41, -35, 75, 45, -35, + 76, 50, -36, 77, 54, -37, 78, 58, -37, 78, 62, -38, + 79, 66, -39, 80, 70, -39, 81, 74, -40, 82, 78, -41, + 83, 82, -41, 84, 87, -42, 85, 91, -43, 86, 95, -43, + 59, -33, -24, 60, -29, -25, 61, -25, -26, 62, -21, -26, + 63, -17, -27, 64, -13, -28, 65, -9, -28, 66, -4, -29, + 67, 0, -30, 68, 3, -30, 69, 7, -31, 69, 11, -32, + 71, 15, -32, 71, 19, -33, 72, 23, -34, 73, 27, -34, + 74, 32, -35, 75, 36, -36, 76, 40, -36, 77, 44, -37, + 78, 48, -38, 79, 52, -38, 80, 56, -39, 81, 60, -40, + 82, 64, -40, 83, 69, -41, 84, 73, -42, 85, 77, -42, + 85, 81, -43, 86, 85, -44, 87, 89, -44, 88, 93, -45, + 62, -35, -26, 62, -31, -27, 63, -27, -27, 64, -22, -28, + 65, -18, -29, 66, -14, -29, 67, -10, -30, 68, -6, -31, + 69, -2, -31, 70, 1, -32, 71, 5, -33, 72, 9, -33, + 73, 14, -34, 74, 18, -35, 75, 22, -35, 76, 26, -36, + 77, 30, -37, 78, 34, -37, 78, 38, -38, 79, 42, -39, + 80, 47, -39, 81, 51, -40, 82, 55, -41, 83, 59, -41, + 84, 63, -42, 85, 67, -43, 86, 71, -43, 87, 75, -44, + 88, 79, -45, 89, 84, -45, 90, 88, -46, 91, 92, -47, + 64, -36, -28, 65, -32, -28, 66, -28, -29, 67, -23, -30, + 68, -19, -30, 69, -15, -31, 70, -11, -32, 71, -7, -32, + 71, -3, -33, 72, 0, -34, 73, 4, -34, 74, 8, -35, + 75, 13, -36, 76, 17, -36, 77, 21, -37, 78, 25, -38, + 79, 29, -38, 80, 33, -39, 81, 37, -40, 82, 41, -40, + 83, 46, -41, 84, 50, -42, 85, 54, -42, 85, 58, -43, + 86, 62, -44, 87, 66, -44, 88, 70, -45, 89, 74, -46, + 90, 78, -46, 91, 83, -47, 92, 87, -48, 93, 91, -48, + 66, -37, -29, 67, -33, -30, 68, -29, -31, 69, -25, -31, + 70, -21, -32, 71, -17, -33, 72, -13, -33, 73, -8, -34, + 74, -4, -35, 75, 0, -35, 76, 3, -36, 77, 7, -37, + 78, 11, -37, 78, 15, -38, 79, 19, -39, 80, 23, -39, + 81, 28, -40, 82, 32, -41, 83, 36, -41, 84, 40, -42, + 85, 44, -43, 86, 48, -43, 87, 52, -44, 88, 56, -45, + 89, 60, -45, 90, 65, -46, 91, 69, -47, 92, 73, -47, + 92, 77, -48, 94, 81, -49, 94, 85, -49, 95, 89, -50, + 69, -39, -31, 70, -35, -32, 70, -31, -32, 71, -26, -33, + 72, -22, -34, 73, -18, -34, 74, -14, -35, 75, -10, -36, + 76, -6, -36, 77, -2, -37, 78, 1, -38, 79, 5, -38, + 80, 10, -39, 81, 14, -40, 82, 18, -40, 83, 22, -41, + 84, 26, -42, 85, 30, -42, 85, 34, -43, 86, 38, -44, + 87, 43, -44, 88, 47, -45, 89, 51, -46, 90, 55, -46, + 91, 59, -47, 92, 63, -48, 93, 67, -48, 94, 71, -49, + 95, 75, -50, 96, 80, -50, 97, 84, -51, 98, 88, -52, + 71, -40, -33, 72, -36, -33, 73, -32, -34, 74, -27, -35, + 75, -23, -35, 76, -19, -36, 77, -15, -37, 78, -11, -37, + 78, -7, -38, 79, -3, -39, 80, 0, -39, 81, 4, -40, + 82, 9, -41, 83, 13, -41, 84, 17, -42, 85, 21, -43, + 86, 25, -43, 87, 29, -44, 88, 33, -45, 89, 37, -45, + 90, 42, -46, 91, 46, -47, 92, 50, -47, 93, 54, -48, + 93, 58, -49, 94, 62, -49, 95, 66, -50, 96, 70, -51, + 97, 74, -51, 98, 79, -52, 99, 83, -53, 100, 87, -53, + 73, -41, -34, 74, -37, -35, 75, -33, -36, 76, -29, -36, + 77, -25, -37, 78, -21, -38, 79, -17, -38, 80, -12, -39, + 81, -8, -40, 82, -4, -40, 83, 0, -41, 84, 3, -42, + 85, 7, -42, 86, 11, -43, 86, 15, -44, 87, 19, -44, + 88, 24, -45, 89, 28, -46, 90, 32, -46, 91, 36, -47, + 92, 40, -48, 93, 44, -48, 94, 48, -49, 95, 52, -50, + 96, 56, -50, 97, 61, -51, 98, 65, -52, 99, 69, -52, + 100, 73, -53, 101, 77, -54, 101, 81, -54, 102, 85, -55, + 76, -42, -36, 77, -38, -37, 77, -34, -37, 79, -30, -38, + 79, -26, -39, 80, -22, -39, 81, -18, -40, 82, -13, -41, + 83, -9, -41, 84, -5, -42, 85, -1, -43, 86, 2, -43, + 87, 6, -44, 88, 10, -45, 89, 14, -45, 90, 18, -46, + 91, 23, -47, 92, 27, -47, 93, 31, -48, 93, 35, -49, + 94, 39, -49, 95, 43, -50, 96, 47, -51, 97, 51, -51, + 98, 55, -52, 99, 60, -53, 100, 64, -53, 101, 68, -54, + 102, 72, -55, 103, 76, -55, 104, 80, -56, 105, 84, -57, + 78, -44, -38, 79, -40, -38, 80, -36, -39, 81, -31, -40, + 82, -27, -40, 83, -23, -41, 84, -19, -42, 85, -15, -42, + 86, -11, -43, 86, -7, -44, 87, -3, -44, 88, 0, -45, + 89, 5, -46, 90, 9, -46, 91, 13, -47, 92, 17, -48, + 93, 21, -48, 94, 25, -49, 95, 29, -50, 96, 33, -50, + 97, 38, -51, 98, 42, -52, 99, 46, -52, 100, 50, -53, + 100, 54, -54, 101, 58, -54, 102, 62, -55, 103, 66, -56, + 104, 70, -56, 105, 75, -57, 106, 79, -58, 107, 83, -58, + 80, -45, -39, 81, -41, -40, 82, -37, -41, 83, -33, -41, + 84, -29, -42, 85, -25, -43, 86, -21, -43, 87, -16, -44, + 88, -12, -45, 89, -8, -45, 90, -4, -46, 91, 0, -47, + 92, 3, -47, 93, 7, -48, 93, 11, -49, 94, 15, -49, + 95, 20, -50, 96, 24, -51, 97, 28, -51, 98, 32, -52, + 99, 36, -53, 100, 40, -53, 101, 44, -54, 102, 48, -55, + 103, 52, -55, 104, 57, -56, 105, 61, -57, 106, 65, -57, + 107, 69, -58, 108, 73, -59, 109, 77, -59, 109, 81, -60, + 83, -47, -41, 84, -43, -42, 85, -39, -43, 86, -34, -43, + 87, -30, -44, 88, -26, -45, 89, -22, -45, 90, -18, -46, + 91, -14, -47, 92, -10, -47, 93, -6, -48, 94, -2, -49, + 95, 2, -49, 95, 6, -50, 96, 10, -51, 97, 14, -51, + 98, 18, -52, 99, 22, -53, 100, 26, -53, 101, 30, -54, + 102, 35, -55, 103, 39, -55, 104, 43, -56, 105, 47, -57, + 106, 51, -57, 107, 55, -58, 108, 59, -59, 109, 63, -59, + 110, 67, -60, 111, 72, -61, 111, 76, -62, 112, 80, -62, + 86, -48, -43, 87, -44, -44, 87, -40, -44, 88, -36, -45, + 89, -32, -46, 90, -28, -46, 91, -24, -47, 92, -19, -48, + 93, -15, -48, 94, -11, -49, 95, -7, -50, 96, -3, -50, + 97, 0, -51, 98, 4, -52, 99, 8, -52, 100, 12, -53, + 101, 17, -54, 102, 21, -54, 103, 25, -55, 103, 29, -56, + 104, 33, -57, 105, 37, -57, 106, 41, -58, 107, 45, -58, + 108, 49, -59, 109, 54, -60, 110, 58, -61, 111, 62, -61, + 112, 66, -62, 113, 70, -63, 114, 74, -63, 115, 78, -64, + 88, -49, -45, 89, -45, -45, 90, -41, -46, 91, -37, -47, + 92, -33, -47, 93, -29, -48, 94, -25, -49, 95, -20, -49, + 96, -16, -50, 96, -12, -51, 97, -8, -51, 98, -4, -52, + 99, 0, -53, 100, 3, -53, 101, 7, -54, 102, 11, -55, + 103, 16, -56, 104, 20, -56, 105, 24, -57, 106, 28, -57, + 107, 32, -58, 108, 36, -59, 109, 40, -59, 110, 44, -60, + 110, 48, -61, 111, 53, -62, 112, 57, -62, 113, 61, -63, + 114, 65, -63, 115, 69, -64, 116, 73, -65, 117, 77, -66, + 90, -51, -46, 91, -47, -47, 92, -43, -48, 93, -38, -48, + 94, -34, -49, 95, -30, -50, 96, -26, -50, 97, -22, -51, + 98, -18, -52, 99, -14, -52, 100, -10, -53, 101, -6, -54, + 102, -1, -55, 103, 2, -55, 103, 6, -56, 104, 10, -56, + 105, 14, -57, 106, 18, -58, 107, 22, -58, 108, 26, -59, + 109, 31, -60, 110, 35, -61, 111, 39, -61, 112, 43, -62, + 113, 47, -62, 114, 51, -63, 115, 55, -64, 116, 59, -65, + 117, 63, -65, 118, 68, -66, 118, 72, -67, 119, 76, -67, + 93, -52, -48, 94, -48, -49, 95, -44, -49, 96, -40, -50, + 96, -36, -51, 97, -32, -51, 98, -28, -52, 99, -23, -53, + 100, -19, -53, 101, -15, -54, 102, -11, -55, 103, -7, -55, + 104, -3, -56, 105, 0, -57, 106, 4, -57, 107, 8, -58, + 108, 13, -59, 109, 17, -60, 110, 21, -60, 110, 25, -61, + 111, 29, -62, 112, 33, -62, 113, 37, -63, 114, 41, -63, + 115, 45, -64, 116, 50, -65, 117, 54, -66, 118, 58, -66, + 119, 62, -67, 120, 66, -68, 121, 70, -68, 122, 74, -69, + 95, -53, -50, 96, -49, -50, 97, -45, -51, 98, -41, -52, + 99, -37, -52, 100, -33, -53, 101, -29, -54, 102, -24, -55, + 103, -20, -55, 103, -16, -56, 104, -12, -56, 105, -8, -57, + 106, -4, -58, 107, 0, -59, 108, 3, -59, 109, 7, -60, + 110, 12, -61, 111, 16, -61, 112, 20, -62, 113, 24, -62, + 114, 28, -63, 115, 32, -64, 116, 36, -65, 117, 40, -65, + 117, 44, -66, 119, 49, -67, 119, 53, -67, 120, 57, -68, + 121, 61, -69, 122, 65, -69, 123, 69, -70, 124, 73, -71, + 97, -55, -51, 98, -51, -52, 99, -47, -53, 100, -42, -54, + 101, -38, -54, 102, -34, -55, 103, -30, -55, 104, -26, -56, + 105, -22, -57, 106, -18, -57, 107, -14, -58, 108, -10, -59, + 109, -5, -60, 110, -1, -60, 110, 2, -61, 111, 6, -61, + 112, 10, -62, 113, 14, -63, 114, 18, -64, 115, 22, -64, + 116, 27, -65, 117, 31, -66, 118, 35, -66, 119, 39, -67, + 120, 43, -67, 121, 47, -68, 122, 51, -69, 123, 55, -70, + 124, 59, -70, 125, 64, -71, 126, 68, -72, 126, 72, -72, + 100, -56, -53, 101, -52, -54, 102, -48, -54, 103, -44, -55, + 103, -40, -56, 104, -36, -56, 105, -32, -57, 106, -27, -58, + 107, -23, -59, 108, -19, -59, 109, -15, -60, 110, -11, -60, + 111, -7, -61, 112, -3, -62, 113, 0, -63, 114, 4, -63, + 115, 9, -64, 116, 13, -65, 117, 17, -65, 118, 21, -66, + 119, 25, -67, 119, 29, -67, 120, 33, -68, 121, 37, -69, + 122, 41, -69, 123, 46, -70, 124, 50, -71, 125, 54, -71, + 126, 58, -72, 127, 62, -73, 128, 66, -73, 129, 70, -74, + 102, -57, -55, 103, -53, -55, 104, -49, -56, 105, -45, -57, + 106, -41, -58, 107, -37, -58, 108, -33, -59, 109, -28, -60, + 110, -24, -60, 111, -20, -61, 111, -16, -61, 112, -12, -62, + 113, -8, -63, 114, -4, -64, 115, 0, -64, 116, 3, -65, + 117, 8, -66, 118, 12, -66, 119, 16, -67, 120, 20, -68, + 121, 24, -68, 122, 28, -69, 123, 32, -70, 124, 36, -70, + 125, 40, -71, 126, 45, -72, 126, 49, -72, 127, 53, -73, + 128, 57, -74, 129, 61, -74, 130, 65, -75, 131, 69, -76, + 104, -59, -57, 105, -55, -57, 106, -51, -58, 107, -46, -59, + 108, -42, -59, 109, -38, -60, 110, -34, -60, 111, -30, -61, + 112, -26, -62, 113, -22, -63, 114, -18, -63, 115, -14, -64, + 116, -9, -65, 117, -5, -65, 118, -1, -66, 118, 2, -67, + 119, 6, -67, 120, 10, -68, 121, 14, -69, 122, 18, -69, + 123, 23, -70, 124, 27, -71, 125, 31, -71, 126, 35, -72, + 127, 39, -73, 128, 43, -73, 129, 47, -74, 130, 51, -75, + 131, 55, -75, 132, 60, -76, 133, 64, -77, 133, 68, -77, + 107, -60, -58, 108, -56, -59, 109, -52, -59, 110, -48, -60, + 111, -44, -61, 111, -40, -62, 112, -36, -62, 113, -31, -63, + 114, -27, -64, 115, -23, -64, 116, -19, -65, 117, -15, -65, + 118, -11, -66, 119, -7, -67, 120, -3, -68, 121, 0, -68, + 122, 5, -69, 123, 9, -70, 124, 13, -70, 125, 17, -71, + 126, 21, -72, 126, 25, -72, 127, 29, -73, 128, 33, -74, + 129, 37, -74, 130, 42, -75, 131, 46, -76, 132, 50, -76, + 133, 54, -77, 134, 58, -78, 135, 62, -78, 136, 66, -79, + 109, -61, -60, 110, -57, -61, 111, -53, -61, 112, -49, -62, + 113, -45, -63, 114, -41, -63, 115, -37, -64, 116, -32, -65, + 117, -28, -65, 118, -24, -66, 118, -20, -67, 119, -16, -67, + 120, -12, -68, 121, -8, -69, 122, -4, -69, 123, 0, -70, + 124, 4, -71, 125, 8, -71, 126, 12, -72, 127, 16, -73, + 128, 20, -73, 129, 24, -74, 130, 28, -75, 131, 32, -75, + 132, 36, -76, 133, 41, -77, 134, 45, -77, 134, 49, -78, + 135, 53, -79, 136, 57, -79, 137, 61, -80, 138, 65, -81, + 111, -63, -62, 112, -59, -62, 113, -55, -63, 114, -50, -64, + 115, -46, -64, 116, -42, -65, 117, -38, -66, 118, -34, -66, + 119, -30, -67, 120, -26, -68, 121, -22, -68, 122, -18, -69, + 123, -13, -70, 124, -9, -70, 125, -5, -71, 125, -1, -72, + 127, 2, -72, 127, 6, -73, 128, 10, -74, 129, 14, -74, + 130, 19, -75, 131, 23, -76, 132, 27, -76, 133, 31, -77, + 134, 35, -78, 135, 39, -78, 136, 43, -79, 137, 47, -80, + 138, 51, -80, 139, 56, -81, 140, 60, -82, 141, 64, -82, + 114, -64, -63, 115, -60, -64, 116, -56, -65, 117, -52, -65, + 118, -48, -66, 118, -44, -67, 119, -40, -67, 120, -35, -68, + 121, -31, -69, 122, -27, -69, 123, -23, -70, 124, -19, -71, + 125, -15, -71, 126, -11, -72, 127, -7, -73, 128, -3, -73, + 129, 1, -74, 130, 5, -75, 131, 9, -75, 132, 13, -76, + 133, 17, -77, 134, 21, -77, 134, 25, -78, 135, 29, -79, + 136, 33, -79, 137, 38, -80, 138, 42, -81, 139, 46, -81, + 140, 50, -82, 141, 54, -83, 142, 58, -83, 143, 62, -84, + 116, -65, -65, 117, -61, -66, 118, -57, -66, 119, -53, -67, + 120, -49, -68, 121, -45, -68, 122, -41, -69, 123, -36, -70, + 124, -32, -70, 125, -28, -71, 126, -24, -72, 126, -20, -72, + 127, -16, -73, 128, -12, -74, 129, -8, -74, 130, -4, -75, + 131, 0, -76, 132, 4, -76, 133, 8, -77, 134, 12, -78, + 135, 16, -78, 136, 20, -79, 137, 24, -80, 138, 28, -80, + 139, 32, -81, 140, 37, -82, 141, 41, -82, 141, 45, -83, + 142, 49, -84, 143, 53, -84, 144, 57, -85, 145, 61, -86, + 119, -67, -67, 119, -63, -67, 120, -59, -68, 121, -54, -69, + 122, -50, -69, 123, -46, -70, 124, -42, -71, 125, -38, -71, + 126, -34, -72, 127, -30, -73, 128, -26, -73, 129, -22, -74, + 130, -17, -75, 131, -13, -75, 132, -9, -76, 133, -5, -77, + 134, -1, -77, 134, 2, -78, 135, 6, -79, 136, 10, -79, + 137, 15, -80, 138, 19, -81, 139, 23, -81, 140, 27, -82, + 141, 31, -83, 142, 35, -83, 143, 39, -84, 144, 43, -85, + 145, 47, -85, 146, 52, -86, 147, 56, -87, 148, 60, -87, + 121, -68, -68, 122, -64, -69, 123, -60, -70, 124, -55, -70, + 125, -51, -71, 126, -47, -72, 126, -43, -72, 127, -39, -73, + 128, -35, -74, 129, -31, -74, 130, -27, -75, 131, -23, -76, + 132, -18, -76, 133, -14, -77, 134, -10, -78, 135, -6, -78, + 136, -2, -79, 137, 1, -80, 138, 5, -80, 139, 9, -81, + 140, 14, -82, 141, 18, -82, 141, 22, -83, 142, 26, -84, + 143, 30, -84, 144, 34, -85, 145, 38, -86, 146, 42, -86, + 147, 46, -87, 148, 51, -88, 149, 55, -88, 150, 59, -89, + 123, -69, -70, 124, -65, -71, 125, -61, -71, 126, -57, -72, + 127, -53, -73, 128, -49, -73, 129, -45, -74, 130, -40, -75, + 131, -36, -75, 132, -32, -76, 133, -28, -77, 133, -24, -77, + 134, -20, -78, 135, -16, -79, 136, -12, -79, 137, -8, -80, + 138, -3, -81, 139, 0, -81, 140, 4, -82, 141, 8, -83, + 142, 12, -83, 143, 16, -84, 144, 20, -85, 145, 24, -85, + 146, 28, -86, 147, 33, -87, 148, 37, -87, 149, 41, -88, + 149, 45, -89, 150, 49, -89, 151, 53, -90, 152, 57, -91, + 126, -71, -72, 126, -67, -72, 127, -63, -73, 128, -58, -74, + 129, -54, -74, 130, -50, -75, 131, -46, -76, 132, -42, -76, + 133, -38, -77, 134, -34, -78, 135, -30, -78, 136, -26, -79, + 137, -21, -80, 138, -17, -80, 139, -13, -81, 140, -9, -82, + 141, -5, -82, 142, -1, -83, 142, 2, -84, 143, 6, -84, + 144, 11, -85, 145, 15, -86, 146, 19, -86, 147, 23, -87, + 148, 27, -88, 149, 31, -88, 150, 35, -89, 151, 39, -90, + 152, 43, -90, 153, 48, -91, 154, 52, -92, 155, 56, -92, + 128, -72, -73, 129, -68, -74, 130, -64, -75, 131, -59, -75, + 132, -55, -76, 133, -51, -77, 133, -47, -77, 135, -43, -78, + 135, -39, -79, 136, -35, -79, 137, -31, -80, 138, -27, -81, + 139, -22, -81, 140, -18, -82, 141, -14, -83, 142, -10, -83, + 143, -6, -84, 144, -2, -85, 145, 1, -85, 146, 5, -86, + 147, 10, -87, 148, 14, -87, 149, 18, -88, 149, 22, -89, + 150, 26, -89, 151, 30, -90, 152, 34, -91, 153, 38, -91, + 154, 42, -92, 155, 47, -93, 156, 51, -93, 157, 55, -94, + 130, -73, -75, 131, -69, -76, 132, -65, -76, 133, -61, -77, + 134, -57, -78, 135, -53, -78, 136, -49, -79, 137, -44, -80, + 138, -40, -80, 139, -36, -81, 140, -32, -82, 141, -28, -82, + 142, -24, -83, 142, -20, -84, 143, -16, -84, 144, -12, -85, + 145, -7, -86, 146, -3, -86, 147, 0, -87, 148, 4, -88, + 149, 8, -88, 150, 12, -89, 151, 16, -90, 152, 20, -90, + 153, 24, -91, 154, 29, -92, 155, 33, -92, 156, 37, -93, + 156, 41, -94, 157, 45, -94, 158, 49, -95, 159, 53, -96, + 133, -75, -77, 134, -71, -78, 135, -67, -78, 136, -62, -79, + 137, -58, -80, 138, -54, -80, 139, -50, -81, 140, -46, -82, + 141, -42, -82, 142, -38, -83, 143, -34, -84, 143, -30, -84, + 144, -25, -85, 145, -21, -86, 146, -17, -86, 147, -13, -87, + 148, -9, -88, 149, -5, -88, 150, -1, -89, 151, 2, -90, + 152, 7, -90, 153, 11, -91, 154, 15, -92, 155, 19, -92, + 156, 23, -93, 157, 27, -94, 158, 31, -94, 158, 35, -95, + 159, 39, -96, 160, 44, -96, 161, 48, -97, 162, 52, -98, + 136, -76, -79, 136, -72, -79, 137, -68, -80, 138, -64, -81, + 139, -60, -81, 140, -56, -82, 141, -52, -83, 142, -47, -83, + 143, -43, -84, 144, -39, -85, 145, -35, -85, 146, -31, -86, + 147, -27, -87, 148, -23, -87, 149, -19, -88, 150, -15, -89, + 151, -10, -89, 151, -6, -90, 152, -2, -91, 153, 1, -91, + 154, 5, -92, 155, 9, -93, 156, 13, -93, 157, 17, -94, + 158, 21, -95, 159, 26, -95, 160, 30, -96, 161, 34, -97, + 162, 38, -97, 163, 42, -98, 164, 46, -99, 165, 50, -99, + 138, -78, -80, 139, -74, -81, 140, -70, -82, 141, -65, -82, + 142, -61, -83, 143, -57, -84, 143, -53, -84, 144, -49, -85, + 145, -45, -86, 146, -41, -86, 147, -37, -87, 148, -33, -88, + 149, -28, -88, 150, -24, -89, 151, -20, -90, 152, -16, -90, + 153, -12, -91, 154, -8, -92, 155, -4, -92, 156, 0, -93, + 157, 4, -94, 158, 8, -94, 159, 12, -95, 159, 16, -96, + 160, 20, -96, 161, 24, -97, 162, 28, -98, 163, 32, -98, + 164, 36, -99, 165, 41, -100, 166, 45, -100, 167, 49, -101, + 140, -79, -82, 141, -75, -83, 142, -71, -83, 143, -66, -84, + 144, -62, -85, 145, -58, -85, 146, -54, -86, 147, -50, -87, + 148, -46, -87, 149, -42, -88, 150, -38, -89, 150, -34, -89, + 152, -29, -90, 152, -25, -91, 153, -21, -91, 154, -17, -92, + 155, -13, -93, 156, -9, -93, 157, -5, -94, 158, -1, -95, + 159, 3, -95, 160, 7, -96, 161, 11, -97, 162, 15, -97, + 163, 19, -98, 164, 23, -99, 165, 27, -99, 166, 31, -100, + 166, 35, -101, 167, 40, -101, 168, 44, -102, 169, 48, -103, + 143, -80, -84, 143, -76, -84, 144, -72, -85, 145, -68, -86, + 146, -64, -86, 147, -60, -87, 148, -56, -88, 149, -51, -88, + 150, -47, -89, 151, -43, -90, 152, -39, -90, 153, -35, -91, + 154, -31, -92, 155, -27, -92, 156, -23, -93, 157, -19, -94, + 158, -14, -94, 159, -10, -95, 159, -6, -96, 160, -2, -96, + 161, 1, -97, 162, 5, -98, 163, 9, -98, 164, 13, -99, + 165, 17, -100, 166, 22, -100, 167, 26, -101, 168, 30, -102, + 169, 34, -102, 170, 38, -103, 171, 42, -104, 172, 46, -104, + 145, -82, -85, 146, -78, -86, 147, -74, -87, 148, -69, -87, + 149, -65, -88, 150, -61, -89, 151, -57, -89, 152, -53, -90, + 152, -49, -91, 153, -45, -91, 154, -41, -92, 155, -37, -93, + 156, -32, -93, 157, -28, -94, 158, -24, -95, 159, -20, -95, + 160, -16, -96, 161, -12, -97, 162, -8, -97, 163, -4, -98, + 164, 0, -99, 165, 4, -99, 166, 8, -100, 166, 12, -101, + 167, 16, -101, 168, 20, -102, 169, 24, -103, 170, 28, -103, + 171, 32, -104, 172, 37, -105, 173, 41, -105, 174, 45, -106, + 147, -83, -87, 148, -79, -88, 149, -75, -88, 150, -70, -89, + 151, -66, -90, 152, -62, -90, 153, -58, -91, 154, -54, -92, + 155, -50, -92, 156, -46, -93, 157, -42, -94, 158, -38, -94, + 159, -33, -95, 159, -29, -96, 160, -25, -96, 161, -21, -97, + 162, -17, -98, 163, -13, -98, 164, -9, -99, 165, -5, -100, + 166, 0, -100, 167, 3, -101, 168, 7, -102, 169, 11, -102, + 170, 15, -103, 171, 19, -104, 172, 23, -104, 173, 27, -105, + 173, 31, -106, 175, 36, -107, 175, 40, -107, 176, 44, -108, + 150, -84, -89, 151, -80, -89, 151, -76, -90, 152, -72, -91, + 153, -68, -91, 154, -64, -92, 155, -60, -93, 156, -55, -93, + 157, -51, -94, 158, -47, -95, 159, -43, -95, 160, -39, -96, + 161, -35, -97, 162, -31, -97, 163, -27, -98, 164, -23, -99, + 165, -18, -99, 166, -14, -100, 166, -10, -101, 167, -6, -101, + 168, -2, -102, 169, 1, -103, 170, 5, -103, 171, 9, -104, + 172, 13, -105, 173, 18, -105, 174, 22, -106, 175, 26, -107, + 176, 30, -107, 177, 34, -108, 178, 38, -109, 179, 42, -109, + 152, -86, -90, 153, -82, -91, 154, -78, -92, 155, -73, -92, + 156, -69, -93, 157, -65, -94, 158, -61, -94, 159, -57, -95, + 159, -53, -96, 160, -49, -96, 161, -45, -97, 162, -41, -98, + 163, -36, -98, 164, -32, -99, 165, -28, -100, 166, -24, -100, + 167, -20, -101, 168, -16, -102, 169, -12, -102, 170, -8, -103, + 171, -3, -104, 172, 0, -104, 173, 4, -105, 174, 8, -106, + 174, 12, -106, 175, 16, -107, 176, 20, -108, 177, 24, -108, + 178, 28, -109, 179, 33, -110, 180, 37, -111, 181, 41, -111, + 154, -87, -92, 155, -83, -93, 156, -79, -93, 157, -74, -94, + 158, -70, -95, 159, -66, -95, 160, -62, -96, 161, -58, -97, + 162, -54, -97, 163, -50, -98, 164, -46, -99, 165, -42, -99, + 166, -37, -100, 167, -33, -101, 167, -29, -101, 168, -25, -102, + 169, -21, -103, 170, -17, -103, 171, -13, -104, 172, -9, -105, + 173, -4, -106, 174, 0, -106, 175, 3, -107, 176, 7, -107, + 177, 11, -108, 178, 15, -109, 179, 19, -109, 180, 23, -110, + 181, 27, -111, 182, 32, -112, 182, 36, -112, 183, 40, -113, + 157, -88, -94, 158, -84, -94, 158, -80, -95, 160, -76, -96, + 160, -72, -96, 161, -68, -97, 162, -64, -98, 163, -59, -98, + 164, -55, -99, 165, -51, -100, 166, -47, -100, 167, -43, -101, + 168, -39, -102, 169, -35, -102, 170, -31, -103, 171, -27, -104, + 172, -22, -104, 173, -18, -105, 174, -14, -106, 174, -10, -106, + 175, -6, -107, 176, -2, -108, 177, 1, -108, 178, 5, -109, + 179, 9, -110, 180, 14, -111, 181, 18, -111, 182, 22, -112, + 183, 26, -112, 184, 30, -113, 185, 34, -114, 186, 38, -115, + 9, -5, 16, 10, -1, 15, 11, 2, 15, 12, 6, 14, + 13, 10, 13, 14, 14, 13, 15, 18, 12, 16, 23, 11, + 17, 27, 11, 18, 31, 10, 19, 35, 9, 20, 39, 9, + 21, 43, 8, 22, 47, 7, 22, 51, 7, 23, 55, 6, + 24, 60, 5, 25, 64, 5, 26, 68, 4, 27, 72, 3, + 28, 76, 3, 29, 80, 2, 30, 84, 1, 31, 88, 1, + 32, 92, 0, 33, 97, 0, 34, 101, 0, 35, 105, -1, + 36, 109, -2, 37, 113, -2, 38, 117, -3, 38, 121, -4, + 12, -6, 14, 13, -2, 14, 14, 1, 13, 15, 5, 12, + 15, 9, 12, 16, 13, 11, 17, 17, 10, 18, 22, 10, + 19, 26, 9, 20, 30, 8, 21, 34, 8, 22, 38, 7, + 23, 42, 6, 24, 46, 6, 25, 50, 5, 26, 54, 4, + 27, 59, 4, 28, 63, 3, 29, 67, 2, 29, 71, 2, + 31, 75, 1, 31, 79, 0, 32, 83, 0, 33, 87, 0, + 34, 91, -1, 35, 96, -1, 36, 100, -2, 37, 104, -3, + 38, 108, -3, 39, 112, -4, 40, 116, -5, 41, 120, -5, + 14, -8, 13, 15, -4, 12, 16, 0, 11, 17, 4, 11, + 18, 8, 10, 19, 12, 9, 20, 16, 9, 21, 20, 8, + 22, 24, 7, 22, 28, 7, 23, 32, 6, 24, 36, 5, + 25, 41, 5, 26, 45, 4, 27, 49, 3, 28, 53, 3, + 29, 57, 2, 30, 61, 1, 31, 65, 1, 32, 69, 0, + 33, 74, 0, 34, 78, 0, 35, 82, -1, 36, 86, -2, + 37, 90, -2, 38, 94, -3, 38, 98, -4, 39, 102, -4, + 40, 106, -5, 41, 111, -6, 42, 115, -6, 43, 119, -7, + 16, -9, 11, 17, -5, 10, 18, -1, 10, 19, 2, 9, + 20, 6, 8, 21, 10, 8, 22, 14, 7, 23, 19, 6, + 24, 23, 6, 25, 27, 5, 26, 31, 4, 27, 35, 4, + 28, 39, 3, 29, 43, 2, 30, 47, 2, 30, 51, 1, + 31, 56, 0, 32, 60, 0, 33, 64, 0, 34, 68, -1, + 35, 72, -1, 36, 76, -2, 37, 80, -3, 38, 84, -3, + 39, 88, -4, 40, 93, -5, 41, 97, -5, 42, 101, -6, + 43, 105, -7, 44, 109, -7, 45, 113, -8, 45, 117, -9, + 19, -10, 9, 20, -6, 9, 21, -2, 8, 22, 1, 7, + 23, 5, 7, 23, 9, 6, 24, 13, 5, 25, 18, 5, + 26, 22, 4, 27, 26, 3, 28, 30, 3, 29, 34, 2, + 30, 38, 1, 31, 42, 1, 32, 46, 0, 33, 50, 0, + 34, 55, 0, 35, 59, -1, 36, 63, -2, 37, 67, -2, + 38, 71, -3, 38, 75, -4, 39, 79, -4, 40, 83, -5, + 41, 87, -6, 42, 92, -6, 43, 96, -7, 44, 100, -8, + 45, 104, -8, 46, 108, -9, 47, 112, -10, 48, 116, -10, + 21, -12, 8, 22, -8, 7, 23, -4, 6, 24, 0, 6, + 25, 4, 5, 26, 8, 4, 27, 12, 4, 28, 16, 3, + 29, 20, 2, 30, 24, 2, 30, 28, 1, 31, 32, 0, + 32, 37, 0, 33, 41, 0, 34, 45, -1, 35, 49, -1, + 36, 53, -2, 37, 57, -3, 38, 61, -3, 39, 65, -4, + 40, 70, -5, 41, 74, -5, 42, 78, -6, 43, 82, -7, + 44, 86, -7, 45, 90, -8, 46, 94, -9, 46, 98, -9, + 47, 102, -10, 48, 107, -11, 49, 111, -11, 50, 115, -12, + 23, -13, 6, 24, -9, 5, 25, -5, 5, 26, -1, 4, + 27, 2, 3, 28, 6, 3, 29, 10, 2, 30, 15, 1, + 31, 19, 1, 32, 23, 0, 33, 27, 0, 34, 31, 0, + 35, 35, -1, 36, 39, -2, 37, 43, -2, 37, 47, -3, + 39, 52, -4, 39, 56, -4, 40, 60, -5, 41, 64, -6, + 42, 68, -6, 43, 72, -7, 44, 76, -8, 45, 80, -8, + 46, 84, -9, 47, 89, -10, 48, 93, -10, 49, 97, -11, + 50, 101, -12, 51, 105, -12, 52, 109, -13, 53, 113, -14, + 26, -14, 4, 27, -10, 4, 28, -6, 3, 29, -2, 2, + 30, 1, 2, 30, 5, 1, 31, 9, 0, 32, 14, 0, + 33, 18, 0, 34, 22, -1, 35, 26, -1, 36, 30, -2, + 37, 34, -3, 38, 38, -3, 39, 42, -4, 40, 46, -5, + 41, 51, -5, 42, 55, -6, 43, 59, -7, 44, 63, -7, + 45, 67, -8, 46, 71, -9, 46, 75, -9, 47, 79, -10, + 48, 83, -11, 49, 88, -11, 50, 92, -12, 51, 96, -13, + 52, 100, -13, 53, 104, -14, 54, 108, -15, 55, 112, -15, + 28, -16, 3, 29, -12, 2, 30, -8, 1, 31, -3, 1, + 32, 0, 0, 33, 4, 0, 34, 8, 0, 35, 12, -1, + 36, 16, -2, 37, 20, -2, 37, 24, -3, 38, 28, -4, + 39, 33, -4, 40, 37, -5, 41, 41, -6, 42, 45, -6, + 43, 49, -7, 44, 53, -8, 45, 57, -8, 46, 61, -9, + 47, 66, -10, 48, 70, -10, 49, 74, -11, 50, 78, -12, + 51, 82, -12, 52, 86, -13, 53, 90, -14, 53, 94, -14, + 54, 98, -15, 55, 103, -16, 56, 107, -16, 57, 111, -17, + 30, -17, 1, 31, -13, 0, 32, -9, 0, 33, -4, 0, + 34, 0, -1, 35, 3, -1, 36, 7, -2, 37, 11, -3, + 38, 15, -3, 39, 19, -4, 40, 23, -5, 41, 27, -5, + 42, 32, -6, 43, 36, -7, 44, 40, -7, 45, 44, -8, + 46, 48, -9, 46, 52, -9, 47, 56, -10, 48, 60, -11, + 49, 65, -11, 50, 69, -12, 51, 73, -13, 52, 77, -13, + 53, 81, -14, 54, 85, -15, 55, 89, -15, 56, 93, -16, + 57, 97, -17, 58, 102, -18, 59, 106, -18, 60, 110, -19, + 33, -18, 0, 34, -14, 0, 35, -10, -1, 36, -6, -2, + 37, -2, -2, 38, 1, -3, 38, 5, -4, 39, 10, -4, + 40, 14, -5, 41, 18, -6, 42, 22, -6, 43, 26, -7, + 44, 30, -8, 45, 34, -8, 46, 38, -9, 47, 42, -10, + 48, 47, -10, 49, 51, -11, 50, 55, -12, 51, 59, -12, + 52, 63, -13, 53, 67, -14, 53, 71, -14, 54, 75, -15, + 55, 79, -16, 56, 84, -16, 57, 88, -17, 58, 92, -18, + 59, 96, -18, 60, 100, -19, 61, 104, -20, 62, 108, -20, + 36, -20, -2, 37, -16, -2, 38, -12, -3, 39, -7, -4, + 40, -3, -5, 40, 0, -5, 41, 4, -6, 42, 8, -7, + 43, 12, -7, 44, 16, -8, 45, 20, -9, 46, 24, -9, + 47, 29, -10, 48, 33, -11, 49, 37, -11, 50, 41, -12, + 51, 45, -13, 52, 49, -13, 53, 53, -14, 54, 57, -15, + 55, 62, -15, 56, 66, -16, 56, 70, -17, 57, 74, -17, + 58, 78, -18, 59, 82, -19, 60, 86, -19, 61, 90, -20, + 62, 94, -21, 63, 99, -21, 64, 103, -22, 65, 107, -23, + 38, -21, -4, 39, -17, -4, 40, -13, -5, 41, -9, -6, + 42, -5, -6, 43, -1, -7, 44, 2, -8, 45, 7, -8, + 46, 11, -9, 47, 15, -10, 47, 19, -10, 48, 23, -11, + 49, 27, -12, 50, 31, -12, 51, 35, -13, 52, 39, -14, + 53, 44, -14, 54, 48, -15, 55, 52, -16, 56, 56, -16, + 57, 60, -17, 58, 64, -18, 59, 68, -18, 60, 72, -19, + 61, 76, -20, 62, 81, -20, 63, 85, -21, 63, 89, -22, + 64, 93, -22, 65, 97, -23, 66, 101, -24, 67, 105, -24, + 40, -23, -5, 41, -19, -6, 42, -15, -6, 43, -10, -7, + 44, -6, -8, 45, -2, -9, 46, 1, -9, 47, 5, -10, + 48, 9, -11, 49, 13, -11, 50, 17, -12, 51, 21, -13, + 52, 26, -13, 53, 30, -14, 54, 34, -15, 54, 38, -15, + 56, 42, -16, 56, 46, -17, 57, 50, -17, 58, 54, -18, + 59, 59, -19, 60, 63, -19, 61, 67, -20, 62, 71, -21, + 63, 75, -21, 64, 79, -22, 65, 83, -23, 66, 87, -23, + 67, 91, -24, 68, 96, -25, 69, 100, -25, 70, 104, -26, + 43, -24, -7, 44, -20, -8, 45, -16, -8, 46, -11, -9, + 47, -7, -10, 47, -3, -10, 48, 0, -11, 49, 4, -12, + 50, 8, -12, 51, 12, -13, 52, 16, -14, 53, 20, -14, + 54, 25, -15, 55, 29, -16, 56, 33, -16, 57, 37, -17, + 58, 41, -18, 59, 45, -18, 60, 49, -19, 61, 53, -20, + 62, 58, -20, 63, 62, -21, 63, 66, -22, 64, 70, -22, + 65, 74, -23, 66, 78, -24, 67, 82, -24, 68, 86, -25, + 69, 90, -26, 70, 95, -26, 71, 99, -27, 72, 103, -28, + 45, -25, -9, 46, -21, -9, 47, -17, -10, 48, -13, -11, + 49, -9, -11, 50, -5, -12, 51, -1, -13, 52, 3, -13, + 53, 7, -14, 54, 11, -15, 55, 15, -15, 55, 19, -16, + 56, 23, -17, 57, 27, -17, 58, 31, -18, 59, 35, -19, + 60, 40, -19, 61, 44, -20, 62, 48, -21, 63, 52, -21, + 64, 56, -22, 65, 60, -23, 66, 64, -23, 67, 68, -24, + 68, 72, -25, 69, 77, -25, 70, 81, -26, 70, 85, -27, + 71, 89, -27, 72, 93, -28, 73, 97, -29, 74, 101, -29, + 48, -27, -10, 48, -23, -11, 49, -19, -12, 50, -14, -12, + 51, -10, -13, 52, -6, -14, 53, -2, -14, 54, 1, -15, + 55, 5, -16, 56, 9, -16, 57, 13, -17, 58, 17, -18, + 59, 22, -18, 60, 26, -19, 61, 30, -20, 62, 34, -20, + 63, 38, -21, 63, 42, -22, 64, 46, -22, 65, 50, -23, + 66, 55, -24, 67, 59, -24, 68, 63, -25, 69, 67, -26, + 70, 71, -26, 71, 75, -27, 72, 79, -28, 73, 83, -28, + 74, 87, -29, 75, 92, -30, 76, 96, -30, 77, 100, -31, + 50, -28, -12, 51, -24, -13, 52, -20, -13, 53, -15, -14, + 54, -11, -15, 55, -7, -15, 55, -3, -16, 56, 0, -17, + 57, 4, -17, 58, 8, -18, 59, 12, -19, 60, 16, -19, + 61, 21, -20, 62, 25, -21, 63, 29, -21, 64, 33, -22, + 65, 37, -23, 66, 41, -23, 67, 45, -24, 68, 49, -25, + 69, 54, -25, 70, 58, -26, 71, 62, -27, 71, 66, -27, + 72, 70, -28, 73, 74, -29, 74, 78, -29, 75, 82, -30, + 76, 86, -31, 77, 91, -31, 78, 95, -32, 79, 99, -33, + 52, -29, -14, 53, -25, -14, 54, -21, -15, 55, -17, -16, + 56, -13, -16, 57, -9, -17, 58, -5, -18, 59, 0, -18, + 60, 3, -19, 61, 7, -20, 62, 11, -20, 62, 15, -21, + 64, 19, -22, 64, 23, -22, 65, 27, -23, 66, 31, -24, + 67, 36, -24, 68, 40, -25, 69, 44, -26, 70, 48, -26, + 71, 52, -27, 72, 56, -28, 73, 60, -28, 74, 64, -29, + 75, 68, -30, 76, 73, -30, 77, 77, -31, 78, 81, -32, + 78, 85, -32, 79, 89, -33, 80, 93, -34, 81, 97, -34, + 55, -31, -15, 55, -27, -16, 56, -23, -17, 57, -18, -17, + 58, -14, -18, 59, -10, -19, 60, -6, -19, 61, -2, -20, + 62, 1, -21, 63, 5, -21, 64, 9, -22, 65, 13, -23, + 66, 18, -23, 67, 22, -24, 68, 26, -25, 69, 30, -25, + 70, 34, -26, 71, 38, -27, 71, 42, -27, 72, 46, -28, + 73, 51, -29, 74, 55, -29, 75, 59, -30, 76, 63, -31, + 77, 67, -31, 78, 71, -32, 79, 75, -33, 80, 79, -33, + 81, 83, -34, 82, 88, -35, 83, 92, -35, 84, 96, -36, + 57, -32, -17, 58, -28, -18, 59, -24, -18, 60, -19, -19, + 61, -15, -20, 62, -11, -20, 62, -7, -21, 64, -3, -22, + 64, 0, -22, 65, 4, -23, 66, 8, -24, 67, 12, -24, + 68, 17, -25, 69, 21, -26, 70, 25, -26, 71, 29, -27, + 72, 33, -28, 73, 37, -28, 74, 41, -29, 75, 45, -30, + 76, 50, -30, 77, 54, -31, 78, 58, -32, 78, 62, -32, + 79, 66, -33, 80, 70, -34, 81, 74, -34, 82, 78, -35, + 83, 82, -36, 84, 87, -36, 85, 91, -37, 86, 95, -38, + 59, -33, -19, 60, -29, -19, 61, -25, -20, 62, -21, -21, + 63, -17, -21, 64, -13, -22, 65, -9, -23, 66, -4, -23, + 67, 0, -24, 68, 3, -25, 69, 7, -25, 70, 11, -26, + 71, 15, -27, 71, 19, -27, 72, 23, -28, 73, 27, -29, + 74, 32, -29, 75, 36, -30, 76, 40, -31, 77, 44, -31, + 78, 48, -32, 79, 52, -33, 80, 56, -33, 81, 60, -34, + 82, 64, -35, 83, 69, -35, 84, 73, -36, 85, 77, -37, + 85, 81, -37, 87, 85, -38, 87, 89, -39, 88, 93, -39, + 62, -35, -20, 63, -31, -21, 63, -27, -22, 64, -22, -22, + 65, -18, -23, 66, -14, -24, 67, -10, -24, 68, -6, -25, + 69, -2, -26, 70, 1, -26, 71, 5, -27, 72, 9, -28, + 73, 14, -28, 74, 18, -29, 75, 22, -30, 76, 26, -30, + 77, 30, -31, 78, 34, -32, 78, 38, -32, 79, 42, -33, + 80, 47, -34, 81, 51, -34, 82, 55, -35, 83, 59, -36, + 84, 63, -36, 85, 67, -37, 86, 71, -38, 87, 75, -38, + 88, 79, -39, 89, 84, -40, 90, 88, -40, 91, 92, -41, + 64, -36, -22, 65, -32, -23, 66, -28, -23, 67, -23, -24, + 68, -19, -25, 69, -15, -25, 70, -11, -26, 71, -7, -27, + 71, -3, -27, 72, 0, -28, 73, 4, -29, 74, 8, -29, + 75, 13, -30, 76, 17, -31, 77, 21, -31, 78, 25, -32, + 79, 29, -33, 80, 33, -33, 81, 37, -34, 82, 41, -35, + 83, 46, -35, 84, 50, -36, 85, 54, -37, 86, 58, -37, + 86, 62, -38, 87, 66, -39, 88, 70, -39, 89, 74, -40, + 90, 78, -41, 91, 83, -41, 92, 87, -42, 93, 91, -43, + 66, -37, -24, 67, -33, -24, 68, -29, -25, 69, -25, -26, + 70, -21, -26, 71, -17, -27, 72, -13, -28, 73, -8, -28, + 74, -4, -29, 75, 0, -30, 76, 3, -30, 77, 7, -31, + 78, 11, -32, 79, 15, -32, 79, 19, -33, 80, 23, -34, + 81, 28, -34, 82, 32, -35, 83, 36, -36, 84, 40, -36, + 85, 44, -37, 86, 48, -38, 87, 52, -38, 88, 56, -39, + 89, 60, -40, 90, 65, -40, 91, 69, -41, 92, 73, -42, + 93, 77, -42, 94, 81, -43, 94, 85, -44, 95, 89, -44, + 69, -39, -25, 70, -35, -26, 70, -31, -27, 72, -26, -27, + 72, -22, -28, 73, -18, -29, 74, -14, -29, 75, -10, -30, + 76, -6, -31, 77, -2, -31, 78, 1, -32, 79, 5, -33, + 80, 10, -33, 81, 14, -34, 82, 18, -35, 83, 22, -35, + 84, 26, -36, 85, 30, -37, 86, 34, -37, 86, 38, -38, + 87, 43, -39, 88, 47, -39, 89, 51, -40, 90, 55, -41, + 91, 59, -41, 92, 63, -42, 93, 67, -43, 94, 71, -43, + 95, 75, -44, 96, 80, -45, 97, 84, -45, 98, 88, -46, + 71, -40, -27, 72, -36, -28, 73, -32, -28, 74, -27, -29, + 75, -23, -30, 76, -19, -30, 77, -15, -31, 78, -11, -32, + 79, -7, -32, 79, -3, -33, 80, 0, -34, 81, 4, -34, + 82, 9, -35, 83, 13, -36, 84, 17, -36, 85, 21, -37, + 86, 25, -38, 87, 29, -38, 88, 33, -39, 89, 37, -40, + 90, 42, -40, 91, 46, -41, 92, 50, -42, 93, 54, -42, + 93, 58, -43, 94, 62, -44, 95, 66, -44, 96, 70, -45, + 97, 74, -46, 98, 79, -46, 99, 83, -47, 100, 87, -48, + 73, -41, -29, 74, -37, -29, 75, -33, -30, 76, -29, -31, + 77, -25, -31, 78, -21, -32, 79, -17, -33, 80, -12, -33, + 81, -8, -34, 82, -4, -35, 83, 0, -35, 84, 3, -36, + 85, 7, -37, 86, 11, -37, 86, 15, -38, 87, 19, -39, + 88, 24, -39, 89, 28, -40, 90, 32, -41, 91, 36, -41, + 92, 40, -42, 93, 44, -43, 94, 48, -43, 95, 52, -44, + 96, 56, -45, 97, 61, -45, 98, 65, -46, 99, 69, -47, + 100, 73, -47, 101, 77, -48, 102, 81, -49, 102, 85, -49, + 76, -43, -30, 77, -39, -31, 78, -35, -32, 79, -30, -32, + 79, -26, -33, 80, -22, -34, 81, -18, -34, 82, -14, -35, + 83, -10, -36, 84, -6, -36, 85, -2, -37, 86, 1, -38, + 87, 6, -38, 88, 10, -39, 89, 14, -40, 90, 18, -40, + 91, 22, -41, 92, 26, -42, 93, 30, -42, 93, 34, -43, + 95, 39, -44, 95, 43, -44, 96, 47, -45, 97, 51, -46, + 98, 55, -46, 99, 59, -47, 100, 63, -48, 101, 67, -48, + 102, 71, -49, 103, 76, -50, 104, 80, -50, 105, 84, -51, + 78, -44, -32, 79, -40, -33, 80, -36, -33, 81, -31, -34, + 82, -27, -35, 83, -23, -35, 84, -19, -36, 85, -15, -37, + 86, -11, -37, 86, -7, -38, 87, -3, -39, 88, 0, -39, + 89, 5, -40, 90, 9, -41, 91, 13, -41, 92, 17, -42, + 93, 21, -43, 94, 25, -43, 95, 29, -44, 96, 33, -45, + 97, 38, -45, 98, 42, -46, 99, 46, -47, 100, 50, -47, + 101, 54, -48, 102, 58, -49, 102, 62, -49, 103, 66, -50, + 104, 70, -51, 105, 75, -51, 106, 79, -52, 107, 83, -53, + 80, -45, -34, 81, -41, -34, 82, -37, -35, 83, -33, -36, + 84, -29, -36, 85, -25, -37, 86, -21, -38, 87, -16, -38, + 88, -12, -39, 89, -8, -40, 90, -4, -40, 91, 0, -41, + 92, 3, -42, 93, 7, -42, 94, 11, -43, 94, 15, -44, + 95, 20, -44, 96, 24, -45, 97, 28, -46, 98, 32, -46, + 99, 36, -47, 100, 40, -48, 101, 44, -48, 102, 48, -49, + 103, 52, -50, 104, 57, -50, 105, 61, -51, 106, 65, -52, + 107, 69, -52, 108, 73, -53, 109, 77, -54, 109, 81, -54, + 83, -46, -35, 84, -42, -36, 85, -38, -37, 86, -34, -37, + 87, -30, -38, 87, -26, -39, 88, -22, -39, 89, -17, -40, + 90, -13, -41, 91, -9, -41, 92, -5, -42, 93, -1, -43, + 94, 2, -43, 95, 6, -44, 96, 10, -45, 97, 14, -45, + 98, 19, -46, 99, 23, -47, 100, 27, -47, 101, 31, -48, + 102, 35, -49, 102, 39, -49, 103, 43, -50, 104, 47, -51, + 105, 51, -51, 106, 56, -52, 107, 60, -53, 108, 64, -53, + 109, 68, -54, 110, 72, -55, 111, 76, -55, 112, 80, -56, + 86, -48, -37, 87, -44, -38, 88, -40, -39, 89, -36, -39, + 89, -32, -40, 90, -28, -41, 91, -24, -41, 92, -19, -42, + 93, -15, -43, 94, -11, -43, 95, -7, -44, 96, -3, -45, + 97, 0, -45, 98, 4, -46, 99, 8, -47, 100, 12, -47, + 101, 17, -48, 102, 21, -49, 103, 25, -49, 103, 29, -50, + 104, 33, -51, 105, 37, -51, 106, 41, -52, 107, 45, -53, + 108, 49, -53, 109, 54, -54, 110, 58, -55, 111, 62, -55, + 112, 66, -56, 113, 70, -57, 114, 74, -58, 115, 78, -58, + 88, -49, -39, 89, -45, -40, 90, -41, -40, 91, -37, -41, + 92, -33, -42, 93, -29, -42, 94, -25, -43, 95, -20, -44, + 96, -16, -44, 96, -12, -45, 97, -8, -46, 98, -4, -46, + 99, 0, -47, 100, 3, -48, 101, 7, -48, 102, 11, -49, + 103, 16, -50, 104, 20, -50, 105, 24, -51, 106, 28, -52, + 107, 32, -53, 108, 36, -53, 109, 40, -54, 110, 44, -54, + 110, 48, -55, 112, 53, -56, 112, 57, -57, 113, 61, -57, + 114, 65, -58, 115, 69, -59, 116, 73, -59, 117, 77, -60, + 90, -51, -41, 91, -47, -41, 92, -43, -42, 93, -38, -43, + 94, -34, -43, 95, -30, -44, 96, -26, -45, 97, -22, -45, + 98, -18, -46, 99, -14, -47, 100, -10, -47, 101, -6, -48, + 102, -1, -49, 103, 2, -49, 103, 6, -50, 104, 10, -51, + 105, 14, -52, 106, 18, -52, 107, 22, -53, 108, 26, -53, + 109, 31, -54, 110, 35, -55, 111, 39, -55, 112, 43, -56, + 113, 47, -57, 114, 51, -58, 115, 55, -58, 116, 59, -59, + 117, 63, -59, 118, 68, -60, 119, 72, -61, 119, 76, -62, + 93, -52, -42, 94, -48, -43, 95, -44, -44, 96, -40, -44, + 96, -36, -45, 97, -32, -46, 98, -28, -46, 99, -23, -47, + 100, -19, -48, 101, -15, -48, 102, -11, -49, 103, -7, -50, + 104, -3, -51, 105, 0, -51, 106, 4, -52, 107, 8, -52, + 108, 13, -53, 109, 17, -54, 110, 21, -54, 111, 25, -55, + 112, 29, -56, 112, 33, -57, 113, 37, -57, 114, 41, -58, + 115, 45, -58, 116, 50, -59, 117, 54, -60, 118, 58, -61, + 119, 62, -61, 120, 66, -62, 121, 70, -63, 122, 74, -63, + 95, -53, -44, 96, -49, -45, 97, -45, -45, 98, -41, -46, + 99, -37, -47, 100, -33, -47, 101, -29, -48, 102, -24, -49, + 103, -20, -49, 104, -16, -50, 104, -12, -51, 105, -8, -51, + 106, -4, -52, 107, 0, -53, 108, 3, -53, 109, 7, -54, + 110, 12, -55, 111, 16, -56, 112, 20, -56, 113, 24, -57, + 114, 28, -58, 115, 32, -58, 116, 36, -59, 117, 40, -59, + 118, 44, -60, 119, 49, -61, 119, 53, -62, 120, 57, -62, + 121, 61, -63, 122, 65, -64, 123, 69, -64, 124, 73, -65, + 97, -55, -46, 98, -51, -46, 99, -47, -47, 100, -42, -48, + 101, -38, -48, 102, -34, -49, 103, -30, -50, 104, -26, -51, + 105, -22, -51, 106, -18, -52, 107, -14, -52, 108, -10, -53, + 109, -5, -54, 110, -1, -55, 111, 2, -55, 111, 6, -56, + 112, 10, -57, 113, 14, -57, 114, 18, -58, 115, 22, -58, + 116, 27, -59, 117, 31, -60, 118, 35, -61, 119, 39, -61, + 120, 43, -62, 121, 47, -63, 122, 51, -63, 123, 55, -64, + 124, 59, -65, 125, 64, -65, 126, 68, -66, 126, 72, -67, + 100, -56, -47, 101, -52, -48, 102, -48, -49, 103, -44, -50, + 104, -40, -50, 104, -36, -51, 105, -32, -51, 106, -27, -52, + 107, -23, -53, 108, -19, -53, 109, -15, -54, 110, -11, -55, + 111, -7, -56, 112, -3, -56, 113, 0, -57, 114, 4, -57, + 115, 9, -58, 116, 13, -59, 117, 17, -60, 118, 21, -60, + 119, 25, -61, 119, 29, -62, 120, 33, -62, 121, 37, -63, + 122, 41, -63, 123, 46, -64, 124, 50, -65, 125, 54, -66, + 126, 58, -66, 127, 62, -67, 128, 66, -68, 129, 70, -68, + 102, -57, -49, 103, -53, -50, 104, -49, -50, 105, -45, -51, + 106, -41, -52, 107, -37, -52, 108, -33, -53, 109, -28, -54, + 110, -24, -55, 111, -20, -55, 111, -16, -56, 112, -12, -56, + 113, -8, -57, 114, -4, -58, 115, 0, -59, 116, 3, -59, + 117, 8, -60, 118, 12, -61, 119, 16, -61, 120, 20, -62, + 121, 24, -63, 122, 28, -63, 123, 32, -64, 124, 36, -65, + 125, 40, -65, 126, 45, -66, 127, 49, -67, 127, 53, -67, + 128, 57, -68, 129, 61, -69, 130, 65, -69, 131, 69, -70, + 104, -59, -51, 105, -55, -51, 106, -51, -52, 107, -46, -53, + 108, -42, -54, 109, -38, -54, 110, -34, -55, 111, -30, -56, + 112, -26, -56, 113, -22, -57, 114, -18, -57, 115, -14, -58, + 116, -9, -59, 117, -5, -60, 118, -1, -60, 118, 2, -61, + 120, 6, -62, 120, 10, -62, 121, 14, -63, 122, 18, -64, + 123, 23, -64, 124, 27, -65, 125, 31, -66, 126, 35, -66, + 127, 39, -67, 128, 43, -68, 129, 47, -68, 130, 51, -69, + 131, 55, -70, 132, 60, -70, 133, 64, -71, 134, 68, -72, + 107, -60, -53, 108, -56, -53, 109, -52, -54, 110, -48, -55, + 111, -44, -55, 111, -40, -56, 112, -36, -56, 113, -31, -57, + 114, -27, -58, 115, -23, -59, 116, -19, -59, 117, -15, -60, + 118, -11, -61, 119, -7, -61, 120, -3, -62, 121, 0, -63, + 122, 5, -63, 123, 9, -64, 124, 13, -65, 125, 17, -65, + 126, 21, -66, 127, 25, -67, 127, 29, -67, 128, 33, -68, + 129, 37, -69, 130, 42, -69, 131, 46, -70, 132, 50, -71, + 133, 54, -71, 134, 58, -72, 135, 62, -73, 136, 66, -73, + 109, -61, -54, 110, -57, -55, 111, -53, -55, 112, -49, -56, + 113, -45, -57, 114, -41, -58, 115, -37, -58, 116, -32, -59, + 117, -28, -60, 118, -24, -60, 119, -20, -61, 119, -16, -61, + 120, -12, -62, 121, -8, -63, 122, -4, -64, 123, 0, -64, + 124, 4, -65, 125, 8, -66, 126, 12, -66, 127, 16, -67, + 128, 20, -68, 129, 24, -68, 130, 28, -69, 131, 32, -70, + 132, 36, -70, 133, 41, -71, 134, 45, -72, 134, 49, -72, + 135, 53, -73, 136, 57, -74, 137, 61, -74, 138, 65, -75, + 112, -63, -56, 112, -59, -57, 113, -55, -57, 114, -50, -58, + 115, -46, -59, 116, -42, -59, 117, -38, -60, 118, -34, -61, + 119, -30, -61, 120, -26, -62, 121, -22, -63, 122, -18, -63, + 123, -13, -64, 124, -9, -65, 125, -5, -65, 126, -1, -66, + 127, 2, -67, 127, 6, -67, 128, 10, -68, 129, 14, -69, + 130, 19, -69, 131, 23, -70, 132, 27, -71, 133, 31, -71, + 134, 35, -72, 135, 39, -73, 136, 43, -73, 137, 47, -74, + 138, 51, -75, 139, 56, -75, 140, 60, -76, 141, 64, -77, + 114, -64, -58, 115, -60, -58, 116, -56, -59, 117, -52, -60, + 118, -48, -60, 119, -44, -61, 119, -40, -62, 120, -35, -62, + 121, -31, -63, 122, -27, -64, 123, -23, -64, 124, -19, -65, + 125, -15, -66, 126, -11, -66, 127, -7, -67, 128, -3, -68, + 129, 1, -68, 130, 5, -69, 131, 9, -70, 132, 13, -70, + 133, 17, -71, 134, 21, -72, 134, 25, -72, 135, 29, -73, + 136, 33, -74, 137, 38, -74, 138, 42, -75, 139, 46, -76, + 140, 50, -76, 141, 54, -77, 142, 58, -78, 143, 62, -78, + 116, -65, -59, 117, -61, -60, 118, -57, -61, 119, -53, -61, + 120, -49, -62, 121, -45, -63, 122, -41, -63, 123, -36, -64, + 124, -32, -65, 125, -28, -65, 126, -24, -66, 126, -20, -67, + 127, -16, -67, 128, -12, -68, 129, -8, -69, 130, -4, -69, + 131, 0, -70, 132, 4, -71, 133, 8, -71, 134, 12, -72, + 135, 16, -73, 136, 20, -73, 137, 24, -74, 138, 28, -75, + 139, 32, -75, 140, 37, -76, 141, 41, -77, 142, 45, -77, + 142, 49, -78, 143, 53, -79, 144, 57, -79, 145, 61, -80, + 119, -67, -61, 119, -63, -62, 120, -59, -62, 121, -54, -63, + 122, -50, -64, 123, -46, -64, 124, -42, -65, 125, -38, -66, + 126, -34, -66, 127, -30, -67, 128, -26, -68, 129, -22, -68, + 130, -17, -69, 131, -13, -70, 132, -9, -70, 133, -5, -71, + 134, -1, -72, 135, 2, -72, 135, 6, -73, 136, 10, -74, + 137, 15, -74, 138, 19, -75, 139, 23, -76, 140, 27, -76, + 141, 31, -77, 142, 35, -78, 143, 39, -78, 144, 43, -79, + 145, 47, -80, 146, 52, -80, 147, 56, -81, 148, 60, -82, + 121, -68, -63, 122, -64, -63, 123, -60, -64, 124, -56, -65, + 125, -52, -65, 126, -48, -66, 126, -44, -67, 128, -39, -67, + 128, -35, -68, 129, -31, -69, 130, -27, -69, 131, -23, -70, + 132, -19, -71, 133, -15, -71, 134, -11, -72, 135, -7, -73, + 136, -2, -73, 137, 1, -74, 138, 5, -75, 139, 9, -75, + 140, 13, -76, 141, 17, -77, 142, 21, -77, 142, 25, -78, + 143, 29, -79, 144, 34, -79, 145, 38, -80, 146, 42, -81, + 147, 46, -81, 148, 50, -82, 149, 54, -83, 150, 58, -83, + 123, -69, -64, 124, -65, -65, 125, -61, -66, 126, -57, -66, + 127, -53, -67, 128, -49, -68, 129, -45, -68, 130, -40, -69, + 131, -36, -70, 132, -32, -70, 133, -28, -71, 134, -24, -72, + 135, -20, -72, 135, -16, -73, 136, -12, -74, 137, -8, -74, + 138, -3, -75, 139, 0, -76, 140, 4, -76, 141, 8, -77, + 142, 12, -78, 143, 16, -78, 144, 20, -79, 145, 24, -80, + 146, 28, -80, 147, 33, -81, 148, 37, -82, 149, 41, -82, + 149, 45, -83, 150, 49, -84, 151, 53, -84, 152, 57, -85, + 126, -71, -66, 127, -67, -67, 127, -63, -67, 128, -58, -68, + 129, -54, -69, 130, -50, -69, 131, -46, -70, 132, -42, -71, + 133, -38, -71, 134, -34, -72, 135, -30, -73, 136, -26, -73, + 137, -21, -74, 138, -17, -75, 139, -13, -75, 140, -9, -76, + 141, -5, -77, 142, -1, -77, 142, 2, -78, 143, 6, -79, + 144, 11, -79, 145, 15, -80, 146, 19, -81, 147, 23, -81, + 148, 27, -82, 149, 31, -83, 150, 35, -83, 151, 39, -84, + 152, 43, -85, 153, 48, -85, 154, 52, -86, 155, 56, -87, + 128, -72, -68, 129, -68, -68, 130, -64, -69, 131, -59, -70, + 132, -55, -70, 133, -51, -71, 134, -47, -72, 135, -43, -72, + 135, -39, -73, 136, -35, -74, 137, -31, -74, 138, -27, -75, + 139, -22, -76, 140, -18, -76, 141, -14, -77, 142, -10, -78, + 143, -6, -78, 144, -2, -79, 145, 1, -80, 146, 5, -80, + 147, 10, -81, 148, 14, -82, 149, 18, -82, 149, 22, -83, + 150, 26, -84, 151, 30, -84, 152, 34, -85, 153, 38, -86, + 154, 42, -86, 155, 47, -87, 156, 51, -88, 157, 55, -88, + 130, -73, -69, 131, -69, -70, 132, -65, -71, 133, -61, -71, + 134, -57, -72, 135, -53, -73, 136, -49, -73, 137, -44, -74, + 138, -40, -75, 139, -36, -75, 140, -32, -76, 141, -28, -77, + 142, -24, -77, 142, -20, -78, 143, -16, -79, 144, -12, -79, + 145, -7, -80, 146, -3, -81, 147, 0, -81, 148, 4, -82, + 149, 8, -83, 150, 12, -83, 151, 16, -84, 152, 20, -85, + 153, 24, -85, 154, 29, -86, 155, 33, -87, 156, 37, -87, + 157, 41, -88, 158, 45, -89, 158, 49, -89, 159, 53, -90, + 133, -75, -71, 134, -71, -72, 134, -67, -72, 135, -62, -73, + 136, -58, -74, 137, -54, -74, 138, -50, -75, 139, -46, -76, + 140, -42, -76, 141, -38, -77, 142, -34, -78, 143, -30, -78, + 144, -25, -79, 145, -21, -80, 146, -17, -80, 147, -13, -81, + 148, -9, -82, 149, -5, -82, 150, -1, -83, 150, 2, -84, + 151, 7, -84, 152, 11, -85, 153, 15, -86, 154, 19, -86, + 155, 23, -87, 156, 27, -88, 157, 31, -88, 158, 35, -89, + 159, 39, -90, 160, 44, -90, 161, 48, -91, 162, 52, -92, + 136, -76, -73, 136, -72, -74, 137, -68, -74, 138, -64, -75, + 139, -60, -76, 140, -56, -76, 141, -52, -77, 142, -47, -78, + 143, -43, -78, 144, -39, -79, 145, -35, -80, 146, -31, -80, + 147, -27, -81, 148, -23, -82, 149, -19, -82, 150, -15, -83, + 151, -10, -84, 152, -6, -84, 152, -2, -85, 153, 1, -86, + 154, 5, -86, 155, 9, -87, 156, 13, -88, 157, 17, -88, + 158, 21, -89, 159, 26, -90, 160, 30, -90, 161, 34, -91, + 162, 38, -92, 163, 42, -92, 164, 46, -93, 165, 50, -94, + 138, -78, -75, 139, -74, -75, 140, -70, -76, 141, -65, -77, + 142, -61, -77, 143, -57, -78, 144, -53, -79, 145, -49, -79, + 145, -45, -80, 146, -41, -81, 147, -37, -81, 148, -33, -82, + 149, -28, -83, 150, -24, -83, 151, -20, -84, 152, -16, -85, + 153, -12, -85, 154, -8, -86, 155, -4, -87, 156, 0, -87, + 157, 4, -88, 158, 8, -89, 159, 12, -89, 159, 16, -90, + 160, 20, -91, 161, 24, -91, 162, 28, -92, 163, 32, -93, + 164, 36, -93, 165, 41, -94, 166, 45, -95, 167, 49, -95, + 140, -79, -76, 141, -75, -77, 142, -71, -78, 143, -66, -78, + 144, -62, -79, 145, -58, -80, 146, -54, -80, 147, -50, -81, + 148, -46, -82, 149, -42, -82, 150, -38, -83, 151, -34, -84, + 152, -29, -84, 152, -25, -85, 153, -21, -86, 154, -17, -86, + 155, -13, -87, 156, -9, -88, 157, -5, -88, 158, -1, -89, + 159, 3, -90, 160, 7, -90, 161, 11, -91, 162, 15, -92, + 163, 19, -92, 164, 23, -93, 165, 27, -94, 166, 31, -94, + 166, 35, -95, 168, 40, -96, 168, 44, -96, 169, 48, -97, + 143, -80, -78, 144, -76, -79, 144, -72, -79, 145, -68, -80, + 146, -64, -81, 147, -60, -81, 148, -56, -82, 149, -51, -83, + 150, -47, -83, 151, -43, -84, 152, -39, -85, 153, -35, -85, + 154, -31, -86, 155, -27, -87, 156, -23, -87, 157, -19, -88, + 158, -14, -89, 159, -10, -89, 159, -6, -90, 160, -2, -91, + 161, 1, -91, 162, 5, -92, 163, 9, -93, 164, 13, -93, + 165, 17, -94, 166, 22, -95, 167, 26, -95, 168, 30, -96, + 169, 34, -97, 170, 38, -97, 171, 42, -98, 172, 46, -99, + 145, -82, -80, 146, -78, -80, 147, -74, -81, 148, -69, -82, + 149, -65, -82, 150, -61, -83, 151, -57, -84, 152, -53, -84, + 152, -49, -85, 153, -45, -86, 154, -41, -86, 155, -37, -87, + 156, -32, -88, 157, -28, -88, 158, -24, -89, 159, -20, -90, + 160, -16, -90, 161, -12, -91, 162, -8, -92, 163, -4, -92, + 164, 0, -93, 165, 4, -94, 166, 8, -94, 167, 12, -95, + 167, 16, -96, 168, 20, -96, 169, 24, -97, 170, 28, -98, + 171, 32, -98, 172, 37, -99, 173, 41, -100, 174, 45, -100, + 147, -83, -81, 148, -79, -82, 149, -75, -83, 150, -70, -83, + 151, -66, -84, 152, -62, -85, 153, -58, -85, 154, -54, -86, + 155, -50, -87, 156, -46, -87, 157, -42, -88, 158, -38, -89, + 159, -33, -89, 160, -29, -90, 160, -25, -91, 161, -21, -91, + 162, -17, -92, 163, -13, -93, 164, -9, -93, 165, -5, -94, + 166, 0, -95, 167, 3, -95, 168, 7, -96, 169, 11, -97, + 170, 15, -97, 171, 19, -98, 172, 23, -99, 173, 27, -99, + 174, 31, -100, 175, 36, -101, 175, 40, -101, 176, 44, -102, + 150, -84, -83, 151, -80, -84, 151, -76, -84, 153, -72, -85, + 153, -68, -86, 154, -64, -86, 155, -60, -87, 156, -55, -88, + 157, -51, -88, 158, -47, -89, 159, -43, -90, 160, -39, -90, + 161, -35, -91, 162, -31, -92, 163, -27, -92, 164, -23, -93, + 165, -18, -94, 166, -14, -94, 167, -10, -95, 167, -6, -96, + 168, -2, -96, 169, 1, -97, 170, 5, -98, 171, 9, -98, + 172, 13, -99, 173, 18, -100, 174, 22, -100, 175, 26, -101, + 176, 30, -102, 177, 34, -103, 178, 38, -103, 179, 42, -104, + 152, -86, -85, 153, -82, -85, 154, -78, -86, 155, -73, -87, + 156, -69, -87, 157, -65, -88, 158, -61, -89, 159, -57, -89, + 160, -53, -90, 160, -49, -91, 161, -45, -91, 162, -41, -92, + 163, -36, -93, 164, -32, -93, 165, -28, -94, 166, -24, -95, + 167, -20, -95, 168, -16, -96, 169, -12, -97, 170, -8, -97, + 171, -3, -98, 172, 0, -99, 173, 4, -99, 174, 8, -100, + 174, 12, -101, 175, 16, -101, 176, 20, -102, 177, 24, -103, + 178, 28, -103, 179, 33, -104, 180, 37, -105, 181, 41, -105, + 154, -87, -86, 155, -83, -87, 156, -79, -88, 157, -74, -88, + 158, -70, -89, 159, -66, -90, 160, -62, -90, 161, -58, -91, + 162, -54, -92, 163, -50, -92, 164, -46, -93, 165, -42, -94, + 166, -37, -94, 167, -33, -95, 167, -29, -96, 168, -25, -96, + 169, -21, -97, 170, -17, -98, 171, -13, -98, 172, -9, -99, + 173, -4, -100, 174, 0, -100, 175, 3, -101, 176, 7, -102, + 177, 11, -102, 178, 15, -103, 179, 19, -104, 180, 23, -104, + 181, 27, -105, 182, 32, -106, 183, 36, -107, 183, 40, -107, + 157, -88, -88, 158, -84, -89, 159, -80, -89, 160, -76, -90, + 160, -72, -91, 161, -68, -91, 162, -64, -92, 163, -59, -93, + 164, -55, -93, 165, -51, -94, 166, -47, -95, 167, -43, -95, + 168, -39, -96, 169, -35, -97, 170, -31, -97, 171, -27, -98, + 172, -22, -99, 173, -18, -99, 174, -14, -100, 174, -10, -101, + 176, -6, -102, 176, -2, -102, 177, 1, -103, 178, 5, -103, + 179, 9, -104, 180, 14, -105, 181, 18, -105, 182, 22, -106, + 183, 26, -107, 184, 30, -108, 185, 34, -108, 186, 38, -109, + 159, -90, -90, 160, -86, -90, 161, -82, -91, 162, -77, -92, + 163, -73, -92, 164, -69, -93, 165, -65, -94, 166, -61, -94, + 167, -57, -95, 167, -53, -96, 168, -49, -96, 169, -45, -97, + 170, -40, -98, 171, -36, -98, 172, -32, -99, 173, -28, -100, + 174, -24, -100, 175, -20, -101, 176, -16, -102, 177, -12, -102, + 178, -7, -103, 179, -3, -104, 180, 0, -104, 181, 4, -105, + 182, 8, -106, 183, 12, -107, 183, 16, -107, 184, 20, -108, + 185, 24, -108, 186, 29, -109, 187, 33, -110, 188, 37, -111, + 12, -6, 20, 13, -2, 19, 14, 1, 19, 15, 5, 18, + 16, 9, 17, 16, 13, 17, 17, 17, 16, 18, 22, 15, + 19, 26, 15, 20, 30, 14, 21, 34, 13, 22, 38, 13, + 23, 42, 12, 24, 46, 11, 25, 50, 11, 26, 54, 10, + 27, 59, 9, 28, 63, 9, 29, 67, 8, 30, 71, 7, + 31, 75, 7, 31, 79, 6, 32, 83, 5, 33, 87, 5, + 34, 91, 4, 35, 96, 3, 36, 100, 3, 37, 104, 2, + 38, 108, 1, 39, 112, 1, 40, 116, 0, 41, 120, 0, + 14, -8, 18, 15, -4, 18, 16, 0, 17, 17, 4, 16, + 18, 8, 16, 19, 12, 15, 20, 16, 14, 21, 20, 14, + 22, 24, 13, 23, 28, 12, 23, 32, 12, 24, 36, 11, + 25, 41, 10, 26, 45, 10, 27, 49, 9, 28, 53, 8, + 29, 57, 8, 30, 61, 7, 31, 65, 6, 32, 69, 6, + 33, 74, 5, 34, 78, 4, 35, 82, 4, 36, 86, 3, + 37, 90, 2, 38, 94, 2, 39, 98, 1, 39, 102, 0, + 40, 106, 0, 41, 111, 0, 42, 115, -1, 43, 119, -1, + 16, -9, 17, 17, -5, 16, 18, -1, 15, 19, 2, 15, + 20, 6, 14, 21, 10, 13, 22, 14, 13, 23, 19, 12, + 24, 23, 11, 25, 27, 11, 26, 31, 10, 27, 35, 9, + 28, 39, 9, 29, 43, 8, 30, 47, 7, 30, 51, 7, + 32, 56, 6, 32, 60, 5, 33, 64, 5, 34, 68, 4, + 35, 72, 3, 36, 76, 3, 37, 80, 2, 38, 84, 1, + 39, 88, 1, 40, 93, 0, 41, 97, 0, 42, 101, 0, + 43, 105, -1, 44, 109, -2, 45, 113, -2, 46, 117, -3, + 19, -10, 15, 20, -6, 14, 21, -2, 14, 22, 1, 13, + 23, 5, 12, 23, 9, 12, 24, 13, 11, 25, 18, 10, + 26, 22, 10, 27, 26, 9, 28, 30, 8, 29, 34, 8, + 30, 38, 7, 31, 42, 6, 32, 46, 6, 33, 50, 5, + 34, 55, 4, 35, 59, 4, 36, 63, 3, 37, 67, 2, + 38, 71, 2, 39, 75, 1, 39, 79, 0, 40, 83, 0, + 41, 87, 0, 42, 92, -1, 43, 96, -1, 44, 100, -2, + 45, 104, -3, 46, 108, -3, 47, 112, -4, 48, 116, -5, + 21, -12, 13, 22, -8, 13, 23, -4, 12, 24, 0, 11, + 25, 4, 11, 26, 8, 10, 27, 12, 9, 28, 16, 9, + 29, 20, 8, 30, 24, 7, 30, 28, 7, 31, 32, 6, + 32, 37, 5, 33, 41, 5, 34, 45, 4, 35, 49, 3, + 36, 53, 3, 37, 57, 2, 38, 61, 1, 39, 65, 1, + 40, 70, 0, 41, 74, 0, 42, 78, 0, 43, 82, -1, + 44, 86, -2, 45, 90, -2, 46, 94, -3, 46, 98, -4, + 47, 102, -4, 48, 107, -5, 49, 111, -6, 50, 115, -6, + 23, -13, 12, 24, -9, 11, 25, -5, 10, 26, -1, 10, + 27, 2, 9, 28, 6, 8, 29, 10, 8, 30, 15, 7, + 31, 19, 6, 32, 23, 6, 33, 27, 5, 34, 31, 4, + 35, 35, 4, 36, 39, 3, 37, 43, 2, 38, 47, 2, + 39, 52, 1, 39, 56, 0, 40, 60, 0, 41, 64, 0, + 42, 68, -1, 43, 72, -1, 44, 76, -2, 45, 80, -3, + 46, 84, -3, 47, 89, -4, 48, 93, -5, 49, 97, -5, + 50, 101, -6, 51, 105, -7, 52, 109, -7, 53, 113, -8, + 26, -14, 10, 27, -10, 9, 28, -6, 9, 29, -2, 8, + 30, 1, 7, 31, 5, 7, 31, 9, 6, 32, 14, 5, + 33, 18, 5, 34, 22, 4, 35, 26, 3, 36, 30, 3, + 37, 34, 2, 38, 38, 1, 39, 42, 1, 40, 46, 0, + 41, 51, 0, 42, 55, 0, 43, 59, -1, 44, 63, -2, + 45, 67, -2, 46, 71, -3, 46, 75, -4, 47, 79, -4, + 48, 83, -5, 49, 88, -6, 50, 92, -6, 51, 96, -7, + 52, 100, -8, 53, 104, -8, 54, 108, -9, 55, 112, -10, + 28, -16, 8, 29, -12, 8, 30, -8, 7, 31, -3, 6, + 32, 0, 6, 33, 4, 5, 34, 8, 4, 35, 12, 4, + 36, 16, 3, 37, 20, 2, 38, 24, 2, 38, 28, 1, + 39, 33, 0, 40, 37, 0, 41, 41, 0, 42, 45, -1, + 43, 49, -1, 44, 53, -2, 45, 57, -3, 46, 61, -3, + 47, 66, -4, 48, 70, -5, 49, 74, -5, 50, 78, -6, + 51, 82, -7, 52, 86, -7, 53, 90, -8, 54, 94, -9, + 54, 98, -9, 55, 103, -10, 56, 107, -11, 57, 111, -11, + 31, -17, 7, 31, -13, 6, 32, -9, 5, 33, -5, 5, + 34, -1, 4, 35, 2, 3, 36, 6, 3, 37, 11, 2, + 38, 15, 1, 39, 19, 1, 40, 23, 0, 41, 27, 0, + 42, 31, 0, 43, 35, -1, 44, 39, -2, 45, 43, -2, + 46, 48, -3, 47, 52, -4, 47, 56, -4, 48, 60, -5, + 49, 64, -6, 50, 68, -6, 51, 72, -7, 52, 76, -8, + 53, 80, -8, 54, 85, -9, 55, 89, -10, 56, 93, -10, + 57, 97, -11, 58, 101, -12, 59, 105, -12, 60, 109, -13, + 33, -18, 5, 34, -14, 4, 35, -10, 4, 36, -6, 3, + 37, -2, 2, 38, 1, 2, 38, 5, 1, 40, 10, 0, + 40, 14, 0, 41, 18, 0, 42, 22, -1, 43, 26, -1, + 44, 30, -2, 45, 34, -3, 46, 38, -3, 47, 42, -4, + 48, 47, -5, 49, 51, -5, 50, 55, -6, 51, 59, -7, + 52, 63, -7, 53, 67, -8, 54, 71, -9, 54, 75, -9, + 55, 79, -10, 56, 84, -11, 57, 88, -11, 58, 92, -12, + 59, 96, -13, 60, 100, -14, 61, 104, -14, 62, 108, -15, + 35, -20, 3, 36, -16, 3, 37, -12, 2, 38, -7, 1, + 39, -3, 1, 40, 0, 0, 41, 4, 0, 42, 8, 0, + 43, 12, -1, 44, 16, -2, 45, 20, -2, 45, 24, -3, + 47, 29, -4, 47, 33, -4, 48, 37, -5, 49, 41, -6, + 50, 45, -6, 51, 49, -7, 52, 53, -8, 53, 57, -8, + 54, 62, -9, 55, 66, -10, 56, 70, -10, 57, 74, -11, + 58, 78, -12, 59, 82, -12, 60, 86, -13, 61, 90, -14, + 61, 94, -14, 62, 99, -15, 63, 103, -16, 64, 107, -16, + 38, -21, 1, 39, -17, 1, 40, -13, 0, 41, -9, 0, + 42, -5, -1, 43, -1, -1, 44, 2, -2, 45, 7, -3, + 46, 11, -3, 47, 15, -4, 48, 19, -5, 48, 23, -5, + 49, 27, -6, 50, 31, -7, 51, 35, -7, 52, 39, -8, + 53, 44, -9, 54, 48, -9, 55, 52, -10, 56, 56, -11, + 57, 60, -11, 58, 64, -12, 59, 68, -13, 60, 72, -13, + 61, 76, -14, 62, 81, -15, 63, 85, -15, 63, 89, -16, + 64, 93, -17, 65, 97, -17, 66, 101, -18, 67, 105, -19, + 41, -23, 0, 41, -19, 0, 42, -15, -1, 43, -10, -2, + 44, -6, -2, 45, -2, -3, 46, 1, -4, 47, 5, -4, + 48, 9, -5, 49, 13, -6, 50, 17, -6, 51, 21, -7, + 52, 26, -8, 53, 30, -8, 54, 34, -9, 55, 38, -10, + 56, 42, -10, 56, 46, -11, 57, 50, -12, 58, 54, -12, + 59, 59, -13, 60, 63, -14, 61, 67, -14, 62, 71, -15, + 63, 75, -16, 64, 79, -16, 65, 83, -17, 66, 87, -18, + 67, 91, -18, 68, 96, -19, 69, 100, -20, 70, 104, -20, + 43, -24, -1, 44, -20, -2, 45, -16, -2, 46, -11, -3, + 47, -7, -4, 48, -3, -5, 48, 0, -5, 49, 4, -6, + 50, 8, -7, 51, 12, -7, 52, 16, -8, 53, 20, -9, + 54, 25, -9, 55, 29, -10, 56, 33, -11, 57, 37, -11, + 58, 41, -12, 59, 45, -13, 60, 49, -13, 61, 53, -14, + 62, 58, -15, 63, 62, -15, 64, 66, -16, 64, 70, -17, + 65, 74, -17, 66, 78, -18, 67, 82, -19, 68, 86, -19, + 69, 90, -20, 70, 95, -21, 71, 99, -21, 72, 103, -22, + 45, -25, -3, 46, -21, -4, 47, -17, -4, 48, -13, -5, + 49, -9, -6, 50, -5, -6, 51, -1, -7, 52, 3, -8, + 53, 7, -8, 54, 11, -9, 55, 15, -10, 55, 19, -10, + 57, 23, -11, 57, 27, -12, 58, 31, -12, 59, 35, -13, + 60, 40, -14, 61, 44, -14, 62, 48, -15, 63, 52, -16, + 64, 56, -16, 65, 60, -17, 66, 64, -18, 67, 68, -18, + 68, 72, -19, 69, 77, -20, 70, 81, -20, 71, 85, -21, + 71, 89, -22, 72, 93, -22, 73, 97, -23, 74, 101, -24, + 48, -27, -5, 48, -23, -5, 49, -19, -6, 50, -14, -7, + 51, -10, -7, 52, -6, -8, 53, -2, -9, 54, 1, -9, + 55, 5, -10, 56, 9, -11, 57, 13, -11, 58, 17, -12, + 59, 22, -13, 60, 26, -13, 61, 30, -14, 62, 34, -15, + 63, 38, -15, 64, 42, -16, 64, 46, -17, 65, 50, -17, + 66, 55, -18, 67, 59, -19, 68, 63, -19, 69, 67, -20, + 70, 71, -21, 71, 75, -21, 72, 79, -22, 73, 83, -23, + 74, 87, -23, 75, 92, -24, 76, 96, -25, 77, 100, -25, + 50, -28, -6, 51, -24, -7, 52, -20, -8, 53, -15, -8, + 54, -11, -9, 55, -7, -10, 56, -3, -10, 57, 0, -11, + 57, 4, -12, 58, 8, -12, 59, 12, -13, 60, 16, -14, + 61, 21, -14, 62, 25, -15, 63, 29, -16, 64, 33, -16, + 65, 37, -17, 66, 41, -18, 67, 45, -18, 68, 49, -19, + 69, 54, -20, 70, 58, -20, 71, 62, -21, 71, 66, -22, + 72, 70, -22, 73, 74, -23, 74, 78, -24, 75, 82, -24, + 76, 86, -25, 77, 91, -26, 78, 95, -26, 79, 99, -27, + 52, -29, -8, 53, -25, -9, 54, -21, -9, 55, -17, -10, + 56, -13, -11, 57, -9, -11, 58, -5, -12, 59, 0, -13, + 60, 3, -13, 61, 7, -14, 62, 11, -15, 63, 15, -15, + 64, 19, -16, 64, 23, -17, 65, 27, -17, 66, 31, -18, + 67, 36, -19, 68, 40, -19, 69, 44, -20, 70, 48, -21, + 71, 52, -21, 72, 56, -22, 73, 60, -23, 74, 64, -23, + 75, 68, -24, 76, 73, -25, 77, 77, -25, 78, 81, -26, + 78, 85, -27, 80, 89, -27, 80, 93, -28, 81, 97, -29, + 55, -31, -10, 56, -27, -10, 56, -23, -11, 57, -18, -12, + 58, -14, -12, 59, -10, -13, 60, -6, -14, 61, -2, -14, + 62, 1, -15, 63, 5, -16, 64, 9, -16, 65, 13, -17, + 66, 18, -18, 67, 22, -18, 68, 26, -19, 69, 30, -20, + 70, 34, -20, 71, 38, -21, 71, 42, -22, 72, 46, -22, + 73, 51, -23, 74, 55, -24, 75, 59, -24, 76, 63, -25, + 77, 67, -26, 78, 71, -26, 79, 75, -27, 80, 79, -28, + 81, 83, -28, 82, 88, -29, 83, 92, -30, 84, 96, -30, + 57, -32, -11, 58, -28, -12, 59, -24, -13, 60, -19, -13, + 61, -15, -14, 62, -11, -15, 63, -7, -15, 64, -3, -16, + 64, 0, -17, 65, 4, -17, 66, 8, -18, 67, 12, -19, + 68, 17, -19, 69, 21, -20, 70, 25, -21, 71, 29, -21, + 72, 33, -22, 73, 37, -23, 74, 41, -23, 75, 45, -24, + 76, 50, -25, 77, 54, -25, 78, 58, -26, 79, 62, -27, + 79, 66, -27, 80, 70, -28, 81, 74, -29, 82, 78, -29, + 83, 82, -30, 84, 87, -31, 85, 91, -31, 86, 95, -32, + 59, -33, -13, 60, -29, -14, 61, -25, -14, 62, -21, -15, + 63, -17, -16, 64, -13, -16, 65, -9, -17, 66, -4, -18, + 67, 0, -18, 68, 3, -19, 69, 7, -20, 70, 11, -20, + 71, 15, -21, 72, 19, -22, 72, 23, -22, 73, 27, -23, + 74, 32, -24, 75, 36, -24, 76, 40, -25, 77, 44, -26, + 78, 48, -26, 79, 52, -27, 80, 56, -28, 81, 60, -28, + 82, 64, -29, 83, 69, -30, 84, 73, -30, 85, 77, -31, + 86, 81, -32, 87, 85, -32, 87, 89, -33, 88, 93, -34, + 62, -35, -15, 63, -31, -15, 63, -27, -16, 65, -22, -17, + 65, -18, -17, 66, -14, -18, 67, -10, -19, 68, -6, -19, + 69, -2, -20, 70, 1, -21, 71, 5, -21, 72, 9, -22, + 73, 14, -23, 74, 18, -23, 75, 22, -24, 76, 26, -25, + 77, 30, -25, 78, 34, -26, 79, 38, -27, 79, 42, -27, + 80, 47, -28, 81, 51, -29, 82, 55, -29, 83, 59, -30, + 84, 63, -31, 85, 67, -31, 86, 71, -32, 87, 75, -33, + 88, 79, -33, 89, 84, -34, 90, 88, -35, 91, 92, -35, + 64, -36, -16, 65, -32, -17, 66, -28, -18, 67, -23, -18, + 68, -19, -19, 69, -15, -20, 70, -11, -20, 71, -7, -21, + 72, -3, -22, 72, 0, -22, 73, 4, -23, 74, 8, -24, + 75, 13, -24, 76, 17, -25, 77, 21, -26, 78, 25, -26, + 79, 29, -27, 80, 33, -28, 81, 37, -28, 82, 41, -29, + 83, 46, -30, 84, 50, -30, 85, 54, -31, 86, 58, -32, + 86, 62, -32, 87, 66, -33, 88, 70, -34, 89, 74, -34, + 90, 78, -35, 91, 83, -36, 92, 87, -36, 93, 91, -37, + 66, -37, -18, 67, -33, -19, 68, -29, -19, 69, -25, -20, + 70, -21, -21, 71, -17, -21, 72, -13, -22, 73, -8, -23, + 74, -4, -23, 75, 0, -24, 76, 3, -25, 77, 7, -25, + 78, 11, -26, 79, 15, -27, 79, 19, -27, 80, 23, -28, + 81, 28, -29, 82, 32, -29, 83, 36, -30, 84, 40, -31, + 85, 44, -31, 86, 48, -32, 87, 52, -33, 88, 56, -33, + 89, 60, -34, 90, 65, -35, 91, 69, -35, 92, 73, -36, + 93, 77, -37, 94, 81, -37, 95, 85, -38, 95, 89, -39, + 69, -39, -20, 70, -35, -20, 71, -31, -21, 72, -26, -22, + 72, -22, -22, 73, -18, -23, 74, -14, -24, 75, -10, -24, + 76, -6, -25, 77, -2, -26, 78, 1, -26, 79, 5, -27, + 80, 10, -28, 81, 14, -28, 82, 18, -29, 83, 22, -30, + 84, 26, -30, 85, 30, -31, 86, 34, -32, 86, 38, -32, + 88, 43, -33, 88, 47, -34, 89, 51, -34, 90, 55, -35, + 91, 59, -36, 92, 63, -36, 93, 67, -37, 94, 71, -38, + 95, 75, -38, 96, 80, -39, 97, 84, -40, 98, 88, -40, + 71, -40, -21, 72, -36, -22, 73, -32, -23, 74, -27, -23, + 75, -23, -24, 76, -19, -25, 77, -15, -25, 78, -11, -26, + 79, -7, -27, 79, -3, -27, 80, 0, -28, 81, 4, -29, + 82, 9, -29, 83, 13, -30, 84, 17, -31, 85, 21, -31, + 86, 25, -32, 87, 29, -33, 88, 33, -33, 89, 37, -34, + 90, 42, -35, 91, 46, -35, 92, 50, -36, 93, 54, -37, + 94, 58, -37, 95, 62, -38, 95, 66, -39, 96, 70, -39, + 97, 74, -40, 98, 79, -41, 99, 83, -41, 100, 87, -42, + 73, -41, -23, 74, -37, -24, 75, -33, -24, 76, -29, -25, + 77, -25, -26, 78, -21, -26, 79, -17, -27, 80, -12, -28, + 81, -8, -28, 82, -4, -29, 83, 0, -30, 84, 3, -30, + 85, 7, -31, 86, 11, -32, 87, 15, -32, 87, 19, -33, + 88, 24, -34, 89, 28, -34, 90, 32, -35, 91, 36, -36, + 92, 40, -36, 93, 44, -37, 94, 48, -38, 95, 52, -38, + 96, 56, -39, 97, 61, -40, 98, 65, -40, 99, 69, -41, + 100, 73, -42, 101, 77, -42, 102, 81, -43, 102, 85, -44, + 76, -43, -25, 77, -39, -25, 78, -35, -26, 79, -30, -27, + 80, -26, -27, 80, -22, -28, 81, -18, -29, 82, -14, -29, + 83, -10, -30, 84, -6, -31, 85, -2, -31, 86, 1, -32, + 87, 6, -33, 88, 10, -33, 89, 14, -34, 90, 18, -35, + 91, 22, -35, 92, 26, -36, 93, 30, -37, 94, 34, -37, + 95, 39, -38, 95, 43, -39, 96, 47, -39, 97, 51, -40, + 98, 55, -41, 99, 59, -41, 100, 63, -42, 101, 67, -43, + 102, 71, -43, 103, 76, -44, 104, 80, -45, 105, 84, -45, + 78, -44, -26, 79, -40, -27, 80, -36, -28, 81, -31, -28, + 82, -27, -29, 83, -23, -30, 84, -19, -30, 85, -15, -31, + 86, -11, -32, 87, -7, -32, 87, -3, -33, 88, 0, -34, + 89, 5, -34, 90, 9, -35, 91, 13, -36, 92, 17, -36, + 93, 21, -37, 94, 25, -38, 95, 29, -38, 96, 33, -39, + 97, 38, -40, 98, 42, -40, 99, 46, -41, 100, 50, -42, + 101, 54, -42, 102, 58, -43, 102, 62, -44, 103, 66, -44, + 104, 70, -45, 105, 75, -46, 106, 79, -46, 107, 83, -47, + 80, -45, -28, 81, -41, -29, 82, -37, -29, 83, -33, -30, + 84, -29, -31, 85, -25, -31, 86, -21, -32, 87, -16, -33, + 88, -12, -33, 89, -8, -34, 90, -4, -35, 91, 0, -35, + 92, 3, -36, 93, 7, -37, 94, 11, -37, 94, 15, -38, + 95, 20, -39, 96, 24, -39, 97, 28, -40, 98, 32, -41, + 99, 36, -41, 100, 40, -42, 101, 44, -43, 102, 48, -43, + 103, 52, -44, 104, 57, -45, 105, 61, -45, 106, 65, -46, + 107, 69, -47, 108, 73, -47, 109, 77, -48, 110, 81, -49, + 83, -47, -30, 84, -43, -30, 85, -39, -31, 86, -34, -32, + 87, -30, -32, 87, -26, -33, 88, -22, -34, 89, -18, -34, + 90, -14, -35, 91, -10, -36, 92, -6, -36, 93, -2, -37, + 94, 2, -38, 95, 6, -38, 96, 10, -39, 97, 14, -40, + 98, 18, -40, 99, 22, -41, 100, 26, -42, 101, 30, -42, + 102, 35, -43, 103, 39, -44, 103, 43, -44, 104, 47, -45, + 105, 51, -46, 106, 55, -46, 107, 59, -47, 108, 63, -48, + 109, 67, -48, 110, 72, -49, 111, 76, -50, 112, 80, -50, + 85, -48, -31, 86, -44, -32, 87, -40, -33, 88, -35, -33, + 89, -31, -34, 90, -27, -35, 91, -23, -35, 92, -19, -36, + 93, -15, -37, 94, -11, -37, 94, -7, -38, 95, -3, -39, + 96, 1, -39, 97, 5, -40, 98, 9, -41, 99, 13, -41, + 100, 17, -42, 101, 21, -43, 102, 25, -43, 103, 29, -44, + 104, 34, -45, 105, 38, -45, 106, 42, -46, 107, 46, -47, + 108, 50, -47, 109, 54, -48, 110, 58, -49, 110, 62, -49, + 111, 66, -50, 112, 71, -51, 113, 75, -51, 114, 79, -52, + 88, -49, -33, 89, -45, -34, 90, -41, -35, 91, -37, -35, + 92, -33, -36, 93, -29, -37, 94, -25, -37, 95, -20, -38, + 96, -16, -39, 97, -12, -39, 97, -8, -40, 98, -4, -41, + 99, 0, -41, 100, 3, -42, 101, 7, -43, 102, 11, -43, + 103, 16, -44, 104, 20, -45, 105, 24, -45, 106, 28, -46, + 107, 32, -47, 108, 36, -47, 109, 40, -48, 110, 44, -49, + 111, 48, -49, 112, 53, -50, 112, 57, -51, 113, 61, -51, + 114, 65, -52, 115, 69, -53, 116, 73, -54, 117, 77, -54, + 90, -51, -35, 91, -47, -36, 92, -43, -36, 93, -38, -37, + 94, -34, -38, 95, -30, -38, 96, -26, -39, 97, -22, -40, + 98, -18, -40, 99, -14, -41, 100, -10, -42, 101, -6, -42, + 102, -1, -43, 103, 2, -44, 104, 6, -44, 104, 10, -45, + 105, 14, -46, 106, 18, -46, 107, 22, -47, 108, 26, -48, + 109, 31, -49, 110, 35, -49, 111, 39, -50, 112, 43, -50, + 113, 47, -51, 114, 51, -52, 115, 55, -53, 116, 59, -53, + 117, 63, -54, 118, 68, -55, 119, 72, -55, 119, 76, -56, + 93, -52, -37, 94, -48, -37, 95, -44, -38, 96, -40, -39, + 97, -36, -39, 97, -32, -40, 98, -28, -41, 99, -23, -41, + 100, -19, -42, 101, -15, -43, 102, -11, -43, 103, -7, -44, + 104, -3, -45, 105, 0, -45, 106, 4, -46, 107, 8, -47, + 108, 13, -48, 109, 17, -48, 110, 21, -49, 111, 25, -49, + 112, 29, -50, 112, 33, -51, 113, 37, -51, 114, 41, -52, + 115, 45, -53, 116, 50, -54, 117, 54, -54, 118, 58, -55, + 119, 62, -55, 120, 66, -56, 121, 70, -57, 122, 74, -58, + 95, -53, -38, 96, -49, -39, 97, -45, -40, 98, -41, -40, + 99, -37, -41, 100, -33, -42, 101, -29, -42, 102, -24, -43, + 103, -20, -44, 104, -16, -44, 104, -12, -45, 105, -8, -46, + 106, -4, -47, 107, 0, -47, 108, 3, -48, 109, 7, -48, + 110, 12, -49, 111, 16, -50, 112, 20, -50, 113, 24, -51, + 114, 28, -52, 115, 32, -53, 116, 36, -53, 117, 40, -54, + 118, 44, -54, 119, 49, -55, 120, 53, -56, 120, 57, -57, + 121, 61, -57, 122, 65, -58, 123, 69, -59, 124, 73, -59, + 97, -55, -40, 98, -51, -41, 99, -47, -41, 100, -42, -42, + 101, -38, -43, 102, -34, -43, 103, -30, -44, 104, -26, -45, + 105, -22, -45, 106, -18, -46, 107, -14, -47, 108, -10, -47, + 109, -5, -48, 110, -1, -49, 111, 2, -49, 111, 6, -50, + 113, 10, -51, 113, 14, -52, 114, 18, -52, 115, 22, -53, + 116, 27, -54, 117, 31, -54, 118, 35, -55, 119, 39, -55, + 120, 43, -56, 121, 47, -57, 122, 51, -58, 123, 55, -58, + 124, 59, -59, 125, 64, -60, 126, 68, -60, 127, 72, -61, + 100, -56, -42, 101, -52, -42, 102, -48, -43, 103, -44, -44, + 104, -40, -44, 104, -36, -45, 105, -32, -46, 106, -27, -47, + 107, -23, -47, 108, -19, -48, 109, -15, -48, 110, -11, -49, + 111, -7, -50, 112, -3, -51, 113, 0, -51, 114, 4, -52, + 115, 9, -53, 116, 13, -53, 117, 17, -54, 118, 21, -54, + 119, 25, -55, 120, 29, -56, 120, 33, -57, 121, 37, -57, + 122, 41, -58, 123, 46, -59, 124, 50, -59, 125, 54, -60, + 126, 58, -61, 127, 62, -61, 128, 66, -62, 129, 70, -63, + 102, -57, -43, 103, -53, -44, 104, -49, -45, 105, -45, -46, + 106, -41, -46, 107, -37, -47, 108, -33, -47, 109, -28, -48, + 110, -24, -49, 111, -20, -49, 112, -16, -50, 112, -12, -51, + 113, -8, -52, 114, -4, -52, 115, 0, -53, 116, 3, -53, + 117, 8, -54, 118, 12, -55, 119, 16, -56, 120, 20, -56, + 121, 24, -57, 122, 28, -58, 123, 32, -58, 124, 36, -59, + 125, 40, -59, 126, 45, -60, 127, 49, -61, 127, 53, -62, + 128, 57, -62, 129, 61, -63, 130, 65, -64, 131, 69, -64, + 105, -59, -45, 105, -55, -46, 106, -51, -46, 107, -46, -47, + 108, -42, -48, 109, -38, -48, 110, -34, -49, 111, -30, -50, + 112, -26, -51, 113, -22, -51, 114, -18, -52, 115, -14, -52, + 116, -9, -53, 117, -5, -54, 118, -1, -55, 119, 2, -55, + 120, 6, -56, 120, 10, -57, 121, 14, -57, 122, 18, -58, + 123, 23, -59, 124, 27, -59, 125, 31, -60, 126, 35, -61, + 127, 39, -61, 128, 43, -62, 129, 47, -63, 130, 51, -63, + 131, 55, -64, 132, 60, -65, 133, 64, -65, 134, 68, -66, + 107, -60, -47, 108, -56, -47, 109, -52, -48, 110, -48, -49, + 111, -44, -50, 112, -40, -50, 112, -36, -51, 113, -31, -52, + 114, -27, -52, 115, -23, -53, 116, -19, -53, 117, -15, -54, + 118, -11, -55, 119, -7, -56, 120, -3, -56, 121, 0, -57, + 122, 5, -58, 123, 9, -58, 124, 13, -59, 125, 17, -60, + 126, 21, -60, 127, 25, -61, 127, 29, -62, 128, 33, -62, + 129, 37, -63, 130, 42, -64, 131, 46, -64, 132, 50, -65, + 133, 54, -66, 134, 58, -66, 135, 62, -67, 136, 66, -68, + 109, -61, -49, 110, -57, -49, 111, -53, -50, 112, -49, -51, + 113, -45, -51, 114, -41, -52, 115, -37, -52, 116, -32, -53, + 117, -28, -54, 118, -24, -55, 119, -20, -55, 119, -16, -56, + 120, -12, -57, 121, -8, -57, 122, -4, -58, 123, 0, -59, + 124, 4, -59, 125, 8, -60, 126, 12, -61, 127, 16, -61, + 128, 20, -62, 129, 24, -63, 130, 28, -63, 131, 32, -64, + 132, 36, -65, 133, 41, -65, 134, 45, -66, 135, 49, -67, + 135, 53, -67, 136, 57, -68, 137, 61, -69, 138, 65, -69, + 112, -63, -50, 112, -59, -51, 113, -55, -51, 114, -50, -52, + 115, -46, -53, 116, -42, -54, 117, -38, -54, 118, -34, -55, + 119, -30, -56, 120, -26, -56, 121, -22, -57, 122, -18, -57, + 123, -13, -58, 124, -9, -59, 125, -5, -60, 126, -1, -60, + 127, 2, -61, 128, 6, -62, 128, 10, -62, 129, 14, -63, + 130, 19, -64, 131, 23, -64, 132, 27, -65, 133, 31, -66, + 134, 35, -66, 135, 39, -67, 136, 43, -68, 137, 47, -68, + 138, 51, -69, 139, 56, -70, 140, 60, -70, 141, 64, -71, + 114, -64, -52, 115, -60, -53, 116, -56, -53, 117, -52, -54, + 118, -48, -55, 119, -44, -55, 119, -40, -56, 121, -35, -57, + 121, -31, -57, 122, -27, -58, 123, -23, -59, 124, -19, -59, + 125, -15, -60, 126, -11, -61, 127, -7, -61, 128, -3, -62, + 129, 1, -63, 130, 5, -63, 131, 9, -64, 132, 13, -65, + 133, 17, -65, 134, 21, -66, 135, 25, -67, 135, 29, -67, + 136, 33, -68, 137, 38, -69, 138, 42, -69, 139, 46, -70, + 140, 50, -71, 141, 54, -71, 142, 58, -72, 143, 62, -73, + 116, -65, -54, 117, -61, -54, 118, -57, -55, 119, -53, -56, + 120, -49, -56, 121, -45, -57, 122, -41, -58, 123, -36, -58, + 124, -32, -59, 125, -28, -60, 126, -24, -60, 127, -20, -61, + 128, -16, -62, 128, -12, -62, 129, -8, -63, 130, -4, -64, + 131, 0, -64, 132, 4, -65, 133, 8, -66, 134, 12, -66, + 135, 16, -67, 136, 20, -68, 137, 24, -68, 138, 28, -69, + 139, 32, -70, 140, 37, -70, 141, 41, -71, 142, 45, -72, + 142, 49, -72, 143, 53, -73, 144, 57, -74, 145, 61, -74, + 119, -67, -55, 120, -63, -56, 120, -59, -57, 121, -54, -57, + 122, -50, -58, 123, -46, -59, 124, -42, -59, 125, -38, -60, + 126, -34, -61, 127, -30, -61, 128, -26, -62, 129, -22, -63, + 130, -17, -63, 131, -13, -64, 132, -9, -65, 133, -5, -65, + 134, -1, -66, 135, 2, -67, 135, 6, -67, 136, 10, -68, + 137, 15, -69, 138, 19, -69, 139, 23, -70, 140, 27, -71, + 141, 31, -71, 142, 35, -72, 143, 39, -73, 144, 43, -73, + 145, 47, -74, 146, 52, -75, 147, 56, -75, 148, 60, -76, + 121, -68, -57, 122, -64, -58, 123, -60, -58, 124, -56, -59, + 125, -52, -60, 126, -48, -60, 127, -44, -61, 128, -39, -62, + 128, -35, -62, 129, -31, -63, 130, -27, -64, 131, -23, -64, + 132, -19, -65, 133, -15, -66, 134, -11, -66, 135, -7, -67, + 136, -2, -68, 137, 1, -68, 138, 5, -69, 139, 9, -70, + 140, 13, -70, 141, 17, -71, 142, 21, -72, 142, 25, -72, + 143, 29, -73, 144, 34, -74, 145, 38, -74, 146, 42, -75, + 147, 46, -76, 148, 50, -76, 149, 54, -77, 150, 58, -78, + 123, -69, -59, 124, -65, -59, 125, -61, -60, 126, -57, -61, + 127, -53, -61, 128, -49, -62, 129, -45, -63, 130, -40, -63, + 131, -36, -64, 132, -32, -65, 133, -28, -65, 134, -24, -66, + 135, -20, -67, 135, -16, -67, 136, -12, -68, 137, -8, -69, + 138, -3, -69, 139, 0, -70, 140, 4, -71, 141, 8, -71, + 142, 12, -72, 143, 16, -73, 144, 20, -73, 145, 24, -74, + 146, 28, -75, 147, 33, -75, 148, 37, -76, 149, 41, -77, + 150, 45, -77, 151, 49, -78, 151, 53, -79, 152, 57, -79, + 126, -71, -60, 127, -67, -61, 127, -63, -62, 128, -58, -62, + 129, -54, -63, 130, -50, -64, 131, -46, -64, 132, -42, -65, + 133, -38, -66, 134, -34, -66, 135, -30, -67, 136, -26, -68, + 137, -21, -68, 138, -17, -69, 139, -13, -70, 140, -9, -70, + 141, -5, -71, 142, -1, -72, 143, 2, -72, 143, 6, -73, + 144, 11, -74, 145, 15, -74, 146, 19, -75, 147, 23, -76, + 148, 27, -76, 149, 31, -77, 150, 35, -78, 151, 39, -78, + 152, 43, -79, 153, 48, -80, 154, 52, -80, 155, 56, -81, + 128, -72, -62, 129, -68, -63, 130, -64, -63, 131, -60, -64, + 132, -56, -65, 133, -52, -65, 134, -48, -66, 135, -43, -67, + 136, -39, -67, 136, -35, -68, 137, -31, -69, 138, -27, -69, + 139, -23, -70, 140, -19, -71, 141, -15, -71, 142, -11, -72, + 143, -6, -73, 144, -2, -73, 145, 1, -74, 146, 5, -75, + 147, 9, -75, 148, 13, -76, 149, 17, -77, 150, 21, -77, + 150, 25, -78, 151, 30, -79, 152, 34, -79, 153, 38, -80, + 154, 42, -81, 155, 46, -81, 156, 50, -82, 157, 54, -83, + 130, -73, -64, 131, -69, -64, 132, -65, -65, 133, -61, -66, + 134, -57, -66, 135, -53, -67, 136, -49, -68, 137, -44, -68, + 138, -40, -69, 139, -36, -70, 140, -32, -70, 141, -28, -71, + 142, -24, -72, 143, -20, -72, 143, -16, -73, 144, -12, -74, + 145, -7, -74, 146, -3, -75, 147, 0, -76, 148, 4, -76, + 149, 8, -77, 150, 12, -78, 151, 16, -78, 152, 20, -79, + 153, 24, -80, 154, 29, -80, 155, 33, -81, 156, 37, -82, + 157, 41, -82, 158, 45, -83, 158, 49, -84, 159, 53, -84, + 133, -75, -65, 134, -71, -66, 135, -67, -67, 136, -62, -67, + 136, -58, -68, 137, -54, -69, 138, -50, -69, 139, -46, -70, + 140, -42, -71, 141, -38, -71, 142, -34, -72, 143, -30, -73, + 144, -25, -73, 145, -21, -74, 146, -17, -75, 147, -13, -75, + 148, -9, -76, 149, -5, -77, 150, -1, -77, 150, 2, -78, + 151, 7, -79, 152, 11, -79, 153, 15, -80, 154, 19, -81, + 155, 23, -81, 156, 27, -82, 157, 31, -83, 158, 35, -83, + 159, 39, -84, 160, 44, -85, 161, 48, -85, 162, 52, -86, + 135, -76, -67, 136, -72, -68, 137, -68, -68, 138, -63, -69, + 139, -59, -70, 140, -55, -70, 141, -51, -71, 142, -47, -72, + 143, -43, -72, 143, -39, -73, 144, -35, -74, 145, -31, -74, + 146, -26, -75, 147, -22, -76, 148, -18, -76, 149, -14, -77, + 150, -10, -78, 151, -6, -78, 152, -2, -79, 153, 1, -80, + 154, 6, -80, 155, 10, -81, 156, 14, -82, 157, 18, -82, + 157, 22, -83, 159, 26, -84, 159, 30, -84, 160, 34, -85, + 161, 38, -86, 162, 43, -86, 163, 47, -87, 164, 51, -88, + 138, -78, -69, 139, -74, -70, 140, -70, -70, 141, -65, -71, + 142, -61, -72, 143, -57, -72, 144, -53, -73, 145, -49, -74, + 145, -45, -74, 146, -41, -75, 147, -37, -76, 148, -33, -76, + 149, -28, -77, 150, -24, -78, 151, -20, -78, 152, -16, -79, + 153, -12, -80, 154, -8, -80, 155, -4, -81, 156, 0, -82, + 157, 4, -82, 158, 8, -83, 159, 12, -84, 160, 16, -84, + 160, 20, -85, 161, 24, -86, 162, 28, -86, 163, 32, -87, + 164, 36, -88, 165, 41, -88, 166, 45, -89, 167, 49, -90, + 140, -79, -71, 141, -75, -71, 142, -71, -72, 143, -66, -73, + 144, -62, -73, 145, -58, -74, 146, -54, -75, 147, -50, -75, + 148, -46, -76, 149, -42, -77, 150, -38, -77, 151, -34, -78, + 152, -29, -79, 153, -25, -79, 153, -21, -80, 154, -17, -81, + 155, -13, -81, 156, -9, -82, 157, -5, -83, 158, -1, -83, + 159, 3, -84, 160, 7, -85, 161, 11, -85, 162, 15, -86, + 163, 19, -87, 164, 23, -87, 165, 27, -88, 166, 31, -89, + 167, 35, -89, 168, 40, -90, 168, 44, -91, 169, 48, -91, + 143, -80, -72, 144, -76, -73, 144, -72, -74, 146, -68, -74, + 146, -64, -75, 147, -60, -76, 148, -56, -76, 149, -51, -77, + 150, -47, -78, 151, -43, -78, 152, -39, -79, 153, -35, -80, + 154, -31, -80, 155, -27, -81, 156, -23, -82, 157, -19, -82, + 158, -14, -83, 159, -10, -84, 160, -6, -84, 160, -2, -85, + 161, 1, -86, 162, 5, -86, 163, 9, -87, 164, 13, -88, + 165, 17, -88, 166, 22, -89, 167, 26, -90, 168, 30, -90, + 169, 34, -91, 170, 38, -92, 171, 42, -92, 172, 46, -93, + 145, -82, -74, 146, -78, -75, 147, -74, -75, 148, -69, -76, + 149, -65, -77, 150, -61, -77, 151, -57, -78, 152, -53, -79, + 153, -49, -79, 153, -45, -80, 154, -41, -81, 155, -37, -81, + 156, -32, -82, 157, -28, -83, 158, -24, -83, 159, -20, -84, + 160, -16, -85, 161, -12, -85, 162, -8, -86, 163, -4, -87, + 164, 0, -87, 165, 4, -88, 166, 8, -89, 167, 12, -89, + 167, 16, -90, 168, 20, -91, 169, 24, -91, 170, 28, -92, + 171, 32, -93, 172, 37, -93, 173, 41, -94, 174, 45, -95, + 147, -83, -76, 148, -79, -76, 149, -75, -77, 150, -70, -78, + 151, -66, -78, 152, -62, -79, 153, -58, -80, 154, -54, -80, + 155, -50, -81, 156, -46, -82, 157, -42, -82, 158, -38, -83, + 159, -33, -84, 160, -29, -84, 160, -25, -85, 161, -21, -86, + 162, -17, -86, 163, -13, -87, 164, -9, -88, 165, -5, -88, + 166, 0, -89, 167, 3, -90, 168, 7, -90, 169, 11, -91, + 170, 15, -92, 171, 19, -92, 172, 23, -93, 173, 27, -94, + 174, 31, -94, 175, 36, -95, 176, 40, -96, 176, 44, -96, + 150, -84, -77, 151, -80, -78, 152, -76, -79, 153, -72, -79, + 153, -68, -80, 154, -64, -81, 155, -60, -81, 156, -55, -82, + 157, -51, -83, 158, -47, -83, 159, -43, -84, 160, -39, -85, + 161, -35, -85, 162, -31, -86, 163, -27, -87, 164, -23, -87, + 165, -18, -88, 166, -14, -89, 167, -10, -89, 167, -6, -90, + 169, -2, -91, 169, 1, -91, 170, 5, -92, 171, 9, -93, + 172, 13, -93, 173, 18, -94, 174, 22, -95, 175, 26, -95, + 176, 30, -96, 177, 34, -97, 178, 38, -97, 179, 42, -98, + 152, -86, -79, 153, -82, -80, 154, -78, -80, 155, -73, -81, + 156, -69, -82, 157, -65, -82, 158, -61, -83, 159, -57, -84, + 160, -53, -84, 160, -49, -85, 161, -45, -86, 162, -41, -86, + 163, -36, -87, 164, -32, -88, 165, -28, -88, 166, -24, -89, + 167, -20, -90, 168, -16, -90, 169, -12, -91, 170, -8, -92, + 171, -3, -92, 172, 0, -93, 173, 4, -94, 174, 8, -94, + 175, 12, -95, 176, 16, -96, 176, 20, -96, 177, 24, -97, + 178, 28, -98, 179, 33, -99, 180, 37, -99, 181, 41, -100, + 154, -87, -81, 155, -83, -81, 156, -79, -82, 157, -74, -83, + 158, -70, -83, 159, -66, -84, 160, -62, -85, 161, -58, -85, + 162, -54, -86, 163, -50, -87, 164, -46, -87, 165, -42, -88, + 166, -37, -89, 167, -33, -89, 168, -29, -90, 168, -25, -91, + 169, -21, -91, 170, -17, -92, 171, -13, -93, 172, -9, -93, + 173, -4, -94, 174, 0, -95, 175, 3, -95, 176, 7, -96, + 177, 11, -97, 178, 15, -97, 179, 19, -98, 180, 23, -99, + 181, 27, -99, 182, 32, -100, 183, 36, -101, 183, 40, -101, + 157, -88, -82, 158, -84, -83, 159, -80, -84, 160, -76, -84, + 161, -72, -85, 161, -68, -86, 162, -64, -86, 163, -59, -87, + 164, -55, -88, 165, -51, -88, 166, -47, -89, 167, -43, -90, + 168, -39, -90, 169, -35, -91, 170, -31, -92, 171, -27, -92, + 172, -22, -93, 173, -18, -94, 174, -14, -94, 175, -10, -95, + 176, -6, -96, 176, -2, -96, 177, 1, -97, 178, 5, -98, + 179, 9, -98, 180, 14, -99, 181, 18, -100, 182, 22, -100, + 183, 26, -101, 184, 30, -102, 185, 34, -103, 186, 38, -103, + 159, -90, -84, 160, -86, -85, 161, -82, -85, 162, -77, -86, + 163, -73, -87, 164, -69, -87, 165, -65, -88, 166, -61, -89, + 167, -57, -89, 168, -53, -90, 168, -49, -91, 169, -45, -91, + 170, -40, -92, 171, -36, -93, 172, -32, -93, 173, -28, -94, + 174, -24, -95, 175, -20, -95, 176, -16, -96, 177, -12, -97, + 178, -7, -98, 179, -3, -98, 180, 0, -99, 181, 4, -99, + 182, 8, -100, 183, 12, -101, 183, 16, -101, 184, 20, -102, + 185, 24, -103, 186, 29, -104, 187, 33, -104, 188, 37, -105, + 161, -91, -86, 162, -87, -86, 163, -83, -87, 164, -78, -88, + 165, -74, -88, 166, -70, -89, 167, -66, -90, 168, -62, -90, + 169, -58, -91, 170, -54, -92, 171, -50, -92, 172, -46, -93, + 173, -41, -94, 174, -37, -94, 175, -33, -95, 175, -29, -96, + 176, -25, -96, 177, -21, -97, 178, -17, -98, 179, -13, -98, + 180, -8, -99, 181, -4, -100, 182, 0, -100, 183, 3, -101, + 184, 7, -102, 185, 11, -103, 186, 15, -103, 187, 19, -104, + 188, 23, -104, 189, 28, -105, 190, 32, -106, 191, 36, -107, + 14, -8, 24, 15, -4, 23, 16, 0, 23, 17, 4, 22, + 18, 8, 21, 19, 12, 21, 20, 16, 20, 21, 20, 19, + 22, 24, 19, 23, 28, 18, 23, 32, 17, 24, 36, 17, + 25, 41, 16, 26, 45, 15, 27, 49, 15, 28, 53, 14, + 29, 57, 13, 30, 61, 13, 31, 65, 12, 32, 69, 11, + 33, 74, 11, 34, 78, 10, 35, 82, 9, 36, 86, 9, + 37, 90, 8, 38, 94, 7, 39, 98, 7, 39, 102, 6, + 40, 106, 5, 41, 111, 5, 42, 115, 4, 43, 119, 3, + 16, -9, 22, 17, -5, 22, 18, -1, 21, 19, 2, 20, + 20, 6, 20, 21, 10, 19, 22, 14, 18, 23, 19, 18, + 24, 23, 17, 25, 27, 16, 26, 31, 16, 27, 35, 15, + 28, 39, 14, 29, 43, 14, 30, 47, 13, 31, 51, 12, + 32, 56, 12, 32, 60, 11, 33, 64, 10, 34, 68, 10, + 35, 72, 9, 36, 76, 8, 37, 80, 8, 38, 84, 7, + 39, 88, 6, 40, 93, 6, 41, 97, 5, 42, 101, 4, + 43, 105, 4, 44, 109, 3, 45, 113, 2, 46, 117, 2, + 19, -10, 21, 20, -6, 20, 21, -2, 19, 22, 1, 19, + 23, 5, 18, 24, 9, 17, 24, 13, 17, 25, 18, 16, + 26, 22, 15, 27, 26, 15, 28, 30, 14, 29, 34, 13, + 30, 38, 13, 31, 42, 12, 32, 46, 11, 33, 50, 11, + 34, 55, 10, 35, 59, 9, 36, 63, 9, 37, 67, 8, + 38, 71, 7, 39, 75, 7, 39, 79, 6, 40, 83, 5, + 41, 87, 5, 42, 92, 4, 43, 96, 3, 44, 100, 3, + 45, 104, 2, 46, 108, 1, 47, 112, 1, 48, 116, 0, + 21, -12, 19, 22, -8, 18, 23, -4, 18, 24, 0, 17, + 25, 4, 16, 26, 8, 16, 27, 12, 15, 28, 16, 14, + 29, 20, 14, 30, 24, 13, 31, 28, 12, 31, 32, 12, + 32, 37, 11, 33, 41, 10, 34, 45, 10, 35, 49, 9, + 36, 53, 8, 37, 57, 8, 38, 61, 7, 39, 65, 6, + 40, 70, 6, 41, 74, 5, 42, 78, 4, 43, 82, 4, + 44, 86, 3, 45, 90, 2, 46, 94, 2, 47, 98, 1, + 47, 102, 0, 48, 107, 0, 49, 111, 0, 50, 115, -1, + 24, -13, 17, 24, -9, 17, 25, -5, 16, 26, -1, 15, + 27, 2, 15, 28, 6, 14, 29, 10, 13, 30, 15, 13, + 31, 19, 12, 32, 23, 11, 33, 27, 11, 34, 31, 10, + 35, 35, 9, 36, 39, 9, 37, 43, 8, 38, 47, 7, + 39, 52, 7, 40, 56, 6, 40, 60, 5, 41, 64, 5, + 42, 68, 4, 43, 72, 3, 44, 76, 3, 45, 80, 2, + 46, 84, 1, 47, 89, 1, 48, 93, 0, 49, 97, 0, + 50, 101, 0, 51, 105, -1, 52, 109, -2, 53, 113, -2, + 26, -14, 16, 27, -10, 15, 28, -6, 14, 29, -2, 14, + 30, 1, 13, 31, 5, 12, 31, 9, 12, 33, 14, 11, + 33, 18, 10, 34, 22, 10, 35, 26, 9, 36, 30, 8, + 37, 34, 8, 38, 38, 7, 39, 42, 6, 40, 46, 6, + 41, 51, 5, 42, 55, 4, 43, 59, 4, 44, 63, 3, + 45, 67, 2, 46, 71, 2, 47, 75, 1, 47, 79, 0, + 48, 83, 0, 49, 88, 0, 50, 92, -1, 51, 96, -1, + 52, 100, -2, 53, 104, -3, 54, 108, -3, 55, 112, -4, + 28, -16, 14, 29, -12, 13, 30, -8, 13, 31, -3, 12, + 32, 0, 11, 33, 4, 11, 34, 8, 10, 35, 12, 9, + 36, 16, 9, 37, 20, 8, 38, 24, 7, 38, 28, 7, + 40, 33, 6, 40, 37, 5, 41, 41, 5, 42, 45, 4, + 43, 49, 3, 44, 53, 3, 45, 57, 2, 46, 61, 1, + 47, 66, 1, 48, 70, 0, 49, 74, 0, 50, 78, 0, + 51, 82, -1, 52, 86, -2, 53, 90, -2, 54, 94, -3, + 54, 98, -4, 55, 103, -4, 56, 107, -5, 57, 111, -6, + 31, -17, 12, 31, -13, 12, 32, -9, 11, 33, -5, 10, + 34, -1, 10, 35, 2, 9, 36, 6, 8, 37, 11, 8, + 38, 15, 7, 39, 19, 6, 40, 23, 6, 41, 27, 5, + 42, 31, 4, 43, 35, 4, 44, 39, 3, 45, 43, 2, + 46, 48, 2, 47, 52, 1, 47, 56, 0, 48, 60, 0, + 49, 64, 0, 50, 68, -1, 51, 72, -1, 52, 76, -2, + 53, 80, -3, 54, 85, -3, 55, 89, -4, 56, 93, -5, + 57, 97, -5, 58, 101, -6, 59, 105, -7, 60, 109, -7, + 33, -18, 11, 34, -14, 10, 35, -10, 9, 36, -6, 9, + 37, -2, 8, 38, 1, 7, 39, 5, 7, 40, 10, 6, + 40, 14, 5, 41, 18, 5, 42, 22, 4, 43, 26, 3, + 44, 30, 3, 45, 34, 2, 46, 38, 1, 47, 42, 1, + 48, 47, 0, 49, 51, 0, 50, 55, 0, 51, 59, -1, + 52, 63, -2, 53, 67, -2, 54, 71, -3, 54, 75, -4, + 55, 79, -4, 56, 84, -5, 57, 88, -6, 58, 92, -6, + 59, 96, -7, 60, 100, -8, 61, 104, -8, 62, 108, -9, + 35, -20, 9, 36, -16, 8, 37, -12, 8, 38, -7, 7, + 39, -3, 6, 40, 0, 6, 41, 4, 5, 42, 8, 4, + 43, 12, 4, 44, 16, 3, 45, 20, 2, 46, 24, 2, + 47, 29, 1, 47, 33, 0, 48, 37, 0, 49, 41, 0, + 50, 45, -1, 51, 49, -1, 52, 53, -2, 53, 57, -3, + 54, 62, -3, 55, 66, -4, 56, 70, -5, 57, 74, -5, + 58, 78, -6, 59, 82, -7, 60, 86, -7, 61, 90, -8, + 62, 94, -9, 63, 99, -10, 63, 103, -10, 64, 107, -11, + 38, -21, 7, 39, -17, 7, 39, -13, 6, 40, -9, 5, + 41, -5, 5, 42, -1, 4, 43, 2, 3, 44, 7, 3, + 45, 11, 2, 46, 15, 1, 47, 19, 1, 48, 23, 0, + 49, 27, 0, 50, 31, 0, 51, 35, -1, 52, 39, -2, + 53, 44, -2, 54, 48, -3, 55, 52, -4, 55, 56, -4, + 56, 60, -5, 57, 64, -6, 58, 68, -6, 59, 72, -7, + 60, 76, -8, 61, 81, -8, 62, 85, -9, 63, 89, -10, + 64, 93, -10, 65, 97, -11, 66, 101, -12, 67, 105, -12, + 41, -23, 5, 41, -19, 5, 42, -15, 4, 43, -10, 3, + 44, -6, 2, 45, -2, 2, 46, 1, 1, 47, 5, 0, + 48, 9, 0, 49, 13, 0, 50, 17, -1, 51, 21, -1, + 52, 26, -2, 53, 30, -3, 54, 34, -3, 55, 38, -4, + 56, 42, -5, 57, 46, -5, 57, 50, -6, 58, 54, -7, + 59, 59, -7, 60, 63, -8, 61, 67, -9, 62, 71, -9, + 63, 75, -10, 64, 79, -11, 65, 83, -11, 66, 87, -12, + 67, 91, -13, 68, 96, -13, 69, 100, -14, 70, 104, -15, + 43, -24, 3, 44, -20, 3, 45, -16, 2, 46, -12, 1, + 47, -8, 1, 48, -4, 0, 49, 0, 0, 50, 4, 0, + 50, 8, -1, 51, 12, -2, 52, 16, -2, 53, 20, -3, + 54, 25, -4, 55, 29, -4, 56, 33, -5, 57, 37, -6, + 58, 41, -6, 59, 45, -7, 60, 49, -8, 61, 53, -8, + 62, 58, -9, 63, 62, -10, 64, 66, -10, 64, 70, -11, + 65, 74, -12, 66, 78, -12, 67, 82, -13, 68, 86, -14, + 69, 90, -14, 70, 95, -15, 71, 99, -16, 72, 103, -16, + 45, -25, 2, 46, -21, 1, 47, -17, 1, 48, -13, 0, + 49, -9, 0, 50, -5, -1, 51, -1, -1, 52, 3, -2, + 53, 7, -3, 54, 11, -3, 55, 15, -4, 56, 19, -5, + 57, 23, -5, 57, 27, -6, 58, 31, -7, 59, 35, -7, + 60, 40, -8, 61, 44, -9, 62, 48, -9, 63, 52, -10, + 64, 56, -11, 65, 60, -11, 66, 64, -12, 67, 68, -13, + 68, 72, -13, 69, 77, -14, 70, 81, -15, 71, 85, -15, + 71, 89, -16, 73, 93, -17, 73, 97, -17, 74, 101, -18, + 48, -27, 0, 49, -23, 0, 49, -19, 0, 50, -14, -1, + 51, -10, -2, 52, -6, -2, 53, -2, -3, 54, 1, -4, + 55, 5, -4, 56, 9, -5, 57, 13, -6, 58, 17, -6, + 59, 22, -7, 60, 26, -8, 61, 30, -8, 62, 34, -9, + 63, 38, -10, 64, 42, -10, 64, 46, -11, 65, 50, -12, + 66, 55, -12, 67, 59, -13, 68, 63, -14, 69, 67, -14, + 70, 71, -15, 71, 75, -16, 72, 79, -16, 73, 83, -17, + 74, 87, -18, 75, 92, -18, 76, 96, -19, 77, 100, -20, + 50, -28, -1, 51, -24, -1, 52, -20, -2, 53, -15, -3, + 54, -11, -3, 55, -7, -4, 56, -3, -5, 57, 0, -5, + 57, 4, -6, 58, 8, -7, 59, 12, -7, 60, 16, -8, + 61, 21, -9, 62, 25, -9, 63, 29, -10, 64, 33, -11, + 65, 37, -11, 66, 41, -12, 67, 45, -13, 68, 49, -13, + 69, 54, -14, 70, 58, -15, 71, 62, -15, 72, 66, -16, + 72, 70, -17, 73, 74, -17, 74, 78, -18, 75, 82, -19, + 76, 86, -19, 77, 91, -20, 78, 95, -21, 79, 99, -21, + 52, -29, -2, 53, -25, -3, 54, -21, -4, 55, -17, -4, + 56, -13, -5, 57, -9, -6, 58, -5, -6, 59, 0, -7, + 60, 3, -8, 61, 7, -8, 62, 11, -9, 63, 15, -10, + 64, 19, -10, 65, 23, -11, 65, 27, -12, 66, 31, -12, + 67, 36, -13, 68, 40, -14, 69, 44, -14, 70, 48, -15, + 71, 52, -16, 72, 56, -16, 73, 60, -17, 74, 64, -18, + 75, 68, -18, 76, 73, -19, 77, 77, -20, 78, 81, -20, + 79, 85, -21, 80, 89, -22, 80, 93, -22, 81, 97, -23, + 55, -31, -4, 56, -27, -5, 56, -23, -5, 58, -18, -6, + 58, -14, -7, 59, -10, -7, 60, -6, -8, 61, -2, -9, + 62, 1, -9, 63, 5, -10, 64, 9, -11, 65, 13, -11, + 66, 18, -12, 67, 22, -13, 68, 26, -13, 69, 30, -14, + 70, 34, -15, 71, 38, -15, 72, 42, -16, 72, 46, -17, + 73, 51, -17, 74, 55, -18, 75, 59, -19, 76, 63, -19, + 77, 67, -20, 78, 71, -21, 79, 75, -21, 80, 79, -22, + 81, 83, -23, 82, 88, -23, 83, 92, -24, 84, 96, -25, + 57, -32, -6, 58, -28, -6, 59, -24, -7, 60, -19, -8, + 61, -15, -8, 62, -11, -9, 63, -7, -10, 64, -3, -10, + 65, 0, -11, 65, 4, -12, 66, 8, -12, 67, 12, -13, + 68, 17, -14, 69, 21, -14, 70, 25, -15, 71, 29, -16, + 72, 33, -16, 73, 37, -17, 74, 41, -18, 75, 45, -18, + 76, 50, -19, 77, 54, -20, 78, 58, -20, 79, 62, -21, + 79, 66, -22, 80, 70, -22, 81, 74, -23, 82, 78, -24, + 83, 82, -24, 84, 87, -25, 85, 91, -26, 86, 95, -26, + 59, -33, -7, 60, -29, -8, 61, -25, -9, 62, -21, -9, + 63, -17, -10, 64, -13, -11, 65, -9, -11, 66, -4, -12, + 67, 0, -13, 68, 3, -13, 69, 7, -14, 70, 11, -15, + 71, 15, -15, 72, 19, -16, 72, 23, -17, 73, 27, -17, + 74, 32, -18, 75, 36, -19, 76, 40, -19, 77, 44, -20, + 78, 48, -21, 79, 52, -21, 80, 56, -22, 81, 60, -23, + 82, 64, -23, 83, 69, -24, 84, 73, -25, 85, 77, -25, + 86, 81, -26, 87, 85, -27, 88, 89, -27, 88, 93, -28, + 62, -35, -9, 63, -31, -10, 64, -27, -10, 65, -22, -11, + 65, -18, -12, 66, -14, -12, 67, -10, -13, 68, -6, -14, + 69, -2, -14, 70, 1, -15, 71, 5, -16, 72, 9, -16, + 73, 14, -17, 74, 18, -18, 75, 22, -18, 76, 26, -19, + 77, 30, -20, 78, 34, -20, 79, 38, -21, 79, 42, -22, + 81, 47, -22, 81, 51, -23, 82, 55, -24, 83, 59, -24, + 84, 63, -25, 85, 67, -26, 86, 71, -26, 87, 75, -27, + 88, 79, -28, 89, 84, -28, 90, 88, -29, 91, 92, -30, + 64, -36, -11, 65, -32, -11, 66, -28, -12, 67, -23, -13, + 68, -19, -13, 69, -15, -14, 70, -11, -15, 71, -7, -15, + 72, -3, -16, 72, 0, -17, 73, 4, -17, 74, 8, -18, + 75, 13, -19, 76, 17, -19, 77, 21, -20, 78, 25, -21, + 79, 29, -21, 80, 33, -22, 81, 37, -23, 82, 41, -23, + 83, 46, -24, 84, 50, -25, 85, 54, -25, 86, 58, -26, + 87, 62, -27, 88, 66, -27, 88, 70, -28, 89, 74, -29, + 90, 78, -29, 91, 83, -30, 92, 87, -31, 93, 91, -31, + 66, -37, -12, 67, -33, -13, 68, -29, -14, 69, -25, -14, + 70, -21, -15, 71, -17, -16, 72, -13, -16, 73, -8, -17, + 74, -4, -18, 75, 0, -18, 76, 3, -19, 77, 7, -20, + 78, 11, -20, 79, 15, -21, 80, 19, -22, 80, 23, -22, + 81, 28, -23, 82, 32, -24, 83, 36, -24, 84, 40, -25, + 85, 44, -26, 86, 48, -26, 87, 52, -27, 88, 56, -28, + 89, 60, -28, 90, 65, -29, 91, 69, -30, 92, 73, -30, + 93, 77, -31, 94, 81, -32, 95, 85, -32, 95, 89, -33, + 69, -39, -14, 70, -35, -15, 71, -31, -15, 72, -26, -16, + 73, -22, -17, 73, -18, -17, 74, -14, -18, 75, -10, -19, + 76, -6, -19, 77, -2, -20, 78, 1, -21, 79, 5, -21, + 80, 10, -22, 81, 14, -23, 82, 18, -23, 83, 22, -24, + 84, 26, -25, 85, 30, -25, 86, 34, -26, 87, 38, -27, + 88, 43, -27, 88, 47, -28, 89, 51, -29, 90, 55, -29, + 91, 59, -30, 92, 63, -31, 93, 67, -31, 94, 71, -32, + 95, 75, -33, 96, 80, -33, 97, 84, -34, 98, 88, -35, + 71, -40, -16, 72, -36, -16, 73, -32, -17, 74, -27, -18, + 75, -23, -18, 76, -19, -19, 77, -15, -20, 78, -11, -20, + 79, -7, -21, 80, -3, -22, 80, 0, -22, 81, 4, -23, + 82, 9, -24, 83, 13, -24, 84, 17, -25, 85, 21, -26, + 86, 25, -26, 87, 29, -27, 88, 33, -28, 89, 37, -28, + 90, 42, -29, 91, 46, -30, 92, 50, -30, 93, 54, -31, + 94, 58, -32, 95, 62, -32, 95, 66, -33, 96, 70, -34, + 97, 74, -34, 98, 79, -35, 99, 83, -36, 100, 87, -36, + 73, -41, -17, 74, -37, -18, 75, -33, -19, 76, -29, -19, + 77, -25, -20, 78, -21, -21, 79, -17, -21, 80, -12, -22, + 81, -8, -23, 82, -4, -23, 83, 0, -24, 84, 3, -25, + 85, 7, -25, 86, 11, -26, 87, 15, -27, 87, 19, -27, + 88, 24, -28, 89, 28, -29, 90, 32, -29, 91, 36, -30, + 92, 40, -31, 93, 44, -31, 94, 48, -32, 95, 52, -33, + 96, 56, -33, 97, 61, -34, 98, 65, -35, 99, 69, -35, + 100, 73, -36, 101, 77, -37, 102, 81, -37, 103, 85, -38, + 76, -43, -19, 77, -39, -20, 78, -35, -20, 79, -30, -21, + 80, -26, -22, 80, -22, -22, 81, -18, -23, 82, -14, -24, + 83, -10, -24, 84, -6, -25, 85, -2, -26, 86, 1, -26, + 87, 6, -27, 88, 10, -28, 89, 14, -28, 90, 18, -29, + 91, 22, -30, 92, 26, -30, 93, 30, -31, 94, 34, -32, + 95, 39, -32, 96, 43, -33, 96, 47, -34, 97, 51, -34, + 98, 55, -35, 99, 59, -36, 100, 63, -36, 101, 67, -37, + 102, 71, -38, 103, 76, -38, 104, 80, -39, 105, 84, -40, + 78, -44, -21, 79, -40, -21, 80, -36, -22, 81, -31, -23, + 82, -27, -23, 83, -23, -24, 84, -19, -25, 85, -15, -25, + 86, -11, -26, 87, -7, -27, 87, -3, -27, 88, 0, -28, + 89, 5, -29, 90, 9, -29, 91, 13, -30, 92, 17, -31, + 93, 21, -31, 94, 25, -32, 95, 29, -33, 96, 33, -33, + 97, 38, -34, 98, 42, -35, 99, 46, -35, 100, 50, -36, + 101, 54, -37, 102, 58, -37, 103, 62, -38, 103, 66, -39, + 104, 70, -39, 105, 75, -40, 106, 79, -41, 107, 83, -41, + 80, -45, -22, 81, -41, -23, 82, -37, -24, 83, -33, -24, + 84, -29, -25, 85, -25, -26, 86, -21, -26, 87, -16, -27, + 88, -12, -28, 89, -8, -28, 90, -4, -29, 91, 0, -30, + 92, 3, -30, 93, 7, -31, 94, 11, -32, 95, 15, -32, + 96, 20, -33, 96, 24, -34, 97, 28, -34, 98, 32, -35, + 99, 36, -36, 100, 40, -36, 101, 44, -37, 102, 48, -38, + 103, 52, -38, 104, 57, -39, 105, 61, -40, 106, 65, -40, + 107, 69, -41, 108, 73, -42, 109, 77, -42, 110, 81, -43, + 83, -47, -24, 84, -43, -25, 85, -39, -25, 86, -34, -26, + 87, -30, -27, 88, -26, -27, 88, -22, -28, 89, -18, -29, + 90, -14, -29, 91, -10, -30, 92, -6, -31, 93, -2, -31, + 94, 2, -32, 95, 6, -33, 96, 10, -33, 97, 14, -34, + 98, 18, -35, 99, 22, -35, 100, 26, -36, 101, 30, -37, + 102, 35, -37, 103, 39, -38, 103, 43, -39, 104, 47, -39, + 105, 51, -40, 106, 55, -41, 107, 59, -41, 108, 63, -42, + 109, 67, -43, 110, 72, -43, 111, 76, -44, 112, 80, -45, + 85, -48, -26, 86, -44, -26, 87, -40, -27, 88, -35, -28, + 89, -31, -28, 90, -27, -29, 91, -23, -30, 92, -19, -30, + 93, -15, -31, 94, -11, -32, 95, -7, -32, 95, -3, -33, + 96, 1, -34, 97, 5, -34, 98, 9, -35, 99, 13, -36, + 100, 17, -36, 101, 21, -37, 102, 25, -38, 103, 29, -38, + 104, 34, -39, 105, 38, -40, 106, 42, -40, 107, 46, -41, + 108, 50, -42, 109, 54, -42, 110, 58, -43, 110, 62, -44, + 111, 66, -44, 112, 71, -45, 113, 75, -46, 114, 79, -46, + 88, -49, -27, 88, -45, -28, 89, -41, -29, 90, -37, -29, + 91, -33, -30, 92, -29, -31, 93, -25, -31, 94, -20, -32, + 95, -16, -33, 96, -12, -33, 97, -8, -34, 98, -4, -35, + 99, 0, -35, 100, 3, -36, 101, 7, -37, 102, 11, -37, + 103, 16, -38, 103, 20, -39, 104, 24, -39, 105, 28, -40, + 106, 32, -41, 107, 36, -41, 108, 40, -42, 109, 44, -43, + 110, 48, -43, 111, 53, -44, 112, 57, -45, 113, 61, -45, + 114, 65, -46, 115, 69, -47, 116, 73, -47, 117, 77, -48, + 90, -51, -29, 91, -47, -30, 92, -43, -31, 93, -38, -31, + 94, -34, -32, 95, -30, -33, 96, -26, -33, 97, -22, -34, + 98, -18, -35, 99, -14, -35, 100, -10, -36, 101, -6, -37, + 102, -1, -37, 103, 2, -38, 104, 6, -39, 104, 10, -39, + 106, 14, -40, 106, 18, -41, 107, 22, -41, 108, 26, -42, + 109, 31, -43, 110, 35, -43, 111, 39, -44, 112, 43, -45, + 113, 47, -45, 114, 51, -46, 115, 55, -47, 116, 59, -47, + 117, 63, -48, 118, 68, -49, 119, 72, -50, 120, 76, -50, + 93, -52, -31, 94, -48, -32, 95, -44, -32, 96, -40, -33, + 97, -36, -34, 97, -32, -34, 98, -28, -35, 99, -23, -36, + 100, -19, -36, 101, -15, -37, 102, -11, -38, 103, -7, -38, + 104, -3, -39, 105, 0, -40, 106, 4, -40, 107, 8, -41, + 108, 13, -42, 109, 17, -42, 110, 21, -43, 111, 25, -44, + 112, 29, -45, 113, 33, -45, 113, 37, -46, 114, 41, -46, + 115, 45, -47, 116, 50, -48, 117, 54, -49, 118, 58, -49, + 119, 62, -50, 120, 66, -51, 121, 70, -51, 122, 74, -52, + 95, -53, -33, 96, -49, -33, 97, -45, -34, 98, -41, -35, + 99, -37, -35, 100, -33, -36, 101, -29, -37, 102, -24, -37, + 103, -20, -38, 104, -16, -39, 105, -12, -39, 105, -8, -40, + 106, -4, -41, 107, 0, -41, 108, 3, -42, 109, 7, -43, + 110, 12, -44, 111, 16, -44, 112, 20, -45, 113, 24, -45, + 114, 28, -46, 115, 32, -47, 116, 36, -47, 117, 40, -48, + 118, 44, -49, 119, 49, -50, 120, 53, -50, 120, 57, -51, + 121, 61, -51, 122, 65, -52, 123, 69, -53, 124, 73, -54, + 98, -55, -34, 98, -51, -35, 99, -47, -36, 100, -42, -36, + 101, -38, -37, 102, -34, -38, 103, -30, -38, 104, -26, -39, + 105, -22, -40, 106, -18, -40, 107, -14, -41, 108, -10, -42, + 109, -5, -43, 110, -1, -43, 111, 2, -44, 112, 6, -44, + 113, 10, -45, 113, 14, -46, 114, 18, -46, 115, 22, -47, + 116, 27, -48, 117, 31, -49, 118, 35, -49, 119, 39, -50, + 120, 43, -50, 121, 47, -51, 122, 51, -52, 123, 55, -53, + 124, 59, -53, 125, 64, -54, 126, 68, -55, 127, 72, -55, + 100, -56, -36, 101, -52, -37, 102, -48, -37, 103, -44, -38, + 104, -40, -39, 105, -36, -39, 105, -32, -40, 106, -27, -41, + 107, -23, -41, 108, -19, -42, 109, -15, -43, 110, -11, -43, + 111, -7, -44, 112, -3, -45, 113, 0, -45, 114, 4, -46, + 115, 9, -47, 116, 13, -48, 117, 17, -48, 118, 21, -49, + 119, 25, -50, 120, 29, -50, 120, 33, -51, 121, 37, -51, + 122, 41, -52, 123, 46, -53, 124, 50, -54, 125, 54, -54, + 126, 58, -55, 127, 62, -56, 128, 66, -56, 129, 70, -57, + 102, -57, -38, 103, -53, -38, 104, -49, -39, 105, -45, -40, + 106, -41, -40, 107, -37, -41, 108, -33, -42, 109, -28, -43, + 110, -24, -43, 111, -20, -44, 112, -16, -44, 112, -12, -45, + 113, -8, -46, 114, -4, -47, 115, 0, -47, 116, 3, -48, + 117, 8, -49, 118, 12, -49, 119, 16, -50, 120, 20, -50, + 121, 24, -51, 122, 28, -52, 123, 32, -53, 124, 36, -53, + 125, 40, -54, 126, 45, -55, 127, 49, -55, 128, 53, -56, + 128, 57, -57, 129, 61, -57, 130, 65, -58, 131, 69, -59, + 105, -59, -39, 105, -55, -40, 106, -51, -41, 107, -46, -42, + 108, -42, -42, 109, -38, -43, 110, -34, -43, 111, -30, -44, + 112, -26, -45, 113, -22, -45, 114, -18, -46, 115, -14, -47, + 116, -9, -48, 117, -5, -48, 118, -1, -49, 119, 2, -49, + 120, 6, -50, 121, 10, -51, 121, 14, -52, 122, 18, -52, + 123, 23, -53, 124, 27, -54, 125, 31, -54, 126, 35, -55, + 127, 39, -55, 128, 43, -56, 129, 47, -57, 130, 51, -58, + 131, 55, -58, 132, 60, -59, 133, 64, -60, 134, 68, -60, + 107, -60, -41, 108, -56, -42, 109, -52, -42, 110, -48, -43, + 111, -44, -44, 112, -40, -44, 112, -36, -45, 114, -31, -46, + 114, -27, -47, 115, -23, -47, 116, -19, -48, 117, -15, -48, + 118, -11, -49, 119, -7, -50, 120, -3, -51, 121, 0, -51, + 122, 5, -52, 123, 9, -53, 124, 13, -53, 125, 17, -54, + 126, 21, -55, 127, 25, -55, 128, 29, -56, 128, 33, -57, + 129, 37, -57, 130, 42, -58, 131, 46, -59, 132, 50, -59, + 133, 54, -60, 134, 58, -61, 135, 62, -61, 136, 66, -62, + 109, -61, -43, 110, -57, -43, 111, -53, -44, 112, -49, -45, + 113, -45, -46, 114, -41, -46, 115, -37, -47, 116, -32, -48, + 117, -28, -48, 118, -24, -49, 119, -20, -49, 120, -16, -50, + 121, -12, -51, 121, -8, -52, 122, -4, -52, 123, 0, -53, + 124, 4, -54, 125, 8, -54, 126, 12, -55, 127, 16, -56, + 128, 20, -56, 129, 24, -57, 130, 28, -58, 131, 32, -58, + 132, 36, -59, 133, 41, -60, 134, 45, -60, 135, 49, -61, + 135, 53, -62, 136, 57, -62, 137, 61, -63, 138, 65, -64, + 112, -63, -45, 113, -59, -45, 113, -55, -46, 114, -50, -47, + 115, -46, -47, 116, -42, -48, 117, -38, -48, 118, -34, -49, + 119, -30, -50, 120, -26, -51, 121, -22, -51, 122, -18, -52, + 123, -13, -53, 124, -9, -53, 125, -5, -54, 126, -1, -55, + 127, 2, -55, 128, 6, -56, 128, 10, -57, 129, 14, -57, + 130, 19, -58, 131, 23, -59, 132, 27, -59, 133, 31, -60, + 134, 35, -61, 135, 39, -61, 136, 43, -62, 137, 47, -63, + 138, 51, -63, 139, 56, -64, 140, 60, -65, 141, 64, -65, + 114, -64, -46, 115, -60, -47, 116, -56, -47, 117, -52, -48, + 118, -48, -49, 119, -44, -50, 120, -40, -50, 121, -35, -51, + 121, -31, -52, 122, -27, -52, 123, -23, -53, 124, -19, -53, + 125, -15, -54, 126, -11, -55, 127, -7, -56, 128, -3, -56, + 129, 1, -57, 130, 5, -58, 131, 9, -58, 132, 13, -59, + 133, 17, -60, 134, 21, -60, 135, 25, -61, 135, 29, -62, + 136, 33, -62, 137, 38, -63, 138, 42, -64, 139, 46, -64, + 140, 50, -65, 141, 54, -66, 142, 58, -66, 143, 62, -67, + 116, -65, -48, 117, -61, -49, 118, -57, -49, 119, -53, -50, + 120, -49, -51, 121, -45, -51, 122, -41, -52, 123, -36, -53, + 124, -32, -53, 125, -28, -54, 126, -24, -55, 127, -20, -55, + 128, -16, -56, 128, -12, -57, 129, -8, -57, 130, -4, -58, + 131, 0, -59, 132, 4, -59, 133, 8, -60, 134, 12, -61, + 135, 16, -61, 136, 20, -62, 137, 24, -63, 138, 28, -63, + 139, 32, -64, 140, 37, -65, 141, 41, -65, 142, 45, -66, + 143, 49, -67, 144, 53, -67, 144, 57, -68, 145, 61, -69, + 119, -67, -50, 120, -63, -50, 120, -59, -51, 121, -54, -52, + 122, -50, -52, 123, -46, -53, 124, -42, -54, 125, -38, -54, + 126, -34, -55, 127, -30, -56, 128, -26, -56, 129, -22, -57, + 130, -17, -58, 131, -13, -58, 132, -9, -59, 133, -5, -60, + 134, -1, -60, 135, 2, -61, 136, 6, -62, 136, 10, -62, + 137, 15, -63, 138, 19, -64, 139, 23, -64, 140, 27, -65, + 141, 31, -66, 142, 35, -66, 143, 39, -67, 144, 43, -68, + 145, 47, -68, 146, 52, -69, 147, 56, -70, 148, 60, -70, + 121, -68, -51, 122, -64, -52, 123, -60, -53, 124, -56, -53, + 125, -52, -54, 126, -48, -55, 127, -44, -55, 128, -39, -56, + 129, -35, -57, 129, -31, -57, 130, -27, -58, 131, -23, -59, + 132, -19, -59, 133, -15, -60, 134, -11, -61, 135, -7, -61, + 136, -2, -62, 137, 1, -63, 138, 5, -63, 139, 9, -64, + 140, 13, -65, 141, 17, -65, 142, 21, -66, 143, 25, -67, + 143, 29, -67, 144, 34, -68, 145, 38, -69, 146, 42, -69, + 147, 46, -70, 148, 50, -71, 149, 54, -71, 150, 58, -72, + 123, -69, -53, 124, -65, -54, 125, -61, -54, 126, -57, -55, + 127, -53, -56, 128, -49, -56, 129, -45, -57, 130, -40, -58, + 131, -36, -58, 132, -32, -59, 133, -28, -60, 134, -24, -60, + 135, -20, -61, 136, -16, -62, 136, -12, -62, 137, -8, -63, + 138, -3, -64, 139, 0, -64, 140, 4, -65, 141, 8, -66, + 142, 12, -66, 143, 16, -67, 144, 20, -68, 145, 24, -68, + 146, 28, -69, 147, 33, -70, 148, 37, -70, 149, 41, -71, + 150, 45, -72, 151, 49, -72, 151, 53, -73, 152, 57, -74, + 126, -71, -55, 127, -67, -55, 128, -63, -56, 129, -58, -57, + 129, -54, -57, 130, -50, -58, 131, -46, -59, 132, -42, -59, + 133, -38, -60, 134, -34, -61, 135, -30, -61, 136, -26, -62, + 137, -21, -63, 138, -17, -63, 139, -13, -64, 140, -9, -65, + 141, -5, -65, 142, -1, -66, 143, 2, -67, 143, 6, -67, + 144, 11, -68, 145, 15, -69, 146, 19, -69, 147, 23, -70, + 148, 27, -71, 149, 31, -71, 150, 35, -72, 151, 39, -73, + 152, 43, -73, 153, 48, -74, 154, 52, -75, 155, 56, -75, + 128, -72, -56, 129, -68, -57, 130, -64, -58, 131, -60, -58, + 132, -56, -59, 133, -52, -60, 134, -48, -60, 135, -43, -61, + 136, -39, -62, 136, -35, -62, 137, -31, -63, 138, -27, -64, + 139, -23, -64, 140, -19, -65, 141, -15, -66, 142, -11, -66, + 143, -6, -67, 144, -2, -68, 145, 1, -68, 146, 5, -69, + 147, 9, -70, 148, 13, -70, 149, 17, -71, 150, 21, -72, + 150, 25, -72, 152, 30, -73, 152, 34, -74, 153, 38, -74, + 154, 42, -75, 155, 46, -76, 156, 50, -76, 157, 54, -77, + 130, -73, -58, 131, -69, -59, 132, -65, -59, 133, -61, -60, + 134, -57, -61, 135, -53, -61, 136, -49, -62, 137, -44, -63, + 138, -40, -63, 139, -36, -64, 140, -32, -65, 141, -28, -65, + 142, -24, -66, 143, -20, -67, 143, -16, -67, 144, -12, -68, + 145, -7, -69, 146, -3, -69, 147, 0, -70, 148, 4, -71, + 149, 8, -71, 150, 12, -72, 151, 16, -73, 152, 20, -73, + 153, 24, -74, 154, 29, -75, 155, 33, -75, 156, 37, -76, + 157, 41, -77, 158, 45, -77, 159, 49, -78, 159, 53, -79, + 133, -75, -60, 134, -71, -60, 135, -67, -61, 136, -62, -62, + 136, -58, -62, 137, -54, -63, 138, -50, -64, 139, -46, -64, + 140, -42, -65, 141, -38, -66, 142, -34, -66, 143, -30, -67, + 144, -25, -68, 145, -21, -68, 146, -17, -69, 147, -13, -70, + 148, -9, -70, 149, -5, -71, 150, -1, -72, 151, 2, -72, + 152, 7, -73, 152, 11, -74, 153, 15, -74, 154, 19, -75, + 155, 23, -76, 156, 27, -76, 157, 31, -77, 158, 35, -78, + 159, 39, -78, 160, 44, -79, 161, 48, -80, 162, 52, -80, + 135, -76, -61, 136, -72, -62, 137, -68, -63, 138, -64, -63, + 139, -60, -64, 140, -56, -65, 141, -52, -65, 142, -47, -66, + 143, -43, -67, 144, -39, -67, 144, -35, -68, 145, -31, -69, + 146, -27, -69, 147, -23, -70, 148, -19, -71, 149, -15, -71, + 150, -10, -72, 151, -6, -73, 152, -2, -73, 153, 1, -74, + 154, 5, -75, 155, 9, -75, 156, 13, -76, 157, 17, -77, + 158, 21, -77, 159, 26, -78, 159, 30, -79, 160, 34, -79, + 161, 38, -80, 162, 42, -81, 163, 46, -81, 164, 50, -82, + 137, -77, -63, 138, -73, -64, 139, -69, -64, 140, -65, -65, + 141, -61, -66, 142, -57, -66, 143, -53, -67, 144, -48, -68, + 145, -44, -68, 146, -40, -69, 147, -36, -70, 148, -32, -70, + 149, -28, -71, 150, -24, -72, 151, -20, -72, 151, -16, -73, + 152, -11, -74, 153, -7, -74, 154, -3, -75, 155, 0, -76, + 156, 4, -76, 157, 8, -77, 158, 12, -78, 159, 16, -78, + 160, 20, -79, 161, 25, -80, 162, 29, -80, 163, 33, -81, + 164, 37, -82, 165, 41, -82, 166, 45, -83, 166, 49, -84, + 140, -79, -65, 141, -75, -66, 142, -71, -66, 143, -66, -67, + 144, -62, -68, 145, -58, -68, 146, -54, -69, 147, -50, -70, + 148, -46, -70, 149, -42, -71, 150, -38, -72, 151, -34, -72, + 152, -29, -73, 153, -25, -74, 153, -21, -74, 154, -17, -75, + 155, -13, -76, 156, -9, -76, 157, -5, -77, 158, -1, -78, + 159, 3, -78, 160, 7, -79, 161, 11, -80, 162, 15, -80, + 163, 19, -81, 164, 23, -82, 165, 27, -82, 166, 31, -83, + 167, 35, -84, 168, 40, -84, 169, 44, -85, 169, 48, -86, + 143, -80, -67, 144, -76, -67, 145, -72, -68, 146, -68, -69, + 146, -64, -69, 147, -60, -70, 148, -56, -71, 149, -51, -71, + 150, -47, -72, 151, -43, -73, 152, -39, -73, 153, -35, -74, + 154, -31, -75, 155, -27, -75, 156, -23, -76, 157, -19, -77, + 158, -14, -77, 159, -10, -78, 160, -6, -79, 160, -2, -79, + 162, 1, -80, 162, 5, -81, 163, 9, -81, 164, 13, -82, + 165, 17, -83, 166, 22, -83, 167, 26, -84, 168, 30, -85, + 169, 34, -85, 170, 38, -86, 171, 42, -87, 172, 46, -87, + 145, -82, -68, 146, -78, -69, 147, -74, -70, 148, -69, -70, + 149, -65, -71, 150, -61, -72, 151, -57, -72, 152, -53, -73, + 153, -49, -74, 153, -45, -74, 154, -41, -75, 155, -37, -76, + 156, -32, -76, 157, -28, -77, 158, -24, -78, 159, -20, -78, + 160, -16, -79, 161, -12, -80, 162, -8, -80, 163, -4, -81, + 164, 0, -82, 165, 4, -82, 166, 8, -83, 167, 12, -84, + 168, 16, -84, 169, 20, -85, 169, 24, -86, 170, 28, -86, + 171, 32, -87, 172, 37, -88, 173, 41, -88, 174, 45, -89, + 147, -83, -70, 148, -79, -71, 149, -75, -71, 150, -70, -72, + 151, -66, -73, 152, -62, -73, 153, -58, -74, 154, -54, -75, + 155, -50, -75, 156, -46, -76, 157, -42, -77, 158, -38, -77, + 159, -33, -78, 160, -29, -79, 161, -25, -79, 161, -21, -80, + 162, -17, -81, 163, -13, -81, 164, -9, -82, 165, -5, -83, + 166, 0, -83, 167, 3, -84, 168, 7, -85, 169, 11, -85, + 170, 15, -86, 171, 19, -87, 172, 23, -87, 173, 27, -88, + 174, 31, -89, 175, 36, -89, 176, 40, -90, 176, 44, -91, + 150, -84, -72, 151, -80, -72, 152, -76, -73, 153, -72, -74, + 154, -68, -74, 154, -64, -75, 155, -60, -76, 156, -55, -76, + 157, -51, -77, 158, -47, -78, 159, -43, -78, 160, -39, -79, + 161, -35, -80, 162, -31, -80, 163, -27, -81, 164, -23, -82, + 165, -18, -82, 166, -14, -83, 167, -10, -84, 168, -6, -84, + 169, -2, -85, 169, 1, -86, 170, 5, -86, 171, 9, -87, + 172, 13, -88, 173, 18, -88, 174, 22, -89, 175, 26, -90, + 176, 30, -90, 177, 34, -91, 178, 38, -92, 179, 42, -92, + 152, -86, -73, 153, -82, -74, 154, -78, -75, 155, -73, -75, + 156, -69, -76, 157, -65, -77, 158, -61, -77, 159, -57, -78, + 160, -53, -79, 161, -49, -79, 161, -45, -80, 162, -41, -81, + 163, -36, -81, 164, -32, -82, 165, -28, -83, 166, -24, -83, + 167, -20, -84, 168, -16, -85, 169, -12, -85, 170, -8, -86, + 171, -3, -87, 172, 0, -87, 173, 4, -88, 174, 8, -89, + 175, 12, -89, 176, 16, -90, 176, 20, -91, 177, 24, -91, + 178, 28, -92, 179, 33, -93, 180, 37, -93, 181, 41, -94, + 154, -87, -75, 155, -83, -76, 156, -79, -76, 157, -74, -77, + 158, -70, -78, 159, -66, -78, 160, -62, -79, 161, -58, -80, + 162, -54, -80, 163, -50, -81, 164, -46, -82, 165, -42, -82, + 166, -37, -83, 167, -33, -84, 168, -29, -84, 168, -25, -85, + 169, -21, -86, 170, -17, -86, 171, -13, -87, 172, -9, -88, + 173, -4, -88, 174, 0, -89, 175, 3, -90, 176, 7, -90, + 177, 11, -91, 178, 15, -92, 179, 19, -92, 180, 23, -93, + 181, 27, -94, 182, 32, -95, 183, 36, -95, 184, 40, -96, + 157, -88, -77, 158, -84, -77, 159, -80, -78, 160, -76, -79, + 161, -72, -79, 161, -68, -80, 162, -64, -81, 163, -59, -81, + 164, -55, -82, 165, -51, -83, 166, -47, -83, 167, -43, -84, + 168, -39, -85, 169, -35, -85, 170, -31, -86, 171, -27, -87, + 172, -22, -87, 173, -18, -88, 174, -14, -89, 175, -10, -89, + 176, -6, -90, 177, -2, -91, 177, 1, -91, 178, 5, -92, + 179, 9, -93, 180, 14, -93, 181, 18, -94, 182, 22, -95, + 183, 26, -95, 184, 30, -96, 185, 34, -97, 186, 38, -97, + 159, -90, -78, 160, -86, -79, 161, -82, -80, 162, -77, -80, + 163, -73, -81, 164, -69, -82, 165, -65, -82, 166, -61, -83, + 167, -57, -84, 168, -53, -84, 168, -49, -85, 169, -45, -86, + 170, -40, -86, 171, -36, -87, 172, -32, -88, 173, -28, -88, + 174, -24, -89, 175, -20, -90, 176, -16, -90, 177, -12, -91, + 178, -7, -92, 179, -3, -92, 180, 0, -93, 181, 4, -94, + 182, 8, -94, 183, 12, -95, 184, 16, -96, 184, 20, -96, + 185, 24, -97, 186, 29, -98, 187, 33, -99, 188, 37, -99, + 161, -91, -80, 162, -87, -81, 163, -83, -81, 164, -78, -82, + 165, -74, -83, 166, -70, -83, 167, -66, -84, 168, -62, -85, + 169, -58, -85, 170, -54, -86, 171, -50, -87, 172, -46, -87, + 173, -41, -88, 174, -37, -89, 175, -33, -89, 176, -29, -90, + 177, -25, -91, 177, -21, -91, 178, -17, -92, 179, -13, -93, + 180, -8, -94, 181, -4, -94, 182, 0, -95, 183, 3, -95, + 184, 7, -96, 185, 11, -97, 186, 15, -97, 187, 19, -98, + 188, 23, -99, 189, 28, -100, 190, 32, -100, 191, 36, -101, + 164, -92, -82, 165, -88, -82, 166, -84, -83, 167, -80, -84, + 168, -76, -84, 169, -72, -85, 169, -68, -86, 170, -63, -86, + 171, -59, -87, 172, -55, -88, 173, -51, -88, 174, -47, -89, + 175, -43, -90, 176, -39, -90, 177, -35, -91, 178, -31, -92, + 179, -26, -92, 180, -22, -93, 181, -18, -94, 182, -14, -94, + 183, -10, -95, 184, -6, -96, 184, -2, -96, 185, 1, -97, + 186, 5, -98, 187, 10, -99, 188, 14, -99, 189, 18, -100, + 190, 22, -100, 191, 26, -101, 192, 30, -102, 193, 34, -103, + 17, -9, 29, 18, -5, 28, 19, -1, 27, 20, 2, 26, + 21, 6, 26, 22, 10, 25, 22, 14, 25, 23, 19, 24, + 24, 23, 23, 25, 27, 22, 26, 31, 22, 27, 35, 21, + 28, 39, 20, 29, 43, 20, 30, 47, 19, 31, 51, 18, + 32, 56, 18, 33, 60, 17, 34, 64, 16, 35, 68, 16, + 36, 72, 15, 37, 76, 14, 37, 80, 14, 38, 84, 13, + 39, 88, 12, 40, 93, 12, 41, 97, 11, 42, 101, 10, + 43, 105, 10, 44, 109, 9, 45, 113, 8, 46, 117, 8, + 19, -11, 27, 20, -7, 26, 21, -3, 26, 22, 1, 25, + 23, 5, 24, 24, 9, 23, 25, 13, 23, 26, 17, 22, + 27, 21, 21, 28, 25, 21, 29, 29, 20, 29, 33, 20, + 30, 38, 19, 31, 42, 18, 32, 46, 17, 33, 50, 17, + 34, 54, 16, 35, 58, 15, 36, 62, 15, 37, 66, 14, + 38, 71, 13, 39, 75, 13, 40, 79, 12, 41, 83, 11, + 42, 87, 11, 43, 91, 10, 44, 95, 9, 44, 99, 9, + 45, 103, 8, 46, 108, 7, 47, 112, 7, 48, 116, 6, + 22, -12, 25, 22, -8, 25, 23, -4, 24, 24, 0, 23, + 25, 4, 22, 26, 8, 22, 27, 12, 21, 28, 16, 20, + 29, 20, 20, 30, 24, 19, 31, 28, 18, 32, 32, 18, + 33, 37, 17, 34, 41, 16, 35, 45, 16, 36, 49, 15, + 37, 53, 14, 37, 57, 14, 38, 61, 13, 39, 65, 12, + 40, 70, 12, 41, 74, 11, 42, 78, 10, 43, 82, 10, + 44, 86, 9, 45, 90, 8, 46, 94, 8, 47, 98, 7, + 48, 102, 6, 49, 107, 6, 50, 111, 5, 51, 115, 4, + 24, -13, 23, 25, -9, 23, 26, -5, 22, 27, -1, 21, + 28, 2, 21, 29, 6, 20, 29, 10, 19, 30, 15, 19, + 31, 19, 18, 32, 23, 17, 33, 27, 17, 34, 31, 16, + 35, 35, 15, 36, 39, 15, 37, 43, 14, 38, 47, 13, + 39, 52, 13, 40, 56, 12, 41, 60, 11, 42, 64, 11, + 43, 68, 10, 44, 72, 9, 45, 76, 9, 45, 80, 8, + 46, 84, 7, 47, 89, 7, 48, 93, 6, 49, 97, 5, + 50, 101, 5, 51, 105, 4, 52, 109, 3, 53, 113, 3, + 26, -15, 22, 27, -11, 21, 28, -7, 21, 29, -2, 20, + 30, 1, 19, 31, 5, 18, 32, 9, 18, 33, 13, 17, + 34, 17, 16, 35, 21, 16, 36, 25, 15, 36, 29, 14, + 38, 34, 14, 38, 38, 13, 39, 42, 12, 40, 46, 12, + 41, 50, 11, 42, 54, 10, 43, 58, 10, 44, 62, 9, + 45, 67, 8, 46, 71, 8, 47, 75, 7, 48, 79, 6, + 49, 83, 6, 50, 87, 5, 51, 91, 4, 52, 95, 4, + 52, 99, 3, 53, 104, 2, 54, 108, 2, 55, 112, 1, + 29, -16, 20, 29, -12, 19, 30, -8, 19, 31, -3, 18, + 32, 0, 17, 33, 4, 17, 34, 8, 16, 35, 12, 15, + 36, 16, 15, 37, 20, 14, 38, 24, 13, 39, 28, 13, + 40, 33, 12, 41, 37, 11, 42, 41, 11, 43, 45, 10, + 44, 49, 9, 45, 53, 9, 45, 57, 8, 46, 61, 7, + 47, 66, 7, 48, 70, 6, 49, 74, 5, 50, 78, 5, + 51, 82, 4, 52, 86, 3, 53, 90, 3, 54, 94, 2, + 55, 98, 1, 56, 103, 1, 57, 107, 0, 58, 111, 0, + 31, -17, 18, 32, -13, 18, 33, -9, 17, 34, -5, 16, + 35, -1, 16, 36, 2, 15, 37, 6, 14, 38, 11, 14, + 38, 15, 13, 39, 19, 12, 40, 23, 12, 41, 27, 11, + 42, 31, 10, 43, 35, 10, 44, 39, 9, 45, 43, 8, + 46, 48, 8, 47, 52, 7, 48, 56, 6, 49, 60, 6, + 50, 64, 5, 51, 68, 4, 52, 72, 4, 52, 76, 3, + 53, 80, 2, 54, 85, 2, 55, 89, 1, 56, 93, 0, + 57, 97, 0, 58, 101, 0, 59, 105, -1, 60, 109, -1, + 33, -19, 17, 34, -15, 16, 35, -11, 15, 36, -6, 15, + 37, -2, 14, 38, 1, 13, 39, 5, 13, 40, 9, 12, + 41, 13, 11, 42, 17, 11, 43, 21, 10, 44, 25, 9, + 45, 30, 9, 45, 34, 8, 46, 38, 7, 47, 42, 7, + 48, 46, 6, 49, 50, 5, 50, 54, 5, 51, 58, 4, + 52, 63, 3, 53, 67, 3, 54, 71, 2, 55, 75, 1, + 56, 79, 1, 57, 83, 0, 58, 87, 0, 59, 91, 0, + 59, 95, -1, 61, 100, -2, 61, 104, -2, 62, 108, -3, + 36, -20, 15, 37, -16, 14, 37, -12, 14, 38, -7, 13, + 39, -3, 12, 40, 0, 12, 41, 4, 11, 42, 8, 10, + 43, 12, 10, 44, 16, 9, 45, 20, 8, 46, 24, 8, + 47, 29, 7, 48, 33, 6, 49, 37, 6, 50, 41, 5, + 51, 45, 4, 52, 49, 4, 52, 53, 3, 53, 57, 2, + 54, 62, 2, 55, 66, 1, 56, 70, 0, 57, 74, 0, + 58, 78, 0, 59, 82, -1, 60, 86, -1, 61, 90, -2, + 62, 94, -3, 63, 99, -3, 64, 103, -4, 65, 107, -5, + 38, -21, 13, 39, -17, 13, 40, -13, 12, 41, -9, 11, + 42, -5, 11, 43, -1, 10, 44, 2, 9, 45, 7, 9, + 45, 11, 8, 46, 15, 7, 47, 19, 7, 48, 23, 6, + 49, 27, 5, 50, 31, 5, 51, 35, 4, 52, 39, 3, + 53, 44, 3, 54, 48, 2, 55, 52, 1, 56, 56, 1, + 57, 60, 0, 58, 64, 0, 59, 68, 0, 60, 72, -1, + 60, 76, -2, 61, 81, -2, 62, 85, -3, 63, 89, -4, + 64, 93, -4, 65, 97, -5, 66, 101, -6, 67, 105, -6, + 40, -23, 12, 41, -19, 11, 42, -15, 10, 43, -10, 10, + 44, -6, 9, 45, -2, 8, 46, 1, 8, 47, 5, 7, + 48, 9, 6, 49, 13, 6, 50, 17, 5, 51, 21, 4, + 52, 26, 4, 53, 30, 3, 53, 34, 2, 54, 38, 2, + 55, 42, 1, 56, 46, 0, 57, 50, 0, 58, 54, 0, + 59, 59, -1, 60, 63, -1, 61, 67, -2, 62, 71, -3, + 63, 75, -3, 64, 79, -4, 65, 83, -5, 66, 87, -5, + 67, 91, -6, 68, 96, -7, 68, 100, -7, 69, 104, -8, + 43, -24, 10, 44, -20, 9, 45, -16, 8, 46, -12, 8, + 47, -8, 7, 48, -4, 6, 49, 0, 6, 50, 4, 5, + 51, 8, 4, 52, 12, 4, 53, 16, 3, 54, 20, 2, + 55, 24, 2, 55, 28, 1, 56, 32, 0, 57, 36, 0, + 58, 41, 0, 59, 45, -1, 60, 49, -1, 61, 53, -2, + 62, 57, -3, 63, 61, -3, 64, 65, -4, 65, 69, -5, + 66, 73, -5, 67, 78, -6, 68, 82, -7, 69, 86, -7, + 69, 90, -8, 71, 94, -9, 71, 98, -9, 72, 102, -10, + 46, -26, 8, 47, -22, 7, 47, -18, 7, 48, -13, 6, + 49, -9, 5, 50, -5, 5, 51, -1, 4, 52, 2, 3, + 53, 6, 3, 54, 10, 2, 55, 14, 1, 56, 18, 1, + 57, 23, 0, 58, 27, 0, 59, 31, 0, 60, 35, -1, + 61, 39, -2, 62, 43, -2, 62, 47, -3, 63, 51, -4, + 64, 56, -4, 65, 60, -5, 66, 64, -6, 67, 68, -6, + 68, 72, -7, 69, 76, -8, 70, 80, -8, 71, 84, -9, + 72, 88, -10, 73, 93, -10, 74, 97, -11, 75, 101, -12, + 48, -27, 6, 49, -23, 6, 50, -19, 5, 51, -14, 4, + 52, -10, 4, 53, -6, 3, 54, -2, 2, 55, 1, 2, + 55, 5, 1, 56, 9, 0, 57, 13, 0, 58, 17, 0, + 59, 22, -1, 60, 26, -1, 61, 30, -2, 62, 34, -3, + 63, 38, -3, 64, 42, -4, 65, 46, -5, 66, 50, -5, + 67, 55, -6, 68, 59, -7, 69, 63, -7, 69, 67, -8, + 70, 71, -9, 71, 75, -9, 72, 79, -10, 73, 83, -11, + 74, 87, -11, 75, 92, -12, 76, 96, -13, 77, 100, -13, + 50, -28, 5, 51, -24, 4, 52, -20, 3, 53, -16, 3, + 54, -12, 2, 55, -8, 1, 56, -4, 1, 57, 0, 0, + 58, 4, 0, 59, 8, 0, 60, 12, -1, 61, 16, -2, + 62, 20, -2, 62, 24, -3, 63, 28, -4, 64, 32, -4, + 65, 37, -5, 66, 41, -6, 67, 45, -6, 68, 49, -7, + 69, 53, -8, 70, 57, -8, 71, 61, -9, 72, 65, -10, + 73, 69, -10, 74, 74, -11, 75, 78, -12, 76, 82, -12, + 77, 86, -13, 78, 90, -14, 78, 94, -14, 79, 98, -15, + 53, -29, 3, 54, -25, 2, 54, -21, 2, 55, -17, 1, + 56, -13, 0, 57, -9, 0, 58, -5, 0, 59, 0, -1, + 60, 3, -1, 61, 7, -2, 62, 11, -3, 63, 15, -3, + 64, 19, -4, 65, 23, -5, 66, 27, -5, 67, 31, -6, + 68, 36, -7, 69, 40, -7, 70, 44, -8, 70, 48, -9, + 71, 52, -9, 72, 56, -10, 73, 60, -11, 74, 64, -11, + 75, 68, -12, 76, 73, -13, 77, 77, -13, 78, 81, -14, + 79, 85, -15, 80, 89, -15, 81, 93, -16, 82, 97, -17, + 55, -31, 1, 56, -27, 1, 57, -23, 0, 58, -18, 0, + 59, -14, 0, 60, -10, -1, 61, -6, -2, 62, -2, -2, + 63, 1, -3, 63, 5, -4, 64, 9, -4, 65, 13, -5, + 66, 18, -6, 67, 22, -6, 68, 26, -7, 69, 30, -8, + 70, 34, -8, 71, 38, -9, 72, 42, -10, 73, 46, -10, + 74, 51, -11, 75, 55, -12, 76, 59, -12, 77, 63, -13, + 77, 67, -14, 78, 71, -14, 79, 75, -15, 80, 79, -16, + 81, 83, -16, 82, 88, -17, 83, 92, -18, 84, 96, -18, + 57, -32, 0, 58, -28, 0, 59, -24, -1, 60, -20, -1, + 61, -16, -2, 62, -12, -3, 63, -8, -3, 64, -3, -4, + 65, 0, -5, 66, 4, -5, 67, 8, -6, 68, 12, -7, + 69, 16, -7, 70, 20, -8, 70, 24, -9, 71, 28, -9, + 72, 33, -10, 73, 37, -11, 74, 41, -11, 75, 45, -12, + 76, 49, -13, 77, 53, -13, 78, 57, -14, 79, 61, -15, + 80, 65, -15, 81, 70, -16, 82, 74, -17, 83, 78, -17, + 84, 82, -18, 85, 86, -19, 86, 90, -19, 86, 94, -20, + 60, -33, -1, 61, -29, -2, 62, -25, -2, 63, -21, -3, + 63, -17, -4, 64, -13, -4, 65, -9, -5, 66, -4, -6, + 67, 0, -6, 68, 3, -7, 69, 7, -8, 70, 11, -8, + 71, 15, -9, 72, 19, -10, 73, 23, -10, 74, 27, -11, + 75, 32, -12, 76, 36, -12, 77, 40, -13, 77, 44, -14, + 79, 48, -14, 79, 52, -15, 80, 56, -16, 81, 60, -16, + 82, 64, -17, 83, 69, -18, 84, 73, -18, 85, 77, -19, + 86, 81, -20, 87, 85, -20, 88, 89, -21, 89, 93, -22, + 62, -35, -3, 63, -31, -3, 64, -27, -4, 65, -22, -5, + 66, -18, -5, 67, -14, -6, 68, -10, -7, 69, -6, -7, + 70, -2, -8, 70, 1, -9, 71, 5, -9, 72, 9, -10, + 73, 14, -11, 74, 18, -11, 75, 22, -12, 76, 26, -13, + 77, 30, -13, 78, 34, -14, 79, 38, -15, 80, 42, -15, + 81, 47, -16, 82, 51, -17, 83, 55, -17, 84, 59, -18, + 84, 63, -19, 86, 67, -19, 86, 71, -20, 87, 75, -21, + 88, 79, -21, 89, 84, -22, 90, 88, -23, 91, 92, -23, + 64, -36, -4, 65, -32, -5, 66, -28, -6, 67, -24, -6, + 68, -20, -7, 69, -16, -8, 70, -12, -8, 71, -7, -9, + 72, -3, -10, 73, 0, -10, 74, 4, -11, 75, 8, -12, + 76, 12, -12, 77, 16, -13, 77, 20, -14, 78, 24, -14, + 79, 29, -15, 80, 33, -16, 81, 37, -16, 82, 41, -17, + 83, 45, -18, 84, 49, -18, 85, 53, -19, 86, 57, -20, + 87, 61, -20, 88, 66, -21, 89, 70, -22, 90, 74, -22, + 91, 78, -23, 92, 82, -24, 93, 86, -24, 93, 90, -25, + 67, -37, -6, 68, -33, -7, 69, -29, -7, 70, -25, -8, + 70, -21, -9, 71, -17, -9, 72, -13, -10, 73, -8, -11, + 74, -4, -11, 75, 0, -12, 76, 3, -13, 77, 7, -13, + 78, 11, -14, 79, 15, -15, 80, 19, -15, 81, 23, -16, + 82, 28, -17, 83, 32, -17, 84, 36, -18, 85, 40, -19, + 86, 44, -20, 86, 48, -20, 87, 52, -21, 88, 56, -21, + 89, 60, -22, 90, 65, -23, 91, 69, -23, 92, 73, -24, + 93, 77, -25, 94, 81, -26, 95, 85, -26, 96, 89, -27, + 69, -39, -8, 70, -35, -8, 71, -31, -9, 72, -26, -10, + 73, -22, -10, 74, -18, -11, 75, -14, -12, 76, -10, -12, + 77, -6, -13, 78, -2, -14, 78, 1, -14, 79, 5, -15, + 80, 10, -16, 81, 14, -16, 82, 18, -17, 83, 22, -18, + 84, 26, -18, 85, 30, -19, 86, 34, -20, 87, 38, -20, + 88, 43, -21, 89, 47, -22, 90, 51, -22, 91, 55, -23, + 92, 59, -24, 93, 63, -25, 93, 67, -25, 94, 71, -26, + 95, 75, -26, 96, 80, -27, 97, 84, -28, 98, 88, -28, + 71, -40, -9, 72, -36, -10, 73, -32, -11, 74, -28, -11, + 75, -24, -12, 76, -20, -13, 77, -16, -13, 78, -11, -14, + 79, -7, -15, 80, -3, -15, 81, 0, -16, 82, 4, -17, + 83, 8, -17, 84, 12, -18, 85, 16, -19, 85, 20, -19, + 86, 25, -20, 87, 29, -21, 88, 33, -21, 89, 37, -22, + 90, 41, -23, 91, 45, -24, 92, 49, -24, 93, 53, -25, + 94, 57, -25, 95, 62, -26, 96, 66, -27, 97, 70, -27, + 98, 74, -28, 99, 78, -29, 100, 82, -30, 101, 86, -30, + 74, -41, -11, 75, -37, -12, 76, -33, -12, 77, -29, -13, + 78, -25, -14, 78, -21, -14, 79, -17, -15, 80, -12, -16, + 81, -8, -16, 82, -4, -17, 83, 0, -18, 84, 3, -18, + 85, 7, -19, 86, 11, -20, 87, 15, -20, 88, 19, -21, + 89, 24, -22, 90, 28, -22, 91, 32, -23, 92, 36, -24, + 93, 40, -25, 94, 44, -25, 94, 48, -26, 95, 52, -26, + 96, 56, -27, 97, 61, -28, 98, 65, -29, 99, 69, -29, + 100, 73, -30, 101, 77, -31, 102, 81, -31, 103, 85, -32, + 76, -43, -13, 77, -39, -13, 78, -35, -14, 79, -30, -15, + 80, -26, -15, 81, -22, -16, 82, -18, -17, 83, -14, -18, + 84, -10, -18, 85, -6, -19, 85, -2, -19, 86, 1, -20, + 87, 6, -21, 88, 10, -21, 89, 14, -22, 90, 18, -23, + 91, 22, -24, 92, 26, -24, 93, 30, -25, 94, 34, -25, + 95, 39, -26, 96, 43, -27, 97, 47, -28, 98, 51, -28, + 99, 55, -29, 100, 59, -30, 101, 63, -30, 101, 67, -31, + 102, 71, -31, 103, 76, -32, 104, 80, -33, 105, 84, -34, + 78, -44, -14, 79, -40, -15, 80, -36, -16, 81, -32, -16, + 82, -28, -17, 83, -24, -18, 84, -20, -18, 85, -15, -19, + 86, -11, -20, 87, -7, -20, 88, -3, -21, 89, 0, -22, + 90, 4, -23, 91, 8, -23, 92, 12, -24, 92, 16, -24, + 94, 21, -25, 94, 25, -26, 95, 29, -26, 96, 33, -27, + 97, 37, -28, 98, 41, -29, 99, 45, -29, 100, 49, -30, + 101, 53, -30, 102, 58, -31, 103, 62, -32, 104, 66, -33, + 105, 70, -33, 106, 74, -34, 107, 78, -35, 108, 82, -35, + 81, -45, -16, 82, -41, -17, 83, -37, -17, 84, -33, -18, + 85, -29, -19, 85, -25, -19, 86, -21, -20, 87, -16, -21, + 88, -12, -22, 89, -8, -22, 90, -4, -23, 91, 0, -23, + 92, 3, -24, 93, 7, -25, 94, 11, -25, 95, 15, -26, + 96, 20, -27, 97, 24, -28, 98, 28, -28, 99, 32, -29, + 100, 36, -30, 101, 40, -30, 101, 44, -31, 102, 48, -32, + 103, 52, -32, 104, 57, -33, 105, 61, -34, 106, 65, -34, + 107, 69, -35, 108, 73, -36, 109, 77, -36, 110, 81, -37, + 83, -47, -18, 84, -43, -18, 85, -39, -19, 86, -34, -20, + 87, -30, -20, 88, -26, -21, 89, -22, -22, 90, -18, -23, + 91, -14, -23, 92, -10, -24, 93, -6, -24, 93, -2, -25, + 94, 2, -26, 95, 6, -27, 96, 10, -27, 97, 14, -28, + 98, 18, -29, 99, 22, -29, 100, 26, -30, 101, 30, -30, + 102, 35, -31, 103, 39, -32, 104, 43, -33, 105, 47, -33, + 106, 51, -34, 107, 55, -35, 108, 59, -35, 108, 63, -36, + 109, 67, -37, 110, 72, -37, 111, 76, -38, 112, 80, -39, + 86, -48, -19, 86, -44, -20, 87, -40, -21, 88, -36, -22, + 89, -32, -22, 90, -28, -23, 91, -24, -23, 92, -19, -24, + 93, -15, -25, 94, -11, -26, 95, -7, -26, 96, -3, -27, + 97, 0, -28, 98, 4, -28, 99, 8, -29, 100, 12, -29, + 101, 17, -30, 101, 21, -31, 102, 25, -32, 103, 29, -32, + 104, 33, -33, 105, 37, -34, 106, 41, -34, 107, 45, -35, + 108, 49, -36, 109, 54, -36, 110, 58, -37, 111, 62, -38, + 112, 66, -38, 113, 70, -39, 114, 74, -40, 115, 78, -40, + 88, -49, -21, 89, -45, -22, 90, -41, -22, 91, -37, -23, + 92, -33, -24, 93, -29, -24, 93, -25, -25, 94, -20, -26, + 95, -16, -27, 96, -12, -27, 97, -8, -28, 98, -4, -28, + 99, 0, -29, 100, 3, -30, 101, 7, -31, 102, 11, -31, + 103, 16, -32, 104, 20, -33, 105, 24, -33, 106, 28, -34, + 107, 32, -35, 108, 36, -35, 109, 40, -36, 109, 44, -37, + 110, 48, -37, 111, 53, -38, 112, 57, -39, 113, 61, -39, + 114, 65, -40, 115, 69, -41, 116, 73, -41, 117, 77, -42, + 90, -51, -23, 91, -47, -23, 92, -43, -24, 93, -38, -25, + 94, -34, -26, 95, -30, -26, 96, -26, -27, 97, -22, -28, + 98, -18, -28, 99, -14, -29, 100, -10, -30, 100, -6, -30, + 102, -1, -31, 102, 2, -32, 103, 6, -32, 104, 10, -33, + 105, 14, -34, 106, 18, -34, 107, 22, -35, 108, 26, -36, + 109, 31, -36, 110, 35, -37, 111, 39, -38, 112, 43, -38, + 113, 47, -39, 114, 51, -40, 115, 55, -40, 116, 59, -41, + 116, 63, -42, 117, 68, -42, 118, 72, -43, 119, 76, -44, + 93, -52, -25, 94, -48, -26, 95, -44, -26, 96, -40, -27, + 97, -36, -28, 98, -32, -28, 99, -28, -29, 100, -23, -30, + 101, -19, -30, 102, -15, -31, 103, -11, -32, 103, -7, -32, + 104, -3, -33, 105, 0, -34, 106, 4, -34, 107, 8, -35, + 108, 13, -36, 109, 17, -36, 110, 21, -37, 111, 25, -38, + 112, 29, -38, 113, 33, -39, 114, 37, -40, 115, 41, -40, + 116, 45, -41, 117, 50, -42, 118, 54, -42, 118, 58, -43, + 119, 62, -44, 120, 66, -44, 121, 70, -45, 122, 74, -46, + 96, -54, -27, 96, -50, -27, 97, -46, -28, 98, -41, -29, + 99, -37, -29, 100, -33, -30, 101, -29, -31, 102, -25, -31, + 103, -21, -32, 104, -17, -33, 105, -13, -33, 106, -9, -34, + 107, -4, -35, 108, 0, -35, 109, 3, -36, 110, 7, -37, + 111, 11, -37, 111, 15, -38, 112, 19, -39, 113, 23, -39, + 114, 28, -40, 115, 32, -41, 116, 36, -41, 117, 40, -42, + 118, 44, -43, 119, 48, -43, 120, 52, -44, 121, 56, -45, + 122, 60, -45, 123, 65, -46, 124, 69, -47, 125, 73, -47, + 98, -55, -28, 99, -51, -29, 100, -47, -30, 101, -43, -30, + 102, -39, -31, 103, -35, -32, 103, -31, -32, 104, -26, -33, + 105, -22, -34, 106, -18, -34, 107, -14, -35, 108, -10, -36, + 109, -6, -36, 110, -2, -37, 111, 1, -38, 112, 5, -38, + 113, 10, -39, 114, 14, -40, 115, 18, -40, 116, 22, -41, + 117, 26, -42, 118, 30, -42, 118, 34, -43, 119, 38, -44, + 120, 42, -44, 121, 47, -45, 122, 51, -46, 123, 55, -46, + 124, 59, -47, 125, 63, -48, 126, 67, -48, 127, 71, -49, + 100, -56, -30, 101, -52, -31, 102, -48, -31, 103, -44, -32, + 104, -40, -33, 105, -36, -33, 106, -32, -34, 107, -27, -35, + 108, -23, -35, 109, -19, -36, 110, -15, -37, 110, -11, -37, + 111, -7, -38, 112, -3, -39, 113, 0, -39, 114, 4, -40, + 115, 9, -41, 116, 13, -41, 117, 17, -42, 118, 21, -43, + 119, 25, -43, 120, 29, -44, 121, 33, -45, 122, 37, -45, + 123, 41, -46, 124, 46, -47, 125, 50, -47, 126, 54, -48, + 126, 58, -49, 127, 62, -49, 128, 66, -50, 129, 70, -51, + 103, -58, -32, 103, -54, -32, 104, -50, -33, 105, -45, -34, + 106, -41, -34, 107, -37, -35, 108, -33, -36, 109, -29, -36, + 110, -25, -37, 111, -21, -38, 112, -17, -38, 113, -13, -39, + 114, -8, -40, 115, -4, -40, 116, 0, -41, 117, 3, -42, + 118, 7, -42, 119, 11, -43, 119, 15, -44, 120, 19, -44, + 121, 24, -45, 122, 28, -46, 123, 32, -46, 124, 36, -47, + 125, 40, -48, 126, 44, -48, 127, 48, -49, 128, 52, -50, + 129, 56, -50, 130, 61, -51, 131, 65, -52, 132, 69, -52, + 105, -59, -33, 106, -55, -34, 107, -51, -35, 108, -46, -35, + 109, -42, -36, 110, -38, -37, 110, -34, -37, 112, -30, -38, + 112, -26, -39, 113, -22, -39, 114, -18, -40, 115, -14, -41, + 116, -9, -41, 117, -5, -42, 118, -1, -43, 119, 2, -43, + 120, 6, -44, 121, 10, -45, 122, 14, -45, 123, 18, -46, + 124, 23, -47, 125, 27, -47, 126, 31, -48, 126, 35, -49, + 127, 39, -49, 128, 43, -50, 129, 47, -51, 130, 51, -51, + 131, 55, -52, 132, 60, -53, 133, 64, -53, 134, 68, -54, + 107, -60, -35, 108, -56, -36, 109, -52, -36, 110, -48, -37, + 111, -44, -38, 112, -40, -38, 113, -36, -39, 114, -31, -40, + 115, -27, -40, 116, -23, -41, 117, -19, -42, 118, -15, -42, + 119, -11, -43, 119, -7, -44, 120, -3, -44, 121, 0, -45, + 122, 5, -46, 123, 9, -46, 124, 13, -47, 125, 17, -48, + 126, 21, -48, 127, 25, -49, 128, 29, -50, 129, 33, -50, + 130, 37, -51, 131, 42, -52, 132, 46, -52, 133, 50, -53, + 133, 54, -54, 134, 58, -54, 135, 62, -55, 136, 66, -56, + 110, -62, -37, 111, -58, -37, 111, -54, -38, 112, -49, -39, + 113, -45, -39, 114, -41, -40, 115, -37, -41, 116, -33, -41, + 117, -29, -42, 118, -25, -43, 119, -21, -43, 120, -17, -44, + 121, -12, -45, 122, -8, -45, 123, -4, -46, 124, 0, -47, + 125, 3, -47, 126, 7, -48, 126, 11, -49, 127, 15, -49, + 128, 20, -50, 129, 24, -51, 130, 28, -51, 131, 32, -52, + 132, 36, -53, 133, 40, -53, 134, 44, -54, 135, 48, -55, + 136, 52, -55, 137, 57, -56, 138, 61, -57, 139, 65, -57, + 112, -63, -38, 113, -59, -39, 114, -55, -40, 115, -50, -40, + 116, -46, -41, 117, -42, -42, 118, -38, -42, 119, -34, -43, + 119, -30, -44, 120, -26, -44, 121, -22, -45, 122, -18, -46, + 123, -13, -46, 124, -9, -47, 125, -5, -48, 126, -1, -48, + 127, 2, -49, 128, 6, -50, 129, 10, -50, 130, 14, -51, + 131, 19, -52, 132, 23, -52, 133, 27, -53, 133, 31, -54, + 134, 35, -54, 135, 39, -55, 136, 43, -56, 137, 47, -56, + 138, 51, -57, 139, 56, -58, 140, 60, -58, 141, 64, -59, + 114, -64, -40, 115, -60, -41, 116, -56, -41, 117, -52, -42, + 118, -48, -43, 119, -44, -43, 120, -40, -44, 121, -35, -45, + 122, -31, -45, 123, -27, -46, 124, -23, -47, 125, -19, -47, + 126, -15, -48, 126, -11, -49, 127, -7, -49, 128, -3, -50, + 129, 1, -51, 130, 5, -51, 131, 9, -52, 132, 13, -53, + 133, 17, -53, 134, 21, -54, 135, 25, -55, 136, 29, -55, + 137, 33, -56, 138, 38, -57, 139, 42, -57, 140, 46, -58, + 141, 50, -59, 142, 54, -59, 142, 58, -60, 143, 62, -61, + 117, -66, -42, 118, -62, -42, 118, -58, -43, 119, -53, -44, + 120, -49, -44, 121, -45, -45, 122, -41, -46, 123, -37, -46, + 124, -33, -47, 125, -29, -48, 126, -25, -48, 127, -21, -49, + 128, -16, -50, 129, -12, -50, 130, -8, -51, 131, -4, -52, + 132, 0, -52, 133, 3, -53, 134, 7, -54, 134, 11, -54, + 135, 16, -55, 136, 20, -56, 137, 24, -56, 138, 28, -57, + 139, 32, -58, 140, 36, -58, 141, 40, -59, 142, 44, -60, + 143, 48, -60, 144, 53, -61, 145, 57, -62, 146, 61, -62, + 119, -67, -43, 120, -63, -44, 121, -59, -45, 122, -54, -45, + 123, -50, -46, 124, -46, -47, 125, -42, -47, 126, -38, -48, + 127, -34, -49, 127, -30, -49, 128, -26, -50, 129, -22, -51, + 130, -17, -51, 131, -13, -52, 132, -9, -53, 133, -5, -53, + 134, -1, -54, 135, 2, -55, 136, 6, -55, 137, 10, -56, + 138, 15, -57, 139, 19, -57, 140, 23, -58, 141, 27, -59, + 141, 31, -59, 142, 35, -60, 143, 39, -61, 144, 43, -61, + 145, 47, -62, 146, 52, -63, 147, 56, -63, 148, 60, -64, + 121, -68, -45, 122, -64, -46, 123, -60, -46, 124, -56, -47, + 125, -52, -48, 126, -48, -48, 127, -44, -49, 128, -39, -50, + 129, -35, -50, 130, -31, -51, 131, -27, -52, 132, -23, -52, + 133, -19, -53, 134, -15, -54, 134, -11, -54, 135, -7, -55, + 136, -2, -56, 137, 1, -56, 138, 5, -57, 139, 9, -58, + 140, 13, -58, 141, 17, -59, 142, 21, -60, 143, 25, -60, + 144, 29, -61, 145, 34, -62, 146, 38, -62, 147, 42, -63, + 148, 46, -64, 149, 50, -64, 149, 54, -65, 150, 58, -66, + 124, -70, -47, 125, -66, -47, 125, -62, -48, 127, -57, -49, + 127, -53, -49, 128, -49, -50, 129, -45, -51, 130, -41, -51, + 131, -37, -52, 132, -33, -53, 133, -29, -53, 134, -25, -54, + 135, -20, -55, 136, -16, -55, 137, -12, -56, 138, -8, -57, + 139, -4, -57, 140, 0, -58, 141, 3, -59, 141, 7, -59, + 142, 12, -60, 143, 16, -61, 144, 20, -61, 145, 24, -62, + 146, 28, -63, 147, 32, -63, 148, 36, -64, 149, 40, -65, + 150, 44, -65, 151, 49, -66, 152, 53, -67, 153, 57, -67, + 126, -71, -48, 127, -67, -49, 128, -63, -50, 129, -58, -50, + 130, -54, -51, 131, -50, -52, 132, -46, -52, 133, -42, -53, + 134, -38, -54, 134, -34, -54, 135, -30, -55, 136, -26, -56, + 137, -21, -56, 138, -17, -57, 139, -13, -58, 140, -9, -58, + 141, -5, -59, 142, -1, -60, 143, 2, -60, 144, 6, -61, + 145, 11, -62, 146, 15, -62, 147, 19, -63, 148, 23, -64, + 148, 27, -64, 150, 31, -65, 150, 35, -66, 151, 39, -66, + 152, 43, -67, 153, 48, -68, 154, 52, -68, 155, 56, -69, + 128, -72, -50, 129, -68, -51, 130, -64, -51, 131, -60, -52, + 132, -56, -53, 133, -52, -53, 134, -48, -54, 135, -43, -55, + 136, -39, -55, 137, -35, -56, 138, -31, -57, 139, -27, -57, + 140, -23, -58, 141, -19, -59, 141, -15, -59, 142, -11, -60, + 143, -6, -61, 144, -2, -61, 145, 1, -62, 146, 5, -63, + 147, 9, -63, 148, 13, -64, 149, 17, -65, 150, 21, -65, + 151, 25, -66, 152, 30, -67, 153, 34, -67, 154, 38, -68, + 155, 42, -69, 156, 46, -69, 157, 50, -70, 157, 54, -71, + 131, -74, -52, 132, -70, -52, 133, -66, -53, 134, -61, -54, + 134, -57, -54, 135, -53, -55, 136, -49, -56, 137, -45, -56, + 138, -41, -57, 139, -37, -58, 140, -33, -58, 141, -29, -59, + 142, -24, -60, 143, -20, -60, 144, -16, -61, 145, -12, -62, + 146, -8, -62, 147, -4, -63, 148, 0, -64, 149, 3, -64, + 150, 8, -65, 150, 12, -66, 151, 16, -66, 152, 20, -67, + 153, 24, -68, 154, 28, -68, 155, 32, -69, 156, 36, -70, + 157, 40, -70, 158, 45, -71, 159, 49, -72, 160, 53, -72, + 133, -75, -53, 134, -71, -54, 135, -67, -55, 136, -62, -55, + 137, -58, -56, 138, -54, -57, 139, -50, -57, 140, -46, -58, + 141, -42, -59, 142, -38, -59, 142, -34, -60, 143, -30, -61, + 144, -25, -61, 145, -21, -62, 146, -17, -63, 147, -13, -63, + 148, -9, -64, 149, -5, -65, 150, -1, -65, 151, 2, -66, + 152, 7, -67, 153, 11, -67, 154, 15, -68, 155, 19, -69, + 156, 23, -69, 157, 27, -70, 157, 31, -71, 158, 35, -71, + 159, 39, -72, 160, 44, -73, 161, 48, -73, 162, 52, -74, + 135, -76, -55, 136, -72, -56, 137, -68, -56, 138, -64, -57, + 139, -60, -58, 140, -56, -58, 141, -52, -59, 142, -47, -60, + 143, -43, -60, 144, -39, -61, 145, -35, -62, 146, -31, -62, + 147, -27, -63, 148, -23, -64, 149, -19, -64, 149, -15, -65, + 150, -10, -66, 151, -6, -66, 152, -2, -67, 153, 1, -68, + 154, 5, -68, 155, 9, -69, 156, 13, -70, 157, 17, -70, + 158, 21, -71, 159, 26, -72, 160, 30, -72, 161, 34, -73, + 162, 38, -74, 163, 42, -75, 164, 46, -75, 164, 50, -76, + 138, -78, -57, 139, -74, -57, 140, -70, -58, 141, -65, -59, + 142, -61, -59, 142, -57, -60, 143, -53, -61, 144, -49, -61, + 145, -45, -62, 146, -41, -63, 147, -37, -63, 148, -33, -64, + 149, -28, -65, 150, -24, -65, 151, -20, -66, 152, -16, -67, + 153, -12, -67, 154, -8, -68, 155, -4, -69, 156, 0, -69, + 157, 4, -70, 157, 8, -71, 158, 12, -71, 159, 16, -72, + 160, 20, -73, 161, 24, -74, 162, 28, -74, 163, 32, -75, + 164, 36, -75, 165, 41, -76, 166, 45, -77, 167, 49, -77, + 140, -79, -58, 141, -75, -59, 142, -71, -60, 143, -66, -60, + 144, -62, -61, 145, -58, -62, 146, -54, -62, 147, -50, -63, + 148, -46, -64, 149, -42, -64, 149, -38, -65, 150, -34, -66, + 151, -29, -66, 152, -25, -67, 153, -21, -68, 154, -17, -68, + 155, -13, -69, 156, -9, -70, 157, -5, -70, 158, -1, -71, + 159, 3, -72, 160, 7, -72, 161, 11, -73, 162, 15, -74, + 163, 19, -74, 164, 23, -75, 165, 27, -76, 165, 31, -76, + 166, 35, -77, 167, 40, -78, 168, 44, -79, 169, 48, -79, + 143, -81, -61, 144, -77, -61, 145, -73, -62, 146, -68, -63, + 147, -64, -63, 148, -60, -64, 149, -56, -65, 150, -52, -65, + 151, -48, -66, 151, -44, -67, 152, -40, -67, 153, -36, -68, + 154, -31, -69, 155, -27, -69, 156, -23, -70, 157, -19, -71, + 158, -15, -71, 159, -11, -72, 160, -7, -73, 161, -3, -73, + 162, 1, -74, 163, 5, -75, 164, 9, -75, 165, 13, -76, + 166, 17, -77, 167, 21, -77, 167, 25, -78, 168, 29, -79, + 169, 33, -79, 170, 38, -80, 171, 42, -81, 172, 46, -81, + 145, -82, -62, 146, -78, -63, 147, -74, -63, 148, -69, -64, + 149, -65, -65, 150, -61, -66, 151, -57, -66, 152, -53, -67, + 153, -49, -68, 154, -45, -68, 155, -41, -69, 156, -37, -70, + 157, -32, -70, 158, -28, -71, 159, -24, -72, 159, -20, -72, + 160, -16, -73, 161, -12, -74, 162, -8, -74, 163, -4, -75, + 164, 0, -76, 165, 4, -76, 166, 8, -77, 167, 12, -78, + 168, 16, -78, 169, 20, -79, 170, 24, -80, 171, 28, -80, + 172, 32, -81, 173, 37, -82, 174, 41, -82, 174, 45, -83, + 148, -83, -64, 149, -79, -65, 150, -75, -65, 151, -71, -66, + 152, -67, -67, 152, -63, -67, 153, -59, -68, 154, -54, -69, + 155, -50, -69, 156, -46, -70, 157, -42, -71, 158, -38, -71, + 159, -34, -72, 160, -30, -73, 161, -26, -73, 162, -22, -74, + 163, -17, -75, 164, -13, -75, 165, -9, -76, 166, -5, -77, + 167, -1, -77, 167, 2, -78, 168, 6, -79, 169, 10, -79, + 170, 14, -80, 171, 19, -81, 172, 23, -81, 173, 27, -82, + 174, 31, -83, 175, 35, -83, 176, 39, -84, 177, 43, -85, + 150, -84, -66, 151, -80, -66, 152, -76, -67, 153, -72, -68, + 154, -68, -68, 155, -64, -69, 156, -60, -70, 157, -55, -70, + 158, -51, -71, 159, -47, -72, 159, -43, -72, 160, -39, -73, + 161, -35, -74, 162, -31, -74, 163, -27, -75, 164, -23, -76, + 165, -18, -76, 166, -14, -77, 167, -10, -78, 168, -6, -78, + 169, -2, -79, 170, 1, -80, 171, 5, -80, 172, 9, -81, + 173, 13, -82, 174, 18, -82, 174, 22, -83, 175, 26, -84, + 176, 30, -84, 177, 34, -85, 178, 38, -86, 179, 42, -86, + 152, -86, -67, 153, -82, -68, 154, -78, -69, 155, -73, -69, + 156, -69, -70, 157, -65, -71, 158, -61, -71, 159, -57, -72, + 160, -53, -73, 161, -49, -73, 162, -45, -74, 163, -41, -75, + 164, -36, -75, 165, -32, -76, 166, -28, -77, 166, -24, -77, + 167, -20, -78, 168, -16, -79, 169, -12, -79, 170, -8, -80, + 171, -3, -81, 172, 0, -81, 173, 4, -82, 174, 8, -83, + 175, 12, -83, 176, 16, -84, 177, 20, -85, 178, 24, -85, + 179, 28, -86, 180, 33, -87, 181, 37, -87, 182, 41, -88, + 155, -87, -69, 156, -83, -70, 157, -79, -70, 158, -75, -71, + 159, -71, -72, 159, -67, -72, 160, -63, -73, 161, -58, -74, + 162, -54, -74, 163, -50, -75, 164, -46, -76, 165, -42, -76, + 166, -38, -77, 167, -34, -78, 168, -30, -78, 169, -26, -79, + 170, -21, -80, 171, -17, -80, 172, -13, -81, 173, -9, -82, + 174, -5, -82, 175, -1, -83, 175, 2, -84, 176, 6, -84, + 177, 10, -85, 178, 15, -86, 179, 19, -86, 180, 23, -87, + 181, 27, -88, 182, 31, -88, 183, 35, -89, 184, 39, -90, + 157, -88, -71, 158, -84, -71, 159, -80, -72, 160, -76, -73, + 161, -72, -73, 162, -68, -74, 163, -64, -75, 164, -59, -75, + 165, -55, -76, 166, -51, -77, 166, -47, -77, 167, -43, -78, + 168, -39, -79, 169, -35, -79, 170, -31, -80, 171, -27, -81, + 172, -22, -81, 173, -18, -82, 174, -14, -83, 175, -10, -83, + 176, -6, -84, 177, -2, -85, 178, 1, -85, 179, 5, -86, + 180, 9, -87, 181, 14, -87, 182, 18, -88, 182, 22, -89, + 183, 26, -89, 184, 30, -90, 185, 34, -91, 186, 38, -91, + 159, -90, -72, 160, -86, -73, 161, -82, -74, 162, -77, -74, + 163, -73, -75, 164, -69, -76, 165, -65, -76, 166, -61, -77, + 167, -57, -78, 168, -53, -78, 169, -49, -79, 170, -45, -80, + 171, -40, -80, 172, -36, -81, 173, -32, -82, 174, -28, -82, + 175, -24, -83, 175, -20, -84, 176, -16, -84, 177, -12, -85, + 178, -7, -86, 179, -3, -86, 180, 0, -87, 181, 4, -88, + 182, 8, -88, 183, 12, -89, 184, 16, -90, 185, 20, -90, + 186, 24, -91, 187, 29, -92, 188, 33, -92, 189, 37, -93, + 162, -91, -74, 163, -87, -75, 164, -83, -75, 165, -79, -76, + 166, -75, -77, 167, -71, -77, 167, -67, -78, 168, -62, -79, + 169, -58, -79, 170, -54, -80, 171, -50, -81, 172, -46, -81, + 173, -42, -82, 174, -38, -83, 175, -34, -83, 176, -30, -84, + 177, -25, -85, 178, -21, -85, 179, -17, -86, 180, -13, -87, + 181, -9, -87, 182, -5, -88, 182, -1, -89, 183, 2, -89, + 184, 6, -90, 185, 11, -91, 186, 15, -91, 187, 19, -92, + 188, 23, -93, 189, 27, -93, 190, 31, -94, 191, 35, -95, + 164, -92, -76, 165, -88, -76, 166, -84, -77, 167, -80, -78, + 168, -76, -78, 169, -72, -79, 170, -68, -80, 171, -63, -80, + 172, -59, -81, 173, -55, -82, 174, -51, -82, 174, -47, -83, + 175, -43, -84, 176, -39, -84, 177, -35, -85, 178, -31, -86, + 179, -26, -86, 180, -22, -87, 181, -18, -88, 182, -14, -88, + 183, -10, -89, 184, -6, -90, 185, -2, -90, 186, 1, -91, + 187, 5, -92, 188, 10, -92, 189, 14, -93, 189, 18, -94, + 190, 22, -94, 191, 26, -95, 192, 30, -96, 193, 34, -96, + 167, -94, -77, 167, -90, -78, 168, -86, -79, 169, -81, -79, + 170, -77, -80, 171, -73, -81, 172, -69, -81, 173, -65, -82, + 174, -61, -83, 175, -57, -83, 176, -53, -84, 177, -49, -85, + 178, -44, -85, 179, -40, -86, 180, -36, -87, 181, -32, -87, + 182, -28, -88, 182, -24, -89, 183, -20, -89, 184, -16, -90, + 185, -11, -91, 186, -7, -91, 187, -3, -92, 188, 0, -93, + 189, 4, -93, 190, 8, -94, 191, 12, -95, 192, 16, -95, + 193, 20, -96, 194, 25, -97, 195, 29, -97, 196, 33, -98, + 19, -11, 33, 20, -7, 32, 21, -3, 31, 22, 1, 30, + 23, 5, 30, 24, 9, 29, 25, 13, 29, 26, 17, 28, + 27, 21, 27, 28, 25, 26, 29, 29, 26, 29, 33, 25, + 31, 38, 24, 31, 42, 24, 32, 46, 23, 33, 50, 22, + 34, 54, 22, 35, 58, 21, 36, 62, 20, 37, 66, 20, + 38, 71, 19, 39, 75, 18, 40, 79, 18, 41, 83, 17, + 42, 87, 16, 43, 91, 16, 44, 95, 15, 45, 99, 14, + 45, 103, 14, 46, 108, 13, 47, 112, 12, 48, 116, 12, + 22, -12, 31, 22, -8, 30, 23, -4, 30, 24, 0, 29, + 25, 4, 28, 26, 8, 27, 27, 12, 27, 28, 16, 26, + 29, 20, 25, 30, 24, 25, 31, 28, 24, 32, 32, 24, + 33, 37, 23, 34, 41, 22, 35, 45, 21, 36, 49, 21, + 37, 53, 20, 38, 57, 19, 38, 61, 19, 39, 65, 18, + 40, 70, 17, 41, 74, 17, 42, 78, 16, 43, 82, 15, + 44, 86, 15, 45, 90, 14, 46, 94, 13, 47, 98, 13, + 48, 102, 12, 49, 107, 11, 50, 111, 11, 51, 115, 10, + 24, -13, 29, 25, -9, 29, 26, -5, 28, 27, -1, 27, + 28, 2, 26, 29, 6, 26, 30, 10, 25, 31, 15, 24, + 31, 19, 24, 32, 23, 23, 33, 27, 22, 34, 31, 22, + 35, 35, 21, 36, 39, 20, 37, 43, 20, 38, 47, 19, + 39, 52, 18, 40, 56, 18, 41, 60, 17, 42, 64, 16, + 43, 68, 16, 44, 72, 15, 45, 76, 14, 45, 80, 14, + 46, 84, 13, 47, 89, 12, 48, 93, 12, 49, 97, 11, + 50, 101, 10, 51, 105, 10, 52, 109, 9, 53, 113, 8, + 26, -15, 27, 27, -11, 27, 28, -7, 26, 29, -2, 25, + 30, 1, 25, 31, 5, 24, 32, 9, 23, 33, 13, 23, + 34, 17, 22, 35, 21, 21, 36, 25, 21, 37, 29, 20, + 38, 34, 19, 38, 38, 19, 39, 42, 18, 40, 46, 17, + 41, 50, 17, 42, 54, 16, 43, 58, 15, 44, 62, 15, + 45, 67, 14, 46, 71, 13, 47, 75, 13, 48, 79, 12, + 49, 83, 11, 50, 87, 11, 51, 91, 10, 52, 95, 9, + 52, 99, 9, 54, 104, 8, 54, 108, 7, 55, 112, 7, + 29, -16, 26, 30, -12, 25, 30, -8, 25, 31, -3, 24, + 32, 0, 23, 33, 4, 22, 34, 8, 22, 35, 12, 21, + 36, 16, 20, 37, 20, 20, 38, 24, 19, 39, 28, 18, + 40, 33, 18, 41, 37, 17, 42, 41, 16, 43, 45, 16, + 44, 49, 15, 45, 53, 14, 45, 57, 14, 46, 61, 13, + 47, 66, 12, 48, 70, 12, 49, 74, 11, 50, 78, 10, + 51, 82, 10, 52, 86, 9, 53, 90, 8, 54, 94, 8, + 55, 98, 7, 56, 103, 6, 57, 107, 6, 58, 111, 5, + 31, -17, 24, 32, -13, 23, 33, -9, 23, 34, -5, 22, + 35, -1, 21, 36, 2, 21, 37, 6, 20, 38, 11, 19, + 38, 15, 19, 39, 19, 18, 40, 23, 17, 41, 27, 17, + 42, 31, 16, 43, 35, 15, 44, 39, 15, 45, 43, 14, + 46, 48, 13, 47, 52, 13, 48, 56, 12, 49, 60, 11, + 50, 64, 11, 51, 68, 10, 52, 72, 9, 53, 76, 9, + 53, 80, 8, 54, 85, 7, 55, 89, 7, 56, 93, 6, + 57, 97, 5, 58, 101, 5, 59, 105, 4, 60, 109, 3, + 33, -19, 22, 34, -15, 22, 35, -11, 21, 36, -6, 20, + 37, -2, 20, 38, 1, 19, 39, 5, 18, 40, 9, 18, + 41, 13, 17, 42, 17, 16, 43, 21, 16, 44, 25, 15, + 45, 30, 14, 46, 34, 14, 46, 38, 13, 47, 42, 12, + 48, 46, 12, 49, 50, 11, 50, 54, 10, 51, 58, 10, + 52, 63, 9, 53, 67, 8, 54, 71, 8, 55, 75, 7, + 56, 79, 6, 57, 83, 6, 58, 87, 5, 59, 91, 4, + 60, 95, 4, 61, 100, 3, 61, 104, 2, 62, 108, 2, + 36, -20, 21, 37, -16, 20, 37, -12, 19, 39, -7, 19, + 39, -3, 18, 40, 0, 17, 41, 4, 17, 42, 8, 16, + 43, 12, 15, 44, 16, 15, 45, 20, 14, 46, 24, 13, + 47, 29, 13, 48, 33, 12, 49, 37, 11, 50, 41, 11, + 51, 45, 10, 52, 49, 9, 53, 53, 9, 53, 57, 8, + 54, 62, 7, 55, 66, 7, 56, 70, 6, 57, 74, 5, + 58, 78, 5, 59, 82, 4, 60, 86, 3, 61, 90, 3, + 62, 94, 2, 63, 99, 1, 64, 103, 1, 65, 107, 0, + 38, -21, 19, 39, -17, 18, 40, -13, 18, 41, -9, 17, + 42, -5, 16, 43, -1, 16, 44, 2, 15, 45, 7, 14, + 46, 11, 14, 46, 15, 13, 47, 19, 12, 48, 23, 12, + 49, 27, 11, 50, 31, 10, 51, 35, 10, 52, 39, 9, + 53, 44, 8, 54, 48, 8, 55, 52, 7, 56, 56, 6, + 57, 60, 6, 58, 64, 5, 59, 68, 4, 60, 72, 4, + 60, 76, 3, 62, 81, 2, 62, 85, 2, 63, 89, 1, + 64, 93, 0, 65, 97, 0, 66, 101, 0, 67, 105, -1, + 40, -23, 17, 41, -19, 17, 42, -15, 16, 43, -10, 15, + 44, -6, 15, 45, -2, 14, 46, 1, 13, 47, 5, 13, + 48, 9, 12, 49, 13, 11, 50, 17, 11, 51, 21, 10, + 52, 26, 9, 53, 30, 9, 53, 34, 8, 54, 38, 7, + 55, 42, 7, 56, 46, 6, 57, 50, 5, 58, 54, 5, + 59, 59, 4, 60, 63, 3, 61, 67, 3, 62, 71, 2, + 63, 75, 1, 64, 79, 1, 65, 83, 0, 66, 87, 0, + 67, 91, 0, 68, 96, -1, 69, 100, -2, 69, 104, -2, + 43, -24, 16, 44, -20, 15, 45, -16, 14, 46, -11, 14, + 46, -7, 13, 47, -3, 12, 48, 0, 12, 49, 4, 11, + 50, 8, 10, 51, 12, 10, 52, 16, 9, 53, 20, 8, + 54, 25, 8, 55, 29, 7, 56, 33, 6, 57, 37, 6, + 58, 41, 5, 59, 45, 4, 60, 49, 4, 60, 53, 3, + 62, 58, 2, 62, 62, 2, 63, 66, 1, 64, 70, 0, + 65, 74, 0, 66, 78, 0, 67, 82, -1, 68, 86, -1, + 69, 90, -2, 70, 95, -3, 71, 99, -3, 72, 103, -4, + 46, -26, 14, 47, -22, 13, 47, -18, 12, 48, -13, 12, + 49, -9, 11, 50, -5, 10, 51, -1, 10, 52, 2, 9, + 53, 6, 8, 54, 10, 8, 55, 14, 7, 56, 18, 6, + 57, 23, 6, 58, 27, 5, 59, 31, 4, 60, 35, 4, + 61, 39, 3, 62, 43, 2, 63, 47, 2, 63, 51, 1, + 64, 56, 0, 65, 60, 0, 66, 64, 0, 67, 68, -1, + 68, 72, -1, 69, 76, -2, 70, 80, -3, 71, 84, -3, + 72, 88, -4, 73, 93, -5, 74, 97, -5, 75, 101, -6, + 48, -27, 12, 49, -23, 11, 50, -19, 11, 51, -14, 10, + 52, -10, 9, 53, -6, 9, 54, -2, 8, 55, 1, 7, + 56, 5, 7, 56, 9, 6, 57, 13, 5, 58, 17, 5, + 59, 22, 4, 60, 26, 3, 61, 30, 3, 62, 34, 2, + 63, 38, 1, 64, 42, 1, 65, 46, 0, 66, 50, 0, + 67, 55, 0, 68, 59, -1, 69, 63, -2, 70, 67, -2, + 70, 71, -3, 71, 75, -4, 72, 79, -4, 73, 83, -5, + 74, 87, -6, 75, 92, -6, 76, 96, -7, 77, 100, -8, + 50, -28, 10, 51, -24, 10, 52, -20, 9, 53, -16, 8, + 54, -12, 8, 55, -8, 7, 56, -4, 6, 57, 0, 6, + 58, 4, 5, 59, 8, 4, 60, 12, 4, 61, 16, 3, + 62, 20, 2, 63, 24, 2, 63, 28, 1, 64, 32, 0, + 65, 37, 0, 66, 41, 0, 67, 45, -1, 68, 49, -1, + 69, 53, -2, 70, 57, -3, 71, 61, -3, 72, 65, -4, + 73, 69, -5, 74, 74, -5, 75, 78, -6, 76, 82, -7, + 77, 86, -7, 78, 90, -8, 79, 94, -9, 79, 98, -9, + 53, -30, 9, 54, -26, 8, 55, -22, 7, 56, -17, 7, + 56, -13, 6, 57, -9, 5, 58, -5, 5, 59, -1, 4, + 60, 2, 3, 61, 6, 3, 62, 10, 2, 63, 14, 1, + 64, 19, 1, 65, 23, 0, 66, 27, 0, 67, 31, 0, + 68, 35, -1, 69, 39, -2, 70, 43, -2, 70, 47, -3, + 72, 52, -4, 72, 56, -4, 73, 60, -5, 74, 64, -6, + 75, 68, -6, 76, 72, -7, 77, 76, -8, 78, 80, -8, + 79, 84, -9, 80, 89, -10, 81, 93, -10, 82, 97, -11, + 55, -31, 7, 56, -27, 6, 57, -23, 6, 58, -18, 5, + 59, -14, 4, 60, -10, 4, 61, -6, 3, 62, -2, 2, + 63, 1, 2, 63, 5, 1, 64, 9, 0, 65, 13, 0, + 66, 18, 0, 67, 22, -1, 68, 26, -1, 69, 30, -2, + 70, 34, -3, 71, 38, -3, 72, 42, -4, 73, 46, -5, + 74, 51, -5, 75, 55, -6, 76, 59, -7, 77, 63, -7, + 77, 67, -8, 79, 71, -9, 79, 75, -9, 80, 79, -10, + 81, 83, -11, 82, 88, -11, 83, 92, -12, 84, 96, -13, + 57, -32, 5, 58, -28, 5, 59, -24, 4, 60, -20, 3, + 61, -16, 3, 62, -12, 2, 63, -8, 1, 64, -3, 1, + 65, 0, 0, 66, 4, 0, 67, 8, 0, 68, 12, -1, + 69, 16, -2, 70, 20, -2, 70, 24, -3, 71, 28, -4, + 72, 33, -4, 73, 37, -5, 74, 41, -6, 75, 45, -6, + 76, 49, -7, 77, 53, -8, 78, 57, -8, 79, 61, -9, + 80, 65, -10, 81, 70, -10, 82, 74, -11, 83, 78, -12, + 84, 82, -12, 85, 86, -13, 86, 90, -14, 86, 94, -14, + 60, -33, 4, 61, -29, 3, 62, -25, 2, 63, -21, 2, + 63, -17, 1, 64, -13, 0, 65, -9, 0, 66, -4, 0, + 67, 0, -1, 68, 3, -1, 69, 7, -2, 70, 11, -3, + 71, 15, -3, 72, 19, -4, 73, 23, -5, 74, 27, -5, + 75, 32, -6, 76, 36, -7, 77, 40, -7, 78, 44, -8, + 79, 48, -9, 79, 52, -9, 80, 56, -10, 81, 60, -11, + 82, 64, -11, 83, 69, -12, 84, 73, -13, 85, 77, -13, + 86, 81, -14, 87, 85, -15, 88, 89, -15, 89, 93, -16, + 62, -35, 2, 63, -31, 1, 64, -27, 1, 65, -22, 0, + 66, -18, 0, 67, -14, 0, 68, -10, -1, 69, -6, -2, + 70, -2, -2, 71, 1, -3, 71, 5, -4, 72, 9, -4, + 73, 14, -5, 74, 18, -6, 75, 22, -6, 76, 26, -7, + 77, 30, -8, 78, 34, -8, 79, 38, -9, 80, 42, -10, + 81, 47, -10, 82, 51, -11, 83, 55, -12, 84, 59, -12, + 85, 63, -13, 86, 67, -14, 86, 71, -14, 87, 75, -15, + 88, 79, -16, 89, 84, -16, 90, 88, -17, 91, 92, -18, + 64, -36, 0, 65, -32, 0, 66, -28, 0, 67, -24, -1, + 68, -20, -1, 69, -16, -2, 70, -12, -3, 71, -7, -3, + 72, -3, -4, 73, 0, -5, 74, 4, -5, 75, 8, -6, + 76, 12, -7, 77, 16, -7, 78, 20, -8, 78, 24, -9, + 79, 29, -9, 80, 33, -10, 81, 37, -11, 82, 41, -11, + 83, 45, -12, 84, 49, -13, 85, 53, -13, 86, 57, -14, + 87, 61, -15, 88, 66, -15, 89, 70, -16, 90, 74, -17, + 91, 78, -17, 92, 82, -18, 93, 86, -19, 94, 90, -19, + 67, -37, 0, 68, -33, -1, 69, -29, -2, 70, -25, -2, + 71, -21, -3, 71, -17, -4, 72, -13, -4, 73, -8, -5, + 74, -4, -6, 75, 0, -6, 76, 3, -7, 77, 7, -8, + 78, 11, -8, 79, 15, -9, 80, 19, -10, 81, 23, -10, + 82, 28, -11, 83, 32, -12, 84, 36, -12, 85, 40, -13, + 86, 44, -14, 87, 48, -14, 87, 52, -15, 88, 56, -16, + 89, 60, -16, 90, 65, -17, 91, 69, -18, 92, 73, -18, + 93, 77, -19, 94, 81, -20, 95, 85, -20, 96, 89, -21, + 69, -39, -2, 70, -35, -3, 71, -31, -3, 72, -26, -4, + 73, -22, -5, 74, -18, -5, 75, -14, -6, 76, -10, -7, + 77, -6, -7, 78, -2, -8, 78, 1, -9, 79, 5, -9, + 80, 10, -10, 81, 14, -11, 82, 18, -11, 83, 22, -12, + 84, 26, -13, 85, 30, -13, 86, 34, -14, 87, 38, -15, + 88, 43, -16, 89, 47, -16, 90, 51, -17, 91, 55, -17, + 92, 59, -18, 93, 63, -19, 94, 67, -19, 94, 71, -20, + 95, 75, -21, 96, 80, -22, 97, 84, -22, 98, 88, -23, + 71, -40, -4, 72, -36, -4, 73, -32, -5, 74, -28, -6, + 75, -24, -6, 76, -20, -7, 77, -16, -8, 78, -11, -8, + 79, -7, -9, 80, -3, -10, 81, 0, -10, 82, 4, -11, + 83, 8, -12, 84, 12, -12, 85, 16, -13, 85, 20, -14, + 87, 25, -14, 87, 29, -15, 88, 33, -16, 89, 37, -16, + 90, 41, -17, 91, 45, -18, 92, 49, -18, 93, 53, -19, + 94, 57, -20, 95, 62, -21, 96, 66, -21, 97, 70, -22, + 98, 74, -22, 99, 78, -23, 100, 82, -24, 101, 86, -24, + 74, -41, -5, 75, -37, -6, 76, -33, -7, 77, -29, -7, + 78, -25, -8, 78, -21, -9, 79, -17, -9, 80, -12, -10, + 81, -8, -11, 82, -4, -11, 83, 0, -12, 84, 3, -13, + 85, 7, -13, 86, 11, -14, 87, 15, -15, 88, 19, -15, + 89, 24, -16, 90, 28, -17, 91, 32, -17, 92, 36, -18, + 93, 40, -19, 94, 44, -20, 94, 48, -20, 95, 52, -21, + 96, 56, -21, 97, 61, -22, 98, 65, -23, 99, 69, -23, + 100, 73, -24, 101, 77, -25, 102, 81, -26, 103, 85, -26, + 76, -43, -7, 77, -39, -8, 78, -35, -8, 79, -30, -9, + 80, -26, -10, 81, -22, -10, 82, -18, -11, 83, -14, -12, + 84, -10, -12, 85, -6, -13, 86, -2, -14, 86, 1, -14, + 87, 6, -15, 88, 10, -16, 89, 14, -16, 90, 18, -17, + 91, 22, -18, 92, 26, -18, 93, 30, -19, 94, 34, -20, + 95, 39, -21, 96, 43, -21, 97, 47, -22, 98, 51, -22, + 99, 55, -23, 100, 59, -24, 101, 63, -25, 101, 67, -25, + 102, 71, -26, 103, 76, -27, 104, 80, -27, 105, 84, -28, + 79, -44, -9, 79, -40, -9, 80, -36, -10, 81, -32, -11, + 82, -28, -11, 83, -24, -12, 84, -20, -13, 85, -15, -14, + 86, -11, -14, 87, -7, -15, 88, -3, -15, 89, 0, -16, + 90, 4, -17, 91, 8, -17, 92, 12, -18, 93, 16, -19, + 94, 21, -20, 94, 25, -20, 95, 29, -21, 96, 33, -21, + 97, 37, -22, 98, 41, -23, 99, 45, -24, 100, 49, -24, + 101, 53, -25, 102, 58, -26, 103, 62, -26, 104, 66, -27, + 105, 70, -27, 106, 74, -28, 107, 78, -29, 108, 82, -30, + 81, -45, -10, 82, -41, -11, 83, -37, -12, 84, -33, -12, + 85, -29, -13, 86, -25, -14, 86, -21, -14, 87, -16, -15, + 88, -12, -16, 89, -8, -16, 90, -4, -17, 91, 0, -18, + 92, 3, -19, 93, 7, -19, 94, 11, -20, 95, 15, -20, + 96, 20, -21, 97, 24, -22, 98, 28, -22, 99, 32, -23, + 100, 36, -24, 101, 40, -25, 102, 44, -25, 102, 48, -26, + 103, 52, -26, 104, 57, -27, 105, 61, -28, 106, 65, -29, + 107, 69, -29, 108, 73, -30, 109, 77, -31, 110, 81, -31, + 83, -47, -12, 84, -43, -13, 85, -39, -13, 86, -34, -14, + 87, -30, -15, 88, -26, -15, 89, -22, -16, 90, -18, -17, + 91, -14, -18, 92, -10, -18, 93, -6, -19, 93, -2, -19, + 95, 2, -20, 95, 6, -21, 96, 10, -21, 97, 14, -22, + 98, 18, -23, 99, 22, -24, 100, 26, -24, 101, 30, -25, + 102, 35, -26, 103, 39, -26, 104, 43, -27, 105, 47, -28, + 106, 51, -28, 107, 55, -29, 108, 59, -30, 109, 63, -30, + 109, 67, -31, 110, 72, -32, 111, 76, -32, 112, 80, -33, + 86, -48, -14, 86, -44, -14, 87, -40, -15, 88, -36, -16, + 89, -32, -16, 90, -28, -17, 91, -24, -18, 92, -19, -19, + 93, -15, -19, 94, -11, -20, 95, -7, -20, 96, -3, -21, + 97, 0, -22, 98, 4, -23, 99, 8, -23, 100, 12, -24, + 101, 17, -25, 102, 21, -25, 102, 25, -26, 103, 29, -26, + 104, 33, -27, 105, 37, -28, 106, 41, -29, 107, 45, -29, + 108, 49, -30, 109, 54, -31, 110, 58, -31, 111, 62, -32, + 112, 66, -33, 113, 70, -33, 114, 74, -34, 115, 78, -35, + 88, -49, -15, 89, -45, -16, 90, -41, -17, 91, -37, -18, + 92, -33, -18, 93, -29, -19, 93, -25, -19, 95, -20, -20, + 95, -16, -21, 96, -12, -22, 97, -8, -22, 98, -4, -23, + 99, 0, -24, 100, 3, -24, 101, 7, -25, 102, 11, -25, + 103, 16, -26, 104, 20, -27, 105, 24, -28, 106, 28, -28, + 107, 32, -29, 108, 36, -30, 109, 40, -30, 109, 44, -31, + 110, 48, -32, 111, 53, -32, 112, 57, -33, 113, 61, -34, + 114, 65, -34, 115, 69, -35, 116, 73, -36, 117, 77, -36, + 90, -51, -17, 91, -47, -18, 92, -43, -18, 93, -38, -19, + 94, -34, -20, 95, -30, -20, 96, -26, -21, 97, -22, -22, + 98, -18, -23, 99, -14, -23, 100, -10, -24, 101, -6, -24, + 102, -1, -25, 102, 2, -26, 103, 6, -27, 104, 10, -27, + 105, 14, -28, 106, 18, -29, 107, 22, -29, 108, 26, -30, + 109, 31, -31, 110, 35, -31, 111, 39, -32, 112, 43, -33, + 113, 47, -33, 114, 51, -34, 115, 55, -35, 116, 59, -35, + 116, 63, -36, 118, 68, -37, 118, 72, -37, 119, 76, -38, + 93, -52, -19, 94, -48, -19, 94, -44, -20, 95, -40, -21, + 96, -36, -22, 97, -32, -22, 98, -28, -23, 99, -23, -24, + 100, -19, -24, 101, -15, -25, 102, -11, -26, 103, -7, -26, + 104, -3, -27, 105, 0, -28, 106, 4, -28, 107, 8, -29, + 108, 13, -30, 109, 17, -30, 109, 21, -31, 110, 25, -32, + 111, 29, -32, 112, 33, -33, 113, 37, -34, 114, 41, -34, + 115, 45, -35, 116, 50, -36, 117, 54, -36, 118, 58, -37, + 119, 62, -38, 120, 66, -38, 121, 70, -39, 122, 74, -40, + 96, -54, -21, 96, -50, -22, 97, -46, -22, 98, -41, -23, + 99, -37, -24, 100, -33, -24, 101, -29, -25, 102, -25, -26, + 103, -21, -26, 104, -17, -27, 105, -13, -28, 106, -9, -28, + 107, -4, -29, 108, 0, -30, 109, 3, -30, 110, 7, -31, + 111, 11, -32, 112, 15, -32, 112, 19, -33, 113, 23, -34, + 114, 28, -34, 115, 32, -35, 116, 36, -36, 117, 40, -36, + 118, 44, -37, 119, 48, -38, 120, 52, -38, 121, 56, -39, + 122, 60, -40, 123, 65, -40, 124, 69, -41, 125, 73, -42, + 98, -55, -23, 99, -51, -23, 100, -47, -24, 101, -43, -25, + 102, -39, -25, 103, -35, -26, 103, -31, -27, 105, -26, -27, + 105, -22, -28, 106, -18, -29, 107, -14, -29, 108, -10, -30, + 109, -6, -31, 110, -2, -31, 111, 1, -32, 112, 5, -33, + 113, 10, -33, 114, 14, -34, 115, 18, -35, 116, 22, -35, + 117, 26, -36, 118, 30, -37, 119, 34, -37, 119, 38, -38, + 120, 42, -39, 121, 47, -39, 122, 51, -40, 123, 55, -41, + 124, 59, -41, 125, 63, -42, 126, 67, -43, 127, 71, -43, + 100, -56, -24, 101, -52, -25, 102, -48, -26, 103, -44, -26, + 104, -40, -27, 105, -36, -28, 106, -32, -28, 107, -27, -29, + 108, -23, -30, 109, -19, -30, 110, -15, -31, 111, -11, -32, + 112, -7, -32, 112, -3, -33, 113, 0, -34, 114, 4, -34, + 115, 9, -35, 116, 13, -36, 117, 17, -36, 118, 21, -37, + 119, 25, -38, 120, 29, -38, 121, 33, -39, 122, 37, -40, + 123, 41, -40, 124, 46, -41, 125, 50, -42, 126, 54, -42, + 126, 58, -43, 127, 62, -44, 128, 66, -44, 129, 70, -45, + 103, -58, -26, 104, -54, -27, 104, -50, -27, 105, -45, -28, + 106, -41, -29, 107, -37, -29, 108, -33, -30, 109, -29, -31, + 110, -25, -31, 111, -21, -32, 112, -17, -33, 113, -13, -33, + 114, -8, -34, 115, -4, -35, 116, 0, -35, 117, 3, -36, + 118, 7, -37, 119, 11, -37, 119, 15, -38, 120, 19, -39, + 121, 24, -39, 122, 28, -40, 123, 32, -41, 124, 36, -41, + 125, 40, -42, 126, 44, -43, 127, 48, -43, 128, 52, -44, + 129, 56, -45, 130, 61, -45, 131, 65, -46, 132, 69, -47, + 105, -59, -28, 106, -55, -28, 107, -51, -29, 108, -47, -30, + 109, -43, -30, 110, -39, -31, 111, -35, -32, 112, -30, -32, + 112, -26, -33, 113, -22, -34, 114, -18, -34, 115, -14, -35, + 116, -10, -36, 117, -6, -36, 118, -2, -37, 119, 1, -38, + 120, 6, -38, 121, 10, -39, 122, 14, -40, 123, 18, -40, + 124, 22, -41, 125, 26, -42, 126, 30, -42, 126, 34, -43, + 127, 38, -44, 128, 43, -44, 129, 47, -45, 130, 51, -46, + 131, 55, -46, 132, 59, -47, 133, 63, -48, 134, 67, -48, + 107, -60, -29, 108, -56, -30, 109, -52, -31, 110, -48, -31, + 111, -44, -32, 112, -40, -33, 113, -36, -33, 114, -31, -34, + 115, -27, -35, 116, -23, -35, 117, -19, -36, 118, -15, -37, + 119, -11, -37, 119, -7, -38, 120, -3, -39, 121, 0, -39, + 122, 5, -40, 123, 9, -41, 124, 13, -41, 125, 17, -42, + 126, 21, -43, 127, 25, -43, 128, 29, -44, 129, 33, -45, + 130, 37, -45, 131, 42, -46, 132, 46, -47, 133, 50, -47, + 134, 54, -48, 135, 58, -49, 135, 62, -49, 136, 66, -50, + 110, -62, -31, 111, -58, -32, 111, -54, -32, 112, -49, -33, + 113, -45, -34, 114, -41, -34, 115, -37, -35, 116, -33, -36, + 117, -29, -36, 118, -25, -37, 119, -21, -38, 120, -17, -38, + 121, -12, -39, 122, -8, -40, 123, -4, -40, 124, 0, -41, + 125, 3, -42, 126, 7, -42, 127, 11, -43, 127, 15, -44, + 128, 20, -44, 129, 24, -45, 130, 28, -46, 131, 32, -46, + 132, 36, -47, 133, 40, -48, 134, 44, -48, 135, 48, -49, + 136, 52, -50, 137, 57, -50, 138, 61, -51, 139, 65, -52, + 112, -63, -33, 113, -59, -33, 114, -55, -34, 115, -50, -35, + 116, -46, -35, 117, -42, -36, 118, -38, -37, 119, -34, -37, + 120, -30, -38, 120, -26, -39, 121, -22, -39, 122, -18, -40, + 123, -13, -41, 124, -9, -41, 125, -5, -42, 126, -1, -43, + 127, 2, -43, 128, 6, -44, 129, 10, -45, 130, 14, -45, + 131, 19, -46, 132, 23, -47, 133, 27, -47, 134, 31, -48, + 134, 35, -49, 135, 39, -49, 136, 43, -50, 137, 47, -51, + 138, 51, -51, 139, 56, -52, 140, 60, -53, 141, 64, -53, + 114, -64, -34, 115, -60, -35, 116, -56, -36, 117, -52, -36, + 118, -48, -37, 119, -44, -38, 120, -40, -38, 121, -35, -39, + 122, -31, -40, 123, -27, -40, 124, -23, -41, 125, -19, -42, + 126, -15, -42, 127, -11, -43, 127, -7, -44, 128, -3, -44, + 129, 1, -45, 130, 5, -46, 131, 9, -46, 132, 13, -47, + 133, 17, -48, 134, 21, -48, 135, 25, -49, 136, 29, -50, + 137, 33, -50, 138, 38, -51, 139, 42, -52, 140, 46, -52, + 141, 50, -53, 142, 54, -54, 142, 58, -54, 143, 62, -55, + 117, -66, -36, 118, -62, -37, 118, -58, -37, 120, -53, -38, + 120, -49, -39, 121, -45, -39, 122, -41, -40, 123, -37, -41, + 124, -33, -41, 125, -29, -42, 126, -25, -43, 127, -21, -43, + 128, -16, -44, 129, -12, -45, 130, -8, -45, 131, -4, -46, + 132, 0, -47, 133, 3, -47, 134, 7, -48, 134, 11, -49, + 135, 16, -49, 136, 20, -50, 137, 24, -51, 138, 28, -51, + 139, 32, -52, 140, 36, -53, 141, 40, -53, 142, 44, -54, + 143, 48, -55, 144, 53, -55, 145, 57, -56, 146, 61, -57, + 119, -67, -38, 120, -63, -38, 121, -59, -39, 122, -54, -40, + 123, -50, -40, 124, -46, -41, 125, -42, -42, 126, -38, -42, + 127, -34, -43, 127, -30, -44, 128, -26, -44, 129, -22, -45, + 130, -17, -46, 131, -13, -46, 132, -9, -47, 133, -5, -48, + 134, -1, -48, 135, 2, -49, 136, 6, -50, 137, 10, -50, + 138, 15, -51, 139, 19, -52, 140, 23, -52, 141, 27, -53, + 141, 31, -54, 143, 35, -54, 143, 39, -55, 144, 43, -56, + 145, 47, -56, 146, 52, -57, 147, 56, -58, 148, 60, -58, + 121, -68, -39, 122, -64, -40, 123, -60, -41, 124, -56, -41, + 125, -52, -42, 126, -48, -43, 127, -44, -43, 128, -39, -44, + 129, -35, -45, 130, -31, -45, 131, -27, -46, 132, -23, -47, + 133, -19, -47, 134, -15, -48, 134, -11, -49, 135, -7, -49, + 136, -2, -50, 137, 1, -51, 138, 5, -51, 139, 9, -52, + 140, 13, -53, 141, 17, -53, 142, 21, -54, 143, 25, -55, + 144, 29, -55, 145, 34, -56, 146, 38, -57, 147, 42, -57, + 148, 46, -58, 149, 50, -59, 150, 54, -59, 150, 58, -60, + 124, -70, -41, 125, -66, -42, 126, -62, -42, 127, -57, -43, + 127, -53, -44, 128, -49, -44, 129, -45, -45, 130, -41, -46, + 131, -37, -46, 132, -33, -47, 133, -29, -48, 134, -25, -48, + 135, -20, -49, 136, -16, -50, 137, -12, -50, 138, -8, -51, + 139, -4, -52, 140, 0, -52, 141, 3, -53, 142, 7, -54, + 143, 12, -54, 143, 16, -55, 144, 20, -56, 145, 24, -56, + 146, 28, -57, 147, 32, -58, 148, 36, -58, 149, 40, -59, + 150, 44, -60, 151, 49, -60, 152, 53, -61, 153, 57, -62, + 126, -71, -43, 127, -67, -43, 128, -63, -44, 129, -58, -45, + 130, -54, -45, 131, -50, -46, 132, -46, -47, 133, -42, -47, + 134, -38, -48, 135, -34, -49, 135, -30, -49, 136, -26, -50, + 137, -21, -51, 138, -17, -51, 139, -13, -52, 140, -9, -53, + 141, -5, -53, 142, -1, -54, 143, 2, -55, 144, 6, -55, + 145, 11, -56, 146, 15, -57, 147, 19, -57, 148, 23, -58, + 149, 27, -59, 150, 31, -59, 150, 35, -60, 151, 39, -61, + 152, 43, -61, 153, 48, -62, 154, 52, -63, 155, 56, -63, + 128, -72, -44, 129, -68, -45, 130, -64, -46, 131, -60, -46, + 132, -56, -47, 133, -52, -48, 134, -48, -48, 135, -43, -49, + 136, -39, -50, 137, -35, -50, 138, -31, -51, 139, -27, -52, + 140, -23, -52, 141, -19, -53, 142, -15, -54, 142, -11, -54, + 143, -6, -55, 144, -2, -56, 145, 1, -56, 146, 5, -57, + 147, 9, -58, 148, 13, -58, 149, 17, -59, 150, 21, -60, + 151, 25, -60, 152, 30, -61, 153, 34, -62, 154, 38, -62, + 155, 42, -63, 156, 46, -64, 157, 50, -64, 157, 54, -65, + 131, -74, -46, 132, -70, -47, 133, -66, -47, 134, -61, -48, + 135, -57, -49, 135, -53, -49, 136, -49, -50, 137, -45, -51, + 138, -41, -51, 139, -37, -52, 140, -33, -53, 141, -29, -53, + 142, -24, -54, 143, -20, -55, 144, -16, -55, 145, -12, -56, + 146, -8, -57, 147, -4, -57, 148, 0, -58, 149, 3, -59, + 150, 8, -59, 150, 12, -60, 151, 16, -61, 152, 20, -61, + 153, 24, -62, 154, 28, -63, 155, 32, -63, 156, 36, -64, + 157, 40, -65, 158, 45, -65, 159, 49, -66, 160, 53, -67, + 133, -75, -48, 134, -71, -48, 135, -67, -49, 136, -62, -50, + 137, -58, -50, 138, -54, -51, 139, -50, -52, 140, -46, -52, + 141, -42, -53, 142, -38, -54, 142, -34, -54, 143, -30, -55, + 144, -25, -56, 145, -21, -56, 146, -17, -57, 147, -13, -58, + 148, -9, -58, 149, -5, -59, 150, -1, -60, 151, 2, -60, + 152, 7, -61, 153, 11, -62, 154, 15, -62, 155, 19, -63, + 156, 23, -64, 157, 27, -64, 158, 31, -65, 158, 35, -66, + 159, 39, -66, 160, 44, -67, 161, 48, -68, 162, 52, -68, + 135, -76, -49, 136, -72, -50, 137, -68, -51, 138, -64, -51, + 139, -60, -52, 140, -56, -53, 141, -52, -53, 142, -47, -54, + 143, -43, -55, 144, -39, -55, 145, -35, -56, 146, -31, -57, + 147, -27, -57, 148, -23, -58, 149, -19, -59, 149, -15, -59, + 151, -10, -60, 151, -6, -61, 152, -2, -61, 153, 1, -62, + 154, 5, -63, 155, 9, -63, 156, 13, -64, 157, 17, -65, + 158, 21, -65, 159, 26, -66, 160, 30, -67, 161, 34, -67, + 162, 38, -68, 163, 42, -69, 164, 46, -69, 165, 50, -70, + 138, -78, -51, 139, -74, -52, 140, -70, -52, 141, -65, -53, + 142, -61, -54, 142, -57, -54, 143, -53, -55, 144, -49, -56, + 145, -45, -56, 146, -41, -57, 147, -37, -58, 148, -33, -58, + 149, -28, -59, 150, -24, -60, 151, -20, -60, 152, -16, -61, + 153, -12, -62, 154, -8, -62, 155, -4, -63, 156, 0, -64, + 157, 4, -64, 158, 8, -65, 158, 12, -66, 159, 16, -66, + 160, 20, -67, 161, 24, -68, 162, 28, -68, 163, 32, -69, + 164, 36, -70, 165, 41, -71, 166, 45, -71, 167, 49, -72, + 140, -79, -53, 141, -75, -53, 142, -71, -54, 143, -66, -55, + 144, -62, -55, 145, -58, -56, 146, -54, -57, 147, -50, -57, + 148, -46, -58, 149, -42, -59, 150, -38, -59, 150, -34, -60, + 151, -29, -61, 152, -25, -61, 153, -21, -62, 154, -17, -63, + 155, -13, -63, 156, -9, -64, 157, -5, -65, 158, -1, -65, + 159, 3, -66, 160, 7, -67, 161, 11, -67, 162, 15, -68, + 163, 19, -69, 164, 23, -70, 165, 27, -70, 165, 31, -71, + 166, 35, -71, 167, 40, -72, 168, 44, -73, 169, 48, -73, + 143, -80, -54, 143, -76, -55, 144, -72, -56, 145, -68, -56, + 146, -64, -57, 147, -60, -58, 148, -56, -58, 149, -51, -59, + 150, -47, -60, 151, -43, -60, 152, -39, -61, 153, -35, -62, + 154, -31, -62, 155, -27, -63, 156, -23, -64, 157, -19, -64, + 158, -14, -65, 158, -10, -66, 159, -6, -66, 160, -2, -67, + 161, 1, -68, 162, 5, -68, 163, 9, -69, 164, 13, -70, + 165, 17, -70, 166, 22, -71, 167, 26, -72, 168, 30, -72, + 169, 34, -73, 170, 38, -74, 171, 42, -75, 172, 46, -75, + 145, -82, -57, 146, -78, -57, 147, -74, -58, 148, -69, -59, + 149, -65, -59, 150, -61, -60, 151, -57, -61, 152, -53, -61, + 153, -49, -62, 154, -45, -63, 155, -41, -63, 156, -37, -64, + 157, -32, -65, 158, -28, -65, 159, -24, -66, 159, -20, -67, + 160, -16, -67, 161, -12, -68, 162, -8, -69, 163, -4, -69, + 164, 0, -70, 165, 4, -71, 166, 8, -71, 167, 12, -72, + 168, 16, -73, 169, 20, -73, 170, 24, -74, 171, 28, -75, + 172, 32, -75, 173, 37, -76, 174, 41, -77, 175, 45, -77, + 148, -83, -58, 149, -79, -59, 150, -75, -59, 151, -71, -60, + 152, -67, -61, 152, -63, -62, 153, -59, -62, 154, -54, -63, + 155, -50, -64, 156, -46, -64, 157, -42, -65, 158, -38, -66, + 159, -34, -66, 160, -30, -67, 161, -26, -68, 162, -22, -68, + 163, -17, -69, 164, -13, -70, 165, -9, -70, 166, -5, -71, + 167, -1, -72, 168, 2, -72, 168, 6, -73, 169, 10, -74, + 170, 14, -74, 171, 19, -75, 172, 23, -76, 173, 27, -76, + 174, 31, -77, 175, 35, -78, 176, 39, -78, 177, 43, -79, + 150, -85, -60, 151, -81, -61, 152, -77, -61, 153, -72, -62, + 154, -68, -63, 155, -64, -63, 156, -60, -64, 157, -56, -65, + 158, -52, -65, 159, -48, -66, 159, -44, -67, 160, -40, -67, + 161, -35, -68, 162, -31, -69, 163, -27, -69, 164, -23, -70, + 165, -19, -71, 166, -15, -71, 167, -11, -72, 168, -7, -73, + 169, -2, -73, 170, 1, -74, 171, 5, -75, 172, 9, -75, + 173, 13, -76, 174, 17, -77, 175, 21, -77, 175, 25, -78, + 176, 29, -79, 177, 34, -79, 178, 38, -80, 179, 42, -81, + 152, -86, -62, 153, -82, -62, 154, -78, -63, 155, -73, -64, + 156, -69, -64, 157, -65, -65, 158, -61, -66, 159, -57, -66, + 160, -53, -67, 161, -49, -68, 162, -45, -68, 163, -41, -69, + 164, -36, -70, 165, -32, -70, 166, -28, -71, 167, -24, -72, + 168, -20, -72, 168, -16, -73, 169, -12, -74, 170, -8, -74, + 171, -3, -75, 172, 0, -76, 173, 4, -76, 174, 8, -77, + 175, 12, -78, 176, 16, -78, 177, 20, -79, 178, 24, -80, + 179, 28, -80, 180, 33, -81, 181, 37, -82, 182, 41, -82, + 155, -87, -63, 156, -83, -64, 157, -79, -65, 158, -75, -65, + 159, -71, -66, 160, -67, -67, 160, -63, -67, 161, -58, -68, + 162, -54, -69, 163, -50, -69, 164, -46, -70, 165, -42, -71, + 166, -38, -71, 167, -34, -72, 168, -30, -73, 169, -26, -73, + 170, -21, -74, 171, -17, -75, 172, -13, -75, 173, -9, -76, + 174, -5, -77, 175, -1, -77, 175, 2, -78, 176, 6, -79, + 177, 10, -79, 178, 15, -80, 179, 19, -81, 180, 23, -81, + 181, 27, -82, 182, 31, -83, 183, 35, -83, 184, 39, -84, + 157, -88, -65, 158, -84, -66, 159, -80, -66, 160, -76, -67, + 161, -72, -68, 162, -68, -68, 163, -64, -69, 164, -59, -70, + 165, -55, -70, 166, -51, -71, 167, -47, -72, 167, -43, -72, + 168, -39, -73, 169, -35, -74, 170, -31, -74, 171, -27, -75, + 172, -22, -76, 173, -18, -76, 174, -14, -77, 175, -10, -78, + 176, -6, -78, 177, -2, -79, 178, 1, -80, 179, 5, -80, + 180, 9, -81, 181, 14, -82, 182, 18, -82, 182, 22, -83, + 183, 26, -84, 184, 30, -84, 185, 34, -85, 186, 38, -86, + 160, -90, -67, 160, -86, -67, 161, -82, -68, 162, -77, -69, + 163, -73, -69, 164, -69, -70, 165, -65, -71, 166, -61, -71, + 167, -57, -72, 168, -53, -73, 169, -49, -73, 170, -45, -74, + 171, -40, -75, 172, -36, -75, 173, -32, -76, 174, -28, -77, + 175, -24, -77, 175, -20, -78, 176, -16, -79, 177, -12, -79, + 178, -7, -80, 179, -3, -81, 180, 0, -81, 181, 4, -82, + 182, 8, -83, 183, 12, -83, 184, 16, -84, 185, 20, -85, + 186, 24, -85, 187, 29, -86, 188, 33, -87, 189, 37, -87, + 162, -91, -68, 163, -87, -69, 164, -83, -70, 165, -79, -70, + 166, -75, -71, 167, -71, -72, 167, -67, -72, 168, -62, -73, + 169, -58, -74, 170, -54, -74, 171, -50, -75, 172, -46, -76, + 173, -42, -76, 174, -38, -77, 175, -34, -78, 176, -30, -78, + 177, -25, -79, 178, -21, -80, 179, -17, -80, 180, -13, -81, + 181, -9, -82, 182, -5, -82, 183, -1, -83, 183, 2, -84, + 184, 6, -84, 185, 11, -85, 186, 15, -86, 187, 19, -86, + 188, 23, -87, 189, 27, -88, 190, 31, -88, 191, 35, -89, + 164, -92, -70, 165, -88, -71, 166, -84, -71, 167, -80, -72, + 168, -76, -73, 169, -72, -73, 170, -68, -74, 171, -63, -75, + 172, -59, -75, 173, -55, -76, 174, -51, -77, 174, -47, -77, + 176, -43, -78, 176, -39, -79, 177, -35, -79, 178, -31, -80, + 179, -26, -81, 180, -22, -81, 181, -18, -82, 182, -14, -83, + 183, -10, -83, 184, -6, -84, 185, -2, -85, 186, 1, -85, + 187, 5, -86, 188, 10, -87, 189, 14, -87, 190, 18, -88, + 190, 22, -89, 191, 26, -89, 192, 30, -90, 193, 34, -91, + 167, -94, -72, 167, -90, -72, 168, -86, -73, 169, -81, -74, + 170, -77, -74, 171, -73, -75, 172, -69, -76, 173, -65, -76, + 174, -61, -77, 175, -57, -78, 176, -53, -78, 177, -49, -79, + 178, -44, -80, 179, -40, -80, 180, -36, -81, 181, -32, -82, + 182, -28, -82, 183, -24, -83, 183, -20, -84, 184, -16, -84, + 185, -11, -85, 186, -7, -86, 187, -3, -86, 188, 0, -87, + 189, 4, -88, 190, 8, -88, 191, 12, -89, 192, 16, -90, + 193, 20, -90, 194, 25, -91, 195, 29, -92, 196, 33, -92, + 169, -95, -73, 170, -91, -74, 171, -87, -75, 172, -83, -75, + 173, -79, -76, 174, -75, -77, 175, -71, -77, 176, -66, -78, + 176, -62, -79, 177, -58, -79, 178, -54, -80, 179, -50, -81, + 180, -46, -81, 181, -42, -82, 182, -38, -83, 183, -34, -83, + 184, -29, -84, 185, -25, -85, 186, -21, -85, 187, -17, -86, + 188, -13, -87, 189, -9, -87, 190, -5, -88, 190, -1, -89, + 191, 2, -89, 192, 7, -90, 193, 11, -91, 194, 15, -91, + 195, 19, -92, 196, 23, -93, 197, 27, -93, 198, 31, -94, + 22, -12, 37, 23, -8, 36, 23, -4, 35, 24, 0, 34, + 25, 4, 34, 26, 8, 33, 27, 12, 33, 28, 16, 32, + 29, 20, 31, 30, 24, 30, 31, 28, 30, 32, 32, 29, + 33, 37, 28, 34, 41, 28, 35, 45, 27, 36, 49, 26, + 37, 53, 26, 38, 57, 25, 38, 61, 24, 39, 65, 24, + 40, 70, 23, 41, 74, 22, 42, 78, 22, 43, 82, 21, + 44, 86, 20, 45, 90, 20, 46, 94, 19, 47, 98, 18, + 48, 102, 18, 49, 107, 17, 50, 111, 16, 51, 115, 16, + 24, -13, 35, 25, -9, 34, 26, -5, 34, 27, -1, 33, + 28, 2, 32, 29, 6, 31, 30, 10, 31, 31, 15, 30, + 31, 19, 29, 32, 23, 29, 33, 27, 28, 34, 31, 28, + 35, 35, 27, 36, 39, 26, 37, 43, 25, 38, 47, 25, + 39, 52, 24, 40, 56, 23, 41, 60, 23, 42, 64, 22, + 43, 68, 21, 44, 72, 21, 45, 76, 20, 46, 80, 19, + 46, 84, 19, 47, 89, 18, 48, 93, 17, 49, 97, 17, + 50, 101, 16, 51, 105, 15, 52, 109, 15, 53, 113, 14, + 26, -15, 33, 27, -11, 33, 28, -7, 32, 29, -2, 31, + 30, 1, 30, 31, 5, 30, 32, 9, 29, 33, 13, 28, + 34, 17, 28, 35, 21, 27, 36, 25, 26, 37, 29, 26, + 38, 34, 25, 39, 38, 24, 39, 42, 24, 40, 46, 23, + 41, 50, 22, 42, 54, 22, 43, 58, 21, 44, 62, 20, + 45, 67, 20, 46, 71, 19, 47, 75, 18, 48, 79, 18, + 49, 83, 17, 50, 87, 16, 51, 91, 16, 52, 95, 15, + 53, 99, 14, 54, 104, 14, 54, 108, 13, 55, 112, 12, + 29, -16, 31, 30, -12, 31, 30, -8, 30, 32, -3, 29, + 32, 0, 29, 33, 4, 28, 34, 8, 27, 35, 12, 27, + 36, 16, 26, 37, 20, 25, 38, 24, 25, 39, 28, 24, + 40, 33, 23, 41, 37, 23, 42, 41, 22, 43, 45, 21, + 44, 49, 21, 45, 53, 20, 46, 57, 19, 46, 61, 19, + 47, 66, 18, 48, 70, 17, 49, 74, 17, 50, 78, 16, + 51, 82, 15, 52, 86, 15, 53, 90, 14, 54, 94, 13, + 55, 98, 13, 56, 103, 12, 57, 107, 11, 58, 111, 11, + 31, -17, 30, 32, -13, 29, 33, -9, 29, 34, -5, 28, + 35, -1, 27, 36, 2, 26, 37, 6, 26, 38, 11, 25, + 39, 15, 24, 39, 19, 24, 40, 23, 23, 41, 27, 22, + 42, 31, 22, 43, 35, 21, 44, 39, 20, 45, 43, 20, + 46, 48, 19, 47, 52, 18, 48, 56, 18, 49, 60, 17, + 50, 64, 16, 51, 68, 16, 52, 72, 15, 53, 76, 14, + 53, 80, 14, 55, 85, 13, 55, 89, 12, 56, 93, 12, + 57, 97, 11, 58, 101, 10, 59, 105, 10, 60, 109, 9, + 33, -19, 28, 34, -15, 27, 35, -11, 27, 36, -6, 26, + 37, -2, 25, 38, 1, 25, 39, 5, 24, 40, 9, 23, + 41, 13, 23, 42, 17, 22, 43, 21, 21, 44, 25, 21, + 45, 30, 20, 46, 34, 19, 46, 38, 19, 47, 42, 18, + 48, 46, 17, 49, 50, 17, 50, 54, 16, 51, 58, 15, + 52, 63, 15, 53, 67, 14, 54, 71, 13, 55, 75, 13, + 56, 79, 12, 57, 83, 11, 58, 87, 11, 59, 91, 10, + 60, 95, 9, 61, 100, 9, 62, 104, 8, 62, 108, 7, + 36, -20, 26, 37, -16, 26, 38, -12, 25, 39, -7, 24, + 39, -3, 24, 40, 0, 23, 41, 4, 22, 42, 8, 22, + 43, 12, 21, 44, 16, 20, 45, 20, 20, 46, 24, 19, + 47, 29, 18, 48, 33, 18, 49, 37, 17, 50, 41, 16, + 51, 45, 16, 52, 49, 15, 53, 53, 14, 53, 57, 14, + 55, 62, 13, 55, 66, 12, 56, 70, 12, 57, 74, 11, + 58, 78, 10, 59, 82, 10, 60, 86, 9, 61, 90, 8, + 62, 94, 8, 63, 99, 7, 64, 103, 6, 65, 107, 6, + 38, -21, 25, 39, -17, 24, 40, -13, 23, 41, -9, 23, + 42, -5, 22, 43, -1, 21, 44, 2, 21, 45, 7, 20, + 46, 11, 19, 46, 15, 19, 47, 19, 18, 48, 23, 17, + 49, 27, 17, 50, 31, 16, 51, 35, 15, 52, 39, 15, + 53, 44, 14, 54, 48, 13, 55, 52, 13, 56, 56, 12, + 57, 60, 11, 58, 64, 11, 59, 68, 10, 60, 72, 9, + 61, 76, 9, 62, 81, 8, 62, 85, 7, 63, 89, 7, + 64, 93, 6, 65, 97, 5, 66, 101, 5, 67, 105, 4, + 40, -23, 23, 41, -19, 22, 42, -15, 22, 43, -10, 21, + 44, -6, 20, 45, -2, 20, 46, 1, 19, 47, 5, 18, + 48, 9, 18, 49, 13, 17, 50, 17, 16, 51, 21, 16, + 52, 26, 15, 53, 30, 14, 54, 34, 14, 54, 38, 13, + 55, 42, 12, 56, 46, 12, 57, 50, 11, 58, 54, 10, + 59, 59, 10, 60, 63, 9, 61, 67, 8, 62, 71, 8, + 63, 75, 7, 64, 79, 6, 65, 83, 6, 66, 87, 5, + 67, 91, 4, 68, 96, 4, 69, 100, 3, 69, 104, 2, + 43, -24, 21, 44, -20, 21, 45, -16, 20, 46, -11, 19, + 47, -7, 19, 47, -3, 18, 48, 0, 17, 49, 4, 17, + 50, 8, 16, 51, 12, 15, 52, 16, 15, 53, 20, 14, + 54, 25, 13, 55, 29, 13, 56, 33, 12, 57, 37, 11, + 58, 41, 11, 59, 45, 10, 60, 49, 9, 61, 53, 9, + 62, 58, 8, 62, 62, 7, 63, 66, 7, 64, 70, 6, + 65, 74, 5, 66, 78, 5, 67, 82, 4, 68, 86, 3, + 69, 90, 3, 70, 95, 2, 71, 99, 1, 72, 103, 1, + 45, -25, 20, 46, -21, 19, 47, -17, 18, 48, -13, 18, + 49, -9, 17, 50, -5, 16, 51, -1, 16, 52, 3, 15, + 53, 7, 14, 54, 11, 14, 54, 15, 13, 55, 19, 12, + 56, 23, 12, 57, 27, 11, 58, 31, 10, 59, 35, 10, + 60, 40, 9, 61, 44, 8, 62, 48, 8, 63, 52, 7, + 64, 56, 6, 65, 60, 6, 66, 64, 5, 67, 68, 4, + 68, 72, 4, 69, 77, 3, 70, 81, 2, 70, 85, 2, + 71, 89, 1, 72, 93, 0, 73, 97, 0, 74, 101, 0, + 48, -27, 18, 49, -23, 17, 50, -19, 16, 51, -14, 16, + 52, -10, 15, 53, -6, 14, 54, -2, 14, 55, 1, 13, + 56, 5, 12, 56, 9, 12, 57, 13, 11, 58, 17, 10, + 59, 22, 10, 60, 26, 9, 61, 30, 8, 62, 34, 8, + 63, 38, 7, 64, 42, 6, 65, 46, 6, 66, 50, 5, + 67, 55, 4, 68, 59, 4, 69, 63, 3, 70, 67, 2, + 70, 71, 2, 72, 75, 1, 72, 79, 0, 73, 83, 0, + 74, 87, 0, 75, 92, -1, 76, 96, -1, 77, 100, -2, + 50, -28, 16, 51, -24, 15, 52, -20, 15, 53, -16, 14, + 54, -12, 13, 55, -8, 13, 56, -4, 12, 57, 0, 11, + 58, 4, 11, 59, 8, 10, 60, 12, 9, 61, 16, 9, + 62, 20, 8, 63, 24, 7, 63, 28, 7, 64, 32, 6, + 65, 37, 5, 66, 41, 5, 67, 45, 4, 68, 49, 3, + 69, 53, 3, 70, 57, 2, 71, 61, 1, 72, 65, 1, + 73, 69, 0, 74, 74, 0, 75, 78, 0, 76, 82, -1, + 77, 86, -2, 78, 90, -2, 79, 94, -3, 79, 98, -4, + 53, -30, 14, 54, -26, 14, 55, -22, 13, 56, -17, 12, + 56, -13, 12, 57, -9, 11, 58, -5, 10, 59, -1, 10, + 60, 2, 9, 61, 6, 8, 62, 10, 8, 63, 14, 7, + 64, 19, 6, 65, 23, 6, 66, 27, 5, 67, 31, 4, + 68, 35, 4, 69, 39, 3, 70, 43, 2, 71, 47, 2, + 72, 52, 1, 72, 56, 0, 73, 60, 0, 74, 64, 0, + 75, 68, -1, 76, 72, -1, 77, 76, -2, 78, 80, -3, + 79, 84, -3, 80, 89, -4, 81, 93, -5, 82, 97, -5, + 55, -31, 13, 56, -27, 12, 57, -23, 11, 58, -18, 11, + 59, -14, 10, 60, -10, 9, 61, -6, 9, 62, -2, 8, + 63, 1, 7, 64, 5, 7, 64, 9, 6, 65, 13, 5, + 66, 18, 5, 67, 22, 4, 68, 26, 3, 69, 30, 3, + 70, 34, 2, 71, 38, 1, 72, 42, 1, 73, 46, 0, + 74, 51, 0, 75, 55, 0, 76, 59, -1, 77, 63, -2, + 78, 67, -2, 79, 71, -3, 79, 75, -4, 80, 79, -4, + 81, 83, -5, 82, 88, -6, 83, 92, -6, 84, 96, -7, + 57, -32, 11, 58, -28, 10, 59, -24, 10, 60, -20, 9, + 61, -16, 8, 62, -12, 8, 63, -8, 7, 64, -3, 6, + 65, 0, 6, 66, 4, 5, 67, 8, 4, 68, 12, 4, + 69, 16, 3, 70, 20, 2, 71, 24, 2, 71, 28, 1, + 72, 33, 0, 73, 37, 0, 74, 41, 0, 75, 45, -1, + 76, 49, -1, 77, 53, -2, 78, 57, -3, 79, 61, -3, + 80, 65, -4, 81, 70, -5, 82, 74, -5, 83, 78, -6, + 84, 82, -7, 85, 86, -7, 86, 90, -8, 87, 94, -9, + 60, -34, 9, 61, -30, 9, 62, -26, 8, 63, -21, 7, + 64, -17, 7, 64, -13, 6, 65, -9, 5, 66, -5, 5, + 67, -1, 4, 68, 2, 3, 69, 6, 3, 70, 10, 2, + 71, 15, 1, 72, 19, 1, 73, 23, 0, 74, 27, 0, + 75, 31, 0, 76, 35, -1, 77, 39, -2, 78, 43, -2, + 79, 48, -3, 80, 52, -4, 80, 56, -4, 81, 60, -5, + 82, 64, -6, 83, 68, -6, 84, 72, -7, 85, 76, -8, + 86, 80, -8, 87, 85, -9, 88, 89, -10, 89, 93, -10, + 62, -35, 8, 63, -31, 7, 64, -27, 6, 65, -22, 6, + 66, -18, 5, 67, -14, 4, 68, -10, 4, 69, -6, 3, + 70, -2, 2, 71, 1, 2, 71, 5, 1, 72, 9, 0, + 73, 14, 0, 74, 18, 0, 75, 22, -1, 76, 26, -1, + 77, 30, -2, 78, 34, -3, 79, 38, -3, 80, 42, -4, + 81, 47, -5, 82, 51, -5, 83, 55, -6, 84, 59, -7, + 85, 63, -7, 86, 67, -8, 87, 71, -9, 87, 75, -9, + 88, 79, -10, 89, 84, -11, 90, 88, -11, 91, 92, -12, + 64, -36, 6, 65, -32, 5, 66, -28, 5, 67, -24, 4, + 68, -20, 3, 69, -16, 3, 70, -12, 2, 71, -7, 1, + 72, -3, 1, 73, 0, 0, 74, 4, 0, 75, 8, 0, + 76, 12, -1, 77, 16, -2, 78, 20, -2, 78, 24, -3, + 80, 29, -4, 80, 33, -4, 81, 37, -5, 82, 41, -6, + 83, 45, -6, 84, 49, -7, 85, 53, -8, 86, 57, -8, + 87, 61, -9, 88, 66, -10, 89, 70, -10, 90, 74, -11, + 91, 78, -12, 92, 82, -12, 93, 86, -13, 94, 90, -14, + 67, -37, 4, 68, -33, 4, 69, -29, 3, 70, -25, 2, + 71, -21, 2, 71, -17, 1, 72, -13, 0, 73, -8, 0, + 74, -4, 0, 75, 0, -1, 76, 3, -1, 77, 7, -2, + 78, 11, -3, 79, 15, -3, 80, 19, -4, 81, 23, -5, + 82, 28, -5, 83, 32, -6, 84, 36, -7, 85, 40, -7, + 86, 44, -8, 87, 48, -9, 87, 52, -9, 88, 56, -10, + 89, 60, -11, 90, 65, -11, 91, 69, -12, 92, 73, -13, + 93, 77, -13, 94, 81, -14, 95, 85, -15, 96, 89, -15, + 69, -39, 3, 70, -35, 2, 71, -31, 1, 72, -26, 1, + 73, -22, 0, 74, -18, 0, 75, -14, 0, 76, -10, -1, + 77, -6, -2, 78, -2, -2, 79, 1, -3, 79, 5, -4, + 80, 10, -4, 81, 14, -5, 82, 18, -6, 83, 22, -6, + 84, 26, -7, 85, 30, -8, 86, 34, -8, 87, 38, -9, + 88, 43, -10, 89, 47, -10, 90, 51, -11, 91, 55, -12, + 92, 59, -12, 93, 63, -13, 94, 67, -14, 94, 71, -14, + 95, 75, -15, 96, 80, -16, 97, 84, -16, 98, 88, -17, + 72, -40, 1, 72, -36, 0, 73, -32, 0, 74, -28, 0, + 75, -24, -1, 76, -20, -1, 77, -16, -2, 78, -11, -3, + 79, -7, -3, 80, -3, -4, 81, 0, -5, 82, 4, -5, + 83, 8, -6, 84, 12, -7, 85, 16, -7, 86, 20, -8, + 87, 25, -9, 87, 29, -9, 88, 33, -10, 89, 37, -11, + 90, 41, -12, 91, 45, -12, 92, 49, -13, 93, 53, -13, + 94, 57, -14, 95, 62, -15, 96, 66, -15, 97, 70, -16, + 98, 74, -17, 99, 78, -18, 100, 82, -18, 101, 86, -19, + 74, -41, 0, 75, -37, 0, 76, -33, -1, 77, -29, -2, + 78, -25, -2, 79, -21, -3, 79, -17, -4, 80, -12, -4, + 81, -8, -5, 82, -4, -6, 83, 0, -6, 84, 3, -7, + 85, 7, -8, 86, 11, -8, 87, 15, -9, 88, 19, -10, + 89, 24, -10, 90, 28, -11, 91, 32, -12, 92, 36, -12, + 93, 40, -13, 94, 44, -14, 95, 48, -14, 95, 52, -15, + 96, 56, -16, 97, 61, -17, 98, 65, -17, 99, 69, -18, + 100, 73, -18, 101, 77, -19, 102, 81, -20, 103, 85, -20, + 76, -43, -1, 77, -39, -2, 78, -35, -3, 79, -30, -3, + 80, -26, -4, 81, -22, -5, 82, -18, -5, 83, -14, -6, + 84, -10, -7, 85, -6, -7, 86, -2, -8, 86, 1, -9, + 88, 6, -9, 88, 10, -10, 89, 14, -11, 90, 18, -11, + 91, 22, -12, 92, 26, -13, 93, 30, -13, 94, 34, -14, + 95, 39, -15, 96, 43, -16, 97, 47, -16, 98, 51, -17, + 99, 55, -17, 100, 59, -18, 101, 63, -19, 102, 67, -19, + 102, 71, -20, 103, 76, -21, 104, 80, -22, 105, 84, -22, + 79, -44, -3, 79, -40, -4, 80, -36, -4, 81, -32, -5, + 82, -28, -6, 83, -24, -6, 84, -20, -7, 85, -15, -8, + 86, -11, -8, 87, -7, -9, 88, -3, -10, 89, 0, -10, + 90, 4, -11, 91, 8, -12, 92, 12, -12, 93, 16, -13, + 94, 21, -14, 95, 25, -14, 95, 29, -15, 96, 33, -16, + 97, 37, -17, 98, 41, -17, 99, 45, -18, 100, 49, -18, + 101, 53, -19, 102, 58, -20, 103, 62, -21, 104, 66, -21, + 105, 70, -22, 106, 74, -23, 107, 78, -23, 108, 82, -24, + 81, -45, -5, 82, -41, -5, 83, -37, -6, 84, -33, -7, + 85, -29, -7, 86, -25, -8, 86, -21, -9, 88, -16, -10, + 88, -12, -10, 89, -8, -11, 90, -4, -11, 91, 0, -12, + 92, 3, -13, 93, 7, -13, 94, 11, -14, 95, 15, -15, + 96, 20, -16, 97, 24, -16, 98, 28, -17, 99, 32, -17, + 100, 36, -18, 101, 40, -19, 102, 44, -20, 102, 48, -20, + 103, 52, -21, 104, 57, -22, 105, 61, -22, 106, 65, -23, + 107, 69, -23, 108, 73, -24, 109, 77, -25, 110, 81, -26, + 83, -47, -6, 84, -43, -7, 85, -39, -8, 86, -34, -8, + 87, -30, -9, 88, -26, -10, 89, -22, -10, 90, -18, -11, + 91, -14, -12, 92, -10, -12, 93, -6, -13, 94, -2, -14, + 95, 2, -15, 95, 6, -15, 96, 10, -16, 97, 14, -16, + 98, 18, -17, 99, 22, -18, 100, 26, -18, 101, 30, -19, + 102, 35, -20, 103, 39, -21, 104, 43, -21, 105, 47, -22, + 106, 51, -22, 107, 55, -23, 108, 59, -24, 109, 63, -25, + 109, 67, -25, 111, 72, -26, 111, 76, -27, 112, 80, -27, + 86, -48, -8, 87, -44, -9, 87, -40, -9, 88, -36, -10, + 89, -32, -11, 90, -28, -11, 91, -24, -12, 92, -19, -13, + 93, -15, -14, 94, -11, -14, 95, -7, -15, 96, -3, -15, + 97, 0, -16, 98, 4, -17, 99, 8, -17, 100, 12, -18, + 101, 17, -19, 102, 21, -20, 102, 25, -20, 103, 29, -21, + 104, 33, -22, 105, 37, -22, 106, 41, -23, 107, 45, -24, + 108, 49, -24, 109, 54, -25, 110, 58, -26, 111, 62, -26, + 112, 66, -27, 113, 70, -28, 114, 74, -28, 115, 78, -29, + 88, -49, -10, 89, -45, -10, 90, -41, -11, 91, -37, -12, + 92, -33, -12, 93, -29, -13, 94, -25, -14, 95, -20, -15, + 95, -16, -15, 96, -12, -16, 97, -8, -16, 98, -4, -17, + 99, 0, -18, 100, 3, -19, 101, 7, -19, 102, 11, -20, + 103, 16, -21, 104, 20, -21, 105, 24, -22, 106, 28, -22, + 107, 32, -23, 108, 36, -24, 109, 40, -25, 110, 44, -25, + 110, 48, -26, 111, 53, -27, 112, 57, -27, 113, 61, -28, + 114, 65, -29, 115, 69, -29, 116, 73, -30, 117, 77, -31, + 90, -51, -11, 91, -47, -12, 92, -43, -13, 93, -38, -14, + 94, -34, -14, 95, -30, -15, 96, -26, -15, 97, -22, -16, + 98, -18, -17, 99, -14, -18, 100, -10, -18, 101, -6, -19, + 102, -1, -20, 103, 2, -20, 103, 6, -21, 104, 10, -21, + 105, 14, -22, 106, 18, -23, 107, 22, -24, 108, 26, -24, + 109, 31, -25, 110, 35, -26, 111, 39, -26, 112, 43, -27, + 113, 47, -28, 114, 51, -28, 115, 55, -29, 116, 59, -30, + 117, 63, -30, 118, 68, -31, 118, 72, -32, 119, 76, -32, + 93, -52, -13, 94, -48, -14, 94, -44, -14, 96, -40, -15, + 96, -36, -16, 97, -32, -16, 98, -28, -17, 99, -23, -18, + 100, -19, -19, 101, -15, -19, 102, -11, -20, 103, -7, -20, + 104, -3, -21, 105, 0, -22, 106, 4, -23, 107, 8, -23, + 108, 13, -24, 109, 17, -25, 110, 21, -25, 110, 25, -26, + 111, 29, -27, 112, 33, -27, 113, 37, -28, 114, 41, -29, + 115, 45, -29, 116, 50, -30, 117, 54, -31, 118, 58, -31, + 119, 62, -32, 120, 66, -33, 121, 70, -33, 122, 74, -34, + 95, -53, -15, 96, -49, -15, 97, -45, -16, 98, -41, -17, + 99, -37, -18, 100, -33, -18, 101, -29, -19, 102, -24, -20, + 103, -20, -20, 103, -16, -21, 104, -12, -22, 105, -8, -22, + 106, -4, -23, 107, 0, -24, 108, 3, -24, 109, 7, -25, + 110, 12, -26, 111, 16, -26, 112, 20, -27, 113, 24, -28, + 114, 28, -28, 115, 32, -29, 116, 36, -30, 117, 40, -30, + 117, 44, -31, 118, 49, -32, 119, 53, -32, 120, 57, -33, + 121, 61, -34, 122, 65, -34, 123, 69, -35, 124, 73, -36, + 98, -55, -17, 99, -51, -18, 100, -47, -18, 101, -43, -19, + 102, -39, -20, 103, -35, -20, 104, -31, -21, 105, -26, -22, + 105, -22, -22, 106, -18, -23, 107, -14, -24, 108, -10, -24, + 109, -6, -25, 110, -2, -26, 111, 1, -26, 112, 5, -27, + 113, 10, -28, 114, 14, -28, 115, 18, -29, 116, 22, -30, + 117, 26, -30, 118, 30, -31, 119, 34, -32, 119, 38, -32, + 120, 42, -33, 121, 47, -34, 122, 51, -34, 123, 55, -35, + 124, 59, -36, 125, 63, -36, 126, 67, -37, 127, 71, -38, + 100, -56, -19, 101, -52, -19, 102, -48, -20, 103, -44, -21, + 104, -40, -21, 105, -36, -22, 106, -32, -23, 107, -27, -23, + 108, -23, -24, 109, -19, -25, 110, -15, -25, 111, -11, -26, + 112, -7, -27, 112, -3, -27, 113, 0, -28, 114, 4, -29, + 115, 9, -29, 116, 13, -30, 117, 17, -31, 118, 21, -31, + 119, 25, -32, 120, 29, -33, 121, 33, -33, 122, 37, -34, + 123, 41, -35, 124, 46, -35, 125, 50, -36, 126, 54, -37, + 127, 58, -37, 128, 62, -38, 128, 66, -39, 129, 70, -39, + 103, -58, -20, 104, -54, -21, 104, -50, -22, 105, -45, -22, + 106, -41, -23, 107, -37, -24, 108, -33, -24, 109, -29, -25, + 110, -25, -26, 111, -21, -26, 112, -17, -27, 113, -13, -28, + 114, -8, -28, 115, -4, -29, 116, 0, -30, 117, 3, -30, + 118, 7, -31, 119, 11, -32, 120, 15, -32, 120, 19, -33, + 121, 24, -34, 122, 28, -34, 123, 32, -35, 124, 36, -36, + 125, 40, -36, 126, 44, -37, 127, 48, -38, 128, 52, -38, + 129, 56, -39, 130, 61, -40, 131, 65, -40, 132, 69, -41, + 105, -59, -22, 106, -55, -23, 107, -51, -23, 108, -47, -24, + 109, -43, -25, 110, -39, -25, 111, -35, -26, 112, -30, -27, + 113, -26, -27, 113, -22, -28, 114, -18, -29, 115, -14, -29, + 116, -10, -30, 117, -6, -31, 118, -2, -31, 119, 1, -32, + 120, 6, -33, 121, 10, -33, 122, 14, -34, 123, 18, -35, + 124, 22, -35, 125, 26, -36, 126, 30, -37, 127, 34, -37, + 127, 38, -38, 128, 43, -39, 129, 47, -39, 130, 51, -40, + 131, 55, -41, 132, 59, -41, 133, 63, -42, 134, 67, -43, + 107, -60, -24, 108, -56, -24, 109, -52, -25, 110, -48, -26, + 111, -44, -26, 112, -40, -27, 113, -36, -28, 114, -31, -28, + 115, -27, -29, 116, -23, -30, 117, -19, -30, 118, -15, -31, + 119, -11, -32, 120, -7, -32, 120, -3, -33, 121, 0, -34, + 122, 5, -34, 123, 9, -35, 124, 13, -36, 125, 17, -36, + 126, 21, -37, 127, 25, -38, 128, 29, -38, 129, 33, -39, + 130, 37, -40, 131, 42, -40, 132, 46, -41, 133, 50, -42, + 134, 54, -42, 135, 58, -43, 135, 62, -44, 136, 66, -44, + 110, -62, -25, 111, -58, -26, 111, -54, -27, 113, -49, -27, + 113, -45, -28, 114, -41, -29, 115, -37, -29, 116, -33, -30, + 117, -29, -31, 118, -25, -31, 119, -21, -32, 120, -17, -33, + 121, -12, -33, 122, -8, -34, 123, -4, -35, 124, 0, -35, + 125, 3, -36, 126, 7, -37, 127, 11, -37, 127, 15, -38, + 128, 20, -39, 129, 24, -39, 130, 28, -40, 131, 32, -41, + 132, 36, -41, 133, 40, -42, 134, 44, -43, 135, 48, -43, + 136, 52, -44, 137, 57, -45, 138, 61, -45, 139, 65, -46, + 112, -63, -27, 113, -59, -28, 114, -55, -28, 115, -51, -29, + 116, -47, -30, 117, -43, -30, 118, -39, -31, 119, -34, -32, + 120, -30, -32, 120, -26, -33, 121, -22, -34, 122, -18, -34, + 123, -14, -35, 124, -10, -36, 125, -6, -36, 126, -2, -37, + 127, 2, -38, 128, 6, -38, 129, 10, -39, 130, 14, -40, + 131, 18, -40, 132, 22, -41, 133, 26, -42, 134, 30, -42, + 134, 34, -43, 136, 39, -44, 136, 43, -44, 137, 47, -45, + 138, 51, -46, 139, 55, -46, 140, 59, -47, 141, 63, -48, + 114, -64, -29, 115, -60, -29, 116, -56, -30, 117, -52, -31, + 118, -48, -31, 119, -44, -32, 120, -40, -33, 121, -35, -33, + 122, -31, -34, 123, -27, -35, 124, -23, -35, 125, -19, -36, + 126, -15, -37, 127, -11, -37, 127, -7, -38, 128, -3, -39, + 129, 1, -39, 130, 5, -40, 131, 9, -41, 132, 13, -41, + 133, 17, -42, 134, 21, -43, 135, 25, -43, 136, 29, -44, + 137, 33, -45, 138, 38, -45, 139, 42, -46, 140, 46, -47, + 141, 50, -47, 142, 54, -48, 143, 58, -49, 143, 62, -49, + 117, -66, -30, 118, -62, -31, 119, -58, -32, 120, -53, -32, + 120, -49, -33, 121, -45, -34, 122, -41, -34, 123, -37, -35, + 124, -33, -36, 125, -29, -36, 126, -25, -37, 127, -21, -38, + 128, -16, -38, 129, -12, -39, 130, -8, -40, 131, -4, -40, + 132, 0, -41, 133, 3, -42, 134, 7, -42, 135, 11, -43, + 136, 16, -44, 136, 20, -44, 137, 24, -45, 138, 28, -46, + 139, 32, -46, 140, 36, -47, 141, 40, -48, 142, 44, -48, + 143, 48, -49, 144, 53, -50, 145, 57, -50, 146, 61, -51, + 119, -67, -32, 120, -63, -33, 121, -59, -33, 122, -54, -34, + 123, -50, -35, 124, -46, -35, 125, -42, -36, 126, -38, -37, + 127, -34, -37, 128, -30, -38, 128, -26, -39, 129, -22, -39, + 130, -17, -40, 131, -13, -41, 132, -9, -41, 133, -5, -42, + 134, -1, -43, 135, 2, -43, 136, 6, -44, 137, 10, -45, + 138, 15, -45, 139, 19, -46, 140, 23, -47, 141, 27, -47, + 142, 31, -48, 143, 35, -49, 143, 39, -49, 144, 43, -50, + 145, 47, -51, 146, 52, -51, 147, 56, -52, 148, 60, -53, + 121, -68, -34, 122, -64, -34, 123, -60, -35, 124, -56, -36, + 125, -52, -36, 126, -48, -37, 127, -44, -38, 128, -39, -38, + 129, -35, -39, 130, -31, -40, 131, -27, -40, 132, -23, -41, + 133, -19, -42, 134, -15, -42, 135, -11, -43, 135, -7, -44, + 136, -2, -44, 137, 1, -45, 138, 5, -46, 139, 9, -46, + 140, 13, -47, 141, 17, -48, 142, 21, -48, 143, 25, -49, + 144, 29, -50, 145, 34, -50, 146, 38, -51, 147, 42, -52, + 148, 46, -52, 149, 50, -53, 150, 54, -54, 150, 58, -54, + 124, -70, -35, 125, -66, -36, 126, -62, -37, 127, -57, -37, + 128, -53, -38, 128, -49, -39, 129, -45, -39, 130, -41, -40, + 131, -37, -41, 132, -33, -41, 133, -29, -42, 134, -25, -43, + 135, -20, -43, 136, -16, -44, 137, -12, -45, 138, -8, -45, + 139, -4, -46, 140, 0, -47, 141, 3, -47, 142, 7, -48, + 143, 12, -49, 143, 16, -49, 144, 20, -50, 145, 24, -51, + 146, 28, -51, 147, 32, -52, 148, 36, -53, 149, 40, -53, + 150, 44, -54, 151, 49, -55, 152, 53, -55, 153, 57, -56, + 126, -71, -37, 127, -67, -38, 128, -63, -38, 129, -58, -39, + 130, -54, -40, 131, -50, -40, 132, -46, -41, 133, -42, -42, + 134, -38, -42, 135, -34, -43, 135, -30, -44, 136, -26, -44, + 137, -21, -45, 138, -17, -46, 139, -13, -46, 140, -9, -47, + 141, -5, -48, 142, -1, -48, 143, 2, -49, 144, 6, -50, + 145, 11, -50, 146, 15, -51, 147, 19, -52, 148, 23, -52, + 149, 27, -53, 150, 31, -54, 151, 35, -54, 151, 39, -55, + 152, 43, -56, 153, 48, -56, 154, 52, -57, 155, 56, -58, + 128, -72, -39, 129, -68, -39, 130, -64, -40, 131, -60, -41, + 132, -56, -41, 133, -52, -42, 134, -48, -43, 135, -43, -43, + 136, -39, -44, 137, -35, -45, 138, -31, -45, 139, -27, -46, + 140, -23, -47, 141, -19, -47, 142, -15, -48, 142, -11, -49, + 144, -6, -49, 144, -2, -50, 145, 1, -51, 146, 5, -51, + 147, 9, -52, 148, 13, -53, 149, 17, -53, 150, 21, -54, + 151, 25, -55, 152, 30, -55, 153, 34, -56, 154, 38, -57, + 155, 42, -57, 156, 46, -58, 157, 50, -59, 158, 54, -59, + 131, -74, -40, 132, -70, -41, 133, -66, -42, 134, -61, -42, + 135, -57, -43, 135, -53, -44, 136, -49, -44, 137, -45, -45, + 138, -41, -46, 139, -37, -46, 140, -33, -47, 141, -29, -48, + 142, -24, -48, 143, -20, -49, 144, -16, -50, 145, -12, -50, + 146, -8, -51, 147, -4, -52, 148, 0, -52, 149, 3, -53, + 150, 8, -54, 151, 12, -54, 151, 16, -55, 152, 20, -56, + 153, 24, -56, 154, 28, -57, 155, 32, -58, 156, 36, -58, + 157, 40, -59, 158, 45, -60, 159, 49, -60, 160, 53, -61, + 133, -75, -42, 134, -71, -43, 135, -67, -43, 136, -62, -44, + 137, -58, -45, 138, -54, -45, 139, -50, -46, 140, -46, -47, + 141, -42, -47, 142, -38, -48, 143, -34, -49, 143, -30, -49, + 144, -25, -50, 145, -21, -51, 146, -17, -51, 147, -13, -52, + 148, -9, -53, 149, -5, -53, 150, -1, -54, 151, 2, -55, + 152, 7, -55, 153, 11, -56, 154, 15, -57, 155, 19, -57, + 156, 23, -58, 157, 27, -59, 158, 31, -59, 158, 35, -60, + 159, 39, -61, 160, 44, -61, 161, 48, -62, 162, 52, -63, + 136, -76, -44, 136, -72, -44, 137, -68, -45, 138, -64, -46, + 139, -60, -46, 140, -56, -47, 141, -52, -48, 142, -47, -48, + 143, -43, -49, 144, -39, -50, 145, -35, -50, 146, -31, -51, + 147, -27, -52, 148, -23, -52, 149, -19, -53, 150, -15, -54, + 151, -10, -54, 151, -6, -55, 152, -2, -56, 153, 1, -56, + 154, 5, -57, 155, 9, -58, 156, 13, -58, 157, 17, -59, + 158, 21, -60, 159, 26, -60, 160, 30, -61, 161, 34, -62, + 162, 38, -62, 163, 42, -63, 164, 46, -64, 165, 50, -64, + 138, -78, -45, 139, -74, -46, 140, -70, -47, 141, -65, -47, + 142, -61, -48, 143, -57, -49, 143, -53, -49, 144, -49, -50, + 145, -45, -51, 146, -41, -51, 147, -37, -52, 148, -33, -53, + 149, -28, -53, 150, -24, -54, 151, -20, -55, 152, -16, -55, + 153, -12, -56, 154, -8, -57, 155, -4, -57, 156, 0, -58, + 157, 4, -59, 158, 8, -59, 158, 12, -60, 159, 16, -61, + 160, 20, -61, 161, 24, -62, 162, 28, -63, 163, 32, -63, + 164, 36, -64, 165, 41, -65, 166, 45, -65, 167, 49, -66, + 140, -79, -47, 141, -75, -48, 142, -71, -48, 143, -66, -49, + 144, -62, -50, 145, -58, -50, 146, -54, -51, 147, -50, -52, + 148, -46, -52, 149, -42, -53, 150, -38, -54, 150, -34, -54, + 151, -29, -55, 152, -25, -56, 153, -21, -56, 154, -17, -57, + 155, -13, -58, 156, -9, -58, 157, -5, -59, 158, -1, -60, + 159, 3, -60, 160, 7, -61, 161, 11, -62, 162, 15, -62, + 163, 19, -63, 164, 23, -64, 165, 27, -64, 166, 31, -65, + 166, 35, -66, 167, 40, -67, 168, 44, -67, 169, 48, -68, + 143, -80, -49, 143, -76, -49, 144, -72, -50, 145, -68, -51, + 146, -64, -51, 147, -60, -52, 148, -56, -53, 149, -51, -53, + 150, -47, -54, 151, -43, -55, 152, -39, -55, 153, -35, -56, + 154, -31, -57, 155, -27, -57, 156, -23, -58, 157, -19, -59, + 158, -14, -59, 159, -10, -60, 159, -6, -61, 160, -2, -61, + 161, 1, -62, 162, 5, -63, 163, 9, -63, 164, 13, -64, + 165, 17, -65, 166, 22, -66, 167, 26, -66, 168, 30, -67, + 169, 34, -67, 170, 38, -68, 171, 42, -69, 172, 46, -69, + 145, -82, -50, 146, -78, -51, 147, -74, -52, 148, -69, -52, + 149, -65, -53, 150, -61, -54, 150, -57, -54, 152, -53, -55, + 152, -49, -56, 153, -45, -56, 154, -41, -57, 155, -37, -58, + 156, -32, -58, 157, -28, -59, 158, -24, -60, 159, -20, -60, + 160, -16, -61, 161, -12, -62, 162, -8, -62, 163, -4, -63, + 164, 0, -64, 165, 4, -64, 166, 8, -65, 166, 12, -66, + 167, 16, -66, 168, 20, -67, 169, 24, -68, 170, 28, -68, + 171, 32, -69, 172, 37, -70, 173, 41, -71, 174, 45, -71, + 148, -83, -53, 149, -79, -53, 150, -75, -54, 151, -71, -55, + 152, -67, -55, 153, -63, -56, 153, -59, -57, 154, -54, -57, + 155, -50, -58, 156, -46, -59, 157, -42, -59, 158, -38, -60, + 159, -34, -61, 160, -30, -61, 161, -26, -62, 162, -22, -63, + 163, -17, -63, 164, -13, -64, 165, -9, -65, 166, -5, -65, + 167, -1, -66, 168, 2, -67, 168, 6, -67, 169, 10, -68, + 170, 14, -69, 171, 19, -69, 172, 23, -70, 173, 27, -71, + 174, 31, -71, 175, 35, -72, 176, 39, -73, 177, 43, -73, + 150, -85, -54, 151, -81, -55, 152, -77, -55, 153, -72, -56, + 154, -68, -57, 155, -64, -58, 156, -60, -58, 157, -56, -59, + 158, -52, -60, 159, -48, -60, 160, -44, -61, 160, -40, -62, + 161, -35, -62, 162, -31, -63, 163, -27, -64, 164, -23, -64, + 165, -19, -65, 166, -15, -66, 167, -11, -66, 168, -7, -67, + 169, -2, -68, 170, 1, -68, 171, 5, -69, 172, 9, -70, + 173, 13, -70, 174, 17, -71, 175, 21, -72, 175, 25, -72, + 176, 29, -73, 177, 34, -74, 178, 38, -74, 179, 42, -75, + 153, -86, -56, 153, -82, -57, 154, -78, -57, 155, -73, -58, + 156, -69, -59, 157, -65, -59, 158, -61, -60, 159, -57, -61, + 160, -53, -61, 161, -49, -62, 162, -45, -63, 163, -41, -63, + 164, -36, -64, 165, -32, -65, 166, -28, -65, 167, -24, -66, + 168, -20, -67, 168, -16, -67, 169, -12, -68, 170, -8, -69, + 171, -3, -69, 172, 0, -70, 173, 4, -71, 174, 8, -71, + 175, 12, -72, 176, 16, -73, 177, 20, -73, 178, 24, -74, + 179, 28, -75, 180, 33, -75, 181, 37, -76, 182, 41, -77, + 155, -87, -58, 156, -83, -58, 157, -79, -59, 158, -75, -60, + 159, -71, -60, 160, -67, -61, 160, -63, -62, 161, -58, -62, + 162, -54, -63, 163, -50, -64, 164, -46, -64, 165, -42, -65, + 166, -38, -66, 167, -34, -66, 168, -30, -67, 169, -26, -68, + 170, -21, -68, 171, -17, -69, 172, -13, -70, 173, -9, -70, + 174, -5, -71, 175, -1, -72, 176, 2, -72, 176, 6, -73, + 177, 10, -74, 178, 15, -74, 179, 19, -75, 180, 23, -76, + 181, 27, -76, 182, 31, -77, 183, 35, -78, 184, 39, -78, + 157, -89, -59, 158, -85, -60, 159, -81, -61, 160, -76, -61, + 161, -72, -62, 162, -68, -63, 163, -64, -63, 164, -60, -64, + 165, -56, -65, 166, -52, -65, 167, -48, -66, 167, -44, -67, + 169, -39, -67, 169, -35, -68, 170, -31, -69, 171, -27, -69, + 172, -23, -70, 173, -19, -71, 174, -15, -71, 175, -11, -72, + 176, -6, -73, 177, -2, -73, 178, 1, -74, 179, 5, -75, + 180, 9, -75, 181, 13, -76, 182, 17, -77, 183, 21, -77, + 183, 25, -78, 184, 30, -79, 185, 34, -79, 186, 38, -80, + 160, -90, -61, 160, -86, -62, 161, -82, -62, 162, -77, -63, + 163, -73, -64, 164, -69, -64, 165, -65, -65, 166, -61, -66, + 167, -57, -66, 168, -53, -67, 169, -49, -68, 170, -45, -68, + 171, -40, -69, 172, -36, -70, 173, -32, -70, 174, -28, -71, + 175, -24, -72, 176, -20, -72, 176, -16, -73, 177, -12, -74, + 178, -7, -74, 179, -3, -75, 180, 0, -76, 181, 4, -76, + 182, 8, -77, 183, 12, -78, 184, 16, -78, 185, 20, -79, + 186, 24, -80, 187, 29, -80, 188, 33, -81, 189, 37, -82, + 162, -91, -63, 163, -87, -63, 164, -83, -64, 165, -79, -65, + 166, -75, -65, 167, -71, -66, 168, -67, -67, 169, -62, -67, + 169, -58, -68, 170, -54, -69, 171, -50, -69, 172, -46, -70, + 173, -42, -71, 174, -38, -71, 175, -34, -72, 176, -30, -73, + 177, -25, -73, 178, -21, -74, 179, -17, -75, 180, -13, -75, + 181, -9, -76, 182, -5, -77, 183, -1, -77, 183, 2, -78, + 184, 6, -79, 185, 11, -79, 186, 15, -80, 187, 19, -81, + 188, 23, -81, 189, 27, -82, 190, 31, -83, 191, 35, -83, + 164, -92, -64, 165, -88, -65, 166, -84, -66, 167, -80, -66, + 168, -76, -67, 169, -72, -68, 170, -68, -68, 171, -63, -69, + 172, -59, -70, 173, -55, -70, 174, -51, -71, 175, -47, -72, + 176, -43, -72, 176, -39, -73, 177, -35, -74, 178, -31, -74, + 179, -26, -75, 180, -22, -76, 181, -18, -76, 182, -14, -77, + 183, -10, -78, 184, -6, -78, 185, -2, -79, 186, 1, -80, + 187, 5, -80, 188, 10, -81, 189, 14, -82, 190, 18, -82, + 190, 22, -83, 192, 26, -84, 192, 30, -84, 193, 34, -85, + 167, -94, -66, 168, -90, -67, 168, -86, -67, 169, -81, -68, + 170, -77, -69, 171, -73, -69, 172, -69, -70, 173, -65, -71, + 174, -61, -71, 175, -57, -72, 176, -53, -73, 177, -49, -73, + 178, -44, -74, 179, -40, -75, 180, -36, -75, 181, -32, -76, + 182, -28, -77, 183, -24, -77, 183, -20, -78, 184, -16, -79, + 185, -11, -79, 186, -7, -80, 187, -3, -81, 188, 0, -81, + 189, 4, -82, 190, 8, -83, 191, 12, -83, 192, 16, -84, + 193, 20, -85, 194, 25, -85, 195, 29, -86, 196, 33, -87, + 169, -95, -68, 170, -91, -68, 171, -87, -69, 172, -83, -70, + 173, -79, -70, 174, -75, -71, 175, -71, -72, 176, -66, -72, + 176, -62, -73, 177, -58, -74, 178, -54, -74, 179, -50, -75, + 180, -46, -76, 181, -42, -76, 182, -38, -77, 183, -34, -78, + 184, -29, -78, 185, -25, -79, 186, -21, -80, 187, -17, -80, + 188, -13, -81, 189, -9, -82, 190, -5, -82, 191, -1, -83, + 191, 2, -84, 192, 7, -84, 193, 11, -85, 194, 15, -86, + 195, 19, -86, 196, 23, -87, 197, 27, -88, 198, 31, -88, + 171, -96, -69, 172, -92, -70, 173, -88, -71, 174, -84, -71, + 175, -80, -72, 176, -76, -73, 177, -72, -73, 178, -67, -74, + 179, -63, -75, 180, -59, -75, 181, -55, -76, 182, -51, -77, + 183, -47, -77, 184, -43, -78, 184, -39, -79, 185, -35, -79, + 186, -30, -80, 187, -26, -81, 188, -22, -81, 189, -18, -82, + 190, -14, -83, 191, -10, -83, 192, -6, -84, 193, -2, -85, + 194, 1, -85, 195, 6, -86, 196, 10, -87, 197, 14, -87, + 198, 18, -88, 199, 22, -89, 199, 26, -89, 200, 30, -90, + 24, -13, 41, 25, -9, 40, 26, -5, 39, 27, -1, 38, + 28, 2, 38, 29, 6, 37, 30, 10, 37, 31, 15, 36, + 32, 19, 35, 32, 23, 34, 33, 27, 34, 34, 31, 33, + 35, 35, 32, 36, 39, 32, 37, 43, 31, 38, 47, 30, + 39, 52, 30, 40, 56, 29, 41, 60, 28, 42, 64, 28, + 43, 68, 27, 44, 72, 26, 45, 76, 26, 46, 80, 25, + 46, 84, 24, 48, 89, 24, 48, 93, 23, 49, 97, 22, + 50, 101, 22, 51, 105, 21, 52, 109, 20, 53, 113, 20, + 26, -15, 39, 27, -11, 38, 28, -7, 38, 29, -2, 37, + 30, 1, 36, 31, 5, 35, 32, 9, 35, 33, 13, 34, + 34, 17, 33, 35, 21, 33, 36, 25, 32, 37, 29, 32, + 38, 34, 31, 39, 38, 30, 39, 42, 29, 40, 46, 29, + 41, 50, 28, 42, 54, 27, 43, 58, 27, 44, 62, 26, + 45, 67, 25, 46, 71, 25, 47, 75, 24, 48, 79, 23, + 49, 83, 23, 50, 87, 22, 51, 91, 21, 52, 95, 21, + 53, 99, 20, 54, 104, 19, 55, 108, 19, 55, 112, 18, + 29, -16, 37, 30, -12, 37, 31, -8, 36, 32, -3, 35, + 32, 0, 34, 33, 4, 34, 34, 8, 33, 35, 12, 32, + 36, 16, 32, 37, 20, 31, 38, 24, 30, 39, 28, 30, + 40, 33, 29, 41, 37, 28, 42, 41, 28, 43, 45, 27, + 44, 49, 26, 45, 53, 26, 46, 57, 25, 46, 61, 24, + 48, 66, 24, 48, 70, 23, 49, 74, 22, 50, 78, 22, + 51, 82, 21, 52, 86, 20, 53, 90, 20, 54, 94, 19, + 55, 98, 18, 56, 103, 18, 57, 107, 17, 58, 111, 16, + 31, -17, 35, 32, -13, 35, 33, -9, 34, 34, -5, 33, + 35, -1, 33, 36, 2, 32, 37, 6, 31, 38, 11, 31, + 39, 15, 30, 39, 19, 29, 40, 23, 29, 41, 27, 28, + 42, 31, 27, 43, 35, 27, 44, 39, 26, 45, 43, 25, + 46, 48, 25, 47, 52, 24, 48, 56, 23, 49, 60, 23, + 50, 64, 22, 51, 68, 21, 52, 72, 21, 53, 76, 20, + 54, 80, 19, 55, 85, 19, 55, 89, 18, 56, 93, 17, + 57, 97, 17, 58, 101, 16, 59, 105, 15, 60, 109, 15, + 33, -19, 34, 34, -15, 33, 35, -11, 33, 36, -6, 32, + 37, -2, 31, 38, 1, 30, 39, 5, 30, 40, 9, 29, + 41, 13, 28, 42, 17, 28, 43, 21, 27, 44, 25, 26, + 45, 30, 26, 46, 34, 25, 47, 38, 24, 47, 42, 24, + 48, 46, 23, 49, 50, 22, 50, 54, 22, 51, 58, 21, + 52, 63, 20, 53, 67, 20, 54, 71, 19, 55, 75, 18, + 56, 79, 18, 57, 83, 17, 58, 87, 16, 59, 91, 16, + 60, 95, 15, 61, 100, 14, 62, 104, 14, 62, 108, 13, + 36, -20, 32, 37, -16, 31, 38, -12, 31, 39, -7, 30, + 40, -3, 29, 40, 0, 29, 41, 4, 28, 42, 8, 27, + 43, 12, 27, 44, 16, 26, 45, 20, 25, 46, 24, 25, + 47, 29, 24, 48, 33, 23, 49, 37, 23, 50, 41, 22, + 51, 45, 21, 52, 49, 21, 53, 53, 20, 54, 57, 19, + 55, 62, 19, 55, 66, 18, 56, 70, 17, 57, 74, 17, + 58, 78, 16, 59, 82, 15, 60, 86, 15, 61, 90, 14, + 62, 94, 13, 63, 99, 13, 64, 103, 12, 65, 107, 11, + 38, -21, 30, 39, -17, 30, 40, -13, 29, 41, -9, 28, + 42, -5, 28, 43, -1, 27, 44, 2, 26, 45, 7, 26, + 46, 11, 25, 47, 15, 24, 47, 19, 24, 48, 23, 23, + 49, 27, 22, 50, 31, 22, 51, 35, 21, 52, 39, 20, + 53, 44, 20, 54, 48, 19, 55, 52, 18, 56, 56, 18, + 57, 60, 17, 58, 64, 16, 59, 68, 16, 60, 72, 15, + 61, 76, 14, 62, 81, 14, 63, 85, 13, 63, 89, 12, + 64, 93, 12, 65, 97, 11, 66, 101, 10, 67, 105, 10, + 40, -23, 29, 41, -19, 28, 42, -15, 27, 43, -10, 27, + 44, -6, 26, 45, -2, 25, 46, 1, 25, 47, 5, 24, + 48, 9, 23, 49, 13, 23, 50, 17, 22, 51, 21, 21, + 52, 26, 21, 53, 30, 20, 54, 34, 19, 54, 38, 19, + 56, 42, 18, 56, 46, 17, 57, 50, 17, 58, 54, 16, + 59, 59, 15, 60, 63, 15, 61, 67, 14, 62, 71, 13, + 63, 75, 13, 64, 79, 12, 65, 83, 11, 66, 87, 11, + 67, 91, 10, 68, 96, 9, 69, 100, 9, 70, 104, 8, + 43, -24, 27, 44, -20, 26, 45, -16, 26, 46, -11, 25, + 47, -7, 24, 47, -3, 24, 48, 0, 23, 49, 4, 22, + 50, 8, 22, 51, 12, 21, 52, 16, 20, 53, 20, 20, + 54, 25, 19, 55, 29, 18, 56, 33, 18, 57, 37, 17, + 58, 41, 16, 59, 45, 16, 60, 49, 15, 61, 53, 14, + 62, 58, 14, 63, 62, 13, 63, 66, 12, 64, 70, 12, + 65, 74, 11, 66, 78, 10, 67, 82, 10, 68, 86, 9, + 69, 90, 8, 70, 95, 8, 71, 99, 7, 72, 103, 6, + 45, -25, 25, 46, -21, 25, 47, -17, 24, 48, -13, 23, + 49, -9, 23, 50, -5, 22, 51, -1, 21, 52, 3, 21, + 53, 7, 20, 54, 11, 19, 54, 15, 19, 55, 19, 18, + 56, 23, 17, 57, 27, 17, 58, 31, 16, 59, 35, 15, + 60, 40, 15, 61, 44, 14, 62, 48, 13, 63, 52, 13, + 64, 56, 12, 65, 60, 11, 66, 64, 11, 67, 68, 10, + 68, 72, 9, 69, 77, 9, 70, 81, 8, 70, 85, 7, + 71, 89, 7, 72, 93, 6, 73, 97, 5, 74, 101, 5, + 47, -27, 24, 48, -23, 23, 49, -19, 22, 50, -14, 22, + 51, -10, 21, 52, -6, 20, 53, -2, 20, 54, 1, 19, + 55, 5, 18, 56, 9, 18, 57, 13, 17, 58, 17, 16, + 59, 22, 16, 60, 26, 15, 61, 30, 14, 62, 34, 14, + 63, 38, 13, 63, 42, 12, 64, 46, 12, 65, 50, 11, + 66, 55, 10, 67, 59, 10, 68, 63, 9, 69, 67, 8, + 70, 71, 8, 71, 75, 7, 72, 79, 6, 73, 83, 6, + 74, 87, 5, 75, 92, 4, 76, 96, 4, 77, 100, 3, + 50, -28, 22, 51, -24, 21, 52, -20, 20, 53, -16, 20, + 54, -12, 19, 55, -8, 18, 56, -4, 18, 57, 0, 17, + 58, 4, 16, 59, 8, 16, 60, 12, 15, 61, 16, 14, + 62, 20, 14, 63, 24, 13, 64, 28, 12, 64, 32, 12, + 65, 37, 11, 66, 41, 10, 67, 45, 10, 68, 49, 9, + 69, 53, 8, 70, 57, 8, 71, 61, 7, 72, 65, 6, + 73, 69, 6, 74, 74, 5, 75, 78, 4, 76, 82, 4, + 77, 86, 3, 78, 90, 2, 79, 94, 2, 80, 98, 1, + 53, -30, 20, 54, -26, 19, 55, -22, 19, 56, -17, 18, + 57, -13, 17, 57, -9, 17, 58, -5, 16, 59, -1, 15, + 60, 2, 15, 61, 6, 14, 62, 10, 13, 63, 14, 13, + 64, 19, 12, 65, 23, 11, 66, 27, 11, 67, 31, 10, + 68, 35, 9, 69, 39, 9, 70, 43, 8, 71, 47, 7, + 72, 52, 7, 73, 56, 6, 73, 60, 5, 74, 64, 5, + 75, 68, 4, 76, 72, 3, 77, 76, 3, 78, 80, 2, + 79, 84, 1, 80, 89, 1, 81, 93, 0, 82, 97, 0, + 55, -31, 18, 56, -27, 18, 57, -23, 17, 58, -18, 16, + 59, -14, 16, 60, -10, 15, 61, -6, 14, 62, -2, 14, + 63, 1, 13, 64, 5, 12, 64, 9, 12, 65, 13, 11, + 66, 18, 10, 67, 22, 10, 68, 26, 9, 69, 30, 8, + 70, 34, 8, 71, 38, 7, 72, 42, 6, 73, 46, 6, + 74, 51, 5, 75, 55, 4, 76, 59, 4, 77, 63, 3, + 78, 67, 2, 79, 71, 2, 80, 75, 1, 80, 79, 0, + 81, 83, 0, 82, 88, 0, 83, 92, -1, 84, 96, -1, + 57, -32, 17, 58, -28, 16, 59, -24, 15, 60, -20, 15, + 61, -16, 14, 62, -12, 13, 63, -8, 13, 64, -3, 12, + 65, 0, 11, 66, 4, 11, 67, 8, 10, 68, 12, 9, + 69, 16, 9, 70, 20, 8, 71, 24, 7, 71, 28, 7, + 73, 33, 6, 73, 37, 5, 74, 41, 5, 75, 45, 4, + 76, 49, 3, 77, 53, 3, 78, 57, 2, 79, 61, 1, + 80, 65, 1, 81, 70, 0, 82, 74, 0, 83, 78, 0, + 84, 82, -1, 85, 86, -2, 86, 90, -2, 87, 94, -3, + 60, -34, 15, 61, -30, 14, 62, -26, 14, 63, -21, 13, + 64, -17, 12, 64, -13, 12, 65, -9, 11, 66, -5, 10, + 67, -1, 10, 68, 2, 9, 69, 6, 8, 70, 10, 8, + 71, 15, 7, 72, 19, 6, 73, 23, 6, 74, 27, 5, + 75, 31, 4, 76, 35, 4, 77, 39, 3, 78, 43, 2, + 79, 48, 2, 80, 52, 1, 80, 56, 0, 81, 60, 0, + 82, 64, 0, 83, 68, -1, 84, 72, -1, 85, 76, -2, + 86, 80, -3, 87, 85, -3, 88, 89, -4, 89, 93, -5, + 62, -35, 13, 63, -31, 13, 64, -27, 12, 65, -22, 11, + 66, -18, 11, 67, -14, 10, 68, -10, 9, 69, -6, 9, + 70, -2, 8, 71, 1, 7, 72, 5, 7, 72, 9, 6, + 73, 14, 5, 74, 18, 5, 75, 22, 4, 76, 26, 3, + 77, 30, 3, 78, 34, 2, 79, 38, 1, 80, 42, 1, + 81, 47, 0, 82, 51, 0, 83, 55, 0, 84, 59, -1, + 85, 63, -2, 86, 67, -2, 87, 71, -3, 87, 75, -4, + 88, 79, -4, 89, 84, -5, 90, 88, -6, 91, 92, -6, + 65, -36, 12, 65, -32, 11, 66, -28, 10, 67, -24, 10, + 68, -20, 9, 69, -16, 8, 70, -12, 8, 71, -7, 7, + 72, -3, 6, 73, 0, 6, 74, 4, 5, 75, 8, 4, + 76, 12, 4, 77, 16, 3, 78, 20, 2, 79, 24, 2, + 80, 29, 1, 80, 33, 0, 81, 37, 0, 82, 41, 0, + 83, 45, -1, 84, 49, -1, 85, 53, -2, 86, 57, -3, + 87, 61, -3, 88, 66, -4, 89, 70, -5, 90, 74, -5, + 91, 78, -6, 92, 82, -7, 93, 86, -7, 94, 90, -8, + 67, -38, 10, 68, -34, 9, 69, -30, 9, 70, -25, 8, + 71, -21, 7, 72, -17, 7, 72, -13, 6, 73, -9, 5, + 74, -5, 5, 75, -1, 4, 76, 2, 3, 77, 6, 3, + 78, 11, 2, 79, 15, 1, 80, 19, 1, 81, 23, 0, + 82, 27, 0, 83, 31, 0, 84, 35, -1, 85, 39, -2, + 86, 44, -2, 87, 48, -3, 88, 52, -4, 88, 56, -4, + 89, 60, -5, 90, 64, -6, 91, 68, -6, 92, 72, -7, + 93, 76, -8, 94, 81, -8, 95, 85, -9, 96, 89, -10, + 69, -39, 8, 70, -35, 8, 71, -31, 7, 72, -26, 6, + 73, -22, 6, 74, -18, 5, 75, -14, 4, 76, -10, 4, + 77, -6, 3, 78, -2, 2, 79, 1, 2, 79, 5, 1, + 81, 10, 0, 81, 14, 0, 82, 18, 0, 83, 22, -1, + 84, 26, -1, 85, 30, -2, 86, 34, -3, 87, 38, -3, + 88, 43, -4, 89, 47, -5, 90, 51, -5, 91, 55, -6, + 92, 59, -7, 93, 63, -7, 94, 67, -8, 95, 71, -9, + 95, 75, -9, 96, 80, -10, 97, 84, -11, 98, 88, -11, + 72, -40, 7, 72, -36, 6, 73, -32, 5, 74, -28, 5, + 75, -24, 4, 76, -20, 3, 77, -16, 3, 78, -11, 2, + 79, -7, 1, 80, -3, 1, 81, 0, 0, 82, 4, 0, + 83, 8, 0, 84, 12, -1, 85, 16, -2, 86, 20, -2, + 87, 25, -3, 88, 29, -4, 88, 33, -4, 89, 37, -5, + 90, 41, -6, 91, 45, -6, 92, 49, -7, 93, 53, -8, + 94, 57, -8, 95, 62, -9, 96, 66, -10, 97, 70, -10, + 98, 74, -11, 99, 78, -12, 100, 82, -12, 101, 86, -13, + 74, -41, 5, 75, -37, 4, 76, -33, 4, 77, -29, 3, + 78, -25, 2, 79, -21, 2, 79, -17, 1, 81, -12, 0, + 81, -8, 0, 82, -4, 0, 83, 0, -1, 84, 3, -1, + 85, 7, -2, 86, 11, -3, 87, 15, -3, 88, 19, -4, + 89, 24, -5, 90, 28, -5, 91, 32, -6, 92, 36, -7, + 93, 40, -8, 94, 44, -8, 95, 48, -9, 95, 52, -9, + 96, 56, -10, 97, 61, -11, 98, 65, -11, 99, 69, -12, + 100, 73, -13, 101, 77, -14, 102, 81, -14, 103, 85, -15, + 76, -43, 3, 77, -39, 3, 78, -35, 2, 79, -30, 1, + 80, -26, 1, 81, -22, 0, 82, -18, 0, 83, -14, 0, + 84, -10, -1, 85, -6, -2, 86, -2, -2, 87, 1, -3, + 88, 6, -4, 88, 10, -4, 89, 14, -5, 90, 18, -6, + 91, 22, -6, 92, 26, -7, 93, 30, -8, 94, 34, -8, + 95, 39, -9, 96, 43, -10, 97, 47, -10, 98, 51, -11, + 99, 55, -12, 100, 59, -13, 101, 63, -13, 102, 67, -14, + 102, 71, -14, 104, 76, -15, 104, 80, -16, 105, 84, -16, + 79, -44, 2, 80, -40, 1, 80, -36, 0, 81, -32, 0, + 82, -28, 0, 83, -24, -1, 84, -20, -1, 85, -15, -2, + 86, -11, -3, 87, -7, -3, 88, -3, -4, 89, 0, -5, + 90, 4, -5, 91, 8, -6, 92, 12, -7, 93, 16, -7, + 94, 21, -8, 95, 25, -9, 95, 29, -9, 96, 33, -10, + 97, 37, -11, 98, 41, -12, 99, 45, -12, 100, 49, -13, + 101, 53, -13, 102, 58, -14, 103, 62, -15, 104, 66, -15, + 105, 70, -16, 106, 74, -17, 107, 78, -18, 108, 82, -18, + 81, -45, 0, 82, -41, 0, 83, -37, 0, 84, -33, -1, + 85, -29, -2, 86, -25, -2, 87, -21, -3, 88, -16, -4, + 88, -12, -4, 89, -8, -5, 90, -4, -6, 91, 0, -6, + 92, 3, -7, 93, 7, -8, 94, 11, -8, 95, 15, -9, + 96, 20, -10, 97, 24, -10, 98, 28, -11, 99, 32, -12, + 100, 36, -13, 101, 40, -13, 102, 44, -14, 103, 48, -14, + 103, 52, -15, 104, 57, -16, 105, 61, -17, 106, 65, -17, + 107, 69, -18, 108, 73, -19, 109, 77, -19, 110, 81, -20, + 83, -47, -1, 84, -43, -1, 85, -39, -2, 86, -34, -3, + 87, -30, -3, 88, -26, -4, 89, -22, -5, 90, -18, -6, + 91, -14, -6, 92, -10, -7, 93, -6, -7, 94, -2, -8, + 95, 2, -9, 96, 6, -9, 96, 10, -10, 97, 14, -11, + 98, 18, -12, 99, 22, -12, 100, 26, -13, 101, 30, -13, + 102, 35, -14, 103, 39, -15, 104, 43, -16, 105, 47, -16, + 106, 51, -17, 107, 55, -18, 108, 59, -18, 109, 63, -19, + 110, 67, -19, 111, 72, -20, 111, 76, -21, 112, 80, -22, + 86, -48, -2, 87, -44, -3, 87, -40, -4, 89, -36, -4, + 89, -32, -5, 90, -28, -6, 91, -24, -6, 92, -19, -7, + 93, -15, -8, 94, -11, -8, 95, -7, -9, 96, -3, -10, + 97, 0, -11, 98, 4, -11, 99, 8, -12, 100, 12, -12, + 101, 17, -13, 102, 21, -14, 103, 25, -14, 103, 29, -15, + 104, 33, -16, 105, 37, -17, 106, 41, -17, 107, 45, -18, + 108, 49, -18, 109, 54, -19, 110, 58, -20, 111, 62, -21, + 112, 66, -21, 113, 70, -22, 114, 74, -23, 115, 78, -23, + 88, -49, -4, 89, -45, -5, 90, -41, -5, 91, -37, -6, + 92, -33, -7, 93, -29, -7, 94, -25, -8, 95, -20, -9, + 96, -16, -10, 96, -12, -10, 97, -8, -11, 98, -4, -11, + 99, 0, -12, 100, 3, -13, 101, 7, -13, 102, 11, -14, + 103, 16, -15, 104, 20, -16, 105, 24, -16, 106, 28, -17, + 107, 32, -18, 108, 36, -18, 109, 40, -19, 110, 44, -20, + 110, 48, -20, 111, 53, -21, 112, 57, -22, 113, 61, -22, + 114, 65, -23, 115, 69, -24, 116, 73, -24, 117, 77, -25, + 90, -51, -6, 91, -47, -6, 92, -43, -7, 93, -38, -8, + 94, -34, -8, 95, -30, -9, 96, -26, -10, 97, -22, -11, + 98, -18, -11, 99, -14, -12, 100, -10, -12, 101, -6, -13, + 102, -1, -14, 103, 2, -15, 103, 6, -15, 104, 10, -16, + 105, 14, -17, 106, 18, -17, 107, 22, -18, 108, 26, -18, + 109, 31, -19, 110, 35, -20, 111, 39, -21, 112, 43, -21, + 113, 47, -22, 114, 51, -23, 115, 55, -23, 116, 59, -24, + 117, 63, -25, 118, 68, -25, 119, 72, -26, 119, 76, -27, + 93, -52, -7, 94, -48, -8, 95, -44, -9, 96, -40, -10, + 96, -36, -10, 97, -32, -11, 98, -28, -11, 99, -23, -12, + 100, -19, -13, 101, -15, -14, 102, -11, -14, 103, -7, -15, + 104, -3, -16, 105, 0, -16, 106, 4, -17, 107, 8, -17, + 108, 13, -18, 109, 17, -19, 110, 21, -20, 110, 25, -20, + 112, 29, -21, 112, 33, -22, 113, 37, -22, 114, 41, -23, + 115, 45, -24, 116, 50, -24, 117, 54, -25, 118, 58, -26, + 119, 62, -26, 120, 66, -27, 121, 70, -28, 122, 74, -28, + 95, -53, -9, 96, -49, -10, 97, -45, -10, 98, -41, -11, + 99, -37, -12, 100, -33, -12, 101, -29, -13, 102, -24, -14, + 103, -20, -15, 103, -16, -15, 104, -12, -16, 105, -8, -16, + 106, -4, -17, 107, 0, -18, 108, 3, -19, 109, 7, -19, + 110, 12, -20, 111, 16, -21, 112, 20, -21, 113, 24, -22, + 114, 28, -23, 115, 32, -23, 116, 36, -24, 117, 40, -25, + 118, 44, -25, 119, 49, -26, 119, 53, -27, 120, 57, -27, + 121, 61, -28, 122, 65, -29, 123, 69, -29, 124, 73, -30, + 97, -55, -11, 98, -51, -11, 99, -47, -12, 100, -42, -13, + 101, -38, -14, 102, -34, -14, 103, -30, -15, 104, -26, -16, + 105, -22, -16, 106, -18, -17, 107, -14, -18, 108, -10, -18, + 109, -5, -19, 110, -1, -20, 111, 2, -20, 111, 6, -21, + 112, 10, -22, 113, 14, -22, 114, 18, -23, 115, 22, -24, + 116, 27, -24, 117, 31, -25, 118, 35, -26, 119, 39, -26, + 120, 43, -27, 121, 47, -28, 122, 51, -28, 123, 55, -29, + 124, 59, -30, 125, 64, -30, 126, 68, -31, 126, 72, -32, + 100, -56, -13, 101, -52, -14, 102, -48, -14, 103, -44, -15, + 104, -40, -16, 105, -36, -16, 106, -32, -17, 107, -27, -18, + 108, -23, -18, 109, -19, -19, 110, -15, -20, 111, -11, -20, + 112, -7, -21, 113, -3, -22, 113, 0, -22, 114, 4, -23, + 115, 9, -24, 116, 13, -24, 117, 17, -25, 118, 21, -26, + 119, 25, -26, 120, 29, -27, 121, 33, -28, 122, 37, -28, + 123, 41, -29, 124, 46, -30, 125, 50, -30, 126, 54, -31, + 127, 58, -32, 128, 62, -32, 128, 66, -33, 129, 70, -34, + 103, -58, -15, 104, -54, -15, 105, -50, -16, 106, -45, -17, + 106, -41, -17, 107, -37, -18, 108, -33, -19, 109, -29, -19, + 110, -25, -20, 111, -21, -21, 112, -17, -21, 113, -13, -22, + 114, -8, -23, 115, -4, -23, 116, 0, -24, 117, 3, -25, + 118, 7, -25, 119, 11, -26, 120, 15, -27, 120, 19, -27, + 121, 24, -28, 122, 28, -29, 123, 32, -29, 124, 36, -30, + 125, 40, -31, 126, 44, -31, 127, 48, -32, 128, 52, -33, + 129, 56, -33, 130, 61, -34, 131, 65, -35, 132, 69, -35, + 105, -59, -16, 106, -55, -17, 107, -51, -18, 108, -47, -18, + 109, -43, -19, 110, -39, -20, 111, -35, -20, 112, -30, -21, + 113, -26, -22, 113, -22, -22, 114, -18, -23, 115, -14, -24, + 116, -10, -24, 117, -6, -25, 118, -2, -26, 119, 1, -26, + 120, 6, -27, 121, 10, -28, 122, 14, -28, 123, 18, -29, + 124, 22, -30, 125, 26, -30, 126, 30, -31, 127, 34, -32, + 127, 38, -32, 129, 43, -33, 129, 47, -34, 130, 51, -34, + 131, 55, -35, 132, 59, -36, 133, 63, -36, 134, 67, -37, + 107, -60, -18, 108, -56, -19, 109, -52, -19, 110, -48, -20, + 111, -44, -21, 112, -40, -21, 113, -36, -22, 114, -31, -23, + 115, -27, -23, 116, -23, -24, 117, -19, -25, 118, -15, -25, + 119, -11, -26, 120, -7, -27, 120, -3, -27, 121, 0, -28, + 122, 5, -29, 123, 9, -29, 124, 13, -30, 125, 17, -31, + 126, 21, -31, 127, 25, -32, 128, 29, -33, 129, 33, -33, + 130, 37, -34, 131, 42, -35, 132, 46, -35, 133, 50, -36, + 134, 54, -37, 135, 58, -37, 136, 62, -38, 136, 66, -39, + 110, -62, -20, 111, -58, -20, 112, -54, -21, 113, -49, -22, + 113, -45, -22, 114, -41, -23, 115, -37, -24, 116, -33, -24, + 117, -29, -25, 118, -25, -26, 119, -21, -26, 120, -17, -27, + 121, -12, -28, 122, -8, -28, 123, -4, -29, 124, 0, -30, + 125, 3, -30, 126, 7, -31, 127, 11, -32, 128, 15, -32, + 129, 20, -33, 129, 24, -34, 130, 28, -34, 131, 32, -35, + 132, 36, -36, 133, 40, -36, 134, 44, -37, 135, 48, -38, + 136, 52, -38, 137, 57, -39, 138, 61, -40, 139, 65, -40, + 112, -63, -21, 113, -59, -22, 114, -55, -23, 115, -51, -23, + 116, -47, -24, 117, -43, -25, 118, -39, -25, 119, -34, -26, + 120, -30, -27, 121, -26, -27, 121, -22, -28, 122, -18, -29, + 123, -14, -29, 124, -10, -30, 125, -6, -31, 126, -2, -31, + 127, 2, -32, 128, 6, -33, 129, 10, -33, 130, 14, -34, + 131, 18, -35, 132, 22, -35, 133, 26, -36, 134, 30, -37, + 135, 34, -37, 136, 39, -38, 136, 43, -39, 137, 47, -39, + 138, 51, -40, 139, 55, -41, 140, 59, -41, 141, 63, -42, + 114, -64, -23, 115, -60, -24, 116, -56, -24, 117, -52, -25, + 118, -48, -26, 119, -44, -26, 120, -40, -27, 121, -35, -28, + 122, -31, -28, 123, -27, -29, 124, -23, -30, 125, -19, -30, + 126, -15, -31, 127, -11, -32, 128, -7, -32, 128, -3, -33, + 129, 1, -34, 130, 5, -34, 131, 9, -35, 132, 13, -36, + 133, 17, -36, 134, 21, -37, 135, 25, -38, 136, 29, -38, + 137, 33, -39, 138, 38, -40, 139, 42, -40, 140, 46, -41, + 141, 50, -42, 142, 54, -42, 143, 58, -43, 143, 62, -44, + 117, -66, -25, 118, -62, -25, 119, -58, -26, 120, -53, -27, + 121, -49, -27, 121, -45, -28, 122, -41, -29, 123, -37, -29, + 124, -33, -30, 125, -29, -31, 126, -25, -31, 127, -21, -32, + 128, -16, -33, 129, -12, -33, 130, -8, -34, 131, -4, -35, + 132, 0, -35, 133, 3, -36, 134, 7, -37, 135, 11, -37, + 136, 16, -38, 136, 20, -39, 137, 24, -39, 138, 28, -40, + 139, 32, -41, 140, 36, -41, 141, 40, -42, 142, 44, -43, + 143, 48, -43, 144, 53, -44, 145, 57, -45, 146, 61, -45, + 119, -67, -26, 120, -63, -27, 121, -59, -28, 122, -55, -28, + 123, -51, -29, 124, -47, -30, 125, -43, -30, 126, -38, -31, + 127, -34, -32, 128, -30, -32, 128, -26, -33, 129, -22, -34, + 130, -18, -34, 131, -14, -35, 132, -10, -36, 133, -6, -36, + 134, -1, -37, 135, 2, -38, 136, 6, -38, 137, 10, -39, + 138, 14, -40, 139, 18, -40, 140, 22, -41, 141, 26, -42, + 142, 30, -42, 143, 35, -43, 144, 39, -44, 144, 43, -44, + 145, 47, -45, 146, 51, -46, 147, 55, -46, 148, 59, -47, + 121, -68, -28, 122, -64, -29, 123, -60, -29, 124, -56, -30, + 125, -52, -31, 126, -48, -31, 127, -44, -32, 128, -39, -33, + 129, -35, -33, 130, -31, -34, 131, -27, -35, 132, -23, -35, + 133, -19, -36, 134, -15, -37, 135, -11, -37, 135, -7, -38, + 137, -2, -39, 137, 1, -39, 138, 5, -40, 139, 9, -41, + 140, 13, -41, 141, 17, -42, 142, 21, -43, 143, 25, -43, + 144, 29, -44, 145, 34, -45, 146, 38, -45, 147, 42, -46, + 148, 46, -47, 149, 50, -47, 150, 54, -48, 151, 58, -49, + 124, -70, -30, 125, -66, -30, 126, -62, -31, 127, -57, -32, + 128, -53, -32, 128, -49, -33, 129, -45, -34, 130, -41, -34, + 131, -37, -35, 132, -33, -36, 133, -29, -36, 134, -25, -37, + 135, -20, -38, 136, -16, -38, 137, -12, -39, 138, -8, -40, + 139, -4, -40, 140, 0, -41, 141, 3, -42, 142, 7, -42, + 143, 12, -43, 144, 16, -44, 144, 20, -44, 145, 24, -45, + 146, 28, -46, 147, 32, -46, 148, 36, -47, 149, 40, -48, + 150, 44, -48, 151, 49, -49, 152, 53, -50, 153, 57, -50, + 126, -71, -31, 127, -67, -32, 128, -63, -33, 129, -58, -33, + 130, -54, -34, 131, -50, -35, 132, -46, -35, 133, -42, -36, + 134, -38, -37, 135, -34, -37, 136, -30, -38, 136, -26, -39, + 137, -21, -39, 138, -17, -40, 139, -13, -41, 140, -9, -41, + 141, -5, -42, 142, -1, -43, 143, 2, -43, 144, 6, -44, + 145, 11, -45, 146, 15, -45, 147, 19, -46, 148, 23, -47, + 149, 27, -47, 150, 31, -48, 151, 35, -49, 151, 39, -49, + 152, 43, -50, 153, 48, -51, 154, 52, -51, 155, 56, -52, + 129, -72, -33, 129, -68, -34, 130, -64, -34, 131, -60, -35, + 132, -56, -36, 133, -52, -36, 134, -48, -37, 135, -43, -38, + 136, -39, -38, 137, -35, -39, 138, -31, -40, 139, -27, -40, + 140, -23, -41, 141, -19, -42, 142, -15, -42, 143, -11, -43, + 144, -6, -44, 144, -2, -44, 145, 1, -45, 146, 5, -46, + 147, 9, -46, 148, 13, -47, 149, 17, -48, 150, 21, -48, + 151, 25, -49, 152, 30, -50, 153, 34, -50, 154, 38, -51, + 155, 42, -52, 156, 46, -52, 157, 50, -53, 158, 54, -54, + 131, -74, -35, 132, -70, -35, 133, -66, -36, 134, -61, -37, + 135, -57, -37, 136, -53, -38, 136, -49, -39, 137, -45, -39, + 138, -41, -40, 139, -37, -41, 140, -33, -41, 141, -29, -42, + 142, -24, -43, 143, -20, -43, 144, -16, -44, 145, -12, -45, + 146, -8, -45, 147, -4, -46, 148, 0, -47, 149, 3, -47, + 150, 8, -48, 151, 12, -49, 151, 16, -49, 152, 20, -50, + 153, 24, -51, 154, 28, -51, 155, 32, -52, 156, 36, -53, + 157, 40, -53, 158, 45, -54, 159, 49, -55, 160, 53, -55, + 133, -75, -36, 134, -71, -37, 135, -67, -38, 136, -62, -38, + 137, -58, -39, 138, -54, -40, 139, -50, -40, 140, -46, -41, + 141, -42, -42, 142, -38, -42, 143, -34, -43, 143, -30, -44, + 144, -25, -44, 145, -21, -45, 146, -17, -46, 147, -13, -46, + 148, -9, -47, 149, -5, -48, 150, -1, -48, 151, 2, -49, + 152, 7, -50, 153, 11, -50, 154, 15, -51, 155, 19, -52, + 156, 23, -52, 157, 27, -53, 158, 31, -54, 159, 35, -54, + 159, 39, -55, 160, 44, -56, 161, 48, -56, 162, 52, -57, + 136, -76, -38, 136, -72, -39, 137, -68, -39, 138, -64, -40, + 139, -60, -41, 140, -56, -41, 141, -52, -42, 142, -47, -43, + 143, -43, -43, 144, -39, -44, 145, -35, -45, 146, -31, -45, + 147, -27, -46, 148, -23, -47, 149, -19, -47, 150, -15, -48, + 151, -10, -49, 152, -6, -49, 152, -2, -50, 153, 1, -51, + 154, 5, -51, 155, 9, -52, 156, 13, -53, 157, 17, -53, + 158, 21, -54, 159, 26, -55, 160, 30, -55, 161, 34, -56, + 162, 38, -57, 163, 42, -57, 164, 46, -58, 165, 50, -59, + 138, -78, -40, 139, -74, -40, 140, -70, -41, 141, -65, -42, + 142, -61, -42, 143, -57, -43, 143, -53, -44, 145, -49, -44, + 145, -45, -45, 146, -41, -46, 147, -37, -46, 148, -33, -47, + 149, -28, -48, 150, -24, -48, 151, -20, -49, 152, -16, -50, + 153, -12, -50, 154, -8, -51, 155, -4, -52, 156, 0, -52, + 157, 4, -53, 158, 8, -54, 159, 12, -54, 159, 16, -55, + 160, 20, -56, 161, 24, -56, 162, 28, -57, 163, 32, -58, + 164, 36, -58, 165, 41, -59, 166, 45, -60, 167, 49, -60, + 140, -79, -41, 141, -75, -42, 142, -71, -43, 143, -66, -43, + 144, -62, -44, 145, -58, -45, 146, -54, -45, 147, -50, -46, + 148, -46, -47, 149, -42, -47, 150, -38, -48, 151, -34, -49, + 152, -29, -49, 152, -25, -50, 153, -21, -51, 154, -17, -51, + 155, -13, -52, 156, -9, -53, 157, -5, -53, 158, -1, -54, + 159, 3, -55, 160, 7, -55, 161, 11, -56, 162, 15, -57, + 163, 19, -57, 164, 23, -58, 165, 27, -59, 166, 31, -59, + 166, 35, -60, 167, 40, -61, 168, 44, -61, 169, 48, -62, + 143, -80, -43, 144, -76, -44, 144, -72, -44, 145, -68, -45, + 146, -64, -46, 147, -60, -46, 148, -56, -47, 149, -51, -48, + 150, -47, -48, 151, -43, -49, 152, -39, -50, 153, -35, -50, + 154, -31, -51, 155, -27, -52, 156, -23, -52, 157, -19, -53, + 158, -14, -54, 159, -10, -54, 159, -6, -55, 160, -2, -56, + 161, 1, -56, 162, 5, -57, 163, 9, -58, 164, 13, -58, + 165, 17, -59, 166, 22, -60, 167, 26, -60, 168, 30, -61, + 169, 34, -62, 170, 38, -63, 171, 42, -63, 172, 46, -64, + 145, -82, -45, 146, -78, -45, 147, -74, -46, 148, -69, -47, + 149, -65, -47, 150, -61, -48, 151, -57, -49, 152, -53, -49, + 152, -49, -50, 153, -45, -51, 154, -41, -51, 155, -37, -52, + 156, -32, -53, 157, -28, -53, 158, -24, -54, 159, -20, -55, + 160, -16, -55, 161, -12, -56, 162, -8, -57, 163, -4, -57, + 164, 0, -58, 165, 4, -59, 166, 8, -59, 166, 12, -60, + 167, 16, -61, 168, 20, -62, 169, 24, -62, 170, 28, -63, + 171, 32, -63, 172, 37, -64, 173, 41, -65, 174, 45, -65, + 147, -83, -46, 148, -79, -47, 149, -75, -48, 150, -70, -48, + 151, -66, -49, 152, -62, -50, 153, -58, -50, 154, -54, -51, + 155, -50, -52, 156, -46, -52, 157, -42, -53, 158, -38, -54, + 159, -33, -54, 159, -29, -55, 160, -25, -56, 161, -21, -56, + 162, -17, -57, 163, -13, -58, 164, -9, -58, 165, -5, -59, + 166, 0, -60, 167, 3, -60, 168, 7, -61, 169, 11, -62, + 170, 15, -62, 171, 19, -63, 172, 23, -64, 173, 27, -64, + 174, 31, -65, 175, 36, -66, 175, 40, -67, 176, 44, -67, + 150, -85, -49, 151, -81, -49, 152, -77, -50, 153, -72, -51, + 154, -68, -51, 155, -64, -52, 156, -60, -53, 157, -56, -53, + 158, -52, -54, 159, -48, -55, 160, -44, -55, 160, -40, -56, + 162, -35, -57, 162, -31, -57, 163, -27, -58, 164, -23, -59, + 165, -19, -59, 166, -15, -60, 167, -11, -61, 168, -7, -61, + 169, -2, -62, 170, 1, -63, 171, 5, -63, 172, 9, -64, + 173, 13, -65, 174, 17, -65, 175, 21, -66, 176, 25, -67, + 176, 29, -67, 177, 34, -68, 178, 38, -69, 179, 42, -69, + 153, -86, -50, 153, -82, -51, 154, -78, -51, 155, -73, -52, + 156, -69, -53, 157, -65, -54, 158, -61, -54, 159, -57, -55, + 160, -53, -56, 161, -49, -56, 162, -45, -57, 163, -41, -58, + 164, -36, -58, 165, -32, -59, 166, -28, -60, 167, -24, -60, + 168, -20, -61, 169, -16, -62, 169, -12, -62, 170, -8, -63, + 171, -3, -64, 172, 0, -64, 173, 4, -65, 174, 8, -66, + 175, 12, -66, 176, 16, -67, 177, 20, -68, 178, 24, -68, + 179, 28, -69, 180, 33, -70, 181, 37, -70, 182, 41, -71, + 155, -87, -52, 156, -83, -53, 157, -79, -53, 158, -75, -54, + 159, -71, -55, 160, -67, -55, 161, -63, -56, 162, -58, -57, + 162, -54, -57, 163, -50, -58, 164, -46, -59, 165, -42, -59, + 166, -38, -60, 167, -34, -61, 168, -30, -61, 169, -26, -62, + 170, -21, -63, 171, -17, -63, 172, -13, -64, 173, -9, -65, + 174, -5, -65, 175, -1, -66, 176, 2, -67, 176, 6, -67, + 177, 10, -68, 178, 15, -69, 179, 19, -69, 180, 23, -70, + 181, 27, -71, 182, 31, -71, 183, 35, -72, 184, 39, -73, + 157, -89, -54, 158, -85, -54, 159, -81, -55, 160, -76, -56, + 161, -72, -56, 162, -68, -57, 163, -64, -58, 164, -60, -58, + 165, -56, -59, 166, -52, -60, 167, -48, -60, 168, -44, -61, + 169, -39, -62, 169, -35, -62, 170, -31, -63, 171, -27, -64, + 172, -23, -64, 173, -19, -65, 174, -15, -66, 175, -11, -66, + 176, -6, -67, 177, -2, -68, 178, 1, -68, 179, 5, -69, + 180, 9, -70, 181, 13, -70, 182, 17, -71, 183, 21, -72, + 183, 25, -72, 185, 30, -73, 185, 34, -74, 186, 38, -74, + 160, -90, -55, 161, -86, -56, 161, -82, -57, 162, -77, -57, + 163, -73, -58, 164, -69, -59, 165, -65, -59, 166, -61, -60, + 167, -57, -61, 168, -53, -61, 169, -49, -62, 170, -45, -63, + 171, -40, -63, 172, -36, -64, 173, -32, -65, 174, -28, -65, + 175, -24, -66, 176, -20, -67, 176, -16, -67, 177, -12, -68, + 178, -7, -69, 179, -3, -69, 180, 0, -70, 181, 4, -71, + 182, 8, -71, 183, 12, -72, 184, 16, -73, 185, 20, -73, + 186, 24, -74, 187, 29, -75, 188, 33, -75, 189, 37, -76, + 162, -91, -57, 163, -87, -58, 164, -83, -58, 165, -79, -59, + 166, -75, -60, 167, -71, -60, 168, -67, -61, 169, -62, -62, + 169, -58, -62, 170, -54, -63, 171, -50, -64, 172, -46, -64, + 173, -42, -65, 174, -38, -66, 175, -34, -66, 176, -30, -67, + 177, -25, -68, 178, -21, -68, 179, -17, -69, 180, -13, -70, + 181, -9, -70, 182, -5, -71, 183, -1, -72, 184, 2, -72, + 184, 6, -73, 185, 11, -74, 186, 15, -74, 187, 19, -75, + 188, 23, -76, 189, 27, -76, 190, 31, -77, 191, 35, -78, + 164, -93, -59, 165, -89, -59, 166, -85, -60, 167, -80, -61, + 168, -76, -61, 169, -72, -62, 170, -68, -63, 171, -64, -63, + 172, -60, -64, 173, -56, -65, 174, -52, -65, 175, -48, -66, + 176, -43, -67, 177, -39, -67, 177, -35, -68, 178, -31, -69, + 179, -27, -69, 180, -23, -70, 181, -19, -71, 182, -15, -71, + 183, -10, -72, 184, -6, -73, 185, -2, -73, 186, 1, -74, + 187, 5, -75, 188, 9, -75, 189, 13, -76, 190, 17, -77, + 191, 21, -77, 192, 26, -78, 192, 30, -79, 193, 34, -79, + 167, -94, -60, 168, -90, -61, 168, -86, -62, 170, -81, -62, + 170, -77, -63, 171, -73, -64, 172, -69, -64, 173, -65, -65, + 174, -61, -66, 175, -57, -66, 176, -53, -67, 177, -49, -68, + 178, -44, -68, 179, -40, -69, 180, -36, -70, 181, -32, -70, + 182, -28, -71, 183, -24, -72, 184, -20, -72, 184, -16, -73, + 185, -11, -74, 186, -7, -74, 187, -3, -75, 188, 0, -76, + 189, 4, -76, 190, 8, -77, 191, 12, -78, 192, 16, -78, + 193, 20, -79, 194, 25, -80, 195, 29, -80, 196, 33, -81, + 169, -95, -62, 170, -91, -63, 171, -87, -63, 172, -83, -64, + 173, -79, -65, 174, -75, -65, 175, -71, -66, 176, -66, -67, + 177, -62, -67, 177, -58, -68, 178, -54, -69, 179, -50, -69, + 180, -46, -70, 181, -42, -71, 182, -38, -71, 183, -34, -72, + 184, -29, -73, 185, -25, -73, 186, -21, -74, 187, -17, -75, + 188, -13, -75, 189, -9, -76, 190, -5, -77, 191, -1, -77, + 191, 2, -78, 192, 7, -79, 193, 11, -79, 194, 15, -80, + 195, 19, -81, 196, 23, -81, 197, 27, -82, 198, 31, -83, + 171, -96, -64, 172, -92, -64, 173, -88, -65, 174, -84, -66, + 175, -80, -66, 176, -76, -67, 177, -72, -68, 178, -67, -68, + 179, -63, -69, 180, -59, -70, 181, -55, -70, 182, -51, -71, + 183, -47, -72, 184, -43, -72, 184, -39, -73, 185, -35, -74, + 186, -30, -74, 187, -26, -75, 188, -22, -76, 189, -18, -76, + 190, -14, -77, 191, -10, -78, 192, -6, -78, 193, -2, -79, + 194, 1, -80, 195, 6, -80, 196, 10, -81, 197, 14, -82, + 198, 18, -82, 199, 22, -83, 200, 26, -84, 200, 30, -84, + 174, -98, -65, 175, -94, -66, 176, -90, -67, 177, -85, -67, + 177, -81, -68, 178, -77, -69, 179, -73, -69, 180, -69, -70, + 181, -65, -71, 182, -61, -71, 183, -57, -72, 184, -53, -73, + 185, -48, -73, 186, -44, -74, 187, -40, -75, 188, -36, -75, + 189, -32, -76, 190, -28, -77, 191, -24, -77, 191, -20, -78, + 193, -15, -79, 193, -11, -79, 194, -7, -80, 195, -3, -81, + 196, 0, -81, 197, 4, -82, 198, 8, -83, 199, 12, -83, + 200, 16, -84, 201, 21, -85, 202, 25, -85, 203, 29, -86, + 26, -15, 45, 27, -11, 44, 28, -7, 43, 29, -2, 42, + 30, 1, 42, 31, 5, 41, 32, 9, 41, 33, 13, 40, + 34, 17, 39, 35, 21, 38, 36, 25, 38, 37, 29, 37, + 38, 34, 36, 39, 38, 36, 40, 42, 35, 40, 46, 34, + 41, 50, 34, 42, 54, 33, 43, 58, 32, 44, 62, 32, + 45, 67, 31, 46, 71, 30, 47, 75, 30, 48, 79, 29, + 49, 83, 28, 50, 87, 28, 51, 91, 27, 52, 95, 26, + 53, 99, 26, 54, 104, 25, 55, 108, 24, 55, 112, 24, + 29, -16, 43, 30, -12, 42, 31, -8, 42, 32, -4, 41, + 33, 0, 40, 33, 3, 39, 34, 7, 39, 35, 12, 38, + 36, 16, 37, 37, 20, 37, 38, 24, 36, 39, 28, 36, + 40, 32, 35, 41, 36, 34, 42, 40, 33, 43, 44, 33, + 44, 49, 32, 45, 53, 31, 46, 57, 31, 47, 61, 30, + 48, 65, 29, 48, 69, 29, 49, 73, 28, 50, 77, 27, + 51, 81, 27, 52, 86, 26, 53, 90, 25, 54, 94, 25, + 55, 98, 24, 56, 102, 23, 57, 106, 23, 58, 110, 22, + 31, -17, 41, 32, -13, 41, 33, -9, 40, 34, -5, 39, + 35, -1, 38, 36, 2, 38, 37, 6, 37, 38, 11, 36, + 39, 15, 36, 40, 19, 35, 40, 23, 34, 41, 27, 34, + 42, 31, 33, 43, 35, 32, 44, 39, 32, 45, 43, 31, + 46, 48, 30, 47, 52, 30, 48, 56, 29, 49, 60, 28, + 50, 64, 28, 51, 68, 27, 52, 72, 26, 53, 76, 26, + 54, 80, 25, 55, 85, 24, 56, 89, 24, 56, 93, 23, + 57, 97, 22, 58, 101, 22, 59, 105, 21, 60, 109, 20, + 33, -19, 39, 34, -15, 39, 35, -11, 38, 36, -6, 37, + 37, -2, 37, 38, 1, 36, 39, 5, 35, 40, 9, 35, + 41, 13, 34, 42, 17, 33, 43, 21, 33, 44, 25, 32, + 45, 30, 31, 46, 34, 31, 47, 38, 30, 47, 42, 29, + 49, 46, 29, 49, 50, 28, 50, 54, 27, 51, 58, 27, + 52, 63, 26, 53, 67, 25, 54, 71, 25, 55, 75, 24, + 56, 79, 23, 57, 83, 23, 58, 87, 22, 59, 91, 21, + 60, 95, 21, 61, 100, 20, 62, 104, 19, 63, 108, 19, + 36, -20, 38, 37, -16, 37, 38, -12, 37, 39, -7, 36, + 40, -3, 35, 40, 0, 34, 41, 4, 34, 42, 8, 33, + 43, 12, 32, 44, 16, 32, 45, 20, 31, 46, 24, 30, + 47, 29, 30, 48, 33, 29, 49, 37, 28, 50, 41, 28, + 51, 45, 27, 52, 49, 26, 53, 53, 26, 54, 57, 25, + 55, 62, 24, 56, 66, 24, 56, 70, 23, 57, 74, 22, + 58, 78, 22, 59, 82, 21, 60, 86, 20, 61, 90, 20, + 62, 94, 19, 63, 99, 18, 64, 103, 18, 65, 107, 17, + 38, -21, 36, 39, -17, 35, 40, -13, 35, 41, -9, 34, + 42, -5, 33, 43, -1, 33, 44, 2, 32, 45, 7, 31, + 46, 11, 31, 47, 15, 30, 47, 19, 29, 48, 23, 29, + 49, 27, 28, 50, 31, 27, 51, 35, 27, 52, 39, 26, + 53, 44, 25, 54, 48, 25, 55, 52, 24, 56, 56, 23, + 57, 60, 23, 58, 64, 22, 59, 68, 21, 60, 72, 21, + 61, 76, 20, 62, 81, 19, 63, 85, 19, 63, 89, 18, + 64, 93, 17, 65, 97, 17, 66, 101, 16, 67, 105, 15, + 40, -23, 34, 41, -19, 34, 42, -15, 33, 43, -10, 32, + 44, -6, 32, 45, -2, 31, 46, 1, 30, 47, 5, 30, + 48, 9, 29, 49, 13, 28, 50, 17, 28, 51, 21, 27, + 52, 26, 26, 53, 30, 26, 54, 34, 25, 55, 38, 24, + 56, 42, 24, 56, 46, 23, 57, 50, 22, 58, 54, 22, + 59, 59, 21, 60, 63, 20, 61, 67, 20, 62, 71, 19, + 63, 75, 18, 64, 79, 18, 65, 83, 17, 66, 87, 16, + 67, 91, 16, 68, 96, 15, 69, 100, 14, 70, 104, 14, + 43, -24, 33, 44, -20, 32, 45, -16, 31, 46, -11, 31, + 47, -7, 30, 48, -3, 29, 48, 0, 29, 49, 4, 28, + 50, 8, 27, 51, 12, 27, 52, 16, 26, 53, 20, 25, + 54, 25, 25, 55, 29, 24, 56, 33, 23, 57, 37, 23, + 58, 41, 22, 59, 45, 21, 60, 49, 21, 61, 53, 20, + 62, 58, 19, 63, 62, 19, 63, 66, 18, 64, 70, 17, + 65, 74, 17, 66, 78, 16, 67, 82, 15, 68, 86, 15, + 69, 90, 14, 70, 95, 13, 71, 99, 13, 72, 103, 12, + 45, -25, 31, 46, -21, 30, 47, -17, 30, 48, -13, 29, + 49, -9, 28, 50, -5, 28, 51, -1, 27, 52, 3, 26, + 53, 7, 26, 54, 11, 25, 55, 15, 24, 55, 19, 24, + 56, 23, 23, 57, 27, 22, 58, 31, 22, 59, 35, 21, + 60, 40, 20, 61, 44, 20, 62, 48, 19, 63, 52, 18, + 64, 56, 18, 65, 60, 17, 66, 64, 16, 67, 68, 16, + 68, 72, 15, 69, 77, 14, 70, 81, 14, 71, 85, 13, + 71, 89, 12, 72, 93, 12, 73, 97, 11, 74, 101, 10, + 48, -27, 29, 48, -23, 29, 49, -19, 28, 50, -14, 27, + 51, -10, 27, 52, -6, 26, 53, -2, 25, 54, 1, 25, + 55, 5, 24, 56, 9, 23, 57, 13, 23, 58, 17, 22, + 59, 22, 21, 60, 26, 21, 61, 30, 20, 62, 34, 19, + 63, 38, 19, 64, 42, 18, 64, 46, 17, 65, 50, 17, + 66, 55, 16, 67, 59, 15, 68, 63, 15, 69, 67, 14, + 70, 71, 13, 71, 75, 13, 72, 79, 12, 73, 83, 11, + 74, 87, 11, 75, 92, 10, 76, 96, 9, 77, 100, 9, + 50, -28, 28, 51, -24, 27, 52, -20, 26, 53, -15, 26, + 54, -11, 25, 55, -7, 24, 55, -3, 24, 57, 0, 23, + 57, 4, 22, 58, 8, 22, 59, 12, 21, 60, 16, 20, + 61, 21, 20, 62, 25, 19, 63, 29, 18, 64, 33, 18, + 65, 37, 17, 66, 41, 16, 67, 45, 16, 68, 49, 15, + 69, 54, 14, 70, 58, 14, 71, 62, 13, 71, 66, 12, + 72, 70, 12, 73, 74, 11, 74, 78, 10, 75, 82, 10, + 76, 86, 9, 77, 91, 8, 78, 95, 8, 79, 99, 7, + 53, -30, 26, 54, -26, 25, 55, -22, 24, 56, -17, 24, + 57, -13, 23, 57, -9, 22, 58, -5, 22, 59, -1, 21, + 60, 2, 20, 61, 6, 20, 62, 10, 19, 63, 14, 18, + 64, 19, 18, 65, 23, 17, 66, 27, 16, 67, 31, 16, + 68, 35, 15, 69, 39, 14, 70, 43, 14, 71, 47, 13, + 72, 52, 12, 73, 56, 12, 73, 60, 11, 74, 64, 10, + 75, 68, 10, 76, 72, 9, 77, 76, 8, 78, 80, 8, + 79, 84, 7, 80, 89, 6, 81, 93, 6, 82, 97, 5, + 55, -31, 24, 56, -27, 23, 57, -23, 23, 58, -18, 22, + 59, -14, 21, 60, -10, 21, 61, -6, 20, 62, -2, 19, + 63, 1, 19, 64, 5, 18, 65, 9, 17, 65, 13, 17, + 66, 18, 16, 67, 22, 15, 68, 26, 15, 69, 30, 14, + 70, 34, 13, 71, 38, 13, 72, 42, 12, 73, 46, 11, + 74, 51, 11, 75, 55, 10, 76, 59, 9, 77, 63, 9, + 78, 67, 8, 79, 71, 7, 80, 75, 7, 80, 79, 6, + 81, 83, 5, 82, 88, 5, 83, 92, 4, 84, 96, 3, + 58, -32, 22, 58, -28, 22, 59, -24, 21, 60, -20, 20, + 61, -16, 20, 62, -12, 19, 63, -8, 18, 64, -3, 18, + 65, 0, 17, 66, 4, 16, 67, 8, 16, 68, 12, 15, + 69, 16, 14, 70, 20, 14, 71, 24, 13, 72, 28, 12, + 73, 33, 12, 73, 37, 11, 74, 41, 10, 75, 45, 10, + 76, 49, 9, 77, 53, 8, 78, 57, 8, 79, 61, 7, + 80, 65, 6, 81, 70, 6, 82, 74, 5, 83, 78, 4, + 84, 82, 4, 85, 86, 3, 86, 90, 2, 87, 94, 2, + 60, -34, 21, 61, -30, 20, 62, -26, 19, 63, -21, 19, + 64, -17, 18, 65, -13, 17, 65, -9, 17, 66, -5, 16, + 67, -1, 15, 68, 2, 15, 69, 6, 14, 70, 10, 13, + 71, 15, 13, 72, 19, 12, 73, 23, 11, 74, 27, 11, + 75, 31, 10, 76, 35, 9, 77, 39, 9, 78, 43, 8, + 79, 48, 7, 80, 52, 7, 81, 56, 6, 81, 60, 5, + 82, 64, 5, 83, 68, 4, 84, 72, 3, 85, 76, 3, + 86, 80, 2, 87, 85, 1, 88, 89, 1, 89, 93, 0, + 62, -35, 19, 63, -31, 18, 64, -27, 18, 65, -22, 17, + 66, -18, 16, 67, -14, 16, 68, -10, 15, 69, -6, 14, + 70, -2, 14, 71, 1, 13, 72, 5, 12, 72, 9, 12, + 74, 14, 11, 74, 18, 10, 75, 22, 10, 76, 26, 9, + 77, 30, 8, 78, 34, 8, 79, 38, 7, 80, 42, 6, + 81, 47, 6, 82, 51, 5, 83, 55, 4, 84, 59, 4, + 85, 63, 3, 86, 67, 2, 87, 71, 2, 88, 75, 1, + 88, 79, 0, 89, 84, 0, 90, 88, 0, 91, 92, -1, + 65, -36, 17, 65, -32, 17, 66, -28, 16, 67, -24, 15, + 68, -20, 15, 69, -16, 14, 70, -12, 13, 71, -7, 13, + 72, -3, 12, 73, 0, 11, 74, 4, 11, 75, 8, 10, + 76, 12, 9, 77, 16, 9, 78, 20, 8, 79, 24, 7, + 80, 29, 7, 81, 33, 6, 81, 37, 5, 82, 41, 5, + 83, 45, 4, 84, 49, 3, 85, 53, 3, 86, 57, 2, + 87, 61, 1, 88, 66, 1, 89, 70, 0, 90, 74, 0, + 91, 78, 0, 92, 82, -1, 93, 86, -2, 94, 90, -2, + 67, -38, 16, 68, -34, 15, 69, -30, 14, 70, -25, 14, + 71, -21, 13, 72, -17, 12, 72, -13, 12, 74, -9, 11, + 74, -5, 10, 75, -1, 10, 76, 2, 9, 77, 6, 8, + 78, 11, 8, 79, 15, 7, 80, 19, 6, 81, 23, 6, + 82, 27, 5, 83, 31, 4, 84, 35, 4, 85, 39, 3, + 86, 44, 2, 87, 48, 2, 88, 52, 1, 88, 56, 0, + 89, 60, 0, 90, 64, 0, 91, 68, -1, 92, 72, -1, + 93, 76, -2, 94, 81, -3, 95, 85, -3, 96, 89, -4, + 69, -39, 14, 70, -35, 13, 71, -31, 13, 72, -26, 12, + 73, -22, 11, 74, -18, 11, 75, -14, 10, 76, -10, 9, + 77, -6, 9, 78, -2, 8, 79, 1, 7, 80, 5, 7, + 81, 10, 6, 81, 14, 5, 82, 18, 5, 83, 22, 4, + 84, 26, 3, 85, 30, 3, 86, 34, 2, 87, 38, 1, + 88, 43, 1, 89, 47, 0, 90, 51, 0, 91, 55, 0, + 92, 59, -1, 93, 63, -2, 94, 67, -2, 95, 71, -3, + 95, 75, -4, 97, 80, -4, 97, 84, -5, 98, 88, -6, + 72, -40, 12, 73, -36, 12, 73, -32, 11, 74, -28, 10, + 75, -24, 10, 76, -20, 9, 77, -16, 8, 78, -11, 8, + 79, -7, 7, 80, -3, 6, 81, 0, 6, 82, 4, 5, + 83, 8, 4, 84, 12, 4, 85, 16, 3, 86, 20, 2, + 87, 25, 2, 88, 29, 1, 88, 33, 0, 89, 37, 0, + 90, 41, 0, 91, 45, -1, 92, 49, -1, 93, 53, -2, + 94, 57, -3, 95, 62, -3, 96, 66, -4, 97, 70, -5, + 98, 74, -5, 99, 78, -6, 100, 82, -7, 101, 86, -7, + 74, -42, 11, 75, -38, 10, 76, -34, 9, 77, -29, 9, + 78, -25, 8, 79, -21, 7, 80, -17, 7, 81, -13, 6, + 81, -9, 5, 82, -5, 5, 83, -1, 4, 84, 2, 3, + 85, 7, 3, 86, 11, 2, 87, 15, 1, 88, 19, 1, + 89, 23, 0, 90, 27, 0, 91, 31, 0, 92, 35, -1, + 93, 40, -2, 94, 44, -2, 95, 48, -3, 96, 52, -4, + 96, 56, -4, 97, 60, -5, 98, 64, -6, 99, 68, -6, + 100, 72, -7, 101, 77, -8, 102, 81, -8, 103, 85, -9, + 76, -43, 9, 77, -39, 8, 78, -35, 8, 79, -30, 7, + 80, -26, 6, 81, -22, 6, 82, -18, 5, 83, -14, 4, + 84, -10, 4, 85, -6, 3, 86, -2, 2, 87, 1, 2, + 88, 6, 1, 89, 10, 0, 89, 14, 0, 90, 18, 0, + 91, 22, -1, 92, 26, -1, 93, 30, -2, 94, 34, -3, + 95, 39, -4, 96, 43, -4, 97, 47, -5, 98, 51, -5, + 99, 55, -6, 100, 59, -7, 101, 63, -7, 102, 67, -8, + 103, 71, -9, 104, 76, -10, 104, 80, -10, 105, 84, -11, + 79, -44, 7, 80, -40, 7, 80, -36, 6, 82, -32, 5, + 82, -28, 5, 83, -24, 4, 84, -20, 3, 85, -15, 3, + 86, -11, 2, 87, -7, 1, 88, -3, 1, 89, 0, 0, + 90, 4, 0, 91, 8, 0, 92, 12, -1, 93, 16, -2, + 94, 21, -2, 95, 25, -3, 96, 29, -4, 96, 33, -4, + 97, 37, -5, 98, 41, -6, 99, 45, -6, 100, 49, -7, + 101, 53, -8, 102, 58, -9, 103, 62, -9, 104, 66, -10, + 105, 70, -10, 106, 74, -11, 107, 78, -12, 108, 82, -12, + 81, -45, 6, 82, -41, 5, 83, -37, 4, 84, -33, 4, + 85, -29, 3, 86, -25, 2, 87, -21, 2, 88, -16, 1, + 89, -12, 0, 89, -8, 0, 90, -4, 0, 91, 0, -1, + 92, 3, -1, 93, 7, -2, 94, 11, -3, 95, 15, -3, + 96, 20, -4, 97, 24, -5, 98, 28, -5, 99, 32, -6, + 100, 36, -7, 101, 40, -8, 102, 44, -8, 103, 48, -9, + 103, 52, -9, 104, 57, -10, 105, 61, -11, 106, 65, -11, + 107, 69, -12, 108, 73, -13, 109, 77, -14, 110, 81, -14, + 83, -47, 4, 84, -43, 3, 85, -39, 3, 86, -34, 2, + 87, -30, 1, 88, -26, 1, 89, -22, 0, 90, -18, 0, + 91, -14, 0, 92, -10, -1, 93, -6, -2, 94, -2, -2, + 95, 2, -3, 96, 6, -4, 96, 10, -4, 97, 14, -5, + 98, 18, -6, 99, 22, -6, 100, 26, -7, 101, 30, -8, + 102, 35, -9, 103, 39, -9, 104, 43, -10, 105, 47, -10, + 106, 51, -11, 107, 55, -12, 108, 59, -13, 109, 63, -13, + 110, 67, -14, 111, 72, -15, 112, 76, -15, 112, 80, -16, + 86, -48, 2, 87, -44, 2, 88, -40, 1, 89, -36, 0, + 89, -32, 0, 90, -28, 0, 91, -24, -1, 92, -19, -2, + 93, -15, -2, 94, -11, -3, 95, -7, -3, 96, -3, -4, + 97, 0, -5, 98, 4, -5, 99, 8, -6, 100, 12, -7, + 101, 17, -8, 102, 21, -8, 103, 25, -9, 103, 29, -9, + 105, 33, -10, 105, 37, -11, 106, 41, -12, 107, 45, -12, + 108, 49, -13, 109, 54, -14, 110, 58, -14, 111, 62, -15, + 112, 66, -15, 113, 70, -16, 114, 74, -17, 115, 78, -18, + 88, -49, 1, 89, -45, 0, 90, -41, 0, 91, -37, 0, + 92, -33, -1, 93, -29, -2, 94, -25, -2, 95, -20, -3, + 96, -16, -4, 96, -12, -4, 97, -8, -5, 98, -4, -6, + 99, 0, -7, 100, 3, -7, 101, 7, -8, 102, 11, -8, + 103, 16, -9, 104, 20, -10, 105, 24, -10, 106, 28, -11, + 107, 32, -12, 108, 36, -13, 109, 40, -13, 110, 44, -14, + 111, 48, -14, 112, 53, -15, 112, 57, -16, 113, 61, -17, + 114, 65, -17, 115, 69, -18, 116, 73, -19, 117, 77, -19, + 90, -51, 0, 91, -47, -1, 92, -43, -1, 93, -38, -2, + 94, -34, -3, 95, -30, -3, 96, -26, -4, 97, -22, -5, + 98, -18, -6, 99, -14, -6, 100, -10, -7, 101, -6, -7, + 102, -1, -8, 103, 2, -9, 104, 6, -9, 104, 10, -10, + 105, 14, -11, 106, 18, -12, 107, 22, -12, 108, 26, -13, + 109, 31, -14, 110, 35, -14, 111, 39, -15, 112, 43, -16, + 113, 47, -16, 114, 51, -17, 115, 55, -18, 116, 59, -18, + 117, 63, -19, 118, 68, -20, 119, 72, -20, 119, 76, -21, + 93, -52, -2, 94, -48, -2, 95, -44, -3, 96, -40, -4, + 97, -36, -4, 97, -32, -5, 98, -28, -6, 99, -23, -7, + 100, -19, -7, 101, -15, -8, 102, -11, -8, 103, -7, -9, + 104, -3, -10, 105, 0, -11, 106, 4, -11, 107, 8, -12, + 108, 13, -13, 109, 17, -13, 110, 21, -14, 111, 25, -14, + 112, 29, -15, 112, 33, -16, 113, 37, -17, 114, 41, -17, + 115, 45, -18, 116, 50, -19, 117, 54, -19, 118, 58, -20, + 119, 62, -21, 120, 66, -21, 121, 70, -22, 122, 74, -23, + 95, -53, -3, 96, -49, -4, 97, -45, -5, 98, -41, -6, + 99, -37, -6, 100, -33, -7, 101, -29, -7, 102, -24, -8, + 103, -20, -9, 104, -16, -10, 104, -12, -10, 105, -8, -11, + 106, -4, -12, 107, 0, -12, 108, 3, -13, 109, 7, -13, + 110, 12, -14, 111, 16, -15, 112, 20, -16, 113, 24, -16, + 114, 28, -17, 115, 32, -18, 116, 36, -18, 117, 40, -19, + 118, 44, -20, 119, 49, -20, 119, 53, -21, 120, 57, -22, + 121, 61, -22, 122, 65, -23, 123, 69, -24, 124, 73, -24, + 97, -55, -5, 98, -51, -6, 99, -47, -6, 100, -42, -7, + 101, -38, -8, 102, -34, -8, 103, -30, -9, 104, -26, -10, + 105, -22, -11, 106, -18, -11, 107, -14, -12, 108, -10, -12, + 109, -5, -13, 110, -1, -14, 111, 2, -15, 111, 6, -15, + 112, 10, -16, 113, 14, -17, 114, 18, -17, 115, 22, -18, + 116, 27, -19, 117, 31, -19, 118, 35, -20, 119, 39, -21, + 120, 43, -21, 121, 47, -22, 122, 51, -23, 123, 55, -23, + 124, 59, -24, 125, 64, -25, 126, 68, -25, 127, 72, -26, + 100, -56, -7, 101, -52, -7, 102, -48, -8, 103, -44, -9, + 104, -40, -10, 104, -36, -10, 105, -32, -11, 106, -27, -12, + 107, -23, -12, 108, -19, -13, 109, -15, -14, 110, -11, -14, + 111, -7, -15, 112, -3, -16, 113, 0, -16, 114, 4, -17, + 115, 9, -18, 116, 13, -18, 117, 17, -19, 118, 21, -20, + 119, 25, -20, 120, 29, -21, 120, 33, -22, 121, 37, -22, + 122, 41, -23, 123, 46, -24, 124, 50, -24, 125, 54, -25, + 126, 58, -26, 127, 62, -26, 128, 66, -27, 129, 70, -28, + 103, -58, -9, 104, -54, -10, 105, -50, -10, 106, -45, -11, + 106, -41, -12, 107, -37, -12, 108, -33, -13, 109, -29, -14, + 110, -25, -14, 111, -21, -15, 112, -17, -16, 113, -13, -16, + 114, -8, -17, 115, -4, -18, 116, 0, -18, 117, 3, -19, + 118, 7, -20, 119, 11, -20, 120, 15, -21, 121, 19, -22, + 122, 24, -22, 122, 28, -23, 123, 32, -24, 124, 36, -24, + 125, 40, -25, 126, 44, -26, 127, 48, -26, 128, 52, -27, + 129, 56, -28, 130, 61, -28, 131, 65, -29, 132, 69, -30, + 105, -59, -11, 106, -55, -11, 107, -51, -12, 108, -47, -13, + 109, -43, -13, 110, -39, -14, 111, -35, -15, 112, -30, -15, + 113, -26, -16, 114, -22, -17, 114, -18, -17, 115, -14, -18, + 116, -10, -19, 117, -6, -19, 118, -2, -20, 119, 1, -21, + 120, 6, -21, 121, 10, -22, 122, 14, -23, 123, 18, -23, + 124, 22, -24, 125, 26, -25, 126, 30, -25, 127, 34, -26, + 128, 38, -27, 129, 43, -27, 129, 47, -28, 130, 51, -29, + 131, 55, -29, 132, 59, -30, 133, 63, -31, 134, 67, -31, + 107, -60, -12, 108, -56, -13, 109, -52, -14, 110, -48, -14, + 111, -44, -15, 112, -40, -16, 113, -36, -16, 114, -31, -17, + 115, -27, -18, 116, -23, -18, 117, -19, -19, 118, -15, -20, + 119, -11, -20, 120, -7, -21, 121, -3, -22, 121, 0, -22, + 122, 5, -23, 123, 9, -24, 124, 13, -24, 125, 17, -25, + 126, 21, -26, 127, 25, -26, 128, 29, -27, 129, 33, -28, + 130, 37, -28, 131, 42, -29, 132, 46, -30, 133, 50, -30, + 134, 54, -31, 135, 58, -32, 136, 62, -32, 136, 66, -33, + 110, -62, -14, 111, -58, -15, 112, -54, -15, 113, -49, -16, + 114, -45, -17, 114, -41, -17, 115, -37, -18, 116, -33, -19, + 117, -29, -19, 118, -25, -20, 119, -21, -21, 120, -17, -21, + 121, -12, -22, 122, -8, -23, 123, -4, -23, 124, 0, -24, + 125, 3, -25, 126, 7, -25, 127, 11, -26, 128, 15, -27, + 129, 20, -27, 129, 24, -28, 130, 28, -29, 131, 32, -29, + 132, 36, -30, 133, 40, -31, 134, 44, -31, 135, 48, -32, + 136, 52, -33, 137, 57, -33, 138, 61, -34, 139, 65, -35, + 112, -63, -16, 113, -59, -16, 114, -55, -17, 115, -51, -18, + 116, -47, -18, 117, -43, -19, 118, -39, -20, 119, -34, -20, + 120, -30, -21, 121, -26, -22, 121, -22, -22, 122, -18, -23, + 123, -14, -24, 124, -10, -24, 125, -6, -25, 126, -2, -26, + 127, 2, -26, 128, 6, -27, 129, 10, -28, 130, 14, -28, + 131, 18, -29, 132, 22, -30, 133, 26, -30, 134, 30, -31, + 135, 34, -32, 136, 39, -32, 137, 43, -33, 137, 47, -34, + 138, 51, -34, 139, 55, -35, 140, 59, -36, 141, 63, -36, + 114, -64, -17, 115, -60, -18, 116, -56, -19, 117, -52, -19, + 118, -48, -20, 119, -44, -21, 120, -40, -21, 121, -35, -22, + 122, -31, -23, 123, -27, -23, 124, -23, -24, 125, -19, -25, + 126, -15, -25, 127, -11, -26, 128, -7, -27, 128, -3, -27, + 130, 1, -28, 130, 5, -29, 131, 9, -29, 132, 13, -30, + 133, 17, -31, 134, 21, -31, 135, 25, -32, 136, 29, -33, + 137, 33, -33, 138, 38, -34, 139, 42, -35, 140, 46, -35, + 141, 50, -36, 142, 54, -37, 143, 58, -37, 144, 62, -38, + 117, -66, -19, 118, -62, -20, 119, -58, -20, 120, -53, -21, + 121, -49, -22, 121, -45, -22, 122, -41, -23, 123, -37, -24, + 124, -33, -24, 125, -29, -25, 126, -25, -26, 127, -21, -26, + 128, -16, -27, 129, -12, -28, 130, -8, -28, 131, -4, -29, + 132, 0, -30, 133, 3, -30, 134, 7, -31, 135, 11, -32, + 136, 16, -32, 137, 20, -33, 137, 24, -34, 138, 28, -34, + 139, 32, -35, 140, 36, -36, 141, 40, -36, 142, 44, -37, + 143, 48, -38, 144, 53, -38, 145, 57, -39, 146, 61, -40, + 119, -67, -21, 120, -63, -21, 121, -59, -22, 122, -55, -23, + 123, -51, -23, 124, -47, -24, 125, -43, -25, 126, -38, -25, + 127, -34, -26, 128, -30, -27, 129, -26, -27, 129, -22, -28, + 130, -18, -29, 131, -14, -29, 132, -10, -30, 133, -6, -31, + 134, -1, -31, 135, 2, -32, 136, 6, -33, 137, 10, -33, + 138, 14, -34, 139, 18, -35, 140, 22, -35, 141, 26, -36, + 142, 30, -37, 143, 35, -37, 144, 39, -38, 144, 43, -39, + 145, 47, -39, 146, 51, -40, 147, 55, -41, 148, 59, -41, + 122, -68, -22, 122, -64, -23, 123, -60, -24, 124, -56, -24, + 125, -52, -25, 126, -48, -26, 127, -44, -26, 128, -39, -27, + 129, -35, -28, 130, -31, -28, 131, -27, -29, 132, -23, -30, + 133, -19, -30, 134, -15, -31, 135, -11, -32, 136, -7, -32, + 137, -2, -33, 137, 1, -34, 138, 5, -34, 139, 9, -35, + 140, 13, -36, 141, 17, -36, 142, 21, -37, 143, 25, -38, + 144, 29, -38, 145, 34, -39, 146, 38, -40, 147, 42, -40, + 148, 46, -41, 149, 50, -42, 150, 54, -42, 151, 58, -43, + 124, -70, -24, 125, -66, -25, 126, -62, -25, 127, -57, -26, + 128, -53, -27, 129, -49, -27, 129, -45, -28, 130, -41, -29, + 131, -37, -29, 132, -33, -30, 133, -29, -31, 134, -25, -31, + 135, -20, -32, 136, -16, -33, 137, -12, -33, 138, -8, -34, + 139, -4, -35, 140, 0, -35, 141, 3, -36, 142, 7, -37, + 143, 12, -37, 144, 16, -38, 144, 20, -39, 145, 24, -39, + 146, 28, -40, 147, 32, -41, 148, 36, -41, 149, 40, -42, + 150, 44, -43, 151, 49, -43, 152, 53, -44, 153, 57, -45, + 126, -71, -26, 127, -67, -26, 128, -63, -27, 129, -59, -28, + 130, -55, -28, 131, -51, -29, 132, -47, -30, 133, -42, -30, + 134, -38, -31, 135, -34, -32, 136, -30, -32, 136, -26, -33, + 137, -22, -34, 138, -18, -34, 139, -14, -35, 140, -10, -36, + 141, -5, -36, 142, -1, -37, 143, 2, -38, 144, 6, -38, + 145, 10, -39, 146, 14, -40, 147, 18, -40, 148, 22, -41, + 149, 26, -42, 150, 31, -42, 151, 35, -43, 152, 39, -44, + 152, 43, -44, 153, 47, -45, 154, 51, -46, 155, 55, -46, + 129, -72, -27, 129, -68, -28, 130, -64, -29, 131, -60, -29, + 132, -56, -30, 133, -52, -31, 134, -48, -31, 135, -43, -32, + 136, -39, -33, 137, -35, -33, 138, -31, -34, 139, -27, -35, + 140, -23, -35, 141, -19, -36, 142, -15, -37, 143, -11, -37, + 144, -6, -38, 145, -2, -39, 145, 1, -39, 146, 5, -40, + 147, 9, -41, 148, 13, -41, 149, 17, -42, 150, 21, -43, + 151, 25, -43, 152, 30, -44, 153, 34, -45, 154, 38, -45, + 155, 42, -46, 156, 46, -47, 157, 50, -47, 158, 54, -48, + 131, -74, -29, 132, -70, -30, 133, -66, -30, 134, -61, -31, + 135, -57, -32, 136, -53, -32, 136, -49, -33, 138, -45, -34, + 138, -41, -34, 139, -37, -35, 140, -33, -36, 141, -29, -36, + 142, -24, -37, 143, -20, -38, 144, -16, -38, 145, -12, -39, + 146, -8, -40, 147, -4, -40, 148, 0, -41, 149, 3, -42, + 150, 8, -42, 151, 12, -43, 152, 16, -44, 152, 20, -44, + 153, 24, -45, 154, 28, -46, 155, 32, -46, 156, 36, -47, + 157, 40, -48, 158, 45, -48, 159, 49, -49, 160, 53, -50, + 133, -75, -31, 134, -71, -31, 135, -67, -32, 136, -62, -33, + 137, -58, -33, 138, -54, -34, 139, -50, -35, 140, -46, -35, + 141, -42, -36, 142, -38, -37, 143, -34, -37, 144, -30, -38, + 145, -25, -39, 145, -21, -39, 146, -17, -40, 147, -13, -41, + 148, -9, -41, 149, -5, -42, 150, -1, -43, 151, 2, -43, + 152, 7, -44, 153, 11, -45, 154, 15, -45, 155, 19, -46, + 156, 23, -47, 157, 27, -47, 158, 31, -48, 159, 35, -49, + 159, 39, -49, 160, 44, -50, 161, 48, -51, 162, 52, -51, + 136, -76, -32, 137, -72, -33, 137, -68, -34, 138, -64, -34, + 139, -60, -35, 140, -56, -36, 141, -52, -36, 142, -47, -37, + 143, -43, -38, 144, -39, -38, 145, -35, -39, 146, -31, -40, + 147, -27, -40, 148, -23, -41, 149, -19, -42, 150, -15, -42, + 151, -10, -43, 152, -6, -44, 152, -2, -44, 153, 1, -45, + 154, 5, -46, 155, 9, -46, 156, 13, -47, 157, 17, -48, + 158, 21, -48, 159, 26, -49, 160, 30, -50, 161, 34, -50, + 162, 38, -51, 163, 42, -52, 164, 46, -52, 165, 50, -53, + 138, -78, -34, 139, -74, -35, 140, -70, -35, 141, -65, -36, + 142, -61, -37, 143, -57, -37, 144, -53, -38, 145, -49, -39, + 145, -45, -39, 146, -41, -40, 147, -37, -41, 148, -33, -41, + 149, -28, -42, 150, -24, -43, 151, -20, -43, 152, -16, -44, + 153, -12, -45, 154, -8, -45, 155, -4, -46, 156, 0, -47, + 157, 4, -47, 158, 8, -48, 159, 12, -49, 159, 16, -49, + 160, 20, -50, 161, 24, -51, 162, 28, -51, 163, 32, -52, + 164, 36, -53, 165, 41, -53, 166, 45, -54, 167, 49, -55, + 140, -79, -36, 141, -75, -36, 142, -71, -37, 143, -66, -38, + 144, -62, -38, 145, -58, -39, 146, -54, -40, 147, -50, -40, + 148, -46, -41, 149, -42, -42, 150, -38, -42, 151, -34, -43, + 152, -29, -44, 152, -25, -44, 153, -21, -45, 154, -17, -46, + 155, -13, -46, 156, -9, -47, 157, -5, -48, 158, -1, -48, + 159, 3, -49, 160, 7, -50, 161, 11, -50, 162, 15, -51, + 163, 19, -52, 164, 23, -52, 165, 27, -53, 166, 31, -54, + 167, 35, -54, 168, 40, -55, 168, 44, -56, 169, 48, -56, + 143, -80, -37, 144, -76, -38, 144, -72, -39, 145, -68, -39, + 146, -64, -40, 147, -60, -41, 148, -56, -41, 149, -51, -42, + 150, -47, -43, 151, -43, -43, 152, -39, -44, 153, -35, -45, + 154, -31, -45, 155, -27, -46, 156, -23, -47, 157, -19, -47, + 158, -14, -48, 159, -10, -49, 160, -6, -49, 160, -2, -50, + 161, 1, -51, 162, 5, -51, 163, 9, -52, 164, 13, -53, + 165, 17, -53, 166, 22, -54, 167, 26, -55, 168, 30, -55, + 169, 34, -56, 170, 38, -57, 171, 42, -57, 172, 46, -58, + 145, -82, -39, 146, -78, -40, 147, -74, -40, 148, -69, -41, + 149, -65, -42, 150, -61, -42, 151, -57, -43, 152, -53, -44, + 153, -49, -44, 153, -45, -45, 154, -41, -46, 155, -37, -46, + 156, -32, -47, 157, -28, -48, 158, -24, -48, 159, -20, -49, + 160, -16, -50, 161, -12, -50, 162, -8, -51, 163, -4, -52, + 164, 0, -52, 165, 4, -53, 166, 8, -54, 167, 12, -54, + 167, 16, -55, 168, 20, -56, 169, 24, -56, 170, 28, -57, + 171, 32, -58, 172, 37, -59, 173, 41, -59, 174, 45, -60, + 147, -83, -41, 148, -79, -41, 149, -75, -42, 150, -70, -43, + 151, -66, -43, 152, -62, -44, 153, -58, -45, 154, -54, -45, + 155, -50, -46, 156, -46, -47, 157, -42, -47, 158, -38, -48, + 159, -33, -49, 160, -29, -49, 160, -25, -50, 161, -21, -51, + 162, -17, -51, 163, -13, -52, 164, -9, -53, 165, -5, -53, + 166, 0, -54, 167, 3, -55, 168, 7, -55, 169, 11, -56, + 170, 15, -57, 171, 19, -58, 172, 23, -58, 173, 27, -59, + 174, 31, -59, 175, 36, -60, 175, 40, -61, 176, 44, -61, + 150, -84, -42, 151, -80, -43, 152, -76, -44, 153, -72, -44, + 153, -68, -45, 154, -64, -46, 155, -60, -46, 156, -55, -47, + 157, -51, -48, 158, -47, -48, 159, -43, -49, 160, -39, -50, + 161, -35, -50, 162, -31, -51, 163, -27, -52, 164, -23, -52, + 165, -18, -53, 166, -14, -54, 167, -10, -54, 167, -6, -55, + 168, -2, -56, 169, 1, -56, 170, 5, -57, 171, 9, -58, + 172, 13, -58, 173, 18, -59, 174, 22, -60, 175, 26, -60, + 176, 30, -61, 177, 34, -62, 178, 38, -63, 179, 42, -63, + 153, -86, -45, 154, -82, -45, 154, -78, -46, 155, -73, -47, + 156, -69, -47, 157, -65, -48, 158, -61, -49, 159, -57, -49, + 160, -53, -50, 161, -49, -51, 162, -45, -51, 163, -41, -52, + 164, -36, -53, 165, -32, -53, 166, -28, -54, 167, -24, -55, + 168, -20, -55, 169, -16, -56, 169, -12, -57, 170, -8, -57, + 171, -3, -58, 172, 0, -59, 173, 4, -59, 174, 8, -60, + 175, 12, -61, 176, 16, -61, 177, 20, -62, 178, 24, -63, + 179, 28, -63, 180, 33, -64, 181, 37, -65, 182, 41, -65, + 155, -87, -46, 156, -83, -47, 157, -79, -47, 158, -75, -48, + 159, -71, -49, 160, -67, -50, 161, -63, -50, 162, -58, -51, + 162, -54, -52, 163, -50, -52, 164, -46, -53, 165, -42, -54, + 166, -38, -54, 167, -34, -55, 168, -30, -56, 169, -26, -56, + 170, -21, -57, 171, -17, -58, 172, -13, -58, 173, -9, -59, + 174, -5, -60, 175, -1, -60, 176, 2, -61, 177, 6, -62, + 177, 10, -62, 178, 15, -63, 179, 19, -64, 180, 23, -64, + 181, 27, -65, 182, 31, -66, 183, 35, -66, 184, 39, -67, + 157, -89, -48, 158, -85, -49, 159, -81, -49, 160, -76, -50, + 161, -72, -51, 162, -68, -51, 163, -64, -52, 164, -60, -53, + 165, -56, -53, 166, -52, -54, 167, -48, -55, 168, -44, -55, + 169, -39, -56, 170, -35, -57, 170, -31, -57, 171, -27, -58, + 172, -23, -59, 173, -19, -59, 174, -15, -60, 175, -11, -61, + 176, -6, -61, 177, -2, -62, 178, 1, -63, 179, 5, -63, + 180, 9, -64, 181, 13, -65, 182, 17, -65, 183, 21, -66, + 184, 25, -67, 185, 30, -67, 185, 34, -68, 186, 38, -69, + 160, -90, -50, 161, -86, -50, 161, -82, -51, 163, -77, -52, + 163, -73, -52, 164, -69, -53, 165, -65, -54, 166, -61, -54, + 167, -57, -55, 168, -53, -56, 169, -49, -56, 170, -45, -57, + 171, -40, -58, 172, -36, -58, 173, -32, -59, 174, -28, -60, + 175, -24, -60, 176, -20, -61, 177, -16, -62, 177, -12, -62, + 178, -7, -63, 179, -3, -64, 180, 0, -64, 181, 4, -65, + 182, 8, -66, 183, 12, -66, 184, 16, -67, 185, 20, -68, + 186, 24, -68, 187, 29, -69, 188, 33, -70, 189, 37, -70, + 162, -91, -51, 163, -87, -52, 164, -83, -53, 165, -79, -53, + 166, -75, -54, 167, -71, -55, 168, -67, -55, 169, -62, -56, + 170, -58, -57, 170, -54, -57, 171, -50, -58, 172, -46, -59, + 173, -42, -59, 174, -38, -60, 175, -34, -61, 176, -30, -61, + 177, -25, -62, 178, -21, -63, 179, -17, -63, 180, -13, -64, + 181, -9, -65, 182, -5, -65, 183, -1, -66, 184, 2, -67, + 184, 6, -67, 185, 11, -68, 186, 15, -69, 187, 19, -69, + 188, 23, -70, 189, 27, -71, 190, 31, -71, 191, 35, -72, + 164, -93, -53, 165, -89, -54, 166, -85, -54, 167, -80, -55, + 168, -76, -56, 169, -72, -56, 170, -68, -57, 171, -64, -58, + 172, -60, -58, 173, -56, -59, 174, -52, -60, 175, -48, -60, + 176, -43, -61, 177, -39, -62, 177, -35, -62, 178, -31, -63, + 179, -27, -64, 180, -23, -64, 181, -19, -65, 182, -15, -66, + 183, -10, -66, 184, -6, -67, 185, -2, -68, 186, 1, -68, + 187, 5, -69, 188, 9, -70, 189, 13, -70, 190, 17, -71, + 191, 21, -72, 192, 26, -72, 193, 30, -73, 193, 34, -74, + 167, -94, -55, 168, -90, -55, 169, -86, -56, 170, -81, -57, + 170, -77, -57, 171, -73, -58, 172, -69, -59, 173, -65, -59, + 174, -61, -60, 175, -57, -61, 176, -53, -61, 177, -49, -62, + 178, -44, -63, 179, -40, -63, 180, -36, -64, 181, -32, -65, + 182, -28, -65, 183, -24, -66, 184, -20, -67, 184, -16, -67, + 186, -11, -68, 186, -7, -69, 187, -3, -69, 188, 0, -70, + 189, 4, -71, 190, 8, -71, 191, 12, -72, 192, 16, -73, + 193, 20, -73, 194, 25, -74, 195, 29, -75, 196, 33, -75, + 169, -95, -56, 170, -91, -57, 171, -87, -58, 172, -83, -58, + 173, -79, -59, 174, -75, -60, 175, -71, -60, 176, -66, -61, + 177, -62, -62, 177, -58, -62, 178, -54, -63, 179, -50, -64, + 180, -46, -64, 181, -42, -65, 182, -38, -66, 183, -34, -66, + 184, -29, -67, 185, -25, -68, 186, -21, -68, 187, -17, -69, + 188, -13, -70, 189, -9, -70, 190, -5, -71, 191, -1, -72, + 192, 2, -72, 193, 7, -73, 193, 11, -74, 194, 15, -74, + 195, 19, -75, 196, 23, -76, 197, 27, -76, 198, 31, -77, + 171, -97, -58, 172, -93, -59, 173, -89, -59, 174, -84, -60, + 175, -80, -61, 176, -76, -61, 177, -72, -62, 178, -68, -63, + 179, -64, -63, 180, -60, -64, 181, -56, -65, 182, -52, -65, + 183, -47, -66, 184, -43, -67, 185, -39, -67, 185, -35, -68, + 186, -31, -69, 187, -27, -69, 188, -23, -70, 189, -19, -71, + 190, -14, -71, 191, -10, -72, 192, -6, -73, 193, -2, -73, + 194, 1, -74, 195, 5, -75, 196, 9, -75, 197, 13, -76, + 198, 17, -77, 199, 22, -77, 200, 26, -78, 200, 30, -79, + 174, -98, -60, 175, -94, -60, 176, -90, -61, 177, -85, -62, + 178, -81, -62, 178, -77, -63, 179, -73, -64, 180, -69, -64, + 181, -65, -65, 182, -61, -66, 183, -57, -66, 184, -53, -67, + 185, -48, -68, 186, -44, -68, 187, -40, -69, 188, -36, -70, + 189, -32, -70, 190, -28, -71, 191, -24, -72, 192, -20, -72, + 193, -15, -73, 193, -11, -74, 194, -7, -74, 195, -3, -75, + 196, 0, -76, 197, 4, -76, 198, 8, -77, 199, 12, -78, + 200, 16, -78, 201, 21, -79, 202, 25, -80, 203, 29, -80, + 176, -99, -61, 177, -95, -62, 178, -91, -63, 179, -87, -63, + 180, -83, -64, 181, -79, -65, 182, -75, -65, 183, -70, -66, + 184, -66, -67, 185, -62, -67, 185, -58, -68, 186, -54, -69, + 187, -50, -69, 188, -46, -70, 189, -42, -71, 190, -38, -71, + 191, -33, -72, 192, -29, -73, 193, -25, -73, 194, -21, -74, + 195, -17, -75, 196, -13, -75, 197, -9, -76, 198, -5, -77, + 199, -1, -77, 200, 3, -78, 200, 7, -79, 201, 11, -79, + 202, 15, -80, 203, 19, -81, 204, 23, -81, 205, 27, -82, + 29, -16, 49, 30, -12, 48, 31, -8, 48, 32, -4, 47, + 33, 0, 46, 34, 3, 46, 35, 7, 45, 36, 12, 44, + 37, 16, 44, 38, 20, 43, 38, 24, 42, 39, 28, 42, + 40, 32, 41, 41, 36, 40, 42, 40, 40, 43, 44, 39, + 44, 49, 38, 45, 53, 38, 46, 57, 37, 47, 61, 36, + 48, 65, 36, 49, 69, 35, 50, 73, 34, 51, 77, 34, + 52, 81, 33, 53, 86, 32, 53, 90, 32, 54, 94, 31, + 55, 98, 30, 56, 102, 30, 57, 106, 29, 58, 110, 28, + 31, -18, 47, 32, -14, 47, 33, -10, 46, 34, -5, 45, + 35, -1, 45, 36, 2, 44, 37, 6, 43, 38, 10, 43, + 39, 14, 42, 40, 18, 41, 41, 22, 41, 42, 26, 40, + 43, 31, 39, 44, 35, 39, 45, 39, 38, 45, 43, 37, + 46, 47, 37, 47, 51, 36, 48, 55, 35, 49, 59, 35, + 50, 64, 34, 51, 68, 33, 52, 72, 33, 53, 76, 32, + 54, 80, 31, 55, 84, 31, 56, 88, 30, 57, 92, 29, + 58, 96, 29, 59, 101, 28, 60, 105, 27, 61, 109, 27, + 34, -19, 46, 35, -15, 45, 36, -11, 44, 37, -6, 44, + 38, -2, 43, 38, 1, 42, 39, 5, 42, 40, 9, 41, + 41, 13, 40, 42, 17, 40, 43, 21, 39, 44, 25, 38, + 45, 30, 38, 46, 34, 37, 47, 38, 36, 48, 42, 36, + 49, 46, 35, 50, 50, 34, 51, 54, 34, 52, 58, 33, + 53, 63, 32, 54, 67, 32, 54, 71, 31, 55, 75, 30, + 56, 79, 30, 57, 83, 29, 58, 87, 28, 59, 91, 28, + 60, 95, 27, 61, 100, 26, 62, 104, 26, 63, 108, 25, + 36, -20, 44, 37, -16, 43, 38, -12, 43, 39, -8, 42, + 40, -4, 41, 41, 0, 41, 42, 3, 40, 43, 8, 39, + 44, 12, 39, 45, 16, 38, 45, 20, 37, 46, 24, 37, + 47, 28, 36, 48, 32, 35, 49, 36, 35, 50, 40, 34, + 51, 45, 33, 52, 49, 33, 53, 53, 32, 54, 57, 31, + 55, 61, 31, 56, 65, 30, 57, 69, 29, 58, 73, 29, + 59, 77, 28, 60, 82, 27, 61, 86, 27, 61, 90, 26, + 62, 94, 25, 63, 98, 25, 64, 102, 24, 65, 106, 23, + 38, -22, 42, 39, -18, 42, 40, -14, 41, 41, -9, 40, + 42, -5, 40, 43, -1, 39, 44, 2, 38, 45, 6, 38, + 46, 10, 37, 47, 14, 36, 48, 18, 36, 49, 22, 35, + 50, 27, 34, 51, 31, 34, 52, 35, 33, 53, 39, 32, + 54, 43, 32, 54, 47, 31, 55, 51, 30, 56, 55, 30, + 57, 60, 29, 58, 64, 28, 59, 68, 28, 60, 72, 27, + 61, 76, 26, 62, 80, 26, 63, 84, 25, 64, 88, 24, + 65, 92, 24, 66, 97, 23, 67, 101, 22, 68, 105, 22, + 41, -23, 41, 42, -19, 40, 43, -15, 39, 44, -10, 39, + 45, -6, 38, 46, -2, 37, 46, 1, 37, 47, 5, 36, + 48, 9, 35, 49, 13, 35, 50, 17, 34, 51, 21, 33, + 52, 26, 33, 53, 30, 32, 54, 34, 31, 55, 38, 31, + 56, 42, 30, 57, 46, 29, 58, 50, 29, 59, 54, 28, + 60, 59, 27, 61, 63, 27, 61, 67, 26, 62, 71, 25, + 63, 75, 25, 64, 79, 24, 65, 83, 23, 66, 87, 23, + 67, 91, 22, 68, 96, 21, 69, 100, 21, 70, 104, 20, + 43, -24, 39, 44, -20, 38, 45, -16, 38, 46, -12, 37, + 47, -8, 36, 48, -4, 36, 49, 0, 35, 50, 4, 34, + 51, 8, 34, 52, 12, 33, 53, 16, 32, 53, 20, 32, + 54, 24, 31, 55, 28, 30, 56, 32, 30, 57, 36, 29, + 58, 41, 28, 59, 45, 28, 60, 49, 27, 61, 53, 26, + 62, 57, 26, 63, 61, 25, 64, 65, 24, 65, 69, 24, + 66, 73, 23, 67, 78, 22, 68, 82, 22, 68, 86, 21, + 69, 90, 20, 70, 94, 20, 71, 98, 19, 72, 102, 18, + 46, -25, 37, 46, -21, 37, 47, -17, 36, 48, -13, 35, + 49, -9, 35, 50, -5, 34, 51, -1, 33, 52, 3, 33, + 53, 7, 32, 54, 11, 31, 55, 15, 31, 56, 19, 30, + 57, 23, 29, 58, 27, 29, 59, 31, 28, 60, 35, 27, + 61, 40, 27, 61, 44, 26, 62, 48, 25, 63, 52, 25, + 64, 56, 24, 65, 60, 23, 66, 64, 23, 67, 68, 22, + 68, 72, 21, 69, 77, 21, 70, 81, 20, 71, 85, 19, + 72, 89, 19, 73, 93, 18, 74, 97, 17, 75, 101, 17, + 48, -27, 36, 49, -23, 35, 50, -19, 34, 51, -14, 34, + 52, -10, 33, 53, -6, 32, 53, -2, 32, 54, 1, 31, + 55, 5, 30, 56, 9, 30, 57, 13, 29, 58, 17, 28, + 59, 22, 28, 60, 26, 27, 61, 30, 26, 62, 34, 26, + 63, 38, 25, 64, 42, 24, 65, 46, 24, 66, 50, 23, + 67, 55, 22, 68, 59, 22, 69, 63, 21, 69, 67, 20, + 70, 71, 20, 71, 75, 19, 72, 79, 18, 73, 83, 18, + 74, 87, 17, 75, 92, 16, 76, 96, 16, 77, 100, 15, + 50, -28, 34, 51, -24, 33, 52, -20, 33, 53, -16, 32, + 54, -12, 31, 55, -8, 31, 56, -4, 30, 57, 0, 29, + 58, 4, 29, 59, 8, 28, 60, 12, 27, 60, 16, 27, + 62, 20, 26, 62, 24, 25, 63, 28, 25, 64, 32, 24, + 65, 37, 23, 66, 41, 23, 67, 45, 22, 68, 49, 21, + 69, 53, 21, 70, 57, 20, 71, 61, 19, 72, 65, 19, + 73, 69, 18, 74, 74, 17, 75, 78, 17, 76, 82, 16, + 76, 86, 15, 77, 90, 14, 78, 94, 14, 79, 98, 13, + 53, -29, 32, 53, -25, 32, 54, -21, 31, 55, -17, 30, + 56, -13, 30, 57, -9, 29, 58, -5, 28, 59, 0, 28, + 60, 3, 27, 61, 7, 26, 62, 11, 26, 63, 15, 25, + 64, 19, 24, 65, 23, 24, 66, 27, 23, 67, 31, 22, + 68, 36, 22, 69, 40, 21, 69, 44, 20, 70, 48, 20, + 71, 52, 19, 72, 56, 18, 73, 60, 18, 74, 64, 17, + 75, 68, 16, 76, 73, 16, 77, 77, 15, 78, 81, 14, + 79, 85, 14, 80, 89, 13, 81, 93, 12, 82, 97, 12, + 56, -31, 30, 56, -27, 30, 57, -23, 29, 58, -19, 28, + 59, -15, 27, 60, -11, 27, 61, -7, 26, 62, -2, 25, + 63, 1, 25, 64, 5, 24, 65, 9, 23, 66, 13, 23, + 67, 17, 22, 68, 21, 21, 69, 25, 21, 70, 29, 20, + 71, 34, 19, 71, 38, 19, 72, 42, 18, 73, 46, 17, + 74, 50, 17, 75, 54, 16, 76, 58, 15, 77, 62, 15, + 78, 66, 14, 79, 71, 13, 80, 75, 13, 81, 79, 12, + 82, 83, 11, 83, 87, 11, 84, 91, 10, 85, 95, 9, + 58, -32, 28, 59, -28, 28, 60, -24, 27, 61, -20, 26, + 62, -16, 26, 63, -12, 25, 63, -8, 25, 64, -3, 24, + 65, 0, 23, 66, 4, 22, 67, 8, 22, 68, 12, 21, + 69, 16, 20, 70, 20, 20, 71, 24, 19, 72, 28, 18, + 73, 33, 18, 74, 37, 17, 75, 41, 16, 76, 45, 16, + 77, 49, 15, 78, 53, 14, 78, 57, 14, 79, 61, 13, + 80, 65, 12, 81, 70, 12, 82, 74, 11, 83, 78, 10, + 84, 82, 10, 85, 86, 9, 86, 90, 8, 87, 94, 8, + 60, -34, 27, 61, -30, 26, 62, -26, 26, 63, -21, 25, + 64, -17, 24, 65, -13, 23, 66, -9, 23, 67, -5, 22, + 68, -1, 21, 69, 2, 21, 70, 6, 20, 70, 10, 19, + 71, 15, 19, 72, 19, 18, 73, 23, 17, 74, 27, 17, + 75, 31, 16, 76, 35, 15, 77, 39, 15, 78, 43, 14, + 79, 48, 13, 80, 52, 13, 81, 56, 12, 82, 60, 11, + 83, 64, 11, 84, 68, 10, 85, 72, 9, 86, 76, 9, + 86, 80, 8, 87, 85, 7, 88, 89, 7, 89, 93, 6, + 63, -35, 25, 63, -31, 24, 64, -27, 24, 65, -23, 23, + 66, -19, 22, 67, -15, 22, 68, -11, 21, 69, -6, 20, + 70, -2, 20, 71, 1, 19, 72, 5, 18, 73, 9, 18, + 74, 13, 17, 75, 17, 16, 76, 21, 16, 77, 25, 15, + 78, 30, 14, 79, 34, 14, 79, 38, 13, 80, 42, 12, + 81, 46, 12, 82, 50, 11, 83, 54, 10, 84, 58, 10, + 85, 62, 9, 86, 67, 8, 87, 71, 8, 88, 75, 7, + 89, 79, 6, 90, 83, 6, 91, 87, 5, 92, 91, 4, + 65, -36, 23, 66, -32, 23, 67, -28, 22, 68, -24, 21, + 69, -20, 21, 70, -16, 20, 70, -12, 19, 72, -7, 19, + 72, -3, 18, 73, 0, 17, 74, 4, 17, 75, 8, 16, + 76, 12, 15, 77, 16, 15, 78, 20, 14, 79, 24, 13, + 80, 29, 13, 81, 33, 12, 82, 37, 11, 83, 41, 11, + 84, 45, 10, 85, 49, 9, 86, 53, 9, 86, 57, 8, + 87, 61, 7, 88, 66, 7, 89, 70, 6, 90, 74, 5, + 91, 78, 5, 92, 82, 4, 93, 86, 3, 94, 90, 3, + 67, -38, 22, 68, -34, 21, 69, -30, 20, 70, -25, 20, + 71, -21, 19, 72, -17, 18, 73, -13, 18, 74, -9, 17, + 75, -5, 16, 76, -1, 16, 77, 2, 15, 78, 6, 14, + 79, 11, 14, 79, 15, 13, 80, 19, 12, 81, 23, 12, + 82, 27, 11, 83, 31, 10, 84, 35, 10, 85, 39, 9, + 86, 44, 8, 87, 48, 8, 88, 52, 7, 89, 56, 6, + 90, 60, 6, 91, 64, 5, 92, 68, 4, 93, 72, 4, + 93, 76, 3, 95, 81, 2, 95, 85, 2, 96, 89, 1, + 70, -39, 20, 71, -35, 19, 71, -31, 19, 72, -27, 18, + 73, -23, 17, 74, -19, 17, 75, -15, 16, 76, -10, 15, + 77, -6, 15, 78, -2, 14, 79, 1, 13, 80, 5, 13, + 81, 9, 12, 82, 13, 11, 83, 17, 11, 84, 21, 10, + 85, 26, 9, 86, 30, 9, 86, 34, 8, 87, 38, 7, + 88, 42, 7, 89, 46, 6, 90, 50, 5, 91, 54, 5, + 92, 58, 4, 93, 63, 3, 94, 67, 3, 95, 71, 2, + 96, 75, 1, 97, 79, 1, 98, 83, 0, 99, 87, 0, + 72, -40, 18, 73, -36, 18, 74, -32, 17, 75, -28, 16, + 76, -24, 16, 77, -20, 15, 78, -16, 14, 79, -11, 14, + 79, -7, 13, 80, -3, 12, 81, 0, 12, 82, 4, 11, + 83, 8, 10, 84, 12, 10, 85, 16, 9, 86, 20, 8, + 87, 25, 8, 88, 29, 7, 89, 33, 6, 90, 37, 6, + 91, 41, 5, 92, 45, 4, 93, 49, 4, 93, 53, 3, + 94, 57, 2, 95, 62, 2, 96, 66, 1, 97, 70, 0, + 98, 74, 0, 99, 78, 0, 100, 82, -1, 101, 86, -1, + 74, -42, 17, 75, -38, 16, 76, -34, 15, 77, -29, 15, + 78, -25, 14, 79, -21, 13, 80, -17, 13, 81, -13, 12, + 82, -9, 11, 83, -5, 11, 84, -1, 10, 85, 2, 9, + 86, 7, 9, 86, 11, 8, 87, 15, 7, 88, 19, 7, + 89, 23, 6, 90, 27, 5, 91, 31, 5, 92, 35, 4, + 93, 40, 3, 94, 44, 3, 95, 48, 2, 96, 52, 1, + 97, 56, 1, 98, 60, 0, 99, 64, 0, 100, 68, 0, + 101, 72, -1, 102, 77, -2, 102, 81, -2, 103, 85, -3, + 77, -43, 15, 78, -39, 14, 78, -35, 14, 79, -31, 13, + 80, -27, 12, 81, -23, 12, 82, -19, 11, 83, -14, 10, + 84, -10, 10, 85, -6, 9, 86, -2, 8, 87, 1, 8, + 88, 5, 7, 89, 9, 6, 90, 13, 6, 91, 17, 5, + 92, 22, 4, 93, 26, 4, 94, 30, 3, 94, 34, 2, + 95, 38, 2, 96, 42, 1, 97, 46, 0, 98, 50, 0, + 99, 54, 0, 100, 59, -1, 101, 63, -1, 102, 67, -2, + 103, 71, -3, 104, 75, -3, 105, 79, -4, 106, 83, -5, + 79, -44, 13, 80, -40, 13, 81, -36, 12, 82, -32, 11, + 83, -28, 11, 84, -24, 10, 85, -20, 9, 86, -15, 9, + 87, -11, 8, 87, -7, 7, 88, -3, 7, 89, 0, 6, + 90, 4, 5, 91, 8, 5, 92, 12, 4, 93, 16, 3, + 94, 21, 3, 95, 25, 2, 96, 29, 1, 97, 33, 1, + 98, 37, 0, 99, 41, 0, 100, 45, 0, 101, 49, -1, + 101, 53, -2, 102, 58, -2, 103, 62, -3, 104, 66, -4, + 105, 70, -4, 106, 74, -5, 107, 78, -6, 108, 82, -6, + 81, -46, 12, 82, -42, 11, 83, -38, 10, 84, -33, 10, + 85, -29, 9, 86, -25, 8, 87, -21, 8, 88, -17, 7, + 89, -13, 6, 90, -9, 6, 91, -5, 5, 92, -1, 4, + 93, 3, 4, 94, 7, 3, 94, 11, 2, 95, 15, 2, + 96, 19, 1, 97, 23, 0, 98, 27, 0, 99, 31, 0, + 100, 36, -1, 101, 40, -1, 102, 44, -2, 103, 48, -3, + 104, 52, -3, 105, 56, -4, 106, 60, -5, 107, 64, -5, + 108, 68, -6, 109, 73, -7, 110, 77, -7, 110, 81, -8, + 84, -47, 10, 85, -43, 9, 86, -39, 9, 87, -35, 8, + 87, -31, 7, 88, -27, 7, 89, -23, 6, 90, -18, 5, + 91, -14, 5, 92, -10, 4, 93, -6, 3, 94, -2, 3, + 95, 1, 2, 96, 5, 1, 97, 9, 1, 98, 13, 0, + 99, 18, 0, 100, 22, 0, 101, 26, -1, 101, 30, -2, + 103, 34, -2, 103, 38, -3, 104, 42, -4, 105, 46, -4, + 106, 50, -5, 107, 55, -6, 108, 59, -6, 109, 63, -7, + 110, 67, -8, 111, 71, -8, 112, 75, -9, 113, 79, -10, + 86, -48, 8, 87, -44, 8, 88, -40, 7, 89, -36, 6, + 90, -32, 6, 91, -28, 5, 92, -24, 4, 93, -19, 4, + 94, -15, 3, 94, -11, 2, 95, -7, 2, 96, -3, 1, + 97, 0, 0, 98, 4, 0, 99, 8, 0, 100, 12, -1, + 101, 17, -1, 102, 21, -2, 103, 25, -3, 104, 29, -3, + 105, 33, -4, 106, 37, -5, 107, 41, -5, 108, 45, -6, + 108, 49, -7, 110, 54, -7, 110, 58, -8, 111, 62, -9, + 112, 66, -9, 113, 70, -10, 114, 74, -11, 115, 78, -11, + 88, -50, 7, 89, -46, 6, 90, -42, 5, 91, -37, 5, + 92, -33, 4, 93, -29, 3, 94, -25, 3, 95, -21, 2, + 96, -17, 1, 97, -13, 1, 98, -9, 0, 99, -5, 0, + 100, 0, 0, 101, 3, -1, 101, 7, -2, 102, 11, -2, + 103, 15, -3, 104, 19, -4, 105, 23, -4, 106, 27, -5, + 107, 32, -6, 108, 36, -6, 109, 40, -7, 110, 44, -8, + 111, 48, -8, 112, 52, -9, 113, 56, -10, 114, 60, -10, + 115, 64, -11, 116, 69, -12, 117, 73, -12, 117, 77, -13, + 91, -51, 5, 92, -47, 4, 93, -43, 4, 94, -38, 3, + 94, -34, 2, 95, -30, 2, 96, -26, 1, 97, -22, 0, + 98, -18, 0, 99, -14, 0, 100, -10, -1, 101, -6, -1, + 102, -1, -2, 103, 2, -3, 104, 6, -3, 105, 10, -4, + 106, 14, -5, 107, 18, -5, 108, 22, -6, 109, 26, -7, + 110, 31, -7, 110, 35, -8, 111, 39, -9, 112, 43, -9, + 113, 47, -10, 114, 51, -11, 115, 55, -11, 116, 59, -12, + 117, 63, -13, 118, 68, -13, 119, 72, -14, 120, 76, -15, + 93, -52, 3, 94, -48, 3, 95, -44, 2, 96, -40, 1, + 97, -36, 1, 98, -32, 0, 99, -28, 0, 100, -23, 0, + 101, -19, -1, 102, -15, -2, 102, -11, -2, 103, -7, -3, + 104, -3, -4, 105, 0, -4, 106, 4, -5, 107, 8, -6, + 108, 13, -6, 109, 17, -7, 110, 21, -8, 111, 25, -8, + 112, 29, -9, 113, 33, -10, 114, 37, -10, 115, 41, -11, + 116, 45, -12, 117, 50, -12, 117, 54, -13, 118, 58, -14, + 119, 62, -14, 120, 66, -15, 121, 70, -16, 122, 74, -16, + 95, -54, 2, 96, -50, 1, 97, -46, 0, 98, -41, 0, + 99, -37, 0, 100, -33, -1, 101, -29, -1, 102, -25, -2, + 103, -21, -3, 104, -17, -3, 105, -13, -4, 106, -9, -5, + 107, -4, -5, 108, 0, -6, 109, 3, -7, 109, 7, -7, + 110, 11, -8, 111, 15, -9, 112, 19, -9, 113, 23, -10, + 114, 28, -11, 115, 32, -11, 116, 36, -12, 117, 40, -13, + 118, 44, -13, 119, 48, -14, 120, 52, -15, 121, 56, -15, + 122, 60, -16, 123, 65, -17, 124, 69, -17, 125, 73, -18, + 98, -55, 0, 99, -51, 0, 100, -47, 0, 101, -42, -1, + 102, -38, -2, 102, -34, -2, 103, -30, -3, 104, -26, -4, + 105, -22, -4, 106, -18, -5, 107, -14, -6, 108, -10, -6, + 109, -5, -7, 110, -1, -8, 111, 2, -8, 112, 6, -9, + 113, 10, -10, 114, 14, -10, 115, 18, -11, 116, 22, -12, + 117, 27, -12, 118, 31, -13, 118, 35, -14, 119, 39, -14, + 120, 43, -15, 121, 47, -16, 122, 51, -16, 123, 55, -17, + 124, 59, -18, 125, 64, -18, 126, 68, -19, 127, 72, -20, + 100, -56, -1, 101, -52, -1, 102, -48, -2, 103, -44, -3, + 104, -40, -3, 105, -36, -4, 106, -32, -5, 107, -27, -5, + 108, -23, -6, 109, -19, -7, 109, -15, -7, 110, -11, -8, + 111, -7, -9, 112, -3, -9, 113, 0, -10, 114, 4, -11, + 115, 9, -11, 116, 13, -12, 117, 17, -13, 118, 21, -13, + 119, 25, -14, 120, 29, -15, 121, 33, -15, 122, 37, -16, + 123, 41, -17, 124, 46, -17, 125, 50, -18, 125, 54, -19, + 126, 58, -19, 127, 62, -20, 128, 66, -21, 129, 70, -21, + 102, -58, -2, 103, -54, -3, 104, -50, -4, 105, -45, -4, + 106, -41, -5, 107, -37, -6, 108, -33, -6, 109, -29, -7, + 110, -25, -8, 111, -21, -8, 112, -17, -9, 113, -13, -10, + 114, -8, -10, 115, -4, -11, 116, 0, -12, 116, 3, -12, + 118, 7, -13, 118, 11, -14, 119, 15, -14, 120, 19, -15, + 121, 24, -16, 122, 28, -16, 123, 32, -17, 124, 36, -18, + 125, 40, -18, 126, 44, -19, 127, 48, -20, 128, 52, -20, + 129, 56, -21, 130, 61, -22, 131, 65, -22, 132, 69, -23, + 105, -59, -4, 106, -55, -5, 107, -51, -6, 108, -47, -6, + 109, -43, -7, 110, -39, -8, 111, -35, -8, 112, -30, -9, + 113, -26, -10, 114, -22, -10, 115, -18, -11, 116, -14, -12, + 117, -10, -12, 118, -6, -13, 119, -2, -14, 119, 1, -14, + 120, 6, -15, 121, 10, -16, 122, 14, -16, 123, 18, -17, + 124, 22, -18, 125, 26, -18, 126, 30, -19, 127, 34, -20, + 128, 38, -20, 129, 43, -21, 130, 47, -22, 131, 51, -22, + 132, 55, -23, 133, 59, -24, 134, 63, -25, 134, 67, -25, + 108, -61, -6, 109, -57, -7, 110, -53, -7, 111, -48, -8, + 112, -44, -9, 112, -40, -9, 113, -36, -10, 114, -32, -11, + 115, -28, -11, 116, -24, -12, 117, -20, -13, 118, -16, -13, + 119, -11, -14, 120, -7, -15, 121, -3, -15, 122, 0, -16, + 123, 4, -17, 124, 8, -17, 125, 12, -18, 126, 16, -19, + 127, 21, -20, 127, 25, -20, 128, 29, -21, 129, 33, -21, + 130, 37, -22, 131, 41, -23, 132, 45, -24, 133, 49, -24, + 134, 53, -25, 135, 58, -26, 136, 62, -26, 137, 66, -27, + 110, -62, -8, 111, -58, -8, 112, -54, -9, 113, -49, -10, + 114, -45, -10, 115, -41, -11, 116, -37, -12, 117, -33, -12, + 118, -29, -13, 119, -25, -14, 119, -21, -14, 120, -17, -15, + 121, -12, -16, 122, -8, -16, 123, -4, -17, 124, 0, -18, + 125, 3, -19, 126, 7, -19, 127, 11, -20, 128, 15, -20, + 129, 20, -21, 130, 24, -22, 131, 28, -22, 132, 32, -23, + 133, 36, -24, 134, 40, -25, 135, 44, -25, 135, 48, -26, + 136, 52, -26, 137, 57, -27, 138, 61, -28, 139, 65, -29, + 112, -63, -9, 113, -59, -10, 114, -55, -11, 115, -51, -11, + 116, -47, -12, 117, -43, -13, 118, -39, -13, 119, -34, -14, + 120, -30, -15, 121, -26, -15, 122, -22, -16, 123, -18, -17, + 124, -14, -18, 125, -10, -18, 126, -6, -19, 126, -2, -19, + 128, 2, -20, 128, 6, -21, 129, 10, -21, 130, 14, -22, + 131, 18, -23, 132, 22, -24, 133, 26, -24, 134, 30, -25, + 135, 34, -25, 136, 39, -26, 137, 43, -27, 138, 47, -28, + 139, 51, -28, 140, 55, -29, 141, 59, -30, 142, 63, -30, + 115, -65, -11, 116, -61, -12, 117, -57, -12, 118, -52, -13, + 119, -48, -14, 119, -44, -14, 120, -40, -15, 121, -36, -16, + 122, -32, -16, 123, -28, -17, 124, -24, -18, 125, -20, -18, + 126, -15, -19, 127, -11, -20, 128, -7, -20, 129, -3, -21, + 130, 0, -22, 131, 4, -23, 132, 8, -23, 133, 12, -24, + 134, 17, -25, 135, 21, -25, 135, 25, -26, 136, 29, -26, + 137, 33, -27, 138, 37, -28, 139, 41, -29, 140, 45, -29, + 141, 49, -30, 142, 54, -31, 143, 58, -31, 144, 62, -32, + 117, -66, -13, 118, -62, -13, 119, -58, -14, 120, -53, -15, + 121, -49, -15, 122, -45, -16, 123, -41, -17, 124, -37, -18, + 125, -33, -18, 126, -29, -19, 126, -25, -19, 127, -21, -20, + 128, -16, -21, 129, -12, -22, 130, -8, -22, 131, -4, -23, + 132, 0, -24, 133, 3, -24, 134, 7, -25, 135, 11, -25, + 136, 16, -26, 137, 20, -27, 138, 24, -28, 139, 28, -28, + 140, 32, -29, 141, 36, -30, 142, 40, -30, 142, 44, -31, + 143, 48, -32, 144, 53, -32, 145, 57, -33, 146, 61, -34, + 119, -67, -14, 120, -63, -15, 121, -59, -16, 122, -55, -17, + 123, -51, -17, 124, -47, -18, 125, -43, -18, 126, -38, -19, + 127, -34, -20, 128, -30, -20, 129, -26, -21, 130, -22, -22, + 131, -18, -23, 132, -14, -23, 133, -10, -24, 134, -6, -24, + 135, -1, -25, 135, 2, -26, 136, 6, -27, 137, 10, -27, + 138, 14, -28, 139, 18, -29, 140, 22, -29, 141, 26, -30, + 142, 30, -30, 143, 35, -31, 144, 39, -32, 145, 43, -33, + 146, 47, -33, 147, 51, -34, 148, 55, -35, 149, 59, -35, + 122, -69, -16, 123, -65, -17, 124, -61, -17, 125, -56, -18, + 126, -52, -19, 127, -48, -19, 127, -44, -20, 128, -40, -21, + 129, -36, -22, 130, -32, -22, 131, -28, -23, 132, -24, -23, + 133, -19, -24, 134, -15, -25, 135, -11, -26, 136, -7, -26, + 137, -3, -27, 138, 0, -28, 139, 4, -28, 140, 8, -29, + 141, 13, -30, 142, 17, -30, 142, 21, -31, 143, 25, -32, + 144, 29, -32, 145, 33, -33, 146, 37, -34, 147, 41, -34, + 148, 45, -35, 149, 50, -36, 150, 54, -36, 151, 58, -37, + 124, -70, -18, 125, -66, -18, 126, -62, -19, 127, -57, -20, + 128, -53, -21, 129, -49, -21, 130, -45, -22, 131, -41, -23, + 132, -37, -23, 133, -33, -24, 134, -29, -24, 134, -25, -25, + 135, -20, -26, 136, -16, -27, 137, -12, -27, 138, -8, -28, + 139, -4, -29, 140, 0, -29, 141, 3, -30, 142, 7, -31, + 143, 12, -31, 144, 16, -32, 145, 20, -33, 146, 24, -33, + 147, 28, -34, 148, 32, -35, 149, 36, -35, 150, 40, -36, + 150, 44, -37, 151, 49, -37, 152, 53, -38, 153, 57, -39, + 127, -71, -20, 127, -67, -20, 128, -63, -21, 129, -59, -22, + 130, -55, -22, 131, -51, -23, 132, -47, -23, 133, -42, -24, + 134, -38, -25, 135, -34, -26, 136, -30, -26, 137, -26, -27, + 138, -22, -28, 139, -18, -28, 140, -14, -29, 141, -10, -30, + 142, -5, -30, 143, -1, -31, 143, 2, -32, 144, 6, -32, + 145, 10, -33, 146, 14, -34, 147, 18, -34, 148, 22, -35, + 149, 26, -36, 150, 31, -36, 151, 35, -37, 152, 39, -38, + 153, 43, -38, 154, 47, -39, 155, 51, -40, 156, 55, -40, + 129, -73, -21, 130, -69, -22, 131, -65, -22, 132, -60, -23, + 133, -56, -24, 134, -52, -25, 134, -48, -25, 136, -44, -26, + 136, -40, -27, 137, -36, -27, 138, -32, -28, 139, -28, -28, + 140, -23, -29, 141, -19, -30, 142, -15, -31, 143, -11, -31, + 144, -7, -32, 145, -3, -33, 146, 0, -33, 147, 4, -34, + 148, 9, -35, 149, 13, -35, 150, 17, -36, 150, 21, -37, + 151, 25, -37, 152, 29, -38, 153, 33, -39, 154, 37, -39, + 155, 41, -40, 156, 46, -41, 157, 50, -41, 158, 54, -42, + 131, -74, -23, 132, -70, -24, 133, -66, -24, 134, -61, -25, + 135, -57, -26, 136, -53, -26, 137, -49, -27, 138, -45, -28, + 139, -41, -28, 140, -37, -29, 141, -33, -30, 141, -29, -30, + 143, -24, -31, 143, -20, -32, 144, -16, -32, 145, -12, -33, + 146, -8, -34, 147, -4, -34, 148, 0, -35, 149, 3, -36, + 150, 8, -36, 151, 12, -37, 152, 16, -38, 153, 20, -38, + 154, 24, -39, 155, 28, -40, 156, 32, -40, 157, 36, -41, + 157, 40, -42, 158, 45, -42, 159, 49, -43, 160, 53, -44, + 134, -75, -25, 134, -71, -25, 135, -67, -26, 136, -63, -27, + 137, -59, -27, 138, -55, -28, 139, -51, -29, 140, -46, -29, + 141, -42, -30, 142, -38, -31, 143, -34, -31, 144, -30, -32, + 145, -26, -33, 146, -22, -33, 147, -18, -34, 148, -14, -35, + 149, -9, -35, 150, -5, -36, 150, -1, -37, 151, 2, -37, + 152, 6, -38, 153, 10, -39, 154, 14, -39, 155, 18, -40, + 156, 22, -41, 157, 27, -41, 158, 31, -42, 159, 35, -43, + 160, 39, -43, 161, 43, -44, 162, 47, -45, 163, 51, -45, + 136, -76, -26, 137, -72, -27, 138, -68, -28, 139, -64, -28, + 140, -60, -29, 141, -56, -30, 142, -52, -30, 143, -47, -31, + 143, -43, -32, 144, -39, -32, 145, -35, -33, 146, -31, -34, + 147, -27, -34, 148, -23, -35, 149, -19, -36, 150, -15, -36, + 151, -10, -37, 152, -6, -38, 153, -2, -38, 154, 1, -39, + 155, 5, -40, 156, 9, -40, 157, 13, -41, 157, 17, -42, + 158, 21, -42, 159, 26, -43, 160, 30, -44, 161, 34, -44, + 162, 38, -45, 163, 42, -46, 164, 46, -46, 165, 50, -47, + 138, -78, -28, 139, -74, -29, 140, -70, -29, 141, -65, -30, + 142, -61, -31, 143, -57, -31, 144, -53, -32, 145, -49, -33, + 146, -45, -33, 147, -41, -34, 148, -37, -35, 149, -33, -35, + 150, -28, -36, 150, -24, -37, 151, -20, -37, 152, -16, -38, + 153, -12, -39, 154, -8, -39, 155, -4, -40, 156, 0, -41, + 157, 4, -41, 158, 8, -42, 159, 12, -43, 160, 16, -43, + 161, 20, -44, 162, 24, -45, 163, 28, -45, 164, 32, -46, + 165, 36, -47, 166, 41, -47, 166, 45, -48, 167, 49, -49, + 141, -79, -30, 142, -75, -30, 142, -71, -31, 143, -67, -32, + 144, -63, -32, 145, -59, -33, 146, -55, -34, 147, -50, -34, + 148, -46, -35, 149, -42, -36, 150, -38, -36, 151, -34, -37, + 152, -30, -38, 153, -26, -38, 154, -22, -39, 155, -18, -40, + 156, -13, -40, 157, -9, -41, 158, -5, -42, 158, -1, -42, + 159, 2, -43, 160, 6, -44, 161, 10, -44, 162, 14, -45, + 163, 18, -46, 164, 23, -46, 165, 27, -47, 166, 31, -48, + 167, 35, -48, 168, 39, -49, 169, 43, -50, 170, 47, -50, + 143, -80, -31, 144, -76, -32, 145, -72, -33, 146, -68, -33, + 147, -64, -34, 148, -60, -35, 149, -56, -35, 150, -51, -36, + 151, -47, -37, 151, -43, -37, 152, -39, -38, 153, -35, -39, + 154, -31, -39, 155, -27, -40, 156, -23, -41, 157, -19, -41, + 158, -14, -42, 159, -10, -43, 160, -6, -43, 161, -2, -44, + 162, 1, -45, 163, 5, -45, 164, 9, -46, 165, 13, -47, + 165, 17, -47, 166, 22, -48, 167, 26, -49, 168, 30, -49, + 169, 34, -50, 170, 38, -51, 171, 42, -51, 172, 46, -52, + 145, -82, -33, 146, -78, -34, 147, -74, -34, 148, -69, -35, + 149, -65, -36, 150, -61, -36, 151, -57, -37, 152, -53, -38, + 153, -49, -38, 154, -45, -39, 155, -41, -40, 156, -37, -40, + 157, -32, -41, 158, -28, -42, 158, -24, -42, 159, -20, -43, + 160, -16, -44, 161, -12, -44, 162, -8, -45, 163, -4, -46, + 164, 0, -46, 165, 4, -47, 166, 8, -48, 167, 12, -48, + 168, 16, -49, 169, 20, -50, 170, 24, -50, 171, 28, -51, + 172, 32, -52, 173, 37, -52, 173, 41, -53, 174, 45, -54, + 148, -83, -35, 149, -79, -35, 149, -75, -36, 151, -71, -37, + 151, -67, -37, 152, -63, -38, 153, -59, -39, 154, -54, -39, + 155, -50, -40, 156, -46, -41, 157, -42, -41, 158, -38, -42, + 159, -34, -43, 160, -30, -43, 161, -26, -44, 162, -22, -45, + 163, -17, -45, 164, -13, -46, 165, -9, -47, 165, -5, -47, + 166, -1, -48, 167, 2, -49, 168, 6, -49, 169, 10, -50, + 170, 14, -51, 171, 19, -51, 172, 23, -52, 173, 27, -53, + 174, 31, -53, 175, 35, -54, 176, 39, -55, 177, 43, -55, + 150, -84, -36, 151, -80, -37, 152, -76, -38, 153, -72, -38, + 154, -68, -39, 155, -64, -40, 156, -60, -40, 157, -55, -41, + 158, -51, -42, 158, -47, -42, 159, -43, -43, 160, -39, -44, + 161, -35, -44, 162, -31, -45, 163, -27, -46, 164, -23, -46, + 165, -18, -47, 166, -14, -48, 167, -10, -48, 168, -6, -49, + 169, -2, -50, 170, 1, -50, 171, 5, -51, 172, 9, -52, + 172, 13, -52, 174, 18, -53, 174, 22, -54, 175, 26, -54, + 176, 30, -55, 177, 34, -56, 178, 38, -56, 179, 42, -57, + 152, -86, -38, 153, -82, -39, 154, -78, -39, 155, -73, -40, + 156, -69, -41, 157, -65, -41, 158, -61, -42, 159, -57, -43, + 160, -53, -43, 161, -49, -44, 162, -45, -45, 163, -41, -45, + 164, -36, -46, 165, -32, -47, 165, -28, -47, 166, -24, -48, + 167, -20, -49, 168, -16, -49, 169, -12, -50, 170, -8, -51, + 171, -3, -51, 172, 0, -52, 173, 4, -53, 174, 8, -53, + 175, 12, -54, 176, 16, -55, 177, 20, -55, 178, 24, -56, + 179, 28, -57, 180, 33, -57, 181, 37, -58, 181, 41, -59, + 155, -87, -40, 156, -83, -41, 157, -79, -41, 158, -75, -42, + 159, -71, -43, 160, -67, -43, 161, -63, -44, 162, -58, -45, + 163, -54, -45, 164, -50, -46, 165, -46, -47, 166, -42, -47, + 167, -38, -48, 168, -34, -49, 168, -30, -49, 169, -26, -50, + 170, -21, -51, 171, -17, -51, 172, -13, -52, 173, -9, -53, + 174, -5, -53, 175, -1, -54, 176, 2, -55, 177, 6, -55, + 178, 10, -56, 179, 15, -57, 180, 19, -57, 181, 23, -58, + 182, 27, -59, 183, 31, -59, 183, 35, -60, 184, 39, -61, + 158, -89, -42, 159, -85, -42, 159, -81, -43, 161, -76, -44, + 161, -72, -44, 162, -68, -45, 163, -64, -46, 164, -60, -46, + 165, -56, -47, 166, -52, -48, 167, -48, -48, 168, -44, -49, + 169, -39, -50, 170, -35, -50, 171, -31, -51, 172, -27, -52, + 173, -23, -52, 174, -19, -53, 175, -15, -54, 175, -11, -54, + 176, -6, -55, 177, -2, -56, 178, 1, -56, 179, 5, -57, + 180, 9, -58, 181, 13, -58, 182, 17, -59, 183, 21, -60, + 184, 25, -60, 185, 30, -61, 186, 34, -62, 187, 38, -62, + 160, -90, -43, 161, -86, -44, 162, -82, -45, 163, -78, -45, + 164, -74, -46, 165, -70, -47, 166, -66, -47, 167, -61, -48, + 168, -57, -49, 168, -53, -49, 169, -49, -50, 170, -45, -51, + 171, -41, -51, 172, -37, -52, 173, -33, -53, 174, -29, -53, + 175, -24, -54, 176, -20, -55, 177, -16, -55, 178, -12, -56, + 179, -8, -57, 180, -4, -57, 181, 0, -58, 182, 3, -59, + 182, 7, -59, 183, 12, -60, 184, 16, -61, 185, 20, -61, + 186, 24, -62, 187, 28, -63, 188, 32, -63, 189, 36, -64, + 162, -91, -45, 163, -87, -46, 164, -83, -46, 165, -79, -47, + 166, -75, -48, 167, -71, -48, 168, -67, -49, 169, -62, -50, + 170, -58, -50, 171, -54, -51, 172, -50, -52, 173, -46, -52, + 174, -42, -53, 175, -38, -54, 175, -34, -54, 176, -30, -55, + 177, -25, -56, 178, -21, -56, 179, -17, -57, 180, -13, -58, + 181, -9, -58, 182, -5, -59, 183, -1, -60, 184, 2, -60, + 185, 6, -61, 186, 11, -62, 187, 15, -62, 188, 19, -63, + 189, 23, -64, 190, 27, -64, 191, 31, -65, 191, 35, -66, + 165, -93, -47, 166, -89, -47, 167, -85, -48, 168, -80, -49, + 168, -76, -49, 169, -72, -50, 170, -68, -51, 171, -64, -51, + 172, -60, -52, 173, -56, -53, 174, -52, -53, 175, -48, -54, + 176, -43, -55, 177, -39, -55, 178, -35, -56, 179, -31, -57, + 180, -27, -57, 181, -23, -58, 182, -19, -59, 182, -15, -59, + 184, -10, -60, 184, -6, -61, 185, -2, -61, 186, 1, -62, + 187, 5, -63, 188, 9, -63, 189, 13, -64, 190, 17, -65, + 191, 21, -65, 192, 26, -66, 193, 30, -67, 194, 34, -67, + 167, -94, -48, 168, -90, -49, 169, -86, -50, 170, -82, -50, + 171, -78, -51, 172, -74, -52, 173, -70, -52, 174, -65, -53, + 175, -61, -54, 175, -57, -54, 176, -53, -55, 177, -49, -56, + 178, -45, -56, 179, -41, -57, 180, -37, -58, 181, -33, -58, + 182, -28, -59, 183, -24, -60, 184, -20, -60, 185, -16, -61, + 186, -12, -62, 187, -8, -62, 188, -4, -63, 189, 0, -64, + 190, 3, -64, 191, 8, -65, 191, 12, -66, 192, 16, -66, + 193, 20, -67, 194, 24, -68, 195, 28, -68, 196, 32, -69, + 169, -95, -50, 170, -91, -51, 171, -87, -51, 172, -83, -52, + 173, -79, -53, 174, -75, -53, 175, -71, -54, 176, -66, -55, + 177, -62, -55, 178, -58, -56, 179, -54, -57, 180, -50, -57, + 181, -46, -58, 182, -42, -59, 183, -38, -59, 183, -34, -60, + 184, -29, -61, 185, -25, -61, 186, -21, -62, 187, -17, -63, + 188, -13, -63, 189, -9, -64, 190, -5, -65, 191, -1, -65, + 192, 2, -66, 193, 7, -67, 194, 11, -67, 195, 15, -68, + 196, 19, -69, 197, 23, -70, 198, 27, -70, 198, 31, -71, + 172, -97, -52, 173, -93, -52, 174, -89, -53, 175, -84, -54, + 176, -80, -54, 176, -76, -55, 177, -72, -56, 178, -68, -56, + 179, -64, -57, 180, -60, -58, 181, -56, -58, 182, -52, -59, + 183, -47, -60, 184, -43, -60, 185, -39, -61, 186, -35, -62, + 187, -31, -62, 188, -27, -63, 189, -23, -64, 190, -19, -64, + 191, -14, -65, 191, -10, -66, 192, -6, -66, 193, -2, -67, + 194, 1, -68, 195, 5, -68, 196, 9, -69, 197, 13, -70, + 198, 17, -70, 199, 22, -71, 200, 26, -72, 201, 30, -72, + 174, -98, -53, 175, -94, -54, 176, -90, -55, 177, -86, -55, + 178, -82, -56, 179, -78, -57, 180, -74, -57, 181, -69, -58, + 182, -65, -59, 183, -61, -59, 183, -57, -60, 184, -53, -61, + 185, -49, -61, 186, -45, -62, 187, -41, -63, 188, -37, -63, + 189, -32, -64, 190, -28, -65, 191, -24, -65, 192, -20, -66, + 193, -16, -67, 194, -12, -67, 195, -8, -68, 196, -4, -69, + 197, 0, -69, 198, 4, -70, 198, 8, -71, 199, 12, -71, + 200, 16, -72, 201, 20, -73, 202, 24, -74, 203, 28, -74, + 176, -99, -55, 177, -95, -56, 178, -91, -56, 179, -87, -57, + 180, -83, -58, 181, -79, -58, 182, -75, -59, 183, -70, -60, + 184, -66, -60, 185, -62, -61, 186, -58, -62, 187, -54, -62, + 188, -50, -63, 189, -46, -64, 190, -42, -64, 190, -38, -65, + 191, -33, -66, 192, -29, -66, 193, -25, -67, 194, -21, -68, + 195, -17, -69, 196, -13, -69, 197, -9, -70, 198, -5, -70, + 199, -1, -71, 200, 3, -72, 201, 7, -72, 202, 11, -73, + 203, 15, -74, 204, 19, -75, 205, 23, -75, 206, 27, -76, + 179, -101, -57, 180, -97, -57, 181, -93, -58, 182, -88, -59, + 183, -84, -59, 183, -80, -60, 184, -76, -61, 185, -72, -61, + 186, -68, -62, 187, -64, -63, 188, -60, -63, 189, -56, -64, + 190, -51, -65, 191, -47, -65, 192, -43, -66, 193, -39, -67, + 194, -35, -67, 195, -31, -68, 196, -27, -69, 197, -23, -69, + 198, -18, -70, 199, -14, -71, 199, -10, -71, 200, -6, -72, + 201, -2, -73, 202, 1, -74, 203, 5, -74, 204, 9, -75, + 205, 13, -75, 206, 18, -76, 207, 22, -77, 208, 26, -78, + 31, -18, 53, 32, -14, 52, 33, -10, 52, 34, -5, 51, + 35, -1, 50, 36, 2, 50, 37, 6, 49, 38, 10, 48, + 39, 14, 48, 40, 18, 47, 41, 22, 46, 42, 26, 46, + 43, 31, 45, 44, 35, 44, 45, 39, 44, 46, 43, 43, + 47, 47, 42, 47, 51, 42, 48, 55, 41, 49, 59, 40, + 50, 64, 40, 51, 68, 39, 52, 72, 38, 53, 76, 38, + 54, 80, 37, 55, 84, 36, 56, 88, 36, 57, 92, 35, + 58, 96, 34, 59, 101, 34, 60, 105, 33, 61, 109, 32, + 34, -19, 51, 35, -15, 51, 36, -11, 50, 37, -6, 49, + 38, -2, 49, 39, 1, 48, 39, 5, 47, 40, 9, 47, + 41, 13, 46, 42, 17, 45, 43, 21, 45, 44, 25, 44, + 45, 30, 43, 46, 34, 43, 47, 38, 42, 48, 42, 41, + 49, 46, 41, 50, 50, 40, 51, 54, 39, 52, 58, 39, + 53, 63, 38, 54, 67, 37, 54, 71, 37, 55, 75, 36, + 56, 79, 35, 57, 83, 35, 58, 87, 34, 59, 91, 33, + 60, 95, 33, 61, 100, 32, 62, 104, 31, 63, 108, 31, + 36, -20, 50, 37, -16, 49, 38, -12, 48, 39, -8, 48, + 40, -4, 47, 41, 0, 46, 42, 3, 46, 43, 8, 45, + 44, 12, 44, 45, 16, 44, 46, 20, 43, 46, 24, 42, + 47, 28, 42, 48, 32, 41, 49, 36, 40, 50, 40, 40, + 51, 45, 39, 52, 49, 38, 53, 53, 38, 54, 57, 37, + 55, 61, 36, 56, 65, 36, 57, 69, 35, 58, 73, 34, + 59, 77, 34, 60, 82, 33, 61, 86, 32, 61, 90, 32, + 62, 94, 31, 63, 98, 30, 64, 102, 30, 65, 106, 29, + 39, -22, 48, 39, -18, 47, 40, -14, 47, 41, -9, 46, + 42, -5, 45, 43, -1, 45, 44, 2, 44, 45, 6, 43, + 46, 10, 43, 47, 14, 42, 48, 18, 41, 49, 22, 41, + 50, 27, 40, 51, 31, 39, 52, 35, 39, 53, 39, 38, + 54, 43, 37, 54, 47, 37, 55, 51, 36, 56, 55, 35, + 57, 60, 35, 58, 64, 34, 59, 68, 33, 60, 72, 33, + 61, 76, 32, 62, 80, 31, 63, 84, 31, 64, 88, 30, + 65, 92, 29, 66, 97, 29, 67, 101, 28, 68, 105, 27, + 41, -23, 46, 42, -19, 46, 43, -15, 45, 44, -10, 44, + 45, -6, 44, 46, -2, 43, 46, 1, 42, 47, 5, 42, + 48, 9, 41, 49, 13, 40, 50, 17, 40, 51, 21, 39, + 52, 26, 38, 53, 30, 38, 54, 34, 37, 55, 38, 36, + 56, 42, 36, 57, 46, 35, 58, 50, 34, 59, 54, 34, + 60, 59, 33, 61, 63, 32, 62, 67, 32, 62, 71, 31, + 63, 75, 30, 64, 79, 30, 65, 83, 29, 66, 87, 28, + 67, 91, 28, 68, 96, 27, 69, 100, 26, 70, 104, 26, + 43, -24, 45, 44, -20, 44, 45, -16, 43, 46, -12, 43, + 47, -8, 42, 48, -4, 41, 49, 0, 41, 50, 4, 40, + 51, 8, 39, 52, 12, 39, 53, 16, 38, 53, 20, 37, + 55, 24, 37, 55, 28, 36, 56, 32, 35, 57, 36, 35, + 58, 41, 34, 59, 45, 33, 60, 49, 33, 61, 53, 32, + 62, 57, 31, 63, 61, 31, 64, 65, 30, 65, 69, 29, + 66, 73, 29, 67, 78, 28, 68, 82, 27, 69, 86, 27, + 69, 90, 26, 70, 94, 25, 71, 98, 25, 72, 102, 24, + 46, -26, 43, 46, -22, 42, 47, -18, 42, 48, -13, 41, + 49, -9, 40, 50, -5, 40, 51, -1, 39, 52, 2, 38, + 53, 6, 38, 54, 10, 37, 55, 14, 36, 56, 18, 36, + 57, 23, 35, 58, 27, 34, 59, 31, 34, 60, 35, 33, + 61, 39, 32, 62, 43, 32, 62, 47, 31, 63, 51, 30, + 64, 56, 30, 65, 60, 29, 66, 64, 28, 67, 68, 28, + 68, 72, 27, 69, 76, 26, 70, 80, 26, 71, 84, 25, + 72, 88, 24, 73, 93, 24, 74, 97, 23, 75, 101, 22, + 48, -27, 41, 49, -23, 41, 50, -19, 40, 51, -14, 39, + 52, -10, 39, 53, -6, 38, 54, -2, 37, 55, 1, 37, + 55, 5, 36, 56, 9, 35, 57, 13, 35, 58, 17, 34, + 59, 22, 33, 60, 26, 33, 61, 30, 32, 62, 34, 31, + 63, 38, 31, 64, 42, 30, 65, 46, 29, 66, 50, 29, + 67, 55, 28, 68, 59, 27, 69, 63, 27, 69, 67, 26, + 70, 71, 25, 71, 75, 25, 72, 79, 24, 73, 83, 23, + 74, 87, 23, 75, 92, 22, 76, 96, 21, 77, 100, 21, + 50, -28, 40, 51, -24, 39, 52, -20, 38, 53, -16, 38, + 54, -12, 37, 55, -8, 36, 56, -4, 36, 57, 0, 35, + 58, 4, 34, 59, 8, 34, 60, 12, 33, 61, 16, 32, + 62, 20, 32, 62, 24, 31, 63, 28, 30, 64, 32, 30, + 65, 37, 29, 66, 41, 28, 67, 45, 28, 68, 49, 27, + 69, 53, 26, 70, 57, 26, 71, 61, 25, 72, 65, 24, + 73, 69, 24, 74, 74, 23, 75, 78, 22, 76, 82, 22, + 76, 86, 21, 78, 90, 20, 78, 94, 20, 79, 98, 19, + 53, -29, 38, 54, -25, 37, 54, -21, 37, 55, -17, 36, + 56, -13, 35, 57, -9, 35, 58, -5, 34, 59, 0, 33, + 60, 3, 33, 61, 7, 32, 62, 11, 31, 63, 15, 31, + 64, 19, 30, 65, 23, 29, 66, 27, 29, 67, 31, 28, + 68, 36, 27, 69, 40, 27, 69, 44, 26, 70, 48, 25, + 71, 52, 25, 72, 56, 24, 73, 60, 23, 74, 64, 23, + 75, 68, 22, 76, 73, 21, 77, 77, 21, 78, 81, 20, + 79, 85, 19, 80, 89, 18, 81, 93, 18, 82, 97, 17, + 55, -31, 36, 56, -27, 36, 57, -23, 35, 58, -18, 34, + 59, -14, 34, 60, -10, 33, 61, -6, 32, 62, -2, 32, + 62, 1, 31, 63, 5, 30, 64, 9, 30, 65, 13, 29, + 66, 18, 28, 67, 22, 28, 68, 26, 27, 69, 30, 26, + 70, 34, 26, 71, 38, 25, 72, 42, 24, 73, 46, 24, + 74, 51, 23, 75, 55, 22, 76, 59, 22, 77, 63, 21, + 77, 67, 20, 78, 71, 20, 79, 75, 19, 80, 79, 18, + 81, 83, 18, 82, 88, 17, 83, 92, 16, 84, 96, 16, + 58, -32, 34, 59, -28, 34, 60, -24, 33, 61, -20, 32, + 62, -16, 31, 63, -12, 31, 63, -8, 30, 65, -3, 29, + 65, 0, 29, 66, 4, 28, 67, 8, 27, 68, 12, 27, + 69, 16, 26, 70, 20, 25, 71, 24, 25, 72, 28, 24, + 73, 33, 23, 74, 37, 23, 75, 41, 22, 76, 45, 21, + 77, 49, 21, 78, 53, 20, 79, 57, 19, 79, 61, 19, + 80, 65, 18, 81, 70, 17, 82, 74, 17, 83, 78, 16, + 84, 82, 15, 85, 86, 15, 86, 90, 14, 87, 94, 13, + 60, -34, 32, 61, -30, 32, 62, -26, 31, 63, -21, 30, + 64, -17, 30, 65, -13, 29, 66, -9, 29, 67, -5, 28, + 68, -1, 27, 69, 2, 26, 70, 6, 26, 71, 10, 25, + 72, 15, 24, 72, 19, 24, 73, 23, 23, 74, 27, 22, + 75, 31, 22, 76, 35, 21, 77, 39, 20, 78, 43, 20, + 79, 48, 19, 80, 52, 18, 81, 56, 18, 82, 60, 17, + 83, 64, 16, 84, 68, 16, 85, 72, 15, 86, 76, 14, + 86, 80, 14, 88, 85, 13, 88, 89, 12, 89, 93, 12, + 63, -35, 31, 64, -31, 30, 64, -27, 30, 65, -23, 29, + 66, -19, 28, 67, -15, 27, 68, -11, 27, 69, -6, 26, + 70, -2, 25, 71, 1, 25, 72, 5, 24, 73, 9, 23, + 74, 13, 23, 75, 17, 22, 76, 21, 21, 77, 25, 21, + 78, 30, 20, 79, 34, 19, 79, 38, 19, 80, 42, 18, + 81, 46, 17, 82, 50, 17, 83, 54, 16, 84, 58, 15, + 85, 62, 15, 86, 67, 14, 87, 71, 13, 88, 75, 13, + 89, 79, 12, 90, 83, 11, 91, 87, 11, 92, 91, 10, + 65, -36, 29, 66, -32, 28, 67, -28, 28, 68, -24, 27, + 69, -20, 26, 70, -16, 26, 71, -12, 25, 72, -7, 24, + 72, -3, 24, 73, 0, 23, 74, 4, 22, 75, 8, 22, + 76, 12, 21, 77, 16, 20, 78, 20, 20, 79, 24, 19, + 80, 29, 18, 81, 33, 18, 82, 37, 17, 83, 41, 16, + 84, 45, 16, 85, 49, 15, 86, 53, 14, 86, 57, 14, + 87, 61, 13, 88, 66, 12, 89, 70, 12, 90, 74, 11, + 91, 78, 10, 92, 82, 10, 93, 86, 9, 94, 90, 8, + 67, -38, 27, 68, -34, 27, 69, -30, 26, 70, -25, 25, + 71, -21, 25, 72, -17, 24, 73, -13, 23, 74, -9, 23, + 75, -5, 22, 76, -1, 21, 77, 2, 21, 78, 6, 20, + 79, 11, 19, 79, 15, 19, 80, 19, 18, 81, 23, 17, + 82, 27, 17, 83, 31, 16, 84, 35, 15, 85, 39, 15, + 86, 44, 14, 87, 48, 13, 88, 52, 13, 89, 56, 12, + 90, 60, 11, 91, 64, 11, 92, 68, 10, 93, 72, 9, + 94, 76, 9, 95, 81, 8, 95, 85, 7, 96, 89, 7, + 70, -39, 26, 71, -35, 25, 71, -31, 24, 72, -27, 24, + 73, -23, 23, 74, -19, 22, 75, -15, 22, 76, -10, 21, + 77, -6, 20, 78, -2, 20, 79, 1, 19, 80, 5, 18, + 81, 9, 18, 82, 13, 17, 83, 17, 16, 84, 21, 16, + 85, 26, 15, 86, 30, 14, 87, 34, 14, 87, 38, 13, + 88, 42, 12, 89, 46, 12, 90, 50, 11, 91, 54, 10, + 92, 58, 10, 93, 63, 9, 94, 67, 8, 95, 71, 8, + 96, 75, 7, 97, 79, 6, 98, 83, 6, 99, 87, 5, + 72, -40, 24, 73, -36, 23, 74, -32, 23, 75, -28, 22, + 76, -24, 21, 77, -20, 21, 78, -16, 20, 79, -11, 19, + 80, -7, 19, 80, -3, 18, 81, 0, 17, 82, 4, 17, + 83, 8, 16, 84, 12, 15, 85, 16, 15, 86, 20, 14, + 87, 25, 13, 88, 29, 13, 89, 33, 12, 90, 37, 11, + 91, 41, 11, 92, 45, 10, 93, 49, 9, 94, 53, 9, + 94, 57, 8, 95, 62, 7, 96, 66, 7, 97, 70, 6, + 98, 74, 5, 99, 78, 5, 100, 82, 4, 101, 86, 3, + 74, -42, 22, 75, -38, 22, 76, -34, 21, 77, -29, 20, + 78, -25, 20, 79, -21, 19, 80, -17, 18, 81, -13, 18, + 82, -9, 17, 83, -5, 16, 84, -1, 16, 85, 2, 15, + 86, 7, 14, 87, 11, 14, 87, 15, 13, 88, 19, 12, + 89, 23, 12, 90, 27, 11, 91, 31, 10, 92, 35, 10, + 93, 40, 9, 94, 44, 8, 95, 48, 8, 96, 52, 7, + 97, 56, 6, 98, 60, 6, 99, 64, 5, 100, 68, 4, + 101, 72, 4, 102, 77, 3, 103, 81, 2, 103, 85, 2, + 77, -43, 21, 78, -39, 20, 79, -35, 19, 80, -31, 19, + 80, -27, 18, 81, -23, 17, 82, -19, 17, 83, -14, 16, + 84, -10, 15, 85, -6, 15, 86, -2, 14, 87, 1, 13, + 88, 5, 13, 89, 9, 12, 90, 13, 11, 91, 17, 11, + 92, 22, 10, 93, 26, 9, 94, 30, 9, 94, 34, 8, + 96, 38, 7, 96, 42, 7, 97, 46, 6, 98, 50, 5, + 99, 54, 5, 100, 59, 4, 101, 63, 3, 102, 67, 3, + 103, 71, 2, 104, 75, 1, 105, 79, 1, 106, 83, 0, + 79, -44, 19, 80, -40, 18, 81, -36, 18, 82, -32, 17, + 83, -28, 16, 84, -24, 16, 85, -20, 15, 86, -15, 14, + 87, -11, 14, 87, -7, 13, 88, -3, 12, 89, 0, 12, + 90, 4, 11, 91, 8, 10, 92, 12, 10, 93, 16, 9, + 94, 21, 8, 95, 25, 8, 96, 29, 7, 97, 33, 6, + 98, 37, 6, 99, 41, 5, 100, 45, 4, 101, 49, 4, + 101, 53, 3, 103, 58, 2, 103, 62, 2, 104, 66, 1, + 105, 70, 0, 106, 74, 0, 107, 78, 0, 108, 82, -1, + 81, -46, 17, 82, -42, 17, 83, -38, 16, 84, -33, 15, + 85, -29, 15, 86, -25, 14, 87, -21, 13, 88, -17, 13, + 89, -13, 12, 90, -9, 11, 91, -5, 11, 92, -1, 10, + 93, 3, 9, 94, 7, 9, 94, 11, 8, 95, 15, 7, + 96, 19, 7, 97, 23, 6, 98, 27, 5, 99, 31, 5, + 100, 36, 4, 101, 40, 3, 102, 44, 3, 103, 48, 2, + 104, 52, 1, 105, 56, 1, 106, 60, 0, 107, 64, 0, + 108, 68, 0, 109, 73, -1, 110, 77, -2, 110, 81, -2, + 84, -47, 16, 85, -43, 15, 86, -39, 14, 87, -35, 14, + 87, -31, 13, 88, -27, 12, 89, -23, 12, 90, -18, 11, + 91, -14, 10, 92, -10, 10, 93, -6, 9, 94, -2, 8, + 95, 1, 8, 96, 5, 7, 97, 9, 6, 98, 13, 6, + 99, 18, 5, 100, 22, 4, 101, 26, 4, 102, 30, 3, + 103, 34, 2, 103, 38, 2, 104, 42, 1, 105, 46, 0, + 106, 50, 0, 107, 55, 0, 108, 59, -1, 109, 63, -1, + 110, 67, -2, 111, 71, -3, 112, 75, -3, 113, 79, -4, + 86, -48, 14, 87, -44, 13, 88, -40, 13, 89, -36, 12, + 90, -32, 11, 91, -28, 11, 92, -24, 10, 93, -19, 9, + 94, -15, 9, 95, -11, 8, 95, -7, 7, 96, -3, 7, + 97, 0, 6, 98, 4, 5, 99, 8, 5, 100, 12, 4, + 101, 17, 3, 102, 21, 3, 103, 25, 2, 104, 29, 1, + 105, 33, 1, 106, 37, 0, 107, 41, 0, 108, 45, 0, + 109, 49, -1, 110, 54, -2, 110, 58, -2, 111, 62, -3, + 112, 66, -4, 113, 70, -4, 114, 74, -5, 115, 78, -6, + 88, -50, 12, 89, -46, 12, 90, -42, 11, 91, -37, 10, + 92, -33, 10, 93, -29, 9, 94, -25, 8, 95, -21, 8, + 96, -17, 7, 97, -13, 6, 98, -9, 6, 99, -5, 5, + 100, 0, 4, 101, 3, 4, 102, 7, 3, 102, 11, 2, + 103, 15, 2, 104, 19, 1, 105, 23, 0, 106, 27, 0, + 107, 32, 0, 108, 36, -1, 109, 40, -1, 110, 44, -2, + 111, 48, -3, 112, 52, -3, 113, 56, -4, 114, 60, -5, + 115, 64, -5, 116, 69, -6, 117, 73, -7, 118, 77, -7, + 91, -51, 11, 92, -47, 10, 93, -43, 9, 94, -39, 9, + 95, -35, 8, 95, -31, 7, 96, -27, 7, 97, -22, 6, + 98, -18, 5, 99, -14, 5, 100, -10, 4, 101, -6, 3, + 102, -2, 3, 103, 1, 2, 104, 5, 1, 105, 9, 1, + 106, 14, 0, 107, 18, 0, 108, 22, 0, 109, 26, -1, + 110, 30, -2, 111, 34, -2, 111, 38, -3, 112, 42, -4, + 113, 46, -4, 114, 51, -5, 115, 55, -6, 116, 59, -6, + 117, 63, -7, 118, 67, -8, 119, 71, -8, 120, 75, -9, + 93, -52, 9, 94, -48, 8, 95, -44, 8, 96, -40, 7, + 97, -36, 6, 98, -32, 6, 99, -28, 5, 100, -23, 4, + 101, -19, 4, 102, -15, 3, 102, -11, 2, 103, -7, 2, + 104, -3, 1, 105, 0, 0, 106, 4, 0, 107, 8, 0, + 108, 13, -1, 109, 17, -1, 110, 21, -2, 111, 25, -3, + 112, 29, -3, 113, 33, -4, 114, 37, -5, 115, 41, -5, + 116, 45, -6, 117, 50, -7, 118, 54, -7, 118, 58, -8, + 119, 62, -9, 120, 66, -9, 121, 70, -10, 122, 74, -11, + 95, -54, 7, 96, -50, 7, 97, -46, 6, 98, -41, 5, + 99, -37, 5, 100, -33, 4, 101, -29, 3, 102, -25, 3, + 103, -21, 2, 104, -17, 1, 105, -13, 1, 106, -9, 0, + 107, -4, 0, 108, 0, 0, 109, 3, -1, 109, 7, -2, + 111, 11, -2, 111, 15, -3, 112, 19, -4, 113, 23, -4, + 114, 28, -5, 115, 32, -6, 116, 36, -6, 117, 40, -7, + 118, 44, -8, 119, 48, -8, 120, 52, -9, 121, 56, -10, + 122, 60, -10, 123, 65, -11, 124, 69, -12, 125, 73, -12, + 98, -55, 6, 99, -51, 5, 100, -47, 4, 101, -42, 4, + 102, -38, 3, 102, -34, 2, 103, -30, 2, 104, -26, 1, + 105, -22, 0, 106, -18, 0, 107, -14, 0, 108, -10, -1, + 109, -5, -1, 110, -1, -2, 111, 2, -3, 112, 6, -3, + 113, 10, -4, 114, 14, -5, 115, 18, -5, 116, 22, -6, + 117, 27, -7, 118, 31, -7, 118, 35, -8, 119, 39, -9, + 120, 43, -9, 121, 47, -10, 122, 51, -11, 123, 55, -11, + 124, 59, -12, 125, 64, -13, 126, 68, -13, 127, 72, -14, + 100, -56, 4, 101, -52, 3, 102, -48, 3, 103, -44, 2, + 104, -40, 1, 105, -36, 1, 106, -32, 0, 107, -27, 0, + 108, -23, 0, 109, -19, -1, 110, -15, -2, 110, -11, -2, + 111, -7, -3, 112, -3, -4, 113, 0, -4, 114, 4, -5, + 115, 9, -6, 116, 13, -6, 117, 17, -7, 118, 21, -8, + 119, 25, -8, 120, 29, -9, 121, 33, -10, 122, 37, -10, + 123, 41, -11, 124, 46, -12, 125, 50, -12, 125, 54, -13, + 126, 58, -14, 127, 62, -14, 128, 66, -15, 129, 70, -16, + 103, -58, 2, 103, -54, 2, 104, -50, 1, 105, -45, 0, + 106, -41, 0, 107, -37, 0, 108, -33, -1, 109, -29, -1, + 110, -25, -2, 111, -21, -3, 112, -17, -3, 113, -13, -4, + 114, -8, -5, 115, -4, -5, 116, 0, -6, 117, 3, -7, + 118, 7, -7, 118, 11, -8, 119, 15, -9, 120, 19, -9, + 121, 24, -10, 122, 28, -11, 123, 32, -11, 124, 36, -12, + 125, 40, -13, 126, 44, -13, 127, 48, -14, 128, 52, -15, + 129, 56, -15, 130, 61, -16, 131, 65, -17, 132, 69, -17, + 105, -59, 1, 106, -55, 0, 107, -51, 0, 108, -46, 0, + 109, -42, -1, 110, -38, -2, 110, -34, -2, 111, -30, -3, + 112, -26, -4, 113, -22, -4, 114, -18, -5, 115, -14, -6, + 116, -9, -6, 117, -5, -7, 118, -1, -8, 119, 2, -8, + 120, 6, -9, 121, 10, -10, 122, 14, -10, 123, 18, -11, + 124, 23, -12, 125, 27, -12, 126, 31, -13, 126, 35, -14, + 127, 39, -14, 128, 43, -15, 129, 47, -16, 130, 51, -16, + 131, 55, -17, 132, 60, -18, 133, 64, -18, 134, 68, -19, + 108, -61, 0, 109, -57, -1, 110, -53, -2, 111, -48, -2, + 112, -44, -3, 112, -40, -4, 113, -36, -4, 114, -32, -5, + 115, -28, -6, 116, -24, -6, 117, -20, -7, 118, -16, -8, + 119, -11, -8, 120, -7, -9, 121, -3, -10, 122, 0, -10, + 123, 4, -11, 124, 8, -12, 125, 12, -12, 126, 16, -13, + 127, 21, -14, 128, 25, -14, 128, 29, -15, 129, 33, -16, + 130, 37, -16, 131, 41, -17, 132, 45, -18, 133, 49, -18, + 134, 53, -19, 135, 58, -20, 136, 62, -21, 137, 66, -21, + 110, -62, -2, 111, -58, -3, 112, -54, -3, 113, -49, -4, + 114, -45, -5, 115, -41, -5, 116, -37, -6, 117, -33, -7, + 118, -29, -7, 119, -25, -8, 119, -21, -9, 120, -17, -9, + 121, -12, -10, 122, -8, -11, 123, -4, -11, 124, 0, -12, + 125, 3, -13, 126, 7, -13, 127, 11, -14, 128, 15, -15, + 129, 20, -16, 130, 24, -16, 131, 28, -17, 132, 32, -17, + 133, 36, -18, 134, 40, -19, 135, 44, -20, 135, 48, -20, + 136, 52, -21, 137, 57, -22, 138, 61, -22, 139, 65, -23, + 112, -63, -4, 113, -59, -4, 114, -55, -5, 115, -51, -6, + 116, -47, -6, 117, -43, -7, 118, -39, -8, 119, -34, -8, + 120, -30, -9, 121, -26, -10, 122, -22, -10, 123, -18, -11, + 124, -14, -12, 125, -10, -12, 126, -6, -13, 127, -2, -14, + 128, 2, -15, 128, 6, -15, 129, 10, -16, 130, 14, -16, + 131, 18, -17, 132, 22, -18, 133, 26, -18, 134, 30, -19, + 135, 34, -20, 136, 39, -21, 137, 43, -21, 138, 47, -22, + 139, 51, -22, 140, 55, -23, 141, 59, -24, 142, 63, -25, + 115, -65, -5, 116, -61, -6, 117, -57, -7, 118, -52, -7, + 119, -48, -8, 120, -44, -9, 120, -40, -9, 121, -36, -10, + 122, -32, -11, 123, -28, -11, 124, -24, -12, 125, -20, -13, + 126, -15, -14, 127, -11, -14, 128, -7, -15, 129, -3, -15, + 130, 0, -16, 131, 4, -17, 132, 8, -17, 133, 12, -18, + 134, 17, -19, 135, 21, -20, 135, 25, -20, 136, 29, -21, + 137, 33, -21, 138, 37, -22, 139, 41, -23, 140, 45, -24, + 141, 49, -24, 142, 54, -25, 143, 58, -26, 144, 62, -26, + 117, -66, -7, 118, -62, -8, 119, -58, -8, 120, -53, -9, + 121, -49, -10, 122, -45, -10, 123, -41, -11, 124, -37, -12, + 125, -33, -12, 126, -29, -13, 127, -25, -14, 127, -21, -14, + 128, -16, -15, 129, -12, -16, 130, -8, -16, 131, -4, -17, + 132, 0, -18, 133, 3, -19, 134, 7, -19, 135, 11, -20, + 136, 16, -21, 137, 20, -21, 138, 24, -22, 139, 28, -22, + 140, 32, -23, 141, 36, -24, 142, 40, -25, 143, 44, -25, + 143, 48, -26, 144, 53, -27, 145, 57, -27, 146, 61, -28, + 120, -67, -9, 120, -63, -9, 121, -59, -10, 122, -55, -11, + 123, -51, -11, 124, -47, -12, 125, -43, -13, 126, -38, -14, + 127, -34, -14, 128, -30, -15, 129, -26, -15, 130, -22, -16, + 131, -18, -17, 132, -14, -18, 133, -10, -18, 134, -6, -19, + 135, -1, -20, 136, 2, -20, 136, 6, -21, 137, 10, -21, + 138, 14, -22, 139, 18, -23, 140, 22, -24, 141, 26, -24, + 142, 30, -25, 143, 35, -26, 144, 39, -26, 145, 43, -27, + 146, 47, -28, 147, 51, -28, 148, 55, -29, 149, 59, -30, + 122, -69, -10, 123, -65, -11, 124, -61, -12, 125, -56, -13, + 126, -52, -13, 127, -48, -14, 127, -44, -14, 129, -40, -15, + 129, -36, -16, 130, -32, -16, 131, -28, -17, 132, -24, -18, + 133, -19, -19, 134, -15, -19, 135, -11, -20, 136, -7, -20, + 137, -3, -21, 138, 0, -22, 139, 4, -23, 140, 8, -23, + 141, 13, -24, 142, 17, -25, 143, 21, -25, 143, 25, -26, + 144, 29, -26, 145, 33, -27, 146, 37, -28, 147, 41, -29, + 148, 45, -29, 149, 50, -30, 150, 54, -31, 151, 58, -31, + 124, -70, -12, 125, -66, -13, 126, -62, -13, 127, -57, -14, + 128, -53, -15, 129, -49, -15, 130, -45, -16, 131, -41, -17, + 132, -37, -18, 133, -33, -18, 134, -29, -19, 134, -25, -19, + 136, -20, -20, 136, -16, -21, 137, -12, -22, 138, -8, -22, + 139, -4, -23, 140, 0, -24, 141, 3, -24, 142, 7, -25, + 143, 12, -26, 144, 16, -26, 145, 20, -27, 146, 24, -28, + 147, 28, -28, 148, 32, -29, 149, 36, -30, 150, 40, -30, + 150, 44, -31, 151, 49, -32, 152, 53, -32, 153, 57, -33, + 127, -71, -14, 127, -67, -14, 128, -63, -15, 129, -59, -16, + 130, -55, -17, 131, -51, -17, 132, -47, -18, 133, -42, -19, + 134, -38, -19, 135, -34, -20, 136, -30, -20, 137, -26, -21, + 138, -22, -22, 139, -18, -23, 140, -14, -23, 141, -10, -24, + 142, -5, -25, 143, -1, -25, 143, 2, -26, 144, 6, -27, + 145, 10, -27, 146, 14, -28, 147, 18, -29, 148, 22, -29, + 149, 26, -30, 150, 31, -31, 151, 35, -31, 152, 39, -32, + 153, 43, -33, 154, 47, -33, 155, 51, -34, 156, 55, -35, + 129, -73, -16, 130, -69, -16, 131, -65, -17, 132, -60, -18, + 133, -56, -18, 134, -52, -19, 135, -48, -19, 136, -44, -20, + 136, -40, -21, 137, -36, -22, 138, -32, -22, 139, -28, -23, + 140, -23, -24, 141, -19, -24, 142, -15, -25, 143, -11, -26, + 144, -7, -26, 145, -3, -27, 146, 0, -28, 147, 4, -28, + 148, 9, -29, 149, 13, -30, 150, 17, -30, 150, 21, -31, + 151, 25, -32, 152, 29, -32, 153, 33, -33, 154, 37, -34, + 155, 41, -34, 156, 46, -35, 157, 50, -36, 158, 54, -36, + 131, -74, -17, 132, -70, -18, 133, -66, -18, 134, -61, -19, + 135, -57, -20, 136, -53, -21, 137, -49, -21, 138, -45, -22, + 139, -41, -23, 140, -37, -23, 141, -33, -24, 142, -29, -24, + 143, -24, -25, 143, -20, -26, 144, -16, -27, 145, -12, -27, + 146, -8, -28, 147, -4, -29, 148, 0, -29, 149, 3, -30, + 150, 8, -31, 151, 12, -31, 152, 16, -32, 153, 20, -33, + 154, 24, -33, 155, 28, -34, 156, 32, -35, 157, 36, -35, + 158, 40, -36, 159, 45, -37, 159, 49, -37, 160, 53, -38, + 134, -75, -19, 135, -71, -20, 135, -67, -20, 136, -63, -21, + 137, -59, -22, 138, -55, -22, 139, -51, -23, 140, -46, -24, + 141, -42, -24, 142, -38, -25, 143, -34, -26, 144, -30, -26, + 145, -26, -27, 146, -22, -28, 147, -18, -28, 148, -14, -29, + 149, -9, -30, 150, -5, -30, 151, -1, -31, 151, 2, -32, + 152, 6, -32, 153, 10, -33, 154, 14, -34, 155, 18, -34, + 156, 22, -35, 157, 27, -36, 158, 31, -36, 159, 35, -37, + 160, 39, -38, 161, 43, -38, 162, 47, -39, 163, 51, -40, + 136, -77, -21, 137, -73, -21, 138, -69, -22, 139, -64, -23, + 140, -60, -23, 141, -56, -24, 142, -52, -25, 143, -48, -25, + 144, -44, -26, 144, -40, -27, 145, -36, -27, 146, -32, -28, + 147, -27, -29, 148, -23, -29, 149, -19, -30, 150, -15, -31, + 151, -11, -31, 152, -7, -32, 153, -3, -33, 154, 0, -33, + 155, 5, -34, 156, 9, -35, 157, 13, -35, 158, 17, -36, + 158, 21, -37, 159, 25, -37, 160, 29, -38, 161, 33, -39, + 162, 37, -39, 163, 42, -40, 164, 46, -41, 165, 50, -41, + 138, -78, -22, 139, -74, -23, 140, -70, -24, 141, -65, -24, + 142, -61, -25, 143, -57, -26, 144, -53, -26, 145, -49, -27, + 146, -45, -28, 147, -41, -28, 148, -37, -29, 149, -33, -30, + 150, -28, -30, 151, -24, -31, 151, -20, -32, 152, -16, -32, + 153, -12, -33, 154, -8, -34, 155, -4, -34, 156, 0, -35, + 157, 4, -36, 158, 8, -36, 159, 12, -37, 160, 16, -38, + 161, 20, -38, 162, 24, -39, 163, 28, -40, 164, 32, -40, + 165, 36, -41, 166, 41, -42, 166, 45, -42, 167, 49, -43, + 141, -79, -24, 142, -75, -25, 142, -71, -25, 144, -67, -26, + 144, -63, -27, 145, -59, -27, 146, -55, -28, 147, -50, -29, + 148, -46, -29, 149, -42, -30, 150, -38, -31, 151, -34, -31, + 152, -30, -32, 153, -26, -33, 154, -22, -33, 155, -18, -34, + 156, -13, -35, 157, -9, -35, 158, -5, -36, 158, -1, -37, + 159, 2, -37, 160, 6, -38, 161, 10, -39, 162, 14, -39, + 163, 18, -40, 164, 23, -41, 165, 27, -41, 166, 31, -42, + 167, 35, -43, 168, 39, -43, 169, 43, -44, 170, 47, -45, + 143, -80, -26, 144, -76, -26, 145, -72, -27, 146, -68, -28, + 147, -64, -28, 148, -60, -29, 149, -56, -30, 150, -51, -30, + 151, -47, -31, 151, -43, -32, 152, -39, -32, 153, -35, -33, + 154, -31, -34, 155, -27, -34, 156, -23, -35, 157, -19, -36, + 158, -14, -36, 159, -10, -37, 160, -6, -38, 161, -2, -38, + 162, 1, -39, 163, 5, -40, 164, 9, -40, 165, 13, -41, + 165, 17, -42, 167, 22, -42, 167, 26, -43, 168, 30, -44, + 169, 34, -44, 170, 38, -45, 171, 42, -46, 172, 46, -46, + 145, -82, -27, 146, -78, -28, 147, -74, -29, 148, -69, -29, + 149, -65, -30, 150, -61, -31, 151, -57, -31, 152, -53, -32, + 153, -49, -33, 154, -45, -33, 155, -41, -34, 156, -37, -35, + 157, -32, -35, 158, -28, -36, 158, -24, -37, 159, -20, -37, + 160, -16, -38, 161, -12, -39, 162, -8, -39, 163, -4, -40, + 164, 0, -41, 165, 4, -41, 166, 8, -42, 167, 12, -43, + 168, 16, -43, 169, 20, -44, 170, 24, -45, 171, 28, -45, + 172, 32, -46, 173, 37, -47, 174, 41, -47, 174, 45, -48, + 148, -83, -29, 149, -79, -30, 150, -75, -30, 151, -71, -31, + 151, -67, -32, 152, -63, -32, 153, -59, -33, 154, -54, -34, + 155, -50, -34, 156, -46, -35, 157, -42, -36, 158, -38, -36, + 159, -34, -37, 160, -30, -38, 161, -26, -38, 162, -22, -39, + 163, -17, -40, 164, -13, -40, 165, -9, -41, 166, -5, -42, + 167, -1, -42, 167, 2, -43, 168, 6, -44, 169, 10, -44, + 170, 14, -45, 171, 19, -46, 172, 23, -46, 173, 27, -47, + 174, 31, -48, 175, 35, -48, 176, 39, -49, 177, 43, -50, + 150, -84, -31, 151, -80, -31, 152, -76, -32, 153, -72, -33, + 154, -68, -33, 155, -64, -34, 156, -60, -35, 157, -55, -35, + 158, -51, -36, 159, -47, -37, 159, -43, -37, 160, -39, -38, + 161, -35, -39, 162, -31, -39, 163, -27, -40, 164, -23, -41, + 165, -18, -41, 166, -14, -42, 167, -10, -43, 168, -6, -43, + 169, -2, -44, 170, 1, -45, 171, 5, -45, 172, 9, -46, + 173, 13, -47, 174, 18, -47, 174, 22, -48, 175, 26, -49, + 176, 30, -49, 177, 34, -50, 178, 38, -51, 179, 42, -51, + 152, -86, -32, 153, -82, -33, 154, -78, -34, 155, -73, -34, + 156, -69, -35, 157, -65, -36, 158, -61, -36, 159, -57, -37, + 160, -53, -38, 161, -49, -38, 162, -45, -39, 163, -41, -40, + 164, -36, -40, 165, -32, -41, 166, -28, -42, 166, -24, -42, + 167, -20, -43, 168, -16, -44, 169, -12, -44, 170, -8, -45, + 171, -3, -46, 172, 0, -46, 173, 4, -47, 174, 8, -48, + 175, 12, -48, 176, 16, -49, 177, 20, -50, 178, 24, -50, + 179, 28, -51, 180, 33, -52, 181, 37, -52, 181, 41, -53, + 155, -87, -34, 156, -83, -35, 157, -79, -35, 158, -75, -36, + 159, -71, -37, 159, -67, -37, 160, -63, -38, 161, -58, -39, + 162, -54, -39, 163, -50, -40, 164, -46, -41, 165, -42, -41, + 166, -38, -42, 167, -34, -43, 168, -30, -43, 169, -26, -44, + 170, -21, -45, 171, -17, -45, 172, -13, -46, 173, -9, -47, + 174, -5, -47, 174, -1, -48, 175, 2, -49, 176, 6, -49, + 177, 10, -50, 178, 15, -51, 179, 19, -51, 180, 23, -52, + 181, 27, -53, 182, 31, -53, 183, 35, -54, 184, 39, -55, + 158, -89, -36, 159, -85, -37, 160, -81, -37, 161, -76, -38, + 161, -72, -39, 162, -68, -39, 163, -64, -40, 164, -60, -41, + 165, -56, -41, 166, -52, -42, 167, -48, -43, 168, -44, -43, + 169, -39, -44, 170, -35, -45, 171, -31, -45, 172, -27, -46, + 173, -23, -47, 174, -19, -47, 175, -15, -48, 175, -11, -49, + 177, -6, -49, 177, -2, -50, 178, 1, -51, 179, 5, -51, + 180, 9, -52, 181, 13, -53, 182, 17, -53, 183, 21, -54, + 184, 25, -55, 185, 30, -55, 186, 34, -56, 187, 38, -57, + 160, -90, -38, 161, -86, -38, 162, -82, -39, 163, -78, -40, + 164, -74, -40, 165, -70, -41, 166, -66, -42, 167, -61, -42, + 168, -57, -43, 168, -53, -44, 169, -49, -44, 170, -45, -45, + 171, -41, -46, 172, -37, -46, 173, -33, -47, 174, -29, -48, + 175, -24, -48, 176, -20, -49, 177, -16, -50, 178, -12, -50, + 179, -8, -51, 180, -4, -52, 181, 0, -52, 182, 3, -53, + 183, 7, -54, 184, 12, -54, 184, 16, -55, 185, 20, -56, + 186, 24, -56, 187, 28, -57, 188, 32, -58, 189, 36, -58, + 162, -91, -39, 163, -87, -40, 164, -83, -41, 165, -79, -41, + 166, -75, -42, 167, -71, -43, 168, -67, -43, 169, -62, -44, + 170, -58, -45, 171, -54, -45, 172, -50, -46, 173, -46, -47, + 174, -42, -47, 175, -38, -48, 176, -34, -49, 176, -30, -49, + 177, -25, -50, 178, -21, -51, 179, -17, -51, 180, -13, -52, + 181, -9, -53, 182, -5, -53, 183, -1, -54, 184, 2, -55, + 185, 6, -55, 186, 11, -56, 187, 15, -57, 188, 19, -57, + 189, 23, -58, 190, 27, -59, 191, 31, -59, 191, 35, -60, + 165, -93, -41, 166, -89, -42, 167, -85, -42, 168, -80, -43, + 169, -76, -44, 169, -72, -44, 170, -68, -45, 171, -64, -46, + 172, -60, -46, 173, -56, -47, 174, -52, -48, 175, -48, -48, + 176, -43, -49, 177, -39, -50, 178, -35, -50, 179, -31, -51, + 180, -27, -52, 181, -23, -52, 182, -19, -53, 183, -15, -54, + 184, -10, -54, 184, -6, -55, 185, -2, -56, 186, 1, -56, + 187, 5, -57, 188, 9, -58, 189, 13, -58, 190, 17, -59, + 191, 21, -60, 192, 26, -60, 193, 30, -61, 194, 34, -62, + 167, -94, -43, 168, -90, -43, 169, -86, -44, 170, -82, -45, + 171, -78, -45, 172, -74, -46, 173, -70, -47, 174, -65, -47, + 175, -61, -48, 176, -57, -49, 176, -53, -49, 177, -49, -50, + 178, -45, -51, 179, -41, -51, 180, -37, -52, 181, -33, -53, + 182, -28, -53, 183, -24, -54, 184, -20, -55, 185, -16, -55, + 186, -12, -56, 187, -8, -57, 188, -4, -57, 189, 0, -58, + 190, 3, -59, 191, 8, -59, 191, 12, -60, 192, 16, -61, + 193, 20, -61, 194, 24, -62, 195, 28, -63, 196, 32, -63, + 169, -95, -44, 170, -91, -45, 171, -87, -46, 172, -83, -46, + 173, -79, -47, 174, -75, -48, 175, -71, -48, 176, -66, -49, + 177, -62, -50, 178, -58, -50, 179, -54, -51, 180, -50, -52, + 181, -46, -52, 182, -42, -53, 183, -38, -54, 183, -34, -54, + 184, -29, -55, 185, -25, -56, 186, -21, -56, 187, -17, -57, + 188, -13, -58, 189, -9, -58, 190, -5, -59, 191, -1, -60, + 192, 2, -60, 193, 7, -61, 194, 11, -62, 195, 15, -62, + 196, 19, -63, 197, 23, -64, 198, 27, -64, 199, 31, -65, + 172, -97, -46, 173, -93, -47, 174, -89, -47, 175, -84, -48, + 176, -80, -49, 176, -76, -49, 177, -72, -50, 178, -68, -51, + 179, -64, -51, 180, -60, -52, 181, -56, -53, 182, -52, -53, + 183, -47, -54, 184, -43, -55, 185, -39, -55, 186, -35, -56, + 187, -31, -57, 188, -27, -57, 189, -23, -58, 190, -19, -59, + 191, -14, -59, 192, -10, -60, 192, -6, -61, 193, -2, -61, + 194, 1, -62, 195, 5, -63, 196, 9, -63, 197, 13, -64, + 198, 17, -65, 199, 22, -66, 200, 26, -66, 201, 30, -67, + 174, -98, -48, 175, -94, -48, 176, -90, -49, 177, -86, -50, + 178, -82, -50, 179, -78, -51, 180, -74, -52, 181, -69, -52, + 182, -65, -53, 183, -61, -54, 183, -57, -54, 184, -53, -55, + 185, -49, -56, 186, -45, -56, 187, -41, -57, 188, -37, -58, + 189, -32, -58, 190, -28, -59, 191, -24, -60, 192, -20, -60, + 193, -16, -61, 194, -12, -62, 195, -8, -62, 196, -4, -63, + 197, 0, -64, 198, 4, -64, 199, 8, -65, 199, 12, -66, + 200, 16, -66, 201, 20, -67, 202, 24, -68, 203, 28, -68, + 176, -99, -49, 177, -95, -50, 178, -91, -51, 179, -87, -51, + 180, -83, -52, 181, -79, -53, 182, -75, -53, 183, -70, -54, + 184, -66, -55, 185, -62, -55, 186, -58, -56, 187, -54, -57, + 188, -50, -57, 189, -46, -58, 190, -42, -59, 191, -38, -59, + 192, -33, -60, 192, -29, -61, 193, -25, -61, 194, -21, -62, + 195, -17, -63, 196, -13, -63, 197, -9, -64, 198, -5, -65, + 199, -1, -65, 200, 3, -66, 201, 7, -67, 202, 11, -67, + 203, 15, -68, 204, 19, -69, 205, 23, -70, 206, 27, -70, + 179, -101, -51, 180, -97, -52, 181, -93, -52, 182, -88, -53, + 183, -84, -54, 184, -80, -54, 184, -76, -55, 185, -72, -56, + 186, -68, -56, 187, -64, -57, 188, -60, -58, 189, -56, -58, + 190, -51, -59, 191, -47, -60, 192, -43, -60, 193, -39, -61, + 194, -35, -62, 195, -31, -62, 196, -27, -63, 197, -23, -64, + 198, -18, -65, 199, -14, -65, 199, -10, -66, 200, -6, -66, + 201, -2, -67, 202, 1, -68, 203, 5, -68, 204, 9, -69, + 205, 13, -70, 206, 18, -71, 207, 22, -71, 208, 26, -72, + 181, -102, -53, 182, -98, -53, 183, -94, -54, 184, -90, -55, + 185, -86, -55, 186, -82, -56, 187, -78, -57, 188, -73, -57, + 189, -69, -58, 190, -65, -59, 191, -61, -59, 191, -57, -60, + 192, -53, -61, 193, -49, -61, 194, -45, -62, 195, -41, -63, + 196, -36, -63, 197, -32, -64, 198, -28, -65, 199, -24, -65, + 200, -20, -66, 201, -16, -67, 202, -12, -67, 203, -8, -68, + 204, -4, -69, 205, 0, -70, 206, 4, -70, 206, 8, -71, + 207, 12, -71, 208, 16, -72, 209, 20, -73, 210, 24, -74, + 34, -19, 57, 35, -15, 56, 36, -11, 56, 37, -6, 55, + 38, -2, 54, 39, 1, 54, 39, 5, 53, 40, 9, 52, + 41, 13, 52, 42, 17, 51, 43, 21, 50, 44, 25, 50, + 45, 30, 49, 46, 34, 48, 47, 38, 48, 48, 42, 47, + 49, 46, 46, 50, 50, 46, 51, 54, 45, 52, 58, 44, + 53, 63, 44, 54, 67, 43, 55, 71, 42, 55, 75, 42, + 56, 79, 41, 57, 83, 40, 58, 87, 40, 59, 91, 39, + 60, 95, 38, 61, 100, 38, 62, 104, 37, 63, 108, 36, + 36, -20, 55, 37, -16, 55, 38, -12, 54, 39, -8, 53, + 40, -4, 53, 41, 0, 52, 42, 3, 51, 43, 8, 51, + 44, 12, 50, 45, 16, 49, 46, 20, 49, 46, 24, 48, + 48, 28, 47, 48, 32, 47, 49, 36, 46, 50, 40, 45, + 51, 45, 45, 52, 49, 44, 53, 53, 43, 54, 57, 43, + 55, 61, 42, 56, 65, 41, 57, 69, 41, 58, 73, 40, + 59, 77, 39, 60, 82, 39, 61, 86, 38, 62, 90, 37, + 62, 94, 37, 63, 98, 36, 64, 102, 35, 65, 106, 35, + 39, -22, 54, 39, -18, 53, 40, -14, 52, 41, -9, 52, + 42, -5, 51, 43, -1, 50, 44, 2, 50, 45, 6, 49, + 46, 10, 48, 47, 14, 48, 48, 18, 47, 49, 22, 46, + 50, 27, 46, 51, 31, 45, 52, 35, 44, 53, 39, 44, + 54, 43, 43, 55, 47, 42, 55, 51, 42, 56, 55, 41, + 57, 60, 40, 58, 64, 40, 59, 68, 39, 60, 72, 38, + 61, 76, 38, 62, 80, 37, 63, 84, 36, 64, 88, 36, + 65, 92, 35, 66, 97, 34, 67, 101, 34, 68, 105, 33, + 41, -23, 52, 42, -19, 51, 43, -15, 51, 44, -10, 50, + 45, -6, 49, 46, -2, 49, 47, 1, 48, 48, 5, 47, + 48, 9, 47, 49, 13, 46, 50, 17, 45, 51, 21, 45, + 52, 26, 44, 53, 30, 43, 54, 34, 43, 55, 38, 42, + 56, 42, 41, 57, 46, 41, 58, 50, 40, 59, 54, 39, + 60, 59, 39, 61, 63, 38, 62, 67, 37, 62, 71, 37, + 63, 75, 36, 64, 79, 35, 65, 83, 35, 66, 87, 34, + 67, 91, 33, 68, 96, 33, 69, 100, 32, 70, 104, 31, + 43, -24, 50, 44, -20, 50, 45, -16, 49, 46, -12, 48, + 47, -8, 48, 48, -4, 47, 49, 0, 46, 50, 4, 46, + 51, 8, 45, 52, 12, 44, 53, 16, 44, 54, 20, 43, + 55, 24, 42, 55, 28, 42, 56, 32, 41, 57, 36, 40, + 58, 41, 40, 59, 45, 39, 60, 49, 38, 61, 53, 38, + 62, 57, 37, 63, 61, 36, 64, 65, 36, 65, 69, 35, + 66, 73, 34, 67, 78, 34, 68, 82, 33, 69, 86, 32, + 69, 90, 32, 71, 94, 31, 71, 98, 30, 72, 102, 30, + 46, -26, 49, 47, -22, 48, 47, -18, 47, 48, -13, 47, + 49, -9, 46, 50, -5, 45, 51, -1, 45, 52, 2, 44, + 53, 6, 43, 54, 10, 43, 55, 14, 42, 56, 18, 41, + 57, 23, 41, 58, 27, 40, 59, 31, 39, 60, 35, 39, + 61, 39, 38, 62, 43, 37, 62, 47, 37, 63, 51, 36, + 64, 56, 35, 65, 60, 35, 66, 64, 34, 67, 68, 33, + 68, 72, 33, 69, 76, 32, 70, 80, 31, 71, 84, 31, + 72, 88, 30, 73, 93, 29, 74, 97, 29, 75, 101, 28, + 48, -27, 47, 49, -23, 46, 50, -19, 46, 51, -14, 45, + 52, -10, 44, 53, -6, 44, 54, -2, 43, 55, 1, 42, + 55, 5, 42, 56, 9, 41, 57, 13, 40, 58, 17, 40, + 59, 22, 39, 60, 26, 38, 61, 30, 38, 62, 34, 37, + 63, 38, 36, 64, 42, 36, 65, 46, 35, 66, 50, 34, + 67, 55, 34, 68, 59, 33, 69, 63, 32, 70, 67, 32, + 70, 71, 31, 71, 75, 30, 72, 79, 30, 73, 83, 29, + 74, 87, 28, 75, 92, 28, 76, 96, 27, 77, 100, 26, + 50, -28, 45, 51, -24, 45, 52, -20, 44, 53, -16, 43, + 54, -12, 43, 55, -8, 42, 56, -4, 41, 57, 0, 41, + 58, 4, 40, 59, 8, 39, 60, 12, 39, 61, 16, 38, + 62, 20, 37, 63, 24, 37, 63, 28, 36, 64, 32, 35, + 65, 37, 35, 66, 41, 34, 67, 45, 33, 68, 49, 33, + 69, 53, 32, 70, 57, 31, 71, 61, 31, 72, 65, 30, + 73, 69, 29, 74, 74, 29, 75, 78, 28, 76, 82, 27, + 77, 86, 27, 78, 90, 26, 78, 94, 25, 79, 98, 25, + 53, -30, 44, 54, -26, 43, 54, -22, 42, 56, -17, 42, + 56, -13, 41, 57, -9, 40, 58, -5, 40, 59, -1, 39, + 60, 2, 38, 61, 6, 38, 62, 10, 37, 63, 14, 36, + 64, 19, 36, 65, 23, 35, 66, 27, 34, 67, 31, 34, + 68, 35, 33, 69, 39, 32, 70, 43, 32, 70, 47, 31, + 71, 52, 30, 72, 56, 30, 73, 60, 29, 74, 64, 28, + 75, 68, 28, 76, 72, 27, 77, 76, 26, 78, 80, 26, + 79, 84, 25, 80, 89, 24, 81, 93, 24, 82, 97, 23, + 55, -31, 42, 56, -27, 41, 57, -23, 41, 58, -18, 40, + 59, -14, 39, 60, -10, 39, 61, -6, 38, 62, -2, 37, + 63, 1, 37, 63, 5, 36, 64, 9, 35, 65, 13, 35, + 66, 18, 34, 67, 22, 33, 68, 26, 33, 69, 30, 32, + 70, 34, 31, 71, 38, 31, 72, 42, 30, 73, 46, 29, + 74, 51, 29, 75, 55, 28, 76, 59, 27, 77, 63, 27, + 77, 67, 26, 79, 71, 25, 79, 75, 25, 80, 79, 24, + 81, 83, 23, 82, 88, 22, 83, 92, 22, 84, 96, 21, + 57, -32, 40, 58, -28, 40, 59, -24, 39, 60, -20, 38, + 61, -16, 38, 62, -12, 37, 63, -8, 36, 64, -3, 36, + 65, 0, 35, 66, 4, 34, 67, 8, 34, 68, 12, 33, + 69, 16, 32, 70, 20, 32, 70, 24, 31, 71, 28, 30, + 72, 33, 30, 73, 37, 29, 74, 41, 28, 75, 45, 28, + 76, 49, 27, 77, 53, 26, 78, 57, 26, 79, 61, 25, + 80, 65, 24, 81, 70, 24, 82, 74, 23, 83, 78, 22, + 84, 82, 22, 85, 86, 21, 86, 90, 20, 86, 94, 20, + 60, -34, 38, 61, -30, 38, 62, -26, 37, 63, -21, 36, + 64, -17, 35, 65, -13, 35, 66, -9, 34, 67, -5, 33, + 68, -1, 33, 69, 2, 32, 70, 6, 31, 71, 10, 31, + 72, 15, 30, 72, 19, 29, 73, 23, 29, 74, 27, 28, + 75, 31, 27, 76, 35, 27, 77, 39, 26, 78, 43, 25, + 79, 48, 25, 80, 52, 24, 81, 56, 23, 82, 60, 23, + 83, 64, 22, 84, 68, 21, 85, 72, 21, 86, 76, 20, + 87, 80, 19, 88, 85, 19, 88, 89, 18, 89, 93, 17, + 63, -35, 36, 64, -31, 36, 64, -27, 35, 65, -23, 34, + 66, -19, 34, 67, -15, 33, 68, -11, 33, 69, -6, 32, + 70, -2, 31, 71, 1, 30, 72, 5, 30, 73, 9, 29, + 74, 13, 28, 75, 17, 28, 76, 21, 27, 77, 25, 26, + 78, 30, 26, 79, 34, 25, 80, 38, 24, 80, 42, 24, + 81, 46, 23, 82, 50, 22, 83, 54, 22, 84, 58, 21, + 85, 62, 20, 86, 67, 20, 87, 71, 19, 88, 75, 18, + 89, 79, 18, 90, 83, 17, 91, 87, 16, 92, 91, 16, + 65, -36, 35, 66, -32, 34, 67, -28, 34, 68, -24, 33, + 69, -20, 32, 70, -16, 31, 71, -12, 31, 72, -7, 30, + 73, -3, 29, 73, 0, 29, 74, 4, 28, 75, 8, 27, + 76, 12, 27, 77, 16, 26, 78, 20, 25, 79, 24, 25, + 80, 29, 24, 81, 33, 23, 82, 37, 23, 83, 41, 22, + 84, 45, 21, 85, 49, 21, 86, 53, 20, 87, 57, 19, + 87, 61, 19, 88, 66, 18, 89, 70, 17, 90, 74, 17, + 91, 78, 16, 92, 82, 15, 93, 86, 15, 94, 90, 14, + 67, -38, 33, 68, -34, 32, 69, -30, 32, 70, -25, 31, + 71, -21, 30, 72, -17, 30, 73, -13, 29, 74, -9, 28, + 75, -5, 28, 76, -1, 27, 77, 2, 26, 78, 6, 26, + 79, 11, 25, 80, 15, 24, 80, 19, 24, 81, 23, 23, + 82, 27, 22, 83, 31, 22, 84, 35, 21, 85, 39, 20, + 86, 44, 20, 87, 48, 19, 88, 52, 18, 89, 56, 18, + 90, 60, 17, 91, 64, 16, 92, 68, 16, 93, 72, 15, + 94, 76, 14, 95, 81, 14, 96, 85, 13, 96, 89, 12, + 70, -39, 31, 71, -35, 31, 72, -31, 30, 73, -27, 29, + 73, -23, 29, 74, -19, 28, 75, -15, 27, 76, -10, 27, + 77, -6, 26, 78, -2, 25, 79, 1, 25, 80, 5, 24, + 81, 9, 23, 82, 13, 23, 83, 17, 22, 84, 21, 21, + 85, 26, 21, 86, 30, 20, 87, 34, 19, 87, 38, 19, + 89, 42, 18, 89, 46, 17, 90, 50, 17, 91, 54, 16, + 92, 58, 15, 93, 63, 15, 94, 67, 14, 95, 71, 13, + 96, 75, 13, 97, 79, 12, 98, 83, 11, 99, 87, 11, + 72, -40, 30, 73, -36, 29, 74, -32, 28, 75, -28, 28, + 76, -24, 27, 77, -20, 26, 78, -16, 26, 79, -11, 25, + 80, -7, 24, 80, -3, 24, 81, 0, 23, 82, 4, 22, + 83, 8, 22, 84, 12, 21, 85, 16, 20, 86, 20, 20, + 87, 25, 19, 88, 29, 18, 89, 33, 18, 90, 37, 17, + 91, 41, 16, 92, 45, 16, 93, 49, 15, 94, 53, 14, + 94, 57, 14, 96, 62, 13, 96, 66, 12, 97, 70, 12, + 98, 74, 11, 99, 78, 10, 100, 82, 10, 101, 86, 9, + 74, -42, 28, 75, -38, 27, 76, -34, 27, 77, -29, 26, + 78, -25, 25, 79, -21, 25, 80, -17, 24, 81, -13, 23, + 82, -9, 23, 83, -5, 22, 84, -1, 21, 85, 2, 21, + 86, 7, 20, 87, 11, 19, 87, 15, 19, 88, 19, 18, + 89, 23, 17, 90, 27, 17, 91, 31, 16, 92, 35, 15, + 93, 40, 15, 94, 44, 14, 95, 48, 13, 96, 52, 13, + 97, 56, 12, 98, 60, 11, 99, 64, 11, 100, 68, 10, + 101, 72, 9, 102, 77, 9, 103, 81, 8, 103, 85, 7, + 77, -43, 26, 78, -39, 26, 79, -35, 25, 80, -31, 24, + 80, -27, 24, 81, -23, 23, 82, -19, 22, 83, -14, 22, + 84, -10, 21, 85, -6, 20, 86, -2, 20, 87, 1, 19, + 88, 5, 18, 89, 9, 18, 90, 13, 17, 91, 17, 16, + 92, 22, 16, 93, 26, 15, 94, 30, 14, 95, 34, 14, + 96, 38, 13, 96, 42, 12, 97, 46, 12, 98, 50, 11, + 99, 54, 10, 100, 59, 10, 101, 63, 9, 102, 67, 8, + 103, 71, 8, 104, 75, 7, 105, 79, 6, 106, 83, 6, + 79, -44, 25, 80, -40, 24, 81, -36, 23, 82, -32, 23, + 83, -28, 22, 84, -24, 21, 85, -20, 21, 86, -15, 20, + 87, -11, 19, 88, -7, 19, 88, -3, 18, 89, 0, 17, + 90, 4, 17, 91, 8, 16, 92, 12, 15, 93, 16, 15, + 94, 21, 14, 95, 25, 13, 96, 29, 13, 97, 33, 12, + 98, 37, 11, 99, 41, 11, 100, 45, 10, 101, 49, 9, + 102, 53, 9, 103, 58, 8, 103, 62, 7, 104, 66, 7, + 105, 70, 6, 106, 74, 5, 107, 78, 5, 108, 82, 4, + 81, -46, 23, 82, -42, 22, 83, -38, 22, 84, -33, 21, + 85, -29, 20, 86, -25, 20, 87, -21, 19, 88, -17, 18, + 89, -13, 18, 90, -9, 17, 91, -5, 16, 92, -1, 16, + 93, 3, 15, 94, 7, 14, 95, 11, 14, 95, 15, 13, + 96, 19, 12, 97, 23, 12, 98, 27, 11, 99, 31, 10, + 100, 36, 10, 101, 40, 9, 102, 44, 8, 103, 48, 8, + 104, 52, 7, 105, 56, 6, 106, 60, 6, 107, 64, 5, + 108, 68, 4, 109, 73, 4, 110, 77, 3, 111, 81, 2, + 84, -47, 21, 85, -43, 21, 86, -39, 20, 87, -35, 19, + 88, -31, 19, 88, -27, 18, 89, -23, 17, 90, -18, 17, + 91, -14, 16, 92, -10, 15, 93, -6, 15, 94, -2, 14, + 95, 1, 13, 96, 5, 13, 97, 9, 12, 98, 13, 11, + 99, 18, 11, 100, 22, 10, 101, 26, 9, 102, 30, 9, + 103, 34, 8, 104, 38, 7, 104, 42, 7, 105, 46, 6, + 106, 50, 5, 107, 55, 5, 108, 59, 4, 109, 63, 3, + 110, 67, 3, 111, 71, 2, 112, 75, 1, 113, 79, 1, + 86, -48, 20, 87, -44, 19, 88, -40, 18, 89, -36, 18, + 90, -32, 17, 91, -28, 16, 92, -24, 16, 93, -19, 15, + 94, -15, 14, 95, -11, 14, 95, -7, 13, 96, -3, 12, + 97, 0, 12, 98, 4, 11, 99, 8, 10, 100, 12, 10, + 101, 17, 9, 102, 21, 8, 103, 25, 8, 104, 29, 7, + 105, 33, 6, 106, 37, 6, 107, 41, 5, 108, 45, 4, + 109, 49, 4, 110, 54, 3, 111, 58, 2, 111, 62, 2, + 112, 66, 1, 113, 70, 0, 114, 74, 0, 115, 78, 0, + 88, -50, 18, 89, -46, 17, 90, -42, 17, 91, -37, 16, + 92, -33, 15, 93, -29, 15, 94, -25, 14, 95, -21, 13, + 96, -17, 13, 97, -13, 12, 98, -9, 11, 99, -5, 11, + 100, 0, 10, 101, 3, 9, 102, 7, 9, 102, 11, 8, + 104, 15, 7, 104, 19, 7, 105, 23, 6, 106, 27, 5, + 107, 32, 5, 108, 36, 4, 109, 40, 3, 110, 44, 3, + 111, 48, 2, 112, 52, 1, 113, 56, 1, 114, 60, 0, + 115, 64, 0, 116, 69, 0, 117, 73, -1, 118, 77, -2, + 91, -51, 16, 92, -47, 16, 93, -43, 15, 94, -39, 14, + 95, -35, 14, 95, -31, 13, 96, -27, 12, 97, -22, 12, + 98, -18, 11, 99, -14, 10, 100, -10, 10, 101, -6, 9, + 102, -2, 8, 103, 1, 8, 104, 5, 7, 105, 9, 6, + 106, 14, 6, 107, 18, 5, 108, 22, 4, 109, 26, 4, + 110, 30, 3, 111, 34, 2, 111, 38, 2, 112, 42, 1, + 113, 46, 0, 114, 51, 0, 115, 55, 0, 116, 59, -1, + 117, 63, -1, 118, 67, -2, 119, 71, -3, 120, 75, -3, + 93, -52, 15, 94, -48, 14, 95, -44, 13, 96, -40, 13, + 97, -36, 12, 98, -32, 11, 99, -28, 11, 100, -23, 10, + 101, -19, 9, 102, -15, 9, 103, -11, 8, 103, -7, 7, + 104, -3, 7, 105, 0, 6, 106, 4, 5, 107, 8, 5, + 108, 13, 4, 109, 17, 3, 110, 21, 3, 111, 25, 2, + 112, 29, 1, 113, 33, 1, 114, 37, 0, 115, 41, 0, + 116, 45, 0, 117, 50, -1, 118, 54, -2, 118, 58, -2, + 119, 62, -3, 120, 66, -4, 121, 70, -4, 122, 74, -5, + 96, -54, 13, 96, -50, 12, 97, -46, 12, 98, -41, 11, + 99, -37, 10, 100, -33, 10, 101, -29, 9, 102, -25, 8, + 103, -21, 8, 104, -17, 7, 105, -13, 6, 106, -9, 6, + 107, -4, 5, 108, 0, 4, 109, 3, 4, 110, 7, 3, + 111, 11, 2, 111, 15, 2, 112, 19, 1, 113, 23, 0, + 114, 28, 0, 115, 32, 0, 116, 36, -1, 117, 40, -1, + 118, 44, -2, 119, 48, -3, 120, 52, -3, 121, 56, -4, + 122, 60, -5, 123, 65, -5, 124, 69, -6, 125, 73, -7, + 98, -55, 11, 99, -51, 11, 100, -47, 10, 101, -43, 9, + 102, -39, 9, 103, -35, 8, 103, -31, 7, 104, -26, 7, + 105, -22, 6, 106, -18, 5, 107, -14, 5, 108, -10, 4, + 109, -6, 3, 110, -2, 3, 111, 1, 2, 112, 5, 1, + 113, 10, 1, 114, 14, 0, 115, 18, 0, 116, 22, 0, + 117, 26, -1, 118, 30, -2, 119, 34, -2, 119, 38, -3, + 120, 42, -4, 121, 47, -4, 122, 51, -5, 123, 55, -6, + 124, 59, -6, 125, 63, -7, 126, 67, -8, 127, 71, -8, + 100, -56, 10, 101, -52, 9, 102, -48, 8, 103, -44, 8, + 104, -40, 7, 105, -36, 6, 106, -32, 6, 107, -27, 5, + 108, -23, 4, 109, -19, 4, 110, -15, 3, 110, -11, 2, + 112, -7, 2, 112, -3, 1, 113, 0, 0, 114, 4, 0, + 115, 9, 0, 116, 13, -1, 117, 17, -1, 118, 21, -2, + 119, 25, -3, 120, 29, -3, 121, 33, -4, 122, 37, -5, + 123, 41, -5, 124, 46, -6, 125, 50, -7, 126, 54, -7, + 126, 58, -8, 127, 62, -9, 128, 66, -9, 129, 70, -10, + 103, -58, 8, 103, -54, 7, 104, -50, 7, 105, -45, 6, + 106, -41, 5, 107, -37, 5, 108, -33, 4, 109, -29, 3, + 110, -25, 3, 111, -21, 2, 112, -17, 1, 113, -13, 1, + 114, -8, 0, 115, -4, 0, 116, 0, 0, 117, 3, -1, + 118, 7, -2, 119, 11, -2, 119, 15, -3, 120, 19, -4, + 121, 24, -4, 122, 28, -5, 123, 32, -6, 124, 36, -6, + 125, 40, -7, 126, 44, -8, 127, 48, -8, 128, 52, -9, + 129, 56, -10, 130, 61, -10, 131, 65, -11, 132, 69, -12, + 105, -59, 6, 106, -55, 6, 107, -51, 5, 108, -46, 4, + 109, -42, 4, 110, -38, 3, 110, -34, 2, 112, -30, 2, + 112, -26, 1, 113, -22, 0, 114, -18, 0, 115, -14, 0, + 116, -9, -1, 117, -5, -1, 118, -1, -2, 119, 2, -3, + 120, 6, -3, 121, 10, -4, 122, 14, -5, 123, 18, -5, + 124, 23, -6, 125, 27, -7, 126, 31, -7, 126, 35, -8, + 127, 39, -9, 128, 43, -9, 129, 47, -10, 130, 51, -11, + 131, 55, -11, 132, 60, -12, 133, 64, -13, 134, 68, -13, + 107, -60, 5, 108, -56, 4, 109, -52, 3, 110, -48, 3, + 111, -44, 2, 112, -40, 1, 113, -36, 1, 114, -31, 0, + 115, -27, 0, 116, -23, 0, 117, -19, -1, 118, -15, -2, + 119, -11, -2, 119, -7, -3, 120, -3, -4, 121, 0, -4, + 122, 5, -5, 123, 9, -6, 124, 13, -6, 125, 17, -7, + 126, 21, -8, 127, 25, -8, 128, 29, -9, 129, 33, -10, + 130, 37, -10, 131, 42, -11, 132, 46, -12, 133, 50, -12, + 133, 54, -13, 135, 58, -14, 135, 62, -14, 136, 66, -15, + 110, -62, 3, 111, -58, 2, 112, -54, 1, 113, -49, 1, + 114, -45, 0, 115, -41, 0, 116, -37, 0, 117, -33, -1, + 118, -29, -2, 119, -25, -2, 120, -21, -3, 120, -17, -4, + 121, -12, -4, 122, -8, -5, 123, -4, -6, 124, 0, -6, + 125, 3, -7, 126, 7, -8, 127, 11, -8, 128, 15, -9, + 129, 20, -10, 130, 24, -10, 131, 28, -11, 132, 32, -12, + 133, 36, -12, 134, 40, -13, 135, 44, -14, 136, 48, -14, + 136, 52, -15, 137, 57, -16, 138, 61, -17, 139, 65, -17, + 113, -63, 1, 113, -59, 0, 114, -55, 0, 115, -51, 0, + 116, -47, -1, 117, -43, -1, 118, -39, -2, 119, -34, -3, + 120, -30, -3, 121, -26, -4, 122, -22, -5, 123, -18, -5, + 124, -14, -6, 125, -10, -7, 126, -6, -7, 127, -2, -8, + 128, 2, -9, 129, 6, -9, 129, 10, -10, 130, 14, -11, + 131, 18, -12, 132, 22, -12, 133, 26, -13, 134, 30, -13, + 135, 34, -14, 136, 39, -15, 137, 43, -16, 138, 47, -16, + 139, 51, -17, 140, 55, -18, 141, 59, -18, 142, 63, -19, + 115, -65, 0, 116, -61, 0, 117, -57, -1, 118, -52, -2, + 119, -48, -2, 120, -44, -3, 120, -40, -4, 122, -36, -4, + 122, -32, -5, 123, -28, -6, 124, -24, -6, 125, -20, -7, + 126, -15, -8, 127, -11, -8, 128, -7, -9, 129, -3, -10, + 130, 0, -11, 131, 4, -11, 132, 8, -12, 133, 12, -12, + 134, 17, -13, 135, 21, -14, 136, 25, -14, 136, 29, -15, + 137, 33, -16, 138, 37, -17, 139, 41, -17, 140, 45, -18, + 141, 49, -18, 142, 54, -19, 143, 58, -20, 144, 62, -21, + 117, -66, -1, 118, -62, -2, 119, -58, -3, 120, -53, -3, + 121, -49, -4, 122, -45, -5, 123, -41, -5, 124, -37, -6, + 125, -33, -7, 126, -29, -7, 127, -25, -8, 127, -21, -9, + 129, -16, -10, 129, -12, -10, 130, -8, -11, 131, -4, -11, + 132, 0, -12, 133, 3, -13, 134, 7, -13, 135, 11, -14, + 136, 16, -15, 137, 20, -16, 138, 24, -16, 139, 28, -17, + 140, 32, -17, 141, 36, -18, 142, 40, -19, 143, 44, -20, + 143, 48, -20, 144, 53, -21, 145, 57, -22, 146, 61, -22, + 120, -67, -3, 120, -63, -4, 121, -59, -4, 122, -55, -5, + 123, -51, -6, 124, -47, -6, 125, -43, -7, 126, -38, -8, + 127, -34, -8, 128, -30, -9, 129, -26, -10, 130, -22, -10, + 131, -18, -11, 132, -14, -12, 133, -10, -12, 134, -6, -13, + 135, -1, -14, 136, 2, -15, 136, 6, -15, 137, 10, -16, + 138, 14, -17, 139, 18, -17, 140, 22, -18, 141, 26, -18, + 142, 30, -19, 143, 35, -20, 144, 39, -21, 145, 43, -21, + 146, 47, -22, 147, 51, -23, 148, 55, -23, 149, 59, -24, + 122, -69, -5, 123, -65, -5, 124, -61, -6, 125, -56, -7, + 126, -52, -7, 127, -48, -8, 128, -44, -9, 129, -40, -10, + 129, -36, -10, 130, -32, -11, 131, -28, -11, 132, -24, -12, + 133, -19, -13, 134, -15, -14, 135, -11, -14, 136, -7, -15, + 137, -3, -16, 138, 0, -16, 139, 4, -17, 140, 8, -17, + 141, 13, -18, 142, 17, -19, 143, 21, -20, 143, 25, -20, + 144, 29, -21, 145, 33, -22, 146, 37, -22, 147, 41, -23, + 148, 45, -24, 149, 50, -24, 150, 54, -25, 151, 58, -26, + 124, -70, -6, 125, -66, -7, 126, -62, -8, 127, -57, -9, + 128, -53, -9, 129, -49, -10, 130, -45, -10, 131, -41, -11, + 132, -37, -12, 133, -33, -12, 134, -29, -13, 135, -25, -14, + 136, -20, -15, 136, -16, -15, 137, -12, -16, 138, -8, -16, + 139, -4, -17, 140, 0, -18, 141, 3, -19, 142, 7, -19, + 143, 12, -20, 144, 16, -21, 145, 20, -21, 146, 24, -22, + 147, 28, -22, 148, 32, -23, 149, 36, -24, 150, 40, -25, + 151, 44, -25, 152, 49, -26, 152, 53, -27, 153, 57, -27, + 127, -71, -8, 128, -67, -9, 128, -63, -9, 129, -59, -10, + 130, -55, -11, 131, -51, -11, 132, -47, -12, 133, -42, -13, + 134, -38, -14, 135, -34, -14, 136, -30, -15, 137, -26, -15, + 138, -22, -16, 139, -18, -17, 140, -14, -18, 141, -10, -18, + 142, -5, -19, 143, -1, -20, 144, 2, -20, 144, 6, -21, + 145, 10, -22, 146, 14, -22, 147, 18, -23, 148, 22, -24, + 149, 26, -24, 150, 31, -25, 151, 35, -26, 152, 39, -26, + 153, 43, -27, 154, 47, -28, 155, 51, -28, 156, 55, -29, + 129, -73, -10, 130, -69, -10, 131, -65, -11, 132, -60, -12, + 133, -56, -13, 134, -52, -13, 135, -48, -14, 136, -44, -15, + 137, -40, -15, 137, -36, -16, 138, -32, -16, 139, -28, -17, + 140, -23, -18, 141, -19, -19, 142, -15, -19, 143, -11, -20, + 144, -7, -21, 145, -3, -21, 146, 0, -22, 147, 4, -23, + 148, 9, -23, 149, 13, -24, 150, 17, -25, 151, 21, -25, + 151, 25, -26, 152, 29, -27, 153, 33, -27, 154, 37, -28, + 155, 41, -29, 156, 46, -29, 157, 50, -30, 158, 54, -31, + 131, -74, -12, 132, -70, -12, 133, -66, -13, 134, -61, -14, + 135, -57, -14, 136, -53, -15, 137, -49, -15, 138, -45, -16, + 139, -41, -17, 140, -37, -18, 141, -33, -18, 142, -29, -19, + 143, -24, -20, 144, -20, -20, 144, -16, -21, 145, -12, -22, + 146, -8, -22, 147, -4, -23, 148, 0, -24, 149, 3, -24, + 150, 8, -25, 151, 12, -26, 152, 16, -26, 153, 20, -27, + 154, 24, -28, 155, 28, -28, 156, 32, -29, 157, 36, -30, + 158, 40, -30, 159, 45, -31, 159, 49, -32, 160, 53, -32, + 134, -75, -13, 135, -71, -14, 135, -67, -14, 137, -63, -15, + 137, -59, -16, 138, -55, -17, 139, -51, -17, 140, -46, -18, + 141, -42, -19, 142, -38, -19, 143, -34, -20, 144, -30, -20, + 145, -26, -21, 146, -22, -22, 147, -18, -23, 148, -14, -23, + 149, -9, -24, 150, -5, -25, 151, -1, -25, 151, 2, -26, + 152, 6, -27, 153, 10, -27, 154, 14, -28, 155, 18, -29, + 156, 22, -29, 157, 27, -30, 158, 31, -31, 159, 35, -31, + 160, 39, -32, 161, 43, -33, 162, 47, -33, 163, 51, -34, + 136, -77, -15, 137, -73, -16, 138, -69, -16, 139, -64, -17, + 140, -60, -18, 141, -56, -18, 142, -52, -19, 143, -48, -20, + 144, -44, -20, 144, -40, -21, 145, -36, -22, 146, -32, -22, + 147, -27, -23, 148, -23, -24, 149, -19, -24, 150, -15, -25, + 151, -11, -26, 152, -7, -26, 153, -3, -27, 154, 0, -28, + 155, 5, -28, 156, 9, -29, 157, 13, -30, 158, 17, -30, + 158, 21, -31, 160, 25, -32, 160, 29, -32, 161, 33, -33, + 162, 37, -34, 163, 42, -34, 164, 46, -35, 165, 50, -36, + 138, -78, -17, 139, -74, -17, 140, -70, -18, 141, -65, -19, + 142, -61, -19, 143, -57, -20, 144, -53, -21, 145, -49, -21, + 146, -45, -22, 147, -41, -23, 148, -37, -23, 149, -33, -24, + 150, -28, -25, 151, -24, -25, 151, -20, -26, 152, -16, -27, + 153, -12, -27, 154, -8, -28, 155, -4, -29, 156, 0, -29, + 157, 4, -30, 158, 8, -31, 159, 12, -31, 160, 16, -32, + 161, 20, -33, 162, 24, -33, 163, 28, -34, 164, 32, -35, + 165, 36, -35, 166, 41, -36, 167, 45, -37, 167, 49, -37, + 141, -79, -18, 142, -75, -19, 143, -71, -20, 144, -67, -20, + 144, -63, -21, 145, -59, -22, 146, -55, -22, 147, -50, -23, + 148, -46, -24, 149, -42, -24, 150, -38, -25, 151, -34, -26, + 152, -30, -26, 153, -26, -27, 154, -22, -28, 155, -18, -28, + 156, -13, -29, 157, -9, -30, 158, -5, -30, 159, -1, -31, + 160, 2, -32, 160, 6, -32, 161, 10, -33, 162, 14, -34, + 163, 18, -34, 164, 23, -35, 165, 27, -36, 166, 31, -36, + 167, 35, -37, 168, 39, -38, 169, 43, -38, 170, 47, -39, + 143, -81, -20, 144, -77, -21, 145, -73, -21, 146, -68, -22, + 147, -64, -23, 148, -60, -23, 149, -56, -24, 150, -52, -25, + 151, -48, -25, 152, -44, -26, 152, -40, -27, 153, -36, -27, + 154, -31, -28, 155, -27, -29, 156, -23, -29, 157, -19, -30, + 158, -15, -31, 159, -11, -31, 160, -7, -32, 161, -3, -33, + 162, 1, -33, 163, 5, -34, 164, 9, -35, 165, 13, -35, + 166, 17, -36, 167, 21, -37, 167, 25, -37, 168, 29, -38, + 169, 33, -39, 170, 38, -39, 171, 42, -40, 172, 46, -41, + 145, -82, -22, 146, -78, -22, 147, -74, -23, 148, -69, -24, + 149, -65, -24, 150, -61, -25, 151, -57, -26, 152, -53, -26, + 153, -49, -27, 154, -45, -28, 155, -41, -28, 156, -37, -29, + 157, -32, -30, 158, -28, -30, 159, -24, -31, 159, -20, -32, + 160, -16, -32, 161, -12, -33, 162, -8, -34, 163, -4, -34, + 164, 0, -35, 165, 4, -36, 166, 8, -36, 167, 12, -37, + 168, 16, -38, 169, 20, -38, 170, 24, -39, 171, 28, -40, + 172, 32, -40, 173, 37, -41, 174, 41, -42, 174, 45, -42, + 148, -83, -23, 149, -79, -24, 150, -75, -25, 151, -71, -25, + 152, -67, -26, 152, -63, -27, 153, -59, -27, 154, -54, -28, + 155, -50, -29, 156, -46, -29, 157, -42, -30, 158, -38, -31, + 159, -34, -31, 160, -30, -32, 161, -26, -33, 162, -22, -33, + 163, -17, -34, 164, -13, -35, 165, -9, -35, 166, -5, -36, + 167, -1, -37, 167, 2, -37, 168, 6, -38, 169, 10, -39, + 170, 14, -39, 171, 19, -40, 172, 23, -41, 173, 27, -41, + 174, 31, -42, 175, 35, -43, 176, 39, -43, 177, 43, -44, + 150, -84, -25, 151, -80, -26, 152, -76, -26, 153, -72, -27, + 154, -68, -28, 155, -64, -28, 156, -60, -29, 157, -55, -30, + 158, -51, -30, 159, -47, -31, 159, -43, -32, 160, -39, -32, + 161, -35, -33, 162, -31, -34, 163, -27, -34, 164, -23, -35, + 165, -18, -36, 166, -14, -36, 167, -10, -37, 168, -6, -38, + 169, -2, -38, 170, 1, -39, 171, 5, -40, 172, 9, -40, + 173, 13, -41, 174, 18, -42, 175, 22, -42, 175, 26, -43, + 176, 30, -44, 177, 34, -44, 178, 38, -45, 179, 42, -46, + 152, -86, -27, 153, -82, -27, 154, -78, -28, 155, -73, -29, + 156, -69, -29, 157, -65, -30, 158, -61, -31, 159, -57, -31, + 160, -53, -32, 161, -49, -33, 162, -45, -33, 163, -41, -34, + 164, -36, -35, 165, -32, -35, 166, -28, -36, 166, -24, -37, + 168, -20, -37, 168, -16, -38, 169, -12, -39, 170, -8, -39, + 171, -3, -40, 172, 0, -41, 173, 4, -41, 174, 8, -42, + 175, 12, -43, 176, 16, -43, 177, 20, -44, 178, 24, -45, + 179, 28, -45, 180, 33, -46, 181, 37, -47, 182, 41, -47, + 155, -87, -28, 156, -83, -29, 157, -79, -30, 158, -75, -30, + 159, -71, -31, 159, -67, -32, 160, -63, -32, 161, -58, -33, + 162, -54, -34, 163, -50, -34, 164, -46, -35, 165, -42, -36, + 166, -38, -36, 167, -34, -37, 168, -30, -38, 169, -26, -38, + 170, -21, -39, 171, -17, -40, 172, -13, -40, 173, -9, -41, + 174, -5, -42, 175, -1, -42, 175, 2, -43, 176, 6, -44, + 177, 10, -44, 178, 15, -45, 179, 19, -46, 180, 23, -46, + 181, 27, -47, 182, 31, -48, 183, 35, -48, 184, 39, -49, + 157, -88, -30, 158, -84, -31, 159, -80, -31, 160, -76, -32, + 161, -72, -33, 162, -68, -33, 163, -64, -34, 164, -59, -35, + 165, -55, -35, 166, -51, -36, 167, -47, -37, 167, -43, -37, + 168, -39, -38, 169, -35, -39, 170, -31, -39, 171, -27, -40, + 172, -22, -41, 173, -18, -41, 174, -14, -42, 175, -10, -43, + 176, -6, -43, 177, -2, -44, 178, 1, -45, 179, 5, -45, + 180, 9, -46, 181, 14, -47, 182, 18, -47, 182, 22, -48, + 183, 26, -49, 184, 30, -49, 185, 34, -50, 186, 38, -51, + 160, -90, -32, 161, -86, -33, 162, -82, -33, 163, -78, -34, + 164, -74, -35, 165, -70, -35, 166, -66, -36, 167, -61, -37, + 168, -57, -37, 169, -53, -38, 169, -49, -39, 170, -45, -39, + 171, -41, -40, 172, -37, -41, 173, -33, -41, 174, -29, -42, + 175, -24, -43, 176, -20, -43, 177, -16, -44, 178, -12, -45, + 179, -8, -45, 180, -4, -46, 181, 0, -47, 182, 3, -47, + 183, 7, -48, 184, 12, -49, 184, 16, -49, 185, 20, -50, + 186, 24, -51, 187, 28, -51, 188, 32, -52, 189, 36, -53, + 162, -91, -34, 163, -87, -34, 164, -83, -35, 165, -79, -36, + 166, -75, -36, 167, -71, -37, 168, -67, -38, 169, -62, -38, + 170, -58, -39, 171, -54, -40, 172, -50, -40, 173, -46, -41, + 174, -42, -42, 175, -38, -42, 176, -34, -43, 176, -30, -44, + 177, -25, -44, 178, -21, -45, 179, -17, -46, 180, -13, -46, + 181, -9, -47, 182, -5, -48, 183, -1, -48, 184, 2, -49, + 185, 6, -50, 186, 11, -50, 187, 15, -51, 188, 19, -52, + 189, 23, -52, 190, 27, -53, 191, 31, -54, 192, 35, -54, + 165, -93, -35, 166, -89, -36, 167, -85, -37, 168, -80, -37, + 169, -76, -38, 169, -72, -39, 170, -68, -39, 171, -64, -40, + 172, -60, -41, 173, -56, -41, 174, -52, -42, 175, -48, -43, + 176, -43, -43, 177, -39, -44, 178, -35, -45, 179, -31, -45, + 180, -27, -46, 181, -23, -47, 182, -19, -47, 183, -15, -48, + 184, -10, -49, 185, -6, -49, 185, -2, -50, 186, 1, -51, + 187, 5, -51, 188, 9, -52, 189, 13, -53, 190, 17, -53, + 191, 21, -54, 192, 26, -55, 193, 30, -55, 194, 34, -56, + 167, -94, -37, 168, -90, -38, 169, -86, -38, 170, -82, -39, + 171, -78, -40, 172, -74, -40, 173, -70, -41, 174, -65, -42, + 175, -61, -42, 176, -57, -43, 176, -53, -44, 177, -49, -44, + 178, -45, -45, 179, -41, -46, 180, -37, -46, 181, -33, -47, + 182, -28, -48, 183, -24, -48, 184, -20, -49, 185, -16, -50, + 186, -12, -50, 187, -8, -51, 188, -4, -52, 189, 0, -52, + 190, 3, -53, 191, 8, -54, 192, 12, -54, 192, 16, -55, + 193, 20, -56, 194, 24, -56, 195, 28, -57, 196, 32, -58, + 169, -95, -39, 170, -91, -39, 171, -87, -40, 172, -83, -41, + 173, -79, -41, 174, -75, -42, 175, -71, -43, 176, -66, -43, + 177, -62, -44, 178, -58, -45, 179, -54, -45, 180, -50, -46, + 181, -46, -47, 182, -42, -47, 183, -38, -48, 184, -34, -49, + 185, -29, -49, 185, -25, -50, 186, -21, -51, 187, -17, -51, + 188, -13, -52, 189, -9, -53, 190, -5, -53, 191, -1, -54, + 192, 2, -55, 193, 7, -55, 194, 11, -56, 195, 15, -57, + 196, 19, -57, 197, 23, -58, 198, 27, -59, 199, 31, -59, + 172, -97, -40, 173, -93, -41, 174, -89, -42, 175, -84, -42, + 176, -80, -43, 177, -76, -44, 177, -72, -44, 178, -68, -45, + 179, -64, -46, 180, -60, -46, 181, -56, -47, 182, -52, -48, + 183, -47, -48, 184, -43, -49, 185, -39, -50, 186, -35, -50, + 187, -31, -51, 188, -27, -52, 189, -23, -52, 190, -19, -53, + 191, -14, -54, 192, -10, -54, 192, -6, -55, 193, -2, -56, + 194, 1, -56, 195, 5, -57, 196, 9, -58, 197, 13, -58, + 198, 17, -59, 199, 22, -60, 200, 26, -60, 201, 30, -61, + 174, -98, -42, 175, -94, -43, 176, -90, -43, 177, -86, -44, + 178, -82, -45, 179, -78, -45, 180, -74, -46, 181, -69, -47, + 182, -65, -47, 183, -61, -48, 184, -57, -49, 184, -53, -49, + 185, -49, -50, 186, -45, -51, 187, -41, -51, 188, -37, -52, + 189, -32, -53, 190, -28, -53, 191, -24, -54, 192, -20, -55, + 193, -16, -55, 194, -12, -56, 195, -8, -57, 196, -4, -57, + 197, 0, -58, 198, 4, -59, 199, 8, -59, 199, 12, -60, + 200, 16, -61, 201, 20, -62, 202, 24, -62, 203, 28, -63, + 177, -99, -44, 177, -95, -44, 178, -91, -45, 179, -87, -46, + 180, -83, -46, 181, -79, -47, 182, -75, -48, 183, -70, -48, + 184, -66, -49, 185, -62, -50, 186, -58, -50, 187, -54, -51, + 188, -50, -52, 189, -46, -52, 190, -42, -53, 191, -38, -54, + 192, -33, -54, 192, -29, -55, 193, -25, -56, 194, -21, -56, + 195, -17, -57, 196, -13, -58, 197, -9, -58, 198, -5, -59, + 199, -1, -60, 200, 3, -60, 201, 7, -61, 202, 11, -62, + 203, 15, -62, 204, 19, -63, 205, 23, -64, 206, 27, -64, + 179, -101, -45, 180, -97, -46, 181, -93, -47, 182, -88, -47, + 183, -84, -48, 184, -80, -49, 184, -76, -49, 185, -72, -50, + 186, -68, -51, 187, -64, -51, 188, -60, -52, 189, -56, -53, + 190, -51, -53, 191, -47, -54, 192, -43, -55, 193, -39, -55, + 194, -35, -56, 195, -31, -57, 196, -27, -57, 197, -23, -58, + 198, -18, -59, 199, -14, -59, 200, -10, -60, 200, -6, -61, + 201, -2, -61, 202, 1, -62, 203, 5, -63, 204, 9, -63, + 205, 13, -64, 206, 18, -65, 207, 22, -66, 208, 26, -66, + 181, -102, -47, 182, -98, -48, 183, -94, -48, 184, -90, -49, + 185, -86, -50, 186, -82, -50, 187, -78, -51, 188, -73, -52, + 189, -69, -52, 190, -65, -53, 191, -61, -54, 191, -57, -54, + 193, -53, -55, 193, -49, -56, 194, -45, -56, 195, -41, -57, + 196, -36, -58, 197, -32, -58, 198, -28, -59, 199, -24, -60, + 200, -20, -61, 201, -16, -61, 202, -12, -62, 203, -8, -62, + 204, -4, -63, 205, 0, -64, 206, 4, -64, 207, 8, -65, + 207, 12, -66, 208, 16, -67, 209, 20, -67, 210, 24, -68, + 184, -103, -49, 184, -99, -49, 185, -95, -50, 186, -91, -51, + 187, -87, -51, 188, -83, -52, 189, -79, -53, 190, -74, -53, + 191, -70, -54, 192, -66, -55, 193, -62, -55, 194, -58, -56, + 195, -54, -57, 196, -50, -57, 197, -46, -58, 198, -42, -59, + 199, -37, -59, 200, -33, -60, 200, -29, -61, 201, -25, -61, + 202, -21, -62, 203, -17, -63, 204, -13, -63, 205, -9, -64, + 206, -5, -65, 207, 0, -66, 208, 3, -66, 209, 7, -67, + 210, 11, -67, 211, 15, -68, 212, 19, -69, 213, 23, -70, + 36, -20, 61, 37, -16, 60, 38, -12, 60, 39, -8, 59, + 40, -4, 58, 41, 0, 58, 42, 3, 57, 43, 8, 56, + 44, 12, 56, 45, 16, 55, 46, 20, 54, 47, 24, 54, + 48, 28, 53, 48, 32, 52, 49, 36, 52, 50, 40, 51, + 51, 45, 50, 52, 49, 50, 53, 53, 49, 54, 57, 48, + 55, 61, 48, 56, 65, 47, 57, 69, 46, 58, 73, 46, + 59, 77, 45, 60, 82, 44, 61, 86, 44, 62, 90, 43, + 62, 94, 42, 64, 98, 42, 64, 102, 41, 65, 106, 40, + 39, -22, 59, 40, -18, 59, 40, -14, 58, 41, -9, 57, + 42, -5, 57, 43, -1, 56, 44, 2, 55, 45, 6, 55, + 46, 10, 54, 47, 14, 53, 48, 18, 53, 49, 22, 52, + 50, 27, 51, 51, 31, 51, 52, 35, 50, 53, 39, 49, + 54, 43, 49, 55, 47, 48, 55, 51, 47, 56, 55, 47, + 57, 60, 46, 58, 64, 45, 59, 68, 45, 60, 72, 44, + 61, 76, 43, 62, 80, 43, 63, 84, 42, 64, 88, 41, + 65, 92, 41, 66, 97, 40, 67, 101, 39, 68, 105, 39, + 41, -23, 58, 42, -19, 57, 43, -15, 56, 44, -10, 56, + 45, -6, 55, 46, -2, 54, 47, 1, 54, 48, 5, 53, + 48, 9, 52, 49, 13, 52, 50, 17, 51, 51, 21, 50, + 52, 26, 50, 53, 30, 49, 54, 34, 48, 55, 38, 48, + 56, 42, 47, 57, 46, 46, 58, 50, 46, 59, 54, 45, + 60, 59, 44, 61, 63, 44, 62, 67, 43, 63, 71, 42, + 63, 75, 42, 64, 79, 41, 65, 83, 40, 66, 87, 40, + 67, 91, 39, 68, 96, 38, 69, 100, 38, 70, 104, 37, + 43, -24, 56, 44, -20, 55, 45, -16, 55, 46, -12, 54, + 47, -8, 53, 48, -4, 53, 49, 0, 52, 50, 4, 51, + 51, 8, 51, 52, 12, 50, 53, 16, 49, 54, 20, 49, + 55, 24, 48, 56, 28, 47, 56, 32, 47, 57, 36, 46, + 58, 41, 45, 59, 45, 45, 60, 49, 44, 61, 53, 43, + 62, 57, 43, 63, 61, 42, 64, 65, 41, 65, 69, 41, + 66, 73, 40, 67, 78, 39, 68, 82, 39, 69, 86, 38, + 70, 90, 37, 71, 94, 37, 71, 98, 36, 72, 102, 35, + 46, -26, 54, 47, -22, 54, 47, -18, 53, 49, -13, 52, + 49, -9, 52, 50, -5, 51, 51, -1, 50, 52, 2, 50, + 53, 6, 49, 54, 10, 48, 55, 14, 48, 56, 18, 47, + 57, 23, 46, 58, 27, 46, 59, 31, 45, 60, 35, 44, + 61, 39, 44, 62, 43, 43, 63, 47, 42, 63, 51, 42, + 64, 56, 41, 65, 60, 40, 66, 64, 40, 67, 68, 39, + 68, 72, 38, 69, 76, 38, 70, 80, 37, 71, 84, 36, + 72, 88, 36, 73, 93, 35, 74, 97, 34, 75, 101, 34, + 48, -27, 53, 49, -23, 52, 50, -19, 51, 51, -14, 51, + 52, -10, 50, 53, -6, 49, 54, -2, 49, 55, 1, 48, + 56, 5, 47, 56, 9, 47, 57, 13, 46, 58, 17, 45, + 59, 22, 45, 60, 26, 44, 61, 30, 43, 62, 34, 43, + 63, 38, 42, 64, 42, 41, 65, 46, 41, 66, 50, 40, + 67, 55, 39, 68, 59, 39, 69, 63, 38, 70, 67, 37, + 70, 71, 37, 72, 75, 36, 72, 79, 35, 73, 83, 35, + 74, 87, 34, 75, 92, 33, 76, 96, 33, 77, 100, 32, + 50, -28, 51, 51, -24, 50, 52, -20, 50, 53, -16, 49, + 54, -12, 48, 55, -8, 48, 56, -4, 47, 57, 0, 46, + 58, 4, 46, 59, 8, 45, 60, 12, 44, 61, 16, 44, + 62, 20, 43, 63, 24, 42, 63, 28, 42, 64, 32, 41, + 65, 37, 40, 66, 41, 40, 67, 45, 39, 68, 49, 38, + 69, 53, 38, 70, 57, 37, 71, 61, 36, 72, 65, 36, + 73, 69, 35, 74, 74, 34, 75, 78, 34, 76, 82, 33, + 77, 86, 32, 78, 90, 32, 79, 94, 31, 79, 98, 30, + 53, -30, 49, 54, -26, 49, 55, -22, 48, 56, -17, 47, + 56, -13, 47, 57, -9, 46, 58, -5, 45, 59, -1, 45, + 60, 2, 44, 61, 6, 43, 62, 10, 43, 63, 14, 42, + 64, 19, 41, 65, 23, 41, 66, 27, 40, 67, 31, 39, + 68, 35, 39, 69, 39, 38, 70, 43, 37, 70, 47, 37, + 72, 52, 36, 72, 56, 35, 73, 60, 35, 74, 64, 34, + 75, 68, 33, 76, 72, 33, 77, 76, 32, 78, 80, 31, + 79, 84, 31, 80, 89, 30, 81, 93, 29, 82, 97, 29, + 55, -31, 48, 56, -27, 47, 57, -23, 46, 58, -18, 46, + 59, -14, 45, 60, -10, 44, 61, -6, 44, 62, -2, 43, + 63, 1, 42, 63, 5, 42, 64, 9, 41, 65, 13, 40, + 66, 18, 40, 67, 22, 39, 68, 26, 38, 69, 30, 38, + 70, 34, 37, 71, 38, 36, 72, 42, 36, 73, 46, 35, + 74, 51, 34, 75, 55, 34, 76, 59, 33, 77, 63, 32, + 78, 67, 32, 79, 71, 31, 79, 75, 30, 80, 79, 30, + 81, 83, 29, 82, 88, 28, 83, 92, 28, 84, 96, 27, + 57, -32, 46, 58, -28, 45, 59, -24, 45, 60, -20, 44, + 61, -16, 43, 62, -12, 43, 63, -8, 42, 64, -3, 41, + 65, 0, 41, 66, 4, 40, 67, 8, 39, 68, 12, 39, + 69, 16, 38, 70, 20, 37, 71, 24, 37, 71, 28, 36, + 72, 33, 35, 73, 37, 35, 74, 41, 34, 75, 45, 33, + 76, 49, 33, 77, 53, 32, 78, 57, 31, 79, 61, 31, + 80, 65, 30, 81, 70, 29, 82, 74, 29, 83, 78, 28, + 84, 82, 27, 85, 86, 26, 86, 90, 26, 86, 94, 25, + 60, -34, 44, 61, -30, 44, 62, -26, 43, 63, -21, 42, + 64, -17, 42, 64, -13, 41, 65, -9, 40, 66, -5, 40, + 67, -1, 39, 68, 2, 38, 69, 6, 38, 70, 10, 37, + 71, 15, 36, 72, 19, 36, 73, 23, 35, 74, 27, 34, + 75, 31, 34, 76, 35, 33, 77, 39, 32, 78, 43, 32, + 79, 48, 31, 79, 52, 30, 80, 56, 30, 81, 60, 29, + 82, 64, 28, 83, 68, 28, 84, 72, 27, 85, 76, 26, + 86, 80, 26, 87, 85, 25, 88, 89, 24, 89, 93, 24, + 63, -35, 42, 64, -31, 42, 65, -27, 41, 66, -23, 40, + 66, -19, 39, 67, -15, 39, 68, -11, 38, 69, -6, 37, + 70, -2, 37, 71, 1, 36, 72, 5, 35, 73, 9, 35, + 74, 13, 34, 75, 17, 33, 76, 21, 33, 77, 25, 32, + 78, 30, 31, 79, 34, 31, 80, 38, 30, 80, 42, 29, + 82, 46, 29, 82, 50, 28, 83, 54, 27, 84, 58, 27, + 85, 62, 26, 86, 67, 25, 87, 71, 25, 88, 75, 24, + 89, 79, 23, 90, 83, 23, 91, 87, 22, 92, 91, 21, + 65, -36, 40, 66, -32, 40, 67, -28, 39, 68, -24, 38, + 69, -20, 38, 70, -16, 37, 71, -12, 37, 72, -7, 36, + 73, -3, 35, 73, 0, 34, 74, 4, 34, 75, 8, 33, + 76, 12, 32, 77, 16, 32, 78, 20, 31, 79, 24, 30, + 80, 29, 30, 81, 33, 29, 82, 37, 28, 83, 41, 28, + 84, 45, 27, 85, 49, 26, 86, 53, 26, 87, 57, 25, + 87, 61, 24, 89, 66, 24, 89, 70, 23, 90, 74, 22, + 91, 78, 22, 92, 82, 21, 93, 86, 20, 94, 90, 20, + 67, -38, 39, 68, -34, 38, 69, -30, 38, 70, -25, 37, + 71, -21, 36, 72, -17, 35, 73, -13, 35, 74, -9, 34, + 75, -5, 33, 76, -1, 33, 77, 2, 32, 78, 6, 31, + 79, 11, 31, 80, 15, 30, 80, 19, 29, 81, 23, 29, + 82, 27, 28, 83, 31, 27, 84, 35, 27, 85, 39, 26, + 86, 44, 25, 87, 48, 25, 88, 52, 24, 89, 56, 23, + 90, 60, 23, 91, 64, 22, 92, 68, 21, 93, 72, 21, + 94, 76, 20, 95, 81, 19, 96, 85, 19, 96, 89, 18, + 70, -39, 37, 71, -35, 36, 72, -31, 36, 73, -27, 35, + 73, -23, 34, 74, -19, 34, 75, -15, 33, 76, -10, 32, + 77, -6, 32, 78, -2, 31, 79, 1, 30, 80, 5, 30, + 81, 9, 29, 82, 13, 28, 83, 17, 28, 84, 21, 27, + 85, 26, 26, 86, 30, 26, 87, 34, 25, 88, 38, 24, + 89, 42, 24, 89, 46, 23, 90, 50, 22, 91, 54, 22, + 92, 58, 21, 93, 63, 20, 94, 67, 20, 95, 71, 19, + 96, 75, 18, 97, 79, 18, 98, 83, 17, 99, 87, 16, + 72, -40, 35, 73, -36, 35, 74, -32, 34, 75, -28, 33, + 76, -24, 33, 77, -20, 32, 78, -16, 31, 79, -11, 31, + 80, -7, 30, 81, -3, 29, 81, 0, 29, 82, 4, 28, + 83, 8, 27, 84, 12, 27, 85, 16, 26, 86, 20, 25, + 87, 25, 25, 88, 29, 24, 89, 33, 23, 90, 37, 23, + 91, 41, 22, 92, 45, 21, 93, 49, 21, 94, 53, 20, + 95, 57, 19, 96, 62, 19, 96, 66, 18, 97, 70, 17, + 98, 74, 17, 99, 78, 16, 100, 82, 15, 101, 86, 15, + 74, -42, 34, 75, -38, 33, 76, -34, 32, 77, -29, 32, + 78, -25, 31, 79, -21, 30, 80, -17, 30, 81, -13, 29, + 82, -9, 28, 83, -5, 28, 84, -1, 27, 85, 2, 26, + 86, 7, 26, 87, 11, 25, 88, 15, 24, 88, 19, 24, + 89, 23, 23, 90, 27, 22, 91, 31, 22, 92, 35, 21, + 93, 40, 20, 94, 44, 20, 95, 48, 19, 96, 52, 18, + 97, 56, 18, 98, 60, 17, 99, 64, 16, 100, 68, 16, + 101, 72, 15, 102, 77, 14, 103, 81, 14, 104, 85, 13, + 77, -43, 32, 78, -39, 31, 79, -35, 31, 80, -31, 30, + 81, -27, 29, 81, -23, 29, 82, -19, 28, 83, -14, 27, + 84, -10, 27, 85, -6, 26, 86, -2, 25, 87, 1, 25, + 88, 5, 24, 89, 9, 23, 90, 13, 23, 91, 17, 22, + 92, 22, 21, 93, 26, 21, 94, 30, 20, 95, 34, 19, + 96, 38, 19, 97, 42, 18, 97, 46, 17, 98, 50, 17, + 99, 54, 16, 100, 59, 15, 101, 63, 15, 102, 67, 14, + 103, 71, 13, 104, 75, 13, 105, 79, 12, 106, 83, 11, + 79, -44, 30, 80, -40, 30, 81, -36, 29, 82, -32, 28, + 83, -28, 28, 84, -24, 27, 85, -20, 26, 86, -15, 26, + 87, -11, 25, 88, -7, 24, 88, -3, 24, 89, 0, 23, + 90, 4, 22, 91, 8, 22, 92, 12, 21, 93, 16, 20, + 94, 21, 20, 95, 25, 19, 96, 29, 18, 97, 33, 18, + 98, 37, 17, 99, 41, 16, 100, 45, 16, 101, 49, 15, + 102, 53, 14, 103, 58, 14, 104, 62, 13, 104, 66, 12, + 105, 70, 12, 106, 74, 11, 107, 78, 10, 108, 82, 10, + 81, -46, 29, 82, -42, 28, 83, -38, 27, 84, -33, 27, + 85, -29, 26, 86, -25, 25, 87, -21, 25, 88, -17, 24, + 89, -13, 23, 90, -9, 23, 91, -5, 22, 92, -1, 21, + 93, 3, 21, 94, 7, 20, 95, 11, 19, 95, 15, 19, + 97, 19, 18, 97, 23, 17, 98, 27, 17, 99, 31, 16, + 100, 36, 15, 101, 40, 15, 102, 44, 14, 103, 48, 13, + 104, 52, 13, 105, 56, 12, 106, 60, 11, 107, 64, 11, + 108, 68, 10, 109, 73, 9, 110, 77, 9, 111, 81, 8, + 84, -47, 27, 85, -43, 26, 86, -39, 26, 87, -35, 25, + 88, -31, 24, 88, -27, 24, 89, -23, 23, 90, -18, 22, + 91, -14, 22, 92, -10, 21, 93, -6, 20, 94, -2, 20, + 95, 1, 19, 96, 5, 18, 97, 9, 18, 98, 13, 17, + 99, 18, 16, 100, 22, 16, 101, 26, 15, 102, 30, 14, + 103, 34, 14, 104, 38, 13, 104, 42, 12, 105, 46, 12, + 106, 50, 11, 107, 55, 10, 108, 59, 10, 109, 63, 9, + 110, 67, 8, 111, 71, 8, 112, 75, 7, 113, 79, 6, + 86, -48, 25, 87, -44, 25, 88, -40, 24, 89, -36, 23, + 90, -32, 23, 91, -28, 22, 92, -24, 21, 93, -19, 21, + 94, -15, 20, 95, -11, 19, 96, -7, 19, 96, -3, 18, + 97, 0, 17, 98, 4, 17, 99, 8, 16, 100, 12, 15, + 101, 17, 15, 102, 21, 14, 103, 25, 13, 104, 29, 13, + 105, 33, 12, 106, 37, 11, 107, 41, 11, 108, 45, 10, + 109, 49, 9, 110, 54, 9, 111, 58, 8, 111, 62, 7, + 112, 66, 7, 113, 70, 6, 114, 74, 5, 115, 78, 5, + 89, -50, 24, 89, -46, 23, 90, -42, 22, 91, -37, 22, + 92, -33, 21, 93, -29, 20, 94, -25, 20, 95, -21, 19, + 96, -17, 18, 97, -13, 18, 98, -9, 17, 99, -5, 16, + 100, 0, 16, 101, 3, 15, 102, 7, 14, 103, 11, 14, + 104, 15, 13, 104, 19, 12, 105, 23, 12, 106, 27, 11, + 107, 32, 10, 108, 36, 10, 109, 40, 9, 110, 44, 8, + 111, 48, 8, 112, 52, 7, 113, 56, 6, 114, 60, 6, + 115, 64, 5, 116, 69, 4, 117, 73, 4, 118, 77, 3, + 91, -51, 22, 92, -47, 21, 93, -43, 21, 94, -39, 20, + 95, -35, 19, 96, -31, 19, 96, -27, 18, 97, -22, 17, + 98, -18, 17, 99, -14, 16, 100, -10, 15, 101, -6, 15, + 102, -2, 14, 103, 1, 13, 104, 5, 13, 105, 9, 12, + 106, 14, 11, 107, 18, 11, 108, 22, 10, 109, 26, 9, + 110, 30, 9, 111, 34, 8, 112, 38, 7, 112, 42, 7, + 113, 46, 6, 114, 51, 5, 115, 55, 5, 116, 59, 4, + 117, 63, 3, 118, 67, 3, 119, 71, 2, 120, 75, 1, + 93, -52, 20, 94, -48, 20, 95, -44, 19, 96, -40, 18, + 97, -36, 18, 98, -32, 17, 99, -28, 16, 100, -23, 16, + 101, -19, 15, 102, -15, 14, 103, -11, 14, 103, -7, 13, + 105, -3, 12, 105, 0, 12, 106, 4, 11, 107, 8, 10, + 108, 13, 10, 109, 17, 9, 110, 21, 8, 111, 25, 8, + 112, 29, 7, 113, 33, 6, 114, 37, 6, 115, 41, 5, + 116, 45, 4, 117, 50, 4, 118, 54, 3, 119, 58, 2, + 119, 62, 2, 120, 66, 1, 121, 70, 0, 122, 74, 0, + 96, -54, 19, 96, -50, 18, 97, -46, 17, 98, -41, 17, + 99, -37, 16, 100, -33, 15, 101, -29, 15, 102, -25, 14, + 103, -21, 13, 104, -17, 13, 105, -13, 12, 106, -9, 11, + 107, -4, 11, 108, 0, 10, 109, 3, 9, 110, 7, 9, + 111, 11, 8, 112, 15, 7, 112, 19, 7, 113, 23, 6, + 114, 28, 5, 115, 32, 5, 116, 36, 4, 117, 40, 3, + 118, 44, 3, 119, 48, 2, 120, 52, 1, 121, 56, 1, + 122, 60, 0, 123, 65, 0, 124, 69, 0, 125, 73, -1, + 98, -55, 17, 99, -51, 16, 100, -47, 16, 101, -43, 15, + 102, -39, 14, 103, -35, 14, 103, -31, 13, 105, -26, 12, + 105, -22, 12, 106, -18, 11, 107, -14, 10, 108, -10, 10, + 109, -6, 9, 110, -2, 8, 111, 1, 8, 112, 5, 7, + 113, 10, 6, 114, 14, 6, 115, 18, 5, 116, 22, 4, + 117, 26, 4, 118, 30, 3, 119, 34, 2, 119, 38, 2, + 120, 42, 1, 121, 47, 0, 122, 51, 0, 123, 55, 0, + 124, 59, -1, 125, 63, -1, 126, 67, -2, 127, 71, -3, + 100, -56, 15, 101, -52, 15, 102, -48, 14, 103, -44, 13, + 104, -40, 13, 105, -36, 12, 106, -32, 11, 107, -27, 11, + 108, -23, 10, 109, -19, 9, 110, -15, 9, 111, -11, 8, + 112, -7, 7, 112, -3, 7, 113, 0, 6, 114, 4, 5, + 115, 9, 5, 116, 13, 4, 117, 17, 3, 118, 21, 3, + 119, 25, 2, 120, 29, 1, 121, 33, 1, 122, 37, 0, + 123, 41, 0, 124, 46, 0, 125, 50, -1, 126, 54, -2, + 126, 58, -2, 128, 62, -3, 128, 66, -4, 129, 70, -4, + 103, -58, 14, 104, -54, 13, 104, -50, 12, 105, -45, 12, + 106, -41, 11, 107, -37, 10, 108, -33, 10, 109, -29, 9, + 110, -25, 8, 111, -21, 8, 112, -17, 7, 113, -13, 6, + 114, -8, 6, 115, -4, 5, 116, 0, 4, 117, 3, 4, + 118, 7, 3, 119, 11, 2, 119, 15, 2, 120, 19, 1, + 121, 24, 0, 122, 28, 0, 123, 32, 0, 124, 36, -1, + 125, 40, -1, 126, 44, -2, 127, 48, -3, 128, 52, -3, + 129, 56, -4, 130, 61, -5, 131, 65, -5, 132, 69, -6, + 105, -59, 12, 106, -55, 11, 107, -51, 11, 108, -47, 10, + 109, -43, 9, 110, -39, 9, 111, -35, 8, 112, -30, 7, + 112, -26, 7, 113, -22, 6, 114, -18, 5, 115, -14, 5, + 116, -10, 4, 117, -6, 3, 118, -2, 3, 119, 1, 2, + 120, 6, 1, 121, 10, 1, 122, 14, 0, 123, 18, 0, + 124, 22, 0, 125, 26, -1, 126, 30, -2, 127, 34, -2, + 127, 38, -3, 128, 43, -4, 129, 47, -4, 130, 51, -5, + 131, 55, -6, 132, 59, -6, 133, 63, -7, 134, 67, -8, + 107, -60, 10, 108, -56, 10, 109, -52, 9, 110, -48, 8, + 111, -44, 8, 112, -40, 7, 113, -36, 6, 114, -31, 6, + 115, -27, 5, 116, -23, 4, 117, -19, 4, 118, -15, 3, + 119, -11, 2, 120, -7, 2, 120, -3, 1, 121, 0, 0, + 122, 5, 0, 123, 9, 0, 124, 13, -1, 125, 17, -1, + 126, 21, -2, 127, 25, -3, 128, 29, -3, 129, 33, -4, + 130, 37, -5, 131, 42, -5, 132, 46, -6, 133, 50, -7, + 134, 54, -7, 135, 58, -8, 135, 62, -9, 136, 66, -9, + 110, -62, 9, 111, -58, 8, 111, -54, 7, 113, -49, 7, + 113, -45, 6, 114, -41, 5, 115, -37, 5, 116, -33, 4, + 117, -29, 3, 118, -25, 3, 119, -21, 2, 120, -17, 1, + 121, -12, 1, 122, -8, 0, 123, -4, 0, 124, 0, 0, + 125, 3, -1, 126, 7, -2, 127, 11, -2, 127, 15, -3, + 128, 20, -4, 129, 24, -4, 130, 28, -5, 131, 32, -6, + 132, 36, -6, 133, 40, -7, 134, 44, -8, 135, 48, -8, + 136, 52, -9, 137, 57, -10, 138, 61, -10, 139, 65, -11, + 113, -63, 7, 113, -59, 6, 114, -55, 5, 115, -51, 5, + 116, -47, 4, 117, -43, 3, 118, -39, 3, 119, -34, 2, + 120, -30, 1, 121, -26, 1, 122, -22, 0, 123, -18, 0, + 124, -14, 0, 125, -10, -1, 126, -6, -2, 127, -2, -2, + 128, 2, -3, 129, 6, -4, 129, 10, -4, 130, 14, -5, + 131, 18, -6, 132, 22, -6, 133, 26, -7, 134, 30, -8, + 135, 34, -8, 136, 39, -9, 137, 43, -10, 138, 47, -10, + 139, 51, -11, 140, 55, -12, 141, 59, -13, 142, 63, -13, + 115, -65, 5, 116, -61, 4, 117, -57, 4, 118, -52, 3, + 119, -48, 2, 120, -44, 2, 121, -40, 1, 122, -36, 0, + 122, -32, 0, 123, -28, 0, 124, -24, -1, 125, -20, -1, + 126, -15, -2, 127, -11, -3, 128, -7, -3, 129, -3, -4, + 130, 0, -5, 131, 4, -5, 132, 8, -6, 133, 12, -7, + 134, 17, -8, 135, 21, -8, 136, 25, -9, 136, 29, -9, + 137, 33, -10, 138, 37, -11, 139, 41, -12, 140, 45, -12, + 141, 49, -13, 142, 54, -14, 143, 58, -14, 144, 62, -15, + 117, -66, 3, 118, -62, 3, 119, -58, 2, 120, -53, 1, + 121, -49, 1, 122, -45, 0, 123, -41, 0, 124, -37, 0, + 125, -33, -1, 126, -29, -2, 127, -25, -2, 128, -21, -3, + 129, -16, -4, 129, -12, -4, 130, -8, -5, 131, -4, -6, + 132, 0, -7, 133, 3, -7, 134, 7, -8, 135, 11, -8, + 136, 16, -9, 137, 20, -10, 138, 24, -10, 139, 28, -11, + 140, 32, -12, 141, 36, -13, 142, 40, -13, 143, 44, -14, + 144, 48, -14, 145, 53, -15, 145, 57, -16, 146, 61, -17, + 120, -67, 2, 121, -63, 1, 121, -59, 0, 122, -55, 0, + 123, -51, 0, 124, -47, -1, 125, -43, -1, 126, -38, -2, + 127, -34, -3, 128, -30, -3, 129, -26, -4, 130, -22, -5, + 131, -18, -6, 132, -14, -6, 133, -10, -7, 134, -6, -7, + 135, -1, -8, 136, 2, -9, 137, 6, -9, 137, 10, -10, + 138, 14, -11, 139, 18, -12, 140, 22, -12, 141, 26, -13, + 142, 30, -13, 143, 35, -14, 144, 39, -15, 145, 43, -16, + 146, 47, -16, 147, 51, -17, 148, 55, -18, 149, 59, -18, + 122, -69, 0, 123, -65, 0, 124, -61, 0, 125, -56, -1, + 126, -52, -2, 127, -48, -2, 128, -44, -3, 129, -40, -4, + 130, -36, -4, 130, -32, -5, 131, -28, -6, 132, -24, -6, + 133, -19, -7, 134, -15, -8, 135, -11, -8, 136, -7, -9, + 137, -3, -10, 138, 0, -11, 139, 4, -11, 140, 8, -12, + 141, 13, -13, 142, 17, -13, 143, 21, -14, 144, 25, -14, + 144, 29, -15, 145, 33, -16, 146, 37, -17, 147, 41, -17, + 148, 45, -18, 149, 50, -19, 150, 54, -19, 151, 58, -20, + 124, -70, -1, 125, -66, -1, 126, -62, -2, 127, -57, -3, + 128, -53, -3, 129, -49, -4, 130, -45, -5, 131, -41, -6, + 132, -37, -6, 133, -33, -7, 134, -29, -7, 135, -25, -8, + 136, -20, -9, 137, -16, -10, 137, -12, -10, 138, -8, -11, + 139, -4, -12, 140, 0, -12, 141, 3, -13, 142, 7, -13, + 143, 12, -14, 144, 16, -15, 145, 20, -16, 146, 24, -16, + 147, 28, -17, 148, 32, -18, 149, 36, -18, 150, 40, -19, + 151, 44, -20, 152, 49, -20, 152, 53, -21, 153, 57, -22, + 127, -71, -2, 128, -67, -3, 128, -63, -4, 130, -59, -5, + 130, -55, -5, 131, -51, -6, 132, -47, -6, 133, -42, -7, + 134, -38, -8, 135, -34, -8, 136, -30, -9, 137, -26, -10, + 138, -22, -11, 139, -18, -11, 140, -14, -12, 141, -10, -12, + 142, -5, -13, 143, -1, -14, 144, 2, -15, 144, 6, -15, + 145, 10, -16, 146, 14, -17, 147, 18, -17, 148, 22, -18, + 149, 26, -18, 150, 31, -19, 151, 35, -20, 152, 39, -21, + 153, 43, -21, 154, 47, -22, 155, 51, -23, 156, 55, -23, + 129, -73, -4, 130, -69, -5, 131, -65, -5, 132, -60, -6, + 133, -56, -7, 134, -52, -7, 135, -48, -8, 136, -44, -9, + 137, -40, -10, 137, -36, -10, 138, -32, -11, 139, -28, -11, + 140, -23, -12, 141, -19, -13, 142, -15, -14, 143, -11, -14, + 144, -7, -15, 145, -3, -16, 146, 0, -16, 147, 4, -17, + 148, 9, -18, 149, 13, -18, 150, 17, -19, 151, 21, -20, + 151, 25, -20, 153, 29, -21, 153, 33, -22, 154, 37, -22, + 155, 41, -23, 156, 46, -24, 157, 50, -24, 158, 54, -25, + 131, -74, -6, 132, -70, -6, 133, -66, -7, 134, -61, -8, + 135, -57, -9, 136, -53, -9, 137, -49, -10, 138, -45, -11, + 139, -41, -11, 140, -37, -12, 141, -33, -12, 142, -29, -13, + 143, -24, -14, 144, -20, -15, 144, -16, -15, 145, -12, -16, + 146, -8, -17, 147, -4, -17, 148, 0, -18, 149, 3, -19, + 150, 8, -19, 151, 12, -20, 152, 16, -21, 153, 20, -21, + 154, 24, -22, 155, 28, -23, 156, 32, -23, 157, 36, -24, + 158, 40, -25, 159, 45, -25, 160, 49, -26, 160, 53, -27, + 134, -75, -8, 135, -71, -8, 136, -67, -9, 137, -63, -10, + 137, -59, -10, 138, -55, -11, 139, -51, -11, 140, -46, -12, + 141, -42, -13, 142, -38, -14, 143, -34, -14, 144, -30, -15, + 145, -26, -16, 146, -22, -16, 147, -18, -17, 148, -14, -18, + 149, -9, -18, 150, -5, -19, 151, -1, -20, 152, 2, -20, + 153, 6, -21, 153, 10, -22, 154, 14, -22, 155, 18, -23, + 156, 22, -24, 157, 27, -24, 158, 31, -25, 159, 35, -26, + 160, 39, -26, 161, 43, -27, 162, 47, -28, 163, 51, -28, + 136, -77, -9, 137, -73, -10, 138, -69, -10, 139, -64, -11, + 140, -60, -12, 141, -56, -13, 142, -52, -13, 143, -48, -14, + 144, -44, -15, 145, -40, -15, 145, -36, -16, 146, -32, -16, + 147, -27, -17, 148, -23, -18, 149, -19, -19, 150, -15, -19, + 151, -11, -20, 152, -7, -21, 153, -3, -21, 154, 0, -22, + 155, 5, -23, 156, 9, -23, 157, 13, -24, 158, 17, -25, + 159, 21, -25, 160, 25, -26, 160, 29, -27, 161, 33, -27, + 162, 37, -28, 163, 42, -29, 164, 46, -29, 165, 50, -30, + 138, -78, -11, 139, -74, -12, 140, -70, -12, 141, -65, -13, + 142, -61, -14, 143, -57, -14, 144, -53, -15, 145, -49, -16, + 146, -45, -16, 147, -41, -17, 148, -37, -18, 149, -33, -18, + 150, -28, -19, 151, -24, -20, 152, -20, -20, 152, -16, -21, + 153, -12, -22, 154, -8, -22, 155, -4, -23, 156, 0, -24, + 157, 4, -24, 158, 8, -25, 159, 12, -26, 160, 16, -26, + 161, 20, -27, 162, 24, -28, 163, 28, -28, 164, 32, -29, + 165, 36, -30, 166, 41, -30, 167, 45, -31, 167, 49, -32, + 141, -79, -13, 142, -75, -13, 143, -71, -14, 144, -67, -15, + 145, -63, -15, 145, -59, -16, 146, -55, -17, 147, -50, -17, + 148, -46, -18, 149, -42, -19, 150, -38, -19, 151, -34, -20, + 152, -30, -21, 153, -26, -21, 154, -22, -22, 155, -18, -23, + 156, -13, -23, 157, -9, -24, 158, -5, -25, 159, -1, -25, + 160, 2, -26, 160, 6, -27, 161, 10, -27, 162, 14, -28, + 163, 18, -29, 164, 23, -29, 165, 27, -30, 166, 31, -31, + 167, 35, -31, 168, 39, -32, 169, 43, -33, 170, 47, -33, + 143, -81, -14, 144, -77, -15, 145, -73, -16, 146, -68, -16, + 147, -64, -17, 148, -60, -18, 149, -56, -18, 150, -52, -19, + 151, -48, -20, 152, -44, -20, 152, -40, -21, 153, -36, -22, + 154, -31, -22, 155, -27, -23, 156, -23, -24, 157, -19, -24, + 158, -15, -25, 159, -11, -26, 160, -7, -26, 161, -3, -27, + 162, 1, -28, 163, 5, -28, 164, 9, -29, 165, 13, -30, + 166, 17, -30, 167, 21, -31, 168, 25, -32, 168, 29, -32, + 169, 33, -33, 170, 38, -34, 171, 42, -34, 172, 46, -35, + 145, -82, -16, 146, -78, -17, 147, -74, -17, 148, -69, -18, + 149, -65, -19, 150, -61, -19, 151, -57, -20, 152, -53, -21, + 153, -49, -21, 154, -45, -22, 155, -41, -23, 156, -37, -23, + 157, -32, -24, 158, -28, -25, 159, -24, -25, 159, -20, -26, + 161, -16, -27, 161, -12, -27, 162, -8, -28, 163, -4, -29, + 164, 0, -29, 165, 4, -30, 166, 8, -31, 167, 12, -31, + 168, 16, -32, 169, 20, -33, 170, 24, -33, 171, 28, -34, + 172, 32, -35, 173, 37, -35, 174, 41, -36, 175, 45, -37, + 148, -83, -18, 149, -79, -18, 150, -75, -19, 151, -71, -20, + 152, -67, -20, 152, -63, -21, 153, -59, -22, 154, -54, -22, + 155, -50, -23, 156, -46, -24, 157, -42, -24, 158, -38, -25, + 159, -34, -26, 160, -30, -26, 161, -26, -27, 162, -22, -28, + 163, -17, -28, 164, -13, -29, 165, -9, -30, 166, -5, -30, + 167, -1, -31, 168, 2, -32, 168, 6, -32, 169, 10, -33, + 170, 14, -34, 171, 19, -34, 172, 23, -35, 173, 27, -36, + 174, 31, -36, 175, 35, -37, 176, 39, -38, 177, 43, -38, + 150, -85, -19, 151, -81, -20, 152, -77, -21, 153, -72, -21, + 154, -68, -22, 155, -64, -23, 156, -60, -23, 157, -56, -24, + 158, -52, -25, 159, -48, -25, 160, -44, -26, 160, -40, -27, + 161, -35, -27, 162, -31, -28, 163, -27, -29, 164, -23, -29, + 165, -19, -30, 166, -15, -31, 167, -11, -31, 168, -7, -32, + 169, -2, -33, 170, 1, -33, 171, 5, -34, 172, 9, -35, + 173, 13, -35, 174, 17, -36, 175, 21, -37, 175, 25, -37, + 176, 29, -38, 177, 34, -39, 178, 38, -39, 179, 42, -40, + 153, -86, -21, 153, -82, -22, 154, -78, -22, 155, -73, -23, + 156, -69, -24, 157, -65, -24, 158, -61, -25, 159, -57, -26, + 160, -53, -26, 161, -49, -27, 162, -45, -28, 163, -41, -28, + 164, -36, -29, 165, -32, -30, 166, -28, -30, 167, -24, -31, + 168, -20, -32, 168, -16, -32, 169, -12, -33, 170, -8, -34, + 171, -3, -34, 172, 0, -35, 173, 4, -36, 174, 8, -36, + 175, 12, -37, 176, 16, -38, 177, 20, -38, 178, 24, -39, + 179, 28, -40, 180, 33, -40, 181, 37, -41, 182, 41, -42, + 155, -87, -23, 156, -83, -23, 157, -79, -24, 158, -75, -25, + 159, -71, -25, 160, -67, -26, 160, -63, -27, 161, -58, -27, + 162, -54, -28, 163, -50, -29, 164, -46, -29, 165, -42, -30, + 166, -38, -31, 167, -34, -31, 168, -30, -32, 169, -26, -33, + 170, -21, -33, 171, -17, -34, 172, -13, -35, 173, -9, -35, + 174, -5, -36, 175, -1, -37, 175, 2, -37, 176, 6, -38, + 177, 10, -39, 178, 15, -39, 179, 19, -40, 180, 23, -41, + 181, 27, -41, 182, 31, -42, 183, 35, -43, 184, 39, -43, + 157, -88, -24, 158, -84, -25, 159, -80, -26, 160, -76, -26, + 161, -72, -27, 162, -68, -28, 163, -64, -28, 164, -59, -29, + 165, -55, -30, 166, -51, -30, 167, -47, -31, 167, -43, -32, + 168, -39, -32, 169, -35, -33, 170, -31, -34, 171, -27, -34, + 172, -22, -35, 173, -18, -36, 174, -14, -36, 175, -10, -37, + 176, -6, -38, 177, -2, -38, 178, 1, -39, 179, 5, -40, + 180, 9, -40, 181, 14, -41, 182, 18, -42, 183, 22, -42, + 183, 26, -43, 184, 30, -44, 185, 34, -44, 186, 38, -45, + 160, -90, -26, 160, -86, -27, 161, -82, -27, 162, -77, -28, + 163, -73, -29, 164, -69, -29, 165, -65, -30, 166, -61, -31, + 167, -57, -31, 168, -53, -32, 169, -49, -33, 170, -45, -33, + 171, -40, -34, 172, -36, -35, 173, -32, -35, 174, -28, -36, + 175, -24, -37, 176, -20, -37, 176, -16, -38, 177, -12, -39, + 178, -7, -39, 179, -3, -40, 180, 0, -41, 181, 4, -41, + 182, 8, -42, 183, 12, -43, 184, 16, -43, 185, 20, -44, + 186, 24, -45, 187, 29, -45, 188, 33, -46, 189, 37, -47, + 162, -91, -28, 163, -87, -29, 164, -83, -29, 165, -79, -30, + 166, -75, -31, 167, -71, -31, 168, -67, -32, 169, -62, -33, + 170, -58, -33, 171, -54, -34, 172, -50, -35, 173, -46, -35, + 174, -42, -36, 175, -38, -37, 176, -34, -37, 177, -30, -38, + 178, -25, -39, 178, -21, -39, 179, -17, -40, 180, -13, -41, + 181, -9, -41, 182, -5, -42, 183, -1, -43, 184, 2, -43, + 185, 6, -44, 186, 11, -45, 187, 15, -45, 188, 19, -46, + 189, 23, -47, 190, 27, -47, 191, 31, -48, 192, 35, -49, + 165, -93, -30, 166, -89, -30, 167, -85, -31, 168, -80, -32, + 169, -76, -32, 170, -72, -33, 170, -68, -34, 171, -64, -34, + 172, -60, -35, 173, -56, -36, 174, -52, -36, 175, -48, -37, + 176, -43, -38, 177, -39, -38, 178, -35, -39, 179, -31, -40, + 180, -27, -40, 181, -23, -41, 182, -19, -42, 183, -15, -42, + 184, -10, -43, 185, -6, -44, 185, -2, -44, 186, 1, -45, + 187, 5, -46, 188, 9, -46, 189, 13, -47, 190, 17, -48, + 191, 21, -48, 192, 26, -49, 193, 30, -50, 194, 34, -50, + 167, -94, -31, 168, -90, -32, 169, -86, -33, 170, -82, -33, + 171, -78, -34, 172, -74, -35, 173, -70, -35, 174, -65, -36, + 175, -61, -37, 176, -57, -37, 177, -53, -38, 177, -49, -39, + 178, -45, -39, 179, -41, -40, 180, -37, -41, 181, -33, -41, + 182, -28, -42, 183, -24, -43, 184, -20, -43, 185, -16, -44, + 186, -12, -45, 187, -8, -45, 188, -4, -46, 189, 0, -47, + 190, 3, -47, 191, 8, -48, 192, 12, -49, 192, 16, -49, + 193, 20, -50, 194, 24, -51, 195, 28, -51, 196, 32, -52, + 170, -95, -33, 170, -91, -34, 171, -87, -34, 172, -83, -35, + 173, -79, -36, 174, -75, -36, 175, -71, -37, 176, -66, -38, + 177, -62, -38, 178, -58, -39, 179, -54, -40, 180, -50, -40, + 181, -46, -41, 182, -42, -42, 183, -38, -42, 184, -34, -43, + 185, -29, -44, 185, -25, -44, 186, -21, -45, 187, -17, -46, + 188, -13, -46, 189, -9, -47, 190, -5, -48, 191, -1, -48, + 192, 2, -49, 193, 7, -50, 194, 11, -50, 195, 15, -51, + 196, 19, -52, 197, 23, -52, 198, 27, -53, 199, 31, -54, + 172, -97, -35, 173, -93, -35, 174, -89, -36, 175, -84, -37, + 176, -80, -37, 177, -76, -38, 177, -72, -39, 178, -68, -39, + 179, -64, -40, 180, -60, -41, 181, -56, -41, 182, -52, -42, + 183, -47, -43, 184, -43, -43, 185, -39, -44, 186, -35, -45, + 187, -31, -45, 188, -27, -46, 189, -23, -47, 190, -19, -47, + 191, -14, -48, 192, -10, -49, 193, -6, -49, 193, -2, -50, + 194, 1, -51, 195, 5, -51, 196, 9, -52, 197, 13, -53, + 198, 17, -53, 199, 22, -54, 200, 26, -55, 201, 30, -55, + 174, -98, -36, 175, -94, -37, 176, -90, -38, 177, -86, -38, + 178, -82, -39, 179, -78, -40, 180, -74, -40, 181, -69, -41, + 182, -65, -42, 183, -61, -42, 184, -57, -43, 184, -53, -44, + 186, -49, -44, 186, -45, -45, 187, -41, -46, 188, -37, -46, + 189, -32, -47, 190, -28, -48, 191, -24, -48, 192, -20, -49, + 193, -16, -50, 194, -12, -50, 195, -8, -51, 196, -4, -52, + 197, 0, -52, 198, 4, -53, 199, 8, -54, 200, 12, -54, + 200, 16, -55, 201, 20, -56, 202, 24, -56, 203, 28, -57, + 177, -99, -38, 177, -95, -39, 178, -91, -39, 179, -87, -40, + 180, -83, -41, 181, -79, -41, 182, -75, -42, 183, -70, -43, + 184, -66, -43, 185, -62, -44, 186, -58, -45, 187, -54, -45, + 188, -50, -46, 189, -46, -47, 190, -42, -47, 191, -38, -48, + 192, -33, -49, 193, -29, -49, 193, -25, -50, 194, -21, -51, + 195, -17, -51, 196, -13, -52, 197, -9, -53, 198, -5, -53, + 199, -1, -54, 200, 3, -55, 201, 7, -55, 202, 11, -56, + 203, 15, -57, 204, 19, -58, 205, 23, -58, 206, 27, -59, + 179, -101, -40, 180, -97, -40, 181, -93, -41, 182, -88, -42, + 183, -84, -42, 184, -80, -43, 185, -76, -44, 186, -72, -44, + 186, -68, -45, 187, -64, -46, 188, -60, -46, 189, -56, -47, + 190, -51, -48, 191, -47, -48, 192, -43, -49, 193, -39, -50, + 194, -35, -50, 195, -31, -51, 196, -27, -52, 197, -23, -52, + 198, -18, -53, 199, -14, -54, 200, -10, -54, 200, -6, -55, + 201, -2, -56, 202, 1, -56, 203, 5, -57, 204, 9, -58, + 205, 13, -58, 206, 18, -59, 207, 22, -60, 208, 26, -60, + 181, -102, -41, 182, -98, -42, 183, -94, -43, 184, -90, -43, + 185, -86, -44, 186, -82, -45, 187, -78, -45, 188, -73, -46, + 189, -69, -47, 190, -65, -47, 191, -61, -48, 192, -57, -49, + 193, -53, -49, 193, -49, -50, 194, -45, -51, 195, -41, -51, + 196, -36, -52, 197, -32, -53, 198, -28, -53, 199, -24, -54, + 200, -20, -55, 201, -16, -55, 202, -12, -56, 203, -8, -57, + 204, -4, -57, 205, 0, -58, 206, 4, -59, 207, 8, -59, + 207, 12, -60, 209, 16, -61, 209, 20, -62, 210, 24, -62, + 184, -103, -43, 185, -99, -44, 185, -95, -44, 186, -91, -45, + 187, -87, -46, 188, -83, -46, 189, -79, -47, 190, -74, -48, + 191, -70, -48, 192, -66, -49, 193, -62, -50, 194, -58, -50, + 195, -54, -51, 196, -50, -52, 197, -46, -52, 198, -42, -53, + 199, -37, -54, 200, -33, -54, 200, -29, -55, 201, -25, -56, + 202, -21, -57, 203, -17, -57, 204, -13, -58, 205, -9, -58, + 206, -5, -59, 207, 0, -60, 208, 3, -60, 209, 7, -61, + 210, 11, -62, 211, 15, -63, 212, 19, -63, 213, 23, -64, + 186, -105, -45, 187, -101, -45, 188, -97, -46, 189, -92, -47, + 190, -88, -47, 191, -84, -48, 192, -80, -49, 193, -76, -49, + 193, -72, -50, 194, -68, -51, 195, -64, -51, 196, -60, -52, + 197, -55, -53, 198, -51, -53, 199, -47, -54, 200, -43, -55, + 201, -39, -55, 202, -35, -56, 203, -31, -57, 204, -27, -57, + 205, -22, -58, 206, -18, -59, 207, -14, -59, 208, -10, -60, + 208, -6, -61, 209, -2, -62, 210, 1, -62, 211, 5, -63, + 212, 9, -63, 213, 14, -64, 214, 18, -65, 215, 22, -66, + 39, -22, 66, 40, -18, 65, 41, -14, 64, 42, -9, 63, + 43, -5, 63, 44, -1, 62, 45, 2, 62, 46, 6, 61, + 46, 10, 60, 47, 14, 59, 48, 18, 59, 49, 22, 58, + 50, 27, 57, 51, 31, 57, 52, 35, 56, 53, 39, 55, + 54, 43, 55, 55, 47, 54, 56, 51, 53, 57, 55, 53, + 58, 60, 52, 59, 64, 51, 60, 68, 51, 61, 72, 50, + 61, 76, 49, 62, 80, 49, 63, 84, 48, 64, 88, 47, + 65, 92, 47, 66, 97, 46, 67, 101, 45, 68, 105, 45, + 41, -23, 64, 42, -19, 63, 43, -15, 63, 44, -11, 62, + 45, -7, 61, 46, -3, 60, 47, 0, 60, 48, 5, 59, + 49, 9, 58, 50, 13, 58, 51, 17, 57, 52, 21, 57, + 53, 25, 56, 54, 29, 55, 54, 33, 54, 55, 37, 54, + 56, 42, 53, 57, 46, 52, 58, 50, 52, 59, 54, 51, + 60, 58, 50, 61, 62, 50, 62, 66, 49, 63, 70, 48, + 64, 74, 48, 65, 79, 47, 66, 83, 46, 67, 87, 46, + 68, 91, 45, 69, 95, 44, 69, 99, 44, 70, 103, 43, + 44, -24, 62, 45, -20, 62, 45, -16, 61, 47, -12, 60, + 47, -8, 59, 48, -4, 59, 49, 0, 58, 50, 4, 57, + 51, 8, 57, 52, 12, 56, 53, 16, 55, 54, 20, 55, + 55, 24, 54, 56, 28, 53, 57, 32, 53, 58, 36, 52, + 59, 41, 51, 60, 45, 51, 61, 49, 50, 61, 53, 49, + 62, 57, 49, 63, 61, 48, 64, 65, 47, 65, 69, 47, + 66, 73, 46, 67, 78, 45, 68, 82, 45, 69, 86, 44, + 70, 90, 43, 71, 94, 43, 72, 98, 42, 73, 102, 41, + 46, -26, 60, 47, -22, 60, 48, -18, 59, 49, -13, 58, + 50, -9, 58, 51, -5, 57, 52, -1, 56, 53, 2, 56, + 54, 6, 55, 54, 10, 54, 55, 14, 54, 56, 18, 53, + 57, 23, 52, 58, 27, 52, 59, 31, 51, 60, 35, 50, + 61, 39, 50, 62, 43, 49, 63, 47, 48, 64, 51, 48, + 65, 56, 47, 66, 60, 46, 67, 64, 46, 68, 68, 45, + 68, 72, 44, 69, 76, 44, 70, 80, 43, 71, 84, 42, + 72, 88, 42, 73, 93, 41, 74, 97, 40, 75, 101, 40, + 48, -27, 59, 49, -23, 58, 50, -19, 58, 51, -15, 57, + 52, -11, 56, 53, -7, 55, 54, -3, 55, 55, 1, 54, + 56, 5, 53, 57, 9, 53, 58, 13, 52, 59, 17, 51, + 60, 21, 51, 61, 25, 50, 61, 29, 49, 62, 33, 49, + 63, 38, 48, 64, 42, 47, 65, 46, 47, 66, 50, 46, + 67, 54, 45, 68, 58, 45, 69, 62, 44, 70, 66, 43, + 71, 70, 43, 72, 75, 42, 73, 79, 41, 74, 83, 41, + 75, 87, 40, 76, 91, 39, 77, 95, 39, 77, 99, 38, + 51, -28, 57, 52, -24, 56, 53, -20, 56, 54, -16, 55, + 54, -12, 54, 55, -8, 54, 56, -4, 53, 57, 0, 52, + 58, 4, 52, 59, 8, 51, 60, 12, 50, 61, 16, 50, + 62, 20, 49, 63, 24, 48, 64, 28, 48, 65, 32, 47, + 66, 37, 46, 67, 41, 46, 68, 45, 45, 68, 49, 44, + 70, 53, 44, 70, 57, 43, 71, 61, 42, 72, 65, 42, + 73, 69, 41, 74, 74, 40, 75, 78, 40, 76, 82, 39, + 77, 86, 38, 78, 90, 38, 79, 94, 37, 80, 98, 36, + 53, -30, 55, 54, -26, 55, 55, -22, 54, 56, -17, 53, + 57, -13, 53, 58, -9, 52, 59, -5, 51, 60, -1, 51, + 61, 2, 50, 61, 6, 49, 62, 10, 49, 63, 14, 48, + 64, 19, 47, 65, 23, 47, 66, 27, 46, 67, 31, 45, + 68, 35, 45, 69, 39, 44, 70, 43, 43, 71, 47, 43, + 72, 52, 42, 73, 56, 41, 74, 60, 41, 75, 64, 40, + 76, 68, 39, 77, 72, 39, 77, 76, 38, 78, 80, 37, + 79, 84, 37, 80, 89, 36, 81, 93, 35, 82, 97, 35, + 55, -31, 54, 56, -27, 53, 57, -23, 52, 58, -19, 52, + 59, -15, 51, 60, -11, 50, 61, -7, 50, 62, -2, 49, + 63, 1, 48, 64, 5, 48, 65, 9, 47, 66, 13, 46, + 67, 17, 46, 68, 21, 45, 69, 25, 44, 69, 29, 44, + 70, 34, 43, 71, 38, 42, 72, 42, 42, 73, 46, 41, + 74, 50, 40, 75, 54, 40, 76, 58, 39, 77, 62, 38, + 78, 66, 38, 79, 71, 37, 80, 75, 36, 81, 79, 36, + 82, 83, 35, 83, 87, 34, 84, 91, 34, 84, 95, 33, + 58, -32, 52, 59, -28, 51, 60, -24, 51, 61, -20, 50, + 62, -16, 49, 62, -12, 49, 63, -8, 48, 64, -3, 47, + 65, 0, 47, 66, 4, 46, 67, 8, 45, 68, 12, 45, + 69, 16, 44, 70, 20, 43, 71, 24, 43, 72, 28, 42, + 73, 33, 41, 74, 37, 41, 75, 41, 40, 76, 45, 39, + 77, 49, 39, 77, 53, 38, 78, 57, 37, 79, 61, 37, + 80, 65, 36, 81, 70, 35, 82, 74, 35, 83, 78, 34, + 84, 82, 33, 85, 86, 33, 86, 90, 32, 87, 94, 31, + 60, -34, 50, 61, -30, 50, 62, -26, 49, 63, -21, 48, + 64, -17, 48, 65, -13, 47, 66, -9, 46, 67, -5, 46, + 68, -1, 45, 69, 2, 44, 69, 6, 44, 70, 10, 43, + 71, 15, 42, 72, 19, 42, 73, 23, 41, 74, 27, 40, + 75, 31, 40, 76, 35, 39, 77, 39, 38, 78, 43, 38, + 79, 48, 37, 80, 52, 36, 81, 56, 36, 82, 60, 35, + 83, 64, 34, 84, 68, 34, 84, 72, 33, 85, 76, 32, + 86, 80, 32, 87, 85, 31, 88, 89, 30, 89, 93, 30, + 62, -35, 49, 63, -31, 48, 64, -27, 47, 65, -23, 47, + 66, -19, 46, 67, -15, 45, 68, -11, 45, 69, -6, 44, + 70, -2, 43, 71, 1, 43, 72, 5, 42, 73, 9, 41, + 74, 13, 41, 75, 17, 40, 76, 21, 39, 76, 25, 39, + 77, 30, 38, 78, 34, 37, 79, 38, 37, 80, 42, 36, + 81, 46, 35, 82, 50, 35, 83, 54, 34, 84, 58, 33, + 85, 62, 33, 86, 67, 32, 87, 71, 31, 88, 75, 31, + 89, 79, 30, 90, 83, 29, 91, 87, 29, 92, 91, 28, + 65, -37, 47, 66, -33, 46, 67, -29, 45, 68, -24, 45, + 69, -20, 44, 70, -16, 43, 71, -12, 43, 72, -8, 42, + 73, -4, 41, 74, 0, 41, 75, 3, 40, 76, 7, 39, + 77, 12, 39, 78, 16, 38, 78, 20, 37, 79, 24, 37, + 80, 28, 36, 81, 32, 35, 82, 36, 35, 83, 40, 34, + 84, 45, 33, 85, 49, 33, 86, 53, 32, 87, 57, 31, + 88, 61, 31, 89, 65, 30, 90, 69, 29, 91, 73, 29, + 92, 77, 28, 93, 82, 27, 94, 86, 27, 94, 90, 26, + 68, -38, 45, 69, -34, 44, 70, -30, 44, 71, -26, 43, + 71, -22, 42, 72, -18, 42, 73, -14, 41, 74, -9, 40, + 75, -5, 40, 76, -1, 39, 77, 2, 38, 78, 6, 38, + 79, 10, 37, 80, 14, 36, 81, 18, 36, 82, 22, 35, + 83, 27, 34, 84, 31, 34, 85, 35, 33, 86, 39, 32, + 87, 43, 32, 87, 47, 31, 88, 51, 30, 89, 55, 30, + 90, 59, 29, 91, 64, 28, 92, 68, 28, 93, 72, 27, + 94, 76, 26, 95, 80, 26, 96, 84, 25, 97, 88, 24, + 70, -39, 43, 71, -35, 43, 72, -31, 42, 73, -27, 41, + 74, -23, 41, 75, -19, 40, 76, -15, 39, 77, -10, 39, + 78, -6, 38, 79, -2, 37, 79, 1, 37, 80, 5, 36, + 81, 9, 35, 82, 13, 35, 83, 17, 34, 84, 21, 33, + 85, 26, 33, 86, 30, 32, 87, 34, 31, 88, 38, 31, + 89, 42, 30, 90, 46, 29, 91, 50, 29, 92, 54, 28, + 93, 58, 27, 94, 63, 27, 94, 67, 26, 95, 71, 25, + 96, 75, 25, 97, 79, 24, 98, 83, 23, 99, 87, 23, + 72, -41, 42, 73, -37, 41, 74, -33, 40, 75, -28, 40, + 76, -24, 39, 77, -20, 38, 78, -16, 38, 79, -12, 37, + 80, -8, 36, 81, -4, 36, 82, 0, 35, 83, 3, 34, + 84, 8, 34, 85, 12, 33, 86, 16, 32, 86, 20, 32, + 87, 24, 31, 88, 28, 30, 89, 32, 30, 90, 36, 29, + 91, 41, 28, 92, 45, 28, 93, 49, 27, 94, 53, 26, + 95, 57, 26, 96, 61, 25, 97, 65, 24, 98, 69, 24, + 99, 73, 23, 100, 78, 22, 101, 82, 22, 101, 86, 21, + 75, -42, 40, 76, -38, 39, 77, -34, 39, 78, -29, 38, + 79, -25, 37, 79, -21, 37, 80, -17, 36, 81, -13, 35, + 82, -9, 35, 83, -5, 34, 84, -1, 33, 85, 2, 33, + 86, 7, 32, 87, 11, 31, 88, 15, 31, 89, 19, 30, + 90, 23, 29, 91, 27, 29, 92, 31, 28, 93, 35, 27, + 94, 40, 27, 94, 44, 26, 95, 48, 25, 96, 52, 25, + 97, 56, 24, 98, 60, 23, 99, 64, 23, 100, 68, 22, + 101, 72, 21, 102, 77, 21, 103, 81, 20, 104, 85, 19, + 77, -43, 38, 78, -39, 38, 79, -35, 37, 80, -31, 36, + 81, -27, 36, 82, -23, 35, 83, -19, 34, 84, -14, 34, + 85, -10, 33, 86, -6, 32, 86, -2, 32, 87, 1, 31, + 88, 5, 30, 89, 9, 30, 90, 13, 29, 91, 17, 28, + 92, 22, 28, 93, 26, 27, 94, 30, 26, 95, 34, 26, + 96, 38, 25, 97, 42, 24, 98, 46, 24, 99, 50, 23, + 100, 54, 22, 101, 59, 22, 102, 63, 21, 102, 67, 20, + 103, 71, 20, 104, 75, 19, 105, 79, 18, 106, 83, 18, + 79, -45, 37, 80, -41, 36, 81, -37, 35, 82, -32, 35, + 83, -28, 34, 84, -24, 33, 85, -20, 33, 86, -16, 32, + 87, -12, 31, 88, -8, 31, 89, -4, 30, 90, 0, 29, + 91, 4, 29, 92, 8, 28, 93, 12, 27, 93, 16, 27, + 95, 20, 26, 95, 24, 25, 96, 28, 25, 97, 32, 24, + 98, 37, 23, 99, 41, 23, 100, 45, 22, 101, 49, 21, + 102, 53, 21, 103, 57, 20, 104, 61, 19, 105, 65, 19, + 106, 69, 18, 107, 74, 17, 108, 78, 17, 109, 82, 16, + 82, -46, 35, 83, -42, 34, 84, -38, 34, 85, -33, 33, + 86, -29, 32, 86, -25, 32, 87, -21, 31, 88, -17, 30, + 89, -13, 30, 90, -9, 29, 91, -5, 28, 92, -1, 28, + 93, 3, 27, 94, 7, 26, 95, 11, 26, 96, 15, 25, + 97, 19, 24, 98, 23, 24, 99, 27, 23, 100, 31, 22, + 101, 36, 22, 102, 40, 21, 102, 44, 20, 103, 48, 20, + 104, 52, 19, 105, 56, 18, 106, 60, 18, 107, 64, 17, + 108, 68, 16, 109, 73, 16, 110, 77, 15, 111, 81, 14, + 84, -47, 33, 85, -43, 33, 86, -39, 32, 87, -35, 31, + 88, -31, 31, 89, -27, 30, 90, -23, 29, 91, -18, 29, + 92, -14, 28, 93, -10, 27, 94, -6, 27, 94, -2, 26, + 95, 1, 25, 96, 5, 25, 97, 9, 24, 98, 13, 23, + 99, 18, 23, 100, 22, 22, 101, 26, 21, 102, 30, 21, + 103, 34, 20, 104, 38, 19, 105, 42, 19, 106, 46, 18, + 107, 50, 17, 108, 55, 17, 109, 59, 16, 109, 63, 15, + 110, 67, 15, 111, 71, 14, 112, 75, 13, 113, 79, 13, + 87, -49, 32, 87, -45, 31, 88, -41, 30, 89, -36, 30, + 90, -32, 29, 91, -28, 28, 92, -24, 28, 93, -20, 27, + 94, -16, 26, 95, -12, 26, 96, -8, 25, 97, -4, 24, + 98, 0, 24, 99, 4, 23, 100, 8, 22, 101, 12, 22, + 102, 16, 21, 102, 20, 20, 103, 24, 20, 104, 28, 19, + 105, 33, 18, 106, 37, 18, 107, 41, 17, 108, 45, 16, + 109, 49, 16, 110, 53, 15, 111, 57, 14, 112, 61, 14, + 113, 65, 13, 114, 70, 12, 115, 74, 12, 116, 78, 11, + 89, -50, 30, 90, -46, 29, 91, -42, 29, 92, -37, 28, + 93, -33, 27, 94, -29, 27, 94, -25, 26, 95, -21, 25, + 96, -17, 25, 97, -13, 24, 98, -9, 23, 99, -5, 23, + 100, 0, 22, 101, 3, 21, 102, 7, 21, 103, 11, 20, + 104, 15, 19, 105, 19, 19, 106, 23, 18, 107, 27, 17, + 108, 32, 16, 109, 36, 16, 109, 40, 15, 110, 44, 15, + 111, 48, 14, 112, 52, 13, 113, 56, 13, 114, 60, 12, + 115, 64, 11, 116, 69, 10, 117, 73, 10, 118, 77, 9, + 91, -51, 28, 92, -47, 28, 93, -43, 27, 94, -39, 26, + 95, -35, 26, 96, -31, 25, 97, -27, 24, 98, -22, 24, + 99, -18, 23, 100, -14, 22, 101, -10, 22, 101, -6, 21, + 102, -2, 20, 103, 1, 20, 104, 5, 19, 105, 9, 18, + 106, 14, 18, 107, 18, 17, 108, 22, 16, 109, 26, 16, + 110, 30, 15, 111, 34, 14, 112, 38, 14, 113, 42, 13, + 114, 46, 12, 115, 51, 11, 116, 55, 11, 117, 59, 10, + 117, 63, 10, 118, 67, 9, 119, 71, 8, 120, 75, 8, + 94, -53, 27, 94, -49, 26, 95, -45, 25, 96, -40, 25, + 97, -36, 24, 98, -32, 23, 99, -28, 23, 100, -24, 22, + 101, -20, 21, 102, -16, 21, 103, -12, 20, 104, -8, 19, + 105, -3, 19, 106, 0, 18, 107, 4, 17, 108, 8, 17, + 109, 12, 16, 110, 16, 15, 110, 20, 15, 111, 24, 14, + 112, 29, 13, 113, 33, 12, 114, 37, 12, 115, 41, 11, + 116, 45, 11, 117, 49, 10, 118, 53, 9, 119, 57, 9, + 120, 61, 8, 121, 66, 7, 122, 70, 6, 123, 74, 6, + 96, -54, 25, 97, -50, 24, 98, -46, 24, 99, -41, 23, + 100, -37, 22, 101, -33, 22, 101, -29, 21, 103, -25, 20, + 103, -21, 20, 104, -17, 19, 105, -13, 18, 106, -9, 18, + 107, -4, 17, 108, 0, 16, 109, 3, 16, 110, 7, 15, + 111, 11, 14, 112, 15, 14, 113, 19, 13, 114, 23, 12, + 115, 28, 11, 116, 32, 11, 117, 36, 10, 117, 40, 10, + 118, 44, 9, 119, 48, 8, 120, 52, 7, 121, 56, 7, + 122, 60, 6, 123, 65, 5, 124, 69, 5, 125, 73, 4, + 98, -55, 23, 99, -51, 23, 100, -47, 22, 101, -43, 21, + 102, -39, 21, 103, -35, 20, 104, -31, 19, 105, -26, 18, + 106, -22, 18, 107, -18, 17, 108, -14, 17, 109, -10, 16, + 110, -6, 15, 110, -2, 15, 111, 1, 14, 112, 5, 13, + 113, 10, 12, 114, 14, 12, 115, 18, 11, 116, 22, 11, + 117, 26, 10, 118, 30, 9, 119, 34, 8, 120, 38, 8, + 121, 42, 7, 122, 47, 6, 123, 51, 6, 124, 55, 5, + 124, 59, 5, 126, 63, 4, 126, 67, 3, 127, 71, 2, + 101, -57, 22, 102, -53, 21, 102, -49, 20, 103, -44, 20, + 104, -40, 19, 105, -36, 18, 106, -32, 18, 107, -28, 17, + 108, -24, 16, 109, -20, 16, 110, -16, 15, 111, -12, 14, + 112, -7, 13, 113, -3, 13, 114, 0, 12, 115, 4, 12, + 116, 8, 11, 117, 12, 10, 117, 16, 10, 118, 20, 9, + 119, 25, 8, 120, 29, 7, 121, 33, 7, 122, 37, 6, + 123, 41, 6, 124, 45, 5, 125, 49, 4, 126, 53, 3, + 127, 57, 3, 128, 62, 2, 129, 66, 1, 130, 70, 1, + 103, -58, 20, 104, -54, 19, 105, -50, 19, 106, -45, 18, + 107, -41, 17, 108, -37, 17, 109, -33, 16, 110, -29, 15, + 110, -25, 14, 111, -21, 14, 112, -17, 13, 113, -13, 13, + 114, -8, 12, 115, -4, 11, 116, 0, 11, 117, 3, 10, + 118, 7, 9, 119, 11, 8, 120, 15, 8, 121, 19, 7, + 122, 24, 6, 123, 28, 6, 124, 32, 5, 124, 36, 4, + 125, 40, 4, 126, 44, 3, 127, 48, 2, 128, 52, 2, + 129, 56, 1, 130, 61, 0, 131, 65, 0, 132, 69, 0, + 105, -59, 18, 106, -55, 18, 107, -51, 17, 108, -47, 16, + 109, -43, 16, 110, -39, 15, 111, -35, 14, 112, -30, 13, + 113, -26, 13, 114, -22, 12, 115, -18, 12, 116, -14, 11, + 117, -10, 10, 117, -6, 9, 118, -2, 9, 119, 1, 8, + 120, 6, 7, 121, 10, 7, 122, 14, 6, 123, 18, 6, + 124, 22, 5, 125, 26, 4, 126, 30, 3, 127, 34, 3, + 128, 38, 2, 129, 43, 1, 130, 47, 1, 131, 51, 0, + 132, 55, 0, 133, 59, 0, 133, 63, -1, 134, 67, -2, + 108, -61, 17, 109, -57, 16, 109, -53, 15, 110, -48, 14, + 111, -44, 14, 112, -40, 13, 113, -36, 13, 114, -32, 12, + 115, -28, 11, 116, -24, 10, 117, -20, 10, 118, -16, 9, + 119, -11, 8, 120, -7, 8, 121, -3, 7, 122, 0, 7, + 123, 4, 6, 124, 8, 5, 125, 12, 4, 125, 16, 4, + 126, 21, 3, 127, 25, 2, 128, 29, 2, 129, 33, 1, + 130, 37, 0, 131, 41, 0, 132, 45, 0, 133, 49, -1, + 134, 53, -1, 135, 58, -2, 136, 62, -3, 137, 66, -3, + 110, -62, 15, 111, -58, 14, 112, -54, 14, 113, -49, 13, + 114, -45, 12, 115, -41, 12, 116, -37, 11, 117, -33, 10, + 118, -29, 9, 118, -25, 9, 119, -21, 8, 120, -17, 8, + 121, -12, 7, 122, -8, 6, 123, -4, 5, 124, 0, 5, + 125, 3, 4, 126, 7, 3, 127, 11, 3, 128, 15, 2, + 129, 20, 1, 130, 24, 1, 131, 28, 0, 132, 32, 0, + 132, 36, 0, 133, 40, -1, 134, 44, -2, 135, 48, -2, + 136, 52, -3, 137, 57, -4, 138, 61, -4, 139, 65, -5, + 112, -63, 13, 113, -59, 13, 114, -55, 12, 115, -51, 11, + 116, -47, 10, 117, -43, 10, 118, -39, 9, 119, -34, 8, + 120, -30, 8, 121, -26, 7, 122, -22, 6, 123, -18, 6, + 124, -14, 5, 125, -10, 4, 125, -6, 4, 126, -2, 3, + 127, 2, 2, 128, 6, 2, 129, 10, 1, 130, 14, 0, + 131, 18, 0, 132, 22, 0, 133, 26, -1, 134, 30, -1, + 135, 34, -2, 136, 39, -3, 137, 43, -3, 138, 47, -4, + 139, 51, -5, 140, 55, -5, 141, 59, -6, 141, 63, -7, + 115, -65, 11, 116, -61, 10, 117, -57, 10, 118, -52, 9, + 119, -48, 8, 120, -44, 8, 121, -40, 7, 122, -36, 6, + 123, -32, 6, 124, -28, 5, 125, -24, 4, 126, -20, 4, + 127, -15, 3, 127, -11, 2, 128, -7, 2, 129, -3, 1, + 130, 0, 0, 131, 4, 0, 132, 8, 0, 133, 12, -1, + 134, 17, -1, 135, 21, -2, 136, 25, -3, 137, 29, -3, + 138, 33, -4, 139, 37, -5, 140, 41, -5, 141, 45, -6, + 141, 49, -7, 143, 54, -7, 143, 58, -8, 144, 62, -9, + 118, -66, 9, 119, -62, 9, 119, -58, 8, 120, -54, 7, + 121, -50, 7, 122, -46, 6, 123, -42, 5, 124, -37, 5, + 125, -33, 4, 126, -29, 3, 127, -25, 3, 128, -21, 2, + 129, -17, 1, 130, -13, 1, 131, -9, 0, 132, -5, 0, + 133, 0, 0, 134, 3, -1, 134, 7, -2, 135, 11, -2, + 136, 15, -3, 137, 19, -4, 138, 23, -4, 139, 27, -5, + 140, 31, -6, 141, 36, -6, 142, 40, -7, 143, 44, -8, + 144, 48, -8, 145, 52, -9, 146, 56, -10, 147, 60, -10, + 120, -67, 8, 121, -63, 7, 122, -59, 6, 123, -55, 6, + 124, -51, 5, 125, -47, 4, 126, -43, 4, 127, -38, 3, + 127, -34, 2, 128, -30, 2, 129, -26, 1, 130, -22, 0, + 131, -18, 0, 132, -14, 0, 133, -10, -1, 134, -6, -1, + 135, -1, -2, 136, 2, -3, 137, 6, -3, 138, 10, -4, + 139, 14, -5, 140, 18, -5, 141, 22, -6, 142, 26, -7, + 142, 30, -7, 143, 35, -8, 144, 39, -9, 145, 43, -9, + 146, 47, -10, 147, 51, -11, 148, 55, -11, 149, 59, -12, + 122, -69, 6, 123, -65, 5, 124, -61, 5, 125, -56, 4, + 126, -52, 3, 127, -48, 3, 128, -44, 2, 129, -40, 1, + 130, -36, 1, 131, -32, 0, 132, -28, 0, 133, -24, 0, + 134, -19, -1, 135, -15, -2, 135, -11, -2, 136, -7, -3, + 137, -3, -4, 138, 0, -4, 139, 4, -5, 140, 8, -6, + 141, 13, -6, 142, 17, -7, 143, 21, -8, 144, 25, -8, + 145, 29, -9, 146, 33, -10, 147, 37, -10, 148, 41, -11, + 149, 45, -12, 150, 50, -12, 150, 54, -13, 151, 58, -14, + 125, -70, 4, 126, -66, 4, 126, -62, 3, 128, -58, 2, + 128, -54, 2, 129, -50, 1, 130, -46, 0, 131, -41, 0, + 132, -37, 0, 133, -33, -1, 134, -29, -1, 135, -25, -2, + 136, -21, -3, 137, -17, -3, 138, -13, -4, 139, -9, -5, + 140, -4, -5, 141, 0, -6, 142, 3, -7, 142, 7, -7, + 143, 11, -8, 144, 15, -9, 145, 19, -9, 146, 23, -10, + 147, 27, -11, 148, 32, -11, 149, 36, -12, 150, 40, -13, + 151, 44, -13, 152, 48, -14, 153, 52, -15, 154, 56, -15, + 127, -71, 3, 128, -67, 2, 129, -63, 1, 130, -59, 1, + 131, -55, 0, 132, -51, 0, 133, -47, 0, 134, -42, -1, + 135, -38, -2, 135, -34, -2, 136, -30, -3, 137, -26, -4, + 138, -22, -4, 139, -18, -5, 140, -14, -6, 141, -10, -6, + 142, -5, -7, 143, -1, -8, 144, 2, -8, 145, 6, -9, + 146, 10, -10, 147, 14, -10, 148, 18, -11, 149, 22, -12, + 149, 26, -12, 151, 31, -13, 151, 35, -14, 152, 39, -14, + 153, 43, -15, 154, 47, -16, 155, 51, -16, 156, 55, -17, + 129, -73, 1, 130, -69, 0, 131, -65, 0, 132, -60, 0, + 133, -56, -1, 134, -52, -1, 135, -48, -2, 136, -44, -3, + 137, -40, -3, 138, -36, -4, 139, -32, -5, 140, -28, -5, + 141, -23, -6, 142, -19, -7, 142, -15, -7, 143, -11, -8, + 144, -7, -9, 145, -3, -9, 146, 0, -10, 147, 4, -11, + 148, 9, -11, 149, 13, -12, 150, 17, -13, 151, 21, -13, + 152, 25, -14, 153, 29, -15, 154, 33, -15, 155, 37, -16, + 156, 41, -17, 157, 46, -17, 158, 50, -18, 158, 54, -19, + 132, -74, 0, 133, -70, 0, 134, -66, -1, 135, -62, -2, + 135, -58, -2, 136, -54, -3, 137, -50, -4, 138, -45, -4, + 139, -41, -5, 140, -37, -6, 141, -33, -6, 142, -29, -7, + 143, -25, -8, 144, -21, -8, 145, -17, -9, 146, -13, -10, + 147, -8, -10, 148, -4, -11, 149, 0, -12, 149, 3, -12, + 151, 7, -13, 151, 11, -14, 152, 15, -14, 153, 19, -15, + 154, 23, -16, 155, 28, -16, 156, 32, -17, 157, 36, -18, + 158, 40, -18, 159, 44, -19, 160, 48, -20, 161, 52, -20, + 134, -75, -1, 135, -71, -2, 136, -67, -3, 137, -63, -3, + 138, -59, -4, 139, -55, -5, 140, -51, -5, 141, -46, -6, + 142, -42, -7, 142, -38, -7, 143, -34, -8, 144, -30, -9, + 145, -26, -9, 146, -22, -10, 147, -18, -11, 148, -14, -11, + 149, -9, -12, 150, -5, -13, 151, -1, -13, 152, 2, -14, + 153, 6, -15, 154, 10, -15, 155, 14, -16, 156, 18, -17, + 157, 22, -17, 158, 27, -18, 158, 31, -19, 159, 35, -19, + 160, 39, -20, 161, 43, -21, 162, 47, -21, 163, 51, -22, + 136, -77, -3, 137, -73, -4, 138, -69, -4, 139, -64, -5, + 140, -60, -6, 141, -56, -6, 142, -52, -7, 143, -48, -8, + 144, -44, -8, 145, -40, -9, 146, -36, -10, 147, -32, -10, + 148, -27, -11, 149, -23, -12, 150, -19, -12, 150, -15, -13, + 151, -11, -14, 152, -7, -14, 153, -3, -15, 154, 0, -16, + 155, 5, -16, 156, 9, -17, 157, 13, -18, 158, 17, -18, + 159, 21, -19, 160, 25, -20, 161, 29, -20, 162, 33, -21, + 163, 37, -22, 164, 42, -22, 165, 46, -23, 165, 50, -24, + 139, -78, -5, 140, -74, -5, 141, -70, -6, 142, -66, -7, + 143, -62, -7, 143, -58, -8, 144, -54, -9, 145, -49, -9, + 146, -45, -10, 147, -41, -11, 148, -37, -11, 149, -33, -12, + 150, -29, -13, 151, -25, -13, 152, -21, -14, 153, -17, -15, + 154, -12, -15, 155, -8, -16, 156, -4, -17, 157, 0, -17, + 158, 3, -18, 158, 7, -19, 159, 11, -19, 160, 15, -20, + 161, 19, -21, 162, 24, -21, 163, 28, -22, 164, 32, -23, + 165, 36, -23, 166, 40, -24, 167, 44, -25, 168, 48, -25, + 141, -79, -6, 142, -75, -7, 143, -71, -8, 144, -67, -8, + 145, -63, -9, 146, -59, -10, 147, -55, -10, 148, -50, -11, + 149, -46, -12, 150, -42, -12, 150, -38, -13, 151, -34, -14, + 152, -30, -14, 153, -26, -15, 154, -22, -16, 155, -18, -16, + 156, -13, -17, 157, -9, -18, 158, -5, -18, 159, -1, -19, + 160, 2, -20, 161, 6, -20, 162, 10, -21, 163, 14, -22, + 164, 18, -22, 165, 23, -23, 166, 27, -24, 166, 31, -24, + 167, 35, -25, 168, 39, -26, 169, 43, -26, 170, 47, -27, + 143, -81, -8, 144, -77, -9, 145, -73, -9, 146, -68, -10, + 147, -64, -11, 148, -60, -11, 149, -56, -12, 150, -52, -13, + 151, -48, -13, 152, -44, -14, 153, -40, -15, 154, -36, -15, + 155, -31, -16, 156, -27, -17, 157, -23, -17, 157, -19, -18, + 159, -15, -19, 159, -11, -19, 160, -7, -20, 161, -3, -21, + 162, 1, -21, 163, 5, -22, 164, 9, -23, 165, 13, -23, + 166, 17, -24, 167, 21, -25, 168, 25, -25, 169, 29, -26, + 170, 33, -27, 171, 38, -27, 172, 42, -28, 173, 46, -29, + 146, -82, -10, 147, -78, -10, 148, -74, -11, 149, -70, -12, + 150, -66, -12, 150, -62, -13, 151, -58, -14, 152, -53, -14, + 153, -49, -15, 154, -45, -16, 155, -41, -16, 156, -37, -17, + 157, -33, -18, 158, -29, -18, 159, -25, -19, 160, -21, -20, + 161, -16, -20, 162, -12, -21, 163, -8, -22, 164, -4, -22, + 165, 0, -23, 166, 3, -24, 166, 7, -24, 167, 11, -25, + 168, 15, -26, 169, 20, -26, 170, 24, -27, 171, 28, -28, + 172, 32, -28, 173, 36, -29, 174, 40, -30, 175, 44, -30, + 148, -83, -11, 149, -79, -12, 150, -75, -13, 151, -71, -13, + 152, -67, -14, 153, -63, -15, 154, -59, -15, 155, -54, -16, + 156, -50, -17, 157, -46, -17, 157, -42, -18, 158, -38, -19, + 159, -34, -19, 160, -30, -20, 161, -26, -21, 162, -22, -21, + 163, -17, -22, 164, -13, -23, 165, -9, -23, 166, -5, -24, + 167, -1, -25, 168, 2, -25, 169, 6, -26, 170, 10, -27, + 171, 14, -27, 172, 19, -28, 173, 23, -29, 173, 27, -29, + 174, 31, -30, 175, 35, -31, 176, 39, -31, 177, 43, -32, + 150, -85, -13, 151, -81, -14, 152, -77, -14, 153, -72, -15, + 154, -68, -16, 155, -64, -16, 156, -60, -17, 157, -56, -18, + 158, -52, -18, 159, -48, -19, 160, -44, -20, 161, -40, -20, + 162, -35, -21, 163, -31, -22, 164, -27, -22, 165, -23, -23, + 166, -19, -24, 166, -15, -24, 167, -11, -25, 168, -7, -26, + 169, -2, -26, 170, 1, -27, 171, 5, -28, 172, 9, -28, + 173, 13, -29, 174, 17, -30, 175, 21, -30, 176, 25, -31, + 177, 29, -32, 178, 34, -32, 179, 38, -33, 180, 42, -34, + 153, -86, -15, 154, -82, -15, 155, -78, -16, 156, -74, -17, + 157, -70, -17, 158, -66, -18, 158, -62, -19, 159, -57, -19, + 160, -53, -20, 161, -49, -21, 162, -45, -21, 163, -41, -22, + 164, -37, -23, 165, -33, -23, 166, -29, -24, 167, -25, -25, + 168, -20, -25, 169, -16, -26, 170, -12, -27, 171, -8, -27, + 172, -4, -28, 173, 0, -29, 173, 3, -29, 174, 7, -30, + 175, 11, -31, 176, 16, -31, 177, 20, -32, 178, 24, -33, + 179, 28, -33, 180, 32, -34, 181, 36, -35, 182, 40, -35, + 155, -87, -16, 156, -83, -17, 157, -79, -18, 158, -75, -18, + 159, -71, -19, 160, -67, -20, 161, -63, -20, 162, -58, -21, + 163, -54, -22, 164, -50, -22, 165, -46, -23, 165, -42, -24, + 166, -38, -24, 167, -34, -25, 168, -30, -26, 169, -26, -26, + 170, -21, -27, 171, -17, -28, 172, -13, -28, 173, -9, -29, + 174, -5, -30, 175, -1, -30, 176, 2, -31, 177, 6, -32, + 178, 10, -32, 179, 15, -33, 180, 19, -34, 181, 23, -34, + 181, 27, -35, 182, 31, -36, 183, 35, -36, 184, 39, -37, + 158, -89, -18, 158, -85, -19, 159, -81, -19, 160, -76, -20, + 161, -72, -21, 162, -68, -21, 163, -64, -22, 164, -60, -23, + 165, -56, -23, 166, -52, -24, 167, -48, -25, 168, -44, -25, + 169, -39, -26, 170, -35, -27, 171, -31, -27, 172, -27, -28, + 173, -23, -29, 174, -19, -29, 174, -15, -30, 175, -11, -31, + 176, -6, -31, 177, -2, -32, 178, 1, -33, 179, 5, -33, + 180, 9, -34, 181, 13, -35, 182, 17, -35, 183, 21, -36, + 184, 25, -37, 185, 30, -38, 186, 34, -38, 187, 38, -39, + 160, -90, -20, 161, -86, -20, 162, -82, -21, 163, -78, -22, + 164, -74, -22, 165, -70, -23, 165, -66, -24, 167, -61, -24, + 167, -57, -25, 168, -53, -26, 169, -49, -26, 170, -45, -27, + 171, -41, -28, 172, -37, -28, 173, -33, -29, 174, -29, -30, + 175, -24, -30, 176, -20, -31, 177, -16, -32, 178, -12, -32, + 179, -8, -33, 180, -4, -34, 181, 0, -34, 181, 3, -35, + 182, 7, -36, 183, 12, -37, 184, 16, -37, 185, 20, -38, + 186, 24, -38, 187, 28, -39, 188, 32, -40, 189, 36, -40, + 162, -91, -21, 163, -87, -22, 164, -83, -23, 165, -79, -23, + 166, -75, -24, 167, -71, -25, 168, -67, -25, 169, -62, -26, + 170, -58, -27, 171, -54, -27, 172, -50, -28, 172, -46, -29, + 174, -42, -29, 174, -38, -30, 175, -34, -31, 176, -30, -31, + 177, -25, -32, 178, -21, -33, 179, -17, -33, 180, -13, -34, + 181, -9, -35, 182, -5, -35, 183, -1, -36, 184, 2, -37, + 185, 6, -37, 186, 11, -38, 187, 15, -39, 188, 19, -39, + 188, 23, -40, 189, 27, -41, 190, 31, -42, 191, 35, -42, + 165, -93, -24, 166, -89, -24, 167, -85, -25, 168, -80, -26, + 169, -76, -26, 170, -72, -27, 171, -68, -28, 172, -64, -28, + 173, -60, -29, 174, -56, -30, 175, -52, -30, 175, -48, -31, + 176, -43, -32, 177, -39, -32, 178, -35, -33, 179, -31, -34, + 180, -27, -34, 181, -23, -35, 182, -19, -36, 183, -15, -36, + 184, -10, -37, 185, -6, -38, 186, -2, -38, 187, 1, -39, + 188, 5, -40, 189, 9, -40, 190, 13, -41, 190, 17, -42, + 191, 21, -42, 192, 26, -43, 193, 30, -44, 194, 34, -44, + 168, -94, -25, 168, -90, -26, 169, -86, -26, 170, -82, -27, + 171, -78, -28, 172, -74, -29, 173, -70, -29, 174, -65, -30, + 175, -61, -31, 176, -57, -31, 177, -53, -32, 178, -49, -33, + 179, -45, -33, 180, -41, -34, 181, -37, -35, 182, -33, -35, + 183, -28, -36, 183, -24, -37, 184, -20, -37, 185, -16, -38, + 186, -12, -39, 187, -8, -39, 188, -4, -40, 189, 0, -41, + 190, 3, -41, 191, 8, -42, 192, 12, -43, 193, 16, -43, + 194, 20, -44, 195, 24, -45, 196, 28, -45, 197, 32, -46, + 170, -96, -27, 171, -92, -28, 172, -88, -28, 173, -83, -29, + 174, -79, -30, 175, -75, -30, 175, -71, -31, 176, -67, -32, + 177, -63, -32, 178, -59, -33, 179, -55, -34, 180, -51, -34, + 181, -46, -35, 182, -42, -36, 183, -38, -36, 184, -34, -37, + 185, -30, -38, 186, -26, -38, 187, -22, -39, 188, -18, -40, + 189, -13, -40, 190, -9, -41, 191, -5, -42, 191, -1, -42, + 192, 2, -43, 193, 6, -44, 194, 10, -44, 195, 14, -45, + 196, 18, -46, 197, 23, -46, 198, 27, -47, 199, 31, -48, + 172, -97, -29, 173, -93, -29, 174, -89, -30, 175, -84, -31, + 176, -80, -31, 177, -76, -32, 178, -72, -33, 179, -68, -33, + 180, -64, -34, 181, -60, -35, 182, -56, -35, 182, -52, -36, + 184, -47, -37, 184, -43, -37, 185, -39, -38, 186, -35, -39, + 187, -31, -39, 188, -27, -40, 189, -23, -41, 190, -19, -41, + 191, -14, -42, 192, -10, -43, 193, -6, -43, 194, -2, -44, + 195, 1, -45, 196, 5, -45, 197, 9, -46, 198, 13, -47, + 198, 17, -47, 199, 22, -48, 200, 26, -49, 201, 30, -49, + 175, -98, -30, 175, -94, -31, 176, -90, -32, 177, -86, -32, + 178, -82, -33, 179, -78, -34, 180, -74, -34, 181, -69, -35, + 182, -65, -36, 183, -61, -36, 184, -57, -37, 185, -53, -38, + 186, -49, -38, 187, -45, -39, 188, -41, -40, 189, -37, -40, + 190, -32, -41, 191, -28, -42, 191, -24, -42, 192, -20, -43, + 193, -16, -44, 194, -12, -44, 195, -8, -45, 196, -4, -46, + 197, 0, -46, 198, 4, -47, 199, 8, -48, 200, 12, -48, + 201, 16, -49, 202, 20, -50, 203, 24, -50, 204, 28, -51, + 177, -100, -32, 178, -96, -33, 179, -92, -33, 180, -87, -34, + 181, -83, -35, 182, -79, -35, 182, -75, -36, 184, -71, -37, + 184, -67, -37, 185, -63, -38, 186, -59, -39, 187, -55, -39, + 188, -50, -40, 189, -46, -41, 190, -42, -41, 191, -38, -42, + 192, -34, -43, 193, -30, -43, 194, -26, -44, 195, -22, -45, + 196, -17, -45, 197, -13, -46, 198, -9, -47, 198, -5, -47, + 199, -1, -48, 200, 2, -49, 201, 6, -49, 202, 10, -50, + 203, 14, -51, 204, 19, -51, 205, 23, -52, 206, 27, -53, + 179, -101, -34, 180, -97, -34, 181, -93, -35, 182, -88, -36, + 183, -84, -36, 184, -80, -37, 185, -76, -38, 186, -72, -38, + 187, -68, -39, 188, -64, -40, 189, -60, -40, 190, -56, -41, + 191, -51, -42, 191, -47, -42, 192, -43, -43, 193, -39, -44, + 194, -35, -44, 195, -31, -45, 196, -27, -46, 197, -23, -46, + 198, -18, -47, 199, -14, -48, 200, -10, -48, 201, -6, -49, + 202, -2, -50, 203, 1, -50, 204, 5, -51, 205, 9, -52, + 205, 13, -52, 207, 18, -53, 207, 22, -54, 208, 26, -54, + 182, -102, -35, 183, -98, -36, 183, -94, -37, 184, -90, -37, + 185, -86, -38, 186, -82, -39, 187, -78, -39, 188, -73, -40, + 189, -69, -41, 190, -65, -41, 191, -61, -42, 192, -57, -43, + 193, -53, -43, 194, -49, -44, 195, -45, -45, 196, -41, -45, + 197, -36, -46, 198, -32, -47, 198, -28, -47, 199, -24, -48, + 200, -20, -49, 201, -16, -49, 202, -12, -50, 203, -8, -51, + 204, -4, -51, 205, 0, -52, 206, 4, -53, 207, 8, -53, + 208, 12, -54, 209, 16, -55, 210, 20, -55, 211, 24, -56, + 184, -104, -37, 185, -100, -38, 186, -96, -38, 187, -91, -39, + 188, -87, -40, 189, -83, -40, 190, -79, -41, 191, -75, -42, + 191, -71, -42, 192, -67, -43, 193, -63, -44, 194, -59, -44, + 195, -54, -45, 196, -50, -46, 197, -46, -46, 198, -42, -47, + 199, -38, -48, 200, -34, -48, 201, -30, -49, 202, -26, -50, + 203, -21, -50, 204, -17, -51, 205, -13, -52, 206, -9, -52, + 206, -5, -53, 207, -1, -54, 208, 2, -54, 209, 6, -55, + 210, 10, -56, 211, 15, -56, 212, 19, -57, 213, 23, -58, + 186, -105, -39, 187, -101, -39, 188, -97, -40, 189, -92, -41, + 190, -88, -41, 191, -84, -42, 192, -80, -43, 193, -76, -43, + 194, -72, -44, 195, -68, -45, 196, -64, -45, 197, -60, -46, + 198, -55, -47, 199, -51, -47, 199, -47, -48, 200, -43, -49, + 201, -39, -49, 202, -35, -50, 203, -31, -51, 204, -27, -51, + 205, -22, -52, 206, -18, -53, 207, -14, -53, 208, -10, -54, + 209, -6, -55, 210, -2, -55, 211, 1, -56, 212, 5, -57, + 213, 9, -57, 214, 14, -58, 214, 18, -59, 215, 22, -59, + 189, -106, -40, 190, -102, -41, 190, -98, -42, 192, -94, -42, + 192, -90, -43, 193, -86, -44, 194, -82, -44, 195, -77, -45, + 196, -73, -46, 197, -69, -46, 198, -65, -47, 199, -61, -48, + 200, -57, -48, 201, -53, -49, 202, -49, -50, 203, -45, -50, + 204, -40, -51, 205, -36, -52, 206, -32, -52, 206, -28, -53, + 207, -24, -54, 208, -20, -54, 209, -16, -55, 210, -12, -56, + 211, -8, -56, 212, -3, -57, 213, 0, -58, 214, 4, -58, + 215, 8, -59, 216, 12, -60, 217, 16, -60, 218, 20, -61, + 41, -23, 70, 42, -19, 69, 43, -15, 68, 44, -11, 67, + 45, -7, 67, 46, -3, 66, 47, 0, 66, 48, 5, 65, + 49, 9, 64, 50, 13, 63, 51, 17, 63, 52, 21, 62, + 53, 25, 61, 54, 29, 61, 54, 33, 60, 55, 37, 59, + 56, 42, 59, 57, 46, 58, 58, 50, 57, 59, 54, 57, + 60, 58, 56, 61, 62, 55, 62, 66, 55, 63, 70, 54, + 64, 74, 53, 65, 79, 53, 66, 83, 52, 67, 87, 51, + 68, 91, 51, 69, 95, 50, 70, 99, 49, 70, 103, 49, + 44, -24, 68, 45, -20, 67, 46, -16, 67, 47, -12, 66, + 47, -8, 65, 48, -4, 64, 49, 0, 64, 50, 4, 63, + 51, 8, 62, 52, 12, 62, 53, 16, 61, 54, 20, 61, + 55, 24, 60, 56, 28, 59, 57, 32, 58, 58, 36, 58, + 59, 41, 57, 60, 45, 56, 61, 49, 56, 61, 53, 55, + 63, 57, 54, 63, 61, 54, 64, 65, 53, 65, 69, 52, + 66, 73, 52, 67, 78, 51, 68, 82, 50, 69, 86, 50, + 70, 90, 49, 71, 94, 48, 72, 98, 48, 73, 102, 47, + 46, -26, 66, 47, -22, 66, 48, -18, 65, 49, -13, 64, + 50, -9, 63, 51, -5, 63, 52, -1, 62, 53, 2, 61, + 54, 6, 61, 54, 10, 60, 55, 14, 59, 56, 18, 59, + 57, 23, 58, 58, 27, 57, 59, 31, 57, 60, 35, 56, + 61, 39, 55, 62, 43, 55, 63, 47, 54, 64, 51, 53, + 65, 56, 53, 66, 60, 52, 67, 64, 51, 68, 68, 51, + 69, 72, 50, 70, 76, 49, 70, 80, 49, 71, 84, 48, + 72, 88, 47, 73, 93, 47, 74, 97, 46, 75, 101, 45, + 48, -27, 64, 49, -23, 64, 50, -19, 63, 51, -15, 62, + 52, -11, 62, 53, -7, 61, 54, -3, 60, 55, 1, 60, + 56, 5, 59, 57, 9, 58, 58, 13, 58, 59, 17, 57, + 60, 21, 56, 61, 25, 56, 62, 29, 55, 62, 33, 54, + 63, 38, 54, 64, 42, 53, 65, 46, 52, 66, 50, 52, + 67, 54, 51, 68, 58, 50, 69, 62, 50, 70, 66, 49, + 71, 70, 48, 72, 75, 48, 73, 79, 47, 74, 83, 46, + 75, 87, 46, 76, 91, 45, 77, 95, 44, 77, 99, 44, + 51, -28, 63, 52, -24, 62, 53, -20, 62, 54, -16, 61, + 55, -12, 60, 55, -8, 59, 56, -4, 59, 57, 0, 58, + 58, 4, 57, 59, 8, 57, 60, 12, 56, 61, 16, 55, + 62, 20, 55, 63, 24, 54, 64, 28, 53, 65, 32, 53, + 66, 37, 52, 67, 41, 51, 68, 45, 51, 69, 49, 50, + 70, 53, 49, 70, 57, 49, 71, 61, 48, 72, 65, 47, + 73, 69, 47, 74, 74, 46, 75, 78, 45, 76, 82, 45, + 77, 86, 44, 78, 90, 43, 79, 94, 43, 80, 98, 42, + 53, -30, 61, 54, -26, 60, 55, -22, 60, 56, -17, 59, + 57, -13, 58, 58, -9, 58, 59, -5, 57, 60, -1, 56, + 61, 2, 56, 62, 6, 55, 62, 10, 54, 63, 14, 54, + 64, 19, 53, 65, 23, 52, 66, 27, 52, 67, 31, 51, + 68, 35, 50, 69, 39, 50, 70, 43, 49, 71, 47, 48, + 72, 52, 48, 73, 56, 47, 74, 60, 46, 75, 64, 46, + 76, 68, 45, 77, 72, 44, 77, 76, 44, 78, 80, 43, + 79, 84, 42, 80, 89, 42, 81, 93, 41, 82, 97, 40, + 55, -31, 59, 56, -27, 59, 57, -23, 58, 58, -19, 57, + 59, -15, 57, 60, -11, 56, 61, -7, 55, 62, -2, 55, + 63, 1, 54, 64, 5, 53, 65, 9, 53, 66, 13, 52, + 67, 17, 51, 68, 21, 51, 69, 25, 50, 69, 29, 49, + 70, 34, 49, 71, 38, 48, 72, 42, 47, 73, 46, 47, + 74, 50, 46, 75, 54, 45, 76, 58, 45, 77, 62, 44, + 78, 66, 43, 79, 71, 43, 80, 75, 42, 81, 79, 41, + 82, 83, 41, 83, 87, 40, 84, 91, 39, 85, 95, 39, + 58, -32, 58, 59, -28, 57, 60, -24, 56, 61, -20, 56, + 62, -16, 55, 62, -12, 54, 63, -8, 54, 64, -3, 53, + 65, 0, 52, 66, 4, 52, 67, 8, 51, 68, 12, 50, + 69, 16, 50, 70, 20, 49, 71, 24, 48, 72, 28, 48, + 73, 33, 47, 74, 37, 46, 75, 41, 46, 76, 45, 45, + 77, 49, 44, 78, 53, 44, 78, 57, 43, 79, 61, 42, + 80, 65, 42, 81, 70, 41, 82, 74, 40, 83, 78, 40, + 84, 82, 39, 85, 86, 38, 86, 90, 38, 87, 94, 37, + 60, -34, 56, 61, -30, 55, 62, -26, 55, 63, -21, 54, + 64, -17, 53, 65, -13, 53, 66, -9, 52, 67, -5, 51, + 68, -1, 51, 69, 2, 50, 69, 6, 49, 70, 10, 49, + 71, 15, 48, 72, 19, 47, 73, 23, 47, 74, 27, 46, + 75, 31, 45, 76, 35, 45, 77, 39, 44, 78, 43, 43, + 79, 48, 43, 80, 52, 42, 81, 56, 41, 82, 60, 41, + 83, 64, 40, 84, 68, 39, 85, 72, 39, 85, 76, 38, + 86, 80, 37, 87, 85, 37, 88, 89, 36, 89, 93, 35, + 62, -35, 54, 63, -31, 54, 64, -27, 53, 65, -23, 52, + 66, -19, 52, 67, -15, 51, 68, -11, 50, 69, -6, 50, + 70, -2, 49, 71, 1, 48, 72, 5, 48, 73, 9, 47, + 74, 13, 46, 75, 17, 46, 76, 21, 45, 77, 25, 44, + 78, 30, 44, 78, 34, 43, 79, 38, 42, 80, 42, 42, + 81, 46, 41, 82, 50, 40, 83, 54, 40, 84, 58, 39, + 85, 62, 38, 86, 67, 38, 87, 71, 37, 88, 75, 36, + 89, 79, 36, 90, 83, 35, 91, 87, 34, 92, 91, 34, + 65, -36, 53, 66, -32, 52, 67, -28, 51, 68, -24, 51, + 69, -20, 50, 70, -16, 49, 70, -12, 49, 71, -7, 48, + 72, -3, 47, 73, 0, 47, 74, 4, 46, 75, 8, 45, + 76, 12, 45, 77, 16, 44, 78, 20, 43, 79, 24, 43, + 80, 29, 42, 81, 33, 41, 82, 37, 41, 83, 41, 40, + 84, 45, 39, 85, 49, 39, 85, 53, 38, 86, 57, 37, + 87, 61, 37, 88, 66, 36, 89, 70, 35, 90, 74, 35, + 91, 78, 34, 92, 82, 33, 93, 86, 33, 94, 90, 32, + 68, -38, 51, 69, -34, 50, 70, -30, 49, 71, -26, 49, + 72, -22, 48, 72, -18, 47, 73, -14, 47, 74, -9, 46, + 75, -5, 45, 76, -1, 45, 77, 2, 44, 78, 6, 43, + 79, 10, 43, 80, 14, 42, 81, 18, 41, 82, 22, 41, + 83, 27, 40, 84, 31, 39, 85, 35, 39, 86, 39, 38, + 87, 43, 37, 87, 47, 37, 88, 51, 36, 89, 55, 35, + 90, 59, 35, 91, 64, 34, 92, 68, 33, 93, 72, 33, + 94, 76, 32, 95, 80, 31, 96, 84, 31, 97, 88, 30, + 70, -39, 49, 71, -35, 48, 72, -31, 48, 73, -27, 47, + 74, -23, 46, 75, -19, 46, 76, -15, 45, 77, -10, 44, + 78, -6, 44, 79, -2, 43, 79, 1, 42, 80, 5, 42, + 81, 9, 41, 82, 13, 40, 83, 17, 40, 84, 21, 39, + 85, 26, 38, 86, 30, 38, 87, 34, 37, 88, 38, 36, + 89, 42, 36, 90, 46, 35, 91, 50, 34, 92, 54, 34, + 93, 58, 33, 94, 63, 32, 95, 67, 32, 95, 71, 31, + 96, 75, 30, 97, 79, 30, 98, 83, 29, 99, 87, 28, + 72, -41, 47, 73, -37, 47, 74, -33, 46, 75, -28, 45, + 76, -24, 45, 77, -20, 44, 78, -16, 43, 79, -12, 43, + 80, -8, 42, 81, -4, 41, 82, 0, 41, 83, 3, 40, + 84, 8, 39, 85, 12, 39, 86, 16, 38, 86, 20, 37, + 88, 24, 37, 88, 28, 36, 89, 32, 35, 90, 36, 35, + 91, 41, 34, 92, 45, 33, 93, 49, 33, 94, 53, 32, + 95, 57, 31, 96, 61, 31, 97, 65, 30, 98, 69, 29, + 99, 73, 29, 100, 78, 28, 101, 82, 27, 102, 86, 27, + 75, -42, 46, 76, -38, 45, 77, -34, 44, 78, -30, 44, + 79, -26, 43, 79, -22, 42, 80, -18, 42, 81, -13, 41, + 82, -9, 40, 83, -5, 40, 84, -1, 39, 85, 2, 38, + 86, 6, 38, 87, 10, 37, 88, 14, 36, 89, 18, 36, + 90, 23, 35, 91, 27, 34, 92, 31, 34, 93, 35, 33, + 94, 39, 32, 95, 43, 32, 95, 47, 31, 96, 51, 30, + 97, 55, 30, 98, 60, 29, 99, 64, 28, 100, 68, 28, + 101, 72, 27, 102, 76, 26, 103, 80, 26, 104, 84, 25, + 77, -43, 44, 78, -39, 43, 79, -35, 43, 80, -31, 42, + 81, -27, 41, 82, -23, 41, 83, -19, 40, 84, -14, 39, + 85, -10, 39, 86, -6, 38, 87, -2, 37, 87, 1, 37, + 88, 5, 36, 89, 9, 35, 90, 13, 35, 91, 17, 34, + 92, 22, 33, 93, 26, 33, 94, 30, 32, 95, 34, 31, + 96, 38, 31, 97, 42, 30, 98, 46, 29, 99, 50, 29, + 100, 54, 28, 101, 59, 27, 102, 63, 27, 102, 67, 26, + 103, 71, 25, 104, 75, 25, 105, 79, 24, 106, 83, 23, + 80, -45, 42, 80, -41, 42, 81, -37, 41, 82, -32, 40, + 83, -28, 40, 84, -24, 39, 85, -20, 38, 86, -16, 38, + 87, -12, 37, 88, -8, 36, 89, -4, 36, 90, 0, 35, + 91, 4, 34, 92, 8, 34, 93, 12, 33, 94, 16, 32, + 95, 20, 32, 95, 24, 31, 96, 28, 30, 97, 32, 30, + 98, 37, 29, 99, 41, 28, 100, 45, 28, 101, 49, 27, + 102, 53, 26, 103, 57, 26, 104, 61, 25, 105, 65, 24, + 106, 69, 24, 107, 74, 23, 108, 78, 22, 109, 82, 22, + 82, -46, 41, 83, -42, 40, 84, -38, 39, 85, -33, 39, + 86, -29, 38, 87, -25, 37, 87, -21, 37, 88, -17, 36, + 89, -13, 35, 90, -9, 35, 91, -5, 34, 92, -1, 33, + 93, 3, 33, 94, 7, 32, 95, 11, 31, 96, 15, 31, + 97, 19, 30, 98, 23, 29, 99, 27, 29, 100, 31, 28, + 101, 36, 27, 102, 40, 27, 102, 44, 26, 103, 48, 25, + 104, 52, 25, 105, 56, 24, 106, 60, 23, 107, 64, 23, + 108, 68, 22, 109, 73, 21, 110, 77, 21, 111, 81, 20, + 84, -47, 39, 85, -43, 38, 86, -39, 38, 87, -35, 37, + 88, -31, 36, 89, -27, 36, 90, -23, 35, 91, -18, 34, + 92, -14, 34, 93, -10, 33, 94, -6, 32, 94, -2, 32, + 95, 1, 31, 96, 5, 30, 97, 9, 30, 98, 13, 29, + 99, 18, 28, 100, 22, 28, 101, 26, 27, 102, 30, 26, + 103, 34, 26, 104, 38, 25, 105, 42, 24, 106, 46, 24, + 107, 50, 23, 108, 55, 22, 109, 59, 22, 110, 63, 21, + 110, 67, 20, 111, 71, 20, 112, 75, 19, 113, 79, 18, + 87, -49, 37, 87, -45, 37, 88, -41, 36, 89, -36, 35, + 90, -32, 35, 91, -28, 34, 92, -24, 33, 93, -20, 33, + 94, -16, 32, 95, -12, 31, 96, -8, 31, 97, -4, 30, + 98, 0, 29, 99, 4, 29, 100, 8, 28, 101, 12, 27, + 102, 16, 27, 103, 20, 26, 103, 24, 25, 104, 28, 25, + 105, 33, 24, 106, 37, 23, 107, 41, 23, 108, 45, 22, + 109, 49, 21, 110, 53, 21, 111, 57, 20, 112, 61, 19, + 113, 65, 19, 114, 70, 18, 115, 74, 17, 116, 78, 17, + 89, -50, 36, 90, -46, 35, 91, -42, 34, 92, -37, 34, + 93, -33, 33, 94, -29, 32, 94, -25, 32, 96, -21, 31, + 96, -17, 30, 97, -13, 30, 98, -9, 29, 99, -5, 28, + 100, 0, 28, 101, 3, 27, 102, 7, 26, 103, 11, 26, + 104, 15, 25, 105, 19, 24, 106, 23, 24, 107, 27, 23, + 108, 32, 22, 109, 36, 22, 110, 40, 21, 110, 44, 20, + 111, 48, 20, 112, 52, 19, 113, 56, 18, 114, 60, 18, + 115, 64, 17, 116, 69, 16, 117, 73, 16, 118, 77, 15, + 91, -51, 34, 92, -47, 33, 93, -43, 33, 94, -39, 32, + 95, -35, 31, 96, -31, 31, 97, -27, 30, 98, -22, 29, + 99, -18, 29, 100, -14, 28, 101, -10, 27, 102, -6, 27, + 103, -2, 26, 103, 1, 25, 104, 5, 25, 105, 9, 24, + 106, 14, 23, 107, 18, 23, 108, 22, 22, 109, 26, 21, + 110, 30, 20, 111, 34, 20, 112, 38, 19, 113, 42, 19, + 114, 46, 18, 115, 51, 17, 116, 55, 17, 117, 59, 16, + 117, 63, 15, 119, 67, 14, 119, 71, 14, 120, 75, 13, + 94, -53, 32, 95, -49, 32, 95, -45, 31, 96, -40, 30, + 97, -36, 30, 98, -32, 29, 99, -28, 28, 100, -24, 28, + 101, -20, 27, 102, -16, 26, 103, -12, 26, 104, -8, 25, + 105, -3, 24, 106, 0, 24, 107, 4, 23, 108, 8, 22, + 109, 12, 22, 110, 16, 21, 110, 20, 20, 111, 24, 20, + 112, 29, 19, 113, 33, 18, 114, 37, 18, 115, 41, 17, + 116, 45, 16, 117, 49, 15, 118, 53, 15, 119, 57, 14, + 120, 61, 14, 121, 66, 13, 122, 70, 12, 123, 74, 12, + 96, -54, 31, 97, -50, 30, 98, -46, 29, 99, -41, 29, + 100, -37, 28, 101, -33, 27, 102, -29, 27, 103, -25, 26, + 103, -21, 25, 104, -17, 25, 105, -13, 24, 106, -9, 23, + 107, -4, 23, 108, 0, 22, 109, 3, 21, 110, 7, 21, + 111, 11, 20, 112, 15, 19, 113, 19, 19, 114, 23, 18, + 115, 28, 17, 116, 32, 16, 117, 36, 16, 117, 40, 15, + 118, 44, 15, 119, 48, 14, 120, 52, 13, 121, 56, 13, + 122, 60, 12, 123, 65, 11, 124, 69, 10, 125, 73, 10, + 98, -55, 29, 99, -51, 28, 100, -47, 28, 101, -43, 27, + 102, -39, 26, 103, -35, 26, 104, -31, 25, 105, -26, 24, + 106, -22, 24, 107, -18, 23, 108, -14, 22, 109, -10, 22, + 110, -6, 21, 110, -2, 20, 111, 1, 20, 112, 5, 19, + 113, 10, 18, 114, 14, 18, 115, 18, 17, 116, 22, 16, + 117, 26, 15, 118, 30, 15, 119, 34, 14, 120, 38, 14, + 121, 42, 13, 122, 47, 12, 123, 51, 11, 124, 55, 11, + 125, 59, 10, 126, 63, 9, 126, 67, 9, 127, 71, 8, + 101, -57, 27, 102, -53, 27, 102, -49, 26, 103, -44, 25, + 104, -40, 25, 105, -36, 24, 106, -32, 23, 107, -28, 22, + 108, -24, 22, 109, -20, 21, 110, -16, 21, 111, -12, 20, + 112, -7, 19, 113, -3, 19, 114, 0, 18, 115, 4, 17, + 116, 8, 16, 117, 12, 16, 118, 16, 15, 118, 20, 15, + 119, 25, 14, 120, 29, 13, 121, 33, 12, 122, 37, 12, + 123, 41, 11, 124, 45, 10, 125, 49, 10, 126, 53, 9, + 127, 57, 9, 128, 62, 8, 129, 66, 7, 130, 70, 6, + 103, -58, 26, 104, -54, 25, 105, -50, 24, 106, -45, 24, + 107, -41, 23, 108, -37, 22, 109, -33, 22, 110, -29, 21, + 111, -25, 20, 111, -21, 20, 112, -17, 19, 113, -13, 18, + 114, -8, 17, 115, -4, 17, 116, 0, 16, 117, 3, 16, + 118, 7, 15, 119, 11, 14, 120, 15, 14, 121, 19, 13, + 122, 24, 12, 123, 28, 11, 124, 32, 11, 125, 36, 10, + 125, 40, 10, 126, 44, 9, 127, 48, 8, 128, 52, 7, + 129, 56, 7, 130, 61, 6, 131, 65, 5, 132, 69, 5, + 105, -59, 24, 106, -55, 23, 107, -51, 23, 108, -47, 22, + 109, -43, 21, 110, -39, 21, 111, -35, 20, 112, -30, 19, + 113, -26, 18, 114, -22, 18, 115, -18, 17, 116, -14, 17, + 117, -10, 16, 118, -6, 15, 118, -2, 15, 119, 1, 14, + 120, 6, 13, 121, 10, 12, 122, 14, 12, 123, 18, 11, + 124, 22, 10, 125, 26, 10, 126, 30, 9, 127, 34, 8, + 128, 38, 8, 129, 43, 7, 130, 47, 6, 131, 51, 6, + 132, 55, 5, 133, 59, 4, 134, 63, 4, 134, 67, 3, + 108, -61, 22, 109, -57, 22, 110, -53, 21, 111, -48, 20, + 111, -44, 20, 112, -40, 19, 113, -36, 18, 114, -32, 17, + 115, -28, 17, 116, -24, 16, 117, -20, 16, 118, -16, 15, + 119, -11, 14, 120, -7, 13, 121, -3, 13, 122, 0, 12, + 123, 4, 11, 124, 8, 11, 125, 12, 10, 125, 16, 10, + 127, 21, 9, 127, 25, 8, 128, 29, 7, 129, 33, 7, + 130, 37, 6, 131, 41, 5, 132, 45, 5, 133, 49, 4, + 134, 53, 3, 135, 58, 3, 136, 62, 2, 137, 66, 1, + 110, -62, 21, 111, -58, 20, 112, -54, 19, 113, -49, 18, + 114, -45, 18, 115, -41, 17, 116, -37, 17, 117, -33, 16, + 118, -29, 15, 118, -25, 14, 119, -21, 14, 120, -17, 13, + 121, -12, 12, 122, -8, 12, 123, -4, 11, 124, 0, 11, + 125, 3, 10, 126, 7, 9, 127, 11, 8, 128, 15, 8, + 129, 20, 7, 130, 24, 6, 131, 28, 6, 132, 32, 5, + 132, 36, 4, 134, 40, 4, 134, 44, 3, 135, 48, 2, + 136, 52, 2, 137, 57, 1, 138, 61, 0, 139, 65, 0, + 112, -63, 19, 113, -59, 18, 114, -55, 18, 115, -51, 17, + 116, -47, 16, 117, -43, 16, 118, -39, 15, 119, -34, 14, + 120, -30, 13, 121, -26, 13, 122, -22, 12, 123, -18, 12, + 124, -14, 11, 125, -10, 10, 125, -6, 9, 126, -2, 9, + 127, 2, 8, 128, 6, 7, 129, 10, 7, 130, 14, 6, + 131, 18, 5, 132, 22, 5, 133, 26, 4, 134, 30, 3, + 135, 34, 3, 136, 39, 2, 137, 43, 1, 138, 47, 1, + 139, 51, 0, 140, 55, 0, 141, 59, 0, 141, 63, -1, + 115, -65, 17, 116, -61, 17, 117, -57, 16, 118, -52, 15, + 118, -48, 14, 119, -44, 14, 120, -40, 13, 121, -36, 12, + 122, -32, 12, 123, -28, 11, 124, -24, 10, 125, -20, 10, + 126, -15, 9, 127, -11, 8, 128, -7, 8, 129, -3, 7, + 130, 0, 6, 131, 4, 6, 132, 8, 5, 133, 12, 4, + 134, 17, 4, 134, 21, 3, 135, 25, 2, 136, 29, 2, + 137, 33, 1, 138, 37, 0, 139, 41, 0, 140, 45, 0, + 141, 49, -1, 142, 54, -1, 143, 58, -2, 144, 62, -3, + 118, -66, 15, 119, -62, 14, 119, -58, 14, 121, -54, 13, + 121, -50, 12, 122, -46, 12, 123, -42, 11, 124, -37, 10, + 125, -33, 10, 126, -29, 9, 127, -25, 8, 128, -21, 8, + 129, -17, 7, 130, -13, 6, 131, -9, 6, 132, -5, 5, + 133, 0, 4, 134, 3, 4, 135, 7, 3, 135, 11, 2, + 136, 15, 2, 137, 19, 1, 138, 23, 0, 139, 27, 0, + 140, 31, 0, 141, 36, -1, 142, 40, -1, 143, 44, -2, + 144, 48, -3, 145, 52, -3, 146, 56, -4, 147, 60, -5, + 120, -68, 13, 121, -64, 13, 122, -60, 12, 123, -55, 11, + 124, -51, 11, 125, -47, 10, 126, -43, 9, 127, -39, 9, + 128, -35, 8, 128, -31, 7, 129, -27, 7, 130, -23, 6, + 131, -18, 5, 132, -14, 5, 133, -10, 4, 134, -6, 3, + 135, -2, 3, 136, 1, 2, 137, 5, 1, 138, 9, 1, + 139, 14, 0, 140, 18, 0, 141, 22, 0, 142, 26, -1, + 142, 30, -2, 144, 34, -2, 144, 38, -3, 145, 42, -4, + 146, 46, -4, 147, 51, -5, 148, 55, -6, 149, 59, -6, + 122, -69, 12, 123, -65, 11, 124, -61, 10, 125, -56, 10, + 126, -52, 9, 127, -48, 8, 128, -44, 8, 129, -40, 7, + 130, -36, 6, 131, -32, 6, 132, -28, 5, 133, -24, 4, + 134, -19, 4, 135, -15, 3, 135, -11, 2, 136, -7, 2, + 137, -3, 1, 138, 0, 0, 139, 4, 0, 140, 8, 0, + 141, 13, -1, 142, 17, -1, 143, 21, -2, 144, 25, -3, + 145, 29, -3, 146, 33, -4, 147, 37, -5, 148, 41, -5, + 149, 45, -6, 150, 50, -7, 151, 54, -7, 151, 58, -8, + 125, -70, 10, 126, -66, 9, 127, -62, 9, 128, -58, 8, + 128, -54, 7, 129, -50, 7, 130, -46, 6, 131, -41, 5, + 132, -37, 5, 133, -33, 4, 134, -29, 3, 135, -25, 3, + 136, -21, 2, 137, -17, 1, 138, -13, 1, 139, -9, 0, + 140, -4, 0, 141, 0, 0, 142, 3, -1, 142, 7, -2, + 144, 11, -2, 144, 15, -3, 145, 19, -4, 146, 23, -4, + 147, 27, -5, 148, 32, -6, 149, 36, -6, 150, 40, -7, + 151, 44, -8, 152, 48, -8, 153, 52, -9, 154, 56, -10, + 127, -71, 8, 128, -67, 8, 129, -63, 7, 130, -59, 6, + 131, -55, 6, 132, -51, 5, 133, -47, 4, 134, -42, 4, + 135, -38, 3, 135, -34, 2, 136, -30, 2, 137, -26, 1, + 138, -22, 0, 139, -18, 0, 140, -14, 0, 141, -10, -1, + 142, -5, -1, 143, -1, -2, 144, 2, -3, 145, 6, -3, + 146, 10, -4, 147, 14, -5, 148, 18, -5, 149, 22, -6, + 150, 26, -7, 151, 31, -7, 151, 35, -8, 152, 39, -9, + 153, 43, -9, 154, 47, -10, 155, 51, -11, 156, 55, -11, + 129, -73, 7, 130, -69, 6, 131, -65, 5, 132, -60, 5, + 133, -56, 4, 134, -52, 3, 135, -48, 3, 136, -44, 2, + 137, -40, 1, 138, -36, 1, 139, -32, 0, 140, -28, 0, + 141, -23, 0, 142, -19, -1, 143, -15, -2, 143, -11, -2, + 144, -7, -3, 145, -3, -4, 146, 0, -4, 147, 4, -5, + 148, 9, -6, 149, 13, -6, 150, 17, -7, 151, 21, -8, + 152, 25, -8, 153, 29, -9, 154, 33, -10, 155, 37, -10, + 156, 41, -11, 157, 46, -12, 158, 50, -12, 158, 54, -13, + 132, -74, 5, 133, -70, 4, 134, -66, 4, 135, -62, 3, + 136, -58, 2, 136, -54, 2, 137, -50, 1, 138, -45, 0, + 139, -41, 0, 140, -37, 0, 141, -33, -1, 142, -29, -1, + 143, -25, -2, 144, -21, -3, 145, -17, -3, 146, -13, -4, + 147, -8, -5, 148, -4, -5, 149, 0, -6, 150, 3, -7, + 151, 7, -7, 151, 11, -8, 152, 15, -9, 153, 19, -9, + 154, 23, -10, 155, 28, -11, 156, 32, -11, 157, 36, -12, + 158, 40, -13, 159, 44, -13, 160, 48, -14, 161, 52, -15, + 134, -75, 3, 135, -71, 3, 136, -67, 2, 137, -63, 1, + 138, -59, 1, 139, -55, 0, 140, -51, 0, 141, -46, 0, + 142, -42, -1, 143, -38, -2, 143, -34, -2, 144, -30, -3, + 145, -26, -4, 146, -22, -4, 147, -18, -5, 148, -14, -6, + 149, -9, -6, 150, -5, -7, 151, -1, -8, 152, 2, -8, + 153, 6, -9, 154, 10, -10, 155, 14, -10, 156, 18, -11, + 157, 22, -12, 158, 27, -12, 159, 31, -13, 159, 35, -14, + 160, 39, -14, 161, 43, -15, 162, 47, -16, 163, 51, -16, + 136, -77, 2, 137, -73, 1, 138, -69, 0, 139, -64, 0, + 140, -60, 0, 141, -56, -1, 142, -52, -1, 143, -48, -2, + 144, -44, -3, 145, -40, -3, 146, -36, -4, 147, -32, -5, + 148, -27, -5, 149, -23, -6, 150, -19, -7, 150, -15, -7, + 152, -11, -8, 152, -7, -9, 153, -3, -9, 154, 0, -10, + 155, 5, -11, 156, 9, -11, 157, 13, -12, 158, 17, -13, + 159, 21, -13, 160, 25, -14, 161, 29, -15, 162, 33, -15, + 163, 37, -16, 164, 42, -17, 165, 46, -17, 166, 50, -18, + 139, -78, 0, 140, -74, 0, 141, -70, 0, 142, -66, -1, + 143, -62, -2, 143, -58, -2, 144, -54, -3, 145, -49, -4, + 146, -45, -4, 147, -41, -5, 148, -37, -6, 149, -33, -6, + 150, -29, -7, 151, -25, -8, 152, -21, -8, 153, -17, -9, + 154, -12, -10, 155, -8, -10, 156, -4, -11, 157, 0, -12, + 158, 3, -12, 159, 7, -13, 159, 11, -14, 160, 15, -14, + 161, 19, -15, 162, 24, -16, 163, 28, -16, 164, 32, -17, + 165, 36, -18, 166, 40, -18, 167, 44, -19, 168, 48, -20, + 141, -79, -1, 142, -75, -1, 143, -71, -2, 144, -67, -3, + 145, -63, -3, 146, -59, -4, 147, -55, -5, 148, -50, -5, + 149, -46, -6, 150, -42, -7, 150, -38, -7, 151, -34, -8, + 152, -30, -9, 153, -26, -9, 154, -22, -10, 155, -18, -11, + 156, -13, -11, 157, -9, -12, 158, -5, -13, 159, -1, -13, + 160, 2, -14, 161, 6, -15, 162, 10, -15, 163, 14, -16, + 164, 18, -17, 165, 23, -17, 166, 27, -18, 166, 31, -19, + 167, 35, -19, 168, 39, -20, 169, 43, -21, 170, 47, -21, + 143, -81, -2, 144, -77, -3, 145, -73, -4, 146, -68, -4, + 147, -64, -5, 148, -60, -6, 149, -56, -6, 150, -52, -7, + 151, -48, -8, 152, -44, -8, 153, -40, -9, 154, -36, -10, + 155, -31, -10, 156, -27, -11, 157, -23, -12, 158, -19, -12, + 159, -15, -13, 159, -11, -14, 160, -7, -14, 161, -3, -15, + 162, 1, -16, 163, 5, -16, 164, 9, -17, 165, 13, -18, + 166, 17, -18, 167, 21, -19, 168, 25, -20, 169, 29, -20, + 170, 33, -21, 171, 38, -22, 172, 42, -22, 173, 46, -23, + 146, -82, -4, 147, -78, -5, 148, -74, -5, 149, -70, -6, + 150, -66, -7, 151, -62, -7, 151, -58, -8, 152, -53, -9, + 153, -49, -9, 154, -45, -10, 155, -41, -11, 156, -37, -11, + 157, -33, -12, 158, -29, -13, 159, -25, -13, 160, -21, -14, + 161, -16, -15, 162, -12, -15, 163, -8, -16, 164, -4, -17, + 165, 0, -17, 166, 3, -18, 166, 7, -19, 167, 11, -19, + 168, 15, -20, 169, 20, -21, 170, 24, -21, 171, 28, -22, + 172, 32, -23, 173, 36, -23, 174, 40, -24, 175, 44, -25, + 148, -83, -6, 149, -79, -6, 150, -75, -7, 151, -71, -8, + 152, -67, -8, 153, -63, -9, 154, -59, -10, 155, -54, -10, + 156, -50, -11, 157, -46, -12, 158, -42, -12, 158, -38, -13, + 159, -34, -14, 160, -30, -14, 161, -26, -15, 162, -22, -16, + 163, -17, -16, 164, -13, -17, 165, -9, -18, 166, -5, -18, + 167, -1, -19, 168, 2, -20, 169, 6, -20, 170, 10, -21, + 171, 14, -22, 172, 19, -22, 173, 23, -23, 174, 27, -24, + 174, 31, -24, 175, 35, -25, 176, 39, -26, 177, 43, -26, + 151, -85, -7, 151, -81, -8, 152, -77, -9, 153, -72, -9, + 154, -68, -10, 155, -64, -11, 156, -60, -11, 157, -56, -12, + 158, -52, -13, 159, -48, -13, 160, -44, -14, 161, -40, -15, + 162, -35, -15, 163, -31, -16, 164, -27, -17, 165, -23, -17, + 166, -19, -18, 167, -15, -19, 167, -11, -19, 168, -7, -20, + 169, -2, -21, 170, 1, -21, 171, 5, -22, 172, 9, -23, + 173, 13, -23, 174, 17, -24, 175, 21, -25, 176, 25, -25, + 177, 29, -26, 178, 34, -27, 179, 38, -27, 180, 42, -28, + 153, -86, -9, 154, -82, -10, 155, -78, -10, 156, -74, -11, + 157, -70, -12, 158, -66, -12, 158, -62, -13, 160, -57, -14, + 160, -53, -14, 161, -49, -15, 162, -45, -16, 163, -41, -16, + 164, -37, -17, 165, -33, -18, 166, -29, -18, 167, -25, -19, + 168, -20, -20, 169, -16, -20, 170, -12, -21, 171, -8, -22, + 172, -4, -22, 173, 0, -23, 174, 3, -24, 174, 7, -24, + 175, 11, -25, 176, 16, -26, 177, 20, -26, 178, 24, -27, + 179, 28, -28, 180, 32, -28, 181, 36, -29, 182, 40, -30, + 155, -87, -11, 156, -83, -11, 157, -79, -12, 158, -75, -13, + 159, -71, -13, 160, -67, -14, 161, -63, -15, 162, -58, -15, + 163, -54, -16, 164, -50, -17, 165, -46, -17, 165, -42, -18, + 167, -38, -19, 167, -34, -19, 168, -30, -20, 169, -26, -21, + 170, -21, -21, 171, -17, -22, 172, -13, -23, 173, -9, -23, + 174, -5, -24, 175, -1, -25, 176, 2, -25, 177, 6, -26, + 178, 10, -27, 179, 15, -27, 180, 19, -28, 181, 23, -29, + 181, 27, -29, 182, 31, -30, 183, 35, -31, 184, 39, -31, + 158, -89, -12, 158, -85, -13, 159, -81, -14, 160, -76, -14, + 161, -72, -15, 162, -68, -16, 163, -64, -16, 164, -60, -17, + 165, -56, -18, 166, -52, -18, 167, -48, -19, 168, -44, -20, + 169, -39, -20, 170, -35, -21, 171, -31, -22, 172, -27, -22, + 173, -23, -23, 174, -19, -24, 174, -15, -24, 175, -11, -25, + 176, -6, -26, 177, -2, -26, 178, 1, -27, 179, 5, -28, + 180, 9, -28, 181, 13, -29, 182, 17, -30, 183, 21, -30, + 184, 25, -31, 185, 30, -32, 186, 34, -32, 187, 38, -33, + 160, -90, -14, 161, -86, -15, 162, -82, -15, 163, -78, -16, + 164, -74, -17, 165, -70, -17, 166, -66, -18, 167, -61, -19, + 167, -57, -19, 168, -53, -20, 169, -49, -21, 170, -45, -21, + 171, -41, -22, 172, -37, -23, 173, -33, -23, 174, -29, -24, + 175, -24, -25, 176, -20, -25, 177, -16, -26, 178, -12, -27, + 179, -8, -27, 180, -4, -28, 181, 0, -29, 181, 3, -29, + 182, 7, -30, 183, 12, -31, 184, 16, -31, 185, 20, -32, + 186, 24, -33, 187, 28, -34, 188, 32, -34, 189, 36, -35, + 162, -91, -16, 163, -87, -16, 164, -83, -17, 165, -79, -18, + 166, -75, -18, 167, -71, -19, 168, -67, -20, 169, -62, -20, + 170, -58, -21, 171, -54, -22, 172, -50, -22, 173, -46, -23, + 174, -42, -24, 174, -38, -24, 175, -34, -25, 176, -30, -26, + 177, -25, -26, 178, -21, -27, 179, -17, -28, 180, -13, -28, + 181, -9, -29, 182, -5, -30, 183, -1, -30, 184, 2, -31, + 185, 6, -32, 186, 11, -33, 187, 15, -33, 188, 19, -34, + 189, 23, -34, 190, 27, -35, 190, 31, -36, 191, 35, -36, + 165, -93, -17, 166, -89, -18, 166, -85, -19, 167, -80, -19, + 168, -76, -20, 169, -72, -21, 170, -68, -21, 171, -64, -22, + 172, -60, -23, 173, -56, -23, 174, -52, -24, 175, -48, -25, + 176, -43, -25, 177, -39, -26, 178, -35, -27, 179, -31, -27, + 180, -27, -28, 181, -23, -29, 182, -19, -29, 182, -15, -30, + 183, -10, -31, 184, -6, -31, 185, -2, -32, 186, 1, -33, + 187, 5, -33, 188, 9, -34, 189, 13, -35, 190, 17, -35, + 191, 21, -36, 192, 26, -37, 193, 30, -38, 194, 34, -38, + 168, -94, -20, 168, -90, -20, 169, -86, -21, 170, -82, -22, + 171, -78, -22, 172, -74, -23, 173, -70, -24, 174, -65, -24, + 175, -61, -25, 176, -57, -26, 177, -53, -26, 178, -49, -27, + 179, -45, -28, 180, -41, -28, 181, -37, -29, 182, -33, -30, + 183, -28, -30, 184, -24, -31, 184, -20, -32, 185, -16, -32, + 186, -12, -33, 187, -8, -34, 188, -4, -34, 189, 0, -35, + 190, 3, -36, 191, 8, -36, 192, 12, -37, 193, 16, -38, + 194, 20, -38, 195, 24, -39, 196, 28, -40, 197, 32, -40, + 170, -96, -21, 171, -92, -22, 172, -88, -22, 173, -83, -23, + 174, -79, -24, 175, -75, -25, 175, -71, -25, 177, -67, -26, + 177, -63, -27, 178, -59, -27, 179, -55, -28, 180, -51, -29, + 181, -46, -29, 182, -42, -30, 183, -38, -31, 184, -34, -31, + 185, -30, -32, 186, -26, -33, 187, -22, -33, 188, -18, -34, + 189, -13, -35, 190, -9, -35, 191, -5, -36, 191, -1, -37, + 192, 2, -37, 193, 6, -38, 194, 10, -39, 195, 14, -39, + 196, 18, -40, 197, 23, -41, 198, 27, -41, 199, 31, -42, + 172, -97, -23, 173, -93, -24, 174, -89, -24, 175, -84, -25, + 176, -80, -26, 177, -76, -26, 178, -72, -27, 179, -68, -28, + 180, -64, -28, 181, -60, -29, 182, -56, -30, 183, -52, -30, + 184, -47, -31, 184, -43, -32, 185, -39, -32, 186, -35, -33, + 187, -31, -34, 188, -27, -34, 189, -23, -35, 190, -19, -36, + 191, -14, -36, 192, -10, -37, 193, -6, -38, 194, -2, -38, + 195, 1, -39, 196, 5, -40, 197, 9, -40, 198, 13, -41, + 198, 17, -42, 200, 22, -42, 200, 26, -43, 201, 30, -44, + 175, -98, -25, 176, -94, -25, 176, -90, -26, 177, -86, -27, + 178, -82, -27, 179, -78, -28, 180, -74, -29, 181, -69, -29, + 182, -65, -30, 183, -61, -31, 184, -57, -31, 185, -53, -32, + 186, -49, -33, 187, -45, -33, 188, -41, -34, 189, -37, -35, + 190, -32, -35, 191, -28, -36, 191, -24, -37, 192, -20, -37, + 193, -16, -38, 194, -12, -39, 195, -8, -39, 196, -4, -40, + 197, 0, -41, 198, 4, -41, 199, 8, -42, 200, 12, -43, + 201, 16, -43, 202, 20, -44, 203, 24, -45, 204, 28, -45, + 177, -100, -26, 178, -96, -27, 179, -92, -28, 180, -87, -28, + 181, -83, -29, 182, -79, -30, 183, -75, -30, 184, -71, -31, + 184, -67, -32, 185, -63, -32, 186, -59, -33, 187, -55, -34, + 188, -50, -34, 189, -46, -35, 190, -42, -36, 191, -38, -36, + 192, -34, -37, 193, -30, -38, 194, -26, -38, 195, -22, -39, + 196, -17, -40, 197, -13, -40, 198, -9, -41, 199, -5, -42, + 199, -1, -42, 200, 2, -43, 201, 6, -44, 202, 10, -44, + 203, 14, -45, 204, 19, -46, 205, 23, -46, 206, 27, -47, + 179, -101, -28, 180, -97, -29, 181, -93, -29, 182, -88, -30, + 183, -84, -31, 184, -80, -31, 185, -76, -32, 186, -72, -33, + 187, -68, -33, 188, -64, -34, 189, -60, -35, 190, -56, -35, + 191, -51, -36, 192, -47, -37, 192, -43, -37, 193, -39, -38, + 194, -35, -39, 195, -31, -39, 196, -27, -40, 197, -23, -41, + 198, -18, -41, 199, -14, -42, 200, -10, -43, 201, -6, -43, + 202, -2, -44, 203, 1, -45, 204, 5, -45, 205, 9, -46, + 206, 13, -47, 207, 18, -47, 207, 22, -48, 208, 26, -49, + 182, -102, -30, 183, -98, -30, 183, -94, -31, 185, -90, -32, + 185, -86, -32, 186, -82, -33, 187, -78, -34, 188, -73, -34, + 189, -69, -35, 190, -65, -36, 191, -61, -36, 192, -57, -37, + 193, -53, -38, 194, -49, -38, 195, -45, -39, 196, -41, -40, + 197, -36, -40, 198, -32, -41, 199, -28, -42, 199, -24, -42, + 200, -20, -43, 201, -16, -44, 202, -12, -44, 203, -8, -45, + 204, -4, -46, 205, 0, -46, 206, 4, -47, 207, 8, -48, + 208, 12, -48, 209, 16, -49, 210, 20, -50, 211, 24, -50, + 184, -104, -31, 185, -100, -32, 186, -96, -33, 187, -91, -33, + 188, -87, -34, 189, -83, -35, 190, -79, -35, 191, -75, -36, + 192, -71, -37, 192, -67, -37, 193, -63, -38, 194, -59, -39, + 195, -54, -39, 196, -50, -40, 197, -46, -41, 198, -42, -41, + 199, -38, -42, 200, -34, -43, 201, -30, -43, 202, -26, -44, + 203, -21, -45, 204, -17, -45, 205, -13, -46, 206, -9, -47, + 206, -5, -47, 207, -1, -48, 208, 2, -49, 209, 6, -49, + 210, 10, -50, 211, 15, -51, 212, 19, -51, 213, 23, -52, + 186, -105, -33, 187, -101, -34, 188, -97, -34, 189, -92, -35, + 190, -88, -36, 191, -84, -36, 192, -80, -37, 193, -76, -38, + 194, -72, -38, 195, -68, -39, 196, -64, -40, 197, -60, -40, + 198, -55, -41, 199, -51, -42, 199, -47, -42, 200, -43, -43, + 201, -39, -44, 202, -35, -44, 203, -31, -45, 204, -27, -46, + 205, -22, -46, 206, -18, -47, 207, -14, -48, 208, -10, -48, + 209, -6, -49, 210, -2, -50, 211, 1, -50, 212, 5, -51, + 213, 9, -52, 214, 14, -52, 215, 18, -53, 215, 22, -54, + 189, -106, -35, 190, -102, -35, 191, -98, -36, 192, -94, -37, + 192, -90, -37, 193, -86, -38, 194, -82, -39, 195, -77, -39, + 196, -73, -40, 197, -69, -41, 198, -65, -41, 199, -61, -42, + 200, -57, -43, 201, -53, -43, 202, -49, -44, 203, -45, -45, + 204, -40, -45, 205, -36, -46, 206, -32, -47, 206, -28, -47, + 208, -24, -48, 208, -20, -49, 209, -16, -49, 210, -12, -50, + 211, -8, -51, 212, -3, -51, 213, 0, -52, 214, 4, -53, + 215, 8, -53, 216, 12, -54, 217, 16, -55, 218, 20, -55, + 191, -108, -36, 192, -104, -37, 193, -100, -38, 194, -95, -38, + 195, -91, -39, 196, -87, -40, 197, -83, -40, 198, -79, -41, + 199, -75, -42, 199, -71, -42, 200, -67, -43, 201, -63, -44, + 202, -58, -44, 203, -54, -45, 204, -50, -46, 205, -46, -46, + 206, -42, -47, 207, -38, -48, 208, -34, -48, 209, -30, -49, + 210, -25, -50, 211, -21, -50, 212, -17, -51, 213, -13, -52, + 214, -9, -52, 215, -5, -53, 215, -1, -54, 216, 2, -54, + 217, 6, -55, 218, 11, -56, 219, 15, -56, 220, 19, -57, + 44, -24, 74, 45, -20, 73, 46, -16, 72, 47, -12, 71, + 48, -8, 71, 48, -4, 70, 49, 0, 70, 50, 4, 69, + 51, 8, 68, 52, 12, 67, 53, 16, 67, 54, 20, 66, + 55, 24, 65, 56, 28, 65, 57, 32, 64, 58, 36, 63, + 59, 41, 63, 60, 45, 62, 61, 49, 61, 62, 53, 61, + 63, 57, 60, 63, 61, 59, 64, 65, 59, 65, 69, 58, + 66, 73, 57, 67, 78, 57, 68, 82, 56, 69, 86, 55, + 70, 90, 55, 71, 94, 54, 72, 98, 53, 73, 102, 53, + 46, -26, 72, 47, -22, 71, 48, -18, 71, 49, -13, 70, + 50, -9, 69, 51, -5, 68, 52, -1, 68, 53, 2, 67, + 54, 6, 66, 55, 10, 66, 55, 14, 65, 56, 18, 65, + 57, 23, 64, 58, 27, 63, 59, 31, 62, 60, 35, 62, + 61, 39, 61, 62, 43, 60, 63, 47, 60, 64, 51, 59, + 65, 56, 58, 66, 60, 58, 67, 64, 57, 68, 68, 56, + 69, 72, 56, 70, 76, 55, 70, 80, 54, 71, 84, 54, + 72, 88, 53, 73, 93, 52, 74, 97, 52, 75, 101, 51, + 48, -27, 70, 49, -23, 70, 50, -19, 69, 51, -15, 68, + 52, -11, 67, 53, -7, 67, 54, -3, 66, 55, 1, 65, + 56, 5, 65, 57, 9, 64, 58, 13, 63, 59, 17, 63, + 60, 21, 62, 61, 25, 61, 62, 29, 61, 62, 33, 60, + 63, 38, 59, 64, 42, 59, 65, 46, 58, 66, 50, 57, + 67, 54, 57, 68, 58, 56, 69, 62, 55, 70, 66, 55, + 71, 70, 54, 72, 75, 53, 73, 79, 53, 74, 83, 52, + 75, 87, 51, 76, 91, 51, 77, 95, 50, 78, 99, 49, + 51, -28, 68, 52, -24, 68, 53, -20, 67, 54, -16, 66, + 55, -12, 66, 55, -8, 65, 56, -4, 64, 57, 0, 64, + 58, 4, 63, 59, 8, 62, 60, 12, 62, 61, 16, 61, + 62, 20, 60, 63, 24, 60, 64, 28, 59, 65, 32, 58, + 66, 37, 58, 67, 41, 57, 68, 45, 56, 69, 49, 56, + 70, 53, 55, 71, 57, 54, 71, 61, 54, 72, 65, 53, + 73, 69, 52, 74, 74, 52, 75, 78, 51, 76, 82, 50, + 77, 86, 50, 78, 90, 49, 79, 94, 48, 80, 98, 48, + 53, -30, 67, 54, -26, 66, 55, -22, 66, 56, -17, 65, + 57, -13, 64, 58, -9, 63, 59, -5, 63, 60, -1, 62, + 61, 2, 61, 62, 6, 61, 62, 10, 60, 63, 14, 59, + 64, 19, 59, 65, 23, 58, 66, 27, 57, 67, 31, 57, + 68, 35, 56, 69, 39, 55, 70, 43, 55, 71, 47, 54, + 72, 52, 53, 73, 56, 53, 74, 60, 52, 75, 64, 51, + 76, 68, 51, 77, 72, 50, 78, 76, 49, 78, 80, 49, + 79, 84, 48, 80, 89, 47, 81, 93, 47, 82, 97, 46, + 55, -31, 65, 56, -27, 64, 57, -23, 64, 58, -19, 63, + 59, -15, 62, 60, -11, 62, 61, -7, 61, 62, -2, 60, + 63, 1, 60, 64, 5, 59, 65, 9, 58, 66, 13, 58, + 67, 17, 57, 68, 21, 56, 69, 25, 56, 70, 29, 55, + 71, 34, 54, 71, 38, 54, 72, 42, 53, 73, 46, 52, + 74, 50, 52, 75, 54, 51, 76, 58, 50, 77, 62, 50, + 78, 66, 49, 79, 71, 48, 80, 75, 48, 81, 79, 47, + 82, 83, 46, 83, 87, 46, 84, 91, 45, 85, 95, 44, + 58, -32, 63, 59, -28, 63, 60, -24, 62, 61, -20, 61, + 62, -16, 61, 63, -12, 60, 63, -8, 59, 64, -3, 59, + 65, 0, 58, 66, 4, 57, 67, 8, 57, 68, 12, 56, + 69, 16, 55, 70, 20, 55, 71, 24, 54, 72, 28, 53, + 73, 33, 53, 74, 37, 52, 75, 41, 51, 76, 45, 51, + 77, 49, 50, 78, 53, 49, 78, 57, 49, 79, 61, 48, + 80, 65, 47, 81, 70, 47, 82, 74, 46, 83, 78, 45, + 84, 82, 45, 85, 86, 44, 86, 90, 43, 87, 94, 43, + 60, -34, 62, 61, -30, 61, 62, -26, 60, 63, -21, 60, + 64, -17, 59, 65, -13, 58, 66, -9, 58, 67, -5, 57, + 68, -1, 56, 69, 2, 56, 70, 6, 55, 70, 10, 54, + 71, 15, 54, 72, 19, 53, 73, 23, 52, 74, 27, 52, + 75, 31, 51, 76, 35, 50, 77, 39, 50, 78, 43, 49, + 79, 48, 48, 80, 52, 48, 81, 56, 47, 82, 60, 46, + 83, 64, 46, 84, 68, 45, 85, 72, 44, 85, 76, 44, + 86, 80, 43, 87, 85, 42, 88, 89, 42, 89, 93, 41, + 63, -35, 60, 63, -31, 59, 64, -27, 59, 65, -23, 58, + 66, -19, 57, 67, -15, 57, 68, -11, 56, 69, -6, 55, + 70, -2, 55, 71, 1, 54, 72, 5, 53, 73, 9, 53, + 74, 13, 52, 75, 17, 51, 76, 21, 51, 77, 25, 50, + 78, 30, 49, 78, 34, 49, 79, 38, 48, 80, 42, 47, + 81, 46, 47, 82, 50, 46, 83, 54, 45, 84, 58, 45, + 85, 62, 44, 86, 67, 43, 87, 71, 43, 88, 75, 42, + 89, 79, 41, 90, 83, 41, 91, 87, 40, 92, 91, 39, + 65, -36, 58, 66, -32, 58, 67, -28, 57, 68, -24, 56, + 69, -20, 56, 70, -16, 55, 70, -12, 54, 71, -7, 54, + 72, -3, 53, 73, 0, 52, 74, 4, 52, 75, 8, 51, + 76, 12, 50, 77, 16, 50, 78, 20, 49, 79, 24, 48, + 80, 29, 48, 81, 33, 47, 82, 37, 46, 83, 41, 46, + 84, 45, 45, 85, 49, 44, 86, 53, 44, 86, 57, 43, + 87, 61, 42, 88, 66, 42, 89, 70, 41, 90, 74, 40, + 91, 78, 40, 92, 82, 39, 93, 86, 38, 94, 90, 38, + 67, -38, 57, 68, -34, 56, 69, -30, 55, 70, -25, 55, + 71, -21, 54, 72, -17, 53, 73, -13, 53, 74, -9, 52, + 75, -5, 51, 76, -1, 51, 77, 2, 50, 77, 6, 49, + 79, 11, 49, 79, 15, 48, 80, 19, 47, 81, 23, 47, + 82, 27, 46, 83, 31, 45, 84, 35, 45, 85, 39, 44, + 86, 44, 43, 87, 48, 43, 88, 52, 42, 89, 56, 41, + 90, 60, 41, 91, 64, 40, 92, 68, 39, 93, 72, 39, + 93, 76, 38, 94, 81, 37, 95, 85, 37, 96, 89, 36, + 70, -39, 55, 71, -35, 54, 72, -31, 53, 73, -27, 53, + 74, -23, 52, 75, -19, 51, 76, -15, 51, 77, -10, 50, + 78, -6, 49, 79, -2, 49, 80, 1, 48, 80, 5, 47, + 81, 9, 47, 82, 13, 46, 83, 17, 45, 84, 21, 45, + 85, 26, 44, 86, 30, 43, 87, 34, 43, 88, 38, 42, + 89, 42, 41, 90, 46, 41, 91, 50, 40, 92, 54, 39, + 93, 58, 39, 94, 63, 38, 95, 67, 37, 95, 71, 37, + 96, 75, 36, 97, 79, 35, 98, 83, 35, 99, 87, 34, + 73, -41, 53, 73, -37, 52, 74, -33, 52, 75, -28, 51, + 76, -24, 50, 77, -20, 50, 78, -16, 49, 79, -12, 48, + 80, -8, 48, 81, -4, 47, 82, 0, 46, 83, 3, 46, + 84, 8, 45, 85, 12, 44, 86, 16, 44, 87, 20, 43, + 88, 24, 42, 88, 28, 42, 89, 32, 41, 90, 36, 40, + 91, 41, 40, 92, 45, 39, 93, 49, 38, 94, 53, 38, + 95, 57, 37, 96, 61, 36, 97, 65, 36, 98, 69, 35, + 99, 73, 34, 100, 78, 34, 101, 82, 33, 102, 86, 32, + 75, -42, 51, 76, -38, 51, 77, -34, 50, 78, -30, 49, + 79, -26, 49, 80, -22, 48, 80, -18, 47, 81, -13, 47, + 82, -9, 46, 83, -5, 45, 84, -1, 45, 85, 2, 44, + 86, 6, 43, 87, 10, 43, 88, 14, 42, 89, 18, 41, + 90, 23, 41, 91, 27, 40, 92, 31, 39, 93, 35, 39, + 94, 39, 38, 95, 43, 37, 95, 47, 37, 96, 51, 36, + 97, 55, 35, 98, 60, 35, 99, 64, 34, 100, 68, 33, + 101, 72, 33, 102, 76, 32, 103, 80, 31, 104, 84, 31, + 77, -43, 50, 78, -39, 49, 79, -35, 48, 80, -31, 48, + 81, -27, 47, 82, -23, 46, 83, -19, 46, 84, -14, 45, + 85, -10, 44, 86, -6, 44, 87, -2, 43, 87, 1, 42, + 88, 5, 42, 89, 9, 41, 90, 13, 40, 91, 17, 40, + 92, 22, 39, 93, 26, 38, 94, 30, 38, 95, 34, 37, + 96, 38, 36, 97, 42, 36, 98, 46, 35, 99, 50, 34, + 100, 54, 34, 101, 59, 33, 102, 63, 32, 103, 67, 32, + 103, 71, 31, 104, 75, 30, 105, 79, 30, 106, 83, 29, + 80, -45, 48, 80, -41, 47, 81, -37, 47, 82, -32, 46, + 83, -28, 45, 84, -24, 45, 85, -20, 44, 86, -16, 43, + 87, -12, 43, 88, -8, 42, 89, -4, 41, 90, 0, 41, + 91, 4, 40, 92, 8, 39, 93, 12, 39, 94, 16, 38, + 95, 20, 37, 96, 24, 37, 96, 28, 36, 97, 32, 35, + 98, 37, 35, 99, 41, 34, 100, 45, 33, 101, 49, 33, + 102, 53, 32, 103, 57, 31, 104, 61, 31, 105, 65, 30, + 106, 69, 29, 107, 74, 29, 108, 78, 28, 109, 82, 27, + 82, -46, 46, 83, -42, 46, 84, -38, 45, 85, -34, 44, + 86, -30, 44, 87, -26, 43, 87, -22, 42, 89, -17, 42, + 89, -13, 41, 90, -9, 40, 91, -5, 40, 92, -1, 39, + 93, 2, 38, 94, 6, 38, 95, 10, 37, 96, 14, 36, + 97, 19, 36, 98, 23, 35, 99, 27, 34, 100, 31, 34, + 101, 35, 33, 102, 39, 32, 103, 43, 32, 103, 47, 31, + 104, 51, 30, 105, 56, 30, 106, 60, 29, 107, 64, 28, + 108, 68, 28, 109, 72, 27, 110, 76, 26, 111, 80, 26, + 84, -47, 45, 85, -43, 44, 86, -39, 43, 87, -35, 43, + 88, -31, 42, 89, -27, 41, 90, -23, 41, 91, -18, 40, + 92, -14, 39, 93, -10, 39, 94, -6, 38, 95, -2, 37, + 96, 1, 37, 96, 5, 36, 97, 9, 35, 98, 13, 35, + 99, 18, 34, 100, 22, 33, 101, 26, 33, 102, 30, 32, + 103, 34, 31, 104, 38, 31, 105, 42, 30, 106, 46, 29, + 107, 50, 29, 108, 55, 28, 109, 59, 27, 110, 63, 27, + 110, 67, 26, 112, 71, 25, 112, 75, 25, 113, 79, 24, + 87, -49, 43, 88, -45, 42, 88, -41, 42, 89, -36, 41, + 90, -32, 40, 91, -28, 40, 92, -24, 39, 93, -20, 38, + 94, -16, 38, 95, -12, 37, 96, -8, 36, 97, -4, 36, + 98, 0, 35, 99, 4, 34, 100, 8, 34, 101, 12, 33, + 102, 16, 32, 103, 20, 32, 103, 24, 31, 104, 28, 30, + 105, 33, 30, 106, 37, 29, 107, 41, 28, 108, 45, 28, + 109, 49, 27, 110, 53, 26, 111, 57, 26, 112, 61, 25, + 113, 65, 24, 114, 70, 24, 115, 74, 23, 116, 78, 22, + 89, -50, 41, 90, -46, 41, 91, -42, 40, 92, -37, 39, + 93, -33, 39, 94, -29, 38, 95, -25, 37, 96, -21, 37, + 96, -17, 36, 97, -13, 35, 98, -9, 35, 99, -5, 34, + 100, 0, 33, 101, 3, 33, 102, 7, 32, 103, 11, 31, + 104, 15, 31, 105, 19, 30, 106, 23, 29, 107, 27, 29, + 108, 32, 28, 109, 36, 27, 110, 40, 27, 110, 44, 26, + 111, 48, 25, 112, 52, 25, 113, 56, 24, 114, 60, 23, + 115, 64, 23, 116, 69, 22, 117, 73, 21, 118, 77, 21, + 91, -51, 40, 92, -47, 39, 93, -43, 38, 94, -39, 38, + 95, -35, 37, 96, -31, 36, 97, -27, 36, 98, -22, 35, + 99, -18, 34, 100, -14, 34, 101, -10, 33, 102, -6, 32, + 103, -2, 32, 103, 1, 31, 104, 5, 30, 105, 9, 30, + 106, 14, 29, 107, 18, 28, 108, 22, 28, 109, 26, 27, + 110, 30, 26, 111, 34, 26, 112, 38, 25, 113, 42, 24, + 114, 46, 24, 115, 51, 23, 116, 55, 22, 117, 59, 22, + 118, 63, 21, 119, 67, 20, 119, 71, 20, 120, 75, 19, + 94, -53, 38, 95, -49, 37, 95, -45, 37, 96, -40, 36, + 97, -36, 35, 98, -32, 35, 99, -28, 34, 100, -24, 33, + 101, -20, 33, 102, -16, 32, 103, -12, 31, 104, -8, 31, + 105, -3, 30, 106, 0, 29, 107, 4, 29, 108, 8, 28, + 109, 12, 27, 110, 16, 27, 111, 20, 26, 111, 24, 25, + 112, 29, 24, 113, 33, 24, 114, 37, 23, 115, 41, 23, + 116, 45, 22, 117, 49, 21, 118, 53, 21, 119, 57, 20, + 120, 61, 19, 121, 66, 18, 122, 70, 18, 123, 74, 17, + 96, -54, 36, 97, -50, 36, 98, -46, 35, 99, -41, 34, + 100, -37, 34, 101, -33, 33, 102, -29, 32, 103, -25, 32, + 104, -21, 31, 104, -17, 30, 105, -13, 30, 106, -9, 29, + 107, -4, 28, 108, 0, 28, 109, 3, 27, 110, 7, 26, + 111, 11, 26, 112, 15, 25, 113, 19, 24, 114, 23, 24, + 115, 28, 23, 116, 32, 22, 117, 36, 22, 118, 40, 21, + 118, 44, 20, 119, 48, 19, 120, 52, 19, 121, 56, 18, + 122, 60, 18, 123, 65, 17, 124, 69, 16, 125, 73, 16, + 98, -55, 35, 99, -51, 34, 100, -47, 33, 101, -43, 33, + 102, -39, 32, 103, -35, 31, 104, -31, 31, 105, -26, 30, + 106, -22, 29, 107, -18, 29, 108, -14, 28, 109, -10, 27, + 110, -6, 27, 111, -2, 26, 111, 1, 25, 112, 5, 25, + 113, 10, 24, 114, 14, 23, 115, 18, 23, 116, 22, 22, + 117, 26, 21, 118, 30, 20, 119, 34, 20, 120, 38, 19, + 121, 42, 19, 122, 47, 18, 123, 51, 17, 124, 55, 17, + 125, 59, 16, 126, 63, 15, 127, 67, 14, 127, 71, 14, + 101, -57, 33, 102, -53, 32, 103, -49, 32, 104, -44, 31, + 104, -40, 30, 105, -36, 30, 106, -32, 29, 107, -28, 28, + 108, -24, 28, 109, -20, 27, 110, -16, 26, 111, -12, 26, + 112, -7, 25, 113, -3, 24, 114, 0, 24, 115, 4, 23, + 116, 8, 22, 117, 12, 22, 118, 16, 21, 118, 20, 20, + 120, 25, 19, 120, 29, 19, 121, 33, 18, 122, 37, 18, + 123, 41, 17, 124, 45, 16, 125, 49, 15, 126, 53, 15, + 127, 57, 14, 128, 62, 13, 129, 66, 13, 130, 70, 12, + 103, -58, 31, 104, -54, 31, 105, -50, 30, 106, -45, 29, + 107, -41, 29, 108, -37, 28, 109, -33, 27, 110, -29, 26, + 111, -25, 26, 111, -21, 25, 112, -17, 25, 113, -13, 24, + 114, -8, 23, 115, -4, 23, 116, 0, 22, 117, 3, 21, + 118, 7, 20, 119, 11, 20, 120, 15, 19, 121, 19, 19, + 122, 24, 18, 123, 28, 17, 124, 32, 16, 125, 36, 16, + 125, 40, 15, 127, 44, 14, 127, 48, 14, 128, 52, 13, + 129, 56, 13, 130, 61, 12, 131, 65, 11, 132, 69, 10, + 105, -59, 30, 106, -55, 29, 107, -51, 28, 108, -47, 28, + 109, -43, 27, 110, -39, 26, 111, -35, 26, 112, -30, 25, + 113, -26, 24, 114, -22, 24, 115, -18, 23, 116, -14, 22, + 117, -10, 21, 118, -6, 21, 118, -2, 20, 119, 1, 20, + 120, 6, 19, 121, 10, 18, 122, 14, 18, 123, 18, 17, + 124, 22, 16, 125, 26, 15, 126, 30, 15, 127, 34, 14, + 128, 38, 14, 129, 43, 13, 130, 47, 12, 131, 51, 11, + 132, 55, 11, 133, 59, 10, 134, 63, 9, 134, 67, 9, + 108, -61, 28, 109, -57, 27, 110, -53, 27, 111, -48, 26, + 111, -44, 25, 112, -40, 25, 113, -36, 24, 114, -32, 23, + 115, -28, 22, 116, -24, 22, 117, -20, 21, 118, -16, 21, + 119, -11, 20, 120, -7, 19, 121, -3, 19, 122, 0, 18, + 123, 4, 17, 124, 8, 16, 125, 12, 16, 126, 16, 15, + 127, 21, 14, 127, 25, 14, 128, 29, 13, 129, 33, 12, + 130, 37, 12, 131, 41, 11, 132, 45, 10, 133, 49, 10, + 134, 53, 9, 135, 58, 8, 136, 62, 8, 137, 66, 7, + 110, -62, 26, 111, -58, 26, 112, -54, 25, 113, -49, 24, + 114, -45, 24, 115, -41, 23, 116, -37, 22, 117, -33, 21, + 118, -29, 21, 119, -25, 20, 119, -21, 20, 120, -17, 19, + 121, -12, 18, 122, -8, 17, 123, -4, 17, 124, 0, 16, + 125, 3, 15, 126, 7, 15, 127, 11, 14, 128, 15, 14, + 129, 20, 13, 130, 24, 12, 131, 28, 11, 132, 32, 11, + 133, 36, 10, 134, 40, 9, 134, 44, 9, 135, 48, 8, + 136, 52, 7, 137, 57, 7, 138, 61, 6, 139, 65, 5, + 112, -63, 25, 113, -59, 24, 114, -55, 23, 115, -51, 22, + 116, -47, 22, 117, -43, 21, 118, -39, 21, 119, -34, 20, + 120, -30, 19, 121, -26, 18, 122, -22, 18, 123, -18, 17, + 124, -14, 16, 125, -10, 16, 126, -6, 15, 126, -2, 15, + 127, 2, 14, 128, 6, 13, 129, 10, 12, 130, 14, 12, + 131, 18, 11, 132, 22, 10, 133, 26, 10, 134, 30, 9, + 135, 34, 8, 136, 39, 8, 137, 43, 7, 138, 47, 6, + 139, 51, 6, 140, 55, 5, 141, 59, 4, 142, 63, 4, + 115, -65, 23, 116, -61, 22, 117, -57, 22, 118, -52, 21, + 119, -48, 20, 119, -44, 20, 120, -40, 19, 121, -36, 18, + 122, -32, 17, 123, -28, 17, 124, -24, 16, 125, -20, 16, + 126, -15, 15, 127, -11, 14, 128, -7, 13, 129, -3, 13, + 130, 0, 12, 131, 4, 11, 132, 8, 11, 133, 12, 10, + 134, 17, 9, 135, 21, 9, 135, 25, 8, 136, 29, 7, + 137, 33, 7, 138, 37, 6, 139, 41, 5, 140, 45, 5, + 141, 49, 4, 142, 54, 3, 143, 58, 3, 144, 62, 2, + 117, -66, 21, 118, -62, 21, 119, -58, 20, 120, -53, 19, + 121, -49, 18, 122, -45, 18, 123, -41, 17, 124, -37, 16, + 125, -33, 16, 126, -29, 15, 126, -25, 14, 127, -21, 14, + 128, -16, 13, 129, -12, 12, 130, -8, 12, 131, -4, 11, + 132, 0, 10, 133, 3, 10, 134, 7, 9, 135, 11, 8, + 136, 16, 8, 137, 20, 7, 138, 24, 6, 139, 28, 6, + 140, 32, 5, 141, 36, 4, 142, 40, 4, 142, 44, 3, + 143, 48, 2, 144, 53, 2, 145, 57, 1, 146, 61, 0, + 120, -68, 19, 121, -64, 18, 122, -60, 18, 123, -55, 17, + 124, -51, 16, 125, -47, 16, 126, -43, 15, 127, -39, 14, + 128, -35, 14, 128, -31, 13, 129, -27, 12, 130, -23, 12, + 131, -18, 11, 132, -14, 10, 133, -10, 10, 134, -6, 9, + 135, -2, 8, 136, 1, 8, 137, 5, 7, 138, 9, 6, + 139, 14, 6, 140, 18, 5, 141, 22, 4, 142, 26, 4, + 143, 30, 3, 144, 34, 2, 144, 38, 2, 145, 42, 1, + 146, 46, 0, 147, 51, 0, 148, 55, 0, 149, 59, -1, + 122, -69, 17, 123, -65, 17, 124, -61, 16, 125, -56, 15, + 126, -52, 15, 127, -48, 14, 128, -44, 13, 129, -40, 13, + 130, -36, 12, 131, -32, 11, 132, -28, 11, 133, -24, 10, + 134, -19, 9, 135, -15, 9, 136, -11, 8, 136, -7, 7, + 137, -3, 7, 138, 0, 6, 139, 4, 5, 140, 8, 5, + 141, 13, 4, 142, 17, 3, 143, 21, 3, 144, 25, 2, + 145, 29, 1, 146, 33, 1, 147, 37, 0, 148, 41, 0, + 149, 45, 0, 150, 50, -1, 151, 54, -2, 151, 58, -2, + 125, -70, 16, 126, -66, 15, 127, -62, 14, 128, -58, 14, + 129, -54, 13, 129, -50, 12, 130, -46, 12, 131, -41, 11, + 132, -37, 10, 133, -33, 10, 134, -29, 9, 135, -25, 8, + 136, -21, 8, 137, -17, 7, 138, -13, 6, 139, -9, 6, + 140, -4, 5, 141, 0, 4, 142, 3, 4, 143, 7, 3, + 144, 11, 2, 144, 15, 2, 145, 19, 1, 146, 23, 0, + 147, 27, 0, 148, 32, 0, 149, 36, -1, 150, 40, -1, + 151, 44, -2, 152, 48, -3, 153, 52, -3, 154, 56, -4, + 127, -72, 14, 128, -68, 13, 129, -64, 13, 130, -59, 12, + 131, -55, 11, 132, -51, 11, 133, -47, 10, 134, -43, 9, + 135, -39, 9, 136, -35, 8, 136, -31, 7, 137, -27, 7, + 138, -22, 6, 139, -18, 5, 140, -14, 5, 141, -10, 4, + 142, -6, 3, 143, -2, 3, 144, 1, 2, 145, 5, 1, + 146, 10, 1, 147, 14, 0, 148, 18, 0, 149, 22, 0, + 150, 26, -1, 151, 30, -2, 152, 34, -2, 152, 38, -3, + 153, 42, -4, 154, 47, -4, 155, 51, -5, 156, 55, -6, + 129, -73, 12, 130, -69, 12, 131, -65, 11, 132, -60, 10, + 133, -56, 10, 134, -52, 9, 135, -48, 8, 136, -44, 8, + 137, -40, 7, 138, -36, 6, 139, -32, 6, 140, -28, 5, + 141, -23, 4, 142, -19, 4, 143, -15, 3, 143, -11, 2, + 145, -7, 2, 145, -3, 1, 146, 0, 0, 147, 4, 0, + 148, 9, 0, 149, 13, -1, 150, 17, -1, 151, 21, -2, + 152, 25, -3, 153, 29, -3, 154, 33, -4, 155, 37, -5, + 156, 41, -5, 157, 46, -6, 158, 50, -7, 159, 54, -7, + 132, -74, 11, 133, -70, 10, 134, -66, 9, 135, -62, 9, + 136, -58, 8, 136, -54, 7, 137, -50, 7, 138, -45, 6, + 139, -41, 5, 140, -37, 5, 141, -33, 4, 142, -29, 3, + 143, -25, 3, 144, -21, 2, 145, -17, 1, 146, -13, 1, + 147, -8, 0, 148, -4, 0, 149, 0, 0, 150, 3, -1, + 151, 7, -2, 152, 11, -2, 152, 15, -3, 153, 19, -4, + 154, 23, -4, 155, 28, -5, 156, 32, -6, 157, 36, -6, + 158, 40, -7, 159, 44, -8, 160, 48, -8, 161, 52, -9, + 134, -75, 9, 135, -71, 8, 136, -67, 8, 137, -63, 7, + 138, -59, 6, 139, -55, 6, 140, -51, 5, 141, -46, 4, + 142, -42, 4, 143, -38, 3, 143, -34, 2, 144, -30, 2, + 145, -26, 1, 146, -22, 0, 147, -18, 0, 148, -14, 0, + 149, -9, -1, 150, -5, -1, 151, -1, -2, 152, 2, -3, + 153, 6, -3, 154, 10, -4, 155, 14, -5, 156, 18, -5, + 157, 22, -6, 158, 27, -7, 159, 31, -7, 159, 35, -8, + 160, 39, -9, 161, 43, -9, 162, 47, -10, 163, 51, -11, + 136, -77, 7, 137, -73, 7, 138, -69, 6, 139, -64, 5, + 140, -60, 5, 141, -56, 4, 142, -52, 3, 143, -48, 3, + 144, -44, 2, 145, -40, 1, 146, -36, 1, 147, -32, 0, + 148, -27, 0, 149, -23, 0, 150, -19, -1, 151, -15, -2, + 152, -11, -2, 152, -7, -3, 153, -3, -4, 154, 0, -4, + 155, 5, -5, 156, 9, -6, 157, 13, -6, 158, 17, -7, + 159, 21, -8, 160, 25, -8, 161, 29, -9, 162, 33, -10, + 163, 37, -10, 164, 42, -11, 165, 46, -12, 166, 50, -12, + 139, -78, 6, 140, -74, 5, 141, -70, 4, 142, -66, 4, + 143, -62, 3, 144, -58, 2, 144, -54, 2, 145, -49, 1, + 146, -45, 0, 147, -41, 0, 148, -37, 0, 149, -33, -1, + 150, -29, -1, 151, -25, -2, 152, -21, -3, 153, -17, -3, + 154, -12, -4, 155, -8, -5, 156, -4, -5, 157, 0, -6, + 158, 3, -7, 159, 7, -7, 159, 11, -8, 160, 15, -9, + 161, 19, -9, 162, 24, -10, 163, 28, -11, 164, 32, -11, + 165, 36, -12, 166, 40, -13, 167, 44, -13, 168, 48, -14, + 141, -79, 4, 142, -75, 3, 143, -71, 3, 144, -67, 2, + 145, -63, 1, 146, -59, 1, 147, -55, 0, 148, -50, 0, + 149, -46, 0, 150, -42, -1, 151, -38, -2, 151, -34, -2, + 152, -30, -3, 153, -26, -4, 154, -22, -4, 155, -18, -5, + 156, -13, -6, 157, -9, -6, 158, -5, -7, 159, -1, -8, + 160, 2, -8, 161, 6, -9, 162, 10, -10, 163, 14, -10, + 164, 18, -11, 165, 23, -12, 166, 27, -12, 167, 31, -13, + 167, 35, -14, 168, 39, -14, 169, 43, -15, 170, 47, -16, + 144, -81, 2, 144, -77, 2, 145, -73, 1, 146, -68, 0, + 147, -64, 0, 148, -60, 0, 149, -56, -1, 150, -52, -1, + 151, -48, -2, 152, -44, -3, 153, -40, -3, 154, -36, -4, + 155, -31, -5, 156, -27, -5, 157, -23, -6, 158, -19, -7, + 159, -15, -7, 160, -11, -8, 160, -7, -9, 161, -3, -9, + 162, 1, -10, 163, 5, -11, 164, 9, -11, 165, 13, -12, + 166, 17, -13, 167, 21, -13, 168, 25, -14, 169, 29, -15, + 170, 33, -15, 171, 38, -16, 172, 42, -17, 173, 46, -17, + 146, -82, 1, 147, -78, 0, 148, -74, 0, 149, -70, 0, + 150, -66, -1, 151, -62, -2, 151, -58, -2, 153, -53, -3, + 153, -49, -4, 154, -45, -4, 155, -41, -5, 156, -37, -6, + 157, -33, -6, 158, -29, -7, 159, -25, -8, 160, -21, -8, + 161, -16, -9, 162, -12, -10, 163, -8, -10, 164, -4, -11, + 165, 0, -12, 166, 3, -12, 167, 7, -13, 167, 11, -14, + 168, 15, -14, 169, 20, -15, 170, 24, -16, 171, 28, -16, + 172, 32, -17, 173, 36, -18, 174, 40, -18, 175, 44, -19, + 148, -83, 0, 149, -79, -1, 150, -75, -1, 151, -71, -2, + 152, -67, -3, 153, -63, -3, 154, -59, -4, 155, -54, -5, + 156, -50, -5, 157, -46, -6, 158, -42, -7, 158, -38, -7, + 160, -34, -8, 160, -30, -9, 161, -26, -9, 162, -22, -10, + 163, -17, -11, 164, -13, -11, 165, -9, -12, 166, -5, -13, + 167, -1, -13, 168, 2, -14, 169, 6, -15, 170, 10, -15, + 171, 14, -16, 172, 19, -17, 173, 23, -17, 174, 27, -18, + 174, 31, -19, 175, 35, -19, 176, 39, -20, 177, 43, -21, + 151, -85, -2, 151, -81, -2, 152, -77, -3, 153, -72, -4, + 154, -68, -4, 155, -64, -5, 156, -60, -6, 157, -56, -6, + 158, -52, -7, 159, -48, -8, 160, -44, -8, 161, -40, -9, + 162, -35, -10, 163, -31, -10, 164, -27, -11, 165, -23, -12, + 166, -19, -12, 167, -15, -13, 167, -11, -14, 168, -7, -14, + 169, -2, -15, 170, 1, -16, 171, 5, -16, 172, 9, -17, + 173, 13, -18, 174, 17, -18, 175, 21, -19, 176, 25, -20, + 177, 29, -20, 178, 34, -21, 179, 38, -22, 180, 42, -22, + 153, -86, -3, 154, -82, -4, 155, -78, -5, 156, -74, -5, + 157, -70, -6, 158, -66, -7, 159, -62, -7, 160, -57, -8, + 160, -53, -9, 161, -49, -9, 162, -45, -10, 163, -41, -11, + 164, -37, -11, 165, -33, -12, 166, -29, -13, 167, -25, -13, + 168, -20, -14, 169, -16, -15, 170, -12, -15, 171, -8, -16, + 172, -4, -17, 173, 0, -17, 174, 3, -18, 174, 7, -19, + 175, 11, -19, 176, 16, -20, 177, 20, -21, 178, 24, -21, + 179, 28, -22, 180, 32, -23, 181, 36, -23, 182, 40, -24, + 155, -87, -5, 156, -83, -6, 157, -79, -6, 158, -75, -7, + 159, -71, -8, 160, -67, -8, 161, -63, -9, 162, -58, -10, + 163, -54, -10, 164, -50, -11, 165, -46, -12, 166, -42, -12, + 167, -38, -13, 167, -34, -14, 168, -30, -14, 169, -26, -15, + 170, -21, -16, 171, -17, -16, 172, -13, -17, 173, -9, -18, + 174, -5, -18, 175, -1, -19, 176, 2, -20, 177, 6, -20, + 178, 10, -21, 179, 15, -22, 180, 19, -22, 181, 23, -23, + 182, 27, -24, 183, 31, -24, 183, 35, -25, 184, 39, -26, + 158, -89, -7, 159, -85, -7, 159, -81, -8, 160, -76, -9, + 161, -72, -9, 162, -68, -10, 163, -64, -11, 164, -60, -11, + 165, -56, -12, 166, -52, -13, 167, -48, -13, 168, -44, -14, + 169, -39, -15, 170, -35, -15, 171, -31, -16, 172, -27, -17, + 173, -23, -17, 174, -19, -18, 175, -15, -19, 175, -11, -19, + 176, -6, -20, 177, -2, -21, 178, 1, -21, 179, 5, -22, + 180, 9, -23, 181, 13, -23, 182, 17, -24, 183, 21, -25, + 184, 25, -25, 185, 30, -26, 186, 34, -27, 187, 38, -27, + 160, -90, -8, 161, -86, -9, 162, -82, -10, 163, -78, -10, + 164, -74, -11, 165, -70, -12, 166, -66, -12, 167, -61, -13, + 168, -57, -14, 168, -53, -14, 169, -49, -15, 170, -45, -16, + 171, -41, -16, 172, -37, -17, 173, -33, -18, 174, -29, -18, + 175, -24, -19, 176, -20, -20, 177, -16, -20, 178, -12, -21, + 179, -8, -22, 180, -4, -22, 181, 0, -23, 182, 3, -24, + 182, 7, -24, 183, 12, -25, 184, 16, -26, 185, 20, -26, + 186, 24, -27, 187, 28, -28, 188, 32, -28, 189, 36, -29, + 162, -91, -10, 163, -87, -11, 164, -83, -11, 165, -79, -12, + 166, -75, -13, 167, -71, -13, 168, -67, -14, 169, -62, -15, + 170, -58, -15, 171, -54, -16, 172, -50, -17, 173, -46, -17, + 174, -42, -18, 175, -38, -19, 175, -34, -19, 176, -30, -20, + 177, -25, -21, 178, -21, -21, 179, -17, -22, 180, -13, -23, + 181, -9, -23, 182, -5, -24, 183, -1, -25, 184, 2, -25, + 185, 6, -26, 186, 11, -27, 187, 15, -27, 188, 19, -28, + 189, 23, -29, 190, 27, -30, 190, 31, -30, 191, 35, -31, + 165, -93, -12, 166, -89, -12, 166, -85, -13, 168, -80, -14, + 168, -76, -14, 169, -72, -15, 170, -68, -16, 171, -64, -16, + 172, -60, -17, 173, -56, -18, 174, -52, -18, 175, -48, -19, + 176, -43, -20, 177, -39, -20, 178, -35, -21, 179, -31, -22, + 180, -27, -22, 181, -23, -23, 182, -19, -24, 182, -15, -24, + 183, -10, -25, 184, -6, -26, 185, -2, -26, 186, 1, -27, + 187, 5, -28, 188, 9, -29, 189, 13, -29, 190, 17, -30, + 191, 21, -30, 192, 26, -31, 193, 30, -32, 194, 34, -32, + 167, -94, -13, 168, -90, -14, 169, -86, -15, 170, -82, -15, + 171, -78, -16, 172, -74, -17, 173, -70, -17, 174, -65, -18, + 175, -61, -19, 175, -57, -19, 176, -53, -20, 177, -49, -21, + 178, -45, -21, 179, -41, -22, 180, -37, -23, 181, -33, -23, + 182, -28, -24, 183, -24, -25, 184, -20, -25, 185, -16, -26, + 186, -12, -27, 187, -8, -27, 188, -4, -28, 189, 0, -29, + 189, 3, -29, 191, 8, -30, 191, 12, -31, 192, 16, -31, + 193, 20, -32, 194, 24, -33, 195, 28, -34, 196, 32, -34, + 170, -96, -16, 171, -92, -16, 172, -88, -17, 173, -83, -18, + 174, -79, -18, 175, -75, -19, 176, -71, -20, 177, -67, -20, + 177, -63, -21, 178, -59, -22, 179, -55, -22, 180, -51, -23, + 181, -46, -24, 182, -42, -24, 183, -38, -25, 184, -34, -26, + 185, -30, -26, 186, -26, -27, 187, -22, -28, 188, -18, -28, + 189, -13, -29, 190, -9, -30, 191, -5, -30, 192, -1, -31, + 192, 2, -32, 193, 6, -32, 194, 10, -33, 195, 14, -34, + 196, 18, -34, 197, 23, -35, 198, 27, -36, 199, 31, -36, + 172, -97, -17, 173, -93, -18, 174, -89, -18, 175, -85, -19, + 176, -81, -20, 177, -77, -21, 178, -73, -21, 179, -68, -22, + 180, -64, -23, 181, -60, -23, 182, -56, -24, 183, -52, -25, + 184, -48, -25, 185, -44, -26, 185, -40, -27, 186, -36, -27, + 187, -31, -28, 188, -27, -29, 189, -23, -29, 190, -19, -30, + 191, -15, -31, 192, -11, -31, 193, -7, -32, 194, -3, -33, + 195, 0, -33, 196, 5, -34, 197, 9, -35, 198, 13, -35, + 199, 17, -36, 200, 21, -37, 200, 25, -37, 201, 29, -38, + 175, -98, -19, 176, -94, -20, 176, -90, -20, 178, -86, -21, + 178, -82, -22, 179, -78, -22, 180, -74, -23, 181, -69, -24, + 182, -65, -24, 183, -61, -25, 184, -57, -26, 185, -53, -26, + 186, -49, -27, 187, -45, -28, 188, -41, -28, 189, -37, -29, + 190, -32, -30, 191, -28, -30, 192, -24, -31, 192, -20, -32, + 193, -16, -32, 194, -12, -33, 195, -8, -34, 196, -4, -34, + 197, 0, -35, 198, 4, -36, 199, 8, -36, 200, 12, -37, + 201, 16, -38, 202, 20, -38, 203, 24, -39, 204, 28, -40, + 177, -100, -21, 178, -96, -21, 179, -92, -22, 180, -87, -23, + 181, -83, -23, 182, -79, -24, 183, -75, -25, 184, -71, -25, + 185, -67, -26, 185, -63, -27, 186, -59, -27, 187, -55, -28, + 188, -50, -29, 189, -46, -29, 190, -42, -30, 191, -38, -31, + 192, -34, -31, 193, -30, -32, 194, -26, -33, 195, -22, -33, + 196, -17, -34, 197, -13, -35, 198, -9, -35, 199, -5, -36, + 199, -1, -37, 200, 2, -37, 201, 6, -38, 202, 10, -39, + 203, 14, -39, 204, 19, -40, 205, 23, -41, 206, 27, -41, + 179, -101, -22, 180, -97, -23, 181, -93, -24, 182, -88, -24, + 183, -84, -25, 184, -80, -26, 185, -76, -26, 186, -72, -27, + 187, -68, -28, 188, -64, -28, 189, -60, -29, 190, -56, -30, + 191, -51, -30, 192, -47, -31, 192, -43, -32, 193, -39, -32, + 194, -35, -33, 195, -31, -34, 196, -27, -34, 197, -23, -35, + 198, -18, -36, 199, -14, -36, 200, -10, -37, 201, -6, -38, + 202, -2, -38, 203, 1, -39, 204, 5, -40, 205, 9, -40, + 206, 13, -41, 207, 18, -42, 208, 22, -42, 208, 26, -43, + 182, -102, -24, 183, -98, -25, 184, -94, -25, 185, -90, -26, + 185, -86, -27, 186, -82, -27, 187, -78, -28, 188, -73, -29, + 189, -69, -29, 190, -65, -30, 191, -61, -31, 192, -57, -31, + 193, -53, -32, 194, -49, -33, 195, -45, -33, 196, -41, -34, + 197, -36, -35, 198, -32, -35, 199, -28, -36, 199, -24, -37, + 201, -20, -37, 201, -16, -38, 202, -12, -39, 203, -8, -39, + 204, -4, -40, 205, 0, -41, 206, 4, -41, 207, 8, -42, + 208, 12, -43, 209, 16, -43, 210, 20, -44, 211, 24, -45, + 184, -104, -26, 185, -100, -26, 186, -96, -27, 187, -91, -28, + 188, -87, -28, 189, -83, -29, 190, -79, -30, 191, -75, -30, + 192, -71, -31, 192, -67, -32, 193, -63, -32, 194, -59, -33, + 195, -54, -34, 196, -50, -34, 197, -46, -35, 198, -42, -36, + 199, -38, -36, 200, -34, -37, 201, -30, -38, 202, -26, -38, + 203, -21, -39, 204, -17, -40, 205, -13, -40, 206, -9, -41, + 207, -5, -42, 208, -1, -42, 208, 2, -43, 209, 6, -44, + 210, 10, -44, 211, 15, -45, 212, 19, -46, 213, 23, -46, + 186, -105, -27, 187, -101, -28, 188, -97, -29, 189, -92, -29, + 190, -88, -30, 191, -84, -31, 192, -80, -31, 193, -76, -32, + 194, -72, -33, 195, -68, -33, 196, -64, -34, 197, -60, -35, + 198, -55, -35, 199, -51, -36, 200, -47, -37, 200, -43, -37, + 201, -39, -38, 202, -35, -39, 203, -31, -39, 204, -27, -40, + 205, -22, -41, 206, -18, -41, 207, -14, -42, 208, -10, -43, + 209, -6, -43, 210, -2, -44, 211, 1, -45, 212, 5, -45, + 213, 9, -46, 214, 14, -47, 215, 18, -47, 215, 22, -48, + 189, -106, -29, 190, -102, -30, 191, -98, -30, 192, -94, -31, + 193, -90, -32, 193, -86, -32, 194, -82, -33, 195, -77, -34, + 196, -73, -34, 197, -69, -35, 198, -65, -36, 199, -61, -36, + 200, -57, -37, 201, -53, -38, 202, -49, -38, 203, -45, -39, + 204, -40, -40, 205, -36, -40, 206, -32, -41, 207, -28, -42, + 208, -24, -42, 208, -20, -43, 209, -16, -44, 210, -12, -44, + 211, -8, -45, 212, -3, -46, 213, 0, -46, 214, 4, -47, + 215, 8, -48, 216, 12, -48, 217, 16, -49, 218, 20, -50, + 191, -108, -31, 192, -104, -31, 193, -100, -32, 194, -95, -33, + 195, -91, -33, 196, -87, -34, 197, -83, -35, 198, -79, -35, + 199, -75, -36, 200, -71, -37, 200, -67, -37, 201, -63, -38, + 202, -58, -39, 203, -54, -39, 204, -50, -40, 205, -46, -41, + 206, -42, -41, 207, -38, -42, 208, -34, -43, 209, -30, -43, + 210, -25, -44, 211, -21, -45, 212, -17, -45, 213, -13, -46, + 214, -9, -47, 215, -5, -47, 215, -1, -48, 216, 2, -49, + 217, 6, -49, 218, 11, -50, 219, 15, -51, 220, 19, -51, + 193, -109, -32, 194, -105, -33, 195, -101, -34, 196, -96, -34, + 197, -92, -35, 198, -88, -36, 199, -84, -36, 200, -80, -37, + 201, -76, -38, 202, -72, -38, 203, -68, -39, 204, -64, -40, + 205, -59, -40, 206, -55, -41, 207, -51, -42, 207, -47, -42, + 208, -43, -43, 209, -39, -44, 210, -35, -44, 211, -31, -45, + 212, -26, -46, 213, -22, -46, 214, -18, -47, 215, -14, -48, + 216, -10, -48, 217, -6, -49, 218, -2, -50, 219, 1, -50, + 220, 5, -51, 221, 10, -52, 222, 14, -52, 223, 18, -53, + 46, -26, 78, 47, -22, 77, 48, -18, 76, 49, -13, 75, + 50, -9, 75, 51, -5, 74, 52, -1, 74, 53, 2, 73, + 54, 6, 72, 55, 10, 71, 55, 14, 71, 56, 18, 70, + 57, 23, 69, 58, 27, 69, 59, 31, 68, 60, 35, 67, + 61, 39, 67, 62, 43, 66, 63, 47, 65, 64, 51, 65, + 65, 56, 64, 66, 60, 63, 67, 64, 63, 68, 68, 62, + 69, 72, 61, 70, 76, 61, 71, 80, 60, 71, 84, 59, + 72, 88, 59, 73, 93, 58, 74, 97, 57, 75, 101, 57, + 48, -27, 76, 49, -23, 75, 50, -19, 75, 51, -15, 74, + 52, -11, 73, 53, -7, 72, 54, -3, 72, 55, 1, 71, + 56, 5, 70, 57, 9, 70, 58, 13, 69, 59, 17, 69, + 60, 21, 68, 61, 25, 67, 62, 29, 66, 63, 33, 66, + 64, 38, 65, 64, 42, 64, 65, 46, 64, 66, 50, 63, + 67, 54, 62, 68, 58, 62, 69, 62, 61, 70, 66, 60, + 71, 70, 60, 72, 75, 59, 73, 79, 58, 74, 83, 58, + 75, 87, 57, 76, 91, 56, 77, 95, 56, 78, 99, 55, + 51, -28, 74, 52, -24, 74, 53, -20, 73, 54, -16, 72, + 55, -12, 71, 56, -8, 71, 56, -4, 70, 57, 0, 69, + 58, 4, 69, 59, 8, 68, 60, 12, 67, 61, 16, 67, + 62, 20, 66, 63, 24, 65, 64, 28, 65, 65, 32, 64, + 66, 37, 63, 67, 41, 63, 68, 45, 62, 69, 49, 61, + 70, 53, 61, 71, 57, 60, 71, 61, 59, 72, 65, 59, + 73, 69, 58, 74, 74, 57, 75, 78, 57, 76, 82, 56, + 77, 86, 55, 78, 90, 55, 79, 94, 54, 80, 98, 53, + 53, -30, 72, 54, -26, 72, 55, -22, 71, 56, -17, 70, + 57, -13, 70, 58, -9, 69, 59, -5, 68, 60, -1, 68, + 61, 2, 67, 62, 6, 66, 63, 10, 66, 63, 14, 65, + 64, 19, 64, 65, 23, 64, 66, 27, 63, 67, 31, 62, + 68, 35, 62, 69, 39, 61, 70, 43, 60, 71, 47, 60, + 72, 52, 59, 73, 56, 58, 74, 60, 58, 75, 64, 57, + 76, 68, 56, 77, 72, 56, 78, 76, 55, 78, 80, 54, + 79, 84, 54, 80, 89, 53, 81, 93, 52, 82, 97, 52, + 56, -31, 71, 56, -27, 70, 57, -23, 70, 58, -19, 69, + 59, -15, 68, 60, -11, 67, 61, -7, 67, 62, -2, 66, + 63, 1, 65, 64, 5, 65, 65, 9, 64, 66, 13, 63, + 67, 17, 63, 68, 21, 62, 69, 25, 61, 70, 29, 61, + 71, 34, 60, 71, 38, 59, 72, 42, 59, 73, 46, 58, + 74, 50, 57, 75, 54, 57, 76, 58, 56, 77, 62, 55, + 78, 66, 55, 79, 71, 54, 80, 75, 53, 81, 79, 53, + 82, 83, 52, 83, 87, 51, 84, 91, 51, 85, 95, 50, + 58, -32, 69, 59, -28, 68, 60, -24, 68, 61, -20, 67, + 62, -16, 66, 63, -12, 66, 63, -8, 65, 64, -3, 64, + 65, 0, 64, 66, 4, 63, 67, 8, 62, 68, 12, 62, + 69, 16, 61, 70, 20, 60, 71, 24, 60, 72, 28, 59, + 73, 33, 58, 74, 37, 58, 75, 41, 57, 76, 45, 56, + 77, 49, 56, 78, 53, 55, 79, 57, 54, 79, 61, 54, + 80, 65, 53, 81, 70, 52, 82, 74, 52, 83, 78, 51, + 84, 82, 50, 85, 86, 50, 86, 90, 49, 87, 94, 48, + 60, -34, 67, 61, -30, 67, 62, -26, 66, 63, -21, 65, + 64, -17, 65, 65, -13, 64, 66, -9, 63, 67, -5, 63, + 68, -1, 62, 69, 2, 61, 70, 6, 61, 70, 10, 60, + 72, 15, 59, 72, 19, 59, 73, 23, 58, 74, 27, 57, + 75, 31, 57, 76, 35, 56, 77, 39, 55, 78, 43, 55, + 79, 48, 54, 80, 52, 53, 81, 56, 53, 82, 60, 52, + 83, 64, 51, 84, 68, 51, 85, 72, 50, 86, 76, 49, + 86, 80, 49, 87, 85, 48, 88, 89, 47, 89, 93, 47, + 63, -35, 66, 63, -31, 65, 64, -27, 64, 65, -23, 64, + 66, -19, 63, 67, -15, 62, 68, -11, 62, 69, -6, 61, + 70, -2, 60, 71, 1, 60, 72, 5, 59, 73, 9, 58, + 74, 13, 58, 75, 17, 57, 76, 21, 56, 77, 25, 56, + 78, 30, 55, 79, 34, 54, 79, 38, 54, 80, 42, 53, + 81, 46, 52, 82, 50, 52, 83, 54, 51, 84, 58, 50, + 85, 62, 50, 86, 67, 49, 87, 71, 48, 88, 75, 48, + 89, 79, 47, 90, 83, 46, 91, 87, 46, 92, 91, 45, + 65, -36, 64, 66, -32, 63, 67, -28, 63, 68, -24, 62, + 69, -20, 61, 70, -16, 61, 71, -12, 60, 72, -7, 59, + 72, -3, 59, 73, 0, 58, 74, 4, 57, 75, 8, 57, + 76, 12, 56, 77, 16, 55, 78, 20, 55, 79, 24, 54, + 80, 29, 53, 81, 33, 53, 82, 37, 52, 83, 41, 51, + 84, 45, 51, 85, 49, 50, 86, 53, 49, 86, 57, 49, + 87, 61, 48, 88, 66, 47, 89, 70, 47, 90, 74, 46, + 91, 78, 45, 92, 82, 45, 93, 86, 44, 94, 90, 43, + 67, -38, 62, 68, -34, 62, 69, -30, 61, 70, -25, 60, + 71, -21, 60, 72, -17, 59, 73, -13, 58, 74, -9, 58, + 75, -5, 57, 76, -1, 56, 77, 2, 56, 78, 6, 55, + 79, 11, 54, 79, 15, 54, 80, 19, 53, 81, 23, 52, + 82, 27, 52, 83, 31, 51, 84, 35, 50, 85, 39, 50, + 86, 44, 49, 87, 48, 48, 88, 52, 48, 89, 56, 47, + 90, 60, 46, 91, 64, 46, 92, 68, 45, 93, 72, 44, + 93, 76, 44, 95, 81, 43, 95, 85, 42, 96, 89, 42, + 70, -39, 61, 71, -35, 60, 71, -31, 59, 72, -27, 59, + 73, -23, 58, 74, -19, 57, 75, -15, 57, 76, -10, 56, + 77, -6, 55, 78, -2, 55, 79, 1, 54, 80, 5, 53, + 81, 9, 53, 82, 13, 52, 83, 17, 51, 84, 21, 51, + 85, 26, 50, 86, 30, 49, 86, 34, 49, 87, 38, 48, + 88, 42, 47, 89, 46, 47, 90, 50, 46, 91, 54, 45, + 92, 58, 45, 93, 63, 44, 94, 67, 43, 95, 71, 43, + 96, 75, 42, 97, 79, 41, 98, 83, 41, 99, 87, 40, + 73, -41, 59, 73, -37, 58, 74, -33, 57, 75, -28, 57, + 76, -24, 56, 77, -20, 55, 78, -16, 55, 79, -12, 54, + 80, -8, 53, 81, -4, 53, 82, 0, 52, 83, 3, 51, + 84, 8, 51, 85, 12, 50, 86, 16, 49, 87, 20, 49, + 88, 24, 48, 89, 28, 47, 89, 32, 47, 90, 36, 46, + 91, 41, 45, 92, 45, 45, 93, 49, 44, 94, 53, 43, + 95, 57, 43, 96, 61, 42, 97, 65, 41, 98, 69, 41, + 99, 73, 40, 100, 78, 39, 101, 82, 39, 102, 86, 38, + 75, -42, 57, 76, -38, 56, 77, -34, 56, 78, -30, 55, + 79, -26, 54, 80, -22, 54, 80, -18, 53, 82, -13, 52, + 82, -9, 52, 83, -5, 51, 84, -1, 50, 85, 2, 50, + 86, 6, 49, 87, 10, 48, 88, 14, 48, 89, 18, 47, + 90, 23, 46, 91, 27, 46, 92, 31, 45, 93, 35, 44, + 94, 39, 44, 95, 43, 43, 96, 47, 42, 96, 51, 42, + 97, 55, 41, 98, 60, 40, 99, 64, 40, 100, 68, 39, + 101, 72, 38, 102, 76, 38, 103, 80, 37, 104, 84, 36, + 77, -43, 55, 78, -39, 55, 79, -35, 54, 80, -31, 53, + 81, -27, 53, 82, -23, 52, 83, -19, 51, 84, -14, 51, + 85, -10, 50, 86, -6, 49, 87, -2, 49, 88, 1, 48, + 89, 5, 47, 89, 9, 47, 90, 13, 46, 91, 17, 45, + 92, 22, 45, 93, 26, 44, 94, 30, 43, 95, 34, 43, + 96, 38, 42, 97, 42, 41, 98, 46, 41, 99, 50, 40, + 100, 54, 39, 101, 59, 39, 102, 63, 38, 103, 67, 37, + 103, 71, 37, 105, 75, 36, 105, 79, 35, 106, 83, 35, + 80, -45, 54, 81, -41, 53, 81, -37, 52, 82, -32, 52, + 83, -28, 51, 84, -24, 50, 85, -20, 50, 86, -16, 49, + 87, -12, 48, 88, -8, 48, 89, -4, 47, 90, 0, 46, + 91, 4, 46, 92, 8, 45, 93, 12, 44, 94, 16, 44, + 95, 20, 43, 96, 24, 42, 96, 28, 42, 97, 32, 41, + 98, 37, 40, 99, 41, 40, 100, 45, 39, 101, 49, 38, + 102, 53, 38, 103, 57, 37, 104, 61, 36, 105, 65, 36, + 106, 69, 35, 107, 74, 34, 108, 78, 34, 109, 82, 33, + 82, -46, 52, 83, -42, 51, 84, -38, 51, 85, -34, 50, + 86, -30, 49, 87, -26, 49, 88, -22, 48, 89, -17, 47, + 89, -13, 47, 90, -9, 46, 91, -5, 45, 92, -1, 45, + 93, 2, 44, 94, 6, 43, 95, 10, 43, 96, 14, 42, + 97, 19, 41, 98, 23, 41, 99, 27, 40, 100, 31, 39, + 101, 35, 39, 102, 39, 38, 103, 43, 37, 103, 47, 37, + 104, 51, 36, 105, 56, 35, 106, 60, 35, 107, 64, 34, + 108, 68, 33, 109, 72, 33, 110, 76, 32, 111, 80, 31, + 84, -47, 50, 85, -43, 50, 86, -39, 49, 87, -35, 48, + 88, -31, 48, 89, -27, 47, 90, -23, 46, 91, -18, 46, + 92, -14, 45, 93, -10, 44, 94, -6, 44, 95, -2, 43, + 96, 1, 42, 96, 5, 42, 97, 9, 41, 98, 13, 40, + 99, 18, 40, 100, 22, 39, 101, 26, 38, 102, 30, 38, + 103, 34, 37, 104, 38, 36, 105, 42, 36, 106, 46, 35, + 107, 50, 34, 108, 55, 34, 109, 59, 33, 110, 63, 32, + 111, 67, 32, 112, 71, 31, 112, 75, 30, 113, 79, 30, + 87, -49, 49, 88, -45, 48, 88, -41, 47, 89, -36, 47, + 90, -32, 46, 91, -28, 45, 92, -24, 45, 93, -20, 44, + 94, -16, 43, 95, -12, 43, 96, -8, 42, 97, -4, 41, + 98, 0, 41, 99, 4, 40, 100, 8, 39, 101, 12, 39, + 102, 16, 38, 103, 20, 37, 104, 24, 37, 104, 28, 36, + 105, 33, 35, 106, 37, 35, 107, 41, 34, 108, 45, 33, + 109, 49, 33, 110, 53, 32, 111, 57, 31, 112, 61, 31, + 113, 65, 30, 114, 70, 29, 115, 74, 29, 116, 78, 28, + 89, -50, 47, 90, -46, 46, 91, -42, 46, 92, -38, 45, + 93, -34, 44, 94, -30, 44, 95, -26, 43, 96, -21, 42, + 97, -17, 42, 97, -13, 41, 98, -9, 40, 99, -5, 40, + 100, -1, 39, 101, 2, 38, 102, 6, 38, 103, 10, 37, + 104, 15, 36, 105, 19, 36, 106, 23, 35, 107, 27, 34, + 108, 31, 34, 109, 35, 33, 110, 39, 32, 111, 43, 32, + 111, 47, 31, 112, 52, 30, 113, 56, 30, 114, 60, 29, + 115, 64, 28, 116, 68, 28, 117, 72, 27, 118, 76, 26, + 91, -51, 45, 92, -47, 45, 93, -43, 44, 94, -39, 43, + 95, -35, 43, 96, -31, 42, 97, -27, 41, 98, -22, 41, + 99, -18, 40, 100, -14, 39, 101, -10, 39, 102, -6, 38, + 103, -2, 37, 104, 1, 37, 104, 5, 36, 105, 9, 35, + 106, 14, 35, 107, 18, 34, 108, 22, 33, 109, 26, 33, + 110, 30, 32, 111, 34, 31, 112, 38, 31, 113, 42, 30, + 114, 46, 29, 115, 51, 29, 116, 55, 28, 117, 59, 27, + 118, 63, 27, 119, 67, 26, 120, 71, 25, 120, 75, 25, + 94, -53, 44, 95, -49, 43, 96, -45, 42, 97, -40, 42, + 97, -36, 41, 98, -32, 40, 99, -28, 40, 100, -24, 39, + 101, -20, 38, 102, -16, 38, 103, -12, 37, 104, -8, 36, + 105, -3, 36, 106, 0, 35, 107, 4, 34, 108, 8, 34, + 109, 12, 33, 110, 16, 32, 111, 20, 32, 111, 24, 31, + 113, 29, 30, 113, 33, 30, 114, 37, 29, 115, 41, 28, + 116, 45, 28, 117, 49, 27, 118, 53, 26, 119, 57, 26, + 120, 61, 25, 121, 66, 24, 122, 70, 24, 123, 74, 23, + 96, -54, 42, 97, -50, 41, 98, -46, 41, 99, -41, 40, + 100, -37, 39, 101, -33, 39, 102, -29, 38, 103, -25, 37, + 104, -21, 37, 104, -17, 36, 105, -13, 35, 106, -9, 35, + 107, -4, 34, 108, 0, 33, 109, 3, 33, 110, 7, 32, + 111, 11, 31, 112, 15, 31, 113, 19, 30, 114, 23, 29, + 115, 28, 28, 116, 32, 28, 117, 36, 27, 118, 40, 27, + 118, 44, 26, 120, 48, 25, 120, 52, 25, 121, 56, 24, + 122, 60, 23, 123, 65, 22, 124, 69, 22, 125, 73, 21, + 98, -55, 40, 99, -51, 40, 100, -47, 39, 101, -43, 38, + 102, -39, 38, 103, -35, 37, 104, -31, 36, 105, -26, 36, + 106, -22, 35, 107, -18, 34, 108, -14, 34, 109, -10, 33, + 110, -6, 32, 111, -2, 32, 111, 1, 31, 112, 5, 30, + 113, 10, 30, 114, 14, 29, 115, 18, 28, 116, 22, 28, + 117, 26, 27, 118, 30, 26, 119, 34, 26, 120, 38, 25, + 121, 42, 24, 122, 47, 23, 123, 51, 23, 124, 55, 22, + 125, 59, 22, 126, 63, 21, 127, 67, 20, 127, 71, 20, + 101, -57, 39, 102, -53, 38, 103, -49, 37, 104, -44, 37, + 104, -40, 36, 105, -36, 35, 106, -32, 35, 107, -28, 34, + 108, -24, 33, 109, -20, 33, 110, -16, 32, 111, -12, 31, + 112, -7, 31, 113, -3, 30, 114, 0, 29, 115, 4, 29, + 116, 8, 28, 117, 12, 27, 118, 16, 27, 119, 20, 26, + 120, 25, 25, 120, 29, 24, 121, 33, 24, 122, 37, 23, + 123, 41, 23, 124, 45, 22, 125, 49, 21, 126, 53, 21, + 127, 57, 20, 128, 62, 19, 129, 66, 18, 130, 70, 18, + 103, -58, 37, 104, -54, 36, 105, -50, 36, 106, -45, 35, + 107, -41, 34, 108, -37, 34, 109, -33, 33, 110, -29, 32, + 111, -25, 32, 112, -21, 31, 112, -17, 30, 113, -13, 30, + 114, -8, 29, 115, -4, 28, 116, 0, 28, 117, 3, 27, + 118, 7, 26, 119, 11, 26, 120, 15, 25, 121, 19, 24, + 122, 24, 23, 123, 28, 23, 124, 32, 22, 125, 36, 22, + 126, 40, 21, 127, 44, 20, 127, 48, 19, 128, 52, 19, + 129, 56, 18, 130, 61, 17, 131, 65, 17, 132, 69, 16, + 105, -59, 35, 106, -55, 35, 107, -51, 34, 108, -47, 33, + 109, -43, 33, 110, -39, 32, 111, -35, 31, 112, -30, 30, + 113, -26, 30, 114, -22, 29, 115, -18, 29, 116, -14, 28, + 117, -10, 27, 118, -6, 27, 119, -2, 26, 119, 1, 25, + 120, 6, 24, 121, 10, 24, 122, 14, 23, 123, 18, 23, + 124, 22, 22, 125, 26, 21, 126, 30, 20, 127, 34, 20, + 128, 38, 19, 129, 43, 18, 130, 47, 18, 131, 51, 17, + 132, 55, 17, 133, 59, 16, 134, 63, 15, 135, 67, 14, + 108, -61, 34, 109, -57, 33, 110, -53, 32, 111, -48, 32, + 112, -44, 31, 112, -40, 30, 113, -36, 30, 114, -32, 29, + 115, -28, 28, 116, -24, 28, 117, -20, 27, 118, -16, 26, + 119, -11, 25, 120, -7, 25, 121, -3, 24, 122, 0, 24, + 123, 4, 23, 124, 8, 22, 125, 12, 22, 126, 16, 21, + 127, 21, 20, 128, 25, 19, 128, 29, 19, 129, 33, 18, + 130, 37, 18, 131, 41, 17, 132, 45, 16, 133, 49, 15, + 134, 53, 15, 135, 58, 14, 136, 62, 13, 137, 66, 13, + 110, -62, 32, 111, -58, 31, 112, -54, 31, 113, -49, 30, + 114, -45, 29, 115, -41, 29, 116, -37, 28, 117, -33, 27, + 118, -29, 26, 119, -25, 26, 119, -21, 25, 120, -17, 25, + 121, -12, 24, 122, -8, 23, 123, -4, 23, 124, 0, 22, + 125, 3, 21, 126, 7, 20, 127, 11, 20, 128, 15, 19, + 129, 20, 18, 130, 24, 18, 131, 28, 17, 132, 32, 16, + 133, 36, 16, 134, 40, 15, 135, 44, 14, 135, 48, 14, + 136, 52, 13, 137, 57, 12, 138, 61, 12, 139, 65, 11, + 112, -63, 30, 113, -59, 30, 114, -55, 29, 115, -51, 28, + 116, -47, 28, 117, -43, 27, 118, -39, 26, 119, -34, 25, + 120, -30, 25, 121, -26, 24, 122, -22, 24, 123, -18, 23, + 124, -14, 22, 125, -10, 21, 126, -6, 21, 126, -2, 20, + 128, 2, 19, 128, 6, 19, 129, 10, 18, 130, 14, 18, + 131, 18, 17, 132, 22, 16, 133, 26, 15, 134, 30, 15, + 135, 34, 14, 136, 39, 13, 137, 43, 13, 138, 47, 12, + 139, 51, 11, 140, 55, 11, 141, 59, 10, 142, 63, 9, + 115, -65, 29, 116, -61, 28, 117, -57, 27, 118, -52, 26, + 119, -48, 26, 119, -44, 25, 120, -40, 25, 121, -36, 24, + 122, -32, 23, 123, -28, 22, 124, -24, 22, 125, -20, 21, + 126, -15, 20, 127, -11, 20, 128, -7, 19, 129, -3, 19, + 130, 0, 18, 131, 4, 17, 132, 8, 16, 133, 12, 16, + 134, 17, 15, 135, 21, 14, 135, 25, 14, 136, 29, 13, + 137, 33, 12, 138, 37, 12, 139, 41, 11, 140, 45, 10, + 141, 49, 10, 142, 54, 9, 143, 58, 8, 144, 62, 8, + 117, -66, 27, 118, -62, 26, 119, -58, 26, 120, -53, 25, + 121, -49, 24, 122, -45, 24, 123, -41, 23, 124, -37, 22, + 125, -33, 21, 126, -29, 21, 127, -25, 20, 127, -21, 20, + 128, -16, 19, 129, -12, 18, 130, -8, 17, 131, -4, 17, + 132, 0, 16, 133, 3, 15, 134, 7, 15, 135, 11, 14, + 136, 16, 13, 137, 20, 13, 138, 24, 12, 139, 28, 11, + 140, 32, 11, 141, 36, 10, 142, 40, 9, 142, 44, 9, + 143, 48, 8, 144, 53, 7, 145, 57, 7, 146, 61, 6, + 120, -67, 25, 120, -63, 25, 121, -59, 24, 122, -55, 23, + 123, -51, 22, 124, -47, 22, 125, -43, 21, 126, -38, 20, + 127, -34, 20, 128, -30, 19, 129, -26, 18, 130, -22, 18, + 131, -18, 17, 132, -14, 16, 133, -10, 16, 134, -6, 15, + 135, -1, 14, 135, 2, 14, 136, 6, 13, 137, 10, 12, + 138, 14, 12, 139, 18, 11, 140, 22, 10, 141, 26, 10, + 142, 30, 9, 143, 35, 8, 144, 39, 8, 145, 43, 7, + 146, 47, 6, 147, 51, 6, 148, 55, 5, 149, 59, 4, + 122, -69, 23, 123, -65, 22, 124, -61, 22, 125, -56, 21, + 126, -52, 20, 127, -48, 20, 128, -44, 19, 129, -40, 18, + 130, -36, 18, 131, -32, 17, 132, -28, 16, 133, -24, 16, + 134, -19, 15, 135, -15, 14, 136, -11, 14, 136, -7, 13, + 138, -3, 12, 138, 0, 12, 139, 4, 11, 140, 8, 10, + 141, 13, 10, 142, 17, 9, 143, 21, 8, 144, 25, 8, + 145, 29, 7, 146, 33, 6, 147, 37, 6, 148, 41, 5, + 149, 45, 4, 150, 50, 4, 151, 54, 3, 152, 58, 2, + 125, -70, 21, 126, -66, 21, 127, -62, 20, 128, -58, 19, + 129, -54, 19, 129, -50, 18, 130, -46, 17, 131, -41, 17, + 132, -37, 16, 133, -33, 15, 134, -29, 15, 135, -25, 14, + 136, -21, 13, 137, -17, 13, 138, -13, 12, 139, -9, 11, + 140, -4, 11, 141, 0, 10, 142, 3, 9, 143, 7, 9, + 144, 11, 8, 145, 15, 7, 145, 19, 7, 146, 23, 6, + 147, 27, 5, 148, 32, 5, 149, 36, 4, 150, 40, 3, + 151, 44, 3, 152, 48, 2, 153, 52, 1, 154, 56, 1, + 127, -72, 20, 128, -68, 19, 129, -64, 18, 130, -59, 18, + 131, -55, 17, 132, -51, 16, 133, -47, 16, 134, -43, 15, + 135, -39, 14, 136, -35, 14, 136, -31, 13, 137, -27, 12, + 138, -22, 12, 139, -18, 11, 140, -14, 10, 141, -10, 10, + 142, -6, 9, 143, -2, 8, 144, 1, 8, 145, 5, 7, + 146, 10, 6, 147, 14, 6, 148, 18, 5, 149, 22, 4, + 150, 26, 4, 151, 30, 3, 152, 34, 2, 152, 38, 2, + 153, 42, 1, 154, 47, 0, 155, 51, 0, 156, 55, 0, + 129, -73, 18, 130, -69, 17, 131, -65, 17, 132, -60, 16, + 133, -56, 15, 134, -52, 15, 135, -48, 14, 136, -44, 13, + 137, -40, 13, 138, -36, 12, 139, -32, 11, 140, -28, 11, + 141, -23, 10, 142, -19, 9, 143, -15, 9, 144, -11, 8, + 145, -7, 7, 145, -3, 7, 146, 0, 6, 147, 4, 5, + 148, 9, 5, 149, 13, 4, 150, 17, 3, 151, 21, 3, + 152, 25, 2, 153, 29, 1, 154, 33, 1, 155, 37, 0, + 156, 41, 0, 157, 46, 0, 158, 50, -1, 159, 54, -2, + 132, -74, 16, 133, -70, 16, 134, -66, 15, 135, -62, 14, + 136, -58, 14, 137, -54, 13, 137, -50, 12, 138, -45, 12, + 139, -41, 11, 140, -37, 10, 141, -33, 10, 142, -29, 9, + 143, -25, 8, 144, -21, 8, 145, -17, 7, 146, -13, 6, + 147, -8, 6, 148, -4, 5, 149, 0, 4, 150, 3, 4, + 151, 7, 3, 152, 11, 2, 152, 15, 2, 153, 19, 1, + 154, 23, 0, 155, 28, 0, 156, 32, 0, 157, 36, -1, + 158, 40, -1, 159, 44, -2, 160, 48, -3, 161, 52, -3, + 134, -76, 15, 135, -72, 14, 136, -68, 13, 137, -63, 13, + 138, -59, 12, 139, -55, 11, 140, -51, 11, 141, -47, 10, + 142, -43, 9, 143, -39, 9, 144, -35, 8, 144, -31, 7, + 145, -26, 7, 146, -22, 6, 147, -18, 5, 148, -14, 5, + 149, -10, 4, 150, -6, 3, 151, -2, 3, 152, 1, 2, + 153, 6, 1, 154, 10, 1, 155, 14, 0, 156, 18, 0, + 157, 22, 0, 158, 26, -1, 159, 30, -2, 160, 34, -2, + 160, 38, -3, 161, 43, -4, 162, 47, -4, 163, 51, -5, + 137, -77, 13, 137, -73, 12, 138, -69, 12, 139, -64, 11, + 140, -60, 10, 141, -56, 10, 142, -52, 9, 143, -48, 8, + 144, -44, 8, 145, -40, 7, 146, -36, 6, 147, -32, 6, + 148, -27, 5, 149, -23, 4, 150, -19, 4, 151, -15, 3, + 152, -11, 2, 153, -7, 2, 153, -3, 1, 154, 0, 0, + 155, 5, 0, 156, 9, 0, 157, 13, -1, 158, 17, -1, + 159, 21, -2, 160, 25, -3, 161, 29, -3, 162, 33, -4, + 163, 37, -5, 164, 42, -5, 165, 46, -6, 166, 50, -7, + 139, -78, 11, 140, -74, 11, 141, -70, 10, 142, -66, 9, + 143, -62, 9, 144, -58, 8, 144, -54, 7, 146, -49, 7, + 146, -45, 6, 147, -41, 5, 148, -37, 5, 149, -33, 4, + 150, -29, 3, 151, -25, 3, 152, -21, 2, 153, -17, 1, + 154, -12, 1, 155, -8, 0, 156, -4, 0, 157, 0, 0, + 158, 3, -1, 159, 7, -2, 160, 11, -2, 160, 15, -3, + 161, 19, -4, 162, 24, -4, 163, 28, -5, 164, 32, -6, + 165, 36, -6, 166, 40, -7, 167, 44, -8, 168, 48, -8, + 141, -79, 10, 142, -75, 9, 143, -71, 8, 144, -67, 8, + 145, -63, 7, 146, -59, 6, 147, -55, 6, 148, -50, 5, + 149, -46, 4, 150, -42, 4, 151, -38, 3, 151, -34, 2, + 153, -30, 2, 153, -26, 1, 154, -22, 0, 155, -18, 0, + 156, -13, 0, 157, -9, -1, 158, -5, -1, 159, -1, -2, + 160, 2, -3, 161, 6, -3, 162, 10, -4, 163, 14, -5, + 164, 18, -5, 165, 23, -6, 166, 27, -7, 167, 31, -7, + 167, 35, -8, 168, 39, -9, 169, 43, -9, 170, 47, -10, + 144, -81, 8, 144, -77, 7, 145, -73, 7, 146, -68, 6, + 147, -64, 5, 148, -60, 5, 149, -56, 4, 150, -52, 3, + 151, -48, 3, 152, -44, 2, 153, -40, 1, 154, -36, 1, + 155, -31, 0, 156, -27, 0, 157, -23, 0, 158, -19, -1, + 159, -15, -2, 160, -11, -2, 160, -7, -3, 161, -3, -4, + 162, 1, -4, 163, 5, -5, 164, 9, -6, 165, 13, -6, + 166, 17, -7, 167, 21, -8, 168, 25, -8, 169, 29, -9, + 170, 33, -10, 171, 38, -10, 172, 42, -11, 173, 46, -12, + 146, -82, 6, 147, -78, 6, 148, -74, 5, 149, -70, 4, + 150, -66, 4, 151, -62, 3, 152, -58, 2, 153, -53, 2, + 153, -49, 1, 154, -45, 0, 155, -41, 0, 156, -37, 0, + 157, -33, -1, 158, -29, -1, 159, -25, -2, 160, -21, -3, + 161, -16, -3, 162, -12, -4, 163, -8, -5, 164, -4, -5, + 165, 0, -6, 166, 3, -7, 167, 7, -7, 167, 11, -8, + 168, 15, -9, 169, 20, -9, 170, 24, -10, 171, 28, -11, + 172, 32, -11, 173, 36, -12, 174, 40, -13, 175, 44, -13, + 148, -83, 5, 149, -79, 4, 150, -75, 3, 151, -71, 3, + 152, -67, 2, 153, -63, 1, 154, -59, 1, 155, -54, 0, + 156, -50, 0, 157, -46, 0, 158, -42, -1, 159, -38, -2, + 160, -34, -2, 160, -30, -3, 161, -26, -4, 162, -22, -4, + 163, -17, -5, 164, -13, -6, 165, -9, -6, 166, -5, -7, + 167, -1, -8, 168, 2, -8, 169, 6, -9, 170, 10, -10, + 171, 14, -10, 172, 19, -11, 173, 23, -12, 174, 27, -12, + 175, 31, -13, 176, 35, -14, 176, 39, -14, 177, 43, -15, + 151, -85, 3, 152, -81, 2, 152, -77, 2, 153, -72, 1, + 154, -68, 0, 155, -64, 0, 156, -60, 0, 157, -56, -1, + 158, -52, -1, 159, -48, -2, 160, -44, -3, 161, -40, -3, + 162, -35, -4, 163, -31, -5, 164, -27, -5, 165, -23, -6, + 166, -19, -7, 167, -15, -7, 168, -11, -8, 168, -7, -9, + 169, -2, -9, 170, 1, -10, 171, 5, -11, 172, 9, -11, + 173, 13, -12, 174, 17, -13, 175, 21, -13, 176, 25, -14, + 177, 29, -15, 178, 34, -15, 179, 38, -16, 180, 42, -17, + 153, -86, 1, 154, -82, 1, 155, -78, 0, 156, -74, 0, + 157, -70, 0, 158, -66, -1, 159, -62, -2, 160, -57, -2, + 161, -53, -3, 161, -49, -4, 162, -45, -4, 163, -41, -5, + 164, -37, -6, 165, -33, -6, 166, -29, -7, 167, -25, -8, + 168, -20, -8, 169, -16, -9, 170, -12, -10, 171, -8, -10, + 172, -4, -11, 173, 0, -12, 174, 3, -12, 175, 7, -13, + 175, 11, -14, 176, 16, -14, 177, 20, -15, 178, 24, -16, + 179, 28, -16, 180, 32, -17, 181, 36, -18, 182, 40, -18, + 155, -87, 0, 156, -83, 0, 157, -79, -1, 158, -75, -1, + 159, -71, -2, 160, -67, -3, 161, -63, -3, 162, -58, -4, + 163, -54, -5, 164, -50, -5, 165, -46, -6, 166, -42, -7, + 167, -38, -7, 168, -34, -8, 168, -30, -9, 169, -26, -9, + 170, -21, -10, 171, -17, -11, 172, -13, -11, 173, -9, -12, + 174, -5, -13, 175, -1, -13, 176, 2, -14, 177, 6, -15, + 178, 10, -15, 179, 15, -16, 180, 19, -17, 181, 23, -17, + 182, 27, -18, 183, 31, -19, 183, 35, -19, 184, 39, -20, + 158, -89, -1, 159, -85, -2, 159, -81, -2, 161, -76, -3, + 161, -72, -4, 162, -68, -4, 163, -64, -5, 164, -60, -6, + 165, -56, -6, 166, -52, -7, 167, -48, -8, 168, -44, -8, + 169, -39, -9, 170, -35, -10, 171, -31, -10, 172, -27, -11, + 173, -23, -12, 174, -19, -12, 175, -15, -13, 175, -11, -14, + 176, -6, -14, 177, -2, -15, 178, 1, -16, 179, 5, -16, + 180, 9, -17, 181, 13, -18, 182, 17, -18, 183, 21, -19, + 184, 25, -20, 185, 30, -20, 186, 34, -21, 187, 38, -22, + 160, -90, -3, 161, -86, -3, 162, -82, -4, 163, -78, -5, + 164, -74, -5, 165, -70, -6, 166, -66, -7, 167, -61, -7, + 168, -57, -8, 168, -53, -9, 169, -49, -9, 170, -45, -10, + 171, -41, -11, 172, -37, -11, 173, -33, -12, 174, -29, -13, + 175, -24, -13, 176, -20, -14, 177, -16, -15, 178, -12, -15, + 179, -8, -16, 180, -4, -17, 181, 0, -17, 182, 3, -18, + 182, 7, -19, 184, 12, -19, 184, 16, -20, 185, 20, -21, + 186, 24, -21, 187, 28, -22, 188, 32, -23, 189, 36, -23, + 162, -91, -4, 163, -87, -5, 164, -83, -6, 165, -79, -6, + 166, -75, -7, 167, -71, -8, 168, -67, -8, 169, -62, -9, + 170, -58, -10, 171, -54, -10, 172, -50, -11, 173, -46, -12, + 174, -42, -12, 175, -38, -13, 175, -34, -14, 176, -30, -14, + 177, -25, -15, 178, -21, -16, 179, -17, -16, 180, -13, -17, + 181, -9, -18, 182, -5, -18, 183, -1, -19, 184, 2, -20, + 185, 6, -20, 186, 11, -21, 187, 15, -22, 188, 19, -22, + 189, 23, -23, 190, 27, -24, 191, 31, -24, 191, 35, -25, + 165, -93, -6, 166, -89, -7, 167, -85, -7, 168, -80, -8, + 168, -76, -9, 169, -72, -9, 170, -68, -10, 171, -64, -11, + 172, -60, -11, 173, -56, -12, 174, -52, -13, 175, -48, -13, + 176, -43, -14, 177, -39, -15, 178, -35, -15, 179, -31, -16, + 180, -27, -17, 181, -23, -17, 182, -19, -18, 183, -15, -19, + 184, -10, -19, 184, -6, -20, 185, -2, -21, 186, 1, -21, + 187, 5, -22, 188, 9, -23, 189, 13, -23, 190, 17, -24, + 191, 21, -25, 192, 26, -26, 193, 30, -26, 194, 34, -27, + 167, -94, -8, 168, -90, -8, 169, -86, -9, 170, -82, -10, + 171, -78, -10, 172, -74, -11, 173, -70, -12, 174, -65, -12, + 175, -61, -13, 176, -57, -14, 176, -53, -14, 177, -49, -15, + 178, -45, -16, 179, -41, -16, 180, -37, -17, 181, -33, -18, + 182, -28, -18, 183, -24, -19, 184, -20, -20, 185, -16, -20, + 186, -12, -21, 187, -8, -22, 188, -4, -22, 189, 0, -23, + 190, 3, -24, 191, 8, -25, 191, 12, -25, 192, 16, -26, + 193, 20, -26, 194, 24, -27, 195, 28, -28, 196, 32, -28, + 169, -95, -9, 170, -91, -10, 171, -87, -11, 172, -83, -11, + 173, -79, -12, 174, -75, -13, 175, -71, -13, 176, -66, -14, + 177, -62, -15, 178, -58, -15, 179, -54, -16, 180, -50, -17, + 181, -46, -17, 182, -42, -18, 183, -38, -19, 183, -34, -19, + 184, -29, -20, 185, -25, -21, 186, -21, -21, 187, -17, -22, + 188, -13, -23, 189, -9, -23, 190, -5, -24, 191, -1, -25, + 192, 2, -25, 193, 7, -26, 194, 11, -27, 195, 15, -27, + 196, 19, -28, 197, 23, -29, 198, 27, -30, 198, 31, -30, + 172, -97, -12, 173, -93, -12, 174, -89, -13, 175, -85, -14, + 176, -81, -14, 177, -77, -15, 178, -73, -16, 179, -68, -16, + 180, -64, -17, 181, -60, -18, 182, -56, -18, 183, -52, -19, + 184, -48, -20, 185, -44, -20, 185, -40, -21, 186, -36, -22, + 187, -31, -22, 188, -27, -23, 189, -23, -24, 190, -19, -24, + 191, -15, -25, 192, -11, -26, 193, -7, -26, 194, -3, -27, + 195, 0, -28, 196, 5, -28, 197, 9, -29, 198, 13, -30, + 199, 17, -30, 200, 21, -31, 201, 25, -32, 201, 29, -32, + 175, -98, -13, 176, -94, -14, 177, -90, -14, 178, -86, -15, + 178, -82, -16, 179, -78, -17, 180, -74, -17, 181, -69, -18, + 182, -65, -19, 183, -61, -19, 184, -57, -20, 185, -53, -21, + 186, -49, -21, 187, -45, -22, 188, -41, -23, 189, -37, -23, + 190, -32, -24, 191, -28, -25, 192, -24, -25, 192, -20, -26, + 194, -16, -27, 194, -12, -27, 195, -8, -28, 196, -4, -29, + 197, 0, -29, 198, 4, -30, 199, 8, -31, 200, 12, -31, + 201, 16, -32, 202, 20, -33, 203, 24, -33, 204, 28, -34, + 177, -100, -15, 178, -96, -16, 179, -92, -16, 180, -87, -17, + 181, -83, -18, 182, -79, -18, 183, -75, -19, 184, -71, -20, + 185, -67, -20, 185, -63, -21, 186, -59, -22, 187, -55, -22, + 188, -50, -23, 189, -46, -24, 190, -42, -24, 191, -38, -25, + 192, -34, -26, 193, -30, -26, 194, -26, -27, 195, -22, -28, + 196, -17, -28, 197, -13, -29, 198, -9, -30, 199, -5, -30, + 200, -1, -31, 201, 2, -32, 201, 6, -32, 202, 10, -33, + 203, 14, -34, 204, 19, -34, 205, 23, -35, 206, 27, -36, + 179, -101, -17, 180, -97, -17, 181, -93, -18, 182, -89, -19, + 183, -85, -19, 184, -81, -20, 185, -77, -21, 186, -72, -21, + 187, -68, -22, 188, -64, -23, 189, -60, -23, 190, -56, -24, + 191, -52, -25, 192, -48, -25, 193, -44, -26, 193, -40, -27, + 194, -35, -27, 195, -31, -28, 196, -27, -29, 197, -23, -29, + 198, -19, -30, 199, -15, -31, 200, -11, -31, 201, -7, -32, + 202, -3, -33, 203, 1, -33, 204, 5, -34, 205, 9, -35, + 206, 13, -35, 207, 17, -36, 208, 21, -37, 208, 25, -37, + 182, -102, -18, 183, -98, -19, 184, -94, -20, 185, -90, -20, + 186, -86, -21, 186, -82, -22, 187, -78, -22, 188, -73, -23, + 189, -69, -24, 190, -65, -24, 191, -61, -25, 192, -57, -26, + 193, -53, -26, 194, -49, -27, 195, -45, -28, 196, -41, -28, + 197, -36, -29, 198, -32, -30, 199, -28, -30, 200, -24, -31, + 201, -20, -32, 201, -16, -32, 202, -12, -33, 203, -8, -34, + 204, -4, -34, 205, 0, -35, 206, 4, -36, 207, 8, -36, + 208, 12, -37, 209, 16, -38, 210, 20, -38, 211, 24, -39, + 184, -104, -20, 185, -100, -21, 186, -96, -21, 187, -91, -22, + 188, -87, -23, 189, -83, -23, 190, -79, -24, 191, -75, -25, + 192, -71, -25, 193, -67, -26, 193, -63, -27, 194, -59, -27, + 195, -54, -28, 196, -50, -29, 197, -46, -29, 198, -42, -30, + 199, -38, -31, 200, -34, -31, 201, -30, -32, 202, -26, -33, + 203, -21, -33, 204, -17, -34, 205, -13, -35, 206, -9, -35, + 207, -5, -36, 208, -1, -37, 208, 2, -37, 209, 6, -38, + 210, 10, -39, 211, 15, -39, 212, 19, -40, 213, 23, -41, + 186, -105, -22, 187, -101, -22, 188, -97, -23, 189, -92, -24, + 190, -88, -24, 191, -84, -25, 192, -80, -26, 193, -76, -26, + 194, -72, -27, 195, -68, -28, 196, -64, -28, 197, -60, -29, + 198, -55, -30, 199, -51, -30, 200, -47, -31, 200, -43, -32, + 201, -39, -32, 202, -35, -33, 203, -31, -34, 204, -27, -34, + 205, -22, -35, 206, -18, -36, 207, -14, -36, 208, -10, -37, + 209, -6, -38, 210, -2, -38, 211, 1, -39, 212, 5, -40, + 213, 9, -40, 214, 14, -41, 215, 18, -42, 216, 22, -42, + 189, -106, -23, 190, -102, -24, 191, -98, -25, 192, -94, -25, + 193, -90, -26, 193, -86, -27, 194, -82, -27, 195, -77, -28, + 196, -73, -29, 197, -69, -29, 198, -65, -30, 199, -61, -31, + 200, -57, -31, 201, -53, -32, 202, -49, -33, 203, -45, -33, + 204, -40, -34, 205, -36, -35, 206, -32, -35, 207, -28, -36, + 208, -24, -37, 209, -20, -37, 209, -16, -38, 210, -12, -39, + 211, -8, -39, 212, -3, -40, 213, 0, -41, 214, 4, -41, + 215, 8, -42, 216, 12, -43, 217, 16, -43, 218, 20, -44, + 191, -108, -25, 192, -104, -26, 193, -100, -26, 194, -95, -27, + 195, -91, -28, 196, -87, -28, 197, -83, -29, 198, -79, -30, + 199, -75, -30, 200, -71, -31, 200, -67, -32, 201, -63, -32, + 202, -58, -33, 203, -54, -34, 204, -50, -34, 205, -46, -35, + 206, -42, -36, 207, -38, -36, 208, -34, -37, 209, -30, -38, + 210, -25, -38, 211, -21, -39, 212, -17, -40, 213, -13, -40, + 214, -9, -41, 215, -5, -42, 216, -1, -42, 216, 2, -43, + 217, 6, -44, 218, 11, -44, 219, 15, -45, 220, 19, -46, + 193, -109, -27, 194, -105, -27, 195, -101, -28, 196, -96, -29, + 197, -92, -29, 198, -88, -30, 199, -84, -31, 200, -80, -31, + 201, -76, -32, 202, -72, -33, 203, -68, -33, 204, -64, -34, + 205, -59, -35, 206, -55, -35, 207, -51, -36, 208, -47, -37, + 209, -43, -37, 209, -39, -38, 210, -35, -39, 211, -31, -39, + 212, -26, -40, 213, -22, -41, 214, -18, -41, 215, -14, -42, + 216, -10, -43, 217, -6, -43, 218, -2, -44, 219, 1, -45, + 220, 5, -45, 221, 10, -46, 222, 14, -47, 223, 18, -47, + 196, -110, -28, 197, -106, -29, 198, -102, -30, 199, -98, -30, + 200, -94, -31, 201, -90, -32, 201, -86, -32, 202, -81, -33, + 203, -77, -34, 204, -73, -34, 205, -69, -35, 206, -65, -36, + 207, -61, -36, 208, -57, -37, 209, -53, -38, 210, -49, -38, + 211, -44, -39, 212, -40, -40, 213, -36, -40, 214, -32, -41, + 215, -28, -42, 216, -24, -42, 216, -20, -43, 217, -16, -44, + 218, -12, -44, 219, -7, -45, 220, -3, -46, 221, 0, -46, + 222, 4, -47, 223, 8, -48, 224, 12, -48, 225, 16, -49, + 49, -27, 82, 50, -23, 81, 51, -19, 81, 52, -15, 80, + 53, -11, 79, 54, -7, 79, 54, -3, 78, 55, 1, 77, + 56, 5, 77, 57, 9, 76, 58, 13, 75, 59, 17, 75, + 60, 21, 74, 61, 25, 73, 62, 29, 73, 63, 33, 72, + 64, 38, 71, 65, 42, 71, 66, 46, 70, 67, 50, 69, + 68, 54, 69, 69, 58, 68, 69, 62, 67, 70, 66, 67, + 71, 70, 66, 72, 75, 65, 73, 79, 65, 74, 83, 64, + 75, 87, 63, 76, 91, 63, 77, 95, 62, 78, 99, 61, + 51, -29, 80, 52, -25, 80, 53, -21, 79, 54, -16, 78, + 55, -12, 78, 56, -8, 77, 57, -4, 76, 58, 0, 76, + 59, 3, 75, 60, 7, 74, 61, 11, 74, 61, 15, 73, + 62, 20, 72, 63, 24, 72, 64, 28, 71, 65, 32, 70, + 66, 36, 70, 67, 40, 69, 68, 44, 68, 69, 48, 68, + 70, 53, 67, 71, 57, 66, 72, 61, 66, 73, 65, 65, + 74, 69, 64, 75, 73, 64, 76, 77, 63, 76, 81, 62, + 77, 85, 62, 78, 90, 61, 79, 94, 60, 80, 98, 60, + 54, -30, 79, 54, -26, 78, 55, -22, 77, 56, -17, 77, + 57, -13, 76, 58, -9, 75, 59, -5, 75, 60, -1, 74, + 61, 2, 73, 62, 6, 73, 63, 10, 72, 64, 14, 71, + 65, 19, 71, 66, 23, 70, 67, 27, 69, 68, 31, 69, + 69, 35, 68, 69, 39, 67, 70, 43, 67, 71, 47, 66, + 72, 52, 65, 73, 56, 65, 74, 60, 64, 75, 64, 63, + 76, 68, 63, 77, 72, 62, 78, 76, 61, 79, 80, 61, + 80, 84, 60, 81, 89, 59, 82, 93, 59, 83, 97, 58, + 56, -31, 77, 57, -27, 76, 58, -23, 76, 59, -19, 75, + 60, -15, 74, 61, -11, 74, 61, -7, 73, 62, -2, 72, + 63, 1, 72, 64, 5, 71, 65, 9, 70, 66, 13, 70, + 67, 17, 69, 68, 21, 68, 69, 25, 68, 70, 29, 67, + 71, 34, 66, 72, 38, 66, 73, 42, 65, 74, 46, 64, + 75, 50, 64, 76, 54, 63, 77, 58, 62, 77, 62, 62, + 78, 66, 61, 79, 71, 60, 80, 75, 60, 81, 79, 59, + 82, 83, 58, 83, 87, 58, 84, 91, 57, 85, 95, 56, + 58, -33, 75, 59, -29, 75, 60, -25, 74, 61, -20, 73, + 62, -16, 73, 63, -12, 72, 64, -8, 71, 65, -4, 71, + 66, 0, 70, 67, 3, 69, 68, 7, 69, 68, 11, 68, + 70, 16, 67, 70, 20, 67, 71, 24, 66, 72, 28, 65, + 73, 32, 65, 74, 36, 64, 75, 40, 63, 76, 44, 63, + 77, 49, 62, 78, 53, 61, 79, 57, 61, 80, 61, 60, + 81, 65, 59, 82, 69, 59, 83, 73, 58, 84, 77, 57, + 84, 81, 57, 85, 86, 56, 86, 90, 55, 87, 94, 55, + 61, -34, 74, 61, -30, 73, 62, -26, 72, 63, -21, 72, + 64, -17, 71, 65, -13, 70, 66, -9, 70, 67, -5, 69, + 68, -1, 68, 69, 2, 68, 70, 6, 67, 71, 10, 66, + 72, 15, 66, 73, 19, 65, 74, 23, 64, 75, 27, 64, + 76, 31, 63, 77, 35, 62, 77, 39, 62, 78, 43, 61, + 79, 48, 60, 80, 52, 60, 81, 56, 59, 82, 60, 58, + 83, 64, 58, 84, 68, 57, 85, 72, 56, 86, 76, 56, + 87, 80, 55, 88, 85, 54, 89, 89, 54, 90, 93, 53, + 63, -35, 72, 64, -31, 71, 65, -27, 71, 66, -23, 70, + 67, -19, 69, 68, -15, 69, 69, -11, 68, 70, -6, 67, + 70, -2, 67, 71, 1, 66, 72, 5, 65, 73, 9, 65, + 74, 13, 64, 75, 17, 63, 76, 21, 63, 77, 25, 62, + 78, 30, 61, 79, 34, 61, 80, 38, 60, 81, 42, 59, + 82, 46, 59, 83, 50, 58, 84, 54, 57, 84, 58, 57, + 85, 62, 56, 86, 67, 55, 87, 71, 55, 88, 75, 54, + 89, 79, 53, 90, 83, 53, 91, 87, 52, 92, 91, 51, + 65, -37, 70, 66, -33, 70, 67, -29, 69, 68, -24, 68, + 69, -20, 68, 70, -16, 67, 71, -12, 66, 72, -8, 66, + 73, -4, 65, 74, 0, 64, 75, 3, 64, 76, 7, 63, + 77, 12, 62, 77, 16, 62, 78, 20, 61, 79, 24, 60, + 80, 28, 60, 81, 32, 59, 82, 36, 58, 83, 40, 58, + 84, 45, 57, 85, 49, 56, 86, 53, 56, 87, 57, 55, + 88, 61, 54, 89, 65, 54, 90, 69, 53, 91, 73, 52, + 91, 77, 52, 93, 82, 51, 93, 86, 50, 94, 90, 50, + 68, -38, 69, 69, -34, 68, 69, -30, 67, 70, -25, 67, + 71, -21, 66, 72, -17, 65, 73, -13, 65, 74, -9, 64, + 75, -5, 63, 76, -1, 63, 77, 2, 62, 78, 6, 61, + 79, 11, 61, 80, 15, 60, 81, 19, 59, 82, 23, 59, + 83, 27, 58, 84, 31, 57, 84, 35, 57, 85, 39, 56, + 86, 44, 55, 87, 48, 55, 88, 52, 54, 89, 56, 53, + 90, 60, 53, 91, 64, 52, 92, 68, 51, 93, 72, 51, + 94, 76, 50, 95, 81, 49, 96, 85, 49, 97, 89, 48, + 70, -39, 67, 71, -35, 66, 72, -31, 66, 73, -27, 65, + 74, -23, 64, 75, -19, 64, 76, -15, 63, 77, -10, 62, + 77, -6, 62, 78, -2, 61, 79, 1, 60, 80, 5, 60, + 81, 9, 59, 82, 13, 58, 83, 17, 58, 84, 21, 57, + 85, 26, 56, 86, 30, 56, 87, 34, 55, 88, 38, 54, + 89, 42, 54, 90, 46, 53, 91, 50, 52, 92, 54, 52, + 92, 58, 51, 93, 63, 50, 94, 67, 50, 95, 71, 49, + 96, 75, 48, 97, 79, 47, 98, 83, 47, 99, 87, 46, + 72, -41, 65, 73, -37, 65, 74, -33, 64, 75, -28, 63, + 76, -24, 63, 77, -20, 62, 78, -16, 61, 79, -12, 61, + 80, -8, 60, 81, -4, 59, 82, 0, 59, 83, 3, 58, + 84, 8, 57, 85, 12, 57, 85, 16, 56, 86, 20, 55, + 87, 24, 55, 88, 28, 54, 89, 32, 53, 90, 36, 53, + 91, 41, 52, 92, 45, 51, 93, 49, 51, 94, 53, 50, + 95, 57, 49, 96, 61, 49, 97, 65, 48, 98, 69, 47, + 99, 73, 47, 100, 78, 46, 100, 82, 45, 101, 86, 45, + 75, -42, 63, 76, -38, 63, 77, -34, 62, 78, -30, 61, + 79, -26, 60, 80, -22, 60, 81, -18, 59, 82, -13, 58, + 83, -9, 58, 84, -5, 57, 85, -1, 56, 86, 2, 56, + 87, 6, 55, 87, 10, 54, 88, 14, 54, 89, 18, 53, + 90, 23, 52, 91, 27, 52, 92, 31, 51, 93, 35, 50, + 94, 39, 50, 95, 43, 49, 96, 47, 48, 97, 51, 48, + 98, 55, 47, 99, 60, 46, 100, 64, 46, 101, 68, 45, + 101, 72, 44, 102, 76, 44, 103, 80, 43, 104, 84, 42, + 78, -44, 61, 79, -40, 61, 79, -36, 60, 80, -31, 59, + 81, -27, 59, 82, -23, 58, 83, -19, 58, 84, -15, 57, + 85, -11, 56, 86, -7, 55, 87, -3, 55, 88, 0, 54, + 89, 5, 53, 90, 9, 53, 91, 13, 52, 92, 17, 51, + 93, 21, 51, 94, 25, 50, 94, 29, 49, 95, 33, 49, + 96, 38, 48, 97, 42, 47, 98, 46, 47, 99, 50, 46, + 100, 54, 45, 101, 58, 45, 102, 62, 44, 103, 66, 43, + 104, 70, 43, 105, 75, 42, 106, 79, 41, 107, 83, 41, + 80, -45, 60, 81, -41, 59, 82, -37, 59, 83, -32, 58, + 84, -28, 57, 85, -24, 56, 86, -20, 56, 87, -16, 55, + 87, -12, 54, 88, -8, 54, 89, -4, 53, 90, 0, 52, + 91, 4, 52, 92, 8, 51, 93, 12, 50, 94, 16, 50, + 95, 20, 49, 96, 24, 48, 97, 28, 48, 98, 32, 47, + 99, 37, 46, 100, 41, 46, 101, 45, 45, 101, 49, 44, + 102, 53, 44, 103, 57, 43, 104, 61, 42, 105, 65, 42, + 106, 69, 41, 107, 74, 40, 108, 78, 40, 109, 82, 39, + 82, -46, 58, 83, -42, 57, 84, -38, 57, 85, -34, 56, + 86, -30, 55, 87, -26, 55, 88, -22, 54, 89, -17, 53, + 90, -13, 53, 91, -9, 52, 92, -5, 51, 93, -1, 51, + 94, 2, 50, 94, 6, 49, 95, 10, 49, 96, 14, 48, + 97, 19, 47, 98, 23, 47, 99, 27, 46, 100, 31, 45, + 101, 35, 45, 102, 39, 44, 103, 43, 43, 104, 47, 43, + 105, 51, 42, 106, 56, 41, 107, 60, 41, 108, 64, 40, + 109, 68, 39, 110, 72, 39, 110, 76, 38, 111, 80, 37, + 85, -48, 56, 86, -44, 56, 86, -40, 55, 87, -35, 54, + 88, -31, 54, 89, -27, 53, 90, -23, 52, 91, -19, 52, + 92, -15, 51, 93, -11, 50, 94, -7, 50, 95, -3, 49, + 96, 1, 48, 97, 5, 48, 98, 9, 47, 99, 13, 46, + 100, 17, 46, 101, 21, 45, 102, 25, 44, 102, 29, 44, + 103, 34, 43, 104, 38, 42, 105, 42, 42, 106, 46, 41, + 107, 50, 40, 108, 54, 40, 109, 58, 39, 110, 62, 38, + 111, 66, 38, 112, 71, 37, 113, 75, 36, 114, 79, 36, + 87, -49, 55, 88, -45, 54, 89, -41, 53, 90, -36, 53, + 91, -32, 52, 92, -28, 51, 93, -24, 51, 94, -20, 50, + 95, -16, 49, 95, -12, 49, 96, -8, 48, 97, -4, 47, + 98, 0, 47, 99, 4, 46, 100, 8, 45, 101, 12, 45, + 102, 16, 44, 103, 20, 43, 104, 24, 43, 105, 28, 42, + 106, 33, 41, 107, 37, 41, 108, 41, 40, 109, 45, 39, + 109, 49, 39, 110, 53, 38, 111, 57, 37, 112, 61, 37, + 113, 65, 36, 114, 70, 35, 115, 74, 35, 116, 78, 34, + 89, -50, 53, 90, -46, 52, 91, -42, 52, 92, -38, 51, + 93, -34, 50, 94, -30, 50, 95, -26, 49, 96, -21, 48, + 97, -17, 48, 98, -13, 47, 99, -9, 46, 100, -5, 46, + 101, -1, 45, 102, 2, 44, 102, 6, 44, 103, 10, 43, + 104, 15, 42, 105, 19, 42, 106, 23, 41, 107, 27, 40, + 108, 31, 40, 109, 35, 39, 110, 39, 38, 111, 43, 38, + 112, 47, 37, 113, 52, 36, 114, 56, 36, 115, 60, 35, + 116, 64, 34, 117, 68, 34, 117, 72, 33, 118, 76, 32, + 92, -52, 51, 93, -48, 51, 94, -44, 50, 95, -39, 49, + 95, -35, 49, 96, -31, 48, 97, -27, 47, 98, -23, 47, + 99, -19, 46, 100, -15, 45, 101, -11, 45, 102, -7, 44, + 103, -2, 43, 104, 1, 43, 105, 5, 42, 106, 9, 41, + 107, 13, 41, 108, 17, 40, 109, 21, 39, 109, 25, 39, + 110, 30, 38, 111, 34, 37, 112, 38, 37, 113, 42, 36, + 114, 46, 35, 115, 50, 35, 116, 54, 34, 117, 58, 33, + 118, 62, 33, 119, 67, 32, 120, 71, 31, 121, 75, 31, + 94, -53, 50, 95, -49, 49, 96, -45, 48, 97, -40, 48, + 98, -36, 47, 99, -32, 46, 100, -28, 46, 101, -24, 45, + 102, -20, 44, 102, -16, 44, 103, -12, 43, 104, -8, 42, + 105, -3, 42, 106, 0, 41, 107, 4, 40, 108, 8, 40, + 109, 12, 39, 110, 16, 38, 111, 20, 38, 112, 24, 37, + 113, 29, 36, 114, 33, 36, 115, 37, 35, 116, 41, 34, + 116, 45, 34, 118, 49, 33, 118, 53, 32, 119, 57, 32, + 120, 61, 31, 121, 66, 30, 122, 70, 30, 123, 74, 29, + 96, -54, 48, 97, -50, 47, 98, -46, 47, 99, -42, 46, + 100, -38, 45, 101, -34, 45, 102, -30, 44, 103, -25, 43, + 104, -21, 43, 105, -17, 42, 106, -13, 41, 107, -9, 41, + 108, -5, 40, 109, -1, 39, 109, 2, 39, 110, 6, 38, + 111, 11, 37, 112, 15, 37, 113, 19, 36, 114, 23, 35, + 115, 27, 35, 116, 31, 34, 117, 35, 33, 118, 39, 33, + 119, 43, 32, 120, 48, 31, 121, 52, 31, 122, 56, 30, + 123, 60, 29, 124, 64, 29, 125, 68, 28, 125, 72, 27, + 99, -55, 46, 100, -51, 46, 101, -47, 45, 102, -43, 44, + 102, -39, 44, 103, -35, 43, 104, -31, 42, 105, -26, 42, + 106, -22, 41, 107, -18, 40, 108, -14, 40, 109, -10, 39, + 110, -6, 38, 111, -2, 38, 112, 1, 37, 113, 5, 36, + 114, 10, 36, 115, 14, 35, 116, 18, 34, 117, 22, 34, + 118, 26, 33, 118, 30, 32, 119, 34, 32, 120, 38, 31, + 121, 42, 30, 122, 47, 30, 123, 51, 29, 124, 55, 28, + 125, 59, 28, 126, 63, 27, 127, 67, 26, 128, 71, 26, + 101, -57, 45, 102, -53, 44, 103, -49, 43, 104, -44, 43, + 105, -40, 42, 106, -36, 41, 107, -32, 41, 108, -28, 40, + 109, -24, 39, 110, -20, 39, 110, -16, 38, 111, -12, 37, + 112, -7, 37, 113, -3, 36, 114, 0, 35, 115, 4, 35, + 116, 8, 34, 117, 12, 33, 118, 16, 33, 119, 20, 32, + 120, 25, 31, 121, 29, 31, 122, 33, 30, 123, 37, 29, + 124, 41, 29, 125, 45, 28, 125, 49, 27, 126, 53, 27, + 127, 57, 26, 128, 62, 25, 129, 66, 25, 130, 70, 24, + 103, -58, 43, 104, -54, 42, 105, -50, 42, 106, -46, 41, + 107, -42, 40, 108, -38, 40, 109, -34, 39, 110, -29, 38, + 111, -25, 38, 112, -21, 37, 113, -17, 36, 114, -13, 36, + 115, -9, 35, 116, -5, 34, 117, -1, 34, 117, 2, 33, + 118, 7, 32, 119, 11, 32, 120, 15, 31, 121, 19, 30, + 122, 23, 30, 123, 27, 29, 124, 31, 28, 125, 35, 28, + 126, 39, 27, 127, 44, 26, 128, 48, 26, 129, 52, 25, + 130, 56, 24, 131, 60, 24, 132, 64, 23, 132, 68, 22, + 106, -59, 41, 107, -55, 41, 108, -51, 40, 109, -47, 39, + 110, -43, 39, 110, -39, 38, 111, -35, 37, 112, -30, 37, + 113, -26, 36, 114, -22, 35, 115, -18, 35, 116, -14, 34, + 117, -10, 33, 118, -6, 33, 119, -2, 32, 120, 1, 31, + 121, 6, 31, 122, 10, 30, 123, 14, 29, 124, 18, 29, + 125, 22, 28, 125, 26, 27, 126, 30, 27, 127, 34, 26, + 128, 38, 25, 129, 43, 25, 130, 47, 24, 131, 51, 23, + 132, 55, 23, 133, 59, 22, 134, 63, 21, 135, 67, 21, + 108, -61, 40, 109, -57, 39, 110, -53, 38, 111, -48, 38, + 112, -44, 37, 113, -40, 36, 114, -36, 36, 115, -32, 35, + 116, -28, 34, 117, -24, 34, 117, -20, 33, 118, -16, 32, + 119, -11, 32, 120, -7, 31, 121, -3, 30, 122, 0, 30, + 123, 4, 29, 124, 8, 28, 125, 12, 28, 126, 16, 27, + 127, 21, 26, 128, 25, 26, 129, 29, 25, 130, 33, 24, + 131, 37, 24, 132, 41, 23, 133, 45, 22, 133, 49, 22, + 134, 53, 21, 135, 58, 20, 136, 62, 20, 137, 66, 19, + 110, -62, 38, 111, -58, 37, 112, -54, 37, 113, -50, 36, + 114, -46, 35, 115, -42, 35, 116, -38, 34, 117, -33, 33, + 118, -29, 33, 119, -25, 32, 120, -21, 31, 121, -17, 31, + 122, -13, 30, 123, -9, 29, 124, -5, 29, 124, -1, 28, + 126, 3, 27, 126, 7, 27, 127, 11, 26, 128, 15, 25, + 129, 19, 25, 130, 23, 24, 131, 27, 23, 132, 31, 23, + 133, 35, 22, 134, 40, 21, 135, 44, 21, 136, 48, 20, + 137, 52, 19, 138, 56, 19, 139, 60, 18, 140, 64, 17, + 113, -63, 36, 114, -59, 36, 115, -55, 35, 116, -51, 34, + 117, -47, 34, 117, -43, 33, 118, -39, 32, 119, -34, 32, + 120, -30, 31, 121, -26, 30, 122, -22, 30, 123, -18, 29, + 124, -14, 28, 125, -10, 28, 126, -6, 27, 127, -2, 26, + 128, 2, 26, 129, 6, 25, 130, 10, 24, 131, 14, 24, + 132, 18, 23, 133, 22, 22, 133, 26, 22, 134, 30, 21, + 135, 34, 20, 136, 39, 20, 137, 43, 19, 138, 47, 18, + 139, 51, 18, 140, 55, 17, 141, 59, 16, 142, 63, 16, + 115, -65, 35, 116, -61, 34, 117, -57, 33, 118, -52, 33, + 119, -48, 32, 120, -44, 31, 121, -40, 31, 122, -36, 30, + 123, -32, 29, 124, -28, 29, 125, -24, 28, 125, -20, 27, + 126, -15, 27, 127, -11, 26, 128, -7, 25, 129, -3, 25, + 130, 0, 24, 131, 4, 23, 132, 8, 23, 133, 12, 22, + 134, 17, 21, 135, 21, 21, 136, 25, 20, 137, 29, 19, + 138, 33, 19, 139, 37, 18, 140, 41, 17, 140, 45, 17, + 141, 49, 16, 142, 54, 15, 143, 58, 15, 144, 62, 14, + 118, -66, 33, 118, -62, 32, 119, -58, 32, 120, -54, 31, + 121, -50, 30, 122, -46, 30, 123, -42, 29, 124, -37, 28, + 125, -33, 28, 126, -29, 27, 127, -25, 26, 128, -21, 26, + 129, -17, 25, 130, -13, 24, 131, -9, 24, 132, -5, 23, + 133, 0, 22, 133, 3, 22, 134, 7, 21, 135, 11, 20, + 136, 15, 20, 137, 19, 19, 138, 23, 18, 139, 27, 18, + 140, 31, 17, 141, 36, 16, 142, 40, 16, 143, 44, 15, + 144, 48, 14, 145, 52, 14, 146, 56, 13, 147, 60, 12, + 120, -67, 31, 121, -63, 31, 122, -59, 30, 123, -55, 29, + 124, -51, 29, 125, -47, 28, 125, -43, 27, 126, -38, 27, + 127, -34, 26, 128, -30, 25, 129, -26, 25, 130, -22, 24, + 131, -18, 23, 132, -14, 23, 133, -10, 22, 134, -6, 21, + 135, -1, 21, 136, 2, 20, 137, 6, 19, 138, 10, 19, + 139, 14, 18, 140, 18, 17, 140, 22, 17, 141, 26, 16, + 142, 30, 15, 143, 35, 15, 144, 39, 14, 145, 43, 13, + 146, 47, 13, 147, 51, 12, 148, 55, 11, 149, 59, 11, + 122, -69, 30, 123, -65, 29, 124, -61, 28, 125, -56, 28, + 126, -52, 27, 127, -48, 26, 128, -44, 26, 129, -40, 25, + 130, -36, 24, 131, -32, 24, 132, -28, 23, 132, -24, 22, + 133, -19, 22, 134, -15, 21, 135, -11, 20, 136, -7, 20, + 137, -3, 19, 138, 0, 18, 139, 4, 18, 140, 8, 17, + 141, 13, 16, 142, 17, 16, 143, 21, 15, 144, 25, 14, + 145, 29, 14, 146, 33, 13, 147, 37, 12, 148, 41, 12, + 148, 45, 11, 149, 50, 10, 150, 54, 10, 151, 58, 9, + 125, -70, 28, 126, -66, 27, 127, -62, 26, 128, -58, 26, + 129, -54, 25, 130, -50, 24, 131, -46, 24, 132, -41, 23, + 133, -37, 22, 134, -33, 22, 134, -29, 21, 135, -25, 20, + 136, -21, 20, 137, -17, 19, 138, -13, 18, 139, -9, 18, + 140, -4, 17, 141, 0, 16, 142, 3, 16, 143, 7, 15, + 144, 11, 14, 145, 15, 14, 146, 19, 13, 147, 23, 12, + 148, 27, 12, 149, 32, 11, 150, 36, 10, 150, 40, 10, + 151, 44, 9, 152, 48, 8, 153, 52, 7, 154, 56, 7, + 127, -72, 26, 128, -68, 25, 129, -64, 25, 130, -59, 24, + 131, -55, 23, 132, -51, 23, 133, -47, 22, 134, -43, 21, + 135, -39, 21, 136, -35, 20, 137, -31, 19, 138, -27, 19, + 139, -22, 18, 140, -18, 17, 141, -14, 17, 142, -10, 16, + 143, -6, 15, 143, -2, 15, 144, 1, 14, 145, 5, 13, + 146, 10, 12, 147, 14, 12, 148, 18, 11, 149, 22, 11, + 150, 26, 10, 151, 30, 9, 152, 34, 8, 153, 38, 8, + 154, 42, 7, 155, 47, 6, 156, 51, 6, 157, 55, 5, + 130, -73, 24, 131, -69, 24, 132, -65, 23, 133, -61, 22, + 134, -57, 22, 135, -53, 21, 135, -49, 20, 136, -44, 20, + 137, -40, 19, 138, -36, 18, 139, -32, 18, 140, -28, 17, + 141, -24, 16, 142, -20, 16, 143, -16, 15, 144, -12, 14, + 145, -7, 13, 146, -3, 13, 147, 0, 12, 148, 4, 12, + 149, 8, 11, 150, 12, 10, 150, 16, 10, 151, 20, 9, + 152, 24, 8, 153, 29, 7, 154, 33, 7, 155, 37, 6, + 156, 41, 6, 157, 45, 5, 158, 49, 4, 159, 53, 3, + 132, -74, 23, 133, -70, 22, 134, -66, 21, 135, -62, 21, + 136, -58, 20, 137, -54, 19, 138, -50, 19, 139, -45, 18, + 140, -41, 17, 141, -37, 17, 142, -33, 16, 142, -29, 15, + 143, -25, 14, 144, -21, 14, 145, -17, 13, 146, -13, 13, + 147, -8, 12, 148, -4, 11, 149, 0, 11, 150, 3, 10, + 151, 7, 9, 152, 11, 8, 153, 15, 8, 154, 19, 7, + 155, 23, 7, 156, 28, 6, 157, 32, 5, 157, 36, 4, + 158, 40, 4, 159, 44, 3, 160, 48, 2, 161, 52, 2, + 135, -76, 21, 135, -72, 20, 136, -68, 20, 137, -63, 19, + 138, -59, 18, 139, -55, 18, 140, -51, 17, 141, -47, 16, + 142, -43, 16, 143, -39, 15, 144, -35, 14, 145, -31, 14, + 146, -26, 13, 147, -22, 12, 148, -18, 12, 149, -14, 11, + 150, -10, 10, 150, -6, 9, 151, -2, 9, 152, 1, 8, + 153, 6, 7, 154, 10, 7, 155, 14, 6, 156, 18, 6, + 157, 22, 5, 158, 26, 4, 159, 30, 3, 160, 34, 3, + 161, 38, 2, 162, 43, 1, 163, 47, 1, 164, 51, 0, + 137, -77, 19, 138, -73, 19, 139, -69, 18, 140, -65, 17, + 141, -61, 17, 142, -57, 16, 142, -53, 15, 143, -48, 14, + 144, -44, 14, 145, -40, 13, 146, -36, 13, 147, -32, 12, + 148, -28, 11, 149, -24, 10, 150, -20, 10, 151, -16, 9, + 152, -11, 8, 153, -7, 8, 154, -3, 7, 155, 0, 7, + 156, 4, 6, 157, 8, 5, 158, 12, 4, 158, 16, 4, + 159, 20, 3, 160, 25, 2, 161, 29, 2, 162, 33, 1, + 163, 37, 0, 164, 41, 0, 165, 45, 0, 166, 49, -1, + 139, -78, 18, 140, -74, 17, 141, -70, 16, 142, -66, 15, + 143, -62, 15, 144, -58, 14, 145, -54, 14, 146, -49, 13, + 147, -45, 12, 148, -41, 12, 149, -37, 11, 149, -33, 10, + 151, -29, 9, 151, -25, 9, 152, -21, 8, 153, -17, 8, + 154, -12, 7, 155, -8, 6, 156, -4, 5, 157, 0, 5, + 158, 3, 4, 159, 7, 3, 160, 11, 3, 161, 15, 2, + 162, 19, 2, 163, 24, 1, 164, 28, 0, 165, 32, 0, + 165, 36, 0, 166, 40, -1, 167, 44, -2, 168, 48, -2, + 142, -80, 16, 142, -76, 15, 143, -72, 15, 144, -67, 14, + 145, -63, 13, 146, -59, 13, 147, -55, 12, 148, -51, 11, + 149, -47, 10, 150, -43, 10, 151, -39, 9, 152, -35, 9, + 153, -30, 8, 154, -26, 7, 155, -22, 6, 156, -18, 6, + 157, -14, 5, 158, -10, 4, 158, -6, 4, 159, -2, 3, + 160, 2, 2, 161, 6, 2, 162, 10, 1, 163, 14, 0, + 164, 18, 0, 165, 22, 0, 166, 26, -1, 167, 30, -1, + 168, 34, -2, 169, 39, -3, 170, 43, -3, 171, 47, -4, + 144, -81, 14, 145, -77, 14, 146, -73, 13, 147, -69, 12, + 148, -65, 11, 149, -61, 11, 150, -57, 10, 151, -52, 9, + 151, -48, 9, 152, -44, 8, 153, -40, 8, 154, -36, 7, + 155, -32, 6, 156, -28, 5, 157, -24, 5, 158, -20, 4, + 159, -15, 3, 160, -11, 3, 161, -7, 2, 162, -3, 1, + 163, 0, 1, 164, 4, 0, 165, 8, 0, 165, 12, 0, + 166, 16, -1, 167, 21, -2, 168, 25, -2, 169, 29, -3, + 170, 33, -4, 171, 37, -4, 172, 41, -5, 173, 45, -6, + 146, -82, 12, 147, -78, 12, 148, -74, 11, 149, -70, 10, + 150, -66, 10, 151, -62, 9, 152, -58, 9, 153, -53, 8, + 154, -49, 7, 155, -45, 6, 156, -41, 6, 157, -37, 5, + 158, -33, 4, 158, -29, 4, 159, -25, 3, 160, -21, 2, + 161, -16, 2, 162, -12, 1, 163, -8, 0, 164, -4, 0, + 165, 0, 0, 166, 3, -1, 167, 7, -1, 168, 11, -2, + 169, 15, -3, 170, 20, -3, 171, 24, -4, 172, 28, -5, + 172, 32, -5, 174, 36, -6, 174, 40, -7, 175, 44, -7, + 149, -84, 11, 150, -80, 10, 150, -76, 10, 151, -71, 9, + 152, -67, 8, 153, -63, 7, 154, -59, 7, 155, -55, 6, + 156, -51, 5, 157, -47, 5, 158, -43, 4, 159, -39, 4, + 160, -34, 3, 161, -30, 2, 162, -26, 1, 163, -22, 1, + 164, -18, 0, 165, -14, 0, 165, -10, 0, 166, -6, -1, + 167, -1, -2, 168, 2, -2, 169, 6, -3, 170, 10, -4, + 171, 14, -4, 172, 18, -5, 173, 22, -6, 174, 26, -6, + 175, 30, -7, 176, 35, -8, 177, 39, -8, 178, 43, -9, + 151, -85, 9, 152, -81, 8, 153, -77, 8, 154, -72, 7, + 155, -68, 6, 156, -64, 6, 157, -60, 5, 158, -56, 4, + 158, -52, 4, 159, -48, 3, 160, -44, 2, 161, -40, 2, + 162, -35, 1, 163, -31, 0, 164, -27, 0, 165, -23, 0, + 166, -19, -1, 167, -15, -1, 168, -11, -2, 169, -7, -3, + 170, -2, -3, 171, 1, -4, 172, 5, -5, 173, 9, -5, + 173, 13, -6, 174, 17, -7, 175, 21, -7, 176, 25, -8, + 177, 29, -9, 178, 34, -9, 179, 38, -10, 180, 42, -11, + 153, -86, 7, 154, -82, 7, 155, -78, 6, 156, -74, 5, + 157, -70, 5, 158, -66, 4, 159, -62, 3, 160, -57, 3, + 161, -53, 2, 162, -49, 1, 163, -45, 1, 164, -41, 0, + 165, -37, 0, 166, -33, 0, 166, -29, -1, 167, -25, -2, + 168, -20, -2, 169, -16, -3, 170, -12, -4, 171, -8, -4, + 172, -4, -5, 173, 0, -6, 174, 3, -6, 175, 7, -7, + 176, 11, -8, 177, 16, -8, 178, 20, -9, 179, 24, -10, + 180, 28, -10, 181, 32, -11, 181, 36, -12, 182, 40, -12, + 156, -88, 6, 157, -84, 5, 157, -80, 4, 159, -75, 4, + 159, -71, 3, 160, -67, 2, 161, -63, 2, 162, -59, 1, + 163, -55, 0, 164, -51, 0, 165, -47, 0, 166, -43, -1, + 167, -38, -1, 168, -34, -2, 169, -30, -3, 170, -26, -3, + 171, -22, -4, 172, -18, -5, 173, -14, -5, 173, -10, -6, + 174, -5, -7, 175, -1, -7, 176, 2, -8, 177, 6, -9, + 178, 10, -9, 179, 14, -10, 180, 18, -11, 181, 22, -11, + 182, 26, -12, 183, 31, -13, 184, 35, -13, 185, 39, -14, + 158, -89, 4, 159, -85, 3, 160, -81, 3, 161, -76, 2, + 162, -72, 1, 163, -68, 1, 164, -64, 0, 165, -60, 0, + 166, -56, 0, 166, -52, -1, 167, -48, -2, 168, -44, -2, + 169, -39, -3, 170, -35, -4, 171, -31, -4, 172, -27, -5, + 173, -23, -6, 174, -19, -6, 175, -15, -7, 176, -11, -8, + 177, -6, -8, 178, -2, -9, 179, 1, -10, 180, 5, -10, + 180, 9, -11, 182, 13, -12, 182, 17, -12, 183, 21, -13, + 184, 25, -14, 185, 30, -14, 186, 34, -15, 187, 38, -16, + 160, -90, 2, 161, -86, 2, 162, -82, 1, 163, -78, 0, + 164, -74, 0, 165, -70, 0, 166, -66, -1, 167, -61, -1, + 168, -57, -2, 169, -53, -3, 170, -49, -3, 171, -45, -4, + 172, -41, -5, 173, -37, -5, 173, -33, -6, 174, -29, -7, + 175, -24, -7, 176, -20, -8, 177, -16, -9, 178, -12, -9, + 179, -8, -10, 180, -4, -11, 181, 0, -11, 182, 3, -12, + 183, 7, -13, 184, 12, -13, 185, 16, -14, 186, 20, -15, + 187, 24, -15, 188, 28, -16, 189, 32, -17, 189, 36, -17, + 163, -92, 1, 164, -88, 0, 165, -84, 0, 166, -79, 0, + 166, -75, -1, 167, -71, -2, 168, -67, -2, 169, -63, -3, + 170, -59, -4, 171, -55, -4, 172, -51, -5, 173, -47, -6, + 174, -42, -6, 175, -38, -7, 176, -34, -8, 177, -30, -8, + 178, -26, -9, 179, -22, -10, 180, -18, -10, 180, -14, -11, + 182, -9, -12, 182, -5, -12, 183, -1, -13, 184, 2, -14, + 185, 6, -14, 186, 10, -15, 187, 14, -16, 188, 18, -16, + 189, 22, -17, 190, 27, -18, 191, 31, -18, 192, 35, -19, + 165, -93, 0, 166, -89, -1, 167, -85, -1, 168, -80, -2, + 169, -76, -3, 170, -72, -3, 171, -68, -4, 172, -64, -5, + 173, -60, -5, 173, -56, -6, 174, -52, -7, 175, -48, -7, + 176, -43, -8, 177, -39, -9, 178, -35, -9, 179, -31, -10, + 180, -27, -11, 181, -23, -11, 182, -19, -12, 183, -15, -13, + 184, -10, -13, 185, -6, -14, 186, -2, -15, 187, 1, -15, + 188, 5, -16, 189, 9, -17, 189, 13, -17, 190, 17, -18, + 191, 21, -19, 192, 26, -19, 193, 30, -20, 194, 34, -21, + 167, -94, -2, 168, -90, -2, 169, -86, -3, 170, -82, -4, + 171, -78, -4, 172, -74, -5, 173, -70, -6, 174, -65, -6, + 175, -61, -7, 176, -57, -8, 177, -53, -8, 178, -49, -9, + 179, -45, -10, 180, -41, -10, 181, -37, -11, 181, -33, -12, + 182, -28, -12, 183, -24, -13, 184, -20, -14, 185, -16, -14, + 186, -12, -15, 187, -8, -16, 188, -4, -16, 189, 0, -17, + 190, 3, -18, 191, 8, -18, 192, 12, -19, 193, 16, -20, + 194, 20, -20, 195, 24, -21, 196, 28, -22, 196, 32, -22, + 170, -96, -3, 171, -92, -4, 172, -88, -5, 173, -83, -5, + 174, -79, -6, 174, -75, -7, 175, -71, -7, 176, -67, -8, + 177, -63, -9, 178, -59, -9, 179, -55, -10, 180, -51, -11, + 181, -46, -11, 182, -42, -12, 183, -38, -13, 184, -34, -13, + 185, -30, -14, 186, -26, -15, 187, -22, -15, 188, -18, -16, + 189, -13, -17, 189, -9, -17, 190, -5, -18, 191, -1, -19, + 192, 2, -19, 193, 6, -20, 194, 10, -21, 195, 14, -21, + 196, 18, -22, 197, 23, -23, 198, 27, -23, 199, 31, -24, + 172, -97, -5, 173, -93, -6, 174, -89, -6, 175, -84, -7, + 176, -80, -8, 177, -76, -8, 178, -72, -9, 179, -68, -10, + 180, -64, -10, 181, -60, -11, 181, -56, -12, 182, -52, -12, + 183, -47, -13, 184, -43, -14, 185, -39, -14, 186, -35, -15, + 187, -31, -16, 188, -27, -16, 189, -23, -17, 190, -19, -18, + 191, -14, -18, 192, -10, -19, 193, -6, -20, 194, -2, -20, + 195, 1, -21, 196, 5, -22, 197, 9, -22, 197, 13, -23, + 198, 17, -24, 199, 22, -24, 200, 26, -25, 201, 30, -26, + 175, -99, -7, 176, -95, -8, 177, -91, -8, 178, -86, -9, + 179, -82, -10, 180, -78, -10, 181, -74, -11, 182, -70, -12, + 183, -66, -12, 183, -62, -13, 184, -58, -14, 185, -54, -14, + 186, -49, -15, 187, -45, -16, 188, -41, -16, 189, -37, -17, + 190, -33, -18, 191, -29, -18, 192, -25, -19, 193, -21, -20, + 194, -16, -20, 195, -12, -21, 196, -8, -22, 197, -4, -22, + 197, 0, -23, 199, 3, -24, 199, 7, -24, 200, 11, -25, + 201, 15, -26, 202, 20, -26, 203, 24, -27, 204, 28, -28, + 177, -100, -9, 178, -96, -9, 179, -92, -10, 180, -87, -11, + 181, -83, -11, 182, -79, -12, 183, -75, -13, 184, -71, -13, + 185, -67, -14, 186, -63, -15, 187, -59, -15, 188, -55, -16, + 189, -50, -17, 190, -46, -17, 190, -42, -18, 191, -38, -19, + 192, -34, -19, 193, -30, -20, 194, -26, -21, 195, -22, -21, + 196, -17, -22, 197, -13, -23, 198, -9, -23, 199, -5, -24, + 200, -1, -25, 201, 2, -25, 202, 6, -26, 203, 10, -27, + 204, 14, -27, 205, 19, -28, 206, 23, -29, 206, 27, -29, + 180, -101, -10, 181, -97, -11, 182, -93, -12, 183, -89, -12, + 183, -85, -13, 184, -81, -14, 185, -77, -14, 186, -72, -15, + 187, -68, -16, 188, -64, -16, 189, -60, -17, 190, -56, -18, + 191, -52, -18, 192, -48, -19, 193, -44, -20, 194, -40, -20, + 195, -35, -21, 196, -31, -22, 197, -27, -22, 198, -23, -23, + 199, -19, -24, 199, -15, -24, 200, -11, -25, 201, -7, -26, + 202, -3, -26, 203, 1, -27, 204, 5, -28, 205, 9, -28, + 206, 13, -29, 207, 17, -30, 208, 21, -30, 209, 25, -31, + 182, -103, -12, 183, -99, -13, 184, -95, -13, 185, -90, -14, + 186, -86, -15, 187, -82, -15, 188, -78, -16, 189, -74, -17, + 190, -70, -17, 191, -66, -18, 191, -62, -19, 192, -58, -19, + 193, -53, -20, 194, -49, -21, 195, -45, -21, 196, -41, -22, + 197, -37, -23, 198, -33, -23, 199, -29, -24, 200, -25, -25, + 201, -20, -25, 202, -16, -26, 203, -12, -27, 204, -8, -27, + 205, -4, -28, 206, 0, -29, 206, 3, -29, 207, 7, -30, + 208, 11, -31, 209, 16, -31, 210, 20, -32, 211, 24, -33, + 184, -104, -14, 185, -100, -14, 186, -96, -15, 187, -91, -16, + 188, -87, -16, 189, -83, -17, 190, -79, -18, 191, -75, -18, + 192, -71, -19, 193, -67, -20, 194, -63, -20, 195, -59, -21, + 196, -54, -22, 197, -50, -22, 198, -46, -23, 198, -42, -24, + 199, -38, -24, 200, -34, -25, 201, -30, -26, 202, -26, -26, + 203, -21, -27, 204, -17, -28, 205, -13, -28, 206, -9, -29, + 207, -5, -30, 208, -1, -30, 209, 2, -31, 210, 6, -32, + 211, 10, -32, 212, 15, -33, 213, 19, -34, 214, 23, -34, + 187, -105, -15, 188, -101, -16, 189, -97, -17, 190, -93, -17, + 191, -89, -18, 191, -85, -19, 192, -81, -19, 193, -76, -20, + 194, -72, -21, 195, -68, -21, 196, -64, -22, 197, -60, -23, + 198, -56, -23, 199, -52, -24, 200, -48, -25, 201, -44, -25, + 202, -39, -26, 203, -35, -27, 204, -31, -27, 205, -27, -28, + 206, -23, -29, 207, -19, -29, 207, -15, -30, 208, -11, -31, + 209, -7, -31, 210, -2, -32, 211, 1, -33, 212, 5, -33, + 213, 9, -34, 214, 13, -35, 215, 17, -35, 216, 21, -36, + 189, -107, -17, 190, -103, -18, 191, -99, -18, 192, -94, -19, + 193, -90, -20, 194, -86, -20, 195, -82, -21, 196, -78, -22, + 197, -74, -22, 198, -70, -23, 198, -66, -24, 199, -62, -24, + 200, -57, -25, 201, -53, -26, 202, -49, -26, 203, -45, -27, + 204, -41, -28, 205, -37, -28, 206, -33, -29, 207, -29, -30, + 208, -24, -30, 209, -20, -31, 210, -16, -32, 211, -12, -32, + 212, -8, -33, 213, -4, -34, 214, 0, -34, 214, 3, -35, + 215, 7, -36, 216, 12, -37, 217, 16, -37, 218, 20, -38, + 191, -108, -19, 192, -104, -19, 193, -100, -20, 194, -95, -21, + 195, -91, -21, 196, -87, -22, 197, -83, -23, 198, -79, -23, + 199, -75, -24, 200, -71, -25, 201, -67, -25, 202, -63, -26, + 203, -58, -27, 204, -54, -27, 205, -50, -28, 205, -46, -29, + 207, -42, -29, 207, -38, -30, 208, -34, -31, 209, -30, -31, + 210, -25, -32, 211, -21, -33, 212, -17, -33, 213, -13, -34, + 214, -9, -35, 215, -5, -35, 216, -1, -36, 217, 2, -37, + 218, 6, -37, 219, 11, -38, 220, 15, -39, 221, 19, -39, + 194, -109, -20, 195, -105, -21, 196, -101, -22, 197, -97, -22, + 198, -93, -23, 198, -89, -24, 199, -85, -24, 200, -80, -25, + 201, -76, -26, 202, -72, -26, 203, -68, -27, 204, -64, -28, + 205, -60, -28, 206, -56, -29, 207, -52, -30, 208, -48, -30, + 209, -43, -31, 210, -39, -32, 211, -35, -32, 212, -31, -33, + 213, -27, -34, 214, -23, -34, 214, -19, -35, 215, -15, -36, + 216, -11, -36, 217, -6, -37, 218, -2, -38, 219, 1, -38, + 220, 5, -39, 221, 9, -40, 222, 13, -41, 223, 17, -41, + 196, -110, -22, 197, -106, -23, 198, -102, -23, 199, -98, -24, + 200, -94, -25, 201, -90, -25, 202, -86, -26, 203, -81, -27, + 204, -77, -27, 205, -73, -28, 206, -69, -29, 206, -65, -29, + 207, -61, -30, 208, -57, -31, 209, -53, -31, 210, -49, -32, + 211, -44, -33, 212, -40, -33, 213, -36, -34, 214, -32, -35, + 215, -28, -36, 216, -24, -36, 217, -20, -37, 218, -16, -37, + 219, -12, -38, 220, -7, -39, 221, -3, -39, 221, 0, -40, + 222, 4, -41, 223, 8, -42, 224, 12, -42, 225, 16, -43, + 199, -112, -24, 199, -108, -24, 200, -104, -25, 201, -99, -26, + 202, -95, -26, 203, -91, -27, 204, -87, -28, 205, -83, -28, + 206, -79, -29, 207, -75, -30, 208, -71, -30, 209, -67, -31, + 210, -62, -32, 211, -58, -32, 212, -54, -33, 213, -50, -34, + 214, -46, -34, 214, -42, -35, 215, -38, -36, 216, -34, -36, + 217, -29, -37, 218, -25, -38, 219, -21, -38, 220, -17, -39, + 221, -13, -40, 222, -9, -41, 223, -5, -41, 224, -1, -42, + 225, 2, -42, 226, 7, -43, 227, 11, -44, 228, 15, -45, + 51, -29, 86, 52, -25, 85, 53, -21, 85, 54, -16, 84, + 55, -12, 83, 56, -8, 83, 57, -4, 82, 58, 0, 81, + 59, 3, 81, 60, 7, 80, 61, 11, 79, 61, 15, 79, + 63, 20, 78, 63, 24, 77, 64, 28, 77, 65, 32, 76, + 66, 36, 75, 67, 40, 75, 68, 44, 74, 69, 48, 73, + 70, 53, 73, 71, 57, 72, 72, 61, 71, 73, 65, 71, + 74, 69, 70, 75, 73, 69, 76, 77, 69, 77, 81, 68, + 77, 85, 67, 78, 90, 67, 79, 94, 66, 80, 98, 65, + 54, -30, 84, 54, -26, 84, 55, -22, 83, 56, -18, 82, + 57, -14, 82, 58, -10, 81, 59, -6, 80, 60, -1, 80, + 61, 2, 79, 62, 6, 78, 63, 10, 78, 64, 14, 77, + 65, 18, 76, 66, 22, 76, 67, 26, 75, 68, 30, 74, + 69, 35, 74, 70, 39, 73, 70, 43, 72, 71, 47, 72, + 72, 51, 71, 73, 55, 70, 74, 59, 70, 75, 63, 69, + 76, 67, 68, 77, 72, 68, 78, 76, 67, 79, 80, 66, + 80, 84, 66, 81, 88, 65, 82, 92, 64, 83, 96, 64, + 56, -31, 83, 57, -27, 82, 58, -23, 81, 59, -19, 81, + 60, -15, 80, 61, -11, 79, 62, -7, 79, 63, -2, 78, + 63, 1, 77, 64, 5, 77, 65, 9, 76, 66, 13, 75, + 67, 17, 75, 68, 21, 74, 69, 25, 73, 70, 29, 73, + 71, 34, 72, 72, 38, 71, 73, 42, 71, 74, 46, 70, + 75, 50, 69, 76, 54, 69, 77, 58, 68, 77, 62, 67, + 78, 66, 67, 79, 71, 66, 80, 75, 65, 81, 79, 65, + 82, 83, 64, 83, 87, 63, 84, 91, 63, 85, 95, 62, + 58, -33, 81, 59, -29, 80, 60, -25, 80, 61, -20, 79, + 62, -16, 78, 63, -12, 78, 64, -8, 77, 65, -4, 76, + 66, 0, 76, 67, 3, 75, 68, 7, 74, 69, 11, 74, + 70, 16, 73, 70, 20, 72, 71, 24, 72, 72, 28, 71, + 73, 32, 70, 74, 36, 70, 75, 40, 69, 76, 44, 68, + 77, 49, 68, 78, 53, 67, 79, 57, 66, 80, 61, 66, + 81, 65, 65, 82, 69, 64, 83, 73, 64, 84, 77, 63, + 84, 81, 62, 86, 86, 62, 86, 90, 61, 87, 94, 60, + 61, -34, 79, 62, -30, 79, 62, -26, 78, 63, -21, 77, + 64, -17, 77, 65, -13, 76, 66, -9, 75, 67, -5, 75, + 68, -1, 74, 69, 2, 73, 70, 6, 73, 71, 10, 72, + 72, 15, 71, 73, 19, 71, 74, 23, 70, 75, 27, 69, + 76, 31, 69, 77, 35, 68, 77, 39, 67, 78, 43, 67, + 79, 48, 66, 80, 52, 65, 81, 56, 65, 82, 60, 64, + 83, 64, 63, 84, 68, 63, 85, 72, 62, 86, 76, 61, + 87, 80, 61, 88, 85, 60, 89, 89, 59, 90, 93, 59, + 63, -35, 78, 64, -31, 77, 65, -27, 76, 66, -23, 76, + 67, -19, 75, 68, -15, 74, 69, -11, 74, 70, -6, 73, + 70, -2, 72, 71, 1, 72, 72, 5, 71, 73, 9, 70, + 74, 13, 70, 75, 17, 69, 76, 21, 68, 77, 25, 68, + 78, 30, 67, 79, 34, 66, 80, 38, 66, 81, 42, 65, + 82, 46, 64, 83, 50, 64, 84, 54, 63, 85, 58, 62, + 85, 62, 62, 86, 67, 61, 87, 71, 60, 88, 75, 60, + 89, 79, 59, 90, 83, 58, 91, 87, 58, 92, 91, 57, + 65, -37, 76, 66, -33, 75, 67, -29, 75, 68, -24, 74, + 69, -20, 73, 70, -16, 73, 71, -12, 72, 72, -8, 71, + 73, -4, 71, 74, 0, 70, 75, 3, 69, 76, 7, 69, + 77, 12, 68, 78, 16, 67, 78, 20, 67, 79, 24, 66, + 80, 28, 65, 81, 32, 65, 82, 36, 64, 83, 40, 63, + 84, 45, 63, 85, 49, 62, 86, 53, 61, 87, 57, 61, + 88, 61, 60, 89, 65, 59, 90, 69, 59, 91, 73, 58, + 92, 77, 57, 93, 82, 57, 93, 86, 56, 94, 90, 55, + 68, -38, 74, 69, -34, 74, 69, -30, 73, 71, -25, 72, + 71, -21, 72, 72, -17, 71, 73, -13, 70, 74, -9, 70, + 75, -5, 69, 76, -1, 68, 77, 2, 68, 78, 6, 67, + 79, 11, 66, 80, 15, 66, 81, 19, 65, 82, 23, 64, + 83, 27, 64, 84, 31, 63, 85, 35, 62, 85, 39, 62, + 86, 44, 61, 87, 48, 60, 88, 52, 60, 89, 56, 59, + 90, 60, 58, 91, 64, 58, 92, 68, 57, 93, 72, 56, + 94, 76, 56, 95, 81, 55, 96, 85, 54, 97, 89, 54, + 70, -39, 73, 71, -35, 72, 72, -31, 71, 73, -27, 71, + 74, -23, 70, 75, -19, 69, 76, -15, 69, 77, -10, 68, + 78, -6, 67, 78, -2, 67, 79, 1, 66, 80, 5, 65, + 81, 9, 65, 82, 13, 64, 83, 17, 63, 84, 21, 63, + 85, 26, 62, 86, 30, 61, 87, 34, 61, 88, 38, 60, + 89, 42, 59, 90, 46, 59, 91, 50, 58, 92, 54, 57, + 92, 58, 57, 93, 63, 56, 94, 67, 55, 95, 71, 55, + 96, 75, 54, 97, 79, 53, 98, 83, 53, 99, 87, 52, + 72, -41, 71, 73, -37, 70, 74, -33, 70, 75, -28, 69, + 76, -24, 68, 77, -20, 68, 78, -16, 67, 79, -12, 66, + 80, -8, 66, 81, -4, 65, 82, 0, 64, 83, 3, 64, + 84, 8, 63, 85, 12, 62, 85, 16, 62, 86, 20, 61, + 87, 24, 60, 88, 28, 60, 89, 32, 59, 90, 36, 58, + 91, 41, 58, 92, 45, 57, 93, 49, 56, 94, 53, 56, + 95, 57, 55, 96, 61, 54, 97, 65, 54, 98, 69, 53, + 99, 73, 52, 100, 78, 51, 101, 82, 51, 101, 86, 50, + 75, -42, 69, 76, -38, 69, 77, -34, 68, 78, -29, 67, + 78, -25, 67, 79, -21, 66, 80, -17, 65, 81, -13, 65, + 82, -9, 64, 83, -5, 63, 84, -1, 63, 85, 2, 62, + 86, 7, 61, 87, 11, 61, 88, 15, 60, 89, 19, 59, + 90, 23, 59, 91, 27, 58, 92, 31, 57, 92, 35, 57, + 94, 40, 56, 94, 44, 55, 95, 48, 55, 96, 52, 54, + 97, 56, 53, 98, 60, 53, 99, 64, 52, 100, 68, 51, + 101, 72, 51, 102, 77, 50, 103, 81, 49, 104, 85, 49, + 78, -44, 67, 79, -40, 67, 79, -36, 66, 80, -31, 65, + 81, -27, 64, 82, -23, 64, 83, -19, 63, 84, -15, 62, + 85, -11, 62, 86, -7, 61, 87, -3, 60, 88, 0, 60, + 89, 5, 59, 90, 9, 58, 91, 13, 58, 92, 17, 57, + 93, 21, 56, 94, 25, 56, 95, 29, 55, 95, 33, 54, + 96, 38, 54, 97, 42, 53, 98, 46, 52, 99, 50, 52, + 100, 54, 51, 101, 58, 50, 102, 62, 50, 103, 66, 49, + 104, 70, 48, 105, 75, 48, 106, 79, 47, 107, 83, 46, + 80, -45, 65, 81, -41, 65, 82, -37, 64, 83, -32, 63, + 84, -28, 63, 85, -24, 62, 86, -20, 61, 87, -16, 61, + 88, -12, 60, 88, -8, 59, 89, -4, 59, 90, 0, 58, + 91, 4, 57, 92, 8, 57, 93, 12, 56, 94, 16, 55, + 95, 20, 55, 96, 24, 54, 97, 28, 53, 98, 32, 53, + 99, 37, 52, 100, 41, 51, 101, 45, 51, 102, 49, 50, + 102, 53, 49, 103, 57, 49, 104, 61, 48, 105, 65, 47, + 106, 69, 47, 107, 74, 46, 108, 78, 45, 109, 82, 45, + 82, -46, 64, 83, -42, 63, 84, -38, 63, 85, -34, 62, + 86, -30, 61, 87, -26, 60, 88, -22, 60, 89, -17, 59, + 90, -13, 58, 91, -9, 58, 92, -5, 57, 93, -1, 56, + 94, 2, 56, 95, 6, 55, 95, 10, 54, 96, 14, 54, + 97, 19, 53, 98, 23, 52, 99, 27, 52, 100, 31, 51, + 101, 35, 50, 102, 39, 50, 103, 43, 49, 104, 47, 48, + 105, 51, 48, 106, 56, 47, 107, 60, 46, 108, 64, 46, + 109, 68, 45, 110, 72, 44, 110, 76, 44, 111, 80, 43, + 85, -48, 62, 86, -44, 61, 87, -40, 61, 88, -35, 60, + 88, -31, 59, 89, -27, 59, 90, -23, 58, 91, -19, 57, + 92, -15, 57, 93, -11, 56, 94, -7, 55, 95, -3, 55, + 96, 1, 54, 97, 5, 53, 98, 9, 53, 99, 13, 52, + 100, 17, 51, 101, 21, 51, 102, 25, 50, 102, 29, 49, + 103, 34, 49, 104, 38, 48, 105, 42, 47, 106, 46, 47, + 107, 50, 46, 108, 54, 45, 109, 58, 45, 110, 62, 44, + 111, 66, 43, 112, 71, 43, 113, 75, 42, 114, 79, 41, + 87, -49, 60, 88, -45, 60, 89, -41, 59, 90, -36, 58, + 91, -32, 58, 92, -28, 57, 93, -24, 56, 94, -20, 56, + 95, -16, 55, 95, -12, 54, 96, -8, 54, 97, -4, 53, + 98, 0, 52, 99, 4, 52, 100, 8, 51, 101, 12, 50, + 102, 16, 50, 103, 20, 49, 104, 24, 48, 105, 28, 48, + 106, 33, 47, 107, 37, 46, 108, 41, 46, 109, 45, 45, + 109, 49, 44, 111, 53, 44, 111, 57, 43, 112, 61, 42, + 113, 65, 42, 114, 70, 41, 115, 74, 40, 116, 78, 40, + 89, -50, 59, 90, -46, 58, 91, -42, 57, 92, -38, 57, + 93, -34, 56, 94, -30, 55, 95, -26, 55, 96, -21, 54, + 97, -17, 53, 98, -13, 53, 99, -9, 52, 100, -5, 51, + 101, -1, 51, 102, 2, 50, 102, 6, 49, 103, 10, 49, + 104, 15, 48, 105, 19, 47, 106, 23, 47, 107, 27, 46, + 108, 31, 45, 109, 35, 45, 110, 39, 44, 111, 43, 43, + 112, 47, 43, 113, 52, 42, 114, 56, 41, 115, 60, 41, + 116, 64, 40, 117, 68, 39, 118, 72, 39, 118, 76, 38, + 92, -52, 57, 93, -48, 56, 94, -44, 56, 95, -39, 55, + 95, -35, 54, 96, -31, 54, 97, -27, 53, 98, -23, 52, + 99, -19, 52, 100, -15, 51, 101, -11, 50, 102, -7, 50, + 103, -2, 49, 104, 1, 48, 105, 5, 48, 106, 9, 47, + 107, 13, 46, 108, 17, 46, 109, 21, 45, 110, 25, 44, + 111, 30, 44, 111, 34, 43, 112, 38, 42, 113, 42, 42, + 114, 46, 41, 115, 50, 40, 116, 54, 40, 117, 58, 39, + 118, 62, 38, 119, 67, 38, 120, 71, 37, 121, 75, 36, + 94, -53, 55, 95, -49, 55, 96, -45, 54, 97, -40, 53, + 98, -36, 53, 99, -32, 52, 100, -28, 51, 101, -24, 51, + 102, -20, 50, 103, -16, 49, 103, -12, 49, 104, -8, 48, + 105, -3, 47, 106, 0, 47, 107, 4, 46, 108, 8, 45, + 109, 12, 45, 110, 16, 44, 111, 20, 43, 112, 24, 43, + 113, 29, 42, 114, 33, 41, 115, 37, 41, 116, 41, 40, + 117, 45, 39, 118, 49, 39, 118, 53, 38, 119, 57, 37, + 120, 61, 37, 121, 66, 36, 122, 70, 35, 123, 74, 35, + 96, -54, 54, 97, -50, 53, 98, -46, 52, 99, -42, 52, + 100, -38, 51, 101, -34, 50, 102, -30, 50, 103, -25, 49, + 104, -21, 48, 105, -17, 48, 106, -13, 47, 107, -9, 46, + 108, -5, 46, 109, -1, 45, 110, 2, 44, 110, 6, 44, + 111, 11, 43, 112, 15, 42, 113, 19, 42, 114, 23, 41, + 115, 27, 40, 116, 31, 40, 117, 35, 39, 118, 39, 38, + 119, 43, 38, 120, 48, 37, 121, 52, 36, 122, 56, 36, + 123, 60, 35, 124, 64, 34, 125, 68, 34, 125, 72, 33, + 99, -56, 52, 100, -52, 51, 101, -48, 51, 102, -43, 50, + 103, -39, 49, 103, -35, 49, 104, -31, 48, 105, -27, 47, + 106, -23, 47, 107, -19, 46, 108, -15, 45, 109, -11, 45, + 110, -6, 44, 111, -2, 43, 112, 1, 43, 113, 5, 42, + 114, 9, 41, 115, 13, 41, 116, 17, 40, 117, 21, 39, + 118, 26, 39, 118, 30, 38, 119, 34, 37, 120, 38, 37, + 121, 42, 36, 122, 46, 35, 123, 50, 35, 124, 54, 34, + 125, 58, 33, 126, 63, 33, 127, 67, 32, 128, 71, 31, + 101, -57, 50, 102, -53, 50, 103, -49, 49, 104, -44, 48, + 105, -40, 48, 106, -36, 47, 107, -32, 46, 108, -28, 46, + 109, -24, 45, 110, -20, 44, 110, -16, 44, 111, -12, 43, + 112, -7, 42, 113, -3, 42, 114, 0, 41, 115, 4, 40, + 116, 8, 40, 117, 12, 39, 118, 16, 38, 119, 20, 38, + 120, 25, 37, 121, 29, 36, 122, 33, 36, 123, 37, 35, + 124, 41, 34, 125, 45, 34, 126, 49, 33, 126, 53, 32, + 127, 57, 32, 128, 62, 31, 129, 66, 30, 130, 70, 30, + 103, -58, 49, 104, -54, 48, 105, -50, 47, 106, -46, 47, + 107, -42, 46, 108, -38, 45, 109, -34, 45, 110, -29, 44, + 111, -25, 43, 112, -21, 43, 113, -17, 42, 114, -13, 41, + 115, -9, 41, 116, -5, 40, 117, -1, 39, 117, 2, 39, + 119, 7, 38, 119, 11, 37, 120, 15, 37, 121, 19, 36, + 122, 23, 35, 123, 27, 35, 124, 31, 34, 125, 35, 33, + 126, 39, 33, 127, 44, 32, 128, 48, 31, 129, 52, 31, + 130, 56, 30, 131, 60, 29, 132, 64, 29, 133, 68, 28, + 106, -59, 47, 107, -55, 46, 108, -51, 46, 109, -47, 45, + 110, -43, 44, 110, -39, 44, 111, -35, 43, 112, -30, 42, + 113, -26, 42, 114, -22, 41, 115, -18, 40, 116, -14, 40, + 117, -10, 39, 118, -6, 38, 119, -2, 38, 120, 1, 37, + 121, 6, 36, 122, 10, 36, 123, 14, 35, 124, 18, 34, + 125, 22, 34, 126, 26, 33, 126, 30, 32, 127, 34, 32, + 128, 38, 31, 129, 43, 30, 130, 47, 30, 131, 51, 29, + 132, 55, 28, 133, 59, 28, 134, 63, 27, 135, 67, 26, + 108, -61, 45, 109, -57, 45, 110, -53, 44, 111, -48, 43, + 112, -44, 43, 113, -40, 42, 114, -36, 41, 115, -32, 41, + 116, -28, 40, 117, -24, 39, 118, -20, 39, 118, -16, 38, + 119, -11, 37, 120, -7, 37, 121, -3, 36, 122, 0, 35, + 123, 4, 35, 124, 8, 34, 125, 12, 33, 126, 16, 33, + 127, 21, 32, 128, 25, 31, 129, 29, 31, 130, 33, 30, + 131, 37, 29, 132, 41, 29, 133, 45, 28, 133, 49, 27, + 134, 53, 27, 135, 58, 26, 136, 62, 25, 137, 66, 25, + 111, -62, 44, 111, -58, 43, 112, -54, 42, 113, -50, 42, + 114, -46, 41, 115, -42, 40, 116, -38, 40, 117, -33, 39, + 118, -29, 38, 119, -25, 38, 120, -21, 37, 121, -17, 36, + 122, -13, 36, 123, -9, 35, 124, -5, 34, 125, -1, 34, + 126, 3, 33, 126, 7, 32, 127, 11, 32, 128, 15, 31, + 129, 19, 30, 130, 23, 30, 131, 27, 29, 132, 31, 28, + 133, 35, 28, 134, 40, 27, 135, 44, 26, 136, 48, 26, + 137, 52, 25, 138, 56, 24, 139, 60, 24, 140, 64, 23, + 113, -63, 42, 114, -59, 41, 115, -55, 41, 116, -51, 40, + 117, -47, 39, 118, -43, 39, 118, -39, 38, 119, -34, 37, + 120, -30, 37, 121, -26, 36, 122, -22, 35, 123, -18, 35, + 124, -14, 34, 125, -10, 33, 126, -6, 33, 127, -2, 32, + 128, 2, 31, 129, 6, 31, 130, 10, 30, 131, 14, 29, + 132, 18, 29, 133, 22, 28, 133, 26, 27, 134, 30, 27, + 135, 34, 26, 136, 39, 25, 137, 43, 25, 138, 47, 24, + 139, 51, 23, 140, 55, 23, 141, 59, 22, 142, 63, 21, + 115, -65, 40, 116, -61, 40, 117, -57, 39, 118, -52, 38, + 119, -48, 38, 120, -44, 37, 121, -40, 36, 122, -36, 36, + 123, -32, 35, 124, -28, 34, 125, -24, 34, 125, -20, 33, + 126, -15, 32, 127, -11, 32, 128, -7, 31, 129, -3, 30, + 130, 0, 30, 131, 4, 29, 132, 8, 28, 133, 12, 28, + 134, 17, 27, 135, 21, 26, 136, 25, 26, 137, 29, 25, + 138, 33, 24, 139, 37, 24, 140, 41, 23, 141, 45, 22, + 141, 49, 22, 142, 54, 21, 143, 58, 20, 144, 62, 20, + 118, -66, 39, 118, -62, 38, 119, -58, 37, 120, -54, 37, + 121, -50, 36, 122, -46, 35, 123, -42, 35, 124, -37, 34, + 125, -33, 33, 126, -29, 33, 127, -25, 32, 128, -21, 31, + 129, -17, 31, 130, -13, 30, 131, -9, 29, 132, -5, 29, + 133, 0, 28, 134, 3, 27, 134, 7, 27, 135, 11, 26, + 136, 15, 25, 137, 19, 25, 138, 23, 24, 139, 27, 23, + 140, 31, 23, 141, 36, 22, 142, 40, 21, 143, 44, 21, + 144, 48, 20, 145, 52, 19, 146, 56, 19, 147, 60, 18, + 120, -67, 37, 121, -63, 36, 122, -59, 36, 123, -55, 35, + 124, -51, 34, 125, -47, 34, 125, -43, 33, 127, -38, 32, + 127, -34, 32, 128, -30, 31, 129, -26, 30, 130, -22, 30, + 131, -18, 29, 132, -14, 28, 133, -10, 28, 134, -6, 27, + 135, -1, 26, 136, 2, 26, 137, 6, 25, 138, 10, 24, + 139, 14, 24, 140, 18, 23, 141, 22, 22, 141, 26, 22, + 142, 30, 21, 143, 35, 20, 144, 39, 20, 145, 43, 19, + 146, 47, 18, 147, 51, 18, 148, 55, 17, 149, 59, 16, + 122, -69, 35, 123, -65, 35, 124, -61, 34, 125, -56, 33, + 126, -52, 33, 127, -48, 32, 128, -44, 31, 129, -40, 31, + 130, -36, 30, 131, -32, 29, 132, -28, 29, 133, -24, 28, + 134, -19, 27, 134, -15, 27, 135, -11, 26, 136, -7, 25, + 137, -3, 25, 138, 0, 24, 139, 4, 23, 140, 8, 23, + 141, 13, 22, 142, 17, 21, 143, 21, 21, 144, 25, 20, + 145, 29, 19, 146, 33, 19, 147, 37, 18, 148, 41, 17, + 148, 45, 17, 150, 50, 16, 150, 54, 15, 151, 58, 15, + 125, -70, 34, 126, -66, 33, 126, -62, 32, 127, -58, 32, + 128, -54, 31, 129, -50, 30, 130, -46, 30, 131, -41, 29, + 132, -37, 28, 133, -33, 28, 134, -29, 27, 135, -25, 26, + 136, -21, 26, 137, -17, 25, 138, -13, 24, 139, -9, 24, + 140, -4, 23, 141, 0, 22, 141, 3, 22, 142, 7, 21, + 143, 11, 20, 144, 15, 20, 145, 19, 19, 146, 23, 18, + 147, 27, 18, 148, 32, 17, 149, 36, 16, 150, 40, 16, + 151, 44, 15, 152, 48, 14, 153, 52, 14, 154, 56, 13, + 128, -72, 32, 128, -68, 31, 129, -64, 30, 130, -59, 30, + 131, -55, 29, 132, -51, 28, 133, -47, 28, 134, -43, 27, + 135, -39, 26, 136, -35, 26, 137, -31, 25, 138, -27, 24, + 139, -22, 24, 140, -18, 23, 141, -14, 22, 142, -10, 22, + 143, -6, 21, 143, -2, 20, 144, 1, 20, 145, 5, 19, + 146, 10, 18, 147, 14, 18, 148, 18, 17, 149, 22, 16, + 150, 26, 16, 151, 30, 15, 152, 34, 14, 153, 38, 14, + 154, 42, 13, 155, 47, 12, 156, 51, 11, 157, 55, 11, + 130, -73, 30, 131, -69, 29, 132, -65, 29, 133, -61, 28, + 134, -57, 27, 135, -53, 27, 135, -49, 26, 136, -44, 25, + 137, -40, 25, 138, -36, 24, 139, -32, 23, 140, -28, 23, + 141, -24, 22, 142, -20, 21, 143, -16, 21, 144, -12, 20, + 145, -7, 19, 146, -3, 19, 147, 0, 18, 148, 4, 17, + 149, 8, 16, 150, 12, 16, 151, 16, 15, 151, 20, 15, + 152, 24, 14, 153, 29, 13, 154, 33, 12, 155, 37, 12, + 156, 41, 11, 157, 45, 10, 158, 49, 10, 159, 53, 9, + 132, -74, 28, 133, -70, 28, 134, -66, 27, 135, -62, 26, + 136, -58, 26, 137, -54, 25, 138, -50, 24, 139, -45, 24, + 140, -41, 23, 141, -37, 22, 142, -33, 22, 142, -29, 21, + 144, -25, 20, 144, -21, 20, 145, -17, 19, 146, -13, 18, + 147, -8, 17, 148, -4, 17, 149, 0, 16, 150, 3, 16, + 151, 7, 15, 152, 11, 14, 153, 15, 14, 154, 19, 13, + 155, 23, 12, 156, 28, 11, 157, 32, 11, 158, 36, 10, + 158, 40, 10, 159, 44, 9, 160, 48, 8, 161, 52, 7, + 135, -76, 27, 135, -72, 26, 136, -68, 25, 137, -63, 25, + 138, -59, 24, 139, -55, 23, 140, -51, 23, 141, -47, 22, + 142, -43, 21, 143, -39, 21, 144, -35, 20, 145, -31, 19, + 146, -26, 18, 147, -22, 18, 148, -18, 17, 149, -14, 17, + 150, -10, 16, 151, -6, 15, 151, -2, 15, 152, 1, 14, + 153, 6, 13, 154, 10, 12, 155, 14, 12, 156, 18, 11, + 157, 22, 11, 158, 26, 10, 159, 30, 9, 160, 34, 8, + 161, 38, 8, 162, 43, 7, 163, 47, 6, 164, 51, 6, + 137, -77, 25, 138, -73, 24, 139, -69, 24, 140, -65, 23, + 141, -61, 22, 142, -57, 22, 143, -53, 21, 144, -48, 20, + 144, -44, 20, 145, -40, 19, 146, -36, 18, 147, -32, 18, + 148, -28, 17, 149, -24, 16, 150, -20, 16, 151, -16, 15, + 152, -11, 14, 153, -7, 13, 154, -3, 13, 155, 0, 12, + 156, 4, 11, 157, 8, 11, 158, 12, 10, 158, 16, 10, + 159, 20, 9, 160, 25, 8, 161, 29, 7, 162, 33, 7, + 163, 37, 6, 164, 41, 5, 165, 45, 5, 166, 49, 4, + 139, -78, 23, 140, -74, 23, 141, -70, 22, 142, -66, 21, + 143, -62, 21, 144, -58, 20, 145, -54, 19, 146, -49, 18, + 147, -45, 18, 148, -41, 17, 149, -37, 17, 150, -33, 16, + 151, -29, 15, 151, -25, 14, 152, -21, 14, 153, -17, 13, + 154, -12, 12, 155, -8, 12, 156, -4, 11, 157, 0, 11, + 158, 3, 10, 159, 7, 9, 160, 11, 8, 161, 15, 8, + 162, 19, 7, 163, 24, 6, 164, 28, 6, 165, 32, 5, + 165, 36, 4, 167, 40, 4, 167, 44, 3, 168, 48, 2, + 142, -80, 22, 143, -76, 21, 143, -72, 20, 144, -67, 19, + 145, -63, 19, 146, -59, 18, 147, -55, 18, 148, -51, 17, + 149, -47, 16, 150, -43, 16, 151, -39, 15, 152, -35, 14, + 153, -30, 13, 154, -26, 13, 155, -22, 12, 156, -18, 12, + 157, -14, 11, 158, -10, 10, 158, -6, 9, 159, -2, 9, + 160, 2, 8, 161, 6, 7, 162, 10, 7, 163, 14, 6, + 164, 18, 6, 165, 22, 5, 166, 26, 4, 167, 30, 3, + 168, 34, 3, 169, 39, 2, 170, 43, 1, 171, 47, 1, + 144, -81, 20, 145, -77, 19, 146, -73, 19, 147, -69, 18, + 148, -65, 17, 149, -61, 17, 150, -57, 16, 151, -52, 15, + 151, -48, 14, 152, -44, 14, 153, -40, 13, 154, -36, 13, + 155, -32, 12, 156, -28, 11, 157, -24, 10, 158, -20, 10, + 159, -15, 9, 160, -11, 8, 161, -7, 8, 162, -3, 7, + 163, 0, 6, 164, 4, 6, 165, 8, 5, 166, 12, 4, + 166, 16, 4, 167, 21, 3, 168, 25, 2, 169, 29, 2, + 170, 33, 1, 171, 37, 0, 172, 41, 0, 173, 45, 0, + 146, -82, 18, 147, -78, 18, 148, -74, 17, 149, -70, 16, + 150, -66, 15, 151, -62, 15, 152, -58, 14, 153, -53, 13, + 154, -49, 13, 155, -45, 12, 156, -41, 12, 157, -37, 11, + 158, -33, 10, 159, -29, 9, 159, -25, 9, 160, -21, 8, + 161, -16, 7, 162, -12, 7, 163, -8, 6, 164, -4, 5, + 165, 0, 5, 166, 3, 4, 167, 7, 3, 168, 11, 3, + 169, 15, 2, 170, 20, 1, 171, 24, 1, 172, 28, 0, + 173, 32, 0, 174, 36, 0, 174, 40, -1, 175, 44, -2, + 149, -84, 16, 150, -80, 16, 150, -76, 15, 152, -71, 14, + 152, -67, 14, 153, -63, 13, 154, -59, 13, 155, -55, 12, + 156, -51, 11, 157, -47, 10, 158, -43, 10, 159, -39, 9, + 160, -34, 8, 161, -30, 8, 162, -26, 7, 163, -22, 6, + 164, -18, 6, 165, -14, 5, 166, -10, 4, 166, -6, 4, + 167, -1, 3, 168, 2, 2, 169, 6, 2, 170, 10, 1, + 171, 14, 0, 172, 18, 0, 173, 22, 0, 174, 26, -1, + 175, 30, -1, 176, 35, -2, 177, 39, -3, 178, 43, -3, + 151, -85, 15, 152, -81, 14, 153, -77, 14, 154, -73, 13, + 155, -69, 12, 156, -65, 11, 157, -61, 11, 158, -56, 10, + 159, -52, 9, 159, -48, 9, 160, -44, 8, 161, -40, 8, + 162, -36, 7, 163, -32, 6, 164, -28, 5, 165, -24, 5, + 166, -19, 4, 167, -15, 3, 168, -11, 3, 169, -7, 2, + 170, -3, 1, 171, 0, 1, 172, 4, 0, 173, 8, 0, + 173, 12, 0, 175, 17, -1, 175, 21, -2, 176, 25, -2, + 177, 29, -3, 178, 33, -4, 179, 37, -4, 180, 41, -5, + 153, -86, 13, 154, -82, 12, 155, -78, 12, 156, -74, 11, + 157, -70, 10, 158, -66, 10, 159, -62, 9, 160, -57, 8, + 161, -53, 8, 162, -49, 7, 163, -45, 6, 164, -41, 6, + 165, -37, 5, 166, -33, 4, 166, -29, 4, 167, -25, 3, + 168, -20, 2, 169, -16, 2, 170, -12, 1, 171, -8, 0, + 172, -4, 0, 173, 0, 0, 174, 3, -1, 175, 7, -1, + 176, 11, -2, 177, 16, -3, 178, 20, -3, 179, 24, -4, + 180, 28, -5, 181, 32, -5, 182, 36, -6, 182, 40, -7, + 156, -88, 11, 157, -84, 11, 158, -80, 10, 159, -75, 9, + 159, -71, 9, 160, -67, 8, 161, -63, 7, 162, -59, 7, + 163, -55, 6, 164, -51, 5, 165, -47, 5, 166, -43, 4, + 167, -38, 3, 168, -34, 3, 169, -30, 2, 170, -26, 1, + 171, -22, 1, 172, -18, 0, 173, -14, 0, 173, -10, 0, + 175, -5, -1, 175, -1, -2, 176, 2, -2, 177, 6, -3, + 178, 10, -4, 179, 14, -4, 180, 18, -5, 181, 22, -6, + 182, 26, -6, 183, 31, -7, 184, 35, -8, 185, 39, -8, + 158, -89, 10, 159, -85, 9, 160, -81, 8, 161, -76, 8, + 162, -72, 7, 163, -68, 6, 164, -64, 6, 165, -60, 5, + 166, -56, 4, 166, -52, 4, 167, -48, 3, 168, -44, 2, + 169, -39, 2, 170, -35, 1, 171, -31, 0, 172, -27, 0, + 173, -23, 0, 174, -19, -1, 175, -15, -1, 176, -11, -2, + 177, -6, -3, 178, -2, -3, 179, 1, -4, 180, 5, -5, + 181, 9, -5, 182, 13, -6, 182, 17, -7, 183, 21, -7, + 184, 25, -8, 185, 30, -9, 186, 34, -9, 187, 38, -10, + 160, -90, 8, 161, -86, 7, 162, -82, 7, 163, -78, 6, + 164, -74, 5, 165, -70, 5, 166, -66, 4, 167, -61, 3, + 168, -57, 3, 169, -53, 2, 170, -49, 1, 171, -45, 1, + 172, -41, 0, 173, -37, 0, 174, -33, 0, 174, -29, -1, + 175, -24, -2, 176, -20, -2, 177, -16, -3, 178, -12, -4, + 179, -8, -4, 180, -4, -5, 181, 0, -6, 182, 3, -6, + 183, 7, -7, 184, 12, -8, 185, 16, -8, 186, 20, -9, + 187, 24, -10, 188, 28, -10, 189, 32, -11, 189, 36, -12, + 163, -92, 6, 164, -88, 6, 165, -84, 5, 166, -79, 4, + 167, -75, 4, 167, -71, 3, 168, -67, 2, 169, -63, 2, + 170, -59, 1, 171, -55, 0, 172, -51, 0, 173, -47, 0, + 174, -42, -1, 175, -38, -1, 176, -34, -2, 177, -30, -3, + 178, -26, -3, 179, -22, -4, 180, -18, -5, 181, -14, -5, + 182, -9, -6, 182, -5, -7, 183, -1, -7, 184, 2, -8, + 185, 6, -9, 186, 10, -9, 187, 14, -10, 188, 18, -11, + 189, 22, -11, 190, 27, -12, 191, 31, -13, 192, 35, -13, + 165, -93, 5, 166, -89, 4, 167, -85, 3, 168, -80, 3, + 169, -76, 2, 170, -72, 1, 171, -68, 1, 172, -64, 0, + 173, -60, 0, 174, -56, 0, 174, -52, -1, 175, -48, -2, + 176, -43, -2, 177, -39, -3, 178, -35, -4, 179, -31, -4, + 180, -27, -5, 181, -23, -6, 182, -19, -6, 183, -15, -7, + 184, -10, -8, 185, -6, -8, 186, -2, -9, 187, 1, -10, + 188, 5, -10, 189, 9, -11, 190, 13, -12, 190, 17, -12, + 191, 21, -13, 192, 26, -14, 193, 30, -14, 194, 34, -15, + 167, -94, 3, 168, -90, 2, 169, -86, 2, 170, -82, 1, + 171, -78, 0, 172, -74, 0, 173, -70, 0, 174, -65, -1, + 175, -61, -1, 176, -57, -2, 177, -53, -3, 178, -49, -3, + 179, -45, -4, 180, -41, -5, 181, -37, -5, 181, -33, -6, + 183, -28, -7, 183, -24, -7, 184, -20, -8, 185, -16, -9, + 186, -12, -9, 187, -8, -10, 188, -4, -11, 189, 0, -11, + 190, 3, -12, 191, 8, -13, 192, 12, -13, 193, 16, -14, + 194, 20, -15, 195, 24, -15, 196, 28, -16, 197, 32, -17, + 170, -96, 1, 171, -92, 1, 172, -88, 0, 173, -83, 0, + 174, -79, 0, 174, -75, -1, 175, -71, -2, 176, -67, -2, + 177, -63, -3, 178, -59, -4, 179, -55, -4, 180, -51, -5, + 181, -46, -6, 182, -42, -6, 183, -38, -7, 184, -34, -8, + 185, -30, -8, 186, -26, -9, 187, -22, -10, 188, -18, -10, + 189, -13, -11, 190, -9, -12, 190, -5, -12, 191, -1, -13, + 192, 2, -14, 193, 6, -14, 194, 10, -15, 195, 14, -16, + 196, 18, -16, 197, 23, -17, 198, 27, -18, 199, 31, -18, + 172, -97, 0, 173, -93, 0, 174, -89, -1, 175, -84, -1, + 176, -80, -2, 177, -76, -3, 178, -72, -3, 179, -68, -4, + 180, -64, -5, 181, -60, -5, 181, -56, -6, 182, -52, -7, + 183, -47, -7, 184, -43, -8, 185, -39, -9, 186, -35, -9, + 187, -31, -10, 188, -27, -11, 189, -23, -11, 190, -19, -12, + 191, -14, -13, 192, -10, -13, 193, -6, -14, 194, -2, -15, + 195, 1, -15, 196, 5, -16, 197, 9, -17, 197, 13, -17, + 198, 17, -18, 199, 22, -19, 200, 26, -19, 201, 30, -20, + 174, -98, -1, 175, -94, -2, 176, -90, -2, 177, -86, -3, + 178, -82, -4, 179, -78, -4, 180, -74, -5, 181, -69, -6, + 182, -65, -6, 183, -61, -7, 184, -57, -8, 185, -53, -8, + 186, -49, -9, 187, -45, -10, 188, -41, -10, 189, -37, -11, + 190, -32, -12, 190, -28, -12, 191, -24, -13, 192, -20, -14, + 193, -16, -14, 194, -12, -15, 195, -8, -16, 196, -4, -16, + 197, 0, -17, 198, 4, -18, 199, 8, -18, 200, 12, -19, + 201, 16, -20, 202, 20, -20, 203, 24, -21, 204, 28, -22, + 177, -100, -3, 178, -96, -4, 179, -92, -4, 180, -87, -5, + 181, -83, -6, 182, -79, -6, 183, -75, -7, 184, -71, -8, + 185, -67, -8, 186, -63, -9, 187, -59, -10, 188, -55, -10, + 189, -50, -11, 190, -46, -12, 191, -42, -12, 191, -38, -13, + 192, -34, -14, 193, -30, -14, 194, -26, -15, 195, -22, -16, + 196, -17, -16, 197, -13, -17, 198, -9, -18, 199, -5, -18, + 200, -1, -19, 201, 2, -20, 202, 6, -20, 203, 10, -21, + 204, 14, -22, 205, 19, -22, 206, 23, -23, 207, 27, -24, + 180, -101, -5, 181, -97, -5, 182, -93, -6, 183, -89, -7, + 184, -85, -7, 184, -81, -8, 185, -77, -9, 186, -72, -9, + 187, -68, -10, 188, -64, -11, 189, -60, -11, 190, -56, -12, + 191, -52, -13, 192, -48, -13, 193, -44, -14, 194, -40, -15, + 195, -35, -15, 196, -31, -16, 197, -27, -17, 198, -23, -17, + 199, -19, -18, 200, -15, -19, 200, -11, -19, 201, -7, -20, + 202, -3, -21, 203, 1, -21, 204, 5, -22, 205, 9, -23, + 206, 13, -23, 207, 17, -24, 208, 21, -25, 209, 25, -25, + 182, -103, -6, 183, -99, -7, 184, -95, -8, 185, -90, -8, + 186, -86, -9, 187, -82, -10, 188, -78, -10, 189, -74, -11, + 190, -70, -12, 191, -66, -12, 191, -62, -13, 192, -58, -14, + 193, -53, -14, 194, -49, -15, 195, -45, -16, 196, -41, -16, + 197, -37, -17, 198, -33, -18, 199, -29, -18, 200, -25, -19, + 201, -20, -20, 202, -16, -20, 203, -12, -21, 204, -8, -22, + 205, -4, -22, 206, 0, -23, 207, 3, -24, 207, 7, -24, + 208, 11, -25, 209, 16, -26, 210, 20, -26, 211, 24, -27, + 184, -104, -8, 185, -100, -9, 186, -96, -9, 187, -91, -10, + 188, -87, -11, 189, -83, -11, 190, -79, -12, 191, -75, -13, + 192, -71, -13, 193, -67, -14, 194, -63, -15, 195, -59, -15, + 196, -54, -16, 197, -50, -17, 198, -46, -17, 198, -42, -18, + 200, -38, -19, 200, -34, -19, 201, -30, -20, 202, -26, -21, + 203, -21, -21, 204, -17, -22, 205, -13, -23, 206, -9, -23, + 207, -5, -24, 208, -1, -25, 209, 2, -25, 210, 6, -26, + 211, 10, -27, 212, 15, -27, 213, 19, -28, 214, 23, -29, + 187, -105, -10, 188, -101, -10, 189, -97, -11, 190, -93, -12, + 191, -89, -12, 191, -85, -13, 192, -81, -14, 193, -76, -14, + 194, -72, -15, 195, -68, -16, 196, -64, -16, 197, -60, -17, + 198, -56, -18, 199, -52, -18, 200, -48, -19, 201, -44, -20, + 202, -39, -20, 203, -35, -21, 204, -31, -22, 205, -27, -22, + 206, -23, -23, 207, -19, -24, 207, -15, -24, 208, -11, -25, + 209, -7, -26, 210, -2, -26, 211, 1, -27, 212, 5, -28, + 213, 9, -28, 214, 13, -29, 215, 17, -30, 216, 21, -30, + 189, -107, -11, 190, -103, -12, 191, -99, -13, 192, -94, -13, + 193, -90, -14, 194, -86, -15, 195, -82, -15, 196, -78, -16, + 197, -74, -17, 198, -70, -17, 199, -66, -18, 199, -62, -19, + 200, -57, -19, 201, -53, -20, 202, -49, -21, 203, -45, -21, + 204, -41, -22, 205, -37, -23, 206, -33, -23, 207, -29, -24, + 208, -24, -25, 209, -20, -25, 210, -16, -26, 211, -12, -27, + 212, -8, -27, 213, -4, -28, 214, 0, -29, 214, 3, -29, + 215, 7, -30, 216, 12, -31, 217, 16, -31, 218, 20, -32, + 192, -108, -13, 192, -104, -14, 193, -100, -14, 194, -95, -15, + 195, -91, -16, 196, -87, -16, 197, -83, -17, 198, -79, -18, + 199, -75, -18, 200, -71, -19, 201, -67, -20, 202, -63, -20, + 203, -58, -21, 204, -54, -22, 205, -50, -22, 206, -46, -23, + 207, -42, -24, 207, -38, -24, 208, -34, -25, 209, -30, -26, + 210, -25, -26, 211, -21, -27, 212, -17, -28, 213, -13, -28, + 214, -9, -29, 215, -5, -30, 216, -1, -30, 217, 2, -31, + 218, 6, -32, 219, 11, -33, 220, 15, -33, 221, 19, -34, + 194, -109, -15, 195, -105, -15, 196, -101, -16, 197, -97, -17, + 198, -93, -17, 199, -89, -18, 199, -85, -19, 200, -80, -19, + 201, -76, -20, 202, -72, -21, 203, -68, -21, 204, -64, -22, + 205, -60, -23, 206, -56, -23, 207, -52, -24, 208, -48, -25, + 209, -43, -25, 210, -39, -26, 211, -35, -27, 212, -31, -27, + 213, -27, -28, 214, -23, -29, 215, -19, -29, 215, -15, -30, + 216, -11, -31, 217, -6, -31, 218, -2, -32, 219, 1, -33, + 220, 5, -33, 221, 9, -34, 222, 13, -35, 223, 17, -35, + 196, -111, -16, 197, -107, -17, 198, -103, -18, 199, -98, -18, + 200, -94, -19, 201, -90, -20, 202, -86, -20, 203, -82, -21, + 204, -78, -22, 205, -74, -22, 206, -70, -23, 206, -66, -24, + 208, -61, -24, 208, -57, -25, 209, -53, -26, 210, -49, -26, + 211, -45, -27, 212, -41, -28, 213, -37, -28, 214, -33, -29, + 215, -28, -30, 216, -24, -30, 217, -20, -31, 218, -16, -32, + 219, -12, -32, 220, -8, -33, 221, -4, -34, 222, 0, -34, + 222, 3, -35, 223, 8, -36, 224, 12, -37, 225, 16, -37, + 199, -112, -18, 199, -108, -19, 200, -104, -19, 201, -99, -20, + 202, -95, -21, 203, -91, -21, 204, -87, -22, 205, -83, -23, + 206, -79, -23, 207, -75, -24, 208, -71, -25, 209, -67, -25, + 210, -62, -26, 211, -58, -27, 212, -54, -27, 213, -50, -28, + 214, -46, -29, 215, -42, -29, 215, -38, -30, 216, -34, -31, + 217, -29, -32, 218, -25, -32, 219, -21, -33, 220, -17, -33, + 221, -13, -34, 222, -9, -35, 223, -5, -35, 224, -1, -36, + 225, 2, -37, 226, 7, -38, 227, 11, -38, 228, 15, -39, + 201, -113, -20, 202, -109, -20, 203, -105, -21, 204, -101, -22, + 205, -97, -22, 206, -93, -23, 206, -89, -24, 208, -84, -24, + 208, -80, -25, 209, -76, -26, 210, -72, -26, 211, -68, -27, + 212, -64, -28, 213, -60, -28, 214, -56, -29, 215, -52, -30, + 216, -47, -30, 217, -43, -31, 218, -39, -32, 219, -35, -32, + 220, -31, -33, 221, -27, -34, 222, -23, -34, 222, -19, -35, + 223, -15, -36, 224, -10, -37, 225, -6, -37, 226, -2, -38, + 227, 1, -38, 228, 5, -39, 229, 9, -40, 230, 13, -41, + 54, -30, 90, 55, -26, 89, 55, -22, 89, 56, -18, 88, + 57, -14, 87, 58, -10, 87, 59, -6, 86, 60, -1, 85, + 61, 2, 85, 62, 6, 84, 63, 10, 83, 64, 14, 83, + 65, 18, 82, 66, 22, 81, 67, 26, 81, 68, 30, 80, + 69, 35, 79, 70, 39, 79, 70, 43, 78, 71, 47, 77, + 72, 51, 77, 73, 55, 76, 74, 59, 75, 75, 63, 75, + 76, 67, 74, 77, 72, 73, 78, 76, 73, 79, 80, 72, + 80, 84, 71, 81, 88, 71, 82, 92, 70, 83, 96, 69, + 56, -31, 88, 57, -27, 88, 58, -23, 87, 59, -19, 86, + 60, -15, 86, 61, -11, 85, 62, -7, 84, 63, -2, 84, + 63, 1, 83, 64, 5, 82, 65, 9, 82, 66, 13, 81, + 67, 17, 80, 68, 21, 80, 69, 25, 79, 70, 29, 78, + 71, 34, 78, 72, 38, 77, 73, 42, 76, 74, 46, 76, + 75, 50, 75, 76, 54, 74, 77, 58, 74, 78, 62, 73, + 78, 66, 72, 79, 71, 72, 80, 75, 71, 81, 79, 70, + 82, 83, 70, 83, 87, 69, 84, 91, 68, 85, 95, 68, + 58, -33, 87, 59, -29, 86, 60, -25, 85, 61, -20, 85, + 62, -16, 84, 63, -12, 83, 64, -8, 83, 65, -4, 82, + 66, 0, 81, 67, 3, 81, 68, 7, 80, 69, 11, 79, + 70, 16, 79, 71, 20, 78, 71, 24, 77, 72, 28, 77, + 73, 32, 76, 74, 36, 75, 75, 40, 75, 76, 44, 74, + 77, 49, 73, 78, 53, 73, 79, 57, 72, 80, 61, 71, + 81, 65, 71, 82, 69, 70, 83, 73, 69, 84, 77, 69, + 85, 81, 68, 86, 86, 67, 86, 90, 67, 87, 94, 66, + 61, -34, 85, 62, -30, 84, 62, -26, 84, 64, -22, 83, + 64, -18, 82, 65, -14, 82, 66, -10, 81, 67, -5, 80, + 68, -1, 80, 69, 2, 79, 70, 6, 78, 71, 10, 78, + 72, 14, 77, 73, 18, 76, 74, 22, 76, 75, 26, 75, + 76, 31, 74, 77, 35, 74, 78, 39, 73, 78, 43, 72, + 79, 47, 72, 80, 51, 71, 81, 55, 70, 82, 59, 70, + 83, 63, 69, 84, 68, 68, 85, 72, 68, 86, 76, 67, + 87, 80, 66, 88, 84, 66, 89, 88, 65, 90, 92, 64, + 63, -35, 83, 64, -31, 83, 65, -27, 82, 66, -23, 81, + 67, -19, 81, 68, -15, 80, 69, -11, 79, 70, -6, 79, + 71, -2, 78, 71, 1, 77, 72, 5, 77, 73, 9, 76, + 74, 13, 75, 75, 17, 75, 76, 21, 74, 77, 25, 73, + 78, 30, 73, 79, 34, 72, 80, 38, 71, 81, 42, 71, + 82, 46, 70, 83, 50, 69, 84, 54, 69, 85, 58, 68, + 85, 62, 67, 86, 67, 67, 87, 71, 66, 88, 75, 65, + 89, 79, 65, 90, 83, 64, 91, 87, 63, 92, 91, 63, + 65, -37, 82, 66, -33, 81, 67, -29, 80, 68, -24, 80, + 69, -20, 79, 70, -16, 78, 71, -12, 78, 72, -8, 77, + 73, -4, 76, 74, 0, 76, 75, 3, 75, 76, 7, 74, + 77, 12, 74, 78, 16, 73, 78, 20, 72, 79, 24, 72, + 80, 28, 71, 81, 32, 70, 82, 36, 70, 83, 40, 69, + 84, 45, 68, 85, 49, 68, 86, 53, 67, 87, 57, 66, + 88, 61, 66, 89, 65, 65, 90, 69, 64, 91, 73, 64, + 92, 77, 63, 93, 82, 62, 94, 86, 62, 94, 90, 61, + 68, -38, 80, 69, -34, 79, 70, -30, 79, 71, -25, 78, + 71, -21, 77, 72, -17, 77, 73, -13, 76, 74, -9, 75, + 75, -5, 75, 76, -1, 74, 77, 2, 73, 78, 6, 73, + 79, 11, 72, 80, 15, 71, 81, 19, 71, 82, 23, 70, + 83, 27, 69, 84, 31, 69, 85, 35, 68, 85, 39, 67, + 87, 44, 67, 87, 48, 66, 88, 52, 65, 89, 56, 65, + 90, 60, 64, 91, 64, 63, 92, 68, 63, 93, 72, 62, + 94, 76, 61, 95, 81, 61, 96, 85, 60, 97, 89, 59, + 70, -39, 78, 71, -35, 78, 72, -31, 77, 73, -27, 76, + 74, -23, 76, 75, -19, 75, 76, -15, 74, 77, -10, 74, + 78, -6, 73, 78, -2, 72, 79, 1, 72, 80, 5, 71, + 81, 9, 70, 82, 13, 70, 83, 17, 69, 84, 21, 68, + 85, 26, 68, 86, 30, 67, 87, 34, 66, 88, 38, 66, + 89, 42, 65, 90, 46, 64, 91, 50, 64, 92, 54, 63, + 93, 58, 62, 94, 63, 62, 94, 67, 61, 95, 71, 60, + 96, 75, 60, 97, 79, 59, 98, 83, 58, 99, 87, 58, + 72, -41, 77, 73, -37, 76, 74, -33, 75, 75, -28, 75, + 76, -24, 74, 77, -20, 73, 78, -16, 73, 79, -12, 72, + 80, -8, 71, 81, -4, 71, 82, 0, 70, 83, 3, 69, + 84, 8, 69, 85, 12, 68, 86, 16, 67, 86, 20, 67, + 87, 24, 66, 88, 28, 65, 89, 32, 65, 90, 36, 64, + 91, 41, 63, 92, 45, 63, 93, 49, 62, 94, 53, 61, + 95, 57, 61, 96, 61, 60, 97, 65, 59, 98, 69, 59, + 99, 73, 58, 100, 78, 57, 101, 82, 57, 101, 86, 56, + 75, -42, 75, 76, -38, 74, 77, -34, 74, 78, -29, 73, + 79, -25, 72, 79, -21, 72, 80, -17, 71, 81, -13, 70, + 82, -9, 70, 83, -5, 69, 84, -1, 68, 85, 2, 68, + 86, 7, 67, 87, 11, 66, 88, 15, 66, 89, 19, 65, + 90, 23, 64, 91, 27, 64, 92, 31, 63, 93, 35, 62, + 94, 40, 62, 94, 44, 61, 95, 48, 60, 96, 52, 60, + 97, 56, 59, 98, 60, 58, 99, 64, 58, 100, 68, 57, + 101, 72, 56, 102, 77, 55, 103, 81, 55, 104, 85, 54, + 77, -43, 73, 78, -39, 73, 79, -35, 72, 80, -31, 71, + 81, -27, 71, 82, -23, 70, 83, -19, 69, 84, -14, 69, + 85, -10, 68, 86, -6, 67, 86, -2, 67, 87, 1, 66, + 88, 5, 65, 89, 9, 65, 90, 13, 64, 91, 17, 63, + 92, 22, 63, 93, 26, 62, 94, 30, 61, 95, 34, 61, + 96, 38, 60, 97, 42, 59, 98, 46, 59, 99, 50, 58, + 100, 54, 57, 101, 59, 57, 101, 63, 56, 102, 67, 55, + 103, 71, 55, 104, 75, 54, 105, 79, 53, 106, 83, 53, + 80, -45, 71, 81, -41, 71, 82, -37, 70, 83, -32, 69, + 84, -28, 68, 85, -24, 68, 86, -20, 67, 87, -16, 66, + 88, -12, 66, 88, -8, 65, 89, -4, 64, 90, 0, 64, + 91, 4, 63, 92, 8, 62, 93, 12, 62, 94, 16, 61, + 95, 20, 60, 96, 24, 60, 97, 28, 59, 98, 32, 58, + 99, 37, 58, 100, 41, 57, 101, 45, 56, 102, 49, 56, + 102, 53, 55, 104, 57, 54, 104, 61, 54, 105, 65, 53, + 106, 69, 52, 107, 74, 52, 108, 78, 51, 109, 82, 50, + 82, -46, 69, 83, -42, 69, 84, -38, 68, 85, -34, 67, + 86, -30, 67, 87, -26, 66, 88, -22, 65, 89, -17, 65, + 90, -13, 64, 91, -9, 63, 92, -5, 63, 93, -1, 62, + 94, 2, 61, 95, 6, 61, 95, 10, 60, 96, 14, 59, + 97, 19, 59, 98, 23, 58, 99, 27, 57, 100, 31, 57, + 101, 35, 56, 102, 39, 55, 103, 43, 55, 104, 47, 54, + 105, 51, 53, 106, 56, 53, 107, 60, 52, 108, 64, 51, + 109, 68, 51, 110, 72, 50, 111, 76, 49, 111, 80, 49, + 85, -48, 68, 86, -44, 67, 87, -40, 67, 88, -35, 66, + 88, -31, 65, 89, -27, 64, 90, -23, 64, 91, -19, 63, + 92, -15, 62, 93, -11, 62, 94, -7, 61, 95, -3, 60, + 96, 1, 60, 97, 5, 59, 98, 9, 58, 99, 13, 58, + 100, 17, 57, 101, 21, 56, 102, 25, 56, 103, 29, 55, + 104, 34, 54, 104, 38, 54, 105, 42, 53, 106, 46, 52, + 107, 50, 52, 108, 54, 51, 109, 58, 50, 110, 62, 50, + 111, 66, 49, 112, 71, 48, 113, 75, 48, 114, 79, 47, + 87, -49, 66, 88, -45, 65, 89, -41, 65, 90, -36, 64, + 91, -32, 63, 92, -28, 63, 93, -24, 62, 94, -20, 61, + 95, -16, 61, 96, -12, 60, 96, -8, 59, 97, -4, 59, + 98, 0, 58, 99, 4, 57, 100, 8, 57, 101, 12, 56, + 102, 16, 55, 103, 20, 55, 104, 24, 54, 105, 28, 53, + 106, 33, 53, 107, 37, 52, 108, 41, 51, 109, 45, 51, + 110, 49, 50, 111, 53, 49, 111, 57, 49, 112, 61, 48, + 113, 65, 47, 114, 70, 47, 115, 74, 46, 116, 78, 45, + 89, -50, 64, 90, -46, 64, 91, -42, 63, 92, -38, 62, + 93, -34, 62, 94, -30, 61, 95, -26, 60, 96, -21, 60, + 97, -17, 59, 98, -13, 58, 99, -9, 58, 100, -5, 57, + 101, -1, 56, 102, 2, 56, 103, 6, 55, 103, 10, 54, + 104, 15, 54, 105, 19, 53, 106, 23, 52, 107, 27, 52, + 108, 31, 51, 109, 35, 50, 110, 39, 50, 111, 43, 49, + 112, 47, 48, 113, 52, 48, 114, 56, 47, 115, 60, 46, + 116, 64, 46, 117, 68, 45, 118, 72, 44, 118, 76, 44, + 92, -52, 63, 93, -48, 62, 94, -44, 61, 95, -39, 61, + 96, -35, 60, 96, -31, 59, 97, -27, 59, 98, -23, 58, + 99, -19, 57, 100, -15, 57, 101, -11, 56, 102, -7, 55, + 103, -2, 55, 104, 1, 54, 105, 5, 53, 106, 9, 53, + 107, 13, 52, 108, 17, 51, 109, 21, 51, 110, 25, 50, + 111, 30, 49, 111, 34, 49, 112, 38, 48, 113, 42, 47, + 114, 46, 47, 115, 50, 46, 116, 54, 45, 117, 58, 45, + 118, 62, 44, 119, 67, 43, 120, 71, 43, 121, 75, 42, + 94, -53, 61, 95, -49, 60, 96, -45, 60, 97, -40, 59, + 98, -36, 58, 99, -32, 58, 100, -28, 57, 101, -24, 56, + 102, -20, 56, 103, -16, 55, 103, -12, 54, 104, -8, 54, + 105, -3, 53, 106, 0, 52, 107, 4, 52, 108, 8, 51, + 109, 12, 50, 110, 16, 50, 111, 20, 49, 112, 24, 48, + 113, 29, 48, 114, 33, 47, 115, 37, 46, 116, 41, 46, + 117, 45, 45, 118, 49, 44, 119, 53, 44, 119, 57, 43, + 120, 61, 42, 121, 66, 42, 122, 70, 41, 123, 74, 40, + 96, -54, 59, 97, -50, 59, 98, -46, 58, 99, -42, 57, + 100, -38, 57, 101, -34, 56, 102, -30, 55, 103, -25, 55, + 104, -21, 54, 105, -17, 53, 106, -13, 53, 107, -9, 52, + 108, -5, 51, 109, -1, 51, 110, 2, 50, 110, 6, 49, + 112, 11, 49, 112, 15, 48, 113, 19, 47, 114, 23, 47, + 115, 27, 46, 116, 31, 45, 117, 35, 45, 118, 39, 44, + 119, 43, 43, 120, 48, 43, 121, 52, 42, 122, 56, 41, + 123, 60, 41, 124, 64, 40, 125, 68, 39, 126, 72, 39, + 99, -56, 58, 100, -52, 57, 101, -48, 56, 102, -43, 56, + 103, -39, 55, 103, -35, 54, 104, -31, 54, 105, -27, 53, + 106, -23, 52, 107, -19, 52, 108, -15, 51, 109, -11, 50, + 110, -6, 50, 111, -2, 49, 112, 1, 48, 113, 5, 48, + 114, 9, 47, 115, 13, 46, 116, 17, 46, 117, 21, 45, + 118, 26, 44, 119, 30, 44, 119, 34, 43, 120, 38, 42, + 121, 42, 42, 122, 46, 41, 123, 50, 40, 124, 54, 40, + 125, 58, 39, 126, 63, 38, 127, 67, 38, 128, 71, 37, + 101, -57, 56, 102, -53, 55, 103, -49, 55, 104, -44, 54, + 105, -40, 53, 106, -36, 53, 107, -32, 52, 108, -28, 51, + 109, -24, 51, 110, -20, 50, 111, -16, 49, 111, -12, 49, + 112, -7, 48, 113, -3, 47, 114, 0, 47, 115, 4, 46, + 116, 8, 45, 117, 12, 45, 118, 16, 44, 119, 20, 43, + 120, 25, 43, 121, 29, 42, 122, 33, 41, 123, 37, 41, + 124, 41, 40, 125, 45, 39, 126, 49, 39, 126, 53, 38, + 127, 57, 37, 128, 62, 37, 129, 66, 36, 130, 70, 35, + 104, -58, 54, 104, -54, 54, 105, -50, 53, 106, -46, 52, + 107, -42, 52, 108, -38, 51, 109, -34, 50, 110, -29, 50, + 111, -25, 49, 112, -21, 48, 113, -17, 48, 114, -13, 47, + 115, -9, 46, 116, -5, 46, 117, -1, 45, 118, 2, 44, + 119, 7, 44, 119, 11, 43, 120, 15, 42, 121, 19, 42, + 122, 23, 41, 123, 27, 40, 124, 31, 40, 125, 35, 39, + 126, 39, 38, 127, 44, 38, 128, 48, 37, 129, 52, 36, + 130, 56, 36, 131, 60, 35, 132, 64, 34, 133, 68, 34, + 106, -60, 53, 107, -56, 52, 108, -52, 51, 109, -47, 51, + 110, -43, 50, 111, -39, 49, 111, -35, 49, 112, -31, 48, + 113, -27, 47, 114, -23, 47, 115, -19, 46, 116, -15, 45, + 117, -10, 45, 118, -6, 44, 119, -2, 43, 120, 1, 43, + 121, 5, 42, 122, 9, 41, 123, 13, 41, 124, 17, 40, + 125, 22, 39, 126, 26, 39, 126, 30, 38, 127, 34, 37, + 128, 38, 37, 129, 42, 36, 130, 46, 35, 131, 50, 35, + 132, 54, 34, 133, 59, 33, 134, 63, 33, 135, 67, 32, + 108, -61, 51, 109, -57, 50, 110, -53, 50, 111, -48, 49, + 112, -44, 48, 113, -40, 48, 114, -36, 47, 115, -32, 46, + 116, -28, 46, 117, -24, 45, 118, -20, 44, 118, -16, 44, + 119, -11, 43, 120, -7, 42, 121, -3, 42, 122, 0, 41, + 123, 4, 40, 124, 8, 40, 125, 12, 39, 126, 16, 38, + 127, 21, 38, 128, 25, 37, 129, 29, 36, 130, 33, 36, + 131, 37, 35, 132, 41, 34, 133, 45, 34, 134, 49, 33, + 134, 53, 32, 135, 58, 32, 136, 62, 31, 137, 66, 30, + 111, -62, 49, 111, -58, 49, 112, -54, 48, 113, -50, 47, + 114, -46, 47, 115, -42, 46, 116, -38, 45, 117, -33, 45, + 118, -29, 44, 119, -25, 43, 120, -21, 43, 121, -17, 42, + 122, -13, 41, 123, -9, 41, 124, -5, 40, 125, -1, 39, + 126, 3, 39, 127, 7, 38, 127, 11, 37, 128, 15, 37, + 129, 19, 36, 130, 23, 35, 131, 27, 35, 132, 31, 34, + 133, 35, 33, 134, 40, 33, 135, 44, 32, 136, 48, 31, + 137, 52, 31, 138, 56, 30, 139, 60, 29, 140, 64, 29, + 113, -63, 48, 114, -59, 47, 115, -55, 46, 116, -51, 46, + 117, -47, 45, 118, -43, 44, 118, -39, 44, 120, -34, 43, + 120, -30, 42, 121, -26, 42, 122, -22, 41, 123, -18, 40, + 124, -14, 40, 125, -10, 39, 126, -6, 38, 127, -2, 38, + 128, 2, 37, 129, 6, 36, 130, 10, 36, 131, 14, 35, + 132, 18, 34, 133, 22, 34, 134, 26, 33, 134, 30, 32, + 135, 34, 32, 136, 39, 31, 137, 43, 30, 138, 47, 30, + 139, 51, 29, 140, 55, 28, 141, 59, 28, 142, 63, 27, + 115, -65, 46, 116, -61, 45, 117, -57, 45, 118, -52, 44, + 119, -48, 43, 120, -44, 43, 121, -40, 42, 122, -36, 41, + 123, -32, 41, 124, -28, 40, 125, -24, 39, 126, -20, 39, + 127, -15, 38, 127, -11, 37, 128, -7, 37, 129, -3, 36, + 130, 0, 35, 131, 4, 35, 132, 8, 34, 133, 12, 33, + 134, 17, 33, 135, 21, 32, 136, 25, 31, 137, 29, 31, + 138, 33, 30, 139, 37, 29, 140, 41, 29, 141, 45, 28, + 141, 49, 27, 143, 54, 27, 143, 58, 26, 144, 62, 25, + 118, -66, 44, 119, -62, 44, 119, -58, 43, 120, -54, 42, + 121, -50, 42, 122, -46, 41, 123, -42, 40, 124, -37, 40, + 125, -33, 39, 126, -29, 38, 127, -25, 38, 128, -21, 37, + 129, -17, 36, 130, -13, 36, 131, -9, 35, 132, -5, 34, + 133, 0, 34, 134, 3, 33, 134, 7, 32, 135, 11, 32, + 136, 15, 31, 137, 19, 30, 138, 23, 30, 139, 27, 29, + 140, 31, 28, 141, 36, 28, 142, 40, 27, 143, 44, 26, + 144, 48, 26, 145, 52, 25, 146, 56, 24, 147, 60, 24, + 120, -67, 43, 121, -63, 42, 122, -59, 41, 123, -55, 41, + 124, -51, 40, 125, -47, 39, 126, -43, 39, 127, -38, 38, + 127, -34, 37, 128, -30, 37, 129, -26, 36, 130, -22, 35, + 131, -18, 35, 132, -14, 34, 133, -10, 33, 134, -6, 33, + 135, -1, 32, 136, 2, 31, 137, 6, 31, 138, 10, 30, + 139, 14, 29, 140, 18, 29, 141, 22, 28, 141, 26, 27, + 142, 30, 27, 143, 35, 26, 144, 39, 25, 145, 43, 25, + 146, 47, 24, 147, 51, 23, 148, 55, 23, 149, 59, 22, + 122, -69, 41, 123, -65, 40, 124, -61, 40, 125, -56, 39, + 126, -52, 38, 127, -48, 38, 128, -44, 37, 129, -40, 36, + 130, -36, 36, 131, -32, 35, 132, -28, 34, 133, -24, 34, + 134, -19, 33, 134, -15, 32, 135, -11, 32, 136, -7, 31, + 137, -3, 30, 138, 0, 30, 139, 4, 29, 140, 8, 28, + 141, 13, 28, 142, 17, 27, 143, 21, 26, 144, 25, 26, + 145, 29, 25, 146, 33, 24, 147, 37, 24, 148, 41, 23, + 149, 45, 22, 150, 50, 22, 150, 54, 21, 151, 58, 20, + 125, -70, 39, 126, -66, 39, 126, -62, 38, 127, -58, 37, + 128, -54, 37, 129, -50, 36, 130, -46, 35, 131, -41, 35, + 132, -37, 34, 133, -33, 33, 134, -29, 33, 135, -25, 32, + 136, -21, 31, 137, -17, 31, 138, -13, 30, 139, -9, 29, + 140, -4, 29, 141, 0, 28, 142, 3, 27, 142, 7, 27, + 143, 11, 26, 144, 15, 25, 145, 19, 25, 146, 23, 24, + 147, 27, 23, 148, 32, 23, 149, 36, 22, 150, 40, 21, + 151, 44, 21, 152, 48, 20, 153, 52, 19, 154, 56, 19, + 127, -71, 38, 128, -67, 37, 129, -63, 36, 130, -59, 36, + 131, -55, 35, 132, -51, 34, 133, -47, 34, 134, -42, 33, + 135, -38, 32, 135, -34, 32, 136, -30, 31, 137, -26, 30, + 138, -22, 30, 139, -18, 29, 140, -14, 28, 141, -10, 28, + 142, -5, 27, 143, -1, 26, 144, 2, 26, 145, 6, 25, + 146, 10, 24, 147, 14, 24, 148, 18, 23, 149, 22, 22, + 149, 26, 22, 150, 31, 21, 151, 35, 20, 152, 39, 20, + 153, 43, 19, 154, 47, 18, 155, 51, 18, 156, 55, 17, + 130, -73, 36, 131, -69, 35, 132, -65, 34, 133, -61, 34, + 134, -57, 33, 135, -53, 32, 136, -49, 32, 137, -44, 31, + 137, -40, 30, 138, -36, 30, 139, -32, 29, 140, -28, 28, + 141, -24, 28, 142, -20, 27, 143, -16, 26, 144, -12, 26, + 145, -7, 25, 146, -3, 24, 147, 0, 24, 148, 4, 23, + 149, 8, 22, 150, 12, 22, 151, 16, 21, 151, 20, 20, + 152, 24, 20, 153, 29, 19, 154, 33, 18, 155, 37, 18, + 156, 41, 17, 157, 45, 16, 158, 49, 15, 159, 53, 15, + 132, -74, 34, 133, -70, 33, 134, -66, 33, 135, -62, 32, + 136, -58, 31, 137, -54, 31, 138, -50, 30, 139, -45, 29, + 140, -41, 29, 141, -37, 28, 142, -33, 27, 143, -29, 27, + 144, -25, 26, 144, -21, 25, 145, -17, 25, 146, -13, 24, + 147, -8, 23, 148, -4, 23, 149, 0, 22, 150, 3, 21, + 151, 7, 20, 152, 11, 20, 153, 15, 19, 154, 19, 19, + 155, 23, 18, 156, 28, 17, 157, 32, 16, 158, 36, 16, + 158, 40, 15, 160, 44, 14, 160, 48, 14, 161, 52, 13, + 135, -76, 32, 136, -72, 32, 136, -68, 31, 137, -63, 30, + 138, -59, 30, 139, -55, 29, 140, -51, 28, 141, -47, 28, + 142, -43, 27, 143, -39, 26, 144, -35, 26, 145, -31, 25, + 146, -26, 24, 147, -22, 24, 148, -18, 23, 149, -14, 22, + 150, -10, 21, 151, -6, 21, 151, -2, 20, 152, 1, 20, + 153, 6, 19, 154, 10, 18, 155, 14, 18, 156, 18, 17, + 157, 22, 16, 158, 26, 15, 159, 30, 15, 160, 34, 14, + 161, 38, 14, 162, 43, 13, 163, 47, 12, 164, 51, 11, + 137, -77, 31, 138, -73, 30, 139, -69, 29, 140, -65, 29, + 141, -61, 28, 142, -57, 27, 143, -53, 27, 144, -48, 26, + 144, -44, 25, 145, -40, 25, 146, -36, 24, 147, -32, 23, + 148, -28, 22, 149, -24, 22, 150, -20, 21, 151, -16, 21, + 152, -11, 20, 153, -7, 19, 154, -3, 19, 155, 0, 18, + 156, 4, 17, 157, 8, 16, 158, 12, 16, 159, 16, 15, + 159, 20, 15, 160, 25, 14, 161, 29, 13, 162, 33, 12, + 163, 37, 12, 164, 41, 11, 165, 45, 10, 166, 49, 10, + 139, -78, 29, 140, -74, 28, 141, -70, 28, 142, -66, 27, + 143, -62, 26, 144, -58, 26, 145, -54, 25, 146, -49, 24, + 147, -45, 24, 148, -41, 23, 149, -37, 22, 150, -33, 22, + 151, -29, 21, 152, -25, 20, 152, -21, 20, 153, -17, 19, + 154, -12, 18, 155, -8, 17, 156, -4, 17, 157, 0, 16, + 158, 3, 15, 159, 7, 15, 160, 11, 14, 161, 15, 14, + 162, 19, 13, 163, 24, 12, 164, 28, 11, 165, 32, 11, + 166, 36, 10, 167, 40, 9, 167, 44, 9, 168, 48, 8, + 142, -80, 27, 143, -76, 27, 143, -72, 26, 145, -67, 25, + 145, -63, 25, 146, -59, 24, 147, -55, 23, 148, -51, 22, + 149, -47, 22, 150, -43, 21, 151, -39, 21, 152, -35, 20, + 153, -30, 19, 154, -26, 18, 155, -22, 18, 156, -18, 17, + 157, -14, 16, 158, -10, 16, 159, -6, 15, 159, -2, 15, + 160, 2, 14, 161, 6, 13, 162, 10, 12, 163, 14, 12, + 164, 18, 11, 165, 22, 10, 166, 26, 10, 167, 30, 9, + 168, 34, 8, 169, 39, 8, 170, 43, 7, 171, 47, 6, + 144, -81, 26, 145, -77, 25, 146, -73, 24, 147, -69, 23, + 148, -65, 23, 149, -61, 22, 150, -57, 22, 151, -52, 21, + 152, -48, 20, 152, -44, 20, 153, -40, 19, 154, -36, 18, + 155, -32, 17, 156, -28, 17, 157, -24, 16, 158, -20, 16, + 159, -15, 15, 160, -11, 14, 161, -7, 13, 162, -3, 13, + 163, 0, 12, 164, 4, 11, 165, 8, 11, 166, 12, 10, + 166, 16, 10, 168, 21, 9, 168, 25, 8, 169, 29, 7, + 170, 33, 7, 171, 37, 6, 172, 41, 5, 173, 45, 5, + 146, -82, 24, 147, -78, 23, 148, -74, 23, 149, -70, 22, + 150, -66, 21, 151, -62, 21, 152, -58, 20, 153, -53, 19, + 154, -49, 18, 155, -45, 18, 156, -41, 17, 157, -37, 17, + 158, -33, 16, 159, -29, 15, 159, -25, 14, 160, -21, 14, + 161, -16, 13, 162, -12, 12, 163, -8, 12, 164, -4, 11, + 165, 0, 10, 166, 3, 10, 167, 7, 9, 168, 11, 8, + 169, 15, 8, 170, 20, 7, 171, 24, 6, 172, 28, 6, + 173, 32, 5, 174, 36, 4, 175, 40, 4, 175, 44, 3, + 149, -84, 22, 150, -80, 22, 151, -76, 21, 152, -71, 20, + 152, -67, 19, 153, -63, 19, 154, -59, 18, 155, -55, 17, + 156, -51, 17, 157, -47, 16, 158, -43, 16, 159, -39, 15, + 160, -34, 14, 161, -30, 13, 162, -26, 13, 163, -22, 12, + 164, -18, 11, 165, -14, 11, 166, -10, 10, 166, -6, 9, + 168, -1, 9, 168, 2, 8, 169, 6, 7, 170, 10, 7, + 171, 14, 6, 172, 18, 5, 173, 22, 5, 174, 26, 4, + 175, 30, 3, 176, 35, 3, 177, 39, 2, 178, 43, 1, + 151, -85, 20, 152, -81, 20, 153, -77, 19, 154, -73, 18, + 155, -69, 18, 156, -65, 17, 157, -61, 17, 158, -56, 16, + 159, -52, 15, 159, -48, 14, 160, -44, 14, 161, -40, 13, + 162, -36, 12, 163, -32, 12, 164, -28, 11, 165, -24, 10, + 166, -19, 10, 167, -15, 9, 168, -11, 8, 169, -7, 8, + 170, -3, 7, 171, 0, 6, 172, 4, 6, 173, 8, 5, + 174, 12, 4, 175, 17, 4, 175, 21, 3, 176, 25, 2, + 177, 29, 2, 178, 33, 1, 179, 37, 0, 180, 41, 0, + 153, -86, 19, 154, -82, 18, 155, -78, 18, 156, -74, 17, + 157, -70, 16, 158, -66, 15, 159, -62, 15, 160, -57, 14, + 161, -53, 13, 162, -49, 13, 163, -45, 12, 164, -41, 12, + 165, -37, 11, 166, -33, 10, 167, -29, 9, 167, -25, 9, + 168, -20, 8, 169, -16, 7, 170, -12, 7, 171, -8, 6, + 172, -4, 5, 173, 0, 5, 174, 3, 4, 175, 7, 3, + 176, 11, 3, 177, 16, 2, 178, 20, 1, 179, 24, 1, + 180, 28, 0, 181, 32, 0, 182, 36, 0, 182, 40, -1, + 156, -88, 17, 157, -84, 16, 158, -80, 16, 159, -75, 15, + 160, -71, 14, 160, -67, 14, 161, -63, 13, 162, -59, 12, + 163, -55, 12, 164, -51, 11, 165, -47, 10, 166, -43, 10, + 167, -38, 9, 168, -34, 8, 169, -30, 8, 170, -26, 7, + 171, -22, 6, 172, -18, 6, 173, -14, 5, 174, -10, 4, + 175, -5, 4, 175, -1, 3, 176, 2, 2, 177, 6, 2, + 178, 10, 1, 179, 14, 0, 180, 18, 0, 181, 22, 0, + 182, 26, -1, 183, 31, -1, 184, 35, -2, 185, 39, -3, + 158, -89, 15, 159, -85, 15, 160, -81, 14, 161, -77, 13, + 162, -73, 13, 163, -69, 12, 164, -65, 11, 165, -60, 11, + 166, -56, 10, 167, -52, 9, 167, -48, 9, 168, -44, 8, + 169, -40, 7, 170, -36, 7, 171, -32, 6, 172, -28, 5, + 173, -23, 5, 174, -19, 4, 175, -15, 3, 176, -11, 3, + 177, -7, 2, 178, -3, 1, 179, 0, 1, 180, 4, 0, + 181, 8, 0, 182, 13, 0, 183, 17, -1, 183, 21, -2, + 184, 25, -2, 185, 29, -3, 186, 33, -4, 187, 37, -4, + 160, -90, 14, 161, -86, 13, 162, -82, 12, 163, -78, 12, + 164, -74, 11, 165, -70, 10, 166, -66, 10, 167, -61, 9, + 168, -57, 8, 169, -53, 8, 170, -49, 7, 171, -45, 6, + 172, -41, 6, 173, -37, 5, 174, -33, 4, 174, -29, 4, + 176, -24, 3, 176, -20, 2, 177, -16, 2, 178, -12, 1, + 179, -8, 0, 180, -4, 0, 181, 0, 0, 182, 3, -1, + 183, 7, -1, 184, 12, -2, 185, 16, -3, 186, 20, -3, + 187, 24, -4, 188, 28, -5, 189, 32, -5, 190, 36, -6, + 163, -92, 12, 164, -88, 11, 165, -84, 11, 166, -79, 10, + 167, -75, 9, 167, -71, 9, 168, -67, 8, 169, -63, 7, + 170, -59, 7, 171, -55, 6, 172, -51, 5, 173, -47, 5, + 174, -42, 4, 175, -38, 3, 176, -34, 3, 177, -30, 2, + 178, -26, 1, 179, -22, 1, 180, -18, 0, 181, -14, 0, + 182, -9, 0, 183, -5, -1, 183, -1, -2, 184, 2, -2, + 185, 6, -3, 186, 10, -4, 187, 14, -4, 188, 18, -5, + 189, 22, -6, 190, 27, -6, 191, 31, -7, 192, 35, -8, + 165, -93, 10, 166, -89, 10, 167, -85, 9, 168, -80, 8, + 169, -76, 8, 170, -72, 7, 171, -68, 6, 172, -64, 6, + 173, -60, 5, 174, -56, 4, 174, -52, 4, 175, -48, 3, + 176, -43, 2, 177, -39, 2, 178, -35, 1, 179, -31, 0, + 180, -27, 0, 181, -23, 0, 182, -19, -1, 183, -15, -1, + 184, -10, -2, 185, -6, -3, 186, -2, -3, 187, 1, -4, + 188, 5, -5, 189, 9, -5, 190, 13, -6, 190, 17, -7, + 191, 21, -7, 192, 26, -8, 193, 30, -9, 194, 34, -9, + 167, -94, 9, 168, -90, 8, 169, -86, 7, 170, -82, 7, + 171, -78, 6, 172, -74, 5, 173, -70, 5, 174, -65, 4, + 175, -61, 3, 176, -57, 3, 177, -53, 2, 178, -49, 1, + 179, -45, 1, 180, -41, 0, 181, -37, 0, 182, -33, 0, + 183, -28, -1, 183, -24, -2, 184, -20, -2, 185, -16, -3, + 186, -12, -4, 187, -8, -4, 188, -4, -5, 189, 0, -6, + 190, 3, -6, 191, 8, -7, 192, 12, -8, 193, 16, -8, + 194, 20, -9, 195, 24, -10, 196, 28, -10, 197, 32, -11, + 170, -96, 7, 171, -92, 6, 172, -88, 6, 173, -83, 5, + 174, -79, 4, 175, -75, 4, 175, -71, 3, 176, -67, 2, + 177, -63, 2, 178, -59, 1, 179, -55, 0, 180, -51, 0, + 181, -46, 0, 182, -42, -1, 183, -38, -1, 184, -34, -2, + 185, -30, -3, 186, -26, -3, 187, -22, -4, 188, -18, -5, + 189, -13, -5, 190, -9, -6, 190, -5, -7, 191, -1, -7, + 192, 2, -8, 193, 6, -9, 194, 10, -9, 195, 14, -10, + 196, 18, -11, 197, 23, -11, 198, 27, -12, 199, 31, -13, + 172, -97, 5, 173, -93, 5, 174, -89, 4, 175, -84, 3, + 176, -80, 3, 177, -76, 2, 178, -72, 1, 179, -68, 1, + 180, -64, 0, 181, -60, 0, 182, -56, 0, 182, -52, -1, + 183, -47, -2, 184, -43, -2, 185, -39, -3, 186, -35, -4, + 187, -31, -4, 188, -27, -5, 189, -23, -6, 190, -19, -6, + 191, -14, -7, 192, -10, -8, 193, -6, -8, 194, -2, -9, + 195, 1, -10, 196, 5, -10, 197, 9, -11, 198, 13, -12, + 198, 17, -12, 199, 22, -13, 200, 26, -14, 201, 30, -14, + 175, -98, 4, 175, -94, 3, 176, -90, 2, 177, -86, 2, + 178, -82, 1, 179, -78, 0, 180, -74, 0, 181, -69, 0, + 182, -65, -1, 183, -61, -1, 184, -57, -2, 185, -53, -3, + 186, -49, -3, 187, -45, -4, 188, -41, -5, 189, -37, -5, + 190, -32, -6, 191, -28, -7, 191, -24, -7, 192, -20, -8, + 193, -16, -9, 194, -12, -9, 195, -8, -10, 196, -4, -11, + 197, 0, -11, 198, 4, -12, 199, 8, -13, 200, 12, -13, + 201, 16, -14, 202, 20, -15, 203, 24, -15, 204, 28, -16, + 177, -100, 2, 178, -96, 1, 179, -92, 1, 180, -87, 0, + 181, -83, 0, 182, -79, 0, 182, -75, -1, 184, -71, -2, + 184, -67, -2, 185, -63, -3, 186, -59, -4, 187, -55, -4, + 188, -50, -5, 189, -46, -6, 190, -42, -6, 191, -38, -7, + 192, -34, -8, 193, -30, -8, 194, -26, -9, 195, -22, -10, + 196, -17, -10, 197, -13, -11, 198, -9, -12, 198, -5, -12, + 199, -1, -13, 200, 2, -14, 201, 6, -14, 202, 10, -15, + 203, 14, -16, 204, 19, -16, 205, 23, -17, 206, 27, -18, + 180, -101, 0, 181, -97, 0, 182, -93, 0, 183, -89, -1, + 184, -85, -2, 184, -81, -2, 185, -77, -3, 186, -72, -4, + 187, -68, -4, 188, -64, -5, 189, -60, -6, 190, -56, -6, + 191, -52, -7, 192, -48, -8, 193, -44, -8, 194, -40, -9, + 195, -35, -10, 196, -31, -10, 197, -27, -11, 198, -23, -12, + 199, -19, -12, 200, -15, -13, 200, -11, -14, 201, -7, -14, + 202, -3, -15, 203, 1, -16, 204, 5, -16, 205, 9, -17, + 206, 13, -18, 207, 17, -18, 208, 21, -19, 209, 25, -20, + 182, -103, -1, 183, -99, -1, 184, -95, -2, 185, -90, -3, + 186, -86, -3, 187, -82, -4, 188, -78, -5, 189, -74, -5, + 190, -70, -6, 191, -66, -7, 192, -62, -7, 192, -58, -8, + 193, -53, -9, 194, -49, -9, 195, -45, -10, 196, -41, -11, + 197, -37, -11, 198, -33, -12, 199, -29, -13, 200, -25, -13, + 201, -20, -14, 202, -16, -15, 203, -12, -15, 204, -8, -16, + 205, -4, -17, 206, 0, -17, 207, 3, -18, 207, 7, -19, + 208, 11, -19, 209, 16, -20, 210, 20, -21, 211, 24, -21, + 185, -104, -2, 185, -100, -3, 186, -96, -4, 187, -91, -4, + 188, -87, -5, 189, -83, -6, 190, -79, -6, 191, -75, -7, + 192, -71, -8, 193, -67, -8, 194, -63, -9, 195, -59, -10, + 196, -54, -10, 197, -50, -11, 198, -46, -12, 199, -42, -12, + 200, -38, -13, 200, -34, -14, 201, -30, -14, 202, -26, -15, + 203, -21, -16, 204, -17, -16, 205, -13, -17, 206, -9, -18, + 207, -5, -18, 208, -1, -19, 209, 2, -20, 210, 6, -20, + 211, 10, -21, 212, 15, -22, 213, 19, -22, 214, 23, -23, + 187, -105, -4, 188, -101, -5, 189, -97, -5, 190, -93, -6, + 191, -89, -7, 192, -85, -7, 192, -81, -8, 193, -76, -9, + 194, -72, -9, 195, -68, -10, 196, -64, -11, 197, -60, -11, + 198, -56, -12, 199, -52, -13, 200, -48, -13, 201, -44, -14, + 202, -39, -15, 203, -35, -15, 204, -31, -16, 205, -27, -17, + 206, -23, -17, 207, -19, -18, 208, -15, -19, 208, -11, -19, + 209, -7, -20, 210, -2, -21, 211, 1, -21, 212, 5, -22, + 213, 9, -23, 214, 13, -23, 215, 17, -24, 216, 21, -25, + 189, -107, -6, 190, -103, -6, 191, -99, -7, 192, -94, -8, + 193, -90, -8, 194, -86, -9, 195, -82, -10, 196, -78, -10, + 197, -74, -11, 198, -70, -12, 199, -66, -12, 199, -62, -13, + 201, -57, -14, 201, -53, -14, 202, -49, -15, 203, -45, -16, + 204, -41, -16, 205, -37, -17, 206, -33, -18, 207, -29, -18, + 208, -24, -19, 209, -20, -20, 210, -16, -20, 211, -12, -21, + 212, -8, -22, 213, -4, -22, 214, 0, -23, 215, 3, -24, + 215, 7, -24, 216, 12, -25, 217, 16, -26, 218, 20, -26, + 192, -108, -7, 192, -104, -8, 193, -100, -9, 194, -95, -9, + 195, -91, -10, 196, -87, -11, 197, -83, -11, 198, -79, -12, + 199, -75, -13, 200, -71, -13, 201, -67, -14, 202, -63, -15, + 203, -58, -15, 204, -54, -16, 205, -50, -17, 206, -46, -17, + 207, -42, -18, 208, -38, -19, 208, -34, -19, 209, -30, -20, + 210, -25, -21, 211, -21, -21, 212, -17, -22, 213, -13, -23, + 214, -9, -23, 215, -5, -24, 216, -1, -25, 217, 2, -25, + 218, 6, -26, 219, 11, -27, 220, 15, -27, 221, 19, -28, + 194, -109, -9, 195, -105, -10, 196, -101, -10, 197, -97, -11, + 198, -93, -12, 199, -89, -12, 199, -85, -13, 201, -80, -14, + 201, -76, -14, 202, -72, -15, 203, -68, -16, 204, -64, -16, + 205, -60, -17, 206, -56, -18, 207, -52, -18, 208, -48, -19, + 209, -43, -20, 210, -39, -20, 211, -35, -21, 212, -31, -22, + 213, -27, -22, 214, -23, -23, 215, -19, -24, 215, -15, -24, + 216, -11, -25, 217, -6, -26, 218, -2, -26, 219, 1, -27, + 220, 5, -28, 221, 9, -29, 222, 13, -29, 223, 17, -30, + 196, -111, -11, 197, -107, -11, 198, -103, -12, 199, -98, -13, + 200, -94, -13, 201, -90, -14, 202, -86, -15, 203, -82, -15, + 204, -78, -16, 205, -74, -17, 206, -70, -17, 207, -66, -18, + 208, -61, -19, 208, -57, -19, 209, -53, -20, 210, -49, -21, + 211, -45, -21, 212, -41, -22, 213, -37, -23, 214, -33, -23, + 215, -28, -24, 216, -24, -25, 217, -20, -25, 218, -16, -26, + 219, -12, -27, 220, -8, -27, 221, -4, -28, 222, 0, -29, + 222, 3, -29, 224, 8, -30, 224, 12, -31, 225, 16, -31, + 199, -112, -12, 200, -108, -13, 200, -104, -14, 201, -99, -14, + 202, -95, -15, 203, -91, -16, 204, -87, -16, 205, -83, -17, + 206, -79, -18, 207, -75, -18, 208, -71, -19, 209, -67, -20, + 210, -62, -20, 211, -58, -21, 212, -54, -22, 213, -50, -22, + 214, -46, -23, 215, -42, -24, 215, -38, -24, 216, -34, -25, + 217, -29, -26, 218, -25, -26, 219, -21, -27, 220, -17, -28, + 221, -13, -28, 222, -9, -29, 223, -5, -30, 224, -1, -30, + 225, 2, -31, 226, 7, -32, 227, 11, -33, 228, 15, -33, + 201, -113, -14, 202, -109, -15, 203, -105, -15, 204, -101, -16, + 205, -97, -17, 206, -93, -17, 207, -89, -18, 208, -84, -19, + 208, -80, -19, 209, -76, -20, 210, -72, -21, 211, -68, -21, + 212, -64, -22, 213, -60, -23, 214, -56, -23, 215, -52, -24, + 216, -47, -25, 217, -43, -25, 218, -39, -26, 219, -35, -27, + 220, -31, -28, 221, -27, -28, 222, -23, -29, 223, -19, -29, + 223, -15, -30, 224, -10, -31, 225, -6, -31, 226, -2, -32, + 227, 1, -33, 228, 5, -34, 229, 9, -34, 230, 13, -35, + 203, -115, -16, 204, -111, -16, 205, -107, -17, 206, -102, -18, + 207, -98, -18, 208, -94, -19, 209, -90, -20, 210, -86, -20, + 211, -82, -21, 212, -78, -22, 213, -74, -22, 214, -70, -23, + 215, -65, -24, 216, -61, -24, 216, -57, -25, 217, -53, -26, + 218, -49, -26, 219, -45, -27, 220, -41, -28, 221, -37, -28, + 222, -32, -29, 223, -28, -30, 224, -24, -30, 225, -20, -31, + 226, -16, -32, 227, -12, -33, 228, -8, -33, 229, -4, -34, + 230, 0, -34, 231, 4, -35, 231, 8, -36, 232, 12, -37, + 56, -31, 94, 57, -27, 93, 58, -23, 93, 59, -19, 92, + 60, -15, 91, 61, -11, 91, 62, -7, 90, 63, -2, 89, + 64, 1, 89, 64, 5, 88, 65, 9, 87, 66, 13, 87, + 67, 17, 86, 68, 21, 85, 69, 25, 85, 70, 29, 84, + 71, 34, 83, 72, 38, 83, 73, 42, 82, 74, 46, 81, + 75, 50, 81, 76, 54, 80, 77, 58, 79, 78, 62, 79, + 78, 66, 78, 79, 71, 77, 80, 75, 77, 81, 79, 76, + 82, 83, 75, 83, 87, 75, 84, 91, 74, 85, 95, 73, + 58, -33, 92, 59, -29, 92, 60, -25, 91, 61, -20, 90, + 62, -16, 90, 63, -12, 89, 64, -8, 88, 65, -4, 88, + 66, 0, 87, 67, 3, 86, 68, 7, 86, 69, 11, 85, + 70, 16, 84, 71, 20, 84, 71, 24, 83, 72, 28, 82, + 73, 32, 82, 74, 36, 81, 75, 40, 80, 76, 44, 80, + 77, 49, 79, 78, 53, 78, 79, 57, 78, 80, 61, 77, + 81, 65, 76, 82, 69, 76, 83, 73, 75, 84, 77, 74, + 85, 81, 74, 86, 86, 73, 87, 90, 72, 87, 94, 72, + 61, -34, 91, 62, -30, 90, 63, -26, 89, 64, -22, 89, + 64, -18, 88, 65, -14, 87, 66, -10, 87, 67, -5, 86, + 68, -1, 85, 69, 2, 85, 70, 6, 84, 71, 10, 83, + 72, 14, 83, 73, 18, 82, 74, 22, 81, 75, 26, 81, + 76, 31, 80, 77, 35, 79, 78, 39, 79, 78, 43, 78, + 80, 47, 77, 80, 51, 77, 81, 55, 76, 82, 59, 75, + 83, 63, 75, 84, 68, 74, 85, 72, 73, 86, 76, 73, + 87, 80, 72, 88, 84, 71, 89, 88, 71, 90, 92, 70, + 63, -35, 89, 64, -31, 88, 65, -27, 88, 66, -23, 87, + 67, -19, 86, 68, -15, 86, 69, -11, 85, 70, -6, 84, + 71, -2, 84, 71, 1, 83, 72, 5, 82, 73, 9, 82, + 74, 13, 81, 75, 17, 80, 76, 21, 80, 77, 25, 79, + 78, 30, 78, 79, 34, 78, 80, 38, 77, 81, 42, 76, + 82, 46, 76, 83, 50, 75, 84, 54, 74, 85, 58, 74, + 86, 62, 73, 87, 67, 72, 87, 71, 72, 88, 75, 71, + 89, 79, 70, 90, 83, 70, 91, 87, 69, 92, 91, 68, + 65, -37, 87, 66, -33, 87, 67, -29, 86, 68, -24, 85, + 69, -20, 85, 70, -16, 84, 71, -12, 83, 72, -8, 83, + 73, -4, 82, 74, 0, 81, 75, 3, 81, 76, 7, 80, + 77, 12, 79, 78, 16, 79, 79, 20, 78, 79, 24, 77, + 80, 28, 77, 81, 32, 76, 82, 36, 75, 83, 40, 75, + 84, 45, 74, 85, 49, 73, 86, 53, 73, 87, 57, 72, + 88, 61, 71, 89, 65, 71, 90, 69, 70, 91, 73, 69, + 92, 77, 69, 93, 82, 68, 94, 86, 67, 94, 90, 67, + 68, -38, 86, 69, -34, 85, 70, -30, 84, 71, -26, 84, + 72, -22, 83, 72, -18, 82, 73, -14, 82, 74, -9, 81, + 75, -5, 80, 76, -1, 80, 77, 2, 79, 78, 6, 78, + 79, 10, 78, 80, 14, 77, 81, 18, 76, 82, 22, 76, + 83, 27, 75, 84, 31, 74, 85, 35, 74, 86, 39, 73, + 87, 43, 72, 87, 47, 72, 88, 51, 71, 89, 55, 70, + 90, 59, 70, 91, 64, 69, 92, 68, 68, 93, 72, 68, + 94, 76, 67, 95, 80, 66, 96, 84, 66, 97, 88, 65, + 70, -39, 84, 71, -35, 83, 72, -31, 83, 73, -27, 82, + 74, -23, 81, 75, -19, 81, 76, -15, 80, 77, -10, 79, + 78, -6, 79, 79, -2, 78, 79, 1, 77, 80, 5, 77, + 81, 9, 76, 82, 13, 75, 83, 17, 75, 84, 21, 74, + 85, 26, 73, 86, 30, 73, 87, 34, 72, 88, 38, 71, + 89, 42, 71, 90, 46, 70, 91, 50, 69, 92, 54, 69, + 93, 58, 68, 94, 63, 67, 94, 67, 67, 95, 71, 66, + 96, 75, 65, 97, 79, 65, 98, 83, 64, 99, 87, 63, + 72, -41, 82, 73, -37, 82, 74, -33, 81, 75, -28, 80, + 76, -24, 80, 77, -20, 79, 78, -16, 78, 79, -12, 78, + 80, -8, 77, 81, -4, 76, 82, 0, 76, 83, 3, 75, + 84, 8, 74, 85, 12, 74, 86, 16, 73, 86, 20, 72, + 87, 24, 72, 88, 28, 71, 89, 32, 70, 90, 36, 70, + 91, 41, 69, 92, 45, 68, 93, 49, 68, 94, 53, 67, + 95, 57, 66, 96, 61, 66, 97, 65, 65, 98, 69, 64, + 99, 73, 64, 100, 78, 63, 101, 82, 62, 102, 86, 62, + 75, -42, 81, 76, -38, 80, 77, -34, 79, 78, -29, 79, + 79, -25, 78, 79, -21, 77, 80, -17, 77, 81, -13, 76, + 82, -9, 75, 83, -5, 75, 84, -1, 74, 85, 2, 73, + 86, 7, 73, 87, 11, 72, 88, 15, 71, 89, 19, 71, + 90, 23, 70, 91, 27, 69, 92, 31, 69, 93, 35, 68, + 94, 40, 67, 95, 44, 67, 95, 48, 66, 96, 52, 65, + 97, 56, 65, 98, 60, 64, 99, 64, 63, 100, 68, 63, + 101, 72, 62, 102, 77, 61, 103, 81, 61, 104, 85, 60, + 77, -43, 79, 78, -39, 78, 79, -35, 78, 80, -31, 77, + 81, -27, 76, 82, -23, 76, 83, -19, 75, 84, -14, 74, + 85, -10, 74, 86, -6, 73, 86, -2, 72, 87, 1, 72, + 88, 5, 71, 89, 9, 70, 90, 13, 70, 91, 17, 69, + 92, 22, 68, 93, 26, 68, 94, 30, 67, 95, 34, 66, + 96, 38, 66, 97, 42, 65, 98, 46, 64, 99, 50, 64, + 100, 54, 63, 101, 59, 62, 102, 63, 62, 102, 67, 61, + 103, 71, 60, 104, 75, 59, 105, 79, 59, 106, 83, 58, + 79, -45, 77, 80, -41, 77, 81, -37, 76, 82, -32, 75, + 83, -28, 75, 84, -24, 74, 85, -20, 73, 86, -16, 73, + 87, -12, 72, 88, -8, 71, 89, -4, 71, 90, 0, 70, + 91, 4, 69, 92, 8, 69, 93, 12, 68, 94, 16, 67, + 95, 20, 67, 95, 24, 66, 96, 28, 65, 97, 32, 65, + 98, 37, 64, 99, 41, 63, 100, 45, 63, 101, 49, 62, + 102, 53, 61, 103, 57, 61, 104, 61, 60, 105, 65, 59, + 106, 69, 59, 107, 74, 58, 108, 78, 57, 109, 82, 57, + 82, -46, 75, 83, -42, 75, 84, -38, 74, 85, -34, 73, + 86, -30, 72, 87, -26, 72, 88, -22, 71, 89, -17, 70, + 90, -13, 70, 91, -9, 69, 92, -5, 68, 93, -1, 68, + 94, 2, 67, 95, 6, 66, 96, 10, 66, 96, 14, 65, + 97, 19, 64, 98, 23, 64, 99, 27, 63, 100, 31, 62, + 101, 35, 62, 102, 39, 61, 103, 43, 60, 104, 47, 60, + 105, 51, 59, 106, 56, 58, 107, 60, 58, 108, 64, 57, + 109, 68, 56, 110, 72, 56, 111, 76, 55, 111, 80, 54, + 85, -48, 73, 86, -44, 73, 87, -40, 72, 88, -35, 71, + 89, -31, 71, 89, -27, 70, 90, -23, 69, 91, -19, 69, + 92, -15, 68, 93, -11, 67, 94, -7, 67, 95, -3, 66, + 96, 1, 65, 97, 5, 65, 98, 9, 64, 99, 13, 63, + 100, 17, 63, 101, 21, 62, 102, 25, 61, 103, 29, 61, + 104, 34, 60, 104, 38, 59, 105, 42, 59, 106, 46, 58, + 107, 50, 57, 108, 54, 57, 109, 58, 56, 110, 62, 55, + 111, 66, 55, 112, 71, 54, 113, 75, 53, 114, 79, 53, + 87, -49, 72, 88, -45, 71, 89, -41, 71, 90, -36, 70, + 91, -32, 69, 92, -28, 68, 93, -24, 68, 94, -20, 67, + 95, -16, 66, 96, -12, 66, 96, -8, 65, 97, -4, 64, + 98, 0, 64, 99, 4, 63, 100, 8, 62, 101, 12, 62, + 102, 16, 61, 103, 20, 60, 104, 24, 60, 105, 28, 59, + 106, 33, 58, 107, 37, 58, 108, 41, 57, 109, 45, 56, + 110, 49, 56, 111, 53, 55, 112, 57, 54, 112, 61, 54, + 113, 65, 53, 114, 70, 52, 115, 74, 52, 116, 78, 51, + 89, -50, 70, 90, -46, 69, 91, -42, 69, 92, -38, 68, + 93, -34, 67, 94, -30, 67, 95, -26, 66, 96, -21, 65, + 97, -17, 65, 98, -13, 64, 99, -9, 63, 100, -5, 63, + 101, -1, 62, 102, 2, 61, 103, 6, 61, 103, 10, 60, + 105, 15, 59, 105, 19, 59, 106, 23, 58, 107, 27, 57, + 108, 31, 57, 109, 35, 56, 110, 39, 55, 111, 43, 55, + 112, 47, 54, 113, 52, 53, 114, 56, 53, 115, 60, 52, + 116, 64, 51, 117, 68, 51, 118, 72, 50, 119, 76, 49, + 92, -52, 68, 93, -48, 68, 94, -44, 67, 95, -39, 66, + 96, -35, 66, 96, -31, 65, 97, -27, 64, 98, -23, 64, + 99, -19, 63, 100, -15, 62, 101, -11, 62, 102, -7, 61, + 103, -2, 60, 104, 1, 60, 105, 5, 59, 106, 9, 58, + 107, 13, 58, 108, 17, 57, 109, 21, 56, 110, 25, 56, + 111, 30, 55, 112, 34, 54, 112, 38, 54, 113, 42, 53, + 114, 46, 52, 115, 50, 52, 116, 54, 51, 117, 58, 50, + 118, 62, 50, 119, 67, 49, 120, 71, 48, 121, 75, 48, + 94, -53, 67, 95, -49, 66, 96, -45, 65, 97, -40, 65, + 98, -36, 64, 99, -32, 63, 100, -28, 63, 101, -24, 62, + 102, -20, 61, 103, -16, 61, 104, -12, 60, 104, -8, 59, + 105, -3, 59, 106, 0, 58, 107, 4, 57, 108, 8, 57, + 109, 12, 56, 110, 16, 55, 111, 20, 55, 112, 24, 54, + 113, 29, 53, 114, 33, 53, 115, 37, 52, 116, 41, 51, + 117, 45, 51, 118, 49, 50, 119, 53, 49, 119, 57, 49, + 120, 61, 48, 121, 66, 47, 122, 70, 47, 123, 74, 46, + 97, -54, 65, 97, -50, 64, 98, -46, 64, 99, -42, 63, + 100, -38, 62, 101, -34, 62, 102, -30, 61, 103, -25, 60, + 104, -21, 60, 105, -17, 59, 106, -13, 58, 107, -9, 58, + 108, -5, 57, 109, -1, 56, 110, 2, 56, 111, 6, 55, + 112, 11, 54, 112, 15, 54, 113, 19, 53, 114, 23, 52, + 115, 27, 52, 116, 31, 51, 117, 35, 50, 118, 39, 50, + 119, 43, 49, 120, 48, 48, 121, 52, 48, 122, 56, 47, + 123, 60, 46, 124, 64, 46, 125, 68, 45, 126, 72, 44, + 99, -56, 63, 100, -52, 63, 101, -48, 62, 102, -43, 61, + 103, -39, 61, 104, -35, 60, 104, -31, 59, 105, -27, 59, + 106, -23, 58, 107, -19, 57, 108, -15, 57, 109, -11, 56, + 110, -6, 55, 111, -2, 55, 112, 1, 54, 113, 5, 53, + 114, 9, 53, 115, 13, 52, 116, 17, 51, 117, 21, 51, + 118, 26, 50, 119, 30, 49, 119, 34, 49, 120, 38, 48, + 121, 42, 47, 122, 46, 47, 123, 50, 46, 124, 54, 45, + 125, 58, 45, 126, 63, 44, 127, 67, 43, 128, 71, 43, + 101, -57, 62, 102, -53, 61, 103, -49, 60, 104, -44, 60, + 105, -40, 59, 106, -36, 58, 107, -32, 58, 108, -28, 57, + 109, -24, 56, 110, -20, 56, 111, -16, 55, 111, -12, 54, + 112, -7, 54, 113, -3, 53, 114, 0, 52, 115, 4, 52, + 116, 8, 51, 117, 12, 50, 118, 16, 50, 119, 20, 49, + 120, 25, 48, 121, 29, 48, 122, 33, 47, 123, 37, 46, + 124, 41, 46, 125, 45, 45, 126, 49, 44, 127, 53, 44, + 127, 57, 43, 128, 62, 42, 129, 66, 42, 130, 70, 41, + 104, -58, 60, 104, -54, 59, 105, -50, 59, 106, -46, 58, + 107, -42, 57, 108, -38, 57, 109, -34, 56, 110, -29, 55, + 111, -25, 55, 112, -21, 54, 113, -17, 53, 114, -13, 53, + 115, -9, 52, 116, -5, 51, 117, -1, 51, 118, 2, 50, + 119, 7, 49, 120, 11, 49, 120, 15, 48, 121, 19, 47, + 122, 23, 47, 123, 27, 46, 124, 31, 45, 125, 35, 45, + 126, 39, 44, 127, 44, 43, 128, 48, 43, 129, 52, 42, + 130, 56, 41, 131, 60, 41, 132, 64, 40, 133, 68, 39, + 106, -60, 58, 107, -56, 58, 108, -52, 57, 109, -47, 56, + 110, -43, 56, 111, -39, 55, 111, -35, 54, 113, -31, 54, + 113, -27, 53, 114, -23, 52, 115, -19, 52, 116, -15, 51, + 117, -10, 50, 118, -6, 50, 119, -2, 49, 120, 1, 48, + 121, 5, 48, 122, 9, 47, 123, 13, 46, 124, 17, 46, + 125, 22, 45, 126, 26, 44, 127, 30, 44, 127, 34, 43, + 128, 38, 42, 129, 42, 42, 130, 46, 41, 131, 50, 40, + 132, 54, 40, 133, 59, 39, 134, 63, 38, 135, 67, 38, + 108, -61, 57, 109, -57, 56, 110, -53, 55, 111, -48, 55, + 112, -44, 54, 113, -40, 53, 114, -36, 53, 115, -32, 52, + 116, -28, 51, 117, -24, 51, 118, -20, 50, 119, -16, 49, + 120, -11, 49, 120, -7, 48, 121, -3, 47, 122, 0, 47, + 123, 4, 46, 124, 8, 45, 125, 12, 45, 126, 16, 44, + 127, 21, 43, 128, 25, 43, 129, 29, 42, 130, 33, 41, + 131, 37, 41, 132, 41, 40, 133, 45, 39, 134, 49, 39, + 134, 53, 38, 136, 58, 37, 136, 62, 37, 137, 66, 36, + 111, -62, 55, 112, -58, 54, 112, -54, 54, 113, -50, 53, + 114, -46, 52, 115, -42, 52, 116, -38, 51, 117, -33, 50, + 118, -29, 50, 119, -25, 49, 120, -21, 48, 121, -17, 48, + 122, -13, 47, 123, -9, 46, 124, -5, 46, 125, -1, 45, + 126, 3, 44, 127, 7, 44, 127, 11, 43, 128, 15, 42, + 129, 19, 42, 130, 23, 41, 131, 27, 40, 132, 31, 40, + 133, 35, 39, 134, 40, 38, 135, 44, 38, 136, 48, 37, + 137, 52, 36, 138, 56, 36, 139, 60, 35, 140, 64, 34, + 113, -64, 53, 114, -60, 53, 115, -56, 52, 116, -51, 51, + 117, -47, 51, 118, -43, 50, 119, -39, 49, 120, -35, 49, + 120, -31, 48, 121, -27, 47, 122, -23, 47, 123, -19, 46, + 124, -14, 45, 125, -10, 45, 126, -6, 44, 127, -2, 43, + 128, 1, 43, 129, 5, 42, 130, 9, 41, 131, 13, 41, + 132, 18, 40, 133, 22, 39, 134, 26, 39, 134, 30, 38, + 135, 34, 37, 136, 38, 37, 137, 42, 36, 138, 46, 35, + 139, 50, 35, 140, 55, 34, 141, 59, 33, 142, 63, 33, + 115, -65, 52, 116, -61, 51, 117, -57, 50, 118, -52, 50, + 119, -48, 49, 120, -44, 48, 121, -40, 48, 122, -36, 47, + 123, -32, 46, 124, -28, 46, 125, -24, 45, 126, -20, 44, + 127, -15, 44, 127, -11, 43, 128, -7, 42, 129, -3, 42, + 130, 0, 41, 131, 4, 40, 132, 8, 40, 133, 12, 39, + 134, 17, 38, 135, 21, 38, 136, 25, 37, 137, 29, 36, + 138, 33, 36, 139, 37, 35, 140, 41, 34, 141, 45, 34, + 142, 49, 33, 143, 54, 32, 143, 58, 32, 144, 62, 31, + 118, -66, 50, 119, -62, 49, 119, -58, 49, 120, -54, 48, + 121, -50, 47, 122, -46, 47, 123, -42, 46, 124, -37, 45, + 125, -33, 45, 126, -29, 44, 127, -25, 43, 128, -21, 43, + 129, -17, 42, 130, -13, 41, 131, -9, 41, 132, -5, 40, + 133, 0, 39, 134, 3, 39, 135, 7, 38, 135, 11, 37, + 136, 15, 37, 137, 19, 36, 138, 23, 35, 139, 27, 35, + 140, 31, 34, 141, 36, 33, 142, 40, 33, 143, 44, 32, + 144, 48, 31, 145, 52, 31, 146, 56, 30, 147, 60, 29, + 120, -67, 48, 121, -63, 48, 122, -59, 47, 123, -55, 46, + 124, -51, 46, 125, -47, 45, 126, -43, 44, 127, -38, 44, + 128, -34, 43, 128, -30, 42, 129, -26, 42, 130, -22, 41, + 131, -18, 40, 132, -14, 40, 133, -10, 39, 134, -6, 38, + 135, -1, 38, 136, 2, 37, 137, 6, 36, 138, 10, 36, + 139, 14, 35, 140, 18, 34, 141, 22, 34, 142, 26, 33, + 142, 30, 32, 143, 35, 32, 144, 39, 31, 145, 43, 30, + 146, 47, 30, 147, 51, 29, 148, 55, 28, 149, 59, 28, + 122, -69, 47, 123, -65, 46, 124, -61, 45, 125, -56, 45, + 126, -52, 44, 127, -48, 43, 128, -44, 43, 129, -40, 42, + 130, -36, 41, 131, -32, 41, 132, -28, 40, 133, -24, 39, + 134, -19, 39, 135, -15, 38, 135, -11, 37, 136, -7, 37, + 137, -3, 36, 138, 0, 35, 139, 4, 35, 140, 8, 34, + 141, 13, 33, 142, 17, 33, 143, 21, 32, 144, 25, 31, + 145, 29, 31, 146, 33, 30, 147, 37, 29, 148, 41, 29, + 149, 45, 28, 150, 50, 27, 151, 54, 27, 151, 58, 26, + 125, -70, 45, 126, -66, 44, 127, -62, 44, 128, -58, 43, + 128, -54, 42, 129, -50, 42, 130, -46, 41, 131, -41, 40, + 132, -37, 40, 133, -33, 39, 134, -29, 38, 135, -25, 38, + 136, -21, 37, 137, -17, 36, 138, -13, 36, 139, -9, 35, + 140, -4, 34, 141, 0, 34, 142, 3, 33, 142, 7, 32, + 144, 11, 32, 144, 15, 31, 145, 19, 30, 146, 23, 30, + 147, 27, 29, 148, 32, 28, 149, 36, 28, 150, 40, 27, + 151, 44, 26, 152, 48, 26, 153, 52, 25, 154, 56, 24, + 127, -71, 43, 128, -67, 43, 129, -63, 42, 130, -59, 41, + 131, -55, 41, 132, -51, 40, 133, -47, 39, 134, -42, 39, + 135, -38, 38, 135, -34, 37, 136, -30, 37, 137, -26, 36, + 138, -22, 35, 139, -18, 35, 140, -14, 34, 141, -10, 33, + 142, -5, 33, 143, -1, 32, 144, 2, 31, 145, 6, 31, + 146, 10, 30, 147, 14, 29, 148, 18, 29, 149, 22, 28, + 149, 26, 27, 151, 31, 27, 151, 35, 26, 152, 39, 25, + 153, 43, 25, 154, 47, 24, 155, 51, 23, 156, 55, 23, + 129, -73, 42, 130, -69, 41, 131, -65, 40, 132, -60, 40, + 133, -56, 39, 134, -52, 38, 135, -48, 38, 136, -44, 37, + 137, -40, 36, 138, -36, 36, 139, -32, 35, 140, -28, 34, + 141, -23, 34, 142, -19, 33, 142, -15, 32, 143, -11, 32, + 144, -7, 31, 145, -3, 30, 146, 0, 30, 147, 4, 29, + 148, 9, 28, 149, 13, 28, 150, 17, 27, 151, 21, 26, + 152, 25, 26, 153, 29, 25, 154, 33, 24, 155, 37, 24, + 156, 41, 23, 157, 46, 22, 158, 50, 22, 158, 54, 21, + 132, -74, 40, 133, -70, 39, 134, -66, 38, 135, -62, 38, + 136, -58, 37, 137, -54, 36, 138, -50, 36, 139, -45, 35, + 140, -41, 34, 141, -37, 34, 142, -33, 33, 143, -29, 32, + 144, -25, 32, 145, -21, 31, 145, -17, 30, 146, -13, 30, + 147, -8, 29, 148, -4, 28, 149, 0, 28, 150, 3, 27, + 151, 7, 26, 152, 11, 26, 153, 15, 25, 154, 19, 24, + 155, 23, 24, 156, 28, 23, 157, 32, 22, 158, 36, 22, + 159, 40, 21, 160, 44, 20, 160, 48, 19, 161, 52, 19, + 135, -76, 38, 136, -72, 37, 136, -68, 37, 138, -63, 36, + 138, -59, 35, 139, -55, 35, 140, -51, 34, 141, -47, 33, + 142, -43, 33, 143, -39, 32, 144, -35, 31, 145, -31, 31, + 146, -26, 30, 147, -22, 29, 148, -18, 29, 149, -14, 28, + 150, -10, 27, 151, -6, 27, 152, -2, 26, 152, 1, 25, + 153, 6, 24, 154, 10, 24, 155, 14, 23, 156, 18, 23, + 157, 22, 22, 158, 26, 21, 159, 30, 20, 160, 34, 20, + 161, 38, 19, 162, 43, 18, 163, 47, 18, 164, 51, 17, + 137, -77, 36, 138, -73, 36, 139, -69, 35, 140, -65, 34, + 141, -61, 34, 142, -57, 33, 143, -53, 32, 144, -48, 32, + 145, -44, 31, 145, -40, 30, 146, -36, 30, 147, -32, 29, + 148, -28, 28, 149, -24, 28, 150, -20, 27, 151, -16, 26, + 152, -11, 25, 153, -7, 25, 154, -3, 24, 155, 0, 24, + 156, 4, 23, 157, 8, 22, 158, 12, 22, 159, 16, 21, + 159, 20, 20, 161, 25, 19, 161, 29, 19, 162, 33, 18, + 163, 37, 18, 164, 41, 17, 165, 45, 16, 166, 49, 15, + 139, -78, 35, 140, -74, 34, 141, -70, 33, 142, -66, 33, + 143, -62, 32, 144, -58, 31, 145, -54, 31, 146, -49, 30, + 147, -45, 29, 148, -41, 29, 149, -37, 28, 150, -33, 27, + 151, -29, 26, 152, -25, 26, 152, -21, 25, 153, -17, 25, + 154, -12, 24, 155, -8, 23, 156, -4, 23, 157, 0, 22, + 158, 3, 21, 159, 7, 20, 160, 11, 20, 161, 15, 19, + 162, 19, 19, 163, 24, 18, 164, 28, 17, 165, 32, 16, + 166, 36, 16, 167, 40, 15, 168, 44, 14, 168, 48, 14, + 142, -80, 33, 143, -76, 32, 144, -72, 32, 145, -67, 31, + 145, -63, 30, 146, -59, 30, 147, -55, 29, 148, -51, 28, + 149, -47, 28, 150, -43, 27, 151, -39, 26, 152, -35, 26, + 153, -30, 25, 154, -26, 24, 155, -22, 24, 156, -18, 23, + 157, -14, 22, 158, -10, 21, 159, -6, 21, 159, -2, 20, + 161, 2, 19, 161, 6, 19, 162, 10, 18, 163, 14, 18, + 164, 18, 17, 165, 22, 16, 166, 26, 15, 167, 30, 15, + 168, 34, 14, 169, 39, 13, 170, 43, 13, 171, 47, 12, + 144, -81, 31, 145, -77, 31, 146, -73, 30, 147, -69, 29, + 148, -65, 29, 149, -61, 28, 150, -57, 27, 151, -52, 26, + 152, -48, 26, 152, -44, 25, 153, -40, 25, 154, -36, 24, + 155, -32, 23, 156, -28, 22, 157, -24, 22, 158, -20, 21, + 159, -15, 20, 160, -11, 20, 161, -7, 19, 162, -3, 19, + 163, 0, 18, 164, 4, 17, 165, 8, 16, 166, 12, 16, + 167, 16, 15, 168, 21, 14, 168, 25, 14, 169, 29, 13, + 170, 33, 12, 171, 37, 12, 172, 41, 11, 173, 45, 10, + 146, -82, 30, 147, -78, 29, 148, -74, 28, 149, -70, 27, + 150, -66, 27, 151, -62, 26, 152, -58, 26, 153, -53, 25, + 154, -49, 24, 155, -45, 24, 156, -41, 23, 157, -37, 22, + 158, -33, 21, 159, -29, 21, 160, -25, 20, 160, -21, 20, + 161, -16, 19, 162, -12, 18, 163, -8, 17, 164, -4, 17, + 165, 0, 16, 166, 3, 15, 167, 7, 15, 168, 11, 14, + 169, 15, 14, 170, 20, 13, 171, 24, 12, 172, 28, 11, + 173, 32, 11, 174, 36, 10, 175, 40, 9, 175, 44, 9, + 149, -84, 28, 150, -80, 27, 151, -76, 27, 152, -71, 26, + 153, -67, 25, 153, -63, 25, 154, -59, 24, 155, -55, 23, + 156, -51, 22, 157, -47, 22, 158, -43, 21, 159, -39, 21, + 160, -34, 20, 161, -30, 19, 162, -26, 18, 163, -22, 18, + 164, -18, 17, 165, -14, 16, 166, -10, 16, 167, -6, 15, + 168, -1, 14, 168, 2, 14, 169, 6, 13, 170, 10, 12, + 171, 14, 12, 172, 18, 11, 173, 22, 10, 174, 26, 10, + 175, 30, 9, 176, 35, 8, 177, 39, 8, 178, 43, 7, + 151, -85, 26, 152, -81, 26, 153, -77, 25, 154, -73, 24, + 155, -69, 23, 156, -65, 23, 157, -61, 22, 158, -56, 21, + 159, -52, 21, 160, -48, 20, 160, -44, 20, 161, -40, 19, + 162, -36, 18, 163, -32, 17, 164, -28, 17, 165, -24, 16, + 166, -19, 15, 167, -15, 15, 168, -11, 14, 169, -7, 13, + 170, -3, 13, 171, 0, 12, 172, 4, 11, 173, 8, 11, + 174, 12, 10, 175, 17, 9, 176, 21, 9, 176, 25, 8, + 177, 29, 7, 178, 33, 7, 179, 37, 6, 180, 41, 5, + 153, -86, 24, 154, -82, 24, 155, -78, 23, 156, -74, 22, + 157, -70, 22, 158, -66, 21, 159, -62, 21, 160, -57, 20, + 161, -53, 19, 162, -49, 18, 163, -45, 18, 164, -41, 17, + 165, -37, 16, 166, -33, 16, 167, -29, 15, 167, -25, 14, + 169, -20, 14, 169, -16, 13, 170, -12, 12, 171, -8, 12, + 172, -4, 11, 173, 0, 10, 174, 3, 10, 175, 7, 9, + 176, 11, 8, 177, 16, 8, 178, 20, 7, 179, 24, 6, + 180, 28, 6, 181, 32, 5, 182, 36, 4, 183, 40, 4, + 156, -88, 23, 157, -84, 22, 158, -80, 22, 159, -75, 21, + 160, -71, 20, 160, -67, 19, 161, -63, 19, 162, -59, 18, + 163, -55, 17, 164, -51, 17, 165, -47, 16, 166, -43, 16, + 167, -38, 15, 168, -34, 14, 169, -30, 13, 170, -26, 13, + 171, -22, 12, 172, -18, 11, 173, -14, 11, 174, -10, 10, + 175, -5, 9, 176, -1, 9, 176, 2, 8, 177, 6, 7, + 178, 10, 7, 179, 14, 6, 180, 18, 5, 181, 22, 5, + 182, 26, 4, 183, 31, 3, 184, 35, 3, 185, 39, 2, + 158, -89, 21, 159, -85, 20, 160, -81, 20, 161, -77, 19, + 162, -73, 18, 163, -69, 18, 164, -65, 17, 165, -60, 16, + 166, -56, 16, 167, -52, 15, 167, -48, 14, 168, -44, 14, + 169, -40, 13, 170, -36, 12, 171, -32, 12, 172, -28, 11, + 173, -23, 10, 174, -19, 10, 175, -15, 9, 176, -11, 8, + 177, -7, 8, 178, -3, 7, 179, 0, 6, 180, 4, 6, + 181, 8, 5, 182, 13, 4, 183, 17, 4, 183, 21, 3, + 184, 25, 2, 185, 29, 2, 186, 33, 1, 187, 37, 0, + 160, -90, 19, 161, -86, 19, 162, -82, 18, 163, -78, 17, + 164, -74, 17, 165, -70, 16, 166, -66, 15, 167, -61, 15, + 168, -57, 14, 169, -53, 13, 170, -49, 13, 171, -45, 12, + 172, -41, 11, 173, -37, 11, 174, -33, 10, 175, -29, 9, + 176, -24, 9, 176, -20, 8, 177, -16, 7, 178, -12, 7, + 179, -8, 6, 180, -4, 5, 181, 0, 5, 182, 3, 4, + 183, 7, 3, 184, 12, 3, 185, 16, 2, 186, 20, 1, + 187, 24, 1, 188, 28, 0, 189, 32, 0, 190, 36, 0, + 163, -92, 18, 164, -88, 17, 165, -84, 16, 166, -79, 16, + 167, -75, 15, 168, -71, 14, 168, -67, 14, 169, -63, 13, + 170, -59, 12, 171, -55, 12, 172, -51, 11, 173, -47, 10, + 174, -42, 10, 175, -38, 9, 176, -34, 8, 177, -30, 8, + 178, -26, 7, 179, -22, 6, 180, -18, 6, 181, -14, 5, + 182, -9, 4, 183, -5, 4, 183, -1, 3, 184, 2, 2, + 185, 6, 2, 186, 10, 1, 187, 14, 0, 188, 18, 0, + 189, 22, 0, 190, 27, -1, 191, 31, -1, 192, 35, -2, + 165, -93, 16, 166, -89, 15, 167, -85, 15, 168, -81, 14, + 169, -77, 13, 170, -73, 13, 171, -69, 12, 172, -64, 11, + 173, -60, 11, 174, -56, 10, 175, -52, 9, 175, -48, 9, + 176, -44, 8, 177, -40, 7, 178, -36, 7, 179, -32, 6, + 180, -27, 5, 181, -23, 5, 182, -19, 4, 183, -15, 3, + 184, -11, 3, 185, -7, 2, 186, -3, 1, 187, 0, 1, + 188, 4, 0, 189, 9, 0, 190, 13, 0, 191, 17, -1, + 191, 21, -2, 192, 25, -2, 193, 29, -3, 194, 33, -4, + 168, -94, 14, 168, -90, 14, 169, -86, 13, 170, -82, 12, + 171, -78, 12, 172, -74, 11, 173, -70, 10, 174, -65, 10, + 175, -61, 9, 176, -57, 8, 177, -53, 8, 178, -49, 7, + 179, -45, 6, 180, -41, 6, 181, -37, 5, 182, -33, 4, + 183, -28, 4, 184, -24, 3, 184, -20, 2, 185, -16, 2, + 186, -12, 1, 187, -8, 0, 188, -4, 0, 189, 0, 0, + 190, 3, -1, 191, 8, -1, 192, 12, -2, 193, 16, -3, + 194, 20, -3, 195, 24, -4, 196, 28, -5, 197, 32, -5, + 170, -96, 13, 171, -92, 12, 172, -88, 11, 173, -83, 11, + 174, -79, 10, 175, -75, 9, 175, -71, 9, 177, -67, 8, + 177, -63, 7, 178, -59, 7, 179, -55, 6, 180, -51, 5, + 181, -46, 5, 182, -42, 4, 183, -38, 3, 184, -34, 3, + 185, -30, 2, 186, -26, 1, 187, -22, 1, 188, -18, 0, + 189, -13, 0, 190, -9, 0, 191, -5, -1, 191, -1, -2, + 192, 2, -2, 193, 6, -3, 194, 10, -4, 195, 14, -4, + 196, 18, -5, 197, 23, -6, 198, 27, -6, 199, 31, -7, + 172, -97, 11, 173, -93, 10, 174, -89, 10, 175, -84, 9, + 176, -80, 8, 177, -76, 8, 178, -72, 7, 179, -68, 6, + 180, -64, 6, 181, -60, 5, 182, -56, 4, 182, -52, 4, + 184, -47, 3, 184, -43, 2, 185, -39, 2, 186, -35, 1, + 187, -31, 0, 188, -27, 0, 189, -23, 0, 190, -19, -1, + 191, -14, -1, 192, -10, -2, 193, -6, -3, 194, -2, -3, + 195, 1, -4, 196, 5, -5, 197, 9, -5, 198, 13, -6, + 198, 17, -7, 199, 22, -7, 200, 26, -8, 201, 30, -9, + 175, -98, 9, 175, -94, 9, 176, -90, 8, 177, -86, 7, + 178, -82, 7, 179, -78, 6, 180, -74, 5, 181, -69, 5, + 182, -65, 4, 183, -61, 3, 184, -57, 3, 185, -53, 2, + 186, -49, 1, 187, -45, 1, 188, -41, 0, 189, -37, 0, + 190, -32, 0, 191, -28, -1, 191, -24, -2, 192, -20, -2, + 193, -16, -3, 194, -12, -4, 195, -8, -4, 196, -4, -5, + 197, 0, -6, 198, 4, -6, 199, 8, -7, 200, 12, -8, + 201, 16, -8, 202, 20, -9, 203, 24, -10, 204, 28, -10, + 177, -100, 8, 178, -96, 7, 179, -92, 6, 180, -87, 6, + 181, -83, 5, 182, -79, 4, 183, -75, 4, 184, -71, 3, + 184, -67, 2, 185, -63, 2, 186, -59, 1, 187, -55, 0, + 188, -50, 0, 189, -46, 0, 190, -42, -1, 191, -38, -1, + 192, -34, -2, 193, -30, -3, 194, -26, -3, 195, -22, -4, + 196, -17, -5, 197, -13, -5, 198, -9, -6, 198, -5, -7, + 199, -1, -7, 200, 2, -8, 201, 6, -9, 202, 10, -9, + 203, 14, -10, 204, 19, -11, 205, 23, -11, 206, 27, -12, + 179, -101, 6, 180, -97, 5, 181, -93, 5, 182, -88, 4, + 183, -84, 3, 184, -80, 3, 185, -76, 2, 186, -72, 1, + 187, -68, 1, 188, -64, 0, 189, -60, 0, 190, -56, 0, + 191, -51, -1, 191, -47, -2, 192, -43, -2, 193, -39, -3, + 194, -35, -4, 195, -31, -4, 196, -27, -5, 197, -23, -6, + 198, -18, -6, 199, -14, -7, 200, -10, -8, 201, -6, -8, + 202, -2, -9, 203, 1, -10, 204, 5, -10, 205, 9, -11, + 206, 13, -12, 207, 18, -12, 207, 22, -13, 208, 26, -14, + 182, -103, 4, 183, -99, 3, 184, -95, 3, 185, -90, 2, + 186, -86, 1, 187, -82, 1, 188, -78, 0, 189, -74, 0, + 190, -70, 0, 191, -66, -1, 192, -62, -2, 192, -58, -2, + 194, -53, -3, 194, -49, -4, 195, -45, -4, 196, -41, -5, + 197, -37, -6, 198, -33, -6, 199, -29, -7, 200, -25, -8, + 201, -20, -8, 202, -16, -9, 203, -12, -10, 204, -8, -10, + 205, -4, -11, 206, 0, -12, 207, 3, -12, 208, 7, -13, + 208, 11, -14, 209, 16, -14, 210, 20, -15, 211, 24, -16, + 185, -104, 2, 185, -100, 2, 186, -96, 1, 187, -91, 0, + 188, -87, 0, 189, -83, 0, 190, -79, -1, 191, -75, -1, + 192, -71, -2, 193, -67, -3, 194, -63, -3, 195, -59, -4, + 196, -54, -5, 197, -50, -5, 198, -46, -6, 199, -42, -7, + 200, -38, -7, 201, -34, -8, 201, -30, -9, 202, -26, -9, + 203, -21, -10, 204, -17, -11, 205, -13, -11, 206, -9, -12, + 207, -5, -13, 208, -1, -13, 209, 2, -14, 210, 6, -15, + 211, 10, -15, 212, 15, -16, 213, 19, -17, 214, 23, -17, + 187, -105, 1, 188, -101, 0, 189, -97, 0, 190, -93, 0, + 191, -89, -1, 192, -85, -2, 192, -81, -2, 194, -76, -3, + 194, -72, -4, 195, -68, -4, 196, -64, -5, 197, -60, -6, + 198, -56, -6, 199, -52, -7, 200, -48, -8, 201, -44, -8, + 202, -39, -9, 203, -35, -10, 204, -31, -10, 205, -27, -11, + 206, -23, -12, 207, -19, -12, 208, -15, -13, 208, -11, -14, + 209, -7, -14, 210, -2, -15, 211, 1, -16, 212, 5, -16, + 213, 9, -17, 214, 13, -18, 215, 17, -18, 216, 21, -19, + 189, -107, 0, 190, -103, -1, 191, -99, -1, 192, -94, -2, + 193, -90, -3, 194, -86, -3, 195, -82, -4, 196, -78, -5, + 197, -74, -5, 198, -70, -6, 199, -66, -7, 200, -62, -7, + 201, -57, -8, 201, -53, -9, 202, -49, -9, 203, -45, -10, + 204, -41, -11, 205, -37, -11, 206, -33, -12, 207, -29, -13, + 208, -24, -13, 209, -20, -14, 210, -16, -15, 211, -12, -15, + 212, -8, -16, 213, -4, -17, 214, 0, -17, 215, 3, -18, + 215, 7, -19, 217, 12, -19, 217, 16, -20, 218, 20, -21, + 192, -108, -2, 193, -104, -2, 193, -100, -3, 194, -95, -4, + 195, -91, -4, 196, -87, -5, 197, -83, -6, 198, -79, -6, + 199, -75, -7, 200, -71, -8, 201, -67, -8, 202, -63, -9, + 203, -58, -10, 204, -54, -10, 205, -50, -11, 206, -46, -12, + 207, -42, -12, 208, -38, -13, 208, -34, -14, 209, -30, -14, + 210, -25, -15, 211, -21, -16, 212, -17, -16, 213, -13, -17, + 214, -9, -18, 215, -5, -18, 216, -1, -19, 217, 2, -20, + 218, 6, -20, 219, 11, -21, 220, 15, -22, 221, 19, -22, + 194, -109, -3, 195, -105, -4, 196, -101, -5, 197, -97, -5, + 198, -93, -6, 199, -89, -7, 200, -85, -7, 201, -80, -8, + 201, -76, -9, 202, -72, -9, 203, -68, -10, 204, -64, -11, + 205, -60, -11, 206, -56, -12, 207, -52, -13, 208, -48, -13, + 209, -43, -14, 210, -39, -15, 211, -35, -15, 212, -31, -16, + 213, -27, -17, 214, -23, -17, 215, -19, -18, 216, -15, -19, + 216, -11, -19, 217, -6, -20, 218, -2, -21, 219, 1, -21, + 220, 5, -22, 221, 9, -23, 222, 13, -23, 223, 17, -24, + 196, -111, -5, 197, -107, -6, 198, -103, -6, 199, -98, -7, + 200, -94, -8, 201, -90, -8, 202, -86, -9, 203, -82, -10, + 204, -78, -10, 205, -74, -11, 206, -70, -12, 207, -66, -12, + 208, -61, -13, 209, -57, -14, 209, -53, -14, 210, -49, -15, + 211, -45, -16, 212, -41, -16, 213, -37, -17, 214, -33, -18, + 215, -28, -18, 216, -24, -19, 217, -20, -20, 218, -16, -20, + 219, -12, -21, 220, -8, -22, 221, -4, -22, 222, 0, -23, + 223, 3, -24, 224, 8, -25, 224, 12, -25, 225, 16, -26, + 199, -112, -7, 200, -108, -7, 200, -104, -8, 202, -99, -9, + 202, -95, -9, 203, -91, -10, 204, -87, -11, 205, -83, -11, + 206, -79, -12, 207, -75, -13, 208, -71, -13, 209, -67, -14, + 210, -62, -15, 211, -58, -15, 212, -54, -16, 213, -50, -17, + 214, -46, -17, 215, -42, -18, 216, -38, -19, 216, -34, -19, + 217, -29, -20, 218, -25, -21, 219, -21, -21, 220, -17, -22, + 221, -13, -23, 222, -9, -23, 223, -5, -24, 224, -1, -25, + 225, 2, -25, 226, 7, -26, 227, 11, -27, 228, 15, -27, + 201, -113, -8, 202, -109, -9, 203, -105, -10, 204, -101, -10, + 205, -97, -11, 206, -93, -12, 207, -89, -12, 208, -84, -13, + 209, -80, -14, 209, -76, -14, 210, -72, -15, 211, -68, -16, + 212, -64, -16, 213, -60, -17, 214, -56, -18, 215, -52, -18, + 216, -47, -19, 217, -43, -20, 218, -39, -20, 219, -35, -21, + 220, -31, -22, 221, -27, -22, 222, -23, -23, 223, -19, -24, + 223, -15, -24, 224, -10, -25, 225, -6, -26, 226, -2, -26, + 227, 1, -27, 228, 5, -28, 229, 9, -29, 230, 13, -29, + 203, -115, -10, 204, -111, -11, 205, -107, -11, 206, -102, -12, + 207, -98, -13, 208, -94, -13, 209, -90, -14, 210, -86, -15, + 211, -82, -15, 212, -78, -16, 213, -74, -17, 214, -70, -17, + 215, -65, -18, 216, -61, -19, 216, -57, -19, 217, -53, -20, + 218, -49, -21, 219, -45, -21, 220, -41, -22, 221, -37, -23, + 222, -32, -24, 223, -28, -24, 224, -24, -25, 225, -20, -25, + 226, -16, -26, 227, -12, -27, 228, -8, -27, 229, -4, -28, + 230, 0, -29, 231, 4, -30, 232, 8, -30, 232, 12, -31, + 206, -116, -12, 207, -112, -12, 208, -108, -13, 209, -103, -14, + 209, -99, -14, 210, -95, -15, 211, -91, -16, 212, -87, -16, + 213, -83, -17, 214, -79, -18, 215, -75, -18, 216, -71, -19, + 217, -66, -20, 218, -62, -20, 219, -58, -21, 220, -54, -22, + 221, -50, -22, 222, -46, -23, 223, -42, -24, 223, -38, -24, + 225, -33, -25, 225, -29, -26, 226, -25, -26, 227, -21, -27, + 228, -17, -28, 229, -13, -29, 230, -9, -29, 231, -5, -30, + 232, -1, -30, 233, 3, -31, 234, 7, -32, 235, 11, -33, + 58, -33, 98, 59, -29, 97, 60, -25, 97, 61, -20, 96, + 62, -16, 95, 63, -12, 95, 64, -8, 94, 65, -4, 93, + 66, 0, 93, 67, 3, 92, 68, 7, 91, 69, 11, 91, + 70, 16, 90, 71, 20, 89, 72, 24, 89, 72, 28, 88, + 73, 32, 87, 74, 36, 87, 75, 40, 86, 76, 44, 85, + 77, 49, 85, 78, 53, 84, 79, 57, 83, 80, 61, 83, + 81, 65, 82, 82, 69, 81, 83, 73, 81, 84, 77, 80, + 85, 81, 79, 86, 86, 79, 87, 90, 78, 87, 94, 77, + 61, -34, 96, 62, -30, 96, 63, -26, 95, 64, -22, 94, + 65, -18, 94, 65, -14, 93, 66, -10, 92, 67, -5, 92, + 68, -1, 91, 69, 2, 90, 70, 6, 90, 71, 10, 89, + 72, 14, 88, 73, 18, 88, 74, 22, 87, 75, 26, 86, + 76, 31, 86, 77, 35, 85, 78, 39, 84, 79, 43, 84, + 80, 47, 83, 80, 51, 82, 81, 55, 82, 82, 59, 81, + 83, 63, 80, 84, 68, 80, 85, 72, 79, 86, 76, 78, + 87, 80, 78, 88, 84, 77, 89, 88, 76, 90, 92, 76, + 63, -35, 95, 64, -31, 94, 65, -27, 93, 66, -23, 93, + 67, -19, 92, 68, -15, 91, 69, -11, 91, 70, -6, 90, + 71, -2, 89, 72, 1, 89, 72, 5, 88, 73, 9, 87, + 74, 13, 87, 75, 17, 86, 76, 21, 85, 77, 25, 85, + 78, 30, 84, 79, 34, 83, 80, 38, 83, 81, 42, 82, + 82, 46, 81, 83, 50, 81, 84, 54, 80, 85, 58, 79, + 86, 62, 79, 87, 67, 78, 87, 71, 77, 88, 75, 77, + 89, 79, 76, 90, 83, 75, 91, 87, 75, 92, 91, 74, + 65, -37, 93, 66, -33, 92, 67, -29, 92, 68, -24, 91, + 69, -20, 90, 70, -16, 90, 71, -12, 89, 72, -8, 88, + 73, -4, 88, 74, 0, 87, 75, 3, 86, 76, 7, 86, + 77, 12, 85, 78, 16, 84, 79, 20, 84, 79, 24, 83, + 80, 28, 82, 81, 32, 82, 82, 36, 81, 83, 40, 80, + 84, 45, 80, 85, 49, 79, 86, 53, 78, 87, 57, 78, + 88, 61, 77, 89, 65, 76, 90, 69, 76, 91, 73, 75, + 92, 77, 74, 93, 82, 74, 94, 86, 73, 95, 90, 72, + 68, -38, 91, 69, -34, 91, 70, -30, 90, 71, -26, 89, + 72, -22, 89, 72, -18, 88, 73, -14, 87, 74, -9, 87, + 75, -5, 86, 76, -1, 85, 77, 2, 85, 78, 6, 84, + 79, 10, 83, 80, 14, 83, 81, 18, 82, 82, 22, 81, + 83, 27, 81, 84, 31, 80, 85, 35, 79, 86, 39, 79, + 87, 43, 78, 88, 47, 77, 88, 51, 77, 89, 55, 76, + 90, 59, 75, 91, 64, 75, 92, 68, 74, 93, 72, 73, + 94, 76, 73, 95, 80, 72, 96, 84, 71, 97, 88, 71, + 70, -39, 90, 71, -35, 89, 72, -31, 88, 73, -27, 88, + 74, -23, 87, 75, -19, 86, 76, -15, 86, 77, -10, 85, + 78, -6, 84, 79, -2, 84, 79, 1, 83, 80, 5, 82, + 81, 9, 82, 82, 13, 81, 83, 17, 80, 84, 21, 80, + 85, 26, 79, 86, 30, 78, 87, 34, 78, 88, 38, 77, + 89, 42, 76, 90, 46, 76, 91, 50, 75, 92, 54, 74, + 93, 58, 74, 94, 63, 73, 95, 67, 72, 95, 71, 72, + 96, 75, 71, 97, 79, 70, 98, 83, 70, 99, 87, 69, + 72, -41, 88, 73, -37, 87, 74, -33, 87, 75, -28, 86, + 76, -24, 85, 77, -20, 85, 78, -16, 84, 79, -12, 83, + 80, -8, 83, 81, -4, 82, 82, 0, 81, 83, 3, 81, + 84, 8, 80, 85, 12, 79, 86, 16, 79, 87, 20, 78, + 88, 24, 77, 88, 28, 77, 89, 32, 76, 90, 36, 75, + 91, 41, 75, 92, 45, 74, 93, 49, 73, 94, 53, 73, + 95, 57, 72, 96, 61, 71, 97, 65, 71, 98, 69, 70, + 99, 73, 69, 100, 78, 69, 101, 82, 68, 102, 86, 67, + 75, -42, 86, 76, -38, 86, 77, -34, 85, 78, -30, 84, + 79, -26, 84, 80, -22, 83, 80, -18, 82, 81, -13, 82, + 82, -9, 81, 83, -5, 80, 84, -1, 80, 85, 2, 79, + 86, 6, 78, 87, 10, 78, 88, 14, 77, 89, 18, 76, + 90, 23, 76, 91, 27, 75, 92, 31, 74, 93, 35, 74, + 94, 39, 73, 95, 43, 72, 95, 47, 72, 96, 51, 71, + 97, 55, 70, 98, 60, 70, 99, 64, 69, 100, 68, 68, + 101, 72, 68, 102, 76, 67, 103, 80, 66, 104, 84, 66, + 77, -43, 85, 78, -39, 84, 79, -35, 83, 80, -31, 83, + 81, -27, 82, 82, -23, 81, 83, -19, 81, 84, -14, 80, + 85, -10, 79, 86, -6, 79, 87, -2, 78, 87, 1, 77, + 88, 5, 77, 89, 9, 76, 90, 13, 75, 91, 17, 75, + 92, 22, 74, 93, 26, 73, 94, 30, 73, 95, 34, 72, + 96, 38, 71, 97, 42, 71, 98, 46, 70, 99, 50, 69, + 100, 54, 69, 101, 59, 68, 102, 63, 67, 102, 67, 67, + 103, 71, 66, 104, 75, 65, 105, 79, 65, 106, 83, 64, + 80, -45, 83, 80, -41, 82, 81, -37, 82, 82, -32, 81, + 83, -28, 80, 84, -24, 80, 85, -20, 79, 86, -16, 78, + 87, -12, 78, 88, -8, 77, 89, -4, 76, 90, 0, 76, + 91, 4, 75, 92, 8, 74, 93, 12, 74, 94, 16, 73, + 95, 20, 72, 95, 24, 72, 96, 28, 71, 97, 32, 70, + 98, 37, 70, 99, 41, 69, 100, 45, 68, 101, 49, 68, + 102, 53, 67, 103, 57, 66, 104, 61, 66, 105, 65, 65, + 106, 69, 64, 107, 74, 63, 108, 78, 63, 109, 82, 62, + 82, -46, 81, 83, -42, 81, 84, -38, 80, 85, -33, 79, + 86, -29, 79, 87, -25, 78, 87, -21, 77, 88, -17, 77, + 89, -13, 76, 90, -9, 75, 91, -5, 75, 92, -1, 74, + 93, 3, 73, 94, 7, 73, 95, 11, 72, 96, 15, 71, + 97, 19, 71, 98, 23, 70, 99, 27, 69, 100, 31, 69, + 101, 36, 68, 102, 40, 67, 103, 44, 67, 103, 48, 66, + 104, 52, 65, 105, 56, 65, 106, 60, 64, 107, 64, 63, + 108, 68, 63, 109, 73, 62, 110, 77, 61, 111, 81, 61, + 85, -48, 79, 86, -44, 79, 87, -40, 78, 88, -35, 77, + 89, -31, 76, 89, -27, 76, 90, -23, 75, 91, -19, 74, + 92, -15, 74, 93, -11, 73, 94, -7, 72, 95, -3, 72, + 96, 1, 71, 97, 5, 70, 98, 9, 70, 99, 13, 69, + 100, 17, 68, 101, 21, 68, 102, 25, 67, 103, 29, 66, + 104, 34, 66, 105, 38, 65, 105, 42, 64, 106, 46, 64, + 107, 50, 63, 108, 54, 62, 109, 58, 62, 110, 62, 61, + 111, 66, 60, 112, 71, 60, 113, 75, 59, 114, 79, 58, + 87, -49, 77, 88, -45, 77, 89, -41, 76, 90, -36, 75, + 91, -32, 75, 92, -28, 74, 93, -24, 73, 94, -20, 73, + 95, -16, 72, 96, -12, 71, 97, -8, 71, 97, -4, 70, + 98, 0, 69, 99, 4, 69, 100, 8, 68, 101, 12, 67, + 102, 16, 67, 103, 20, 66, 104, 24, 65, 105, 28, 65, + 106, 33, 64, 107, 37, 63, 108, 41, 63, 109, 45, 62, + 110, 49, 61, 111, 53, 61, 112, 57, 60, 112, 61, 59, + 113, 65, 59, 114, 70, 58, 115, 74, 57, 116, 78, 57, + 90, -50, 76, 90, -46, 75, 91, -42, 75, 92, -38, 74, + 93, -34, 73, 94, -30, 72, 95, -26, 72, 96, -21, 71, + 97, -17, 70, 98, -13, 70, 99, -9, 69, 100, -5, 68, + 101, -1, 68, 102, 2, 67, 103, 6, 66, 104, 10, 66, + 105, 15, 65, 105, 19, 64, 106, 23, 64, 107, 27, 63, + 108, 31, 62, 109, 35, 62, 110, 39, 61, 111, 43, 60, + 112, 47, 60, 113, 52, 59, 114, 56, 58, 115, 60, 58, + 116, 64, 57, 117, 68, 56, 118, 72, 56, 119, 76, 55, + 92, -52, 74, 93, -48, 73, 94, -44, 73, 95, -39, 72, + 96, -35, 71, 97, -31, 71, 97, -27, 70, 98, -23, 69, + 99, -19, 69, 100, -15, 68, 101, -11, 67, 102, -7, 67, + 103, -2, 66, 104, 1, 65, 105, 5, 65, 106, 9, 64, + 107, 13, 63, 108, 17, 63, 109, 21, 62, 110, 25, 61, + 111, 30, 61, 112, 34, 60, 112, 38, 59, 113, 42, 59, + 114, 46, 58, 115, 50, 57, 116, 54, 57, 117, 58, 56, + 118, 62, 55, 119, 67, 55, 120, 71, 54, 121, 75, 53, + 94, -53, 72, 95, -49, 72, 96, -45, 71, 97, -40, 70, + 98, -36, 70, 99, -32, 69, 100, -28, 68, 101, -24, 68, + 102, -20, 67, 103, -16, 66, 104, -12, 66, 104, -8, 65, + 105, -3, 64, 106, 0, 64, 107, 4, 63, 108, 8, 62, + 109, 12, 62, 110, 16, 61, 111, 20, 60, 112, 24, 60, + 113, 29, 59, 114, 33, 58, 115, 37, 58, 116, 41, 57, + 117, 45, 56, 118, 49, 56, 119, 53, 55, 120, 57, 54, + 120, 61, 54, 121, 66, 53, 122, 70, 52, 123, 74, 52, + 97, -54, 71, 97, -50, 70, 98, -46, 69, 99, -42, 69, + 100, -38, 68, 101, -34, 67, 102, -30, 67, 103, -25, 66, + 104, -21, 65, 105, -17, 65, 106, -13, 64, 107, -9, 63, + 108, -5, 63, 109, -1, 62, 110, 2, 61, 111, 6, 61, + 112, 11, 60, 113, 15, 59, 113, 19, 59, 114, 23, 58, + 115, 27, 57, 116, 31, 57, 117, 35, 56, 118, 39, 55, + 119, 43, 55, 120, 48, 54, 121, 52, 53, 122, 56, 53, + 123, 60, 52, 124, 64, 51, 125, 68, 51, 126, 72, 50, + 99, -56, 69, 100, -52, 68, 101, -48, 68, 102, -43, 67, + 103, -39, 66, 104, -35, 66, 104, -31, 65, 106, -27, 64, + 106, -23, 64, 107, -19, 63, 108, -15, 62, 109, -11, 62, + 110, -6, 61, 111, -2, 60, 112, 1, 60, 113, 5, 59, + 114, 9, 58, 115, 13, 58, 116, 17, 57, 117, 21, 56, + 118, 26, 56, 119, 30, 55, 120, 34, 54, 120, 38, 54, + 121, 42, 53, 122, 46, 52, 123, 50, 52, 124, 54, 51, + 125, 58, 50, 126, 63, 50, 127, 67, 49, 128, 71, 48, + 101, -57, 67, 102, -53, 67, 103, -49, 66, 104, -44, 65, + 105, -40, 65, 106, -36, 64, 107, -32, 63, 108, -28, 63, + 109, -24, 62, 110, -20, 61, 111, -16, 61, 112, -12, 60, + 113, -7, 59, 113, -3, 59, 114, 0, 58, 115, 4, 57, + 116, 8, 57, 117, 12, 56, 118, 16, 55, 119, 20, 55, + 120, 25, 54, 121, 29, 53, 122, 33, 53, 123, 37, 52, + 124, 41, 51, 125, 45, 51, 126, 49, 50, 127, 53, 49, + 127, 57, 49, 129, 62, 48, 129, 66, 47, 130, 70, 47, + 104, -58, 66, 105, -54, 65, 105, -50, 64, 106, -46, 64, + 107, -42, 63, 108, -38, 62, 109, -34, 62, 110, -29, 61, + 111, -25, 60, 112, -21, 60, 113, -17, 59, 114, -13, 58, + 115, -9, 58, 116, -5, 57, 117, -1, 56, 118, 2, 56, + 119, 7, 55, 120, 11, 54, 120, 15, 54, 121, 19, 53, + 122, 23, 52, 123, 27, 52, 124, 31, 51, 125, 35, 50, + 126, 39, 50, 127, 44, 49, 128, 48, 48, 129, 52, 48, + 130, 56, 47, 131, 60, 46, 132, 64, 46, 133, 68, 45, + 106, -60, 64, 107, -56, 63, 108, -52, 63, 109, -47, 62, + 110, -43, 61, 111, -39, 61, 112, -35, 60, 113, -31, 59, + 113, -27, 59, 114, -23, 58, 115, -19, 57, 116, -15, 57, + 117, -10, 56, 118, -6, 55, 119, -2, 55, 120, 1, 54, + 121, 5, 53, 122, 9, 53, 123, 13, 52, 124, 17, 51, + 125, 22, 51, 126, 26, 50, 127, 30, 49, 127, 34, 49, + 128, 38, 48, 129, 42, 47, 130, 46, 47, 131, 50, 46, + 132, 54, 45, 133, 59, 45, 134, 63, 44, 135, 67, 43, + 108, -61, 62, 109, -57, 62, 110, -53, 61, 111, -48, 60, + 112, -44, 60, 113, -40, 59, 114, -36, 58, 115, -32, 58, + 116, -28, 57, 117, -24, 56, 118, -20, 56, 119, -16, 55, + 120, -11, 54, 120, -7, 54, 121, -3, 53, 122, 0, 52, + 123, 4, 52, 124, 8, 51, 125, 12, 50, 126, 16, 50, + 127, 21, 49, 128, 25, 48, 129, 29, 48, 130, 33, 47, + 131, 37, 46, 132, 41, 46, 133, 45, 45, 134, 49, 44, + 135, 53, 44, 136, 58, 43, 136, 62, 42, 137, 66, 42, + 111, -62, 61, 112, -58, 60, 112, -54, 59, 113, -50, 59, + 114, -46, 58, 115, -42, 57, 116, -38, 57, 117, -33, 56, + 118, -29, 55, 119, -25, 55, 120, -21, 54, 121, -17, 53, + 122, -13, 53, 123, -9, 52, 124, -5, 51, 125, -1, 51, + 126, 3, 50, 127, 7, 49, 128, 11, 49, 128, 15, 48, + 129, 19, 47, 130, 23, 47, 131, 27, 46, 132, 31, 45, + 133, 35, 45, 134, 40, 44, 135, 44, 43, 136, 48, 43, + 137, 52, 42, 138, 56, 41, 139, 60, 41, 140, 64, 40, + 113, -64, 59, 114, -60, 58, 115, -56, 58, 116, -51, 57, + 117, -47, 56, 118, -43, 56, 119, -39, 55, 120, -35, 54, + 121, -31, 54, 121, -27, 53, 122, -23, 52, 123, -19, 52, + 124, -14, 51, 125, -10, 50, 126, -6, 50, 127, -2, 49, + 128, 1, 48, 129, 5, 48, 130, 9, 47, 131, 13, 46, + 132, 18, 46, 133, 22, 45, 134, 26, 44, 135, 30, 44, + 135, 34, 43, 136, 38, 42, 137, 42, 42, 138, 46, 41, + 139, 50, 40, 140, 55, 40, 141, 59, 39, 142, 63, 38, + 115, -65, 57, 116, -61, 57, 117, -57, 56, 118, -52, 55, + 119, -48, 55, 120, -44, 54, 121, -40, 53, 122, -36, 53, + 123, -32, 52, 124, -28, 51, 125, -24, 51, 126, -20, 50, + 127, -15, 49, 128, -11, 49, 128, -7, 48, 129, -3, 47, + 130, 0, 47, 131, 4, 46, 132, 8, 45, 133, 12, 45, + 134, 17, 44, 135, 21, 43, 136, 25, 43, 137, 29, 42, + 138, 33, 41, 139, 37, 41, 140, 41, 40, 141, 45, 39, + 142, 49, 39, 143, 54, 38, 144, 58, 37, 144, 62, 37, + 118, -66, 56, 119, -62, 55, 120, -58, 54, 121, -54, 54, + 121, -50, 53, 122, -46, 52, 123, -42, 52, 124, -37, 51, + 125, -33, 50, 126, -29, 50, 127, -25, 49, 128, -21, 48, + 129, -17, 48, 130, -13, 47, 131, -9, 46, 132, -5, 46, + 133, 0, 45, 134, 3, 44, 135, 7, 44, 135, 11, 43, + 137, 15, 42, 137, 19, 42, 138, 23, 41, 139, 27, 40, + 140, 31, 40, 141, 36, 39, 142, 40, 38, 143, 44, 38, + 144, 48, 37, 145, 52, 36, 146, 56, 36, 147, 60, 35, + 120, -68, 54, 121, -64, 53, 122, -60, 53, 123, -55, 52, + 124, -51, 51, 125, -47, 51, 126, -43, 50, 127, -39, 49, + 128, -35, 49, 128, -31, 48, 129, -27, 47, 130, -23, 47, + 131, -18, 46, 132, -14, 45, 133, -10, 45, 134, -6, 44, + 135, -2, 43, 136, 1, 43, 137, 5, 42, 138, 9, 41, + 139, 14, 41, 140, 18, 40, 141, 22, 39, 142, 26, 39, + 142, 30, 38, 144, 34, 37, 144, 38, 37, 145, 42, 36, + 146, 46, 35, 147, 51, 35, 148, 55, 34, 149, 59, 33, + 122, -69, 52, 123, -65, 52, 124, -61, 51, 125, -56, 50, + 126, -52, 50, 127, -48, 49, 128, -44, 48, 129, -40, 48, + 130, -36, 47, 131, -32, 46, 132, -28, 46, 133, -24, 45, + 134, -19, 44, 135, -15, 44, 135, -11, 43, 136, -7, 42, + 137, -3, 42, 138, 0, 41, 139, 4, 40, 140, 8, 40, + 141, 13, 39, 142, 17, 38, 143, 21, 38, 144, 25, 37, + 145, 29, 36, 146, 33, 36, 147, 37, 35, 148, 41, 34, + 149, 45, 34, 150, 50, 33, 151, 54, 32, 151, 58, 32, + 125, -70, 51, 126, -66, 50, 127, -62, 49, 128, -58, 49, + 128, -54, 48, 129, -50, 47, 130, -46, 47, 131, -41, 46, + 132, -37, 45, 133, -33, 45, 134, -29, 44, 135, -25, 43, + 136, -21, 43, 137, -17, 42, 138, -13, 41, 139, -9, 41, + 140, -4, 40, 141, 0, 39, 142, 3, 39, 143, 7, 38, + 144, 11, 37, 144, 15, 37, 145, 19, 36, 146, 23, 35, + 147, 27, 35, 148, 32, 34, 149, 36, 33, 150, 40, 33, + 151, 44, 32, 152, 48, 31, 153, 52, 31, 154, 56, 30, + 127, -71, 49, 128, -67, 48, 129, -63, 48, 130, -59, 47, + 131, -55, 46, 132, -51, 46, 133, -47, 45, 134, -42, 44, + 135, -38, 44, 136, -34, 43, 136, -30, 42, 137, -26, 42, + 138, -22, 41, 139, -18, 40, 140, -14, 40, 141, -10, 39, + 142, -5, 38, 143, -1, 38, 144, 2, 37, 145, 6, 36, + 146, 10, 36, 147, 14, 35, 148, 18, 34, 149, 22, 34, + 150, 26, 33, 151, 31, 32, 151, 35, 32, 152, 39, 31, + 153, 43, 30, 154, 47, 30, 155, 51, 29, 156, 55, 28, + 129, -73, 47, 130, -69, 47, 131, -65, 46, 132, -60, 45, + 133, -56, 45, 134, -52, 44, 135, -48, 43, 136, -44, 43, + 137, -40, 42, 138, -36, 41, 139, -32, 41, 140, -28, 40, + 141, -23, 39, 142, -19, 39, 143, -15, 38, 143, -11, 37, + 144, -7, 37, 145, -3, 36, 146, 0, 35, 147, 4, 35, + 148, 9, 34, 149, 13, 33, 150, 17, 33, 151, 21, 32, + 152, 25, 31, 153, 29, 31, 154, 33, 30, 155, 37, 29, + 156, 41, 29, 157, 46, 28, 158, 50, 27, 159, 54, 27, + 132, -74, 46, 133, -70, 45, 134, -66, 44, 135, -62, 44, + 136, -58, 43, 136, -54, 42, 137, -50, 42, 138, -45, 41, + 139, -41, 40, 140, -37, 40, 141, -33, 39, 142, -29, 38, + 143, -25, 38, 144, -21, 37, 145, -17, 36, 146, -13, 36, + 147, -8, 35, 148, -4, 34, 149, 0, 34, 150, 3, 33, + 151, 7, 32, 152, 11, 32, 152, 15, 31, 153, 19, 30, + 154, 23, 30, 155, 28, 29, 156, 32, 28, 157, 36, 28, + 158, 40, 27, 159, 44, 26, 160, 48, 26, 161, 52, 25, + 135, -76, 44, 136, -72, 43, 137, -68, 42, 138, -63, 42, + 138, -59, 41, 139, -55, 40, 140, -51, 40, 141, -47, 39, + 142, -43, 38, 143, -39, 38, 144, -35, 37, 145, -31, 36, + 146, -26, 36, 147, -22, 35, 148, -18, 34, 149, -14, 34, + 150, -10, 33, 151, -6, 32, 152, -2, 32, 152, 1, 31, + 154, 6, 30, 154, 10, 30, 155, 14, 29, 156, 18, 28, + 157, 22, 28, 158, 26, 27, 159, 30, 26, 160, 34, 26, + 161, 38, 25, 162, 43, 24, 163, 47, 23, 164, 51, 23, + 137, -77, 42, 138, -73, 41, 139, -69, 41, 140, -65, 40, + 141, -61, 39, 142, -57, 39, 143, -53, 38, 144, -48, 37, + 145, -44, 37, 145, -40, 36, 146, -36, 35, 147, -32, 35, + 148, -28, 34, 149, -24, 33, 150, -20, 33, 151, -16, 32, + 152, -11, 31, 153, -7, 31, 154, -3, 30, 155, 0, 29, + 156, 4, 28, 157, 8, 28, 158, 12, 27, 159, 16, 27, + 160, 20, 26, 161, 25, 25, 161, 29, 24, 162, 33, 24, + 163, 37, 23, 164, 41, 22, 165, 45, 22, 166, 49, 21, + 139, -78, 40, 140, -74, 40, 141, -70, 39, 142, -66, 38, + 143, -62, 38, 144, -58, 37, 145, -54, 36, 146, -49, 36, + 147, -45, 35, 148, -41, 34, 149, -37, 34, 150, -33, 33, + 151, -29, 32, 152, -25, 32, 153, -21, 31, 153, -17, 30, + 154, -12, 29, 155, -8, 29, 156, -4, 28, 157, 0, 28, + 158, 3, 27, 159, 7, 26, 160, 11, 26, 161, 15, 25, + 162, 19, 24, 163, 24, 23, 164, 28, 23, 165, 32, 22, + 166, 36, 22, 167, 40, 21, 168, 44, 20, 168, 48, 19, + 142, -80, 39, 143, -76, 38, 144, -72, 37, 145, -67, 37, + 146, -63, 36, 146, -59, 35, 147, -55, 35, 148, -51, 34, + 149, -47, 33, 150, -43, 33, 151, -39, 32, 152, -35, 31, + 153, -30, 30, 154, -26, 30, 155, -22, 29, 156, -18, 29, + 157, -14, 28, 158, -10, 27, 159, -6, 27, 160, -2, 26, + 161, 2, 25, 161, 6, 24, 162, 10, 24, 163, 14, 23, + 164, 18, 23, 165, 22, 22, 166, 26, 21, 167, 30, 20, + 168, 34, 20, 169, 39, 19, 170, 43, 18, 171, 47, 18, + 144, -81, 37, 145, -77, 36, 146, -73, 36, 147, -69, 35, + 148, -65, 34, 149, -61, 34, 150, -57, 33, 151, -52, 32, + 152, -48, 32, 153, -44, 31, 153, -40, 30, 154, -36, 30, + 155, -32, 29, 156, -28, 28, 157, -24, 28, 158, -20, 27, + 159, -15, 26, 160, -11, 25, 161, -7, 25, 162, -3, 24, + 163, 0, 23, 164, 4, 23, 165, 8, 22, 166, 12, 22, + 167, 16, 21, 168, 21, 20, 169, 25, 19, 169, 29, 19, + 170, 33, 18, 171, 37, 17, 172, 41, 17, 173, 45, 16, + 146, -82, 35, 147, -78, 35, 148, -74, 34, 149, -70, 33, + 150, -66, 33, 151, -62, 32, 152, -58, 31, 153, -53, 30, + 154, -49, 30, 155, -45, 29, 156, -41, 29, 157, -37, 28, + 158, -33, 27, 159, -29, 26, 160, -25, 26, 160, -21, 25, + 162, -16, 24, 162, -12, 24, 163, -8, 23, 164, -4, 23, + 165, 0, 22, 166, 3, 21, 167, 7, 20, 168, 11, 20, + 169, 15, 19, 170, 20, 18, 171, 24, 18, 172, 28, 17, + 173, 32, 16, 174, 36, 16, 175, 40, 15, 176, 44, 14, + 149, -84, 34, 150, -80, 33, 151, -76, 32, 152, -71, 31, + 153, -67, 31, 153, -63, 30, 154, -59, 30, 155, -55, 29, + 156, -51, 28, 157, -47, 28, 158, -43, 27, 159, -39, 26, + 160, -34, 25, 161, -30, 25, 162, -26, 24, 163, -22, 24, + 164, -18, 23, 165, -14, 22, 166, -10, 21, 167, -6, 21, + 168, -1, 20, 169, 2, 19, 169, 6, 19, 170, 10, 18, + 171, 14, 18, 172, 18, 17, 173, 22, 16, 174, 26, 15, + 175, 30, 15, 176, 35, 14, 177, 39, 13, 178, 43, 13, + 151, -85, 32, 152, -81, 31, 153, -77, 31, 154, -73, 30, + 155, -69, 29, 156, -65, 29, 157, -61, 28, 158, -56, 27, + 159, -52, 26, 160, -48, 26, 160, -44, 25, 161, -40, 25, + 162, -36, 24, 163, -32, 23, 164, -28, 22, 165, -24, 22, + 166, -19, 21, 167, -15, 20, 168, -11, 20, 169, -7, 19, + 170, -3, 18, 171, 0, 18, 172, 4, 17, 173, 8, 16, + 174, 12, 16, 175, 17, 15, 176, 21, 14, 176, 25, 14, + 177, 29, 13, 178, 33, 12, 179, 37, 12, 180, 41, 11, + 153, -86, 30, 154, -82, 30, 155, -78, 29, 156, -74, 28, + 157, -70, 27, 158, -66, 27, 159, -62, 26, 160, -57, 25, + 161, -53, 25, 162, -49, 24, 163, -45, 24, 164, -41, 23, + 165, -37, 22, 166, -33, 21, 167, -29, 21, 168, -25, 20, + 169, -20, 19, 169, -16, 19, 170, -12, 18, 171, -8, 17, + 172, -4, 17, 173, 0, 16, 174, 3, 15, 175, 7, 15, + 176, 11, 14, 177, 16, 13, 178, 20, 13, 179, 24, 12, + 180, 28, 11, 181, 32, 11, 182, 36, 10, 183, 40, 9, + 156, -88, 28, 157, -84, 28, 158, -80, 27, 159, -75, 26, + 160, -71, 26, 161, -67, 25, 161, -63, 25, 162, -59, 24, + 163, -55, 23, 164, -51, 22, 165, -47, 22, 166, -43, 21, + 167, -38, 20, 168, -34, 20, 169, -30, 19, 170, -26, 18, + 171, -22, 18, 172, -18, 17, 173, -14, 16, 174, -10, 16, + 175, -5, 15, 176, -1, 14, 176, 2, 14, 177, 6, 13, + 178, 10, 12, 179, 14, 12, 180, 18, 11, 181, 22, 10, + 182, 26, 10, 183, 31, 9, 184, 35, 8, 185, 39, 8, + 158, -89, 27, 159, -85, 26, 160, -81, 26, 161, -77, 25, + 162, -73, 24, 163, -69, 23, 164, -65, 23, 165, -60, 22, + 166, -56, 21, 167, -52, 21, 168, -48, 20, 168, -44, 20, + 169, -40, 19, 170, -36, 18, 171, -32, 17, 172, -28, 17, + 173, -23, 16, 174, -19, 15, 175, -15, 15, 176, -11, 14, + 177, -7, 13, 178, -3, 13, 179, 0, 12, 180, 4, 11, + 181, 8, 11, 182, 13, 10, 183, 17, 9, 184, 21, 9, + 184, 25, 8, 185, 29, 7, 186, 33, 7, 187, 37, 6, + 161, -90, 25, 161, -86, 24, 162, -82, 24, 163, -78, 23, + 164, -74, 22, 165, -70, 22, 166, -66, 21, 167, -61, 20, + 168, -57, 20, 169, -53, 19, 170, -49, 18, 171, -45, 18, + 172, -41, 17, 173, -37, 16, 174, -33, 16, 175, -29, 15, + 176, -24, 14, 177, -20, 14, 177, -16, 13, 178, -12, 12, + 179, -8, 12, 180, -4, 11, 181, 0, 10, 182, 3, 10, + 183, 7, 9, 184, 12, 8, 185, 16, 8, 186, 20, 7, + 187, 24, 6, 188, 28, 6, 189, 32, 5, 190, 36, 4, + 163, -92, 23, 164, -88, 23, 165, -84, 22, 166, -79, 21, + 167, -75, 21, 168, -71, 20, 168, -67, 19, 170, -63, 19, + 170, -59, 18, 171, -55, 17, 172, -51, 17, 173, -47, 16, + 174, -42, 15, 175, -38, 15, 176, -34, 14, 177, -30, 13, + 178, -26, 13, 179, -22, 12, 180, -18, 11, 181, -14, 11, + 182, -9, 10, 183, -5, 9, 184, -1, 9, 184, 2, 8, + 185, 6, 7, 186, 10, 7, 187, 14, 6, 188, 18, 5, + 189, 22, 5, 190, 27, 4, 191, 31, 3, 192, 35, 3, + 165, -93, 22, 166, -89, 21, 167, -85, 20, 168, -81, 20, + 169, -77, 19, 170, -73, 18, 171, -69, 18, 172, -64, 17, + 173, -60, 16, 174, -56, 16, 175, -52, 15, 175, -48, 14, + 177, -44, 14, 177, -40, 13, 178, -36, 12, 179, -32, 12, + 180, -27, 11, 181, -23, 10, 182, -19, 10, 183, -15, 9, + 184, -11, 8, 185, -7, 8, 186, -3, 7, 187, 0, 6, + 188, 4, 6, 189, 9, 5, 190, 13, 4, 191, 17, 4, + 191, 21, 3, 192, 25, 2, 193, 29, 2, 194, 33, 1, + 168, -94, 20, 168, -90, 19, 169, -86, 19, 170, -82, 18, + 171, -78, 17, 172, -74, 17, 173, -70, 16, 174, -65, 15, + 175, -61, 15, 176, -57, 14, 177, -53, 13, 178, -49, 13, + 179, -45, 12, 180, -41, 11, 181, -37, 11, 182, -33, 10, + 183, -28, 9, 184, -24, 9, 184, -20, 8, 185, -16, 7, + 186, -12, 7, 187, -8, 6, 188, -4, 5, 189, 0, 5, + 190, 3, 4, 191, 8, 3, 192, 12, 3, 193, 16, 2, + 194, 20, 1, 195, 24, 1, 196, 28, 0, 197, 32, 0, + 170, -96, 18, 171, -92, 18, 172, -88, 17, 173, -83, 16, + 174, -79, 16, 175, -75, 15, 176, -71, 14, 177, -67, 14, + 177, -63, 13, 178, -59, 12, 179, -55, 12, 180, -51, 11, + 181, -46, 10, 182, -42, 10, 183, -38, 9, 184, -34, 8, + 185, -30, 8, 186, -26, 7, 187, -22, 6, 188, -18, 6, + 189, -13, 5, 190, -9, 4, 191, -5, 4, 191, -1, 3, + 192, 2, 2, 193, 6, 2, 194, 10, 1, 195, 14, 0, + 196, 18, 0, 197, 23, 0, 198, 27, -1, 199, 31, -1, + 172, -97, 17, 173, -93, 16, 174, -89, 15, 175, -85, 15, + 176, -81, 14, 177, -77, 13, 178, -73, 13, 179, -68, 12, + 180, -64, 11, 181, -60, 11, 182, -56, 10, 183, -52, 9, + 184, -48, 9, 184, -44, 8, 185, -40, 7, 186, -36, 7, + 187, -31, 6, 188, -27, 5, 189, -23, 5, 190, -19, 4, + 191, -15, 3, 192, -11, 3, 193, -7, 2, 194, -3, 1, + 195, 0, 1, 196, 5, 0, 197, 9, 0, 198, 13, 0, + 199, 17, -1, 200, 21, -2, 200, 25, -2, 201, 29, -3, + 175, -98, 15, 176, -94, 14, 176, -90, 14, 177, -86, 13, + 178, -82, 12, 179, -78, 12, 180, -74, 11, 181, -69, 10, + 182, -65, 10, 183, -61, 9, 184, -57, 8, 185, -53, 8, + 186, -49, 7, 187, -45, 6, 188, -41, 6, 189, -37, 5, + 190, -32, 4, 191, -28, 4, 192, -24, 3, 192, -20, 2, + 193, -16, 2, 194, -12, 1, 195, -8, 0, 196, -4, 0, + 197, 0, 0, 198, 4, -1, 199, 8, -1, 200, 12, -2, + 201, 16, -3, 202, 20, -3, 203, 24, -4, 204, 28, -5, + 177, -100, 13, 178, -96, 13, 179, -92, 12, 180, -87, 11, + 181, -83, 11, 182, -79, 10, 183, -75, 9, 184, -71, 9, + 185, -67, 8, 185, -63, 7, 186, -59, 7, 187, -55, 6, + 188, -50, 5, 189, -46, 5, 190, -42, 4, 191, -38, 3, + 192, -34, 3, 193, -30, 2, 194, -26, 1, 195, -22, 1, + 196, -17, 0, 197, -13, 0, 198, -9, 0, 199, -5, -1, + 199, -1, -2, 200, 2, -2, 201, 6, -3, 202, 10, -4, + 203, 14, -4, 204, 19, -5, 205, 23, -6, 206, 27, -6, + 179, -101, 12, 180, -97, 11, 181, -93, 10, 182, -88, 10, + 183, -84, 9, 184, -80, 8, 185, -76, 8, 186, -72, 7, + 187, -68, 6, 188, -64, 6, 189, -60, 5, 190, -56, 4, + 191, -51, 4, 192, -47, 3, 192, -43, 2, 193, -39, 2, + 194, -35, 1, 195, -31, 0, 196, -27, 0, 197, -23, 0, + 198, -18, -1, 199, -14, -1, 200, -10, -2, 201, -6, -3, + 202, -2, -3, 203, 1, -4, 204, 5, -5, 205, 9, -5, + 206, 13, -6, 207, 18, -7, 207, 22, -7, 208, 26, -8, + 182, -102, 10, 183, -98, 9, 183, -94, 9, 185, -90, 8, + 185, -86, 7, 186, -82, 7, 187, -78, 6, 188, -73, 5, + 189, -69, 5, 190, -65, 4, 191, -61, 3, 192, -57, 3, + 193, -53, 2, 194, -49, 1, 195, -45, 1, 196, -41, 0, + 197, -36, 0, 198, -32, 0, 199, -28, -1, 199, -24, -2, + 200, -20, -2, 201, -16, -3, 202, -12, -4, 203, -8, -4, + 204, -4, -5, 205, 0, -6, 206, 4, -6, 207, 8, -7, + 208, 12, -8, 209, 16, -8, 210, 20, -9, 211, 24, -10, + 185, -104, 8, 186, -100, 7, 186, -96, 7, 187, -91, 6, + 188, -87, 5, 189, -83, 5, 190, -79, 4, 191, -75, 3, + 192, -71, 3, 193, -67, 2, 194, -63, 1, 195, -59, 1, + 196, -54, 0, 197, -50, 0, 198, -46, 0, 199, -42, -1, + 200, -38, -2, 201, -34, -2, 201, -30, -3, 202, -26, -4, + 203, -21, -4, 204, -17, -5, 205, -13, -6, 206, -9, -6, + 207, -5, -7, 208, -1, -8, 209, 2, -8, 210, 6, -9, + 211, 10, -10, 212, 15, -10, 213, 19, -11, 214, 23, -12, + 187, -105, 6, 188, -101, 6, 189, -97, 5, 190, -93, 4, + 191, -89, 4, 192, -85, 3, 193, -81, 2, 194, -76, 2, + 194, -72, 1, 195, -68, 0, 196, -64, 0, 197, -60, 0, + 198, -56, -1, 199, -52, -1, 200, -48, -2, 201, -44, -3, + 202, -39, -3, 203, -35, -4, 204, -31, -5, 205, -27, -5, + 206, -23, -6, 207, -19, -7, 208, -15, -7, 209, -11, -8, + 209, -7, -9, 210, -2, -9, 211, 1, -10, 212, 5, -11, + 213, 9, -11, 214, 13, -12, 215, 17, -13, 216, 21, -13, + 189, -107, 5, 190, -103, 4, 191, -99, 3, 192, -94, 3, + 193, -90, 2, 194, -86, 1, 195, -82, 1, 196, -78, 0, + 197, -74, 0, 198, -70, 0, 199, -66, -1, 200, -62, -2, + 201, -57, -2, 202, -53, -3, 202, -49, -4, 203, -45, -4, + 204, -41, -5, 205, -37, -6, 206, -33, -6, 207, -29, -7, + 208, -24, -8, 209, -20, -8, 210, -16, -9, 211, -12, -10, + 212, -8, -10, 213, -4, -11, 214, 0, -12, 215, 3, -12, + 216, 7, -13, 217, 12, -14, 217, 16, -14, 218, 20, -15, + 192, -108, 3, 193, -104, 2, 193, -100, 2, 195, -95, 1, + 195, -91, 0, 196, -87, 0, 197, -83, 0, 198, -79, -1, + 199, -75, -1, 200, -71, -2, 201, -67, -3, 202, -63, -3, + 203, -58, -4, 204, -54, -5, 205, -50, -5, 206, -46, -6, + 207, -42, -7, 208, -38, -7, 209, -34, -8, 209, -30, -9, + 210, -25, -9, 211, -21, -10, 212, -17, -11, 213, -13, -11, + 214, -9, -12, 215, -5, -13, 216, -1, -13, 217, 2, -14, + 218, 6, -15, 219, 11, -15, 220, 15, -16, 221, 19, -17, + 194, -109, 1, 195, -105, 1, 196, -101, 0, 197, -97, 0, + 198, -93, 0, 199, -89, -1, 200, -85, -2, 201, -80, -2, + 202, -76, -3, 202, -72, -4, 203, -68, -4, 204, -64, -5, + 205, -60, -6, 206, -56, -6, 207, -52, -7, 208, -48, -8, + 209, -43, -8, 210, -39, -9, 211, -35, -10, 212, -31, -10, + 213, -27, -11, 214, -23, -12, 215, -19, -12, 216, -15, -13, + 216, -11, -14, 217, -6, -14, 218, -2, -15, 219, 1, -16, + 220, 5, -16, 221, 9, -17, 222, 13, -18, 223, 17, -18, + 196, -111, 0, 197, -107, 0, 198, -103, -1, 199, -98, -1, + 200, -94, -2, 201, -90, -3, 202, -86, -3, 203, -82, -4, + 204, -78, -5, 205, -74, -5, 206, -70, -6, 207, -66, -7, + 208, -61, -7, 209, -57, -8, 209, -53, -9, 210, -49, -9, + 211, -45, -10, 212, -41, -11, 213, -37, -11, 214, -33, -12, + 215, -28, -13, 216, -24, -13, 217, -20, -14, 218, -16, -15, + 219, -12, -15, 220, -8, -16, 221, -4, -17, 222, 0, -17, + 223, 3, -18, 224, 8, -19, 225, 12, -19, 225, 16, -20, + 199, -112, -1, 200, -108, -2, 201, -104, -2, 202, -99, -3, + 202, -95, -4, 203, -91, -4, 204, -87, -5, 205, -83, -6, + 206, -79, -6, 207, -75, -7, 208, -71, -8, 209, -67, -8, + 210, -62, -9, 211, -58, -10, 212, -54, -10, 213, -50, -11, + 214, -46, -12, 215, -42, -12, 216, -38, -13, 216, -34, -14, + 218, -29, -14, 218, -25, -15, 219, -21, -16, 220, -17, -16, + 221, -13, -17, 222, -9, -18, 223, -5, -18, 224, -1, -19, + 225, 2, -20, 226, 7, -21, 227, 11, -21, 228, 15, -22, + 201, -113, -3, 202, -109, -3, 203, -105, -4, 204, -101, -5, + 205, -97, -5, 206, -93, -6, 207, -89, -7, 208, -84, -7, + 209, -80, -8, 209, -76, -9, 210, -72, -9, 211, -68, -10, + 212, -64, -11, 213, -60, -11, 214, -56, -12, 215, -52, -13, + 216, -47, -13, 217, -43, -14, 218, -39, -15, 219, -35, -15, + 220, -31, -16, 221, -27, -17, 222, -23, -17, 223, -19, -18, + 224, -15, -19, 225, -10, -19, 225, -6, -20, 226, -2, -21, + 227, 1, -21, 228, 5, -22, 229, 9, -23, 230, 13, -23, + 203, -115, -4, 204, -111, -5, 205, -107, -6, 206, -102, -6, + 207, -98, -7, 208, -94, -8, 209, -90, -8, 210, -86, -9, + 211, -82, -10, 212, -78, -10, 213, -74, -11, 214, -70, -12, + 215, -65, -12, 216, -61, -13, 217, -57, -14, 217, -53, -14, + 218, -49, -15, 219, -45, -16, 220, -41, -16, 221, -37, -17, + 222, -32, -18, 223, -28, -18, 224, -24, -19, 225, -20, -20, + 226, -16, -20, 227, -12, -21, 228, -8, -22, 229, -4, -22, + 230, 0, -23, 231, 4, -24, 232, 8, -25, 232, 12, -25, + 206, -116, -6, 207, -112, -7, 208, -108, -7, 209, -103, -8, + 210, -99, -9, 210, -95, -9, 211, -91, -10, 212, -87, -11, + 213, -83, -11, 214, -79, -12, 215, -75, -13, 216, -71, -13, + 217, -66, -14, 218, -62, -15, 219, -58, -15, 220, -54, -16, + 221, -50, -17, 222, -46, -17, 223, -42, -18, 224, -38, -19, + 225, -33, -20, 225, -29, -20, 226, -25, -21, 227, -21, -21, + 228, -17, -22, 229, -13, -23, 230, -9, -23, 231, -5, -24, + 232, -1, -25, 233, 3, -26, 234, 7, -26, 235, 11, -27, + 208, -117, -8, 209, -113, -8, 210, -109, -9, 211, -105, -10, + 212, -101, -10, 213, -97, -11, 214, -93, -12, 215, -88, -12, + 216, -84, -13, 217, -80, -14, 217, -76, -14, 218, -72, -15, + 219, -68, -16, 220, -64, -16, 221, -60, -17, 222, -56, -18, + 223, -51, -18, 224, -47, -19, 225, -43, -20, 226, -39, -20, + 227, -35, -21, 228, -31, -22, 229, -27, -22, 230, -23, -23, + 231, -19, -24, 232, -14, -25, 232, -10, -25, 233, -6, -26, + 234, -2, -26, 235, 1, -27, 236, 5, -28, 237, 9, -28, + 61, -34, 103, 62, -30, 102, 63, -26, 101, 64, -22, 100, + 65, -18, 100, 66, -14, 99, 67, -10, 99, 68, -5, 98, + 69, -1, 97, 70, 2, 96, 70, 6, 96, 71, 10, 95, + 72, 14, 94, 73, 18, 94, 74, 22, 93, 75, 26, 92, + 76, 31, 92, 77, 35, 91, 78, 39, 90, 79, 43, 90, + 80, 47, 89, 81, 51, 88, 82, 55, 88, 83, 59, 87, + 84, 63, 86, 85, 68, 86, 85, 72, 85, 86, 76, 84, + 87, 80, 84, 88, 84, 83, 89, 88, 82, 90, 92, 82, + 63, -36, 101, 64, -32, 100, 65, -28, 100, 66, -23, 99, + 67, -19, 98, 68, -15, 97, 69, -11, 97, 70, -7, 96, + 71, -3, 95, 72, 0, 95, 73, 4, 94, 74, 8, 94, + 75, 13, 93, 76, 17, 92, 77, 21, 91, 77, 25, 91, + 78, 29, 90, 79, 33, 89, 80, 37, 89, 81, 41, 88, + 82, 46, 87, 83, 50, 87, 84, 54, 86, 85, 58, 85, + 86, 62, 85, 87, 66, 84, 88, 70, 83, 89, 74, 83, + 90, 78, 82, 91, 83, 81, 92, 87, 81, 93, 91, 80, + 66, -37, 99, 67, -33, 99, 68, -29, 98, 69, -24, 97, + 70, -20, 96, 70, -16, 96, 71, -12, 95, 72, -8, 94, + 73, -4, 94, 74, 0, 93, 75, 3, 92, 76, 7, 92, + 77, 12, 91, 78, 16, 90, 79, 20, 90, 80, 24, 89, + 81, 28, 88, 82, 32, 88, 83, 36, 87, 84, 40, 86, + 85, 45, 86, 86, 49, 85, 86, 53, 84, 87, 57, 84, + 88, 61, 83, 89, 65, 82, 90, 69, 82, 91, 73, 81, + 92, 77, 80, 93, 82, 80, 94, 86, 79, 95, 90, 78, + 68, -38, 97, 69, -34, 97, 70, -30, 96, 71, -26, 95, + 72, -22, 95, 73, -18, 94, 74, -14, 93, 75, -9, 93, + 76, -5, 92, 77, -1, 91, 77, 2, 91, 78, 6, 90, + 79, 10, 89, 80, 14, 89, 81, 18, 88, 82, 22, 87, + 83, 27, 87, 84, 31, 86, 85, 35, 85, 86, 39, 85, + 87, 43, 84, 88, 47, 83, 89, 51, 83, 90, 55, 82, + 91, 59, 81, 92, 64, 81, 93, 68, 80, 93, 72, 79, + 94, 76, 79, 95, 80, 78, 96, 84, 77, 97, 88, 77, + 70, -40, 96, 71, -36, 95, 72, -32, 95, 73, -27, 94, + 74, -23, 93, 75, -19, 92, 76, -15, 92, 77, -11, 91, + 78, -7, 90, 79, -3, 90, 80, 0, 89, 81, 4, 88, + 82, 9, 88, 83, 13, 87, 84, 17, 86, 85, 21, 86, + 86, 25, 85, 86, 29, 84, 87, 33, 84, 88, 37, 83, + 89, 42, 82, 90, 46, 82, 91, 50, 81, 92, 54, 80, + 93, 58, 80, 94, 62, 79, 95, 66, 78, 96, 70, 78, + 97, 74, 77, 98, 79, 76, 99, 83, 76, 100, 87, 75, + 73, -41, 94, 74, -37, 93, 75, -33, 93, 76, -28, 92, + 77, -24, 91, 78, -20, 91, 78, -16, 90, 79, -12, 89, + 80, -8, 89, 81, -4, 88, 82, 0, 87, 83, 3, 87, + 84, 8, 86, 85, 12, 85, 86, 16, 85, 87, 20, 84, + 88, 24, 83, 89, 28, 83, 90, 32, 82, 91, 36, 81, + 92, 41, 81, 93, 45, 80, 93, 49, 79, 94, 53, 79, + 95, 57, 78, 96, 61, 77, 97, 65, 77, 98, 69, 76, + 99, 73, 75, 100, 78, 75, 101, 82, 74, 102, 86, 73, + 75, -42, 92, 76, -38, 92, 77, -34, 91, 78, -30, 90, + 79, -26, 90, 80, -22, 89, 81, -18, 88, 82, -13, 88, + 83, -9, 87, 84, -5, 86, 85, -1, 86, 85, 2, 85, + 86, 6, 84, 87, 10, 84, 88, 14, 83, 89, 18, 82, + 90, 23, 82, 91, 27, 81, 92, 31, 80, 93, 35, 80, + 94, 39, 79, 95, 43, 78, 96, 47, 78, 97, 51, 77, + 98, 55, 76, 99, 60, 76, 100, 64, 75, 100, 68, 74, + 101, 72, 74, 102, 76, 73, 103, 80, 72, 104, 84, 72, + 78, -44, 91, 78, -40, 90, 79, -36, 89, 80, -31, 89, + 81, -27, 88, 82, -23, 87, 83, -19, 87, 84, -15, 86, + 85, -11, 85, 86, -7, 85, 87, -3, 84, 88, 0, 83, + 89, 5, 83, 90, 9, 82, 91, 13, 81, 92, 17, 81, + 93, 21, 80, 93, 25, 79, 94, 29, 79, 95, 33, 78, + 96, 38, 77, 97, 42, 77, 98, 46, 76, 99, 50, 75, + 100, 54, 75, 101, 58, 74, 102, 62, 73, 103, 66, 73, + 104, 70, 72, 105, 75, 71, 106, 79, 71, 107, 83, 70, + 80, -45, 89, 81, -41, 88, 82, -37, 88, 83, -32, 87, + 84, -28, 86, 85, -24, 86, 85, -20, 85, 86, -16, 84, + 87, -12, 84, 88, -8, 83, 89, -4, 82, 90, 0, 82, + 91, 4, 81, 92, 8, 80, 93, 12, 80, 94, 16, 79, + 95, 20, 78, 96, 24, 78, 97, 28, 77, 98, 32, 76, + 99, 37, 76, 100, 41, 75, 101, 45, 74, 101, 49, 74, + 102, 53, 73, 103, 57, 72, 104, 61, 72, 105, 65, 71, + 106, 69, 70, 107, 74, 70, 108, 78, 69, 109, 82, 68, + 82, -46, 87, 83, -42, 87, 84, -38, 86, 85, -34, 85, + 86, -30, 85, 87, -26, 84, 88, -22, 83, 89, -17, 83, + 90, -13, 82, 91, -9, 81, 92, -5, 81, 92, -1, 80, + 94, 2, 79, 94, 6, 79, 95, 10, 78, 96, 14, 77, + 97, 19, 77, 98, 23, 76, 99, 27, 75, 100, 31, 75, + 101, 35, 74, 102, 39, 73, 103, 43, 73, 104, 47, 72, + 105, 51, 71, 106, 56, 71, 107, 60, 70, 108, 64, 69, + 108, 68, 69, 109, 72, 68, 110, 76, 67, 111, 80, 67, + 85, -48, 86, 85, -44, 85, 86, -40, 84, 87, -35, 84, + 88, -31, 83, 89, -27, 82, 90, -23, 82, 91, -19, 81, + 92, -15, 80, 93, -11, 80, 94, -7, 79, 95, -3, 78, + 96, 1, 78, 97, 5, 77, 98, 9, 76, 99, 13, 76, + 100, 17, 75, 101, 21, 74, 101, 25, 74, 102, 29, 73, + 103, 34, 72, 104, 38, 72, 105, 42, 71, 106, 46, 70, + 107, 50, 70, 108, 54, 69, 109, 58, 68, 110, 62, 68, + 111, 66, 67, 112, 71, 66, 113, 75, 66, 114, 79, 65, + 88, -49, 84, 88, -45, 83, 89, -41, 82, 90, -37, 82, + 91, -33, 81, 92, -29, 80, 93, -25, 80, 94, -20, 79, + 95, -16, 78, 96, -12, 78, 97, -8, 77, 98, -4, 76, + 99, 0, 76, 100, 3, 75, 101, 7, 74, 102, 11, 74, + 103, 16, 73, 103, 20, 72, 104, 24, 72, 105, 28, 71, + 106, 32, 70, 107, 36, 70, 108, 40, 69, 109, 44, 68, + 110, 48, 68, 111, 53, 67, 112, 57, 66, 113, 61, 66, + 114, 65, 65, 115, 69, 64, 116, 73, 64, 117, 77, 63, + 90, -50, 82, 91, -46, 81, 92, -42, 81, 93, -38, 80, + 94, -34, 79, 95, -30, 79, 95, -26, 78, 96, -21, 77, + 97, -17, 77, 98, -13, 76, 99, -9, 75, 100, -5, 75, + 101, -1, 74, 102, 2, 73, 103, 6, 73, 104, 10, 72, + 105, 15, 71, 106, 19, 71, 107, 23, 70, 108, 27, 69, + 109, 31, 69, 110, 35, 68, 110, 39, 67, 111, 43, 67, + 112, 47, 66, 113, 52, 65, 114, 56, 65, 115, 60, 64, + 116, 64, 63, 117, 68, 63, 118, 72, 62, 119, 76, 61, + 92, -52, 80, 93, -48, 80, 94, -44, 79, 95, -39, 78, + 96, -35, 78, 97, -31, 77, 98, -27, 76, 99, -23, 76, + 100, -19, 75, 101, -15, 74, 102, -11, 74, 102, -7, 73, + 103, -2, 72, 104, 1, 72, 105, 5, 71, 106, 9, 70, + 107, 13, 70, 108, 17, 69, 109, 21, 68, 110, 25, 68, + 111, 30, 67, 112, 34, 66, 113, 38, 66, 114, 42, 65, + 115, 46, 64, 116, 50, 64, 117, 54, 63, 118, 58, 62, + 118, 62, 62, 119, 67, 61, 120, 71, 60, 121, 75, 60, + 95, -53, 79, 95, -49, 78, 96, -45, 77, 97, -41, 77, + 98, -37, 76, 99, -33, 75, 100, -29, 75, 101, -24, 74, + 102, -20, 73, 103, -16, 73, 104, -12, 72, 105, -8, 71, + 106, -4, 71, 107, 0, 70, 108, 3, 69, 109, 7, 69, + 110, 12, 68, 111, 16, 67, 111, 20, 67, 112, 24, 66, + 113, 28, 65, 114, 32, 65, 115, 36, 64, 116, 40, 63, + 117, 44, 63, 118, 49, 62, 119, 53, 61, 120, 57, 61, + 121, 61, 60, 122, 65, 59, 123, 69, 59, 124, 73, 58, + 97, -54, 77, 98, -50, 76, 99, -46, 76, 100, -42, 75, + 101, -38, 74, 102, -34, 74, 102, -30, 73, 104, -25, 72, + 104, -21, 72, 105, -17, 71, 106, -13, 70, 107, -9, 70, + 108, -5, 69, 109, -1, 68, 110, 2, 68, 111, 6, 67, + 112, 11, 66, 113, 15, 66, 114, 19, 65, 115, 23, 64, + 116, 27, 64, 117, 31, 63, 118, 35, 62, 118, 39, 62, + 119, 43, 61, 120, 48, 60, 121, 52, 60, 122, 56, 59, + 123, 60, 58, 124, 64, 58, 125, 68, 57, 126, 72, 56, + 99, -56, 75, 100, -52, 75, 101, -48, 74, 102, -43, 73, + 103, -39, 73, 104, -35, 72, 105, -31, 71, 106, -27, 71, + 107, -23, 70, 108, -19, 69, 109, -15, 69, 110, -11, 68, + 111, -6, 67, 111, -2, 67, 112, 1, 66, 113, 5, 65, + 114, 9, 65, 115, 13, 64, 116, 17, 63, 117, 21, 63, + 118, 26, 62, 119, 30, 61, 120, 34, 61, 121, 38, 60, + 122, 42, 59, 123, 46, 59, 124, 50, 58, 125, 54, 57, + 125, 58, 57, 126, 63, 56, 127, 67, 55, 128, 71, 55, + 102, -57, 74, 103, -53, 73, 103, -49, 72, 104, -45, 72, + 105, -41, 71, 106, -37, 70, 107, -33, 70, 108, -28, 69, + 109, -24, 68, 110, -20, 68, 111, -16, 67, 112, -12, 66, + 113, -8, 66, 114, -4, 65, 115, 0, 64, 116, 3, 64, + 117, 8, 63, 118, 12, 62, 118, 16, 62, 119, 20, 61, + 120, 24, 60, 121, 28, 60, 122, 32, 59, 123, 36, 58, + 124, 40, 58, 125, 45, 57, 126, 49, 56, 127, 53, 56, + 128, 57, 55, 129, 61, 54, 130, 65, 54, 131, 69, 53, + 104, -58, 72, 105, -54, 71, 106, -50, 71, 107, -46, 70, + 108, -42, 69, 109, -38, 69, 110, -34, 68, 111, -29, 67, + 111, -25, 67, 112, -21, 66, 113, -17, 65, 114, -13, 65, + 115, -9, 64, 116, -5, 63, 117, -1, 63, 118, 2, 62, + 119, 7, 61, 120, 11, 61, 121, 15, 60, 122, 19, 59, + 123, 23, 59, 124, 27, 58, 125, 31, 57, 125, 35, 57, + 126, 39, 56, 127, 44, 55, 128, 48, 55, 129, 52, 54, + 130, 56, 53, 131, 60, 53, 132, 64, 52, 133, 68, 51, + 106, -60, 70, 107, -56, 70, 108, -52, 69, 109, -47, 68, + 110, -43, 68, 111, -39, 67, 112, -35, 66, 113, -31, 66, + 114, -27, 65, 115, -23, 64, 116, -19, 64, 117, -15, 63, + 118, -10, 62, 118, -6, 62, 119, -2, 61, 120, 1, 60, + 121, 5, 60, 122, 9, 59, 123, 13, 58, 124, 17, 58, + 125, 22, 57, 126, 26, 56, 127, 30, 56, 128, 34, 55, + 129, 38, 54, 130, 42, 54, 131, 46, 53, 132, 50, 52, + 133, 54, 52, 134, 59, 51, 134, 63, 50, 135, 67, 50, + 109, -61, 69, 110, -57, 68, 110, -53, 67, 111, -49, 67, + 112, -45, 66, 113, -41, 65, 114, -37, 65, 115, -32, 64, + 116, -28, 63, 117, -24, 63, 118, -20, 62, 119, -16, 61, + 120, -12, 61, 121, -8, 60, 122, -4, 59, 123, 0, 59, + 124, 4, 58, 125, 8, 57, 126, 12, 57, 126, 16, 56, + 127, 20, 55, 128, 24, 55, 129, 28, 54, 130, 32, 53, + 131, 36, 53, 132, 41, 52, 133, 45, 51, 134, 49, 51, + 135, 53, 50, 136, 57, 49, 137, 61, 49, 138, 65, 48, + 111, -62, 67, 112, -58, 66, 113, -54, 66, 114, -50, 65, + 115, -46, 64, 116, -42, 64, 117, -38, 63, 118, -33, 62, + 119, -29, 62, 119, -25, 61, 120, -21, 60, 121, -17, 60, + 122, -13, 59, 123, -9, 58, 124, -5, 58, 125, -1, 57, + 126, 3, 56, 127, 7, 56, 128, 11, 55, 129, 15, 54, + 130, 19, 53, 131, 23, 53, 132, 27, 52, 133, 31, 52, + 133, 35, 51, 134, 40, 50, 135, 44, 50, 136, 48, 49, + 137, 52, 48, 138, 56, 47, 139, 60, 47, 140, 64, 46, + 113, -64, 65, 114, -60, 65, 115, -56, 64, 116, -51, 63, + 117, -47, 63, 118, -43, 62, 119, -39, 61, 120, -35, 61, + 121, -31, 60, 122, -27, 59, 123, -23, 59, 124, -19, 58, + 125, -14, 57, 126, -10, 57, 126, -6, 56, 127, -2, 55, + 128, 1, 55, 129, 5, 54, 130, 9, 53, 131, 13, 53, + 132, 18, 52, 133, 22, 51, 134, 26, 51, 135, 30, 50, + 136, 34, 49, 137, 38, 48, 138, 42, 48, 139, 46, 47, + 140, 50, 47, 141, 55, 46, 141, 59, 45, 142, 63, 45, + 116, -65, 64, 117, -61, 63, 118, -57, 62, 119, -53, 62, + 119, -49, 61, 120, -45, 60, 121, -41, 60, 122, -36, 59, + 123, -32, 58, 124, -28, 58, 125, -24, 57, 126, -20, 56, + 127, -16, 56, 128, -12, 55, 129, -8, 54, 130, -4, 54, + 131, 0, 53, 132, 4, 52, 133, 8, 52, 133, 12, 51, + 134, 16, 50, 135, 20, 49, 136, 24, 49, 137, 28, 48, + 138, 32, 48, 139, 37, 47, 140, 41, 46, 141, 45, 46, + 142, 49, 45, 143, 53, 44, 144, 57, 43, 145, 61, 43, + 118, -66, 62, 119, -62, 61, 120, -58, 61, 121, -54, 60, + 122, -50, 59, 123, -46, 59, 124, -42, 58, 125, -37, 57, + 126, -33, 57, 126, -29, 56, 127, -25, 55, 128, -21, 55, + 129, -17, 54, 130, -13, 53, 131, -9, 53, 132, -5, 52, + 133, 0, 51, 134, 3, 51, 135, 7, 50, 136, 11, 49, + 137, 15, 48, 138, 19, 48, 139, 23, 47, 140, 27, 47, + 140, 31, 46, 142, 36, 45, 142, 40, 44, 143, 44, 44, + 144, 48, 43, 145, 52, 42, 146, 56, 42, 147, 60, 41, + 120, -68, 60, 121, -64, 60, 122, -60, 59, 123, -55, 58, + 124, -51, 58, 125, -47, 57, 126, -43, 56, 127, -39, 55, + 128, -35, 55, 129, -31, 54, 130, -27, 54, 131, -23, 53, + 132, -18, 52, 133, -14, 52, 133, -10, 51, 134, -6, 50, + 135, -2, 49, 136, 1, 49, 137, 5, 48, 138, 9, 48, + 139, 14, 47, 140, 18, 46, 141, 22, 45, 142, 26, 45, + 143, 30, 44, 144, 34, 43, 145, 38, 43, 146, 42, 42, + 147, 46, 42, 148, 51, 41, 149, 55, 40, 149, 59, 39, + 123, -69, 59, 124, -65, 58, 125, -61, 57, 126, -57, 57, + 126, -53, 56, 127, -49, 55, 128, -45, 55, 129, -40, 54, + 130, -36, 53, 131, -32, 53, 132, -28, 52, 133, -24, 51, + 134, -20, 50, 135, -16, 50, 136, -12, 49, 137, -8, 49, + 138, -3, 48, 139, 0, 47, 140, 4, 47, 141, 8, 46, + 142, 12, 45, 142, 16, 44, 143, 20, 44, 144, 24, 43, + 145, 28, 43, 146, 33, 42, 147, 37, 41, 148, 41, 40, + 149, 45, 40, 150, 49, 39, 151, 53, 38, 152, 57, 38, + 125, -70, 57, 126, -66, 56, 127, -62, 56, 128, -58, 55, + 129, -54, 54, 130, -50, 54, 131, -46, 53, 132, -41, 52, + 133, -37, 51, 134, -33, 51, 134, -29, 50, 135, -25, 50, + 136, -21, 49, 137, -17, 48, 138, -13, 48, 139, -9, 47, + 140, -4, 46, 141, 0, 45, 142, 3, 45, 143, 7, 44, + 144, 11, 43, 145, 15, 43, 146, 19, 42, 147, 23, 41, + 148, 27, 41, 149, 32, 40, 149, 36, 39, 150, 40, 39, + 151, 44, 38, 152, 48, 37, 153, 52, 37, 154, 56, 36, + 127, -72, 55, 128, -68, 55, 129, -64, 54, 130, -59, 53, + 131, -55, 53, 132, -51, 52, 133, -47, 51, 134, -43, 50, + 135, -39, 50, 136, -35, 49, 137, -31, 49, 138, -27, 48, + 139, -22, 47, 140, -18, 46, 141, -14, 46, 141, -10, 45, + 142, -6, 44, 143, -2, 44, 144, 1, 43, 145, 5, 43, + 146, 10, 42, 147, 14, 41, 148, 18, 40, 149, 22, 40, + 150, 26, 39, 151, 30, 38, 152, 34, 38, 153, 38, 37, + 154, 42, 36, 155, 47, 36, 156, 51, 35, 156, 55, 34, + 130, -73, 54, 131, -69, 53, 132, -65, 52, 133, -61, 51, + 134, -57, 51, 134, -53, 50, 135, -49, 50, 136, -44, 49, + 137, -40, 48, 138, -36, 47, 139, -32, 47, 140, -28, 46, + 141, -24, 45, 142, -20, 45, 143, -16, 44, 144, -12, 44, + 145, -7, 43, 146, -3, 42, 147, 0, 41, 148, 4, 41, + 149, 8, 40, 149, 12, 39, 150, 16, 39, 151, 20, 38, + 152, 24, 37, 153, 29, 37, 154, 33, 36, 155, 37, 35, + 156, 41, 35, 157, 45, 34, 158, 49, 33, 159, 53, 33, + 132, -74, 52, 133, -70, 51, 134, -66, 51, 135, -62, 50, + 136, -58, 49, 137, -54, 49, 138, -50, 48, 139, -45, 47, + 140, -41, 46, 141, -37, 46, 141, -33, 45, 142, -29, 45, + 143, -25, 44, 144, -21, 43, 145, -17, 42, 146, -13, 42, + 147, -8, 41, 148, -4, 40, 149, 0, 40, 150, 3, 39, + 151, 7, 38, 152, 11, 38, 153, 15, 37, 154, 19, 36, + 155, 23, 36, 156, 28, 35, 157, 32, 34, 157, 36, 34, + 158, 40, 33, 159, 44, 32, 160, 48, 32, 161, 52, 31, + 134, -76, 50, 135, -72, 50, 136, -68, 49, 137, -63, 48, + 138, -59, 47, 139, -55, 47, 140, -51, 46, 141, -47, 45, + 142, -43, 45, 143, -39, 44, 144, -35, 43, 145, -31, 43, + 146, -26, 42, 147, -22, 41, 148, -18, 41, 148, -14, 40, + 150, -10, 39, 150, -6, 39, 151, -2, 38, 152, 1, 37, + 153, 6, 37, 154, 10, 36, 155, 14, 35, 156, 18, 35, + 157, 22, 34, 158, 26, 33, 159, 30, 33, 160, 34, 32, + 161, 38, 31, 162, 43, 31, 163, 47, 30, 164, 51, 29, + 137, -77, 48, 138, -73, 47, 139, -69, 47, 140, -65, 46, + 141, -61, 45, 142, -57, 45, 143, -53, 44, 144, -48, 43, + 145, -44, 43, 146, -40, 42, 147, -36, 41, 148, -32, 41, + 149, -28, 40, 150, -24, 39, 151, -20, 39, 151, -16, 38, + 152, -11, 37, 153, -7, 37, 154, -3, 36, 155, 0, 35, + 156, 4, 35, 157, 8, 34, 158, 12, 33, 159, 16, 33, + 160, 20, 32, 161, 25, 31, 162, 29, 31, 163, 33, 30, + 164, 37, 29, 165, 41, 29, 166, 45, 28, 166, 49, 27, + 140, -79, 46, 141, -75, 46, 142, -71, 45, 143, -66, 44, + 144, -62, 44, 144, -58, 43, 145, -54, 42, 146, -50, 42, + 147, -46, 41, 148, -42, 40, 149, -38, 40, 150, -34, 39, + 151, -29, 38, 152, -25, 38, 153, -21, 37, 154, -17, 36, + 155, -13, 36, 156, -9, 35, 157, -5, 34, 158, -1, 34, + 159, 3, 33, 159, 7, 32, 160, 11, 32, 161, 15, 31, + 162, 19, 30, 163, 23, 30, 164, 27, 29, 165, 31, 28, + 166, 35, 28, 167, 40, 27, 168, 44, 26, 169, 48, 26, + 142, -80, 45, 143, -76, 44, 144, -72, 43, 145, -67, 43, + 146, -63, 42, 147, -59, 41, 148, -55, 41, 149, -51, 40, + 150, -47, 39, 151, -43, 39, 151, -39, 38, 152, -35, 37, + 153, -30, 37, 154, -26, 36, 155, -22, 35, 156, -18, 35, + 157, -14, 34, 158, -10, 33, 159, -6, 33, 160, -2, 32, + 161, 2, 31, 162, 6, 31, 163, 10, 30, 164, 14, 29, + 165, 18, 29, 166, 22, 28, 166, 26, 27, 167, 30, 27, + 168, 34, 26, 169, 39, 25, 170, 43, 25, 171, 47, 24, + 144, -81, 43, 145, -77, 42, 146, -73, 42, 147, -69, 41, + 148, -65, 40, 149, -61, 40, 150, -57, 39, 151, -52, 38, + 152, -48, 38, 153, -44, 37, 154, -40, 36, 155, -36, 36, + 156, -32, 35, 157, -28, 34, 158, -24, 34, 158, -20, 33, + 159, -15, 32, 160, -11, 32, 161, -7, 31, 162, -3, 30, + 163, 0, 30, 164, 4, 29, 165, 8, 28, 166, 12, 28, + 167, 16, 27, 168, 21, 26, 169, 25, 26, 170, 29, 25, + 171, 33, 24, 172, 37, 24, 173, 41, 23, 174, 45, 22, + 147, -83, 41, 148, -79, 41, 149, -75, 40, 150, -70, 39, + 151, -66, 39, 151, -62, 38, 152, -58, 37, 153, -54, 37, + 154, -50, 36, 155, -46, 35, 156, -42, 35, 157, -38, 34, + 158, -33, 33, 159, -29, 33, 160, -25, 32, 161, -21, 31, + 162, -17, 31, 163, -13, 30, 164, -9, 29, 165, -5, 29, + 166, 0, 28, 167, 3, 27, 167, 7, 27, 168, 11, 26, + 169, 15, 25, 170, 19, 25, 171, 23, 24, 172, 27, 23, + 173, 31, 23, 174, 36, 22, 175, 40, 21, 176, 44, 21, + 149, -84, 40, 150, -80, 39, 151, -76, 38, 152, -71, 38, + 153, -67, 37, 154, -63, 36, 155, -59, 36, 156, -55, 35, + 157, -51, 34, 158, -47, 34, 158, -43, 33, 159, -39, 32, + 160, -34, 32, 161, -30, 31, 162, -26, 30, 163, -22, 30, + 164, -18, 29, 165, -14, 28, 166, -10, 28, 167, -6, 27, + 168, -1, 26, 169, 2, 26, 170, 6, 25, 171, 10, 24, + 172, 14, 24, 173, 18, 23, 174, 22, 22, 174, 26, 22, + 175, 30, 21, 176, 35, 20, 177, 39, 20, 178, 43, 19, + 151, -85, 38, 152, -81, 37, 153, -77, 37, 154, -73, 36, + 155, -69, 35, 156, -65, 35, 157, -61, 34, 158, -56, 33, + 159, -52, 33, 160, -48, 32, 161, -44, 31, 162, -40, 31, + 163, -36, 30, 164, -32, 29, 165, -28, 29, 166, -24, 28, + 167, -19, 27, 167, -15, 27, 168, -11, 26, 169, -7, 25, + 170, -3, 25, 171, 0, 24, 172, 4, 23, 173, 8, 23, + 174, 12, 22, 175, 17, 21, 176, 21, 21, 177, 25, 20, + 178, 29, 19, 179, 33, 19, 180, 37, 18, 181, 41, 17, + 154, -87, 36, 155, -83, 36, 156, -79, 35, 157, -74, 34, + 158, -70, 34, 159, -66, 33, 159, -62, 32, 160, -58, 32, + 161, -54, 31, 162, -50, 30, 163, -46, 30, 164, -42, 29, + 165, -37, 28, 166, -33, 28, 167, -29, 27, 168, -25, 26, + 169, -21, 26, 170, -17, 25, 171, -13, 24, 172, -9, 24, + 173, -4, 23, 174, 0, 22, 174, 3, 22, 175, 7, 21, + 176, 11, 20, 177, 15, 20, 178, 19, 19, 179, 23, 18, + 180, 27, 18, 181, 32, 17, 182, 36, 16, 183, 40, 16, + 156, -88, 35, 157, -84, 34, 158, -80, 33, 159, -75, 33, + 160, -71, 32, 161, -67, 31, 162, -63, 31, 163, -59, 30, + 164, -55, 29, 165, -51, 29, 166, -47, 28, 166, -43, 27, + 167, -38, 27, 168, -34, 26, 169, -30, 25, 170, -26, 25, + 171, -22, 24, 172, -18, 23, 173, -14, 23, 174, -10, 22, + 175, -5, 21, 176, -1, 21, 177, 2, 20, 178, 6, 19, + 179, 10, 19, 180, 14, 18, 181, 18, 17, 181, 22, 17, + 182, 26, 16, 183, 31, 15, 184, 35, 15, 185, 39, 14, + 159, -89, 33, 159, -85, 32, 160, -81, 32, 161, -77, 31, + 162, -73, 30, 163, -69, 30, 164, -65, 29, 165, -60, 28, + 166, -56, 28, 167, -52, 27, 168, -48, 26, 169, -44, 26, + 170, -40, 25, 171, -36, 24, 172, -32, 24, 173, -28, 23, + 174, -23, 22, 174, -19, 22, 175, -15, 21, 176, -11, 20, + 177, -7, 20, 178, -3, 19, 179, 0, 18, 180, 4, 18, + 181, 8, 17, 182, 13, 16, 183, 17, 16, 184, 21, 15, + 185, 25, 14, 186, 29, 14, 187, 33, 13, 188, 37, 12, + 161, -91, 31, 162, -87, 31, 163, -83, 30, 164, -78, 29, + 165, -74, 29, 166, -70, 28, 166, -66, 27, 167, -62, 27, + 168, -58, 26, 169, -54, 25, 170, -50, 25, 171, -46, 24, + 172, -41, 23, 173, -37, 23, 174, -33, 22, 175, -29, 21, + 176, -25, 21, 177, -21, 20, 178, -17, 19, 179, -13, 19, + 180, -8, 18, 181, -4, 17, 182, 0, 17, 182, 3, 16, + 183, 7, 15, 184, 11, 15, 185, 15, 14, 186, 19, 13, + 187, 23, 13, 188, 28, 12, 189, 32, 11, 190, 36, 11, + 163, -92, 30, 164, -88, 29, 165, -84, 28, 166, -79, 28, + 167, -75, 27, 168, -71, 26, 169, -67, 26, 170, -63, 25, + 171, -59, 24, 172, -55, 24, 173, -51, 23, 173, -47, 22, + 175, -42, 22, 175, -38, 21, 176, -34, 20, 177, -30, 20, + 178, -26, 19, 179, -22, 18, 180, -18, 18, 181, -14, 17, + 182, -9, 16, 183, -5, 16, 184, -1, 15, 185, 2, 14, + 186, 6, 14, 187, 10, 13, 188, 14, 12, 189, 18, 12, + 189, 22, 11, 190, 27, 10, 191, 31, 10, 192, 35, 9, + 166, -93, 28, 166, -89, 27, 167, -85, 27, 168, -81, 26, + 169, -77, 25, 170, -73, 25, 171, -69, 24, 172, -64, 23, + 173, -60, 23, 174, -56, 22, 175, -52, 21, 176, -48, 21, + 177, -44, 20, 178, -40, 19, 179, -36, 19, 180, -32, 18, + 181, -27, 17, 182, -23, 17, 182, -19, 16, 183, -15, 15, + 184, -11, 15, 185, -7, 14, 186, -3, 13, 187, 0, 13, + 188, 4, 12, 189, 9, 11, 190, 13, 11, 191, 17, 10, + 192, 21, 9, 193, 25, 9, 194, 29, 8, 195, 33, 7, + 168, -95, 26, 169, -91, 26, 170, -87, 25, 171, -82, 24, + 172, -78, 24, 173, -74, 23, 174, -70, 22, 175, -66, 22, + 175, -62, 21, 176, -58, 20, 177, -54, 20, 178, -50, 19, + 179, -45, 18, 180, -41, 18, 181, -37, 17, 182, -33, 16, + 183, -29, 16, 184, -25, 15, 185, -21, 14, 186, -17, 14, + 187, -12, 13, 188, -8, 12, 189, -4, 12, 189, 0, 11, + 190, 3, 10, 191, 7, 10, 192, 11, 9, 193, 15, 8, + 194, 19, 8, 195, 24, 7, 196, 28, 6, 197, 32, 6, + 170, -96, 25, 171, -92, 24, 172, -88, 23, 173, -83, 23, + 174, -79, 22, 175, -75, 21, 176, -71, 21, 177, -67, 20, + 178, -63, 19, 179, -59, 19, 180, -55, 18, 181, -51, 17, + 182, -46, 17, 182, -42, 16, 183, -38, 15, 184, -34, 15, + 185, -30, 14, 186, -26, 13, 187, -22, 13, 188, -18, 12, + 189, -13, 11, 190, -9, 11, 191, -5, 10, 192, -1, 9, + 193, 2, 9, 194, 6, 8, 195, 10, 7, 196, 14, 7, + 196, 18, 6, 198, 23, 5, 198, 27, 5, 199, 31, 4, + 173, -97, 23, 174, -93, 22, 174, -89, 22, 175, -85, 21, + 176, -81, 20, 177, -77, 20, 178, -73, 19, 179, -68, 18, + 180, -64, 18, 181, -60, 17, 182, -56, 16, 183, -52, 16, + 184, -48, 15, 185, -44, 14, 186, -40, 14, 187, -36, 13, + 188, -31, 12, 189, -27, 12, 189, -23, 11, 190, -19, 10, + 191, -15, 10, 192, -11, 9, 193, -7, 8, 194, -3, 8, + 195, 0, 7, 196, 5, 6, 197, 9, 6, 198, 13, 5, + 199, 17, 4, 200, 21, 4, 201, 25, 3, 202, 29, 2, + 175, -99, 21, 176, -95, 21, 177, -91, 20, 178, -86, 19, + 179, -82, 19, 180, -78, 18, 181, -74, 17, 182, -70, 17, + 182, -66, 16, 183, -62, 15, 184, -58, 15, 185, -54, 14, + 186, -49, 13, 187, -45, 13, 188, -41, 12, 189, -37, 11, + 190, -33, 11, 191, -29, 10, 192, -25, 9, 193, -21, 9, + 194, -16, 8, 195, -12, 7, 196, -8, 7, 197, -4, 6, + 197, 0, 5, 198, 3, 5, 199, 7, 4, 200, 11, 3, + 201, 15, 3, 202, 20, 2, 203, 24, 1, 204, 28, 1, + 177, -100, 20, 178, -96, 19, 179, -92, 18, 180, -87, 18, + 181, -83, 17, 182, -79, 16, 183, -75, 16, 184, -71, 15, + 185, -67, 14, 186, -63, 14, 187, -59, 13, 188, -55, 12, + 189, -50, 12, 190, -46, 11, 190, -42, 10, 191, -38, 10, + 192, -34, 9, 193, -30, 8, 194, -26, 8, 195, -22, 7, + 196, -17, 6, 197, -13, 6, 198, -9, 5, 199, -5, 4, + 200, -1, 4, 201, 2, 3, 202, 6, 2, 203, 10, 2, + 204, 14, 1, 205, 19, 0, 205, 23, 0, 206, 27, 0, + 180, -101, 18, 181, -97, 17, 181, -93, 17, 183, -89, 16, + 183, -85, 15, 184, -81, 15, 185, -77, 14, 186, -72, 13, + 187, -68, 13, 188, -64, 12, 189, -60, 11, 190, -56, 11, + 191, -52, 10, 192, -48, 9, 193, -44, 9, 194, -40, 8, + 195, -35, 7, 196, -31, 7, 197, -27, 6, 197, -23, 5, + 198, -19, 5, 199, -15, 4, 200, -11, 3, 201, -7, 3, + 202, -3, 2, 203, 1, 1, 204, 5, 1, 205, 9, 0, + 206, 13, 0, 207, 17, -1, 208, 21, -1, 209, 25, -2, + 182, -103, 16, 183, -99, 16, 184, -95, 15, 185, -90, 14, + 186, -86, 14, 187, -82, 13, 188, -78, 12, 189, -74, 12, + 190, -70, 11, 190, -66, 10, 191, -62, 10, 192, -58, 9, + 193, -53, 8, 194, -49, 8, 195, -45, 7, 196, -41, 6, + 197, -37, 6, 198, -33, 5, 199, -29, 4, 200, -25, 4, + 201, -20, 3, 202, -16, 2, 203, -12, 2, 204, -8, 1, + 204, -4, 0, 206, 0, 0, 206, 4, 0, 207, 8, -1, + 208, 12, -1, 209, 16, -2, 210, 20, -3, 211, 24, -3, + 184, -104, 15, 185, -100, 14, 186, -96, 13, 187, -91, 13, + 188, -87, 12, 189, -83, 11, 190, -79, 11, 191, -75, 10, + 192, -71, 9, 193, -67, 9, 194, -63, 8, 195, -59, 7, + 196, -54, 7, 197, -50, 6, 197, -46, 5, 198, -42, 5, + 199, -38, 4, 200, -34, 3, 201, -30, 3, 202, -26, 2, + 203, -21, 1, 204, -17, 1, 205, -13, 0, 206, -9, 0, + 207, -5, 0, 208, -1, -1, 209, 2, -2, 210, 6, -2, + 211, 10, -3, 212, 15, -4, 213, 19, -5, 213, 23, -5, + 187, -105, 12, 188, -101, 12, 189, -97, 11, 190, -93, 10, + 191, -89, 10, 192, -85, 9, 193, -81, 8, 194, -76, 8, + 195, -72, 7, 196, -68, 6, 197, -64, 6, 198, -60, 5, + 199, -56, 4, 199, -52, 4, 200, -48, 3, 201, -44, 2, + 202, -39, 2, 203, -35, 1, 204, -31, 0, 205, -27, 0, + 206, -23, 0, 207, -19, -1, 208, -15, -1, 209, -11, -2, + 210, -7, -3, 211, -2, -3, 212, 1, -4, 213, 5, -5, + 214, 9, -5, 215, 13, -6, 215, 17, -7, 216, 21, -7, + 190, -107, 11, 191, -103, 10, 191, -99, 10, 192, -94, 9, + 193, -90, 8, 194, -86, 7, 195, -82, 7, 196, -78, 6, + 197, -74, 5, 198, -70, 5, 199, -66, 4, 200, -62, 3, + 201, -57, 3, 202, -53, 2, 203, -49, 1, 204, -45, 1, + 205, -41, 0, 206, -37, 0, 207, -33, 0, 207, -29, -1, + 208, -24, -2, 209, -20, -2, 210, -16, -3, 211, -12, -4, + 212, -8, -4, 213, -4, -5, 214, 0, -6, 215, 3, -6, + 216, 7, -7, 217, 12, -8, 218, 16, -8, 219, 20, -9, + 192, -108, 9, 193, -104, 8, 194, -100, 8, 195, -96, 7, + 196, -92, 6, 197, -88, 6, 198, -84, 5, 199, -79, 4, + 200, -75, 4, 200, -71, 3, 201, -67, 2, 202, -63, 2, + 203, -59, 1, 204, -55, 0, 205, -51, 0, 206, -47, 0, + 207, -42, -1, 208, -38, -1, 209, -34, -2, 210, -30, -3, + 211, -26, -3, 212, -22, -4, 213, -18, -5, 214, -14, -5, + 214, -10, -6, 215, -5, -7, 216, -1, -7, 217, 2, -8, + 218, 6, -9, 219, 10, -9, 220, 14, -10, 221, 18, -11, + 194, -109, 7, 195, -105, 7, 196, -101, 6, 197, -97, 5, + 198, -93, 5, 199, -89, 4, 200, -85, 3, 201, -80, 3, + 202, -76, 2, 203, -72, 1, 204, -68, 1, 205, -64, 0, + 206, -60, 0, 207, -56, 0, 207, -52, -1, 208, -48, -2, + 209, -43, -2, 210, -39, -3, 211, -35, -4, 212, -31, -4, + 213, -27, -5, 214, -23, -6, 215, -19, -6, 216, -15, -7, + 217, -11, -8, 218, -6, -8, 219, -2, -9, 220, 1, -10, + 221, 5, -10, 222, 9, -11, 223, 13, -12, 223, 17, -12, + 197, -111, 6, 198, -107, 5, 199, -103, 4, 200, -98, 4, + 200, -94, 3, 201, -90, 2, 202, -86, 2, 203, -82, 1, + 204, -78, 0, 205, -74, 0, 206, -70, 0, 207, -66, -1, + 208, -61, -1, 209, -57, -2, 210, -53, -3, 211, -49, -3, + 212, -45, -4, 213, -41, -5, 214, -37, -5, 214, -33, -6, + 216, -28, -7, 216, -24, -7, 217, -20, -8, 218, -16, -9, + 219, -12, -9, 220, -8, -10, 221, -4, -11, 222, 0, -11, + 223, 3, -12, 224, 8, -13, 225, 12, -13, 226, 16, -14, + 199, -112, 4, 200, -108, 3, 201, -104, 3, 202, -100, 2, + 203, -96, 1, 204, -92, 1, 205, -88, 0, 206, -83, 0, + 207, -79, 0, 207, -75, -1, 208, -71, -2, 209, -67, -2, + 210, -63, -3, 211, -59, -4, 212, -55, -4, 213, -51, -5, + 214, -46, -6, 215, -42, -6, 216, -38, -7, 217, -34, -8, + 218, -30, -8, 219, -26, -9, 220, -22, -10, 221, -18, -10, + 221, -14, -11, 223, -9, -12, 223, -5, -12, 224, -1, -13, + 225, 2, -14, 226, 6, -14, 227, 10, -15, 228, 14, -16, + 201, -113, 2, 202, -109, 2, 203, -105, 1, 204, -101, 0, + 205, -97, 0, 206, -93, 0, 207, -89, -1, 208, -84, -1, + 209, -80, -2, 210, -76, -3, 211, -72, -3, 212, -68, -4, + 213, -64, -5, 214, -60, -5, 214, -56, -6, 215, -52, -7, + 216, -47, -7, 217, -43, -8, 218, -39, -9, 219, -35, -9, + 220, -31, -10, 221, -27, -11, 222, -23, -11, 223, -19, -12, + 224, -15, -13, 225, -10, -13, 226, -6, -14, 227, -2, -15, + 228, 1, -15, 229, 5, -16, 230, 9, -17, 230, 13, -17, + 204, -115, 1, 205, -111, 0, 206, -107, 0, 207, -102, 0, + 207, -98, -1, 208, -94, -2, 209, -90, -2, 210, -86, -3, + 211, -82, -4, 212, -78, -4, 213, -74, -5, 214, -70, -6, + 215, -65, -6, 216, -61, -7, 217, -57, -8, 218, -53, -8, + 219, -49, -9, 220, -45, -10, 221, -41, -10, 222, -37, -11, + 223, -32, -12, 223, -28, -12, 224, -24, -13, 225, -20, -14, + 226, -16, -14, 227, -12, -15, 228, -8, -16, 229, -4, -16, + 230, 0, -17, 231, 4, -18, 232, 8, -18, 233, 12, -19, + 206, -116, 0, 207, -112, -1, 208, -108, -1, 209, -104, -2, + 210, -100, -3, 211, -96, -3, 212, -92, -4, 213, -87, -5, + 214, -83, -5, 215, -79, -6, 215, -75, -7, 216, -71, -7, + 217, -67, -8, 218, -63, -9, 219, -59, -9, 220, -55, -10, + 221, -50, -11, 222, -46, -11, 223, -42, -12, 224, -38, -13, + 225, -34, -13, 226, -30, -14, 227, -26, -15, 228, -22, -15, + 229, -18, -16, 230, -13, -17, 230, -9, -17, 231, -5, -18, + 232, -1, -19, 233, 2, -19, 234, 6, -20, 235, 10, -21, + 208, -117, -2, 209, -113, -2, 210, -109, -3, 211, -105, -4, + 212, -101, -4, 213, -97, -5, 214, -93, -6, 215, -88, -6, + 216, -84, -7, 217, -80, -8, 218, -76, -8, 219, -72, -9, + 220, -68, -10, 221, -64, -10, 222, -60, -11, 222, -56, -12, + 223, -51, -12, 224, -47, -13, 225, -43, -14, 226, -39, -14, + 227, -35, -15, 228, -31, -16, 229, -27, -16, 230, -23, -17, + 231, -19, -18, 232, -14, -18, 233, -10, -19, 234, -6, -20, + 235, -2, -20, 236, 1, -21, 237, 5, -22, 238, 9, -22, + 211, -119, -3, 212, -115, -4, 213, -111, -5, 214, -106, -5, + 215, -102, -6, 215, -98, -7, 216, -94, -7, 217, -90, -8, + 218, -86, -9, 219, -82, -9, 220, -78, -10, 221, -74, -11, + 222, -69, -11, 223, -65, -12, 224, -61, -13, 225, -57, -13, + 226, -53, -14, 227, -49, -15, 228, -45, -15, 229, -41, -16, + 230, -36, -17, 231, -32, -17, 231, -28, -18, 232, -24, -19, + 233, -20, -19, 234, -16, -20, 235, -12, -21, 236, -8, -21, + 237, -4, -22, 238, 0, -23, 239, 4, -23, 240, 8, -24, + 63, -36, 107, 64, -32, 106, 65, -28, 105, 66, -23, 104, + 67, -19, 104, 68, -15, 103, 69, -11, 103, 70, -7, 102, + 71, -3, 101, 72, 0, 100, 73, 4, 100, 74, 8, 99, + 75, 13, 98, 76, 17, 98, 77, 21, 97, 78, 25, 96, + 79, 29, 96, 79, 33, 95, 80, 37, 94, 81, 41, 94, + 82, 46, 93, 83, 50, 92, 84, 54, 92, 85, 58, 91, + 86, 62, 90, 87, 66, 90, 88, 70, 89, 89, 74, 88, + 90, 78, 88, 91, 83, 87, 92, 87, 86, 93, 91, 86, + 66, -37, 105, 67, -33, 104, 68, -29, 104, 69, -24, 103, + 70, -20, 102, 71, -16, 101, 71, -12, 101, 72, -8, 100, + 73, -4, 99, 74, 0, 99, 75, 3, 98, 76, 7, 98, + 77, 12, 97, 78, 16, 96, 79, 20, 95, 80, 24, 95, + 81, 28, 94, 82, 32, 93, 83, 36, 93, 84, 40, 92, + 85, 45, 91, 86, 49, 91, 86, 53, 90, 87, 57, 89, + 88, 61, 89, 89, 65, 88, 90, 69, 87, 91, 73, 87, + 92, 77, 86, 93, 82, 85, 94, 86, 85, 95, 90, 84, + 68, -38, 103, 69, -34, 103, 70, -30, 102, 71, -26, 101, + 72, -22, 100, 73, -18, 100, 74, -14, 99, 75, -9, 98, + 76, -5, 98, 77, -1, 97, 78, 2, 96, 78, 6, 96, + 79, 10, 95, 80, 14, 94, 81, 18, 94, 82, 22, 93, + 83, 27, 92, 84, 31, 92, 85, 35, 91, 86, 39, 90, + 87, 43, 90, 88, 47, 89, 89, 51, 88, 90, 55, 88, + 91, 59, 87, 92, 64, 86, 93, 68, 86, 93, 72, 85, + 94, 76, 84, 95, 80, 84, 96, 84, 83, 97, 88, 82, + 71, -40, 101, 71, -36, 101, 72, -32, 100, 73, -27, 99, + 74, -23, 99, 75, -19, 98, 76, -15, 97, 77, -11, 97, + 78, -7, 96, 79, -3, 95, 80, 0, 95, 81, 4, 94, + 82, 9, 93, 83, 13, 93, 84, 17, 92, 85, 21, 91, + 86, 25, 91, 86, 29, 90, 87, 33, 89, 88, 37, 89, + 89, 42, 88, 90, 46, 87, 91, 50, 87, 92, 54, 86, + 93, 58, 85, 94, 62, 85, 95, 66, 84, 96, 70, 83, + 97, 74, 83, 98, 79, 82, 99, 83, 81, 100, 87, 81, + 73, -41, 100, 74, -37, 99, 75, -33, 99, 76, -28, 98, + 77, -24, 97, 78, -20, 96, 78, -16, 96, 79, -12, 95, + 80, -8, 94, 81, -4, 94, 82, 0, 93, 83, 3, 92, + 84, 8, 92, 85, 12, 91, 86, 16, 90, 87, 20, 90, + 88, 24, 89, 89, 28, 88, 90, 32, 88, 91, 36, 87, + 92, 41, 86, 93, 45, 86, 94, 49, 85, 94, 53, 84, + 95, 57, 84, 96, 61, 83, 97, 65, 82, 98, 69, 82, + 99, 73, 81, 100, 78, 80, 101, 82, 80, 102, 86, 79, + 75, -42, 98, 76, -38, 97, 77, -34, 97, 78, -30, 96, + 79, -26, 95, 80, -22, 95, 81, -18, 94, 82, -13, 93, + 83, -9, 93, 84, -5, 92, 85, -1, 91, 85, 2, 91, + 87, 6, 90, 87, 10, 89, 88, 14, 89, 89, 18, 88, + 90, 23, 87, 91, 27, 87, 92, 31, 86, 93, 35, 85, + 94, 39, 85, 95, 43, 84, 96, 47, 83, 97, 51, 83, + 98, 55, 82, 99, 60, 81, 100, 64, 81, 101, 68, 80, + 101, 72, 79, 102, 76, 79, 103, 80, 78, 104, 84, 77, + 78, -44, 96, 78, -40, 96, 79, -36, 95, 80, -31, 94, + 81, -27, 94, 82, -23, 93, 83, -19, 92, 84, -15, 92, + 85, -11, 91, 86, -7, 90, 87, -3, 90, 88, 0, 89, + 89, 5, 88, 90, 9, 88, 91, 13, 87, 92, 17, 86, + 93, 21, 86, 94, 25, 85, 94, 29, 84, 95, 33, 84, + 96, 38, 83, 97, 42, 82, 98, 46, 82, 99, 50, 81, + 100, 54, 80, 101, 58, 80, 102, 62, 79, 103, 66, 78, + 104, 70, 78, 105, 75, 77, 106, 79, 76, 107, 83, 76, + 80, -45, 95, 81, -41, 94, 82, -37, 93, 83, -32, 93, + 84, -28, 92, 85, -24, 91, 86, -20, 91, 87, -16, 90, + 87, -12, 89, 88, -8, 89, 89, -4, 88, 90, 0, 87, + 91, 4, 87, 92, 8, 86, 93, 12, 85, 94, 16, 85, + 95, 20, 84, 96, 24, 83, 97, 28, 83, 98, 32, 82, + 99, 37, 81, 100, 41, 81, 101, 45, 80, 101, 49, 79, + 102, 53, 79, 103, 57, 78, 104, 61, 77, 105, 65, 77, + 106, 69, 76, 107, 74, 75, 108, 78, 75, 109, 82, 74, + 82, -46, 93, 83, -42, 92, 84, -38, 92, 85, -34, 91, + 86, -30, 90, 87, -26, 90, 88, -22, 89, 89, -17, 88, + 90, -13, 88, 91, -9, 87, 92, -5, 86, 93, -1, 86, + 94, 2, 85, 94, 6, 84, 95, 10, 84, 96, 14, 83, + 97, 19, 82, 98, 23, 82, 99, 27, 81, 100, 31, 80, + 101, 35, 80, 102, 39, 79, 103, 43, 78, 104, 47, 78, + 105, 51, 77, 106, 56, 76, 107, 60, 76, 108, 64, 75, + 108, 68, 74, 110, 72, 74, 110, 76, 73, 111, 80, 72, + 85, -48, 91, 86, -44, 91, 86, -40, 90, 87, -35, 89, + 88, -31, 89, 89, -27, 88, 90, -23, 87, 91, -19, 87, + 92, -15, 86, 93, -11, 85, 94, -7, 85, 95, -3, 84, + 96, 1, 83, 97, 5, 83, 98, 9, 82, 99, 13, 81, + 100, 17, 81, 101, 21, 80, 101, 25, 79, 102, 29, 79, + 103, 34, 78, 104, 38, 77, 105, 42, 77, 106, 46, 76, + 107, 50, 75, 108, 54, 75, 109, 58, 74, 110, 62, 73, + 111, 66, 73, 112, 71, 72, 113, 75, 71, 114, 79, 71, + 87, -49, 90, 88, -45, 89, 89, -41, 88, 90, -36, 88, + 91, -32, 87, 92, -28, 86, 93, -24, 86, 94, -20, 85, + 94, -16, 84, 95, -12, 84, 96, -8, 83, 97, -4, 82, + 98, 0, 82, 99, 4, 81, 100, 8, 80, 101, 12, 80, + 102, 16, 79, 103, 20, 78, 104, 24, 78, 105, 28, 77, + 106, 33, 76, 107, 37, 76, 108, 41, 75, 109, 45, 74, + 109, 49, 74, 110, 53, 73, 111, 57, 72, 112, 61, 72, + 113, 65, 71, 114, 70, 70, 115, 74, 70, 116, 78, 69, + 90, -51, 88, 91, -47, 87, 92, -43, 86, 93, -38, 86, + 94, -34, 85, 95, -30, 84, 95, -26, 84, 97, -22, 83, + 97, -18, 82, 98, -14, 82, 99, -10, 81, 100, -6, 80, + 101, -1, 80, 102, 2, 79, 103, 6, 78, 104, 10, 78, + 105, 14, 77, 106, 18, 76, 107, 22, 76, 108, 26, 75, + 109, 31, 74, 110, 35, 74, 111, 39, 73, 111, 43, 72, + 112, 47, 72, 113, 51, 71, 114, 55, 70, 115, 59, 70, + 116, 63, 69, 117, 68, 68, 118, 72, 68, 119, 76, 67, + 92, -52, 86, 93, -48, 85, 94, -44, 85, 95, -39, 84, + 96, -35, 83, 97, -31, 83, 98, -27, 82, 99, -23, 81, + 100, -19, 81, 101, -15, 80, 102, -11, 79, 103, -7, 79, + 104, -2, 78, 104, 1, 77, 105, 5, 77, 106, 9, 76, + 107, 13, 75, 108, 17, 75, 109, 21, 74, 110, 25, 73, + 111, 30, 73, 112, 34, 72, 113, 38, 71, 114, 42, 71, + 115, 46, 70, 116, 50, 69, 117, 54, 69, 118, 58, 68, + 118, 62, 67, 119, 67, 67, 120, 71, 66, 121, 75, 65, + 95, -53, 84, 96, -49, 84, 96, -45, 83, 97, -41, 82, + 98, -37, 82, 99, -33, 81, 100, -29, 80, 101, -24, 80, + 102, -20, 79, 103, -16, 78, 104, -12, 78, 105, -8, 77, + 106, -4, 76, 107, 0, 76, 108, 3, 75, 109, 7, 74, + 110, 12, 74, 111, 16, 73, 111, 20, 72, 112, 24, 72, + 113, 28, 71, 114, 32, 70, 115, 36, 70, 116, 40, 69, + 117, 44, 68, 118, 49, 68, 119, 53, 67, 120, 57, 66, + 121, 61, 66, 122, 65, 65, 123, 69, 64, 124, 73, 64, + 97, -54, 83, 98, -50, 82, 99, -46, 81, 100, -42, 81, + 101, -38, 80, 102, -34, 79, 103, -30, 79, 104, -25, 78, + 104, -21, 77, 105, -17, 77, 106, -13, 76, 107, -9, 75, + 108, -5, 75, 109, -1, 74, 110, 2, 73, 111, 6, 73, + 112, 11, 72, 113, 15, 71, 114, 19, 71, 115, 23, 70, + 116, 27, 69, 117, 31, 69, 118, 35, 68, 118, 39, 67, + 119, 43, 67, 120, 48, 66, 121, 52, 65, 122, 56, 65, + 123, 60, 64, 124, 64, 63, 125, 68, 63, 126, 72, 62, + 99, -56, 81, 100, -52, 80, 101, -48, 80, 102, -43, 79, + 103, -39, 78, 104, -35, 78, 105, -31, 77, 106, -27, 76, + 107, -23, 76, 108, -19, 75, 109, -15, 74, 110, -11, 74, + 111, -6, 73, 111, -2, 72, 112, 1, 72, 113, 5, 71, + 114, 9, 70, 115, 13, 70, 116, 17, 69, 117, 21, 68, + 118, 26, 68, 119, 30, 67, 120, 34, 66, 121, 38, 66, + 122, 42, 65, 123, 46, 64, 124, 50, 64, 125, 54, 63, + 126, 58, 62, 127, 63, 62, 127, 67, 61, 128, 71, 60, + 102, -57, 79, 103, -53, 79, 103, -49, 78, 104, -45, 77, + 105, -41, 77, 106, -37, 76, 107, -33, 75, 108, -28, 75, + 109, -24, 74, 110, -20, 73, 111, -16, 73, 112, -12, 72, + 113, -8, 71, 114, -4, 71, 115, 0, 70, 116, 3, 69, + 117, 8, 69, 118, 12, 68, 119, 16, 67, 119, 20, 67, + 120, 24, 66, 121, 28, 65, 122, 32, 65, 123, 36, 64, + 124, 40, 63, 125, 45, 63, 126, 49, 62, 127, 53, 61, + 128, 57, 61, 129, 61, 60, 130, 65, 59, 131, 69, 59, + 104, -58, 78, 105, -54, 77, 106, -50, 76, 107, -46, 76, + 108, -42, 75, 109, -38, 74, 110, -34, 74, 111, -29, 73, + 112, -25, 72, 112, -21, 72, 113, -17, 71, 114, -13, 70, + 115, -9, 70, 116, -5, 69, 117, -1, 68, 118, 2, 68, + 119, 7, 67, 120, 11, 66, 121, 15, 66, 122, 19, 65, + 123, 23, 64, 124, 27, 64, 125, 31, 63, 126, 35, 62, + 126, 39, 62, 127, 44, 61, 128, 48, 60, 129, 52, 60, + 130, 56, 59, 131, 60, 58, 132, 64, 58, 133, 68, 57, + 106, -60, 76, 107, -56, 75, 108, -52, 75, 109, -47, 74, + 110, -43, 73, 111, -39, 73, 112, -35, 72, 113, -31, 71, + 114, -27, 71, 115, -23, 70, 116, -19, 69, 117, -15, 69, + 118, -10, 68, 119, -6, 67, 119, -2, 67, 120, 1, 66, + 121, 5, 65, 122, 9, 65, 123, 13, 64, 124, 17, 63, + 125, 22, 63, 126, 26, 62, 127, 30, 61, 128, 34, 61, + 129, 38, 60, 130, 42, 59, 131, 46, 59, 132, 50, 58, + 133, 54, 57, 134, 59, 57, 134, 63, 56, 135, 67, 55, + 109, -61, 74, 110, -57, 74, 111, -53, 73, 112, -49, 72, + 112, -45, 72, 113, -41, 71, 114, -37, 70, 115, -32, 70, + 116, -28, 69, 117, -24, 68, 118, -20, 68, 119, -16, 67, + 120, -12, 66, 121, -8, 66, 122, -4, 65, 123, 0, 64, + 124, 4, 64, 125, 8, 63, 126, 12, 62, 126, 16, 62, + 127, 20, 61, 128, 24, 60, 129, 28, 60, 130, 32, 59, + 131, 36, 58, 132, 41, 58, 133, 45, 57, 134, 49, 56, + 135, 53, 56, 136, 57, 55, 137, 61, 54, 138, 65, 54, + 111, -62, 73, 112, -58, 72, 113, -54, 71, 114, -50, 71, + 115, -46, 70, 116, -42, 69, 117, -38, 69, 118, -33, 68, + 119, -29, 67, 119, -25, 67, 120, -21, 66, 121, -17, 65, + 122, -13, 65, 123, -9, 64, 124, -5, 63, 125, -1, 63, + 126, 3, 62, 127, 7, 61, 128, 11, 61, 129, 15, 60, + 130, 19, 59, 131, 23, 59, 132, 27, 58, 133, 31, 57, + 133, 35, 57, 135, 40, 56, 135, 44, 55, 136, 48, 55, + 137, 52, 54, 138, 56, 53, 139, 60, 53, 140, 64, 52, + 113, -64, 71, 114, -60, 70, 115, -56, 70, 116, -51, 69, + 117, -47, 68, 118, -43, 68, 119, -39, 67, 120, -35, 66, + 121, -31, 66, 122, -27, 65, 123, -23, 64, 124, -19, 64, + 125, -14, 63, 126, -10, 62, 126, -6, 62, 127, -2, 61, + 128, 1, 60, 129, 5, 60, 130, 9, 59, 131, 13, 58, + 132, 18, 57, 133, 22, 57, 134, 26, 56, 135, 30, 56, + 136, 34, 55, 137, 38, 54, 138, 42, 54, 139, 46, 53, + 140, 50, 52, 141, 55, 51, 142, 59, 51, 142, 63, 50, + 116, -65, 69, 117, -61, 69, 118, -57, 68, 119, -53, 67, + 119, -49, 67, 120, -45, 66, 121, -41, 65, 122, -36, 65, + 123, -32, 64, 124, -28, 63, 125, -24, 63, 126, -20, 62, + 127, -16, 61, 128, -12, 61, 129, -8, 60, 130, -4, 59, + 131, 0, 59, 132, 4, 58, 133, 8, 57, 134, 12, 57, + 135, 16, 56, 135, 20, 55, 136, 24, 55, 137, 28, 54, + 138, 32, 53, 139, 37, 52, 140, 41, 52, 141, 45, 51, + 142, 49, 51, 143, 53, 50, 144, 57, 49, 145, 61, 49, + 118, -66, 68, 119, -62, 67, 120, -58, 66, 121, -54, 66, + 122, -50, 65, 123, -46, 64, 124, -42, 64, 125, -37, 63, + 126, -33, 62, 127, -29, 62, 127, -25, 61, 128, -21, 60, + 129, -17, 60, 130, -13, 59, 131, -9, 58, 132, -5, 58, + 133, 0, 57, 134, 3, 56, 135, 7, 56, 136, 11, 55, + 137, 15, 54, 138, 19, 53, 139, 23, 53, 140, 27, 52, + 141, 31, 52, 142, 36, 51, 142, 40, 50, 143, 44, 50, + 144, 48, 49, 145, 52, 48, 146, 56, 47, 147, 60, 47, + 120, -68, 66, 121, -64, 65, 122, -60, 65, 123, -55, 64, + 124, -51, 63, 125, -47, 63, 126, -43, 62, 127, -39, 61, + 128, -35, 61, 129, -31, 60, 130, -27, 59, 131, -23, 59, + 132, -18, 58, 133, -14, 57, 134, -10, 57, 134, -6, 56, + 135, -2, 55, 136, 1, 55, 137, 5, 54, 138, 9, 53, + 139, 14, 52, 140, 18, 52, 141, 22, 51, 142, 26, 51, + 143, 30, 50, 144, 34, 49, 145, 38, 48, 146, 42, 48, + 147, 46, 47, 148, 51, 46, 149, 55, 46, 149, 59, 45, + 123, -69, 64, 124, -65, 64, 125, -61, 63, 126, -57, 62, + 127, -53, 62, 127, -49, 61, 128, -45, 60, 129, -40, 59, + 130, -36, 59, 131, -32, 58, 132, -28, 58, 133, -24, 57, + 134, -20, 56, 135, -16, 56, 136, -12, 55, 137, -8, 54, + 138, -3, 53, 139, 0, 53, 140, 4, 52, 141, 8, 52, + 142, 12, 51, 142, 16, 50, 143, 20, 49, 144, 24, 49, + 145, 28, 48, 146, 33, 47, 147, 37, 47, 148, 41, 46, + 149, 45, 46, 150, 49, 45, 151, 53, 44, 152, 57, 43, + 125, -70, 63, 126, -66, 62, 127, -62, 61, 128, -58, 61, + 129, -54, 60, 130, -50, 59, 131, -46, 59, 132, -41, 58, + 133, -37, 57, 134, -33, 57, 134, -29, 56, 135, -25, 55, + 136, -21, 54, 137, -17, 54, 138, -13, 53, 139, -9, 53, + 140, -4, 52, 141, 0, 51, 142, 3, 51, 143, 7, 50, + 144, 11, 49, 145, 15, 48, 146, 19, 48, 147, 23, 47, + 148, 27, 47, 149, 32, 46, 150, 36, 45, 150, 40, 44, + 151, 44, 44, 152, 48, 43, 153, 52, 42, 154, 56, 42, + 127, -72, 61, 128, -68, 60, 129, -64, 60, 130, -59, 59, + 131, -55, 58, 132, -51, 58, 133, -47, 57, 134, -43, 56, + 135, -39, 55, 136, -35, 55, 137, -31, 54, 138, -27, 54, + 139, -22, 53, 140, -18, 52, 141, -14, 52, 141, -10, 51, + 143, -6, 50, 143, -2, 49, 144, 1, 49, 145, 5, 48, + 146, 10, 47, 147, 14, 47, 148, 18, 46, 149, 22, 45, + 150, 26, 45, 151, 30, 44, 152, 34, 43, 153, 38, 43, + 154, 42, 42, 155, 47, 41, 156, 51, 41, 157, 55, 40, + 130, -73, 59, 131, -69, 59, 132, -65, 58, 133, -61, 57, + 134, -57, 57, 134, -53, 56, 135, -49, 55, 136, -44, 54, + 137, -40, 54, 138, -36, 53, 139, -32, 53, 140, -28, 52, + 141, -24, 51, 142, -20, 50, 143, -16, 50, 144, -12, 49, + 145, -7, 48, 146, -3, 48, 147, 0, 47, 148, 4, 47, + 149, 8, 46, 150, 12, 45, 150, 16, 44, 151, 20, 44, + 152, 24, 43, 153, 29, 42, 154, 33, 42, 155, 37, 41, + 156, 41, 40, 157, 45, 40, 158, 49, 39, 159, 53, 38, + 132, -74, 58, 133, -70, 57, 134, -66, 56, 135, -62, 55, + 136, -58, 55, 137, -54, 54, 138, -50, 54, 139, -45, 53, + 140, -41, 52, 141, -37, 51, 142, -33, 51, 142, -29, 50, + 143, -25, 49, 144, -21, 49, 145, -17, 48, 146, -13, 48, + 147, -8, 47, 148, -4, 46, 149, 0, 45, 150, 3, 45, + 151, 7, 44, 152, 11, 43, 153, 15, 43, 154, 19, 42, + 155, 23, 41, 156, 28, 41, 157, 32, 40, 157, 36, 39, + 158, 40, 39, 159, 44, 38, 160, 48, 37, 161, 52, 37, + 135, -76, 56, 135, -72, 55, 136, -68, 55, 137, -63, 54, + 138, -59, 53, 139, -55, 53, 140, -51, 52, 141, -47, 51, + 142, -43, 50, 143, -39, 50, 144, -35, 49, 145, -31, 49, + 146, -26, 48, 147, -22, 47, 148, -18, 46, 149, -14, 46, + 150, -10, 45, 150, -6, 44, 151, -2, 44, 152, 1, 43, + 153, 6, 42, 154, 10, 42, 155, 14, 41, 156, 18, 40, + 157, 22, 40, 158, 26, 39, 159, 30, 38, 160, 34, 38, + 161, 38, 37, 162, 43, 36, 163, 47, 36, 164, 51, 35, + 137, -77, 54, 138, -73, 54, 139, -69, 53, 140, -65, 52, + 141, -61, 51, 142, -57, 51, 142, -53, 50, 143, -48, 49, + 144, -44, 49, 145, -40, 48, 146, -36, 47, 147, -32, 47, + 148, -28, 46, 149, -24, 45, 150, -20, 45, 151, -16, 44, + 152, -11, 43, 153, -7, 43, 154, -3, 42, 155, 0, 41, + 156, 4, 41, 157, 8, 40, 157, 12, 39, 158, 16, 39, + 159, 20, 38, 160, 25, 37, 161, 29, 37, 162, 33, 36, + 163, 37, 35, 164, 41, 35, 165, 45, 34, 166, 49, 33, + 140, -79, 52, 141, -75, 51, 142, -71, 51, 143, -66, 50, + 144, -62, 49, 144, -58, 49, 145, -54, 48, 146, -50, 47, + 147, -46, 47, 148, -42, 46, 149, -38, 45, 150, -34, 45, + 151, -29, 44, 152, -25, 43, 153, -21, 43, 154, -17, 42, + 155, -13, 41, 156, -9, 41, 157, -5, 40, 158, -1, 39, + 159, 3, 39, 160, 7, 38, 160, 11, 37, 161, 15, 37, + 162, 19, 36, 163, 23, 35, 164, 27, 35, 165, 31, 34, + 166, 35, 33, 167, 40, 33, 168, 44, 32, 169, 48, 31, + 142, -80, 50, 143, -76, 50, 144, -72, 49, 145, -67, 48, + 146, -63, 48, 147, -59, 47, 148, -55, 46, 149, -51, 46, + 150, -47, 45, 151, -43, 44, 151, -39, 44, 152, -35, 43, + 153, -30, 42, 154, -26, 42, 155, -22, 41, 156, -18, 40, + 157, -14, 40, 158, -10, 39, 159, -6, 38, 160, -2, 38, + 161, 2, 37, 162, 6, 36, 163, 10, 36, 164, 14, 35, + 165, 18, 34, 166, 22, 34, 167, 26, 33, 167, 30, 32, + 168, 34, 32, 169, 39, 31, 170, 43, 30, 171, 47, 30, + 144, -81, 49, 145, -77, 48, 146, -73, 47, 147, -69, 47, + 148, -65, 46, 149, -61, 45, 150, -57, 45, 151, -52, 44, + 152, -48, 43, 153, -44, 43, 154, -40, 42, 155, -36, 41, + 156, -32, 41, 157, -28, 40, 158, -24, 39, 159, -20, 39, + 160, -15, 38, 160, -11, 37, 161, -7, 37, 162, -3, 36, + 163, 0, 35, 164, 4, 35, 165, 8, 34, 166, 12, 33, + 167, 16, 33, 168, 21, 32, 169, 25, 31, 170, 29, 31, + 171, 33, 30, 172, 37, 29, 173, 41, 29, 174, 45, 28, + 147, -83, 47, 148, -79, 46, 149, -75, 46, 150, -70, 45, + 151, -66, 44, 152, -62, 44, 152, -58, 43, 153, -54, 42, + 154, -50, 42, 155, -46, 41, 156, -42, 40, 157, -38, 40, + 158, -33, 39, 159, -29, 38, 160, -25, 38, 161, -21, 37, + 162, -17, 36, 163, -13, 36, 164, -9, 35, 165, -5, 34, + 166, 0, 34, 167, 3, 33, 167, 7, 32, 168, 11, 32, + 169, 15, 31, 170, 19, 30, 171, 23, 30, 172, 27, 29, + 173, 31, 28, 174, 36, 28, 175, 40, 27, 176, 44, 26, + 149, -84, 45, 150, -80, 45, 151, -76, 44, 152, -71, 43, + 153, -67, 43, 154, -63, 42, 155, -59, 41, 156, -55, 41, + 157, -51, 40, 158, -47, 39, 159, -43, 39, 159, -39, 38, + 160, -34, 37, 161, -30, 37, 162, -26, 36, 163, -22, 35, + 164, -18, 35, 165, -14, 34, 166, -10, 33, 167, -6, 33, + 168, -1, 32, 169, 2, 31, 170, 6, 31, 171, 10, 30, + 172, 14, 29, 173, 18, 29, 174, 22, 28, 174, 26, 27, + 175, 30, 27, 176, 35, 26, 177, 39, 25, 178, 43, 25, + 152, -85, 44, 152, -81, 43, 153, -77, 42, 154, -73, 42, + 155, -69, 41, 156, -65, 40, 157, -61, 40, 158, -56, 39, + 159, -52, 38, 160, -48, 38, 161, -44, 37, 162, -40, 36, + 163, -36, 36, 164, -32, 35, 165, -28, 34, 166, -24, 34, + 167, -19, 33, 167, -15, 32, 168, -11, 32, 169, -7, 31, + 170, -3, 30, 171, 0, 30, 172, 4, 29, 173, 8, 28, + 174, 12, 28, 175, 17, 27, 176, 21, 26, 177, 25, 26, + 178, 29, 25, 179, 33, 24, 180, 37, 24, 181, 41, 23, + 154, -87, 42, 155, -83, 41, 156, -79, 41, 157, -74, 40, + 158, -70, 39, 159, -66, 39, 159, -62, 38, 160, -58, 37, + 161, -54, 37, 162, -50, 36, 163, -46, 35, 164, -42, 35, + 165, -37, 34, 166, -33, 33, 167, -29, 33, 168, -25, 32, + 169, -21, 31, 170, -17, 31, 171, -13, 30, 172, -9, 29, + 173, -4, 29, 174, 0, 28, 175, 3, 27, 175, 7, 27, + 176, 11, 26, 177, 15, 25, 178, 19, 25, 179, 23, 24, + 180, 27, 23, 181, 32, 23, 182, 36, 22, 183, 40, 21, + 156, -88, 40, 157, -84, 40, 158, -80, 39, 159, -75, 38, + 160, -71, 38, 161, -67, 37, 162, -63, 36, 163, -59, 36, + 164, -55, 35, 165, -51, 34, 166, -47, 34, 166, -43, 33, + 168, -38, 32, 168, -34, 32, 169, -30, 31, 170, -26, 30, + 171, -22, 30, 172, -18, 29, 173, -14, 28, 174, -10, 28, + 175, -5, 27, 176, -1, 26, 177, 2, 26, 178, 6, 25, + 179, 10, 24, 180, 14, 24, 181, 18, 23, 182, 22, 22, + 182, 26, 22, 183, 31, 21, 184, 35, 20, 185, 39, 20, + 159, -89, 39, 159, -85, 38, 160, -81, 37, 161, -77, 37, + 162, -73, 36, 163, -69, 35, 164, -65, 35, 165, -60, 34, + 166, -56, 33, 167, -52, 33, 168, -48, 32, 169, -44, 31, + 170, -40, 31, 171, -36, 30, 172, -32, 29, 173, -28, 29, + 174, -23, 28, 175, -19, 27, 175, -15, 27, 176, -11, 26, + 177, -7, 25, 178, -3, 25, 179, 0, 24, 180, 4, 23, + 181, 8, 23, 182, 13, 22, 183, 17, 21, 184, 21, 21, + 185, 25, 20, 186, 29, 19, 187, 33, 19, 188, 37, 18, + 161, -91, 37, 162, -87, 36, 163, -83, 36, 164, -78, 35, + 165, -74, 34, 166, -70, 34, 167, -66, 33, 168, -62, 32, + 168, -58, 32, 169, -54, 31, 170, -50, 30, 171, -46, 30, + 172, -41, 29, 173, -37, 28, 174, -33, 28, 175, -29, 27, + 176, -25, 26, 177, -21, 26, 178, -17, 25, 179, -13, 24, + 180, -8, 24, 181, -4, 23, 182, 0, 22, 182, 3, 22, + 183, 7, 21, 184, 11, 20, 185, 15, 20, 186, 19, 19, + 187, 23, 18, 188, 28, 18, 189, 32, 17, 190, 36, 16, + 163, -92, 35, 164, -88, 35, 165, -84, 34, 166, -79, 33, + 167, -75, 33, 168, -71, 32, 169, -67, 31, 170, -63, 31, + 171, -59, 30, 172, -55, 29, 173, -51, 29, 174, -47, 28, + 175, -42, 27, 175, -38, 27, 176, -34, 26, 177, -30, 25, + 178, -26, 25, 179, -22, 24, 180, -18, 23, 181, -14, 23, + 182, -9, 22, 183, -5, 21, 184, -1, 21, 185, 2, 20, + 186, 6, 19, 187, 10, 19, 188, 14, 18, 189, 18, 17, + 189, 22, 17, 191, 27, 16, 191, 31, 15, 192, 35, 15, + 166, -93, 34, 167, -89, 33, 167, -85, 32, 168, -81, 32, + 169, -77, 31, 170, -73, 30, 171, -69, 30, 172, -64, 29, + 173, -60, 28, 174, -56, 28, 175, -52, 27, 176, -48, 26, + 177, -44, 26, 178, -40, 25, 179, -36, 24, 180, -32, 24, + 181, -27, 23, 182, -23, 22, 182, -19, 22, 183, -15, 21, + 184, -11, 20, 185, -7, 20, 186, -3, 19, 187, 0, 18, + 188, 4, 18, 189, 9, 17, 190, 13, 16, 191, 17, 16, + 192, 21, 15, 193, 25, 14, 194, 29, 14, 195, 33, 13, + 168, -95, 32, 169, -91, 31, 170, -87, 31, 171, -82, 30, + 172, -78, 29, 173, -74, 29, 174, -70, 28, 175, -66, 27, + 175, -62, 27, 176, -58, 26, 177, -54, 25, 178, -50, 25, + 179, -45, 24, 180, -41, 23, 181, -37, 23, 182, -33, 22, + 183, -29, 21, 184, -25, 21, 185, -21, 20, 186, -17, 19, + 187, -12, 19, 188, -8, 18, 189, -4, 17, 190, 0, 17, + 190, 3, 16, 191, 7, 15, 192, 11, 15, 193, 15, 14, + 194, 19, 13, 195, 24, 13, 196, 28, 12, 197, 32, 11, + 170, -96, 30, 171, -92, 30, 172, -88, 29, 173, -83, 28, + 174, -79, 28, 175, -75, 27, 176, -71, 26, 177, -67, 26, + 178, -63, 25, 179, -59, 24, 180, -55, 24, 181, -51, 23, + 182, -46, 22, 183, -42, 22, 183, -38, 21, 184, -34, 20, + 185, -30, 20, 186, -26, 19, 187, -22, 18, 188, -18, 18, + 189, -13, 17, 190, -9, 16, 191, -5, 16, 192, -1, 15, + 193, 2, 14, 194, 6, 14, 195, 10, 13, 196, 14, 12, + 197, 18, 12, 198, 23, 11, 198, 27, 10, 199, 31, 10, + 173, -97, 29, 174, -93, 28, 174, -89, 27, 176, -85, 27, + 176, -81, 26, 177, -77, 25, 178, -73, 25, 179, -68, 24, + 180, -64, 23, 181, -60, 23, 182, -56, 22, 183, -52, 21, + 184, -48, 21, 185, -44, 20, 186, -40, 19, 187, -36, 19, + 188, -31, 18, 189, -27, 17, 190, -23, 17, 190, -19, 16, + 191, -15, 15, 192, -11, 15, 193, -7, 14, 194, -3, 13, + 195, 0, 13, 196, 5, 12, 197, 9, 11, 198, 13, 11, + 199, 17, 10, 200, 21, 9, 201, 25, 9, 202, 29, 8, + 175, -99, 27, 176, -95, 26, 177, -91, 26, 178, -86, 25, + 179, -82, 24, 180, -78, 24, 181, -74, 23, 182, -70, 22, + 183, -66, 22, 183, -62, 21, 184, -58, 20, 185, -54, 20, + 186, -49, 19, 187, -45, 18, 188, -41, 18, 189, -37, 17, + 190, -33, 16, 191, -29, 16, 192, -25, 15, 193, -21, 14, + 194, -16, 14, 195, -12, 13, 196, -8, 12, 197, -4, 12, + 197, 0, 11, 199, 3, 10, 199, 7, 10, 200, 11, 9, + 201, 15, 8, 202, 20, 8, 203, 24, 7, 204, 28, 6, + 177, -100, 25, 178, -96, 25, 179, -92, 24, 180, -87, 23, + 181, -83, 23, 182, -79, 22, 183, -75, 21, 184, -71, 21, + 185, -67, 20, 186, -63, 19, 187, -59, 19, 188, -55, 18, + 189, -50, 17, 190, -46, 17, 190, -42, 16, 191, -38, 15, + 192, -34, 15, 193, -30, 14, 194, -26, 13, 195, -22, 13, + 196, -17, 12, 197, -13, 11, 198, -9, 11, 199, -5, 10, + 200, -1, 9, 201, 2, 9, 202, 6, 8, 203, 10, 7, + 204, 14, 7, 205, 19, 6, 206, 23, 5, 206, 27, 5, + 180, -101, 24, 181, -97, 23, 182, -93, 22, 183, -89, 22, + 183, -85, 21, 184, -81, 20, 185, -77, 20, 186, -72, 19, + 187, -68, 18, 188, -64, 18, 189, -60, 17, 190, -56, 16, + 191, -52, 16, 192, -48, 15, 193, -44, 14, 194, -40, 14, + 195, -35, 13, 196, -31, 12, 197, -27, 12, 197, -23, 11, + 199, -19, 10, 199, -15, 10, 200, -11, 9, 201, -7, 8, + 202, -3, 8, 203, 1, 7, 204, 5, 6, 205, 9, 6, + 206, 13, 5, 207, 17, 4, 208, 21, 4, 209, 25, 3, + 182, -103, 22, 183, -99, 21, 184, -95, 21, 185, -90, 20, + 186, -86, 19, 187, -82, 19, 188, -78, 18, 189, -74, 17, + 190, -70, 17, 190, -66, 16, 191, -62, 15, 192, -58, 15, + 193, -53, 14, 194, -49, 13, 195, -45, 13, 196, -41, 12, + 197, -37, 11, 198, -33, 11, 199, -29, 10, 200, -25, 9, + 201, -20, 9, 202, -16, 8, 203, -12, 7, 204, -8, 7, + 205, -4, 6, 206, 0, 5, 206, 3, 5, 207, 7, 4, + 208, 11, 3, 209, 16, 2, 210, 20, 2, 211, 24, 1, + 184, -104, 20, 185, -100, 20, 186, -96, 19, 187, -91, 18, + 188, -87, 18, 189, -83, 17, 190, -79, 16, 191, -75, 16, + 192, -71, 15, 193, -67, 14, 194, -63, 14, 195, -59, 13, + 196, -54, 12, 197, -50, 12, 198, -46, 11, 198, -42, 10, + 199, -38, 10, 200, -34, 9, 201, -30, 8, 202, -26, 8, + 203, -21, 7, 204, -17, 6, 205, -13, 6, 206, -9, 5, + 207, -5, 4, 208, -1, 3, 209, 2, 3, 210, 6, 2, + 211, 10, 2, 212, 15, 1, 213, 19, 0, 213, 23, 0, + 187, -105, 19, 188, -101, 18, 189, -97, 17, 190, -93, 17, + 191, -89, 16, 191, -85, 15, 192, -81, 15, 193, -76, 14, + 194, -72, 13, 195, -68, 13, 196, -64, 12, 197, -60, 11, + 198, -56, 11, 199, -52, 10, 200, -48, 9, 201, -44, 9, + 202, -39, 8, 203, -35, 7, 204, -31, 7, 205, -27, 6, + 206, -23, 5, 206, -19, 5, 207, -15, 4, 208, -11, 3, + 209, -7, 3, 210, -2, 2, 211, 1, 1, 212, 5, 1, + 213, 9, 0, 214, 13, 0, 215, 17, -1, 216, 21, -1, + 190, -107, 16, 191, -103, 16, 192, -99, 15, 193, -94, 14, + 193, -90, 14, 194, -86, 13, 195, -82, 12, 196, -78, 12, + 197, -74, 11, 198, -70, 10, 199, -66, 10, 200, -62, 9, + 201, -57, 8, 202, -53, 8, 203, -49, 7, 204, -45, 6, + 205, -41, 6, 206, -37, 5, 207, -33, 4, 207, -29, 4, + 209, -24, 3, 209, -20, 2, 210, -16, 2, 211, -12, 1, + 212, -8, 0, 213, -4, 0, 214, 0, 0, 215, 3, -1, + 216, 7, -1, 217, 12, -2, 218, 16, -3, 219, 20, -3, + 192, -108, 15, 193, -104, 14, 194, -100, 14, 195, -96, 13, + 196, -92, 12, 197, -88, 11, 198, -84, 11, 199, -79, 10, + 200, -75, 9, 200, -71, 9, 201, -67, 8, 202, -63, 7, + 203, -59, 7, 204, -55, 6, 205, -51, 5, 206, -47, 5, + 207, -42, 4, 208, -38, 3, 209, -34, 3, 210, -30, 2, + 211, -26, 1, 212, -22, 1, 213, -18, 0, 214, -14, 0, + 214, -10, 0, 216, -5, -1, 216, -1, -2, 217, 2, -2, + 218, 6, -3, 219, 10, -4, 220, 14, -4, 221, 18, -5, + 194, -109, 13, 195, -105, 12, 196, -101, 12, 197, -97, 11, + 198, -93, 10, 199, -89, 10, 200, -85, 9, 201, -80, 8, + 202, -76, 8, 203, -72, 7, 204, -68, 6, 205, -64, 6, + 206, -60, 5, 207, -56, 4, 207, -52, 4, 208, -48, 3, + 209, -43, 2, 210, -39, 2, 211, -35, 1, 212, -31, 0, + 213, -27, 0, 214, -23, 0, 215, -19, -1, 216, -15, -1, + 217, -11, -2, 218, -6, -3, 219, -2, -3, 220, 1, -4, + 221, 5, -5, 222, 9, -5, 223, 13, -6, 223, 17, -7, + 197, -111, 11, 198, -107, 11, 199, -103, 10, 200, -98, 9, + 200, -94, 9, 201, -90, 8, 202, -86, 7, 203, -82, 7, + 204, -78, 6, 205, -74, 5, 206, -70, 5, 207, -66, 4, + 208, -61, 3, 209, -57, 3, 210, -53, 2, 211, -49, 1, + 212, -45, 1, 213, -41, 0, 214, -37, 0, 215, -33, 0, + 216, -28, -1, 216, -24, -2, 217, -20, -2, 218, -16, -3, + 219, -12, -4, 220, -8, -4, 221, -4, -5, 222, 0, -6, + 223, 3, -6, 224, 8, -7, 225, 12, -8, 226, 16, -8, + 199, -112, 10, 200, -108, 9, 201, -104, 8, 202, -100, 8, + 203, -96, 7, 204, -92, 6, 205, -88, 6, 206, -83, 5, + 207, -79, 4, 208, -75, 4, 208, -71, 3, 209, -67, 2, + 210, -63, 2, 211, -59, 1, 212, -55, 0, 213, -51, 0, + 214, -46, 0, 215, -42, -1, 216, -38, -1, 217, -34, -2, + 218, -30, -3, 219, -26, -3, 220, -22, -4, 221, -18, -5, + 222, -14, -5, 223, -9, -6, 223, -5, -7, 224, -1, -7, + 225, 2, -8, 226, 6, -9, 227, 10, -9, 228, 14, -10, + 201, -113, 8, 202, -109, 7, 203, -105, 7, 204, -101, 6, + 205, -97, 5, 206, -93, 5, 207, -89, 4, 208, -84, 3, + 209, -80, 3, 210, -76, 2, 211, -72, 1, 212, -68, 1, + 213, -64, 0, 214, -60, 0, 215, -56, 0, 215, -52, -1, + 216, -47, -2, 217, -43, -2, 218, -39, -3, 219, -35, -4, + 220, -31, -4, 221, -27, -5, 222, -23, -6, 223, -19, -6, + 224, -15, -7, 225, -10, -8, 226, -6, -8, 227, -2, -9, + 228, 1, -10, 229, 5, -10, 230, 9, -11, 231, 13, -12, + 204, -115, 6, 205, -111, 6, 206, -107, 5, 207, -102, 4, + 208, -98, 4, 208, -94, 3, 209, -90, 2, 210, -86, 2, + 211, -82, 1, 212, -78, 0, 213, -74, 0, 214, -70, 0, + 215, -65, -1, 216, -61, -1, 217, -57, -2, 218, -53, -3, + 219, -49, -3, 220, -45, -4, 221, -41, -5, 222, -37, -5, + 223, -32, -6, 224, -28, -7, 224, -24, -7, 225, -20, -8, + 226, -16, -9, 227, -12, -9, 228, -8, -10, 229, -4, -11, + 230, 0, -11, 231, 4, -12, 232, 8, -13, 233, 12, -13, + 206, -116, 5, 207, -112, 4, 208, -108, 3, 209, -104, 3, + 210, -100, 2, 211, -96, 1, 212, -92, 1, 213, -87, 0, + 214, -83, 0, 215, -79, 0, 215, -75, -1, 216, -71, -2, + 217, -67, -2, 218, -63, -3, 219, -59, -4, 220, -55, -4, + 221, -50, -5, 222, -46, -6, 223, -42, -6, 224, -38, -7, + 225, -34, -8, 226, -30, -8, 227, -26, -9, 228, -22, -10, + 229, -18, -10, 230, -13, -11, 231, -9, -12, 231, -5, -12, + 232, -1, -13, 233, 2, -14, 234, 6, -14, 235, 10, -15, + 208, -117, 3, 209, -113, 2, 210, -109, 2, 211, -105, 1, + 212, -101, 0, 213, -97, 0, 214, -93, 0, 215, -88, -1, + 216, -84, -1, 217, -80, -2, 218, -76, -3, 219, -72, -3, + 220, -68, -4, 221, -64, -5, 222, -60, -5, 222, -56, -6, + 224, -51, -7, 224, -47, -7, 225, -43, -8, 226, -39, -9, + 227, -35, -9, 228, -31, -10, 229, -27, -11, 230, -23, -11, + 231, -19, -12, 232, -14, -13, 233, -10, -13, 234, -6, -14, + 235, -2, -15, 236, 1, -15, 237, 5, -16, 238, 9, -17, + 211, -119, 1, 212, -115, 1, 213, -111, 0, 214, -106, 0, + 215, -102, 0, 215, -98, -1, 216, -94, -2, 217, -90, -2, + 218, -86, -3, 219, -82, -4, 220, -78, -4, 221, -74, -5, + 222, -69, -6, 223, -65, -6, 224, -61, -7, 225, -57, -8, + 226, -53, -8, 227, -49, -9, 228, -45, -10, 229, -41, -10, + 230, -36, -11, 231, -32, -12, 231, -28, -12, 232, -24, -13, + 233, -20, -14, 234, -16, -14, 235, -12, -15, 236, -8, -16, + 237, -4, -16, 238, 0, -17, 239, 4, -18, 240, 8, -18, + 213, -120, 0, 214, -116, 0, 215, -112, -1, 216, -108, -1, + 217, -104, -2, 218, -100, -3, 219, -96, -3, 220, -91, -4, + 221, -87, -5, 222, -83, -5, 223, -79, -6, 223, -75, -7, + 224, -71, -7, 225, -67, -8, 226, -63, -9, 227, -59, -9, + 228, -54, -10, 229, -50, -11, 230, -46, -11, 231, -42, -12, + 232, -38, -13, 233, -34, -13, 234, -30, -14, 235, -26, -15, + 236, -22, -15, 237, -17, -16, 238, -13, -17, 238, -9, -17, + 239, -5, -18, 240, -1, -19, 241, 2, -19, 242, 6, -20, + 66, -37, 111, 67, -33, 110, 68, -29, 109, 69, -24, 108, + 70, -20, 108, 71, -16, 107, 71, -12, 107, 72, -8, 106, + 73, -4, 105, 74, 0, 104, 75, 3, 104, 76, 7, 103, + 77, 12, 102, 78, 16, 102, 79, 20, 101, 80, 24, 100, + 81, 28, 100, 82, 32, 99, 83, 36, 98, 84, 40, 98, + 85, 45, 97, 86, 49, 96, 87, 53, 96, 87, 57, 95, + 88, 61, 94, 89, 65, 94, 90, 69, 93, 91, 73, 92, + 92, 77, 92, 93, 82, 91, 94, 86, 90, 95, 90, 90, + 68, -38, 109, 69, -34, 108, 70, -30, 108, 71, -26, 107, + 72, -22, 106, 73, -18, 105, 74, -14, 105, 75, -9, 104, + 76, -5, 103, 77, -1, 103, 78, 2, 102, 78, 6, 102, + 80, 10, 101, 80, 14, 100, 81, 18, 99, 82, 22, 99, + 83, 27, 98, 84, 31, 97, 85, 35, 97, 86, 39, 96, + 87, 43, 95, 88, 47, 95, 89, 51, 94, 90, 55, 93, + 91, 59, 93, 92, 64, 92, 93, 68, 91, 94, 72, 91, + 94, 76, 90, 95, 80, 89, 96, 84, 89, 97, 88, 88, + 71, -40, 107, 71, -36, 107, 72, -32, 106, 73, -27, 105, + 74, -23, 104, 75, -19, 104, 76, -15, 103, 77, -11, 102, + 78, -7, 102, 79, -3, 101, 80, 0, 100, 81, 4, 100, + 82, 9, 99, 83, 13, 98, 84, 17, 98, 85, 21, 97, + 86, 25, 96, 87, 29, 96, 87, 33, 95, 88, 37, 94, + 89, 42, 94, 90, 46, 93, 91, 50, 92, 92, 54, 92, + 93, 58, 91, 94, 62, 90, 95, 66, 90, 96, 70, 89, + 97, 74, 88, 98, 79, 88, 99, 83, 87, 100, 87, 86, + 73, -41, 105, 74, -37, 105, 75, -33, 104, 76, -28, 103, + 77, -24, 103, 78, -20, 102, 79, -16, 101, 80, -12, 101, + 80, -8, 100, 81, -4, 99, 82, 0, 99, 83, 3, 98, + 84, 8, 97, 85, 12, 97, 86, 16, 96, 87, 20, 95, + 88, 24, 95, 89, 28, 94, 90, 32, 93, 91, 36, 93, + 92, 41, 92, 93, 45, 91, 94, 49, 91, 94, 53, 90, + 95, 57, 89, 96, 61, 89, 97, 65, 88, 98, 69, 87, + 99, 73, 87, 100, 78, 86, 101, 82, 85, 102, 86, 85, + 75, -42, 104, 76, -38, 103, 77, -34, 103, 78, -30, 102, + 79, -26, 101, 80, -22, 100, 81, -18, 100, 82, -13, 99, + 83, -9, 98, 84, -5, 98, 85, -1, 97, 86, 2, 96, + 87, 6, 96, 87, 10, 95, 88, 14, 94, 89, 18, 94, + 90, 23, 93, 91, 27, 92, 92, 31, 92, 93, 35, 91, + 94, 39, 90, 95, 43, 90, 96, 47, 89, 97, 51, 88, + 98, 55, 88, 99, 60, 87, 100, 64, 86, 101, 68, 86, + 101, 72, 85, 103, 76, 84, 103, 80, 84, 104, 84, 83, + 78, -44, 102, 79, -40, 101, 79, -36, 101, 80, -31, 100, + 81, -27, 99, 82, -23, 99, 83, -19, 98, 84, -15, 97, + 85, -11, 97, 86, -7, 96, 87, -3, 95, 88, 0, 95, + 89, 5, 94, 90, 9, 93, 91, 13, 93, 92, 17, 92, + 93, 21, 91, 94, 25, 91, 94, 29, 90, 95, 33, 89, + 96, 38, 89, 97, 42, 88, 98, 46, 87, 99, 50, 87, + 100, 54, 86, 101, 58, 85, 102, 62, 85, 103, 66, 84, + 104, 70, 83, 105, 75, 83, 106, 79, 82, 107, 83, 81, + 80, -45, 100, 81, -41, 100, 82, -37, 99, 83, -32, 98, + 84, -28, 98, 85, -24, 97, 86, -20, 96, 87, -16, 96, + 87, -12, 95, 88, -8, 94, 89, -4, 94, 90, 0, 93, + 91, 4, 92, 92, 8, 92, 93, 12, 91, 94, 16, 90, + 95, 20, 90, 96, 24, 89, 97, 28, 88, 98, 32, 88, + 99, 37, 87, 100, 41, 86, 101, 45, 86, 102, 49, 85, + 102, 53, 84, 103, 57, 84, 104, 61, 83, 105, 65, 82, + 106, 69, 82, 107, 74, 81, 108, 78, 80, 109, 82, 80, + 82, -46, 99, 83, -42, 98, 84, -38, 97, 85, -34, 97, + 86, -30, 96, 87, -26, 95, 88, -22, 95, 89, -17, 94, + 90, -13, 93, 91, -9, 93, 92, -5, 92, 93, -1, 91, + 94, 2, 91, 95, 6, 90, 95, 10, 89, 96, 14, 89, + 97, 19, 88, 98, 23, 87, 99, 27, 87, 100, 31, 86, + 101, 35, 85, 102, 39, 85, 103, 43, 84, 104, 47, 83, + 105, 51, 83, 106, 56, 82, 107, 60, 81, 108, 64, 81, + 109, 68, 80, 110, 72, 79, 110, 76, 79, 111, 80, 78, + 85, -48, 97, 86, -44, 96, 86, -40, 96, 88, -35, 95, + 88, -31, 94, 89, -27, 94, 90, -23, 93, 91, -19, 92, + 92, -15, 92, 93, -11, 91, 94, -7, 90, 95, -3, 90, + 96, 1, 89, 97, 5, 88, 98, 9, 88, 99, 13, 87, + 100, 17, 86, 101, 21, 86, 102, 25, 85, 102, 29, 84, + 103, 34, 84, 104, 38, 83, 105, 42, 82, 106, 46, 82, + 107, 50, 81, 108, 54, 80, 109, 58, 80, 110, 62, 79, + 111, 66, 78, 112, 71, 78, 113, 75, 77, 114, 79, 76, + 87, -49, 95, 88, -45, 95, 89, -41, 94, 90, -36, 93, + 91, -32, 93, 92, -28, 92, 93, -24, 91, 94, -20, 91, + 95, -16, 90, 95, -12, 89, 96, -8, 89, 97, -4, 88, + 98, 0, 87, 99, 4, 87, 100, 8, 86, 101, 12, 85, + 102, 16, 85, 103, 20, 84, 104, 24, 83, 105, 28, 83, + 106, 33, 82, 107, 37, 81, 108, 41, 81, 109, 45, 80, + 109, 49, 79, 110, 53, 79, 111, 57, 78, 112, 61, 77, + 113, 65, 77, 114, 70, 76, 115, 74, 75, 116, 78, 75, + 89, -50, 94, 90, -46, 93, 91, -42, 92, 92, -38, 92, + 93, -34, 91, 94, -30, 90, 95, -26, 90, 96, -21, 89, + 97, -17, 88, 98, -13, 88, 99, -9, 87, 100, -5, 86, + 101, -1, 86, 102, 2, 85, 102, 6, 84, 103, 10, 84, + 104, 15, 83, 105, 19, 82, 106, 23, 82, 107, 27, 81, + 108, 31, 80, 109, 35, 80, 110, 39, 79, 111, 43, 78, + 112, 47, 78, 113, 52, 77, 114, 56, 76, 115, 60, 76, + 116, 64, 75, 117, 68, 74, 118, 72, 74, 118, 76, 73, + 92, -52, 92, 93, -48, 91, 94, -44, 90, 95, -39, 90, + 96, -35, 89, 97, -31, 88, 98, -27, 88, 99, -23, 87, + 100, -19, 86, 101, -15, 86, 102, -11, 85, 103, -7, 84, + 104, -2, 84, 104, 1, 83, 105, 5, 82, 106, 9, 82, + 107, 13, 81, 108, 17, 80, 109, 21, 80, 110, 25, 79, + 111, 30, 78, 112, 34, 78, 113, 38, 77, 114, 42, 76, + 115, 46, 76, 116, 50, 75, 117, 54, 74, 118, 58, 74, + 119, 62, 73, 120, 67, 72, 120, 71, 72, 121, 75, 71, + 95, -53, 90, 96, -49, 89, 96, -45, 89, 97, -41, 88, + 98, -37, 87, 99, -33, 87, 100, -29, 86, 101, -24, 85, + 102, -20, 85, 103, -16, 84, 104, -12, 83, 105, -8, 83, + 106, -4, 82, 107, 0, 81, 108, 3, 81, 109, 7, 80, + 110, 12, 79, 111, 16, 79, 112, 20, 78, 112, 24, 77, + 113, 28, 77, 114, 32, 76, 115, 36, 75, 116, 40, 75, + 117, 44, 74, 118, 49, 73, 119, 53, 73, 120, 57, 72, + 121, 61, 71, 122, 65, 71, 123, 69, 70, 124, 73, 69, + 97, -55, 88, 98, -51, 88, 99, -47, 87, 100, -42, 86, + 101, -38, 86, 102, -34, 85, 103, -30, 84, 104, -26, 84, + 105, -22, 83, 105, -18, 82, 106, -14, 82, 107, -10, 81, + 108, -5, 80, 109, -1, 80, 110, 2, 79, 111, 6, 78, + 112, 10, 78, 113, 14, 77, 114, 18, 76, 115, 22, 76, + 116, 27, 75, 117, 31, 74, 118, 35, 74, 119, 39, 73, + 119, 43, 72, 120, 47, 72, 121, 51, 71, 122, 55, 70, + 123, 59, 70, 124, 64, 69, 125, 68, 68, 126, 72, 68, + 99, -56, 87, 100, -52, 86, 101, -48, 85, 102, -43, 85, + 103, -39, 84, 104, -35, 83, 105, -31, 83, 106, -27, 82, + 107, -23, 81, 108, -19, 81, 109, -15, 80, 110, -11, 79, + 111, -6, 79, 112, -2, 78, 112, 1, 77, 113, 5, 77, + 114, 9, 76, 115, 13, 75, 116, 17, 75, 117, 21, 74, + 118, 26, 73, 119, 30, 73, 120, 34, 72, 121, 38, 71, + 122, 42, 71, 123, 46, 70, 124, 50, 69, 125, 54, 69, + 126, 58, 68, 127, 63, 67, 127, 67, 67, 128, 71, 66, + 102, -57, 85, 103, -53, 84, 104, -49, 84, 105, -45, 83, + 105, -41, 82, 106, -37, 82, 107, -33, 81, 108, -28, 80, + 109, -24, 80, 110, -20, 79, 111, -16, 78, 112, -12, 78, + 113, -8, 77, 114, -4, 76, 115, 0, 76, 116, 3, 75, + 117, 8, 74, 118, 12, 74, 119, 16, 73, 119, 20, 72, + 120, 24, 72, 121, 28, 71, 122, 32, 70, 123, 36, 70, + 124, 40, 69, 125, 45, 68, 126, 49, 68, 127, 53, 67, + 128, 57, 66, 129, 61, 66, 130, 65, 65, 131, 69, 64, + 104, -58, 83, 105, -54, 83, 106, -50, 82, 107, -46, 81, + 108, -42, 81, 109, -38, 80, 110, -34, 79, 111, -29, 79, + 112, -25, 78, 112, -21, 77, 113, -17, 77, 114, -13, 76, + 115, -9, 75, 116, -5, 75, 117, -1, 74, 118, 2, 73, + 119, 7, 73, 120, 11, 72, 121, 15, 71, 122, 19, 71, + 123, 23, 70, 124, 27, 69, 125, 31, 69, 126, 35, 68, + 126, 39, 67, 128, 44, 67, 128, 48, 66, 129, 52, 65, + 130, 56, 65, 131, 60, 64, 132, 64, 63, 133, 68, 63, + 106, -60, 82, 107, -56, 81, 108, -52, 80, 109, -47, 80, + 110, -43, 79, 111, -39, 78, 112, -35, 78, 113, -31, 77, + 114, -27, 76, 115, -23, 76, 116, -19, 75, 117, -15, 74, + 118, -10, 74, 119, -6, 73, 119, -2, 72, 120, 1, 72, + 121, 5, 71, 122, 9, 70, 123, 13, 70, 124, 17, 69, + 125, 22, 68, 126, 26, 68, 127, 30, 67, 128, 34, 66, + 129, 38, 66, 130, 42, 65, 131, 46, 64, 132, 50, 64, + 133, 54, 63, 134, 59, 62, 135, 63, 62, 135, 67, 61, + 109, -61, 80, 110, -57, 79, 111, -53, 79, 112, -49, 78, + 112, -45, 77, 113, -41, 77, 114, -37, 76, 115, -32, 75, + 116, -28, 75, 117, -24, 74, 118, -20, 73, 119, -16, 73, + 120, -12, 72, 121, -8, 71, 122, -4, 71, 123, 0, 70, + 124, 4, 69, 125, 8, 69, 126, 12, 68, 127, 16, 67, + 128, 20, 67, 128, 24, 66, 129, 28, 65, 130, 32, 65, + 131, 36, 64, 132, 41, 63, 133, 45, 63, 134, 49, 62, + 135, 53, 61, 136, 57, 61, 137, 61, 60, 138, 65, 59, + 111, -62, 78, 112, -58, 78, 113, -54, 77, 114, -50, 76, + 115, -46, 76, 116, -42, 75, 117, -38, 74, 118, -33, 74, + 119, -29, 73, 120, -25, 72, 120, -21, 72, 121, -17, 71, + 122, -13, 70, 123, -9, 70, 124, -5, 69, 125, -1, 68, + 126, 3, 68, 127, 7, 67, 128, 11, 66, 129, 15, 66, + 130, 19, 65, 131, 23, 64, 132, 27, 64, 133, 31, 63, + 134, 35, 62, 135, 40, 62, 135, 44, 61, 136, 48, 60, + 137, 52, 60, 138, 56, 59, 139, 60, 58, 140, 64, 58, + 113, -64, 77, 114, -60, 76, 115, -56, 75, 116, -51, 75, + 117, -47, 74, 118, -43, 73, 119, -39, 73, 120, -35, 72, + 121, -31, 71, 122, -27, 71, 123, -23, 70, 124, -19, 69, + 125, -14, 69, 126, -10, 68, 127, -6, 67, 127, -2, 67, + 128, 1, 66, 129, 5, 65, 130, 9, 65, 131, 13, 64, + 132, 18, 63, 133, 22, 63, 134, 26, 62, 135, 30, 61, + 136, 34, 61, 137, 38, 60, 138, 42, 59, 139, 46, 59, + 140, 50, 58, 141, 55, 57, 142, 59, 57, 142, 63, 56, + 116, -65, 75, 117, -61, 74, 118, -57, 74, 119, -53, 73, + 120, -49, 72, 120, -45, 72, 121, -41, 71, 122, -36, 70, + 123, -32, 70, 124, -28, 69, 125, -24, 68, 126, -20, 68, + 127, -16, 67, 128, -12, 66, 129, -8, 66, 130, -4, 65, + 131, 0, 64, 132, 4, 64, 133, 8, 63, 134, 12, 62, + 135, 16, 61, 135, 20, 61, 136, 24, 60, 137, 28, 60, + 138, 32, 59, 139, 37, 58, 140, 41, 58, 141, 45, 57, + 142, 49, 56, 143, 53, 55, 144, 57, 55, 145, 61, 54, + 118, -66, 73, 119, -62, 73, 120, -58, 72, 121, -54, 71, + 122, -50, 71, 123, -46, 70, 124, -42, 69, 125, -37, 69, + 126, -33, 68, 127, -29, 67, 127, -25, 67, 128, -21, 66, + 129, -17, 65, 130, -13, 65, 131, -9, 64, 132, -5, 63, + 133, 0, 63, 134, 3, 62, 135, 7, 61, 136, 11, 61, + 137, 15, 60, 138, 19, 59, 139, 23, 59, 140, 27, 58, + 141, 31, 57, 142, 36, 56, 143, 40, 56, 143, 44, 55, + 144, 48, 55, 145, 52, 54, 146, 56, 53, 147, 60, 53, + 120, -68, 72, 121, -64, 71, 122, -60, 70, 123, -55, 70, + 124, -51, 69, 125, -47, 68, 126, -43, 68, 127, -39, 67, + 128, -35, 66, 129, -31, 66, 130, -27, 65, 131, -23, 64, + 132, -18, 64, 133, -14, 63, 134, -10, 62, 134, -6, 62, + 136, -2, 61, 136, 1, 60, 137, 5, 60, 138, 9, 59, + 139, 14, 58, 140, 18, 57, 141, 22, 57, 142, 26, 56, + 143, 30, 56, 144, 34, 55, 145, 38, 54, 146, 42, 54, + 147, 46, 53, 148, 51, 52, 149, 55, 51, 150, 59, 51, + 123, -69, 70, 124, -65, 69, 125, -61, 69, 126, -57, 68, + 127, -53, 67, 127, -49, 67, 128, -45, 66, 129, -40, 65, + 130, -36, 65, 131, -32, 64, 132, -28, 63, 133, -24, 63, + 134, -20, 62, 135, -16, 61, 136, -12, 61, 137, -8, 60, + 138, -3, 59, 139, 0, 59, 140, 4, 58, 141, 8, 57, + 142, 12, 56, 143, 16, 56, 143, 20, 55, 144, 24, 55, + 145, 28, 54, 146, 33, 53, 147, 37, 52, 148, 41, 52, + 149, 45, 51, 150, 49, 50, 151, 53, 50, 152, 57, 49, + 125, -70, 68, 126, -66, 68, 127, -62, 67, 128, -58, 66, + 129, -54, 66, 130, -50, 65, 131, -46, 64, 132, -41, 63, + 133, -37, 63, 134, -33, 62, 135, -29, 62, 135, -25, 61, + 136, -21, 60, 137, -17, 60, 138, -13, 59, 139, -9, 58, + 140, -4, 57, 141, 0, 57, 142, 3, 56, 143, 7, 56, + 144, 11, 55, 145, 15, 54, 146, 19, 53, 147, 23, 53, + 148, 27, 52, 149, 32, 51, 150, 36, 51, 150, 40, 50, + 151, 44, 50, 152, 48, 49, 153, 52, 48, 154, 56, 47, + 128, -72, 67, 128, -68, 66, 129, -64, 65, 130, -59, 65, + 131, -55, 64, 132, -51, 63, 133, -47, 63, 134, -43, 62, + 135, -39, 61, 136, -35, 61, 137, -31, 60, 138, -27, 59, + 139, -22, 58, 140, -18, 58, 141, -14, 57, 142, -10, 57, + 143, -6, 56, 143, -2, 55, 144, 1, 55, 145, 5, 54, + 146, 10, 53, 147, 14, 52, 148, 18, 52, 149, 22, 51, + 150, 26, 51, 151, 30, 50, 152, 34, 49, 153, 38, 48, + 154, 42, 48, 155, 47, 47, 156, 51, 46, 157, 55, 46, + 130, -73, 65, 131, -69, 64, 132, -65, 64, 133, -61, 63, + 134, -57, 62, 135, -53, 62, 135, -49, 61, 136, -44, 60, + 137, -40, 59, 138, -36, 59, 139, -32, 58, 140, -28, 58, + 141, -24, 57, 142, -20, 56, 143, -16, 56, 144, -12, 55, + 145, -7, 54, 146, -3, 53, 147, 0, 53, 148, 4, 52, + 149, 8, 51, 150, 12, 51, 150, 16, 50, 151, 20, 49, + 152, 24, 49, 153, 29, 48, 154, 33, 47, 155, 37, 47, + 156, 41, 46, 157, 45, 45, 158, 49, 45, 159, 53, 44, + 132, -74, 63, 133, -70, 63, 134, -66, 62, 135, -62, 61, + 136, -58, 61, 137, -54, 60, 138, -50, 59, 139, -45, 58, + 140, -41, 58, 141, -37, 57, 142, -33, 57, 142, -29, 56, + 143, -25, 55, 144, -21, 54, 145, -17, 54, 146, -13, 53, + 147, -8, 52, 148, -4, 52, 149, 0, 51, 150, 3, 51, + 151, 7, 50, 152, 11, 49, 153, 15, 48, 154, 19, 48, + 155, 23, 47, 156, 28, 46, 157, 32, 46, 158, 36, 45, + 158, 40, 44, 159, 44, 44, 160, 48, 43, 161, 52, 42, + 135, -76, 62, 135, -72, 61, 136, -68, 60, 137, -63, 59, + 138, -59, 59, 139, -55, 58, 140, -51, 58, 141, -47, 57, + 142, -43, 56, 143, -39, 55, 144, -35, 55, 145, -31, 54, + 146, -26, 53, 147, -22, 53, 148, -18, 52, 149, -14, 52, + 150, -10, 51, 151, -6, 50, 151, -2, 49, 152, 1, 49, + 153, 6, 48, 154, 10, 47, 155, 14, 47, 156, 18, 46, + 157, 22, 45, 158, 26, 45, 159, 30, 44, 160, 34, 43, + 161, 38, 43, 162, 43, 42, 163, 47, 41, 164, 51, 41, + 137, -77, 60, 138, -73, 59, 139, -69, 59, 140, -65, 58, + 141, -61, 57, 142, -57, 57, 142, -53, 56, 144, -48, 55, + 144, -44, 54, 145, -40, 54, 146, -36, 53, 147, -32, 53, + 148, -28, 52, 149, -24, 51, 150, -20, 50, 151, -16, 50, + 152, -11, 49, 153, -7, 48, 154, -3, 48, 155, 0, 47, + 156, 4, 46, 157, 8, 46, 158, 12, 45, 158, 16, 44, + 159, 20, 44, 160, 25, 43, 161, 29, 42, 162, 33, 42, + 163, 37, 41, 164, 41, 40, 165, 45, 40, 166, 49, 39, + 139, -78, 58, 140, -74, 58, 141, -70, 57, 142, -66, 56, + 143, -62, 55, 144, -58, 55, 145, -54, 54, 146, -49, 53, + 147, -45, 53, 148, -41, 52, 149, -37, 51, 150, -33, 51, + 151, -29, 50, 151, -25, 49, 152, -21, 49, 153, -17, 48, + 154, -12, 47, 155, -8, 47, 156, -4, 46, 157, 0, 45, + 158, 3, 45, 159, 7, 44, 160, 11, 43, 161, 15, 43, + 162, 19, 42, 163, 24, 41, 164, 28, 41, 165, 32, 40, + 165, 36, 39, 166, 40, 39, 167, 44, 38, 168, 48, 37, + 142, -80, 56, 143, -76, 55, 144, -72, 55, 145, -68, 54, + 146, -64, 53, 147, -60, 53, 148, -56, 52, 149, -51, 51, + 150, -47, 51, 151, -43, 50, 152, -39, 49, 152, -35, 49, + 153, -31, 48, 154, -27, 47, 155, -23, 47, 156, -19, 46, + 157, -14, 45, 158, -10, 45, 159, -6, 44, 160, -2, 43, + 161, 1, 43, 162, 5, 42, 163, 9, 41, 164, 13, 41, + 165, 17, 40, 166, 22, 39, 167, 26, 39, 167, 30, 38, + 168, 34, 37, 169, 38, 37, 170, 42, 36, 171, 46, 35, + 145, -81, 54, 145, -77, 54, 146, -73, 53, 147, -69, 52, + 148, -65, 52, 149, -61, 51, 150, -57, 50, 151, -52, 50, + 152, -48, 49, 153, -44, 48, 154, -40, 48, 155, -36, 47, + 156, -32, 46, 157, -28, 46, 158, -24, 45, 159, -20, 44, + 160, -15, 44, 160, -11, 43, 161, -7, 42, 162, -3, 42, + 163, 0, 41, 164, 4, 40, 165, 8, 40, 166, 12, 39, + 167, 16, 38, 168, 21, 38, 169, 25, 37, 170, 29, 36, + 171, 33, 36, 172, 37, 35, 173, 41, 34, 174, 45, 34, + 147, -83, 53, 148, -79, 52, 149, -75, 51, 150, -70, 51, + 151, -66, 50, 152, -62, 49, 152, -58, 49, 153, -54, 48, + 154, -50, 47, 155, -46, 47, 156, -42, 46, 157, -38, 45, + 158, -33, 45, 159, -29, 44, 160, -25, 43, 161, -21, 43, + 162, -17, 42, 163, -13, 41, 164, -9, 41, 165, -5, 40, + 166, 0, 39, 167, 3, 39, 168, 7, 38, 168, 11, 37, + 169, 15, 37, 170, 19, 36, 171, 23, 35, 172, 27, 35, + 173, 31, 34, 174, 36, 33, 175, 40, 33, 176, 44, 32, + 149, -84, 51, 150, -80, 50, 151, -76, 50, 152, -71, 49, + 153, -67, 48, 154, -63, 48, 155, -59, 47, 156, -55, 46, + 157, -51, 46, 158, -47, 45, 159, -43, 44, 159, -39, 44, + 161, -34, 43, 161, -30, 42, 162, -26, 42, 163, -22, 41, + 164, -18, 40, 165, -14, 40, 166, -10, 39, 167, -6, 38, + 168, -1, 38, 169, 2, 37, 170, 6, 36, 171, 10, 36, + 172, 14, 35, 173, 18, 34, 174, 22, 34, 175, 26, 33, + 175, 30, 32, 176, 35, 32, 177, 39, 31, 178, 43, 30, + 152, -85, 49, 152, -81, 49, 153, -77, 48, 154, -73, 47, + 155, -69, 47, 156, -65, 46, 157, -61, 45, 158, -56, 45, + 159, -52, 44, 160, -48, 43, 161, -44, 43, 162, -40, 42, + 163, -36, 41, 164, -32, 41, 165, -28, 40, 166, -24, 39, + 167, -19, 39, 168, -15, 38, 168, -11, 37, 169, -7, 37, + 170, -3, 36, 171, 0, 35, 172, 4, 35, 173, 8, 34, + 174, 12, 33, 175, 17, 33, 176, 21, 32, 177, 25, 31, + 178, 29, 31, 179, 33, 30, 180, 37, 29, 181, 41, 29, + 154, -87, 48, 155, -83, 47, 156, -79, 46, 157, -74, 46, + 158, -70, 45, 159, -66, 44, 160, -62, 44, 161, -58, 43, + 161, -54, 42, 162, -50, 42, 163, -46, 41, 164, -42, 40, + 165, -37, 40, 166, -33, 39, 167, -29, 38, 168, -25, 38, + 169, -21, 37, 170, -17, 36, 171, -13, 36, 172, -9, 35, + 173, -4, 34, 174, 0, 34, 175, 3, 33, 175, 7, 32, + 176, 11, 32, 177, 15, 31, 178, 19, 30, 179, 23, 30, + 180, 27, 29, 181, 32, 28, 182, 36, 28, 183, 40, 27, + 156, -88, 46, 157, -84, 45, 158, -80, 45, 159, -75, 44, + 160, -71, 43, 161, -67, 43, 162, -63, 42, 163, -59, 41, + 164, -55, 41, 165, -51, 40, 166, -47, 39, 167, -43, 39, + 168, -38, 38, 168, -34, 37, 169, -30, 37, 170, -26, 36, + 171, -22, 35, 172, -18, 35, 173, -14, 34, 174, -10, 33, + 175, -5, 33, 176, -1, 32, 177, 2, 31, 178, 6, 31, + 179, 10, 30, 180, 14, 29, 181, 18, 29, 182, 22, 28, + 182, 26, 27, 184, 31, 27, 184, 35, 26, 185, 39, 25, + 159, -89, 44, 160, -85, 44, 160, -81, 43, 161, -77, 42, + 162, -73, 42, 163, -69, 41, 164, -65, 40, 165, -60, 40, + 166, -56, 39, 167, -52, 38, 168, -48, 38, 169, -44, 37, + 170, -40, 36, 171, -36, 36, 172, -32, 35, 173, -28, 34, + 174, -23, 34, 175, -19, 33, 175, -15, 32, 176, -11, 32, + 177, -7, 31, 178, -3, 30, 179, 0, 30, 180, 4, 29, + 181, 8, 28, 182, 13, 28, 183, 17, 27, 184, 21, 26, + 185, 25, 26, 186, 29, 25, 187, 33, 24, 188, 37, 24, + 161, -91, 43, 162, -87, 42, 163, -83, 41, 164, -78, 41, + 165, -74, 40, 166, -70, 39, 167, -66, 39, 168, -62, 38, + 168, -58, 37, 169, -54, 37, 170, -50, 36, 171, -46, 35, + 172, -41, 35, 173, -37, 34, 174, -33, 33, 175, -29, 33, + 176, -25, 32, 177, -21, 31, 178, -17, 31, 179, -13, 30, + 180, -8, 29, 181, -4, 29, 182, 0, 28, 183, 3, 27, + 183, 7, 27, 184, 11, 26, 185, 15, 25, 186, 19, 25, + 187, 23, 24, 188, 28, 23, 189, 32, 23, 190, 36, 22, + 163, -92, 41, 164, -88, 40, 165, -84, 40, 166, -79, 39, + 167, -75, 38, 168, -71, 38, 169, -67, 37, 170, -63, 36, + 171, -59, 36, 172, -55, 35, 173, -51, 34, 174, -47, 34, + 175, -42, 33, 176, -38, 32, 176, -34, 32, 177, -30, 31, + 178, -26, 30, 179, -22, 30, 180, -18, 29, 181, -14, 28, + 182, -9, 28, 183, -5, 27, 184, -1, 26, 185, 2, 26, + 186, 6, 25, 187, 10, 24, 188, 14, 24, 189, 18, 23, + 190, 22, 22, 191, 27, 22, 191, 31, 21, 192, 35, 20, + 166, -93, 39, 167, -89, 39, 167, -85, 38, 169, -81, 37, + 169, -77, 37, 170, -73, 36, 171, -69, 35, 172, -64, 35, + 173, -60, 34, 174, -56, 33, 175, -52, 33, 176, -48, 32, + 177, -44, 31, 178, -40, 31, 179, -36, 30, 180, -32, 29, + 181, -27, 29, 182, -23, 28, 183, -19, 27, 183, -15, 27, + 184, -11, 26, 185, -7, 25, 186, -3, 25, 187, 0, 24, + 188, 4, 23, 189, 9, 23, 190, 13, 22, 191, 17, 21, + 192, 21, 21, 193, 25, 20, 194, 29, 19, 195, 33, 19, + 168, -95, 38, 169, -91, 37, 170, -87, 36, 171, -82, 36, + 172, -78, 35, 173, -74, 34, 174, -70, 34, 175, -66, 33, + 176, -62, 32, 176, -58, 32, 177, -54, 31, 178, -50, 30, + 179, -45, 30, 180, -41, 29, 181, -37, 28, 182, -33, 28, + 183, -29, 27, 184, -25, 26, 185, -21, 26, 186, -17, 25, + 187, -12, 24, 188, -8, 24, 189, -4, 23, 190, 0, 22, + 190, 3, 22, 192, 7, 21, 192, 11, 20, 193, 15, 20, + 194, 19, 19, 195, 24, 18, 196, 28, 18, 197, 32, 17, + 170, -96, 36, 171, -92, 35, 172, -88, 35, 173, -83, 34, + 174, -79, 33, 175, -75, 33, 176, -71, 32, 177, -67, 31, + 178, -63, 31, 179, -59, 30, 180, -55, 29, 181, -51, 29, + 182, -46, 28, 183, -42, 27, 183, -38, 27, 184, -34, 26, + 185, -30, 25, 186, -26, 25, 187, -22, 24, 188, -18, 23, + 189, -13, 23, 190, -9, 22, 191, -5, 21, 192, -1, 21, + 193, 2, 20, 194, 6, 19, 195, 10, 19, 196, 14, 18, + 197, 18, 17, 198, 23, 17, 199, 27, 16, 199, 31, 15, + 173, -97, 34, 174, -93, 34, 175, -89, 33, 176, -85, 32, + 176, -81, 32, 177, -77, 31, 178, -73, 30, 179, -68, 30, + 180, -64, 29, 181, -60, 28, 182, -56, 28, 183, -52, 27, + 184, -48, 26, 185, -44, 26, 186, -40, 25, 187, -36, 24, + 188, -31, 24, 189, -27, 23, 190, -23, 22, 190, -19, 22, + 192, -15, 21, 192, -11, 20, 193, -7, 20, 194, -3, 19, + 195, 0, 18, 196, 5, 18, 197, 9, 17, 198, 13, 16, + 199, 17, 16, 200, 21, 15, 201, 25, 14, 202, 29, 14, + 175, -99, 33, 176, -95, 32, 177, -91, 31, 178, -86, 31, + 179, -82, 30, 180, -78, 29, 181, -74, 29, 182, -70, 28, + 183, -66, 27, 183, -62, 27, 184, -58, 26, 185, -54, 25, + 186, -49, 25, 187, -45, 24, 188, -41, 23, 189, -37, 23, + 190, -33, 22, 191, -29, 21, 192, -25, 21, 193, -21, 20, + 194, -16, 19, 195, -12, 19, 196, -8, 18, 197, -4, 17, + 198, 0, 17, 199, 3, 16, 199, 7, 15, 200, 11, 15, + 201, 15, 14, 202, 20, 13, 203, 24, 13, 204, 28, 12, + 177, -100, 31, 178, -96, 30, 179, -92, 30, 180, -87, 29, + 181, -83, 28, 182, -79, 28, 183, -75, 27, 184, -71, 26, + 185, -67, 26, 186, -63, 25, 187, -59, 24, 188, -55, 24, + 189, -50, 23, 190, -46, 22, 191, -42, 22, 191, -38, 21, + 192, -34, 20, 193, -30, 20, 194, -26, 19, 195, -22, 18, + 196, -17, 18, 197, -13, 17, 198, -9, 16, 199, -5, 16, + 200, -1, 15, 201, 2, 14, 202, 6, 14, 203, 10, 13, + 204, 14, 12, 205, 19, 12, 206, 23, 11, 206, 27, 10, + 180, -101, 29, 181, -97, 29, 182, -93, 28, 183, -89, 27, + 184, -85, 27, 184, -81, 26, 185, -77, 25, 186, -72, 25, + 187, -68, 24, 188, -64, 23, 189, -60, 23, 190, -56, 22, + 191, -52, 21, 192, -48, 21, 193, -44, 20, 194, -40, 19, + 195, -35, 19, 196, -31, 18, 197, -27, 17, 198, -23, 17, + 199, -19, 16, 199, -15, 15, 200, -11, 15, 201, -7, 14, + 202, -3, 13, 203, 1, 13, 204, 5, 12, 205, 9, 11, + 206, 13, 11, 207, 17, 10, 208, 21, 9, 209, 25, 9, + 182, -103, 28, 183, -99, 27, 184, -95, 26, 185, -90, 26, + 186, -86, 25, 187, -82, 24, 188, -78, 24, 189, -74, 23, + 190, -70, 22, 191, -66, 22, 191, -62, 21, 192, -58, 20, + 193, -53, 20, 194, -49, 19, 195, -45, 18, 196, -41, 18, + 197, -37, 17, 198, -33, 16, 199, -29, 16, 200, -25, 15, + 201, -20, 14, 202, -16, 14, 203, -12, 13, 204, -8, 12, + 205, -4, 12, 206, 0, 11, 207, 3, 10, 207, 7, 10, + 208, 11, 9, 209, 16, 8, 210, 20, 8, 211, 24, 7, + 184, -104, 26, 185, -100, 25, 186, -96, 25, 187, -91, 24, + 188, -87, 23, 189, -83, 23, 190, -79, 22, 191, -75, 21, + 192, -71, 21, 193, -67, 20, 194, -63, 19, 195, -59, 19, + 196, -54, 18, 197, -50, 17, 198, -46, 17, 198, -42, 16, + 200, -38, 15, 200, -34, 15, 201, -30, 14, 202, -26, 13, + 203, -21, 13, 204, -17, 12, 205, -13, 11, 206, -9, 11, + 207, -5, 10, 208, -1, 9, 209, 2, 9, 210, 6, 8, + 211, 10, 7, 212, 15, 6, 213, 19, 6, 214, 23, 5, + 187, -105, 24, 188, -101, 24, 189, -97, 23, 190, -93, 22, + 191, -89, 22, 191, -85, 21, 192, -81, 20, 193, -76, 20, + 194, -72, 19, 195, -68, 18, 196, -64, 18, 197, -60, 17, + 198, -56, 16, 199, -52, 16, 200, -48, 15, 201, -44, 14, + 202, -39, 14, 203, -35, 13, 204, -31, 12, 205, -27, 12, + 206, -23, 11, 207, -19, 10, 207, -15, 10, 208, -11, 9, + 209, -7, 8, 210, -2, 7, 211, 1, 7, 212, 5, 6, + 213, 9, 6, 214, 13, 5, 215, 17, 4, 216, 21, 4, + 189, -107, 23, 190, -103, 22, 191, -99, 21, 192, -94, 21, + 193, -90, 20, 194, -86, 19, 195, -82, 19, 196, -78, 18, + 197, -74, 17, 198, -70, 17, 198, -66, 16, 199, -62, 15, + 200, -57, 15, 201, -53, 14, 202, -49, 13, 203, -45, 13, + 204, -41, 12, 205, -37, 11, 206, -33, 11, 207, -29, 10, + 208, -24, 9, 209, -20, 9, 210, -16, 8, 211, -12, 7, + 212, -8, 7, 213, -4, 6, 214, 0, 5, 214, 3, 5, + 215, 7, 4, 216, 12, 3, 217, 16, 2, 218, 20, 2, + 192, -108, 20, 193, -104, 20, 194, -100, 19, 195, -96, 18, + 196, -92, 18, 197, -88, 17, 198, -84, 16, 199, -79, 16, + 200, -75, 15, 201, -71, 14, 201, -67, 14, 202, -63, 13, + 203, -59, 12, 204, -55, 12, 205, -51, 11, 206, -47, 10, + 207, -42, 10, 208, -38, 9, 209, -34, 8, 210, -30, 8, + 211, -26, 7, 212, -22, 6, 213, -18, 6, 214, -14, 5, + 215, -10, 4, 216, -5, 4, 216, -1, 3, 217, 2, 2, + 218, 6, 2, 219, 10, 1, 220, 14, 0, 221, 18, 0, + 194, -110, 19, 195, -106, 18, 196, -102, 18, 197, -97, 17, + 198, -93, 16, 199, -89, 15, 200, -85, 15, 201, -81, 14, + 202, -77, 13, 203, -73, 13, 204, -69, 12, 205, -65, 11, + 206, -60, 11, 207, -56, 10, 208, -52, 9, 208, -48, 9, + 209, -44, 8, 210, -40, 7, 211, -36, 7, 212, -32, 6, + 213, -27, 5, 214, -23, 5, 215, -19, 4, 216, -15, 3, + 217, -11, 3, 218, -7, 2, 219, -3, 1, 220, 0, 1, + 221, 4, 0, 222, 9, 0, 223, 13, 0, 224, 17, -1, + 197, -111, 17, 198, -107, 16, 199, -103, 16, 200, -98, 15, + 201, -94, 14, 201, -90, 14, 202, -86, 13, 203, -82, 12, + 204, -78, 12, 205, -74, 11, 206, -70, 10, 207, -66, 10, + 208, -61, 9, 209, -57, 8, 210, -53, 8, 211, -49, 7, + 212, -45, 6, 213, -41, 6, 214, -37, 5, 215, -33, 4, + 216, -28, 4, 217, -24, 3, 217, -20, 2, 218, -16, 2, + 219, -12, 1, 220, -8, 0, 221, -4, 0, 222, 0, 0, + 223, 3, -1, 224, 8, -1, 225, 12, -2, 226, 16, -3, + 199, -112, 15, 200, -108, 15, 201, -104, 14, 202, -100, 13, + 203, -96, 13, 204, -92, 12, 205, -88, 11, 206, -83, 11, + 207, -79, 10, 208, -75, 9, 208, -71, 9, 209, -67, 8, + 210, -63, 7, 211, -59, 7, 212, -55, 6, 213, -51, 5, + 214, -46, 5, 215, -42, 4, 216, -38, 3, 217, -34, 3, + 218, -30, 2, 219, -26, 1, 220, -22, 1, 221, -18, 0, + 222, -14, 0, 223, -9, 0, 224, -5, -1, 224, -1, -2, + 225, 2, -2, 226, 6, -3, 227, 10, -4, 228, 14, -4, + 201, -113, 14, 202, -109, 13, 203, -105, 12, 204, -101, 12, + 205, -97, 11, 206, -93, 10, 207, -89, 10, 208, -84, 9, + 209, -80, 8, 210, -76, 8, 211, -72, 7, 212, -68, 6, + 213, -64, 6, 214, -60, 5, 215, -56, 4, 215, -52, 4, + 217, -47, 3, 217, -43, 2, 218, -39, 2, 219, -35, 1, + 220, -31, 0, 221, -27, 0, 222, -23, 0, 223, -19, -1, + 224, -15, -1, 225, -10, -2, 226, -6, -3, 227, -2, -3, + 228, 1, -4, 229, 5, -5, 230, 9, -5, 231, 13, -6, + 204, -115, 12, 205, -111, 11, 206, -107, 11, 207, -102, 10, + 208, -98, 9, 208, -94, 9, 209, -90, 8, 210, -86, 7, + 211, -82, 7, 212, -78, 6, 213, -74, 5, 214, -70, 5, + 215, -65, 4, 216, -61, 3, 217, -57, 3, 218, -53, 2, + 219, -49, 1, 220, -45, 1, 221, -41, 0, 222, -37, 0, + 223, -32, 0, 224, -28, -1, 224, -24, -2, 225, -20, -2, + 226, -16, -3, 227, -12, -4, 228, -8, -4, 229, -4, -5, + 230, 0, -6, 231, 4, -6, 232, 8, -7, 233, 12, -8, + 206, -116, 10, 207, -112, 10, 208, -108, 9, 209, -104, 8, + 210, -100, 8, 211, -96, 7, 212, -92, 6, 213, -87, 6, + 214, -83, 5, 215, -79, 4, 216, -75, 4, 216, -71, 3, + 217, -67, 2, 218, -63, 2, 219, -59, 1, 220, -55, 0, + 221, -50, 0, 222, -46, 0, 223, -42, -1, 224, -38, -1, + 225, -34, -2, 226, -30, -3, 227, -26, -3, 228, -22, -4, + 229, -18, -5, 230, -13, -5, 231, -9, -6, 231, -5, -7, + 232, -1, -7, 233, 2, -8, 234, 6, -9, 235, 10, -9, + 209, -117, 9, 209, -113, 8, 210, -109, 7, 211, -105, 7, + 212, -101, 6, 213, -97, 5, 214, -93, 5, 215, -88, 4, + 216, -84, 3, 217, -80, 3, 218, -76, 2, 219, -72, 1, + 220, -68, 1, 221, -64, 0, 222, -60, 0, 223, -56, 0, + 224, -51, -1, 224, -47, -2, 225, -43, -2, 226, -39, -3, + 227, -35, -4, 228, -31, -4, 229, -27, -5, 230, -23, -6, + 231, -19, -6, 232, -14, -7, 233, -10, -8, 234, -6, -8, + 235, -2, -9, 236, 1, -10, 237, 5, -10, 238, 9, -11, + 211, -119, 7, 212, -115, 6, 213, -111, 6, 214, -106, 5, + 215, -102, 4, 216, -98, 4, 216, -94, 3, 217, -90, 2, + 218, -86, 2, 219, -82, 1, 220, -78, 0, 221, -74, 0, + 222, -69, 0, 223, -65, -1, 224, -61, -1, 225, -57, -2, + 226, -53, -3, 227, -49, -3, 228, -45, -4, 229, -41, -5, + 230, -36, -5, 231, -32, -6, 232, -28, -7, 232, -24, -7, + 233, -20, -8, 234, -16, -9, 235, -12, -9, 236, -8, -10, + 237, -4, -11, 238, 0, -11, 239, 4, -12, 240, 8, -13, + 213, -120, 5, 214, -116, 5, 215, -112, 4, 216, -108, 3, + 217, -104, 3, 218, -100, 2, 219, -96, 1, 220, -91, 1, + 221, -87, 0, 222, -83, 0, 223, -79, 0, 223, -75, -1, + 225, -71, -2, 225, -67, -2, 226, -63, -3, 227, -59, -4, + 228, -54, -4, 229, -50, -5, 230, -46, -6, 231, -42, -6, + 232, -38, -7, 233, -34, -8, 234, -30, -8, 235, -26, -9, + 236, -22, -10, 237, -17, -10, 238, -13, -11, 239, -9, -12, + 239, -5, -12, 240, -1, -13, 241, 2, -14, 242, 6, -14, + 216, -121, 4, 216, -117, 3, 217, -113, 2, 218, -109, 2, + 219, -105, 1, 220, -101, 0, 221, -97, 0, 222, -92, 0, + 223, -88, -1, 224, -84, -1, 225, -80, -2, 226, -76, -3, + 227, -72, -3, 228, -68, -4, 229, -64, -5, 230, -60, -5, + 231, -55, -6, 232, -51, -7, 232, -47, -7, 233, -43, -8, + 234, -39, -9, 235, -35, -9, 236, -31, -10, 237, -27, -11, + 238, -23, -11, 239, -18, -12, 240, -14, -13, 241, -10, -13, + 242, -6, -14, 243, -2, -15, 244, 1, -15, 245, 5, -16, + 68, -38, 115, 69, -34, 114, 70, -30, 113, 71, -26, 112, + 72, -22, 112, 73, -18, 111, 74, -14, 111, 75, -9, 110, + 76, -5, 109, 77, -1, 108, 78, 2, 108, 79, 6, 107, + 80, 10, 106, 80, 14, 106, 81, 18, 105, 82, 22, 104, + 83, 27, 104, 84, 31, 103, 85, 35, 102, 86, 39, 102, + 87, 43, 101, 88, 47, 100, 89, 51, 100, 90, 55, 99, + 91, 59, 98, 92, 64, 98, 93, 68, 97, 94, 72, 96, + 94, 76, 96, 96, 80, 95, 96, 84, 94, 97, 88, 94, + 71, -40, 113, 72, -36, 112, 72, -32, 112, 73, -27, 111, + 74, -23, 110, 75, -19, 109, 76, -15, 109, 77, -11, 108, + 78, -7, 107, 79, -3, 107, 80, 0, 106, 81, 4, 106, + 82, 9, 105, 83, 13, 104, 84, 17, 103, 85, 21, 103, + 86, 25, 102, 87, 29, 101, 87, 33, 101, 88, 37, 100, + 89, 42, 99, 90, 46, 99, 91, 50, 98, 92, 54, 97, + 93, 58, 97, 94, 62, 96, 95, 66, 95, 96, 70, 95, + 97, 74, 94, 98, 79, 93, 99, 83, 93, 100, 87, 92, + 73, -41, 111, 74, -37, 111, 75, -33, 110, 76, -28, 109, + 77, -24, 108, 78, -20, 108, 79, -16, 107, 80, -12, 106, + 80, -8, 106, 81, -4, 105, 82, 0, 104, 83, 3, 104, + 84, 8, 103, 85, 12, 102, 86, 16, 102, 87, 20, 101, + 88, 24, 100, 89, 28, 100, 90, 32, 99, 91, 36, 98, + 92, 41, 98, 93, 45, 97, 94, 49, 96, 95, 53, 96, + 95, 57, 95, 96, 61, 94, 97, 65, 94, 98, 69, 93, + 99, 73, 92, 100, 78, 92, 101, 82, 91, 102, 86, 90, + 75, -42, 109, 76, -38, 109, 77, -34, 108, 78, -30, 107, + 79, -26, 107, 80, -22, 106, 81, -18, 105, 82, -13, 105, + 83, -9, 104, 84, -5, 103, 85, -1, 103, 86, 2, 102, + 87, 6, 101, 88, 10, 101, 88, 14, 100, 89, 18, 99, + 90, 23, 99, 91, 27, 98, 92, 31, 97, 93, 35, 97, + 94, 39, 96, 95, 43, 95, 96, 47, 95, 97, 51, 94, + 98, 55, 93, 99, 60, 93, 100, 64, 92, 101, 68, 91, + 102, 72, 91, 103, 76, 90, 103, 80, 89, 104, 84, 89, + 78, -44, 108, 79, -40, 107, 79, -36, 107, 81, -31, 106, + 81, -27, 105, 82, -23, 104, 83, -19, 104, 84, -15, 103, + 85, -11, 102, 86, -7, 102, 87, -3, 101, 88, 0, 100, + 89, 5, 100, 90, 9, 99, 91, 13, 98, 92, 17, 98, + 93, 21, 97, 94, 25, 96, 95, 29, 96, 95, 33, 95, + 96, 38, 94, 97, 42, 94, 98, 46, 93, 99, 50, 92, + 100, 54, 92, 101, 58, 91, 102, 62, 90, 103, 66, 90, + 104, 70, 89, 105, 75, 88, 106, 79, 88, 107, 83, 87, + 80, -45, 106, 81, -41, 105, 82, -37, 105, 83, -32, 104, + 84, -28, 103, 85, -24, 103, 86, -20, 102, 87, -16, 101, + 88, -12, 101, 88, -8, 100, 89, -4, 99, 90, 0, 99, + 91, 4, 98, 92, 8, 97, 93, 12, 97, 94, 16, 96, + 95, 20, 95, 96, 24, 95, 97, 28, 94, 98, 32, 93, + 99, 37, 93, 100, 41, 92, 101, 45, 91, 102, 49, 91, + 102, 53, 90, 103, 57, 89, 104, 61, 89, 105, 65, 88, + 106, 69, 87, 107, 74, 87, 108, 78, 86, 109, 82, 85, + 82, -46, 104, 83, -42, 104, 84, -38, 103, 85, -34, 102, + 86, -30, 102, 87, -26, 101, 88, -22, 100, 89, -17, 100, + 90, -13, 99, 91, -9, 98, 92, -5, 98, 93, -1, 97, + 94, 2, 96, 95, 6, 96, 95, 10, 95, 96, 14, 94, + 97, 19, 94, 98, 23, 93, 99, 27, 92, 100, 31, 92, + 101, 35, 91, 102, 39, 90, 103, 43, 90, 104, 47, 89, + 105, 51, 88, 106, 56, 88, 107, 60, 87, 108, 64, 86, + 109, 68, 86, 110, 72, 85, 111, 76, 84, 111, 80, 84, + 85, -48, 103, 86, -44, 102, 87, -40, 101, 88, -35, 101, + 88, -31, 100, 89, -27, 99, 90, -23, 99, 91, -19, 98, + 92, -15, 97, 93, -11, 97, 94, -7, 96, 95, -3, 95, + 96, 1, 95, 97, 5, 94, 98, 9, 93, 99, 13, 93, + 100, 17, 92, 101, 21, 91, 102, 25, 91, 102, 29, 90, + 104, 34, 89, 104, 38, 89, 105, 42, 88, 106, 46, 87, + 107, 50, 87, 108, 54, 86, 109, 58, 85, 110, 62, 85, + 111, 66, 84, 112, 71, 83, 113, 75, 83, 114, 79, 82, + 87, -49, 101, 88, -45, 100, 89, -41, 100, 90, -36, 99, + 91, -32, 98, 92, -28, 98, 93, -24, 97, 94, -20, 96, + 95, -16, 96, 95, -12, 95, 96, -8, 94, 97, -4, 94, + 98, 0, 93, 99, 4, 92, 100, 8, 92, 101, 12, 91, + 102, 16, 90, 103, 20, 90, 104, 24, 89, 105, 28, 88, + 106, 33, 88, 107, 37, 87, 108, 41, 86, 109, 45, 86, + 110, 49, 85, 111, 53, 84, 111, 57, 84, 112, 61, 83, + 113, 65, 82, 114, 70, 82, 115, 74, 81, 116, 78, 80, + 89, -50, 99, 90, -46, 99, 91, -42, 98, 92, -38, 97, + 93, -34, 97, 94, -30, 96, 95, -26, 95, 96, -21, 95, + 97, -17, 94, 98, -13, 93, 99, -9, 93, 100, -5, 92, + 101, -1, 91, 102, 2, 91, 103, 6, 90, 103, 10, 89, + 104, 15, 89, 105, 19, 88, 106, 23, 87, 107, 27, 87, + 108, 31, 86, 109, 35, 85, 110, 39, 85, 111, 43, 84, + 112, 47, 83, 113, 52, 83, 114, 56, 82, 115, 60, 81, + 116, 64, 81, 117, 68, 80, 118, 72, 79, 118, 76, 79, + 92, -52, 98, 93, -48, 97, 94, -44, 96, 95, -39, 96, + 96, -35, 95, 96, -31, 94, 97, -27, 94, 98, -23, 93, + 99, -19, 92, 100, -15, 92, 101, -11, 91, 102, -7, 90, + 103, -2, 90, 104, 1, 89, 105, 5, 88, 106, 9, 88, + 107, 13, 87, 108, 17, 86, 109, 21, 86, 110, 25, 85, + 111, 30, 84, 111, 34, 84, 112, 38, 83, 113, 42, 82, + 114, 46, 82, 115, 50, 81, 116, 54, 80, 117, 58, 80, + 118, 62, 79, 119, 67, 78, 120, 71, 78, 121, 75, 77, + 95, -53, 96, 96, -49, 95, 97, -45, 94, 98, -41, 94, + 98, -37, 93, 99, -33, 92, 100, -29, 92, 101, -24, 91, + 102, -20, 90, 103, -16, 90, 104, -12, 89, 105, -8, 88, + 106, -4, 88, 107, 0, 87, 108, 3, 86, 109, 7, 86, + 110, 12, 85, 111, 16, 84, 112, 20, 84, 112, 24, 83, + 113, 28, 82, 114, 32, 82, 115, 36, 81, 116, 40, 80, + 117, 44, 80, 118, 49, 79, 119, 53, 78, 120, 57, 78, + 121, 61, 77, 122, 65, 76, 123, 69, 76, 124, 73, 75, + 97, -55, 94, 98, -51, 93, 99, -47, 93, 100, -42, 92, + 101, -38, 91, 102, -34, 91, 103, -30, 90, 104, -26, 89, + 105, -22, 89, 105, -18, 88, 106, -14, 87, 107, -10, 87, + 108, -5, 86, 109, -1, 85, 110, 2, 85, 111, 6, 84, + 112, 10, 83, 113, 14, 83, 114, 18, 82, 115, 22, 81, + 116, 27, 81, 117, 31, 80, 118, 35, 79, 119, 39, 79, + 119, 43, 78, 121, 47, 77, 121, 51, 77, 122, 55, 76, + 123, 59, 75, 124, 64, 75, 125, 68, 74, 126, 72, 73, + 99, -56, 92, 100, -52, 92, 101, -48, 91, 102, -43, 90, + 103, -39, 90, 104, -35, 89, 105, -31, 88, 106, -27, 88, + 107, -23, 87, 108, -19, 86, 109, -15, 86, 110, -11, 85, + 111, -6, 84, 112, -2, 84, 112, 1, 83, 113, 5, 82, + 114, 9, 82, 115, 13, 81, 116, 17, 80, 117, 21, 80, + 118, 26, 79, 119, 30, 78, 120, 34, 78, 121, 38, 77, + 122, 42, 76, 123, 46, 76, 124, 50, 75, 125, 54, 74, + 126, 58, 74, 127, 63, 73, 128, 67, 72, 128, 71, 72, + 102, -57, 91, 103, -53, 90, 104, -49, 89, 105, -45, 89, + 105, -41, 88, 106, -37, 87, 107, -33, 87, 108, -28, 86, + 109, -24, 85, 110, -20, 85, 111, -16, 84, 112, -12, 83, + 113, -8, 83, 114, -4, 82, 115, 0, 81, 116, 3, 81, + 117, 8, 80, 118, 12, 79, 119, 16, 79, 120, 20, 78, + 121, 24, 77, 121, 28, 77, 122, 32, 76, 123, 36, 75, + 124, 40, 75, 125, 45, 74, 126, 49, 73, 127, 53, 73, + 128, 57, 72, 129, 61, 71, 130, 65, 71, 131, 69, 70, + 104, -59, 89, 105, -55, 88, 106, -51, 88, 107, -46, 87, + 108, -42, 86, 109, -38, 86, 110, -34, 85, 111, -30, 84, + 112, -26, 84, 113, -22, 83, 113, -18, 82, 114, -14, 82, + 115, -9, 81, 116, -5, 80, 117, -1, 80, 118, 2, 79, + 119, 6, 78, 120, 10, 78, 121, 14, 77, 122, 18, 76, + 123, 23, 76, 124, 27, 75, 125, 31, 74, 126, 35, 74, + 127, 39, 73, 128, 43, 72, 128, 47, 72, 129, 51, 71, + 130, 55, 70, 131, 60, 70, 132, 64, 69, 133, 68, 68, + 106, -60, 87, 107, -56, 87, 108, -52, 86, 109, -47, 85, + 110, -43, 85, 111, -39, 84, 112, -35, 83, 113, -31, 83, + 114, -27, 82, 115, -23, 81, 116, -19, 81, 117, -15, 80, + 118, -10, 79, 119, -6, 79, 120, -2, 78, 120, 1, 77, + 121, 5, 77, 122, 9, 76, 123, 13, 75, 124, 17, 75, + 125, 22, 74, 126, 26, 73, 127, 30, 73, 128, 34, 72, + 129, 38, 71, 130, 42, 71, 131, 46, 70, 132, 50, 69, + 133, 54, 69, 134, 59, 68, 135, 63, 67, 135, 67, 67, + 109, -61, 86, 110, -57, 85, 111, -53, 84, 112, -49, 84, + 113, -45, 83, 113, -41, 82, 114, -37, 82, 115, -32, 81, + 116, -28, 80, 117, -24, 80, 118, -20, 79, 119, -16, 78, + 120, -12, 78, 121, -8, 77, 122, -4, 76, 123, 0, 76, + 124, 4, 75, 125, 8, 74, 126, 12, 74, 127, 16, 73, + 128, 20, 72, 128, 24, 72, 129, 28, 71, 130, 32, 70, + 131, 36, 70, 132, 41, 69, 133, 45, 68, 134, 49, 68, + 135, 53, 67, 136, 57, 66, 137, 61, 66, 138, 65, 65, + 111, -62, 84, 112, -58, 83, 113, -54, 83, 114, -50, 82, + 115, -46, 81, 116, -42, 81, 117, -38, 80, 118, -33, 79, + 119, -29, 79, 120, -25, 78, 120, -21, 77, 121, -17, 77, + 122, -13, 76, 123, -9, 75, 124, -5, 75, 125, -1, 74, + 126, 3, 73, 127, 7, 73, 128, 11, 72, 129, 15, 71, + 130, 19, 71, 131, 23, 70, 132, 27, 69, 133, 31, 69, + 134, 35, 68, 135, 40, 67, 136, 44, 67, 136, 48, 66, + 137, 52, 65, 138, 56, 65, 139, 60, 64, 140, 64, 63, + 113, -64, 82, 114, -60, 82, 115, -56, 81, 116, -51, 80, + 117, -47, 80, 118, -43, 79, 119, -39, 78, 120, -35, 78, + 121, -31, 77, 122, -27, 76, 123, -23, 76, 124, -19, 75, + 125, -14, 74, 126, -10, 74, 127, -6, 73, 127, -2, 72, + 129, 1, 72, 129, 5, 71, 130, 9, 70, 131, 13, 70, + 132, 18, 69, 133, 22, 68, 134, 26, 68, 135, 30, 67, + 136, 34, 66, 137, 38, 66, 138, 42, 65, 139, 46, 64, + 140, 50, 64, 141, 55, 63, 142, 59, 62, 143, 63, 62, + 116, -65, 81, 117, -61, 80, 118, -57, 79, 119, -53, 79, + 120, -49, 78, 120, -45, 77, 121, -41, 77, 122, -36, 76, + 123, -32, 75, 124, -28, 75, 125, -24, 74, 126, -20, 73, + 127, -16, 73, 128, -12, 72, 129, -8, 71, 130, -4, 71, + 131, 0, 70, 132, 4, 69, 133, 8, 69, 134, 12, 68, + 135, 16, 67, 136, 20, 67, 136, 24, 66, 137, 28, 65, + 138, 32, 65, 139, 37, 64, 140, 41, 63, 141, 45, 63, + 142, 49, 62, 143, 53, 61, 144, 57, 61, 145, 61, 60, + 118, -66, 79, 119, -62, 78, 120, -58, 78, 121, -54, 77, + 122, -50, 76, 123, -46, 76, 124, -42, 75, 125, -37, 74, + 126, -33, 74, 127, -29, 73, 128, -25, 72, 128, -21, 72, + 129, -17, 71, 130, -13, 70, 131, -9, 70, 132, -5, 69, + 133, 0, 68, 134, 3, 68, 135, 7, 67, 136, 11, 66, + 137, 15, 65, 138, 19, 65, 139, 23, 64, 140, 27, 64, + 141, 31, 63, 142, 36, 62, 143, 40, 62, 143, 44, 61, + 144, 48, 60, 145, 52, 59, 146, 56, 59, 147, 60, 58, + 121, -68, 77, 121, -64, 77, 122, -60, 76, 123, -55, 75, + 124, -51, 75, 125, -47, 74, 126, -43, 73, 127, -39, 73, + 128, -35, 72, 129, -31, 71, 130, -27, 71, 131, -23, 70, + 132, -18, 69, 133, -14, 69, 134, -10, 68, 135, -6, 67, + 136, -2, 67, 136, 1, 66, 137, 5, 65, 138, 9, 65, + 139, 14, 64, 140, 18, 63, 141, 22, 63, 142, 26, 62, + 143, 30, 61, 144, 34, 60, 145, 38, 60, 146, 42, 59, + 147, 46, 59, 148, 51, 58, 149, 55, 57, 150, 59, 57, + 123, -69, 76, 124, -65, 75, 125, -61, 74, 126, -57, 74, + 127, -53, 73, 128, -49, 72, 128, -45, 72, 129, -40, 71, + 130, -36, 70, 131, -32, 70, 132, -28, 69, 133, -24, 68, + 134, -20, 68, 135, -16, 67, 136, -12, 66, 137, -8, 66, + 138, -3, 65, 139, 0, 64, 140, 4, 64, 141, 8, 63, + 142, 12, 62, 143, 16, 61, 143, 20, 61, 144, 24, 60, + 145, 28, 60, 146, 33, 59, 147, 37, 58, 148, 41, 58, + 149, 45, 57, 150, 49, 56, 151, 53, 55, 152, 57, 55, + 125, -70, 74, 126, -66, 73, 127, -62, 73, 128, -58, 72, + 129, -54, 71, 130, -50, 71, 131, -46, 70, 132, -41, 69, + 133, -37, 69, 134, -33, 68, 135, -29, 67, 135, -25, 67, + 136, -21, 66, 137, -17, 65, 138, -13, 65, 139, -9, 64, + 140, -4, 63, 141, 0, 63, 142, 3, 62, 143, 7, 61, + 144, 11, 60, 145, 15, 60, 146, 19, 59, 147, 23, 59, + 148, 27, 58, 149, 32, 57, 150, 36, 56, 151, 40, 56, + 151, 44, 55, 152, 48, 54, 153, 52, 54, 154, 56, 53, + 128, -72, 72, 128, -68, 72, 129, -64, 71, 130, -59, 70, + 131, -55, 70, 132, -51, 69, 133, -47, 68, 134, -43, 67, + 135, -39, 67, 136, -35, 66, 137, -31, 66, 138, -27, 65, + 139, -22, 64, 140, -18, 64, 141, -14, 63, 142, -10, 62, + 143, -6, 61, 144, -2, 61, 144, 1, 60, 145, 5, 60, + 146, 10, 59, 147, 14, 58, 148, 18, 57, 149, 22, 57, + 150, 26, 56, 151, 30, 55, 152, 34, 55, 153, 38, 54, + 154, 42, 54, 155, 47, 53, 156, 51, 52, 157, 55, 51, + 130, -73, 71, 131, -69, 70, 132, -65, 69, 133, -61, 69, + 134, -57, 68, 135, -53, 67, 135, -49, 67, 137, -44, 66, + 137, -40, 65, 138, -36, 65, 139, -32, 64, 140, -28, 63, + 141, -24, 62, 142, -20, 62, 143, -16, 61, 144, -12, 61, + 145, -7, 60, 146, -3, 59, 147, 0, 59, 148, 4, 58, + 149, 8, 57, 150, 12, 56, 151, 16, 56, 151, 20, 55, + 152, 24, 55, 153, 29, 54, 154, 33, 53, 155, 37, 52, + 156, 41, 52, 157, 45, 51, 158, 49, 50, 159, 53, 50, + 132, -74, 69, 133, -70, 68, 134, -66, 68, 135, -62, 67, + 136, -58, 66, 137, -54, 66, 138, -50, 65, 139, -45, 64, + 140, -41, 63, 141, -37, 63, 142, -33, 62, 143, -29, 62, + 144, -25, 61, 144, -21, 60, 145, -17, 60, 146, -13, 59, + 147, -8, 58, 148, -4, 57, 149, 0, 57, 150, 3, 56, + 151, 7, 55, 152, 11, 55, 153, 15, 54, 154, 19, 53, + 155, 23, 53, 156, 28, 52, 157, 32, 51, 158, 36, 51, + 158, 40, 50, 159, 44, 49, 160, 48, 49, 161, 52, 48, + 135, -76, 67, 136, -72, 67, 136, -68, 66, 137, -63, 65, + 138, -59, 65, 139, -55, 64, 140, -51, 63, 141, -47, 62, + 142, -43, 62, 143, -39, 61, 144, -35, 61, 145, -31, 60, + 146, -26, 59, 147, -22, 58, 148, -18, 58, 149, -14, 57, + 150, -10, 56, 151, -6, 56, 151, -2, 55, 152, 1, 55, + 153, 6, 54, 154, 10, 53, 155, 14, 52, 156, 18, 52, + 157, 22, 51, 158, 26, 50, 159, 30, 50, 160, 34, 49, + 161, 38, 48, 162, 43, 48, 163, 47, 47, 164, 51, 46, + 137, -77, 66, 138, -73, 65, 139, -69, 64, 140, -65, 63, + 141, -61, 63, 142, -57, 62, 143, -53, 62, 144, -48, 61, + 144, -44, 60, 145, -40, 59, 146, -36, 59, 147, -32, 58, + 148, -28, 57, 149, -24, 57, 150, -20, 56, 151, -16, 56, + 152, -11, 55, 153, -7, 54, 154, -3, 53, 155, 0, 53, + 156, 4, 52, 157, 8, 51, 158, 12, 51, 158, 16, 50, + 159, 20, 49, 160, 25, 49, 161, 29, 48, 162, 33, 47, + 163, 37, 47, 164, 41, 46, 165, 45, 45, 166, 49, 45, + 139, -78, 64, 140, -74, 63, 141, -70, 63, 142, -66, 62, + 143, -62, 61, 144, -58, 61, 145, -54, 60, 146, -49, 59, + 147, -45, 58, 148, -41, 58, 149, -37, 57, 150, -33, 57, + 151, -29, 56, 151, -25, 55, 152, -21, 54, 153, -17, 54, + 154, -12, 53, 155, -8, 52, 156, -4, 52, 157, 0, 51, + 158, 3, 50, 159, 7, 50, 160, 11, 49, 161, 15, 48, + 162, 19, 48, 163, 24, 47, 164, 28, 46, 165, 32, 46, + 166, 36, 45, 167, 40, 44, 167, 44, 44, 168, 48, 43, + 142, -80, 62, 143, -76, 62, 143, -72, 61, 144, -67, 60, + 145, -63, 59, 146, -59, 59, 147, -55, 58, 148, -51, 57, + 149, -47, 57, 150, -43, 56, 151, -39, 55, 152, -35, 55, + 153, -30, 54, 154, -26, 53, 155, -22, 53, 156, -18, 52, + 157, -14, 51, 158, -10, 51, 159, -6, 50, 159, -2, 49, + 160, 2, 49, 161, 6, 48, 162, 10, 47, 163, 14, 47, + 164, 18, 46, 165, 22, 45, 166, 26, 45, 167, 30, 44, + 168, 34, 43, 169, 39, 43, 170, 43, 42, 171, 47, 41, + 145, -81, 60, 145, -77, 59, 146, -73, 59, 147, -69, 58, + 148, -65, 57, 149, -61, 57, 150, -57, 56, 151, -52, 55, + 152, -48, 55, 153, -44, 54, 154, -40, 53, 155, -36, 53, + 156, -32, 52, 157, -28, 51, 158, -24, 51, 159, -20, 50, + 160, -15, 49, 161, -11, 49, 161, -7, 48, 162, -3, 47, + 163, 0, 47, 164, 4, 46, 165, 8, 45, 166, 12, 45, + 167, 16, 44, 168, 21, 43, 169, 25, 43, 170, 29, 42, + 171, 33, 41, 172, 37, 41, 173, 41, 40, 174, 45, 39, + 147, -83, 58, 148, -79, 58, 149, -75, 57, 150, -70, 56, + 151, -66, 56, 152, -62, 55, 153, -58, 54, 154, -54, 54, + 154, -50, 53, 155, -46, 52, 156, -42, 52, 157, -38, 51, + 158, -33, 50, 159, -29, 50, 160, -25, 49, 161, -21, 48, + 162, -17, 48, 163, -13, 47, 164, -9, 46, 165, -5, 46, + 166, 0, 45, 167, 3, 44, 168, 7, 44, 168, 11, 43, + 169, 15, 42, 170, 19, 42, 171, 23, 41, 172, 27, 40, + 173, 31, 40, 174, 36, 39, 175, 40, 38, 176, 44, 38, + 149, -84, 57, 150, -80, 56, 151, -76, 55, 152, -72, 55, + 153, -68, 54, 154, -64, 53, 155, -60, 53, 156, -55, 52, + 157, -51, 51, 158, -47, 51, 159, -43, 50, 160, -39, 49, + 161, -35, 49, 161, -31, 48, 162, -27, 47, 163, -23, 47, + 164, -18, 46, 165, -14, 45, 166, -10, 45, 167, -6, 44, + 168, -2, 43, 169, 1, 43, 170, 5, 42, 171, 9, 41, + 172, 13, 41, 173, 18, 40, 174, 22, 39, 175, 26, 39, + 175, 30, 38, 177, 34, 37, 177, 38, 37, 178, 42, 36, + 152, -85, 55, 153, -81, 54, 153, -77, 54, 154, -73, 53, + 155, -69, 52, 156, -65, 52, 157, -61, 51, 158, -56, 50, + 159, -52, 50, 160, -48, 49, 161, -44, 48, 162, -40, 48, + 163, -36, 47, 164, -32, 46, 165, -28, 46, 166, -24, 45, + 167, -19, 44, 168, -15, 44, 168, -11, 43, 169, -7, 42, + 170, -3, 42, 171, 0, 41, 172, 4, 40, 173, 8, 40, + 174, 12, 39, 175, 17, 38, 176, 21, 38, 177, 25, 37, + 178, 29, 36, 179, 33, 36, 180, 37, 35, 181, 41, 34, + 154, -87, 53, 155, -83, 53, 156, -79, 52, 157, -74, 51, + 158, -70, 51, 159, -66, 50, 160, -62, 49, 161, -58, 49, + 161, -54, 48, 162, -50, 47, 163, -46, 47, 164, -42, 46, + 165, -37, 45, 166, -33, 45, 167, -29, 44, 168, -25, 43, + 169, -21, 43, 170, -17, 42, 171, -13, 41, 172, -9, 41, + 173, -4, 40, 174, 0, 39, 175, 3, 39, 176, 7, 38, + 176, 11, 37, 177, 15, 37, 178, 19, 36, 179, 23, 35, + 180, 27, 35, 181, 32, 34, 182, 36, 33, 183, 40, 33, + 156, -88, 52, 157, -84, 51, 158, -80, 50, 159, -75, 50, + 160, -71, 49, 161, -67, 48, 162, -63, 48, 163, -59, 47, + 164, -55, 46, 165, -51, 46, 166, -47, 45, 167, -43, 44, + 168, -38, 44, 169, -34, 43, 169, -30, 42, 170, -26, 42, + 171, -22, 41, 172, -18, 40, 173, -14, 40, 174, -10, 39, + 175, -5, 38, 176, -1, 38, 177, 2, 37, 178, 6, 36, + 179, 10, 36, 180, 14, 35, 181, 18, 34, 182, 22, 34, + 183, 26, 33, 184, 31, 32, 184, 35, 32, 185, 39, 31, + 159, -89, 50, 160, -85, 49, 160, -81, 49, 162, -77, 48, + 162, -73, 47, 163, -69, 47, 164, -65, 46, 165, -60, 45, + 166, -56, 45, 167, -52, 44, 168, -48, 43, 169, -44, 43, + 170, -40, 42, 171, -36, 41, 172, -32, 41, 173, -28, 40, + 174, -23, 39, 175, -19, 39, 176, -15, 38, 176, -11, 37, + 177, -7, 37, 178, -3, 36, 179, 0, 35, 180, 4, 35, + 181, 8, 34, 182, 13, 33, 183, 17, 33, 184, 21, 32, + 185, 25, 31, 186, 29, 31, 187, 33, 30, 188, 37, 29, + 161, -91, 48, 162, -87, 48, 163, -83, 47, 164, -78, 46, + 165, -74, 46, 166, -70, 45, 167, -66, 44, 168, -62, 44, + 169, -58, 43, 169, -54, 42, 170, -50, 42, 171, -46, 41, + 172, -41, 40, 173, -37, 40, 174, -33, 39, 175, -29, 38, + 176, -25, 38, 177, -21, 37, 178, -17, 36, 179, -13, 36, + 180, -8, 35, 181, -4, 34, 182, 0, 34, 183, 3, 33, + 183, 7, 32, 185, 11, 32, 185, 15, 31, 186, 19, 30, + 187, 23, 30, 188, 28, 29, 189, 32, 28, 190, 36, 28, + 163, -92, 47, 164, -88, 46, 165, -84, 45, 166, -79, 45, + 167, -75, 44, 168, -71, 43, 169, -67, 43, 170, -63, 42, + 171, -59, 41, 172, -55, 41, 173, -51, 40, 174, -47, 39, + 175, -42, 39, 176, -38, 38, 176, -34, 37, 177, -30, 37, + 178, -26, 36, 179, -22, 35, 180, -18, 35, 181, -14, 34, + 182, -9, 33, 183, -5, 33, 184, -1, 32, 185, 2, 31, + 186, 6, 31, 187, 10, 30, 188, 14, 29, 189, 18, 29, + 190, 22, 28, 191, 27, 27, 192, 31, 27, 192, 35, 26, + 166, -93, 45, 167, -89, 44, 168, -85, 44, 169, -81, 43, + 169, -77, 42, 170, -73, 42, 171, -69, 41, 172, -64, 40, + 173, -60, 40, 174, -56, 39, 175, -52, 38, 176, -48, 38, + 177, -44, 37, 178, -40, 36, 179, -36, 36, 180, -32, 35, + 181, -27, 34, 182, -23, 34, 183, -19, 33, 183, -15, 32, + 185, -11, 32, 185, -7, 31, 186, -3, 30, 187, 0, 30, + 188, 4, 29, 189, 9, 28, 190, 13, 28, 191, 17, 27, + 192, 21, 26, 193, 25, 26, 194, 29, 25, 195, 33, 24, + 168, -95, 43, 169, -91, 43, 170, -87, 42, 171, -82, 41, + 172, -78, 41, 173, -74, 40, 174, -70, 39, 175, -66, 39, + 176, -62, 38, 176, -58, 37, 177, -54, 37, 178, -50, 36, + 179, -45, 35, 180, -41, 35, 181, -37, 34, 182, -33, 33, + 183, -29, 33, 184, -25, 32, 185, -21, 31, 186, -17, 31, + 187, -12, 30, 188, -8, 29, 189, -4, 29, 190, 0, 28, + 191, 3, 27, 192, 7, 27, 192, 11, 26, 193, 15, 25, + 194, 19, 25, 195, 24, 24, 196, 28, 23, 197, 32, 23, + 170, -96, 42, 171, -92, 41, 172, -88, 40, 173, -83, 40, + 174, -79, 39, 175, -75, 38, 176, -71, 38, 177, -67, 37, + 178, -63, 36, 179, -59, 36, 180, -55, 35, 181, -51, 34, + 182, -46, 34, 183, -42, 33, 184, -38, 32, 184, -34, 32, + 185, -30, 31, 186, -26, 30, 187, -22, 30, 188, -18, 29, + 189, -13, 28, 190, -9, 28, 191, -5, 27, 192, -1, 26, + 193, 2, 26, 194, 6, 25, 195, 10, 24, 196, 14, 24, + 197, 18, 23, 198, 23, 22, 199, 27, 22, 199, 31, 21, + 173, -97, 40, 174, -93, 39, 175, -89, 39, 176, -85, 38, + 177, -81, 37, 177, -77, 37, 178, -73, 36, 179, -68, 35, + 180, -64, 35, 181, -60, 34, 182, -56, 33, 183, -52, 33, + 184, -48, 32, 185, -44, 31, 186, -40, 31, 187, -36, 30, + 188, -31, 29, 189, -27, 29, 190, -23, 28, 191, -19, 27, + 192, -15, 27, 192, -11, 26, 193, -7, 25, 194, -3, 25, + 195, 0, 24, 196, 5, 23, 197, 9, 23, 198, 13, 22, + 199, 17, 21, 200, 21, 21, 201, 25, 20, 202, 29, 19, + 175, -99, 38, 176, -95, 38, 177, -91, 37, 178, -86, 36, + 179, -82, 36, 180, -78, 35, 181, -74, 34, 182, -70, 34, + 183, -66, 33, 184, -62, 32, 184, -58, 32, 185, -54, 31, + 186, -49, 30, 187, -45, 30, 188, -41, 29, 189, -37, 28, + 190, -33, 28, 191, -29, 27, 192, -25, 26, 193, -21, 26, + 194, -16, 25, 195, -12, 24, 196, -8, 24, 197, -4, 23, + 198, 0, 22, 199, 3, 22, 200, 7, 21, 200, 11, 20, + 201, 15, 20, 202, 20, 19, 203, 24, 18, 204, 28, 18, + 177, -100, 37, 178, -96, 36, 179, -92, 35, 180, -87, 35, + 181, -83, 34, 182, -79, 33, 183, -75, 33, 184, -71, 32, + 185, -67, 31, 186, -63, 31, 187, -59, 30, 188, -55, 29, + 189, -50, 29, 190, -46, 28, 191, -42, 27, 191, -38, 27, + 193, -34, 26, 193, -30, 25, 194, -26, 25, 195, -22, 24, + 196, -17, 23, 197, -13, 23, 198, -9, 22, 199, -5, 21, + 200, -1, 21, 201, 2, 20, 202, 6, 19, 203, 10, 19, + 204, 14, 18, 205, 19, 17, 206, 23, 17, 207, 27, 16, + 180, -101, 35, 181, -97, 34, 182, -93, 34, 183, -89, 33, + 184, -85, 32, 184, -81, 32, 185, -77, 31, 186, -72, 30, + 187, -68, 30, 188, -64, 29, 189, -60, 28, 190, -56, 28, + 191, -52, 27, 192, -48, 26, 193, -44, 26, 194, -40, 25, + 195, -35, 24, 196, -31, 24, 197, -27, 23, 198, -23, 22, + 199, -19, 22, 200, -15, 21, 200, -11, 20, 201, -7, 20, + 202, -3, 19, 203, 1, 18, 204, 5, 18, 205, 9, 17, + 206, 13, 16, 207, 17, 16, 208, 21, 15, 209, 25, 14, + 182, -103, 33, 183, -99, 33, 184, -95, 32, 185, -90, 31, + 186, -86, 31, 187, -82, 30, 188, -78, 29, 189, -74, 29, + 190, -70, 28, 191, -66, 27, 191, -62, 27, 192, -58, 26, + 193, -53, 25, 194, -49, 25, 195, -45, 24, 196, -41, 23, + 197, -37, 23, 198, -33, 22, 199, -29, 21, 200, -25, 21, + 201, -20, 20, 202, -16, 19, 203, -12, 19, 204, -8, 18, + 205, -4, 17, 206, 0, 17, 207, 3, 16, 207, 7, 15, + 208, 11, 15, 209, 16, 14, 210, 20, 13, 211, 24, 13, + 184, -104, 32, 185, -100, 31, 186, -96, 30, 187, -91, 30, + 188, -87, 29, 189, -83, 28, 190, -79, 28, 191, -75, 27, + 192, -71, 26, 193, -67, 26, 194, -63, 25, 195, -59, 24, + 196, -54, 24, 197, -50, 23, 198, -46, 22, 199, -42, 22, + 200, -38, 21, 200, -34, 20, 201, -30, 20, 202, -26, 19, + 203, -21, 18, 204, -17, 18, 205, -13, 17, 206, -9, 16, + 207, -5, 16, 208, -1, 15, 209, 2, 14, 210, 6, 14, + 211, 10, 13, 212, 15, 12, 213, 19, 12, 214, 23, 11, + 187, -105, 30, 188, -101, 29, 189, -97, 29, 190, -93, 28, + 191, -89, 27, 192, -85, 27, 192, -81, 26, 193, -76, 25, + 194, -72, 25, 195, -68, 24, 196, -64, 23, 197, -60, 23, + 198, -56, 22, 199, -52, 21, 200, -48, 21, 201, -44, 20, + 202, -39, 19, 203, -35, 19, 204, -31, 18, 205, -27, 17, + 206, -23, 17, 207, -19, 16, 207, -15, 15, 208, -11, 15, + 209, -7, 14, 210, -2, 13, 211, 1, 13, 212, 5, 12, + 213, 9, 11, 214, 13, 10, 215, 17, 10, 216, 21, 9, + 189, -107, 28, 190, -103, 28, 191, -99, 27, 192, -94, 26, + 193, -90, 26, 194, -86, 25, 195, -82, 24, 196, -78, 24, + 197, -74, 23, 198, -70, 22, 199, -66, 22, 199, -62, 21, + 200, -57, 20, 201, -53, 20, 202, -49, 19, 203, -45, 18, + 204, -41, 18, 205, -37, 17, 206, -33, 16, 207, -29, 16, + 208, -24, 15, 209, -20, 14, 210, -16, 14, 211, -12, 13, + 212, -8, 12, 213, -4, 11, 214, 0, 11, 215, 3, 10, + 215, 7, 10, 216, 12, 9, 217, 16, 8, 218, 20, 8, + 192, -108, 27, 192, -104, 26, 193, -100, 25, 194, -95, 25, + 195, -91, 24, 196, -87, 23, 197, -83, 23, 198, -79, 22, + 199, -75, 21, 200, -71, 21, 201, -67, 20, 202, -63, 19, + 203, -58, 19, 204, -54, 18, 205, -50, 17, 206, -46, 17, + 207, -42, 16, 208, -38, 15, 208, -34, 15, 209, -30, 14, + 210, -25, 13, 211, -21, 13, 212, -17, 12, 213, -13, 11, + 214, -9, 11, 215, -5, 10, 216, -1, 9, 217, 2, 9, + 218, 6, 8, 219, 11, 7, 220, 15, 6, 221, 19, 6, + 194, -110, 24, 195, -106, 24, 196, -102, 23, 197, -97, 22, + 198, -93, 22, 199, -89, 21, 200, -85, 20, 201, -81, 20, + 202, -77, 19, 203, -73, 18, 204, -69, 18, 205, -65, 17, + 206, -60, 16, 207, -56, 16, 208, -52, 15, 208, -48, 14, + 210, -44, 14, 210, -40, 13, 211, -36, 12, 212, -32, 12, + 213, -27, 11, 214, -23, 10, 215, -19, 10, 216, -15, 9, + 217, -11, 8, 218, -7, 8, 219, -3, 7, 220, 0, 6, + 221, 4, 6, 222, 9, 5, 223, 13, 4, 224, 17, 4, + 197, -111, 23, 198, -107, 22, 199, -103, 22, 200, -98, 21, + 201, -94, 20, 201, -90, 19, 202, -86, 19, 203, -82, 18, + 204, -78, 17, 205, -74, 17, 206, -70, 16, 207, -66, 15, + 208, -61, 15, 209, -57, 14, 210, -53, 13, 211, -49, 13, + 212, -45, 12, 213, -41, 11, 214, -37, 11, 215, -33, 10, + 216, -28, 9, 217, -24, 9, 217, -20, 8, 218, -16, 7, + 219, -12, 7, 220, -8, 6, 221, -4, 5, 222, 0, 5, + 223, 3, 4, 224, 8, 3, 225, 12, 3, 226, 16, 2, + 199, -112, 21, 200, -108, 20, 201, -104, 20, 202, -100, 19, + 203, -96, 18, 204, -92, 18, 205, -88, 17, 206, -83, 16, + 207, -79, 16, 208, -75, 15, 209, -71, 14, 209, -67, 14, + 210, -63, 13, 211, -59, 12, 212, -55, 12, 213, -51, 11, + 214, -46, 10, 215, -42, 10, 216, -38, 9, 217, -34, 8, + 218, -30, 8, 219, -26, 7, 220, -22, 6, 221, -18, 6, + 222, -14, 5, 223, -9, 4, 224, -5, 4, 224, -1, 3, + 225, 2, 2, 226, 6, 2, 227, 10, 1, 228, 14, 0, + 202, -114, 19, 202, -110, 19, 203, -106, 18, 204, -101, 17, + 205, -97, 17, 206, -93, 16, 207, -89, 15, 208, -85, 15, + 209, -81, 14, 210, -77, 13, 211, -73, 13, 212, -69, 12, + 213, -64, 11, 214, -60, 11, 215, -56, 10, 216, -52, 9, + 217, -48, 9, 217, -44, 8, 218, -40, 7, 219, -36, 7, + 220, -31, 6, 221, -27, 5, 222, -23, 5, 223, -19, 4, + 224, -15, 3, 225, -11, 3, 226, -7, 2, 227, -3, 1, + 228, 0, 1, 229, 5, 0, 230, 9, 0, 231, 13, 0, + 204, -115, 18, 205, -111, 17, 206, -107, 16, 207, -102, 16, + 208, -98, 15, 209, -94, 14, 209, -90, 14, 210, -86, 13, + 211, -82, 12, 212, -78, 12, 213, -74, 11, 214, -70, 10, + 215, -65, 10, 216, -61, 9, 217, -57, 8, 218, -53, 8, + 219, -49, 7, 220, -45, 6, 221, -41, 6, 222, -37, 5, + 223, -32, 4, 224, -28, 4, 225, -24, 3, 225, -20, 2, + 226, -16, 2, 227, -12, 1, 228, -8, 0, 229, -4, 0, + 230, 0, 0, 231, 4, -1, 232, 8, -1, 233, 12, -2, + 206, -116, 16, 207, -112, 15, 208, -108, 15, 209, -104, 14, + 210, -100, 13, 211, -96, 13, 212, -92, 12, 213, -87, 11, + 214, -83, 11, 215, -79, 10, 216, -75, 9, 216, -71, 9, + 218, -67, 8, 218, -63, 7, 219, -59, 7, 220, -55, 6, + 221, -50, 5, 222, -46, 5, 223, -42, 4, 224, -38, 3, + 225, -34, 3, 226, -30, 2, 227, -26, 1, 228, -22, 1, + 229, -18, 0, 230, -13, 0, 231, -9, 0, 232, -5, -1, + 232, -1, -2, 233, 2, -2, 234, 6, -3, 235, 10, -4, + 209, -117, 14, 209, -113, 14, 210, -109, 13, 211, -105, 12, + 212, -101, 12, 213, -97, 11, 214, -93, 10, 215, -88, 10, + 216, -84, 9, 217, -80, 8, 218, -76, 8, 219, -72, 7, + 220, -68, 6, 221, -64, 6, 222, -60, 5, 223, -56, 4, + 224, -51, 4, 225, -47, 3, 225, -43, 2, 226, -39, 2, + 227, -35, 1, 228, -31, 0, 229, -27, 0, 230, -23, 0, + 231, -19, -1, 232, -14, -1, 233, -10, -2, 234, -6, -3, + 235, -2, -3, 236, 1, -4, 237, 5, -5, 238, 9, -5, + 211, -119, 13, 212, -115, 12, 213, -111, 11, 214, -106, 11, + 215, -102, 10, 216, -98, 9, 216, -94, 9, 218, -90, 8, + 218, -86, 7, 219, -82, 7, 220, -78, 6, 221, -74, 5, + 222, -69, 5, 223, -65, 4, 224, -61, 3, 225, -57, 3, + 226, -53, 2, 227, -49, 1, 228, -45, 1, 229, -41, 0, + 230, -36, 0, 231, -32, 0, 232, -28, -1, 232, -24, -2, + 233, -20, -2, 234, -16, -3, 235, -12, -4, 236, -8, -4, + 237, -4, -5, 238, 0, -6, 239, 4, -6, 240, 8, -7, + 213, -120, 11, 214, -116, 10, 215, -112, 10, 216, -108, 9, + 217, -104, 8, 218, -100, 8, 219, -96, 7, 220, -91, 6, + 221, -87, 6, 222, -83, 5, 223, -79, 4, 224, -75, 4, + 225, -71, 3, 225, -67, 2, 226, -63, 2, 227, -59, 1, + 228, -54, 0, 229, -50, 0, 230, -46, 0, 231, -42, -1, + 232, -38, -1, 233, -34, -2, 234, -30, -3, 235, -26, -3, + 236, -22, -4, 237, -17, -5, 238, -13, -5, 239, -9, -6, + 239, -5, -7, 241, -1, -7, 241, 2, -8, 242, 6, -9, + 216, -121, 9, 217, -117, 9, 217, -113, 8, 218, -109, 7, + 219, -105, 7, 220, -101, 6, 221, -97, 5, 222, -92, 5, + 223, -88, 4, 224, -84, 3, 225, -80, 3, 226, -76, 2, + 227, -72, 1, 228, -68, 1, 229, -64, 0, 230, -60, 0, + 231, -55, 0, 232, -51, -1, 232, -47, -2, 233, -43, -2, + 234, -39, -3, 235, -35, -4, 236, -31, -4, 237, -27, -5, + 238, -23, -6, 239, -18, -6, 240, -14, -7, 241, -10, -8, + 242, -6, -8, 243, -2, -9, 244, 1, -10, 245, 5, -10, + 218, -123, 8, 219, -119, 7, 220, -115, 6, 221, -110, 6, + 222, -106, 5, 223, -102, 4, 224, -98, 4, 225, -94, 3, + 225, -90, 2, 226, -86, 2, 227, -82, 1, 228, -78, 0, + 229, -73, 0, 230, -69, 0, 231, -65, -1, 232, -61, -1, + 233, -57, -2, 234, -53, -3, 235, -49, -3, 236, -45, -4, + 237, -40, -5, 238, -36, -5, 239, -32, -6, 240, -28, -7, + 240, -24, -7, 241, -20, -8, 242, -16, -9, 243, -12, -9, + 244, -8, -10, 245, -3, -11, 246, 0, -11, 247, 4, -12, + 71, -40, 119, 72, -36, 118, 73, -32, 118, 74, -27, 117, + 75, -23, 116, 76, -19, 116, 77, -15, 115, 78, -11, 114, + 78, -7, 114, 79, -3, 113, 80, 0, 112, 81, 4, 112, + 82, 9, 111, 83, 13, 110, 84, 17, 110, 85, 21, 109, + 86, 25, 108, 87, 29, 108, 88, 33, 107, 89, 37, 106, + 90, 42, 106, 91, 46, 105, 92, 50, 104, 93, 54, 104, + 93, 58, 103, 94, 62, 102, 95, 66, 102, 96, 70, 101, + 97, 74, 100, 98, 79, 100, 99, 83, 99, 100, 87, 98, + 73, -41, 117, 74, -37, 117, 75, -33, 116, 76, -29, 115, + 77, -25, 115, 78, -21, 114, 79, -17, 113, 80, -12, 113, + 81, -8, 112, 82, -4, 111, 83, 0, 111, 84, 3, 110, + 85, 7, 109, 86, 11, 109, 86, 15, 108, 87, 19, 107, + 88, 24, 107, 89, 28, 106, 90, 32, 105, 91, 36, 105, + 92, 40, 104, 93, 44, 103, 94, 48, 103, 95, 52, 102, + 96, 56, 101, 97, 61, 101, 98, 65, 100, 99, 69, 99, + 100, 73, 99, 101, 77, 98, 101, 81, 97, 102, 85, 97, + 76, -42, 116, 77, -38, 115, 77, -34, 114, 79, -30, 114, + 79, -26, 113, 80, -22, 112, 81, -18, 112, 82, -13, 111, + 83, -9, 110, 84, -5, 110, 85, -1, 109, 86, 2, 108, + 87, 6, 108, 88, 10, 107, 89, 14, 106, 90, 18, 106, + 91, 23, 105, 92, 27, 104, 93, 31, 104, 93, 35, 103, + 94, 39, 102, 95, 43, 102, 96, 47, 101, 97, 51, 100, + 98, 55, 100, 99, 60, 99, 100, 64, 98, 101, 68, 98, + 102, 72, 97, 103, 76, 96, 104, 80, 96, 105, 84, 95, + 78, -44, 114, 79, -40, 113, 80, -36, 113, 81, -31, 112, + 82, -27, 111, 83, -23, 111, 84, -19, 110, 85, -15, 109, + 86, -11, 109, 86, -7, 108, 87, -3, 107, 88, 0, 107, + 89, 5, 106, 90, 9, 105, 91, 13, 105, 92, 17, 104, + 93, 21, 103, 94, 25, 103, 95, 29, 102, 96, 33, 101, + 97, 38, 101, 98, 42, 100, 99, 46, 99, 100, 50, 99, + 100, 54, 98, 101, 58, 97, 102, 62, 97, 103, 66, 96, + 104, 70, 95, 105, 75, 95, 106, 79, 94, 107, 83, 93, + 80, -45, 112, 81, -41, 112, 82, -37, 111, 83, -33, 110, + 84, -29, 110, 85, -25, 109, 86, -21, 108, 87, -16, 108, + 88, -12, 107, 89, -8, 106, 90, -4, 106, 91, 0, 105, + 92, 3, 104, 93, 7, 104, 93, 11, 103, 94, 15, 102, + 95, 20, 102, 96, 24, 101, 97, 28, 100, 98, 32, 100, + 99, 36, 99, 100, 40, 98, 101, 44, 98, 102, 48, 97, + 103, 52, 96, 104, 57, 96, 105, 61, 95, 106, 65, 94, + 107, 69, 94, 108, 73, 93, 109, 77, 92, 109, 81, 92, + 83, -46, 111, 84, -42, 110, 85, -38, 109, 86, -34, 109, + 86, -30, 108, 87, -26, 107, 88, -22, 107, 89, -17, 106, + 90, -13, 105, 91, -9, 105, 92, -5, 104, 93, -1, 103, + 94, 2, 103, 95, 6, 102, 96, 10, 101, 97, 14, 101, + 98, 19, 100, 99, 23, 99, 100, 27, 99, 100, 31, 98, + 102, 35, 97, 102, 39, 97, 103, 43, 96, 104, 47, 95, + 105, 51, 95, 106, 56, 94, 107, 60, 93, 108, 64, 93, + 109, 68, 92, 110, 72, 91, 111, 76, 91, 112, 80, 90, + 85, -48, 109, 86, -44, 108, 87, -40, 108, 88, -35, 107, + 89, -31, 106, 90, -27, 106, 91, -23, 105, 92, -19, 104, + 93, -15, 104, 93, -11, 103, 94, -7, 102, 95, -3, 102, + 96, 1, 101, 97, 5, 100, 98, 9, 100, 99, 13, 99, + 100, 17, 98, 101, 21, 98, 102, 25, 97, 103, 29, 96, + 104, 34, 96, 105, 38, 95, 106, 42, 94, 107, 46, 94, + 108, 50, 93, 109, 54, 92, 109, 58, 92, 110, 62, 91, + 111, 66, 90, 112, 71, 90, 113, 75, 89, 114, 79, 88, + 87, -49, 107, 88, -45, 107, 89, -41, 106, 90, -37, 105, + 91, -33, 105, 92, -29, 104, 93, -25, 103, 94, -20, 103, + 95, -16, 102, 96, -12, 101, 97, -8, 101, 98, -4, 100, + 99, 0, 99, 100, 3, 99, 101, 7, 98, 101, 11, 97, + 102, 16, 97, 103, 20, 96, 104, 24, 95, 105, 28, 95, + 106, 32, 94, 107, 36, 93, 108, 40, 93, 109, 44, 92, + 110, 48, 91, 111, 53, 91, 112, 57, 90, 113, 61, 89, + 114, 65, 89, 115, 69, 88, 116, 73, 87, 116, 77, 87, + 90, -50, 106, 91, -46, 105, 92, -42, 104, 93, -38, 104, + 94, -34, 103, 94, -30, 102, 95, -26, 102, 96, -21, 101, + 97, -17, 100, 98, -13, 100, 99, -9, 99, 100, -5, 98, + 101, -1, 98, 102, 2, 97, 103, 6, 96, 104, 10, 96, + 105, 15, 95, 106, 19, 94, 107, 23, 94, 108, 27, 93, + 109, 31, 92, 109, 35, 92, 110, 39, 91, 111, 43, 90, + 112, 47, 90, 113, 52, 89, 114, 56, 88, 115, 60, 88, + 116, 64, 87, 117, 68, 86, 118, 72, 86, 119, 76, 85, + 92, -52, 104, 93, -48, 103, 94, -44, 103, 95, -39, 102, + 96, -35, 101, 97, -31, 101, 98, -27, 100, 99, -23, 99, + 100, -19, 99, 101, -15, 98, 101, -11, 97, 102, -7, 97, + 103, -2, 96, 104, 1, 95, 105, 5, 95, 106, 9, 94, + 107, 13, 93, 108, 17, 93, 109, 21, 92, 110, 25, 91, + 111, 30, 91, 112, 34, 90, 113, 38, 89, 114, 42, 89, + 115, 46, 88, 116, 50, 87, 116, 54, 87, 117, 58, 86, + 118, 62, 85, 119, 67, 84, 120, 71, 84, 121, 75, 83, + 94, -53, 102, 95, -49, 102, 96, -45, 101, 97, -41, 100, + 98, -37, 100, 99, -33, 99, 100, -29, 98, 101, -24, 98, + 102, -20, 97, 103, -16, 96, 104, -12, 96, 105, -8, 95, + 106, -4, 94, 107, 0, 94, 108, 3, 93, 108, 7, 92, + 109, 12, 92, 110, 16, 91, 111, 20, 90, 112, 24, 90, + 113, 28, 89, 114, 32, 88, 115, 36, 88, 116, 40, 87, + 117, 44, 86, 118, 49, 86, 119, 53, 85, 120, 57, 84, + 121, 61, 84, 122, 65, 83, 123, 69, 82, 124, 73, 82, + 97, -55, 100, 98, -51, 100, 99, -47, 99, 100, -42, 98, + 101, -38, 97, 102, -34, 97, 103, -30, 96, 104, -26, 95, + 105, -22, 95, 106, -18, 94, 107, -14, 93, 108, -10, 93, + 109, -5, 92, 110, -1, 91, 110, 2, 91, 111, 6, 90, + 112, 10, 89, 113, 14, 89, 114, 18, 88, 115, 22, 87, + 116, 27, 87, 117, 31, 86, 118, 35, 85, 119, 39, 85, + 120, 43, 84, 121, 47, 83, 122, 51, 83, 123, 55, 82, + 124, 59, 81, 125, 64, 81, 126, 68, 80, 126, 72, 79, + 100, -56, 98, 101, -52, 98, 102, -48, 97, 103, -44, 96, + 103, -40, 96, 104, -36, 95, 105, -32, 94, 106, -27, 94, + 107, -23, 93, 108, -19, 92, 109, -15, 92, 110, -11, 91, + 111, -7, 90, 112, -3, 90, 113, 0, 89, 114, 4, 88, + 115, 9, 88, 116, 13, 87, 117, 17, 86, 118, 21, 86, + 119, 25, 85, 119, 29, 84, 120, 33, 84, 121, 37, 83, + 122, 41, 82, 123, 46, 82, 124, 50, 81, 125, 54, 80, + 126, 58, 80, 127, 62, 79, 128, 66, 78, 129, 70, 78, + 102, -57, 97, 103, -53, 96, 104, -49, 96, 105, -45, 95, + 106, -41, 94, 107, -37, 93, 108, -33, 93, 109, -28, 92, + 110, -24, 91, 111, -20, 91, 111, -16, 90, 112, -12, 89, + 113, -8, 89, 114, -4, 88, 115, 0, 87, 116, 3, 87, + 117, 8, 86, 118, 12, 85, 119, 16, 85, 120, 20, 84, + 121, 24, 83, 122, 28, 83, 123, 32, 82, 124, 36, 81, + 125, 40, 81, 126, 45, 80, 126, 49, 79, 127, 53, 79, + 128, 57, 78, 129, 61, 77, 130, 65, 77, 131, 69, 76, + 104, -59, 95, 105, -55, 94, 106, -51, 94, 107, -46, 93, + 108, -42, 92, 109, -38, 92, 110, -34, 91, 111, -30, 90, + 112, -26, 90, 113, -22, 89, 114, -18, 88, 115, -14, 88, + 116, -9, 87, 117, -5, 86, 118, -1, 86, 118, 2, 85, + 119, 6, 84, 120, 10, 84, 121, 14, 83, 122, 18, 82, + 123, 23, 82, 124, 27, 81, 125, 31, 80, 126, 35, 80, + 127, 39, 79, 128, 43, 78, 129, 47, 78, 130, 51, 77, + 131, 55, 76, 132, 60, 76, 133, 64, 75, 133, 68, 74, + 107, -60, 93, 108, -56, 93, 109, -52, 92, 110, -48, 91, + 111, -44, 91, 111, -40, 90, 112, -36, 89, 113, -31, 89, + 114, -27, 88, 115, -23, 87, 116, -19, 87, 117, -15, 86, + 118, -11, 85, 119, -7, 85, 120, -3, 84, 121, 0, 83, + 122, 5, 83, 123, 9, 82, 124, 13, 81, 125, 17, 81, + 126, 21, 80, 126, 25, 79, 127, 29, 79, 128, 33, 78, + 129, 37, 77, 130, 42, 77, 131, 46, 76, 132, 50, 75, + 133, 54, 75, 134, 58, 74, 135, 62, 73, 136, 66, 73, + 109, -61, 92, 110, -57, 91, 111, -53, 90, 112, -49, 90, + 113, -45, 89, 114, -41, 88, 115, -37, 88, 116, -32, 87, + 117, -28, 86, 118, -24, 86, 118, -20, 85, 119, -16, 84, + 120, -12, 84, 121, -8, 83, 122, -4, 82, 123, 0, 82, + 124, 4, 81, 125, 8, 80, 126, 12, 80, 127, 16, 79, + 128, 20, 78, 129, 24, 78, 130, 28, 77, 131, 32, 76, + 132, 36, 76, 133, 41, 75, 134, 45, 74, 134, 49, 74, + 135, 53, 73, 136, 57, 72, 137, 61, 72, 138, 65, 71, + 111, -63, 90, 112, -59, 89, 113, -55, 89, 114, -50, 88, + 115, -46, 87, 116, -42, 87, 117, -38, 86, 118, -34, 85, + 119, -30, 85, 120, -26, 84, 121, -22, 83, 122, -18, 83, + 123, -13, 82, 124, -9, 81, 125, -5, 81, 125, -1, 80, + 127, 2, 79, 127, 6, 79, 128, 10, 78, 129, 14, 77, + 130, 19, 77, 131, 23, 76, 132, 27, 75, 133, 31, 75, + 134, 35, 74, 135, 39, 73, 136, 43, 73, 137, 47, 72, + 138, 51, 71, 139, 56, 71, 140, 60, 70, 141, 64, 69, + 114, -64, 88, 115, -60, 88, 116, -56, 87, 117, -52, 86, + 118, -48, 86, 118, -44, 85, 119, -40, 84, 120, -35, 84, + 121, -31, 83, 122, -27, 82, 123, -23, 82, 124, -19, 81, + 125, -15, 80, 126, -11, 80, 127, -7, 79, 128, -3, 78, + 129, 1, 78, 130, 5, 77, 131, 9, 76, 132, 13, 76, + 133, 17, 75, 134, 21, 74, 134, 25, 74, 135, 29, 73, + 136, 33, 72, 137, 38, 72, 138, 42, 71, 139, 46, 70, + 140, 50, 70, 141, 54, 69, 142, 58, 68, 143, 62, 68, + 116, -65, 87, 117, -61, 86, 118, -57, 85, 119, -53, 85, + 120, -49, 84, 121, -45, 83, 122, -41, 83, 123, -36, 82, + 124, -32, 81, 125, -28, 81, 126, -24, 80, 126, -20, 79, + 127, -16, 79, 128, -12, 78, 129, -8, 77, 130, -4, 77, + 131, 0, 76, 132, 4, 75, 133, 8, 75, 134, 12, 74, + 135, 16, 73, 136, 20, 73, 137, 24, 72, 138, 28, 71, + 139, 32, 71, 140, 37, 70, 141, 41, 69, 141, 45, 69, + 142, 49, 68, 143, 53, 67, 144, 57, 67, 145, 61, 66, + 119, -67, 85, 119, -63, 84, 120, -59, 84, 121, -54, 83, + 122, -50, 82, 123, -46, 82, 124, -42, 81, 125, -38, 80, + 126, -34, 80, 127, -30, 79, 128, -26, 78, 129, -22, 78, + 130, -17, 77, 131, -13, 76, 132, -9, 76, 133, -5, 75, + 134, -1, 74, 134, 2, 74, 135, 6, 73, 136, 10, 72, + 137, 15, 72, 138, 19, 71, 139, 23, 70, 140, 27, 70, + 141, 31, 69, 142, 35, 68, 143, 39, 68, 144, 43, 67, + 145, 47, 66, 146, 52, 66, 147, 56, 65, 148, 60, 64, + 121, -68, 83, 122, -64, 83, 123, -60, 82, 124, -55, 81, + 125, -51, 81, 126, -47, 80, 126, -43, 79, 127, -39, 79, + 128, -35, 78, 129, -31, 77, 130, -27, 77, 131, -23, 76, + 132, -18, 75, 133, -14, 75, 134, -10, 74, 135, -6, 73, + 136, -2, 73, 137, 1, 72, 138, 5, 71, 139, 9, 71, + 140, 14, 70, 141, 18, 69, 141, 22, 69, 142, 26, 68, + 143, 30, 67, 144, 34, 67, 145, 38, 66, 146, 42, 65, + 147, 46, 65, 148, 51, 64, 149, 55, 63, 150, 59, 63, + 123, -69, 82, 124, -65, 81, 125, -61, 80, 126, -57, 80, + 127, -53, 79, 128, -49, 78, 129, -45, 78, 130, -40, 77, + 131, -36, 76, 132, -32, 76, 133, -28, 75, 133, -24, 74, + 134, -20, 74, 135, -16, 73, 136, -12, 72, 137, -8, 72, + 138, -3, 71, 139, 0, 70, 140, 4, 70, 141, 8, 69, + 142, 12, 68, 143, 16, 68, 144, 20, 67, 145, 24, 66, + 146, 28, 66, 147, 33, 65, 148, 37, 64, 149, 41, 64, + 149, 45, 63, 150, 49, 62, 151, 53, 62, 152, 57, 61, + 126, -71, 80, 126, -67, 79, 127, -63, 79, 128, -58, 78, + 129, -54, 77, 130, -50, 77, 131, -46, 76, 132, -42, 75, + 133, -38, 75, 134, -34, 74, 135, -30, 73, 136, -26, 73, + 137, -21, 72, 138, -17, 71, 139, -13, 71, 140, -9, 70, + 141, -5, 69, 142, -1, 69, 142, 2, 68, 143, 6, 67, + 144, 11, 67, 145, 15, 66, 146, 19, 65, 147, 23, 65, + 148, 27, 64, 149, 31, 63, 150, 35, 63, 151, 39, 62, + 152, 43, 61, 153, 48, 61, 154, 52, 60, 155, 56, 59, + 128, -72, 78, 129, -68, 78, 130, -64, 77, 131, -59, 76, + 132, -55, 76, 133, -51, 75, 133, -47, 74, 135, -43, 74, + 135, -39, 73, 136, -35, 72, 137, -31, 72, 138, -27, 71, + 139, -22, 70, 140, -18, 70, 141, -14, 69, 142, -10, 68, + 143, -6, 68, 144, -2, 67, 145, 1, 66, 146, 5, 66, + 147, 10, 65, 148, 14, 64, 149, 18, 64, 149, 22, 63, + 150, 26, 62, 151, 30, 62, 152, 34, 61, 153, 38, 60, + 154, 42, 60, 155, 47, 59, 156, 51, 58, 157, 55, 58, + 130, -73, 77, 131, -69, 76, 132, -65, 75, 133, -61, 75, + 134, -57, 74, 135, -53, 73, 136, -49, 73, 137, -44, 72, + 138, -40, 71, 139, -36, 71, 140, -32, 70, 141, -28, 69, + 142, -24, 69, 142, -20, 68, 143, -16, 67, 144, -12, 67, + 145, -7, 66, 146, -3, 65, 147, 0, 65, 148, 4, 64, + 149, 8, 63, 150, 12, 63, 151, 16, 62, 152, 20, 61, + 153, 24, 61, 154, 29, 60, 155, 33, 59, 156, 37, 59, + 156, 41, 58, 157, 45, 57, 158, 49, 57, 159, 53, 56, + 133, -75, 75, 134, -71, 74, 134, -67, 74, 135, -62, 73, + 136, -58, 72, 137, -54, 72, 138, -50, 71, 139, -46, 70, + 140, -42, 70, 141, -38, 69, 142, -34, 68, 143, -30, 68, + 144, -25, 67, 145, -21, 66, 146, -17, 66, 147, -13, 65, + 148, -9, 64, 149, -5, 64, 149, -1, 63, 150, 2, 62, + 151, 7, 62, 152, 11, 61, 153, 15, 60, 154, 19, 60, + 155, 23, 59, 156, 27, 58, 157, 31, 58, 158, 35, 57, + 159, 39, 56, 160, 44, 56, 161, 48, 55, 162, 52, 54, + 135, -76, 73, 136, -72, 73, 137, -68, 72, 138, -63, 71, + 139, -59, 71, 140, -55, 70, 141, -51, 69, 142, -47, 69, + 142, -43, 68, 143, -39, 67, 144, -35, 67, 145, -31, 66, + 146, -26, 65, 147, -22, 65, 148, -18, 64, 149, -14, 63, + 150, -10, 63, 151, -6, 62, 152, -2, 61, 153, 1, 61, + 154, 6, 60, 155, 10, 59, 156, 14, 59, 156, 18, 58, + 157, 22, 57, 158, 26, 57, 159, 30, 56, 160, 34, 55, + 161, 38, 55, 162, 43, 54, 163, 47, 53, 164, 51, 53, + 137, -77, 72, 138, -73, 71, 139, -69, 70, 140, -65, 70, + 141, -61, 69, 142, -57, 68, 143, -53, 68, 144, -48, 67, + 145, -44, 66, 146, -40, 66, 147, -36, 65, 148, -32, 64, + 149, -28, 64, 149, -24, 63, 150, -20, 62, 151, -16, 62, + 152, -11, 61, 153, -7, 60, 154, -3, 60, 155, 0, 59, + 156, 4, 58, 157, 8, 58, 158, 12, 57, 159, 16, 56, + 160, 20, 56, 161, 25, 55, 162, 29, 54, 163, 33, 54, + 164, 37, 53, 165, 41, 52, 165, 45, 52, 166, 49, 51, + 140, -79, 70, 141, -75, 69, 141, -71, 69, 142, -66, 68, + 143, -62, 67, 144, -58, 67, 145, -54, 66, 146, -50, 65, + 147, -46, 65, 148, -42, 64, 149, -38, 63, 150, -34, 63, + 151, -29, 62, 152, -25, 61, 153, -21, 61, 154, -17, 60, + 155, -13, 59, 156, -9, 59, 157, -5, 58, 157, -1, 57, + 158, 3, 57, 159, 7, 56, 160, 11, 55, 161, 15, 55, + 162, 19, 54, 163, 23, 53, 164, 27, 53, 165, 31, 52, + 166, 35, 51, 167, 40, 51, 168, 44, 50, 169, 48, 49, + 142, -80, 68, 143, -76, 68, 144, -72, 67, 145, -67, 66, + 146, -63, 66, 147, -59, 65, 148, -55, 64, 149, -51, 64, + 150, -47, 63, 150, -43, 62, 151, -39, 62, 152, -35, 61, + 153, -30, 60, 154, -26, 60, 155, -22, 59, 156, -18, 58, + 157, -14, 58, 158, -10, 57, 159, -6, 56, 160, -2, 56, + 161, 2, 55, 162, 6, 54, 163, 10, 54, 164, 14, 53, + 164, 18, 52, 165, 22, 52, 166, 26, 51, 167, 30, 50, + 168, 34, 50, 169, 39, 49, 170, 43, 48, 171, 47, 48, + 144, -81, 67, 145, -77, 66, 146, -73, 65, 147, -69, 65, + 148, -65, 64, 149, -61, 63, 150, -57, 63, 151, -52, 62, + 152, -48, 61, 153, -44, 61, 154, -40, 60, 155, -36, 59, + 156, -32, 59, 157, -28, 58, 157, -24, 57, 158, -20, 57, + 159, -15, 56, 160, -11, 55, 161, -7, 55, 162, -3, 54, + 163, 0, 53, 164, 4, 53, 165, 8, 52, 166, 12, 51, + 167, 16, 51, 168, 21, 50, 169, 25, 49, 170, 29, 49, + 171, 33, 48, 172, 37, 47, 172, 41, 47, 173, 45, 46, + 147, -83, 65, 148, -79, 64, 149, -75, 63, 150, -70, 63, + 151, -66, 62, 152, -62, 61, 153, -58, 61, 154, -54, 60, + 155, -50, 59, 156, -46, 59, 157, -42, 58, 158, -38, 57, + 159, -33, 57, 159, -29, 56, 160, -25, 55, 161, -21, 55, + 162, -17, 54, 163, -13, 53, 164, -9, 53, 165, -5, 52, + 166, 0, 51, 167, 3, 51, 168, 7, 50, 169, 11, 49, + 170, 15, 49, 171, 19, 48, 172, 23, 47, 173, 27, 47, + 173, 31, 46, 175, 36, 45, 175, 40, 44, 176, 44, 44, + 150, -84, 63, 151, -80, 62, 151, -76, 62, 152, -72, 61, + 153, -68, 60, 154, -64, 60, 155, -60, 59, 156, -55, 58, + 157, -51, 58, 158, -47, 57, 159, -43, 56, 160, -39, 56, + 161, -35, 55, 162, -31, 54, 163, -27, 54, 164, -23, 53, + 165, -18, 52, 166, -14, 52, 166, -10, 51, 167, -6, 50, + 168, -2, 49, 169, 1, 49, 170, 5, 48, 171, 9, 48, + 172, 13, 47, 173, 18, 46, 174, 22, 45, 175, 26, 45, + 176, 30, 44, 177, 34, 43, 178, 38, 43, 179, 42, 42, + 152, -86, 61, 153, -82, 61, 154, -78, 60, 155, -73, 59, + 156, -69, 59, 157, -65, 58, 158, -61, 57, 159, -57, 57, + 159, -53, 56, 160, -49, 55, 161, -45, 55, 162, -41, 54, + 163, -36, 53, 164, -32, 53, 165, -28, 52, 166, -24, 51, + 167, -20, 50, 168, -16, 50, 169, -12, 49, 170, -8, 49, + 171, -3, 48, 172, 0, 47, 173, 4, 47, 174, 8, 46, + 174, 12, 45, 175, 16, 44, 176, 20, 44, 177, 24, 43, + 178, 28, 43, 179, 33, 42, 180, 37, 41, 181, 41, 40, + 154, -87, 60, 155, -83, 59, 156, -79, 58, 157, -74, 58, + 158, -70, 57, 159, -66, 56, 160, -62, 56, 161, -58, 55, + 162, -54, 54, 163, -50, 54, 164, -46, 53, 165, -42, 52, + 166, -37, 51, 167, -33, 51, 167, -29, 50, 168, -25, 50, + 169, -21, 49, 170, -17, 48, 171, -13, 48, 172, -9, 47, + 173, -4, 46, 174, 0, 45, 175, 3, 45, 176, 7, 44, + 177, 11, 44, 178, 15, 43, 179, 19, 42, 180, 23, 41, + 181, 27, 41, 182, 32, 40, 182, 36, 39, 183, 40, 39, + 157, -88, 58, 158, -84, 57, 158, -80, 57, 160, -76, 56, + 160, -72, 55, 161, -68, 55, 162, -64, 54, 163, -59, 53, + 164, -55, 53, 165, -51, 52, 166, -47, 51, 167, -43, 51, + 168, -39, 50, 169, -35, 49, 170, -31, 49, 171, -27, 48, + 172, -22, 47, 173, -18, 46, 174, -14, 46, 174, -10, 45, + 175, -6, 44, 176, -2, 44, 177, 1, 43, 178, 5, 43, + 179, 9, 42, 180, 14, 41, 181, 18, 40, 182, 22, 40, + 183, 26, 39, 184, 30, 38, 185, 34, 38, 186, 38, 37, + 159, -90, 56, 160, -86, 56, 161, -82, 55, 162, -77, 54, + 163, -73, 54, 164, -69, 53, 165, -65, 52, 166, -61, 51, + 167, -57, 51, 167, -53, 50, 168, -49, 50, 169, -45, 49, + 170, -40, 48, 171, -36, 47, 172, -32, 47, 173, -28, 46, + 174, -24, 45, 175, -20, 45, 176, -16, 44, 177, -12, 44, + 178, -7, 43, 179, -3, 42, 180, 0, 41, 181, 4, 41, + 181, 8, 40, 182, 12, 39, 183, 16, 39, 184, 20, 38, + 185, 24, 37, 186, 29, 37, 187, 33, 36, 188, 37, 35, + 161, -91, 55, 162, -87, 54, 163, -83, 53, 164, -78, 52, + 165, -74, 52, 166, -70, 51, 167, -66, 51, 168, -62, 50, + 169, -58, 49, 170, -54, 49, 171, -50, 48, 172, -46, 47, + 173, -41, 46, 174, -37, 46, 174, -33, 45, 175, -29, 45, + 176, -25, 44, 177, -21, 43, 178, -17, 42, 179, -13, 42, + 180, -8, 41, 181, -4, 40, 182, 0, 40, 183, 3, 39, + 184, 7, 39, 185, 11, 38, 186, 15, 37, 187, 19, 36, + 188, 23, 36, 189, 28, 35, 190, 32, 34, 190, 36, 34, + 164, -92, 53, 165, -88, 52, 166, -84, 52, 167, -80, 51, + 167, -76, 50, 168, -72, 50, 169, -68, 49, 170, -63, 48, + 171, -59, 47, 172, -55, 47, 173, -51, 46, 174, -47, 46, + 175, -43, 45, 176, -39, 44, 177, -35, 43, 178, -31, 43, + 179, -26, 42, 180, -22, 41, 181, -18, 41, 181, -14, 40, + 183, -10, 39, 183, -6, 39, 184, -2, 38, 185, 1, 37, + 186, 5, 37, 187, 10, 36, 188, 14, 35, 189, 18, 35, + 190, 22, 34, 191, 26, 33, 192, 30, 33, 193, 34, 32, + 166, -93, 51, 167, -89, 51, 168, -85, 50, 169, -81, 49, + 170, -77, 48, 171, -73, 48, 172, -69, 47, 173, -64, 46, + 174, -60, 46, 174, -56, 45, 175, -52, 45, 176, -48, 44, + 177, -44, 43, 178, -40, 42, 179, -36, 42, 180, -32, 41, + 181, -27, 40, 182, -23, 40, 183, -19, 39, 184, -15, 38, + 185, -11, 38, 186, -7, 37, 187, -3, 36, 188, 0, 36, + 189, 4, 35, 190, 9, 34, 190, 13, 34, 191, 17, 33, + 192, 21, 32, 193, 25, 32, 194, 29, 31, 195, 33, 30, + 168, -95, 49, 169, -91, 49, 170, -87, 48, 171, -82, 47, + 172, -78, 47, 173, -74, 46, 174, -70, 46, 175, -66, 45, + 176, -62, 44, 177, -58, 43, 178, -54, 43, 179, -50, 42, + 180, -45, 41, 181, -41, 41, 182, -37, 40, 182, -33, 39, + 183, -29, 39, 184, -25, 38, 185, -21, 37, 186, -17, 37, + 187, -12, 36, 188, -8, 35, 189, -4, 35, 190, 0, 34, + 191, 3, 33, 192, 7, 33, 193, 11, 32, 194, 15, 31, + 195, 19, 31, 196, 24, 30, 197, 28, 29, 197, 32, 29, + 171, -96, 48, 172, -92, 47, 173, -88, 47, 174, -84, 46, + 175, -80, 45, 175, -76, 44, 176, -72, 44, 177, -67, 43, + 178, -63, 42, 179, -59, 42, 180, -55, 41, 181, -51, 41, + 182, -47, 40, 183, -43, 39, 184, -39, 38, 185, -35, 38, + 186, -30, 37, 187, -26, 36, 188, -22, 36, 189, -18, 35, + 190, -14, 34, 190, -10, 34, 191, -6, 33, 192, -2, 32, + 193, 1, 32, 194, 6, 31, 195, 10, 30, 196, 14, 30, + 197, 18, 29, 198, 22, 28, 199, 26, 28, 200, 30, 27, + 173, -97, 46, 174, -93, 45, 175, -89, 45, 176, -85, 44, + 177, -81, 43, 178, -77, 43, 179, -73, 42, 180, -68, 41, + 181, -64, 41, 182, -60, 40, 182, -56, 39, 183, -52, 39, + 184, -48, 38, 185, -44, 37, 186, -40, 37, 187, -36, 36, + 188, -31, 35, 189, -27, 35, 190, -23, 34, 191, -19, 33, + 192, -15, 33, 193, -11, 32, 194, -7, 31, 195, -3, 31, + 196, 0, 30, 197, 5, 29, 197, 9, 29, 198, 13, 28, + 199, 17, 27, 200, 21, 27, 201, 25, 26, 202, 29, 25, + 175, -99, 44, 176, -95, 44, 177, -91, 43, 178, -86, 42, + 179, -82, 42, 180, -78, 41, 181, -74, 40, 182, -70, 40, + 183, -66, 39, 184, -62, 38, 185, -58, 38, 186, -54, 37, + 187, -49, 36, 188, -45, 36, 189, -41, 35, 189, -37, 34, + 190, -33, 34, 191, -29, 33, 192, -25, 32, 193, -21, 32, + 194, -16, 31, 195, -12, 30, 196, -8, 30, 197, -4, 29, + 198, 0, 28, 199, 3, 28, 200, 7, 27, 201, 11, 26, + 202, 15, 26, 203, 20, 25, 204, 24, 24, 205, 28, 24, + 178, -100, 43, 179, -96, 42, 180, -92, 41, 181, -88, 41, + 182, -84, 40, 182, -80, 39, 183, -76, 39, 184, -71, 38, + 185, -67, 37, 186, -63, 37, 187, -59, 36, 188, -55, 35, + 189, -51, 35, 190, -47, 34, 191, -43, 33, 192, -39, 33, + 193, -34, 32, 194, -30, 31, 195, -26, 31, 196, -22, 30, + 197, -18, 29, 198, -14, 29, 198, -10, 28, 199, -6, 27, + 200, -2, 27, 201, 2, 26, 202, 6, 25, 203, 10, 25, + 204, 14, 24, 205, 18, 23, 206, 22, 23, 207, 26, 22, + 180, -101, 41, 181, -97, 40, 182, -93, 40, 183, -89, 39, + 184, -85, 38, 185, -81, 38, 186, -77, 37, 187, -72, 36, + 188, -68, 36, 189, -64, 35, 189, -60, 34, 190, -56, 34, + 191, -52, 33, 192, -48, 32, 193, -44, 32, 194, -40, 31, + 195, -35, 30, 196, -31, 30, 197, -27, 29, 198, -23, 28, + 199, -19, 28, 200, -15, 27, 201, -11, 26, 202, -7, 26, + 203, -3, 25, 204, 1, 24, 205, 5, 24, 205, 9, 23, + 206, 13, 22, 207, 17, 22, 208, 21, 21, 209, 25, 20, + 182, -103, 39, 183, -99, 39, 184, -95, 38, 185, -90, 37, + 186, -86, 37, 187, -82, 36, 188, -78, 35, 189, -74, 35, + 190, -70, 34, 191, -66, 33, 192, -62, 33, 193, -58, 32, + 194, -53, 31, 195, -49, 31, 196, -45, 30, 197, -41, 29, + 198, -37, 29, 198, -33, 28, 199, -29, 27, 200, -25, 27, + 201, -20, 26, 202, -16, 25, 203, -12, 25, 204, -8, 24, + 205, -4, 23, 206, 0, 23, 207, 3, 22, 208, 7, 21, + 209, 11, 21, 210, 16, 20, 211, 20, 19, 212, 24, 19, + 185, -104, 38, 186, -100, 37, 187, -96, 36, 188, -92, 36, + 189, -88, 35, 190, -84, 34, 190, -80, 34, 191, -75, 33, + 192, -71, 32, 193, -67, 32, 194, -63, 31, 195, -59, 30, + 196, -55, 30, 197, -51, 29, 198, -47, 28, 199, -43, 28, + 200, -38, 27, 201, -34, 26, 202, -30, 26, 203, -26, 25, + 204, -22, 24, 205, -18, 24, 205, -14, 23, 206, -10, 22, + 207, -6, 22, 208, -1, 21, 209, 2, 20, 210, 6, 20, + 211, 10, 19, 212, 14, 18, 213, 18, 18, 214, 22, 17, + 187, -105, 36, 188, -101, 35, 189, -97, 35, 190, -93, 34, + 191, -89, 33, 192, -85, 33, 193, -81, 32, 194, -76, 31, + 195, -72, 31, 196, -68, 30, 197, -64, 29, 197, -60, 29, + 198, -56, 28, 199, -52, 27, 200, -48, 27, 201, -44, 26, + 202, -39, 25, 203, -35, 25, 204, -31, 24, 205, -27, 23, + 206, -23, 23, 207, -19, 22, 208, -15, 21, 209, -11, 21, + 210, -7, 20, 211, -2, 19, 212, 1, 19, 212, 5, 18, + 213, 9, 17, 214, 13, 17, 215, 17, 16, 216, 21, 15, + 190, -107, 34, 190, -103, 34, 191, -99, 33, 192, -94, 32, + 193, -90, 32, 194, -86, 31, 195, -82, 30, 196, -78, 30, + 197, -74, 29, 198, -70, 28, 199, -66, 28, 200, -62, 27, + 201, -57, 26, 202, -53, 26, 203, -49, 25, 204, -45, 24, + 205, -41, 24, 205, -37, 23, 206, -33, 22, 207, -29, 22, + 208, -24, 21, 209, -20, 20, 210, -16, 20, 211, -12, 19, + 212, -8, 18, 213, -4, 18, 214, 0, 17, 215, 3, 16, + 216, 7, 16, 217, 12, 15, 218, 16, 14, 219, 20, 14, + 192, -108, 33, 193, -104, 32, 194, -100, 31, 195, -96, 31, + 196, -92, 30, 197, -88, 29, 197, -84, 29, 198, -79, 28, + 199, -75, 27, 200, -71, 27, 201, -67, 26, 202, -63, 25, + 203, -59, 25, 204, -55, 24, 205, -51, 23, 206, -47, 23, + 207, -42, 22, 208, -38, 21, 209, -34, 21, 210, -30, 20, + 211, -26, 19, 212, -22, 19, 213, -18, 18, 213, -14, 17, + 214, -10, 17, 215, -5, 16, 216, -1, 15, 217, 2, 15, + 218, 6, 14, 219, 10, 13, 220, 14, 13, 221, 18, 12, + 194, -109, 31, 195, -105, 30, 196, -101, 30, 197, -97, 29, + 198, -93, 28, 199, -89, 28, 200, -85, 27, 201, -80, 26, + 202, -76, 26, 203, -72, 25, 204, -68, 24, 204, -64, 24, + 206, -60, 23, 206, -56, 22, 207, -52, 22, 208, -48, 21, + 209, -43, 20, 210, -39, 20, 211, -35, 19, 212, -31, 18, + 213, -27, 18, 214, -23, 17, 215, -19, 16, 216, -15, 16, + 217, -11, 15, 218, -6, 14, 219, -2, 14, 220, 1, 13, + 220, 5, 12, 221, 9, 12, 222, 13, 11, 223, 17, 10, + 197, -111, 29, 198, -107, 28, 199, -103, 28, 200, -99, 27, + 201, -95, 26, 202, -91, 26, 203, -87, 25, 204, -82, 24, + 205, -78, 24, 206, -74, 23, 207, -70, 22, 207, -66, 22, + 208, -62, 21, 209, -58, 20, 210, -54, 20, 211, -50, 19, + 212, -45, 18, 213, -41, 18, 214, -37, 17, 215, -33, 16, + 216, -29, 16, 217, -25, 15, 218, -21, 14, 219, -17, 14, + 220, -13, 13, 221, -8, 12, 222, -4, 12, 222, 0, 11, + 223, 3, 10, 224, 7, 10, 225, 11, 9, 226, 15, 8, + 200, -112, 27, 200, -108, 27, 201, -104, 26, 202, -100, 25, + 203, -96, 25, 204, -92, 24, 205, -88, 23, 206, -83, 23, + 207, -79, 22, 208, -75, 21, 209, -71, 21, 210, -67, 20, + 211, -63, 19, 212, -59, 19, 213, -55, 18, 214, -51, 17, + 215, -46, 17, 215, -42, 16, 216, -38, 15, 217, -34, 15, + 218, -30, 14, 219, -26, 13, 220, -22, 13, 221, -18, 12, + 222, -14, 11, 223, -9, 11, 224, -5, 10, 225, -1, 9, + 226, 2, 9, 227, 6, 8, 228, 10, 7, 229, 14, 7, + 202, -114, 26, 203, -110, 25, 204, -106, 24, 205, -101, 24, + 206, -97, 23, 207, -93, 22, 207, -89, 22, 208, -85, 21, + 209, -81, 20, 210, -77, 20, 211, -73, 19, 212, -69, 18, + 213, -64, 18, 214, -60, 17, 215, -56, 16, 216, -52, 16, + 217, -48, 15, 218, -44, 14, 219, -40, 14, 220, -36, 13, + 221, -31, 12, 222, -27, 12, 222, -23, 11, 223, -19, 10, + 224, -15, 10, 225, -11, 9, 226, -7, 8, 227, -3, 8, + 228, 0, 7, 229, 5, 6, 230, 9, 6, 231, 13, 5, + 204, -115, 24, 205, -111, 23, 206, -107, 23, 207, -103, 22, + 208, -99, 21, 209, -95, 21, 210, -91, 20, 211, -86, 19, + 212, -82, 19, 213, -78, 18, 214, -74, 17, 214, -70, 17, + 215, -66, 16, 216, -62, 15, 217, -58, 15, 218, -54, 14, + 219, -49, 13, 220, -45, 13, 221, -41, 12, 222, -37, 11, + 223, -33, 11, 224, -29, 10, 225, -25, 9, 226, -21, 9, + 227, -17, 8, 228, -12, 7, 229, -8, 7, 230, -4, 6, + 230, 0, 5, 231, 3, 5, 232, 7, 4, 233, 11, 3, + 207, -116, 22, 207, -112, 22, 208, -108, 21, 209, -104, 20, + 210, -100, 20, 211, -96, 19, 212, -92, 18, 213, -87, 18, + 214, -83, 17, 215, -79, 16, 216, -75, 16, 217, -71, 15, + 218, -67, 14, 219, -63, 14, 220, -59, 13, 221, -55, 12, + 222, -50, 12, 223, -46, 11, 223, -42, 10, 224, -38, 10, + 225, -34, 9, 226, -30, 8, 227, -26, 8, 228, -22, 7, + 229, -18, 6, 230, -13, 6, 231, -9, 5, 232, -5, 4, + 233, -1, 4, 234, 2, 3, 235, 6, 2, 236, 10, 2, + 209, -118, 21, 210, -114, 20, 211, -110, 19, 212, -105, 19, + 213, -101, 18, 214, -97, 17, 214, -93, 17, 216, -89, 16, + 216, -85, 15, 217, -81, 15, 218, -77, 14, 219, -73, 13, + 220, -68, 13, 221, -64, 12, 222, -60, 11, 223, -56, 11, + 224, -52, 10, 225, -48, 9, 226, -44, 9, 227, -40, 8, + 228, -35, 7, 229, -31, 7, 230, -27, 6, 230, -23, 5, + 231, -19, 5, 232, -15, 4, 233, -11, 3, 234, -7, 3, + 235, -3, 2, 236, 1, 1, 237, 5, 1, 238, 9, 0, + 211, -119, 19, 212, -115, 18, 213, -111, 18, 214, -107, 17, + 215, -103, 16, 216, -99, 16, 217, -95, 15, 218, -90, 14, + 219, -86, 14, 220, -82, 13, 221, -78, 12, 222, -74, 12, + 223, -70, 11, 223, -66, 10, 224, -62, 10, 225, -58, 9, + 226, -53, 8, 227, -49, 8, 228, -45, 7, 229, -41, 6, + 230, -37, 6, 231, -33, 5, 232, -29, 4, 233, -25, 4, + 234, -21, 3, 235, -16, 2, 236, -12, 2, 237, -8, 1, + 237, -4, 0, 238, 0, 0, 239, 4, 0, 240, 8, -1, + 214, -120, 17, 215, -116, 17, 215, -112, 16, 216, -108, 15, + 217, -104, 15, 218, -100, 14, 219, -96, 13, 220, -91, 13, + 221, -87, 12, 222, -83, 11, 223, -79, 11, 224, -75, 10, + 225, -71, 9, 226, -67, 9, 227, -63, 8, 228, -59, 7, + 229, -54, 7, 230, -50, 6, 230, -46, 5, 231, -42, 5, + 232, -38, 4, 233, -34, 3, 234, -30, 3, 235, -26, 2, + 236, -22, 1, 237, -17, 1, 238, -13, 0, 239, -9, 0, + 240, -5, 0, 241, -1, -1, 242, 2, -2, 243, 6, -2, + 216, -122, 16, 217, -118, 15, 218, -114, 14, 219, -109, 14, + 220, -105, 13, 221, -101, 12, 222, -97, 12, 223, -93, 11, + 223, -89, 10, 224, -85, 10, 225, -81, 9, 226, -77, 8, + 227, -72, 8, 228, -68, 7, 229, -64, 6, 230, -60, 6, + 231, -56, 5, 232, -52, 4, 233, -48, 4, 234, -44, 3, + 235, -39, 2, 236, -35, 2, 237, -31, 1, 237, -27, 0, + 238, -23, 0, 239, -19, 0, 240, -15, -1, 241, -11, -1, + 242, -7, -2, 243, -2, -3, 244, 1, -4, 245, 5, -4, + 218, -123, 14, 219, -119, 13, 220, -115, 13, 221, -110, 12, + 222, -106, 11, 223, -102, 11, 224, -98, 10, 225, -94, 9, + 226, -90, 9, 227, -86, 8, 228, -82, 7, 229, -78, 7, + 230, -73, 6, 230, -69, 5, 231, -65, 5, 232, -61, 4, + 233, -57, 3, 234, -53, 3, 235, -49, 2, 236, -45, 1, + 237, -40, 0, 238, -36, 0, 239, -32, 0, 240, -28, 0, + 241, -24, -1, 242, -20, -2, 243, -16, -2, 244, -12, -3, + 245, -8, -4, 246, -3, -5, 246, 0, -5, 247, 4, -6, + 221, -124, 12, 222, -120, 12, 222, -116, 11, 223, -112, 10, + 224, -108, 10, 225, -104, 9, 226, -100, 8, 227, -95, 8, + 228, -91, 7, 229, -87, 6, 230, -83, 6, 231, -79, 5, + 232, -75, 4, 233, -71, 4, 234, -67, 3, 235, -63, 2, + 236, -58, 2, 237, -54, 1, 238, -50, 0, 238, -46, 0, + 239, -42, 0, 240, -38, -1, 241, -34, -1, 242, -30, -2, + 243, -26, -3, 244, -21, -4, 245, -17, -4, 246, -13, -5, + 247, -9, -5, 248, -5, -6, 249, -1, -7, 250, 2, -7, + 73, -41, 123, 74, -37, 122, 75, -33, 122, 76, -29, 121, + 77, -25, 120, 78, -21, 120, 79, -17, 119, 80, -12, 118, + 81, -8, 118, 82, -4, 117, 83, 0, 116, 84, 3, 116, + 85, 7, 115, 86, 11, 114, 86, 15, 114, 87, 19, 113, + 88, 24, 112, 89, 28, 112, 90, 32, 111, 91, 36, 110, + 92, 40, 110, 93, 44, 109, 94, 48, 108, 95, 52, 108, + 96, 56, 107, 97, 61, 106, 98, 65, 106, 99, 69, 105, + 100, 73, 104, 101, 77, 104, 102, 81, 103, 102, 85, 102, + 76, -43, 121, 77, -39, 121, 78, -35, 120, 79, -30, 119, + 79, -26, 119, 80, -22, 118, 81, -18, 117, 82, -14, 117, + 83, -10, 116, 84, -6, 115, 85, -2, 115, 86, 1, 114, + 87, 6, 113, 88, 10, 113, 89, 14, 112, 90, 18, 111, + 91, 22, 111, 92, 26, 110, 93, 30, 109, 93, 34, 109, + 95, 39, 108, 95, 43, 107, 96, 47, 107, 97, 51, 106, + 98, 55, 105, 99, 59, 105, 100, 63, 104, 101, 67, 103, + 102, 71, 103, 103, 76, 102, 104, 80, 101, 105, 84, 101, + 78, -44, 120, 79, -40, 119, 80, -36, 118, 81, -31, 118, + 82, -27, 117, 83, -23, 116, 84, -19, 116, 85, -15, 115, + 86, -11, 114, 86, -7, 114, 87, -3, 113, 88, 0, 112, + 89, 5, 112, 90, 9, 111, 91, 13, 110, 92, 17, 110, + 93, 21, 109, 94, 25, 108, 95, 29, 108, 96, 33, 107, + 97, 38, 106, 98, 42, 106, 99, 46, 105, 100, 50, 104, + 101, 54, 104, 102, 58, 103, 102, 62, 102, 103, 66, 102, + 104, 70, 101, 105, 75, 100, 106, 79, 100, 107, 83, 99, + 80, -45, 118, 81, -41, 117, 82, -37, 117, 83, -33, 116, + 84, -29, 115, 85, -25, 115, 86, -21, 114, 87, -16, 113, + 88, -12, 113, 89, -8, 112, 90, -4, 111, 91, 0, 111, + 92, 3, 110, 93, 7, 109, 94, 11, 109, 94, 15, 108, + 95, 20, 107, 96, 24, 107, 97, 28, 106, 98, 32, 105, + 99, 36, 105, 100, 40, 104, 101, 44, 103, 102, 48, 103, + 103, 52, 102, 104, 57, 101, 105, 61, 101, 106, 65, 100, + 107, 69, 99, 108, 73, 99, 109, 77, 98, 109, 81, 97, + 83, -46, 116, 84, -42, 116, 85, -38, 115, 86, -34, 114, + 87, -30, 114, 87, -26, 113, 88, -22, 112, 89, -17, 112, + 90, -13, 111, 91, -9, 110, 92, -5, 110, 93, -1, 109, + 94, 2, 108, 95, 6, 108, 96, 10, 107, 97, 14, 106, + 98, 19, 106, 99, 23, 105, 100, 27, 104, 101, 31, 104, + 102, 35, 103, 102, 39, 102, 103, 43, 102, 104, 47, 101, + 105, 51, 100, 106, 56, 100, 107, 60, 99, 108, 64, 98, + 109, 68, 98, 110, 72, 97, 111, 76, 96, 112, 80, 96, + 85, -48, 115, 86, -44, 114, 87, -40, 113, 88, -35, 113, + 89, -31, 112, 90, -27, 111, 91, -23, 111, 92, -19, 110, + 93, -15, 109, 94, -11, 109, 94, -7, 108, 95, -3, 107, + 96, 1, 107, 97, 5, 106, 98, 9, 105, 99, 13, 105, + 100, 17, 104, 101, 21, 103, 102, 25, 103, 103, 29, 102, + 104, 34, 101, 105, 38, 101, 106, 42, 100, 107, 46, 99, + 108, 50, 99, 109, 54, 98, 109, 58, 97, 110, 62, 97, + 111, 66, 96, 112, 71, 95, 113, 75, 95, 114, 79, 94, + 87, -49, 113, 88, -45, 112, 89, -41, 112, 90, -37, 111, + 91, -33, 110, 92, -29, 110, 93, -25, 109, 94, -20, 108, + 95, -16, 108, 96, -12, 107, 97, -8, 106, 98, -4, 106, + 99, 0, 105, 100, 3, 104, 101, 7, 104, 101, 11, 103, + 102, 16, 102, 103, 20, 102, 104, 24, 101, 105, 28, 100, + 106, 32, 100, 107, 36, 99, 108, 40, 98, 109, 44, 98, + 110, 48, 97, 111, 53, 96, 112, 57, 96, 113, 61, 95, + 114, 65, 94, 115, 69, 94, 116, 73, 93, 117, 77, 92, + 90, -50, 111, 91, -46, 111, 92, -42, 110, 93, -38, 109, + 94, -34, 109, 94, -30, 108, 95, -26, 107, 96, -21, 107, + 97, -17, 106, 98, -13, 105, 99, -9, 105, 100, -5, 104, + 101, -1, 103, 102, 2, 103, 103, 6, 102, 104, 10, 101, + 105, 15, 101, 106, 19, 100, 107, 23, 99, 108, 27, 99, + 109, 31, 98, 110, 35, 97, 110, 39, 97, 111, 43, 96, + 112, 47, 95, 113, 52, 95, 114, 56, 94, 115, 60, 93, + 116, 64, 93, 117, 68, 92, 118, 72, 91, 119, 76, 91, + 92, -52, 110, 93, -48, 109, 94, -44, 108, 95, -39, 108, + 96, -35, 107, 97, -31, 106, 98, -27, 106, 99, -23, 105, + 100, -19, 104, 101, -15, 104, 101, -11, 103, 102, -7, 102, + 103, -2, 102, 104, 1, 101, 105, 5, 100, 106, 9, 100, + 107, 13, 99, 108, 17, 98, 109, 21, 98, 110, 25, 97, + 111, 30, 96, 112, 34, 96, 113, 38, 95, 114, 42, 94, + 115, 46, 94, 116, 50, 93, 117, 54, 92, 117, 58, 92, + 118, 62, 91, 119, 67, 90, 120, 71, 90, 121, 75, 89, + 94, -53, 108, 95, -49, 107, 96, -45, 107, 97, -41, 106, + 98, -37, 105, 99, -33, 105, 100, -29, 104, 101, -24, 103, + 102, -20, 103, 103, -16, 102, 104, -12, 101, 105, -8, 101, + 106, -4, 100, 107, 0, 99, 108, 3, 99, 109, 7, 98, + 110, 12, 97, 110, 16, 97, 111, 20, 96, 112, 24, 95, + 113, 28, 95, 114, 32, 94, 115, 36, 93, 116, 40, 93, + 117, 44, 92, 118, 49, 91, 119, 53, 91, 120, 57, 90, + 121, 61, 89, 122, 65, 88, 123, 69, 88, 124, 73, 87, + 97, -54, 106, 98, -50, 106, 99, -46, 105, 100, -42, 104, + 101, -38, 104, 102, -34, 103, 102, -30, 102, 103, -25, 102, + 104, -21, 101, 105, -17, 100, 106, -13, 100, 107, -9, 99, + 108, -5, 98, 109, -1, 98, 110, 2, 97, 111, 6, 96, + 112, 11, 96, 113, 15, 95, 114, 19, 94, 115, 23, 94, + 116, 27, 93, 117, 31, 92, 117, 35, 92, 118, 39, 91, + 119, 43, 90, 120, 48, 90, 121, 52, 89, 122, 56, 88, + 123, 60, 88, 124, 64, 87, 125, 68, 86, 126, 72, 86, + 100, -56, 104, 101, -52, 104, 102, -48, 103, 103, -44, 102, + 104, -40, 101, 104, -36, 101, 105, -32, 100, 106, -27, 99, + 107, -23, 99, 108, -19, 98, 109, -15, 97, 110, -11, 97, + 111, -7, 96, 112, -3, 95, 113, 0, 95, 114, 4, 94, + 115, 9, 93, 116, 13, 93, 117, 17, 92, 118, 21, 91, + 119, 25, 91, 119, 29, 90, 120, 33, 89, 121, 37, 89, + 122, 41, 88, 123, 46, 87, 124, 50, 87, 125, 54, 86, + 126, 58, 85, 127, 62, 85, 128, 66, 84, 129, 70, 83, + 102, -57, 102, 103, -53, 102, 104, -49, 101, 105, -45, 100, + 106, -41, 100, 107, -37, 99, 108, -33, 98, 109, -28, 98, + 110, -24, 97, 111, -20, 96, 111, -16, 96, 112, -12, 95, + 113, -8, 94, 114, -4, 94, 115, 0, 93, 116, 3, 92, + 117, 8, 92, 118, 12, 91, 119, 16, 90, 120, 20, 90, + 121, 24, 89, 122, 28, 88, 123, 32, 88, 124, 36, 87, + 125, 40, 86, 126, 45, 86, 127, 49, 85, 127, 53, 84, + 128, 57, 84, 129, 61, 83, 130, 65, 82, 131, 69, 82, + 104, -59, 101, 105, -55, 100, 106, -51, 100, 107, -46, 99, + 108, -42, 98, 109, -38, 97, 110, -34, 97, 111, -30, 96, + 112, -26, 95, 113, -22, 95, 114, -18, 94, 115, -14, 93, + 116, -9, 93, 117, -5, 92, 118, -1, 91, 118, 2, 91, + 120, 6, 90, 120, 10, 89, 121, 14, 89, 122, 18, 88, + 123, 23, 87, 124, 27, 87, 125, 31, 86, 126, 35, 85, + 127, 39, 85, 128, 43, 84, 129, 47, 83, 130, 51, 83, + 131, 55, 82, 132, 60, 81, 133, 64, 81, 134, 68, 80, + 107, -60, 99, 108, -56, 98, 109, -52, 98, 110, -48, 97, + 111, -44, 96, 111, -40, 96, 112, -36, 95, 113, -31, 94, + 114, -27, 94, 115, -23, 93, 116, -19, 92, 117, -15, 92, + 118, -11, 91, 119, -7, 90, 120, -3, 90, 121, 0, 89, + 122, 5, 88, 123, 9, 88, 124, 13, 87, 125, 17, 86, + 126, 21, 86, 127, 25, 85, 127, 29, 84, 128, 33, 84, + 129, 37, 83, 130, 42, 82, 131, 46, 82, 132, 50, 81, + 133, 54, 80, 134, 58, 80, 135, 62, 79, 136, 66, 78, + 109, -61, 97, 110, -57, 97, 111, -53, 96, 112, -49, 95, + 113, -45, 95, 114, -41, 94, 115, -37, 93, 116, -32, 93, + 117, -28, 92, 118, -24, 91, 119, -20, 91, 119, -16, 90, + 120, -12, 89, 121, -8, 89, 122, -4, 88, 123, 0, 87, + 124, 4, 87, 125, 8, 86, 126, 12, 85, 127, 16, 85, + 128, 20, 84, 129, 24, 83, 130, 28, 83, 131, 32, 82, + 132, 36, 81, 133, 41, 81, 134, 45, 80, 134, 49, 79, + 135, 53, 79, 136, 57, 78, 137, 61, 77, 138, 65, 77, + 112, -63, 96, 112, -59, 95, 113, -55, 94, 114, -50, 94, + 115, -46, 93, 116, -42, 92, 117, -38, 92, 118, -34, 91, + 119, -30, 90, 120, -26, 90, 121, -22, 89, 122, -18, 88, + 123, -13, 88, 124, -9, 87, 125, -5, 86, 126, -1, 86, + 127, 2, 85, 127, 6, 84, 128, 10, 84, 129, 14, 83, + 130, 19, 82, 131, 23, 82, 132, 27, 81, 133, 31, 80, + 134, 35, 80, 135, 39, 79, 136, 43, 78, 137, 47, 78, + 138, 51, 77, 139, 56, 76, 140, 60, 76, 141, 64, 75, + 114, -64, 94, 115, -60, 93, 116, -56, 93, 117, -52, 92, + 118, -48, 91, 119, -44, 91, 119, -40, 90, 120, -35, 89, + 121, -31, 89, 122, -27, 88, 123, -23, 87, 124, -19, 87, + 125, -15, 86, 126, -11, 85, 127, -7, 85, 128, -3, 84, + 129, 1, 83, 130, 5, 83, 131, 9, 82, 132, 13, 81, + 133, 17, 81, 134, 21, 80, 134, 25, 79, 135, 29, 79, + 136, 33, 78, 137, 38, 77, 138, 42, 77, 139, 46, 76, + 140, 50, 75, 141, 54, 75, 142, 58, 74, 143, 62, 73, + 116, -65, 92, 117, -61, 92, 118, -57, 91, 119, -53, 90, + 120, -49, 90, 121, -45, 89, 122, -41, 88, 123, -36, 88, + 124, -32, 87, 125, -28, 86, 126, -24, 86, 126, -20, 85, + 127, -16, 84, 128, -12, 84, 129, -8, 83, 130, -4, 82, + 131, 0, 82, 132, 4, 81, 133, 8, 80, 134, 12, 80, + 135, 16, 79, 136, 20, 78, 137, 24, 78, 138, 28, 77, + 139, 32, 76, 140, 37, 76, 141, 41, 75, 142, 45, 74, + 142, 49, 74, 143, 53, 73, 144, 57, 72, 145, 61, 72, + 119, -67, 91, 119, -63, 90, 120, -59, 89, 121, -54, 89, + 122, -50, 88, 123, -46, 87, 124, -42, 87, 125, -38, 86, + 126, -34, 85, 127, -30, 85, 128, -26, 84, 129, -22, 83, + 130, -17, 83, 131, -13, 82, 132, -9, 81, 133, -5, 81, + 134, -1, 80, 135, 2, 79, 135, 6, 79, 136, 10, 78, + 137, 15, 77, 138, 19, 77, 139, 23, 76, 140, 27, 75, + 141, 31, 75, 142, 35, 74, 143, 39, 73, 144, 43, 73, + 145, 47, 72, 146, 52, 71, 147, 56, 71, 148, 60, 70, + 121, -68, 89, 122, -64, 88, 123, -60, 88, 124, -56, 87, + 125, -52, 86, 126, -48, 86, 126, -44, 85, 128, -39, 84, + 128, -35, 84, 129, -31, 83, 130, -27, 82, 131, -23, 82, + 132, -19, 81, 133, -15, 80, 134, -11, 80, 135, -7, 79, + 136, -2, 78, 137, 1, 78, 138, 5, 77, 139, 9, 76, + 140, 13, 76, 141, 17, 75, 142, 21, 74, 142, 25, 74, + 143, 29, 73, 144, 34, 72, 145, 38, 72, 146, 42, 71, + 147, 46, 70, 148, 50, 70, 149, 54, 69, 150, 58, 68, + 123, -69, 87, 124, -65, 87, 125, -61, 86, 126, -57, 85, + 127, -53, 85, 128, -49, 84, 129, -45, 83, 130, -40, 83, + 131, -36, 82, 132, -32, 81, 133, -28, 81, 134, -24, 80, + 135, -20, 79, 135, -16, 79, 136, -12, 78, 137, -8, 77, + 138, -3, 77, 139, 0, 76, 140, 4, 75, 141, 8, 75, + 142, 12, 74, 143, 16, 73, 144, 20, 73, 145, 24, 72, + 146, 28, 71, 147, 33, 71, 148, 37, 70, 149, 41, 69, + 149, 45, 69, 150, 49, 68, 151, 53, 67, 152, 57, 67, + 126, -71, 86, 127, -67, 85, 127, -63, 84, 128, -58, 84, + 129, -54, 83, 130, -50, 82, 131, -46, 82, 132, -42, 81, + 133, -38, 80, 134, -34, 80, 135, -30, 79, 136, -26, 78, + 137, -21, 78, 138, -17, 77, 139, -13, 76, 140, -9, 76, + 141, -5, 75, 142, -1, 74, 142, 2, 74, 143, 6, 73, + 144, 11, 72, 145, 15, 72, 146, 19, 71, 147, 23, 70, + 148, 27, 70, 149, 31, 69, 150, 35, 68, 151, 39, 68, + 152, 43, 67, 153, 48, 66, 154, 52, 66, 155, 56, 65, + 128, -72, 84, 129, -68, 83, 130, -64, 83, 131, -59, 82, + 132, -55, 81, 133, -51, 81, 134, -47, 80, 135, -43, 79, + 135, -39, 79, 136, -35, 78, 137, -31, 77, 138, -27, 77, + 139, -22, 76, 140, -18, 75, 141, -14, 75, 142, -10, 74, + 143, -6, 73, 144, -2, 73, 145, 1, 72, 146, 5, 71, + 147, 10, 71, 148, 14, 70, 149, 18, 69, 149, 22, 69, + 150, 26, 68, 151, 30, 67, 152, 34, 67, 153, 38, 66, + 154, 42, 65, 155, 47, 65, 156, 51, 64, 157, 55, 63, + 130, -73, 82, 131, -69, 82, 132, -65, 81, 133, -61, 80, + 134, -57, 80, 135, -53, 79, 136, -49, 78, 137, -44, 78, + 138, -40, 77, 139, -36, 76, 140, -32, 76, 141, -28, 75, + 142, -24, 74, 142, -20, 74, 143, -16, 73, 144, -12, 72, + 145, -7, 72, 146, -3, 71, 147, 0, 70, 148, 4, 70, + 149, 8, 69, 150, 12, 68, 151, 16, 68, 152, 20, 67, + 153, 24, 66, 154, 29, 66, 155, 33, 65, 156, 37, 64, + 157, 41, 64, 158, 45, 63, 158, 49, 62, 159, 53, 62, + 133, -75, 81, 134, -71, 80, 134, -67, 79, 135, -62, 79, + 136, -58, 78, 137, -54, 77, 138, -50, 77, 139, -46, 76, + 140, -42, 75, 141, -38, 75, 142, -34, 74, 143, -30, 73, + 144, -25, 73, 145, -21, 72, 146, -17, 71, 147, -13, 71, + 148, -9, 70, 149, -5, 69, 150, -1, 69, 150, 2, 68, + 151, 7, 67, 152, 11, 67, 153, 15, 66, 154, 19, 65, + 155, 23, 65, 156, 27, 64, 157, 31, 63, 158, 35, 63, + 159, 39, 62, 160, 44, 61, 161, 48, 61, 162, 52, 60, + 135, -76, 79, 136, -72, 78, 137, -68, 78, 138, -63, 77, + 139, -59, 76, 140, -55, 76, 141, -51, 75, 142, -47, 74, + 143, -43, 74, 143, -39, 73, 144, -35, 72, 145, -31, 72, + 146, -26, 71, 147, -22, 70, 148, -18, 70, 149, -14, 69, + 150, -10, 68, 151, -6, 68, 152, -2, 67, 153, 1, 66, + 154, 6, 66, 155, 10, 65, 156, 14, 64, 157, 18, 64, + 157, 22, 63, 158, 26, 62, 159, 30, 62, 160, 34, 61, + 161, 38, 60, 162, 43, 60, 163, 47, 59, 164, 51, 58, + 137, -77, 77, 138, -73, 77, 139, -69, 76, 140, -65, 75, + 141, -61, 75, 142, -57, 74, 143, -53, 73, 144, -48, 73, + 145, -44, 72, 146, -40, 71, 147, -36, 71, 148, -32, 70, + 149, -28, 69, 150, -24, 69, 150, -20, 68, 151, -16, 67, + 152, -11, 67, 153, -7, 66, 154, -3, 65, 155, 0, 65, + 156, 4, 64, 157, 8, 63, 158, 12, 63, 159, 16, 62, + 160, 20, 61, 161, 25, 61, 162, 29, 60, 163, 33, 59, + 164, 37, 59, 165, 41, 58, 165, 45, 57, 166, 49, 57, + 140, -79, 76, 141, -75, 75, 142, -71, 74, 143, -66, 74, + 143, -62, 73, 144, -58, 72, 145, -54, 72, 146, -50, 71, + 147, -46, 70, 148, -42, 70, 149, -38, 69, 150, -34, 68, + 151, -29, 68, 152, -25, 67, 153, -21, 66, 154, -17, 66, + 155, -13, 65, 156, -9, 64, 157, -5, 64, 157, -1, 63, + 158, 3, 62, 159, 7, 62, 160, 11, 61, 161, 15, 60, + 162, 19, 60, 163, 23, 59, 164, 27, 58, 165, 31, 58, + 166, 35, 57, 167, 40, 56, 168, 44, 56, 169, 48, 55, + 142, -80, 74, 143, -76, 73, 144, -72, 73, 145, -67, 72, + 146, -63, 71, 147, -59, 71, 148, -55, 70, 149, -51, 69, + 150, -47, 69, 150, -43, 68, 151, -39, 67, 152, -35, 67, + 153, -30, 66, 154, -26, 65, 155, -22, 65, 156, -18, 64, + 157, -14, 63, 158, -10, 63, 159, -6, 62, 160, -2, 61, + 161, 2, 61, 162, 6, 60, 163, 10, 59, 164, 14, 59, + 164, 18, 58, 166, 22, 57, 166, 26, 57, 167, 30, 56, + 168, 34, 55, 169, 39, 55, 170, 43, 54, 171, 47, 53, + 144, -81, 72, 145, -77, 72, 146, -73, 71, 147, -69, 70, + 148, -65, 70, 149, -61, 69, 150, -57, 68, 151, -52, 68, + 152, -48, 67, 153, -44, 66, 154, -40, 66, 155, -36, 65, + 156, -32, 64, 157, -28, 64, 157, -24, 63, 158, -20, 62, + 159, -15, 62, 160, -11, 61, 161, -7, 60, 162, -3, 60, + 163, 0, 59, 164, 4, 58, 165, 8, 58, 166, 12, 57, + 167, 16, 56, 168, 21, 56, 169, 25, 55, 170, 29, 54, + 171, 33, 54, 172, 37, 53, 173, 41, 52, 173, 45, 52, + 147, -83, 71, 148, -79, 70, 149, -75, 69, 150, -70, 69, + 150, -66, 68, 151, -62, 67, 152, -58, 67, 153, -54, 66, + 154, -50, 65, 155, -46, 65, 156, -42, 64, 157, -38, 63, + 158, -33, 63, 159, -29, 62, 160, -25, 61, 161, -21, 61, + 162, -17, 60, 163, -13, 59, 164, -9, 59, 165, -5, 58, + 166, 0, 57, 166, 3, 57, 167, 7, 56, 168, 11, 55, + 169, 15, 55, 170, 19, 54, 171, 23, 53, 172, 27, 53, + 173, 31, 52, 174, 36, 51, 175, 40, 51, 176, 44, 50, + 150, -84, 69, 151, -80, 68, 151, -76, 67, 153, -72, 67, + 153, -68, 66, 154, -64, 65, 155, -60, 65, 156, -55, 64, + 157, -51, 63, 158, -47, 63, 159, -43, 62, 160, -39, 61, + 161, -35, 61, 162, -31, 60, 163, -27, 59, 164, -23, 59, + 165, -18, 58, 166, -14, 57, 167, -10, 57, 167, -6, 56, + 168, -2, 55, 169, 1, 55, 170, 5, 54, 171, 9, 53, + 172, 13, 53, 173, 18, 52, 174, 22, 51, 175, 26, 51, + 176, 30, 50, 177, 34, 49, 178, 38, 48, 179, 42, 48, + 152, -86, 67, 153, -82, 66, 154, -78, 66, 155, -73, 65, + 156, -69, 64, 157, -65, 64, 158, -61, 63, 159, -57, 62, + 160, -53, 62, 160, -49, 61, 161, -45, 60, 162, -41, 60, + 163, -36, 59, 164, -32, 58, 165, -28, 58, 166, -24, 57, + 167, -20, 56, 168, -16, 56, 169, -12, 55, 170, -8, 54, + 171, -3, 53, 172, 0, 53, 173, 4, 52, 174, 8, 52, + 174, 12, 51, 175, 16, 50, 176, 20, 49, 177, 24, 49, + 178, 28, 48, 179, 33, 47, 180, 37, 47, 181, 41, 46, + 154, -87, 65, 155, -83, 65, 156, -79, 64, 157, -74, 63, + 158, -70, 63, 159, -66, 62, 160, -62, 61, 161, -58, 61, + 162, -54, 60, 163, -50, 59, 164, -46, 59, 165, -42, 58, + 166, -37, 57, 167, -33, 57, 167, -29, 56, 168, -25, 55, + 169, -21, 54, 170, -17, 54, 171, -13, 53, 172, -9, 53, + 173, -4, 52, 174, 0, 51, 175, 3, 51, 176, 7, 50, + 177, 11, 49, 178, 15, 48, 179, 19, 48, 180, 23, 47, + 181, 27, 47, 182, 32, 46, 183, 36, 45, 183, 40, 44, + 157, -88, 64, 158, -84, 63, 159, -80, 62, 160, -76, 62, + 160, -72, 61, 161, -68, 60, 162, -64, 60, 163, -59, 59, + 164, -55, 58, 165, -51, 58, 166, -47, 57, 167, -43, 56, + 168, -39, 55, 169, -35, 55, 170, -31, 54, 171, -27, 54, + 172, -22, 53, 173, -18, 52, 174, -14, 52, 174, -10, 51, + 176, -6, 50, 176, -2, 49, 177, 1, 49, 178, 5, 48, + 179, 9, 48, 180, 14, 47, 181, 18, 46, 182, 22, 45, + 183, 26, 45, 184, 30, 44, 185, 34, 43, 186, 38, 43, + 159, -90, 62, 160, -86, 61, 161, -82, 61, 162, -77, 60, + 163, -73, 59, 164, -69, 59, 165, -65, 58, 166, -61, 57, + 167, -57, 57, 167, -53, 56, 168, -49, 55, 169, -45, 55, + 170, -40, 54, 171, -36, 53, 172, -32, 53, 173, -28, 52, + 174, -24, 51, 175, -20, 50, 176, -16, 50, 177, -12, 49, + 178, -7, 48, 179, -3, 48, 180, 0, 47, 181, 4, 47, + 182, 8, 46, 183, 12, 45, 183, 16, 44, 184, 20, 44, + 185, 24, 43, 186, 29, 42, 187, 33, 42, 188, 37, 41, + 161, -91, 60, 162, -87, 60, 163, -83, 59, 164, -78, 58, + 165, -74, 58, 166, -70, 57, 167, -66, 56, 168, -62, 55, + 169, -58, 55, 170, -54, 54, 171, -50, 54, 172, -46, 53, + 173, -41, 52, 174, -37, 51, 175, -33, 51, 175, -29, 50, + 176, -25, 49, 177, -21, 49, 178, -17, 48, 179, -13, 48, + 180, -8, 47, 181, -4, 46, 182, 0, 45, 183, 3, 45, + 184, 7, 44, 185, 11, 43, 186, 15, 43, 187, 19, 42, + 188, 23, 41, 189, 28, 41, 190, 32, 40, 190, 36, 39, + 164, -92, 59, 165, -88, 58, 166, -84, 57, 167, -80, 56, + 168, -76, 56, 168, -72, 55, 169, -68, 55, 170, -63, 54, + 171, -59, 53, 172, -55, 53, 173, -51, 52, 174, -47, 51, + 175, -43, 50, 176, -39, 50, 177, -35, 49, 178, -31, 49, + 179, -26, 48, 180, -22, 47, 181, -18, 46, 182, -14, 46, + 183, -10, 45, 183, -6, 44, 184, -2, 44, 185, 1, 43, + 186, 5, 43, 187, 10, 42, 188, 14, 41, 189, 18, 40, + 190, 22, 40, 191, 26, 39, 192, 30, 38, 193, 34, 38, + 166, -94, 57, 167, -90, 56, 168, -86, 56, 169, -81, 55, + 170, -77, 54, 171, -73, 54, 172, -69, 53, 173, -65, 52, + 174, -61, 51, 175, -57, 51, 175, -53, 50, 176, -49, 50, + 177, -44, 49, 178, -40, 48, 179, -36, 47, 180, -32, 47, + 181, -28, 46, 182, -24, 45, 183, -20, 45, 184, -16, 44, + 185, -11, 43, 186, -7, 43, 187, -3, 42, 188, 0, 41, + 189, 4, 41, 190, 8, 40, 190, 12, 39, 191, 16, 39, + 192, 20, 38, 193, 25, 37, 194, 29, 37, 195, 33, 36, + 168, -95, 55, 169, -91, 55, 170, -87, 54, 171, -82, 53, + 172, -78, 52, 173, -74, 52, 174, -70, 51, 175, -66, 50, + 176, -62, 50, 177, -58, 49, 178, -54, 49, 179, -50, 48, + 180, -45, 47, 181, -41, 46, 182, -37, 46, 182, -33, 45, + 183, -29, 44, 184, -25, 44, 185, -21, 43, 186, -17, 42, + 187, -12, 42, 188, -8, 41, 189, -4, 40, 190, 0, 40, + 191, 3, 39, 192, 7, 38, 193, 11, 38, 194, 15, 37, + 195, 19, 36, 196, 24, 36, 197, 28, 35, 198, 32, 34, + 171, -96, 53, 172, -92, 53, 173, -88, 52, 174, -84, 51, + 175, -80, 51, 175, -76, 50, 176, -72, 50, 177, -67, 49, + 178, -63, 48, 179, -59, 47, 180, -55, 47, 181, -51, 46, + 182, -47, 45, 183, -43, 45, 184, -39, 44, 185, -35, 43, + 186, -30, 43, 187, -26, 42, 188, -22, 41, 189, -18, 41, + 190, -14, 40, 191, -10, 39, 191, -6, 39, 192, -2, 38, + 193, 1, 37, 194, 6, 37, 195, 10, 36, 196, 14, 35, + 197, 18, 35, 198, 22, 34, 199, 26, 33, 200, 30, 33, + 173, -97, 52, 174, -93, 51, 175, -89, 51, 176, -85, 50, + 177, -81, 49, 178, -77, 48, 179, -73, 48, 180, -68, 47, + 181, -64, 46, 182, -60, 46, 182, -56, 45, 183, -52, 45, + 184, -48, 44, 185, -44, 43, 186, -40, 42, 187, -36, 42, + 188, -31, 41, 189, -27, 40, 190, -23, 40, 191, -19, 39, + 192, -15, 38, 193, -11, 38, 194, -7, 37, 195, -3, 36, + 196, 0, 36, 197, 5, 35, 198, 9, 34, 198, 13, 34, + 199, 17, 33, 200, 21, 32, 201, 25, 32, 202, 29, 31, + 175, -99, 50, 176, -95, 49, 177, -91, 49, 178, -86, 48, + 179, -82, 47, 180, -78, 47, 181, -74, 46, 182, -70, 45, + 183, -66, 45, 184, -62, 44, 185, -58, 43, 186, -54, 43, + 187, -49, 42, 188, -45, 41, 189, -41, 41, 190, -37, 40, + 191, -33, 39, 191, -29, 39, 192, -25, 38, 193, -21, 37, + 194, -16, 37, 195, -12, 36, 196, -8, 35, 197, -4, 35, + 198, 0, 34, 199, 3, 33, 200, 7, 33, 201, 11, 32, + 202, 15, 31, 203, 20, 31, 204, 24, 30, 205, 28, 29, + 178, -100, 48, 179, -96, 48, 180, -92, 47, 181, -88, 46, + 182, -84, 46, 183, -80, 45, 183, -76, 44, 184, -71, 44, + 185, -67, 43, 186, -63, 42, 187, -59, 42, 188, -55, 41, + 189, -51, 40, 190, -47, 40, 191, -43, 39, 192, -39, 38, + 193, -34, 38, 194, -30, 37, 195, -26, 36, 196, -22, 36, + 197, -18, 35, 198, -14, 34, 198, -10, 34, 199, -6, 33, + 200, -2, 32, 201, 2, 32, 202, 6, 31, 203, 10, 30, + 204, 14, 30, 205, 18, 29, 206, 22, 28, 207, 26, 28, + 180, -101, 47, 181, -97, 46, 182, -93, 45, 183, -89, 45, + 184, -85, 44, 185, -81, 43, 186, -77, 43, 187, -72, 42, + 188, -68, 41, 189, -64, 41, 190, -60, 40, 190, -56, 39, + 191, -52, 39, 192, -48, 38, 193, -44, 37, 194, -40, 37, + 195, -35, 36, 196, -31, 35, 197, -27, 35, 198, -23, 34, + 199, -19, 33, 200, -15, 33, 201, -11, 32, 202, -7, 31, + 203, -3, 31, 204, 1, 30, 205, 5, 29, 205, 9, 29, + 206, 13, 28, 207, 17, 27, 208, 21, 27, 209, 25, 26, + 183, -103, 45, 183, -99, 44, 184, -95, 44, 185, -90, 43, + 186, -86, 42, 187, -82, 42, 188, -78, 41, 189, -74, 40, + 190, -70, 40, 191, -66, 39, 192, -62, 38, 193, -58, 38, + 194, -53, 37, 195, -49, 36, 196, -45, 36, 197, -41, 35, + 198, -37, 34, 198, -33, 34, 199, -29, 33, 200, -25, 32, + 201, -20, 32, 202, -16, 31, 203, -12, 30, 204, -8, 30, + 205, -4, 29, 206, 0, 28, 207, 3, 28, 208, 7, 27, + 209, 11, 26, 210, 16, 26, 211, 20, 25, 212, 24, 24, + 185, -104, 43, 186, -100, 43, 187, -96, 42, 188, -92, 41, + 189, -88, 41, 190, -84, 40, 190, -80, 39, 191, -75, 39, + 192, -71, 38, 193, -67, 37, 194, -63, 37, 195, -59, 36, + 196, -55, 35, 197, -51, 35, 198, -47, 34, 199, -43, 33, + 200, -38, 33, 201, -34, 32, 202, -30, 31, 203, -26, 31, + 204, -22, 30, 205, -18, 29, 206, -14, 29, 206, -10, 28, + 207, -6, 27, 208, -1, 27, 209, 2, 26, 210, 6, 25, + 211, 10, 25, 212, 14, 24, 213, 18, 23, 214, 22, 23, + 187, -105, 42, 188, -101, 41, 189, -97, 40, 190, -93, 40, + 191, -89, 39, 192, -85, 38, 193, -81, 38, 194, -76, 37, + 195, -72, 36, 196, -68, 36, 197, -64, 35, 197, -60, 34, + 199, -56, 34, 199, -52, 33, 200, -48, 32, 201, -44, 32, + 202, -39, 31, 203, -35, 30, 204, -31, 30, 205, -27, 29, + 206, -23, 28, 207, -19, 28, 208, -15, 27, 209, -11, 26, + 210, -7, 26, 211, -2, 25, 212, 1, 24, 213, 5, 24, + 213, 9, 23, 214, 13, 22, 215, 17, 22, 216, 21, 21, + 190, -107, 40, 190, -103, 39, 191, -99, 39, 192, -94, 38, + 193, -90, 37, 194, -86, 37, 195, -82, 36, 196, -78, 35, + 197, -74, 35, 198, -70, 34, 199, -66, 33, 200, -62, 33, + 201, -57, 32, 202, -53, 31, 203, -49, 31, 204, -45, 30, + 205, -41, 29, 206, -37, 29, 206, -33, 28, 207, -29, 27, + 208, -24, 27, 209, -20, 26, 210, -16, 25, 211, -12, 25, + 212, -8, 24, 213, -4, 23, 214, 0, 23, 215, 3, 22, + 216, 7, 21, 217, 12, 21, 218, 16, 20, 219, 20, 19, + 192, -108, 38, 193, -104, 38, 194, -100, 37, 195, -96, 36, + 196, -92, 36, 197, -88, 35, 198, -84, 34, 199, -79, 34, + 199, -75, 33, 200, -71, 32, 201, -67, 32, 202, -63, 31, + 203, -59, 30, 204, -55, 30, 205, -51, 29, 206, -47, 28, + 207, -42, 28, 208, -38, 27, 209, -34, 26, 210, -30, 26, + 211, -26, 25, 212, -22, 24, 213, -18, 24, 213, -14, 23, + 214, -10, 22, 215, -5, 22, 216, -1, 21, 217, 2, 20, + 218, 6, 20, 219, 10, 19, 220, 14, 18, 221, 18, 18, + 194, -109, 37, 195, -105, 36, 196, -101, 35, 197, -97, 35, + 198, -93, 34, 199, -89, 33, 200, -85, 33, 201, -80, 32, + 202, -76, 31, 203, -72, 31, 204, -68, 30, 205, -64, 29, + 206, -60, 29, 206, -56, 28, 207, -52, 27, 208, -48, 27, + 209, -43, 26, 210, -39, 25, 211, -35, 25, 212, -31, 24, + 213, -27, 23, 214, -23, 23, 215, -19, 22, 216, -15, 21, + 217, -11, 21, 218, -6, 20, 219, -2, 19, 220, 1, 19, + 220, 5, 18, 222, 9, 17, 222, 13, 17, 223, 17, 16, + 197, -111, 35, 198, -107, 34, 198, -103, 34, 199, -98, 33, + 200, -94, 32, 201, -90, 32, 202, -86, 31, 203, -82, 30, + 204, -78, 30, 205, -74, 29, 206, -70, 28, 207, -66, 28, + 208, -61, 27, 209, -57, 26, 210, -53, 26, 211, -49, 25, + 212, -45, 24, 213, -41, 24, 213, -37, 23, 214, -33, 22, + 215, -28, 22, 216, -24, 21, 217, -20, 20, 218, -16, 20, + 219, -12, 19, 220, -8, 18, 221, -4, 18, 222, 0, 17, + 223, 3, 16, 224, 8, 16, 225, 12, 15, 226, 16, 14, + 200, -112, 33, 200, -108, 32, 201, -104, 32, 202, -100, 31, + 203, -96, 30, 204, -92, 30, 205, -88, 29, 206, -83, 28, + 207, -79, 28, 208, -75, 27, 209, -71, 26, 210, -67, 26, + 211, -63, 25, 212, -59, 24, 213, -55, 24, 214, -51, 23, + 215, -46, 22, 216, -42, 22, 216, -38, 21, 217, -34, 20, + 218, -30, 20, 219, -26, 19, 220, -22, 18, 221, -18, 18, + 222, -14, 17, 223, -9, 16, 224, -5, 16, 225, -1, 15, + 226, 2, 14, 227, 6, 14, 228, 10, 13, 229, 14, 12, + 202, -114, 31, 203, -110, 31, 204, -106, 30, 205, -101, 29, + 206, -97, 29, 207, -93, 28, 207, -89, 27, 209, -85, 27, + 209, -81, 26, 210, -77, 25, 211, -73, 25, 212, -69, 24, + 213, -64, 23, 214, -60, 23, 215, -56, 22, 216, -52, 21, + 217, -48, 21, 218, -44, 20, 219, -40, 19, 220, -36, 19, + 221, -31, 18, 222, -27, 17, 223, -23, 17, 223, -19, 16, + 224, -15, 15, 225, -11, 15, 226, -7, 14, 227, -3, 13, + 228, 0, 13, 229, 5, 12, 230, 9, 11, 231, 13, 11, + 204, -115, 30, 205, -111, 29, 206, -107, 28, 207, -103, 28, + 208, -99, 27, 209, -95, 26, 210, -91, 26, 211, -86, 25, + 212, -82, 24, 213, -78, 24, 214, -74, 23, 215, -70, 22, + 216, -66, 22, 216, -62, 21, 217, -58, 20, 218, -54, 20, + 219, -49, 19, 220, -45, 18, 221, -41, 18, 222, -37, 17, + 223, -33, 16, 224, -29, 16, 225, -25, 15, 226, -21, 14, + 227, -17, 14, 228, -12, 13, 229, -8, 12, 230, -4, 12, + 230, 0, 11, 231, 3, 10, 232, 7, 10, 233, 11, 9, + 207, -116, 28, 208, -112, 27, 208, -108, 27, 209, -104, 26, + 210, -100, 25, 211, -96, 25, 212, -92, 24, 213, -87, 23, + 214, -83, 23, 215, -79, 22, 216, -75, 21, 217, -71, 21, + 218, -67, 20, 219, -63, 19, 220, -59, 19, 221, -55, 18, + 222, -50, 17, 223, -46, 17, 223, -42, 16, 224, -38, 15, + 225, -34, 15, 226, -30, 14, 227, -26, 13, 228, -22, 13, + 229, -18, 12, 230, -13, 11, 231, -9, 11, 232, -5, 10, + 233, -1, 9, 234, 2, 9, 235, 6, 8, 236, 10, 7, + 209, -118, 26, 210, -114, 26, 211, -110, 25, 212, -105, 24, + 213, -101, 24, 214, -97, 23, 215, -93, 22, 216, -89, 22, + 216, -85, 21, 217, -81, 20, 218, -77, 20, 219, -73, 19, + 220, -68, 18, 221, -64, 18, 222, -60, 17, 223, -56, 16, + 224, -52, 16, 225, -48, 15, 226, -44, 14, 227, -40, 14, + 228, -35, 13, 229, -31, 12, 230, -27, 12, 230, -23, 11, + 231, -19, 10, 232, -15, 10, 233, -11, 9, 234, -7, 8, + 235, -3, 8, 236, 1, 7, 237, 5, 6, 238, 9, 6, + 211, -119, 25, 212, -115, 24, 213, -111, 23, 214, -107, 23, + 215, -103, 22, 216, -99, 21, 217, -95, 21, 218, -90, 20, + 219, -86, 19, 220, -82, 19, 221, -78, 18, 222, -74, 17, + 223, -70, 17, 223, -66, 16, 224, -62, 15, 225, -58, 15, + 226, -53, 14, 227, -49, 13, 228, -45, 13, 229, -41, 12, + 230, -37, 11, 231, -33, 11, 232, -29, 10, 233, -25, 9, + 234, -21, 9, 235, -16, 8, 236, -12, 7, 237, -8, 7, + 238, -4, 6, 239, 0, 5, 239, 3, 5, 240, 7, 4, + 214, -120, 23, 215, -116, 22, 215, -112, 22, 216, -108, 21, + 217, -104, 20, 218, -100, 20, 219, -96, 19, 220, -91, 18, + 221, -87, 18, 222, -83, 17, 223, -79, 16, 224, -75, 16, + 225, -71, 15, 226, -67, 14, 227, -63, 14, 228, -59, 13, + 229, -54, 12, 230, -50, 12, 231, -46, 11, 231, -42, 10, + 232, -38, 10, 233, -34, 9, 234, -30, 8, 235, -26, 8, + 236, -22, 7, 237, -17, 6, 238, -13, 6, 239, -9, 5, + 240, -5, 4, 241, -1, 3, 242, 2, 3, 243, 6, 2, + 216, -122, 21, 217, -118, 21, 218, -114, 20, 219, -109, 19, + 220, -105, 19, 221, -101, 18, 222, -97, 17, 223, -93, 17, + 224, -89, 16, 224, -85, 15, 225, -81, 15, 226, -77, 14, + 227, -72, 13, 228, -68, 13, 229, -64, 12, 230, -60, 11, + 231, -56, 11, 232, -52, 10, 233, -48, 9, 234, -44, 9, + 235, -39, 8, 236, -35, 7, 237, -31, 7, 238, -27, 6, + 238, -23, 5, 239, -19, 5, 240, -15, 4, 241, -11, 3, + 242, -7, 3, 243, -2, 2, 244, 1, 1, 245, 5, 1, + 218, -123, 20, 219, -119, 19, 220, -115, 18, 221, -111, 18, + 222, -107, 17, 223, -103, 16, 224, -99, 16, 225, -94, 15, + 226, -90, 14, 227, -86, 14, 228, -82, 13, 229, -78, 12, + 230, -74, 12, 231, -70, 11, 231, -66, 10, 232, -62, 10, + 233, -57, 9, 234, -53, 8, 235, -49, 8, 236, -45, 7, + 237, -41, 6, 238, -37, 6, 239, -33, 5, 240, -29, 4, + 241, -25, 4, 242, -20, 3, 243, -16, 2, 244, -12, 2, + 245, -8, 1, 246, -4, 0, 247, 0, 0, 247, 4, 0, + 221, -124, 18, 222, -120, 17, 223, -116, 17, 224, -112, 16, + 224, -108, 15, 225, -104, 15, 226, -100, 14, 227, -95, 13, + 228, -91, 13, 229, -87, 12, 230, -83, 11, 231, -79, 11, + 232, -75, 10, 233, -71, 9, 234, -67, 9, 235, -63, 8, + 236, -58, 7, 237, -54, 7, 238, -50, 6, 238, -46, 5, + 240, -42, 4, 240, -38, 4, 241, -34, 3, 242, -30, 3, + 243, -26, 2, 244, -21, 1, 245, -17, 1, 246, -13, 0, + 247, -9, 0, 248, -5, -1, 249, -1, -1, 250, 2, -2, + 223, -126, 16, 224, -122, 16, 225, -118, 15, 226, -113, 14, + 227, -109, 14, 228, -105, 13, 229, -101, 12, 230, -97, 12, + 231, -93, 11, 231, -89, 10, 232, -85, 10, 233, -81, 9, + 234, -76, 8, 235, -72, 8, 236, -68, 7, 237, -64, 6, + 238, -60, 6, 239, -56, 5, 240, -52, 4, 241, -48, 4, + 242, -43, 3, 243, -39, 2, 244, -35, 2, 245, -31, 1, + 245, -27, 0, 247, -23, 0, 247, -19, 0, 248, -15, -1, + 249, -11, -1, 250, -6, -2, 251, -2, -3, 252, 1, -3, + 76, -43, 127, 77, -39, 126, 78, -35, 126, 79, -30, 125, + 80, -26, 124, 80, -22, 124, 81, -18, 123, 82, -14, 122, + 83, -10, 122, 84, -6, 121, 85, -2, 120, 86, 1, 120, + 87, 6, 119, 88, 10, 118, 89, 14, 118, 90, 18, 117, + 91, 22, 116, 92, 26, 116, 93, 30, 115, 94, 34, 114, + 95, 39, 114, 95, 43, 113, 96, 47, 112, 97, 51, 112, + 98, 55, 111, 99, 59, 110, 100, 63, 110, 101, 67, 109, + 102, 71, 108, 103, 76, 108, 104, 80, 107, 105, 84, 106, + 78, -44, 125, 79, -40, 125, 80, -36, 124, 81, -31, 123, + 82, -27, 123, 83, -23, 122, 84, -19, 121, 85, -15, 121, + 86, -11, 120, 87, -7, 119, 87, -3, 119, 88, 0, 118, + 89, 5, 117, 90, 9, 117, 91, 13, 116, 92, 17, 115, + 93, 21, 115, 94, 25, 114, 95, 29, 113, 96, 33, 113, + 97, 38, 112, 98, 42, 111, 99, 46, 111, 100, 50, 110, + 101, 54, 109, 102, 58, 109, 102, 62, 108, 103, 66, 107, + 104, 70, 107, 105, 75, 106, 106, 79, 105, 107, 83, 105, + 80, -45, 124, 81, -41, 123, 82, -37, 122, 83, -33, 122, + 84, -29, 121, 85, -25, 120, 86, -21, 120, 87, -16, 119, + 88, -12, 118, 89, -8, 118, 90, -4, 117, 91, 0, 116, + 92, 3, 116, 93, 7, 115, 94, 11, 114, 94, 15, 114, + 95, 20, 113, 96, 24, 112, 97, 28, 112, 98, 32, 111, + 99, 36, 110, 100, 40, 110, 101, 44, 109, 102, 48, 108, + 103, 52, 108, 104, 57, 107, 105, 61, 106, 106, 65, 106, + 107, 69, 105, 108, 73, 104, 109, 77, 104, 110, 81, 103, + 83, -47, 122, 84, -43, 121, 85, -39, 121, 86, -34, 120, + 87, -30, 119, 87, -26, 119, 88, -22, 118, 89, -18, 117, + 90, -14, 117, 91, -10, 116, 92, -6, 115, 93, -2, 115, + 94, 2, 114, 95, 6, 113, 96, 10, 113, 97, 14, 112, + 98, 18, 111, 99, 22, 111, 100, 26, 110, 101, 30, 109, + 102, 35, 109, 103, 39, 108, 103, 43, 107, 104, 47, 107, + 105, 51, 106, 106, 55, 105, 107, 59, 105, 108, 63, 104, + 109, 67, 103, 110, 72, 103, 111, 76, 102, 112, 80, 101, + 85, -48, 120, 86, -44, 120, 87, -40, 119, 88, -35, 118, + 89, -31, 118, 90, -27, 117, 91, -23, 116, 92, -19, 116, + 93, -15, 115, 94, -11, 114, 94, -7, 114, 95, -3, 113, + 96, 1, 112, 97, 5, 112, 98, 9, 111, 99, 13, 110, + 100, 17, 110, 101, 21, 109, 102, 25, 108, 103, 29, 108, + 104, 34, 107, 105, 38, 106, 106, 42, 106, 107, 46, 105, + 108, 50, 104, 109, 54, 104, 110, 58, 103, 110, 62, 102, + 111, 66, 102, 112, 71, 101, 113, 75, 100, 114, 79, 100, + 87, -49, 119, 88, -45, 118, 89, -41, 117, 90, -37, 117, + 91, -33, 116, 92, -29, 115, 93, -25, 115, 94, -20, 114, + 95, -16, 113, 96, -12, 113, 97, -8, 112, 98, -4, 111, + 99, 0, 111, 100, 3, 110, 101, 7, 109, 102, 11, 109, + 103, 16, 108, 103, 20, 107, 104, 24, 107, 105, 28, 106, + 106, 32, 105, 107, 36, 105, 108, 40, 104, 109, 44, 103, + 110, 48, 103, 111, 53, 102, 112, 57, 101, 113, 61, 101, + 114, 65, 100, 115, 69, 99, 116, 73, 99, 117, 77, 98, + 90, -50, 117, 91, -46, 116, 92, -42, 116, 93, -38, 115, + 94, -34, 114, 95, -30, 114, 95, -26, 113, 96, -21, 112, + 97, -17, 112, 98, -13, 111, 99, -9, 110, 100, -5, 110, + 101, -1, 109, 102, 2, 108, 103, 6, 108, 104, 10, 107, + 105, 15, 106, 106, 19, 106, 107, 23, 105, 108, 27, 104, + 109, 31, 104, 110, 35, 103, 110, 39, 102, 111, 43, 102, + 112, 47, 101, 113, 52, 100, 114, 56, 100, 115, 60, 99, + 116, 64, 98, 117, 68, 98, 118, 72, 97, 119, 76, 96, + 92, -52, 115, 93, -48, 115, 94, -44, 114, 95, -39, 113, + 96, -35, 113, 97, -31, 112, 98, -27, 111, 99, -23, 111, + 100, -19, 110, 101, -15, 109, 102, -11, 109, 102, -7, 108, + 103, -2, 107, 104, 1, 107, 105, 5, 106, 106, 9, 105, + 107, 13, 105, 108, 17, 104, 109, 21, 103, 110, 25, 103, + 111, 30, 102, 112, 34, 101, 113, 38, 101, 114, 42, 100, + 115, 46, 99, 116, 50, 99, 117, 54, 98, 117, 58, 97, + 118, 62, 97, 119, 67, 96, 120, 71, 95, 121, 75, 95, + 95, -53, 114, 95, -49, 113, 96, -45, 112, 97, -41, 112, + 98, -37, 111, 99, -33, 110, 100, -29, 110, 101, -24, 109, + 102, -20, 108, 103, -16, 108, 104, -12, 107, 105, -8, 106, + 106, -4, 106, 107, 0, 105, 108, 3, 104, 109, 7, 104, + 110, 12, 103, 110, 16, 102, 111, 20, 102, 112, 24, 101, + 113, 28, 100, 114, 32, 100, 115, 36, 99, 116, 40, 98, + 117, 44, 98, 118, 49, 97, 119, 53, 96, 120, 57, 96, + 121, 61, 95, 122, 65, 94, 123, 69, 94, 124, 73, 93, + 97, -54, 112, 98, -50, 111, 99, -46, 111, 100, -42, 110, + 101, -38, 109, 102, -34, 109, 102, -30, 108, 103, -25, 107, + 104, -21, 107, 105, -17, 106, 106, -13, 105, 107, -9, 105, + 108, -5, 104, 109, -1, 103, 110, 2, 103, 111, 6, 102, + 112, 11, 101, 113, 15, 101, 114, 19, 100, 115, 23, 99, + 116, 27, 99, 117, 31, 98, 118, 35, 97, 118, 39, 97, + 119, 43, 96, 120, 48, 95, 121, 52, 95, 122, 56, 94, + 123, 60, 93, 124, 64, 92, 125, 68, 92, 126, 72, 91, + 99, -56, 110, 100, -52, 110, 101, -48, 109, 102, -43, 108, + 103, -39, 108, 104, -35, 107, 105, -31, 106, 106, -27, 106, + 107, -23, 105, 108, -19, 104, 109, -15, 104, 109, -11, 103, + 111, -6, 102, 111, -2, 102, 112, 1, 101, 113, 5, 100, + 114, 9, 100, 115, 13, 99, 116, 17, 98, 117, 21, 98, + 118, 26, 97, 119, 30, 96, 120, 34, 96, 121, 38, 95, + 122, 42, 94, 123, 46, 94, 124, 50, 93, 125, 54, 92, + 125, 58, 92, 126, 63, 91, 127, 67, 90, 128, 71, 90, + 102, -57, 108, 103, -53, 108, 104, -49, 107, 105, -45, 106, + 106, -41, 105, 107, -37, 105, 108, -33, 104, 109, -28, 103, + 110, -24, 103, 111, -20, 102, 112, -16, 101, 112, -12, 101, + 113, -8, 100, 114, -4, 99, 115, 0, 99, 116, 3, 98, + 117, 8, 97, 118, 12, 97, 119, 16, 96, 120, 20, 95, + 121, 24, 95, 122, 28, 94, 123, 32, 93, 124, 36, 93, + 125, 40, 92, 126, 45, 91, 127, 49, 91, 127, 53, 90, + 128, 57, 89, 129, 61, 89, 130, 65, 88, 131, 69, 87, + 105, -59, 106, 105, -55, 106, 106, -51, 105, 107, -46, 104, + 108, -42, 104, 109, -38, 103, 110, -34, 102, 111, -30, 102, + 112, -26, 101, 113, -22, 100, 114, -18, 100, 115, -14, 99, + 116, -9, 98, 117, -5, 98, 118, -1, 97, 119, 2, 96, + 120, 6, 96, 120, 10, 95, 121, 14, 94, 122, 18, 94, + 123, 23, 93, 124, 27, 92, 125, 31, 92, 126, 35, 91, + 127, 39, 90, 128, 43, 90, 129, 47, 89, 130, 51, 88, + 131, 55, 88, 132, 60, 87, 133, 64, 86, 134, 68, 86, + 107, -60, 105, 108, -56, 104, 109, -52, 104, 110, -48, 103, + 111, -44, 102, 112, -40, 101, 112, -36, 101, 113, -31, 100, + 114, -27, 99, 115, -23, 99, 116, -19, 98, 117, -15, 97, + 118, -11, 97, 119, -7, 96, 120, -3, 95, 121, 0, 95, + 122, 5, 94, 123, 9, 93, 124, 13, 93, 125, 17, 92, + 126, 21, 91, 127, 25, 91, 127, 29, 90, 128, 33, 89, + 129, 37, 89, 130, 42, 88, 131, 46, 87, 132, 50, 87, + 133, 54, 86, 134, 58, 85, 135, 62, 85, 136, 66, 84, + 109, -61, 103, 110, -57, 102, 111, -53, 102, 112, -49, 101, + 113, -45, 100, 114, -41, 100, 115, -37, 99, 116, -32, 98, + 117, -28, 98, 118, -24, 97, 119, -20, 96, 119, -16, 96, + 120, -12, 95, 121, -8, 94, 122, -4, 94, 123, 0, 93, + 124, 4, 92, 125, 8, 92, 126, 12, 91, 127, 16, 90, + 128, 20, 90, 129, 24, 89, 130, 28, 88, 131, 32, 88, + 132, 36, 87, 133, 41, 86, 134, 45, 86, 135, 49, 85, + 135, 53, 84, 136, 57, 84, 137, 61, 83, 138, 65, 82, + 112, -63, 101, 112, -59, 101, 113, -55, 100, 114, -50, 99, + 115, -46, 99, 116, -42, 98, 117, -38, 97, 118, -34, 97, + 119, -30, 96, 120, -26, 95, 121, -22, 95, 122, -18, 94, + 123, -13, 93, 124, -9, 93, 125, -5, 92, 126, -1, 91, + 127, 2, 91, 128, 6, 90, 128, 10, 89, 129, 14, 89, + 130, 19, 88, 131, 23, 87, 132, 27, 87, 133, 31, 86, + 134, 35, 85, 135, 39, 85, 136, 43, 84, 137, 47, 83, + 138, 51, 83, 139, 56, 82, 140, 60, 81, 141, 64, 81, + 114, -64, 100, 115, -60, 99, 116, -56, 98, 117, -52, 98, + 118, -48, 97, 119, -44, 96, 119, -40, 96, 121, -35, 95, + 121, -31, 94, 122, -27, 94, 123, -23, 93, 124, -19, 92, + 125, -15, 92, 126, -11, 91, 127, -7, 90, 128, -3, 90, + 129, 1, 89, 130, 5, 88, 131, 9, 88, 132, 13, 87, + 133, 17, 86, 134, 21, 86, 135, 25, 85, 135, 29, 84, + 136, 33, 84, 137, 38, 83, 138, 42, 82, 139, 46, 82, + 140, 50, 81, 141, 54, 80, 142, 58, 80, 143, 62, 79, + 116, -65, 98, 117, -61, 97, 118, -57, 97, 119, -53, 96, + 120, -49, 95, 121, -45, 95, 122, -41, 94, 123, -36, 93, + 124, -32, 93, 125, -28, 92, 126, -24, 91, 127, -20, 91, + 128, -16, 90, 128, -12, 89, 129, -8, 89, 130, -4, 88, + 131, 0, 87, 132, 4, 87, 133, 8, 86, 134, 12, 85, + 135, 16, 85, 136, 20, 84, 137, 24, 83, 138, 28, 83, + 139, 32, 82, 140, 37, 81, 141, 41, 81, 142, 45, 80, + 142, 49, 79, 143, 53, 79, 144, 57, 78, 145, 61, 77, + 119, -67, 96, 120, -63, 96, 120, -59, 95, 121, -54, 94, + 122, -50, 94, 123, -46, 93, 124, -42, 92, 125, -38, 92, + 126, -34, 91, 127, -30, 90, 128, -26, 90, 129, -22, 89, + 130, -17, 88, 131, -13, 88, 132, -9, 87, 133, -5, 86, + 134, -1, 86, 135, 2, 85, 135, 6, 84, 136, 10, 84, + 137, 15, 83, 138, 19, 82, 139, 23, 82, 140, 27, 81, + 141, 31, 80, 142, 35, 80, 143, 39, 79, 144, 43, 78, + 145, 47, 78, 146, 52, 77, 147, 56, 76, 148, 60, 76, + 121, -68, 95, 122, -64, 94, 123, -60, 93, 124, -56, 93, + 125, -52, 92, 126, -48, 91, 127, -44, 91, 128, -39, 90, + 128, -35, 89, 129, -31, 89, 130, -27, 88, 131, -23, 87, + 132, -19, 87, 133, -15, 86, 134, -11, 85, 135, -7, 85, + 136, -2, 84, 137, 1, 83, 138, 5, 83, 139, 9, 82, + 140, 13, 81, 141, 17, 81, 142, 21, 80, 142, 25, 79, + 143, 29, 79, 144, 34, 78, 145, 38, 77, 146, 42, 77, + 147, 46, 76, 148, 50, 75, 149, 54, 75, 150, 58, 74, + 123, -69, 93, 124, -65, 92, 125, -61, 92, 126, -57, 91, + 127, -53, 90, 128, -49, 90, 129, -45, 89, 130, -40, 88, + 131, -36, 88, 132, -32, 87, 133, -28, 86, 134, -24, 86, + 135, -20, 85, 135, -16, 84, 136, -12, 84, 137, -8, 83, + 138, -3, 82, 139, 0, 82, 140, 4, 81, 141, 8, 80, + 142, 12, 80, 143, 16, 79, 144, 20, 78, 145, 24, 78, + 146, 28, 77, 147, 33, 76, 148, 37, 76, 149, 41, 75, + 150, 45, 74, 151, 49, 74, 151, 53, 73, 152, 57, 72, + 126, -71, 91, 127, -67, 91, 127, -63, 90, 128, -58, 89, + 129, -54, 89, 130, -50, 88, 131, -46, 87, 132, -42, 87, + 133, -38, 86, 134, -34, 85, 135, -30, 85, 136, -26, 84, + 137, -21, 83, 138, -17, 83, 139, -13, 82, 140, -9, 81, + 141, -5, 81, 142, -1, 80, 143, 2, 79, 143, 6, 79, + 144, 11, 78, 145, 15, 77, 146, 19, 77, 147, 23, 76, + 148, 27, 75, 149, 31, 75, 150, 35, 74, 151, 39, 73, + 152, 43, 73, 153, 48, 72, 154, 52, 71, 155, 56, 71, + 128, -72, 90, 129, -68, 89, 130, -64, 88, 131, -60, 88, + 132, -56, 87, 133, -52, 86, 134, -48, 86, 135, -43, 85, + 136, -39, 84, 136, -35, 84, 137, -31, 83, 138, -27, 82, + 139, -23, 82, 140, -19, 81, 141, -15, 80, 142, -11, 80, + 143, -6, 79, 144, -2, 78, 145, 1, 78, 146, 5, 77, + 147, 9, 76, 148, 13, 76, 149, 17, 75, 150, 21, 74, + 150, 25, 74, 151, 30, 73, 152, 34, 72, 153, 38, 72, + 154, 42, 71, 155, 46, 70, 156, 50, 70, 157, 54, 69, + 130, -73, 88, 131, -69, 87, 132, -65, 87, 133, -61, 86, + 134, -57, 85, 135, -53, 85, 136, -49, 84, 137, -44, 83, + 138, -40, 83, 139, -36, 82, 140, -32, 81, 141, -28, 81, + 142, -24, 80, 143, -20, 79, 143, -16, 79, 144, -12, 78, + 145, -7, 77, 146, -3, 77, 147, 0, 76, 148, 4, 75, + 149, 8, 75, 150, 12, 74, 151, 16, 73, 152, 20, 73, + 153, 24, 72, 154, 29, 71, 155, 33, 71, 156, 37, 70, + 157, 41, 69, 158, 45, 69, 158, 49, 68, 159, 53, 67, + 133, -75, 86, 134, -71, 86, 135, -67, 85, 136, -62, 84, + 136, -58, 84, 137, -54, 83, 138, -50, 82, 139, -46, 82, + 140, -42, 81, 141, -38, 80, 142, -34, 80, 143, -30, 79, + 144, -25, 78, 145, -21, 78, 146, -17, 77, 147, -13, 76, + 148, -9, 76, 149, -5, 75, 150, -1, 74, 150, 2, 74, + 151, 7, 73, 152, 11, 72, 153, 15, 72, 154, 19, 71, + 155, 23, 70, 156, 27, 70, 157, 31, 69, 158, 35, 68, + 159, 39, 68, 160, 44, 67, 161, 48, 66, 162, 52, 66, + 135, -76, 85, 136, -72, 84, 137, -68, 83, 138, -63, 83, + 139, -59, 82, 140, -55, 81, 141, -51, 81, 142, -47, 80, + 143, -43, 79, 143, -39, 79, 144, -35, 78, 145, -31, 77, + 146, -26, 77, 147, -22, 76, 148, -18, 75, 149, -14, 75, + 150, -10, 74, 151, -6, 73, 152, -2, 73, 153, 1, 72, + 154, 6, 71, 155, 10, 71, 156, 14, 70, 157, 18, 69, + 157, 22, 69, 159, 26, 68, 159, 30, 67, 160, 34, 67, + 161, 38, 66, 162, 43, 65, 163, 47, 65, 164, 51, 64, + 137, -77, 83, 138, -73, 82, 139, -69, 82, 140, -65, 81, + 141, -61, 80, 142, -57, 80, 143, -53, 79, 144, -48, 78, + 145, -44, 78, 146, -40, 77, 147, -36, 76, 148, -32, 76, + 149, -28, 75, 150, -24, 74, 150, -20, 74, 151, -16, 73, + 152, -11, 72, 153, -7, 72, 154, -3, 71, 155, 0, 70, + 156, 4, 70, 157, 8, 69, 158, 12, 68, 159, 16, 68, + 160, 20, 67, 161, 25, 66, 162, 29, 66, 163, 33, 65, + 164, 37, 64, 165, 41, 64, 166, 45, 63, 166, 49, 62, + 140, -79, 81, 141, -75, 81, 142, -71, 80, 143, -66, 79, + 143, -62, 79, 144, -58, 78, 145, -54, 77, 146, -50, 77, + 147, -46, 76, 148, -42, 75, 149, -38, 75, 150, -34, 74, + 151, -29, 73, 152, -25, 73, 153, -21, 72, 154, -17, 71, + 155, -13, 71, 156, -9, 70, 157, -5, 69, 158, -1, 69, + 159, 3, 68, 159, 7, 67, 160, 11, 67, 161, 15, 66, + 162, 19, 65, 163, 23, 65, 164, 27, 64, 165, 31, 63, + 166, 35, 63, 167, 40, 62, 168, 44, 61, 169, 48, 61, + 142, -80, 80, 143, -76, 79, 144, -72, 78, 145, -67, 78, + 146, -63, 77, 147, -59, 76, 148, -55, 76, 149, -51, 75, + 150, -47, 74, 151, -43, 74, 151, -39, 73, 152, -35, 72, + 153, -30, 72, 154, -26, 71, 155, -22, 70, 156, -18, 70, + 157, -14, 69, 158, -10, 68, 159, -6, 68, 160, -2, 67, + 161, 2, 66, 162, 6, 66, 163, 10, 65, 164, 14, 64, + 165, 18, 64, 166, 22, 63, 166, 26, 62, 167, 30, 62, + 168, 34, 61, 169, 39, 60, 170, 43, 60, 171, 47, 59, + 144, -81, 78, 145, -77, 77, 146, -73, 77, 147, -69, 76, + 148, -65, 75, 149, -61, 75, 150, -57, 74, 151, -52, 73, + 152, -48, 73, 153, -44, 72, 154, -40, 71, 155, -36, 71, + 156, -32, 70, 157, -28, 69, 158, -24, 69, 158, -20, 68, + 159, -15, 67, 160, -11, 67, 161, -7, 66, 162, -3, 65, + 163, 0, 65, 164, 4, 64, 165, 8, 63, 166, 12, 63, + 167, 16, 62, 168, 21, 61, 169, 25, 61, 170, 29, 60, + 171, 33, 59, 172, 37, 59, 173, 41, 58, 173, 45, 57, + 147, -83, 76, 148, -79, 76, 149, -75, 75, 150, -70, 74, + 151, -66, 74, 151, -62, 73, 152, -58, 72, 153, -54, 72, + 154, -50, 71, 155, -46, 70, 156, -42, 70, 157, -38, 69, + 158, -33, 68, 159, -29, 68, 160, -25, 67, 161, -21, 66, + 162, -17, 66, 163, -13, 65, 164, -9, 64, 165, -5, 64, + 166, 0, 63, 166, 3, 62, 167, 7, 62, 168, 11, 61, + 169, 15, 60, 170, 19, 60, 171, 23, 59, 172, 27, 58, + 173, 31, 58, 174, 36, 57, 175, 40, 56, 176, 44, 56, + 149, -84, 75, 150, -80, 74, 151, -76, 73, 152, -71, 73, + 153, -67, 72, 154, -63, 71, 155, -59, 71, 156, -55, 70, + 157, -51, 69, 158, -47, 69, 158, -43, 68, 159, -39, 67, + 160, -34, 67, 161, -30, 66, 162, -26, 65, 163, -22, 65, + 164, -18, 64, 165, -14, 63, 166, -10, 63, 167, -6, 62, + 168, -1, 61, 169, 2, 61, 170, 6, 60, 171, 10, 59, + 172, 14, 59, 173, 18, 58, 174, 22, 57, 174, 26, 57, + 175, 30, 56, 176, 35, 55, 177, 39, 55, 178, 43, 54, + 152, -86, 73, 153, -82, 72, 154, -78, 71, 155, -73, 71, + 156, -69, 70, 157, -65, 69, 158, -61, 69, 159, -57, 68, + 160, -53, 67, 160, -49, 67, 161, -45, 66, 162, -41, 65, + 163, -36, 65, 164, -32, 64, 165, -28, 63, 166, -24, 63, + 167, -20, 62, 168, -16, 61, 169, -12, 61, 170, -8, 60, + 171, -3, 59, 172, 0, 59, 173, 4, 58, 174, 8, 57, + 175, 12, 57, 176, 16, 56, 176, 20, 55, 177, 24, 55, + 178, 28, 54, 179, 33, 53, 180, 37, 52, 181, 41, 52, + 154, -87, 71, 155, -83, 70, 156, -79, 70, 157, -74, 69, + 158, -70, 68, 159, -66, 68, 160, -62, 67, 161, -58, 66, + 162, -54, 66, 163, -50, 65, 164, -46, 64, 165, -42, 64, + 166, -37, 63, 167, -33, 62, 168, -29, 62, 168, -25, 61, + 169, -21, 60, 170, -17, 60, 171, -13, 59, 172, -9, 58, + 173, -4, 57, 174, 0, 57, 175, 3, 56, 176, 7, 56, + 177, 11, 55, 178, 15, 54, 179, 19, 53, 180, 23, 53, + 181, 27, 52, 182, 32, 51, 183, 36, 51, 183, 40, 50, + 157, -88, 69, 158, -84, 69, 159, -80, 68, 160, -76, 67, + 161, -72, 67, 161, -68, 66, 162, -64, 65, 163, -59, 65, + 164, -55, 64, 165, -51, 63, 166, -47, 63, 167, -43, 62, + 168, -39, 61, 169, -35, 61, 170, -31, 60, 171, -27, 59, + 172, -22, 58, 173, -18, 58, 174, -14, 57, 175, -10, 57, + 176, -6, 56, 176, -2, 55, 177, 1, 55, 178, 5, 54, + 179, 9, 53, 180, 14, 52, 181, 18, 52, 182, 22, 51, + 183, 26, 51, 184, 30, 50, 185, 34, 49, 186, 38, 48, + 159, -90, 68, 160, -86, 67, 161, -82, 66, 162, -77, 66, + 163, -73, 65, 164, -69, 64, 165, -65, 64, 166, -61, 63, + 167, -57, 62, 168, -53, 62, 168, -49, 61, 169, -45, 60, + 170, -40, 59, 171, -36, 59, 172, -32, 58, 173, -28, 58, + 174, -24, 57, 175, -20, 56, 176, -16, 56, 177, -12, 55, + 178, -7, 54, 179, -3, 53, 180, 0, 53, 181, 4, 52, + 182, 8, 52, 183, 12, 51, 183, 16, 50, 184, 20, 49, + 185, 24, 49, 186, 29, 48, 187, 33, 47, 188, 37, 47, + 161, -91, 66, 162, -87, 65, 163, -83, 65, 164, -78, 64, + 165, -74, 63, 166, -70, 63, 167, -66, 62, 168, -62, 61, + 169, -58, 61, 170, -54, 60, 171, -50, 59, 172, -46, 59, + 173, -41, 58, 174, -37, 57, 175, -33, 57, 175, -29, 56, + 176, -25, 55, 177, -21, 54, 178, -17, 54, 179, -13, 53, + 180, -8, 52, 181, -4, 52, 182, 0, 51, 183, 3, 51, + 184, 7, 50, 185, 11, 49, 186, 15, 48, 187, 19, 48, + 188, 23, 47, 189, 28, 46, 190, 32, 46, 191, 36, 45, + 164, -92, 64, 165, -88, 64, 166, -84, 63, 167, -80, 62, + 168, -76, 62, 168, -72, 61, 169, -68, 60, 170, -63, 59, + 171, -59, 59, 172, -55, 58, 173, -51, 58, 174, -47, 57, + 175, -43, 56, 176, -39, 55, 177, -35, 55, 178, -31, 54, + 179, -26, 53, 180, -22, 53, 181, -18, 52, 182, -14, 52, + 183, -10, 51, 184, -6, 50, 184, -2, 49, 185, 1, 49, + 186, 5, 48, 187, 10, 47, 188, 14, 47, 189, 18, 46, + 190, 22, 45, 191, 26, 45, 192, 30, 44, 193, 34, 43, + 166, -94, 63, 167, -90, 62, 168, -86, 61, 169, -81, 60, + 170, -77, 60, 171, -73, 59, 172, -69, 59, 173, -65, 58, + 174, -61, 57, 175, -57, 57, 175, -53, 56, 176, -49, 55, + 177, -44, 54, 178, -40, 54, 179, -36, 53, 180, -32, 53, + 181, -28, 52, 182, -24, 51, 183, -20, 50, 184, -16, 50, + 185, -11, 49, 186, -7, 48, 187, -3, 48, 188, 0, 47, + 189, 4, 47, 190, 8, 46, 191, 12, 45, 191, 16, 44, + 192, 20, 44, 193, 25, 43, 194, 29, 42, 195, 33, 42, + 168, -95, 61, 169, -91, 60, 170, -87, 60, 171, -82, 59, + 172, -78, 58, 173, -74, 58, 174, -70, 57, 175, -66, 56, + 176, -62, 55, 177, -58, 55, 178, -54, 54, 179, -50, 54, + 180, -45, 53, 181, -41, 52, 182, -37, 51, 183, -33, 51, + 184, -29, 50, 184, -25, 49, 185, -21, 49, 186, -17, 48, + 187, -12, 47, 188, -8, 47, 189, -4, 46, 190, 0, 45, + 191, 3, 45, 192, 7, 44, 193, 11, 43, 194, 15, 43, + 195, 19, 42, 196, 24, 41, 197, 28, 41, 198, 32, 40, + 171, -96, 59, 172, -92, 59, 173, -88, 58, 174, -84, 57, + 175, -80, 56, 176, -76, 56, 176, -72, 55, 177, -67, 54, + 178, -63, 54, 179, -59, 53, 180, -55, 53, 181, -51, 52, + 182, -47, 51, 183, -43, 50, 184, -39, 50, 185, -35, 49, + 186, -30, 48, 187, -26, 48, 188, -22, 47, 189, -18, 46, + 190, -14, 46, 191, -10, 45, 191, -6, 44, 192, -2, 44, + 193, 1, 43, 194, 6, 42, 195, 10, 42, 196, 14, 41, + 197, 18, 40, 198, 22, 40, 199, 26, 39, 200, 30, 38, + 173, -98, 57, 174, -94, 57, 175, -90, 56, 176, -85, 55, + 177, -81, 55, 178, -77, 54, 179, -73, 54, 180, -69, 53, + 181, -65, 52, 182, -61, 51, 183, -57, 51, 183, -53, 50, + 184, -48, 49, 185, -44, 49, 186, -40, 48, 187, -36, 47, + 188, -32, 47, 189, -28, 46, 190, -24, 45, 191, -20, 45, + 192, -15, 44, 193, -11, 43, 194, -7, 43, 195, -3, 42, + 196, 0, 41, 197, 4, 41, 198, 8, 40, 198, 12, 39, + 199, 16, 39, 200, 21, 38, 201, 25, 37, 202, 29, 37, + 176, -99, 56, 176, -95, 55, 177, -91, 55, 178, -86, 54, + 179, -82, 53, 180, -78, 52, 181, -74, 52, 182, -70, 51, + 183, -66, 50, 184, -62, 50, 185, -58, 49, 186, -54, 49, + 187, -49, 48, 188, -45, 47, 189, -41, 46, 190, -37, 46, + 191, -33, 45, 191, -29, 44, 192, -25, 44, 193, -21, 43, + 194, -16, 42, 195, -12, 42, 196, -8, 41, 197, -4, 40, + 198, 0, 40, 199, 3, 39, 200, 7, 38, 201, 11, 38, + 202, 15, 37, 203, 20, 36, 204, 24, 36, 205, 28, 35, + 178, -100, 54, 179, -96, 53, 180, -92, 53, 181, -88, 52, + 182, -84, 51, 183, -80, 51, 183, -76, 50, 184, -71, 49, + 185, -67, 49, 186, -63, 48, 187, -59, 47, 188, -55, 47, + 189, -51, 46, 190, -47, 45, 191, -43, 45, 192, -39, 44, + 193, -34, 43, 194, -30, 43, 195, -26, 42, 196, -22, 41, + 197, -18, 41, 198, -14, 40, 199, -10, 39, 199, -6, 39, + 200, -2, 38, 201, 2, 37, 202, 6, 37, 203, 10, 36, + 204, 14, 35, 205, 18, 35, 206, 22, 34, 207, 26, 33, + 180, -101, 52, 181, -97, 52, 182, -93, 51, 183, -89, 50, + 184, -85, 50, 185, -81, 49, 186, -77, 48, 187, -72, 48, + 188, -68, 47, 189, -64, 46, 190, -60, 46, 190, -56, 45, + 192, -52, 44, 192, -48, 44, 193, -44, 43, 194, -40, 42, + 195, -35, 42, 196, -31, 41, 197, -27, 40, 198, -23, 40, + 199, -19, 39, 200, -15, 38, 201, -11, 38, 202, -7, 37, + 203, -3, 36, 204, 1, 36, 205, 5, 35, 206, 9, 34, + 206, 13, 34, 207, 17, 33, 208, 21, 32, 209, 25, 32, + 183, -103, 51, 183, -99, 50, 184, -95, 49, 185, -90, 49, + 186, -86, 48, 187, -82, 47, 188, -78, 47, 189, -74, 46, + 190, -70, 45, 191, -66, 45, 192, -62, 44, 193, -58, 43, + 194, -53, 43, 195, -49, 42, 196, -45, 41, 197, -41, 41, + 198, -37, 40, 199, -33, 39, 199, -29, 39, 200, -25, 38, + 201, -20, 37, 202, -16, 37, 203, -12, 36, 204, -8, 35, + 205, -4, 35, 206, 0, 34, 207, 3, 33, 208, 7, 33, + 209, 11, 32, 210, 16, 31, 211, 20, 31, 212, 24, 30, + 185, -104, 49, 186, -100, 48, 187, -96, 48, 188, -92, 47, + 189, -88, 46, 190, -84, 46, 191, -80, 45, 192, -75, 44, + 192, -71, 44, 193, -67, 43, 194, -63, 42, 195, -59, 42, + 196, -55, 41, 197, -51, 40, 198, -47, 40, 199, -43, 39, + 200, -38, 38, 201, -34, 38, 202, -30, 37, 203, -26, 36, + 204, -22, 36, 205, -18, 35, 206, -14, 34, 206, -10, 34, + 207, -6, 33, 208, -1, 32, 209, 2, 32, 210, 6, 31, + 211, 10, 30, 212, 14, 30, 213, 18, 29, 214, 22, 28, + 187, -105, 47, 188, -101, 47, 189, -97, 46, 190, -93, 45, + 191, -89, 45, 192, -85, 44, 193, -81, 43, 194, -76, 43, + 195, -72, 42, 196, -68, 41, 197, -64, 41, 198, -60, 40, + 199, -56, 39, 199, -52, 39, 200, -48, 38, 201, -44, 37, + 202, -39, 37, 203, -35, 36, 204, -31, 35, 205, -27, 35, + 206, -23, 34, 207, -19, 33, 208, -15, 33, 209, -11, 32, + 210, -7, 31, 211, -2, 31, 212, 1, 30, 213, 5, 29, + 213, 9, 29, 215, 13, 28, 215, 17, 27, 216, 21, 27, + 190, -107, 46, 191, -103, 45, 191, -99, 44, 192, -94, 44, + 193, -90, 43, 194, -86, 42, 195, -82, 42, 196, -78, 41, + 197, -74, 40, 198, -70, 40, 199, -66, 39, 200, -62, 38, + 201, -57, 38, 202, -53, 37, 203, -49, 36, 204, -45, 36, + 205, -41, 35, 206, -37, 34, 206, -33, 34, 207, -29, 33, + 208, -24, 32, 209, -20, 32, 210, -16, 31, 211, -12, 30, + 212, -8, 30, 213, -4, 29, 214, 0, 28, 215, 3, 28, + 216, 7, 27, 217, 12, 26, 218, 16, 26, 219, 20, 25, + 192, -108, 44, 193, -104, 43, 194, -100, 43, 195, -96, 42, + 196, -92, 41, 197, -88, 41, 198, -84, 40, 199, -79, 39, + 199, -75, 39, 200, -71, 38, 201, -67, 37, 202, -63, 37, + 203, -59, 36, 204, -55, 35, 205, -51, 35, 206, -47, 34, + 207, -42, 33, 208, -38, 33, 209, -34, 32, 210, -30, 31, + 211, -26, 31, 212, -22, 30, 213, -18, 29, 214, -14, 29, + 214, -10, 28, 215, -5, 27, 216, -1, 27, 217, 2, 26, + 218, 6, 25, 219, 10, 25, 220, 14, 24, 221, 18, 23, + 194, -109, 42, 195, -105, 42, 196, -101, 41, 197, -97, 40, + 198, -93, 40, 199, -89, 39, 200, -85, 38, 201, -80, 38, + 202, -76, 37, 203, -72, 36, 204, -68, 36, 205, -64, 35, + 206, -60, 34, 207, -56, 34, 207, -52, 33, 208, -48, 32, + 209, -43, 32, 210, -39, 31, 211, -35, 30, 212, -31, 30, + 213, -27, 29, 214, -23, 28, 215, -19, 28, 216, -15, 27, + 217, -11, 26, 218, -6, 26, 219, -2, 25, 220, 1, 24, + 221, 5, 24, 222, 9, 23, 222, 13, 22, 223, 17, 22, + 197, -111, 41, 198, -107, 40, 198, -103, 39, 200, -98, 39, + 200, -94, 38, 201, -90, 37, 202, -86, 37, 203, -82, 36, + 204, -78, 35, 205, -74, 35, 206, -70, 34, 207, -66, 33, + 208, -61, 33, 209, -57, 32, 210, -53, 31, 211, -49, 31, + 212, -45, 30, 213, -41, 29, 214, -37, 29, 214, -33, 28, + 215, -28, 27, 216, -24, 27, 217, -20, 26, 218, -16, 25, + 219, -12, 25, 220, -8, 24, 221, -4, 23, 222, 0, 23, + 223, 3, 22, 224, 8, 21, 225, 12, 21, 226, 16, 20, + 199, -112, 39, 200, -108, 38, 201, -104, 38, 202, -100, 37, + 203, -96, 36, 204, -92, 36, 205, -88, 35, 206, -83, 34, + 207, -79, 34, 207, -75, 33, 208, -71, 32, 209, -67, 32, + 210, -63, 31, 211, -59, 30, 212, -55, 30, 213, -51, 29, + 214, -46, 28, 215, -42, 28, 216, -38, 27, 217, -34, 26, + 218, -30, 26, 219, -26, 25, 220, -22, 24, 221, -18, 24, + 221, -14, 23, 222, -9, 22, 223, -5, 22, 224, -1, 21, + 225, 2, 20, 226, 6, 20, 227, 10, 19, 228, 14, 18, + 202, -114, 37, 203, -110, 36, 204, -106, 36, 205, -101, 35, + 206, -97, 34, 207, -93, 34, 208, -89, 33, 209, -85, 32, + 209, -81, 32, 210, -77, 31, 211, -73, 30, 212, -69, 30, + 213, -64, 29, 214, -60, 28, 215, -56, 28, 216, -52, 27, + 217, -48, 26, 218, -44, 26, 219, -40, 25, 220, -36, 24, + 221, -31, 24, 222, -27, 23, 223, -23, 22, 223, -19, 22, + 224, -15, 21, 225, -11, 20, 226, -7, 20, 227, -3, 19, + 228, 0, 18, 229, 5, 18, 230, 9, 17, 231, 13, 16, + 204, -115, 35, 205, -111, 35, 206, -107, 34, 207, -103, 33, + 208, -99, 33, 209, -95, 32, 210, -91, 31, 211, -86, 31, + 212, -82, 30, 213, -78, 29, 214, -74, 29, 215, -70, 28, + 216, -66, 27, 216, -62, 27, 217, -58, 26, 218, -54, 25, + 219, -49, 25, 220, -45, 24, 221, -41, 23, 222, -37, 23, + 223, -33, 22, 224, -29, 21, 225, -25, 21, 226, -21, 20, + 227, -17, 19, 228, -12, 19, 229, -8, 18, 230, -4, 17, + 231, 0, 17, 232, 3, 16, 232, 7, 15, 233, 11, 15, + 207, -116, 34, 208, -112, 33, 208, -108, 32, 209, -104, 32, + 210, -100, 31, 211, -96, 30, 212, -92, 30, 213, -87, 29, + 214, -83, 28, 215, -79, 28, 216, -75, 27, 217, -71, 26, + 218, -67, 26, 219, -63, 25, 220, -59, 24, 221, -55, 24, + 222, -50, 23, 223, -46, 22, 224, -42, 22, 224, -38, 21, + 225, -34, 20, 226, -30, 20, 227, -26, 19, 228, -22, 18, + 229, -18, 18, 230, -13, 17, 231, -9, 16, 232, -5, 16, + 233, -1, 15, 234, 2, 14, 235, 6, 14, 236, 10, 13, + 209, -118, 32, 210, -114, 31, 211, -110, 31, 212, -105, 30, + 213, -101, 29, 214, -97, 29, 215, -93, 28, 216, -89, 27, + 217, -85, 27, 217, -81, 26, 218, -77, 25, 219, -73, 25, + 220, -68, 24, 221, -64, 23, 222, -60, 23, 223, -56, 22, + 224, -52, 21, 225, -48, 21, 226, -44, 20, 227, -40, 19, + 228, -35, 19, 229, -31, 18, 230, -27, 17, 231, -23, 17, + 231, -19, 16, 232, -15, 15, 233, -11, 15, 234, -7, 14, + 235, -3, 13, 236, 1, 13, 237, 5, 12, 238, 9, 11, + 211, -119, 30, 212, -115, 30, 213, -111, 29, 214, -107, 28, + 215, -103, 28, 216, -99, 27, 217, -95, 26, 218, -90, 26, + 219, -86, 25, 220, -82, 24, 221, -78, 24, 222, -74, 23, + 223, -70, 22, 224, -66, 22, 224, -62, 21, 225, -58, 20, + 226, -53, 20, 227, -49, 19, 228, -45, 18, 229, -41, 18, + 230, -37, 17, 231, -33, 16, 232, -29, 16, 233, -25, 15, + 234, -21, 14, 235, -16, 14, 236, -12, 13, 237, -8, 12, + 238, -4, 12, 239, 0, 11, 240, 3, 10, 240, 7, 10, + 214, -120, 29, 215, -116, 28, 216, -112, 27, 217, -108, 27, + 217, -104, 26, 218, -100, 25, 219, -96, 25, 220, -91, 24, + 221, -87, 23, 222, -83, 23, 223, -79, 22, 224, -75, 21, + 225, -71, 21, 226, -67, 20, 227, -63, 19, 228, -59, 19, + 229, -54, 18, 230, -50, 17, 231, -46, 17, 231, -42, 16, + 233, -38, 15, 233, -34, 15, 234, -30, 14, 235, -26, 13, + 236, -22, 13, 237, -17, 12, 238, -13, 11, 239, -9, 11, + 240, -5, 10, 241, -1, 9, 242, 2, 9, 243, 6, 8, + 216, -122, 27, 217, -118, 26, 218, -114, 26, 219, -109, 25, + 220, -105, 24, 221, -101, 24, 222, -97, 23, 223, -93, 22, + 224, -89, 22, 224, -85, 21, 225, -81, 20, 226, -77, 20, + 227, -72, 19, 228, -68, 18, 229, -64, 18, 230, -60, 17, + 231, -56, 16, 232, -52, 16, 233, -48, 15, 234, -44, 14, + 235, -39, 14, 236, -35, 13, 237, -31, 12, 238, -27, 12, + 238, -23, 11, 240, -19, 10, 240, -15, 10, 241, -11, 9, + 242, -7, 8, 243, -2, 7, 244, 1, 7, 245, 5, 6, + 218, -123, 25, 219, -119, 25, 220, -115, 24, 221, -111, 23, + 222, -107, 23, 223, -103, 22, 224, -99, 21, 225, -94, 21, + 226, -90, 20, 227, -86, 19, 228, -82, 19, 229, -78, 18, + 230, -74, 17, 231, -70, 17, 231, -66, 16, 232, -62, 15, + 233, -57, 15, 234, -53, 14, 235, -49, 13, 236, -45, 13, + 237, -41, 12, 238, -37, 11, 239, -33, 11, 240, -29, 10, + 241, -25, 9, 242, -20, 9, 243, -16, 8, 244, -12, 7, + 245, -8, 7, 246, -4, 6, 247, 0, 5, 247, 3, 5, + 221, -124, 24, 222, -120, 23, 223, -116, 22, 224, -112, 22, + 224, -108, 21, 225, -104, 20, 226, -100, 20, 227, -95, 19, + 228, -91, 18, 229, -87, 18, 230, -83, 17, 231, -79, 16, + 232, -75, 16, 233, -71, 15, 234, -67, 14, 235, -63, 14, + 236, -58, 13, 237, -54, 12, 238, -50, 12, 239, -46, 11, + 240, -42, 10, 240, -38, 10, 241, -34, 9, 242, -30, 8, + 243, -26, 8, 244, -21, 7, 245, -17, 6, 246, -13, 6, + 247, -9, 5, 248, -5, 4, 249, -1, 4, 250, 2, 3, + 223, -126, 22, 224, -122, 21, 225, -118, 21, 226, -113, 20, + 227, -109, 19, 228, -105, 19, 229, -101, 18, 230, -97, 17, + 231, -93, 17, 232, -89, 16, 232, -85, 15, 233, -81, 15, + 234, -76, 14, 235, -72, 13, 236, -68, 13, 237, -64, 12, + 238, -60, 11, 239, -56, 11, 240, -52, 10, 241, -48, 9, + 242, -43, 8, 243, -39, 8, 244, -35, 7, 245, -31, 7, + 246, -27, 6, 247, -23, 5, 247, -19, 5, 248, -15, 4, + 249, -11, 3, 250, -6, 2, 251, -2, 2, 252, 1, 1, + 225, -127, 20, 226, -123, 20, 227, -119, 19, 228, -115, 18, + 229, -111, 18, 230, -107, 17, 231, -103, 16, 232, -98, 16, + 233, -94, 15, 234, -90, 14, 235, -86, 14, 236, -82, 13, + 237, -78, 12, 238, -74, 12, 239, -70, 11, 239, -66, 10, + 240, -61, 10, 241, -57, 9, 242, -53, 8, 243, -49, 8, + 244, -45, 7, 245, -41, 6, 246, -37, 6, 247, -33, 5, + 248, -29, 4, 249, -24, 3, 250, -20, 3, 251, -16, 2, + 252, -12, 2, 253, -8, 1, 254, -4, 0, 255, 0, 0 +}; diff --git a/components/3rd_party/omv/omv/imlib/zbar.c b/components/3rd_party/omv/omv/imlib/zbar.c new file mode 100644 index 00000000..6ba1ca1b --- /dev/null +++ b/components/3rd_party/omv/omv/imlib/zbar.c @@ -0,0 +1,8868 @@ +/* + * 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; \ + }) +#undef assert +#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 + +// *INDENT-OFF* +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "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 accommodate 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; + imlib_draw_image(&img, ptr, 0, 0, 1.f, 1.f, roi, -1, 256, NULL, NULL, 0, NULL, 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; + + 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; + 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); + // umm_init_x() is not implemented, so it does not need to free memory + // fb_free(); // umm_init_x(); + if (ptr->pixfmt != PIXFORMAT_GRAYSCALE) { + if (grayscale_image) fb_free(grayscale_image); // grayscale_image; + } +} + +#pragma GCC diagnostic pop +#endif //IMLIB_ENABLE_BARCODES *INDENT-ON* diff --git a/components/3rd_party/omv/omv/modules/examplemodule.c b/components/3rd_party/omv/omv/modules/examplemodule.c new file mode 100644 index 00000000..220a6e0d --- /dev/null +++ b/components/3rd_party/omv/omv/modules/examplemodule.c @@ -0,0 +1,34 @@ +// Include MicroPython API. +#include "py/runtime.h" + +// This is the function which will be called from Python as cexample.add_ints(a, b). +STATIC mp_obj_t example_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) { + // Extract the ints from the micropython input objects. + int a = mp_obj_get_int(a_obj); + int b = mp_obj_get_int(b_obj); + + // Calculate the addition and convert to MicroPython object. + return mp_obj_new_int(a + b); +} +// Define a Python reference to the function above. +STATIC MP_DEFINE_CONST_FUN_OBJ_2(example_add_ints_obj, example_add_ints); + +// Define all properties of the module. +// Table entries are key/value pairs of the attribute name (a string) +// and the MicroPython object reference. +// All identifiers and strings are written as MP_QSTR_xxx and will be +// optimized to word-sized integers by the build system (interned strings). +STATIC const mp_rom_map_elem_t example_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_cexample) }, + { MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&example_add_ints_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table); + +// Define module object. +const mp_obj_module_t example_user_cmodule = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *) &example_module_globals, +}; + +// Register the module to make it available in Python. +//MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule); diff --git a/components/3rd_party/omv/omv/modules/micropython.mk b/components/3rd_party/omv/omv/modules/micropython.mk new file mode 100644 index 00000000..2c7c8df1 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/micropython.mk @@ -0,0 +1,23 @@ +OMV_MOD_DIR := $(USERMOD_DIR) +OMV_PORT_MOD_DIR := $(OMV_MOD_DIR)/../ports/$(PORT)/modules + +# Add OpenMV common modules. +SRC_USERMOD += $(wildcard $(OMV_MOD_DIR)/*.c) + +# Add OpenMV port-specific modules. +SRC_USERMOD += $(wildcard $(OMV_PORT_MOD_DIR)/*.c) + +# Extra module flags. +CFLAGS_USERMOD += -I$(OMV_MOD_DIR) -I$(OMV_PORT_MOD_DIR) -Wno-float-conversion + +# Add CubeAI module if enabled. +ifeq ($(MICROPY_PY_CUBEAI), 1) +SRC_USERMOD += $(OMV_MOD_DIR)/../../stm32cubeai/py_st_nn.c +endif + +ifeq ($(MICROPY_PY_ULAB), 1) +# NOTE: overrides USERMOD_DIR +# Workaround to build and link ulab. +USERMOD_DIR := $(USERMOD_DIR)/ulab/code +include $(USERMOD_DIR)/micropython.mk +endif diff --git a/components/3rd_party/omv/omv/modules/py_assert.h b/components/3rd_party/omv/omv/modules/py_assert.h new file mode 100644 index 00000000..773bd5b9 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/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/components/3rd_party/omv/omv/modules/py_clock.c b/components/3rd_party/omv/omv/modules/py_clock.c new file mode 100644 index 00000000..b2d0d170 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_clock.c @@ -0,0 +1,87 @@ +/* + * 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); + +MP_DEFINE_CONST_OBJ_TYPE( + py_clock_type, + MP_QSTR_Clock, + MP_TYPE_FLAG_NONE, + print, py_clock_print, + make_new, py_clock_make_new, + locals_dict, &py_clock_locals_dict + ); diff --git a/components/3rd_party/omv/omv/modules/py_clock.h b/components/3rd_party/omv/omv/modules/py_clock.h new file mode 100644 index 00000000..6945dd94 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/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/components/3rd_party/omv/omv/modules/py_display.c b/components/3rd_party/omv/omv/modules/py_display.c new file mode 100644 index 00000000..db577e49 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_display.c @@ -0,0 +1,312 @@ +/* + * 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. + * + * Display Python module. + */ +#include "omv_boardconfig.h" + +#if MICROPY_PY_DISPLAY + +#include "py/obj.h" +#include "py/objarray.h" +#include "py/mphal.h" +#include "py/runtime.h" + +#include "py_helper.h" +#include "py_image.h" +#include "py_display.h" + +STATIC mp_obj_t py_display_width(mp_obj_t self_in) { + py_display_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->width); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_display_width_obj, py_display_width); + +STATIC mp_obj_t py_display_height(mp_obj_t self_in) { + py_display_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->height); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_display_height_obj, py_display_height); + +STATIC mp_obj_t py_display_triple_buffer(mp_obj_t self_in) { + py_display_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->triple_buffer); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_display_triple_buffer_obj, py_display_triple_buffer); + +STATIC mp_obj_t py_display_bgr(mp_obj_t self_in) { + py_display_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->bgr); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_display_bgr_obj, py_display_bgr); + +STATIC mp_obj_t py_display_byte_swap(mp_obj_t self_in) { + py_display_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->byte_swap); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_display_byte_swap_obj, py_display_byte_swap); + +STATIC mp_obj_t py_display_framesize(mp_obj_t self_in) { + py_display_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->framesize); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_display_framesize_obj, py_display_framesize); + +STATIC mp_obj_t py_display_refresh(mp_obj_t self_in) { + py_display_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->refresh); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_display_refresh_obj, py_display_refresh); + +STATIC mp_obj_t py_display_deinit(mp_obj_t self_in) { + py_display_obj_t *self = MP_OBJ_TO_PTR(self_in); + py_display_p_t *display_p = (py_display_p_t *) MP_OBJ_TYPE_GET_SLOT(self->base.type, protocol); + if (display_p->deinit != NULL) { + display_p->deinit(self); + } + if (self->bl_controller != mp_const_none) { + mp_obj_t dest[2]; + mp_load_method_maybe(self->bl_controller, MP_QSTR_deinit, dest); + if (dest[0] != MP_OBJ_NULL) { + mp_call_method_n_kw(0, 0, dest); + } + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_display_deinit_obj, py_display_deinit); + +STATIC mp_obj_t py_display_clear(uint n_args, const mp_obj_t *args) { + py_display_obj_t *self = MP_OBJ_TO_PTR(args[0]); + bool display_off = (n_args > 1 && mp_obj_get_int(args[1])); + py_display_p_t *display_p = (py_display_p_t *) MP_OBJ_TYPE_GET_SLOT(self->base.type, protocol); + if (display_p->clear != NULL) { + display_p->clear(self, display_off); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_display_clear_obj, 1, 2, py_display_clear); + +STATIC mp_obj_t py_display_backlight(uint n_args, const mp_obj_t *args) { + py_display_obj_t *self = MP_OBJ_TO_PTR(args[0]); + if (n_args > 1) { + uint32_t intensity = mp_obj_get_int(args[1]); + if (intensity > 100) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Intensity ranges between 0 (off) and 100 (full on)")); + } + if (self->bl_controller != mp_const_none) { + // If the display has a backlight controller set, call it first. + mp_obj_t dest[3]; + mp_load_method(self->bl_controller, MP_QSTR_backlight, dest); + dest[2] = args[1]; + mp_call_method_n_kw(1, 0, dest); + } else { + // Otherwise, if the display protocol's set_backlight is set, call it. + py_display_p_t *display_p = (py_display_p_t *) MP_OBJ_TYPE_GET_SLOT(self->base.type, protocol); + if (display_p->set_backlight != NULL) { + display_p->set_backlight(self, intensity); + } else { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Display does not support backlight control.")); + } + } + self->intensity = intensity; + } else { + return mp_obj_new_int(self->intensity); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_display_backlight_obj, 1, 2, py_display_backlight); + +STATIC mp_obj_t py_display_write(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { + ARG_image, ARG_x, ARG_y, ARG_x_scale, ARG_y_scale, ARG_roi, + ARG_channel, ARG_alpha, ARG_color_palette, ARG_alpha_palette, ARG_hint + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_image, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_x, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0 } }, + { MP_QSTR_y, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0 } }, + { MP_QSTR_x_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_y_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_roi, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_rgb_channel, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + { MP_QSTR_alpha, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 256 } }, + { MP_QSTR_color_palette, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_alpha_palette, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_hint, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0 } }, + }; + + // Parse args. + py_display_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + fb_alloc_mark(); + image_t *image = py_helper_arg_to_image(args[ARG_image].u_obj, ARG_IMAGE_ANY | ARG_IMAGE_ALLOC); + rectangle_t roi = py_helper_arg_to_roi(args[ARG_roi].u_obj, image); + + if (args[ARG_channel].u_int < -1 || args[ARG_channel].u_int > 2) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("RGB channel can be 0, 1, or 2")); + } + + if (args[ARG_alpha].u_int < 0 || args[ARG_alpha].u_int > 256) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Alpha ranges between 0 and 256")); + } + + float x_scale = 1.0f; + float y_scale = 1.0f; + py_helper_arg_to_scale(args[ARG_x_scale].u_obj, args[ARG_y_scale].u_obj, &x_scale, &y_scale); + + if (y_scale < 0 && !self->triple_buffer) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Vertical flip requires triple buffering!")); + } + + const uint16_t *color_palette = py_helper_arg_to_palette(args[ARG_color_palette].u_obj, PIXFORMAT_RGB565); + const uint8_t *alpha_palette = py_helper_arg_to_palette(args[ARG_alpha_palette].u_obj, PIXFORMAT_GRAYSCALE); + + py_display_p_t *display_p = (py_display_p_t *) MP_OBJ_TYPE_GET_SLOT(self->base.type, protocol); + display_p->write(self, image, args[ARG_x].u_int, args[ARG_y].u_int, x_scale, y_scale, &roi, + args[ARG_channel].u_int, args[ARG_alpha].u_int, color_palette, alpha_palette, args[ARG_hint].u_int); + fb_alloc_free_till_mark(); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_display_write_obj, 2, py_display_write); + +#ifdef OMV_DSI_DISPLAY_CONTROLLER +STATIC mp_obj_t py_display_dsi_write(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_cmd, ARG_args, ARG_dcs }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_cmd, MP_ARG_INT | MP_ARG_REQUIRED }, + { MP_QSTR_args, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_dcs, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = false } }, + }; + + // Parse args. + py_display_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + py_display_p_t *display_p = (py_display_p_t *) MP_OBJ_TYPE_GET_SLOT(self->base.type, protocol); + if (display_p->dsi_write == NULL) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected a DSI display controller")); + } else if (args[ARG_args].u_obj == mp_const_none) { + display_p->dsi_write(self, args[ARG_cmd].u_int, NULL, 0, args[ARG_dcs].u_bool); + } else if (mp_obj_is_int(args[ARG_args].u_obj)) { + uint8_t arg = mp_obj_get_int(args[ARG_args].u_obj); + display_p->dsi_write(self, args[ARG_cmd].u_int, &arg, 1, args[ARG_dcs].u_bool); + } else { + mp_buffer_info_t rbuf; + mp_get_buffer_raise(args[ARG_args].u_obj, &rbuf, MP_BUFFER_READ); + display_p->dsi_write(self, args[ARG_cmd].u_int, rbuf.buf, rbuf.len, args[ARG_dcs].u_bool); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_display_dsi_write_obj, 1, py_display_dsi_write); + +STATIC mp_obj_t py_display_dsi_read(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_cmd, ARG_len, ARG_args, ARG_dcs }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_cmd, MP_ARG_INT | MP_ARG_REQUIRED }, + { MP_QSTR_len, MP_ARG_INT | MP_ARG_REQUIRED }, + { MP_QSTR_args, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_dcs, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = false } }, + }; + + // Parse args. + py_display_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + py_display_p_t *display_p = (py_display_p_t *) MP_OBJ_TYPE_GET_SLOT(self->base.type, protocol); + mp_obj_array_t *wbuf = MP_OBJ_TO_PTR(mp_obj_new_bytearray_by_ref(args[ARG_len].u_int, + m_new(byte, args[ARG_len].u_int))); + if (display_p->dsi_read == NULL) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected a DSI display controller")); + } else if (args[ARG_args].u_obj == mp_const_none) { + display_p->dsi_read(self, args[ARG_cmd].u_int, NULL, 0, wbuf->items, wbuf->len, args[ARG_dcs].u_bool); + } else if (mp_obj_is_int(args[ARG_args].u_obj)) { + uint8_t arg = mp_obj_get_int(args[ARG_args].u_obj); + display_p->dsi_read(self, args[ARG_cmd].u_int, &arg, 1, wbuf->items, wbuf->len, args[ARG_dcs].u_bool); + } else { + mp_buffer_info_t rbuf; + mp_get_buffer_raise(args[ARG_args].u_obj, &rbuf, MP_BUFFER_READ); + display_p->dsi_read(self, args[ARG_cmd].u_int, rbuf.buf, rbuf.len, + wbuf->items, wbuf->len, args[ARG_dcs].u_bool); + } + return MP_OBJ_FROM_PTR(wbuf); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_display_dsi_read_obj, 1, py_display_dsi_read); +#endif + +STATIC const mp_rom_map_elem_t py_display_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_display) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&py_display_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&py_display_width_obj) }, + { MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&py_display_height_obj) }, + { MP_ROM_QSTR(MP_QSTR_triple_buffer), MP_ROM_PTR(&py_display_triple_buffer_obj) }, + { MP_ROM_QSTR(MP_QSTR_bgr), MP_ROM_PTR(&py_display_bgr_obj) }, + { MP_ROM_QSTR(MP_QSTR_byte_swap), MP_ROM_PTR(&py_display_byte_swap_obj) }, + { MP_ROM_QSTR(MP_QSTR_framesize), MP_ROM_PTR(&py_display_framesize_obj) }, + { MP_ROM_QSTR(MP_QSTR_refresh), MP_ROM_PTR(&py_display_refresh_obj) }, + { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&py_display_clear_obj) }, + { MP_ROM_QSTR(MP_QSTR_backlight), MP_ROM_PTR(&py_display_backlight_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&py_display_write_obj) }, + #ifdef OMV_DSI_DISPLAY_CONTROLLER + { MP_ROM_QSTR(MP_QSTR_dsi_write), MP_ROM_PTR(&py_display_dsi_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_dsi_read), MP_ROM_PTR(&py_display_dsi_read_obj) }, + #endif +}; +MP_DEFINE_CONST_DICT(py_display_locals_dict, py_display_locals_dict_table); + +STATIC const mp_rom_map_elem_t globals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_display) }, + { MP_ROM_QSTR(MP_QSTR_QVGA), MP_ROM_INT(DISPLAY_RESOLUTION_QVGA) }, + { MP_ROM_QSTR(MP_QSTR_TQVGA), MP_ROM_INT(DISPLAY_RESOLUTION_TQVGA) }, + { MP_ROM_QSTR(MP_QSTR_FHVGA), MP_ROM_INT(DISPLAY_RESOLUTION_FHVGA) }, + { MP_ROM_QSTR(MP_QSTR_FHVGA2), MP_ROM_INT(DISPLAY_RESOLUTION_FHVGA2) }, + { MP_ROM_QSTR(MP_QSTR_VGA), MP_ROM_INT(DISPLAY_RESOLUTION_VGA) }, + { MP_ROM_QSTR(MP_QSTR_THVGA), MP_ROM_INT(DISPLAY_RESOLUTION_THVGA) }, + { MP_ROM_QSTR(MP_QSTR_FWVGA), MP_ROM_INT(DISPLAY_RESOLUTION_FWVGA) }, + { MP_ROM_QSTR(MP_QSTR_FWVGA2), MP_ROM_INT(DISPLAY_RESOLUTION_FWVGA2) }, + { MP_ROM_QSTR(MP_QSTR_TFWVGA), MP_ROM_INT(DISPLAY_RESOLUTION_TFWVGA) }, + { MP_ROM_QSTR(MP_QSTR_TFWVGA2), MP_ROM_INT(DISPLAY_RESOLUTION_TFWVGA2) }, + { MP_ROM_QSTR(MP_QSTR_SVGA), MP_ROM_INT(DISPLAY_RESOLUTION_SVGA) }, + { MP_ROM_QSTR(MP_QSTR_WSVGA), MP_ROM_INT(DISPLAY_RESOLUTION_WSVGA) }, + { MP_ROM_QSTR(MP_QSTR_XGA), MP_ROM_INT(DISPLAY_RESOLUTION_XGA) }, + { MP_ROM_QSTR(MP_QSTR_SXGA), MP_ROM_INT(DISPLAY_RESOLUTION_SXGA) }, + { MP_ROM_QSTR(MP_QSTR_SXGA2), MP_ROM_INT(DISPLAY_RESOLUTION_SXGA2) }, + { MP_ROM_QSTR(MP_QSTR_UXGA), MP_ROM_INT(DISPLAY_RESOLUTION_UXGA) }, + { MP_ROM_QSTR(MP_QSTR_HD), MP_ROM_INT(DISPLAY_RESOLUTION_HD) }, + { MP_ROM_QSTR(MP_QSTR_FHD), MP_ROM_INT(DISPLAY_RESOLUTION_FHD) }, + + #ifdef OMV_SPI_DISPLAY_CONTROLLER + { MP_ROM_QSTR(MP_QSTR_SPIDisplay), MP_ROM_PTR(&py_spi_display_type) }, + #endif + #ifdef OMV_RGB_DISPLAY_CONTROLLER + { MP_ROM_QSTR(MP_QSTR_RGBDisplay), MP_ROM_PTR(&py_rgb_display_type) }, + #endif + #ifdef OMV_DSI_DISPLAY_CONTROLLER + { MP_ROM_QSTR(MP_QSTR_DSIDisplay), MP_ROM_PTR(&py_dsi_display_type) }, + #endif + #if OMV_DISPLAY_CEC_ENABLE || OMV_DISPLAY_DDC_ENABLE + { MP_ROM_QSTR(MP_QSTR_DisplayData), MP_ROM_PTR(&py_display_data_type) }, + #endif +}; +STATIC MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); + +const mp_obj_module_t display_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_t) &globals_dict, +}; + +#ifdef MP_REGISTER_EXTENSIBLE_MODULE +MP_REGISTER_EXTENSIBLE_MODULE(MP_QSTR_display, display_module); +#else +MP_REGISTER_MODULE(MP_QSTR_udisplay, display_module); +#endif +#endif // MICROPY_PY_DISPLAY diff --git a/components/3rd_party/omv/omv/modules/py_display.h b/components/3rd_party/omv/omv/modules/py_display.h new file mode 100644 index 00000000..3375a69a --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_display.h @@ -0,0 +1,87 @@ +/* + * 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. + * + * Display Python module. + */ +#ifndef __PY_DISPLAY_H__ +#define __PY_DISPLAY_H__ + +#include "omv_gpio.h" +#include "omv_spi.h" +#include "py_image.h" + +#define FRAMEBUFFER_COUNT 3 + +typedef enum { + DISPLAY_RESOLUTION_QVGA, + DISPLAY_RESOLUTION_TQVGA, + DISPLAY_RESOLUTION_FHVGA, + DISPLAY_RESOLUTION_FHVGA2, + DISPLAY_RESOLUTION_VGA, + DISPLAY_RESOLUTION_THVGA, + DISPLAY_RESOLUTION_FWVGA, + DISPLAY_RESOLUTION_FWVGA2, + DISPLAY_RESOLUTION_TFWVGA, + DISPLAY_RESOLUTION_TFWVGA2, + DISPLAY_RESOLUTION_SVGA, + DISPLAY_RESOLUTION_WSVGA, + DISPLAY_RESOLUTION_XGA, + DISPLAY_RESOLUTION_SXGA, + DISPLAY_RESOLUTION_SXGA2, + DISPLAY_RESOLUTION_UXGA, + DISPLAY_RESOLUTION_HD, + DISPLAY_RESOLUTION_FHD, + DISPLAY_RESOLUTION_MAX +} display_resolution_t; + +typedef struct _py_display_obj_t { + mp_obj_base_t base; + uint32_t vcid; + uint32_t width; + uint32_t height; + uint32_t framesize; + uint32_t refresh; + uint32_t intensity; + bool bgr; + bool byte_swap; + bool display_on; + bool portrait; + mp_obj_t controller; + mp_obj_t bl_controller; + #if defined(OMV_SPI_DISPLAY_CONTROLLER) + omv_spi_t spi_bus; + bool spi_tx_running; + uint32_t spi_baudrate; + #endif + bool triple_buffer; + uint32_t framebuffer_tail; + volatile uint32_t framebuffer_head; + uint16_t *framebuffers[FRAMEBUFFER_COUNT]; +} py_display_obj_t; + +// Display protocol +typedef struct _py_display_p_t { + void (*deinit) (py_display_obj_t *self); + void (*clear) (py_display_obj_t *self, bool display_off); + void (*write) (py_display_obj_t *self, 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); + void (*set_backlight) (py_display_obj_t *self, uint32_t intensity); + #ifdef OMV_DSI_DISPLAY_CONTROLLER + // To be implemented by MIPI DSI controllers. + int (*dsi_write) (py_display_obj_t *self, uint8_t cmd, uint8_t *args, size_t n_args, bool dcs); + int (*dsi_read) (py_display_obj_t *self, uint8_t cmd, uint8_t *args, size_t n_args, uint8_t *buf, size_t len, bool dcs); + #endif +} py_display_p_t; + +extern const mp_obj_type_t py_spi_display_type; +extern const mp_obj_type_t py_rgb_display_type; +extern const mp_obj_type_t py_dsi_display_type; +extern const mp_obj_type_t py_display_data_type; +extern const mp_obj_dict_t py_display_locals_dict; +#endif // __PY_DISPLAY_H__ diff --git a/components/3rd_party/omv/omv/modules/py_display_data.c b/components/3rd_party/omv/omv/modules/py_display_data.c new file mode 100644 index 00000000..b4c783a3 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_display_data.c @@ -0,0 +1,263 @@ +/* + * 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. + * + * Display data channel module (such as CEC/DDC). + */ +#include "omv_boardconfig.h" + +#if OMV_DISPLAY_CEC_ENABLE || OMV_DISPLAY_DDC_ENABLE +#include "py/obj.h" +#include "py/objarray.h" +#include "py/runtime.h" +#include "mphal.h" +#include "extmod/machine_i2c.h" + +#include "omv_gpio.h" +#include "fb_alloc.h" +#include "py_display.h" +#include "cec.h" + +typedef struct _py_display_data_obj_t { + mp_obj_base_t base; + #if OMV_DISPLAY_CEC_ENABLE + bool cec_enabled; + uint8_t dst_addr; + mp_obj_t cec_frame; + mp_obj_t cec_callback; + #endif + #if OMV_DISPLAY_DDC_ENABLE + bool ddc_enabled; + uint8_t ddc_addr; + mp_obj_t ddc_bus; + #endif +} py_display_data_obj_t; + +#if OMV_DISPLAY_CEC_ENABLE +static void cec_extint_callback(mp_obj_t self_in) { + py_display_data_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->cec_callback != mp_const_none) { + uint8_t src_addr = 0; + mp_obj_array_t *frame = MP_OBJ_TO_PTR(self->cec_frame); + if (cec_receive_frame(frame->items, &frame->len, self->dst_addr, &src_addr) != -1) { + mp_call_function_2(self->cec_callback, MP_OBJ_NEW_SMALL_INT(src_addr), self->cec_frame); + } + } +} + +STATIC mp_obj_t py_cec_send_frame(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_dst_addr, ARG_src_addr, ARG_data }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_dst_addr, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_src_addr, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_data, MP_ARG_REQUIRED | MP_ARG_OBJ }, + }; + + // Parse args. + py_display_data_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[ARG_data].u_obj, &bufinfo, MP_BUFFER_READ); + + // Disable interrupt while transmitting a frame. + if (self->cec_callback) { + omv_gpio_irq_enable(OMV_CEC_PIN, false); + } + if (!cec_send_frame(args[ARG_dst_addr].u_int, args[ARG_src_addr].u_int, bufinfo.len, bufinfo.buf)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to send frame.")); + } + if (self->cec_callback) { + omv_gpio_irq_enable(OMV_CEC_PIN, true); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_cec_send_frame_obj, 4, py_cec_send_frame); + +STATIC mp_obj_t py_cec_receive_frame(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_dst_addr, ARG_timeout }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_dst_addr, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1000 } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_uint_t start = mp_hal_ticks_ms(); + while (omv_gpio_read(OMV_CEC_PIN)) { + if ((mp_hal_ticks_ms() - start) > args[ARG_timeout].u_int) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Receive timeout!")); + } + } + uint8_t src_addr = 0; + mp_obj_array_t *frame = MP_OBJ_TO_PTR(mp_obj_new_bytearray_by_ref(16, m_new(byte, 16))); + if (cec_receive_frame(frame->items, &frame->len, args[ARG_dst_addr].u_int, &src_addr) != 0) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to receive frame.")); + } + return mp_obj_new_tuple(2, (mp_obj_t []) { MP_OBJ_NEW_SMALL_INT(src_addr), frame }); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_cec_receive_frame_obj, 2, py_cec_receive_frame); + +STATIC mp_obj_t py_cec_frame_callback(mp_obj_t self_in, mp_obj_t cb, mp_obj_t dst_addr) { + py_display_data_obj_t *self = MP_OBJ_TO_PTR(self_in); + + self->cec_callback = cb; + if (cb == mp_const_none) { + omv_gpio_irq_enable(OMV_CEC_PIN, false); + self->cec_frame = mp_const_none; + } else { + self->dst_addr = mp_obj_get_int(dst_addr); + self->cec_frame = mp_obj_new_bytearray_by_ref(0, m_new(byte, 16)); + omv_gpio_config(OMV_CEC_PIN, OMV_GPIO_MODE_IT_FALL, OMV_GPIO_PULL_UP, OMV_GPIO_SPEED_LOW, -1); + omv_gpio_irq_register(OMV_CEC_PIN, cec_extint_callback, self_in); + omv_gpio_irq_enable(OMV_CEC_PIN, true); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(py_cec_frame_callback_obj, py_cec_frame_callback); +#endif // OMV_DISPLAY_CEC_ENABLE + +#if OMV_DISPLAY_DDC_ENABLE +static bool ddc_checksum(uint8_t *data, int long_count) { + uint32_t *data32 = (uint32_t *) data; + uint32_t sum = 0; + + for (int i = 0; i < long_count; i++) { + sum = __USADA8(data32[i], 0, sum); + } + + return !(sum & 0xFF); +} + +STATIC mp_obj_t py_ddc_display_id(mp_obj_t self_in) { + py_display_data_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (mp_machine_soft_i2c_transfer(self->ddc_bus, self->ddc_addr, 1, &((mp_machine_i2c_buf_t) { + .len = 1, .buf = (uint8_t []) {0x00} // addr + }), MP_MACHINE_I2C_FLAG_STOP) == 1) { + fb_alloc_mark(); + uint8_t *data = fb_alloc(128, FB_ALLOC_NO_HINT); + + if (mp_machine_soft_i2c_transfer(self->ddc_bus, self->ddc_addr, 1, &((mp_machine_i2c_buf_t) { + .len = 128, .buf = data + }), MP_MACHINE_I2C_FLAG_READ | MP_MACHINE_I2C_FLAG_STOP) == 0) { + uint32_t *data32 = (uint32_t *) data; + + if ((data32[0] == 0xFFFFFF00) && (data32[1] == 0x00FFFFFF) && ddc_checksum(data, 32) + && (mp_machine_soft_i2c_transfer(self->ddc_bus, self->ddc_addr, 1, &((mp_machine_i2c_buf_t) { + .len = 1, .buf = (uint8_t []) {0x80} // addr + }), MP_MACHINE_I2C_FLAG_STOP) == 1)) { + int extensions = data[126]; + int extensions_byte_size = extensions * 128; + int total_data_byte_size = extensions_byte_size + 128; + uint8_t *data2 = fb_alloc(total_data_byte_size, FB_ALLOC_NO_HINT), *data2_ext = data2 + 128; + memcpy(data2, data, 128); + + if ((mp_machine_soft_i2c_transfer(self->ddc_bus, self->ddc_addr, 1, &((mp_machine_i2c_buf_t) { + .len = extensions_byte_size, .buf = data2_ext + }), MP_MACHINE_I2C_FLAG_READ | MP_MACHINE_I2C_FLAG_STOP) == 0) + && ddc_checksum(data2_ext, extensions_byte_size / 4)) { + mp_obj_t result = mp_obj_new_bytes(data2, total_data_byte_size); + fb_alloc_free_till_mark(); + return result; + } + } + } + } + + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to get display id data!")); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_ddc_display_id_obj, py_ddc_display_id); +#endif // OMV_DISPLAY_DDC_ENABLE + +mp_obj_t py_display_data_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_cec, ARG_ddc, ARG_ddc_addr }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_cec, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_ddc, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_ddc_addr, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0x50 } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + py_display_data_obj_t *self = m_new_obj_with_finaliser(py_display_data_obj_t); + self->base.type = &py_display_data_type; + + #if OMV_DISPLAY_CEC_ENABLE + self->cec_enabled = args[ARG_cec].u_bool; + self->dst_addr = 0; + self->cec_frame = mp_const_none; + self->cec_callback = mp_const_none; + if (self->cec_enabled) { + cec_init(); + } + #endif // OMV_DISPLAY_CEC_ENABLE + + #if OMV_DISPLAY_DDC_ENABLE + self->ddc_enabled = args[ARG_ddc].u_bool; + self->ddc_addr = args[ARG_ddc_addr].u_int; + self->ddc_bus = mp_const_none; + if (self->ddc_enabled) { + self->ddc_bus = MP_OBJ_TYPE_GET_SLOT( + &mp_machine_soft_i2c_type, make_new) (&mp_machine_soft_i2c_type, 2, 1, (const mp_obj_t []) { + (mp_obj_t) OMV_DDC_SCL_PIN, (mp_obj_t) OMV_DDC_SDA_PIN, + MP_OBJ_NEW_QSTR(MP_QSTR_freq), + MP_OBJ_NEW_SMALL_INT(100000) + }); + } + #endif // OMV_DISPLAY_DDC_ENABLE + + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t py_display_data_deinit(mp_obj_t self_in) { + py_display_data_obj_t *self = MP_OBJ_TO_PTR(self_in); + + #if OMV_DISPLAY_DDC_ENABLE + if (self->ddc_enabled) { + // Deinit bus. + } + #endif // OMV_DISPLAY_DDC_ENABLE + + #if OMV_DISPLAY_CEC_ENABLE + if (self->cec_enabled) { + cec_deinit(); + } + #endif // OMV_DISPLAY_CEC_ENABLE + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_display_data_deinit_obj, py_display_data_deinit); + +STATIC const mp_rom_map_elem_t py_display_data_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_display_data) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&py_display_data_deinit_obj) }, + #if OMV_DISPLAY_DDC_ENABLE + { MP_ROM_QSTR(MP_QSTR_display_id), MP_ROM_PTR(&py_ddc_display_id_obj) }, + #endif + #if OMV_DISPLAY_CEC_ENABLE + { MP_ROM_QSTR(MP_QSTR_send_frame), MP_ROM_PTR(&py_cec_send_frame_obj) }, + { MP_ROM_QSTR(MP_QSTR_receive_frame), MP_ROM_PTR(&py_cec_receive_frame_obj) }, + { MP_ROM_QSTR(MP_QSTR_frame_callback), MP_ROM_PTR(&py_cec_frame_callback_obj) }, + #endif +}; +MP_DEFINE_CONST_DICT(py_display_data_locals_dict, py_display_data_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + py_display_data_type, + MP_QSTR_DisplayData, + MP_TYPE_FLAG_NONE, + make_new, py_display_data_make_new, + locals_dict, &py_display_data_locals_dict + ); + +#endif // OMV_DISPLAY_DATA diff --git a/components/3rd_party/omv/omv/modules/py_fir.c b/components/3rd_party/omv/omv/modules/py_fir.c new file mode 100644 index 00000000..db972cf0 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_fir.c @@ -0,0 +1,1165 @@ +/* + * 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" + +#if OMV_ENABLE_FIR_MLX90621 || \ + OMV_ENABLE_FIR_MLX90640 || \ + OMV_ENABLE_FIR_MLX90641 || \ + OMV_ENABLE_FIR_AMG8833 || \ + OMV_ENABLE_FIR_LEPTON +#include "omv_i2c.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 omv_i2c_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 + +typedef enum fir_sensor_type { + 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_type_t; + +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; +static fir_sensor_type_t fir_sensor = FIR_NONE; + +// 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); + + if (data) fb_free(data); +} +#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); + + // Wait for a new data to be available before calling GetFrameData. + MLX90640_SynchFrame(MLX90640_ADDR); + + // 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); + + if (data) fb_free(data); +} +#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); + + // Wait for a new data to be available before calling GetFrameData. + MLX90641_SynchFrame(MLX90641_ADDR); + + 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); + + if (data) fb_free(data); +} +#endif + +#if (OMV_ENABLE_FIR_AMG8833 == 1) +static void fir_AMG8833_get_frame(float *Ta, float *To) { + int16_t temp; + int error = 0; + error |= omv_i2c_write_bytes(&fir_bus, + AMG8833_ADDR, + (uint8_t [1]) {AMG8833_THERMISTOR_REGISTER}, + 1, OMV_I2C_XFER_NO_STOP); + error |= omv_i2c_read_bytes(&fir_bus, AMG8833_ADDR, (uint8_t *) &temp, sizeof(temp), OMV_I2C_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 |= omv_i2c_write_bytes(&fir_bus, + AMG8833_ADDR, + (uint8_t [1]) {AMG8833_TEMPERATURE_REGISTER}, + 1, OMV_I2C_XFER_NO_STOP); + error |= omv_i2c_read_bytes(&fir_bus, + AMG8833_ADDR, + (uint8_t *) data, + AMG8833_WIDTH * AMG8833_HEIGHT * 2, + OMV_I2C_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; + } + + if (data) fb_free(data); +} +#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; + + if (!src_transpose) { + for (int y = 0; y < h; y++) { + int y_dst = flip ? (h - y - 1) : 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 - x - 1) : 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 - x - 1) : 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 - y - 1) : 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) { + omv_i2c_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 *pos_args, mp_map_t *kw_args) { + enum { ARG_type, ARG_refresh, ARG_resolution }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_type, MP_ARG_INT, {.u_int = -1 } }, + { MP_QSTR_refresh, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + { MP_QSTR_resolution, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + py_fir_deinit(); + bool first_init = true; + int type = args[ARG_type].u_int; + if (type == -1) { + FIR_SCAN_RETRY: + omv_i2c_init(&fir_bus, FIR_I2C_ID, OMV_I2C_SPEED_STANDARD); + // Scan and detect any supported sensor. + uint8_t dev_list[10]; + int dev_size = omv_i2c_scan(&fir_bus, dev_list, sizeof(dev_list)); + for (int i = 0; i < dev_size && type == -1; i++) { + switch (dev_list[i]) { + #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: + continue; + } + } + + if (type == -1 && first_init) { + first_init = false; + // Recover bus and scan one more time. + omv_i2c_pulse_scl(&fir_bus); + goto FIR_SCAN_RETRY; + } + + omv_i2c_deinit(&fir_bus); + } + + // Initialize the detected sensor. + first_init = true; + switch (type) { + #if (OMV_ENABLE_FIR_MLX90621 == 1) + case FIR_MLX90621: { + // Set refresh rate and ADC resolution + uint32_t ir_fresh_rate = args[ARG_refresh].u_int != -1 ? args[ARG_refresh].u_int : 64; + uint32_t adc_resolution = args[ARG_resolution].u_int != -1 ? args[ARG_resolution].u_int : 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: + omv_i2c_init(&fir_bus, FIR_I2C_ID, OMV_I2C_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; + omv_i2c_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 + omv_i2c_deinit(&fir_bus); + omv_i2c_init(&fir_bus, FIR_I2C_ID, OMV_I2C_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: { + // Set refresh rate and ADC resolution + uint32_t ir_fresh_rate = args[ARG_refresh].u_int != -1 ? args[ARG_refresh].u_int : 32; + uint32_t adc_resolution = args[ARG_resolution].u_int != -1 ? args[ARG_resolution].u_int : 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: + omv_i2c_init(&fir_bus, FIR_I2C_ID, OMV_I2C_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; + omv_i2c_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 + omv_i2c_deinit(&fir_bus); + omv_i2c_init(&fir_bus, FIR_I2C_ID, OMV_I2C_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: { + // Set refresh rate and ADC resolution + uint32_t ir_fresh_rate = args[ARG_refresh].u_int != -1 ? args[ARG_refresh].u_int : 32; + uint32_t adc_resolution = args[ARG_resolution].u_int != -1 ? args[ARG_resolution].u_int : 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: + omv_i2c_init(&fir_bus, FIR_I2C_ID, OMV_I2C_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; + omv_i2c_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 + omv_i2c_deinit(&fir_bus); + omv_i2c_init(&fir_bus, FIR_I2C_ID, OMV_I2C_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: + omv_i2c_init(&fir_bus, FIR_I2C_ID, OMV_I2C_SPEED_STANDARD); + + int error = omv_i2c_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; + omv_i2c_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: + omv_i2c_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; + omv_i2c_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("Failed to detect a supported FIR sensor.")); + } + } + + 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_obj_new_int(fir_sensor); + } + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("FIR sensor is not initialized")); +} +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_obj_new_int(fir_width); + } + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("FIR sensor is not initialized")); +} +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_obj_new_int(fir_height); + } + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("FIR sensor is not initialized")); +} +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) { + #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) + case FIR_MLX90640: + return mp_obj_new_int(mlx_90640_1_refresh_rates[fir_ir_fresh_rate]); + #endif + #if (OMV_ENABLE_FIR_MLX90641 == 1) + case FIR_MLX90641: + 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: + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("FIR sensor is not initialized")); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_fir_refresh_obj, py_fir_refresh); + +static mp_obj_t py_fir_resolution() { + switch (fir_sensor) { + #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) + case FIR_MLX90640: + return mp_obj_new_int(fir_adc_resolution + 16); + #endif + #if (OMV_ENABLE_FIR_MLX90641 == 1) + case FIR_MLX90641: + 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: + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("FIR sensor is not initialized")); + } +} +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(); + } else { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Operation not supported by this FIR sensor")); + } +} +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); + } else { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Operation not supported by this FIR sensor")); + } + 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); + } else { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Operation not supported by this FIR sensor")); + } + 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(); + } else { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Operation not supported by this FIR sensor")); + } +} +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 *pos_args, mp_map_t *kw_args) { + enum { ARG_timeout }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_timeout, MP_ARG_INT, {.u_int = -1 } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if (fir_sensor == FIR_LEPTON) { + fir_lepton_trigger_ffc(args[ARG_timeout].u_int); + } else { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Operation not supported by this FIR sensor")); + } + 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) { + #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 |= omv_i2c_write_bytes(&fir_bus, + AMG8833_ADDR, + (uint8_t [1]) {AMG8833_THERMISTOR_REGISTER}, + 1, + OMV_I2C_XFER_NO_STOP); + error |= omv_i2c_read_bytes(&fir_bus, AMG8833_ADDR, (uint8_t *) &temp, sizeof(temp), OMV_I2C_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 + default: { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("FIR sensor is not initialized")); + } + } + + 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 *pos_args, mp_map_t *kw_args) { + enum { ARG_hmirror, ARG_vflip, ARG_transpose, ARG_timeout }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_hmirror, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = false } }, + { MP_QSTR_vflip, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = false } }, + { MP_QSTR_transpose, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = false } }, + { MP_QSTR_timeout, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + fir_transposed = args[ARG_transpose].u_bool; + + switch (fir_sensor) { + #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, !args[ARG_hmirror].u_bool, + args[ARG_vflip].u_bool, args[ARG_transpose].u_bool, 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, !args[ARG_hmirror].u_bool, + args[ARG_vflip].u_bool, args[ARG_transpose].u_bool, 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, !args[ARG_hmirror].u_bool, + args[ARG_vflip].u_bool, args[ARG_transpose].u_bool, 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, !args[ARG_hmirror].u_bool, + args[ARG_vflip].u_bool, args[ARG_transpose].u_bool, 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, args[ARG_hmirror].u_bool, + args[ARG_vflip].u_bool, args[ARG_transpose].u_bool, args[ARG_timeout].u_int); + } + #endif + default: { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("FIR sensor is not initialized")); + } + } + + 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 *pos_args, mp_map_t *kw_args) { + enum { + ARG_x, ARG_y, ARG_x_scale, ARG_y_scale, ARG_roi, ARG_channel, ARG_alpha, + ARG_color_palette, ARG_alpha_palette, ARG_hint, ARG_scale + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_x, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0 } }, + { MP_QSTR_y, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0 } }, + { MP_QSTR_x_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_y_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_roi, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_rgb_channel, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + { MP_QSTR_alpha, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 256 } }, + { MP_QSTR_color_palette, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_INT(COLOR_PALETTE_RAINBOW)} }, + { MP_QSTR_alpha_palette, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_hint, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0 } }, + { MP_QSTR_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 2, pos_args + 2, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Sanity checks + if (fir_sensor == FIR_NONE) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("FIR sensor is not initialized")); + } + + if (args[ARG_channel].u_int < -1 || args[ARG_channel].u_int > 2) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("RGB channel can be 0, 1, or 2")); + } + + if (args[ARG_alpha].u_int < 0 || args[ARG_alpha].u_int > 256) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Alpha ranges between 0 and 256")); + } + + image_t src_img = { + .w = fir_transposed ? fir_height : fir_width, + .h = fir_transposed ? fir_width : fir_height, + .pixfmt = PIXFORMAT_GRAYSCALE, + //.data is allocated later. + }; + + image_t *dst_img = py_helper_arg_to_image(pos_args[0], ARG_IMAGE_MUTABLE); + + mp_obj_t *ir_array; + mp_obj_get_array_fixed_n(pos_args[1], src_img.w * src_img.h, &ir_array); + + rectangle_t roi = py_helper_arg_to_roi(args[ARG_roi].u_obj, &src_img); + + float x_scale = 1.0f; + float y_scale = 1.0f; + py_helper_arg_to_scale(args[ARG_x_scale].u_obj, args[ARG_y_scale].u_obj, &x_scale, &y_scale); + + float min = FLT_MAX; + float max = -FLT_MAX; + py_helper_arg_to_minmax(args[ARG_scale].u_obj, &min, &max, ir_array, src_img.w * src_img.h); + + const uint16_t *color_palette = py_helper_arg_to_palette(args[ARG_color_palette].u_obj, PIXFORMAT_RGB565); + const uint8_t *alpha_palette = py_helper_arg_to_palette(args[ARG_alpha_palette].u_obj, PIXFORMAT_GRAYSCALE); + + 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, ir_array, min, max); + + imlib_draw_image(dst_img, &src_img, args[ARG_x].u_int, args[ARG_y].u_int, x_scale, y_scale, &roi, + args[ARG_channel].u_int, args[ARG_alpha].u_int, color_palette, alpha_palette, + args[ARG_hint].u_int, NULL, 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 *pos_args, mp_map_t *kw_args) { + enum { + ARG_hmirror, ARG_vflip, ARG_transpose, ARG_x_scale, ARG_y_scale, ARG_roi, ARG_channel, + ARG_alpha, ARG_color_palette, ARG_alpha_palette, ARG_hint, ARG_scale, ARG_pixformat, + ARG_copy_to_fb, ARG_timeout + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_hmirror, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = false } }, + { MP_QSTR_vflip, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = false } }, + { MP_QSTR_transpose, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = false } }, + { MP_QSTR_x_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_y_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_roi, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_rgb_channel, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + { MP_QSTR_alpha, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 256 } }, + { MP_QSTR_color_palette, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_INT(COLOR_PALETTE_RAINBOW)} }, + { MP_QSTR_alpha_palette, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_hint, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0 } }, + { MP_QSTR_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_pixformat, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = PIXFORMAT_RGB565 } }, + { MP_QSTR_copy_to_fb, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = false } }, + { MP_QSTR_timeout, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Sanity checks + if (args[ARG_channel].u_int < -1 || args[ARG_channel].u_int > 2) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("RGB channel can be 0, 1, or 2")); + } + + if (args[ARG_alpha].u_int < 0 || args[ARG_alpha].u_int > 256) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Alpha ranges between 0 and 256")); + } + + if ((args[ARG_pixformat].u_int != PIXFORMAT_GRAYSCALE) && (args[ARG_pixformat].u_int != PIXFORMAT_RGB565)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid pixformat")); + } + + image_t src_img = { + .w = args[ARG_transpose].u_bool ? fir_height : fir_width, + .h = args[ARG_transpose].u_bool ? fir_width : fir_height, + .pixfmt = PIXFORMAT_GRAYSCALE, + //.data is allocated later. + }; + + rectangle_t roi = py_helper_arg_to_roi(args[ARG_roi].u_obj, &src_img); + + float x_scale = 1.0f; + float y_scale = 1.0f; + py_helper_arg_to_scale(args[ARG_x_scale].u_obj, args[ARG_y_scale].u_obj, &x_scale, &y_scale); + + image_t dst_img = { + .w = fast_floorf(roi.w * x_scale), + .h = fast_floorf(roi.h * y_scale), + .pixfmt = args[ARG_pixformat].u_int, + }; + if (args[ARG_copy_to_fb].u_bool) { + py_helper_set_to_framebuffer(&dst_img); + } else { + dst_img.data = xalloc(image_size(&dst_img)); + } + + float min = FLT_MAX; + float max = -FLT_MAX; + py_helper_arg_to_minmax(args[ARG_scale].u_obj, &min, &max, NULL, 0); + + const uint16_t *color_palette = py_helper_arg_to_palette(args[ARG_color_palette].u_obj, PIXFORMAT_RGB565); + const uint8_t *alpha_palette = py_helper_arg_to_palette(args[ARG_alpha_palette].u_obj, PIXFORMAT_GRAYSCALE); + + fb_alloc_mark(); + // Allocate source image data. + 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 (args[ARG_scale].u_obj == mp_const_none) { + 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, !args[ARG_hmirror].u_bool, + args[ARG_vflip].u_bool, args[ARG_transpose].u_bool, true); + 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 (args[ARG_scale].u_obj == mp_const_none) { + 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, !args[ARG_hmirror].u_bool, + args[ARG_vflip].u_bool, args[ARG_transpose].u_bool, false); + 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 (args[ARG_scale].u_obj == mp_const_none) { + 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, !args[ARG_hmirror].u_bool, + args[ARG_vflip].u_bool, args[ARG_transpose].u_bool, false); + 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 (args[ARG_scale].u_obj == mp_const_none) { + 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, !args[ARG_hmirror].u_bool, + args[ARG_vflip].u_bool, args[ARG_transpose].u_bool, true); + break; + } + #endif + #if (OMV_ENABLE_FIR_LEPTON == 1) + case FIR_LEPTON: { + bool auto_range = args[ARG_scale].u_obj == mp_const_none; + fir_lepton_fill_image(&src_img, fir_width, fir_height, auto_range, min, max, + args[ARG_hmirror].u_bool, args[ARG_vflip].u_bool, + args[ARG_transpose].u_bool, args[ARG_timeout].u_int); + break; + } + #endif + default: { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("FIR sensor is not initialized")); + } + } + + imlib_draw_image(&dst_img, &src_img, 0, 0, x_scale, y_scale, &roi, + args[ARG_channel].u_int, args[ARG_alpha].u_int, color_palette, alpha_palette, + (args[ARG_hint].u_int & (~IMAGE_HINT_CENTER)) | IMAGE_HINT_BLACK_BACKGROUND, NULL, NULL, NULL); + + fb_alloc_free_till_mark(); + + if (args[ARG_copy_to_fb].u_bool) { + framebuffer_update_jpeg_buffer(); + } + 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) }, + #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_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); +#endif diff --git a/components/3rd_party/omv/omv/modules/py_fir.h b/components/3rd_party/omv/omv/modules/py_fir.h new file mode 100644 index 00000000..3f5a12b5 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/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/components/3rd_party/omv/omv/modules/py_fir_lepton.c b/components/3rd_party/omv/omv/modules/py_fir_lepton.c new file mode 100644 index 00000000..4de21246 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_fir_lepton.c @@ -0,0 +1,617 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2020 Ibrahim Abdelkader + * Copyright (c) 2013-2020 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * FIR Python module. + */ +#include "omv_boardconfig.h" +#if (OMV_ENABLE_FIR_LEPTON == 1) +#include "py/nlr.h" +#include "py/runtime.h" +#include "py/obj.h" +#include "py/mphal.h" +#include "softtimer.h" + +#include "crc16.h" +#include "LEPTON_SDK.h" +#include "LEPTON_AGC.h" +#include "LEPTON_SYS.h" +#include "LEPTON_VID.h" +#include "LEPTON_OEM.h" +#include "LEPTON_RAD.h" +#include "LEPTON_I2C_Reg.h" + +#include "py_helper.h" +#include "omv_common.h" +#include "omv_gpio.h" +#include "omv_spi.h" + +#define FRAMEBUFFER_COUNT 3 +static volatile int framebuffer_tail = 0; +static int framebuffer_head = 0; +static uint16_t *framebuffers[FRAMEBUFFER_COUNT] = {}; + +static int fir_lepton_rad_en = false; +static bool fir_lepton_3 = false; + +#if defined(OMV_FIR_LEPTON_MCLK_TIM) +static TIM_HandleTypeDef fir_lepton_mclk_tim_handle = {}; +#endif + +static LEP_CAMERA_PORT_DESC_T fir_lepton_handle = {}; +static omv_spi_t spi_bus = {}; + +#define VOSPI_HEADER_WORDS (2) // 16-bits +#define VOSPI_PID_SIZE_PIXELS (80) // w, 16-bits per pixel +#define VOSPI_PIDS_PER_SID (60) // h +#define VOSPI_SIDS_PER_FRAME (4) +#define VOSPI_PACKET_SIZE (VOSPI_HEADER_WORDS + VOSPI_PID_SIZE_PIXELS) // 16-bits +#define VOSPI_SID_SIZE_PIXELS (VOSPI_PIDS_PER_SID * VOSPI_PID_SIZE_PIXELS) // 16-bits + +#define VOSPI_BUFFER_SIZE (VOSPI_PACKET_SIZE * 2) // 16-bits +#define VOSPI_CLOCK_SPEED 20000000 // hz +#define VOSPI_SYNC_MS 200 // ms + +#define VOSPI_SPECIAL_PACKET (20) +#define VOSPI_DONT_CARE_PACKET (0x0F00) +#define VOSPI_HEADER_DONT_CARE(x) (((x) & VOSPI_DONT_CARE_PACKET) == VOSPI_DONT_CARE_PACKET) +#define VOSPI_HEADER_PID(id) ((id) & 0x0FFF) +#define VOSPI_HEADER_SID(id) (((id) >> 12) & 0x7) + +static soft_timer_entry_t flir_lepton_spi_rx_timer = {}; +static int fir_lepton_spi_rx_cb_tail = 0; +static int fir_lepton_spi_rx_cb_expected_pid = 0; +static int fir_lepton_spi_rx_cb_expected_sid = 0; +static uint16_t OMV_ATTR_SECTION(OMV_ATTR_ALIGNED_DMA(fir_lepton_buf[VOSPI_BUFFER_SIZE]), ".dma_buffer"); +static void fir_lepton_spi_callback(omv_spi_t *spi, void *userdata, void *buf); + +STATIC mp_obj_t fir_lepton_spi_resync_callback(mp_obj_t unused) { + // For triple buffering we are never drawing where tail or head + // (which may instantly update to be equal to tail) is. + fir_lepton_spi_rx_cb_tail = (framebuffer_tail + 1) % FRAMEBUFFER_COUNT; + if (fir_lepton_spi_rx_cb_tail == framebuffer_head) { + fir_lepton_spi_rx_cb_tail = (fir_lepton_spi_rx_cb_tail + 1) % FRAMEBUFFER_COUNT; + } + + omv_spi_transfer_t spi_xfer = { + .rxbuf = fir_lepton_buf, + .size = VOSPI_BUFFER_SIZE, + .flags = OMV_SPI_XFER_DMA, + .callback = fir_lepton_spi_callback, + }; + + omv_gpio_write(OMV_FIR_LEPTON_SSEL_PIN, 0); + omv_spi_transfer_start(&spi_bus, &spi_xfer); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(fir_lepton_spi_resync_callback_obj, fir_lepton_spi_resync_callback); + +static void fir_lepton_spi_resync() { + flir_lepton_spi_rx_timer.flags = SOFT_TIMER_FLAG_PY_CALLBACK; + flir_lepton_spi_rx_timer.mode = SOFT_TIMER_MODE_ONE_SHOT; + flir_lepton_spi_rx_timer.delta_ms = VOSPI_SYNC_MS; + flir_lepton_spi_rx_timer.py_callback = (mp_obj_t) &fir_lepton_spi_resync_callback_obj; + soft_timer_insert(&flir_lepton_spi_rx_timer, VOSPI_SYNC_MS); +} + +#if defined(OMV_FIR_LEPTON_CHECK_CRC) +static bool fir_lepton_spi_check_crc(const uint16_t *base) { + int id = base[0]; + int packet_crc = base[1]; + int crc = ByteCRC16((id >> 8) & 0x0F, 0); + crc = ByteCRC16(id, crc); + crc = ByteCRC16(0, crc); + crc = ByteCRC16(0, crc); + + for (int i = VOSPI_HEADER_WORDS; i < VOSPI_PACKET_SIZE; i++) { + int value = base[i]; + crc = ByteCRC16(value >> 8, crc); + crc = ByteCRC16(value, crc); + } + + return packet_crc == crc; +} +#endif + +static mp_obj_t fir_lepton_frame_cb = mp_const_none; + +void fir_lepton_spi_callback(omv_spi_t *spi, void *userdata, void *buf) { + const uint16_t *base = (uint16_t *) buf; + + int id = base[0]; + + // Ignore don't care packets. + if (VOSPI_HEADER_DONT_CARE(id)) { + return; + } + + int pid = VOSPI_HEADER_PID(id); + int sid = VOSPI_HEADER_SID(id) - 1; + + // Discard packets with a pid != 0 when waiting for the first packet. + if ((fir_lepton_spi_rx_cb_expected_pid == 0) && (pid != 0)) { + return; + } + + // Discard sidments with a sid != 0 when waiting for the first segment. + if (fir_lepton_3 && (pid == VOSPI_SPECIAL_PACKET) && (fir_lepton_spi_rx_cb_expected_sid == 0) && (sid != 0)) { + fir_lepton_spi_rx_cb_expected_pid = 0; + return; + } + + // Are we in sync with the flir lepton? + if ((pid != fir_lepton_spi_rx_cb_expected_pid) + #if defined(OMV_FIR_LEPTON_CHECK_CRC) + || (!fir_lepton_spi_check_crc(base)) + #endif + || (fir_lepton_3 && (pid == VOSPI_SPECIAL_PACKET) && (sid != fir_lepton_spi_rx_cb_expected_sid))) { + fir_lepton_spi_rx_cb_expected_pid = 0; + fir_lepton_spi_rx_cb_expected_sid = 0; + omv_spi_transfer_abort(&spi_bus); + omv_gpio_write(OMV_FIR_LEPTON_SSEL_PIN, 1); + fir_lepton_spi_resync(); + return; + } + + memcpy(framebuffers[fir_lepton_spi_rx_cb_tail] + + (fir_lepton_spi_rx_cb_expected_pid * VOSPI_PID_SIZE_PIXELS) + + (fir_lepton_spi_rx_cb_expected_sid * VOSPI_SID_SIZE_PIXELS), + base + VOSPI_HEADER_WORDS, VOSPI_PID_SIZE_PIXELS * sizeof(uint16_t)); + + fir_lepton_spi_rx_cb_expected_pid += 1; + if (fir_lepton_spi_rx_cb_expected_pid == VOSPI_PIDS_PER_SID) { + fir_lepton_spi_rx_cb_expected_pid = 0; + + bool frame_ready = false; + + // For the FLIR Lepton 3 we have to receive all the pids in all the segments. + if (fir_lepton_3) { + fir_lepton_spi_rx_cb_expected_sid += 1; + if (fir_lepton_spi_rx_cb_expected_sid == VOSPI_SIDS_PER_FRAME) { + fir_lepton_spi_rx_cb_expected_sid = 0; + frame_ready = true; + } + // For the FLIR Lepton 1/2 we just have to receive all the pids. + } else { + frame_ready = true; + } + + if (frame_ready) { + // Update tail which means a new image is ready. + framebuffer_tail = fir_lepton_spi_rx_cb_tail; + + // For triple buffering we are never drawing where tail or head + // (which may instantly update to be equal to tail) is. + fir_lepton_spi_rx_cb_tail = (fir_lepton_spi_rx_cb_tail + 1) % FRAMEBUFFER_COUNT; + if (fir_lepton_spi_rx_cb_tail == framebuffer_head) { + fir_lepton_spi_rx_cb_tail = (fir_lepton_spi_rx_cb_tail + 1) % FRAMEBUFFER_COUNT; + } + + // User should use micropython.schedule() in their callback to process the new frame. + if (fir_lepton_frame_cb != mp_const_none) { + mp_call_function_0(fir_lepton_frame_cb); + } + } + } +} + +#if defined(OMV_FIR_LEPTON_VSYNC_PIN) +static mp_obj_t fir_lepton_vsync_cb = NULL; + +static void fir_lepton_extint_callback(void *data) { + if (fir_lepton_vsync_cb) { + mp_call_function_0(fir_lepton_vsync_cb); + } +} +#endif + +void fir_lepton_deinit() { + omv_spi_transfer_abort(&spi_bus); + fir_lepton_spi_rx_cb_expected_pid = 0; + fir_lepton_spi_rx_cb_expected_sid = 0; + fb_alloc_free_till_mark_past_mark_permanent(); + + #if defined(OMV_FIR_LEPTON_MCLK) + HAL_TIM_PWM_Stop(&fir_lepton_mclk_tim_handle, OMV_FIR_LEPTON_MCLK_TIM_CHANNEL); + HAL_TIM_PWM_DeInit(&fir_lepton_mclk_tim_handle); + OMV_FIR_LEPTON_MCLK_TIM_FORCE_RESET(); + OMV_FIR_LEPTON_MCLK_TIM_RELEASE_RESET(); + OMV_FIR_LEPTON_MCLK_TIM_CLK_DISABLE(); + omv_gpio_deinit(OMV_FIR_LEPTON_MCLK_PIN); + #endif + + omv_spi_deinit(&spi_bus); + + #if defined(OMV_FIR_LEPTON_RESET_PIN) + omv_gpio_deinit(OMV_FIR_LEPTON_RESET_PIN); + #endif + + #if defined(OMV_FIR_LEPTON_POWER_PIN) + omv_gpio_deinit(OMV_FIR_LEPTON_POWER_PIN); + #endif +} + +int fir_lepton_init(omv_i2c_t *bus, int *w, int *h, int *refresh, int *resolution) { + omv_spi_config_t spi_config; + omv_spi_default_config(&spi_config, OMV_FIR_LEPTON_SPI_BUS); + + #if OMV_FIR_LEPTON_RX_CLK_DIV + spi_config.baudrate = VOSPI_CLOCK_SPEED / OMV_FIR_LEPTON_RX_CLK_DIV; + #else + spi_config.baudrate = VOSPI_CLOCK_SPEED; + #endif + + spi_config.datasize = 16; + spi_config.bus_mode = OMV_SPI_BUS_RX; + spi_config.nss_enable = false; + spi_config.dma_flags = OMV_SPI_DMA_CIRCULAR | OMV_SPI_DMA_DOUBLE; + omv_spi_init(&spi_bus, &spi_config); + + omv_gpio_write(OMV_FIR_LEPTON_SSEL_PIN, 1); + + #if defined(OMV_FIR_LEPTON_RESET_PIN) + omv_gpio_config(OMV_FIR_LEPTON_RESET_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_UP, OMV_GPIO_SPEED_LOW, -1); + omv_gpio_write(OMV_FIR_LEPTON_RESET_PIN, 1); + #endif + + #if defined(OMV_FIR_LEPTON_POWER_PIN) + omv_gpio_config(OMV_FIR_LEPTON_POWER_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_UP, OMV_GPIO_SPEED_LOW, -1); + omv_gpio_write(OMV_FIR_LEPTON_POWER_PIN, 1); + #endif + + #if defined(OMV_FIR_LEPTON_MCLK_TIM) + int tclk = OMV_FIR_LEPTON_MCLK_TIM_PCLK_FREQ() * 2; + int period = (tclk / OMV_FIR_LEPTON_MCLK_FREQ) - 1; + + omv_gpio_config(OMV_FIR_LEPTON_MCLK_PIN, OMV_GPIO_MODE_ALT, GPIO_PULLUP, OMV_GPIO_SPEED_MED, -1); + + fir_lepton_mclk_tim_handle.Instance = OMV_FIR_LEPTON_MCLK_TIM; + fir_lepton_mclk_tim_handle.Init.Prescaler = 0; + fir_lepton_mclk_tim_handle.Init.CounterMode = TIM_COUNTERMODE_UP; + fir_lepton_mclk_tim_handle.Init.Period = period; + fir_lepton_mclk_tim_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; + fir_lepton_mclk_tim_handle.Init.RepetitionCounter = 0; + fir_lepton_mclk_tim_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; + + TIM_OC_InitTypeDef fir_lepton_mclk_tim_oc_handle; + fir_lepton_mclk_tim_oc_handle.Pulse = period / 2; + fir_lepton_mclk_tim_oc_handle.OCMode = TIM_OCMODE_PWM1; + fir_lepton_mclk_tim_oc_handle.OCPolarity = TIM_OCPOLARITY_HIGH; + fir_lepton_mclk_tim_oc_handle.OCNPolarity = TIM_OCNPOLARITY_HIGH; + fir_lepton_mclk_tim_oc_handle.OCFastMode = TIM_OCFAST_DISABLE; + fir_lepton_mclk_tim_oc_handle.OCIdleState = TIM_OCIDLESTATE_RESET; + fir_lepton_mclk_tim_oc_handle.OCNIdleState = TIM_OCNIDLESTATE_RESET; + + OMV_FIR_LEPTON_MCLK_TIM_CLK_ENABLE(); + HAL_TIM_PWM_Init(&fir_lepton_mclk_tim_handle); + HAL_TIM_PWM_ConfigChannel(&fir_lepton_mclk_tim_handle, + &fir_lepton_mclk_tim_oc_handle, + OMV_FIR_LEPTON_MCLK_TIM_CHANNEL); + HAL_TIM_PWM_Start(&fir_lepton_mclk_tim_handle, OMV_FIR_LEPTON_MCLK_TIM_CHANNEL); + #endif + + #if defined(OMV_FIR_LEPTON_POWER_PIN) + omv_gpio_write(OMV_FIR_LEPTON_POWER_PIN, 0); + mp_hal_delay_ms(10); + + omv_gpio_write(OMV_FIR_LEPTON_POWER_PIN, 1); + mp_hal_delay_ms(10); + #endif + + #if defined(OMV_FIR_LEPTON_RESET_PIN) + omv_gpio_write(OMV_FIR_LEPTON_RESET_PIN, 0); + mp_hal_delay_ms(10); + + omv_gpio_write(OMV_FIR_LEPTON_RESET_PIN, 1); + mp_hal_delay_ms(1000); + #endif + + LEP_RAD_ENABLE_E rad; + LEP_AGC_ROI_T roi; + + for (uint32_t start = mp_hal_ticks_ms();; mp_hal_delay_ms(1)) { + if (LEP_OpenPort(bus, LEP_CCI_TWI, 0, &fir_lepton_handle) == LEP_OK) { + break; + } + + if ((mp_hal_ticks_ms() - start) >= 1000) { + return -1; + } + } + + #if (!defined(OMV_FIR_LEPTON_POWER_PIN)) && (!defined(OMV_FIR_LEPTON_RESET_PIN)) + if (LEP_RunOemReboot(&fir_lepton_handle) != LEP_OK) { + return -2; + } + + mp_hal_delay_ms(1000); + #endif + + for (uint32_t start = mp_hal_ticks_ms();; mp_hal_delay_ms(1)) { + LEP_SDK_BOOT_STATUS_E status; + + if (LEP_GetCameraBootStatus(&fir_lepton_handle, &status) != LEP_OK) { + return -3; + } + + if (status == LEP_BOOT_STATUS_BOOTED) { + break; + } + + if ((mp_hal_ticks_ms() - start) >= 1000) { + return -4; + } + } + + for (uint32_t start = mp_hal_ticks_ms();; mp_hal_delay_ms(1)) { + LEP_UINT16 status; + + if (LEP_DirectReadRegister(&fir_lepton_handle, LEP_I2C_STATUS_REG, &status) != LEP_OK) { + return -5; + } + + if (!(status & LEP_I2C_STATUS_BUSY_BIT_MASK)) { + break; + } + + if ((mp_hal_ticks_ms() - start) >= 1000) { + return -6; + } + } + + if (LEP_GetAgcROI(&fir_lepton_handle, &roi) != LEP_OK) { + return -7; + } + + if (LEP_GetRadEnableState(&fir_lepton_handle, &rad) != LEP_OK) { + return -8; + } + + int flir_w = roi.endCol + 1; + int flir_h = roi.endRow + 1; + fir_lepton_3 = flir_h > VOSPI_PIDS_PER_SID; + fir_lepton_rad_en = rad == LEP_RAD_ENABLE; + *w = flir_w; + *h = flir_h; + *refresh = fir_lepton_3 ? 27 : 9; + *resolution = fir_lepton_rad_en ? 16 : 14; + + #if defined(OMV_FIR_LEPTON_VSYNC_PIN) + if (LEP_SetOemGpioMode(&fir_lepton_handle, LEP_OEM_GPIO_MODE_VSYNC) != LEP_OK) { + return -9; + } + omv_gpio_config(OMV_FIR_LEPTON_VSYNC_PIN, OMV_GPIO_MODE_IT_FALL, OMV_GPIO_PULL_UP, OMV_GPIO_SPEED_LOW, -1); + omv_gpio_irq_register(OMV_FIR_LEPTON_VSYNC_PIN, fir_lepton_extint_callback, NULL); + #endif + + /////////////////////////////////////////////////////////////////////// + + fb_alloc_mark(); + + framebuffer_tail = 0; + framebuffer_head = 0; + + for (int i = 0; i < FRAMEBUFFER_COUNT; i++) { + framebuffers[i] = (uint16_t *) fb_alloc0(flir_w * flir_h * sizeof(uint16_t), FB_ALLOC_NO_HINT); + } + + fb_alloc_mark_permanent(); + fir_lepton_spi_resync(); + return 0; +} + +#if defined(OMV_FIR_LEPTON_VSYNC_PIN) +void fir_lepton_register_vsync_cb(mp_obj_t cb) { + omv_gpio_irq_enable(OMV_FIR_LEPTON_VSYNC_PIN, false); + + fir_lepton_vsync_cb = cb; + + if (cb != mp_const_none) { + omv_gpio_irq_enable(OMV_FIR_LEPTON_VSYNC_PIN, true); + } +} +#endif + +mp_obj_t fir_lepton_get_radiometry() { + return mp_obj_new_bool(fir_lepton_rad_en); +} + +void fir_lepton_register_frame_cb(mp_obj_t cb) { + fir_lepton_frame_cb = cb; +} + +mp_obj_t fir_lepton_get_frame_available() { + return mp_obj_new_bool(framebuffer_tail != framebuffer_head); +} + +static const uint16_t *fir_lepton_get_frame(int timeout) { + int sampled_framebuffer_tail = framebuffer_tail; + + if (timeout >= 0) { + for (uint32_t start = mp_hal_ticks_ms();;) { + sampled_framebuffer_tail = framebuffer_tail; + + if (framebuffer_head != sampled_framebuffer_tail) { + break; + } + + if ((mp_hal_ticks_ms() - start) >= timeout) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Timeout!")); + } + + MICROPY_EVENT_POLL_HOOK + } + } + + framebuffer_head = sampled_framebuffer_tail; + return framebuffers[sampled_framebuffer_tail]; +} + +static int fir_lepton_get_temperature() { + LEP_SYS_FPA_TEMPERATURE_KELVIN_T kelvin; + + if (LEP_GetSysFpaTemperatureKelvin(&fir_lepton_handle, &kelvin) != LEP_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("FPA Error!")); + } + + return kelvin; +} + +mp_obj_t fir_lepton_read_ta() { + return mp_obj_new_float((fir_lepton_get_temperature() * 0.01f) - 273.15f); +} + +mp_obj_t fir_lepton_read_ir(int w, int h, bool mirror, bool flip, bool transpose, int timeout) { + int kelvin = fir_lepton_get_temperature(); + mp_obj_list_t *list = (mp_obj_list_t *) mp_obj_new_list(w * h, NULL); + const uint16_t *data = fir_lepton_get_frame(timeout); + float min = +FLT_MAX; + float max = -FLT_MAX; + int w_1 = w - 1; + int h_1 = h - 1; + + for (int y = 0; y < h; y++) { + int y_dst = flip ? (h_1 - y) : y; + const uint16_t *raw_row = data + (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; + int raw = raw_row[x]; + + if (!fir_lepton_rad_en) { + raw = (raw - 8192) + kelvin; + } + + float celcius = (raw * 0.01f) - 273.15f; + + if (celcius < min) { + min = celcius; + } + + if (celcius > max) { + max = celcius; + } + + mp_obj_t f = mp_obj_new_float(celcius); + + if (!transpose) { + list_row[x_dst] = f; + } else { + t_list_row[x_dst * h] = f; + } + } + } + + mp_obj_t tuple[4]; + tuple[0] = mp_obj_new_float((kelvin * 0.01f) - 273.15f); + 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); +} + +void fir_lepton_fill_image(image_t *img, int w, int h, bool auto_range, float min, float max, + bool mirror, bool flip, bool transpose, int timeout) { + int kelvin = fir_lepton_get_temperature(); + const uint16_t *data = fir_lepton_get_frame(timeout); + int new_min; + int new_max; + + if (auto_range) { + new_min = INT_MAX; + new_max = INT_MIN; + + for (int i = 0, ii = w * h; i < ii; i++) { + int temp = data[i]; + + if (!fir_lepton_rad_en) { + temp = (temp - 8192) + kelvin; + } + + if (temp < new_min) { + new_min = temp; + } + + if (temp > new_max) { + new_max = temp; + } + } + } else { + float tmp = min; + min = (min < max) ? min : max; + max = (max > tmp) ? max : tmp; + new_min = fast_roundf((min + 273.15f) * 100.f); // to kelvin + new_max = fast_roundf((max + 273.15f) * 100.f); // to kelvin + } + + float diff = 255.f / (new_max - new_min); + int w_1 = w - 1; + int h_1 = h - 1; + + for (int y = 0; y < h; y++) { + int y_dst = flip ? (h_1 - y) : y; + const uint16_t *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; + int raw = raw_row[x]; + + if (!fir_lepton_rad_en) { + raw = (raw - 8192) + kelvin; + } + + if (raw < new_min) { + raw = new_min; + } + + if (raw > new_max) { + raw = new_max; + } + + int pixel = fast_roundf((raw - new_min) * diff); + pixel = __USAT(pixel, 8); + + if (!transpose) { + row_pointer[x_dst] = pixel; + } else { + t_row_pointer[x_dst * h] = pixel; + } + } + } +} + +void fir_lepton_trigger_ffc(int timeout) { + if (LEP_RunSysFFCNormalization(&fir_lepton_handle) != LEP_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("FFC Error!")); + } + + if (timeout >= 0) { + for (uint32_t start = mp_hal_ticks_ms();;) { + LEP_SYS_STATUS_E status; + + if (LEP_GetSysFFCStatus(&fir_lepton_handle, &status) != LEP_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("SYS Error!")); + } + + if (status == LEP_SYS_STATUS_READY) { + break; + } + + if ((mp_hal_ticks_ms() - start) >= timeout) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Timeout!")); + } + + mp_hal_delay_ms(1); + } + } +} + +#endif // OMV_ENABLE_FIR_LEPTON diff --git a/components/3rd_party/omv/omv/modules/py_fir_lepton.h b/components/3rd_party/omv/omv/modules/py_fir_lepton.h new file mode 100644 index 00000000..c5d0f51f --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_fir_lepton.h @@ -0,0 +1,24 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2020 Ibrahim Abdelkader + * Copyright (c) 2013-2020 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * FIR Python module. + */ +#ifndef __PY_FIR_LEPTON_H__ +#define __PY_FIR_LEPTON_H__ +void fir_lepton_deinit(); +int fir_lepton_init(omv_i2c_t *bus, int *w, int *h, int *refresh, int *resolution); +void fir_lepton_register_vsync_cb(mp_obj_t cb); +mp_obj_t fir_lepton_get_radiometry(); +void fir_lepton_register_frame_cb(mp_obj_t cb); +mp_obj_t fir_lepton_get_frame_available(); +mp_obj_t fir_lepton_read_ta(); +mp_obj_t fir_lepton_read_ir(int w, int h, bool mirror, bool flip, bool transpose, int timeout); +void fir_lepton_fill_image(image_t *img, int w, int h, bool auto_range, float min, float max, + bool mirror, bool flip, bool transpose, int timeout); +void fir_lepton_trigger_ffc(int timeout); +#endif // __PY_FIR_LEPTON_H__ diff --git a/components/3rd_party/omv/omv/modules/py_ft5x06.c b/components/3rd_party/omv/omv/modules/py_ft5x06.c new file mode 100644 index 00000000..0b707a13 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_ft5x06.c @@ -0,0 +1,291 @@ +/* + * 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. + * + * FT5X06 touch panel Python module. + */ +#include "omv_boardconfig.h" + +#if OMV_FT5X06_ENABLE + +#include "py/obj.h" +#include "py/nlr.h" +#include "py/runtime.h" +#include "extmod/machine_i2c.h" + +#include "py_helper.h" +#include "omv_gpio.h" +#include "py_display.h" + +#define FT5X06_I2C_ADDR 0x38 +#define NUM_TOUCH_POINTS 5 + +typedef enum ft5x06_gesture { + FT5X06_GESTURE_MOVE_UP = 0x1C, // Rotated by 90 degrees - 0x10, + FT5X06_GESTURE_MOVE_LEFT = 0x10, // Rotated by 90 degrees - 0x14, + FT5X06_GESTURE_MOVE_DOWN = 0x14, // Rotated by 90 degrees - 0x18, + FT5X06_GESTURE_MOVE_RIGHT = 0x18, // Rotated by 90 degrees - 0x1C, + FT5X06_GESTURE_ZOOM_IN = 0x48, + FT5X06_GESTURE_ZOOM_OUT = 0x49, + FT5X06_GESTURE_NONE = 0x00 +} ft5x06_gesture_t; + +typedef enum ft5x06_event { + FT5X06_EVENT_PUT_DOWN = 0x0, + FT5X06_EVENT_PUT_UP = 0x1, + FT5X06_EVENT_CONTACT = 0x2 +} ft5x06_event_t; + +typedef struct _py_ft5x06_obj_t { + mp_obj_base_t base; + mp_obj_t i2c_bus; + uint8_t i2c_addr; + mp_obj_t touch_callback; + volatile uint8_t touch_gesture; + volatile uint8_t touch_points; + volatile uint8_t touch_points_old; + volatile uint8_t touch_flag[NUM_TOUCH_POINTS]; + volatile uint8_t touch_id[NUM_TOUCH_POINTS]; + volatile uint16_t x[NUM_TOUCH_POINTS]; + volatile uint16_t y[NUM_TOUCH_POINTS]; +} py_ft5x06_obj_t; + +const mp_obj_type_t py_ft5x06_type; + +STATIC mp_obj_t py_ft5x06_update_points(mp_obj_t self_in); + +static void ft5x06_extint_callback(mp_obj_t self_in) { + py_ft5x06_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->touch_callback != mp_const_none) { + mp_obj_t tp = py_ft5x06_update_points(self_in); + mp_call_function_1(self->touch_callback, tp); + } +} + +STATIC mp_obj_t py_ft5x06_get_gesture(mp_obj_t self_in) { + py_ft5x06_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->touch_gesture); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_ft5x06_get_gesture_obj, py_ft5x06_get_gesture); + +STATIC mp_obj_t py_ft5x06_get_points(mp_obj_t self_in) { + py_ft5x06_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->touch_points); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_ft5x06_get_points_obj, py_ft5x06_get_points); + +STATIC mp_obj_t py_ft5x06_get_point_flag(mp_obj_t self_in, mp_obj_t index) { + py_ft5x06_obj_t *self = MP_OBJ_TO_PTR(self_in); + int i = mp_obj_get_int(index); + + if ((i < 0) || (NUM_TOUCH_POINTS <= i)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Index out of bounds!")); + } + return mp_obj_new_int(self->touch_flag[i]); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_ft5x06_get_point_flag_obj, py_ft5x06_get_point_flag); + +STATIC mp_obj_t py_ft5x06_get_point_id(mp_obj_t self_in, mp_obj_t index) { + py_ft5x06_obj_t *self = MP_OBJ_TO_PTR(self_in); + int i = mp_obj_get_int(index); + + if ((i < 0) || (NUM_TOUCH_POINTS <= i)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Index out of bounds!")); + } + return mp_obj_new_int(self->touch_id[i]); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_ft5x06_get_point_id_obj, py_ft5x06_get_point_id); + +STATIC mp_obj_t py_ft5x06_get_point_x(mp_obj_t self_in, mp_obj_t index) { + py_ft5x06_obj_t *self = MP_OBJ_TO_PTR(self_in); + int i = mp_obj_get_int(index); + + if ((i < 0) || (NUM_TOUCH_POINTS <= i)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Index out of bounds!")); + } + return mp_obj_new_int(self->x[i]); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_ft5x06_get_point_x_obj, py_ft5x06_get_point_x); + +STATIC mp_obj_t py_ft5x06_get_point_y(mp_obj_t self_in, mp_obj_t index) { + py_ft5x06_obj_t *self = MP_OBJ_TO_PTR(self_in); + int i = mp_obj_get_int(index); + + if ((i < 0) || (NUM_TOUCH_POINTS <= i)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Index out of bounds!")); + } + return mp_obj_new_int(self->y[i]); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_ft5x06_get_point_y_obj, py_ft5x06_get_point_y); + +STATIC mp_obj_t py_ft5x06_callback(mp_obj_t self_in, mp_obj_t cb) { + py_ft5x06_obj_t *self = MP_OBJ_TO_PTR(self_in); + + self->touch_callback = cb; + if (cb == mp_const_none) { + omv_gpio_irq_enable(OMV_FT5X06_INT_PIN, false); + } else { + omv_gpio_config(OMV_FT5X06_INT_PIN, OMV_GPIO_MODE_IT_FALL, OMV_GPIO_PULL_UP, OMV_GPIO_SPEED_LOW, -1); + omv_gpio_irq_register(OMV_FT5X06_INT_PIN, ft5x06_extint_callback, self_in); + omv_gpio_irq_enable(OMV_FT5X06_INT_PIN, true); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_ft5x06_callback_obj, py_ft5x06_callback); + +STATIC mp_obj_t py_ft5x06_update_points(mp_obj_t self_in) { + py_ft5x06_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (mp_machine_soft_i2c_transfer(self->i2c_bus, self->i2c_addr, 1, &((mp_machine_i2c_buf_t) { + .len = 1, .buf = (uint8_t []) {0x01} // addr + }), MP_MACHINE_I2C_FLAG_STOP) == 1) { + uint8_t regs[30]; + + if (mp_machine_soft_i2c_transfer(self->i2c_bus, self->i2c_addr, 1, &((mp_machine_i2c_buf_t) { + .len = 30, .buf = regs + }), MP_MACHINE_I2C_FLAG_READ | MP_MACHINE_I2C_FLAG_STOP) == 0) { + int points = regs[1] & 0xF; + if (points > NUM_TOUCH_POINTS) { + points = NUM_TOUCH_POINTS; + } + + // Update valid touch points... + for (int i = 0; i < points; i++) { + self->touch_flag[i] = regs[2 + (i * 6)] >> 6; + self->touch_id[i] = regs[4 + (i * 6)] >> 4; + self->x[i] = ((regs[2 + (i * 6)] & 0xF) << 8) | regs[3 + (i * 6)]; + self->y[i] = ((regs[4 + (i * 6)] & 0xF) << 8) | regs[5 + (i * 6)]; + } + + // Reset invalid touch points... + for (int i = points; i < NUM_TOUCH_POINTS; i++) { + self->touch_flag[i] = FT5X06_EVENT_PUT_UP; + } + + // Latch gesture as long as touch is valid. + if (points && regs[0]) { + self->touch_gesture = regs[0]; + } else if (!points) { + self->touch_gesture = FT5X06_GESTURE_NONE; + } + + // When the number of points increase the result is immediately valid. + if (points >= self->touch_points) { + self->touch_points = points; + // When the number of points decrease track the last valid number of events. + } else if (points <= self->touch_points_old) { + self->touch_points = self->touch_points_old; + } + + self->touch_points_old = points; + return mp_obj_new_int(self->touch_points); + } + } + + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to update the number of touch points!")); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_ft5x06_update_points_obj, py_ft5x06_update_points); + +STATIC mp_obj_t py_ft5x06_deinit(mp_obj_t self_in) { + omv_gpio_irq_enable(OMV_FT5X06_INT_PIN, false); + + omv_gpio_write(OMV_FT5X06_RESET_PIN, 0); + mp_hal_delay_ms(1); + + omv_gpio_write(OMV_FT5X06_RESET_PIN, 1); + mp_hal_delay_ms(39); + + omv_gpio_deinit(OMV_FT5X06_INT_PIN); + omv_gpio_deinit(OMV_FT5X06_RESET_PIN); + + HAL_GPIO_DeInit(OMV_FT5X06_SDA_PIN->gpio, OMV_FT5X06_SDA_PIN->pin_mask); + HAL_GPIO_DeInit(OMV_FT5X06_SCL_PIN->gpio, OMV_FT5X06_SCL_PIN->pin_mask); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_ft5x06_deinit_obj, py_ft5x06_deinit); + +mp_obj_t py_ft5x06_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_i2c_addr }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_i2c_addr, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = FT5X06_I2C_ADDR } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + py_ft5x06_obj_t *self = m_new_obj_with_finaliser(py_ft5x06_obj_t); + self->base.type = &py_ft5x06_type; + self->i2c_addr = args[ARG_i2c_addr].u_int; + self->i2c_bus = MP_OBJ_TYPE_GET_SLOT( + &mp_machine_soft_i2c_type, make_new) (&mp_machine_soft_i2c_type, 2, 0, (const mp_obj_t []) { + (mp_obj_t) OMV_FT5X06_SCL_PIN, (mp_obj_t) OMV_FT5X06_SDA_PIN + }); + + omv_gpio_config(OMV_FT5X06_RESET_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); + omv_gpio_write(OMV_FT5X06_RESET_PIN, 0); + mp_hal_delay_ms(1); + omv_gpio_write(OMV_FT5X06_RESET_PIN, 1); + mp_hal_delay_ms(39); + + if (mp_machine_soft_i2c_transfer(self->i2c_bus, self->i2c_addr, 1, &((mp_machine_i2c_buf_t) { + .len = 2, .buf = (uint8_t []) {0x00, 0x00} // addr, DEVICE_MODE + }), MP_MACHINE_I2C_FLAG_STOP) != 2) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("TouchPanel init failed.")); + } + + return MP_OBJ_FROM_PTR(self); +} + +STATIC const mp_rom_map_elem_t py_ft5x06_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ft5x06) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&py_ft5x06_deinit_obj) }, + + { MP_ROM_QSTR(MP_QSTR_FLAG_PRESSED), MP_ROM_INT(FT5X06_EVENT_PUT_DOWN) }, + { MP_ROM_QSTR(MP_QSTR_FLAG_RELEASED), MP_ROM_INT(FT5X06_EVENT_PUT_UP) }, + { MP_ROM_QSTR(MP_QSTR_FLAG_MOVED), MP_ROM_INT(FT5X06_EVENT_CONTACT) }, + { MP_ROM_QSTR(MP_QSTR_GESTURE_MOVE_UP), MP_ROM_INT(FT5X06_GESTURE_MOVE_UP) }, + { MP_ROM_QSTR(MP_QSTR_GESTURE_MOVE_LEFT), MP_ROM_INT(FT5X06_GESTURE_MOVE_LEFT) }, + { MP_ROM_QSTR(MP_QSTR_GESTURE_MOVE_DOWN), MP_ROM_INT(FT5X06_GESTURE_MOVE_DOWN) }, + { MP_ROM_QSTR(MP_QSTR_GESTURE_MOVE_RIGHT), MP_ROM_INT(FT5X06_GESTURE_MOVE_RIGHT) }, + { MP_ROM_QSTR(MP_QSTR_GESTURE_ZOOM_IN), MP_ROM_INT(FT5X06_GESTURE_ZOOM_IN) }, + { MP_ROM_QSTR(MP_QSTR_GESTURE_ZOOM_OUT), MP_ROM_INT(FT5X06_GESTURE_ZOOM_OUT) }, + { MP_ROM_QSTR(MP_QSTR_GESTURE_NONE), MP_ROM_INT(FT5X06_GESTURE_NONE) }, + + { MP_ROM_QSTR(MP_QSTR_get_gesture), MP_ROM_PTR(&py_ft5x06_get_gesture_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_points), MP_ROM_PTR(&py_ft5x06_get_points_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_point_flag), MP_ROM_PTR(&py_ft5x06_get_point_flag_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_point_id), MP_ROM_PTR(&py_ft5x06_get_point_id_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_point_x), MP_ROM_PTR(&py_ft5x06_get_point_x_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_point_y), MP_ROM_PTR(&py_ft5x06_get_point_y_obj) }, + { MP_ROM_QSTR(MP_QSTR_touch_callback), MP_ROM_PTR(&py_ft5x06_callback_obj) }, + { MP_ROM_QSTR(MP_QSTR_update_points), MP_ROM_PTR(&py_ft5x06_update_points_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(py_ft5x06_locals_dict, py_ft5x06_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + py_ft5x06_type, + MP_QSTR_FT5X06, + MP_TYPE_FLAG_NONE, + make_new, py_ft5x06_make_new, + locals_dict, &py_ft5x06_locals_dict + ); + +STATIC const mp_rom_map_elem_t globals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ft5x06) }, + { MP_ROM_QSTR(MP_QSTR_FT5X06), MP_ROM_PTR(&py_ft5x06_type) }, +}; +STATIC MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); + +const mp_obj_module_t ft5x06_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_t) &globals_dict, +}; + +MP_REGISTER_MODULE(MP_QSTR_ft5x06, ft5x06_module); +#endif // OMV_FT5X06_ENABLE diff --git a/components/3rd_party/omv/omv/modules/py_gif.c b/components/3rd_party/omv/omv/modules/py_gif.c new file mode 100644 index 00000000..46476028 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_gif.c @@ -0,0 +1,166 @@ +/* + * 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 "imlib_config.h" +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + +#include "py/mphal.h" +#include "py/runtime.h" + +#include "file_utils.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; + +// Gif object +typedef struct py_gif_obj { + mp_obj_base_t base; + uint32_t width; + uint32_t height; + bool color; + bool loop; + FIL fp; +} py_gif_obj_t; + +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_obj_t py_gif_width(mp_obj_t self_in) { + py_gif_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->width); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_gif_width_obj, py_gif_width); + +static mp_obj_t py_gif_height(mp_obj_t self_in) { + py_gif_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->height); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_gif_height_obj, py_gif_height); + +static mp_obj_t py_gif_format(mp_obj_t self_in) { + py_gif_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->color ? PIXFORMAT_RGB565 : PIXFORMAT_GRAYSCALE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_gif_format_obj, py_gif_format); + +static mp_obj_t py_gif_size(mp_obj_t self_in) { + py_gif_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(file_size(&self->fp)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_gif_size_obj, py_gif_size); + +static mp_obj_t py_gif_loop(mp_obj_t self_in) { + py_gif_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->loop); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_gif_loop_obj, py_gif_loop); + +static mp_obj_t py_gif_add_frame(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_delay }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_delay, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 10 } }, + }; + + // Parse args. + py_gif_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + image_t *image = py_helper_arg_to_image(pos_args[1], ARG_IMAGE_MUTABLE); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 2, pos_args + 2, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Sanity checks + if (self->width != image->w || self->height != image->h) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unexpected image geometry")); + } + if (image->pixfmt == PIXFORMAT_JPEG || image->pixfmt == PIXFORMAT_BINARY) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Image format is not supported")); + } + + gif_add_frame(&self->fp, image, args[ARG_delay].u_int); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_gif_add_frame_obj, 2, py_gif_add_frame); + +STATIC mp_obj_t py_gif_close(mp_obj_t self_in) { + py_gif_obj_t *self = MP_OBJ_TO_PTR(self_in); + gif_close(&self->fp); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_gif_close_obj, py_gif_close); + +static mp_obj_t py_gif_open(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_width, ARG_height, ARG_color, ARG_loop }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_width, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + { MP_QSTR_height, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + { MP_QSTR_color, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + { MP_QSTR_loop, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = true } }, + }; + + // Parse args. + const char *path = mp_obj_str_get_str(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + py_gif_obj_t *gif = m_new_obj_with_finaliser(py_gif_obj_t); + gif->base.type = &py_gif_type; + gif->width = (args[ARG_width].u_int == -1) ? framebuffer_get_width() : args[ARG_width].u_int; + gif->height = (args[ARG_height].u_int == -1) ? framebuffer_get_height() : args[ARG_height].u_int; + gif->color = (args[ARG_color].u_int == -1) ? (framebuffer_get_depth() >= 2) : args[ARG_color].u_bool; + gif->loop = args[ARG_loop].u_bool; + + file_open(&gif->fp, path, false, FA_WRITE | FA_CREATE_ALWAYS); + gif_open(&gif->fp, gif->width, gif->height, gif->color, gif->loop); + return gif; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_gif_open_obj, 1, py_gif_open); + +STATIC const mp_rom_map_elem_t py_gif_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_gif) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&py_gif_close_obj) }, + + { MP_OBJ_NEW_QSTR(MP_QSTR_width), MP_ROM_PTR(&py_gif_width_obj) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_height), MP_ROM_PTR(&py_gif_height_obj) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_format), MP_ROM_PTR(&py_gif_format_obj) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_size), MP_ROM_PTR(&py_gif_size_obj) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_loop), MP_ROM_PTR(&py_gif_loop_obj) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_add_frame), MP_ROM_PTR(&py_gif_add_frame_obj) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_close), MP_ROM_PTR(&py_gif_close_obj) }, + { NULL, NULL }, +}; +STATIC MP_DEFINE_CONST_DICT(py_gif_locals_dict, py_gif_locals_dict_table); + +STATIC MP_DEFINE_CONST_OBJ_TYPE( + py_gif_type, + MP_QSTR_Gif, + MP_TYPE_FLAG_NONE, + print, py_gif_print, + locals_dict, &py_gif_locals_dict + ); + +STATIC const mp_rom_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_ROM_PTR(&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); +#endif // IMLIB_ENABLE_IMAGE_FILE_IO diff --git a/components/3rd_party/omv/omv/modules/py_helper.c b/components/3rd_party/omv/omv/modules/py_helper.c new file mode 100644 index 00000000..03e6dc27 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_helper.c @@ -0,0 +1,545 @@ +/* + * 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(const mp_obj_t arg, uint32_t flags) { + image_t *image = NULL; + if ((flags & ARG_IMAGE_ALLOC) && MP_OBJ_IS_STR(arg)) { + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + const char *path = mp_obj_str_get_str(arg); + FIL fp; + image = xalloc(sizeof(image_t)); + img_read_settings_t rs; + imlib_read_geometry(&fp, image, path, &rs); + file_close(&fp); + image->data = fb_alloc(image_size(image), FB_ALLOC_CACHE_ALIGN); + imlib_load_image(image, path); + #else + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Image I/O is not supported")); + #endif // IMLIB_ENABLE_IMAGE_FILE_IO + } else { + image = py_image_cobj(arg); + } + if (flags) { + if ((flags & ARG_IMAGE_MUTABLE) && !image->is_mutable) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected a mutable image")); + } else if ((flags & ARG_IMAGE_UNCOMPRESSED) && image->is_compressed) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected an uncompressed image")); + } else if ((flags & ARG_IMAGE_GRAYSCALE) && image->pixfmt != PIXFORMAT_GRAYSCALE) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected an uncompressed image")); + } + } + return image; +} + +const void *py_helper_arg_to_palette(const mp_obj_t arg, uint32_t pixfmt) { + const void *palette = NULL; + if (mp_obj_is_int(arg)) { + uint32_t type = mp_obj_get_int(arg); + if (type == COLOR_PALETTE_RAINBOW) { + palette = rainbow_table; + } else if (type == COLOR_PALETTE_IRONBOW) { + palette = ironbow_table; + } else { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid color palette")); + } + } else if (arg != mp_const_none) { + image_t *img = py_helper_arg_to_image(arg, ARG_IMAGE_MUTABLE); + if (img->pixfmt != pixfmt) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Unexpcted color palette format")); + } + if ((img->w * img->h) != 256) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Color palette must be 256 pixels")); + } + palette = img->data; + } + return palette; +} + +rectangle_t py_helper_arg_to_roi(const mp_obj_t arg, const image_t *img) { + rectangle_t roi = {0, 0, img->w, img->h}; + if (arg != mp_const_none) { + mp_obj_t *arg_roi; + mp_obj_get_array_fixed_n(arg, 4, &arg_roi); + roi.x = mp_obj_get_int(arg_roi[0]); + roi.y = mp_obj_get_int(arg_roi[1]); + roi.w = mp_obj_get_int(arg_roi[2]); + roi.h = mp_obj_get_int(arg_roi[3]); + + PY_ASSERT_TRUE_MSG((roi.w >= 1) && (roi.h >= 1), "Invalid ROI dimensions!"); + rectangle_t bounds = {0, 0, img->w, img->h}; + PY_ASSERT_TRUE_MSG(rectangle_overlap(&roi, &bounds), "ROI does not overlap on the image!"); + rectangle_intersected(&roi, &bounds); + } + return roi; +} + +void py_helper_arg_to_scale(const mp_obj_t arg_x_scale, const mp_obj_t arg_y_scale, + float *x_scale, float *y_scale) { + if (arg_x_scale != mp_const_none) { + *x_scale = mp_obj_get_float(arg_x_scale); + } + if (arg_y_scale != mp_const_none) { + *y_scale = mp_obj_get_float(arg_y_scale); + } + + if (arg_x_scale == mp_const_none && arg_y_scale != mp_const_none) { + *x_scale = *y_scale; + } else if (arg_y_scale == mp_const_none && arg_x_scale != mp_const_none) { + *y_scale = *x_scale; + } +} + +void py_helper_arg_to_minmax(const mp_obj_t minmax, float *min, float *max, + const mp_obj_t *array, size_t array_size) { + float min_out = FLT_MAX; + float max_out = -FLT_MAX; + + if (minmax != mp_const_none) { + mp_obj_t *arg_scale; + mp_obj_get_array_fixed_n(minmax, 2, &arg_scale); + min_out = mp_obj_get_float(arg_scale[0]); + max_out = mp_obj_get_float(arg_scale[1]); + } else if (array && array_size) { + for (int i = 0; i < array_size; i++) { + float t = mp_obj_get_float(array[i]); + if (t < min_out) { + min_out = t; + } + if (t > max_out) { + max_out = t; + } + } + } + + *min = min_out; + *max = max_out; +} + +float py_helper_arg_to_float(const mp_obj_t arg, float default_value) { + if (arg != mp_const_none) { + return mp_obj_get_float(arg); + } + return default_value; +} + +void py_helper_arg_to_float_array(const mp_obj_t arg, float *array, size_t size) { + if (arg != mp_const_none) { + mp_obj_t *arg_array; + mp_obj_get_array_fixed_n(arg, size, &arg_array); + for (int i = 0; i < size; i++) { + array[i] = mp_obj_get_float(arg_array[i]); + } + } +} + +image_t *py_helper_keyword_to_image(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(kw_arg->value, ARG_IMAGE_MUTABLE); + } else if (n_args > arg_index) { + default_val = py_helper_arg_to_image(args[arg_index], ARG_IMAGE_MUTABLE); + } + + return default_val; +} + +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]); + } + } +} + +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; +} + +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(n_args, args, arg_index, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color_palette), NULL); + + 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(n_args, args, 9, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_alpha_palette), NULL); + + 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/components/3rd_party/omv/omv/modules/py_helper.h b/components/3rd_party/omv/omv/modules/py_helper.h new file mode 100644 index 00000000..1726c841 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_helper.h @@ -0,0 +1,71 @@ +/* + * 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" + +typedef enum py_helper_arg_image_flags { + ARG_IMAGE_ANY = (0 << 0), + ARG_IMAGE_MUTABLE = (1 << 0), + ARG_IMAGE_UNCOMPRESSED = (1 << 1), + ARG_IMAGE_GRAYSCALE = (1 << 2), + ARG_IMAGE_ALLOC = (1 << 3) +} py_helper_arg_image_flags_t; + +extern const mp_obj_fun_builtin_var_t py_func_unavailable_obj; +image_t *py_helper_arg_to_image(const mp_obj_t arg, uint32_t flags); +const void *py_helper_arg_to_palette(const mp_obj_t arg, uint32_t pixfmt); +rectangle_t py_helper_arg_to_roi(const mp_obj_t arg, const image_t *img); +void py_helper_arg_to_scale(const mp_obj_t arg_x_scale, const mp_obj_t arg_y_scale, + float *x_scale, float *y_scale); +void py_helper_arg_to_minmax(const mp_obj_t minmax, float *min, float *max, + const mp_obj_t *array, size_t array_size); +float py_helper_arg_to_float(const mp_obj_t arg, float default_value); +void py_helper_arg_to_float_array(const mp_obj_t arg, float *array, size_t size); + +image_t *py_helper_keyword_to_image(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, image_t *default_val); +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); +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/components/3rd_party/omv/omv/modules/py_image.c b/components/3rd_party/omv/omv/modules/py_image.c new file mode 100644 index 00000000..8834ae3b --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_image.c @@ -0,0 +1,7449 @@ +/* + * 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 "file_utils.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 MP_DEFINE_CONST_OBJ_TYPE( + py_cascade_type, + MP_QSTR_Cascade, + MP_TYPE_FLAG_NONE, + 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 MP_DEFINE_CONST_OBJ_TYPE( + py_kp_type, + MP_QSTR_kp_desc, + MP_TYPE_FLAG_NONE, + 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 MP_DEFINE_CONST_OBJ_TYPE( + py_lbp_type, + MP_QSTR_lbp_desc, + MP_TYPE_FLAG_NONE, + 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; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kptmatch_cx_obj, py_kptmatch_cx); + +mp_obj_t py_kptmatch_cy(mp_obj_t self_in) { + return ((py_kptmatch_obj_t *) self_in)->cy; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kptmatch_cy_obj, py_kptmatch_cy); + +mp_obj_t py_kptmatch_x(mp_obj_t self_in) { + return ((py_kptmatch_obj_t *) self_in)->x; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kptmatch_x_obj, py_kptmatch_x); + +mp_obj_t py_kptmatch_y(mp_obj_t self_in) { + return ((py_kptmatch_obj_t *) self_in)->y; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kptmatch_y_obj, py_kptmatch_y); + +mp_obj_t py_kptmatch_w(mp_obj_t self_in) { + return ((py_kptmatch_obj_t *) self_in)->w; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kptmatch_w_obj, py_kptmatch_w); + +mp_obj_t py_kptmatch_h(mp_obj_t self_in) { + return ((py_kptmatch_obj_t *) self_in)->h; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kptmatch_h_obj, py_kptmatch_h); + +mp_obj_t py_kptmatch_count(mp_obj_t self_in) { + return ((py_kptmatch_obj_t *) self_in)->count; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kptmatch_count_obj, py_kptmatch_count); + +mp_obj_t py_kptmatch_theta(mp_obj_t self_in) { + return ((py_kptmatch_obj_t *) self_in)->theta; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kptmatch_theta_obj, py_kptmatch_theta); + +mp_obj_t py_kptmatch_match(mp_obj_t self_in) { + return ((py_kptmatch_obj_t *) self_in)->match; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kptmatch_match_obj, py_kptmatch_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_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 MP_DEFINE_CONST_OBJ_TYPE( + py_kptmatch_type, + MP_QSTR_kptmatch, + MP_TYPE_FLAG_NONE, + print, py_kptmatch_print, + subscr, py_kptmatch_subscr, + locals_dict, &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->is_compressed) { + // For JPEG/PNG 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; i < img->w; 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; i < img->w; 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; i < img->w; i++) { + mp_obj_list_append(row, mp_obj_new_int(IMAGE_GET_RGB565_PIXEL(img, i, self->cur))); + } + self->cur++; + return row; + } + } + default: { + // JPEG/PNG + 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->is_compressed + && 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" : + (image->pixfmt == PIXFORMAT_PNG) ? "png" : "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: + case PIXFORMAT_PNG: { + 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: + case PIXFORMAT_PNG: { + 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); + case PIXFORMAT_JPEG: + return mp_obj_new_int(PIXFORMAT_JPEG); + case PIXFORMAT_PNG: + return mp_obj_new_int(PIXFORMAT_PNG); + default: + return mp_obj_new_int(PIXFORMAT_INVALID); + } +} +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(args[0], ARG_IMAGE_UNCOMPRESSED); + + 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(args[0], ARG_IMAGE_UNCOMPRESSED); + + 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(img_obj, ARG_IMAGE_MUTABLE); + + 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(img_obj, ARG_IMAGE_MUTABLE); + + 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(args[0], ARG_IMAGE_MUTABLE); + + 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(args[0], ARG_IMAGE_MUTABLE); + + 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, + mp_obj_t copy_default, bool quality_is_first_arg, bool encode_for_ide_default, + uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { + image_t *src_img = py_image_cobj(args[0]); + + int quality_default = 90; + if (quality_is_first_arg && (n_args > 1)) { + quality_default = mp_obj_get_int(args[1]); + n_args -= 1; + args += 1; + } + + 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); + + float arg_x_size; + bool got_x_size = py_helper_keyword_float_maybe(n_args, args, 9, kw_args, + MP_OBJ_NEW_QSTR(MP_QSTR_x_size), &arg_x_size); + + float arg_y_size; + bool got_y_size = py_helper_keyword_float_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 / arg_roi.w; + } + + if (got_y_size) { + arg_y_scale = arg_y_size / 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), copy_default); + bool copy = false; + image_t *arg_other = copy_to_fb ? NULL : src_img; + + int arg_q = py_helper_keyword_int(n_args, args, 12, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_quality), quality_default); + if ((arg_q < 1) || (100 < arg_q)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("1 <= quality <= 100!")); + } + + bool arg_e = py_helper_keyword_int(n_args, args, 13, kw_args, + MP_OBJ_NEW_QSTR(MP_QSTR_encode_for_ide), encode_for_ide_default); + + if (copy_obj) { + if (mp_obj_is_integer(copy_obj)) { + copy = mp_obj_get_int(copy_obj); + if (copy) { + arg_other = NULL; + } + } else { + arg_other = py_image_cobj(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), + .pixfmt = (pixfmt == PIXFORMAT_INVALID) ? src_img->pixfmt : pixfmt, + .size = src_img->size, + .pixels = NULL, + }; + + image_t dst_img_tmp = dst_img; + + 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.is_compressed) { + fb_alloc_mark(); + + bool simple = (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); + + if ((dst_img.pixfmt != src_img->pixfmt) || (!simple)) { + image_t temp; + memcpy(&temp, src_img, sizeof(image_t)); + + if (src_img->is_compressed || (!simple)) { + temp.w = dst_img.w; + temp.h = dst_img.h; + temp.pixfmt = PIXFORMAT_RGB565; // TODO PIXFORMAT_ARGB8888 + temp.size = 0; + temp.data = fb_alloc(image_size(&temp), FB_ALLOC_NO_HINT); + imlib_draw_image(&temp, 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, NULL); + } + + if (((dst_img.pixfmt == PIXFORMAT_JPEG) && jpeg_compress(&temp, &dst_img_tmp, arg_q, false)) + || ((dst_img.pixfmt == PIXFORMAT_PNG) && png_compress(&temp, &dst_img_tmp))) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Compression Failed!")); + } + } else if (arg_e) { + dst_img_tmp.data = fb_alloc(image_size(&dst_img_tmp), FB_ALLOC_NO_HINT); + memcpy(dst_img_tmp.data, src_img->data, dst_img_tmp.size); + } else { + dst_img_tmp.data = src_img->data; + } + + if (arg_e) { + dst_img.size = fb_encode_for_ide_new_size(&dst_img_tmp); + } else { + dst_img.size = dst_img_tmp.size; + } + } + + 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; + // Update now if not in place to possibly freeup framebuffer RAM, otherwise update after. + if (dst_img.data != src_img->data) { + py_helper_update_framebuffer(&dst_img); + } + } else { + dst_img.data = xalloc(size); + } + + if (dst_img.is_compressed) { + if (arg_e) { + fb_encode_for_ide(dst_img.data, &dst_img_tmp); + } else if (dst_img.data != dst_img_tmp.data) { + memcpy(dst_img.data, dst_img_tmp.data, dst_img.size); + } + fb_alloc_free_till_mark(); + } 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, NULL); + fb_alloc_free_till_mark(); + } + + if (arg_other) { + if (dst_img.data == src_img->data) { + py_helper_update_framebuffer(&dst_img); + } + 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, NULL, false, 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, NULL, false, 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, NULL, false, 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, NULL, false, 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_to_ironbow(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { + return py_image_to(PIXFORMAT_RGB565, ironbow_table, false, NULL, false, false, n_args, args, kw_args); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_to_ironbow_obj, 1, py_image_to_ironbow); + +static mp_obj_t py_image_to_jpeg(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { + return py_image_to(PIXFORMAT_JPEG, NULL, false, NULL, false, false, n_args, args, kw_args); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_to_jpeg_obj, 1, py_image_to_jpeg); + +static mp_obj_t py_image_to_png(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { + return py_image_to(PIXFORMAT_PNG, NULL, false, NULL, false, false, n_args, args, kw_args); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_to_png_obj, 1, py_image_to_png); + +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, NULL, false, false, 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, NULL, false, 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_compress(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { + return py_image_to(PIXFORMAT_JPEG, NULL, false, NULL, true, false, n_args, args, kw_args); +} +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) { + return py_image_to(PIXFORMAT_JPEG, NULL, false, NULL, true, true, n_args, args, kw_args); +} +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) { + return py_image_to(PIXFORMAT_JPEG, NULL, false, mp_const_true, true, false, n_args, args, kw_args); +} +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) { + return py_image_to(PIXFORMAT_JPEG, NULL, false, mp_const_true, true, true, n_args, args, kw_args); +} +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(args[0], ARG_IMAGE_UNCOMPRESSED); + + image_t *arg_msk = + py_helper_keyword_to_image(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[0], ARG_IMAGE_MUTABLE); + + 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(args[0], ARG_IMAGE_MUTABLE); + + 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(args[0], ARG_IMAGE_MUTABLE); + + 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(args[0], ARG_IMAGE_MUTABLE); + + 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(args[0], ARG_IMAGE_MUTABLE); + + 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(args[0], ARG_IMAGE_MUTABLE); + + 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(args[0], ARG_IMAGE_MUTABLE); + + 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(args[0], ARG_IMAGE_MUTABLE); + + 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) { + fb_alloc_mark(); + image_t *arg_img = py_helper_arg_to_image(args[0], ARG_IMAGE_MUTABLE); + image_t *arg_other = py_helper_arg_to_image(args[1], ARG_IMAGE_ANY | ARG_IMAGE_ALLOC); + + 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); + + float arg_x_size; + bool got_x_size = py_helper_keyword_float_maybe(n_args, + args, + offset + 8, + kw_args, + MP_OBJ_NEW_QSTR(MP_QSTR_x_size), + &arg_x_size); + + float arg_y_size; + bool got_y_size = py_helper_keyword_float_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 / arg_roi.w; + } + if (got_y_size) { + arg_y_scale = arg_y_size / 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; + } + + 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, 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(args[0], ARG_IMAGE_MUTABLE); + + 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(args[0], ARG_IMAGE_MUTABLE); + 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(args[0], ARG_IMAGE_MUTABLE); + 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(args[0], ARG_IMAGE_MUTABLE); + 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(args[0], ARG_IMAGE_MUTABLE); + + 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(n_args, args, offset + 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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_ISP_OPS +////////////// +// ISP Methods +////////////// + +STATIC mp_obj_t py_awb(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { + image_t *arg_img = + py_helper_arg_to_image(args[0], ARG_IMAGE_UNCOMPRESSED); + bool arg_max = + py_helper_keyword_float(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_max), false); + + imlib_awb(arg_img, arg_max); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_awb_obj, 1, py_awb); + +STATIC mp_obj_t py_ccm(mp_obj_t img_obj, mp_obj_t ccm_obj) { + image_t *arg_img = + py_helper_arg_to_image(img_obj, ARG_IMAGE_MUTABLE); + + size_t len; + mp_obj_t *items; + mp_obj_get_array(ccm_obj, &len, &items); + + if ((len != 9) && (len != 12)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected a 3x3 or 4x3 matrix!")); + } + + float ccm[12] = {}; + for (size_t i = 0; i < len; i++) { + ccm[i] = mp_obj_get_float(items[i]); + } + + imlib_ccm(arg_img, ccm, len == 12); + return img_obj; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_ccm_obj, py_ccm); + +STATIC mp_obj_t py_image_gamma(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { + image_t *arg_img = + py_helper_arg_to_image(args[0], ARG_IMAGE_UNCOMPRESSED); + 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(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_obj, 1, py_image_gamma); + +#endif // IMLIB_ENABLE_ISP_OPS + +#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(args[0], ARG_IMAGE_MUTABLE); + + 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(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + 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(img_obj, ARG_IMAGE_MUTABLE)); + 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(args[0], ARG_IMAGE_MUTABLE); + image_t *arg_msk = + py_helper_keyword_to_image(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[1], ARG_IMAGE_MUTABLE), 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(args[0], ARG_IMAGE_MUTABLE); + image_t *arg_msk = + py_helper_keyword_to_image(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[1], ARG_IMAGE_MUTABLE), 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(args[0], ARG_IMAGE_MUTABLE); + image_t *arg_msk = + py_helper_keyword_to_image(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[1], ARG_IMAGE_MUTABLE), 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(args[0], ARG_IMAGE_MUTABLE); + image_t *arg_msk = + py_helper_keyword_to_image(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[1], ARG_IMAGE_MUTABLE), 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(args[0], ARG_IMAGE_MUTABLE); + image_t *arg_msk = + py_helper_keyword_to_image(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[1], ARG_IMAGE_MUTABLE), 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(args[0], ARG_IMAGE_MUTABLE); + image_t *arg_msk = + py_helper_keyword_to_image(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[1], ARG_IMAGE_MUTABLE), 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(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + fb_alloc_mark(); + imlib_erode(py_helper_arg_to_image(args[0], ARG_IMAGE_MUTABLE), 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(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + fb_alloc_mark(); + imlib_dilate(py_helper_arg_to_image(args[0], ARG_IMAGE_MUTABLE), 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(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + fb_alloc_mark(); + imlib_open(py_helper_arg_to_image(args[0], ARG_IMAGE_MUTABLE), 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(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + fb_alloc_mark(); + imlib_close(py_helper_arg_to_image(args[0], ARG_IMAGE_MUTABLE), 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_negate(mp_obj_t img_obj) { + imlib_negate(py_helper_arg_to_image(img_obj, ARG_IMAGE_MUTABLE)); + 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(args[0], ARG_IMAGE_MUTABLE); + 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(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(arg_1, ARG_IMAGE_MUTABLE), 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(args[0], ARG_IMAGE_MUTABLE); + image_t *arg_msk = + py_helper_keyword_to_image(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[1], ARG_IMAGE_MUTABLE), 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(args[0], ARG_IMAGE_MUTABLE); + 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(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[1], ARG_IMAGE_MUTABLE), 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(args[0], ARG_IMAGE_MUTABLE); + 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(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[1], ARG_IMAGE_MUTABLE), 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(args[0], ARG_IMAGE_MUTABLE); + 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(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[1], ARG_IMAGE_MUTABLE), 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(args[0], ARG_IMAGE_MUTABLE); + image_t *arg_msk = + py_helper_keyword_to_image(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[1], ARG_IMAGE_MUTABLE), 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(args[0], ARG_IMAGE_MUTABLE); + image_t *arg_msk = + py_helper_keyword_to_image(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[1], ARG_IMAGE_MUTABLE), 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(args[0], ARG_IMAGE_MUTABLE); + image_t *arg_msk = + py_helper_keyword_to_image(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[1], ARG_IMAGE_MUTABLE), 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(args[0], ARG_IMAGE_MUTABLE); + 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(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[1], ARG_IMAGE_MUTABLE), 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(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + fb_alloc_mark(); + imlib_top_hat(py_helper_arg_to_image(args[0], ARG_IMAGE_MUTABLE), 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(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + fb_alloc_mark(); + imlib_black_hat(py_helper_arg_to_image(args[0], ARG_IMAGE_MUTABLE), 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(args[0], ARG_IMAGE_MUTABLE); + 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(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[0], ARG_IMAGE_MUTABLE); + 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(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[0], ARG_IMAGE_MUTABLE); + 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(n_args, args, 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[0], ARG_IMAGE_MUTABLE); + 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(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[0], ARG_IMAGE_MUTABLE); + 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(n_args, args, 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[0], ARG_IMAGE_MUTABLE); + 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(n_args, args, 8, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[0], ARG_IMAGE_MUTABLE); + 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(n_args, args, 8, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[0], ARG_IMAGE_MUTABLE); + 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(n_args, args, 8, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[0], ARG_IMAGE_MUTABLE); + 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(n_args, args, 7, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[0], ARG_IMAGE_MUTABLE); + 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(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); + + 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(args[0], ARG_IMAGE_MUTABLE); + 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(args[0], ARG_IMAGE_MUTABLE); + 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(args[0], ARG_IMAGE_MUTABLE); + 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(args[0], ARG_IMAGE_MUTABLE); + 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; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_similarity_mean_obj, py_similarity_mean); + +mp_obj_t py_similarity_stdev(mp_obj_t self_in) { + return ((py_similarity_obj_t *) self_in)->std; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_similarity_stdev_obj, py_similarity_stdev); + +mp_obj_t py_similarity_min(mp_obj_t self_in) { + return ((py_similarity_obj_t *) self_in)->min; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_similarity_min_obj, py_similarity_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_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 MP_DEFINE_CONST_OBJ_TYPE( + py_similarity_type, + MP_QSTR_similarity, + MP_TYPE_FLAG_NONE, + print, py_similarity_print, + subscr, py_similarity_subscr, + locals_dict, &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(img_obj, ARG_IMAGE_MUTABLE); + 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(other_obj, ARG_IMAGE_MUTABLE), + 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; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_mean_obj, py_statistics_mean); + +mp_obj_t py_statistics_median(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->LMedian; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_median_obj, py_statistics_median); + +mp_obj_t py_statistics_mode(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->LMode; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_mode_obj, py_statistics_mode); + +mp_obj_t py_statistics_stdev(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->LSTDev; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_stdev_obj, py_statistics_stdev); + +mp_obj_t py_statistics_min(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->LMin; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_min_obj, py_statistics_min); + +mp_obj_t py_statistics_max(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->LMax; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_max_obj, py_statistics_max); + +mp_obj_t py_statistics_lq(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->LLQ; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_lq_obj, py_statistics_lq); + +mp_obj_t py_statistics_uq(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->LUQ; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_uq_obj, py_statistics_uq); + +mp_obj_t py_statistics_l_mean(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->LMean; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_l_mean_obj, py_statistics_l_mean); + +mp_obj_t py_statistics_l_median(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->LMedian; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_l_median_obj, py_statistics_l_median); + +mp_obj_t py_statistics_l_mode(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->LMode; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_l_mode_obj, py_statistics_l_mode); + +mp_obj_t py_statistics_l_stdev(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->LSTDev; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_l_stdev_obj, py_statistics_l_stdev); + +mp_obj_t py_statistics_l_min(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->LMin; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_l_min_obj, py_statistics_l_min); + +mp_obj_t py_statistics_l_max(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->LMax; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_l_max_obj, py_statistics_l_max); + +mp_obj_t py_statistics_l_lq(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->LLQ; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_l_lq_obj, py_statistics_l_lq); + +mp_obj_t py_statistics_l_uq(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->LUQ; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_l_uq_obj, py_statistics_l_uq); + +mp_obj_t py_statistics_a_mean(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->AMean; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_a_mean_obj, py_statistics_a_mean); + +mp_obj_t py_statistics_a_median(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->AMedian; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_a_median_obj, py_statistics_a_median); + +mp_obj_t py_statistics_a_mode(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->AMode; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_a_mode_obj, py_statistics_a_mode); + +mp_obj_t py_statistics_a_stdev(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->ASTDev; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_a_stdev_obj, py_statistics_a_stdev); + +mp_obj_t py_statistics_a_min(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->AMin; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_a_min_obj, py_statistics_a_min); + +mp_obj_t py_statistics_a_max(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->AMax; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_a_max_obj, py_statistics_a_max); + +mp_obj_t py_statistics_a_lq(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->ALQ; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_a_lq_obj, py_statistics_a_lq); + +mp_obj_t py_statistics_a_uq(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->AUQ; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_a_uq_obj, py_statistics_a_uq); + +mp_obj_t py_statistics_b_mean(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->BMean; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_b_mean_obj, py_statistics_b_mean); + +mp_obj_t py_statistics_b_median(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->BMedian; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_b_median_obj, py_statistics_b_median); + +mp_obj_t py_statistics_b_mode(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->BMode; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_b_mode_obj, py_statistics_b_mode); + +mp_obj_t py_statistics_b_stdev(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->BSTDev; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_b_stdev_obj, py_statistics_b_stdev); + +mp_obj_t py_statistics_b_min(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->BMin; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_b_min_obj, py_statistics_b_min); + +mp_obj_t py_statistics_b_max(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->BMax; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_b_max_obj, py_statistics_b_max); + +mp_obj_t py_statistics_b_lq(mp_obj_t self_in) { + return ((py_statistics_obj_t *) self_in)->BLQ; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_b_lq_obj, py_statistics_b_lq); + +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_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 MP_DEFINE_CONST_OBJ_TYPE( + py_statistics_type, + MP_QSTR_statistics, + MP_TYPE_FLAG_NONE, + print, py_statistics_print, + subscr, py_statistics_subscr, + locals_dict, &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; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_percentile_value_obj, py_percentile_value); + +mp_obj_t py_percentile_l_value(mp_obj_t self_in) { + return ((py_percentile_obj_t *) self_in)->LValue; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_percentile_l_value_obj, py_percentile_l_value); + +mp_obj_t py_percentile_a_value(mp_obj_t self_in) { + return ((py_percentile_obj_t *) self_in)->AValue; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_percentile_a_value_obj, py_percentile_a_value); + +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_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 MP_DEFINE_CONST_OBJ_TYPE( + py_percentile_type, + MP_QSTR_percentile, + MP_TYPE_FLAG_NONE, + print, py_percentile_print, + subscr, py_percentile_subscr, + locals_dict, &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; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_threshold_value_obj, py_threshold_value); + +mp_obj_t py_threshold_l_value(mp_obj_t self_in) { + return ((py_threshold_obj_t *) self_in)->LValue; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_threshold_l_value_obj, py_threshold_l_value); + +mp_obj_t py_threshold_a_value(mp_obj_t self_in) { + return ((py_threshold_obj_t *) self_in)->AValue; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_threshold_a_value_obj, py_threshold_a_value); + +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_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 MP_DEFINE_CONST_OBJ_TYPE( + py_threshold_type, + MP_QSTR_threshold, + MP_TYPE_FLAG_NONE, + print, py_threshold_print, + subscr, py_threshold_subscr, + locals_dict, &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; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_histogram_bins_obj, py_histogram_bins); + +mp_obj_t py_histogram_l_bins(mp_obj_t self_in) { + return ((py_histogram_obj_t *) self_in)->LBins; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_histogram_l_bins_obj, py_histogram_l_bins); + +mp_obj_t py_histogram_a_bins(mp_obj_t self_in) { + return ((py_histogram_obj_t *) self_in)->ABins; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_histogram_a_bins_obj, py_histogram_a_bins); + +mp_obj_t py_histogram_b_bins(mp_obj_t self_in) { + return ((py_histogram_obj_t *) self_in)->BBins; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_histogram_b_bins_obj, py_histogram_b_bins); + +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; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_histogram_get_percentile_obj, py_histogram_get_percentile); + +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; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_histogram_get_threshold_obj, py_histogram_get_threshold); + +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_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 MP_DEFINE_CONST_OBJ_TYPE( + py_histogram_type, + MP_QSTR_histogram, + MP_TYPE_FLAG_NONE, + print, py_histogram_print, + subscr, py_histogram_subscr, + locals_dict, &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(args[0], ARG_IMAGE_MUTABLE); + + 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(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(args[0], ARG_IMAGE_MUTABLE); + + 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(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}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_line_line_obj, py_line_line); + +mp_obj_t py_line_x1(mp_obj_t self_in) { + return ((py_line_obj_t *) self_in)->x1; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_line_x1_obj, py_line_x1); + +mp_obj_t py_line_y1(mp_obj_t self_in) { + return ((py_line_obj_t *) self_in)->y1; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_line_y1_obj, py_line_y1); + +mp_obj_t py_line_x2(mp_obj_t self_in) { + return ((py_line_obj_t *) self_in)->x2; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_line_x2_obj, py_line_x2); + +mp_obj_t py_line_y2(mp_obj_t self_in) { + return ((py_line_obj_t *) self_in)->y2; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_line_y2_obj, py_line_y2); + +mp_obj_t py_line_length(mp_obj_t self_in) { + return ((py_line_obj_t *) self_in)->length; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_line_length_obj, py_line_length); + +mp_obj_t py_line_magnitude(mp_obj_t self_in) { + return ((py_line_obj_t *) self_in)->magnitude; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_line_magnitude_obj, py_line_magnitude); + +mp_obj_t py_line_theta(mp_obj_t self_in) { + return ((py_line_obj_t *) self_in)->theta; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_line_theta_obj, py_line_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_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 MP_DEFINE_CONST_OBJ_TYPE( + py_line_type, + MP_QSTR_line, + MP_TYPE_FLAG_NONE, + print, py_line_print, + subscr, py_line_subscr, + locals_dict, &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(args[0], ARG_IMAGE_MUTABLE); + + 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; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_corners_obj, py_blob_corners); + +mp_obj_t py_blob_min_corners(mp_obj_t self_in) { + return ((py_blob_obj_t *) self_in)->min_corners; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_min_corners_obj, py_blob_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}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_rect_obj, py_blob_rect); + +mp_obj_t py_blob_x(mp_obj_t self_in) { + return ((py_blob_obj_t *) self_in)->x; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_x_obj, py_blob_x); + +mp_obj_t py_blob_y(mp_obj_t self_in) { + return ((py_blob_obj_t *) self_in)->y; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_y_obj, py_blob_y); + +mp_obj_t py_blob_w(mp_obj_t self_in) { + return ((py_blob_obj_t *) self_in)->w; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_w_obj, py_blob_w); + +mp_obj_t py_blob_h(mp_obj_t self_in) { + return ((py_blob_obj_t *) self_in)->h; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_h_obj, py_blob_h); + +mp_obj_t py_blob_pixels(mp_obj_t self_in) { + return ((py_blob_obj_t *) self_in)->pixels; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_pixels_obj, py_blob_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))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_cx_obj, py_blob_cx); + +mp_obj_t py_blob_cxf(mp_obj_t self_in) { + return ((py_blob_obj_t *) self_in)->cx; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_cxf_obj, py_blob_cxf); + +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))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_cy_obj, py_blob_cy); + +mp_obj_t py_blob_cyf(mp_obj_t self_in) { + return ((py_blob_obj_t *) self_in)->cy; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_cyf_obj, py_blob_cyf); + +mp_obj_t py_blob_rotation(mp_obj_t self_in) { + return ((py_blob_obj_t *) self_in)->rotation; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_rotation_obj, py_blob_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))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_rotation_deg_obj, py_blob_rotation_deg); + +mp_obj_t py_blob_rotation_rad(mp_obj_t self_in) { + return ((py_blob_obj_t *) self_in)->rotation; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_rotation_rad_obj, py_blob_rotation_rad); + +mp_obj_t py_blob_code(mp_obj_t self_in) { + return ((py_blob_obj_t *) self_in)->code; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_code_obj, py_blob_code); + +mp_obj_t py_blob_count(mp_obj_t self_in) { + return ((py_blob_obj_t *) self_in)->count; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_count_obj, py_blob_count); + +mp_obj_t py_blob_perimeter(mp_obj_t self_in) { + return ((py_blob_obj_t *) self_in)->perimeter; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_perimeter_obj, py_blob_perimeter); + +mp_obj_t py_blob_roundness(mp_obj_t self_in) { + return ((py_blob_obj_t *) self_in)->roundness; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_roundness_obj, py_blob_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)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_elongation_obj, py_blob_elongation); + +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)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_area_obj, py_blob_area); + +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))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_density_obj, py_blob_density); + +// 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))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_compactness_obj, py_blob_compactness); + +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)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_solidity_obj, py_blob_solidity); + +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)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_convexity_obj, py_blob_convexity); +// 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; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_x_hist_bins_obj, py_blob_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; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_y_hist_bins_obj, py_blob_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)}); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_major_axis_line_obj, py_blob_major_axis_line); + +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)}); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_minor_axis_line_obj, py_blob_minor_axis_line); + +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))}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_enclosing_circle_obj, py_blob_enclosing_circle); + +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_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 MP_DEFINE_CONST_OBJ_TYPE( + py_blob_type, + MP_QSTR_blob, + MP_TYPE_FLAG_NONE, + print, py_blob_print, + subscr, py_blob_subscr, + locals_dict, &py_blob_locals_dict + ); + +#define NEW_CORNER_TUPLE(corners, index) \ + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(corners[(index)].x), mp_obj_new_int(corners[(index)].y)}) + +static py_blob_obj_t *py_blob_new(find_blobs_list_lnk_data_t *blob) { + point_t min_corners[4]; + + py_blob_obj_t *o = m_new_obj(py_blob_obj_t); + o->base.type = &py_blob_type; + + 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->cx = mp_obj_new_float(blob->centroid_x); + o->cy = mp_obj_new_float(blob->centroid_y); + + o->pixels = mp_obj_new_int(blob->pixels); + 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); + + o->corners = mp_obj_new_tuple(4, (mp_obj_t []) { + NEW_CORNER_TUPLE(blob->corners, ((FIND_BLOBS_CORNERS_RESOLUTION * 0) / 4)), + NEW_CORNER_TUPLE(blob->corners, ((FIND_BLOBS_CORNERS_RESOLUTION * 1) / 4)), + NEW_CORNER_TUPLE(blob->corners, ((FIND_BLOBS_CORNERS_RESOLUTION * 2) / 4)), + NEW_CORNER_TUPLE(blob->corners, ((FIND_BLOBS_CORNERS_RESOLUTION * 3) / 4)) + }); + + point_min_area_rectangle(blob->corners, min_corners, FIND_BLOBS_CORNERS_RESOLUTION); + + o->min_corners = mp_obj_new_tuple(4, (mp_obj_t []) { + NEW_CORNER_TUPLE(min_corners, 0), + NEW_CORNER_TUPLE(min_corners, 1), + NEW_CORNER_TUPLE(min_corners, 2), + NEW_CORNER_TUPLE(min_corners, 3) + }); + + 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 o; +} +static bool py_image_find_blobs_threshold_cb(void *fun_obj, find_blobs_list_lnk_data_t *blob) { + return mp_obj_is_true(mp_call_function_1(fun_obj, py_blob_new(blob))); +} + +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) { + return mp_obj_is_true(mp_call_function_2(fun_obj, py_blob_new(blob0), py_blob_new(blob1))); +} + +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(args[0], ARG_IMAGE_MUTABLE); + + 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); + objects_list->items[i] = py_blob_new(&lnk_data); + 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(args[0], ARG_IMAGE_MUTABLE); + + 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}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_circle_circle_obj, py_circle_circle); + +mp_obj_t py_circle_x(mp_obj_t self_in) { + return ((py_circle_obj_t *) self_in)->x; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_circle_x_obj, py_circle_x); + +mp_obj_t py_circle_y(mp_obj_t self_in) { + return ((py_circle_obj_t *) self_in)->y; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_circle_y_obj, py_circle_y); + +mp_obj_t py_circle_r(mp_obj_t self_in) { + return ((py_circle_obj_t *) self_in)->r; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_circle_r_obj, py_circle_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_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 MP_DEFINE_CONST_OBJ_TYPE( + py_circle_type, + MP_QSTR_circle, + MP_TYPE_FLAG_NONE, + print, py_circle_print, + subscr, py_circle_subscr, + locals_dict, &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(args[0], ARG_IMAGE_MUTABLE); + + 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; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_rect_corners_obj, py_rect_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}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_rect_rect_obj, py_rect_rect); + +mp_obj_t py_rect_x(mp_obj_t self_in) { + return ((py_rect_obj_t *) self_in)->x; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_rect_x_obj, py_rect_x); + +mp_obj_t py_rect_y(mp_obj_t self_in) { + return ((py_rect_obj_t *) self_in)->y; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_rect_y_obj, py_rect_y); + +mp_obj_t py_rect_w(mp_obj_t self_in) { + return ((py_rect_obj_t *) self_in)->w; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_rect_w_obj, py_rect_w); + +mp_obj_t py_rect_h(mp_obj_t self_in) { + return ((py_rect_obj_t *) self_in)->h; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_rect_h_obj, py_rect_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_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 MP_DEFINE_CONST_OBJ_TYPE( + py_rect_type, + MP_QSTR_rect, + MP_TYPE_FLAG_NONE, + print, py_rect_print, + subscr, py_rect_subscr, + locals_dict, &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; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_corners_obj, py_qrcode_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}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_rect_obj, py_qrcode_rect); + +mp_obj_t py_qrcode_x(mp_obj_t self_in) { + return ((py_qrcode_obj_t *) self_in)->x; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_x_obj, py_qrcode_x); + +mp_obj_t py_qrcode_y(mp_obj_t self_in) { + return ((py_qrcode_obj_t *) self_in)->y; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_y_obj, py_qrcode_y); + +mp_obj_t py_qrcode_w(mp_obj_t self_in) { + return ((py_qrcode_obj_t *) self_in)->w; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_w_obj, py_qrcode_w); + +mp_obj_t py_qrcode_h(mp_obj_t self_in) { + return ((py_qrcode_obj_t *) self_in)->h; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_h_obj, py_qrcode_h); + +mp_obj_t py_qrcode_payload(mp_obj_t self_in) { + return ((py_qrcode_obj_t *) self_in)->payload; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_payload_obj, py_qrcode_payload); + +mp_obj_t py_qrcode_version(mp_obj_t self_in) { + return ((py_qrcode_obj_t *) self_in)->version; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_version_obj, py_qrcode_version); + +mp_obj_t py_qrcode_ecc_level(mp_obj_t self_in) { + return ((py_qrcode_obj_t *) self_in)->ecc_level; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_ecc_level_obj, py_qrcode_ecc_level); + +mp_obj_t py_qrcode_mask(mp_obj_t self_in) { + return ((py_qrcode_obj_t *) self_in)->mask; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_mask_obj, py_qrcode_mask); + +mp_obj_t py_qrcode_data_type(mp_obj_t self_in) { + return ((py_qrcode_obj_t *) self_in)->data_type; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_data_type_obj, py_qrcode_data_type); + +mp_obj_t py_qrcode_eci(mp_obj_t self_in) { + return ((py_qrcode_obj_t *) self_in)->eci; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_eci_obj, py_qrcode_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); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_is_numeric_obj, py_qrcode_is_numeric); + +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); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_is_alphanumeric_obj, py_qrcode_is_alphanumeric); + +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); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_is_binary_obj, py_qrcode_is_binary); + +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_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 MP_DEFINE_CONST_OBJ_TYPE( + py_qrcode_type, + MP_QSTR_qrcode, + MP_TYPE_FLAG_NONE, + print, py_qrcode_print, + subscr, py_qrcode_subscr, + locals_dict, &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; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_corners_obj, py_apriltag_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}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_rect_obj, py_apriltag_rect); + +mp_obj_t py_apriltag_x(mp_obj_t self_in) { + return ((py_apriltag_obj_t *) self_in)->x; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_x_obj, py_apriltag_x); + +mp_obj_t py_apriltag_y(mp_obj_t self_in) { + return ((py_apriltag_obj_t *) self_in)->y; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_y_obj, py_apriltag_y); + +mp_obj_t py_apriltag_w(mp_obj_t self_in) { + return ((py_apriltag_obj_t *) self_in)->w; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_w_obj, py_apriltag_w); + +mp_obj_t py_apriltag_h(mp_obj_t self_in) { + return ((py_apriltag_obj_t *) self_in)->h; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_h_obj, py_apriltag_h); + +mp_obj_t py_apriltag_id(mp_obj_t self_in) { + return ((py_apriltag_obj_t *) self_in)->id; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_id_obj, py_apriltag_id); + +mp_obj_t py_apriltag_family(mp_obj_t self_in) { + return ((py_apriltag_obj_t *) self_in)->family; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_family_obj, py_apriltag_family); + +mp_obj_t py_apriltag_cx(mp_obj_t self_in) { + return mp_obj_new_int(fast_roundf(mp_obj_get_float(((py_apriltag_obj_t *) self_in)->cx))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_cx_obj, py_apriltag_cx); + +mp_obj_t py_apriltag_cxf(mp_obj_t self_in) { + return ((py_apriltag_obj_t *) self_in)->cx; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_cxf_obj, py_apriltag_cxf); + +mp_obj_t py_apriltag_cy(mp_obj_t self_in) { + return mp_obj_new_int(fast_roundf(mp_obj_get_float(((py_apriltag_obj_t *) self_in)->cy))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_cy_obj, py_apriltag_cy); + +mp_obj_t py_apriltag_cyf(mp_obj_t self_in) { + return ((py_apriltag_obj_t *) self_in)->cy; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_cyf_obj, py_apriltag_cyf); + +mp_obj_t py_apriltag_rotation(mp_obj_t self_in) { + return ((py_apriltag_obj_t *) self_in)->rotation; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_rotation_obj, py_apriltag_rotation); + +mp_obj_t py_apriltag_decision_margin(mp_obj_t self_in) { + return ((py_apriltag_obj_t *) self_in)->decision_margin; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_decision_margin_obj, py_apriltag_decision_margin); + +mp_obj_t py_apriltag_hamming(mp_obj_t self_in) { + return ((py_apriltag_obj_t *) self_in)->hamming; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_hamming_obj, py_apriltag_hamming); + +mp_obj_t py_apriltag_goodness(mp_obj_t self_in) { + return ((py_apriltag_obj_t *) self_in)->goodness; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_goodness_obj, py_apriltag_goodness); + +mp_obj_t py_apriltag_x_translation(mp_obj_t self_in) { + return ((py_apriltag_obj_t *) self_in)->x_translation; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_x_translation_obj, py_apriltag_x_translation); + +mp_obj_t py_apriltag_y_translation(mp_obj_t self_in) { + return ((py_apriltag_obj_t *) self_in)->y_translation; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_y_translation_obj, py_apriltag_y_translation); + +mp_obj_t py_apriltag_z_translation(mp_obj_t self_in) { + return ((py_apriltag_obj_t *) self_in)->z_translation; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_z_translation_obj, py_apriltag_z_translation); + +mp_obj_t py_apriltag_x_rotation(mp_obj_t self_in) { + return ((py_apriltag_obj_t *) self_in)->x_rotation; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_x_rotation_obj, py_apriltag_x_rotation); + +mp_obj_t py_apriltag_y_rotation(mp_obj_t self_in) { + return ((py_apriltag_obj_t *) self_in)->y_rotation; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_y_rotation_obj, py_apriltag_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_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_cxf), MP_ROM_PTR(&py_apriltag_cxf_obj) }, + { MP_ROM_QSTR(MP_QSTR_cy), MP_ROM_PTR(&py_apriltag_cy_obj) }, + { MP_ROM_QSTR(MP_QSTR_cyf), MP_ROM_PTR(&py_apriltag_cyf_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 MP_DEFINE_CONST_OBJ_TYPE( + py_apriltag_type, + MP_QSTR_apriltag, + MP_TYPE_FLAG_NONE, + print, py_apriltag_print, + subscr, py_apriltag_subscr, + locals_dict, &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; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_corners_obj, py_datamatrix_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}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_rect_obj, py_datamatrix_rect); + +mp_obj_t py_datamatrix_x(mp_obj_t self_in) { + return ((py_datamatrix_obj_t *) self_in)->x; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_x_obj, py_datamatrix_x); + +mp_obj_t py_datamatrix_y(mp_obj_t self_in) { + return ((py_datamatrix_obj_t *) self_in)->y; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_y_obj, py_datamatrix_y); + +mp_obj_t py_datamatrix_w(mp_obj_t self_in) { + return ((py_datamatrix_obj_t *) self_in)->w; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_w_obj, py_datamatrix_w); + +mp_obj_t py_datamatrix_h(mp_obj_t self_in) { + return ((py_datamatrix_obj_t *) self_in)->h; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_h_obj, py_datamatrix_h); + +mp_obj_t py_datamatrix_payload(mp_obj_t self_in) { + return ((py_datamatrix_obj_t *) self_in)->payload; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_payload_obj, py_datamatrix_payload); + +mp_obj_t py_datamatrix_rotation(mp_obj_t self_in) { + return ((py_datamatrix_obj_t *) self_in)->rotation; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_rotation_obj, py_datamatrix_rotation); + +mp_obj_t py_datamatrix_rows(mp_obj_t self_in) { + return ((py_datamatrix_obj_t *) self_in)->rows; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_rows_obj, py_datamatrix_rows); + +mp_obj_t py_datamatrix_columns(mp_obj_t self_in) { + return ((py_datamatrix_obj_t *) self_in)->columns; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_columns_obj, py_datamatrix_columns); + +mp_obj_t py_datamatrix_capacity(mp_obj_t self_in) { + return ((py_datamatrix_obj_t *) self_in)->capacity; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_capacity_obj, py_datamatrix_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_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 MP_DEFINE_CONST_OBJ_TYPE( + py_datamatrix_type, + MP_QSTR_datamatrix, + MP_TYPE_FLAG_NONE, + print, py_datamatrix_print, + subscr, py_datamatrix_subscr, + locals_dict, &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; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_barcode_corners_obj, py_barcode_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}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_barcode_rect_obj, py_barcode_rect); + +mp_obj_t py_barcode_x(mp_obj_t self_in) { + return ((py_barcode_obj_t *) self_in)->x; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_barcode_x_obj, py_barcode_x); + +mp_obj_t py_barcode_y(mp_obj_t self_in) { + return ((py_barcode_obj_t *) self_in)->y; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_barcode_y_obj, py_barcode_y); + +mp_obj_t py_barcode_w(mp_obj_t self_in) { + return ((py_barcode_obj_t *) self_in)->w; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_barcode_w_obj, py_barcode_w); + +mp_obj_t py_barcode_h(mp_obj_t self_in) { + return ((py_barcode_obj_t *) self_in)->h; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_barcode_h_obj, py_barcode_h); + +mp_obj_t py_barcode_payload_fun(mp_obj_t self_in) { + return ((py_barcode_obj_t *) self_in)->payload; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_barcode_payload_fun_obj, py_barcode_payload_fun); + +mp_obj_t py_barcode_type_fun(mp_obj_t self_in) { + return ((py_barcode_obj_t *) self_in)->type; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_barcode_type_fun_obj, py_barcode_type_fun); + +mp_obj_t py_barcode_rotation_fun(mp_obj_t self_in) { + return ((py_barcode_obj_t *) self_in)->rotation; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_barcode_rotation_fun_obj, py_barcode_rotation_fun); + +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_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 MP_DEFINE_CONST_OBJ_TYPE( + py_barcode_type, + MP_QSTR_barcode, + MP_TYPE_FLAG_NONE, + print, py_barcode_print, + subscr, py_barcode_subscr, + locals_dict, &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; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_displacement_x_translation_obj, py_displacement_x_translation); + +mp_obj_t py_displacement_y_translation(mp_obj_t self_in) { + return ((py_displacement_obj_t *) self_in)->y_translation; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_displacement_y_translation_obj, py_displacement_y_translation); + +mp_obj_t py_displacement_rotation(mp_obj_t self_in) { + return ((py_displacement_obj_t *) self_in)->rotation; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_displacement_rotation_obj, py_displacement_rotation); + +mp_obj_t py_displacement_scale(mp_obj_t self_in) { + return ((py_displacement_obj_t *) self_in)->scale; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_displacement_scale_obj, py_displacement_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_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 MP_DEFINE_CONST_OBJ_TYPE( + py_displacement_type, + MP_QSTR_displacement, + MP_TYPE_FLAG_NONE, + print, py_displacement_print, + subscr, py_displacement_subscr, + locals_dict, &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(args[0], ARG_IMAGE_MUTABLE); + image_t *arg_template_img = py_helper_arg_to_image(args[1], ARG_IMAGE_MUTABLE); + + 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(args[0], ARG_IMAGE_GRAYSCALE); + image_t *arg_template = py_helper_arg_to_image(args[1], ARG_IMAGE_GRAYSCALE); + 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(args[0], ARG_IMAGE_MUTABLE); + 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; i < array_length(objects_array); i++) { + rectangle_t *r = array_at(objects_array, i); + 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), + }; + 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(args[0], ARG_IMAGE_GRAYSCALE); + + 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(args[0], ARG_IMAGE_GRAYSCALE); + + 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(args[0], ARG_IMAGE_MUTABLE); + + 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(args[0], ARG_IMAGE_GRAYSCALE); + 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(args[0], ARG_IMAGE_GRAYSCALE); + + 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(args[0], ARG_IMAGE_MUTABLE); + 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; i < array_length(proposals_array); i++) { + rectangle_t *r = array_at(proposals_array, i); + 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), + }; + 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 + +#ifdef IMLIB_ENABLE_STEREO_DISPARITY +static mp_obj_t py_image_stereo_disparity(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { + image_t *img = py_helper_arg_to_image(args[0], ARG_IMAGE_GRAYSCALE); + + if (img->w % 2) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Image width must be even!")); + } + + int reversed = py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_reversed), false); + int max_disparity = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_max_disparity), 64); + int threshold = py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), 64); + + if ((max_disparity < 1) || (255 < max_disparity)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("1 <= max_disparity <= 255!")); + } + + if (threshold < 0) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("0 <= threshold!")); + } + + fb_alloc_mark(); + imlib_stereo_disparity(img, reversed, max_disparity, threshold); + fb_alloc_free_till_mark(); + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_stereo_disparity_obj, 1, py_image_stereo_disparity); +#endif // IMLIB_ENABLE_STEREO_DISPARITY + +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_to_ironbow), MP_ROM_PTR(&py_image_to_ironbow_obj)}, + {MP_ROM_QSTR(MP_QSTR_to_jpeg), MP_ROM_PTR(&py_image_to_jpeg_obj)}, + {MP_ROM_QSTR(MP_QSTR_to_png), MP_ROM_PTR(&py_image_to_png_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_compress_for_ide_obj)}, + {MP_ROM_QSTR(MP_QSTR_jpeg_encoded_for_ide), MP_ROM_PTR(&py_image_compressed_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)}, + /* ISP Methods */ + #ifdef IMLIB_ENABLE_ISP_OPS + {MP_ROM_QSTR(MP_QSTR_awb), MP_ROM_PTR(&py_awb_obj)}, + {MP_ROM_QSTR(MP_QSTR_ccm), MP_ROM_PTR(&py_ccm_obj)}, + {MP_ROM_QSTR(MP_QSTR_gamma), MP_ROM_PTR(&py_image_gamma_obj)}, + {MP_ROM_QSTR(MP_QSTR_gamma_corr), MP_ROM_PTR(&py_image_gamma_obj)}, + #else + {MP_ROM_QSTR(MP_QSTR_awb), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_ccm), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_gamma), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_gamma_corr), MP_ROM_PTR(&py_func_unavailable_obj)}, + #endif // IMLIB_ENABLE_ISP_OPS + /* 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_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 + #ifdef IMLIB_ENABLE_STEREO_DISPARITY + {MP_ROM_QSTR(MP_QSTR_stereo_disparity), MP_ROM_PTR(&py_image_stereo_disparity_obj)}, + #else + {MP_ROM_QSTR(MP_QSTR_stereo_disparity), MP_ROM_PTR(&py_func_unavailable_obj)}, + #endif +}; + +STATIC MP_DEFINE_CONST_DICT(py_image_locals_dict, locals_dict_table); + +STATIC MP_DEFINE_CONST_OBJ_TYPE( + py_image_type, + MP_QSTR_Image, + MP_TYPE_FLAG_ITER_IS_GETITER, + print, py_image_print, + buffer, py_image_get_buffer, + subscr, py_image_subscr, + iter, py_image_getiter, + unary_op, py_image_unary_op, + locals_dict, &py_image_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 *pos_args, mp_map_t *kw_args) { + enum { ARG_height, ARG_pixformat, ARG_buffer, ARG_copy_to_fb }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_height, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_pixformat, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_buffer, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_copy_to_fb, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + image_t image = {0}; + + if (mp_obj_is_str(pos_args[0])) { + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + FIL fp; + img_read_settings_t rs; + const char *path = mp_obj_str_get_str(pos_args[0]); + + fb_alloc_mark(); + imlib_read_geometry(&fp, &image, path, &rs); + file_close(&fp); + + if (args[ARG_copy_to_fb].u_bool) { + py_helper_set_to_framebuffer(&image); + } else { + image.data = xalloc(image_size(&image)); + } + + imlib_load_image(&image, path); + fb_alloc_free_till_mark(); + #else + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Image I/O is not supported")); + #endif // IMLIB_ENABLE_IMAGE_FILE_IO + } else { + image.w = mp_obj_get_int(pos_args[0]); + PY_ASSERT_TRUE_MSG(image.w > 0, "Image width must be > 0"); + + image.h = args[ARG_height].u_int; + PY_ASSERT_TRUE_MSG(image.h > 0, "Image height must be > 0"); + + image.pixfmt = args[ARG_pixformat].u_int; + PY_ASSERT_TRUE_MSG(IMLIB_PIXFORMAT_IS_VALID(image.pixfmt), "Pixel format is not set or unsupported"); + + mp_buffer_info_t bufinfo = {0}; + if (args[ARG_buffer].u_obj != mp_const_none) { + mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_READ); + image.size = bufinfo.len; + } else if (image.is_compressed) { + mp_raise_ValueError(MP_ERROR_TEXT("Expected an image buffer")); + } + + if (args[ARG_copy_to_fb].u_bool) { + py_helper_set_to_framebuffer(&image); + if (bufinfo.buf != NULL) { + memcpy(image.data, bufinfo.buf, bufinfo.len); + } else { + memset(image.data, 0, image_size(&image)); + } + } else if (bufinfo.buf != NULL) { + image.data = bufinfo.buf; + } else { + image.data = xalloc0(image_size(&image)); + } + } + + if (args[ARG_copy_to_fb].u_bool) { + 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; + FRESULT res = FR_OK; + + uint32_t desc_type; + mp_obj_t desc = mp_const_none; + const char *path = mp_obj_str_get_str(args[0]); + + file_open(&fp, path, false, FA_READ | FA_OPEN_EXISTING); + + // Read descriptor type + file_read(&fp, &desc_type, sizeof(desc_type)); + + // 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 + } + + file_close(&fp); + + // File read 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; + FRESULT res = FR_OK; + + uint32_t desc_type; + const char *path = mp_obj_str_get_str(args[1]); + + file_open(&fp, path, false, FA_WRITE | FA_CREATE_ALWAYS); + + // 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 + file_write(&fp, &desc_type, sizeof(desc_type)); + + // 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 + file_close(&fp); + + // File write 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; i < count * 2; i += 2) { + mp_obj_t index_obj[2] = { + mp_obj_new_int(match[i + 0]), + mp_obj_new_int(match[i + 1]), + }; + mp_obj_list_append(match_list, mp_obj_new_tuple(2, index_obj)); + } + + // Free match list + fb_alloc_free_till_mark(); + + if (filter_outliers == true) { + count = orb_filter_keypoints(kpts2->kpts, &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; + array_t *kpts = orb_find_keypoints(img, false, 20, 1.5f, 100, CORNER_AGAST, roi); + if (array_length(kpts)) { + file_open(&fp, path, false, FA_WRITE | FA_CREATE_ALWAYS); + FRESULT res = orb_save_descriptor(&fp, kpts); + file_close(&fp); + // File 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_PNG), MP_ROM_INT(PIXFORMAT_PNG)}, /* PNG/COMPRESSED*/ + {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_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_HMIRROR), MP_ROM_INT(IMAGE_HINT_HMIRROR)}, + {MP_ROM_QSTR(MP_QSTR_VFLIP), MP_ROM_INT(IMAGE_HINT_VFLIP)}, + {MP_ROM_QSTR(MP_QSTR_TRANSPOSE), MP_ROM_INT(IMAGE_HINT_TRANSPOSE)}, + {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)}, + {MP_ROM_QSTR(MP_QSTR_SCALE_ASPECT_KEEP), MP_ROM_INT(IMAGE_HINT_SCALE_ASPECT_KEEP)}, + {MP_ROM_QSTR(MP_QSTR_SCALE_ASPECT_EXPAND), MP_ROM_INT(IMAGE_HINT_SCALE_ASPECT_EXPAND)}, + {MP_ROM_QSTR(MP_QSTR_SCALE_ASPECT_IGNORE), MP_ROM_INT(IMAGE_HINT_SCALE_ASPECT_IGNORE)}, + {MP_ROM_QSTR(MP_QSTR_ROTATE_90), MP_ROM_INT(IMAGE_HINT_VFLIP | IMAGE_HINT_TRANSPOSE)}, + {MP_ROM_QSTR(MP_QSTR_ROTATE_180), MP_ROM_INT(IMAGE_HINT_HMIRROR | IMAGE_HINT_VFLIP)}, + {MP_ROM_QSTR(MP_QSTR_ROTATE_270), MP_ROM_INT(IMAGE_HINT_HMIRROR | IMAGE_HINT_TRANSPOSE)}, + #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); diff --git a/components/3rd_party/omv/omv/modules/py_image.h b/components/3rd_party/omv/omv/modules/py_image.h new file mode 100644 index 00000000..93dd9f12 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/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/components/3rd_party/omv/omv/modules/py_imageio.c b/components/3rd_party/omv/omv/modules/py_imageio.c new file mode 100644 index 00000000..c8ede2b7 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_imageio.c @@ -0,0 +1,616 @@ +/* + * 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 "file_utils.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; + + file_write_long(fp, elapsed_ms); + file_write_long(fp, image->w); + file_write_long(fp, image->h); + + char padding[ALIGN_SIZE] = {}; + + if (stream->version < NEW_PIXFORMAT_VER) { + if (image->pixfmt == PIXFORMAT_BINARY) { + file_write_long(fp, OLD_BINARY_BPP); + } else if (image->pixfmt == PIXFORMAT_GRAYSCALE) { + file_write_long(fp, OLD_GRAYSCALE_BPP); + } else if (image->pixfmt == PIXFORMAT_RGB565) { + file_write_long(fp, OLD_RGB565_BPP); + } else if (image->pixfmt == PIXFORMAT_BAYER) { + file_write_long(fp, OLD_BAYER_BPP); + } else if (image->pixfmt == PIXFORMAT_JPEG) { + file_write_long(fp, image->size); + } else { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid image stream bpp")); + } + } else { + file_write_long(fp, image->pixfmt); + file_write_long(fp, image->size); + file_write(fp, padding, AFTER_SIZE_PADDING); + } + + uint32_t size = image_size(image); + file_write(fp, image->data, size); + + if (size % ALIGN_SIZE) { + file_write(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) { + file_read(&stream->fp, &elapsed_ms, 4); + #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); + + file_read(fp, &image->w, 4); + file_read(fp, &image->h, 4); + + uint32_t bpp; + file_read(fp, &bpp, 4); + + 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; + file_read(fp, &image->size, 4); + + char ignore[AFTER_SIZE_PADDING]; + file_read(fp, ignore, AFTER_SIZE_PADDING); + } +} +#endif + +STATIC mp_obj_t py_imageio_read(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_copy_to_fb, ARG_loop, ARG_pause }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_copy_to_fb, MP_ARG_INT, {.u_bool = true } }, + { MP_QSTR_loop, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = true } }, + { MP_QSTR_pause, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = true } }, + }; + + // Parse args. + py_imageio_obj_t *stream = py_imageio_obj(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + image_t image = { 0 }; + + 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 (args[ARG_loop].u_bool == false) { + return mp_const_none; + } + // Skip the header + file_seek(fp, MAGIC_SIZE); + + stream->offset = 0; + + if (f_eof(fp)) { + // Empty file + return mp_const_none; + } + } + + int_py_imageio_read_chunk(stream, &image, args[ARG_pause].u_bool); + #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, args[ARG_pause].u_bool); + memcpy(&image, stream->buffer + (stream->offset * stream->size) + sizeof(uint32_t), sizeof(image_t)); + } + + uint32_t size = image_size(&image); + + if (args[ARG_copy_to_fb].u_bool) { + py_helper_set_to_framebuffer(&image); + } 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; + file_read(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]; + file_read(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 (args[ARG_copy_to_fb].u_bool) { + 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_with_finaliser(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_open(fp, mp_obj_str_get_str(args[0]), false, FA_READ | FA_WRITE | FA_OPEN_ALWAYS); + 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) { + file_write(fp, string, sizeof(string) - 1); // exclude null terminator + } else { + uint8_t version_hi, period, version_lo; + char temp[sizeof(string) - 3] = {}; + file_read(fp, temp, sizeof(temp) - 1); + file_read(fp, &version_hi, 1); + file_read(fp, &period, 1); + file_read(fp, &version_lo, 1); + 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); + file_write(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_open(fp, mp_obj_str_get_str(args[0]), false, FA_READ | FA_WRITE | FA_OPEN_EXISTING); + file_read_check(fp, "OMV IMG STR ", 12); // Magic + file_read_check(fp, "V", 1); + file_read(fp, &version_hi, 1); + file_read_check(fp, ".", 1); + file_read(fp, &version_lo, 1); + + 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___del__), MP_ROM_PTR(&py_imageio_close_obj) }, + { 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); + +MP_DEFINE_CONST_OBJ_TYPE( + py_imageio_type, + MP_QSTR_ImageIO, + MP_TYPE_FLAG_NONE, + print, py_imageio_print, + make_new, py_imageio_make_new, + locals_dict, &py_imageio_locals_dict + ); +#endif // IMLIB_ENABLE_IMAGE_IO diff --git a/components/3rd_party/omv/omv/modules/py_imageio.h b/components/3rd_party/omv/omv/modules/py_imageio.h new file mode 100644 index 00000000..2b26009d --- /dev/null +++ b/components/3rd_party/omv/omv/modules/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/components/3rd_party/omv/omv/modules/py_mjpeg.c b/components/3rd_party/omv/omv/modules/py_mjpeg.c new file mode 100644 index 00000000..6915f6db --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_mjpeg.c @@ -0,0 +1,210 @@ +/* + * 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. + * + * MJPEG Python module. + */ +#include "imlib_config.h" +#if defined(IMLIB_ENABLE_IMAGE_FILE_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 "file_utils.h" +#include "framebuffer.h" +#include "omv_boardconfig.h" + +static const mp_obj_type_t py_mjpeg_type; + +typedef struct py_mjpeg_obj { + mp_obj_base_t base; + uint32_t frames; + uint32_t bytes; + uint32_t width; + uint32_t height; + bool closed; + FIL fp; +} py_mjpeg_obj_t; + +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 = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "{\"closed\":%s, \"width\":%u, \"height\":%u, \"count\":%u, \"size\":%u}", + self->closed ? "\"true\"" : "\"false\"", + self->width, + self->height, + self->frames, + f_size(&self->fp)); +} + +STATIC mp_obj_t py_mjpeg_is_closed(mp_obj_t self_in) { + py_mjpeg_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->closed); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_mjpeg_is_closed_obj, py_mjpeg_is_closed); + +STATIC mp_obj_t py_mjpeg_width(mp_obj_t self_in) { + py_mjpeg_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->width); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_mjpeg_width_obj, py_mjpeg_width); + +STATIC mp_obj_t py_mjpeg_height(mp_obj_t self_in) { + py_mjpeg_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->height); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_mjpeg_height_obj, py_mjpeg_height); + +STATIC mp_obj_t py_mjpeg_count(mp_obj_t self_in) { + py_mjpeg_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->frames); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_mjpeg_count_obj, py_mjpeg_count); + +STATIC mp_obj_t py_mjpeg_size(mp_obj_t self_in) { + py_mjpeg_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(f_size(&self->fp)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_mjpeg_size_obj, py_mjpeg_size); + +STATIC mp_obj_t py_mjpeg_write(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_roi, ARG_channel, ARG_alpha, ARG_color_palette, ARG_alpha_palette, ARG_hint, ARG_quality }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_roi, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_rgb_channel, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + { MP_QSTR_alpha, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 256 } }, + { MP_QSTR_color_palette, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_alpha_palette, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_hint, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0 } }, + { MP_QSTR_quality, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 90 } }, + }; + + // Parse args. + py_mjpeg_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 2, pos_args + 2, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Sanity checks + if (self->closed) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("MJPEG stream is closed")); + } + + if (args[ARG_channel].u_int < -1 || args[ARG_channel].u_int > 2) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("RGB channel can be 0, 1, or 2")); + } + + if (args[ARG_alpha].u_int < 0 || args[ARG_alpha].u_int > 256) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Alpha ranges between 0 and 256")); + } + + if (args[ARG_quality].u_int < 0 || args[ARG_quality].u_int > 100) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Quality ranges between 0 and 100")); + } + + image_t *image = py_helper_arg_to_image(pos_args[1], ARG_IMAGE_ANY); + rectangle_t roi = py_helper_arg_to_roi(args[ARG_roi].u_obj, image); + + const uint16_t *color_palette = py_helper_arg_to_palette(args[ARG_color_palette].u_obj, PIXFORMAT_RGB565); + const uint8_t *alpha_palette = py_helper_arg_to_palette(args[ARG_alpha_palette].u_obj, PIXFORMAT_GRAYSCALE); + + mjpeg_write(&self->fp, self->width, self->height, &self->frames, &self->bytes, + image, args[ARG_quality].u_int, &roi, args[ARG_channel].u_int, + args[ARG_alpha].u_int, color_palette, alpha_palette, args[ARG_hint].u_int); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_mjpeg_write_obj, 2, py_mjpeg_write); + +STATIC mp_obj_t py_mjpeg_sync(mp_obj_t self_in, mp_obj_t fps_obj) { + py_mjpeg_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->closed) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("MJPEG stream is closed")); + } + mjpeg_sync(&self->fp, &self->frames, &self->bytes, mp_obj_get_float(fps_obj)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_mjpeg_sync_obj, py_mjpeg_sync); + +STATIC mp_obj_t py_mjpeg_close(mp_obj_t self_in, mp_obj_t fps_obj) { + py_mjpeg_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (!self->closed) { + mjpeg_close(&self->fp, &self->frames, &self->bytes, mp_obj_get_float(fps_obj)); + } + self->closed = true; + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_mjpeg_close_obj, py_mjpeg_close); + +STATIC mp_obj_t py_mjpeg_open(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_width, ARG_height }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_width, MP_ARG_INT, {.u_int = -1 } }, + { MP_QSTR_height, MP_ARG_INT, {.u_int = -1 } }, + }; + + // Parse args. + const char *path = mp_obj_str_get_str(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + py_mjpeg_obj_t *mjpeg = m_new_obj_with_finaliser(py_mjpeg_obj_t); + mjpeg->base.type = &py_mjpeg_type; + mjpeg->frames = 0; + mjpeg->bytes = 0; + mjpeg->width = (args[ARG_width].u_int == -1) ? framebuffer_get_width() : args[ARG_width].u_int; + mjpeg->height = (args[ARG_height].u_int == -1) ? framebuffer_get_height() : args[ARG_height].u_int; + mjpeg->closed = false; + + file_open(&mjpeg->fp, path, false, FA_WRITE | FA_CREATE_ALWAYS); + mjpeg_open(&mjpeg->fp, mjpeg->width, mjpeg->height); + return mjpeg; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_mjpeg_open_obj, 1, py_mjpeg_open); + +STATIC const mp_rom_map_elem_t py_mjpeg_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_Mjpeg) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&py_mjpeg_close_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_closed), MP_ROM_PTR(&py_mjpeg_is_closed_obj) }, + { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&py_mjpeg_width_obj) }, + { MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&py_mjpeg_height_obj) }, + { MP_ROM_QSTR(MP_QSTR_count), MP_ROM_PTR(&py_mjpeg_count_obj) }, + { MP_ROM_QSTR(MP_QSTR_size), MP_ROM_PTR(&py_mjpeg_size_obj) }, + { MP_ROM_QSTR(MP_QSTR_add_frame), MP_ROM_PTR(&py_mjpeg_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&py_mjpeg_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_sync), MP_ROM_PTR(&py_mjpeg_sync_obj) }, + { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&py_mjpeg_close_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(py_mjpeg_locals_dict, py_mjpeg_locals_dict_table); + +STATIC MP_DEFINE_CONST_OBJ_TYPE( + py_mjpeg_type, + MP_QSTR_Mjpeg, + MP_TYPE_FLAG_NONE, + print, py_mjpeg_print, + locals_dict, &py_mjpeg_locals_dict + ); + +STATIC const mp_rom_map_elem_t globals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_mjpeg) }, + { MP_ROM_QSTR(MP_QSTR_Mjpeg), MP_ROM_PTR(&py_mjpeg_open_obj) }, +}; + +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); +#endif // IMLIB_ENABLE_IMAGE_FILE_IO diff --git a/components/3rd_party/omv/omv/modules/py_omv.c b/components/3rd_party/omv/omv/modules/py_omv.c new file mode 100644 index 00000000..6d9561f1 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_omv.c @@ -0,0 +1,79 @@ +/* + * 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); diff --git a/components/3rd_party/omv/omv/modules/py_sensor.c b/components/3rd_party/omv/omv/modules/py_sensor.c new file mode 100644 index 00000000..1e95afd8 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_sensor.c @@ -0,0 +1,1224 @@ +/* + * 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 +#include "py/mphal.h" +#include "py/runtime.h" + +#if MICROPY_PY_SENSOR + +#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 "omv_i2c.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)) +#define sensor_print_error(op) printf("\x1B[31mWARNING: %s control is not supported by this image sensor.\x1B[0m\n", op); + +#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_DEFINE_CONST_FUN_OBJ_0(py_sensor__init__obj, py_sensor__init__); + +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_DEFINE_CONST_FUN_OBJ_0(py_sensor_reset_obj, py_sensor_reset); + +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_DEFINE_CONST_FUN_OBJ_1(py_sensor_sleep_obj, py_sensor_sleep); + +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_DEFINE_CONST_FUN_OBJ_1(py_sensor_shutdown_obj, py_sensor_shutdown); + +static mp_obj_t py_sensor_flush() { + framebuffer_update_jpeg_buffer(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_flush_obj, py_sensor_flush); + +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_DEFINE_CONST_FUN_OBJ_KW(py_sensor_snapshot_obj, 0, py_sensor_snapshot); + +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_ROM_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_DEFINE_CONST_FUN_OBJ_KW(py_sensor_skip_frames_obj, 0, py_sensor_skip_frames); + +static mp_obj_t py_sensor_width() { + return mp_obj_new_int(resolution[sensor.framesize][0]); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_width_obj, py_sensor_width); + +static mp_obj_t py_sensor_height() { + return mp_obj_new_int(resolution[sensor.framesize][1]); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_height_obj, py_sensor_height); + +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_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_fb_obj, py_sensor_get_fb); + +static mp_obj_t py_sensor_get_id() { + return mp_obj_new_int(sensor_get_id()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_id_obj, py_sensor_get_id); + +static mp_obj_t py_sensor_get_frame_available() { + return mp_obj_new_bool(framebuffer->tail != framebuffer->head); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_frame_available_obj, py_sensor_get_frame_available); + +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_DEFINE_CONST_FUN_OBJ_3(py_sensor_alloc_extra_fb_obj, py_sensor_alloc_extra_fb); + +static mp_obj_t py_sensor_dealloc_extra_fb() { + fb_alloc_free_till_mark_past_mark_permanent(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_dealloc_extra_fb_obj, py_sensor_dealloc_extra_fb); + +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_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_pixformat_obj, py_sensor_set_pixformat); + +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_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_pixformat_obj, py_sensor_get_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_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_framesize_obj, py_sensor_set_framesize); + +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_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_framesize_obj, py_sensor_get_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_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_framerate_obj, py_sensor_set_framerate); + +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_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_framerate_obj, py_sensor_get_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_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_sensor_set_windowing_obj, 1, 4, py_sensor_set_windowing); + +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_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_windowing_obj, py_sensor_get_windowing); + +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_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_gainceiling_obj, py_sensor_set_gainceiling); + +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_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_brightness_obj, py_sensor_set_brightness); + +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_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_contrast_obj, py_sensor_set_contrast); + +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_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_saturation_obj, py_sensor_set_saturation); + +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_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_quality_obj, py_sensor_set_quality); + +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_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_colorbar_obj, py_sensor_set_colorbar); + +static mp_obj_t py_sensor_set_auto_gain(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_gain_db, ARG_gain_db_ceiling }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_gain_db, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_gain_db_ceiling, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + }; + + // Parse args. + int enable = mp_obj_get_int(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + float gain_db = py_helper_arg_to_float(args[ARG_gain_db].u_obj, NAN); + float gain_db_ceiling = py_helper_arg_to_float(args[ARG_gain_db_ceiling].u_obj, NAN); + + int error = sensor_set_auto_gain(enable, gain_db, gain_db_ceiling); + if (error != 0) { + if (error != SENSOR_ERROR_CTL_UNSUPPORTED) { + sensor_raise_error(error); + } + sensor_print_error("Auto Gain"); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_sensor_set_auto_gain_obj, 1, py_sensor_set_auto_gain); + +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_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_gain_db_obj, py_sensor_get_gain_db); + +static mp_obj_t py_sensor_set_auto_exposure(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_exposure_us }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_exposure_us, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1} }, + }; + + // Parse args. + int enable = mp_obj_get_int(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + int error = sensor_set_auto_exposure(enable, args[ARG_exposure_us].u_int); + if (error != 0) { + if (error != SENSOR_ERROR_CTL_UNSUPPORTED) { + sensor_raise_error(error); + } + sensor_print_error("Auto Exposure"); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_sensor_set_auto_exposure_obj, 1, py_sensor_set_auto_exposure); + +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_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_exposure_us_obj, py_sensor_get_exposure_us); + +static mp_obj_t py_sensor_set_auto_whitebal(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_rgb_gain_db }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_rgb_gain_db, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + }; + + // Parse args. + int enable = mp_obj_get_int(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + float rgb_gain_db[3] = {NAN, NAN, NAN}; + py_helper_arg_to_float_array(args[ARG_rgb_gain_db].u_obj, 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) { + if (error != SENSOR_ERROR_CTL_UNSUPPORTED) { + sensor_raise_error(error); + } + sensor_print_error("Auto White Balance"); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_sensor_set_auto_whitebal_obj, 1, py_sensor_set_auto_whitebal); + +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_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_rgb_gain_db_obj, py_sensor_get_rgb_gain_db); + +static mp_obj_t py_sensor_set_auto_blc(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_enable, ARG_regs }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_enable, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_regs, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + int enable = args[ARG_enable].u_int; + int regs[sensor.hw_flags.blc_size]; + bool regs_present = args[ARG_regs].u_obj != mp_const_none; + if (regs_present) { + mp_obj_t *arg_array; + mp_obj_get_array_fixed_n(args[ARG_regs].u_obj, sensor.hw_flags.blc_size, &arg_array); + for (uint32_t i = 0; i < sensor.hw_flags.blc_size; i++) { + regs[i] = mp_obj_get_int(arg_array[i]); + } + } + + int error = sensor_set_auto_blc(enable, regs_present ? regs : NULL); + if (error != 0) { + if (error != SENSOR_ERROR_CTL_UNSUPPORTED) { + sensor_raise_error(error); + } + sensor_print_error("Auto BLC"); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_sensor_set_auto_blc_obj, 1, py_sensor_set_auto_blc); + +static mp_obj_t py_sensor_get_blc_regs() { + int regs[sensor.hw_flags.blc_size]; + int error = sensor_get_blc_regs(regs); + if (error != 0) { + sensor_raise_error(error); + } + + mp_obj_list_t *l = mp_obj_new_list(sensor.hw_flags.blc_size, NULL); + for (uint32_t i = 0; i < sensor.hw_flags.blc_size; i++) { + l->items[i] = mp_obj_new_int(regs[i]); + } + return l; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_blc_regs_obj, py_sensor_get_blc_regs); + +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_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_hmirror_obj, py_sensor_set_hmirror); + +static mp_obj_t py_sensor_get_hmirror() { + return mp_obj_new_bool(sensor_get_hmirror()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_hmirror_obj, py_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_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_vflip_obj, py_sensor_set_vflip); + +static mp_obj_t py_sensor_get_vflip() { + return mp_obj_new_bool(sensor_get_vflip()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_vflip_obj, py_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_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_transpose_obj, py_sensor_set_transpose); + +static mp_obj_t py_sensor_get_transpose() { + return mp_obj_new_bool(sensor_get_transpose()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_transpose_obj, py_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_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_auto_rotation_obj, py_sensor_set_auto_rotation); + +static mp_obj_t py_sensor_get_auto_rotation() { + return mp_obj_new_bool(sensor_get_auto_rotation()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_auto_rotation_obj, py_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_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_framebuffers_obj, py_sensor_set_framebuffers); + +static mp_obj_t py_sensor_get_framebuffers() { + return mp_obj_new_int(framebuffer->n_buffers); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_framebuffers_obj, py_sensor_get_framebuffers); + +static mp_obj_t py_sensor_disable_delays(uint n_args, const mp_obj_t *args) { + if (!n_args) { + return mp_obj_new_bool(sensor.disable_delays); + } + + sensor.disable_delays = mp_obj_get_int(args[0]); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_sensor_disable_delays_obj, 0, 1, py_sensor_disable_delays); + +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_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_sensor_disable_full_flush_obj, 0, 1, py_sensor_disable_full_flush); + +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_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_special_effect_obj, py_sensor_set_special_effect); + +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 MP_DEFINE_CONST_FUN_OBJ_3(py_sensor_set_lens_correction_obj, py_sensor_set_lens_correction); + +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 MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_vsync_callback_obj, py_sensor_set_vsync_callback); + +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_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_frame_callback_obj, py_sensor_set_frame_callback); + +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: + case IOCTL_SET_FOV_WIDE: + case IOCTL_SET_NIGHT_MODE: { + if (n_args >= 2) { + error = sensor_ioctl(request, mp_obj_get_int(args[1])); + } + break; + } + + case IOCTL_GET_TRIGGERED_MODE: + case IOCTL_GET_FOV_WIDE: + case IOCTL_GET_NIGHT_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) { + int high_temp = (n_args == 2) ? false : mp_obj_get_int(args[2]); + error = sensor_ioctl(request, mp_obj_get_int(args[1]), high_temp); + } + break; + + case IOCTL_LEPTON_GET_MEASUREMENT_MODE: { + int enabled, high_temp; + error = sensor_ioctl(request, &enabled, &high_temp); + if (error == 0) { + ret_obj = mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_bool(enabled), mp_obj_new_bool(high_temp)}); + } + 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_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_sensor_ioctl_obj, 1, 5, py_sensor_ioctl); + +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_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_color_palette_obj, py_sensor_set_color_palette); + +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_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_color_palette_obj, py_sensor_get_color_palette); + +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_DEFINE_CONST_FUN_OBJ_2(py_sensor_write_reg_obj, py_sensor_write_reg); + +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_1(py_sensor_read_reg_obj, py_sensor_read_reg); + +STATIC const mp_rom_map_elem_t globals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_sensor)}, + + // 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*/ + + // Image Sensors + { MP_ROM_QSTR(MP_QSTR_OV2640), MP_ROM_INT(OV2640_ID)}, + { MP_ROM_QSTR(MP_QSTR_OV5640), MP_ROM_INT(OV5640_ID)}, + { MP_ROM_QSTR(MP_QSTR_OV7670), MP_ROM_INT(OV7670_ID)}, + { MP_ROM_QSTR(MP_QSTR_OV7690), MP_ROM_INT(OV7690_ID)}, + { MP_ROM_QSTR(MP_QSTR_OV7725), MP_ROM_INT(OV7725_ID)}, + { MP_ROM_QSTR(MP_QSTR_OV9650), MP_ROM_INT(OV9650_ID)}, + { MP_ROM_QSTR(MP_QSTR_MT9V022), MP_ROM_INT(MT9V0X2_ID)}, + { MP_ROM_QSTR(MP_QSTR_MT9V024), MP_ROM_INT(MT9V0X4_ID)}, + { MP_ROM_QSTR(MP_QSTR_MT9V032), MP_ROM_INT(MT9V0X2_ID)}, + { MP_ROM_QSTR(MP_QSTR_MT9V034), MP_ROM_INT(MT9V0X4_ID)}, + { MP_ROM_QSTR(MP_QSTR_MT9M114), MP_ROM_INT(MT9M114_ID)}, + { MP_ROM_QSTR(MP_QSTR_LEPTON), MP_ROM_INT(LEPTON_ID)}, + { MP_ROM_QSTR(MP_QSTR_HM01B0), MP_ROM_INT(HM01B0_ID)}, + { MP_ROM_QSTR(MP_QSTR_GC2145), MP_ROM_INT(GC2145_ID)}, + { MP_ROM_QSTR(MP_QSTR_PAJ6100), MP_ROM_INT(PAJ6100_ID)}, + { MP_ROM_QSTR(MP_QSTR_FROGEYE2020), MP_ROM_INT(FROGEYE2020_ID)}, + + // Special effects + { MP_ROM_QSTR(MP_QSTR_NORMAL), MP_ROM_INT(SDE_NORMAL)}, /* Normal/No SDE */ + { MP_ROM_QSTR(MP_QSTR_NEGATIVE), MP_ROM_INT(SDE_NEGATIVE)}, /* Negative image */ + + // C/SIF Resolutions + { MP_ROM_QSTR(MP_QSTR_QQCIF), MP_ROM_INT(FRAMESIZE_QQCIF)}, /* 88x72 */ + { MP_ROM_QSTR(MP_QSTR_QCIF), MP_ROM_INT(FRAMESIZE_QCIF)}, /* 176x144 */ + { MP_ROM_QSTR(MP_QSTR_CIF), MP_ROM_INT(FRAMESIZE_CIF)}, /* 352x288 */ + { MP_ROM_QSTR(MP_QSTR_QQSIF), MP_ROM_INT(FRAMESIZE_QQSIF)}, /* 88x60 */ + { MP_ROM_QSTR(MP_QSTR_QSIF), MP_ROM_INT(FRAMESIZE_QSIF)}, /* 176x120 */ + { MP_ROM_QSTR(MP_QSTR_SIF), MP_ROM_INT(FRAMESIZE_SIF)}, /* 352x240 */ + // VGA Resolutions + { MP_ROM_QSTR(MP_QSTR_QQQQVGA), MP_ROM_INT(FRAMESIZE_QQQQVGA)}, /* 40x30 */ + { MP_ROM_QSTR(MP_QSTR_QQQVGA), MP_ROM_INT(FRAMESIZE_QQQVGA)}, /* 80x60 */ + { MP_ROM_QSTR(MP_QSTR_QQVGA), MP_ROM_INT(FRAMESIZE_QQVGA)}, /* 160x120 */ + { MP_ROM_QSTR(MP_QSTR_QVGA), MP_ROM_INT(FRAMESIZE_QVGA)}, /* 320x240 */ + { MP_ROM_QSTR(MP_QSTR_VGA), MP_ROM_INT(FRAMESIZE_VGA)}, /* 640x480 */ + { MP_ROM_QSTR(MP_QSTR_HQQQQVGA), MP_ROM_INT(FRAMESIZE_HQQQQVGA)}, /* 40x20 */ + { MP_ROM_QSTR(MP_QSTR_HQQQVGA), MP_ROM_INT(FRAMESIZE_HQQQVGA)}, /* 80x40 */ + { MP_ROM_QSTR(MP_QSTR_HQQVGA), MP_ROM_INT(FRAMESIZE_HQQVGA)}, /* 160x80 */ + { MP_ROM_QSTR(MP_QSTR_HQVGA), MP_ROM_INT(FRAMESIZE_HQVGA)}, /* 240x160 */ + { MP_ROM_QSTR(MP_QSTR_HVGA), MP_ROM_INT(FRAMESIZE_HVGA)}, /* 480x320 */ + // FFT Resolutions + { MP_ROM_QSTR(MP_QSTR_B64X32), MP_ROM_INT(FRAMESIZE_64X32)}, /* 64x32 */ + { MP_ROM_QSTR(MP_QSTR_B64X64), MP_ROM_INT(FRAMESIZE_64X64)}, /* 64x64 */ + { MP_ROM_QSTR(MP_QSTR_B128X64), MP_ROM_INT(FRAMESIZE_128X64)}, /* 128x64 */ + { MP_ROM_QSTR(MP_QSTR_B128X128), MP_ROM_INT(FRAMESIZE_128X128)}, /* 128x128 */ + // Himax Resolutions + { MP_ROM_QSTR(MP_QSTR_B160X160), MP_ROM_INT(FRAMESIZE_160X160)}, /* 160x160 */ + { MP_ROM_QSTR(MP_QSTR_B320X320), MP_ROM_INT(FRAMESIZE_320X320)}, /* 320x320 */ + // Other Resolutions + { MP_ROM_QSTR(MP_QSTR_LCD), MP_ROM_INT(FRAMESIZE_LCD)}, /* 128x160 */ + { MP_ROM_QSTR(MP_QSTR_QQVGA2), MP_ROM_INT(FRAMESIZE_QQVGA2)}, /* 128x160 */ + { MP_ROM_QSTR(MP_QSTR_WVGA), MP_ROM_INT(FRAMESIZE_WVGA)}, /* 720x480 */ + { MP_ROM_QSTR(MP_QSTR_WVGA2), MP_ROM_INT(FRAMESIZE_WVGA2)}, /* 752x480 */ + { MP_ROM_QSTR(MP_QSTR_SVGA), MP_ROM_INT(FRAMESIZE_SVGA)}, /* 800x600 */ + { MP_ROM_QSTR(MP_QSTR_XGA), MP_ROM_INT(FRAMESIZE_XGA)}, /* 1024x768 */ + { MP_ROM_QSTR(MP_QSTR_WXGA), MP_ROM_INT(FRAMESIZE_WXGA)}, /* 1280x768 */ + { MP_ROM_QSTR(MP_QSTR_SXGA), MP_ROM_INT(FRAMESIZE_SXGA)}, /* 1280x1024 */ + { MP_ROM_QSTR(MP_QSTR_SXGAM), MP_ROM_INT(FRAMESIZE_SXGAM)}, /* 1280x960 */ + { MP_ROM_QSTR(MP_QSTR_UXGA), MP_ROM_INT(FRAMESIZE_UXGA)}, /* 1600x1200 */ + { MP_ROM_QSTR(MP_QSTR_HD), MP_ROM_INT(FRAMESIZE_HD)}, /* 1280x720 */ + { MP_ROM_QSTR(MP_QSTR_FHD), MP_ROM_INT(FRAMESIZE_FHD)}, /* 1920x1080 */ + { MP_ROM_QSTR(MP_QSTR_QHD), MP_ROM_INT(FRAMESIZE_QHD)}, /* 2560x1440 */ + { MP_ROM_QSTR(MP_QSTR_QXGA), MP_ROM_INT(FRAMESIZE_QXGA)}, /* 2048x1536 */ + { MP_ROM_QSTR(MP_QSTR_WQXGA), MP_ROM_INT(FRAMESIZE_WQXGA)}, /* 2560x1600 */ + { MP_ROM_QSTR(MP_QSTR_WQXGA2), MP_ROM_INT(FRAMESIZE_WQXGA2)}, /* 2592x1944 */ + + // Framebuffer Sizes + { MP_ROM_QSTR(MP_QSTR_SINGLE_BUFFER), MP_ROM_INT(1)}, + { MP_ROM_QSTR(MP_QSTR_DOUBLE_BUFFER), MP_ROM_INT(2)}, + { MP_ROM_QSTR(MP_QSTR_TRIPLE_BUFFER), MP_ROM_INT(3)}, + { MP_ROM_QSTR(MP_QSTR_VIDEO_FIFO), MP_ROM_INT(4)}, + + // IOCTLs + { MP_ROM_QSTR(MP_QSTR_IOCTL_SET_READOUT_WINDOW), MP_ROM_INT(IOCTL_SET_READOUT_WINDOW)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_GET_READOUT_WINDOW), MP_ROM_INT(IOCTL_GET_READOUT_WINDOW)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_SET_TRIGGERED_MODE), MP_ROM_INT(IOCTL_SET_TRIGGERED_MODE)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_GET_TRIGGERED_MODE), MP_ROM_INT(IOCTL_GET_TRIGGERED_MODE)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_SET_FOV_WIDE), MP_ROM_INT(IOCTL_SET_FOV_WIDE)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_GET_FOV_WIDE), MP_ROM_INT(IOCTL_GET_FOV_WIDE)}, + #if (OMV_ENABLE_OV5640_AF == 1) + { MP_ROM_QSTR(MP_QSTR_IOCTL_TRIGGER_AUTO_FOCUS), MP_ROM_INT(IOCTL_TRIGGER_AUTO_FOCUS)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_PAUSE_AUTO_FOCUS), MP_ROM_INT(IOCTL_PAUSE_AUTO_FOCUS)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_RESET_AUTO_FOCUS), MP_ROM_INT(IOCTL_RESET_AUTO_FOCUS)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_WAIT_ON_AUTO_FOCUS), MP_ROM_INT(IOCTL_WAIT_ON_AUTO_FOCUS)}, + #endif + { MP_ROM_QSTR(MP_QSTR_IOCTL_SET_NIGHT_MODE), MP_ROM_INT(IOCTL_SET_NIGHT_MODE)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_GET_NIGHT_MODE), MP_ROM_INT(IOCTL_GET_NIGHT_MODE)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_GET_WIDTH), MP_ROM_INT(IOCTL_LEPTON_GET_WIDTH)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_GET_HEIGHT), MP_ROM_INT(IOCTL_LEPTON_GET_HEIGHT)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_GET_RADIOMETRY), MP_ROM_INT(IOCTL_LEPTON_GET_RADIOMETRY)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_GET_REFRESH), MP_ROM_INT(IOCTL_LEPTON_GET_REFRESH)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_GET_RESOLUTION), MP_ROM_INT(IOCTL_LEPTON_GET_RESOLUTION)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_RUN_COMMAND), MP_ROM_INT(IOCTL_LEPTON_RUN_COMMAND)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_SET_ATTRIBUTE), MP_ROM_INT(IOCTL_LEPTON_SET_ATTRIBUTE)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_GET_ATTRIBUTE), MP_ROM_INT(IOCTL_LEPTON_GET_ATTRIBUTE)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_GET_FPA_TEMPERATURE), MP_ROM_INT(IOCTL_LEPTON_GET_FPA_TEMPERATURE)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_GET_AUX_TEMPERATURE), MP_ROM_INT(IOCTL_LEPTON_GET_AUX_TEMPERATURE)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_SET_MEASUREMENT_MODE), MP_ROM_INT(IOCTL_LEPTON_SET_MEASUREMENT_MODE)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_GET_MEASUREMENT_MODE), MP_ROM_INT(IOCTL_LEPTON_GET_MEASUREMENT_MODE)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_SET_MEASUREMENT_RANGE), MP_ROM_INT(IOCTL_LEPTON_SET_MEASUREMENT_RANGE)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_GET_MEASUREMENT_RANGE), MP_ROM_INT(IOCTL_LEPTON_GET_MEASUREMENT_RANGE)}, + #if (OMV_ENABLE_HM01B0 == 1) + { MP_ROM_QSTR(MP_QSTR_IOCTL_HIMAX_MD_ENABLE), MP_ROM_INT(IOCTL_HIMAX_MD_ENABLE)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_HIMAX_MD_WINDOW), MP_ROM_INT(IOCTL_HIMAX_MD_WINDOW)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_HIMAX_MD_THRESHOLD), MP_ROM_INT(IOCTL_HIMAX_MD_THRESHOLD)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_HIMAX_MD_CLEAR), MP_ROM_INT(IOCTL_HIMAX_MD_CLEAR)}, + { MP_ROM_QSTR(MP_QSTR_IOCTL_HIMAX_OSC_ENABLE), MP_ROM_INT(IOCTL_HIMAX_OSC_ENABLE)}, + #endif + + // Sensor functions + { MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&py_sensor__init__obj) }, + { MP_ROM_QSTR(MP_QSTR_reset), MP_ROM_PTR(&py_sensor_reset_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&py_sensor_sleep_obj) }, + { MP_ROM_QSTR(MP_QSTR_shutdown), MP_ROM_PTR(&py_sensor_shutdown_obj) }, + { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&py_sensor_flush_obj) }, + { MP_ROM_QSTR(MP_QSTR_snapshot), MP_ROM_PTR(&py_sensor_snapshot_obj) }, + { MP_ROM_QSTR(MP_QSTR_skip_frames), MP_ROM_PTR(&py_sensor_skip_frames_obj) }, + { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&py_sensor_width_obj) }, + { MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&py_sensor_height_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_fb), MP_ROM_PTR(&py_sensor_get_fb_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_id), MP_ROM_PTR(&py_sensor_get_id_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_frame_available), MP_ROM_PTR(&py_sensor_get_frame_available_obj) }, + { MP_ROM_QSTR(MP_QSTR_alloc_extra_fb), MP_ROM_PTR(&py_sensor_alloc_extra_fb_obj) }, + { MP_ROM_QSTR(MP_QSTR_dealloc_extra_fb), MP_ROM_PTR(&py_sensor_dealloc_extra_fb_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_pixformat), MP_ROM_PTR(&py_sensor_set_pixformat_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_pixformat), MP_ROM_PTR(&py_sensor_get_pixformat_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_framesize), MP_ROM_PTR(&py_sensor_set_framesize_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_framesize), MP_ROM_PTR(&py_sensor_get_framesize_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_framerate), MP_ROM_PTR(&py_sensor_set_framerate_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_framerate), MP_ROM_PTR(&py_sensor_get_framerate_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_windowing), MP_ROM_PTR(&py_sensor_set_windowing_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_windowing), MP_ROM_PTR(&py_sensor_get_windowing_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_gainceiling), MP_ROM_PTR(&py_sensor_set_gainceiling_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_contrast), MP_ROM_PTR(&py_sensor_set_contrast_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_brightness), MP_ROM_PTR(&py_sensor_set_brightness_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_saturation), MP_ROM_PTR(&py_sensor_set_saturation_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_quality), MP_ROM_PTR(&py_sensor_set_quality_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_colorbar), MP_ROM_PTR(&py_sensor_set_colorbar_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_auto_gain), MP_ROM_PTR(&py_sensor_set_auto_gain_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_gain_db), MP_ROM_PTR(&py_sensor_get_gain_db_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_auto_exposure), MP_ROM_PTR(&py_sensor_set_auto_exposure_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_exposure_us), MP_ROM_PTR(&py_sensor_get_exposure_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_auto_whitebal), MP_ROM_PTR(&py_sensor_set_auto_whitebal_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_rgb_gain_db), MP_ROM_PTR(&py_sensor_get_rgb_gain_db_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_auto_blc), MP_ROM_PTR(&py_sensor_set_auto_blc_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_blc_regs), MP_ROM_PTR(&py_sensor_get_blc_regs_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_hmirror), MP_ROM_PTR(&py_sensor_set_hmirror_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_hmirror), MP_ROM_PTR(&py_sensor_get_hmirror_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_vflip), MP_ROM_PTR(&py_sensor_set_vflip_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_vflip), MP_ROM_PTR(&py_sensor_get_vflip_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_transpose), MP_ROM_PTR(&py_sensor_set_transpose_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_transpose), MP_ROM_PTR(&py_sensor_get_transpose_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_auto_rotation), MP_ROM_PTR(&py_sensor_set_auto_rotation_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_auto_rotation), MP_ROM_PTR(&py_sensor_get_auto_rotation_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_framebuffers), MP_ROM_PTR(&py_sensor_set_framebuffers_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_framebuffers), MP_ROM_PTR(&py_sensor_get_framebuffers_obj) }, + { MP_ROM_QSTR(MP_QSTR_disable_delays), MP_ROM_PTR(&py_sensor_disable_delays_obj) }, + { MP_ROM_QSTR(MP_QSTR_disable_full_flush), MP_ROM_PTR(&py_sensor_disable_full_flush_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_special_effect), MP_ROM_PTR(&py_sensor_set_special_effect_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_lens_correction), MP_ROM_PTR(&py_sensor_set_lens_correction_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_vsync_callback), MP_ROM_PTR(&py_sensor_set_vsync_callback_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_frame_callback), MP_ROM_PTR(&py_sensor_set_frame_callback_obj) }, + { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&py_sensor_ioctl_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_color_palette), MP_ROM_PTR(&py_sensor_set_color_palette_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_color_palette), MP_ROM_PTR(&py_sensor_get_color_palette_obj) }, + { MP_ROM_QSTR(MP_QSTR___write_reg), MP_ROM_PTR(&py_sensor_write_reg_obj) }, + { MP_ROM_QSTR(MP_QSTR___read_reg), MP_ROM_PTR(&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); +#endif // MICROPY_PY_SENSOR diff --git a/components/3rd_party/omv/omv/modules/py_spi_display.c b/components/3rd_party/omv/omv/modules/py_spi_display.c new file mode 100644 index 00000000..f4230aa6 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_spi_display.c @@ -0,0 +1,408 @@ +/* + * 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. + * + * SPI Display Python module. + */ +#include "omv_boardconfig.h" + +#if MICROPY_PY_DISPLAY && defined(OMV_SPI_DISPLAY_CONTROLLER) + +#include "py/obj.h" +#include "py/nlr.h" +#include "py/runtime.h" +#include "mphal.h" + +#include "py_image.h" +#include "omv_gpio.h" +#include "omv_spi.h" +#include "py_display.h" + +#define LCD_COMMAND_DISPOFF (0x28) +#define LCD_COMMAND_DISPON (0x29) +#define LCD_COMMAND_RAMWR (0x2C) +#define LCD_COMMAND_SLPOUT (0x11) +#define LCD_COMMAND_MADCTL (0x36) +#define LCD_COMMAND_COLMOD (0x3A) + +#if OMV_SPI_DISPLAY_TRIPLE_BUFFER +#define LCD_TRIPLE_BUFFER_DEFAULT (true) +#else +#define LCD_TRIPLE_BUFFER_DEFAULT (false) +#endif + +static void spi_transmit(py_display_obj_t *self, uint8_t *txdata, uint16_t size) { + omv_spi_transfer_t spi_xfer = { + .txbuf = txdata, + .size = size, + .timeout = OMV_SPI_MAX_TIMEOUT, + .flags = OMV_SPI_XFER_BLOCKING + }; + + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 0); + omv_spi_transfer_start(&self->spi_bus, &spi_xfer); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1); +} + +static void spi_transmit_16(py_display_obj_t *self, uint8_t *txdata, uint16_t size) { + omv_spi_transfer_t spi_xfer = { + .txbuf = txdata, + .size = (!self->byte_swap) ? size : (size * 2), + .timeout = OMV_SPI_MAX_TIMEOUT, + .flags = OMV_SPI_XFER_BLOCKING, + }; + + omv_spi_transfer_start(&self->spi_bus, &spi_xfer); +} + +static void spi_switch_mode(py_display_obj_t *self, int bits, bool dma) { + omv_spi_deinit(&self->spi_bus); + + omv_spi_config_t spi_config; + omv_spi_default_config(&spi_config, OMV_SPI_DISPLAY_CONTROLLER); + + spi_config.baudrate = self->spi_baudrate; + spi_config.datasize = bits; + spi_config.bus_mode = OMV_SPI_BUS_TX; + spi_config.nss_enable = false; + spi_config.dma_flags = dma ? OMV_SPI_DMA_NORMAL : 0; + omv_spi_init(&self->spi_bus, &spi_config); +} + +static void spi_display_command(py_display_obj_t *self, uint8_t cmd, uint8_t arg) { + omv_gpio_write(OMV_SPI_DISPLAY_RS_PIN, 0); + spi_transmit(self, (uint8_t []) { cmd }, 1); + omv_gpio_write(OMV_SPI_DISPLAY_RS_PIN, 1); + if (arg) { + spi_transmit(self, (uint8_t []) { arg }, 1); + } +} + +static void spi_display_callback(omv_spi_t *spi, void *userdata, void *buf) { + py_display_obj_t *self = (py_display_obj_t *) userdata; + + static uint8_t *spi_state_write_addr = NULL; + static size_t spi_state_write_count = 0; + + // If userdata is not null then it means that we are being kicked off. + if (buf == NULL) { + spi_state_write_count = 0; + } + + if (!spi_state_write_count) { + spi_state_write_addr = (uint8_t *) self->framebuffers[self->framebuffer_tail]; + spi_state_write_count = self->width * self->height; + + if (self->byte_swap) { + spi_state_write_count *= 2; + } + + self->framebuffer_head = self->framebuffer_tail; + } + + size_t spi_state_write_limit = (!self->byte_swap) ? OMV_SPI_MAX_16BIT_XFER : OMV_SPI_MAX_8BIT_XFER; + uint8_t *addr = spi_state_write_addr; + size_t count = IM_MIN(spi_state_write_count, spi_state_write_limit); + + spi_state_write_addr += (!self->byte_swap) ? (count * 2) : count; + spi_state_write_count -= count; + + // When starting the interrupt chain the first transfer is not executed in interrupt context. + // So, disable interrupts for the first transfer so that it completes first and unlocks the + // SPI bus before allowing the interrupt it causes to trigger starting the interrupt chain. + omv_spi_transfer_t spi_xfer = { + .txbuf = addr, + .size = count, + .flags = OMV_SPI_XFER_DMA, + .userdata = self, + .callback = spi_display_callback, + }; + + if (buf == NULL) { + uint32_t irq_state = disable_irq(); + omv_spi_transfer_start(&self->spi_bus, &spi_xfer); + enable_irq(irq_state); + } else { + omv_spi_transfer_start(&self->spi_bus, &spi_xfer); + } +} + +static void spi_display_kick(py_display_obj_t *self) { + if (!self->spi_tx_running) { + spi_display_command(self, LCD_COMMAND_RAMWR, 0); + spi_switch_mode(self, (!self->byte_swap) ? 16 : 8, true); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 0); + + // Limit the transfer size to single lines as you cannot send more + // than 64KB per SPI transaction generally. + for (int i = 0; i < self->height; i++) { + uint8_t *buffer = (uint8_t *) (self->framebuffers[self->framebuffer_tail] + (self->width * i)); + spi_transmit_16(self, buffer, self->width); + } + + spi_switch_mode(self, 8, false); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1); + spi_display_command(self, LCD_COMMAND_DISPON, 0); + spi_display_command(self, LCD_COMMAND_RAMWR, 0); + spi_switch_mode(self, (!self->byte_swap) ? 16 : 8, true); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 0); + + // Kickoff interrupt driven image update. + self->spi_tx_running = true; + spi_display_callback(&self->spi_bus, self, NULL); + } +} + +static void spi_display_draw_image_cb(int x_start, int x_end, int y_row, imlib_draw_row_data_t *data) { + py_display_obj_t *lcd_self = (py_display_obj_t *) data->callback_arg; + spi_transmit_16(lcd_self, data->dst_row_override, lcd_self->width); +} + +static void spi_display_write(py_display_obj_t *self, 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) { + image_t dst_img; + dst_img.w = self->width; + dst_img.h = self->height; + dst_img.pixfmt = PIXFORMAT_RGB565; + + point_t p0, p1; + imlib_draw_image_get_bounds(&dst_img, src_img, dst_x_start, dst_y_start, x_scale, + y_scale, roi, alpha, alpha_palette, hint, &p0, &p1); + bool black = p0.x == -1; + + if (!self->triple_buffer) { + dst_img.data = fb_alloc0(self->width * sizeof(uint16_t), FB_ALLOC_NO_HINT); + + spi_display_command(self, LCD_COMMAND_RAMWR, 0); + spi_switch_mode(self, (!self->byte_swap) ? 16 : 8, true); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 0); + + if (black) { + // zero the whole image + for (int i = 0; i < self->height; i++) { + spi_transmit_16(self, dst_img.data, self->width); + } + } else { + // Zero the top rows + for (int i = 0; i < p0.y; i++) { + spi_transmit_16(self, dst_img.data, self->width); + } + + // Transmits left/right parts already zeroed... + imlib_draw_image(&dst_img, src_img, dst_x_start, dst_y_start, + x_scale, y_scale, roi, rgb_channel, alpha, color_palette, alpha_palette, + hint | IMAGE_HINT_BLACK_BACKGROUND, spi_display_draw_image_cb, self, dst_img.data); + + // Zero the bottom rows + if (p1.y < self->height) { + memset(dst_img.data, 0, self->width * sizeof(uint16_t)); + } + + for (int i = p1.y; i < self->height; i++) { + spi_transmit_16(self, dst_img.data, self->width); + } + } + + spi_switch_mode(self, 8, false); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1); + spi_display_command(self, LCD_COMMAND_DISPON, 0); + if (dst_img.data) fb_free(dst_img.data); + } else { + // For triple buffering we are never drawing where tail or head + // (which may instantly update to to be equal to tail) is. + int new_framebuffer_tail = (self->framebuffer_tail + 1) % FRAMEBUFFER_COUNT; + if (new_framebuffer_tail == self->framebuffer_head) { + new_framebuffer_tail = (new_framebuffer_tail + 1) % FRAMEBUFFER_COUNT; + } + dst_img.data = (uint8_t *) self->framebuffers[new_framebuffer_tail]; + + if (black) { + // zero the whole image + memset(dst_img.data, 0, self->width * self->height * sizeof(uint16_t)); + } else { + // Zero the top rows + if (p0.y) { + memset(dst_img.data, 0, self->width * p0.y * sizeof(uint16_t)); + } + + if (p0.x) { + for (int i = p0.y; i < p1.y; i++) { + // Zero left + memset(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&dst_img, i), 0, p0.x * sizeof(uint16_t)); + } + } + + imlib_draw_image(&dst_img, src_img, dst_x_start, dst_y_start, + x_scale, y_scale, roi, rgb_channel, alpha, color_palette, + alpha_palette, hint | IMAGE_HINT_BLACK_BACKGROUND, NULL, NULL, NULL); + + if (self->width - p1.x) { + for (int i = p0.y; i < p1.y; i++) { + // Zero right + memset(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&dst_img, i) + p1.x, 0, + (self->width - p1.x) * sizeof(uint16_t)); + } + } + + // Zero the bottom rows + if (self->height - p1.y) { + memset(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&dst_img, p1.y), + 0, self->width * (self->height - p1.y) * sizeof(uint16_t)); + } + } + + #ifdef __DCACHE_PRESENT + // Flush data for DMA + SCB_CleanDCache_by_Addr((uint32_t *) dst_img.data, image_size(&dst_img)); + #endif + + // Update tail which means a new image is ready. + self->framebuffer_tail = new_framebuffer_tail; + + // Kick off an update of the display. + spi_display_kick(self); + } +} + +static void spi_display_clear(py_display_obj_t *self, bool display_off) { + if (display_off) { + // turns the display off (may not be black) + if (self->spi_tx_running) { + omv_spi_transfer_abort(&self->spi_bus); + self->spi_tx_running = false; + spi_switch_mode(self, 8, false); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1); + } + } else { + spi_display_command(self, LCD_COMMAND_DISPOFF, 0); + fb_alloc_mark(); + spi_display_write(self, NULL, 0, 0, 1.f, 1.f, NULL, 0, 0, NULL, NULL, 0); + fb_alloc_free_till_mark(); + } +} + +#ifdef OMV_SPI_DISPLAY_BL_PIN +static void spi_display_set_backlight(py_display_obj_t *self, uint32_t intensity) { + omv_gpio_config(OMV_SPI_DISPLAY_BL_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); + omv_gpio_write(OMV_SPI_DISPLAY_BL_PIN, !!intensity); +} +#endif + +static void spi_display_deinit(py_display_obj_t *self) { + if (self->triple_buffer) { + omv_spi_transfer_abort(&self->spi_bus); + fb_alloc_free_till_mark_past_mark_permanent(); + } + + omv_spi_deinit(&self->spi_bus); + omv_gpio_deinit(OMV_SPI_DISPLAY_RS_PIN); + omv_gpio_deinit(OMV_SPI_DISPLAY_RST_PIN); + #ifdef OMV_SPI_DISPLAY_BL_PIN + omv_gpio_deinit(OMV_SPI_DISPLAY_BL_PIN); + #endif +} + +mp_obj_t spi_display_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_width, ARG_height, ARG_refresh, ARG_bgr, ARG_byte_swap, ARG_triple_buffer, ARG_backlight }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_width, MP_ARG_INT, {.u_int = 128 } }, + { MP_QSTR_height, MP_ARG_INT, {.u_int = 160 } }, + { MP_QSTR_refresh, MP_ARG_INT, {.u_int = 60 } }, + { MP_QSTR_bgr, MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_byte_swap, MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_triple_buffer, MP_ARG_BOOL, {.u_bool = LCD_TRIPLE_BUFFER_DEFAULT} }, + { MP_QSTR_backlight, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if ((args[ARG_width].u_int <= 0) || (args[ARG_width].u_int > 32767)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid Width!")); + } + if ((args[ARG_height].u_int <= 0) || (args[ARG_height].u_int > 32767)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid Height!")); + } + if ((args[ARG_refresh].u_int < 30) || (args[ARG_refresh].u_int > 120)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid Refresh Rate!")); + } + + py_display_obj_t *self = m_new_obj_with_finaliser(py_display_obj_t); + self->base.type = &py_spi_display_type; + self->framebuffer_tail = 0; + self->framebuffer_head = 0; + self->width = args[ARG_width].u_int; + self->height = args[ARG_height].u_int; + self->refresh = args[ARG_refresh].u_int; + self->triple_buffer = args[ARG_triple_buffer].u_bool; + self->bgr = args[ARG_bgr].u_bool; + self->byte_swap = args[ARG_byte_swap].u_bool; + self->bl_controller = args[ARG_backlight].u_obj; + + omv_spi_config_t spi_config; + omv_spi_default_config(&spi_config, OMV_SPI_DISPLAY_CONTROLLER); + + self->spi_baudrate = self->width * self->height * self->refresh * 16; + spi_config.baudrate = self->spi_baudrate; + spi_config.bus_mode = OMV_SPI_BUS_TX; + spi_config.nss_enable = false; + omv_spi_init(&self->spi_bus, &spi_config); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1); + + omv_gpio_config(OMV_SPI_DISPLAY_RST_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); + omv_gpio_write(OMV_SPI_DISPLAY_RST_PIN, 1); + + omv_gpio_config(OMV_SPI_DISPLAY_RS_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); + omv_gpio_write(OMV_SPI_DISPLAY_RS_PIN, 1); + + // Reset LCD + omv_gpio_write(OMV_SPI_DISPLAY_RST_PIN, 0); + mp_hal_delay_ms(100); + omv_gpio_write(OMV_SPI_DISPLAY_RST_PIN, 1); + mp_hal_delay_ms(100); + + // Sleep out + spi_display_command(self, LCD_COMMAND_SLPOUT, 0); + mp_hal_delay_ms(120); + // Memory data access control + spi_display_command(self, LCD_COMMAND_MADCTL, self->bgr ? 0xC8 : 0xC0); + // Interface pixel format + spi_display_command(self, LCD_COMMAND_COLMOD, 0x05); + + if (self->triple_buffer) { + fb_alloc_mark(); + uint32_t fb_size = self->width * self->height * sizeof(uint16_t); + for (int i = 0; i < FRAMEBUFFER_COUNT; i++) { + self->framebuffers[i] = (uint16_t *) fb_alloc0(fb_size, FB_ALLOC_CACHE_ALIGN); + } + fb_alloc_mark_permanent(); + } + + return MP_OBJ_FROM_PTR(self); +} + +STATIC const py_display_p_t py_display_p = { + .deinit = spi_display_deinit, + .clear = spi_display_clear, + .write = spi_display_write, + #ifdef OMV_SPI_DISPLAY_BL_PIN + .set_backlight = spi_display_set_backlight, + #endif +}; + +MP_DEFINE_CONST_OBJ_TYPE( + py_spi_display_type, + MP_QSTR_SPIDisplay, + MP_TYPE_FLAG_NONE, + make_new, spi_display_make_new, + protocol, &py_display_p, + locals_dict, &py_display_locals_dict + ); + +#endif // MICROPY_PY_DISPLAY diff --git a/components/3rd_party/omv/omv/modules/py_tf.c b/components/3rd_party/omv/omv/modules/py_tf.c new file mode 100644 index 00000000..7cc6c96d --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_tf.c @@ -0,0 +1,895 @@ +/* + * 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 +#include "py/runtime.h" +#include "py/obj.h" +#include "py/objlist.h" +#include "py/objtuple.h" +#include "py/binary.h" + +#include "py_helper.h" +#include "imlib_config.h" + +#include "ulab/code/ulab.h" +#include "ulab/code/ndarray.h" + +#ifdef IMLIB_ENABLE_TF +#include "py_image.h" +#include "file_utils.h" +#include "py_tf.h" +#include "libtf_builtin_models.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); + +MP_DEFINE_CONST_OBJ_TYPE( + py_tf_classification_type, + MP_QSTR_tf_classification, + MP_TYPE_FLAG_NONE, + print, py_tf_classification_print, + subscr, py_tf_classification_subscr, + locals_dict, &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; + tf_model->model_data = NULL; + + for (int i = 0; i < MP_ARRAY_SIZE(libtf_builtin_models); i++) { + const libtf_builtin_model_t *model = &libtf_builtin_models[i]; + if (!strcmp(path, model->name)) { + tf_model->model_data = (unsigned char *) model->data; + tf_model->model_data_len = model->size; + } + } + + if (tf_model->model_data == NULL) { + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + FIL fp; + file_open(&fp, path, false, FA_READ | FA_OPEN_EXISTING); + 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); + file_read(&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); + } + + if (tensor_arena) fb_free(tensor_arena); // free fb_alloc_all() + + if (!helper_mode) { + if (py_tf_putchar_buffer) { + fb_free(py_tf_putchar_buffer); // free py_tf_alloc_putchar_buffer() + py_tf_putchar_buffer = NULL; + } + } + + // 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 *pos_args, mp_map_t *kw_args) { + enum { ARG_load_to_fb }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_load_to_fb, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = false } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + return int_py_tf_load(pos_args[0], args[ARG_load_to_fb].u_int, false); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_tf_load_obj, 1, py_tf_load); + +STATIC mp_obj_t py_tf_load_builtin_model(mp_obj_t path_obj) { + mp_obj_t net = int_py_tf_load(path_obj, false, false); + const char *path = mp_obj_str_get_str(path_obj); + mp_obj_t labels = mp_obj_new_list(0, NULL); + + for (int i = 0; i < MP_ARRAY_SIZE(libtf_builtin_models); i++) { + const libtf_builtin_model_t *model = &libtf_builtin_models[i]; + if (!strcmp(path, model->name)) { + for (int l = 0; l < model->n_labels; l++) { + const char *label = model->labels[l]; + mp_obj_list_append(labels, mp_obj_new_str(label, strlen(label))); + } + break; + } + } + return mp_obj_new_tuple(2, (mp_obj_t []) {labels, net}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_load_builtin_model_obj, py_tf_load_builtin_model); + +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); + } +} + +STATIC mp_obj_t py_tf_regression(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { + fb_alloc_mark(); + py_tf_alloc_putchar_buffer(); + + // read model + py_tf_model_obj_t *model = py_tf_load_alloc(args[0]); + + // read input(2D or 1D) and output size(1D) + size_t input_size_width = (&model->params)->input_width; + size_t input_size_height = (&model->params)->input_height; + size_t output_size = (&model->params)->output_channels; + + // read input + ndarray_obj_t *arg_input_array = args[1]; + + // check for the input size + if ((input_size_width * input_size_height) != arg_input_array->len) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Input array size is not same as model input size!")); + } + float *input_array = (float *) (arg_input_array->array); + + uint8_t *tensor_arena = fb_alloc(model->params.tensor_arena_size, FB_ALLOC_PREFER_SPEED | FB_ALLOC_CACHE_ALIGN); + + + float output_data[output_size]; + + // predict the output using tflite model + if (libtf_regression(model->model_data, + tensor_arena, &model->params, input_array, output_data) != 0) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Coundnt execute the model to predict the output")); + } + + // read output + mp_obj_list_t *out = (mp_obj_list_t *) mp_obj_new_list(output_size, NULL); + for (size_t j = 0; j < (output_size); j++) { + out->items[j] = mp_obj_new_float(output_data[j]); + } + + fb_alloc_free_till_mark(); + return out; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_tf_regression_obj, 2, py_tf_regression); + +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, 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 if (params->output_datatype == LIBTF_DATATYPE_INT8) { + for (int i = 0, ii = params->output_channels; i < ii; i++) { + ((mp_obj_list_t *) arg->out)->items[i] = + mp_obj_new_float( ((float) (((int8_t *) model_output)[i] - params->output_zero_point)) * params->output_scale); + } + } else { + for (int i = 0, ii = params->output_channels; i < ii; i++) { + ((mp_obj_list_t *) arg->out)->items[i] = + mp_obj_new_float( ((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 *pos_args, mp_map_t *kw_args) { + enum { ARG_roi, ARG_min_scale, ARG_scale_mul, ARG_x_overlap, ARG_y_overlap }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_roi, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_min_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_scale_mul, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_x_overlap, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_y_overlap, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 2, pos_args + 2, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + image_t *image = py_helper_arg_to_image(pos_args[1], ARG_IMAGE_ANY); + rectangle_t roi = py_helper_arg_to_roi(args[ARG_roi].u_obj, image); + float min_scale = py_helper_arg_to_float(args[ARG_min_scale].u_obj, 1.0f); + float scale_mul = py_helper_arg_to_float(args[ARG_scale_mul].u_obj, 0.5f); + float x_overlap = py_helper_arg_to_float(args[ARG_x_overlap].u_obj, 0.0f); + float y_overlap = py_helper_arg_to_float(args[ARG_y_overlap].u_obj, 0.0f); + + // Sanity checks + if ((min_scale <= 0.0f) || (min_scale > 1.0f)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("0 < min_scale <= 1")); + } + + if ((scale_mul < 0.0f) || (scale_mul >= 1.0f)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("0 <= scale_mul < 1")); + } + + if ((x_overlap != -1.f) && ((x_overlap < 0.0f) || (x_overlap >= 1.0f))) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("0 <= x_overlap < 1")); + } + + if ((y_overlap != -1.0f) && ((y_overlap < 0.0f) || (y_overlap >= 1.0f))) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("0 <= y_overlap < 1")); + } + + fb_alloc_mark(); + py_tf_alloc_putchar_buffer(); + + py_tf_model_obj_t *model = py_tf_load_alloc(pos_args[0]); + uint8_t *tensor_arena = fb_alloc(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 >= min_scale; scale *= scale_mul) { + // Either provide a subtle offset to center multiple detection windows or center the only detection window. + for (int y = roi.y + ((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 += ((y_overlap != -1.0f) ? (roi.h * scale * (1.0f - 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 + ((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 += ((x_overlap != -1.0f) ? (roi.w * scale * (1.0f - 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 = image; + 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(model->model_data, + tensor_arena, + &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 *pos_args, mp_map_t *kw_args) { + enum { ARG_roi, ARG_thresholds, ARG_invert }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_roi, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_thresholds, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_invert, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = false } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 2, pos_args + 2, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + image_t *image = py_helper_arg_to_image(pos_args[1], ARG_IMAGE_ANY); + rectangle_t roi = py_helper_arg_to_roi(args[ARG_roi].u_obj, image); + bool invert = args[ARG_invert].u_int; + + fb_alloc_mark(); + py_tf_alloc_putchar_buffer(); + + py_tf_model_obj_t *model = py_tf_load_alloc(pos_args[0]); + uint8_t *tensor_arena = fb_alloc(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 = image; + 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(model->model_data, + tensor_arena, + &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_arg_to_thresholds(args[ARG_thresholds].u_obj, &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); + } + + 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); + if (hist.LBins) fb_free(hist.LBins); // 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) }, + { MP_ROM_QSTR(MP_QSTR_regression), MP_ROM_PTR(&py_tf_regression_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(py_tf_locals_dict, locals_dict_table); + +STATIC MP_DEFINE_CONST_OBJ_TYPE( + py_tf_model_type, + MP_QSTR_tf_model, + MP_TYPE_FLAG_NONE, + print, py_tf_model_print, + locals_dict, &py_tf_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_load_builtin_model), MP_ROM_PTR(&py_tf_load_builtin_model_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) }, + { MP_ROM_QSTR(MP_QSTR_regression), MP_ROM_PTR(&py_tf_regression_obj) } + #else + { MP_ROM_QSTR(MP_QSTR_load), MP_ROM_PTR(&py_func_unavailable_obj) }, + { MP_ROM_QSTR(MP_QSTR_load_builtin_model), 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) }, + { MP_ROM_QSTR(MP_QSTR_regression), 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); diff --git a/components/3rd_party/omv/omv/modules/py_tf.h b/components/3rd_party/omv/omv/modules/py_tf.h new file mode 100644 index 00000000..8cdca70e --- /dev/null +++ b/components/3rd_party/omv/omv/modules/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/components/3rd_party/omv/omv/modules/py_tfp410.c b/components/3rd_party/omv/omv/modules/py_tfp410.c new file mode 100644 index 00000000..d48c3610 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_tfp410.c @@ -0,0 +1,168 @@ +/* + * 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. + * + * TFP410 DVI serializer module. + */ +#include "omv_boardconfig.h" + +#if OMV_TFP410_ENABLE +#include "py/obj.h" +#include "py/objarray.h" +#include "py/runtime.h" +#include "mphal.h" +#include "extmod/machine_i2c.h" + +#include "omv_gpio.h" +#include "fb_alloc.h" +#include "cec.h" + +#define TFP410_I2C_ADDR 0x3F + +typedef struct _py_tfp410_obj_t { + mp_obj_base_t base; + mp_obj_t i2c_bus; + uint8_t i2c_addr; + mp_obj_t hotplug_callback; +} py_tfp410_obj_t; + +const mp_obj_type_t py_tfp410_type; + +static int dvi_is_connected(py_tfp410_obj_t *self, bool *connected) { + if (mp_machine_soft_i2c_transfer(self->i2c_bus, self->i2c_addr, 1, + &((mp_machine_i2c_buf_t) {.len = 1, .buf = (uint8_t []) {0x09}}), 0) == 1) { + uint8_t reg; + if ((mp_machine_soft_i2c_transfer(self->i2c_bus, self->i2c_addr, 1, + &((mp_machine_i2c_buf_t) { .len = 1, .buf = ® }), + MP_MACHINE_I2C_FLAG_READ | MP_MACHINE_I2C_FLAG_STOP) == 0) + && (mp_machine_soft_i2c_transfer(self->i2c_bus, self->i2c_addr, 1, &((mp_machine_i2c_buf_t) { + .len = 2, .buf = (uint8_t []) {0x09, 0x19} // clear interrupt flag + }), MP_MACHINE_I2C_FLAG_STOP) == 2)) { + *connected = (reg & 2); + return 0; + } + } + // Generate stop on error + mp_machine_soft_i2c_transfer(self->i2c_bus, self->i2c_addr, 1, + &((mp_machine_i2c_buf_t) {.len = 0, .buf = NULL }), MP_MACHINE_I2C_FLAG_STOP); + return -1; +} + +static void dvi_extint_callback(mp_obj_t self_in) { + py_tfp410_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->hotplug_callback != mp_const_none) { + bool connected; + if (dvi_is_connected(self, &connected) != -1) { + mp_call_function_1(self->hotplug_callback, mp_obj_new_bool(connected)); + } + } +} + +STATIC mp_obj_t py_dvi_is_connected(mp_obj_t self_in) { + py_tfp410_obj_t *self = MP_OBJ_TO_PTR(self_in); + + bool connected; + if (dvi_is_connected(self, &connected) != -1) { + return mp_obj_new_bool(connected); + } + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Display init failed!")); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_dvi_is_connected_obj, py_dvi_is_connected); + +STATIC mp_obj_t py_dvi_hotplug_callback(mp_obj_t self_in, mp_obj_t cb) { + py_tfp410_obj_t *self = MP_OBJ_TO_PTR(self_in); + + self->hotplug_callback = cb; + if (cb == mp_const_none) { + omv_gpio_irq_enable(OMV_TFP410_INT_PIN, false); + } else { + omv_gpio_config(OMV_TFP410_INT_PIN, OMV_GPIO_MODE_IT_FALL, OMV_GPIO_PULL_UP, OMV_GPIO_SPEED_LOW, -1); + omv_gpio_irq_register(OMV_TFP410_INT_PIN, dvi_extint_callback, self_in); + omv_gpio_irq_enable(OMV_TFP410_INT_PIN, true); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_dvi_hotplug_callback_obj, py_dvi_hotplug_callback); + +mp_obj_t py_tfp410_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_i2c_addr }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_i2c_addr, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = TFP410_I2C_ADDR } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + py_tfp410_obj_t *self = m_new_obj_with_finaliser(py_tfp410_obj_t); + self->base.type = &py_tfp410_type; + self->hotplug_callback = mp_const_none; + self->i2c_addr = args[ARG_i2c_addr].u_int; + self->i2c_bus = MP_OBJ_TYPE_GET_SLOT( + &mp_machine_soft_i2c_type, make_new) (&mp_machine_soft_i2c_type, 2, 0, (const mp_obj_t []) { + (mp_obj_t) OMV_TFP410_SCL_PIN, (mp_obj_t) OMV_TFP410_SDA_PIN + }); + + omv_gpio_config(OMV_TFP410_RESET_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); + omv_gpio_write(OMV_TFP410_RESET_PIN, 0); + mp_hal_delay_ms(1); + omv_gpio_write(OMV_TFP410_RESET_PIN, 1); + mp_hal_delay_ms(1); + + if (mp_machine_soft_i2c_transfer(self->i2c_bus, TFP410_I2C_ADDR, 1, &((mp_machine_i2c_buf_t) { + .len = 4, .buf = (uint8_t []) {0x08, 0xB7, 0x19, 0x80} // addr, CTL_1, CTL_2, CTL_3 + }), MP_MACHINE_I2C_FLAG_STOP) != 4) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to init DVI bus")); + } + + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t py_tfp410_deinit(mp_obj_t self_in) { + omv_gpio_irq_enable(OMV_TFP410_INT_PIN, false); + + omv_gpio_write(OMV_TFP410_RESET_PIN, 0); + mp_hal_delay_ms(1); + omv_gpio_write(OMV_TFP410_RESET_PIN, 1); + mp_hal_delay_ms(1); + + omv_gpio_deinit(OMV_TFP410_INT_PIN); + omv_gpio_deinit(OMV_TFP410_RESET_PIN); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tfp410_deinit_obj, py_tfp410_deinit); + +STATIC const mp_rom_map_elem_t py_tfp410_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_tfp410) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&py_tfp410_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&py_dvi_is_connected_obj) }, + { MP_ROM_QSTR(MP_QSTR_hotplug_callback), MP_ROM_PTR(&py_dvi_hotplug_callback_obj)}, +}; +MP_DEFINE_CONST_DICT(py_tfp410_locals_dict, py_tfp410_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + py_tfp410_type, + MP_QSTR_TFP410, + MP_TYPE_FLAG_NONE, + make_new, py_tfp410_make_new, + locals_dict, &py_tfp410_locals_dict + ); + +STATIC const mp_rom_map_elem_t globals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_tfp410) }, + { MP_ROM_QSTR(MP_QSTR_TFP410), MP_ROM_PTR(&py_tfp410_type) }, +}; +STATIC MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); + +const mp_obj_module_t tfp410_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_t) &globals_dict, +}; + +MP_REGISTER_MODULE(MP_QSTR_tfp410, tfp410_module); +#endif // OMV_TFP410_ENABLE diff --git a/components/3rd_party/omv/omv/modules/py_tof.c b/components/3rd_party/omv/omv/modules/py_tof.c new file mode 100644 index 00000000..9dc2dbee --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_tof.c @@ -0,0 +1,588 @@ +/* + * 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 module for time of flight sensors. + */ +#include "py/runtime.h" +#include "py/objlist.h" +#include "py/mphal.h" + +#include "omv_boardconfig.h" +#include "omv_i2c.h" + +#if (MICROPY_PY_TOF == 1) +#include "py_assert.h" +#include "py_helper.h" +#include "py_image.h" +#include "framebuffer.h" + +#if (OMV_ENABLE_TOF_VL53L5CX == 1) +#include "vl53l5cx_api.h" +#endif + +#define VL53L5CX_ADDR 0x52 +#define VL53L5CX_WIDTH 8 +#define VL53L5CX_HEIGHT 8 +#define VL53L5CX_FRAME_DATA_SIZE 64 + +static omv_i2c_t tof_bus = {}; + +typedef enum tof_type { + TOF_NONE, + #if (OMV_ENABLE_TOF_VL53L5CX == 1) + TOF_VL53L5CX, + #endif +} tof_type_t; + +static int tof_width = 0; +static int tof_height = 0; +static bool tof_transposed = false; +static tof_type_t tof_sensor = TOF_NONE; + +#if (OMV_ENABLE_TOF_VL53L5CX == 1) +static VL53L5CX_Configuration vl53l5cx_dev = { + .platform = { + .bus = &tof_bus, + .address = VL53L5CX_ADDR, + } +}; +#endif + +// img->w == data_w && img->h == data_h && img->pixfmt == PIXFORMAT_GRAYSCALE +static void tof_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_TOF_VL53L5CX == 1) +static void tof_vl53l5cx_get_depth(VL53L5CX_Configuration *vl53l5cx_dev, float *frame, uint32_t timeout) { + uint8_t frame_ready = 0; + // Note depending on the config in platform.h, this struct can be too big to alloc on the stack. + VL53L5CX_ResultsData ranging_data; + + for (mp_uint_t start = mp_hal_ticks_ms(); !frame_ready; mp_hal_delay_ms(1)) { + if (vl53l5cx_check_data_ready(vl53l5cx_dev, &frame_ready) != 0) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("VL53L5CX ranging failed")); + } + + if ((mp_hal_ticks_ms() - start) >= timeout) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("VL53L5CX ranging timeout")); + } + } + + if (vl53l5cx_get_ranging_data(vl53l5cx_dev, &ranging_data) != 0) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("VL53L5CX ranging failed")); + } + + for (int i = 0, ii = VL53L5CX_WIDTH * VL53L5CX_HEIGHT; i < ii; i++) { + frame[i] = (float) ranging_data.distance_mm[i]; + } +} + +static mp_obj_t tof_get_depth_obj(int w, int h, float *frame, 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 = frame + (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 = frame + (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[3] = { + MP_OBJ_FROM_PTR(list), + mp_obj_new_float(min), + mp_obj_new_float(max) + }; + return mp_obj_new_tuple(3, tuple); +} +#endif + +static mp_obj_t py_tof_deinit() { + tof_width = 0; + tof_height = 0; + tof_transposed = false; + + if (tof_sensor != TOF_NONE) { + #if (OMV_ENABLE_TOF_VL53L5CX == 1) + if (tof_sensor == TOF_VL53L5CX) { + vl53l5cx_stop_ranging(&vl53l5cx_dev); + } + #endif + omv_i2c_deinit(&tof_bus); + tof_sensor = TOF_NONE; + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_tof_deinit_obj, py_tof_deinit); + +mp_obj_t py_tof_init(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_type }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_type, MP_ARG_INT, {.u_int = -1 } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + py_tof_deinit(); + bool first_init = true; + int type = args[ARG_type].u_int; + + if (type == -1) { + TOF_SCAN_RETRY: + omv_i2c_init(&tof_bus, TOF_I2C_ID, OMV_I2C_SPEED_STANDARD); + // Scan and detect any supported sensor. + uint8_t dev_list[10]; + int dev_size = omv_i2c_scan(&tof_bus, dev_list, sizeof(dev_list)); + for (int i = 0; i < dev_size && type == -1; i++) { + switch (dev_list[i]) { + #if (OMV_ENABLE_TOF_VL53L5CX == 1) + case (VL53L5CX_ADDR): { + type = TOF_VL53L5CX; + break; + } + #endif + default: + continue; + } + } + + if (type == -1 && first_init) { + first_init = false; + // Recover bus and scan one more time. + omv_i2c_pulse_scl(&tof_bus); + goto TOF_SCAN_RETRY; + } + + omv_i2c_deinit(&tof_bus); + } + + // Initialize the detected sensor. + first_init = true; + switch (type) { + #if (OMV_ENABLE_TOF_VL53L5CX == 1) + case TOF_VL53L5CX: { + int error = 0; + uint8_t isAlive = 0; + TOF_VL53L5CX_RETRY: + //vl53l5cx_dev.platform.bus = tof_bus; + //vl53l5cx_dev.platform.address = VL53L5CX_ADDRESS; + omv_i2c_init(&tof_bus, TOF_I2C_ID, OMV_I2C_SPEED_FAST); + + // Check sensor and initialize. + error |= vl53l5cx_is_alive(&vl53l5cx_dev, &isAlive); + error |= vl53l5cx_init(&vl53l5cx_dev); + + // Set resolution (number of zones). + // NOTE: This function must be called before updating the ranging frequency. + error |= vl53l5cx_set_resolution(&vl53l5cx_dev, VL53L5CX_RESOLUTION_8X8); + + // Set ranging frequency (FPS). + // For 4x4 the allowed ranging frequency range is 1 -> 60. + // For 8x8 the allowed ranging frequency range is 1 -> 15. + error |= vl53l5cx_set_ranging_frequency_hz(&vl53l5cx_dev, 15); + + // Set ranging mode to continuous: + // The device continuously grabs frames with the set ranging frequency. + // Maximum ranging depth and ambient immunity are better. + // This mode is advised for fast ranging measurements or high performances. + error |= vl53l5cx_set_ranging_mode(&vl53l5cx_dev, VL53L5CX_RANGING_MODE_CONTINUOUS); + + error |= vl53l5cx_set_sharpener_percent(&vl53l5cx_dev, 50); + + // Start ranging. + error |= vl53l5cx_start_ranging(&vl53l5cx_dev); + + if (error != 0 && first_init) { + first_init = false; + omv_i2c_pulse_scl(&tof_bus); + goto TOF_VL53L5CX_RETRY; + } else if (error != 0) { + py_tof_deinit(); + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Failed to init the VL53L5CX")); + } + tof_sensor = TOF_VL53L5CX; + tof_width = VL53L5CX_WIDTH; + tof_height = VL53L5CX_HEIGHT; + break; + } + #endif + default: { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Failed to detect a supported TOF sensor.")); + } + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_tof_init_obj, 0, py_tof_init); + +static mp_obj_t py_tof_type() { + if (tof_sensor != TOF_NONE) { + return mp_obj_new_int(tof_sensor); + } + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TOF sensor is not initialized")); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_tof_type_obj, py_tof_type); + +static mp_obj_t py_tof_width() { + if (tof_sensor != TOF_NONE) { + return mp_obj_new_int(tof_width); + } + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TOF sensor is not initialized")); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_tof_width_obj, py_tof_width); + +static mp_obj_t py_tof_height() { + if (tof_sensor != TOF_NONE) { + return mp_obj_new_int(tof_height); + } + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TOF sensor is not initialized")); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_tof_height_obj, py_tof_height); + +static mp_obj_t py_tof_refresh() { + switch (tof_sensor) { + #if (OMV_ENABLE_TOF_VL53L5CX == 1) + case TOF_VL53L5CX: + return mp_obj_new_int(15); + #endif + default: + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TOF sensor is not initialized")); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_tof_refresh_obj, py_tof_refresh); + +mp_obj_t py_tof_read_depth(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_hmirror, ARG_vflip, ARG_transpose, ARG_timeout }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_hmirror, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = false } }, + { MP_QSTR_vflip, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = false } }, + { MP_QSTR_transpose, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = false } }, + { MP_QSTR_timeout, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + tof_transposed = args[ARG_transpose].u_bool; + + switch (tof_sensor) { + #if (OMV_ENABLE_TOF_VL53L5CX == 1) + case TOF_VL53L5CX: { + fb_alloc_mark(); + float *frame = fb_alloc(VL53L5CX_WIDTH * VL53L5CX_HEIGHT * sizeof(float), FB_ALLOC_PREFER_SPEED); + tof_vl53l5cx_get_depth(&vl53l5cx_dev, frame, args[ARG_timeout].u_int); + mp_obj_t result = tof_get_depth_obj(VL53L5CX_WIDTH, VL53L5CX_HEIGHT, frame, !args[ARG_hmirror].u_bool, + args[ARG_vflip].u_bool, args[ARG_transpose].u_bool, true); + fb_alloc_free_till_mark(); + return result; + } + #endif + default: + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TOF sensor is not initialized")); + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_tof_read_depth_obj, 0, py_tof_read_depth); + +mp_obj_t py_tof_draw_depth(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { + ARG_x, ARG_y, ARG_x_scale, ARG_y_scale, ARG_roi, ARG_channel, ARG_alpha, + ARG_color_palette, ARG_alpha_palette, ARG_hint, ARG_scale + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_x, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0 } }, + { MP_QSTR_y, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0 } }, + { MP_QSTR_x_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_y_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_roi, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_rgb_channel, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + { MP_QSTR_alpha, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 256 } }, + { MP_QSTR_color_palette, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_INT(COLOR_PALETTE_RAINBOW)} }, + { MP_QSTR_alpha_palette, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_hint, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0 } }, + { MP_QSTR_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 2, pos_args + 2, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Sanity checks + if (tof_sensor == TOF_NONE) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TOF sensor is not initialized")); + } + + if (args[ARG_channel].u_int < -1 || args[ARG_channel].u_int > 2) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("RGB channel can be 0, 1, or 2")); + } + + if (args[ARG_alpha].u_int < 0 || args[ARG_alpha].u_int > 256) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Alpha ranges between 0 and 256")); + } + + image_t src_img = { + .w = tof_transposed ? tof_height : tof_width, + .h = tof_transposed ? tof_width : tof_height, + .pixfmt = PIXFORMAT_GRAYSCALE, + //.data is allocated later. + }; + + image_t *dst_img = py_helper_arg_to_image(pos_args[0], ARG_IMAGE_MUTABLE); + + mp_obj_t *depth_array; + mp_obj_get_array_fixed_n(pos_args[1], src_img.w * src_img.h, &depth_array); + + rectangle_t roi = py_helper_arg_to_roi(args[ARG_roi].u_obj, &src_img); + + float x_scale = 1.0f; + float y_scale = 1.0f; + py_helper_arg_to_scale(args[ARG_x_scale].u_obj, args[ARG_y_scale].u_obj, &x_scale, &y_scale); + + float min = FLT_MAX; + float max = -FLT_MAX; + py_helper_arg_to_minmax(args[ARG_scale].u_obj, &min, &max, depth_array, src_img.w * src_img.h); + + const uint16_t *color_palette = py_helper_arg_to_palette(args[ARG_color_palette].u_obj, PIXFORMAT_RGB565); + const uint8_t *alpha_palette = py_helper_arg_to_palette(args[ARG_alpha_palette].u_obj, PIXFORMAT_GRAYSCALE); + + fb_alloc_mark(); + src_img.data = fb_alloc(src_img.w * src_img.h * sizeof(uint8_t), FB_ALLOC_NO_HINT); + tof_fill_image_float_obj(&src_img, depth_array, min, max); + + imlib_draw_image(dst_img, &src_img, args[ARG_x].u_int, args[ARG_y].u_int, x_scale, y_scale, &roi, + args[ARG_channel].u_int, args[ARG_alpha].u_int, color_palette, alpha_palette, + args[ARG_hint].u_int, NULL, NULL, NULL); + + fb_alloc_free_till_mark(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_tof_draw_depth_obj, 2, py_tof_draw_depth); + +mp_obj_t py_tof_snapshot(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { + ARG_hmirror, ARG_vflip, ARG_transpose, ARG_x_scale, ARG_y_scale, ARG_roi, ARG_channel, + ARG_alpha, ARG_color_palette, ARG_alpha_palette, ARG_hint, ARG_scale, ARG_pixformat, + ARG_copy_to_fb, ARG_timeout + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_hmirror, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = false } }, + { MP_QSTR_vflip, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = false } }, + { MP_QSTR_transpose, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = false } }, + { MP_QSTR_x_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_y_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_roi, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_rgb_channel, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + { MP_QSTR_alpha, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 256 } }, + { MP_QSTR_color_palette, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_INT(COLOR_PALETTE_RAINBOW)} }, + { MP_QSTR_alpha_palette, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_hint, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0 } }, + { MP_QSTR_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_pixformat, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = PIXFORMAT_RGB565 } }, + { MP_QSTR_copy_to_fb, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = false } }, + { MP_QSTR_timeout, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Sanity checks + if (args[ARG_channel].u_int < -1 || args[ARG_channel].u_int > 2) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("RGB channel can be 0, 1, or 2")); + } + + if (args[ARG_alpha].u_int < 0 || args[ARG_alpha].u_int > 256) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Alpha ranges between 0 and 256")); + } + + if ((args[ARG_pixformat].u_int != PIXFORMAT_GRAYSCALE) && (args[ARG_pixformat].u_int != PIXFORMAT_RGB565)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid pixformat")); + } + + image_t src_img = { + .w = args[ARG_transpose].u_bool ? tof_height : tof_width, + .h = args[ARG_transpose].u_bool ? tof_width : tof_height, + .pixfmt = PIXFORMAT_GRAYSCALE, + //.data is allocated later. + }; + + rectangle_t roi = py_helper_arg_to_roi(args[ARG_roi].u_obj, &src_img); + + float x_scale = 1.0f; + float y_scale = 1.0f; + py_helper_arg_to_scale(args[ARG_x_scale].u_obj, args[ARG_y_scale].u_obj, &x_scale, &y_scale); + + image_t dst_img = { + .w = fast_floorf(roi.w * x_scale), + .h = fast_floorf(roi.h * y_scale), + .pixfmt = args[ARG_pixformat].u_int, + }; + + if (args[ARG_copy_to_fb].u_bool == false) { + py_helper_set_to_framebuffer(&dst_img); + } else { + dst_img.data = xalloc(image_size(&dst_img)); + } + + float min = FLT_MAX; + float max = -FLT_MAX; + py_helper_arg_to_minmax(args[ARG_scale].u_obj, &min, &max, NULL, 0); + + const uint16_t *color_palette = py_helper_arg_to_palette(args[ARG_color_palette].u_obj, PIXFORMAT_RGB565); + const uint8_t *alpha_palette = py_helper_arg_to_palette(args[ARG_alpha_palette].u_obj, PIXFORMAT_GRAYSCALE); + + fb_alloc_mark(); + // Allocate source image data. + src_img.data = fb_alloc(src_img.w * src_img.h * sizeof(uint8_t), FB_ALLOC_NO_HINT); + + switch (tof_sensor) { + #if (OMV_ENABLE_TOF_VL53L5CX == 1) + case TOF_VL53L5CX: { + float *frame = fb_alloc(VL53L5CX_WIDTH * VL53L5CX_HEIGHT * sizeof(float), FB_ALLOC_PREFER_SPEED); + tof_vl53l5cx_get_depth(&vl53l5cx_dev, frame, args[ARG_timeout].u_int); + if (args[ARG_scale].u_obj == mp_const_none) { + fast_get_min_max(frame, VL53L5CX_WIDTH * VL53L5CX_HEIGHT, &min, &max); + } + imlib_fill_image_from_float(&src_img, VL53L5CX_WIDTH, VL53L5CX_HEIGHT, frame, min, max, + !args[ARG_hmirror].u_bool, args[ARG_vflip].u_bool, + args[ARG_transpose].u_bool, true); + break; + } + #endif + default: + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TOF sensor is not initialized")); + } + + imlib_draw_image(&dst_img, &src_img, 0, 0, x_scale, y_scale, &roi, + args[ARG_channel].u_int, args[ARG_alpha].u_int, color_palette, alpha_palette, + (args[ARG_hint].u_int & (~IMAGE_HINT_CENTER)) | IMAGE_HINT_BLACK_BACKGROUND, NULL, NULL, NULL); + + fb_alloc_free_till_mark(); + + if (args[ARG_copy_to_fb].u_bool) { + framebuffer_update_jpeg_buffer(); + } + return py_image_from_struct(&dst_img); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_tof_snapshot_obj, 0, py_tof_snapshot); + +STATIC const mp_rom_map_elem_t globals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_tof) }, + { MP_ROM_QSTR(MP_QSTR_TOF_NONE), MP_ROM_INT(TOF_NONE) }, + #if (OMV_ENABLE_TOF_VL53L5CX == 1) + { MP_ROM_QSTR(MP_QSTR_TOF_VL53L5CX), MP_ROM_INT(TOF_VL53L5CX) }, + #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_tof_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&py_tof_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_type), MP_ROM_PTR(&py_tof_type_obj) }, + { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&py_tof_width_obj) }, + { MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&py_tof_height_obj) }, + { MP_ROM_QSTR(MP_QSTR_refresh), MP_ROM_PTR(&py_tof_refresh_obj) }, + { MP_ROM_QSTR(MP_QSTR_read_depth), MP_ROM_PTR(&py_tof_read_depth_obj) }, + { MP_ROM_QSTR(MP_QSTR_draw_depth), MP_ROM_PTR(&py_tof_draw_depth_obj) }, + { MP_ROM_QSTR(MP_QSTR_snapshot), MP_ROM_PTR(&py_tof_snapshot_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); + +const mp_obj_module_t tof_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_t) &globals_dict, +}; + +MP_REGISTER_MODULE(MP_QSTR_tof, tof_module); +#endif diff --git a/components/3rd_party/omv/omv/modules/py_tof.h b/components/3rd_party/omv/omv/modules/py_tof.h new file mode 100644 index 00000000..3a8c222e --- /dev/null +++ b/components/3rd_party/omv/omv/modules/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/components/3rd_party/omv/omv/modules/py_tv.c b/components/3rd_party/omv/omv/modules/py_tv.c new file mode 100644 index 00000000..bb93fbc0 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_tv.c @@ -0,0 +1,978 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2023 Ibrahim Abdelkader + * Copyright (c) 2013-2023 Kwabena W. Agyeman + * Copyright (c) 2013-2023 Kaizhi Wong + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * TV Python module. + */ +#include "omv_boardconfig.h" + +#if MICROPY_PY_TV + +#include "py/obj.h" +#include "py/nlr.h" +#include "py/runtime.h" + +#include "py_helper.h" +#include "py_image.h" +#include "omv_gpio.h" +#include "omv_spi.h" + +#define TV_WIDTH 352 +#define TV_HEIGHT 240 +#define TV_REFRESH 60 + +#if ((TV_WIDTH) % 2) +#error "TV_WIDTH not even" +#endif + +#if ((TV_HEIGHT) % 2) +#error "TV_HEIGHT not even" +#endif + +#ifdef OMV_SPI_DISPLAY_CONTROLLER +///////////////////////////////////////////////////////////// +// http://www.vsdsp-forum.com/phpbb/viewtopic.php?f=14&t=1801 +///////////////////////////////////////////////////////////// + +// Crystal frequency in MHZ (float, observe accuracy) +#define XTAL_MHZ 3.579545 + +// Line length in microseconds (float, observe accuracy) +#define LINE_LENGTH_US 63.556 + +#define FIXED_VCLK_CYCLES 10 +#define FIXED_CSCLK_CYCLES ((FIXED_VCLK_CYCLES) / 8.0) + +// Normal visible picture line sync length is 4.7 us +#define SYNC_US 4.7 +#define SYNC ((uint16_t) (((SYNC_US) *(XTAL_MHZ)) - (FIXED_CSCLK_CYCLES) +0.5)) + +// Color burst starts at 5.3 us +#define BURST_US 5.3 +#define BURST ((uint16_t) (((BURST_US) *(XTAL_MHZ)) - (FIXED_CSCLK_CYCLES) +0.5)) + +// Color burst duration is 2.5 us +#define BURST_DUR_US 2.5 +#define BURST_DUR ((uint16_t) (((BURST_DUR_US) *(XTAL_MHZ)) + 0.5)) + +// Black video starts at 9.4 us +#define BLACK_US 9.4 +#define BLACK ((uint16_t) (((BLACK_US) *(XTAL_MHZ)) - (FIXED_CSCLK_CYCLES) +0.5)) + +// Black video duration is 52.656 us +#define BLACK_DUR_US 52.656 +#define BLACK_DUR ((uint16_t) (((BLACK_DUR_US) *(XTAL_MHZ)) + 0.5)) + +// Define NTSC video timing constants +// NTSC short sync duration is 2.3 us +#define SHORT_SYNC_US 2.3 + +// For the start of the line, the first 10 extra PLLCLK sync (0) cycles are subtracted. +#define SHORTSYNC ((uint16_t) (((SHORT_SYNC_US) *(XTAL_MHZ)) - (FIXED_CSCLK_CYCLES) +0.5)) + +// For the middle of the line the whole duration of sync pulse is used. +#define SHORTSYNCM ((uint16_t) (((SHORT_SYNC_US) *(XTAL_MHZ)) + 0.5)) + +// NTSC long sync duration is 27.078 us +#define LONG_SYNC_US 27.078 +#define LONGSYNC ((uint16_t) (((LONG_SYNC_US) *(XTAL_MHZ)) - (FIXED_CSCLK_CYCLES) +0.5)) +#define LONGSYNCM ((uint16_t) (((LONG_SYNC_US) *(XTAL_MHZ)) + 0.5)) + +// Number of lines used after the VSYNC but before visible area. +#define VSYNC_LINES 9 +#define FRONT_PORCH_LINES 13 + +// Definitions for picture lines +// On which line the picture area begins, the Y direction. +#define STARTLINE ((VSYNC_LINES) + (FRONT_PORCH_LINES)) + +// Frame length in lines (visible lines + nonvisible lines) +// Amount has to be odd for NTSC and RGB colors +#define TOTAL_LINES ((STARTLINE) + (TV_HEIGHT) +1) +#if ((TOTAL_LINES) != 263) +#error "Progressive NTSC must have 263 lines!" +#endif + +// Width, in PLL clocks, of each pixel. +#define PLLCLKS_PER_PIXEL 4 + +// The first pixel of the picture area, the X direction. +#define STARTPIX ((BLACK) +7) + +// The last pixel of the picture area. +#define ENDPIX ((uint16_t) ((STARTPIX) + (((PLLCLKS_PER_PIXEL) *(TV_WIDTH)) / 8))) + +// Reserve memory for this number of different prototype lines +// (prototype lines are used for sync timing, porch and border area) +#define PROTOLINES 3 + +// PLL frequency +#define PLL_MHZ ((XTAL_MHZ) * 8) + +// 10 first pllclks, which are not in the counters are decremented here +#define PLLCLKS_PER_LINE ((uint16_t) (((LINE_LENGTH_US) *(PLL_MHZ)) - (FIXED_VCLK_CYCLES))) + +// 10 first pllclks, which are not in the counters are decremented here +#define COLORCLKS_PER_LINE ((uint16_t) ((((((LINE_LENGTH_US) *(PLL_MHZ)) / 1) + 7) / 8) - (FIXED_CSCLK_CYCLES))) +#define COLORCLKS_LINE_HALF ((uint16_t) ((((((LINE_LENGTH_US) *(PLL_MHZ)) / 2) + 7) / 8) - (FIXED_CSCLK_CYCLES))) + +#define PROTO_AREA_WORDS ((COLORCLKS_PER_LINE) *(PROTOLINES)) +#define INDEX_START_LONGWORDS (((PROTO_AREA_WORDS) +1) / 2) +#define INDEX_START_BYTES ((INDEX_START_LONGWORDS) * 4) + +// Protoline 0 starts always at address 0 +#define PROTOLINE_BYTE_ADDRESS(n) ((COLORCLKS_PER_LINE) * 2 * (n)) +#define PROTOLINE_WORD_ADDRESS(n) ((COLORCLKS_PER_LINE) * 1 * (n)) + +// Calculate picture lengths in pixels and bytes, coordinate areas for picture area +#define PICBITS 12 +#define PICLINE_LENGTH_BYTES (((TV_WIDTH) *(PICBITS)) / 8) + +#define LINE_INDEX_BYTE_SIZE 3 + +// Picture area memory start point +#define PICLINE_START ((INDEX_START_BYTES) + ((TOTAL_LINES) *(LINE_INDEX_BYTE_SIZE))) + +// Picture area line start addresses +#define PICLINE_BYTE_ADDRESS(n) ((PICLINE_START) + ((PICLINE_LENGTH_BYTES) *(n))) + +// Pattern generator microcode +// --------------------------- +// Bits 7:6 a=00|b=01|y=10|-=11 +// Bits 5:3 n pick bits 1..8 +// bits 2:0 shift 0..6 +#define PICK_A (0 << 6) +#define PICK_B (1 << 6) +#define PICK_Y (2 << 6) +#define PICK_NOTHING (3 << 6) +#define PICK_BITS(a) (((a) - 1) << 3) +#define SHIFT_BITS(a) (a) + +// 16 bits per pixel, U4 V4 Y8 +// PICK_B is U +#define OP1 (PICK_B + PICK_BITS(4) + SHIFT_BITS(4)) +// PICK_A is V +#define OP2 (PICK_A + PICK_BITS(4) + SHIFT_BITS(4)) +#define OP3 (PICK_Y + PICK_BITS(8) + SHIFT_BITS(6)) +#define OP4 (PICK_NOTHING + SHIFT_BITS(2)) + +// General VS23 commands +#define WRITE_STATUS 0x01 +#define WRITE_SRAM 0x02 +#define WRITE_GPIO 0x82 +#define READ_GPIO 0x84 +#define WRITE_MULTIIC 0xb8 +#define WRITE_BLOCKMVC1 0x34 + +// Bit definitions +#define VDCTRL1 0x2B +#define VDCTRL1_UVSKIP (1 << 0) +#define VDCTRL1_PLL_ENABLE (1 << 12) +#define VDCTRL2 0x2D +#define VDCTRL2_LINECOUNT (1 << 0) +#define VDCTRL2_PIXEL_WIDTH (1 << 10) +#define VDCTRL2_ENABLE_VIDEO (1 << 15) +#define BLOCKMVC1_PYF (1 << 4) + +// VS23 video commands +#define PROGRAM 0x30 +#define PICSTART 0x28 +#define PICEND 0x29 +#define LINELEN 0x2a +#define INDEXSTART 0x2c + +// Sync, blank, burst and white level definitions, here are several options +// These are for proto lines and so format is VVVVUUUUYYYYYYYY + +// Sync is always 0 +#define SYNC_LEVEL 0x0000 + +// 285 mV to 75 ohm load +#define BLANK_LEVEL 0x0066 + +// 285 mV burst +#define BURST_LEVEL 0x0d66 + +#define SPI_RAM_SIZE (128 * 1024) + +// COLORCLKS_PER_LINE can't be used in pre-processor logic. +#if ((((((227 * (PROTOLINES)) + 1) / 2) * 4) + ((TOTAL_LINES) *(LINE_INDEX_BYTE_SIZE)) + \ + ((PICLINE_LENGTH_BYTES) *(TV_HEIGHT))) > (SPI_RAM_SIZE)) +#error "TV_WIDTH * TV_HEIGHT is too big!" +#endif + +#define TV_BAUDRATE (TV_WIDTH * TV_HEIGHT * TV_REFRESH * PICBITS) + +#if OMV_SPI_DISPLAY_TRIPLE_BUFFER +#define TV_TRIPLE_BUFFER_DEFAULT (true) +#else +#define TV_TRIPLE_BUFFER_DEFAULT (false) +#endif + +static omv_spi_t spi_bus = {}; + +static void SpiTransmitReceivePacket(uint8_t *txdata, uint8_t *rxdata, uint16_t size, bool end) { + omv_spi_transfer_t spi_xfer = { + .txbuf = txdata, + .rxbuf = rxdata, + .size = size, + .timeout = OMV_SPI_MAX_TIMEOUT, + .flags = OMV_SPI_XFER_BLOCKING + }; + + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 0); + omv_spi_transfer_start(&spi_bus, &spi_xfer); + + if (end) { + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1); + } +} + +static void SpiRamWriteByteRegister(int opcode, int data) { + uint8_t packet[2] = {opcode, data}; + SpiTransmitReceivePacket(packet, NULL, sizeof(packet), true); +} + +static int SpiRamReadByteRegister(int opcode) { + uint8_t packet[2] = {opcode, 0}; + SpiTransmitReceivePacket(packet, packet, sizeof(packet), true); + return packet[1]; +} + +static void SpiRamWriteWordRegister(int opcode, int data) { + uint8_t packet[3] = {opcode, data >> 8, data}; + SpiTransmitReceivePacket(packet, NULL, sizeof(packet), true); +} + +static void SpiClearRam() { + uint8_t packet[4] = {WRITE_SRAM, 0, 0, 0}; + SpiTransmitReceivePacket(packet, NULL, sizeof(packet), false); + packet[0] = 0; + + for (int i = 0; i < (SPI_RAM_SIZE / sizeof(packet)); i++) { + SpiTransmitReceivePacket(packet, NULL, sizeof(packet), (i + 1) == (SPI_RAM_SIZE / sizeof(packet))); + } +} + +static void SpiRamWriteProgram(int data0, int data1, int data2, int data3) { + uint8_t packet[5] = {PROGRAM, data3, data2, data1, data0}; + SpiTransmitReceivePacket(packet, NULL, sizeof(packet), true); +} + +static void SpiRamWriteLowPassFilter(int data) { + uint8_t packet[6] = {WRITE_BLOCKMVC1, 0, 0, 0, 0, data}; + SpiTransmitReceivePacket(packet, NULL, sizeof(packet), true); +} + +static void SpiRamWriteWord(int w_address, int data) { + int address = w_address * sizeof(uint16_t); + uint8_t packet[6] = {WRITE_SRAM, address >> 16, address >> 8, address, data >> 8, data}; + SpiTransmitReceivePacket(packet, NULL, sizeof(packet), true); +} + +static void SpiRamWriteVSyncProtoLine(int line, int length_1, int length_2) { + int w0 = PROTOLINE_WORD_ADDRESS(line); + for (int i = 0; i < COLORCLKS_PER_LINE; i++) { + SpiRamWriteWord(w0++, BLANK_LEVEL); + } + + int w1 = PROTOLINE_WORD_ADDRESS(line); + for (int i = 0; i < length_1; i++) { + SpiRamWriteWord(w1++, SYNC_LEVEL); + } + + int w2 = PROTOLINE_WORD_ADDRESS(line) + COLORCLKS_LINE_HALF; + for (int i = 0; i < length_2; i++) { + SpiRamWriteWord(w2++, SYNC_LEVEL); + } +} + +static void SpiRamWriteLine(int line, int index) { + int address = INDEX_START_BYTES + (line * LINE_INDEX_BYTE_SIZE); + int data = index << 7; + uint8_t packet[7] = {WRITE_SRAM, address >> 16, address >> 8, address, data, data >> 8, data >> 16}; + SpiTransmitReceivePacket(packet, NULL, sizeof(packet), true); +} + +static void SpiRamVideoInit() { + // Select the first VS23 for following commands in case there + // are several VS23 ICs connected to same SPI bus. + SpiRamWriteByteRegister(WRITE_MULTIIC, 0xe); + + // Set SPI memory address autoincrement + SpiRamWriteByteRegister(WRITE_STATUS, 0x40); + + // Reset the video display controller + SpiRamWriteWordRegister(VDCTRL1, 0); + SpiRamWriteWordRegister(VDCTRL2, 0); + + // Write picture start and end + SpiRamWriteWordRegister(PICSTART, (STARTPIX - 1)); + SpiRamWriteWordRegister(PICEND, (ENDPIX - 1)); + + // Enable PLL clock + SpiRamWriteWordRegister(VDCTRL1, VDCTRL1_PLL_ENABLE | VDCTRL1_UVSKIP); + + // Clear the video memory + SpiClearRam(); + + // Set length of one complete line (unit: PLL clocks) + SpiRamWriteWordRegister(LINELEN, PLLCLKS_PER_LINE); + + // Set microcode program for picture lines + SpiRamWriteProgram(OP1, OP2, OP3, OP4); + + // Define where Line Indexes are stored in memory + SpiRamWriteWordRegister(INDEXSTART, INDEX_START_LONGWORDS); + + // At this time, the chip would continuously output the proto line 0. + // This protoline will become our most "normal" horizontal line. + // For TV-Out, fill the line with black level, + // and insert a few pixels of sync level (0) and color burst to the beginning. + // Note that the chip hardware adds black level to all nonproto areas so + // protolines and normal picture have different meaning for the same Y value. + // In protolines, Y=0 is at sync level and in normal picture Y=0 is at black level (offset +102). + + // In protolines, each pixel is 8 PLLCLKs, which in TV-out modes means one color + // subcarrier cycle. Each pixel has 16 bits (one word): VVVVUUUUYYYYYYYY. + + SpiRamWriteVSyncProtoLine(0, SYNC, 0); + + int w = PROTOLINE_WORD_ADDRESS(0) + BURST; + for (int i = 0; i < BURST_DUR; i++) { + SpiRamWriteWord(w++, BURST_LEVEL); + } + + // short_low + long_high + short_low + long_high + SpiRamWriteVSyncProtoLine(1, SHORTSYNC, SHORTSYNCM); + + // long_low + short_high + long_low + short_high + SpiRamWriteVSyncProtoLine(2, LONGSYNC, LONGSYNCM); + + for (int i = 0; i <= 2; i++) { + SpiRamWriteLine(i, PROTOLINE_BYTE_ADDRESS(1)); // short_low + long_high + short_low + long_high + } + + for (int i = 3; i <= 5; i++) { + SpiRamWriteLine(i, PROTOLINE_BYTE_ADDRESS(2)); // long_low + short_high + long_low + short_high + } + + for (int i = 6; i <= 8; i++) { + SpiRamWriteLine(i, PROTOLINE_BYTE_ADDRESS(1)); // short_low + long_high + short_low + long_high + } + + // Set pic line indexes to point to protoline 0 and their individual picture line. + for (int i = 0; i < TV_HEIGHT; i++) { + SpiRamWriteLine(STARTLINE + i, PICLINE_BYTE_ADDRESS(i)); + } + + // Set number of lines, length of pixel and enable video generation + SpiRamWriteWordRegister(VDCTRL2, (VDCTRL2_LINECOUNT * (TOTAL_LINES - 1)) + | (VDCTRL2_PIXEL_WIDTH * (PLLCLKS_PER_PIXEL - 1)) + | (VDCTRL2_ENABLE_VIDEO)); + + // Enable the low-pass Y filter. + SpiRamWriteLowPassFilter(BLOCKMVC1_PYF); +} +#endif + +// TV lines are converted from 16-bit RGB565 to 12-bit YUV. +#define TV_WIDTH_RGB565 ((TV_WIDTH) * 2) // bytes + +#if ((PICLINE_LENGTH_BYTES) > (TV_WIDTH_RGB565)) +#error "PICLINE_LENGTH_BYTES > TV_WIDTH_RGB565" +#endif + +#define FRAMEBUFFER_COUNT 3 +static int framebuffer_head = 0; +static volatile int framebuffer_tail = 0; +static uint16_t *framebuffers[FRAMEBUFFER_COUNT] = {}; + +typedef enum tv_type { + TV_NONE, + TV_SHIELD, +} tv_type_t; + +static tv_type_t tv_type = TV_NONE; +static bool tv_triple_buffer = false; + +#ifdef OMV_SPI_DISPLAY_CONTROLLER +static volatile enum { + SPI_TX_CB_IDLE, + SPI_TX_CB_MEMORY_WRITE_CMD, + SPI_TX_CB_MEMORY_WRITE +} +spi_tx_cb_state = SPI_TX_CB_IDLE; + +static void spi_config_deinit() { + if (tv_triple_buffer) { + omv_spi_transfer_abort(&spi_bus); + spi_tx_cb_state = SPI_TX_CB_IDLE; + fb_alloc_free_till_mark_past_mark_permanent(); + } + + omv_spi_deinit(&spi_bus); +} + +static void spi_config_init(bool triple_buffer) { + omv_spi_config_t spi_config; + omv_spi_default_config(&spi_config, OMV_SPI_DISPLAY_CONTROLLER); + + spi_config.baudrate = TV_BAUDRATE; + spi_config.nss_enable = false; + spi_config.dma_flags = triple_buffer ? OMV_SPI_DMA_NORMAL : 0; + omv_spi_init(&spi_bus, &spi_config); + + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1); + + SpiRamVideoInit(); + + // Set default channel. + SpiRamWriteByteRegister(WRITE_GPIO, 0x77); + + if (triple_buffer) { + fb_alloc_mark(); + + framebuffer_head = 0; + framebuffer_tail = 0; + + for (int i = 0; i < FRAMEBUFFER_COUNT; i++) { + framebuffers[i] = (uint16_t *) fb_alloc0(TV_WIDTH_RGB565 * TV_HEIGHT, FB_ALLOC_CACHE_ALIGN); + } + + fb_alloc_mark_permanent(); + } +} + +static const uint8_t write_sram[] = { + // Cannot be allocated on the stack. + WRITE_SRAM, + (uint8_t) (PICLINE_BYTE_ADDRESS(0) >> 16), + (uint8_t) (PICLINE_BYTE_ADDRESS(0) >> 8), + (uint8_t) (PICLINE_BYTE_ADDRESS(0) >> 0) +}; + +static void spi_tv_callback(omv_spi_t *spi, void *userdata, void *buf) { + if (tv_type == TV_SHIELD) { + static uint8_t *spi_tx_cb_state_memory_write_addr = NULL; + static size_t spi_tx_cb_state_memory_write_count = 0; + + switch (spi_tx_cb_state) { + case SPI_TX_CB_MEMORY_WRITE_CMD: { + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1); + spi_tx_cb_state = SPI_TX_CB_MEMORY_WRITE; + spi_tx_cb_state_memory_write_addr = (uint8_t *) framebuffers[framebuffer_head]; + spi_tx_cb_state_memory_write_count = PICLINE_LENGTH_BYTES * TV_HEIGHT; + framebuffer_tail = framebuffer_head; + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 0); + // When starting the interrupt chain the first transfer is not executed + // in interrupt context. So, disable interrupts for the first transfer so + // that it completes first and unlocks the SPI bus before allowing the interrupt + // it causes to trigger starting the interrupt chain. + omv_spi_transfer_t spi_xfer = { + .txbuf = (uint8_t *) write_sram, + .size = sizeof(write_sram), + .flags = OMV_SPI_XFER_NONBLOCK, + .callback = spi_tv_callback, + }; + uint32_t irq_state = disable_irq(); + omv_spi_transfer_start(&spi_bus, &spi_xfer); + enable_irq(irq_state); + break; + } + case SPI_TX_CB_MEMORY_WRITE: { + uint8_t *addr = spi_tx_cb_state_memory_write_addr; + size_t count = IM_MIN(spi_tx_cb_state_memory_write_count, OMV_SPI_MAX_8BIT_XFER); + spi_tx_cb_state = (spi_tx_cb_state_memory_write_count > OMV_SPI_MAX_8BIT_XFER) + ? SPI_TX_CB_MEMORY_WRITE + : SPI_TX_CB_MEMORY_WRITE_CMD; + spi_tx_cb_state_memory_write_addr += count; + spi_tx_cb_state_memory_write_count -= count; + omv_spi_transfer_t spi_xfer = { + .txbuf = addr, + .size = count, + .flags = OMV_SPI_XFER_DMA, + .callback = spi_tv_callback, + }; + omv_spi_transfer_start(&spi_bus, &spi_xfer); + break; + } + default: { + break; + } + } + } +} + +// Convert a 8-bit Grayscale line of pixels to 12-bit YUV422 with padding (line is 16-bit per pixel). +static void spi_tv_draw_image_cb_convert_grayscale(uint8_t *row_pointer_i, uint8_t *row_pointer_o) { + for (int i = TV_WIDTH - 2, j = ((TV_WIDTH * 3) / 2) - 3; i >= 0; i -= 2, j -= 3) { + int y0 = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_pointer_i, i); + int y1 = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_pointer_i, i + 1); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_pointer_o, j, 0); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_pointer_o, j + 1, y0); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_pointer_o, j + 2, y1); + } +} + +// Convert a 16-bit RGB565 line of pixels to 12-bit YUV422 with padding (line is 16-bit per pixel). +static void spi_tv_draw_image_cb_convert_rgb565(uint16_t *row_pointer_i, uint8_t *row_pointer_o) { + for (int i = 0, j = 0; i < TV_WIDTH; i += 2, j += 3) { + #if defined(ARM_MATH_DSP) + + int pixels = *((uint32_t *) (row_pointer_i + i)); + 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; + int u = __SSUB16(b_pixels * 64, (r_pixels * 21) + (g_pixels * 43)); + int v = __SSUB16(r_pixels * 64, (g_pixels * 54) + (b_pixels * 10)); + + int y0 = __UXTB_RORn(y, 0), y1 = __UXTB_RORn(y, 16); + + int u_avg = __SMUAD(u, 0x00010001) >> 7; + int v_avg = __SMUAD(v, 0x00010001) >> 7; + + #else + + int pixel0 = IMAGE_GET_RGB565_PIXEL_FAST(row_pointer_i, i); + int r0 = COLOR_RGB565_TO_R8(pixel0); + int g0 = COLOR_RGB565_TO_G8(pixel0); + int b0 = COLOR_RGB565_TO_B8(pixel0); + int y0 = COLOR_RGB888_TO_Y(r0, g0, b0); + int u0 = COLOR_RGB888_TO_U(r0, g0, b0); + int v0 = COLOR_RGB888_TO_V(r0, g0, b0); + + int pixel1 = IMAGE_GET_RGB565_PIXEL_FAST(row_pointer_i, i + 1); + int r1 = COLOR_RGB565_TO_R8(pixel1); + int g1 = COLOR_RGB565_TO_G8(pixel1); + int b1 = COLOR_RGB565_TO_B8(pixel1); + int y1 = COLOR_RGB888_TO_Y(r1, g1, b1); + int u1 = COLOR_RGB888_TO_U(r1, g1, b1); + int v1 = COLOR_RGB888_TO_V(r1, g1, b1); + + int u_avg = u0 + u1; + int v_avg = v0 + v1; + + #endif + + int uv = ((u_avg >> 1) & 0xf0) | (((-v_avg) >> 5) & 0xf); + + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_pointer_o, j, uv); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_pointer_o, j + 1, y0); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_pointer_o, j + 2, y1); + } +} + +static void spi_tv_draw_image_cb_grayscale(int x_start, int x_end, int y_row, imlib_draw_row_data_t *data) { + memset(((uint8_t *) data->dst_row_override) + x_end, 0, TV_WIDTH - x_end); // clear trailing bytes. + spi_tv_draw_image_cb_convert_grayscale((uint8_t *) data->dst_row_override, (uint8_t *) data->dst_row_override); + SpiTransmitReceivePacket(data->dst_row_override, NULL, PICLINE_LENGTH_BYTES, false); +} + +static void spi_tv_draw_image_cb_rgb565(int x_start, int x_end, int y_row, imlib_draw_row_data_t *data) { + memset(data->dst_row_override, 0, x_start * sizeof(uint16_t)); // clear leading bytes. + spi_tv_draw_image_cb_convert_rgb565((uint16_t *) data->dst_row_override, (uint8_t *) data->dst_row_override); + SpiTransmitReceivePacket(data->dst_row_override, NULL, PICLINE_LENGTH_BYTES, false); +} + +static void spi_tv_display(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) { + bool rgb565 = ((rgb_channel == -1) && src_img->is_color) || color_palette; + imlib_draw_row_callback_t cb = rgb565 ? spi_tv_draw_image_cb_rgb565 : spi_tv_draw_image_cb_grayscale; + + image_t dst_img; + dst_img.w = TV_WIDTH; + dst_img.h = TV_HEIGHT; + dst_img.pixfmt = rgb565 ? PIXFORMAT_RGB565 : PIXFORMAT_GRAYSCALE; + + point_t p0, p1; + imlib_draw_image_get_bounds(&dst_img, src_img, dst_x_start, dst_y_start, x_scale, y_scale, + roi, alpha, alpha_palette, hint, &p0, &p1); + bool black = p0.x == -1; + + if (!tv_triple_buffer) { + dst_img.data = fb_alloc0(TV_WIDTH_RGB565, FB_ALLOC_NO_HINT); + + SpiTransmitReceivePacket((uint8_t *) write_sram, NULL, sizeof(write_sram), false); + + if (black) { + // zero the whole image + for (int i = 0; i < TV_HEIGHT; i++) { + SpiTransmitReceivePacket(dst_img.data, NULL, PICLINE_LENGTH_BYTES, false); + } + } else { + // Zero the top rows + for (int i = 0; i < p0.y; i++) { + SpiTransmitReceivePacket(dst_img.data, NULL, PICLINE_LENGTH_BYTES, false); + } + + // Transmits left/right parts already zeroed... + imlib_draw_image(&dst_img, src_img, dst_x_start, dst_y_start, x_scale, y_scale, roi, + rgb_channel, alpha, color_palette, alpha_palette, hint | IMAGE_HINT_BLACK_BACKGROUND, + cb, NULL, dst_img.data); + + // Zero the bottom rows + if (p1.y < TV_HEIGHT) { + memset(dst_img.data, 0, TV_WIDTH_RGB565); + } + + for (int i = p1.y; i < TV_HEIGHT; i++) { + SpiTransmitReceivePacket(dst_img.data, NULL, PICLINE_LENGTH_BYTES, false); + } + } + + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1); + if (dst_img.data) fb_free(dst_img.data); + } else { + // For triple buffering we are never drawing where head or tail (which may instantly update to + // to be equal to head) is. + int new_framebuffer_head = (framebuffer_head + 1) % FRAMEBUFFER_COUNT; + if (new_framebuffer_head == framebuffer_tail) { + new_framebuffer_head = (new_framebuffer_head + 1) % FRAMEBUFFER_COUNT; + } + + dst_img.data = (uint8_t *) framebuffers[new_framebuffer_head]; + + if (rgb565) { + if (black) { + // zero the whole image + memset(dst_img.data, 0, TV_WIDTH * TV_HEIGHT * sizeof(uint16_t)); + } else { + // Zero the top rows + if (p0.y) { + memset(dst_img.data, 0, TV_WIDTH * p0.y * sizeof(uint16_t)); + } + + if (p0.x) { + for (int i = p0.y; i < p1.y; i++) { + // Zero left + memset(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&dst_img, i), 0, p0.x * sizeof(uint16_t)); + } + } + + imlib_draw_image(&dst_img, src_img, dst_x_start, dst_y_start, x_scale, y_scale, roi, + rgb_channel, alpha, color_palette, alpha_palette, hint | IMAGE_HINT_BLACK_BACKGROUND, + NULL, NULL, NULL); + + if (TV_WIDTH - p1.x) { + for (int i = p0.y; i < p1.y; i++) { + // Zero right + memset(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&dst_img, i) + p1.x, 0, + (TV_WIDTH - p1.x) * sizeof(uint16_t)); + } + } + + // Zero the bottom rows + if (TV_HEIGHT - p1.y) { + memset(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&dst_img, p1.y), 0, + TV_WIDTH * (TV_HEIGHT - p1.y) * sizeof(uint16_t)); + } + } + + for (int i = 0; i < TV_HEIGHT; i++) { + // Convert the image. + spi_tv_draw_image_cb_convert_rgb565(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&dst_img, i), + dst_img.data + (PICLINE_LENGTH_BYTES * i)); + } + } else { + if (black) { + // zero the whole image + memset(dst_img.data, 0, TV_WIDTH * TV_HEIGHT * sizeof(uint8_t)); + } else { + // Zero the top rows + if (p0.y) { + memset(dst_img.data, 0, TV_WIDTH * p0.y * sizeof(uint8_t)); + } + + if (p0.x) { + for (int i = p0.y; i < p1.y; i++) { + // Zero left + memset(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&dst_img, i), 0, p0.x * sizeof(uint8_t)); + } + } + + imlib_draw_image(&dst_img, src_img, dst_x_start, dst_y_start, x_scale, y_scale, roi, + rgb_channel, alpha, color_palette, alpha_palette, hint | IMAGE_HINT_BLACK_BACKGROUND, + NULL, NULL, NULL); + + if (TV_WIDTH - p1.x) { + for (int i = p0.y; i < p1.y; i++) { + // Zero right + memset(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&dst_img, i) + p1.x, 0, + (TV_WIDTH - p1.x) * sizeof(uint8_t)); + } + } + + // Zero the bottom rows + if (TV_HEIGHT - p1.y) { + memset(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&dst_img, p1.y), 0, + TV_WIDTH * (TV_HEIGHT - p1.y) * sizeof(uint8_t)); + } + } + + for (int i = TV_HEIGHT - 1; i >= 0; i--) { + // Convert the image. + spi_tv_draw_image_cb_convert_grayscale(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&dst_img, i), + dst_img.data + (PICLINE_LENGTH_BYTES * i)); + } + } + + #ifdef __DCACHE_PRESENT + // Flush data for DMA + SCB_CleanDCache(); + #endif + + // Update head which means a new image is ready. + framebuffer_head = new_framebuffer_head; + + // Kick off an update of the display. + if (spi_tx_cb_state == SPI_TX_CB_IDLE) { + spi_tx_cb_state = SPI_TX_CB_MEMORY_WRITE_CMD; + spi_tv_callback(&spi_bus, NULL, NULL); + } + } +} +#endif + +STATIC mp_obj_t py_tv_deinit() { + switch (tv_type) { + #ifdef OMV_SPI_DISPLAY_CONTROLLER + case TV_SHIELD: { + spi_config_deinit(); + break; + } + #endif + default: + break; + } + + tv_type = TV_NONE; + tv_triple_buffer = false; + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_tv_deinit_obj, py_tv_deinit); + +STATIC mp_obj_t py_tv_init(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_type, ARG_triple_buffer }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_type, MP_ARG_INT, {.u_int = TV_SHIELD } }, + { MP_QSTR_triple_buffer, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = TV_TRIPLE_BUFFER_DEFAULT } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + py_tv_deinit(); + tv_type = args[ARG_type].u_int; + tv_triple_buffer = args[ARG_triple_buffer].u_bool; + + switch (tv_type) { + #ifdef OMV_SPI_DISPLAY_CONTROLLER + case TV_SHIELD: + spi_config_init(tv_triple_buffer); + break; + #endif + default: + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Failed to detect a supported TV controller.")); + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_tv_init_obj, 0, py_tv_init); + +STATIC mp_obj_t py_tv_width() { + if (tv_type != TV_NONE) { + return mp_obj_new_int(TV_WIDTH); + } + + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TV controller is not initialized")); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_tv_width_obj, py_tv_width); + +STATIC mp_obj_t py_tv_height() { + if (tv_type != TV_NONE) { + return mp_obj_new_int(TV_HEIGHT); + } + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TV controller is not initialized")); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_tv_height_obj, py_tv_height); + +STATIC mp_obj_t py_tv_type() { + if (tv_type != TV_NONE) { + return mp_obj_new_int(tv_type); + } + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TV controller is not initialized")); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_tv_type_obj, py_tv_type); + +STATIC mp_obj_t py_tv_triple_buffer() { + if (tv_type != TV_NONE) { + return mp_obj_new_int(tv_triple_buffer); + } + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TV controller is not initialized")); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_tv_triple_buffer_obj, py_tv_triple_buffer); + +STATIC mp_obj_t py_tv_refresh() { + if (tv_type != TV_NONE) { + return mp_obj_new_int(TV_REFRESH); + } + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TV controller is not initialized")); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_tv_refresh_obj, py_tv_refresh); + +STATIC mp_obj_t py_tv_channel(uint n_args, const mp_obj_t *args) { + if (tv_type == TV_NONE) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TV controller is not initialized")); + } + + #ifdef OMV_SPI_DISPLAY_CONTROLLER + if (tv_triple_buffer) { + omv_spi_transfer_abort(&spi_bus); + spi_tx_cb_state = SPI_TX_CB_IDLE; + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1); + } + + if (n_args) { + int channel = mp_obj_get_int(*args); + + if ((channel < 1) || (8 < channel)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Channel ranges between 1 and 8")); + } + + SpiRamWriteByteRegister(WRITE_GPIO, 0x70 | (channel - 1)); + return mp_const_none; + } else { + #ifdef OMV_SPI_DISPLAY_RX_CLK_DIV + omv_spi_set_baudrate(&spi_bus, TV_BAUDRATE / OMV_SPI_DISPLAY_RX_CLK_DIV); + #endif + int channel = SpiRamReadByteRegister(READ_GPIO); + #ifdef OMV_SPI_DISPLAY_RX_CLK_DIV + omv_spi_set_baudrate(&spi_bus, TV_BAUDRATE); + #endif + return mp_obj_new_int((channel & 0x7) + 1); + } + #endif +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_tv_channel_obj, 0, 1, py_tv_channel); + +STATIC mp_obj_t py_tv_display(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { + ARG_x, ARG_y, ARG_x_scale, ARG_y_scale, ARG_roi, ARG_channel, ARG_alpha, + ARG_color_palette, ARG_alpha_palette, ARG_hint + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_x, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0 } }, + { MP_QSTR_y, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0 } }, + { MP_QSTR_x_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_y_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_roi, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_rgb_channel, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + { MP_QSTR_alpha, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 256 } }, + { MP_QSTR_color_palette, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_alpha_palette, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_hint, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0 } }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args + 1, pos_args - 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + image_t *image = py_helper_arg_to_image(pos_args[0], 0); + rectangle_t roi = py_helper_arg_to_roi(args[ARG_roi].u_obj, image); + + if (args[ARG_channel].u_int < -1 || args[ARG_channel].u_int > 2) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("RGB channel can be 0, 1, or 2")); + } + + if (args[ARG_alpha].u_int < 0 || args[ARG_alpha].u_int > 256) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Alpha ranges between 0 and 256")); + } + + float x_scale = 1.0f; + float y_scale = 1.0f; + py_helper_arg_to_scale(args[ARG_x_scale].u_obj, args[ARG_y_scale].u_obj, &x_scale, &y_scale); + + const uint16_t *color_palette = py_helper_arg_to_palette(args[ARG_color_palette].u_obj, PIXFORMAT_RGB565); + const uint8_t *alpha_palette = py_helper_arg_to_palette(args[ARG_alpha_palette].u_obj, PIXFORMAT_GRAYSCALE); + + switch (tv_type) { + #ifdef OMV_SPI_DISPLAY_CONTROLLER + case TV_SHIELD: { + fb_alloc_mark(); + spi_tv_display(image, args[ARG_x].u_int, args[ARG_y].u_int, x_scale, y_scale, &roi, + args[ARG_channel].u_int, args[ARG_alpha].u_int, color_palette, alpha_palette, + args[ARG_hint].u_int); + fb_alloc_free_till_mark(); + break; + } + #endif + default: + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TV controller is not initialized")); + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_tv_display_obj, 1, py_tv_display); + +STATIC mp_obj_t py_tv_clear() { + switch (tv_type) { + #ifdef OMV_SPI_DISPLAY_CONTROLLER + case TV_SHIELD: { + fb_alloc_mark(); + spi_tv_display(NULL, 0, 0, 1.f, 1.f, NULL, + 0, 0, NULL, NULL, 0); + fb_alloc_free_till_mark(); + break; + } + #endif + default: { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("TV controller is not initialized")); + } + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_tv_clear_obj, py_tv_clear); + +STATIC const mp_rom_map_elem_t globals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_tv) }, + { MP_ROM_QSTR(MP_QSTR_TV_NONE), MP_ROM_INT(TV_NONE) }, + { MP_ROM_QSTR(MP_QSTR_TV_SHIELD), MP_ROM_INT(TV_SHIELD) }, + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&py_tv_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&py_tv_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&py_tv_width_obj) }, + { MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&py_tv_height_obj) }, + { MP_ROM_QSTR(MP_QSTR_type), MP_ROM_PTR(&py_tv_type_obj) }, + { MP_ROM_QSTR(MP_QSTR_triple_buffer), MP_ROM_PTR(&py_tv_triple_buffer_obj) }, + { MP_ROM_QSTR(MP_QSTR_refresh), MP_ROM_PTR(&py_tv_refresh_obj) }, + { MP_ROM_QSTR(MP_QSTR_channel), MP_ROM_PTR(&py_tv_channel_obj) }, + { MP_ROM_QSTR(MP_QSTR_display), MP_ROM_PTR(&py_tv_display_obj) }, + { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&py_tv_clear_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); + +const mp_obj_module_t tv_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_t) &globals_dict, +}; + +void py_tv_init0() { + py_tv_deinit(); +} + +MP_REGISTER_MODULE(MP_QSTR_tv, tv_module); + +#endif // MICROPY_PY_TV diff --git a/components/3rd_party/omv/omv/modules/py_tv.h b/components/3rd_party/omv/omv/modules/py_tv.h new file mode 100644 index 00000000..2342f8a9 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/py_tv.h @@ -0,0 +1,15 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2020 Ibrahim Abdelkader + * Copyright (c) 2013-2020 Kwabena W. Agyeman + * Copyright (c) 2013-2020 Kaizhi Wong + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * TV Python module. + */ +#ifndef __PY_TV_H__ +#define __PY_TV_H__ +void py_tv_init0(); +#endif // __PY_TV_H__ diff --git a/components/3rd_party/omv/omv/modules/ulab/.github/ISSUE_TEMPLATE/bug_report.md b/components/3rd_party/omv/omv/modules/ulab/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..23520f2f --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,25 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG]" +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. Give the `ulab` version + +```python +import ulab +print(ulab.__version__) +``` + +**To Reproduce** +Describe the steps to reproduce the behavior. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Additional context** +Add any other context that might help to locate the root of the problem. diff --git a/components/3rd_party/omv/omv/modules/ulab/.github/ISSUE_TEMPLATE/feature_request.md b/components/3rd_party/omv/omv/modules/ulab/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..bcd832cf --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,14 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[FEATURE REQUEST]" +labels: enhancement +assignees: '' + +--- + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. If possible, link to the `numpy/scipy` function. + +**Additional context** +Add any other context about the feature request here. diff --git a/components/3rd_party/omv/omv/modules/ulab/.github/workflows/build.yml b/components/3rd_party/omv/omv/modules/ulab/.github/workflows/build.yml new file mode 100644 index 00000000..d6587f5b --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/.github/workflows/build.yml @@ -0,0 +1,96 @@ +name: Build CI + +on: + push: + pull_request: + paths: + - 'code/**' + - 'tests/**' + - '.github/workflows/**' + - 'build*.sh' + - 'requirements*.txt' + release: + types: [published] + check_suite: + types: [rerequested] + +jobs: + micropython: + strategy: + matrix: + os: + - ubuntu-latest + - macOS-latest + dims: [1, 2, 3, 4] + runs-on: ${{ matrix.os }} + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - name: Set up Python 3.10 + uses: actions/setup-python@v1 + with: + python-version: "3.10" + + - name: Install requirements + run: | + if type -path apt-get; then + sudo apt-get install gcc-multilib + fi + + - name: Versions + run: | + gcc --version + python3 --version + - name: Checkout ulab + uses: actions/checkout@v1 + + - name: Checkout micropython repo + uses: actions/checkout@v2 + with: + repository: micropython/micropython + path: micropython + + - name: Run build.sh + run: ./build.sh ${{ matrix.dims }} + + circuitpython: + strategy: + matrix: + os: + - ubuntu-20.04 + - macOS-latest + dims: [1, 2, 3, 4] + runs-on: ${{ matrix.os }} + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - name: Set up Python 3.10 + uses: actions/setup-python@v1 + with: + python-version: "3.10" + + - name: Versions + run: | + gcc --version + python3 --version + + - name: Checkout ulab + uses: actions/checkout@v1 + + - name: Install requirements + run: | + if type -path apt-get; then + sudo apt-get install gettext librsvg2-bin + else + brew install gettext librsvg + echo >>$GITHUB_PATH /usr/local/opt/gettext/bin + echo >>$GITHUB_PATH /usr/local/opt/librsvg/bin + fi + python3 -mpip install --upgrade -r requirements_cp_dev.txt + + - name: Run build-cp.sh + run: ./build-cp.sh ${{ matrix.dims }} diff --git a/components/3rd_party/omv/omv/modules/ulab/.gitignore b/components/3rd_party/omv/omv/modules/ulab/.gitignore new file mode 100644 index 00000000..e7dd0952 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/.gitignore @@ -0,0 +1,13 @@ +/micropython +/circuitpython +/*.exp +/*.out +/docs/manual/build/ +/docs/manual/source/**/*.pyi +/docs/.ipynb_checkpoints/ +/docs/ulab-test.ipynb +/code/.atom-build.yml +build/micropython +build/ulab + +.idea \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/.readthedocs.yaml b/components/3rd_party/omv/omv/modules/ulab/.readthedocs.yaml new file mode 100644 index 00000000..2d51ccea --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/.readthedocs.yaml @@ -0,0 +1,24 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-20.04 + tools: + python: "3.9" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/manual/source/conf.py + +# If using Sphinx, optionally build your docs in additional formats such as PDF +formats: all + +# Optionally declare the Python requirements required to build your docs +python: + install: + - requirements: requirements.txt diff --git a/components/3rd_party/omv/omv/modules/ulab/CONTRIBUTING.md b/components/3rd_party/omv/omv/modules/ulab/CONTRIBUTING.md new file mode 100644 index 00000000..edfdc7a8 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/CONTRIBUTING.md @@ -0,0 +1,17 @@ +Contributions of any kind are always welcome. + +# Contributing to the code base + +If you feel like adding to the code, you can simply issue a pull request. If you do so, please, try to adhere to `micropython`'s [coding conventions](https://github.com/micropython/micropython/blob/master/CODECONVENTIONS.md#c-code-conventions). + +# Documentation + +However, you can also contribute to the documentation (preferably via the [jupyter notebooks](https://github.com/v923z/micropython-ulab/tree/master/docs). + +## Testing + +If you decide to lend a hand with testing, here are the steps: + +1. Write a test script that checks a particular function, or a set of related functions! +1. Drop this script in one of the folders in [ulab tests](https://github.com/v923z/micropython-ulab/tree/master/tests)! +1. Run the [./build.sh](https://github.com/v923z/micropython-ulab/blob/master/build.sh) script in the root directory of `ulab`! This will clone the latest `micropython`, compile the firmware for `unix`, execute all scripts in the `ulab/tests`, and compare the results to those in the expected results files, which are also in `ulab/tests`, and have an extension `.exp`. In case you have a new snippet, i.e., you have no expected results file, or if the results differ from those in the expected file, a new expected file will be generated in the root directory. You should inspect the contents of this file, and if they are satisfactory, then the file can be moved to the `ulab/tests` folder, alongside your snippet. diff --git a/components/3rd_party/omv/omv/modules/ulab/LICENSE b/components/3rd_party/omv/omv/modules/ulab/LICENSE new file mode 100644 index 00000000..1d4df66d --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Zoltán Vörös + +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/components/3rd_party/omv/omv/modules/ulab/README.md b/components/3rd_party/omv/omv/modules/ulab/README.md new file mode 100644 index 00000000..fa3831a4 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/README.md @@ -0,0 +1,447 @@ +# ulab + +[![Documentation Status](https://readthedocs.org/projects/micropython-ulab-robert/badge/?version=latest)](https://micropython-ulab-robert.readthedocs.io/en/latest/?badge=latest) + +`ulab` is a `numpy`-like array manipulation library for [micropython](http://micropython.org/) and [CircuitPython](https://circuitpython.org/). +The module is written in C, defines compact containers (`ndarray`s) for numerical data of one to four +dimensions, and is fast. The library is a software-only standard `micropython` user module, +i.e., it has no hardware dependencies, and can be compiled for any platform. 8-, and 16-bit signed +and unsigned integer `dtypes`, as well as `float`, and, optionally, ` complex` are supported. +The `float` implementation of `micropython` (32-bit `float`, or 64-bit `double`) is automatically +detected and handled. + +1. [Supported functions and methods](#supported-functions-and-methods) + 1. [ndarray methods](#ndarray-methods) + 2. [numpy and scipy functions](#numpy-and-scipy-functions) + 3. [ulab utilities](#ulab-utilities) + 4. [user module](#user-module) +4. [Usage](#usage) +5. [Finding help](#finding-help) +6. [Benchmarks](#benchmarks) +7. [Firmware](#firmware) + 1. [Customising the firmware](#customising-the-firmware) + 1. [Platforms including ulab](#platforms-including-ulab) + 1. [Compiling](#compiling) + 1. [UNIX](#unix-port) + 1. [STM-based boards](#stm-based-boards) + 1. [ESP32-based boards](#esp32-based-boards) + 1. [RP2-based boards](#rp2-based-boards) + 1. [Compiling for CircuitPython](#compiling-for-circuitpython) +8. [Issues, contributing, and testing](#issues-contributing-and-testing) + 1. [Testing](#testing) + +# Supported functions and methods + + +## ndarray methods + +`ulab` implements `numpy`'s `ndarray` with the `==`, `!=`, `<`, `<=`, `>`, `>=`, `+`, `-`, `/`, `*`, `**`, +`+=`, `-=`, `*=`, `/=`, `**=` binary operators, and the `len`, `~`, `-`, `+`, `abs` unary operators that +operate element-wise. Type-aware `ndarray`s can be initialised from any `micropython` iterable, lists of +iterables via the `array` constructor, or by means of the `arange`, `concatenate`, `diag`, `eye`, +`frombuffer`, `full`, `linspace`, `logspace`, `ones`, or `zeros` functions. + +`ndarray`s can be sliced, and iterated on, and have a number of their own methods, and properties, such as `flatten()`, `itemsize`, `reshape()`, +`shape`, `size`, `strides`, `tobytes()`, `tolist()`, and `transpose()` and `T`. If the firmware is compiled with `complex` support, +the `imag`, and `real` properties are automatically included. + +## `numpy` and `scipy` functions + +In addition, `ulab` includes [universal functions](https://micropython-ulab.readthedocs.io/en/latest/numpy-universal.html), [many `numpy` functions](https://micropython-ulab.readthedocs.io/en/latest/numpy-functions.html), and functions from the [`numpy.fft`](https://micropython-ulab.readthedocs.io/en/latest/numpy-fft.html), [`numpy.linalg`](https://micropython-ulab.readthedocs.io/en/latest/numpy-linalg.html), [`scipy.linalg`](https://micropython-ulab.readthedocs.io/en/latest/scipy-linalg.html), [`scipy.optimize`](https://micropython-ulab.readthedocs.io/en/latest/scipy-optimize.html), [`scipy.signal`](https://micropython-ulab.readthedocs.io/en/latest/scipy-signal.html), and [`scipy.special`](https://micropython-ulab.readthedocs.io/en/latest/scipy-special.html) modules. A complete list of available routines can be found under [micropython-ulab](https://micropython-ulab.readthedocs.io/en/latest). + +## `ulab` utilities + +The [`utils`](https://micropython-ulab.readthedocs.io/en/latest/ulab-utils.html) module contains functions for +interfacing with peripheral devices supporting the buffer protocol. These functions do not have an obvious +`numpy` equivalent, but share a similar programming interface, and allow direct data input-output between +numerical arrays and hardware components. + +## `user` module + +User-defined functions operating on numerical data can easily be added via the `user` module. This allows for transparent extensions, without having to change anything in the core. Hints as to how to work with `ndarray`s at the C level can be found in the [programming manual](https://micropython-ulab.readthedocs.io/en/latest/ulab-programming.html). + +# Usage + +`ulab` sports a `numpy/scipy`-compatible interface, which makes porting of `CPython` code straightforward. The following +snippet should run equally well in `micropython`, or on a PC. + +```python +try: + from ulab import numpy + from ulab import scipy +except ImportError: + import numpy + import scipy.special + +x = numpy.array([1, 2, 3]) +scipy.special.erf(x) +``` + +# Finding help + +Documentation can be found on [readthedocs](https://readthedocs.org/) under +[micropython-ulab](https://micropython-ulab.readthedocs.io/en/latest), +as well as at [circuitpython-ulab](https://circuitpython.readthedocs.io/en/latest/shared-bindings/ulab/__init__.html). +A number of practical examples are listed in Jeff Epler's excellent +[circuitpython-ulab](https://learn.adafruit.com/ulab-crunch-numbers-fast-with-circuitpython/overview) overview. +The [tricks](https://micropython-ulab.readthedocs.io/en/latest/ulab-tricks.html) chapter of the user manual discusses +methods by which RAM and speed can be leveraged in particular numerical problems. + +# Benchmarks + +Representative numbers on performance can be found under [ulab samples](https://github.com/thiagofe/ulab_samples). + +# Firmware + +Pre-built, and up-to-date firmware files for select platforms can be downloaded +from [micropython-builder](https://github.com/v923z/micropython-builder). +## Customising the firmware + +If flash space is a concern, unnecessary functions can be excluded from the compiled firmware with +pre-processor switches. In addition, `ulab` also has options for trading execution speed for firmware size. +A thorough discussion on how the firmware can be customised can be found in the +[corresponding section](https://micropython-ulab.readthedocs.io/en/latest/ulab-intro.html#customising-the-firmware) +of the user manual. + +## Platforms including ulab + +`ulab` is also included in the following compiled `micropython` variants and derivatives: + +1. `CircuitPython` for SAMD51 and nRF microcontrollers https://github.com/adafruit/circuitpython +1. `MicroPython for K210` https://github.com/loboris/MicroPython_K210_LoBo +1. `MaixPy` https://github.com/sipeed/MaixPy +1. `OpenMV` https://github.com/openmv/openmv +1. `pimoroni-pico` https://github.com/pimoroni/pimoroni-pico +3. `pycom` https://pycom.io/ + +## Compiling + +If you want to try the latest version of `ulab` on `micropython` or one of its forks, the firmware can be compiled +from the source by following these steps: + +### UNIX port + +Simply clone the `ulab` repository with + +```bash +git clone https://github.com/v923z/micropython-ulab.git ulab +``` +and then run + +```bash +./build.sh [matrix.dims] # Dimensions is 2 by default +``` +This command will clone `micropython`, and build the `unix` port automatically, as well as run the test scripts. If you want an interactive `unix` session, you can launch it in + +```bash +ulab/micropython/ports/unix +``` + +### STM-based boards + +First, you have to clone the `micropython` repository by running + +```bash +git clone https://github.com/micropython/micropython.git +``` +on the command line. This will create a new repository with the name `micropython`. Staying there, clone the `ulab` repository with + +```bash +git clone https://github.com/v923z/micropython-ulab.git ulab +``` +If you don't have the cross-compiler installed, your might want to do that now, for instance on Linux by executing + +```bash +sudo apt-get install gcc-arm-none-eabi +``` + +If this step was successful, you can try to run the `make` command in the port's directory as + +```bash +make BOARD=PYBV11 USER_C_MODULES=../../../ulab all +``` +which will prepare the firmware for pyboard.v.11. Similarly, + +```bash +make BOARD=PYBD_SF6 USER_C_MODULES=../../../ulab all +``` +will compile for the SF6 member of the PYBD series. If your target is `unix`, you don't need to specify the `BOARD` parameter. + +Provided that you managed to compile the firmware, you would upload that by running either + +```bash +dfu-util --alt 0 -D firmware.dfu +``` +or + +```bash +python pydfu.py -u firmware.dfu +``` + +In case you got stuck somewhere in the process, a bit more detailed instructions can be found under https://github.com/micropython/micropython/wiki/Getting-Started, and https://github.com/micropython/micropython/wiki/Pyboard-Firmware-Update. + + +### ESP32-based boards + +`ulab` can be tested on the ESP32 in [wokwi's micropython emulator](https://wokwi.com/arduino/projects/322114140704342610) without having to compile the C code. This utility also offers the possibility to save and share your `micropython` code. + +Firmware for `Espressif` hardware can be built in two different ways, which are discussed in the next two paragraphs. A solution for issues with the firmware size is outlined in the [last paragraph](#what-to-do-if-the-firmware-is-too-large) of this section. + +#### Compiling with cmake + +Beginning with version 1.15, `micropython` switched to `cmake` on the ESP32 port. If your operating system supports `CMake > 3.12`, you can either simply download, and run the single [build script](https://github.com/v923z/micropython-ulab/blob/master/build/esp32-cmake.sh), or follow the step in this section. Otherwise, you should skip to the [next one](#compiling-with-make), where the old, `make`-based approach is discussed. + +In case you encounter difficulties during the build process, you can consult the (general instructions for the ESP32)[https://github.com/micropython/micropython/tree/master/ports/esp32#micropython-port-to-the-esp32]. + +First, clone the `ulab`, the `micropython`, as well as the `espressif` repositories: + +```bash +export BUILD_DIR=$(pwd) + +git clone https://github.com/v923z/micropython-ulab.git ulab +git clone https://github.com/micropython/micropython.git + +cd $BUILD_DIR/micropython/ + +git clone -b v4.0.2 --recursive https://github.com/espressif/esp-idf.git + +``` +Also later releases of `esp-idf` are possible (e.g. `v4.2.1`). + +Then install the `ESP-IDF` tools: + +```bash +cd esp-idf +./install.sh +. ./export.sh +``` + +Next, build the `micropython` cross-compiler, and the `ESP` sub-modules: + +```bash +cd $BUILD_DIR/micropython/mpy-cross +make +cd $BUILD_DIR/micropython/ports/esp32 +make submodules +``` +At this point, all requirements are installed and built. We can now compile the firmware with `ulab`. In `$BUILD_DIR/micropython/ports/esp32` create a `makefile` with the following content: + +```bash +BOARD = GENERIC +USER_C_MODULES = $(BUILD_DIR)/ulab/code/micropython.cmake + +include Makefile +``` +You specify with the `BOARD` variable, what you want to compile for, a generic board, or `TINYPICO` (for `micropython` version >1.1.5, use `UM_TINYPICO`), etc. Still in `$BUILD_DIR/micropython/ports/esp32`, you can now run `make`. + +#### Compiling with make + +If your operating system does not support a recent enough version of `CMake`, you have to stay with `micropython` version 1.14. The firmware can be compiled either by downloading and running the [build script](https://github.com/v923z/micropython-ulab/blob/master/build/esp32.sh), or following the steps below: + +First, clone `ulab` with + +```bash +git clone https://github.com/v923z/micropython-ulab.git ulab +``` + +and then, in the same directory, `micropython` + +```bash +git clone https://github.com/micropython/micropython.git +``` + +At this point, you should have `ulab`, and `micropython` side by side. + +With version 1.14, `micropython` switched to `cmake` on the `ESP32` port, thus breaking compatibility with user modules. `ulab` can, however, still be compiled with version 1.14. You can check out a particular version by pinning the release tag as + +```bash + +cd ./micropython/ +git checkout tags/v1.14 + +``` +Next, update the submodules, + +```bash +git submodule update --init +cd ./mpy-cross && make # build cross-compiler (required) +``` +and find the ESP commit hash + +```bash +cd ./micropython/ports/esp32 +make ESPIDF= # will display supported ESP-IDF commit hashes +# output should look like: """ +# ... +# Supported git hash (v3.3): 9e70825d1e1cbf7988cf36981774300066580ea7 +# Supported git hash (v4.0) (experimental): 4c81978a3e2220674a432a588292a4c860eef27b +``` + +Choose an ESPIDF version from one of the options printed by the previous command: + +```bash +ESPIDF_VER=9e70825d1e1cbf7988cf36981774300066580ea7 +``` + +In the `micropython` directory, create a new directory with +```bash +mkdir esp32 +``` +Your `micropython` directory should now look like + +```bash +ls +ACKNOWLEDGEMENTS CONTRIBUTING.md esp32 lib mpy-cross README.md +CODECONVENTIONS.md docs examples LICENSE ports tests +CODEOFCONDUCT.md drivers extmod logo py tools +``` + +In `./micropython/esp32`, download the software development kit with + +```bash +git clone https://github.com/espressif/esp-idf.git esp-idf +cd ./esp-idf +git checkout $ESPIDF_VER +git submodule update --init --recursive # get idf submodules +pip install -r ./requirements.txt # install python reqs +``` + +Next, still staying in `./micropython/eps32/esd-idf/`, install the ESP32 compiler. If using an ESP-IDF version >= 4.x (chosen by `$ESPIDF_VER` above), this can be done by running `. $BUILD_DIR/esp-idf/install.sh`. Otherwise, for version 3.x, run the following commands in in `.micropython/esp32/esp-idf`: + +```bash +# for 64 bit linux +curl https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz | tar xvz + +# for 32 bit +# curl https://dl.espressif.com/dl/xtensa-esp32-elf-linux32-1.22.0-80-g6c4433a-5.2.0.tar.gz | tar xvz + +# don't worry about adding to path; we'll specify that later + +# also, see https://docs.espressif.com/projects/esp-idf/en/v3.3.2/get-started for more info +``` + +Finally, build the firmware: + +```bash +cd ./micropython/ports/esp32 +# temporarily add esp32 compiler to path +export PATH=../../esp32/esp-idf/xtensa-esp32-elf/bin:$PATH +export ESPIDF=../../esp32/esp-idf # req'd by Makefile +export BOARD=GENERIC # options are dirs in ./boards +export USER_C_MODULES=../../../ulab # include ulab in firmware + +make submodules & make all +``` + +If it compiles without error, you can plug in your ESP32 via USB and then flash it with: + +```bash +make erase && make deploy +``` + +#### What to do, if the firmware is too large? + +When selecting `BOARD=TINYPICO`, the firmware is built but fails to deploy, because it is too large for the standard partitions. We can rectify the problem by creating a new partition table. In order to do so, in `$BUILD_DIR/micropython/ports/esp32/`, copy the following 8 lines to a file named `partitions_ulab.cvs`: + +``` +# Notes: the offset of the partition table itself is set in +# $ESPIDF/components/partition_table/Kconfig.projbuild and the +# offset of the factory/ota_0 partition is set in makeimg.py +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 0x200000, +vfs, data, fat, 0x220000, 0x180000, +``` +This expands the `factory` partition by 128 kB, and reduces the size of `vfs` by the same amount. Having defined the new partition table, we should extend `sdkconfig.board` by adding the following two lines: + +``` +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_ulab.csv" +``` +This file can be found in `$BUILD_DIR/micropython/ports/esp32/boards/TINYPICO/`. Finally, run `make clean`, and `make`. The new firmware contains the modified partition table, and should fit on the microcontroller. + +### RP2-based boards + +RP2 firmware can be compiled either by downloading and running the single [build script](https://github.com/v923z/micropython-ulab/blob/master/build/rp2.sh)/[build script for Pico W](https://github.com/v923z/micropython-ulab/blob/master/build/rp2w.sh), or executing the commands below. + +First, clone `micropython`: + +```bash +git clone https://github.com/micropython/micropython.git +``` + +Then, setup the required submodules: + +```bash +cd micropython +git submodule update --init lib/tinyusb +git submodule update --init lib/pico-sdk +cd lib/pico-sdk +git submodule update --init lib/tinyusb +``` + +You'll also need to compile `mpy-cross`: + +```bash +cd ../../mpy-cross +make +``` + +That's all you need to do for the `micropython` repository. Now, let us clone `ulab` (in a directory outside the micropython repository): + +```bash +cd ../../ +git clone https://github.com/v923z/micropython-ulab ulab +``` + +With this setup, we can now build the firmware. Back in the `micropython` repository, use these commands: + +```bash +cd ports/rp2 +make USER_C_MODULES=/path/to/ulab/code/micropython.cmake +``` + +If `micropython` and `ulab` were in the same folder on the computer, you can set `USER_C_MODULES=../../../ulab/code/micropython.cmake`. The compiled firmware will be placed in `micropython/ports/rp2/build`. + +# Compiling for CircuitPython + +[Adafruit Industries](www.adafruit.com) always include a relatively recent version of `ulab` in their nightly builds. However, if you really need the bleeding edge, you can easily compile the firmware from the source. Simply clone `circuitpython`, and move the commit pointer to the latest version of `ulab` (`ulab` will automatically be cloned with `circuitpython`): + +```bash +git clone https://github.com/adafruit/circuitpython.git + +cd circuitpyton/extmod/ulab + +# update ulab here +git checkout master +git pull +``` +You might have to check, whether the `CIRCUITPY_ULAB` variable is set to `1` for the port that you want to compile for. You find this piece of information in the `make` fragment: + +```bash +circuitpython/ports/port_of_your_choice/mpconfigport.mk +``` +After this, you would run `make` with the single `BOARD` argument, e.g.: + +```bash +make BOARD=mini_sam_m4 +``` + +# Issues, contributing, and testing + +If you find a problem with the code, please, raise an [issue](https://github.com/v923z/micropython-ulab/issues)! An issue should address a single problem, and should contain a minimal code snippet that demonstrates the difference from the expected behaviour. Reducing a problem to the bare minimum significantly increases the chances of a quick fix. + +Feature requests (porting a particular function from `numpy` or `scipy`) should also be posted at [ulab issue](https://github.com/v923z/micropython-ulab/issues). + +Contributions of any kind are always welcome. If you feel like adding to the code, you can simply issue a pull request. If you do so, please, try to adhere to `micropython`'s [coding conventions](https://github.com/micropython/micropython/blob/master/CODECONVENTIONS.md#c-code-conventions). + +However, you can also contribute to the documentation (preferably via the [jupyter notebooks](https://github.com/v923z/micropython-ulab/tree/master/docs), or improve the [tests](https://github.com/v923z/micropython-ulab/tree/master/tests). + +## Testing + +If you decide to lend a hand with testing, here are the steps: + +1. Write a test script that checks a particular function, or a set of related functions! +1. Drop this script in one of the folders in [ulab tests](https://github.com/v923z/micropython-ulab/tree/master/tests)! +1. Run the [./build.sh](https://github.com/v923z/micropython-ulab/blob/master/build.sh) script in the root directory of `ulab`! This will clone the latest `micropython`, compile the firmware for `unix`, execute all scripts in the `ulab/tests`, and compare the results to those in the expected results files, which are also in `ulab/tests`, and have an extension `.exp`. In case you have a new snippet, i.e., you have no expected results file, or if the results differ from those in the expected file, a new expected file will be generated in the root directory. You should inspect the contents of this file, and if they are satisfactory, then the file can be moved to the `ulab/tests` folder, alongside your snippet. diff --git a/components/3rd_party/omv/omv/modules/ulab/build-cp.sh b/components/3rd_party/omv/omv/modules/ulab/build-cp.sh new file mode 100644 index 00000000..0f75487e --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/build-cp.sh @@ -0,0 +1,53 @@ +#!/bin/sh +set -e +# POSIX compliant version +readlinkf_posix() { + [ "${1:-}" ] || return 1 + max_symlinks=40 + CDPATH='' # to avoid changing to an unexpected directory + + target=$1 + [ -e "${target%/}" ] || target=${1%"${1##*[!/]}"} # trim trailing slashes + [ -d "${target:-/}" ] && target="$target/" + + cd -P . 2>/dev/null || return 1 + while [ "$max_symlinks" -ge 0 ] && max_symlinks=$((max_symlinks - 1)); do + if [ ! "$target" = "${target%/*}" ]; then + case $target in + /*) cd -P "${target%/*}/" 2>/dev/null || break ;; + *) cd -P "./${target%/*}" 2>/dev/null || break ;; + esac + target=${target##*/} + fi + + if [ ! -L "$target" ]; then + target="${PWD%/}${target:+/}${target}" + printf '%s\n' "${target:-/}" + return 0 + fi + + # `ls -dl` format: "%s %u %s %s %u %s %s -> %s\n", + # , , , , + # , , , + # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html + link=$(ls -dl -- "$target" 2>/dev/null) || break + target=${link#*" $target -> "} + done + return 1 +} +NPROC=$(python3 -c 'import multiprocessing; print(multiprocessing.cpu_count())') +HERE="$(dirname -- "$(readlinkf_posix -- "${0}")" )" +[ -e circuitpython/py/py.mk ] || (git clone --branch main https://github.com/adafruit/circuitpython && cd circuitpython && make fetch-submodules && git submodule update --init lib/uzlib tools) +rm -rf circuitpython/extmod/ulab; ln -s "$HERE" circuitpython/extmod/ulab +dims=${1-2} +make -C circuitpython/mpy-cross -j$NPROC +sed -e '/MICROPY_PY_UHASHLIB/s/1/0/' < circuitpython/ports/unix/mpconfigport.h > circuitpython/ports/unix/mpconfigport_ulab.h +make -k -C circuitpython/ports/unix -j$NPROC DEBUG=1 MICROPY_PY_FFI=0 MICROPY_PY_BTREE=0 MICROPY_SSL_AXTLS=0 MICROPY_PY_USSL=0 CFLAGS_EXTRA="-DMP_CONFIGFILE=\"\" -Wno-tautological-constant-out-of-range-compare -Wno-unknown-pragmas -DULAB_MAX_DIMS=$dims" BUILD=build-$dims PROG=micropython-$dims + +# bash test-common.sh "${dims}" "circuitpython/ports/unix/micropython-$dims" + +# Docs don't depend on the dimensionality, so only do it once +if [ "$dims" -eq 2 ]; then + (cd circuitpython && sphinx-build -E -W -b html . _build/html) + (cd circuitpython && make check-stubs) +fi diff --git a/components/3rd_party/omv/omv/modules/ulab/build.sh b/components/3rd_party/omv/omv/modules/ulab/build.sh new file mode 100644 index 00000000..7927d4ac --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/build.sh @@ -0,0 +1,70 @@ +#!/bin/sh + +GIT_HASH=`git describe --abbrev=8 --always` + +# POSIX compliant version +readlinkf_posix() { + [ "${1:-}" ] || return 1 + max_symlinks=40 + CDPATH='' # to avoid changing to an unexpected directory + + target=$1 + [ -e "${target%/}" ] || target=${1%"${1##*[!/]}"} # trim trailing slashes + [ -d "${target:-/}" ] && target="$target/" + + cd -P . 2>/dev/null || return 1 + while [ "$max_symlinks" -ge 0 ] && max_symlinks=$((max_symlinks - 1)); do + if [ ! "$target" = "${target%/*}" ]; then + case $target in + /*) cd -P "${target%/*}/" 2>/dev/null || break ;; + *) cd -P "./${target%/*}" 2>/dev/null || break ;; + esac + target=${target##*/} + fi + + if [ ! -L "$target" ]; then + target="${PWD%/}${target:+/}${target}" + printf '%s\n' "${target:-/}" + return 0 + fi + + # `ls -dl` format: "%s %u %s %s %u %s %s -> %s\n", + # , , , , + # , , , + # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html + link=$(ls -dl -- "$target" 2>/dev/null) || break + target=${link#*" $target -> "} + done + return 1 +} +NPROC=`python3 -c 'import multiprocessing; print(multiprocessing.cpu_count())'` +PLATFORM=`python3 -c 'import sys; print(sys.platform)'` +set -e +HERE="$(dirname -- "$(readlinkf_posix -- "${0}")" )" +dims=${1-2} +if [ ! -d "micropython" ] ; then + git clone https://github.com/micropython/micropython +else + git -C micropython pull +fi +make -C micropython/mpy-cross -j${NPROC} +make -C micropython/ports/unix submodules +make -C micropython/ports/unix -j${NPROC} USER_C_MODULES="${HERE}" DEBUG=1 STRIP=: MICROPY_PY_FFI=0 MICROPY_PY_BTREE=0 CFLAGS_EXTRA=-DULAB_MAX_DIMS=$dims CFLAGS_EXTRA+=-DULAB_HASH=$GIT_HASH BUILD=build-$dims PROG=micropython-$dims + +PROG="micropython/ports/unix/build-$dims/micropython-$dims" +if [ ! -e "$PROG" ]; then + # Older MicroPython revision, executable is still in ports/unix. + PROG="micropython/ports/unix/micropython-$dims" +fi + +bash test-common.sh "${dims}" "$PROG" + +# Build with single-precision float. +make -C micropython/ports/unix -j${NPROC} USER_C_MODULES="${HERE}" DEBUG=1 STRIP=: MICROPY_PY_FFI=0 MICROPY_PY_BTREE=0 CFLAGS_EXTRA=-DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_FLOAT CFLAGS_EXTRA+=-DULAB_MAX_DIMS=$dims CFLAGS_EXTRA+=-DULAB_HASH=$GIT_HASH BUILD=build-nanbox-$dims PROG=micropython-nanbox-$dims + +# The unix nanbox variant builds as a 32-bit executable and requires gcc-multilib. +# macOS doesn't support i386 builds so only build on linux. +if [ $PLATFORM = linux ]; then + make -C micropython/ports/unix -j${NPROC} VARIANT=nanbox USER_C_MODULES="${HERE}" DEBUG=1 STRIP=: MICROPY_PY_FFI=0 MICROPY_PY_BTREE=0 CFLAGS_EXTRA=-DULAB_MAX_DIMS=$dims CFLAGS_EXTRA+=-DULAB_HASH=$GIT_HASH BUILD=build-nanbox-$dims PROG=micropython-nanbox-$dims +fi + diff --git a/components/3rd_party/omv/omv/modules/ulab/code/micropython.cmake b/components/3rd_party/omv/omv/modules/ulab/code/micropython.cmake new file mode 100644 index 00000000..66890c0d --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/micropython.cmake @@ -0,0 +1,18 @@ +add_library(usermod_ulab INTERFACE) + +file(GLOB_RECURSE ULAB_SOURCES ${CMAKE_CURRENT_LIST_DIR}/*.c) + +target_sources(usermod_ulab INTERFACE + ${ULAB_SOURCES} +) + +target_include_directories(usermod_ulab INTERFACE + ${CMAKE_CURRENT_LIST_DIR} +) + +target_compile_definitions(usermod_ulab INTERFACE + MODULE_ULAB_ENABLED=1 +) + +target_link_libraries(usermod INTERFACE usermod_ulab) + diff --git a/components/3rd_party/omv/omv/modules/ulab/code/micropython.mk b/components/3rd_party/omv/omv/modules/ulab/code/micropython.mk new file mode 100644 index 00000000..f36d1d61 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/micropython.mk @@ -0,0 +1,39 @@ + +USERMODULES_DIR := $(USERMOD_DIR) + +# Add all C files to SRC_USERMOD. +SRC_USERMOD += $(USERMODULES_DIR)/scipy/linalg/linalg.c +SRC_USERMOD += $(USERMODULES_DIR)/scipy/optimize/optimize.c +SRC_USERMOD += $(USERMODULES_DIR)/scipy/signal/signal.c +SRC_USERMOD += $(USERMODULES_DIR)/scipy/special/special.c +SRC_USERMOD += $(USERMODULES_DIR)/ndarray_operators.c +SRC_USERMOD += $(USERMODULES_DIR)/ulab_tools.c +SRC_USERMOD += $(USERMODULES_DIR)/ndarray.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/ndarray/ndarray_iter.c +SRC_USERMOD += $(USERMODULES_DIR)/ndarray_properties.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/approx.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/compare.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/carray/carray.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/carray/carray_tools.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/create.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/fft/fft.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/fft/fft_tools.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/filter.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/io/io.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/linalg/linalg.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/linalg/linalg_tools.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/numerical.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/poly.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/stats.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/transform.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/vector.c + +SRC_USERMOD += $(USERMODULES_DIR)/numpy/numpy.c +SRC_USERMOD += $(USERMODULES_DIR)/scipy/scipy.c +SRC_USERMOD += $(USERMODULES_DIR)/user/user.c +SRC_USERMOD += $(USERMODULES_DIR)/utils/utils.c +SRC_USERMOD += $(USERMODULES_DIR)/ulab.c + +CFLAGS_USERMOD += -I$(USERMODULES_DIR) + +override CFLAGS_EXTRA += -DMODULE_ULAB_ENABLED=1 diff --git a/components/3rd_party/omv/omv/modules/ulab/code/ndarray.c b/components/3rd_party/omv/omv/modules/ulab/code/ndarray.c new file mode 100644 index 00000000..4b340b23 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/ndarray.c @@ -0,0 +1,2227 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2022 Zoltán Vörös + * 2020 Jeff Epler for Adafruit Industries + * 2020 Taku Fukada +*/ + +#include +#include +#include +#include +#include +#include "py/runtime.h" +#include "py/binary.h" +#include "py/obj.h" +#include "py/objtuple.h" +#include "py/objint.h" + +#include "ulab_tools.h" +#include "ndarray.h" +#include "ndarray_operators.h" +#include "numpy/carray/carray.h" +#include "numpy/carray/carray_tools.h" + +mp_uint_t ndarray_print_threshold = NDARRAY_PRINT_THRESHOLD; +mp_uint_t ndarray_print_edgeitems = NDARRAY_PRINT_EDGEITEMS; + +//| """Manipulate numeric data similar to numpy +//| +//| `ulab` is a numpy-like module for micropython, meant to simplify and +//| speed up common mathematical operations on arrays. The primary goal was to +//| implement a small subset of numpy that might be useful in the context of a +//| microcontroller. This means low-level data processing of linear (array) and +//| two-dimensional (matrix) data. +//| +//| `ulab` is adapted from micropython-ulab, and the original project's +//| documentation can be found at +//| https://micropython-ulab.readthedocs.io/en/latest/ +//| +//| `ulab` is modeled after numpy, and aims to be a compatible subset where +//| possible. Numpy's documentation can be found at +//| https://docs.scipy.org/doc/numpy/index.html""" +//| + +void ndarray_set_complex_value(void *p, size_t index, mp_obj_t value) { + mp_float_t real, imag; + if(mp_obj_is_type(value, &mp_type_complex)) { + mp_obj_get_complex(value, &real, &imag); + ((mp_float_t *)p)[2 * index] = real; + ((mp_float_t *)p)[2 * index + 1] = imag; + } else { + real = mp_obj_get_float(value); + ((mp_float_t *)p)[2 * index] = real; + ((mp_float_t *)p)[2 * index + 1] = MICROPY_FLOAT_CONST(0.0); + } +} + +#ifdef CIRCUITPY +void ndarray_set_value(char typecode, void *p, size_t index, mp_obj_t val_in) { + switch (typecode) { + case NDARRAY_INT8: + ((signed char *)p)[index] = mp_obj_get_int(val_in); + break; + case NDARRAY_UINT8: + ((unsigned char *)p)[index] = mp_obj_get_int(val_in); + break; + case NDARRAY_INT16: + ((short *)p)[index] = mp_obj_get_int(val_in); + break; + case NDARRAY_UINT16: + ((unsigned short *)p)[index] = mp_obj_get_int(val_in); + break; + case NDARRAY_FLOAT: + ((mp_float_t *)p)[index] = mp_obj_get_float(val_in); + break; + #if ULAB_SUPPORTS_COMPLEX + case NDARRAY_COMPLEX: + ndarray_set_complex_value(p, index, val_in); + break; + #endif + } +} +#endif + +#if defined(MICROPY_VERSION_MAJOR) && MICROPY_VERSION_MAJOR == 1 && MICROPY_VERSION_MINOR == 11 + +void mp_obj_slice_indices(mp_obj_t self_in, mp_int_t length, mp_bound_slice_t *result) { + mp_obj_slice_t *self = MP_OBJ_TO_PTR(self_in); + mp_int_t start, stop, step; + + if (self->step == mp_const_none) { + step = 1; + } else { + step = mp_obj_get_int(self->step); + if (step == 0) { + mp_raise_ValueError(translate("slice step can't be zero")); + } + } + + if (step > 0) { + // Positive step + if (self->start == mp_const_none) { + start = 0; + } else { + start = mp_obj_get_int(self->start); + if (start < 0) { + start += length; + } + start = MIN(length, MAX(start, 0)); + } + + if (self->stop == mp_const_none) { + stop = length; + } else { + stop = mp_obj_get_int(self->stop); + if (stop < 0) { + stop += length; + } + stop = MIN(length, MAX(stop, 0)); + } + } else { + // Negative step + if (self->start == mp_const_none) { + start = length - 1; + } else { + start = mp_obj_get_int(self->start); + if (start < 0) { + start += length; + } + start = MIN(length - 1, MAX(start, -1)); + } + + if (self->stop == mp_const_none) { + stop = -1; + } else { + stop = mp_obj_get_int(self->stop); + if (stop < 0) { + stop += length; + } + stop = MIN(length - 1, MAX(stop, -1)); + } + } + + result->start = start; + result->stop = stop; + result->step = step; +} +#endif /* MICROPY_VERSION v1.11 */ + +void ndarray_fill_array_iterable(mp_float_t *array, mp_obj_t iterable) { + mp_obj_iter_buf_t x_buf; + mp_obj_t x_item, x_iterable = mp_getiter(iterable, &x_buf); + while ((x_item = mp_iternext(x_iterable)) != MP_OBJ_STOP_ITERATION) { + *array++ = (mp_float_t)mp_obj_get_float(x_item); + } +} + +#if ULAB_HAS_FUNCTION_ITERATOR +size_t *ndarray_new_coords(uint8_t ndim) { + size_t *coords = m_new0(size_t, ndim); + return coords; +} + +void ndarray_rewind_array(uint8_t ndim, uint8_t *array, size_t *shape, int32_t *strides, size_t *coords) { + // resets the data pointer of a single array, whenever an axis is full + // since we always iterate over the very last axis, we have to keep track of + // the last ndim-2 axes only + array -= shape[ULAB_MAX_DIMS - 1] * strides[ULAB_MAX_DIMS - 1]; + array += strides[ULAB_MAX_DIMS - 2]; + for(uint8_t i=1; i < ndim-1; i++) { + coords[ULAB_MAX_DIMS - 1 - i] += 1; + if(coords[ULAB_MAX_DIMS - 1 - i] == shape[ULAB_MAX_DIMS - 1 - i]) { // we are at a dimension boundary + array -= shape[ULAB_MAX_DIMS - 1 - i] * strides[ULAB_MAX_DIMS - 1 - i]; + array += strides[ULAB_MAX_DIMS - 2 - i]; + coords[ULAB_MAX_DIMS - 1 - i] = 0; + coords[ULAB_MAX_DIMS - 2 - i] += 1; + } else { // coordinates can change only, if the last coordinate changes + return; + } + } +} +#endif + +static int32_t *strides_from_shape(size_t *shape, uint8_t dtype) { + // returns a strides array that corresponds to a dense array with the prescribed shape + int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); + strides[ULAB_MAX_DIMS-1] = (int32_t)ulab_binary_get_size(dtype); + for(uint8_t i=ULAB_MAX_DIMS; i > 1; i--) { + strides[i-2] = strides[i-1] * shape[i-1]; + } + return strides; +} + +size_t *ndarray_shape_vector(size_t a, size_t b, size_t c, size_t d) { + // returns a ULAB_MAX_DIMS-aware array of shapes + // WARNING: this assumes that the maximum possible dimension is 4! + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + shape[ULAB_MAX_DIMS - 1] = d; + #if ULAB_MAX_DIMS > 1 + shape[ULAB_MAX_DIMS - 2] = c; + #endif + #if ULAB_MAX_DIMS > 2 + shape[ULAB_MAX_DIMS - 3] = b; + #endif + #if ULAB_MAX_DIMS > 3 + shape[ULAB_MAX_DIMS - 4] = a; + #endif + return shape; +} + +bool ndarray_object_is_array_like(mp_obj_t o_in) { + if(mp_obj_is_type(o_in, &ulab_ndarray_type) || + mp_obj_is_type(o_in, &mp_type_tuple) || + mp_obj_is_type(o_in, &mp_type_list) || + mp_obj_is_type(o_in, &mp_type_range)) { + return true; + } + return false; +} + +void fill_array_iterable(mp_float_t *array, mp_obj_t iterable) { + mp_obj_iter_buf_t x_buf; + mp_obj_t x_item, x_iterable = mp_getiter(iterable, &x_buf); + size_t i=0; + while ((x_item = mp_iternext(x_iterable)) != MP_OBJ_STOP_ITERATION) { + array[i] = (mp_float_t)mp_obj_get_float(x_item); + i++; + } +} + +#if NDARRAY_HAS_DTYPE +#if ULAB_HAS_DTYPE_OBJECT +void ndarray_dtype_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + dtype_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_print_str(print, "dtype('"); + if(self->dtype == NDARRAY_BOOLEAN) { + mp_print_str(print, "bool')"); + } else if(self->dtype == NDARRAY_UINT8) { + mp_print_str(print, "uint8')"); + } else if(self->dtype == NDARRAY_INT8) { + mp_print_str(print, "int8')"); + } else if(self->dtype == NDARRAY_UINT16) { + mp_print_str(print, "uint16')"); + } else if(self->dtype == NDARRAY_INT16) { + mp_print_str(print, "int16')"); + } + #if ULAB_SUPPORTS_COMPLEX + else if(self->dtype == NDARRAY_COMPLEX) { + mp_print_str(print, "complex')"); + } + #endif + else { + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + mp_print_str(print, "float32')"); + #else + mp_print_str(print, "float64')"); + #endif + } +} + +mp_obj_t ndarray_dtype_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void) type; + mp_arg_check_num(n_args, n_kw, 0, 1, true); + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + }; + mp_arg_val_t _args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, args, &kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, _args); + + dtype_obj_t *dtype = m_new_obj(dtype_obj_t); + dtype->base.type = &ulab_dtype_type; + + if(mp_obj_is_type(args[0], &ulab_ndarray_type)) { + // return the dtype of the array + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0]); + dtype->dtype = ndarray->dtype; + } else { + uint8_t _dtype; + if(mp_obj_is_int(_args[0].u_obj)) { + _dtype = mp_obj_get_int(_args[0].u_obj); + if((_dtype != NDARRAY_BOOL) && (_dtype != NDARRAY_UINT8) + && (_dtype != NDARRAY_INT8) && (_dtype != NDARRAY_UINT16) + && (_dtype != NDARRAY_INT16) && (_dtype != NDARRAY_FLOAT)) { + mp_raise_TypeError(translate("data type not understood")); + } + } else { + GET_STR_DATA_LEN(_args[0].u_obj, _dtype_, len); + if(memcmp(_dtype_, "uint8", 5) == 0) { + _dtype = NDARRAY_UINT8; + } else if(memcmp(_dtype_, "int8", 4) == 0) { + _dtype = NDARRAY_INT8; + } else if(memcmp(_dtype_, "uint16", 6) == 0) { + _dtype = NDARRAY_UINT16; + } else if(memcmp(_dtype_, "int16", 5) == 0) { + _dtype = NDARRAY_INT16; + } else if(memcmp(_dtype_, "float", 5) == 0) { + _dtype = NDARRAY_FLOAT; + } + #if ULAB_SUPPORTS_COMPLEX + else if(memcmp(_dtype_, "complex", 7) == 0) { + _dtype = NDARRAY_COMPLEX; + } + #endif + else { + mp_raise_TypeError(translate("data type not understood")); + } + } + dtype->dtype = _dtype; + } + return MP_OBJ_FROM_PTR(dtype); +} + +mp_obj_t ndarray_dtype(mp_obj_t self_in) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + dtype_obj_t *dtype = m_new_obj(dtype_obj_t); + dtype->base.type = &ulab_dtype_type; + dtype->dtype = self->dtype; + return MP_OBJ_FROM_PTR(dtype); +} + +#else +// this is the cheap implementation of tbe dtype +mp_obj_t ndarray_dtype(mp_obj_t self_in) { + uint8_t dtype; + if(mp_obj_is_type(self_in, &ulab_ndarray_type)) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + dtype = self->dtype; + } else { // we assume here that the input is a single character + GET_STR_DATA_LEN(self_in, _dtype, len); + if((len != 1) || ((*_dtype != NDARRAY_BOOL) && (*_dtype != NDARRAY_UINT8) + && (*_dtype != NDARRAY_INT8) && (*_dtype != NDARRAY_UINT16) + && (*_dtype != NDARRAY_INT16) && (*_dtype != NDARRAY_FLOAT) + #if ULAB_SUPPORTS_COMPLEX + && (*_dtype != NDARRAY_COMPLEX) + #endif + )) { + mp_raise_TypeError(translate("data type not understood")); + } + dtype = *_dtype; + } + return mp_obj_new_int(dtype); +} +#endif /* ULAB_HAS_DTYPE_OBJECT */ +#endif /* NDARRAY_HAS_DTYPE */ + +#if ULAB_HAS_PRINTOPTIONS +mp_obj_t ndarray_set_printoptions(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_threshold, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_edgeitems, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + if(args[0].u_obj != mp_const_none) { + ndarray_print_threshold = mp_obj_get_int(args[0].u_obj); + } + if(args[1].u_obj != mp_const_none) { + ndarray_print_edgeitems = mp_obj_get_int(args[1].u_obj); + } + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(ndarray_set_printoptions_obj, 0, ndarray_set_printoptions); + +mp_obj_t ndarray_get_printoptions(void) { + mp_obj_t dict = mp_obj_new_dict(2); + mp_obj_dict_store(MP_OBJ_FROM_PTR(dict), MP_OBJ_NEW_QSTR(MP_QSTR_threshold), mp_obj_new_int(ndarray_print_threshold)); + mp_obj_dict_store(MP_OBJ_FROM_PTR(dict), MP_OBJ_NEW_QSTR(MP_QSTR_edgeitems), mp_obj_new_int(ndarray_print_edgeitems)); + return dict; +} + +MP_DEFINE_CONST_FUN_OBJ_0(ndarray_get_printoptions_obj, ndarray_get_printoptions); +#endif + +mp_obj_t ndarray_get_item(ndarray_obj_t *ndarray, void *array) { + // returns a proper micropython object from an array + if(!ndarray->boolean) { + #if ULAB_SUPPORTS_COMPLEX + if(ndarray->dtype == NDARRAY_COMPLEX) { + mp_float_t *c = (mp_float_t *)array; + mp_float_t real = *c++; + mp_float_t imag = *c; + return mp_obj_new_complex(real, imag); + } + #endif + return mp_binary_get_val_array(ndarray->dtype, array, 0); + } else { + if(*(uint8_t *)array) { + return mp_const_true; + } else { + return mp_const_false; + } + } +} + +static void ndarray_print_element(const mp_print_t *print, ndarray_obj_t *ndarray, uint8_t *array) { + #if ULAB_SUPPORTS_COMPLEX + if(ndarray->dtype == NDARRAY_COMPLEX) { + // real part first + mp_float_t fvalue = *(mp_float_t *)array; + mp_obj_print_helper(print, mp_obj_new_float(fvalue), PRINT_REPR); + // imaginary part + array += ndarray->itemsize / 2; + fvalue = *(mp_float_t *)array; + if(fvalue >= MICROPY_FLOAT_CONST(0.0) || isnan(fvalue)) { + mp_print_str(print, "+"); + } + array += ndarray->itemsize / 2; + mp_obj_print_helper(print, mp_obj_new_float(fvalue), PRINT_REPR); + mp_print_str(print, "j"); + } else { + mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR); + } + #else + mp_obj_print_helper(print, ndarray_get_item(ndarray, array), PRINT_REPR); + #endif +} + +static void ndarray_print_row(const mp_print_t *print, ndarray_obj_t *ndarray, uint8_t *array, int32_t stride, size_t n) { + if(n == 0) { + return; + } + mp_print_str(print, "["); + if((n <= ndarray_print_threshold) || (n <= 2*ndarray_print_edgeitems)) { // if the array is short, print everything + ndarray_print_element(print, ndarray, array); + array += stride; + for(size_t i=1; i < n; i++, array += stride) { + mp_print_str(print, ", "); + ndarray_print_element(print, ndarray, array); + } + } else { + ndarray_print_element(print, ndarray, array); + array += stride; + for(size_t i=1; i < ndarray_print_edgeitems; i++, array += stride) { + mp_print_str(print, ", "); + ndarray_print_element(print, ndarray, array); + } + mp_printf(print, ", ..., "); + array += stride * (n - 2 * ndarray_print_edgeitems); + ndarray_print_element(print, ndarray, array); + array += stride; + for(size_t i=1; i < ndarray_print_edgeitems; i++, array += stride) { + mp_print_str(print, ", "); + ndarray_print_element(print, ndarray, array); + } + } + mp_print_str(print, "]"); +} + +#if ULAB_MAX_DIMS > 1 +static void ndarray_print_bracket(const mp_print_t *print, const size_t condition, const size_t shape, const char *string) { + if(condition < shape) { + mp_print_str(print, string); + } +} +#endif + +void ndarray_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + uint8_t *array = (uint8_t *)self->array; + mp_print_str(print, "array("); + if(self->len == 0) { + mp_print_str(print, "[]"); + if(self->ndim > 1) { + mp_print_str(print, ", shape=("); + #if ULAB_MAX_DIMS > 1 + for(uint8_t ndim = self->ndim; ndim > 1; ndim--) { + mp_printf(MP_PYTHON_PRINTER, "%d,", self->shape[ULAB_MAX_DIMS - ndim]); + } + #else + mp_printf(MP_PYTHON_PRINTER, "%d,", self->shape[0]); + #endif + mp_printf(MP_PYTHON_PRINTER, "%d)", self->shape[ULAB_MAX_DIMS - 1]); + } + } else { + #if ULAB_MAX_DIMS > 3 + size_t i=0; + ndarray_print_bracket(print, 0, self->shape[ULAB_MAX_DIMS-4], "["); + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + ndarray_print_bracket(print, 0, self->shape[ULAB_MAX_DIMS-3], "["); + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + ndarray_print_bracket(print, 0, self->shape[ULAB_MAX_DIMS-2], "["); + do { + #endif + ndarray_print_row(print, self, array, self->strides[ULAB_MAX_DIMS-1], self->shape[ULAB_MAX_DIMS-1]); + #if ULAB_MAX_DIMS > 1 + array += self->strides[ULAB_MAX_DIMS-2]; + k++; + ndarray_print_bracket(print, k, self->shape[ULAB_MAX_DIMS-2], ",\n "); + } while(k < self->shape[ULAB_MAX_DIMS-2]); + ndarray_print_bracket(print, 0, self->shape[ULAB_MAX_DIMS-2], "]"); + #endif + #if ULAB_MAX_DIMS > 2 + j++; + ndarray_print_bracket(print, j, self->shape[ULAB_MAX_DIMS-3], ",\n\n "); + array -= self->strides[ULAB_MAX_DIMS-2] * self->shape[ULAB_MAX_DIMS-2]; + array += self->strides[ULAB_MAX_DIMS-3]; + } while(j < self->shape[ULAB_MAX_DIMS-3]); + ndarray_print_bracket(print, 0, self->shape[ULAB_MAX_DIMS-3], "]"); + #endif + #if ULAB_MAX_DIMS > 3 + array -= self->strides[ULAB_MAX_DIMS-3] * self->shape[ULAB_MAX_DIMS-3]; + array += self->strides[ULAB_MAX_DIMS-4]; + i++; + ndarray_print_bracket(print, i, self->shape[ULAB_MAX_DIMS-4], ",\n\n "); + } while(i < self->shape[ULAB_MAX_DIMS-4]); + ndarray_print_bracket(print, 0, self->shape[ULAB_MAX_DIMS-4], "]"); + #endif + } + mp_print_str(print, ", dtype="); + if(self->boolean) { + mp_print_str(print, "bool)"); + } else if(self->dtype == NDARRAY_UINT8) { + mp_print_str(print, "uint8)"); + } else if(self->dtype == NDARRAY_INT8) { + mp_print_str(print, "int8)"); + } else if(self->dtype == NDARRAY_UINT16) { + mp_print_str(print, "uint16)"); + } else if(self->dtype == NDARRAY_INT16) { + mp_print_str(print, "int16)"); + } + #if ULAB_SUPPORTS_COMPLEX + else if(self->dtype == NDARRAY_COMPLEX) { + mp_print_str(print, "complex)"); + } + #endif /* ULAB_SUPPORTS_COMPLEX */ + else { + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + mp_print_str(print, "float32)"); + #else + mp_print_str(print, "float64)"); + #endif + } +} + +void ndarray_assign_elements(ndarray_obj_t *ndarray, mp_obj_t iterable, uint8_t dtype, size_t *idx) { + // assigns a single row in the tensor + mp_obj_t item; + if(ndarray->boolean) { + uint8_t *array = (uint8_t *)ndarray->array; + array += *idx; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + if(mp_obj_is_true(item)) { + *array = 1; + } + array++; + (*idx)++; + } + } else { + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + #if ULAB_SUPPORTS_COMPLEX + mp_float_t real; + mp_float_t imag; + if(dtype == NDARRAY_COMPLEX) { + mp_obj_get_complex(item, &real, &imag); + ndarray_set_value(NDARRAY_FLOAT, ndarray->array, (*idx)++, mp_obj_new_float(real)); + ndarray_set_value(NDARRAY_FLOAT, ndarray->array, (*idx)++, mp_obj_new_float(imag)); + } else { + ndarray_set_value(dtype, ndarray->array, (*idx)++, item); + } + #else + ndarray_set_value(dtype, ndarray->array, (*idx)++, item); + #endif + } + } +} + +bool ndarray_is_dense(ndarray_obj_t *ndarray) { + // returns true, if the array is dense, false otherwise + // the array should be dense, if the very first stride can be calculated from shape + int32_t stride = ndarray->itemsize; + for(uint8_t i = ULAB_MAX_DIMS - 1; i > ULAB_MAX_DIMS-ndarray->ndim; i--) { + stride *= ndarray->shape[i]; + } + return stride == ndarray->strides[ULAB_MAX_DIMS-ndarray->ndim] ? true : false; +} + +static size_t multiply_size(size_t a, size_t b) { + size_t result; + if (__builtin_mul_overflow(a, b, &result)) { + mp_raise_ValueError(translate("array is too big")); + } + return result; +} + +ndarray_obj_t *ndarray_new_ndarray(uint8_t ndim, size_t *shape, int32_t *strides, uint8_t dtype) { + // Creates the base ndarray with shape, and initialises the values to straight 0s + ndarray_obj_t *ndarray = m_new_obj(ndarray_obj_t); + ndarray->base.type = &ulab_ndarray_type; + ndarray->dtype = dtype == NDARRAY_BOOL ? NDARRAY_UINT8 : dtype; + ndarray->boolean = dtype == NDARRAY_BOOL ? NDARRAY_BOOLEAN : NDARRAY_NUMERIC; + ndarray->ndim = ndim; + ndarray->len = ndim == 0 ? 0 : 1; + ndarray->itemsize = ulab_binary_get_size(dtype); + int32_t *_strides; + if(strides == NULL) { + _strides = strides_from_shape(shape, ndarray->dtype); + } else { + _strides = strides; + } + for(uint8_t i=ULAB_MAX_DIMS; i > ULAB_MAX_DIMS-ndim; i--) { + ndarray->shape[i-1] = shape[i-1]; + ndarray->strides[i-1] = _strides[i-1]; + ndarray->len = multiply_size(ndarray->len, shape[i-1]); + } + + // if the length is 0, still allocate a single item, so that contractions can be handled + size_t len = multiply_size(ndarray->itemsize, MAX(1, ndarray->len)); + uint8_t *array = m_new0(byte, len); + // this should set all elements to 0, irrespective of the of the dtype (all bits are zero) + // we could, perhaps, leave this step out, and initialise the array only, when needed + ndarray->array = array; + ndarray->origin = array; + return ndarray; +} + +ndarray_obj_t *ndarray_new_dense_ndarray(uint8_t ndim, size_t *shape, uint8_t dtype) { + // creates a dense array, i.e., one, where the strides are derived directly from the shapes + // the function should work in the general n-dimensional case + int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); + strides[ULAB_MAX_DIMS-1] = (int32_t)ulab_binary_get_size(dtype); + for(size_t i=ULAB_MAX_DIMS; i > 1; i--) { + strides[i-2] = strides[i-1] * MAX(1, shape[i-1]); + } + return ndarray_new_ndarray(ndim, shape, strides, dtype); +} + +ndarray_obj_t *ndarray_new_ndarray_from_tuple(mp_obj_tuple_t *_shape, uint8_t dtype) { + // creates a dense array from a tuple + // the function should work in the general n-dimensional case + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + for(size_t i=0; i < ULAB_MAX_DIMS; i++) { + if(i < ULAB_MAX_DIMS - _shape->len) { + shape[i] = 0; + } else { + shape[i] = mp_obj_get_int(_shape->items[i]); + } + } + return ndarray_new_dense_ndarray(_shape->len, shape, dtype); +} + +void ndarray_copy_array(ndarray_obj_t *source, ndarray_obj_t *target, uint8_t shift) { + // TODO: if the array is dense, the content could be copied in a single pass + // copies the content of source->array into a new dense void pointer + // it is assumed that the dtypes in source and target are the same + // Since the target is a new array, it is supposed to be dense + uint8_t *sarray = (uint8_t *)source->array; + uint8_t *tarray = (uint8_t *)target->array; + #if ULAB_SUPPORTS_COMPLEX + if(source->dtype == NDARRAY_COMPLEX) { + sarray += shift; + } + #endif + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + memcpy(tarray, sarray, target->itemsize); + tarray += target->itemsize; + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif +} + +ndarray_obj_t *ndarray_new_view(ndarray_obj_t *source, uint8_t ndim, size_t *shape, int32_t *strides, int32_t offset) { + // creates a new view from the input arguments + ndarray_obj_t *ndarray = m_new_obj(ndarray_obj_t); + ndarray->base.type = &ulab_ndarray_type; + ndarray->boolean = source->boolean; + ndarray->dtype = source->dtype; + ndarray->ndim = ndim; + ndarray->itemsize = source->itemsize; + ndarray->len = ndim == 0 ? 0 : 1; + for(uint8_t i=ULAB_MAX_DIMS; i > ULAB_MAX_DIMS-ndim; i--) { + ndarray->shape[i-1] = shape[i-1]; + ndarray->strides[i-1] = strides[i-1]; + ndarray->len *= shape[i-1]; + } + uint8_t *pointer = (uint8_t *)source->array; + pointer += offset; + ndarray->array = pointer; + ndarray->origin = source->origin; + return ndarray; +} + +ndarray_obj_t *ndarray_copy_view(ndarray_obj_t *source) { + // creates a one-to-one deep copy of the input ndarray or its view + // the function should work in the general n-dimensional case + // In order to make it dtype-agnostic, we copy the memory content + // instead of reading out the values + + int32_t *strides = strides_from_shape(source->shape, source->dtype); + + uint8_t dtype = source->dtype; + if(source->boolean) { + dtype = NDARRAY_BOOL; + } + ndarray_obj_t *ndarray = ndarray_new_ndarray(source->ndim, source->shape, strides, dtype); + ndarray_copy_array(source, ndarray, 0); + return ndarray; +} + +ndarray_obj_t *ndarray_copy_view_convert_type(ndarray_obj_t *source, uint8_t dtype) { + // creates a copy, similar to ndarray_copy_view, but it also converts the dtype, if necessary + if(dtype == source->dtype) { + return ndarray_copy_view(source); + } + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, dtype); + uint8_t *sarray = (uint8_t *)source->array; + uint8_t *array = (uint8_t *)ndarray->array; + + #if ULAB_SUPPORTS_COMPLEX + uint8_t complex_size = 2 * sizeof(mp_float_t); + #endif + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_obj_t item; + #if ULAB_SUPPORTS_COMPLEX + if(source->dtype == NDARRAY_COMPLEX) { + if(dtype != NDARRAY_COMPLEX) { + mp_raise_TypeError(translate("cannot convert complex type")); + } else { + memcpy(array, sarray, complex_size); + } + } else { + #endif + if((source->dtype == NDARRAY_FLOAT) && (dtype != NDARRAY_FLOAT)) { + // floats must be treated separately, because they can't directly be converted to integer types + mp_float_t f = ndarray_get_float_value(sarray, source->dtype); + item = mp_obj_new_int((int32_t)MICROPY_FLOAT_C_FUN(round)(f)); + } else { + item = mp_binary_get_val_array(source->dtype, sarray, 0); + } + #if ULAB_SUPPORTS_COMPLEX + if(dtype == NDARRAY_COMPLEX) { + ndarray_set_value(NDARRAY_FLOAT, array, 0, item); + } else { + ndarray_set_value(dtype, array, 0, item); + } + } + #else + ndarray_set_value(dtype, array, 0, item); + #endif + array += ndarray->itemsize; + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif + return ndarray; +} + +#if NDARRAY_HAS_BYTESWAP +mp_obj_t ndarray_byteswap(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + // changes the endiannes of an array + // if the dtype of the input uint8/int8/bool, simply return a copy or view + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_inplace, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_FALSE } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + ndarray_obj_t *self = MP_OBJ_TO_PTR(args[0].u_obj); + ndarray_obj_t *ndarray = NULL; + if(args[1].u_obj == mp_const_false) { + ndarray = ndarray_copy_view(self); + } else { + ndarray = ndarray_new_view(self, self->ndim, self->shape, self->strides, 0); + } + if((self->dtype == NDARRAY_BOOL) || (self->dtype == NDARRAY_UINT8) || (self->dtype == NDARRAY_INT8)) { + return MP_OBJ_FROM_PTR(ndarray); + } else { + uint8_t *array = (uint8_t *)ndarray->array; + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + if(self->dtype == NDARRAY_FLOAT) { + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + SWAP(uint8_t, array[0], array[3]); + SWAP(uint8_t, array[1], array[2]); + #else + SWAP(uint8_t, array[0], array[7]); + SWAP(uint8_t, array[1], array[6]); + SWAP(uint8_t, array[2], array[5]); + SWAP(uint8_t, array[3], array[4]); + #endif + } else { + SWAP(uint8_t, array[0], array[1]); + } + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < ndarray->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + array -= ndarray->strides[ULAB_MAX_DIMS - 1] * ndarray->shape[ULAB_MAX_DIMS-1]; + array += ndarray->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < ndarray->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + array -= ndarray->strides[ULAB_MAX_DIMS - 2] * ndarray->shape[ULAB_MAX_DIMS-2]; + array += ndarray->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < ndarray->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= ndarray->strides[ULAB_MAX_DIMS - 3] * ndarray->shape[ULAB_MAX_DIMS-3]; + array += ndarray->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < ndarray->shape[ULAB_MAX_DIMS - 4]); + #endif + } + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(ndarray_byteswap_obj, 1, ndarray_byteswap); +#endif + +#if NDARRAY_HAS_COPY +mp_obj_t ndarray_copy(mp_obj_t self_in) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_FROM_PTR(ndarray_copy_view(self)); +} + +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_copy_obj, ndarray_copy); +#endif + +ndarray_obj_t *ndarray_new_linear_array(size_t len, uint8_t dtype) { + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + if(len == 0) { + return ndarray_new_dense_ndarray(0, shape, dtype); + } + shape[ULAB_MAX_DIMS-1] = len; + return ndarray_new_dense_ndarray(1, shape, dtype); +} + +ndarray_obj_t *ndarray_from_iterable(mp_obj_t obj, uint8_t dtype) { + // returns an ndarray from an iterable micropython object + // if the input is an ndarray, returns the input... + if(mp_obj_is_type(obj, &ulab_ndarray_type)) { + return MP_OBJ_TO_PTR(obj); + } + // ... otherwise, takes the values from the iterable, and creates the corresponding ndarray + + // First, we have to figure out, whether the elements of the iterable are iterables themself + uint8_t ndim = 0; + size_t shape[ULAB_MAX_DIMS]; + mp_obj_iter_buf_t iter_buf[ULAB_MAX_DIMS]; + mp_obj_t iterable[ULAB_MAX_DIMS]; + // inspect only the very first element in each dimension; this is fast, + // but not completely safe, e.g., length compatibility is not checked + mp_obj_t item = obj; + + while(1) { + if(mp_obj_len_maybe(item) == MP_OBJ_NULL) { + break; + } + if(ndim == ULAB_MAX_DIMS) { + mp_raise_ValueError(translate("too many dimensions")); + } + shape[ndim] = MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(item)); + if(shape[ndim] == 0) { + ndim++; + break; + } + iterable[ndim] = mp_getiter(item, &iter_buf[ndim]); + item = mp_iternext(iterable[ndim]); + ndim++; + } + for(uint8_t i = 0; i < ndim; i++) { + // align all values to the right + shape[ULAB_MAX_DIMS - i - 1] = shape[ndim - 1 - i]; + } + + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(ndim, shape, dtype); + item = obj; + for(uint8_t i = 0; i < ndim - 1; i++) { + // if ndim > 1, descend into the hierarchy + iterable[ULAB_MAX_DIMS - ndim + i] = mp_getiter(item, &iter_buf[ULAB_MAX_DIMS - ndim + i]); + item = mp_iternext(iterable[ULAB_MAX_DIMS - ndim + i]); + } + + size_t idx = 0; + // TODO: this could surely be done in a more elegant way... + #if ULAB_MAX_DIMS > 3 + do { + #endif + #if ULAB_MAX_DIMS > 2 + do { + #endif + #if ULAB_MAX_DIMS > 1 + do { + #endif + iterable[ULAB_MAX_DIMS - 1] = mp_getiter(item, &iter_buf[ULAB_MAX_DIMS - 1]); + ndarray_assign_elements(ndarray, iterable[ULAB_MAX_DIMS - 1], ndarray->dtype, &idx); + #if ULAB_MAX_DIMS > 1 + item = ndim > 1 ? mp_iternext(iterable[ULAB_MAX_DIMS - 2]) : MP_OBJ_STOP_ITERATION; + } while(item != MP_OBJ_STOP_ITERATION); + #endif + #if ULAB_MAX_DIMS > 2 + item = ndim > 2 ? mp_iternext(iterable[ULAB_MAX_DIMS - 3]) : MP_OBJ_STOP_ITERATION; + if(item != MP_OBJ_STOP_ITERATION) { + iterable[ULAB_MAX_DIMS - 2] = mp_getiter(item, &iter_buf[ULAB_MAX_DIMS - 2]); + item = mp_iternext(iterable[ULAB_MAX_DIMS - 2]); + } else { + iterable[ULAB_MAX_DIMS - 2] = MP_OBJ_STOP_ITERATION; + } + } while(iterable[ULAB_MAX_DIMS - 2] != MP_OBJ_STOP_ITERATION); + #endif + #if ULAB_MAX_DIMS > 3 + item = ndim > 3 ? mp_iternext(iterable[ULAB_MAX_DIMS - 4]) : MP_OBJ_STOP_ITERATION; + if(item != MP_OBJ_STOP_ITERATION) { + iterable[ULAB_MAX_DIMS - 3] = mp_getiter(item, &iter_buf[ULAB_MAX_DIMS - 3]); + item = mp_iternext(iterable[ULAB_MAX_DIMS - 3]); + } else { + iterable[ULAB_MAX_DIMS - 3] = MP_OBJ_STOP_ITERATION; + } + } while(iterable[ULAB_MAX_DIMS - 3] != MP_OBJ_STOP_ITERATION); + #endif + + return ndarray; +} + +STATIC uint8_t ndarray_init_helper(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_INT(NDARRAY_FLOAT) } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + uint8_t _dtype; + #if ULAB_HAS_DTYPE_OBJECT + if(mp_obj_is_type(args[1].u_obj, &ulab_dtype_type)) { + dtype_obj_t *dtype = MP_OBJ_TO_PTR(args[1].u_obj); + _dtype = dtype->dtype; + } else { // this must be an integer defined as a class constant (ulab.numpy.uint8 etc.) + _dtype = mp_obj_get_int(args[1].u_obj); + } + #else + _dtype = mp_obj_get_int(args[1].u_obj); + #endif + return _dtype; +} + +STATIC mp_obj_t ndarray_make_new_core(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args, mp_map_t *kw_args) { + uint8_t dtype = ndarray_init_helper(n_args, args, kw_args); + + if(mp_obj_is_type(args[0], &ulab_ndarray_type)) { + ndarray_obj_t *source = MP_OBJ_TO_PTR(args[0]); + return MP_OBJ_FROM_PTR(ndarray_copy_view_convert_type(source, dtype)); + } else { + // assume that the input is an iterable + return MP_OBJ_FROM_PTR(ndarray_from_iterable(args[0], dtype)); + } +} + +mp_obj_t ndarray_array_constructor(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + // array constructor for ndarray, equivalent to numpy.array(...) + return ndarray_make_new_core(&ulab_ndarray_type, n_args, kw_args->used, pos_args, kw_args); +} +MP_DEFINE_CONST_FUN_OBJ_KW(ndarray_array_constructor_obj, 1, ndarray_array_constructor); + +mp_obj_t ndarray_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void) type; + mp_arg_check_num(n_args, n_kw, 1, 2, true); + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + return ndarray_make_new_core(type, n_args, n_kw, args, &kw_args); +} + +// broadcasting is used at a number of places, always include +bool ndarray_can_broadcast(ndarray_obj_t *lhs, ndarray_obj_t *rhs, uint8_t *ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + // Returns true or false, depending on, whether the two arrays can be broadcast together + // with numpy's broadcasting rules. These are as follows: + // + // 1. the two shapes are either equal + // 2. one of the shapes is 1 + + lstrides[ULAB_MAX_DIMS - 1] = lhs->strides[ULAB_MAX_DIMS - 1]; + rstrides[ULAB_MAX_DIMS - 1] = rhs->strides[ULAB_MAX_DIMS - 1]; + for(uint8_t i=ULAB_MAX_DIMS; i > 0; i--) { + if((lhs->shape[i-1] == rhs->shape[i-1]) || (lhs->shape[i-1] == 0) || (lhs->shape[i-1] == 1) || + (rhs->shape[i-1] == 0) || (rhs->shape[i-1] == 1)) { + shape[i-1] = MAX(lhs->shape[i-1], rhs->shape[i-1]); + if(shape[i-1] > 0) (*ndim)++; + if(lhs->shape[i-1] < 2) { + lstrides[i-1] = 0; + } else { + lstrides[i-1] = lhs->strides[i-1]; + } + if(rhs->shape[i-1] < 2) { + rstrides[i-1] = 0; + } else { + rstrides[i-1] = rhs->strides[i-1]; + } + } else { + return false; + } + } + return true; +} + +#if NDARRAY_HAS_INPLACE_OPS +bool ndarray_can_broadcast_inplace(ndarray_obj_t *lhs, ndarray_obj_t *rhs, int32_t *rstrides) { + // returns true or false, depending on, whether the two arrays can be broadcast together inplace + // this means that the right hand side always must be "smaller" than the left hand side, i.e. + // the broadcasting rules are as follows: + // + // 1. the two shapes are either equal + // 2. the shapes on the right hand side is 1 + + rstrides[ULAB_MAX_DIMS - 1] = rhs->strides[ULAB_MAX_DIMS - 1]; + for(uint8_t i=ULAB_MAX_DIMS; i > 0; i--) { + if((lhs->shape[i-1] == rhs->shape[i-1]) || (rhs->shape[i-1] == 0) || (rhs->shape[i-1] == 1)) { + if(rhs->shape[i-1] < 2) { + rstrides[i-1] = 0; + } else { + rstrides[i-1] = rhs->strides[i-1]; + } + } else { + return false; + } + } + return true; +} +#endif + +#if NDARRAY_IS_SLICEABLE +static size_t slice_length(mp_bound_slice_t slice) { + ssize_t len, correction = 1; + if(slice.step > 0) correction = -1; + len = (ssize_t)(slice.stop - slice.start + (slice.step + correction)) / slice.step; + if(len < 0) return 0; + return (size_t)len; +} + +static mp_bound_slice_t generate_slice(mp_int_t n, mp_obj_t index) { + mp_bound_slice_t slice; + if(mp_obj_is_type(index, &mp_type_slice)) { + mp_obj_slice_indices(index, n, &slice); + } else if(mp_obj_is_int(index)) { + mp_int_t _index = mp_obj_get_int(index); + if(_index < 0) { + _index += n; + } + if((_index >= n) || (_index < 0)) { + mp_raise_msg(&mp_type_IndexError, translate("index is out of bounds")); + } + slice.start = _index; + slice.stop = _index + 1; + slice.step = 1; + } else { + mp_raise_msg(&mp_type_IndexError, translate("indices must be integers, slices, or Boolean lists")); + } + return slice; +} + +static ndarray_obj_t *ndarray_view_from_slices(ndarray_obj_t *ndarray, mp_obj_tuple_t *tuple) { + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + int32_t *strides = m_new0(int32_t, ULAB_MAX_DIMS); + + uint8_t ndim = ndarray->ndim; + + for(uint8_t i=0; i < ndim; i++) { + // copy from the end + shape[ULAB_MAX_DIMS - 1 - i] = ndarray->shape[ULAB_MAX_DIMS - 1 - i]; + strides[ULAB_MAX_DIMS - 1 - i] = ndarray->strides[ULAB_MAX_DIMS - 1 - i]; + } + int32_t offset = 0; + for(uint8_t i=0; i < tuple->len; i++) { + if(mp_obj_is_int(tuple->items[i])) { + // if item is an int, the dimension will first be reduced ... + ndim--; + int32_t k = mp_obj_get_int(tuple->items[i]); + if(k < 0) { + k += ndarray->shape[ULAB_MAX_DIMS - ndarray->ndim + i]; + } + if((k >= (int32_t)ndarray->shape[ULAB_MAX_DIMS - ndarray->ndim + i]) || (k < 0)) { + mp_raise_msg(&mp_type_IndexError, translate("index is out of bounds")); + } + offset += ndarray->strides[ULAB_MAX_DIMS - ndarray->ndim + i] * k; + // ... and then we have to shift the shapes to the right + for(uint8_t j=0; j < i; j++) { + shape[ULAB_MAX_DIMS - ndarray->ndim + i - j] = shape[ULAB_MAX_DIMS - ndarray->ndim + i - j - 1]; + strides[ULAB_MAX_DIMS - ndarray->ndim + i - j] = strides[ULAB_MAX_DIMS - ndarray->ndim + i - j - 1]; + } + } else { + mp_bound_slice_t slice = generate_slice(shape[ULAB_MAX_DIMS - ndarray->ndim + i], tuple->items[i]); + shape[ULAB_MAX_DIMS - ndarray->ndim + i] = slice_length(slice); + offset += ndarray->strides[ULAB_MAX_DIMS - ndarray->ndim + i] * (int32_t)slice.start; + strides[ULAB_MAX_DIMS - ndarray->ndim + i] = (int32_t)slice.step * ndarray->strides[ULAB_MAX_DIMS - ndarray->ndim + i]; + } + } + return ndarray_new_view(ndarray, ndim, shape, strides, offset); +} + +void ndarray_assign_view(ndarray_obj_t *view, ndarray_obj_t *values) { + if(values->len == 0) { + return; + } + uint8_t ndim = 0; + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + int32_t *lstrides = m_new0(int32_t, ULAB_MAX_DIMS); + int32_t *rstrides = m_new0(int32_t, ULAB_MAX_DIMS); + if(!ndarray_can_broadcast(view, values, &ndim, shape, lstrides, rstrides)) { + mp_raise_ValueError(translate("operands could not be broadcast together")); + } else { + + ndarray_obj_t *ndarray = ndarray_copy_view_convert_type(values, view->dtype); + // re-calculate rstrides, since the copy operation might have changed the directions of the strides + ndarray_can_broadcast(view, ndarray, &ndim, shape, lstrides, rstrides); + uint8_t *rarray = (uint8_t *)ndarray->array; + + + uint8_t *larray = (uint8_t *)view->array; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + memcpy(larray, rarray, view->itemsize); + larray += lstrides[ULAB_MAX_DIMS - 1]; + rarray += rstrides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < view->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + larray -= lstrides[ULAB_MAX_DIMS - 1] * view->shape[ULAB_MAX_DIMS-1]; + larray += lstrides[ULAB_MAX_DIMS - 2]; + rarray -= rstrides[ULAB_MAX_DIMS - 1] * view->shape[ULAB_MAX_DIMS-1]; + rarray += rstrides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < view->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + larray -= lstrides[ULAB_MAX_DIMS - 2] * view->shape[ULAB_MAX_DIMS-2]; + larray += lstrides[ULAB_MAX_DIMS - 3]; + rarray -= rstrides[ULAB_MAX_DIMS - 2] * view->shape[ULAB_MAX_DIMS-2]; + rarray += rstrides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < view->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + larray -= lstrides[ULAB_MAX_DIMS - 3] * view->shape[ULAB_MAX_DIMS-3]; + larray += lstrides[ULAB_MAX_DIMS - 4]; + rarray -= rstrides[ULAB_MAX_DIMS - 3] * view->shape[ULAB_MAX_DIMS-3]; + rarray += rstrides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < view->shape[ULAB_MAX_DIMS - 4]); + #endif + } + + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(int32_t, lstrides, ULAB_MAX_DIMS); + m_del(int32_t, rstrides, ULAB_MAX_DIMS); + + return; +} + +static mp_obj_t ndarray_from_boolean_index(ndarray_obj_t *ndarray, ndarray_obj_t *index) { + // returns a 1D array, indexed by a Boolean array + if(ndarray->len != index->len) { + mp_raise_ValueError(translate("array and index length must be equal")); + } + uint8_t *iarray = (uint8_t *)index->array; + // first we have to find out how many trues there are + size_t count = 0; + for(size_t i=0; i < index->len; i++) { + count += *iarray; + iarray += index->strides[ULAB_MAX_DIMS - 1]; + } + ndarray_obj_t *results = ndarray_new_linear_array(count, ndarray->dtype); + uint8_t *rarray = (uint8_t *)results->array; + uint8_t *array = (uint8_t *)ndarray->array; + // re-wind the index array + iarray = index->array; + for(size_t i=0; i < index->len; i++) { + if(*iarray) { + memcpy(rarray, array, results->itemsize); + rarray += results->itemsize; + count++; + } + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + iarray += index->strides[ULAB_MAX_DIMS - 1]; + } + return MP_OBJ_FROM_PTR(results); +} + +static mp_obj_t ndarray_assign_from_boolean_index(ndarray_obj_t *ndarray, ndarray_obj_t *index, ndarray_obj_t *values) { + // assigns values to a Boolean-indexed array + // first we have to find out how many trues there are + uint8_t *iarray = (uint8_t *)index->array; + size_t istride = index->strides[ULAB_MAX_DIMS - 1]; + size_t count = 0; + for(size_t i=0; i < index->len; i++) { + count += *iarray; + iarray += istride; + } + // re-wind the index array + iarray = index->array; + uint8_t *varray = (uint8_t *)values->array; + size_t vstride; + + if(count == values->len) { + // there are as many values as true indices + vstride = values->strides[ULAB_MAX_DIMS - 1]; + } else { + // there is a single value + vstride = 0; + } + + #if ULAB_SUPPORTS_COMPLEX + if(values->dtype == NDARRAY_COMPLEX) { + if(ndarray->dtype != NDARRAY_COMPLEX) { + mp_raise_TypeError(translate("cannot convert complex to dtype")); + } else { + uint8_t *array = (uint8_t *)ndarray->array; + for(size_t i = 0; i < ndarray->len; i++) { + if(*iarray) { + memcpy(array, varray, ndarray->itemsize); + varray += vstride; + } + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + iarray += istride; + } while(0); + return MP_OBJ_FROM_PTR(ndarray); + } + } + #endif + + int32_t lstrides = ndarray->strides[ULAB_MAX_DIMS - 1] / ndarray->itemsize; + + if(ndarray->dtype == NDARRAY_UINT8) { + if(values->dtype == NDARRAY_UINT8) { + BOOLEAN_ASSIGNMENT_LOOP(uint8_t, uint8_t, ndarray, lstrides, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_INT8) { + BOOLEAN_ASSIGNMENT_LOOP(uint8_t, int8_t, ndarray, lstrides, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_UINT16) { + BOOLEAN_ASSIGNMENT_LOOP(uint8_t, uint16_t, ndarray, lstrides, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_INT16) { + BOOLEAN_ASSIGNMENT_LOOP(uint8_t, int16_t, ndarray, lstrides, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_FLOAT) { + BOOLEAN_ASSIGNMENT_LOOP(uint8_t, mp_float_t, ndarray, lstrides, iarray, istride, varray, vstride); + } + } else if(ndarray->dtype == NDARRAY_INT8) { + if(values->dtype == NDARRAY_UINT8) { + BOOLEAN_ASSIGNMENT_LOOP(int8_t, uint8_t, ndarray, lstrides, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_INT8) { + BOOLEAN_ASSIGNMENT_LOOP(int8_t, int8_t, ndarray, lstrides, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_UINT16) { + BOOLEAN_ASSIGNMENT_LOOP(int8_t, uint16_t, ndarray, lstrides, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_INT16) { + BOOLEAN_ASSIGNMENT_LOOP(int8_t, int16_t, ndarray, lstrides, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_FLOAT) { + BOOLEAN_ASSIGNMENT_LOOP(int8_t, mp_float_t, ndarray, lstrides, iarray, istride, varray, vstride); + } + } else if(ndarray->dtype == NDARRAY_UINT16) { + if(values->dtype == NDARRAY_UINT8) { + BOOLEAN_ASSIGNMENT_LOOP(uint16_t, uint8_t, ndarray, lstrides, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_INT8) { + BOOLEAN_ASSIGNMENT_LOOP(uint16_t, int8_t, ndarray, lstrides, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_UINT16) { + BOOLEAN_ASSIGNMENT_LOOP(uint16_t, uint16_t, ndarray, lstrides, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_INT16) { + BOOLEAN_ASSIGNMENT_LOOP(uint16_t, int16_t, ndarray, lstrides, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_FLOAT) { + BOOLEAN_ASSIGNMENT_LOOP(uint16_t, mp_float_t, ndarray, lstrides, iarray, istride, varray, vstride); + } + } else if(ndarray->dtype == NDARRAY_INT16) { + if(values->dtype == NDARRAY_UINT8) { + BOOLEAN_ASSIGNMENT_LOOP(int16_t, uint8_t, ndarray, lstrides, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_INT8) { + BOOLEAN_ASSIGNMENT_LOOP(int16_t, int8_t, ndarray, lstrides, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_UINT16) { + BOOLEAN_ASSIGNMENT_LOOP(int16_t, uint16_t, ndarray, lstrides, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_INT16) { + BOOLEAN_ASSIGNMENT_LOOP(int16_t, int16_t, ndarray, lstrides, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_FLOAT) { + BOOLEAN_ASSIGNMENT_LOOP(int16_t, mp_float_t, ndarray, lstrides, iarray, istride, varray, vstride); + } + } else { + #if ULAB_SUPPORTS_COMPLEX + if(ndarray->dtype == NDARRAY_COMPLEX) { + lstrides *= 2; + } + #endif + if(values->dtype == NDARRAY_UINT8) { + BOOLEAN_ASSIGNMENT_LOOP(mp_float_t, uint8_t, ndarray, lstrides, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_INT8) { + BOOLEAN_ASSIGNMENT_LOOP(mp_float_t, int8_t, ndarray, lstrides, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_UINT16) { + BOOLEAN_ASSIGNMENT_LOOP(mp_float_t, uint16_t, ndarray, lstrides, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_INT16) { + BOOLEAN_ASSIGNMENT_LOOP(mp_float_t, int16_t, ndarray, lstrides, iarray, istride, varray, vstride); + } else if(values->dtype == NDARRAY_FLOAT) { + BOOLEAN_ASSIGNMENT_LOOP(mp_float_t, mp_float_t, ndarray, lstrides, iarray, istride, varray, vstride); + } + } + return MP_OBJ_FROM_PTR(ndarray); +} + +static mp_obj_t ndarray_get_slice(ndarray_obj_t *ndarray, mp_obj_t index, ndarray_obj_t *values) { + if(mp_obj_is_type(index, &ulab_ndarray_type)) { + ndarray_obj_t *nindex = MP_OBJ_TO_PTR(index); + if((nindex->ndim > 1) || (nindex->boolean == false)) { + mp_raise_NotImplementedError(translate("operation is implemented for 1D Boolean arrays only")); + } + if(values == NULL) { // return value(s) + return ndarray_from_boolean_index(ndarray, nindex); + } else { // assign value(s) + ndarray_assign_from_boolean_index(ndarray, nindex, values); + } + } + if(mp_obj_is_type(index, &mp_type_tuple) || mp_obj_is_int(index) || mp_obj_is_type(index, &mp_type_slice)) { + mp_obj_tuple_t *tuple; + if(mp_obj_is_type(index, &mp_type_tuple)) { + tuple = MP_OBJ_TO_PTR(index); + if(tuple->len > ndarray->ndim) { + mp_raise_msg(&mp_type_IndexError, translate("too many indices")); + } + } else { + mp_obj_t *items = m_new(mp_obj_t, 1); + items[0] = index; + tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(1, items)); + } + ndarray_obj_t *view = ndarray_view_from_slices(ndarray, tuple); + if(values == NULL) { // return value(s) + // if the view has been reduced to nothing, return a single value + if(view->ndim == 0) { + return ndarray_get_item(view, view->array); + } else { + return MP_OBJ_FROM_PTR(view); + } + } else { // assign value(s) + ndarray_assign_view(view, values); + } + } + return mp_const_none; +} + +mp_obj_t ndarray_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (value == MP_OBJ_SENTINEL) { // return value(s) + return ndarray_get_slice(self, index, NULL); + } else { // assignment to slices; the value must be an ndarray, or a scalar + ndarray_obj_t *values = ndarray_from_mp_obj(value, 0); + return ndarray_get_slice(self, index, values); + } + return mp_const_none; +} +#endif /* NDARRAY_IS_SLICEABLE */ + +#if NDARRAY_IS_ITERABLE + +// itarray iterator +mp_obj_t ndarray_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { + return ndarray_new_ndarray_iterator(o_in, iter_buf); +} + +typedef struct _mp_obj_ndarray_it_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + mp_obj_t ndarray; + size_t cur; +} mp_obj_ndarray_it_t; + +mp_obj_t ndarray_iternext(mp_obj_t self_in) { + mp_obj_ndarray_it_t *self = MP_OBJ_TO_PTR(self_in); + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(self->ndarray); + uint8_t *array = (uint8_t *)ndarray->array; + + size_t iter_end = ndarray->shape[ULAB_MAX_DIMS-ndarray->ndim]; + if(self->cur < iter_end) { + // separating this case out saves 50 bytes for 1D arrays + #if ULAB_MAX_DIMS == 1 + array += self->cur * ndarray->strides[0]; + self->cur++; + return ndarray_get_item(ndarray, array); + #else + if(ndarray->ndim == 1) { // we have a linear array + array += self->cur * ndarray->strides[ULAB_MAX_DIMS - 1]; + self->cur++; + return ndarray_get_item(ndarray, array); + } else { // we have a tensor, return the reduced view + size_t offset = self->cur * ndarray->strides[ULAB_MAX_DIMS - ndarray->ndim]; + self->cur++; + return MP_OBJ_FROM_PTR(ndarray_new_view(ndarray, ndarray->ndim-1, ndarray->shape, ndarray->strides, offset)); + } + #endif + } else { + return MP_OBJ_STOP_ITERATION; + } +} + +mp_obj_t ndarray_new_ndarray_iterator(mp_obj_t ndarray, mp_obj_iter_buf_t *iter_buf) { + assert(sizeof(mp_obj_ndarray_it_t) <= sizeof(mp_obj_iter_buf_t)); + mp_obj_ndarray_it_t *iter = (mp_obj_ndarray_it_t *)iter_buf; + iter->base.type = &mp_type_polymorph_iter; + iter->iternext = ndarray_iternext; + iter->ndarray = ndarray; + iter->cur = 0; + return MP_OBJ_FROM_PTR(iter); +} +#endif /* NDARRAY_IS_ITERABLE */ + +#if NDARRAY_HAS_FLATTEN +mp_obj_t ndarray_flatten(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_order, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_QSTR(MP_QSTR_C)} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + ndarray_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + GET_STR_DATA_LEN(args[0].u_obj, order, len); + if((len != 1) || ((memcmp(order, "C", 1) != 0) && (memcmp(order, "F", 1) != 0))) { + mp_raise_ValueError(translate("flattening order must be either 'C', or 'F'")); + } + + uint8_t *sarray = (uint8_t *)self->array; + ndarray_obj_t *ndarray = ndarray_new_linear_array(self->len, self->dtype); + uint8_t *array = (uint8_t *)ndarray->array; + + if(memcmp(order, "C", 1) == 0) { // C-type ordering + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + memcpy(array, sarray, self->itemsize); + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + sarray += self->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < self->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= self->strides[ULAB_MAX_DIMS - 1] * self->shape[ULAB_MAX_DIMS-1]; + sarray += self->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < self->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + sarray -= self->strides[ULAB_MAX_DIMS - 2] * self->shape[ULAB_MAX_DIMS-2]; + sarray += self->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < self->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + sarray -= self->strides[ULAB_MAX_DIMS - 3] * self->shape[ULAB_MAX_DIMS-3]; + sarray += self->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < self->shape[ULAB_MAX_DIMS - 4]); + #endif + } else { // 'F', Fortran-type ordering + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + memcpy(array, sarray, self->itemsize); + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + sarray += self->strides[0]; + l++; + } while(l < self->shape[0]); + #if ULAB_MAX_DIMS > 1 + sarray -= self->strides[0] * self->shape[0]; + sarray += self->strides[1]; + k++; + } while(k < self->shape[1]); + #endif + #if ULAB_MAX_DIMS > 2 + sarray -= self->strides[1] * self->shape[1]; + sarray += self->strides[2]; + j++; + } while(j < self->shape[2]); + #endif + #if ULAB_MAX_DIMS > 3 + sarray -= self->strides[2] * self->shape[2]; + sarray += self->strides[3]; + i++; + } while(i < self->shape[3]); + #endif + } + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(ndarray_flatten_obj, 1, ndarray_flatten); +#endif + +#if NDARRAY_HAS_ITEMSIZE +mp_obj_t ndarray_itemsize(mp_obj_t self_in) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_NEW_SMALL_INT(self->itemsize); +} +#endif + +#if NDARRAY_HAS_SHAPE +mp_obj_t ndarray_shape(mp_obj_t self_in) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + uint8_t nitems = MAX(1, self->ndim); + mp_obj_t *items = m_new(mp_obj_t, nitems); + for(uint8_t i = 0; i < nitems; i++) { + items[nitems - i - 1] = mp_obj_new_int(self->shape[ULAB_MAX_DIMS - i - 1]); + } + mp_obj_t tuple = mp_obj_new_tuple(nitems, items); + m_del(mp_obj_t, items, nitems); + return tuple; +} +#endif + +#if NDARRAY_HAS_SIZE +mp_obj_t ndarray_size(mp_obj_t self_in) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->len); +} +#endif + +#if NDARRAY_HAS_STRIDES +mp_obj_t ndarray_strides(mp_obj_t self_in) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_t *items = m_new(mp_obj_t, self->ndim); + for(int8_t i=0; i < self->ndim; i++) { + items[i] = mp_obj_new_int(self->strides[ULAB_MAX_DIMS - self->ndim + i]); + } + mp_obj_t tuple = mp_obj_new_tuple(self->ndim, items); + m_del(mp_obj_t, items, self->ndim); + return tuple; +} +#endif + +#if NDARRAY_HAS_TOBYTES +mp_obj_t ndarray_tobytes(mp_obj_t self_in) { + // As opposed to numpy, this function returns a bytearray object with the data pointer (i.e., not a copy) + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + // Piping into a bytearray makes sense for dense arrays only, + // so bail out, if that is not the case + if(!ndarray_is_dense(self)) { + mp_raise_ValueError(translate("tobytes can be invoked for dense arrays only")); + } + return mp_obj_new_bytearray_by_ref(self->itemsize * self->len, self->array); +} + +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_tobytes_obj, ndarray_tobytes); +#endif + +#if NDARRAY_HAS_TOLIST +static mp_obj_t ndarray_recursive_list(ndarray_obj_t *self, uint8_t *array, uint8_t dim) { + int32_t stride = self->strides[ULAB_MAX_DIMS - dim]; + size_t len = self->shape[ULAB_MAX_DIMS - dim]; + + mp_obj_list_t *list = MP_OBJ_TO_PTR(mp_obj_new_list(len, NULL)); + for(size_t i = 0; i < len; i++) { + if(dim == 1) { + list->items[i] = ndarray_get_item(self, array); + } else { + list->items[i] = ndarray_recursive_list(self, array, dim-1); + } + array += stride; + } + return MP_OBJ_FROM_PTR(list); +} + +mp_obj_t ndarray_tolist(mp_obj_t self_in) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + uint8_t *array = (uint8_t *)self->array; + return ndarray_recursive_list(self, array, self->ndim); +} + +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_tolist_obj, ndarray_tolist); +#endif + +// Binary operations +ndarray_obj_t *ndarray_from_mp_obj(mp_obj_t obj, uint8_t other_type) { + // creates an ndarray from a micropython int or float + // if the input is an ndarray, it is returned + // if other_type is 0, return the smallest type that can accommodate the object + ndarray_obj_t *ndarray; + + if(mp_obj_is_int(obj)) { + int32_t ivalue = mp_obj_get_int(obj); + if((ivalue < -32767) || (ivalue > 32767)) { + // the integer value clearly does not fit the ulab integer types, so move on to float + ndarray = ndarray_new_linear_array(1, NDARRAY_FLOAT); + mp_float_t *array = (mp_float_t *)ndarray->array; + array[0] = (mp_float_t)ivalue; + } else { + uint8_t dtype; + if(ivalue < 0) { + if(ivalue > -128) { + dtype = NDARRAY_INT8; + } else { + dtype = NDARRAY_INT16; + } + } else { // ivalue >= 0 + if((other_type == NDARRAY_INT8) || (other_type == NDARRAY_INT16)) { + if(ivalue < 128) { + dtype = NDARRAY_INT8; + } else { + dtype = NDARRAY_INT16; + } + } else { // other_type = 0 is also included here + if(ivalue < 256) { + dtype = NDARRAY_UINT8; + } else { + dtype = NDARRAY_UINT16; + } + } + } + ndarray = ndarray_new_linear_array(1, dtype); + ndarray_set_value(dtype, ndarray->array, 0, obj); + } + } else if(mp_obj_is_float(obj)) { + ndarray = ndarray_new_linear_array(1, NDARRAY_FLOAT); + mp_float_t *array = (mp_float_t *)ndarray->array; + array[0] = mp_obj_get_float(obj); + } else if(mp_obj_is_bool(obj)) { + ndarray = ndarray_new_linear_array(1, NDARRAY_BOOLEAN); + uint8_t *array = (uint8_t *)ndarray->array; + if(obj == mp_const_true) { + *array = 1; + } + } else if(mp_obj_is_type(obj, &ulab_ndarray_type)){ + return MP_OBJ_TO_PTR(obj); + } + #if ULAB_SUPPORTS_COMPLEX + else if(mp_obj_is_type(obj, &mp_type_complex)) { + ndarray = ndarray_new_linear_array(1, NDARRAY_COMPLEX); + mp_float_t *array = (mp_float_t *)ndarray->array; + mp_obj_get_complex(obj, &array[0], &array[1]); + } + #endif + else { + // assume that the input is an iterable (raises an exception, if it is not the case) + ndarray = ndarray_from_iterable(obj, NDARRAY_FLOAT); + } + return ndarray; +} + +#if NDARRAY_HAS_BINARY_OPS || NDARRAY_HAS_INPLACE_OPS +mp_obj_t ndarray_binary_op(mp_binary_op_t _op, mp_obj_t lobj, mp_obj_t robj) { + // TODO: implement in-place operators + // if the ndarray stands on the right hand side of the expression, simply swap the operands + ndarray_obj_t *lhs, *rhs; + mp_binary_op_t op = _op; + if((op == MP_BINARY_OP_REVERSE_ADD) || (op == MP_BINARY_OP_REVERSE_MULTIPLY) || + (op == MP_BINARY_OP_REVERSE_POWER) || (op == MP_BINARY_OP_REVERSE_SUBTRACT) || + (op == MP_BINARY_OP_REVERSE_TRUE_DIVIDE)) { + lhs = ndarray_from_mp_obj(robj, 0); + rhs = ndarray_from_mp_obj(lobj, lhs->dtype); + } else { + lhs = ndarray_from_mp_obj(lobj, 0); + rhs = ndarray_from_mp_obj(robj, lhs->dtype); + } + if(op == MP_BINARY_OP_REVERSE_ADD) { + op = MP_BINARY_OP_ADD; + } else if(op == MP_BINARY_OP_REVERSE_MULTIPLY) { + op = MP_BINARY_OP_MULTIPLY; + } else if(op == MP_BINARY_OP_REVERSE_POWER) { + op = MP_BINARY_OP_POWER; + } else if(op == MP_BINARY_OP_REVERSE_SUBTRACT) { + op = MP_BINARY_OP_SUBTRACT; + } else if(op == MP_BINARY_OP_REVERSE_TRUE_DIVIDE) { + op = MP_BINARY_OP_TRUE_DIVIDE; + } + + uint8_t ndim = 0; + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + int32_t *lstrides = m_new0(int32_t, ULAB_MAX_DIMS); + int32_t *rstrides = m_new0(int32_t, ULAB_MAX_DIMS); + uint8_t broadcastable; + if((op == MP_BINARY_OP_INPLACE_ADD) || (op == MP_BINARY_OP_INPLACE_MULTIPLY) || (op == MP_BINARY_OP_INPLACE_POWER) || + (op == MP_BINARY_OP_INPLACE_SUBTRACT) || (op == MP_BINARY_OP_INPLACE_TRUE_DIVIDE)) { + broadcastable = ndarray_can_broadcast_inplace(lhs, rhs, rstrides); + } else { + broadcastable = ndarray_can_broadcast(lhs, rhs, &ndim, shape, lstrides, rstrides); + } + if(!broadcastable) { + mp_raise_ValueError(translate("operands could not be broadcast together")); + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(int32_t, lstrides, ULAB_MAX_DIMS); + m_del(int32_t, rstrides, ULAB_MAX_DIMS); + } + // the empty arrays have to be treated separately + uint8_t dtype = NDARRAY_INT16; + ndarray_obj_t *nd; + if((lhs->len == 0) || (rhs->len == 0)) { + switch(op) { + case MP_BINARY_OP_INPLACE_ADD: + case MP_BINARY_OP_INPLACE_MULTIPLY: + case MP_BINARY_OP_INPLACE_SUBTRACT: + case MP_BINARY_OP_ADD: + case MP_BINARY_OP_MULTIPLY: + case MP_BINARY_OP_SUBTRACT: + // here we don't have to list those cases that result in an int16, + // because dtype is initialised with that NDARRAY_INT16 + if(lhs->dtype == rhs->dtype) { + dtype = rhs->dtype; + } else if((lhs->dtype == NDARRAY_FLOAT) || (rhs->dtype == NDARRAY_FLOAT)) { + dtype = NDARRAY_FLOAT; + } else if(((lhs->dtype == NDARRAY_UINT8) && (rhs->dtype == NDARRAY_UINT16)) || + ((lhs->dtype == NDARRAY_INT8) && (rhs->dtype == NDARRAY_UINT16)) || + ((rhs->dtype == NDARRAY_UINT8) && (lhs->dtype == NDARRAY_UINT16)) || + ((rhs->dtype == NDARRAY_INT8) && (lhs->dtype == NDARRAY_UINT16))) { + dtype = NDARRAY_UINT16; + } + return MP_OBJ_FROM_PTR(ndarray_new_linear_array(0, dtype)); + break; + + case MP_BINARY_OP_INPLACE_POWER: + case MP_BINARY_OP_INPLACE_TRUE_DIVIDE: + case MP_BINARY_OP_POWER: + case MP_BINARY_OP_TRUE_DIVIDE: + return MP_OBJ_FROM_PTR(ndarray_new_linear_array(0, NDARRAY_FLOAT)); + break; + + case MP_BINARY_OP_LESS: + case MP_BINARY_OP_LESS_EQUAL: + case MP_BINARY_OP_MORE: + case MP_BINARY_OP_MORE_EQUAL: + case MP_BINARY_OP_EQUAL: + case MP_BINARY_OP_NOT_EQUAL: + nd = ndarray_new_linear_array(0, NDARRAY_UINT8); + nd->boolean = 1; + return MP_OBJ_FROM_PTR(nd); + + default: + return mp_const_none; + break; + } + } + + switch(op) { + // first the in-place operators + #if NDARRAY_HAS_INPLACE_ADD + case MP_BINARY_OP_INPLACE_ADD: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); + return ndarray_inplace_ams(lhs, rhs, rstrides, op); + break; + #endif + #if NDARRAY_HAS_INPLACE_MULTIPLY + case MP_BINARY_OP_INPLACE_MULTIPLY: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); + return ndarray_inplace_ams(lhs, rhs, rstrides, op); + break; + #endif + #if NDARRAY_HAS_INPLACE_POWER + case MP_BINARY_OP_INPLACE_POWER: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); + return ndarray_inplace_power(lhs, rhs, rstrides); + break; + #endif + #if NDARRAY_HAS_INPLACE_SUBTRACT + case MP_BINARY_OP_INPLACE_SUBTRACT: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); + return ndarray_inplace_ams(lhs, rhs, rstrides, op); + break; + #endif + #if NDARRAY_HAS_INPLACE_TRUE_DIVIDE + case MP_BINARY_OP_INPLACE_TRUE_DIVIDE: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); + return ndarray_inplace_divide(lhs, rhs, rstrides); + break; + #endif + // end if in-place operators + + #if NDARRAY_HAS_BINARY_OP_LESS + case MP_BINARY_OP_LESS: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); + // here we simply swap the operands + return ndarray_binary_more(rhs, lhs, ndim, shape, rstrides, lstrides, MP_BINARY_OP_MORE); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_LESS_EQUAL + case MP_BINARY_OP_LESS_EQUAL: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); + // here we simply swap the operands + return ndarray_binary_more(rhs, lhs, ndim, shape, rstrides, lstrides, MP_BINARY_OP_MORE_EQUAL); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_EQUAL + case MP_BINARY_OP_EQUAL: + return ndarray_binary_equality(lhs, rhs, ndim, shape, lstrides, rstrides, MP_BINARY_OP_EQUAL); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_NOT_EQUAL + case MP_BINARY_OP_NOT_EQUAL: + return ndarray_binary_equality(lhs, rhs, ndim, shape, lstrides, rstrides, MP_BINARY_OP_NOT_EQUAL); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_ADD + case MP_BINARY_OP_ADD: + return ndarray_binary_add(lhs, rhs, ndim, shape, lstrides, rstrides); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_MULTIPLY + case MP_BINARY_OP_MULTIPLY: + return ndarray_binary_multiply(lhs, rhs, ndim, shape, lstrides, rstrides); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_MORE + case MP_BINARY_OP_MORE: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); + return ndarray_binary_more(lhs, rhs, ndim, shape, lstrides, rstrides, MP_BINARY_OP_MORE); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_MORE_EQUAL + case MP_BINARY_OP_MORE_EQUAL: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); + return ndarray_binary_more(lhs, rhs, ndim, shape, lstrides, rstrides, MP_BINARY_OP_MORE_EQUAL); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_SUBTRACT + case MP_BINARY_OP_SUBTRACT: + return ndarray_binary_subtract(lhs, rhs, ndim, shape, lstrides, rstrides); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_TRUE_DIVIDE + case MP_BINARY_OP_TRUE_DIVIDE: + return ndarray_binary_true_divide(lhs, rhs, ndim, shape, lstrides, rstrides); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_POWER + case MP_BINARY_OP_POWER: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); + return ndarray_binary_power(lhs, rhs, ndim, shape, lstrides, rstrides); + break; + #endif + #if NDARRAY_HAS_BINARY_OP_FLOOR_DIVIDE + case MP_BINARY_OP_FLOOR_DIVIDE: + COMPLEX_DTYPE_NOT_IMPLEMENTED(lhs->dtype); + return ndarray_binary_floor_divide(lhs, rhs, ndim, shape, lstrides, rstrides); + break; + #endif + default: + return MP_OBJ_NULL; // op not supported + break; + } + return MP_OBJ_NULL; +} +#endif /* NDARRAY_HAS_BINARY_OPS || NDARRAY_HAS_INPLACE_OPS */ + +#if NDARRAY_HAS_UNARY_OPS +mp_obj_t ndarray_unary_op(mp_unary_op_t op, mp_obj_t self_in) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + ndarray_obj_t *ndarray = NULL; + + switch (op) { + #if NDARRAY_HAS_UNARY_OP_ABS + case MP_UNARY_OP_ABS: + #if ULAB_SUPPORTS_COMPLEX + if(self->dtype == NDARRAY_COMPLEX) { + int32_t *strides = strides_from_shape(self->shape, NDARRAY_FLOAT); + ndarray_obj_t *target = ndarray_new_ndarray(self->ndim, self->shape, strides, NDARRAY_FLOAT); + ndarray = MP_OBJ_TO_PTR(carray_abs(self, target)); + } else { + #endif + ndarray = ndarray_copy_view(self); + // if Boolean, NDARRAY_UINT8, or NDARRAY_UINT16, there is nothing to do + if(self->dtype == NDARRAY_INT8) { + int8_t *array = (int8_t *)ndarray->array; + for(size_t i=0; i < self->len; i++, array++) { + if(*array < 0) *array = -(*array); + } + } else if(self->dtype == NDARRAY_INT16) { + int16_t *array = (int16_t *)ndarray->array; + for(size_t i=0; i < self->len; i++, array++) { + if(*array < 0) *array = -(*array); + } + } else { + mp_float_t *array = (mp_float_t *)ndarray->array; + for(size_t i=0; i < self->len; i++, array++) { + if(*array < 0) *array = -(*array); + } + } + #if ULAB_SUPPORTS_COMPLEX + } + #endif + return MP_OBJ_FROM_PTR(ndarray); + break; + #endif + #if NDARRAY_HAS_UNARY_OP_INVERT + case MP_UNARY_OP_INVERT: + #if ULAB_SUPPORTS_COMPLEX + if(self->dtype == NDARRAY_FLOAT || self->dtype == NDARRAY_COMPLEX) { + #else + if(self->dtype == NDARRAY_FLOAT) { + #endif + mp_raise_ValueError(translate("operation is not supported for given type")); + } + // we can invert the content byte by byte, no need to distinguish between different dtypes + ndarray = ndarray_copy_view(self); // from this point, this is a dense copy + uint8_t *array = (uint8_t *)ndarray->array; + if(ndarray->boolean) { + for(size_t i=0; i < ndarray->len; i++, array++) *array = *array ^ 0x01; + } else { + uint8_t itemsize = ulab_binary_get_size(self->dtype); + for(size_t i=0; i < ndarray->len*itemsize; i++, array++) *array ^= 0xFF; + } + return MP_OBJ_FROM_PTR(ndarray); + break; + #endif + #if NDARRAY_HAS_UNARY_OP_LEN + case MP_UNARY_OP_LEN: + return mp_obj_new_int(self->shape[ULAB_MAX_DIMS - self->ndim]); + break; + #endif + #if NDARRAY_HAS_UNARY_OP_NEGATIVE + case MP_UNARY_OP_NEGATIVE: + ndarray = ndarray_copy_view(self); // from this point, this is a dense copy + if(self->dtype == NDARRAY_UINT8) { + uint8_t *array = (uint8_t *)ndarray->array; + for(size_t i=0; i < self->len; i++, array++) *array = -(*array); + } else if(self->dtype == NDARRAY_INT8) { + int8_t *array = (int8_t *)ndarray->array; + for(size_t i=0; i < self->len; i++, array++) *array = -(*array); + } else if(self->dtype == NDARRAY_UINT16) { + uint16_t *array = (uint16_t *)ndarray->array; + for(size_t i=0; i < self->len; i++, array++) *array = -(*array); + } else if(self->dtype == NDARRAY_INT16) { + int16_t *array = (int16_t *)ndarray->array; + for(size_t i=0; i < self->len; i++, array++) *array = -(*array); + } else { + mp_float_t *array = (mp_float_t *)ndarray->array; + size_t len = self->len; + #if ULAB_SUPPORTS_COMPLEX + if(self->dtype == NDARRAY_COMPLEX) { + len *= 2; + } + #endif + for(size_t i=0; i < len; i++, array++) *array = -(*array); + } + return MP_OBJ_FROM_PTR(ndarray); + break; + #endif + #if NDARRAY_HAS_UNARY_OP_POSITIVE + case MP_UNARY_OP_POSITIVE: + return MP_OBJ_FROM_PTR(ndarray_copy_view(self)); + #endif + + default: + return MP_OBJ_NULL; // operator not supported + break; + } +} +#endif /* NDARRAY_HAS_UNARY_OPS */ + +#if NDARRAY_HAS_TRANSPOSE +mp_obj_t ndarray_transpose(mp_obj_t self_in) { + #if ULAB_MAX_DIMS == 1 + return self_in; + #endif + // TODO: check, what happens to the offset here, if we have a view + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + if(self->ndim == 1) { + return self_in; + } + size_t *shape = m_new(size_t, self->ndim); + int32_t *strides = m_new(int32_t, self->ndim); + for(uint8_t i=0; i < self->ndim; i++) { + shape[ULAB_MAX_DIMS - 1 - i] = self->shape[ULAB_MAX_DIMS - self->ndim + i]; + strides[ULAB_MAX_DIMS - 1 - i] = self->strides[ULAB_MAX_DIMS - self->ndim + i]; + } + // TODO: I am not sure ndarray_new_view is OK here... + // should be deep copy... + ndarray_obj_t *ndarray = ndarray_new_view(self, self->ndim, shape, strides, 0); + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_transpose_obj, ndarray_transpose); +#endif /* NDARRAY_HAS_TRANSPOSE */ + +#if ULAB_MAX_DIMS > 1 +#if NDARRAY_HAS_RESHAPE +mp_obj_t ndarray_reshape_core(mp_obj_t oin, mp_obj_t _shape, bool inplace) { + ndarray_obj_t *source = MP_OBJ_TO_PTR(oin); + if(!mp_obj_is_type(_shape, &mp_type_tuple) && !mp_obj_is_int(_shape)) { + mp_raise_TypeError(translate("shape must be integer or tuple of integers")); + } + + mp_obj_tuple_t *shape; + + if(mp_obj_is_int(_shape)) { + mp_obj_t *items = m_new(mp_obj_t, 1); + items[0] = _shape; + shape = mp_obj_new_tuple(1, items); + } else { + shape = MP_OBJ_TO_PTR(_shape); + } + + if(shape->len > ULAB_MAX_DIMS) { + mp_raise_ValueError(translate("maximum number of dimensions is " MP_STRINGIFY(ULAB_MAX_DIMS))); + } + + size_t new_length = 1; + size_t *new_shape = m_new0(size_t, ULAB_MAX_DIMS); + uint8_t unknown_dim = 0; + uint8_t unknown_index = 0; + + for(uint8_t i = 0; i < shape->len; i++) { + int32_t ax_len = mp_obj_get_int(shape->items[shape->len - i - 1]); + if(ax_len >= 0) { + new_shape[ULAB_MAX_DIMS - i - 1] = (size_t)ax_len; + new_length *= new_shape[ULAB_MAX_DIMS - i - 1]; + } else { + unknown_dim++; + unknown_index = ULAB_MAX_DIMS - i - 1; + } + } + + if(unknown_dim > 1) { + mp_raise_ValueError(translate("can only specify one unknown dimension")); + } else if(unknown_dim == 1) { + new_shape[unknown_index] = source->len / new_length; + new_length = source->len; + } + + if(source->len != new_length) { + mp_raise_ValueError(translate("cannot reshape array")); + } + + ndarray_obj_t *ndarray; + if(ndarray_is_dense(source)) { + int32_t *new_strides = strides_from_shape(new_shape, source->dtype); + if(inplace) { + for(uint8_t i = 0; i < ULAB_MAX_DIMS; i++) { + source->shape[i] = new_shape[i]; + source->strides[i] = new_strides[i]; + } + return MP_OBJ_FROM_PTR(oin); + } else { + ndarray = ndarray_new_view(source, shape->len, new_shape, new_strides, 0); + } + } else { + if(inplace) { + mp_raise_ValueError(translate("cannot assign new shape")); + } + if(mp_obj_is_type(_shape, &mp_type_tuple)) { + ndarray = ndarray_new_ndarray_from_tuple(shape, source->dtype); + } else { + ndarray = ndarray_new_linear_array(source->len, source->dtype); + } + ndarray_copy_array(source, ndarray, 0); + } + return MP_OBJ_FROM_PTR(ndarray); +} + +mp_obj_t ndarray_reshape(mp_obj_t oin, mp_obj_t _shape) { + return ndarray_reshape_core(oin, _shape, 0); +} + +MP_DEFINE_CONST_FUN_OBJ_2(ndarray_reshape_obj, ndarray_reshape); +#endif /* NDARRAY_HAS_RESHAPE */ +#endif /* ULAB_MAX_DIMS > 1 */ + +#if ULAB_NUMPY_HAS_NDINFO +mp_obj_t ndarray_info(mp_obj_t obj_in) { + if(!mp_obj_is_type(obj_in, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("function is defined for ndarrays only")); + } + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(obj_in); + mp_printf(MP_PYTHON_PRINTER, "class: ndarray\n"); + mp_printf(MP_PYTHON_PRINTER, "shape: ("); + if(ndarray->ndim == 1) { + mp_printf(MP_PYTHON_PRINTER, "%d,", ndarray->shape[ULAB_MAX_DIMS-1]); + } else { + for(uint8_t i=0; i < ndarray->ndim-1; i++) mp_printf(MP_PYTHON_PRINTER, "%d, ", ndarray->shape[i]); + mp_printf(MP_PYTHON_PRINTER, "%d", ndarray->shape[ULAB_MAX_DIMS-1]); + } + mp_printf(MP_PYTHON_PRINTER, ")\n"); + mp_printf(MP_PYTHON_PRINTER, "strides: ("); + if(ndarray->ndim == 1) { + mp_printf(MP_PYTHON_PRINTER, "%d,", ndarray->strides[ULAB_MAX_DIMS-1]); + } else { + for(uint8_t i=0; i < ndarray->ndim-1; i++) mp_printf(MP_PYTHON_PRINTER, "%d, ", ndarray->strides[i]); + mp_printf(MP_PYTHON_PRINTER, "%d", ndarray->strides[ULAB_MAX_DIMS-1]); + } + mp_printf(MP_PYTHON_PRINTER, ")\n"); + mp_printf(MP_PYTHON_PRINTER, "itemsize: %d\n", ndarray->itemsize); + mp_printf(MP_PYTHON_PRINTER, "data pointer: 0x%p\n", ndarray->array); + mp_printf(MP_PYTHON_PRINTER, "type: "); + if(ndarray->boolean) { + mp_printf(MP_PYTHON_PRINTER, "bool\n"); + } else if(ndarray->dtype == NDARRAY_UINT8) { + mp_printf(MP_PYTHON_PRINTER, "uint8\n"); + } else if(ndarray->dtype == NDARRAY_INT8) { + mp_printf(MP_PYTHON_PRINTER, "int8\n"); + } else if(ndarray->dtype == NDARRAY_UINT16) { + mp_printf(MP_PYTHON_PRINTER, "uint16\n"); + } else if(ndarray->dtype == NDARRAY_INT16) { + mp_printf(MP_PYTHON_PRINTER, "int16\n"); + } else if(ndarray->dtype == NDARRAY_FLOAT) { + mp_printf(MP_PYTHON_PRINTER, "float\n"); + } + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_info_obj, ndarray_info); +#endif + +// (the get_buffer protocol returns 0 for success, 1 for failure) +mp_int_t ndarray_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { + ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in); + if(!ndarray_is_dense(self)) { + return 1; + } + bufinfo->len = self->itemsize * self->len; + bufinfo->buf = self->array; + bufinfo->typecode = self->dtype; + return 0; +} diff --git a/components/3rd_party/omv/omv/modules/ulab/code/ndarray.h b/components/3rd_party/omv/omv/modules/ulab/code/ndarray.h new file mode 100644 index 00000000..4564f772 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/ndarray.h @@ -0,0 +1,718 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * 2020 Jeff Epler for Adafruit Industries +*/ + +#ifndef _NDARRAY_ +#define _NDARRAY_ + +#include "py/objarray.h" +#include "py/binary.h" +#include "py/objstr.h" +#include "py/objlist.h" + +#include "ulab.h" + +#ifndef MP_PI +#define MP_PI MICROPY_FLOAT_CONST(3.14159265358979323846) +#endif +#ifndef MP_E +#define MP_E MICROPY_FLOAT_CONST(2.71828182845904523536) +#endif + +#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT +#define FLOAT_TYPECODE 'f' +#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE +#define FLOAT_TYPECODE 'd' +#endif + +#if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_A || MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_B + +// For object representations A and B a Python float object is allocated as a +// concrete object in a struct, with the first entry pointing to &mp_type_float. +// Constant float objects are a struct in ROM and are referenced via their pointer. + +// Use ULAB_DEFINE_FLOAT_CONST to define a constant float object. +// id is the name of the constant, num is it's floating point value. +// hex32 is computed as: hex(int.from_bytes(array.array('f', [num]), 'little')) +// hex64 is computed as: hex(int.from_bytes(array.array('d', [num]), 'little')) + +// Use ULAB_REFERENCE_FLOAT_CONST to reference a constant float object in code. + +#define ULAB_DEFINE_FLOAT_CONST(id, num, hex32, hex64) \ + const mp_obj_float_t id##_obj = {{&mp_type_float}, (num)} + +#define ULAB_REFERENCE_FLOAT_CONST(id) MP_ROM_PTR(&id##_obj) + +// this typedef is lifted from objfloat.c, because mp_obj_float_t is not exposed +typedef struct _mp_obj_float_t { + mp_obj_base_t base; + mp_float_t value; +} mp_obj_float_t; + +#elif MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C + +// For object representation C a Python float object is stored directly in the +// mp_obj_t value. + +// See above for how to use ULAB_DEFINE_FLOAT_CONST and ULAB_REFERENCE_FLOAT_CONST. + +#define ULAB_DEFINE_FLOAT_CONST(id, num, hex32, hex64) \ + enum { \ + id = (((((uint32_t)hex32) & ~3) | 2) + 0x80800000) \ + } + +#define ULAB_REFERENCE_FLOAT_CONST(id) ((mp_obj_t)(id)) + +#elif MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_D + +// For object representation D (nan-boxing) a Python float object is stored +// directly in the mp_obj_t value. + +// See above for how to use ULAB_DEFINE_FLOAT_CONST and ULAB_REFERENCE_FLOAT_CONST. + +#define ULAB_DEFINE_FLOAT_CONST(id, num, hex32, hex64) \ + const uint64_t id = (((uint64_t)hex64) + 0x8004000000000000ULL) + +#define ULAB_REFERENCE_FLOAT_CONST(id) {id} + +#endif + +#if defined(MICROPY_VERSION_MAJOR) && MICROPY_VERSION_MAJOR == 1 && MICROPY_VERSION_MINOR == 11 +typedef struct _mp_obj_slice_t { + mp_obj_base_t base; + mp_obj_t start; + mp_obj_t stop; + mp_obj_t step; +} mp_obj_slice_t; +#define MP_ERROR_TEXT(x) x +#endif + +#if !defined(MP_OBJ_TYPE_GET_SLOT) +#if defined(MP_TYPE_FLAG_EXTENDED) +// Provide MP_OBJ_TYPE_{HAS,GET}_SLOT for CircuitPython. +#define MP_OBJ_TYPE_HAS_SLOT(t, f) (mp_type_get_##f##_slot(t) != NULL) +#define MP_OBJ_TYPE_GET_SLOT(t, f) mp_type_get_##f##_slot(t) +#else +// Provide MP_OBJ_TYPE_{HAS,GET}_SLOT for older revisions of MicroPython. +#define MP_OBJ_TYPE_HAS_SLOT(t, f) ((t)->f != NULL) +#define MP_OBJ_TYPE_GET_SLOT(t, f) (t)->f + +// Also allow CiruitPython-style mp_obj_type_t definitions. +#define MP_TYPE_FLAG_EXTENDED (0) +#define MP_TYPE_EXTENDED_FIELDS(...) __VA_ARGS__ +#endif +#endif + +#if !CIRCUITPY +#define translate(x) MP_ERROR_TEXT(x) +#define ndarray_set_value(a, b, c, d) mp_binary_set_val_array(a, b, c, d) +#else +void ndarray_set_value(char , void *, size_t , mp_obj_t ); +#endif + +void ndarray_set_complex_value(void *, size_t , mp_obj_t ); + +#define NDARRAY_NUMERIC 0 +#define NDARRAY_BOOLEAN 1 + +#define NDARRAY_NDARRAY_TYPE 1 +#define NDARRAY_ITERABLE_TYPE 2 + +extern const mp_obj_type_t ulab_ndarray_type; + +enum NDARRAY_TYPE { + NDARRAY_BOOL = '?', // this must never be assigned to the dtype! + NDARRAY_UINT8 = 'B', + NDARRAY_INT8 = 'b', + NDARRAY_UINT16 = 'H', + NDARRAY_INT16 = 'h', + #if ULAB_SUPPORTS_COMPLEX + NDARRAY_COMPLEX = 'c', + #endif + NDARRAY_FLOAT = FLOAT_TYPECODE, +}; + +typedef struct _ndarray_obj_t { + mp_obj_base_t base; + uint8_t dtype; + uint8_t itemsize; + uint8_t boolean; + uint8_t ndim; + size_t len; + size_t shape[ULAB_MAX_DIMS]; + int32_t strides[ULAB_MAX_DIMS]; + void *array; + void *origin; +} ndarray_obj_t; + +#if ULAB_HAS_DTYPE_OBJECT +extern const mp_obj_type_t ulab_dtype_type; + +typedef struct _dtype_obj_t { + mp_obj_base_t base; + uint8_t dtype; +} dtype_obj_t; + +void ndarray_dtype_print(const mp_print_t *, mp_obj_t , mp_print_kind_t ); + +mp_obj_t ndarray_dtype_make_new(const mp_obj_type_t *, size_t , size_t , const mp_obj_t *); +#endif /* ULAB_HAS_DTYPE_OBJECT */ + +extern const mp_obj_type_t ndarray_flatiter_type; + +mp_obj_t ndarray_new_ndarray_iterator(mp_obj_t , mp_obj_iter_buf_t *); + +mp_obj_t ndarray_get_item(ndarray_obj_t *, void *); +mp_float_t ndarray_get_float_value(void *, uint8_t ); +mp_float_t ndarray_get_float_index(void *, uint8_t , size_t ); +bool ndarray_object_is_array_like(mp_obj_t ); +void fill_array_iterable(mp_float_t *, mp_obj_t ); +size_t *ndarray_shape_vector(size_t , size_t , size_t , size_t ); + +void ndarray_print(const mp_print_t *, mp_obj_t , mp_print_kind_t ); + +#if ULAB_HAS_PRINTOPTIONS +mp_obj_t ndarray_set_printoptions(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(ndarray_set_printoptions_obj); + +mp_obj_t ndarray_get_printoptions(void); +MP_DECLARE_CONST_FUN_OBJ_0(ndarray_get_printoptions_obj); +#endif + +void ndarray_assign_elements(ndarray_obj_t *, mp_obj_t , uint8_t , size_t *); +size_t *ndarray_contract_shape(ndarray_obj_t *, uint8_t ); +int32_t *ndarray_contract_strides(ndarray_obj_t *, uint8_t ); + +ndarray_obj_t *ndarray_from_iterable(mp_obj_t , uint8_t ); +ndarray_obj_t *ndarray_new_dense_ndarray(uint8_t , size_t *, uint8_t ); +ndarray_obj_t *ndarray_new_ndarray_from_tuple(mp_obj_tuple_t *, uint8_t ); +ndarray_obj_t *ndarray_new_ndarray(uint8_t , size_t *, int32_t *, uint8_t ); +ndarray_obj_t *ndarray_new_linear_array(size_t , uint8_t ); +ndarray_obj_t *ndarray_new_view(ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t ); +bool ndarray_is_dense(ndarray_obj_t *); +ndarray_obj_t *ndarray_copy_view(ndarray_obj_t *); +ndarray_obj_t *ndarray_copy_view_convert_type(ndarray_obj_t *, uint8_t ); +void ndarray_copy_array(ndarray_obj_t *, ndarray_obj_t *, uint8_t ); + +MP_DECLARE_CONST_FUN_OBJ_KW(ndarray_array_constructor_obj); +mp_obj_t ndarray_make_new(const mp_obj_type_t *, size_t , size_t , const mp_obj_t *); +mp_obj_t ndarray_subscr(mp_obj_t , mp_obj_t , mp_obj_t ); +mp_obj_t ndarray_getiter(mp_obj_t , mp_obj_iter_buf_t *); +bool ndarray_can_broadcast(ndarray_obj_t *, ndarray_obj_t *, uint8_t *, size_t *, int32_t *, int32_t *); +bool ndarray_can_broadcast_inplace(ndarray_obj_t *, ndarray_obj_t *, int32_t *); +mp_obj_t ndarray_binary_op(mp_binary_op_t , mp_obj_t , mp_obj_t ); +mp_obj_t ndarray_unary_op(mp_unary_op_t , mp_obj_t ); + +size_t *ndarray_new_coords(uint8_t ); +void ndarray_rewind_array(uint8_t , uint8_t *, size_t *, int32_t *, size_t *); + +// various ndarray methods +#if NDARRAY_HAS_BYTESWAP +mp_obj_t ndarray_byteswap(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(ndarray_byteswap_obj); +#endif + +#if NDARRAY_HAS_COPY +mp_obj_t ndarray_copy(mp_obj_t ); +MP_DECLARE_CONST_FUN_OBJ_1(ndarray_copy_obj); +#endif + +#if NDARRAY_HAS_FLATTEN +mp_obj_t ndarray_flatten(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(ndarray_flatten_obj); +#endif + +#if NDARRAY_HAS_DTYPE +mp_obj_t ndarray_dtype(mp_obj_t ); +#endif + +#if NDARRAY_HAS_ITEMSIZE +mp_obj_t ndarray_itemsize(mp_obj_t ); +#endif + +#if NDARRAY_HAS_SIZE +mp_obj_t ndarray_size(mp_obj_t ); +#endif + +#if NDARRAY_HAS_SHAPE +mp_obj_t ndarray_shape(mp_obj_t ); +#endif + +#if NDARRAY_HAS_STRIDES +mp_obj_t ndarray_strides(mp_obj_t ); +#endif + +#if NDARRAY_HAS_RESHAPE +mp_obj_t ndarray_reshape_core(mp_obj_t , mp_obj_t , bool ); +mp_obj_t ndarray_reshape(mp_obj_t , mp_obj_t ); +MP_DECLARE_CONST_FUN_OBJ_2(ndarray_reshape_obj); +#endif + +#if NDARRAY_HAS_TOBYTES +mp_obj_t ndarray_tobytes(mp_obj_t ); +MP_DECLARE_CONST_FUN_OBJ_1(ndarray_tobytes_obj); +#endif + +#if NDARRAY_HAS_TOLIST +mp_obj_t ndarray_tolist(mp_obj_t ); +MP_DECLARE_CONST_FUN_OBJ_1(ndarray_tolist_obj); +#endif + +#if NDARRAY_HAS_TRANSPOSE +mp_obj_t ndarray_transpose(mp_obj_t ); +MP_DECLARE_CONST_FUN_OBJ_1(ndarray_transpose_obj); +#endif + +#if ULAB_NUMPY_HAS_NDINFO +mp_obj_t ndarray_info(mp_obj_t ); +MP_DECLARE_CONST_FUN_OBJ_1(ndarray_info_obj); +#endif + +mp_int_t ndarray_get_buffer(mp_obj_t , mp_buffer_info_t *, mp_uint_t ); +//void ndarray_attributes(mp_obj_t , qstr , mp_obj_t *); + +ndarray_obj_t *ndarray_from_mp_obj(mp_obj_t , uint8_t ); + + +#define BOOLEAN_ASSIGNMENT_LOOP(type_left, type_right, ndarray, lstrides, iarray, istride, varray, vstride)\ + type_left *array = (type_left *)(ndarray)->array;\ + for(size_t i=0; i < (ndarray)->len; i++) {\ + if(*(iarray)) {\ + *array = (type_left)(*((type_right *)(varray)));\ + (varray) += (vstride);\ + }\ + array += (lstrides);\ + (iarray) += (istride);\ + } while(0) + +#if ULAB_HAS_FUNCTION_ITERATOR +#define BINARY_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + type_out *array = (type_out *)(results)->array;\ + size_t *lcoords = ndarray_new_coords((results)->ndim);\ + size_t *rcoords = ndarray_new_coords((results)->ndim);\ + for(size_t i=0; i < (results)->len/(results)->shape[ULAB_MAX_DIMS -1]; i++) {\ + size_t l = 0;\ + do {\ + *array++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + ndarray_rewind_array((results)->ndim, (larray), (results)->shape, (lstrides), lcoords);\ + ndarray_rewind_array((results)->ndim, (rarray), (results)->shape, (rstrides), rcoords);\ + } while(0) + +#define INPLACE_LOOP(results, type_left, type_right, larray, rarray, rstrides, OPERATOR)\ + size_t *lcoords = ndarray_new_coords((results)->ndim);\ + size_t *rcoords = ndarray_new_coords((results)->ndim);\ + for(size_t i=0; i < (results)->len/(results)->shape[ULAB_MAX_DIMS -1]; i++) {\ + size_t l = 0;\ + do {\ + *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + ndarray_rewind_array((results)->ndim, (larray), (results)->shape, (results)->strides, lcoords);\ + ndarray_rewind_array((results)->ndim, (rarray), (results)->shape, (rstrides), rcoords);\ + } while(0) + +#define EQUALITY_LOOP(results, array, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t *lcoords = ndarray_new_coords((results)->ndim);\ + size_t *rcoords = ndarray_new_coords((results)->ndim);\ + for(size_t i=0; i < (results)->len/(results)->shape[ULAB_MAX_DIMS -1]; i++) {\ + size_t l = 0;\ + do {\ + *(array)++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray)) ? 1 : 0;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + ndarray_rewind_array((results)->ndim, (larray), (results)->shape, (lstrides), lcoords);\ + ndarray_rewind_array((results)->ndim, (rarray), (results)->shape, (rstrides), rcoords);\ + } while(0) + +#define POWER_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides)\ + type_out *array = (type_out *)(results)->array;\ + size_t *lcoords = ndarray_new_coords((results)->ndim);\ + size_t *rcoords = ndarray_new_coords((results)->ndim);\ + for(size_t i=0; i < (results)->len/(results)->shape[ULAB_MAX_DIMS -1]; i++) {\ + size_t l = 0;\ + do {\ + *array++ = MICROPY_FLOAT_C_FUN(pow)(*((type_left *)(larray)), *((type_right *)(rarray)));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + ndarray_rewind_array((results)->ndim, (larray), (results)->shape, (lstrides), lcoords);\ + ndarray_rewind_array((results)->ndim, (rarray), (results)->shape, (rstrides), rcoords);\ + } while(0) + +#else + +#if ULAB_MAX_DIMS == 1 +#define BINARY_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + type_out *array = (type_out *)results->array;\ + size_t l = 0;\ + do {\ + *array++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + +#define INPLACE_LOOP(results, type_left, type_right, larray, rarray, rstrides, OPERATOR)\ + size_t l = 0;\ + do {\ + *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + +#define EQUALITY_LOOP(results, array, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t l = 0;\ + do {\ + *(array)++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray)) ? 1 : 0;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + +#define POWER_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides)\ + type_out *array = (type_out *)results->array;\ + size_t l = 0;\ + do {\ + *array++ = MICROPY_FLOAT_C_FUN(pow)(*((type_left *)(larray)), *((type_right *)(rarray)));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + +#endif /* ULAB_MAX_DIMS == 1 */ + +#if ULAB_MAX_DIMS == 2 +#define BINARY_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + type_out *array = (type_out *)(results)->array;\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *array++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + +#define INPLACE_LOOP(results, type_left, type_right, larray, rarray, rstrides, OPERATOR)\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + +#define EQUALITY_LOOP(results, array, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *(array)++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray)) ? 1 : 0;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + +#define POWER_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides)\ + type_out *array = (type_out *)(results)->array;\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *array++ = MICROPY_FLOAT_C_FUN(pow)(*((type_left *)(larray)), *((type_right *)(rarray)));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + +#endif /* ULAB_MAX_DIMS == 2 */ + +#if ULAB_MAX_DIMS == 3 +#define BINARY_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + type_out *array = (type_out *)results->array;\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *array++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + +#define INPLACE_LOOP(results, type_left, type_right, larray, rarray, rstrides, OPERATOR)\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + +#define EQUALITY_LOOP(results, array, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *(array)++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray)) ? 1 : 0;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + +#define POWER_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides)\ + type_out *array = (type_out *)results->array;\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *array++ = MICROPY_FLOAT_C_FUN(pow)(*((type_left *)(larray)), *((type_right *)(rarray)));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + +#endif /* ULAB_MAX_DIMS == 3 */ + +#if ULAB_MAX_DIMS == 4 +#define BINARY_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + type_out *array = (type_out *)results->array;\ + size_t i = 0;\ + do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *array++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ + +#define INPLACE_LOOP(results, type_left, type_right, larray, rarray, rstrides, OPERATOR)\ + size_t i = 0;\ + do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ + +#define EQUALITY_LOOP(results, array, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t i = 0;\ + do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *(array)++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray)) ? 1 : 0;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ + +#define POWER_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides)\ + type_out *array = (type_out *)results->array;\ + size_t i = 0;\ + do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *array++ = MICROPY_FLOAT_C_FUN(pow)(*((type_left *)(larray)), *((type_right *)(rarray)));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ + +#endif /* ULAB_MAX_DIMS == 4 */ +#endif /* ULAB_HAS_FUNCTION_ITERATOR */ + +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/ndarray_operators.c b/components/3rd_party/omv/omv/modules/ulab/code/ndarray_operators.c new file mode 100644 index 00000000..3983dabc --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/ndarray_operators.c @@ -0,0 +1,935 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös +*/ + + +#include + +#include "py/runtime.h" +#include "py/objtuple.h" +#include "ndarray.h" +#include "ndarray_operators.h" +#include "ulab.h" +#include "ulab_tools.h" +#include "numpy/carray/carray.h" + +/* + This file contains the actual implementations of the various + ndarray operators. + + These are the upcasting rules of the binary operators + + - if complex is supported, and if one of the operarands is a complex, the result is always complex + - if both operarands are real one of them is a float, then the result is also a float + - operation on identical types preserves type + + uint8 + int8 => int16 + uint8 + int16 => int16 + uint8 + uint16 => uint16 + int8 + int16 => int16 + int8 + uint16 => uint16 + uint16 + int16 => float +*/ + +#if NDARRAY_HAS_BINARY_OP_EQUAL | NDARRAY_HAS_BINARY_OP_NOT_EQUAL +mp_obj_t ndarray_binary_equality(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides, mp_binary_op_t op) { + + #if ULAB_SUPPORTS_COMPLEX + if((lhs->dtype == NDARRAY_COMPLEX) || (rhs->dtype == NDARRAY_COMPLEX)) { + return carray_binary_equal_not_equal(lhs, rhs, ndim, shape, lstrides, rstrides, op); + } + #endif + + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT8); + results->boolean = 1; + uint8_t *array = (uint8_t *)results->array; + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + #if NDARRAY_HAS_BINARY_OP_EQUAL + if(op == MP_BINARY_OP_EQUAL) { + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, uint8_t, uint8_t, larray, lstrides, rarray, rstrides, ==); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, uint8_t, int8_t, larray, lstrides, rarray, rstrides, ==); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, uint8_t, uint16_t, larray, lstrides, rarray, rstrides, ==); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, uint8_t, int16_t, larray, lstrides, rarray, rstrides, ==); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides, ==); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, int8_t, int8_t, larray, lstrides, rarray, rstrides, ==); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, int8_t, uint16_t, larray, lstrides, rarray, rstrides, ==); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, int8_t, int16_t, larray, lstrides, rarray, rstrides, ==); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, int8_t, mp_float_t, larray, lstrides, rarray, rstrides, ==); + } else { + return ndarray_binary_op(op, MP_OBJ_FROM_PTR(rhs), MP_OBJ_FROM_PTR(lhs)); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, uint16_t, uint16_t, larray, lstrides, rarray, rstrides, ==); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, uint16_t, int16_t, larray, lstrides, rarray, rstrides, ==); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, ==); + } else { + return ndarray_binary_op(op, MP_OBJ_FROM_PTR(rhs), MP_OBJ_FROM_PTR(lhs)); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, int16_t, int16_t, larray, lstrides, rarray, rstrides, ==); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, int16_t, mp_float_t, larray, lstrides, rarray, rstrides, ==); + } else { + return ndarray_binary_op(op, MP_OBJ_FROM_PTR(rhs), MP_OBJ_FROM_PTR(lhs)); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides, ==); + } else { + return ndarray_binary_op(op, MP_OBJ_FROM_PTR(rhs), MP_OBJ_FROM_PTR(lhs)); + } + } + } + #endif /* NDARRAY_HAS_BINARY_OP_EQUAL */ + + #if NDARRAY_HAS_BINARY_OP_NOT_EQUAL + if(op == MP_BINARY_OP_NOT_EQUAL) { + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, uint8_t, uint8_t, larray, lstrides, rarray, rstrides, !=); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, uint8_t, int8_t, larray, lstrides, rarray, rstrides, !=); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, uint8_t, uint16_t, larray, lstrides, rarray, rstrides, !=); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, uint8_t, int16_t, larray, lstrides, rarray, rstrides, !=); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides, !=); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, int8_t, int8_t, larray, lstrides, rarray, rstrides, !=); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, int8_t, uint16_t, larray, lstrides, rarray, rstrides, !=); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, int8_t, int16_t, larray, lstrides, rarray, rstrides, !=); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, int8_t, mp_float_t, larray, lstrides, rarray, rstrides, !=); + } else { + return ndarray_binary_op(op, MP_OBJ_FROM_PTR(rhs), MP_OBJ_FROM_PTR(lhs)); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, uint16_t, uint16_t, larray, lstrides, rarray, rstrides, !=); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, uint16_t, int16_t, larray, lstrides, rarray, rstrides, !=); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, !=); + } else { + return ndarray_binary_op(op, MP_OBJ_FROM_PTR(rhs), MP_OBJ_FROM_PTR(lhs)); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, int16_t, int16_t, larray, lstrides, rarray, rstrides, !=); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, int16_t, mp_float_t, larray, lstrides, rarray, rstrides, !=); + } else { + return ndarray_binary_op(op, MP_OBJ_FROM_PTR(rhs), MP_OBJ_FROM_PTR(lhs)); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides, !=); + } else { + return ndarray_binary_op(op, MP_OBJ_FROM_PTR(rhs), MP_OBJ_FROM_PTR(lhs)); + } + } + } + #endif /* NDARRAY_HAS_BINARY_OP_NOT_EQUAL */ + + return MP_OBJ_FROM_PTR(results); +} +#endif /* NDARRAY_HAS_BINARY_OP_EQUAL | NDARRAY_HAS_BINARY_OP_NOT_EQUAL */ + +#if NDARRAY_HAS_BINARY_OP_ADD +mp_obj_t ndarray_binary_add(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + + #if ULAB_SUPPORTS_COMPLEX + if((lhs->dtype == NDARRAY_COMPLEX) || (rhs->dtype == NDARRAY_COMPLEX)) { + return carray_binary_add(lhs, rhs, ndim, shape, lstrides, rstrides); + } + #endif + + ndarray_obj_t *results = NULL; + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + BINARY_LOOP(results, uint16_t, uint8_t, uint8_t, larray, lstrides, rarray, rstrides, +); + } else if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, uint8_t, int8_t, larray, lstrides, rarray, rstrides, +); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + BINARY_LOOP(results, uint16_t, uint8_t, uint16_t, larray, lstrides, rarray, rstrides, +); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, uint8_t, int16_t, larray, lstrides, rarray, rstrides, +); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides, +); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT8); + BINARY_LOOP(results, int8_t, int8_t, int8_t, larray, lstrides, rarray, rstrides, +); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int8_t, uint16_t, larray, lstrides, rarray, rstrides, +); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int8_t, int16_t, larray, lstrides, rarray, rstrides, +); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, int8_t, mp_float_t, larray, lstrides, rarray, rstrides, +); + } else { + return ndarray_binary_op(MP_BINARY_OP_ADD, MP_OBJ_FROM_PTR(rhs), MP_OBJ_FROM_PTR(lhs)); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + BINARY_LOOP(results, uint16_t, uint16_t, uint16_t, larray, lstrides, rarray, rstrides, +); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, uint16_t, int16_t, larray, lstrides, rarray, rstrides, +); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, +); + } else { + return ndarray_binary_op(MP_BINARY_OP_ADD, MP_OBJ_FROM_PTR(rhs), MP_OBJ_FROM_PTR(lhs)); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int16_t, int16_t, larray, lstrides, rarray, rstrides, +); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, int16_t, mp_float_t, larray, lstrides, rarray, rstrides, +); + } else { + return ndarray_binary_op(MP_BINARY_OP_ADD, MP_OBJ_FROM_PTR(rhs), MP_OBJ_FROM_PTR(lhs)); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides, +); + } else { + return ndarray_binary_op(MP_BINARY_OP_ADD, MP_OBJ_FROM_PTR(rhs), MP_OBJ_FROM_PTR(lhs)); + } + } + + return MP_OBJ_FROM_PTR(results); +} +#endif /* NDARRAY_HAS_BINARY_OP_ADD */ + +#if NDARRAY_HAS_BINARY_OP_MULTIPLY +mp_obj_t ndarray_binary_multiply(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + + #if ULAB_SUPPORTS_COMPLEX + if((lhs->dtype == NDARRAY_COMPLEX) || (rhs->dtype == NDARRAY_COMPLEX)) { + return carray_binary_multiply(lhs, rhs, ndim, shape, lstrides, rstrides); + } + #endif + + ndarray_obj_t *results = NULL; + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + BINARY_LOOP(results, uint16_t, uint8_t, uint8_t, larray, lstrides, rarray, rstrides, *); + } else if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, uint8_t, int8_t, larray, lstrides, rarray, rstrides, *); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + BINARY_LOOP(results, uint16_t, uint8_t, uint16_t, larray, lstrides, rarray, rstrides, *); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, uint8_t, int16_t, larray, lstrides, rarray, rstrides, *); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides, *); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT8); + BINARY_LOOP(results, int8_t, int8_t, int8_t, larray, lstrides, rarray, rstrides, *); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int8_t, uint16_t, larray, lstrides, rarray, rstrides, *); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int8_t, int16_t, larray, lstrides, rarray, rstrides, *); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, int8_t, mp_float_t, larray, lstrides, rarray, rstrides, *); + } else { + return ndarray_binary_op(MP_BINARY_OP_MULTIPLY, MP_OBJ_FROM_PTR(rhs), MP_OBJ_FROM_PTR(lhs)); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + BINARY_LOOP(results, uint16_t, uint16_t, uint16_t, larray, lstrides, rarray, rstrides, *); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, uint16_t, int16_t, larray, lstrides, rarray, rstrides, *); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, *); + } else { + return ndarray_binary_op(MP_BINARY_OP_MULTIPLY, MP_OBJ_FROM_PTR(rhs), MP_OBJ_FROM_PTR(lhs)); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int16_t, int16_t, larray, lstrides, rarray, rstrides, *); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, int16_t, mp_float_t, larray, lstrides, rarray, rstrides, *); + } else { + return ndarray_binary_op(MP_BINARY_OP_MULTIPLY, MP_OBJ_FROM_PTR(rhs), MP_OBJ_FROM_PTR(lhs)); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides, *); + } else { + return ndarray_binary_op(MP_BINARY_OP_MULTIPLY, MP_OBJ_FROM_PTR(rhs), MP_OBJ_FROM_PTR(lhs)); + } + } + + return MP_OBJ_FROM_PTR(results); +} +#endif /* NDARRAY_HAS_BINARY_OP_MULTIPLY */ + +#if NDARRAY_HAS_BINARY_OP_MORE | NDARRAY_HAS_BINARY_OP_MORE_EQUAL | NDARRAY_HAS_BINARY_OP_LESS | NDARRAY_HAS_BINARY_OP_LESS_EQUAL +mp_obj_t ndarray_binary_more(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides, mp_binary_op_t op) { + + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT8); + results->boolean = 1; + uint8_t *array = (uint8_t *)results->array; + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + #if NDARRAY_HAS_BINARY_OP_MORE | NDARRAY_HAS_BINARY_OP_LESS + if(op == MP_BINARY_OP_MORE) { + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, uint8_t, uint8_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, uint8_t, int8_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, uint8_t, uint16_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, uint8_t, int16_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides, >); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, int8_t, uint8_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, int8_t, int8_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, int8_t, uint16_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, int8_t, int16_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, int8_t, mp_float_t, larray, lstrides, rarray, rstrides, >); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, uint16_t, uint8_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, uint16_t, int8_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, uint16_t, uint16_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, uint16_t, int16_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, >); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, int16_t, uint8_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, int16_t, int8_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, int16_t, uint16_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, int16_t, int16_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, >); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, mp_float_t, uint8_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, mp_float_t, int8_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, mp_float_t, uint16_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, mp_float_t, int16_t, larray, lstrides, rarray, rstrides, >); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides, >); + } + } + } + #endif /* NDARRAY_HAS_BINARY_OP_MORE | NDARRAY_HAS_BINARY_OP_LESS*/ + #if NDARRAY_HAS_BINARY_OP_MORE_EQUAL | NDARRAY_HAS_BINARY_OP_LESS_EQUAL + if(op == MP_BINARY_OP_MORE_EQUAL) { + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, uint8_t, uint8_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, uint8_t, int8_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, uint8_t, uint16_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, uint8_t, int16_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides, >=); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, int8_t, uint8_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, int8_t, int8_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, int8_t, uint16_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, int8_t, int16_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, int8_t, mp_float_t, larray, lstrides, rarray, rstrides, >=); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, uint16_t, uint8_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, uint16_t, int8_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, uint16_t, uint16_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, uint16_t, int16_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, >=); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, int16_t, uint8_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, int16_t, int8_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, int16_t, uint16_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, int16_t, int16_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, >=); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + if(rhs->dtype == NDARRAY_UINT8) { + EQUALITY_LOOP(results, array, mp_float_t, uint8_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_INT8) { + EQUALITY_LOOP(results, array, mp_float_t, int8_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_UINT16) { + EQUALITY_LOOP(results, array, mp_float_t, uint16_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_INT16) { + EQUALITY_LOOP(results, array, mp_float_t, int16_t, larray, lstrides, rarray, rstrides, >=); + } else if(rhs->dtype == NDARRAY_FLOAT) { + EQUALITY_LOOP(results, array, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides, >=); + } + } + } + #endif /* NDARRAY_HAS_BINARY_OP_MORE_EQUAL | NDARRAY_HAS_BINARY_OP_LESS_EQUAL */ + + return MP_OBJ_FROM_PTR(results); +} +#endif /* NDARRAY_HAS_BINARY_OP_MORE | NDARRAY_HAS_BINARY_OP_MORE_EQUAL | NDARRAY_HAS_BINARY_OP_LESS | NDARRAY_HAS_BINARY_OP_LESS_EQUAL */ + +#if NDARRAY_HAS_BINARY_OP_SUBTRACT +mp_obj_t ndarray_binary_subtract(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + + #if ULAB_SUPPORTS_COMPLEX + if((lhs->dtype == NDARRAY_COMPLEX) || (rhs->dtype == NDARRAY_COMPLEX)) { + return carray_binary_subtract(lhs, rhs, ndim, shape, lstrides, rstrides); + } + #endif + + ndarray_obj_t *results = NULL; + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT8); + BINARY_LOOP(results, uint8_t, uint8_t, uint8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, uint8_t, int8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + BINARY_LOOP(results, uint16_t, uint8_t, uint16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, uint8_t, int16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides, -); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_UINT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int8_t, uint8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT8); + BINARY_LOOP(results, int8_t, int8_t, int8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int8_t, uint16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int8_t, int16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, int8_t, mp_float_t, larray, lstrides, rarray, rstrides, -); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + BINARY_LOOP(results, uint16_t, uint16_t, uint8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + BINARY_LOOP(results, uint16_t, uint16_t, int8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + BINARY_LOOP(results, uint16_t, uint16_t, uint16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, uint16_t, int16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, -); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_UINT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int16_t, uint8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int16_t, int8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, int16_t, uint16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + BINARY_LOOP(results, int16_t, int16_t, int16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, -); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + if(rhs->dtype == NDARRAY_UINT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, mp_float_t, uint8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, mp_float_t, int8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, mp_float_t, uint16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, mp_float_t, int16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + BINARY_LOOP(results, mp_float_t, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides, -); + } + } + + return MP_OBJ_FROM_PTR(results); +} +#endif /* NDARRAY_HAS_BINARY_OP_SUBTRACT */ + +#if NDARRAY_HAS_BINARY_OP_TRUE_DIVIDE +mp_obj_t ndarray_binary_true_divide(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + + #if ULAB_SUPPORTS_COMPLEX + if((lhs->dtype == NDARRAY_COMPLEX) || (rhs->dtype == NDARRAY_COMPLEX)) { + return carray_binary_divide(lhs, rhs, ndim, shape, lstrides, rstrides); + } + #endif + + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + #if NDARRAY_BINARY_USES_FUN_POINTER + mp_float_t (*get_lhs)(void *) = ndarray_get_float_function(lhs->dtype); + mp_float_t (*get_rhs)(void *) = ndarray_get_float_function(rhs->dtype); + + uint8_t *array = (uint8_t *)results->array; + void (*set_result)(void *, mp_float_t ) = ndarray_set_float_function(NDARRAY_FLOAT); + + // Note that lvalue and rvalue are local variables in the macro itself + FUNC_POINTER_LOOP(results, array, get_lhs, get_rhs, larray, lstrides, rarray, rstrides, lvalue/rvalue); + + #else + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + BINARY_LOOP(results, mp_float_t, uint8_t, uint8_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_INT8) { + BINARY_LOOP(results, mp_float_t, uint8_t, int8_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_UINT16) { + BINARY_LOOP(results, mp_float_t, uint8_t, uint16_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_INT16) { + BINARY_LOOP(results, mp_float_t, uint8_t, int16_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_FLOAT) { + BINARY_LOOP(results, mp_float_t, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides, /); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_UINT8) { + BINARY_LOOP(results, mp_float_t, int8_t, uint8_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_INT8) { + BINARY_LOOP(results, mp_float_t, int8_t, int8_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_UINT16) { + BINARY_LOOP(results, mp_float_t, int8_t, uint16_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_INT16) { + BINARY_LOOP(results, mp_float_t, int8_t, int16_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_FLOAT) { + BINARY_LOOP(results, mp_float_t, int8_t, mp_float_t, larray, lstrides, rarray, rstrides, /); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT8) { + BINARY_LOOP(results, mp_float_t, uint16_t, uint8_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_INT8) { + BINARY_LOOP(results, mp_float_t, uint16_t, int8_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_UINT16) { + BINARY_LOOP(results, mp_float_t, uint16_t, uint16_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_INT16) { + BINARY_LOOP(results, mp_float_t, uint16_t, int16_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_FLOAT) { + BINARY_LOOP(results, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, /); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_UINT8) { + BINARY_LOOP(results, mp_float_t, int16_t, uint8_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_INT8) { + BINARY_LOOP(results, mp_float_t, int16_t, int8_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_UINT16) { + BINARY_LOOP(results, mp_float_t, int16_t, uint16_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_INT16) { + BINARY_LOOP(results, mp_float_t, int16_t, int16_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_FLOAT) { + BINARY_LOOP(results, mp_float_t, int16_t, mp_float_t, larray, lstrides, rarray, rstrides, /); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + if(rhs->dtype == NDARRAY_UINT8) { + BINARY_LOOP(results, mp_float_t, mp_float_t, uint8_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_INT8) { + BINARY_LOOP(results, mp_float_t, mp_float_t, int8_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_UINT16) { + BINARY_LOOP(results, mp_float_t, mp_float_t, uint16_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_INT16) { + BINARY_LOOP(results, mp_float_t, mp_float_t, int16_t, larray, lstrides, rarray, rstrides, /); + } else if(rhs->dtype == NDARRAY_FLOAT) { + BINARY_LOOP(results, mp_float_t, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides, /); + } + } + #endif /* NDARRAY_BINARY_USES_FUN_POINTER */ + + return MP_OBJ_FROM_PTR(results); +} +#endif /* NDARRAY_HAS_BINARY_OP_TRUE_DIVIDE */ + +#if NDARRAY_HAS_BINARY_OP_FLOOR_DIVIDE +mp_obj_t ndarray_binary_floor_divide(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + + ndarray_obj_t *results = NULL; + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT8); + FLOOR_DIVIDE_LOOP_UINT(results, uint8_t, uint8_t, uint8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + FLOOR_DIVIDE_LOOP(results, int16_t, uint8_t, int8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + FLOOR_DIVIDE_LOOP_UINT(results, uint16_t, uint8_t, uint16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + FLOOR_DIVIDE_LOOP(results, int16_t, uint8_t, int16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + FLOOR_DIVIDE_LOOP_FLOAT(results, mp_float_t, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_UINT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + FLOOR_DIVIDE_LOOP(results, int16_t, int8_t, uint8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT8); + FLOOR_DIVIDE_LOOP(results, int8_t, int8_t, int8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + FLOOR_DIVIDE_LOOP(results, uint16_t, int8_t, uint16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + FLOOR_DIVIDE_LOOP(results, int16_t, int8_t, int16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + FLOOR_DIVIDE_LOOP_FLOAT(results, mp_float_t, int8_t, mp_float_t, larray, lstrides, rarray, rstrides); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + FLOOR_DIVIDE_LOOP_UINT(results, uint16_t, uint16_t, uint8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + FLOOR_DIVIDE_LOOP(results, uint16_t, uint16_t, int8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT16); + FLOOR_DIVIDE_LOOP_UINT(results, uint16_t, uint16_t, uint16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + FLOOR_DIVIDE_LOOP_FLOAT(results, mp_float_t, uint16_t, int16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + FLOOR_DIVIDE_LOOP_FLOAT(results, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_UINT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + FLOOR_DIVIDE_LOOP(results, int16_t, int16_t, uint8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT8) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + FLOOR_DIVIDE_LOOP(results, int16_t, int16_t, int8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_UINT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + FLOOR_DIVIDE_LOOP_FLOAT(results, mp_float_t, int16_t, uint16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT16) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_INT16); + FLOOR_DIVIDE_LOOP(results, int16_t, int16_t, int16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + FLOOR_DIVIDE_LOOP_FLOAT(results, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + if(rhs->dtype == NDARRAY_UINT8) { + FLOOR_DIVIDE_LOOP_FLOAT(results, mp_float_t, mp_float_t, uint8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT8) { + FLOOR_DIVIDE_LOOP_FLOAT(results, mp_float_t, mp_float_t, int8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_UINT16) { + FLOOR_DIVIDE_LOOP_FLOAT(results, mp_float_t, mp_float_t, uint16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT16) { + FLOOR_DIVIDE_LOOP_FLOAT(results, mp_float_t, mp_float_t, int16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_FLOAT) { + FLOOR_DIVIDE_LOOP_FLOAT(results, mp_float_t, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides); + } + } + + return MP_OBJ_FROM_PTR(results); + +} +#endif /* NDARRAY_HAS_BINARY_OP_FLOOR_DIVIDE */ + +#if NDARRAY_HAS_BINARY_OP_POWER +mp_obj_t ndarray_binary_power(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + + // Note that numpy upcasts the results to int64, if the inputs are of integer type, + // while we always return a float array. + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + #if NDARRAY_BINARY_USES_FUN_POINTER + mp_float_t (*get_lhs)(void *) = ndarray_get_float_function(lhs->dtype); + mp_float_t (*get_rhs)(void *) = ndarray_get_float_function(rhs->dtype); + + uint8_t *array = (uint8_t *)results->array; + void (*set_result)(void *, mp_float_t ) = ndarray_set_float_function(NDARRAY_FLOAT); + + // Note that lvalue and rvalue are local variables in the macro itself + FUNC_POINTER_LOOP(results, array, get_lhs, get_rhs, larray, lstrides, rarray, rstrides, MICROPY_FLOAT_C_FUN(pow)(lvalue, rvalue)); + + #else + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + POWER_LOOP(results, mp_float_t, uint8_t, uint8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT8) { + POWER_LOOP(results, mp_float_t, uint8_t, int8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_UINT16) { + POWER_LOOP(results, mp_float_t, uint8_t, uint16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT16) { + POWER_LOOP(results, mp_float_t, uint8_t, int16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_FLOAT) { + POWER_LOOP(results, mp_float_t, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_UINT8) { + POWER_LOOP(results, mp_float_t, int8_t, uint8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT8) { + POWER_LOOP(results, mp_float_t, int8_t, int8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_UINT16) { + POWER_LOOP(results, mp_float_t, int8_t, uint16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT16) { + POWER_LOOP(results, mp_float_t, int8_t, int16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_FLOAT) { + POWER_LOOP(results, mp_float_t, int8_t, mp_float_t, larray, lstrides, rarray, rstrides); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT8) { + POWER_LOOP(results, mp_float_t, uint16_t, uint8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT8) { + POWER_LOOP(results, mp_float_t, uint16_t, int8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_UINT16) { + POWER_LOOP(results, mp_float_t, uint16_t, uint16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT16) { + POWER_LOOP(results, mp_float_t, uint16_t, int16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_FLOAT) { + POWER_LOOP(results, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_UINT8) { + POWER_LOOP(results, mp_float_t, int16_t, uint8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT8) { + POWER_LOOP(results, mp_float_t, int16_t, int8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_UINT16) { + POWER_LOOP(results, mp_float_t, int16_t, uint16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT16) { + POWER_LOOP(results, mp_float_t, int16_t, int16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_FLOAT) { + POWER_LOOP(results, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + if(rhs->dtype == NDARRAY_UINT8) { + POWER_LOOP(results, mp_float_t, mp_float_t, uint8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT8) { + POWER_LOOP(results, mp_float_t, mp_float_t, int8_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_UINT16) { + POWER_LOOP(results, mp_float_t, mp_float_t, uint16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT16) { + POWER_LOOP(results, mp_float_t, mp_float_t, int16_t, larray, lstrides, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_FLOAT) { + POWER_LOOP(results, mp_float_t, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides); + } + } + #endif /* NDARRAY_BINARY_USES_FUN_POINTER */ + + return MP_OBJ_FROM_PTR(results); +} +#endif /* NDARRAY_HAS_BINARY_OP_POWER */ + +#if NDARRAY_HAS_INPLACE_ADD || NDARRAY_HAS_INPLACE_MULTIPLY || NDARRAY_HAS_INPLACE_SUBTRACT +mp_obj_t ndarray_inplace_ams(ndarray_obj_t *lhs, ndarray_obj_t *rhs, int32_t *rstrides, uint8_t optype) { + + if((lhs->dtype != NDARRAY_FLOAT) && (rhs->dtype == NDARRAY_FLOAT)) { + mp_raise_TypeError(translate("cannot cast output with casting rule")); + } + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + #if NDARRAY_HAS_INPLACE_ADD + if(optype == MP_BINARY_OP_INPLACE_ADD) { + UNWRAP_INPLACE_OPERATOR(lhs, larray, rarray, rstrides, +=); + } + #endif + #if NDARRAY_HAS_INPLACE_ADD + if(optype == MP_BINARY_OP_INPLACE_MULTIPLY) { + UNWRAP_INPLACE_OPERATOR(lhs, larray, rarray, rstrides, *=); + } + #endif + #if NDARRAY_HAS_INPLACE_SUBTRACT + if(optype == MP_BINARY_OP_INPLACE_SUBTRACT) { + UNWRAP_INPLACE_OPERATOR(lhs, larray, rarray, rstrides, -=); + } + #endif + + return MP_OBJ_FROM_PTR(lhs); +} +#endif /* NDARRAY_HAS_INPLACE_ADD || NDARRAY_HAS_INPLACE_MULTIPLY || NDARRAY_HAS_INPLACE_SUBTRACT */ + +#if NDARRAY_HAS_INPLACE_TRUE_DIVIDE +mp_obj_t ndarray_inplace_divide(ndarray_obj_t *lhs, ndarray_obj_t *rhs, int32_t *rstrides) { + + if((lhs->dtype != NDARRAY_FLOAT)) { + mp_raise_TypeError(translate("results cannot be cast to specified type")); + } + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + if(rhs->dtype == NDARRAY_UINT8) { + INPLACE_LOOP(lhs, mp_float_t, uint8_t, larray, rarray, rstrides, /=); + } else if(rhs->dtype == NDARRAY_INT8) { + INPLACE_LOOP(lhs, mp_float_t, int8_t, larray, rarray, rstrides, /=); + } else if(rhs->dtype == NDARRAY_UINT16) { + INPLACE_LOOP(lhs, mp_float_t, uint16_t, larray, rarray, rstrides, /=); + } else if(rhs->dtype == NDARRAY_INT16) { + INPLACE_LOOP(lhs, mp_float_t, int16_t, larray, rarray, rstrides, /=); + } else if(lhs->dtype == NDARRAY_FLOAT) { + INPLACE_LOOP(lhs, mp_float_t, mp_float_t, larray, rarray, rstrides, /=); + } + return MP_OBJ_FROM_PTR(lhs); +} +#endif /* NDARRAY_HAS_INPLACE_TRUE_DIVIDE */ + +#if NDARRAY_HAS_INPLACE_POWER +mp_obj_t ndarray_inplace_power(ndarray_obj_t *lhs, ndarray_obj_t *rhs, int32_t *rstrides) { + + if((lhs->dtype != NDARRAY_FLOAT)) { + mp_raise_TypeError(translate("results cannot be cast to specified type")); + } + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + if(rhs->dtype == NDARRAY_UINT8) { + INPLACE_POWER(lhs, mp_float_t, uint8_t, larray, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT8) { + INPLACE_POWER(lhs, mp_float_t, int8_t, larray, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_UINT16) { + INPLACE_POWER(lhs, mp_float_t, uint16_t, larray, rarray, rstrides); + } else if(rhs->dtype == NDARRAY_INT16) { + INPLACE_POWER(lhs, mp_float_t, int16_t, larray, rarray, rstrides); + } else if(lhs->dtype == NDARRAY_FLOAT) { + INPLACE_POWER(lhs, mp_float_t, mp_float_t, larray, rarray, rstrides); + } + return MP_OBJ_FROM_PTR(lhs); +} +#endif /* NDARRAY_HAS_INPLACE_POWER */ diff --git a/components/3rd_party/omv/omv/modules/ulab/code/ndarray_operators.h b/components/3rd_party/omv/omv/modules/ulab/code/ndarray_operators.h new file mode 100644 index 00000000..b8f080fa --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/ndarray_operators.h @@ -0,0 +1,538 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2023 Zoltán Vörös +*/ + +#include "ndarray.h" + +mp_obj_t ndarray_binary_equality(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *, mp_binary_op_t ); +mp_obj_t ndarray_binary_add(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *); +mp_obj_t ndarray_binary_multiply(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *); +mp_obj_t ndarray_binary_more(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *, mp_binary_op_t ); +mp_obj_t ndarray_binary_power(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *); +mp_obj_t ndarray_binary_subtract(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *); +mp_obj_t ndarray_binary_true_divide(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *); +mp_obj_t ndarray_binary_floor_divide(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *); + +mp_obj_t ndarray_inplace_ams(ndarray_obj_t *, ndarray_obj_t *, int32_t *, uint8_t ); +mp_obj_t ndarray_inplace_power(ndarray_obj_t *, ndarray_obj_t *, int32_t *); +mp_obj_t ndarray_inplace_divide(ndarray_obj_t *, ndarray_obj_t *, int32_t *); + +#define UNWRAP_INPLACE_OPERATOR(lhs, larray, rarray, rstrides, OPERATOR)\ +({\ + if((lhs)->dtype == NDARRAY_UINT8) {\ + if((rhs)->dtype == NDARRAY_UINT8) {\ + INPLACE_LOOP((lhs), uint8_t, uint8_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_INT8) {\ + INPLACE_LOOP((lhs), uint8_t, int8_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_UINT16) {\ + INPLACE_LOOP((lhs), uint8_t, uint16_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else {\ + INPLACE_LOOP((lhs), uint8_t, int16_t, (larray), (rarray), (rstrides), OPERATOR);\ + }\ + } else if(lhs->dtype == NDARRAY_INT8) {\ + if(rhs->dtype == NDARRAY_UINT8) {\ + INPLACE_LOOP((lhs), int8_t, uint8_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_INT8) {\ + INPLACE_LOOP((lhs), int8_t, int8_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_UINT16) {\ + INPLACE_LOOP((lhs), int8_t, uint16_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else {\ + INPLACE_LOOP((lhs), int8_t, int16_t, (larray), (rarray), (rstrides), OPERATOR);\ + }\ + } else if(lhs->dtype == NDARRAY_UINT16) {\ + if(rhs->dtype == NDARRAY_UINT8) {\ + INPLACE_LOOP((lhs), uint16_t, uint8_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_INT8) {\ + INPLACE_LOOP((lhs), uint16_t, int8_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_UINT16) {\ + INPLACE_LOOP((lhs), uint16_t, uint16_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else {\ + INPLACE_LOOP((lhs), uint16_t, int16_t, (larray), (rarray), (rstrides), OPERATOR);\ + }\ + } else if(lhs->dtype == NDARRAY_INT16) {\ + if(rhs->dtype == NDARRAY_UINT8) {\ + INPLACE_LOOP((lhs), int16_t, uint8_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_INT8) {\ + INPLACE_LOOP((lhs), int16_t, int8_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_UINT16) {\ + INPLACE_LOOP((lhs), int16_t, uint16_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else {\ + INPLACE_LOOP((lhs), int16_t, int16_t, (larray), (rarray), (rstrides), OPERATOR);\ + }\ + } else if(lhs->dtype == NDARRAY_FLOAT) {\ + if(rhs->dtype == NDARRAY_UINT8) {\ + INPLACE_LOOP((lhs), mp_float_t, uint8_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_INT8) {\ + INPLACE_LOOP((lhs), mp_float_t, int8_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_UINT16) {\ + INPLACE_LOOP((lhs), mp_float_t, uint16_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else if(rhs->dtype == NDARRAY_INT16) {\ + INPLACE_LOOP((lhs), mp_float_t, int16_t, (larray), (rarray), (rstrides), OPERATOR);\ + } else {\ + INPLACE_LOOP((lhs), mp_float_t, mp_float_t, (larray), (rarray), (rstrides), OPERATOR);\ + }\ + }\ +}) + +#if ULAB_MAX_DIMS == 1 +#define INPLACE_POWER(results, type_left, type_right, larray, rarray, rstrides)\ +({ size_t l = 0;\ + do {\ + *((type_left *)(larray)) = MICROPY_FLOAT_C_FUN(pow)(*((type_left *)(larray)), *((type_right *)(rarray)));\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ +}) + +#define FUNC_POINTER_LOOP(results, array, get_lhs, get_rhs, larray, lstrides, rarray, rstrides, OPERATION)\ +({ size_t l = 0;\ + do {\ + mp_float_t lvalue = (get_lhs)((larray));\ + mp_float_t rvalue = (get_rhs)((rarray));\ + (set_result)((array), OPERATION);\ + (array) += (results)->itemsize;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ +}) +#endif /* ULAB_MAX_DIMS == 1 */ + +#if ULAB_MAX_DIMS == 2 +#define INPLACE_POWER(results, type_left, type_right, larray, rarray, rstrides)\ +({ size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *((type_left *)(larray)) = MICROPY_FLOAT_C_FUN(pow)(*((type_left *)(larray)), *((type_right *)(rarray)));\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ +}) + +#define FUNC_POINTER_LOOP(results, array, get_lhs, get_rhs, larray, lstrides, rarray, rstrides, OPERATION)\ +({ size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + mp_float_t lvalue = (get_lhs)((larray));\ + mp_float_t rvalue = (get_rhs)((rarray));\ + (set_result)((array), OPERATION);\ + (array) += (results)->itemsize;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < results->shape[ULAB_MAX_DIMS - 2]);\ +}) +#endif /* ULAB_MAX_DIMS == 2 */ + +#if ULAB_MAX_DIMS == 3 +#define INPLACE_POWER(results, type_left, type_right, larray, rarray, rstrides)\ +({ size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *((type_left *)(larray)) = MICROPY_FLOAT_C_FUN(pow)(*((type_left *)(larray)), *((type_right *)(rarray)));\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ +}) + + +#define FUNC_POINTER_LOOP(results, array, get_lhs, get_rhs, larray, lstrides, rarray, rstrides, OPERATION)\ +({ size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + mp_float_t lvalue = (get_lhs)((larray));\ + mp_float_t rvalue = (get_rhs)((rarray));\ + (set_result)((array), OPERATION);\ + (array) += (results)->itemsize;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < results->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ +}) +#endif /* ULAB_MAX_DIMS == 3 */ + +#if ULAB_MAX_DIMS == 4 +#define INPLACE_POWER(results, type_left, type_right, larray, rarray, rstrides)\ +({ size_t i = 0;\ + do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *((type_left *)(larray)) = MICROPY_FLOAT_C_FUN(pow)(*((type_left *)(larray)), *((type_right *)(rarray)));\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ +}) + +#define FUNC_POINTER_LOOP(results, array, get_lhs, get_rhs, larray, lstrides, rarray, rstrides, OPERATION)\ +({ size_t i = 0;\ + do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + mp_float_t lvalue = (get_lhs)((larray));\ + mp_float_t rvalue = (get_rhs)((rarray));\ + (set_result)((array), OPERATION);\ + (array) += (results)->itemsize;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < results->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + (larray) -= (results)->strides[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (larray) += (results)->strides[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS-3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ +}) +#endif /* ULAB_MAX_DIMS == 4 */ + +#define FLOOR_DIVIDE_UINT1(results, array, type_left, type_right, larray, lstrides, rarray, rstrides)\ +({\ + size_t l = 0;\ + do {\ + *(array)++ = *((type_left *)(larray)) / *((type_right *)(rarray));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ +}) + +#define FLOOR_DIVIDE1(results, array, type_left, type_right, larray, lstrides, rarray, rstrides)\ +({\ + size_t l = 0;\ + int16_t num;\ + int16_t denom = (int16_t)*((type_right *)(rarray));\ + do {\ + num = (int16_t)*((type_left *)(larray));\ + if(num >= 0) {\ + if(denom < 0) {\ + num += -denom - 1;\ + }\ + } else {\ + if(denom >= 0) {\ + num += -denom + 1;\ + }\ + }\ + *(array)++ = num / denom;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ +}) + +#define FLOOR_DIVIDE_FLOAT1(results, array, type_left, type_right, larray, lstrides, rarray, rstrides)\ +({\ + size_t l = 0;\ + do {\ + *(array)++ = MICROPY_FLOAT_C_FUN(floor)(*((type_left *)(larray)) / *((type_right *)(rarray)));\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ +}) + +#if ULAB_MAX_DIMS == 1 +#define FLOOR_DIVIDE_LOOP_UINT(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides) do {\ + type_out *array = (type_out *)(results)->array;\ + FLOOR_DIVIDE_UINT1((results), (array), type_left, type_right, (larray), (lstrides), (rarray), (rstrides));\ +} while(0) + +#define FLOOR_DIVIDE_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides) do {\ + type_out *array = (type_out *)(results)->array;\ + FLOOR_DIVIDE1((results), (array), type_left, type_right, (larray), (lstrides), (rarray), (rstrides));\ +} while(0) + +#define FLOOR_DIVIDE_LOOP_FLOAT(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides) do {\ + type_out *array = (type_out *)(results)->array;\ + FLOOR_DIVIDE_FLOAT1((results), (array), type_left, type_right, (larray), (lstrides), (rarray), (rstrides));\ +} while(0) +#endif /* ULAB_MAX_DIMS == 1 */ + +#if ULAB_MAX_DIMS == 2 +#define FLOOR_DIVIDE_LOOP_UINT(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides) do {\ + type_out *array = (type_out *)(results)->array;\ + size_t l = 0;\ + do {\ + FLOOR_DIVIDE_UINT1((results), (array), type_left, type_right, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 2]);\ +} while(0) + +#define FLOOR_DIVIDE_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides) do {\ + type_out *array = (type_out *)(results)->array;\ + size_t l = 0;\ + do {\ + FLOOR_DIVIDE1((results), (array), type_left, type_right, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 2]);\ +} while(0) + +#define FLOOR_DIVIDE_LOOP_FLOAT(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides) do {\ + type_out *array = (type_out *)(results)->array;\ + size_t l = 0;\ + do {\ + FLOOR_DIVIDE_FLOAT1((results), (array), type_left, type_right, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 2]);\ +} while(0) + +#endif /* ULAB_MAX_DIMS == 2 */ + +#if ULAB_MAX_DIMS == 3 +#define FLOOR_DIVIDE_LOOP_UINT(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides) do {\ + type_out *array = (type_out *)(results)->array;\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + FLOOR_DIVIDE_UINT1((results), (array), type_left, type_right, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 3]);\ +} while(0) + +#define FLOOR_DIVIDE_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides) do {\ + type_out *array = (type_out *)(results)->array;\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + FLOOR_DIVIDE1((results), (array), type_left, type_right, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 3]);\ +} while(0) + +#define FLOOR_DIVIDE_LOOP_FLOAT(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides) do {\ + type_out *array = (type_out *)(results)->array;\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + FLOOR_DIVIDE_FLOAT1((results), (array), type_left, type_right, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 3]);\ +} while(0) + +#endif /* ULAB_MAX_DIMS == 3 */ + +#if ULAB_MAX_DIMS == 4 +#define FLOOR_DIVIDE_LOOP_UINT(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides) do {\ + type_out *array = (type_out *)(results)->array;\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + FLOOR_DIVIDE_UINT1((results), (array), type_left, type_right, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 3]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 4]);\ +} while(0) + +#define FLOOR_DIVIDE_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides) do {\ + type_out *array = (type_out *)(results)->array;\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + FLOOR_DIVIDE1((results), (array), type_left, type_right, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 3]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 4]);\ +} while(0) + +#define FLOOR_DIVIDE_LOOP_FLOAT(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides) do {\ + type_out *array = (type_out *)(results)->array;\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + FLOOR_DIVIDE_FLOAT1((results), (array), type_left, type_right, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 3]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 4]);\ +} while(0) + +#endif /* ULAB_MAX_DIMS == 4 */ diff --git a/components/3rd_party/omv/omv/modules/ulab/code/ndarray_properties.c b/components/3rd_party/omv/omv/modules/ulab/code/ndarray_properties.c new file mode 100644 index 00000000..547b1912 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/ndarray_properties.c @@ -0,0 +1,123 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Zoltán Vörös + * +*/ + +#include +#include +#include +#include +#include "py/obj.h" +#include "py/runtime.h" + +#include "ulab.h" +#include "ndarray.h" +#include "numpy/ndarray/ndarray_iter.h" +#if ULAB_SUPPORTS_COMPLEX +#include "numpy/carray/carray.h" +#endif + +#ifndef CIRCUITPY + +// a somewhat hackish implementation of property getters/setters; +// this functions is hooked into the attr member of ndarray + +STATIC void call_local_method(mp_obj_t obj, qstr attr, mp_obj_t *dest) { + const mp_obj_type_t *type = mp_obj_get_type(obj); + while (MP_OBJ_TYPE_HAS_SLOT(type, locals_dict)) { + assert(MP_OBJ_TYPE_GET_SLOT(type, locals_dict)->base.type == &mp_type_dict); // MicroPython restriction, for now + mp_map_t *locals_map = &MP_OBJ_TYPE_GET_SLOT(type, locals_dict)->map; + mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP); + if (elem != NULL) { + mp_convert_member_lookup(obj, type, elem->value, dest); + break; + } + if (!MP_OBJ_TYPE_HAS_SLOT(type, parent)) { + break; + } + type = MP_OBJ_TYPE_GET_SLOT(type, parent); + } +} + + +void ndarray_properties_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + if (dest[0] == MP_OBJ_NULL) { + switch(attr) { + #if NDARRAY_HAS_DTYPE + case MP_QSTR_dtype: + dest[0] = ndarray_dtype(self_in); + break; + #endif + #if NDARRAY_HAS_FLATITER + case MP_QSTR_flat: + dest[0] = ndarray_flatiter_make_new(self_in); + break; + #endif + #if NDARRAY_HAS_ITEMSIZE + case MP_QSTR_itemsize: + dest[0] = ndarray_itemsize(self_in); + break; + #endif + #if NDARRAY_HAS_SHAPE + case MP_QSTR_shape: + dest[0] = ndarray_shape(self_in); + break; + #endif + #if NDARRAY_HAS_SIZE + case MP_QSTR_size: + dest[0] = ndarray_size(self_in); + break; + #endif + #if NDARRAY_HAS_STRIDES + case MP_QSTR_strides: + dest[0] = ndarray_strides(self_in); + break; + #endif + #if NDARRAY_HAS_TRANSPOSE + case MP_QSTR_T: + dest[0] = ndarray_transpose(self_in); + break; + #endif + #if ULAB_SUPPORTS_COMPLEX + #if ULAB_NUMPY_HAS_IMAG + case MP_QSTR_imag: + dest[0] = carray_imag(self_in); + break; + #endif + #if ULAB_NUMPY_HAS_IMAG + case MP_QSTR_real: + dest[0] = carray_real(self_in); + break; + #endif + #endif /* ULAB_SUPPORTS_COMPLEX */ + default: + call_local_method(self_in, attr, dest); + break; + } + } else { + if(dest[1]) { + switch(attr) { + #if ULAB_MAX_DIMS > 1 + #if NDARRAY_HAS_RESHAPE + case MP_QSTR_shape: + ndarray_reshape_core(self_in, dest[1], 1); + break; + #endif + #endif + default: + return; + break; + } + dest[0] = MP_OBJ_NULL; + } + } +} + +#endif /* CIRCUITPY */ diff --git a/components/3rd_party/omv/omv/modules/ulab/code/ndarray_properties.h b/components/3rd_party/omv/omv/modules/ulab/code/ndarray_properties.h new file mode 100644 index 00000000..a074ec1f --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/ndarray_properties.h @@ -0,0 +1,121 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2020-2021 Zoltán Vörös +*/ + +#ifndef _NDARRAY_PROPERTIES_ +#define _NDARRAY_PROPERTIES_ + +#include "py/runtime.h" +#include "py/binary.h" +#include "py/obj.h" +#include "py/objarray.h" + +#include "ulab.h" +#include "ndarray.h" +#include "numpy/ndarray/ndarray_iter.h" + +#if CIRCUITPY +typedef struct _mp_obj_property_t { + mp_obj_base_t base; + mp_obj_t proxy[3]; // getter, setter, deleter +} mp_obj_property_t; + +#if NDARRAY_HAS_DTYPE +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_get_dtype_obj, ndarray_dtype); +STATIC const mp_obj_property_t ndarray_dtype_obj = { + .base.type = &mp_type_property, + .proxy = {(mp_obj_t)&ndarray_get_dtype_obj, + mp_const_none, + mp_const_none }, +}; +#endif /* NDARRAY_HAS_DTYPE */ + +#if NDARRAY_HAS_FLATITER +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_flatiter_make_new_obj, ndarray_flatiter_make_new); +STATIC const mp_obj_property_t ndarray_flat_obj = { + .base.type = &mp_type_property, + .proxy = {(mp_obj_t)&ndarray_flatiter_make_new_obj, + mp_const_none, + mp_const_none }, +}; +#endif /* NDARRAY_HAS_FLATITER */ + +#if NDARRAY_HAS_ITEMSIZE +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_get_itemsize_obj, ndarray_itemsize); +STATIC const mp_obj_property_t ndarray_itemsize_obj = { + .base.type = &mp_type_property, + .proxy = {(mp_obj_t)&ndarray_get_itemsize_obj, + mp_const_none, + mp_const_none }, +}; +#endif /* NDARRAY_HAS_ITEMSIZE */ + +#if NDARRAY_HAS_SHAPE +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_get_shape_obj, ndarray_shape); +STATIC const mp_obj_property_t ndarray_shape_obj = { + .base.type = &mp_type_property, + .proxy = {(mp_obj_t)&ndarray_get_shape_obj, + mp_const_none, + mp_const_none }, +}; +#endif /* NDARRAY_HAS_SHAPE */ + +#if NDARRAY_HAS_SIZE +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_get_size_obj, ndarray_size); +STATIC const mp_obj_property_t ndarray_size_obj = { + .base.type = &mp_type_property, + .proxy = {(mp_obj_t)&ndarray_get_size_obj, + mp_const_none, + mp_const_none }, +}; +#endif /* NDARRAY_HAS_SIZE */ + +#if NDARRAY_HAS_STRIDES +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_get_strides_obj, ndarray_strides); +STATIC const mp_obj_property_t ndarray_strides_obj = { + .base.type = &mp_type_property, + .proxy = {(mp_obj_t)&ndarray_get_strides_obj, + mp_const_none, + mp_const_none }, +}; +#endif /* NDARRAY_HAS_STRIDES */ + +#else + +void ndarray_properties_attr(mp_obj_t , qstr , mp_obj_t *); + +#if NDARRAY_HAS_DTYPE +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_dtype_obj, ndarray_dtype); +#endif + +#if NDARRAY_HAS_FLATITER +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_flatiter_make_new_obj, ndarray_flatiter_make_new); +#endif + +#if NDARRAY_HAS_ITEMSIZE +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_itemsize_obj, ndarray_itemsize); +#endif + +#if NDARRAY_HAS_SHAPE +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_shape_obj, ndarray_shape); +#endif + +#if NDARRAY_HAS_SIZE +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_size_obj, ndarray_size); +#endif + +#if NDARRAY_HAS_STRIDES +MP_DEFINE_CONST_FUN_OBJ_1(ndarray_strides_obj, ndarray_strides); +#endif + +#endif /* CIRCUITPY */ + +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/approx.c b/components/3rd_party/omv/omv/modules/ulab/code/numpy/approx.c new file mode 100644 index 00000000..6088173d --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/approx.c @@ -0,0 +1,227 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös + * 2020 Diego Elio Pettenò + * 2020 Taku Fukada +*/ + +#include +#include +#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" + +#include "../ulab.h" +#include "../ulab_tools.h" +#include "carray/carray_tools.h" +#include "approx.h" + +//| """Numerical approximation methods""" +//| + +ULAB_DEFINE_FLOAT_CONST(approx_trapz_dx, MICROPY_FLOAT_CONST(1.0), 0x3f800000UL, 0x3ff0000000000000ULL); + +#if ULAB_NUMPY_HAS_INTERP +//| def interp( +//| x: ulab.numpy.ndarray, +//| xp: ulab.numpy.ndarray, +//| fp: ulab.numpy.ndarray, +//| *, +//| left: Optional[_float] = None, +//| right: Optional[_float] = None +//| ) -> ulab.numpy.ndarray: +//| """ +//| :param ulab.numpy.ndarray x: The x-coordinates at which to evaluate the interpolated values. +//| :param ulab.numpy.ndarray xp: The x-coordinates of the data points, must be increasing +//| :param ulab.numpy.ndarray fp: The y-coordinates of the data points, same length as xp +//| :param left: Value to return for ``x < xp[0]``, default is ``fp[0]``. +//| :param right: Value to return for ``x > xp[-1]``, default is ``fp[-1]``. +//| +//| Returns the one-dimensional piecewise linear interpolant to a function with given discrete data points (xp, fp), evaluated at x.""" +//| ... +//| + +STATIC mp_obj_t approx_interp(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_left, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_right, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + ndarray_obj_t *x = ndarray_from_mp_obj(args[0].u_obj, 0); + ndarray_obj_t *xp = ndarray_from_mp_obj(args[1].u_obj, 0); // xp must hold an increasing sequence of independent values + ndarray_obj_t *fp = ndarray_from_mp_obj(args[2].u_obj, 0); + COMPLEX_DTYPE_NOT_IMPLEMENTED(x->dtype) + COMPLEX_DTYPE_NOT_IMPLEMENTED(xp->dtype) + COMPLEX_DTYPE_NOT_IMPLEMENTED(fp->dtype) + if((xp->ndim != 1) || (fp->ndim != 1) || (xp->len < 2) || (fp->len < 2) || (xp->len != fp->len)) { + mp_raise_ValueError(translate("interp is defined for 1D iterables of equal length")); + } + + ndarray_obj_t *y = ndarray_new_linear_array(x->len, NDARRAY_FLOAT); + mp_float_t left_value, right_value; + uint8_t *xparray = (uint8_t *)xp->array; + + mp_float_t xp_left = ndarray_get_float_value(xparray, xp->dtype); + xparray += (xp->len-1) * xp->strides[ULAB_MAX_DIMS - 1]; + mp_float_t xp_right = ndarray_get_float_value(xparray, xp->dtype); + + uint8_t *fparray = (uint8_t *)fp->array; + + if(args[3].u_obj == mp_const_none) { + left_value = ndarray_get_float_value(fparray, fp->dtype); + } else { + left_value = mp_obj_get_float(args[3].u_obj); + } + if(args[4].u_obj == mp_const_none) { + fparray += (fp->len-1) * fp->strides[ULAB_MAX_DIMS - 1]; + right_value = ndarray_get_float_value(fparray, fp->dtype); + } else { + right_value = mp_obj_get_float(args[4].u_obj); + } + + xparray = xp->array; + fparray = fp->array; + + uint8_t *xarray = (uint8_t *)x->array; + mp_float_t *yarray = (mp_float_t *)y->array; + uint8_t *temp; + + for(size_t i=0; i < x->len; i++, yarray++) { + mp_float_t x_value = ndarray_get_float_value(xarray, x->dtype); + xarray += x->strides[ULAB_MAX_DIMS - 1]; + if(x_value < xp_left) { + *yarray = left_value; + } else if(x_value > xp_right) { + *yarray = right_value; + } else { // do the binary search here + mp_float_t xp_left_, xp_right_; + mp_float_t fp_left, fp_right; + size_t left_index = 0, right_index = xp->len - 1, middle_index; + while(right_index - left_index > 1) { + middle_index = left_index + (right_index - left_index) / 2; + temp = xparray + middle_index * xp->strides[ULAB_MAX_DIMS - 1]; + mp_float_t xp_middle = ndarray_get_float_value(temp, xp->dtype); + if(x_value <= xp_middle) { + right_index = middle_index; + } else { + left_index = middle_index; + } + } + temp = xparray + left_index * xp->strides[ULAB_MAX_DIMS - 1]; + xp_left_ = ndarray_get_float_value(temp, xp->dtype); + + temp = xparray + right_index * xp->strides[ULAB_MAX_DIMS - 1]; + xp_right_ = ndarray_get_float_value(temp, xp->dtype); + + temp = fparray + left_index * fp->strides[ULAB_MAX_DIMS - 1]; + fp_left = ndarray_get_float_value(temp, fp->dtype); + + temp = fparray + right_index * fp->strides[ULAB_MAX_DIMS - 1]; + fp_right = ndarray_get_float_value(temp, fp->dtype); + + *yarray = fp_left + (x_value - xp_left_) * (fp_right - fp_left) / (xp_right_ - xp_left_); + } + } + return MP_OBJ_FROM_PTR(y); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(approx_interp_obj, 2, approx_interp); +#endif + +#if ULAB_NUMPY_HAS_TRAPZ +//| def trapz(y: ulab.numpy.ndarray, x: Optional[ulab.numpy.ndarray] = None, dx: _float = 1.0) -> _float: +//| """ +//| :param 1D ulab.numpy.ndarray y: the values of the dependent variable +//| :param 1D ulab.numpy.ndarray x: optional, the coordinates of the independent variable. Defaults to uniformly spaced values. +//| :param float dx: the spacing between sample points, if x=None +//| +//| Returns the integral of y(x) using the trapezoidal rule. +//| """ +//| ... +//| + +STATIC mp_obj_t approx_trapz(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_x, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_dx, MP_ARG_OBJ, {.u_rom_obj = ULAB_REFERENCE_FLOAT_CONST(approx_trapz_dx)} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + ndarray_obj_t *y = ndarray_from_mp_obj(args[0].u_obj, 0); + COMPLEX_DTYPE_NOT_IMPLEMENTED(y->dtype) + ndarray_obj_t *x; + mp_float_t mean = MICROPY_FLOAT_CONST(0.0); + if(y->len < 2) { + return mp_obj_new_float(mean); + } + if((y->ndim != 1)) { + mp_raise_ValueError(translate("trapz is defined for 1D iterables")); + } + + mp_float_t (*funcy)(void *) = ndarray_get_float_function(y->dtype); + uint8_t *yarray = (uint8_t *)y->array; + + size_t count = 1; + mp_float_t y1, y2, m; + + if(args[1].u_obj != mp_const_none) { + x = ndarray_from_mp_obj(args[1].u_obj, 0); // x must hold an increasing sequence of independent values + COMPLEX_DTYPE_NOT_IMPLEMENTED(x->dtype) + if((x->ndim != 1) || (y->len != x->len)) { + mp_raise_ValueError(translate("trapz is defined for 1D arrays of equal length")); + } + + mp_float_t (*funcx)(void *) = ndarray_get_float_function(x->dtype); + uint8_t *xarray = (uint8_t *)x->array; + mp_float_t x1, x2; + + y1 = funcy(yarray); + yarray += y->strides[ULAB_MAX_DIMS - 1]; + x1 = funcx(xarray); + xarray += x->strides[ULAB_MAX_DIMS - 1]; + + for(size_t i=1; i < y->len; i++) { + y2 = funcy(yarray); + yarray += y->strides[ULAB_MAX_DIMS - 1]; + x2 = funcx(xarray); + xarray += x->strides[ULAB_MAX_DIMS - 1]; + mp_float_t value = (x2 - x1) * (y2 + y1); + m = mean + (value - mean) / (mp_float_t)count; + mean = m; + x1 = x2; + y1 = y2; + count++; + } + } else { + mp_float_t dx = mp_obj_get_float(args[2].u_obj); + y1 = funcy(yarray); + yarray += y->strides[ULAB_MAX_DIMS - 1]; + + for(size_t i=1; i < y->len; i++) { + y2 = ndarray_get_float_index(y->array, y->dtype, i); + mp_float_t value = (y2 + y1); + m = mean + (value - mean) / (mp_float_t)count; + mean = m; + y1 = y2; + count++; + } + mean *= dx; + } + return mp_obj_new_float(MICROPY_FLOAT_CONST(0.5)*mean*(y->len-1)); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(approx_trapz_obj, 1, approx_trapz); +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/approx.h b/components/3rd_party/omv/omv/modules/ulab/code/numpy/approx.h new file mode 100644 index 00000000..487a98b5 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/approx.h @@ -0,0 +1,29 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös +*/ + +#ifndef _APPROX_ +#define _APPROX_ + +#include "../ulab.h" +#include "../ndarray.h" + +#define APPROX_EPS MICROPY_FLOAT_CONST(1.0e-4) +#define APPROX_NONZDELTA MICROPY_FLOAT_CONST(0.05) +#define APPROX_ZDELTA MICROPY_FLOAT_CONST(0.00025) +#define APPROX_ALPHA MICROPY_FLOAT_CONST(1.0) +#define APPROX_BETA MICROPY_FLOAT_CONST(2.0) +#define APPROX_GAMMA MICROPY_FLOAT_CONST(0.5) +#define APPROX_DELTA MICROPY_FLOAT_CONST(0.5) + +MP_DECLARE_CONST_FUN_OBJ_KW(approx_interp_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(approx_trapz_obj); + +#endif /* _APPROX_ */ diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/carray/carray.c b/components/3rd_party/omv/omv/modules/ulab/code/numpy/carray/carray.c new file mode 100644 index 00000000..44e9ab12 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/carray/carray.c @@ -0,0 +1,830 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2021-2022 Zoltán Vörös +*/ + +#include +#include +#include +#include "py/obj.h" +#include "py/objint.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/misc.h" + +#include "../../ulab.h" +#include "../../ndarray.h" +#include "../../ulab_tools.h" +#include "carray.h" + +#if ULAB_SUPPORTS_COMPLEX + +//| import ulab.numpy + +//| def real(val): +//| """ +//| Return the real part of the complex argument, which can be +//| either an ndarray, or a scalar.""" +//| ... +//| + +mp_obj_t carray_real(mp_obj_t _source) { + if(mp_obj_is_type(_source, &ulab_ndarray_type)) { + ndarray_obj_t *source = MP_OBJ_TO_PTR(_source); + if(source->dtype != NDARRAY_COMPLEX) { + ndarray_obj_t *target = ndarray_new_dense_ndarray(source->ndim, source->shape, source->dtype); + ndarray_copy_array(source, target, 0); + return MP_OBJ_FROM_PTR(target); + } else { // the input is most definitely a complex array + ndarray_obj_t *target = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_FLOAT); + ndarray_copy_array(source, target, 0); + return MP_OBJ_FROM_PTR(target); + } + } else { + mp_raise_NotImplementedError(translate("function is implemented for ndarrays only")); + } + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_1(carray_real_obj, carray_real); + +//| def imag(val): +//| """ +//| Return the imaginary part of the complex argument, which can be +//| either an ndarray, or a scalar.""" +//| ... +//| + +mp_obj_t carray_imag(mp_obj_t _source) { + if(mp_obj_is_type(_source, &ulab_ndarray_type)) { + ndarray_obj_t *source = MP_OBJ_TO_PTR(_source); + if(source->dtype != NDARRAY_COMPLEX) { // if not complex, then the imaginary part is zero + ndarray_obj_t *target = ndarray_new_dense_ndarray(source->ndim, source->shape, source->dtype); + return MP_OBJ_FROM_PTR(target); + } else { // the input is most definitely a complex array + ndarray_obj_t *target = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_FLOAT); + ndarray_copy_array(source, target, source->itemsize / 2); + return MP_OBJ_FROM_PTR(target); + } + } else { + mp_raise_NotImplementedError(translate("function is implemented for ndarrays only")); + } + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_1(carray_imag_obj, carray_imag); + +#if ULAB_NUMPY_HAS_CONJUGATE + +//| def conjugate(val): +//| """ +//| Return the conjugate of the complex argument, which can be +//| either an ndarray, or a scalar.""" +//| ... +//| +mp_obj_t carray_conjugate(mp_obj_t _source) { + if(mp_obj_is_type(_source, &ulab_ndarray_type)) { + ndarray_obj_t *source = MP_OBJ_TO_PTR(_source); + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, source->dtype); + ndarray_copy_array(source, ndarray, 0); + if(source->dtype == NDARRAY_COMPLEX) { + mp_float_t *array = (mp_float_t *)ndarray->array; + array++; + for(size_t i = 0; i < ndarray->len; i++) { + *array *= MICROPY_FLOAT_CONST(-1.0); + array += 2; + } + } + return MP_OBJ_FROM_PTR(ndarray); + } else { + if(mp_obj_is_type(_source, &mp_type_complex)) { + mp_float_t real, imag; + mp_obj_get_complex(_source, &real, &imag); + imag = imag * MICROPY_FLOAT_CONST(-1.0); + return mp_obj_new_complex(real, imag); + } else if(mp_obj_is_int(_source) || mp_obj_is_float(_source)) { + return _source; + } else { + mp_raise_TypeError(translate("input must be an ndarray, or a scalar")); + } + } + // this should never happen + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_1(carray_conjugate_obj, carray_conjugate); +#endif + +#if ULAB_NUMPY_HAS_SORT_COMPLEX +//| def sort_complex(a: ulab.numpy.ndarray) -> ulab.numpy.ndarray: +//| """ +//| .. param: a +//| a one-dimensional ndarray +//| +//| Sort a complex array using the real part first, then the imaginary part. +//| Always returns a sorted complex array, even if the input was real.""" +//| ... +//| + +static void carray_sort_complex_(mp_float_t *array, size_t len) { + // array is assumed to be a floating vector containing the real and imaginary parts + // of a complex array at alternating positions as + // array[0] = real[0] + // array[1] = imag[0] + // array[2] = real[1] + // array[3] = imag[1] + + mp_float_t real, imag; + size_t c, q = len, p, r = len >> 1; + for (;;) { + if (r > 0) { + r--; + real = array[2 * r]; + imag = array[2 * r + 1]; + } else { + q--; + if(q == 0) { + break; + } + real = array[2 * q]; + imag = array[2 * q + 1]; + array[2 * q] = array[0]; + array[2 * q + 1] = array[1]; + } + p = r; + c = r + r + 1; + while (c < q) { + if(c + 1 < q) { + if((array[2 * (c+1)] > array[2 * c]) || + ((array[2 * (c+1)] == array[2 * c]) && (array[2 * (c+1) + 1] > array[2 * c + 1]))) { + c++; + } + } + if((array[2 * c] > real) || + ((array[2 * c] == real) && (array[2 * c + 1] > imag))) { + array[2 * p] = array[2 * c]; // real part + array[2 * p + 1] = array[2 * c + 1]; // imag part + p = c; + c = p + p + 1; + } else { + break; + } + } + array[2 * p] = real; + array[2 * p + 1] = imag; + } +} + +mp_obj_t carray_sort_complex(mp_obj_t _source) { + if(!mp_obj_is_type(_source, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("input must be a 1D ndarray")); + } + ndarray_obj_t *source = MP_OBJ_TO_PTR(_source); + if(source->ndim != 1) { + mp_raise_TypeError(translate("input must be a 1D ndarray")); + } + + ndarray_obj_t *ndarray = ndarray_copy_view_convert_type(source, NDARRAY_COMPLEX); + + if(ndarray->len != 0) { + mp_float_t *array = (mp_float_t *)ndarray->array; + carray_sort_complex_(array, ndarray->len); + } + + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_1(carray_sort_complex_obj, carray_sort_complex); +#endif + +//| def abs(a: ulab.numpy.ndarray) -> ulab.numpy.ndarray: +//| """ +//| .. param: a +//| a one-dimensional ndarray +//| +//| Return the absolute value of complex ndarray.""" +//| ... +//| + +mp_obj_t carray_abs(ndarray_obj_t *source, ndarray_obj_t *target) { + // calculates the absolute value of a complex array and returns a dense array + uint8_t *sarray = (uint8_t *)source->array; + mp_float_t *tarray = (mp_float_t *)target->array; + uint8_t itemsize = mp_binary_get_size('@', NDARRAY_FLOAT, NULL); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_float_t rvalue = *(mp_float_t *)sarray; + mp_float_t ivalue = *(mp_float_t *)(sarray + itemsize); + *tarray++ = MICROPY_FLOAT_C_FUN(sqrt)(rvalue * rvalue + ivalue * ivalue); + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif + return MP_OBJ_FROM_PTR(target); +} + +static void carray_copy_part(uint8_t *tarray, uint8_t *sarray, size_t *shape, int32_t *strides) { + // copies the real or imaginary part of an array + // into the respective part of a dense complex array + uint8_t sz = sizeof(mp_float_t); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + memcpy(tarray, sarray, sz); + tarray += 2 * sz; + sarray += strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= strides[ULAB_MAX_DIMS - 1] * shape[ULAB_MAX_DIMS-1]; + sarray += strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + sarray -= strides[ULAB_MAX_DIMS - 2] * shape[ULAB_MAX_DIMS-2]; + sarray += strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + sarray -= strides[ULAB_MAX_DIMS - 3] * shape[ULAB_MAX_DIMS-3]; + sarray += strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ +} + +mp_obj_t carray_binary_equal_not_equal(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides, mp_binary_op_t op) { + + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_UINT8); + results->boolean = 1; + uint8_t *array = (uint8_t *)results->array; + + if(op == MP_BINARY_OP_NOT_EQUAL) { + memset(array, 1, results->len); + } + + if((lhs->dtype == NDARRAY_COMPLEX) && (rhs->dtype == NDARRAY_COMPLEX)) { + mp_float_t *larray = (mp_float_t *)lhs->array; + mp_float_t *rarray = (mp_float_t *)rhs->array; + + ulab_rescale_float_strides(lstrides); + ulab_rescale_float_strides(rstrides); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + if((larray[0] == rarray[0]) && (larray[1] == rarray[1])) { + *array ^= 0x01; + } + array++; + larray += lstrides[ULAB_MAX_DIMS - 1]; + rarray += rstrides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < results->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + larray -= lstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + larray += lstrides[ULAB_MAX_DIMS - 2]; + rarray -= rstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + rarray += rstrides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < results->shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + larray -= lstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + larray += lstrides[ULAB_MAX_DIMS - 3]; + rarray -= rstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + rarray += rstrides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < results->shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + larray -= lstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + larray += lstrides[ULAB_MAX_DIMS - 4]; + rarray -= rstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + rarray += rstrides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < results->shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ + } else { // only one of the operands is complex + mp_float_t *larray = (mp_float_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + // align the complex array to the left + uint8_t rdtype = rhs->dtype; + int32_t *lstrides_ = lstrides; + int32_t *rstrides_ = rstrides; + + if(rhs->dtype == NDARRAY_COMPLEX) { + larray = (mp_float_t *)rhs->array; + rarray = (uint8_t *)lhs->array; + lstrides_ = rstrides; + rstrides_ = lstrides; + rdtype = lhs->dtype; + } + + ulab_rescale_float_strides(lstrides_); + + if(rdtype == NDARRAY_UINT8) { + BINARY_LOOP_COMPLEX_EQUAL(results, array, uint8_t, larray, lstrides_, rarray, rstrides_); + } else if(rdtype == NDARRAY_INT8) { + BINARY_LOOP_COMPLEX_EQUAL(results, array, int8_t, larray, lstrides_, rarray, rstrides_); + } else if(rdtype == NDARRAY_UINT16) { + BINARY_LOOP_COMPLEX_EQUAL(results, array, uint16_t, larray, lstrides_, rarray, rstrides_); + } else if(rdtype == NDARRAY_INT16) { + BINARY_LOOP_COMPLEX_EQUAL(results, array, int16_t, larray, lstrides_, rarray, rstrides_); + } else if(rdtype == NDARRAY_FLOAT) { + BINARY_LOOP_COMPLEX_EQUAL(results, array, mp_float_t, larray, lstrides_, rarray, rstrides_); + } + } + return MP_OBJ_FROM_PTR(results); +} + +mp_obj_t carray_binary_add(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_COMPLEX); + mp_float_t *resarray = (mp_float_t *)results->array; + + if((lhs->dtype == NDARRAY_COMPLEX) && (rhs->dtype == NDARRAY_COMPLEX)) { + mp_float_t *larray = (mp_float_t *)lhs->array; + mp_float_t *rarray = (mp_float_t *)rhs->array; + + ulab_rescale_float_strides(lstrides); + ulab_rescale_float_strides(rstrides); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + // real part + *resarray++ = larray[0] + rarray[0]; + // imaginary part + *resarray++ = larray[1] + rarray[1]; + larray += lstrides[ULAB_MAX_DIMS - 1]; + rarray += rstrides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < results->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + larray -= lstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + larray += lstrides[ULAB_MAX_DIMS - 2]; + rarray -= rstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + rarray += rstrides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < results->shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + larray -= lstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + larray += lstrides[ULAB_MAX_DIMS - 3]; + rarray -= rstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + rarray += rstrides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < results->shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + larray -= lstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + larray += lstrides[ULAB_MAX_DIMS - 4]; + rarray -= rstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + rarray += rstrides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < results->shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ + } else { // only one of the operands is complex + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + // align the complex array to the left + uint8_t rdtype = rhs->dtype; + int32_t *lstrides_ = lstrides; + int32_t *rstrides_ = rstrides; + + if(rhs->dtype == NDARRAY_COMPLEX) { + larray = (uint8_t *)rhs->array; + rarray = (uint8_t *)lhs->array; + lstrides_ = rstrides; + rstrides_ = lstrides; + rdtype = lhs->dtype; + } + + if(rdtype == NDARRAY_UINT8) { + BINARY_LOOP_COMPLEX(results, resarray, uint8_t, larray, lstrides_, rarray, rstrides_, +); + } else if(rdtype == NDARRAY_INT8) { + BINARY_LOOP_COMPLEX(results, resarray, int8_t, larray, lstrides_, rarray, rstrides_, +); + } else if(rdtype == NDARRAY_UINT16) { + BINARY_LOOP_COMPLEX(results, resarray, uint16_t, larray, lstrides_, rarray, rstrides_, +); + } else if(rdtype == NDARRAY_INT16) { + BINARY_LOOP_COMPLEX(results, resarray, int16_t, larray, lstrides_, rarray, rstrides_, +); + } else if(rdtype == NDARRAY_FLOAT) { + BINARY_LOOP_COMPLEX(results, resarray, mp_float_t, larray, lstrides_, rarray, rstrides_, +); + } + + // simply copy the imaginary part + uint8_t *tarray = (uint8_t *)results->array; + tarray += sizeof(mp_float_t); + + if(lhs->dtype == NDARRAY_COMPLEX) { + rarray = (uint8_t *)lhs->array; + rstrides = lstrides; + } else { + rarray = (uint8_t *)rhs->array; + } + rarray += sizeof(mp_float_t); + carray_copy_part(tarray, rarray, results->shape, rstrides); + } + return MP_OBJ_FROM_PTR(results); +} + +static void carray_binary_multiply_(ndarray_obj_t *results, mp_float_t *resarray, uint8_t *larray, uint8_t *rarray, + int32_t *lstrides, int32_t *rstrides, uint8_t rdtype) { + + if(rdtype == NDARRAY_UINT8) { + BINARY_LOOP_COMPLEX(results, resarray, uint8_t, larray, lstrides, rarray, rstrides, *); + } else if(rdtype == NDARRAY_INT8) { + BINARY_LOOP_COMPLEX(results, resarray, int8_t, larray, lstrides, rarray, rstrides, *); + } else if(rdtype == NDARRAY_UINT16) { + BINARY_LOOP_COMPLEX(results, resarray, uint16_t, larray, lstrides, rarray, rstrides, *); + } else if(rdtype == NDARRAY_INT16) { + BINARY_LOOP_COMPLEX(results, resarray, int16_t, larray, lstrides, rarray, rstrides, *); + } else if(rdtype == NDARRAY_FLOAT) { + BINARY_LOOP_COMPLEX(results, resarray, mp_float_t, larray, lstrides, rarray, rstrides, *); + } +} + +mp_obj_t carray_binary_multiply(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_COMPLEX); + mp_float_t *resarray = (mp_float_t *)results->array; + + if((lhs->dtype == NDARRAY_COMPLEX) && (rhs->dtype == NDARRAY_COMPLEX)) { + mp_float_t *larray = (mp_float_t *)lhs->array; + mp_float_t *rarray = (mp_float_t *)rhs->array; + + ulab_rescale_float_strides(lstrides); + ulab_rescale_float_strides(rstrides); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + // real part + *resarray++ = larray[0] * rarray[0] - larray[1] * rarray[1]; + // imaginary part + *resarray++ = larray[0] * rarray[1] + larray[1] * rarray[0]; + larray += lstrides[ULAB_MAX_DIMS - 1]; + rarray += rstrides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < results->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + larray -= lstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + larray += lstrides[ULAB_MAX_DIMS - 2]; + rarray -= rstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + rarray += rstrides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < results->shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + larray -= lstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + larray += lstrides[ULAB_MAX_DIMS - 3]; + rarray -= rstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + rarray += rstrides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < results->shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + larray -= lstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + larray += lstrides[ULAB_MAX_DIMS - 4]; + rarray -= rstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + rarray += rstrides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < results->shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ + } else { // only one of the operands is complex + + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + uint8_t *lo = larray, *ro = rarray; + int32_t *left_strides = lstrides; + int32_t *right_strides = rstrides; + uint8_t rdtype = rhs->dtype; + + // align the complex array to the left + if(rhs->dtype == NDARRAY_COMPLEX) { + lo = (uint8_t *)rhs->array; + ro = (uint8_t *)lhs->array; + rdtype = lhs->dtype; + left_strides = rstrides; + right_strides = lstrides; + } + + larray = lo; + rarray = ro; + // real part + carray_binary_multiply_(results, resarray, larray, rarray, left_strides, right_strides, rdtype); + + larray = lo + sizeof(mp_float_t); + rarray = ro; + resarray = (mp_float_t *)results->array; + resarray++; + // imaginary part + carray_binary_multiply_(results, resarray, larray, rarray, left_strides, right_strides, rdtype); + } + return MP_OBJ_FROM_PTR(results); +} + +mp_obj_t carray_binary_subtract(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_COMPLEX); + mp_float_t *resarray = (mp_float_t *)results->array; + + if((lhs->dtype == NDARRAY_COMPLEX) && (rhs->dtype == NDARRAY_COMPLEX)) { + mp_float_t *larray = (mp_float_t *)lhs->array; + mp_float_t *rarray = (mp_float_t *)rhs->array; + + ulab_rescale_float_strides(lstrides); + ulab_rescale_float_strides(rstrides); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + // real part + *resarray++ = larray[0] - rarray[0]; + // imaginary part + *resarray++ = larray[1] - rarray[1]; + larray += lstrides[ULAB_MAX_DIMS - 1]; + rarray += rstrides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < results->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + larray -= lstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + larray += lstrides[ULAB_MAX_DIMS - 2]; + rarray -= rstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + rarray += rstrides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < results->shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + larray -= lstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + larray += lstrides[ULAB_MAX_DIMS - 3]; + rarray -= rstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + rarray += rstrides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < results->shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + larray -= lstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + larray += lstrides[ULAB_MAX_DIMS - 4]; + rarray -= rstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + rarray += rstrides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < results->shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ + } else { + uint8_t *larray = (uint8_t *)lhs->array; + if(lhs->dtype == NDARRAY_COMPLEX) { + uint8_t *rarray = (uint8_t *)rhs->array; + if(rhs->dtype == NDARRAY_UINT8) { + BINARY_LOOP_COMPLEX(results, resarray, uint8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT8) { + BINARY_LOOP_COMPLEX(results, resarray, int8_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_UINT16) { + BINARY_LOOP_COMPLEX(results, resarray, uint16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_INT16) { + BINARY_LOOP_COMPLEX(results, resarray, int16_t, larray, lstrides, rarray, rstrides, -); + } else if(rhs->dtype == NDARRAY_FLOAT) { + BINARY_LOOP_COMPLEX(results, resarray, mp_float_t, larray, lstrides, rarray, rstrides, -); + } + // copy the imaginary part + uint8_t *tarray = (uint8_t *)results->array; + tarray += sizeof(mp_float_t); + + larray = (uint8_t *)lhs->array; + larray += sizeof(mp_float_t); + + carray_copy_part(tarray, larray, results->shape, lstrides); + } else if(rhs->dtype == NDARRAY_COMPLEX) { + mp_float_t *rarray = (mp_float_t *)rhs->array; + ulab_rescale_float_strides(rstrides); + + if(lhs->dtype == NDARRAY_UINT8) { + BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT(results, resarray, uint8_t, larray, lstrides, rarray, rstrides); + } else if(lhs->dtype == NDARRAY_INT8) { + BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT(results, resarray, int8_t, larray, lstrides, rarray, rstrides); + } else if(lhs->dtype == NDARRAY_UINT16) { + BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT(results, resarray, uint16_t, larray, lstrides, rarray, rstrides); + } else if(lhs->dtype == NDARRAY_INT16) { + BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT(results, resarray, int16_t, larray, lstrides, rarray, rstrides); + } else if(lhs->dtype == NDARRAY_FLOAT) { + BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT(results, resarray, mp_float_t, larray, lstrides, rarray, rstrides); + } + } + } + + return MP_OBJ_FROM_PTR(results); +} + +static void carray_binary_left_divide_(ndarray_obj_t *results, mp_float_t *resarray, uint8_t *larray, uint8_t *rarray, + int32_t *lstrides, int32_t *rstrides, uint8_t rdtype) { + + if(rdtype == NDARRAY_UINT8) { + BINARY_LOOP_COMPLEX(results, resarray, uint8_t, larray, lstrides, rarray, rstrides, /); + } else if(rdtype == NDARRAY_INT8) { + BINARY_LOOP_COMPLEX(results, resarray, int8_t, larray, lstrides, rarray, rstrides, /); + } else if(rdtype == NDARRAY_UINT16) { + BINARY_LOOP_COMPLEX(results, resarray, uint16_t, larray, lstrides, rarray, rstrides, /); + } else if(rdtype == NDARRAY_INT16) { + BINARY_LOOP_COMPLEX(results, resarray, int16_t, larray, lstrides, rarray, rstrides, /); + } else if(rdtype == NDARRAY_FLOAT) { + BINARY_LOOP_COMPLEX(results, resarray, mp_float_t, larray, lstrides, rarray, rstrides, /); + } +} + +mp_obj_t carray_binary_divide(ndarray_obj_t *lhs, ndarray_obj_t *rhs, + uint8_t ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_COMPLEX); + mp_float_t *resarray = (mp_float_t *)results->array; + + if((lhs->dtype == NDARRAY_COMPLEX) && (rhs->dtype == NDARRAY_COMPLEX)) { + mp_float_t *larray = (mp_float_t *)lhs->array; + mp_float_t *rarray = (mp_float_t *)rhs->array; + + ulab_rescale_float_strides(lstrides); + ulab_rescale_float_strides(rstrides); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + // (a + bi) / (c + di) = + // (ac + bd) / (c^2 + d^2) + i (bc - ad) / (c^2 + d^2) + // denominator + mp_float_t denom = rarray[0] * rarray[0] + rarray[1] * rarray[1]; + + // real part + *resarray++ = (larray[0] * rarray[0] + larray[1] * rarray[1]) / denom; + // imaginary part + *resarray++ = (larray[1] * rarray[0] - larray[0] * rarray[1]) / denom; + larray += lstrides[ULAB_MAX_DIMS - 1]; + rarray += rstrides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < results->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + larray -= lstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + larray += lstrides[ULAB_MAX_DIMS - 2]; + rarray -= rstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + rarray += rstrides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < results->shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + larray -= lstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + larray += lstrides[ULAB_MAX_DIMS - 3]; + rarray -= rstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + rarray += rstrides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < results->shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + larray -= lstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + larray += lstrides[ULAB_MAX_DIMS - 4]; + rarray -= rstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + rarray += rstrides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < results->shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ + } else { + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + if(lhs->dtype == NDARRAY_COMPLEX) { + // real part + carray_binary_left_divide_(results, resarray, larray, rarray, lstrides, rstrides, rhs->dtype); + // imaginary part + resarray = (mp_float_t *)results->array; + resarray++; + larray = (uint8_t *)lhs->array; + larray += sizeof(mp_float_t); + rarray = (uint8_t *)rhs->array; + carray_binary_left_divide_(results, resarray, larray, rarray, lstrides, rstrides, rhs->dtype); + } else { + if(lhs->dtype == NDARRAY_UINT8) { + BINARY_LOOP_COMPLEX_RIGHT_DIVIDE(results, resarray, uint8_t, larray, lstrides, rarray, rstrides); + } else if(lhs->dtype == NDARRAY_INT8) { + BINARY_LOOP_COMPLEX_RIGHT_DIVIDE(results, resarray, int8_t, larray, lstrides, rarray, rstrides); + } else if(lhs->dtype == NDARRAY_UINT16) { + BINARY_LOOP_COMPLEX_RIGHT_DIVIDE(results, resarray, uint16_t, larray, lstrides, rarray, rstrides); + } else if(lhs->dtype == NDARRAY_INT16) { + BINARY_LOOP_COMPLEX_RIGHT_DIVIDE(results, resarray, int16_t, larray, lstrides, rarray, rstrides); + } else if(lhs->dtype == NDARRAY_FLOAT) { + BINARY_LOOP_COMPLEX_RIGHT_DIVIDE(results, resarray, mp_float_t, larray, lstrides, rarray, rstrides); + } + } + } + + return MP_OBJ_FROM_PTR(results); +} + +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/carray/carray.h b/components/3rd_party/omv/omv/modules/ulab/code/numpy/carray/carray.h new file mode 100644 index 00000000..8ca5de2d --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/carray/carray.h @@ -0,0 +1,237 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2021-2022 Zoltán Vörös +*/ + +#ifndef _CARRAY_ +#define _CARRAY_ + +MP_DECLARE_CONST_FUN_OBJ_1(carray_real_obj); +MP_DECLARE_CONST_FUN_OBJ_1(carray_imag_obj); +MP_DECLARE_CONST_FUN_OBJ_1(carray_conjugate_obj); +MP_DECLARE_CONST_FUN_OBJ_1(carray_sort_complex_obj); + + +mp_obj_t carray_imag(mp_obj_t ); +mp_obj_t carray_real(mp_obj_t ); + +mp_obj_t carray_abs(ndarray_obj_t *, ndarray_obj_t *); +mp_obj_t carray_binary_add(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *); +mp_obj_t carray_binary_multiply(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *); +mp_obj_t carray_binary_subtract(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *); +mp_obj_t carray_binary_divide(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *); +mp_obj_t carray_binary_equal_not_equal(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *, mp_binary_op_t ); + +#define BINARY_LOOP_COMPLEX1(results, resarray, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t l = 0;\ + do {\ + *(resarray) = *((mp_float_t *)(larray)) OPERATOR *((type_right *)(rarray));\ + (resarray) += 2;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + +#define BINARY_LOOP_COMPLEX2(results, resarray, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t k = 0;\ + do {\ + BINARY_LOOP_COMPLEX1((results), (resarray), type_right, (larray), (lstrides), (rarray), (rstrides), OPERATOR);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + +#define BINARY_LOOP_COMPLEX3(results, resarray, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t j = 0;\ + do {\ + BINARY_LOOP_COMPLEX2((results), (resarray), type_right, (larray), (lstrides), (rarray), (rstrides), OPERATOR);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + +#define BINARY_LOOP_COMPLEX4(results, resarray, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t i = 0;\ + do {\ + BINARY_LOOP_COMPLEX3((results), (resarray), type_right, (larray), (lstrides), (rarray), (rstrides), OPERATOR);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ + +#define BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT1(results, resarray, type_left, larray, lstrides, rarray, rstrides)\ + size_t l = 0;\ + do {\ + *(resarray)++ = *((type_left *)(larray)) - (rarray)[0];\ + *(resarray)++ = -(rarray)[1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + +#define BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT2(results, resarray, type_left, larray, lstrides, rarray, rstrides)\ + size_t k = 0;\ + do {\ + BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT1((results), (resarray), type_left, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + +#define BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT3(results, resarray, type_left, larray, lstrides, rarray, rstrides)\ + size_t j = 0;\ + do {\ + BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT2((results), (resarray), type_left, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + +#define BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT4(results, resarray, type_left, larray, lstrides, rarray, rstrides)\ + size_t i = 0;\ + do {\ + BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT3((results), (resarray), type_left, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ + +#define BINARY_LOOP_COMPLEX_RIGHT_DIVIDE1(results, resarray, type_left, larray, lstrides, rarray, rstrides)\ + size_t l = 0;\ + do {\ + mp_float_t *c = (mp_float_t *)(rarray);\ + mp_float_t denom = c[0] * c[0] + c[1] * c[1];\ + mp_float_t a = *((type_left *)(larray)) / denom;\ + *(resarray)++ = a * c[0];\ + *(resarray)++ = -a * c[1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + +#define BINARY_LOOP_COMPLEX_RIGHT_DIVIDE2(results, resarray, type_left, larray, lstrides, rarray, rstrides)\ + size_t k = 0;\ + do {\ + BINARY_LOOP_COMPLEX_RIGHT_DIVIDE1((results), (resarray), type_left, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + +#define BINARY_LOOP_COMPLEX_RIGHT_DIVIDE3(results, resarray, type_left, larray, lstrides, rarray, rstrides)\ + size_t j = 0;\ + do {\ + BINARY_LOOP_COMPLEX_RIGHT_DIVIDE2((results), (resarray), type_left, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + +#define BINARY_LOOP_COMPLEX_RIGHT_DIVIDE4(results, resarray, type_left, larray, lstrides, rarray, rstrides)\ + size_t i = 0;\ + do {\ + BINARY_LOOP_COMPLEX_RIGHT_DIVIDE3((results), (resarray), type_left, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ + + +#define BINARY_LOOP_COMPLEX_EQUAL1(results, array, type_right, larray, lstrides, rarray, rstrides)\ + size_t l = 0;\ + do {\ + if((*(larray) == *((type_right *)(rarray))) && ((larray)[1] == MICROPY_FLOAT_CONST(0.0))) {\ + *(array) ^= 0x01;\ + }\ + (array)++;\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\ + +#define BINARY_LOOP_COMPLEX_EQUAL2(results, array, type_right, larray, lstrides, rarray, rstrides)\ + size_t k = 0;\ + do {\ + BINARY_LOOP_COMPLEX_EQUAL1((results), (array), type_right, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\ + +#define BINARY_LOOP_COMPLEX_EQUAL3(results, array, type_right, larray, lstrides, rarray, rstrides)\ + size_t j = 0;\ + do {\ + BINARY_LOOP_COMPLEX_EQUAL2((results), (array), type_right, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\ + +#define BINARY_LOOP_COMPLEX_EQUAL4(results, array, type_right, larray, lstrides, rarray, rstrides)\ + size_t i = 0;\ + do {\ + BINARY_LOOP_COMPLEX_EQUAL3((results), (array), type_right, (larray), (lstrides), (rarray), (rstrides));\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (results)->shape[ULAB_MAX_DIMS - 4]);\ + +#if ULAB_MAX_DIMS == 1 +#define BINARY_LOOP_COMPLEX BINARY_LOOP_COMPLEX1 +#define BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT1 +#define BINARY_LOOP_COMPLEX_RIGHT_DIVIDE BINARY_LOOP_COMPLEX_RIGHT_DIVIDE1 +#define BINARY_LOOP_COMPLEX_EQUAL BINARY_LOOP_COMPLEX_EQUAL1 +#endif /* ULAB_MAX_DIMS == 1 */ + +#if ULAB_MAX_DIMS == 2 +#define BINARY_LOOP_COMPLEX BINARY_LOOP_COMPLEX2 +#define BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT2 +#define BINARY_LOOP_COMPLEX_RIGHT_DIVIDE BINARY_LOOP_COMPLEX_RIGHT_DIVIDE2 +#define BINARY_LOOP_COMPLEX_EQUAL BINARY_LOOP_COMPLEX_EQUAL2 +#endif /* ULAB_MAX_DIMS == 2 */ + +#if ULAB_MAX_DIMS == 3 +#define BINARY_LOOP_COMPLEX BINARY_LOOP_COMPLEX3 +#define BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT3 +#define BINARY_LOOP_COMPLEX_RIGHT_DIVIDE BINARY_LOOP_COMPLEX_RIGHT_DIVIDE3 +#define BINARY_LOOP_COMPLEX_EQUAL BINARY_LOOP_COMPLEX_EQUAL3 +#endif /* ULAB_MAX_DIMS == 3 */ + +#if ULAB_MAX_DIMS == 4 +#define BINARY_LOOP_COMPLEX BINARY_LOOP_COMPLEX4 +#define BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT BINARY_LOOP_COMPLEX_REVERSED_SUBTRACT4 +#define BINARY_LOOP_COMPLEX_RIGHT_DIVIDE BINARY_LOOP_COMPLEX_RIGHT_DIVIDE4 +#define BINARY_LOOP_COMPLEX_EQUAL BINARY_LOOP_COMPLEX_EQUAL4 +#endif /* ULAB_MAX_DIMS == 4 */ + +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/carray/carray_tools.c b/components/3rd_party/omv/omv/modules/ulab/code/numpy/carray/carray_tools.c new file mode 100644 index 00000000..7b623d34 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/carray/carray_tools.c @@ -0,0 +1,28 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Zoltán Vörös +*/ + +#include +#include +#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" + +#include "../../ulab.h" +#include "../../ndarray.h" + +#if ULAB_SUPPORTS_COMPLEX + +void raise_complex_NotImplementedError(void) { + mp_raise_NotImplementedError(translate("not implemented for complex dtype")); +} + +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/carray/carray_tools.h b/components/3rd_party/omv/omv/modules/ulab/code/numpy/carray/carray_tools.h new file mode 100644 index 00000000..3ac79b5f --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/carray/carray_tools.h @@ -0,0 +1,25 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Zoltán Vörös +*/ + +#ifndef _CARRAY_TOOLS_ +#define _CARRAY_TOOLS_ + +void raise_complex_NotImplementedError(void); + +#if ULAB_SUPPORTS_COMPLEX + #define NOT_IMPLEMENTED_FOR_COMPLEX() raise_complex_NotImplementedError(); + #define COMPLEX_DTYPE_NOT_IMPLEMENTED(dtype) if((dtype) == NDARRAY_COMPLEX) raise_complex_NotImplementedError(); +#else + #define NOT_IMPLEMENTED_FOR_COMPLEX() // do nothing + #define COMPLEX_DTYPE_NOT_IMPLEMENTED(dtype) // do nothing +#endif + +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/compare.c b/components/3rd_party/omv/omv/modules/ulab/code/numpy/compare.c new file mode 100644 index 00000000..54647d6f --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/compare.c @@ -0,0 +1,560 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös + * 2020 Jeff Epler for Adafruit Industries +*/ + +#include +#include +#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" + +#include "../ulab.h" +#include "../ndarray_operators.h" +#include "../ulab_tools.h" +#include "carray/carray_tools.h" +#include "compare.h" + +static mp_obj_t compare_function(mp_obj_t x1, mp_obj_t x2, uint8_t op) { + ndarray_obj_t *lhs = ndarray_from_mp_obj(x1, 0); + ndarray_obj_t *rhs = ndarray_from_mp_obj(x2, 0); + #if ULAB_SUPPORTS_COMPLEX + if((lhs->dtype == NDARRAY_COMPLEX) || (rhs->dtype == NDARRAY_COMPLEX)) { + NOT_IMPLEMENTED_FOR_COMPLEX() + } + #endif + uint8_t ndim = 0; + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + int32_t *lstrides = m_new(int32_t, ULAB_MAX_DIMS); + int32_t *rstrides = m_new(int32_t, ULAB_MAX_DIMS); + if(!ndarray_can_broadcast(lhs, rhs, &ndim, shape, lstrides, rstrides)) { + mp_raise_ValueError(translate("operands could not be broadcast together")); + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(int32_t, lstrides, ULAB_MAX_DIMS); + m_del(int32_t, rstrides, ULAB_MAX_DIMS); + } + + uint8_t *larray = (uint8_t *)lhs->array; + uint8_t *rarray = (uint8_t *)rhs->array; + + if(op == COMPARE_EQUAL) { + return ndarray_binary_equality(lhs, rhs, ndim, shape, lstrides, rstrides, MP_BINARY_OP_EQUAL); + } else if(op == COMPARE_NOT_EQUAL) { + return ndarray_binary_equality(lhs, rhs, ndim, shape, lstrides, rstrides, MP_BINARY_OP_NOT_EQUAL); + } + // These are the upcasting rules + // float always becomes float + // operation on identical types preserves type + // uint8 + int8 => int16 + // uint8 + int16 => int16 + // uint8 + uint16 => uint16 + // int8 + int16 => int16 + // int8 + uint16 => uint16 + // uint16 + int16 => float + // The parameters of RUN_COMPARE_LOOP are + // typecode of result, type_out, type_left, type_right, lhs operand, rhs operand, operator + if(lhs->dtype == NDARRAY_UINT8) { + if(rhs->dtype == NDARRAY_UINT8) { + RUN_COMPARE_LOOP(NDARRAY_UINT8, uint8_t, uint8_t, uint8_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_INT8) { + RUN_COMPARE_LOOP(NDARRAY_INT16, int16_t, uint8_t, int8_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_UINT16) { + RUN_COMPARE_LOOP(NDARRAY_UINT16, uint16_t, uint8_t, uint16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_INT16) { + RUN_COMPARE_LOOP(NDARRAY_INT16, int16_t, uint8_t, int16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_FLOAT) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, uint8_t, mp_float_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } + } else if(lhs->dtype == NDARRAY_INT8) { + if(rhs->dtype == NDARRAY_UINT8) { + RUN_COMPARE_LOOP(NDARRAY_INT16, int16_t, int8_t, uint8_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_INT8) { + RUN_COMPARE_LOOP(NDARRAY_INT8, int8_t, int8_t, int8_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_UINT16) { + RUN_COMPARE_LOOP(NDARRAY_INT16, int16_t, int8_t, uint16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_INT16) { + RUN_COMPARE_LOOP(NDARRAY_INT16, int16_t, int8_t, int16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_FLOAT) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, int8_t, mp_float_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } + } else if(lhs->dtype == NDARRAY_UINT16) { + if(rhs->dtype == NDARRAY_UINT8) { + RUN_COMPARE_LOOP(NDARRAY_UINT16, uint16_t, uint16_t, uint8_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_INT8) { + RUN_COMPARE_LOOP(NDARRAY_UINT16, uint16_t, uint16_t, int8_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_UINT16) { + RUN_COMPARE_LOOP(NDARRAY_UINT16, uint16_t, uint16_t, uint16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_INT16) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, uint16_t, int16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_FLOAT) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, uint16_t, mp_float_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } + } else if(lhs->dtype == NDARRAY_INT16) { + if(rhs->dtype == NDARRAY_UINT8) { + RUN_COMPARE_LOOP(NDARRAY_INT16, int16_t, int16_t, uint8_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_INT8) { + RUN_COMPARE_LOOP(NDARRAY_INT16, int16_t, int16_t, int8_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_UINT16) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, int16_t, uint16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_INT16) { + RUN_COMPARE_LOOP(NDARRAY_INT16, int16_t, int16_t, int16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_FLOAT) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, int16_t, mp_float_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } + } else if(lhs->dtype == NDARRAY_FLOAT) { + if(rhs->dtype == NDARRAY_UINT8) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, mp_float_t, uint8_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_INT8) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, mp_float_t, int8_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_UINT16) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, mp_float_t, uint16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_INT16) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, mp_float_t, int16_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } else if(rhs->dtype == NDARRAY_FLOAT) { + RUN_COMPARE_LOOP(NDARRAY_FLOAT, mp_float_t, mp_float_t, mp_float_t, larray, lstrides, rarray, rstrides, ndim, shape, op); + } + } + return mp_const_none; // we should never reach this point +} + +#if ULAB_NUMPY_HAS_EQUAL | ULAB_NUMPY_HAS_NOTEQUAL +static mp_obj_t compare_equal_helper(mp_obj_t x1, mp_obj_t x2, uint8_t comptype) { + // scalar comparisons should return a single object of mp_obj_t type + mp_obj_t result = compare_function(x1, x2, comptype); + if((mp_obj_is_int(x1) || mp_obj_is_float(x1)) && (mp_obj_is_int(x2) || mp_obj_is_float(x2))) { + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(result, &iter_buf); + mp_obj_t item = mp_iternext(iterable); + return item; + } + return result; +} +#endif + +#if ULAB_NUMPY_HAS_CLIP + +mp_obj_t compare_clip(mp_obj_t x1, mp_obj_t x2, mp_obj_t x3) { + // Note: this function could be made faster by implementing a single-loop comparison in + // RUN_COMPARE_LOOP. However, that would add around 2 kB of compile size, while we + // would not gain a factor of two in speed, since the two comparisons should still be + // evaluated. In contrast, calling the function twice adds only 140 bytes to the firmware + if(mp_obj_is_int(x1) || mp_obj_is_float(x1)) { + mp_float_t v1 = mp_obj_get_float(x1); + mp_float_t v2 = mp_obj_get_float(x2); + mp_float_t v3 = mp_obj_get_float(x3); + if(v1 < v2) { + return x2; + } else if(v1 > v3) { + return x3; + } else { + return x1; + } + } else { // assume ndarrays + return compare_function(x2, compare_function(x1, x3, COMPARE_MINIMUM), COMPARE_MAXIMUM); + } +} + +MP_DEFINE_CONST_FUN_OBJ_3(compare_clip_obj, compare_clip); +#endif + +#if ULAB_NUMPY_HAS_EQUAL + +mp_obj_t compare_equal(mp_obj_t x1, mp_obj_t x2) { + return compare_equal_helper(x1, x2, COMPARE_EQUAL); +} + +MP_DEFINE_CONST_FUN_OBJ_2(compare_equal_obj, compare_equal); +#endif + +#if ULAB_NUMPY_HAS_NOTEQUAL + +mp_obj_t compare_not_equal(mp_obj_t x1, mp_obj_t x2) { + return compare_equal_helper(x1, x2, COMPARE_NOT_EQUAL); +} + +MP_DEFINE_CONST_FUN_OBJ_2(compare_not_equal_obj, compare_not_equal); +#endif + +#if ULAB_NUMPY_HAS_ISFINITE | ULAB_NUMPY_HAS_ISINF +static mp_obj_t compare_isinf_isfinite(mp_obj_t _x, uint8_t mask) { + // mask should signify, whether the function is called from isinf (mask = 1), + // or from isfinite (mask = 0) + if(mp_obj_is_int(_x)) { + if(mask) { + return mp_const_false; + } else { + return mp_const_true; + } + } else if(mp_obj_is_float(_x)) { + mp_float_t x = mp_obj_get_float(_x); + if(isnan(x)) { + return mp_const_false; + } + if(mask) { // called from isinf + return isinf(x) ? mp_const_true : mp_const_false; + } else { // called from isfinite + return isinf(x) ? mp_const_false : mp_const_true; + } + } else if(mp_obj_is_type(_x, &ulab_ndarray_type)) { + ndarray_obj_t *x = MP_OBJ_TO_PTR(_x); + COMPLEX_DTYPE_NOT_IMPLEMENTED(x->dtype) + ndarray_obj_t *results = ndarray_new_dense_ndarray(x->ndim, x->shape, NDARRAY_BOOL); + // At this point, results is all False + uint8_t *rarray = (uint8_t *)results->array; + if(x->dtype != NDARRAY_FLOAT) { + // int types can never be infinite... + if(!mask) { + // ...so flip all values in the array, if the function was called from isfinite + memset(rarray, 1, results->len); + } + return MP_OBJ_FROM_PTR(results); + } + uint8_t *xarray = (uint8_t *)x->array; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_float_t value = *(mp_float_t *)xarray; + if(isnan(value)) { + *rarray++ = 0; + } else { + *rarray++ = isinf(value) ? mask : 1 - mask; + } + xarray += x->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < x->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + xarray -= x->strides[ULAB_MAX_DIMS - 1] * x->shape[ULAB_MAX_DIMS-1]; + xarray += x->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < x->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + xarray -= x->strides[ULAB_MAX_DIMS - 2] * x->shape[ULAB_MAX_DIMS-2]; + xarray += x->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < x->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + xarray -= x->strides[ULAB_MAX_DIMS - 3] * x->shape[ULAB_MAX_DIMS-3]; + xarray += x->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < x->shape[ULAB_MAX_DIMS - 4]); + #endif + + return MP_OBJ_FROM_PTR(results); + } else { + mp_raise_TypeError(translate("wrong input type")); + } + return mp_const_none; +} +#endif + +#if ULAB_NUMPY_HAS_ISFINITE +mp_obj_t compare_isfinite(mp_obj_t _x) { + return compare_isinf_isfinite(_x, 0); +} + +MP_DEFINE_CONST_FUN_OBJ_1(compare_isfinite_obj, compare_isfinite); +#endif + +#if ULAB_NUMPY_HAS_ISINF +mp_obj_t compare_isinf(mp_obj_t _x) { + return compare_isinf_isfinite(_x, 1); +} + +MP_DEFINE_CONST_FUN_OBJ_1(compare_isinf_obj, compare_isinf); +#endif + +#if ULAB_NUMPY_HAS_MAXIMUM +mp_obj_t compare_maximum(mp_obj_t x1, mp_obj_t x2) { + // extra round, so that we can return maximum(3, 4) properly + mp_obj_t result = compare_function(x1, x2, COMPARE_MAXIMUM); + if((mp_obj_is_int(x1) || mp_obj_is_float(x1)) && (mp_obj_is_int(x2) || mp_obj_is_float(x2))) { + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(result); + return mp_binary_get_val_array(ndarray->dtype, ndarray->array, 0); + } + return result; +} + +MP_DEFINE_CONST_FUN_OBJ_2(compare_maximum_obj, compare_maximum); +#endif + +#if ULAB_NUMPY_HAS_MINIMUM + +mp_obj_t compare_minimum(mp_obj_t x1, mp_obj_t x2) { + // extra round, so that we can return minimum(3, 4) properly + mp_obj_t result = compare_function(x1, x2, COMPARE_MINIMUM); + if((mp_obj_is_int(x1) || mp_obj_is_float(x1)) && (mp_obj_is_int(x2) || mp_obj_is_float(x2))) { + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(result); + return mp_binary_get_val_array(ndarray->dtype, ndarray->array, 0); + } + return result; +} + +MP_DEFINE_CONST_FUN_OBJ_2(compare_minimum_obj, compare_minimum); +#endif + +#if ULAB_NUMPY_HAS_NONZERO + +mp_obj_t compare_nonzero(mp_obj_t x) { + ndarray_obj_t *ndarray_x = ndarray_from_mp_obj(x, 0); + // since ndarray_new_linear_array calls m_new0, the content of zero is a single zero + ndarray_obj_t *zero = ndarray_new_linear_array(1, NDARRAY_UINT8); + + uint8_t ndim = 0; + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + int32_t *x_strides = m_new(int32_t, ULAB_MAX_DIMS); + int32_t *zero_strides = m_new(int32_t, ULAB_MAX_DIMS); + // we don't actually have to inspect the outcome of ndarray_can_broadcast, + // because the right hand side is a linear array with a single element + ndarray_can_broadcast(ndarray_x, zero, &ndim, shape, x_strides, zero_strides); + + // equal_obj is a Boolean ndarray + mp_obj_t equal_obj = ndarray_binary_equality(ndarray_x, zero, ndim, shape, x_strides, zero_strides, MP_BINARY_OP_NOT_EQUAL); + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(equal_obj); + + // these are no longer needed, get rid of them + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(int32_t, x_strides, ULAB_MAX_DIMS); + m_del(int32_t, zero_strides, ULAB_MAX_DIMS); + + uint8_t *array = (uint8_t *)ndarray->array; + uint8_t *origin = (uint8_t *)ndarray->array; + + // First, count the number of Trues: + uint16_t count = 0; + size_t indices[ULAB_MAX_DIMS]; + + #if ULAB_MAX_DIMS > 3 + indices[3] = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + indices[2] = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + indices[1] = 0; + do { + #endif + indices[0] = 0; + do { + if(*array != 0) { + count++; + } + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + indices[0]++; + } while(indices[0] < ndarray->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + array -= ndarray->strides[ULAB_MAX_DIMS - 1] * ndarray->shape[ULAB_MAX_DIMS-1]; + array += ndarray->strides[ULAB_MAX_DIMS - 2]; + indices[1]++; + } while(indices[1] < ndarray->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + array -= ndarray->strides[ULAB_MAX_DIMS - 2] * ndarray->shape[ULAB_MAX_DIMS-2]; + array += ndarray->strides[ULAB_MAX_DIMS - 3]; + indices[2]++; + } while(indices[2] < ndarray->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= ndarray->strides[ULAB_MAX_DIMS - 3] * ndarray->shape[ULAB_MAX_DIMS-3]; + array += ndarray->strides[ULAB_MAX_DIMS - 4]; + indices[3]++; + } while(indices[3] < ndarray->shape[ULAB_MAX_DIMS - 4]); + #endif + + mp_obj_t *items = m_new(mp_obj_t, ndarray->ndim); + uint16_t *arrays[ULAB_MAX_DIMS]; + + for(uint8_t i = 0; i < ndarray->ndim; i++) { + ndarray_obj_t *item_array = ndarray_new_linear_array(count, NDARRAY_UINT16); + uint16_t *iarray = (uint16_t *)item_array->array; + arrays[ULAB_MAX_DIMS - 1 - i] = iarray; + items[ndarray->ndim - 1 - i] = MP_OBJ_FROM_PTR(item_array); + } + array = origin; + count = 0; + + #if ULAB_MAX_DIMS > 3 + indices[3] = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + indices[2] = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + indices[1] = 0; + do { + #endif + indices[0] = 0; + do { + if(*array != 0) { + for(uint8_t d = 0; d < ndarray->ndim; d++) { + arrays[ULAB_MAX_DIMS - 1 - d][count] = indices[d]; + } + count++; + } + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + indices[0]++; + } while(indices[0] < ndarray->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + array -= ndarray->strides[ULAB_MAX_DIMS - 1] * ndarray->shape[ULAB_MAX_DIMS-1]; + array += ndarray->strides[ULAB_MAX_DIMS - 2]; + indices[1]++; + } while(indices[1] < ndarray->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + array -= ndarray->strides[ULAB_MAX_DIMS - 2] * ndarray->shape[ULAB_MAX_DIMS-2]; + array += ndarray->strides[ULAB_MAX_DIMS - 3]; + indices[2]++; + } while(indices[2] < ndarray->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= ndarray->strides[ULAB_MAX_DIMS - 3] * ndarray->shape[ULAB_MAX_DIMS-3]; + array += ndarray->strides[ULAB_MAX_DIMS - 4]; + indices[3]++; + } while(indices[3] < ndarray->shape[ULAB_MAX_DIMS - 4]); + #endif + + return mp_obj_new_tuple(ndarray->ndim, items); +} + +MP_DEFINE_CONST_FUN_OBJ_1(compare_nonzero_obj, compare_nonzero); +#endif /* ULAB_NUMPY_HAS_NONZERO */ + +#if ULAB_NUMPY_HAS_WHERE + +mp_obj_t compare_where(mp_obj_t _condition, mp_obj_t _x, mp_obj_t _y) { + // this implementation will work with ndarrays, and scalars only + ndarray_obj_t *c = ndarray_from_mp_obj(_condition, 0); + ndarray_obj_t *x = ndarray_from_mp_obj(_x, 0); + ndarray_obj_t *y = ndarray_from_mp_obj(_y, 0); + + COMPLEX_DTYPE_NOT_IMPLEMENTED(c->dtype) + COMPLEX_DTYPE_NOT_IMPLEMENTED(x->dtype) + COMPLEX_DTYPE_NOT_IMPLEMENTED(y->dtype) + + int32_t *cstrides = m_new(int32_t, ULAB_MAX_DIMS); + int32_t *xstrides = m_new(int32_t, ULAB_MAX_DIMS); + int32_t *ystrides = m_new(int32_t, ULAB_MAX_DIMS); + + size_t *oshape = m_new(size_t, ULAB_MAX_DIMS); + + uint8_t ndim; + + // establish the broadcasting conditions first + // if any two of the arrays can be broadcast together, then + // the three arrays can also be broadcast together + if(!ndarray_can_broadcast(c, x, &ndim, oshape, cstrides, ystrides) || + !ndarray_can_broadcast(c, y, &ndim, oshape, cstrides, ystrides) || + !ndarray_can_broadcast(x, y, &ndim, oshape, xstrides, ystrides)) { + mp_raise_ValueError(translate("operands could not be broadcast together")); + } + + ndim = MAX(MAX(c->ndim, x->ndim), y->ndim); + + for(uint8_t i = 1; i <= ndim; i++) { + cstrides[ULAB_MAX_DIMS - i] = c->shape[ULAB_MAX_DIMS - i] < 2 ? 0 : c->strides[ULAB_MAX_DIMS - i]; + xstrides[ULAB_MAX_DIMS - i] = x->shape[ULAB_MAX_DIMS - i] < 2 ? 0 : x->strides[ULAB_MAX_DIMS - i]; + ystrides[ULAB_MAX_DIMS - i] = y->shape[ULAB_MAX_DIMS - i] < 2 ? 0 : y->strides[ULAB_MAX_DIMS - i]; + oshape[ULAB_MAX_DIMS - i] = MAX(MAX(c->shape[ULAB_MAX_DIMS - i], x->shape[ULAB_MAX_DIMS - i]), y->shape[ULAB_MAX_DIMS - i]); + } + + uint8_t out_dtype = ndarray_upcast_dtype(x->dtype, y->dtype); + ndarray_obj_t *out = ndarray_new_dense_ndarray(ndim, oshape, out_dtype); + + mp_float_t (*cfunc)(void *) = ndarray_get_float_function(c->dtype); + mp_float_t (*xfunc)(void *) = ndarray_get_float_function(x->dtype); + mp_float_t (*yfunc)(void *) = ndarray_get_float_function(y->dtype); + mp_float_t (*ofunc)(void *, mp_float_t ) = ndarray_set_float_function(out->dtype); + + uint8_t *oarray = (uint8_t *)out->array; + uint8_t *carray = (uint8_t *)c->array; + uint8_t *xarray = (uint8_t *)x->array; + uint8_t *yarray = (uint8_t *)y->array; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_float_t value; + mp_float_t cvalue = cfunc(carray); + if(cvalue != MICROPY_FLOAT_CONST(0.0)) { + value = xfunc(xarray); + } else { + value = yfunc(yarray); + } + ofunc(oarray, value); + oarray += out->itemsize; + carray += cstrides[ULAB_MAX_DIMS - 1]; + xarray += xstrides[ULAB_MAX_DIMS - 1]; + yarray += ystrides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < out->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + carray -= cstrides[ULAB_MAX_DIMS - 1] * c->shape[ULAB_MAX_DIMS-1]; + carray += cstrides[ULAB_MAX_DIMS - 2]; + xarray -= xstrides[ULAB_MAX_DIMS - 1] * x->shape[ULAB_MAX_DIMS-1]; + xarray += xstrides[ULAB_MAX_DIMS - 2]; + yarray -= ystrides[ULAB_MAX_DIMS - 1] * y->shape[ULAB_MAX_DIMS-1]; + yarray += ystrides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < out->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + carray -= cstrides[ULAB_MAX_DIMS - 2] * c->shape[ULAB_MAX_DIMS-2]; + carray += cstrides[ULAB_MAX_DIMS - 3]; + xarray -= xstrides[ULAB_MAX_DIMS - 2] * x->shape[ULAB_MAX_DIMS-2]; + xarray += xstrides[ULAB_MAX_DIMS - 3]; + yarray -= ystrides[ULAB_MAX_DIMS - 2] * y->shape[ULAB_MAX_DIMS-2]; + yarray += ystrides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < out->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + carray -= cstrides[ULAB_MAX_DIMS - 3] * c->shape[ULAB_MAX_DIMS-3]; + carray += cstrides[ULAB_MAX_DIMS - 4]; + xarray -= xstrides[ULAB_MAX_DIMS - 3] * x->shape[ULAB_MAX_DIMS-3]; + xarray += xstrides[ULAB_MAX_DIMS - 4]; + yarray -= ystrides[ULAB_MAX_DIMS - 3] * y->shape[ULAB_MAX_DIMS-3]; + yarray += ystrides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < out->shape[ULAB_MAX_DIMS - 4]); + #endif + return MP_OBJ_FROM_PTR(out); +} + +MP_DEFINE_CONST_FUN_OBJ_3(compare_where_obj, compare_where); +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/compare.h b/components/3rd_party/omv/omv/modules/ulab/code/numpy/compare.h new file mode 100644 index 00000000..de3d7e65 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/compare.h @@ -0,0 +1,151 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös +*/ + +#ifndef _COMPARE_ +#define _COMPARE_ + +#include "../ulab.h" +#include "../ndarray.h" + +enum COMPARE_FUNCTION_TYPE { + COMPARE_EQUAL, + COMPARE_NOT_EQUAL, + COMPARE_MINIMUM, + COMPARE_MAXIMUM, + COMPARE_CLIP, +}; + +MP_DECLARE_CONST_FUN_OBJ_3(compare_clip_obj); +MP_DECLARE_CONST_FUN_OBJ_2(compare_equal_obj); +MP_DECLARE_CONST_FUN_OBJ_2(compare_isfinite_obj); +MP_DECLARE_CONST_FUN_OBJ_2(compare_isinf_obj); +MP_DECLARE_CONST_FUN_OBJ_2(compare_minimum_obj); +MP_DECLARE_CONST_FUN_OBJ_2(compare_maximum_obj); +MP_DECLARE_CONST_FUN_OBJ_1(compare_nonzero_obj); +MP_DECLARE_CONST_FUN_OBJ_2(compare_not_equal_obj); +MP_DECLARE_CONST_FUN_OBJ_3(compare_where_obj); + +#if ULAB_MAX_DIMS == 1 +#define COMPARE_LOOP(results, array, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t l = 0;\ + do {\ + *((type_out *)(array)) = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray)) ? (type_out)(*((type_left *)(larray))) : (type_out)(*((type_right *)(rarray)));\ + (array) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < results->shape[ULAB_MAX_DIMS - 1]);\ + return MP_OBJ_FROM_PTR(results);\ + +#endif // ULAB_MAX_DIMS == 1 + +#if ULAB_MAX_DIMS == 2 +#define COMPARE_LOOP(results, array, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *((type_out *)(array)) = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray)) ? (type_out)(*((type_left *)(larray))) : (type_out)(*((type_right *)(rarray)));\ + (array) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < results->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < results->shape[ULAB_MAX_DIMS - 2]);\ + return MP_OBJ_FROM_PTR(results);\ + +#endif // ULAB_MAX_DIMS == 2 + +#if ULAB_MAX_DIMS == 3 +#define COMPARE_LOOP(results, array, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *((type_out *)(array)) = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray)) ? (type_out)(*((type_left *)(larray))) : (type_out)(*((type_right *)(rarray)));\ + (array) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < results->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < results->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < results->shape[ULAB_MAX_DIMS - 3]);\ + return MP_OBJ_FROM_PTR(results);\ + +#endif // ULAB_MAX_DIMS == 3 + +#if ULAB_MAX_DIMS == 4 +#define COMPARE_LOOP(results, array, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ + size_t i = 0;\ + do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *((type_out *)(array)) = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray)) ? (type_out)(*((type_left *)(larray))) : (type_out)(*((type_right *)(rarray)));\ + (array) += (results)->strides[ULAB_MAX_DIMS - 1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < results->shape[ULAB_MAX_DIMS - 1]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < results->shape[ULAB_MAX_DIMS - 2]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < results->shape[ULAB_MAX_DIMS - 3]);\ + (larray) -= (lstrides)[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3];\ + (larray) += (lstrides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3];\ + (rarray) += (rstrides)[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < results->shape[ULAB_MAX_DIMS - 4]);\ + return MP_OBJ_FROM_PTR(results);\ + +#endif // ULAB_MAX_DIMS == 4 + +#define RUN_COMPARE_LOOP(dtype, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, ndim, shape, op) do {\ + ndarray_obj_t *results = ndarray_new_dense_ndarray((ndim), (shape), (dtype));\ + uint8_t *array = (uint8_t *)results->array;\ + if((op) == COMPARE_MINIMUM) {\ + COMPARE_LOOP(results, array, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, <);\ + }\ + if((op) == COMPARE_MAXIMUM) {\ + COMPARE_LOOP(results, array, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, >);\ + }\ +} while(0) + +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/create.c b/components/3rd_party/omv/omv/modules/ulab/code/numpy/create.c new file mode 100644 index 00000000..0f5bce00 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/create.c @@ -0,0 +1,859 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2019-2021 Zoltán Vörös + * 2020 Taku Fukada +*/ + +#include +#include +#include +#include +#include "py/obj.h" +#include "py/runtime.h" + +#include "create.h" +#include "../ulab.h" +#include "../ulab_tools.h" + +#if ULAB_NUMPY_HAS_ONES | ULAB_NUMPY_HAS_ZEROS | ULAB_NUMPY_HAS_FULL | ULAB_NUMPY_HAS_EMPTY +static mp_obj_t create_zeros_ones_full(mp_obj_t oshape, uint8_t dtype, mp_obj_t value) { + if(!mp_obj_is_int(oshape) && !mp_obj_is_type(oshape, &mp_type_tuple) && !mp_obj_is_type(oshape, &mp_type_list)) { + mp_raise_TypeError(translate("input argument must be an integer, a tuple, or a list")); + } + ndarray_obj_t *ndarray = NULL; + if(mp_obj_is_int(oshape)) { + size_t n = mp_obj_get_int(oshape); + ndarray = ndarray_new_linear_array(n, dtype); + } else if(mp_obj_is_type(oshape, &mp_type_tuple) || mp_obj_is_type(oshape, &mp_type_list)) { + uint8_t len = (uint8_t)mp_obj_get_int(mp_obj_len_maybe(oshape)); + if(len > ULAB_MAX_DIMS) { + mp_raise_TypeError(translate("too many dimensions")); + } + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + + size_t i = 0; + mp_obj_iter_buf_t iter_buf; + mp_obj_t item, iterable = mp_getiter(oshape, &iter_buf); + while((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION){ + shape[ULAB_MAX_DIMS - len + i] = (size_t)mp_obj_get_int(item); + i++; + } + ndarray = ndarray_new_dense_ndarray(len, shape, dtype); + } + if(value != mp_const_none) { + if(dtype == NDARRAY_BOOL) { + dtype = NDARRAY_UINT8; + if(mp_obj_is_true(value)) { + value = mp_obj_new_int(1); + } else { + value = mp_obj_new_int(0); + } + } + for(size_t i=0; i < ndarray->len; i++) { + #if ULAB_SUPPORTS_COMPLEX + if(dtype == NDARRAY_COMPLEX) { + ndarray_set_complex_value(ndarray->array, i, value); + } else { + ndarray_set_value(dtype, ndarray->array, i, value); + } + #else + ndarray_set_value(dtype, ndarray->array, i, value); + #endif + } + } + // if zeros calls the function, we don't have to do anything + return MP_OBJ_FROM_PTR(ndarray); +} +#endif + +#if ULAB_NUMPY_HAS_ARANGE | ULAB_NUMPY_HAS_LINSPACE +static ndarray_obj_t *create_linspace_arange(mp_float_t start, mp_float_t step, mp_float_t stop, size_t len, uint8_t dtype) { + mp_float_t value = start; + + ndarray_obj_t *ndarray = ndarray_new_linear_array(len, dtype); + if(ndarray->boolean == NDARRAY_BOOLEAN) { + uint8_t *array = (uint8_t *)ndarray->array; + for(size_t i=0; i < len; i++, value += step) { + *array++ = value == MICROPY_FLOAT_CONST(0.0) ? 0 : 1; + } + } else if(dtype == NDARRAY_UINT8) { + ARANGE_LOOP(uint8_t, ndarray, len, step, stop); + } else if(dtype == NDARRAY_INT8) { + ARANGE_LOOP(int8_t, ndarray, len, step, stop); + } else if(dtype == NDARRAY_UINT16) { + ARANGE_LOOP(uint16_t, ndarray, len, step, stop); + } else if(dtype == NDARRAY_INT16) { + ARANGE_LOOP(int16_t, ndarray, len, step, stop); + } else { + ARANGE_LOOP(mp_float_t, ndarray, len, step, stop); + } + return ndarray; +} +#endif + +#if ULAB_NUMPY_HAS_ARANGE +//| @overload +//| def arange(stop: _float, step: _float = 1, *, dtype: _DType = ulab.numpy.float) -> ulab.numpy.ndarray: ... +//| @overload +//| def arange(start: _float, stop: _float, step: _float = 1, *, dtype: _DType = ulab.numpy.float) -> ulab.numpy.ndarray: +//| """ +//| .. param: start +//| First value in the array, optional, defaults to 0 +//| .. param: stop +//| Final value in the array +//| .. param: step +//| Difference between consecutive elements, optional, defaults to 1.0 +//| .. param: dtype +//| Type of values in the array +//| +//| Return a new 1-D array with elements ranging from ``start`` to ``stop``, with step size ``step``.""" +//| ... +//| + +mp_obj_t create_arange(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_, MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_, MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + uint8_t dtype = NDARRAY_FLOAT; + mp_float_t start, stop, step; + if(n_args == 1) { + start = MICROPY_FLOAT_CONST(0.0); + stop = mp_obj_get_float(args[0].u_obj); + step = MICROPY_FLOAT_CONST(1.0); + if(mp_obj_is_int(args[0].u_obj)) dtype = NDARRAY_INT16; + } else if(n_args == 2) { + start = mp_obj_get_float(args[0].u_obj); + stop = mp_obj_get_float(args[1].u_obj); + step = MICROPY_FLOAT_CONST(1.0); + if(mp_obj_is_int(args[0].u_obj) && mp_obj_is_int(args[1].u_obj)) dtype = NDARRAY_INT16; + } else if(n_args == 3) { + start = mp_obj_get_float(args[0].u_obj); + stop = mp_obj_get_float(args[1].u_obj); + step = mp_obj_get_float(args[2].u_obj); + if(mp_obj_is_int(args[0].u_obj) && mp_obj_is_int(args[1].u_obj) && mp_obj_is_int(args[2].u_obj)) dtype = NDARRAY_INT16; + } else { + mp_raise_TypeError(translate("wrong number of arguments")); + } + if((MICROPY_FLOAT_C_FUN(fabs)(stop) > 32768) || (MICROPY_FLOAT_C_FUN(fabs)(start) > 32768) || (MICROPY_FLOAT_C_FUN(fabs)(step) > 32768)) { + dtype = NDARRAY_FLOAT; + } + if(args[3].u_obj != mp_const_none) { + dtype = (uint8_t)mp_obj_get_int(args[3].u_obj); + } + + // bail out, if the range cannot be constructed + if(step == MICROPY_FLOAT_CONST(0.0)) { + mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("divide by zero")); + } + + if(isnan(start) || isnan(stop) || isnan(step)) { + mp_raise_ValueError(translate("arange: cannot compute length")); + } + + ndarray_obj_t *ndarray; + if((stop - start)/step <= 0) { + ndarray = ndarray_new_linear_array(0, dtype); + } else { + size_t len = (size_t)(MICROPY_FLOAT_C_FUN(ceil)((stop - start) / step)); + stop = start + (len - 1) * step; + ndarray = create_linspace_arange(start, step, stop, len, dtype); + } + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_arange_obj, 1, create_arange); +#endif + + +#if ULAB_NUMPY_HAS_ASARRAY +mp_obj_t create_asarray(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + uint8_t _dtype; + #if ULAB_HAS_DTYPE_OBJECT + if(mp_obj_is_type(args[1].u_obj, &ulab_dtype_type)) { + dtype_obj_t *dtype = MP_OBJ_TO_PTR(args[1].u_obj); + _dtype = dtype->dtype; + } else { // this must be an integer defined as a class constant (ulab.numpy.uint8 etc.) + if(args[1].u_obj == mp_const_none) { + _dtype = 0; + } else { + _dtype = mp_obj_get_int(args[1].u_obj); + } + } + #else + if(args[1].u_obj == mp_const_none) { + _dtype = 0; + } else { + _dtype = mp_obj_get_int(args[1].u_obj); + } + #endif + + if(ulab_tools_mp_obj_is_scalar(args[0].u_obj)) { + return args[0].u_obj; + } else if(mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj); + if((_dtype == ndarray->dtype) || (_dtype == 0)) { + return args[0].u_obj; + } else { + return MP_OBJ_FROM_PTR(ndarray_copy_view_convert_type(ndarray, _dtype)); + } + } else if(ndarray_object_is_array_like(args[0].u_obj)) { + if(_dtype == 0) { + _dtype = NDARRAY_FLOAT; + } + return MP_OBJ_FROM_PTR(ndarray_from_iterable(args[0].u_obj, _dtype)); + } else { + mp_raise_TypeError(translate("wrong input type")); + } + return mp_const_none; // this should never happen +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_asarray_obj, 1, create_asarray); +#endif + +#if ULAB_NUMPY_HAS_CONCATENATE +//| def concatenate(arrays: Tuple[ulab.numpy.ndarray], *, axis: int = 0) -> ulab.numpy.ndarray: +//| """ +//| .. param: arrays +//| tuple of ndarrays +//| .. param: axis +//| axis along which the arrays will be joined +//| +//| Join a sequence of arrays along an existing axis.""" +//| ... +//| + +mp_obj_t create_concatenate(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 0 } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!mp_obj_is_type(args[0].u_obj, &mp_type_tuple)) { + mp_raise_TypeError(translate("first argument must be a tuple of ndarrays")); + } + int8_t axis = (int8_t)args[1].u_int; + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + mp_obj_tuple_t *ndarrays = MP_OBJ_TO_PTR(args[0].u_obj); + + for(uint8_t i = 0; i < ndarrays->len; i++) { + if(!mp_obj_is_type(ndarrays->items[i], &ulab_ndarray_type)) { + mp_raise_ValueError(translate("only ndarrays can be concatenated")); + } + } + + // first check, whether the arrays are compatible + ndarray_obj_t *_ndarray = MP_OBJ_TO_PTR(ndarrays->items[0]); + uint8_t dtype = _ndarray->dtype; + uint8_t ndim = _ndarray->ndim; + if(axis < 0) { + axis += ndim; + } + if((axis < 0) || (axis >= ndim)) { + mp_raise_ValueError(translate("wrong axis specified")); + } + // shift axis + axis = ULAB_MAX_DIMS - ndim + axis; + for(uint8_t j=0; j < ULAB_MAX_DIMS; j++) { + shape[j] = _ndarray->shape[j]; + } + + for(uint8_t i=1; i < ndarrays->len; i++) { + _ndarray = MP_OBJ_TO_PTR(ndarrays->items[i]); + // check, whether the arrays are compatible + if((dtype != _ndarray->dtype) || (ndim != _ndarray->ndim)) { + mp_raise_ValueError(translate("input arrays are not compatible")); + } + for(uint8_t j=0; j < ULAB_MAX_DIMS; j++) { + if(j == axis) { + shape[j] += _ndarray->shape[j]; + } else { + if(shape[j] != _ndarray->shape[j]) { + mp_raise_ValueError(translate("input arrays are not compatible")); + } + } + } + } + + ndarray_obj_t *target = ndarray_new_dense_ndarray(ndim, shape, dtype); + uint8_t *tpos = (uint8_t *)target->array; + uint8_t *tarray; + + for(uint8_t p=0; p < ndarrays->len; p++) { + // reset the pointer along the axis + ndarray_obj_t *source = MP_OBJ_TO_PTR(ndarrays->items[p]); + uint8_t *sarray = (uint8_t *)source->array; + tarray = tpos; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + memcpy(tarray, sarray, source->itemsize); + tarray += target->strides[ULAB_MAX_DIMS - 1]; + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + tarray -= target->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + tarray += target->strides[ULAB_MAX_DIMS - 2]; + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + tarray -= target->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + tarray += target->strides[ULAB_MAX_DIMS - 3]; + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + tarray -= target->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + tarray += target->strides[ULAB_MAX_DIMS - 4]; + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif + if(p < ndarrays->len - 1) { + tpos += target->strides[axis] * source->shape[axis]; + } + } + return MP_OBJ_FROM_PTR(target); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_concatenate_obj, 1, create_concatenate); +#endif + +#if ULAB_MAX_DIMS > 1 +#if ULAB_NUMPY_HAS_DIAG +//| def diag(a: ulab.numpy.ndarray, *, k: int = 0) -> ulab.numpy.ndarray: +//| """ +//| .. param: a +//| an ndarray +//| .. param: k +//| Offset of the diagonal from the main diagonal. Can be positive or negative. +//| +//| Return specified diagonals.""" +//| ... +//| + +mp_obj_t create_diag(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_k, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 0 } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + ndarray_obj_t *source = ndarray_from_iterable(args[0].u_obj, NDARRAY_FLOAT); + ndarray_obj_t *target = NULL; + + int32_t k = args[1].u_int; + size_t k_abs = k >= 0 ? (size_t)k : (size_t)(-k); + if(source->ndim == 2) { // return the diagonal + size_t len; + if(k >= 0) { + len = (k_abs <= source->shape[ULAB_MAX_DIMS - 1]) ? source->shape[ULAB_MAX_DIMS - 1] - k_abs : 0; + } else { + len = (k_abs <= source->shape[ULAB_MAX_DIMS - 2]) ? source->shape[ULAB_MAX_DIMS - 2] - k_abs : 0; + } + target = ndarray_new_linear_array(len, source->dtype); + + if(len == 0) { + return MP_OBJ_FROM_PTR(target); + } + + uint8_t *sarray = (uint8_t *)source->array; + uint8_t *tarray = (uint8_t *)target->array; + if(k >= 0) { + sarray += source->strides[ULAB_MAX_DIMS - 1] * k; + } else { + sarray += source->strides[ULAB_MAX_DIMS - 2] * k_abs; + } + for(size_t i=0; i < len; i++) { + memcpy(tarray, sarray, source->itemsize); + sarray += (source->strides[ULAB_MAX_DIMS - 1] + source->strides[ULAB_MAX_DIMS - 2]); + tarray += target->itemsize; + } + } else if(source->ndim == 1) { // return a rank-2 tensor with the prescribed diagonal + size_t len = source->len + k_abs; + target = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, len, len), source->dtype); + uint8_t *sarray = (uint8_t *)source->array; + uint8_t *tarray = (uint8_t *)target->array; + + if(k < 0) { + tarray += len * k_abs * target->itemsize; + } else { + tarray += k_abs * target->itemsize; + } + for(size_t i = 0; i < source->len; i++) { + memcpy(tarray, sarray, source->itemsize); + sarray += source->strides[ULAB_MAX_DIMS - 1]; + tarray += (len + 1) * target->itemsize; + } + } + #if ULAB_MAX_DIMS > 2 + else { + mp_raise_ValueError(translate("input must be 1- or 2-d")); + } + #endif + + return MP_OBJ_FROM_PTR(target); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_diag_obj, 1, create_diag); +#endif /* ULAB_NUMPY_HAS_DIAG */ + +#if ULAB_NUMPY_HAS_EMPTY +// This function is bound in numpy.c to numpy.zeros(), and is simply an alias for that + +//| def empty(shape: Union[int, Tuple[int, ...]], *, dtype: _DType = ulab.numpy.float) -> ulab.numpy.ndarray: +//| """ +//| .. param: shape +//| Shape of the array, either an integer (for a 1-D array) or a tuple of 2 integers (for a 2-D array) +//| .. param: dtype +//| Type of values in the array +//| +//| Return a new array of the given shape with all elements set to 0. An alias for numpy.zeros.""" +//| ... +//| +#endif + +#if ULAB_NUMPY_HAS_EYE +//| def eye(size: int, *, M: Optional[int] = None, k: int = 0, dtype: _DType = ulab.numpy.float) -> ulab.numpy.ndarray: +//| """Return a new square array of size, with the diagonal elements set to 1 +//| and the other elements set to 0. If k is given, the diagonal is shifted by the specified amount.""" +//| ... +//| + +mp_obj_t create_eye(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_INT, { .u_int = 0 } }, + { MP_QSTR_M, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_k, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 0 } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = NDARRAY_FLOAT } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + size_t n = args[0].u_int, m; + size_t k = args[2].u_int > 0 ? (size_t)args[2].u_int : (size_t)(-args[2].u_int); + uint8_t dtype = args[3].u_int; + if(args[1].u_obj == mp_const_none) { + m = n; + } else { + m = mp_obj_get_int(args[1].u_obj); + } + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, n, m), dtype); + if(dtype == NDARRAY_BOOL) { + dtype = NDARRAY_UINT8; + } + mp_obj_t one = mp_obj_new_int(1); + size_t i = 0; + if((args[2].u_int >= 0)) { + while(k < m) { + ndarray_set_value(dtype, ndarray->array, i*m+k, one); + k++; + i++; + } + } else { + while(k < n) { + ndarray_set_value(dtype, ndarray->array, k*m+i, one); + k++; + i++; + } + } + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_eye_obj, 1, create_eye); +#endif /* ULAB_NUMPY_HAS_EYE */ +#endif /* ULAB_MAX_DIMS > 1 */ + +#if ULAB_NUMPY_HAS_FULL +//| def full(shape: Union[int, Tuple[int, ...]], fill_value: Union[_float, _bool], *, dtype: _DType = ulab.numpy.float) -> ulab.numpy.ndarray: +//| """ +//| .. param: shape +//| Shape of the array, either an integer (for a 1-D array) or a tuple of integers (for tensors of higher rank) +//| .. param: fill_value +//| scalar, the value with which the array is filled +//| .. param: dtype +//| Type of values in the array +//| +//| Return a new array of the given shape with all elements set to 0.""" +//| ... +//| + +mp_obj_t create_full(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = NDARRAY_FLOAT } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + uint8_t dtype = args[2].u_int; + + return create_zeros_ones_full(args[0].u_obj, dtype, args[1].u_obj); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_full_obj, 0, create_full); +#endif + + +#if ULAB_NUMPY_HAS_LINSPACE +//| def linspace( +//| start: _float, +//| stop: _float, +//| *, +//| dtype: _DType = ulab.numpy.float, +//| num: int = 50, +//| endpoint: _bool = True, +//| retstep: _bool = False +//| ) -> ulab.numpy.ndarray: +//| """ +//| .. param: start +//| First value in the array +//| .. param: stop +//| Final value in the array +//| .. param int: num +//| Count of values in the array. +//| .. param: dtype +//| Type of values in the array +//| .. param bool: endpoint +//| Whether the ``stop`` value is included. Note that even when +//| endpoint=True, the exact ``stop`` value may not be included due to the +//| inaccuracy of floating point arithmetic. +//| .. param bool: retstep, +//| If True, return (`samples`, `step`), where `step` is the spacing between samples. +//| +//| Return a new 1-D array with ``num`` elements ranging from ``start`` to ``stop`` linearly.""" +//| ... +//| + +mp_obj_t create_linspace(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_num, MP_ARG_INT, { .u_int = 50 } }, + { MP_QSTR_endpoint, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_TRUE } }, + { MP_QSTR_retstep, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_FALSE } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = NDARRAY_FLOAT } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(args[2].u_int < 2) { + mp_raise_ValueError(translate("number of points must be at least 2")); + } + size_t len = (size_t)args[2].u_int; + mp_float_t start, step, stop; + + ndarray_obj_t *ndarray = NULL; + + #if ULAB_SUPPORTS_COMPLEX + mp_float_t step_real, step_imag; + bool complex_out = false; + + if(mp_obj_is_type(args[0].u_obj, &mp_type_complex) || mp_obj_is_type(args[1].u_obj, &mp_type_complex)) { + complex_out = true; + ndarray = ndarray_new_linear_array(len, NDARRAY_COMPLEX); + mp_float_t *array = (mp_float_t *)ndarray->array; + mp_float_t start_real, start_imag; + mp_float_t stop_real, stop_imag; + + mp_obj_get_complex(args[0].u_obj, &start_real, &start_imag); + mp_obj_get_complex(args[1].u_obj, &stop_real, &stop_imag); + if(args[3].u_obj == mp_const_true) { + step_real = (stop_real - start_real) / (len - 1); + step_imag = (stop_imag - start_imag) / (len - 1); + } else { + step_real = (stop_real - start_real) / len; + step_imag = (stop_imag - start_imag) / len; + } + + for(size_t i = 0; i < len; i++) { + *array++ = start_real; + *array++ = start_imag; + start_real += step_real; + start_imag += step_imag; + } + } else { + #endif + start = mp_obj_get_float(args[0].u_obj); + stop = mp_obj_get_float(args[1].u_obj); + + uint8_t typecode = args[5].u_int; + + if(args[3].u_obj == mp_const_true) { + step = (stop - start) / (len - 1); + } else { + step = (stop - start) / len; + stop = start + step * (len - 1); + } + + ndarray = create_linspace_arange(start, step, stop, len, typecode); + #if ULAB_SUPPORTS_COMPLEX + } + #endif + + if(args[4].u_obj == mp_const_false) { + return MP_OBJ_FROM_PTR(ndarray); + } else { + mp_obj_t tuple[2]; + tuple[0] = MP_OBJ_FROM_PTR(ndarray); + #if ULAB_SUPPORTS_COMPLEX + if(complex_out) { + tuple[1] = mp_obj_new_complex(step_real, step_imag); + } else { + tuple[1] = mp_obj_new_float(step); + } + #else /* ULAB_SUPPORTS_COMPLEX */ + tuple[1] = mp_obj_new_float(step); + #endif + + return mp_obj_new_tuple(2, tuple); + } +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_linspace_obj, 2, create_linspace); +#endif + +#if ULAB_NUMPY_HAS_LOGSPACE +//| def logspace( +//| start: _float, +//| stop: _float, +//| *, +//| dtype: _DType = ulab.numpy.float, +//| num: int = 50, +//| endpoint: _bool = True, +//| base: _float = 10.0 +//| ) -> ulab.numpy.ndarray: +//| """ +//| .. param: start +//| First value in the array +//| .. param: stop +//| Final value in the array +//| .. param int: num +//| Count of values in the array. Defaults to 50. +//| .. param: base +//| The base of the log space. The step size between the elements in +//| ``ln(samples) / ln(base)`` (or ``log_base(samples)``) is uniform. Defaults to 10.0. +//| .. param: dtype +//| Type of values in the array +//| .. param bool: endpoint +//| Whether the ``stop`` value is included. Note that even when +//| endpoint=True, the exact ``stop`` value may not be included due to the +//| inaccuracy of floating point arithmetic. Defaults to True. +//| +//| Return a new 1-D array with ``num`` evenly spaced elements on a log scale. +//| The sequence starts at ``base ** start``, and ends with ``base ** stop``.""" +//| ... +//| + +ULAB_DEFINE_FLOAT_CONST(const_ten, MICROPY_FLOAT_CONST(10.0), 0x41200000UL, 0x4024000000000000ULL); + +mp_obj_t create_logspace(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_num, MP_ARG_INT, { .u_int = 50 } }, + { MP_QSTR_base, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = ULAB_REFERENCE_FLOAT_CONST(const_ten) } }, + { MP_QSTR_endpoint, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_TRUE } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = NDARRAY_FLOAT } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(args[2].u_int < 2) { + mp_raise_ValueError(translate("number of points must be at least 2")); + } + size_t len = (size_t)args[2].u_int; + mp_float_t start, step, quotient; + start = mp_obj_get_float(args[0].u_obj); + uint8_t dtype = args[5].u_int; + mp_float_t base = mp_obj_get_float(args[3].u_obj); + if(args[4].u_obj == mp_const_true) step = (mp_obj_get_float(args[1].u_obj) - start)/(len - 1); + else step = (mp_obj_get_float(args[1].u_obj) - start) / len; + quotient = MICROPY_FLOAT_C_FUN(pow)(base, step); + ndarray_obj_t *ndarray = ndarray_new_linear_array(len, dtype); + + mp_float_t value = MICROPY_FLOAT_C_FUN(pow)(base, start); + if(ndarray->dtype == NDARRAY_UINT8) { + uint8_t *array = (uint8_t *)ndarray->array; + if(ndarray->boolean) { + memset(array, 1, len); + } else { + for(size_t i=0; i < len; i++, value *= quotient) *array++ = (uint8_t)value; + } + } else if(ndarray->dtype == NDARRAY_INT8) { + int8_t *array = (int8_t *)ndarray->array; + for(size_t i=0; i < len; i++, value *= quotient) *array++ = (int8_t)value; + } else if(ndarray->dtype == NDARRAY_UINT16) { + uint16_t *array = (uint16_t *)ndarray->array; + for(size_t i=0; i < len; i++, value *= quotient) *array++ = (uint16_t)value; + } else if(ndarray->dtype == NDARRAY_INT16) { + int16_t *array = (int16_t *)ndarray->array; + for(size_t i=0; i < len; i++, value *= quotient) *array++ = (int16_t)value; + } else { + mp_float_t *array = (mp_float_t *)ndarray->array; + for(size_t i=0; i < len; i++, value *= quotient) *array++ = value; + } + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_logspace_obj, 2, create_logspace); +#endif + +#if ULAB_NUMPY_HAS_ONES +//| def ones(shape: Union[int, Tuple[int, ...]], *, dtype: _DType = ulab.numpy.float) -> ulab.numpy.ndarray: +//| """ +//| .. param: shape +//| Shape of the array, either an integer (for a 1-D array) or a tuple of 2 integers (for a 2-D array) +//| .. param: dtype +//| Type of values in the array +//| +//| Return a new array of the given shape with all elements set to 1.""" +//| ... +//| + +mp_obj_t create_ones(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = NDARRAY_FLOAT } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + uint8_t dtype = args[1].u_int; + mp_obj_t one = mp_obj_new_int(1); + return create_zeros_ones_full(args[0].u_obj, dtype, one); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_ones_obj, 0, create_ones); +#endif + +#if ULAB_NUMPY_HAS_ZEROS +//| def zeros(shape: Union[int, Tuple[int, ...]], *, dtype: _DType = ulab.numpy.float) -> ulab.numpy.ndarray: +//| """ +//| .. param: shape +//| Shape of the array, either an integer (for a 1-D array) or a tuple of 2 integers (for a 2-D array) +//| .. param: dtype +//| Type of values in the array +//| +//| Return a new array of the given shape with all elements set to 0.""" +//| ... +//| + +mp_obj_t create_zeros(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = NDARRAY_FLOAT } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + uint8_t dtype = args[1].u_int; + return create_zeros_ones_full(args[0].u_obj, dtype, mp_const_none); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_zeros_obj, 0, create_zeros); +#endif + +#if ULAB_NUMPY_HAS_FROMBUFFER +mp_obj_t create_frombuffer(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_INT(NDARRAY_FLOAT) } }, + { MP_QSTR_count, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_INT(-1) } }, + { MP_QSTR_offset, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_INT(0) } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + uint8_t dtype = mp_obj_get_int(args[1].u_obj); + size_t offset = mp_obj_get_int(args[3].u_obj); + + mp_buffer_info_t bufinfo; + if(mp_get_buffer(args[0].u_obj, &bufinfo, MP_BUFFER_READ)) { + size_t sz = ulab_binary_get_size(dtype); + + if(bufinfo.len < offset) { + mp_raise_ValueError(translate("offset must be non-negative and no greater than buffer length")); + } + size_t len = (bufinfo.len - offset) / sz; + if((len * sz) != (bufinfo.len - offset)) { + mp_raise_ValueError(translate("buffer size must be a multiple of element size")); + } + if(mp_obj_get_int(args[2].u_obj) > 0) { + size_t count = mp_obj_get_int(args[2].u_obj); + if(len < count) { + mp_raise_ValueError(translate("buffer is smaller than requested size")); + } else { + len = count; + } + } + ndarray_obj_t *ndarray = m_new_obj(ndarray_obj_t); + ndarray->base.type = &ulab_ndarray_type; + ndarray->dtype = dtype == NDARRAY_BOOL ? NDARRAY_UINT8 : dtype; + ndarray->boolean = dtype == NDARRAY_BOOL ? NDARRAY_BOOLEAN : NDARRAY_NUMERIC; + ndarray->ndim = 1; + ndarray->len = len; + ndarray->itemsize = sz; + ndarray->shape[ULAB_MAX_DIMS - 1] = len; + ndarray->strides[ULAB_MAX_DIMS - 1] = sz; + + uint8_t *buffer = bufinfo.buf; + ndarray->array = buffer + offset; + return MP_OBJ_FROM_PTR(ndarray); + } + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(create_frombuffer_obj, 1, create_frombuffer); +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/create.h b/components/3rd_party/omv/omv/modules/ulab/code/numpy/create.h new file mode 100644 index 00000000..6e54b10e --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/create.h @@ -0,0 +1,84 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2019-2021 Zoltán Vörös +*/ + +#ifndef _CREATE_ +#define _CREATE_ + +#include "../ulab.h" +#include "../ndarray.h" + +#if ULAB_NUMPY_HAS_ARANGE +mp_obj_t create_arange(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_arange_obj); +#endif + +#if ULAB_NUMPY_HAS_ASARRAY +mp_obj_t create_arange(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_asarray_obj); +#endif + +#if ULAB_NUMPY_HAS_CONCATENATE +mp_obj_t create_concatenate(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_concatenate_obj); +#endif + +#if ULAB_NUMPY_HAS_DIAG +mp_obj_t create_diag(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_diag_obj); +#endif + +#if ULAB_MAX_DIMS > 1 +#if ULAB_NUMPY_HAS_EYE +mp_obj_t create_eye(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_eye_obj); +#endif +#endif + +#if ULAB_NUMPY_HAS_FULL +mp_obj_t create_full(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_full_obj); +#endif + +#if ULAB_NUMPY_HAS_LINSPACE +mp_obj_t create_linspace(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_linspace_obj); +#endif + +#if ULAB_NUMPY_HAS_LOGSPACE +mp_obj_t create_logspace(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_logspace_obj); +#endif + +#if ULAB_NUMPY_HAS_ONES +mp_obj_t create_ones(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_ones_obj); +#endif + +#if ULAB_NUMPY_HAS_ZEROS +mp_obj_t create_zeros(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_zeros_obj); +#endif + +#if ULAB_NUMPY_HAS_FROMBUFFER +mp_obj_t create_frombuffer(size_t , const mp_obj_t *, mp_map_t *); +MP_DECLARE_CONST_FUN_OBJ_KW(create_frombuffer_obj); +#endif + +#define ARANGE_LOOP(type_, ndarray, len, step, stop) \ +({\ + type_ *array = (type_ *)(ndarray)->array;\ + for (size_t i = 0; i < (len) - 1; i++, (value) += (step)) {\ + *array++ = (type_)(value);\ + }\ + *array = (type_)(stop);\ +}) + +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/fft/fft.c b/components/3rd_party/omv/omv/modules/ulab/code/numpy/fft/fft.c new file mode 100644 index 00000000..ad4996e3 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/fft/fft.c @@ -0,0 +1,109 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * 2020 Scott Shawcroft for Adafruit Industries + * 2020 Taku Fukada +*/ + +#include +#include +#include +#include +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/binary.h" +#include "py/obj.h" +#include "py/objarray.h" + +#include "../carray/carray_tools.h" +#include "fft.h" + +//| """Frequency-domain functions""" +//| +//| import ulab.numpy +//| import ulab.utils + + +//| def fft(r: ulab.numpy.ndarray, c: Optional[ulab.numpy.ndarray] = None) -> Tuple[ulab.numpy.ndarray, ulab.numpy.ndarray]: +//| """ +//| :param ulab.numpy.ndarray r: A 1-dimension array of values whose size is a power of 2 +//| :param ulab.numpy.ndarray c: An optional 1-dimension array of values whose size is a power of 2, giving the complex part of the value +//| :return tuple (r, c): The real and complex parts of the FFT +//| +//| Perform a Fast Fourier Transform from the time domain into the frequency domain +//| +//| See also `ulab.utils.spectrogram`, which computes the magnitude of the fft, +//| rather than separately returning its real and imaginary parts.""" +//| ... +//| +#if ULAB_SUPPORTS_COMPLEX & ULAB_FFT_IS_NUMPY_COMPATIBLE +static mp_obj_t fft_fft(mp_obj_t arg) { + return fft_fft_ifft_spectrogram(arg, FFT_FFT); +} + +MP_DEFINE_CONST_FUN_OBJ_1(fft_fft_obj, fft_fft); +#else +static mp_obj_t fft_fft(size_t n_args, const mp_obj_t *args) { + if(n_args == 2) { + return fft_fft_ifft_spectrogram(n_args, args[0], args[1], FFT_FFT); + } else { + return fft_fft_ifft_spectrogram(n_args, args[0], mp_const_none, FFT_FFT); + } +} + +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fft_fft_obj, 1, 2, fft_fft); +#endif + +//| def ifft(r: ulab.numpy.ndarray, c: Optional[ulab.numpy.ndarray] = None) -> Tuple[ulab.numpy.ndarray, ulab.numpy.ndarray]: +//| """ +//| :param ulab.numpy.ndarray r: A 1-dimension array of values whose size is a power of 2 +//| :param ulab.numpy.ndarray c: An optional 1-dimension array of values whose size is a power of 2, giving the complex part of the value +//| :return tuple (r, c): The real and complex parts of the inverse FFT +//| +//| Perform an Inverse Fast Fourier Transform from the frequeny domain into the time domain""" +//| ... +//| + +#if ULAB_SUPPORTS_COMPLEX & ULAB_FFT_IS_NUMPY_COMPATIBLE +static mp_obj_t fft_ifft(mp_obj_t arg) { + return fft_fft_ifft_spectrogram(arg, FFT_IFFT); +} + +MP_DEFINE_CONST_FUN_OBJ_1(fft_ifft_obj, fft_ifft); +#else +static mp_obj_t fft_ifft(size_t n_args, const mp_obj_t *args) { + NOT_IMPLEMENTED_FOR_COMPLEX() + if(n_args == 2) { + return fft_fft_ifft_spectrogram(n_args, args[0], args[1], FFT_IFFT); + } else { + return fft_fft_ifft_spectrogram(n_args, args[0], mp_const_none, FFT_IFFT); + } +} + +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fft_ifft_obj, 1, 2, fft_ifft); +#endif + +STATIC const mp_rom_map_elem_t ulab_fft_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_fft) }, + { MP_ROM_QSTR(MP_QSTR_fft), MP_ROM_PTR(&fft_fft_obj) }, + { MP_ROM_QSTR(MP_QSTR_ifft), MP_ROM_PTR(&fft_ifft_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_ulab_fft_globals, ulab_fft_globals_table); + +const mp_obj_module_t ulab_fft_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_fft_globals, +}; +#if CIRCUITPY_ULAB +#if !defined(MICROPY_VERSION) || MICROPY_VERSION <= 70144 +MP_REGISTER_MODULE(MP_QSTR_ulab_dot_numpy_dot_fft, ulab_fft_module, MODULE_ULAB_ENABLED); +#else +MP_REGISTER_MODULE(MP_QSTR_ulab_dot_numpy_dot_fft, ulab_fft_module); +#endif +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/fft/fft.h b/components/3rd_party/omv/omv/modules/ulab/code/numpy/fft/fft.h new file mode 100644 index 00000000..1e50a8da --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/fft/fft.h @@ -0,0 +1,30 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös +*/ + +#ifndef _FFT_ +#define _FFT_ + +#include "../../ulab.h" +#include "../../ulab_tools.h" +#include "../../ndarray.h" +#include "fft_tools.h" + +extern const mp_obj_module_t ulab_fft_module; + +#if ULAB_SUPPORTS_COMPLEX & ULAB_FFT_IS_NUMPY_COMPATIBLE +MP_DECLARE_CONST_FUN_OBJ_3(fft_fft_obj); +MP_DECLARE_CONST_FUN_OBJ_3(fft_ifft_obj); +#else +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(fft_fft_obj); +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(fft_ifft_obj); +#endif + +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/fft/fft_tools.c b/components/3rd_party/omv/omv/modules/ulab/code/numpy/fft/fft_tools.c new file mode 100644 index 00000000..595b866b --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/fft/fft_tools.c @@ -0,0 +1,287 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös +*/ + +#include +#include +#include "py/runtime.h" + +#include "../../ndarray.h" +#include "../../ulab_tools.h" +#include "../carray/carray_tools.h" +#include "fft_tools.h" + +#ifndef MP_PI +#define MP_PI MICROPY_FLOAT_CONST(3.14159265358979323846) +#endif +#ifndef MP_E +#define MP_E MICROPY_FLOAT_CONST(2.71828182845904523536) +#endif + +/* Kernel implementation for the case, when ulab has no complex support + + * The following function takes two arrays, namely, the real and imaginary + * parts of a complex array, and calculates the Fourier transform in place. + * + * The function is basically a modification of four1 from Numerical Recipes, + * has no dependencies beyond micropython itself (for the definition of mp_float_t), + * and can be used independent of ulab. + */ + +#if ULAB_SUPPORTS_COMPLEX & ULAB_FFT_IS_NUMPY_COMPATIBLE +/* Kernel implementation for the complex case. Data are contained in data as + + data[0], data[1], data[2], data[3], .... , data[2n - 2], data[2n-1] + real[0], imag[0], real[1], imag[1], .... , real[n-1], imag[n-1] + + In general + real[i] = data[2i] + imag[i] = data[2i+1] + +*/ +void fft_kernel_complex(mp_float_t *data, size_t n, int isign) { + size_t j, m, mmax, istep; + mp_float_t tempr, tempi; + mp_float_t wtemp, wr, wpr, wpi, wi, theta; + + j = 0; + for(size_t i = 0; i < n; i++) { + if (j > i) { + SWAP(mp_float_t, data[2*i], data[2*j]); + SWAP(mp_float_t, data[2*i+1], data[2*j+1]); + } + m = n >> 1; + while (j >= m && m > 0) { + j -= m; + m >>= 1; + } + j += m; + } + + mmax = 1; + while (n > mmax) { + istep = mmax << 1; + theta = MICROPY_FLOAT_CONST(-2.0)*isign*MP_PI/istep; + wtemp = MICROPY_FLOAT_C_FUN(sin)(MICROPY_FLOAT_CONST(0.5) * theta); + wpr = MICROPY_FLOAT_CONST(-2.0) * wtemp * wtemp; + wpi = MICROPY_FLOAT_C_FUN(sin)(theta); + wr = MICROPY_FLOAT_CONST(1.0); + wi = MICROPY_FLOAT_CONST(0.0); + for(m = 0; m < mmax; m++) { + for(size_t i = m; i < n; i += istep) { + j = i + mmax; + tempr = wr * data[2*j] - wi * data[2*j+1]; + tempi = wr * data[2*j+1] + wi * data[2*j]; + data[2*j] = data[2*i] - tempr; + data[2*j+1] = data[2*i+1] - tempi; + data[2*i] += tempr; + data[2*i+1] += tempi; + } + wtemp = wr; + wr = wr*wpr - wi*wpi + wr; + wi = wi*wpr + wtemp*wpi + wi; + } + mmax = istep; + } +} + +/* + * The following function is a helper interface to the python side. + * It has been factored out from fft.c, so that the same argument parsing + * routine can be called from scipy.signal.spectrogram. + */ +mp_obj_t fft_fft_ifft_spectrogram(mp_obj_t data_in, uint8_t type) { + if(!mp_obj_is_type(data_in, &ulab_ndarray_type)) { + mp_raise_NotImplementedError(translate("FFT is defined for ndarrays only")); + } + ndarray_obj_t *in = MP_OBJ_TO_PTR(data_in); + #if ULAB_MAX_DIMS > 1 + if(in->ndim != 1) { + mp_raise_TypeError(translate("FFT is implemented for linear arrays only")); + } + #endif + size_t len = in->len; + // Check if input is of length of power of 2 + if((len & (len-1)) != 0) { + mp_raise_ValueError(translate("input array length must be power of 2")); + } + + ndarray_obj_t *out = ndarray_new_linear_array(len, NDARRAY_COMPLEX); + mp_float_t *data = (mp_float_t *)out->array; + uint8_t *array = (uint8_t *)in->array; + + if(in->dtype == NDARRAY_COMPLEX) { + uint8_t sz = 2 * sizeof(mp_float_t); + for(size_t i = 0; i < len; i++) { + memcpy(data, array, sz); + data += 2; + array += in->strides[ULAB_MAX_DIMS - 1]; + } + } else { + mp_float_t (*func)(void *) = ndarray_get_float_function(in->dtype); + for(size_t i = 0; i < len; i++) { + // real part; the imaginary part is 0, no need to assign + *data = func(array); + data += 2; + array += in->strides[ULAB_MAX_DIMS - 1]; + } + } + data -= 2 * len; + + if((type == FFT_FFT) || (type == FFT_SPECTROGRAM)) { + fft_kernel_complex(data, len, 1); + if(type == FFT_SPECTROGRAM) { + ndarray_obj_t *spectrum = ndarray_new_linear_array(len, NDARRAY_FLOAT); + mp_float_t *sarray = (mp_float_t *)spectrum->array; + for(size_t i = 0; i < len; i++) { + *sarray++ = MICROPY_FLOAT_C_FUN(sqrt)(data[0] * data[0] + data[1] * data[1]); + data += 2; + } + m_del(mp_float_t, data, 2 * len); + return MP_OBJ_FROM_PTR(spectrum); + } + } else { // inverse transform + fft_kernel_complex(data, len, -1); + // TODO: numpy accepts the norm keyword argument + for(size_t i = 0; i < 2 * len; i++) { + *data++ /= len; + } + } + return MP_OBJ_FROM_PTR(out); +} +#else /* ULAB_SUPPORTS_COMPLEX & ULAB_FFT_IS_NUMPY_COMPATIBLE */ +void fft_kernel(mp_float_t *real, mp_float_t *imag, size_t n, int isign) { + size_t j, m, mmax, istep; + mp_float_t tempr, tempi; + mp_float_t wtemp, wr, wpr, wpi, wi, theta; + + j = 0; + for(size_t i = 0; i < n; i++) { + if (j > i) { + SWAP(mp_float_t, real[i], real[j]); + SWAP(mp_float_t, imag[i], imag[j]); + } + m = n >> 1; + while (j >= m && m > 0) { + j -= m; + m >>= 1; + } + j += m; + } + + mmax = 1; + while (n > mmax) { + istep = mmax << 1; + theta = MICROPY_FLOAT_CONST(-2.0)*isign*MP_PI/istep; + wtemp = MICROPY_FLOAT_C_FUN(sin)(MICROPY_FLOAT_CONST(0.5) * theta); + wpr = MICROPY_FLOAT_CONST(-2.0) * wtemp * wtemp; + wpi = MICROPY_FLOAT_C_FUN(sin)(theta); + wr = MICROPY_FLOAT_CONST(1.0); + wi = MICROPY_FLOAT_CONST(0.0); + for(m = 0; m < mmax; m++) { + for(size_t i = m; i < n; i += istep) { + j = i + mmax; + tempr = wr * real[j] - wi * imag[j]; + tempi = wr * imag[j] + wi * real[j]; + real[j] = real[i] - tempr; + imag[j] = imag[i] - tempi; + real[i] += tempr; + imag[i] += tempi; + } + wtemp = wr; + wr = wr*wpr - wi*wpi + wr; + wi = wi*wpr + wtemp*wpi + wi; + } + mmax = istep; + } +} + +mp_obj_t fft_fft_ifft_spectrogram(size_t n_args, mp_obj_t arg_re, mp_obj_t arg_im, uint8_t type) { + if(!mp_obj_is_type(arg_re, &ulab_ndarray_type)) { + mp_raise_NotImplementedError(translate("FFT is defined for ndarrays only")); + } + if(n_args == 2) { + if(!mp_obj_is_type(arg_im, &ulab_ndarray_type)) { + mp_raise_NotImplementedError(translate("FFT is defined for ndarrays only")); + } + } + ndarray_obj_t *re = MP_OBJ_TO_PTR(arg_re); + #if ULAB_MAX_DIMS > 1 + if(re->ndim != 1) { + COMPLEX_DTYPE_NOT_IMPLEMENTED(re->dtype) + mp_raise_TypeError(translate("FFT is implemented for linear arrays only")); + } + #endif + size_t len = re->len; + // Check if input is of length of power of 2 + if((len & (len-1)) != 0) { + mp_raise_ValueError(translate("input array length must be power of 2")); + } + + ndarray_obj_t *out_re = ndarray_new_linear_array(len, NDARRAY_FLOAT); + mp_float_t *data_re = (mp_float_t *)out_re->array; + + uint8_t *array = (uint8_t *)re->array; + mp_float_t (*func)(void *) = ndarray_get_float_function(re->dtype); + + for(size_t i=0; i < len; i++) { + *data_re++ = func(array); + array += re->strides[ULAB_MAX_DIMS - 1]; + } + data_re -= len; + ndarray_obj_t *out_im = ndarray_new_linear_array(len, NDARRAY_FLOAT); + mp_float_t *data_im = (mp_float_t *)out_im->array; + + if(n_args == 2) { + ndarray_obj_t *im = MP_OBJ_TO_PTR(arg_im); + #if ULAB_MAX_DIMS > 1 + if(im->ndim != 1) { + COMPLEX_DTYPE_NOT_IMPLEMENTED(im->dtype) + mp_raise_TypeError(translate("FFT is implemented for linear arrays only")); + } + #endif + if (re->len != im->len) { + mp_raise_ValueError(translate("real and imaginary parts must be of equal length")); + } + array = (uint8_t *)im->array; + func = ndarray_get_float_function(im->dtype); + for(size_t i=0; i < len; i++) { + *data_im++ = func(array); + array += im->strides[ULAB_MAX_DIMS - 1]; + } + data_im -= len; + } + + if((type == FFT_FFT) || (type == FFT_SPECTROGRAM)) { + fft_kernel(data_re, data_im, len, 1); + if(type == FFT_SPECTROGRAM) { + for(size_t i=0; i < len; i++) { + *data_re = MICROPY_FLOAT_C_FUN(sqrt)(*data_re * *data_re + *data_im * *data_im); + data_re++; + data_im++; + } + } + } else { // inverse transform + fft_kernel(data_re, data_im, len, -1); + // TODO: numpy accepts the norm keyword argument + for(size_t i=0; i < len; i++) { + *data_re++ /= len; + *data_im++ /= len; + } + } + if(type == FFT_SPECTROGRAM) { + return MP_OBJ_FROM_PTR(out_re); + } else { + mp_obj_t tuple[2]; + tuple[0] = MP_OBJ_FROM_PTR(out_re); + tuple[1] = MP_OBJ_FROM_PTR(out_im); + return mp_obj_new_tuple(2, tuple); + } +} +#endif /* ULAB_SUPPORTS_COMPLEX & ULAB_FFT_IS_NUMPY_COMPATIBLE */ diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/fft/fft_tools.h b/components/3rd_party/omv/omv/modules/ulab/code/numpy/fft/fft_tools.h new file mode 100644 index 00000000..9444232f --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/fft/fft_tools.h @@ -0,0 +1,28 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös +*/ + +#ifndef _FFT_TOOLS_ +#define _FFT_TOOLS_ + +enum FFT_TYPE { + FFT_FFT, + FFT_IFFT, + FFT_SPECTROGRAM, +}; + +#if ULAB_SUPPORTS_COMPLEX & ULAB_FFT_IS_NUMPY_COMPATIBLE +void fft_kernel(mp_float_t *, size_t , int ); +mp_obj_t fft_fft_ifft_spectrogram(mp_obj_t , uint8_t ); +#else +void fft_kernel(mp_float_t *, mp_float_t *, size_t , int ); +mp_obj_t fft_fft_ifft_spectrogram(size_t , mp_obj_t , mp_obj_t , uint8_t ); +#endif /* ULAB_SUPPORTS_COMPLEX & ULAB_FFT_IS_NUMPY_COMPATIBLE */ + +#endif /* _FFT_TOOLS_ */ diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/filter.c b/components/3rd_party/omv/omv/modules/ulab/code/numpy/filter.c new file mode 100644 index 00000000..92290388 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/filter.c @@ -0,0 +1,132 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2020 Scott Shawcroft for Adafruit Industries + * 2020-2021 Zoltán Vörös + * 2020 Taku Fukada +*/ + +#include +#include +#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" + +#include "../ulab.h" +#include "../scipy/signal/signal.h" +#include "carray/carray_tools.h" +#include "filter.h" + +#if ULAB_NUMPY_HAS_CONVOLVE + +mp_obj_t filter_convolve(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_a, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_v, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type) || !mp_obj_is_type(args[1].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("convolve arguments must be ndarrays")); + } + + ndarray_obj_t *a = MP_OBJ_TO_PTR(args[0].u_obj); + ndarray_obj_t *c = MP_OBJ_TO_PTR(args[1].u_obj); + // deal with linear arrays only + #if ULAB_MAX_DIMS > 1 + if((a->ndim != 1) || (c->ndim != 1)) { + mp_raise_TypeError(translate("convolve arguments must be linear arrays")); + } + #endif + size_t len_a = a->len; + size_t len_c = c->len; + if(len_a == 0 || len_c == 0) { + mp_raise_TypeError(translate("convolve arguments must not be empty")); + } + + int len = len_a + len_c - 1; // convolve mode "full" + int32_t off = len_c - 1; + uint8_t dtype = NDARRAY_FLOAT; + + #if ULAB_SUPPORTS_COMPLEX + if((a->dtype == NDARRAY_COMPLEX) || (c->dtype == NDARRAY_COMPLEX)) { + dtype = NDARRAY_COMPLEX; + } + #endif + ndarray_obj_t *ndarray = ndarray_new_linear_array(len, dtype); + mp_float_t *array = (mp_float_t *)ndarray->array; + + uint8_t *aarray = (uint8_t *)a->array; + uint8_t *carray = (uint8_t *)c->array; + + int32_t as = a->strides[ULAB_MAX_DIMS - 1] / a->itemsize; + int32_t cs = c->strides[ULAB_MAX_DIMS - 1] / c->itemsize; + + + #if ULAB_SUPPORTS_COMPLEX + if(dtype == NDARRAY_COMPLEX) { + mp_float_t a_real, a_imag; + mp_float_t c_real, c_imag = MICROPY_FLOAT_CONST(0.0); + for(int32_t k = -off; k < len-off; k++) { + mp_float_t accum_real = MICROPY_FLOAT_CONST(0.0); + mp_float_t accum_imag = MICROPY_FLOAT_CONST(0.0); + + int32_t top_n = MIN(len_c, len_a - k); + int32_t bot_n = MAX(-k, 0); + + for(int32_t n = bot_n; n < top_n; n++) { + int32_t idx_c = (len_c - n - 1) * cs; + int32_t idx_a = (n + k) * as; + if(a->dtype != NDARRAY_COMPLEX) { + a_real = ndarray_get_float_index(aarray, a->dtype, idx_a); + a_imag = MICROPY_FLOAT_CONST(0.0); + } else { + a_real = ndarray_get_float_index(aarray, NDARRAY_FLOAT, 2 * idx_a); + a_imag = ndarray_get_float_index(aarray, NDARRAY_FLOAT, 2 * idx_a + 1); + } + + if(c->dtype != NDARRAY_COMPLEX) { + c_real = ndarray_get_float_index(carray, c->dtype, idx_c); + c_imag = MICROPY_FLOAT_CONST(0.0); + } else { + c_real = ndarray_get_float_index(carray, NDARRAY_FLOAT, 2 * idx_c); + c_imag = ndarray_get_float_index(carray, NDARRAY_FLOAT, 2 * idx_c + 1); + } + accum_real += a_real * c_real - a_imag * c_imag; + accum_imag += a_real * c_imag + a_imag * c_real; + } + *array++ = accum_real; + *array++ = accum_imag; + } + return MP_OBJ_FROM_PTR(ndarray); + } + #endif + + for(int32_t k = -off; k < len-off; k++) { + mp_float_t accum = MICROPY_FLOAT_CONST(0.0); + int32_t top_n = MIN(len_c, len_a - k); + int32_t bot_n = MAX(-k, 0); + for(int32_t n = bot_n; n < top_n; n++) { + int32_t idx_c = (len_c - n - 1) * cs; + int32_t idx_a = (n + k) * as; + mp_float_t ai = ndarray_get_float_index(aarray, a->dtype, idx_a); + mp_float_t ci = ndarray_get_float_index(carray, c->dtype, idx_c); + accum += ai * ci; + } + *array++ = accum; + } + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(filter_convolve_obj, 2, filter_convolve); + +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/filter.h b/components/3rd_party/omv/omv/modules/ulab/code/numpy/filter.h new file mode 100644 index 00000000..d6d0f172 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/filter.h @@ -0,0 +1,20 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2020-2021 Zoltán Vörös +*/ + +#ifndef _FILTER_ +#define _FILTER_ + +#include "../ulab.h" +#include "../ndarray.h" + +MP_DECLARE_CONST_FUN_OBJ_KW(filter_convolve_obj); +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/io/io.c b/components/3rd_party/omv/omv/modules/ulab/code/numpy/io/io.c new file mode 100644 index 00000000..b9e5d367 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/io/io.c @@ -0,0 +1,817 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Zoltán Vörös +*/ + +#include +#include + +#include "py/builtin.h" +#include "py/formatfloat.h" +#include "py/obj.h" +#include "py/parsenum.h" +#include "py/runtime.h" +#include "py/stream.h" +#include "extmod/vfs.h" + +#include "../../ndarray.h" +#include "../../ulab_tools.h" +#include "io.h" + +#define ULAB_IO_BUFFER_SIZE 128 +#define ULAB_IO_CLIPBOARD_SIZE 32 +#define ULAB_IO_MAX_ROWS 65535 + +#define ULAB_IO_NULL_ENDIAN 0 +#define ULAB_IO_LITTLE_ENDIAN 1 +#define ULAB_IO_BIG_ENDIAN 2 + +#if ULAB_NUMPY_HAS_LOAD +static void io_read_(mp_obj_t stream, const mp_stream_p_t *stream_p, char *buffer, const char *string, uint16_t len, int *error) { + size_t read = stream_p->read(stream, buffer, len, error); + bool fail = false; + if(read == len) { + if(string != NULL) { + if(memcmp(buffer, string, len) != 0) { + fail = true; + } + } + } else { + fail = true; + } + if(fail) { + stream_p->ioctl(stream, MP_STREAM_CLOSE, 0, error); + mp_raise_msg(&mp_type_RuntimeError, translate("corrupted file")); + } +} + +static mp_obj_t io_load(mp_obj_t file) { + if(!mp_obj_is_str(file)) { + mp_raise_TypeError(translate("wrong input type")); + } + + int error; + char *buffer = m_new(char, ULAB_IO_BUFFER_SIZE); + + // test for endianness + uint16_t x = 1; + int8_t native_endianness = (x >> 8) == 1 ? ULAB_IO_BIG_ENDIAN : ULAB_IO_LITTLE_ENDIAN; + + mp_obj_t open_args[2] = { + file, + MP_OBJ_NEW_QSTR(MP_QSTR_rb) + }; + + mp_obj_t stream = mp_builtin_open_obj.fun.kw(2, open_args, (mp_map_t *)&mp_const_empty_map); + const mp_stream_p_t *stream_p = mp_get_stream(stream); + + // read header + // magic string + io_read_(stream, stream_p, buffer, "\x93NUMPY", 6, &error); + // simply discard the version number + io_read_(stream, stream_p, buffer, NULL, 2, &error); + // header length, represented as a little endian uint16 (0x76, 0x00) + io_read_(stream, stream_p, buffer, NULL, 2, &error); + + uint16_t header_length = buffer[1]; + header_length <<= 8; + header_length += buffer[0]; + + // beginning of the dictionary describing the array + io_read_(stream, stream_p, buffer, "{'descr': '", 11, &error); + uint8_t dtype; + + io_read_(stream, stream_p, buffer, NULL, 1, &error); + uint8_t endianness = ULAB_IO_NULL_ENDIAN; + if(*buffer == '<') { + endianness = ULAB_IO_LITTLE_ENDIAN; + } else if(*buffer == '>') { + endianness = ULAB_IO_BIG_ENDIAN; + } + + io_read_(stream, stream_p, buffer, NULL, 2, &error); + if(memcmp(buffer, "u1", 2) == 0) { + dtype = NDARRAY_UINT8; + } else if(memcmp(buffer, "i1", 2) == 0) { + dtype = NDARRAY_INT8; + } else if(memcmp(buffer, "u2", 2) == 0) { + dtype = NDARRAY_UINT16; + } else if(memcmp(buffer, "i2", 2) == 0) { + dtype = NDARRAY_INT16; + } + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + else if(memcmp(buffer, "f4", 2) == 0) { + dtype = NDARRAY_FLOAT; + } + #else + else if(memcmp(buffer, "f8", 2) == 0) { + dtype = NDARRAY_FLOAT; + } + #endif + #if ULAB_SUPPORTS_COMPLEX + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + else if(memcmp(buffer, "c8", 2) == 0) { + dtype = NDARRAY_COMPLEX; + } + #else + else if(memcmp(buffer, "c16", 3) == 0) { + dtype = NDARRAY_COMPLEX; + } + #endif + #endif /* ULAB_SUPPORT_COPMLEX */ + else { + stream_p->ioctl(stream, MP_STREAM_CLOSE, 0, &error); + mp_raise_TypeError(translate("wrong dtype")); + } + + io_read_(stream, stream_p, buffer, "', 'fortran_order': False, 'shape': (", 37, &error); + + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + + uint16_t bytes_to_read = MIN(ULAB_IO_BUFFER_SIZE, header_length - 51); + // bytes_to_read is 128 at most. This should be enough to contain a + // maximum of 4 size_t numbers plus the delimiters + io_read_(stream, stream_p, buffer, NULL, bytes_to_read, &error); + char *needle = buffer; + uint8_t ndim = 0; + + // find out the number of dimensions by counting the commas in the string + while(1) { + if(*needle == ',') { + ndim++; + if(needle[1] == ')') { + break; + } + } else if((*needle == ')') && (ndim > 0)) { + ndim++; + break; + } + needle++; + } + + needle = buffer; + for(uint8_t i = 0; i < ndim; i++) { + size_t number = 0; + // trivial number parsing here + while(1) { + if((*needle == ' ') || (*needle == '\t')) { + needle++; + } + if((*needle > 47) && (*needle < 58)) { + number = number * 10 + (*needle - 48); + } else if((*needle == ',') || (*needle == ')')) { + break; + } + else { + stream_p->ioctl(stream, MP_STREAM_CLOSE, 0, &error); + mp_raise_msg(&mp_type_RuntimeError, translate("corrupted file")); + } + needle++; + } + needle++; + shape[ULAB_MAX_DIMS - ndim + i] = number; + } + + // strip the rest of the header + if((bytes_to_read + 51) < header_length) { + io_read_(stream, stream_p, buffer, NULL, header_length - (bytes_to_read + 51), &error); + } + + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(ndim, shape, dtype); + char *array = (char *)ndarray->array; + + size_t read = stream_p->read(stream, array, ndarray->len * ndarray->itemsize, &error); + if(read != ndarray->len * ndarray->itemsize) { + stream_p->ioctl(stream, MP_STREAM_CLOSE, 0, &error); + mp_raise_msg(&mp_type_RuntimeError, translate("corrupted file")); + } + + stream_p->ioctl(stream, MP_STREAM_CLOSE, 0, &error); + m_del(char, buffer, ULAB_IO_BUFFER_SIZE); + + // swap the bytes, if necessary + if((native_endianness != endianness) && (dtype != NDARRAY_UINT8) && (dtype != NDARRAY_INT8)) { + uint8_t sz = ndarray->itemsize; + char *tmpbuff = NULL; + + #if ULAB_SUPPORTS_COMPLEX + if(dtype == NDARRAY_COMPLEX) { + // work with the floating point real and imaginary parts + sz /= 2; + tmpbuff = m_new(char, sz); + for(size_t i = 0; i < ndarray->len; i++) { + for(uint8_t k = 0; k < 2; k++) { + tmpbuff += sz; + for(uint8_t j = 0; j < sz; j++) { + memcpy(--tmpbuff, array++, 1); + } + memcpy(array-sz, tmpbuff, sz); + } + } + } else { + #endif + tmpbuff = m_new(char, sz); + for(size_t i = 0; i < ndarray->len; i++) { + tmpbuff += sz; + for(uint8_t j = 0; j < sz; j++) { + memcpy(--tmpbuff, array++, 1); + } + memcpy(array-sz, tmpbuff, sz); + } + #if ULAB_SUPPORTS_COMPLEX + } + #endif + m_del(char, tmpbuff, sz); + } + + m_del(size_t, shape, ULAB_MAX_DIMS); + + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_1(io_load_obj, io_load); +#endif /* ULAB_NUMPY_HAS_LOAD */ + +#if ULAB_NUMPY_HAS_LOADTXT +static void io_assign_value(const char *clipboard, uint8_t len, ndarray_obj_t *ndarray, size_t *idx, uint8_t dtype) { + mp_obj_t value = mp_parse_num_decimal(clipboard, len, false, false, NULL); + if(dtype != NDARRAY_FLOAT) { + mp_float_t _value = mp_obj_get_float(value); + value = mp_obj_new_int((int32_t)MICROPY_FLOAT_C_FUN(round)(_value)); + } + ndarray_set_value(dtype, ndarray->array, (*idx)++, value); +} + +static mp_obj_t io_loadtxt(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_delimiter, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_comments, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_max_rows, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = -1 } }, + { MP_QSTR_usecols, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = NDARRAY_FLOAT } }, + { MP_QSTR_skiprows, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 0 } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t open_args[2] = { + args[0].u_obj, + MP_OBJ_NEW_QSTR(MP_QSTR_r) + }; + + mp_obj_t stream = mp_builtin_open_obj.fun.kw(2, open_args, (mp_map_t *)&mp_const_empty_map); + const mp_stream_p_t *stream_p = mp_get_stream(stream); + + char *buffer = m_new(char, ULAB_IO_BUFFER_SIZE); + int error; + + char delimiter = ' '; + if(args[1].u_obj != mp_const_none) { + size_t _len; + char *_delimiter = m_new(char, 8); + _delimiter = (char *)mp_obj_str_get_data(args[1].u_obj, &_len); + delimiter = _delimiter[0]; + } + + char comment_char = '#'; + if(args[2].u_obj != mp_const_none) { + size_t _len; + char *_comment_char = m_new(char, 8); + _comment_char = (char *)mp_obj_str_get_data(args[2].u_obj, &_len); + comment_char = _comment_char[0]; + } + + uint16_t skiprows = args[6].u_int; + uint16_t max_rows = ULAB_IO_MAX_ROWS; + if((args[3].u_int > 0) && (args[3].u_int < ULAB_IO_MAX_ROWS)) { + max_rows = args[3].u_int + skiprows; + } + + uint16_t *cols = NULL; + uint8_t used_columns = 0; + if(args[4].u_obj != mp_const_none) { + if(mp_obj_is_int(args[4].u_obj)) { + used_columns = 1; + cols = m_new(uint16_t, used_columns); + cols[0] = (uint16_t)mp_obj_get_int(args[4].u_obj); + } else { + #if ULAB_MAX_DIMS == 1 + mp_raise_ValueError(translate("usecols keyword must be specified")); + #else + // assume that the argument is an iterable + used_columns = (uint16_t)mp_obj_get_int(mp_obj_len(args[4].u_obj)); + cols = m_new(uint16_t, used_columns); + mp_obj_iter_buf_t iter_buf; + mp_obj_t item, iterable = mp_getiter(args[4].u_obj, &iter_buf); + while((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + *cols++ = (uint16_t)mp_obj_get_int(item); + } + cols -= used_columns; + #endif + } + } + + uint8_t dtype = args[5].u_int; + + // count the columns and rows + // we actually count only the rows and the items, and assume that + // the number of columns can be gotten by means of a simple division, + // i.e., that each row has the same number of columns + char *offset; + uint16_t rows = 0, items = 0, all_rows = 0; + uint8_t read; + uint8_t len = 0; + + do { + read = (uint8_t)stream_p->read(stream, buffer, ULAB_IO_BUFFER_SIZE - 1, &error); + buffer[read] = '\0'; + offset = buffer; + while(*offset != '\0') { + if(*offset == comment_char) { + // clear the line till the end, or the buffer's end + while((*offset != '\0')) { + offset++; + if(*offset == '\n') { + offset++; + all_rows++; + break; + } + } + } + + // catch whitespaces here: if these are not on a comment line, then they delimit a number + if(*offset == '\n') { + all_rows++; + if(all_rows > skiprows) { + rows++; + items++; + len = 0; + } + if(all_rows == max_rows) { + break; + } + } + + if((*offset == ' ') || (*offset == '\t') || (*offset == '\v') || + (*offset == '\f') || (*offset == '\r') || (*offset == delimiter)) { + offset++; + while((*offset == ' ') || (*offset == '\t') || (*offset == '\v') || (*offset == '\f') || (*offset == '\r')) { + offset++; + } + if(len > 0) { + if(all_rows >= skiprows) { + items++; + } + len = 0; + } + } else { + offset++; + len++; + } + } + } while((read > 0) && (all_rows < max_rows)); + + if(rows == 0) { + mp_raise_ValueError(translate("empty file")); + } + uint16_t columns = items / rows; + + if(columns < used_columns) { + mp_raise_ValueError(translate("usecols is too high")); + } + + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + + #if ULAB_MAX_DIMS == 1 + shape[0] = rows; + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(1, shape, dtype); + #else + if(args[4].u_obj == mp_const_none) { + shape[ULAB_MAX_DIMS - 1] = columns; + } else { + shape[ULAB_MAX_DIMS - 1] = used_columns; + } + shape[ULAB_MAX_DIMS - 2] = rows; + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(2, shape, dtype); + #endif + + struct mp_stream_seek_t seek_s; + seek_s.offset = 0; + seek_s.whence = MP_SEEK_SET; + stream_p->ioctl(stream, MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &error); + + char *clipboard = m_new(char, ULAB_IO_CLIPBOARD_SIZE); + char *clipboard_origin = clipboard; + + rows = 0; + columns = 0; + len = 0; + + size_t idx = 0; + do { + read = stream_p->read(stream, buffer, ULAB_IO_BUFFER_SIZE - 1, &error); + buffer[read] = '\0'; + offset = buffer; + + while(*offset != '\0') { + if(*offset == comment_char) { + // clear the line till the end, or the buffer's end + while((*offset != '\0')) { + offset++; + if(*offset == '\n') { + rows++; + offset++; + break; + } + } + } + + if(rows == max_rows) { + break; + } + + if((*offset == ' ') || (*offset == '\t') || (*offset == '\v') || + (*offset == '\f') || (*offset == '\r') || (*offset == '\n') || (*offset == delimiter)) { + offset++; + while((*offset == ' ') || (*offset == '\t') || (*offset == '\v') || + (*offset == '\f') || (*offset == '\r') || (*offset == '\n')) { + offset++; + } + if(len > 0) { + clipboard = clipboard_origin; + if(rows >= skiprows) { + #if ULAB_MAX_DIMS == 1 + if(columns == cols[0]) { + io_assign_value(clipboard, len, ndarray, &idx, dtype); + } + #else + if(args[4].u_obj == mp_const_none) { + io_assign_value(clipboard, len, ndarray, &idx, dtype); + } else { + for(uint8_t c = 0; c < used_columns; c++) { + if(columns == cols[c]) { + io_assign_value(clipboard, len, ndarray, &idx, dtype); + break; + } + } + } + #endif + } + columns++; + len = 0; + + if(offset[-1] == '\n') { + columns = 0; + rows++; + } + } + } else { + *clipboard++ = *offset++; + len++; + } + } + } while((read > 0) && (rows < max_rows)); + + stream_p->ioctl(stream, MP_STREAM_CLOSE, 0, &error); + + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(char, buffer, ULAB_IO_BUFFER_SIZE); + m_del(char, clipboard, ULAB_IO_CLIPBOARD_SIZE); + m_del(uint16_t, cols, used_columns); + + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(io_loadtxt_obj, 1, io_loadtxt); +#endif /* ULAB_NUMPY_HAS_LOADTXT */ + + +#if ULAB_NUMPY_HAS_SAVE +static uint8_t io_sprintf(char *buffer, const char *comma, size_t x) { + uint8_t offset = 1; + char *buf = buffer; + // our own minimal implementation of sprintf for size_t types + // this is required on systems, where sprintf is not available + + // find out, how many characters are required + // we could call log10 here... + for(size_t i = 10; i < 100000000; i *= 10) { + if(x < i) { + break; + } + buf++; + } + + while(x > 0) { + uint8_t rem = x % 10; + *buf-- = '0' + rem; + x /= 10; + offset++; + } + + buf += offset; + while(*comma != '\0') { + *buf++ = *comma++; + offset++; + } + return offset - 1; +} + +static mp_obj_t io_save(mp_obj_t file, mp_obj_t ndarray_) { + if(!mp_obj_is_str(file) || !mp_obj_is_type(ndarray_, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("wrong input type")); + } + + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(ndarray_); + int error; + char *buffer = m_new(char, ULAB_IO_BUFFER_SIZE); + uint8_t offset = 0; + + // test for endianness + uint16_t x = 1; + int8_t native_endianness = (x >> 8) == 1 ? '>' : '<'; + + mp_obj_t open_args[2] = { + file, + MP_OBJ_NEW_QSTR(MP_QSTR_wb) + }; + + mp_obj_t stream = mp_builtin_open_obj.fun.kw(2, open_args, (mp_map_t *)&mp_const_empty_map); + const mp_stream_p_t *stream_p = mp_get_stream(stream); + + // write header; + // magic string + header length, which is always 128 - 10 = 118, represented as a little endian uint16 (0x76, 0x00) + // + beginning of the dictionary describing the array + memcpy(buffer, "\x93NUMPY\x01\x00\x76\x00{'descr': '", 21); + offset += 21; + + buffer[offset] = native_endianness; + if((ndarray->dtype == NDARRAY_UINT8) || (ndarray->dtype == NDARRAY_INT8)) { + // for single-byte data, the endianness doesn't matter + buffer[offset] = '|'; + } + offset++; + switch(ndarray->dtype) { + case NDARRAY_UINT8: + memcpy(buffer+offset, "u1", 2); + break; + case NDARRAY_INT8: + memcpy(buffer+offset, "i1", 2); + break; + case NDARRAY_UINT16: + memcpy(buffer+offset, "u2", 2); + break; + case NDARRAY_INT16: + memcpy(buffer+offset, "i2", 2); + break; + case NDARRAY_FLOAT: + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + memcpy(buffer+offset, "f4", 2); + #else + memcpy(buffer+offset, "f8", 2); + #endif + break; + #if ULAB_SUPPORTS_COMPLEX + case NDARRAY_COMPLEX: + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + memcpy(buffer+offset, "c8", 2); + #else + memcpy(buffer+offset, "c16", 3); + offset++; + #endif + break; + #endif + } + + offset += 2; + memcpy(buffer+offset, "', 'fortran_order': False, 'shape': (", 37); + offset += 37; + + if(ndarray->ndim == 1) { + offset += io_sprintf(buffer+offset, ",\0", ndarray->shape[ULAB_MAX_DIMS - 1]); + } else { + for(uint8_t i = ndarray->ndim; i > 1; i--) { + offset += io_sprintf(buffer+offset, ", \0", ndarray->shape[ULAB_MAX_DIMS - i]); + } + offset += io_sprintf(buffer+offset, "\0", ndarray->shape[ULAB_MAX_DIMS - 1]); + } + memcpy(buffer+offset, "), }", 4); + offset += 4; + // pad with space till the very end + memset(buffer+offset, 32, ULAB_IO_BUFFER_SIZE - offset - 1); + buffer[ULAB_IO_BUFFER_SIZE - 1] = '\n'; + stream_p->write(stream, buffer, ULAB_IO_BUFFER_SIZE, &error); + + // write the array data + uint8_t sz = ndarray->itemsize; + offset = 0; + + uint8_t *array = (uint8_t *)ndarray->array; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + memcpy(buffer+offset, array, sz); + offset += sz; + if(offset == ULAB_IO_BUFFER_SIZE) { + stream_p->write(stream, buffer, offset, &error); + offset = 0; + } + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < ndarray->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + array -= ndarray->strides[ULAB_MAX_DIMS - 1] * ndarray->shape[ULAB_MAX_DIMS-1]; + array += ndarray->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < ndarray->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + array -= ndarray->strides[ULAB_MAX_DIMS - 2] * ndarray->shape[ULAB_MAX_DIMS-2]; + array += ndarray->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < ndarray->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= ndarray->strides[ULAB_MAX_DIMS - 3] * ndarray->shape[ULAB_MAX_DIMS-3]; + array += ndarray->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < ndarray->shape[ULAB_MAX_DIMS - 4]); + #endif + + stream_p->write(stream, buffer, offset, &error); + stream_p->ioctl(stream, MP_STREAM_CLOSE, 0, &error); + + m_del(char, buffer, ULAB_IO_BUFFER_SIZE); + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_2(io_save_obj, io_save); +#endif /* ULAB_NUMPY_HAS_SAVE */ + +#if ULAB_NUMPY_HAS_SAVETXT +static int8_t io_format_float(ndarray_obj_t *ndarray, mp_float_t (*func)(void *), uint8_t *array, char *buffer, const char *delimiter) { + // own implementation of float formatting for platforms that don't have sprintf + int8_t offset = 0; + + #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT + #if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C + const int precision = 6; + #else + const int precision = 7; + #endif + #else + const int precision = 16; + #endif + + #if ULAB_SUPPORTS_COMPLEX + if(ndarray->dtype == NDARRAY_COMPLEX) { + mp_float_t real = func(array); + mp_float_t imag = func(array + ndarray->itemsize / 2); + offset = mp_format_float(real, buffer, ULAB_IO_BUFFER_SIZE, 'f', precision, 'j'); + if(imag >= MICROPY_FLOAT_CONST(0.0)) { + buffer[offset++] = '+'; + } else { + buffer[offset++] = '-'; + } + offset += mp_format_float(-imag, &buffer[offset], ULAB_IO_BUFFER_SIZE, 'f', precision, 'j'); + } + #endif + offset = (uint8_t)mp_format_float(func(array), buffer, ULAB_IO_BUFFER_SIZE, 'f', precision, '\0'); + + #if ULAB_SUPPORTS_COMPLEX + if(ndarray->dtype != NDARRAY_COMPLEX) { + // complexes end with a 'j', floats with a '\0', so we have to wind back by one character + offset--; + } + #endif + + while(*delimiter != '\0') { + buffer[offset++] = *delimiter++; + } + + return offset; +} + +static mp_obj_t io_savetxt(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_delimiter, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_header, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_footer, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_comments, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!mp_obj_is_str(args[0].u_obj) || !mp_obj_is_type(args[1].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("wrong input type")); + } + + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[1].u_obj); + + #if ULAB_MAX_DIMS > 2 + if(ndarray->ndim > 2) { + mp_raise_ValueError(translate("array has too many dimensions")); + } + #endif + + mp_obj_t open_args[2] = { + args[0].u_obj, + MP_OBJ_NEW_QSTR(MP_QSTR_w) + }; + + mp_obj_t stream = mp_builtin_open_obj.fun.kw(2, open_args, (mp_map_t *)&mp_const_empty_map); + const mp_stream_p_t *stream_p = mp_get_stream(stream); + + char *buffer = m_new(char, ULAB_IO_BUFFER_SIZE); + int error; + + if(mp_obj_is_str(args[3].u_obj)) { + size_t _len; + if(mp_obj_is_str(args[5].u_obj)) { + const char *comments = mp_obj_str_get_data(args[5].u_obj, &_len); + stream_p->write(stream, comments, _len, &error); + } else { + stream_p->write(stream, "# ", 2, &error); + } + const char *header = mp_obj_str_get_data(args[3].u_obj, &_len); + stream_p->write(stream, header, _len, &error); + stream_p->write(stream, "\n", 1, &error); + } + + uint8_t *array = (uint8_t *)ndarray->array; + mp_float_t (*func)(void *) = ndarray_get_float_function(ndarray->dtype); + char *delimiter = m_new(char, 8); + + if(ndarray->ndim == 1) { + delimiter[0] = '\n'; + delimiter[1] = '\0'; + } else if(args[2].u_obj == mp_const_none) { + delimiter[0] = ' '; + delimiter[1] = '\0'; + } else { + size_t delimiter_len; + delimiter = (char *)mp_obj_str_get_data(args[2].u_obj, &delimiter_len); + } + + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + int8_t chars = io_format_float(ndarray, func, array, buffer, l == ndarray->shape[ULAB_MAX_DIMS - 1] - 1 ? "\n" : delimiter); + if(chars > 0) { + stream_p->write(stream, buffer, chars, &error); + } + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < ndarray->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + array -= ndarray->strides[ULAB_MAX_DIMS - 1] * ndarray->shape[ULAB_MAX_DIMS-1]; + array += ndarray->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < ndarray->shape[ULAB_MAX_DIMS - 2]); + #endif + + if(mp_obj_is_str(args[4].u_obj)) { + size_t _len; + if(mp_obj_is_str(args[5].u_obj)) { + const char *comments = mp_obj_str_get_data(args[5].u_obj, &_len); + stream_p->write(stream, comments, _len, &error); + } else { + stream_p->write(stream, "# ", 2, &error); + } + const char *footer = mp_obj_str_get_data(args[4].u_obj, &_len); + stream_p->write(stream, footer, _len, &error); + stream_p->write(stream, "\n", 1, &error); + } + + stream_p->ioctl(stream, MP_STREAM_CLOSE, 0, &error); + + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(io_savetxt_obj, 2, io_savetxt); +#endif /* ULAB_NUMPY_HAS_SAVETXT */ diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/io/io.h b/components/3rd_party/omv/omv/modules/ulab/code/numpy/io/io.h new file mode 100644 index 00000000..33f1b687 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/io/io.h @@ -0,0 +1,19 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Zoltán Vörös +*/ + +#ifndef _ULAB_IO_ +#define _ULAB_IO_ + +MP_DECLARE_CONST_FUN_OBJ_1(io_load_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(io_loadtxt_obj); +MP_DECLARE_CONST_FUN_OBJ_2(io_save_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(io_savetxt_obj); + +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/linalg/linalg.c b/components/3rd_party/omv/omv/modules/ulab/code/numpy/linalg/linalg.c new file mode 100644 index 00000000..0243a5d9 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/linalg/linalg.c @@ -0,0 +1,546 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * 2020 Scott Shawcroft for Adafruit Industries + * 2020 Roberto Colistete Jr. + * 2020 Taku Fukada + * +*/ + +#include +#include +#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" + +#include "../../ulab.h" +#include "../../ulab_tools.h" +#include "../carray/carray_tools.h" +#include "linalg.h" + +#if ULAB_NUMPY_HAS_LINALG_MODULE +//| +//| import ulab.numpy +//| +//| """Linear algebra functions""" +//| + +#if ULAB_MAX_DIMS > 1 +//| def cholesky(A: ulab.numpy.ndarray) -> ulab.numpy.ndarray: +//| """ +//| :param ~ulab.numpy.ndarray A: a positive definite, symmetric square matrix +//| :return ~ulab.numpy.ndarray L: a square root matrix in the lower triangular form +//| :raises ValueError: If the input does not fulfill the necessary conditions +//| +//| The returned matrix satisfies the equation m=LL*""" +//| ... +//| + +static mp_obj_t linalg_cholesky(mp_obj_t oin) { + ndarray_obj_t *ndarray = tools_object_is_square(oin); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) + ndarray_obj_t *L = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, ndarray->shape[ULAB_MAX_DIMS - 1], ndarray->shape[ULAB_MAX_DIMS - 1]), NDARRAY_FLOAT); + mp_float_t *Larray = (mp_float_t *)L->array; + + size_t N = ndarray->shape[ULAB_MAX_DIMS - 1]; + uint8_t *array = (uint8_t *)ndarray->array; + mp_float_t (*func)(void *) = ndarray_get_float_function(ndarray->dtype); + + for(size_t m=0; m < N; m++) { // rows + for(size_t n=0; n < N; n++) { // columns + *Larray++ = func(array); + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + } + array -= ndarray->strides[ULAB_MAX_DIMS - 1] * N; + array += ndarray->strides[ULAB_MAX_DIMS - 2]; + } + Larray -= N*N; + // make sure the matrix is symmetric + for(size_t m=0; m < N; m++) { // rows + for(size_t n=m+1; n < N; n++) { // columns + // compare entry (m, n) to (n, m) + if(LINALG_EPSILON < MICROPY_FLOAT_C_FUN(fabs)(Larray[m * N + n] - Larray[n * N + m])) { + mp_raise_ValueError(translate("input matrix is asymmetric")); + } + } + } + + // this is actually not needed, but Cholesky in numpy returns the lower triangular matrix + for(size_t i=0; i < N; i++) { // rows + for(size_t j=i+1; j < N; j++) { // columns + Larray[i*N + j] = MICROPY_FLOAT_CONST(0.0); + } + } + mp_float_t sum = 0.0; + for(size_t i=0; i < N; i++) { // rows + for(size_t j=0; j <= i; j++) { // columns + sum = Larray[i * N + j]; + for(size_t k=0; k < j; k++) { + sum -= Larray[i * N + k] * Larray[j * N + k]; + } + if(i == j) { + if(sum <= MICROPY_FLOAT_CONST(0.0)) { + mp_raise_ValueError(translate("matrix is not positive definite")); + } else { + Larray[i * N + i] = MICROPY_FLOAT_C_FUN(sqrt)(sum); + } + } else { + Larray[i * N + j] = sum / Larray[j * N + j]; + } + } + } + return MP_OBJ_FROM_PTR(L); +} + +MP_DEFINE_CONST_FUN_OBJ_1(linalg_cholesky_obj, linalg_cholesky); + +//| def det(m: ulab.numpy.ndarray) -> float: +//| """ +//| :param: m, a square matrix +//| :return float: The determinant of the matrix +//| +//| Computes the eigenvalues and eigenvectors of a square matrix""" +//| ... +//| + +static mp_obj_t linalg_det(mp_obj_t oin) { + ndarray_obj_t *ndarray = tools_object_is_square(oin); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) + uint8_t *array = (uint8_t *)ndarray->array; + size_t N = ndarray->shape[ULAB_MAX_DIMS - 1]; + mp_float_t *tmp = m_new(mp_float_t, N * N); + for(size_t m=0; m < N; m++) { // rows + for(size_t n=0; n < N; n++) { // columns + *tmp++ = ndarray_get_float_value(array, ndarray->dtype); + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + } + array -= ndarray->strides[ULAB_MAX_DIMS - 1] * N; + array += ndarray->strides[ULAB_MAX_DIMS - 2]; + } + + // re-wind the pointer + tmp -= N*N; + + mp_float_t c; + mp_float_t det_sign = 1.0; + + for(size_t m=0; m < N-1; m++){ + if(MICROPY_FLOAT_C_FUN(fabs)(tmp[m * (N+1)]) < LINALG_EPSILON) { + size_t m1 = m + 1; + for(; m1 < N; m1++) { + if(!(MICROPY_FLOAT_C_FUN(fabs)(tmp[m1*N+m]) < LINALG_EPSILON)) { + //look for a line to swap + for(size_t m2=0; m2 < N; m2++) { + mp_float_t swapVal = tmp[m*N+m2]; + tmp[m*N+m2] = tmp[m1*N+m2]; + tmp[m1*N+m2] = swapVal; + } + det_sign = -det_sign; + break; + } + } + if (m1 >= N) { + m_del(mp_float_t, tmp, N * N); + return mp_obj_new_float(0.0); + } + } + for(size_t n=0; n < N; n++) { + if(m != n) { + c = tmp[N * n + m] / tmp[m * (N+1)]; + for(size_t k=0; k < N; k++){ + tmp[N * n + k] -= c * tmp[N * m + k]; + } + } + } + } + mp_float_t det = det_sign; + + for(size_t m=0; m < N; m++){ + det *= tmp[m * (N+1)]; + } + m_del(mp_float_t, tmp, N * N); + return mp_obj_new_float(det); +} + +MP_DEFINE_CONST_FUN_OBJ_1(linalg_det_obj, linalg_det); + +#endif + +#if ULAB_MAX_DIMS > 1 +//| def eig(m: ulab.numpy.ndarray) -> Tuple[ulab.numpy.ndarray, ulab.numpy.ndarray]: +//| """ +//| :param m: a square matrix +//| :return tuple (eigenvectors, eigenvalues): +//| +//| Computes the eigenvalues and eigenvectors of a square matrix""" +//| ... +//| + +static mp_obj_t linalg_eig(mp_obj_t oin) { + ndarray_obj_t *in = tools_object_is_square(oin); + COMPLEX_DTYPE_NOT_IMPLEMENTED(in->dtype) + uint8_t *iarray = (uint8_t *)in->array; + size_t S = in->shape[ULAB_MAX_DIMS - 1]; + mp_float_t *array = m_new(mp_float_t, S*S); + for(size_t i=0; i < S; i++) { // rows + for(size_t j=0; j < S; j++) { // columns + *array++ = ndarray_get_float_value(iarray, in->dtype); + iarray += in->strides[ULAB_MAX_DIMS - 1]; + } + iarray -= in->strides[ULAB_MAX_DIMS - 1] * S; + iarray += in->strides[ULAB_MAX_DIMS - 2]; + } + array -= S * S; + // make sure the matrix is symmetric + for(size_t m=0; m < S; m++) { + for(size_t n=m+1; n < S; n++) { + // compare entry (m, n) to (n, m) + // TODO: this must probably be scaled! + if(LINALG_EPSILON < MICROPY_FLOAT_C_FUN(fabs)(array[m * S + n] - array[n * S + m])) { + mp_raise_ValueError(translate("input matrix is asymmetric")); + } + } + } + + // if we got this far, then the matrix will be symmetric + + ndarray_obj_t *eigenvectors = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, S, S), NDARRAY_FLOAT); + mp_float_t *eigvectors = (mp_float_t *)eigenvectors->array; + + size_t iterations = linalg_jacobi_rotations(array, eigvectors, S); + + if(iterations == 0) { + // the computation did not converge; numpy raises LinAlgError + m_del(mp_float_t, array, in->len); + mp_raise_ValueError(translate("iterations did not converge")); + } + ndarray_obj_t *eigenvalues = ndarray_new_linear_array(S, NDARRAY_FLOAT); + mp_float_t *eigvalues = (mp_float_t *)eigenvalues->array; + for(size_t i=0; i < S; i++) { + eigvalues[i] = array[i * (S + 1)]; + } + m_del(mp_float_t, array, in->len); + + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); + tuple->items[0] = MP_OBJ_FROM_PTR(eigenvalues); + tuple->items[1] = MP_OBJ_FROM_PTR(eigenvectors); + return MP_OBJ_FROM_PTR(tuple); +} + +MP_DEFINE_CONST_FUN_OBJ_1(linalg_eig_obj, linalg_eig); + +//| def inv(m: ulab.numpy.ndarray) -> ulab.numpy.ndarray: +//| """ +//| :param ~ulab.numpy.ndarray m: a square matrix +//| :return: The inverse of the matrix, if it exists +//| :raises ValueError: if the matrix is not invertible +//| +//| Computes the inverse of a square matrix""" +//| ... +//| +static mp_obj_t linalg_inv(mp_obj_t o_in) { + ndarray_obj_t *ndarray = tools_object_is_square(o_in); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) + uint8_t *array = (uint8_t *)ndarray->array; + size_t N = ndarray->shape[ULAB_MAX_DIMS - 1]; + ndarray_obj_t *inverted = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, N, N), NDARRAY_FLOAT); + mp_float_t *iarray = (mp_float_t *)inverted->array; + + mp_float_t (*func)(void *) = ndarray_get_float_function(ndarray->dtype); + + for(size_t i=0; i < N; i++) { // rows + for(size_t j=0; j < N; j++) { // columns + *iarray++ = func(array); + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + } + array -= ndarray->strides[ULAB_MAX_DIMS - 1] * N; + array += ndarray->strides[ULAB_MAX_DIMS - 2]; + } + // re-wind the pointer + iarray -= N*N; + + if(!linalg_invert_matrix(iarray, N)) { + mp_raise_ValueError(translate("input matrix is singular")); + } + return MP_OBJ_FROM_PTR(inverted); +} + +MP_DEFINE_CONST_FUN_OBJ_1(linalg_inv_obj, linalg_inv); +#endif + +//| def norm(x: ulab.numpy.ndarray) -> float: +//| """ +//| :param ~ulab.numpy.ndarray x: a vector or a matrix +//| +//| Computes the 2-norm of a vector or a matrix, i.e., ``sqrt(sum(x*x))``, however, without the RAM overhead.""" +//| ... +//| + +static mp_obj_t linalg_norm(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE} } , + { MP_QSTR_axis, MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t x = args[0].u_obj; + mp_obj_t axis = args[1].u_obj; + + mp_float_t dot = 0.0, value; + size_t count = 1; + + if(mp_obj_is_type(x, &mp_type_tuple) || mp_obj_is_type(x, &mp_type_list) || mp_obj_is_type(x, &mp_type_range)) { + mp_obj_iter_buf_t iter_buf; + mp_obj_t item, iterable = mp_getiter(x, &iter_buf); + while((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + value = mp_obj_get_float(item); + // we could simply take the sum of value ** 2, + // but this method is numerically stable + dot = dot + (value * value - dot) / count++; + } + return mp_obj_new_float(MICROPY_FLOAT_C_FUN(sqrt)(dot * (count - 1))); + } else if(mp_obj_is_type(x, &ulab_ndarray_type)) { + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(x); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) + uint8_t *array = (uint8_t *)ndarray->array; + // always get a float, so that we don't have to resolve the dtype later + mp_float_t (*func)(void *) = ndarray_get_float_function(ndarray->dtype); + shape_strides _shape_strides = tools_reduce_axes(ndarray, axis); + ndarray_obj_t *results = ndarray_new_dense_ndarray(_shape_strides.ndim, _shape_strides.shape, NDARRAY_FLOAT); + mp_float_t *rarray = (mp_float_t *)results->array; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + if(axis != mp_const_none) { + count = 1; + dot = 0.0; + } + do { + value = func(array); + dot = dot + (value * value - dot) / count++; + array += _shape_strides.strides[0]; + l++; + } while(l < _shape_strides.shape[0]); + *rarray = MICROPY_FLOAT_C_FUN(sqrt)(dot * (count - 1)); + #if ULAB_MAX_DIMS > 1 + rarray += _shape_strides.increment; + array -= _shape_strides.strides[0] * _shape_strides.shape[0]; + array += _shape_strides.strides[ULAB_MAX_DIMS - 1]; + k++; + } while(k < _shape_strides.shape[ULAB_MAX_DIMS - 1]); + #endif + #if ULAB_MAX_DIMS > 2 + array -= _shape_strides.strides[ULAB_MAX_DIMS - 1] * _shape_strides.shape[ULAB_MAX_DIMS - 1]; + array += _shape_strides.strides[ULAB_MAX_DIMS - 2]; + j++; + } while(j < _shape_strides.shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= _shape_strides.strides[ULAB_MAX_DIMS - 2] * _shape_strides.shape[ULAB_MAX_DIMS - 2]; + array += _shape_strides.strides[ULAB_MAX_DIMS - 3]; + i++; + } while(i < _shape_strides.shape[ULAB_MAX_DIMS - 3]); + #endif + if(results->ndim == 0) { + return mp_obj_new_float(*rarray); + } + return MP_OBJ_FROM_PTR(results); + } + return mp_const_none; // we should never reach this point +} + +MP_DEFINE_CONST_FUN_OBJ_KW(linalg_norm_obj, 1, linalg_norm); + +#if ULAB_MAX_DIMS > 1 +//| def qr(m: ulab.numpy.ndarray) -> Tuple[ulab.numpy.ndarray, ulab.numpy.ndarray]: +//| """ +//| :param m: a matrix +//| :return tuple (Q, R): +//| +//| Factor the matrix a as QR, where Q is orthonormal and R is upper-triangular. +//| """ +//| ... +//| + +static mp_obj_t linalg_qr(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_mode, MP_ARG_OBJ, { .u_rom_obj = MP_ROM_QSTR(MP_QSTR_reduced) } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("operation is defined for ndarrays only")); + } + ndarray_obj_t *source = MP_OBJ_TO_PTR(args[0].u_obj); + if(source->ndim != 2) { + mp_raise_ValueError(translate("operation is defined for 2D arrays only")); + } + + size_t m = source->shape[ULAB_MAX_DIMS - 2]; // rows + size_t n = source->shape[ULAB_MAX_DIMS - 1]; // columns + + ndarray_obj_t *Q = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, m, m), NDARRAY_FLOAT); + ndarray_obj_t *R = ndarray_new_dense_ndarray(2, source->shape, NDARRAY_FLOAT); + + mp_float_t *qarray = (mp_float_t *)Q->array; + mp_float_t *rarray = (mp_float_t *)R->array; + + // simply copy the entries of source to a float array + mp_float_t (*func)(void *) = ndarray_get_float_function(source->dtype); + uint8_t *sarray = (uint8_t *)source->array; + + for(size_t i = 0; i < m; i++) { + for(size_t j = 0; j < n; j++) { + *rarray++ = func(sarray); + sarray += source->strides[ULAB_MAX_DIMS - 1]; + } + sarray -= n * source->strides[ULAB_MAX_DIMS - 1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + } + rarray -= m * n; + + // start with the unit matrix + for(size_t i = 0; i < m; i++) { + qarray[i * (m + 1)] = 1.0; + } + + for(size_t j = 0; j < n; j++) { // columns + for(size_t i = m - 1; i > j; i--) { // rows + mp_float_t c, s; + // Givens matrix: note that numpy uses a strange form of the rotation + // [[c s], + // [s -c]] + if(MICROPY_FLOAT_C_FUN(fabs)(rarray[i * n + j]) < LINALG_EPSILON) { // r[i, j] + c = (rarray[(i - 1) * n + j] >= MICROPY_FLOAT_CONST(0.0)) ? MICROPY_FLOAT_CONST(1.0) : MICROPY_FLOAT_CONST(-1.0); // r[i-1, j] + s = 0.0; + } else if(MICROPY_FLOAT_C_FUN(fabs)(rarray[(i - 1) * n + j]) < LINALG_EPSILON) { // r[i-1, j] + c = 0.0; + s = (rarray[i * n + j] >= MICROPY_FLOAT_CONST(0.0)) ? MICROPY_FLOAT_CONST(-1.0) : MICROPY_FLOAT_CONST(1.0); // r[i, j] + } else { + mp_float_t t, u; + if(MICROPY_FLOAT_C_FUN(fabs)(rarray[(i - 1) * n + j]) > MICROPY_FLOAT_C_FUN(fabs)(rarray[i * n + j])) { // r[i-1, j], r[i, j] + t = rarray[i * n + j] / rarray[(i - 1) * n + j]; // r[i, j]/r[i-1, j] + u = MICROPY_FLOAT_C_FUN(sqrt)(1 + t * t); + c = MICROPY_FLOAT_CONST(-1.0) / u; + s = c * t; + } else { + t = rarray[(i - 1) * n + j] / rarray[i * n + j]; // r[i-1, j]/r[i, j] + u = MICROPY_FLOAT_C_FUN(sqrt)(1 + t * t); + s = MICROPY_FLOAT_CONST(-1.0) / u; + c = s * t; + } + } + + mp_float_t r1, r2; + // update R: multiply with the rotation matrix from the left + for(size_t k = 0; k < n; k++) { + r1 = rarray[(i - 1) * n + k]; // r[i-1, k] + r2 = rarray[i * n + k]; // r[i, k] + rarray[(i - 1) * n + k] = c * r1 + s * r2; // r[i-1, k] + rarray[i * n + k] = s * r1 - c * r2; // r[i, k] + } + + // update Q: multiply with the transpose of the rotation matrix from the right + for(size_t k = 0; k < m; k++) { + r1 = qarray[k * m + (i - 1)]; + r2 = qarray[k * m + i]; + qarray[k * m + (i - 1)] = c * r1 + s * r2; + qarray[k * m + i] = s * r1 - c * r2; + } + } + } + + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); + GET_STR_DATA_LEN(args[1].u_obj, mode, len); + if(memcmp(mode, "complete", 8) == 0) { + tuple->items[0] = MP_OBJ_FROM_PTR(Q); + tuple->items[1] = MP_OBJ_FROM_PTR(R); + } else if(memcmp(mode, "reduced", 7) == 0) { + size_t k = MAX(m, n) - MIN(m, n); + ndarray_obj_t *q = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, m, m - k), NDARRAY_FLOAT); + ndarray_obj_t *r = ndarray_new_dense_ndarray(2, ndarray_shape_vector(0, 0, m - k, n), NDARRAY_FLOAT); + mp_float_t *qa = (mp_float_t *)q->array; + mp_float_t *ra = (mp_float_t *)r->array; + for(size_t i = 0; i < m; i++) { + memcpy(qa, qarray, (m - k) * q->itemsize); + qa += (m - k); + qarray += m; + } + for(size_t i = 0; i < m - k; i++) { + memcpy(ra, rarray, n * r->itemsize); + ra += n; + rarray += n; + } + tuple->items[0] = MP_OBJ_FROM_PTR(q); + tuple->items[1] = MP_OBJ_FROM_PTR(r); + } else { + mp_raise_ValueError(translate("mode must be complete, or reduced")); + } + return MP_OBJ_FROM_PTR(tuple); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(linalg_qr_obj, 1, linalg_qr); +#endif + +STATIC const mp_rom_map_elem_t ulab_linalg_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_linalg) }, + #if ULAB_MAX_DIMS > 1 + #if ULAB_LINALG_HAS_CHOLESKY + { MP_ROM_QSTR(MP_QSTR_cholesky), MP_ROM_PTR(&linalg_cholesky_obj) }, + #endif + #if ULAB_LINALG_HAS_DET + { MP_ROM_QSTR(MP_QSTR_det), MP_ROM_PTR(&linalg_det_obj) }, + #endif + #if ULAB_LINALG_HAS_EIG + { MP_ROM_QSTR(MP_QSTR_eig), MP_ROM_PTR(&linalg_eig_obj) }, + #endif + #if ULAB_LINALG_HAS_INV + { MP_ROM_QSTR(MP_QSTR_inv), MP_ROM_PTR(&linalg_inv_obj) }, + #endif + #if ULAB_LINALG_HAS_QR + { MP_ROM_QSTR(MP_QSTR_qr), MP_ROM_PTR(&linalg_qr_obj) }, + #endif + #endif + #if ULAB_LINALG_HAS_NORM + { MP_ROM_QSTR(MP_QSTR_norm), MP_ROM_PTR(&linalg_norm_obj) }, + #endif +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_ulab_linalg_globals, ulab_linalg_globals_table); + +const mp_obj_module_t ulab_linalg_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_linalg_globals, +}; +#if CIRCUITPY_ULAB +#if !defined(MICROPY_VERSION) || MICROPY_VERSION <= 70144 +MP_REGISTER_MODULE(MP_QSTR_ulab_dot_numpy_dot_linalg, ulab_linalg_module, MODULE_ULAB_ENABLED); +#else +MP_REGISTER_MODULE(MP_QSTR_ulab_dot_numpy_dot_linalg, ulab_linalg_module); +#endif +#endif +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/linalg/linalg.h b/components/3rd_party/omv/omv/modules/ulab/code/numpy/linalg/linalg.h new file mode 100644 index 00000000..35fc4035 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/linalg/linalg.h @@ -0,0 +1,27 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös +*/ + +#ifndef _LINALG_ +#define _LINALG_ + +#include "../../ulab.h" +#include "../../ndarray.h" +#include "linalg_tools.h" + +extern const mp_obj_module_t ulab_linalg_module; + +MP_DECLARE_CONST_FUN_OBJ_1(linalg_cholesky_obj); +MP_DECLARE_CONST_FUN_OBJ_1(linalg_det_obj); +MP_DECLARE_CONST_FUN_OBJ_1(linalg_eig_obj); +MP_DECLARE_CONST_FUN_OBJ_1(linalg_inv_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(linalg_norm_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(linalg_qr_obj); +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/linalg/linalg_tools.c b/components/3rd_party/omv/omv/modules/ulab/code/numpy/linalg/linalg_tools.c new file mode 100644 index 00000000..7ae97d21 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/linalg/linalg_tools.c @@ -0,0 +1,170 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2010 Zoltán Vörös +*/ + +#include +#include +#include "py/runtime.h" + +#include "linalg_tools.h" + +/* + * The following function inverts a matrix, whose entries are given in the input array + * The function has no dependencies beyond micropython itself (for the definition of mp_float_t), + * and can be used independent of ulab. + */ + +bool linalg_invert_matrix(mp_float_t *data, size_t N) { + // returns true, of the inversion was successful, + // false, if the matrix is singular + + // initially, this is the unit matrix: the contents of this matrix is what + // will be returned after all the transformations + mp_float_t *unit = m_new0(mp_float_t, N*N); + mp_float_t elem = 1.0; + + for(size_t m=0; m < N; m++) { + memcpy(&unit[m * (N+1)], &elem, sizeof(mp_float_t)); + } + for(size_t m=0; m < N; m++){ + // this could be faster with ((c < epsilon) && (c > -epsilon)) + if(MICROPY_FLOAT_C_FUN(fabs)(data[m * (N+1)]) < LINALG_EPSILON) { + //look for a line to swap + size_t m1 = m + 1; + for(; m1 < N; m1++) { + if(!(MICROPY_FLOAT_C_FUN(fabs)(data[m1*N + m]) < LINALG_EPSILON)) { + for(size_t m2=0; m2 < N; m2++) { + mp_float_t swapVal = data[m*N+m2]; + data[m*N+m2] = data[m1*N+m2]; + data[m1*N+m2] = swapVal; + swapVal = unit[m*N+m2]; + unit[m*N+m2] = unit[m1*N+m2]; + unit[m1*N+m2] = swapVal; + } + break; + } + } + if (m1 >= N) { + m_del(mp_float_t, unit, N*N); + return false; + } + } + for(size_t n=0; n < N; n++) { + if(m != n){ + elem = data[N * n + m] / data[m * (N+1)]; + for(size_t k=0; k < N; k++) { + data[N * n + k] -= elem * data[N * m + k]; + unit[N * n + k] -= elem * unit[N * m + k]; + } + } + } + } + for(size_t m=0; m < N; m++) { + elem = data[m * (N+1)]; + for(size_t n=0; n < N; n++) { + data[N * m + n] /= elem; + unit[N * m + n] /= elem; + } + } + memcpy(data, unit, sizeof(mp_float_t)*N*N); + m_del(mp_float_t, unit, N * N); + return true; +} + +/* + * The following function calculates the eigenvalues and eigenvectors of a symmetric + * real matrix, whose entries are given in the input array. + * The function has no dependencies beyond micropython itself (for the definition of mp_float_t), + * and can be used independent of ulab. + */ + +size_t linalg_jacobi_rotations(mp_float_t *array, mp_float_t *eigvectors, size_t S) { + // eigvectors should be a 0-array; start out with the unit matrix + for(size_t m=0; m < S; m++) { + eigvectors[m * (S+1)] = 1.0; + } + mp_float_t largest, w, t, c, s, tau, aMk, aNk, vm, vn; + size_t M, N; + size_t iterations = JACOBI_MAX * S * S; + do { + iterations--; + // find the pivot here + M = 0; + N = 0; + largest = 0.0; + for(size_t m=0; m < S-1; m++) { // -1: no need to inspect last row + for(size_t n=m+1; n < S; n++) { + w = MICROPY_FLOAT_C_FUN(fabs)(array[m * S + n]); + if((largest < w) && (LINALG_EPSILON < w)) { + M = m; + N = n; + largest = w; + } + } + } + if(M + N == 0) { // all entries are smaller than epsilon, there is not much we can do... + break; + } + // at this point, we have the pivot, and it is the entry (M, N) + // now we have to find the rotation angle + w = (array[N * S + N] - array[M * S + M]) / (MICROPY_FLOAT_CONST(2.0)*array[M * S + N]); + // The following if/else chooses the smaller absolute value for the tangent + // of the rotation angle. Going with the smaller should be numerically stabler. + if(w > 0) { + t = MICROPY_FLOAT_C_FUN(sqrt)(w*w + MICROPY_FLOAT_CONST(1.0)) - w; + } else { + t = MICROPY_FLOAT_CONST(-1.0)*(MICROPY_FLOAT_C_FUN(sqrt)(w*w + MICROPY_FLOAT_CONST(1.0)) + w); + } + s = t / MICROPY_FLOAT_C_FUN(sqrt)(t*t + MICROPY_FLOAT_CONST(1.0)); // the sine of the rotation angle + c = MICROPY_FLOAT_CONST(1.0) / MICROPY_FLOAT_C_FUN(sqrt)(t*t + MICROPY_FLOAT_CONST(1.0)); // the cosine of the rotation angle + tau = (MICROPY_FLOAT_CONST(1.0)-c)/s; // this is equal to the tangent of the half of the rotation angle + + // at this point, we have the rotation angles, so we can transform the matrix + // first the two diagonal elements + // a(M, M) = a(M, M) - t*a(M, N) + array[M * S + M] = array[M * S + M] - t * array[M * S + N]; + // a(N, N) = a(N, N) + t*a(M, N) + array[N * S + N] = array[N * S + N] + t * array[M * S + N]; + // after the rotation, the a(M, N), and a(N, M) entries should become zero + array[M * S + N] = array[N * S + M] = MICROPY_FLOAT_CONST(0.0); + // then all other elements in the column + for(size_t k=0; k < S; k++) { + if((k == M) || (k == N)) { + continue; + } + aMk = array[M * S + k]; + aNk = array[N * S + k]; + // a(M, k) = a(M, k) - s*(a(N, k) + tau*a(M, k)) + array[M * S + k] -= s * (aNk + tau * aMk); + // a(N, k) = a(N, k) + s*(a(M, k) - tau*a(N, k)) + array[N * S + k] += s * (aMk - tau * aNk); + // a(k, M) = a(M, k) + array[k * S + M] = array[M * S + k]; + // a(k, N) = a(N, k) + array[k * S + N] = array[N * S + k]; + } + // now we have to update the eigenvectors + // the rotation matrix, R, multiplies from the right + // R is the unit matrix, except for the + // R(M,M) = R(N, N) = c + // R(N, M) = s + // (M, N) = -s + // entries. This means that only the Mth, and Nth columns will change + for(size_t m=0; m < S; m++) { + vm = eigvectors[m * S + M]; + vn = eigvectors[m * S + N]; + // the new value of eigvectors(m, M) + eigvectors[m * S + M] = c * vm - s * vn; + // the new value of eigvectors(m, N) + eigvectors[m * S + N] = s * vm + c * vn; + } + } while(iterations > 0); + + return iterations; +} diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/linalg/linalg_tools.h b/components/3rd_party/omv/omv/modules/ulab/code/numpy/linalg/linalg_tools.h new file mode 100644 index 00000000..942da001 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/linalg/linalg_tools.h @@ -0,0 +1,28 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös +*/ + +#ifndef _TOOLS_TOOLS_ +#define _TOOLS_TOOLS_ + +#ifndef LINALG_EPSILON +#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT +#define LINALG_EPSILON MICROPY_FLOAT_CONST(1.2e-7) +#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE +#define LINALG_EPSILON MICROPY_FLOAT_CONST(2.3e-16) +#endif +#endif /* LINALG_EPSILON */ + +#define JACOBI_MAX 20 + +bool linalg_invert_matrix(mp_float_t *, size_t ); +size_t linalg_jacobi_rotations(mp_float_t *, mp_float_t *, size_t ); + +#endif /* _TOOLS_TOOLS_ */ + diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/ndarray/ndarray_iter.c b/components/3rd_party/omv/omv/modules/ulab/code/numpy/ndarray/ndarray_iter.c new file mode 100644 index 00000000..423e4a05 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/ndarray/ndarray_iter.c @@ -0,0 +1,66 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Zoltán Vörös + * +*/ + +#include +#include +#include +#include +#include "py/obj.h" +#include "py/runtime.h" + +#include "ndarray_iter.h" + +#ifdef NDARRAY_HAS_FLATITER +mp_obj_t ndarray_flatiter_make_new(mp_obj_t self_in) { + ndarray_flatiter_t *flatiter = m_new_obj(ndarray_flatiter_t); + flatiter->base.type = &ndarray_flatiter_type; + flatiter->iternext = ndarray_flatiter_next; + flatiter->ndarray = self_in; + flatiter->cur = 0; + return MP_OBJ_FROM_PTR(flatiter); +} + +mp_obj_t ndarray_flatiter_next(mp_obj_t self_in) { + ndarray_flatiter_t *self = MP_OBJ_TO_PTR(self_in); + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(self->ndarray); + uint8_t *array = (uint8_t *)ndarray->array; + + if(self->cur < ndarray->len) { + uint32_t remainder = self->cur; + uint8_t i = ULAB_MAX_DIMS - 1; + do { + size_t div = (remainder / ndarray->shape[i]); + array += remainder * ndarray->strides[i]; + remainder -= div * ndarray->shape[i]; + i--; + } while(i > ULAB_MAX_DIMS - ndarray->ndim); + self->cur++; + return ndarray_get_item(ndarray, array); + } + return MP_OBJ_STOP_ITERATION; +} + +mp_obj_t ndarray_new_flatiterator(mp_obj_t flatiter_in, mp_obj_iter_buf_t *iter_buf) { + assert(sizeof(ndarray_flatiter_t) <= sizeof(mp_obj_iter_buf_t)); + ndarray_flatiter_t *iter = (ndarray_flatiter_t *)iter_buf; + ndarray_flatiter_t *flatiter = MP_OBJ_TO_PTR(flatiter_in); + iter->base.type = &mp_type_polymorph_iter; + iter->iternext = ndarray_flatiter_next; + iter->ndarray = flatiter->ndarray; + iter->cur = 0; + return MP_OBJ_FROM_PTR(iter); +} + +mp_obj_t ndarray_get_flatiterator(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) { + return ndarray_new_flatiterator(o_in, iter_buf); +} +#endif /* NDARRAY_HAS_FLATITER */ diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/ndarray/ndarray_iter.h b/components/3rd_party/omv/omv/modules/ulab/code/numpy/ndarray/ndarray_iter.h new file mode 100644 index 00000000..f740f416 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/ndarray/ndarray_iter.h @@ -0,0 +1,36 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2020-2021 Zoltán Vörös +*/ + +#ifndef _NDARRAY_ITER_ +#define _NDARRAY_ITER_ + +#include "py/runtime.h" +#include "py/binary.h" +#include "py/obj.h" +#include "py/objarray.h" + +#include "../../ulab.h" +#include "../../ndarray.h" + +// TODO: take simply mp_obj_ndarray_it_t from ndarray.c +typedef struct _mp_obj_ndarray_flatiter_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + mp_obj_t ndarray; + size_t cur; +} ndarray_flatiter_t; + +mp_obj_t ndarray_get_flatiterator(mp_obj_t , mp_obj_iter_buf_t *); +mp_obj_t ndarray_flatiter_make_new(mp_obj_t ); +mp_obj_t ndarray_flatiter_next(mp_obj_t ); + +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/numerical.c b/components/3rd_party/omv/omv/modules/ulab/code/numpy/numerical.c new file mode 100644 index 00000000..01bd0bd9 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/numerical.c @@ -0,0 +1,1417 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * 2020 Scott Shawcroft for Adafruit Industries + * 2020 Taku Fukada +*/ + +#include +#include +#include +#include "py/obj.h" +#include "py/objint.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/misc.h" + +#include "../ulab.h" +#include "../ulab_tools.h" +#include "./carray/carray_tools.h" +#include "numerical.h" + +enum NUMERICAL_FUNCTION_TYPE { + NUMERICAL_ALL, + NUMERICAL_ANY, + NUMERICAL_ARGMAX, + NUMERICAL_ARGMIN, + NUMERICAL_MAX, + NUMERICAL_MEAN, + NUMERICAL_MIN, + NUMERICAL_STD, + NUMERICAL_SUM, +}; + +//| """Numerical and Statistical functions +//| +//| Most of these functions take an "axis" argument, which indicates whether to +//| operate over the flattened array (None), or a particular axis (integer).""" +//| +//| from typing import Dict +//| +//| _ArrayLike = Union[ndarray, List[_float], Tuple[_float], range] +//| +//| _DType = int +//| """`ulab.numpy.int8`, `ulab.numpy.uint8`, `ulab.numpy.int16`, `ulab.numpy.uint16`, `ulab.numpy.float` or `ulab.numpy.bool`""" +//| +//| from builtins import float as _float +//| from builtins import bool as _bool +//| +//| int8: _DType +//| """Type code for signed integers in the range -128 .. 127 inclusive, like the 'b' typecode of `array.array`""" +//| +//| int16: _DType +//| """Type code for signed integers in the range -32768 .. 32767 inclusive, like the 'h' typecode of `array.array`""" +//| +//| float: _DType +//| """Type code for floating point values, like the 'f' typecode of `array.array`""" +//| +//| uint8: _DType +//| """Type code for unsigned integers in the range 0 .. 255 inclusive, like the 'H' typecode of `array.array`""" +//| +//| uint16: _DType +//| """Type code for unsigned integers in the range 0 .. 65535 inclusive, like the 'h' typecode of `array.array`""" +//| +//| bool: _DType +//| """Type code for boolean values""" +//| + +static void numerical_reduce_axes(ndarray_obj_t *ndarray, int8_t axis, size_t *shape, int32_t *strides) { + // removes the values corresponding to a single axis from the shape and strides array + uint8_t index = ULAB_MAX_DIMS - ndarray->ndim + axis; + if((ndarray->ndim == 1) && (axis == 0)) { + index = 0; + shape[ULAB_MAX_DIMS - 1] = 1; + return; + } + for(uint8_t i = ULAB_MAX_DIMS - 1; i > 0; i--) { + if(i > index) { + shape[i] = ndarray->shape[i]; + strides[i] = ndarray->strides[i]; + } else { + shape[i] = ndarray->shape[i-1]; + strides[i] = ndarray->strides[i-1]; + } + } +} + +#if ULAB_NUMPY_HAS_ALL | ULAB_NUMPY_HAS_ANY +static mp_obj_t numerical_all_any(mp_obj_t oin, mp_obj_t axis, uint8_t optype) { + bool anytype = optype == NUMERICAL_ALL ? 1 : 0; + if(mp_obj_is_type(oin, &ulab_ndarray_type)) { + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(oin); + uint8_t *array = (uint8_t *)ndarray->array; + if(ndarray->len == 0) { // return immediately with empty arrays + if(optype == NUMERICAL_ALL) { + return mp_const_true; + } else { + return mp_const_false; + } + } + // always get a float, so that we don't have to resolve the dtype later + mp_float_t (*func)(void *) = ndarray_get_float_function(ndarray->dtype); + ndarray_obj_t *results = NULL; + uint8_t *rarray = NULL; + shape_strides _shape_strides = tools_reduce_axes(ndarray, axis); + if(axis != mp_const_none) { + results = ndarray_new_dense_ndarray(_shape_strides.ndim, _shape_strides.shape, NDARRAY_BOOL); + rarray = results->array; + if(optype == NUMERICAL_ALL) { + memset(rarray, 1, results->len); + } + } + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + if(axis == mp_const_none) { + do { + #if ULAB_SUPPORTS_COMPLEX + if(ndarray->dtype == NDARRAY_COMPLEX) { + mp_float_t real = *((mp_float_t *)array); + mp_float_t imag = *((mp_float_t *)(array + sizeof(mp_float_t))); + if(((real != MICROPY_FLOAT_CONST(0.0)) | (imag != MICROPY_FLOAT_CONST(0.0))) & !anytype) { + // optype = NUMERICAL_ANY + return mp_const_true; + } else if(((real == MICROPY_FLOAT_CONST(0.0)) & (imag == MICROPY_FLOAT_CONST(0.0))) & anytype) { + // optype == NUMERICAL_ALL + return mp_const_false; + } + } else { + #endif + mp_float_t value = func(array); + if((value != MICROPY_FLOAT_CONST(0.0)) & !anytype) { + // optype = NUMERICAL_ANY + return mp_const_true; + } else if((value == MICROPY_FLOAT_CONST(0.0)) & anytype) { + // optype == NUMERICAL_ALL + return mp_const_false; + } + #if ULAB_SUPPORTS_COMPLEX + } + #endif + array += _shape_strides.strides[0]; + l++; + } while(l < _shape_strides.shape[0]); + } else { // a scalar axis keyword was supplied + do { + #if ULAB_SUPPORTS_COMPLEX + if(ndarray->dtype == NDARRAY_COMPLEX) { + mp_float_t real = *((mp_float_t *)array); + mp_float_t imag = *((mp_float_t *)(array + sizeof(mp_float_t))); + if(((real != MICROPY_FLOAT_CONST(0.0)) | (imag != MICROPY_FLOAT_CONST(0.0))) & !anytype) { + // optype = NUMERICAL_ANY + *rarray = 1; + // since we are breaking out of the loop, move the pointer forward + array += _shape_strides.strides[0] * (_shape_strides.shape[0] - l); + break; + } else if(((real == MICROPY_FLOAT_CONST(0.0)) & (imag == MICROPY_FLOAT_CONST(0.0))) & anytype) { + // optype == NUMERICAL_ALL + *rarray = 0; + // since we are breaking out of the loop, move the pointer forward + array += _shape_strides.strides[0] * (_shape_strides.shape[0] - l); + break; + } + } else { + #endif + mp_float_t value = func(array); + if((value != MICROPY_FLOAT_CONST(0.0)) & !anytype) { + // optype == NUMERICAL_ANY + *rarray = 1; + // since we are breaking out of the loop, move the pointer forward + array += _shape_strides.strides[0] * (_shape_strides.shape[0] - l); + break; + } else if((value == MICROPY_FLOAT_CONST(0.0)) & anytype) { + // optype == NUMERICAL_ALL + *rarray = 0; + // since we are breaking out of the loop, move the pointer forward + array += _shape_strides.strides[0] * (_shape_strides.shape[0] - l); + break; + } + #if ULAB_SUPPORTS_COMPLEX + } + #endif + array += _shape_strides.strides[0]; + l++; + } while(l < _shape_strides.shape[0]); + } + #if ULAB_MAX_DIMS > 1 + rarray += _shape_strides.increment; + array -= _shape_strides.strides[0] * _shape_strides.shape[0]; + array += _shape_strides.strides[ULAB_MAX_DIMS - 1]; + k++; + } while(k < _shape_strides.shape[ULAB_MAX_DIMS - 1]); + #endif + #if ULAB_MAX_DIMS > 2 + array -= _shape_strides.strides[ULAB_MAX_DIMS - 1] * _shape_strides.shape[ULAB_MAX_DIMS - 1]; + array += _shape_strides.strides[ULAB_MAX_DIMS - 2]; + j++; + } while(j < _shape_strides.shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= _shape_strides.strides[ULAB_MAX_DIMS - 2] * _shape_strides.shape[ULAB_MAX_DIMS - 2]; + array += _shape_strides.strides[ULAB_MAX_DIMS - 3]; + i++; + } while(i < _shape_strides.shape[ULAB_MAX_DIMS - 3]); + #endif + if(axis == mp_const_none) { + // the innermost loop fell through, so return the result here + if(!anytype) { + return mp_const_false; + } else { + return mp_const_true; + } + } + return MP_OBJ_FROM_PTR(results); + } else if(mp_obj_is_int(oin) || mp_obj_is_float(oin)) { + return mp_obj_is_true(oin) ? mp_const_true : mp_const_false; + } else { + mp_obj_iter_buf_t iter_buf; + mp_obj_t item, iterable = mp_getiter(oin, &iter_buf); + while((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + if(!mp_obj_is_true(item) & !anytype) { + return mp_const_false; + } else if(mp_obj_is_true(item) & anytype) { + return mp_const_true; + } + } + } + return anytype ? mp_const_true : mp_const_false; +} +#endif + +#if ULAB_NUMPY_HAS_SUM | ULAB_NUMPY_HAS_MEAN | ULAB_NUMPY_HAS_STD +static mp_obj_t numerical_sum_mean_std_iterable(mp_obj_t oin, uint8_t optype, size_t ddof) { + mp_float_t value = MICROPY_FLOAT_CONST(0.0); + mp_float_t M = MICROPY_FLOAT_CONST(0.0); + mp_float_t m = MICROPY_FLOAT_CONST(0.0); + mp_float_t S = MICROPY_FLOAT_CONST(0.0); + mp_float_t s = MICROPY_FLOAT_CONST(0.0); + size_t count = 0; + mp_obj_iter_buf_t iter_buf; + mp_obj_t item, iterable = mp_getiter(oin, &iter_buf); + while((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + value = mp_obj_get_float(item); + m = M + (value - M) / (count + 1); + s = S + (value - M) * (value - m); + M = m; + S = s; + count++; + } + if(optype == NUMERICAL_SUM) { + return mp_obj_new_float(m * count); + } else if(optype == NUMERICAL_MEAN) { + return count > 0 ? mp_obj_new_float(m) : mp_obj_new_float(MICROPY_FLOAT_CONST(0.0)); + } else { // this should be the case of the standard deviation + return count > ddof ? mp_obj_new_float(MICROPY_FLOAT_C_FUN(sqrt)(s / (count - ddof))) : mp_obj_new_float(MICROPY_FLOAT_CONST(0.0)); + } +} + +static mp_obj_t numerical_sum_mean_std_ndarray(ndarray_obj_t *ndarray, mp_obj_t axis, uint8_t optype, size_t ddof) { + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) + uint8_t *array = (uint8_t *)ndarray->array; + shape_strides _shape_strides = tools_reduce_axes(ndarray, axis); + + if(axis == mp_const_none) { + // work with the flattened array + if((optype == NUMERICAL_STD) && (ddof > ndarray->len)) { + // if there are too many degrees of freedom, there is no point in calculating anything + return mp_obj_new_float(MICROPY_FLOAT_CONST(0.0)); + } + mp_float_t (*func)(void *) = ndarray_get_float_function(ndarray->dtype); + mp_float_t M = MICROPY_FLOAT_CONST(0.0); + mp_float_t m = MICROPY_FLOAT_CONST(0.0); + mp_float_t S = MICROPY_FLOAT_CONST(0.0); + mp_float_t s = MICROPY_FLOAT_CONST(0.0); + size_t count = 0; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + count++; + mp_float_t value = func(array); + m = M + (value - M) / (mp_float_t)count; + if(optype == NUMERICAL_STD) { + s = S + (value - M) * (value - m); + S = s; + } + M = m; + array += _shape_strides.strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < _shape_strides.shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + array -= _shape_strides.strides[ULAB_MAX_DIMS - 1] * _shape_strides.shape[ULAB_MAX_DIMS - 1]; + array += _shape_strides.strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < _shape_strides.shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + array -= _shape_strides.strides[ULAB_MAX_DIMS - 2] * _shape_strides.shape[ULAB_MAX_DIMS - 2]; + array += _shape_strides.strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < _shape_strides.shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= _shape_strides.strides[ULAB_MAX_DIMS - 3] * _shape_strides.shape[ULAB_MAX_DIMS - 3]; + array += _shape_strides.strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < _shape_strides.shape[ULAB_MAX_DIMS - 4]); + #endif + if(optype == NUMERICAL_SUM) { + // numpy returns an integer for integer input types + if(ndarray->dtype == NDARRAY_FLOAT) { + return mp_obj_new_float(M * ndarray->len); + } else { + return mp_obj_new_int((int32_t)MICROPY_FLOAT_C_FUN(round)(M * ndarray->len)); + } + } else if(optype == NUMERICAL_MEAN) { + return mp_obj_new_float(M); + } else { // this must be the case of the standard deviation + // we have already made certain that ddof < ndarray->len holds + return mp_obj_new_float(MICROPY_FLOAT_C_FUN(sqrt)(S / (ndarray->len - ddof))); + } + } else { + ndarray_obj_t *results = NULL; + uint8_t *rarray = NULL; + mp_float_t *farray = NULL; + if(optype == NUMERICAL_SUM) { + results = ndarray_new_dense_ndarray(_shape_strides.ndim, _shape_strides.shape, ndarray->dtype); + rarray = (uint8_t *)results->array; + // TODO: numpy promotes the output to the highest integer type + if(ndarray->dtype == NDARRAY_UINT8) { + RUN_SUM(uint8_t, array, results, rarray, _shape_strides); + } else if(ndarray->dtype == NDARRAY_INT8) { + RUN_SUM(int8_t, array, results, rarray, _shape_strides); + } else if(ndarray->dtype == NDARRAY_UINT16) { + RUN_SUM(uint16_t, array, results, rarray, _shape_strides); + } else if(ndarray->dtype == NDARRAY_INT16) { + RUN_SUM(int16_t, array, results, rarray, _shape_strides); + } else { + // for floats, the sum might be inaccurate with the naive summation + // call mean, and multiply with the number of samples + farray = (mp_float_t *)results->array; + RUN_MEAN_STD(mp_float_t, array, farray, _shape_strides, MICROPY_FLOAT_CONST(0.0), 0); + mp_float_t norm = (mp_float_t)_shape_strides.shape[0]; + // re-wind the array here + farray = (mp_float_t *)results->array; + for(size_t i=0; i < results->len; i++) { + *farray++ *= norm; + } + } + } else { + bool isStd = optype == NUMERICAL_STD ? 1 : 0; + results = ndarray_new_dense_ndarray(_shape_strides.ndim, _shape_strides.shape, NDARRAY_FLOAT); + farray = (mp_float_t *)results->array; + // we can return the 0 array here, if the degrees of freedom is larger than the length of the axis + if((optype == NUMERICAL_STD) && (_shape_strides.shape[0] <= ddof)) { + return MP_OBJ_FROM_PTR(results); + } + mp_float_t div = optype == NUMERICAL_STD ? (mp_float_t)(_shape_strides.shape[0] - ddof) : MICROPY_FLOAT_CONST(0.0); + if(ndarray->dtype == NDARRAY_UINT8) { + RUN_MEAN_STD(uint8_t, array, farray, _shape_strides, div, isStd); + } else if(ndarray->dtype == NDARRAY_INT8) { + RUN_MEAN_STD(int8_t, array, farray, _shape_strides, div, isStd); + } else if(ndarray->dtype == NDARRAY_UINT16) { + RUN_MEAN_STD(uint16_t, array, farray, _shape_strides, div, isStd); + } else if(ndarray->dtype == NDARRAY_INT16) { + RUN_MEAN_STD(int16_t, array, farray, _shape_strides, div, isStd); + } else { + RUN_MEAN_STD(mp_float_t, array, farray, _shape_strides, div, isStd); + } + } + if(results->ndim == 0) { // return a scalar here + return mp_binary_get_val_array(results->dtype, results->array, 0); + } + return MP_OBJ_FROM_PTR(results); + } + return mp_const_none; +} +#endif + +#if ULAB_NUMPY_HAS_ARGMINMAX +static mp_obj_t numerical_argmin_argmax_iterable(mp_obj_t oin, uint8_t optype) { + if(MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(oin)) == 0) { + mp_raise_ValueError(translate("attempt to get argmin/argmax of an empty sequence")); + } + size_t idx = 0, best_idx = 0; + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(oin, &iter_buf); + mp_obj_t item; + uint8_t op = 0; // argmin, min + if((optype == NUMERICAL_ARGMAX) || (optype == NUMERICAL_MAX)) op = 1; + item = mp_iternext(iterable); + mp_obj_t best_obj = item; + mp_float_t value, best_value = mp_obj_get_float(item); + value = best_value; + while((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + idx++; + value = mp_obj_get_float(item); + if((op == 0) && (value < best_value)) { + best_obj = item; + best_idx = idx; + best_value = value; + } else if((op == 1) && (value > best_value)) { + best_obj = item; + best_idx = idx; + best_value = value; + } + } + if((optype == NUMERICAL_ARGMIN) || (optype == NUMERICAL_ARGMAX)) { + return MP_OBJ_NEW_SMALL_INT(best_idx); + } else { + return best_obj; + } +} + +static mp_obj_t numerical_argmin_argmax_ndarray(ndarray_obj_t *ndarray, mp_obj_t axis, uint8_t optype) { + // TODO: treat the flattened array + if(ndarray->len == 0) { + mp_raise_ValueError(translate("attempt to get (arg)min/(arg)max of empty sequence")); + } + + if(axis == mp_const_none) { + // work with the flattened array + mp_float_t (*func)(void *) = ndarray_get_float_function(ndarray->dtype); + uint8_t *array = (uint8_t *)ndarray->array; + mp_float_t best_value = func(array); + mp_float_t value; + size_t index = 0, best_index = 0; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + value = func(array); + if((optype == NUMERICAL_ARGMAX) || (optype == NUMERICAL_MAX)) { + if(best_value < value) { + best_value = value; + best_index = index; + } + } else { + if(best_value > value) { + best_value = value; + best_index = index; + } + } + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + l++; + index++; + } while(l < ndarray->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + array -= ndarray->strides[ULAB_MAX_DIMS - 1] * ndarray->shape[ULAB_MAX_DIMS-1]; + array += ndarray->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < ndarray->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + array -= ndarray->strides[ULAB_MAX_DIMS - 2] * ndarray->shape[ULAB_MAX_DIMS-2]; + array += ndarray->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < ndarray->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= ndarray->strides[ULAB_MAX_DIMS - 3] * ndarray->shape[ULAB_MAX_DIMS-3]; + array += ndarray->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < ndarray->shape[ULAB_MAX_DIMS - 4]); + #endif + + if((optype == NUMERICAL_ARGMIN) || (optype == NUMERICAL_ARGMAX)) { + return mp_obj_new_int(best_index); + } else { + if(ndarray->dtype == NDARRAY_FLOAT) { + return mp_obj_new_float(best_value); + } else { + return MP_OBJ_NEW_SMALL_INT((int32_t)best_value); + } + } + } else { + int8_t ax = tools_get_axis(axis, ndarray->ndim); + + uint8_t *array = (uint8_t *)ndarray->array; + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + int32_t *strides = m_new0(int32_t, ULAB_MAX_DIMS); + + numerical_reduce_axes(ndarray, ax, shape, strides); + uint8_t index = ULAB_MAX_DIMS - ndarray->ndim + ax; + + ndarray_obj_t *results = NULL; + + if((optype == NUMERICAL_ARGMIN) || (optype == NUMERICAL_ARGMAX)) { + results = ndarray_new_dense_ndarray(MAX(1, ndarray->ndim-1), shape, NDARRAY_INT16); + } else { + results = ndarray_new_dense_ndarray(MAX(1, ndarray->ndim-1), shape, ndarray->dtype); + } + + uint8_t *rarray = (uint8_t *)results->array; + + if(ndarray->dtype == NDARRAY_UINT8) { + RUN_ARGMIN(ndarray, uint8_t, array, results, rarray, shape, strides, index, optype); + } else if(ndarray->dtype == NDARRAY_INT8) { + RUN_ARGMIN(ndarray, int8_t, array, results, rarray, shape, strides, index, optype); + } else if(ndarray->dtype == NDARRAY_UINT16) { + RUN_ARGMIN(ndarray, uint16_t, array, results, rarray, shape, strides, index, optype); + } else if(ndarray->dtype == NDARRAY_INT16) { + RUN_ARGMIN(ndarray, int16_t, array, results, rarray, shape, strides, index, optype); + } else { + RUN_ARGMIN(ndarray, mp_float_t, array, results, rarray, shape, strides, index, optype); + } + + m_del(int32_t, strides, ULAB_MAX_DIMS); + + if(results->len == 1) { + return mp_binary_get_val_array(results->dtype, results->array, 0); + } + return MP_OBJ_FROM_PTR(results); + } + return mp_const_none; +} +#endif + +static mp_obj_t numerical_function(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args, uint8_t optype) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE} } , + { MP_QSTR_axis, MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t oin = args[0].u_obj; + mp_obj_t axis = args[1].u_obj; + if((axis != mp_const_none) && (!mp_obj_is_int(axis))) { + mp_raise_TypeError(translate("axis must be None, or an integer")); + } + +#if ULAB_NUMPY_HAS_ALL | ULAB_NUMPY_HAS_ANY + if((optype == NUMERICAL_ALL) || (optype == NUMERICAL_ANY)) { + return numerical_all_any(oin, axis, optype); + } +#endif + if(mp_obj_is_type(oin, &mp_type_tuple) || mp_obj_is_type(oin, &mp_type_list) || + mp_obj_is_type(oin, &mp_type_range)) { + switch(optype) { + case NUMERICAL_MIN: + case NUMERICAL_ARGMIN: + case NUMERICAL_MAX: + case NUMERICAL_ARGMAX: + return numerical_argmin_argmax_iterable(oin, optype); + case NUMERICAL_SUM: + case NUMERICAL_MEAN: + return numerical_sum_mean_std_iterable(oin, optype, 0); + default: // we should never reach this point, but whatever + return mp_const_none; + } + } else if(mp_obj_is_type(oin, &ulab_ndarray_type)) { + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(oin); + switch(optype) { + case NUMERICAL_MIN: + case NUMERICAL_MAX: + case NUMERICAL_ARGMIN: + case NUMERICAL_ARGMAX: + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) + return numerical_argmin_argmax_ndarray(ndarray, axis, optype); + case NUMERICAL_SUM: + case NUMERICAL_MEAN: + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) + return numerical_sum_mean_std_ndarray(ndarray, axis, optype, 0); + default: + mp_raise_NotImplementedError(translate("operation is not implemented on ndarrays")); + } + } else { + mp_raise_TypeError(translate("input must be tuple, list, range, or ndarray")); + } + return mp_const_none; +} + +#if ULAB_NUMPY_HAS_SORT | NDARRAY_HAS_SORT +static mp_obj_t numerical_sort_helper(mp_obj_t oin, mp_obj_t axis, uint8_t inplace) { + if(!mp_obj_is_type(oin, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("sort argument must be an ndarray")); + } + + ndarray_obj_t *ndarray; + if(inplace == 1) { + ndarray = MP_OBJ_TO_PTR(oin); + } else { + ndarray = ndarray_copy_view(MP_OBJ_TO_PTR(oin)); + } + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) + + int8_t ax = 0; + if(axis == mp_const_none) { + // flatten the array + #if ULAB_MAX_DIMS > 1 + for(uint8_t i=0; i < ULAB_MAX_DIMS - 1; i++) { + ndarray->shape[i] = 0; + ndarray->strides[i] = 0; + } + ndarray->shape[ULAB_MAX_DIMS - 1] = ndarray->len; + ndarray->strides[ULAB_MAX_DIMS - 1] = ndarray->itemsize; + ndarray->ndim = 1; + #endif + } else { + ax = tools_get_axis(axis, ndarray->ndim); + } + + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + int32_t *strides = m_new0(int32_t, ULAB_MAX_DIMS); + + numerical_reduce_axes(ndarray, ax, shape, strides); + ax = ULAB_MAX_DIMS - ndarray->ndim + ax; + // we work with the typed array, so re-scale the stride + int32_t increment = ndarray->strides[ax] / ndarray->itemsize; + + uint8_t *array = (uint8_t *)ndarray->array; + if(ndarray->shape[ax]) { + if((ndarray->dtype == NDARRAY_UINT8) || (ndarray->dtype == NDARRAY_INT8)) { + HEAPSORT(ndarray, uint8_t, array, shape, strides, ax, increment, ndarray->shape[ax]); + } else if((ndarray->dtype == NDARRAY_UINT16) || (ndarray->dtype == NDARRAY_INT16)) { + HEAPSORT(ndarray, uint16_t, array, shape, strides, ax, increment, ndarray->shape[ax]); + } else { + HEAPSORT(ndarray, mp_float_t, array, shape, strides, ax, increment, ndarray->shape[ax]); + } + } + + m_del(int32_t, strides, ULAB_MAX_DIMS); + + if(inplace == 1) { + return mp_const_none; + } else { + return MP_OBJ_FROM_PTR(ndarray); + } +} +#endif /* ULAB_NUMERICAL_HAS_SORT | NDARRAY_HAS_SORT */ + +#if ULAB_NUMPY_HAS_ALL +mp_obj_t numerical_all(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return numerical_function(n_args, pos_args, kw_args, NUMERICAL_ALL); +} +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_all_obj, 1, numerical_all); +#endif + +#if ULAB_NUMPY_HAS_ANY +mp_obj_t numerical_any(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return numerical_function(n_args, pos_args, kw_args, NUMERICAL_ANY); +} +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_any_obj, 1, numerical_any); +#endif + +#if ULAB_NUMPY_HAS_ARGMINMAX +//| def argmax(array: _ArrayLike, *, axis: Optional[int] = None) -> int: +//| """Return the index of the maximum element of the 1D array""" +//| ... +//| + +mp_obj_t numerical_argmax(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return numerical_function(n_args, pos_args, kw_args, NUMERICAL_ARGMAX); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_argmax_obj, 1, numerical_argmax); + +//| def argmin(array: _ArrayLike, *, axis: Optional[int] = None) -> int: +//| """Return the index of the minimum element of the 1D array""" +//| ... +//| + +static mp_obj_t numerical_argmin(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return numerical_function(n_args, pos_args, kw_args, NUMERICAL_ARGMIN); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_argmin_obj, 1, numerical_argmin); +#endif + +#if ULAB_NUMPY_HAS_ARGSORT +//| def argsort(array: ulab.numpy.ndarray, *, axis: int = -1) -> ulab.numpy.ndarray: +//| """Returns an array which gives indices into the input array from least to greatest.""" +//| ... +//| + +mp_obj_t numerical_argsort(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("argsort argument must be an ndarray")); + } + + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) + if(args[1].u_obj == mp_const_none) { + // bail out, though dense arrays could still be sorted + mp_raise_NotImplementedError(translate("argsort is not implemented for flattened arrays")); + } + // Since we are returning an NDARRAY_UINT16 array, bail out, + // if the axis is longer than what we can hold + for(uint8_t i=0; i < ULAB_MAX_DIMS; i++) { + if(ndarray->shape[i] > 65535) { + mp_raise_ValueError(translate("axis too long")); + } + } + int8_t ax = tools_get_axis(args[1].u_obj, ndarray->ndim); + + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + int32_t *strides = m_new0(int32_t, ULAB_MAX_DIMS); + numerical_reduce_axes(ndarray, ax, shape, strides); + + // We could return an NDARRAY_UINT8 array, if all lengths are shorter than 256 + ndarray_obj_t *indices = ndarray_new_ndarray(ndarray->ndim, ndarray->shape, NULL, NDARRAY_UINT16); + int32_t *istrides = m_new0(int32_t, ULAB_MAX_DIMS); + numerical_reduce_axes(indices, ax, shape, istrides); + + for(uint8_t i=0; i < ULAB_MAX_DIMS; i++) { + istrides[i] /= sizeof(uint16_t); + } + + ax = ULAB_MAX_DIMS - ndarray->ndim + ax; + // we work with the typed array, so re-scale the stride + int32_t increment = ndarray->strides[ax] / ndarray->itemsize; + uint16_t iincrement = indices->strides[ax] / sizeof(uint16_t); + + uint8_t *array = (uint8_t *)ndarray->array; + uint16_t *iarray = (uint16_t *)indices->array; + + // fill in the index values + #if ULAB_MAX_DIMS > 3 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t k = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t l = 0; + do { + #endif + uint16_t m = 0; + do { + *iarray = m++; + iarray += iincrement; + } while(m < indices->shape[ax]); + #if ULAB_MAX_DIMS > 1 + iarray -= iincrement * indices->shape[ax]; + iarray += istrides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < shape[ULAB_MAX_DIMS - 1]); + iarray -= istrides[ULAB_MAX_DIMS - 1] * shape[ULAB_MAX_DIMS - 1]; + iarray += istrides[ULAB_MAX_DIMS - 2]; + #endif + #if ULAB_MAX_DIMS > 2 + k++; + } while(k < shape[ULAB_MAX_DIMS - 2]); + iarray -= istrides[ULAB_MAX_DIMS - 2] * shape[ULAB_MAX_DIMS - 2]; + iarray += istrides[ULAB_MAX_DIMS - 3]; + #endif + #if ULAB_MAX_DIMS > 3 + j++; + } while(j < shape[ULAB_MAX_DIMS - 3]); + #endif + // reset the array + iarray = indices->array; + + if(ndarray->shape[ax]) { + if((ndarray->dtype == NDARRAY_UINT8) || (ndarray->dtype == NDARRAY_INT8)) { + HEAP_ARGSORT(ndarray, uint8_t, array, shape, strides, ax, increment, ndarray->shape[ax], iarray, istrides, iincrement); + } else if((ndarray->dtype == NDARRAY_UINT16) || (ndarray->dtype == NDARRAY_INT16)) { + HEAP_ARGSORT(ndarray, uint16_t, array, shape, strides, ax, increment, ndarray->shape[ax], iarray, istrides, iincrement); + } else { + HEAP_ARGSORT(ndarray, mp_float_t, array, shape, strides, ax, increment, ndarray->shape[ax], iarray, istrides, iincrement); + } + } + + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(int32_t, strides, ULAB_MAX_DIMS); + m_del(int32_t, istrides, ULAB_MAX_DIMS); + + return MP_OBJ_FROM_PTR(indices); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_argsort_obj, 1, numerical_argsort); +#endif + +#if ULAB_NUMPY_HAS_CROSS +//| def cross(a: ulab.numpy.ndarray, b: ulab.numpy.ndarray) -> ulab.numpy.ndarray: +//| """Return the cross product of two vectors of length 3""" +//| ... +//| + +static mp_obj_t numerical_cross(mp_obj_t _a, mp_obj_t _b) { + if (!mp_obj_is_type(_a, &ulab_ndarray_type) || !mp_obj_is_type(_b, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("arguments must be ndarrays")); + } + ndarray_obj_t *a = MP_OBJ_TO_PTR(_a); + ndarray_obj_t *b = MP_OBJ_TO_PTR(_b); + COMPLEX_DTYPE_NOT_IMPLEMENTED(a->dtype) + COMPLEX_DTYPE_NOT_IMPLEMENTED(b->dtype) + if((a->ndim != 1) || (b->ndim != 1) || (a->len != b->len) || (a->len != 3)) { + mp_raise_ValueError(translate("cross is defined for 1D arrays of length 3")); + } + + mp_float_t *results = m_new(mp_float_t, 3); + results[0] = ndarray_get_float_index(a->array, a->dtype, 1) * ndarray_get_float_index(b->array, b->dtype, 2); + results[0] -= ndarray_get_float_index(a->array, a->dtype, 2) * ndarray_get_float_index(b->array, b->dtype, 1); + results[1] = -ndarray_get_float_index(a->array, a->dtype, 0) * ndarray_get_float_index(b->array, b->dtype, 2); + results[1] += ndarray_get_float_index(a->array, a->dtype, 2) * ndarray_get_float_index(b->array, b->dtype, 0); + results[2] = ndarray_get_float_index(a->array, a->dtype, 0) * ndarray_get_float_index(b->array, b->dtype, 1); + results[2] -= ndarray_get_float_index(a->array, a->dtype, 1) * ndarray_get_float_index(b->array, b->dtype, 0); + + /* The upcasting happens here with the rules + + - if one of the operarands is a float, the result is always float + - operation on identical types preserves type + + uint8 + int8 => int16 + uint8 + int16 => int16 + uint8 + uint16 => uint16 + int8 + int16 => int16 + int8 + uint16 => uint16 + uint16 + int16 => float + + */ + + uint8_t dtype = NDARRAY_FLOAT; + if(a->dtype == b->dtype) { + dtype = a->dtype; + } else if(((a->dtype == NDARRAY_UINT8) && (b->dtype == NDARRAY_INT8)) || ((a->dtype == NDARRAY_INT8) && (b->dtype == NDARRAY_UINT8))) { + dtype = NDARRAY_INT16; + } else if(((a->dtype == NDARRAY_UINT8) && (b->dtype == NDARRAY_INT16)) || ((a->dtype == NDARRAY_INT16) && (b->dtype == NDARRAY_UINT8))) { + dtype = NDARRAY_INT16; + } else if(((a->dtype == NDARRAY_UINT8) && (b->dtype == NDARRAY_UINT16)) || ((a->dtype == NDARRAY_UINT16) && (b->dtype == NDARRAY_UINT8))) { + dtype = NDARRAY_UINT16; + } else if(((a->dtype == NDARRAY_INT8) && (b->dtype == NDARRAY_INT16)) || ((a->dtype == NDARRAY_INT16) && (b->dtype == NDARRAY_INT8))) { + dtype = NDARRAY_INT16; + } else if(((a->dtype == NDARRAY_INT8) && (b->dtype == NDARRAY_UINT16)) || ((a->dtype == NDARRAY_UINT16) && (b->dtype == NDARRAY_INT8))) { + dtype = NDARRAY_UINT16; + } + + ndarray_obj_t *ndarray = ndarray_new_linear_array(3, dtype); + if(dtype == NDARRAY_UINT8) { + uint8_t *array = (uint8_t *)ndarray->array; + for(uint8_t i=0; i < 3; i++) array[i] = (uint8_t)results[i]; + } else if(dtype == NDARRAY_INT8) { + int8_t *array = (int8_t *)ndarray->array; + for(uint8_t i=0; i < 3; i++) array[i] = (int8_t)results[i]; + } else if(dtype == NDARRAY_UINT16) { + uint16_t *array = (uint16_t *)ndarray->array; + for(uint8_t i=0; i < 3; i++) array[i] = (uint16_t)results[i]; + } else if(dtype == NDARRAY_INT16) { + int16_t *array = (int16_t *)ndarray->array; + for(uint8_t i=0; i < 3; i++) array[i] = (int16_t)results[i]; + } else { + mp_float_t *array = (mp_float_t *)ndarray->array; + for(uint8_t i=0; i < 3; i++) array[i] = results[i]; + } + m_del(mp_float_t, results, 3); + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_2(numerical_cross_obj, numerical_cross); + +#endif /* ULAB_NUMERICAL_HAS_CROSS */ + +#if ULAB_NUMPY_HAS_DIFF +//| def diff(array: ulab.numpy.ndarray, *, n: int = 1, axis: int = -1) -> ulab.numpy.ndarray: +//| """Return the numerical derivative of successive elements of the array, as +//| an array. axis=None is not supported.""" +//| ... +//| + +mp_obj_t numerical_diff(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_n, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1 } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1 } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("diff argument must be an ndarray")); + } + + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) + int8_t ax = args[2].u_int; + if(ax < 0) ax += ndarray->ndim; + + if((ax < 0) || (ax > ndarray->ndim - 1)) { + mp_raise_ValueError(translate("index out of range")); + } + + if((args[1].u_int < 0) || (args[1].u_int > 9)) { + mp_raise_ValueError(translate("differentiation order out of range")); + } + uint8_t N = (uint8_t)args[1].u_int; + uint8_t index = ULAB_MAX_DIMS - ndarray->ndim + ax; + if(N > ndarray->shape[index]) { + mp_raise_ValueError(translate("differentiation order out of range")); + } + + int8_t *stencil = m_new(int8_t, N+1); + stencil[0] = 1; + for(uint8_t i = 1; i < N+1; i++) { + stencil[i] = -stencil[i-1]*(N-i+1)/i; + } + + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + for(uint8_t i = 0; i < ULAB_MAX_DIMS; i++) { + shape[i] = ndarray->shape[i]; + if(i == index) { + shape[i] -= N; + } + } + uint8_t *array = (uint8_t *)ndarray->array; + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndarray->ndim, shape, ndarray->dtype); + uint8_t *rarray = (uint8_t *)results->array; + + memset(shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); + int32_t *strides = m_new0(int32_t, ULAB_MAX_DIMS); + numerical_reduce_axes(ndarray, ax, shape, strides); + + if(ndarray->dtype == NDARRAY_UINT8) { + RUN_DIFF(ndarray, uint8_t, array, results, rarray, shape, strides, index, stencil, N); + } else if(ndarray->dtype == NDARRAY_INT8) { + RUN_DIFF(ndarray, int8_t, array, results, rarray, shape, strides, index, stencil, N); + } else if(ndarray->dtype == NDARRAY_UINT16) { + RUN_DIFF(ndarray, uint16_t, array, results, rarray, shape, strides, index, stencil, N); + } else if(ndarray->dtype == NDARRAY_INT16) { + RUN_DIFF(ndarray, int16_t, array, results, rarray, shape, strides, index, stencil, N); + } else { + RUN_DIFF(ndarray, mp_float_t, array, results, rarray, shape, strides, index, stencil, N); + } + m_del(int8_t, stencil, N+1); + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(int32_t, strides, ULAB_MAX_DIMS); + return MP_OBJ_FROM_PTR(results); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_diff_obj, 1, numerical_diff); +#endif + +#if ULAB_NUMPY_HAS_FLIP +//| def flip(array: ulab.numpy.ndarray, *, axis: Optional[int] = None) -> ulab.numpy.ndarray: +//| """Returns a new array that reverses the order of the elements along the +//| given axis, or along all axes if axis is None.""" +//| ... +//| + +mp_obj_t numerical_flip(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("flip argument must be an ndarray")); + } + + ndarray_obj_t *results = NULL; + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj); + if(args[1].u_obj == mp_const_none) { // flip the flattened array + results = ndarray_new_linear_array(ndarray->len, ndarray->dtype); + ndarray_copy_array(ndarray, results, 0); + uint8_t *rarray = (uint8_t *)results->array; + rarray += (results->len - 1) * results->itemsize; + results->array = rarray; + results->strides[ULAB_MAX_DIMS - 1] = -results->strides[ULAB_MAX_DIMS - 1]; + } else if(mp_obj_is_int(args[1].u_obj)){ + int8_t ax = tools_get_axis(args[1].u_obj, ndarray->ndim); + + ax = ULAB_MAX_DIMS - ndarray->ndim + ax; + int32_t offset = (ndarray->shape[ax] - 1) * ndarray->strides[ax]; + results = ndarray_new_view(ndarray, ndarray->ndim, ndarray->shape, ndarray->strides, offset); + results->strides[ax] = -results->strides[ax]; + } else { + mp_raise_TypeError(translate("wrong axis index")); + } + return MP_OBJ_FROM_PTR(results); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_flip_obj, 1, numerical_flip); +#endif + +#if ULAB_NUMPY_HAS_MINMAX +//| def max(array: _ArrayLike, *, axis: Optional[int] = None) -> _float: +//| """Return the maximum element of the 1D array""" +//| ... +//| + +mp_obj_t numerical_max(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return numerical_function(n_args, pos_args, kw_args, NUMERICAL_MAX); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_max_obj, 1, numerical_max); +#endif + +#if ULAB_NUMPY_HAS_MEAN +//| def mean(array: _ArrayLike, *, axis: Optional[int] = None) -> _float: +//| """Return the mean element of the 1D array, as a number if axis is None, otherwise as an array.""" +//| ... +//| + +mp_obj_t numerical_mean(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return numerical_function(n_args, pos_args, kw_args, NUMERICAL_MEAN); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_mean_obj, 1, numerical_mean); +#endif + +#if ULAB_NUMPY_HAS_MEDIAN +//| def median(array: ulab.numpy.ndarray, *, axis: int = -1) -> ulab.numpy.ndarray: +//| """Find the median value in an array along the given axis, or along all axes if axis is None.""" +//| ... +//| + +mp_obj_t numerical_median(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("median argument must be an ndarray")); + } + + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj); + if(ndarray->len == 0) { + return mp_obj_new_float(MICROPY_FLOAT_C_FUN(nan)("")); + } + + ndarray = MP_OBJ_TO_PTR(numerical_sort_helper(args[0].u_obj, args[1].u_obj, 0)); + + if((args[1].u_obj == mp_const_none) || (ndarray->ndim == 1)) { + // at this point, the array holding the sorted values should be flat + uint8_t *array = (uint8_t *)ndarray->array; + size_t len = ndarray->len; + array += (len >> 1) * ndarray->itemsize; + mp_float_t median = ndarray_get_float_value(array, ndarray->dtype); + if(!(len & 0x01)) { // len is an even number + array -= ndarray->itemsize; + median += ndarray_get_float_value(array, ndarray->dtype); + median *= MICROPY_FLOAT_CONST(0.5); + } + return mp_obj_new_float(median); + } else { + int8_t ax = tools_get_axis(args[1].u_obj, ndarray->ndim); + + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + int32_t *strides = m_new0(int32_t, ULAB_MAX_DIMS); + numerical_reduce_axes(ndarray, ax, shape, strides); + + ax = ULAB_MAX_DIMS - ndarray->ndim + ax; + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndarray->ndim-1, shape, NDARRAY_FLOAT); + m_del(size_t, shape, ULAB_MAX_DIMS); + + mp_float_t *rarray = (mp_float_t *)results->array; + + uint8_t *array = (uint8_t *)ndarray->array; + + size_t len = ndarray->shape[ax]; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + size_t k = 0; + do { + array += ndarray->strides[ax] * (len >> 1); + mp_float_t median = ndarray_get_float_value(array, ndarray->dtype); + if(!(len & 0x01)) { // len is an even number + array -= ndarray->strides[ax]; + median += ndarray_get_float_value(array, ndarray->dtype); + median *= MICROPY_FLOAT_CONST(0.5); + array += ndarray->strides[ax]; + } + array -= ndarray->strides[ax] * (len >> 1); + array += strides[ULAB_MAX_DIMS - 1]; + *rarray = median; + rarray++; + k++; + } while(k < shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 2 + array -= strides[ULAB_MAX_DIMS - 1] * shape[ULAB_MAX_DIMS - 1]; + array += strides[ULAB_MAX_DIMS - 2]; + j++; + } while(j < shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= strides[ULAB_MAX_DIMS - 2] * shape[ULAB_MAX_DIMS-2]; + array += strides[ULAB_MAX_DIMS - 3]; + i++; + } while(i < shape[ULAB_MAX_DIMS - 3]); + #endif + + return MP_OBJ_FROM_PTR(results); + } + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_median_obj, 1, numerical_median); +#endif + +#if ULAB_NUMPY_HAS_MINMAX +//| def min(array: _ArrayLike, *, axis: Optional[int] = None) -> _float: +//| """Return the minimum element of the 1D array""" +//| ... +//| + +mp_obj_t numerical_min(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return numerical_function(n_args, pos_args, kw_args, NUMERICAL_MIN); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_min_obj, 1, numerical_min); +#endif + +#if ULAB_NUMPY_HAS_ROLL +//| def roll(array: ulab.numpy.ndarray, distance: int, *, axis: Optional[int] = None) -> None: +//| """Shift the content of a vector by the positions given as the second +//| argument. If the ``axis`` keyword is supplied, the shift is applied to +//| the given axis. The array is modified in place.""" +//| ... +//| + +mp_obj_t numerical_roll(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("roll argument must be an ndarray")); + } + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj); + uint8_t *array = ndarray->array; + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndarray->ndim, ndarray->shape, ndarray->dtype); + + int32_t shift = mp_obj_get_int(args[1].u_obj); + int32_t _shift = shift < 0 ? -shift : shift; + + size_t counter; + uint8_t *rarray = (uint8_t *)results->array; + + if(args[2].u_obj == mp_const_none) { // roll the flattened array + _shift = _shift % results->len; + if(shift > 0) { // shift to the right + rarray += _shift * results->itemsize; + counter = results->len - _shift; + } else { // shift to the left + rarray += (results->len - _shift) * results->itemsize; + counter = _shift; + } + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + memcpy(rarray, array, ndarray->itemsize); + rarray += results->itemsize; + array += ndarray->strides[ULAB_MAX_DIMS - 1]; + l++; + if(--counter == 0) { + rarray = results->array; + } + } while(l < ndarray->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + array -= ndarray->strides[ULAB_MAX_DIMS - 1] * ndarray->shape[ULAB_MAX_DIMS-1]; + array += ndarray->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < ndarray->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + array -= ndarray->strides[ULAB_MAX_DIMS - 2] * ndarray->shape[ULAB_MAX_DIMS-2]; + array += ndarray->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < ndarray->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= ndarray->strides[ULAB_MAX_DIMS - 3] * ndarray->shape[ULAB_MAX_DIMS-3]; + array += ndarray->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < ndarray->shape[ULAB_MAX_DIMS - 4]); + #endif + } else if(mp_obj_is_int(args[2].u_obj)){ + int8_t ax = tools_get_axis(args[2].u_obj, ndarray->ndim); + + size_t *shape = m_new0(size_t, ULAB_MAX_DIMS); + int32_t *strides = m_new0(int32_t, ULAB_MAX_DIMS); + numerical_reduce_axes(ndarray, ax, shape, strides); + + size_t *rshape = m_new0(size_t, ULAB_MAX_DIMS); + int32_t *rstrides = m_new0(int32_t, ULAB_MAX_DIMS); + numerical_reduce_axes(results, ax, rshape, rstrides); + + ax = ULAB_MAX_DIMS - ndarray->ndim + ax; + uint8_t *_rarray; + _shift = _shift % results->shape[ax]; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + _rarray = rarray; + if(shift < 0) { + rarray += (results->shape[ax] - _shift) * results->strides[ax]; + counter = _shift; + } else { + rarray += _shift * results->strides[ax]; + counter = results->shape[ax] - _shift; + } + do { + memcpy(rarray, array, ndarray->itemsize); + array += ndarray->strides[ax]; + rarray += results->strides[ax]; + if(--counter == 0) { + rarray = _rarray; + } + l++; + } while(l < ndarray->shape[ax]); + #if ULAB_MAX_DIMS > 1 + rarray = _rarray; + rarray += rstrides[ULAB_MAX_DIMS - 1]; + array -= ndarray->strides[ax] * ndarray->shape[ax]; + array += strides[ULAB_MAX_DIMS - 1]; + k++; + } while(k < shape[ULAB_MAX_DIMS - 1]); + #endif + #if ULAB_MAX_DIMS > 2 + rarray -= rstrides[ULAB_MAX_DIMS - 1] * rshape[ULAB_MAX_DIMS-1]; + rarray += rstrides[ULAB_MAX_DIMS - 2]; + array -= strides[ULAB_MAX_DIMS - 1] * shape[ULAB_MAX_DIMS-1]; + array += strides[ULAB_MAX_DIMS - 2]; + j++; + } while(j < shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 3 + rarray -= rstrides[ULAB_MAX_DIMS - 2] * rshape[ULAB_MAX_DIMS-2]; + rarray += rstrides[ULAB_MAX_DIMS - 3]; + array -= strides[ULAB_MAX_DIMS - 2] * shape[ULAB_MAX_DIMS-2]; + array += strides[ULAB_MAX_DIMS - 3]; + i++; + } while(i < shape[ULAB_MAX_DIMS - 3]); + #endif + + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(int32_t, strides, ULAB_MAX_DIMS); + m_del(size_t, rshape, ULAB_MAX_DIMS); + m_del(int32_t, rstrides, ULAB_MAX_DIMS); + + } else { + mp_raise_TypeError(translate("wrong axis index")); + } + + return MP_OBJ_FROM_PTR(results); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_roll_obj, 2, numerical_roll); +#endif + +#if ULAB_NUMPY_HAS_SORT +//| def sort(array: ulab.numpy.ndarray, *, axis: int = -1) -> ulab.numpy.ndarray: +//| """Sort the array along the given axis, or along all axes if axis is None. +//| The array is modified in place.""" +//| ... +//| + +mp_obj_t numerical_sort(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + return numerical_sort_helper(args[0].u_obj, args[1].u_obj, 0); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_sort_obj, 1, numerical_sort); +#endif + +#if NDARRAY_HAS_SORT +// method of an ndarray +static mp_obj_t numerical_sort_inplace(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_int = -1 } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + return numerical_sort_helper(args[0].u_obj, args[1].u_obj, 1); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_sort_inplace_obj, 1, numerical_sort_inplace); +#endif /* NDARRAY_HAS_SORT */ + +#if ULAB_NUMPY_HAS_STD +//| def std(array: _ArrayLike, *, axis: Optional[int] = None, ddof: int = 0) -> _float: +//| """Return the standard deviation of the array, as a number if axis is None, otherwise as an array.""" +//| ... +//| + +mp_obj_t numerical_std(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } } , + { MP_QSTR_axis, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_ddof, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t oin = args[0].u_obj; + mp_obj_t axis = args[1].u_obj; + size_t ddof = args[2].u_int; + if((axis != mp_const_none) && (mp_obj_get_int(axis) != 0) && (mp_obj_get_int(axis) != 1)) { + // this seems to pass with False, and True... + mp_raise_ValueError(translate("axis must be None, or an integer")); + } + if(mp_obj_is_type(oin, &mp_type_tuple) || mp_obj_is_type(oin, &mp_type_list) || mp_obj_is_type(oin, &mp_type_range)) { + return numerical_sum_mean_std_iterable(oin, NUMERICAL_STD, ddof); + } else if(mp_obj_is_type(oin, &ulab_ndarray_type)) { + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(oin); + return numerical_sum_mean_std_ndarray(ndarray, axis, NUMERICAL_STD, ddof); + } else { + mp_raise_TypeError(translate("input must be tuple, list, range, or ndarray")); + } + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_std_obj, 1, numerical_std); +#endif + +#if ULAB_NUMPY_HAS_SUM +//| def sum(array: _ArrayLike, *, axis: Optional[int] = None) -> Union[_float, int, ulab.numpy.ndarray]: +//| """Return the sum of the array, as a number if axis is None, otherwise as an array.""" +//| ... +//| + +mp_obj_t numerical_sum(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return numerical_function(n_args, pos_args, kw_args, NUMERICAL_SUM); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(numerical_sum_obj, 1, numerical_sum); +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/numerical.h b/components/3rd_party/omv/omv/modules/ulab/code/numpy/numerical.h new file mode 100644 index 00000000..186c817b --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/numerical.h @@ -0,0 +1,653 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös +*/ + +#ifndef _NUMERICAL_ +#define _NUMERICAL_ + +#include "../ulab.h" +#include "../ndarray.h" + +// TODO: implement cumsum + +#define RUN_ARGMIN1(ndarray, type, array, results, rarray, index, op)\ +({\ + uint16_t best_index = 0;\ + type best_value = *((type *)(array));\ + if(((op) == NUMERICAL_MAX) || ((op) == NUMERICAL_ARGMAX)) {\ + for(uint16_t i=0; i < (ndarray)->shape[(index)]; i++) {\ + if(*((type *)(array)) > best_value) {\ + best_index = i;\ + best_value = *((type *)(array));\ + }\ + (array) += (ndarray)->strides[(index)];\ + }\ + } else {\ + for(uint16_t i=0; i < (ndarray)->shape[(index)]; i++) {\ + if(*((type *)(array)) < best_value) {\ + best_index = i;\ + best_value = *((type *)(array));\ + }\ + (array) += (ndarray)->strides[(index)];\ + }\ + }\ + if(((op) == NUMERICAL_ARGMAX) || ((op) == NUMERICAL_ARGMIN)) {\ + memcpy((rarray), &best_index, (results)->itemsize);\ + } else {\ + memcpy((rarray), &best_value, (results)->itemsize);\ + }\ + (rarray) += (results)->itemsize;\ +}) + +#define RUN_SUM1(type, array, results, rarray, ss)\ +({\ + type sum = 0;\ + for(size_t i=0; i < (ss).shape[0]; i++) {\ + sum += *((type *)(array));\ + (array) += (ss).strides[0];\ + }\ + memcpy((rarray), &sum, (results)->itemsize);\ + (rarray) += (results)->itemsize;\ +}) + +// The mean could be calculated by simply dividing the sum by +// the number of elements, but that method is numerically unstable +#define RUN_MEAN1(type, array, rarray, ss)\ +({\ + mp_float_t M = 0.0;\ + for(size_t i=0; i < (ss).shape[0]; i++) {\ + mp_float_t value = (mp_float_t)(*(type *)(array));\ + M = M + (value - M) / (mp_float_t)(i+1);\ + (array) += (ss).strides[0];\ + }\ + *(rarray)++ = M;\ +}) + +// Instead of the straightforward implementation of the definition, +// we take the numerically stable Welford algorithm here +// https://www.johndcook.com/blog/2008/09/26/comparing-three-methods-of-computing-standard-deviation/ +#define RUN_STD1(type, array, rarray, ss, div)\ +({\ + mp_float_t M = 0.0, m = 0.0, S = 0.0;\ + for(size_t i=0; i < (ss).shape[0]; i++) {\ + mp_float_t value = (mp_float_t)(*(type *)(array));\ + m = M + (value - M) / (mp_float_t)(i+1);\ + S = S + (value - M) * (value - m);\ + M = m;\ + (array) += (ss).strides[0];\ + }\ + *(rarray)++ = MICROPY_FLOAT_C_FUN(sqrt)(S / (div));\ +}) + +#define RUN_MEAN_STD1(type, array, rarray, ss, div, isStd)\ +({\ + mp_float_t M = 0.0, m = 0.0, S = 0.0;\ + for(size_t i=0; i < (ss).shape[0]; i++) {\ + mp_float_t value = (mp_float_t)(*(type *)(array));\ + m = M + (value - M) / (mp_float_t)(i+1);\ + if(isStd) {\ + S += (value - M) * (value - m);\ + }\ + M = m;\ + (array) += (ss).strides[0];\ + }\ + *(rarray)++ = isStd ? MICROPY_FLOAT_C_FUN(sqrt)(S / (div)) : M;\ +}) + +#define RUN_DIFF1(ndarray, type, array, results, rarray, index, stencil, N)\ +({\ + for(size_t i=0; i < (results)->shape[ULAB_MAX_DIMS - 1]; i++) {\ + type sum = 0;\ + uint8_t *source = (array);\ + for(uint8_t d=0; d < (N)+1; d++) {\ + sum -= (stencil)[d] * *((type *)source);\ + source += (ndarray)->strides[(index)];\ + }\ + (array) += (ndarray)->strides[ULAB_MAX_DIMS - 1];\ + *(type *)(rarray) = sum;\ + (rarray) += (results)->itemsize;\ + }\ +}) + +#define HEAPSORT1(type, array, increment, N)\ +({\ + type *_array = (type *)array;\ + type tmp;\ + size_t c, q = (N), p, r = (N) >> 1;\ + for (;;) {\ + if (r > 0) {\ + tmp = _array[(--r)*(increment)];\ + } else {\ + q--;\ + if(q == 0) {\ + break;\ + }\ + tmp = _array[q*(increment)];\ + _array[q*(increment)] = _array[0];\ + }\ + p = r;\ + c = r + r + 1;\ + while (c < q) {\ + if((c + 1 < q) && (_array[(c+1)*(increment)] > _array[c*(increment)])) {\ + c++;\ + }\ + if(_array[c*(increment)] > tmp) {\ + _array[p*(increment)] = _array[c*(increment)];\ + p = c;\ + c = p + p + 1;\ + } else {\ + break;\ + }\ + }\ + _array[p*(increment)] = tmp;\ + }\ +}) + +#define HEAP_ARGSORT1(type, array, increment, N, iarray, iincrement)\ +({\ + type *_array = (type *)array;\ + type tmp;\ + uint16_t itmp, c, q = (N), p, r = (N) >> 1;\ + assert(N);\ + for (;;) {\ + if (r > 0) {\ + r--;\ + itmp = (iarray)[r*(iincrement)];\ + tmp = _array[itmp*(increment)];\ + } else {\ + q--;\ + if(q == 0) {\ + break;\ + }\ + itmp = (iarray)[q*(iincrement)];\ + tmp = _array[itmp*(increment)];\ + (iarray)[q*(iincrement)] = (iarray)[0];\ + }\ + p = r;\ + c = r + r + 1;\ + while (c < q) {\ + if((c + 1 < q) && (_array[(iarray)[(c+1)*(iincrement)]*(increment)] > _array[(iarray)[c*(iincrement)]*(increment)])) {\ + c++;\ + }\ + if(_array[(iarray)[c*(iincrement)]*(increment)] > tmp) {\ + (iarray)[p*(iincrement)] = (iarray)[c*(iincrement)];\ + p = c;\ + c = p + p + 1;\ + } else {\ + break;\ + }\ + }\ + (iarray)[p*(iincrement)] = itmp;\ + }\ +}) + +#if ULAB_MAX_DIMS == 1 +#define RUN_SUM(type, array, results, rarray, ss) do {\ + RUN_SUM1(type, (array), (results), (rarray), (ss));\ +} while(0) + +#define RUN_MEAN(type, array, rarray, ss) do {\ + RUN_MEAN1(type, (array), (rarray), (ss));\ +} while(0) + +#define RUN_STD(type, array, rarray, ss, div) do {\ + RUN_STD1(type, (array), (results), (rarray), (ss), (div));\ +} while(0) + +#define RUN_MEAN_STD(type, array, rarray, ss, div, isStd) do {\ + RUN_MEAN_STD1(type, (array), (rarray), (ss), (div), (isStd));\ +} while(0) + +#define RUN_ARGMIN(ndarray, type, array, results, rarray, shape, strides, index, op) do {\ + RUN_ARGMIN1((ndarray), type, (array), (results), (rarray), (index), (op));\ +} while(0) + +#define RUN_DIFF(ndarray, type, array, results, rarray, shape, strides, index, stencil, N) do {\ + RUN_DIFF1((ndarray), type, (array), (results), (rarray), (index), (stencil), (N));\ +} while(0) + +#define HEAPSORT(ndarray, type, array, shape, strides, index, increment, N) do {\ + HEAPSORT1(type, (array), (increment), (N));\ +} while(0) + +#define HEAP_ARGSORT(ndarray, type, array, shape, strides, index, increment, N, iarray, istrides, iincrement) do {\ + HEAP_ARGSORT1(type, (array), (increment), (N), (iarray), (iincrement));\ +} while(0) + +#endif + +#if ULAB_MAX_DIMS == 2 +#define RUN_SUM(type, array, results, rarray, ss) do {\ + size_t l = 0;\ + do {\ + RUN_SUM1(type, (array), (results), (rarray), (ss));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ +} while(0) + +#define RUN_MEAN(type, array, rarray, ss) do {\ + size_t l = 0;\ + do {\ + RUN_MEAN1(type, (array), (rarray), (ss));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ +} while(0) + +#define RUN_STD(type, array, rarray, ss, div) do {\ + size_t l = 0;\ + do {\ + RUN_STD1(type, (array), (rarray), (ss), (div));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ +} while(0) + +#define RUN_MEAN_STD(type, array, rarray, ss, div, isStd) do {\ + size_t l = 0;\ + do {\ + RUN_MEAN_STD1(type, (array), (rarray), (ss), (div), (isStd));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ +} while(0) + + +#define RUN_ARGMIN(ndarray, type, array, results, rarray, shape, strides, index, op) do {\ + size_t l = 0;\ + do {\ + RUN_ARGMIN1((ndarray), type, (array), (results), (rarray), (index), (op));\ + (array) -= (ndarray)->strides[(index)] * (ndarray)->shape[(index)];\ + (array) += (strides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ +} while(0) + +#define RUN_DIFF(ndarray, type, array, results, rarray, shape, strides, index, stencil, N) do {\ + size_t l = 0;\ + do {\ + RUN_DIFF1((ndarray), type, (array), (results), (rarray), (index), (stencil), (N));\ + (array) -= (ndarray)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (array) += (ndarray)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (results)->strides[ULAB_MAX_DIMS - 2];\ + l++;\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 2]);\ +} while(0) + +#define HEAPSORT(ndarray, type, array, shape, strides, index, increment, N) do {\ + size_t l = 0;\ + do {\ + HEAPSORT1(type, (array), (increment), (N));\ + (array) += (strides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ +} while(0) + +#define HEAP_ARGSORT(ndarray, type, array, shape, strides, index, increment, N, iarray, istrides, iincrement) do {\ + size_t l = 0;\ + do {\ + HEAP_ARGSORT1(type, (array), (increment), (N), (iarray), (iincrement));\ + (array) += (strides)[ULAB_MAX_DIMS - 1];\ + (iarray) += (istrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ +} while(0) + +#endif + +#if ULAB_MAX_DIMS == 3 +#define RUN_SUM(type, array, results, rarray, ss) do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_SUM1(type, (array), (results), (rarray), (ss));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 1] * (ss).shape[ULAB_MAX_DIMS - 1];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (ss).shape[ULAB_MAX_DIMS - 2]);\ +} while(0) + +#define RUN_MEAN(type, array, rarray, ss) do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_MEAN1(type, (array), (rarray), (ss));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 1] * (ss).shape[ULAB_MAX_DIMS - 1];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (ss).shape[ULAB_MAX_DIMS - 2]);\ +} while(0) + +#define RUN_STD(type, array, rarray, ss, div) do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_STD1(type, (array), (rarray), (ss), (div));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 1] * (ss).shape[ULAB_MAX_DIMS - 1];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (ss).shape[ULAB_MAX_DIMS - 2]);\ +} while(0) + +#define RUN_MEAN_STD(type, array, rarray, ss, div, isStd) do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_MEAN_STD1(type, (array), (rarray), (ss), (div), (isStd));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 1] * (ss).shape[ULAB_MAX_DIMS - 1];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (ss).shape[ULAB_MAX_DIMS - 2]);\ +} while(0) + +#define RUN_ARGMIN(ndarray, type, array, results, rarray, shape, strides, index, op) do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_ARGMIN1((ndarray), type, (array), (results), (rarray), (index), (op));\ + (array) -= (ndarray)->strides[(index)] * (ndarray)->shape[(index)];\ + (array) += (strides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ + (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ + (array) += (strides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (shape)[ULAB_MAX_DIMS - 2]);\ +} while(0) + +#define RUN_DIFF(ndarray, type, array, results, rarray, shape, strides, index, stencil, N) do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_DIFF1((ndarray), type, (array), (results), (rarray), (index), (stencil), (N));\ + (array) -= (ndarray)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (array) += (ndarray)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (results)->strides[ULAB_MAX_DIMS - 2];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 2]);\ + (array) -= (ndarray)->strides[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (array) += (ndarray)->strides[ULAB_MAX_DIMS - 3];\ + (rarray) -= (results)->strides[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (rarray) += (results)->strides[ULAB_MAX_DIMS - 3];\ + k++;\ + } while(k < (shape)[ULAB_MAX_DIMS - 3]);\ +} while(0) + +#define HEAPSORT(ndarray, type, array, shape, strides, index, increment, N) do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + HEAPSORT1(type, (array), (increment), (N));\ + (array) += (strides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ + (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ + (array) += (strides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (shape)[ULAB_MAX_DIMS - 2]);\ +} while(0) + +#define HEAP_ARGSORT(ndarray, type, array, shape, strides, index, increment, N, iarray, istrides, iincrement) do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + HEAP_ARGSORT1(type, (array), (increment), (N), (iarray), (iincrement));\ + (array) += (strides)[ULAB_MAX_DIMS - 1];\ + (iarray) += (istrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ + (iarray) -= (istrides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ + (iarray) += (istrides)[ULAB_MAX_DIMS - 2];\ + (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ + (array) += (strides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (shape)[ULAB_MAX_DIMS - 2]);\ +} while(0) + +#endif + +#if ULAB_MAX_DIMS == 4 +#define RUN_SUM(type, array, results, rarray, ss) do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_SUM1(type, (array), (results), (rarray), (ss));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 1] * (ss).shape[ULAB_MAX_DIMS - 1];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (ss).shape[ULAB_MAX_DIMS - 2]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 2] * (ss).shape[ULAB_MAX_DIMS - 2];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (ss).shape[ULAB_MAX_DIMS - 3]);\ +} while(0) + +#define RUN_MEAN(type, array, rarray, ss) do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_MEAN1(type, (array), (rarray), (ss));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 1] * (ss).shape[ULAB_MAX_DIMS - 1];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (ss).shape[ULAB_MAX_DIMS - 2]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 2] * (ss).shape[ULAB_MAX_DIMS - 2];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (ss).shape[ULAB_MAX_DIMS - 3]);\ +} while(0) + +#define RUN_STD(type, array, rarray, ss, div) do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_STD1(type, (array), (rarray), (ss), (div));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 1] * (ss).shape[ULAB_MAX_DIMS - 1];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (ss).shape[ULAB_MAX_DIMS - 2]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 2] * (ss).shape[ULAB_MAX_DIMS - 2];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (ss).shape[ULAB_MAX_DIMS - 3]);\ +} while(0) + +#define RUN_MEAN_STD(type, array, rarray, ss, div, isStd) do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_MEAN_STD1(type, (array), (rarray), (ss), (div), (isStd));\ + (array) -= (ss).strides[0] * (ss).shape[0];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (ss).shape[ULAB_MAX_DIMS - 1]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 1] * (ss).shape[ULAB_MAX_DIMS - 1];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (ss).shape[ULAB_MAX_DIMS - 2]);\ + (array) -= (ss).strides[ULAB_MAX_DIMS - 2] * (ss).shape[ULAB_MAX_DIMS - 2];\ + (array) += (ss).strides[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (ss).shape[ULAB_MAX_DIMS - 3]);\ +} while(0) + +#define RUN_ARGMIN(ndarray, type, array, results, rarray, shape, strides, index, op) do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_ARGMIN1((ndarray), type, (array), (results), (rarray), (index), (op));\ + (array) -= (ndarray)->strides[(index)] * (ndarray)->shape[(index)];\ + (array) += (strides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ + (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ + (array) += (strides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (shape)[ULAB_MAX_DIMS - 2]);\ + (array) -= (strides)[ULAB_MAX_DIMS - 2] * (shape)[ULAB_MAX_DIMS-2];\ + (array) += (strides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (shape)[ULAB_MAX_DIMS - 3]);\ +} while(0) + +#define RUN_DIFF(ndarray, type, array, results, rarray, shape, strides, index, stencil, N) do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + RUN_DIFF1((ndarray), type, (array), (results), (rarray), (index), (stencil), (N));\ + (array) -= (ndarray)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (array) += (ndarray)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (results)->strides[ULAB_MAX_DIMS - 2];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 2]);\ + (array) -= (strides)[ULAB_MAX_DIMS - 2] * (shape)[ULAB_MAX_DIMS-2];\ + (array) += (strides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (results)->strides[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (rarray) += (results)->strides[ULAB_MAX_DIMS - 3];\ + k++;\ + } while(k < (shape)[ULAB_MAX_DIMS - 3]);\ + (array) -= (strides)[ULAB_MAX_DIMS - 3] * (shape)[ULAB_MAX_DIMS-3];\ + (array) += (strides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (results)->strides[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (rarray) += (results)->strides[ULAB_MAX_DIMS - 4];\ + j++;\ + } while(j < (shape)[ULAB_MAX_DIMS - 4]);\ +} while(0) + +#define HEAPSORT(ndarray, type, array, shape, strides, index, increment, N) do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + HEAPSORT1(type, (array), (increment), (N));\ + (array) += (strides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ + (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ + (array) += (strides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (shape)[ULAB_MAX_DIMS - 2]);\ + (array) -= (strides)[ULAB_MAX_DIMS - 2] * (shape)[ULAB_MAX_DIMS-2];\ + (array) += (strides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (shape)[ULAB_MAX_DIMS - 3]);\ +} while(0) + +#define HEAP_ARGSORT(ndarray, type, array, shape, strides, index, increment, N, iarray, istrides, iincrement) do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + HEAP_ARGSORT1(type, (array), (increment), (N), (iarray), (iincrement));\ + (array) += (strides)[ULAB_MAX_DIMS - 1];\ + (iarray) += (istrides)[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ + (iarray) -= (istrides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ + (iarray) += (istrides)[ULAB_MAX_DIMS - 2];\ + (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ + (array) += (strides)[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (shape)[ULAB_MAX_DIMS - 2]);\ + (iarray) -= (istrides)[ULAB_MAX_DIMS - 2] * (shape)[ULAB_MAX_DIMS-2];\ + (iarray) += (istrides)[ULAB_MAX_DIMS - 3];\ + (array) -= (strides)[ULAB_MAX_DIMS - 2] * (shape)[ULAB_MAX_DIMS-2];\ + (array) += (strides)[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (shape)[ULAB_MAX_DIMS - 3]);\ +} while(0) + +#endif + +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_all_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_any_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_argmax_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_argmin_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_argsort_obj); +MP_DECLARE_CONST_FUN_OBJ_2(numerical_cross_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_diff_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_flip_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_max_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_mean_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_median_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_min_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_roll_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_std_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_sum_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_sort_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(numerical_sort_inplace_obj); + +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/numpy.c b/components/3rd_party/omv/omv/modules/ulab/code/numpy/numpy.c new file mode 100644 index 00000000..a53a32cf --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/numpy.c @@ -0,0 +1,387 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2020 Scott Shawcroft for Adafruit Industries + * 2020-2022 Zoltán Vörös + * 2020 Taku Fukada +*/ + +#include +#include +#include "py/runtime.h" + +#include "numpy.h" +#include "approx.h" +#include "carray/carray.h" +#include "compare.h" +#include "create.h" +#include "fft/fft.h" +#include "filter.h" +#include "io/io.h" +#include "linalg/linalg.h" +#include "numerical.h" +#include "stats.h" +#include "transform.h" +#include "poly.h" +#include "vector.h" + +//| """Compatibility layer for numpy""" +//| + +//| class ndarray: ... + +//| def get_printoptions() -> Dict[str, int]: +//| """Get printing options""" +//| ... +//| +//| def set_printoptions(threshold: Optional[int] = None, edgeitems: Optional[int] = None) -> None: +//| """Set printing options""" +//| ... +//| +//| def ndinfo(array: ulab.numpy.ndarray) -> None: +//| ... +//| +//| def array( +//| values: Union[ndarray, Iterable[Union[_float, _bool, Iterable[Any]]]], +//| *, +//| dtype: _DType = ulab.numpy.float +//| ) -> ulab.numpy.ndarray: +//| """alternate constructor function for `ulab.numpy.ndarray`. Mirrors numpy.array""" +//| ... + +// math constants +#if ULAB_NUMPY_HAS_E +ULAB_DEFINE_FLOAT_CONST(ulab_const_float_e, MP_E, 0x402df854UL, 0x4005bf0a8b145769ULL); +#endif + +#if ULAB_NUMPY_HAS_INF +ULAB_DEFINE_FLOAT_CONST(numpy_const_float_inf, (mp_float_t)INFINITY, 0x7f800000UL, 0x7ff0000000000000ULL); +#endif + +#if ULAB_NUMPY_HAS_NAN +ULAB_DEFINE_FLOAT_CONST(numpy_const_float_nan, (mp_float_t)NAN, 0x7fc00000UL, 0x7ff8000000000000ULL); +#endif + +#if ULAB_NUMPY_HAS_PI +ULAB_DEFINE_FLOAT_CONST(ulab_const_float_pi, MP_PI, 0x40490fdbUL, 0x400921fb54442d18ULL); +#endif + +static const mp_rom_map_elem_t ulab_numpy_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_numpy) }, + { MP_ROM_QSTR(MP_QSTR_ndarray), MP_ROM_PTR(&ulab_ndarray_type) }, + { MP_ROM_QSTR(MP_QSTR_array), MP_ROM_PTR(&ndarray_array_constructor_obj) }, + #if ULAB_NUMPY_HAS_FROMBUFFER + { MP_ROM_QSTR(MP_QSTR_frombuffer), MP_ROM_PTR(&create_frombuffer_obj) }, + #endif + // math constants + #if ULAB_NUMPY_HAS_E + { MP_ROM_QSTR(MP_QSTR_e), ULAB_REFERENCE_FLOAT_CONST(ulab_const_float_e) }, + #endif + #if ULAB_NUMPY_HAS_INF + { MP_ROM_QSTR(MP_QSTR_inf), ULAB_REFERENCE_FLOAT_CONST(numpy_const_float_inf) }, + #endif + #if ULAB_NUMPY_HAS_NAN + { MP_ROM_QSTR(MP_QSTR_nan), ULAB_REFERENCE_FLOAT_CONST(numpy_const_float_nan) }, + #endif + #if ULAB_NUMPY_HAS_PI + { MP_ROM_QSTR(MP_QSTR_pi), ULAB_REFERENCE_FLOAT_CONST(ulab_const_float_pi) }, + #endif + // class constants, always included + { MP_ROM_QSTR(MP_QSTR_bool), MP_ROM_INT(NDARRAY_BOOL) }, + { MP_ROM_QSTR(MP_QSTR_uint8), MP_ROM_INT(NDARRAY_UINT8) }, + { MP_ROM_QSTR(MP_QSTR_int8), MP_ROM_INT(NDARRAY_INT8) }, + { MP_ROM_QSTR(MP_QSTR_uint16), MP_ROM_INT(NDARRAY_UINT16) }, + { MP_ROM_QSTR(MP_QSTR_int16), MP_ROM_INT(NDARRAY_INT16) }, + { MP_ROM_QSTR(MP_QSTR_float), MP_ROM_INT(NDARRAY_FLOAT) }, + #if ULAB_SUPPORTS_COMPLEX + { MP_ROM_QSTR(MP_QSTR_complex), MP_ROM_INT(NDARRAY_COMPLEX) }, + #endif + // modules of numpy + #if ULAB_NUMPY_HAS_FFT_MODULE + { MP_ROM_QSTR(MP_QSTR_fft), MP_ROM_PTR(&ulab_fft_module) }, + #endif + #if ULAB_NUMPY_HAS_LINALG_MODULE + { MP_ROM_QSTR(MP_QSTR_linalg), MP_ROM_PTR(&ulab_linalg_module) }, + #endif + #if ULAB_HAS_PRINTOPTIONS + { MP_ROM_QSTR(MP_QSTR_set_printoptions), MP_ROM_PTR(&ndarray_set_printoptions_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_printoptions), MP_ROM_PTR(&ndarray_get_printoptions_obj) }, + #endif + #if ULAB_NUMPY_HAS_NDINFO + { MP_ROM_QSTR(MP_QSTR_ndinfo), MP_ROM_PTR(&ndarray_info_obj) }, + #endif + #if ULAB_NUMPY_HAS_ARANGE + { MP_ROM_QSTR(MP_QSTR_arange), MP_ROM_PTR(&create_arange_obj) }, + #endif + #if ULAB_NUMPY_HAS_COMPRESS + { MP_ROM_QSTR(MP_QSTR_compress), MP_ROM_PTR(&transform_compress_obj) }, + #endif + #if ULAB_NUMPY_HAS_CONCATENATE + { MP_ROM_QSTR(MP_QSTR_concatenate), MP_ROM_PTR(&create_concatenate_obj) }, + #endif + #if ULAB_NUMPY_HAS_DELETE + { MP_ROM_QSTR(MP_QSTR_delete), MP_ROM_PTR(&transform_delete_obj) }, + #endif + #if ULAB_NUMPY_HAS_DIAG + #if ULAB_MAX_DIMS > 1 + { MP_ROM_QSTR(MP_QSTR_diag), MP_ROM_PTR(&create_diag_obj) }, + #endif + #endif + #if ULAB_NUMPY_HAS_EMPTY + { MP_ROM_QSTR(MP_QSTR_empty), MP_ROM_PTR(&create_zeros_obj) }, + #endif + #if ULAB_MAX_DIMS > 1 + #if ULAB_NUMPY_HAS_EYE + { MP_ROM_QSTR(MP_QSTR_eye), MP_ROM_PTR(&create_eye_obj) }, + #endif + #endif /* ULAB_MAX_DIMS */ + // functions of the approx sub-module + #if ULAB_NUMPY_HAS_INTERP + { MP_ROM_QSTR(MP_QSTR_interp), MP_ROM_PTR(&approx_interp_obj) }, + #endif + #if ULAB_NUMPY_HAS_TRAPZ + { MP_ROM_QSTR(MP_QSTR_trapz), MP_ROM_PTR(&approx_trapz_obj) }, + #endif + // functions of the create sub-module + #if ULAB_NUMPY_HAS_FULL + { MP_ROM_QSTR(MP_QSTR_full), MP_ROM_PTR(&create_full_obj) }, + #endif + #if ULAB_NUMPY_HAS_LINSPACE + { MP_ROM_QSTR(MP_QSTR_linspace), MP_ROM_PTR(&create_linspace_obj) }, + #endif + #if ULAB_NUMPY_HAS_LOGSPACE + { MP_ROM_QSTR(MP_QSTR_logspace), MP_ROM_PTR(&create_logspace_obj) }, + #endif + #if ULAB_NUMPY_HAS_ONES + { MP_ROM_QSTR(MP_QSTR_ones), MP_ROM_PTR(&create_ones_obj) }, + #endif + #if ULAB_NUMPY_HAS_ZEROS + { MP_ROM_QSTR(MP_QSTR_zeros), MP_ROM_PTR(&create_zeros_obj) }, + #endif + // functions of the compare sub-module + #if ULAB_NUMPY_HAS_CLIP + { MP_ROM_QSTR(MP_QSTR_clip), MP_ROM_PTR(&compare_clip_obj) }, + #endif + #if ULAB_NUMPY_HAS_EQUAL + { MP_ROM_QSTR(MP_QSTR_equal), MP_ROM_PTR(&compare_equal_obj) }, + #endif + #if ULAB_NUMPY_HAS_NOTEQUAL + { MP_ROM_QSTR(MP_QSTR_not_equal), MP_ROM_PTR(&compare_not_equal_obj) }, + #endif + #if ULAB_NUMPY_HAS_ISFINITE + { MP_ROM_QSTR(MP_QSTR_isfinite), MP_ROM_PTR(&compare_isfinite_obj) }, + #endif + #if ULAB_NUMPY_HAS_ISINF + { MP_ROM_QSTR(MP_QSTR_isinf), MP_ROM_PTR(&compare_isinf_obj) }, + #endif + #if ULAB_NUMPY_HAS_MAXIMUM + { MP_ROM_QSTR(MP_QSTR_maximum), MP_ROM_PTR(&compare_maximum_obj) }, + #endif + #if ULAB_NUMPY_HAS_MINIMUM + { MP_ROM_QSTR(MP_QSTR_minimum), MP_ROM_PTR(&compare_minimum_obj) }, + #endif + #if ULAB_NUMPY_HAS_NONZERO + { MP_ROM_QSTR(MP_QSTR_nonzero), MP_ROM_PTR(&compare_nonzero_obj) }, + #endif + + #if ULAB_NUMPY_HAS_WHERE + { MP_ROM_QSTR(MP_QSTR_where), MP_ROM_PTR(&compare_where_obj) }, + #endif + // functions of the filter sub-module + #if ULAB_NUMPY_HAS_CONVOLVE + { MP_ROM_QSTR(MP_QSTR_convolve), MP_ROM_PTR(&filter_convolve_obj) }, + #endif + // functions of the numerical sub-module + #if ULAB_NUMPY_HAS_ALL + { MP_ROM_QSTR(MP_QSTR_all), MP_ROM_PTR(&numerical_all_obj) }, + #endif + #if ULAB_NUMPY_HAS_ANY + { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&numerical_any_obj) }, + #endif + #if ULAB_NUMPY_HAS_ARGMINMAX + { MP_ROM_QSTR(MP_QSTR_argmax), MP_ROM_PTR(&numerical_argmax_obj) }, + { MP_ROM_QSTR(MP_QSTR_argmin), MP_ROM_PTR(&numerical_argmin_obj) }, + #endif + #if ULAB_NUMPY_HAS_ARGSORT + { MP_ROM_QSTR(MP_QSTR_argsort), MP_ROM_PTR(&numerical_argsort_obj) }, + #endif + #if ULAB_NUMPY_HAS_ASARRAY + { MP_ROM_QSTR(MP_QSTR_asarray), MP_ROM_PTR(&create_asarray_obj) }, + #endif + #if ULAB_NUMPY_HAS_CROSS + { MP_ROM_QSTR(MP_QSTR_cross), MP_ROM_PTR(&numerical_cross_obj) }, + #endif + #if ULAB_NUMPY_HAS_DIFF + { MP_ROM_QSTR(MP_QSTR_diff), MP_ROM_PTR(&numerical_diff_obj) }, + #endif + #if ULAB_NUMPY_HAS_DOT + #if ULAB_MAX_DIMS > 1 + { MP_ROM_QSTR(MP_QSTR_dot), MP_ROM_PTR(&transform_dot_obj) }, + #endif + #endif + #if ULAB_NUMPY_HAS_TRACE + #if ULAB_MAX_DIMS > 1 + { MP_ROM_QSTR(MP_QSTR_trace), MP_ROM_PTR(&stats_trace_obj) }, + #endif + #endif + #if ULAB_NUMPY_HAS_FLIP + { MP_ROM_QSTR(MP_QSTR_flip), MP_ROM_PTR(&numerical_flip_obj) }, + #endif + #if ULAB_NUMPY_HAS_LOAD + { MP_ROM_QSTR(MP_QSTR_load), MP_ROM_PTR(&io_load_obj) }, + #endif + #if ULAB_NUMPY_HAS_LOADTXT + { MP_ROM_QSTR(MP_QSTR_loadtxt), MP_ROM_PTR(&io_loadtxt_obj) }, + #endif + #if ULAB_NUMPY_HAS_MINMAX + { MP_ROM_QSTR(MP_QSTR_max), MP_ROM_PTR(&numerical_max_obj) }, + #endif + #if ULAB_NUMPY_HAS_MEAN + { MP_ROM_QSTR(MP_QSTR_mean), MP_ROM_PTR(&numerical_mean_obj) }, + #endif + #if ULAB_NUMPY_HAS_MEDIAN + { MP_ROM_QSTR(MP_QSTR_median), MP_ROM_PTR(&numerical_median_obj) }, + #endif + #if ULAB_NUMPY_HAS_MINMAX + { MP_ROM_QSTR(MP_QSTR_min), MP_ROM_PTR(&numerical_min_obj) }, + #endif + #if ULAB_NUMPY_HAS_ROLL + { MP_ROM_QSTR(MP_QSTR_roll), MP_ROM_PTR(&numerical_roll_obj) }, + #endif + #if ULAB_NUMPY_HAS_SAVE + { MP_ROM_QSTR(MP_QSTR_save), MP_ROM_PTR(&io_save_obj) }, + #endif + #if ULAB_NUMPY_HAS_SAVETXT + { MP_ROM_QSTR(MP_QSTR_savetxt), MP_ROM_PTR(&io_savetxt_obj) }, + #endif + #if ULAB_NUMPY_HAS_SIZE + { MP_ROM_QSTR(MP_QSTR_size), MP_ROM_PTR(&transform_size_obj) }, + #endif + #if ULAB_NUMPY_HAS_SORT + { MP_ROM_QSTR(MP_QSTR_sort), MP_ROM_PTR(&numerical_sort_obj) }, + #endif + #if ULAB_NUMPY_HAS_STD + { MP_ROM_QSTR(MP_QSTR_std), MP_ROM_PTR(&numerical_std_obj) }, + #endif + #if ULAB_NUMPY_HAS_SUM + { MP_ROM_QSTR(MP_QSTR_sum), MP_ROM_PTR(&numerical_sum_obj) }, + #endif + // functions of the poly sub-module + #if ULAB_NUMPY_HAS_POLYFIT + { MP_ROM_QSTR(MP_QSTR_polyfit), MP_ROM_PTR(&poly_polyfit_obj) }, + #endif + #if ULAB_NUMPY_HAS_POLYVAL + { MP_ROM_QSTR(MP_QSTR_polyval), MP_ROM_PTR(&poly_polyval_obj) }, + #endif + // functions of the vector sub-module + #if ULAB_NUMPY_HAS_ACOS + { MP_ROM_QSTR(MP_QSTR_acos), MP_ROM_PTR(&vector_acos_obj) }, + #endif + #if ULAB_NUMPY_HAS_ACOSH + { MP_ROM_QSTR(MP_QSTR_acosh), MP_ROM_PTR(&vector_acosh_obj) }, + #endif + #if ULAB_NUMPY_HAS_ARCTAN2 + { MP_ROM_QSTR(MP_QSTR_arctan2), MP_ROM_PTR(&vector_arctan2_obj) }, + #endif + #if ULAB_NUMPY_HAS_AROUND + { MP_ROM_QSTR(MP_QSTR_around), MP_ROM_PTR(&vector_around_obj) }, + #endif + #if ULAB_NUMPY_HAS_ASIN + { MP_ROM_QSTR(MP_QSTR_asin), MP_ROM_PTR(&vector_asin_obj) }, + #endif + #if ULAB_NUMPY_HAS_ASINH + { MP_ROM_QSTR(MP_QSTR_asinh), MP_ROM_PTR(&vector_asinh_obj) }, + #endif + #if ULAB_NUMPY_HAS_ATAN + { MP_ROM_QSTR(MP_QSTR_atan), MP_ROM_PTR(&vector_atan_obj) }, + #endif + #if ULAB_NUMPY_HAS_ATANH + { MP_ROM_QSTR(MP_QSTR_atanh), MP_ROM_PTR(&vector_atanh_obj) }, + #endif + #if ULAB_NUMPY_HAS_CEIL + { MP_ROM_QSTR(MP_QSTR_ceil), MP_ROM_PTR(&vector_ceil_obj) }, + #endif + #if ULAB_NUMPY_HAS_COS + { MP_ROM_QSTR(MP_QSTR_cos), MP_ROM_PTR(&vector_cos_obj) }, + #endif + #if ULAB_NUMPY_HAS_COSH + { MP_ROM_QSTR(MP_QSTR_cosh), MP_ROM_PTR(&vector_cosh_obj) }, + #endif + #if ULAB_NUMPY_HAS_DEGREES + { MP_ROM_QSTR(MP_QSTR_degrees), MP_ROM_PTR(&vector_degrees_obj) }, + #endif + #if ULAB_NUMPY_HAS_EXP + { MP_ROM_QSTR(MP_QSTR_exp), MP_ROM_PTR(&vector_exp_obj) }, + #endif + #if ULAB_NUMPY_HAS_EXPM1 + { MP_ROM_QSTR(MP_QSTR_expm1), MP_ROM_PTR(&vector_expm1_obj) }, + #endif + #if ULAB_NUMPY_HAS_FLOOR + { MP_ROM_QSTR(MP_QSTR_floor), MP_ROM_PTR(&vector_floor_obj) }, + #endif + #if ULAB_NUMPY_HAS_LOG + { MP_ROM_QSTR(MP_QSTR_log), MP_ROM_PTR(&vector_log_obj) }, + #endif + #if ULAB_NUMPY_HAS_LOG10 + { MP_ROM_QSTR(MP_QSTR_log10), MP_ROM_PTR(&vector_log10_obj) }, + #endif + #if ULAB_NUMPY_HAS_LOG2 + { MP_ROM_QSTR(MP_QSTR_log2), MP_ROM_PTR(&vector_log2_obj) }, + #endif + #if ULAB_NUMPY_HAS_RADIANS + { MP_ROM_QSTR(MP_QSTR_radians), MP_ROM_PTR(&vector_radians_obj) }, + #endif + #if ULAB_NUMPY_HAS_SIN + { MP_ROM_QSTR(MP_QSTR_sin), MP_ROM_PTR(&vector_sin_obj) }, + #endif + #if ULAB_NUMPY_HAS_SINH + { MP_ROM_QSTR(MP_QSTR_sinh), MP_ROM_PTR(&vector_sinh_obj) }, + #endif + #if ULAB_NUMPY_HAS_SQRT + { MP_ROM_QSTR(MP_QSTR_sqrt), MP_ROM_PTR(&vector_sqrt_obj) }, + #endif + #if ULAB_NUMPY_HAS_TAN + { MP_ROM_QSTR(MP_QSTR_tan), MP_ROM_PTR(&vector_tan_obj) }, + #endif + #if ULAB_NUMPY_HAS_TANH + { MP_ROM_QSTR(MP_QSTR_tanh), MP_ROM_PTR(&vector_tanh_obj) }, + #endif + #if ULAB_NUMPY_HAS_VECTORIZE + { MP_ROM_QSTR(MP_QSTR_vectorize), MP_ROM_PTR(&vector_vectorize_obj) }, + #endif + #if ULAB_SUPPORTS_COMPLEX + #if ULAB_NUMPY_HAS_REAL + { MP_ROM_QSTR(MP_QSTR_real), MP_ROM_PTR(&carray_real_obj) }, + #endif + #if ULAB_NUMPY_HAS_IMAG + { MP_ROM_QSTR(MP_QSTR_imag), MP_ROM_PTR(&carray_imag_obj) }, + #endif + #if ULAB_NUMPY_HAS_CONJUGATE + { MP_ROM_QSTR(MP_QSTR_conjugate), MP_ROM_PTR(&carray_conjugate_obj) }, + #endif + #if ULAB_NUMPY_HAS_SORT_COMPLEX + { MP_ROM_QSTR(MP_QSTR_sort_complex), MP_ROM_PTR(&carray_sort_complex_obj) }, + #endif + #endif +}; + +static MP_DEFINE_CONST_DICT(mp_module_ulab_numpy_globals, ulab_numpy_globals_table); + +const mp_obj_module_t ulab_numpy_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_numpy_globals, +}; + +#if CIRCUITPY_ULAB +#if !defined(MICROPY_VERSION) || MICROPY_VERSION <= 70144 +MP_REGISTER_MODULE(MP_QSTR_ulab_dot_numpy, ulab_numpy_module, MODULE_ULAB_ENABLED); +#else +MP_REGISTER_MODULE(MP_QSTR_ulab_dot_numpy, ulab_numpy_module); +#endif +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/numpy.h b/components/3rd_party/omv/omv/modules/ulab/code/numpy/numpy.h new file mode 100644 index 00000000..f1348f38 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/numpy.h @@ -0,0 +1,21 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös + * +*/ + +#ifndef _NUMPY_ +#define _NUMPY_ + +#include "../ulab.h" +#include "../ndarray.h" + +extern const mp_obj_module_t ulab_numpy_module; + +#endif /* _NUMPY_ */ diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/poly.c b/components/3rd_party/omv/omv/modules/ulab/code/numpy/poly.c new file mode 100644 index 00000000..97ee5c75 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/poly.c @@ -0,0 +1,250 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * 2020 Jeff Epler for Adafruit Industries + * 2020 Scott Shawcroft for Adafruit Industries + * 2020 Taku Fukada +*/ + +#include "py/obj.h" +#include "py/runtime.h" +#include "py/objarray.h" + +#include "../ulab.h" +#include "linalg/linalg_tools.h" +#include "../ulab_tools.h" +#include "carray/carray_tools.h" +#include "poly.h" + +#if ULAB_NUMPY_HAS_POLYFIT + +mp_obj_t poly_polyfit(size_t n_args, const mp_obj_t *args) { + if(!ndarray_object_is_array_like(args[0])) { + mp_raise_ValueError(translate("input data must be an iterable")); + } + #if ULAB_SUPPORTS_COMPLEX + if(mp_obj_is_type(args[0], &ulab_ndarray_type)) { + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0]); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) + } + #endif + size_t lenx = 0, leny = 0; + uint8_t deg = 0; + mp_float_t *x, *XT, *y, *prod; + + if(n_args == 2) { // only the y values are supplied + // TODO: this is actually not enough: the first argument can very well be a matrix, + // in which case we are between the rock and a hard place + leny = (size_t)mp_obj_get_int(mp_obj_len_maybe(args[0])); + deg = (uint8_t)mp_obj_get_int(args[1]); + if(leny < deg) { + mp_raise_ValueError(translate("more degrees of freedom than data points")); + } + lenx = leny; + x = m_new(mp_float_t, lenx); // assume uniformly spaced data points + for(size_t i=0; i < lenx; i++) { + x[i] = i; + } + y = m_new(mp_float_t, leny); + fill_array_iterable(y, args[0]); + } else /* n_args == 3 */ { + if(!ndarray_object_is_array_like(args[1])) { + mp_raise_ValueError(translate("input data must be an iterable")); + } + lenx = (size_t)mp_obj_get_int(mp_obj_len_maybe(args[0])); + leny = (size_t)mp_obj_get_int(mp_obj_len_maybe(args[1])); + if(lenx != leny) { + mp_raise_ValueError(translate("input vectors must be of equal length")); + } + deg = (uint8_t)mp_obj_get_int(args[2]); + if(leny < deg) { + mp_raise_ValueError(translate("more degrees of freedom than data points")); + } + x = m_new(mp_float_t, lenx); + fill_array_iterable(x, args[0]); + y = m_new(mp_float_t, leny); + fill_array_iterable(y, args[1]); + } + + // one could probably express X as a function of XT, + // and thereby save RAM, because X is used only in the product + XT = m_new(mp_float_t, (deg+1)*leny); // XT is a matrix of shape (deg+1, len) (rows, columns) + for(size_t i=0; i < leny; i++) { // column index + XT[i+0*lenx] = 1.0; // top row + for(uint8_t j=1; j < deg+1; j++) { // row index + XT[i+j*leny] = XT[i+(j-1)*leny]*x[i]; + } + } + + prod = m_new(mp_float_t, (deg+1)*(deg+1)); // the product matrix is of shape (deg+1, deg+1) + mp_float_t sum; + for(uint8_t i=0; i < deg+1; i++) { // column index + for(uint8_t j=0; j < deg+1; j++) { // row index + sum = 0.0; + for(size_t k=0; k < lenx; k++) { + // (j, k) * (k, i) + // Note that the second matrix is simply the transpose of the first: + // X(k, i) = XT(i, k) = XT[k*lenx+i] + sum += XT[j*lenx+k]*XT[i*lenx+k]; // X[k*(deg+1)+i]; + } + prod[j*(deg+1)+i] = sum; + } + } + if(!linalg_invert_matrix(prod, deg+1)) { + // Although X was a Vandermonde matrix, whose inverse is guaranteed to exist, + // we bail out here, if prod couldn't be inverted: if the values in x are not all + // distinct, prod is singular + m_del(mp_float_t, XT, (deg+1)*lenx); + m_del(mp_float_t, x, lenx); + m_del(mp_float_t, y, lenx); + m_del(mp_float_t, prod, (deg+1)*(deg+1)); + mp_raise_ValueError(translate("could not invert Vandermonde matrix")); + } + // at this point, we have the inverse of X^T * X + // y is a column vector; x is free now, we can use it for storing intermediate values + for(uint8_t i=0; i < deg+1; i++) { // row index + sum = 0.0; + for(size_t j=0; j < lenx; j++) { // column index + sum += XT[i*lenx+j]*y[j]; + } + x[i] = sum; + } + // XT is no longer needed + m_del(mp_float_t, XT, (deg+1)*leny); + + ndarray_obj_t *beta = ndarray_new_linear_array(deg+1, NDARRAY_FLOAT); + mp_float_t *betav = (mp_float_t *)beta->array; + // x[0..(deg+1)] contains now the product X^T * y; we can get rid of y + m_del(float, y, leny); + + // now, we calculate beta, i.e., we apply prod = (X^T * X)^(-1) on x = X^T * y; x is a column vector now + for(uint8_t i=0; i < deg+1; i++) { + sum = 0.0; + for(uint8_t j=0; j < deg+1; j++) { + sum += prod[i*(deg+1)+j]*x[j]; + } + betav[i] = sum; + } + m_del(mp_float_t, x, lenx); + m_del(mp_float_t, prod, (deg+1)*(deg+1)); + for(uint8_t i=0; i < (deg+1)/2; i++) { + // We have to reverse the array, for the leading coefficient comes first. + SWAP(mp_float_t, betav[i], betav[deg-i]); + } + return MP_OBJ_FROM_PTR(beta); +} + +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(poly_polyfit_obj, 2, 3, poly_polyfit); +#endif + +#if ULAB_NUMPY_HAS_POLYVAL + +mp_obj_t poly_polyval(mp_obj_t o_p, mp_obj_t o_x) { + if(!ndarray_object_is_array_like(o_p) || !ndarray_object_is_array_like(o_x)) { + mp_raise_TypeError(translate("inputs are not iterable")); + } + #if ULAB_SUPPORTS_COMPLEX + ndarray_obj_t *input; + if(mp_obj_is_type(o_p, &ulab_ndarray_type)) { + input = MP_OBJ_TO_PTR(o_p); + COMPLEX_DTYPE_NOT_IMPLEMENTED(input->dtype) + } + if(mp_obj_is_type(o_x, &ulab_ndarray_type)) { + input = MP_OBJ_TO_PTR(o_x); + COMPLEX_DTYPE_NOT_IMPLEMENTED(input->dtype) + } + #endif + // p had better be a one-dimensional standard iterable + uint8_t plen = mp_obj_get_int(mp_obj_len_maybe(o_p)); + mp_float_t *p = m_new(mp_float_t, plen); + mp_obj_iter_buf_t p_buf; + mp_obj_t p_item, p_iterable = mp_getiter(o_p, &p_buf); + uint8_t i = 0; + while((p_item = mp_iternext(p_iterable)) != MP_OBJ_STOP_ITERATION) { + p[i] = mp_obj_get_float(p_item); + i++; + } + + // polynomials are going to be of type float, except, when both + // the coefficients and the independent variable are integers + ndarray_obj_t *ndarray; + if(mp_obj_is_type(o_x, &ulab_ndarray_type)) { + ndarray_obj_t *source = MP_OBJ_TO_PTR(o_x); + uint8_t *sarray = (uint8_t *)source->array; + ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_FLOAT); + mp_float_t *array = (mp_float_t *)ndarray->array; + + mp_float_t (*func)(void *) = ndarray_get_float_function(source->dtype); + + // TODO: these loops are really nothing, but the re-impplementation of + // ITERATE_VECTOR from vectorise.c. We could pass a function pointer here + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_float_t y = p[0]; + mp_float_t _x = func(sarray); + for(uint8_t m=0; m < plen-1; m++) { + y *= _x; + y += p[m+1]; + } + *array++ = y; + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif + } else { + // o_x had better be a one-dimensional standard iterable + ndarray = ndarray_new_linear_array(mp_obj_get_int(mp_obj_len_maybe(o_x)), NDARRAY_FLOAT); + mp_float_t *array = (mp_float_t *)ndarray->array; + mp_obj_iter_buf_t x_buf; + mp_obj_t x_item, x_iterable = mp_getiter(o_x, &x_buf); + while ((x_item = mp_iternext(x_iterable)) != MP_OBJ_STOP_ITERATION) { + mp_float_t _x = mp_obj_get_float(x_item); + mp_float_t y = p[0]; + for(uint8_t j=0; j < plen-1; j++) { + y *= _x; + y += p[j+1]; + } + *array++ = y; + } + } + m_del(mp_float_t, p, plen); + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_2(poly_polyval_obj, poly_polyval); +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/poly.h b/components/3rd_party/omv/omv/modules/ulab/code/numpy/poly.h new file mode 100644 index 00000000..59cb9f51 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/poly.h @@ -0,0 +1,21 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös +*/ + +#ifndef _POLY_ +#define _POLY_ + +#include "../ulab.h" +#include "../ndarray.h" + +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(poly_polyfit_obj); +MP_DECLARE_CONST_FUN_OBJ_2(poly_polyval_obj); + +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/stats.c b/components/3rd_party/omv/omv/modules/ulab/code/numpy/stats.c new file mode 100644 index 00000000..2d348893 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/stats.c @@ -0,0 +1,54 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * 2020 Scott Shawcroft for Adafruit Industries + * 2020 Roberto Colistete Jr. + * 2020 Taku Fukada + * +*/ + +#include +#include +#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" + +#include "../ulab.h" +#include "../ulab_tools.h" +#include "carray/carray_tools.h" +#include "stats.h" + +#if ULAB_MAX_DIMS > 1 +#if ULAB_NUMPY_HAS_TRACE + +//| def trace(m: ulab.numpy.ndarray) -> _float: +//| """ +//| :param m: a square matrix +//| +//| Compute the trace of the matrix, the sum of its diagonal elements.""" +//| ... +//| + +static mp_obj_t stats_trace(mp_obj_t oin) { + ndarray_obj_t *ndarray = tools_object_is_square(oin); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) + mp_float_t trace = 0.0; + for(size_t i=0; i < ndarray->shape[ULAB_MAX_DIMS - 1]; i++) { + int32_t pos = i * (ndarray->strides[ULAB_MAX_DIMS - 1] + ndarray->strides[ULAB_MAX_DIMS - 2]); + trace += ndarray_get_float_index(ndarray->array, ndarray->dtype, pos/ndarray->itemsize); + } + if(ndarray->dtype == NDARRAY_FLOAT) { + return mp_obj_new_float(trace); + } + return mp_obj_new_int_from_float(trace); +} + +MP_DEFINE_CONST_FUN_OBJ_1(stats_trace_obj, stats_trace); +#endif +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/stats.h b/components/3rd_party/omv/omv/modules/ulab/code/numpy/stats.h new file mode 100644 index 00000000..62bba9ff --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/stats.h @@ -0,0 +1,20 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös +*/ + +#ifndef _STATS_ +#define _STATS_ + +#include "../ulab.h" +#include "../ndarray.h" + +MP_DECLARE_CONST_FUN_OBJ_1(stats_trace_obj); + +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/transform.c b/components/3rd_party/omv/omv/modules/ulab/code/numpy/transform.c new file mode 100644 index 00000000..6a849b94 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/transform.c @@ -0,0 +1,451 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * +*/ + +#include +#include +#include +#include +#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" + +#include "../ulab.h" +#include "../ulab_tools.h" +#include "carray/carray_tools.h" +#include "numerical.h" +#include "transform.h" + +#if ULAB_NUMPY_HAS_COMPRESS +static mp_obj_t transform_compress(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t condition = args[0].u_obj; + + if(!mp_obj_is_type(args[1].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("wrong input type")); + } + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[1].u_obj); + uint8_t *array = (uint8_t *)ndarray->array; + + mp_obj_t axis = args[2].u_obj; + + size_t len = MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(condition)); + int8_t ax, shift_ax = 0; + + if(axis != mp_const_none) { + ax = tools_get_axis(axis, ndarray->ndim); + shift_ax = ULAB_MAX_DIMS - ndarray->ndim + ax; + } + + if(((axis == mp_const_none) && (len != ndarray->len)) || + ((axis != mp_const_none) && (len != ndarray->shape[shift_ax]))) { + mp_raise_ValueError(translate("wrong length of condition array")); + } + + size_t true_count = 0; + mp_obj_iter_buf_t iter_buf; + mp_obj_t item, iterable = mp_getiter(condition, &iter_buf); + while((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + if(mp_obj_is_true(item)) { + true_count++; + } + } + + iterable = mp_getiter(condition, &iter_buf); + + ndarray_obj_t *result = NULL; + + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + memcpy(shape, ndarray->shape, ULAB_MAX_DIMS * sizeof(size_t)); + + size_t *rshape = m_new(size_t, ULAB_MAX_DIMS); + memcpy(rshape, ndarray->shape, ULAB_MAX_DIMS * sizeof(size_t)); + + int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); + memcpy(strides, ndarray->strides, ULAB_MAX_DIMS * sizeof(int32_t)); + + int32_t *rstrides = m_new0(int32_t, ULAB_MAX_DIMS); + + if(axis == mp_const_none) { + result = ndarray_new_linear_array(true_count, ndarray->dtype); + + rstrides[ULAB_MAX_DIMS - 1] = ndarray->itemsize; + rshape[ULAB_MAX_DIMS - 1] = 0; + } else { + rshape[shift_ax] = true_count; + + result = ndarray_new_dense_ndarray(ndarray->ndim, rshape, ndarray->dtype); + + SWAP(size_t, shape[shift_ax], shape[ULAB_MAX_DIMS - 1]); + SWAP(size_t, rshape[shift_ax], rshape[ULAB_MAX_DIMS - 1]); + SWAP(int32_t, strides[shift_ax], strides[ULAB_MAX_DIMS - 1]); + + memcpy(rstrides, result->strides, ULAB_MAX_DIMS * sizeof(int32_t)); + SWAP(int32_t, rstrides[shift_ax], rstrides[ULAB_MAX_DIMS - 1]); + } + + uint8_t *rarray = (uint8_t *)result->array; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + if(axis != mp_const_none) { + iterable = mp_getiter(condition, &iter_buf); + } + do { + item = mp_iternext(iterable); + if(mp_obj_is_true(item)) { + memcpy(rarray, array, ndarray->itemsize); + rarray += rstrides[ULAB_MAX_DIMS - 1]; + } + array += strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + array -= strides[ULAB_MAX_DIMS - 1] * shape[ULAB_MAX_DIMS - 1]; + array += strides[ULAB_MAX_DIMS - 2]; + rarray -= rstrides[ULAB_MAX_DIMS - 1] * rshape[ULAB_MAX_DIMS - 1]; + rarray += rstrides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + array -= strides[ULAB_MAX_DIMS - 2] * shape[ULAB_MAX_DIMS - 2]; + array += strides[ULAB_MAX_DIMS - 3]; + rarray -= rstrides[ULAB_MAX_DIMS - 2] * rshape[ULAB_MAX_DIMS - 2]; + rarray += rstrides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= strides[ULAB_MAX_DIMS - 3] * shape[ULAB_MAX_DIMS - 3]; + array += strides[ULAB_MAX_DIMS - 4]; + rarray -= rstrides[ULAB_MAX_DIMS - 2] * rshape[ULAB_MAX_DIMS - 2]; + rarray += rstrides[ULAB_MAX_DIMS - 3]; + i++; + } while(i < shape[ULAB_MAX_DIMS - 4]); + #endif + + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(size_t, rshape, ULAB_MAX_DIMS); + m_del(int32_t, strides, ULAB_MAX_DIMS); + m_del(int32_t, rstrides, ULAB_MAX_DIMS); + + return MP_OBJ_FROM_PTR(result); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(transform_compress_obj, 2, transform_compress); +#endif /* ULAB_NUMPY_HAS_COMPRESS */ + +#if ULAB_NUMPY_HAS_DELETE +static mp_obj_t transform_delete(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("first argument must be an ndarray")); + } + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj); + uint8_t *array = (uint8_t *)ndarray->array; + + mp_obj_t indices = args[1].u_obj; + + mp_obj_t axis = args[2].u_obj; + + int8_t shift_ax; + + size_t axis_len; + + if(axis != mp_const_none) { + int8_t ax = tools_get_axis(axis, ndarray->ndim); + shift_ax = ULAB_MAX_DIMS - ndarray->ndim + ax; + axis_len = ndarray->shape[shift_ax]; + } else { + axis_len = ndarray->len; + } + + size_t index_len; + if(mp_obj_is_int(indices)) { + index_len = 1; + } else { + if(mp_obj_len_maybe(indices) == MP_OBJ_NULL) { + mp_raise_TypeError(translate("wrong index type")); + } + index_len = MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(indices)); + } + + if(index_len > axis_len) { + mp_raise_ValueError(translate("wrong length of index array")); + } + + size_t *index_array = m_new(size_t, index_len); + + if(mp_obj_is_int(indices)) { + ssize_t value = (ssize_t)mp_obj_get_int(indices); + if(value < 0) { + value += axis_len; + } + if((value < 0) || (value > (ssize_t)axis_len)) { + mp_raise_ValueError(translate("index is out of bounds")); + } else { + *index_array++ = (size_t)value; + } + } else { + mp_obj_iter_buf_t iter_buf; + mp_obj_t item, iterable = mp_getiter(indices, &iter_buf); + while((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + ssize_t value = (ssize_t)mp_obj_get_int(item); + if(value < 0) { + value += axis_len; + } + if((value < 0) || (value > (ssize_t)axis_len)) { + mp_raise_ValueError(translate("index is out of bounds")); + } else { + *index_array++ = (size_t)value; + } + } + } + + // sort the array, since it is not guaranteed that the input is sorted + HEAPSORT1(size_t, index_array, 1, index_len); + + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + memcpy(shape, ndarray->shape, ULAB_MAX_DIMS * sizeof(size_t)); + + size_t *rshape = m_new(size_t, ULAB_MAX_DIMS); + memcpy(rshape, ndarray->shape, ULAB_MAX_DIMS * sizeof(size_t)); + + int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); + memcpy(strides, ndarray->strides, ULAB_MAX_DIMS * sizeof(int32_t)); + + int32_t *rstrides = m_new0(int32_t, ULAB_MAX_DIMS); + + ndarray_obj_t *result = NULL; + + if(axis == mp_const_none) { + result = ndarray_new_linear_array(ndarray->len - index_len, ndarray->dtype); + rstrides[ULAB_MAX_DIMS - 1] = ndarray->itemsize; + memset(rshape, 0, sizeof(size_t) * ULAB_MAX_DIMS); + } else { + rshape[shift_ax] = shape[shift_ax] - index_len; + + result = ndarray_new_dense_ndarray(ndarray->ndim, rshape, ndarray->dtype); + + SWAP(size_t, shape[shift_ax], shape[ULAB_MAX_DIMS - 1]); + SWAP(size_t, rshape[shift_ax], rshape[ULAB_MAX_DIMS - 1]); + SWAP(int32_t, strides[shift_ax], strides[ULAB_MAX_DIMS - 1]); + + memcpy(rstrides, result->strides, ULAB_MAX_DIMS * sizeof(int32_t)); + SWAP(int32_t, rstrides[shift_ax], rstrides[ULAB_MAX_DIMS - 1]); + } + + uint8_t *rarray = (uint8_t *)result->array; + index_array -= index_len; + size_t count = 0; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + if(count == *index_array) { + index_array++; + } else { + memcpy(rarray, array, ndarray->itemsize); + rarray += rstrides[ULAB_MAX_DIMS - 1]; + } + array += strides[ULAB_MAX_DIMS - 1]; + l++; + count++; + } while(l < shape[ULAB_MAX_DIMS - 1]); + if(axis != mp_const_none) { + index_array -= index_len; + count = 0; + } + #if ULAB_MAX_DIMS > 1 + array -= strides[ULAB_MAX_DIMS - 1] * shape[ULAB_MAX_DIMS - 1]; + array += strides[ULAB_MAX_DIMS - 2]; + rarray -= rstrides[ULAB_MAX_DIMS - 1] * rshape[ULAB_MAX_DIMS - 1]; + rarray += rstrides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + array -= strides[ULAB_MAX_DIMS - 2] * shape[ULAB_MAX_DIMS - 2]; + array += strides[ULAB_MAX_DIMS - 3]; + rarray -= rstrides[ULAB_MAX_DIMS - 2] * rshape[ULAB_MAX_DIMS - 2]; + rarray += rstrides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + array -= strides[ULAB_MAX_DIMS - 3] * shape[ULAB_MAX_DIMS - 3]; + array += strides[ULAB_MAX_DIMS - 4]; + rarray -= rstrides[ULAB_MAX_DIMS - 3] * rshape[ULAB_MAX_DIMS - 3]; + rarray += rstrides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < shape[ULAB_MAX_DIMS - 4]); + #endif + + // TODO: deleting shape generates a seg fault + // m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(size_t, rshape, ULAB_MAX_DIMS); + m_del(int32_t, strides, ULAB_MAX_DIMS); + m_del(int32_t, rstrides, ULAB_MAX_DIMS); + + return MP_OBJ_FROM_PTR(result); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(transform_delete_obj, 2, transform_delete); +#endif /* ULAB_NUMPY_HAS_DELETE */ + + +#if ULAB_MAX_DIMS > 1 +#if ULAB_NUMPY_HAS_DOT +//| def dot(m1: ulab.numpy.ndarray, m2: ulab.numpy.ndarray) -> Union[ulab.numpy.ndarray, _float]: +//| """ +//| :param ~ulab.numpy.ndarray m1: a matrix, or a vector +//| :param ~ulab.numpy.ndarray m2: a matrix, or a vector +//| +//| Computes the product of two matrices, or two vectors. In the letter case, the inner product is returned.""" +//| ... +//| + +mp_obj_t transform_dot(mp_obj_t _m1, mp_obj_t _m2) { + // TODO: should the results be upcast? + // This implements 2D operations only! + if(!mp_obj_is_type(_m1, &ulab_ndarray_type) || !mp_obj_is_type(_m2, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("arguments must be ndarrays")); + } + ndarray_obj_t *m1 = MP_OBJ_TO_PTR(_m1); + ndarray_obj_t *m2 = MP_OBJ_TO_PTR(_m2); + COMPLEX_DTYPE_NOT_IMPLEMENTED(m1->dtype) + COMPLEX_DTYPE_NOT_IMPLEMENTED(m2->dtype) + + uint8_t *array1 = (uint8_t *)m1->array; + uint8_t *array2 = (uint8_t *)m2->array; + + mp_float_t (*func1)(void *) = ndarray_get_float_function(m1->dtype); + mp_float_t (*func2)(void *) = ndarray_get_float_function(m2->dtype); + + if(m1->shape[ULAB_MAX_DIMS - 1] != m2->shape[ULAB_MAX_DIMS - m2->ndim]) { + mp_raise_ValueError(translate("dimensions do not match")); + } + uint8_t ndim = MIN(m1->ndim, m2->ndim); + size_t shape1 = m1->ndim == 2 ? m1->shape[ULAB_MAX_DIMS - m1->ndim] : 1; + size_t shape2 = m2->ndim == 2 ? m2->shape[ULAB_MAX_DIMS - 1] : 1; + + size_t *shape = NULL; + if(ndim == 2) { // matrix times matrix -> matrix + shape = ndarray_shape_vector(0, 0, shape1, shape2); + } else { // matrix times vector -> vector, vector times vector -> vector (size 1) + shape = ndarray_shape_vector(0, 0, 0, shape1 * shape2); + } + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + mp_float_t *rarray = (mp_float_t *)results->array; + + for(size_t i=0; i < shape1; i++) { // rows of m1 + for(size_t j=0; j < shape2; j++) { // columns of m2 + mp_float_t dot = 0.0; + for(size_t k=0; k < m1->shape[ULAB_MAX_DIMS - 1]; k++) { + // (i, k) * (k, j) + dot += func1(array1) * func2(array2); + array1 += m1->strides[ULAB_MAX_DIMS - 1]; + array2 += m2->strides[ULAB_MAX_DIMS - m2->ndim]; + } + *rarray++ = dot; + array1 -= m1->strides[ULAB_MAX_DIMS - 1] * m1->shape[ULAB_MAX_DIMS - 1]; + array2 -= m2->strides[ULAB_MAX_DIMS - m2->ndim] * m2->shape[ULAB_MAX_DIMS - m2->ndim]; + array2 += m2->strides[ULAB_MAX_DIMS - 1]; + } + array1 += m1->strides[ULAB_MAX_DIMS - m1->ndim]; + array2 = m2->array; + } + if((m1->ndim * m2->ndim) == 1) { // return a scalar, if product of two vectors + return mp_obj_new_float(*(--rarray)); + } else { + return MP_OBJ_FROM_PTR(results); + } +} + +MP_DEFINE_CONST_FUN_OBJ_2(transform_dot_obj, transform_dot); +#endif /* ULAB_NUMPY_HAS_DOT */ +#endif /* ULAB_MAX_DIMS > 1 */ + +#if ULAB_NUMPY_HAS_SIZE +static mp_obj_t transform_size(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(ulab_tools_mp_obj_is_scalar(args[0].u_obj)) { + return mp_obj_new_int(1); + } + + if(!ndarray_object_is_array_like(args[0].u_obj)) { + mp_raise_TypeError(translate("first argument must be an ndarray")); + } + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + return mp_obj_len_maybe(args[0].u_obj); + } + + // at this point, the args[0] is most certainly an ndarray + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj); + mp_obj_t axis = args[1].u_obj; + size_t len; + if(axis != mp_const_none) { + int8_t ax = tools_get_axis(axis, ndarray->ndim); + len = ndarray->shape[ULAB_MAX_DIMS - ndarray->ndim + ax]; + } else { + len = ndarray->len; + } + + return mp_obj_new_int(len); +} +MP_DEFINE_CONST_FUN_OBJ_KW(transform_size_obj, 1, transform_size); +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/transform.h b/components/3rd_party/omv/omv/modules/ulab/code/numpy/transform.h new file mode 100644 index 00000000..bfb4482c --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/transform.h @@ -0,0 +1,30 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * +*/ + +#ifndef _TRANSFORM_ +#define _TRANSFORM_ + +#include +#include +#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" + +#include "../ulab.h" +#include "../ulab_tools.h" + +MP_DECLARE_CONST_FUN_OBJ_KW(transform_compress_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(transform_delete_obj); +MP_DECLARE_CONST_FUN_OBJ_2(transform_dot_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(transform_size_obj); + +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/vector.c b/components/3rd_party/omv/omv/modules/ulab/code/numpy/vector.c new file mode 100644 index 00000000..01e209ea --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/vector.c @@ -0,0 +1,899 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * 2020 Jeff Epler for Adafruit Industries + * 2020 Scott Shawcroft for Adafruit Industries + * 2020 Taku Fukada +*/ + +#include +#include +#include +#include "py/runtime.h" +#include "py/binary.h" +#include "py/obj.h" +#include "py/objarray.h" + +#include "../ulab.h" +#include "../ulab_tools.h" +#include "carray/carray_tools.h" +#include "vector.h" + +//| """Element-by-element functions +//| +//| These functions can operate on numbers, 1-D iterables, and arrays of 1 to 4 dimensions by +//| applying the function to every element in the array. This is typically +//| much more efficient than expressing the same operation as a Python loop.""" +//| + +static mp_obj_t vector_generic_vector(mp_obj_t o_in, mp_float_t (*f)(mp_float_t)) { + // Return a single value, if o_in is not iterable + if(mp_obj_is_float(o_in) || mp_obj_is_int(o_in)) { + return mp_obj_new_float(f(mp_obj_get_float(o_in))); + } + ndarray_obj_t *ndarray = NULL; + if(mp_obj_is_type(o_in, &ulab_ndarray_type)) { + ndarray_obj_t *source = MP_OBJ_TO_PTR(o_in); + COMPLEX_DTYPE_NOT_IMPLEMENTED(source->dtype) + uint8_t *sarray = (uint8_t *)source->array; + ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_FLOAT); + mp_float_t *array = (mp_float_t *)ndarray->array; + + #if ULAB_VECTORISE_USES_FUN_POINTER + + mp_float_t (*func)(void *) = ndarray_get_float_function(source->dtype); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_float_t value = func(sarray); + *array++ = f(value); + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ + #else + if(source->dtype == NDARRAY_UINT8) { + ITERATE_VECTOR(uint8_t, array, source, sarray); + } else if(source->dtype == NDARRAY_INT8) { + ITERATE_VECTOR(int8_t, array, source, sarray); + } else if(source->dtype == NDARRAY_UINT16) { + ITERATE_VECTOR(uint16_t, array, source, sarray); + } else if(source->dtype == NDARRAY_INT16) { + ITERATE_VECTOR(int16_t, array, source, sarray); + } else { + ITERATE_VECTOR(mp_float_t, array, source, sarray); + } + #endif /* ULAB_VECTORISE_USES_FUN_POINTER */ + } else { + ndarray = ndarray_from_mp_obj(o_in, 0); + mp_float_t *narray = (mp_float_t *)ndarray->array; + for(size_t i = 0; i < ndarray->len; i++) { + *narray = f(*narray); + narray++; + } + } + return MP_OBJ_FROM_PTR(ndarray); +} + +#if ULAB_NUMPY_HAS_ACOS +//| def acos(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the inverse cosine function""" +//| ... +//| + +MATH_FUN_1(acos, acos); +MP_DEFINE_CONST_FUN_OBJ_1(vector_acos_obj, vector_acos); +#endif + +#if ULAB_NUMPY_HAS_ACOSH +//| def acosh(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the inverse hyperbolic cosine function""" +//| ... +//| + +MATH_FUN_1(acosh, acosh); +MP_DEFINE_CONST_FUN_OBJ_1(vector_acosh_obj, vector_acosh); +#endif + +#if ULAB_NUMPY_HAS_ASIN +//| def asin(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the inverse sine function""" +//| ... +//| + +MATH_FUN_1(asin, asin); +MP_DEFINE_CONST_FUN_OBJ_1(vector_asin_obj, vector_asin); +#endif + +#if ULAB_NUMPY_HAS_ASINH +//| def asinh(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the inverse hyperbolic sine function""" +//| ... +//| + +MATH_FUN_1(asinh, asinh); +MP_DEFINE_CONST_FUN_OBJ_1(vector_asinh_obj, vector_asinh); +#endif + +#if ULAB_NUMPY_HAS_AROUND +//| def around(a: _ArrayLike, *, decimals: int = 0) -> ulab.numpy.ndarray: +//| """Returns a new float array in which each element is rounded to +//| ``decimals`` places.""" +//| ... +//| + +mp_obj_t vector_around(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_decimals, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0 } } + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("first argument must be an ndarray")); + } + int8_t n = args[1].u_int; + mp_float_t mul = MICROPY_FLOAT_C_FUN(pow)(10.0, n); + ndarray_obj_t *source = MP_OBJ_TO_PTR(args[0].u_obj); + COMPLEX_DTYPE_NOT_IMPLEMENTED(source->dtype) + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_FLOAT); + mp_float_t *narray = (mp_float_t *)ndarray->array; + uint8_t *sarray = (uint8_t *)source->array; + + mp_float_t (*func)(void *) = ndarray_get_float_function(source->dtype); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_float_t f = func(sarray); + *narray++ = MICROPY_FLOAT_C_FUN(round)(f * mul) / mul; + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif + return MP_OBJ_FROM_PTR(ndarray); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(vector_around_obj, 1, vector_around); +#endif + +#if ULAB_NUMPY_HAS_ATAN +//| def atan(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the inverse tangent function; the return values are in the +//| range [-pi/2,pi/2].""" +//| ... +//| + +MATH_FUN_1(atan, atan); +MP_DEFINE_CONST_FUN_OBJ_1(vector_atan_obj, vector_atan); +#endif + +#if ULAB_NUMPY_HAS_ARCTAN2 +//| def arctan2(ya: _ArrayLike, xa: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the inverse tangent function of y/x; the return values are in +//| the range [-pi, pi].""" +//| ... +//| + +mp_obj_t vector_arctan2(mp_obj_t y, mp_obj_t x) { + if((mp_obj_is_float(y) || mp_obj_is_int(y)) && + (mp_obj_is_float(x) || mp_obj_is_int(x))) { + mp_float_t _y = mp_obj_get_float(y); + mp_float_t _x = mp_obj_get_float(x); + return mp_obj_new_float(MICROPY_FLOAT_C_FUN(atan2)(_y, _x)); + } + + ndarray_obj_t *ndarray_x = ndarray_from_mp_obj(x, 0); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray_x->dtype) + + ndarray_obj_t *ndarray_y = ndarray_from_mp_obj(y, 0); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray_y->dtype) + + uint8_t ndim = 0; + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + int32_t *xstrides = m_new(int32_t, ULAB_MAX_DIMS); + int32_t *ystrides = m_new(int32_t, ULAB_MAX_DIMS); + if(!ndarray_can_broadcast(ndarray_x, ndarray_y, &ndim, shape, xstrides, ystrides)) { + mp_raise_ValueError(translate("operands could not be broadcast together")); + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(int32_t, xstrides, ULAB_MAX_DIMS); + m_del(int32_t, ystrides, ULAB_MAX_DIMS); + } + + uint8_t *xarray = (uint8_t *)ndarray_x->array; + uint8_t *yarray = (uint8_t *)ndarray_y->array; + + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + mp_float_t *rarray = (mp_float_t *)results->array; + + mp_float_t (*funcx)(void *) = ndarray_get_float_function(ndarray_x->dtype); + mp_float_t (*funcy)(void *) = ndarray_get_float_function(ndarray_y->dtype); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_float_t _x = funcx(xarray); + mp_float_t _y = funcy(yarray); + *rarray++ = MICROPY_FLOAT_C_FUN(atan2)(_y, _x); + xarray += xstrides[ULAB_MAX_DIMS - 1]; + yarray += ystrides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < results->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + xarray -= xstrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + xarray += xstrides[ULAB_MAX_DIMS - 2]; + yarray -= ystrides[ULAB_MAX_DIMS - 1] * results->shape[ULAB_MAX_DIMS-1]; + yarray += ystrides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < results->shape[ULAB_MAX_DIMS - 2]); + #endif + #if ULAB_MAX_DIMS > 2 + xarray -= xstrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + xarray += xstrides[ULAB_MAX_DIMS - 3]; + yarray -= ystrides[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + yarray += ystrides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < results->shape[ULAB_MAX_DIMS - 3]); + #endif + #if ULAB_MAX_DIMS > 3 + xarray -= xstrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + xarray += xstrides[ULAB_MAX_DIMS - 4]; + yarray -= ystrides[ULAB_MAX_DIMS - 3] * results->shape[ULAB_MAX_DIMS-3]; + yarray += ystrides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < results->shape[ULAB_MAX_DIMS - 4]); + #endif + + return MP_OBJ_FROM_PTR(results); +} + +MP_DEFINE_CONST_FUN_OBJ_2(vector_arctan2_obj, vector_arctan2); +#endif /* ULAB_VECTORISE_HAS_ARCTAN2 */ + +#if ULAB_NUMPY_HAS_ATANH +//| def atanh(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the inverse hyperbolic tangent function""" +//| ... +//| + +MATH_FUN_1(atanh, atanh); +MP_DEFINE_CONST_FUN_OBJ_1(vector_atanh_obj, vector_atanh); +#endif + +#if ULAB_NUMPY_HAS_CEIL +//| def ceil(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Rounds numbers up to the next whole number""" +//| ... +//| + +MATH_FUN_1(ceil, ceil); +MP_DEFINE_CONST_FUN_OBJ_1(vector_ceil_obj, vector_ceil); +#endif + +#if ULAB_NUMPY_HAS_COS +//| def cos(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the cosine function""" +//| ... +//| + +MATH_FUN_1(cos, cos); +MP_DEFINE_CONST_FUN_OBJ_1(vector_cos_obj, vector_cos); +#endif + +#if ULAB_NUMPY_HAS_COSH +//| def cosh(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the hyperbolic cosine function""" +//| ... +//| + +MATH_FUN_1(cosh, cosh); +MP_DEFINE_CONST_FUN_OBJ_1(vector_cosh_obj, vector_cosh); +#endif + +#if ULAB_NUMPY_HAS_DEGREES +//| def degrees(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Converts angles from radians to degrees""" +//| ... +//| + +static mp_float_t vector_degrees_(mp_float_t value) { + return value * MICROPY_FLOAT_CONST(180.0) / MP_PI; +} + +static mp_obj_t vector_degrees(mp_obj_t x_obj) { + return vector_generic_vector(x_obj, vector_degrees_); +} + +MP_DEFINE_CONST_FUN_OBJ_1(vector_degrees_obj, vector_degrees); +#endif + +#if ULAB_SCIPY_SPECIAL_HAS_ERF +//| def erf(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the error function, which has applications in statistics""" +//| ... +//| + +MATH_FUN_1(erf, erf); +MP_DEFINE_CONST_FUN_OBJ_1(vector_erf_obj, vector_erf); +#endif + +#if ULAB_SCIPY_SPECIAL_HAS_ERFC +//| def erfc(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the complementary error function, which has applications in statistics""" +//| ... +//| + +MATH_FUN_1(erfc, erfc); +MP_DEFINE_CONST_FUN_OBJ_1(vector_erfc_obj, vector_erfc); +#endif + +#if ULAB_NUMPY_HAS_EXP +//| def exp(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the exponent function.""" +//| ... +//| + +static mp_obj_t vector_exp(mp_obj_t o_in) { + #if ULAB_SUPPORTS_COMPLEX + if(mp_obj_is_type(o_in, &mp_type_complex)) { + mp_float_t real, imag; + mp_obj_get_complex(o_in, &real, &imag); + mp_float_t exp_real = MICROPY_FLOAT_C_FUN(exp)(real); + return mp_obj_new_complex(exp_real * MICROPY_FLOAT_C_FUN(cos)(imag), exp_real * MICROPY_FLOAT_C_FUN(sin)(imag)); + } else if(mp_obj_is_type(o_in, &ulab_ndarray_type)) { + ndarray_obj_t *source = MP_OBJ_TO_PTR(o_in); + if(source->dtype == NDARRAY_COMPLEX) { + uint8_t *sarray = (uint8_t *)source->array; + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_COMPLEX); + mp_float_t *array = (mp_float_t *)ndarray->array; + uint8_t itemsize = sizeof(mp_float_t); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_float_t real = *(mp_float_t *)sarray; + mp_float_t imag = *(mp_float_t *)(sarray + itemsize); + mp_float_t exp_real = MICROPY_FLOAT_C_FUN(exp)(real); + *array++ = exp_real * MICROPY_FLOAT_C_FUN(cos)(imag); + *array++ = exp_real * MICROPY_FLOAT_C_FUN(sin)(imag); + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ + return MP_OBJ_FROM_PTR(ndarray); + } + } + #endif + return vector_generic_vector(o_in, MICROPY_FLOAT_C_FUN(exp)); +} + +MP_DEFINE_CONST_FUN_OBJ_1(vector_exp_obj, vector_exp); +#endif + +#if ULAB_NUMPY_HAS_EXPM1 +//| def expm1(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes $e^x-1$. In certain applications, using this function preserves numeric accuracy better than the `exp` function.""" +//| ... +//| + +MATH_FUN_1(expm1, expm1); +MP_DEFINE_CONST_FUN_OBJ_1(vector_expm1_obj, vector_expm1); +#endif + +#if ULAB_NUMPY_HAS_FLOOR +//| def floor(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Rounds numbers up to the next whole number""" +//| ... +//| + +MATH_FUN_1(floor, floor); +MP_DEFINE_CONST_FUN_OBJ_1(vector_floor_obj, vector_floor); +#endif + +#if ULAB_SCIPY_SPECIAL_HAS_GAMMA +//| def gamma(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the gamma function""" +//| ... +//| + +MATH_FUN_1(gamma, tgamma); +MP_DEFINE_CONST_FUN_OBJ_1(vector_gamma_obj, vector_gamma); +#endif + +#if ULAB_SCIPY_SPECIAL_HAS_GAMMALN +//| def lgamma(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the natural log of the gamma function""" +//| ... +//| + +MATH_FUN_1(lgamma, lgamma); +MP_DEFINE_CONST_FUN_OBJ_1(vector_lgamma_obj, vector_lgamma); +#endif + +#if ULAB_NUMPY_HAS_LOG +//| def log(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the natural log""" +//| ... +//| + +MATH_FUN_1(log, log); +MP_DEFINE_CONST_FUN_OBJ_1(vector_log_obj, vector_log); +#endif + +#if ULAB_NUMPY_HAS_LOG10 +//| def log10(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the log base 10""" +//| ... +//| + +MATH_FUN_1(log10, log10); +MP_DEFINE_CONST_FUN_OBJ_1(vector_log10_obj, vector_log10); +#endif + +#if ULAB_NUMPY_HAS_LOG2 +//| def log2(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the log base 2""" +//| ... +//| + +MATH_FUN_1(log2, log2); +MP_DEFINE_CONST_FUN_OBJ_1(vector_log2_obj, vector_log2); +#endif + +#if ULAB_NUMPY_HAS_RADIANS +//| def radians(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Converts angles from degrees to radians""" +//| ... +//| + +static mp_float_t vector_radians_(mp_float_t value) { + return value * MP_PI / MICROPY_FLOAT_CONST(180.0); +} + +static mp_obj_t vector_radians(mp_obj_t x_obj) { + return vector_generic_vector(x_obj, vector_radians_); +} + +MP_DEFINE_CONST_FUN_OBJ_1(vector_radians_obj, vector_radians); +#endif + +#if ULAB_NUMPY_HAS_SIN +//| def sin(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the sine function""" +//| ... +//| + +MATH_FUN_1(sin, sin); +MP_DEFINE_CONST_FUN_OBJ_1(vector_sin_obj, vector_sin); +#endif + +#if ULAB_NUMPY_HAS_SINH +//| def sinh(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the hyperbolic sine""" +//| ... +//| + +MATH_FUN_1(sinh, sinh); +MP_DEFINE_CONST_FUN_OBJ_1(vector_sinh_obj, vector_sinh); +#endif + + +#if ULAB_NUMPY_HAS_SQRT +//| def sqrt(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the square root""" +//| ... +//| + +#if ULAB_SUPPORTS_COMPLEX +mp_obj_t vector_sqrt(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_INT(NDARRAY_FLOAT) } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t o_in = args[0].u_obj; + uint8_t dtype = mp_obj_get_int(args[1].u_obj); + if((dtype != NDARRAY_FLOAT) && (dtype != NDARRAY_COMPLEX)) { + mp_raise_TypeError(translate("dtype must be float, or complex")); + } + + if(mp_obj_is_type(o_in, &mp_type_complex)) { + mp_float_t real, imag; + mp_obj_get_complex(o_in, &real, &imag); + mp_float_t sqrt_abs = MICROPY_FLOAT_C_FUN(sqrt)(real * real + imag * imag); + sqrt_abs = MICROPY_FLOAT_C_FUN(sqrt)(sqrt_abs); + mp_float_t theta = MICROPY_FLOAT_CONST(0.5) * MICROPY_FLOAT_C_FUN(atan2)(imag, real); + return mp_obj_new_complex(sqrt_abs * MICROPY_FLOAT_C_FUN(cos)(theta), sqrt_abs * MICROPY_FLOAT_C_FUN(sin)(theta)); + } else if(mp_obj_is_type(o_in, &ulab_ndarray_type)) { + ndarray_obj_t *source = MP_OBJ_TO_PTR(o_in); + if((source->dtype == NDARRAY_COMPLEX) && (dtype == NDARRAY_FLOAT)) { + mp_raise_TypeError(translate("can't convert complex to float")); + } + + if(dtype == NDARRAY_COMPLEX) { + if(source->dtype == NDARRAY_COMPLEX) { + uint8_t *sarray = (uint8_t *)source->array; + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_COMPLEX); + mp_float_t *array = (mp_float_t *)ndarray->array; + uint8_t itemsize = sizeof(mp_float_t); + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_float_t real = *(mp_float_t *)sarray; + mp_float_t imag = *(mp_float_t *)(sarray + itemsize); + mp_float_t sqrt_abs = MICROPY_FLOAT_C_FUN(sqrt)(real * real + imag * imag); + sqrt_abs = MICROPY_FLOAT_C_FUN(sqrt)(sqrt_abs); + mp_float_t theta = MICROPY_FLOAT_CONST(0.5) * MICROPY_FLOAT_C_FUN(atan2)(imag, real); + *array++ = sqrt_abs * MICROPY_FLOAT_C_FUN(cos)(theta); + *array++ = sqrt_abs * MICROPY_FLOAT_C_FUN(sin)(theta); + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ + return MP_OBJ_FROM_PTR(ndarray); + } else if(source->dtype == NDARRAY_FLOAT) { + uint8_t *sarray = (uint8_t *)source->array; + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, NDARRAY_COMPLEX); + mp_float_t *array = (mp_float_t *)ndarray->array; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + mp_float_t value = *(mp_float_t *)sarray; + if(value >= MICROPY_FLOAT_CONST(0.0)) { + *array++ = MICROPY_FLOAT_C_FUN(sqrt)(value); + array++; + } else { + array++; + *array++ = MICROPY_FLOAT_C_FUN(sqrt)(-value); + } + sarray += source->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS-1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS-2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS-3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ + return MP_OBJ_FROM_PTR(ndarray); + } else { + mp_raise_TypeError(translate("input dtype must be float or complex")); + } + } + } + return vector_generic_vector(o_in, MICROPY_FLOAT_C_FUN(sqrt)); +} +MP_DEFINE_CONST_FUN_OBJ_KW(vector_sqrt_obj, 1, vector_sqrt); +#else +MATH_FUN_1(sqrt, sqrt); +MP_DEFINE_CONST_FUN_OBJ_1(vector_sqrt_obj, vector_sqrt); +#endif /* ULAB_SUPPORTS_COMPLEX */ + +#endif /* ULAB_NUMPY_HAS_SQRT */ + +#if ULAB_NUMPY_HAS_TAN +//| def tan(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the tangent""" +//| ... +//| + +MATH_FUN_1(tan, tan); +MP_DEFINE_CONST_FUN_OBJ_1(vector_tan_obj, vector_tan); +#endif + +#if ULAB_NUMPY_HAS_TANH +//| def tanh(a: _ArrayLike) -> ulab.numpy.ndarray: +//| """Computes the hyperbolic tangent""" +//| ... + +MATH_FUN_1(tanh, tanh); +MP_DEFINE_CONST_FUN_OBJ_1(vector_tanh_obj, vector_tanh); +#endif + +#if ULAB_NUMPY_HAS_VECTORIZE +static mp_obj_t vector_vectorized_function_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void) n_args; + (void) n_kw; + vectorized_function_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_t avalue[1]; + mp_obj_t fvalue; + if(mp_obj_is_type(args[0], &ulab_ndarray_type)) { + ndarray_obj_t *source = MP_OBJ_TO_PTR(args[0]); + COMPLEX_DTYPE_NOT_IMPLEMENTED(source->dtype) + + ndarray_obj_t *ndarray = ndarray_new_dense_ndarray(source->ndim, source->shape, self->otypes); + uint8_t *sarray = (uint8_t *)source->array; + uint8_t *narray = (uint8_t *)ndarray->array; + + #if ULAB_MAX_DIMS > 3 + size_t i = 0; + do { + #endif + #if ULAB_MAX_DIMS > 2 + size_t j = 0; + do { + #endif + #if ULAB_MAX_DIMS > 1 + size_t k = 0; + do { + #endif + size_t l = 0; + do { + avalue[0] = mp_binary_get_val_array(source->dtype, sarray, 0); + fvalue = MP_OBJ_TYPE_GET_SLOT(self->type, call)(self->fun, 1, 0, avalue); + ndarray_set_value(self->otypes, narray, 0, fvalue); + sarray += source->strides[ULAB_MAX_DIMS - 1]; + narray += ndarray->itemsize; + l++; + } while(l < source->shape[ULAB_MAX_DIMS - 1]); + #if ULAB_MAX_DIMS > 1 + sarray -= source->strides[ULAB_MAX_DIMS - 1] * source->shape[ULAB_MAX_DIMS - 1]; + sarray += source->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < source->shape[ULAB_MAX_DIMS - 2]); + #endif /* ULAB_MAX_DIMS > 1 */ + #if ULAB_MAX_DIMS > 2 + sarray -= source->strides[ULAB_MAX_DIMS - 2] * source->shape[ULAB_MAX_DIMS - 2]; + sarray += source->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < source->shape[ULAB_MAX_DIMS - 3]); + #endif /* ULAB_MAX_DIMS > 2 */ + #if ULAB_MAX_DIMS > 3 + sarray -= source->strides[ULAB_MAX_DIMS - 3] * source->shape[ULAB_MAX_DIMS - 3]; + sarray += source->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < source->shape[ULAB_MAX_DIMS - 4]); + #endif /* ULAB_MAX_DIMS > 3 */ + + return MP_OBJ_FROM_PTR(ndarray); + } else if(mp_obj_is_type(args[0], &mp_type_tuple) || mp_obj_is_type(args[0], &mp_type_list) || + mp_obj_is_type(args[0], &mp_type_range)) { // i.e., the input is a generic iterable + size_t len = (size_t)mp_obj_get_int(mp_obj_len_maybe(args[0])); + ndarray_obj_t *ndarray = ndarray_new_linear_array(len, self->otypes); + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(args[0], &iter_buf); + size_t i=0; + while ((avalue[0] = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + fvalue = MP_OBJ_TYPE_GET_SLOT(self->type, call)(self->fun, 1, 0, avalue); + ndarray_set_value(self->otypes, ndarray->array, i, fvalue); + i++; + } + return MP_OBJ_FROM_PTR(ndarray); + } else if(mp_obj_is_int(args[0]) || mp_obj_is_float(args[0])) { + ndarray_obj_t *ndarray = ndarray_new_linear_array(1, self->otypes); + fvalue = MP_OBJ_TYPE_GET_SLOT(self->type, call)(self->fun, 1, 0, args); + ndarray_set_value(self->otypes, ndarray->array, 0, fvalue); + return MP_OBJ_FROM_PTR(ndarray); + } else { + mp_raise_ValueError(translate("wrong input type")); + } + return mp_const_none; +} + +#if defined(MP_DEFINE_CONST_OBJ_TYPE) +MP_DEFINE_CONST_OBJ_TYPE( + vector_function_type, + MP_QSTR_, + MP_TYPE_FLAG_NONE, + call, vector_vectorized_function_call +); +#else +const mp_obj_type_t vector_function_type = { + { &mp_type_type }, + .flags = MP_TYPE_FLAG_EXTENDED, + .name = MP_QSTR_, + MP_TYPE_EXTENDED_FIELDS( + .call = vector_vectorized_function_call, + ) +}; +#endif + +//| def vectorize( +//| f: Union[Callable[[int], _float], Callable[[_float], _float]], +//| *, +//| otypes: Optional[_DType] = None +//| ) -> Callable[[_ArrayLike], ulab.numpy.ndarray]: +//| """ +//| :param callable f: The function to wrap +//| :param otypes: List of array types that may be returned by the function. None is interpreted to mean the return value is float. +//| +//| Wrap a Python function ``f`` so that it can be applied to arrays. +//| The callable must return only values of the types specified by ``otypes``, or the result is undefined.""" +//| ... +//| + +static mp_obj_t vector_vectorize(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_otypes, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} } + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + const mp_obj_type_t *type = mp_obj_get_type(args[0].u_obj); + if(!MP_OBJ_TYPE_HAS_SLOT(type, call)) { + mp_raise_TypeError(translate("first argument must be a callable")); + } + mp_obj_t _otypes = args[1].u_obj; + uint8_t otypes = NDARRAY_FLOAT; + if(_otypes == mp_const_none) { + // TODO: is this what numpy does? + otypes = NDARRAY_FLOAT; + } else if(mp_obj_is_int(_otypes)) { + otypes = mp_obj_get_int(_otypes); + if(otypes != NDARRAY_FLOAT && otypes != NDARRAY_UINT8 && otypes != NDARRAY_INT8 && + otypes != NDARRAY_UINT16 && otypes != NDARRAY_INT16) { + mp_raise_ValueError(translate("wrong output type")); + } + } + else { + mp_raise_ValueError(translate("wrong output type")); + } + vectorized_function_obj_t *function = m_new_obj(vectorized_function_obj_t); + function->base.type = &vector_function_type; + function->otypes = otypes; + function->fun = args[0].u_obj; + function->type = type; + return MP_OBJ_FROM_PTR(function); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(vector_vectorize_obj, 1, vector_vectorize); +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/numpy/vector.h b/components/3rd_party/omv/omv/modules/ulab/code/numpy/vector.h new file mode 100644 index 00000000..ea38b0fd --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/numpy/vector.h @@ -0,0 +1,161 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös +*/ + +#ifndef _VECTOR_ +#define _VECTOR_ + +#include "../ulab.h" +#include "../ndarray.h" + +MP_DECLARE_CONST_FUN_OBJ_1(vector_acos_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_acosh_obj); +MP_DECLARE_CONST_FUN_OBJ_2(vector_arctan2_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(vector_around_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_asin_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_asinh_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_atan_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_atanh_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_ceil_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_cos_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_cosh_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_degrees_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_erf_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_erfc_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_exp_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_expm1_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_floor_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_gamma_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_lgamma_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_log_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_log10_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_log2_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_radians_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_sin_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_sinh_obj); +#if ULAB_SUPPORTS_COMPLEX +MP_DECLARE_CONST_FUN_OBJ_KW(vector_sqrt_obj); +#else +MP_DECLARE_CONST_FUN_OBJ_1(vector_sqrt_obj); +#endif +MP_DECLARE_CONST_FUN_OBJ_1(vector_tan_obj); +MP_DECLARE_CONST_FUN_OBJ_1(vector_tanh_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(vector_vectorize_obj); + +typedef struct _vectorized_function_obj_t { + mp_obj_base_t base; + uint8_t otypes; + mp_obj_t fun; + const mp_obj_type_t *type; +} vectorized_function_obj_t; + +#if ULAB_HAS_FUNCTION_ITERATOR +#define ITERATE_VECTOR(type, array, source, sarray, shift)\ +({\ + size_t *scoords = ndarray_new_coords((source)->ndim);\ + for(size_t i=0; i < (source)->len/(source)->shape[ULAB_MAX_DIMS -1]; i++) {\ + for(size_t l=0; l < (source)->shape[ULAB_MAX_DIMS - 1]; l++) {\ + *(array) = f(*((type *)(sarray)));\ + (array) += (shift);\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 1];\ + }\ + ndarray_rewind_array((source)->ndim, sarray, (source)->shape, (source)->strides, scoords);\ + }\ +}) + +#else + +#if ULAB_MAX_DIMS == 4 +#define ITERATE_VECTOR(type, array, source, sarray) do {\ + size_t i=0;\ + do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *(array)++ = f(*((type *)(sarray)));\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (source)->shape[ULAB_MAX_DIMS-1]);\ + (sarray) -= (source)->strides[ULAB_MAX_DIMS - 1] * (source)->shape[ULAB_MAX_DIMS-1];\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (source)->shape[ULAB_MAX_DIMS-2]);\ + (sarray) -= (source)->strides[ULAB_MAX_DIMS - 2] * (source)->shape[ULAB_MAX_DIMS-2];\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (source)->shape[ULAB_MAX_DIMS-3]);\ + (sarray) -= (source)->strides[ULAB_MAX_DIMS - 3] * (source)->shape[ULAB_MAX_DIMS-3];\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 4];\ + i++;\ + } while(i < (source)->shape[ULAB_MAX_DIMS-4]);\ +} while(0) +#endif /* ULAB_MAX_DIMS == 4 */ + +#if ULAB_MAX_DIMS == 3 +#define ITERATE_VECTOR(type, array, source, sarray) do {\ + size_t j = 0;\ + do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *(array)++ = f(*((type *)(sarray)));\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (source)->shape[ULAB_MAX_DIMS-1]);\ + (sarray) -= (source)->strides[ULAB_MAX_DIMS - 1] * (source)->shape[ULAB_MAX_DIMS-1];\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (source)->shape[ULAB_MAX_DIMS-2]);\ + (sarray) -= (source)->strides[ULAB_MAX_DIMS - 2] * (source)->shape[ULAB_MAX_DIMS-2];\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 3];\ + j++;\ + } while(j < (source)->shape[ULAB_MAX_DIMS-3]);\ +} while(0) +#endif /* ULAB_MAX_DIMS == 3 */ + +#if ULAB_MAX_DIMS == 2 +#define ITERATE_VECTOR(type, array, source, sarray) do {\ + size_t k = 0;\ + do {\ + size_t l = 0;\ + do {\ + *(array)++ = f(*((type *)(sarray)));\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (source)->shape[ULAB_MAX_DIMS-1]);\ + (sarray) -= (source)->strides[ULAB_MAX_DIMS - 1] * (source)->shape[ULAB_MAX_DIMS-1];\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 2];\ + k++;\ + } while(k < (source)->shape[ULAB_MAX_DIMS-2]);\ +} while(0) +#endif /* ULAB_MAX_DIMS == 2 */ + +#if ULAB_MAX_DIMS == 1 +#define ITERATE_VECTOR(type, array, source, sarray) do {\ + size_t l = 0;\ + do {\ + *(array)++ = f(*((type *)(sarray)));\ + (sarray) += (source)->strides[ULAB_MAX_DIMS - 1];\ + l++;\ + } while(l < (source)->shape[ULAB_MAX_DIMS-1]);\ +} while(0) +#endif /* ULAB_MAX_DIMS == 1 */ +#endif /* ULAB_HAS_FUNCTION_ITERATOR */ + +#define MATH_FUN_1(py_name, c_name) \ + static mp_obj_t vector_ ## py_name(mp_obj_t x_obj) { \ + return vector_generic_vector(x_obj, MICROPY_FLOAT_C_FUN(c_name)); \ +} + +#endif /* _VECTOR_ */ diff --git a/components/3rd_party/omv/omv/modules/ulab/code/scipy/linalg/linalg.c b/components/3rd_party/omv/omv/modules/ulab/code/scipy/linalg/linalg.c new file mode 100644 index 00000000..52c04afe --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/scipy/linalg/linalg.c @@ -0,0 +1,285 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Vikas Udupa + * +*/ + +#include +#include +#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" + +#include "../../ulab.h" +#include "../../ulab_tools.h" +#include "../../numpy/linalg/linalg_tools.h" +#include "linalg.h" + +#if ULAB_SCIPY_HAS_LINALG_MODULE +//| +//| import ulab.scipy +//| import ulab.numpy +//| +//| """Linear algebra functions""" +//| + +#if ULAB_MAX_DIMS > 1 + +//| def solve_triangular(A: ulab.numpy.ndarray, b: ulab.numpy.ndarray, lower: bool) -> ulab.numpy.ndarray: +//| """ +//| :param ~ulab.numpy.ndarray A: a matrix +//| :param ~ulab.numpy.ndarray b: a vector +//| :param ~bool lower: if true, use only data contained in lower triangle of A, else use upper triangle of A +//| :return: solution to the system A x = b. Shape of return matches b +//| :raises TypeError: if A and b are not of type ndarray and are not dense +//| :raises ValueError: if A is a singular matrix +//| +//| Solve the equation A x = b for x, assuming A is a triangular matrix""" +//| ... +//| + +static mp_obj_t solve_triangular(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + + size_t i, j; + + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE} } , + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE} } , + { MP_QSTR_lower, MP_ARG_OBJ, { .u_rom_obj = MP_ROM_TRUE } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type) || !mp_obj_is_type(args[1].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("first two arguments must be ndarrays")); + } + + ndarray_obj_t *A = MP_OBJ_TO_PTR(args[0].u_obj); + ndarray_obj_t *b = MP_OBJ_TO_PTR(args[1].u_obj); + + if(!ndarray_is_dense(A) || !ndarray_is_dense(b)) { + mp_raise_TypeError(translate("input must be a dense ndarray")); + } + + size_t A_rows = A->shape[ULAB_MAX_DIMS - 2]; + size_t A_cols = A->shape[ULAB_MAX_DIMS - 1]; + + uint8_t *A_arr = (uint8_t *)A->array; + uint8_t *b_arr = (uint8_t *)b->array; + + mp_float_t (*get_A_ele)(void *) = ndarray_get_float_function(A->dtype); + mp_float_t (*get_b_ele)(void *) = ndarray_get_float_function(b->dtype); + + uint8_t *temp_A = A_arr; + + // check if input matrix A is singular + for (i = 0; i < A_rows; i++) { + if (MICROPY_FLOAT_C_FUN(fabs)(get_A_ele(A_arr)) < LINALG_EPSILON) + mp_raise_ValueError(translate("input matrix is singular")); + A_arr += A->strides[ULAB_MAX_DIMS - 2]; + A_arr += A->strides[ULAB_MAX_DIMS - 1]; + } + + A_arr = temp_A; + + ndarray_obj_t *x = ndarray_new_dense_ndarray(b->ndim, b->shape, NDARRAY_FLOAT); + mp_float_t *x_arr = (mp_float_t *)x->array; + + if (mp_obj_is_true(args[2].u_obj)) { + // Solve the lower triangular matrix by iterating each row of A. + // Start by finding the first unknown using the first row. + // On finding this unknown, find the second unknown using the second row. + // Continue the same till the last unknown is found using the last row. + + for (i = 0; i < A_rows; i++) { + mp_float_t sum = 0.0; + for (j = 0; j < i; j++) { + sum += (get_A_ele(A_arr) * (*x_arr++)); + A_arr += A->strides[ULAB_MAX_DIMS - 1]; + } + + sum = (get_b_ele(b_arr) - sum) / (get_A_ele(A_arr)); + *x_arr = sum; + + x_arr -= j; + A_arr -= A->strides[ULAB_MAX_DIMS - 1] * j; + A_arr += A->strides[ULAB_MAX_DIMS - 2]; + b_arr += b->strides[ULAB_MAX_DIMS - 1]; + } + } else { + // Solve the upper triangular matrix by iterating each row of A. + // Start by finding the last unknown using the last row. + // On finding this unknown, find the last-but-one unknown using the last-but-one row. + // Continue the same till the first unknown is found using the first row. + + A_arr += (A->strides[ULAB_MAX_DIMS - 2] * A_rows); + b_arr += (b->strides[ULAB_MAX_DIMS - 1] * A_cols); + x_arr += A_cols; + + for (i = A_rows - 1; i < A_rows; i--) { + mp_float_t sum = 0.0; + for (j = i + 1; j < A_cols; j++) { + sum += (get_A_ele(A_arr) * (*x_arr++)); + A_arr += A->strides[ULAB_MAX_DIMS - 1]; + } + + x_arr -= (j - i); + A_arr -= (A->strides[ULAB_MAX_DIMS - 1] * (j - i)); + b_arr -= b->strides[ULAB_MAX_DIMS - 1]; + + sum = (get_b_ele(b_arr) - sum) / get_A_ele(A_arr); + *x_arr = sum; + + A_arr -= A->strides[ULAB_MAX_DIMS - 2]; + } + } + + return MP_OBJ_FROM_PTR(x); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(linalg_solve_triangular_obj, 2, solve_triangular); + +//| def cho_solve(L: ulab.numpy.ndarray, b: ulab.numpy.ndarray) -> ulab.numpy.ndarray: +//| """ +//| :param ~ulab.numpy.ndarray L: the lower triangular, Cholesky factorization of A +//| :param ~ulab.numpy.ndarray b: right-hand-side vector b +//| :return: solution to the system A x = b. Shape of return matches b +//| :raises TypeError: if L and b are not of type ndarray and are not dense +//| +//| Solve the linear equations A x = b, given the Cholesky factorization of A as input""" +//| ... +//| + +static mp_obj_t cho_solve(mp_obj_t _L, mp_obj_t _b) { + + if(!mp_obj_is_type(_L, &ulab_ndarray_type) || !mp_obj_is_type(_b, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("first two arguments must be ndarrays")); + } + + ndarray_obj_t *L = MP_OBJ_TO_PTR(_L); + ndarray_obj_t *b = MP_OBJ_TO_PTR(_b); + + if(!ndarray_is_dense(L) || !ndarray_is_dense(b)) { + mp_raise_TypeError(translate("input must be a dense ndarray")); + } + + mp_float_t (*get_L_ele)(void *) = ndarray_get_float_function(L->dtype); + mp_float_t (*get_b_ele)(void *) = ndarray_get_float_function(b->dtype); + void (*set_L_ele)(void *, mp_float_t) = ndarray_set_float_function(L->dtype); + + size_t L_rows = L->shape[ULAB_MAX_DIMS - 2]; + size_t L_cols = L->shape[ULAB_MAX_DIMS - 1]; + + // Obtain transpose of the input matrix L in L_t + size_t L_t_shape[ULAB_MAX_DIMS]; + size_t L_t_rows = L_t_shape[ULAB_MAX_DIMS - 2] = L_cols; + size_t L_t_cols = L_t_shape[ULAB_MAX_DIMS - 1] = L_rows; + ndarray_obj_t *L_t = ndarray_new_dense_ndarray(L->ndim, L_t_shape, L->dtype); + + uint8_t *L_arr = (uint8_t *)L->array; + uint8_t *L_t_arr = (uint8_t *)L_t->array; + uint8_t *b_arr = (uint8_t *)b->array; + + size_t i, j; + + uint8_t *L_ptr = L_arr; + uint8_t *L_t_ptr = L_t_arr; + for (i = 0; i < L_rows; i++) { + for (j = 0; j < L_cols; j++) { + set_L_ele(L_t_ptr, get_L_ele(L_ptr)); + L_t_ptr += L_t->strides[ULAB_MAX_DIMS - 2]; + L_ptr += L->strides[ULAB_MAX_DIMS - 1]; + } + + L_t_ptr -= j * L_t->strides[ULAB_MAX_DIMS - 2]; + L_t_ptr += L_t->strides[ULAB_MAX_DIMS - 1]; + L_ptr -= j * L->strides[ULAB_MAX_DIMS - 1]; + L_ptr += L->strides[ULAB_MAX_DIMS - 2]; + } + + ndarray_obj_t *x = ndarray_new_dense_ndarray(b->ndim, b->shape, NDARRAY_FLOAT); + mp_float_t *x_arr = (mp_float_t *)x->array; + + ndarray_obj_t *y = ndarray_new_dense_ndarray(b->ndim, b->shape, NDARRAY_FLOAT); + mp_float_t *y_arr = (mp_float_t *)y->array; + + // solve L y = b to obtain y, where L_t x = y + for (i = 0; i < L_rows; i++) { + mp_float_t sum = 0.0; + for (j = 0; j < i; j++) { + sum += (get_L_ele(L_arr) * (*y_arr++)); + L_arr += L->strides[ULAB_MAX_DIMS - 1]; + } + + sum = (get_b_ele(b_arr) - sum) / (get_L_ele(L_arr)); + *y_arr = sum; + + y_arr -= j; + L_arr -= L->strides[ULAB_MAX_DIMS - 1] * j; + L_arr += L->strides[ULAB_MAX_DIMS - 2]; + b_arr += b->strides[ULAB_MAX_DIMS - 1]; + } + + // using y, solve L_t x = y to obtain x + L_t_arr += (L_t->strides[ULAB_MAX_DIMS - 2] * L_t_rows); + y_arr += L_t_cols; + x_arr += L_t_cols; + + for (i = L_t_rows - 1; i < L_t_rows; i--) { + mp_float_t sum = 0.0; + for (j = i + 1; j < L_t_cols; j++) { + sum += (get_L_ele(L_t_arr) * (*x_arr++)); + L_t_arr += L_t->strides[ULAB_MAX_DIMS - 1]; + } + + x_arr -= (j - i); + L_t_arr -= (L_t->strides[ULAB_MAX_DIMS - 1] * (j - i)); + y_arr--; + + sum = ((*y_arr) - sum) / get_L_ele(L_t_arr); + *x_arr = sum; + + L_t_arr -= L_t->strides[ULAB_MAX_DIMS - 2]; + } + + return MP_OBJ_FROM_PTR(x); +} + +MP_DEFINE_CONST_FUN_OBJ_2(linalg_cho_solve_obj, cho_solve); + +#endif + +static const mp_rom_map_elem_t ulab_scipy_linalg_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_linalg) }, + #if ULAB_MAX_DIMS > 1 + #if ULAB_SCIPY_LINALG_HAS_SOLVE_TRIANGULAR + { MP_ROM_QSTR(MP_QSTR_solve_triangular), MP_ROM_PTR(&linalg_solve_triangular_obj) }, + #endif + #if ULAB_SCIPY_LINALG_HAS_CHO_SOLVE + { MP_ROM_QSTR(MP_QSTR_cho_solve), MP_ROM_PTR(&linalg_cho_solve_obj) }, + #endif + #endif +}; + +static MP_DEFINE_CONST_DICT(mp_module_ulab_scipy_linalg_globals, ulab_scipy_linalg_globals_table); + +const mp_obj_module_t ulab_scipy_linalg_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_scipy_linalg_globals, +}; +#if CIRCUITPY_ULAB +#if !defined(MICROPY_VERSION) || MICROPY_VERSION <= 70144 +MP_REGISTER_MODULE(MP_QSTR_ulab_dot_scipy_dot_linalg, ulab_scipy_linalg_module, MODULE_ULAB_ENABLED); +#else +MP_REGISTER_MODULE(MP_QSTR_ulab_dot_scipy_dot_linalg, ulab_scipy_linalg_module); +#endif +#endif +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/scipy/linalg/linalg.h b/components/3rd_party/omv/omv/modules/ulab/code/scipy/linalg/linalg.h new file mode 100644 index 00000000..628051f4 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/scipy/linalg/linalg.h @@ -0,0 +1,21 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Vikas Udupa + * +*/ + +#ifndef _SCIPY_LINALG_ +#define _SCIPY_LINALG_ + +extern const mp_obj_module_t ulab_scipy_linalg_module; + +MP_DECLARE_CONST_FUN_OBJ_KW(linalg_solve_triangular_obj); +MP_DECLARE_CONST_FUN_OBJ_2(linalg_cho_solve_obj); + +#endif /* _SCIPY_LINALG_ */ diff --git a/components/3rd_party/omv/omv/modules/ulab/code/scipy/optimize/optimize.c b/components/3rd_party/omv/omv/modules/ulab/code/scipy/optimize/optimize.c new file mode 100644 index 00000000..7fc6b670 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/scipy/optimize/optimize.c @@ -0,0 +1,421 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2020 Scott Shawcroft for Adafruit Industries + * 2020-2021 Zoltán Vörös + * 2020 Taku Fukada +*/ + +#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" + +#include "../../ndarray.h" +#include "../../ulab.h" +#include "../../ulab_tools.h" +#include "optimize.h" + +ULAB_DEFINE_FLOAT_CONST(xtolerance, MICROPY_FLOAT_CONST(2.4e-7), 0x3480d959UL, 0x3e901b2b29a4692bULL); +ULAB_DEFINE_FLOAT_CONST(rtolerance, MICROPY_FLOAT_CONST(0.0), 0UL, 0ULL); + +static mp_float_t optimize_python_call(const mp_obj_type_t *type, mp_obj_t fun, mp_float_t x, mp_obj_t *fargs, uint8_t nparams) { + // Helper function for calculating the value of f(x, a, b, c, ...), + // where f is defined in python. Takes a float, returns a float. + // The array of mp_obj_t type must be supplied, as must the number of parameters (a, b, c...) in nparams + fargs[0] = mp_obj_new_float(x); + return mp_obj_get_float(MP_OBJ_TYPE_GET_SLOT(type, call)(fun, nparams+1, 0, fargs)); +} + +#if ULAB_SCIPY_OPTIMIZE_HAS_BISECT +//| def bisect( +//| fun: Callable[[float], float], +//| a: float, +//| b: float, +//| *, +//| xtol: float = 2.4e-7, +//| maxiter: int = 100 +//| ) -> float: +//| """ +//| :param callable f: The function to bisect +//| :param float a: The left side of the interval +//| :param float b: The right side of the interval +//| :param float xtol: The tolerance value +//| :param float maxiter: The maximum number of iterations to perform +//| +//| Find a solution (zero) of the function ``f(x)`` on the interval +//| (``a``..``b``) using the bisection method. The result is accurate to within +//| ``xtol`` unless more than ``maxiter`` steps are required.""" +//| ... +//| + +STATIC mp_obj_t optimize_bisect(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + // Simple bisection routine + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_xtol, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = ULAB_REFERENCE_FLOAT_CONST(xtolerance)} }, + { MP_QSTR_maxiter, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 100} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t fun = args[0].u_obj; + const mp_obj_type_t *type = mp_obj_get_type(fun); + if(!MP_OBJ_TYPE_HAS_SLOT(type, call)) { + mp_raise_TypeError(translate("first argument must be a function")); + } + mp_float_t xtol = mp_obj_get_float(args[3].u_obj); + mp_obj_t fargs[1]; + mp_float_t left, right; + mp_float_t x_mid; + mp_float_t a = mp_obj_get_float(args[1].u_obj); + mp_float_t b = mp_obj_get_float(args[2].u_obj); + left = optimize_python_call(type, fun, a, fargs, 0); + right = optimize_python_call(type, fun, b, fargs, 0); + if(left * right > 0) { + mp_raise_ValueError(translate("function has the same sign at the ends of interval")); + } + mp_float_t rtb = left < MICROPY_FLOAT_CONST(0.0) ? a : b; + mp_float_t dx = left < MICROPY_FLOAT_CONST(0.0) ? b - a : a - b; + if(args[4].u_int < 0) { + mp_raise_ValueError(translate("maxiter should be > 0")); + } + for(uint16_t i=0; i < args[4].u_int; i++) { + dx *= MICROPY_FLOAT_CONST(0.5); + x_mid = rtb + dx; + if(optimize_python_call(type, fun, x_mid, fargs, 0) < MICROPY_FLOAT_CONST(0.0)) { + rtb = x_mid; + } + if(MICROPY_FLOAT_C_FUN(fabs)(dx) < xtol) break; + } + return mp_obj_new_float(rtb); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(optimize_bisect_obj, 3, optimize_bisect); +#endif + +#if ULAB_SCIPY_OPTIMIZE_HAS_FMIN +//| def fmin( +//| fun: Callable[[float], float], +//| x0: float, +//| *, +//| xatol: float = 2.4e-7, +//| fatol: float = 2.4e-7, +//| maxiter: int = 200 +//| ) -> float: +//| """ +//| :param callable f: The function to bisect +//| :param float x0: The initial x value +//| :param float xatol: The absolute tolerance value +//| :param float fatol: The relative tolerance value +//| +//| Find a minimum of the function ``f(x)`` using the downhill simplex method. +//| The located ``x`` is within ``fxtol`` of the actual minimum, and ``f(x)`` +//| is within ``fatol`` of the actual minimum unless more than ``maxiter`` +//| steps are requried.""" +//| ... +//| + +STATIC mp_obj_t optimize_fmin(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + // downhill simplex method in 1D + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_xatol, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = ULAB_REFERENCE_FLOAT_CONST(xtolerance)} }, + { MP_QSTR_fatol, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = ULAB_REFERENCE_FLOAT_CONST(xtolerance)} }, + { MP_QSTR_maxiter, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 200} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t fun = args[0].u_obj; + const mp_obj_type_t *type = mp_obj_get_type(fun); + if(!MP_OBJ_TYPE_HAS_SLOT(type, call)) { + mp_raise_TypeError(translate("first argument must be a function")); + } + + // parameters controlling convergence conditions + mp_float_t xatol = mp_obj_get_float(args[2].u_obj); + mp_float_t fatol = mp_obj_get_float(args[3].u_obj); + if(args[4].u_int <= 0) { + mp_raise_ValueError(translate("maxiter must be > 0")); + } + uint16_t maxiter = (uint16_t)args[4].u_int; + + mp_float_t x0 = mp_obj_get_float(args[1].u_obj); + mp_float_t x1 = MICROPY_FLOAT_C_FUN(fabs)(x0) > OPTIMIZE_EPSILON ? (MICROPY_FLOAT_CONST(1.0) + OPTIMIZE_NONZDELTA) * x0 : OPTIMIZE_ZDELTA; + mp_obj_t fargs[1]; + mp_float_t f0 = optimize_python_call(type, fun, x0, fargs, 0); + mp_float_t f1 = optimize_python_call(type, fun, x1, fargs, 0); + if(f1 < f0) { + SWAP(mp_float_t, x0, x1); + SWAP(mp_float_t, f0, f1); + } + for(uint16_t i=0; i < maxiter; i++) { + uint8_t shrink = 0; + f0 = optimize_python_call(type, fun, x0, fargs, 0); + f1 = optimize_python_call(type, fun, x1, fargs, 0); + + // reflection + mp_float_t xr = (MICROPY_FLOAT_CONST(1.0) + OPTIMIZE_ALPHA) * x0 - OPTIMIZE_ALPHA * x1; + mp_float_t fr = optimize_python_call(type, fun, xr, fargs, 0); + if(fr < f0) { // expansion + mp_float_t xe = (1 + OPTIMIZE_ALPHA * OPTIMIZE_BETA) * x0 - OPTIMIZE_ALPHA * OPTIMIZE_BETA * x1; + mp_float_t fe = optimize_python_call(type, fun, xe, fargs, 0); + if(fe < fr) { + x1 = xe; + f1 = fe; + } else { + x1 = xr; + f1 = fr; + } + } else { + if(fr < f1) { // contraction + mp_float_t xc = (1 + OPTIMIZE_GAMMA * OPTIMIZE_ALPHA) * x0 - OPTIMIZE_GAMMA * OPTIMIZE_ALPHA * x1; + mp_float_t fc = optimize_python_call(type, fun, xc, fargs, 0); + if(fc < fr) { + x1 = xc; + f1 = fc; + } else { + shrink = 1; + } + } else { // inside contraction + mp_float_t xc = (MICROPY_FLOAT_CONST(1.0) - OPTIMIZE_GAMMA) * x0 + OPTIMIZE_GAMMA * x1; + mp_float_t fc = optimize_python_call(type, fun, xc, fargs, 0); + if(fc < f1) { + x1 = xc; + f1 = fc; + } else { + shrink = 1; + } + } + if(shrink == 1) { + x1 = x0 + OPTIMIZE_DELTA * (x1 - x0); + f1 = optimize_python_call(type, fun, x1, fargs, 0); + } + if((MICROPY_FLOAT_C_FUN(fabs)(f1 - f0) < fatol) || + (MICROPY_FLOAT_C_FUN(fabs)(x1 - x0) < xatol)) { + break; + } + if(f1 < f0) { + SWAP(mp_float_t, x0, x1); + SWAP(mp_float_t, f0, f1); + } + } + } + return mp_obj_new_float(x0); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(optimize_fmin_obj, 2, optimize_fmin); +#endif + +#if ULAB_SCIPY_OPTIMIZE_HAS_CURVE_FIT +static void optimize_jacobi(const mp_obj_type_t *type, mp_obj_t fun, mp_float_t *x, mp_float_t *y, uint16_t len, mp_float_t *params, uint8_t nparams, mp_float_t *jacobi, mp_float_t *grad) { + /* Calculates the Jacobian and the gradient of the cost function + * + * The entries in the Jacobian are + * J(m, n) = de_m/da_n, + * + * where + * + * e_m = (f(x_m, a1, a2, ...) - y_m)/sigma_m is the error at x_m, + * + * and + * + * a1, a2, ..., a_n are the free parameters + */ + mp_obj_t *fargs0 = m_new(mp_obj_t, lenp+1); + mp_obj_t *fargs1 = m_new(mp_obj_t, lenp+1); + for(uint8_t p=0; p < nparams; p++) { + fargs0[p+1] = mp_obj_new_float(params[p]); + fargs1[p+1] = mp_obj_new_float(params[p]); + } + for(uint8_t p=0; p < nparams; p++) { + mp_float_t da = params[p] != MICROPY_FLOAT_CONST(0.0) ? (MICROPY_FLOAT_CONST(1.0) + APPROX_NONZDELTA) * params[p] : APPROX_ZDELTA; + fargs1[p+1] = mp_obj_new_float(params[p] + da); + grad[p] = MICROPY_FLOAT_CONST(0.0); + for(uint16_t i=0; i < len; i++) { + mp_float_t f0 = optimize_python_call(type, fun, x[i], fargs0, nparams); + mp_float_t f1 = optimize_python_call(type, fun, x[i], fargs1, nparams); + jacobi[i*nparamp+p] = (f1 - f0) / da; + grad[p] += (f0 - y[i]) * jacobi[i*nparamp+p]; + } + fargs1[p+1] = fargs0[p+1]; // set back to the original value + } +} + +static void optimize_delta(mp_float_t *jacobi, mp_float_t *grad, uint16_t len, uint8_t nparams, mp_float_t lambda) { + // +} + +mp_obj_t optimize_curve_fit(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + // Levenberg-Marquardt non-linear fit + // The implementation follows the introductory discussion in Mark Tanstrum's paper, https://arxiv.org/abs/1201.5885 + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_p0, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_xatol, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&xtolerance)} }, + { MP_QSTR_fatol, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&xtolerance)} }, + { MP_QSTR_maxiter, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t fun = args[0].u_obj; + const mp_obj_type_t *type = mp_obj_get_type(fun); + if(!MP_OBJ_TYPE_HAS_SLOT(type, call)) { + mp_raise_TypeError(translate("first argument must be a function")); + } + + mp_obj_t x_obj = args[1].u_obj; + mp_obj_t y_obj = args[2].u_obj; + mp_obj_t p0_obj = args[3].u_obj; + if(!ndarray_object_is_array_like(x_obj) || !ndarray_object_is_array_like(y_obj)) { + mp_raise_TypeError(translate("data must be iterable")); + } + if(!ndarray_object_is_nditerable(p0_obj)) { + mp_raise_TypeError(translate("initial values must be iterable")); + } + size_t len = (size_t)mp_obj_get_int(mp_obj_len_maybe(x_obj)); + uint8_t lenp = (uint8_t)mp_obj_get_int(mp_obj_len_maybe(p0_obj)); + if(len != (uint16_t)mp_obj_get_int(mp_obj_len_maybe(y_obj))) { + mp_raise_ValueError(translate("data must be of equal length")); + } + + mp_float_t *x = m_new(mp_float_t, len); + fill_array_iterable(x, x_obj); + mp_float_t *y = m_new(mp_float_t, len); + fill_array_iterable(y, y_obj); + mp_float_t *p0 = m_new(mp_float_t, lenp); + fill_array_iterable(p0, p0_obj); + mp_float_t *grad = m_new(mp_float_t, len); + mp_float_t *jacobi = m_new(mp_float_t, len*len); + mp_obj_t *fargs = m_new(mp_obj_t, lenp+1); + + m_del(mp_float_t, p0, lenp); + // parameters controlling convergence conditions + //mp_float_t xatol = mp_obj_get_float(args[2].u_obj); + //mp_float_t fatol = mp_obj_get_float(args[3].u_obj); + + // this has finite binary representation; we will multiply/divide by 4 + //mp_float_t lambda = 0.0078125; + + //linalg_invert_matrix(mp_float_t *data, size_t N) + + m_del(mp_float_t, x, len); + m_del(mp_float_t, y, len); + m_del(mp_float_t, grad, len); + m_del(mp_float_t, jacobi, len*len); + m_del(mp_obj_t, fargs, lenp+1); + return mp_const_none; +} + +MP_DEFINE_CONST_FUN_OBJ_KW(optimize_curve_fit_obj, 2, optimize_curve_fit); +#endif + +#if ULAB_SCIPY_OPTIMIZE_HAS_NEWTON +//| def newton( +//| fun: Callable[[float], float], +//| x0: float, +//| *, +//| xtol: float = 2.4e-7, +//| rtol: float = 0.0, +//| maxiter: int = 50 +//| ) -> float: +//| """ +//| :param callable f: The function to bisect +//| :param float x0: The initial x value +//| :param float xtol: The absolute tolerance value +//| :param float rtol: The relative tolerance value +//| :param float maxiter: The maximum number of iterations to perform +//| +//| Find a solution (zero) of the function ``f(x)`` using Newton's Method. +//| The result is accurate to within ``xtol * rtol * |f(x)|`` unless more than +//| ``maxiter`` steps are requried.""" +//| ... +//| + +static mp_obj_t optimize_newton(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + // this is actually the secant method, as the first derivative of the function + // is not accepted as an argument. The function whose root we want to solve for + // must depend on a single variable without parameters, i.e., f(x) + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_tol, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = ULAB_REFERENCE_FLOAT_CONST(xtolerance) } }, + { MP_QSTR_rtol, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = ULAB_REFERENCE_FLOAT_CONST(rtolerance) } }, + { MP_QSTR_maxiter, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 50 } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t fun = args[0].u_obj; + const mp_obj_type_t *type = mp_obj_get_type(fun); + if(!MP_OBJ_TYPE_HAS_SLOT(type, call)) { + mp_raise_TypeError(translate("first argument must be a function")); + } + mp_float_t x = mp_obj_get_float(args[1].u_obj); + mp_float_t tol = mp_obj_get_float(args[2].u_obj); + mp_float_t rtol = mp_obj_get_float(args[3].u_obj); + mp_float_t dx, df, fx; + dx = x > MICROPY_FLOAT_CONST(0.0) ? OPTIMIZE_EPS * x : -OPTIMIZE_EPS * x; + mp_obj_t fargs[1]; + if(args[4].u_int <= 0) { + mp_raise_ValueError(translate("maxiter must be > 0")); + } + for(uint16_t i=0; i < args[4].u_int; i++) { + fx = optimize_python_call(type, fun, x, fargs, 0); + df = (optimize_python_call(type, fun, x + dx, fargs, 0) - fx) / dx; + dx = fx / df; + x -= dx; + if(MICROPY_FLOAT_C_FUN(fabs)(dx) < (tol + rtol * MICROPY_FLOAT_C_FUN(fabs)(x))) break; + } + return mp_obj_new_float(x); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(optimize_newton_obj, 2, optimize_newton); +#endif + +static const mp_rom_map_elem_t ulab_scipy_optimize_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_optimize) }, + #if ULAB_SCIPY_OPTIMIZE_HAS_BISECT + { MP_ROM_QSTR(MP_QSTR_bisect), MP_ROM_PTR(&optimize_bisect_obj) }, + #endif + #if ULAB_SCIPY_OPTIMIZE_HAS_CURVE_FIT + { MP_ROM_QSTR(MP_QSTR_curve_fit), MP_ROM_PTR(&optimize_curve_fit_obj) }, + #endif + #if ULAB_SCIPY_OPTIMIZE_HAS_FMIN + { MP_ROM_QSTR(MP_QSTR_fmin), MP_ROM_PTR(&optimize_fmin_obj) }, + #endif + #if ULAB_SCIPY_OPTIMIZE_HAS_NEWTON + { MP_ROM_QSTR(MP_QSTR_newton), MP_ROM_PTR(&optimize_newton_obj) }, + #endif +}; + +static MP_DEFINE_CONST_DICT(mp_module_ulab_scipy_optimize_globals, ulab_scipy_optimize_globals_table); + +const mp_obj_module_t ulab_scipy_optimize_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_scipy_optimize_globals, +}; +#if CIRCUITPY_ULAB +#if !defined(MICROPY_VERSION) || MICROPY_VERSION <= 70144 +MP_REGISTER_MODULE(MP_QSTR_ulab_dot_scipy_dot_optimize, ulab_scipy_optimize_module, MODULE_ULAB_ENABLED); +#else +MP_REGISTER_MODULE(MP_QSTR_ulab_dot_scipy_dot_optimize, ulab_scipy_optimize_module); +#endif +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/scipy/optimize/optimize.h b/components/3rd_party/omv/omv/modules/ulab/code/scipy/optimize/optimize.h new file mode 100644 index 00000000..174b3863 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/scipy/optimize/optimize.h @@ -0,0 +1,41 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös + * +*/ + +#ifndef _SCIPY_OPTIMIZE_ +#define _SCIPY_OPTIMIZE_ + +#include "../../ulab_tools.h" + +#ifndef OPTIMIZE_EPSILON +#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT +#define OPTIMIZE_EPSILON MICROPY_FLOAT_CONST(1.2e-7) +#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE +#define OPTIMIZE_EPSILON MICROPY_FLOAT_CONST(2.3e-16) +#endif +#endif + +#define OPTIMIZE_EPS MICROPY_FLOAT_CONST(1.0e-4) +#define OPTIMIZE_NONZDELTA MICROPY_FLOAT_CONST(0.05) +#define OPTIMIZE_ZDELTA MICROPY_FLOAT_CONST(0.00025) +#define OPTIMIZE_ALPHA MICROPY_FLOAT_CONST(1.0) +#define OPTIMIZE_BETA MICROPY_FLOAT_CONST(2.0) +#define OPTIMIZE_GAMMA MICROPY_FLOAT_CONST(0.5) +#define OPTIMIZE_DELTA MICROPY_FLOAT_CONST(0.5) + +extern const mp_obj_module_t ulab_scipy_optimize_module; + +MP_DECLARE_CONST_FUN_OBJ_KW(optimize_bisect_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(optimize_curve_fit_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(optimize_fmin_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(optimize_newton_obj); + +#endif /* _SCIPY_OPTIMIZE_ */ diff --git a/components/3rd_party/omv/omv/modules/ulab/code/scipy/scipy.c b/components/3rd_party/omv/omv/modules/ulab/code/scipy/scipy.c new file mode 100644 index 00000000..c9f51df5 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/scipy/scipy.c @@ -0,0 +1,58 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2020 Scott Shawcroft for Adafruit Industries + * 2020-2021 Zoltán Vörös + * 2020 Taku Fukada +*/ + +#include +#include "py/runtime.h" + +#include "../ulab.h" +#include "optimize/optimize.h" +#include "signal/signal.h" +#include "special/special.h" +#include "linalg/linalg.h" + +#if ULAB_HAS_SCIPY + +//| """Compatibility layer for scipy""" +//| + +static const mp_rom_map_elem_t ulab_scipy_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_scipy) }, + #if ULAB_SCIPY_HAS_LINALG_MODULE + { MP_ROM_QSTR(MP_QSTR_linalg), MP_ROM_PTR(&ulab_scipy_linalg_module) }, + #endif + #if ULAB_SCIPY_HAS_OPTIMIZE_MODULE + { MP_ROM_QSTR(MP_QSTR_optimize), MP_ROM_PTR(&ulab_scipy_optimize_module) }, + #endif + #if ULAB_SCIPY_HAS_SIGNAL_MODULE + { MP_ROM_QSTR(MP_QSTR_signal), MP_ROM_PTR(&ulab_scipy_signal_module) }, + #endif + #if ULAB_SCIPY_HAS_SPECIAL_MODULE + { MP_ROM_QSTR(MP_QSTR_special), MP_ROM_PTR(&ulab_scipy_special_module) }, + #endif +}; + +static MP_DEFINE_CONST_DICT(mp_module_ulab_scipy_globals, ulab_scipy_globals_table); + +const mp_obj_module_t ulab_scipy_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_scipy_globals, +}; +#if CIRCUITPY_ULAB +#if !defined(MICROPY_VERSION) || MICROPY_VERSION <= 70144 +MP_REGISTER_MODULE(MP_QSTR_ulab_dot_scipy, ulab_scipy_module, MODULE_ULAB_ENABLED); +#else +MP_REGISTER_MODULE(MP_QSTR_ulab_dot_scipy, ulab_scipy_module); +#endif +#endif +#endif /* ULAB_HAS_SCIPY */ diff --git a/components/3rd_party/omv/omv/modules/ulab/code/scipy/scipy.h b/components/3rd_party/omv/omv/modules/ulab/code/scipy/scipy.h new file mode 100644 index 00000000..ec8c8042 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/scipy/scipy.h @@ -0,0 +1,21 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös + * +*/ + +#ifndef _SCIPY_ +#define _SCIPY_ + +#include "../ulab.h" +#include "../ndarray.h" + +extern const mp_obj_module_t ulab_scipy_module; + +#endif /* _SCIPY_ */ diff --git a/components/3rd_party/omv/omv/modules/ulab/code/scipy/signal/signal.c b/components/3rd_party/omv/omv/modules/ulab/code/scipy/signal/signal.c new file mode 100644 index 00000000..6afa05f6 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/scipy/signal/signal.c @@ -0,0 +1,142 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2020 Scott Shawcroft for Adafruit Industries + * 2020-2021 Zoltán Vörös + * 2020 Taku Fukada +*/ + +#include +#include +#include "py/runtime.h" + +#include "../../ulab.h" +#include "../../ndarray.h" +#include "../../numpy/carray/carray_tools.h" + +#if ULAB_SCIPY_SIGNAL_HAS_SOSFILT & ULAB_MAX_DIMS > 1 +static void signal_sosfilt_array(mp_float_t *x, const mp_float_t *coeffs, mp_float_t *zf, const size_t len) { + for(size_t i=0; i < len; i++) { + mp_float_t xn = *x; + *x = coeffs[0] * xn + zf[0]; + zf[0] = zf[1] + coeffs[1] * xn - coeffs[4] * *x; + zf[1] = coeffs[2] * xn - coeffs[5] * *x; + x++; + } + x -= len; +} + +mp_obj_t signal_sosfilt(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_sos, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_x, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_zi, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!ndarray_object_is_array_like(args[0].u_obj) || !ndarray_object_is_array_like(args[1].u_obj)) { + mp_raise_TypeError(translate("sosfilt requires iterable arguments")); + } + #if ULAB_SUPPORTS_COMPLEX + if(mp_obj_is_type(args[1].u_obj, &ulab_ndarray_type)) { + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[1].u_obj); + COMPLEX_DTYPE_NOT_IMPLEMENTED(ndarray->dtype) + } + #endif + size_t lenx = (size_t)mp_obj_get_int(mp_obj_len_maybe(args[1].u_obj)); + ndarray_obj_t *y = ndarray_new_linear_array(lenx, NDARRAY_FLOAT); + mp_float_t *yarray = (mp_float_t *)y->array; + mp_float_t coeffs[6]; + if(mp_obj_is_type(args[1].u_obj, &ulab_ndarray_type)) { + ndarray_obj_t *inarray = MP_OBJ_TO_PTR(args[1].u_obj); + #if ULAB_MAX_DIMS > 1 + if(inarray->ndim > 1) { + mp_raise_ValueError(translate("input must be one-dimensional")); + } + #endif + uint8_t *iarray = (uint8_t *)inarray->array; + for(size_t i=0; i < lenx; i++) { + *yarray++ = ndarray_get_float_value(iarray, inarray->dtype); + iarray += inarray->strides[ULAB_MAX_DIMS - 1]; + } + yarray -= lenx; + } else { + fill_array_iterable(yarray, args[1].u_obj); + } + + mp_obj_iter_buf_t iter_buf; + mp_obj_t item, iterable = mp_getiter(args[0].u_obj, &iter_buf); + size_t lensos = (size_t)mp_obj_get_int(mp_obj_len_maybe(args[0].u_obj)); + + size_t *shape = ndarray_shape_vector(0, 0, lensos, 2); + ndarray_obj_t *zf = ndarray_new_dense_ndarray(2, shape, NDARRAY_FLOAT); + mp_float_t *zf_array = (mp_float_t *)zf->array; + + if(args[2].u_obj != mp_const_none) { + if(!mp_obj_is_type(args[2].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("zi must be an ndarray")); + } else { + ndarray_obj_t *zi = MP_OBJ_TO_PTR(args[2].u_obj); + if((zi->shape[ULAB_MAX_DIMS - 2] != lensos) || (zi->shape[ULAB_MAX_DIMS - 1] != 2)) { + mp_raise_ValueError(translate("zi must be of shape (n_section, 2)")); + } + if(zi->dtype != NDARRAY_FLOAT) { + mp_raise_ValueError(translate("zi must be of float type")); + } + // TODO: this won't work with sparse arrays + memcpy(zf_array, zi->array, 2*lensos*sizeof(mp_float_t)); + } + } + while((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + if(mp_obj_get_int(mp_obj_len_maybe(item)) != 6) { + mp_raise_ValueError(translate("sos array must be of shape (n_section, 6)")); + } else { + fill_array_iterable(coeffs, item); + if(coeffs[3] != MICROPY_FLOAT_CONST(1.0)) { + mp_raise_ValueError(translate("sos[:, 3] should be all ones")); + } + signal_sosfilt_array(yarray, coeffs, zf_array, lenx); + zf_array += 2; + } + } + if(args[2].u_obj == mp_const_none) { + return MP_OBJ_FROM_PTR(y); + } else { + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(2, NULL)); + tuple->items[0] = MP_OBJ_FROM_PTR(y); + tuple->items[1] = MP_OBJ_FROM_PTR(zf); + return MP_OBJ_FROM_PTR(tuple); + } +} + +MP_DEFINE_CONST_FUN_OBJ_KW(signal_sosfilt_obj, 2, signal_sosfilt); +#endif /* ULAB_SCIPY_SIGNAL_HAS_SOSFILT */ + +static const mp_rom_map_elem_t ulab_scipy_signal_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_signal) }, + #if ULAB_SCIPY_SIGNAL_HAS_SOSFILT & ULAB_MAX_DIMS > 1 + { MP_ROM_QSTR(MP_QSTR_sosfilt), MP_ROM_PTR(&signal_sosfilt_obj) }, + #endif +}; + +static MP_DEFINE_CONST_DICT(mp_module_ulab_scipy_signal_globals, ulab_scipy_signal_globals_table); + +const mp_obj_module_t ulab_scipy_signal_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_scipy_signal_globals, +}; +#if CIRCUITPY_ULAB +#if !defined(MICROPY_VERSION) || MICROPY_VERSION <= 70144 +MP_REGISTER_MODULE(MP_QSTR_ulab_dot_scipy_dot_signal, ulab_scipy_signal_module, MODULE_ULAB_ENABLED); +#else +MP_REGISTER_MODULE(MP_QSTR_ulab_dot_scipy_dot_signal, ulab_scipy_signal_module); +#endif +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/scipy/signal/signal.h b/components/3rd_party/omv/omv/modules/ulab/code/scipy/signal/signal.h new file mode 100644 index 00000000..033f6e4c --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/scipy/signal/signal.h @@ -0,0 +1,23 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös + * +*/ + +#ifndef _SCIPY_SIGNAL_ +#define _SCIPY_SIGNAL_ + +#include "../../ulab.h" +#include "../../ndarray.h" + +extern const mp_obj_module_t ulab_scipy_signal_module; + +MP_DECLARE_CONST_FUN_OBJ_KW(signal_sosfilt_obj); + +#endif /* _SCIPY_SIGNAL_ */ diff --git a/components/3rd_party/omv/omv/modules/ulab/code/scipy/special/special.c b/components/3rd_party/omv/omv/modules/ulab/code/scipy/special/special.c new file mode 100644 index 00000000..9d5ca629 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/scipy/special/special.c @@ -0,0 +1,49 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * 2020 Scott Shawcroft for Adafruit Industries + * 2020-2021 Zoltán Vörös + * 2020 Taku Fukada +*/ + +#include +#include "py/runtime.h" + +#include "../../ulab.h" +#include "../../numpy/vector.h" + +static const mp_rom_map_elem_t ulab_scipy_special_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_special) }, + #if ULAB_SCIPY_SPECIAL_HAS_ERF + { MP_ROM_QSTR(MP_QSTR_erf), MP_ROM_PTR(&vector_erf_obj) }, + #endif + #if ULAB_SCIPY_SPECIAL_HAS_ERFC + { MP_ROM_QSTR(MP_QSTR_erfc), MP_ROM_PTR(&vector_erfc_obj) }, + #endif + #if ULAB_SCIPY_SPECIAL_HAS_GAMMA + { MP_ROM_QSTR(MP_QSTR_gamma), MP_ROM_PTR(&vector_gamma_obj) }, + #endif + #if ULAB_SCIPY_SPECIAL_HAS_GAMMALN + { MP_ROM_QSTR(MP_QSTR_gammaln), MP_ROM_PTR(&vector_lgamma_obj) }, + #endif +}; + +static MP_DEFINE_CONST_DICT(mp_module_ulab_scipy_special_globals, ulab_scipy_special_globals_table); + +const mp_obj_module_t ulab_scipy_special_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_scipy_special_globals, +}; +#if CIRCUITPY_ULAB +#if !defined(MICROPY_VERSION) || MICROPY_VERSION <= 70144 +MP_REGISTER_MODULE(MP_QSTR_ulab_dot_scipy_dot_special, ulab_scipy_special_module, MODULE_ULAB_ENABLED); +#else +MP_REGISTER_MODULE(MP_QSTR_ulab_dot_scipy_dot_special, ulab_scipy_special_module); +#endif +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/scipy/special/special.h b/components/3rd_party/omv/omv/modules/ulab/code/scipy/special/special.h new file mode 100644 index 00000000..bb34e27e --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/scipy/special/special.h @@ -0,0 +1,21 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös + * +*/ + +#ifndef _SCIPY_SPECIAL_ +#define _SCIPY_SPECIAL_ + +#include "../../ulab.h" +#include "../../ndarray.h" + +extern const mp_obj_module_t ulab_scipy_special_module; + +#endif /* _SCIPY_SPECIAL_ */ diff --git a/components/3rd_party/omv/omv/modules/ulab/code/ulab.c b/components/3rd_party/omv/omv/modules/ulab/code/ulab.c new file mode 100644 index 00000000..a389831a --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/ulab.c @@ -0,0 +1,262 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Zoltán Vörös + * 2020 Jeff Epler for Adafruit Industries +*/ + +#include +#include +#include +#include +#include "py/runtime.h" +#include "py/binary.h" +#include "py/obj.h" +#include "py/objarray.h" + +#include "ulab.h" +#include "ndarray.h" +#include "ndarray_properties.h" +#include "numpy/create.h" +#include "numpy/ndarray/ndarray_iter.h" + +#include "numpy/numpy.h" +#include "scipy/scipy.h" +// TODO: we should get rid of this; array.sort depends on it +#include "numpy/numerical.h" + +#include "user/user.h" +#include "utils/utils.h" + +#define ULAB_VERSION 6.0.12 +#define xstr(s) str(s) +#define str(s) #s + +#if ULAB_SUPPORTS_COMPLEX +#define ULAB_VERSION_STRING xstr(ULAB_VERSION) xstr(-) xstr(ULAB_MAX_DIMS) xstr(D-c) +#else +#define ULAB_VERSION_STRING xstr(ULAB_VERSION) xstr(-) xstr(ULAB_MAX_DIMS) xstr(D) +#endif + +STATIC MP_DEFINE_STR_OBJ(ulab_version_obj, ULAB_VERSION_STRING); + +#ifdef ULAB_HASH +STATIC MP_DEFINE_STR_OBJ(ulab_sha_obj, xstr(ULAB_HASH)); +#endif + +STATIC const mp_rom_map_elem_t ulab_ndarray_locals_dict_table[] = { + #if ULAB_MAX_DIMS > 1 + #if NDARRAY_HAS_RESHAPE + { MP_ROM_QSTR(MP_QSTR_reshape), MP_ROM_PTR(&ndarray_reshape_obj) }, + #endif + #if NDARRAY_HAS_TRANSPOSE + { MP_ROM_QSTR(MP_QSTR_transpose), MP_ROM_PTR(&ndarray_transpose_obj) }, + #endif + #endif + #if NDARRAY_HAS_BYTESWAP + { MP_ROM_QSTR(MP_QSTR_byteswap), MP_ROM_PTR(&ndarray_byteswap_obj) }, + #endif + #if NDARRAY_HAS_COPY + { MP_ROM_QSTR(MP_QSTR_copy), MP_ROM_PTR(&ndarray_copy_obj) }, + #endif + #if NDARRAY_HAS_FLATTEN + { MP_ROM_QSTR(MP_QSTR_flatten), MP_ROM_PTR(&ndarray_flatten_obj) }, + #endif + #if NDARRAY_HAS_TOBYTES + { MP_ROM_QSTR(MP_QSTR_tobytes), MP_ROM_PTR(&ndarray_tobytes_obj) }, + #endif + #if NDARRAY_HAS_TOLIST + { MP_ROM_QSTR(MP_QSTR_tolist), MP_ROM_PTR(&ndarray_tolist_obj) }, + #endif + #if NDARRAY_HAS_SORT + { MP_ROM_QSTR(MP_QSTR_sort), MP_ROM_PTR(&numerical_sort_inplace_obj) }, + #endif + #ifdef CIRCUITPY + #if NDARRAY_HAS_DTYPE + { MP_ROM_QSTR(MP_QSTR_dtype), MP_ROM_PTR(&ndarray_dtype_obj) }, + #endif + #if NDARRAY_HAS_FLATITER + { MP_ROM_QSTR(MP_QSTR_flat), MP_ROM_PTR(&ndarray_flat_obj) }, + #endif + #if NDARRAY_HAS_ITEMSIZE + { MP_ROM_QSTR(MP_QSTR_itemsize), MP_ROM_PTR(&ndarray_itemsize_obj) }, + #endif + #if NDARRAY_HAS_SHAPE + { MP_ROM_QSTR(MP_QSTR_shape), MP_ROM_PTR(&ndarray_shape_obj) }, + #endif + #if NDARRAY_HAS_SIZE + { MP_ROM_QSTR(MP_QSTR_size), MP_ROM_PTR(&ndarray_size_obj) }, + #endif + #if NDARRAY_HAS_STRIDES + { MP_ROM_QSTR(MP_QSTR_strides), MP_ROM_PTR(&ndarray_strides_obj) }, + #endif + #endif /* CIRCUITPY */ +}; + +STATIC MP_DEFINE_CONST_DICT(ulab_ndarray_locals_dict, ulab_ndarray_locals_dict_table); + +#if defined(MP_DEFINE_CONST_OBJ_TYPE) +// MicroPython after-b41aaaa (Sept 19 2022). + +#if NDARRAY_IS_SLICEABLE +#define NDARRAY_TYPE_SUBSCR subscr, ndarray_subscr, +#else +#define NDARRAY_TYPE_SUBSCR +#endif +#if NDARRAY_IS_ITERABLE +#define NDARRAY_TYPE_ITER iter, ndarray_getiter, +#define NDARRAY_TYPE_ITER_FLAGS MP_TYPE_FLAG_ITER_IS_GETITER +#else +#define NDARRAY_TYPE_ITER +#define NDARRAY_TYPE_ITER_FLAGS 0 +#endif +#if NDARRAY_HAS_UNARY_OPS +#define NDARRAY_TYPE_UNARY_OP unary_op, ndarray_unary_op, +#else +#define NDARRAY_TYPE_UNARY_OP +#endif +#if NDARRAY_HAS_BINARY_OPS +#define NDARRAY_TYPE_BINARY_OP binary_op, ndarray_binary_op, +#else +#define NDARRAY_TYPE_BINARY_OP +#endif + +MP_DEFINE_CONST_OBJ_TYPE( + ulab_ndarray_type, + MP_QSTR_ndarray, + MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE | MP_TYPE_FLAG_EQ_HAS_NEQ_TEST | NDARRAY_TYPE_ITER_FLAGS, + print, ndarray_print, + make_new, ndarray_make_new, + locals_dict, &ulab_ndarray_locals_dict, + NDARRAY_TYPE_SUBSCR + NDARRAY_TYPE_ITER + NDARRAY_TYPE_UNARY_OP + NDARRAY_TYPE_BINARY_OP + attr, ndarray_properties_attr, + buffer, ndarray_get_buffer +); + +#else +// CircuitPython and earlier MicroPython revisions. +const mp_obj_type_t ulab_ndarray_type = { + { &mp_type_type }, + .flags = MP_TYPE_FLAG_EXTENDED + #if defined(MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE) && defined(MP_TYPE_FLAG_EQ_HAS_NEQ_TEST) + | MP_TYPE_FLAG_EQ_CHECKS_OTHER_TYPE | MP_TYPE_FLAG_EQ_HAS_NEQ_TEST + #endif + , + .name = MP_QSTR_ndarray, + .print = ndarray_print, + .make_new = ndarray_make_new, + .locals_dict = (mp_obj_dict_t*)&ulab_ndarray_locals_dict, + MP_TYPE_EXTENDED_FIELDS( + #if NDARRAY_IS_SLICEABLE + .subscr = ndarray_subscr, + #endif + #if NDARRAY_IS_ITERABLE + .getiter = ndarray_getiter, + #endif + #if NDARRAY_HAS_UNARY_OPS + .unary_op = ndarray_unary_op, + #endif + #if NDARRAY_HAS_BINARY_OPS + .binary_op = ndarray_binary_op, + #endif + #ifndef CIRCUITPY + .attr = ndarray_properties_attr, + #endif + .buffer_p = { .get_buffer = ndarray_get_buffer, }, + ) +}; +#endif + +#if ULAB_HAS_DTYPE_OBJECT + +#if defined(MP_DEFINE_CONST_OBJ_TYPE) +MP_DEFINE_CONST_OBJ_TYPE( + ulab_dtype_type, + MP_QSTR_dtype, + MP_TYPE_FLAG_NONE, + print, ndarray_dtype_print, + make_new, ndarray_dtype_make_new +); +#else +const mp_obj_type_t ulab_dtype_type = { + { &mp_type_type }, + .name = MP_QSTR_dtype, + .print = ndarray_dtype_print, + .make_new = ndarray_dtype_make_new, +}; +#endif +#endif + +#if NDARRAY_HAS_FLATITER +#if defined(MP_DEFINE_CONST_OBJ_TYPE) +MP_DEFINE_CONST_OBJ_TYPE( + ndarray_flatiter_type, + MP_QSTR_flatiter, + MP_TYPE_FLAG_ITER_IS_GETITER, + iter, ndarray_get_flatiterator +); +#else +const mp_obj_type_t ndarray_flatiter_type = { + { &mp_type_type }, + .name = MP_QSTR_flatiter, + MP_TYPE_EXTENDED_FIELDS( + .getiter = ndarray_get_flatiterator, + ) +}; +#endif +#endif + +STATIC const mp_rom_map_elem_t ulab_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ulab) }, + { MP_ROM_QSTR(MP_QSTR___version__), MP_ROM_PTR(&ulab_version_obj) }, + #ifdef ULAB_HASH + { MP_ROM_QSTR(MP_QSTR___sha__), MP_ROM_PTR(&ulab_sha_obj) }, + #endif + #if ULAB_HAS_DTYPE_OBJECT + { MP_ROM_QSTR(MP_QSTR_dtype), MP_ROM_PTR(&ulab_dtype_type) }, + #else + #if NDARRAY_HAS_DTYPE + { MP_ROM_QSTR(MP_QSTR_dtype), MP_ROM_PTR(&ndarray_dtype_obj) }, + #endif /* NDARRAY_HAS_DTYPE */ + #endif /* ULAB_HAS_DTYPE_OBJECT */ + { MP_ROM_QSTR(MP_QSTR_numpy), MP_ROM_PTR(&ulab_numpy_module) }, + #if ULAB_HAS_SCIPY + { MP_ROM_QSTR(MP_QSTR_scipy), MP_ROM_PTR(&ulab_scipy_module) }, + #endif + #if ULAB_HAS_USER_MODULE + { MP_ROM_QSTR(MP_QSTR_user), MP_ROM_PTR(&ulab_user_module) }, + #endif + #if ULAB_HAS_UTILS_MODULE + { MP_ROM_QSTR(MP_QSTR_utils), MP_ROM_PTR(&ulab_utils_module) }, + #endif +}; + +STATIC MP_DEFINE_CONST_DICT ( + mp_module_ulab_globals, + ulab_globals_table +); + +#ifdef OPENMV +const struct _mp_obj_module_t ulab_user_cmodule = { +#else +const mp_obj_module_t ulab_user_cmodule = { +#endif + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_globals, +}; + +// Use old three-argument MP_REGISTER_MODULE for +// MicroPython <= v1.18.0: (1 << 16) | (18 << 8) | 0 +#if !defined(MICROPY_VERSION) || MICROPY_VERSION <= 70144 +MP_REGISTER_MODULE(MP_QSTR_ulab, ulab_user_cmodule, MODULE_ULAB_ENABLED); +#else +MP_REGISTER_MODULE(MP_QSTR_ulab, ulab_user_cmodule); +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/ulab.h b/components/3rd_party/omv/omv/modules/ulab/code/ulab.h new file mode 100644 index 00000000..eedddc5e --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/ulab.h @@ -0,0 +1,749 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2019-2022 Zoltán Vörös +*/ + +#ifndef __ULAB__ +#define __ULAB__ + + + +// The pre-processor constants in this file determine how ulab behaves: +// +// - how many dimensions ulab can handle +// - which functions are included in the compiled firmware +// - whether arrays can be sliced and iterated over +// - which binary/unary operators are supported +// - whether ulab can deal with complex numbers +// +// A considerable amount of flash space can be saved by removing (setting +// the corresponding constants to 0) the unnecessary functions and features. + +// Values defined here can be overridden by your own config file as +// make -DULAB_CONFIG_FILE="my_ulab_config.h" +#if defined(ULAB_CONFIG_FILE) +#include ULAB_CONFIG_FILE +#endif + +// Adds support for complex ndarrays +#ifndef ULAB_SUPPORTS_COMPLEX +#define ULAB_SUPPORTS_COMPLEX (1) +#endif + +// Determines, whether scipy is defined in ulab. The sub-modules and functions +// of scipy have to be defined separately +#ifndef ULAB_HAS_SCIPY +#define ULAB_HAS_SCIPY (1) +#endif + +// The maximum number of dimensions the firmware should be able to support +// Possible values lie between 1, and 4, inclusive +#ifndef ULAB_MAX_DIMS +#define ULAB_MAX_DIMS 2 +#endif + +// By setting this constant to 1, iteration over array dimensions will be implemented +// as a function (ndarray_rewind_array), instead of writing out the loops in macros +// This reduces firmware size at the expense of speed +#ifndef ULAB_HAS_FUNCTION_ITERATOR +#define ULAB_HAS_FUNCTION_ITERATOR (0) +#endif + +// If NDARRAY_IS_ITERABLE is 1, the ndarray object defines its own iterator function +// This option saves approx. 250 bytes of flash space +#ifndef NDARRAY_IS_ITERABLE +#define NDARRAY_IS_ITERABLE (1) +#endif + +// Slicing can be switched off by setting this variable to 0 +#ifndef NDARRAY_IS_SLICEABLE +#define NDARRAY_IS_SLICEABLE (1) +#endif + +// The default threshold for pretty printing. These variables can be overwritten +// at run-time via the set_printoptions() function +#ifndef ULAB_HAS_PRINTOPTIONS +#define ULAB_HAS_PRINTOPTIONS (1) +#endif +#define NDARRAY_PRINT_THRESHOLD 10 +#define NDARRAY_PRINT_EDGEITEMS 3 + +// determines, whether the dtype is an object, or simply a character +// the object implementation is numpythonic, but requires more space +#ifndef ULAB_HAS_DTYPE_OBJECT +#define ULAB_HAS_DTYPE_OBJECT (0) +#endif + +// the ndarray binary operators +#ifndef NDARRAY_HAS_BINARY_OPS +#define NDARRAY_HAS_BINARY_OPS (1) +#endif + +// Firmware size can be reduced at the expense of speed by using function +// pointers in iterations. For each operator, he function pointer saves around +// 2 kB in the two-dimensional case, and around 4 kB in the four-dimensional case. + +#ifndef NDARRAY_BINARY_USES_FUN_POINTER +#define NDARRAY_BINARY_USES_FUN_POINTER (0) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_ADD +#define NDARRAY_HAS_BINARY_OP_ADD (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_EQUAL +#define NDARRAY_HAS_BINARY_OP_EQUAL (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_FLOOR_DIVIDE +#define NDARRAY_HAS_BINARY_OP_FLOOR_DIVIDE (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_LESS +#define NDARRAY_HAS_BINARY_OP_LESS (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_LESS_EQUAL +#define NDARRAY_HAS_BINARY_OP_LESS_EQUAL (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_MORE +#define NDARRAY_HAS_BINARY_OP_MORE (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_MORE_EQUAL +#define NDARRAY_HAS_BINARY_OP_MORE_EQUAL (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_MULTIPLY +#define NDARRAY_HAS_BINARY_OP_MULTIPLY (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_NOT_EQUAL +#define NDARRAY_HAS_BINARY_OP_NOT_EQUAL (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_POWER +#define NDARRAY_HAS_BINARY_OP_POWER (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_SUBTRACT +#define NDARRAY_HAS_BINARY_OP_SUBTRACT (1) +#endif + +#ifndef NDARRAY_HAS_BINARY_OP_TRUE_DIVIDE +#define NDARRAY_HAS_BINARY_OP_TRUE_DIVIDE (1) +#endif + +#ifndef NDARRAY_HAS_INPLACE_OPS +#define NDARRAY_HAS_INPLACE_OPS (1) +#endif + +#ifndef NDARRAY_HAS_INPLACE_ADD +#define NDARRAY_HAS_INPLACE_ADD (1) +#endif + +#ifndef NDARRAY_HAS_INPLACE_MULTIPLY +#define NDARRAY_HAS_INPLACE_MULTIPLY (1) +#endif + +#ifndef NDARRAY_HAS_INPLACE_POWER +#define NDARRAY_HAS_INPLACE_POWER (1) +#endif + +#ifndef NDARRAY_HAS_INPLACE_SUBTRACT +#define NDARRAY_HAS_INPLACE_SUBTRACT (1) +#endif + +#ifndef NDARRAY_HAS_INPLACE_TRUE_DIVIDE +#define NDARRAY_HAS_INPLACE_TRUE_DIVIDE (1) +#endif + +// the ndarray unary operators +#ifndef NDARRAY_HAS_UNARY_OPS +#define NDARRAY_HAS_UNARY_OPS (1) +#endif + +#ifndef NDARRAY_HAS_UNARY_OP_ABS +#define NDARRAY_HAS_UNARY_OP_ABS (1) +#endif + +#ifndef NDARRAY_HAS_UNARY_OP_INVERT +#define NDARRAY_HAS_UNARY_OP_INVERT (1) +#endif + +#ifndef NDARRAY_HAS_UNARY_OP_LEN +#define NDARRAY_HAS_UNARY_OP_LEN (1) +#endif + +#ifndef NDARRAY_HAS_UNARY_OP_NEGATIVE +#define NDARRAY_HAS_UNARY_OP_NEGATIVE (1) +#endif + +#ifndef NDARRAY_HAS_UNARY_OP_POSITIVE +#define NDARRAY_HAS_UNARY_OP_POSITIVE (1) +#endif + + +// determines, which ndarray methods are available +#ifndef NDARRAY_HAS_BYTESWAP +#define NDARRAY_HAS_BYTESWAP (1) +#endif + +#ifndef NDARRAY_HAS_COPY +#define NDARRAY_HAS_COPY (1) +#endif + +#ifndef NDARRAY_HAS_DTYPE +#define NDARRAY_HAS_DTYPE (1) +#endif + +#ifndef NDARRAY_HAS_FLATTEN +#define NDARRAY_HAS_FLATTEN (1) +#endif + +#ifndef NDARRAY_HAS_ITEMSIZE +#define NDARRAY_HAS_ITEMSIZE (1) +#endif + +#ifndef NDARRAY_HAS_RESHAPE +#define NDARRAY_HAS_RESHAPE (1) +#endif + +#ifndef NDARRAY_HAS_SHAPE +#define NDARRAY_HAS_SHAPE (1) +#endif + +#ifndef NDARRAY_HAS_SIZE +#define NDARRAY_HAS_SIZE (1) +#endif + +#ifndef NDARRAY_HAS_SORT +#define NDARRAY_HAS_SORT (1) +#endif + +#ifndef NDARRAY_HAS_STRIDES +#define NDARRAY_HAS_STRIDES (1) +#endif + +#ifndef NDARRAY_HAS_TOBYTES +#define NDARRAY_HAS_TOBYTES (1) +#endif + +#ifndef NDARRAY_HAS_TOLIST +#define NDARRAY_HAS_TOLIST (1) +#endif + +#ifndef NDARRAY_HAS_TRANSPOSE +#define NDARRAY_HAS_TRANSPOSE (1) +#endif + +// Firmware size can be reduced at the expense of speed by using a function +// pointer in iterations. Setting ULAB_VECTORISE_USES_FUNCPOINTER to 1 saves +// around 800 bytes in the four-dimensional case, and around 200 in two dimensions. +#ifndef ULAB_VECTORISE_USES_FUN_POINTER +#define ULAB_VECTORISE_USES_FUN_POINTER (1) +#endif + +// determines, whether e is defined in ulab.numpy itself +#ifndef ULAB_NUMPY_HAS_E +#define ULAB_NUMPY_HAS_E (1) +#endif + +// ulab defines infinite as a class constant in ulab.numpy +#ifndef ULAB_NUMPY_HAS_INF +#define ULAB_NUMPY_HAS_INF (1) +#endif + +// ulab defines NaN as a class constant in ulab.numpy +#ifndef ULAB_NUMPY_HAS_NAN +#define ULAB_NUMPY_HAS_NAN (1) +#endif + +// determines, whether pi is defined in ulab.numpy itself +#ifndef ULAB_NUMPY_HAS_PI +#define ULAB_NUMPY_HAS_PI (1) +#endif + +// determines, whether the ndinfo function is available +#ifndef ULAB_NUMPY_HAS_NDINFO +#define ULAB_NUMPY_HAS_NDINFO (1) +#endif + +// if this constant is set to 1, the interpreter can iterate +// over the flat array without copying any data +#ifndef NDARRAY_HAS_FLATITER +#define NDARRAY_HAS_FLATITER (1) +#endif + +// frombuffer adds 600 bytes to the firmware +#ifndef ULAB_NUMPY_HAS_FROMBUFFER +#define ULAB_NUMPY_HAS_FROMBUFFER (1) +#endif + +// functions that create an array +#ifndef ULAB_NUMPY_HAS_ARANGE +#define ULAB_NUMPY_HAS_ARANGE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_CONCATENATE +#define ULAB_NUMPY_HAS_CONCATENATE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_DIAG +#define ULAB_NUMPY_HAS_DIAG (1) +#endif + +#ifndef ULAB_NUMPY_HAS_EMPTY +#define ULAB_NUMPY_HAS_EMPTY (1) +#endif + +#ifndef ULAB_NUMPY_HAS_EYE +#define ULAB_NUMPY_HAS_EYE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_FULL +#define ULAB_NUMPY_HAS_FULL (1) +#endif + +#ifndef ULAB_NUMPY_HAS_LINSPACE +#define ULAB_NUMPY_HAS_LINSPACE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_LOGSPACE +#define ULAB_NUMPY_HAS_LOGSPACE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ONES +#define ULAB_NUMPY_HAS_ONES (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ZEROS +#define ULAB_NUMPY_HAS_ZEROS (1) +#endif + +// functions that compare arrays +#ifndef ULAB_NUMPY_HAS_CLIP +#define ULAB_NUMPY_HAS_CLIP (1) +#endif + +#ifndef ULAB_NUMPY_HAS_EQUAL +#define ULAB_NUMPY_HAS_EQUAL (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ISFINITE +#define ULAB_NUMPY_HAS_ISFINITE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ISINF +#define ULAB_NUMPY_HAS_ISINF (1) +#endif + +#ifndef ULAB_NUMPY_HAS_MAXIMUM +#define ULAB_NUMPY_HAS_MAXIMUM (1) +#endif + +#ifndef ULAB_NUMPY_HAS_MINIMUM +#define ULAB_NUMPY_HAS_MINIMUM (1) +#endif + +#ifndef ULAB_NUMPY_HAS_NONZERO +#define ULAB_NUMPY_HAS_NONZERO (1) +#endif + +#ifndef ULAB_NUMPY_HAS_NOTEQUAL +#define ULAB_NUMPY_HAS_NOTEQUAL (1) +#endif + +#ifndef ULAB_NUMPY_HAS_WHERE +#define ULAB_NUMPY_HAS_WHERE (1) +#endif + +// the linalg module; functions of the linalg module still have +// to be defined separately +#ifndef ULAB_NUMPY_HAS_LINALG_MODULE +#define ULAB_NUMPY_HAS_LINALG_MODULE (1) +#endif + +#ifndef ULAB_LINALG_HAS_CHOLESKY +#define ULAB_LINALG_HAS_CHOLESKY (1) +#endif + +#ifndef ULAB_LINALG_HAS_DET +#define ULAB_LINALG_HAS_DET (1) +#endif + +#ifndef ULAB_LINALG_HAS_EIG +#define ULAB_LINALG_HAS_EIG (1) +#endif + +#ifndef ULAB_LINALG_HAS_INV +#define ULAB_LINALG_HAS_INV (1) +#endif + +#ifndef ULAB_LINALG_HAS_NORM +#define ULAB_LINALG_HAS_NORM (1) +#endif + +#ifndef ULAB_LINALG_HAS_QR +#define ULAB_LINALG_HAS_QR (1) +#endif + +// the FFT module; functions of the fft module still have +// to be defined separately +#ifndef ULAB_NUMPY_HAS_FFT_MODULE +#define ULAB_NUMPY_HAS_FFT_MODULE (1) +#endif + +// By setting this constant to 1, the FFT routine will behave in a +// numpy-compatible way, i.e., it will output a complex array +// This setting has no effect, if ULAB_SUPPORTS_COMPLEX is 0 +// Note that in this case, the input also must be numpythonic, +// i.e., the real an imaginary parts cannot be passed as two arguments +#ifndef ULAB_FFT_IS_NUMPY_COMPATIBLE +#define ULAB_FFT_IS_NUMPY_COMPATIBLE (0) +#endif + +#ifndef ULAB_FFT_HAS_FFT +#define ULAB_FFT_HAS_FFT (1) +#endif + +#ifndef ULAB_FFT_HAS_IFFT +#define ULAB_FFT_HAS_IFFT (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ALL +#define ULAB_NUMPY_HAS_ALL (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ANY +#define ULAB_NUMPY_HAS_ANY (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ARGMINMAX +#define ULAB_NUMPY_HAS_ARGMINMAX (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ARGSORT +#define ULAB_NUMPY_HAS_ARGSORT (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ASARRAY +#define ULAB_NUMPY_HAS_ASARRAY (1) +#endif + +#ifndef ULAB_NUMPY_HAS_COMPRESS +#define ULAB_NUMPY_HAS_COMPRESS (1) +#endif + +#ifndef ULAB_NUMPY_HAS_CONVOLVE +#define ULAB_NUMPY_HAS_CONVOLVE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_CROSS +#define ULAB_NUMPY_HAS_CROSS (1) +#endif + +#ifndef ULAB_NUMPY_HAS_DELETE +#define ULAB_NUMPY_HAS_DELETE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_DIFF +#define ULAB_NUMPY_HAS_DIFF (1) +#endif + +#ifndef ULAB_NUMPY_HAS_DOT +#define ULAB_NUMPY_HAS_DOT (1) +#endif + +#ifndef ULAB_NUMPY_HAS_FLIP +#define ULAB_NUMPY_HAS_FLIP (1) +#endif + +#ifndef ULAB_NUMPY_HAS_INTERP +#define ULAB_NUMPY_HAS_INTERP (1) +#endif + +#ifndef ULAB_NUMPY_HAS_LOAD +#define ULAB_NUMPY_HAS_LOAD (1) +#endif + +#ifndef ULAB_NUMPY_HAS_LOADTXT +#define ULAB_NUMPY_HAS_LOADTXT (1) +#endif + +#ifndef ULAB_NUMPY_HAS_MEAN +#define ULAB_NUMPY_HAS_MEAN (1) +#endif + +#ifndef ULAB_NUMPY_HAS_MEDIAN +#define ULAB_NUMPY_HAS_MEDIAN (1) +#endif + +#ifndef ULAB_NUMPY_HAS_MINMAX +#define ULAB_NUMPY_HAS_MINMAX (1) +#endif + +#ifndef ULAB_NUMPY_HAS_POLYFIT +#define ULAB_NUMPY_HAS_POLYFIT (1) +#endif + +#ifndef ULAB_NUMPY_HAS_POLYVAL +#define ULAB_NUMPY_HAS_POLYVAL (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ROLL +#define ULAB_NUMPY_HAS_ROLL (1) +#endif + +#ifndef ULAB_NUMPY_HAS_SAVE +#define ULAB_NUMPY_HAS_SAVE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_SAVETXT +#define ULAB_NUMPY_HAS_SAVETXT (1) +#endif + +#ifndef ULAB_NUMPY_HAS_SIZE +#define ULAB_NUMPY_HAS_SIZE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_SORT +#define ULAB_NUMPY_HAS_SORT (1) +#endif + +#ifndef ULAB_NUMPY_HAS_STD +#define ULAB_NUMPY_HAS_STD (1) +#endif + +#ifndef ULAB_NUMPY_HAS_SUM +#define ULAB_NUMPY_HAS_SUM (1) +#endif + +#ifndef ULAB_NUMPY_HAS_TRACE +#define ULAB_NUMPY_HAS_TRACE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_TRAPZ +#define ULAB_NUMPY_HAS_TRAPZ (1) +#endif + +// vectorised versions of the functions of the math python module, with +// the exception of the functions listed in scipy.special +#ifndef ULAB_NUMPY_HAS_ACOS +#define ULAB_NUMPY_HAS_ACOS (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ACOSH +#define ULAB_NUMPY_HAS_ACOSH (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ARCTAN2 +#define ULAB_NUMPY_HAS_ARCTAN2 (1) +#endif + +#ifndef ULAB_NUMPY_HAS_AROUND +#define ULAB_NUMPY_HAS_AROUND (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ASIN +#define ULAB_NUMPY_HAS_ASIN (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ASINH +#define ULAB_NUMPY_HAS_ASINH (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ATAN +#define ULAB_NUMPY_HAS_ATAN (1) +#endif + +#ifndef ULAB_NUMPY_HAS_ATANH +#define ULAB_NUMPY_HAS_ATANH (1) +#endif + +#ifndef ULAB_NUMPY_HAS_CEIL +#define ULAB_NUMPY_HAS_CEIL (1) +#endif + +#ifndef ULAB_NUMPY_HAS_COS +#define ULAB_NUMPY_HAS_COS (1) +#endif + +#ifndef ULAB_NUMPY_HAS_COSH +#define ULAB_NUMPY_HAS_COSH (1) +#endif + +#ifndef ULAB_NUMPY_HAS_DEGREES +#define ULAB_NUMPY_HAS_DEGREES (1) +#endif + +#ifndef ULAB_NUMPY_HAS_EXP +#define ULAB_NUMPY_HAS_EXP (1) +#endif + +#ifndef ULAB_NUMPY_HAS_EXPM1 +#define ULAB_NUMPY_HAS_EXPM1 (1) +#endif + +#ifndef ULAB_NUMPY_HAS_FLOOR +#define ULAB_NUMPY_HAS_FLOOR (1) +#endif + +#ifndef ULAB_NUMPY_HAS_LOG +#define ULAB_NUMPY_HAS_LOG (1) +#endif + +#ifndef ULAB_NUMPY_HAS_LOG10 +#define ULAB_NUMPY_HAS_LOG10 (1) +#endif + +#ifndef ULAB_NUMPY_HAS_LOG2 +#define ULAB_NUMPY_HAS_LOG2 (1) +#endif + +#ifndef ULAB_NUMPY_HAS_RADIANS +#define ULAB_NUMPY_HAS_RADIANS (1) +#endif + +#ifndef ULAB_NUMPY_HAS_SIN +#define ULAB_NUMPY_HAS_SIN (1) +#endif + +#ifndef ULAB_NUMPY_HAS_SINH +#define ULAB_NUMPY_HAS_SINH (1) +#endif + +#ifndef ULAB_NUMPY_HAS_SQRT +#define ULAB_NUMPY_HAS_SQRT (1) +#endif + +#ifndef ULAB_NUMPY_HAS_TAN +#define ULAB_NUMPY_HAS_TAN (1) +#endif + +#ifndef ULAB_NUMPY_HAS_TANH +#define ULAB_NUMPY_HAS_TANH (1) +#endif + +#ifndef ULAB_NUMPY_HAS_VECTORIZE +#define ULAB_NUMPY_HAS_VECTORIZE (1) +#endif + +// Complex functions. The implementations are compiled into +// the firmware, only if ULAB_SUPPORTS_COMPLEX is set to 1 +#ifndef ULAB_NUMPY_HAS_CONJUGATE +#define ULAB_NUMPY_HAS_CONJUGATE (1) +#endif + +#ifndef ULAB_NUMPY_HAS_IMAG +#define ULAB_NUMPY_HAS_IMAG (1) +#endif + +#ifndef ULAB_NUMPY_HAS_REAL +#define ULAB_NUMPY_HAS_REAL (1) +#endif + +#ifndef ULAB_NUMPY_HAS_SORT_COMPLEX +#define ULAB_NUMPY_HAS_SORT_COMPLEX (1) +#endif + +// scipy modules +#ifndef ULAB_SCIPY_HAS_LINALG_MODULE +#define ULAB_SCIPY_HAS_LINALG_MODULE (1) +#endif + +#ifndef ULAB_SCIPY_LINALG_HAS_CHO_SOLVE +#define ULAB_SCIPY_LINALG_HAS_CHO_SOLVE (1) +#endif + +#ifndef ULAB_SCIPY_LINALG_HAS_SOLVE_TRIANGULAR +#define ULAB_SCIPY_LINALG_HAS_SOLVE_TRIANGULAR (1) +#endif + +#ifndef ULAB_SCIPY_HAS_SIGNAL_MODULE +#define ULAB_SCIPY_HAS_SIGNAL_MODULE (1) +#endif + +#ifndef ULAB_SCIPY_SIGNAL_HAS_SOSFILT +#define ULAB_SCIPY_SIGNAL_HAS_SOSFILT (1) +#endif + +#ifndef ULAB_SCIPY_HAS_OPTIMIZE_MODULE +#define ULAB_SCIPY_HAS_OPTIMIZE_MODULE (1) +#endif + +#ifndef ULAB_SCIPY_OPTIMIZE_HAS_BISECT +#define ULAB_SCIPY_OPTIMIZE_HAS_BISECT (1) +#endif + +#ifndef ULAB_SCIPY_OPTIMIZE_HAS_CURVE_FIT +#define ULAB_SCIPY_OPTIMIZE_HAS_CURVE_FIT (0) // not fully implemented +#endif + +#ifndef ULAB_SCIPY_OPTIMIZE_HAS_FMIN +#define ULAB_SCIPY_OPTIMIZE_HAS_FMIN (1) +#endif + +#ifndef ULAB_SCIPY_OPTIMIZE_HAS_NEWTON +#define ULAB_SCIPY_OPTIMIZE_HAS_NEWTON (1) +#endif + +#ifndef ULAB_SCIPY_HAS_SPECIAL_MODULE +#define ULAB_SCIPY_HAS_SPECIAL_MODULE (1) +#endif + +#ifndef ULAB_SCIPY_SPECIAL_HAS_ERF +#define ULAB_SCIPY_SPECIAL_HAS_ERF (1) +#endif + +#ifndef ULAB_SCIPY_SPECIAL_HAS_ERFC +#define ULAB_SCIPY_SPECIAL_HAS_ERFC (1) +#endif + +#ifndef ULAB_SCIPY_SPECIAL_HAS_GAMMA +#define ULAB_SCIPY_SPECIAL_HAS_GAMMA (1) +#endif + +#ifndef ULAB_SCIPY_SPECIAL_HAS_GAMMALN +#define ULAB_SCIPY_SPECIAL_HAS_GAMMALN (1) +#endif + +// functions of the utils module +#ifndef ULAB_HAS_UTILS_MODULE +#define ULAB_HAS_UTILS_MODULE (1) +#endif + +#ifndef ULAB_UTILS_HAS_FROM_INT16_BUFFER +#define ULAB_UTILS_HAS_FROM_INT16_BUFFER (1) +#endif + +#ifndef ULAB_UTILS_HAS_FROM_UINT16_BUFFER +#define ULAB_UTILS_HAS_FROM_UINT16_BUFFER (1) +#endif + +#ifndef ULAB_UTILS_HAS_FROM_INT32_BUFFER +#define ULAB_UTILS_HAS_FROM_INT32_BUFFER (1) +#endif + +#ifndef ULAB_UTILS_HAS_FROM_UINT32_BUFFER +#define ULAB_UTILS_HAS_FROM_UINT32_BUFFER (1) +#endif + +#ifndef ULAB_UTILS_HAS_SPECTROGRAM +#define ULAB_UTILS_HAS_SPECTROGRAM (1) +#endif + +// user-defined module; source of the module and +// its sub-modules should be placed in code/user/ +#ifndef ULAB_HAS_USER_MODULE +#define ULAB_HAS_USER_MODULE (0) +#endif + +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/ulab_tools.c b/components/3rd_party/omv/omv/modules/ulab/code/ulab_tools.c new file mode 100644 index 00000000..514721f7 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/ulab_tools.c @@ -0,0 +1,276 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2022 Zoltán Vörös + */ + + +#include +#include "py/runtime.h" + +#include "ulab.h" +#include "ndarray.h" +#include "ulab_tools.h" + +// The following five functions return a float from a void type +// The value in question is supposed to be located at the head of the pointer + +mp_float_t ndarray_get_float_uint8(void *data) { + // Returns a float value from an uint8_t type + return (mp_float_t)(*(uint8_t *)data); +} + +mp_float_t ndarray_get_float_int8(void *data) { + // Returns a float value from an int8_t type + return (mp_float_t)(*(int8_t *)data); +} + +mp_float_t ndarray_get_float_uint16(void *data) { + // Returns a float value from an uint16_t type + return (mp_float_t)(*(uint16_t *)data); +} + +mp_float_t ndarray_get_float_int16(void *data) { + // Returns a float value from an int16_t type + return (mp_float_t)(*(int16_t *)data); +} + + +mp_float_t ndarray_get_float_float(void *data) { + // Returns a float value from an mp_float_t type + return *((mp_float_t *)data); +} + +// returns a single function pointer, depending on the dtype +void *ndarray_get_float_function(uint8_t dtype) { + if(dtype == NDARRAY_UINT8) { + return ndarray_get_float_uint8; + } else if(dtype == NDARRAY_INT8) { + return ndarray_get_float_int8; + } else if(dtype == NDARRAY_UINT16) { + return ndarray_get_float_uint16; + } else if(dtype == NDARRAY_INT16) { + return ndarray_get_float_int16; + } else { + return ndarray_get_float_float; + } +} + +mp_float_t ndarray_get_float_index(void *data, uint8_t dtype, size_t index) { + // returns a single float value from an array located at index + if(dtype == NDARRAY_UINT8) { + return (mp_float_t)((uint8_t *)data)[index]; + } else if(dtype == NDARRAY_INT8) { + return (mp_float_t)((int8_t *)data)[index]; + } else if(dtype == NDARRAY_UINT16) { + return (mp_float_t)((uint16_t *)data)[index]; + } else if(dtype == NDARRAY_INT16) { + return (mp_float_t)((int16_t *)data)[index]; + } else { + return (mp_float_t)((mp_float_t *)data)[index]; + } +} + +mp_float_t ndarray_get_float_value(void *data, uint8_t dtype) { + // Returns a float value from an arbitrary data type + // The value in question is supposed to be located at the head of the pointer + if(dtype == NDARRAY_UINT8) { + return (mp_float_t)(*(uint8_t *)data); + } else if(dtype == NDARRAY_INT8) { + return (mp_float_t)(*(int8_t *)data); + } else if(dtype == NDARRAY_UINT16) { + return (mp_float_t)(*(uint16_t *)data); + } else if(dtype == NDARRAY_INT16) { + return (mp_float_t)(*(int16_t *)data); + } else { + return *((mp_float_t *)data); + } +} + +#if NDARRAY_BINARY_USES_FUN_POINTER | ULAB_NUMPY_HAS_WHERE +uint8_t ndarray_upcast_dtype(uint8_t ldtype, uint8_t rdtype) { + // returns a single character that corresponds to the broadcasting rules + // - if one of the operarands is a float, the result is always float + // - operation on identical types preserves type + // + // uint8 + int8 => int16 + // uint8 + int16 => int16 + // uint8 + uint16 => uint16 + // int8 + int16 => int16 + // int8 + uint16 => uint16 + // uint16 + int16 => float + + if(ldtype == rdtype) { + // if the two dtypes are equal, the result is also of that type + return ldtype; + } else if(((ldtype == NDARRAY_UINT8) && (rdtype == NDARRAY_INT8)) || + ((ldtype == NDARRAY_INT8) && (rdtype == NDARRAY_UINT8)) || + ((ldtype == NDARRAY_UINT8) && (rdtype == NDARRAY_INT16)) || + ((ldtype == NDARRAY_INT16) && (rdtype == NDARRAY_UINT8)) || + ((ldtype == NDARRAY_INT8) && (rdtype == NDARRAY_INT16)) || + ((ldtype == NDARRAY_INT16) && (rdtype == NDARRAY_INT8))) { + return NDARRAY_INT16; + } else if(((ldtype == NDARRAY_UINT8) && (rdtype == NDARRAY_UINT16)) || + ((ldtype == NDARRAY_UINT16) && (rdtype == NDARRAY_UINT8)) || + ((ldtype == NDARRAY_INT8) && (rdtype == NDARRAY_UINT16)) || + ((ldtype == NDARRAY_UINT16) && (rdtype == NDARRAY_INT8))) { + return NDARRAY_UINT16; + } + return NDARRAY_FLOAT; +} + +// The following five functions are the inverse of the ndarray_get_... functions, +// and write a floating point datum into a void pointer + +void ndarray_set_float_uint8(void *data, mp_float_t datum) { + *((uint8_t *)data) = (uint8_t)datum; +} + +void ndarray_set_float_int8(void *data, mp_float_t datum) { + *((int8_t *)data) = (int8_t)datum; +} + +void ndarray_set_float_uint16(void *data, mp_float_t datum) { + *((uint16_t *)data) = (uint16_t)datum; +} + +void ndarray_set_float_int16(void *data, mp_float_t datum) { + *((int16_t *)data) = (int16_t)datum; +} + +void ndarray_set_float_float(void *data, mp_float_t datum) { + *((mp_float_t *)data) = datum; +} + +// returns a single function pointer, depending on the dtype +void *ndarray_set_float_function(uint8_t dtype) { + if(dtype == NDARRAY_UINT8) { + return ndarray_set_float_uint8; + } else if(dtype == NDARRAY_INT8) { + return ndarray_set_float_int8; + } else if(dtype == NDARRAY_UINT16) { + return ndarray_set_float_uint16; + } else if(dtype == NDARRAY_INT16) { + return ndarray_set_float_int16; + } else { + return ndarray_set_float_float; + } +} +#endif /* NDARRAY_BINARY_USES_FUN_POINTER */ + +shape_strides tools_reduce_axes(ndarray_obj_t *ndarray, mp_obj_t axis) { + // TODO: replace numerical_reduce_axes with this function, wherever applicable + // This function should be used, whenever a tensor is contracted; + // The shape and strides at `axis` are moved to the zeroth position, + // everything else is aligned to the right + if(!mp_obj_is_int(axis) & (axis != mp_const_none)) { + mp_raise_TypeError(translate("axis must be None, or an integer")); + } + shape_strides _shape_strides; + + size_t *shape = m_new(size_t, ULAB_MAX_DIMS + 1); + _shape_strides.shape = shape; + int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS + 1); + _shape_strides.strides = strides; + + _shape_strides.increment = 0; + // this is the contracted dimension (won't be overwritten for axis == None) + _shape_strides.ndim = 0; + + memcpy(_shape_strides.shape, ndarray->shape, sizeof(size_t) * ULAB_MAX_DIMS); + memcpy(_shape_strides.strides, ndarray->strides, sizeof(int32_t) * ULAB_MAX_DIMS); + + if(axis == mp_const_none) { + return _shape_strides; + } + + uint8_t index = ULAB_MAX_DIMS - 1; // value of index for axis == mp_const_none (won't be overwritten) + + if(axis != mp_const_none) { // i.e., axis is an integer + int8_t ax = mp_obj_get_int(axis); + if(ax < 0) ax += ndarray->ndim; + if((ax < 0) || (ax > ndarray->ndim - 1)) { + mp_raise_ValueError(translate("index out of range")); + } + index = ULAB_MAX_DIMS - ndarray->ndim + ax; + _shape_strides.ndim = ndarray->ndim - 1; + } + + // move the value stored at index to the leftmost position, and align everything else to the right + _shape_strides.shape[0] = ndarray->shape[index]; + _shape_strides.strides[0] = ndarray->strides[index]; + for(uint8_t i = 0; i < index; i++) { + // entries to the right of index must be shifted by one position to the left + _shape_strides.shape[i + 1] = ndarray->shape[i]; + _shape_strides.strides[i + 1] = ndarray->strides[i]; + } + + if(_shape_strides.ndim != 0) { + _shape_strides.increment = 1; + } + + return _shape_strides; +} + +int8_t tools_get_axis(mp_obj_t axis, uint8_t ndim) { + int8_t ax = mp_obj_get_int(axis); + if(ax < 0) ax += ndim; + if((ax < 0) || (ax > ndim - 1)) { + mp_raise_ValueError(translate("axis is out of bounds")); + } + return ax; +} + +#if ULAB_MAX_DIMS > 1 +ndarray_obj_t *tools_object_is_square(mp_obj_t obj) { + // Returns an ndarray, if the object is a square ndarray, + // raises the appropriate exception otherwise + if(!mp_obj_is_type(obj, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("size is defined for ndarrays only")); + } + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(obj); + if((ndarray->shape[ULAB_MAX_DIMS - 1] != ndarray->shape[ULAB_MAX_DIMS - 2]) || (ndarray->ndim != 2)) { + mp_raise_ValueError(translate("input must be square matrix")); + } + return ndarray; +} +#endif + +uint8_t ulab_binary_get_size(uint8_t dtype) { + #if ULAB_SUPPORTS_COMPLEX + if(dtype == NDARRAY_COMPLEX) { + return 2 * (uint8_t)sizeof(mp_float_t); + } + #endif + return dtype == NDARRAY_BOOL ? 1 : mp_binary_get_size('@', dtype, NULL); +} + +#if ULAB_SUPPORTS_COMPLEX +void ulab_rescale_float_strides(int32_t *strides) { + // re-scale the strides, so that we can work with floats, when iterating + uint8_t sz = sizeof(mp_float_t); + for(uint8_t i = 0; i < ULAB_MAX_DIMS; i++) { + strides[i] /= sz; + } +} +#endif + +bool ulab_tools_mp_obj_is_scalar(mp_obj_t obj) { + #if ULAB_SUPPORTS_COMPLEX + if(mp_obj_is_int(obj) || mp_obj_is_float(obj) || mp_obj_is_type(obj, &mp_type_complex)) { + return true; + } else { + return false; + } + #else + if(mp_obj_is_int(obj) || mp_obj_is_float(obj)) { + return true; + } else { + return false; + } + #endif +} diff --git a/components/3rd_party/omv/omv/modules/ulab/code/ulab_tools.h b/components/3rd_party/omv/omv/modules/ulab/code/ulab_tools.h new file mode 100644 index 00000000..5ae99df9 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/ulab_tools.h @@ -0,0 +1,46 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2022 Zoltán Vörös +*/ + +#ifndef _TOOLS_ +#define _TOOLS_ + +#include "ndarray.h" + +#define SWAP(t, a, b) { t tmp = a; a = b; b = tmp; } + +typedef struct _shape_strides_t { + uint8_t increment; + uint8_t ndim; + size_t *shape; + int32_t *strides; +} shape_strides; + +mp_float_t ndarray_get_float_uint8(void *); +mp_float_t ndarray_get_float_int8(void *); +mp_float_t ndarray_get_float_uint16(void *); +mp_float_t ndarray_get_float_int16(void *); +mp_float_t ndarray_get_float_float(void *); +void *ndarray_get_float_function(uint8_t ); + +uint8_t ndarray_upcast_dtype(uint8_t , uint8_t ); +void *ndarray_set_float_function(uint8_t ); + +shape_strides tools_reduce_axes(ndarray_obj_t *, mp_obj_t ); +int8_t tools_get_axis(mp_obj_t , uint8_t ); +ndarray_obj_t *tools_object_is_square(mp_obj_t ); + +uint8_t ulab_binary_get_size(uint8_t ); + +#if ULAB_SUPPORTS_COMPLEX +void ulab_rescale_float_strides(int32_t *); +#endif + +bool ulab_tools_mp_obj_is_scalar(mp_obj_t ); +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/user/user.c b/components/3rd_party/omv/omv/modules/ulab/code/user/user.c new file mode 100644 index 00000000..dfc3fcde --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/user/user.c @@ -0,0 +1,102 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös +*/ + +#include +#include +#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" +#include "user.h" + +#if ULAB_HAS_USER_MODULE + +//| """This module should hold arbitrary user-defined functions.""" +//| + +static mp_obj_t user_square(mp_obj_t arg) { + // the function takes a single dense ndarray, and calculates the + // element-wise square of its entries + + // raise a TypeError exception, if the input is not an ndarray + if(!mp_obj_is_type(arg, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("input must be an ndarray")); + } + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(arg); + + // make sure that the input is a dense array + if(!ndarray_is_dense(ndarray)) { + mp_raise_TypeError(translate("input must be a dense ndarray")); + } + + // if the input is a dense array, create `results` with the same number of + // dimensions, shape, and dtype + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndarray->ndim, ndarray->shape, ndarray->dtype); + + // since in a dense array the iteration over the elements is trivial, we + // can cast the data arrays ndarray->array and results->array to the actual type + if(ndarray->dtype == NDARRAY_UINT8) { + uint8_t *array = (uint8_t *)ndarray->array; + uint8_t *rarray = (uint8_t *)results->array; + for(size_t i=0; i < ndarray->len; i++, array++) { + *rarray++ = (*array) * (*array); + } + } else if(ndarray->dtype == NDARRAY_INT8) { + int8_t *array = (int8_t *)ndarray->array; + int8_t *rarray = (int8_t *)results->array; + for(size_t i=0; i < ndarray->len; i++, array++) { + *rarray++ = (*array) * (*array); + } + } else if(ndarray->dtype == NDARRAY_UINT16) { + uint16_t *array = (uint16_t *)ndarray->array; + uint16_t *rarray = (uint16_t *)results->array; + for(size_t i=0; i < ndarray->len; i++, array++) { + *rarray++ = (*array) * (*array); + } + } else if(ndarray->dtype == NDARRAY_INT16) { + int16_t *array = (int16_t *)ndarray->array; + int16_t *rarray = (int16_t *)results->array; + for(size_t i=0; i < ndarray->len; i++, array++) { + *rarray++ = (*array) * (*array); + } + } else { // if we end up here, the dtype is NDARRAY_FLOAT + mp_float_t *array = (mp_float_t *)ndarray->array; + mp_float_t *rarray = (mp_float_t *)results->array; + for(size_t i=0; i < ndarray->len; i++, array++) { + *rarray++ = (*array) * (*array); + } + } + // at the end, return a micrppython object + return MP_OBJ_FROM_PTR(results); +} + +MP_DEFINE_CONST_FUN_OBJ_1(user_square_obj, user_square); + +static const mp_rom_map_elem_t ulab_user_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_user) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_square), (mp_obj_t)&user_square_obj }, +}; + +static MP_DEFINE_CONST_DICT(mp_module_ulab_user_globals, ulab_user_globals_table); + +const mp_obj_module_t ulab_user_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_user_globals, +}; +#if CIRCUITPY_ULAB +#if !defined(MICROPY_VERSION) || MICROPY_VERSION <= 70144 +MP_REGISTER_MODULE(MP_QSTR_ulab_dot_user, ulab_user_module, ULAB_HAS_USER_MODULE); +#else +MP_REGISTER_MODULE(MP_QSTR_ulab_dot_user, ulab_user_module); +#endif +#endif + +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/user/user.h b/components/3rd_party/omv/omv/modules/ulab/code/user/user.h new file mode 100644 index 00000000..ff274f43 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/user/user.h @@ -0,0 +1,20 @@ + +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös +*/ + +#ifndef _USER_ +#define _USER_ + +#include "../ulab.h" +#include "../ndarray.h" + +extern const mp_obj_module_t ulab_user_module; + +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/code/utils/utils.c b/components/3rd_party/omv/omv/modules/ulab/code/utils/utils.c new file mode 100644 index 00000000..fee67d47 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/utils/utils.c @@ -0,0 +1,260 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös +*/ + +#include +#include +#include +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" +#include "utils.h" + +#include "../numpy/fft/fft_tools.h" + +#if ULAB_HAS_UTILS_MODULE + +enum UTILS_BUFFER_TYPE { + UTILS_INT16_BUFFER, + UTILS_UINT16_BUFFER, + UTILS_INT32_BUFFER, + UTILS_UINT32_BUFFER, +}; + +#if ULAB_UTILS_HAS_FROM_INT16_BUFFER | ULAB_UTILS_HAS_FROM_UINT16_BUFFER | ULAB_UTILS_HAS_FROM_INT32_BUFFER | ULAB_UTILS_HAS_FROM_UINT32_BUFFER +static mp_obj_t utils_from_intbuffer_helper(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args, uint8_t buffer_type) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } } , + { MP_QSTR_count, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_INT(-1) } }, + { MP_QSTR_offset, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_INT(0) } }, + { MP_QSTR_out, MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_byteswap, MP_ARG_OBJ, { .u_rom_obj = MP_ROM_FALSE } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + ndarray_obj_t *ndarray = NULL; + + if(args[3].u_obj != mp_const_none) { + ndarray = MP_OBJ_TO_PTR(args[3].u_obj); + if((ndarray->dtype != NDARRAY_FLOAT) || !ndarray_is_dense(ndarray)) { + mp_raise_TypeError(translate("out must be a float dense array")); + } + } + + size_t offset = mp_obj_get_int(args[2].u_obj); + + mp_buffer_info_t bufinfo; + if(mp_get_buffer(args[0].u_obj, &bufinfo, MP_BUFFER_READ)) { + if(bufinfo.len < offset) { + mp_raise_ValueError(translate("offset is too large")); + } + uint8_t sz = sizeof(int16_t); + #if ULAB_UTILS_HAS_FROM_INT32_BUFFER | ULAB_UTILS_HAS_FROM_UINT32_BUFFER + if((buffer_type == UTILS_INT32_BUFFER) || (buffer_type == UTILS_UINT32_BUFFER)) { + sz = sizeof(int32_t); + } + #endif + + size_t len = (bufinfo.len - offset) / sz; + if((len * sz) != (bufinfo.len - offset)) { + mp_raise_ValueError(translate("buffer size must be a multiple of element size")); + } + if(mp_obj_get_int(args[1].u_obj) > 0) { + size_t count = mp_obj_get_int(args[1].u_obj); + if(len < count) { + mp_raise_ValueError(translate("buffer is smaller than requested size")); + } else { + len = count; + } + } + if(args[3].u_obj == mp_const_none) { + ndarray = ndarray_new_linear_array(len, NDARRAY_FLOAT); + } else { + if(ndarray->len < len) { + mp_raise_ValueError(translate("out array is too small")); + } + } + uint8_t *buffer = bufinfo.buf; + + mp_float_t *array = (mp_float_t *)ndarray->array; + if(args[4].u_obj == mp_const_true) { + // swap the bytes before conversion + uint8_t *tmpbuff = m_new(uint8_t, sz); + #if ULAB_UTILS_HAS_FROM_INT16_BUFFER | ULAB_UTILS_HAS_FROM_UINT16_BUFFER + if((buffer_type == UTILS_INT16_BUFFER) || (buffer_type == UTILS_UINT16_BUFFER)) { + for(size_t i = 0; i < len; i++) { + tmpbuff += sz; + for(uint8_t j = 0; j < sz; j++) { + memcpy(--tmpbuff, buffer++, 1); + } + if(buffer_type == UTILS_INT16_BUFFER) { + *array++ = (mp_float_t)(*(int16_t *)tmpbuff); + } else { + *array++ = (mp_float_t)(*(uint16_t *)tmpbuff); + } + } + } + #endif + #if ULAB_UTILS_HAS_FROM_INT32_BUFFER | ULAB_UTILS_HAS_FROM_UINT32_BUFFER + if((buffer_type == UTILS_INT32_BUFFER) || (buffer_type == UTILS_UINT32_BUFFER)) { + for(size_t i = 0; i < len; i++) { + tmpbuff += sz; + for(uint8_t j = 0; j < sz; j++) { + memcpy(--tmpbuff, buffer++, 1); + } + if(buffer_type == UTILS_INT32_BUFFER) { + *array++ = (mp_float_t)(*(int32_t *)tmpbuff); + } else { + *array++ = (mp_float_t)(*(uint32_t *)tmpbuff); + } + } + } + #endif + } else { + #if ULAB_UTILS_HAS_FROM_INT16_BUFFER + if(buffer_type == UTILS_INT16_BUFFER) { + for(size_t i = 0; i < len; i++) { + *array++ = (mp_float_t)(*(int16_t *)buffer); + buffer += sz; + } + } + #endif + #if ULAB_UTILS_HAS_FROM_UINT16_BUFFER + if(buffer_type == UTILS_UINT16_BUFFER) { + for(size_t i = 0; i < len; i++) { + *array++ = (mp_float_t)(*(uint16_t *)buffer); + buffer += sz; + } + } + #endif + #if ULAB_UTILS_HAS_FROM_INT32_BUFFER + if(buffer_type == UTILS_INT32_BUFFER) { + for(size_t i = 0; i < len; i++) { + *array++ = (mp_float_t)(*(int32_t *)buffer); + buffer += sz; + } + } + #endif + #if ULAB_UTILS_HAS_FROM_UINT32_BUFFER + if(buffer_type == UTILS_UINT32_BUFFER) { + for(size_t i = 0; i < len; i++) { + *array++ = (mp_float_t)(*(uint32_t *)buffer); + buffer += sz; + } + } + #endif + } + return MP_OBJ_FROM_PTR(ndarray); + } + return mp_const_none; +} + +#ifdef ULAB_UTILS_HAS_FROM_INT16_BUFFER +static mp_obj_t utils_from_int16_buffer(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return utils_from_intbuffer_helper(n_args, pos_args, kw_args, UTILS_INT16_BUFFER); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(utils_from_int16_buffer_obj, 1, utils_from_int16_buffer); +#endif + +#ifdef ULAB_UTILS_HAS_FROM_UINT16_BUFFER +static mp_obj_t utils_from_uint16_buffer(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return utils_from_intbuffer_helper(n_args, pos_args, kw_args, UTILS_UINT16_BUFFER); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(utils_from_uint16_buffer_obj, 1, utils_from_uint16_buffer); +#endif + +#ifdef ULAB_UTILS_HAS_FROM_INT32_BUFFER +static mp_obj_t utils_from_int32_buffer(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return utils_from_intbuffer_helper(n_args, pos_args, kw_args, UTILS_INT32_BUFFER); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(utils_from_int32_buffer_obj, 1, utils_from_int32_buffer); +#endif + +#ifdef ULAB_UTILS_HAS_FROM_UINT32_BUFFER +static mp_obj_t utils_from_uint32_buffer(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return utils_from_intbuffer_helper(n_args, pos_args, kw_args, UTILS_UINT32_BUFFER); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(utils_from_uint32_buffer_obj, 1, utils_from_uint32_buffer); +#endif + +#endif /* ULAB_UTILS_HAS_FROM_INT16_BUFFER | ULAB_UTILS_HAS_FROM_UINT16_BUFFER | ULAB_UTILS_HAS_FROM_INT32_BUFFER | ULAB_UTILS_HAS_FROM_UINT32_BUFFER */ + +#if ULAB_UTILS_HAS_SPECTROGRAM +//| import ulab.numpy +//| +//| def spectrogram(r: ulab.numpy.ndarray) -> ulab.numpy.ndarray: +//| """ +//| :param ulab.numpy.ndarray r: A 1-dimension array of values whose size is a power of 2 +//| +//| Computes the spectrum of the input signal. This is the absolute value of the (complex-valued) fft of the signal. +//| This function is similar to scipy's ``scipy.signal.welch`` https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.welch.html.""" +//| ... +//| + +mp_obj_t utils_spectrogram(size_t n_args, const mp_obj_t *args) { + #if ULAB_SUPPORTS_COMPLEX & ULAB_FFT_IS_NUMPY_COMPATIBLE + return fft_fft_ifft_spectrogram(args[0], FFT_SPECTROGRAM); + #else + if(n_args == 2) { + return fft_fft_ifft_spectrogram(n_args, args[0], args[1], FFT_SPECTROGRAM); + } else { + return fft_fft_ifft_spectrogram(n_args, args[0], mp_const_none, FFT_SPECTROGRAM); + } + #endif +} + +#if ULAB_SUPPORTS_COMPLEX & ULAB_FFT_IS_NUMPY_COMPATIBLE +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(utils_spectrogram_obj, 1, 1, utils_spectrogram); +#else +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(utils_spectrogram_obj, 1, 2, utils_spectrogram); +#endif + +#endif /* ULAB_UTILS_HAS_SPECTROGRAM */ + + +static const mp_rom_map_elem_t ulab_utils_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_utils) }, + #if ULAB_UTILS_HAS_FROM_INT16_BUFFER + { MP_ROM_QSTR(MP_QSTR_from_int16_buffer), MP_ROM_PTR(&utils_from_int16_buffer_obj) }, + #endif + #if ULAB_UTILS_HAS_FROM_UINT16_BUFFER + { MP_ROM_QSTR(MP_QSTR_from_uint16_buffer), MP_ROM_PTR(&utils_from_uint16_buffer_obj) }, + #endif + #if ULAB_UTILS_HAS_FROM_INT32_BUFFER + { MP_ROM_QSTR(MP_QSTR_from_int32_buffer), MP_ROM_PTR(&utils_from_int32_buffer_obj) }, + #endif + #if ULAB_UTILS_HAS_FROM_UINT32_BUFFER + { MP_ROM_QSTR(MP_QSTR_from_uint32_buffer), MP_ROM_PTR(&utils_from_uint32_buffer_obj) }, + #endif + #if ULAB_UTILS_HAS_SPECTROGRAM + { MP_ROM_QSTR(MP_QSTR_spectrogram), MP_ROM_PTR(&utils_spectrogram_obj) }, + #endif +}; + +static MP_DEFINE_CONST_DICT(mp_module_ulab_utils_globals, ulab_utils_globals_table); + +const mp_obj_module_t ulab_utils_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_ulab_utils_globals, +}; +#if CIRCUITPY_ULAB +#if !defined(MICROPY_VERSION) || MICROPY_VERSION <= 70144 +MP_REGISTER_MODULE(MP_QSTR_ulab_dot_utils, ulab_utils_module, MODULE_ULAB_ENABLED); +#else +MP_REGISTER_MODULE(MP_QSTR_ulab_dot_utils, ulab_utils_module); +#endif +#endif + +#endif /* ULAB_HAS_UTILS_MODULE */ diff --git a/components/3rd_party/omv/omv/modules/ulab/code/utils/utils.h b/components/3rd_party/omv/omv/modules/ulab/code/utils/utils.h new file mode 100644 index 00000000..b2155c38 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/code/utils/utils.h @@ -0,0 +1,19 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös +*/ + +#ifndef _UTILS_ +#define _UTILS_ + +#include "../ulab.h" +#include "../ndarray.h" + +extern const mp_obj_module_t ulab_utils_module; + +#endif diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/manual/Makefile b/components/3rd_party/omv/omv/modules/ulab/docs/manual/Makefile new file mode 100644 index 00000000..a97f7258 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/manual/Makefile @@ -0,0 +1,24 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +clean: + rm -rf "$(BUILDDIR)" + + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/manual/make.bat b/components/3rd_party/omv/omv/modules/ulab/docs/manual/make.bat new file mode 100644 index 00000000..6247f7e2 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/manual/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/conf.py b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/conf.py new file mode 100644 index 00000000..3f4d5eb4 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/conf.py @@ -0,0 +1,112 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + +#import sphinx_rtd_theme + +from sphinx.transforms import SphinxTransform +from docutils import nodes +from sphinx import addnodes + +# -- Project information ----------------------------------------------------- + +project = 'The ulab book' +copyright = '2019-2022, Zoltán Vörös and contributors' +author = 'Zoltán Vörös' + +# The full version, including alpha/beta/rc tags +release = '5.1.0' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +latex_maketitle = r''' +\begin{titlepage} +\begin{flushright} +\Huge\textbf{The $\mu$lab book} +\vskip 0.5em +\LARGE +\textbf{Release %s} +\vskip 5em +\huge\textbf{Zoltán Vörös} +\end{flushright} +\begin{flushright} +\LARGE +\vskip 2em +with contributions by +\vskip 2em +\textbf{Roberto Colistete Jr.} +\vskip 0.2em +\textbf{Jeff Epler} +\vskip 0.2em +\textbf{Taku Fukada} +\vskip 0.2em +\textbf{Diego Elio Pettenò} +\vskip 0.2em +\textbf{Scott Shawcroft} +\vskip 5em +\today +\end{flushright} +\end{titlepage} +'''%release + +latex_elements = { + 'maketitle': latex_maketitle +} + + +master_doc = 'index' + +author=u'Zoltán Vörös' +copyright=author +language='en' + +latex_documents = [ +(master_doc, 'the-ulab-book.tex', 'The $\mu$lab book', +'Zoltán Vörös', 'manual'), +] + +# Read the docs theme +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +if not on_rtd: + try: + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), '.'] + except ImportError: + html_theme = 'default' + html_theme_path = ['.'] +else: + html_theme_path = ['.'] diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/index.rst b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/index.rst new file mode 100644 index 00000000..1bae7a3d --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/index.rst @@ -0,0 +1,38 @@ + +.. ulab-manual documentation master file, created by + sphinx-quickstart on Sat Oct 19 12:48:00 2019. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to the ulab book! +======================================= + +.. toctree:: + :maxdepth: 2 + :caption: Introduction + + ulab-intro + +.. toctree:: + :maxdepth: 2 + :caption: User's guide: + + ulab-ndarray + numpy-functions + numpy-universal + numpy-fft + numpy-linalg + scipy-linalg + scipy-optimize + scipy-signal + scipy-special + ulab-utils + ulab-tricks + ulab-programming + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/numpy-fft.rst b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/numpy-fft.rst new file mode 100644 index 00000000..7da9b60e --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/numpy-fft.rst @@ -0,0 +1,197 @@ + +numpy.fft +========= + +Functions related to Fourier transforms can be called by prepending them +with ``numpy.fft.``. The module defines the following two functions: + +1. `numpy.fft.fft <#fft>`__ +2. `numpy.fft.ifft <#ifft>`__ + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.fft.ifft.html + +fft +--- + +Since ``ulab``\ ’s ``ndarray`` does not support complex numbers, the +invocation of the Fourier transform differs from that in ``numpy``. In +``numpy``, you can simply pass an array or iterable to the function, and +it will be treated as a complex array: + +.. code:: + + # code to be run in CPython + + fft.fft([1, 2, 3, 4, 1, 2, 3, 4]) + + + +.. parsed-literal:: + + array([20.+0.j, 0.+0.j, -4.+4.j, 0.+0.j, -4.+0.j, 0.+0.j, -4.-4.j, + 0.+0.j]) + + + +**WARNING:** The array returned is also complex, i.e., the real and +imaginary components are cast together. In ``ulab``, the real and +imaginary parts are treated separately: you have to pass two +``ndarray``\ s to the function, although, the second argument is +optional, in which case the imaginary part is assumed to be zero. + +**WARNING:** The function, as opposed to ``numpy``, returns a 2-tuple, +whose elements are two ``ndarray``\ s, holding the real and imaginary +parts of the transform separately. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + x = np.linspace(0, 10, num=1024) + y = np.sin(x) + z = np.zeros(len(x)) + + a, b = np.fft.fft(x) + print('real part:\t', a) + print('\nimaginary part:\t', b) + + c, d = np.fft.fft(x, z) + print('\nreal part:\t', c) + print('\nimaginary part:\t', d) + +.. parsed-literal:: + + real part: array([5119.996, -5.004663, -5.004798, ..., -5.005482, -5.005643, -5.006577], dtype=float) + + imaginary part: array([0.0, 1631.333, 815.659, ..., -543.764, -815.6588, -1631.333], dtype=float) + + real part: array([5119.996, -5.004663, -5.004798, ..., -5.005482, -5.005643, -5.006577], dtype=float) + + imaginary part: array([0.0, 1631.333, 815.659, ..., -543.764, -815.6588, -1631.333], dtype=float) + + + +ulab with complex support +~~~~~~~~~~~~~~~~~~~~~~~~~ + +If the ``ULAB_SUPPORTS_COMPLEX``, and ``ULAB_FFT_IS_NUMPY_COMPATIBLE`` +pre-processor constants are set to 1 in +`ulab.h `__ +as + +.. code:: c + + // Adds support for complex ndarrays + #ifndef ULAB_SUPPORTS_COMPLEX + #define ULAB_SUPPORTS_COMPLEX (1) + #endif + +.. code:: c + + #ifndef ULAB_FFT_IS_NUMPY_COMPATIBLE + #define ULAB_FFT_IS_NUMPY_COMPATIBLE (1) + #endif + +then the FFT routine will behave in a ``numpy``-compatible way: the +single input array can either be real, in which case the imaginary part +is assumed to be zero, or complex. The output is also complex. + +While ``numpy``-compatibility might be a desired feature, it has one +side effect, namely, the FFT routine consumes approx. 50% more RAM. The +reason for this lies in the implementation details. + +ifft +---- + +The above-mentioned rules apply to the inverse Fourier transform. The +inverse is also normalised by ``N``, the number of elements, as is +customary in ``numpy``. With the normalisation, we can ascertain that +the inverse of the transform is equal to the original array. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + x = np.linspace(0, 10, num=1024) + y = np.sin(x) + + a, b = np.fft.fft(y) + + print('original vector:\t', y) + + y, z = np.fft.ifft(a, b) + # the real part should be equal to y + print('\nreal part of inverse:\t', y) + # the imaginary part should be equal to zero + print('\nimaginary part of inverse:\t', z) + +.. parsed-literal:: + + original vector: array([0.0, 0.009775016, 0.0195491, ..., -0.5275068, -0.5357859, -0.5440139], dtype=float) + + real part of inverse: array([-2.980232e-08, 0.0097754, 0.0195494, ..., -0.5275064, -0.5357857, -0.5440133], dtype=float) + + imaginary part of inverse: array([-2.980232e-08, -1.451171e-07, 3.693752e-08, ..., 6.44871e-08, 9.34986e-08, 2.18336e-07], dtype=float) + + + +Note that unlike in ``numpy``, the length of the array on which the +Fourier transform is carried out must be a power of 2. If this is not +the case, the function raises a ``ValueError`` exception. + +ulab with complex support +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``fft.ifft`` function can also be made ``numpy``-compatible by +setting the ``ULAB_SUPPORTS_COMPLEX``, and +``ULAB_FFT_IS_NUMPY_COMPATIBLE`` pre-processor constants to 1. + +Computation and storage costs +----------------------------- + +RAM +~~~ + +The FFT routine of ``ulab`` calculates the transform in place. This +means that beyond reserving space for the two ``ndarray``\ s that will +be returned (the computation uses these two as intermediate storage +space), only a handful of temporary variables, all floats or 32-bit +integers, are required. + +Speed of FFTs +~~~~~~~~~~~~~ + +A comment on the speed: a 1024-point transform implemented in python +would cost around 90 ms, and 13 ms in assembly, if the code runs on the +pyboard, v.1.1. You can gain a factor of four by moving to the D series +https://github.com/peterhinch/micropython-fourier/blob/master/README.md#8-performance. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + x = np.linspace(0, 10, num=1024) + y = np.sin(x) + + @timeit + def np_fft(y): + return np.fft.fft(y) + + a, b = np_fft(y) + +.. parsed-literal:: + + execution time: 1985 us + + + +The C implementation runs in less than 2 ms on the pyboard (we have just +measured that), and has been reported to run in under 0.8 ms on the D +series board. That is an improvement of at least a factor of four. diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/numpy-functions.rst b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/numpy-functions.rst new file mode 100644 index 00000000..aac19bde --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/numpy-functions.rst @@ -0,0 +1,2031 @@ + +Numpy functions +=============== + +This section of the manual discusses those functions that were adapted +from ``numpy``. Starred functions accept complex arrays as arguments, if +the firmware was compiled with complex support. + +1. `numpy.all\* <#all>`__ +2. `numpy.any\* <#any>`__ +3. `numpy.argmax <#argmax>`__ +4. `numpy.argmin <#argmin>`__ +5. `numpy.argsort <#argsort>`__ +6. `numpy.asarray\* <#asarray>`__ +7. `numpy.clip <#clip>`__ +8. `numpy.compress\* <#compress>`__ +9. `numpy.conjugate\* <#conjugate>`__ +10. `numpy.convolve\* <#convolve>`__ +11. `numpy.delete <#delete>`__ +12. `numpy.diff <#diff>`__ +13. `numpy.dot <#dot>`__ +14. `numpy.equal <#equal>`__ +15. `numpy.flip\* <#flip>`__ +16. `numpy.imag\* <#imag>`__ +17. `numpy.interp <#interp>`__ +18. `numpy.isfinite <#isfinite>`__ +19. `numpy.isinf <#isinf>`__ +20. `numpy.load <#load>`__ +21. `numpy.loadtxt <#loadtxt>`__ +22. `numpy.max <#max>`__ +23. `numpy.maximum <#maximum>`__ +24. `numpy.mean <#mean>`__ +25. `numpy.median <#median>`__ +26. `numpy.min <#min>`__ +27. `numpy.minimum <#minimum>`__ +28. `numpy.nozero <#nonzero>`__ +29. `numpy.not_equal <#equal>`__ +30. `numpy.polyfit <#polyfit>`__ +31. `numpy.polyval <#polyval>`__ +32. `numpy.real\* <#real>`__ +33. `numpy.roll <#roll>`__ +34. `numpy.save <#save>`__ +35. `numpy.savetxt <#savetxt>`__ +36. `numpy.size <#size>`__ +37. `numpy.sort <#sort>`__ +38. `numpy.sort_complex\* <#sort_complex>`__ +39. `numpy.std <#std>`__ +40. `numpy.sum <#sum>`__ +41. `numpy.trace <#trace>`__ +42. `numpy.trapz <#trapz>`__ +43. `numpy.where <#where>`__ + +all +--- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.all.html + +The function takes one positional, and one keyword argument, the +``axis``, with a default value of ``None``, and tests, whether *all* +array elements along the given axis evaluate to ``True``. If the keyword +argument is ``None``, the flattened array is inspected. + +Elements of an array evaluate to ``True``, if they are not equal to +zero, or the Boolean ``False``. The return value if a Boolean +``ndarray``. + +If the firmware was compiled with complex support, the function can +accept complex arrays. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(12)).reshape((3, 4)) + + print('\na:\n', a) + + b = np.all(a) + print('\nall of the flattened array:\n', b) + + c = np.all(a, axis=0) + print('\nall of a along 0th axis:\n', c) + + d = np.all(a, axis=1) + print('\nall of a along 1st axis:\n', d) + +.. parsed-literal:: + + + a: + array([[0.0, 1.0, 2.0, 3.0], + [4.0, 5.0, 6.0, 7.0], + [8.0, 9.0, 10.0, 11.0]], dtype=float64) + + all of the flattened array: + False + + all of a along 0th axis: + array([False, True, True, True], dtype=bool) + + all of a along 1st axis: + array([False, True, True], dtype=bool) + + + + +any +--- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.any.html + +The function takes one positional, and one keyword argument, the +``axis``, with a default value of ``None``, and tests, whether *any* +array element along the given axis evaluates to ``True``. If the keyword +argument is ``None``, the flattened array is inspected. + +Elements of an array evaluate to ``True``, if they are not equal to +zero, or the Boolean ``False``. The return value if a Boolean +``ndarray``. + +If the firmware was compiled with complex support, the function can +accept complex arrays. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(12)).reshape((3, 4)) + + print('\na:\n', a) + + b = np.any(a) + print('\nany of the flattened array:\n', b) + + c = np.any(a, axis=0) + print('\nany of a along 0th axis:\n', c) + + d = np.any(a, axis=1) + print('\nany of a along 1st axis:\n', d) + +.. parsed-literal:: + + + a: + array([[0.0, 1.0, 2.0, 3.0], + [4.0, 5.0, 6.0, 7.0], + [8.0, 9.0, 10.0, 11.0]], dtype=float64) + + any of the flattened array: + True + + any of a along 0th axis: + array([True, True, True, True], dtype=bool) + + any of a along 1st axis: + array([True, True, True], dtype=bool) + + + + +argmax +------ + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmax.html + +See `numpy.max <#max>`__. + +argmin +------ + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmin.html + +See `numpy.max <#max>`__. + +argsort +------- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.argsort.html + +Similarly to `sort <#sort>`__, ``argsort`` takes a positional, and a +keyword argument, and returns an unsigned short index array of type +``ndarray`` with the same dimensions as the input, or, if ``axis=None``, +as a row vector with length equal to the number of elements in the input +(i.e., the flattened array). The indices in the output sort the input in +ascending order. The routine in ``argsort`` is the same as in ``sort``, +therefore, the comments on computational expenses (time and RAM) also +apply. In particular, since no copy of the original data is required, +virtually no RAM beyond the output array is used. + +Since the underlying container of the output array is of type +``uint16_t``, neither of the output dimensions should be larger than +65535. If that happens to be the case, the function will bail out with a +``ValueError``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.float) + print('\na:\n', a) + b = np.argsort(a, axis=0) + print('\na sorted along vertical axis:\n', b) + + c = np.argsort(a, axis=1) + print('\na sorted along horizontal axis:\n', c) + + c = np.argsort(a, axis=None) + print('\nflattened a sorted:\n', c) + +.. parsed-literal:: + + + a: + array([[1.0, 12.0, 3.0, 0.0], + [5.0, 3.0, 4.0, 1.0], + [9.0, 11.0, 1.0, 8.0], + [7.0, 10.0, 0.0, 1.0]], dtype=float64) + + a sorted along vertical axis: + array([[0, 1, 3, 0], + [1, 3, 2, 1], + [3, 2, 0, 3], + [2, 0, 1, 2]], dtype=uint16) + + a sorted along horizontal axis: + array([[3, 0, 2, 1], + [3, 1, 2, 0], + [2, 3, 0, 1], + [2, 3, 0, 1]], dtype=uint16) + + Traceback (most recent call last): + File "/dev/shm/micropython.py", line 12, in + NotImplementedError: argsort is not implemented for flattened arrays + + + +Since during the sorting, only the indices are shuffled, ``argsort`` +does not modify the input array, as one can verify this by the following +example: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([0, 5, 1, 3, 2, 4], dtype=np.uint8) + print('\na:\n', a) + b = np.argsort(a, axis=0) + print('\nsorting indices:\n', b) + print('\nthe original array:\n', a) + +.. parsed-literal:: + + + a: + array([0, 5, 1, 3, 2, 4], dtype=uint8) + + sorting indices: + array([0, 2, 4, 3, 5, 1], dtype=uint16) + + the original array: + array([0, 5, 1, 3, 2, 4], dtype=uint8) + + + + +asarray +------- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.asarray.html + +The function takes a single positional argument, and an optional keyword +argument, ``dtype``, with a default value of ``None``. + +If the positional argument is an ``ndarray``, and its ``dtypes`` is +identical to the value of the keyword argument, or if the keyword +argument is ``None``, then the positional argument is simply returned. +If the original ``dtype``, and the value of the keyword argument are +different, then a copy is returned, with appropriate ``dtype`` +conversion. + +If the positional argument is an iterable, then the function is simply +an alias for ``array``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(9), dtype=np.uint8) + b = np.asarray(a) + c = np.asarray(a, dtype=np.int8) + print('a:{}'.format(a)) + print('b:{}'.format(b)) + print('a == b: {}'.format(a is b)) + + print('\nc:{}'.format(c)) + print('a == c: {}'.format(a is c)) + +.. parsed-literal:: + + a:array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8) + b:array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8) + a == b: True + + c:array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=int8) + a == c: False + + + + +clip +---- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.clip.html + +Clips an array, i.e., values that are outside of an interval are clipped +to the interval edges. The function is equivalent to +``maximum(a_min, minimum(a, a_max))`` broadcasting takes place exactly +as in `minimum <#minimum>`__. If the arrays are of different ``dtype``, +the output is upcast as in `Binary operators <#Binary-operators>`__. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(9), dtype=np.uint8) + print('a:\t\t', a) + print('clipped:\t', np.clip(a, 3, 7)) + + b = 3 * np.ones(len(a), dtype=np.float) + print('\na:\t\t', a) + print('b:\t\t', b) + print('clipped:\t', np.clip(a, b, 7)) + +.. parsed-literal:: + + a: array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8) + clipped: array([3, 3, 3, 3, 4, 5, 6, 7, 7], dtype=uint8) + + a: array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8) + b: array([3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0], dtype=float64) + clipped: array([3.0, 3.0, 3.0, 3.0, 4.0, 5.0, 6.0, 7.0, 7.0], dtype=float64) + + + + +compress +-------- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.compress.html + +The function returns selected slices of an array along given axis. If +the axis keyword is ``None``, the flattened array is used. + +If the firmware was compiled with complex support, the function can +accept complex arguments. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(6)).reshape((2, 3)) + + print('a:\n', a) + print('\ncompress(a):\n', np.compress([0, 1], a, axis=0)) + +.. parsed-literal:: + + a: + array([[0.0, 1.0, 2.0], + [3.0, 4.0, 5.0]], dtype=float64) + + compress(a): + array([[3.0, 4.0, 5.0]], dtype=float64) + + + + +conjugate +--------- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.conjugate.html + +If the firmware was compiled with complex support, the function +calculates the complex conjugate of the input array. If the input array +is of real ``dtype``, then the output is simply a copy, preserving the +``dtype``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3, 4], dtype=np.uint8) + b = np.array([1+1j, 2-2j, 3+3j, 4-4j], dtype=np.complex) + + print('a:\t\t', a) + print('conjugate(a):\t', np.conjugate(a)) + print() + print('b:\t\t', b) + print('conjugate(b):\t', np.conjugate(b)) + +.. parsed-literal:: + + a: array([1, 2, 3, 4], dtype=uint8) + conjugate(a): array([1, 2, 3, 4], dtype=uint8) + + b: array([1.0+1.0j, 2.0-2.0j, 3.0+3.0j, 4.0-4.0j], dtype=complex) + conjugate(b): array([1.0-1.0j, 2.0+2.0j, 3.0-3.0j, 4.0+4.0j], dtype=complex) + + + + +convolve +-------- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.convolve.html + +Returns the discrete, linear convolution of two one-dimensional arrays. + +Only the ``full`` mode is supported, and the ``mode`` named parameter is +not accepted. Note that all other modes can be had by slicing a ``full`` +result. + +If the firmware was compiled with complex support, the function can +accept complex arrays. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + x = np.array((1, 2, 3)) + y = np.array((1, 10, 100, 1000)) + + print(np.convolve(x, y)) + +.. parsed-literal:: + + array([1.0, 12.0, 123.0, 1230.0, 2300.0, 3000.0], dtype=float64) + + + + +delete +------ + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.delete.html + +The function returns a new array with sub-arrays along an axis deleted. +It takes two positional arguments, the array, and the indices, which +will be removed, as well as the ``axis`` keyword argument with a default +value of ``None``. If the ``axis`` is ``None``, the will be flattened +first. + +The second positional argument can be a scalar, or any ``micropython`` +iterable. Since ``range`` can also be passed in place of the indices, +slicing can be emulated. If the indices are negative, the elements are +counted from the end of the axis. + +Note that the function creates a copy of the indices first, because it +is not guaranteed that the indices are ordered. Keep this in mind, when +working with large arrays. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(25), dtype=np.uint8).reshape((5,5)) + print('a:\n', a) + print('\naxis = 0\n', np.delete(a, 2, axis=0)) + print('\naxis = 1\n', np.delete(a, -2, axis=1)) + print('\naxis = None\n', np.delete(a, [0, 1, 2, 22])) + +.. parsed-literal:: + + a: + array([[0, 1, 2, 3, 4], + [5, 6, 7, 8, 9], + [10, 11, 12, 13, 14], + [15, 16, 17, 18, 19], + [20, 21, 22, 23, 24]], dtype=uint8) + + axis = 0 + array([[0, 1, 2, 3, 4], + [5, 6, 7, 8, 9], + [15, 16, 17, 18, 19], + [20, 21, 22, 23, 24]], dtype=uint8) + + axis = 1 + array([[0, 1, 2, 4], + [5, 6, 7, 9], + [10, 11, 12, 14], + [15, 16, 17, 19], + [20, 21, 22, 24]], dtype=uint8) + + axis = None + array([3, 4, 5, ..., 21, 23, 24], dtype=uint8) + + + + +diff +---- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.diff.html + +The ``diff`` function returns the numerical derivative of the forward +scheme, or more accurately, the differences of an ``ndarray`` along a +given axis. The order of derivative can be stipulated with the ``n`` +keyword argument, which should be between 0, and 9. Default is 1. If +higher order derivatives are required, they can be gotten by repeated +calls to the function. The ``axis`` keyword argument should be -1 (last +axis, in ``ulab`` equivalent to the second axis, and this also happens +to be the default value), 0, or 1. + +Beyond the output array, the function requires only a couple of bytes of +extra RAM for the differentiation stencil. (The stencil is an ``int8`` +array, one byte longer than ``n``. This also explains, why the highest +order is 9: the coefficients of a ninth-order stencil all fit in signed +bytes, while 10 would require ``int16``.) Note that as usual in +numerical differentiation (and also in ``numpy``), the length of the +respective axis will be reduced by ``n`` after the operation. If ``n`` +is larger than, or equal to the length of the axis, an empty array will +be returned. + +**WARNING**: the ``diff`` function does not implement the ``prepend`` +and ``append`` keywords that can be found in ``numpy``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(9), dtype=np.uint8) + a[3] = 10 + print('a:\n', a) + + print('\nfirst derivative:\n', np.diff(a, n=1)) + print('\nsecond derivative:\n', np.diff(a, n=2)) + + c = np.array([[1, 2, 3, 4], [4, 3, 2, 1], [1, 4, 9, 16], [0, 0, 0, 0]]) + print('\nc:\n', c) + print('\nfirst derivative, first axis:\n', np.diff(c, axis=0)) + print('\nfirst derivative, second axis:\n', np.diff(c, axis=1)) + +.. parsed-literal:: + + a: + array([0, 1, 2, 10, 4, 5, 6, 7, 8], dtype=uint8) + + first derivative: + array([1, 1, 8, 250, 1, 1, 1, 1], dtype=uint8) + + second derivative: + array([0, 249, 14, 249, 0, 0, 0], dtype=uint8) + + c: + array([[1.0, 2.0, 3.0, 4.0], + [4.0, 3.0, 2.0, 1.0], + [1.0, 4.0, 9.0, 16.0], + [0.0, 0.0, 0.0, 0.0]], dtype=float64) + + first derivative, first axis: + array([[3.0, 1.0, -1.0, -3.0], + [-3.0, 1.0, 7.0, 15.0], + [-1.0, -4.0, -9.0, -16.0]], dtype=float64) + + first derivative, second axis: + array([[1.0, 1.0, 1.0], + [-1.0, -1.0, -1.0], + [3.0, 5.0, 7.0], + [0.0, 0.0, 0.0]], dtype=float64) + + + + +dot +--- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.dot.html + +**WARNING:** numpy applies upcasting rules for the multiplication of +matrices, while ``ulab`` simply returns a float matrix. + +Once you can invert a matrix, you might want to know, whether the +inversion is correct. You can simply take the original matrix and its +inverse, and multiply them by calling the ``dot`` function, which takes +the two matrices as its arguments. If the matrix dimensions do not +match, the function raises a ``ValueError``. The result of the +multiplication is expected to be the unit matrix, which is demonstrated +below. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + m = np.array([[1, 2, 3], [4, 5, 6], [7, 10, 9]], dtype=np.uint8) + n = np.linalg.inv(m) + print("m:\n", m) + print("\nm^-1:\n", n) + # this should be the unit matrix + print("\nm*m^-1:\n", np.dot(m, n)) + +.. parsed-literal:: + + m: + array([[1, 2, 3], + [4, 5, 6], + [7, 10, 9]], dtype=uint8) + + m^-1: + array([[-1.25, 1.0, -0.25], + [0.4999999999999998, -1.0, 0.5], + [0.4166666666666668, 0.3333333333333333, -0.25]], dtype=float64) + + m*m^-1: + array([[1.0, 0.0, 0.0], + [4.440892098500626e-16, 1.0, 0.0], + [8.881784197001252e-16, 0.0, 1.0]], dtype=float64) + + + + +Note that for matrix multiplication you don’t necessarily need square +matrices, it is enough, if their dimensions are compatible (i.e., the +the left-hand-side matrix has as many columns, as does the +right-hand-side matrix rows): + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + m = np.array([[1, 2, 3, 4], [5, 6, 7, 8]], dtype=np.uint8) + n = np.array([[1, 2], [3, 4], [5, 6], [7, 8]], dtype=np.uint8) + print(m) + print(n) + print(np.dot(m, n)) + +.. parsed-literal:: + + array([[1, 2, 3, 4], + [5, 6, 7, 8]], dtype=uint8) + array([[1, 2], + [3, 4], + [5, 6], + [7, 8]], dtype=uint8) + array([[50.0, 60.0], + [114.0, 140.0]], dtype=float64) + + + + +equal +----- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.equal.html + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.not_equal.html + +In ``micropython``, equality of arrays or scalars can be established by +utilising the ``==``, ``!=``, ``<``, ``>``, ``<=``, or ``=>`` binary +operators. In ``circuitpython``, ``==`` and ``!=`` will produce +unexpected results. In order to avoid this discrepancy, and to maintain +compatibility with ``numpy``, ``ulab`` implements the ``equal`` and +``not_equal`` operators that return the same results, irrespective of +the ``python`` implementation. + +These two functions take two ``ndarray``\ s, or scalars as their +arguments. No keyword arguments are implemented. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(9)) + b = np.zeros(9) + + print('a: ', a) + print('b: ', b) + print('\na == b: ', np.equal(a, b)) + print('a != b: ', np.not_equal(a, b)) + + # comparison with scalars + print('a == 2: ', np.equal(a, 2)) + +.. parsed-literal:: + + a: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64) + b: array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], dtype=float64) + + a == b: array([True, False, False, False, False, False, False, False, False], dtype=bool) + a != b: array([False, True, True, True, True, True, True, True, True], dtype=bool) + a == 2: array([False, False, True, False, False, False, False, False, False], dtype=bool) + + + + +flip +---- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.flip.html + +The ``flip`` function takes one positional, an ``ndarray``, and one +keyword argument, ``axis = None``, and reverses the order of elements +along the given axis. If the keyword argument is ``None``, the matrix’ +entries are flipped along all axes. ``flip`` returns a new copy of the +array. + +If the firmware was compiled with complex support, the function can +accept complex arrays. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3, 4, 5]) + print("a: \t", a) + print("a flipped:\t", np.flip(a)) + + a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.uint8) + print("\na flipped horizontally\n", np.flip(a, axis=1)) + print("\na flipped vertically\n", np.flip(a, axis=0)) + print("\na flipped horizontally+vertically\n", np.flip(a)) + +.. parsed-literal:: + + a: array([1.0, 2.0, 3.0, 4.0, 5.0], dtype=float64) + a flipped: array([5.0, 4.0, 3.0, 2.0, 1.0], dtype=float64) + + a flipped horizontally + array([[3, 2, 1], + [6, 5, 4], + [9, 8, 7]], dtype=uint8) + + a flipped vertically + array([[7, 8, 9], + [4, 5, 6], + [1, 2, 3]], dtype=uint8) + + a flipped horizontally+vertically + array([9, 8, 7, 6, 5, 4, 3, 2, 1], dtype=uint8) + + + + +imag +---- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.imag.html + +The ``imag`` function returns the imaginary part of an array, or scalar. +It cannot accept a generic iterable as its argument. The function is +defined only, if the firmware was compiled with complex support. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3], dtype=np.uint16) + print("a:\t\t", a) + print("imag(a):\t", np.imag(a)) + + b = np.array([1, 2+1j, 3-1j], dtype=np.complex) + print("\nb:\t\t", b) + print("imag(b):\t", np.imag(b)) + +.. parsed-literal:: + + a: array([1, 2, 3], dtype=uint16) + imag(a): array([0, 0, 0], dtype=uint16) + + b: array([1.0+0.0j, 2.0+1.0j, 3.0-1.0j], dtype=complex) + imag(b): array([0.0, 1.0, -1.0], dtype=float64) + + + + +interp +------ + +``numpy``: https://docs.scipy.org/doc/numpy/numpy.interp + +The ``interp`` function returns the linearly interpolated values of a +one-dimensional numerical array. It requires three positional +arguments,\ ``x``, at which the interpolated values are evaluated, +``xp``, the array of the independent data variable, and ``fp``, the +array of the dependent values of the data. ``xp`` must be a +monotonically increasing sequence of numbers. + +Two keyword arguments, ``left``, and ``right`` can also be supplied; +these determine the return values, if ``x < xp[0]``, and ``x > xp[-1]``, +respectively. If these arguments are not supplied, ``left``, and +``right`` default to ``fp[0]``, and ``fp[-1]``, respectively. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + x = np.array([1, 2, 3, 4, 5]) - 0.2 + xp = np.array([1, 2, 3, 4]) + fp = np.array([1, 2, 3, 5]) + + print(x) + print(np.interp(x, xp, fp)) + print(np.interp(x, xp, fp, left=0.0)) + print(np.interp(x, xp, fp, right=10.0)) + +.. parsed-literal:: + + array([0.8, 1.8, 2.8, 3.8, 4.8], dtype=float64) + array([1.0, 1.8, 2.8, 4.6, 5.0], dtype=float64) + array([0.0, 1.8, 2.8, 4.6, 5.0], dtype=float64) + array([1.0, 1.8, 2.8, 4.6, 10.0], dtype=float64) + + + + +isfinite +-------- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.isfinite.html + +Returns a Boolean array of the same shape as the input, or a +``True/False``, if the input is a scalar. In the return value, all +elements are ``True`` at positions, where the input value was finite. +Integer types are automatically finite, therefore, if the input is of +integer type, the output will be the ``True`` tensor. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + print('isfinite(0): ', np.isfinite(0)) + + a = np.array([1, 2, np.nan]) + print('\n' + '='*20) + print('a:\n', a) + print('\nisfinite(a):\n', np.isfinite(a)) + + b = np.array([1, 2, np.inf]) + print('\n' + '='*20) + print('b:\n', b) + print('\nisfinite(b):\n', np.isfinite(b)) + + c = np.array([1, 2, 3], dtype=np.uint16) + print('\n' + '='*20) + print('c:\n', c) + print('\nisfinite(c):\n', np.isfinite(c)) + +.. parsed-literal:: + + isfinite(0): True + + ==================== + a: + array([1.0, 2.0, nan], dtype=float64) + + isfinite(a): + array([True, True, False], dtype=bool) + + ==================== + b: + array([1.0, 2.0, inf], dtype=float64) + + isfinite(b): + array([True, True, False], dtype=bool) + + ==================== + c: + array([1, 2, 3], dtype=uint16) + + isfinite(c): + array([True, True, True], dtype=bool) + + + + +isinf +----- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.isinf.html + +Similar to `isfinite <#isfinite>`__, but the output is ``True`` at +positions, where the input is infinite. Integer types return the +``False`` tensor. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + print('isinf(0): ', np.isinf(0)) + + a = np.array([1, 2, np.nan]) + print('\n' + '='*20) + print('a:\n', a) + print('\nisinf(a):\n', np.isinf(a)) + + b = np.array([1, 2, np.inf]) + print('\n' + '='*20) + print('b:\n', b) + print('\nisinf(b):\n', np.isinf(b)) + + c = np.array([1, 2, 3], dtype=np.uint16) + print('\n' + '='*20) + print('c:\n', c) + print('\nisinf(c):\n', np.isinf(c)) + +.. parsed-literal:: + + isinf(0): False + + ==================== + a: + array([1.0, 2.0, nan], dtype=float64) + + isinf(a): + array([False, False, False], dtype=bool) + + ==================== + b: + array([1.0, 2.0, inf], dtype=float64) + + isinf(b): + array([False, False, True], dtype=bool) + + ==================== + c: + array([1, 2, 3], dtype=uint16) + + isinf(c): + array([False, False, False], dtype=bool) + + + + +load +---- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.load.html + +The function reads data from a file in ``numpy``\ ’s +`platform-independent +format `__, +and returns the generated array. If the endianness of the data in the +file and the microcontroller differ, the bytes are automatically +swapped. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.load('a.npy') + print(a) + +.. parsed-literal:: + + array([[0.0, 1.0, 2.0, 3.0, 4.0], + [5.0, 6.0, 7.0, 8.0, 9.0], + [10.0, 11.0, 12.0, 13.0, 14.0], + [15.0, 16.0, 17.0, 18.0, 19.0], + [20.0, 21.0, 22.0, 23.0, 24.0]], dtype=float64) + + + + +loadtxt +------- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.loadtxt.html + +The function reads data from a text file, and returns the generated +array. It takes a file name as the single positional argument, and the +following keyword arguments: + +1. ``comments='#'`` +2. ``dtype=float`` +3. ``delimiter=','`` +4. ``max_rows`` (with a default of all rows) +5. ``skip_rows=0`` +6. ``usecols`` (with a default of all columns) + +If ``dtype`` is supplied and is not ``float``, the data entries will be +converted to the appropriate integer type by rounding the values. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + print('read all data') + print(np.loadtxt('loadtxt.dat')) + + print('\nread maximum 5 rows (first row is a comment line)') + print(np.loadtxt('loadtxt.dat', max_rows=5)) + + print('\nread maximum 5 rows, convert dtype (first row is a comment line)') + print(np.loadtxt('loadtxt.dat', max_rows=5, dtype=np.uint8)) + + print('\nskip the first 3 rows, convert dtype (first row is a comment line)') + print(np.loadtxt('loadtxt.dat', skiprows=3, dtype=np.uint8)) + +.. parsed-literal:: + + read all data + array([[0.0, 1.0, 2.0, 3.0], + [4.0, 5.0, 6.0, 7.0], + [8.0, 9.0, 10.0, 11.0], + [12.0, 13.0, 14.0, 15.0], + [16.0, 17.0, 18.0, 19.0], + [20.0, 21.0, 22.0, 23.0], + [24.0, 25.0, 26.0, 27.0], + [28.00000000000001, 29.0, 30.0, 31.0], + [32.0, 33.0, 34.00000000000001, 35.0]], dtype=float64) + + read maximum 5 rows (first row is a comment line) + array([[0.0, 1.0, 2.0, 3.0], + [4.0, 5.0, 6.0, 7.0], + [8.0, 9.0, 10.0, 11.0], + [12.0, 13.0, 14.0, 15.0]], dtype=float64) + + read maximum 5 rows, convert dtype (first row is a comment line) + array([[0, 1, 2, 3], + [4, 5, 6, 7], + [8, 9, 10, 11], + [12, 13, 14, 15]], dtype=uint8) + + skip the first 3 rows, convert dtype (first row is a comment line) + array([[8, 9, 10, 11], + [12, 13, 14, 15], + [16, 17, 18, 19], + [20, 21, 22, 23], + [24, 25, 26, 27], + [28, 29, 30, 31], + [32, 33, 34, 35]], dtype=uint8) + + + + +mean +---- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.mean.html + +If the axis keyword is not specified, it assumes the default value of +``None``, and returns the result of the computation for the flattened +array. Otherwise, the calculation is along the given axis. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + print('a: \n', a) + print('mean, flat: ', np.mean(a)) + print('mean, horizontal: ', np.mean(a, axis=1)) + print('mean, vertical: ', np.mean(a, axis=0)) + +.. parsed-literal:: + + a: + array([[1.0, 2.0, 3.0], + [4.0, 5.0, 6.0], + [7.0, 8.0, 9.0]], dtype=float64) + mean, flat: 5.0 + mean, horizontal: array([2.0, 5.0, 8.0], dtype=float64) + mean, vertical: array([4.0, 5.0, 6.0], dtype=float64) + + + + +max +--- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.max.html + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmax.html + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.min.html + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmin.html + +**WARNING:** Difference to ``numpy``: the ``out`` keyword argument is +not implemented. + +These functions follow the same pattern, and work with generic +iterables, and ``ndarray``\ s. ``min``, and ``max`` return the minimum +or maximum of a sequence. If the input array is two-dimensional, the +``axis`` keyword argument can be supplied, in which case the +minimum/maximum along the given axis will be returned. If ``axis=None`` +(this is also the default value), the minimum/maximum of the flattened +array will be determined. + +``argmin/argmax`` return the position (index) of the minimum/maximum in +the sequence. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 0, 1, 10]) + print('a:', a) + print('min of a:', np.min(a)) + print('argmin of a:', np.argmin(a)) + + b = np.array([[1, 2, 0], [1, 10, -1]]) + print('\nb:\n', b) + print('min of b (flattened):', np.min(b)) + print('min of b (axis=0):', np.min(b, axis=0)) + print('min of b (axis=1):', np.min(b, axis=1)) + +.. parsed-literal:: + + a: array([1.0, 2.0, 0.0, 1.0, 10.0], dtype=float64) + min of a: 0.0 + argmin of a: 2 + + b: + array([[1.0, 2.0, 0.0], + [1.0, 10.0, -1.0]], dtype=float64) + min of b (flattened): -1.0 + min of b (axis=0): array([1.0, 2.0, -1.0], dtype=float64) + min of b (axis=1): array([0.0, -1.0], dtype=float64) + + + + +median +------ + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.median.html + +The function computes the median along the specified axis, and returns +the median of the array elements. If the ``axis`` keyword argument is +``None``, the arrays is flattened first. The ``dtype`` of the results is +always float. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(12), dtype=np.int8).reshape((3, 4)) + print('a:\n', a) + print('\nmedian of the flattened array: ', np.median(a)) + print('\nmedian along the vertical axis: ', np.median(a, axis=0)) + print('\nmedian along the horizontal axis: ', np.median(a, axis=1)) + +.. parsed-literal:: + + a: + array([[0, 1, 2, 3], + [4, 5, 6, 7], + [8, 9, 10, 11]], dtype=int8) + + median of the flattened array: 5.5 + + median along the vertical axis: array([4.0, 5.0, 6.0, 7.0], dtype=float64) + + median along the horizontal axis: array([1.5, 5.5, 9.5], dtype=float64) + + + + +min +--- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.min.html + +See `numpy.max <#max>`__. + +minimum +------- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.minimum.html + +See `numpy.maximum <#maximum>`__ + +maximum +------- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.maximum.html + +Returns the maximum of two arrays, or two scalars, or an array, and a +scalar. If the arrays are of different ``dtype``, the output is upcast +as in `Binary operators <#Binary-operators>`__. If both inputs are +scalars, a scalar is returned. Only positional arguments are +implemented. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3, 4, 5], dtype=np.uint8) + b = np.array([5, 4, 3, 2, 1], dtype=np.float) + print('minimum of a, and b:') + print(np.minimum(a, b)) + + print('\nmaximum of a, and b:') + print(np.maximum(a, b)) + + print('\nmaximum of 1, and 5.5:') + print(np.maximum(1, 5.5)) + +.. parsed-literal:: + + minimum of a, and b: + array([1.0, 2.0, 3.0, 2.0, 1.0], dtype=float64) + + maximum of a, and b: + array([5.0, 4.0, 3.0, 4.0, 5.0], dtype=float64) + + maximum of 1, and 5.5: + 5.5 + + + + +nonzero +------- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.nonzero.html + +``nonzero`` returns the indices of the elements of an array that are not +zero. If the number of dimensions of the array is larger than one, a +tuple of arrays is returned, one for each dimension, containing the +indices of the non-zero elements in that dimension. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(9)) - 5 + print('a:\n', a) + print(np.nonzero(a)) + + a = a.reshape((3,3)) + print('\na:\n', a) + print(np.nonzero(a)) + +.. parsed-literal:: + + a: + array([-5.0, -4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0], dtype=float64) + (array([0, 1, 2, 3, 4, 6, 7, 8], dtype=uint16),) + + a: + array([[-5.0, -4.0, -3.0], + [-2.0, -1.0, 0.0], + [1.0, 2.0, 3.0]], dtype=float64) + (array([0, 0, 0, 1, 1, 2, 2, 2], dtype=uint16), array([0, 1, 2, 0, 1, 0, 1, 2], dtype=uint16)) + + + + +not_equal +--------- + +See `numpy.equal <#equal>`__. + +polyfit +------- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.polyfit.html + +``polyfit`` takes two, or three arguments. The last one is the degree of +the polynomial that will be fitted, the last but one is an array or +iterable with the ``y`` (dependent) values, and the first one, an array +or iterable with the ``x`` (independent) values, can be dropped. If that +is the case, ``x`` will be generated in the function as +``range(len(y))``. + +If the lengths of ``x``, and ``y`` are not the same, the function raises +a ``ValueError``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + x = np.array([0, 1, 2, 3, 4, 5, 6]) + y = np.array([9, 4, 1, 0, 1, 4, 9]) + print('independent values:\t', x) + print('dependent values:\t', y) + print('fitted values:\t\t', np.polyfit(x, y, 2)) + + # the same with missing x + print('\ndependent values:\t', y) + print('fitted values:\t\t', np.polyfit(y, 2)) + +.. parsed-literal:: + + independent values: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0], dtype=float64) + dependent values: array([9.0, 4.0, 1.0, 0.0, 1.0, 4.0, 9.0], dtype=float64) + fitted values: array([1.0, -6.0, 9.000000000000004], dtype=float64) + + dependent values: array([9.0, 4.0, 1.0, 0.0, 1.0, 4.0, 9.0], dtype=float64) + fitted values: array([1.0, -6.0, 9.000000000000004], dtype=float64) + + + + +Execution time +~~~~~~~~~~~~~~ + +``polyfit`` is based on the inversion of a matrix (there is more on the +background in https://en.wikipedia.org/wiki/Polynomial_regression), and +it requires the intermediate storage of ``2*N*(deg+1)`` floats, where +``N`` is the number of entries in the input array, and ``deg`` is the +fit’s degree. The additional computation costs of the matrix inversion +discussed in `linalg.inv <#inv>`__ also apply. The example from above +needs around 150 microseconds to return: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + @timeit + def time_polyfit(x, y, n): + return np.polyfit(x, y, n) + + x = np.array([0, 1, 2, 3, 4, 5, 6]) + y = np.array([9, 4, 1, 0, 1, 4, 9]) + + time_polyfit(x, y, 2) + +.. parsed-literal:: + + execution time: 153 us + + +polyval +------- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.polyval.html + +``polyval`` takes two arguments, both arrays or generic ``micropython`` +iterables returning scalars. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + p = [1, 1, 1, 0] + x = [0, 1, 2, 3, 4] + print('coefficients: ', p) + print('independent values: ', x) + print('\nvalues of p(x): ', np.polyval(p, x)) + + # the same works with one-dimensional ndarrays + a = np.array(x) + print('\nndarray (a): ', a) + print('value of p(a): ', np.polyval(p, a)) + +.. parsed-literal:: + + coefficients: [1, 1, 1, 0] + independent values: [0, 1, 2, 3, 4] + + values of p(x): array([0.0, 3.0, 14.0, 39.0, 84.0], dtype=float64) + + ndarray (a): array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float64) + value of p(a): array([0.0, 3.0, 14.0, 39.0, 84.0], dtype=float64) + + + + +real +---- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.real.html + +The ``real`` function returns the real part of an array, or scalar. It +cannot accept a generic iterable as its argument. The function is +defined only, if the firmware was compiled with complex support. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3], dtype=np.uint16) + print("a:\t\t", a) + print("real(a):\t", np.real(a)) + + b = np.array([1, 2+1j, 3-1j], dtype=np.complex) + print("\nb:\t\t", b) + print("real(b):\t", np.real(b)) + +.. parsed-literal:: + + a: array([1, 2, 3], dtype=uint16) + real(a): array([1, 2, 3], dtype=uint16) + + b: array([1.0+0.0j, 2.0+1.0j, 3.0-1.0j], dtype=complex) + real(b): array([1.0, 2.0, 3.0], dtype=float64) + + + + +roll +---- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html + +The roll function shifts the content of a vector by the positions given +as the second argument. If the ``axis`` keyword is supplied, the shift +is applied to the given axis. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3, 4, 5, 6, 7, 8]) + print("a:\t\t\t", a) + + a = np.roll(a, 2) + print("a rolled to the left:\t", a) + + # this should be the original vector + a = np.roll(a, -2) + print("a rolled to the right:\t", a) + +.. parsed-literal:: + + a: array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64) + a rolled to the left: array([7.0, 8.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0], dtype=float64) + a rolled to the right: array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64) + + + + +Rolling works with matrices, too. If the ``axis`` keyword is 0, the +matrix is rolled along its vertical axis, otherwise, horizontally. + +Horizontal rolls are faster, because they require fewer steps, and +larger memory chunks are copied, however, they also require more RAM: +basically the whole row must be stored internally. Most expensive are +the ``None`` keyword values, because with ``axis = None``, the array is +flattened first, hence the row’s length is the size of the whole matrix. + +Vertical rolls require two internal copies of single columns. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(12)).reshape((3, 4)) + print("a:\n", a) + a = np.roll(a, 2, axis=0) + print("\na rolled up:\n", a) + + a = np.array(range(12)).reshape((3, 4)) + print("a:\n", a) + a = np.roll(a, -1, axis=1) + print("\na rolled to the left:\n", a) + + a = np.array(range(12)).reshape((3, 4)) + print("a:\n", a) + a = np.roll(a, 1, axis=None) + print("\na rolled with None:\n", a) + +.. parsed-literal:: + + a: + array([[0.0, 1.0, 2.0, 3.0], + [4.0, 5.0, 6.0, 7.0], + [8.0, 9.0, 10.0, 11.0]], dtype=float64) + + a rolled up: + array([[4.0, 5.0, 6.0, 7.0], + [8.0, 9.0, 10.0, 11.0], + [0.0, 1.0, 2.0, 3.0]], dtype=float64) + a: + array([[0.0, 1.0, 2.0, 3.0], + [4.0, 5.0, 6.0, 7.0], + [8.0, 9.0, 10.0, 11.0]], dtype=float64) + + a rolled to the left: + array([[1.0, 2.0, 3.0, 0.0], + [5.0, 6.0, 7.0, 4.0], + [9.0, 10.0, 11.0, 8.0]], dtype=float64) + a: + array([[0.0, 1.0, 2.0, 3.0], + [4.0, 5.0, 6.0, 7.0], + [8.0, 9.0, 10.0, 11.0]], dtype=float64) + + a rolled with None: + array([[11.0, 0.0, 1.0, 2.0], + [3.0, 4.0, 5.0, 6.0], + [7.0, 8.0, 9.0, 10.0]], dtype=float64) + + + + +save +---- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.save.html + +With the help of this function, numerical array can be saved in +``numpy``\ ’s `platform-independent +format `__. + +The function takes two positional arguments, the name of the output +file, and the array. + +.. code:: + + # code to be run in CPython + + a = np.array(range(25)).reshape((5, 5)) + np.save('a.npy', a) +savetxt +------- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.savetxt.html + +With the help of this function, numerical array can be saved in a text +file. The function takes two positional arguments, the name of the +output file, and the array, and also implements the ``comments='#'`` +``delimiter=' '``, the ``header=''``, and ``footer=''`` keyword +arguments. The input is treated as of type ``float``, i.e., the output +is always in the floating point representation. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(12), dtype=np.uint8).reshape((3, 4)) + np.savetxt('savetxt.dat', a) + + with open('savetxt.dat', 'r') as fin: + print(fin.read()) + + np.savetxt('savetxt.dat', a, + comments='!', + delimiter=';', + header='col1;col2;col3;col4', + footer='saved data') + + with open('savetxt.dat', 'r') as fin: + print(fin.read()) + +.. parsed-literal:: + + 0.000000000000000 1.000000000000000 2.000000000000000 3.000000000000000 + 4.000000000000000 5.000000000000000 6.000000000000000 7.000000000000000 + 8.000000000000000 9.000000000000000 10.000000000000000 11.000000000000000 + + !col1;col2;col3;col4 + 0.000000000000000;1.000000000000000;2.000000000000000;3.000000000000000 + 4.000000000000000;5.000000000000000;6.000000000000000;7.000000000000000 + 8.000000000000000;9.000000000000000;10.000000000000000;11.000000000000000 + !saved data + + + + + +size +---- + +The function takes a single positional argument, and an optional keyword +argument, ``axis``, with a default value of ``None``, and returns the +size of an array along that axis. If ``axis`` is ``None``, the total +length of the array (the product of the elements of its shape) is +returned. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.ones((2, 3)) + + print(a) + print('size(a, axis=0): ', np.size(a, axis=0)) + print('size(a, axis=1): ', np.size(a, axis=1)) + print('size(a, axis=None): ', np.size(a, axis=None)) + +.. parsed-literal:: + + array([[1.0, 1.0, 1.0], + [1.0, 1.0, 1.0]], dtype=float64) + size(a, axis=0): 2 + size(a, axis=1): 3 + size(a, axis=None): 6 + + + + +sort +---- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.sort.html + +The sort function takes an ndarray, and sorts its elements in ascending +order along the specified axis using a heap sort algorithm. As opposed +to the ``.sort()`` method discussed earlier, this function creates a +copy of its input before sorting, and at the end, returns this copy. +Sorting takes place in place, without auxiliary storage. The ``axis`` +keyword argument takes on the possible values of -1 (the last axis, in +``ulab`` equivalent to the second axis, and this also happens to be the +default value), 0, 1, or ``None``. The first three cases are identical +to those in `diff <#diff>`__, while the last one flattens the array +before sorting. + +If descending order is required, the result can simply be ``flip``\ ped, +see `flip <#flip>`__. + +**WARNING:** ``numpy`` defines the ``kind``, and ``order`` keyword +arguments that are not implemented here. The function in ``ulab`` always +uses heap sort, and since ``ulab`` does not have the concept of data +fields, the ``order`` keyword argument would have no meaning. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.float) + print('\na:\n', a) + b = np.sort(a, axis=0) + print('\na sorted along vertical axis:\n', b) + + c = np.sort(a, axis=1) + print('\na sorted along horizontal axis:\n', c) + + c = np.sort(a, axis=None) + print('\nflattened a sorted:\n', c) + +.. parsed-literal:: + + + a: + array([[1.0, 12.0, 3.0, 0.0], + [5.0, 3.0, 4.0, 1.0], + [9.0, 11.0, 1.0, 8.0], + [7.0, 10.0, 0.0, 1.0]], dtype=float64) + + a sorted along vertical axis: + array([[1.0, 3.0, 0.0, 0.0], + [5.0, 10.0, 1.0, 1.0], + [7.0, 11.0, 3.0, 1.0], + [9.0, 12.0, 4.0, 8.0]], dtype=float64) + + a sorted along horizontal axis: + array([[0.0, 1.0, 3.0, 12.0], + [1.0, 3.0, 4.0, 5.0], + [1.0, 8.0, 9.0, 11.0], + [0.0, 1.0, 7.0, 10.0]], dtype=float64) + + flattened a sorted: + array([0.0, 0.0, 1.0, ..., 10.0, 11.0, 12.0], dtype=float64) + + + + +Heap sort requires :math:`\sim N\log N` operations, and notably, the +worst case costs only 20% more time than the average. In order to get an +order-of-magnitude estimate, we will take the sine of 1000 uniformly +spaced numbers between 0, and two pi, and sort them: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + @timeit + def sort_time(array): + return nup.sort(array) + + b = np.sin(np.linspace(0, 6.28, num=1000)) + print('b: ', b) + sort_time(b) + print('\nb sorted:\n', b) +sort_complex +------------ + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.sort_complex.html + +If the firmware was compiled with complex support, the functions sorts +the input array first according to its real part, and then the imaginary +part. The input must be a one-dimensional array. The output is always of +``dtype`` complex, even if the input was real integer. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([5, 4, 3, 2, 1], dtype=np.int16) + print('a:\t\t\t', a) + print('sort_complex(a):\t', np.sort_complex(a)) + print() + + b = np.array([5, 4+3j, 4-2j, 0, 1j], dtype=np.complex) + print('b:\t\t\t', b) + print('sort_complex(b):\t', np.sort_complex(b)) + +.. parsed-literal:: + + a: array([5, 4, 3, 2, 1], dtype=int16) + sort_complex(a): array([1.0+0.0j, 2.0+0.0j, 3.0+0.0j, 4.0+0.0j, 5.0+0.0j], dtype=complex) + + b: array([5.0+0.0j, 4.0+3.0j, 4.0-2.0j, 0.0+0.0j, 0.0+1.0j], dtype=complex) + sort_complex(b): array([0.0+0.0j, 0.0+1.0j, 4.0-2.0j, 4.0+3.0j, 5.0+0.0j], dtype=complex) + + + + +std +--- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.std.html + +If the axis keyword is not specified, it assumes the default value of +``None``, and returns the result of the computation for the flattened +array. Otherwise, the calculation is along the given axis. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + print('a: \n', a) + print('sum, flat array: ', np.std(a)) + print('std, vertical: ', np.std(a, axis=0)) + print('std, horizonal: ', np.std(a, axis=1)) + +.. parsed-literal:: + + a: + array([[1.0, 2.0, 3.0], + [4.0, 5.0, 6.0], + [7.0, 8.0, 9.0]], dtype=float64) + sum, flat array: 2.581988897471611 + std, vertical: array([2.449489742783178, 2.449489742783178, 2.449489742783178], dtype=float64) + std, horizonal: array([0.8164965809277261, 0.8164965809277261, 0.8164965809277261], dtype=float64) + + + + +sum +--- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html + +If the axis keyword is not specified, it assumes the default value of +``None``, and returns the result of the computation for the flattened +array. Otherwise, the calculation is along the given axis. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + print('a: \n', a) + + print('sum, flat array: ', np.sum(a)) + print('sum, horizontal: ', np.sum(a, axis=1)) + print('std, vertical: ', np.sum(a, axis=0)) + +.. parsed-literal:: + + a: + array([[1.0, 2.0, 3.0], + [4.0, 5.0, 6.0], + [7.0, 8.0, 9.0]], dtype=float64) + sum, flat array: 45.0 + sum, horizontal: array([6.0, 15.0, 24.0], dtype=float64) + std, vertical: array([12.0, 15.0, 18.0], dtype=float64) + + + + +trace +----- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.trace.html + +The ``trace`` function returns the sum of the diagonal elements of a +square matrix. If the input argument is not a square matrix, an +exception will be raised. + +The scalar so returned will inherit the type of the input array, i.e., +integer arrays have integer trace, and floating point arrays a floating +point trace. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([[25, 15, -5], [15, 18, 0], [-5, 0, 11]], dtype=np.int8) + print('a: ', a) + print('\ntrace of a: ', np.trace(a)) + + b = np.array([[25, 15, -5], [15, 18, 0], [-5, 0, 11]], dtype=np.float) + + print('='*20 + '\nb: ', b) + print('\ntrace of b: ', np.trace(b)) + +.. parsed-literal:: + + a: array([[25, 15, -5], + [15, 18, 0], + [-5, 0, 11]], dtype=int8) + + trace of a: 54 + ==================== + b: array([[25.0, 15.0, -5.0], + [15.0, 18.0, 0.0], + [-5.0, 0.0, 11.0]], dtype=float64) + + trace of b: 54.0 + + + + +trapz +----- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.trapz.html + +The function takes one or two one-dimensional ``ndarray``\ s, and +integrates the dependent values (``y``) using the trapezoidal rule. If +the independent variable (``x``) is given, that is taken as the sample +points corresponding to ``y``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + x = np.linspace(0, 9, num=10) + y = x*x + + print('x: ', x) + print('y: ', y) + print('============================') + print('integral of y: ', np.trapz(y)) + print('integral of y at x: ', np.trapz(y, x=x)) + +.. parsed-literal:: + + x: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0], dtype=float64) + y: array([0.0, 1.0, 4.0, 9.0, 16.0, 25.0, 36.0, 49.0, 64.0, 81.0], dtype=float64) + ============================ + integral of y: 244.5 + integral of y at x: 244.5 + + + + +where +----- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.where.html + +The function takes three positional arguments, ``condition``, ``x``, and +``y``, and returns a new ``ndarray``, whose values are taken from either +``x``, or ``y``, depending on the truthness of ``condition``. The three +arguments are broadcast together, and the function raises a +``ValueError`` exception, if broadcasting is not possible. + +The function is implemented for ``ndarray``\ s only: other iterable +types can be passed after casting them to an ``ndarray`` by calling the +``array`` constructor. + +If the ``dtype``\ s of ``x``, and ``y`` differ, the output is upcast as +discussed earlier. + +Note that the ``condition`` is expanded into an Boolean ``ndarray``. +This means that the storage required to hold the condition should be +taken into account, whenever the function is called. + +The following example returns an ``ndarray`` of length 4, with 1 at +positions, where ``condition`` is smaller than 3, and with -1 otherwise. + +.. code:: + + # code to be run in micropython + + + from ulab import numpy as np + + condition = np.array([1, 2, 3, 4], dtype=np.uint8) + print(np.where(condition < 3, 1, -1)) + +.. parsed-literal:: + + array([1, 1, -1, -1], dtype=int16) + + + + +The next snippet shows, how values from two arrays can be fed into the +output: + +.. code:: + + # code to be run in micropython + + + from ulab import numpy as np + + condition = np.array([1, 2, 3, 4], dtype=np.uint8) + x = np.array([11, 22, 33, 44], dtype=np.uint8) + y = np.array([1, 2, 3, 4], dtype=np.uint8) + print(np.where(condition < 3, x, y)) + +.. parsed-literal:: + + array([11, 22, 3, 4], dtype=uint8) + + + diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/numpy-linalg.rst b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/numpy-linalg.rst new file mode 100644 index 00000000..8439f337 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/numpy-linalg.rst @@ -0,0 +1,386 @@ + +numpy.linalg +============ + +Functions in the ``linalg`` module can be called by prepending them by +``numpy.linalg.``. The module defines the following seven functions: + +1. `numpy.linalg.cholesky <#cholesky>`__ +2. `numpy.linalg.det <#det>`__ +3. `numpy.linalg.eig <#eig>`__ +4. `numpy.linalg.inv <#inv>`__ +5. `numpy.linalg.norm <#norm>`__ +6. `numpy.linalg.qr <#qr>`__ + +cholesky +-------- + +``numpy``: +https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.linalg.cholesky.html + +The function of the Cholesky decomposition takes a positive definite, +symmetric square matrix as its single argument, and returns the *square +root matrix* in the lower triangular form. If the input argument does +not fulfill the positivity or symmetry condition, a ``ValueError`` is +raised. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([[25, 15, -5], [15, 18, 0], [-5, 0, 11]]) + print('a: ', a) + print('\n' + '='*20 + '\nCholesky decomposition\n', np.linalg.cholesky(a)) + +.. parsed-literal:: + + a: array([[25.0, 15.0, -5.0], + [15.0, 18.0, 0.0], + [-5.0, 0.0, 11.0]], dtype=float) + + ==================== + Cholesky decomposition + array([[5.0, 0.0, 0.0], + [3.0, 3.0, 0.0], + [-1.0, 1.0, 3.0]], dtype=float) + + + + +det +--- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.det.html + +The ``det`` function takes a square matrix as its single argument, and +calculates the determinant. The calculation is based on successive +elimination of the matrix elements, and the return value is a float, +even if the input array was of integer type. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([[1, 2], [3, 4]], dtype=np.uint8) + print(np.linalg.det(a)) + +.. parsed-literal:: + + -2.0 + + + +Benchmark +~~~~~~~~~ + +Since the routine for calculating the determinant is pretty much the +same as for finding the `inverse of a matrix <#inv>`__, the execution +times are similar: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + @timeit + def matrix_det(m): + return np.linalg.inv(m) + + m = np.array([[1, 2, 3, 4, 5, 6, 7, 8], [0, 5, 6, 4, 5, 6, 4, 5], + [0, 0, 9, 7, 8, 9, 7, 8], [0, 0, 0, 10, 11, 12, 11, 12], + [0, 0, 0, 0, 4, 6, 7, 8], [0, 0, 0, 0, 0, 5, 6, 7], + [0, 0, 0, 0, 0, 0, 7, 6], [0, 0, 0, 0, 0, 0, 0, 2]]) + + matrix_det(m) + +.. parsed-literal:: + + execution time: 294 us + + + +eig +--- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.eig.html + +The ``eig`` function calculates the eigenvalues and the eigenvectors of +a real, symmetric square matrix. If the matrix is not symmetric, a +``ValueError`` will be raised. The function takes a single argument, and +returns a tuple with the eigenvalues, and eigenvectors. With the help of +the eigenvectors, amongst other things, you can implement sophisticated +stabilisation routines for robots. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([[1, 2, 1, 4], [2, 5, 3, 5], [1, 3, 6, 1], [4, 5, 1, 7]], dtype=np.uint8) + x, y = np.linalg.eig(a) + print('eigenvectors of a:\n', y) + print('\neigenvalues of a:\n', x) + +.. parsed-literal:: + + eigenvectors of a: + array([[0.8151560042509081, -0.4499411232970823, -0.1644660242574522, 0.3256141906686505], + [0.2211334179893007, 0.7846992598235538, 0.08372081379922657, 0.5730077734355189], + [-0.1340114162071679, -0.3100776411558949, 0.8742786816656, 0.3486109343758527], + [-0.5183258053659028, -0.292663481927148, -0.4489749870391468, 0.6664142156731531]], dtype=float) + + eigenvalues of a: + array([-1.165288365404889, 0.8029365530314914, 5.585625756072663, 13.77672605630074], dtype=float) + + + + +The same matrix diagonalised with ``numpy`` yields: + +.. code:: + + # code to be run in CPython + + a = array([[1, 2, 1, 4], [2, 5, 3, 5], [1, 3, 6, 1], [4, 5, 1, 7]], dtype=np.uint8) + x, y = eig(a) + print('eigenvectors of a:\n', y) + print('\neigenvalues of a:\n', x) + +.. parsed-literal:: + + eigenvectors of a: + [[ 0.32561419 0.815156 0.44994112 -0.16446602] + [ 0.57300777 0.22113342 -0.78469926 0.08372081] + [ 0.34861093 -0.13401142 0.31007764 0.87427868] + [ 0.66641421 -0.51832581 0.29266348 -0.44897499]] + + eigenvalues of a: + [13.77672606 -1.16528837 0.80293655 5.58562576] + + +When comparing results, we should keep two things in mind: + +1. the eigenvalues and eigenvectors are not necessarily sorted in the + same way +2. an eigenvector can be multiplied by an arbitrary non-zero scalar, and + it is still an eigenvector with the same eigenvalue. This is why all + signs of the eigenvector belonging to 5.58, and 0.80 are flipped in + ``ulab`` with respect to ``numpy``. This difference, however, is of + absolutely no consequence. + +Computation expenses +~~~~~~~~~~~~~~~~~~~~ + +Since the function is based on `Givens +rotations `__ and runs +till convergence is achieved, or till the maximum number of allowed +rotations is exhausted, there is no universal estimate for the time +required to find the eigenvalues. However, an order of magnitude can, at +least, be guessed based on the measurement below: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + @timeit + def matrix_eig(a): + return np.linalg.eig(a) + + a = np.array([[1, 2, 1, 4], [2, 5, 3, 5], [1, 3, 6, 1], [4, 5, 1, 7]], dtype=np.uint8) + + matrix_eig(a) + +.. parsed-literal:: + + execution time: 111 us + + + +inv +--- + +``numpy``: +https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.linalg.inv.html + +A square matrix, provided that it is not singular, can be inverted by +calling the ``inv`` function that takes a single argument. The inversion +is based on successive elimination of elements in the lower left +triangle, and raises a ``ValueError`` exception, if the matrix turns out +to be singular (i.e., one of the diagonal entries is zero). + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + m = np.array([[1, 2, 3, 4], [4, 5, 6, 4], [7, 8.6, 9, 4], [3, 4, 5, 6]]) + + print(np.linalg.inv(m)) + +.. parsed-literal:: + + array([[-2.166666666666667, 1.500000000000001, -0.8333333333333337, 1.0], + [1.666666666666667, -3.333333333333335, 1.666666666666668, -0.0], + [0.1666666666666666, 2.166666666666668, -0.8333333333333337, -1.0], + [-0.1666666666666667, -0.3333333333333333, 0.0, 0.5]], dtype=float64) + + + + +Computation expenses +~~~~~~~~~~~~~~~~~~~~ + +Note that the cost of inverting a matrix is approximately twice as many +floats (RAM), as the number of entries in the original matrix, and +approximately as many operations, as the number of entries. Here are a +couple of numbers: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + @timeit + def invert_matrix(m): + return np.linalg.inv(m) + + m = np.array([[1, 2,], [4, 5]]) + print('2 by 2 matrix:') + invert_matrix(m) + + m = np.array([[1, 2, 3, 4], [4, 5, 6, 4], [7, 8.6, 9, 4], [3, 4, 5, 6]]) + print('\n4 by 4 matrix:') + invert_matrix(m) + + m = np.array([[1, 2, 3, 4, 5, 6, 7, 8], [0, 5, 6, 4, 5, 6, 4, 5], + [0, 0, 9, 7, 8, 9, 7, 8], [0, 0, 0, 10, 11, 12, 11, 12], + [0, 0, 0, 0, 4, 6, 7, 8], [0, 0, 0, 0, 0, 5, 6, 7], + [0, 0, 0, 0, 0, 0, 7, 6], [0, 0, 0, 0, 0, 0, 0, 2]]) + print('\n8 by 8 matrix:') + invert_matrix(m) + +.. parsed-literal:: + + 2 by 2 matrix: + execution time: 65 us + + 4 by 4 matrix: + execution time: 105 us + + 8 by 8 matrix: + execution time: 299 us + + + +The above-mentioned scaling is not obeyed strictly. The reason for the +discrepancy is that the function call is still the same for all three +cases: the input must be inspected, the output array must be created, +and so on. + +norm +---- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html + +The function takes a vector or matrix without options, and returns its +2-norm, i.e., the square root of the sum of the square of the elements. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3, 4, 5]) + b = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + + print('norm of a:', np.linalg.norm(a)) + print('norm of b:', np.linalg.norm(b)) + +.. parsed-literal:: + + norm of a: 7.416198487095663 + norm of b: 16.88194301613414 + + + + +qr +-- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.linalg.qr.html + +The function computes the QR decomposition of a matrix ``m`` of +dimensions ``(M, N)``, i.e., it returns two such matrices, ``q``\ ’, and +``r``, that ``m = qr``, where ``q`` is orthonormal, and ``r`` is upper +triangular. In addition to the input matrix, which is the first +positional argument, the function accepts the ``mode`` keyword argument +with a default value of ``reduced``. If ``mode`` is ``reduced``, ``q``, +and ``r`` are returned in the reduced representation. Otherwise, the +outputs will have dimensions ``(M, M)``, and ``(M, N)``, respectively. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + A = np.arange(6).reshape((3, 2)) + print('A: \n', A) + + print('complete decomposition') + q, r = np.linalg.qr(A, mode='complete') + print('q: \n', q) + print() + print('r: \n', r) + + print('\n\nreduced decomposition') + q, r = np.linalg.qr(A, mode='reduced') + print('q: \n', q) + print() + print('r: \n', r) + +.. parsed-literal:: + + A: + array([[0, 1], + [2, 3], + [4, 5]], dtype=int16) + complete decomposition + q: + array([[0.0, -0.9128709291752768, 0.408248290463863], + [-0.447213595499958, -0.3651483716701107, -0.8164965809277261], + [-0.8944271909999159, 0.1825741858350553, 0.408248290463863]], dtype=float64) + + r: + array([[-4.47213595499958, -5.813776741499454], + [0.0, -1.095445115010332], + [0.0, 0.0]], dtype=float64) + + + reduced decomposition + q: + array([[0.0, -0.9128709291752768], + [-0.447213595499958, -0.3651483716701107], + [-0.8944271909999159, 0.1825741858350553]], dtype=float64) + + r: + array([[-4.47213595499958, -5.813776741499454], + [0.0, -1.095445115010332]], dtype=float64) + + + diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/numpy-universal.rst b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/numpy-universal.rst new file mode 100644 index 00000000..b9b7f9f1 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/numpy-universal.rst @@ -0,0 +1,487 @@ + +Universal functions +=================== + +Standard mathematical functions can be calculated on any scalar, +scalar-valued iterable (ranges, lists, tuples containing numbers), and +on ``ndarray``\ s without having to change the call signature. In all +cases the functions return a new ``ndarray`` of typecode ``float`` +(since these functions usually generate float values, anyway). The only +exceptions to this rule are the ``exp``, and ``sqrt`` functions, which, +if ``ULAB_SUPPORTS_COMPLEX`` is set to 1 in +`ulab.h `__, +can return complex arrays, depending on the argument. All functions +execute faster with ``ndarray`` arguments than with iterables, because +the values of the input vector can be extracted faster. + +At present, the following functions are supported (starred functions can +operate on, or can return complex arrays): + +``acos``, ``acosh``, ``arctan2``, ``around``, ``asin``, ``asinh``, +``atan``, ``arctan2``, ``atanh``, ``ceil``, ``cos``, ``degrees``, +``exp*``, ``expm1``, ``floor``, ``log``, ``log10``, ``log2``, +``radians``, ``sin``, ``sinh``, ``sqrt*``, ``tan``, ``tanh``. + +These functions are applied element-wise to the arguments, thus, e.g., +the exponential of a matrix cannot be calculated in this way, only the +exponential of the matrix entries. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = range(9) + b = np.array(a) + + # works with ranges, lists, tuples etc. + print('a:\t', a) + print('exp(a):\t', np.exp(a)) + + # with 1D arrays + print('\n=============\nb:\n', b) + print('exp(b):\n', np.exp(b)) + + # as well as with matrices + c = np.array(range(9)).reshape((3, 3)) + print('\n=============\nc:\n', c) + print('exp(c):\n', np.exp(c)) + +.. parsed-literal:: + + a: range(0, 9) + exp(a): array([1.0, 2.718281828459045, 7.38905609893065, 20.08553692318767, 54.59815003314424, 148.4131591025766, 403.4287934927351, 1096.633158428459, 2980.957987041728], dtype=float64) + + ============= + b: + array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64) + exp(b): + array([1.0, 2.718281828459045, 7.38905609893065, 20.08553692318767, 54.59815003314424, 148.4131591025766, 403.4287934927351, 1096.633158428459, 2980.957987041728], dtype=float64) + + ============= + c: + array([[0.0, 1.0, 2.0], + [3.0, 4.0, 5.0], + [6.0, 7.0, 8.0]], dtype=float64) + exp(c): + array([[1.0, 2.718281828459045, 7.38905609893065], + [20.08553692318767, 54.59815003314424, 148.4131591025766], + [403.4287934927351, 1096.633158428459, 2980.957987041728]], dtype=float64) + + + + +Computation expenses +-------------------- + +The overhead for calculating with micropython iterables is quite +significant: for the 1000 samples below, the difference is more than 800 +microseconds, because internally the function has to create the +``ndarray`` for the output, has to fetch the iterable’s items of unknown +type, and then convert them to floats. All these steps are skipped for +``ndarray``\ s, because these pieces of information are already known. + +Doing the same with ``list`` comprehension requires 30 times more time +than with the ``ndarray``, which would become even more, if we converted +the resulting list to an ``ndarray``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + import math + + a = [0]*1000 + b = np.array(a) + + @timeit + def timed_vector(iterable): + return np.exp(iterable) + + @timeit + def timed_list(iterable): + return [math.exp(i) for i in iterable] + + print('iterating over ndarray in ulab') + timed_vector(b) + + print('\niterating over list in ulab') + timed_vector(a) + + print('\niterating over list in python') + timed_list(a) + +.. parsed-literal:: + + iterating over ndarray in ulab + execution time: 441 us + + iterating over list in ulab + execution time: 1266 us + + iterating over list in python + execution time: 11379 us + + + +arctan2 +------- + +``numpy``: +https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.arctan2.html + +The two-argument inverse tangent function is also part of the ``vector`` +sub-module. The function implements broadcasting as discussed in the +section on ``ndarray``\ s. Scalars (``micropython`` integers or floats) +are also allowed. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2.2, 33.33, 444.444]) + print('a:\n', a) + print('\narctan2(a, 1.0)\n', np.arctan2(a, 1.0)) + print('\narctan2(1.0, a)\n', np.arctan2(1.0, a)) + print('\narctan2(a, a): \n', np.arctan2(a, a)) + +.. parsed-literal:: + + a: + array([1.0, 2.2, 33.33, 444.444], dtype=float64) + + arctan2(a, 1.0) + array([0.7853981633974483, 1.14416883366802, 1.5408023243361, 1.568546328341769], dtype=float64) + + arctan2(1.0, a) + array([0.7853981633974483, 0.426627493126876, 0.02999400245879636, 0.002249998453127392], dtype=float64) + + arctan2(a, a): + array([0.7853981633974483, 0.7853981633974483, 0.7853981633974483, 0.7853981633974483], dtype=float64) + + + + +around +------ + +``numpy``: +https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.around.html + +``numpy``\ ’s ``around`` function can also be found in the ``vector`` +sub-module. The function implements the ``decimals`` keyword argument +with default value ``0``. The first argument must be an ``ndarray``. If +this is not the case, the function raises a ``TypeError`` exception. +Note that ``numpy`` accepts general iterables. The ``out`` keyword +argument known from ``numpy`` is not accepted. The function always +returns an ndarray of type ``mp_float_t``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2.2, 33.33, 444.444]) + print('a:\t\t', a) + print('\ndecimals = 0\t', np.around(a, decimals=0)) + print('\ndecimals = 1\t', np.around(a, decimals=1)) + print('\ndecimals = -1\t', np.around(a, decimals=-1)) + +.. parsed-literal:: + + a: array([1.0, 2.2, 33.33, 444.444], dtype=float64) + + decimals = 0 array([1.0, 2.0, 33.0, 444.0], dtype=float64) + + decimals = 1 array([1.0, 2.2, 33.3, 444.4], dtype=float64) + + decimals = -1 array([0.0, 0.0, 30.0, 440.0], dtype=float64) + + + + +exp +--- + +If ``ULAB_SUPPORTS_COMPLEX`` is set to 1 in +`ulab.h `__, +the exponential function can also take complex arrays as its argument, +in which case the return value is also complex. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3]) + print('a:\t\t', a) + print('exp(a):\t\t', np.exp(a)) + print() + + b = np.array([1+1j, 2+2j, 3+3j], dtype=np.complex) + print('b:\t\t', b) + print('exp(b):\t\t', np.exp(b)) + +.. parsed-literal:: + + a: array([1.0, 2.0, 3.0], dtype=float64) + exp(a): array([2.718281828459045, 7.38905609893065, 20.08553692318767], dtype=float64) + + b: array([1.0+1.0j, 2.0+2.0j, 3.0+3.0j], dtype=complex) + exp(b): array([1.468693939915885+2.287355287178842j, -3.074932320639359+6.71884969742825j, -19.88453084414699+2.834471132487004j], dtype=complex) + + + + +sqrt +---- + +If ``ULAB_SUPPORTS_COMPLEX`` is set to 1 in +`ulab.h `__, +the exponential function can also take complex arrays as its argument, +in which case the return value is also complex. If the input is real, +but the results might be complex, the user is supposed to specify the +output ``dtype`` in the function call. Otherwise, the square roots of +negative numbers will result in ``NaN``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, -1]) + print('a:\t\t', a) + print('sqrt(a):\t\t', np.sqrt(a)) + print('sqrt(a):\t\t', np.sqrt(a, dtype=np.complex)) + +.. parsed-literal:: + + a: array([1.0, -1.0], dtype=float64) + sqrt(a): array([1.0, nan], dtype=float64) + sqrt(a): array([1.0+0.0j, 0.0+1.0j], dtype=complex) + + + + +Vectorising generic python functions +------------------------------------ + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.vectorize.html + +The examples above use factory functions. In fact, they are nothing but +the vectorised versions of the standard mathematical functions. +User-defined ``python`` functions can also be vectorised by help of +``vectorize``. This function takes a positional argument, namely, the +``python`` function that you want to vectorise, and a non-mandatory +keyword argument, ``otypes``, which determines the ``dtype`` of the +output array. The ``otypes`` must be ``None`` (default), or any of the +``dtypes`` defined in ``ulab``. With ``None``, the output is +automatically turned into a float array. + +The return value of ``vectorize`` is a ``micropython`` object that can +be called as a standard function, but which now accepts either a scalar, +an ``ndarray``, or a generic ``micropython`` iterable as its sole +argument. Note that the function that is to be vectorised must have a +single argument. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + def f(x): + return x*x + + vf = np.vectorize(f) + + # calling with a scalar + print('{:20}'.format('f on a scalar: '), vf(44.0)) + + # calling with an ndarray + a = np.array([1, 2, 3, 4]) + print('{:20}'.format('f on an ndarray: '), vf(a)) + + # calling with a list + print('{:20}'.format('f on a list: '), vf([2, 3, 4])) + +.. parsed-literal:: + + f on a scalar: array([1936.0], dtype=float64) + f on an ndarray: array([1.0, 4.0, 9.0, 16.0], dtype=float64) + f on a list: array([4.0, 9.0, 16.0], dtype=float64) + + + + +As mentioned, the ``dtype`` of the resulting ``ndarray`` can be +specified via the ``otypes`` keyword. The value is bound to the function +object that ``vectorize`` returns, therefore, if the same function is to +be vectorised with different output types, then for each type a new +function object must be created. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + l = [1, 2, 3, 4] + def f(x): + return x*x + + vf1 = np.vectorize(f, otypes=np.uint8) + vf2 = np.vectorize(f, otypes=np.float) + + print('{:20}'.format('output is uint8: '), vf1(l)) + print('{:20}'.format('output is float: '), vf2(l)) + +.. parsed-literal:: + + output is uint8: array([1, 4, 9, 16], dtype=uint8) + output is float: array([1.0, 4.0, 9.0, 16.0], dtype=float64) + + + + +The ``otypes`` keyword argument cannot be used for type coercion: if the +function evaluates to a float, but ``otypes`` would dictate an integer +type, an exception will be raised: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + int_list = [1, 2, 3, 4] + float_list = [1.0, 2.0, 3.0, 4.0] + def f(x): + return x*x + + vf = np.vectorize(f, otypes=np.uint8) + + print('{:20}'.format('integer list: '), vf(int_list)) + # this will raise a TypeError exception + print(vf(float_list)) + +.. parsed-literal:: + + integer list: array([1, 4, 9, 16], dtype=uint8) + + Traceback (most recent call last): + File "/dev/shm/micropython.py", line 14, in + TypeError: can't convert float to int + + + +Benchmarks +~~~~~~~~~~ + +It should be pointed out that the ``vectorize`` function produces the +pseudo-vectorised version of the ``python`` function that is fed into +it, i.e., on the C level, the same ``python`` function is called, with +the all-encompassing ``mp_obj_t`` type arguments, and all that happens +is that the ``for`` loop in ``[f(i) for i in iterable]`` runs purely in +C. Since type checking and type conversion in ``f()`` is expensive, the +speed-up is not so spectacular as when iterating over an ``ndarray`` +with a factory function: a gain of approximately 30% can be expected, +when a native ``python`` type (e.g., ``list``) is returned by the +function, and this becomes around 50% (a factor of 2), if conversion to +an ``ndarray`` is also counted. + +The following code snippet calculates the square of a 1000 numbers with +the vectorised function (which returns an ``ndarray``), with ``list`` +comprehension, and with ``list`` comprehension followed by conversion to +an ``ndarray``. For comparison, the execution time is measured also for +the case, when the square is calculated entirely in ``ulab``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + def f(x): + return x*x + + vf = np.vectorize(f) + + @timeit + def timed_vectorised_square(iterable): + return vf(iterable) + + @timeit + def timed_python_square(iterable): + return [f(i) for i in iterable] + + @timeit + def timed_ndarray_square(iterable): + return np.array([f(i) for i in iterable]) + + @timeit + def timed_ulab_square(ndarray): + return ndarray**2 + + print('vectorised function') + squares = timed_vectorised_square(range(1000)) + + print('\nlist comprehension') + squares = timed_python_square(range(1000)) + + print('\nlist comprehension + ndarray conversion') + squares = timed_ndarray_square(range(1000)) + + print('\nsquaring an ndarray entirely in ulab') + a = np.array(range(1000)) + squares = timed_ulab_square(a) + +.. parsed-literal:: + + vectorised function + execution time: 7237 us + + list comprehension + execution time: 10248 us + + list comprehension + ndarray conversion + execution time: 12562 us + + squaring an ndarray entirely in ulab + execution time: 560 us + + + +From the comparisons above, it is obvious that ``python`` functions +should only be vectorised, when the same effect cannot be gotten in +``ulab`` only. However, although the time savings are not significant, +there is still a good reason for caring about vectorised functions. +Namely, user-defined ``python`` functions become universal, i.e., they +can accept generic iterables as well as ``ndarray``\ s as their +arguments. A vectorised function is still a one-liner, resulting in +transparent and elegant code. + +A final comment on this subject: the ``f(x)`` that we defined is a +*generic* ``python`` function. This means that it is not required that +it just crunches some numbers. It has to return a number object, but it +can still access the hardware in the meantime. So, e.g., + +.. code:: python + + + led = pyb.LED(2) + + def f(x): + if x < 100: + led.toggle() + return x*x + +is perfectly valid code. diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/scipy-linalg.rst b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/scipy-linalg.rst new file mode 100644 index 00000000..52596820 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/scipy-linalg.rst @@ -0,0 +1,151 @@ + +scipy.linalg +============ + +``scipy``\ ’s ``linalg`` module contains two functions, +``solve_triangular``, and ``cho_solve``. The functions can be called by +prepending them by ``scipy.linalg.``. + +1. `scipy.linalg.solve_cho <#cho_solve>`__ +2. `scipy.linalg.solve_triangular <#solve_triangular>`__ + +cho_solve +--------- + +``scipy``: +https://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.cho_solve.html + +Solve the linear equations + +:raw-latex:`\begin{equation} +\mathbf{A}\cdot\mathbf{x} = \mathbf{b} +\end{equation}` + +given the Cholesky factorization of :math:`\mathbf{A}`. As opposed to +``scipy``, the function simply takes the Cholesky-factorised matrix, +:math:`\mathbf{A}`, and :math:`\mathbf{b}` as inputs. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + from ulab import scipy as spy + + A = np.array([[3, 0, 0, 0], [2, 1, 0, 0], [1, 0, 1, 0], [1, 2, 1, 8]]) + b = np.array([4, 2, 4, 2]) + + print(spy.linalg.cho_solve(A, b)) + +.. parsed-literal:: + + array([-0.01388888888888906, -0.6458333333333331, 2.677083333333333, -0.01041666666666667], dtype=float64) + + + + +solve_triangular +---------------- + +``scipy``: +https://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.solve_triangular.html + +Solve the linear equation + +:raw-latex:`\begin{equation} +\mathbf{a}\cdot\mathbf{x} = \mathbf{b} +\end{equation}` + +with the assumption that :math:`\mathbf{a}` is a triangular matrix. The +two position arguments are :math:`\mathbf{a}`, and :math:`\mathbf{b}`, +and the optional keyword argument is ``lower`` with a default value of +``False``. ``lower`` determines, whether data are taken from the lower, +or upper triangle of :math:`\mathbf{a}`. + +Note that :math:`\mathbf{a}` itself does not have to be a triangular +matrix: if it is not, then the values are simply taken to be 0 in the +upper or lower triangle, as dictated by ``lower``. However, +:math:`\mathbf{a}\cdot\mathbf{x}` will yield :math:`\mathbf{b}` only, +when :math:`\mathbf{a}` is triangular. You should keep this in mind, +when trying to establish the validity of the solution by back +substitution. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + from ulab import scipy as spy + + a = np.array([[3, 0, 0, 0], [2, 1, 0, 0], [1, 0, 1, 0], [1, 2, 1, 8]]) + b = np.array([4, 2, 4, 2]) + + print('a:\n') + print(a) + print('\nb: ', b) + + x = spy.linalg.solve_triangular(a, b, lower=True) + + print('='*20) + print('x: ', x) + print('\ndot(a, x): ', np.dot(a, x)) + +.. parsed-literal:: + + a: + + array([[3.0, 0.0, 0.0, 0.0], + [2.0, 1.0, 0.0, 0.0], + [1.0, 0.0, 1.0, 0.0], + [1.0, 2.0, 1.0, 8.0]], dtype=float64) + + b: array([4.0, 2.0, 4.0, 2.0], dtype=float64) + ==================== + x: array([1.333333333333333, -0.6666666666666665, 2.666666666666667, -0.08333333333333337], dtype=float64) + + dot(a, x): array([4.0, 2.0, 4.0, 2.0], dtype=float64) + + + + +With get the same solution, :math:`\mathbf{x}`, with the following +matrix, but the dot product of :math:`\mathbf{a}`, and +:math:`\mathbf{x}` is no longer :math:`\mathbf{b}`: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + from ulab import scipy as spy + + a = np.array([[3, 2, 1, 0], [2, 1, 0, 1], [1, 0, 1, 4], [1, 2, 1, 8]]) + b = np.array([4, 2, 4, 2]) + + print('a:\n') + print(a) + print('\nb: ', b) + + x = spy.linalg.solve_triangular(a, b, lower=True) + + print('='*20) + print('x: ', x) + print('\ndot(a, x): ', np.dot(a, x)) + +.. parsed-literal:: + + a: + + array([[3.0, 2.0, 1.0, 0.0], + [2.0, 1.0, 0.0, 1.0], + [1.0, 0.0, 1.0, 4.0], + [1.0, 2.0, 1.0, 8.0]], dtype=float64) + + b: array([4.0, 2.0, 4.0, 2.0], dtype=float64) + ==================== + x: array([1.333333333333333, -0.6666666666666665, 2.666666666666667, -0.08333333333333337], dtype=float64) + + dot(a, x): array([5.333333333333334, 1.916666666666666, 3.666666666666667, 2.0], dtype=float64) + + + diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/scipy-optimize.rst b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/scipy-optimize.rst new file mode 100644 index 00000000..63d60dda --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/scipy-optimize.rst @@ -0,0 +1,173 @@ + +scipy.optimize +============== + +Functions in the ``optimize`` module can be called by prepending them by +``scipy.optimize.``. The module defines the following three functions: + +1. `scipy.optimize.bisect <#bisect>`__ +2. `scipy.optimize.fmin <#fmin>`__ +3. `scipy.optimize.newton <#newton>`__ + +Note that routines that work with user-defined functions still have to +call the underlying ``python`` code, and therefore, gains in speed are +not as significant as with other vectorised operations. As a rule of +thumb, a factor of two can be expected, when compared to an optimised +``python`` implementation. + +bisect +------ + +``scipy``: +https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.bisect.html + +``bisect`` finds the root of a function of one variable using a simple +bisection routine. It takes three positional arguments, the function +itself, and two starting points. The function must have opposite signs +at the starting points. Returned is the position of the root. + +Two keyword arguments, ``xtol``, and ``maxiter`` can be supplied to +control the accuracy, and the number of bisections, respectively. + +.. code:: + + # code to be run in micropython + + from ulab import scipy as spy + + def f(x): + return x*x - 1 + + print(spy.optimize.bisect(f, 0, 4)) + + print('only 8 bisections: ', spy.optimize.bisect(f, 0, 4, maxiter=8)) + + print('with 0.1 accuracy: ', spy.optimize.bisect(f, 0, 4, xtol=0.1)) + +.. parsed-literal:: + + 0.9999997615814209 + only 8 bisections: 0.984375 + with 0.1 accuracy: 0.9375 + + + + +Performance +~~~~~~~~~~~ + +Since the ``bisect`` routine calls user-defined ``python`` functions, +the speed gain is only about a factor of two, if compared to a purely +``python`` implementation. + +.. code:: + + # code to be run in micropython + + from ulab import scipy as spy + + def f(x): + return (x-1)*(x-1) - 2.0 + + def bisect(f, a, b, xtol=2.4e-7, maxiter=100): + if f(a) * f(b) > 0: + raise ValueError + + rtb = a if f(a) < 0.0 else b + dx = b - a if f(a) < 0.0 else a - b + for i in range(maxiter): + dx *= 0.5 + x_mid = rtb + dx + mid_value = f(x_mid) + if mid_value < 0: + rtb = x_mid + if abs(dx) < xtol: + break + + return rtb + + @timeit + def bisect_scipy(f, a, b): + return spy.optimize.bisect(f, a, b) + + @timeit + def bisect_timed(f, a, b): + return bisect(f, a, b) + + print('bisect running in python') + bisect_timed(f, 3, 2) + + print('bisect running in C') + bisect_scipy(f, 3, 2) + +.. parsed-literal:: + + bisect running in python + execution time: 1270 us + bisect running in C + execution time: 642 us + + + +fmin +---- + +``scipy``: +https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin.html + +The ``fmin`` function finds the position of the minimum of a +user-defined function by using the downhill simplex method. Requires two +positional arguments, the function, and the initial value. Three keyword +arguments, ``xatol``, ``fatol``, and ``maxiter`` stipulate conditions +for stopping. + +.. code:: + + # code to be run in micropython + + from ulab import scipy as spy + + def f(x): + return (x-1)**2 - 1 + + print(spy.optimize.fmin(f, 3.0)) + print(spy.optimize.fmin(f, 3.0, xatol=0.1)) + +.. parsed-literal:: + + 0.9996093749999952 + 1.199999999999996 + + + + +newton +------ + +``scipy``:https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.newton.html + +``newton`` finds a zero of a real, user-defined function using the +Newton-Raphson (or secant or Halley’s) method. The routine requires two +positional arguments, the function, and the initial value. Three keyword +arguments can be supplied to control the iteration. These are the +absolute and relative tolerances ``tol``, and ``rtol``, respectively, +and the number of iterations before stopping, ``maxiter``. The function +retuns a single scalar, the position of the root. + +.. code:: + + # code to be run in micropython + + from ulab import scipy as spy + + def f(x): + return x*x*x - 2.0 + + print(spy.optimize.newton(f, 3., tol=0.001, rtol=0.01)) + +.. parsed-literal:: + + 1.260135727246117 + + + diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/scipy-signal.rst b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/scipy-signal.rst new file mode 100644 index 00000000..d1f34818 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/scipy-signal.rst @@ -0,0 +1,69 @@ + +scipy.signal +============ + +This module defines the single function: + +1. `scipy.signal.sosfilt <#sosfilt>`__ + +sosfilt +------- + +``scipy``: +https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.sosfilt.html + +Filter data along one dimension using cascaded second-order sections. + +The function takes two positional arguments, ``sos``, the filter +segments of length 6, and the one-dimensional, uniformly sampled data +set to be filtered. Returns the filtered data, or the filtered data and +the final filter delays, if the ``zi`` keyword arguments is supplied. +The keyword argument must be a float ``ndarray`` of shape +``(n_sections, 2)``. If ``zi`` is not passed to the function, the +initial values are assumed to be 0. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + from ulab import scipy as spy + + x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + sos = [[1, 2, 3, 1, 5, 6], [1, 2, 3, 1, 5, 6]] + y = spy.signal.sosfilt(sos, x) + print('y: ', y) + +.. parsed-literal:: + + y: array([0.0, 1.0, -4.0, 24.0, -104.0, 440.0, -1728.0, 6532.000000000001, -23848.0, 84864.0], dtype=float) + + + + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + from ulab import scipy as spy + + x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + sos = [[1, 2, 3, 1, 5, 6], [1, 2, 3, 1, 5, 6]] + # initial conditions of the filter + zi = np.array([[1, 2], [3, 4]]) + + y, zf = spy.signal.sosfilt(sos, x, zi=zi) + print('y: ', y) + print('\n' + '='*40 + '\nzf: ', zf) + +.. parsed-literal:: + + y: array([4.0, -16.0, 63.00000000000001, -227.0, 802.9999999999999, -2751.0, 9271.000000000001, -30775.0, 101067.0, -328991.0000000001], dtype=float) + + ======================================== + zf: array([[37242.0, 74835.0], + [1026187.0, 1936542.0]], dtype=float) + + + diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/scipy-special.rst b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/scipy-special.rst new file mode 100644 index 00000000..755a5359 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/scipy-special.rst @@ -0,0 +1,44 @@ + +scipy.special +============= + +``scipy``\ ’s ``special`` module defines several functions that behave +as do the standard mathematical functions of the ``numpy``, i.e., they +can be called on any scalar, scalar-valued iterable (ranges, lists, +tuples containing numbers), and on ``ndarray``\ s without having to +change the call signature. In all cases the functions return a new +``ndarray`` of typecode ``float`` (since these functions usually +generate float values, anyway). + +At present, ``ulab``\ ’s ``special`` module contains the following +functions: + +``erf``, ``erfc``, ``gamma``, and ``gammaln``, and they can be called by +prepending them by ``scipy.special.``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + from ulab import scipy as spy + + a = range(9) + b = np.array(a) + + print('a: ', a) + print(spy.special.erf(a)) + + print('\nb: ', b) + print(spy.special.erfc(b)) + +.. parsed-literal:: + + a: range(0, 9) + array([0.0, 0.8427007929497149, 0.9953222650189527, 0.9999779095030014, 0.9999999845827421, 1.0, 1.0, 1.0, 1.0], dtype=float64) + + b: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64) + array([1.0, 0.1572992070502851, 0.004677734981047265, 2.209049699858544e-05, 1.541725790028002e-08, 1.537459794428035e-12, 2.151973671249892e-17, 4.183825607779414e-23, 1.122429717298293e-29], dtype=float64) + + + diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/ulab-intro.rst b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/ulab-intro.rst new file mode 100644 index 00000000..81019e33 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/ulab-intro.rst @@ -0,0 +1,624 @@ + +Introduction +============ + +Enter ulab +---------- + +``ulab`` is a ``numpy``-like module for ``micropython`` and its +derivatives, meant to simplify and speed up common mathematical +operations on arrays. ``ulab`` implements a small subset of ``numpy`` +and ``scipy``. The functions were chosen such that they might be useful +in the context of a microcontroller. However, the project is a living +one, and suggestions for new features are always welcome. + +This document discusses how you can use the library, starting from +building your own firmware, through questions like what affects the +firmware size, what are the trade-offs, and what are the most important +differences to ``numpy`` and ``scipy``, respectively. The document is +organised as follows: + +The chapter after this one helps you with firmware customisation. + +The third chapter gives a very concise summary of the ``ulab`` functions +and array methods. This chapter can be used as a quick reference. + +The chapters after that are an in-depth review of most functions. Here +you can find usage examples, benchmarks, as well as a thorough +discussion of such concepts as broadcasting, and views versus copies. + +The final chapter of this book can be regarded as the programming +manual. The inner working of ``ulab`` is dissected here, and you will +also find hints as to how to implement your own ``numpy``-compatible +functions. + +Purpose +------- + +Of course, the first question that one has to answer is, why on Earth +one would need a fast math library on a microcontroller. After all, it +is not expected that heavy number crunching is going to take place on +bare metal. It is not meant to. On a PC, the main reason for writing +fast code is the sheer amount of data that one wants to process. On a +microcontroller, the data volume is probably small, but it might lead to +catastrophic system failure, if these data are not processed in time, +because the microcontroller is supposed to interact with the outside +world in a timely fashion. In fact, this latter objective was the +initiator of this project: I needed the Fourier transform of a signal +coming from the ADC of the ``pyboard``, and all available options were +simply too slow. + +In addition to speed, another issue that one has to keep in mind when +working with embedded systems is the amount of available RAM: I believe, +everything here could be implemented in pure ``python`` with relatively +little effort (in fact, there are a couple of ``python``-only +implementations of ``numpy`` functions out there), but the price we +would have to pay for that is not only speed, but RAM, too. ``python`` +code, if is not frozen, and compiled into the firmware, has to be +compiled at runtime, which is not exactly a cheap process. On top of +that, if numbers are stored in a list or tuple, which would be the +high-level container, then they occupy 8 bytes, no matter, whether they +are all smaller than 100, or larger than one hundred million. This is +obviously a waste of resources in an environment, where resources are +scarce. + +Finally, there is a reason for using ``micropython`` in the first place. +Namely, that a microcontroller can be programmed in a very elegant, and +*pythonic* way. But if it is so, why should we not extend this idea to +other tasks and concepts that might come up in this context? If there +was no other reason than this *elegance*, I would find that convincing +enough. + +Based on the above-mentioned considerations, all functions in ``ulab`` +are implemented in a way that + +1. conforms to ``numpy`` as much as possible +2. is so frugal with RAM as possible, +3. and yet, fast. Much faster than pure python. Think of speed-ups of + 30-50! + +The main points of ``ulab`` are + +- compact, iterable and slicable containers of numerical data in one to + four dimensions. These containers support all the relevant unary and + binary operators (e.g., ``len``, ==, +, \*, etc.) +- vectorised computations on ``micropython`` iterables and numerical + arrays (in ``numpy``-speak, universal functions) +- computing statistical properties (mean, standard deviation etc.) on + arrays +- basic linear algebra routines (matrix inversion, multiplication, + reshaping, transposition, determinant, and eigenvalues, Cholesky + decomposition and so on) +- polynomial fits to numerical data, and evaluation of polynomials +- fast Fourier transforms +- filtering of data (convolution and second-order filters) +- function minimisation, fitting, and numerical approximation routines +- interfacing between numerical data and peripheral hardware devices + +``ulab`` implements close to a hundred functions and array methods. At +the time of writing this manual (for version 4.0.0), the library adds +approximately 120 kB of extra compiled code to the ``micropython`` +(pyboard.v.1.17) firmware. However, if you are tight with flash space, +you can easily shave tens of kB off the firmware. In fact, if only a +small sub-set of functions are needed, you can get away with less than +10 kB of flash space. See the section on `customising +ulab <#Customising-the-firmware>`__. + +Resources and legal matters +--------------------------- + +The source code of the module can be found under +https://github.com/v923z/micropython-ulab/tree/master/code. while the +source of this user manual is under +https://github.com/v923z/micropython-ulab/tree/master/docs. + +The MIT licence applies to all material. + +Friendly request +---------------- + +If you use ``ulab``, and bump into a bug, or think that a particular +function is missing, or its behaviour does not conform to ``numpy``, +please, raise a `ulab +issue <#https://github.com/v923z/micropython-ulab/issues>`__ on github, +so that the community can profit from your experiences. + +Even better, if you find the project to be useful, and think that it +could be made better, faster, tighter, and shinier, please, consider +contributing, and issue a pull request with the implementation of your +improvements and new features. ``ulab`` can only become successful, if +it offers what the community needs. + +These last comments apply to the documentation, too. If, in your +opinion, the documentation is obscure, misleading, or not detailed +enough, please, let us know, so that *we* can fix it. + +Differences between micropython-ulab and circuitpython-ulab +----------------------------------------------------------- + +``ulab`` has originally been developed for ``micropython``, but has +since been integrated into a number of its flavours. Most of these are +simply forks of ``micropython`` itself, with some additional +functionality. One of the notable exceptions is ``circuitpython``, which +has slightly diverged at the core level, and this has some minor +consequences. Some of these concern the C implementation details only, +which all have been sorted out with the generous and enthusiastic +support of Jeff Epler from `Adafruit +Industries `__. + +There are, however, a couple of instances, where the two environments +differ at the python level in how the class properties can be accessed. +We will point out the differences and possible workarounds at the +relevant places in this document. + +Customising the firmware +======================== + +As mentioned above, ``ulab`` has considerably grown since its +conception, which also means that it might no longer fit on the +microcontroller of your choice. There are, however, a couple of ways of +customising the firmware, and thereby reducing its size. + +All ``ulab`` options are listed in a single header file, +`ulab.h `__, +which contains pre-processor flags for each feature that can be +fine-tuned. The first couple of lines of the file look like this + +.. code:: c + + // The pre-processor constants in this file determine how ulab behaves: + // + // - how many dimensions ulab can handle + // - which functions are included in the compiled firmware + // - whether the python syntax is numpy-like, or modular + // - whether arrays can be sliced and iterated over + // - which binary/unary operators are supported + // + // A considerable amount of flash space can be saved by removing (setting + // the corresponding constants to 0) the unnecessary functions and features. + + // Values defined here can be overridden by your own config file as + // make -DULAB_CONFIG_FILE="my_ulab_config.h" + #if defined(ULAB_CONFIG_FILE) + #include ULAB_CONFIG_FILE + #endif + + // Adds support for complex ndarrays + #ifndef ULAB_SUPPORTS_COMPLEX + #define ULAB_SUPPORTS_COMPLEX (1) + #endif + + // Determines, whether scipy is defined in ulab. The sub-modules and functions + // of scipy have to be defined separately + #define ULAB_HAS_SCIPY (1) + + // The maximum number of dimensions the firmware should be able to support + // Possible values lie between 1, and 4, inclusive + #define ULAB_MAX_DIMS 2 + + // By setting this constant to 1, iteration over array dimensions will be implemented + // as a function (ndarray_rewind_array), instead of writing out the loops in macros + // This reduces firmware size at the expense of speed + #define ULAB_HAS_FUNCTION_ITERATOR (0) + + // If NDARRAY_IS_ITERABLE is 1, the ndarray object defines its own iterator function + // This option saves approx. 250 bytes of flash space + #define NDARRAY_IS_ITERABLE (1) + + // Slicing can be switched off by setting this variable to 0 + #define NDARRAY_IS_SLICEABLE (1) + + // The default threshold for pretty printing. These variables can be overwritten + // at run-time via the set_printoptions() function + #define ULAB_HAS_PRINTOPTIONS (1) + #define NDARRAY_PRINT_THRESHOLD 10 + #define NDARRAY_PRINT_EDGEITEMS 3 + + // determines, whether the dtype is an object, or simply a character + // the object implementation is numpythonic, but requires more space + #define ULAB_HAS_DTYPE_OBJECT (0) + + // the ndarray binary operators + #define NDARRAY_HAS_BINARY_OPS (1) + + // Firmware size can be reduced at the expense of speed by using function + // pointers in iterations. For each operator, he function pointer saves around + // 2 kB in the two-dimensional case, and around 4 kB in the four-dimensional case. + + #define NDARRAY_BINARY_USES_FUN_POINTER (0) + + #define NDARRAY_HAS_BINARY_OP_ADD (1) + #define NDARRAY_HAS_BINARY_OP_EQUAL (1) + #define NDARRAY_HAS_BINARY_OP_LESS (1) + #define NDARRAY_HAS_BINARY_OP_LESS_EQUAL (1) + #define NDARRAY_HAS_BINARY_OP_MORE (1) + #define NDARRAY_HAS_BINARY_OP_MORE_EQUAL (1) + #define NDARRAY_HAS_BINARY_OP_MULTIPLY (1) + #define NDARRAY_HAS_BINARY_OP_NOT_EQUAL (1) + #define NDARRAY_HAS_BINARY_OP_POWER (1) + #define NDARRAY_HAS_BINARY_OP_SUBTRACT (1) + #define NDARRAY_HAS_BINARY_OP_TRUE_DIVIDE (1) + ... + +The meaning of flags with names ``_HAS_`` should be obvious, so we will +just explain the other options. + +To see how much you can gain by un-setting the functions that you do not +need, here are some pointers. In four dimensions, including all +functions adds around 120 kB to the ``micropython`` firmware. On the +other hand, if you are interested in Fourier transforms only, and strip +everything else, you get away with less than 5 kB extra. + +Compatibility with numpy +------------------------ + +The functions implemented in ``ulab`` are organised in four sub-modules +at the C level, namely, ``numpy``, ``scipy``, ``utils``, and ``user``. +This modularity is elevated to ``python``, meaning that in order to use +functions that are part of ``numpy``, you have to import ``numpy`` as + +.. code:: python + + from ulab import numpy as np + + x = np.array([4, 5, 6]) + p = np.array([1, 2, 3]) + np.polyval(p, x) + +There are a couple of exceptions to this rule, namely ``fft``, and +``linalg``, which are sub-modules even in ``numpy``, thus you have to +write them out as + +.. code:: python + + from ulab import numpy as np + + A = np.array([1, 2, 3, 4]).reshape() + np.linalg.trace(A) + +Some of the functions in ``ulab`` are re-implementations of ``scipy`` +functions, and they are to be imported as + +.. code:: python + + from ulab import numpy as np + from ulab import scipy as spy + + + x = np.array([1, 2, 3]) + spy.special.erf(x) + +``numpy``-compatibility has an enormous benefit : namely, by +``try``\ ing to ``import``, we can guarantee that the same, unmodified +code runs in ``CPython``, as in ``micropython``. The following snippet +is platform-independent, thus, the ``python`` code can be tested and +debugged on a computer before loading it onto the microcontroller. + +.. code:: python + + + try: + from ulab import numpy as np + from ulab import scipy as spy + except ImportError: + import numpy as np + import scipy as spy + + x = np.array([1, 2, 3]) + spy.special.erf(x) + +The impact of dimensionality +---------------------------- + +Reducing the number of dimensions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``ulab`` supports tensors of rank four, but this is expensive in terms +of flash: with all available functions and options, the library adds +around 100 kB to the firmware. However, if such high dimensions are not +required, significant reductions in size can be gotten by changing the +value of + +.. code:: c + + #define ULAB_MAX_DIMS 2 + +Two dimensions cost a bit more than half of four, while you can get away +with around 20 kB of flash in one dimension, because all those functions +that don’t make sense (e.g., matrix inversion, eigenvalues etc.) are +automatically stripped from the firmware. + +Using the function iterator +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In higher dimensions, the firmware size increases, because each +dimension (axis) adds another level of nested loops. An example of this +is the macro of the binary operator in three dimensions + +.. code:: c + + #define BINARY_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR) + type_out *array = (type_out *)results->array; + size_t j = 0; + do { + size_t k = 0; + do { + size_t l = 0; + do { + *array++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray)); + (larray) += (lstrides)[ULAB_MAX_DIMS - 1]; + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1]; + l++; + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]); + (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1]; + (larray) += (lstrides)[ULAB_MAX_DIMS - 2]; + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1]; + (rarray) += (rstrides)[ULAB_MAX_DIMS - 2]; + k++; + } while(k < (results)->shape[ULAB_MAX_DIMS - 2]); + (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + (larray) += (lstrides)[ULAB_MAX_DIMS - 3]; + (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2]; + (rarray) += (rstrides)[ULAB_MAX_DIMS - 3]; + j++; + } while(j < (results)->shape[ULAB_MAX_DIMS - 3]); + +In order to reduce firmware size, it *might* make sense in higher +dimensions to make use of the function iterator by setting the + +.. code:: c + + #define ULAB_HAS_FUNCTION_ITERATOR (1) + +constant to 1. This allows the compiler to call the +``ndarray_rewind_array`` function, so that it doesn’t have to unwrap the +loops for ``k``, and ``j``. Instead of the macro above, we now have + +.. code:: c + + #define BINARY_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR) + type_out *array = (type_out *)(results)->array; + size_t *lcoords = ndarray_new_coords((results)->ndim); + size_t *rcoords = ndarray_new_coords((results)->ndim); + for(size_t i=0; i < (results)->len/(results)->shape[ULAB_MAX_DIMS -1]; i++) { + size_t l = 0; + do { + *array++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray)); + (larray) += (lstrides)[ULAB_MAX_DIMS - 1]; + (rarray) += (rstrides)[ULAB_MAX_DIMS - 1]; + l++; + } while(l < (results)->shape[ULAB_MAX_DIMS - 1]); + ndarray_rewind_array((results)->ndim, larray, (results)->shape, lstrides, lcoords); + ndarray_rewind_array((results)->ndim, rarray, (results)->shape, rstrides, rcoords); + } while(0) + +Since the ``ndarray_rewind_array`` function is implemented only once, a +lot of space can be saved. Obviously, function calls cost time, thus +such trade-offs must be evaluated for each application. The gain also +depends on which functions and features you include. Operators and +functions that involve two arrays are expensive, because at the C level, +the number of cases that must be handled scales with the squares of the +number of data types. As an example, the innocent-looking expression + +.. code:: python + + + from ulab import numpy as np + + a = np.array([1, 2, 3]) + b = np.array([4, 5, 6]) + + c = a + b + +requires 25 loops in C, because the ``dtypes`` of both ``a``, and ``b`` +can assume 5 different values, and the addition has to be resolved for +all possible cases. Hint: each binary operator costs between 3 and 4 kB +in two dimensions. + +The ulab version string +----------------------- + +As is customary with ``python`` packages, information on the package +version can be found be querying the ``__version__`` string. + +.. code:: + + # code to be run in micropython + + import ulab + + print('you are running ulab version', ulab.__version__) + +.. parsed-literal:: + + you are running ulab version 2.1.0-2D + + + + +The first three numbers indicate the major, minor, and sub-minor +versions of ``ulab`` (defined by the ``ULAB_VERSION`` constant in +`ulab.c `__). +We usually change the minor version, whenever a new function is added to +the code, and the sub-minor version will be incremented, if a bug fix is +implemented. + +``2D`` tells us that the particular firmware supports tensors of rank 2 +(defined by ``ULAB_MAX_DIMS`` in +`ulab.h `__). + +If you find a bug, please, include the version string in your report! + +Should you need the numerical value of ``ULAB_MAX_DIMS``, you can get it +from the version string in the following way: + +.. code:: + + # code to be run in micropython + + import ulab + + version = ulab.__version__ + version_dims = version.split('-')[1] + version_num = int(version_dims.replace('D', '')) + + print('version string: ', version) + print('version dimensions: ', version_dims) + print('numerical value of dimensions: ', version_num) + +.. parsed-literal:: + + version string: 2.1.0-2D + version dimensions: 2D + numerical value of dimensions: 2 + + + + +ulab with complex arrays +~~~~~~~~~~~~~~~~~~~~~~~~ + +If the firmware supports complex arrays, ``-c`` is appended to the +version string as can be seen below. + +.. code:: + + # code to be run in micropython + + import ulab + + version = ulab.__version__ + + print('version string: ', version) + +.. parsed-literal:: + + version string: 4.0.0-2D-c + + + + +Finding out what your firmware supports +--------------------------------------- + +``ulab`` implements a number of array operators and functions, but this +does not mean that all of these functions and methods are actually +compiled into the firmware. You can fine-tune your firmware by +setting/unsetting any of the ``_HAS_`` constants in +`ulab.h `__. + +Functions included in the firmware +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The version string will not tell you everything about your firmware, +because the supported functions and sub-modules can still arbitrarily be +included or excluded. One way of finding out what is compiled into the +firmware is calling ``dir`` with ``ulab`` as its argument. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + from ulab import scipy as spy + + + print('===== constants, functions, and modules of numpy =====\n\n', dir(np)) + + # since fft and linalg are sub-modules, print them separately + print('\nfunctions included in the fft module:\n', dir(np.fft)) + print('\nfunctions included in the linalg module:\n', dir(np.linalg)) + + print('\n\n===== modules of scipy =====\n\n', dir(spy)) + print('\nfunctions included in the optimize module:\n', dir(spy.optimize)) + print('\nfunctions included in the signal module:\n', dir(spy.signal)) + print('\nfunctions included in the special module:\n', dir(spy.special)) + +.. parsed-literal:: + + ===== constants, functions, and modules of numpy ===== + + ['__class__', '__name__', 'bool', 'sort', 'sum', 'acos', 'acosh', 'arange', 'arctan2', 'argmax', 'argmin', 'argsort', 'around', 'array', 'asin', 'asinh', 'atan', 'atanh', 'ceil', 'clip', 'concatenate', 'convolve', 'cos', 'cosh', 'cross', 'degrees', 'diag', 'diff', 'e', 'equal', 'exp', 'expm1', 'eye', 'fft', 'flip', 'float', 'floor', 'frombuffer', 'full', 'get_printoptions', 'inf', 'int16', 'int8', 'interp', 'linalg', 'linspace', 'log', 'log10', 'log2', 'logspace', 'max', 'maximum', 'mean', 'median', 'min', 'minimum', 'nan', 'ndinfo', 'not_equal', 'ones', 'pi', 'polyfit', 'polyval', 'radians', 'roll', 'set_printoptions', 'sin', 'sinh', 'sqrt', 'std', 'tan', 'tanh', 'trapz', 'uint16', 'uint8', 'vectorize', 'zeros'] + + functions included in the fft module: + ['__class__', '__name__', 'fft', 'ifft'] + + functions included in the linalg module: + ['__class__', '__name__', 'cholesky', 'det', 'dot', 'eig', 'inv', 'norm', 'trace'] + + + ===== modules of scipy ===== + + ['__class__', '__name__', 'optimize', 'signal', 'special'] + + functions included in the optimize module: + ['__class__', '__name__', 'bisect', 'fmin', 'newton'] + + functions included in the signal module: + ['__class__', '__name__', 'sosfilt', 'spectrogram'] + + functions included in the special module: + ['__class__', '__name__', 'erf', 'erfc', 'gamma', 'gammaln'] + + + + +Methods included in the firmware +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``dir`` function applied to the module or its sub-modules gives +information on what the module and sub-modules include, but is not +enough to find out which methods the ``ndarray`` class supports. We can +list the methods by calling ``dir`` with the ``array`` object itself: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + print(dir(np.array)) + +.. parsed-literal:: + + ['__class__', '__name__', 'copy', 'sort', '__bases__', '__dict__', 'dtype', 'flatten', 'itemsize', 'reshape', 'shape', 'size', 'strides', 'tobytes', 'transpose'] + + + + +Operators included in the firmware +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A list of operators cannot be generated as shown above. If you really +need to find out, whether, e.g., the ``**`` operator is supported by the +firmware, you have to ``try`` it: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3]) + b = np.array([4, 5, 6]) + + try: + print(a ** b) + except Exception as e: + print('operator is not supported: ', e) + +.. parsed-literal:: + + operator is not supported: unsupported types for __pow__: 'ndarray', 'ndarray' + + + + +The exception above would be raised, if the firmware was compiled with +the + +.. code:: c + + #define NDARRAY_HAS_BINARY_OP_POWER (0) + +definition. diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/ulab-ndarray.rst b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/ulab-ndarray.rst new file mode 100644 index 00000000..ff8e6eb7 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/ulab-ndarray.rst @@ -0,0 +1,2652 @@ + +ndarray, the base class +======================= + +The ``ndarray`` is the underlying container of numerical data. It can be +thought of as micropython’s own ``array`` object, but has a great number +of extra features starting with how it can be initialised, which +operations can be done on it, and which functions can accept it as an +argument. One important property of an ``ndarray`` is that it is also a +proper ``micropython`` iterable. + +The ``ndarray`` consists of a short header, and a pointer that holds the +data. The pointer always points to a contiguous segment in memory +(``numpy`` is more flexible in this regard), and the header tells the +interpreter, how the data from this segment is to be read out, and what +the bytes mean. Some operations, e.g., ``reshape``, are fast, because +they do not operate on the data, they work on the header, and therefore, +only a couple of bytes are manipulated, even if there are a million data +entries. A more detailed exposition of how operators are implemented can +be found in the section titled `Programming ulab <#Programming_ula>`__. + +Since the ``ndarray`` is a binary container, it is also compact, meaning +that it takes only a couple of bytes of extra RAM in addition to what is +required for storing the numbers themselves. ``ndarray``\ s are also +type-aware, i.e., one can save RAM by specifying a data type, and using +the smallest reasonable one. Five such types are defined, namely +``uint8``, ``int8``, which occupy a single byte of memory per datum, +``uint16``, and ``int16``, which occupy two bytes per datum, and +``float``, which occupies four or eight bytes per datum. The +precision/size of the ``float`` type depends on the definition of +``mp_float_t``. Some platforms, e.g., the PYBD, implement ``double``\ s, +but some, e.g., the pyboard.v.11, do not. You can find out, what type of +float your particular platform implements by looking at the output of +the `.itemsize <#.itemsize>`__ class property, or looking at the exact +``dtype``, when you print out an array. + +In addition to the five above-mentioned numerical types, it is also +possible to define Boolean arrays, which can be used in the indexing of +data. However, Boolean arrays are really nothing but arrays of type +``uint8`` with an extra flag. + +On the following pages, we will see how one can work with +``ndarray``\ s. Those familiar with ``numpy`` should find that the +nomenclature and naming conventions of ``numpy`` are adhered to as +closely as possible. We will point out the few differences, where +necessary. + +For the sake of comparison, in addition to the ``ulab`` code snippets, +sometimes the equivalent ``numpy`` code is also presented. You can find +out, where the snippet is supposed to run by looking at its first line, +the header of the code block. + +The ndinfo function +------------------- + +A concise summary of a couple of the properties of an ``ndarray`` can be +printed out by calling the ``ndinfo`` function. In addition to finding +out what the *shape* and *strides* of the array array, we also get the +``itemsize``, as well as the type. An interesting piece of information +is the *data pointer*, which tells us, what the address of the data +segment of the ``ndarray`` is. We will see the significance of this in +the section `Slicing and indexing <#Slicing-and-indexing>`__. + +Note that this function simply prints some information, but does not +return anything. If you need to get a handle of the data contained in +the printout, you should call the dedicated ``shape``, ``strides``, or +``itemsize`` functions directly. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(5), dtype=np.float) + b = np.array(range(25), dtype=np.uint8).reshape((5, 5)) + np.ndinfo(a) + print('\n') + np.ndinfo(b) + +.. parsed-literal:: + + class: ndarray + shape: (5,) + strides: (8,) + itemsize: 8 + data pointer: 0x7f8f6fa2e240 + type: float + + + class: ndarray + shape: (5, 5) + strides: (5, 1) + itemsize: 1 + data pointer: 0x7f8f6fa2e2e0 + type: uint8 + + + + +Initialising an array +--------------------- + +A new array can be created by passing either a standard micropython +iterable, or another ``ndarray`` into the constructor. + +Initialising by passing iterables +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If the iterable is one-dimensional, i.e., one whose elements are +numbers, then a row vector will be created and returned. If the iterable +is two-dimensional, i.e., one whose elements are again iterables, a +matrix will be created. If the lengths of the iterables are not +consistent, a ``ValueError`` will be raised. Iterables of different +types can be mixed in the initialisation function. + +If the ``dtype`` keyword with the possible +``uint8/int8/uint16/int16/float`` values is supplied, the new +``ndarray`` will have that type, otherwise, it assumes ``float`` as +default. In addition, if ``ULAB_SUPPORTS_COMPLEX`` is set to 1 in +`ulab.h `__, +the ``dtype`` can also take on the value of ``complex``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = [1, 2, 3, 4, 5, 6, 7, 8] + b = np.array(a) + + print("a:\t", a) + print("b:\t", b) + + # a two-dimensional array with mixed-type initialisers + c = np.array([range(5), range(20, 25, 1), [44, 55, 66, 77, 88]], dtype=np.uint8) + print("\nc:\t", c) + + # and now we throw an exception + d = np.array([range(5), range(10), [44, 55, 66, 77, 88]], dtype=np.uint8) + print("\nd:\t", d) + +.. parsed-literal:: + + a: [1, 2, 3, 4, 5, 6, 7, 8] + b: array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64) + + c: array([[0, 1, 2, 3, 4], + [20, 21, 22, 23, 24], + [44, 55, 66, 77, 88]], dtype=uint8) + + Traceback (most recent call last): + File "/dev/shm/micropython.py", line 15, in + ValueError: iterables are not of the same length + + + +Initialising by passing arrays +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +An ``ndarray`` can be initialised by supplying another array. This +statement is almost trivial, since ``ndarray``\ s are iterables +themselves, though it should be pointed out that initialising through +arrays is a bit faster. This statement is especially true, if the +``dtype``\ s of the source and output arrays are the same, because then +the contents can simply be copied without further ado. While type +conversion is also possible, it will always be slower than straight +copying. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = [1, 2, 3, 4, 5, 6, 7, 8] + b = np.array(a) + c = np.array(b) + d = np.array(b, dtype=np.uint8) + + print("a:\t", a) + print("\nb:\t", b) + print("\nc:\t", c) + print("\nd:\t", d) + +.. parsed-literal:: + + a: [1, 2, 3, 4, 5, 6, 7, 8] + + b: array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64) + + c: array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64) + + d: array([1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8) + + + + +Note that the default type of the ``ndarray`` is ``float``. Hence, if +the array is initialised from another array, type conversion will always +take place, except, when the output type is specifically supplied. I.e., + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(5), dtype=np.uint8) + b = np.array(a) + print("a:\t", a) + print("\nb:\t", b) + +.. parsed-literal:: + + a: array([0, 1, 2, 3, 4], dtype=uint8) + + b: array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float64) + + + + +will iterate over the elements in ``a``, since in the assignment +``b = np.array(a)``, no output type was given, therefore, ``float`` was +assumed. On the other hand, + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(5), dtype=np.uint8) + b = np.array(a, dtype=np.uint8) + print("a:\t", a) + print("\nb:\t", b) + +.. parsed-literal:: + + a: array([0, 1, 2, 3, 4], dtype=uint8) + + b: array([0, 1, 2, 3, 4], dtype=uint8) + + + + +will simply copy the content of ``a`` into ``b`` without any iteration, +and will, therefore, be faster. Keep this in mind, whenever the output +type, or performance is important. + +Array initialisation functions +============================== + +There are nine functions that can be used for initialising an array. +Starred functions accept ``complex`` as the value of the ``dtype``, if +the firmware was compiled with complex support. + +1. `numpy.arange <#arange>`__ +2. `numpy.concatenate <#concatenate>`__ +3. `numpy.diag\* <#diag>`__ +4. `numpy.empty\* <#empty>`__ +5. `numpy.eye\* <#eye>`__ +6. `numpy.frombuffer <#frombuffer>`__ +7. `numpy.full\* <#full>`__ +8. `numpy.linspace\* <#linspace>`__ +9. `numpy.logspace <#logspace>`__ +10. `numpy.ones\* <#ones>`__ +11. `numpy.zeros\* <#zeros>`__ + +arange +------ + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.arange.html + +The function returns a one-dimensional array with evenly spaced values. +Takes 3 positional arguments (two are optional), and the ``dtype`` +keyword argument. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + print(np.arange(10)) + print(np.arange(2, 10)) + print(np.arange(2, 10, 3)) + print(np.arange(2, 10, 3, dtype=np.float)) + +.. parsed-literal:: + + array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=int16) + array([2, 3, 4, 5, 6, 7, 8, 9], dtype=int16) + array([2, 5, 8], dtype=int16) + array([2.0, 5.0, 8.0], dtype=float64) + + + + +concatenate +----------- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.concatenate.html + +The function joins a sequence of arrays, if they are compatible in +shape, i.e., if all shapes except the one along the joining axis are +equal. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(25), dtype=np.uint8).reshape((5, 5)) + b = np.array(range(15), dtype=np.uint8).reshape((3, 5)) + + c = np.concatenate((a, b), axis=0) + print(c) + +.. parsed-literal:: + + array([[0, 1, 2, 3, 4], + [5, 6, 7, 8, 9], + [10, 11, 12, 13, 14], + [15, 16, 17, 18, 19], + [20, 21, 22, 23, 24], + [0, 1, 2, 3, 4], + [5, 6, 7, 8, 9], + [10, 11, 12, 13, 14]], dtype=uint8) + + + + +**WARNING**: ``numpy`` accepts arbitrary ``dtype``\ s in the sequence of +arrays, in ``ulab`` the ``dtype``\ s must be identical. If you want to +concatenate different types, you have to convert all arrays to the same +type first. Here ``b`` is of ``float`` type, so it cannot directly be +concatenated to ``a``. However, if we cast the ``dtype`` of ``b``, the +concatenation works: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(25), dtype=np.uint8).reshape((5, 5)) + b = np.array(range(15), dtype=np.float).reshape((5, 3)) + d = np.array(b+1, dtype=np.uint8) + print('a: ', a) + print('='*20 + '\nd: ', d) + c = np.concatenate((d, a), axis=1) + print('='*20 + '\nc: ', c) + +.. parsed-literal:: + + a: array([[0, 1, 2, 3, 4], + [5, 6, 7, 8, 9], + [10, 11, 12, 13, 14], + [15, 16, 17, 18, 19], + [20, 21, 22, 23, 24]], dtype=uint8) + ==================== + d: array([[1, 2, 3], + [4, 5, 6], + [7, 8, 9], + [10, 11, 12], + [13, 14, 15]], dtype=uint8) + ==================== + c: array([[1, 2, 3, 0, 1, 2, 3, 4], + [4, 5, 6, 5, 6, 7, 8, 9], + [7, 8, 9, 10, 11, 12, 13, 14], + [10, 11, 12, 15, 16, 17, 18, 19], + [13, 14, 15, 20, 21, 22, 23, 24]], dtype=uint8) + + + + +diag +---- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.diag.html + +Extract a diagonal, or construct a diagonal array. + +The function takes a positional argument, an ``ndarray``, or any +``micropython`` iterable, and an optional keyword argument, a shift, +with a default value of 0. If the first argument is a two-dimensional +array (or a two-dimensional iterable, e.g., a list of lists), the +function returns a one-dimensional array containing the diagonal +entries. The diagonal can be shifted by an amount given in the second +argument. If the shift is larger than the length of the corresponding +axis, an empty array is returned. + +If the first argument is a one-dimensional array, the function returns a +two-dimensional square tensor with its diagonal elements given by the +first argument. Again, the diagonal be shifted by an amount given by the +keyword argument. + +The ``diag`` function can accept a complex array, if the firmware was +compiled with complex support. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3], dtype=np.uint8) + print(np.diag(a)) + + print('\ndiagonal shifted by 2') + print(np.diag(a, k=2)) + + print('\ndiagonal shifted by -2') + print(np.diag(a, k=-2)) + +.. parsed-literal:: + + array([[1, 0, 0], + [0, 2, 0], + [0, 0, 3]], dtype=uint8) + + diagonal shifted by 2 + array([[0, 0, 1, 0, 0], + [0, 0, 0, 2, 0], + [0, 0, 0, 0, 3], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0]], dtype=uint8) + + diagonal shifted by -2 + array([[0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [1, 0, 0, 0, 0], + [0, 2, 0, 0, 0], + [0, 0, 3, 0, 0]], dtype=uint8) + + + + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.arange(16).reshape((4, 4)) + print(a) + print('\ndiagonal of a:') + print(np.diag(a)) + + print('\ndiagonal of a:') + print(np.diag(a)) + + print('\ndiagonal of a, shifted by 2') + print(np.diag(a, k=2)) + + print('\ndiagonal of a, shifted by 5') + print(np.diag(a, k=5)) + +.. parsed-literal:: + + array([[0, 1, 2, 3], + [4, 5, 6, 7], + [8, 9, 10, 11], + [12, 13, 14, 15]], dtype=int16) + + diagonal of a: + array([0, 5, 10, 15], dtype=int16) + + diagonal of a: + array([0, 5, 10, 15], dtype=int16) + + diagonal of a, shifted by 2 + array([2, 7], dtype=int16) + + diagonal of a, shifted by 5 + array([], dtype=int16) + + + + +empty +----- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.empty.html + +``empty`` is simply an alias for ``zeros``, i.e., as opposed to +``numpy``, the entries of the tensor will be initialised to zero. + +The ``empty`` function can accept complex as the value of the dtype, if +the firmware was compiled with complex support. + +eye +--- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.eye.html + +Another special array method is the ``eye`` function, whose call +signature is + +.. code:: python + + eye(N, M, k=0, dtype=float) + +where ``N`` (``M``) specify the dimensions of the matrix (if only ``N`` +is supplied, then we get a square matrix, otherwise one with ``M`` rows, +and ``N`` columns), and ``k`` is the shift of the ones (the main +diagonal corresponds to ``k=0``). Here are a couple of examples. + +The ``eye`` function can accept ``complex`` as the value of the +``dtype``, if the firmware was compiled with complex support. + +With a single argument +~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + print(np.eye(5)) + +.. parsed-literal:: + + array([[1.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 1.0]], dtype=float64) + + + + +Specifying the dimensions of the matrix +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + print(np.eye(4, M=6, k=-1, dtype=np.int16)) + +.. parsed-literal:: + + array([[0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0]], dtype=int16) + + + + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + print(np.eye(4, M=6, dtype=np.int8)) + +.. parsed-literal:: + + array([[1, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0], + [0, 0, 0, 1, 0, 0]], dtype=int8) + + + + +frombuffer +---------- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.frombuffer.html + +The function interprets a contiguous buffer as a one-dimensional array, +and thus can be used for piping buffered data directly into an array. +This method of analysing, e.g., ADC data is much more efficient than +passing the ADC buffer into the ``array`` constructor, because +``frombuffer`` simply creates the ``ndarray`` header and blindly copies +the memory segment, without inspecting the underlying data. + +The function takes a single positional argument, the buffer, and three +keyword arguments. These are the ``dtype`` with a default value of +``float``, the ``offset``, with a default of 0, and the ``count``, with +a default of -1, meaning that all data are taken in. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + buffer = b'\x01\x02\x03\x04\x05\x06\x07\x08' + print('buffer: ', buffer) + + a = np.frombuffer(buffer, dtype=np.uint8) + print('a, all data read: ', a) + + b = np.frombuffer(buffer, dtype=np.uint8, offset=2) + print('b, all data with an offset: ', b) + + c = np.frombuffer(buffer, dtype=np.uint8, offset=2, count=3) + print('c, only 3 items with an offset: ', c) + +.. parsed-literal:: + + buffer: b'\x01\x02\x03\x04\x05\x06\x07\x08' + a, all data read: array([1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8) + b, all data with an offset: array([3, 4, 5, 6, 7, 8], dtype=uint8) + c, only 3 items with an offset: array([3, 4, 5], dtype=uint8) + + + + +full +---- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.full.html + +The function returns an array of arbitrary dimension, whose elements are +all equal to the second positional argument. The first argument is a +tuple describing the shape of the tensor. The ``dtype`` keyword argument +with a default value of ``float`` can also be supplied. + +The ``full`` function can accept a complex scalar, or ``complex`` as the +value of ``dtype``, if the firmware was compiled with complex support. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + # create an array with the default type + print(np.full((2, 4), 3)) + + print('\n' + '='*20 + '\n') + # the array type is uint8 now + print(np.full((2, 4), 3, dtype=np.uint8)) + +.. parsed-literal:: + + array([[3.0, 3.0, 3.0, 3.0], + [3.0, 3.0, 3.0, 3.0]], dtype=float64) + + ==================== + + array([[3, 3, 3, 3], + [3, 3, 3, 3]], dtype=uint8) + + + + +linspace +-------- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.linspace.html + +This function returns an array, whose elements are uniformly spaced +between the ``start``, and ``stop`` points. The number of intervals is +determined by the ``num`` keyword argument, whose default value is 50. +With the ``endpoint`` keyword argument (defaults to ``True``) one can +include ``stop`` in the sequence. In addition, the ``dtype`` keyword can +be supplied to force type conversion of the output. The default is +``float``. Note that, when ``dtype`` is of integer type, the sequence is +not necessarily evenly spaced. This is not an error, rather a +consequence of rounding. (This is also the ``numpy`` behaviour.) + +The ``linspace`` function can accept ``complex`` as the value of the +``dtype``, if the firmware was compiled with complex support. The output +``dtype`` is automatically complex, if either of the endpoints is a +complex scalar. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + # generate a sequence with defaults + print('default sequence:\t', np.linspace(0, 10)) + + # num=5 + print('num=5:\t\t\t', np.linspace(0, 10, num=5)) + + # num=5, endpoint=False + print('num=5:\t\t\t', np.linspace(0, 10, num=5, endpoint=False)) + + # num=5, endpoint=False, dtype=uint8 + print('num=5:\t\t\t', np.linspace(0, 5, num=7, endpoint=False, dtype=np.uint8)) + +.. parsed-literal:: + + default sequence: array([0.0, 0.2040816326530612, 0.4081632653061225, ..., 9.591836734693871, 9.795918367346932, 9.999999999999993], dtype=float64) + num=5: array([0.0, 2.5, 5.0, 7.5, 10.0], dtype=float64) + num=5: array([0.0, 2.0, 4.0, 6.0, 8.0], dtype=float64) + num=5: array([0, 0, 1, 2, 2, 3, 4], dtype=uint8) + + + + +logspace +-------- + +``linspace``\ ’ equivalent for logarithmically spaced data is +``logspace``. This function produces a sequence of numbers, in which the +quotient of consecutive numbers is constant. This is a geometric +sequence. + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.logspace.html + +This function returns an array, whose elements are uniformly spaced +between the ``start``, and ``stop`` points. The number of intervals is +determined by the ``num`` keyword argument, whose default value is 50. +With the ``endpoint`` keyword argument (defaults to ``True``) one can +include ``stop`` in the sequence. In addition, the ``dtype`` keyword can +be supplied to force type conversion of the output. The default is +``float``. Note that, exactly as in ``linspace``, when ``dtype`` is of +integer type, the sequence is not necessarily evenly spaced in log +space. + +In addition to the keyword arguments found in ``linspace``, ``logspace`` +also accepts the ``base`` argument. The default value is 10. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + # generate a sequence with defaults + print('default sequence:\t', np.logspace(0, 3)) + + # num=5 + print('num=5:\t\t\t', np.logspace(1, 10, num=5)) + + # num=5, endpoint=False + print('num=5:\t\t\t', np.logspace(1, 10, num=5, endpoint=False)) + + # num=5, endpoint=False + print('num=5:\t\t\t', np.logspace(1, 10, num=5, endpoint=False, base=2)) + +.. parsed-literal:: + + default sequence: array([1.0, 1.151395399326447, 1.325711365590109, ..., 754.3120063354646, 868.5113737513561, 1000.000000000004], dtype=float64) + num=5: array([10.0, 1778.279410038923, 316227.766016838, 56234132.5190349, 10000000000.0], dtype=float64) + num=5: array([10.0, 630.9573444801933, 39810.71705534974, 2511886.431509581, 158489319.2461114], dtype=float64) + num=5: array([2.0, 6.964404506368993, 24.25146506416637, 84.44850628946524, 294.066778879241], dtype=float64) + + + + +ones, zeros +----------- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.zeros.html + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.ones.html + +A couple of special arrays and matrices can easily be initialised by +calling one of the ``ones``, or ``zeros`` functions. ``ones`` and +``zeros`` follow the same pattern, and have the call signature + +.. code:: python + + ones(shape, dtype=float) + zeros(shape, dtype=float) + +where shape is either an integer, or a tuple specifying the shape. + +The ``ones/zeros`` functions can accept complex as the value of the +dtype, if the firmware was compiled with complex support. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + print(np.ones(6, dtype=np.uint8)) + + print(np.zeros((6, 4))) + +.. parsed-literal:: + + array([1, 1, 1, 1, 1, 1], dtype=uint8) + array([[0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0]], dtype=float64) + + + + +When specifying the shape, make sure that the length of the tuple is not +larger than the maximum dimension of your firmware. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + import ulab + + print('maximum number of dimensions: ', ulab.__version__) + + print(np.zeros((2, 2, 2))) + +.. parsed-literal:: + + maximum number of dimensions: 2.1.0-2D + + Traceback (most recent call last): + File "/dev/shm/micropython.py", line 7, in + TypeError: too many dimensions + + + +Customising array printouts +=========================== + +``ndarray``\ s are pretty-printed, i.e., if the number of entries along +the last axis is larger than 10 (default value), then only the first and +last three entries will be printed. Also note that, as opposed to +``numpy``, the printout always contains the ``dtype``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(200)) + print("a:\t", a) + +.. parsed-literal:: + + a: array([0.0, 1.0, 2.0, ..., 197.0, 198.0, 199.0], dtype=float64) + + + + +set_printoptions +---------------- + +The default values can be overwritten by means of the +``set_printoptions`` function +`numpy.set_printoptions `__, +which accepts two keywords arguments, the ``threshold``, and the +``edgeitems``. The first of these arguments determines the length of the +longest array that will be printed in full, while the second is the +number of items that will be printed on the left and right hand side of +the ellipsis, if the array is longer than ``threshold``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(20)) + print("a printed with defaults:\t", a) + + np.set_printoptions(threshold=200) + print("\na printed in full:\t\t", a) + + np.set_printoptions(threshold=10, edgeitems=2) + print("\na truncated with 2 edgeitems:\t", a) + +.. parsed-literal:: + + a printed with defaults: array([0.0, 1.0, 2.0, ..., 17.0, 18.0, 19.0], dtype=float64) + + a printed in full: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0], dtype=float64) + + a truncated with 2 edgeitems: array([0.0, 1.0, ..., 18.0, 19.0], dtype=float64) + + + + +get_printoptions +---------------- + +The set value of the ``threshold`` and ``edgeitems`` can be retrieved by +calling the ``get_printoptions`` function with no arguments. The +function returns a *dictionary* with two keys. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + np.set_printoptions(threshold=100, edgeitems=20) + print(np.get_printoptions()) + +.. parsed-literal:: + + {'threshold': 100, 'edgeitems': 20} + + + + +Methods and properties of ndarrays +================================== + +Arrays have several *properties* that can queried, and some methods that +can be called. With the exception of the flatten and transpose +operators, properties return an object that describe some feature of the +array, while the methods return a new array-like object. The ``imag``, +and ``real`` properties are included in the firmware only, when it was +compiled with complex support. + +1. `.byteswap <#.byteswap>`__ +2. `.copy <#.copy>`__ +3. `.dtype <#.dtype>`__ +4. `.flat <#.flat>`__ +5. `.flatten <#.flatten>`__ +6. `.imag\* <#.imag>`__ +7. `.itemsize <#.itemsize>`__ +8. `.real\* <#.real>`__ +9. `.reshape <#.reshape>`__ +10. `.shape <#.shape>`__ +11. `.size <#.size>`__ +12. `.T <#.transpose>`__ +13. `.tobytes <#.tobytes>`__ +14. `.tolist <#.tolist>`__ +15. `.transpose <#.transpose>`__ +16. `.sort <#.sort>`__ + +.byteswap +--------- + +``numpy`` +https://numpy.org/doc/stable/reference/generated/numpy.char.chararray.byteswap.html + +The method takes a single keyword argument, ``inplace``, with values +``True`` or ``False``, and swaps the bytes in the array. If +``inplace = False``, a new ``ndarray`` is returned, otherwise the +original values are overwritten. + +The ``frombuffer`` function is a convenient way of receiving data from +peripheral devices that work with buffers. However, it is not guaranteed +that the byte order (in other words, the *endianness*) of the peripheral +device matches that of the microcontroller. The ``.byteswap`` method +makes it possible to change the endianness of the incoming data stream. + +Obviously, byteswapping makes sense only for those cases, when a datum +occupies more than one byte, i.e., for the ``uint16``, ``int16``, and +``float`` ``dtype``\ s. When ``dtype`` is either ``uint8``, or ``int8``, +the method simply returns a view or copy of self, depending upon the +value of ``inplace``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + buffer = b'\x01\x02\x03\x04\x05\x06\x07\x08' + print('buffer: ', buffer) + + a = np.frombuffer(buffer, dtype=np.uint16) + print('a: ', a) + b = a.byteswap() + print('b: ', b) + +.. parsed-literal:: + + buffer: b'\x01\x02\x03\x04\x05\x06\x07\x08' + a: array([513, 1027, 1541, 2055], dtype=uint16) + b: array([258, 772, 1286, 1800], dtype=uint16) + + + + +.copy +----- + +The ``.copy`` method creates a new *deep copy* of an array, i.e., the +entries of the source array are *copied* into the target array. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3, 4], dtype=np.int8) + b = a.copy() + print('a: ', a) + print('='*20) + print('b: ', b) + +.. parsed-literal:: + + a: array([1, 2, 3, 4], dtype=int8) + ==================== + b: array([1, 2, 3, 4], dtype=int8) + + + + +.dtype +------ + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.dtype.htm + +The ``.dtype`` property is the ``dtype`` of an array. This can then be +used for initialising another array with the matching type. ``ulab`` +implements two versions of ``dtype``; one that is ``numpy``-like, i.e., +one, which returns a ``dtype`` object, and one that is significantly +cheaper in terms of flash space, but does not define a ``dtype`` object, +and holds a single character (number) instead. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3, 4], dtype=np.int8) + b = np.array([5, 6, 7], dtype=a.dtype) + print('a: ', a) + print('dtype of a: ', a.dtype) + print('\nb: ', b) + +.. parsed-literal:: + + a: array([1, 2, 3, 4], dtype=int8) + dtype of a: dtype('int8') + + b: array([5, 6, 7], dtype=int8) + + + + +If the ``ulab.h`` header file sets the pre-processor constant +``ULAB_HAS_DTYPE_OBJECT`` to 0 as + +.. code:: c + + #define ULAB_HAS_DTYPE_OBJECT (0) + +then the output of the previous snippet will be + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3, 4], dtype=np.int8) + b = np.array([5, 6, 7], dtype=a.dtype) + print('a: ', a) + print('dtype of a: ', a.dtype) + print('\nb: ', b) + +.. parsed-literal:: + + a: array([1, 2, 3, 4], dtype=int8) + dtype of a: 98 + + b: array([5, 6, 7], dtype=int8) + + + + +Here 98 is nothing but the ASCII value of the character ``b``, which is +the type code for signed 8-bit integers. The object definition adds +around 600 bytes to the firmware. + +.flat +----- + +numpy: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.flat.htm + +``.flat`` returns the array’s flat iterator. For one-dimensional objects +the flat iterator is equivalent to the standart iterator, while for +higher dimensional tensors, it amounts to first flattening the array, +and then iterating over it. Note, however, that the flat iterator does +not consume RAM beyond what is required for holding the position of the +iterator itself, while flattening produces a new copy. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3, 4], dtype=np.int8) + for _a in a: + print(_a) + + a = np.array([[1, 2, 3, 4], [5, 6, 7, 8]], dtype=np.int8) + print('a:\n', a) + + for _a in a: + print(_a) + + for _a in a.flat: + print(_a) + +.. parsed-literal:: + + 1 + 2 + 3 + 4 + a: + array([[1, 2, 3, 4], + [5, 6, 7, 8]], dtype=int8) + array([1, 2, 3, 4], dtype=int8) + array([5, 6, 7, 8], dtype=int8) + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + + + + +.flatten +-------- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.flatten.htm + +``.flatten`` returns the flattened array. The array can be flattened in +``C`` style (i.e., moving along the last axis in the tensor), or in +``fortran`` style (i.e., moving along the first axis in the tensor). + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3, 4], dtype=np.int8) + print("a: \t\t", a) + print("a flattened: \t", a.flatten()) + + b = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int8) + print("\nb:", b) + + print("b flattened (C): \t", b.flatten()) + print("b flattened (F): \t", b.flatten(order='F')) + +.. parsed-literal:: + + a: array([1, 2, 3, 4], dtype=int8) + a flattened: array([1, 2, 3, 4], dtype=int8) + + b: array([[1, 2, 3], + [4, 5, 6]], dtype=int8) + b flattened (C): array([1, 2, 3, 4, 5, 6], dtype=int8) + b flattened (F): array([1, 4, 2, 5, 3, 6], dtype=int8) + + + + +.imag +----- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.ndarray.imag.html + +The ``.imag`` property is defined only, if the firmware was compiled +with complex support, and returns a copy with the imaginary part of an +array. If the array is real, then the output is straight zeros with the +``dtype`` of the input. If the input is complex, the output ``dtype`` is +always ``float``, irrespective of the values. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3], dtype=np.uint16) + print("a:\t", a) + print("a.imag:\t", a.imag) + + b = np.array([1, 2+1j, 3-1j], dtype=np.complex) + print("\nb:\t", b) + print("b.imag:\t", b.imag) + +.. parsed-literal:: + + a: array([1, 2, 3], dtype=uint16) + a.imag: array([0, 0, 0], dtype=uint16) + + b: array([1.0+0.0j, 2.0+1.0j, 3.0-1.0j], dtype=complex) + b.imag: array([0.0, 1.0, -1.0], dtype=float64) + + + + +.itemsize +--------- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.ndarray.itemsize.html + +The ``.itemsize`` property is an integer with the size of elements in +the array. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3], dtype=np.int8) + print("a:\n", a) + print("itemsize of a:", a.itemsize) + + b= np.array([[1, 2], [3, 4]], dtype=np.float) + print("\nb:\n", b) + print("itemsize of b:", b.itemsize) + +.. parsed-literal:: + + a: + array([1, 2, 3], dtype=int8) + itemsize of a: 1 + + b: + array([[1.0, 2.0], + [3.0, 4.0]], dtype=float64) + itemsize of b: 8 + + + + +.real +----- + +numpy: +https://numpy.org/doc/stable/reference/generated/numpy.ndarray.real.html + +The ``.real`` property is defined only, if the firmware was compiled +with complex support, and returns a copy with the real part of an array. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3], dtype=np.uint16) + print("a:\t", a) + print("a.real:\t", a.real) + + b = np.array([1, 2+1j, 3-1j], dtype=np.complex) + print("\nb:\t", b) + print("b.real:\t", b.real) + +.. parsed-literal:: + + a: array([1, 2, 3], dtype=uint16) + a.real: array([1, 2, 3], dtype=uint16) + + b: array([1.0+0.0j, 2.0+1.0j, 3.0-1.0j], dtype=complex) + b.real: array([1.0, 2.0, 3.0], dtype=float64) + + + + +.reshape +-------- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.reshape.html + +``reshape`` re-writes the shape properties of an ``ndarray``, but the +array will not be modified in any other way. The function takes a single +2-tuple with two integers as its argument. The 2-tuple should specify +the desired number of rows and columns. If the new shape is not +consistent with the old, a ``ValueError`` exception will be raised. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]], dtype=np.uint8) + print('a (4 by 4):', a) + print('a (2 by 8):', a.reshape((2, 8))) + print('a (1 by 16):', a.reshape((1, 16))) + +.. parsed-literal:: + + a (4 by 4): array([[1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12], + [13, 14, 15, 16]], dtype=uint8) + a (2 by 8): array([[1, 2, 3, 4, 5, 6, 7, 8], + [9, 10, 11, 12, 13, 14, 15, 16]], dtype=uint8) + a (1 by 16): array([[1, 2, 3, ..., 14, 15, 16]], dtype=uint8) + + + + +.. code:: + + # code to be run in CPython + + Note that `ndarray.reshape()` can also be called by assigning to `ndarray.shape`. +.shape +------ + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.ndarray.shape.html + +The ``.shape`` property is a tuple whose elements are the length of the +array along each axis. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3, 4], dtype=np.int8) + print("a:\n", a) + print("shape of a:", a.shape) + + b= np.array([[1, 2], [3, 4]], dtype=np.int8) + print("\nb:\n", b) + print("shape of b:", b.shape) + +.. parsed-literal:: + + a: + array([1, 2, 3, 4], dtype=int8) + shape of a: (4,) + + b: + array([[1, 2], + [3, 4]], dtype=int8) + shape of b: (2, 2) + + + + +By assigning a tuple to the ``.shape`` property, the array can be +``reshape``\ d: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9]) + print('a:\n', a) + + a.shape = (3, 3) + print('\na:\n', a) + +.. parsed-literal:: + + a: + array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0], dtype=float64) + + a: + array([[1.0, 2.0, 3.0], + [4.0, 5.0, 6.0], + [7.0, 8.0, 9.0]], dtype=float64) + + + + +.size +----- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.ndarray.size.html + +The ``.size`` property is an integer specifying the number of elements +in the array. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3], dtype=np.int8) + print("a:\n", a) + print("size of a:", a.size) + + b= np.array([[1, 2], [3, 4]], dtype=np.int8) + print("\nb:\n", b) + print("size of b:", b.size) + +.. parsed-literal:: + + a: + array([1, 2, 3], dtype=int8) + size of a: 3 + + b: + array([[1, 2], + [3, 4]], dtype=int8) + size of b: 4 + + + + +.T + +The ``.T`` property of the ``ndarray`` is equivalent to +`.transpose <#.transpose>`__. + +.tobytes +-------- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.ndarray.tobytes.html + +The ``.tobytes`` method can be used for acquiring a handle of the +underlying data pointer of an array, and it returns a new ``bytearray`` +that can be fed into any method that can accep a ``bytearray``, e.g., +ADC data can be buffered into this ``bytearray``, or the ``bytearray`` +can be fed into a DAC. Since the ``bytearray`` is really nothing but the +bare data container of the array, any manipulation on the ``bytearray`` +automatically modifies the array itself. + +Note that the method raises a ``ValueError`` exception, if the array is +not dense (i.e., it has already been sliced). + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(8), dtype=np.uint8) + print('a: ', a) + b = a.tobytes() + print('b: ', b) + + # modify b + b[0] = 13 + + print('='*20) + print('b: ', b) + print('a: ', a) + +.. parsed-literal:: + + a: array([0, 1, 2, 3, 4, 5, 6, 7], dtype=uint8) + b: bytearray(b'\x00\x01\x02\x03\x04\x05\x06\x07') + ==================== + b: bytearray(b'\r\x01\x02\x03\x04\x05\x06\x07') + a: array([13, 1, 2, 3, 4, 5, 6, 7], dtype=uint8) + + + + +.tolist +------- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.ndarray.tolist.html + +The ``.tolist`` method can be used for converting the numerical array +into a (nested) ``python`` lists. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(4), dtype=np.uint8) + print('a: ', a) + b = a.tolist() + print('b: ', b) + + c = a.reshape((2, 2)) + print('='*20) + print('c: ', c) + d = c.tolist() + print('d: ', d) + +.. parsed-literal:: + + a: array([0, 1, 2, 3], dtype=uint8) + b: [0, 1, 2, 3] + ==================== + c: array([[0, 1], + [2, 3]], dtype=uint8) + d: [[0, 1], [2, 3]] + + + + +.transpose +---------- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.transpose.html + +Returns the transposed array. Only defined, if the number of maximum +dimensions is larger than 1. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]], dtype=np.uint8) + print('a:\n', a) + print('shape of a:', a.shape) + a.transpose() + print('\ntranspose of a:\n', a) + print('shape of a:', a.shape) + +.. parsed-literal:: + + a: + array([[1, 2, 3], + [4, 5, 6], + [7, 8, 9], + [10, 11, 12]], dtype=uint8) + shape of a: (4, 3) + + transpose of a: + array([[1, 4, 7, 10], + [2, 5, 8, 11], + [3, 6, 9, 12]], dtype=uint8) + shape of a: (3, 4) + + + + +The transpose of the array can also be gotten through the ``T`` +property: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.uint8) + print('a:\n', a) + print('\ntranspose of a:\n', a.T) + +.. parsed-literal:: + + a: + array([[1, 2, 3], + [4, 5, 6], + [7, 8, 9]], dtype=uint8) + + transpose of a: + array([[1, 4, 7], + [2, 5, 8], + [3, 6, 9]], dtype=uint8) + + + + +.sort +----- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.sort.html + +In-place sorting of an ``ndarray``. For a more detailed exposition, see +`sort <#sort>`__. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.uint8) + print('\na:\n', a) + a.sort(axis=0) + print('\na sorted along vertical axis:\n', a) + + a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.uint8) + a.sort(axis=1) + print('\na sorted along horizontal axis:\n', a) + + a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.uint8) + a.sort(axis=None) + print('\nflattened a sorted:\n', a) + +.. parsed-literal:: + + + a: + array([[1, 12, 3, 0], + [5, 3, 4, 1], + [9, 11, 1, 8], + [7, 10, 0, 1]], dtype=uint8) + + a sorted along vertical axis: + array([[1, 3, 0, 0], + [5, 10, 1, 1], + [7, 11, 3, 1], + [9, 12, 4, 8]], dtype=uint8) + + a sorted along horizontal axis: + array([[0, 1, 3, 12], + [1, 3, 4, 5], + [1, 8, 9, 11], + [0, 1, 7, 10]], dtype=uint8) + + flattened a sorted: + array([0, 0, 1, ..., 10, 11, 12], dtype=uint8) + + + + +Unary operators +=============== + +With the exception of ``len``, which returns a single number, all unary +operators manipulate the underlying data element-wise. + +len +--- + +This operator takes a single argument, the array, and returns either the +length of the first axis. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3, 4, 5], dtype=np.uint8) + b = np.array([range(5), range(5), range(5), range(5)], dtype=np.uint8) + + print("a:\t", a) + print("length of a: ", len(a)) + print("shape of a: ", a.shape) + print("\nb:\t", b) + print("length of b: ", len(b)) + print("shape of b: ", b.shape) + +.. parsed-literal:: + + a: array([1, 2, 3, 4, 5], dtype=uint8) + length of a: 5 + shape of a: (5,) + + b: array([[0, 1, 2, 3, 4], + [0, 1, 2, 3, 4], + [0, 1, 2, 3, 4], + [0, 1, 2, 3, 4]], dtype=uint8) + length of b: 2 + shape of b: (4, 5) + + + + +The number returned by ``len`` is also the length of the iterations, +when the array supplies the elements for an iteration (see later). + +invert +------ + +The function is defined for integer data types (``uint8``, ``int8``, +``uint16``, and ``int16``) only, takes a single argument, and returns +the element-by-element, bit-wise inverse of the array. If a ``float`` is +supplied, the function raises a ``ValueError`` exception. + +With signed integers (``int8``, and ``int16``), the results might be +unexpected, as in the example below: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([0, -1, -100], dtype=np.int8) + print("a:\t\t", a) + print("inverse of a:\t", ~a) + + a = np.array([0, 1, 254, 255], dtype=np.uint8) + print("\na:\t\t", a) + print("inverse of a:\t", ~a) + +.. parsed-literal:: + + a: array([0, -1, -100], dtype=int8) + inverse of a: array([-1, 0, 99], dtype=int8) + + a: array([0, 1, 254, 255], dtype=uint8) + inverse of a: array([255, 254, 1, 0], dtype=uint8) + + + + +abs +--- + +This function takes a single argument, and returns the +element-by-element absolute value of the array. When the data type is +unsigned (``uint8``, or ``uint16``), a copy of the array will be +returned immediately, and no calculation takes place. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([0, -1, -100], dtype=np.int8) + print("a:\t\t\t ", a) + print("absolute value of a:\t ", abs(a)) + +.. parsed-literal:: + + a: array([0, -1, -100], dtype=int8) + absolute value of a: array([0, 1, 100], dtype=int8) + + + + +neg +--- + +This operator takes a single argument, and changes the sign of each +element in the array. Unsigned values are wrapped. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([10, -1, 1], dtype=np.int8) + print("a:\t\t", a) + print("negative of a:\t", -a) + + b = np.array([0, 100, 200], dtype=np.uint8) + print("\nb:\t\t", b) + print("negative of b:\t", -b) + +.. parsed-literal:: + + a: array([10, -1, 1], dtype=int8) + negative of a: array([-10, 1, -1], dtype=int8) + + b: array([0, 100, 200], dtype=uint8) + negative of b: array([0, 156, 56], dtype=uint8) + + + + +pos +--- + +This function takes a single argument, and simply returns a copy of the +array. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([10, -1, 1], dtype=np.int8) + print("a:\t\t", a) + print("positive of a:\t", +a) + +.. parsed-literal:: + + a: array([10, -1, 1], dtype=int8) + positive of a: array([10, -1, 1], dtype=int8) + + + + +Binary operators +================ + +``ulab`` implements the ``+``, ``-``, ``*``, ``/``, ``**``, ``<``, +``>``, ``<=``, ``>=``, ``==``, ``!=``, ``+=``, ``-=``, ``*=``, ``/=``, +``**=`` binary operators that work element-wise. Broadcasting is +available, meaning that the two operands do not even have to have the +same shape. If the lengths along the respective axes are equal, or one +of them is 1, or the axis is missing, the element-wise operation can +still be carried out. A thorough explanation of broadcasting can be +found under https://numpy.org/doc/stable/user/basics.broadcasting.html. + +**WARNING**: note that relational operators (``<``, ``>``, ``<=``, +``>=``, ``==``, ``!=``) should have the ``ndarray`` on their left hand +side, when compared to scalars. This means that the following works + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3]) + print(a > 2) + +.. parsed-literal:: + + array([False, False, True], dtype=bool) + + + + +while the equivalent statement, ``2 < a``, will raise a ``TypeError`` +exception: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3]) + print(2 < a) + +.. parsed-literal:: + + + Traceback (most recent call last): + File "/dev/shm/micropython.py", line 5, in + TypeError: unsupported types for __lt__: 'int', 'ndarray' + + + +**WARNING:** ``circuitpython`` users should use the ``equal``, and +``not_equal`` operators instead of ``==``, and ``!=``. See the section +on `array comparison <#Comparison-of-arrays>`__ for details. + +Upcasting +--------- + +Binary operations require special attention, because two arrays with +different typecodes can be the operands of an operation, in which case +it is not trivial, what the typecode of the result is. This decision on +the result’s typecode is called upcasting. Since the number of typecodes +in ``ulab`` is significantly smaller than in ``numpy``, we have to +define new upcasting rules. Where possible, I followed ``numpy``\ ’s +conventions. + +``ulab`` observes the following upcasting rules: + +1. Operations on two ``ndarray``\ s of the same ``dtype`` preserve their + ``dtype``, even when the results overflow. + +2. if either of the operands is a float, the result is automatically a + float + +3. When one of the operands is a scalar, it will internally be turned + into a single-element ``ndarray`` with the *smallest* possible + ``dtype``. Thus, e.g., if the scalar is 123, it will be converted + into an array of ``dtype`` ``uint8``, while -1000 will be converted + into ``int16``. An ``mp_obj_float``, will always be promoted to + ``dtype`` ``float``. Similarly, if ``ulab`` supports complex arrays, + the result of a binary operation involving a ``complex`` array is + always complex. Other ``micropython`` types (e.g., lists, tuples, + etc.) raise a ``TypeError`` exception. + +4. + +============== =============== =========== ============ +left hand side right hand side ulab result numpy result +============== =============== =========== ============ +``uint8`` ``int8`` ``int16`` ``int16`` +``uint8`` ``int16`` ``int16`` ``int16`` +``uint8`` ``uint16`` ``uint16`` ``uint16`` +``int8`` ``int16`` ``int16`` ``int16`` +``int8`` ``uint16`` ``uint16`` ``int32`` +``uint16`` ``int16`` ``float`` ``int32`` +============== =============== =========== ============ + +Note that the last two operations are promoted to ``int32`` in +``numpy``. + +**WARNING:** Due to the lower number of available data types, the +upcasting rules of ``ulab`` are slightly different to those of +``numpy``. Watch out for this, when porting code! + +Upcasting can be seen in action in the following snippet: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3, 4], dtype=np.uint8) + b = np.array([1, 2, 3, 4], dtype=np.int8) + print("a:\t", a) + print("b:\t", b) + print("a+b:\t", a+b) + + c = np.array([1, 2, 3, 4], dtype=np.float) + print("\na:\t", a) + print("c:\t", c) + print("a*c:\t", a*c) + +.. parsed-literal:: + + a: array([1, 2, 3, 4], dtype=uint8) + b: array([1, 2, 3, 4], dtype=int8) + a+b: array([2, 4, 6, 8], dtype=int16) + + a: array([1, 2, 3, 4], dtype=uint8) + c: array([1.0, 2.0, 3.0, 4.0], dtype=float64) + a*c: array([1.0, 4.0, 9.0, 16.0], dtype=float64) + + + + +Benchmarks +---------- + +The following snippet compares the performance of binary operations to a +possible implementation in python. For the time measurement, we will +take the following snippet from the micropython manual: + +.. code:: + + # code to be run in micropython + + import utime + + def timeit(f, *args, **kwargs): + func_name = str(f).split(' ')[1] + def new_func(*args, **kwargs): + t = utime.ticks_us() + result = f(*args, **kwargs) + print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us') + return result + return new_func + +.. parsed-literal:: + + + + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + @timeit + def py_add(a, b): + return [a[i]+b[i] for i in range(1000)] + + @timeit + def py_multiply(a, b): + return [a[i]*b[i] for i in range(1000)] + + @timeit + def ulab_add(a, b): + return a + b + + @timeit + def ulab_multiply(a, b): + return a * b + + a = [0.0]*1000 + b = range(1000) + + print('python add:') + py_add(a, b) + + print('\npython multiply:') + py_multiply(a, b) + + a = np.linspace(0, 10, num=1000) + b = np.ones(1000) + + print('\nulab add:') + ulab_add(a, b) + + print('\nulab multiply:') + ulab_multiply(a, b) + +.. parsed-literal:: + + python add: + execution time: 10051 us + + python multiply: + execution time: 14175 us + + ulab add: + execution time: 222 us + + ulab multiply: + execution time: 213 us + + + +The python implementation above is not perfect, and certainly, there is +much room for improvement. However, the factor of 50 difference in +execution time is very spectacular. This is nothing but a consequence of +the fact that the ``ulab`` functions run ``C`` code, with very little +python overhead. The factor of 50 appears to be quite universal: the FFT +routine obeys similar scaling (see `Speed of FFTs <#Speed-of-FFTs>`__), +and this number came up with font rendering, too: `fast font rendering +on graphical +displays `__. + +Comparison operators +==================== + +The smaller than, greater than, smaller or equal, and greater or equal +operators return a vector of Booleans indicating the positions +(``True``), where the condition is satisfied. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3, 4, 5, 6, 7, 8], dtype=np.uint8) + print(a < 5) + +.. parsed-literal:: + + array([True, True, True, True, False, False, False, False], dtype=bool) + + + + +**WARNING**: at the moment, due to ``micropython``\ ’s implementation +details, the ``ndarray`` must be on the left hand side of the relational +operators. + +That is, while ``a < 5`` and ``5 > a`` have the same meaning, the +following code will not work: + +.. code:: + + # code to be run in micropython + + import ulab as np + + a = np.array([1, 2, 3, 4, 5, 6, 7, 8], dtype=np.uint8) + print(5 > a) + +.. parsed-literal:: + + + Traceback (most recent call last): + File "/dev/shm/micropython.py", line 5, in + TypeError: unsupported types for __gt__: 'int', 'ndarray' + + + +Iterating over arrays +===================== + +``ndarray``\ s are iterable, which means that their elements can also be +accessed as can the elements of a list, tuple, etc. If the array is +one-dimensional, the iterator returns scalars, otherwise a new +reduced-dimensional *view* is created and returned. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3, 4, 5], dtype=np.uint8) + b = np.array([range(5), range(10, 15, 1), range(20, 25, 1), range(30, 35, 1)], dtype=np.uint8) + + print("a:\t", a) + + for i, _a in enumerate(a): + print("element %d in a:"%i, _a) + + print("\nb:\t", b) + + for i, _b in enumerate(b): + print("element %d in b:"%i, _b) + +.. parsed-literal:: + + a: array([1, 2, 3, 4, 5], dtype=uint8) + element 0 in a: 1 + element 1 in a: 2 + element 2 in a: 3 + element 3 in a: 4 + element 4 in a: 5 + + b: array([[0, 1, 2, 3, 4], + [10, 11, 12, 13, 14], + [20, 21, 22, 23, 24], + [30, 31, 32, 33, 34]], dtype=uint8) + element 0 in b: array([0, 1, 2, 3, 4], dtype=uint8) + element 1 in b: array([10, 11, 12, 13, 14], dtype=uint8) + element 2 in b: array([20, 21, 22, 23, 24], dtype=uint8) + element 3 in b: array([30, 31, 32, 33, 34], dtype=uint8) + + + + +Slicing and indexing +==================== + +Views vs. copies +---------------- + +``numpy`` has a very important concept called *views*, which is a +powerful extension of ``python``\ ’s own notion of slicing. Slices are +special python objects of the form + +.. code:: python + + slice = start:end:stop + +where ``start``, ``end``, and ``stop`` are (not necessarily +non-negative) integers. Not all of these three numbers must be specified +in an index, in fact, all three of them can be missing. The interpreter +takes care of filling in the missing values. (Note that slices cannot be +defined in this way, only there, where an index is expected.) For a good +explanation on how slices work in python, you can read the stackoverflow +question +https://stackoverflow.com/questions/509211/understanding-slice-notation. + +In order to see what slicing does, let us take the string +``a = '012345679'``! We can extract every second character by creating +the slice ``::2``, which is equivalent to ``0:len(a):2``, i.e., +increments the character pointer by 2 starting from 0, and traversing +the string up to the very end. + +.. code:: + + # code to be run in CPython + + string = '0123456789' + string[::2] + + + +.. parsed-literal:: + + '02468' + + + +Now, we can do the same with numerical arrays. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(10), dtype=np.uint8) + print('a:\t', a) + + print('a[::2]:\t', a[::2]) + +.. parsed-literal:: + + a: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8) + a[::2]: array([0, 2, 4, 6, 8], dtype=uint8) + + + + +This looks similar to ``string`` above, but there is a very important +difference that is not so obvious. Namely, ``string[::2]`` produces a +partial copy of ``string``, while ``a[::2]`` only produces a *view* of +``a``. What this means is that ``a``, and ``a[::2]`` share their data, +and the only difference between the two is, how the data are read out. +In other words, internally, ``a[::2]`` has the same data pointer as +``a``. We can easily convince ourselves that this is indeed the case by +calling the `ndinfo <#The_ndinfo_function>`__ function: the *data +pointer* entry is the same in the two printouts. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(10), dtype=np.uint8) + print('a: ', a, '\n') + np.ndinfo(a) + print('\n' + '='*20) + print('a[::2]: ', a[::2], '\n') + np.ndinfo(a[::2]) + +.. parsed-literal:: + + a: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8) + + class: ndarray + shape: (10,) + strides: (1,) + itemsize: 1 + data pointer: 0x7ff6c6193220 + type: uint8 + + ==================== + a[::2]: array([0, 2, 4, 6, 8], dtype=uint8) + + class: ndarray + shape: (5,) + strides: (2,) + itemsize: 1 + data pointer: 0x7ff6c6193220 + type: uint8 + + + + +If you are still a bit confused about the meaning of *views*, the +section `Slicing and assigning to +slices <#Slicing-and-assigning-to-slices>`__ should clarify the issue. + +Indexing +-------- + +The simplest form of indexing is specifying a single integer between the +square brackets as in + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(10), dtype=np.uint8) + print("a: ", a) + print("the first, and last element of a:\n", a[0], a[-1]) + print("the second, and last but one element of a:\n", a[1], a[-2]) + +.. parsed-literal:: + + a: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8) + the first, and last element of a: + 0 9 + the second, and last but one element of a: + 1 8 + + + + +Indexing can be applied to higher-dimensional tensors, too. When the +length of the indexing sequences is smaller than the number of +dimensions, a new *view* is returned, otherwise, we get a single number. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(9), dtype=np.uint8).reshape((3, 3)) + print("a:\n", a) + print("a[0]:\n", a[0]) + print("a[1,1]: ", a[1,1]) + +.. parsed-literal:: + + a: + array([[0, 1, 2], + [3, 4, 5], + [6, 7, 8]], dtype=uint8) + a[0]: + array([[0, 1, 2]], dtype=uint8) + a[1,1]: 4 + + + + +Indices can also be a list of Booleans. By using a Boolean list, we can +select those elements of an array that satisfy a specific condition. At +the moment, such indexing is defined for row vectors only; when the rank +of the tensor is higher than 1, the function raises a +``NotImplementedError`` exception, though this will be rectified in a +future version of ``ulab``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(9), dtype=np.float) + print("a:\t", a) + print("a < 5:\t", a[a < 5]) + +.. parsed-literal:: + + a: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float) + a < 5: array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float) + + + + +Indexing with Boolean arrays can take more complicated expressions. This +is a very concise way of comparing two vectors, e.g.: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(9), dtype=np.uint8) + b = np.array([4, 4, 4, 3, 3, 3, 13, 13, 13], dtype=np.uint8) + print("a:\t", a) + print("\na**2:\t", a*a) + print("\nb:\t", b) + print("\n100*sin(b):\t", np.sin(b)*100.0) + print("\na[a*a > np.sin(b)*100.0]:\t", a[a*a > np.sin(b)*100.0]) + +.. parsed-literal:: + + a: array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8) + + a**2: array([0, 1, 4, 9, 16, 25, 36, 49, 64], dtype=uint16) + + b: array([4, 4, 4, 3, 3, 3, 13, 13, 13], dtype=uint8) + + 100*sin(b): array([-75.68024953079282, -75.68024953079282, -75.68024953079282, 14.11200080598672, 14.11200080598672, 14.11200080598672, 42.01670368266409, 42.01670368266409, 42.01670368266409], dtype=float) + + a[a*a > np.sin(b)*100.0]: array([0, 1, 2, 4, 5, 7, 8], dtype=uint8) + + + + +Boolean indices can also be used in assignments, if the array is +one-dimensional. The following example replaces the data in an array, +wherever some condition is fulfilled. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(9), dtype=np.uint8) + b = np.array(range(9)) + 12 + + print(a[b < 15]) + + a[b < 15] = 123 + print(a) + +.. parsed-literal:: + + array([0, 1, 2], dtype=uint8) + array([123, 123, 123, 3, 4, 5, 6, 7, 8], dtype=uint8) + + + + +On the right hand side of the assignment we can even have another array. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(9), dtype=np.uint8) + b = np.array(range(9)) + 12 + + print(a[b < 15], b[b < 15]) + + a[b < 15] = b[b < 15] + print(a) + +.. parsed-literal:: + + array([0, 1, 2], dtype=uint8) array([12.0, 13.0, 14.0], dtype=float) + array([12, 13, 14, 3, 4, 5, 6, 7, 8], dtype=uint8) + + + + +Slicing and assigning to slices +------------------------------- + +You can also generate sub-arrays by specifying slices as the index of an +array. Slices are special python objects of the form + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.uint8) + print('a:\n', a) + + # the first row + print('\na[0]:\n', a[0]) + + # the first two elements of the first row + print('\na[0,:2]:\n', a[0,:2]) + + # the zeroth element in each row (also known as the zeroth column) + print('\na[:,0]:\n', a[:,0]) + + # the last row + print('\na[-1]:\n', a[-1]) + + # the last two rows backwards + print('\na[-1:-3:-1]:\n', a[-1:-3:-1]) + +.. parsed-literal:: + + a: + array([[1, 2, 3], + [4, 5, 6], + [7, 8, 9]], dtype=uint8) + + a[0]: + array([[1, 2, 3]], dtype=uint8) + + a[0,:2]: + array([[1, 2]], dtype=uint8) + + a[:,0]: + array([[1], + [4], + [7]], dtype=uint8) + + a[-1]: + array([[7, 8, 9]], dtype=uint8) + + a[-1:-3:-1]: + array([[7, 8, 9], + [4, 5, 6]], dtype=uint8) + + + + +Assignment to slices can be done for the whole slice, per row, and per +column. A couple of examples should make these statements clearer: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.zeros((3, 3), dtype=np.uint8) + print('a:\n', a) + + # assigning to the whole row + a[0] = 1 + print('\na[0] = 1\n', a) + + a = np.zeros((3, 3), dtype=np.uint8) + + # assigning to a column + a[:,2] = 3.0 + print('\na[:,0]:\n', a) + +.. parsed-literal:: + + a: + array([[0, 0, 0], + [0, 0, 0], + [0, 0, 0]], dtype=uint8) + + a[0] = 1 + array([[1, 1, 1], + [0, 0, 0], + [0, 0, 0]], dtype=uint8) + + a[:,0]: + array([[0, 0, 3], + [0, 0, 3], + [0, 0, 3]], dtype=uint8) + + + + +Now, you should notice that we re-set the array ``a`` after the first +assignment. Do you care to see what happens, if we do not do that? Well, +here are the results: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.zeros((3, 3), dtype=np.uint8) + b = a[:,:] + # assign 1 to the first row + b[0] = 1 + + # assigning to the last column + b[:,2] = 3 + print('a: ', a) + +.. parsed-literal:: + + a: array([[1, 1, 3], + [0, 0, 3], + [0, 0, 3]], dtype=uint8) + + + + +Note that both assignments involved ``b``, and not ``a``, yet, when we +print out ``a``, its entries are updated. This proves our earlier +statement about the behaviour of *views*: in the statement +``b = a[:,:]`` we simply created a *view* of ``a``, and not a *deep* +copy of it, meaning that whenever we modify ``b``, we actually modify +``a``, because the underlying data container of ``a`` and ``b`` are +shared between the two object. Having a single data container for two +seemingly different objects provides an extremely powerful way of +manipulating sub-sets of numerical data. + +If you want to work on a *copy* of your data, you can use the ``.copy`` +method of the ``ndarray``. The following snippet should drive the point +home: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.zeros((3, 3), dtype=np.uint8) + b = a.copy() + + # get the address of the underlying data pointer + + np.ndinfo(a) + print() + np.ndinfo(b) + + # assign 1 to the first row of b, and do not touch a + b[0] = 1 + + print() + print('a: ', a) + print('='*20) + print('b: ', b) + +.. parsed-literal:: + + class: ndarray + shape: (3, 3) + strides: (3, 1) + itemsize: 1 + data pointer: 0x7ff737ea3220 + type: uint8 + + class: ndarray + shape: (3, 3) + strides: (3, 1) + itemsize: 1 + data pointer: 0x7ff737ea3340 + type: uint8 + + a: array([[0, 0, 0], + [0, 0, 0], + [0, 0, 0]], dtype=uint8) + ==================== + b: array([[1, 1, 1], + [0, 0, 0], + [0, 0, 0]], dtype=uint8) + + + + +The ``.copy`` method can also be applied to views: below, ``a[0]`` is a +*view* of ``a``, out of which we create a *deep copy* called ``b``. This +is a row vector now. We can then do whatever we want to with ``b``, and +that leaves ``a`` unchanged. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.zeros((3, 3), dtype=np.uint8) + b = a[0].copy() + print('b: ', b) + print('='*20) + # assign 1 to the first entry of b, and do not touch a + b[0] = 1 + print('a: ', a) + print('='*20) + print('b: ', b) + +.. parsed-literal:: + + b: array([0, 0, 0], dtype=uint8) + ==================== + a: array([[0, 0, 0], + [0, 0, 0], + [0, 0, 0]], dtype=uint8) + ==================== + b: array([1, 0, 0], dtype=uint8) + + + + +The fact that the underlying data of a view is the same as that of the +original array has another important consequence, namely, that the +creation of a view is cheap. Both in terms of RAM, and execution time. A +view is really nothing but a short header with a data array that already +exists, and is filled up. Hence, creating the view requires only the +creation of its header. This operation is fast, and uses virtually no +RAM. diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/ulab-programming.rst b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/ulab-programming.rst new file mode 100644 index 00000000..f8c81c9c --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/ulab-programming.rst @@ -0,0 +1,911 @@ + +Programming ulab +================ + +Earlier we have seen, how ``ulab``\ ’s functions and methods can be +accessed in ``micropython``. This last section of the book explains, how +these functions are implemented. By the end of this chapter, not only +would you be able to extend ``ulab``, and write your own +``numpy``-compatible functions, but through a deeper understanding of +the inner workings of the functions, you would also be able to see what +the trade-offs are at the ``python`` level. + +Code organisation +----------------- + +As mentioned earlier, the ``python`` functions are organised into +sub-modules at the C level. The C sub-modules can be found in +``./ulab/code/``. + +The ``ndarray`` object +---------------------- + +General comments +~~~~~~~~~~~~~~~~ + +``ndarrays`` are efficient containers of numerical data of the same type +(i.e., signed/unsigned chars, signed/unsigned integers or +``mp_float_t``\ s, which, depending on the platform, are either C +``float``\ s, or C ``double``\ s). Beyond storing the actual data in the +void pointer ``*array``, the type definition has eight additional +members (on top of the ``base`` type). Namely, the ``dtype``, which +tells us, how the bytes are to be interpreted. Moreover, the +``itemsize``, which stores the size of a single entry in the array, +``boolean``, an unsigned integer, which determines, whether the arrays +is to be treated as a set of Booleans, or as numerical data, ``ndim``, +the number of dimensions (``uint8_t``), ``len``, the length of the array +(the number of entries), the shape (``*size_t``), the strides +(``*int32_t``). The length is simply the product of the numbers in +``shape``. + +The type definition is as follows: + +.. code:: c + + typedef struct _ndarray_obj_t { + mp_obj_base_t base; + uint8_t dtype; + uint8_t itemsize; + uint8_t boolean; + uint8_t ndim; + size_t len; + size_t shape[ULAB_MAX_DIMS]; + int32_t strides[ULAB_MAX_DIMS]; + void *array; + } ndarray_obj_t; + +Memory layout +~~~~~~~~~~~~~ + +The values of an ``ndarray`` are stored in a contiguous segment in the +RAM. The ``ndarray`` can be dense, meaning that all numbers in the +linear memory segment belong to a linar combination of coordinates, and +it can also be sparse, i.e., some elements of the linear storage space +will be skipped, when the elements of the tensor are traversed. + +In the RAM, the position of the item +:math:`M(n_1, n_2, ..., n_{k-1}, n_k)` in a dense tensor of rank +:math:`k` is given by the linear combination + +:raw-latex:`\begin{equation} +P(n_1, n_2, ..., n_{k-1}, n_k) = n_1 s_1 + n_2 s_2 + ... + n_{k-1}s_{k-1} + n_ks_k = \sum_{i=1}^{k}n_is_i +\end{equation}` where :math:`s_i` are the strides of the tensor, defined +as + +:raw-latex:`\begin{equation} +s_i = \prod_{j=i+1}^k l_j +\end{equation}` + +where :math:`l_j` is length of the tensor along the :math:`j`\ th axis. +When the tensor is sparse (e.g., when the tensor is sliced), the strides +along a particular axis will be multiplied by a non-zero integer. If +this integer is different to :math:`\pm 1`, the linear combination above +cannot access all elements in the RAM, i.e., some numbers will be +skipped. Note that :math:`|s_1| > |s_2| > ... > |s_{k-1}| > |s_k|`, even +if the tensor is sparse. The statement is trivial for dense tensors, and +it follows from the definition of :math:`s_i`. For sparse tensors, a +slice cannot have a step larger than the shape along that axis. But for +dense tensors, :math:`s_i/s_{i+1} = l_i`. + +When creating a *view*, we simply re-calculate the ``strides``, and +re-set the ``*array`` pointer. + +Iterating over elements of a tensor +----------------------------------- + +The ``shape`` and ``strides`` members of the array tell us how we have +to move our pointer, when we want to read out the numbers. For technical +reasons that will become clear later, the numbers in ``shape`` and in +``strides`` are aligned to the right, and begin on the right hand side, +i.e., if the number of possible dimensions is ``ULAB_MAX_DIMS``, then +``shape[ULAB_MAX_DIMS-1]`` is the length of the last axis, +``shape[ULAB_MAX_DIMS-2]`` is the length of the last but one axis, and +so on. If the number of actual dimensions, ``ndim < ULAB_MAX_DIMS``, the +first ``ULAB_MAX_DIMS - ndim`` entries in ``shape`` and ``strides`` will +be equal to zero, but they could, in fact, be assigned any value, +because these will never be accessed in an operation. + +With this definition of the strides, the linear combination in +:math:`P(n_1, n_2, ..., n_{k-1}, n_k)` is a one-to-one mapping from the +space of tensor coordinates, :math:`(n_1, n_2, ..., n_{k-1}, n_k)`, and +the coordinate in the linear array, +:math:`n_1s_1 + n_2s_2 + ... + n_{k-1}s_{k-1} + n_ks_k`, i.e., no two +distinct sets of coordinates will result in the same position in the +linear array. + +Since the ``strides`` are given in terms of bytes, when we iterate over +an array, the void data pointer is usually cast to ``uint8_t``, and the +values are converted using the proper data type stored in +``ndarray->dtype``. However, there might be cases, when it makes perfect +sense to cast ``*array`` to a different type, in which case the +``strides`` have to be re-scaled by the value of ``ndarray->itemsize``. + +Iterating using the unwrapped loops +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following macro definition is taken from +`vector.h `__, +and demonstrates, how we can iterate over a single array in four +dimensions. + +.. code:: c + + #define ITERATE_VECTOR(type, array, source, sarray) do { + size_t i=0; + do { + size_t j = 0; + do { + size_t k = 0; + do { + size_t l = 0; + do { + *(array)++ = f(*((type *)(sarray))); + (sarray) += (source)->strides[ULAB_MAX_DIMS - 1]; + l++; + } while(l < (source)->shape[ULAB_MAX_DIMS-1]); + (sarray) -= (source)->strides[ULAB_MAX_DIMS - 1] * (source)->shape[ULAB_MAX_DIMS-1]; + (sarray) += (source)->strides[ULAB_MAX_DIMS - 2]; + k++; + } while(k < (source)->shape[ULAB_MAX_DIMS-2]); + (sarray) -= (source)->strides[ULAB_MAX_DIMS - 2] * (source)->shape[ULAB_MAX_DIMS-2]; + (sarray) += (source)->strides[ULAB_MAX_DIMS - 3]; + j++; + } while(j < (source)->shape[ULAB_MAX_DIMS-3]); + (sarray) -= (source)->strides[ULAB_MAX_DIMS - 3] * (source)->shape[ULAB_MAX_DIMS-3]; + (sarray) += (source)->strides[ULAB_MAX_DIMS - 4]; + i++; + } while(i < (source)->shape[ULAB_MAX_DIMS-4]); + } while(0) + +We start with the innermost loop, the one recursing ``l``. ``array`` is +already of type ``mp_float_t``, while the source array, ``sarray``, has +been cast to ``uint8_t`` in the calling function. The numbers contained +in ``sarray`` have to be read out in the proper type dictated by +``ndarray->dtype``. This is what happens in the statement +``*((type *)(sarray))``, and this number is then fed into the function +``f``. Vectorised mathematical functions produce *dense* arrays, and for +this reason, we can simply advance the ``array`` pointer. + +The advancing of the ``sarray`` pointer is a bit more involving: first, +in the innermost loop, we simply move forward by the amount given by the +last stride, which is ``(source)->strides[ULAB_MAX_DIMS - 1]``, because +the ``shape`` and the ``strides`` are aligned to the right. We move the +pointer as many times as given by ``(source)->shape[ULAB_MAX_DIMS-1]``, +which is the length of the very last axis. Hence the the structure of +the loop + +.. code:: c + + size_t l = 0; + do { + ... + l++; + } while(l < (source)->shape[ULAB_MAX_DIMS-1]); + +Once we have exhausted the last axis, we have to re-wind the pointer, +and advance it by an amount given by the last but one stride. Keep in +mind that in the the innermost loop we moved our pointer +``(source)->shape[ULAB_MAX_DIMS-1]`` times by +``(source)->strides[ULAB_MAX_DIMS - 1]``, i.e., we re-wind it by moving +it backwards by +``(source)->strides[ULAB_MAX_DIMS - 1] * (source)->shape[ULAB_MAX_DIMS-1]``. +In the next step, we move forward by +``(source)->strides[ULAB_MAX_DIMS - 2]``, which is the last but one +stride. + +.. code:: c + + (sarray) -= (source)->strides[ULAB_MAX_DIMS - 1] * (source)->shape[ULAB_MAX_DIMS-1]; + (sarray) += (source)->strides[ULAB_MAX_DIMS - 2]; + +This pattern must be repeated for each axis of the array, and this is +how we arrive at the four nested loops listed above. + +Re-winding arrays by means of a function +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In addition to un-wrapping the iteration loops by means of macros, there +is another way of traversing all elements of a tensor: we note that, +since :math:`|s_1| > |s_2| > ... > |s_{k-1}| > |s_k|`, +:math:`P(n1, n2, ..., n_{k-1}, n_k)` changes most slowly in the last +coordinate. Hence, if we start from the very beginning, (:math:`n_i = 0` +for all :math:`i`), and walk along the linear RAM segment, we increment +the value of :math:`n_k` as long as :math:`n_k < l_k`. Once +:math:`n_k = l_k`, we have to reset :math:`n_k` to 0, and increment +:math:`n_{k-1}` by one. After each such round, :math:`n_{k-1}` will be +incremented by one, as long as :math:`n_{k-1} < l_{k-1}`. Once +:math:`n_{k-1} = l_{k-1}`, we reset both :math:`n_k`, and +:math:`n_{k-1}` to 0, and increment :math:`n_{k-2}` by one. + +Rewinding the arrays in this way is implemented in the function +``ndarray_rewind_array`` in +`ndarray.c `__. + +.. code:: c + + void ndarray_rewind_array(uint8_t ndim, uint8_t *array, size_t *shape, int32_t *strides, size_t *coords) { + // resets the data pointer of a single array, whenever an axis is full + // since we always iterate over the very last axis, we have to keep track of + // the last ndim-2 axes only + array -= shape[ULAB_MAX_DIMS - 1] * strides[ULAB_MAX_DIMS - 1]; + array += strides[ULAB_MAX_DIMS - 2]; + for(uint8_t i=1; i < ndim-1; i++) { + coords[ULAB_MAX_DIMS - 1 - i] += 1; + if(coords[ULAB_MAX_DIMS - 1 - i] == shape[ULAB_MAX_DIMS - 1 - i]) { // we are at a dimension boundary + array -= shape[ULAB_MAX_DIMS - 1 - i] * strides[ULAB_MAX_DIMS - 1 - i]; + array += strides[ULAB_MAX_DIMS - 2 - i]; + coords[ULAB_MAX_DIMS - 1 - i] = 0; + coords[ULAB_MAX_DIMS - 2 - i] += 1; + } else { // coordinates can change only, if the last coordinate changes + return; + } + } + } + +and the function would be called as in the snippet below. Note that the +innermost loop is factored out, so that we can save the ``if(...)`` +statement for the last axis. + +.. code:: c + + size_t *coords = ndarray_new_coords(results->ndim); + for(size_t i=0; i < results->len/results->shape[ULAB_MAX_DIMS -1]; i++) { + size_t l = 0; + do { + ... + l++; + } while(l < results->shape[ULAB_MAX_DIMS - 1]); + ndarray_rewind_array(results->ndim, array, results->shape, strides, coords); + } while(0) + +The advantage of this method is that the implementation is independent +of the number of dimensions: the iteration requires more or less the +same flash space for 2 dimensions as for 22. However, the price we have +to pay for this convenience is the extra function call. + +Iterating over two ndarrays simultaneously: broadcasting +-------------------------------------------------------- + +Whenever we invoke a binary operator, call a function with two arguments +of ``ndarray`` type, or assign something to an ``ndarray``, we have to +iterate over two views at the same time. The task is trivial, if the two +``ndarray``\ s in question have the same shape (but not necessarily the +same set of strides), because in this case, we can still iterate in the +same loop. All that happens is that we move two data pointers in sync. + +The problem becomes a bit more involving, when the shapes of the two +``ndarray``\ s are not identical. For such cases, ``numpy`` defines +so-called broadcasting, which boils down to two rules. + +1. The shapes in the tensor with lower rank has to be prepended with + axes of size 1 till the two ranks become equal. +2. Along all axes the two tensors should have the same size, or one of + the sizes must be 1. + +If, after applying the first rule the second is not satisfied, the two +``ndarray``\ s cannot be broadcast together. + +Now, let us suppose that we have two compatible ``ndarray``\ s, i.e., +after applying the first rule, the second is satisfied. How do we +iterate over the elements in the tensors? + +We should recall, what exactly we do, when iterating over a single +array: normally, we move the data pointer by the last stride, except, +when we arrive at a dimension boundary (when the last axis is +exhausted). At that point, we move the pointer by an amount dictated by +the strides. And this is the key: *dictated by the strides*. Now, if we +have two arrays that are originally not compatible, we define new +strides for them, and use these in the iteration. With that, we are back +to the case, where we had two compatible arrays. + +Now, let us look at the second broadcasting rule: if the two arrays have +the same size, we take both ``ndarray``\ s’ strides along that axis. If, +on the other hand, one of the ``ndarray``\ s is of length 1 along one of +its axes, we set the corresponding strides to 0. This will ensure that +that data pointer is not moved, when we iterate over both ``ndarray``\ s +at the same time. + +Thus, in order to implement broadcasting, we first have to check, +whether the two above-mentioned rules can be satisfied, and if so, we +have to find the two new sets strides. + +The ``ndarray_can_broadcast`` function from +`ndarray.c `__ +takes two ``ndarray``\ s, and returns ``true``, if the two arrays can be +broadcast together. At the same time, it also calculates new strides for +the two arrays, so that they can be iterated over at the same time. + +.. code:: c + + bool ndarray_can_broadcast(ndarray_obj_t *lhs, ndarray_obj_t *rhs, uint8_t *ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) { + // returns True or False, depending on, whether the two arrays can be broadcast together + // numpy's broadcasting rules are as follows: + // + // 1. the two shapes are either equal + // 2. one of the shapes is 1 + memset(lstrides, 0, sizeof(size_t)*ULAB_MAX_DIMS); + memset(rstrides, 0, sizeof(size_t)*ULAB_MAX_DIMS); + lstrides[ULAB_MAX_DIMS - 1] = lhs->strides[ULAB_MAX_DIMS - 1]; + rstrides[ULAB_MAX_DIMS - 1] = rhs->strides[ULAB_MAX_DIMS - 1]; + for(uint8_t i=ULAB_MAX_DIMS; i > 0; i--) { + if((lhs->shape[i-1] == rhs->shape[i-1]) || (lhs->shape[i-1] == 0) || (lhs->shape[i-1] == 1) || + (rhs->shape[i-1] == 0) || (rhs->shape[i-1] == 1)) { + shape[i-1] = MAX(lhs->shape[i-1], rhs->shape[i-1]); + if(shape[i-1] > 0) (*ndim)++; + if(lhs->shape[i-1] < 2) { + lstrides[i-1] = 0; + } else { + lstrides[i-1] = lhs->strides[i-1]; + } + if(rhs->shape[i-1] < 2) { + rstrides[i-1] = 0; + } else { + rstrides[i-1] = rhs->strides[i-1]; + } + } else { + return false; + } + } + return true; + } + +A good example of how the function would be called can be found in +`vector.c `__, +in the ``vector_arctan2`` function: + +.. code:: c + + mp_obj_t vector_arctan2(mp_obj_t y, mp_obj_t x) { + ... + uint8_t ndim = 0; + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + int32_t *xstrides = m_new(int32_t, ULAB_MAX_DIMS); + int32_t *ystrides = m_new(int32_t, ULAB_MAX_DIMS); + if(!ndarray_can_broadcast(ndarray_x, ndarray_y, &ndim, shape, xstrides, ystrides)) { + mp_raise_ValueError(translate("operands could not be broadcast together")); + m_del(size_t, shape, ULAB_MAX_DIMS); + m_del(int32_t, xstrides, ULAB_MAX_DIMS); + m_del(int32_t, ystrides, ULAB_MAX_DIMS); + } + + uint8_t *xarray = (uint8_t *)ndarray_x->array; + uint8_t *yarray = (uint8_t *)ndarray_y->array; + + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT); + mp_float_t *rarray = (mp_float_t *)results->array; + ... + +After the new strides have been calculated, the iteration loop is +identical to what we discussed in the previous section. + +Contracting an ``ndarray`` +-------------------------- + +There are many operations that reduce the number of dimensions of an +``ndarray`` by 1, i.e., that remove an axis from the tensor. The drill +is the same as before, with the exception that first we have to remove +the ``strides`` and ``shape`` that corresponds to the axis along which +we intend to contract. The ``numerical_reduce_axes`` function from +`numerical.c `__ +does that. + +.. code:: c + + static void numerical_reduce_axes(ndarray_obj_t *ndarray, int8_t axis, size_t *shape, int32_t *strides) { + // removes the values corresponding to a single axis from the shape and strides array + uint8_t index = ULAB_MAX_DIMS - ndarray->ndim + axis; + if((ndarray->ndim == 1) && (axis == 0)) { + index = 0; + shape[ULAB_MAX_DIMS - 1] = 0; + return; + } + for(uint8_t i = ULAB_MAX_DIMS - 1; i > 0; i--) { + if(i > index) { + shape[i] = ndarray->shape[i]; + strides[i] = ndarray->strides[i]; + } else { + shape[i] = ndarray->shape[i-1]; + strides[i] = ndarray->strides[i-1]; + } + } + } + +Once the reduced ``strides`` and ``shape`` are known, we place the axis +in question in the innermost loop, and wrap it with the loops, whose +coordinates are in the ``strides``, and ``shape`` arrays. The +``RUN_STD`` macro from +`numerical.h `__ +is a good example. The macro is expanded in the +``numerical_sum_mean_std_ndarray`` function. + +.. code:: c + + static mp_obj_t numerical_sum_mean_std_ndarray(ndarray_obj_t *ndarray, mp_obj_t axis, uint8_t optype, size_t ddof) { + uint8_t *array = (uint8_t *)ndarray->array; + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + memset(shape, 0, sizeof(size_t)*ULAB_MAX_DIMS); + int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); + memset(strides, 0, sizeof(uint32_t)*ULAB_MAX_DIMS); + + int8_t ax = mp_obj_get_int(axis); + if(ax < 0) ax += ndarray->ndim; + if((ax < 0) || (ax > ndarray->ndim - 1)) { + mp_raise_ValueError(translate("index out of range")); + } + numerical_reduce_axes(ndarray, ax, shape, strides); + uint8_t index = ULAB_MAX_DIMS - ndarray->ndim + ax; + ndarray_obj_t *results = NULL; + uint8_t *rarray = NULL; + ... + +Here is the macro for the three-dimensional case: + +.. code:: c + + #define RUN_STD(ndarray, type, array, results, r, shape, strides, index, div) do { + size_t k = 0; + do { + size_t l = 0; + do { + RUN_STD1((ndarray), type, (array), (results), (r), (index), (div)); + (array) -= (ndarray)->strides[(index)] * (ndarray)->shape[(index)]; + (array) += (strides)[ULAB_MAX_DIMS - 1]; + l++; + } while(l < (shape)[ULAB_MAX_DIMS - 1]); + (array) -= (strides)[ULAB_MAX_DIMS - 2] * (shape)[ULAB_MAX_DIMS-2]; + (array) += (strides)[ULAB_MAX_DIMS - 3]; + k++; + } while(k < (shape)[ULAB_MAX_DIMS - 2]); + } while(0) + +In ``RUN_STD``, we simply move our pointers; the calculation itself +happens in the ``RUN_STD1`` macro below. (Note that this is the +implementation of the numerically stable Welford algorithm.) + +.. code:: c + + #define RUN_STD1(ndarray, type, array, results, r, index, div) + ({ + mp_float_t M, m, S = 0.0, s = 0.0; + M = m = *(mp_float_t *)((type *)(array)); + for(size_t i=1; i < (ndarray)->shape[(index)]; i++) { + (array) += (ndarray)->strides[(index)]; + mp_float_t value = *(mp_float_t *)((type *)(array)); + m = M + (value - M) / (mp_float_t)i; + s = S + (value - M) * (value - m); + M = m; + S = s; + } + (array) += (ndarray)->strides[(index)]; + *(r)++ = MICROPY_FLOAT_C_FUN(sqrt)((ndarray)->shape[(index)] * s / (div)); + }) + +Upcasting +--------- + +When in an operation the ``dtype``\ s of two arrays are different, the +result’s ``dtype`` will be decided by the following upcasting rules: + +1. Operations with two ``ndarray``\ s of the same ``dtype`` preserve + their ``dtype``, even when the results overflow. + +2. if either of the operands is a float, the result automatically + becomes a float + +3. otherwise + + - ``uint8`` + ``int8`` => ``int16``, + + - ``uint8`` + ``int16`` => ``int16`` + + - ``uint8`` + ``uint16`` => ``uint16`` + + - ``int8`` + ``int16`` => ``int16`` + + - ``int8`` + ``uint16`` => ``uint16`` (in numpy, the result is a + ``int32``) + + - ``uint16`` + ``int16`` => ``float`` (in numpy, the result is a + ``int32``) + +4. When one operand of a binary operation is a generic scalar + ``micropython`` variable, i.e., ``mp_obj_int``, or ``mp_obj_float``, + it will be converted to a linear array of length 1, and with the + smallest ``dtype`` that can accommodate the variable in question. + After that the broadcasting rules apply, as described in the section + `Iterating over two ndarrays simultaneously: + broadcasting <#Iterating_over_two_ndarrays_simultaneously:_broadcasting>`__ + +Upcasting is resolved in place, wherever it is required. Notable +examples can be found in +`ndarray_operators.c `__ + +Slicing and indexing +-------------------- + +An ``ndarray`` can be indexed with three types of objects: integer +scalars, slices, and another ``ndarray``, whose elements are either +integer scalars, or Booleans. Since slice and integer indices can be +thought of as modifications of the ``strides``, these indices return a +view of the ``ndarray``. This statement does not hold for ``ndarray`` +indices, and therefore, the return a copy of the array. + +Extending ulab +-------------- + +The ``user`` module is disabled by default, as can be seen from the last +couple of lines of +`ulab.h `__ + +.. code:: c + + // user-defined module + #ifndef ULAB_USER_MODULE + #define ULAB_USER_MODULE (0) + #endif + +The module contains a very simple function, ``user_dummy``, and this +function is bound to the module itself. In other words, even if the +module is enabled, one has to ``import``: + +.. code:: python + + + import ulab + from ulab import user + + user.dummy_function(2.5) + +which should just return 5.0. Even if ``numpy``-compatibility is +required (i.e., if most functions are bound at the top level to ``ulab`` +directly), having to ``import`` the module has a great advantage. +Namely, only the +`user.h `__ +and +`user.c `__ +files have to be modified, thus it should be relatively straightforward +to update your local copy from +`github `__. + +Now, let us see, how we can add a more meaningful function. + +Creating a new ndarray +---------------------- + +In the `General comments <#General_comments>`__ sections we have seen +the type definition of an ``ndarray``. This structure can be generated +by means of a couple of functions listed in +`ndarray.c `__. + +ndarray_new_ndarray +~~~~~~~~~~~~~~~~~~~ + +The ``ndarray_new_ndarray`` functions is called by all other +array-generating functions. It takes the number of dimensions, ``ndim``, +a ``uint8_t``, the ``shape``, a pointer to ``size_t``, the ``strides``, +a pointer to ``int32_t``, and ``dtype``, another ``uint8_t`` as its +arguments, and returns a new array with all entries initialised to 0. + +Assuming that ``ULAB_MAX_DIMS > 2``, a new dense array of dimension 3, +of ``shape`` (3, 4, 5), of ``strides`` (1000, 200, 10), and ``dtype`` +``uint16_t`` can be generated by the following instructions + +.. code:: c + + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + shape[ULAB_MAX_DIMS - 1] = 5; + shape[ULAB_MAX_DIMS - 2] = 4; + shape[ULAB_MAX_DIMS - 3] = 3; + + int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); + strides[ULAB_MAX_DIMS - 1] = 10; + strides[ULAB_MAX_DIMS - 2] = 200; + strides[ULAB_MAX_DIMS - 3] = 1000; + + ndarray_obj_t *new_ndarray = ndarray_new_ndarray(3, shape, strides, NDARRAY_UINT16); + +ndarray_new_dense_ndarray +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The functions simply calculates the ``strides`` from the ``shape``, and +calls ``ndarray_new_ndarray``. Assuming that ``ULAB_MAX_DIMS > 2``, a +new dense array of dimension 3, of ``shape`` (3, 4, 5), and ``dtype`` +``mp_float_t`` can be generated by the following instructions + +.. code:: c + + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + shape[ULAB_MAX_DIMS - 1] = 5; + shape[ULAB_MAX_DIMS - 2] = 4; + shape[ULAB_MAX_DIMS - 3] = 3; + + ndarray_obj_t *new_ndarray = ndarray_new_dense_ndarray(3, shape, NDARRAY_FLOAT); + +ndarray_new_linear_array +~~~~~~~~~~~~~~~~~~~~~~~~ + +Since the dimensions of a linear array are known (1), the +``ndarray_new_linear_array`` takes the ``length``, a ``size_t``, and the +``dtype``, an ``uint8_t``. Internally, ``ndarray_new_linear_array`` +generates the ``shape`` array, and calls ``ndarray_new_dense_array`` +with ``ndim = 1``. + +A linear array of length 100, and ``dtype`` ``uint8`` could be created +by the function call + +.. code:: c + + ndarray_obj_t *new_ndarray = ndarray_new_linear_array(100, NDARRAY_UINT8) + +ndarray_new_ndarray_from_tuple +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This function takes a ``tuple``, which should hold the lengths of the +axes (in other words, the ``shape``), and the ``dtype``, and calls +internally ``ndarray_new_dense_array``. A new ``ndarray`` can be +generated by calling + +.. code:: c + + ndarray_obj_t *new_ndarray = ndarray_new_ndarray_from_tuple(shape, NDARRAY_FLOAT); + +where ``shape`` is a tuple. + +ndarray_new_view +~~~~~~~~~~~~~~~~ + +This function crates a *view*, and takes the source, an ``ndarray``, the +number of dimensions, an ``uint8_t``, the ``shape``, a pointer to +``size_t``, the ``strides``, a pointer to ``int32_t``, and the offset, +an ``int32_t`` as arguments. The offset is the number of bytes by which +the void ``array`` pointer is shifted. E.g., the ``python`` statement + +.. code:: python + + a = np.array([0, 1, 2, 3, 4, 5], dtype=uint8) + b = a[1::2] + +produces the array + +.. code:: python + + array([1, 3, 5], dtype=uint8) + +which holds its data at position ``x0 + 1``, if ``a``\ ’s pointer is at +``x0``. In this particular case, the offset is 1. + +The array ``b`` from the example above could be generated as + +.. code:: c + + size_t *shape = m_new(size_t, ULAB_MAX_DIMS); + shape[ULAB_MAX_DIMS - 1] = 3; + + int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS); + strides[ULAB_MAX_DIMS - 1] = 2; + + int32_t offset = 1; + uint8_t ndim = 1; + + ndarray_obj_t *new_ndarray = ndarray_new_view(ndarray_a, ndim, shape, strides, offset); + +ndarray_copy_array +~~~~~~~~~~~~~~~~~~ + +The ``ndarray_copy_array`` function can be used for copying the contents +of an array. Note that the target array has to be created beforehand. +E.g., a one-to-one copy can be gotten by + +.. code:: c + + ndarray_obj_t *new_ndarray = ndarray_new_ndarray(source->ndim, source->shape, source->strides, source->dtype); + ndarray_copy_array(source, new_ndarray); + +Note that the function cannot be used for forcing type conversion, i.e., +the input and output types must be identical, because the function +simply calls the ``memcpy`` function. On the other hand, the input and +output ``strides`` do not necessarily have to be equal. + +ndarray_copy_view +~~~~~~~~~~~~~~~~~ + +The ``ndarray_obj_t *new_ndarray = ...`` instruction can be saved by +calling the ``ndarray_copy_view`` function with the single ``source`` +argument. + +Accessing data in the ndarray +----------------------------- + +Having seen, how arrays can be generated and copied, it is time to look +at how the data in an ``ndarray`` can be accessed and modified. + +For starters, let us suppose that the object in question comes from the +user (i.e., via the ``micropython`` interface), First, we have to +acquire a pointer to the ``ndarray`` by calling + +.. code:: c + + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(object_in); + +If it is not clear, whether the object is an ``ndarray`` (e.g., if we +want to write a function that can take ``ndarray``\ s, and other +iterables as its argument), we find this out by evaluating + +.. code:: c + + mp_obj_is_type(object_in, &ulab_ndarray_type) + +which should return ``true``. Once the pointer is at our disposal, we +can get a pointer to the underlying numerical array as discussed +earlier, i.e., + +.. code:: c + + uint8_t *array = (uint8_t *)ndarray->array; + +If you need to find out the ``dtype`` of the array, you can get it by +accessing the ``dtype`` member of the ``ndarray``, i.e., + +.. code:: c + + ndarray->dtype + +should be equal to ``B``, ``b``, ``H``, ``h``, or ``f``. The size of a +single item is stored in the ``itemsize`` member. This number should be +equal to 1, if the ``dtype`` is ``B``, or ``b``, 2, if the ``dtype`` is +``H``, or ``h``, 4, if the ``dtype`` is ``f``, and 8 for ``d``. + +Boilerplate +----------- + +In the next section, we will construct a function that generates the +element-wise square of a dense array, otherwise, raises a ``TypeError`` +exception. Dense arrays can easily be iterated over, since we do not +have to care about the ``shape`` and the ``strides``. If the array is +sparse, the section `Iterating over elements of a +tensor <#Iterating-over-elements-of-a-tensor>`__ should contain hints as +to how the iteration can be implemented. + +The function is listed under +`user.c `__. +The ``user`` module is bound to ``ulab`` in +`ulab.c `__ +in the lines + +.. code:: c + + #if ULAB_USER_MODULE + { MP_ROM_QSTR(MP_QSTR_user), MP_ROM_PTR(&ulab_user_module) }, + #endif + +which assumes that at the very end of +`ulab.h `__ +the + +.. code:: c + + // user-defined module + #ifndef ULAB_USER_MODULE + #define ULAB_USER_MODULE (1) + #endif + +constant has been set to 1. After compilation, you can call a particular +``user`` function in ``python`` by importing the module first, i.e., + +.. code:: python + + from ulab import numpy as np + from ulab import user + + user.some_function(...) + +This separation of user-defined functions from the rest of the code +ensures that the integrity of the main module and all its functions are +always preserved. Even in case of a catastrophic failure, you can +exclude the ``user`` module, and start over. + +And now the function: + +.. code:: c + + static mp_obj_t user_square(mp_obj_t arg) { + // the function takes a single dense ndarray, and calculates the + // element-wise square of its entries + + // raise a TypeError exception, if the input is not an ndarray + if(!mp_obj_is_type(arg, &ulab_ndarray_type)) { + mp_raise_TypeError(translate("input must be an ndarray")); + } + ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(arg); + + // make sure that the input is a dense array + if(!ndarray_is_dense(ndarray)) { + mp_raise_TypeError(translate("input must be a dense ndarray")); + } + + // if the input is a dense array, create `results` with the same number of + // dimensions, shape, and dtype + ndarray_obj_t *results = ndarray_new_dense_ndarray(ndarray->ndim, ndarray->shape, ndarray->dtype); + + // since in a dense array the iteration over the elements is trivial, we + // can cast the data arrays ndarray->array and results->array to the actual type + if(ndarray->dtype == NDARRAY_UINT8) { + uint8_t *array = (uint8_t *)ndarray->array; + uint8_t *rarray = (uint8_t *)results->array; + for(size_t i=0; i < ndarray->len; i++, array++) { + *rarray++ = (*array) * (*array); + } + } else if(ndarray->dtype == NDARRAY_INT8) { + int8_t *array = (int8_t *)ndarray->array; + int8_t *rarray = (int8_t *)results->array; + for(size_t i=0; i < ndarray->len; i++, array++) { + *rarray++ = (*array) * (*array); + } + } else if(ndarray->dtype == NDARRAY_UINT16) { + uint16_t *array = (uint16_t *)ndarray->array; + uint16_t *rarray = (uint16_t *)results->array; + for(size_t i=0; i < ndarray->len; i++, array++) { + *rarray++ = (*array) * (*array); + } + } else if(ndarray->dtype == NDARRAY_INT16) { + int16_t *array = (int16_t *)ndarray->array; + int16_t *rarray = (int16_t *)results->array; + for(size_t i=0; i < ndarray->len; i++, array++) { + *rarray++ = (*array) * (*array); + } + } else { // if we end up here, the dtype is NDARRAY_FLOAT + mp_float_t *array = (mp_float_t *)ndarray->array; + mp_float_t *rarray = (mp_float_t *)results->array; + for(size_t i=0; i < ndarray->len; i++, array++) { + *rarray++ = (*array) * (*array); + } + } + // at the end, return a micropython object + return MP_OBJ_FROM_PTR(results); + } + +To summarise, the steps for *implementing* a function are + +1. If necessary, inspect the type of the input object, which is always a + ``mp_obj_t`` object +2. If the input is an ``ndarray_obj_t``, acquire a pointer to it by + calling ``ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(arg);`` +3. Create a new array, or modify the existing one; get a pointer to the + data by calling ``uint8_t *array = (uint8_t *)ndarray->array;``, or + something equivalent +4. Once the new data have been calculated, return a ``micropython`` + object by calling ``MP_OBJ_FROM_PTR(...)``. + +The listing above contains the implementation of the function, but as +such, it cannot be called from ``python``: it still has to be bound to +the name space. This we do by first defining a function object in + +.. code:: c + + MP_DEFINE_CONST_FUN_OBJ_1(user_square_obj, user_square); + +``micropython`` defines a number of ``MP_DEFINE_CONST_FUN_OBJ_N`` macros +in +`obj.h `__. +``N`` is always the number of arguments the function takes. We had a +function definition ``static mp_obj_t user_square(mp_obj_t arg)``, i.e., +we dealt with a single argument. + +Finally, we have to bind this function object in the globals table of +the ``user`` module: + +.. code:: c + + STATIC const mp_rom_map_elem_t ulab_user_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_user) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_square), (mp_obj_t)&user_square_obj }, + }; + +Thus, the three steps required for the definition of a user-defined +function are + +1. The low-level implementation of the function itself +2. The definition of a function object by calling + MP_DEFINE_CONST_FUN_OBJ_N() +3. Binding this function object to the namespace in the + ``ulab_user_globals_table[]`` diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/ulab-tricks.rst b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/ulab-tricks.rst new file mode 100644 index 00000000..4c3802ba --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/ulab-tricks.rst @@ -0,0 +1,268 @@ + +Tricks +====== + +This section of the book discusses a couple of tricks that can be +exploited to either speed up computations, or save on RAM. However, +there is probably no silver bullet, and you have to evaluate your code +in terms of execution speed (if the execution is time critical), or RAM +used. You should also keep in mind that, if a particular code snippet is +optimised on some hardware, there is no guarantee that on another piece +of hardware, you will get similar improvements. Hardware implementations +are vastly different. Some microcontrollers do not even have an FPU, so +you should not be surprised that you get significantly different +benchmarks. Just to underline this statement, you can study the +`collection of benchmarks `__. + +Use an ``ndarray``, if you can +------------------------------ + +Many functions in ``ulab`` are implemented in a universal fashion, +meaning that both generic ``micropython`` iterables, and ``ndarray``\ s +can be passed as an argument. E.g., both + +.. code:: python + + from ulab import numpy as np + + np.sum([1, 2, 3, 4, 5]) + +and + +.. code:: python + + from ulab import numpy as np + + a = np.array([1, 2, 3, 4, 5]) + np.sum(a) + +will return the ``micropython`` variable 15 as the result. Still, +``np.sum(a)`` is evaluated significantly faster, because in +``np.sum([1, 2, 3, 4, 5])``, the interpreter has to fetch 5 +``micropython`` variables, convert them to ``float``, and sum the +values, while the C type of ``a`` is known, thus the interpreter can +invoke a single ``for`` loop for the evaluation of the ``sum``. In the +``for`` loop, there are no function calls, the iteration simply walks +through the pointer holding the values of ``a``, and adds the values to +an accumulator. If the array ``a`` is already available, then you can +gain a factor of 3 in speed by calling ``sum`` on the array, instead of +using the list. Compared to the python implementation of the same +functionality, the speed-up is around 40 (again, this might depend on +the hardware). + +On the other hand, if the array is not available, then there is not much +point in converting the list to an ``ndarray`` and passing that to the +function. In fact, you should expect a slow-down: the constructor has to +iterate over the list elements, and has to convert them to a numerical +type. On top of that, it also has to reserve RAM for the ``ndarray``. + +Use a reasonable ``dtype`` +-------------------------- + +Just as in ``numpy``, the default ``dtype`` is ``float``. But this does +not mean that that is the most suitable one in all scenarios. If data +are streamed from an 8-bit ADC, and you only want to know the maximum, +or the sum, then it is quite reasonable to use ``uint8`` for the +``dtype``. Storing the same data in ``float`` array would cost 4 or 8 +times as much RAM, with absolutely no gain. Do not rely on the default +value of the constructor’s keyword argument, and choose one that fits! + +Beware the axis! +---------------- + +Whenever ``ulab`` iterates over multi-dimensional arrays, the outermost +loop is the first axis, then the second axis, and so on. E.g., when the +``sum`` of + +.. code:: python + + a = array([[1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12]], dtype=uint8) + +is being calculated, first the data pointer walks along ``[1, 2, 3, 4]`` +(innermost loop, last axis), then is moved back to the position, where 5 +is stored (this is the nesting loop), and traverses ``[5, 6, 7, 8]``, +and so on. Moving the pointer back to 5 is more expensive, than moving +it along an axis, because the position of 5 has to be calculated, +whereas moving from 5 to 6 is simply an addition to the address. Thus, +while the matrix + +.. code:: python + + b = array([[1, 5, 9], + [2, 6, 10], + [3, 7, 11], + [4, 8, 12]], dtype=uint8) + +holds the same data as ``a``, the summation over the entries in ``b`` is +slower, because the pointer has to be re-wound three times, as opposed +to twice in ``a``. For small matrices the savings are not significant, +but you would definitely notice the difference, if you had + +:: + + a = array(range(2000)).reshape((2, 1000)) + b = array(range(2000)).reshape((1000, 2)) + +The moral is that, in order to improve on the execution speed, whenever +possible, you should try to make the last axis the longest. As a side +note, ``numpy`` can re-arrange its loops, and puts the longest axis in +the innermost loop. This is why the longest axis is sometimes referred +to as the fast axis. In ``ulab``, the order of the axes is fixed. + +Reduce the number of artifacts +------------------------------ + +Before showing a real-life example, let us suppose that we want to +interpolate uniformly sampled data, and the absolute magnitude is not +really important, we only care about the ratios between neighbouring +value. One way of achieving this is calling the ``interp`` functions. +However, we could just as well work with slices. + +.. code:: + + # code to be run in CPython + + a = array([0, 10, 2, 20, 4], dtype=np.uint8) + b = np.zeros(9, dtype=np.uint8) + + b[::2] = 2 * a + b[1::2] = a[:-1] + a[1:] + + b //= 2 + b + + + +.. parsed-literal:: + + array([ 0, 5, 10, 6, 2, 11, 20, 12, 4], dtype=uint8) + + + +``b`` now has values from ``a`` at every even position, and interpolates +the values on every odd position. If only the relative magnitudes are +important, then we can even save the division by 2, and we end up with + +.. code:: + + # code to be run in CPython + + a = array([0, 10, 2, 20, 4], dtype=np.uint8) + b = np.zeros(9, dtype=np.uint8) + + b[::2] = 2 * a + b[1::2] = a[:-1] + a[1:] + + b + + + +.. parsed-literal:: + + array([ 0, 10, 20, 12, 4, 22, 40, 24, 8], dtype=uint8) + + + +Importantly, we managed to keep the results in the smaller ``dtype``, +``uint8``. Now, while the two assignments above are terse and pythonic, +the code is not the most efficient: the right hand sides are compound +statements, generating intermediate results. To store them, RAM has to +be allocated. This takes time, and leads to memory fragmentation. Better +is to write out the assignments in 4 instructions: + +.. code:: + + # code to be run in CPython + + b = np.zeros(9, dtype=np.uint8) + + b[::2] = a + b[::2] += a + b[1::2] = a[:-1] + b[1::2] += a[1:] + + b + + + +.. parsed-literal:: + + array([ 0, 10, 20, 12, 4, 22, 40, 24, 8], dtype=uint8) + + + +The results are the same, but no extra RAM is allocated, except for the +views ``a[:-1]``, and ``a[1:]``, but those had to be created even in the +origin implementation. + +Upscaling images +~~~~~~~~~~~~~~~~ + +And now the example: there are low-resolution thermal cameras out there. +Low resolution might mean 8 by 8 pixels. Such a small number of pixels +is just not reasonable to plot, no matter how small the display is. If +you want to make the camera image a bit more pleasing, you can upscale +(stretch) it in both dimensions. This can be done exactly as we +up-scaled the linear array: + +.. code:: + + # code to be run in CPython + + b = np.zeros((15, 15), dtype=np.uint8) + + b[1::2,::2] = a[:-1,:] + b[1::2,::2] += a[1:, :] + b[1::2,::2] //= 2 + b[::,1::2] = a[::,:-1:2] + b[::,1::2] += a[::,2::2] + b[::,1::2] //= 2 +Up-scaling by larger numbers can be done in a similar fashion, you +simply have more assignments. + +There are cases, when one cannot do away with the intermediate results. +Two prominent cases are the ``where`` function, and indexing by means of +a Boolean array. E.g., in + +.. code:: + + # code to be run in CPython + + a = array([1, 2, 3, 4, 5]) + b = a[a < 4] + b + + + +.. parsed-literal:: + + array([1, 2, 3]) + + + +the expression ``a < 4`` produces the Boolean array, + +.. code:: + + # code to be run in CPython + + a < 4 + + + +.. parsed-literal:: + + array([ True, True, True, False, False]) + + + +If you repeatedly have such conditions in a loop, you might have to +peridically call the garbage collector to remove the Boolean arrays that +are used only once. + +.. code:: + + # code to be run in CPython + diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/ulab-utils.rst b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/ulab-utils.rst new file mode 100644 index 00000000..82476bad --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/manual/source/ulab-utils.rst @@ -0,0 +1,212 @@ + +ulab utilities +============== + +There might be cases, when the format of your data does not conform to +``ulab``, i.e., there is no obvious way to map the data to any of the +five supported ``dtype``\ s. A trivial example is an ADC or microphone +signal with 32-bit resolution. For such cases, ``ulab`` defines the +``utils`` module, which, at the moment, has four functions that are not +``numpy`` compatible, but which should ease interfacing ``ndarray``\ s +to peripheral devices. + +The ``utils`` module can be enabled by setting the +``ULAB_HAS_UTILS_MODULE`` constant to 1 in +`ulab.h `__: + +.. code:: c + + #ifndef ULAB_HAS_UTILS_MODULE + #define ULAB_HAS_UTILS_MODULE (1) + #endif + +This still does not compile any functions into the firmware. You can add +a function by setting the corresponding pre-processor constant to 1. +E.g., + +.. code:: c + + #ifndef ULAB_UTILS_HAS_FROM_INT16_BUFFER + #define ULAB_UTILS_HAS_FROM_INT16_BUFFER (1) + #endif + +from_int32_buffer, from_uint32_buffer +------------------------------------- + +With the help of ``utils.from_int32_buffer``, and +``utils.from_uint32_buffer``, it is possible to convert 32-bit integer +buffers to ``ndarrays`` of float type. These functions have a syntax +similar to ``numpy.frombuffer``; they support the ``count=-1``, and +``offset=0`` keyword arguments. However, in addition, they also accept +``out=None``, and ``byteswap=False``. + +Here is an example without keyword arguments + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + from ulab import utils + + a = bytearray([1, 1, 0, 0, 0, 0, 0, 255]) + print('a: ', a) + print() + print('unsigned integers: ', utils.from_uint32_buffer(a)) + + b = bytearray([1, 1, 0, 0, 0, 0, 0, 255]) + print('\nb: ', b) + print() + print('signed integers: ', utils.from_int32_buffer(b)) + +.. parsed-literal:: + + a: bytearray(b'\x01\x01\x00\x00\x00\x00\x00\xff') + + unsigned integers: array([257.0, 4278190080.000001], dtype=float64) + + b: bytearray(b'\x01\x01\x00\x00\x00\x00\x00\xff') + + signed integers: array([257.0, -16777216.0], dtype=float64) + + + + +The meaning of ``count``, and ``offset`` is similar to that in +``numpy.frombuffer``. ``count`` is the number of floats that will be +converted, while ``offset`` would discard the first ``offset`` number of +bytes from the buffer before the conversion. + +In the example above, repeated calls to either of the functions returns +a new ``ndarray``. You can save RAM by supplying the ``out`` keyword +argument with a pre-defined ``ndarray`` of sufficient size, in which +case the results will be inserted into the ``ndarray``. If the ``dtype`` +of ``out`` is not ``float``, a ``TypeError`` exception will be raised. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + from ulab import utils + + a = np.array([1, 2], dtype=np.float) + b = bytearray([1, 0, 1, 0, 0, 1, 0, 1]) + print('b: ', b) + utils.from_uint32_buffer(b, out=a) + print('a: ', a) + +.. parsed-literal:: + + b: bytearray(b'\x01\x00\x01\x00\x00\x01\x00\x01') + a: array([65537.0, 16777472.0], dtype=float64) + + + + +Finally, since there is no guarantee that the endianness of a particular +peripheral device supplying the buffer is the same as that of the +microcontroller, ``from_(u)intbuffer`` allows a conversion via the +``byteswap`` keyword argument. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + from ulab import utils + + a = bytearray([1, 0, 0, 0, 0, 0, 0, 1]) + print('a: ', a) + print('buffer without byteswapping: ', utils.from_uint32_buffer(a)) + print('buffer with byteswapping: ', utils.from_uint32_buffer(a, byteswap=True)) + +.. parsed-literal:: + + a: bytearray(b'\x01\x00\x00\x00\x00\x00\x00\x01') + buffer without byteswapping: array([1.0, 16777216.0], dtype=float64) + buffer with byteswapping: array([16777216.0, 1.0], dtype=float64) + + + + +from_int16_buffer, from_uint16_buffer +------------------------------------- + +These two functions are identical to ``utils.from_int32_buffer``, and +``utils.from_uint32_buffer``, with the exception that they convert +16-bit integers to floating point ``ndarray``\ s. + +spectrogram +----------- + +In addition to the Fourier transform and its inverse, ``ulab`` also +sports a function called ``spectrogram``, which returns the absolute +value of the Fourier transform, also known as the power spectrum. This +could be used to find the dominant spectral component in a time series. +The arguments are treated in the same way as in ``fft``, and ``ifft``. +This means that, if the firmware was compiled with complex support, the +input can also be a complex array. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + from ulab import utils as utils + + x = np.linspace(0, 10, num=1024) + y = np.sin(x) + + a = utils.spectrogram(y) + + print('original vector:\n', y) + print('\nspectrum:\n', a) + +.. parsed-literal:: + + original vector: + array([0.0, 0.009775015390171337, 0.01954909674625918, ..., -0.5275140569487312, -0.5357931822978732, -0.5440211108893697], dtype=float64) + + spectrum: + array([187.8635087634579, 315.3112063607119, 347.8814873399374, ..., 84.45888934298905, 347.8814873399374, 315.3112063607118], dtype=float64) + + + + +As such, ``spectrogram`` is really just a shorthand for +``np.sqrt(a*a + b*b)``, however, it saves significant amounts of RAM: +the expression ``a*a + b*b`` has to allocate memory for ``a*a``, +``b*b``, and finally, their sum. In contrast, ``spectrogram`` calculates +the spectrum internally, and stores it in the memory segment that was +reserved for the real part of the Fourier transform. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + from ulab import utils as utils + + x = np.linspace(0, 10, num=1024) + y = np.sin(x) + + a, b = np.fft.fft(y) + + print('\nspectrum calculated the hard way:\n', np.sqrt(a*a + b*b)) + + a = utils.spectrogram(y) + + print('\nspectrum calculated the lazy way:\n', a) + +.. parsed-literal:: + + + spectrum calculated the hard way: + array([187.8635087634579, 315.3112063607119, 347.8814873399374, ..., 84.45888934298905, 347.8814873399374, 315.3112063607118], dtype=float64) + + spectrum calculated the lazy way: + array([187.8635087634579, 315.3112063607119, 347.8814873399374, ..., 84.45888934298905, 347.8814873399374, 315.3112063607118], dtype=float64) + + + diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/numpy-fft.ipynb b/components/3rd_party/omv/omv/modules/ulab/docs/numpy-fft.ipynb new file mode 100644 index 00000000..803c9239 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/numpy-fft.ipynb @@ -0,0 +1,546 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-01T09:27:13.438054Z", + "start_time": "2020-05-01T09:27:13.191491Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Notebook magic" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-07T18:24:48.499467Z", + "start_time": "2022-01-07T18:24:48.488004Z" + } + }, + "outputs": [], + "source": [ + "from IPython.core.magic import Magics, magics_class, line_cell_magic\n", + "from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n", + "from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n", + "import subprocess\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "ExecuteTime": { + "end_time": "2020-07-23T20:31:25.296014Z", + "start_time": "2020-07-23T20:31:25.265937Z" + } + }, + "outputs": [], + "source": [ + "@magics_class\n", + "class PyboardMagic(Magics):\n", + " @cell_magic\n", + " @magic_arguments()\n", + " @argument('-skip')\n", + " @argument('-unix')\n", + " @argument('-pyboard')\n", + " @argument('-file')\n", + " @argument('-data')\n", + " @argument('-time')\n", + " @argument('-memory')\n", + " def micropython(self, line='', cell=None):\n", + " args = parse_argstring(self.micropython, line)\n", + " if args.skip: # doesn't care about the cell's content\n", + " print('skipped execution')\n", + " return None # do not parse the rest\n", + " if args.unix: # tests the code on the unix port. Note that this works on unix only\n", + " with open('/dev/shm/micropython.py', 'w') as fout:\n", + " fout.write(cell)\n", + " proc = subprocess.Popen([\"../../micropython/ports/unix/micropython\", \"/dev/shm/micropython.py\"], \n", + " stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " print(proc.stdout.read().decode(\"utf-8\"))\n", + " print(proc.stderr.read().decode(\"utf-8\"))\n", + " return None\n", + " if args.file: # can be used to copy the cell content onto the pyboard's flash\n", + " spaces = \" \"\n", + " try:\n", + " with open(args.file, 'w') as fout:\n", + " fout.write(cell.replace('\\t', spaces))\n", + " printf('written cell to {}'.format(args.file))\n", + " except:\n", + " print('Failed to write to disc!')\n", + " return None # do not parse the rest\n", + " if args.data: # can be used to load data from the pyboard directly into kernel space\n", + " message = pyb.exec(cell)\n", + " if len(message) == 0:\n", + " print('pyboard >>>')\n", + " else:\n", + " print(message.decode('utf-8'))\n", + " # register new variable in user namespace\n", + " self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n", + " \n", + " if args.time: # measures the time of executions\n", + " pyb.exec('import utime')\n", + " message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n", + " \"\\nprint('execution time: {:d} us'.format(delta))\")\n", + " print(message.decode('utf-8'))\n", + " \n", + " if args.memory: # prints out memory information \n", + " message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n", + " print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n", + " message = pyb.exec(cell)\n", + " print(\">>> \", message.decode('utf-8'))\n", + " message = pyb.exec('print(mem_info())')\n", + " print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n", + "\n", + " if args.pyboard:\n", + " message = pyb.exec(cell)\n", + " print(message.decode('utf-8'))\n", + "\n", + "ip = get_ipython()\n", + "ip.register_magics(PyboardMagic)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pyboard" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:35.126401Z", + "start_time": "2020-05-07T07:35:35.105824Z" + } + }, + "outputs": [], + "source": [ + "import pyboard\n", + "pyb = pyboard.Pyboard('/dev/ttyACM0')\n", + "pyb.enter_raw_repl()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:11:18.145548Z", + "start_time": "2020-05-19T19:11:18.137468Z" + } + }, + "outputs": [], + "source": [ + "pyb.exit_raw_repl()\n", + "pyb.close()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:38.725924Z", + "start_time": "2020-05-07T07:35:38.645488Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "import ulab as np\n", + "\n", + "def timeit(n=1000):\n", + " def wrapper(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " run_times = np.zeros(n, dtype=np.uint16)\n", + " for i in range(n):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n", + " print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n", + " print('\\tbest: %d us'%np.min(run_times))\n", + " print('\\tworst: %d us'%np.max(run_times))\n", + " print('\\taverage: %d us'%np.mean(run_times))\n", + " print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n", + " return result\n", + " return new_func\n", + " return wrapper\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__END_OF_DEFS__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# numpy.fft\n", + "\n", + "Functions related to Fourier transforms can be called by prepending them with `numpy.fft.`. The module defines the following two functions:\n", + "\n", + "1. [numpy.fft.fft](#fft)\n", + "1. [numpy.fft.ifft](#ifft)\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.fft.ifft.html\n", + "\n", + "## fft\n", + "\n", + "Since `ulab`'s `ndarray` does not support complex numbers, the invocation of the Fourier transform differs from that in `numpy`. In `numpy`, you can simply pass an array or iterable to the function, and it will be treated as a complex array:" + ] + }, + { + "cell_type": "code", + "execution_count": 341, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-17T17:33:38.487729Z", + "start_time": "2019-10-17T17:33:38.473515Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([20.+0.j, 0.+0.j, -4.+4.j, 0.+0.j, -4.+0.j, 0.+0.j, -4.-4.j,\n", + " 0.+0.j])" + ] + }, + "execution_count": 341, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fft.fft([1, 2, 3, 4, 1, 2, 3, 4])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**WARNING:** The array returned is also complex, i.e., the real and imaginary components are cast together. In `ulab`, the real and imaginary parts are treated separately: you have to pass two `ndarray`s to the function, although, the second argument is optional, in which case the imaginary part is assumed to be zero.\n", + "\n", + "**WARNING:** The function, as opposed to `numpy`, returns a 2-tuple, whose elements are two `ndarray`s, holding the real and imaginary parts of the transform separately. " + ] + }, + { + "cell_type": "code", + "execution_count": 114, + "metadata": { + "ExecuteTime": { + "end_time": "2020-02-16T18:38:07.294862Z", + "start_time": "2020-02-16T18:38:07.233842Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "real part:\t array([5119.996, -5.004663, -5.004798, ..., -5.005482, -5.005643, -5.006577], dtype=float)\r\n", + "\r\n", + "imaginary part:\t array([0.0, 1631.333, 815.659, ..., -543.764, -815.6588, -1631.333], dtype=float)\r\n", + "\r\n", + "real part:\t array([5119.996, -5.004663, -5.004798, ..., -5.005482, -5.005643, -5.006577], dtype=float)\r\n", + "\r\n", + "imaginary part:\t array([0.0, 1631.333, 815.659, ..., -543.764, -815.6588, -1631.333], dtype=float)\r\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "x = np.linspace(0, 10, num=1024)\n", + "y = np.sin(x)\n", + "z = np.zeros(len(x))\n", + "\n", + "a, b = np.fft.fft(x)\n", + "print('real part:\\t', a)\n", + "print('\\nimaginary part:\\t', b)\n", + "\n", + "c, d = np.fft.fft(x, z)\n", + "print('\\nreal part:\\t', c)\n", + "print('\\nimaginary part:\\t', d)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ulab with complex support\n", + "\n", + "If the `ULAB_SUPPORTS_COMPLEX`, and `ULAB_FFT_IS_NUMPY_COMPATIBLE` pre-processor constants are set to 1 in [ulab.h](https://github.com/v923z/micropython-ulab/blob/master/code/ulab.h) as \n", + "\n", + "```c\n", + "// Adds support for complex ndarrays\n", + "#ifndef ULAB_SUPPORTS_COMPLEX\n", + "#define ULAB_SUPPORTS_COMPLEX (1)\n", + "#endif\n", + "```\n", + "\n", + "```c\n", + "#ifndef ULAB_FFT_IS_NUMPY_COMPATIBLE\n", + "#define ULAB_FFT_IS_NUMPY_COMPATIBLE (1)\n", + "#endif\n", + "```\n", + "then the FFT routine will behave in a `numpy`-compatible way: the single input array can either be real, in which case the imaginary part is assumed to be zero, or complex. The output is also complex. \n", + "\n", + "While `numpy`-compatibility might be a desired feature, it has one side effect, namely, the FFT routine consumes approx. 50% more RAM. The reason for this lies in the implementation details." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ifft\n", + "\n", + "The above-mentioned rules apply to the inverse Fourier transform. The inverse is also normalised by `N`, the number of elements, as is customary in `numpy`. With the normalisation, we can ascertain that the inverse of the transform is equal to the original array." + ] + }, + { + "cell_type": "code", + "execution_count": 459, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-19T13:08:17.647416Z", + "start_time": "2019-10-19T13:08:17.597456Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "original vector:\t array([0.0, 0.009775016, 0.0195491, ..., -0.5275068, -0.5357859, -0.5440139], dtype=float)\n", + "\n", + "real part of inverse:\t array([-2.980232e-08, 0.0097754, 0.0195494, ..., -0.5275064, -0.5357857, -0.5440133], dtype=float)\n", + "\n", + "imaginary part of inverse:\t array([-2.980232e-08, -1.451171e-07, 3.693752e-08, ..., 6.44871e-08, 9.34986e-08, 2.18336e-07], dtype=float)\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "x = np.linspace(0, 10, num=1024)\n", + "y = np.sin(x)\n", + "\n", + "a, b = np.fft.fft(y)\n", + "\n", + "print('original vector:\\t', y)\n", + "\n", + "y, z = np.fft.ifft(a, b)\n", + "# the real part should be equal to y\n", + "print('\\nreal part of inverse:\\t', y)\n", + "# the imaginary part should be equal to zero\n", + "print('\\nimaginary part of inverse:\\t', z)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that unlike in `numpy`, the length of the array on which the Fourier transform is carried out must be a power of 2. If this is not the case, the function raises a `ValueError` exception." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ulab with complex support\n", + "\n", + "The `fft.ifft` function can also be made `numpy`-compatible by setting the `ULAB_SUPPORTS_COMPLEX`, and `ULAB_FFT_IS_NUMPY_COMPATIBLE` pre-processor constants to 1." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Computation and storage costs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### RAM\n", + "\n", + "The FFT routine of `ulab` calculates the transform in place. This means that beyond reserving space for the two `ndarray`s that will be returned (the computation uses these two as intermediate storage space), only a handful of temporary variables, all floats or 32-bit integers, are required. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Speed of FFTs\n", + "\n", + "A comment on the speed: a 1024-point transform implemented in python would cost around 90 ms, and 13 ms in assembly, if the code runs on the pyboard, v.1.1. You can gain a factor of four by moving to the D series \n", + "https://github.com/peterhinch/micropython-fourier/blob/master/README.md#8-performance. " + ] + }, + { + "cell_type": "code", + "execution_count": 494, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-19T13:25:40.540913Z", + "start_time": "2019-10-19T13:25:40.509598Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "execution time: 1985 us\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "x = np.linspace(0, 10, num=1024)\n", + "y = np.sin(x)\n", + "\n", + "@timeit\n", + "def np_fft(y):\n", + " return np.fft.fft(y)\n", + "\n", + "a, b = np_fft(y)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The C implementation runs in less than 2 ms on the pyboard (we have just measured that), and has been reported to run in under 0.8 ms on the D series board. That is an improvement of at least a factor of four. " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/numpy-functions.ipynb b/components/3rd_party/omv/omv/modules/ulab/docs/numpy-functions.ipynb new file mode 100644 index 00000000..c57225f9 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/numpy-functions.ipynb @@ -0,0 +1,2846 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2021-02-13T08:28:06.727371Z", + "start_time": "2021-02-13T08:28:04.925338Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Notebook magic" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "ExecuteTime": { + "end_time": "2022-02-01T17:37:25.505687Z", + "start_time": "2022-02-01T17:37:25.493850Z" + } + }, + "outputs": [], + "source": [ + "from IPython.core.magic import Magics, magics_class, line_cell_magic\n", + "from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n", + "from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n", + "import subprocess\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "ExecuteTime": { + "end_time": "2022-02-01T17:37:25.717714Z", + "start_time": "2022-02-01T17:37:25.532299Z" + } + }, + "outputs": [], + "source": [ + "@magics_class\n", + "class PyboardMagic(Magics):\n", + " @cell_magic\n", + " @magic_arguments()\n", + " @argument('-skip')\n", + " @argument('-unix')\n", + " @argument('-pyboard')\n", + " @argument('-file')\n", + " @argument('-data')\n", + " @argument('-time')\n", + " @argument('-memory')\n", + " def micropython(self, line='', cell=None):\n", + " args = parse_argstring(self.micropython, line)\n", + " if args.skip: # doesn't care about the cell's content\n", + " print('skipped execution')\n", + " return None # do not parse the rest\n", + " if args.unix: # tests the code on the unix port. Note that this works on unix only\n", + " with open('/dev/shm/micropython.py', 'w') as fout:\n", + " fout.write(cell)\n", + " proc = subprocess.Popen([\"../micropython/ports/unix/micropython-2\", \"/dev/shm/micropython.py\"], \n", + " stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " print(proc.stdout.read().decode(\"utf-8\"))\n", + " print(proc.stderr.read().decode(\"utf-8\"))\n", + " return None\n", + " if args.file: # can be used to copy the cell content onto the pyboard's flash\n", + " spaces = \" \"\n", + " try:\n", + " with open(args.file, 'w') as fout:\n", + " fout.write(cell.replace('\\t', spaces))\n", + " printf('written cell to {}'.format(args.file))\n", + " except:\n", + " print('Failed to write to disc!')\n", + " return None # do not parse the rest\n", + " if args.data: # can be used to load data from the pyboard directly into kernel space\n", + " message = pyb.exec(cell)\n", + " if len(message) == 0:\n", + " print('pyboard >>>')\n", + " else:\n", + " print(message.decode('utf-8'))\n", + " # register new variable in user namespace\n", + " self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n", + " \n", + " if args.time: # measures the time of executions\n", + " pyb.exec('import utime')\n", + " message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n", + " \"\\nprint('execution time: {:d} us'.format(delta))\")\n", + " print(message.decode('utf-8'))\n", + " \n", + " if args.memory: # prints out memory information \n", + " message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n", + " print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n", + " message = pyb.exec(cell)\n", + " print(\">>> \", message.decode('utf-8'))\n", + " message = pyb.exec('print(mem_info())')\n", + " print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n", + "\n", + " if args.pyboard:\n", + " message = pyb.exec(cell)\n", + " print(message.decode('utf-8'))\n", + "\n", + "ip = get_ipython()\n", + "ip.register_magics(PyboardMagic)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pyboard" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:35.126401Z", + "start_time": "2020-05-07T07:35:35.105824Z" + } + }, + "outputs": [], + "source": [ + "import pyboard\n", + "pyb = pyboard.Pyboard('/dev/ttyACM0')\n", + "pyb.enter_raw_repl()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:11:18.145548Z", + "start_time": "2020-05-19T19:11:18.137468Z" + } + }, + "outputs": [], + "source": [ + "pyb.exit_raw_repl()\n", + "pyb.close()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:38.725924Z", + "start_time": "2020-05-07T07:35:38.645488Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "import ulab as np\n", + "\n", + "def timeit(n=1000):\n", + " def wrapper(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " run_times = np.zeros(n, dtype=np.uint16)\n", + " for i in range(n):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n", + " print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n", + " print('\\tbest: %d us'%np.min(run_times))\n", + " print('\\tworst: %d us'%np.max(run_times))\n", + " print('\\taverage: %d us'%np.mean(run_times))\n", + " print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n", + " return result\n", + " return new_func\n", + " return wrapper\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__END_OF_DEFS__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Numpy functions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This section of the manual discusses those functions that were adapted from `numpy`. Starred functions accept complex arrays as arguments, if the firmware was compiled with complex support.\n", + "\n", + "1. [numpy.all*](#all)\n", + "1. [numpy.any*](#any)\n", + "1. [numpy.argmax](#argmax)\n", + "1. [numpy.argmin](#argmin)\n", + "1. [numpy.argsort](#argsort)\n", + "1. [numpy.asarray*](#asarray)\n", + "1. [numpy.clip](#clip)\n", + "1. [numpy.compress*](#compress)\n", + "1. [numpy.conjugate*](#conjugate)\n", + "1. [numpy.convolve*](#convolve)\n", + "1. [numpy.delete](#delete)\n", + "1. [numpy.diff](#diff)\n", + "1. [numpy.dot](#dot)\n", + "1. [numpy.equal](#equal)\n", + "1. [numpy.flip*](#flip)\n", + "1. [numpy.imag*](#imag)\n", + "1. [numpy.interp](#interp)\n", + "1. [numpy.isfinite](#isfinite)\n", + "1. [numpy.isinf](#isinf)\n", + "1. [numpy.load](#load)\n", + "1. [numpy.loadtxt](#loadtxt)\n", + "1. [numpy.max](#max)\n", + "1. [numpy.maximum](#maximum)\n", + "1. [numpy.mean](#mean)\n", + "1. [numpy.median](#median)\n", + "1. [numpy.min](#min)\n", + "1. [numpy.minimum](#minimum)\n", + "1. [numpy.nozero](#nonzero)\n", + "1. [numpy.not_equal](#equal)\n", + "1. [numpy.polyfit](#polyfit)\n", + "1. [numpy.polyval](#polyval)\n", + "1. [numpy.real*](#real)\n", + "1. [numpy.roll](#roll)\n", + "1. [numpy.save](#save)\n", + "1. [numpy.savetxt](#savetxt)\n", + "1. [numpy.size](#size)\n", + "1. [numpy.sort](#sort)\n", + "1. [numpy.sort_complex*](#sort_complex)\n", + "1. [numpy.std](#std)\n", + "1. [numpy.sum](#sum)\n", + "1. [numpy.trace](#trace)\n", + "1. [numpy.trapz](#trapz)\n", + "1. [numpy.where](#where)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## all\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.all.html\n", + "\n", + "The function takes one positional, and one keyword argument, the `axis`, with a default value of `None`, and tests, whether *all* array elements along the given axis evaluate to `True`. If the keyword argument is `None`, the flattened array is inspected. \n", + "\n", + "Elements of an array evaluate to `True`, if they are not equal to zero, or the Boolean `False`. The return value if a Boolean `ndarray`.\n", + "\n", + "If the firmware was compiled with complex support, the function can accept complex arrays." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "ExecuteTime": { + "end_time": "2021-02-08T16:54:57.117630Z", + "start_time": "2021-02-08T16:54:57.105337Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "a:\n", + " array([[0.0, 1.0, 2.0, 3.0],\n", + " [4.0, 5.0, 6.0, 7.0],\n", + " [8.0, 9.0, 10.0, 11.0]], dtype=float64)\n", + "\n", + "all of the flattened array:\n", + " False\n", + "\n", + "all of a along 0th axis:\n", + " array([False, True, True, True], dtype=bool)\n", + "\n", + "all of a along 1st axis:\n", + " array([False, True, True], dtype=bool)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(12)).reshape((3, 4))\n", + "\n", + "print('\\na:\\n', a)\n", + "\n", + "b = np.all(a)\n", + "print('\\nall of the flattened array:\\n', b)\n", + "\n", + "c = np.all(a, axis=0)\n", + "print('\\nall of a along 0th axis:\\n', c)\n", + "\n", + "d = np.all(a, axis=1)\n", + "print('\\nall of a along 1st axis:\\n', d)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## any\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.any.html\n", + "\n", + "The function takes one positional, and one keyword argument, the `axis`, with a default value of `None`, and tests, whether *any* array element along the given axis evaluates to `True`. If the keyword argument is `None`, the flattened array is inspected. \n", + "\n", + "Elements of an array evaluate to `True`, if they are not equal to zero, or the Boolean `False`. The return value if a Boolean `ndarray`.\n", + "\n", + "If the firmware was compiled with complex support, the function can accept complex arrays." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "ExecuteTime": { + "end_time": "2021-02-08T16:54:14.704132Z", + "start_time": "2021-02-08T16:54:14.693700Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "a:\n", + " array([[0.0, 1.0, 2.0, 3.0],\n", + " [4.0, 5.0, 6.0, 7.0],\n", + " [8.0, 9.0, 10.0, 11.0]], dtype=float64)\n", + "\n", + "any of the flattened array:\n", + " True\n", + "\n", + "any of a along 0th axis:\n", + " array([True, True, True, True], dtype=bool)\n", + "\n", + "any of a along 1st axis:\n", + " array([True, True, True], dtype=bool)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(12)).reshape((3, 4))\n", + "\n", + "print('\\na:\\n', a)\n", + "\n", + "b = np.any(a)\n", + "print('\\nany of the flattened array:\\n', b)\n", + "\n", + "c = np.any(a, axis=0)\n", + "print('\\nany of a along 0th axis:\\n', c)\n", + "\n", + "d = np.any(a, axis=1)\n", + "print('\\nany of a along 1st axis:\\n', d)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## argmax\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmax.html\n", + "\n", + "See [numpy.max](#max)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## argmin\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmin.html\n", + "\n", + "See [numpy.max](#max)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## argsort\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.argsort.html\n", + "\n", + "Similarly to [sort](#sort), `argsort` takes a positional, and a keyword argument, and returns an unsigned short index array of type `ndarray` with the same dimensions as the input, or, if `axis=None`, as a row vector with length equal to the number of elements in the input (i.e., the flattened array). The indices in the output sort the input in ascending order. The routine in `argsort` is the same as in `sort`, therefore, the comments on computational expenses (time and RAM) also apply. In particular, since no copy of the original data is required, virtually no RAM beyond the output array is used. \n", + "\n", + "Since the underlying container of the output array is of type `uint16_t`, neither of the output dimensions should be larger than 65535. If that happens to be the case, the function will bail out with a `ValueError`." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:33:33.292717Z", + "start_time": "2021-01-13T16:33:33.280144Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "a:\n", + " array([[1.0, 12.0, 3.0, 0.0],\n", + " [5.0, 3.0, 4.0, 1.0],\n", + " [9.0, 11.0, 1.0, 8.0],\n", + " [7.0, 10.0, 0.0, 1.0]], dtype=float64)\n", + "\n", + "a sorted along vertical axis:\n", + " array([[0, 1, 3, 0],\n", + " [1, 3, 2, 1],\n", + " [3, 2, 0, 3],\n", + " [2, 0, 1, 2]], dtype=uint16)\n", + "\n", + "a sorted along horizontal axis:\n", + " array([[3, 0, 2, 1],\n", + " [3, 1, 2, 0],\n", + " [2, 3, 0, 1],\n", + " [2, 3, 0, 1]], dtype=uint16)\n", + "\n", + "Traceback (most recent call last):\n", + " File \"/dev/shm/micropython.py\", line 12, in \n", + "NotImplementedError: argsort is not implemented for flattened arrays\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.float)\n", + "print('\\na:\\n', a)\n", + "b = np.argsort(a, axis=0)\n", + "print('\\na sorted along vertical axis:\\n', b)\n", + "\n", + "c = np.argsort(a, axis=1)\n", + "print('\\na sorted along horizontal axis:\\n', c)\n", + "\n", + "c = np.argsort(a, axis=None)\n", + "print('\\nflattened a sorted:\\n', c)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since during the sorting, only the indices are shuffled, `argsort` does not modify the input array, as one can verify this by the following example:" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:34:48.446211Z", + "start_time": "2021-01-13T16:34:48.424276Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "a:\n", + " array([0, 5, 1, 3, 2, 4], dtype=uint8)\n", + "\n", + "sorting indices:\n", + " array([0, 2, 4, 3, 5, 1], dtype=uint16)\n", + "\n", + "the original array:\n", + " array([0, 5, 1, 3, 2, 4], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([0, 5, 1, 3, 2, 4], dtype=np.uint8)\n", + "print('\\na:\\n', a)\n", + "b = np.argsort(a, axis=0)\n", + "print('\\nsorting indices:\\n', b)\n", + "print('\\nthe original array:\\n', a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## asarray\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.asarray.html\n", + "\n", + "The function takes a single positional argument, and an optional keyword argument, `dtype`, with a default value of `None`. \n", + "\n", + "If the positional argument is an `ndarray`, and its `dtypes` is identical to the value of the keyword argument, or if the keyword argument is `None`, then the positional argument is simply returned. If the original `dtype`, and the value of the keyword argument are different, then a copy is returned, with appropriate `dtype` conversion. \n", + "\n", + "If the positional argument is an iterable, then the function is simply an alias for `array`." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-14T20:05:22.017031Z", + "start_time": "2022-01-14T20:05:22.002463Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8)\n", + "b:array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8)\n", + "a == b: True\n", + "\n", + "c:array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=int8)\n", + "a == c: False\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(9), dtype=np.uint8)\n", + "b = np.asarray(a)\n", + "c = np.asarray(a, dtype=np.int8)\n", + "print('a:{}'.format(a))\n", + "print('b:{}'.format(b))\n", + "print('a == b: {}'.format(a is b))\n", + "\n", + "print('\\nc:{}'.format(c))\n", + "print('a == c: {}'.format(a is c))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## clip\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.clip.html\n", + "\n", + "Clips an array, i.e., values that are outside of an interval are clipped to the interval edges. The function is equivalent to `maximum(a_min, minimum(a, a_max))` broadcasting takes place exactly as in [minimum](#minimum). If the arrays are of different `dtype`, the output is upcast as in [Binary operators](#Binary-operators)." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T13:22:14.147310Z", + "start_time": "2021-01-08T13:22:14.123961Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8)\n", + "clipped:\t array([3, 3, 3, 3, 4, 5, 6, 7, 7], dtype=uint8)\n", + "\n", + "a:\t\t array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8)\n", + "b:\t\t array([3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0], dtype=float64)\n", + "clipped:\t array([3.0, 3.0, 3.0, 3.0, 4.0, 5.0, 6.0, 7.0, 7.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(9), dtype=np.uint8)\n", + "print('a:\\t\\t', a)\n", + "print('clipped:\\t', np.clip(a, 3, 7))\n", + "\n", + "b = 3 * np.ones(len(a), dtype=np.float)\n", + "print('\\na:\\t\\t', a)\n", + "print('b:\\t\\t', b)\n", + "print('clipped:\\t', np.clip(a, b, 7))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## compress\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.compress.html\n", + "\n", + "The function returns selected slices of an array along given axis. If the axis keyword is `None`, the flattened array is used.\n", + "\n", + "If the firmware was compiled with complex support, the function can accept complex arguments." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-07T19:51:44.994323Z", + "start_time": "2022-01-07T19:51:44.978185Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([[0.0, 1.0, 2.0],\n", + " [3.0, 4.0, 5.0]], dtype=float64)\n", + "\n", + "compress(a):\n", + " array([[3.0, 4.0, 5.0]], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(6)).reshape((2, 3))\n", + "\n", + "print('a:\\n', a)\n", + "print('\\ncompress(a):\\n', np.compress([0, 1], a, axis=0))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## conjugate\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.conjugate.html\n", + "\n", + "If the firmware was compiled with complex support, the function calculates the complex conjugate of the input array. If the input array is of real `dtype`, then the output is simply a copy, preserving the `dtype`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-07T19:30:53.394539Z", + "start_time": "2022-01-07T19:30:53.374737Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t array([1, 2, 3, 4], dtype=uint8)\n", + "conjugate(a):\t array([1, 2, 3, 4], dtype=uint8)\n", + "\n", + "b:\t\t array([1.0+1.0j, 2.0-2.0j, 3.0+3.0j, 4.0-4.0j], dtype=complex)\n", + "conjugate(b):\t array([1.0-1.0j, 2.0+2.0j, 3.0-3.0j, 4.0+4.0j], dtype=complex)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4], dtype=np.uint8)\n", + "b = np.array([1+1j, 2-2j, 3+3j, 4-4j], dtype=np.complex)\n", + "\n", + "print('a:\\t\\t', a)\n", + "print('conjugate(a):\\t', np.conjugate(a))\n", + "print()\n", + "print('b:\\t\\t', b)\n", + "print('conjugate(b):\\t', np.conjugate(b))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## convolve\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.convolve.html\n", + "\n", + "Returns the discrete, linear convolution of two one-dimensional arrays.\n", + "\n", + "Only the ``full`` mode is supported, and the ``mode`` named parameter is not accepted. Note that all other modes can be had by slicing a ``full`` result.\n", + "\n", + "If the firmware was compiled with complex support, the function can accept complex arrays." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T15:57:39.028884Z", + "start_time": "2021-01-13T15:57:39.008749Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([1.0, 12.0, 123.0, 1230.0, 2300.0, 3000.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "x = np.array((1, 2, 3))\n", + "y = np.array((1, 10, 100, 1000))\n", + "\n", + "print(np.convolve(x, y))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## delete\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.delete.html\n", + "\n", + "The function returns a new array with sub-arrays along an axis deleted. It takes two positional arguments, the array, and the indices, which will be removed, as well as the `axis` keyword argument with a default value of `None`. If the `axis` is `None`, the will be flattened first. \n", + "\n", + "The second positional argument can be a scalar, or any `micropython` iterable. Since `range` can also be passed in place of the indices, slicing can be emulated. If the indices are negative, the elements are counted from the end of the axis.\n", + "\n", + "Note that the function creates a copy of the indices first, because it is not guaranteed that the indices are ordered. Keep this in mind, when working with large arrays." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-12T17:03:29.099233Z", + "start_time": "2022-01-12T17:03:29.084117Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([[0, 1, 2, 3, 4],\n", + " [5, 6, 7, 8, 9],\n", + " [10, 11, 12, 13, 14],\n", + " [15, 16, 17, 18, 19],\n", + " [20, 21, 22, 23, 24]], dtype=uint8)\n", + "\n", + "axis = 0\n", + " array([[0, 1, 2, 3, 4],\n", + " [5, 6, 7, 8, 9],\n", + " [15, 16, 17, 18, 19],\n", + " [20, 21, 22, 23, 24]], dtype=uint8)\n", + "\n", + "axis = 1\n", + " array([[0, 1, 2, 4],\n", + " [5, 6, 7, 9],\n", + " [10, 11, 12, 14],\n", + " [15, 16, 17, 19],\n", + " [20, 21, 22, 24]], dtype=uint8)\n", + "\n", + "axis = None\n", + " array([3, 4, 5, ..., 21, 23, 24], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(25), dtype=np.uint8).reshape((5,5))\n", + "print('a:\\n', a)\n", + "print('\\naxis = 0\\n', np.delete(a, 2, axis=0))\n", + "print('\\naxis = 1\\n', np.delete(a, -2, axis=1))\n", + "print('\\naxis = None\\n', np.delete(a, [0, 1, 2, 22]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## diff\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.diff.html\n", + "\n", + "The `diff` function returns the numerical derivative of the forward scheme, or more accurately, the differences of an `ndarray` along a given axis. The order of derivative can be stipulated with the `n` keyword argument, which should be between 0, and 9. Default is 1. If higher order derivatives are required, they can be gotten by repeated calls to the function. The `axis` keyword argument should be -1 (last axis, in `ulab` equivalent to the second axis, and this also happens to be the default value), 0, or 1. \n", + "\n", + "Beyond the output array, the function requires only a couple of bytes of extra RAM for the differentiation stencil. (The stencil is an `int8` array, one byte longer than `n`. This also explains, why the highest order is 9: the coefficients of a ninth-order stencil all fit in signed bytes, while 10 would require `int16`.) Note that as usual in numerical differentiation (and also in `numpy`), the length of the respective axis will be reduced by `n` after the operation. If `n` is larger than, or equal to the length of the axis, an empty array will be returned.\n", + "\n", + "**WARNING**: the `diff` function does not implement the `prepend` and `append` keywords that can be found in `numpy`. " + ] + }, + { + "cell_type": "code", + "execution_count": 106, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-14T16:06:27.468909Z", + "start_time": "2021-01-14T16:06:27.439067Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([0, 1, 2, 10, 4, 5, 6, 7, 8], dtype=uint8)\n", + "\n", + "first derivative:\n", + " array([1, 1, 8, 250, 1, 1, 1, 1], dtype=uint8)\n", + "\n", + "second derivative:\n", + " array([0, 249, 14, 249, 0, 0, 0], dtype=uint8)\n", + "\n", + "c:\n", + " array([[1.0, 2.0, 3.0, 4.0],\n", + " [4.0, 3.0, 2.0, 1.0],\n", + " [1.0, 4.0, 9.0, 16.0],\n", + " [0.0, 0.0, 0.0, 0.0]], dtype=float64)\n", + "\n", + "first derivative, first axis:\n", + " array([[3.0, 1.0, -1.0, -3.0],\n", + " [-3.0, 1.0, 7.0, 15.0],\n", + " [-1.0, -4.0, -9.0, -16.0]], dtype=float64)\n", + "\n", + "first derivative, second axis:\n", + " array([[1.0, 1.0, 1.0],\n", + " [-1.0, -1.0, -1.0],\n", + " [3.0, 5.0, 7.0],\n", + " [0.0, 0.0, 0.0]], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(9), dtype=np.uint8)\n", + "a[3] = 10\n", + "print('a:\\n', a)\n", + "\n", + "print('\\nfirst derivative:\\n', np.diff(a, n=1))\n", + "print('\\nsecond derivative:\\n', np.diff(a, n=2))\n", + "\n", + "c = np.array([[1, 2, 3, 4], [4, 3, 2, 1], [1, 4, 9, 16], [0, 0, 0, 0]])\n", + "print('\\nc:\\n', c)\n", + "print('\\nfirst derivative, first axis:\\n', np.diff(c, axis=0))\n", + "print('\\nfirst derivative, second axis:\\n', np.diff(c, axis=1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## dot\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.dot.html\n", + "\n", + "\n", + "**WARNING:** numpy applies upcasting rules for the multiplication of matrices, while `ulab` simply returns a float matrix. \n", + "\n", + "Once you can invert a matrix, you might want to know, whether the inversion is correct. You can simply take the original matrix and its inverse, and multiply them by calling the `dot` function, which takes the two matrices as its arguments. If the matrix dimensions do not match, the function raises a `ValueError`. The result of the multiplication is expected to be the unit matrix, which is demonstrated below." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "ExecuteTime": { + "end_time": "2021-02-13T08:32:09.139378Z", + "start_time": "2021-02-13T08:32:09.122083Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "m:\n", + " array([[1, 2, 3],\n", + " [4, 5, 6],\n", + " [7, 10, 9]], dtype=uint8)\n", + "\n", + "m^-1:\n", + " array([[-1.25, 1.0, -0.25],\n", + " [0.4999999999999998, -1.0, 0.5],\n", + " [0.4166666666666668, 0.3333333333333333, -0.25]], dtype=float64)\n", + "\n", + "m*m^-1:\n", + " array([[1.0, 0.0, 0.0],\n", + " [4.440892098500626e-16, 1.0, 0.0],\n", + " [8.881784197001252e-16, 0.0, 1.0]], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "m = np.array([[1, 2, 3], [4, 5, 6], [7, 10, 9]], dtype=np.uint8)\n", + "n = np.linalg.inv(m)\n", + "print(\"m:\\n\", m)\n", + "print(\"\\nm^-1:\\n\", n)\n", + "# this should be the unit matrix\n", + "print(\"\\nm*m^-1:\\n\", np.dot(m, n))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that for matrix multiplication you don't necessarily need square matrices, it is enough, if their dimensions are compatible (i.e., the the left-hand-side matrix has as many columns, as does the right-hand-side matrix rows):" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2021-02-13T08:33:07.630825Z", + "start_time": "2021-02-13T08:33:07.608260Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([[1, 2, 3, 4],\n", + " [5, 6, 7, 8]], dtype=uint8)\n", + "array([[1, 2],\n", + " [3, 4],\n", + " [5, 6],\n", + " [7, 8]], dtype=uint8)\n", + "array([[50.0, 60.0],\n", + " [114.0, 140.0]], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "m = np.array([[1, 2, 3, 4], [5, 6, 7, 8]], dtype=np.uint8)\n", + "n = np.array([[1, 2], [3, 4], [5, 6], [7, 8]], dtype=np.uint8)\n", + "print(m)\n", + "print(n)\n", + "print(np.dot(m, n))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## equal\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.equal.html\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.not_equal.html\n", + "\n", + "In `micropython`, equality of arrays or scalars can be established by utilising the `==`, `!=`, `<`, `>`, `<=`, or `=>` binary operators. In `circuitpython`, `==` and `!=` will produce unexpected results. In order to avoid this discrepancy, and to maintain compatibility with `numpy`, `ulab` implements the `equal` and `not_equal` operators that return the same results, irrespective of the `python` implementation.\n", + "\n", + "These two functions take two `ndarray`s, or scalars as their arguments. No keyword arguments are implemented." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T14:22:13.990898Z", + "start_time": "2021-01-08T14:22:13.941896Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64)\n", + "b: array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], dtype=float64)\n", + "\n", + "a == b: array([True, False, False, False, False, False, False, False, False], dtype=bool)\n", + "a != b: array([False, True, True, True, True, True, True, True, True], dtype=bool)\n", + "a == 2: array([False, False, True, False, False, False, False, False, False], dtype=bool)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(9))\n", + "b = np.zeros(9)\n", + "\n", + "print('a: ', a)\n", + "print('b: ', b)\n", + "print('\\na == b: ', np.equal(a, b))\n", + "print('a != b: ', np.not_equal(a, b))\n", + "\n", + "# comparison with scalars\n", + "print('a == 2: ', np.equal(a, 2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## flip\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.flip.html\n", + "\n", + "The `flip` function takes one positional, an `ndarray`, and one keyword argument, `axis = None`, and reverses the order of elements along the given axis. If the keyword argument is `None`, the matrix' entries are flipped along all axes. `flip` returns a new copy of the array.\n", + "\n", + "If the firmware was compiled with complex support, the function can accept complex arrays." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:25:08.425583Z", + "start_time": "2021-01-13T16:25:08.407004Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: \t array([1.0, 2.0, 3.0, 4.0, 5.0], dtype=float64)\n", + "a flipped:\t array([5.0, 4.0, 3.0, 2.0, 1.0], dtype=float64)\n", + "\n", + "a flipped horizontally\n", + " array([[3, 2, 1],\n", + " [6, 5, 4],\n", + " [9, 8, 7]], dtype=uint8)\n", + "\n", + "a flipped vertically\n", + " array([[7, 8, 9],\n", + " [4, 5, 6],\n", + " [1, 2, 3]], dtype=uint8)\n", + "\n", + "a flipped horizontally+vertically\n", + " array([9, 8, 7, 6, 5, 4, 3, 2, 1], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4, 5])\n", + "print(\"a: \\t\", a)\n", + "print(\"a flipped:\\t\", np.flip(a))\n", + "\n", + "a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.uint8)\n", + "print(\"\\na flipped horizontally\\n\", np.flip(a, axis=1))\n", + "print(\"\\na flipped vertically\\n\", np.flip(a, axis=0))\n", + "print(\"\\na flipped horizontally+vertically\\n\", np.flip(a))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## imag\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.imag.html\n", + "\n", + "The `imag` function returns the imaginary part of an array, or scalar. It cannot accept a generic iterable as its argument. The function is defined only, if the firmware was compiled with complex support." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-07T19:26:42.901258Z", + "start_time": "2022-01-07T19:26:42.880755Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t array([1, 2, 3], dtype=uint16)\n", + "imag(a):\t array([0, 0, 0], dtype=uint16)\n", + "\n", + "b:\t\t array([1.0+0.0j, 2.0+1.0j, 3.0-1.0j], dtype=complex)\n", + "imag(b):\t array([0.0, 1.0, -1.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3], dtype=np.uint16)\n", + "print(\"a:\\t\\t\", a)\n", + "print(\"imag(a):\\t\", np.imag(a))\n", + "\n", + "b = np.array([1, 2+1j, 3-1j], dtype=np.complex)\n", + "print(\"\\nb:\\t\\t\", b)\n", + "print(\"imag(b):\\t\", np.imag(b))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## interp\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/numpy.interp\n", + "\n", + "The `interp` function returns the linearly interpolated values of a one-dimensional numerical array. It requires three positional arguments,`x`, at which the interpolated values are evaluated, `xp`, the array\n", + "of the independent data variable, and `fp`, the array of the dependent values of the data. `xp` must be a monotonically increasing sequence of numbers.\n", + "\n", + "Two keyword arguments, `left`, and `right` can also be supplied; these determine the return values, if `x < xp[0]`, and `x > xp[-1]`, respectively. If these arguments are not supplied, `left`, and `right` default to `fp[0]`, and `fp[-1]`, respectively." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:00:43.505722Z", + "start_time": "2021-01-13T16:00:43.489060Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([0.8, 1.8, 2.8, 3.8, 4.8], dtype=float64)\n", + "array([1.0, 1.8, 2.8, 4.6, 5.0], dtype=float64)\n", + "array([0.0, 1.8, 2.8, 4.6, 5.0], dtype=float64)\n", + "array([1.0, 1.8, 2.8, 4.6, 10.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "x = np.array([1, 2, 3, 4, 5]) - 0.2\n", + "xp = np.array([1, 2, 3, 4])\n", + "fp = np.array([1, 2, 3, 5])\n", + "\n", + "print(x)\n", + "print(np.interp(x, xp, fp))\n", + "print(np.interp(x, xp, fp, left=0.0))\n", + "print(np.interp(x, xp, fp, right=10.0))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## isfinite\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.isfinite.html\n", + "\n", + "Returns a Boolean array of the same shape as the input, or a `True/False`, if the input is a scalar. In the return value, all elements are `True` at positions, where the input value was finite. Integer types are automatically finite, therefore, if the input is of integer type, the output will be the `True` tensor." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-29T21:34:42.026689Z", + "start_time": "2021-01-29T21:34:42.010935Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "isfinite(0): True\n", + "\n", + "====================\n", + "a:\n", + " array([1.0, 2.0, nan], dtype=float64)\n", + "\n", + "isfinite(a):\n", + " array([True, True, False], dtype=bool)\n", + "\n", + "====================\n", + "b:\n", + " array([1.0, 2.0, inf], dtype=float64)\n", + "\n", + "isfinite(b):\n", + " array([True, True, False], dtype=bool)\n", + "\n", + "====================\n", + "c:\n", + " array([1, 2, 3], dtype=uint16)\n", + "\n", + "isfinite(c):\n", + " array([True, True, True], dtype=bool)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "print('isfinite(0): ', np.isfinite(0))\n", + "\n", + "a = np.array([1, 2, np.nan])\n", + "print('\\n' + '='*20)\n", + "print('a:\\n', a)\n", + "print('\\nisfinite(a):\\n', np.isfinite(a))\n", + "\n", + "b = np.array([1, 2, np.inf])\n", + "print('\\n' + '='*20)\n", + "print('b:\\n', b)\n", + "print('\\nisfinite(b):\\n', np.isfinite(b))\n", + "\n", + "c = np.array([1, 2, 3], dtype=np.uint16)\n", + "print('\\n' + '='*20)\n", + "print('c:\\n', c)\n", + "print('\\nisfinite(c):\\n', np.isfinite(c))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## isinf\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.isinf.html\n", + "\n", + "Similar to [isfinite](#isfinite), but the output is `True` at positions, where the input is infinite. Integer types return the `False` tensor." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-29T21:35:21.938514Z", + "start_time": "2021-01-29T21:35:21.923741Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "isinf(0): False\n", + "\n", + "====================\n", + "a:\n", + " array([1.0, 2.0, nan], dtype=float64)\n", + "\n", + "isinf(a):\n", + " array([False, False, False], dtype=bool)\n", + "\n", + "====================\n", + "b:\n", + " array([1.0, 2.0, inf], dtype=float64)\n", + "\n", + "isinf(b):\n", + " array([False, False, True], dtype=bool)\n", + "\n", + "====================\n", + "c:\n", + " array([1, 2, 3], dtype=uint16)\n", + "\n", + "isinf(c):\n", + " array([False, False, False], dtype=bool)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "print('isinf(0): ', np.isinf(0))\n", + "\n", + "a = np.array([1, 2, np.nan])\n", + "print('\\n' + '='*20)\n", + "print('a:\\n', a)\n", + "print('\\nisinf(a):\\n', np.isinf(a))\n", + "\n", + "b = np.array([1, 2, np.inf])\n", + "print('\\n' + '='*20)\n", + "print('b:\\n', b)\n", + "print('\\nisinf(b):\\n', np.isinf(b))\n", + "\n", + "c = np.array([1, 2, 3], dtype=np.uint16)\n", + "print('\\n' + '='*20)\n", + "print('c:\\n', c)\n", + "print('\\nisinf(c):\\n', np.isinf(c))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## load\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.load.html\n", + "\n", + "The function reads data from a file in `numpy`'s [platform-independent format](https://numpy.org/doc/stable/reference/generated/numpy.lib.format.html#module-numpy.lib.format), and returns the generated array. If the endianness of the data in the file and the microcontroller differ, the bytes are automatically swapped." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-12T19:11:10.361592Z", + "start_time": "2022-01-12T19:11:10.342439Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([[0.0, 1.0, 2.0, 3.0, 4.0],\n", + " [5.0, 6.0, 7.0, 8.0, 9.0],\n", + " [10.0, 11.0, 12.0, 13.0, 14.0],\n", + " [15.0, 16.0, 17.0, 18.0, 19.0],\n", + " [20.0, 21.0, 22.0, 23.0, 24.0]], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.load('a.npy')\n", + "print(a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## loadtxt\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.loadtxt.html\n", + "\n", + "The function reads data from a text file, and returns the generated array. It takes a file name as the single positional argument, and the following keyword arguments:\n", + "\n", + "1. `comments='#'`\n", + "1. `dtype=float`\n", + "1. `delimiter=','`\n", + "1. `max_rows` (with a default of all rows) \n", + "1. `skip_rows=0`\n", + "1. `usecols` (with a default of all columns)\n", + "\n", + "If `dtype` is supplied and is not `float`, the data entries will be converted to the appropriate integer type by rounding the values." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "ExecuteTime": { + "end_time": "2022-02-01T17:41:22.384706Z", + "start_time": "2022-02-01T17:41:22.362821Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "read all data\n", + "array([[0.0, 1.0, 2.0, 3.0],\n", + " [4.0, 5.0, 6.0, 7.0],\n", + " [8.0, 9.0, 10.0, 11.0],\n", + " [12.0, 13.0, 14.0, 15.0],\n", + " [16.0, 17.0, 18.0, 19.0],\n", + " [20.0, 21.0, 22.0, 23.0],\n", + " [24.0, 25.0, 26.0, 27.0],\n", + " [28.00000000000001, 29.0, 30.0, 31.0],\n", + " [32.0, 33.0, 34.00000000000001, 35.0]], dtype=float64)\n", + "\n", + "read maximum 5 rows (first row is a comment line)\n", + "array([[0.0, 1.0, 2.0, 3.0],\n", + " [4.0, 5.0, 6.0, 7.0],\n", + " [8.0, 9.0, 10.0, 11.0],\n", + " [12.0, 13.0, 14.0, 15.0]], dtype=float64)\n", + "\n", + "read maximum 5 rows, convert dtype (first row is a comment line)\n", + "array([[0, 1, 2, 3],\n", + " [4, 5, 6, 7],\n", + " [8, 9, 10, 11],\n", + " [12, 13, 14, 15]], dtype=uint8)\n", + "\n", + "skip the first 3 rows, convert dtype (first row is a comment line)\n", + "array([[8, 9, 10, 11],\n", + " [12, 13, 14, 15],\n", + " [16, 17, 18, 19],\n", + " [20, 21, 22, 23],\n", + " [24, 25, 26, 27],\n", + " [28, 29, 30, 31],\n", + " [32, 33, 34, 35]], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "print('read all data')\n", + "print(np.loadtxt('loadtxt.dat'))\n", + "\n", + "print('\\nread maximum 5 rows (first row is a comment line)')\n", + "print(np.loadtxt('loadtxt.dat', max_rows=5))\n", + "\n", + "print('\\nread maximum 5 rows, convert dtype (first row is a comment line)')\n", + "print(np.loadtxt('loadtxt.dat', max_rows=5, dtype=np.uint8))\n", + "\n", + "print('\\nskip the first 3 rows, convert dtype (first row is a comment line)')\n", + "print(np.loadtxt('loadtxt.dat', skiprows=3, dtype=np.uint8))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## mean\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.mean.html\n", + "\n", + "If the axis keyword is not specified, it assumes the default value of `None`, and returns the result of the computation for the flattened array. Otherwise, the calculation is along the given axis." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:15:39.921212Z", + "start_time": "2021-01-13T16:15:39.908217Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: \n", + " array([[1.0, 2.0, 3.0],\n", + " [4.0, 5.0, 6.0],\n", + " [7.0, 8.0, 9.0]], dtype=float64)\n", + "mean, flat: 5.0\n", + "mean, horizontal: array([2.0, 5.0, 8.0], dtype=float64)\n", + "mean, vertical: array([4.0, 5.0, 6.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])\n", + "print('a: \\n', a)\n", + "print('mean, flat: ', np.mean(a))\n", + "print('mean, horizontal: ', np.mean(a, axis=1))\n", + "print('mean, vertical: ', np.mean(a, axis=0))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## max\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.max.html\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmax.html\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.min.html\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmin.html\n", + "\n", + "**WARNING:** Difference to `numpy`: the `out` keyword argument is not implemented.\n", + "\n", + "These functions follow the same pattern, and work with generic iterables, and `ndarray`s. `min`, and `max` return the minimum or maximum of a sequence. If the input array is two-dimensional, the `axis` keyword argument can be supplied, in which case the minimum/maximum along the given axis will be returned. If `axis=None` (this is also the default value), the minimum/maximum of the flattened array will be determined.\n", + "\n", + "`argmin/argmax` return the position (index) of the minimum/maximum in the sequence." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:08:56.986619Z", + "start_time": "2021-01-13T16:08:56.964492Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([1.0, 2.0, 0.0, 1.0, 10.0], dtype=float64)\n", + "min of a: 0.0\n", + "argmin of a: 2\n", + "\n", + "b:\n", + " array([[1.0, 2.0, 0.0],\n", + " [1.0, 10.0, -1.0]], dtype=float64)\n", + "min of b (flattened): -1.0\n", + "min of b (axis=0): array([1.0, 2.0, -1.0], dtype=float64)\n", + "min of b (axis=1): array([0.0, -1.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 0, 1, 10])\n", + "print('a:', a)\n", + "print('min of a:', np.min(a))\n", + "print('argmin of a:', np.argmin(a))\n", + "\n", + "b = np.array([[1, 2, 0], [1, 10, -1]])\n", + "print('\\nb:\\n', b)\n", + "print('min of b (flattened):', np.min(b))\n", + "print('min of b (axis=0):', np.min(b, axis=0))\n", + "print('min of b (axis=1):', np.min(b, axis=1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## median\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.median.html\n", + "\n", + "The function computes the median along the specified axis, and returns the median of the array elements. If the `axis` keyword argument is `None`, the arrays is flattened first. The `dtype` of the results is always float." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:31:13.833800Z", + "start_time": "2021-01-13T16:31:13.809560Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([[0, 1, 2, 3],\n", + " [4, 5, 6, 7],\n", + " [8, 9, 10, 11]], dtype=int8)\n", + "\n", + "median of the flattened array: 5.5\n", + "\n", + "median along the vertical axis: array([4.0, 5.0, 6.0, 7.0], dtype=float64)\n", + "\n", + "median along the horizontal axis: array([1.5, 5.5, 9.5], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(12), dtype=np.int8).reshape((3, 4))\n", + "print('a:\\n', a)\n", + "print('\\nmedian of the flattened array: ', np.median(a))\n", + "print('\\nmedian along the vertical axis: ', np.median(a, axis=0))\n", + "print('\\nmedian along the horizontal axis: ', np.median(a, axis=1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## min\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.min.html\n", + "\n", + "See [numpy.max](#max)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## minimum\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.minimum.html\n", + "\n", + "See [numpy.maximum](#maximum)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## maximum\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.maximum.html\n", + "\n", + "Returns the maximum of two arrays, or two scalars, or an array, and a scalar. If the arrays are of different `dtype`, the output is upcast as in [Binary operators](#Binary-operators). If both inputs are scalars, a scalar is returned. Only positional arguments are implemented." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T13:21:17.151280Z", + "start_time": "2021-01-08T13:21:17.123768Z" + }, + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "minimum of a, and b:\n", + "array([1.0, 2.0, 3.0, 2.0, 1.0], dtype=float64)\n", + "\n", + "maximum of a, and b:\n", + "array([5.0, 4.0, 3.0, 4.0, 5.0], dtype=float64)\n", + "\n", + "maximum of 1, and 5.5:\n", + "5.5\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4, 5], dtype=np.uint8)\n", + "b = np.array([5, 4, 3, 2, 1], dtype=np.float)\n", + "print('minimum of a, and b:')\n", + "print(np.minimum(a, b))\n", + "\n", + "print('\\nmaximum of a, and b:')\n", + "print(np.maximum(a, b))\n", + "\n", + "print('\\nmaximum of 1, and 5.5:')\n", + "print(np.maximum(1, 5.5))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## nonzero\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.nonzero.html\n", + "\n", + "`nonzero` returns the indices of the elements of an array that are not zero. If the number of dimensions of the array is larger than one, a tuple of arrays is returned, one for each dimension, containing the indices of the non-zero elements in that dimension." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([-5.0, -4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0], dtype=float64)\n", + "(array([0, 1, 2, 3, 4, 6, 7, 8], dtype=uint16),)\n", + "\n", + "a:\n", + " array([[-5.0, -4.0, -3.0],\n", + " [-2.0, -1.0, 0.0],\n", + " [1.0, 2.0, 3.0]], dtype=float64)\n", + "(array([0, 0, 0, 1, 1, 2, 2, 2], dtype=uint16), array([0, 1, 2, 0, 1, 0, 1, 2], dtype=uint16))\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(9)) - 5\n", + "print('a:\\n', a)\n", + "print(np.nonzero(a))\n", + "\n", + "a = a.reshape((3,3))\n", + "print('\\na:\\n', a)\n", + "print(np.nonzero(a))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## not_equal\n", + "\n", + "See [numpy.equal](#equal)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## polyfit\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.polyfit.html\n", + "\n", + "`polyfit` takes two, or three arguments. The last one is the degree of the polynomial that will be fitted, the last but one is an array or iterable with the `y` (dependent) values, and the first one, an array or iterable with the `x` (independent) values, can be dropped. If that is the case, `x` will be generated in the function as `range(len(y))`.\n", + "\n", + "If the lengths of `x`, and `y` are not the same, the function raises a `ValueError`." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T18:23:39.238450Z", + "start_time": "2021-01-13T18:23:39.221063Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "independent values:\t array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0], dtype=float64)\n", + "dependent values:\t array([9.0, 4.0, 1.0, 0.0, 1.0, 4.0, 9.0], dtype=float64)\n", + "fitted values:\t\t array([1.0, -6.0, 9.000000000000004], dtype=float64)\n", + "\n", + "dependent values:\t array([9.0, 4.0, 1.0, 0.0, 1.0, 4.0, 9.0], dtype=float64)\n", + "fitted values:\t\t array([1.0, -6.0, 9.000000000000004], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "x = np.array([0, 1, 2, 3, 4, 5, 6])\n", + "y = np.array([9, 4, 1, 0, 1, 4, 9])\n", + "print('independent values:\\t', x)\n", + "print('dependent values:\\t', y)\n", + "print('fitted values:\\t\\t', np.polyfit(x, y, 2))\n", + "\n", + "# the same with missing x\n", + "print('\\ndependent values:\\t', y)\n", + "print('fitted values:\\t\\t', np.polyfit(y, 2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Execution time\n", + "\n", + "`polyfit` is based on the inversion of a matrix (there is more on the background in https://en.wikipedia.org/wiki/Polynomial_regression), and it requires the intermediate storage of `2*N*(deg+1)` floats, where `N` is the number of entries in the input array, and `deg` is the fit's degree. The additional computation costs of the matrix inversion discussed in [linalg.inv](#inv) also apply. The example from above needs around 150 microseconds to return:" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T18:31:40.919764Z", + "start_time": "2021-01-13T18:31:40.912817Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "execution time: 153 us\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "@timeit\n", + "def time_polyfit(x, y, n):\n", + " return np.polyfit(x, y, n)\n", + "\n", + "x = np.array([0, 1, 2, 3, 4, 5, 6])\n", + "y = np.array([9, 4, 1, 0, 1, 4, 9])\n", + "\n", + "time_polyfit(x, y, 2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## polyval\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.polyval.html\n", + "\n", + "`polyval` takes two arguments, both arrays or generic `micropython` iterables returning scalars." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T18:12:56.736643Z", + "start_time": "2021-01-13T18:12:56.668042Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "coefficients: [1, 1, 1, 0]\n", + "independent values: [0, 1, 2, 3, 4]\n", + "\n", + "values of p(x): array([0.0, 3.0, 14.0, 39.0, 84.0], dtype=float64)\n", + "\n", + "ndarray (a): array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float64)\n", + "value of p(a): array([0.0, 3.0, 14.0, 39.0, 84.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "p = [1, 1, 1, 0]\n", + "x = [0, 1, 2, 3, 4]\n", + "print('coefficients: ', p)\n", + "print('independent values: ', x)\n", + "print('\\nvalues of p(x): ', np.polyval(p, x))\n", + "\n", + "# the same works with one-dimensional ndarrays\n", + "a = np.array(x)\n", + "print('\\nndarray (a): ', a)\n", + "print('value of p(a): ', np.polyval(p, a))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## real\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.real.html\n", + "\n", + "The `real` function returns the real part of an array, or scalar. It cannot accept a generic iterable as its argument. The function is defined only, if the firmware was compiled with complex support." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-07T19:27:22.141930Z", + "start_time": "2022-01-07T19:27:22.122577Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t array([1, 2, 3], dtype=uint16)\n", + "real(a):\t array([1, 2, 3], dtype=uint16)\n", + "\n", + "b:\t\t array([1.0+0.0j, 2.0+1.0j, 3.0-1.0j], dtype=complex)\n", + "real(b):\t array([1.0, 2.0, 3.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3], dtype=np.uint16)\n", + "print(\"a:\\t\\t\", a)\n", + "print(\"real(a):\\t\", np.real(a))\n", + "\n", + "b = np.array([1, 2+1j, 3-1j], dtype=np.complex)\n", + "print(\"\\nb:\\t\\t\", b)\n", + "print(\"real(b):\\t\", np.real(b))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## roll\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html\n", + "\n", + "The roll function shifts the content of a vector by the positions given as the second argument. If the `axis` keyword is supplied, the shift is applied to the given axis." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:18:30.387043Z", + "start_time": "2021-01-13T16:18:30.363374Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t\t array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64)\n", + "a rolled to the left:\t array([7.0, 8.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0], dtype=float64)\n", + "a rolled to the right:\t array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4, 5, 6, 7, 8])\n", + "print(\"a:\\t\\t\\t\", a)\n", + "\n", + "a = np.roll(a, 2)\n", + "print(\"a rolled to the left:\\t\", a)\n", + "\n", + "# this should be the original vector\n", + "a = np.roll(a, -2)\n", + "print(\"a rolled to the right:\\t\", a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Rolling works with matrices, too. If the `axis` keyword is 0, the matrix is rolled along its vertical axis, otherwise, horizontally. \n", + "\n", + "Horizontal rolls are faster, because they require fewer steps, and larger memory chunks are copied, however, they also require more RAM: basically the whole row must be stored internally. Most expensive are the `None` keyword values, because with `axis = None`, the array is flattened first, hence the row's length is the size of the whole matrix.\n", + "\n", + "Vertical rolls require two internal copies of single columns. " + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:23:52.025977Z", + "start_time": "2021-01-13T16:23:52.001252Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([[0.0, 1.0, 2.0, 3.0],\n", + " [4.0, 5.0, 6.0, 7.0],\n", + " [8.0, 9.0, 10.0, 11.0]], dtype=float64)\n", + "\n", + "a rolled up:\n", + " array([[4.0, 5.0, 6.0, 7.0],\n", + " [8.0, 9.0, 10.0, 11.0],\n", + " [0.0, 1.0, 2.0, 3.0]], dtype=float64)\n", + "a:\n", + " array([[0.0, 1.0, 2.0, 3.0],\n", + " [4.0, 5.0, 6.0, 7.0],\n", + " [8.0, 9.0, 10.0, 11.0]], dtype=float64)\n", + "\n", + "a rolled to the left:\n", + " array([[1.0, 2.0, 3.0, 0.0],\n", + " [5.0, 6.0, 7.0, 4.0],\n", + " [9.0, 10.0, 11.0, 8.0]], dtype=float64)\n", + "a:\n", + " array([[0.0, 1.0, 2.0, 3.0],\n", + " [4.0, 5.0, 6.0, 7.0],\n", + " [8.0, 9.0, 10.0, 11.0]], dtype=float64)\n", + "\n", + "a rolled with None:\n", + " array([[11.0, 0.0, 1.0, 2.0],\n", + " [3.0, 4.0, 5.0, 6.0],\n", + " [7.0, 8.0, 9.0, 10.0]], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(12)).reshape((3, 4))\n", + "print(\"a:\\n\", a)\n", + "a = np.roll(a, 2, axis=0)\n", + "print(\"\\na rolled up:\\n\", a)\n", + "\n", + "a = np.array(range(12)).reshape((3, 4))\n", + "print(\"a:\\n\", a)\n", + "a = np.roll(a, -1, axis=1)\n", + "print(\"\\na rolled to the left:\\n\", a)\n", + "\n", + "a = np.array(range(12)).reshape((3, 4))\n", + "print(\"a:\\n\", a)\n", + "a = np.roll(a, 1, axis=None)\n", + "print(\"\\na rolled with None:\\n\", a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## save\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.save.html\n", + "\n", + "With the help of this function, numerical array can be saved in `numpy`'s [platform-independent format](https://numpy.org/doc/stable/reference/generated/numpy.lib.format.html#module-numpy.lib.format).\n", + "\n", + "The function takes two positional arguments, the name of the output file, and the array. " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-15T08:51:08.827144Z", + "start_time": "2022-01-15T08:51:08.813813Z" + } + }, + "outputs": [], + "source": [ + "a = np.array(range(25)).reshape((5, 5))\n", + "np.save('a.npy', a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## savetxt\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.savetxt.html\n", + "\n", + "With the help of this function, numerical array can be saved in a text file. The function takes two positional arguments, the name of the output file, and the array, and also implements the `comments='#'`\n", + "`delimiter=' '`, the `header=''`, and `footer=''` keyword arguments. The input is treated as of type `float`, i.e., the output is always in the floating point representation." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-28T18:56:06.933706Z", + "start_time": "2022-01-28T18:56:06.872547Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.000000000000000 1.000000000000000 2.000000000000000 3.000000000000000\n", + "4.000000000000000 5.000000000000000 6.000000000000000 7.000000000000000\n", + "8.000000000000000 9.000000000000000 10.000000000000000 11.000000000000000\n", + "\n", + "!col1;col2;col3;col4\n", + "0.000000000000000;1.000000000000000;2.000000000000000;3.000000000000000\n", + "4.000000000000000;5.000000000000000;6.000000000000000;7.000000000000000\n", + "8.000000000000000;9.000000000000000;10.000000000000000;11.000000000000000\n", + "!saved data\n", + "\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(12), dtype=np.uint8).reshape((3, 4))\n", + "np.savetxt('savetxt.dat', a)\n", + "\n", + "with open('savetxt.dat', 'r') as fin:\n", + " print(fin.read())\n", + " \n", + "np.savetxt('savetxt.dat', a, \n", + " comments='!', \n", + " delimiter=';', \n", + " header='col1;col2;col3;col4', \n", + " footer='saved data')\n", + "\n", + "with open('savetxt.dat', 'r') as fin:\n", + " print(fin.read())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## size\n", + "\n", + "The function takes a single positional argument, and an optional keyword argument, `axis`, with a default value of `None`, and returns the size of an array along that axis. If `axis` is `None`, the total length of the array (the product of the elements of its shape) is returned." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-15T08:50:57.254168Z", + "start_time": "2022-01-15T08:50:57.245772Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([[1.0, 1.0, 1.0],\n", + " [1.0, 1.0, 1.0]], dtype=float64)\n", + "size(a, axis=0): 2\n", + "size(a, axis=1): 3\n", + "size(a, axis=None): 6\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.ones((2, 3))\n", + "\n", + "print(a)\n", + "print('size(a, axis=0): ', np.size(a, axis=0))\n", + "print('size(a, axis=1): ', np.size(a, axis=1))\n", + "print('size(a, axis=None): ', np.size(a, axis=None))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## sort\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.sort.html\n", + "\n", + "The sort function takes an ndarray, and sorts its elements in ascending order along the specified axis using a heap sort algorithm. As opposed to the `.sort()` method discussed earlier, this function creates a copy of its input before sorting, and at the end, returns this copy. Sorting takes place in place, without auxiliary storage. The `axis` keyword argument takes on the possible values of -1 (the last axis, in `ulab` equivalent to the second axis, and this also happens to be the default value), 0, 1, or `None`. The first three cases are identical to those in [diff](#diff), while the last one flattens the array before sorting. \n", + "\n", + "If descending order is required, the result can simply be `flip`ped, see [flip](#flip).\n", + "\n", + "**WARNING:** `numpy` defines the `kind`, and `order` keyword arguments that are not implemented here. The function in `ulab` always uses heap sort, and since `ulab` does not have the concept of data fields, the `order` keyword argument would have no meaning." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:32:07.748972Z", + "start_time": "2021-01-13T16:32:07.730498Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "a:\n", + " array([[1.0, 12.0, 3.0, 0.0],\n", + " [5.0, 3.0, 4.0, 1.0],\n", + " [9.0, 11.0, 1.0, 8.0],\n", + " [7.0, 10.0, 0.0, 1.0]], dtype=float64)\n", + "\n", + "a sorted along vertical axis:\n", + " array([[1.0, 3.0, 0.0, 0.0],\n", + " [5.0, 10.0, 1.0, 1.0],\n", + " [7.0, 11.0, 3.0, 1.0],\n", + " [9.0, 12.0, 4.0, 8.0]], dtype=float64)\n", + "\n", + "a sorted along horizontal axis:\n", + " array([[0.0, 1.0, 3.0, 12.0],\n", + " [1.0, 3.0, 4.0, 5.0],\n", + " [1.0, 8.0, 9.0, 11.0],\n", + " [0.0, 1.0, 7.0, 10.0]], dtype=float64)\n", + "\n", + "flattened a sorted:\n", + " array([0.0, 0.0, 1.0, ..., 10.0, 11.0, 12.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.float)\n", + "print('\\na:\\n', a)\n", + "b = np.sort(a, axis=0)\n", + "print('\\na sorted along vertical axis:\\n', b)\n", + "\n", + "c = np.sort(a, axis=1)\n", + "print('\\na sorted along horizontal axis:\\n', c)\n", + "\n", + "c = np.sort(a, axis=None)\n", + "print('\\nflattened a sorted:\\n', c)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Heap sort requires $\\sim N\\log N$ operations, and notably, the worst case costs only 20% more time than the average. In order to get an order-of-magnitude estimate, we will take the sine of 1000 uniformly spaced numbers between 0, and two pi, and sort them:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "@timeit\n", + "def sort_time(array):\n", + " return nup.sort(array)\n", + "\n", + "b = np.sin(np.linspace(0, 6.28, num=1000))\n", + "print('b: ', b)\n", + "sort_time(b)\n", + "print('\\nb sorted:\\n', b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## sort_complex\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.sort_complex.html\n", + "\n", + "If the firmware was compiled with complex support, the functions sorts the input array first according to its real part, and then the imaginary part. The input must be a one-dimensional array. The output is always of `dtype` complex, even if the input was real integer." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-07T19:36:15.750029Z", + "start_time": "2022-01-07T19:36:15.732210Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t\t array([5, 4, 3, 2, 1], dtype=int16)\n", + "sort_complex(a):\t array([1.0+0.0j, 2.0+0.0j, 3.0+0.0j, 4.0+0.0j, 5.0+0.0j], dtype=complex)\n", + "\n", + "b:\t\t\t array([5.0+0.0j, 4.0+3.0j, 4.0-2.0j, 0.0+0.0j, 0.0+1.0j], dtype=complex)\n", + "sort_complex(b):\t array([0.0+0.0j, 0.0+1.0j, 4.0-2.0j, 4.0+3.0j, 5.0+0.0j], dtype=complex)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([5, 4, 3, 2, 1], dtype=np.int16)\n", + "print('a:\\t\\t\\t', a)\n", + "print('sort_complex(a):\\t', np.sort_complex(a))\n", + "print()\n", + "\n", + "b = np.array([5, 4+3j, 4-2j, 0, 1j], dtype=np.complex)\n", + "print('b:\\t\\t\\t', b)\n", + "print('sort_complex(b):\\t', np.sort_complex(b))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## std\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.std.html\n", + "\n", + "If the axis keyword is not specified, it assumes the default value of `None`, and returns the result of the computation for the flattened array. Otherwise, the calculation is along the given axis." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:14:54.051061Z", + "start_time": "2021-01-13T16:14:54.029924Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: \n", + " array([[1.0, 2.0, 3.0],\n", + " [4.0, 5.0, 6.0],\n", + " [7.0, 8.0, 9.0]], dtype=float64)\n", + "sum, flat array: 2.581988897471611\n", + "std, vertical: array([2.449489742783178, 2.449489742783178, 2.449489742783178], dtype=float64)\n", + "std, horizonal: array([0.8164965809277261, 0.8164965809277261, 0.8164965809277261], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])\n", + "print('a: \\n', a)\n", + "print('sum, flat array: ', np.std(a))\n", + "print('std, vertical: ', np.std(a, axis=0))\n", + "print('std, horizonal: ', np.std(a, axis=1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## sum\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html\n", + "\n", + "If the axis keyword is not specified, it assumes the default value of `None`, and returns the result of the computation for the flattened array. Otherwise, the calculation is along the given axis." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:14:34.576723Z", + "start_time": "2021-01-13T16:14:34.556304Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: \n", + " array([[1.0, 2.0, 3.0],\n", + " [4.0, 5.0, 6.0],\n", + " [7.0, 8.0, 9.0]], dtype=float64)\n", + "sum, flat array: 45.0\n", + "sum, horizontal: array([6.0, 15.0, 24.0], dtype=float64)\n", + "std, vertical: array([12.0, 15.0, 18.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])\n", + "print('a: \\n', a)\n", + "\n", + "print('sum, flat array: ', np.sum(a))\n", + "print('sum, horizontal: ', np.sum(a, axis=1))\n", + "print('std, vertical: ', np.sum(a, axis=0))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## trace\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.trace.html\n", + "\n", + "The `trace` function returns the sum of the diagonal elements of a square matrix. If the input argument is not a square matrix, an exception will be raised.\n", + "\n", + "The scalar so returned will inherit the type of the input array, i.e., integer arrays have integer trace, and floating point arrays a floating point trace." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "ExecuteTime": { + "end_time": "2021-02-13T08:30:25.211965Z", + "start_time": "2021-02-13T08:30:25.195102Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([[25, 15, -5],\n", + " [15, 18, 0],\n", + " [-5, 0, 11]], dtype=int8)\n", + "\n", + "trace of a: 54\n", + "====================\n", + "b: array([[25.0, 15.0, -5.0],\n", + " [15.0, 18.0, 0.0],\n", + " [-5.0, 0.0, 11.0]], dtype=float64)\n", + "\n", + "trace of b: 54.0\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[25, 15, -5], [15, 18, 0], [-5, 0, 11]], dtype=np.int8)\n", + "print('a: ', a)\n", + "print('\\ntrace of a: ', np.trace(a))\n", + "\n", + "b = np.array([[25, 15, -5], [15, 18, 0], [-5, 0, 11]], dtype=np.float)\n", + "\n", + "print('='*20 + '\\nb: ', b)\n", + "print('\\ntrace of b: ', np.trace(b))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## trapz\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.trapz.html\n", + "\n", + "The function takes one or two one-dimensional `ndarray`s, and integrates the dependent values (`y`) using the trapezoidal rule. If the independent variable (`x`) is given, that is taken as the sample points corresponding to `y`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:03:42.566302Z", + "start_time": "2021-01-13T16:03:42.545630Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "x: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0], dtype=float64)\n", + "y: array([0.0, 1.0, 4.0, 9.0, 16.0, 25.0, 36.0, 49.0, 64.0, 81.0], dtype=float64)\n", + "============================\n", + "integral of y: 244.5\n", + "integral of y at x: 244.5\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "x = np.linspace(0, 9, num=10)\n", + "y = x*x\n", + "\n", + "print('x: ', x)\n", + "print('y: ', y)\n", + "print('============================')\n", + "print('integral of y: ', np.trapz(y))\n", + "print('integral of y at x: ', np.trapz(y, x=x))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## where\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.where.html\n", + "\n", + "The function takes three positional arguments, `condition`, `x`, and `y`, and returns a new `ndarray`, whose values are taken from either `x`, or `y`, depending on the truthness of `condition`. The three arguments are broadcast together, and the function raises a `ValueError` exception, if broadcasting is not possible.\n", + "\n", + "The function is implemented for `ndarray`s only: other iterable types can be passed after casting them to an `ndarray` by calling the `array` constructor.\n", + "\n", + "If the `dtype`s of `x`, and `y` differ, the output is upcast as discussed earlier. \n", + "\n", + "Note that the `condition` is expanded into an Boolean `ndarray`. This means that the storage required to hold the condition should be taken into account, whenever the function is called." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following example returns an `ndarray` of length 4, with 1 at positions, where `condition` is smaller than 3, and with -1 otherwise." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "ExecuteTime": { + "end_time": "2021-03-23T16:18:14.396840Z", + "start_time": "2021-03-23T16:18:14.385134Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([1, 1, -1, -1], dtype=int16)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "condition = np.array([1, 2, 3, 4], dtype=np.uint8)\n", + "print(np.where(condition < 3, 1, -1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The next snippet shows, how values from two arrays can be fed into the output:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "ExecuteTime": { + "end_time": "2021-03-23T16:15:29.954224Z", + "start_time": "2021-03-23T16:15:29.937205Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([11, 22, 3, 4], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "condition = np.array([1, 2, 3, 4], dtype=np.uint8)\n", + "x = np.array([11, 22, 33, 44], dtype=np.uint8)\n", + "y = np.array([1, 2, 3, 4], dtype=np.uint8)\n", + "print(np.where(condition < 3, x, y))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8.5 ('base')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + }, + "vscode": { + "interpreter": { + "hash": "9e4ec6f642f986afcc9e252c165e44859a62defc5c697cae6f82c2943465ec10" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/numpy-linalg.ipynb b/components/3rd_party/omv/omv/modules/ulab/docs/numpy-linalg.ipynb new file mode 100644 index 00000000..e57e63a3 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/numpy-linalg.ipynb @@ -0,0 +1,811 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "source": [ + "%pylab inline" + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:16:40.844266Z", + "start_time": "2021-01-13T06:16:39.992092Z" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## Notebook magic" + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 1, + "source": [ + "from IPython.core.magic import Magics, magics_class, line_cell_magic\n", + "from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n", + "from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n", + "import subprocess\n", + "import os" + ], + "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:16:40.857076Z", + "start_time": "2021-01-13T06:16:40.852721Z" + } + } + }, + { + "cell_type": "code", + "execution_count": 2, + "source": [ + "@magics_class\n", + "class PyboardMagic(Magics):\n", + " @cell_magic\n", + " @magic_arguments()\n", + " @argument('-skip')\n", + " @argument('-unix')\n", + " @argument('-pyboard')\n", + " @argument('-file')\n", + " @argument('-data')\n", + " @argument('-time')\n", + " @argument('-memory')\n", + " def micropython(self, line='', cell=None):\n", + " args = parse_argstring(self.micropython, line)\n", + " if args.skip: # doesn't care about the cell's content\n", + " print('skipped execution')\n", + " return None # do not parse the rest\n", + " if args.unix: # tests the code on the unix port. Note that this works on unix only\n", + " with open('/dev/shm/micropython.py', 'w') as fout:\n", + " fout.write(cell)\n", + " proc = subprocess.Popen([\"../../micropython/ports/unix/micropython\", \"/dev/shm/micropython.py\"], \n", + " stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " print(proc.stdout.read().decode(\"utf-8\"))\n", + " print(proc.stderr.read().decode(\"utf-8\"))\n", + " return None\n", + " if args.file: # can be used to copy the cell content onto the pyboard's flash\n", + " spaces = \" \"\n", + " try:\n", + " with open(args.file, 'w') as fout:\n", + " fout.write(cell.replace('\\t', spaces))\n", + " printf('written cell to {}'.format(args.file))\n", + " except:\n", + " print('Failed to write to disc!')\n", + " return None # do not parse the rest\n", + " if args.data: # can be used to load data from the pyboard directly into kernel space\n", + " message = pyb.exec(cell)\n", + " if len(message) == 0:\n", + " print('pyboard >>>')\n", + " else:\n", + " print(message.decode('utf-8'))\n", + " # register new variable in user namespace\n", + " self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n", + " \n", + " if args.time: # measures the time of executions\n", + " pyb.exec('import utime')\n", + " message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n", + " \"\\nprint('execution time: {:d} us'.format(delta))\")\n", + " print(message.decode('utf-8'))\n", + " \n", + " if args.memory: # prints out memory information \n", + " message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n", + " print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n", + " message = pyb.exec(cell)\n", + " print(\">>> \", message.decode('utf-8'))\n", + " message = pyb.exec('print(mem_info())')\n", + " print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n", + "\n", + " if args.pyboard:\n", + " message = pyb.exec(cell)\n", + " print(message.decode('utf-8'))\n", + "\n", + "ip = get_ipython()\n", + "ip.register_magics(PyboardMagic)" + ], + "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:16:40.947944Z", + "start_time": "2021-01-13T06:16:40.865720Z" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## pyboard" + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 57, + "source": [ + "import pyboard\n", + "pyb = pyboard.Pyboard('/dev/ttyACM0')\n", + "pyb.enter_raw_repl()" + ], + "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:35.126401Z", + "start_time": "2020-05-07T07:35:35.105824Z" + } + } + }, + { + "cell_type": "code", + "execution_count": 9, + "source": [ + "pyb.exit_raw_repl()\n", + "pyb.close()" + ], + "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:11:18.145548Z", + "start_time": "2020-05-19T19:11:18.137468Z" + } + } + }, + { + "cell_type": "code", + "execution_count": 58, + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "import ulab as np\n", + "\n", + "def timeit(n=1000):\n", + " def wrapper(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " run_times = np.zeros(n, dtype=np.uint16)\n", + " for i in range(n):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n", + " print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n", + " print('\\tbest: %d us'%np.min(run_times))\n", + " print('\\tworst: %d us'%np.max(run_times))\n", + " print('\\taverage: %d us'%np.mean(run_times))\n", + " print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n", + " return result\n", + " return new_func\n", + " return wrapper\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "\n" + ] + } + ], + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:38.725924Z", + "start_time": "2020-05-07T07:35:38.645488Z" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "__END_OF_DEFS__" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "# numpy.linalg\n", + "\n", + "Functions in the `linalg` module can be called by prepending them by `numpy.linalg.`. The module defines the following seven functions:\n", + "\n", + "1. [numpy.linalg.cholesky](#cholesky)\n", + "1. [numpy.linalg.det](#det)\n", + "1. [numpy.linalg.eig](#eig)\n", + "1. [numpy.linalg.inv](#inv)\n", + "1. [numpy.linalg.norm](#norm)\n", + "1. [numpy.linalg.qr](#qr)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## cholesky\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.linalg.cholesky.html\n", + "\n", + "The function of the Cholesky decomposition takes a positive definite, symmetric square matrix as its single argument, and returns the *square root matrix* in the lower triangular form. If the input argument does not fulfill the positivity or symmetry condition, a `ValueError` is raised." + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 18, + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[25, 15, -5], [15, 18, 0], [-5, 0, 11]])\n", + "print('a: ', a)\n", + "print('\\n' + '='*20 + '\\nCholesky decomposition\\n', np.linalg.cholesky(a))" + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "a: array([[25.0, 15.0, -5.0],\n", + "\t [15.0, 18.0, 0.0],\n", + "\t [-5.0, 0.0, 11.0]], dtype=float)\n", + "\n", + "====================\n", + "Cholesky decomposition\n", + " array([[5.0, 0.0, 0.0],\n", + "\t [3.0, 3.0, 0.0],\n", + "\t [-1.0, 1.0, 3.0]], dtype=float)\n", + "\n", + "\n" + ] + } + ], + "metadata": { + "ExecuteTime": { + "end_time": "2020-03-10T19:25:21.754166Z", + "start_time": "2020-03-10T19:25:21.740726Z" + }, + "scrolled": true + } + }, + { + "cell_type": "markdown", + "source": [ + "## det\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.det.html\n", + "\n", + "The `det` function takes a square matrix as its single argument, and calculates the determinant. The calculation is based on successive elimination of the matrix elements, and the return value is a float, even if the input array was of integer type." + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 495, + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 2], [3, 4]], dtype=np.uint8)\n", + "print(np.linalg.det(a))" + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "-2.0\n", + "\n" + ] + } + ], + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-19T13:27:24.246995Z", + "start_time": "2019-10-19T13:27:24.228698Z" + }, + "scrolled": true + } + }, + { + "cell_type": "markdown", + "source": [ + "### Benchmark\n", + "\n", + "Since the routine for calculating the determinant is pretty much the same as for finding the [inverse of a matrix](#inv), the execution times are similar:" + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 557, + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "@timeit\n", + "def matrix_det(m):\n", + " return np.linalg.inv(m)\n", + "\n", + "m = np.array([[1, 2, 3, 4, 5, 6, 7, 8], [0, 5, 6, 4, 5, 6, 4, 5], \n", + " [0, 0, 9, 7, 8, 9, 7, 8], [0, 0, 0, 10, 11, 12, 11, 12], \n", + " [0, 0, 0, 0, 4, 6, 7, 8], [0, 0, 0, 0, 0, 5, 6, 7], \n", + " [0, 0, 0, 0, 0, 0, 7, 6], [0, 0, 0, 0, 0, 0, 0, 2]])\n", + "\n", + "matrix_det(m)" + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "execution time: 294 us\n", + "\n" + ] + } + ], + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-20T07:14:59.778987Z", + "start_time": "2019-10-20T07:14:59.740021Z" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## eig\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.eig.html\n", + "\n", + "The `eig` function calculates the eigenvalues and the eigenvectors of a real, symmetric square matrix. If the matrix is not symmetric, a `ValueError` will be raised. The function takes a single argument, and returns a tuple with the eigenvalues, and eigenvectors. With the help of the eigenvectors, amongst other things, you can implement sophisticated stabilisation routines for robots." + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 18, + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 2, 1, 4], [2, 5, 3, 5], [1, 3, 6, 1], [4, 5, 1, 7]], dtype=np.uint8)\n", + "x, y = np.linalg.eig(a)\n", + "print('eigenvectors of a:\\n', y)\n", + "print('\\neigenvalues of a:\\n', x)" + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "eigenvectors of a:\n", + " array([[0.8151560042509081, -0.4499411232970823, -0.1644660242574522, 0.3256141906686505],\n", + " [0.2211334179893007, 0.7846992598235538, 0.08372081379922657, 0.5730077734355189],\n", + " [-0.1340114162071679, -0.3100776411558949, 0.8742786816656, 0.3486109343758527],\n", + " [-0.5183258053659028, -0.292663481927148, -0.4489749870391468, 0.6664142156731531]], dtype=float)\n", + "\n", + "eigenvalues of a:\n", + " array([-1.165288365404889, 0.8029365530314914, 5.585625756072663, 13.77672605630074], dtype=float)\n", + "\n", + "\n" + ] + } + ], + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-03T20:25:26.952290Z", + "start_time": "2020-11-03T20:25:26.930184Z" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "The same matrix diagonalised with `numpy` yields:" + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 6, + "source": [ + "a = array([[1, 2, 1, 4], [2, 5, 3, 5], [1, 3, 6, 1], [4, 5, 1, 7]], dtype=np.uint8)\n", + "x, y = eig(a)\n", + "print('eigenvectors of a:\\n', y)\n", + "print('\\neigenvalues of a:\\n', x)" + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "eigenvectors of a:\n", + " [[ 0.32561419 0.815156 0.44994112 -0.16446602]\n", + " [ 0.57300777 0.22113342 -0.78469926 0.08372081]\n", + " [ 0.34861093 -0.13401142 0.31007764 0.87427868]\n", + " [ 0.66641421 -0.51832581 0.29266348 -0.44897499]]\n", + "\n", + "eigenvalues of a:\n", + " [13.77672606 -1.16528837 0.80293655 5.58562576]\n" + ] + } + ], + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-03T20:13:27.236159Z", + "start_time": "2020-11-03T20:13:27.069967Z" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "When comparing results, we should keep two things in mind: \n", + "\n", + "1. the eigenvalues and eigenvectors are not necessarily sorted in the same way\n", + "2. an eigenvector can be multiplied by an arbitrary non-zero scalar, and it is still an eigenvector with the same eigenvalue. This is why all signs of the eigenvector belonging to 5.58, and 0.80 are flipped in `ulab` with respect to `numpy`. This difference, however, is of absolutely no consequence. " + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "### Computation expenses\n", + "\n", + "Since the function is based on [Givens rotations](https://en.wikipedia.org/wiki/Givens_rotation) and runs till convergence is achieved, or till the maximum number of allowed rotations is exhausted, there is no universal estimate for the time required to find the eigenvalues. However, an order of magnitude can, at least, be guessed based on the measurement below:" + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 559, + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "@timeit\n", + "def matrix_eig(a):\n", + " return np.linalg.eig(a)\n", + "\n", + "a = np.array([[1, 2, 1, 4], [2, 5, 3, 5], [1, 3, 6, 1], [4, 5, 1, 7]], dtype=np.uint8)\n", + "\n", + "matrix_eig(a)" + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "execution time: 111 us\n", + "\n" + ] + } + ], + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-20T07:18:52.520515Z", + "start_time": "2019-10-20T07:18:52.499653Z" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## inv\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.linalg.inv.html\n", + "\n", + "A square matrix, provided that it is not singular, can be inverted by calling the `inv` function that takes a single argument. The inversion is based on successive elimination of elements in the lower left triangle, and raises a `ValueError` exception, if the matrix turns out to be singular (i.e., one of the diagonal entries is zero)." + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 5, + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "m = np.array([[1, 2, 3, 4], [4, 5, 6, 4], [7, 8.6, 9, 4], [3, 4, 5, 6]])\n", + "\n", + "print(np.linalg.inv(m))" + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "array([[-2.166666666666667, 1.500000000000001, -0.8333333333333337, 1.0],\n", + " [1.666666666666667, -3.333333333333335, 1.666666666666668, -0.0],\n", + " [0.1666666666666666, 2.166666666666668, -0.8333333333333337, -1.0],\n", + " [-0.1666666666666667, -0.3333333333333333, 0.0, 0.5]], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:17:13.053816Z", + "start_time": "2021-01-13T06:17:13.038403Z" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "### Computation expenses\n", + "\n", + "Note that the cost of inverting a matrix is approximately twice as many floats (RAM), as the number of entries in the original matrix, and approximately as many operations, as the number of entries. Here are a couple of numbers: " + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 552, + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "@timeit\n", + "def invert_matrix(m):\n", + " return np.linalg.inv(m)\n", + "\n", + "m = np.array([[1, 2,], [4, 5]])\n", + "print('2 by 2 matrix:')\n", + "invert_matrix(m)\n", + "\n", + "m = np.array([[1, 2, 3, 4], [4, 5, 6, 4], [7, 8.6, 9, 4], [3, 4, 5, 6]])\n", + "print('\\n4 by 4 matrix:')\n", + "invert_matrix(m)\n", + "\n", + "m = np.array([[1, 2, 3, 4, 5, 6, 7, 8], [0, 5, 6, 4, 5, 6, 4, 5], \n", + " [0, 0, 9, 7, 8, 9, 7, 8], [0, 0, 0, 10, 11, 12, 11, 12], \n", + " [0, 0, 0, 0, 4, 6, 7, 8], [0, 0, 0, 0, 0, 5, 6, 7], \n", + " [0, 0, 0, 0, 0, 0, 7, 6], [0, 0, 0, 0, 0, 0, 0, 2]])\n", + "print('\\n8 by 8 matrix:')\n", + "invert_matrix(m)" + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "2 by 2 matrix:\n", + "execution time: 65 us\n", + "\n", + "4 by 4 matrix:\n", + "execution time: 105 us\n", + "\n", + "8 by 8 matrix:\n", + "execution time: 299 us\n", + "\n" + ] + } + ], + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-20T07:10:39.190734Z", + "start_time": "2019-10-20T07:10:39.138872Z" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "The above-mentioned scaling is not obeyed strictly. The reason for the discrepancy is that the function call is still the same for all three cases: the input must be inspected, the output array must be created, and so on. " + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## norm\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html\n", + "\n", + "The function takes a vector or matrix without options, and returns its 2-norm, i.e., the square root of the sum of the square of the elements." + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 11, + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4, 5])\n", + "b = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])\n", + "\n", + "print('norm of a:', np.linalg.norm(a))\n", + "print('norm of b:', np.linalg.norm(b))" + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "norm of a: 7.416198487095663\n", + "norm of b: 16.88194301613414\n", + "\n", + "\n" + ] + } + ], + "metadata": { + "ExecuteTime": { + "end_time": "2020-07-23T20:41:10.341349Z", + "start_time": "2020-07-23T20:41:10.327624Z" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## qr\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.linalg.qr.html\n", + "\n", + "\n", + "The function computes the QR decomposition of a matrix `m` of dimensions `(M, N)`, i.e., it returns two such matrices, `q`', and `r`, that `m = qr`, where `q` is orthonormal, and `r` is upper triangular. In addition to the input matrix, which is the first positional argument, the function accepts the `mode` keyword argument with a default value of `reduced`. If `mode` is `reduced`, `q`, and `r` are returned in the reduced representation. Otherwise, the outputs will have dimensions `(M, M)`, and `(M, N)`, respectively." + ], + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 3, + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "A = np.arange(6).reshape((3, 2))\n", + "print('A: \\n', A)\n", + "\n", + "print('complete decomposition')\n", + "q, r = np.linalg.qr(A, mode='complete')\n", + "print('q: \\n', q)\n", + "print()\n", + "print('r: \\n', r)\n", + "\n", + "print('\\n\\nreduced decomposition')\n", + "q, r = np.linalg.qr(A, mode='reduced')\n", + "print('q: \\n', q)\n", + "print()\n", + "print('r: \\n', r)\n" + ], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "A: \n", + " array([[0, 1],\n", + " [2, 3],\n", + " [4, 5]], dtype=int16)\n", + "complete decomposition\n", + "q: \n", + " array([[0.0, -0.9128709291752768, 0.408248290463863],\n", + " [-0.447213595499958, -0.3651483716701107, -0.8164965809277261],\n", + " [-0.8944271909999159, 0.1825741858350553, 0.408248290463863]], dtype=float64)\n", + "\n", + "r: \n", + " array([[-4.47213595499958, -5.813776741499454],\n", + " [0.0, -1.095445115010332],\n", + " [0.0, 0.0]], dtype=float64)\n", + "\n", + "\n", + "reduced decomposition\n", + "q: \n", + " array([[0.0, -0.9128709291752768],\n", + " [-0.447213595499958, -0.3651483716701107],\n", + " [-0.8944271909999159, 0.1825741858350553]], dtype=float64)\n", + "\n", + "r: \n", + " array([[-4.47213595499958, -5.813776741499454],\n", + " [0.0, -1.095445115010332]], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "metadata": {} + } + ], + "metadata": { + "kernelspec": { + "name": "python3", + "display_name": "Python 3.8.5 64-bit ('base': conda)" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + }, + "interpreter": { + "hash": "ce9a02f9f7db620716422019cafa4bc1786ca85daa298b819f6da075e7993842" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/numpy-universal.ipynb b/components/3rd_party/omv/omv/modules/ulab/docs/numpy-universal.ipynb new file mode 100644 index 00000000..8934fa6e --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/numpy-universal.ipynb @@ -0,0 +1,869 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T18:54:58.722373Z", + "start_time": "2021-01-13T18:54:57.178438Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Notebook magic" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-07T19:10:30.696795Z", + "start_time": "2022-01-07T19:10:30.690003Z" + } + }, + "outputs": [], + "source": [ + "from IPython.core.magic import Magics, magics_class, line_cell_magic\n", + "from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n", + "from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n", + "import subprocess\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-07T19:10:30.785887Z", + "start_time": "2022-01-07T19:10:30.710912Z" + } + }, + "outputs": [], + "source": [ + "@magics_class\n", + "class PyboardMagic(Magics):\n", + " @cell_magic\n", + " @magic_arguments()\n", + " @argument('-skip')\n", + " @argument('-unix')\n", + " @argument('-pyboard')\n", + " @argument('-file')\n", + " @argument('-data')\n", + " @argument('-time')\n", + " @argument('-memory')\n", + " def micropython(self, line='', cell=None):\n", + " args = parse_argstring(self.micropython, line)\n", + " if args.skip: # doesn't care about the cell's content\n", + " print('skipped execution')\n", + " return None # do not parse the rest\n", + " if args.unix: # tests the code on the unix port. Note that this works on unix only\n", + " with open('/dev/shm/micropython.py', 'w') as fout:\n", + " fout.write(cell)\n", + " proc = subprocess.Popen([\"../micropython/ports/unix/micropython-2\", \"/dev/shm/micropython.py\"], \n", + " stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " print(proc.stdout.read().decode(\"utf-8\"))\n", + " print(proc.stderr.read().decode(\"utf-8\"))\n", + " return None\n", + " if args.file: # can be used to copy the cell content onto the pyboard's flash\n", + " spaces = \" \"\n", + " try:\n", + " with open(args.file, 'w') as fout:\n", + " fout.write(cell.replace('\\t', spaces))\n", + " printf('written cell to {}'.format(args.file))\n", + " except:\n", + " print('Failed to write to disc!')\n", + " return None # do not parse the rest\n", + " if args.data: # can be used to load data from the pyboard directly into kernel space\n", + " message = pyb.exec(cell)\n", + " if len(message) == 0:\n", + " print('pyboard >>>')\n", + " else:\n", + " print(message.decode('utf-8'))\n", + " # register new variable in user namespace\n", + " self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n", + " \n", + " if args.time: # measures the time of executions\n", + " pyb.exec('import utime')\n", + " message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n", + " \"\\nprint('execution time: {:d} us'.format(delta))\")\n", + " print(message.decode('utf-8'))\n", + " \n", + " if args.memory: # prints out memory information \n", + " message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n", + " print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n", + " message = pyb.exec(cell)\n", + " print(\">>> \", message.decode('utf-8'))\n", + " message = pyb.exec('print(mem_info())')\n", + " print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n", + "\n", + " if args.pyboard:\n", + " message = pyb.exec(cell)\n", + " print(message.decode('utf-8'))\n", + "\n", + "ip = get_ipython()\n", + "ip.register_magics(PyboardMagic)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pyboard" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:35.126401Z", + "start_time": "2020-05-07T07:35:35.105824Z" + } + }, + "outputs": [], + "source": [ + "import pyboard\n", + "pyb = pyboard.Pyboard('/dev/ttyACM0')\n", + "pyb.enter_raw_repl()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:11:18.145548Z", + "start_time": "2020-05-19T19:11:18.137468Z" + } + }, + "outputs": [], + "source": [ + "pyb.exit_raw_repl()\n", + "pyb.close()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:38.725924Z", + "start_time": "2020-05-07T07:35:38.645488Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "import ulab as np\n", + "\n", + "def timeit(n=1000):\n", + " def wrapper(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " run_times = np.zeros(n, dtype=np.uint16)\n", + " for i in range(n):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n", + " print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n", + " print('\\tbest: %d us'%np.min(run_times))\n", + " print('\\tworst: %d us'%np.max(run_times))\n", + " print('\\taverage: %d us'%np.mean(run_times))\n", + " print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n", + " return result\n", + " return new_func\n", + " return wrapper\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__END_OF_DEFS__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Universal functions\n", + "\n", + "Standard mathematical functions can be calculated on any scalar, scalar-valued iterable (ranges, lists, tuples containing numbers), and on `ndarray`s without having to change the call signature. In all cases the functions return a new `ndarray` of typecode `float` (since these functions usually generate float values, anyway). The only exceptions to this rule are the `exp`, and `sqrt` functions, which, if `ULAB_SUPPORTS_COMPLEX` is set to 1 in [ulab.h](https://github.com/v923z/micropython-ulab/blob/master/code/ulab.h), can return complex arrays, depending on the argument. All functions execute faster with `ndarray` arguments than with iterables, because the values of the input vector can be extracted faster. \n", + "\n", + "At present, the following functions are supported (starred functions can operate on, or can return complex arrays):\n", + "\n", + "`acos`, `acosh`, `arctan2`, `around`, `asin`, `asinh`, `atan`, `arctan2`, `atanh`, `ceil`, `cos`, `degrees`, `exp*`, `expm1`, `floor`, `log`, `log10`, `log2`, `radians`, `sin`, `sinh`, `sqrt*`, `tan`, `tanh`.\n", + "\n", + "These functions are applied element-wise to the arguments, thus, e.g., the exponential of a matrix cannot be calculated in this way, only the exponential of the matrix entries." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T19:11:07.579601Z", + "start_time": "2021-01-13T19:11:07.554672Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t range(0, 9)\n", + "exp(a):\t array([1.0, 2.718281828459045, 7.38905609893065, 20.08553692318767, 54.59815003314424, 148.4131591025766, 403.4287934927351, 1096.633158428459, 2980.957987041728], dtype=float64)\n", + "\n", + "=============\n", + "b:\n", + " array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64)\n", + "exp(b):\n", + " array([1.0, 2.718281828459045, 7.38905609893065, 20.08553692318767, 54.59815003314424, 148.4131591025766, 403.4287934927351, 1096.633158428459, 2980.957987041728], dtype=float64)\n", + "\n", + "=============\n", + "c:\n", + " array([[0.0, 1.0, 2.0],\n", + " [3.0, 4.0, 5.0],\n", + " [6.0, 7.0, 8.0]], dtype=float64)\n", + "exp(c):\n", + " array([[1.0, 2.718281828459045, 7.38905609893065],\n", + " [20.08553692318767, 54.59815003314424, 148.4131591025766],\n", + " [403.4287934927351, 1096.633158428459, 2980.957987041728]], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = range(9)\n", + "b = np.array(a)\n", + "\n", + "# works with ranges, lists, tuples etc.\n", + "print('a:\\t', a)\n", + "print('exp(a):\\t', np.exp(a))\n", + "\n", + "# with 1D arrays\n", + "print('\\n=============\\nb:\\n', b)\n", + "print('exp(b):\\n', np.exp(b))\n", + "\n", + "# as well as with matrices\n", + "c = np.array(range(9)).reshape((3, 3))\n", + "print('\\n=============\\nc:\\n', c)\n", + "print('exp(c):\\n', np.exp(c))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Computation expenses\n", + "\n", + "The overhead for calculating with micropython iterables is quite significant: for the 1000 samples below, the difference is more than 800 microseconds, because internally the function has to create the `ndarray` for the output, has to fetch the iterable's items of unknown type, and then convert them to floats. All these steps are skipped for `ndarray`s, because these pieces of information are already known. \n", + "\n", + "Doing the same with `list` comprehension requires 30 times more time than with the `ndarray`, which would become even more, if we converted the resulting list to an `ndarray`. " + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:45.696282Z", + "start_time": "2020-05-07T07:35:45.629909Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "iterating over ndarray in ulab\r\n", + "execution time: 441 us\r\n", + "\r\n", + "iterating over list in ulab\r\n", + "execution time: 1266 us\r\n", + "\r\n", + "iterating over list in python\r\n", + "execution time: 11379 us\r\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "from ulab import numpy as np\n", + "import math\n", + "\n", + "a = [0]*1000\n", + "b = np.array(a)\n", + "\n", + "@timeit\n", + "def timed_vector(iterable):\n", + " return np.exp(iterable)\n", + "\n", + "@timeit\n", + "def timed_list(iterable):\n", + " return [math.exp(i) for i in iterable]\n", + "\n", + "print('iterating over ndarray in ulab')\n", + "timed_vector(b)\n", + "\n", + "print('\\niterating over list in ulab')\n", + "timed_vector(a)\n", + "\n", + "print('\\niterating over list in python')\n", + "timed_list(a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## arctan2\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.arctan2.html\n", + "\n", + "The two-argument inverse tangent function is also part of the `vector` sub-module. The function implements broadcasting as discussed in the section on `ndarray`s. Scalars (`micropython` integers or floats) are also allowed." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T19:15:08.215912Z", + "start_time": "2021-01-13T19:15:08.189806Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([1.0, 2.2, 33.33, 444.444], dtype=float64)\n", + "\n", + "arctan2(a, 1.0)\n", + " array([0.7853981633974483, 1.14416883366802, 1.5408023243361, 1.568546328341769], dtype=float64)\n", + "\n", + "arctan2(1.0, a)\n", + " array([0.7853981633974483, 0.426627493126876, 0.02999400245879636, 0.002249998453127392], dtype=float64)\n", + "\n", + "arctan2(a, a): \n", + " array([0.7853981633974483, 0.7853981633974483, 0.7853981633974483, 0.7853981633974483], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2.2, 33.33, 444.444])\n", + "print('a:\\n', a)\n", + "print('\\narctan2(a, 1.0)\\n', np.arctan2(a, 1.0))\n", + "print('\\narctan2(1.0, a)\\n', np.arctan2(1.0, a))\n", + "print('\\narctan2(a, a): \\n', np.arctan2(a, a))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## around\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.around.html\n", + "\n", + "`numpy`'s `around` function can also be found in the `vector` sub-module. The function implements the `decimals` keyword argument with default value `0`. The first argument must be an `ndarray`. If this is not the case, the function raises a `TypeError` exception. Note that `numpy` accepts general iterables. The `out` keyword argument known from `numpy` is not accepted. The function always returns an ndarray of type `mp_float_t`." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T19:19:46.728823Z", + "start_time": "2021-01-13T19:19:46.703348Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t array([1.0, 2.2, 33.33, 444.444], dtype=float64)\n", + "\n", + "decimals = 0\t array([1.0, 2.0, 33.0, 444.0], dtype=float64)\n", + "\n", + "decimals = 1\t array([1.0, 2.2, 33.3, 444.4], dtype=float64)\n", + "\n", + "decimals = -1\t array([0.0, 0.0, 30.0, 440.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2.2, 33.33, 444.444])\n", + "print('a:\\t\\t', a)\n", + "print('\\ndecimals = 0\\t', np.around(a, decimals=0))\n", + "print('\\ndecimals = 1\\t', np.around(a, decimals=1))\n", + "print('\\ndecimals = -1\\t', np.around(a, decimals=-1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## exp\n", + "\n", + "If `ULAB_SUPPORTS_COMPLEX` is set to 1 in [ulab.h](https://github.com/v923z/micropython-ulab/blob/master/code/ulab.h), the exponential function can also take complex arrays as its argument, in which case the return value is also complex." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-07T18:41:51.865779Z", + "start_time": "2022-01-07T18:41:51.843897Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t array([1.0, 2.0, 3.0], dtype=float64)\n", + "exp(a):\t\t array([2.718281828459045, 7.38905609893065, 20.08553692318767], dtype=float64)\n", + "\n", + "b:\t\t array([1.0+1.0j, 2.0+2.0j, 3.0+3.0j], dtype=complex)\n", + "exp(b):\t\t array([1.468693939915885+2.287355287178842j, -3.074932320639359+6.71884969742825j, -19.88453084414699+2.834471132487004j], dtype=complex)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3])\n", + "print('a:\\t\\t', a)\n", + "print('exp(a):\\t\\t', np.exp(a))\n", + "print()\n", + "\n", + "b = np.array([1+1j, 2+2j, 3+3j], dtype=np.complex)\n", + "print('b:\\t\\t', b)\n", + "print('exp(b):\\t\\t', np.exp(b))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## sqrt\n", + "\n", + "If `ULAB_SUPPORTS_COMPLEX` is set to 1 in [ulab.h](https://github.com/v923z/micropython-ulab/blob/master/code/ulab.h), the exponential function can also take complex arrays as its argument, in which case the return value is also complex. If the input is real, but the results might be complex, the user is supposed to specify the output `dtype` in the function call. Otherwise, the square roots of negative numbers will result in `NaN`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-07T18:45:26.554520Z", + "start_time": "2022-01-07T18:45:26.543552Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t array([1.0, -1.0], dtype=float64)\n", + "sqrt(a):\t\t array([1.0, nan], dtype=float64)\n", + "sqrt(a):\t\t array([1.0+0.0j, 0.0+1.0j], dtype=complex)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, -1])\n", + "print('a:\\t\\t', a)\n", + "print('sqrt(a):\\t\\t', np.sqrt(a))\n", + "print('sqrt(a):\\t\\t', np.sqrt(a, dtype=np.complex))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Vectorising generic python functions\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.vectorize.html\n", + "\n", + "The examples above use factory functions. In fact, they are nothing but the vectorised versions of the standard mathematical functions. User-defined `python` functions can also be vectorised by help of `vectorize`. This function takes a positional argument, namely, the `python` function that you want to vectorise, and a non-mandatory keyword argument, `otypes`, which determines the `dtype` of the output array. The `otypes` must be `None` (default), or any of the `dtypes` defined in `ulab`. With `None`, the output is automatically turned into a float array. \n", + "\n", + "The return value of `vectorize` is a `micropython` object that can be called as a standard function, but which now accepts either a scalar, an `ndarray`, or a generic `micropython` iterable as its sole argument. Note that the function that is to be vectorised must have a single argument." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T19:16:55.709617Z", + "start_time": "2021-01-13T19:16:55.688222Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "f on a scalar: array([1936.0], dtype=float64)\n", + "f on an ndarray: array([1.0, 4.0, 9.0, 16.0], dtype=float64)\n", + "f on a list: array([4.0, 9.0, 16.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "def f(x):\n", + " return x*x\n", + "\n", + "vf = np.vectorize(f)\n", + "\n", + "# calling with a scalar\n", + "print('{:20}'.format('f on a scalar: '), vf(44.0))\n", + "\n", + "# calling with an ndarray\n", + "a = np.array([1, 2, 3, 4])\n", + "print('{:20}'.format('f on an ndarray: '), vf(a))\n", + "\n", + "# calling with a list\n", + "print('{:20}'.format('f on a list: '), vf([2, 3, 4]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As mentioned, the `dtype` of the resulting `ndarray` can be specified via the `otypes` keyword. The value is bound to the function object that `vectorize` returns, therefore, if the same function is to be vectorised with different output types, then for each type a new function object must be created." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T19:19:36.090837Z", + "start_time": "2021-01-13T19:19:36.069088Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "output is uint8: array([1, 4, 9, 16], dtype=uint8)\n", + "output is float: array([1.0, 4.0, 9.0, 16.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "l = [1, 2, 3, 4]\n", + "def f(x):\n", + " return x*x\n", + "\n", + "vf1 = np.vectorize(f, otypes=np.uint8)\n", + "vf2 = np.vectorize(f, otypes=np.float)\n", + "\n", + "print('{:20}'.format('output is uint8: '), vf1(l))\n", + "print('{:20}'.format('output is float: '), vf2(l))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `otypes` keyword argument cannot be used for type coercion: if the function evaluates to a float, but `otypes` would dictate an integer type, an exception will be raised:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-06T22:21:43.616220Z", + "start_time": "2020-05-06T22:21:43.601280Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "integer list: array([1, 4, 9, 16], dtype=uint8)\n", + "\n", + "Traceback (most recent call last):\n", + " File \"/dev/shm/micropython.py\", line 14, in \n", + "TypeError: can't convert float to int\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "int_list = [1, 2, 3, 4]\n", + "float_list = [1.0, 2.0, 3.0, 4.0]\n", + "def f(x):\n", + " return x*x\n", + "\n", + "vf = np.vectorize(f, otypes=np.uint8)\n", + "\n", + "print('{:20}'.format('integer list: '), vf(int_list))\n", + "# this will raise a TypeError exception\n", + "print(vf(float_list))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Benchmarks\n", + "\n", + "It should be pointed out that the `vectorize` function produces the pseudo-vectorised version of the `python` function that is fed into it, i.e., on the C level, the same `python` function is called, with the all-encompassing `mp_obj_t` type arguments, and all that happens is that the `for` loop in `[f(i) for i in iterable]` runs purely in C. Since type checking and type conversion in `f()` is expensive, the speed-up is not so spectacular as when iterating over an `ndarray` with a factory function: a gain of approximately 30% can be expected, when a native `python` type (e.g., `list`) is returned by the function, and this becomes around 50% (a factor of 2), if conversion to an `ndarray` is also counted.\n", + "\n", + "The following code snippet calculates the square of a 1000 numbers with the vectorised function (which returns an `ndarray`), with `list` comprehension, and with `list` comprehension followed by conversion to an `ndarray`. For comparison, the execution time is measured also for the case, when the square is calculated entirely in `ulab`." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:32:20.048553Z", + "start_time": "2020-05-07T07:32:19.951851Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "vectorised function\r\n", + "execution time: 7237 us\r\n", + "\r\n", + "list comprehension\r\n", + "execution time: 10248 us\r\n", + "\r\n", + "list comprehension + ndarray conversion\r\n", + "execution time: 12562 us\r\n", + "\r\n", + "squaring an ndarray entirely in ulab\r\n", + "execution time: 560 us\r\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "def f(x):\n", + " return x*x\n", + "\n", + "vf = np.vectorize(f)\n", + "\n", + "@timeit\n", + "def timed_vectorised_square(iterable):\n", + " return vf(iterable)\n", + "\n", + "@timeit\n", + "def timed_python_square(iterable):\n", + " return [f(i) for i in iterable]\n", + "\n", + "@timeit\n", + "def timed_ndarray_square(iterable):\n", + " return np.array([f(i) for i in iterable])\n", + "\n", + "@timeit\n", + "def timed_ulab_square(ndarray):\n", + " return ndarray**2\n", + "\n", + "print('vectorised function')\n", + "squares = timed_vectorised_square(range(1000))\n", + "\n", + "print('\\nlist comprehension')\n", + "squares = timed_python_square(range(1000))\n", + "\n", + "print('\\nlist comprehension + ndarray conversion')\n", + "squares = timed_ndarray_square(range(1000))\n", + "\n", + "print('\\nsquaring an ndarray entirely in ulab')\n", + "a = np.array(range(1000))\n", + "squares = timed_ulab_square(a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From the comparisons above, it is obvious that `python` functions should only be vectorised, when the same effect cannot be gotten in `ulab` only. However, although the time savings are not significant, there is still a good reason for caring about vectorised functions. Namely, user-defined `python` functions become universal, i.e., they can accept generic iterables as well as `ndarray`s as their arguments. A vectorised function is still a one-liner, resulting in transparent and elegant code.\n", + "\n", + "A final comment on this subject: the `f(x)` that we defined is a *generic* `python` function. This means that it is not required that it just crunches some numbers. It has to return a number object, but it can still access the hardware in the meantime. So, e.g., \n", + "\n", + "```python\n", + "\n", + "led = pyb.LED(2)\n", + "\n", + "def f(x):\n", + " if x < 100:\n", + " led.toggle()\n", + " return x*x\n", + "```\n", + "\n", + "is perfectly valid code." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/scipy-linalg.ipynb b/components/3rd_party/omv/omv/modules/ulab/docs/scipy-linalg.ipynb new file mode 100644 index 00000000..6adaa11b --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/scipy-linalg.ipynb @@ -0,0 +1,474 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T18:54:58.722373Z", + "start_time": "2021-01-13T18:54:57.178438Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Notebook magic" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2021-05-09T05:37:22.600510Z", + "start_time": "2021-05-09T05:37:22.595924Z" + } + }, + "outputs": [], + "source": [ + "from IPython.core.magic import Magics, magics_class, line_cell_magic\n", + "from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n", + "from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n", + "import subprocess\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2021-05-09T05:37:26.429136Z", + "start_time": "2021-05-09T05:37:26.403191Z" + } + }, + "outputs": [], + "source": [ + "@magics_class\n", + "class PyboardMagic(Magics):\n", + " @cell_magic\n", + " @magic_arguments()\n", + " @argument('-skip')\n", + " @argument('-unix')\n", + " @argument('-pyboard')\n", + " @argument('-file')\n", + " @argument('-data')\n", + " @argument('-time')\n", + " @argument('-memory')\n", + " def micropython(self, line='', cell=None):\n", + " args = parse_argstring(self.micropython, line)\n", + " if args.skip: # doesn't care about the cell's content\n", + " print('skipped execution')\n", + " return None # do not parse the rest\n", + " if args.unix: # tests the code on the unix port. Note that this works on unix only\n", + " with open('/dev/shm/micropython.py', 'w') as fout:\n", + " fout.write(cell)\n", + " proc = subprocess.Popen([\"../../micropython/ports/unix/micropython\", \"/dev/shm/micropython.py\"], \n", + " stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " print(proc.stdout.read().decode(\"utf-8\"))\n", + " print(proc.stderr.read().decode(\"utf-8\"))\n", + " return None\n", + " if args.file: # can be used to copy the cell content onto the pyboard's flash\n", + " spaces = \" \"\n", + " try:\n", + " with open(args.file, 'w') as fout:\n", + " fout.write(cell.replace('\\t', spaces))\n", + " printf('written cell to {}'.format(args.file))\n", + " except:\n", + " print('Failed to write to disc!')\n", + " return None # do not parse the rest\n", + " if args.data: # can be used to load data from the pyboard directly into kernel space\n", + " message = pyb.exec(cell)\n", + " if len(message) == 0:\n", + " print('pyboard >>>')\n", + " else:\n", + " print(message.decode('utf-8'))\n", + " # register new variable in user namespace\n", + " self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n", + " \n", + " if args.time: # measures the time of executions\n", + " pyb.exec('import utime')\n", + " message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n", + " \"\\nprint('execution time: {:d} us'.format(delta))\")\n", + " print(message.decode('utf-8'))\n", + " \n", + " if args.memory: # prints out memory information \n", + " message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n", + " print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n", + " message = pyb.exec(cell)\n", + " print(\">>> \", message.decode('utf-8'))\n", + " message = pyb.exec('print(mem_info())')\n", + " print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n", + "\n", + " if args.pyboard:\n", + " message = pyb.exec(cell)\n", + " print(message.decode('utf-8'))\n", + "\n", + "ip = get_ipython()\n", + "ip.register_magics(PyboardMagic)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pyboard" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:35.126401Z", + "start_time": "2020-05-07T07:35:35.105824Z" + } + }, + "outputs": [], + "source": [ + "import pyboard\n", + "pyb = pyboard.Pyboard('/dev/ttyACM0')\n", + "pyb.enter_raw_repl()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:11:18.145548Z", + "start_time": "2020-05-19T19:11:18.137468Z" + } + }, + "outputs": [], + "source": [ + "pyb.exit_raw_repl()\n", + "pyb.close()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:38.725924Z", + "start_time": "2020-05-07T07:35:38.645488Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "import ulab as np\n", + "\n", + "def timeit(n=1000):\n", + " def wrapper(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " run_times = np.zeros(n, dtype=np.uint16)\n", + " for i in range(n):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n", + " print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n", + " print('\\tbest: %d us'%np.min(run_times))\n", + " print('\\tworst: %d us'%np.max(run_times))\n", + " print('\\taverage: %d us'%np.mean(run_times))\n", + " print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n", + " return result\n", + " return new_func\n", + " return wrapper\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__END_OF_DEFS__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# scipy.linalg\n", + "\n", + "`scipy`'s `linalg` module contains two functions, `solve_triangular`, and `cho_solve`. The functions can be called by prepending them by `scipy.linalg.`.\n", + "\n", + "1. [scipy.linalg.solve_cho](#cho_solve)\n", + "2. [scipy.linalg.solve_triangular](#solve_triangular)" + ] + }, + { + "source": [ + "## cho_solve\n", + "\n", + "`scipy`: https://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.cho_solve.html\n", + "\n", + "Solve the linear equations \n", + "\n", + "\n", + "\\begin{equation}\n", + "\\mathbf{A}\\cdot\\mathbf{x} = \\mathbf{b}\n", + "\\end{equation}\n", + "\n", + "given the Cholesky factorization of $\\mathbf{A}$. As opposed to `scipy`, the function simply takes the Cholesky-factorised matrix, $\\mathbf{A}$, and $\\mathbf{b}$ as inputs." + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "array([-0.01388888888888906, -0.6458333333333331, 2.677083333333333, -0.01041666666666667], dtype=float64)\n\n\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "from ulab import scipy as spy\n", + "\n", + "A = np.array([[3, 0, 0, 0], [2, 1, 0, 0], [1, 0, 1, 0], [1, 2, 1, 8]])\n", + "b = np.array([4, 2, 4, 2])\n", + "\n", + "print(spy.linalg.cho_solve(A, b))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## solve_triangular\n", + "\n", + "`scipy`: https://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.solve_triangular.html \n", + "\n", + "Solve the linear equation \n", + "\n", + "\\begin{equation}\n", + "\\mathbf{a}\\cdot\\mathbf{x} = \\mathbf{b}\n", + "\\end{equation}\n", + "\n", + "with the assumption that $\\mathbf{a}$ is a triangular matrix. The two position arguments are $\\mathbf{a}$, and $\\mathbf{b}$, and the optional keyword argument is `lower` with a default value of `False`. `lower` determines, whether data are taken from the lower, or upper triangle of $\\mathbf{a}$. \n", + "\n", + "Note that $\\mathbf{a}$ itself does not have to be a triangular matrix: if it is not, then the values are simply taken to be 0 in the upper or lower triangle, as dictated by `lower`. However, $\\mathbf{a}\\cdot\\mathbf{x}$ will yield $\\mathbf{b}$ only, when $\\mathbf{a}$ is triangular. You should keep this in mind, when trying to establish the validity of the solution by back substitution." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "ExecuteTime": { + "end_time": "2021-05-09T05:56:57.449996Z", + "start_time": "2021-05-09T05:56:57.422515Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + "\n", + "array([[3.0, 0.0, 0.0, 0.0],\n", + " [2.0, 1.0, 0.0, 0.0],\n", + " [1.0, 0.0, 1.0, 0.0],\n", + " [1.0, 2.0, 1.0, 8.0]], dtype=float64)\n", + "\n", + "b: array([4.0, 2.0, 4.0, 2.0], dtype=float64)\n", + "====================\n", + "x: array([1.333333333333333, -0.6666666666666665, 2.666666666666667, -0.08333333333333337], dtype=float64)\n", + "\n", + "dot(a, x): array([4.0, 2.0, 4.0, 2.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "from ulab import scipy as spy\n", + "\n", + "a = np.array([[3, 0, 0, 0], [2, 1, 0, 0], [1, 0, 1, 0], [1, 2, 1, 8]])\n", + "b = np.array([4, 2, 4, 2])\n", + "\n", + "print('a:\\n')\n", + "print(a)\n", + "print('\\nb: ', b)\n", + "\n", + "x = spy.linalg.solve_triangular(a, b, lower=True)\n", + "\n", + "print('='*20)\n", + "print('x: ', x)\n", + "print('\\ndot(a, x): ', np.dot(a, x))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With get the same solution, $\\mathbf{x}$, with the following matrix, but the dot product of $\\mathbf{a}$, and $\\mathbf{x}$ is no longer $\\mathbf{b}$:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "ExecuteTime": { + "end_time": "2021-05-09T06:03:30.853054Z", + "start_time": "2021-05-09T06:03:30.841500Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + "\n", + "array([[3.0, 2.0, 1.0, 0.0],\n", + " [2.0, 1.0, 0.0, 1.0],\n", + " [1.0, 0.0, 1.0, 4.0],\n", + " [1.0, 2.0, 1.0, 8.0]], dtype=float64)\n", + "\n", + "b: array([4.0, 2.0, 4.0, 2.0], dtype=float64)\n", + "====================\n", + "x: array([1.333333333333333, -0.6666666666666665, 2.666666666666667, -0.08333333333333337], dtype=float64)\n", + "\n", + "dot(a, x): array([5.333333333333334, 1.916666666666666, 3.666666666666667, 2.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "from ulab import scipy as spy\n", + "\n", + "a = np.array([[3, 2, 1, 0], [2, 1, 0, 1], [1, 0, 1, 4], [1, 2, 1, 8]])\n", + "b = np.array([4, 2, 4, 2])\n", + "\n", + "print('a:\\n')\n", + "print(a)\n", + "print('\\nb: ', b)\n", + "\n", + "x = spy.linalg.solve_triangular(a, b, lower=True)\n", + "\n", + "print('='*20)\n", + "print('x: ', x)\n", + "print('\\ndot(a, x): ', np.dot(a, x))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/scipy-optimize.ipynb b/components/3rd_party/omv/omv/modules/ulab/docs/scipy-optimize.ipynb new file mode 100644 index 00000000..eea97b7e --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/scipy-optimize.ipynb @@ -0,0 +1,515 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T12:50:51.417613Z", + "start_time": "2021-01-08T12:50:51.208257Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Notebook magic" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T12:50:52.581876Z", + "start_time": "2021-01-08T12:50:52.567901Z" + } + }, + "outputs": [], + "source": [ + "from IPython.core.magic import Magics, magics_class, line_cell_magic\n", + "from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n", + "from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n", + "import subprocess\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T12:50:53.516712Z", + "start_time": "2021-01-08T12:50:53.454984Z" + } + }, + "outputs": [], + "source": [ + "@magics_class\n", + "class PyboardMagic(Magics):\n", + " @cell_magic\n", + " @magic_arguments()\n", + " @argument('-skip')\n", + " @argument('-unix')\n", + " @argument('-pyboard')\n", + " @argument('-file')\n", + " @argument('-data')\n", + " @argument('-time')\n", + " @argument('-memory')\n", + " def micropython(self, line='', cell=None):\n", + " args = parse_argstring(self.micropython, line)\n", + " if args.skip: # doesn't care about the cell's content\n", + " print('skipped execution')\n", + " return None # do not parse the rest\n", + " if args.unix: # tests the code on the unix port. Note that this works on unix only\n", + " with open('/dev/shm/micropython.py', 'w') as fout:\n", + " fout.write(cell)\n", + " proc = subprocess.Popen([\"../../micropython/ports/unix/micropython\", \"/dev/shm/micropython.py\"], \n", + " stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " print(proc.stdout.read().decode(\"utf-8\"))\n", + " print(proc.stderr.read().decode(\"utf-8\"))\n", + " return None\n", + " if args.file: # can be used to copy the cell content onto the pyboard's flash\n", + " spaces = \" \"\n", + " try:\n", + " with open(args.file, 'w') as fout:\n", + " fout.write(cell.replace('\\t', spaces))\n", + " printf('written cell to {}'.format(args.file))\n", + " except:\n", + " print('Failed to write to disc!')\n", + " return None # do not parse the rest\n", + " if args.data: # can be used to load data from the pyboard directly into kernel space\n", + " message = pyb.exec(cell)\n", + " if len(message) == 0:\n", + " print('pyboard >>>')\n", + " else:\n", + " print(message.decode('utf-8'))\n", + " # register new variable in user namespace\n", + " self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n", + " \n", + " if args.time: # measures the time of executions\n", + " pyb.exec('import utime')\n", + " message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n", + " \"\\nprint('execution time: {:d} us'.format(delta))\")\n", + " print(message.decode('utf-8'))\n", + " \n", + " if args.memory: # prints out memory information \n", + " message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n", + " print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n", + " message = pyb.exec(cell)\n", + " print(\">>> \", message.decode('utf-8'))\n", + " message = pyb.exec('print(mem_info())')\n", + " print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n", + "\n", + " if args.pyboard:\n", + " message = pyb.exec(cell)\n", + " print(message.decode('utf-8'))\n", + "\n", + "ip = get_ipython()\n", + "ip.register_magics(PyboardMagic)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pyboard" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:35.126401Z", + "start_time": "2020-05-07T07:35:35.105824Z" + } + }, + "outputs": [], + "source": [ + "import pyboard\n", + "pyb = pyboard.Pyboard('/dev/ttyACM0')\n", + "pyb.enter_raw_repl()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:11:18.145548Z", + "start_time": "2020-05-19T19:11:18.137468Z" + } + }, + "outputs": [], + "source": [ + "pyb.exit_raw_repl()\n", + "pyb.close()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:38.725924Z", + "start_time": "2020-05-07T07:35:38.645488Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "import ulab as np\n", + "\n", + "def timeit(n=1000):\n", + " def wrapper(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " run_times = np.zeros(n, dtype=np.uint16)\n", + " for i in range(n):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n", + " print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n", + " print('\\tbest: %d us'%np.min(run_times))\n", + " print('\\tworst: %d us'%np.max(run_times))\n", + " print('\\taverage: %d us'%np.mean(run_times))\n", + " print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n", + " return result\n", + " return new_func\n", + " return wrapper\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__END_OF_DEFS__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# scipy.optimize\n", + "\n", + "Functions in the `optimize` module can be called by prepending them by `scipy.optimize.`. The module defines the following three functions:\n", + "\n", + "1. [scipy.optimize.bisect](#bisect)\n", + "1. [scipy.optimize.fmin](#fmin)\n", + "1. [scipy.optimize.newton](#newton)\n", + "\n", + "Note that routines that work with user-defined functions still have to call the underlying `python` code, and therefore, gains in speed are not as significant as with other vectorised operations. As a rule of thumb, a factor of two can be expected, when compared to an optimised `python` implementation." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## bisect \n", + "\n", + "`scipy`: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.bisect.html\n", + "\n", + "`bisect` finds the root of a function of one variable using a simple bisection routine. It takes three positional arguments, the function itself, and two starting points. The function must have opposite signs\n", + "at the starting points. Returned is the position of the root.\n", + "\n", + "Two keyword arguments, `xtol`, and `maxiter` can be supplied to control the accuracy, and the number of bisections, respectively." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T12:58:28.444300Z", + "start_time": "2021-01-08T12:58:28.421989Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.9999997615814209\n", + "only 8 bisections: 0.984375\n", + "with 0.1 accuracy: 0.9375\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import scipy as spy\n", + " \n", + "def f(x):\n", + " return x*x - 1\n", + "\n", + "print(spy.optimize.bisect(f, 0, 4))\n", + "\n", + "print('only 8 bisections: ', spy.optimize.bisect(f, 0, 4, maxiter=8))\n", + "\n", + "print('with 0.1 accuracy: ', spy.optimize.bisect(f, 0, 4, xtol=0.1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Performance\n", + "\n", + "Since the `bisect` routine calls user-defined `python` functions, the speed gain is only about a factor of two, if compared to a purely `python` implementation." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:08:24.750562Z", + "start_time": "2020-05-19T19:08:24.682959Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bisect running in python\r\n", + "execution time: 1270 us\r\n", + "bisect running in C\r\n", + "execution time: 642 us\r\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "from ulab import scipy as spy\n", + "\n", + "def f(x):\n", + " return (x-1)*(x-1) - 2.0\n", + "\n", + "def bisect(f, a, b, xtol=2.4e-7, maxiter=100):\n", + " if f(a) * f(b) > 0:\n", + " raise ValueError\n", + "\n", + " rtb = a if f(a) < 0.0 else b\n", + " dx = b - a if f(a) < 0.0 else a - b\n", + " for i in range(maxiter):\n", + " dx *= 0.5\n", + " x_mid = rtb + dx\n", + " mid_value = f(x_mid)\n", + " if mid_value < 0:\n", + " rtb = x_mid\n", + " if abs(dx) < xtol:\n", + " break\n", + "\n", + " return rtb\n", + "\n", + "@timeit\n", + "def bisect_scipy(f, a, b):\n", + " return spy.optimize.bisect(f, a, b)\n", + "\n", + "@timeit\n", + "def bisect_timed(f, a, b):\n", + " return bisect(f, a, b)\n", + "\n", + "print('bisect running in python')\n", + "bisect_timed(f, 3, 2)\n", + "\n", + "print('bisect running in C')\n", + "bisect_scipy(f, 3, 2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## fmin\n", + "\n", + "`scipy`: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin.html\n", + "\n", + "The `fmin` function finds the position of the minimum of a user-defined function by using the downhill simplex method. Requires two positional arguments, the function, and the initial value. Three keyword arguments, `xatol`, `fatol`, and `maxiter` stipulate conditions for stopping." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T13:00:26.729947Z", + "start_time": "2021-01-08T13:00:26.702748Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.9996093749999952\n", + "1.199999999999996\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import scipy as spy\n", + "\n", + "def f(x):\n", + " return (x-1)**2 - 1\n", + "\n", + "print(spy.optimize.fmin(f, 3.0))\n", + "print(spy.optimize.fmin(f, 3.0, xatol=0.1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## newton\n", + "\n", + "`scipy`:https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.newton.html\n", + "\n", + "`newton` finds a zero of a real, user-defined function using the Newton-Raphson (or secant or Halley’s) method. The routine requires two positional arguments, the function, and the initial value. Three keyword\n", + "arguments can be supplied to control the iteration. These are the absolute and relative tolerances `tol`, and `rtol`, respectively, and the number of iterations before stopping, `maxiter`. The function retuns a single scalar, the position of the root." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T12:56:35.139958Z", + "start_time": "2021-01-08T12:56:35.119712Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.260135727246117\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import scipy as spy\n", + " \n", + "def f(x):\n", + " return x*x*x - 2.0\n", + "\n", + "print(spy.optimize.newton(f, 3., tol=0.001, rtol=0.01))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/scipy-signal.ipynb b/components/3rd_party/omv/omv/modules/ulab/docs/scipy-signal.ipynb new file mode 100644 index 00000000..ec10d2e6 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/scipy-signal.ipynb @@ -0,0 +1,387 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:11:12.111639Z", + "start_time": "2021-01-12T16:11:11.914041Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Notebook magic" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-29T20:50:20.813162Z", + "start_time": "2022-01-29T20:50:20.794562Z" + } + }, + "outputs": [], + "source": [ + "from IPython.core.magic import Magics, magics_class, line_cell_magic\n", + "from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n", + "from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n", + "import subprocess\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-29T20:50:21.613220Z", + "start_time": "2022-01-29T20:50:21.557819Z" + } + }, + "outputs": [], + "source": [ + "@magics_class\n", + "class PyboardMagic(Magics):\n", + " @cell_magic\n", + " @magic_arguments()\n", + " @argument('-skip')\n", + " @argument('-unix')\n", + " @argument('-pyboard')\n", + " @argument('-file')\n", + " @argument('-data')\n", + " @argument('-time')\n", + " @argument('-memory')\n", + " def micropython(self, line='', cell=None):\n", + " args = parse_argstring(self.micropython, line)\n", + " if args.skip: # doesn't care about the cell's content\n", + " print('skipped execution')\n", + " return None # do not parse the rest\n", + " if args.unix: # tests the code on the unix port. Note that this works on unix only\n", + " with open('/dev/shm/micropython.py', 'w') as fout:\n", + " fout.write(cell)\n", + " proc = subprocess.Popen([\"../micropython/ports/unix/micropython-2\", \"/dev/shm/micropython.py\"], \n", + " stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " print(proc.stdout.read().decode(\"utf-8\"))\n", + " print(proc.stderr.read().decode(\"utf-8\"))\n", + " return None\n", + " if args.file: # can be used to copy the cell content onto the pyboard's flash\n", + " spaces = \" \"\n", + " try:\n", + " with open(args.file, 'w') as fout:\n", + " fout.write(cell.replace('\\t', spaces))\n", + " printf('written cell to {}'.format(args.file))\n", + " except:\n", + " print('Failed to write to disc!')\n", + " return None # do not parse the rest\n", + " if args.data: # can be used to load data from the pyboard directly into kernel space\n", + " message = pyb.exec(cell)\n", + " if len(message) == 0:\n", + " print('pyboard >>>')\n", + " else:\n", + " print(message.decode('utf-8'))\n", + " # register new variable in user namespace\n", + " self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n", + " \n", + " if args.time: # measures the time of executions\n", + " pyb.exec('import utime')\n", + " message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n", + " \"\\nprint('execution time: {:d} us'.format(delta))\")\n", + " print(message.decode('utf-8'))\n", + " \n", + " if args.memory: # prints out memory information \n", + " message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n", + " print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n", + " message = pyb.exec(cell)\n", + " print(\">>> \", message.decode('utf-8'))\n", + " message = pyb.exec('print(mem_info())')\n", + " print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n", + "\n", + " if args.pyboard:\n", + " message = pyb.exec(cell)\n", + " print(message.decode('utf-8'))\n", + "\n", + "ip = get_ipython()\n", + "ip.register_magics(PyboardMagic)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pyboard" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:35.126401Z", + "start_time": "2020-05-07T07:35:35.105824Z" + } + }, + "outputs": [], + "source": [ + "import pyboard\n", + "pyb = pyboard.Pyboard('/dev/ttyACM0')\n", + "pyb.enter_raw_repl()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:11:18.145548Z", + "start_time": "2020-05-19T19:11:18.137468Z" + } + }, + "outputs": [], + "source": [ + "pyb.exit_raw_repl()\n", + "pyb.close()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:38.725924Z", + "start_time": "2020-05-07T07:35:38.645488Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "import ulab as np\n", + "\n", + "def timeit(n=1000):\n", + " def wrapper(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " run_times = np.zeros(n, dtype=np.uint16)\n", + " for i in range(n):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n", + " print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n", + " print('\\tbest: %d us'%np.min(run_times))\n", + " print('\\tworst: %d us'%np.max(run_times))\n", + " print('\\taverage: %d us'%np.mean(run_times))\n", + " print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n", + " return result\n", + " return new_func\n", + " return wrapper\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__END_OF_DEFS__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# scipy.signal\n", + "\n", + "This module defines the single function:\n", + "\n", + "1. [scipy.signal.sosfilt](#sosfilt)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## sosfilt\n", + "\n", + "`scipy`: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.sosfilt.html \n", + "\n", + "Filter data along one dimension using cascaded second-order sections.\n", + "\n", + "The function takes two positional arguments, `sos`, the filter segments of length 6, and the one-dimensional, uniformly sampled data set to be filtered. Returns the filtered data, or the filtered data and the final filter delays, if the `zi` keyword arguments is supplied. The keyword argument must be a float `ndarray` of shape `(n_sections, 2)`. If `zi` is not passed to the function, the initial values are assumed to be 0." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "ExecuteTime": { + "end_time": "2020-06-19T20:24:10.529668Z", + "start_time": "2020-06-19T20:24:10.520389Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "y: array([0.0, 1.0, -4.0, 24.0, -104.0, 440.0, -1728.0, 6532.000000000001, -23848.0, 84864.0], dtype=float)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "from ulab import scipy as spy\n", + "\n", + "x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])\n", + "sos = [[1, 2, 3, 1, 5, 6], [1, 2, 3, 1, 5, 6]]\n", + "y = spy.signal.sosfilt(sos, x)\n", + "print('y: ', y)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "ExecuteTime": { + "end_time": "2020-06-19T20:27:39.508508Z", + "start_time": "2020-06-19T20:27:39.498256Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "y: array([4.0, -16.0, 63.00000000000001, -227.0, 802.9999999999999, -2751.0, 9271.000000000001, -30775.0, 101067.0, -328991.0000000001], dtype=float)\n", + "\n", + "========================================\n", + "zf: array([[37242.0, 74835.0],\n", + "\t [1026187.0, 1936542.0]], dtype=float)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "from ulab import scipy as spy\n", + "\n", + "x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])\n", + "sos = [[1, 2, 3, 1, 5, 6], [1, 2, 3, 1, 5, 6]]\n", + "# initial conditions of the filter\n", + "zi = np.array([[1, 2], [3, 4]])\n", + "\n", + "y, zf = spy.signal.sosfilt(sos, x, zi=zi)\n", + "print('y: ', y)\n", + "print('\\n' + '='*40 + '\\nzf: ', zf)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/scipy-special.ipynb b/components/3rd_party/omv/omv/modules/ulab/docs/scipy-special.ipynb new file mode 100644 index 00000000..c3a0cf84 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/scipy-special.ipynb @@ -0,0 +1,344 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T18:54:58.722373Z", + "start_time": "2021-01-13T18:54:57.178438Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Notebook magic" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T18:57:41.555892Z", + "start_time": "2021-01-13T18:57:41.551121Z" + } + }, + "outputs": [], + "source": [ + "from IPython.core.magic import Magics, magics_class, line_cell_magic\n", + "from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n", + "from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n", + "import subprocess\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T18:57:42.313231Z", + "start_time": "2021-01-13T18:57:42.288402Z" + } + }, + "outputs": [], + "source": [ + "@magics_class\n", + "class PyboardMagic(Magics):\n", + " @cell_magic\n", + " @magic_arguments()\n", + " @argument('-skip')\n", + " @argument('-unix')\n", + " @argument('-pyboard')\n", + " @argument('-file')\n", + " @argument('-data')\n", + " @argument('-time')\n", + " @argument('-memory')\n", + " def micropython(self, line='', cell=None):\n", + " args = parse_argstring(self.micropython, line)\n", + " if args.skip: # doesn't care about the cell's content\n", + " print('skipped execution')\n", + " return None # do not parse the rest\n", + " if args.unix: # tests the code on the unix port. Note that this works on unix only\n", + " with open('/dev/shm/micropython.py', 'w') as fout:\n", + " fout.write(cell)\n", + " proc = subprocess.Popen([\"../../micropython/ports/unix/micropython\", \"/dev/shm/micropython.py\"], \n", + " stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " print(proc.stdout.read().decode(\"utf-8\"))\n", + " print(proc.stderr.read().decode(\"utf-8\"))\n", + " return None\n", + " if args.file: # can be used to copy the cell content onto the pyboard's flash\n", + " spaces = \" \"\n", + " try:\n", + " with open(args.file, 'w') as fout:\n", + " fout.write(cell.replace('\\t', spaces))\n", + " printf('written cell to {}'.format(args.file))\n", + " except:\n", + " print('Failed to write to disc!')\n", + " return None # do not parse the rest\n", + " if args.data: # can be used to load data from the pyboard directly into kernel space\n", + " message = pyb.exec(cell)\n", + " if len(message) == 0:\n", + " print('pyboard >>>')\n", + " else:\n", + " print(message.decode('utf-8'))\n", + " # register new variable in user namespace\n", + " self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n", + " \n", + " if args.time: # measures the time of executions\n", + " pyb.exec('import utime')\n", + " message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n", + " \"\\nprint('execution time: {:d} us'.format(delta))\")\n", + " print(message.decode('utf-8'))\n", + " \n", + " if args.memory: # prints out memory information \n", + " message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n", + " print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n", + " message = pyb.exec(cell)\n", + " print(\">>> \", message.decode('utf-8'))\n", + " message = pyb.exec('print(mem_info())')\n", + " print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n", + "\n", + " if args.pyboard:\n", + " message = pyb.exec(cell)\n", + " print(message.decode('utf-8'))\n", + "\n", + "ip = get_ipython()\n", + "ip.register_magics(PyboardMagic)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pyboard" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:35.126401Z", + "start_time": "2020-05-07T07:35:35.105824Z" + } + }, + "outputs": [], + "source": [ + "import pyboard\n", + "pyb = pyboard.Pyboard('/dev/ttyACM0')\n", + "pyb.enter_raw_repl()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:11:18.145548Z", + "start_time": "2020-05-19T19:11:18.137468Z" + } + }, + "outputs": [], + "source": [ + "pyb.exit_raw_repl()\n", + "pyb.close()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:38.725924Z", + "start_time": "2020-05-07T07:35:38.645488Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "import ulab as np\n", + "\n", + "def timeit(n=1000):\n", + " def wrapper(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " run_times = np.zeros(n, dtype=np.uint16)\n", + " for i in range(n):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n", + " print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n", + " print('\\tbest: %d us'%np.min(run_times))\n", + " print('\\tworst: %d us'%np.max(run_times))\n", + " print('\\taverage: %d us'%np.mean(run_times))\n", + " print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n", + " return result\n", + " return new_func\n", + " return wrapper\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__END_OF_DEFS__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# scipy.special\n", + "\n", + "`scipy`'s `special` module defines several functions that behave as do the standard mathematical functions of the `numpy`, i.e., they can be called on any scalar, scalar-valued iterable (ranges, lists, tuples containing numbers), and on `ndarray`s without having to change the call signature. In all cases the functions return a new `ndarray` of typecode `float` (since these functions usually generate float values, anyway). \n", + "\n", + "At present, `ulab`'s `special` module contains the following functions:\n", + "\n", + "`erf`, `erfc`, `gamma`, and `gammaln`, and they can be called by prepending them by `scipy.special.`." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T19:06:54.640444Z", + "start_time": "2021-01-13T19:06:54.623467Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: range(0, 9)\n", + "array([0.0, 0.8427007929497149, 0.9953222650189527, 0.9999779095030014, 0.9999999845827421, 1.0, 1.0, 1.0, 1.0], dtype=float64)\n", + "\n", + "b: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64)\n", + "array([1.0, 0.1572992070502851, 0.004677734981047265, 2.209049699858544e-05, 1.541725790028002e-08, 1.537459794428035e-12, 2.151973671249892e-17, 4.183825607779414e-23, 1.122429717298293e-29], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "from ulab import scipy as spy\n", + "\n", + "a = range(9)\n", + "b = np.array(a)\n", + "\n", + "print('a: ', a)\n", + "print(spy.special.erf(a))\n", + "\n", + "print('\\nb: ', b)\n", + "print(spy.special.erfc(b))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/templates/manual.tpl b/components/3rd_party/omv/omv/modules/ulab/docs/templates/manual.tpl new file mode 100644 index 00000000..ba6b73e9 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/templates/manual.tpl @@ -0,0 +1,113 @@ + +{%- extends 'display_priority.tpl' -%} + + +{% block in_prompt %} +{% endblock in_prompt %} + +{% block output_prompt %} +{% endblock output_prompt %} + +{% block input scoped%} + +{%- if cell.source.split('\n')[0].startswith('%%micropython') -%} +.. code:: + +{{ '\n'.join(['# code to be run in micropython'] + cell.source.strip().split('\n')[1:]) | indent}} + +{%- else -%} +.. code:: + +{{ '\n'.join(['# code to be run in CPython\n'] + cell.source.strip().split('\n')) | indent}} +{%- endif -%} +{% endblock input %} + +{% block error %} +:: + +{{ super() }} +{% endblock error %} + +{% block traceback_line %} +{{ line | indent | strip_ansi }} +{% endblock traceback_line %} + +{% block execute_result %} +{% block data_priority scoped %} +{{ super() }} +{% endblock %} +{% endblock execute_result %} + +{% block stream %} +.. parsed-literal:: + +{{ output.text | indent }} +{% endblock stream %} + +{% block data_svg %} +.. image:: {{ output.metadata.filenames['image/svg+xml'] | urlencode }} +{% endblock data_svg %} + +{% block data_png %} +.. image:: {{ output.metadata.filenames['image/png'] | urlencode }} +{%- set width=output | get_metadata('width', 'image/png') -%} +{%- if width is not none %} + :width: {{ width }}px +{%- endif %} +{%- set height=output | get_metadata('height', 'image/png') -%} +{%- if height is not none %} + :height: {{ height }}px +{%- endif %} +{% endblock data_png %} + +{% block data_jpg %} +.. image:: {{ output.metadata.filenames['image/jpeg'] | urlencode }} +{%- set width=output | get_metadata('width', 'image/jpeg') -%} +{%- if width is not none %} + :width: {{ width }}px +{%- endif %} +{%- set height=output | get_metadata('height', 'image/jpeg') -%} +{%- if height is not none %} + :height: {{ height }}px +{%- endif %} +{% endblock data_jpg %} + +{% block data_markdown %} +{{ output.data['text/markdown'] | convert_pandoc("markdown", "rst") }} +{% endblock data_markdown %} + +{% block data_latex %} +.. math:: + +{{ output.data['text/latex'] | strip_dollars | indent }} +{% endblock data_latex %} + +{% block data_text scoped %} +.. parsed-literal:: + +{{ output.data['text/plain'] | indent }} +{% endblock data_text %} + +{% block data_html scoped %} +.. raw:: html + +{{ output.data['text/html'] | indent }} +{% endblock data_html %} + +{% block markdowncell scoped %} +{{ cell.source | convert_pandoc("markdown", "rst") }} +{% endblock markdowncell %} + +{%- block rawcell scoped -%} +{%- if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) %} +{{cell.source}} +{% endif -%} +{%- endblock rawcell -%} + +{% block headingcell scoped %} +{{ ("#" * cell.level + cell.source) | replace('\n', ' ') | convert_pandoc("markdown", "rst") }} +{% endblock headingcell %} + +{% block unknowncell scoped %} +unknown type {{cell.type}} +{% endblock unknowncell %} diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/templates/rst.tpl b/components/3rd_party/omv/omv/modules/ulab/docs/templates/rst.tpl new file mode 100644 index 00000000..479a69fc --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/templates/rst.tpl @@ -0,0 +1,144 @@ + +{%- extends 'display_priority.tpl' -%} + + +{% block in_prompt %} +{% endblock in_prompt %} + +{% block output_prompt %} +{% endblock output_prompt %} + +{% block input scoped%} + +{%- if '%%ccode' in cell.source.strip().split('\n')[0] -%} + +{{ 'https://github.com/v923z/micropython-ulab/tree/master/code/' + cell.source.strip().split('\n')[0].split()[-1] }} + +.. code:: cpp + +{{ '\n'.join( cell.source.strip().split('\n')[1:] ) | indent }} + +{%- elif '%%makefile' in cell.source.strip().split('\n')[0] -%} + +{{ 'https://github.com/v923z/micropython-ulab/tree/master/code/' + cell.source.strip().split('\n')[0].split()[-1].split('/')[1] + '/micropython.mk' }} + +.. code:: make + +{{ '\n'.join( cell.source.strip().split('\n')[1:] ) | indent }} + +{%- elif cell.source.strip().split('\n')[0].startswith('!') -%} + +.. code:: bash + +{{ cell.source | indent }} + +{%- else -%} +{%- if 'magics_language' in cell.metadata -%} + {{ cell.metadata.magics_language}} +{%- elif 'pygments_lexer' in nb.metadata.get('language_info', {}) -%} + {{ nb.metadata.language_info.pygments_lexer }} +{%- elif 'name' in nb.metadata.get('language_info', {}) -%} + {{ nb.metadata.language_info.name }} +{%- endif -%} + +.. code :: + +{{ cell.source | indent}} +{%- endif -%} +{% endblock input %} + +{% block error %} +:: + +{{ super() }} +{% endblock error %} + +{% block traceback_line %} +{{ line | indent | strip_ansi }} +{% endblock traceback_line %} + +{% block execute_result %} +{% block data_priority scoped %} +{{ super() }} +{% endblock %} +{% endblock execute_result %} + +{% block stream %} +{%- if '%%ccode' in cell.source.strip().split('\n')[0] -%} +{%- else -%} + +.. parsed-literal:: + +{{ output.text | indent }} +{%- endif -%} +{% endblock stream %} + +{% block data_svg %} +.. image:: {{ output.metadata.filenames['image/svg+xml'] | urlencode }} +{% endblock data_svg %} + +{% block data_png %} +.. image:: {{ output.metadata.filenames['image/png'] | urlencode }} +{%- set width=output | get_metadata('width', 'image/png') -%} +{%- if width is not none %} + :width: {{ width }}px +{%- endif %} +{%- set height=output | get_metadata('height', 'image/png') -%} +{%- if height is not none %} + :height: {{ height }}px +{%- endif %} +{% endblock data_png %} + +{% block data_jpg %} +.. image:: {{ output.metadata.filenames['image/jpeg'] | urlencode }} +{%- set width=output | get_metadata('width', 'image/jpeg') -%} +{%- if width is not none %} + :width: {{ width }}px +{%- endif %} +{%- set height=output | get_metadata('height', 'image/jpeg') -%} +{%- if height is not none %} + :height: {{ height }}px +{%- endif %} +{% endblock data_jpg %} + +{% block data_markdown %} +{{ output.data['text/markdown'] | convert_pandoc("markdown", "rst") }} +{% endblock data_markdown %} + +{% block data_latex %} +.. math:: + +{{ output.data['text/latex'] | strip_dollars | indent }} +{% endblock data_latex %} + +{% block data_text scoped %} + +.. parsed-literal:: + +{{ output.data['text/plain'] | indent }} +{% endblock data_text %} + +{% block data_html scoped %} +.. raw:: html + +{{ output.data['text/html'] | indent }} +{% endblock data_html %} + +{% block markdowncell scoped %} +{{ cell.source | convert_pandoc("markdown", "rst") }} +{% endblock markdowncell %} + +{%- block rawcell scoped -%} +{%- if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) %} +{{cell.source}} +{% endif -%} +{%- endblock rawcell -%} + +{% block headingcell scoped %} +{{ ("#" * cell.level + cell.source) | replace('\n', ' ') | convert_pandoc("markdown", "rst") }} + +{% endblock headingcell %} + +{% block unknowncell scoped %} +unknown type {{cell.type}} +{% endblock unknowncell %} diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/ulab-approx.ipynb b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-approx.ipynb new file mode 100644 index 00000000..52dc205e --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-approx.ipynb @@ -0,0 +1,613 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T12:50:51.417613Z", + "start_time": "2021-01-08T12:50:51.208257Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Notebook magic" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T12:50:52.581876Z", + "start_time": "2021-01-08T12:50:52.567901Z" + } + }, + "outputs": [], + "source": [ + "from IPython.core.magic import Magics, magics_class, line_cell_magic\n", + "from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n", + "from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n", + "import subprocess\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T12:50:53.516712Z", + "start_time": "2021-01-08T12:50:53.454984Z" + } + }, + "outputs": [], + "source": [ + "@magics_class\n", + "class PyboardMagic(Magics):\n", + " @cell_magic\n", + " @magic_arguments()\n", + " @argument('-skip')\n", + " @argument('-unix')\n", + " @argument('-pyboard')\n", + " @argument('-file')\n", + " @argument('-data')\n", + " @argument('-time')\n", + " @argument('-memory')\n", + " def micropython(self, line='', cell=None):\n", + " args = parse_argstring(self.micropython, line)\n", + " if args.skip: # doesn't care about the cell's content\n", + " print('skipped execution')\n", + " return None # do not parse the rest\n", + " if args.unix: # tests the code on the unix port. Note that this works on unix only\n", + " with open('/dev/shm/micropython.py', 'w') as fout:\n", + " fout.write(cell)\n", + " proc = subprocess.Popen([\"../../micropython/ports/unix/micropython\", \"/dev/shm/micropython.py\"], \n", + " stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " print(proc.stdout.read().decode(\"utf-8\"))\n", + " print(proc.stderr.read().decode(\"utf-8\"))\n", + " return None\n", + " if args.file: # can be used to copy the cell content onto the pyboard's flash\n", + " spaces = \" \"\n", + " try:\n", + " with open(args.file, 'w') as fout:\n", + " fout.write(cell.replace('\\t', spaces))\n", + " printf('written cell to {}'.format(args.file))\n", + " except:\n", + " print('Failed to write to disc!')\n", + " return None # do not parse the rest\n", + " if args.data: # can be used to load data from the pyboard directly into kernel space\n", + " message = pyb.exec(cell)\n", + " if len(message) == 0:\n", + " print('pyboard >>>')\n", + " else:\n", + " print(message.decode('utf-8'))\n", + " # register new variable in user namespace\n", + " self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n", + " \n", + " if args.time: # measures the time of executions\n", + " pyb.exec('import utime')\n", + " message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n", + " \"\\nprint('execution time: {:d} us'.format(delta))\")\n", + " print(message.decode('utf-8'))\n", + " \n", + " if args.memory: # prints out memory information \n", + " message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n", + " print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n", + " message = pyb.exec(cell)\n", + " print(\">>> \", message.decode('utf-8'))\n", + " message = pyb.exec('print(mem_info())')\n", + " print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n", + "\n", + " if args.pyboard:\n", + " message = pyb.exec(cell)\n", + " print(message.decode('utf-8'))\n", + "\n", + "ip = get_ipython()\n", + "ip.register_magics(PyboardMagic)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pyboard" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:35.126401Z", + "start_time": "2020-05-07T07:35:35.105824Z" + } + }, + "outputs": [], + "source": [ + "import pyboard\n", + "pyb = pyboard.Pyboard('/dev/ttyACM0')\n", + "pyb.enter_raw_repl()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:11:18.145548Z", + "start_time": "2020-05-19T19:11:18.137468Z" + } + }, + "outputs": [], + "source": [ + "pyb.exit_raw_repl()\n", + "pyb.close()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:38.725924Z", + "start_time": "2020-05-07T07:35:38.645488Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "import ulab as np\n", + "\n", + "def timeit(n=1000):\n", + " def wrapper(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " run_times = np.zeros(n, dtype=np.uint16)\n", + " for i in range(n):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n", + " print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n", + " print('\\tbest: %d us'%np.min(run_times))\n", + " print('\\tworst: %d us'%np.max(run_times))\n", + " print('\\taverage: %d us'%np.mean(run_times))\n", + " print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n", + " return result\n", + " return new_func\n", + " return wrapper\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__END_OF_DEFS__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Approximation methods\n", + "\n", + "`ulab` implements five functions that can be used for interpolating, root finding, and minimising arbitrary `python` functions in one dimension. Two of these functions, namely, `interp`, and `trapz` are defined in `numpy`, while the other three are parts of `scipy`'s `optimize` module. \n", + "\n", + "Note that routines that work with user-defined functions still have to call the underlying `python` code, and therefore, gains in speed are not as significant as with other vectorised operations. As a rule of thumb, a factor of two can be expected, when compared to an optimised `python` implementation." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## interp\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/numpy.interp\n", + "\n", + "The `interp` function returns the linearly interpolated values of a one-dimensional numerical array. It requires three positional arguments,`x`, at which the interpolated values are evaluated, `xp`, the array\n", + "of the independent data variable, and `fp`, the array of the dependent values of the data. `xp` must be a monotonically increasing sequence of numbers.\n", + "\n", + "Two keyword arguments, `left`, and `right` can also be supplied; these determine the return values, if `x < xp[0]`, and `x > xp[-1]`, respectively. If these arguments are not supplied, `left`, and `right` default to `fp[0]`, and `fp[-1]`, respectively." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T12:54:58.895801Z", + "start_time": "2021-01-08T12:54:58.869338Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([0.8, 1.8, 2.8, 3.8, 4.8], dtype=float64)\n", + "array([1.0, 1.8, 2.8, 4.6, 5.0], dtype=float64)\n", + "array([0.0, 1.8, 2.8, 4.6, 5.0], dtype=float64)\n", + "array([1.0, 1.8, 2.8, 4.6, 10.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "x = np.array([1, 2, 3, 4, 5]) - 0.2\n", + "xp = np.array([1, 2, 3, 4])\n", + "fp = np.array([1, 2, 3, 5])\n", + "\n", + "print(x)\n", + "print(np.interp(x, xp, fp))\n", + "print(np.interp(x, xp, fp, left=0.0))\n", + "print(np.interp(x, xp, fp, right=10.0))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## newton\n", + "\n", + "`scipy`:https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.newton.html\n", + "\n", + "`newton` finds a zero of a real, user-defined function using the Newton-Raphson (or secant or Halley’s) method. The routine requires two positional arguments, the function, and the initial value. Three keyword\n", + "arguments can be supplied to control the iteration. These are the absolute and relative tolerances `tol`, and `rtol`, respectively, and the number of iterations before stopping, `maxiter`. The function retuns a single scalar, the position of the root." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T12:56:35.139958Z", + "start_time": "2021-01-08T12:56:35.119712Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.260135727246117\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import scipy as spy\n", + " \n", + "def f(x):\n", + " return x*x*x - 2.0\n", + "\n", + "print(spy.optimize.newton(f, 3., tol=0.001, rtol=0.01))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## bisect \n", + "\n", + "`scipy`: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.bisect.html\n", + "\n", + "`bisect` finds the root of a function of one variable using a simple bisection routine. It takes three positional arguments, the function itself, and two starting points. The function must have opposite signs\n", + "at the starting points. Returned is the position of the root.\n", + "\n", + "Two keyword arguments, `xtol`, and `maxiter` can be supplied to control the accuracy, and the number of bisections, respectively." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T12:58:28.444300Z", + "start_time": "2021-01-08T12:58:28.421989Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.9999997615814209\n", + "only 8 bisections: 0.984375\n", + "with 0.1 accuracy: 0.9375\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import scipy as spy\n", + " \n", + "def f(x):\n", + " return x*x - 1\n", + "\n", + "print(spy.optimize.bisect(f, 0, 4))\n", + "\n", + "print('only 8 bisections: ', spy.optimize.bisect(f, 0, 4, maxiter=8))\n", + "\n", + "print('with 0.1 accuracy: ', spy.optimize.bisect(f, 0, 4, xtol=0.1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Performance\n", + "\n", + "Since the `bisect` routine calls user-defined `python` functions, the speed gain is only about a factor of two, if compared to a purely `python` implementation." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:08:24.750562Z", + "start_time": "2020-05-19T19:08:24.682959Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bisect running in python\r\n", + "execution time: 1270 us\r\n", + "bisect running in C\r\n", + "execution time: 642 us\r\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "from ulab import scipy as spy\n", + "\n", + "def f(x):\n", + " return (x-1)*(x-1) - 2.0\n", + "\n", + "def bisect(f, a, b, xtol=2.4e-7, maxiter=100):\n", + " if f(a) * f(b) > 0:\n", + " raise ValueError\n", + "\n", + " rtb = a if f(a) < 0.0 else b\n", + " dx = b - a if f(a) < 0.0 else a - b\n", + " for i in range(maxiter):\n", + " dx *= 0.5\n", + " x_mid = rtb + dx\n", + " mid_value = f(x_mid)\n", + " if mid_value < 0:\n", + " rtb = x_mid\n", + " if abs(dx) < xtol:\n", + " break\n", + "\n", + " return rtb\n", + "\n", + "@timeit\n", + "def bisect_scipy(f, a, b):\n", + " return spy.optimize.bisect(f, a, b)\n", + "\n", + "@timeit\n", + "def bisect_timed(f, a, b):\n", + " return bisect(f, a, b)\n", + "\n", + "print('bisect running in python')\n", + "bisect_timed(f, 3, 2)\n", + "\n", + "print('bisect running in C')\n", + "bisect_scipy(f, 3, 2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## fmin\n", + "\n", + "`scipy`: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin.html\n", + "\n", + "The `fmin` function finds the position of the minimum of a user-defined function by using the downhill simplex method. Requires two positional arguments, the function, and the initial value. Three keyword arguments, `xatol`, `fatol`, and `maxiter` stipulate conditions for stopping." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T13:00:26.729947Z", + "start_time": "2021-01-08T13:00:26.702748Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.9996093749999952\n", + "1.199999999999996\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import scipy as spy\n", + "\n", + "def f(x):\n", + " return (x-1)**2 - 1\n", + "\n", + "print(spy.optimize.fmin(f, 3.0))\n", + "print(spy.optimize.fmin(f, 3.0, xatol=0.1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## trapz\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.trapz.html\n", + "\n", + "The function takes one or two one-dimensional `ndarray`s, and integrates the dependent values (`y`) using the trapezoidal rule. If the independent variable (`x`) is given, that is taken as the sample points corresponding to `y`." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T13:01:29.515166Z", + "start_time": "2021-01-08T13:01:29.494285Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "x: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0], dtype=float64)\n", + "y: array([0.0, 1.0, 4.0, 9.0, 16.0, 25.0, 36.0, 49.0, 64.0, 81.0], dtype=float64)\n", + "============================\n", + "integral of y: 244.5\n", + "integral of y at x: 244.5\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "x = np.linspace(0, 9, num=10)\n", + "y = x*x\n", + "\n", + "print('x: ', x)\n", + "print('y: ', y)\n", + "print('============================')\n", + "print('integral of y: ', np.trapz(y))\n", + "print('integral of y at x: ', np.trapz(y, x=x))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/ulab-change-log.md b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-change-log.md new file mode 100644 index 00000000..f4aba8b6 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-change-log.md @@ -0,0 +1,1155 @@ +Sun, 7 May 2023 + +version 6.0.12 + + ndarray_from_mp_obj correctly treats Boolean arguments + +Sat, 6 May 2023 + +version 6.0.11 + + .reshape can now interpret unknown shape dimension + +Sat, 6 May 2023 + +version 6.0.10 + + fix binary division + +Sun, 21 Jan 2023 + +version 6.0.6 + + raise proper exception in arange + +Sun, 21 Jan 2023 + +version 6.0.7 + + treat empty arrays in sort_complex correctly + +Sun, 21 Jan 2023 + +version 6.0.5 + + fix ones()/zeros() method when the amount of memory to allocate overflows + +Sun, 15 Jan 2023 + +version 6.0.4 + + fix dot function + +Sat, 14 Jan 2023 + +version 6.0.3 + + fix how concatenate deals with scalar inputs + +Tue, 3 Jan 2023 + +version 6.0.2 + + fix vectorize + +Sat, 5 Nov 2022 + +version 6.0.1 + + fix fft.ifft + +Wed, 21 Sep 2022 + +version 6.0.0 + + bring ulab in line with the latest version of micropython + +Thu, 4 Aug 2022 + +version 5.1.1 + + fix how arctan2 treats scalars + +Mon, 25 July 2022 + +version 5.1.0 + + add nonzero + +Mon, 16 May 2022 + +version 5.0.7 + + fix in-place assignment from slices + +Thu, 14 Apr 2022 + +version 5.0.6 + + use m_new0 conditionally + +Thu, 14 Apr 2022 + +version 5.0.5 + + fix sorting on empty arrays + +Fri, 18 Feb 2022 + +version 5.0.4 + + fix the handling of empty arrays in binary_op + +Thu, 10 Feb 2022 + +version 5.0.3 + + fix complex slicing + +Tue, 8 Feb 2022 + +version 5.0.2 + + fix np.diag + +Thu, 3 Feb 2022 + +version 5.0.1 + + add optional ULAB_HASH string + +Tue, 1 Feb 2022 + +version 5.0.0 + + move scipy.signal.spectrogram to utils.spectrogram + +Tue, 1 Feb 2022 + +version 4.4.2 + + add skiprows keyword to loadtxt + +Sat, 29 Jan 2022 + +version 4.4.1 + + add dtype keyword to loadtxt + +Thu, 27 Jan 2022 + +version 4.4.0 + + implement numpy.savetxt, numpy.loadtxt + +Tue, 15 Jan 2022 + +version 4.3.2 + + fix rp2 port compilation + +Wed, 19 Jan 2022 + +version 4.3.1 + + fix signal.sosfilt + +Wed, 19 Jan 2022 + +version 4.3.0 + + implement numpy.save, numpy.load + +Tue, 18 Jan 2022 + +version 4.2.1 + + fix ndarray_copy_view for Boolean dtypes + +Fri, 14 Jan 2022 + +version 4.2.0 + + add numpy.size, asarray + +Wed, 12 Jan 2022 + + version 4.2.0 + + implement numpy.save, numpy.load + +Wed, 12 Jan 2022 + +version 4.1.1 + + fix complex printout for long arrays + +Wed, 12 Jan 2022 + +version 4.1.0 + + add numpy.delete + +Sat, 8 Jan 2022 + +version 4.0.0 + + add complex support, .tolist() method, .imag, .real array properties, compress, conjugate, imag, real, sort_complex functions + +Fri, 3 Dec 2021 + +version 3.3.8 + + fix any/all function + +Tue, 30 Nov 2021 + +version 3.3.7 + + fix sum() for integer/Boolean types + +Sat, 20 Nov 2021 + +version 3.3.6 + + fix .shape for arrays of zero length (#454) + +Sun, 07 Nov 2021 + +version 3.3.5 + + fix cast in numpy/compare.c:compare_function() + +Sat, 07 Aug 2021 + +version 3.3.4 + + change default keyword value in linalg.qr + +Fri, 23 Jul 2021 + +version 3.3.3 + + fix compilation for one dimension + +Thu, 22 Jul 2021 + +version 3.3.2 + + fix compilation error on SAMD devices + +Thu, 22 Jul 2021 + +version 3.3.1 + + fix sum for 4D arrays + +Thu, 22 Jul 2021 + +version 3.3.0 + + add QR decomposition + +Tue, 13 Jul 2021 + +version 3.2.0 + + add flatiter/flat to ndarray methods + +Tue, 22 Jun 2021 + +version 3.1.1 + + fix float comparison in scipy/linalg.c + +Sat, 19 Jun 2021 + +version 3.1.0 + + ndarray.shape can now be assigned to + +Thu, 17 Jun 2021 + +version 3.0.1 + + add the .T ndarray property + +Wed, 9 Jun 2021 + +version 3.0.0 + + implement property getter/setter for micropython + +Thu, 3 Jun 2021 + +version 2.9.0 + + add empty as alias for zeros + +Thu, 3 Jun 2021 + +version 2.8.8 + + allow functions in approx to take iterables as argument + +Thu, 3 Jun 2021 + +version 2.8.7 + + simplify vectorised function code + +Wed, 2 Jun 2021 + +version 2.8.6 + + factor out array creation from iterables, so that generic iterables can be passed to numerical functions + +Tue, 1 Jun 2021 + +version 2.8.5 + + fix upcasting rules for ndarray + scalar + +Mon, 31 May 2021 + +version 2.8.4 + + initialise arange values via macro + +Mon, 24 May 2021 + +version 2.8.3 + + fix nan return value + +Sat, 22 May 2021 + +version 2.8.2 + + fix all/any/median for empty arrays + +Tue, 18 May 2021 + +version 2.8.1 + + fix array initialisation/print with empty iterables + +Sun, 16 May 2021 + +version 2.8.0 + + added cho_solve function in scipy.linalg module + +Thu, 13 May 2021 + +version 2.7.1 + + fix garbage collection problem + +Wed, 5 May 2021 + +version 2.7.0 + + added linalg module in scipy with solve_triangular function + +Mon, 26 Apr 2021 + +version 2.6.2 + + fix optimize zero condition + +Sat, 23 Apr 2021 + +version 2.6.1 + + fix implementation of math constants + + +Mon, 22 Mar 2021 + +version 2.6.0 + + add where function + +Mon, 8 Mar 2021 + +version 2.5.1 + + fix linspace/logspace/arange for Boolean dtypes + +Wed, 03 Mar 2021 + +version 2.5.0 + + added utils sub-module with from_intbuffer function + +Tue, 23 Feb 2021 + +version 2.4.5 + + fix dot function + +Sun, 21 Feb 2021 + +version 2.4.3 + + re-introduce ndarray_get_buffer, and buffer protocol + +Sun, 21 Feb 2021 + +version 2.4.2 + + fix ndarray_is_dense, eye, ones, full, and zeros for Boolean type + +Sat, 13 Feb 2021 + +version 2.4.1 + + fixed dot error + +Fri, 12 Feb 2021 + +version 2.4.0 + + added byteswap method + +Sun, 14 Feb 2021 + +version 2.3.7 + + fixed frombuffer implementation glitch + +Sat, 13 Feb 2021 + +version 2.3.6 + + moved trace and dot to the top level + +Wed, 10 Feb 2021 + +version 2.3.5 + + fixed invisible error in tools_reduce_axes, simplified the implementation of all/any + +Tue, 9 Feb 2021 + +version 2.3.4 + + removed redundant exception from linalg.norm, fixed exception message in tools_reduce_axes + +Tue, 9 Feb 2021 + +version 2.3.3 + + linalg.norm should now work with the axis keyword argument + +Mon, 8 Feb 2021 + +version 2.3.2 + + improved the accuracy of linalg.norm, and extended it to generic iterables + +Mon, 8 Feb 2021 + +version 2.3.1 + + partially fix https://github.com/v923z/micropython-ulab/issues/304, and len unary operator + +Mon, 8 Feb 2021 + +version 2.3.0 + + added any and all functions + +Fri, 29 Jan 2021 + +version 2.2.0 + + added isinf/infinite functions + +Fri, 29 Jan 2021 + +version 2.1.5 + + fixed error, when calculating standard deviation of iterables + +wed, 27 Jan 2021 + +version 2.1.4 + + arrays can now be initialised from nested iterables + +Thu, 21 Jan 2021 + +version 2.1.3 + + added ifndef/endif wrappers in ulab.h + +Fri, 15 Jan 2021 + +version 2.1.2 + + fixed small error in frombuffer + +Thu, 14 Jan 2021 + +version 2.1.1 + + fixed bad error in diff + +Thu, 26 Nov 2020 + +version 2.1.0 + + implemented frombuffer + +Tue, 24 Nov 2020 + +version 2.0.0 + + implemented numpy/scipy compatibility + +Tue, 24 Nov 2020 + +version 1.6.0 + + added Boolean initialisation option + +Mon, 23 Nov 2020 + +version 1.5.1 + + fixed nan definition + +version 1.5.0 + + added nan/inf class level constants + +version 1.4.10 + + fixed sosfilt + +version 1.4.9 + + added in-place sort + +version 1.4.8 + + fixed convolve + +version 1.4.7. + + fixed iteration loop in norm + +Fri, 20 Nov 2020 + +version 1.4.6 + + fixed interp + +Thu, 19 Nov 2020 + +version 1.4.5 + + eliminated fatal micropython error in ndarray_init_helper + +version 1.4.4 + + fixed min, max + +version 1.4.3 + + fixed full, zeros, ones + +version 1.4.2 + + fixed dtype + +Wed, 18 Nov 2020 + +version 1.4.1. + + fixed std + +version 1.4.0 + + removed size from linalg + +version 1.3.8 + + fixed trapz + +Tue, 17 Nov 2020 + +version 1.3.7 + + fixed in-place power, in-place divide, roll + +Mon, 16 Nov 2020 + +version 1.3.6 + + fixed eye + +Mon, 16 Nov 2020 + +version 1.3.5 + + fixed trace + +Mon, 16 Nov 2020 + +version 1.3.4 + + fixed clip + +Mon, 16 Nov 2020 + +version 1.3.3 + + added function pointer option to some binary operators + +Fri, 13 Nov 2020 + +version 1.3.2 + + implemented function pointer option in vectorise + +Thu, 12 Nov 2020 + +version 1.3.1 + + factored out some of the math functions in re-usable form + +Wed, 11 Nov 2020 + +version 1.3.0 + + added dtype function/method/property + +Wed, 11 Nov 2020 + +version 1.2.8 + + improved the accuracy of sum for float types + +Wed, 11 Nov 2020 + +version 1.2.7 + + fixed transpose + improved the accuracy of trapz + +Tue, 10 Nov 2020 + +version 1.2.6 + + fixed slicing + +Mon, 9 Nov 2020 + +version 1.2.5 + + fixed array casting glitch in make_new_core + +Mon, 9 Nov 2020 + +version 1.2.4 + + sum/mean/std can flatten the arrays now + +Tue, 3 Nov 2020 + +version 1.2.1 + + fixed pointer issue in eig, and corrected the docs + +Tue, 3 Nov 2020 + +version 1.2.0 + + added median function + +Tue, 3 Nov 2020 + +version 1.1.4 + + fixed norm and shape + +Mon, 2 Nov 2020 + +version 1.1.3 + + fixed small glitch in diagonal, and ndarray_make_new_core + +Sun, 1 Nov 2020 + +version 1.1.1 + + fixed compilation error for 4D + +Sat, 31 Oct 2020 + +version 1.1.0 + + added the diagonal function + +Fri, 30 Oct 2020 + +version 1.0.0 + + added : + support for tensors of rank 4 + proper broadcasting + views + .tobytes() + concatenate + cross + full + logspace + in-place operators + +Sat, 25 Oct 2020 + +version 0.54.5 + + wrong type in slices raise TypeError exception + +Fri, 23 Oct 2020 + +version 0.54.4 + + fixed indexing error in slices + +Mon, 17 Aug 2020 + +version 0.54.3 + + fixed small error in linalg + +Mon, 03 Aug 2020 + +version 0.54.2 + + argsort throws an error, if the array is longer than 65535 + +Wed, 29 Jul 2020 + +version 0.54.1 + + changed to size_t for the length of arrays + +Thu, 23 Jul 2020 + +version 0.54.0 + + added norm to linalg + +Wed, 22 Jul 2020 + +version 0.53.2 + + added circuitpython documentation stubs to the source files + +Wed, 22 Jul 2020 + +version 0.53.1 + + fixed arange with negative steps + +Mon, 20 Jul 2020 + +version 0.53.0 + + added arange to create.c + +Thu, 16 Jul 2020 + +version 0.52.0 + + added trapz to approx + +Mon, 29 Jun 2020 + +version 0.51.1 + + fixed argmin/argmax issue + +Fri, 19 Jun 2020 + +version 0.51.0 + + add sosfilt to the filter sub-module + +Fri, 12 Jun 2020 + +version 0.50.2 + + fixes compilation error in openmv + +Mon, 1 Jun 2020 + +version 0.50.1 + + fixes error in numerical max/min + +Mon, 18 May 2020 + +version 0.50.0 + + move interp to the approx sub-module + +Wed, 06 May 2020 + +version 0.46.0 + + add curve_fit to the approx sub-module + +version 0.44.0 + + add approx sub-module with newton, fmin, and bisect functions + +Thu, 30 Apr 2020 + +version 0.44.0 + + add approx sub-module with newton, fmin, and bisect functions + +Tue, 19 May 2020 + +version 0.46.1 + + fixed bad error in binary_op + +Wed, 6 May 2020 + +version 0.46 + + added vectorisation of python functions + +Sat, 2 May 2020 + +version 0.45.0 + + add equal/not_equal to the compare module + +Tue, 21 Apr 2020 + +version 0.42.0 + + add minimum/maximum/clip functions + +Mon, 20 Apr 2020 + +version 0.41.6 + + argument handling improvement in polyfit + +Mon, 20 Apr 2020 + +version 0.41.5 + + fix compilation errors due to https://github.com/micropython/micropython/commit/30840ebc9925bb8ef025dbc2d5982b1bfeb75f1b + +Sat, 18 Apr 2020 + +version 0.41.4 + + fix compilation error on hardware ports + +Tue, 14 Apr 2020 + +version 0.41.3 + + fix indexing error in dot function + +Thu, 9 Apr 2020 + +version 0.41.2 + + fix transpose function + +Tue, 7 Apr 2020 + +version 0.41.2 + + fix discrepancy in argmin/argmax behaviour + +Tue, 7 Apr 2020 + +version 0.41.1 + + fix error in argsort + +Sat, 4 Apr 2020 + +version 0.41.0 + + implemented == and != binary operators + +Fri, 3 Apr 2020 + +version 0.40.0 + + added trace to linalg + +Thu, 2 Apr 2020 + +version 0.39.0 + + added the ** operator, and operand swapping in binary operators + +Thu, 2 Apr 2020 + +version 0.38.1 + + added fast option, when initialising from ndarray_properties + +Thu, 12 Mar 2020 + +version 0.38.0 + + added initialisation from ndarray, and the around function + +Tue, 10 Mar 2020 + +version 0.37.0 + + added Cholesky decomposition to linalg.c + +Thu, 27 Feb 2020 + +version 0.36.0 + + moved zeros, ones, eye and linspace into separate module (they are still bound at the top level) + +Thu, 27 Feb 2020 + +version 0.35.0 + + Move zeros, ones back into top level ulab module + +Tue, 18 Feb 2020 + +version 0.34.0 + + split ulab into multiple modules + +Sun, 16 Feb 2020 + +version 0.33.2 + + moved properties into ndarray_properties.h, implemented pointer arithmetic in fft.c to save some time + +Fri, 14 Feb 2020 + +version 0.33.1 + + added the __name__attribute to all sub-modules + +Thu, 13 Feb 2020 + +version 0.33.0 + + sub-modules are now proper sub-modules of ulab + +Mon, 17 Feb 2020 + +version 0.32.1 + + temporary fix for issue #40 + +Tue, 11 Feb 2020 + +version 0.32.0 + + added itemsize, size and shape attributes to ndarrays, and removed rawsize + +Mon, 10 Feb 2020 + +version 0.31.0 + + removed asbytearray, and added buffer protocol to ndarrays, fixed bad error in filter.c + +Sun, 09 Feb 2020 + +version 0.30.2 + + fixed slice_length in ndarray.c + +Sat, 08 Feb 2020 + +version 0.30.1 + + fixed typecode error, added variable inspection, and replaced ternary operators in filter.c + +Fri, 07 Feb 2020 + +version 0.30.0 + + ulab functions can arbitrarily be excluded from the firmware via the ulab.h configuration file + +Thu, 06 Feb 2020 + +version 0.27.0 + + add convolve, the start of a 'filter' functionality group + +Wed, 29 Jan 2020 + +version 0.26.7 + + fixed indexing error in linalg.dot + +Mon, 20 Jan 2020 + +version 0.26.6 + + replaced MP_ROM_PTR(&mp_const_none_obj), so that module can be compiled for the nucleo board + +Tue, 7 Jan 2020 + +version 0.26.5 + + fixed glitch in numerical.c, numerical.h + +Mon, 6 Jan 2020 + +version 0.26.4 + + switched version constant to string + +Tue, 31 Dec 2019 + +version 0.263 + + changed declaration of ulab_ndarray_type to extern + +Fri, 29 Nov 2019 + +version 0.262 + + fixed error in macro in vectorise.h + +Thu, 28 Nov 2019 + +version 0.261 + + fixed bad indexing error in linalg.dot + +Tue, 6 Nov 2019 + +version 0.26 + + added in-place sorting (method of ndarray), and argsort + +Mon, 4 Nov 2019 + +version 0.25 + + added first implementation of sort, and fixed section on compiling the module in the manual + +Thu, 31 Oct 2019 + +version 0.24 + + added diff to numerical.c + +Tue, 29 Oct 2019 + +version 0.23 + + major revamp of subscription method + +Sat, 19 Oct 2019 + +version 0.21 + + fixed trivial bug in .rawsize() + +Sat, 19 Oct 2019 + +version 0.22 + + fixed small error in linalg_det, and implemented linalg_eig. + + +Thu, 17 Oct 2019 + +version 0.21 + + implemented uniform interface for fft, and spectrum, and added ifft. + +Wed, 16 Oct 2019 + +version 0.20 + + Added flip function to numerical.c, and moved the size function to linalg. In addition, + size is a function now, and not a method. + +Tue, 15 Oct 2019 + +version 0.19 + + fixed roll in numerical.c: it can now accept the axis=None keyword argument, added determinant to linalg.c + +Mon, 14 Oct 2019 + +version 0.18 + + fixed min/man function in numerical.c; it conforms to numpy behaviour + +Fri, 11 Oct 2019 + +version 0.171 + + found and fixed small bux in roll function + +Fri, 11 Oct 2019 + +version 0.17 + + universal function can now take arbitrary typecodes + +Fri, 11 Oct 2019 + +version 0.161 + + fixed bad error in iterator, and make_new_ndarray + +Thu, 10 Oct 2019 + +varsion 0.16 + + changed ndarray to array in ulab.c, so as to conform to numpy's notation + extended subscr method to include slices (partially works) + +Tue, 8 Oct 2019 + +version 0.15 + + added inv, neg, pos, and abs unary operators to ndarray.c + +Mon, 7 Oct 2019 + +version 0.14 + + made the internal binary_op function tighter, and added keyword arguments to linspace + +Sat, 4 Oct 2019 + +version 0.13 + + added the <, <=, >, >= binary operators to ndarray + +Fri, 4 Oct 2019 + +version 0.12 + + added .flatten to ndarray, ones, zeros, and eye to linalg + +Thu, 3 Oct 2019 + +version 0.11 + + binary operators are now based on macros diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/ulab-compare.ipynb b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-compare.ipynb new file mode 100644 index 00000000..69fa762c --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-compare.ipynb @@ -0,0 +1,467 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T13:02:42.934528Z", + "start_time": "2021-01-08T13:02:42.720862Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Notebook magic" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T13:02:44.890094Z", + "start_time": "2021-01-08T13:02:44.878787Z" + } + }, + "outputs": [], + "source": [ + "from IPython.core.magic import Magics, magics_class, line_cell_magic\n", + "from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n", + "from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n", + "import subprocess\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T13:06:20.583308Z", + "start_time": "2021-01-08T13:06:20.525830Z" + } + }, + "outputs": [], + "source": [ + "@magics_class\n", + "class PyboardMagic(Magics):\n", + " @cell_magic\n", + " @magic_arguments()\n", + " @argument('-skip')\n", + " @argument('-unix')\n", + " @argument('-pyboard')\n", + " @argument('-file')\n", + " @argument('-data')\n", + " @argument('-time')\n", + " @argument('-memory')\n", + " def micropython(self, line='', cell=None):\n", + " args = parse_argstring(self.micropython, line)\n", + " if args.skip: # doesn't care about the cell's content\n", + " print('skipped execution')\n", + " return None # do not parse the rest\n", + " if args.unix: # tests the code on the unix port. Note that this works on unix only\n", + " with open('/dev/shm/micropython.py', 'w') as fout:\n", + " fout.write(cell)\n", + " proc = subprocess.Popen([\"../../micropython/ports/unix/micropython\", \"/dev/shm/micropython.py\"], \n", + " stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " print(proc.stdout.read().decode(\"utf-8\"))\n", + " print(proc.stderr.read().decode(\"utf-8\"))\n", + " return None\n", + " if args.file: # can be used to copy the cell content onto the pyboard's flash\n", + " spaces = \" \"\n", + " try:\n", + " with open(args.file, 'w') as fout:\n", + " fout.write(cell.replace('\\t', spaces))\n", + " printf('written cell to {}'.format(args.file))\n", + " except:\n", + " print('Failed to write to disc!')\n", + " return None # do not parse the rest\n", + " if args.data: # can be used to load data from the pyboard directly into kernel space\n", + " message = pyb.exec(cell)\n", + " if len(message) == 0:\n", + " print('pyboard >>>')\n", + " else:\n", + " print(message.decode('utf-8'))\n", + " # register new variable in user namespace\n", + " self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n", + " \n", + " if args.time: # measures the time of executions\n", + " pyb.exec('import utime')\n", + " message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n", + " \"\\nprint('execution time: {:d} us'.format(delta))\")\n", + " print(message.decode('utf-8'))\n", + " \n", + " if args.memory: # prints out memory information \n", + " message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n", + " print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n", + " message = pyb.exec(cell)\n", + " print(\">>> \", message.decode('utf-8'))\n", + " message = pyb.exec('print(mem_info())')\n", + " print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n", + "\n", + " if args.pyboard:\n", + " message = pyb.exec(cell)\n", + " print(message.decode('utf-8'))\n", + "\n", + "ip = get_ipython()\n", + "ip.register_magics(PyboardMagic)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pyboard" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:35.126401Z", + "start_time": "2020-05-07T07:35:35.105824Z" + } + }, + "outputs": [], + "source": [ + "import pyboard\n", + "pyb = pyboard.Pyboard('/dev/ttyACM0')\n", + "pyb.enter_raw_repl()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:11:18.145548Z", + "start_time": "2020-05-19T19:11:18.137468Z" + } + }, + "outputs": [], + "source": [ + "pyb.exit_raw_repl()\n", + "pyb.close()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:38.725924Z", + "start_time": "2020-05-07T07:35:38.645488Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "import ulab as np\n", + "\n", + "def timeit(n=1000):\n", + " def wrapper(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " run_times = np.zeros(n, dtype=np.uint16)\n", + " for i in range(n):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n", + " print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n", + " print('\\tbest: %d us'%np.min(run_times))\n", + " print('\\tworst: %d us'%np.max(run_times))\n", + " print('\\taverage: %d us'%np.mean(run_times))\n", + " print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n", + " return result\n", + " return new_func\n", + " return wrapper\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__END_OF_DEFS__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Comparison of arrays" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## equal, not_equal\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.equal.html\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.not_equal.html\n", + "\n", + "In `micropython`, equality of arrays or scalars can be established by utilising the `==`, `!=`, `<`, `>`, `<=`, or `=>` binary operators. In `circuitpython`, `==` and `!=` will produce unexpected results. In order to avoid this discrepancy, and to maintain compatibility with `numpy`, `ulab` implements the `equal` and `not_equal` operators that return the same results, irrespective of the `python` implementation.\n", + "\n", + "These two functions take two `ndarray`s, or scalars as their arguments. No keyword arguments are implemented." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T14:22:13.990898Z", + "start_time": "2021-01-08T14:22:13.941896Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64)\n", + "b: array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], dtype=float64)\n", + "\n", + "a == b: array([True, False, False, False, False, False, False, False, False], dtype=bool)\n", + "a != b: array([False, True, True, True, True, True, True, True, True], dtype=bool)\n", + "a == 2: array([False, False, True, False, False, False, False, False, False], dtype=bool)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(9))\n", + "b = np.zeros(9)\n", + "\n", + "print('a: ', a)\n", + "print('b: ', b)\n", + "print('\\na == b: ', np.equal(a, b))\n", + "print('a != b: ', np.not_equal(a, b))\n", + "\n", + "# comparison with scalars\n", + "print('a == 2: ', np.equal(a, 2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## minimum\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.minimum.html\n", + "\n", + "Returns the minimum of two arrays, or two scalars, or an array, and a scalar. If the arrays are of different `dtype`, the output is upcast as in [Binary operators](#Binary-operators). If both inputs are scalars, a scalar is returned. Only positional arguments are implemented.\n", + "\n", + "## maximum\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.maximum.html\n", + "\n", + "Returns the maximum of two arrays, or two scalars, or an array, and a scalar. If the arrays are of different `dtype`, the output is upcast as in [Binary operators](#Binary-operators). If both inputs are scalars, a scalar is returned. Only positional arguments are implemented." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T13:21:17.151280Z", + "start_time": "2021-01-08T13:21:17.123768Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "minimum of a, and b:\n", + "array([1.0, 2.0, 3.0, 2.0, 1.0], dtype=float64)\n", + "\n", + "maximum of a, and b:\n", + "array([5.0, 4.0, 3.0, 4.0, 5.0], dtype=float64)\n", + "\n", + "maximum of 1, and 5.5:\n", + "5.5\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4, 5], dtype=np.uint8)\n", + "b = np.array([5, 4, 3, 2, 1], dtype=np.float)\n", + "print('minimum of a, and b:')\n", + "print(np.minimum(a, b))\n", + "\n", + "print('\\nmaximum of a, and b:')\n", + "print(np.maximum(a, b))\n", + "\n", + "print('\\nmaximum of 1, and 5.5:')\n", + "print(np.maximum(1, 5.5))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## clip\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.clip.html\n", + "\n", + "Clips an array, i.e., values that are outside of an interval are clipped to the interval edges. The function is equivalent to `maximum(a_min, minimum(a, a_max))` broadcasting takes place exactly as in [minimum](#minimum). If the arrays are of different `dtype`, the output is upcast as in [Binary operators](#Binary-operators)." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T13:22:14.147310Z", + "start_time": "2021-01-08T13:22:14.123961Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8)\n", + "clipped:\t array([3, 3, 3, 3, 4, 5, 6, 7, 7], dtype=uint8)\n", + "\n", + "a:\t\t array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8)\n", + "b:\t\t array([3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0], dtype=float64)\n", + "clipped:\t array([3.0, 3.0, 3.0, 3.0, 4.0, 5.0, 6.0, 7.0, 7.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(9), dtype=np.uint8)\n", + "print('a:\\t\\t', a)\n", + "print('clipped:\\t', np.clip(a, 3, 7))\n", + "\n", + "b = 3 * np.ones(len(a), dtype=np.float)\n", + "print('\\na:\\t\\t', a)\n", + "print('b:\\t\\t', b)\n", + "print('clipped:\\t', np.clip(a, b, 7))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/ulab-convert.ipynb b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-convert.ipynb new file mode 100644 index 00000000..bd587912 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-convert.ipynb @@ -0,0 +1,509 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-01T09:27:13.438054Z", + "start_time": "2020-05-01T09:27:13.191491Z" + } + }, + "source": [ + "# conf.py" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2022-02-09T06:27:15.118699Z", + "start_time": "2022-02-09T06:27:15.100980Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting manual/source/conf.py\n" + ] + } + ], + "source": [ + "%%writefile manual/source/conf.py\n", + "# Configuration file for the Sphinx documentation builder.\n", + "#\n", + "# This file only contains a selection of the most common options. For a full\n", + "# list see the documentation:\n", + "# http://www.sphinx-doc.org/en/master/config\n", + "\n", + "# -- Path setup --------------------------------------------------------------\n", + "\n", + "# If extensions (or modules to document with autodoc) are in another directory,\n", + "# add these directories to sys.path here. If the directory is relative to the\n", + "# documentation root, use os.path.abspath to make it absolute, like shown here.\n", + "#\n", + "import os\n", + "# import sys\n", + "# sys.path.insert(0, os.path.abspath('.'))\n", + "\n", + "#import sphinx_rtd_theme\n", + "\n", + "from sphinx.transforms import SphinxTransform\n", + "from docutils import nodes\n", + "from sphinx import addnodes\n", + "\n", + "# -- Project information -----------------------------------------------------\n", + "\n", + "project = 'The ulab book'\n", + "copyright = '2019-2022, Zoltán Vörös and contributors'\n", + "author = 'Zoltán Vörös'\n", + "\n", + "# The full version, including alpha/beta/rc tags\n", + "release = '5.1.0'\n", + "\n", + "\n", + "# -- General configuration ---------------------------------------------------\n", + "\n", + "# Add any Sphinx extension module names here, as strings. They can be\n", + "# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n", + "# ones.\n", + "extensions = [\n", + "]\n", + "\n", + "# Add any paths that contain templates here, relative to this directory.\n", + "templates_path = ['_templates']\n", + "\n", + "# List of patterns, relative to source directory, that match files and\n", + "# directories to ignore when looking for source files.\n", + "# This pattern also affects html_static_path and html_extra_path.\n", + "exclude_patterns = []\n", + "\n", + "\n", + "# Add any paths that contain custom static files (such as style sheets) here,\n", + "# relative to this directory. They are copied after the builtin static files,\n", + "# so a file named \"default.css\" will overwrite the builtin \"default.css\".\n", + "html_static_path = ['_static']\n", + "\n", + "latex_maketitle = r'''\n", + "\\begin{titlepage}\n", + "\\begin{flushright}\n", + "\\Huge\\textbf{The $\\mu$lab book}\n", + "\\vskip 0.5em\n", + "\\LARGE\n", + "\\textbf{Release %s}\n", + "\\vskip 5em\n", + "\\huge\\textbf{Zoltán Vörös}\n", + "\\end{flushright}\n", + "\\begin{flushright}\n", + "\\LARGE\n", + "\\vskip 2em\n", + "with contributions by\n", + "\\vskip 2em\n", + "\\textbf{Roberto Colistete Jr.}\n", + "\\vskip 0.2em\n", + "\\textbf{Jeff Epler}\n", + "\\vskip 0.2em\n", + "\\textbf{Taku Fukada}\n", + "\\vskip 0.2em\n", + "\\textbf{Diego Elio Pettenò}\n", + "\\vskip 0.2em\n", + "\\textbf{Scott Shawcroft}\n", + "\\vskip 5em\n", + "\\today\n", + "\\end{flushright}\n", + "\\end{titlepage}\n", + "'''%release\n", + "\n", + "latex_elements = {\n", + " 'maketitle': latex_maketitle\n", + "}\n", + "\n", + "\n", + "master_doc = 'index'\n", + "\n", + "author=u'Zoltán Vörös'\n", + "copyright=author\n", + "language='en'\n", + "\n", + "latex_documents = [\n", + "(master_doc, 'the-ulab-book.tex', 'The $\\mu$lab book',\n", + "'Zoltán Vörös', 'manual'),\n", + "]\n", + "\n", + "# Read the docs theme\n", + "on_rtd = os.environ.get('READTHEDOCS', None) == 'True'\n", + "if not on_rtd:\n", + " try:\n", + " import sphinx_rtd_theme\n", + " html_theme = 'sphinx_rtd_theme'\n", + " html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), '.']\n", + " except ImportError:\n", + " html_theme = 'default'\n", + " html_theme_path = ['.']\n", + "else:\n", + " html_theme_path = ['.']" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2021-05-09T06:06:28.491158Z", + "start_time": "2021-05-09T06:06:28.477127Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting manual/source/index.rst\n" + ] + } + ], + "source": [ + "%%writefile manual/source/index.rst\n", + "\n", + ".. ulab-manual documentation master file, created by\n", + " sphinx-quickstart on Sat Oct 19 12:48:00 2019.\n", + " You can adapt this file completely to your liking, but it should at least\n", + " contain the root `toctree` directive.\n", + "\n", + "Welcome to the ulab book!\n", + "=======================================\n", + "\n", + ".. toctree::\n", + " :maxdepth: 2\n", + " :caption: Introduction\n", + "\n", + " ulab-intro\n", + "\n", + ".. toctree::\n", + " :maxdepth: 2\n", + " :caption: User's guide:\n", + "\n", + " ulab-ndarray\n", + " numpy-functions\n", + " numpy-universal\n", + " numpy-fft\n", + " numpy-linalg\n", + " scipy-linalg\n", + " scipy-optimize\n", + " scipy-signal\n", + " scipy-special\n", + " ulab-utils\n", + " ulab-tricks\n", + " ulab-programming\n", + "\n", + "Indices and tables\n", + "==================\n", + "\n", + "* :ref:`genindex`\n", + "* :ref:`modindex`\n", + "* :ref:`search`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Notebook conversion" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2022-02-09T06:27:21.647179Z", + "start_time": "2022-02-09T06:27:20.019520Z" + } + }, + "outputs": [], + "source": [ + "import nbformat as nb\n", + "import nbformat.v4.nbbase as nb4\n", + "from nbconvert import RSTExporter\n", + "\n", + "from jinja2 import FileSystemLoader\n", + "rstexporter = RSTExporter(\n", + " extra_loaders=[FileSystemLoader('./templates')],\n", + " template_file = './templates/manual.tpl'\n", + ")\n", + "\n", + "def convert_notebook(fn):\n", + " source = nb.read(fn+'.ipynb', nb.NO_CONVERT)\n", + " notebook = nb4.new_notebook()\n", + " notebook.cells = []\n", + " append_cell = False\n", + " for cell in source['cells']:\n", + " if append_cell:\n", + " notebook.cells.append(cell)\n", + " else:\n", + " if cell.cell_type == 'markdown':\n", + " if cell.source == '__END_OF_DEFS__':\n", + " append_cell = True\n", + " \n", + " (rst, resources) = rstexporter.from_notebook_node(notebook)\n", + " with open('./manual/source/' + fn + '.rst', 'w') as fout:\n", + " # it's a bit odd, but even an emtpy notebook is converted into a \"None\" string\n", + " rst = rst.lstrip('None')\n", + " fout.write(rst)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "ExecuteTime": { + "end_time": "2022-02-09T06:27:42.024028Z", + "start_time": "2022-02-09T06:27:36.109093Z" + } + }, + "outputs": [], + "source": [ + "files = ['ulab-intro',\n", + " 'ulab-ndarray',\n", + " 'numpy-functions', \n", + " 'numpy-universal',\n", + " 'numpy-fft',\n", + " 'numpy-linalg',\n", + " 'scipy-linalg',\n", + " 'scipy-optimize',\n", + " 'scipy-signal',\n", + " 'scipy-special',\n", + " 'ulab-utils',\n", + " 'ulab-tricks',\n", + " 'ulab-programming']\n", + "\n", + "for file in files:\n", + " convert_notebook(file)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Template" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-30T19:04:50.295563Z", + "start_time": "2020-10-30T19:04:50.227535Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting ./templates/manual.tpl\n" + ] + } + ], + "source": [ + "%%writefile ./templates/manual.tpl\n", + "\n", + "{%- extends 'display_priority.tpl' -%}\n", + "\n", + "\n", + "{% block in_prompt %}\n", + "{% endblock in_prompt %}\n", + "\n", + "{% block output_prompt %}\n", + "{% endblock output_prompt %}\n", + "\n", + "{% block input scoped%}\n", + "\n", + "{%- if cell.source.split('\\n')[0].startswith('%%micropython') -%}\n", + ".. code::\n", + " \n", + "{{ '\\n'.join(['# code to be run in micropython'] + cell.source.strip().split('\\n')[1:]) | indent}}\n", + "\n", + "{%- else -%}\n", + ".. code::\n", + "\n", + "{{ '\\n'.join(['# code to be run in CPython\\n'] + cell.source.strip().split('\\n')) | indent}}\n", + "{%- endif -%}\n", + "{% endblock input %}\n", + "\n", + "{% block error %}\n", + "::\n", + "\n", + "{{ super() }}\n", + "{% endblock error %}\n", + "\n", + "{% block traceback_line %}\n", + "{{ line | indent | strip_ansi }}\n", + "{% endblock traceback_line %}\n", + "\n", + "{% block execute_result %}\n", + "{% block data_priority scoped %}\n", + "{{ super() }}\n", + "{% endblock %}\n", + "{% endblock execute_result %}\n", + "\n", + "{% block stream %}\n", + ".. parsed-literal::\n", + "\n", + "{{ output.text | indent }}\n", + "{% endblock stream %}\n", + "\n", + "{% block data_svg %}\n", + ".. image:: {{ output.metadata.filenames['image/svg+xml'] | urlencode }}\n", + "{% endblock data_svg %}\n", + "\n", + "{% block data_png %}\n", + ".. image:: {{ output.metadata.filenames['image/png'] | urlencode }}\n", + "{%- set width=output | get_metadata('width', 'image/png') -%}\n", + "{%- if width is not none %}\n", + " :width: {{ width }}px\n", + "{%- endif %}\n", + "{%- set height=output | get_metadata('height', 'image/png') -%}\n", + "{%- if height is not none %}\n", + " :height: {{ height }}px\n", + "{%- endif %}\n", + "{% endblock data_png %}\n", + "\n", + "{% block data_jpg %}\n", + ".. image:: {{ output.metadata.filenames['image/jpeg'] | urlencode }}\n", + "{%- set width=output | get_metadata('width', 'image/jpeg') -%}\n", + "{%- if width is not none %}\n", + " :width: {{ width }}px\n", + "{%- endif %}\n", + "{%- set height=output | get_metadata('height', 'image/jpeg') -%}\n", + "{%- if height is not none %}\n", + " :height: {{ height }}px\n", + "{%- endif %}\n", + "{% endblock data_jpg %}\n", + "\n", + "{% block data_markdown %}\n", + "{{ output.data['text/markdown'] | convert_pandoc(\"markdown\", \"rst\") }}\n", + "{% endblock data_markdown %}\n", + "\n", + "{% block data_latex %}\n", + ".. math::\n", + "\n", + "{{ output.data['text/latex'] | strip_dollars | indent }}\n", + "{% endblock data_latex %}\n", + "\n", + "{% block data_text scoped %}\n", + ".. parsed-literal::\n", + "\n", + "{{ output.data['text/plain'] | indent }}\n", + "{% endblock data_text %}\n", + "\n", + "{% block data_html scoped %}\n", + ".. raw:: html\n", + "\n", + "{{ output.data['text/html'] | indent }}\n", + "{% endblock data_html %}\n", + "\n", + "{% block markdowncell scoped %}\n", + "{{ cell.source | convert_pandoc(\"markdown\", \"rst\") }}\n", + "{% endblock markdowncell %}\n", + "\n", + "{%- block rawcell scoped -%}\n", + "{%- if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) %}\n", + "{{cell.source}}\n", + "{% endif -%}\n", + "{%- endblock rawcell -%}\n", + "\n", + "{% block headingcell scoped %}\n", + "{{ (\"#\" * cell.level + cell.source) | replace('\\n', ' ') | convert_pandoc(\"markdown\", \"rst\") }}\n", + "{% endblock headingcell %}\n", + "\n", + "{% block unknowncell scoped %}\n", + "unknown type {{cell.type}}\n", + "{% endblock unknowncell %}\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8.5 ('base')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + }, + "vscode": { + "interpreter": { + "hash": "9e4ec6f642f986afcc9e252c165e44859a62defc5c697cae6f82c2943465ec10" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/ulab-intro.ipynb b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-intro.ipynb new file mode 100644 index 00000000..67d6b608 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-intro.ipynb @@ -0,0 +1,897 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T12:07:55.382930Z", + "start_time": "2021-01-08T12:07:46.895325Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Matplotlib is building the font cache; this may take a moment.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Notebook magic" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-07T18:13:14.590799Z", + "start_time": "2022-01-07T18:13:14.585845Z" + } + }, + "outputs": [], + "source": [ + "from IPython.core.magic import Magics, magics_class, line_cell_magic\n", + "from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n", + "from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n", + "import subprocess\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-07T18:20:56.550047Z", + "start_time": "2022-01-07T18:20:56.527475Z" + } + }, + "outputs": [], + "source": [ + "@magics_class\n", + "class PyboardMagic(Magics):\n", + " @cell_magic\n", + " @magic_arguments()\n", + " @argument('-skip')\n", + " @argument('-unix')\n", + " @argument('-pyboard')\n", + " @argument('-file')\n", + " @argument('-data')\n", + " @argument('-time')\n", + " @argument('-memory')\n", + " def micropython(self, line='', cell=None):\n", + " args = parse_argstring(self.micropython, line)\n", + " if args.skip: # doesn't care about the cell's content\n", + " print('skipped execution')\n", + " return None # do not parse the rest\n", + " if args.unix: # tests the code on the unix port. Note that this works on unix only\n", + " with open('/dev/shm/micropython.py', 'w') as fout:\n", + " fout.write(cell)\n", + " proc = subprocess.Popen([\"../micropython/ports/unix/micropython-2\", \"/dev/shm/micropython.py\"], \n", + " stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " print(proc.stdout.read().decode(\"utf-8\"))\n", + " print(proc.stderr.read().decode(\"utf-8\"))\n", + " return None\n", + " if args.file: # can be used to copy the cell content onto the pyboard's flash\n", + " spaces = \" \"\n", + " try:\n", + " with open(args.file, 'w') as fout:\n", + " fout.write(cell.replace('\\t', spaces))\n", + " printf('written cell to {}'.format(args.file))\n", + " except:\n", + " print('Failed to write to disc!')\n", + " return None # do not parse the rest\n", + " if args.data: # can be used to load data from the pyboard directly into kernel space\n", + " message = pyb.exec(cell)\n", + " if len(message) == 0:\n", + " print('pyboard >>>')\n", + " else:\n", + " print(message.decode('utf-8'))\n", + " # register new variable in user namespace\n", + " self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n", + " \n", + " if args.time: # measures the time of executions\n", + " pyb.exec('import utime')\n", + " message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n", + " \"\\nprint('execution time: {:d} us'.format(delta))\")\n", + " print(message.decode('utf-8'))\n", + " \n", + " if args.memory: # prints out memory information \n", + " message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n", + " print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n", + " message = pyb.exec(cell)\n", + " print(\">>> \", message.decode('utf-8'))\n", + " message = pyb.exec('print(mem_info())')\n", + " print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n", + "\n", + " if args.pyboard:\n", + " message = pyb.exec(cell)\n", + " print(message.decode('utf-8'))\n", + "\n", + "ip = get_ipython()\n", + "ip.register_magics(PyboardMagic)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pyboard" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:35.126401Z", + "start_time": "2020-05-07T07:35:35.105824Z" + } + }, + "outputs": [], + "source": [ + "import pyboard\n", + "pyb = pyboard.Pyboard('/dev/ttyACM0')\n", + "pyb.enter_raw_repl()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:11:18.145548Z", + "start_time": "2020-05-19T19:11:18.137468Z" + } + }, + "outputs": [], + "source": [ + "pyb.exit_raw_repl()\n", + "pyb.close()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:38.725924Z", + "start_time": "2020-05-07T07:35:38.645488Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "from ulab import numpy as np\n", + "\n", + "def timeit(n=1000):\n", + " def wrapper(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " run_times = np.zeros(n, dtype=np.uint16)\n", + " for i in range(n):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n", + " print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n", + " print('\\tbest: %d us'%np.min(run_times))\n", + " print('\\tworst: %d us'%np.max(run_times))\n", + " print('\\taverage: %d us'%np.mean(run_times))\n", + " print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n", + " return result\n", + " return new_func\n", + " return wrapper\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__END_OF_DEFS__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Introduction" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Enter ulab\n", + "\n", + "`ulab` is a `numpy`-like module for `micropython` and its derivatives, meant to simplify and speed up common mathematical operations on arrays. `ulab` implements a small subset of `numpy` and `scipy`. The functions were chosen such that they might be useful in the context of a microcontroller. However, the project is a living one, and suggestions for new features are always welcome. \n", + "\n", + "This document discusses how you can use the library, starting from building your own firmware, through questions like what affects the firmware size, what are the trade-offs, and what are the most important differences to `numpy` and `scipy`, respectively. The document is organised as follows:\n", + "\n", + "The chapter after this one helps you with firmware customisation.\n", + "\n", + "The third chapter gives a very concise summary of the `ulab` functions and array methods. This chapter can be used as a quick reference.\n", + "\n", + "The chapters after that are an in-depth review of most functions. Here you can find usage examples, benchmarks, as well as a thorough discussion of such concepts as broadcasting, and views versus copies. \n", + "\n", + "The final chapter of this book can be regarded as the programming manual. The inner working of `ulab` is dissected here, and you will also find hints as to how to implement your own `numpy`-compatible functions.\n", + "\n", + "\n", + "## Purpose\n", + "\n", + "Of course, the first question that one has to answer is, why on Earth one would need a fast math library on a microcontroller. After all, it is not expected that heavy number crunching is going to take place on bare metal. It is not meant to. On a PC, the main reason for writing fast code is the sheer amount of data that one wants to process. On a microcontroller, the data volume is probably small, but it might lead to catastrophic system failure, if these data are not processed in time, because the microcontroller is supposed to interact with the outside world in a timely fashion. In fact, this latter objective was the initiator of this project: I needed the Fourier transform of a signal coming from the ADC of the `pyboard`, and all available options were simply too slow. \n", + "\n", + "In addition to speed, another issue that one has to keep in mind when working with embedded systems is the amount of available RAM: I believe, everything here could be implemented in pure `python` with relatively little effort (in fact, there are a couple of `python`-only implementations of `numpy` functions out there), but the price we would have to pay for that is not only speed, but RAM, too. `python` code, if is not frozen, and compiled into the firmware, has to be compiled at runtime, which is not exactly a cheap process. On top of that, if numbers are stored in a list or tuple, which would be the high-level container, then they occupy 8 bytes, no matter, whether they are all smaller than 100, or larger than one hundred million. This is obviously a waste of resources in an environment, where resources are scarce. \n", + "\n", + "Finally, there is a reason for using `micropython` in the first place. Namely, that a microcontroller can be programmed in a very elegant, and *pythonic* way. But if it is so, why should we not extend this idea to other tasks and concepts that might come up in this context? If there was no other reason than this *elegance*, I would find that convincing enough.\n", + "\n", + "Based on the above-mentioned considerations, all functions in `ulab` are implemented in a way that \n", + "\n", + "1. conforms to `numpy` as much as possible\n", + "2. is so frugal with RAM as possible,\n", + "3. and yet, fast. Much faster than pure python. Think of speed-ups of 30-50!\n", + "\n", + "The main points of `ulab` are \n", + "\n", + "- compact, iterable and slicable containers of numerical data in one to four dimensions. These containers support all the relevant unary and binary operators (e.g., `len`, ==, +, *, etc.)\n", + "- vectorised computations on `micropython` iterables and numerical arrays (in `numpy`-speak, universal functions)\n", + "- computing statistical properties (mean, standard deviation etc.) on arrays\n", + "- basic linear algebra routines (matrix inversion, multiplication, reshaping, transposition, determinant, and eigenvalues, Cholesky decomposition and so on)\n", + "- polynomial fits to numerical data, and evaluation of polynomials\n", + "- fast Fourier transforms\n", + "- filtering of data (convolution and second-order filters)\n", + "- function minimisation, fitting, and numerical approximation routines\n", + "- interfacing between numerical data and peripheral hardware devices\n", + "\n", + "`ulab` implements close to a hundred functions and array methods. At the time of writing this manual (for version 4.0.0), the library adds approximately 120 kB of extra compiled code to the `micropython` (pyboard.v.1.17) firmware. However, if you are tight with flash space, you can easily shave tens of kB off the firmware. In fact, if only a small sub-set of functions are needed, you can get away with less than 10 kB of flash space. See the section on [customising ulab](#Customising-the-firmware).\n", + "\n", + "## Resources and legal matters\n", + "\n", + "The source code of the module can be found under https://github.com/v923z/micropython-ulab/tree/master/code. while the source of this user manual is under https://github.com/v923z/micropython-ulab/tree/master/docs.\n", + "\n", + "The MIT licence applies to all material. \n", + "\n", + "## Friendly request\n", + "\n", + "If you use `ulab`, and bump into a bug, or think that a particular function is missing, or its behaviour does not conform to `numpy`, please, raise a [ulab issue](#https://github.com/v923z/micropython-ulab/issues) on github, so that the community can profit from your experiences. \n", + "\n", + "Even better, if you find the project to be useful, and think that it could be made better, faster, tighter, and shinier, please, consider contributing, and issue a pull request with the implementation of your improvements and new features. `ulab` can only become successful, if it offers what the community needs.\n", + "\n", + "These last comments apply to the documentation, too. If, in your opinion, the documentation is obscure, misleading, or not detailed enough, please, let us know, so that *we* can fix it.\n", + "\n", + "## Differences between micropython-ulab and circuitpython-ulab\n", + "\n", + "`ulab` has originally been developed for `micropython`, but has since been integrated into a number of its flavours. Most of these are simply forks of `micropython` itself, with some additional functionality. One of the notable exceptions is `circuitpython`, which has slightly diverged at the core level, and this has some minor consequences. Some of these concern the C implementation details only, which all have been sorted out with the generous and enthusiastic support of Jeff Epler from [Adafruit Industries](http://www.adafruit.com).\n", + "\n", + "There are, however, a couple of instances, where the two environments differ at the python level in how the class properties can be accessed. We will point out the differences and possible workarounds at the relevant places in this document." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Customising the firmware\n", + "\n", + "\n", + "As mentioned above, `ulab` has considerably grown since its conception, which also means that it might no longer fit on the microcontroller of your choice. There are, however, a couple of ways of customising the firmware, and thereby reducing its size. \n", + "\n", + "All `ulab` options are listed in a single header file, [ulab.h](https://github.com/v923z/micropython-ulab/blob/master/code/ulab.h), which contains pre-processor flags for each feature that can be fine-tuned. The first couple of lines of the file look like this\n", + "\n", + "```c\n", + "// The pre-processor constants in this file determine how ulab behaves:\n", + "//\n", + "// - how many dimensions ulab can handle\n", + "// - which functions are included in the compiled firmware\n", + "// - whether the python syntax is numpy-like, or modular\n", + "// - whether arrays can be sliced and iterated over\n", + "// - which binary/unary operators are supported\n", + "//\n", + "// A considerable amount of flash space can be saved by removing (setting\n", + "// the corresponding constants to 0) the unnecessary functions and features.\n", + "\n", + "// Values defined here can be overridden by your own config file as\n", + "// make -DULAB_CONFIG_FILE=\"my_ulab_config.h\"\n", + "#if defined(ULAB_CONFIG_FILE)\n", + "#include ULAB_CONFIG_FILE\n", + "#endif\n", + "\n", + "// Adds support for complex ndarrays\n", + "#ifndef ULAB_SUPPORTS_COMPLEX\n", + "#define ULAB_SUPPORTS_COMPLEX (1)\n", + "#endif\n", + "\n", + "// Determines, whether scipy is defined in ulab. The sub-modules and functions\n", + "// of scipy have to be defined separately\n", + "#define ULAB_HAS_SCIPY (1)\n", + "\n", + "// The maximum number of dimensions the firmware should be able to support\n", + "// Possible values lie between 1, and 4, inclusive\n", + "#define ULAB_MAX_DIMS 2\n", + "\n", + "// By setting this constant to 1, iteration over array dimensions will be implemented\n", + "// as a function (ndarray_rewind_array), instead of writing out the loops in macros\n", + "// This reduces firmware size at the expense of speed\n", + "#define ULAB_HAS_FUNCTION_ITERATOR (0)\n", + "\n", + "// If NDARRAY_IS_ITERABLE is 1, the ndarray object defines its own iterator function\n", + "// This option saves approx. 250 bytes of flash space\n", + "#define NDARRAY_IS_ITERABLE (1)\n", + "\n", + "// Slicing can be switched off by setting this variable to 0\n", + "#define NDARRAY_IS_SLICEABLE (1)\n", + "\n", + "// The default threshold for pretty printing. These variables can be overwritten\n", + "// at run-time via the set_printoptions() function\n", + "#define ULAB_HAS_PRINTOPTIONS (1)\n", + "#define NDARRAY_PRINT_THRESHOLD 10\n", + "#define NDARRAY_PRINT_EDGEITEMS 3\n", + "\n", + "// determines, whether the dtype is an object, or simply a character\n", + "// the object implementation is numpythonic, but requires more space\n", + "#define ULAB_HAS_DTYPE_OBJECT (0)\n", + "\n", + "// the ndarray binary operators\n", + "#define NDARRAY_HAS_BINARY_OPS (1)\n", + "\n", + "// Firmware size can be reduced at the expense of speed by using function\n", + "// pointers in iterations. For each operator, he function pointer saves around\n", + "// 2 kB in the two-dimensional case, and around 4 kB in the four-dimensional case.\n", + "\n", + "#define NDARRAY_BINARY_USES_FUN_POINTER (0)\n", + "\n", + "#define NDARRAY_HAS_BINARY_OP_ADD (1)\n", + "#define NDARRAY_HAS_BINARY_OP_EQUAL (1)\n", + "#define NDARRAY_HAS_BINARY_OP_LESS (1)\n", + "#define NDARRAY_HAS_BINARY_OP_LESS_EQUAL (1)\n", + "#define NDARRAY_HAS_BINARY_OP_MORE (1)\n", + "#define NDARRAY_HAS_BINARY_OP_MORE_EQUAL (1)\n", + "#define NDARRAY_HAS_BINARY_OP_MULTIPLY (1)\n", + "#define NDARRAY_HAS_BINARY_OP_NOT_EQUAL (1)\n", + "#define NDARRAY_HAS_BINARY_OP_POWER (1)\n", + "#define NDARRAY_HAS_BINARY_OP_SUBTRACT (1)\n", + "#define NDARRAY_HAS_BINARY_OP_TRUE_DIVIDE (1)\n", + "... \n", + "```\n", + "\n", + "The meaning of flags with names `_HAS_` should be obvious, so we will just explain the other options. \n", + "\n", + "To see how much you can gain by un-setting the functions that you do not need, here are some pointers. In four dimensions, including all functions adds around 120 kB to the `micropython` firmware. On the other hand, if you are interested in Fourier transforms only, and strip everything else, you get away with less than 5 kB extra. \n", + "\n", + "## Compatibility with numpy\n", + "\n", + "The functions implemented in `ulab` are organised in four sub-modules at the C level, namely, `numpy`, `scipy`, `utils`, and `user`. This modularity is elevated to `python`, meaning that in order to use functions that are part of `numpy`, you have to import `numpy` as\n", + "\n", + "```python\n", + "from ulab import numpy as np\n", + "\n", + "x = np.array([4, 5, 6])\n", + "p = np.array([1, 2, 3])\n", + "np.polyval(p, x)\n", + "```\n", + "\n", + "There are a couple of exceptions to this rule, namely `fft`, and `linalg`, which are sub-modules even in `numpy`, thus you have to write them out as \n", + "\n", + "```python\n", + "from ulab import numpy as np\n", + "\n", + "A = np.array([1, 2, 3, 4]).reshape()\n", + "np.linalg.trace(A)\n", + "```\n", + "\n", + "Some of the functions in `ulab` are re-implementations of `scipy` functions, and they are to be imported as \n", + "\n", + "```python\n", + "from ulab import numpy as np\n", + "from ulab import scipy as spy\n", + "\n", + "\n", + "x = np.array([1, 2, 3])\n", + "spy.special.erf(x)\n", + "```\n", + "\n", + "`numpy`-compatibility has an enormous benefit : namely, by `try`ing to `import`, we can guarantee that the same, unmodified code runs in `CPython`, as in `micropython`. The following snippet is platform-independent, thus, the `python` code can be tested and debugged on a computer before loading it onto the microcontroller.\n", + "\n", + "```python\n", + "\n", + "try:\n", + " from ulab import numpy as np\n", + " from ulab import scipy as spy\n", + "except ImportError:\n", + " import numpy as np\n", + " import scipy as spy\n", + " \n", + "x = np.array([1, 2, 3])\n", + "spy.special.erf(x) \n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The impact of dimensionality\n", + "\n", + "### Reducing the number of dimensions\n", + "\n", + "`ulab` supports tensors of rank four, but this is expensive in terms of flash: with all available functions and options, the library adds around 100 kB to the firmware. However, if such high dimensions are not required, significant reductions in size can be gotten by changing the value of \n", + "\n", + "```c\n", + "#define ULAB_MAX_DIMS 2\n", + "```\n", + "\n", + "Two dimensions cost a bit more than half of four, while you can get away with around 20 kB of flash in one dimension, because all those functions that don't make sense (e.g., matrix inversion, eigenvalues etc.) are automatically stripped from the firmware.\n", + "\n", + "### Using the function iterator\n", + "\n", + "In higher dimensions, the firmware size increases, because each dimension (axis) adds another level of nested loops. An example of this is the macro of the binary operator in three dimensions\n", + "\n", + "```c\n", + "#define BINARY_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\n", + " type_out *array = (type_out *)results->array;\n", + " size_t j = 0;\n", + " do {\n", + " size_t k = 0;\n", + " do {\n", + " size_t l = 0;\n", + " do {\n", + " *array++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\n", + " (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\n", + " (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\n", + " l++;\n", + " } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\n", + " (larray) -= (lstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\n", + " (larray) += (lstrides)[ULAB_MAX_DIMS - 2];\n", + " (rarray) -= (rstrides)[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS-1];\n", + " (rarray) += (rstrides)[ULAB_MAX_DIMS - 2];\n", + " k++;\n", + " } while(k < (results)->shape[ULAB_MAX_DIMS - 2]);\n", + " (larray) -= (lstrides)[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2];\n", + " (larray) += (lstrides)[ULAB_MAX_DIMS - 3];\n", + " (rarray) -= (rstrides)[ULAB_MAX_DIMS - 2] * results->shape[ULAB_MAX_DIMS-2];\n", + " (rarray) += (rstrides)[ULAB_MAX_DIMS - 3];\n", + " j++;\n", + " } while(j < (results)->shape[ULAB_MAX_DIMS - 3]);\n", + "```\n", + "\n", + "In order to reduce firmware size, it *might* make sense in higher dimensions to make use of the function iterator by setting the \n", + "\n", + "```c\n", + "#define ULAB_HAS_FUNCTION_ITERATOR (1)\n", + "```\n", + "\n", + "constant to 1. This allows the compiler to call the `ndarray_rewind_array` function, so that it doesn't have to unwrap the loops for `k`, and `j`. Instead of the macro above, we now have \n", + "\n", + "```c\n", + "#define BINARY_LOOP(results, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\n", + " type_out *array = (type_out *)(results)->array;\n", + " size_t *lcoords = ndarray_new_coords((results)->ndim);\n", + " size_t *rcoords = ndarray_new_coords((results)->ndim);\n", + " for(size_t i=0; i < (results)->len/(results)->shape[ULAB_MAX_DIMS -1]; i++) {\n", + " size_t l = 0;\n", + " do {\n", + " *array++ = *((type_left *)(larray)) OPERATOR *((type_right *)(rarray));\n", + " (larray) += (lstrides)[ULAB_MAX_DIMS - 1];\n", + " (rarray) += (rstrides)[ULAB_MAX_DIMS - 1];\n", + " l++;\n", + " } while(l < (results)->shape[ULAB_MAX_DIMS - 1]);\n", + " ndarray_rewind_array((results)->ndim, larray, (results)->shape, lstrides, lcoords);\n", + " ndarray_rewind_array((results)->ndim, rarray, (results)->shape, rstrides, rcoords);\n", + " } while(0)\n", + "```\n", + "\n", + "Since the `ndarray_rewind_array` function is implemented only once, a lot of space can be saved. Obviously, function calls cost time, thus such trade-offs must be evaluated for each application. The gain also depends on which functions and features you include. Operators and functions that involve two arrays are expensive, because at the C level, the number of cases that must be handled scales with the squares of the number of data types. As an example, the innocent-looking expression\n", + "\n", + "```python\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3])\n", + "b = np.array([4, 5, 6])\n", + "\n", + "c = a + b\n", + "```\n", + "requires 25 loops in C, because the `dtypes` of both `a`, and `b` can assume 5 different values, and the addition has to be resolved for all possible cases. Hint: each binary operator costs between 3 and 4 kB in two dimensions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The ulab version string\n", + "\n", + "As is customary with `python` packages, information on the package version can be found be querying the `__version__` string. " + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T06:25:27.328061Z", + "start_time": "2021-01-12T06:25:27.308199Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "you are running ulab version 2.1.0-2D\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "import ulab\n", + "\n", + "print('you are running ulab version', ulab.__version__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The first three numbers indicate the major, minor, and sub-minor versions of `ulab` (defined by the `ULAB_VERSION` constant in [ulab.c](https://github.com/v923z/micropython-ulab/blob/master/code/ulab.c)). We usually change the minor version, whenever a new function is added to the code, and the sub-minor version will be incremented, if a bug fix is implemented. \n", + "\n", + "`2D` tells us that the particular firmware supports tensors of rank 2 (defined by `ULAB_MAX_DIMS` in [ulab.h](https://github.com/v923z/micropython-ulab/blob/master/code/ulab.h)). \n", + "\n", + "If you find a bug, please, include the version string in your report!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Should you need the numerical value of `ULAB_MAX_DIMS`, you can get it from the version string in the following way:" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:00:00.616473Z", + "start_time": "2021-01-13T06:00:00.602787Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "version string: 2.1.0-2D\n", + "version dimensions: 2D\n", + "numerical value of dimensions: 2\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "import ulab\n", + "\n", + "version = ulab.__version__\n", + "version_dims = version.split('-')[1]\n", + "version_num = int(version_dims.replace('D', ''))\n", + "\n", + "print('version string: ', version)\n", + "print('version dimensions: ', version_dims)\n", + "print('numerical value of dimensions: ', version_num)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ulab with complex arrays\n", + "\n", + "If the firmware supports complex arrays, `-c` is appended to the version string as can be seen below." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-07T18:21:04.079894Z", + "start_time": "2022-01-07T18:21:04.058855Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "version string: 4.0.0-2D-c\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "import ulab\n", + "\n", + "version = ulab.__version__\n", + "\n", + "print('version string: ', version)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Finding out what your firmware supports\n", + "\n", + "`ulab` implements a number of array operators and functions, but this does not mean that all of these functions and methods are actually compiled into the firmware. You can fine-tune your firmware by setting/unsetting any of the `_HAS_` constants in [ulab.h](https://github.com/v923z/micropython-ulab/blob/master/code/ulab.h). \n", + "\n", + "### Functions included in the firmware\n", + "\n", + "The version string will not tell you everything about your firmware, because the supported functions and sub-modules can still arbitrarily be included or excluded. One way of finding out what is compiled into the firmware is calling `dir` with `ulab` as its argument." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T12:47:37.963507Z", + "start_time": "2021-01-08T12:47:37.936641Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "===== constants, functions, and modules of numpy =====\n", + "\n", + " ['__class__', '__name__', 'bool', 'sort', 'sum', 'acos', 'acosh', 'arange', 'arctan2', 'argmax', 'argmin', 'argsort', 'around', 'array', 'asin', 'asinh', 'atan', 'atanh', 'ceil', 'clip', 'concatenate', 'convolve', 'cos', 'cosh', 'cross', 'degrees', 'diag', 'diff', 'e', 'equal', 'exp', 'expm1', 'eye', 'fft', 'flip', 'float', 'floor', 'frombuffer', 'full', 'get_printoptions', 'inf', 'int16', 'int8', 'interp', 'linalg', 'linspace', 'log', 'log10', 'log2', 'logspace', 'max', 'maximum', 'mean', 'median', 'min', 'minimum', 'nan', 'ndinfo', 'not_equal', 'ones', 'pi', 'polyfit', 'polyval', 'radians', 'roll', 'set_printoptions', 'sin', 'sinh', 'sqrt', 'std', 'tan', 'tanh', 'trapz', 'uint16', 'uint8', 'vectorize', 'zeros']\n", + "\n", + "functions included in the fft module:\n", + " ['__class__', '__name__', 'fft', 'ifft']\n", + "\n", + "functions included in the linalg module:\n", + " ['__class__', '__name__', 'cholesky', 'det', 'dot', 'eig', 'inv', 'norm', 'trace']\n", + "\n", + "\n", + "===== modules of scipy =====\n", + "\n", + " ['__class__', '__name__', 'optimize', 'signal', 'special']\n", + "\n", + "functions included in the optimize module:\n", + " ['__class__', '__name__', 'bisect', 'fmin', 'newton']\n", + "\n", + "functions included in the signal module:\n", + " ['__class__', '__name__', 'sosfilt', 'spectrogram']\n", + "\n", + "functions included in the special module:\n", + " ['__class__', '__name__', 'erf', 'erfc', 'gamma', 'gammaln']\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "from ulab import scipy as spy\n", + "\n", + "\n", + "print('===== constants, functions, and modules of numpy =====\\n\\n', dir(np))\n", + "\n", + "# since fft and linalg are sub-modules, print them separately\n", + "print('\\nfunctions included in the fft module:\\n', dir(np.fft))\n", + "print('\\nfunctions included in the linalg module:\\n', dir(np.linalg))\n", + "\n", + "print('\\n\\n===== modules of scipy =====\\n\\n', dir(spy))\n", + "print('\\nfunctions included in the optimize module:\\n', dir(spy.optimize))\n", + "print('\\nfunctions included in the signal module:\\n', dir(spy.signal))\n", + "print('\\nfunctions included in the special module:\\n', dir(spy.special))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Methods included in the firmware\n", + "\n", + "The `dir` function applied to the module or its sub-modules gives information on what the module and sub-modules include, but is not enough to find out which methods the `ndarray` class supports. We can list the methods by calling `dir` with the `array` object itself:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T12:48:17.927709Z", + "start_time": "2021-01-08T12:48:17.903132Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['__class__', '__name__', 'copy', 'sort', '__bases__', '__dict__', 'dtype', 'flatten', 'itemsize', 'reshape', 'shape', 'size', 'strides', 'tobytes', 'transpose']\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "print(dir(np.array))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Operators included in the firmware\n", + "\n", + "A list of operators cannot be generated as shown above. If you really need to find out, whether, e.g., the `**` operator is supported by the firmware, you have to `try` it:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T12:49:59.902054Z", + "start_time": "2021-01-08T12:49:59.875760Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "operator is not supported: unsupported types for __pow__: 'ndarray', 'ndarray'\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3])\n", + "b = np.array([4, 5, 6])\n", + "\n", + "try:\n", + " print(a ** b)\n", + "except Exception as e:\n", + " print('operator is not supported: ', e)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The exception above would be raised, if the firmware was compiled with the \n", + "\n", + "```c\n", + "#define NDARRAY_HAS_BINARY_OP_POWER (0)\n", + "```\n", + "\n", + "definition." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/ulab-ndarray.ipynb b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-ndarray.ipynb new file mode 100644 index 00000000..bf016b1a --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-ndarray.ipynb @@ -0,0 +1,3802 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:20:20.064769Z", + "start_time": "2021-01-12T16:20:19.787429Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Notebook magic" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2022-02-09T06:10:18.391925Z", + "start_time": "2022-02-09T06:10:18.388146Z" + } + }, + "outputs": [], + "source": [ + "from IPython.core.magic import Magics, magics_class, line_cell_magic\n", + "from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n", + "from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n", + "import subprocess\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2022-02-09T06:10:19.000982Z", + "start_time": "2022-02-09T06:10:18.979322Z" + } + }, + "outputs": [], + "source": [ + "@magics_class\n", + "class PyboardMagic(Magics):\n", + " @cell_magic\n", + " @magic_arguments()\n", + " @argument('-skip')\n", + " @argument('-unix')\n", + " @argument('-pyboard')\n", + " @argument('-file')\n", + " @argument('-data')\n", + " @argument('-time')\n", + " @argument('-memory')\n", + " def micropython(self, line='', cell=None):\n", + " args = parse_argstring(self.micropython, line)\n", + " if args.skip: # doesn't care about the cell's content\n", + " print('skipped execution')\n", + " return None # do not parse the rest\n", + " if args.unix: # tests the code on the unix port. Note that this works on unix only\n", + " with open('/dev/shm/micropython.py', 'w') as fout:\n", + " fout.write(cell)\n", + " proc = subprocess.Popen([\"../micropython/ports/unix/micropython-2\", \"/dev/shm/micropython.py\"], \n", + " stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " print(proc.stdout.read().decode(\"utf-8\"))\n", + " print(proc.stderr.read().decode(\"utf-8\"))\n", + " return None\n", + " if args.file: # can be used to copy the cell content onto the pyboard's flash\n", + " spaces = \" \"\n", + " try:\n", + " with open(args.file, 'w') as fout:\n", + " fout.write(cell.replace('\\t', spaces))\n", + " printf('written cell to {}'.format(args.file))\n", + " except:\n", + " print('Failed to write to disc!')\n", + " return None # do not parse the rest\n", + " if args.data: # can be used to load data from the pyboard directly into kernel space\n", + " message = pyb.exec(cell)\n", + " if len(message) == 0:\n", + " print('pyboard >>>')\n", + " else:\n", + " print(message.decode('utf-8'))\n", + " # register new variable in user namespace\n", + " self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n", + " \n", + " if args.time: # measures the time of executions\n", + " pyb.exec('import utime')\n", + " message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n", + " \"\\nprint('execution time: {:d} us'.format(delta))\")\n", + " print(message.decode('utf-8'))\n", + " \n", + " if args.memory: # prints out memory information \n", + " message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n", + " print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n", + " message = pyb.exec(cell)\n", + " print(\">>> \", message.decode('utf-8'))\n", + " message = pyb.exec('print(mem_info())')\n", + " print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n", + "\n", + " if args.pyboard:\n", + " message = pyb.exec(cell)\n", + " print(message.decode('utf-8'))\n", + "\n", + "ip = get_ipython()\n", + "ip.register_magics(PyboardMagic)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pyboard" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:35.126401Z", + "start_time": "2020-05-07T07:35:35.105824Z" + } + }, + "outputs": [], + "source": [ + "import pyboard\n", + "pyb = pyboard.Pyboard('/dev/ttyACM0')\n", + "pyb.enter_raw_repl()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:11:18.145548Z", + "start_time": "2020-05-19T19:11:18.137468Z" + } + }, + "outputs": [], + "source": [ + "pyb.exit_raw_repl()\n", + "pyb.close()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:38.725924Z", + "start_time": "2020-05-07T07:35:38.645488Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "import ulab as np\n", + "\n", + "def timeit(n=1000):\n", + " def wrapper(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " run_times = np.zeros(n, dtype=np.uint16)\n", + " for i in range(n):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n", + " print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n", + " print('\\tbest: %d us'%np.min(run_times))\n", + " print('\\tworst: %d us'%np.max(run_times))\n", + " print('\\taverage: %d us'%np.mean(run_times))\n", + " print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n", + " return result\n", + " return new_func\n", + " return wrapper\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__END_OF_DEFS__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# ndarray, the base class\n", + "\n", + "The `ndarray` is the underlying container of numerical data. It can be thought of as micropython's own `array` object, but has a great number of extra features starting with how it can be initialised, which operations can be done on it, and which functions can accept it as an argument. One important property of an `ndarray` is that it is also a proper `micropython` iterable.\n", + "\n", + "The `ndarray` consists of a short header, and a pointer that holds the data. The pointer always points to a contiguous segment in memory (`numpy` is more flexible in this regard), and the header tells the interpreter, how the data from this segment is to be read out, and what the bytes mean. Some operations, e.g., `reshape`, are fast, because they do not operate on the data, they work on the header, and therefore, only a couple of bytes are manipulated, even if there are a million data entries. A more detailed exposition of how operators are implemented can be found in the section titled [Programming ulab](#Programming_ula).\n", + "\n", + "Since the `ndarray` is a binary container, it is also compact, meaning that it takes only a couple of bytes of extra RAM in addition to what is required for storing the numbers themselves. `ndarray`s are also type-aware, i.e., one can save RAM by specifying a data type, and using the smallest reasonable one. Five such types are defined, namely `uint8`, `int8`, which occupy a single byte of memory per datum, `uint16`, and `int16`, which occupy two bytes per datum, and `float`, which occupies four or eight bytes per datum. The precision/size of the `float` type depends on the definition of `mp_float_t`. Some platforms, e.g., the PYBD, implement `double`s, but some, e.g., the pyboard.v.11, do not. You can find out, what type of float your particular platform implements by looking at the output of the [.itemsize](#.itemsize) class property, or looking at the exact `dtype`, when you print out an array.\n", + "\n", + "In addition to the five above-mentioned numerical types, it is also possible to define Boolean arrays, which can be used in the indexing of data. However, Boolean arrays are really nothing but arrays of type `uint8` with an extra flag. \n", + "\n", + "On the following pages, we will see how one can work with `ndarray`s. Those familiar with `numpy` should find that the nomenclature and naming conventions of `numpy` are adhered to as closely as possible. We will point out the few differences, where necessary.\n", + "\n", + "For the sake of comparison, in addition to the `ulab` code snippets, sometimes the equivalent `numpy` code is also presented. You can find out, where the snippet is supposed to run by looking at its first line, the header of the code block." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The ndinfo function\n", + "\n", + "A concise summary of a couple of the properties of an `ndarray` can be printed out by calling the `ndinfo` \n", + "function. In addition to finding out what the *shape* and *strides* of the array array, we also get the `itemsize`, as well as the type. An interesting piece of information is the *data pointer*, which tells us, what the address of the data segment of the `ndarray` is. We will see the significance of this in the section [Slicing and indexing](#Slicing-and-indexing).\n", + "\n", + "Note that this function simply prints some information, but does not return anything. If you need to get a handle of the data contained in the printout, you should call the dedicated `shape`, `strides`, or `itemsize` functions directly." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:24:08.710325Z", + "start_time": "2021-01-12T16:24:08.699287Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "class: ndarray\n", + "shape: (5,)\n", + "strides: (8,)\n", + "itemsize: 8\n", + "data pointer: 0x7f8f6fa2e240\n", + "type: float\n", + "\n", + "\n", + "class: ndarray\n", + "shape: (5, 5)\n", + "strides: (5, 1)\n", + "itemsize: 1\n", + "data pointer: 0x7f8f6fa2e2e0\n", + "type: uint8\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(5), dtype=np.float)\n", + "b = np.array(range(25), dtype=np.uint8).reshape((5, 5))\n", + "np.ndinfo(a)\n", + "print('\\n')\n", + "np.ndinfo(b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initialising an array\n", + "\n", + "A new array can be created by passing either a standard micropython iterable, or another `ndarray` into the constructor." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Initialising by passing iterables\n", + "\n", + "If the iterable is one-dimensional, i.e., one whose elements are numbers, then a row vector will be created and returned. If the iterable is two-dimensional, i.e., one whose elements are again iterables, a matrix will be created. If the lengths of the iterables are not consistent, a `ValueError` will be raised. Iterables of different types can be mixed in the initialisation function. \n", + "\n", + "If the `dtype` keyword with the possible `uint8/int8/uint16/int16/float` values is supplied, the new `ndarray` will have that type, otherwise, it assumes `float` as default. In addition, if `ULAB_SUPPORTS_COMPLEX` is set to 1 in [ulab.h](https://github.com/v923z/micropython-ulab/blob/master/code/ulab.h), the `dtype` can also take on the value of `complex`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:24:21.952689Z", + "start_time": "2021-01-12T16:24:21.938231Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t [1, 2, 3, 4, 5, 6, 7, 8]\n", + "b:\t array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64)\n", + "\n", + "c:\t array([[0, 1, 2, 3, 4],\n", + " [20, 21, 22, 23, 24],\n", + " [44, 55, 66, 77, 88]], dtype=uint8)\n", + "\n", + "Traceback (most recent call last):\n", + " File \"/dev/shm/micropython.py\", line 15, in \n", + "ValueError: iterables are not of the same length\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = [1, 2, 3, 4, 5, 6, 7, 8]\n", + "b = np.array(a)\n", + "\n", + "print(\"a:\\t\", a)\n", + "print(\"b:\\t\", b)\n", + "\n", + "# a two-dimensional array with mixed-type initialisers\n", + "c = np.array([range(5), range(20, 25, 1), [44, 55, 66, 77, 88]], dtype=np.uint8)\n", + "print(\"\\nc:\\t\", c)\n", + "\n", + "# and now we throw an exception\n", + "d = np.array([range(5), range(10), [44, 55, 66, 77, 88]], dtype=np.uint8)\n", + "print(\"\\nd:\\t\", d)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Initialising by passing arrays\n", + "\n", + "An `ndarray` can be initialised by supplying another array. This statement is almost trivial, since `ndarray`s are iterables themselves, though it should be pointed out that initialising through arrays is a bit faster. This statement is especially true, if the `dtype`s of the source and output arrays are the same, because then the contents can simply be copied without further ado. While type conversion is also possible, it will always be slower than straight copying." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:24:33.050654Z", + "start_time": "2021-01-12T16:24:33.039754Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t [1, 2, 3, 4, 5, 6, 7, 8]\n", + "\n", + "b:\t array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64)\n", + "\n", + "c:\t array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64)\n", + "\n", + "d:\t array([1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = [1, 2, 3, 4, 5, 6, 7, 8]\n", + "b = np.array(a)\n", + "c = np.array(b)\n", + "d = np.array(b, dtype=np.uint8)\n", + "\n", + "print(\"a:\\t\", a)\n", + "print(\"\\nb:\\t\", b)\n", + "print(\"\\nc:\\t\", c)\n", + "print(\"\\nd:\\t\", d)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that the default type of the `ndarray` is `float`. Hence, if the array is initialised from another array, type conversion will always take place, except, when the output type is specifically supplied. I.e., " + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:24:39.722844Z", + "start_time": "2021-01-12T16:24:39.709963Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t array([0, 1, 2, 3, 4], dtype=uint8)\n", + "\n", + "b:\t array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(5), dtype=np.uint8)\n", + "b = np.array(a)\n", + "print(\"a:\\t\", a)\n", + "print(\"\\nb:\\t\", b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "will iterate over the elements in `a`, since in the assignment `b = np.array(a)`, no output type was given, therefore, `float` was assumed. On the other hand, " + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:25:06.597051Z", + "start_time": "2021-01-12T16:25:06.585511Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t array([0, 1, 2, 3, 4], dtype=uint8)\n", + "\n", + "b:\t array([0, 1, 2, 3, 4], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(5), dtype=np.uint8)\n", + "b = np.array(a, dtype=np.uint8)\n", + "print(\"a:\\t\", a)\n", + "print(\"\\nb:\\t\", b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "will simply copy the content of `a` into `b` without any iteration, and will, therefore, be faster. Keep this in mind, whenever the output type, or performance is important." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Array initialisation functions\n", + "\n", + "There are nine functions that can be used for initialising an array. Starred functions accept `complex` as the value of the `dtype`, if the firmware was compiled with complex support.\n", + "\n", + "1. [numpy.arange](#arange)\n", + "1. [numpy.concatenate](#concatenate)\n", + "1. [numpy.diag*](#diag)\n", + "1. [numpy.empty*](#empty)\n", + "1. [numpy.eye*](#eye)\n", + "1. [numpy.frombuffer](#frombuffer)\n", + "1. [numpy.full*](#full)\n", + "1. [numpy.linspace*](#linspace)\n", + "1. [numpy.logspace](#logspace)\n", + "1. [numpy.ones*](#ones)\n", + "1. [numpy.zeros*](#zeros)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## arange\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.arange.html\n", + "\n", + "The function returns a one-dimensional array with evenly spaced values. Takes 3 positional arguments (two are optional), and the `dtype` keyword argument. " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:26:03.795728Z", + "start_time": "2021-01-12T16:26:03.782352Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=int16)\n", + "array([2, 3, 4, 5, 6, 7, 8, 9], dtype=int16)\n", + "array([2, 5, 8], dtype=int16)\n", + "array([2.0, 5.0, 8.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "print(np.arange(10))\n", + "print(np.arange(2, 10))\n", + "print(np.arange(2, 10, 3))\n", + "print(np.arange(2, 10, 3, dtype=np.float))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## concatenate\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.concatenate.html\n", + "\n", + "The function joins a sequence of arrays, if they are compatible in shape, i.e., if all shapes except the one along the joining axis are equal. " + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:26:37.145965Z", + "start_time": "2021-01-12T16:26:37.134350Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([[0, 1, 2, 3, 4],\n", + " [5, 6, 7, 8, 9],\n", + " [10, 11, 12, 13, 14],\n", + " [15, 16, 17, 18, 19],\n", + " [20, 21, 22, 23, 24],\n", + " [0, 1, 2, 3, 4],\n", + " [5, 6, 7, 8, 9],\n", + " [10, 11, 12, 13, 14]], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(25), dtype=np.uint8).reshape((5, 5))\n", + "b = np.array(range(15), dtype=np.uint8).reshape((3, 5))\n", + "\n", + "c = np.concatenate((a, b), axis=0)\n", + "print(c)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**WARNING**: `numpy` accepts arbitrary `dtype`s in the sequence of arrays, in `ulab` the `dtype`s must be identical. If you want to concatenate different types, you have to convert all arrays to the same type first. Here `b` is of `float` type, so it cannot directly be concatenated to `a`. However, if we cast the `dtype` of `b`, the concatenation works:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:26:56.120820Z", + "start_time": "2021-01-12T16:26:56.102365Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([[0, 1, 2, 3, 4],\n", + " [5, 6, 7, 8, 9],\n", + " [10, 11, 12, 13, 14],\n", + " [15, 16, 17, 18, 19],\n", + " [20, 21, 22, 23, 24]], dtype=uint8)\n", + "====================\n", + "d: array([[1, 2, 3],\n", + " [4, 5, 6],\n", + " [7, 8, 9],\n", + " [10, 11, 12],\n", + " [13, 14, 15]], dtype=uint8)\n", + "====================\n", + "c: array([[1, 2, 3, 0, 1, 2, 3, 4],\n", + " [4, 5, 6, 5, 6, 7, 8, 9],\n", + " [7, 8, 9, 10, 11, 12, 13, 14],\n", + " [10, 11, 12, 15, 16, 17, 18, 19],\n", + " [13, 14, 15, 20, 21, 22, 23, 24]], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(25), dtype=np.uint8).reshape((5, 5))\n", + "b = np.array(range(15), dtype=np.float).reshape((5, 3))\n", + "d = np.array(b+1, dtype=np.uint8)\n", + "print('a: ', a)\n", + "print('='*20 + '\\nd: ', d)\n", + "c = np.concatenate((d, a), axis=1)\n", + "print('='*20 + '\\nc: ', c)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## diag\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.diag.html\n", + "\n", + "Extract a diagonal, or construct a diagonal array.\n", + "\n", + "The function takes a positional argument, an `ndarray`, or any `micropython` iterable, and an optional keyword argument, a shift, with a default value of 0. If the first argument is a two-dimensional array (or a two-dimensional iterable, e.g., a list of lists), the function returns a one-dimensional array containing the diagonal entries. The diagonal can be shifted by an amount given in the second argument. If the shift is larger than the length of the corresponding axis, an empty array is returned.\n", + "\n", + "If the first argument is a one-dimensional array, the function returns a two-dimensional square tensor with its diagonal elements given by the first argument. Again, the diagonal be shifted by an amount given by the keyword argument.\n", + "\n", + "The `diag` function can accept a complex array, if the firmware was compiled with complex support." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "ExecuteTime": { + "end_time": "2022-02-09T06:24:38.290495Z", + "start_time": "2022-02-09T06:24:38.273075Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([[1, 0, 0],\n", + " [0, 2, 0],\n", + " [0, 0, 3]], dtype=uint8)\n", + "\n", + "diagonal shifted by 2\n", + "array([[0, 0, 1, 0, 0],\n", + " [0, 0, 0, 2, 0],\n", + " [0, 0, 0, 0, 3],\n", + " [0, 0, 0, 0, 0],\n", + " [0, 0, 0, 0, 0]], dtype=uint8)\n", + "\n", + "diagonal shifted by -2\n", + "array([[0, 0, 0, 0, 0],\n", + " [0, 0, 0, 0, 0],\n", + " [1, 0, 0, 0, 0],\n", + " [0, 2, 0, 0, 0],\n", + " [0, 0, 3, 0, 0]], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3], dtype=np.uint8)\n", + "print(np.diag(a))\n", + "\n", + "print('\\ndiagonal shifted by 2')\n", + "print(np.diag(a, k=2))\n", + "\n", + "print('\\ndiagonal shifted by -2')\n", + "print(np.diag(a, k=-2))" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "ExecuteTime": { + "end_time": "2022-02-09T06:26:39.213828Z", + "start_time": "2022-02-09T06:26:39.199294Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([[0, 1, 2, 3],\n", + " [4, 5, 6, 7],\n", + " [8, 9, 10, 11],\n", + " [12, 13, 14, 15]], dtype=int16)\n", + "\n", + "diagonal of a:\n", + "array([0, 5, 10, 15], dtype=int16)\n", + "\n", + "diagonal of a:\n", + "array([0, 5, 10, 15], dtype=int16)\n", + "\n", + "diagonal of a, shifted by 2\n", + "array([2, 7], dtype=int16)\n", + "\n", + "diagonal of a, shifted by 5\n", + "array([], dtype=int16)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.arange(16).reshape((4, 4))\n", + "print(a)\n", + "print('\\ndiagonal of a:')\n", + "print(np.diag(a))\n", + "\n", + "print('\\ndiagonal of a:')\n", + "print(np.diag(a))\n", + "\n", + "print('\\ndiagonal of a, shifted by 2')\n", + "print(np.diag(a, k=2))\n", + "\n", + "print('\\ndiagonal of a, shifted by 5')\n", + "print(np.diag(a, k=5))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## empty\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.empty.html\n", + "\n", + "`empty` is simply an alias for `zeros`, i.e., as opposed to `numpy`, the entries of the tensor will be initialised to zero. \n", + "\n", + "The `empty` function can accept complex as the value of the dtype, if the firmware was compiled with complex support." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## eye\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.eye.html\n", + "\n", + "Another special array method is the `eye` function, whose call signature is \n", + "\n", + "```python\n", + "eye(N, M, k=0, dtype=float)\n", + "```\n", + "where `N` (`M`) specify the dimensions of the matrix (if only `N` is supplied, then we get a square matrix, otherwise one with `M` rows, and `N` columns), and `k` is the shift of the ones (the main diagonal corresponds to `k=0`). Here are a couple of examples.\n", + "\n", + "The `eye` function can accept `complex` as the value of the `dtype`, if the firmware was compiled with complex support." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### With a single argument" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:27:08.533394Z", + "start_time": "2021-01-12T16:27:08.518940Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([[1.0, 0.0, 0.0, 0.0, 0.0],\n", + " [0.0, 1.0, 0.0, 0.0, 0.0],\n", + " [0.0, 0.0, 1.0, 0.0, 0.0],\n", + " [0.0, 0.0, 0.0, 1.0, 0.0],\n", + " [0.0, 0.0, 0.0, 0.0, 1.0]], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "print(np.eye(5))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Specifying the dimensions of the matrix" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:27:34.075468Z", + "start_time": "2021-01-12T16:27:34.064137Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([[0, 0, 0, 0, 0, 0],\n", + " [1, 0, 0, 0, 0, 0],\n", + " [0, 1, 0, 0, 0, 0],\n", + " [0, 0, 1, 0, 0, 0]], dtype=int16)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "print(np.eye(4, M=6, k=-1, dtype=np.int16))" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:27:42.492135Z", + "start_time": "2021-01-12T16:27:42.477684Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([[1, 0, 0, 0, 0, 0],\n", + " [0, 1, 0, 0, 0, 0],\n", + " [0, 0, 1, 0, 0, 0],\n", + " [0, 0, 0, 1, 0, 0]], dtype=int8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "print(np.eye(4, M=6, dtype=np.int8))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## frombuffer\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.frombuffer.html\n", + "\n", + "The function interprets a contiguous buffer as a one-dimensional array, and thus can be used for piping buffered data directly into an array. This method of analysing, e.g., ADC data is much more efficient than passing the ADC buffer into the `array` constructor, because `frombuffer` simply creates the `ndarray` header and blindly copies the memory segment, without inspecting the underlying data. \n", + "\n", + "The function takes a single positional argument, the buffer, and three keyword arguments. These are the `dtype` with a default value of `float`, the `offset`, with a default of 0, and the `count`, with a default of -1, meaning that all data are taken in." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-15T07:01:35.320458Z", + "start_time": "2021-01-15T07:01:35.307407Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "buffer: b'\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08'\n", + "a, all data read: array([1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8)\n", + "b, all data with an offset: array([3, 4, 5, 6, 7, 8], dtype=uint8)\n", + "c, only 3 items with an offset: array([3, 4, 5], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "buffer = b'\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08'\n", + "print('buffer: ', buffer)\n", + "\n", + "a = np.frombuffer(buffer, dtype=np.uint8)\n", + "print('a, all data read: ', a)\n", + "\n", + "b = np.frombuffer(buffer, dtype=np.uint8, offset=2)\n", + "print('b, all data with an offset: ', b)\n", + "\n", + "c = np.frombuffer(buffer, dtype=np.uint8, offset=2, count=3)\n", + "print('c, only 3 items with an offset: ', c)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## full\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.full.html\n", + "\n", + "The function returns an array of arbitrary dimension, whose elements are all equal to the second positional argument. The first argument is a tuple describing the shape of the tensor. The `dtype` keyword argument with a default value of `float` can also be supplied.\n", + "\n", + "The `full` function can accept a complex scalar, or `complex` as the value of `dtype`, if the firmware was compiled with complex support." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:29:11.931011Z", + "start_time": "2021-01-12T16:29:11.915195Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([[3.0, 3.0, 3.0, 3.0],\n", + " [3.0, 3.0, 3.0, 3.0]], dtype=float64)\n", + "\n", + "====================\n", + "\n", + "array([[3, 3, 3, 3],\n", + " [3, 3, 3, 3]], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "# create an array with the default type\n", + "print(np.full((2, 4), 3))\n", + "\n", + "print('\\n' + '='*20 + '\\n')\n", + "# the array type is uint8 now\n", + "print(np.full((2, 4), 3, dtype=np.uint8))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## linspace\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.linspace.html\n", + "\n", + "This function returns an array, whose elements are uniformly spaced between the `start`, and `stop` points. The number of intervals is determined by the `num` keyword argument, whose default value is 50. With the `endpoint` keyword argument (defaults to `True`) one can include `stop` in the sequence. In addition, the `dtype` keyword can be supplied to force type conversion of the output. The default is `float`. Note that, when `dtype` is of integer type, the sequence is not necessarily evenly spaced. This is not an error, rather a consequence of rounding. (This is also the `numpy` behaviour.)\n", + "\n", + "The `linspace` function can accept `complex` as the value of the `dtype`, if the firmware was compiled with complex support. The output `dtype` is automatically complex, if either of the endpoints is a complex scalar." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:29:45.897927Z", + "start_time": "2021-01-12T16:29:45.876325Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "default sequence:\t array([0.0, 0.2040816326530612, 0.4081632653061225, ..., 9.591836734693871, 9.795918367346932, 9.999999999999993], dtype=float64)\n", + "num=5:\t\t\t array([0.0, 2.5, 5.0, 7.5, 10.0], dtype=float64)\n", + "num=5:\t\t\t array([0.0, 2.0, 4.0, 6.0, 8.0], dtype=float64)\n", + "num=5:\t\t\t array([0, 0, 1, 2, 2, 3, 4], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "# generate a sequence with defaults\n", + "print('default sequence:\\t', np.linspace(0, 10))\n", + "\n", + "# num=5\n", + "print('num=5:\\t\\t\\t', np.linspace(0, 10, num=5))\n", + "\n", + "# num=5, endpoint=False\n", + "print('num=5:\\t\\t\\t', np.linspace(0, 10, num=5, endpoint=False))\n", + "\n", + "# num=5, endpoint=False, dtype=uint8\n", + "print('num=5:\\t\\t\\t', np.linspace(0, 5, num=7, endpoint=False, dtype=np.uint8))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## logspace\n", + "\n", + "`linspace`' equivalent for logarithmically spaced data is `logspace`. This function produces a sequence of numbers, in which the quotient of consecutive numbers is constant. This is a geometric sequence.\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.logspace.html\n", + "\n", + "This function returns an array, whose elements are uniformly spaced between the `start`, and `stop` points. The number of intervals is determined by the `num` keyword argument, whose default value is 50. With the `endpoint` keyword argument (defaults to `True`) one can include `stop` in the sequence. In addition, the `dtype` keyword can be supplied to force type conversion of the output. The default is `float`. Note that, exactly as in `linspace`, when `dtype` is of integer type, the sequence is not necessarily evenly spaced in log space.\n", + "\n", + "In addition to the keyword arguments found in `linspace`, `logspace` also accepts the `base` argument. The default value is 10. " + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:30:44.483893Z", + "start_time": "2021-01-12T16:30:44.466705Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "default sequence:\t array([1.0, 1.151395399326447, 1.325711365590109, ..., 754.3120063354646, 868.5113737513561, 1000.000000000004], dtype=float64)\n", + "num=5:\t\t\t array([10.0, 1778.279410038923, 316227.766016838, 56234132.5190349, 10000000000.0], dtype=float64)\n", + "num=5:\t\t\t array([10.0, 630.9573444801933, 39810.71705534974, 2511886.431509581, 158489319.2461114], dtype=float64)\n", + "num=5:\t\t\t array([2.0, 6.964404506368993, 24.25146506416637, 84.44850628946524, 294.066778879241], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "# generate a sequence with defaults\n", + "print('default sequence:\\t', np.logspace(0, 3))\n", + "\n", + "# num=5\n", + "print('num=5:\\t\\t\\t', np.logspace(1, 10, num=5))\n", + "\n", + "# num=5, endpoint=False\n", + "print('num=5:\\t\\t\\t', np.logspace(1, 10, num=5, endpoint=False))\n", + "\n", + "# num=5, endpoint=False\n", + "print('num=5:\\t\\t\\t', np.logspace(1, 10, num=5, endpoint=False, base=2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ones, zeros\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.zeros.html\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.ones.html\n", + "\n", + "A couple of special arrays and matrices can easily be initialised by calling one of the `ones`, or `zeros` functions. `ones` and `zeros` follow the same pattern, and have the call signature\n", + "\n", + "```python\n", + "ones(shape, dtype=float)\n", + "zeros(shape, dtype=float)\n", + "```\n", + "where shape is either an integer, or a tuple specifying the shape.\n", + "\n", + "The `ones/zeros` functions can accept complex as the value of the dtype, if the firmware was compiled with complex support." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:32:05.422109Z", + "start_time": "2021-01-12T16:32:05.407921Z" + }, + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([1, 1, 1, 1, 1, 1], dtype=uint8)\n", + "array([[0.0, 0.0, 0.0, 0.0],\n", + " [0.0, 0.0, 0.0, 0.0],\n", + " [0.0, 0.0, 0.0, 0.0],\n", + " [0.0, 0.0, 0.0, 0.0],\n", + " [0.0, 0.0, 0.0, 0.0],\n", + " [0.0, 0.0, 0.0, 0.0]], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "print(np.ones(6, dtype=np.uint8))\n", + "\n", + "print(np.zeros((6, 4)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " When specifying the shape, make sure that the length of the tuple is not larger than the maximum dimension of your firmware." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:01:44.960353Z", + "start_time": "2021-01-13T06:01:44.944935Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "maximum number of dimensions: 2.1.0-2D\n", + "\n", + "Traceback (most recent call last):\n", + " File \"/dev/shm/micropython.py\", line 7, in \n", + "TypeError: too many dimensions\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "import ulab\n", + "\n", + "print('maximum number of dimensions: ', ulab.__version__)\n", + "\n", + "print(np.zeros((2, 2, 2)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Customising array printouts" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`ndarray`s are pretty-printed, i.e., if the number of entries along the last axis is larger than 10 (default value), then only the first and last three entries will be printed. Also note that, as opposed to `numpy`, the printout always contains the `dtype`." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:02:20.162127Z", + "start_time": "2021-01-13T06:02:20.146219Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t array([0.0, 1.0, 2.0, ..., 197.0, 198.0, 199.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(200))\n", + "print(\"a:\\t\", a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## set_printoptions\n", + "\n", + "The default values can be overwritten by means of the `set_printoptions` function [numpy.set_printoptions](https://numpy.org/doc/1.18/reference/generated/numpy.set_printoptions.html), which accepts two keywords arguments, the `threshold`, and the `edgeitems`. The first of these arguments determines the length of the longest array that will be printed in full, while the second is the number of items that will be printed on the left and right hand side of the ellipsis, if the array is longer than `threshold`." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:02:42.073823Z", + "start_time": "2021-01-13T06:02:42.057424Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a printed with defaults:\t array([0.0, 1.0, 2.0, ..., 17.0, 18.0, 19.0], dtype=float64)\n", + "\n", + "a printed in full:\t\t array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0], dtype=float64)\n", + "\n", + "a truncated with 2 edgeitems:\t array([0.0, 1.0, ..., 18.0, 19.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(20))\n", + "print(\"a printed with defaults:\\t\", a)\n", + "\n", + "np.set_printoptions(threshold=200)\n", + "print(\"\\na printed in full:\\t\\t\", a)\n", + "\n", + "np.set_printoptions(threshold=10, edgeitems=2)\n", + "print(\"\\na truncated with 2 edgeitems:\\t\", a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## get_printoptions\n", + "\n", + "The set value of the `threshold` and `edgeitems` can be retrieved by calling the `get_printoptions` function with no arguments. The function returns a *dictionary* with two keys." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:02:51.383653Z", + "start_time": "2021-01-13T06:02:51.372551Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'threshold': 100, 'edgeitems': 20}\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "np.set_printoptions(threshold=100, edgeitems=20)\n", + "print(np.get_printoptions())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Methods and properties of ndarrays\n", + "\n", + "Arrays have several *properties* that can queried, and some methods that can be called. With the exception of the flatten and transpose operators, properties return an object that describe some feature of the array, while the methods return a new array-like object. The `imag`, and `real` properties are included in the firmware only, when it was compiled with complex support.\n", + "\n", + "1. [.byteswap](#.byteswap)\n", + "1. [.copy](#.copy)\n", + "1. [.dtype](#.dtype)\n", + "1. [.flat](#.flat)\n", + "1. [.flatten](#.flatten)\n", + "1. [.imag*](#.imag)\n", + "1. [.itemsize](#.itemsize)\n", + "1. [.real*](#.real)\n", + "1. [.reshape](#.reshape)\n", + "1. [.shape](#.shape)\n", + "1. [.size](#.size)\n", + "1. [.T](#.transpose)\n", + "1. [.tobytes](#.tobytes)\n", + "1. [.tolist](#.tolist)\n", + "1. [.transpose](#.transpose)\n", + "1. [.sort](#.sort)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## .byteswap\n", + "\n", + "`numpy` https://numpy.org/doc/stable/reference/generated/numpy.char.chararray.byteswap.html\n", + "\n", + "The method takes a single keyword argument, `inplace`, with values `True` or `False`, and swaps the bytes in the array. If `inplace = False`, a new `ndarray` is returned, otherwise the original values are overwritten.\n", + "\n", + "The `frombuffer` function is a convenient way of receiving data from peripheral devices that work with buffers. However, it is not guaranteed that the byte order (in other words, the _endianness_) of the peripheral device matches that of the microcontroller. The `.byteswap` method makes it possible to change the endianness of the incoming data stream.\n", + "\n", + "Obviously, byteswapping makes sense only for those cases, when a datum occupies more than one byte, i.e., for the `uint16`, `int16`, and `float` `dtype`s. When `dtype` is either `uint8`, or `int8`, the method simply returns a view or copy of self, depending upon the value of `inplace`." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "ExecuteTime": { + "end_time": "2021-02-15T16:06:20.409727Z", + "start_time": "2021-02-15T16:06:20.398057Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "buffer: b'\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08'\n", + "a: array([513, 1027, 1541, 2055], dtype=uint16)\n", + "b: array([258, 772, 1286, 1800], dtype=uint16)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "buffer = b'\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08'\n", + "print('buffer: ', buffer)\n", + "\n", + "a = np.frombuffer(buffer, dtype=np.uint16)\n", + "print('a: ', a)\n", + "b = a.byteswap()\n", + "print('b: ', b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## .copy\n", + "\n", + "The `.copy` method creates a new *deep copy* of an array, i.e., the entries of the source array are *copied* into the target array." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:02:58.898485Z", + "start_time": "2021-01-13T06:02:58.878864Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([1, 2, 3, 4], dtype=int8)\n", + "====================\n", + "b: array([1, 2, 3, 4], dtype=int8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4], dtype=np.int8)\n", + "b = a.copy()\n", + "print('a: ', a)\n", + "print('='*20)\n", + "print('b: ', b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## .dtype\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.dtype.htm\n", + "\n", + "The `.dtype` property is the `dtype` of an array. This can then be used for initialising another array with the matching type. `ulab` implements two versions of `dtype`; one that is `numpy`-like, i.e., one, which returns a `dtype` object, and one that is significantly cheaper in terms of flash space, but does not define a `dtype` object, and holds a single character (number) instead. " + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-02T17:16:12.818777Z", + "start_time": "2020-11-02T17:16:12.807147Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([1, 2, 3, 4], dtype=int8)\n", + "dtype of a: dtype('int8')\n", + "\n", + "b: array([5, 6, 7], dtype=int8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4], dtype=np.int8)\n", + "b = np.array([5, 6, 7], dtype=a.dtype)\n", + "print('a: ', a)\n", + "print('dtype of a: ', a.dtype)\n", + "print('\\nb: ', b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If the `ulab.h` header file sets the pre-processor constant `ULAB_HAS_DTYPE_OBJECT` to 0 as\n", + "\n", + "```c\n", + "#define ULAB_HAS_DTYPE_OBJECT (0)\n", + "```\n", + "then the output of the previous snippet will be" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-02T20:36:23.099166Z", + "start_time": "2020-11-02T20:36:23.088586Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([1, 2, 3, 4], dtype=int8)\n", + "dtype of a: 98\n", + "\n", + "b: array([5, 6, 7], dtype=int8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4], dtype=np.int8)\n", + "b = np.array([5, 6, 7], dtype=a.dtype)\n", + "print('a: ', a)\n", + "print('dtype of a: ', a.dtype)\n", + "print('\\nb: ', b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here 98 is nothing but the ASCII value of the character `b`, which is the type code for signed 8-bit integers. The object definition adds around 600 bytes to the firmware." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## .flat\n", + "\n", + "numpy: https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.flat.htm\n", + "\n", + "`.flat` returns the array's flat iterator. For one-dimensional objects the flat iterator is equivalent to the standart iterator, while for higher dimensional tensors, it amounts to first flattening the array, and then iterating over it. Note, however, that the flat iterator does not consume RAM beyond what is required for holding the position of the iterator itself, while flattening produces a new copy." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "2\n", + "3\n", + "4\n", + "a:\n", + " array([[1, 2, 3, 4],\n", + " [5, 6, 7, 8]], dtype=int8)\n", + "array([1, 2, 3, 4], dtype=int8)\n", + "array([5, 6, 7, 8], dtype=int8)\n", + "1\n", + "2\n", + "3\n", + "4\n", + "5\n", + "6\n", + "7\n", + "8\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4], dtype=np.int8)\n", + "for _a in a:\n", + " print(_a)\n", + "\n", + "a = np.array([[1, 2, 3, 4], [5, 6, 7, 8]], dtype=np.int8)\n", + "print('a:\\n', a)\n", + "\n", + "for _a in a:\n", + " print(_a)\n", + "\n", + "for _a in a.flat:\n", + " print(_a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## .flatten\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.flatten.htm\n", + "\n", + "`.flatten` returns the flattened array. The array can be flattened in `C` style (i.e., moving along the last axis in the tensor), or in `fortran` style (i.e., moving along the first axis in the tensor)." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:07:16.735771Z", + "start_time": "2021-01-13T06:07:16.723514Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: \t\t array([1, 2, 3, 4], dtype=int8)\n", + "a flattened: \t array([1, 2, 3, 4], dtype=int8)\n", + "\n", + "b: array([[1, 2, 3],\n", + " [4, 5, 6]], dtype=int8)\n", + "b flattened (C): \t array([1, 2, 3, 4, 5, 6], dtype=int8)\n", + "b flattened (F): \t array([1, 4, 2, 5, 3, 6], dtype=int8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4], dtype=np.int8)\n", + "print(\"a: \\t\\t\", a)\n", + "print(\"a flattened: \\t\", a.flatten())\n", + "\n", + "b = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int8)\n", + "print(\"\\nb:\", b)\n", + "\n", + "print(\"b flattened (C): \\t\", b.flatten())\n", + "print(\"b flattened (F): \\t\", b.flatten(order='F'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## .imag\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.imag.html\n", + "\n", + "The `.imag` property is defined only, if the firmware was compiled with complex support, and returns a copy with the imaginary part of an array. If the array is real, then the output is straight zeros with the `dtype` of the input. If the input is complex, the output `dtype` is always `float`, irrespective of the values." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-07T19:07:26.171208Z", + "start_time": "2022-01-07T19:07:26.152633Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t array([1, 2, 3], dtype=uint16)\n", + "a.imag:\t array([0, 0, 0], dtype=uint16)\n", + "\n", + "b:\t array([1.0+0.0j, 2.0+1.0j, 3.0-1.0j], dtype=complex)\n", + "b.imag:\t array([0.0, 1.0, -1.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3], dtype=np.uint16)\n", + "print(\"a:\\t\", a)\n", + "print(\"a.imag:\\t\", a.imag)\n", + "\n", + "b = np.array([1, 2+1j, 3-1j], dtype=np.complex)\n", + "print(\"\\nb:\\t\", b)\n", + "print(\"b.imag:\\t\", b.imag)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## .itemsize\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.itemsize.html\n", + "\n", + "The `.itemsize` property is an integer with the size of elements in the array." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:07:49.080817Z", + "start_time": "2021-01-13T06:07:49.065749Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([1, 2, 3], dtype=int8)\n", + "itemsize of a: 1\n", + "\n", + "b:\n", + " array([[1.0, 2.0],\n", + " [3.0, 4.0]], dtype=float64)\n", + "itemsize of b: 8\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3], dtype=np.int8)\n", + "print(\"a:\\n\", a)\n", + "print(\"itemsize of a:\", a.itemsize)\n", + "\n", + "b= np.array([[1, 2], [3, 4]], dtype=np.float)\n", + "print(\"\\nb:\\n\", b)\n", + "print(\"itemsize of b:\", b.itemsize)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## .real\n", + "\n", + "numpy: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.real.html\n", + "\n", + "The `.real` property is defined only, if the firmware was compiled with complex support, and returns a copy with the real part of an array." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-07T19:10:01.870921Z", + "start_time": "2022-01-07T19:10:01.860071Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t array([1, 2, 3], dtype=uint16)\n", + "a.real:\t array([1, 2, 3], dtype=uint16)\n", + "\n", + "b:\t array([1.0+0.0j, 2.0+1.0j, 3.0-1.0j], dtype=complex)\n", + "b.real:\t array([1.0, 2.0, 3.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3], dtype=np.uint16)\n", + "print(\"a:\\t\", a)\n", + "print(\"a.real:\\t\", a.real)\n", + "\n", + "b = np.array([1, 2+1j, 3-1j], dtype=np.complex)\n", + "print(\"\\nb:\\t\", b)\n", + "print(\"b.real:\\t\", b.real)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## .reshape\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.reshape.html\n", + "\n", + "`reshape` re-writes the shape properties of an `ndarray`, but the array will not be modified in any other way. The function takes a single 2-tuple with two integers as its argument. The 2-tuple should specify the desired number of rows and columns. If the new shape is not consistent with the old, a `ValueError` exception will be raised." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:08:12.234490Z", + "start_time": "2021-01-13T06:08:12.217652Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a (4 by 4): array([[1, 2, 3, 4],\n", + " [5, 6, 7, 8],\n", + " [9, 10, 11, 12],\n", + " [13, 14, 15, 16]], dtype=uint8)\n", + "a (2 by 8): array([[1, 2, 3, 4, 5, 6, 7, 8],\n", + " [9, 10, 11, 12, 13, 14, 15, 16]], dtype=uint8)\n", + "a (1 by 16): array([[1, 2, 3, ..., 14, 15, 16]], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]], dtype=np.uint8)\n", + "print('a (4 by 4):', a)\n", + "print('a (2 by 8):', a.reshape((2, 8)))\n", + "print('a (1 by 16):', a.reshape((1, 16)))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Note that `ndarray.reshape()` can also be called by assigning to `ndarray.shape`. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## .shape\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.shape.html\n", + "\n", + "The `.shape` property is a tuple whose elements are the length of the array along each axis. " + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:08:50.479850Z", + "start_time": "2021-01-13T06:08:50.464741Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([1, 2, 3, 4], dtype=int8)\n", + "shape of a: (4,)\n", + "\n", + "b:\n", + " array([[1, 2],\n", + " [3, 4]], dtype=int8)\n", + "shape of b: (2, 2)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4], dtype=np.int8)\n", + "print(\"a:\\n\", a)\n", + "print(\"shape of a:\", a.shape)\n", + "\n", + "b= np.array([[1, 2], [3, 4]], dtype=np.int8)\n", + "print(\"\\nb:\\n\", b)\n", + "print(\"shape of b:\", b.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By assigning a tuple to the `.shape` property, the array can be `reshape`d:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0], dtype=float64)\n", + "\n", + "a:\n", + " array([[1.0, 2.0, 3.0],\n", + " [4.0, 5.0, 6.0],\n", + " [7.0, 8.0, 9.0]], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])\n", + "print('a:\\n', a)\n", + "\n", + "a.shape = (3, 3)\n", + "print('\\na:\\n', a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## .size\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.size.html\n", + "\n", + "The `.size` property is an integer specifying the number of elements in the array. " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "ExecuteTime": { + "end_time": "2020-02-11T06:32:22.721112Z", + "start_time": "2020-02-11T06:32:22.713111Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([1, 2, 3], dtype=int8)\n", + "size of a: 3\n", + "\n", + "b:\n", + " array([[1, 2],\n", + "\t [3, 4]], dtype=int8)\n", + "size of b: 4\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3], dtype=np.int8)\n", + "print(\"a:\\n\", a)\n", + "print(\"size of a:\", a.size)\n", + "\n", + "b= np.array([[1, 2], [3, 4]], dtype=np.int8)\n", + "print(\"\\nb:\\n\", b)\n", + "print(\"size of b:\", b.size)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ".T\n", + "\n", + "The `.T` property of the `ndarray` is equivalent to [.transpose](#.transpose)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## .tobytes\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.tobytes.html\n", + "\n", + "The `.tobytes` method can be used for acquiring a handle of the underlying data pointer of an array, and it returns a new `bytearray` that can be fed into any method that can accep a `bytearray`, e.g., ADC data can be buffered into this `bytearray`, or the `bytearray` can be fed into a DAC. Since the `bytearray` is really nothing but the bare data container of the array, any manipulation on the `bytearray` automatically modifies the array itself.\n", + "\n", + "Note that the method raises a `ValueError` exception, if the array is not dense (i.e., it has already been sliced)." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:09:57.262071Z", + "start_time": "2021-01-13T06:09:57.250519Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([0, 1, 2, 3, 4, 5, 6, 7], dtype=uint8)\n", + "b: bytearray(b'\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07')\n", + "====================\n", + "b: bytearray(b'\\r\\x01\\x02\\x03\\x04\\x05\\x06\\x07')\n", + "a: array([13, 1, 2, 3, 4, 5, 6, 7], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(8), dtype=np.uint8)\n", + "print('a: ', a)\n", + "b = a.tobytes()\n", + "print('b: ', b)\n", + "\n", + "# modify b\n", + "b[0] = 13\n", + "\n", + "print('='*20)\n", + "print('b: ', b)\n", + "print('a: ', a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## .tolist\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.tolist.html\n", + "\n", + "The `.tolist` method can be used for converting the numerical array into a (nested) `python` lists." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-07T19:01:28.671234Z", + "start_time": "2022-01-07T19:01:28.568786Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([0, 1, 2, 3], dtype=uint8)\n", + "b: [0, 1, 2, 3]\n", + "====================\n", + "c: array([[0, 1],\n", + " [2, 3]], dtype=uint8)\n", + "d: [[0, 1], [2, 3]]\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(4), dtype=np.uint8)\n", + "print('a: ', a)\n", + "b = a.tolist()\n", + "print('b: ', b)\n", + "\n", + "c = a.reshape((2, 2))\n", + "print('='*20)\n", + "print('c: ', c)\n", + "d = c.tolist()\n", + "print('d: ', d)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## .transpose\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.transpose.html\n", + "\n", + "Returns the transposed array. Only defined, if the number of maximum dimensions is larger than 1." + ] + }, + { + "cell_type": "code", + "execution_count": 384, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-19T08:39:11.844987Z", + "start_time": "2019-10-19T08:39:11.828099Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([[1, 2, 3],\n", + "\t [4, 5, 6],\n", + "\t [7, 8, 9],\n", + "\t [10, 11, 12]], dtype=uint8)\n", + "shape of a: (4, 3)\n", + "\n", + "transpose of a:\n", + " array([[1, 4, 7, 10],\n", + "\t [2, 5, 8, 11],\n", + "\t [3, 6, 9, 12]], dtype=uint8)\n", + "shape of a: (3, 4)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]], dtype=np.uint8)\n", + "print('a:\\n', a)\n", + "print('shape of a:', a.shape)\n", + "a.transpose()\n", + "print('\\ntranspose of a:\\n', a)\n", + "print('shape of a:', a.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The transpose of the array can also be gotten through the `T` property:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([[1, 2, 3],\n", + " [4, 5, 6],\n", + " [7, 8, 9]], dtype=uint8)\n", + "\n", + "transpose of a:\n", + " array([[1, 4, 7],\n", + " [2, 5, 8],\n", + " [3, 6, 9]], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.uint8)\n", + "print('a:\\n', a)\n", + "print('\\ntranspose of a:\\n', a.T)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## .sort\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.sort.html\n", + "\n", + "In-place sorting of an `ndarray`. For a more detailed exposition, see [sort](#sort)." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:11:20.989109Z", + "start_time": "2021-01-13T06:11:20.972842Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "a:\n", + " array([[1, 12, 3, 0],\n", + " [5, 3, 4, 1],\n", + " [9, 11, 1, 8],\n", + " [7, 10, 0, 1]], dtype=uint8)\n", + "\n", + "a sorted along vertical axis:\n", + " array([[1, 3, 0, 0],\n", + " [5, 10, 1, 1],\n", + " [7, 11, 3, 1],\n", + " [9, 12, 4, 8]], dtype=uint8)\n", + "\n", + "a sorted along horizontal axis:\n", + " array([[0, 1, 3, 12],\n", + " [1, 3, 4, 5],\n", + " [1, 8, 9, 11],\n", + " [0, 1, 7, 10]], dtype=uint8)\n", + "\n", + "flattened a sorted:\n", + " array([0, 0, 1, ..., 10, 11, 12], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.uint8)\n", + "print('\\na:\\n', a)\n", + "a.sort(axis=0)\n", + "print('\\na sorted along vertical axis:\\n', a)\n", + "\n", + "a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.uint8)\n", + "a.sort(axis=1)\n", + "print('\\na sorted along horizontal axis:\\n', a)\n", + "\n", + "a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.uint8)\n", + "a.sort(axis=None)\n", + "print('\\nflattened a sorted:\\n', a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Unary operators\n", + "\n", + "With the exception of `len`, which returns a single number, all unary operators manipulate the underlying data element-wise. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## len\n", + "\n", + "This operator takes a single argument, the array, and returns either the length of the first axis." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:11:49.266192Z", + "start_time": "2021-01-13T06:11:49.255493Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t array([1, 2, 3, 4, 5], dtype=uint8)\n", + "length of a: 5\n", + "shape of a: (5,)\n", + "\n", + "b:\t array([[0, 1, 2, 3, 4],\n", + " [0, 1, 2, 3, 4],\n", + " [0, 1, 2, 3, 4],\n", + " [0, 1, 2, 3, 4]], dtype=uint8)\n", + "length of b: 2\n", + "shape of b: (4, 5)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4, 5], dtype=np.uint8)\n", + "b = np.array([range(5), range(5), range(5), range(5)], dtype=np.uint8)\n", + "\n", + "print(\"a:\\t\", a)\n", + "print(\"length of a: \", len(a))\n", + "print(\"shape of a: \", a.shape)\n", + "print(\"\\nb:\\t\", b)\n", + "print(\"length of b: \", len(b))\n", + "print(\"shape of b: \", b.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " The number returned by `len` is also the length of the iterations, when the array supplies the elements for an iteration (see later)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## invert\n", + "\n", + "The function is defined for integer data types (`uint8`, `int8`, `uint16`, and `int16`) only, takes a single argument, and returns the element-by-element, bit-wise inverse of the array. If a `float` is supplied, the function raises a `ValueError` exception.\n", + "\n", + "With signed integers (`int8`, and `int16`), the results might be unexpected, as in the example below:" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-11T13:16:16.754210Z", + "start_time": "2019-10-11T13:16:16.735618Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t array([0, -1, -100], dtype=int8)\n", + "inverse of a:\t array([-1, 0, 99], dtype=int8)\n", + "\n", + "a:\t\t array([0, 1, 254, 255], dtype=uint8)\n", + "inverse of a:\t array([255, 254, 1, 0], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([0, -1, -100], dtype=np.int8)\n", + "print(\"a:\\t\\t\", a)\n", + "print(\"inverse of a:\\t\", ~a)\n", + "\n", + "a = np.array([0, 1, 254, 255], dtype=np.uint8)\n", + "print(\"\\na:\\t\\t\", a)\n", + "print(\"inverse of a:\\t\", ~a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## abs\n", + "\n", + "This function takes a single argument, and returns the element-by-element absolute value of the array. When the data type is unsigned (`uint8`, or `uint16`), a copy of the array will be returned immediately, and no calculation takes place." + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-11T13:05:43.926821Z", + "start_time": "2019-10-11T13:05:43.912629Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t\t array([0, -1, -100], dtype=int8)\n", + "absolute value of a:\t array([0, 1, 100], dtype=int8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([0, -1, -100], dtype=np.int8)\n", + "print(\"a:\\t\\t\\t \", a)\n", + "print(\"absolute value of a:\\t \", abs(a))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## neg\n", + "\n", + "This operator takes a single argument, and changes the sign of each element in the array. Unsigned values are wrapped. " + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-11T13:17:00.946009Z", + "start_time": "2019-10-11T13:17:00.927264Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t array([10, -1, 1], dtype=int8)\n", + "negative of a:\t array([-10, 1, -1], dtype=int8)\n", + "\n", + "b:\t\t array([0, 100, 200], dtype=uint8)\n", + "negative of b:\t array([0, 156, 56], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([10, -1, 1], dtype=np.int8)\n", + "print(\"a:\\t\\t\", a)\n", + "print(\"negative of a:\\t\", -a)\n", + "\n", + "b = np.array([0, 100, 200], dtype=np.uint8)\n", + "print(\"\\nb:\\t\\t\", b)\n", + "print(\"negative of b:\\t\", -b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pos\n", + "\n", + "This function takes a single argument, and simply returns a copy of the array." + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-11T13:09:15.965662Z", + "start_time": "2019-10-11T13:09:15.945461Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t array([10, -1, 1], dtype=int8)\n", + "positive of a:\t array([10, -1, 1], dtype=int8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([10, -1, 1], dtype=np.int8)\n", + "print(\"a:\\t\\t\", a)\n", + "print(\"positive of a:\\t\", +a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Binary operators\n", + "\n", + "`ulab` implements the `+`, `-`, `*`, `/`, `**`, `<`, `>`, `<=`, `>=`, `==`, `!=`, `+=`, `-=`, `*=`, `/=`, `**=` binary operators that work element-wise. Broadcasting is available, meaning that the two operands do not even have to have the same shape. If the lengths along the respective axes are equal, or one of them is 1, or the axis is missing, the element-wise operation can still be carried out. \n", + "A thorough explanation of broadcasting can be found under https://numpy.org/doc/stable/user/basics.broadcasting.html. \n", + "\n", + "**WARNING**: note that relational operators (`<`, `>`, `<=`, `>=`, `==`, `!=`) should have the `ndarray` on their left hand side, when compared to scalars. This means that the following works" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:12:30.802935Z", + "start_time": "2021-01-13T06:12:30.786069Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([False, False, True], dtype=bool)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3])\n", + "print(a > 2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "while the equivalent statement, `2 < a`, will raise a `TypeError` exception:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:12:51.262197Z", + "start_time": "2021-01-13T06:12:51.244206Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Traceback (most recent call last):\n", + " File \"/dev/shm/micropython.py\", line 5, in \n", + "TypeError: unsupported types for __lt__: 'int', 'ndarray'\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3])\n", + "print(2 < a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**WARNING:** `circuitpython` users should use the `equal`, and `not_equal` operators instead of `==`, and `!=`. See the section on [array comparison](#Comparison-of-arrays) for details." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Upcasting\n", + "\n", + "Binary operations require special attention, because two arrays with different typecodes can be the operands of an operation, in which case it is not trivial, what the typecode of the result is. This decision on the result's typecode is called upcasting. Since the number of typecodes in `ulab` is significantly smaller than in `numpy`, we have to define new upcasting rules. Where possible, I followed `numpy`'s conventions. \n", + "\n", + "`ulab` observes the following upcasting rules:\n", + "\n", + "1. Operations on two `ndarray`s of the same `dtype` preserve their `dtype`, even when the results overflow.\n", + "\n", + "2. if either of the operands is a float, the result is automatically a float\n", + "\n", + "3. When one of the operands is a scalar, it will internally be turned into a single-element `ndarray` with the *smallest* possible `dtype`. Thus, e.g., if the scalar is 123, it will be converted into an array of `dtype` `uint8`, while -1000 will be converted into `int16`. An `mp_obj_float`, will always be promoted to `dtype` `float`. Similarly, if `ulab` supports complex arrays, the result of a binary operation involving a `complex` array is always complex. Other `micropython` types (e.g., lists, tuples, etc.) raise a `TypeError` exception. \n", + "\n", + "4. \n", + " \n", + "| left hand side | right hand side | ulab result | numpy result |\n", + "|----------------|-----------------|-------------|--------------|\n", + "|`uint8` |`int8` |`int16` |`int16` |\n", + "|`uint8` |`int16` |`int16` |`int16` |\n", + "|`uint8` |`uint16` |`uint16` |`uint16` |\n", + "|`int8` |`int16` |`int16` |`int16` | \n", + "|`int8` |`uint16` |`uint16` |`int32` |\n", + "|`uint16` |`int16` |`float` |`int32` |\n", + " \n", + "Note that the last two operations are promoted to `int32` in `numpy`.\n", + " \n", + "**WARNING:** Due to the lower number of available data types, the upcasting rules of `ulab` are slightly different to those of `numpy`. Watch out for this, when porting code!\n", + "\n", + "Upcasting can be seen in action in the following snippet:" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:13:23.026904Z", + "start_time": "2021-01-13T06:13:23.009315Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t array([1, 2, 3, 4], dtype=uint8)\n", + "b:\t array([1, 2, 3, 4], dtype=int8)\n", + "a+b:\t array([2, 4, 6, 8], dtype=int16)\n", + "\n", + "a:\t array([1, 2, 3, 4], dtype=uint8)\n", + "c:\t array([1.0, 2.0, 3.0, 4.0], dtype=float64)\n", + "a*c:\t array([1.0, 4.0, 9.0, 16.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4], dtype=np.uint8)\n", + "b = np.array([1, 2, 3, 4], dtype=np.int8)\n", + "print(\"a:\\t\", a)\n", + "print(\"b:\\t\", b)\n", + "print(\"a+b:\\t\", a+b)\n", + "\n", + "c = np.array([1, 2, 3, 4], dtype=np.float)\n", + "print(\"\\na:\\t\", a)\n", + "print(\"c:\\t\", c)\n", + "print(\"a*c:\\t\", a*c)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Benchmarks\n", + "\n", + "The following snippet compares the performance of binary operations to a possible implementation in python. For the time measurement, we will take the following snippet from the micropython manual:" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T06:39:52.225256Z", + "start_time": "2020-05-07T06:39:52.194691Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "code", + "execution_count": 490, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-19T13:23:45.432395Z", + "start_time": "2019-10-19T13:23:45.344021Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "python add:\n", + "execution time: 10051 us\n", + "\n", + "python multiply:\n", + "execution time: 14175 us\n", + "\n", + "ulab add:\n", + "execution time: 222 us\n", + "\n", + "ulab multiply:\n", + "execution time: 213 us\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "@timeit\n", + "def py_add(a, b):\n", + " return [a[i]+b[i] for i in range(1000)]\n", + "\n", + "@timeit\n", + "def py_multiply(a, b):\n", + " return [a[i]*b[i] for i in range(1000)]\n", + "\n", + "@timeit\n", + "def ulab_add(a, b):\n", + " return a + b\n", + "\n", + "@timeit\n", + "def ulab_multiply(a, b):\n", + " return a * b\n", + "\n", + "a = [0.0]*1000\n", + "b = range(1000)\n", + "\n", + "print('python add:')\n", + "py_add(a, b)\n", + "\n", + "print('\\npython multiply:')\n", + "py_multiply(a, b)\n", + "\n", + "a = np.linspace(0, 10, num=1000)\n", + "b = np.ones(1000)\n", + "\n", + "print('\\nulab add:')\n", + "ulab_add(a, b)\n", + "\n", + "print('\\nulab multiply:')\n", + "ulab_multiply(a, b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The python implementation above is not perfect, and certainly, there is much room for improvement. However, the factor of 50 difference in execution time is very spectacular. This is nothing but a consequence of the fact that the `ulab` functions run `C` code, with very little python overhead. The factor of 50 appears to be quite universal: the FFT routine obeys similar scaling (see [Speed of FFTs](#Speed-of-FFTs)), and this number came up with font rendering, too: [fast font rendering on graphical displays](https://forum.micropython.org/viewtopic.php?f=15&t=5815&p=33362&hilit=ufont#p33383)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Comparison operators\n", + "\n", + "The smaller than, greater than, smaller or equal, and greater or equal operators return a vector of Booleans indicating the positions (`True`), where the condition is satisfied. " + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-17T15:08:38.673585Z", + "start_time": "2020-10-17T15:08:38.659225Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([True, True, True, True, False, False, False, False], dtype=bool)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4, 5, 6, 7, 8], dtype=np.uint8)\n", + "print(a < 5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**WARNING**: at the moment, due to `micropython`'s implementation details, the `ndarray` must be on the left hand side of the relational operators.\n", + "\n", + "That is, while `a < 5` and `5 > a` have the same meaning, the following code will not work:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Traceback (most recent call last):\n", + " File \"/dev/shm/micropython.py\", line 5, in \n", + "TypeError: unsupported types for __gt__: 'int', 'ndarray'\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "import ulab as np\n", + "\n", + "a = np.array([1, 2, 3, 4, 5, 6, 7, 8], dtype=np.uint8)\n", + "print(5 > a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Iterating over arrays\n", + "\n", + "`ndarray`s are iterable, which means that their elements can also be accessed as can the elements of a list, tuple, etc. If the array is one-dimensional, the iterator returns scalars, otherwise a new reduced-dimensional *view* is created and returned." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:14:11.756254Z", + "start_time": "2021-01-13T06:14:11.742246Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t array([1, 2, 3, 4, 5], dtype=uint8)\n", + "element 0 in a: 1\n", + "element 1 in a: 2\n", + "element 2 in a: 3\n", + "element 3 in a: 4\n", + "element 4 in a: 5\n", + "\n", + "b:\t array([[0, 1, 2, 3, 4],\n", + " [10, 11, 12, 13, 14],\n", + " [20, 21, 22, 23, 24],\n", + " [30, 31, 32, 33, 34]], dtype=uint8)\n", + "element 0 in b: array([0, 1, 2, 3, 4], dtype=uint8)\n", + "element 1 in b: array([10, 11, 12, 13, 14], dtype=uint8)\n", + "element 2 in b: array([20, 21, 22, 23, 24], dtype=uint8)\n", + "element 3 in b: array([30, 31, 32, 33, 34], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4, 5], dtype=np.uint8)\n", + "b = np.array([range(5), range(10, 15, 1), range(20, 25, 1), range(30, 35, 1)], dtype=np.uint8)\n", + "\n", + "print(\"a:\\t\", a)\n", + "\n", + "for i, _a in enumerate(a):\n", + " print(\"element %d in a:\"%i, _a)\n", + " \n", + "print(\"\\nb:\\t\", b)\n", + "\n", + "for i, _b in enumerate(b):\n", + " print(\"element %d in b:\"%i, _b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Slicing and indexing\n", + "\n", + "\n", + "## Views vs. copies\n", + "\n", + "`numpy` has a very important concept called *views*, which is a powerful extension of `python`'s own notion of slicing. Slices are special python objects of the form\n", + "\n", + "```python\n", + "slice = start:end:stop\n", + "```\n", + "\n", + "where `start`, `end`, and `stop` are (not necessarily non-negative) integers. Not all of these three numbers must be specified in an index, in fact, all three of them can be missing. The interpreter takes care of filling in the missing values. (Note that slices cannot be defined in this way, only there, where an index is expected.) For a good explanation on how slices work in python, you can read the stackoverflow question https://stackoverflow.com/questions/509211/understanding-slice-notation.\n", + "\n", + "In order to see what slicing does, let us take the string `a = '012345679'`! We can extract every second character by creating the slice `::2`, which is equivalent to `0:len(a):2`, i.e., increments the character pointer by 2 starting from 0, and traversing the string up to the very end." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-12T05:26:17.758735Z", + "start_time": "2020-10-12T05:26:17.748487Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'02468'" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "string = '0123456789'\n", + "string[::2]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we can do the same with numerical arrays." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-12T05:25:49.352435Z", + "start_time": "2020-10-12T05:25:49.339452Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8)\n", + "a[::2]:\t array([0, 2, 4, 6, 8], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(10), dtype=np.uint8)\n", + "print('a:\\t', a)\n", + "\n", + "print('a[::2]:\\t', a[::2])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This looks similar to `string` above, but there is a very important difference that is not so obvious. Namely, `string[::2]` produces a partial copy of `string`, while `a[::2]` only produces a *view* of `a`. What this means is that `a`, and `a[::2]` share their data, and the only difference between the two is, how the data are read out. In other words, internally, `a[::2]` has the same data pointer as `a`. We can easily convince ourselves that this is indeed the case by calling the [ndinfo](#The_ndinfo_function) function: the *data pointer* entry is the same in the two printouts." + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-16T18:43:07.480791Z", + "start_time": "2020-10-16T18:43:07.471473Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8) \n", + "\n", + "class: ndarray\n", + "shape: (10,)\n", + "strides: (1,)\n", + "itemsize: 1\n", + "data pointer: 0x7ff6c6193220\n", + "type: uint8\n", + "\n", + "====================\n", + "a[::2]: array([0, 2, 4, 6, 8], dtype=uint8) \n", + "\n", + "class: ndarray\n", + "shape: (5,)\n", + "strides: (2,)\n", + "itemsize: 1\n", + "data pointer: 0x7ff6c6193220\n", + "type: uint8\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(10), dtype=np.uint8)\n", + "print('a: ', a, '\\n')\n", + "np.ndinfo(a)\n", + "print('\\n' + '='*20)\n", + "print('a[::2]: ', a[::2], '\\n')\n", + "np.ndinfo(a[::2])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you are still a bit confused about the meaning of *views*, the section [Slicing and assigning to slices](#Slicing-and-assigning-to-slices) should clarify the issue." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Indexing\n", + "\n", + "The simplest form of indexing is specifying a single integer between the square brackets as in " + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-12T18:31:45.485584Z", + "start_time": "2020-10-12T18:31:45.464551Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8)\n", + "the first, and last element of a:\n", + " 0 9\n", + "the second, and last but one element of a:\n", + " 1 8\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(10), dtype=np.uint8)\n", + "print(\"a: \", a)\n", + "print(\"the first, and last element of a:\\n\", a[0], a[-1])\n", + "print(\"the second, and last but one element of a:\\n\", a[1], a[-2])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Indexing can be applied to higher-dimensional tensors, too. When the length of the indexing sequences is smaller than the number of dimensions, a new *view* is returned, otherwise, we get a single number." + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-12T18:26:12.783883Z", + "start_time": "2020-10-12T18:26:12.770180Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([[0, 1, 2],\n", + "\t[3, 4, 5],\n", + "\t[6, 7, 8]], dtype=uint8)\n", + "a[0]:\n", + " array([[0, 1, 2]], dtype=uint8)\n", + "a[1,1]: 4\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(9), dtype=np.uint8).reshape((3, 3))\n", + "print(\"a:\\n\", a)\n", + "print(\"a[0]:\\n\", a[0])\n", + "print(\"a[1,1]: \", a[1,1])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Indices can also be a list of Booleans. By using a Boolean list, we can select those elements of an array that satisfy a specific condition. At the moment, such indexing is defined for row vectors only; when the rank of the tensor is higher than 1, the function raises a `NotImplementedError` exception, though this will be rectified in a future version of `ulab`." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-12T17:34:34.105614Z", + "start_time": "2020-10-12T17:34:34.094017Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float)\n", + "a < 5:\t array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(9), dtype=np.float)\n", + "print(\"a:\\t\", a)\n", + "print(\"a < 5:\\t\", a[a < 5])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Indexing with Boolean arrays can take more complicated expressions. This is a very concise way of comparing two vectors, e.g.:" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-12T18:03:38.846377Z", + "start_time": "2020-10-12T18:03:38.826689Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8)\n", + "\n", + "a**2:\t array([0, 1, 4, 9, 16, 25, 36, 49, 64], dtype=uint16)\n", + "\n", + "b:\t array([4, 4, 4, 3, 3, 3, 13, 13, 13], dtype=uint8)\n", + "\n", + "100*sin(b):\t array([-75.68024953079282, -75.68024953079282, -75.68024953079282, 14.11200080598672, 14.11200080598672, 14.11200080598672, 42.01670368266409, 42.01670368266409, 42.01670368266409], dtype=float)\n", + "\n", + "a[a*a > np.sin(b)*100.0]:\t array([0, 1, 2, 4, 5, 7, 8], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(9), dtype=np.uint8)\n", + "b = np.array([4, 4, 4, 3, 3, 3, 13, 13, 13], dtype=np.uint8)\n", + "print(\"a:\\t\", a)\n", + "print(\"\\na**2:\\t\", a*a)\n", + "print(\"\\nb:\\t\", b)\n", + "print(\"\\n100*sin(b):\\t\", np.sin(b)*100.0)\n", + "print(\"\\na[a*a > np.sin(b)*100.0]:\\t\", a[a*a > np.sin(b)*100.0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Boolean indices can also be used in assignments, if the array is one-dimensional. The following example replaces the data in an array, wherever some condition is fulfilled." + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-13T16:14:21.055356Z", + "start_time": "2020-10-13T16:14:21.035329Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([0, 1, 2], dtype=uint8)\n", + "array([123, 123, 123, 3, 4, 5, 6, 7, 8], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(9), dtype=np.uint8)\n", + "b = np.array(range(9)) + 12\n", + "\n", + "print(a[b < 15])\n", + "\n", + "a[b < 15] = 123\n", + "print(a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "On the right hand side of the assignment we can even have another array." + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-13T16:14:10.054210Z", + "start_time": "2020-10-13T16:14:10.039523Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([0, 1, 2], dtype=uint8) array([12.0, 13.0, 14.0], dtype=float)\n", + "array([12, 13, 14, 3, 4, 5, 6, 7, 8], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(9), dtype=np.uint8)\n", + "b = np.array(range(9)) + 12\n", + "\n", + "print(a[b < 15], b[b < 15])\n", + "\n", + "a[b < 15] = b[b < 15]\n", + "print(a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Slicing and assigning to slices\n", + "\n", + "You can also generate sub-arrays by specifying slices as the index of an array. Slices are special python objects of the form " + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-12T17:38:15.975404Z", + "start_time": "2020-10-12T17:38:15.955070Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([[1, 2, 3],\n", + "\t[4, 5, 6],\n", + "\t[7, 8, 9]], dtype=uint8)\n", + "\n", + "a[0]:\n", + " array([[1, 2, 3]], dtype=uint8)\n", + "\n", + "a[0,:2]:\n", + " array([[1, 2]], dtype=uint8)\n", + "\n", + "a[:,0]:\n", + " array([[1],\n", + "\t[4],\n", + "\t[7]], dtype=uint8)\n", + "\n", + "a[-1]:\n", + " array([[7, 8, 9]], dtype=uint8)\n", + "\n", + "a[-1:-3:-1]:\n", + " array([[7, 8, 9],\n", + "\t[4, 5, 6]], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.uint8)\n", + "print('a:\\n', a)\n", + "\n", + "# the first row\n", + "print('\\na[0]:\\n', a[0])\n", + "\n", + "# the first two elements of the first row\n", + "print('\\na[0,:2]:\\n', a[0,:2])\n", + "\n", + "# the zeroth element in each row (also known as the zeroth column)\n", + "print('\\na[:,0]:\\n', a[:,0])\n", + "\n", + "# the last row\n", + "print('\\na[-1]:\\n', a[-1])\n", + "\n", + "# the last two rows backwards\n", + "print('\\na[-1:-3:-1]:\\n', a[-1:-3:-1])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Assignment to slices can be done for the whole slice, per row, and per column. A couple of examples should make these statements clearer:" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-12T17:40:24.031254Z", + "start_time": "2020-10-12T17:40:24.011816Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([[0, 0, 0],\n", + "\t[0, 0, 0],\n", + "\t[0, 0, 0]], dtype=uint8)\n", + "\n", + "a[0] = 1\n", + " array([[1, 1, 1],\n", + "\t[0, 0, 0],\n", + "\t[0, 0, 0]], dtype=uint8)\n", + "\n", + "a[:,0]:\n", + " array([[0, 0, 3],\n", + "\t[0, 0, 3],\n", + "\t[0, 0, 3]], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.zeros((3, 3), dtype=np.uint8)\n", + "print('a:\\n', a)\n", + "\n", + "# assigning to the whole row\n", + "a[0] = 1\n", + "print('\\na[0] = 1\\n', a)\n", + "\n", + "a = np.zeros((3, 3), dtype=np.uint8)\n", + "\n", + "# assigning to a column\n", + "a[:,2] = 3.0\n", + "print('\\na[:,0]:\\n', a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, you should notice that we re-set the array `a` after the first assignment. Do you care to see what happens, if we do not do that? Well, here are the results:" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-12T17:44:09.180623Z", + "start_time": "2020-10-12T17:44:09.161578Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([[1, 1, 3],\n", + "\t[0, 0, 3],\n", + "\t[0, 0, 3]], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.zeros((3, 3), dtype=np.uint8)\n", + "b = a[:,:]\n", + "# assign 1 to the first row\n", + "b[0] = 1\n", + "\n", + "# assigning to the last column\n", + "b[:,2] = 3\n", + "print('a: ', a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that both assignments involved `b`, and not `a`, yet, when we print out `a`, its entries are updated. This proves our earlier statement about the behaviour of *views*: in the statement `b = a[:,:]` we simply created a *view* of `a`, and not a *deep* copy of it, meaning that whenever we modify `b`, we actually modify `a`, because the underlying data container of `a` and `b` are shared between the two object. Having a single data container for two seemingly different objects provides an extremely powerful way of manipulating sub-sets of numerical data." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you want to work on a *copy* of your data, you can use the `.copy` method of the `ndarray`. The following snippet should drive the point home:" + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-17T13:06:20.223171Z", + "start_time": "2020-10-17T13:06:20.206422Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "class: ndarray\n", + "shape: (3, 3)\n", + "strides: (3, 1)\n", + "itemsize: 1\n", + "data pointer: 0x7ff737ea3220\n", + "type: uint8\n", + "\n", + "class: ndarray\n", + "shape: (3, 3)\n", + "strides: (3, 1)\n", + "itemsize: 1\n", + "data pointer: 0x7ff737ea3340\n", + "type: uint8\n", + "\n", + "a: array([[0, 0, 0],\n", + "\t[0, 0, 0],\n", + "\t[0, 0, 0]], dtype=uint8)\n", + "====================\n", + "b: array([[1, 1, 1],\n", + "\t[0, 0, 0],\n", + "\t[0, 0, 0]], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.zeros((3, 3), dtype=np.uint8)\n", + "b = a.copy()\n", + "\n", + "# get the address of the underlying data pointer\n", + "\n", + "np.ndinfo(a)\n", + "print()\n", + "np.ndinfo(b)\n", + "\n", + "# assign 1 to the first row of b, and do not touch a\n", + "b[0] = 1\n", + "\n", + "print()\n", + "print('a: ', a)\n", + "print('='*20)\n", + "print('b: ', b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `.copy` method can also be applied to views: below, `a[0]` is a *view* of `a`, out of which we create a *deep copy* called `b`. This is a row vector now. We can then do whatever we want to with `b`, and that leaves `a` unchanged." + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-17T13:00:06.217232Z", + "start_time": "2020-10-17T13:00:06.207417Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "b: array([0, 0, 0], dtype=uint8)\n", + "====================\n", + "a: array([[0, 0, 0],\n", + "\t[0, 0, 0],\n", + "\t[0, 0, 0]], dtype=uint8)\n", + "====================\n", + "b: array([1, 0, 0], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.zeros((3, 3), dtype=np.uint8)\n", + "b = a[0].copy()\n", + "print('b: ', b)\n", + "print('='*20)\n", + "# assign 1 to the first entry of b, and do not touch a\n", + "b[0] = 1\n", + "print('a: ', a)\n", + "print('='*20)\n", + "print('b: ', b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The fact that the underlying data of a view is the same as that of the original array has another important consequence, namely, that the creation of a view is cheap. Both in terms of RAM, and execution time. A view is really nothing but a short header with a data array that already exists, and is filled up. Hence, creating the view requires only the creation of its header. This operation is fast, and uses virtually no RAM." + ] + } + ], + "metadata": { + "interpreter": { + "hash": "ce9a02f9f7db620716422019cafa4bc1786ca85daa298b819f6da075e7993842" + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/ulab-numerical.ipynb b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-numerical.ipynb new file mode 100644 index 00000000..be549545 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-numerical.ipynb @@ -0,0 +1,1160 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-03T19:50:50.150235Z", + "start_time": "2020-11-03T19:50:48.888079Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Notebook magic" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-07T19:16:29.118001Z", + "start_time": "2022-01-07T19:16:29.114692Z" + } + }, + "outputs": [], + "source": [ + "from IPython.core.magic import Magics, magics_class, line_cell_magic\n", + "from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n", + "from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n", + "import subprocess\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-07T19:16:37.453883Z", + "start_time": "2022-01-07T19:16:37.422478Z" + } + }, + "outputs": [], + "source": [ + "@magics_class\n", + "class PyboardMagic(Magics):\n", + " @cell_magic\n", + " @magic_arguments()\n", + " @argument('-skip')\n", + " @argument('-unix')\n", + " @argument('-pyboard')\n", + " @argument('-file')\n", + " @argument('-data')\n", + " @argument('-time')\n", + " @argument('-memory')\n", + " def micropython(self, line='', cell=None):\n", + " args = parse_argstring(self.micropython, line)\n", + " if args.skip: # doesn't care about the cell's content\n", + " print('skipped execution')\n", + " return None # do not parse the rest\n", + " if args.unix: # tests the code on the unix port. Note that this works on unix only\n", + " with open('/dev/shm/micropython.py', 'w') as fout:\n", + " fout.write(cell)\n", + " proc = subprocess.Popen([\"../micropython/ports/unix/micropython-2\", \"/dev/shm/micropython.py\"], \n", + " stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " print(proc.stdout.read().decode(\"utf-8\"))\n", + " print(proc.stderr.read().decode(\"utf-8\"))\n", + " return None\n", + " if args.file: # can be used to copy the cell content onto the pyboard's flash\n", + " spaces = \" \"\n", + " try:\n", + " with open(args.file, 'w') as fout:\n", + " fout.write(cell.replace('\\t', spaces))\n", + " printf('written cell to {}'.format(args.file))\n", + " except:\n", + " print('Failed to write to disc!')\n", + " return None # do not parse the rest\n", + " if args.data: # can be used to load data from the pyboard directly into kernel space\n", + " message = pyb.exec(cell)\n", + " if len(message) == 0:\n", + " print('pyboard >>>')\n", + " else:\n", + " print(message.decode('utf-8'))\n", + " # register new variable in user namespace\n", + " self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n", + " \n", + " if args.time: # measures the time of executions\n", + " pyb.exec('import utime')\n", + " message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n", + " \"\\nprint('execution time: {:d} us'.format(delta))\")\n", + " print(message.decode('utf-8'))\n", + " \n", + " if args.memory: # prints out memory information \n", + " message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n", + " print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n", + " message = pyb.exec(cell)\n", + " print(\">>> \", message.decode('utf-8'))\n", + " message = pyb.exec('print(mem_info())')\n", + " print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n", + "\n", + " if args.pyboard:\n", + " message = pyb.exec(cell)\n", + " print(message.decode('utf-8'))\n", + "\n", + "ip = get_ipython()\n", + "ip.register_magics(PyboardMagic)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pyboard" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:35.126401Z", + "start_time": "2020-05-07T07:35:35.105824Z" + } + }, + "outputs": [], + "source": [ + "import pyboard\n", + "pyb = pyboard.Pyboard('/dev/ttyACM0')\n", + "pyb.enter_raw_repl()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:11:18.145548Z", + "start_time": "2020-05-19T19:11:18.137468Z" + } + }, + "outputs": [], + "source": [ + "pyb.exit_raw_repl()\n", + "pyb.close()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:38.725924Z", + "start_time": "2020-05-07T07:35:38.645488Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "import ulab as np\n", + "\n", + "def timeit(n=1000):\n", + " def wrapper(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " run_times = np.zeros(n, dtype=np.uint16)\n", + " for i in range(n):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n", + " print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n", + " print('\\tbest: %d us'%np.min(run_times))\n", + " print('\\tworst: %d us'%np.max(run_times))\n", + " print('\\taverage: %d us'%np.mean(run_times))\n", + " print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n", + " return result\n", + " return new_func\n", + " return wrapper\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__END_OF_DEFS__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Numerical\n", + "\n", + "Function in this section can be used for calculating statistical properties, or manipulating the arrangement of array elements." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## min, argmin, max, argmax\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.min.html\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmax.html\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.max.html\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmax.html\n", + "\n", + "**WARNING:** Difference to `numpy`: the `out` keyword argument is not implemented.\n", + "\n", + "These functions follow the same pattern, and work with generic iterables, and `ndarray`s. `min`, and `max` return the minimum or maximum of a sequence. If the input array is two-dimensional, the `axis` keyword argument can be supplied, in which case the minimum/maximum along the given axis will be returned. If `axis=None` (this is also the default value), the minimum/maximum of the flattened array will be determined.\n", + "\n", + "`argmin/argmax` return the position (index) of the minimum/maximum in the sequence." + ] + }, + { + "cell_type": "code", + "execution_count": 108, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-17T21:26:22.507996Z", + "start_time": "2020-10-17T21:26:22.492543Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([1.0, 2.0, 3.0], dtype=float)\n", + "array([], dtype=float)\n", + "[] 0\n", + "array([1.0, 2.0, 3.0], dtype=float)\n", + "array([], dtype=float)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "import ulab as np\n", + "\n", + "a = np.array([1, 2, 3])\n", + "print(a)\n", + "print(a[-1:-1:-3])\n", + "try:\n", + " sa = list(a[-1:-1:-3])\n", + " la = len(sa)\n", + "except IndexError as e:\n", + " sa = str(e)\n", + " la = -1\n", + " \n", + "print(sa, la)\n", + "\n", + "a[-1:-1:-3] = np.ones(0)\n", + "print(a)\n", + "\n", + "b = np.ones(0) + 1\n", + "print(b)\n", + "# print('b', b.shape())" + ] + }, + { + "cell_type": "code", + "execution_count": 122, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-17T21:54:49.123748Z", + "start_time": "2020-10-17T21:54:49.093819Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0, 1, -3array([], dtype=float)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "import ulab as np\n", + "a = np.array([1, 2, 3])\n", + "print(a[0:1:-3])" + ] + }, + { + "cell_type": "code", + "execution_count": 127, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-17T21:57:01.482277Z", + "start_time": "2020-10-17T21:57:01.477362Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[0]" + ] + }, + "execution_count": 127, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "l = list(range(13))\n", + "\n", + "l[0:10:113]" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-17T20:59:58.285134Z", + "start_time": "2020-10-17T20:59:58.263605Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(0,)" + ] + }, + "execution_count": 81, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a = np.array([1, 2, 3])\n", + "np.ones(0, dtype=uint8) / np.zeros(0, dtype=uint16)\n", + "np.ones(0).shape" + ] + }, + { + "cell_type": "code", + "execution_count": 375, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-18T13:08:28.113525Z", + "start_time": "2019-10-18T13:08:28.093518Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([1.0, 2.0, 0.0, 1.0, 10.0], dtype=float)\n", + "min of a: 0.0\n", + "argmin of a: 2\n", + "\n", + "b:\n", + " array([[1.0, 2.0, 0.0],\n", + "\t [1.0, 10.0, -1.0]], dtype=float)\n", + "min of b (flattened): -1.0\n", + "min of b (axis=0): array([1.0, 2.0, -1.0], dtype=float)\n", + "min of b (axis=1): array([0.0, -1.0], dtype=float)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "import ulab as np\n", + "from ulab import numerical\n", + "\n", + "a = np.array([1, 2, 0, 1, 10])\n", + "print('a:', a)\n", + "print('min of a:', numerical.min(a))\n", + "print('argmin of a:', numerical.argmin(a))\n", + "\n", + "b = np.array([[1, 2, 0], [1, 10, -1]])\n", + "print('\\nb:\\n', b)\n", + "print('min of b (flattened):', numerical.min(b))\n", + "print('min of b (axis=0):', numerical.min(b, axis=0))\n", + "print('min of b (axis=1):', numerical.min(b, axis=1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## sum, std, mean\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.std.html\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.mean.html\n", + "\n", + "These three functions follow the same pattern: if the axis keyword is not specified, it assumes the default value of `None`, and returns the result of the computation for the flattened array. Otherwise, the calculation is along the given axis." + ] + }, + { + "cell_type": "code", + "execution_count": 527, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-20T06:51:58.845076Z", + "start_time": "2019-10-20T06:51:58.798730Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: \n", + " array([[1.0, 2.0, 3.0],\n", + "\t [4.0, 5.0, 6.0],\n", + "\t [7.0, 8.0, 9.0]], dtype=float)\n", + "sum, flat array: 45.0\n", + "mean, horizontal: array([2.0, 5.0, 8.0], dtype=float)\n", + "std, vertical: array([2.44949, 2.44949, 2.44949], dtype=float)\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import ulab as np\n", + "from ulab import numerical\n", + "\n", + "a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])\n", + "print('a: \\n', a)\n", + "\n", + "print('sum, flat array: ', numerical.sum(a))\n", + "\n", + "print('mean, horizontal: ', numerical.mean(a, axis=1))\n", + "\n", + "print('std, vertical: ', numerical.std(a, axis=0))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## roll\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html\n", + "\n", + "The roll function shifts the content of a vector by the positions given as the second argument. If the `axis` keyword is supplied, the shift is applied to the given axis." + ] + }, + { + "cell_type": "code", + "execution_count": 229, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-11T19:39:47.459395Z", + "start_time": "2019-10-11T19:39:47.443691Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t\t array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float)\n", + "a rolled to the left:\t array([3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 1.0, 2.0], dtype=float)\n", + "a rolled to the right:\t array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "import ulab as np\n", + "from ulab import numerical\n", + "\n", + "a = np.array([1, 2, 3, 4, 5, 6, 7, 8])\n", + "print(\"a:\\t\\t\\t\", a)\n", + "\n", + "numerical.roll(a, 2)\n", + "print(\"a rolled to the left:\\t\", a)\n", + "\n", + "# this should be the original vector\n", + "numerical.roll(a, -2)\n", + "print(\"a rolled to the right:\\t\", a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Rolling works with matrices, too. If the `axis` keyword is 0, the matrix is rolled along its vertical axis, otherwise, horizontally. \n", + "\n", + "Horizontal rolls are faster, because they require fewer steps, and larger memory chunks are copied, however, they also require more RAM: basically the whole row must be stored internally. Most expensive are the `None` keyword values, because with `axis = None`, the array is flattened first, hence the row's length is the size of the whole matrix.\n", + "\n", + "Vertical rolls require two internal copies of single columns. " + ] + }, + { + "cell_type": "code", + "execution_count": 268, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-15T17:46:20.051069Z", + "start_time": "2019-10-15T17:46:20.033205Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([[1.0, 2.0, 3.0, 4.0],\n", + "\t [5.0, 6.0, 7.0, 8.0]], dtype=float)\n", + "\n", + "a rolled to the left:\n", + " array([[3.0, 4.0, 5.0, 6.0],\n", + "\t [7.0, 8.0, 1.0, 2.0]], dtype=float)\n", + "\n", + "a rolled up:\n", + " array([[6.0, 3.0, 4.0, 5.0],\n", + "\t [2.0, 7.0, 8.0, 1.0]], dtype=float)\n", + "\n", + "a rolled with None:\n", + " array([[3.0, 4.0, 5.0, 2.0],\n", + "\t [7.0, 8.0, 1.0, 6.0]], dtype=float)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "import ulab as np\n", + "from ulab import numerical\n", + "\n", + "a = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])\n", + "print(\"a:\\n\", a)\n", + "\n", + "numerical.roll(a, 2)\n", + "print(\"\\na rolled to the left:\\n\", a)\n", + "\n", + "numerical.roll(a, -1, axis=1)\n", + "print(\"\\na rolled up:\\n\", a)\n", + "\n", + "numerical.roll(a, 1, axis=None)\n", + "print(\"\\na rolled with None:\\n\", a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Simple running weighted average\n", + "\n", + "As a demonstration of the conciseness of `ulab/numpy` operations, we will calculate an exponentially weighted running average of a measurement vector in just a couple of lines. I chose this particular example, because I think that this can indeed be used in real-life applications." + ] + }, + { + "cell_type": "code", + "execution_count": 230, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-11T20:03:00.713235Z", + "start_time": "2019-10-11T20:03:00.696932Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([0.01165623031556606, 0.03168492019176483, 0.08612854033708572, 0.234121635556221, 0.6364086270332336], dtype=float)\n", + "0.2545634508132935\n", + "array([0.0, 0.0, 0.0, 0.0, 2.0], dtype=float)\n", + "0.3482121050357819\n", + "array([0.0, 0.0, 0.0, 2.0, 2.0], dtype=float)\n", + "0.3826635211706161\n", + "array([0.0, 0.0, 2.0, 2.0, 2.0], dtype=float)\n", + "0.3953374892473221\n", + "array([0.0, 2.0, 2.0, 2.0, 2.0], dtype=float)\n", + "0.3999999813735485\n", + "array([2.0, 2.0, 2.0, 2.0, 2.0], dtype=float)\n", + "0.3999999813735485\n", + "array([2.0, 2.0, 2.0, 2.0, 2.0], dtype=float)\n", + "0.3999999813735485\n", + "array([2.0, 2.0, 2.0, 2.0, 2.0], dtype=float)\n", + "0.3999999813735485\n", + "array([2.0, 2.0, 2.0, 2.0, 2.0], dtype=float)\n", + "0.3999999813735485\n", + "array([2.0, 2.0, 2.0, 2.0, 2.0], dtype=float)\n", + "0.3999999813735485\n", + "array([2.0, 2.0, 2.0, 2.0, 2.0], dtype=float)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "import ulab as np\n", + "from ulab import numerical\n", + "from ulab import vector\n", + "\n", + "def dummy_adc():\n", + " # dummy adc function, so that the results are reproducible\n", + " return 2\n", + " \n", + "n = 10\n", + "# These are the normalised weights; the last entry is the most dominant\n", + "weight = vector.exp([1, 2, 3, 4, 5])\n", + "weight = weight/numerical.sum(weight)\n", + "\n", + "print(weight)\n", + "# initial array of samples\n", + "samples = np.array([0]*n)\n", + "\n", + "for i in range(n):\n", + " # a new datum is inserted on the right hand side. This simply overwrites whatever was in the last slot\n", + " samples[-1] = dummy_adc()\n", + " print(numerical.mean(samples[-5:]*weight))\n", + " print(samples[-5:])\n", + " # the data are shifted by one position to the left\n", + " numerical.roll(samples, 1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## flip\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.flip.html\n", + "\n", + "The `flip` function takes one positional, an `ndarray`, and one keyword argument, `axis = None`, and reverses the order of elements along the given axis. If the keyword argument is `None`, the matrix' entries are flipped along all axes. `flip` returns a new copy of the array." + ] + }, + { + "cell_type": "code", + "execution_count": 275, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-16T06:35:52.163725Z", + "start_time": "2019-10-16T06:35:52.149231Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: \t array([1.0, 2.0, 3.0, 4.0, 5.0], dtype=float)\n", + "a flipped:\t array([5.0, 4.0, 3.0, 2.0, 1.0], dtype=float)\n", + "\n", + "a flipped horizontally\n", + " array([[3, 2, 1],\n", + "\t [6, 5, 4],\n", + "\t [9, 8, 7]], dtype=uint8)\n", + "\n", + "a flipped vertically\n", + " array([[7, 8, 9],\n", + "\t [4, 5, 6],\n", + "\t [1, 2, 3]], dtype=uint8)\n", + "\n", + "a flipped horizontally+vertically\n", + " array([[9, 8, 7],\n", + "\t [6, 5, 4],\n", + "\t [3, 2, 1]], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "import ulab as np\n", + "from ulab import numerical\n", + "\n", + "a = np.array([1, 2, 3, 4, 5])\n", + "print(\"a: \\t\", a)\n", + "print(\"a flipped:\\t\", np.flip(a))\n", + "\n", + "a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.uint8)\n", + "print(\"\\na flipped horizontally\\n\", numerical.flip(a, axis=1))\n", + "print(\"\\na flipped vertically\\n\", numerical.flip(a, axis=0))\n", + "print(\"\\na flipped horizontally+vertically\\n\", numerical.flip(a))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## diff\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.diff.html\n", + "\n", + "The `diff` function returns the numerical derivative of the forward scheme, or more accurately, the differences of an `ndarray` along a given axis. The order of derivative can be stipulated with the `n` keyword argument, which should be between 0, and 9. Default is 1. If higher order derivatives are required, they can be gotten by repeated calls to the function. The `axis` keyword argument should be -1 (last axis, in `ulab` equivalent to the second axis, and this also happens to be the default value), 0, or 1. \n", + "\n", + "Beyond the output array, the function requires only a couple of bytes of extra RAM for the differentiation stencil. (The stencil is an `int8` array, one byte longer than `n`. This also explains, why the highest order is 9: the coefficients of a ninth-order stencil all fit in signed bytes, while 10 would require `int16`.) Note that as usual in numerical differentiation (and also in `numpy`), the length of the respective axis will be reduced by `n` after the operation. If `n` is larger than, or equal to the length of the axis, an empty array will be returned.\n", + "\n", + "**WARNING**: the `diff` function does not implement the `prepend` and `append` keywords that can be found in `numpy`. " + ] + }, + { + "cell_type": "code", + "execution_count": 169, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-31T11:51:02.854338Z", + "start_time": "2019-10-31T11:51:02.838000Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8)\n", + "\n", + "first derivative:\n", + " array([1, 1, 1, 1, 1, 1, 1, 1], dtype=uint8)\n", + "\n", + "second derivative:\n", + " array([0, 0, 0, 0, 0, 0, 0], dtype=uint8)\n", + "\n", + "c:\n", + " array([[1.0, 2.0, 3.0, 4.0],\n", + "\t [4.0, 3.0, 2.0, 1.0],\n", + "\t [1.0, 4.0, 9.0, 16.0],\n", + "\t [0.0, 0.0, 0.0, 0.0]], dtype=float)\n", + "\n", + "first derivative, first axis:\n", + " array([[3.0, 1.0, -1.0, -3.0],\n", + "\t [-3.0, 1.0, 7.0, 15.0],\n", + "\t [-1.0, -4.0, -9.0, -16.0]], dtype=float)\n", + "\n", + "first derivative, second axis:\n", + " array([[1.0, 1.0, 1.0],\n", + "\t [-1.0, -1.0, -1.0],\n", + "\t [3.0, 5.0, 7.0],\n", + "\t [0.0, 0.0, 0.0]], dtype=float)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "import ulab as np\n", + "from ulab import numerical\n", + "\n", + "a = np.array(range(9), dtype=np.uint8)\n", + "print('a:\\n', a)\n", + "\n", + "print('\\nfirst derivative:\\n', numerical.diff(a, n=1))\n", + "print('\\nsecond derivative:\\n', numerical.diff(a, n=2))\n", + "\n", + "c = np.array([[1, 2, 3, 4], [4, 3, 2, 1], [1, 4, 9, 16], [0, 0, 0, 0]])\n", + "print('\\nc:\\n', c)\n", + "print('\\nfirst derivative, first axis:\\n', numerical.diff(c, axis=0))\n", + "print('\\nfirst derivative, second axis:\\n', numerical.diff(c, axis=1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## median\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.median.html\n", + "\n", + "The function computes the median along the specified axis, and returns the median of the array elements. If the `axis` keyword argument is `None`, the arrays is flattened first. The `dtype` of the results is always float." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-03T19:54:38.047790Z", + "start_time": "2020-11-03T19:54:38.029264Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([[0, 1, 2, 3],\n", + " [4, 5, 6, 7],\n", + " [8, 9, 10, 11]], dtype=int8)\n", + "\n", + "median of the flattened array: 5.5\n", + "\n", + "median along the vertical axis: array([4.0, 5.0, 6.0, 7.0], dtype=float)\n", + "\n", + "median along the horizontal axis: array([1.5, 5.5, 9.5], dtype=float)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "import ulab as np\n", + "\n", + "a = np.array(range(12), dtype=np.int8).reshape((3, 4))\n", + "print('a:\\n', a)\n", + "print('\\nmedian of the flattened array: ', np.median(a))\n", + "print('\\nmedian along the vertical axis: ', np.median(a, axis=0))\n", + "print('\\nmedian along the horizontal axis: ', np.median(a, axis=1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## sort\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.sort.html\n", + "\n", + "The sort function takes an ndarray, and sorts its elements in ascending order along the specified axis using a heap sort algorithm. As opposed to the `.sort()` method discussed earlier, this function creates a copy of its input before sorting, and at the end, returns this copy. Sorting takes place in place, without auxiliary storage. The `axis` keyword argument takes on the possible values of -1 (the last axis, in `ulab` equivalent to the second axis, and this also happens to be the default value), 0, 1, or `None`. The first three cases are identical to those in [diff](#diff), while the last one flattens the array before sorting. \n", + "\n", + "If descending order is required, the result can simply be `flip`ped, see [flip](#flip).\n", + "\n", + "**WARNING:** `numpy` defines the `kind`, and `order` keyword arguments that are not implemented here. The function in `ulab` always uses heap sort, and since `ulab` does not have the concept of data fields, the `order` keyword argument would have no meaning." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "ExecuteTime": { + "end_time": "2019-11-05T16:06:27.536193Z", + "start_time": "2019-11-05T16:06:27.521792Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "a:\n", + " array([[1.0, 12.0, 3.0, 0.0],\n", + "\t [5.0, 3.0, 4.0, 1.0],\n", + "\t [9.0, 11.0, 1.0, 8.0],\n", + "\t [7.0, 10.0, 0.0, 1.0]], dtype=float)\n", + "\n", + "a sorted along vertical axis:\n", + " array([[1.0, 3.0, 0.0, 0.0],\n", + "\t [5.0, 10.0, 1.0, 1.0],\n", + "\t [7.0, 11.0, 3.0, 1.0],\n", + "\t [9.0, 12.0, 4.0, 8.0]], dtype=float)\n", + "\n", + "a sorted along horizontal axis:\n", + " array([[0.0, 1.0, 3.0, 12.0],\n", + "\t [1.0, 3.0, 4.0, 5.0],\n", + "\t [1.0, 8.0, 9.0, 11.0],\n", + "\t [0.0, 1.0, 7.0, 10.0]], dtype=float)\n", + "\n", + "flattened a sorted:\n", + " array([0.0, 0.0, 1.0, ..., 10.0, 11.0, 12.0], dtype=float)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "import ulab as np\n", + "from ulab import numerical\n", + "\n", + "a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.float)\n", + "print('\\na:\\n', a)\n", + "b = numerical.sort(a, axis=0)\n", + "print('\\na sorted along vertical axis:\\n', b)\n", + "\n", + "c = numerical.sort(a, axis=1)\n", + "print('\\na sorted along horizontal axis:\\n', c)\n", + "\n", + "c = numerical.sort(a, axis=None)\n", + "print('\\nflattened a sorted:\\n', c)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Heap sort requires $\\sim N\\log N$ operations, and notably, the worst case costs only 20% more time than the average. In order to get an order-of-magnitude estimate, we will take the sine of 1000 uniformly spaced numbers between 0, and two pi, and sort them:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import ulab as np\n", + "from ulab import vector\n", + "from ulab import numerical\n", + "\n", + "@timeit\n", + "def sort_time(array):\n", + " return numerical.sort(array)\n", + "\n", + "b = vector.sin(np.linspace(0, 6.28, num=1000))\n", + "print('b: ', b)\n", + "sort_time(b)\n", + "print('\\nb sorted:\\n', b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## argsort\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.argsort.html\n", + "\n", + "Similarly to [sort](#sort), `argsort` takes a positional, and a keyword argument, and returns an unsigned short index array of type `ndarray` with the same dimensions as the input, or, if `axis=None`, as a row vector with length equal to the number of elements in the input (i.e., the flattened array). The indices in the output sort the input in ascending order. The routine in `argsort` is the same as in `sort`, therefore, the comments on computational expenses (time and RAM) also apply. In particular, since no copy of the original data is required, virtually no RAM beyond the output array is used. \n", + "\n", + "Since the underlying container of the output array is of type `uint16_t`, neither of the output dimensions should be larger than 65535. If that happens to be the case, the function will bail out with a `ValueError`." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "ExecuteTime": { + "end_time": "2019-11-06T06:28:45.719578Z", + "start_time": "2019-11-06T06:28:45.704072Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "a:\n", + " array([[1.0, 12.0, 3.0, 0.0],\n", + "\t [5.0, 3.0, 4.0, 1.0],\n", + "\t [9.0, 11.0, 1.0, 8.0],\n", + "\t [7.0, 10.0, 0.0, 1.0]], dtype=float)\n", + "\n", + "a sorted along vertical axis:\n", + " array([[0, 1, 3, 0],\n", + "\t [1, 3, 2, 1],\n", + "\t [3, 2, 0, 3],\n", + "\t [2, 0, 1, 2]], dtype=uint16)\n", + "\n", + "a sorted along horizontal axis:\n", + " array([[3, 0, 2, 1],\n", + "\t [3, 1, 2, 0],\n", + "\t [2, 3, 0, 1],\n", + "\t [2, 3, 0, 1]], dtype=uint16)\n", + "\n", + "flattened a sorted:\n", + " array([3, 14, 0, ..., 13, 9, 1], dtype=uint16)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "import ulab as np\n", + "from ulab import numerical\n", + "\n", + "a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.float)\n", + "print('\\na:\\n', a)\n", + "b = numerical.argsort(a, axis=0)\n", + "print('\\na sorted along vertical axis:\\n', b)\n", + "\n", + "c = numerical.argsort(a, axis=1)\n", + "print('\\na sorted along horizontal axis:\\n', c)\n", + "\n", + "c = numerical.argsort(a, axis=None)\n", + "print('\\nflattened a sorted:\\n', c)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since during the sorting, only the indices are shuffled, `argsort` does not modify the input array, as one can verify this by the following example:" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "ExecuteTime": { + "end_time": "2019-11-06T16:04:31.653444Z", + "start_time": "2019-11-06T16:04:31.634995Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "a:\n", + " array([0, 5, 1, 3, 2, 4], dtype=uint8)\n", + "\n", + "sorting indices:\n", + " array([0, 2, 4, 3, 5, 1], dtype=uint16)\n", + "\n", + "the original array:\n", + " array([0, 5, 1, 3, 2, 4], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "import ulab as np\n", + "from ulab import numerical\n", + "\n", + "a = np.array([0, 5, 1, 3, 2, 4], dtype=np.uint8)\n", + "print('\\na:\\n', a)\n", + "b = numerical.argsort(a, axis=1)\n", + "print('\\nsorting indices:\\n', b)\n", + "print('\\nthe original array:\\n', a)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/ulab-poly.ipynb b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-poly.ipynb new file mode 100644 index 00000000..9cd7223e --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-poly.ipynb @@ -0,0 +1,454 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-01T09:27:13.438054Z", + "start_time": "2020-05-01T09:27:13.191491Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Notebook magic" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2020-08-03T18:32:45.342280Z", + "start_time": "2020-08-03T18:32:45.338442Z" + } + }, + "outputs": [], + "source": [ + "from IPython.core.magic import Magics, magics_class, line_cell_magic\n", + "from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n", + "from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n", + "import subprocess\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "ExecuteTime": { + "end_time": "2020-07-23T20:31:25.296014Z", + "start_time": "2020-07-23T20:31:25.265937Z" + } + }, + "outputs": [], + "source": [ + "@magics_class\n", + "class PyboardMagic(Magics):\n", + " @cell_magic\n", + " @magic_arguments()\n", + " @argument('-skip')\n", + " @argument('-unix')\n", + " @argument('-pyboard')\n", + " @argument('-file')\n", + " @argument('-data')\n", + " @argument('-time')\n", + " @argument('-memory')\n", + " def micropython(self, line='', cell=None):\n", + " args = parse_argstring(self.micropython, line)\n", + " if args.skip: # doesn't care about the cell's content\n", + " print('skipped execution')\n", + " return None # do not parse the rest\n", + " if args.unix: # tests the code on the unix port. Note that this works on unix only\n", + " with open('/dev/shm/micropython.py', 'w') as fout:\n", + " fout.write(cell)\n", + " proc = subprocess.Popen([\"../../micropython/ports/unix/micropython\", \"/dev/shm/micropython.py\"], \n", + " stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " print(proc.stdout.read().decode(\"utf-8\"))\n", + " print(proc.stderr.read().decode(\"utf-8\"))\n", + " return None\n", + " if args.file: # can be used to copy the cell content onto the pyboard's flash\n", + " spaces = \" \"\n", + " try:\n", + " with open(args.file, 'w') as fout:\n", + " fout.write(cell.replace('\\t', spaces))\n", + " printf('written cell to {}'.format(args.file))\n", + " except:\n", + " print('Failed to write to disc!')\n", + " return None # do not parse the rest\n", + " if args.data: # can be used to load data from the pyboard directly into kernel space\n", + " message = pyb.exec(cell)\n", + " if len(message) == 0:\n", + " print('pyboard >>>')\n", + " else:\n", + " print(message.decode('utf-8'))\n", + " # register new variable in user namespace\n", + " self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n", + " \n", + " if args.time: # measures the time of executions\n", + " pyb.exec('import utime')\n", + " message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n", + " \"\\nprint('execution time: {:d} us'.format(delta))\")\n", + " print(message.decode('utf-8'))\n", + " \n", + " if args.memory: # prints out memory information \n", + " message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n", + " print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n", + " message = pyb.exec(cell)\n", + " print(\">>> \", message.decode('utf-8'))\n", + " message = pyb.exec('print(mem_info())')\n", + " print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n", + "\n", + " if args.pyboard:\n", + " message = pyb.exec(cell)\n", + " print(message.decode('utf-8'))\n", + "\n", + "ip = get_ipython()\n", + "ip.register_magics(PyboardMagic)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pyboard" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:35.126401Z", + "start_time": "2020-05-07T07:35:35.105824Z" + } + }, + "outputs": [], + "source": [ + "import pyboard\n", + "pyb = pyboard.Pyboard('/dev/ttyACM0')\n", + "pyb.enter_raw_repl()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:11:18.145548Z", + "start_time": "2020-05-19T19:11:18.137468Z" + } + }, + "outputs": [], + "source": [ + "pyb.exit_raw_repl()\n", + "pyb.close()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:38.725924Z", + "start_time": "2020-05-07T07:35:38.645488Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "import ulab as np\n", + "\n", + "def timeit(n=1000):\n", + " def wrapper(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " run_times = np.zeros(n, dtype=np.uint16)\n", + " for i in range(n):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n", + " print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n", + " print('\\tbest: %d us'%np.min(run_times))\n", + " print('\\tworst: %d us'%np.max(run_times))\n", + " print('\\taverage: %d us'%np.mean(run_times))\n", + " print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n", + " return result\n", + " return new_func\n", + " return wrapper\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__END_OF_DEFS__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Polynomials\n", + "\n", + "Functions in the polynomial sub-module can be invoked by importing the module first." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## polyval\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.polyval.html\n", + "\n", + "`polyval` takes two arguments, both arrays or other iterables." + ] + }, + { + "cell_type": "code", + "execution_count": 187, + "metadata": { + "ExecuteTime": { + "end_time": "2019-11-01T12:53:22.448303Z", + "start_time": "2019-11-01T12:53:22.435176Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "coefficients: [1, 1, 1, 0]\n", + "independent values: [0, 1, 2, 3, 4]\n", + "\n", + "values of p(x): array([0.0, 3.0, 14.0, 39.0, 84.0], dtype=float)\n", + "\n", + "ndarray (a): array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float)\n", + "value of p(a): array([0.0, 3.0, 14.0, 39.0, 84.0], dtype=float)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "import ulab as np\n", + "from ulab import poly\n", + "\n", + "p = [1, 1, 1, 0]\n", + "x = [0, 1, 2, 3, 4]\n", + "print('coefficients: ', p)\n", + "print('independent values: ', x)\n", + "print('\\nvalues of p(x): ', poly.polyval(p, x))\n", + "\n", + "# the same works with one-dimensional ndarrays\n", + "a = np.array(x)\n", + "print('\\nndarray (a): ', a)\n", + "print('value of p(a): ', poly.polyval(p, a))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## polyfit\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.polyfit.html\n", + "\n", + "polyfit takes two, or three arguments. The last one is the degree of the polynomial that will be fitted, the last but one is an array or iterable with the `y` (dependent) values, and the first one, an array or iterable with the `x` (independent) values, can be dropped. If that is the case, `x` will be generated in the function, assuming uniform sampling. \n", + "\n", + "If the length of `x`, and `y` are not the same, the function raises a `ValueError`." + ] + }, + { + "cell_type": "code", + "execution_count": 189, + "metadata": { + "ExecuteTime": { + "end_time": "2019-11-01T12:54:08.326802Z", + "start_time": "2019-11-01T12:54:08.311182Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "independent values:\t array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0], dtype=float)\n", + "dependent values:\t array([9.0, 4.0, 1.0, 0.0, 1.0, 4.0, 9.0], dtype=float)\n", + "fitted values:\t\t array([1.0, -6.0, 9.000000000000004], dtype=float)\n", + "\n", + "dependent values:\t array([9.0, 4.0, 1.0, 0.0, 1.0, 4.0, 9.0], dtype=float)\n", + "fitted values:\t\t array([1.0, -6.0, 9.000000000000004], dtype=float)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "import ulab as np\n", + "from ulab import poly\n", + "\n", + "x = np.array([0, 1, 2, 3, 4, 5, 6])\n", + "y = np.array([9, 4, 1, 0, 1, 4, 9])\n", + "print('independent values:\\t', x)\n", + "print('dependent values:\\t', y)\n", + "print('fitted values:\\t\\t', poly.polyfit(x, y, 2))\n", + "\n", + "# the same with missing x\n", + "print('\\ndependent values:\\t', y)\n", + "print('fitted values:\\t\\t', poly.polyfit(y, 2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Execution time\n", + "\n", + "`polyfit` is based on the inversion of a matrix (there is more on the background in https://en.wikipedia.org/wiki/Polynomial_regression), and it requires the intermediate storage of `2*N*(deg+1)` floats, where `N` is the number of entries in the input array, and `deg` is the fit's degree. The additional computation costs of the matrix inversion discussed in [inv](#inv) also apply. The example from above needs around 150 microseconds to return:" + ] + }, + { + "cell_type": "code", + "execution_count": 560, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-20T07:24:39.002243Z", + "start_time": "2019-10-20T07:24:38.978687Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "execution time: 153 us\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import ulab as np\n", + "from ulab import poly\n", + "\n", + "@timeit\n", + "def time_polyfit(x, y, n):\n", + " return poly.polyfit(x, y, n)\n", + "\n", + "x = np.array([0, 1, 2, 3, 4, 5, 6])\n", + "y = np.array([9, 4, 1, 0, 1, 4, 9])\n", + "\n", + "time_polyfit(x, y, 2)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/ulab-programming.ipynb b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-programming.ipynb new file mode 100644 index 00000000..6eabf6d5 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-programming.ipynb @@ -0,0 +1,798 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-25T21:25:53.804315Z", + "start_time": "2020-10-25T21:25:43.765649Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__END_OF_DEFS__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Programming ulab\n", + "\n", + "Earlier we have seen, how `ulab`'s functions and methods can be accessed in `micropython`. This last section of the book explains, how these functions are implemented. By the end of this chapter, not only would you be able to extend `ulab`, and write your own `numpy`-compatible functions, but through a deeper understanding of the inner workings of the functions, you would also be able to see what the trade-offs are at the `python` level.\n", + "\n", + "\n", + "## Code organisation\n", + "\n", + "As mentioned earlier, the `python` functions are organised into sub-modules at the C level. The C sub-modules can be found in `./ulab/code/`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The `ndarray` object" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### General comments\n", + "\n", + "`ndarrays` are efficient containers of numerical data of the same type (i.e., signed/unsigned chars, signed/unsigned integers or `mp_float_t`s, which, depending on the platform, are either C `float`s, or C `double`s). Beyond storing the actual data in the void pointer `*array`, the type definition has eight additional members (on top of the `base` type). Namely, the `dtype`, which tells us, how the bytes are to be interpreted. Moreover, the `itemsize`, which stores the size of a single entry in the array, `boolean`, an unsigned integer, which determines, whether the arrays is to be treated as a set of Booleans, or as numerical data, `ndim`, the number of dimensions (`uint8_t`), `len`, the length of the array (the number of entries), the shape (`*size_t`), the strides (`*int32_t`). The length is simply the product of the numbers in `shape`.\n", + "\n", + "The type definition is as follows:\n", + "\n", + "```c\n", + "typedef struct _ndarray_obj_t {\n", + " mp_obj_base_t base;\n", + " uint8_t dtype;\n", + " uint8_t itemsize;\n", + " uint8_t boolean;\n", + " uint8_t ndim;\n", + " size_t len;\n", + " size_t shape[ULAB_MAX_DIMS];\n", + " int32_t strides[ULAB_MAX_DIMS];\n", + " void *array;\n", + "} ndarray_obj_t;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Memory layout\n", + "\n", + "The values of an `ndarray` are stored in a contiguous segment in the RAM. The `ndarray` can be dense, meaning that all numbers in the linear memory segment belong to a linar combination of coordinates, and it can also be sparse, i.e., some elements of the linear storage space will be skipped, when the elements of the tensor are traversed. \n", + "\n", + "In the RAM, the position of the item $M(n_1, n_2, ..., n_{k-1}, n_k)$ in a dense tensor of rank $k$ is given by the linear combination \n", + "\n", + "\\begin{equation}\n", + "P(n_1, n_2, ..., n_{k-1}, n_k) = n_1 s_1 + n_2 s_2 + ... + n_{k-1}s_{k-1} + n_ks_k = \\sum_{i=1}^{k}n_is_i\n", + "\\end{equation}\n", + "where $s_i$ are the strides of the tensor, defined as \n", + "\n", + "\\begin{equation}\n", + "s_i = \\prod_{j=i+1}^k l_j\n", + "\\end{equation}\n", + "\n", + "where $l_j$ is length of the tensor along the $j$th axis. When the tensor is sparse (e.g., when the tensor is sliced), the strides along a particular axis will be multiplied by a non-zero integer. If this integer is different to $\\pm 1$, the linear combination above cannot access all elements in the RAM, i.e., some numbers will be skipped. Note that $|s_1| > |s_2| > ... > |s_{k-1}| > |s_k|$, even if the tensor is sparse. The statement is trivial for dense tensors, and it follows from the definition of $s_i$. For sparse tensors, a slice cannot have a step larger than the shape along that axis. But for dense tensors, $s_i/s_{i+1} = l_i$. \n", + "\n", + "When creating a *view*, we simply re-calculate the `strides`, and re-set the `*array` pointer.\n", + "\n", + "## Iterating over elements of a tensor\n", + "\n", + "The `shape` and `strides` members of the array tell us how we have to move our pointer, when we want to read out the numbers. For technical reasons that will become clear later, the numbers in `shape` and in `strides` are aligned to the right, and begin on the right hand side, i.e., if the number of possible dimensions is `ULAB_MAX_DIMS`, then `shape[ULAB_MAX_DIMS-1]` is the length of the last axis, `shape[ULAB_MAX_DIMS-2]` is the length of the last but one axis, and so on. If the number of actual dimensions, `ndim < ULAB_MAX_DIMS`, the first `ULAB_MAX_DIMS - ndim` entries in `shape` and `strides` will be equal to zero, but they could, in fact, be assigned any value, because these will never be accessed in an operation.\n", + "\n", + "With this definition of the strides, the linear combination in $P(n_1, n_2, ..., n_{k-1}, n_k)$ is a one-to-one mapping from the space of tensor coordinates, $(n_1, n_2, ..., n_{k-1}, n_k)$, and the coordinate in the linear array, $n_1s_1 + n_2s_2 + ... + n_{k-1}s_{k-1} + n_ks_k$, i.e., no two distinct sets of coordinates will result in the same position in the linear array. \n", + "\n", + "Since the `strides` are given in terms of bytes, when we iterate over an array, the void data pointer is usually cast to `uint8_t`, and the values are converted using the proper data type stored in `ndarray->dtype`. However, there might be cases, when it makes perfect sense to cast `*array` to a different type, in which case the `strides` have to be re-scaled by the value of `ndarray->itemsize`.\n", + "\n", + "### Iterating using the unwrapped loops\n", + "\n", + "The following macro definition is taken from [vector.h](https://github.com/v923z/micropython-ulab/blob/master/code/numpy/vector/vector.h), and demonstrates, how we can iterate over a single array in four dimensions. \n", + "\n", + "```c\n", + "#define ITERATE_VECTOR(type, array, source, sarray) do {\n", + " size_t i=0;\n", + " do {\n", + " size_t j = 0;\n", + " do {\n", + " size_t k = 0;\n", + " do {\n", + " size_t l = 0;\n", + " do {\n", + " *(array)++ = f(*((type *)(sarray)));\n", + " (sarray) += (source)->strides[ULAB_MAX_DIMS - 1];\n", + " l++;\n", + " } while(l < (source)->shape[ULAB_MAX_DIMS-1]);\n", + " (sarray) -= (source)->strides[ULAB_MAX_DIMS - 1] * (source)->shape[ULAB_MAX_DIMS-1];\n", + " (sarray) += (source)->strides[ULAB_MAX_DIMS - 2];\n", + " k++;\n", + " } while(k < (source)->shape[ULAB_MAX_DIMS-2]);\n", + " (sarray) -= (source)->strides[ULAB_MAX_DIMS - 2] * (source)->shape[ULAB_MAX_DIMS-2];\n", + " (sarray) += (source)->strides[ULAB_MAX_DIMS - 3];\n", + " j++;\n", + " } while(j < (source)->shape[ULAB_MAX_DIMS-3]);\n", + " (sarray) -= (source)->strides[ULAB_MAX_DIMS - 3] * (source)->shape[ULAB_MAX_DIMS-3];\n", + " (sarray) += (source)->strides[ULAB_MAX_DIMS - 4];\n", + " i++;\n", + " } while(i < (source)->shape[ULAB_MAX_DIMS-4]);\n", + "} while(0)\n", + "```\n", + "\n", + "We start with the innermost loop, the one recursing `l`. `array` is already of type `mp_float_t`, while the source array, `sarray`, has been cast to `uint8_t` in the calling function. The numbers contained in `sarray` have to be read out in the proper type dictated by `ndarray->dtype`. This is what happens in the statement `*((type *)(sarray))`, and this number is then fed into the function `f`. Vectorised mathematical functions produce *dense* arrays, and for this reason, we can simply advance the `array` pointer. \n", + "\n", + "The advancing of the `sarray` pointer is a bit more involving: first, in the innermost loop, we simply move forward by the amount given by the last stride, which is `(source)->strides[ULAB_MAX_DIMS - 1]`, because the `shape` and the `strides` are aligned to the right. We move the pointer as many times as given by `(source)->shape[ULAB_MAX_DIMS-1]`, which is the length of the very last axis. Hence the the structure of the loop\n", + "\n", + "```c\n", + " size_t l = 0;\n", + " do {\n", + " ...\n", + " l++;\n", + " } while(l < (source)->shape[ULAB_MAX_DIMS-1]);\n", + "\n", + "```\n", + "Once we have exhausted the last axis, we have to re-wind the pointer, and advance it by an amount given by the last but one stride. Keep in mind that in the the innermost loop we moved our pointer `(source)->shape[ULAB_MAX_DIMS-1]` times by `(source)->strides[ULAB_MAX_DIMS - 1]`, i.e., we re-wind it by moving it backwards by `(source)->strides[ULAB_MAX_DIMS - 1] * (source)->shape[ULAB_MAX_DIMS-1]`. In the next step, we move forward by `(source)->strides[ULAB_MAX_DIMS - 2]`, which is the last but one stride. \n", + "\n", + "\n", + "```c\n", + " (sarray) -= (source)->strides[ULAB_MAX_DIMS - 1] * (source)->shape[ULAB_MAX_DIMS-1];\n", + " (sarray) += (source)->strides[ULAB_MAX_DIMS - 2];\n", + "\n", + "```\n", + "\n", + "This pattern must be repeated for each axis of the array, and this is how we arrive at the four nested loops listed above." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Re-winding arrays by means of a function\n", + "\n", + "\n", + "In addition to un-wrapping the iteration loops by means of macros, there is another way of traversing all elements of a tensor: we note that, since $|s_1| > |s_2| > ... > |s_{k-1}| > |s_k|$, $P(n1, n2, ..., n_{k-1}, n_k)$ changes most slowly in the last coordinate. Hence, if we start from the very beginning, ($n_i = 0$ for all $i$), and walk along the linear RAM segment, we increment the value of $n_k$ as long as $n_k < l_k$. Once $n_k = l_k$, we have to reset $n_k$ to 0, and increment $n_{k-1}$ by one. After each such round, $n_{k-1}$ will be incremented by one, as long as $n_{k-1} < l_{k-1}$. Once $n_{k-1} = l_{k-1}$, we reset both $n_k$, and $n_{k-1}$ to 0, and increment $n_{k-2}$ by one. \n", + "\n", + "Rewinding the arrays in this way is implemented in the function `ndarray_rewind_array` in [ndarray.c](https://github.com/v923z/micropython-ulab/blob/master/code/ndarray.c). \n", + "\n", + "```c\n", + "void ndarray_rewind_array(uint8_t ndim, uint8_t *array, size_t *shape, int32_t *strides, size_t *coords) {\n", + " // resets the data pointer of a single array, whenever an axis is full\n", + " // since we always iterate over the very last axis, we have to keep track of\n", + " // the last ndim-2 axes only\n", + " array -= shape[ULAB_MAX_DIMS - 1] * strides[ULAB_MAX_DIMS - 1];\n", + " array += strides[ULAB_MAX_DIMS - 2];\n", + " for(uint8_t i=1; i < ndim-1; i++) {\n", + " coords[ULAB_MAX_DIMS - 1 - i] += 1;\n", + " if(coords[ULAB_MAX_DIMS - 1 - i] == shape[ULAB_MAX_DIMS - 1 - i]) { // we are at a dimension boundary\n", + " array -= shape[ULAB_MAX_DIMS - 1 - i] * strides[ULAB_MAX_DIMS - 1 - i];\n", + " array += strides[ULAB_MAX_DIMS - 2 - i];\n", + " coords[ULAB_MAX_DIMS - 1 - i] = 0;\n", + " coords[ULAB_MAX_DIMS - 2 - i] += 1;\n", + " } else { // coordinates can change only, if the last coordinate changes\n", + " return;\n", + " }\n", + " }\n", + "}\n", + "```\n", + "\n", + "and the function would be called as in the snippet below. Note that the innermost loop is factored out, so that we can save the `if(...)` statement for the last axis.\n", + "\n", + "```c\n", + " size_t *coords = ndarray_new_coords(results->ndim);\n", + " for(size_t i=0; i < results->len/results->shape[ULAB_MAX_DIMS -1]; i++) {\n", + " size_t l = 0;\n", + " do {\n", + " ...\n", + " l++;\n", + " } while(l < results->shape[ULAB_MAX_DIMS - 1]);\n", + " ndarray_rewind_array(results->ndim, array, results->shape, strides, coords);\n", + " } while(0)\n", + "\n", + "```\n", + "\n", + "The advantage of this method is that the implementation is independent of the number of dimensions: the iteration requires more or less the same flash space for 2 dimensions as for 22. However, the price we have to pay for this convenience is the extra function call." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Iterating over two ndarrays simultaneously: broadcasting\n", + "\n", + "Whenever we invoke a binary operator, call a function with two arguments of `ndarray` type, or assign something to an `ndarray`, we have to iterate over two views at the same time. The task is trivial, if the two `ndarray`s in question have the same shape (but not necessarily the same set of strides), because in this case, we can still iterate in the same loop. All that happens is that we move two data pointers in sync.\n", + "\n", + "The problem becomes a bit more involving, when the shapes of the two `ndarray`s are not identical. For such cases, `numpy` defines so-called broadcasting, which boils down to two rules. \n", + "\n", + "1. The shapes in the tensor with lower rank has to be prepended with axes of size 1 till the two ranks become equal.\n", + "2. Along all axes the two tensors should have the same size, or one of the sizes must be 1. \n", + "\n", + "If, after applying the first rule the second is not satisfied, the two `ndarray`s cannot be broadcast together. \n", + "\n", + "Now, let us suppose that we have two compatible `ndarray`s, i.e., after applying the first rule, the second is satisfied. How do we iterate over the elements in the tensors? \n", + "\n", + "We should recall, what exactly we do, when iterating over a single array: normally, we move the data pointer by the last stride, except, when we arrive at a dimension boundary (when the last axis is exhausted). At that point, we move the pointer by an amount dictated by the strides. And this is the key: *dictated by the strides*. Now, if we have two arrays that are originally not compatible, we define new strides for them, and use these in the iteration. With that, we are back to the case, where we had two compatible arrays. \n", + "\n", + "Now, let us look at the second broadcasting rule: if the two arrays have the same size, we take both `ndarray`s' strides along that axis. If, on the other hand, one of the `ndarray`s is of length 1 along one of its axes, we set the corresponding strides to 0. This will ensure that that data pointer is not moved, when we iterate over both `ndarray`s at the same time. \n", + "\n", + "Thus, in order to implement broadcasting, we first have to check, whether the two above-mentioned rules can be satisfied, and if so, we have to find the two new sets strides. \n", + "\n", + "The `ndarray_can_broadcast` function from [ndarray.c](https://github.com/v923z/micropython-ulab/blob/master/code/ndarray.c) takes two `ndarray`s, and returns `true`, if the two arrays can be broadcast together. At the same time, it also calculates new strides for the two arrays, so that they can be iterated over at the same time. \n", + "\n", + "```c\n", + "bool ndarray_can_broadcast(ndarray_obj_t *lhs, ndarray_obj_t *rhs, uint8_t *ndim, size_t *shape, int32_t *lstrides, int32_t *rstrides) {\n", + " // returns True or False, depending on, whether the two arrays can be broadcast together\n", + " // numpy's broadcasting rules are as follows:\n", + " //\n", + " // 1. the two shapes are either equal\n", + " // 2. one of the shapes is 1\n", + " memset(lstrides, 0, sizeof(size_t)*ULAB_MAX_DIMS);\n", + " memset(rstrides, 0, sizeof(size_t)*ULAB_MAX_DIMS);\n", + " lstrides[ULAB_MAX_DIMS - 1] = lhs->strides[ULAB_MAX_DIMS - 1];\n", + " rstrides[ULAB_MAX_DIMS - 1] = rhs->strides[ULAB_MAX_DIMS - 1];\n", + " for(uint8_t i=ULAB_MAX_DIMS; i > 0; i--) {\n", + " if((lhs->shape[i-1] == rhs->shape[i-1]) || (lhs->shape[i-1] == 0) || (lhs->shape[i-1] == 1) ||\n", + " (rhs->shape[i-1] == 0) || (rhs->shape[i-1] == 1)) {\n", + " shape[i-1] = MAX(lhs->shape[i-1], rhs->shape[i-1]);\n", + " if(shape[i-1] > 0) (*ndim)++;\n", + " if(lhs->shape[i-1] < 2) {\n", + " lstrides[i-1] = 0;\n", + " } else {\n", + " lstrides[i-1] = lhs->strides[i-1];\n", + " }\n", + " if(rhs->shape[i-1] < 2) {\n", + " rstrides[i-1] = 0;\n", + " } else {\n", + " rstrides[i-1] = rhs->strides[i-1];\n", + " }\n", + " } else {\n", + " return false;\n", + " }\n", + " }\n", + " return true;\n", + "}\n", + "```\n", + "\n", + "A good example of how the function would be called can be found in [vector.c](https://github.com/v923z/micropython-ulab/blob/master/code/numpy/vector/vector.c), in the `vector_arctan2` function:\n", + "\n", + "```c\n", + "mp_obj_t vector_arctan2(mp_obj_t y, mp_obj_t x) {\n", + " ...\n", + " uint8_t ndim = 0;\n", + " size_t *shape = m_new(size_t, ULAB_MAX_DIMS);\n", + " int32_t *xstrides = m_new(int32_t, ULAB_MAX_DIMS);\n", + " int32_t *ystrides = m_new(int32_t, ULAB_MAX_DIMS);\n", + " if(!ndarray_can_broadcast(ndarray_x, ndarray_y, &ndim, shape, xstrides, ystrides)) {\n", + " mp_raise_ValueError(translate(\"operands could not be broadcast together\"));\n", + " m_del(size_t, shape, ULAB_MAX_DIMS);\n", + " m_del(int32_t, xstrides, ULAB_MAX_DIMS);\n", + " m_del(int32_t, ystrides, ULAB_MAX_DIMS);\n", + " }\n", + "\n", + " uint8_t *xarray = (uint8_t *)ndarray_x->array;\n", + " uint8_t *yarray = (uint8_t *)ndarray_y->array;\n", + " \n", + " ndarray_obj_t *results = ndarray_new_dense_ndarray(ndim, shape, NDARRAY_FLOAT);\n", + " mp_float_t *rarray = (mp_float_t *)results->array;\n", + " ...\n", + "```\n", + "\n", + "After the new strides have been calculated, the iteration loop is identical to what we discussed in the previous section." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Contracting an `ndarray`\n", + "\n", + "\n", + "There are many operations that reduce the number of dimensions of an `ndarray` by 1, i.e., that remove an axis from the tensor. The drill is the same as before, with the exception that first we have to remove the `strides` and `shape` that corresponds to the axis along which we intend to contract. The `numerical_reduce_axes` function from [numerical.c](https://github.com/v923z/micropython-ulab/blob/master/code/numerical/numerical.c) does that. \n", + "\n", + "\n", + "```c\n", + "static void numerical_reduce_axes(ndarray_obj_t *ndarray, int8_t axis, size_t *shape, int32_t *strides) {\n", + " // removes the values corresponding to a single axis from the shape and strides array\n", + " uint8_t index = ULAB_MAX_DIMS - ndarray->ndim + axis;\n", + " if((ndarray->ndim == 1) && (axis == 0)) {\n", + " index = 0;\n", + " shape[ULAB_MAX_DIMS - 1] = 0;\n", + " return;\n", + " }\n", + " for(uint8_t i = ULAB_MAX_DIMS - 1; i > 0; i--) {\n", + " if(i > index) {\n", + " shape[i] = ndarray->shape[i];\n", + " strides[i] = ndarray->strides[i];\n", + " } else {\n", + " shape[i] = ndarray->shape[i-1];\n", + " strides[i] = ndarray->strides[i-1];\n", + " }\n", + " }\n", + "}\n", + "```\n", + "\n", + "Once the reduced `strides` and `shape` are known, we place the axis in question in the innermost loop, and wrap it with the loops, whose coordinates are in the `strides`, and `shape` arrays. The `RUN_STD` macro from [numerical.h](https://github.com/v923z/micropython-ulab/blob/master/code/numpy/numerical/numerical.h) is a good example. The macro is expanded in the `numerical_sum_mean_std_ndarray` function. \n", + "\n", + "\n", + "```c\n", + "static mp_obj_t numerical_sum_mean_std_ndarray(ndarray_obj_t *ndarray, mp_obj_t axis, uint8_t optype, size_t ddof) {\n", + " uint8_t *array = (uint8_t *)ndarray->array;\n", + " size_t *shape = m_new(size_t, ULAB_MAX_DIMS);\n", + " memset(shape, 0, sizeof(size_t)*ULAB_MAX_DIMS);\n", + " int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS);\n", + " memset(strides, 0, sizeof(uint32_t)*ULAB_MAX_DIMS);\n", + "\n", + " int8_t ax = mp_obj_get_int(axis);\n", + " if(ax < 0) ax += ndarray->ndim;\n", + " if((ax < 0) || (ax > ndarray->ndim - 1)) {\n", + " mp_raise_ValueError(translate(\"index out of range\"));\n", + " }\n", + " numerical_reduce_axes(ndarray, ax, shape, strides);\n", + " uint8_t index = ULAB_MAX_DIMS - ndarray->ndim + ax;\n", + " ndarray_obj_t *results = NULL;\n", + " uint8_t *rarray = NULL;\n", + " ...\n", + "\n", + "```\n", + "Here is the macro for the three-dimensional case: \n", + "\n", + "```c\n", + "#define RUN_STD(ndarray, type, array, results, r, shape, strides, index, div) do {\n", + " size_t k = 0;\n", + " do {\n", + " size_t l = 0;\n", + " do {\n", + " RUN_STD1((ndarray), type, (array), (results), (r), (index), (div));\n", + " (array) -= (ndarray)->strides[(index)] * (ndarray)->shape[(index)];\n", + " (array) += (strides)[ULAB_MAX_DIMS - 1];\n", + " l++;\n", + " } while(l < (shape)[ULAB_MAX_DIMS - 1]);\n", + " (array) -= (strides)[ULAB_MAX_DIMS - 2] * (shape)[ULAB_MAX_DIMS-2];\n", + " (array) += (strides)[ULAB_MAX_DIMS - 3];\n", + " k++;\n", + " } while(k < (shape)[ULAB_MAX_DIMS - 2]);\n", + "} while(0)\n", + "```\n", + "In `RUN_STD`, we simply move our pointers; the calculation itself happens in the `RUN_STD1` macro below. (Note that this is the implementation of the numerically stable Welford algorithm.)\n", + "\n", + "```c\n", + "#define RUN_STD1(ndarray, type, array, results, r, index, div)\n", + "({\n", + " mp_float_t M, m, S = 0.0, s = 0.0;\n", + " M = m = *(mp_float_t *)((type *)(array));\n", + " for(size_t i=1; i < (ndarray)->shape[(index)]; i++) {\n", + " (array) += (ndarray)->strides[(index)];\n", + " mp_float_t value = *(mp_float_t *)((type *)(array));\n", + " m = M + (value - M) / (mp_float_t)i;\n", + " s = S + (value - M) * (value - m);\n", + " M = m;\n", + " S = s;\n", + " }\n", + " (array) += (ndarray)->strides[(index)];\n", + " *(r)++ = MICROPY_FLOAT_C_FUN(sqrt)((ndarray)->shape[(index)] * s / (div));\n", + "})\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Upcasting\n", + "\n", + "When in an operation the `dtype`s of two arrays are different, the result's `dtype` will be decided by the following upcasting rules:\n", + "\n", + "1. Operations with two `ndarray`s of the same `dtype` preserve their `dtype`, even when the results overflow.\n", + "\n", + "2. if either of the operands is a float, the result automatically becomes a float\n", + "\n", + "3. otherwise\n", + "\n", + " - `uint8` + `int8` => `int16`, \n", + " - `uint8` + `int16` => `int16`\n", + " - `uint8` + `uint16` => `uint16`\n", + " \n", + " - `int8` + `int16` => `int16`\n", + " - `int8` + `uint16` => `uint16` (in numpy, the result is a `int32`)\n", + "\n", + " - `uint16` + `int16` => `float` (in numpy, the result is a `int32`)\n", + " \n", + "4. When one operand of a binary operation is a generic scalar `micropython` variable, i.e., `mp_obj_int`, or `mp_obj_float`, it will be converted to a linear array of length 1, and with the smallest `dtype` that can accommodate the variable in question. After that the broadcasting rules apply, as described in the section [Iterating over two ndarrays simultaneously: broadcasting](#Iterating_over_two_ndarrays_simultaneously:_broadcasting)\n", + "\n", + "Upcasting is resolved in place, wherever it is required. Notable examples can be found in [ndarray_operators.c](https://github.com/v923z/micropython-ulab/blob/master/code/ndarray_operators.c)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Slicing and indexing" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "An `ndarray` can be indexed with three types of objects: integer scalars, slices, and another `ndarray`, whose elements are either integer scalars, or Booleans. Since slice and integer indices can be thought of as modifications of the `strides`, these indices return a view of the `ndarray`. This statement does not hold for `ndarray` indices, and therefore, the return a copy of the array." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Extending ulab\n", + "\n", + "The `user` module is disabled by default, as can be seen from the last couple of lines of [ulab.h](https://github.com/v923z/micropython-ulab/blob/master/code/ulab.h)\n", + "\n", + "```c\n", + "// user-defined module\n", + "#ifndef ULAB_USER_MODULE\n", + "#define ULAB_USER_MODULE (0)\n", + "#endif\n", + "```\n", + "\n", + "The module contains a very simple function, `user_dummy`, and this function is bound to the module itself. In other words, even if the module is enabled, one has to `import`:\n", + "\n", + "```python\n", + "\n", + "import ulab\n", + "from ulab import user\n", + "\n", + "user.dummy_function(2.5)\n", + "```\n", + "which should just return 5.0. Even if `numpy`-compatibility is required (i.e., if most functions are bound at the top level to `ulab` directly), having to `import` the module has a great advantage. Namely, only the [user.h](https://github.com/v923z/micropython-ulab/blob/master/code/user/user.h) and [user.c](https://github.com/v923z/micropython-ulab/blob/master/code/user/user.c) files have to be modified, thus it should be relatively straightforward to update your local copy from [github](https://github.com/v923z/micropython-ulab/blob/master/). \n", + "\n", + "Now, let us see, how we can add a more meaningful function. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Creating a new ndarray\n", + "\n", + "In the [General comments](#General_comments) sections we have seen the type definition of an `ndarray`. This structure can be generated by means of a couple of functions listed in [ndarray.c](https://github.com/v923z/micropython-ulab/blob/master/code/ndarray.c). \n", + "\n", + "\n", + "### ndarray_new_ndarray\n", + "\n", + "The `ndarray_new_ndarray` functions is called by all other array-generating functions. It takes the number of dimensions, `ndim`, a `uint8_t`, the `shape`, a pointer to `size_t`, the `strides`, a pointer to `int32_t`, and `dtype`, another `uint8_t` as its arguments, and returns a new array with all entries initialised to 0. \n", + "\n", + "Assuming that `ULAB_MAX_DIMS > 2`, a new dense array of dimension 3, of `shape` (3, 4, 5), of `strides` (1000, 200, 10), and `dtype` `uint16_t` can be generated by the following instructions\n", + "\n", + "```c\n", + "size_t *shape = m_new(size_t, ULAB_MAX_DIMS);\n", + "shape[ULAB_MAX_DIMS - 1] = 5;\n", + "shape[ULAB_MAX_DIMS - 2] = 4;\n", + "shape[ULAB_MAX_DIMS - 3] = 3;\n", + "\n", + "int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS);\n", + "strides[ULAB_MAX_DIMS - 1] = 10;\n", + "strides[ULAB_MAX_DIMS - 2] = 200;\n", + "strides[ULAB_MAX_DIMS - 3] = 1000;\n", + "\n", + "ndarray_obj_t *new_ndarray = ndarray_new_ndarray(3, shape, strides, NDARRAY_UINT16);\n", + "```\n", + "\n", + "### ndarray_new_dense_ndarray\n", + "\n", + "The functions simply calculates the `strides` from the `shape`, and calls `ndarray_new_ndarray`. Assuming that `ULAB_MAX_DIMS > 2`, a new dense array of dimension 3, of `shape` (3, 4, 5), and `dtype` `mp_float_t` can be generated by the following instructions\n", + "\n", + "```c\n", + "size_t *shape = m_new(size_t, ULAB_MAX_DIMS);\n", + "shape[ULAB_MAX_DIMS - 1] = 5;\n", + "shape[ULAB_MAX_DIMS - 2] = 4;\n", + "shape[ULAB_MAX_DIMS - 3] = 3;\n", + "\n", + "ndarray_obj_t *new_ndarray = ndarray_new_dense_ndarray(3, shape, NDARRAY_FLOAT);\n", + "```\n", + "\n", + "### ndarray_new_linear_array\n", + "\n", + "Since the dimensions of a linear array are known (1), the `ndarray_new_linear_array` takes the `length`, a `size_t`, and the `dtype`, an `uint8_t`. Internally, `ndarray_new_linear_array` generates the `shape` array, and calls `ndarray_new_dense_array` with `ndim = 1`.\n", + "\n", + "A linear array of length 100, and `dtype` `uint8` could be created by the function call\n", + "\n", + "```c\n", + "ndarray_obj_t *new_ndarray = ndarray_new_linear_array(100, NDARRAY_UINT8)\n", + "```\n", + "\n", + "### ndarray_new_ndarray_from_tuple\n", + "\n", + "This function takes a `tuple`, which should hold the lengths of the axes (in other words, the `shape`), and the `dtype`, and calls internally `ndarray_new_dense_array`. A new `ndarray` can be generated by calling \n", + "\n", + "```c\n", + "ndarray_obj_t *new_ndarray = ndarray_new_ndarray_from_tuple(shape, NDARRAY_FLOAT);\n", + "```\n", + "where `shape` is a tuple.\n", + "\n", + "\n", + "### ndarray_new_view\n", + "\n", + "This function crates a *view*, and takes the source, an `ndarray`, the number of dimensions, an `uint8_t`, the `shape`, a pointer to `size_t`, the `strides`, a pointer to `int32_t`, and the offset, an `int32_t` as arguments. The offset is the number of bytes by which the void `array` pointer is shifted. E.g., the `python` statement\n", + "\n", + "```python\n", + "a = np.array([0, 1, 2, 3, 4, 5], dtype=uint8)\n", + "b = a[1::2]\n", + "```\n", + "\n", + "produces the array\n", + "\n", + "```python\n", + "array([1, 3, 5], dtype=uint8)\n", + "```\n", + "which holds its data at position `x0 + 1`, if `a`'s pointer is at `x0`. In this particular case, the offset is 1. \n", + "\n", + "The array `b` from the example above could be generated as \n", + "\n", + "```c\n", + "size_t *shape = m_new(size_t, ULAB_MAX_DIMS);\n", + "shape[ULAB_MAX_DIMS - 1] = 3;\n", + "\n", + "int32_t *strides = m_new(int32_t, ULAB_MAX_DIMS);\n", + "strides[ULAB_MAX_DIMS - 1] = 2;\n", + "\n", + "int32_t offset = 1;\n", + "uint8_t ndim = 1;\n", + "\n", + "ndarray_obj_t *new_ndarray = ndarray_new_view(ndarray_a, ndim, shape, strides, offset);\n", + "```\n", + "\n", + "### ndarray_copy_array\n", + "\n", + "The `ndarray_copy_array` function can be used for copying the contents of an array. Note that the target array has to be created beforehand. E.g., a one-to-one copy can be gotten by \n", + "\n", + "```c\n", + "ndarray_obj_t *new_ndarray = ndarray_new_ndarray(source->ndim, source->shape, source->strides, source->dtype);\n", + "ndarray_copy_array(source, new_ndarray);\n", + "\n", + "```\n", + "Note that the function cannot be used for forcing type conversion, i.e., the input and output types must be identical, because the function simply calls the `memcpy` function. On the other hand, the input and output `strides` do not necessarily have to be equal.\n", + "\n", + "### ndarray_copy_view\n", + "\n", + "The `ndarray_obj_t *new_ndarray = ...` instruction can be saved by calling the `ndarray_copy_view` function with the single `source` argument. \n", + "\n", + "\n", + "## Accessing data in the ndarray\n", + "\n", + "Having seen, how arrays can be generated and copied, it is time to look at how the data in an `ndarray` can be accessed and modified. \n", + "\n", + "For starters, let us suppose that the object in question comes from the user (i.e., via the `micropython` interface), First, we have to acquire a pointer to the `ndarray` by calling \n", + "\n", + "```c\n", + "ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(object_in);\n", + "```\n", + "\n", + "If it is not clear, whether the object is an `ndarray` (e.g., if we want to write a function that can take `ndarray`s, and other iterables as its argument), we find this out by evaluating \n", + "\n", + "```c\n", + "mp_obj_is_type(object_in, &ulab_ndarray_type)\n", + "```\n", + "which should return `true`. Once the pointer is at our disposal, we can get a pointer to the underlying numerical array as discussed earlier, i.e., \n", + "\n", + "```c\n", + "uint8_t *array = (uint8_t *)ndarray->array;\n", + "```\n", + "\n", + "If you need to find out the `dtype` of the array, you can get it by accessing the `dtype` member of the `ndarray`, i.e., \n", + "\n", + "```c\n", + "ndarray->dtype\n", + "```\n", + "should be equal to `B`, `b`, `H`, `h`, or `f`. The size of a single item is stored in the `itemsize` member. This number should be equal to 1, if the `dtype` is `B`, or `b`, 2, if the `dtype` is `H`, or `h`, 4, if the `dtype` is `f`, and 8 for `d`. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Boilerplate\n", + "\n", + "In the next section, we will construct a function that generates the element-wise square of a dense array, otherwise, raises a `TypeError` exception. Dense arrays can easily be iterated over, since we do not have to care about the `shape` and the `strides`. If the array is sparse, the section [Iterating over elements of a tensor](#Iterating-over-elements-of-a-tensor) should contain hints as to how the iteration can be implemented.\n", + "\n", + "The function is listed under [user.c](https://github.com/v923z/micropython-ulab/tree/master/code/user/). The `user` module is bound to `ulab` in [ulab.c](https://github.com/v923z/micropython-ulab/tree/master/code/ulab.c) in the lines \n", + "\n", + "```c\n", + " #if ULAB_USER_MODULE\n", + " { MP_ROM_QSTR(MP_QSTR_user), MP_ROM_PTR(&ulab_user_module) },\n", + " #endif\n", + "```\n", + "which assumes that at the very end of [ulab.h](https://github.com/v923z/micropython-ulab/tree/master/code/ulab.h) the \n", + "\n", + "```c\n", + "// user-defined module\n", + "#ifndef ULAB_USER_MODULE\n", + "#define ULAB_USER_MODULE (1)\n", + "#endif\n", + "```\n", + "constant has been set to 1. After compilation, you can call a particular `user` function in `python` by importing the module first, i.e., \n", + "\n", + "```python\n", + "from ulab import numpy as np\n", + "from ulab import user\n", + "\n", + "user.some_function(...)\n", + "```\n", + "\n", + "This separation of user-defined functions from the rest of the code ensures that the integrity of the main module and all its functions are always preserved. Even in case of a catastrophic failure, you can exclude the `user` module, and start over.\n", + "\n", + "And now the function:\n", + "\n", + "\n", + "```c\n", + "static mp_obj_t user_square(mp_obj_t arg) {\n", + " // the function takes a single dense ndarray, and calculates the \n", + " // element-wise square of its entries\n", + " \n", + " // raise a TypeError exception, if the input is not an ndarray\n", + " if(!mp_obj_is_type(arg, &ulab_ndarray_type)) {\n", + " mp_raise_TypeError(translate(\"input must be an ndarray\"));\n", + " }\n", + " ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(arg);\n", + " \n", + " // make sure that the input is a dense array\n", + " if(!ndarray_is_dense(ndarray)) {\n", + " mp_raise_TypeError(translate(\"input must be a dense ndarray\"));\n", + " }\n", + " \n", + " // if the input is a dense array, create `results` with the same number of \n", + " // dimensions, shape, and dtype\n", + " ndarray_obj_t *results = ndarray_new_dense_ndarray(ndarray->ndim, ndarray->shape, ndarray->dtype);\n", + " \n", + " // since in a dense array the iteration over the elements is trivial, we \n", + " // can cast the data arrays ndarray->array and results->array to the actual type\n", + " if(ndarray->dtype == NDARRAY_UINT8) {\n", + " uint8_t *array = (uint8_t *)ndarray->array;\n", + " uint8_t *rarray = (uint8_t *)results->array;\n", + " for(size_t i=0; i < ndarray->len; i++, array++) {\n", + " *rarray++ = (*array) * (*array);\n", + " }\n", + " } else if(ndarray->dtype == NDARRAY_INT8) {\n", + " int8_t *array = (int8_t *)ndarray->array;\n", + " int8_t *rarray = (int8_t *)results->array;\n", + " for(size_t i=0; i < ndarray->len; i++, array++) {\n", + " *rarray++ = (*array) * (*array);\n", + " }\n", + " } else if(ndarray->dtype == NDARRAY_UINT16) {\n", + " uint16_t *array = (uint16_t *)ndarray->array;\n", + " uint16_t *rarray = (uint16_t *)results->array;\n", + " for(size_t i=0; i < ndarray->len; i++, array++) {\n", + " *rarray++ = (*array) * (*array);\n", + " }\n", + " } else if(ndarray->dtype == NDARRAY_INT16) {\n", + " int16_t *array = (int16_t *)ndarray->array;\n", + " int16_t *rarray = (int16_t *)results->array;\n", + " for(size_t i=0; i < ndarray->len; i++, array++) {\n", + " *rarray++ = (*array) * (*array);\n", + " }\n", + " } else { // if we end up here, the dtype is NDARRAY_FLOAT\n", + " mp_float_t *array = (mp_float_t *)ndarray->array;\n", + " mp_float_t *rarray = (mp_float_t *)results->array;\n", + " for(size_t i=0; i < ndarray->len; i++, array++) {\n", + " *rarray++ = (*array) * (*array);\n", + " } \n", + " }\n", + " // at the end, return a micropython object\n", + " return MP_OBJ_FROM_PTR(results);\n", + "}\n", + "\n", + "```\n", + "\n", + "To summarise, the steps for *implementing* a function are\n", + "\n", + "1. If necessary, inspect the type of the input object, which is always a `mp_obj_t` object\n", + "2. If the input is an `ndarray_obj_t`, acquire a pointer to it by calling `ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(arg);`\n", + "3. Create a new array, or modify the existing one; get a pointer to the data by calling `uint8_t *array = (uint8_t *)ndarray->array;`, or something equivalent\n", + "4. Once the new data have been calculated, return a `micropython` object by calling `MP_OBJ_FROM_PTR(...)`.\n", + "\n", + "The listing above contains the implementation of the function, but as such, it cannot be called from `python`: \n", + "it still has to be bound to the name space. This we do by first defining a function object in \n", + "\n", + "```c\n", + "MP_DEFINE_CONST_FUN_OBJ_1(user_square_obj, user_square);\n", + "\n", + "```\n", + "\n", + "`micropython` defines a number of `MP_DEFINE_CONST_FUN_OBJ_N` macros in [obj.h](https://github.com/micropython/micropython/blob/master/py/obj.h). `N` is always the number of arguments the function takes. We had a function definition `static mp_obj_t user_square(mp_obj_t arg)`, i.e., we dealt with a single argument. \n", + "\n", + "Finally, we have to bind this function object in the globals table of the `user` module: \n", + "\n", + "```c\n", + "STATIC const mp_rom_map_elem_t ulab_user_globals_table[] = {\n", + " { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_user) },\n", + " { MP_OBJ_NEW_QSTR(MP_QSTR_square), (mp_obj_t)&user_square_obj },\n", + "};\n", + "```\n", + "\n", + "Thus, the three steps required for the definition of a user-defined function are \n", + "\n", + "1. The low-level implementation of the function itself\n", + "2. The definition of a function object by calling MP_DEFINE_CONST_FUN_OBJ_N()\n", + "3. Binding this function object to the namespace in the `ulab_user_globals_table[]`" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": {}, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/ulab-tricks.ipynb b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-tricks.ipynb new file mode 100644 index 00000000..ec67c8c8 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-tricks.ipynb @@ -0,0 +1,582 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-01T09:27:13.438054Z", + "start_time": "2020-05-01T09:27:13.191491Z" + } + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Notebook magic" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2020-08-03T18:32:45.342280Z", + "start_time": "2020-08-03T18:32:45.338442Z" + } + }, + "outputs": [], + "source": [ + "from IPython.core.magic import Magics, magics_class, line_cell_magic\n", + "from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n", + "from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n", + "import subprocess\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "ExecuteTime": { + "end_time": "2020-07-23T20:31:25.296014Z", + "start_time": "2020-07-23T20:31:25.265937Z" + } + }, + "outputs": [], + "source": [ + "@magics_class\n", + "class PyboardMagic(Magics):\n", + " @cell_magic\n", + " @magic_arguments()\n", + " @argument('-skip')\n", + " @argument('-unix')\n", + " @argument('-pyboard')\n", + " @argument('-file')\n", + " @argument('-data')\n", + " @argument('-time')\n", + " @argument('-memory')\n", + " def micropython(self, line='', cell=None):\n", + " args = parse_argstring(self.micropython, line)\n", + " if args.skip: # doesn't care about the cell's content\n", + " print('skipped execution')\n", + " return None # do not parse the rest\n", + " if args.unix: # tests the code on the unix port. Note that this works on unix only\n", + " with open('/dev/shm/micropython.py', 'w') as fout:\n", + " fout.write(cell)\n", + " proc = subprocess.Popen([\"../../micropython/ports/unix/micropython\", \"/dev/shm/micropython.py\"], \n", + " stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " print(proc.stdout.read().decode(\"utf-8\"))\n", + " print(proc.stderr.read().decode(\"utf-8\"))\n", + " return None\n", + " if args.file: # can be used to copy the cell content onto the pyboard's flash\n", + " spaces = \" \"\n", + " try:\n", + " with open(args.file, 'w') as fout:\n", + " fout.write(cell.replace('\\t', spaces))\n", + " printf('written cell to {}'.format(args.file))\n", + " except:\n", + " print('Failed to write to disc!')\n", + " return None # do not parse the rest\n", + " if args.data: # can be used to load data from the pyboard directly into kernel space\n", + " message = pyb.exec(cell)\n", + " if len(message) == 0:\n", + " print('pyboard >>>')\n", + " else:\n", + " print(message.decode('utf-8'))\n", + " # register new variable in user namespace\n", + " self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n", + " \n", + " if args.time: # measures the time of executions\n", + " pyb.exec('import utime')\n", + " message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n", + " \"\\nprint('execution time: {:d} us'.format(delta))\")\n", + " print(message.decode('utf-8'))\n", + " \n", + " if args.memory: # prints out memory information \n", + " message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n", + " print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n", + " message = pyb.exec(cell)\n", + " print(\">>> \", message.decode('utf-8'))\n", + " message = pyb.exec('print(mem_info())')\n", + " print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n", + "\n", + " if args.pyboard:\n", + " message = pyb.exec(cell)\n", + " print(message.decode('utf-8'))\n", + "\n", + "ip = get_ipython()\n", + "ip.register_magics(PyboardMagic)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pyboard" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:35.126401Z", + "start_time": "2020-05-07T07:35:35.105824Z" + } + }, + "outputs": [], + "source": [ + "import pyboard\n", + "pyb = pyboard.Pyboard('/dev/ttyACM0')\n", + "pyb.enter_raw_repl()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:11:18.145548Z", + "start_time": "2020-05-19T19:11:18.137468Z" + } + }, + "outputs": [], + "source": [ + "pyb.exit_raw_repl()\n", + "pyb.close()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:38.725924Z", + "start_time": "2020-05-07T07:35:38.645488Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "import ulab as np\n", + "\n", + "def timeit(n=1000):\n", + " def wrapper(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " run_times = np.zeros(n, dtype=np.uint16)\n", + " for i in range(n):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n", + " print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n", + " print('\\tbest: %d us'%np.min(run_times))\n", + " print('\\tworst: %d us'%np.max(run_times))\n", + " print('\\taverage: %d us'%np.mean(run_times))\n", + " print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n", + " return result\n", + " return new_func\n", + " return wrapper\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__END_OF_DEFS__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Tricks\n", + "\n", + "This section of the book discusses a couple of tricks that can be exploited to either speed up computations, or save on RAM. However, there is probably no silver bullet, and you have to evaluate your code in terms of execution speed (if the execution is time critical), or RAM used. You should also keep in mind that, if a particular code snippet is optimised on some hardware, there is no guarantee that on another piece of hardware, you will get similar improvements. Hardware implementations are vastly different. Some microcontrollers do not even have an FPU, so you should not be surprised that you get significantly different benchmarks. Just to underline this statement, you can study the [collection of benchmarks](https://github.com/thiagofe/ulab_samples)." + ] + }, + { + "source": [ + "## Use an `ndarray`, if you can\n", + "\n", + "Many functions in `ulab` are implemented in a universal fashion, meaning that both generic `micropython` iterables, and `ndarray`s can be passed as an argument. E.g., both \n", + "\n", + "```python\n", + "from ulab import numpy as np\n", + "\n", + "np.sum([1, 2, 3, 4, 5])\n", + "```\n", + "and\n", + "\n", + "```python\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4, 5])\n", + "np.sum(a)\n", + "```\n", + "\n", + "will return the `micropython` variable 15 as the result. Still, `np.sum(a)` is evaluated significantly faster, because in `np.sum([1, 2, 3, 4, 5])`, the interpreter has to fetch 5 `micropython` variables, convert them to `float`, and sum the values, while the C type of `a` is known, thus the interpreter can invoke a single `for` loop for the evaluation of the `sum`. In the `for` loop, there are no function calls, the iteration simply walks through the pointer holding the values of `a`, and adds the values to an accumulator. If the array `a` is already available, then you can gain a factor of 3 in speed by calling `sum` on the array, instead of using the list. Compared to the python implementation of the same functionality, the speed-up is around 40 (again, this might depend on the hardware).\n", + "\n", + "On the other hand, if the array is not available, then there is not much point in converting the list to an `ndarray` and passing that to the function. In fact, you should expect a slow-down: the constructor has to iterate over the list elements, and has to convert them to a numerical type. On top of that, it also has to reserve RAM for the `ndarray`." + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "source": [ + "## Use a reasonable `dtype`\n", + "\n", + "Just as in `numpy`, the default `dtype` is `float`. But this does not mean that that is the most suitable one in all scenarios. If data are streamed from an 8-bit ADC, and you only want to know the maximum, or the sum, then it is quite reasonable to use `uint8` for the `dtype`. Storing the same data in `float` array would cost 4 or 8 times as much RAM, with absolutely no gain. Do not rely on the default value of the constructor's keyword argument, and choose one that fits!" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "source": [ + "## Beware the axis!\n", + "\n", + "Whenever `ulab` iterates over multi-dimensional arrays, the outermost loop is the first axis, then the second axis, and so on. E.g., when the `sum` of \n", + "\n", + "```python\n", + "a = array([[1, 2, 3, 4],\n", + " [5, 6, 7, 8], \n", + " [9, 10, 11, 12]], dtype=uint8)\n", + "```\n", + "\n", + "is being calculated, first the data pointer walks along `[1, 2, 3, 4]` (innermost loop, last axis), then is moved back to the position, where 5 is stored (this is the nesting loop), and traverses `[5, 6, 7, 8]`, and so on. Moving the pointer back to 5 is more expensive, than moving it along an axis, because the position of 5 has to be calculated, whereas moving from 5 to 6 is simply an addition to the address. Thus, while the matrix\n", + "\n", + "```python\n", + "b = array([[1, 5, 9],\n", + " [2, 6, 10], \n", + " [3, 7, 11],\n", + " [4, 8, 12]], dtype=uint8)\n", + "```\n", + "\n", + "holds the same data as `a`, the summation over the entries in `b` is slower, because the pointer has to be re-wound three times, as opposed to twice in `a`. For small matrices the savings are not significant, but you would definitely notice the difference, if you had \n", + "\n", + "```\n", + "a = array(range(2000)).reshape((2, 1000))\n", + "b = array(range(2000)).reshape((1000, 2))\n", + "```\n", + "\n", + "The moral is that, in order to improve on the execution speed, whenever possible, you should try to make the last axis the longest. As a side note, `numpy` can re-arrange its loops, and puts the longest axis in the innermost loop. This is why the longest axis is sometimes referred to as the fast axis. In `ulab`, the order of the axes is fixed. " + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "source": [ + "## Reduce the number of artifacts\n", + "\n", + "Before showing a real-life example, let us suppose that we want to interpolate uniformly sampled data, and the absolute magnitude is not really important, we only care about the ratios between neighbouring value. One way of achieving this is calling the `interp` functions. However, we could just as well work with slices." + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "array([ 0, 5, 10, 6, 2, 11, 20, 12, 4], dtype=uint8)" + ] + }, + "metadata": {}, + "execution_count": 18 + } + ], + "source": [ + "a = array([0, 10, 2, 20, 4], dtype=np.uint8)\n", + "b = np.zeros(9, dtype=np.uint8)\n", + "\n", + "b[::2] = 2 * a\n", + "b[1::2] = a[:-1] + a[1:]\n", + "\n", + "b //= 2\n", + "b" + ] + }, + { + "source": [ + "`b` now has values from `a` at every even position, and interpolates the values on every odd position. If only the relative magnitudes are important, then we can even save the division by 2, and we end up with " + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "source": [ + "a = array([0, 10, 2, 20, 4], dtype=np.uint8)\n", + "b = np.zeros(9, dtype=np.uint8)\n", + "\n", + "b[::2] = 2 * a\n", + "b[1::2] = a[:-1] + a[1:]\n", + "\n", + "b" + ], + "cell_type": "code", + "metadata": {}, + "execution_count": 16, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "array([ 0, 10, 20, 12, 4, 22, 40, 24, 8], dtype=uint8)" + ] + }, + "metadata": {}, + "execution_count": 16 + } + ] + }, + { + "source": [ + "Importantly, we managed to keep the results in the smaller `dtype`, `uint8`. Now, while the two assignments above are terse and pythonic, the code is not the most efficient: the right hand sides are compound statements, generating intermediate results. To store them, RAM has to be allocated. This takes time, and leads to memory fragmentation. Better is to write out the assignments in 4 instructions:" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "array([ 0, 10, 20, 12, 4, 22, 40, 24, 8], dtype=uint8)" + ] + }, + "metadata": {}, + "execution_count": 15 + } + ], + "source": [ + "b = np.zeros(9, dtype=np.uint8)\n", + "\n", + "b[::2] = a\n", + "b[::2] += a\n", + "b[1::2] = a[:-1]\n", + "b[1::2] += a[1:]\n", + "\n", + "b" + ] + }, + { + "source": [ + "The results are the same, but no extra RAM is allocated, except for the views `a[:-1]`, and `a[1:]`, but those had to be created even in the origin implementation." + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "source": [ + "### Upscaling images\n", + "\n", + "And now the example: there are low-resolution thermal cameras out there. Low resolution might mean 8 by 8 pixels. Such a small number of pixels is just not reasonable to plot, no matter how small the display is. If you want to make the camera image a bit more pleasing, you can upscale (stretch) it in both dimensions. This can be done exactly as we up-scaled the linear array:" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "b = np.zeros((15, 15), dtype=np.uint8)\n", + "\n", + "b[1::2,::2] = a[:-1,:]\n", + "b[1::2,::2] += a[1:, :]\n", + "b[1::2,::2] //= 2\n", + "b[::,1::2] = a[::,:-1:2]\n", + "b[::,1::2] += a[::,2::2]\n", + "b[::,1::2] //= 2" + ] + }, + { + "source": [ + "Up-scaling by larger numbers can be done in a similar fashion, you simply have more assignments." + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "source": [ + "There are cases, when one cannot do away with the intermediate results. Two prominent cases are the `where` function, and indexing by means of a Boolean array. E.g., in" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "array([1, 2, 3])" + ] + }, + "metadata": {}, + "execution_count": 20 + } + ], + "source": [ + "a = array([1, 2, 3, 4, 5])\n", + "b = a[a < 4]\n", + "b" + ] + }, + { + "source": [ + "the expression `a < 4` produces the Boolean array, " + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "source": [ + "a < 4" + ], + "cell_type": "code", + "metadata": {}, + "execution_count": 22, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "array([ True, True, True, False, False])" + ] + }, + "metadata": {}, + "execution_count": 22 + } + ] + }, + { + "source": [ + "If you repeatedly have such conditions in a loop, you might have to peridically call the garbage collector to remove the Boolean arrays that are used only once." + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/docs/ulab-utils.ipynb b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-utils.ipynb new file mode 100644 index 00000000..3c5e5c66 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/docs/ulab-utils.ipynb @@ -0,0 +1,562 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2021-03-04T18:21:22.822563Z", + "start_time": "2021-03-04T18:21:18.656643Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Notebook magic" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-29T16:53:11.972661Z", + "start_time": "2022-01-29T16:53:11.965952Z" + } + }, + "outputs": [], + "source": [ + "from IPython.core.magic import Magics, magics_class, line_cell_magic\n", + "from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n", + "from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n", + "import subprocess\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-29T16:59:24.652277Z", + "start_time": "2022-01-29T16:59:24.639828Z" + } + }, + "outputs": [], + "source": [ + "@magics_class\n", + "class PyboardMagic(Magics):\n", + " @cell_magic\n", + " @magic_arguments()\n", + " @argument('-skip')\n", + " @argument('-unix')\n", + " @argument('-pyboard')\n", + " @argument('-file')\n", + " @argument('-data')\n", + " @argument('-time')\n", + " @argument('-memory')\n", + " def micropython(self, line='', cell=None):\n", + " args = parse_argstring(self.micropython, line)\n", + " if args.skip: # doesn't care about the cell's content\n", + " print('skipped execution')\n", + " return None # do not parse the rest\n", + " if args.unix: # tests the code on the unix port. Note that this works on unix only\n", + " with open('/dev/shm/micropython.py', 'w') as fout:\n", + " fout.write(cell)\n", + " proc = subprocess.Popen([\"../micropython/ports/unix/micropython-2\", \"/dev/shm/micropython.py\"], \n", + " stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " print(proc.stdout.read().decode(\"utf-8\"))\n", + " print(proc.stderr.read().decode(\"utf-8\"))\n", + " return None\n", + " if args.file: # can be used to copy the cell content onto the pyboard's flash\n", + " spaces = \" \"\n", + " try:\n", + " with open(args.file, 'w') as fout:\n", + " fout.write(cell.replace('\\t', spaces))\n", + " printf('written cell to {}'.format(args.file))\n", + " except:\n", + " print('Failed to write to disc!')\n", + " return None # do not parse the rest\n", + " if args.data: # can be used to load data from the pyboard directly into kernel space\n", + " message = pyb.exec(cell)\n", + " if len(message) == 0:\n", + " print('pyboard >>>')\n", + " else:\n", + " print(message.decode('utf-8'))\n", + " # register new variable in user namespace\n", + " self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n", + " \n", + " if args.time: # measures the time of executions\n", + " pyb.exec('import utime')\n", + " message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n", + " \"\\nprint('execution time: {:d} us'.format(delta))\")\n", + " print(message.decode('utf-8'))\n", + " \n", + " if args.memory: # prints out memory information \n", + " message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n", + " print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n", + " message = pyb.exec(cell)\n", + " print(\">>> \", message.decode('utf-8'))\n", + " message = pyb.exec('print(mem_info())')\n", + " print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n", + "\n", + " if args.pyboard:\n", + " message = pyb.exec(cell)\n", + " print(message.decode('utf-8'))\n", + "\n", + "ip = get_ipython()\n", + "ip.register_magics(PyboardMagic)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pyboard" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:35.126401Z", + "start_time": "2020-05-07T07:35:35.105824Z" + } + }, + "outputs": [], + "source": [ + "import pyboard\n", + "pyb = pyboard.Pyboard('/dev/ttyACM0')\n", + "pyb.enter_raw_repl()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:11:18.145548Z", + "start_time": "2020-05-19T19:11:18.137468Z" + } + }, + "outputs": [], + "source": [ + "pyb.exit_raw_repl()\n", + "pyb.close()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:38.725924Z", + "start_time": "2020-05-07T07:35:38.645488Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "import ulab as np\n", + "\n", + "def timeit(n=1000):\n", + " def wrapper(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " run_times = np.zeros(n, dtype=np.uint16)\n", + " for i in range(n):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n", + " print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n", + " print('\\tbest: %d us'%np.min(run_times))\n", + " print('\\tworst: %d us'%np.max(run_times))\n", + " print('\\taverage: %d us'%np.mean(run_times))\n", + " print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n", + " return result\n", + " return new_func\n", + " return wrapper\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__END_OF_DEFS__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# ulab utilities\n", + "\n", + "\n", + "There might be cases, when the format of your data does not conform to `ulab`, i.e., there is no obvious way to map the data to any of the five supported `dtype`s. A trivial example is an ADC or microphone signal with 32-bit resolution. For such cases, `ulab` defines the `utils` module, which, at the moment, has four functions that are not `numpy` compatible, but which should ease interfacing `ndarray`s to peripheral devices. \n", + "\n", + "The `utils` module can be enabled by setting the `ULAB_HAS_UTILS_MODULE` constant to 1 in [ulab.h](https://github.com/v923z/micropython-ulab/blob/master/code/ulab.h):\n", + "\n", + "```c\n", + "#ifndef ULAB_HAS_UTILS_MODULE\n", + "#define ULAB_HAS_UTILS_MODULE (1)\n", + "#endif\n", + "```\n", + "\n", + "This still does not compile any functions into the firmware. You can add a function by setting the corresponding pre-processor constant to 1. E.g., \n", + "\n", + "```c\n", + "#ifndef ULAB_UTILS_HAS_FROM_INT16_BUFFER\n", + "#define ULAB_UTILS_HAS_FROM_INT16_BUFFER (1)\n", + "#endif\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## from_int32_buffer, from_uint32_buffer\n", + "\n", + "With the help of `utils.from_int32_buffer`, and `utils.from_uint32_buffer`, it is possible to convert 32-bit integer buffers to `ndarrays` of float type. These functions have a syntax similar to `numpy.frombuffer`; they support the `count=-1`, and `offset=0` keyword arguments. However, in addition, they also accept `out=None`, and `byteswap=False`. \n", + "\n", + "Here is an example without keyword arguments" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "ExecuteTime": { + "end_time": "2021-03-05T06:53:26.256516Z", + "start_time": "2021-03-05T06:53:26.007070Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: bytearray(b'\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\xff')\n", + "\n", + "unsigned integers: array([257.0, 4278190080.000001], dtype=float64)\n", + "\n", + "b: bytearray(b'\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\xff')\n", + "\n", + "signed integers: array([257.0, -16777216.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "from ulab import utils\n", + "\n", + "a = bytearray([1, 1, 0, 0, 0, 0, 0, 255])\n", + "print('a: ', a)\n", + "print()\n", + "print('unsigned integers: ', utils.from_uint32_buffer(a))\n", + "\n", + "b = bytearray([1, 1, 0, 0, 0, 0, 0, 255])\n", + "print('\\nb: ', b)\n", + "print()\n", + "print('signed integers: ', utils.from_int32_buffer(b))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The meaning of `count`, and `offset` is similar to that in `numpy.frombuffer`. `count` is the number of floats that will be converted, while `offset` would discard the first `offset` number of bytes from the buffer before the conversion.\n", + "\n", + "In the example above, repeated calls to either of the functions returns a new `ndarray`. You can save RAM by supplying the `out` keyword argument with a pre-defined `ndarray` of sufficient size, in which case the results will be inserted into the `ndarray`. If the `dtype` of `out` is not `float`, a `TypeError` exception will be raised." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "ExecuteTime": { + "end_time": "2021-03-05T06:53:41.551440Z", + "start_time": "2021-03-05T06:53:41.534163Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "b: bytearray(b'\\x01\\x00\\x01\\x00\\x00\\x01\\x00\\x01')\n", + "a: array([65537.0, 16777472.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "from ulab import utils\n", + "\n", + "a = np.array([1, 2], dtype=np.float)\n", + "b = bytearray([1, 0, 1, 0, 0, 1, 0, 1])\n", + "print('b: ', b)\n", + "utils.from_uint32_buffer(b, out=a)\n", + "print('a: ', a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, since there is no guarantee that the endianness of a particular peripheral device supplying the buffer is the same as that of the microcontroller, `from_(u)intbuffer` allows a conversion via the `byteswap` keyword argument." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "ExecuteTime": { + "end_time": "2021-03-05T06:53:52.242950Z", + "start_time": "2021-03-05T06:53:52.229160Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: bytearray(b'\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x01')\n", + "buffer without byteswapping: array([1.0, 16777216.0], dtype=float64)\n", + "buffer with byteswapping: array([16777216.0, 1.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "from ulab import utils\n", + "\n", + "a = bytearray([1, 0, 0, 0, 0, 0, 0, 1])\n", + "print('a: ', a)\n", + "print('buffer without byteswapping: ', utils.from_uint32_buffer(a))\n", + "print('buffer with byteswapping: ', utils.from_uint32_buffer(a, byteswap=True))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## from_int16_buffer, from_uint16_buffer\n", + "\n", + "These two functions are identical to `utils.from_int32_buffer`, and `utils.from_uint32_buffer`, with the exception that they convert 16-bit integers to floating point `ndarray`s. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## spectrogram\n", + "\n", + "In addition to the Fourier transform and its inverse, `ulab` also sports a function called `spectrogram`, which returns the absolute value of the Fourier transform, also known as the power spectrum. This could be used to find the dominant spectral component in a time series. The arguments are treated in the same way as in `fft`, and `ifft`. This means that, if the firmware was compiled with complex support, the input can also be a complex array." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-29T16:59:56.400603Z", + "start_time": "2022-01-29T16:59:56.374748Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "original vector:\n", + " array([0.0, 0.009775015390171337, 0.01954909674625918, ..., -0.5275140569487312, -0.5357931822978732, -0.5440211108893697], dtype=float64)\n", + "\n", + "spectrum:\n", + " array([187.8635087634579, 315.3112063607119, 347.8814873399374, ..., 84.45888934298905, 347.8814873399374, 315.3112063607118], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "from ulab import utils as utils\n", + "\n", + "x = np.linspace(0, 10, num=1024)\n", + "y = np.sin(x)\n", + "\n", + "a = utils.spectrogram(y)\n", + "\n", + "print('original vector:\\n', y)\n", + "print('\\nspectrum:\\n', a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As such, `spectrogram` is really just a shorthand for `np.sqrt(a*a + b*b)`, however, it saves significant amounts of RAM: the expression `a*a + b*b` has to allocate memory for `a*a`, `b*b`, and finally, their sum. In contrast, `spectrogram` calculates the spectrum internally, and stores it in the memory segment that was reserved for the real part of the Fourier transform." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2022-01-29T16:59:48.485610Z", + "start_time": "2022-01-29T16:59:48.462593Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "spectrum calculated the hard way:\n", + " array([187.8635087634579, 315.3112063607119, 347.8814873399374, ..., 84.45888934298905, 347.8814873399374, 315.3112063607118], dtype=float64)\n", + "\n", + "spectrum calculated the lazy way:\n", + " array([187.8635087634579, 315.3112063607119, 347.8814873399374, ..., 84.45888934298905, 347.8814873399374, 315.3112063607118], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "from ulab import utils as utils\n", + "\n", + "x = np.linspace(0, 10, num=1024)\n", + "y = np.sin(x)\n", + "\n", + "a, b = np.fft.fft(y)\n", + "\n", + "print('\\nspectrum calculated the hard way:\\n', np.sqrt(a*a + b*b))\n", + "\n", + "a = utils.spectrogram(y)\n", + "\n", + "print('\\nspectrum calculated the lazy way:\\n', a)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/components/3rd_party/omv/omv/modules/ulab/requirements.txt b/components/3rd_party/omv/omv/modules/ulab/requirements.txt new file mode 100644 index 00000000..44a988dc --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/requirements.txt @@ -0,0 +1 @@ +sphinx-autoapi diff --git a/components/3rd_party/omv/omv/modules/ulab/requirements_cp_dev.txt b/components/3rd_party/omv/omv/modules/ulab/requirements_cp_dev.txt new file mode 100644 index 00000000..06f35fbd --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/requirements_cp_dev.txt @@ -0,0 +1,19 @@ +# For docs +mypy +black +isort +astroid +setuptools +setuptools_scm + +Sphinx>=4.0.0 +sphinx-autoapi +sphinx-rtd-theme +sphinxcontrib-svg2pdfconverter +readthedocs-sphinx-search +myst-parser + +# For stubs and annotations +adafruit-circuitpython-typing + + diff --git a/components/3rd_party/omv/omv/modules/ulab/run-tests b/components/3rd_party/omv/omv/modules/ulab/run-tests new file mode 100644 index 00000000..880b13f0 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/run-tests @@ -0,0 +1,570 @@ +#! /usr/bin/env python3 + +import os +import subprocess +import sys +import platform +import argparse +import re +import threading +import multiprocessing +from multiprocessing.pool import ThreadPool +from glob import glob + +if os.name == 'nt': + MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', 'micropython/ports/windows/micropython.exe') +else: + MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', 'micropython/ports/unix/micropython') + +# mpy-cross is only needed if --via-mpy command-line arg is passed +MPYCROSS = os.getenv('MICROPY_MPYCROSS', '../mpy-cross/mpy-cross') + +# Set PYTHONIOENCODING so that CPython will use utf-8 on systems which set another encoding in the locale +os.environ['PYTHONIOENCODING'] = 'utf-8' + +def rm_f(fname): + if os.path.exists(fname): + os.remove(fname) + + +# unescape wanted regex chars and escape unwanted ones +def convert_regex_escapes(line): + cs = [] + escape = False + for c in str(line, 'utf8'): + if escape: + escape = False + cs.append(c) + elif c == '\\': + escape = True + elif c in ('(', ')', '[', ']', '{', '}', '.', '*', '+', '^', '$'): + cs.append('\\' + c) + else: + cs.append(c) + # accept carriage-return(s) before final newline + if cs[-1] == '\n': + cs[-1] = '\r*\n' + return bytes(''.join(cs), 'utf8') + + +def run_micropython(pyb, args, test_file, is_special=False): + special_tests = ( + 'micropython/meminfo.py', 'basics/bytes_compare3.py', + 'basics/builtin_help.py', 'thread/thread_exc2.py', + ) + had_crash = False + if pyb is None: + # run on PC + if test_file.startswith(('cmdline/', 'feature_check/')) or test_file in special_tests: + # special handling for tests of the unix cmdline program + is_special = True + + if is_special: + # check for any cmdline options needed for this test + args = [MICROPYTHON] + with open(test_file, 'rb') as f: + line = f.readline() + if line.startswith(b'# cmdline:'): + # subprocess.check_output on Windows only accepts strings, not bytes + args += [str(c, 'utf-8') for c in line[10:].strip().split()] + + # run the test, possibly with redirected input + try: + if 'repl_' in test_file: + # Need to use a PTY to test command line editing + try: + import pty + except ImportError: + # in case pty module is not available, like on Windows + return b'SKIP\n' + import select + + def get(required=False): + rv = b'' + while True: + ready = select.select([emulator], [], [], 0.02) + if ready[0] == [emulator]: + rv += os.read(emulator, 1024) + else: + if not required or rv: + return rv + + def send_get(what): + os.write(emulator, what) + return get() + + with open(test_file, 'rb') as f: + # instead of: output_mupy = subprocess.check_output(args, stdin=f) + # openpty returns two read/write file descriptors. The first one is + # used by the program which provides the virtual + # terminal service, and the second one is used by the + # subprogram which requires a tty to work. + emulator, subterminal = pty.openpty() + p = subprocess.Popen(args, stdin=subterminal, stdout=subterminal, + stderr=subprocess.STDOUT, bufsize=0) + banner = get(True) + output_mupy = banner + b''.join(send_get(line) for line in f) + send_get(b'\x04') # exit the REPL, so coverage info is saved + p.kill() + os.close(emulator) + os.close(subterminal) + else: + output_mupy = subprocess.check_output(args + [test_file], stderr=subprocess.STDOUT) + except subprocess.CalledProcessError: + return b'CRASH' + + else: + # a standard test run on PC + + # create system command + cmdlist = [MICROPYTHON, '-X', 'emit=' + args.emit] + if args.heapsize is not None: + cmdlist.extend(['-X', 'heapsize=' + args.heapsize]) + + # if running via .mpy, first compile the .py file + if args.via_mpy: + subprocess.check_output([MPYCROSS, '-mcache-lookup-bc', '-o', 'mpytest.mpy', test_file]) + cmdlist.extend(['-m', 'mpytest']) + else: + cmdlist.append(test_file) + + # run the actual test + e = {"MICROPYPATH": os.getcwd() + ":", "LANG": "en_US.UTF-8"} + p = subprocess.Popen(cmdlist, env=e, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + output_mupy = b'' + while p.poll() is None: + output_mupy += p.stdout.read() + output_mupy += p.stdout.read() + if p.returncode != 0: + output_mupy = b'CRASH' + + # clean up if we had an intermediate .mpy file + if args.via_mpy: + rm_f('mpytest.mpy') + + else: + # run on pyboard + import pyboard + pyb.enter_raw_repl() + try: + output_mupy = pyb.execfile(test_file) + except pyboard.PyboardError: + had_crash = True + output_mupy = b'CRASH' + + # canonical form for all ports/platforms is to use \n for end-of-line + output_mupy = output_mupy.replace(b'\r\n', b'\n') + + # don't try to convert the output if we should skip this test + if had_crash or output_mupy in (b'SKIP\n', b'CRASH'): + return output_mupy + + if is_special or test_file in special_tests: + # convert parts of the output that are not stable across runs + with open(test_file + '.exp', 'rb') as f: + lines_exp = [] + for line in f.readlines(): + if line == b'########\n': + line = (line,) + else: + line = (line, re.compile(convert_regex_escapes(line))) + lines_exp.append(line) + lines_mupy = [line + b'\n' for line in output_mupy.split(b'\n')] + if output_mupy.endswith(b'\n'): + lines_mupy = lines_mupy[:-1] # remove erroneous last empty line + i_mupy = 0 + for i in range(len(lines_exp)): + if lines_exp[i][0] == b'########\n': + # 8x #'s means match 0 or more whole lines + line_exp = lines_exp[i + 1] + skip = 0 + while i_mupy + skip < len(lines_mupy) and not line_exp[1].match(lines_mupy[i_mupy + skip]): + skip += 1 + if i_mupy + skip >= len(lines_mupy): + lines_mupy[i_mupy] = b'######## FAIL\n' + break + del lines_mupy[i_mupy:i_mupy + skip] + lines_mupy.insert(i_mupy, b'########\n') + i_mupy += 1 + else: + # a regex + if lines_exp[i][1].match(lines_mupy[i_mupy]): + lines_mupy[i_mupy] = lines_exp[i][0] + else: + #print("don't match: %r %s" % (lines_exp[i][1], lines_mupy[i_mupy])) # DEBUG + pass + i_mupy += 1 + if i_mupy >= len(lines_mupy): + break + output_mupy = b''.join(lines_mupy) + + return output_mupy + + +def run_feature_check(pyb, args, base_path, test_file): + return run_micropython(pyb, args, base_path + "/feature_check/" + test_file, is_special=True) + +class ThreadSafeCounter: + def __init__(self, start=0): + self._value = start + self._lock = threading.Lock() + + def add(self, to_add): + with self._lock: self._value += to_add + + def append(self, arg): + self.add([arg]) + + @property + def value(self): + return self._value + +def run_tests(pyb, tests, args, base_path=".", num_threads=1): + test_count = ThreadSafeCounter() + testcase_count = ThreadSafeCounter() + passed_count = ThreadSafeCounter() + failed_tests = ThreadSafeCounter([]) + skipped_tests = ThreadSafeCounter([]) + + skip_tests = set() + skip_native = False + skip_int_big = False + skip_set_type = False + skip_async = False + skip_const = False + skip_revops = False + skip_endian = False + has_complex = True + has_coverage = False + + upy_float_precision = 32 + + # Some tests shouldn't be run under Travis CI + if os.getenv('TRAVIS') == 'true': + skip_tests.add('basics/memoryerror.py') + skip_tests.add('thread/thread_gc1.py') # has reliability issues + skip_tests.add('thread/thread_lock4.py') # has reliability issues + skip_tests.add('thread/stress_heap.py') # has reliability issues + skip_tests.add('thread/stress_recurse.py') # has reliability issues + + if upy_float_precision == 0: + skip_tests.add('extmod/ujson_dumps_float.py') + skip_tests.add('extmod/ujson_loads_float.py') + skip_tests.add('misc/rge_sm.py') + if upy_float_precision < 32: + skip_tests.add('float/float2int_intbig.py') # requires fp32, there's float2int_fp30_intbig.py instead + skip_tests.add('float/string_format.py') # requires fp32, there's string_format_fp30.py instead + skip_tests.add('float/bytes_construct.py') # requires fp32 + skip_tests.add('float/bytearray_construct.py') # requires fp32 + if upy_float_precision < 64: + skip_tests.add('float/float_divmod.py') # tested by float/float_divmod_relaxed.py instead + skip_tests.add('float/float2int_doubleprec_intbig.py') + skip_tests.add('float/float_parse_doubleprec.py') + + if not has_complex: + skip_tests.add('float/complex1.py') + skip_tests.add('float/complex1_intbig.py') + skip_tests.add('float/int_big_float.py') + skip_tests.add('float/true_value.py') + skip_tests.add('float/types.py') + + if not has_coverage: + skip_tests.add('cmdline/cmd_parsetree.py') + + # Some tests shouldn't be run on a PC + if args.target == 'unix': + # unix build does not have the GIL so can't run thread mutation tests + for t in tests: + if t.startswith('thread/mutate_'): + skip_tests.add(t) + + # Some tests shouldn't be run on pyboard + if args.target != 'unix': + skip_tests.add('basics/exception_chain.py') # warning is not printed + skip_tests.add('micropython/meminfo.py') # output is very different to PC output + skip_tests.add('extmod/machine_mem.py') # raw memory access not supported + + if args.target == 'wipy': + skip_tests.add('misc/print_exception.py') # requires error reporting full + skip_tests.update({'extmod/uctypes_%s.py' % t for t in 'bytearray le native_le ptr_le ptr_native_le sizeof sizeof_native array_assign_le array_assign_native_le'.split()}) # requires uctypes + skip_tests.add('extmod/zlibd_decompress.py') # requires zlib + skip_tests.add('extmod/uheapq1.py') # uheapq not supported by WiPy + skip_tests.add('extmod/urandom_basic.py') # requires urandom + skip_tests.add('extmod/urandom_extra.py') # requires urandom + elif args.target == 'esp8266': + skip_tests.add('misc/rge_sm.py') # too large + elif args.target == 'minimal': + skip_tests.add('basics/class_inplace_op.py') # all special methods not supported + skip_tests.add('basics/subclass_native_init.py')# native subclassing corner cases not support + skip_tests.add('misc/rge_sm.py') # too large + skip_tests.add('micropython/opt_level.py') # don't assume line numbers are stored + + # Some tests are known to fail on 64-bit machines + if pyb is None and platform.architecture()[0] == '64bit': + pass + + # Some tests use unsupported features on Windows + if os.name == 'nt': + skip_tests.add('import/import_file.py') # works but CPython prints forward slashes + + # Some tests are known to fail with native emitter + # Remove them from the below when they work + if args.emit == 'native': + skip_tests.update({'basics/%s.py' % t for t in 'gen_yield_from gen_yield_from_close gen_yield_from_ducktype gen_yield_from_exc gen_yield_from_executing gen_yield_from_iter gen_yield_from_send gen_yield_from_stopped gen_yield_from_throw gen_yield_from_throw2 gen_yield_from_throw3 generator1 generator2 generator_args generator_close generator_closure generator_exc generator_pend_throw generator_return generator_send'.split()}) # require yield + skip_tests.update({'basics/%s.py' % t for t in 'bytes_gen class_store_class globals_del string_join gen_stack_overflow'.split()}) # require yield + skip_tests.update({'basics/async_%s.py' % t for t in 'def await await2 for for2 with with2 coroutine'.split()}) # require yield + skip_tests.update({'basics/%s.py' % t for t in 'try_reraise try_reraise2'.split()}) # require raise_varargs + skip_tests.update({'basics/%s.py' % t for t in 'with_break with_continue with_return'.split()}) # require complete with support + skip_tests.add('basics/array_construct2.py') # requires generators + skip_tests.add('basics/bool1.py') # seems to randomly fail + skip_tests.add('basics/builtin_hash_gen.py') # requires yield + skip_tests.add('basics/class_bind_self.py') # requires yield + skip_tests.add('basics/del_deref.py') # requires checking for unbound local + skip_tests.add('basics/del_local.py') # requires checking for unbound local + skip_tests.add('basics/exception_chain.py') # raise from is not supported + skip_tests.add('basics/for_range.py') # requires yield_value + skip_tests.add('basics/try_finally_loops.py') # requires proper try finally code + skip_tests.add('basics/try_finally_return.py') # requires proper try finally code + skip_tests.add('basics/try_finally_return2.py') # requires proper try finally code + skip_tests.add('basics/unboundlocal.py') # requires checking for unbound local + skip_tests.add('import/gen_context.py') # requires yield_value + skip_tests.add('misc/features.py') # requires raise_varargs + skip_tests.add('misc/rge_sm.py') # requires yield + skip_tests.add('misc/print_exception.py') # because native doesn't have proper traceback info + skip_tests.add('misc/sys_exc_info.py') # sys.exc_info() is not supported for native + skip_tests.add('micropython/emg_exc.py') # because native doesn't have proper traceback info + skip_tests.add('micropython/heapalloc_traceback.py') # because native doesn't have proper traceback info + skip_tests.add('micropython/heapalloc_iter.py') # requires generators + skip_tests.add('micropython/schedule.py') # native code doesn't check pending events + skip_tests.add('stress/gc_trace.py') # requires yield + skip_tests.add('stress/recursive_gen.py') # requires yield + skip_tests.add('extmod/vfs_userfs.py') # because native doesn't properly handle globals across different modules + skip_tests.add('../extmod/ulab/tests/argminmax.py') # requires yield + + def run_one_test(test_file): + test_file = test_file.replace('\\', '/') + + if args.filters: + # Default verdict is the opposit of the first action + verdict = "include" if args.filters[0][0] == "exclude" else "exclude" + for action, pat in args.filters: + if pat.search(test_file): + verdict = action + if verdict == "exclude": + return + + test_basename = os.path.basename(test_file) + test_name = os.path.splitext(test_basename)[0] + is_native = test_name.startswith("native_") or test_name.startswith("viper_") + is_endian = test_name.endswith("_endian") + is_int_big = test_name.startswith("int_big") or test_name.endswith("_intbig") + is_set_type = test_name.startswith("set_") or test_name.startswith("frozenset") + is_async = test_name.startswith("async_") + is_const = test_name.startswith("const") + + skip_it = test_file in skip_tests + skip_it |= skip_native and is_native + skip_it |= skip_endian and is_endian + skip_it |= skip_int_big and is_int_big + skip_it |= skip_set_type and is_set_type + skip_it |= skip_async and is_async + skip_it |= skip_const and is_const + skip_it |= skip_revops and test_name.startswith("class_reverse_op") + + if args.list_tests: + if not skip_it: + print(test_file) + return + + if skip_it: + print("skip ", test_file) + skipped_tests.append(test_name) + return + + # get expected output + test_file_expected = test_file + '.exp' + if os.path.isfile(test_file_expected): + # expected output given by a file, so read that in + with open(test_file_expected, 'rb') as f: + output_expected = f.read() + else: + if not args.write_exp: + output_expected = b"NOEXP\n" + else: + # run CPython to work out expected output + e = {"PYTHONPATH": os.getcwd(), + "PATH": os.environ["PATH"], + "LANG": "en_US.UTF-8"} + p = subprocess.Popen([MICROPYTHON, test_file], env=e, stdout=subprocess.PIPE) + output_expected = b'' + while p.poll() is None: + output_expected += p.stdout.read() + output_expected += p.stdout.read() + with open(test_file_expected, 'wb') as f: + f.write(output_expected) + + # canonical form for all host platforms is to use \n for end-of-line + output_expected = output_expected.replace(b'\r\n', b'\n') + + if args.write_exp: + return + + # run MicroPython + output_mupy = run_micropython(pyb, args, test_file) + + if output_mupy == b'SKIP\n': + print("skip ", test_file) + skipped_tests.append(test_name) + return + + if output_expected == b'NOEXP\n': + print("noexp", test_file) + failed_tests.append(test_name) + return + + testcase_count.add(len(output_expected.splitlines())) + + filename_expected = test_basename + ".exp" + filename_mupy = test_basename + ".out" + + if output_expected == output_mupy: + print("pass ", test_file) + passed_count.add(1) + rm_f(filename_expected) + rm_f(filename_mupy) + else: + with open(filename_expected, "wb") as f: + f.write(output_expected) + with open(filename_mupy, "wb") as f: + f.write(output_mupy) + print("### Expected") + print(output_expected) + print("### Actual") + print(output_mupy) + print("FAIL ", test_file) + failed_tests.append(test_name) + + test_count.add(1) + + if args.list_tests: + return True + + if num_threads > 1: + pool = ThreadPool(num_threads) + pool.map(run_one_test, tests) + else: + for test in tests: + run_one_test(test) + + print("{} tests performed ({} individual testcases)".format(test_count.value, testcase_count.value)) + print("{} tests passed".format(passed_count.value)) + + if len(skipped_tests.value) > 0: + print("{} tests skipped: {}".format(len(skipped_tests.value), ' '.join(sorted(skipped_tests.value)))) + if len(failed_tests.value) > 0: + print("{} tests failed: {}".format(len(failed_tests.value), ' '.join(sorted(failed_tests.value)))) + return False + + # all tests succeeded + return True + + +class append_filter(argparse.Action): + + def __init__(self, option_strings, dest, **kwargs): + super().__init__(option_strings, dest, default=[], **kwargs) + + def __call__(self, parser, args, value, option): + if not hasattr(args, self.dest): + args.filters = [] + if option.startswith(("-e", "--e")): + option = "exclude" + else: + option = "include" + args.filters.append((option, re.compile(value))) + + +def main(): + cmd_parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description='Run and manage tests for MicroPython.', + epilog='''\ +Options -i and -e can be multiple and processed in the order given. Regex +"search" (vs "match") operation is used. An action (include/exclude) of +the last matching regex is used: + run-tests -i async - exclude all, then include tests containg "async" anywhere + run-tests -e '/big.+int' - include all, then exclude by regex + run-tests -e async -i async_foo - include all, exclude async, yet still include async_foo +''') + cmd_parser.add_argument('--target', default='unix', help='the target platform') + cmd_parser.add_argument('--device', default='/dev/ttyACM0', help='the serial device or the IP address of the pyboard') + cmd_parser.add_argument('-b', '--baudrate', default=115200, help='the baud rate of the serial device') + cmd_parser.add_argument('-u', '--user', default='micro', help='the telnet login username') + cmd_parser.add_argument('-p', '--password', default='python', help='the telnet login password') + cmd_parser.add_argument('-d', '--test-dirs', nargs='*', help='input test directories (if no files given)') + cmd_parser.add_argument('-e', '--exclude', action=append_filter, metavar='REGEX', dest='filters', help='exclude test by regex on path/name.py') + cmd_parser.add_argument('-i', '--include', action=append_filter, metavar='REGEX', dest='filters', help='include test by regex on path/name.py') + cmd_parser.add_argument('--write-exp', action='store_true', help='save .exp files to run tests w/o CPython') + cmd_parser.add_argument('--list-tests', action='store_true', help='list tests instead of running them') + cmd_parser.add_argument('--emit', default='bytecode', help='MicroPython emitter to use (bytecode or native)') + cmd_parser.add_argument('--heapsize', help='heapsize to use (use default if not specified)') + cmd_parser.add_argument('--via-mpy', action='store_true', help='compile .py files to .mpy first') + cmd_parser.add_argument('--keep-path', action='store_true', help='do not clear MICROPYPATH when running tests') + cmd_parser.add_argument('-j', '--jobs', default=1, metavar='N', type=int, help='Number of tests to run simultaneously') + cmd_parser.add_argument('--auto-jobs', action='store_const', dest='jobs', const=multiprocessing.cpu_count(), help='Set the -j values to the CPU (thread) count') + cmd_parser.add_argument('files', nargs='*', help='input test files') + args = cmd_parser.parse_args() + + EXTERNAL_TARGETS = ('pyboard', 'wipy', 'esp8266', 'esp32', 'minimal') + if args.target == 'unix' or args.list_tests: + pyb = None + elif args.target in EXTERNAL_TARGETS: + import pyboard + pyb = pyboard.Pyboard(args.device, args.baudrate, args.user, args.password) + pyb.enter_raw_repl() + else: + raise ValueError('target must be either %s or unix' % ", ".join(EXTERNAL_TARGETS)) + + if len(args.files) == 0: + if args.test_dirs is None: + if args.target == 'pyboard': + # run pyboard tests + test_dirs = ('basics', 'micropython', 'float', 'misc', 'stress', 'extmod', 'pyb', 'pybnative', 'inlineasm') + elif args.target in ('esp8266', 'esp32', 'minimal'): + test_dirs = ('basics', 'micropython', 'float', 'misc', 'extmod') + elif args.target == 'wipy': + # run WiPy tests + test_dirs = ('basics', 'micropython', 'misc', 'extmod', 'wipy') + else: + # run PC tests + test_dirs = ( + 'basics', 'micropython', 'float', 'import', 'io', 'misc', + 'stress', 'unicode', 'extmod', '../extmod/ulab/tests', 'unix', 'cmdline', + ) + else: + # run tests from these directories + test_dirs = args.test_dirs + tests = sorted(test_file for test_files in (glob('{}/*.py'.format(dir)) for dir in test_dirs) for test_file in test_files) + else: + # tests explicitly given + tests = args.files + + if not args.keep_path: + # clear search path to make sure tests use only builtin modules + os.environ['MICROPYPATH'] = '' + + # Even if we run completely different tests in a different directory, + # we need to access feature_check's from the same directory as the + # run-tests script itself. + base_path = os.path.dirname(sys.argv[0]) or "." + try: + res = run_tests(pyb, tests, args, base_path, args.jobs) + finally: + if pyb: + pyb.close() + + if not res: + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/json_to_ndarray.py b/components/3rd_party/omv/omv/modules/ulab/snippets/json_to_ndarray.py new file mode 100644 index 00000000..eaeb7f83 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/json_to_ndarray.py @@ -0,0 +1,85 @@ +# This file is part of the micropython-ulab project, https://github.com/v923z/micropython-ulab +# +# The MIT License (MIT) +# +# Copyright (c) 2022 Zoltán Vörös + +import sys + +use_ulab = False + +try: + from ubinascii import a2b_base64 as b64decode + from ubinascii import unhexlify + import ujson as json + from ulab import numpy as np + use_ulab = True +except: + from base64 import b64decode + import json + import numpy as np + from numpy.lib.format import descr_to_dtype + +def ulab_descr_to_dtype(descriptor): + descriptor = descriptor[1:] + + if descriptor == 'u1': + return np.uint8 + elif descriptor == 'i1': + return np.int8 + if descriptor == 'u2': + return np.uint16 + if descriptor == 'i2': + return np.int16 + elif descriptor == 'f8': + if np.float != ord('d'): + raise TypeError('doubles are not supported') + else: + return np.float + elif descriptor == 'f16': + if np.float != ord('f'): + raise TypeError('') + else: + return np.float + else: + raise TypeError('descriptor could not be decoded') + + +def json_to_ndarray(json_string, b64=True): + """ + Turn a json string into an ndarray + The string must be the representation of a dictionary with the three keys + + - dtype: a valid numpy dtype string (one of |u1, |i1, u2, >i2, >f4, >f8, >c8, >c16) + - array: the hexified, or base64-encoded raw data array + - shape: the shape of the array (a list or tuple of integers) + + Usage: + str = '{"dtype": "u2, >i2, >f4, >f8, >c8, >c16) + - array: the hexified, or base64-encoded raw data array + - shape: the shape of the array (a list or tuple of integers) + + Usage: + ndarray = np.array([1, 2, 3], dtype=np.uint8) + ndarray_to_json(ndarray, b64=True) + """ + + if not isinstance(obj, np.ndarray): + raise TypeError('input argument must be an ndarray') + + if use_ulab: + dtype_desciptor = ulab_dtype_to_descr(obj.dtype) + else: + dtype_desciptor = dtype_to_descr(obj.dtype) + + if not b64: + data = hexlify(obj.tobytes()) + else: + data = b64encode(obj.tobytes()) + + return json.dumps({'array': data, 'dtype': dtype_desciptor, 'shape': obj.shape}) diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/__init__.py b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/__init__.py new file mode 100644 index 00000000..84779d4d --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/__init__.py @@ -0,0 +1,5 @@ + +from . import core +from .core import * +from . import lib +from .lib import * \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/core/__init__.py b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/core/__init__.py new file mode 100644 index 00000000..3a64f5a8 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/core/__init__.py @@ -0,0 +1,5 @@ + +from .multiarray import * +from .numeric import * +from .fromnumeric import * +from .shape_base import * \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/core/fromnumeric.py b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/core/fromnumeric.py new file mode 100644 index 00000000..0a078764 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/core/fromnumeric.py @@ -0,0 +1,83 @@ +# This file is part of the micropython-ulab project, https://github.com/v923z/micropython-ulab +# +# The MIT License (MIT) +# +# Copyright (c) 2022 Phil Jepsen + +from .overrides import set_module +from .multiarray import asarray +from ulab import numpy as np +from ... import numpy + +def prod(arr): + result = 1 + for x in arr: + result = result * x + return result + +def size(a, axis=None): + """ + Return the number of elements along a given axis. + Parameters + ---------- + a : array_like + Input data. + axis : int, optional + Axis along which the elements are counted. By default, give + the total number of elements. + Returns + ------- + element_count : int + Number of elements along the specified axis. + See Also + -------- + shape : dimensions of array + ndarray.shape : dimensions of array + ndarray.size : number of elements in array + Examples + -------- + >>> a = np.array([[1,2,3],[4,5,6]]) + >>> np.size(a) + 6 + >>> np.size(a,1) + 3 + >>> np.size(a,0) + 2 + """ + if axis is None: + try: + return a.size + except AttributeError: + return asarray(a).size + else: + try: + return a.shape[axis] + except AttributeError: + return asarray(a).shape[axis] + +def nonzero(a): + if not isinstance(a,(np.ndarray)): + a = asarray(a) + x = a.shape + row = x[0] + if len(x) == 1: + column = 0 + else: + column = x[1] + + nonzero_row = np.array([],dtype=np.float) + nonzero_col = np.array([],dtype=np.float) + + if column == 0: + for i in range(0,row): + if a[i] != 0: + nonzero_row = numpy.append(nonzero_row,i) + return (np.array(nonzero_row, dtype=np.int8),) + + for i in range(0,row): + for j in range(0,column): + if a[i,j] != 0: + nonzero_row = numpy.append(nonzero_row,i) + nonzero_col = numpy.append(nonzero_col,j) + + return (np.array(nonzero_row, dtype=np.int8), np.array(nonzero_col, dtype=np.int8)) \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/core/multiarray.py b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/core/multiarray.py new file mode 100644 index 00000000..0cf0606a --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/core/multiarray.py @@ -0,0 +1,27 @@ +# This file is part of the micropython-ulab project, https://github.com/v923z/micropython-ulab +# +# The MIT License (MIT) +# +# Copyright (c) 2022 Phil Jepsen + +from ulab import numpy as np + +def asarray(a, dtype=None): + if isinstance(a,(np.ndarray)): + return a + try: + if dtype is not None: + a = np.array([a], dtype=dtype) + elif isinstance(a, list) or isinstance(a, tuple): + a = np.array(a) + else: + a = np.array([a]) + return a + except Exception as e: + if "can't convert complex to float" in e.args or "'complex' object isn't iterable" in e.args: + try: + a = np.array([a], dtype=np.complex).flatten() + return a + except: + pass + raise ValueError('Could not cast %s to array' % (a)) diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/core/numeric.py b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/core/numeric.py new file mode 100644 index 00000000..71e18b69 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/core/numeric.py @@ -0,0 +1,65 @@ +# This file is part of the micropython-ulab project, https://github.com/v923z/micropython-ulab +# +# The MIT License (MIT) +# +# Copyright (c) 2022 Phil Jepsen + +from ulab import numpy as np +from .multiarray import (asarray) + +def zeros_like(a, dtype=None, order='K', subok=True, shape=None): + """ + Return an array of zeros with the same shape and type as a given array. + Parameters + ---------- + a : array_like + The shape and data-type of `a` define these same attributes of + the returned array. + dtype : data-type, optional + Overrides the data type of the result. + .. versionadded:: 1.6.0 + order : {'C', 'F', 'A', or 'K'}, optional + Overrides the memory layout of the result. 'C' means C-order, + 'F' means F-order, 'A' means 'F' if `a` is Fortran contiguous, + 'C' otherwise. 'K' means match the layout of `a` as closely + as possible. + .. versionadded:: 1.6.0 + subok : bool, optional. + If True, then the newly created array will use the sub-class + type of `a`, otherwise it will be a base-class array. Defaults + to True. + shape : int or sequence of ints, optional. + Overrides the shape of the result. If order='K' and the number of + dimensions is unchanged, will try to keep order, otherwise, + order='C' is implied. + .. versionadded:: 1.17.0 + Returns + ------- + out : ndarray + Array of zeros with the same shape and type as `a`. + See Also + -------- + empty_like : Return an empty array with shape and type of input. + ones_like : Return an array of ones with shape and type of input. + full_like : Return a new array with shape of input filled with value. + zeros : Return a new array setting values to zero. + Examples + -------- + >>> x = np.arange(6) + >>> x = x.reshape((2, 3)) + >>> x + array([[0, 1, 2], + [3, 4, 5]]) + >>> np.zeros_like(x) + array([[0, 0, 0], + [0, 0, 0]]) + >>> y = np.arange(3, dtype=float) + >>> y + array([0., 1., 2.]) + >>> np.zeros_like(y) + array([0., 0., 0.]) + """ + + res = np.full(a.shape, 0, dtype=a.dtype) + return res + diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/core/overrides.py b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/core/overrides.py new file mode 100644 index 00000000..25ebe341 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/core/overrides.py @@ -0,0 +1,22 @@ + +# This file is part of the micropython-ulab project, https://github.com/v923z/micropython-ulab +# +# The MIT License (MIT) +# +# Copyright (c) 2022 Phil Jepsen + +import sys + +def set_module(module): + """Decorator for overriding __module__ on a function or class. + Example usage:: + @set_module('numpy') + def example(): + pass + assert example.__module__ == 'numpy' + """ + def decorator(func): + if module is not None: + sys.modules[func.__globals__['__name__']] = module + return func + return decorator \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/core/shape_base.py b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/core/shape_base.py new file mode 100644 index 00000000..2ac0ebaf --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/core/shape_base.py @@ -0,0 +1,22 @@ +# This file is part of the micropython-ulab project, https://github.com/v923z/micropython-ulab +# +# The MIT License (MIT) +# +# Copyright (c) 2022 Phil Jepsen + +from ulab import numpy as np +from .multiarray import asarray + +def atleast_1d(*arys): + res = [] + for ary in arys: + ary = asarray(ary) + if not isinstance(ary,(np.ndarray)): + result = ary.reshape((1,)) + else: + result = ary + res.append(result) + if len(res) == 1: + return res[0] + else: + return res \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/lib/__init__.py b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/lib/__init__.py new file mode 100644 index 00000000..698d29fb --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/lib/__init__.py @@ -0,0 +1,5 @@ + +from .function_base import * +from .polynomial import * +from .type_check import * +from .block import * \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/lib/block.py b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/lib/block.py new file mode 100644 index 00000000..eacacc1f --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/lib/block.py @@ -0,0 +1,17 @@ +from ulab.numpy import zeros + +def block(S): + w = sum([len(m[0]) for m in S[0]]) + h = sum([len(row[0]) for row in S]) + M = zeros((h, w)) + i = 0 + j = 0 + for row in S: + di = len(row[0]) + for matrix in row: + dj = len(matrix[0]) + M[i:i + di, j:j + dj] = matrix + j += dj + i += di + j = 0 + return M \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/lib/function_base.py b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/lib/function_base.py new file mode 100644 index 00000000..66797fd6 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/lib/function_base.py @@ -0,0 +1,20 @@ +# This file is part of the micropython-ulab project, https://github.com/v923z/micropython-ulab +# +# The MIT License (MIT) +# +# Copyright (c) 2022 Phil Jepsen + +from ulab import numpy as np +from ..core.multiarray import (asarray) +from ..core.overrides import set_module + +@set_module('numpy') +def append(arr, values, axis=None): + arr = asarray(arr) + values = asarray(values) + if axis is None: + if len(arr.shape) != 1: + arr = arr.flatten() + values = values.flatten() + axis = len(arr.shape)-1 + return np.concatenate((arr, values), axis=axis) diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/lib/polynomial.py b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/lib/polynomial.py new file mode 100644 index 00000000..3fd9f4b4 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/lib/polynomial.py @@ -0,0 +1,42 @@ +# This file is part of the micropython-ulab project, https://github.com/v923z/micropython-ulab +# +# The MIT License (MIT) +# +# Copyright (c) 2022 Phil Jepsen + +from ..core import (atleast_1d, asarray) +from ..core.overrides import set_module +from ulab import numpy as np + +@set_module('numpy') +def poly(seq_of_zeros): + seq_of_zeros = atleast_1d(seq_of_zeros) + sh = seq_of_zeros.shape + + if len(sh) == 2 and sh[0] == sh[1] and sh[0] != 0: + seq_of_zeros = np.linalg.eig(seq_of_zeros) + elif len(sh) == 1: + dt = seq_of_zeros.dtype + # Let object arrays slip through, e.g. for arbitrary precision + if dt != object: + seq_of_zeros = seq_of_zeros #seq_of_zeros.astype(mintypecode(dt.char)) + else: + raise ValueError("input must be 1d or non-empty square 2d array.") + + if len(seq_of_zeros) == 0: + return 1.0 + dt = seq_of_zeros.dtype + a = np.ones((1,), dtype=dt) + + for k in range(len(seq_of_zeros)): + a = np.convolve(a, np.array([1, -seq_of_zeros[k]], dtype=dt)) + + if a.dtype == np.complex: + # if complex roots are all complex conjugates, the roots are real. + roots = asarray(seq_of_zeros, complex) + p = np.sort_complex(roots) + c = np.real(p) - np.imag(p) * 1j + q = np.sort_complex(c) + if np.all(p == q): + a = a.real.copy() + return a \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/lib/type_check.py b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/lib/type_check.py new file mode 100644 index 00000000..2f28095b --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/numpy/lib/type_check.py @@ -0,0 +1,70 @@ + +from ulab import numpy as np +from ..core.multiarray import (asarray) +from ..core.overrides import set_module + +@set_module('numpy') + +# This file is part of the micropython-ulab project, https://github.com/v923z/micropython-ulab +# +# The MIT License (MIT) +# +# Copyright (c) 2022 Phil Jepsen + +def _isreal(a): + result = [] + for x in a: + if isinstance(x, float): + result.append(True) + elif isinstance(x, complex) and x.imag == 0: + result.append(True) + else: + result.append(False) + return result + +def isreal(x): + """ + Returns a bool array, where True if input element is real. + If element has complex type with zero complex part, the return value + for that element is True. + Parameters + ---------- + x : array_like + Input array. + Returns + ------- + out : ndarray, bool + Boolean array of same shape as `x`. + Notes + ----- + `isreal` may behave unexpectedly for string or object arrays (see examples) + See Also + -------- + iscomplex + isrealobj : Return True if x is not a complex type. + Examples + -------- + >>> a = np.array([1+1j, 1+0j, 4.5, 3, 2, 2j], dtype=complex) + >>> np.isreal(a) + array([False, True, True, True, True, False]) + + The function does not work on string arrays. + >>> a = np.array([2j, "a"], dtype="U") + >>> np.isreal(a) # Warns about non-elementwise comparison + False + + Returns True for all elements in input array of ``dtype=object`` even if + any of the elements is complex. + >>> a = np.array([1, "2", 3+4j], dtype=object) + >>> np.isreal(a) + array([ True, True, True]) + + isreal should not be used with object arrays + + >>> a = np.array([1+2j, 2+1j], dtype=object) + >>> np.isreal(a) + array([ True, True]) + """ + x = asarray(x) + result = _isreal(x) + return result if len(result) > 1 else result[0] diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/rclass.py b/components/3rd_party/omv/omv/modules/ulab/snippets/rclass.py new file mode 100644 index 00000000..cb95021a --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/rclass.py @@ -0,0 +1,75 @@ +from typing import List, Tuple, Union # upip.install("pycopy-typing") +from ulab import numpy as np + +_DType = int +_RClassKeyType = Union[slice, int, float, list, tuple, np.ndarray] + +# this is a stripped down version of RClass (used by np.r_[...etc]) +# it doesn't include support for string arguments as the first index element +class RClass: + + def __getitem__(self, key: Union[_RClassKeyType, Tuple[_RClassKeyType, ...]]): + + if not isinstance(key, tuple): + key = (key,) + + objs: List[np.ndarray] = [] + scalars: List[int] = [] + arraytypes: List[_DType] = [] + scalartypes: List[_DType] = [] + + # these may get overridden in following loop + axis = 0 + + for idx, item in enumerate(key): + scalar = False + + try: + if isinstance(item, np.ndarray): + newobj = item + + elif isinstance(item, slice): + step = item.step + start = item.start + stop = item.stop + if start is None: + start = 0 + if step is None: + step = 1 + if isinstance(step, complex): + size = int(abs(step)) + newobj: np.ndarray = np.linspace(start, stop, num=size) + else: + newobj = np.arange(start, stop, step) + + # if is number + elif isinstance(item, (int, float, bool)): + newobj = np.array([item]) + scalars.append(len(objs)) + scalar = True + scalartypes.append(newobj.dtype()) + + else: + newobj = np.array(item) + + except TypeError: + raise Exception("index object %s of type %s is not supported by r_[]" % ( + str(item), type(item))) + + objs.append(newobj) + if not scalar and isinstance(newobj, np.ndarray): + arraytypes.append(newobj.dtype()) + + # Ensure that scalars won't up-cast unless warranted + final_dtype = min(arraytypes + scalartypes) + for idx, obj in enumerate(objs): + if obj.dtype != final_dtype: + objs[idx] = np.array(objs[idx], dtype=final_dtype) + + return np.concatenate(tuple(objs), axis=axis) + + # this seems weird - not sure what it's for + def __len__(self): + return 0 + +r_ = RClass() diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/scipy/__init__.py b/components/3rd_party/omv/omv/modules/ulab/snippets/scipy/__init__.py new file mode 100644 index 00000000..f2de8d89 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/scipy/__init__.py @@ -0,0 +1,3 @@ + +from . import signal +from .signal import * \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/scipy/signal/__init__.py b/components/3rd_party/omv/omv/modules/ulab/snippets/scipy/signal/__init__.py new file mode 100644 index 00000000..776f79d4 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/scipy/signal/__init__.py @@ -0,0 +1,2 @@ + +from .filter_design import * \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/scipy/signal/filter_design.py b/components/3rd_party/omv/omv/modules/ulab/snippets/scipy/signal/filter_design.py new file mode 100644 index 00000000..840b4a44 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/scipy/signal/filter_design.py @@ -0,0 +1,1479 @@ +"""Filter design.""" +import math +from ulab import numpy +from ulab import numpy as np +from ulab import scipy as spy + +from ...numpy import (atleast_1d, poly, asarray, prod, size, append, nonzero, zeros_like, isreal) + +def butter(N, Wn, btype='low', analog=False, output='ba', fs=None): + """ + Butterworth digital and analog filter design. + + Design an Nth-order digital or analog Butterworth filter and return + the filter coefficients. + + Parameters + ---------- + N : int + The order of the filter. + Wn : array_like + The critical frequency or frequencies. For lowpass and highpass + filters, Wn is a scalar; for bandpass and bandstop filters, + Wn is a length-2 sequence. + + For a Butterworth filter, this is the point at which the gain + drops to 1/sqrt(2) that of the passband (the "-3 dB point"). + + For digital filters, `Wn` are in the same units as `fs`. By default, + `fs` is 2 half-cycles/sample, so these are normalized from 0 to 1, + where 1 is the Nyquist frequency. (`Wn` is thus in + half-cycles / sample.) + + For analog filters, `Wn` is an angular frequency (e.g. rad/s). + btype : {'lowpass', 'highpass', 'bandpass', 'bandstop'}, optional + The type of filter. Default is 'lowpass'. + analog : bool, optional + When True, return an analog filter, otherwise a digital filter is + returned. + output : {'ba', 'zpk', 'sos'}, optional + Type of output: numerator/denominator ('ba'), pole-zero ('zpk'), or + second-order sections ('sos'). Default is 'ba' for backwards + compatibility, but 'sos' should be used for general-purpose filtering. + fs : float, optional + The sampling frequency of the digital system. + + .. versionadded:: 1.2.0 + + Returns + ------- + b, a : ndarray, ndarray + Numerator (`b`) and denominator (`a`) polynomials of the IIR filter. + Only returned if ``output='ba'``. + z, p, k : ndarray, ndarray, float + Zeros, poles, and system gain of the IIR filter transfer + function. Only returned if ``output='zpk'``. + sos : ndarray + Second-order sections representation of the IIR filter. + Only returned if ``output=='sos'``. + + See Also + -------- + buttord, buttap + + Notes + ----- + The Butterworth filter has maximally flat frequency response in the + passband. + + The ``'sos'`` output parameter was added in 0.16.0. + + If the transfer function form ``[b, a]`` is requested, numerical + problems can occur since the conversion between roots and + the polynomial coefficients is a numerically sensitive operation, + even for N >= 4. It is recommended to work with the SOS + representation. + + Examples + -------- + Design an analog filter and plot its frequency response, showing the + critical points: + + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + >>> b, a = signal.butter(4, 100, 'low', analog=True) + >>> w, h = signal.freqs(b, a) + >>> plt.semilogx(w, 20 * np.log10(abs(h))) + >>> plt.title('Butterworth filter frequency response') + >>> plt.xlabel('Frequency [radians / second]') + >>> plt.ylabel('Amplitude [dB]') + >>> plt.margins(0, 0.1) + >>> plt.grid(which='both', axis='both') + >>> plt.axvline(100, color='green') # cutoff frequency + >>> plt.show() + + Generate a signal made up of 10 Hz and 20 Hz, sampled at 1 kHz + + >>> t = np.linspace(0, 1, 1000, False) # 1 second + >>> sig = np.sin(2*np.pi*10*t) + np.sin(2*np.pi*20*t) + >>> fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True) + >>> ax1.plot(t, sig) + >>> ax1.set_title('10 Hz and 20 Hz sinusoids') + >>> ax1.axis([0, 1, -2, 2]) + + Design a digital high-pass filter at 15 Hz to remove the 10 Hz tone, and + apply it to the signal. (It's recommended to use second-order sections + format when filtering, to avoid numerical error with transfer function + (``ba``) format): + + >>> sos = signal.butter(10, 15, 'hp', fs=1000, output='sos') + >>> filtered = signal.sosfilt(sos, sig) + >>> ax2.plot(t, filtered) + >>> ax2.set_title('After 15 Hz high-pass filter') + >>> ax2.axis([0, 1, -2, 2]) + >>> ax2.set_xlabel('Time [seconds]') + >>> plt.tight_layout() + >>> plt.show() + """ + return iirfilter(N, Wn, btype=btype, analog=analog, + output=output, ftype='butter', fs=fs) + +def iirfilter(N, Wn, rp=None, rs=None, btype='band', analog=False, + ftype='butter', output='ba', fs=None): + """ + IIR digital and analog filter design given order and critical points. + + Design an Nth-order digital or analog filter and return the filter + coefficients. + + Parameters + ---------- + N : int + The order of the filter. + Wn : array_like + A scalar or length-2 sequence giving the critical frequencies. + + For digital filters, `Wn` are in the same units as `fs`. By default, + `fs` is 2 half-cycles/sample, so these are normalized from 0 to 1, + where 1 is the Nyquist frequency. (`Wn` is thus in + half-cycles / sample.) + + For analog filters, `Wn` is an angular frequency (e.g., rad/s). + rp : float, optional + For Chebyshev and elliptic filters, provides the maximum ripple + in the passband. (dB) + rs : float, optional + For Chebyshev and elliptic filters, provides the minimum attenuation + in the stop band. (dB) + btype : {'bandpass', 'lowpass', 'highpass', 'bandstop'}, optional + The type of filter. Default is 'bandpass'. + analog : bool, optional + When True, return an analog filter, otherwise a digital filter is + returned. + ftype : str, optional + The type of IIR filter to design: + + - Butterworth : 'butter' + - Chebyshev I : 'cheby1' + - Chebyshev II : 'cheby2' + - Cauer/elliptic: 'ellip' + - Bessel/Thomson: 'bessel' + + output : {'ba', 'zpk', 'sos'}, optional + Filter form of the output: + + - second-order sections (recommended): 'sos' + - numerator/denominator (default) : 'ba' + - pole-zero : 'zpk' + + In general the second-order sections ('sos') form is + recommended because inferring the coefficients for the + numerator/denominator form ('ba') suffers from numerical + instabilities. For reasons of backward compatibility the default + form is the numerator/denominator form ('ba'), where the 'b' + and the 'a' in 'ba' refer to the commonly used names of the + coefficients used. + + Note: Using the second-order sections form ('sos') is sometimes + associated with additional computational costs: for + data-intense use cases it is therefore recommended to also + investigate the numerator/denominator form ('ba'). + + fs : float, optional + The sampling frequency of the digital system. + + .. versionadded:: 1.2.0 + + Returns + ------- + b, a : ndarray, ndarray + Numerator (`b`) and denominator (`a`) polynomials of the IIR filter. + Only returned if ``output='ba'``. + z, p, k : ndarray, ndarray, float + Zeros, poles, and system gain of the IIR filter transfer + function. Only returned if ``output='zpk'``. + sos : ndarray + Second-order sections representation of the IIR filter. + Only returned if ``output=='sos'``. + + See Also + -------- + butter : Filter design using order and critical points + cheby1, cheby2, ellip, bessel + buttord : Find order and critical points from passband and stopband spec + cheb1ord, cheb2ord, ellipord + iirdesign : General filter design using passband and stopband spec + + Notes + ----- + The ``'sos'`` output parameter was added in 0.16.0. + + Examples + -------- + Generate a 17th-order Chebyshev II analog bandpass filter from 50 Hz to + 200 Hz and plot the frequency response: + + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + >>> b, a = signal.iirfilter(17, [2*np.pi*50, 2*np.pi*200], rs=60, + ... btype='band', analog=True, ftype='cheby2') + >>> w, h = signal.freqs(b, a, 1000) + >>> fig = plt.figure() + >>> ax = fig.add_subplot(1, 1, 1) + >>> ax.semilogx(w / (2*np.pi), 20 * np.log10(np.maximum(abs(h), 1e-5))) + >>> ax.set_title('Chebyshev Type II bandpass frequency response') + >>> ax.set_xlabel('Frequency [Hz]') + >>> ax.set_ylabel('Amplitude [dB]') + >>> ax.axis((10, 1000, -100, 10)) + >>> ax.grid(which='both', axis='both') + >>> plt.show() + + Create a digital filter with the same properties, in a system with + sampling rate of 2000 Hz, and plot the frequency response. (Second-order + sections implementation is required to ensure stability of a filter of + this order): + + >>> sos = signal.iirfilter(17, [50, 200], rs=60, btype='band', + ... analog=False, ftype='cheby2', fs=2000, + ... output='sos') + >>> w, h = signal.sosfreqz(sos, 2000, fs=2000) + >>> fig = plt.figure() + >>> ax = fig.add_subplot(1, 1, 1) + >>> ax.semilogx(w, 20 * np.log10(np.maximum(abs(h), 1e-5))) + >>> ax.set_title('Chebyshev Type II bandpass frequency response') + >>> ax.set_xlabel('Frequency [Hz]') + >>> ax.set_ylabel('Amplitude [dB]') + >>> ax.axis((10, 1000, -100, 10)) + >>> ax.grid(which='both', axis='both') + >>> plt.show() + + """ + ftype, btype, output = [x.lower() for x in (ftype, btype, output)] + Wn = asarray(Wn) + if fs is not None: + if analog: + raise ValueError("fs cannot be specified for an analog filter") + Wn = 2*Wn/fs + + try: + btype = band_dict[btype] + except KeyError as e: + raise ValueError("'%s' is an invalid bandtype for filter." % btype) from e + + try: + typefunc = filter_dict[ftype][0] + except KeyError as e: + raise ValueError("'%s' is not a valid basic IIR filter." % ftype) from e + + if output not in ['ba', 'zpk', 'sos']: + raise ValueError("'%s' is not a valid output form." % output) + + if rp is not None and rp < 0: + raise ValueError("passband ripple (rp) must be positive") + + if rs is not None and rs < 0: + raise ValueError("stopband attenuation (rs) must be positive") + + # Get analog lowpass prototype + if typefunc == buttap: + z, p, k = typefunc(N) + else: + raise NotImplementedError("'%s' not implemented in iirfilter." % ftype) + + # Pre-warp frequencies for digital filter design + if not analog: + if numpy.any(Wn <= 0) or numpy.any(Wn >= 1): + if fs is not None: + raise ValueError("Digital filter critical frequencies " + "must be 0 < Wn < fs/2 (fs={} -> fs/2={})".format(fs, fs/2)) + raise ValueError("Digital filter critical frequencies " + "must be 0 < Wn < 1") + fs = 2.0 + b = [] + for x in Wn: + b.append(2 * fs * math.tan(np.pi * x / fs)) + warped = np.array(b) + else: + warped = Wn + + # transform to lowpass, bandpass, highpass, or bandstop + if btype in ('lowpass', 'highpass'): + if size(Wn) != 1: + raise ValueError('Must specify a single critical frequency Wn for lowpass or highpass filter') + + if btype == 'lowpass': + z, p, k = lp2lp_zpk(z, p, k, wo=warped[0]) + elif btype == 'highpass': + z, p, k = lp2hp_zpk(z, p, k, wo=warped[0]) + elif btype in ('bandpass', 'bandstop'): + try: + bw = warped[1] - warped[0] + wo = math.sqrt(warped[0] * warped[1]) + except IndexError as e: + raise ValueError('Wn must specify start and stop frequencies for bandpass or bandstop ' + 'filter') from e + + if btype == 'bandpass': + z, p, k = lp2bp_zpk(z, p, k, wo=wo, bw=bw) + elif btype == 'bandstop': + z, p, k = lp2bs_zpk(z, p, k, wo=wo, bw=bw) + else: + raise NotImplementedError("'%s' not implemented in iirfilter." % btype) + # Find discrete equivalent if necessary + if not analog: + z, p, k = bilinear_zpk(z, p, k, fs=fs) + + # Transform to proper out type (pole-zero, state-space, numer-denom) + if output == 'zpk': + return z, p, k + elif output == 'ba': + return zpk2tf(z, p, k) + elif output == 'sos': + return zpk2sos(z, p, k) + +def zpk2tf(z, p, k): + """ + Return polynomial transfer function representation from zeros and poles + + Parameters + ---------- + z : array_like + Zeros of the transfer function. + p : array_like + Poles of the transfer function. + k : float + System gain. + + Returns + ------- + b : ndarray + Numerator polynomial coefficients. + a : ndarray + Denominator polynomial coefficients. + + """ + z = atleast_1d(z) + k = atleast_1d(k) + if len(z.shape) > 1: + temp = poly(z[0]) + b = np.empty((z.shape[0], z.shape[1] + 1), temp.dtype.char) + if len(k) == 1: + k = [k[0]] * z.shape[0] + for i in range(z.shape[0]): + b[i] = k[i] * poly(z[i]) + else: + b = k * poly(z) + a = atleast_1d(poly(p)) + # Use real output if possible. Copied from numpy.np.poly, since + # we can't depend on a specific version of numpy. + if b.dtype == np.complex: + # if complex roots are all complex conjugates, the roots are real. + roots = asarray(z, complex) + pos_roots = np.compress(roots.imag > 0, roots) + neg_roots = np.conjugate(np.compress(roots.imag < 0, roots)) + if len(pos_roots) > 0 and len(pos_roots) == len(neg_roots): + p = np.sort_complex(neg_roots) + q = np.sort_complex(pos_roots) + if np.all(p == q): + b = b.real.copy() + + if a.dtype == np.complex: + # if complex roots are all complex conjugates, the roots are real. + roots = asarray(p, complex) + pos_roots = np.compress(roots.imag > 0, roots) + neg_roots = np.conjugate(np.compress(roots.imag < 0, roots)) + if len(pos_roots) > 0 and len(pos_roots) == len(neg_roots): + p = np.sort_complex(neg_roots) + q = np.sort_complex(pos_roots) + if np.all(p == q): + a = a.real.copy() + + return b, a + +def _to_tuple(a): + result = [] + for x in a: + result.append([x.real, x.imag]) + return result + +def _to_complex(a): + result = np.array([], dtype=np.complex) + for x in a: + t = np.array([complex(x[0] + x[1] * 1j)], dtype=np.complex) + result = np.concatenate((result, t), axis=0) + return result + +def lexsort(z): + z = _to_tuple(z) + return sorted(range(len(z)), key=lambda i: (z[i][0],abs(z[i][1]))) + +def _lexsort(z): + z = _to_tuple(z) + z = sorted(z,key=lambda x:(x[0], abs(x[1]))) + return _to_complex(z) + +def _cplxreal(z, tol=None): + """ + Split into complex and real parts, combining conjugate pairs. + The 1-D input vector `z` is split up into its complex (`zc`) and real (`zr`) + elements. Every complex element must be part of a complex-conjugate pair, + which are combined into a single number (with positive imaginary part) in + the output. Two complex numbers are considered a conjugate pair if their + real and imaginary parts differ in magnitude by less than ``tol * abs(z)``. + Parameters + ---------- + z : array_like + Vector of complex numbers to be sorted and split + tol : float, optional + Relative tolerance for testing realness and conjugate equality. + Default is ``100 * spacing(1)`` of `z`'s data type (i.e., 2e-14 for + float64) + Returns + ------- + zc : ndarray + Complex elements of `z`, with each pair represented by a single value + having positive imaginary part, sorted first by real part, and then + by magnitude of imaginary part. The pairs are averaged when combined + to reduce error. + zr : ndarray + Real elements of `z` (those having imaginary part less than + `tol` times their magnitude), sorted by value. + Raises + ------ + ValueError + If there are any complex numbers in `z` for which a conjugate + cannot be found. + See Also + -------- + _cplxpair + Examples + -------- + >>> a = [4, 3, 1, 2-2j, 2+2j, 2-1j, 2+1j, 2-1j, 2+1j, 1+1j, 1-1j] + >>> zc, zr = _cplxreal(a) + >>> print(zc) + [ 1.+1.j 2.+1.j 2.+1.j 2.+2.j] + >>> print(zr) + [ 1. 3. 4.] + """ + + z = atleast_1d(z) + if z.size == 0: + return z, z + elif len(np.ndarray(z, dtype=np.complex).shape) != 1: + raise ValueError('_cplxreal only accepts 1-D input') + + if tol is None: + # Get tolerance from dtype of input + tol = 100 * abs(7./3 - 4./3 - 1) #np.finfo((1.0 * z).dtype).eps + + # Sort by real part, magnitude of imaginary part (speed up further sorting) + z = _lexsort(z) + # Split reals from conjugate pairs + real_indices = abs(z.imag) <= tol * abs(z) + zr = z[real_indices].real + + if len(zr) == len(z): + # Input is entirely real + return np.array([],dtype=np.float), zr + + # Split positive and negative halves of conjugates + inv_real_indices = np.array([not elem for elem in real_indices], dtype=np.bool) + z = z[inv_real_indices] + zp = z[z.imag > 0] + zn = z[z.imag < 0] + + if len(zp) != len(zn): + raise ValueError('Array contains complex value with no matching ' + 'conjugate.') + + # Find runs of (approximately) the same real part + same_real = np.diff(zp.real) <= tol * abs(zp[:-1]) + same_real = np.array(same_real * 1, dtype=np.uint8) + a = np.array([0], dtype=np.uint8) + b = np.array([0], dtype=np.uint8) + x = np.concatenate((a, same_real, b)) + diffs = numpy.diff(np.array(x, dtype=np.float)) + start = np.array((diffs > 0) * 1, dtype=np.uint16) + stop = np.array((diffs < 0) * 1, dtype=np.uint16) + run_starts = nonzero(start)[0] + run_stops = nonzero(stop)[0] + + # Sort each run by their imaginary parts + for i in range(len(run_starts)): + start = int(run_starts[i]) + stop = int(run_stops[i]) + 1 + for chunk in (zp[start:stop], zn[start:stop]): + a = 1 + + + # Check that negatives match positives + if any(abs(zp - np.conjugate(zn)) > tol * abs(zn)): + raise ValueError('Array contains complex value with no matching ' + 'conjugate.') + + # Average out numerical inaccuracy in real vs imag parts of pairs + zc = (zp + np.conjugate(zn)) / 2 + + return zc, zr + +def zpk2sos(z, p, k, pairing='nearest'): + """ + Return second-order sections from zeros, poles, and gain of a system + + Parameters + ---------- + z : array_like + Zeros of the transfer function. + p : array_like + Poles of the transfer function. + k : float + System gain. + pairing : {'nearest', 'keep_odd'}, optional + The method to use to combine pairs of poles and zeros into sections. + See Notes below. + + Returns + ------- + sos : ndarray + Array of second-order filter coefficients, with shape + ``(n_sections, 6)``. See `sosfilt` for the SOS filter format + specification. + + See Also + -------- + sosfilt + + Notes + ----- + The algorithm used to convert ZPK to SOS format is designed to + minimize errors due to numerical precision issues. The pairing + algorithm attempts to minimize the peak gain of each biquadratic + section. This is done by pairing poles with the nearest zeros, starting + with the poles closest to the unit circle. + + *Algorithms* + + The current algorithms are designed specifically for use with digital + filters. (The output coefficients are not correct for analog filters.) + + The steps in the ``pairing='nearest'`` and ``pairing='keep_odd'`` + algorithms are mostly shared. The ``nearest`` algorithm attempts to + minimize the peak gain, while ``'keep_odd'`` minimizes peak gain under + the constraint that odd-order systems should retain one section + as first order. The algorithm steps and are as follows: + + As a pre-processing step, add poles or zeros to the origin as + necessary to obtain the same number of poles and zeros for pairing. + If ``pairing == 'nearest'`` and there are an odd number of poles, + add an additional pole and a zero at the origin. + + The following steps are then iterated over until no more poles or + zeros remain: + + 1. Take the (next remaining) pole (complex or real) closest to the + unit circle to begin a new filter section. + + 2. If the pole is real and there are no other remaining real poles [#]_, + add the closest real zero to the section and leave it as a first + order section. Note that after this step we are guaranteed to be + left with an even number of real poles, complex poles, real zeros, + and complex zeros for subsequent pairing iterations. + + 3. Else: + + 1. If the pole is complex and the zero is the only remaining real + zero*, then pair the pole with the *next* closest zero + (guaranteed to be complex). This is necessary to ensure that + there will be a real zero remaining to eventually create a + first-order section (thus keeping the odd order). + + 2. Else pair the pole with the closest remaining zero (complex or + real). + + 3. Proceed to complete the second-order section by adding another + pole and zero to the current pole and zero in the section: + + 1. If the current pole and zero are both complex, add their + conjugates. + + 2. Else if the pole is complex and the zero is real, add the + conjugate pole and the next closest real zero. + + 3. Else if the pole is real and the zero is complex, add the + conjugate zero and the real pole closest to those zeros. + + 4. Else (we must have a real pole and real zero) add the next + real pole closest to the unit circle, and then add the real + zero closest to that pole. + + .. [#] This conditional can only be met for specific odd-order inputs + with the ``pairing == 'keep_odd'`` method. + + .. versionadded:: 0.16.0 + + Examples + -------- + + Design a 6th order low-pass elliptic digital filter for a system with a + sampling rate of 8000 Hz that has a pass-band corner frequency of + 1000 Hz. The ripple in the pass-band should not exceed 0.087 dB, and + the attenuation in the stop-band should be at least 90 dB. + + In the following call to `signal.ellip`, we could use ``output='sos'``, + but for this example, we'll use ``output='zpk'``, and then convert to SOS + format with `zpk2sos`: + + >>> from scipy import signal + >>> z, p, k = signal.ellip(6, 0.087, 90, 1000/(0.5*8000), output='zpk') + + Now convert to SOS format. + + >>> sos = signal.zpk2sos(z, p, k) + + The coefficients of the numerators of the sections: + + >>> sos[:, :3] + array([[ 0.0014154 , 0.00248707, 0.0014154 ], + [ 1. , 0.72965193, 1. ], + [ 1. , 0.17594966, 1. ]]) + + The symmetry in the coefficients occurs because all the zeros are on the + unit circle. + + The coefficients of the denominators of the sections: + + >>> sos[:, 3:] + array([[ 1. , -1.32543251, 0.46989499], + [ 1. , -1.26117915, 0.6262586 ], + [ 1. , -1.25707217, 0.86199667]]) + + The next example shows the effect of the `pairing` option. We have a + system with three poles and three zeros, so the SOS array will have + shape (2, 6). The means there is, in effect, an extra pole and an extra + zero at the origin in the SOS representation. + + >>> z1 = np.array([-1, -0.5-0.5j, -0.5+0.5j]) + >>> p1 = np.array([0.75, 0.8+0.1j, 0.8-0.1j]) + + With ``pairing='nearest'`` (the default), we obtain + + >>> signal.zpk2sos(z1, p1, 1) + array([[ 1. , 1. , 0.5 , 1. , -0.75, 0. ], + [ 1. , 1. , 0. , 1. , -1.6 , 0.65]]) + + The first section has the zeros {-0.5-0.05j, -0.5+0.5j} and the poles + {0, 0.75}, and the second section has the zeros {-1, 0} and poles + {0.8+0.1j, 0.8-0.1j}. Note that the extra pole and zero at the origin + have been assigned to different sections. + + With ``pairing='keep_odd'``, we obtain: + + >>> signal.zpk2sos(z1, p1, 1, pairing='keep_odd') + array([[ 1. , 1. , 0. , 1. , -0.75, 0. ], + [ 1. , 1. , 0.5 , 1. , -1.6 , 0.65]]) + + The extra pole and zero at the origin are in the same section. + The first section is, in effect, a first-order section. + + """ + # TODO in the near future: + # 1. Add SOS capability to `filtfilt`, `freqz`, etc. somehow (#3259). + # 2. Make `decimate` use `sosfilt` instead of `lfilter`. + # 3. Make sosfilt automatically simplify sections to first order + # when possible. Note this might make `sosfiltfilt` a bit harder (ICs). + # 4. Further optimizations of the section ordering / pole-zero pairing. + # See the wiki for other potential issues. + + valid_pairings = ['nearest', 'keep_odd'] + if pairing not in valid_pairings: + raise ValueError('pairing must be one of %s, not %s' + % (valid_pairings, pairing)) + if len(z) == len(p) == 0: + return np.array([[k, 0., 0., 1., 0., 0.]]) + + # ensure we have the same number of poles and zeros, and make copies + + if len(z) != len(p): + if max(len(z) - len(p),0) > 0: + p = np.concatenate((p, np.zeros((max(len(z) - len(p), 0)),dtype=np.complex))) + if max(len(p) - len(z),0) > 0: + z = np.concatenate((z, np.zeros((max(len(p) - len(z), 0)), dtype=np.complex))) + + n_sections = (max(len(p), len(z)) + 1) // 2 + sos = np.zeros((n_sections, 6)) + + + if len(p) % 2 == 1 and pairing == 'nearest': + p = np.concatenate((p, np.array([0.],dtype=np.complex))) + z = np.concatenate((z, np.array([0.],dtype=np.complex))) + assert len(p) == len(z) + + z = np.array(z, dtype=np.complex) + + + # Ensure we have complex conjugate pairs + # (note that _cplxreal only gives us one element of each complex pair): + cplx, rel = _cplxreal(p) + if len(rel) > 0 and len(cplx) > 0: + p = np.concatenate((cplx, np.array(rel, dtype=np.complex))) + else: + p = cplx + + + cplx, rel = _cplxreal(z) + if len(rel) > 0 and len(cplx) > 0: + z = np.concatenate((cplx, np.array(rel, dtype=np.complex))) + else: + z = rel + + p_sos = np.zeros((n_sections, 2)) + z_sos = zeros_like(p_sos) + + for si in range(n_sections): + # Select the next "worst" pole + p1_idx = np.argmin(abs(1 - abs(p))) + p1 = p[p1_idx] + p = np.delete(p, p1_idx) + # Pair that pole with a zero + if isreal(p1) and np.sum([isreal(p)]) == 0: + # Special case to set a first-order section + z1_idx = _nearest_real_complex_idx(z, p1, 'real') + z1 = z[z1_idx] + z = np.delete(z, z1_idx) + p2 = z2 = 0 + else: + if not isreal(p1) and np.sum(isreal(z)) == 1: + # Special case to ensure we choose a complex zero to pair + # with so later (setting up a first-order section) + z1_idx = _nearest_real_complex_idx(z, p1, 'complex') + assert not isreal(z[z1_idx]) + else: + # Pair the pole with the closest zero (real or complex) + z1_idx = np.argmin(abs(p1 - z)) + z1 = z[z1_idx] + z = np.delete(z, z1_idx) + # Now that we have p1 and z1, figure out what p2 and z2 need to be + if not isreal(p1): + if not isreal(z1): # complex pole, complex zero + p2 = np.conjugate(p1) + z2 = np.conjugate(z1) + else: # complex pole, real zero + p2 = np.conjugate(p1) #p1.conj() + z2_idx = _nearest_real_complex_idx(z, p1, 'real') + z2 = z[z2_idx] + assert isreal(z2) + z = np.delete(z, z2_idx) + else: + if not isreal(z1): # real pole, complex zero + z2 = np.conjugate(z1) + p2_idx = _nearest_real_complex_idx(p, z1, 'real') + p2 = p[p2_idx] + assert isreal(p2) + else: # real pole, real zero + # pick the next "worst" pole to use + idx = nonzero(isreal(p))[0] + assert len(idx) > 0 + + a = abs(abs(p[idx[0]]) - 1) + a = np.array([a]) + p2_idx = idx[np.argmin(a) - 1] + p2 = p[p2_idx] + # find a real zero to match the added pole + assert isreal(p2) + z2_idx = _nearest_real_complex_idx(z, p2, 'real') + z2 = z[z2_idx] + assert isreal(z2) + z = np.delete(z, z2_idx) + + p = np.delete(p, p2_idx) + + p_sos = np.array(p_sos, dtype=np.complex) + p_sos[si] = np.array([p1, p2], dtype=np.complex) + z_sos[si] = np.array([z1, z2], dtype=np.float) + + assert len(p) == len(z) == 0 # we've consumed all poles and zeros + del p, z + + # Construct the system, reversing order so the "worst" are last + p_sos = p_sos[::-1].reshape((n_sections, 2)) + z_sos = z_sos[::-1].reshape((n_sections, 2)) + + gains = np.ones(n_sections, dtype=np.float) + gains[0] = k + for si in range(n_sections): + x = zpk2tf(z_sos[si], p_sos[si], gains[si]) + sos[si] = np.concatenate(x) + return sos + +def lp2bp_zpk(z, p, k, wo=1.0, bw=1.0): + r""" + Transform a lowpass filter prototype to a bandpass filter. + + Return an analog band-pass filter with center frequency `wo` and + bandwidth `bw` from an analog low-pass filter prototype with unity + cutoff frequency, using zeros, poles, and gain ('zpk') representation. + + Parameters + ---------- + z : array_like + Zeros of the analog filter transfer function. + p : array_like + Poles of the analog filter transfer function. + k : float + System gain of the analog filter transfer function. + wo : float + Desired passband center, as angular frequency (e.g., rad/s). + Defaults to no change. + bw : float + Desired passband width, as angular frequency (e.g., rad/s). + Defaults to 1. + + Returns + ------- + z : ndarray + Zeros of the transformed band-pass filter transfer function. + p : ndarray + Poles of the transformed band-pass filter transfer function. + k : float + System gain of the transformed band-pass filter. + + See Also + -------- + lp2lp_zpk, lp2hp_zpk, lp2bs_zpk, bilinear + lp2bp + + Notes + ----- + This is derived from the s-plane substitution + + .. math:: s \rightarrow \frac{s^2 + {\omega_0}^2}{s \cdot \mathrm{BW}} + + This is the "wideband" transformation, producing a passband with + geometric (log frequency) symmetry about `wo`. + + .. versionadded:: 1.1.0 + + """ + z = atleast_1d(z) + p = atleast_1d(p) + wo = float(wo) + bw = float(bw) + + degree = _relative_degree(z, p) + + # Scale poles and zeros to desired bandwidth + z_lp = [] + for x in z: + z_lp.append(x * bw/2) + z_lp = np.array(z_lp, dtype=np.complex) + + p_lp = [] + for x in p: + p_lp.append(x * bw/2) + p_lp = np.array(p_lp, dtype=np.complex) + + + + # Square root needs to produce complex result, not NaN + + + # Duplicate poles and zeros and shift from baseband to +wo and -wo + if len(z_lp) > 0: + z_bp = np.concatenate((z_lp + np.sqrt(z_lp*z_lp - wo*wo, dtype=np.complex), + z_lp - np.sqrt(z_lp*z_lp - wo*wo, dtype=np.complex))) + z_bp = append(z_bp, np.zeros(degree),dtype=np.complex) + else: + z_bp = np.zeros(degree, dtype=np.complex) + + p_bp = np.concatenate((p_lp + np.sqrt(p_lp*p_lp - wo*wo, dtype=np.complex), + p_lp - np.sqrt(p_lp*p_lp - wo*wo, dtype=np.complex))) + + + # Move degree zeros to origin, leaving degree zeros at infinity for BPF + + + # Cancel out gain change from frequency scaling + k_bp = k * bw**degree + + return z_bp, p_bp, k_bp + +def lp2bs_zpk(z, p, k, wo=1.0, bw=1.0): + r""" + Transform a lowpass filter prototype to a bandstop filter. + + Return an analog band-stop filter with center frequency `wo` and + stopband width `bw` from an analog low-pass filter prototype with unity + cutoff frequency, using zeros, poles, and gain ('zpk') representation. + + Parameters + ---------- + z : array_like + Zeros of the analog filter transfer function. + p : array_like + Poles of the analog filter transfer function. + k : float + System gain of the analog filter transfer function. + wo : float + Desired stopband center, as angular frequency (e.g., rad/s). + Defaults to no change. + bw : float + Desired stopband width, as angular frequency (e.g., rad/s). + Defaults to 1. + + Returns + ------- + z : ndarray + Zeros of the transformed band-stop filter transfer function. + p : ndarray + Poles of the transformed band-stop filter transfer function. + k : float + System gain of the transformed band-stop filter. + + See Also + -------- + lp2lp_zpk, lp2hp_zpk, lp2bp_zpk, bilinear + lp2bs + + Notes + ----- + This is derived from the s-plane substitution + + .. math:: s \rightarrow \frac{s \cdot \mathrm{BW}}{s^2 + {\omega_0}^2} + + This is the "wideband" transformation, producing a stopband with + geometric (log frequency) symmetry about `wo`. + + .. versionadded:: 1.1.0 + + """ + z = atleast_1d(z) + p = atleast_1d(p) + wo = float(wo) + bw = float(bw) + + degree = _relative_degree(z, p) + + # Invert to a highpass filter with desired bandwidth + z_hp = (bw/2) / z + p_hp = (bw/2) / p + + if z_hp == float('inf'): + z_hp = [] + + # Square root needs to produce complex result, not NaN + z_hp = np.array(z_hp, dtype=np.complex) + p_hp = np.array(p_hp, dtype=np.complex) + + if len(z_hp) > 0: + # Duplicate poles and zeros and shift from baseband to +wo and -wo + z_bs = np.concatenate((z_hp + np.sqrt(z_hp*z_hp - wo*wo, dtype=np.complex), + z_hp - np.sqrt(z_hp*z_hp - wo*wo, dtype=np.complex))) + else: + z_bs = np.array([], dtype=np.complex) + + p_bs = np.concatenate((p_hp + np.sqrt(p_hp*p_hp - wo*wo, dtype=np.complex), + p_hp - np.sqrt(p_hp*p_hp - wo*wo, dtype=np.complex))) + + # Move any zeros that were at infinity to the center of the stopband + z_bs = append(z_bs, np.full(degree, 1j*wo, dtype=np.complex)) + z_bs = append(z_bs, np.full(degree, -1j*wo, dtype=np.complex)) + + # Cancel out gain change caused by inversion + k_bs = k * (prod(-z) / prod(-p)).real + + return z_bs, p_bs, k_bs + +def bilinear_zpk(z, p, k, fs): + r""" + Return a digital IIR filter from an analog one using a bilinear transform. + + Transform a set of poles and zeros from the analog s-plane to the digital + z-plane using Tustin's method, which substitutes ``(z-1) / (z+1)`` for + ``s``, maintaining the shape of the frequency response. + + Parameters + ---------- + z : array_like + Zeros of the analog filter transfer function. + p : array_like + Poles of the analog filter transfer function. + k : float + System gain of the analog filter transfer function. + fs : float + Sample rate, as ordinary frequency (e.g., hertz). No prewarping is + done in this function. + + Returns + ------- + z : ndarray + Zeros of the transformed digital filter transfer function. + p : ndarray + Poles of the transformed digital filter transfer function. + k : float + System gain of the transformed digital filter. + + See Also + -------- + lp2lp_zpk, lp2hp_zpk, lp2bp_zpk, lp2bs_zpk + bilinear + + Notes + ----- + .. versionadded:: 1.1.0 + + Examples + -------- + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + >>> fs = 100 + >>> bf = 2 * np.pi * np.array([7, 13]) + >>> filts = signal.lti(*signal.butter(4, bf, btype='bandpass', analog=True, + ... output='zpk')) + >>> filtz = signal.lti(*signal.bilinear_zpk(filts.zeros, filts.poles, + ... filts.gain, fs)) + >>> wz, hz = signal.freqz_zpk(filtz.zeros, filtz.poles, filtz.gain) + >>> ws, hs = signal.freqs_zpk(filts.zeros, filts.poles, filts.gain, + ... worN=fs*wz) + >>> plt.semilogx(wz*fs/(2*np.pi), 20*np.log10(np.abs(hz).clip(1e-15)), + ... label=r'$|H_z(e^{j \omega})|$') + >>> plt.semilogx(wz*fs/(2*np.pi), 20*np.log10(np.abs(hs).clip(1e-15)), + ... label=r'$|H(j \omega)|$') + >>> plt.legend() + >>> plt.xlabel('Frequency [Hz]') + >>> plt.ylabel('Magnitude [dB]') + >>> plt.grid() + """ + + z = atleast_1d(z) + p = atleast_1d(p) + + degree = _relative_degree(z, p) + + fs2 = 2.0*fs + + # Bilinear transform the poles and zeros + z_z = (fs2 + z) / (fs2 - z) + p_z = (fs2 + p) / (fs2 - p) + + # Any zeros that were at infinity get moved to the Nyquist frequency + a = -np.ones(degree) + 0j + + if len(a) > 0: + z_z = append(z_z, a) + + # Compensate for gain change + k_z = k * (prod(fs2 - z) / prod(fs2 - p)).real + + return z_z, p_z, k_z + +def _nearest_real_complex_idx(fro, to, which): + """Get the next closest real or complex element based on distance""" + assert which in ('real', 'complex') + a = np.array(abs(fro - to), dtype=np.float) + order = np.argsort(a, axis=0) # Differs from numpy TODO + fo = [fro[i] for i in order] + sorted_array_list = [fro[i] for i in order] + mask = isreal(np.array(sorted_array_list, dtype=np.float)) + if which == 'complex': + mask = ~mask + mask = np.array([mask], dtype=np.uint16) + nzm = np.array(nonzero(mask)[0],dtype=np.int8) + return order[nzm[0]] + +def _relative_degree(z, p): + """ + Return relative degree of transfer function from zeros and poles + """ + degree = len(p) - len(z) + if degree < 0: + raise ValueError("Improper transfer function. " + "Must have at least as many poles as zeros.") + else: + return degree + +def lp2lp_zpk(z, p, k, wo=1.0): + r""" + Transform a lowpass filter prototype to a different frequency. + + Return an analog low-pass filter with cutoff frequency `wo` + from an analog low-pass filter prototype with unity cutoff frequency, + using zeros, poles, and gain ('zpk') representation. + + Parameters + ---------- + z : array_like + Zeros of the analog filter transfer function. + p : array_like + Poles of the analog filter transfer function. + k : float + System gain of the analog filter transfer function. + wo : float + Desired cutoff, as angular frequency (e.g., rad/s). + Defaults to no change. + + Returns + ------- + z : ndarray + Zeros of the transformed low-pass filter transfer function. + p : ndarray + Poles of the transformed low-pass filter transfer function. + k : float + System gain of the transformed low-pass filter. + + See Also + -------- + lp2hp_zpk, lp2bp_zpk, lp2bs_zpk, bilinear + lp2lp + + Notes + ----- + This is derived from the s-plane substitution + + .. math:: s \rightarrow \frac{s}{\omega_0} + + .. versionadded:: 1.1.0 + + """ + z = atleast_1d(z) + p = atleast_1d(p) + wo = float(wo) # Avoid int wraparound + + degree = _relative_degree(z, p) + + # Scale all points radially from origin to shift cutoff frequency + + z_lp = [] + for x in z: + z_lp.append(wo * x) + z_lp = np.array(z_lp, dtype=np.complex) + + p_lp = [] + for x in p: + p_lp.append(wo * x) + p_lp = np.array(p_lp, dtype=np.complex) + + # Each shifted pole decreases gain by wo, each shifted zero increases it. + # Cancel out the net change to keep overall gain the same + k_lp = k * wo**degree + + return z_lp, p_lp, k_lp + +def lp2hp_zpk(z, p, k, wo=1.0): + r""" + Transform a lowpass filter prototype to a highpass filter. + + Return an analog high-pass filter with cutoff frequency `wo` + from an analog low-pass filter prototype with unity cutoff frequency, + using zeros, poles, and gain ('zpk') representation. + + Parameters + ---------- + z : array_like + Zeros of the analog filter transfer function. + p : array_like + Poles of the analog filter transfer function. + k : float + System gain of the analog filter transfer function. + wo : float + Desired cutoff, as angular frequency (e.g., rad/s). + Defaults to no change. + + Returns + ------- + z : ndarray + Zeros of the transformed high-pass filter transfer function. + p : ndarray + Poles of the transformed high-pass filter transfer function. + k : float + System gain of the transformed high-pass filter. + + See Also + -------- + lp2lp_zpk, lp2bp_zpk, lp2bs_zpk, bilinear + lp2hp + + Notes + ----- + This is derived from the s-plane substitution + + .. math:: s \rightarrow \frac{\omega_0}{s} + + This maintains symmetry of the lowpass and highpass responses on a + logarithmic scale. + + .. versionadded:: 1.1.0 + + """ + z = atleast_1d(z) + p = atleast_1d(p) + + wo = float(wo) + + degree = _relative_degree(z, p) + + # Invert positions radially about unit circle to convert LPF to HPF + # Scale all points radially from origin to shift cutoff frequency + z_hp = wo / z + p_hp = wo / p + + if z_hp == float('inf'): + z_hp = [] + + # If lowpass had zeros at infinity, inverting moves them to origin. + z_hp = append(z_hp, np.zeros(degree)) + + # Cancel out gain change caused by inversion + k_hp = k * (prod(-z) / prod(-p)).real + + return z_hp, p_hp, k_hp + +def buttord(wp, ws, gpass, gstop, analog=False, fs=None): + """Butterworth filter order selection. + + Return the order of the lowest order digital or analog Butterworth filter + that loses no more than `gpass` dB in the passband and has at least + `gstop` dB attenuation in the stopband. + + Parameters + ---------- + wp, ws : float + Passband and stopband edge frequencies. + + For digital filters, these are in the same units as `fs`. By default, + `fs` is 2 half-cycles/sample, so these are normalized from 0 to 1, + where 1 is the Nyquist frequency. (`wp` and `ws` are thus in + half-cycles / sample.) For example: + + - Lowpass: wp = 0.2, ws = 0.3 + - Highpass: wp = 0.3, ws = 0.2 + - Bandpass: wp = [0.2, 0.5], ws = [0.1, 0.6] + - Bandstop: wp = [0.1, 0.6], ws = [0.2, 0.5] + + For analog filters, `wp` and `ws` are angular frequencies (e.g., rad/s). + gpass : float + The maximum loss in the passband (dB). + gstop : float + The minimum attenuation in the stopband (dB). + analog : bool, optional + When True, return an analog filter, otherwise a digital filter is + returned. + fs : float, optional + The sampling frequency of the digital system. + + .. versionadded:: 1.2.0 + + Returns + ------- + ord : int + The lowest order for a Butterworth filter which meets specs. + wn : ndarray or float + The Butterworth natural frequency (i.e. the "3dB frequency"). Should + be used with `butter` to give filter results. If `fs` is specified, + this is in the same units, and `fs` must also be passed to `butter`. + + See Also + -------- + butter : Filter design using order and critical points + cheb1ord : Find order and critical points from passband and stopband spec + cheb2ord, ellipord + iirfilter : General filter design using order and critical frequencies + iirdesign : General filter design using passband and stopband spec + + Examples + -------- + Design an analog bandpass filter with passband within 3 dB from 20 to + 50 rad/s, while rejecting at least -40 dB below 14 and above 60 rad/s. + Plot its frequency response, showing the passband and stopband + constraints in gray. + + >>> from scipy import signal + >>> import matplotlib.pyplot as plt + + >>> N, Wn = signal.buttord([20, 50], [14, 60], 3, 40, True) + >>> b, a = signal.butter(N, Wn, 'band', True) + >>> w, h = signal.freqs(b, a, np.logspace(1, 2, 500)) + >>> plt.semilogx(w, 20 * np.log10(abs(h))) + >>> plt.title('Butterworth bandpass filter fit to constraints') + >>> plt.xlabel('Frequency [radians / second]') + >>> plt.ylabel('Amplitude [dB]') + >>> plt.grid(which='both', axis='both') + >>> plt.fill([1, 14, 14, 1], [-40, -40, 99, 99], '0.9', lw=0) # stop + >>> plt.fill([20, 20, 50, 50], [-99, -3, -3, -99], '0.9', lw=0) # pass + >>> plt.fill([60, 60, 1e9, 1e9], [99, -40, -40, 99], '0.9', lw=0) # stop + >>> plt.axis([10, 100, -60, 3]) + >>> plt.show() + + """ + + _validate_gpass_gstop(gpass, gstop) + + wp = atleast_1d(wp) + ws = atleast_1d(ws) + if fs is not None: + if analog: + raise ValueError("fs cannot be specified for an analog filter") + wp = 2*wp/fs + ws = 2*ws/fs + + filter_type = 2 * (len(wp) - 1) + filter_type += 1 + if wp[0] >= ws[0]: + filter_type += 1 + + # Pre-warp frequencies for digital filter design + if not analog: + passb = math.tan(np.pi * wp / 2.0) + stopb = math.tan(np.pi * ws / 2.0) + else: + passb = wp * 1.0 + stopb = ws * 1.0 + + if filter_type == 1: # low + nat = stopb / passb + elif filter_type == 2: # high + nat = passb / stopb + elif filter_type == 3: # stop + wp0 = optimize.fminbound(band_stop_obj, passb[0], stopb[0] - 1e-12, #TODO + args=(0, passb, stopb, gpass, gstop, + 'butter'), + disp=0) + passb[0] = wp0 + wp1 = optimize.fminbound(band_stop_obj, stopb[1] + 1e-12, passb[1], #TODO + args=(1, passb, stopb, gpass, gstop, + 'butter'), + disp=0) + passb[1] = wp1 + nat = ((stopb * (passb[0] - passb[1])) / + (stopb ** 2 - passb[0] * passb[1])) + elif filter_type == 4: # pass + nat = ((stopb ** 2 - passb[0] * passb[1]) / + (stopb * (passb[0] - passb[1]))) + + nat = min(abs(nat)) + + GSTOP = 10 ** (0.1 * abs(gstop)) + GPASS = 10 ** (0.1 * abs(gpass)) + ord = int(math.ceil(math.log10((GSTOP - 1.0) / (GPASS - 1.0)) / (2 * math.log10(nat)))) + + # Find the Butterworth natural frequency WN (or the "3dB" frequency") + # to give exactly gpass at passb. + try: + W0 = (GPASS - 1.0) ** (-1.0 / (2.0 * ord)) + except ZeroDivisionError: + W0 = 1.0 + print("Warning, order is zero...check input parameters.") + + # now convert this frequency back from lowpass prototype + # to the original analog filter + + if filter_type == 1: # low + WN = W0 * passb + elif filter_type == 2: # high + WN = passb / W0 + elif filter_type == 3: # stop + WN = numpy.empty(2, float) + discr = math.sqrt((passb[1] - passb[0]) ** 2 + + 4 * W0 ** 2 * passb[0] * passb[1]) + WN[0] = ((passb[1] - passb[0]) + discr) / (2 * W0) + WN[1] = ((passb[1] - passb[0]) - discr) / (2 * W0) + WN = numpy.sort(abs(WN)) + elif filter_type == 4: # pass + W0 = numpy.array([-W0, W0], float) + WN = (-W0 * (passb[1] - passb[0]) / 2.0 + + math.sqrt(W0 ** 2 / 4.0 * (passb[1] - passb[0]) ** 2 + + passb[0] * passb[1])) + WN = numpy.sort(abs(WN)) + else: + raise ValueError("Bad type: %s" % filter_type) + + if not analog: + wn = (2.0 / np.pi) * math.arctan(WN) + else: + wn = WN + + if len(wn) == 1: + wn = wn[0] + + if fs is not None: + wn = wn*fs/2 + + return ord, wn + +def buttap(N): + """Return (z,p,k) for analog prototype of Nth-order Butterworth filter. + + The filter will have an angular (e.g., rad/s) cutoff frequency of 1. + + See Also + -------- + butter : Filter design function using this prototype + + """ + if abs(int(N)) != N: + raise ValueError("Filter order must be a nonnegative integer") + z = numpy.array([]) + m = numpy.arange(-N+1, N, 2) + # Middle value is 0 to ensure an exactly real pole + a = np.pi * m / (2 * N) + b = [] + for x in a: + b.append(1j * x) + p = np.array(b, dtype=np.complex) + p = -numpy.exp(p) + k = 1 + return z, p, k + +def butter_bandpass(lowcut, highcut, fs, order=5): + nyq = 0.5 * fs + low = lowcut / nyq + high = highcut / nyq + sos = butter(order, [low, high], analog=False, btype='band', output='sos') + return sos + +def butter_bandpass_filter(data, lowcut, highcut, fs, order=5): + sos = butter_bandpass(lowcut, highcut, fs, order=order) + y = spy.sosfilter(sos, data) + return y + +def fft(x): + n = len(x) + if n <= 1: + return x + even = fft(x[0::2]) + odd = fft(x[1::2]) + return [even[m] + math.e**(-2j*math.pi*m/n)*odd[m] for m in range(n//2)] + [even[m] - math.e**(-2j*math.pi*m/n)*odd[m] for m in range(n//2)] + +filter_dict = {'butter': [buttap, buttord], + 'butterworth': [buttap, buttord] + } + +band_dict = {'band': 'bandpass', + 'bandpass': 'bandpass', + 'pass': 'bandpass', + 'bp': 'bandpass', + + 'bs': 'bandstop', + 'bandstop': 'bandstop', + 'bands': 'bandstop', + 'stop': 'bandstop', + + 'l': 'lowpass', + 'low': 'lowpass', + 'lowpass': 'lowpass', + 'lp': 'lowpass', + + 'high': 'highpass', + 'highpass': 'highpass', + 'h': 'highpass', + 'hp': 'highpass', + } + + + + + + + diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/core/fromnumeric.py b/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/core/fromnumeric.py new file mode 100644 index 00000000..3d851622 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/core/fromnumeric.py @@ -0,0 +1,27 @@ +import math +import sys +sys.path.append('.') + +from snippets import numpy +from ulab import numpy as np + +a = np.array([[1,2,3],[4,5,6]]) +print(numpy.size(a)) +print(numpy.size(a,1)) +print(numpy.size(a,0)) + +print(numpy.prod([1, 10, 100, 5])) +print(numpy.prod([])) +print(numpy.prod([1.,2.])) + + +a = np.array([1,2,3]) +print(numpy.nonzero(a)) +b = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) +print(numpy.nonzero(b > 3)) + +c = np.array([0,1,0,-1]) +print(numpy.nonzero(c > 0)) +print(numpy.nonzero(c < 0)) + + diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/core/multiarray.py b/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/core/multiarray.py new file mode 100644 index 00000000..785f4327 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/core/multiarray.py @@ -0,0 +1,11 @@ +import math +import sys +sys.path.append('.') + +from snippets import numpy +from ulab import numpy as np +np.set_printoptions(threshold=100) + +print (numpy.asarray([1])) +print (numpy.asarray([1.0, 2.0, 3j])) +print (numpy.asarray([4, 3, 1, (2-2j), (2+2j), (2-1j), (2+1j), (2-1j), (2+1j), (1+1j), (1-1j)])) \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/core/numeric.py b/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/core/numeric.py new file mode 100644 index 00000000..b903598e --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/core/numeric.py @@ -0,0 +1,14 @@ +import math +import sys +sys.path.append('.') + +from ulab import numpy as np +from snippets import numpy + +x = np.array([[0, 1, 2], + [3, 4, 5]]) +print(numpy.zeros_like(x)) + +y = np.array([[0, 1j, -2j],[3, 4, 5]], dtype=np.complex) +print(numpy.zeros_like(y)) + diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/core/shape_base.py b/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/core/shape_base.py new file mode 100644 index 00000000..ee3c0780 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/core/shape_base.py @@ -0,0 +1,15 @@ +import math +import sys +sys.path.append('.') + +from ulab import numpy as np +from snippets import numpy + +print(numpy.atleast_1d(1.0)) + +x = np.arange(9.0).reshape((3,3)) + +print(numpy.atleast_1d(x)) +print(numpy.atleast_1d(x) is x) + +print(numpy.atleast_1d(1, [3, 4])) diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/lib/block.py b/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/lib/block.py new file mode 100644 index 00000000..71bfb920 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/lib/block.py @@ -0,0 +1,19 @@ +from ulab.numpy import array, zeros, eye, ones +from snippets import numpy + +a = array([[1, 1]]) +b = array([[2]]) +c = array( + [[3, 3], + [3, 3], + [3, 3]]) +d = array( + [[4], + [4], + [4]]) +print(numpy.block([[a, b], [c, d]])) +a = zeros((2, 3)) +b = eye(2) * 2 +c = eye(3) * 5 +d = ones((3, 2)) +print(numpy.block([[a, b], [c, d]])) diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/lib/function_base.py b/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/lib/function_base.py new file mode 100644 index 00000000..76e0f8f4 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/lib/function_base.py @@ -0,0 +1,11 @@ +import math +import sys +sys.path.append('.') + +from snippets import numpy +from ulab import numpy as np + +print(numpy.append([1, 2, 3], [[4, 5, 6], [7, 8, 9]])) + +print(numpy.append([[1, 2, 3], [4, 5, 6]], [[7, 8, 9]], axis=0)) + diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/lib/polynomial.py b/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/lib/polynomial.py new file mode 100644 index 00000000..7b56212c --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/lib/polynomial.py @@ -0,0 +1,16 @@ +import math +import sys +sys.path.append('.') + +from snippets import numpy +from ulab import numpy as np + + +print(numpy.poly((0, 0, 0))) + +print(numpy.poly((-1./2, 0, 1./2))) + +print(numpy.poly((0.847, 0, 0.9883))) + +#P = np.array([[0, 1./3], [-1./2, 0]]) +#print(numpy.poly(P)) diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/lib/type_check.py b/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/lib/type_check.py new file mode 100644 index 00000000..1d23a3cf --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/tests/numpy/lib/type_check.py @@ -0,0 +1,17 @@ +import math +import sys +sys.path.append('.') + +from snippets import numpy +from ulab import numpy as np + +a = np.array([1+1j, 1+0j, 4.5, 3, 2, 2j], dtype=np.complex) +print(numpy.isreal(a)) + + +a = np.array([1+2j, 2+1j], dtype=np.complex) +print(numpy.isreal(a)) + + +print(numpy.isreal(1)) +print(numpy.isreal(1j)) \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/snippets/tests/scipy/signal/filter_design.py b/components/3rd_party/omv/omv/modules/ulab/snippets/tests/scipy/signal/filter_design.py new file mode 100644 index 00000000..8861ba18 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/snippets/tests/scipy/signal/filter_design.py @@ -0,0 +1,59 @@ +import math +import sys +sys.path.append('.') + +from snippets import scipy +from ulab import numpy as np + +np.set_printoptions(threshold=100) + +a = [4, 3, 1, 2-2j, 2+2j, 2-1j, 2+1j, 2-1j, 2+1j, 1+1j, 1-1j] +#print('_cplxreal: ', scipy.cplxreal(a)) +f = np.array([-1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0], dtype=np.float) +t = (0.9984772174419884+0.01125340518638924j) +w = 'real' +#print('nearest_real_complex_idx: ', scipy.nearest_real_complex_idx(f,t,w)) + + +nyquistRate = 48000 * 2 +centerFrequency_Hz = 480.0 +lowerCutoffFrequency_Hz = centerFrequency_Hz/math.sqrt(2) +upperCutoffFrequenc_Hz = centerFrequency_Hz*math.sqrt(2) +wn = np.array([ lowerCutoffFrequency_Hz, upperCutoffFrequenc_Hz])/nyquistRate + +z = [] +p = np.array([-0.1564344650402309+0.9876883405951379j, -0.4539904997395468+0.8910065241883679j, +-0.7071067811865476+0.7071067811865475j, -0.8910065241883679+0.4539904997395467j, -0.9876883405951379+0.1564344650402309j, +-0.9876883405951379-0.1564344650402309j, -0.8910065241883679-0.4539904997395467j, -0.7071067811865476-0.7071067811865475j, +-0.4539904997395468-0.8910065241883679j, -0.1564344650402309-0.9876883405951379j], dtype=np.complex) +k = 1 +wo = 0.1886352115099219 + +print(scipy.lp2hp_zpk(z,p,k,wo)) + +z = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], dtype=np.float) +p = np.array([-0.02950904840030544-0.1863127990340476j, -0.08563859394186457-0.1680752041469931j, +-0.1333852372292245-0.1333852372292244j, -0.1680752041469931-0.08563859394186453j, -0.1863127990340476-0.02950904840030543j, + -0.1863127990340476+0.02950904840030543j, -0.1680752041469931+0.08563859394186453j, -0.1333852372292245+0.1333852372292244j, + -0.08563859394186457+0.1680752041469931j, -0.02950904840030544+0.1863127990340476j], dtype=np.complex) +k = 1.0 +fs = 2.0 + +print(scipy.bilinear_zpk(z,p,k,fs)) + +z = np.array([], dtype=np.float) +p = np.array([-0.3826834323650898+0.9238795325112868j, +-0.9238795325112868+0.3826834323650898j, -0.9238795325112868-0.3826834323650898j, +-0.3826834323650898-0.9238795325112868j], dtype=np.complex) +k = 1 +wo = 0.03141673402115484 +bw = 0.02221601345771878 +print(scipy.lp2bs_zpk(z, p, k, wo=wo, bw=bw)) + +print(scipy.butter(N=4, Wn=wn, btype='bandpass', analog=False, output='ba')) +print(scipy.butter(N=4, Wn=wn, btype='bandpass', analog=False, output='zpk')) +print(scipy.butter(N=4, Wn=wn, btype='bandpass', analog=False, output='sos')) +print(scipy.butter(N=4, Wn=wn, btype='bandstop', analog=False, output='ba')) +print(scipy.butter(10, 15, 'lp', fs=1000, output='sos')) +print(scipy.butter(10, 15, 'hp', fs=1000, output='sos')) + diff --git a/components/3rd_party/omv/omv/modules/ulab/test-common.sh b/components/3rd_party/omv/omv/modules/ulab/test-common.sh new file mode 100644 index 00000000..45b9e94a --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/test-common.sh @@ -0,0 +1,27 @@ +#!/bin/sh +set -e +dims="$1" +micropython="$2" +for level1 in $(printf "%dd " $(seq 1 ${dims})) +do + for level2 in numpy scipy utils complex; do + rm -f *.exp + if [ ! -d tests/"$level1"/"$level2" ]; then + break; + fi + for file in tests/"$level1"/"$level2"/*.py; do + if [ ! -f "$file"".exp" ]; then + echo "" > "$file"".exp" + fi + done + if ! env MICROPY_MICROPYTHON="$micropython" ./run-tests -d tests/"$level1"/"$level2"; then + for exp in *.exp; do + testbase=$(basename $exp .exp); + echo "\nFAILURE $testbase"; + diff -u $testbase.exp $testbase.out; + done + exit 1 + fi + done +done + diff --git a/components/3rd_party/omv/omv/modules/ulab/test-snippets.sh b/components/3rd_party/omv/omv/modules/ulab/test-snippets.sh new file mode 100644 index 00000000..fcbbfc83 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/test-snippets.sh @@ -0,0 +1,18 @@ +#!/bin/sh +set -e +micropython="$1" +for level1 in numpy scipy; +do + for level2 in core lib signal; do + rm -f *.exp + if ! env MICROPY_MICROPYTHON="$micropython" ./run-tests -d snippets/tests/"$level1"/"$level2"; then + for exp in *.exp; do + testbase=$(basename $exp .exp); + echo -e "\nFAILURE $testbase"; + diff -u $testbase.exp $testbase.out; + done + exit 1 + fi + done +done + diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/1d/complex/complex_exp.py b/components/3rd_party/omv/omv/modules/ulab/tests/1d/complex/complex_exp.py new file mode 100644 index 00000000..979b5b8e --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/1d/complex/complex_exp.py @@ -0,0 +1,17 @@ +# this test is meaningful only, when the firmware supports complex arrays + +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float, np.complex) + +for dtype in dtypes: + a = np.array(range(4), dtype=dtype) + print('\narray:\n', a) + print('\nexponential:\n', np.exp(a)) + +b = np.array([0, 1j, 2+2j, 3-3j], dtype=np.complex) +print('\narray:\n', b) +print('\nexponential:\n', np.exp(b)) \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/1d/complex/complex_sqrt.py b/components/3rd_party/omv/omv/modules/ulab/tests/1d/complex/complex_sqrt.py new file mode 100644 index 00000000..aa709aef --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/1d/complex/complex_sqrt.py @@ -0,0 +1,18 @@ +# this test is meaningful only, when the firmware supports complex arrays + +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float, np.complex) + +for dtype in dtypes: + a = np.array(range(4), dtype=dtype) + outtype = np.float if dtype is not np.complex else np.complex + print('\narray:\n', a) + print('\nsquare root:\n', np.sqrt(a, dtype=outtype)) + +b = np.array([0, 1j, 2+2j, 3-3j], dtype=np.complex) +print('\narray:\n', b) +print('\nsquare root:\n', np.sqrt(b, dtype=np.complex)) \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/1d/complex/imag_real.py b/components/3rd_party/omv/omv/modules/ulab/tests/1d/complex/imag_real.py new file mode 100644 index 00000000..e05783b6 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/1d/complex/imag_real.py @@ -0,0 +1,19 @@ +# this test is meaningful only, when the firmware supports complex arrays + +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float, np.complex) + +for dtype in dtypes: + a = np.array(range(5), dtype=dtype) + print('real part: ', np.real(a)) + print('imaginary part: ', np.imag(a)) + + +b = np.array([0, 1j, 2+2j, 3-3j], dtype=np.complex) +print('real part: ', np.real(b)) +print('imaginary part: ', np.imag(b)) + diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/00smoke.py b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/00smoke.py new file mode 100644 index 00000000..c7562739 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/00smoke.py @@ -0,0 +1,3 @@ +from ulab import numpy as np + +print(np.ones(3)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/argminmax.py b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/argminmax.py new file mode 100644 index 00000000..e2aa0bc9 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/argminmax.py @@ -0,0 +1,62 @@ +from ulab import numpy as np + +# Adapted from https://docs.python.org/3.8/library/itertools.html#itertools.permutations +def permutations(iterable, r=None): + # permutations('ABCD', 2) --> AB AC AD BA BC BD CA CB CD DA DB DC + # permutations(range(3)) --> 012 021 102 120 201 210 + pool = tuple(iterable) + n = len(pool) + r = n if r is None else r + if r > n: + return + indices = list(range(n)) + cycles = list(range(n, n-r, -1)) + yield tuple(pool[i] for i in indices[:r]) + while n: + for i in reversed(range(r)): + cycles[i] -= 1 + if cycles[i] == 0: + indices[i:] = indices[i+1:] + indices[i:i+1] + cycles[i] = n - i + else: + j = cycles[i] + indices[i], indices[-j] = indices[-j], indices[i] + yield tuple(pool[i] for i in indices[:r]) + break + else: + return + +# Combinations expected to throw +try: + print(np.argmin([])) +except ValueError: + print("ValueError") + +try: + print(np.argmax([])) +except ValueError: + print("ValueError") + +# Combinations expected to succeed +print(np.argmin([1])) +print(np.argmax([1])) +print(np.argmin(np.array([1]))) +print(np.argmax(np.array([1]))) + +print() +print("max tests") +for p in permutations((100,200,300)): + m1 = np.argmax(p) + m2 = np.argmax(np.array(p)) + print(p, m1, m2) + if m1 != m2 or p[m1] != max(p): + print("FAIL", p, m1, m2, max(p)) + +print() +print("min tests") +for p in permutations((100,200,300)): + m1 = np.argmin(p) + m2 = np.argmin(np.array(p)) + print(p, m1, m2) + if m1 != m2 or p[m1] != min(p): + print("FAIL", p, m1, m2, min(p)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/compare.py b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/compare.py new file mode 100644 index 00000000..cd9fb98e --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/compare.py @@ -0,0 +1,13 @@ +from ulab import numpy as np + +a = np.array([1, 2, 3, 4, 5], dtype=np.uint8) +b = np.array([5, 4, 3, 2, 1], dtype=np.float) +print(np.minimum(a, b)) +print(np.maximum(a, b)) +print(np.maximum(1, 5.5)) + +a = np.array(range(9), dtype=np.uint8) +print(np.clip(a, 3, 7)) + +b = 3 * np.ones(len(a), dtype=np.float) +print(np.clip(a, b, 7)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/convolve.py b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/convolve.py new file mode 100644 index 00000000..93aa23f7 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/convolve.py @@ -0,0 +1,15 @@ +import math + +try: + from ulab import numpy as np +except ImportError: + import numpy as np + +x = np.array((1,2,3)) +y = np.array((1,10,100,1000)) +result = (np.convolve(x, y)) +ref_result = np.array([1, 12, 123, 1230, 2300, 3000],dtype=np.float) +cmp_result = [] +for p,q in zip(list(result), list(ref_result)): + cmp_result.append(math.isclose(p, q, rel_tol=1e-06, abs_tol=1e-06)) +print(cmp_result) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/fft.py b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/fft.py new file mode 100644 index 00000000..1a1dee75 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/fft.py @@ -0,0 +1,37 @@ +import math +try: + from ulab import numpy as np + use_ulab = True +except ImportError: + import numpy as np + use_ulab = False + +x = np.linspace(-np.pi, np.pi, num=8) +y = np.sin(x) + +if use_ulab: + a, b = np.fft.fft(y) + c, d = np.fft.ifft(a, b) + # c should be equal to y + cmp_result = [] + for p,q in zip(list(y), list(c)): + cmp_result.append(math.isclose(p, q, rel_tol=1e-09, abs_tol=1e-09)) + print(cmp_result) + + z = np.zeros(len(x)) + a, b = np.fft.fft(y, z) + c, d = np.fft.ifft(a, b) + # c should be equal to y + cmp_result = [] + for p,q in zip(list(y), list(c)): + cmp_result.append(math.isclose(p, q, rel_tol=1e-09, abs_tol=1e-09)) + print(cmp_result) + +else: + a = np.fft.fft(y) + c = np.fft.ifft(a) + # c should be equal to y + cmp_result = [] + for p,q in zip(list(y), list(c.real)): + cmp_result.append(math.isclose(p, q, rel_tol=1e-09, abs_tol=1e-09)) + print(cmp_result) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/gc.py b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/gc.py new file mode 100644 index 00000000..4dbf079c --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/gc.py @@ -0,0 +1,11 @@ +from ulab import numpy as np +import gc + +data = np.ones(1000)[6:-6] +print(sum(data)) +print(data) + +gc.collect() + +print(sum(data)) +print(data) \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/interp.py b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/interp.py new file mode 100644 index 00000000..09d3dc3c --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/interp.py @@ -0,0 +1,12 @@ +try: + from ulab import numpy as np +except ImportError: + import numpy as np + +x = np.array([1, 2, 3, 4, 5]) +xp = np.array([1, 2, 3, 4]) +fp = np.array([1, 2, 3, 4]) +print(np.interp(x, xp, fp)) +print(np.interp(x, xp, fp, left=0.0)) +print(np.interp(x, xp, fp, right=10.0)) +print(np.interp(x, xp, fp, left=0.0, right=10.0)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/optimize.py b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/optimize.py new file mode 100644 index 00000000..fce86724 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/optimize.py @@ -0,0 +1,28 @@ +import math + +try: + from ulab import scipy as spy +except ImportError: + import scipy as spy + +def f(x): + return x**2 - 2.0 + +ref_result = 1.4142135623715149 +result = (spy.optimize.bisect(f, 1.0, 3.0)) +print(math.isclose(result, ref_result, rel_tol=1E-6, abs_tol=1E-6)) + +ref_result = -7.105427357601002e-15 +result = spy.optimize.fmin(f, 3.0, fatol=1e-15) +print(math.isclose(result, ref_result, rel_tol=1E-6, abs_tol=1E-6)) + +ref_result = -7.105427357601002e-15 +result = spy.optimize.fmin(f, 3.0, xatol=1e-8, fatol=1e-15, maxiter=500) +print(math.isclose(result, ref_result, rel_tol=1E-6, abs_tol=1E-6)) + +ref_result = 1.41421826342255 +result = (spy.optimize.newton(f, 3.0, tol=0.001, rtol=0.01)) +print(math.isclose(result, ref_result, rel_tol=1E-6, abs_tol=1E-6)) + +result = (spy.optimize.newton(f, 3.0, tol=0.001, rtol=0.01, maxiter=100)) +print(math.isclose(result, ref_result, rel_tol=1E-6, abs_tol=1E-6)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/poly.py b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/poly.py new file mode 100644 index 00000000..02ce7f5b --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/poly.py @@ -0,0 +1,51 @@ +import math + +try: + from ulab import numpy as np +except ImportError: + import numpy as np + +p = [1, 1, 1, 0] +x = [0, 1, 2, 3, 4] +result = np.polyval(p, x) +ref_result = np.array([0, 3, 14, 39, 84]) +for i in range(len(x)): + print(math.isclose(result[i], ref_result[i], rel_tol=1E-9, abs_tol=1E-9)) + +a = np.array(x) +result = np.polyval(p, a) +ref_result = np.array([0, 3, 14, 39, 84]) +for i in range(len(x)): + print(math.isclose(result[i], ref_result[i], rel_tol=1E-9, abs_tol=1E-9)) + +# linear fit +x = np.linspace(-10, 10, 20) +y = 1.5*x + 3 +result = np.polyfit(x, y, 1) +ref_result = np.array([ 1.5, 3.0]) +for i in range(2): + print(math.isclose(result[i], ref_result[i], rel_tol=1E-9, abs_tol=1E-9)) + +# 2nd degree fit +x = np.linspace(-10, 10, 20) +y = x*x*2.5 - x*0.5 + 1.2 +result = np.polyfit(x, y, 2) +ref_result = np.array([2.5, -0.5, 1.2]) +for i in range(3): + print(math.isclose(result[i], ref_result[i], rel_tol=1E-9, abs_tol=1E-9)) + +# 3rd degree fit +x = np.linspace(-10, 10, 20) +y = x*x*x*1.255 + x*x*1.0 - x*0.75 + 0.0 +result = np.polyfit(x, y, 3) +ref_result = np.array([1.255, 1.0, -0.75, 0.0]) +for i in range(4): + print(math.isclose(result[i], ref_result[i], rel_tol=1E-9, abs_tol=1E-9)) + +# 4th degree fit +x = np.linspace(-10, 10, 20) +y = x*x*x*x + x*x*x*1.255 + x*x*1.0 - x*0.75 + 0.0 +result = np.polyfit(x, y, 4) +ref_result = np.array([1.0, 1.255, 1.0, -0.75, 0.0]) +for i in range(5): + print(math.isclose(result[i], ref_result[i], rel_tol=1E-9, abs_tol=1E-9)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/slicing.py b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/slicing.py new file mode 100644 index 00000000..466c3b2c --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/slicing.py @@ -0,0 +1,23 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +for num in range(1,4): + for start in range(-num, num+1): + for end in range(-num, num+1): + for stride in (-3, -2, -1, 1, 2, 3): + l = list(range(num)) + a = np.array(l, dtype=np.int8) + sl = l[start:end:stride] + ll = len(sl) + try: + sa = list(a[start:end:stride]) + except IndexError as e: + sa = str(e) + print("%2d [% d:% d:% d] %-24r %-24r%s" % ( + num, start, end, stride, sl, sa, " ***" if sa != sl else "")) + + a[start:end:stride] = np.ones(len(sl)) * -1 + print("%2d [% d:% d:% d] %r" % ( + num, start, end, stride, list(a))) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/slicing2.py b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/slicing2.py new file mode 100644 index 00000000..05b2d792 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/slicing2.py @@ -0,0 +1,8 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +a = np.array(range(9), dtype=np.float) +print("a:\t", list(a)) +print("a < 5:\t", list(a[a < 5])) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/sum.py b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/sum.py new file mode 100644 index 00000000..a0293136 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/sum.py @@ -0,0 +1,21 @@ +from ulab import numpy as np + +r = range(15) + +a = np.array(r, dtype=np.uint8) +print(np.sum(a)) + +a = np.array(r, dtype=np.int8) +print(np.sum(a)) + +a = np.array(r, dtype=np.uint16) +print(np.sum(a)) + +a = np.array(r, dtype=np.int16) +print(np.sum(a)) + +a = np.array(r, dtype=np.float) +print(np.sum(a)) + +a = np.array([False] + [True]*15, dtype=np.bool) +print(np.sum(a)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/trapz.py b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/trapz.py new file mode 100644 index 00000000..7060c12e --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/trapz.py @@ -0,0 +1,9 @@ +try: + from ulab import numpy as np +except ImportError: + import numpy as np + +x = np.linspace(0, 9, num=10) +y = x*x +print(np.trapz(y)) +print(np.trapz(y, x=x)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/universal_functions.py b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/universal_functions.py new file mode 100644 index 00000000..c58d9333 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/1d/numpy/universal_functions.py @@ -0,0 +1,141 @@ +import math + +try: + from ulab import numpy as np + from ulab import scipy as spy +except ImportError: + import numpy as np + import scipy as spy + +result = (np.sin(np.pi/2)) +ref_result = 1.0 +print(math.isclose(result, ref_result, rel_tol=1E-6, abs_tol=1E-6)) + +result = (np.cos(np.pi/2)) +ref_result = 0.0 +print(math.isclose(result, ref_result, rel_tol=1E-6, abs_tol=1E-6)) + +result = (np.tan(np.pi/2)) +ref_result = 1.633123935319537e+16 +print(math.isclose(result, ref_result, rel_tol=1E-6, abs_tol=1E-6)) + +result = (np.sinh(np.pi/2)) +ref_result = 2.3012989023072947 +print(math.isclose(result, ref_result, rel_tol=1E-6, abs_tol=1E-6)) + +result = (np.cosh(np.pi/2)) +ref_result = 2.5091784786580567 +print(math.isclose(result, ref_result, rel_tol=1E-6, abs_tol=1E-6)) + +result = (np.tanh(np.pi/2)) +ref_result = 0.9171523356672744 +print(math.isclose(result, ref_result, rel_tol=1E-6, abs_tol=1E-6)) + +ref_result = np.pi/2 +result = (np.asin(np.sin(np.pi/2))) +print(math.isclose(result, ref_result, rel_tol=1E-6, abs_tol=1E-6)) + +result = (np.acos(np.cos(np.pi/2))) +print(math.isclose(result, ref_result, rel_tol=1E-6, abs_tol=1E-6)) + +result = (np.atan(np.tan(np.pi/2))) +print(math.isclose(result, ref_result, rel_tol=1E-6, abs_tol=1E-6)) + +result = (np.cosh(np.acosh(np.pi/2))) +print(math.isclose(result, ref_result, rel_tol=1E-6, abs_tol=1E-6)) + +result = (np.sinh(np.asinh(np.pi/2))) +print(math.isclose(result, ref_result, rel_tol=1E-6, abs_tol=1E-6)) + +print(np.degrees(np.pi)) +print(np.radians(np.degrees(np.pi))) +print(np.floor(np.pi)) +print(np.ceil(np.pi)) +print(np.sqrt(np.pi)) +print(np.exp(1)) +print(np.log(np.exp(1))) + +print(np.log2(2**1)) + +print(np.log10(10**1)) +print(math.isclose(np.exp(1) - np.expm1(1), 1)) + +x = np.array([-1, +1, +1, -1]) +y = np.array([-1, -1, +1, +1]) +result = (np.arctan2(y, x) * 180 / np.pi) +ref_result = np.array([-135.0, -45.0, 45.0, 135.0], dtype=np.float) +cmp_result = [] +for i in range(len(x)): + cmp_result.append(math.isclose(result[i], ref_result[i], rel_tol=1E-9, abs_tol=1E-9)) +print(cmp_result) + +x = np.linspace(-2*np.pi, 2*np.pi, 5) +result = np.sin(x) +ref_result = np.array([2.4492936e-16, -1.2246468e-16, 0.0000000e+00, 1.2246468e-16, -2.4492936e-16], dtype=np.float) +cmp_result = [] +for i in range(len(x)): + cmp_result.append(math.isclose(result[i], ref_result[i], rel_tol=1E-9, abs_tol=1E-9)) +print(cmp_result) + +result = np.cos(x) +ref_result = np.array([1., -1., 1., -1., 1.], dtype=np.float) +cmp_result = [] +for i in range(len(x)): + cmp_result.append(math.isclose(result[i], ref_result[i], rel_tol=1E-9, abs_tol=1E-9)) +print(cmp_result) + +result = np.tan(x) +ref_result = np.array([2.4492936e-16, 1.2246468e-16, 0.0000000e+00, -1.2246468e-16, -2.4492936e-16], dtype=np.float) +cmp_result = [] +for i in range(len(x)): + cmp_result.append(math.isclose(result[i], ref_result[i], rel_tol=1E-9, abs_tol=1E-9)) +print(cmp_result) + +result = np.sinh(x) +ref_result = np.array([-267.74489404, -11.54873936, 0., 11.54873936, 267.74489404], dtype=np.float) +cmp_result = [] +for i in range(len(x)): + cmp_result.append(math.isclose(result[i], ref_result[i], rel_tol=1E-9, abs_tol=1E-9)) +print(cmp_result) + +result = np.cosh(x) +ref_result = np.array([267.74676148, 11.59195328, 1.0, 11.59195328, 267.74676148], dtype=np.float) +cmp_result = [] +for i in range(len(x)): + cmp_result.append(math.isclose(result[i], ref_result[i], rel_tol=1E-9, abs_tol=1E-9)) +print(cmp_result) + +result = np.tanh(x) +ref_result = np.array([-0.9999930253396107, -0.99627207622075, 0.0, 0.99627207622075, 0.9999930253396107], dtype=np.float) +cmp_result = [] +for i in range(len(x)): + cmp_result.append(math.isclose(result[i], ref_result[i], rel_tol=1E-9, abs_tol=1E-9)) +print(cmp_result) + +result = (spy.special.erf(np.linspace(-3, 3, num=5))) +ref_result = np.array([-0.9999779095030014, -0.9661051464753108, 0.0, 0.9661051464753108, 0.9999779095030014], dtype=np.float) +cmp_result = [] +for i in range(len(ref_result)): + cmp_result.append(math.isclose(result[i], ref_result[i], rel_tol=1E-9, abs_tol=1E-9)) +print(cmp_result) + +result = (spy.special.erfc(np.linspace(-3, 3, num=5))) +ref_result = np.array([1.99997791e+00, 1.96610515e+00, 1.00000000e+00, 3.38948535e-02, 2.20904970e-05], dtype=np.float) +cmp_result = [] +for i in range(len(ref_result)): + cmp_result.append(math.isclose(result[i], ref_result[i], rel_tol=1E-6, abs_tol=1E-6)) +print(cmp_result) + +result = (spy.special.gamma(np.array([0, 0.5, 1, 5]))) +ref_result = np.array([1.77245385, 1.0, 24.0]) +cmp_result = [] +cmp_result.append(math.isinf(result[0])) +for i in range(len(ref_result)): + cmp_result.append(math.isclose(result[i+1], ref_result[i], rel_tol=1E-9, abs_tol=1E-9)) +print(cmp_result) + +result = (spy.special.gammaln([0, -1, -2, -3, -4])) +cmp_result = [] +for i in range(len(ref_result)): + cmp_result.append(math.isinf(result[i])) +print(cmp_result) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/complex/binary_op.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/complex/binary_op.py new file mode 100644 index 00000000..36efa76f --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/complex/binary_op.py @@ -0,0 +1,26 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float) + +n = 5 +a = np.array(range(n), dtype=np.complex) +c = np.array(range(n), dtype=np.complex) + +print(a == c) +print(a != c) +print() + +c = np.array(range(n), dtype=np.complex) * 1j +print(a == c) +print(a != c) +print() + +for dtype in dtypes: + b = np.array(range(n), dtype=dtype) + print(b == a) + print(b != a) + print() + diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/complex/complex_exp.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/complex/complex_exp.py new file mode 100644 index 00000000..90b3adf7 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/complex/complex_exp.py @@ -0,0 +1,24 @@ +# this test is meaningful only, when the firmware supports complex arrays + +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float, np.complex) + +for dtype in dtypes: + a = np.array(range(4), dtype=dtype) + b = a.reshape((2, 2)) + print('\narray:\n', a) + print('\nexponential:\n', np.exp(a)) + print('\narray:\n', b) + print('\nexponential:\n', np.exp(b)) + +b = np.array([0, 1j, 2+2j, 3-3j], dtype=np.complex) +print('\narray:\n', b) +print('\nexponential:\n', np.exp(b)) + +b = np.array([[0, 1j, 2+2j, 3-3j], [0, 1j, 2+2j, 3-3j]], dtype=np.complex) +print('\narray:\n', b) +print('\nexponential:\n', np.exp(b)) \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/complex/complex_sqrt.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/complex/complex_sqrt.py new file mode 100644 index 00000000..5baebb5f --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/complex/complex_sqrt.py @@ -0,0 +1,25 @@ +# this test is meaningful only, when the firmware supports complex arrays + +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float, np.complex) + +for dtype in dtypes: + a = np.array(range(4), dtype=dtype) + b = a.reshape((2, 2)) + outtype = np.float if dtype is not np.complex else np.complex + print('\narray:\n', a) + print('\nsquare root:\n', np.sqrt(a, dtype=outtype)) + print('\narray:\n', b) + print('\nsquare root:\n', np.sqrt(b, dtype=outtype)) + +b = np.array([0, 1j, 2+2j, 3-3j], dtype=np.complex) +print('\narray:\n', b) +print('\nsquare root:\n', np.sqrt(b, dtype=np.complex)) + +b = np.array([[0, 1j, 2+2j, 3-3j], [0, 1j, 2+2j, 3-3j]], dtype=np.complex) +print('\narray:\n', b) +print('\nsquare root:\n', np.sqrt(b, dtype=np.complex)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/complex/conjugate.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/complex/conjugate.py new file mode 100644 index 00000000..eafaf574 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/complex/conjugate.py @@ -0,0 +1,12 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float, np.complex) + +for dtype in dtypes: + print(np.conjugate(np.array(range(5), dtype=dtype))) + +a = np.array([1, 2+2j, 3-3j, 4j], dtype=np.complex) +print(np.conjugate(a)) \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/complex/imag_real.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/complex/imag_real.py new file mode 100644 index 00000000..536d7297 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/complex/imag_real.py @@ -0,0 +1,28 @@ +# this test is meaningful only, when the firmware supports complex arrays + +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float, np.complex) + +for dtype in dtypes: + a = np.array(range(4), dtype=dtype) + b = a.reshape((2, 2)) + print('\narray:\n', a) + print('\nreal part:\n', np.real(a)) + print('\nimaginary part:\n', np.imag(a)) + print('\narray:\n', b) + print('\nreal part:\n', np.real(b)) + print('\nimaginary part:\n', np.imag(b), '\n') + + +b = np.array([0, 1j, 2+2j, 3-3j], dtype=np.complex) +print('\nreal part:\n', np.real(b)) +print('\nimaginary part:\n', np.imag(b)) + +b = np.array([[0, 1j, 2+2j, 3-3j], [0, 1j, 2+2j, 3-3j]], dtype=np.complex) +print('\nreal part:\n', np.real(b)) +print('\nimaginary part:\n', np.imag(b)) + diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/complex/sort_complex.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/complex/sort_complex.py new file mode 100644 index 00000000..a4154730 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/complex/sort_complex.py @@ -0,0 +1,28 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float, np.complex) + +for dtype in dtypes: + print(np.sort_complex(np.array(range(5, 0, -1), dtype=dtype))) + # these should all return an empty complex array + print(np.sort_complex(np.array(range(5, 0, 1), dtype=dtype))) + +print() +n = 6 +a = np.array(range(n, 0, -1)) * 1j +b = np.array([1] * n) +print(np.sort_complex(a + b)) + +a = np.array(range(n)) * 1j +b = np.array([1] * n) +print(np.sort_complex(a + b)) + +print() +a = np.array([0, -3j, 1+2j, 1-2j, 2j], dtype=np.complex) +print(np.sort_complex(a)) + +a = np.array([0, 3j, 1-2j, 1+2j, -2j], dtype=np.complex) +print(np.sort_complex(a)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/00smoke.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/00smoke.py new file mode 100644 index 00000000..bc7dcf8f --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/00smoke.py @@ -0,0 +1,3 @@ +from ulab import numpy as np + +print(np.eye(3)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/any_all.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/any_all.py new file mode 100644 index 00000000..08788bc5 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/any_all.py @@ -0,0 +1,11 @@ +from ulab import numpy as np + +a = np.array(range(12)).reshape((3, 4)) + +print(np.all(a)) +print(np.all(a, axis=0)) +print(np.all(a, axis=1)) + +print(np.any(a)) +print(np.any(a, axis=0)) +print(np.any(a, axis=1)) \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/arange.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/arange.py new file mode 100644 index 00000000..79cd0b80 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/arange.py @@ -0,0 +1,25 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float) + +for dtype in dtypes: + print(np.arange(10, dtype=dtype)) + print(np.arange(2, 10, dtype=dtype)) + print(np.arange(2, 10, 3, dtype=dtype)) + # test empty range + print(np.arange(0, 0, dtype=dtype)) + +# test for ZeroDivisionError exception +try: + np.arange(0, 10, 0) +except ZeroDivisionError as e: + print('ZeroDivisionError: ', e) + +# test for NAN length exception +try: + np.arange(0, np.nan) +except ValueError as e: + print('ValueError: ', e) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/asarray.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/asarray.py new file mode 100644 index 00000000..1166a05e --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/asarray.py @@ -0,0 +1,15 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float) + +for dtype in dtypes: + a = np.ones((2, 2), dtype=dtype) + print() + for _dtype in dtypes: + b = np.asarray(a, dtype=_dtype) + print('a: ', a) + print('b: ', b) + print('a is b: {}\n'.format(a is b)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/buffer.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/buffer.py new file mode 100644 index 00000000..5cce5b9c --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/buffer.py @@ -0,0 +1,17 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +def print_as_buffer(a): + print(len(memoryview(a)), list(memoryview(a))) +print_as_buffer(np.ones(3)) +print_as_buffer(np.zeros(3)) +print_as_buffer(np.eye(4)) +print_as_buffer(np.ones(1, dtype=np.int8)) +print_as_buffer(np.ones(2, dtype=np.uint8)) +print_as_buffer(np.ones(3, dtype=np.int16)) +print_as_buffer(np.ones(4, dtype=np.uint16)) +print_as_buffer(np.ones(5, dtype=np.float)) +print_as_buffer(np.linspace(0, 1, 9)) + diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/cholesky.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/cholesky.py new file mode 100644 index 00000000..beab3c1d --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/cholesky.py @@ -0,0 +1,14 @@ +from ulab import numpy as np + +a = np.array([[1, 2], [2, 5]]) +print(np.linalg.cholesky(a)) + +b = a = np.array([[25, 15, -5], [15, 18, 0], [-5, 0, 11]]) +print(np.linalg.cholesky(b)) + +c = np.array([[18, 22, 54, 42], [22, 70, 86, 62], [54, 86, 174, 134], [42, 62, 134, 106]]) +print(np.linalg.cholesky(c)) + + + + diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/concatenate.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/concatenate.py new file mode 100644 index 00000000..1a7a440a --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/concatenate.py @@ -0,0 +1,30 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +# test input types; the following should raise ValueErrors +objects = [([1, 2], [3, 4]), + ((1, 2), (3, 4)), + (1, 2, 3)] + +for obj in objects: + try: + np.concatenate(obj) + except ValueError as e: + print('ValueError: {}; failed with object {}\n'.format(e, obj)) + + +a = np.array([1,2,3], dtype=np.float) +b = np.array([4,5,6], dtype=np.float) + +print(np.concatenate((a,b))) +print(np.concatenate((a,b), axis=0)) + +a = np.array([[1,2,3],[4,5,6],[7,8,9]], dtype=np.float) +b = np.array([[1,2,3],[4,5,6],[7,8,9]], dtype=np.float) + +print(np.concatenate((a,b), axis=0)) +print(np.concatenate((a,b), axis=1)) +print(np.concatenate((b,a), axis=0)) +print(np.concatenate((b,a), axis=1)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/delete.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/delete.py new file mode 100644 index 00000000..c7718ad5 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/delete.py @@ -0,0 +1,26 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +np.set_printoptions(threshold=200) + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float) + +for dtype in dtypes: + a = np.array(range(25), dtype=dtype).reshape((5,5)) + print(np.delete(a, [1, 2], axis=0)) + print(np.delete(a, [1, 2], axis=1)) + print(np.delete(a, [1, 5, 10])) + +for dtype in dtypes: + a = np.array(range(25), dtype=dtype).reshape((5,5)) + print(np.delete(a, 2, axis=0)) + print(np.delete(a, 2, axis=1)) + print(np.delete(a, 2)) + +for dtype in dtypes: + a = np.array(range(25), dtype=dtype).reshape((5,5)) + print(np.delete(a, -3, axis=0)) + print(np.delete(a, -3, axis=1)) + print(np.delete(a, -3)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/diag.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/diag.py new file mode 100644 index 00000000..5eed1633 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/diag.py @@ -0,0 +1,29 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +a = np.arange(25).reshape((5,5)) + +print(np.diag(a)) +print(np.diag(a, k=2)) +print(np.diag(a, k=-2)) +print(np.diag(a, k=10)) +print(np.diag(a, k=-10)) + +a = np.arange(4) + +print(np.diag(a)) +print(np.diag(a, k=2)) +print(np.diag(a, k=-2)) + + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float) + +for dtype in dtypes: + a = np.array(range(4), dtype=dtype) + print(np.diag(a)) + +for dtype in dtypes: + a = np.array(range(16), dtype=dtype).reshape((4,4)) + print(np.diag(a)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/eye.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/eye.py new file mode 100644 index 00000000..630eed4e --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/eye.py @@ -0,0 +1,30 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float) + +print(np.ones(3)) +print(np.ones((3,3))) + +print(np.eye(3)) +print(np.eye(3, M=4)) +print(np.eye(3, M=4, k=0)) +print(np.eye(3, M=4, k=-1)) +print(np.eye(3, M=4, k=-2)) +print(np.eye(3, M=4, k=-3)) +print(np.eye(3, M=4, k=1)) +print(np.eye(3, M=4, k=2)) +print(np.eye(3, M=4, k=3)) +print(np.eye(4, M=4)) +print(np.eye(4, M=3, k=0)) +print(np.eye(4, M=3, k=-1)) +print(np.eye(4, M=3, k=-2)) +print(np.eye(4, M=3, k=-3)) +print(np.eye(4, M=3, k=1)) +print(np.eye(4, M=3, k=2)) +print(np.eye(4, M=3, k=3)) + +for dtype in dtypes: + print(np.eye(3, dtype=dtype)) \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/full.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/full.py new file mode 100644 index 00000000..474f5185 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/full.py @@ -0,0 +1,9 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float) + +for dtype in dtypes: + print(np.full((2, 4), 3, dtype=dtype)) \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/initialisation.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/initialisation.py new file mode 100644 index 00000000..6136d513 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/initialisation.py @@ -0,0 +1,10 @@ +try: + from ulab import numpy as np +except ImportError: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float) + +for dtype1 in dtypes: + for dtype2 in dtypes: + print(np.array(np.array(range(5), dtype=dtype1), dtype=dtype2)) \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/isinf.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/isinf.py new file mode 100644 index 00000000..7beff9d6 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/isinf.py @@ -0,0 +1,24 @@ + +from ulab import numpy as np + +print('isinf(0): ', np.isinf(0)) + +a = np.array([1, 2, np.nan]) +print('\n' + '='*20) +print('a:\n', a) +print('\nisinf(a):\n', np.isinf(a)) + +b = np.array([1, 2, np.inf]) +print('\n' + '='*20) +print('b:\n', b) +print('\nisinf(b):\n', np.isinf(b)) + +c = np.array([1, 2, 3], dtype=np.uint16) +print('\n' + '='*20) +print('c:\n', c) +print('\nisinf(c):\n', np.isinf(c)) + +d = np.eye(5) * 1e999 +print('\n' + '='*20) +print('d:\n', d) +print('\nisinf(d):\n', np.isinf(d)) \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/linalg.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/linalg.py new file mode 100644 index 00000000..ead6f1fe --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/linalg.py @@ -0,0 +1,95 @@ +import math + +try: + from ulab import numpy as np +except ImportError: + import numpy as np + +def matrix_is_close(A, B, n): + # primitive (i.e., independent of other functions) check of closeness of two square matrices + for i in range(n): + for j in range(n): + print(math.isclose(A[i][j], B[i][j], rel_tol=1E-9, abs_tol=1E-9)) + +a = np.array([1,2,3], dtype=np.int16) +b = np.array([4,5,6], dtype=np.int16) +ab = np.dot(a.transpose(), b) +print(math.isclose(ab, 32.0, rel_tol=1E-9, abs_tol=1E-9)) + +a = np.array([1,2,3], dtype=np.int16) +b = np.array([4,5,6], dtype=np.float) +ab = np.dot(a.transpose(), b) +print(math.isclose(ab, 32.0, rel_tol=1E-9, abs_tol=1E-9)) + +a = np.array([[1, 2], [3, 4]]) +b = np.array([[5, 6], [7, 8]]) + +c = np.array([[19, 22], [43, 50]]) +matrix_is_close(np.dot(a, b), c, 2) + +c = np.array([[26, 30], [38, 44]]) +matrix_is_close(np.dot(a.transpose(), b), c, 2) + +c = np.array([[17, 23], [39, 53]]) +matrix_is_close(np.dot(a, b.transpose()), c, 2) + +c = np.array([[23, 31], [34, 46]]) +matrix_is_close(np.dot(a.transpose(), b.transpose()), c, 2) + +a = np.array([[1., 2.], [3., 4.]]) +b = np.linalg.inv(a) +ab = np.dot(a, b) +c = np.eye(2) +matrix_is_close(ab, c, 2) + +a = np.array([[1, 2, 3, 4], [4, 5, 6, 4], [7, 8.6, 9, 4], [3, 4, 5, 6]]) +b = np.linalg.inv(a) +ab = np.dot(a, b) +c = np.eye(4) +matrix_is_close(ab, c, 4) + +a = np.array([[1, 2, 3, 4], [4, 5, 6, 4], [7, 8.6, 9, 4], [3, 4, 5, 6]]) +result = (np.linalg.det(a)) +ref_result = 7.199999999999995 +print(math.isclose(result, ref_result, rel_tol=1E-9, abs_tol=1E-9)) + +a = np.array([1, 2, 3]) +w, v = np.linalg.eig(np.diag(a)) +for i in range(3): + print(math.isclose(w[i], a[i], rel_tol=1E-9, abs_tol=1E-9)) +for i in range(3): + for j in range(3): + if i == j: + print(math.isclose(v[i][j], 1.0, rel_tol=1E-9, abs_tol=1E-9)) + else: + print(math.isclose(v[i][j], 0.0, rel_tol=1E-9, abs_tol=1E-9)) + + +a = np.array([[25, 15, -5], [15, 18, 0], [-5, 0, 11]]) +result = (np.linalg.cholesky(a)) +ref_result = np.array([[5., 0., 0.], [ 3., 3., 0.], [-1., 1., 3.]]) +for i in range(3): + for j in range(3): + print(math.isclose(result[i][j], ref_result[i][j], rel_tol=1E-9, abs_tol=1E-9)) + +a = np.array([1,2,3,4,5], dtype=np.float) +result = (np.linalg.norm(a)) +ref_result = 7.416198487095663 +print(math.isclose(result, ref_result, rel_tol=1E-9, abs_tol=1E-9)) + +a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) +result = (np.linalg.norm(a)) ## Here is a problem +ref_result = 16.881943016134134 +print(math.isclose(result, ref_result, rel_tol=1E-6, abs_tol=1E-6)) + +a = np.array([[0, 1, 2], [3, 4 ,5], [5, 4, 8], [4, 4, 8] ], dtype=np.int16) +result = (np.linalg.norm(a,axis=0)) # fails on low tolerance +ref_result = np.array([7.071068, 7.0, 12.52996]) +for i in range(3): + print(math.isclose(result[i], ref_result[i], rel_tol=1E-6, abs_tol=1E-6)) + +a = np.array([[0, 1, 2], [3, 4 ,5], [5, 4, 8], [4, 4, 8] ], dtype=np.int16) +result = (np.linalg.norm(a,axis=1)) # fails on low tolerance +ref_result = np.array([2.236068, 7.071068, 10.24695, 9.797959]) +for i in range(4): + print(math.isclose(result[i], ref_result[i], rel_tol=1E-6, abs_tol=1E-6)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/linspace.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/linspace.py new file mode 100644 index 00000000..c97199a2 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/linspace.py @@ -0,0 +1,10 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float) + +for dtype in dtypes: + print(np.linspace(0, 10, num=5, dtype=dtype)) + print(np.linspace(0, 10, num=5, endpoint=True, dtype=dtype)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/load_save.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/load_save.py new file mode 100644 index 00000000..6fb9d2a3 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/load_save.py @@ -0,0 +1,14 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float) + +for dtype in dtypes: + a = np.array(range(25), dtype=dtype) + b = a.reshape((5, 5)) + np.save('out.npy', a) + print(np.load('out.npy')) + np.save('out.npy', b) + print(np.load('out.npy')) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/loadtxt.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/loadtxt.py new file mode 100644 index 00000000..f08a9164 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/loadtxt.py @@ -0,0 +1,37 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16) + +a = np.array(range(8)).reshape((2, 4)) +np.savetxt('loadtxt.dat', a, header='test file data') + +print(np.loadtxt('loadtxt.dat')) +print() + +for dtype in dtypes: + print(np.loadtxt('loadtxt.dat', dtype=dtype)) + print() + +np.savetxt('loadtxt.dat', a, delimiter=',', header='test file data') + +print(np.loadtxt('loadtxt.dat', delimiter=',')) +print() + +np.savetxt('loadtxt.dat', a, delimiter=',', comments='!', header='test file data') + +print(np.loadtxt('loadtxt.dat', delimiter=',', comments='!')) +print() +print(np.loadtxt('loadtxt.dat', delimiter=',', comments='!', usecols=1)) +print() +print(np.loadtxt('loadtxt.dat', delimiter=',', comments='!', usecols=(0, 1))) +print() + +a = np.array(range(36)).reshape((9, 4)) +np.savetxt('loadtxt.dat', a, header='9 data rows and a comment') +print(np.loadtxt('loadtxt.dat', max_rows=5)) + +print() +print(np.loadtxt('loadtxt.dat', skiprows=5, dtype=np.uint16)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/logspace.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/logspace.py new file mode 100644 index 00000000..e6f2047b --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/logspace.py @@ -0,0 +1,10 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float) + +for dtype in dtypes: + print(np.logspace(0, 10, num=5, endpoint=False, dtype=dtype)) + print(np.logspace(0, 10, num=5, endpoint=True, dtype=dtype)) \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/methods.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/methods.py new file mode 100644 index 00000000..1c687d13 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/methods.py @@ -0,0 +1,44 @@ +try: + from ulab import numpy as np +except ImportError: + import numpy as np + +a = np.array([1, 2, 3, 4], dtype=np.int8) +b = a.copy() +print(b) +a = np.array([[1,2,3],[4,5,6],[7,8,9]], dtype=np.int16) +b = a.copy() +print(b) +a = np.array([[1,2,3],[4,5,6],[7,8,9]], dtype=np.float) +b = a.copy() +print(b) +print(a.dtype) +print(a.flatten()) +print(np.array([1,2,3], dtype=np.uint8).itemsize) +print(np.array([1,2,3], dtype=np.uint16).itemsize) +print(np.array([1,2,3], dtype=np.int8).itemsize) +print(np.array([1,2,3], dtype=np.int16).itemsize) +print(np.array([1,2,3], dtype=np.float).itemsize) +print(np.array([1,2,3], dtype=np.float).shape) +print(np.array([[1],[2],[3]], dtype=np.float).shape) +print(np.array([[1],[2],[3]], dtype=np.float).reshape((1,3))) +print(np.array([[1],[2],[3]]).size) +print(np.array([1,2,3], dtype=np.float).size) +print(np.array([1,2,3], dtype=np.uint8).tobytes()) +print(np.array([1,2,3], dtype=np.int8).tobytes()) +print(np.array([1,2,3], dtype=np.float).transpose().shape) +print(np.array([[1],[2],[3]], dtype=np.float).transpose().shape) +a = np.array([1, 2, 3, 4, 5, 6], dtype=np.uint8) +b = a.byteswap(inplace=False) +print(a) +print(b) +c = a.byteswap(inplace=True) +print(a) +print(c) +a = np.array([1, 2, 3, 4, 5, 6], dtype=np.uint16) +b = a.byteswap(inplace=False) +print(a) +print(b) +c = a.byteswap(inplace=True) +print(a) +print(c) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/nonzero.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/nonzero.py new file mode 100644 index 00000000..510ef415 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/nonzero.py @@ -0,0 +1,16 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +array = np.array(range(16)).reshape((4,4)) +print(array) +print(array < 5) +print(np.nonzero(array < 5)) + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float) + +for dtype in dtypes: + array = (np.arange(2, 12, 3, dtype=dtype)).reshape((2,2)) - 2 + print(array) + print(np.nonzero(array)) \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/numericals.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/numericals.py new file mode 100644 index 00000000..909929f0 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/numericals.py @@ -0,0 +1,214 @@ +import math +try: + from ulab import numpy as np +except ImportError: + import numpy as np + +print("Testing np.min:") +print(np.min([1])) +print(np.min(np.array([1], dtype=np.float))) +a = np.array([[1,2,3],[4,5,6],[7,8,9]], dtype=np.uint8) +print(np.min(a)) +print(np.min(a, axis=0)) +print(np.min(a, axis=1)) +a = np.array([range(255-5, 255),range(240-5, 240),range(250-5,250)], dtype=np.uint8) +print(np.min(a)) +print(np.min(a, axis=0)) +print(np.min(a, axis=1)) +a = np.array([range(255-5, 255),range(240-5, 240),range(250-5,250)], dtype=np.int8) +print(np.min(a)) ## Problem here +print(np.min(a, axis=0)) +print(np.min(a, axis=1)) +a = np.array([range(255-5, 255),range(240-5, 240),range(250-5,250)], dtype=np.uint16) +print(np.min(a)) +print(np.min(a, axis=0)) +print(np.min(a, axis=1)) +a = np.array([range(255-5, 255),range(240-5, 240),range(250-5,250)], dtype=np.int16) +print(np.min(a)) +print(np.min(a, axis=0)) +print(np.min(a, axis=1)) +a = np.array([range(2**56-3, 2**56),range(2**16-3, 2**16),range(2**8-3, 2**8)], dtype=np.float) +print(np.min(a)) +print(np.min(a, axis=0)) +print(np.min(a, axis=1)) + +print("Testing np.max:") +print(np.max([1])) +print(np.max(np.array([1], dtype=np.float))) +a = np.array([[1,2,3],[4,5,6],[7,8,9]], dtype=np.uint8) +print(np.max(a)) +print(np.max(a, axis=0)) +print(np.max(a, axis=1)) +a = np.array([range(255-5, 255),range(240-5, 240),range(250-5,250)], dtype=np.uint8) +print(np.max(a)) +print(np.max(a, axis=0)) +print(np.max(a, axis=1)) +a = np.array([range(255-5, 255),range(240-5, 240),range(250-5,250)], dtype=np.int8) +print(np.max(a)) ## Problem here +print(np.max(a, axis=0)) +print(np.max(a, axis=1)) +a = np.array([range(255-5, 255),range(240-5, 240),range(250-5,250)], dtype=np.uint16) +print(np.max(a)) +print(np.max(a, axis=0)) +print(np.max(a, axis=1)) +a = np.array([range(255-5, 255),range(240-5, 240),range(250-5,250)], dtype=np.int16) +print(np.max(a)) +print(np.max(a, axis=0)) +print(np.max(a, axis=1)) +a = np.array([range(2**56-3, 2**56),range(2**16-3, 2**16),range(2**8-3, 2**8)], dtype=np.float) +print(np.max(a)) +print(np.max(a, axis=0)) +print(np.max(a, axis=1)) + +print("Testing np.argmin:") +print(np.argmin([1])) +print(np.argmin(np.array([1], dtype=np.float))) +a = np.array([[1,2,3],[4,5,6],[7,8,9]], dtype=np.uint8) +print(np.argmin(a)) +print(np.argmin(a, axis=0)) +print(np.argmin(a, axis=1)) +a = np.array([range(255-5, 255),range(240-5, 240),range(250-5,250)], dtype=np.uint8) +print(np.argmin(a)) +print(np.argmin(a, axis=0)) +print(np.argmin(a, axis=1)) +a = np.array([range(255-5, 255),range(240-5, 240),range(250-5,250)], dtype=np.int8) +print(np.argmin(a)) ## Problem here +print(np.argmin(a, axis=0)) +print(np.argmin(a, axis=1)) +a = np.array([range(255-5, 255),range(240-5, 240),range(250-5,250)], dtype=np.uint16) +print(np.argmin(a)) +print(np.argmin(a, axis=0)) +print(np.argmin(a, axis=1)) +a = np.array([range(255-5, 255),range(240-5, 240),range(250-5,250)], dtype=np.int16) +print(np.argmin(a)) +print(np.argmin(a, axis=0)) +print(np.argmin(a, axis=1)) +a = np.array([range(2**56-3, 2**56),range(2**16-3, 2**16),range(2**8-3, 2**8)], dtype=np.float) +print(np.argmin(a)) +print(np.argmin(a, axis=0)) +print(np.argmin(a, axis=1)) + +print("Testing np.argmax:") +print(np.argmax([1])) +print(np.argmax(np.array([1], dtype=np.float))) +a = np.array([[1,2,3],[4,5,6],[7,8,9]], dtype=np.uint8) +print(np.argmax(a)) +print(np.argmax(a, axis=0)) +print(np.argmax(a, axis=1)) +a = np.array([range(255-5, 255),range(240-5, 240),range(250-5,250)], dtype=np.uint8) +print(np.argmax(a)) +print(np.argmax(a, axis=0)) +print(np.argmax(a, axis=1)) +a = np.array([range(255-5, 255),range(240-5, 240),range(250-5,250)], dtype=np.int8) +print(np.argmax(a)) ## Problem here +print(np.argmax(a, axis=0)) +print(np.argmax(a, axis=1)) +a = np.array([range(255-5, 255),range(240-5, 240),range(250-5,250)], dtype=np.uint16) +print(np.argmax(a)) +print(np.argmax(a, axis=0)) +print(np.argmax(a, axis=1)) +a = np.array([range(255-5, 255),range(240-5, 240),range(250-5,250)], dtype=np.int16) +print(np.argmax(a)) +print(np.argmax(a, axis=0)) +print(np.argmax(a, axis=1)) +a = np.array([range(2**56-3, 2**56),range(2**16-3, 2**16),range(2**8-3, 2**8)], dtype=np.float) +print(np.argmax(a)) +print(np.argmax(a, axis=0)) +print(np.argmax(a, axis=1)) + +print("Testing np.minimum:") +print(np.minimum(10, 9)) +print(np.minimum(10.0, 9.0)) +a = np.array([range(255-3, 255),range(240-3, 240),range(250-3,250)], dtype=np.float) +b = np.array([range(2**56-3, 2**56),range(2**16-3, 2**16),range(2**8-3, 2**8)], dtype=np.float) +print(np.minimum(a, b)) + +print("Testing np.maximum:") +print(np.maximum(a, b)) +print(np.maximum(10, 9)) +print(np.maximum(10.0, 9.0)) +a = np.array([range(255-3, 255),range(240-3, 240),range(250-3,250)], dtype=np.float) +b = np.array([range(2**56-3, 2**56),range(2**16-3, 2**16),range(2**8-3, 2**8)], dtype=np.float) +print(np.maximum(a, b)) + +print("Testing np.sort:") +a = np.array([range(255-3, 255),range(240-3, 240),range(250-3,250)], dtype=np.uint8) +b = np.array([range(2**56-3, 2**56),range(2**16-3, 2**16),range(2**8-3, 2**8)], dtype=np.float) +print(np.sort(a, axis=None)) +print(np.sort(b, axis=None)) +print(np.sort(a, axis=0)) +print(np.sort(b, axis=0)) +print(np.sort(a, axis=1)) +print(np.sort(b, axis=1)) + +print("Testing np.sum:") +a = np.array([253, 254, 255], dtype=np.uint8) +print(np.sum(a)) +print(np.sum(a, axis=0)) +a = np.array([range(255-3, 255),range(240-3, 240),range(250-3,250)], dtype=np.float) +print(np.sum(a)) +print(np.sum(a, axis=0)) +print(np.sum(a, axis=1)) + +print("Testing np.mean:") +a = np.array([253, 254, 255], dtype=np.uint8) +print(np.mean(a)) +print(np.mean(a, axis=0)) +a = np.array([range(255-3, 255),range(240-3, 240),range(250-3,250)], dtype=np.float) +#print(np.mean(a)) +print(math.isclose(np.mean(a), 246.3333333333333, rel_tol=1e-06, abs_tol=1e-06)) +#print(np.mean(a, axis=0)) +result = np.mean(a, axis=0) +ref_result = [245.33333333, 246.33333333, 247.33333333] +for p, q in zip(list(result), ref_result): + print(math.isclose(p, q, rel_tol=1e-06, abs_tol=1e-06)) + +#print(np.mean(a, axis=1)) +result = np.mean(a, axis=1) +ref_result = [253., 238., 248.] +for p, q in zip(list(result), ref_result): + print(math.isclose(p, q, rel_tol=1e-06, abs_tol=1e-06)) + +print("Testing np.std:") +a = np.array([253, 254, 255], dtype=np.uint8) +#print(np.std(a)) +print(math.isclose(np.std(a), 0.816496580927726, rel_tol=1e-06, abs_tol=1e-06)) +print(math.isclose(np.std(a, axis=0), 0.816496580927726, rel_tol=1e-06, abs_tol=1e-06)) +a = np.array([range(255-3, 255),range(240-3, 240),range(250-3,250)], dtype=np.float) +#print(np.std(a)) +print(math.isclose(np.std(a), 6.289320754704403, rel_tol=1e-06, abs_tol=1e-06)) +#print(np.std(a, axis=0)) +result = np.std(a, axis=0) +ref_result = [6.23609564, 6.23609564, 6.23609564] +for p, q in zip(list(result), ref_result): + print(math.isclose(p, q, rel_tol=1e-06, abs_tol=1e-06)) + +#print(np.std(a, axis=1)) +result = np.std(a, axis=1) +ref_result = [0.81649658, 0.81649658, 0.81649658] +for p, q in zip(list(result), ref_result): + print(math.isclose(p, q, rel_tol=1e-06, abs_tol=1e-06)) + +print("Testing np.median:") +a = np.array([253, 254, 255], dtype=np.uint8) +print(np.median(a)) +print(np.median(a, axis=0)) +a = np.array([range(255-3, 255),range(240-3, 240),range(250-3,250)], dtype=np.float) +print(np.median(a)) +print(np.median(a, axis=0)) +print(np.median(a, axis=1)) +print("Testing np.roll:") ## Here is problem +print(np.arange(10)) +print(np.roll(np.arange(10), 2)) +print(np.roll(np.arange(10), -2)) +a = np.array([1, 2, 3, 4, 5, 6, 7, 8]) +print(np.roll(a, 2)) +print(np.roll(a, -2)) +print("Testing np.clip:") +print(np.clip(5, 3, 6)) ## Here is problem +print(np.clip(7, 3, 6)) +print(np.clip(1, 3, 6)) +a = np.array([1,2,3,4,5,6,7], dtype=np.int8) +print(np.clip(a, 3, 5)) +a = np.array([1,2,3,4,5,6,7], dtype=np.float) +print(np.clip(a, 3, 5)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/ones.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/ones.py new file mode 100644 index 00000000..f0aee868 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/ones.py @@ -0,0 +1,13 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float) + +print(np.ones(3)) +print(np.ones((3,3))) + +for dtype in dtypes: + print(np.ones((3,3), dtype=dtype)) + print(np.ones((4,2), dtype=dtype)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/operators.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/operators.py new file mode 100644 index 00000000..42d00432 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/operators.py @@ -0,0 +1,181 @@ +try: + from ulab import numpy as np +except ImportError: + import numpy as np + + +print(len(np.array([1, 2, 3, 4, 5], dtype=np.uint8))) +print(len(np.array([[1, 2, 3],[4, 5, 6]]))) + +print(~np.array([0, -1, -100], dtype=np.uint8)) +print(~np.array([0, -1, -100], dtype=np.uint16)) +print(~np.array([0, -1, -100], dtype=np.int8)) +print(~np.array([0, -1, -100], dtype=np.int16)) + +print(abs(np.array([0, -1, -100], dtype=np.uint8))) +print(abs(np.array([0, -1, -100], dtype=np.uint16))) +print(abs(np.array([0, -1, -100], dtype=np.int8))) +print(abs(np.array([0, -1, -100], dtype=np.int16))) +print(abs(np.array([0, -1, -100], dtype=np.float))) + +print(-(np.array([0, -1, -100], dtype=np.uint8))) +print(-(np.array([0, -1, -100], dtype=np.uint16))) +print(-(np.array([0, -1, -100], dtype=np.int8))) +print(-(np.array([0, -1, -100], dtype=np.int16))) +print(-(np.array([0, -1, -100], dtype=np.float))) + +print(+(np.array([0, -1, -100], dtype=np.uint8))) +print(+(np.array([0, -1, -100], dtype=np.uint16))) +print(+(np.array([0, -1, -100], dtype=np.int8))) +print(+(np.array([0, -1, -100], dtype=np.int16))) +print(+(np.array([0, -1, -100], dtype=np.float))) + +print(np.array([1,2,3], dtype=np.float) > np.array([4,5,6], dtype=np.float)) +print(np.array([1,2,3], dtype=np.float) > np.array([4,5,6], dtype=np.uint16)) +print(np.array([1,2,3], dtype=np.float) > np.array([4,5,6], dtype=np.int16)) +print(np.array([1,2,3], dtype=np.float) < np.array([4,5,6], dtype=np.float)) +print(np.array([1,2,3], dtype=np.float) < np.array([4,5,6], dtype=np.uint16)) +print(np.array([1,2,3], dtype=np.float) < np.array([4,5,6], dtype=np.int16)) + +print(np.array([1,2,3], dtype=np.float) >= np.array([4,5,6], dtype=np.float)) +print(np.array([1,2,3], dtype=np.float) >= np.array([4,5,6], dtype=np.uint16)) +print(np.array([1,2,3], dtype=np.float) >= np.array([4,5,6], dtype=np.int16)) +print(np.array([1,2,3], dtype=np.float) <= np.array([4,5,6], dtype=np.float)) +print(np.array([1,2,3], dtype=np.float) <= np.array([4,5,6], dtype=np.uint16)) +print(np.array([1,2,3], dtype=np.float) <= np.array([4,5,6], dtype=np.int16)) + +print(np.array([1,2,3], dtype=np.float) > 4) +print(np.array([1,2,3], dtype=np.float) > 4.0) +print(np.array([1,2,3], dtype=np.float) < 4) +print(np.array([1,2,3], dtype=np.float) < 4.0) + +print(np.array([1,2,3], dtype=np.float) == np.array([4,5,6], dtype=np.float)) +print(np.array([1,2,3], dtype=np.float) == np.array([4,5,6], dtype=np.uint16)) +print(np.array([1,2,3], dtype=np.float) == np.array([4,5,6], dtype=np.int16)) +print(np.array([1,2,3], dtype=np.float) != np.array([4,5,6], dtype=np.float)) +print(np.array([1,2,3], dtype=np.float) != np.array([4,5,6], dtype=np.uint16)) +print(np.array([1,2,3], dtype=np.float) != np.array([4,5,6], dtype=np.int16)) + +print(np.array([1,2,3], dtype=np.float) == 4) +print(np.array([1,2,3], dtype=np.float) == 4.0) +print(np.array([1,2,3], dtype=np.float) != 4) +print(np.array([1,2,3], dtype=np.float) != 4.0) + +print(np.array([1,2,3], dtype=np.float) - np.array([4,5,6], dtype=np.float)) +print(np.array([1,2,3], dtype=np.float) - np.array([4,5,6], dtype=np.uint16)) +print(np.array([1,2,3], dtype=np.float) - np.array([4,5,6], dtype=np.int16)) + +print(np.array([1,2,3], dtype=np.float) + np.array([4,5,6], dtype=np.float)) +print(np.array([1,2,3], dtype=np.float) + np.array([4,5,6], dtype=np.uint16)) +print(np.array([1,2,3], dtype=np.float) + np.array([4,5,6], dtype=np.int16)) + +print(np.array([1,2,3], dtype=np.float) * np.array([4,5,6], dtype=np.float)) +print(np.array([1,2,3], dtype=np.float) * np.array([4,5,6], dtype=np.uint16)) +print(np.array([1,2,3], dtype=np.float) * np.array([4,5,6], dtype=np.int16)) + +print(np.array([1,2,3], dtype=np.float) ** np.array([4,5,6], dtype=np.float)) +print(np.array([1,2,3], dtype=np.float) ** np.array([4,5,6], dtype=np.uint16)) +print(np.array([1,2,3], dtype=np.float) ** np.array([4,5,6], dtype=np.int16)) + +print(np.array([1,2,3], dtype=np.float) / np.array([4,5,6], dtype=np.float)) +print(np.array([1,2,3], dtype=np.float) / np.array([4,5,6], dtype=np.uint16)) +print(np.array([1,2,3], dtype=np.float) / np.array([4,5,6], dtype=np.int16)) + +print(np.array([10,20,30], dtype=np.float) // np.array([4,5,6], dtype=np.float)) +print(np.array([10,20,30], dtype=np.float) // np.array([4,5,6], dtype=np.uint16)) +print(np.array([10,20,30], dtype=np.float) // np.array([4,5,6], dtype=np.int16)) + +print(np.array([1,2,3], dtype=np.float) - 4) +print(np.array([1,2,3], dtype=np.float) - 4.0) +print(np.array([1,2,3], dtype=np.float) + 4) +print(np.array([1,2,3], dtype=np.float) + 4.0) + +print(np.array([1,2,3], dtype=np.float) * 4) +print(np.array([1,2,3], dtype=np.float) * 4.0) +print(np.array([1,2,3], dtype=np.float) ** 4) +print(np.array([1,2,3], dtype=np.float) ** 4.0) + +print(np.array([1,2,3], dtype=np.float) / 4) +print(np.array([1,2,3], dtype=np.float) / 4.0) +print(np.array([10,20,30], dtype=np.float) // 4) +print(np.array([10,20,30], dtype=np.float) // 4.0) +print(np.array([10,20,30], dtype=np.int8) // 4) +print(np.array([10,20,30], dtype=np.int8) // 4.0) +print(np.array([10,20,30], dtype=np.uint16) // 4) +print(np.array([10,20,30], dtype=np.uint16) // 4.0) +print(np.array([10,20,30], dtype=np.int16) // 4) +print(np.array([10,20,30], dtype=np.int16) // 4.0) + +a = np.array([1,2,3], dtype=np.float) +a -= np.array([4,5,6], dtype=np.float) +print(a) + +a = np.array([1,2,3], dtype=np.float) +a -= np.array([4,5,6], dtype=np.uint16) +print(a) + +a = np.array([1,2,3], dtype=np.float) +a -= np.array([4,5,6], dtype=np.int16) +print(a) + +a = np.array([1,2,3], dtype=np.float) +a += np.array([4,5,6], dtype=np.float) +print(a) + +a = np.array([1,2,3], dtype=np.float) +a += np.array([4,5,6], dtype=np.uint16) +print(a) + +a = np.array([1,2,3], dtype=np.float) +a += np.array([4,5,6], dtype=np.int16) +print(a) + +a = np.array([1,2,3], dtype=np.float) +a *= np.array([4,5,6], dtype=np.float) +print(a) + +a = np.array([1,2,3], dtype=np.float) +a *= np.array([4,5,6], dtype=np.uint16) +print(a) + +a = np.array([1,2,3], dtype=np.float) +a *= np.array([4,5,6], dtype=np.int16) +print(a) + +a = np.array([1,2,3], dtype=np.float) +#a /= np.array([4,5,6]) +print(a) + +a = np.array([1,2,3], dtype=np.float) +a **= np.array([4,5,6], dtype=np.float) +print(a) + +a = np.array([1,2,3], dtype=np.float) +a **= np.array([4,5,6], dtype=np.uint16) +print(a) + +a = np.array([1,2,3], dtype=np.float) +a **= np.array([4,5,6], dtype=np.int16) +print(a) + +print(np.array([1,2,3],dtype=np.uint8) + np.array([4,5,6],dtype=np.uint8)) +print(np.array([1,2,3],dtype=np.uint8) + np.array([4,5,6],dtype=np.int8)) +print(np.array([1,2,3],dtype=np.int8) + np.array([4,5,6],dtype=np.int8)) +print(np.array([1,2,3],dtype=np.uint8) + np.array([4,5,6],dtype=np.uint16)) +print(np.array([1,2,3],dtype=np.int8) + np.array([4,5,6],dtype=np.uint16)) +print(np.array([1,2,3],dtype=np.uint8) + np.array([4,5,6],dtype=np.int16)) +print(np.array([1,2,3],dtype=np.int8) + np.array([4,5,6],dtype=np.int16)) +print(np.array([1,2,3],dtype=np.uint16) + np.array([4,5,6],dtype=np.uint16)) +print(np.array([1,2,3],dtype=np.int16) + np.array([4,5,6],dtype=np.int16)) +print(np.array([1,2,3],dtype=np.int16) + np.array([4,5,6],dtype=np.uint16)) + +print(np.array([1,2,3],dtype=np.uint8) + np.array([4,5,6],dtype=np.float)) +print(np.array([1,2,3],dtype=np.int8) + np.array([4,5,6],dtype=np.float)) +print(np.array([1,2,3],dtype=np.uint16) + np.array([4,5,6],dtype=np.float)) +print(np.array([1,2,3],dtype=np.int16) + np.array([4,5,6],dtype=np.float)) +print(np.array([1,2,3],dtype=np.int16) + np.array([4,5,6],dtype=np.float)) + +a = np.array([1, 2, 3, 4, 5], dtype=np.uint8) +for i, _a in enumerate(a): + print("element %d in a:"%i, _a) + diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/savetxt.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/savetxt.py new file mode 100644 index 00000000..857c910e --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/savetxt.py @@ -0,0 +1,38 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +a = np.array(range(9)) + +print('savetxt with linear arrays') +np.savetxt('savetxt.dat', a) + +with open('savetxt.dat', 'r') as fin: + print(fin.read()) + +a = a.reshape((3, 3)) + +print('savetxt with no keyword arguments') +np.savetxt('savetxt.dat', a) + +with open('savetxt.dat', 'r') as fin: + print(fin.read()) + +print('savetxt with delimiter') +np.savetxt('savetxt.dat', a, delimiter=',') + +with open('savetxt.dat', 'r') as fin: + print(fin.read()) + +print('savetxt with header') +np.savetxt('savetxt.dat', a, header='column1 column2 column3') + +with open('savetxt.dat', 'r') as fin: + print(fin.read()) + +print('savetxt with footer') +np.savetxt('savetxt.dat', a, footer='written data file') + +with open('savetxt.dat', 'r') as fin: + print(fin.read()) \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/signal.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/signal.py new file mode 100644 index 00000000..d7a6412b --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/signal.py @@ -0,0 +1,37 @@ +import math +try: + from ulab import numpy as np + from ulab import scipy as spy +except ImportError: + import numpy as np + import scipy as spy + +x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=np.float) +sos = np.array([[1, 2, 3, 1, 5, 6], [1, 2, 3, 1, 5, 6]],dtype=np.float) +result = spy.signal.sosfilt(sos, x) + +ref_result = np.array([0.0000e+00, 1.0000e+00, -4.0000e+00, 2.4000e+01, -1.0400e+02, 4.4000e+02, -1.7280e+03, 6.5320e+03, -2.3848e+04, 8.4864e+04], dtype=np.float) +cmp_result = [] +for p,q in zip(list(result), list(ref_result)): + cmp_result.append(math.isclose(p, q, rel_tol=1e-06, abs_tol=1e-06)) +print(cmp_result) + +x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) +sos = np.array([[1, 2, 3, 1, 5, 6], [1, 2, 3, 1, 5, 6]],dtype=np.float) +zi = np.array([[1, 2], [3, 4]],dtype=np.float) +y, zo = spy.signal.sosfilt(sos, x, zi=zi) + +y_ref = np.array([ 4.00000e+00, -1.60000e+01, 6.30000e+01, -2.27000e+02, 8.03000e+02, -2.75100e+03, 9.27100e+03, -3.07750e+04, 1.01067e+05, -3.28991e+05], dtype=np.float) +zo_ref = np.array([[37242.0, 74835.],[1026187.0, 1936542.0]], dtype=np.float) +cmp_result = [] +for p,q in zip(list(y), list(y_ref)): + cmp_result.append(math.isclose(p, q, rel_tol=1e-06, abs_tol=1e-06)) +print(cmp_result) + +cmp_result = [] +for i in range(2): + temp = [] + for j in range(2): + temp.append(math.isclose(zo[i][j], zo_ref[i][j], rel_tol=1E-9, abs_tol=1E-9)) + cmp_result.append(temp) +print(cmp_result) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/size.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/size.py new file mode 100644 index 00000000..636bf1f2 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/size.py @@ -0,0 +1,10 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +a = np.zeros((3, 4)) + +print(np.size(a, axis=0)) +print(np.size(a, axis=1)) +print(np.size(a, axis=None)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/sort.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/sort.py new file mode 100644 index 00000000..d91bb6fd --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/sort.py @@ -0,0 +1,18 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float) + +for dtype in dtypes: + print() + a = np.array([], dtype=dtype) + print(np.sort(a, axis=0)) + print(list(np.argsort(a, axis=0))) + + a = np.array([4, 1, 3, 2], dtype=dtype) + print(np.sort(a, axis=0)) + print(list(np.argsort(a, axis=0))) + + diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/vectorize.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/vectorize.py new file mode 100644 index 00000000..8cb6e104 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/vectorize.py @@ -0,0 +1,18 @@ +try: + from ulab import numpy as np +except: + import numpy as np + + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float) + +square = np.vectorize(lambda n: n*n) + +for dtype in dtypes: + a = np.array(range(9), dtype=dtype).reshape((3, 3)) + print(a) + print(square(a)) + + b = a[:,2] + print(square(b)) + print() diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/where.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/where.py new file mode 100644 index 00000000..18bf1cce --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/where.py @@ -0,0 +1,18 @@ +from ulab import numpy as np + + +a = np.array(range(8)) + +print(np.where(a < 4, 1, 0)) +print(np.where(a < 4, 2 * a, 0)) + +a = np.array(range(12)).reshape((3, 4)) +print(np.where(a < 6, a, -1)) + +b = np.array(range(4)) +print(np.where(a < 6, 10 + b, -1)) + +# test upcasting here +b = np.array(range(4), dtype=np.uint8) +c = np.array([25, 25, 25, 25], dtype=np.int16) +print(np.where(a < 6, b, c)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/zeros.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/zeros.py new file mode 100644 index 00000000..8e86f9d3 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/numpy/zeros.py @@ -0,0 +1,28 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float) + +print(np.zeros(3)) +print(np.zeros((3,3))) + +for dtype in dtypes: + print(np.zeros((3,3), dtype=dtype)) + print(np.zeros((4,2), dtype=dtype)) + +try: + np.zeros((1<<31, 1<<31)) +except ValueError: + print("ValueError") + +try: + np.zeros((2147483653, 2147483649)) +except ValueError: + print("ValueError") + +try: + np.zeros((194899806, 189294637612)) +except ValueError: + print("ValueError") diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/scipy/cho_solve.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/scipy/cho_solve.py new file mode 100644 index 00000000..57643c81 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/scipy/cho_solve.py @@ -0,0 +1,29 @@ +import math + +try: + from ulab import scipy, numpy as np +except ImportError: + import scipy + import numpy as np + +## test cholesky solve +L = np.array([[3, 0, 0, 0], [2, 1, 0, 0], [1, 0, 1, 0], [1, 2, 1, 8]]) +b = np.array([4, 2, 4, 2]) + +# L needs to be a lower triangular matrix +result = scipy.linalg.cho_solve(L, b) +ref_result = np.array([-0.01388888888888906, -0.6458333333333331, 2.677083333333333, -0.01041666666666667]) + +for i in range(4): + print(math.isclose(result[i], ref_result[i], rel_tol=1E-6, abs_tol=1E-6)) + +## test cholesky and cho_solve together +C = np.array([[18, 22, 54, 42], [22, 70, 86, 62], [54, 86, 174, 134], [42, 62, 134, 106]]) +L = np.linalg.cholesky(C) + +# L is a lower triangular matrix obtained by performing cholesky of positive-definite linear system +result = scipy.linalg.cho_solve(L, b) +ref_result = np.array([6.5625, 1.1875, -2.9375, 0.4375]) + +for i in range(4): + print(math.isclose(result[i], ref_result[i], rel_tol=1E-6, abs_tol=1E-6)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/scipy/solve_triangular.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/scipy/solve_triangular.py new file mode 100644 index 00000000..fdb67439 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/scipy/solve_triangular.py @@ -0,0 +1,22 @@ +import math + +try: + from ulab import scipy, numpy as np +except ImportError: + import scipy + import numpy as np + +A = np.array([[3, 0, 2, 6], [2, 1, 0, 1], [1, 0, 1, 4], [1, 2, 1, 8]]) +b = np.array([4, 2, 4, 2]) + +# forward substitution +result = scipy.linalg.solve_triangular(A, b, lower=True) +ref_result = np.array([1.333333333, -0.666666666, 2.666666666, -0.083333333]) +for i in range(4): + print(math.isclose(result[i], ref_result[i], rel_tol=1E-6, abs_tol=1E-6)) + +# backward substitution +result = scipy.linalg.solve_triangular(A, b, lower=False) +ref_result = np.array([-1.166666666, 1.75, 3.0, 0.25]) +for i in range(4): + print(math.isclose(result[i], ref_result[i], rel_tol=1E-6, abs_tol=1E-6)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/scipy/sosfilt.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/scipy/sosfilt.py new file mode 100644 index 00000000..015d6728 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/scipy/sosfilt.py @@ -0,0 +1,13 @@ +try: + from ulab import numpy as np + from ulab import scipy as spy +except: + import numpy as np + import scipy as spy + +x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) +sos = [[1, 2, 3, 1, 5, 6], [1, 2, 3, 1, 5, 6], [1, 2, 3, 1, 5, 6]] +zi = np.array([[1, 2], [3, 4], [5, 6]],dtype=np.float) +y, zo = spy.signal.sosfilt(sos, x, zi=zi) +print('y: ', y) +print('zo: ', zo) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/2d/utils/from_buffer.py b/components/3rd_party/omv/omv/modules/ulab/tests/2d/utils/from_buffer.py new file mode 100644 index 00000000..64a98972 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/2d/utils/from_buffer.py @@ -0,0 +1,22 @@ +from ulab import numpy as np +from ulab import utils + +a = bytearray([1, 0, 0, 1, 0, 255, 255, 255]) +print(utils.from_uint16_buffer(a)) +a = bytearray([1, 0, 0, 1, 0, 255, 255, 255]) +print(utils.from_int16_buffer(a)) + +a = bytearray([1, 0, 0, 1, 0, 255, 255, 255]) +print(utils.from_uint32_buffer(a)) +a = bytearray([1, 0, 0, 1, 0, 255, 255, 255]) +print(utils.from_int32_buffer(a)) + +a = bytearray([1, 0, 0, 1, 0, 0, 255, 255]) +print(utils.from_uint32_buffer(a)) +a = bytearray([1, 0, 0, 1, 0, 0, 255, 255]) +print(utils.from_int32_buffer(a)) + +a = bytearray([1, 0, 0, 0, 0, 0, 0, 1]) +print(utils.from_uint32_buffer(a)) +a = bytearray([1, 0, 0, 0, 0, 0, 0, 1]) +print(utils.from_int32_buffer(a)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/3d/complex/complex_exp.py b/components/3rd_party/omv/omv/modules/ulab/tests/3d/complex/complex_exp.py new file mode 100644 index 00000000..ef36e226 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/3d/complex/complex_exp.py @@ -0,0 +1,24 @@ +# this test is meaningful only, when the firmware supports complex arrays + +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float, np.complex) + +for dtype in dtypes: + a = np.array(range(4), dtype=dtype) + b = a.reshape((2, 2)) + print('\narray:\n', a) + print('\nexponential:\n', np.exp(a)) + print('\narray:\n', b) + print('\nexponential:\n', np.exp(b)) + +a = np.array([0, 1j, 2+2j, 3-3j], dtype=np.complex) +b = np.array([[0, 1j, 2+2j, 3-3j], [0, 1j, 2+2j, 3-3j]], dtype=np.complex) +c = np.array([[[0, 1j, 2+2j, 3-3j], [0, 1j, 2+2j, 3-3j]], [[0, 1j, 2+2j, 3-3j], [0, 1j, 2+2j, 3-3j]]], dtype=np.complex) + +for m in (a, b, c): + print('\n\narray:\n', m) + print('\nexponential:\n', np.exp(m)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/3d/complex/complex_sqrt.py b/components/3rd_party/omv/omv/modules/ulab/tests/3d/complex/complex_sqrt.py new file mode 100644 index 00000000..4bc9def0 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/3d/complex/complex_sqrt.py @@ -0,0 +1,26 @@ +# this test is meaningful only, when the firmware supports complex arrays + +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float, np.complex) + +for dtype in dtypes: + a = np.array(range(8), dtype=dtype) + b = a.reshape((2, 2, 2)) + outtype = np.float if dtype is not np.complex else np.complex + print('\narray:\n', a) + print('\nsquare root:\n', np.sqrt(a, dtype=outtype)) + print('\narray:\n', b) + print('\nsquare root:\n', np.sqrt(b, dtype=outtype)) + + +a = np.array([0, 1j, 2+2j, 3-3j], dtype=np.complex) +b = np.array([0, 1j, 2+2j, 3-3j] * 2, dtype=np.complex).reshape((2, 4)) +c = np.array([0, 1j, 2+2j, 3-3j] * 2, dtype=np.complex).reshape((2, 2, 2)) + +for m in (a, b, c): + print('\n\narray:\n', m) + print('\nsquare root:\n', np.sqrt(m, dtype=np.complex)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/3d/complex/imag_real.py b/components/3rd_party/omv/omv/modules/ulab/tests/3d/complex/imag_real.py new file mode 100644 index 00000000..1e12a8df --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/3d/complex/imag_real.py @@ -0,0 +1,28 @@ +# this test is meaningful only, when the firmware supports complex arrays + +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float, np.complex) + +for dtype in dtypes: + a = np.array(range(8), dtype=dtype) + print('\narray:\n', a) + print('\nreal part:\n', np.real(a)) + print('\nimaginary part:\n', np.imag(a)) + for m in (a.reshape((2, 4)), a.reshape((2, 2, 2))): + print('\narray:\n', m) + print('\nreal part:\n', np.real(m)) + print('\nimaginary part:\n', np.imag(m), '\n') + + +a = np.array([0, 1j, 2+2j, 3-3j], dtype=np.complex) +b = np.array([[0, 1j, 2+2j, 3-3j], [0, 1j, 2+2j, 3-3j]], dtype=np.complex) +c = np.array([[[0, 1j, 2+2j, 3-3j], [0, 1j, 2+2j, 3-3j]], [[0, 1j, 2+2j, 3-3j], [0, 1j, 2+2j, 3-3j]]], dtype=np.complex) + +for m in (a, b, c): + print('\n\narray:\n', m) + print('\nreal part:\n', np.real(m)) + print('\nimaginary part:\n', np.imag(m)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/3d/numpy/create.py b/components/3rd_party/omv/omv/modules/ulab/tests/3d/numpy/create.py new file mode 100644 index 00000000..a5c1fa15 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/3d/numpy/create.py @@ -0,0 +1,2 @@ +from ulab import numpy as np +print(sum(np.ones((3,2,4)))) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/4d/complex/complex_exp.py b/components/3rd_party/omv/omv/modules/ulab/tests/4d/complex/complex_exp.py new file mode 100644 index 00000000..63ed8732 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/4d/complex/complex_exp.py @@ -0,0 +1,26 @@ +# this test is meaningful only, when the firmware supports complex arrays + +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float, np.complex) + +for dtype in dtypes: + a = np.array(range(4), dtype=dtype) + b = a.reshape((2, 2)) + print('\narray:\n', a) + print('\nexponential:\n', np.exp(a)) + print('\narray:\n', b) + print('\nexponential:\n', np.exp(b)) + + +a = np.array([0, 1j, 2+2j, 3-3j], dtype=np.complex) +b = np.array([0, 1j, 2+2j, 3-3j] * 2, dtype=np.complex).reshape((2, 4)) +c = np.array([0, 1j, 2+2j, 3-3j] * 2, dtype=np.complex).reshape((2, 2, 2)) +d = np.array([0, 1j, 2+2j, 3-3j] * 4, dtype=np.complex).reshape((2, 2, 2, 2)) + +for m in (a, b, c, d): + print('\n\narray:\n', m) + print('\nexponential:\n', np.exp(m)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/4d/complex/complex_sqrt.py b/components/3rd_party/omv/omv/modules/ulab/tests/4d/complex/complex_sqrt.py new file mode 100644 index 00000000..052a07d7 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/4d/complex/complex_sqrt.py @@ -0,0 +1,27 @@ +# this test is meaningful only, when the firmware supports complex arrays + +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float, np.complex) + +for dtype in dtypes: + a = np.array(range(16), dtype=dtype) + b = a.reshape((2, 2, 2, 2)) + outtype = np.float if dtype is not np.complex else np.complex + print('\narray:\n', a) + print('\nsquare root:\n', np.sqrt(a, dtype=outtype)) + print('\narray:\n', b) + print('\nsquare root:\n', np.sqrt(b, dtype=outtype)) + + +a = np.array([0, 1j, 2+2j, 3-3j], dtype=np.complex) +b = np.array([0, 1j, 2+2j, 3-3j] * 2, dtype=np.complex).reshape((2, 4)) +c = np.array([0, 1j, 2+2j, 3-3j] * 2, dtype=np.complex).reshape((2, 2, 2)) +d = np.array([0, 1j, 2+2j, 3-3j] * 4, dtype=np.complex).reshape((2, 2, 2, 2)) + +for m in (a, b, c, d): + print('\n\narray:\n', m) + print('\nsquare root:\n', np.sqrt(m, dtype=np.complex)) diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/4d/complex/imag_real.py b/components/3rd_party/omv/omv/modules/ulab/tests/4d/complex/imag_real.py new file mode 100644 index 00000000..63b9da5e --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/4d/complex/imag_real.py @@ -0,0 +1,29 @@ +# this test is meaningful only, when the firmware supports complex arrays + +try: + from ulab import numpy as np +except: + import numpy as np + +dtypes = (np.uint8, np.int8, np.uint16, np.int16, np.float, np.complex) + +for dtype in dtypes: + a = np.array(range(16), dtype=dtype) + print('\narray:\n', a) + print('\nreal part:\n', np.real(a)) + print('\nimaginary part:\n', np.imag(a)) + for m in (a.reshape((4, 4)), a.reshape((2, 2, 4)), a.reshape((2, 2, 2, 2))): + print('\narray:\n', m) + print('\nreal part:\n', np.real(m)) + print('\nimaginary part:\n', np.imag(m), '\n') + + +a = np.array([0, 1j, 2+2j, 3-3j], dtype=np.complex) +b = np.array([0, 1j, 2+2j, 3-3j] * 2, dtype=np.complex).reshape((2, 4)) +c = np.array([0, 1j, 2+2j, 3-3j] * 2, dtype=np.complex).reshape((2, 2, 2)) +d = np.array([0, 1j, 2+2j, 3-3j] * 4, dtype=np.complex).reshape((2, 2, 2, 2)) + +for m in (a, b, c, d): + print('\n\narray:\n', m) + print('\nreal part:\n', np.real(m)) + print('\nimaginary part:\n', np.imag(m)) \ No newline at end of file diff --git a/components/3rd_party/omv/omv/modules/ulab/tests/4d/numpy/create.py b/components/3rd_party/omv/omv/modules/ulab/tests/4d/numpy/create.py new file mode 100644 index 00000000..64c344c2 --- /dev/null +++ b/components/3rd_party/omv/omv/modules/ulab/tests/4d/numpy/create.py @@ -0,0 +1,2 @@ +from ulab import numpy as np +print(sum(np.ones((3,4,2,5)))) diff --git a/components/3rd_party/omv/omv/ports/linux/cmsis/arm_compat.h b/components/3rd_party/omv/omv/ports/linux/cmsis/arm_compat.h new file mode 100644 index 00000000..c06f5153 --- /dev/null +++ b/components/3rd_party/omv/omv/ports/linux/cmsis/arm_compat.h @@ -0,0 +1,212 @@ +#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 + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuninitialized" + +#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 ( 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 ( 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; \ + }) + +#define arm_sin_f32(x) sinf(x) +#define arm_cos_f32(x) cosf(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__ + + +#pragma GCC diagnostic pop // -Wuninitialized + +#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 diff --git a/components/3rd_party/omv/omv/ports/linux/cmsis/arm_math.h b/components/3rd_party/omv/omv/ports/linux/cmsis/arm_math.h new file mode 100644 index 00000000..0addf58d --- /dev/null +++ b/components/3rd_party/omv/omv/ports/linux/cmsis/arm_math.h @@ -0,0 +1,11 @@ +#ifndef __ARM_MATH_H__ +#define __ARM_MATH_H__ + +#ifndef M_PI +#define M_PI 3.14159265f +#define M_PI_2 1.57079632f +#define M_PI_4 0.78539816f +#endif + + +#endif /* __ARM_MATH_H__ */ \ No newline at end of file diff --git a/components/3rd_party/omv/omv/ports/linux/file_utils.h b/components/3rd_party/omv/omv/ports/linux/file_utils.h new file mode 100644 index 00000000..7b93921b --- /dev/null +++ b/components/3rd_party/omv/omv/ports/linux/file_utils.h @@ -0,0 +1,4 @@ +#ifndef __FILE_UTILS_H__ +#define __FILE_UTILS_H__ + +#endif // __FILE_UTILS_H__ \ No newline at end of file diff --git a/components/3rd_party/omv/omv/ports/linux/imlib_config.h b/components/3rd_party/omv/omv/ports/linux/imlib_config.h new file mode 100644 index 00000000..2090c9ab --- /dev/null +++ b/components/3rd_party/omv/omv/ports/linux/imlib_config.h @@ -0,0 +1,168 @@ +/* + * 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. + */ +#ifndef __IMLIB_CONFIG_H__ +#define __IMLIB_CONFIG_H__ + +// 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 ISP ops +#define IMLIB_ENABLE_ISP_OPS + +// 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 rotation_corr() +#define IMLIB_ENABLE_ROTATION_CORR + +// Enable phasecorrelate() +#if defined(IMLIB_ENABLE_ROTATION_CORR) +#define IMLIB_ENABLE_FIND_DISPLACEMENT +#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_rects() +#define IMLIB_ENABLE_FIND_RECTS + +// Enable find_qrcodes() (14 KB) +#define IMLIB_ENABLE_QRCODES + +// 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 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 PNG encoder/decoder +// #define IMLIB_ENABLE_PNG_ENCODER +// #define IMLIB_ENABLE_PNG_DECODER + +// Stereo Imaging +// #define IMLIB_ENABLE_STEREO_DISPARITY + +// Enable python +#define IMLIB_ENABLE_PYTHON + +// Enable SIPEED code +#define IMLIB_ENABLE_SIPEED_CODE + +#endif //__IMLIB_CONFIG_H__ diff --git a/components/3rd_party/omv/omv/ports/linux/omv_boardconfig.h b/components/3rd_party/omv/omv/ports/linux/omv_boardconfig.h new file mode 100644 index 00000000..76a8798a --- /dev/null +++ b/components/3rd_party/omv/omv/ports/linux/omv_boardconfig.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. + * + * Board configuration and pin definitions. + */ +#ifndef __OMV_BOARDCONFIG_H__ +#define __OMV_BOARDCONFIG_H__ + +#include "arm_compat.h" +#include "stdbool.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// minimum fb alloc size +#define OMV_FB_ALLOC_SIZE (1 * 1024 * 1024) + +#ifndef PI +#define PI (3.1415926) +#endif + +#ifdef __cplusplus +} +#endif + +#endif //__OMV_BOARDCONFIG_H__ diff --git a/components/3rd_party/omv/omv/ports/linux/oofatfs/diskio.h b/components/3rd_party/omv/omv/ports/linux/oofatfs/diskio.h new file mode 100644 index 00000000..608937fa --- /dev/null +++ b/components/3rd_party/omv/omv/ports/linux/oofatfs/diskio.h @@ -0,0 +1,77 @@ +/*-----------------------------------------------------------------------/ +/ Low level disk interface modlue include file (C)ChaN, 2014 / +/-----------------------------------------------------------------------*/ + +#ifndef _DISKIO_DEFINED +#define _DISKIO_DEFINED + +#ifdef __cplusplus +extern "C" { +#endif + +/* Status of Disk Functions */ +typedef BYTE DSTATUS; + +/* Results of Disk Functions */ +typedef enum { + RES_OK = 0, /* 0: Successful */ + RES_ERROR, /* 1: R/W Error */ + RES_WRPRT, /* 2: Write Protected */ + RES_NOTRDY, /* 3: Not Ready */ + RES_PARERR /* 4: Invalid Parameter */ +} DRESULT; + + +/*---------------------------------------*/ +/* Prototypes for disk control functions */ + + +DRESULT disk_read (void *drv, BYTE* buff, DWORD sector, UINT count); +DRESULT disk_write (void *drv, const BYTE* buff, DWORD sector, UINT count); +DRESULT disk_ioctl (void *drv, BYTE cmd, void* buff); + + +/* Disk Status Bits (DSTATUS) */ + +#define STA_NOINIT 0x01 /* Drive not initialized */ +#define STA_NODISK 0x02 /* No medium in the drive */ +#define STA_PROTECT 0x04 /* Write protected */ + + +/* Command code for disk_ioctrl fucntion */ + +/* Generic command (Used by FatFs) */ +#define CTRL_SYNC 0 /* Complete pending write process (needed at FF_FS_READONLY == 0) */ +#define GET_SECTOR_COUNT 1 /* Get media size (needed at FF_USE_MKFS == 1) */ +#define GET_SECTOR_SIZE 2 /* Get sector size (needed at FF_MAX_SS != FF_MIN_SS) */ +#define GET_BLOCK_SIZE 3 /* Get erase block size (needed at FF_USE_MKFS == 1) */ +#define CTRL_TRIM 4 /* Inform device that the data on the block of sectors is no longer used (needed at FF_USE_TRIM == 1) */ +#define IOCTL_INIT 5 +#define IOCTL_STATUS 6 + +/* Generic command (Not used by FatFs) */ +#define CTRL_POWER 5 /* Get/Set power status */ +#define CTRL_LOCK 6 /* Lock/Unlock media removal */ +#define CTRL_EJECT 7 /* Eject media */ +#define CTRL_FORMAT 8 /* Create physical format on the media */ + +/* MMC/SDC specific ioctl command */ +#define MMC_GET_TYPE 10 /* Get card type */ +#define MMC_GET_CSD 11 /* Get CSD */ +#define MMC_GET_CID 12 /* Get CID */ +#define MMC_GET_OCR 13 /* Get OCR */ +#define MMC_GET_SDSTAT 14 /* Get SD status */ +#define ISDIO_READ 55 /* Read data form SD iSDIO register */ +#define ISDIO_WRITE 56 /* Write data to SD iSDIO register */ +#define ISDIO_MRITE 57 /* Masked write data to SD iSDIO register */ + +/* ATA/CF specific ioctl command */ +#define ATA_GET_REV 20 /* Get F/W revision */ +#define ATA_GET_MODEL 21 /* Get model name */ +#define ATA_GET_SN 22 /* Get serial number */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/components/3rd_party/omv/omv/ports/linux/oofatfs/ff.c b/components/3rd_party/omv/omv/ports/linux/oofatfs/ff.c new file mode 100644 index 00000000..0c9d04fe --- /dev/null +++ b/components/3rd_party/omv/omv/ports/linux/oofatfs/ff.c @@ -0,0 +1,5947 @@ +/* This file is part of ooFatFs, a customised version of FatFs + * See https://github.com/micropython/oofatfs for details + */ + +/*----------------------------------------------------------------------------/ +/ FatFs - Generic FAT Filesystem Module R0.13c / +/-----------------------------------------------------------------------------/ +/ +/ Copyright (C) 2018, ChaN, all right reserved. +/ +/ FatFs module is an open source software. Redistribution and use of FatFs in +/ source and binary forms, with or without modification, are permitted provided +/ that the following condition is met: +/ +/ 1. Redistributions of source code must retain the above copyright notice, +/ this condition and the following disclaimer. +/ +/ This software is provided by the copyright holder and contributors "AS IS" +/ and any warranties related to this software are DISCLAIMED. +/ The copyright owner or contributors be NOT LIABLE for any damages caused +/ by use of this software. +/ +/----------------------------------------------------------------------------*/ + + +#include + +#include "ff.h" /* Declarations of FatFs API */ +#include "diskio.h" /* Declarations of device I/O functions */ + +// DIR has been renamed FF_DIR in the public API so it doesn't clash with POSIX +#define DIR FF_DIR + +/*-------------------------------------------------------------------------- + + Module Private Definitions + +---------------------------------------------------------------------------*/ + +#if FF_DEFINED != 86604 /* Revision ID */ +#error Wrong include file (ff.h). +#endif + + +/* Limits and boundaries */ +#define MAX_DIR 0x200000 /* Max size of FAT directory */ +#define MAX_DIR_EX 0x10000000 /* Max size of exFAT directory */ +#define MAX_FAT12 0xFF5 /* Max FAT12 clusters (differs from specs, but right for real DOS/Windows behavior) */ +#define MAX_FAT16 0xFFF5 /* Max FAT16 clusters (differs from specs, but right for real DOS/Windows behavior) */ +#define MAX_FAT32 0x0FFFFFF5 /* Max FAT32 clusters (not specified, practical limit) */ +#define MAX_EXFAT 0x7FFFFFFD /* Max exFAT clusters (differs from specs, implementation limit) */ + + +/* Character code support macros */ +#define IsUpper(c) ((c) >= 'A' && (c) <= 'Z') +#define IsLower(c) ((c) >= 'a' && (c) <= 'z') +#define IsDigit(c) ((c) >= '0' && (c) <= '9') +#define IsSurrogate(c) ((c) >= 0xD800 && (c) <= 0xDFFF) +#define IsSurrogateH(c) ((c) >= 0xD800 && (c) <= 0xDBFF) +#define IsSurrogateL(c) ((c) >= 0xDC00 && (c) <= 0xDFFF) + + +/* Additional file access control and file status flags for internal use */ +#define FA_SEEKEND 0x20 /* Seek to end of the file on file open */ +#define FA_MODIFIED 0x40 /* File has been modified */ +#define FA_DIRTY 0x80 /* FIL.buf[] needs to be written-back */ + + +/* Additional file attribute bits for internal use */ +#define AM_VOL 0x08 /* Volume label */ +#define AM_LFN 0x0F /* LFN entry */ +#define AM_MASK 0x3F /* Mask of defined bits */ + + +/* Name status flags in fn[11] */ +#define NSFLAG 11 /* Index of the name status byte */ +#define NS_LOSS 0x01 /* Out of 8.3 format */ +#define NS_LFN 0x02 /* Force to create LFN entry */ +#define NS_LAST 0x04 /* Last segment */ +#define NS_BODY 0x08 /* Lower case flag (body) */ +#define NS_EXT 0x10 /* Lower case flag (ext) */ +#define NS_DOT 0x20 /* Dot entry */ +#define NS_NOLFN 0x40 /* Do not find LFN */ +#define NS_NONAME 0x80 /* Not followed */ + + +/* exFAT directory entry types */ +#define ET_BITMAP 0x81 /* Allocation bitmap */ +#define ET_UPCASE 0x82 /* Up-case table */ +#define ET_VLABEL 0x83 /* Volume label */ +#define ET_FILEDIR 0x85 /* File and directory */ +#define ET_STREAM 0xC0 /* Stream extension */ +#define ET_FILENAME 0xC1 /* Name extension */ + + +/* FatFs refers the FAT structure as simple byte array instead of structure member +/ because the C structure is not binary compatible between different platforms */ + +#define BS_JmpBoot 0 /* x86 jump instruction (3-byte) */ +#define BS_OEMName 3 /* OEM name (8-byte) */ +#define BPB_BytsPerSec 11 /* Sector size [byte] (WORD) */ +#define BPB_SecPerClus 13 /* Cluster size [sector] (BYTE) */ +#define BPB_RsvdSecCnt 14 /* Size of reserved area [sector] (WORD) */ +#define BPB_NumFATs 16 /* Number of FATs (BYTE) */ +#define BPB_RootEntCnt 17 /* Size of root directory area for FAT [entry] (WORD) */ +#define BPB_TotSec16 19 /* Volume size (16-bit) [sector] (WORD) */ +#define BPB_Media 21 /* Media descriptor byte (BYTE) */ +#define BPB_FATSz16 22 /* FAT size (16-bit) [sector] (WORD) */ +#define BPB_SecPerTrk 24 /* Number of sectors per track for int13h [sector] (WORD) */ +#define BPB_NumHeads 26 /* Number of heads for int13h (WORD) */ +#define BPB_HiddSec 28 /* Volume offset from top of the drive (DWORD) */ +#define BPB_TotSec32 32 /* Volume size (32-bit) [sector] (DWORD) */ +#define BS_DrvNum 36 /* Physical drive number for int13h (BYTE) */ +#define BS_NTres 37 /* WindowsNT error flag (BYTE) */ +#define BS_BootSig 38 /* Extended boot signature (BYTE) */ +#define BS_VolID 39 /* Volume serial number (DWORD) */ +#define BS_VolLab 43 /* Volume label string (8-byte) */ +#define BS_FilSysType 54 /* Filesystem type string (8-byte) */ +#define BS_BootCode 62 /* Boot code (448-byte) */ +#define BS_55AA 510 /* Signature word (WORD) */ + +#define BPB_FATSz32 36 /* FAT32: FAT size [sector] (DWORD) */ +#define BPB_ExtFlags32 40 /* FAT32: Extended flags (WORD) */ +#define BPB_FSVer32 42 /* FAT32: Filesystem version (WORD) */ +#define BPB_RootClus32 44 /* FAT32: Root directory cluster (DWORD) */ +#define BPB_FSInfo32 48 /* FAT32: Offset of FSINFO sector (WORD) */ +#define BPB_BkBootSec32 50 /* FAT32: Offset of backup boot sector (WORD) */ +#define BS_DrvNum32 64 /* FAT32: Physical drive number for int13h (BYTE) */ +#define BS_NTres32 65 /* FAT32: Error flag (BYTE) */ +#define BS_BootSig32 66 /* FAT32: Extended boot signature (BYTE) */ +#define BS_VolID32 67 /* FAT32: Volume serial number (DWORD) */ +#define BS_VolLab32 71 /* FAT32: Volume label string (8-byte) */ +#define BS_FilSysType32 82 /* FAT32: Filesystem type string (8-byte) */ +#define BS_BootCode32 90 /* FAT32: Boot code (420-byte) */ + +#define BPB_ZeroedEx 11 /* exFAT: MBZ field (53-byte) */ +#define BPB_VolOfsEx 64 /* exFAT: Volume offset from top of the drive [sector] (QWORD) */ +#define BPB_TotSecEx 72 /* exFAT: Volume size [sector] (QWORD) */ +#define BPB_FatOfsEx 80 /* exFAT: FAT offset from top of the volume [sector] (DWORD) */ +#define BPB_FatSzEx 84 /* exFAT: FAT size [sector] (DWORD) */ +#define BPB_DataOfsEx 88 /* exFAT: Data offset from top of the volume [sector] (DWORD) */ +#define BPB_NumClusEx 92 /* exFAT: Number of clusters (DWORD) */ +#define BPB_RootClusEx 96 /* exFAT: Root directory start cluster (DWORD) */ +#define BPB_VolIDEx 100 /* exFAT: Volume serial number (DWORD) */ +#define BPB_FSVerEx 104 /* exFAT: Filesystem version (WORD) */ +#define BPB_VolFlagEx 106 /* exFAT: Volume flags (WORD) */ +#define BPB_BytsPerSecEx 108 /* exFAT: Log2 of sector size in unit of byte (BYTE) */ +#define BPB_SecPerClusEx 109 /* exFAT: Log2 of cluster size in unit of sector (BYTE) */ +#define BPB_NumFATsEx 110 /* exFAT: Number of FATs (BYTE) */ +#define BPB_DrvNumEx 111 /* exFAT: Physical drive number for int13h (BYTE) */ +#define BPB_PercInUseEx 112 /* exFAT: Percent in use (BYTE) */ +#define BPB_RsvdEx 113 /* exFAT: Reserved (7-byte) */ +#define BS_BootCodeEx 120 /* exFAT: Boot code (390-byte) */ + +#define DIR_Name 0 /* Short file name (11-byte) */ +#define DIR_Attr 11 /* Attribute (BYTE) */ +#define DIR_NTres 12 /* Lower case flag (BYTE) */ +#define DIR_CrtTime10 13 /* Created time sub-second (BYTE) */ +#define DIR_CrtTime 14 /* Created time (DWORD) */ +#define DIR_LstAccDate 18 /* Last accessed date (WORD) */ +#define DIR_FstClusHI 20 /* Higher 16-bit of first cluster (WORD) */ +#define DIR_ModTime 22 /* Modified time (DWORD) */ +#define DIR_FstClusLO 26 /* Lower 16-bit of first cluster (WORD) */ +#define DIR_FileSize 28 /* File size (DWORD) */ +#define LDIR_Ord 0 /* LFN: LFN order and LLE flag (BYTE) */ +#define LDIR_Attr 11 /* LFN: LFN attribute (BYTE) */ +#define LDIR_Type 12 /* LFN: Entry type (BYTE) */ +#define LDIR_Chksum 13 /* LFN: Checksum of the SFN (BYTE) */ +#define LDIR_FstClusLO 26 /* LFN: MBZ field (WORD) */ +#define XDIR_Type 0 /* exFAT: Type of exFAT directory entry (BYTE) */ +#define XDIR_NumLabel 1 /* exFAT: Number of volume label characters (BYTE) */ +#define XDIR_Label 2 /* exFAT: Volume label (11-WORD) */ +#define XDIR_CaseSum 4 /* exFAT: Sum of case conversion table (DWORD) */ +#define XDIR_NumSec 1 /* exFAT: Number of secondary entries (BYTE) */ +#define XDIR_SetSum 2 /* exFAT: Sum of the set of directory entries (WORD) */ +#define XDIR_Attr 4 /* exFAT: File attribute (WORD) */ +#define XDIR_CrtTime 8 /* exFAT: Created time (DWORD) */ +#define XDIR_ModTime 12 /* exFAT: Modified time (DWORD) */ +#define XDIR_AccTime 16 /* exFAT: Last accessed time (DWORD) */ +#define XDIR_CrtTime10 20 /* exFAT: Created time subsecond (BYTE) */ +#define XDIR_ModTime10 21 /* exFAT: Modified time subsecond (BYTE) */ +#define XDIR_CrtTZ 22 /* exFAT: Created timezone (BYTE) */ +#define XDIR_ModTZ 23 /* exFAT: Modified timezone (BYTE) */ +#define XDIR_AccTZ 24 /* exFAT: Last accessed timezone (BYTE) */ +#define XDIR_GenFlags 33 /* exFAT: General secondary flags (BYTE) */ +#define XDIR_NumName 35 /* exFAT: Number of file name characters (BYTE) */ +#define XDIR_NameHash 36 /* exFAT: Hash of file name (WORD) */ +#define XDIR_ValidFileSize 40 /* exFAT: Valid file size (QWORD) */ +#define XDIR_FstClus 52 /* exFAT: First cluster of the file data (DWORD) */ +#define XDIR_FileSize 56 /* exFAT: File/Directory size (QWORD) */ + +#define SZDIRE 32 /* Size of a directory entry */ +#define DDEM 0xE5 /* Deleted directory entry mark set to DIR_Name[0] */ +#define RDDEM 0x05 /* Replacement of the character collides with DDEM */ +#define LLEF 0x40 /* Last long entry flag in LDIR_Ord */ + +#define FSI_LeadSig 0 /* FAT32 FSI: Leading signature (DWORD) */ +#define FSI_StrucSig 484 /* FAT32 FSI: Structure signature (DWORD) */ +#define FSI_Free_Count 488 /* FAT32 FSI: Number of free clusters (DWORD) */ +#define FSI_Nxt_Free 492 /* FAT32 FSI: Last allocated cluster (DWORD) */ + +#define MBR_Table 446 /* MBR: Offset of partition table in the MBR */ +#define SZ_PTE 16 /* MBR: Size of a partition table entry */ +#define PTE_Boot 0 /* MBR PTE: Boot indicator */ +#define PTE_StHead 1 /* MBR PTE: Start head */ +#define PTE_StSec 2 /* MBR PTE: Start sector */ +#define PTE_StCyl 3 /* MBR PTE: Start cylinder */ +#define PTE_System 4 /* MBR PTE: System ID */ +#define PTE_EdHead 5 /* MBR PTE: End head */ +#define PTE_EdSec 6 /* MBR PTE: End sector */ +#define PTE_EdCyl 7 /* MBR PTE: End cylinder */ +#define PTE_StLba 8 /* MBR PTE: Start in LBA */ +#define PTE_SizLba 12 /* MBR PTE: Size in LBA */ + + +/* Post process on fatal error in the file operations */ +#define ABORT(fs, res) { fp->err = (BYTE)(res); LEAVE_FF(fs, res); } + + +/* Re-entrancy related */ +#if FF_FS_REENTRANT +#if FF_USE_LFN == 1 +#error Static LFN work area cannot be used at thread-safe configuration +#endif +#define LEAVE_FF(fs, res) { unlock_fs(fs, res); return res; } +#else +#define LEAVE_FF(fs, res) return res +#endif + + +/* Definitions of volume - physical location conversion */ +#if FF_MULTI_PARTITION +#define LD2PT(fs) (fs->part) /* Get partition index */ +#else +#define LD2PT(fs) 0 /* Find first valid partition or in SFD */ +#endif + + +/* Definitions of sector size */ +#if (FF_MAX_SS < FF_MIN_SS) || (FF_MAX_SS != 512 && FF_MAX_SS != 1024 && FF_MAX_SS != 2048 && FF_MAX_SS != 4096) || (FF_MIN_SS != 512 && FF_MIN_SS != 1024 && FF_MIN_SS != 2048 && FF_MIN_SS != 4096) +#error Wrong sector size configuration +#endif +#if FF_MAX_SS == FF_MIN_SS +#define SS(fs) ((UINT)FF_MAX_SS) /* Fixed sector size */ +#else +#define SS(fs) ((fs)->ssize) /* Variable sector size */ +#endif + + +/* Timestamp */ +#if FF_FS_NORTC == 1 +#if FF_NORTC_YEAR < 1980 || FF_NORTC_YEAR > 2107 || FF_NORTC_MON < 1 || FF_NORTC_MON > 12 || FF_NORTC_MDAY < 1 || FF_NORTC_MDAY > 31 +#error Invalid FF_FS_NORTC settings +#endif +#define GET_FATTIME() ((DWORD)(FF_NORTC_YEAR - 1980) << 25 | (DWORD)FF_NORTC_MON << 21 | (DWORD)FF_NORTC_MDAY << 16) +#else +#define GET_FATTIME() get_fattime() +#endif + + +/* File lock controls */ +#if FF_FS_LOCK != 0 +#if FF_FS_READONLY +#error FF_FS_LOCK must be 0 at read-only configuration +#endif +typedef struct { + FATFS *fs; /* Object ID 1, volume (NULL:blank entry) */ + DWORD clu; /* Object ID 2, containing directory (0:root) */ + DWORD ofs; /* Object ID 3, offset in the directory */ + WORD ctr; /* Object open counter, 0:none, 0x01..0xFF:read mode open count, 0x100:write mode */ +} FILESEM; +#endif + + +/* SBCS up-case tables (\x80-\xFF) */ +#define TBL_CT437 {0x80,0x9A,0x45,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \ + 0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x41,0x49,0x4F,0x55,0xA5,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} +#define TBL_CT720 {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} +#define TBL_CT737 {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \ + 0x90,0x92,0x92,0x93,0x94,0x95,0x96,0x97,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87, \ + 0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0xAA,0x92,0x93,0x94,0x95,0x96, \ + 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, \ + 0x97,0xEA,0xEB,0xEC,0xE4,0xED,0xEE,0xEF,0xF5,0xF0,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} +#define TBL_CT771 {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, \ + 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \ + 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,0xDC,0xDE,0xDE, \ + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0xF0,0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF8,0xFA,0xFA,0xFC,0xFC,0xFE,0xFF} +#define TBL_CT775 {0x80,0x9A,0x91,0xA0,0x8E,0x95,0x8F,0x80,0xAD,0xED,0x8A,0x8A,0xA1,0x8D,0x8E,0x8F, \ + 0x90,0x92,0x92,0xE2,0x99,0x95,0x96,0x97,0x97,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9F, \ + 0xA0,0xA1,0xE0,0xA3,0xA3,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, \ + 0xB5,0xB6,0xB7,0xB8,0xBD,0xBE,0xC6,0xC7,0xA5,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE3,0xE8,0xE8,0xEA,0xEA,0xEE,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} +#define TBL_CT850 {0x43,0x55,0x45,0x41,0x41,0x41,0x41,0x43,0x45,0x45,0x45,0x49,0x49,0x49,0x41,0x41, \ + 0x45,0x92,0x92,0x4F,0x4F,0x4F,0x55,0x55,0x59,0x4F,0x55,0x4F,0x9C,0x4F,0x9E,0x9F, \ + 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0x41,0x41,0x41,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0x41,0x41,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD1,0xD1,0x45,0x45,0x45,0x49,0x49,0x49,0x49,0xD9,0xDA,0xDB,0xDC,0xDD,0x49,0xDF, \ + 0x4F,0xE1,0x4F,0x4F,0x4F,0x4F,0xE6,0xE8,0xE8,0x55,0x55,0x55,0x59,0x59,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} +#define TBL_CT852 {0x80,0x9A,0x90,0xB6,0x8E,0xDE,0x8F,0x80,0x9D,0xD3,0x8A,0x8A,0xD7,0x8D,0x8E,0x8F, \ + 0x90,0x91,0x91,0xE2,0x99,0x95,0x95,0x97,0x97,0x99,0x9A,0x9B,0x9B,0x9D,0x9E,0xAC, \ + 0xB5,0xD6,0xE0,0xE9,0xA4,0xA4,0xA6,0xA6,0xA8,0xA8,0xAA,0x8D,0xAC,0xB8,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBD,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC6,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD1,0xD1,0xD2,0xD3,0xD2,0xD5,0xD6,0xD7,0xB7,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE3,0xD5,0xE6,0xE6,0xE8,0xE9,0xE8,0xEB,0xED,0xED,0xDD,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xEB,0xFC,0xFC,0xFE,0xFF} +#define TBL_CT855 {0x81,0x81,0x83,0x83,0x85,0x85,0x87,0x87,0x89,0x89,0x8B,0x8B,0x8D,0x8D,0x8F,0x8F, \ + 0x91,0x91,0x93,0x93,0x95,0x95,0x97,0x97,0x99,0x99,0x9B,0x9B,0x9D,0x9D,0x9F,0x9F, \ + 0xA1,0xA1,0xA3,0xA3,0xA5,0xA5,0xA7,0xA7,0xA9,0xA9,0xAB,0xAB,0xAD,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB6,0xB6,0xB8,0xB8,0xB9,0xBA,0xBB,0xBC,0xBE,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD1,0xD1,0xD3,0xD3,0xD5,0xD5,0xD7,0xD7,0xDD,0xD9,0xDA,0xDB,0xDC,0xDD,0xE0,0xDF, \ + 0xE0,0xE2,0xE2,0xE4,0xE4,0xE6,0xE6,0xE8,0xE8,0xEA,0xEA,0xEC,0xEC,0xEE,0xEE,0xEF, \ + 0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF8,0xFA,0xFA,0xFC,0xFC,0xFD,0xFE,0xFF} +#define TBL_CT857 {0x80,0x9A,0x90,0xB6,0x8E,0xB7,0x8F,0x80,0xD2,0xD3,0xD4,0xD8,0xD7,0x49,0x8E,0x8F, \ + 0x90,0x92,0x92,0xE2,0x99,0xE3,0xEA,0xEB,0x98,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9E, \ + 0xB5,0xD6,0xE0,0xE9,0xA5,0xA5,0xA6,0xA6,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,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0x49,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xDE,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} +#define TBL_CT860 {0x80,0x9A,0x90,0x8F,0x8E,0x91,0x86,0x80,0x89,0x89,0x92,0x8B,0x8C,0x98,0x8E,0x8F, \ + 0x90,0x91,0x92,0x8C,0x99,0xA9,0x96,0x9D,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x86,0x8B,0x9F,0x96,0xA5,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} +#define TBL_CT861 {0x80,0x9A,0x90,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x8B,0x8B,0x8D,0x8E,0x8F, \ + 0x90,0x92,0x92,0x4F,0x99,0x8D,0x55,0x97,0x97,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9F, \ + 0xA4,0xA5,0xA6,0xA7,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} +#define TBL_CT862 {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, \ + 0x41,0x49,0x4F,0x55,0xA5,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} +#define TBL_CT863 {0x43,0x55,0x45,0x41,0x41,0x41,0x86,0x43,0x45,0x45,0x45,0x49,0x49,0x8D,0x41,0x8F, \ + 0x45,0x45,0x45,0x4F,0x45,0x49,0x55,0x55,0x98,0x4F,0x55,0x9B,0x9C,0x55,0x55,0x9F, \ + 0xA0,0xA1,0x4F,0x55,0xA4,0xA5,0xA6,0xA7,0x49,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} +#define TBL_CT864 {0x80,0x9A,0x45,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \ + 0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x41,0x49,0x4F,0x55,0xA5,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} +#define TBL_CT865 {0x80,0x9A,0x90,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \ + 0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x41,0x49,0x4F,0x55,0xA5,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} +#define TBL_CT866 {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, \ + 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \ + 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, \ + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0xF0,0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} +#define TBL_CT869 {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,0x86,0x9C,0x8D,0x8F,0x90, \ + 0x91,0x90,0x92,0x95,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,0xA4,0xA5,0xA6,0xD9,0xDA,0xDB,0xDC,0xA7,0xA8,0xDF, \ + 0xA9,0xAA,0xAC,0xAD,0xB5,0xB6,0xB7,0xB8,0xBD,0xBE,0xC6,0xC7,0xCF,0xCF,0xD0,0xEF, \ + 0xF0,0xF1,0xD1,0xD2,0xD3,0xF5,0xD4,0xF7,0xF8,0xF9,0xD5,0x96,0x95,0x98,0xFE,0xFF} + + +/* DBCS code range |----- 1st byte -----| |----------- 2nd byte -----------| */ +#define TBL_DC932 {0x81, 0x9F, 0xE0, 0xFC, 0x40, 0x7E, 0x80, 0xFC, 0x00, 0x00} +#define TBL_DC936 {0x81, 0xFE, 0x00, 0x00, 0x40, 0x7E, 0x80, 0xFE, 0x00, 0x00} +#define TBL_DC949 {0x81, 0xFE, 0x00, 0x00, 0x41, 0x5A, 0x61, 0x7A, 0x81, 0xFE} +#define TBL_DC950 {0x81, 0xFE, 0x00, 0x00, 0x40, 0x7E, 0xA1, 0xFE, 0x00, 0x00} + + +/* Macros for table definitions */ +#define MERGE_2STR(a, b) a ## b +#define MKCVTBL(hd, cp) MERGE_2STR(hd, cp) + + + + +/*-------------------------------------------------------------------------- + + Module Private Work Area + +---------------------------------------------------------------------------*/ +/* Remark: Variables defined here without initial value shall be guaranteed +/ zero/null at start-up. If not, the linker option or start-up routine is +/ not compliance with C standard. */ + +/*--------------------------------*/ +/* File/Volume controls */ +/*--------------------------------*/ + +#if FF_VOLUMES < 1 || FF_VOLUMES > 10 +#error Wrong FF_VOLUMES setting +#endif +static WORD Fsid; /* Filesystem mount ID */ + +#if FF_FS_LOCK != 0 +static FILESEM Files[FF_FS_LOCK]; /* Open object lock semaphores */ +#endif + +#if FF_STR_VOLUME_ID +#ifdef FF_VOLUME_STRS +static const char* const VolumeStr[FF_VOLUMES] = {FF_VOLUME_STRS}; /* Pre-defined volume ID */ +#endif +#endif + + +/*--------------------------------*/ +/* LFN/Directory working buffer */ +/*--------------------------------*/ + +#if FF_USE_LFN == 0 /* Non-LFN configuration */ +#if FF_FS_EXFAT +#error LFN must be enabled when enable exFAT +#endif +#define DEF_NAMBUF +#define INIT_NAMBUF(fs) +#define FREE_NAMBUF() +#define LEAVE_MKFS(res) return res + +#else /* LFN configurations */ +#if FF_MAX_LFN < 12 || FF_MAX_LFN > 255 +#error Wrong setting of FF_MAX_LFN +#endif +#if FF_LFN_BUF < FF_SFN_BUF || FF_SFN_BUF < 12 +#error Wrong setting of FF_LFN_BUF or FF_SFN_BUF +#endif +#if FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3 +#error Wrong setting of FF_LFN_UNICODE +#endif +static const BYTE LfnOfs[] = {1,3,5,7,9,14,16,18,20,22,24,28,30}; /* FAT: Offset of LFN characters in the directory entry */ +#define MAXDIRB(nc) ((nc + 44U) / 15 * SZDIRE) /* exFAT: Size of directory entry block scratchpad buffer needed for the name length */ + +#if FF_USE_LFN == 1 /* LFN enabled with static working buffer */ +#if FF_FS_EXFAT +static BYTE DirBuf[MAXDIRB(FF_MAX_LFN)]; /* Directory entry block scratchpad buffer */ +#endif +static WCHAR LfnBuf[FF_MAX_LFN + 1]; /* LFN working buffer */ +#define DEF_NAMBUF +#define INIT_NAMBUF(fs) +#define FREE_NAMBUF() +#define LEAVE_MKFS(res) return res + +#elif FF_USE_LFN == 2 /* LFN enabled with dynamic working buffer on the stack */ +#if FF_FS_EXFAT +#define DEF_NAMBUF WCHAR lbuf[FF_MAX_LFN+1]; BYTE dbuf[MAXDIRB(FF_MAX_LFN)]; /* LFN working buffer and directory entry block scratchpad buffer */ +#define INIT_NAMBUF(fs) { (fs)->lfnbuf = lbuf; (fs)->dirbuf = dbuf; } +#define FREE_NAMBUF() +#else +#define DEF_NAMBUF WCHAR lbuf[FF_MAX_LFN+1]; /* LFN working buffer */ +#define INIT_NAMBUF(fs) { (fs)->lfnbuf = lbuf; } +#define FREE_NAMBUF() +#endif +#define LEAVE_MKFS(res) return res + +#elif FF_USE_LFN == 3 /* LFN enabled with dynamic working buffer on the heap */ +#if FF_FS_EXFAT +#define DEF_NAMBUF WCHAR *lfn; /* Pointer to LFN working buffer and directory entry block scratchpad buffer */ +#define INIT_NAMBUF(fs) { lfn = ff_memalloc((FF_MAX_LFN+1)*2 + MAXDIRB(FF_MAX_LFN)); if (!lfn) LEAVE_FF(fs, FR_NOT_ENOUGH_CORE); (fs)->lfnbuf = lfn; (fs)->dirbuf = (BYTE*)(lfn+FF_MAX_LFN+1); } +#define FREE_NAMBUF() ff_memfree(lfn) +#else +#define DEF_NAMBUF WCHAR *lfn; /* Pointer to LFN working buffer */ +#define INIT_NAMBUF(fs) { lfn = ff_memalloc((FF_MAX_LFN+1)*2); if (!lfn) LEAVE_FF(fs, FR_NOT_ENOUGH_CORE); (fs)->lfnbuf = lfn; } +#define FREE_NAMBUF() ff_memfree(lfn) +#endif +#define LEAVE_MKFS(res) { if (!work) ff_memfree(buf); return res; } +#define MAX_MALLOC 0x8000 /* Must be >=FF_MAX_SS */ + +#else +#error Wrong setting of FF_USE_LFN + +#endif /* FF_USE_LFN == 1 */ +#endif /* FF_USE_LFN == 0 */ + + + +/*--------------------------------*/ +/* Code conversion tables */ +/*--------------------------------*/ + +#if FF_CODE_PAGE == 0 /* Run-time code page configuration */ +#define CODEPAGE CodePage +static WORD CodePage; /* Current code page */ +static const BYTE *ExCvt, *DbcTbl; /* Pointer to current SBCS up-case table and DBCS code range table below */ + +static const BYTE Ct437[] = TBL_CT437; +static const BYTE Ct720[] = TBL_CT720; +static const BYTE Ct737[] = TBL_CT737; +static const BYTE Ct771[] = TBL_CT771; +static const BYTE Ct775[] = TBL_CT775; +static const BYTE Ct850[] = TBL_CT850; +static const BYTE Ct852[] = TBL_CT852; +static const BYTE Ct855[] = TBL_CT855; +static const BYTE Ct857[] = TBL_CT857; +static const BYTE Ct860[] = TBL_CT860; +static const BYTE Ct861[] = TBL_CT861; +static const BYTE Ct862[] = TBL_CT862; +static const BYTE Ct863[] = TBL_CT863; +static const BYTE Ct864[] = TBL_CT864; +static const BYTE Ct865[] = TBL_CT865; +static const BYTE Ct866[] = TBL_CT866; +static const BYTE Ct869[] = TBL_CT869; +static const BYTE Dc932[] = TBL_DC932; +static const BYTE Dc936[] = TBL_DC936; +static const BYTE Dc949[] = TBL_DC949; +static const BYTE Dc950[] = TBL_DC950; + +#elif FF_CODE_PAGE < 900 /* Static code page configuration (SBCS) */ +#define CODEPAGE FF_CODE_PAGE +static const BYTE ExCvt[] = MKCVTBL(TBL_CT, FF_CODE_PAGE); + +#else /* Static code page configuration (DBCS) */ +#define CODEPAGE FF_CODE_PAGE +static const BYTE DbcTbl[] = MKCVTBL(TBL_DC, FF_CODE_PAGE); + +#endif + + + + +/*-------------------------------------------------------------------------- + + Module Private Functions + +---------------------------------------------------------------------------*/ + + +/*-----------------------------------------------------------------------*/ +/* Load/Store multi-byte word in the FAT structure */ +/*-----------------------------------------------------------------------*/ + +static WORD ld_word (const BYTE* ptr) /* Load a 2-byte little-endian word */ +{ + WORD rv; + + rv = ptr[1]; + rv = rv << 8 | ptr[0]; + return rv; +} + +static DWORD ld_dword (const BYTE* ptr) /* Load a 4-byte little-endian word */ +{ + DWORD rv; + + rv = ptr[3]; + rv = rv << 8 | ptr[2]; + rv = rv << 8 | ptr[1]; + rv = rv << 8 | ptr[0]; + return rv; +} + +#if FF_FS_EXFAT +static QWORD ld_qword (const BYTE* ptr) /* Load an 8-byte little-endian word */ +{ + QWORD rv; + + rv = ptr[7]; + rv = rv << 8 | ptr[6]; + rv = rv << 8 | ptr[5]; + rv = rv << 8 | ptr[4]; + rv = rv << 8 | ptr[3]; + rv = rv << 8 | ptr[2]; + rv = rv << 8 | ptr[1]; + rv = rv << 8 | ptr[0]; + return rv; +} +#endif + +#if !FF_FS_READONLY +static void st_word (BYTE* ptr, WORD val) /* Store a 2-byte word in little-endian */ +{ + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; +} + +static void st_dword (BYTE* ptr, DWORD val) /* Store a 4-byte word in little-endian */ +{ + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; +} + +#if FF_FS_EXFAT +static void st_qword (BYTE* ptr, QWORD val) /* Store an 8-byte word in little-endian */ +{ + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; +} +#endif +#endif /* !FF_FS_READONLY */ + + + +/*-----------------------------------------------------------------------*/ +/* String functions */ +/*-----------------------------------------------------------------------*/ + +// These were originally provided by the FatFs library but we use externally +// provided versions from C stdlib to (hopefully) reduce code size and use +// more efficient versions. +#define mem_cpy memcpy +#define mem_set memset +#define mem_cmp memcmp + + +/* Check if chr is contained in the string */ +static int chk_chr (const char* str, int chr) /* NZ:contained, ZR:not contained */ +{ + while (*str && *str != chr) str++; + return *str; +} + + +/* Test if the character is DBC 1st byte */ +static int dbc_1st (BYTE c) +{ +#if FF_CODE_PAGE == 0 /* Variable code page */ + if (DbcTbl && c >= DbcTbl[0]) { + if (c <= DbcTbl[1]) return 1; /* 1st byte range 1 */ + if (c >= DbcTbl[2] && c <= DbcTbl[3]) return 1; /* 1st byte range 2 */ + } +#elif FF_CODE_PAGE >= 900 /* DBCS fixed code page */ + if (c >= DbcTbl[0]) { + if (c <= DbcTbl[1]) return 1; + if (c >= DbcTbl[2] && c <= DbcTbl[3]) return 1; + } +#else /* SBCS fixed code page */ + if (c != 0) return 0; /* Always false */ +#endif + return 0; +} + + +/* Test if the character is DBC 2nd byte */ +static int dbc_2nd (BYTE c) +{ +#if FF_CODE_PAGE == 0 /* Variable code page */ + if (DbcTbl && c >= DbcTbl[4]) { + if (c <= DbcTbl[5]) return 1; /* 2nd byte range 1 */ + if (c >= DbcTbl[6] && c <= DbcTbl[7]) return 1; /* 2nd byte range 2 */ + if (c >= DbcTbl[8] && c <= DbcTbl[9]) return 1; /* 2nd byte range 3 */ + } +#elif FF_CODE_PAGE >= 900 /* DBCS fixed code page */ + if (c >= DbcTbl[4]) { + if (c <= DbcTbl[5]) return 1; + if (c >= DbcTbl[6] && c <= DbcTbl[7]) return 1; + if (c >= DbcTbl[8] && c <= DbcTbl[9]) return 1; + } +#else /* SBCS fixed code page */ + if (c != 0) return 0; /* Always false */ +#endif + return 0; +} + + +#if FF_USE_LFN + +/* Get a character from TCHAR string in defined API encodeing */ +static DWORD tchar2uni ( /* Returns character in UTF-16 encoding (>=0x10000 on double encoding unit, 0xFFFFFFFF on decode error) */ + const TCHAR** str /* Pointer to pointer to TCHAR string in configured encoding */ +) +{ + DWORD uc; + const TCHAR *p = *str; + +#if FF_LFN_UNICODE == 1 /* UTF-16 input */ + WCHAR wc; + + uc = *p++; /* Get a unit */ + if (IsSurrogate(uc)) { /* Surrogate? */ + wc = *p++; /* Get low surrogate */ + if (!IsSurrogateH(uc) || !IsSurrogateL(wc)) return 0xFFFFFFFF; /* Wrong surrogate? */ + uc = uc << 16 | wc; + } + +#elif FF_LFN_UNICODE == 2 /* UTF-8 input */ + BYTE b; + int nf; + + uc = (BYTE)*p++; /* Get a unit */ + if (uc & 0x80) { /* Multiple byte code? */ + if ((uc & 0xE0) == 0xC0) { /* 2-byte sequence? */ + uc &= 0x1F; nf = 1; + } else { + if ((uc & 0xF0) == 0xE0) { /* 3-byte sequence? */ + uc &= 0x0F; nf = 2; + } else { + if ((uc & 0xF8) == 0xF0) { /* 4-byte sequence? */ + uc &= 0x07; nf = 3; + } else { /* Wrong sequence */ + return 0xFFFFFFFF; + } + } + } + do { /* Get trailing bytes */ + b = (BYTE)*p++; + if ((b & 0xC0) != 0x80) return 0xFFFFFFFF; /* Wrong sequence? */ + uc = uc << 6 | (b & 0x3F); + } while (--nf != 0); + if (uc < 0x80 || IsSurrogate(uc) || uc >= 0x110000) return 0xFFFFFFFF; /* Wrong code? */ + if (uc >= 0x010000) uc = 0xD800DC00 | ((uc - 0x10000) << 6 & 0x3FF0000) | (uc & 0x3FF); /* Make a surrogate pair if needed */ + } + +#elif FF_LFN_UNICODE == 3 /* UTF-32 input */ + uc = (TCHAR)*p++; /* Get a unit */ + if (uc >= 0x110000) return 0xFFFFFFFF; /* Wrong code? */ + if (uc >= 0x010000) uc = 0xD800DC00 | ((uc - 0x10000) << 6 & 0x3FF0000) | (uc & 0x3FF); /* Make a surrogate pair if needed */ + +#else /* ANSI/OEM input */ + BYTE b; + WCHAR wc; + + wc = (BYTE)*p++; /* Get a byte */ + if (dbc_1st((BYTE)wc)) { /* Is it a DBC 1st byte? */ + b = (BYTE)*p++; /* Get 2nd byte */ + if (!dbc_2nd(b)) return 0xFFFFFFFF; /* Invalid code? */ + wc = (wc << 8) + b; /* Make a DBC */ + } + if (wc != 0) { + wc = ff_oem2uni(wc, CODEPAGE); /* ANSI/OEM ==> Unicode */ + if (wc == 0) return 0xFFFFFFFF; /* Invalid code? */ + } + uc = wc; + +#endif + *str = p; /* Next read pointer */ + return uc; +} + + +/* Output a TCHAR string in defined API encoding */ +static BYTE put_utf ( /* Returns number of encoding units written (0:buffer overflow or wrong encoding) */ + DWORD chr, /* UTF-16 encoded character (Double encoding unit char if >=0x10000) */ + TCHAR* buf, /* Output buffer */ + UINT szb /* Size of the buffer */ +) +{ +#if FF_LFN_UNICODE == 1 /* UTF-16 output */ + WCHAR hs, wc; + + hs = (WCHAR)(chr >> 16); + wc = (WCHAR)chr; + if (hs == 0) { /* Single encoding unit? */ + if (szb < 1 || IsSurrogate(wc)) return 0; /* Buffer overflow or wrong code? */ + *buf = wc; + return 1; + } + if (szb < 2 || !IsSurrogateH(hs) || !IsSurrogateL(wc)) return 0; /* Buffer overflow or wrong surrogate? */ + *buf++ = hs; + *buf++ = wc; + return 2; + +#elif FF_LFN_UNICODE == 2 /* UTF-8 output */ + DWORD hc; + + if (chr < 0x80) { /* Single byte code? */ + if (szb < 1) return 0; /* Buffer overflow? */ + *buf = (TCHAR)chr; + return 1; + } + if (chr < 0x800) { /* 2-byte sequence? */ + if (szb < 2) return 0; /* Buffer overflow? */ + *buf++ = (TCHAR)(0xC0 | (chr >> 6 & 0x1F)); + *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F)); + return 2; + } + if (chr < 0x10000) { /* 3-byte sequence? */ + if (szb < 3 || IsSurrogate(chr)) return 0; /* Buffer overflow or wrong code? */ + *buf++ = (TCHAR)(0xE0 | (chr >> 12 & 0x0F)); + *buf++ = (TCHAR)(0x80 | (chr >> 6 & 0x3F)); + *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F)); + return 3; + } + /* 4-byte sequence */ + if (szb < 4) return 0; /* Buffer overflow? */ + hc = ((chr & 0xFFFF0000) - 0xD8000000) >> 6; /* Get high 10 bits */ + chr = (chr & 0xFFFF) - 0xDC00; /* Get low 10 bits */ + if (hc >= 0x100000 || chr >= 0x400) return 0; /* Wrong surrogate? */ + chr = (hc | chr) + 0x10000; + *buf++ = (TCHAR)(0xF0 | (chr >> 18 & 0x07)); + *buf++ = (TCHAR)(0x80 | (chr >> 12 & 0x3F)); + *buf++ = (TCHAR)(0x80 | (chr >> 6 & 0x3F)); + *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F)); + return 4; + +#elif FF_LFN_UNICODE == 3 /* UTF-32 output */ + DWORD hc; + + if (szb < 1) return 0; /* Buffer overflow? */ + if (chr >= 0x10000) { /* Out of BMP? */ + hc = ((chr & 0xFFFF0000) - 0xD8000000) >> 6; /* Get high 10 bits */ + chr = (chr & 0xFFFF) - 0xDC00; /* Get low 10 bits */ + if (hc >= 0x100000 || chr >= 0x400) return 0; /* Wrong surrogate? */ + chr = (hc | chr) + 0x10000; + } + *buf++ = (TCHAR)chr; + return 1; + +#else /* ANSI/OEM output */ + WCHAR wc; + + wc = ff_uni2oem(chr, CODEPAGE); + if (wc >= 0x100) { /* Is this a DBC? */ + if (szb < 2) return 0; + *buf++ = (char)(wc >> 8); /* Store DBC 1st byte */ + *buf++ = (TCHAR)wc; /* Store DBC 2nd byte */ + return 2; + } + if (wc == 0 || szb < 1) return 0; /* Invalid char or buffer overflow? */ + *buf++ = (TCHAR)wc; /* Store the character */ + return 1; +#endif +} +#endif /* FF_USE_LFN */ + + +#if FF_FS_REENTRANT +/*-----------------------------------------------------------------------*/ +/* Request/Release grant to access the volume */ +/*-----------------------------------------------------------------------*/ +static int lock_fs ( /* 1:Ok, 0:timeout */ + FATFS* fs /* Filesystem object */ +) +{ + return ff_req_grant(fs->sobj); +} + + +static void unlock_fs ( + FATFS* fs, /* Filesystem object */ + FRESULT res /* Result code to be returned */ +) +{ + if (fs && res != FR_NOT_ENABLED && res != FR_INVALID_DRIVE && res != FR_TIMEOUT) { + ff_rel_grant(fs->sobj); + } +} + +#endif + + + +#if FF_FS_LOCK != 0 +/*-----------------------------------------------------------------------*/ +/* File lock control functions */ +/*-----------------------------------------------------------------------*/ + +static FRESULT chk_lock ( /* Check if the file can be accessed */ + DIR* dp, /* Directory object pointing the file to be checked */ + int acc /* Desired access type (0:Read mode open, 1:Write mode open, 2:Delete or rename) */ +) +{ + UINT i, be; + + /* Search open object table for the object */ + be = 0; + for (i = 0; i < FF_FS_LOCK; i++) { + if (Files[i].fs) { /* Existing entry */ + if (Files[i].fs == dp->obj.fs && /* Check if the object matches with an open object */ + Files[i].clu == dp->obj.sclust && + Files[i].ofs == dp->dptr) break; + } else { /* Blank entry */ + be = 1; + } + } + if (i == FF_FS_LOCK) { /* The object has not been opened */ + return (!be && acc != 2) ? FR_TOO_MANY_OPEN_FILES : FR_OK; /* Is there a blank entry for new object? */ + } + + /* The object was opened. Reject any open against writing file and all write mode open */ + return (acc != 0 || Files[i].ctr == 0x100) ? FR_LOCKED : FR_OK; +} + + +static int enq_lock (void) /* Check if an entry is available for a new object */ +{ + UINT i; + + for (i = 0; i < FF_FS_LOCK && Files[i].fs; i++) ; + return (i == FF_FS_LOCK) ? 0 : 1; +} + + +static UINT inc_lock ( /* Increment object open counter and returns its index (0:Internal error) */ + DIR* dp, /* Directory object pointing the file to register or increment */ + int acc /* Desired access (0:Read, 1:Write, 2:Delete/Rename) */ +) +{ + UINT i; + + + for (i = 0; i < FF_FS_LOCK; i++) { /* Find the object */ + if (Files[i].fs == dp->obj.fs && + Files[i].clu == dp->obj.sclust && + Files[i].ofs == dp->dptr) break; + } + + if (i == FF_FS_LOCK) { /* Not opened. Register it as new. */ + for (i = 0; i < FF_FS_LOCK && Files[i].fs; i++) ; + if (i == FF_FS_LOCK) return 0; /* No free entry to register (int err) */ + Files[i].fs = dp->obj.fs; + Files[i].clu = dp->obj.sclust; + Files[i].ofs = dp->dptr; + Files[i].ctr = 0; + } + + if (acc >= 1 && Files[i].ctr) return 0; /* Access violation (int err) */ + + Files[i].ctr = acc ? 0x100 : Files[i].ctr + 1; /* Set semaphore value */ + + return i + 1; /* Index number origin from 1 */ +} + + +static FRESULT dec_lock ( /* Decrement object open counter */ + UINT i /* Semaphore index (1..) */ +) +{ + WORD n; + FRESULT res; + + + if (--i < FF_FS_LOCK) { /* Index number origin from 0 */ + n = Files[i].ctr; + if (n == 0x100) n = 0; /* If write mode open, delete the entry */ + if (n > 0) n--; /* Decrement read mode open count */ + Files[i].ctr = n; + if (n == 0) Files[i].fs = 0; /* Delete the entry if open count gets zero */ + res = FR_OK; + } else { + res = FR_INT_ERR; /* Invalid index nunber */ + } + return res; +} + + +static void clear_lock ( /* Clear lock entries of the volume */ + FATFS *fs +) +{ + UINT i; + + for (i = 0; i < FF_FS_LOCK; i++) { + if (Files[i].fs == fs) Files[i].fs = 0; + } +} + +#endif /* FF_FS_LOCK != 0 */ + + + +/*-----------------------------------------------------------------------*/ +/* Move/Flush disk access window in the filesystem object */ +/*-----------------------------------------------------------------------*/ +#if !FF_FS_READONLY +static FRESULT sync_window ( /* Returns FR_OK or FR_DISK_ERR */ + FATFS* fs /* Filesystem object */ +) +{ + FRESULT res = FR_OK; + + + if (fs->wflag) { /* Is the disk access window dirty */ + if (disk_write(fs->drv, fs->win, fs->winsect, 1) == RES_OK) { /* Write back the window */ + fs->wflag = 0; /* Clear window dirty flag */ + if (fs->winsect - fs->fatbase < fs->fsize) { /* Is it in the 1st FAT? */ + if (fs->n_fats == 2) disk_write(fs->drv, fs->win, fs->winsect + fs->fsize, 1); /* Reflect it to 2nd FAT if needed */ + } + } else { + res = FR_DISK_ERR; + } + } + return res; +} +#endif + + +static FRESULT move_window ( /* Returns FR_OK or FR_DISK_ERR */ + FATFS* fs, /* Filesystem object */ + DWORD sector /* Sector number to make appearance in the fs->win[] */ +) +{ + FRESULT res = FR_OK; + + + if (sector != fs->winsect) { /* Window offset changed? */ +#if !FF_FS_READONLY + res = sync_window(fs); /* Write-back changes */ +#endif + if (res == FR_OK) { /* Fill sector window with new data */ + if (disk_read(fs->drv, fs->win, sector, 1) != RES_OK) { + sector = 0xFFFFFFFF; /* Invalidate window if read data is not valid */ + res = FR_DISK_ERR; + } + fs->winsect = sector; + } + } + return res; +} + + + + +#if !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Synchronize filesystem and data on the storage */ +/*-----------------------------------------------------------------------*/ + +static FRESULT sync_fs ( /* Returns FR_OK or FR_DISK_ERR */ + FATFS* fs /* Filesystem object */ +) +{ + FRESULT res; + + + res = sync_window(fs); + if (res == FR_OK) { + if (fs->fs_type == FS_FAT32 && fs->fsi_flag == 1) { /* FAT32: Update FSInfo sector if needed */ + /* Create FSInfo structure */ + mem_set(fs->win, 0, sizeof fs->win); + st_word(fs->win + BS_55AA, 0xAA55); + st_dword(fs->win + FSI_LeadSig, 0x41615252); + st_dword(fs->win + FSI_StrucSig, 0x61417272); + st_dword(fs->win + FSI_Free_Count, fs->free_clst); + st_dword(fs->win + FSI_Nxt_Free, fs->last_clst); + /* Write it into the FSInfo sector */ + fs->winsect = fs->volbase + 1; + disk_write(fs->drv, fs->win, fs->winsect, 1); + fs->fsi_flag = 0; + } + /* Make sure that no pending write process in the lower layer */ + if (disk_ioctl(fs->drv, CTRL_SYNC, 0) != RES_OK) res = FR_DISK_ERR; + } + + return res; +} + +#endif + + + +/*-----------------------------------------------------------------------*/ +/* Get physical sector number from cluster number */ +/*-----------------------------------------------------------------------*/ + +static DWORD clst2sect ( /* !=0:Sector number, 0:Failed (invalid cluster#) */ + FATFS* fs, /* Filesystem object */ + DWORD clst /* Cluster# to be converted */ +) +{ + clst -= 2; /* Cluster number is origin from 2 */ + if (clst >= fs->n_fatent - 2) return 0; /* Is it invalid cluster number? */ + return fs->database + fs->csize * clst; /* Start sector number of the cluster */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* FAT access - Read value of a FAT entry */ +/*-----------------------------------------------------------------------*/ + +static DWORD get_fat ( /* 0xFFFFFFFF:Disk error, 1:Internal error, 2..0x7FFFFFFF:Cluster status */ + FFOBJID* obj, /* Corresponding object */ + DWORD clst /* Cluster number to get the value */ +) +{ + UINT wc, bc; + DWORD val; + FATFS *fs = obj->fs; + + + if (clst < 2 || clst >= fs->n_fatent) { /* Check if in valid range */ + val = 1; /* Internal error */ + + } else { + val = 0xFFFFFFFF; /* Default value falls on disk error */ + + switch (fs->fs_type) { + case FS_FAT12 : + bc = (UINT)clst; bc += bc / 2; + if (move_window(fs, fs->fatbase + (bc / SS(fs))) != FR_OK) break; + wc = fs->win[bc++ % SS(fs)]; /* Get 1st byte of the entry */ + if (move_window(fs, fs->fatbase + (bc / SS(fs))) != FR_OK) break; + wc |= fs->win[bc % SS(fs)] << 8; /* Merge 2nd byte of the entry */ + val = (clst & 1) ? (wc >> 4) : (wc & 0xFFF); /* Adjust bit position */ + break; + + case FS_FAT16 : + if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 2))) != FR_OK) break; + val = ld_word(fs->win + clst * 2 % SS(fs)); /* Simple WORD array */ + break; + + case FS_FAT32 : + if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 4))) != FR_OK) break; + val = ld_dword(fs->win + clst * 4 % SS(fs)) & 0x0FFFFFFF; /* Simple DWORD array but mask out upper 4 bits */ + break; +#if FF_FS_EXFAT + case FS_EXFAT : + if ((obj->objsize != 0 && obj->sclust != 0) || obj->stat == 0) { /* Object except root dir must have valid data length */ + DWORD cofs = clst - obj->sclust; /* Offset from start cluster */ + DWORD clen = (DWORD)((obj->objsize - 1) / SS(fs)) / fs->csize; /* Number of clusters - 1 */ + + if (obj->stat == 2 && cofs <= clen) { /* Is it a contiguous chain? */ + val = (cofs == clen) ? 0x7FFFFFFF : clst + 1; /* No data on the FAT, generate the value */ + break; + } + if (obj->stat == 3 && cofs < obj->n_cont) { /* Is it in the 1st fragment? */ + val = clst + 1; /* Generate the value */ + break; + } + if (obj->stat != 2) { /* Get value from FAT if FAT chain is valid */ + if (obj->n_frag != 0) { /* Is it on the growing edge? */ + val = 0x7FFFFFFF; /* Generate EOC */ + } else { + if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 4))) != FR_OK) break; + val = ld_dword(fs->win + clst * 4 % SS(fs)) & 0x7FFFFFFF; + } + break; + } + } + /* go to default */ +#endif + default: + val = 1; /* Internal error */ + } + } + + return val; +} + + + + +#if !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* FAT access - Change value of a FAT entry */ +/*-----------------------------------------------------------------------*/ + +static FRESULT put_fat ( /* FR_OK(0):succeeded, !=0:error */ + FATFS* fs, /* Corresponding filesystem object */ + DWORD clst, /* FAT index number (cluster number) to be changed */ + DWORD val /* New value to be set to the entry */ +) +{ + UINT bc; + BYTE *p; + FRESULT res = FR_INT_ERR; + + + if (clst >= 2 && clst < fs->n_fatent) { /* Check if in valid range */ + switch (fs->fs_type) { + case FS_FAT12 : + bc = (UINT)clst; bc += bc / 2; /* bc: byte offset of the entry */ + res = move_window(fs, fs->fatbase + (bc / SS(fs))); + if (res != FR_OK) break; + p = fs->win + bc++ % SS(fs); + *p = (clst & 1) ? ((*p & 0x0F) | ((BYTE)val << 4)) : (BYTE)val; /* Put 1st byte */ + fs->wflag = 1; + res = move_window(fs, fs->fatbase + (bc / SS(fs))); + if (res != FR_OK) break; + p = fs->win + bc % SS(fs); + *p = (clst & 1) ? (BYTE)(val >> 4) : ((*p & 0xF0) | ((BYTE)(val >> 8) & 0x0F)); /* Put 2nd byte */ + fs->wflag = 1; + break; + + case FS_FAT16 : + res = move_window(fs, fs->fatbase + (clst / (SS(fs) / 2))); + if (res != FR_OK) break; + st_word(fs->win + clst * 2 % SS(fs), (WORD)val); /* Simple WORD array */ + fs->wflag = 1; + break; + + case FS_FAT32 : +#if FF_FS_EXFAT + case FS_EXFAT : +#endif + res = move_window(fs, fs->fatbase + (clst / (SS(fs) / 4))); + if (res != FR_OK) break; + if (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) { + val = (val & 0x0FFFFFFF) | (ld_dword(fs->win + clst * 4 % SS(fs)) & 0xF0000000); + } + st_dword(fs->win + clst * 4 % SS(fs), val); + fs->wflag = 1; + break; + } + } + return res; +} + +#endif /* !FF_FS_READONLY */ + + + + +#if FF_FS_EXFAT && !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* exFAT: Accessing FAT and Allocation Bitmap */ +/*-----------------------------------------------------------------------*/ + +/*--------------------------------------*/ +/* Find a contiguous free cluster block */ +/*--------------------------------------*/ + +static DWORD find_bitmap ( /* 0:Not found, 2..:Cluster block found, 0xFFFFFFFF:Disk error */ + FATFS* fs, /* Filesystem object */ + DWORD clst, /* Cluster number to scan from */ + DWORD ncl /* Number of contiguous clusters to find (1..) */ +) +{ + BYTE bm, bv; + UINT i; + DWORD val, scl, ctr; + + + clst -= 2; /* The first bit in the bitmap corresponds to cluster #2 */ + if (clst >= fs->n_fatent - 2) clst = 0; + scl = val = clst; ctr = 0; + for (;;) { + if (move_window(fs, fs->bitbase + val / 8 / SS(fs)) != FR_OK) return 0xFFFFFFFF; + i = val / 8 % SS(fs); bm = 1 << (val % 8); + do { + do { + bv = fs->win[i] & bm; bm <<= 1; /* Get bit value */ + if (++val >= fs->n_fatent - 2) { /* Next cluster (with wrap-around) */ + val = 0; bm = 0; i = SS(fs); + } + if (bv == 0) { /* Is it a free cluster? */ + if (++ctr == ncl) return scl + 2; /* Check if run length is sufficient for required */ + } else { + scl = val; ctr = 0; /* Encountered a cluster in-use, restart to scan */ + } + if (val == clst) return 0; /* All cluster scanned? */ + } while (bm != 0); + bm = 1; + } while (++i < SS(fs)); + } +} + + +/*----------------------------------------*/ +/* Set/Clear a block of allocation bitmap */ +/*----------------------------------------*/ + +static FRESULT change_bitmap ( + FATFS* fs, /* Filesystem object */ + DWORD clst, /* Cluster number to change from */ + DWORD ncl, /* Number of clusters to be changed */ + int bv /* bit value to be set (0 or 1) */ +) +{ + BYTE bm; + UINT i; + DWORD sect; + + + clst -= 2; /* The first bit corresponds to cluster #2 */ + sect = fs->bitbase + clst / 8 / SS(fs); /* Sector address */ + i = clst / 8 % SS(fs); /* Byte offset in the sector */ + bm = 1 << (clst % 8); /* Bit mask in the byte */ + for (;;) { + if (move_window(fs, sect++) != FR_OK) return FR_DISK_ERR; + do { + do { + if (bv == (int)((fs->win[i] & bm) != 0)) return FR_INT_ERR; /* Is the bit expected value? */ + fs->win[i] ^= bm; /* Flip the bit */ + fs->wflag = 1; + if (--ncl == 0) return FR_OK; /* All bits processed? */ + } while (bm <<= 1); /* Next bit */ + bm = 1; + } while (++i < SS(fs)); /* Next byte */ + i = 0; + } +} + + +/*---------------------------------------------*/ +/* Fill the first fragment of the FAT chain */ +/*---------------------------------------------*/ + +static FRESULT fill_first_frag ( + FFOBJID* obj /* Pointer to the corresponding object */ +) +{ + FRESULT res; + DWORD cl, n; + + + if (obj->stat == 3) { /* Has the object been changed 'fragmented' in this session? */ + for (cl = obj->sclust, n = obj->n_cont; n; cl++, n--) { /* Create cluster chain on the FAT */ + res = put_fat(obj->fs, cl, cl + 1); + if (res != FR_OK) return res; + } + obj->stat = 0; /* Change status 'FAT chain is valid' */ + } + return FR_OK; +} + + +/*---------------------------------------------*/ +/* Fill the last fragment of the FAT chain */ +/*---------------------------------------------*/ + +static FRESULT fill_last_frag ( + FFOBJID* obj, /* Pointer to the corresponding object */ + DWORD lcl, /* Last cluster of the fragment */ + DWORD term /* Value to set the last FAT entry */ +) +{ + FRESULT res; + + + while (obj->n_frag > 0) { /* Create the chain of last fragment */ + res = put_fat(obj->fs, lcl - obj->n_frag + 1, (obj->n_frag > 1) ? lcl - obj->n_frag + 2 : term); + if (res != FR_OK) return res; + obj->n_frag--; + } + return FR_OK; +} + +#endif /* FF_FS_EXFAT && !FF_FS_READONLY */ + + + +#if !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* FAT handling - Remove a cluster chain */ +/*-----------------------------------------------------------------------*/ + +static FRESULT remove_chain ( /* FR_OK(0):succeeded, !=0:error */ + FFOBJID* obj, /* Corresponding object */ + DWORD clst, /* Cluster to remove a chain from */ + DWORD pclst /* Previous cluster of clst (0 if entire chain) */ +) +{ + FRESULT res = FR_OK; + DWORD nxt; + FATFS *fs = obj->fs; +#if FF_FS_EXFAT || FF_USE_TRIM + DWORD scl = clst, ecl = clst; +#endif +#if FF_USE_TRIM + DWORD rt[2]; +#endif + + if (clst < 2 || clst >= fs->n_fatent) return FR_INT_ERR; /* Check if in valid range */ + + /* Mark the previous cluster 'EOC' on the FAT if it exists */ + if (pclst != 0 && (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT || obj->stat != 2)) { + res = put_fat(fs, pclst, 0xFFFFFFFF); + if (res != FR_OK) return res; + } + + /* Remove the chain */ + do { + nxt = get_fat(obj, clst); /* Get cluster status */ + if (nxt == 0) break; /* Empty cluster? */ + if (nxt == 1) return FR_INT_ERR; /* Internal error? */ + if (nxt == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error? */ + if (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) { + res = put_fat(fs, clst, 0); /* Mark the cluster 'free' on the FAT */ + if (res != FR_OK) return res; + } + if (fs->free_clst < fs->n_fatent - 2) { /* Update FSINFO */ + fs->free_clst++; + fs->fsi_flag |= 1; + } +#if FF_FS_EXFAT || FF_USE_TRIM + if (ecl + 1 == nxt) { /* Is next cluster contiguous? */ + ecl = nxt; + } else { /* End of contiguous cluster block */ +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + res = change_bitmap(fs, scl, ecl - scl + 1, 0); /* Mark the cluster block 'free' on the bitmap */ + if (res != FR_OK) return res; + } +#endif +#if FF_USE_TRIM + rt[0] = clst2sect(fs, scl); /* Start of data area freed */ + rt[1] = clst2sect(fs, ecl) + fs->csize - 1; /* End of data area freed */ + disk_ioctl(fs->drv, CTRL_TRIM, rt); /* Inform device the data in the block is no longer needed */ +#endif + scl = ecl = nxt; + } +#endif + clst = nxt; /* Next cluster */ + } while (clst < fs->n_fatent); /* Repeat while not the last link */ + +#if FF_FS_EXFAT + /* Some post processes for chain status */ + if (fs->fs_type == FS_EXFAT) { + if (pclst == 0) { /* Has the entire chain been removed? */ + obj->stat = 0; /* Change the chain status 'initial' */ + } else { + if (obj->stat == 0) { /* Is it a fragmented chain from the beginning of this session? */ + clst = obj->sclust; /* Follow the chain to check if it gets contiguous */ + while (clst != pclst) { + nxt = get_fat(obj, clst); + if (nxt < 2) return FR_INT_ERR; + if (nxt == 0xFFFFFFFF) return FR_DISK_ERR; + if (nxt != clst + 1) break; /* Not contiguous? */ + clst++; + } + if (clst == pclst) { /* Has the chain got contiguous again? */ + obj->stat = 2; /* Change the chain status 'contiguous' */ + } + } else { + if (obj->stat == 3 && pclst >= obj->sclust && pclst <= obj->sclust + obj->n_cont) { /* Was the chain fragmented in this session and got contiguous again? */ + obj->stat = 2; /* Change the chain status 'contiguous' */ + } + } + } + } +#endif + return FR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* FAT handling - Stretch a chain or Create a new chain */ +/*-----------------------------------------------------------------------*/ + +static DWORD create_chain ( /* 0:No free cluster, 1:Internal error, 0xFFFFFFFF:Disk error, >=2:New cluster# */ + FFOBJID* obj, /* Corresponding object */ + DWORD clst /* Cluster# to stretch, 0:Create a new chain */ +) +{ + DWORD cs, ncl, scl; + FRESULT res; + FATFS *fs = obj->fs; + + + if (clst == 0) { /* Create a new chain */ + scl = fs->last_clst; /* Suggested cluster to start to find */ + if (scl == 0 || scl >= fs->n_fatent) scl = 1; + } + else { /* Stretch a chain */ + cs = get_fat(obj, clst); /* Check the cluster status */ + if (cs < 2) return 1; /* Test for insanity */ + if (cs == 0xFFFFFFFF) return cs; /* Test for disk error */ + if (cs < fs->n_fatent) return cs; /* It is already followed by next cluster */ + scl = clst; /* Cluster to start to find */ + } + if (fs->free_clst == 0) return 0; /* No free cluster */ + +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + ncl = find_bitmap(fs, scl, 1); /* Find a free cluster */ + if (ncl == 0 || ncl == 0xFFFFFFFF) return ncl; /* No free cluster or hard error? */ + res = change_bitmap(fs, ncl, 1, 1); /* Mark the cluster 'in use' */ + if (res == FR_INT_ERR) return 1; + if (res == FR_DISK_ERR) return 0xFFFFFFFF; + if (clst == 0) { /* Is it a new chain? */ + obj->stat = 2; /* Set status 'contiguous' */ + } else { /* It is a stretched chain */ + if (obj->stat == 2 && ncl != scl + 1) { /* Is the chain got fragmented? */ + obj->n_cont = scl - obj->sclust; /* Set size of the contiguous part */ + obj->stat = 3; /* Change status 'just fragmented' */ + } + } + if (obj->stat != 2) { /* Is the file non-contiguous? */ + if (ncl == clst + 1) { /* Is the cluster next to previous one? */ + obj->n_frag = obj->n_frag ? obj->n_frag + 1 : 2; /* Increment size of last framgent */ + } else { /* New fragment */ + if (obj->n_frag == 0) obj->n_frag = 1; + res = fill_last_frag(obj, clst, ncl); /* Fill last fragment on the FAT and link it to new one */ + if (res == FR_OK) obj->n_frag = 1; + } + } + } else +#endif + { /* On the FAT/FAT32 volume */ + ncl = 0; + if (scl == clst) { /* Stretching an existing chain? */ + ncl = scl + 1; /* Test if next cluster is free */ + if (ncl >= fs->n_fatent) ncl = 2; + cs = get_fat(obj, ncl); /* Get next cluster status */ + if (cs == 1 || cs == 0xFFFFFFFF) return cs; /* Test for error */ + if (cs != 0) { /* Not free? */ + cs = fs->last_clst; /* Start at suggested cluster if it is valid */ + if (cs >= 2 && cs < fs->n_fatent) scl = cs; + ncl = 0; + } + } + if (ncl == 0) { /* The new cluster cannot be contiguous and find another fragment */ + ncl = scl; /* Start cluster */ + for (;;) { + ncl++; /* Next cluster */ + if (ncl >= fs->n_fatent) { /* Check wrap-around */ + ncl = 2; + if (ncl > scl) return 0; /* No free cluster found? */ + } + cs = get_fat(obj, ncl); /* Get the cluster status */ + if (cs == 0) break; /* Found a free cluster? */ + if (cs == 1 || cs == 0xFFFFFFFF) return cs; /* Test for error */ + if (ncl == scl) return 0; /* No free cluster found? */ + } + } + res = put_fat(fs, ncl, 0xFFFFFFFF); /* Mark the new cluster 'EOC' */ + if (res == FR_OK && clst != 0) { + res = put_fat(fs, clst, ncl); /* Link it from the previous one if needed */ + } + } + + if (res == FR_OK) { /* Update FSINFO if function succeeded. */ + fs->last_clst = ncl; + if (fs->free_clst <= fs->n_fatent - 2) fs->free_clst--; + fs->fsi_flag |= 1; + } else { + ncl = (res == FR_DISK_ERR) ? 0xFFFFFFFF : 1; /* Failed. Generate error status */ + } + + return ncl; /* Return new cluster number or error status */ +} + +#endif /* !FF_FS_READONLY */ + + + + +#if FF_USE_FASTSEEK +/*-----------------------------------------------------------------------*/ +/* FAT handling - Convert offset into cluster with link map table */ +/*-----------------------------------------------------------------------*/ + +static DWORD clmt_clust ( /* <2:Error, >=2:Cluster number */ + FIL* fp, /* Pointer to the file object */ + FSIZE_t ofs /* File offset to be converted to cluster# */ +) +{ + DWORD cl, ncl, *tbl; + FATFS *fs = fp->obj.fs; + + + tbl = fp->cltbl + 1; /* Top of CLMT */ + cl = (DWORD)(ofs / SS(fs) / fs->csize); /* Cluster order from top of the file */ + for (;;) { + ncl = *tbl++; /* Number of cluters in the fragment */ + if (ncl == 0) return 0; /* End of table? (error) */ + if (cl < ncl) break; /* In this fragment? */ + cl -= ncl; tbl++; /* Next fragment */ + } + return cl + *tbl; /* Return the cluster number */ +} + +#endif /* FF_USE_FASTSEEK */ + + + + +/*-----------------------------------------------------------------------*/ +/* Directory handling - Fill a cluster with zeros */ +/*-----------------------------------------------------------------------*/ + +#if !FF_FS_READONLY +static FRESULT dir_clear ( /* Returns FR_OK or FR_DISK_ERR */ + FATFS *fs, /* Filesystem object */ + DWORD clst /* Directory table to clear */ +) +{ + DWORD sect; + UINT n, szb; + BYTE *ibuf; + + + if (sync_window(fs) != FR_OK) return FR_DISK_ERR; /* Flush disk access window */ + sect = clst2sect(fs, clst); /* Top of the cluster */ + fs->winsect = sect; /* Set window to top of the cluster */ + mem_set(fs->win, 0, sizeof fs->win); /* Clear window buffer */ +#if FF_USE_LFN == 3 /* Quick table clear by using multi-secter write */ + /* Allocate a temporary buffer */ + for (szb = ((DWORD)fs->csize * SS(fs) >= MAX_MALLOC) ? MAX_MALLOC : fs->csize * SS(fs), ibuf = 0; szb > SS(fs) && (ibuf = ff_memalloc(szb)) == 0; szb /= 2) ; + if (szb > SS(fs)) { /* Buffer allocated? */ + mem_set(ibuf, 0, szb); + szb /= SS(fs); /* Bytes -> Sectors */ + for (n = 0; n < fs->csize && disk_write(fs->drv, ibuf, sect + n, szb) == RES_OK; n += szb) ; /* Fill the cluster with 0 */ + ff_memfree(ibuf); + } else +#endif + { + ibuf = fs->win; szb = 1; /* Use window buffer (many single-sector writes may take a time) */ + for (n = 0; n < fs->csize && disk_write(fs->drv, ibuf, sect + n, szb) == RES_OK; n += szb) ; /* Fill the cluster with 0 */ + } + return (n == fs->csize) ? FR_OK : FR_DISK_ERR; +} +#endif /* !FF_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* Directory handling - Set directory index */ +/*-----------------------------------------------------------------------*/ + +static FRESULT dir_sdi ( /* FR_OK(0):succeeded, !=0:error */ + DIR* dp, /* Pointer to directory object */ + DWORD ofs /* Offset of directory table */ +) +{ + DWORD csz, clst; + FATFS *fs = dp->obj.fs; + + + if (ofs >= (DWORD)((FF_FS_EXFAT && fs->fs_type == FS_EXFAT) ? MAX_DIR_EX : MAX_DIR) || ofs % SZDIRE) { /* Check range of offset and alignment */ + return FR_INT_ERR; + } + dp->dptr = ofs; /* Set current offset */ + clst = dp->obj.sclust; /* Table start cluster (0:root) */ + if (clst == 0 && fs->fs_type >= FS_FAT32) { /* Replace cluster# 0 with root cluster# */ + clst = fs->dirbase; + if (FF_FS_EXFAT) dp->obj.stat = 0; /* exFAT: Root dir has an FAT chain */ + } + + if (clst == 0) { /* Static table (root-directory on the FAT volume) */ + if (ofs / SZDIRE >= fs->n_rootdir) return FR_INT_ERR; /* Is index out of range? */ + dp->sect = fs->dirbase; + + } else { /* Dynamic table (sub-directory or root-directory on the FAT32/exFAT volume) */ + csz = (DWORD)fs->csize * SS(fs); /* Bytes per cluster */ + while (ofs >= csz) { /* Follow cluster chain */ + clst = get_fat(&dp->obj, clst); /* Get next cluster */ + if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */ + if (clst < 2 || clst >= fs->n_fatent) return FR_INT_ERR; /* Reached to end of table or internal error */ + ofs -= csz; + } + dp->sect = clst2sect(fs, clst); + } + dp->clust = clst; /* Current cluster# */ + if (dp->sect == 0) return FR_INT_ERR; + dp->sect += ofs / SS(fs); /* Sector# of the directory entry */ + dp->dir = fs->win + (ofs % SS(fs)); /* Pointer to the entry in the win[] */ + + return FR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Directory handling - Move directory table index next */ +/*-----------------------------------------------------------------------*/ + +static FRESULT dir_next ( /* FR_OK(0):succeeded, FR_NO_FILE:End of table, FR_DENIED:Could not stretch */ + DIR* dp, /* Pointer to the directory object */ + int stretch /* 0: Do not stretch table, 1: Stretch table if needed */ +) +{ + DWORD ofs, clst; + FATFS *fs = dp->obj.fs; + + + ofs = dp->dptr + SZDIRE; /* Next entry */ + if (ofs >= (DWORD)((FF_FS_EXFAT && fs->fs_type == FS_EXFAT) ? MAX_DIR_EX : MAX_DIR)) dp->sect = 0; /* Disable it if the offset reached the max value */ + if (dp->sect == 0) return FR_NO_FILE; /* Report EOT if it has been disabled */ + + if (ofs % SS(fs) == 0) { /* Sector changed? */ + dp->sect++; /* Next sector */ + + if (dp->clust == 0) { /* Static table */ + if (ofs / SZDIRE >= fs->n_rootdir) { /* Report EOT if it reached end of static table */ + dp->sect = 0; return FR_NO_FILE; + } + } + else { /* Dynamic table */ + if ((ofs / SS(fs) & (fs->csize - 1)) == 0) { /* Cluster changed? */ + clst = get_fat(&dp->obj, dp->clust); /* Get next cluster */ + if (clst <= 1) return FR_INT_ERR; /* Internal error */ + if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */ + if (clst >= fs->n_fatent) { /* It reached end of dynamic table */ +#if !FF_FS_READONLY + if (!stretch) { /* If no stretch, report EOT */ + dp->sect = 0; return FR_NO_FILE; + } + clst = create_chain(&dp->obj, dp->clust); /* Allocate a cluster */ + if (clst == 0) return FR_DENIED; /* No free cluster */ + if (clst == 1) return FR_INT_ERR; /* Internal error */ + if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */ + if (dir_clear(fs, clst) != FR_OK) return FR_DISK_ERR; /* Clean up the stretched table */ + if (FF_FS_EXFAT) dp->obj.stat |= 4; /* exFAT: The directory has been stretched */ +#else + if (!stretch) dp->sect = 0; /* (this line is to suppress compiler warning) */ + dp->sect = 0; return FR_NO_FILE; /* Report EOT */ +#endif + } + dp->clust = clst; /* Initialize data for new cluster */ + dp->sect = clst2sect(fs, clst); + } + } + } + dp->dptr = ofs; /* Current entry */ + dp->dir = fs->win + ofs % SS(fs); /* Pointer to the entry in the win[] */ + + return FR_OK; +} + + + + +#if !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Directory handling - Reserve a block of directory entries */ +/*-----------------------------------------------------------------------*/ + +static FRESULT dir_alloc ( /* FR_OK(0):succeeded, !=0:error */ + DIR* dp, /* Pointer to the directory object */ + UINT nent /* Number of contiguous entries to allocate */ +) +{ + FRESULT res; + UINT n; + FATFS *fs = dp->obj.fs; + + + res = dir_sdi(dp, 0); + if (res == FR_OK) { + n = 0; + do { + res = move_window(fs, dp->sect); + if (res != FR_OK) break; +#if FF_FS_EXFAT + if ((fs->fs_type == FS_EXFAT) ? (int)((dp->dir[XDIR_Type] & 0x80) == 0) : (int)(dp->dir[DIR_Name] == DDEM || dp->dir[DIR_Name] == 0)) { +#else + if (dp->dir[DIR_Name] == DDEM || dp->dir[DIR_Name] == 0) { +#endif + if (++n == nent) break; /* A block of contiguous free entries is found */ + } else { + n = 0; /* Not a blank entry. Restart to search */ + } + res = dir_next(dp, 1); + } while (res == FR_OK); /* Next entry with table stretch enabled */ + } + + if (res == FR_NO_FILE) res = FR_DENIED; /* No directory entry to allocate */ + return res; +} + +#endif /* !FF_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* FAT: Directory handling - Load/Store start cluster number */ +/*-----------------------------------------------------------------------*/ + +static DWORD ld_clust ( /* Returns the top cluster value of the SFN entry */ + FATFS* fs, /* Pointer to the fs object */ + const BYTE* dir /* Pointer to the key entry */ +) +{ + DWORD cl; + + cl = ld_word(dir + DIR_FstClusLO); + if (fs->fs_type == FS_FAT32) { + cl |= (DWORD)ld_word(dir + DIR_FstClusHI) << 16; + } + + return cl; +} + + +#if !FF_FS_READONLY +static void st_clust ( + FATFS* fs, /* Pointer to the fs object */ + BYTE* dir, /* Pointer to the key entry */ + DWORD cl /* Value to be set */ +) +{ + st_word(dir + DIR_FstClusLO, (WORD)cl); + if (fs->fs_type == FS_FAT32) { + st_word(dir + DIR_FstClusHI, (WORD)(cl >> 16)); + } +} +#endif + + + +#if FF_USE_LFN +/*--------------------------------------------------------*/ +/* FAT-LFN: Compare a part of file name with an LFN entry */ +/*--------------------------------------------------------*/ + +static int cmp_lfn ( /* 1:matched, 0:not matched */ + const WCHAR* lfnbuf, /* Pointer to the LFN working buffer to be compared */ + BYTE* dir /* Pointer to the directory entry containing the part of LFN */ +) +{ + UINT i, s; + WCHAR wc, uc; + + + if (ld_word(dir + LDIR_FstClusLO) != 0) return 0; /* Check LDIR_FstClusLO */ + + i = ((dir[LDIR_Ord] & 0x3F) - 1) * 13; /* Offset in the LFN buffer */ + + for (wc = 1, s = 0; s < 13; s++) { /* Process all characters in the entry */ + uc = ld_word(dir + LfnOfs[s]); /* Pick an LFN character */ + if (wc != 0) { + if (i >= FF_MAX_LFN + 1 || ff_wtoupper(uc) != ff_wtoupper(lfnbuf[i++])) { /* Compare it */ + return 0; /* Not matched */ + } + wc = uc; + } else { + if (uc != 0xFFFF) return 0; /* Check filler */ + } + } + + if ((dir[LDIR_Ord] & LLEF) && wc && lfnbuf[i]) return 0; /* Last segment matched but different length */ + + return 1; /* The part of LFN matched */ +} + + +#if FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 || FF_USE_LABEL || FF_FS_EXFAT +/*-----------------------------------------------------*/ +/* FAT-LFN: Pick a part of file name from an LFN entry */ +/*-----------------------------------------------------*/ + +static int pick_lfn ( /* 1:succeeded, 0:buffer overflow or invalid LFN entry */ + WCHAR* lfnbuf, /* Pointer to the LFN working buffer */ + BYTE* dir /* Pointer to the LFN entry */ +) +{ + UINT i, s; + WCHAR wc, uc; + + + if (ld_word(dir + LDIR_FstClusLO) != 0) return 0; /* Check LDIR_FstClusLO is 0 */ + + i = ((dir[LDIR_Ord] & ~LLEF) - 1) * 13; /* Offset in the LFN buffer */ + + for (wc = 1, s = 0; s < 13; s++) { /* Process all characters in the entry */ + uc = ld_word(dir + LfnOfs[s]); /* Pick an LFN character */ + if (wc != 0) { + if (i >= FF_MAX_LFN + 1) return 0; /* Buffer overflow? */ + lfnbuf[i++] = wc = uc; /* Store it */ + } else { + if (uc != 0xFFFF) return 0; /* Check filler */ + } + } + + if (dir[LDIR_Ord] & LLEF && wc != 0) { /* Put terminator if it is the last LFN part and not terminated */ + if (i >= FF_MAX_LFN + 1) return 0; /* Buffer overflow? */ + lfnbuf[i] = 0; + } + + return 1; /* The part of LFN is valid */ +} +#endif + + +#if !FF_FS_READONLY +/*-----------------------------------------*/ +/* FAT-LFN: Create an entry of LFN entries */ +/*-----------------------------------------*/ + +static void put_lfn ( + const WCHAR* lfn, /* Pointer to the LFN */ + BYTE* dir, /* Pointer to the LFN entry to be created */ + BYTE ord, /* LFN order (1-20) */ + BYTE sum /* Checksum of the corresponding SFN */ +) +{ + UINT i, s; + WCHAR wc; + + + dir[LDIR_Chksum] = sum; /* Set checksum */ + dir[LDIR_Attr] = AM_LFN; /* Set attribute. LFN entry */ + dir[LDIR_Type] = 0; + st_word(dir + LDIR_FstClusLO, 0); + + i = (ord - 1) * 13; /* Get offset in the LFN working buffer */ + s = wc = 0; + do { + if (wc != 0xFFFF) wc = lfn[i++]; /* Get an effective character */ + st_word(dir + LfnOfs[s], wc); /* Put it */ + if (wc == 0) wc = 0xFFFF; /* Padding characters for left locations */ + } while (++s < 13); + if (wc == 0xFFFF || !lfn[i]) ord |= LLEF; /* Last LFN part is the start of LFN sequence */ + dir[LDIR_Ord] = ord; /* Set the LFN order */ +} + +#endif /* !FF_FS_READONLY */ +#endif /* FF_USE_LFN */ + + + +#if FF_USE_LFN && !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* FAT-LFN: Create a Numbered SFN */ +/*-----------------------------------------------------------------------*/ + +static void gen_numname ( + BYTE* dst, /* Pointer to the buffer to store numbered SFN */ + const BYTE* src, /* Pointer to SFN */ + const WCHAR* lfn, /* Pointer to LFN */ + UINT seq /* Sequence number */ +) +{ + BYTE ns[8], c; + UINT i, j; + WCHAR wc; + DWORD sr; + + + mem_cpy(dst, src, 11); + + if (seq > 5) { /* In case of many collisions, generate a hash number instead of sequential number */ + sr = seq; + while (*lfn) { /* Create a CRC as hash value */ + wc = *lfn++; + for (i = 0; i < 16; i++) { + sr = (sr << 1) + (wc & 1); + wc >>= 1; + if (sr & 0x10000) sr ^= 0x11021; + } + } + seq = (UINT)sr; + } + + /* itoa (hexdecimal) */ + i = 7; + do { + c = (BYTE)((seq % 16) + '0'); + if (c > '9') c += 7; + ns[i--] = c; + seq /= 16; + } while (seq); + ns[i] = '~'; + + /* Append the number to the SFN body */ + for (j = 0; j < i && dst[j] != ' '; j++) { + if (dbc_1st(dst[j])) { + if (j == i - 1) break; + j++; + } + } + do { + dst[j++] = (i < 8) ? ns[i++] : ' '; + } while (j < 8); +} +#endif /* FF_USE_LFN && !FF_FS_READONLY */ + + + +#if FF_USE_LFN +/*-----------------------------------------------------------------------*/ +/* FAT-LFN: Calculate checksum of an SFN entry */ +/*-----------------------------------------------------------------------*/ + +static BYTE sum_sfn ( + const BYTE* dir /* Pointer to the SFN entry */ +) +{ + BYTE sum = 0; + UINT n = 11; + + do { + sum = (sum >> 1) + (sum << 7) + *dir++; + } while (--n); + return sum; +} + +#endif /* FF_USE_LFN */ + + + +#if FF_FS_EXFAT +/*-----------------------------------------------------------------------*/ +/* exFAT: Checksum */ +/*-----------------------------------------------------------------------*/ + +static WORD xdir_sum ( /* Get checksum of the directoly entry block */ + const BYTE* dir /* Directory entry block to be calculated */ +) +{ + UINT i, szblk; + WORD sum; + + + szblk = (dir[XDIR_NumSec] + 1) * SZDIRE; /* Number of bytes of the entry block */ + for (i = sum = 0; i < szblk; i++) { + if (i == XDIR_SetSum) { /* Skip 2-byte sum field */ + i++; + } else { + sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + dir[i]; + } + } + return sum; +} + + + +static WORD xname_sum ( /* Get check sum (to be used as hash) of the file name */ + const WCHAR* name /* File name to be calculated */ +) +{ + WCHAR chr; + WORD sum = 0; + + + while ((chr = *name++) != 0) { + chr = (WCHAR)ff_wtoupper(chr); /* File name needs to be up-case converted */ + sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + (chr & 0xFF); + sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + (chr >> 8); + } + return sum; +} + + +#if !FF_FS_READONLY && FF_USE_MKFS +static DWORD xsum32 ( /* Returns 32-bit checksum */ + BYTE dat, /* Byte to be calculated (byte-by-byte processing) */ + DWORD sum /* Previous sum value */ +) +{ + sum = ((sum & 1) ? 0x80000000 : 0) + (sum >> 1) + dat; + return sum; +} +#endif + + +#if FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 +/*------------------------------------------------------*/ +/* exFAT: Get object information from a directory block */ +/*------------------------------------------------------*/ + +static void get_xfileinfo ( + BYTE* dirb, /* Pointer to the direcotry entry block 85+C0+C1s */ + FILINFO* fno /* Buffer to store the extracted file information */ +) +{ + WCHAR wc, hs; + UINT di, si, nc; + + /* Get file name from the entry block */ + si = SZDIRE * 2; /* 1st C1 entry */ + nc = 0; hs = 0; di = 0; + while (nc < dirb[XDIR_NumName]) { + if (si >= MAXDIRB(FF_MAX_LFN)) { di = 0; break; } /* Truncated directory block? */ + if ((si % SZDIRE) == 0) si += 2; /* Skip entry type field */ + wc = ld_word(dirb + si); si += 2; nc++; /* Get a character */ + if (hs == 0 && IsSurrogate(wc)) { /* Is it a surrogate? */ + hs = wc; continue; /* Get low surrogate */ + } + wc = put_utf((DWORD)hs << 16 | wc, &fno->fname[di], FF_LFN_BUF - di); /* Store it in API encoding */ + if (wc == 0) { di = 0; break; } /* Buffer overflow or wrong encoding? */ + di += wc; + hs = 0; + } + if (hs != 0) di = 0; /* Broken surrogate pair? */ + if (di == 0) fno->fname[di++] = '?'; /* Inaccessible object name? */ + fno->fname[di] = 0; /* Terminate the name */ + fno->altname[0] = 0; /* exFAT does not support SFN */ + + fno->fattrib = dirb[XDIR_Attr]; /* Attribute */ + fno->fsize = (fno->fattrib & AM_DIR) ? 0 : ld_qword(dirb + XDIR_FileSize); /* Size */ + fno->ftime = ld_word(dirb + XDIR_ModTime + 0); /* Time */ + fno->fdate = ld_word(dirb + XDIR_ModTime + 2); /* Date */ +} + +#endif /* FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 */ + + +/*-----------------------------------*/ +/* exFAT: Get a directry entry block */ +/*-----------------------------------*/ + +static FRESULT load_xdir ( /* FR_INT_ERR: invalid entry block */ + DIR* dp /* Reading direcotry object pointing top of the entry block to load */ +) +{ + FRESULT res; + UINT i, sz_ent; + BYTE* dirb = dp->obj.fs->dirbuf; /* Pointer to the on-memory direcotry entry block 85+C0+C1s */ + + + /* Load file-directory entry */ + res = move_window(dp->obj.fs, dp->sect); + if (res != FR_OK) return res; + if (dp->dir[XDIR_Type] != ET_FILEDIR) return FR_INT_ERR; /* Invalid order */ + mem_cpy(dirb + 0 * SZDIRE, dp->dir, SZDIRE); + sz_ent = (dirb[XDIR_NumSec] + 1) * SZDIRE; + if (sz_ent < 3 * SZDIRE || sz_ent > 19 * SZDIRE) return FR_INT_ERR; + + /* Load stream-extension entry */ + res = dir_next(dp, 0); + if (res == FR_NO_FILE) res = FR_INT_ERR; /* It cannot be */ + if (res != FR_OK) return res; + res = move_window(dp->obj.fs, dp->sect); + if (res != FR_OK) return res; + if (dp->dir[XDIR_Type] != ET_STREAM) return FR_INT_ERR; /* Invalid order */ + mem_cpy(dirb + 1 * SZDIRE, dp->dir, SZDIRE); + if (MAXDIRB(dirb[XDIR_NumName]) > sz_ent) return FR_INT_ERR; + + /* Load file-name entries */ + i = 2 * SZDIRE; /* Name offset to load */ + do { + res = dir_next(dp, 0); + if (res == FR_NO_FILE) res = FR_INT_ERR; /* It cannot be */ + if (res != FR_OK) return res; + res = move_window(dp->obj.fs, dp->sect); + if (res != FR_OK) return res; + if (dp->dir[XDIR_Type] != ET_FILENAME) return FR_INT_ERR; /* Invalid order */ + if (i < MAXDIRB(FF_MAX_LFN)) mem_cpy(dirb + i, dp->dir, SZDIRE); + } while ((i += SZDIRE) < sz_ent); + + /* Sanity check (do it for only accessible object) */ + if (i <= MAXDIRB(FF_MAX_LFN)) { + if (xdir_sum(dirb) != ld_word(dirb + XDIR_SetSum)) return FR_INT_ERR; + } + return FR_OK; +} + + +/*------------------------------------------------------------------*/ +/* exFAT: Initialize object allocation info with loaded entry block */ +/*------------------------------------------------------------------*/ + +static void init_alloc_info ( + FATFS* fs, /* Filesystem object */ + FFOBJID* obj /* Object allocation information to be initialized */ +) +{ + obj->sclust = ld_dword(fs->dirbuf + XDIR_FstClus); /* Start cluster */ + obj->objsize = ld_qword(fs->dirbuf + XDIR_FileSize); /* Size */ + obj->stat = fs->dirbuf[XDIR_GenFlags] & 2; /* Allocation status */ + obj->n_frag = 0; /* No last fragment info */ +} + + + +#if !FF_FS_READONLY || FF_FS_RPATH != 0 +/*------------------------------------------------*/ +/* exFAT: Load the object's directory entry block */ +/*------------------------------------------------*/ + +static FRESULT load_obj_xdir ( + DIR* dp, /* Blank directory object to be used to access containing direcotry */ + const FFOBJID* obj /* Object with its containing directory information */ +) +{ + FRESULT res; + + /* Open object containing directory */ + dp->obj.fs = obj->fs; + dp->obj.sclust = obj->c_scl; + dp->obj.stat = (BYTE)obj->c_size; + dp->obj.objsize = obj->c_size & 0xFFFFFF00; + dp->obj.n_frag = 0; + dp->blk_ofs = obj->c_ofs; + + res = dir_sdi(dp, dp->blk_ofs); /* Goto object's entry block */ + if (res == FR_OK) { + res = load_xdir(dp); /* Load the object's entry block */ + } + return res; +} +#endif + + +#if !FF_FS_READONLY +/*----------------------------------------*/ +/* exFAT: Store the directory entry block */ +/*----------------------------------------*/ + +static FRESULT store_xdir ( + DIR* dp /* Pointer to the direcotry object */ +) +{ + FRESULT res; + UINT nent; + BYTE* dirb = dp->obj.fs->dirbuf; /* Pointer to the direcotry entry block 85+C0+C1s */ + + /* Create set sum */ + st_word(dirb + XDIR_SetSum, xdir_sum(dirb)); + nent = dirb[XDIR_NumSec] + 1; + + /* Store the direcotry entry block to the directory */ + res = dir_sdi(dp, dp->blk_ofs); + while (res == FR_OK) { + res = move_window(dp->obj.fs, dp->sect); + if (res != FR_OK) break; + mem_cpy(dp->dir, dirb, SZDIRE); + dp->obj.fs->wflag = 1; + if (--nent == 0) break; + dirb += SZDIRE; + res = dir_next(dp, 0); + } + return (res == FR_OK || res == FR_DISK_ERR) ? res : FR_INT_ERR; +} + + + +/*-------------------------------------------*/ +/* exFAT: Create a new directory enrty block */ +/*-------------------------------------------*/ + +static void create_xdir ( + BYTE* dirb, /* Pointer to the direcotry entry block buffer */ + const WCHAR* lfn /* Pointer to the object name */ +) +{ + UINT i; + BYTE nc1, nlen; + WCHAR wc; + + + /* Create file-directory and stream-extension entry */ + mem_set(dirb, 0, 2 * SZDIRE); + dirb[0 * SZDIRE + XDIR_Type] = ET_FILEDIR; + dirb[1 * SZDIRE + XDIR_Type] = ET_STREAM; + + /* Create file-name entries */ + i = SZDIRE * 2; /* Top of file_name entries */ + nlen = nc1 = 0; wc = 1; + do { + dirb[i++] = ET_FILENAME; dirb[i++] = 0; + do { /* Fill name field */ + if (wc != 0 && (wc = lfn[nlen]) != 0) nlen++; /* Get a character if exist */ + st_word(dirb + i, wc); /* Store it */ + i += 2; + } while (i % SZDIRE != 0); + nc1++; + } while (lfn[nlen]); /* Fill next entry if any char follows */ + + dirb[XDIR_NumName] = nlen; /* Set name length */ + dirb[XDIR_NumSec] = 1 + nc1; /* Set secondary count (C0 + C1s) */ + st_word(dirb + XDIR_NameHash, xname_sum(lfn)); /* Set name hash */ +} + +#endif /* !FF_FS_READONLY */ +#endif /* FF_FS_EXFAT */ + + + +#if FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 || FF_USE_LABEL || FF_FS_EXFAT +/*-----------------------------------------------------------------------*/ +/* Read an object from the directory */ +/*-----------------------------------------------------------------------*/ + +#define DIR_READ_FILE(dp) dir_read(dp, 0) +#define DIR_READ_LABEL(dp) dir_read(dp, 1) + +static FRESULT dir_read ( + DIR* dp, /* Pointer to the directory object */ + int vol /* Filtered by 0:file/directory or 1:volume label */ +) +{ + FRESULT res = FR_NO_FILE; + FATFS *fs = dp->obj.fs; + BYTE attr, b; +#if FF_USE_LFN + BYTE ord = 0xFF, sum = 0xFF; +#endif + + while (dp->sect) { + res = move_window(fs, dp->sect); + if (res != FR_OK) break; + b = dp->dir[DIR_Name]; /* Test for the entry type */ + if (b == 0) { + res = FR_NO_FILE; break; /* Reached to end of the directory */ + } +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + if (FF_USE_LABEL && vol) { + if (b == ET_VLABEL) break; /* Volume label entry? */ + } else { + if (b == ET_FILEDIR) { /* Start of the file entry block? */ + dp->blk_ofs = dp->dptr; /* Get location of the block */ + res = load_xdir(dp); /* Load the entry block */ + if (res == FR_OK) { + dp->obj.attr = fs->dirbuf[XDIR_Attr] & AM_MASK; /* Get attribute */ + } + break; + } + } + } else +#endif + { /* On the FAT/FAT32 volume */ + dp->obj.attr = attr = dp->dir[DIR_Attr] & AM_MASK; /* Get attribute */ +#if FF_USE_LFN /* LFN configuration */ + if (b == DDEM || b == '.' || (int)((attr & ~AM_ARC) == AM_VOL) != vol) { /* An entry without valid data */ + ord = 0xFF; + } else { + if (attr == AM_LFN) { /* An LFN entry is found */ + if (b & LLEF) { /* Is it start of an LFN sequence? */ + sum = dp->dir[LDIR_Chksum]; + b &= (BYTE)~LLEF; ord = b; + dp->blk_ofs = dp->dptr; + } + /* Check LFN validity and capture it */ + ord = (b == ord && sum == dp->dir[LDIR_Chksum] && pick_lfn(fs->lfnbuf, dp->dir)) ? ord - 1 : 0xFF; + } else { /* An SFN entry is found */ + if (ord != 0 || sum != sum_sfn(dp->dir)) { /* Is there a valid LFN? */ + dp->blk_ofs = 0xFFFFFFFF; /* It has no LFN. */ + } + break; + } + } +#else /* Non LFN configuration */ + if (b != DDEM && b != '.' && attr != AM_LFN && (int)((attr & ~AM_ARC) == AM_VOL) == vol) { /* Is it a valid entry? */ + break; + } +#endif + } + res = dir_next(dp, 0); /* Next entry */ + if (res != FR_OK) break; + } + + if (res != FR_OK) dp->sect = 0; /* Terminate the read operation on error or EOT */ + return res; +} + +#endif /* FF_FS_MINIMIZE <= 1 || FF_USE_LABEL || FF_FS_RPATH >= 2 */ + + + +/*-----------------------------------------------------------------------*/ +/* Directory handling - Find an object in the directory */ +/*-----------------------------------------------------------------------*/ + +static FRESULT dir_find ( /* FR_OK(0):succeeded, !=0:error */ + DIR* dp /* Pointer to the directory object with the file name */ +) +{ + FRESULT res; + FATFS *fs = dp->obj.fs; + BYTE c; +#if FF_USE_LFN + BYTE a, ord, sum; +#endif + + res = dir_sdi(dp, 0); /* Rewind directory object */ + if (res != FR_OK) return res; +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + BYTE nc; + UINT di, ni; + WORD hash = xname_sum(fs->lfnbuf); /* Hash value of the name to find */ + + while ((res = DIR_READ_FILE(dp)) == FR_OK) { /* Read an item */ +#if FF_MAX_LFN < 255 + if (fs->dirbuf[XDIR_NumName] > FF_MAX_LFN) continue; /* Skip comparison if inaccessible object name */ +#endif + if (ld_word(fs->dirbuf + XDIR_NameHash) != hash) continue; /* Skip comparison if hash mismatched */ + for (nc = fs->dirbuf[XDIR_NumName], di = SZDIRE * 2, ni = 0; nc; nc--, di += 2, ni++) { /* Compare the name */ + if ((di % SZDIRE) == 0) di += 2; + if (ff_wtoupper(ld_word(fs->dirbuf + di)) != ff_wtoupper(fs->lfnbuf[ni])) break; + } + if (nc == 0 && !fs->lfnbuf[ni]) break; /* Name matched? */ + } + return res; + } +#endif + /* On the FAT/FAT32 volume */ +#if FF_USE_LFN + ord = sum = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */ +#endif + do { + res = move_window(fs, dp->sect); + if (res != FR_OK) break; + c = dp->dir[DIR_Name]; + if (c == 0) { res = FR_NO_FILE; break; } /* Reached to end of table */ +#if FF_USE_LFN /* LFN configuration */ + dp->obj.attr = a = dp->dir[DIR_Attr] & AM_MASK; + if (c == DDEM || ((a & AM_VOL) && a != AM_LFN)) { /* An entry without valid data */ + ord = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */ + } else { + if (a == AM_LFN) { /* An LFN entry is found */ + if (!(dp->fn[NSFLAG] & NS_NOLFN)) { + if (c & LLEF) { /* Is it start of LFN sequence? */ + sum = dp->dir[LDIR_Chksum]; + c &= (BYTE)~LLEF; ord = c; /* LFN start order */ + dp->blk_ofs = dp->dptr; /* Start offset of LFN */ + } + /* Check validity of the LFN entry and compare it with given name */ + ord = (c == ord && sum == dp->dir[LDIR_Chksum] && cmp_lfn(fs->lfnbuf, dp->dir)) ? ord - 1 : 0xFF; + } + } else { /* An SFN entry is found */ + if (ord == 0 && sum == sum_sfn(dp->dir)) break; /* LFN matched? */ + if (!(dp->fn[NSFLAG] & NS_LOSS) && !mem_cmp(dp->dir, dp->fn, 11)) break; /* SFN matched? */ + ord = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */ + } + } +#else /* Non LFN configuration */ + dp->obj.attr = dp->dir[DIR_Attr] & AM_MASK; + if (!(dp->dir[DIR_Attr] & AM_VOL) && !mem_cmp(dp->dir, dp->fn, 11)) break; /* Is it a valid entry? */ +#endif + res = dir_next(dp, 0); /* Next entry */ + } while (res == FR_OK); + + return res; +} + + + + +#if !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Register an object to the directory */ +/*-----------------------------------------------------------------------*/ + +static FRESULT dir_register ( /* FR_OK:succeeded, FR_DENIED:no free entry or too many SFN collision, FR_DISK_ERR:disk error */ + DIR* dp /* Target directory with object name to be created */ +) +{ + FRESULT res; + FATFS *fs = dp->obj.fs; +#if FF_USE_LFN /* LFN configuration */ + UINT n, nlen, nent; + BYTE sn[12], sum; + + + if (dp->fn[NSFLAG] & (NS_DOT | NS_NONAME)) return FR_INVALID_NAME; /* Check name validity */ + for (nlen = 0; fs->lfnbuf[nlen]; nlen++) ; /* Get lfn length */ + +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + nent = (nlen + 14) / 15 + 2; /* Number of entries to allocate (85+C0+C1s) */ + res = dir_alloc(dp, nent); /* Allocate directory entries */ + if (res != FR_OK) return res; + dp->blk_ofs = dp->dptr - SZDIRE * (nent - 1); /* Set the allocated entry block offset */ + + if (dp->obj.stat & 4) { /* Has the directory been stretched by new allocation? */ + dp->obj.stat &= ~4; + res = fill_first_frag(&dp->obj); /* Fill the first fragment on the FAT if needed */ + if (res != FR_OK) return res; + res = fill_last_frag(&dp->obj, dp->clust, 0xFFFFFFFF); /* Fill the last fragment on the FAT if needed */ + if (res != FR_OK) return res; + if (dp->obj.sclust != 0) { /* Is it a sub-directory? */ + DIR dj; + + res = load_obj_xdir(&dj, &dp->obj); /* Load the object status */ + if (res != FR_OK) return res; + dp->obj.objsize += (DWORD)fs->csize * SS(fs); /* Increase the directory size by cluster size */ + st_qword(fs->dirbuf + XDIR_FileSize, dp->obj.objsize); /* Update the allocation status */ + st_qword(fs->dirbuf + XDIR_ValidFileSize, dp->obj.objsize); + fs->dirbuf[XDIR_GenFlags] = dp->obj.stat | 1; + res = store_xdir(&dj); /* Store the object status */ + if (res != FR_OK) return res; + } + } + + create_xdir(fs->dirbuf, fs->lfnbuf); /* Create on-memory directory block to be written later */ + return FR_OK; + } +#endif + /* On the FAT/FAT32 volume */ + mem_cpy(sn, dp->fn, 12); + if (sn[NSFLAG] & NS_LOSS) { /* When LFN is out of 8.3 format, generate a numbered name */ + dp->fn[NSFLAG] = NS_NOLFN; /* Find only SFN */ + for (n = 1; n < 100; n++) { + gen_numname(dp->fn, sn, fs->lfnbuf, n); /* Generate a numbered name */ + res = dir_find(dp); /* Check if the name collides with existing SFN */ + if (res != FR_OK) break; + } + if (n == 100) return FR_DENIED; /* Abort if too many collisions */ + if (res != FR_NO_FILE) return res; /* Abort if the result is other than 'not collided' */ + dp->fn[NSFLAG] = sn[NSFLAG]; + } + + /* Create an SFN with/without LFNs. */ + nent = (sn[NSFLAG] & NS_LFN) ? (nlen + 12) / 13 + 1 : 1; /* Number of entries to allocate */ + res = dir_alloc(dp, nent); /* Allocate entries */ + if (res == FR_OK && --nent) { /* Set LFN entry if needed */ + res = dir_sdi(dp, dp->dptr - nent * SZDIRE); + if (res == FR_OK) { + sum = sum_sfn(dp->fn); /* Checksum value of the SFN tied to the LFN */ + do { /* Store LFN entries in bottom first */ + res = move_window(fs, dp->sect); + if (res != FR_OK) break; + put_lfn(fs->lfnbuf, dp->dir, (BYTE)nent, sum); + fs->wflag = 1; + res = dir_next(dp, 0); /* Next entry */ + } while (res == FR_OK && --nent); + } + } + +#else /* Non LFN configuration */ + res = dir_alloc(dp, 1); /* Allocate an entry for SFN */ + +#endif + + /* Set SFN entry */ + if (res == FR_OK) { + res = move_window(fs, dp->sect); + if (res == FR_OK) { + mem_set(dp->dir, 0, SZDIRE); /* Clean the entry */ + mem_cpy(dp->dir + DIR_Name, dp->fn, 11); /* Put SFN */ +#if FF_USE_LFN + dp->dir[DIR_NTres] = dp->fn[NSFLAG] & (NS_BODY | NS_EXT); /* Put NT flag */ +#endif + fs->wflag = 1; + } + } + + return res; +} + +#endif /* !FF_FS_READONLY */ + + + +#if !FF_FS_READONLY && FF_FS_MINIMIZE == 0 +/*-----------------------------------------------------------------------*/ +/* Remove an object from the directory */ +/*-----------------------------------------------------------------------*/ + +static FRESULT dir_remove ( /* FR_OK:Succeeded, FR_DISK_ERR:A disk error */ + DIR* dp /* Directory object pointing the entry to be removed */ +) +{ + FRESULT res; + FATFS *fs = dp->obj.fs; +#if FF_USE_LFN /* LFN configuration */ + DWORD last = dp->dptr; + + res = (dp->blk_ofs == 0xFFFFFFFF) ? FR_OK : dir_sdi(dp, dp->blk_ofs); /* Goto top of the entry block if LFN is exist */ + if (res == FR_OK) { + do { + res = move_window(fs, dp->sect); + if (res != FR_OK) break; + if (FF_FS_EXFAT && fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + dp->dir[XDIR_Type] &= 0x7F; /* Clear the entry InUse flag. */ + } else { /* On the FAT/FAT32 volume */ + dp->dir[DIR_Name] = DDEM; /* Mark the entry 'deleted'. */ + } + fs->wflag = 1; + if (dp->dptr >= last) break; /* If reached last entry then all entries of the object has been deleted. */ + res = dir_next(dp, 0); /* Next entry */ + } while (res == FR_OK); + if (res == FR_NO_FILE) res = FR_INT_ERR; + } +#else /* Non LFN configuration */ + + res = move_window(fs, dp->sect); + if (res == FR_OK) { + dp->dir[DIR_Name] = DDEM; /* Mark the entry 'deleted'.*/ + fs->wflag = 1; + } +#endif + + return res; +} + +#endif /* !FF_FS_READONLY && FF_FS_MINIMIZE == 0 */ + + + +#if FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 +/*-----------------------------------------------------------------------*/ +/* Get file information from directory entry */ +/*-----------------------------------------------------------------------*/ + +static void get_fileinfo ( + DIR* dp, /* Pointer to the directory object */ + FILINFO* fno /* Pointer to the file information to be filled */ +) +{ + UINT si, di; +#if FF_USE_LFN + BYTE lcf; + WCHAR wc, hs; + FATFS *fs = dp->obj.fs; +#else + TCHAR c; +#endif + + + fno->fname[0] = 0; /* Invaidate file info */ + if (dp->sect == 0) return; /* Exit if read pointer has reached end of directory */ + +#if FF_USE_LFN /* LFN configuration */ +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + get_xfileinfo(fs->dirbuf, fno); + return; + } else +#endif + { /* On the FAT/FAT32 volume */ + if (dp->blk_ofs != 0xFFFFFFFF) { /* Get LFN if available */ + si = di = hs = 0; + while (fs->lfnbuf[si] != 0) { + wc = fs->lfnbuf[si++]; /* Get an LFN character (UTF-16) */ + if (hs == 0 && IsSurrogate(wc)) { /* Is it a surrogate? */ + hs = wc; continue; /* Get low surrogate */ + } + wc = put_utf((DWORD)hs << 16 | wc, &fno->fname[di], FF_LFN_BUF - di); /* Store it in UTF-16 or UTF-8 encoding */ + if (wc == 0) { di = 0; break; } /* Invalid char or buffer overflow? */ + di += wc; + hs = 0; + } + if (hs != 0) di = 0; /* Broken surrogate pair? */ + fno->fname[di] = 0; /* Terminate the LFN (null string means LFN is invalid) */ + } + } + + si = di = 0; + while (si < 11) { /* Get SFN from SFN entry */ + wc = dp->dir[si++]; /* Get a char */ + if (wc == ' ') continue; /* Skip padding spaces */ + if (wc == RDDEM) wc = DDEM; /* Restore replaced DDEM character */ + if (si == 9 && di < FF_SFN_BUF) fno->altname[di++] = '.'; /* Insert a . if extension is exist */ +#if FF_LFN_UNICODE >= 1 /* Unicode output */ + if (dbc_1st((BYTE)wc) && si != 8 && si != 11 && dbc_2nd(dp->dir[si])) { /* Make a DBC if needed */ + wc = wc << 8 | dp->dir[si++]; + } + wc = ff_oem2uni(wc, CODEPAGE); /* ANSI/OEM -> Unicode */ + if (wc == 0) { di = 0; break; } /* Wrong char in the current code page? */ + wc = put_utf(wc, &fno->altname[di], FF_SFN_BUF - di); /* Store it in Unicode */ + if (wc == 0) { di = 0; break; } /* Buffer overflow? */ + di += wc; +#else /* ANSI/OEM output */ + fno->altname[di++] = (TCHAR)wc; /* Store it without any conversion */ +#endif + } + fno->altname[di] = 0; /* Terminate the SFN (null string means SFN is invalid) */ + + if (fno->fname[0] == 0) { /* If LFN is invalid, altname[] needs to be copied to fname[] */ + if (di == 0) { /* If LFN and SFN both are invalid, this object is inaccesible */ + fno->fname[di++] = '?'; + } else { + for (si = di = 0, lcf = NS_BODY; fno->altname[si]; si++, di++) { /* Copy altname[] to fname[] with case information */ + wc = (WCHAR)fno->altname[si]; + if (wc == '.') lcf = NS_EXT; + if (IsUpper(wc) && (dp->dir[DIR_NTres] & lcf)) wc += 0x20; + fno->fname[di] = (TCHAR)wc; + } + } + fno->fname[di] = 0; /* Terminate the LFN */ + if (!dp->dir[DIR_NTres]) fno->altname[0] = 0; /* Altname is not needed if neither LFN nor case info is exist. */ + } + +#else /* Non-LFN configuration */ + si = di = 0; + while (si < 11) { /* Copy name body and extension */ + c = (TCHAR)dp->dir[si++]; + if (c == ' ') continue; /* Skip padding spaces */ + if (c == RDDEM) c = DDEM; /* Restore replaced DDEM character */ + if (si == 9) fno->fname[di++] = '.';/* Insert a . if extension is exist */ + fno->fname[di++] = c; + } + fno->fname[di] = 0; +#endif + + fno->fattrib = dp->dir[DIR_Attr]; /* Attribute */ + fno->fsize = ld_dword(dp->dir + DIR_FileSize); /* Size */ + fno->ftime = ld_word(dp->dir + DIR_ModTime + 0); /* Time */ + fno->fdate = ld_word(dp->dir + DIR_ModTime + 2); /* Date */ +} + +#endif /* FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 */ + + + +#if FF_USE_FIND && FF_FS_MINIMIZE <= 1 +/*-----------------------------------------------------------------------*/ +/* Pattern matching */ +/*-----------------------------------------------------------------------*/ + +static DWORD get_achar ( /* Get a character and advances ptr */ + const TCHAR** ptr /* Pointer to pointer to the ANSI/OEM or Unicode string */ +) +{ + DWORD chr; + + +#if FF_USE_LFN && FF_LFN_UNICODE >= 1 /* Unicode input */ + chr = tchar2uni(ptr); + if (chr == 0xFFFFFFFF) chr = 0; /* Wrong UTF encoding is recognized as end of the string */ + chr = ff_wtoupper(chr); + +#else /* ANSI/OEM input */ + chr = (BYTE)*(*ptr)++; /* Get a byte */ + if (IsLower(chr)) chr -= 0x20; /* To upper ASCII char */ +#if FF_CODE_PAGE == 0 + if (ExCvt && chr >= 0x80) chr = ExCvt[chr - 0x80]; /* To upper SBCS extended char */ +#elif FF_CODE_PAGE < 900 + if (chr >= 0x80) chr = ExCvt[chr - 0x80]; /* To upper SBCS extended char */ +#endif +#if FF_CODE_PAGE == 0 || FF_CODE_PAGE >= 900 + if (dbc_1st((BYTE)chr)) { /* Get DBC 2nd byte if needed */ + chr = dbc_2nd((BYTE)**ptr) ? chr << 8 | (BYTE)*(*ptr)++ : 0; + } +#endif + +#endif + return chr; +} + + +static int pattern_matching ( /* 0:not matched, 1:matched */ + const TCHAR* pat, /* Matching pattern */ + const TCHAR* nam, /* String to be tested */ + int skip, /* Number of pre-skip chars (number of ?s) */ + int inf /* Infinite search (* specified) */ +) +{ + const TCHAR *pp, *np; + DWORD pc, nc; + int nm, nx; + + + while (skip--) { /* Pre-skip name chars */ + if (!get_achar(&nam)) return 0; /* Branch mismatched if less name chars */ + } + if (*pat == 0 && inf) return 1; /* (short circuit) */ + + do { + pp = pat; np = nam; /* Top of pattern and name to match */ + for (;;) { + if (*pp == '?' || *pp == '*') { /* Wildcard? */ + nm = nx = 0; + do { /* Analyze the wildcard block */ + if (*pp++ == '?') nm++; else nx = 1; + } while (*pp == '?' || *pp == '*'); + if (pattern_matching(pp, np, nm, nx)) return 1; /* Test new branch (recurs upto number of wildcard blocks in the pattern) */ + nc = *np; break; /* Branch mismatched */ + } + pc = get_achar(&pp); /* Get a pattern char */ + nc = get_achar(&np); /* Get a name char */ + if (pc != nc) break; /* Branch mismatched? */ + if (pc == 0) return 1; /* Branch matched? (matched at end of both strings) */ + } + get_achar(&nam); /* nam++ */ + } while (inf && nc); /* Retry until end of name if infinite search is specified */ + + return 0; +} + +#endif /* FF_USE_FIND && FF_FS_MINIMIZE <= 1 */ + + + +/*-----------------------------------------------------------------------*/ +/* Pick a top segment and create the object name in directory form */ +/*-----------------------------------------------------------------------*/ + +static FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not create */ + DIR* dp, /* Pointer to the directory object */ + const TCHAR** path /* Pointer to pointer to the segment in the path string */ +) +{ +#if FF_USE_LFN /* LFN configuration */ + BYTE b, cf; + WCHAR wc, *lfn; + DWORD uc; + UINT i, ni, si, di; + const TCHAR *p; + + + /* Create LFN into LFN working buffer */ + p = *path; lfn = dp->obj.fs->lfnbuf; di = 0; + for (;;) { + uc = tchar2uni(&p); /* Get a character */ + if (uc == 0xFFFFFFFF) return FR_INVALID_NAME; /* Invalid code or UTF decode error */ + if (uc >= 0x10000) lfn[di++] = (WCHAR)(uc >> 16); /* Store high surrogate if needed */ + wc = (WCHAR)uc; + if (wc < ' ' || wc == '/' || wc == '\\') break; /* Break if end of the path or a separator is found */ + if (wc < 0x80 && chk_chr("\"*:<>\?|\x7F", wc)) return FR_INVALID_NAME; /* Reject illegal characters for LFN */ + if (di >= FF_MAX_LFN) return FR_INVALID_NAME; /* Reject too long name */ + lfn[di++] = wc; /* Store the Unicode character */ + } + while (*p == '/' || *p == '\\') p++; /* Skip duplicated separators if exist */ + *path = p; /* Return pointer to the next segment */ + cf = (wc < ' ') ? NS_LAST : 0; /* Set last segment flag if end of the path */ + +#if FF_FS_RPATH != 0 + if ((di == 1 && lfn[di - 1] == '.') || + (di == 2 && lfn[di - 1] == '.' && lfn[di - 2] == '.')) { /* Is this segment a dot name? */ + lfn[di] = 0; + for (i = 0; i < 11; i++) { /* Create dot name for SFN entry */ + dp->fn[i] = (i < di) ? '.' : ' '; + } + dp->fn[i] = cf | NS_DOT; /* This is a dot entry */ + return FR_OK; + } +#endif + while (di) { /* Snip off trailing spaces and dots if exist */ + wc = lfn[di - 1]; + if (wc != ' ' && wc != '.') break; + di--; + } + lfn[di] = 0; /* LFN is created into the working buffer */ + if (di == 0) return FR_INVALID_NAME; /* Reject null name */ + + /* Create SFN in directory form */ + for (si = 0; lfn[si] == ' '; si++) ; /* Remove leading spaces */ + if (si > 0 || lfn[si] == '.') cf |= NS_LOSS | NS_LFN; /* Is there any leading space or dot? */ + while (di > 0 && lfn[di - 1] != '.') di--; /* Find last dot (di<=si: no extension) */ + + mem_set(dp->fn, ' ', 11); + i = b = 0; ni = 8; + for (;;) { + wc = lfn[si++]; /* Get an LFN character */ + if (wc == 0) break; /* Break on end of the LFN */ + if (wc == ' ' || (wc == '.' && si != di)) { /* Remove embedded spaces and dots */ + cf |= NS_LOSS | NS_LFN; + continue; + } + + if (i >= ni || si == di) { /* End of field? */ + if (ni == 11) { /* Name extension overflow? */ + cf |= NS_LOSS | NS_LFN; + break; + } + if (si != di) cf |= NS_LOSS | NS_LFN; /* Name body overflow? */ + if (si > di) break; /* No name extension? */ + si = di; i = 8; ni = 11; b <<= 2; /* Enter name extension */ + continue; + } + + if (wc >= 0x80) { /* Is this a non-ASCII character? */ + cf |= NS_LFN; /* LFN entry needs to be created */ +#if FF_CODE_PAGE == 0 + if (ExCvt) { /* At SBCS */ + wc = ff_uni2oem(wc, CODEPAGE); /* Unicode ==> ANSI/OEM code */ + if (wc & 0x80) wc = ExCvt[wc & 0x7F]; /* Convert extended character to upper (SBCS) */ + } else { /* At DBCS */ + wc = ff_uni2oem(ff_wtoupper(wc), CODEPAGE); /* Unicode ==> Upper convert ==> ANSI/OEM code */ + } +#elif FF_CODE_PAGE < 900 /* SBCS cfg */ + wc = ff_uni2oem(wc, CODEPAGE); /* Unicode ==> ANSI/OEM code */ + if (wc & 0x80) wc = ExCvt[wc & 0x7F]; /* Convert extended character to upper (SBCS) */ +#else /* DBCS cfg */ + wc = ff_uni2oem(ff_wtoupper(wc), CODEPAGE); /* Unicode ==> Upper convert ==> ANSI/OEM code */ +#endif + } + + if (wc >= 0x100) { /* Is this a DBC? */ + if (i >= ni - 1) { /* Field overflow? */ + cf |= NS_LOSS | NS_LFN; + i = ni; continue; /* Next field */ + } + dp->fn[i++] = (BYTE)(wc >> 8); /* Put 1st byte */ + } else { /* SBC */ + if (wc == 0 || chk_chr("+,;=[]", wc)) { /* Replace illegal characters for SFN if needed */ + wc = '_'; cf |= NS_LOSS | NS_LFN;/* Lossy conversion */ + } else { + if (IsUpper(wc)) { /* ASCII upper case? */ + b |= 2; + } + if (IsLower(wc)) { /* ASCII lower case? */ + b |= 1; wc -= 0x20; + } + } + } + dp->fn[i++] = (BYTE)wc; + } + + if (dp->fn[0] == DDEM) dp->fn[0] = RDDEM; /* If the first character collides with DDEM, replace it with RDDEM */ + + if (ni == 8) b <<= 2; /* Shift capital flags if no extension */ + if ((b & 0x0C) == 0x0C || (b & 0x03) == 0x03) cf |= NS_LFN; /* LFN entry needs to be created if composite capitals */ + if (!(cf & NS_LFN)) { /* When LFN is in 8.3 format without extended character, NT flags are created */ + if (b & 0x01) cf |= NS_EXT; /* NT flag (Extension has small capital letters only) */ + if (b & 0x04) cf |= NS_BODY; /* NT flag (Body has small capital letters only) */ + } + + dp->fn[NSFLAG] = cf; /* SFN is created into dp->fn[] */ + + return FR_OK; + + +#else /* FF_USE_LFN : Non-LFN configuration */ + BYTE c, d, *sfn; + UINT ni, si, i; + const char *p; + + /* Create file name in directory form */ + p = *path; sfn = dp->fn; + mem_set(sfn, ' ', 11); + si = i = 0; ni = 8; +#if FF_FS_RPATH != 0 + if (p[si] == '.') { /* Is this a dot entry? */ + for (;;) { + c = (BYTE)p[si++]; + if (c != '.' || si >= 3) break; + sfn[i++] = c; + } + if (c != '/' && c != '\\' && c > ' ') return FR_INVALID_NAME; + *path = p + si; /* Return pointer to the next segment */ + sfn[NSFLAG] = (c <= ' ') ? NS_LAST | NS_DOT : NS_DOT; /* Set last segment flag if end of the path */ + return FR_OK; + } +#endif + for (;;) { + c = (BYTE)p[si++]; /* Get a byte */ + if (c <= ' ') break; /* Break if end of the path name */ + if (c == '/' || c == '\\') { /* Break if a separator is found */ + while (p[si] == '/' || p[si] == '\\') si++; /* Skip duplicated separator if exist */ + break; + } + if (c == '.' || i >= ni) { /* End of body or field overflow? */ + if (ni == 11 || c != '.') return FR_INVALID_NAME; /* Field overflow or invalid dot? */ + i = 8; ni = 11; /* Enter file extension field */ + continue; + } +#if FF_CODE_PAGE == 0 + if (ExCvt && c >= 0x80) { /* Is SBC extended character? */ + c = ExCvt[c & 0x7F]; /* To upper SBC extended character */ + } +#elif FF_CODE_PAGE < 900 + if (c >= 0x80) { /* Is SBC extended character? */ + c = ExCvt[c & 0x7F]; /* To upper SBC extended character */ + } +#endif + if (dbc_1st(c)) { /* Check if it is a DBC 1st byte */ + d = (BYTE)p[si++]; /* Get 2nd byte */ + if (!dbc_2nd(d) || i >= ni - 1) return FR_INVALID_NAME; /* Reject invalid DBC */ + sfn[i++] = c; + sfn[i++] = d; + } else { /* SBC */ + if (chk_chr("\"*+,:;<=>\?[]|\x7F", c)) return FR_INVALID_NAME; /* Reject illegal chrs for SFN */ + if (IsLower(c)) c -= 0x20; /* To upper */ + sfn[i++] = c; + } + } + *path = p + si; /* Return pointer to the next segment */ + if (i == 0) return FR_INVALID_NAME; /* Reject nul string */ + + if (sfn[0] == DDEM) sfn[0] = RDDEM; /* If the first character collides with DDEM, replace it with RDDEM */ + sfn[NSFLAG] = (c <= ' ') ? NS_LAST : 0; /* Set last segment flag if end of the path */ + + return FR_OK; +#endif /* FF_USE_LFN */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Follow a file path */ +/*-----------------------------------------------------------------------*/ + +static FRESULT follow_path ( /* FR_OK(0): successful, !=0: error code */ + DIR* dp, /* Directory object to return last directory and found object */ + const TCHAR* path /* Full-path string to find a file or directory */ +) +{ + FRESULT res; + BYTE ns; + FATFS *fs = dp->obj.fs; + + +#if FF_FS_RPATH != 0 + if (*path != '/' && *path != '\\') { /* Without heading separator */ + dp->obj.sclust = fs->cdir; /* Start from current directory */ + } else +#endif + { /* With heading separator */ + while (*path == '/' || *path == '\\') path++; /* Strip heading separator */ + dp->obj.sclust = 0; /* Start from root directory */ + } +#if FF_FS_EXFAT + dp->obj.n_frag = 0; /* Invalidate last fragment counter of the object */ +#if FF_FS_RPATH != 0 + if (fs->fs_type == FS_EXFAT && dp->obj.sclust) { /* exFAT: Retrieve the sub-directory's status */ + DIR dj; + + dp->obj.c_scl = fs->cdc_scl; + dp->obj.c_size = fs->cdc_size; + dp->obj.c_ofs = fs->cdc_ofs; + res = load_obj_xdir(&dj, &dp->obj); + if (res != FR_OK) return res; + dp->obj.objsize = ld_dword(fs->dirbuf + XDIR_FileSize); + dp->obj.stat = fs->dirbuf[XDIR_GenFlags] & 2; + } +#endif +#endif + + if ((UINT)*path < ' ') { /* Null path name is the origin directory itself */ + dp->fn[NSFLAG] = NS_NONAME; + res = dir_sdi(dp, 0); + + } else { /* Follow path */ + for (;;) { + res = create_name(dp, &path); /* Get a segment name of the path */ + if (res != FR_OK) break; + res = dir_find(dp); /* Find an object with the segment name */ + ns = dp->fn[NSFLAG]; + if (res != FR_OK) { /* Failed to find the object */ + if (res == FR_NO_FILE) { /* Object is not found */ + if (FF_FS_RPATH && (ns & NS_DOT)) { /* If dot entry is not exist, stay there */ + if (!(ns & NS_LAST)) continue; /* Continue to follow if not last segment */ + dp->fn[NSFLAG] = NS_NONAME; + res = FR_OK; + } else { /* Could not find the object */ + if (!(ns & NS_LAST)) res = FR_NO_PATH; /* Adjust error code if not last segment */ + } + } + break; + } + if (ns & NS_LAST) break; /* Last segment matched. Function completed. */ + /* Get into the sub-directory */ + if (!(dp->obj.attr & AM_DIR)) { /* It is not a sub-directory and cannot follow */ + res = FR_NO_PATH; break; + } +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* Save containing directory information for next dir */ + dp->obj.c_scl = dp->obj.sclust; + dp->obj.c_size = ((DWORD)dp->obj.objsize & 0xFFFFFF00) | dp->obj.stat; + dp->obj.c_ofs = dp->blk_ofs; + init_alloc_info(fs, &dp->obj); /* Open next directory */ + } else +#endif + { + dp->obj.sclust = ld_clust(fs, fs->win + dp->dptr % SS(fs)); /* Open next directory */ + } + } + } + + return res; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Load a sector and check if it is an FAT VBR */ +/*-----------------------------------------------------------------------*/ + +static BYTE check_fs ( /* 0:FAT, 1:exFAT, 2:Valid BS but not FAT, 3:Not a BS, 4:Disk error */ + FATFS* fs, /* Filesystem object */ + DWORD sect /* Sector# (lba) to load and check if it is an FAT-VBR or not */ +) +{ + fs->wflag = 0; fs->winsect = 0xFFFFFFFF; /* Invaidate window */ + if (move_window(fs, sect) != FR_OK) return 4; /* Load boot record */ + + if (ld_word(fs->win + BS_55AA) != 0xAA55) return 3; /* Check boot record signature (always here regardless of the sector size) */ + +#if FF_FS_EXFAT + if (!mem_cmp(fs->win + BS_JmpBoot, "\xEB\x76\x90" "EXFAT ", 11)) return 1; /* Check if exFAT VBR */ +#endif + if (fs->win[BS_JmpBoot] == 0xE9 || fs->win[BS_JmpBoot] == 0xEB || fs->win[BS_JmpBoot] == 0xE8) { /* Valid JumpBoot code? */ + if (!mem_cmp(fs->win + BS_FilSysType, "FAT", 3)) return 0; /* Is it an FAT VBR? */ + if (!mem_cmp(fs->win + BS_FilSysType32, "FAT32", 5)) return 0; /* Is it an FAT32 VBR? */ + } + return 2; /* Valid BS but not FAT */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Determine logical drive number and mount the volume if needed */ +/*-----------------------------------------------------------------------*/ + +static FRESULT find_volume ( /* FR_OK(0): successful, !=0: an error occurred */ + FATFS *fs, /* Pointer to the file system object */ + BYTE mode /* !=0: Check write protection for write access */ +) +{ + BYTE fmt, *pt; + DSTATUS stat; + DWORD bsect, fasize, tsect, sysect, nclst, szbfat, br[4]; + WORD nrsv; + UINT i; + + +#if FF_FS_REENTRANT + if (!lock_fs(fs)) return FR_TIMEOUT; /* Lock the volume */ +#endif + + mode &= (BYTE)~FA_READ; /* Desired access mode, write access or not */ + if (fs->fs_type != 0) { /* If the volume has been mounted */ + disk_ioctl(fs->drv, IOCTL_STATUS, &stat); + if (!(stat & STA_NOINIT)) { /* and the physical drive is kept initialized */ + if (!FF_FS_READONLY && mode && (stat & STA_PROTECT)) { /* Check write protection if needed */ + return FR_WRITE_PROTECTED; + } + return FR_OK; /* The filesystem object is valid */ + } + } + + /* The filesystem object is not valid. */ + /* Following code attempts to mount the volume. (analyze BPB and initialize the filesystem object) */ + + fs->fs_type = 0; /* Clear the filesystem object */ + disk_ioctl(fs->drv, IOCTL_INIT, &stat); /* Initialize the physical drive */ + if (stat & STA_NOINIT) { /* Check if the initialization succeeded */ + return FR_NOT_READY; /* Failed to initialize due to no medium or hard error */ + } + if (!FF_FS_READONLY && mode && (stat & STA_PROTECT)) { /* Check disk write protection if needed */ + return FR_WRITE_PROTECTED; + } +#if FF_MAX_SS != FF_MIN_SS /* Get sector size (multiple sector size cfg only) */ + if (disk_ioctl(fs->drv, GET_SECTOR_SIZE, &SS(fs)) != RES_OK) return FR_DISK_ERR; + if (SS(fs) > FF_MAX_SS || SS(fs) < FF_MIN_SS || (SS(fs) & (SS(fs) - 1))) return FR_DISK_ERR; +#endif + + /* Find an FAT partition on the drive. Supports only generic partitioning rules, FDISK (MBR) and SFD (w/o partition). */ + bsect = 0; + fmt = check_fs(fs, bsect); /* Load sector 0 and check if it is an FAT-VBR as SFD */ + if (fmt == 2 || (fmt < 2 && LD2PT(fs) != 0)) { /* Not an FAT-VBR or forced partition number */ + for (i = 0; i < 4; i++) { /* Get partition offset */ + pt = fs->win + (MBR_Table + i * SZ_PTE); + br[i] = pt[PTE_System] ? ld_dword(pt + PTE_StLba) : 0; + } + i = LD2PT(fs); /* Partition number: 0:auto, 1-4:forced */ + if (i != 0) i--; + do { /* Find an FAT volume */ + bsect = br[i]; + fmt = bsect ? check_fs(fs, bsect) : 3; /* Check the partition */ + } while (LD2PT(fs) == 0 && fmt >= 2 && ++i < 4); + } + if (fmt == 4) return FR_DISK_ERR; /* An error occured in the disk I/O layer */ + if (fmt >= 2) return FR_NO_FILESYSTEM; /* No FAT volume is found */ + + /* An FAT volume is found (bsect). Following code initializes the filesystem object */ + +#if FF_FS_EXFAT + if (fmt == 1) { + QWORD maxlba; + DWORD so, cv, bcl; + + for (i = BPB_ZeroedEx; i < BPB_ZeroedEx + 53 && fs->win[i] == 0; i++) ; /* Check zero filler */ + if (i < BPB_ZeroedEx + 53) return FR_NO_FILESYSTEM; + + if (ld_word(fs->win + BPB_FSVerEx) != 0x100) return FR_NO_FILESYSTEM; /* Check exFAT version (must be version 1.0) */ + + if (1 << fs->win[BPB_BytsPerSecEx] != SS(fs)) { /* (BPB_BytsPerSecEx must be equal to the physical sector size) */ + return FR_NO_FILESYSTEM; + } + + maxlba = ld_qword(fs->win + BPB_TotSecEx) + bsect; /* Last LBA + 1 of the volume */ + if (maxlba >= 0x100000000) return FR_NO_FILESYSTEM; /* (It cannot be handled in 32-bit LBA) */ + + fs->fsize = ld_dword(fs->win + BPB_FatSzEx); /* Number of sectors per FAT */ + + fs->n_fats = fs->win[BPB_NumFATsEx]; /* Number of FATs */ + if (fs->n_fats != 1) return FR_NO_FILESYSTEM; /* (Supports only 1 FAT) */ + + fs->csize = 1 << fs->win[BPB_SecPerClusEx]; /* Cluster size */ + if (fs->csize == 0) return FR_NO_FILESYSTEM; /* (Must be 1..32768) */ + + nclst = ld_dword(fs->win + BPB_NumClusEx); /* Number of clusters */ + if (nclst > MAX_EXFAT) return FR_NO_FILESYSTEM; /* (Too many clusters) */ + fs->n_fatent = nclst + 2; + + /* Boundaries and Limits */ + fs->volbase = bsect; + fs->database = bsect + ld_dword(fs->win + BPB_DataOfsEx); + fs->fatbase = bsect + ld_dword(fs->win + BPB_FatOfsEx); + if (maxlba < (QWORD)fs->database + nclst * fs->csize) return FR_NO_FILESYSTEM; /* (Volume size must not be smaller than the size requiered) */ + fs->dirbase = ld_dword(fs->win + BPB_RootClusEx); + + /* Get bitmap location and check if it is contiguous (implementation assumption) */ + so = i = 0; + for (;;) { /* Find the bitmap entry in the root directory (in only first cluster) */ + if (i == 0) { + if (so >= fs->csize) return FR_NO_FILESYSTEM; /* Not found? */ + if (move_window(fs, clst2sect(fs, fs->dirbase) + so) != FR_OK) return FR_DISK_ERR; + so++; + } + if (fs->win[i] == ET_BITMAP) break; /* Is it a bitmap entry? */ + i = (i + SZDIRE) % SS(fs); /* Next entry */ + } + bcl = ld_dword(fs->win + i + 20); /* Bitmap cluster */ + if (bcl < 2 || bcl >= fs->n_fatent) return FR_NO_FILESYSTEM; + fs->bitbase = fs->database + fs->csize * (bcl - 2); /* Bitmap sector */ + for (;;) { /* Check if bitmap is contiguous */ + if (move_window(fs, fs->fatbase + bcl / (SS(fs) / 4)) != FR_OK) return FR_DISK_ERR; + cv = ld_dword(fs->win + bcl % (SS(fs) / 4) * 4); + if (cv == 0xFFFFFFFF) break; /* Last link? */ + if (cv != ++bcl) return FR_NO_FILESYSTEM; /* Fragmented? */ + } + +#if !FF_FS_READONLY + fs->last_clst = fs->free_clst = 0xFFFFFFFF; /* Initialize cluster allocation information */ +#endif + fmt = FS_EXFAT; /* FAT sub-type */ + } else +#endif /* FF_FS_EXFAT */ + { + if (ld_word(fs->win + BPB_BytsPerSec) != SS(fs)) return FR_NO_FILESYSTEM; /* (BPB_BytsPerSec must be equal to the physical sector size) */ + + fasize = ld_word(fs->win + BPB_FATSz16); /* Number of sectors per FAT */ + if (fasize == 0) fasize = ld_dword(fs->win + BPB_FATSz32); + fs->fsize = fasize; + + fs->n_fats = fs->win[BPB_NumFATs]; /* Number of FATs */ + if (fs->n_fats != 1 && fs->n_fats != 2) return FR_NO_FILESYSTEM; /* (Must be 1 or 2) */ + fasize *= fs->n_fats; /* Number of sectors for FAT area */ + + fs->csize = fs->win[BPB_SecPerClus]; /* Cluster size */ + if (fs->csize == 0 || (fs->csize & (fs->csize - 1))) return FR_NO_FILESYSTEM; /* (Must be power of 2) */ + + fs->n_rootdir = ld_word(fs->win + BPB_RootEntCnt); /* Number of root directory entries */ + if (fs->n_rootdir % (SS(fs) / SZDIRE)) return FR_NO_FILESYSTEM; /* (Must be sector aligned) */ + + tsect = ld_word(fs->win + BPB_TotSec16); /* Number of sectors on the volume */ + if (tsect == 0) tsect = ld_dword(fs->win + BPB_TotSec32); + + nrsv = ld_word(fs->win + BPB_RsvdSecCnt); /* Number of reserved sectors */ + if (nrsv == 0) return FR_NO_FILESYSTEM; /* (Must not be 0) */ + + /* Determine the FAT sub type */ + sysect = nrsv + fasize + fs->n_rootdir / (SS(fs) / SZDIRE); /* RSV + FAT + DIR */ + if (tsect < sysect) return FR_NO_FILESYSTEM; /* (Invalid volume size) */ + nclst = (tsect - sysect) / fs->csize; /* Number of clusters */ + if (nclst == 0) return FR_NO_FILESYSTEM; /* (Invalid volume size) */ + fmt = 0; + if (nclst <= MAX_FAT32) fmt = FS_FAT32; + if (nclst <= MAX_FAT16) fmt = FS_FAT16; + if (nclst <= MAX_FAT12) fmt = FS_FAT12; + if (fmt == 0) return FR_NO_FILESYSTEM; + + /* Boundaries and Limits */ + fs->n_fatent = nclst + 2; /* Number of FAT entries */ + fs->volbase = bsect; /* Volume start sector */ + fs->fatbase = bsect + nrsv; /* FAT start sector */ + fs->database = bsect + sysect; /* Data start sector */ + if (fmt == FS_FAT32) { + if (ld_word(fs->win + BPB_FSVer32) != 0) return FR_NO_FILESYSTEM; /* (Must be FAT32 revision 0.0) */ + if (fs->n_rootdir != 0) return FR_NO_FILESYSTEM; /* (BPB_RootEntCnt must be 0) */ + fs->dirbase = ld_dword(fs->win + BPB_RootClus32); /* Root directory start cluster */ + szbfat = fs->n_fatent * 4; /* (Needed FAT size) */ + } else { + if (fs->n_rootdir == 0) return FR_NO_FILESYSTEM; /* (BPB_RootEntCnt must not be 0) */ + fs->dirbase = fs->fatbase + fasize; /* Root directory start sector */ + szbfat = (fmt == FS_FAT16) ? /* (Needed FAT size) */ + fs->n_fatent * 2 : fs->n_fatent * 3 / 2 + (fs->n_fatent & 1); + } + if (fs->fsize < (szbfat + (SS(fs) - 1)) / SS(fs)) return FR_NO_FILESYSTEM; /* (BPB_FATSz must not be less than the size needed) */ + +#if !FF_FS_READONLY + /* Get FSInfo if available */ + fs->last_clst = fs->free_clst = 0xFFFFFFFF; /* Initialize cluster allocation information */ + fs->fsi_flag = 0x80; +#if (FF_FS_NOFSINFO & 3) != 3 + if (fmt == FS_FAT32 /* Allow to update FSInfo only if BPB_FSInfo32 == 1 */ + && ld_word(fs->win + BPB_FSInfo32) == 1 + && move_window(fs, bsect + 1) == FR_OK) + { + fs->fsi_flag = 0; + if (ld_word(fs->win + BS_55AA) == 0xAA55 /* Load FSInfo data if available */ + && ld_dword(fs->win + FSI_LeadSig) == 0x41615252 + && ld_dword(fs->win + FSI_StrucSig) == 0x61417272) + { +#if (FF_FS_NOFSINFO & 1) == 0 + fs->free_clst = ld_dword(fs->win + FSI_Free_Count); +#endif +#if (FF_FS_NOFSINFO & 2) == 0 + fs->last_clst = ld_dword(fs->win + FSI_Nxt_Free); +#endif + } + } +#endif /* (FF_FS_NOFSINFO & 3) != 3 */ +#endif /* !FF_FS_READONLY */ + } + + fs->fs_type = fmt; /* FAT sub-type */ + fs->id = ++Fsid; /* Volume mount ID */ +#if FF_USE_LFN == 1 + fs->lfnbuf = LfnBuf; /* Static LFN working buffer */ +#if FF_FS_EXFAT + fs->dirbuf = DirBuf; /* Static directory block scratchpad buuffer */ +#endif +#endif +#if FF_FS_RPATH != 0 + fs->cdir = 0; /* Initialize current directory */ +#endif +#if FF_FS_LOCK != 0 /* Clear file lock semaphores */ + clear_lock(fs); +#endif + return FR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Check if the file/directory object is valid or not */ +/*-----------------------------------------------------------------------*/ + +static FRESULT validate ( /* Returns FR_OK or FR_INVALID_OBJECT */ + FFOBJID* obj, /* Pointer to the FFOBJID, the 1st member in the FIL/DIR object, to check validity */ + FATFS** rfs /* Pointer to pointer to the owner filesystem object to return */ +) +{ + FRESULT res = FR_INVALID_OBJECT; + DSTATUS stat; + + + if (obj && obj->fs && obj->fs->fs_type && obj->id == obj->fs->id) { /* Test if the object is valid */ +#if FF_FS_REENTRANT + if (lock_fs(obj->fs)) { /* Obtain the filesystem object */ + if (disk_ioctl(obj->fs->drv, IOCTL_STATUS, &stat) == RES_OK && !(stat & STA_NOINIT)) { /* Test if the phsical drive is kept initialized */ + res = FR_OK; + } else { + unlock_fs(obj->fs, FR_OK); + } + } else { + res = FR_TIMEOUT; + } +#else + if (disk_ioctl(obj->fs->drv, IOCTL_STATUS, &stat) == RES_OK && !(stat & STA_NOINIT)) { /* Test if the phsical drive is kept initialized */ + res = FR_OK; + } +#endif + } + *rfs = (res == FR_OK) ? obj->fs : 0; /* Corresponding filesystem object */ + return res; +} + + + + +/*--------------------------------------------------------------------------- + + Public Functions (FatFs API) + +----------------------------------------------------------------------------*/ + + + +/*-----------------------------------------------------------------------*/ +/* Mount/Unmount a Logical Drive */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_mount ( + FATFS* fs /* Pointer to the file system object to mount */ +) +{ + FRESULT res; + + fs->fs_type = 0; /* Clear new fs object */ +#if FF_FS_REENTRANT /* Create sync object for the new volume */ + if (!ff_cre_syncobj(fs, &fs->sobj)) return FR_INT_ERR; +#endif + + res = find_volume(fs, 0); /* Force mounted the volume */ + LEAVE_FF(fs, res); +} + + +FRESULT f_umount ( + FATFS* fs /* Pointer to the file system object to unmount */ +) +{ +#if FF_FS_LOCK + clear_lock(fs); +#endif +#if FF_FS_REENTRANT /* Discard sync object of the current volume */ + if (!ff_del_syncobj(fs->sobj)) return FR_INT_ERR; +#endif + fs->fs_type = 0; /* Clear old fs object */ + + return FR_OK; +} + + +/*-----------------------------------------------------------------------*/ +/* Open or Create a File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_open ( + FATFS *fs, + FIL* fp, /* Pointer to the blank file object */ + const TCHAR* path, /* Pointer to the file name */ + BYTE mode /* Access mode and file open mode flags */ +) +{ + FRESULT res; + DIR dj; +#if !FF_FS_READONLY + DWORD dw, cl, bcs, clst, sc; + FSIZE_t ofs; +#endif + DEF_NAMBUF + + + if (!fp) return FR_INVALID_OBJECT; + + /* Get logical drive number */ + mode &= FF_FS_READONLY ? FA_READ : FA_READ | FA_WRITE | FA_CREATE_ALWAYS | FA_CREATE_NEW | FA_OPEN_ALWAYS | FA_OPEN_APPEND; + res = find_volume(fs, mode); + if (res == FR_OK) { + dj.obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(&dj, path); /* Follow the file path */ +#if !FF_FS_READONLY /* Read/Write configuration */ + if (res == FR_OK) { + if (dj.fn[NSFLAG] & NS_NONAME) { /* Origin directory itself? */ + res = FR_INVALID_NAME; + } +#if FF_FS_LOCK != 0 + else { + res = chk_lock(&dj, (mode & ~FA_READ) ? 1 : 0); /* Check if the file can be used */ + } +#endif + } + /* Create or Open a file */ + if (mode & (FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW)) { + if (res != FR_OK) { /* No file, create new */ + if (res == FR_NO_FILE) { /* There is no file to open, create a new entry */ +#if FF_FS_LOCK != 0 + res = enq_lock() ? dir_register(&dj) : FR_TOO_MANY_OPEN_FILES; +#else + res = dir_register(&dj); +#endif + } + mode |= FA_CREATE_ALWAYS; /* File is created */ + } + else { /* Any object with the same name is already existing */ + if (dj.obj.attr & (AM_RDO | AM_DIR)) { /* Cannot overwrite it (R/O or DIR) */ + res = FR_DENIED; + } else { + if (mode & FA_CREATE_NEW) res = FR_EXIST; /* Cannot create as new file */ + } + } + if (res == FR_OK && (mode & FA_CREATE_ALWAYS)) { /* Truncate the file if overwrite mode */ +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + /* Get current allocation info */ + fp->obj.fs = fs; + init_alloc_info(fs, &fp->obj); + /* Set directory entry block initial state */ + mem_set(fs->dirbuf + 2, 0, 30); /* Clear 85 entry except for NumSec */ + mem_set(fs->dirbuf + 38, 0, 26); /* Clear C0 entry except for NumName and NameHash */ + fs->dirbuf[XDIR_Attr] = AM_ARC; + st_dword(fs->dirbuf + XDIR_CrtTime, GET_FATTIME()); + fs->dirbuf[XDIR_GenFlags] = 1; + res = store_xdir(&dj); + if (res == FR_OK && fp->obj.sclust != 0) { /* Remove the cluster chain if exist */ + res = remove_chain(&fp->obj, fp->obj.sclust, 0); + fs->last_clst = fp->obj.sclust - 1; /* Reuse the cluster hole */ + } + } else +#endif + { + /* Set directory entry initial state */ + cl = ld_clust(fs, dj.dir); /* Get current cluster chain */ + st_dword(dj.dir + DIR_CrtTime, GET_FATTIME()); /* Set created time */ + dj.dir[DIR_Attr] = AM_ARC; /* Reset attribute */ + st_clust(fs, dj.dir, 0); /* Reset file allocation info */ + st_dword(dj.dir + DIR_FileSize, 0); + fs->wflag = 1; + if (cl != 0) { /* Remove the cluster chain if exist */ + dw = fs->winsect; + res = remove_chain(&dj.obj, cl, 0); + if (res == FR_OK) { + res = move_window(fs, dw); + fs->last_clst = cl - 1; /* Reuse the cluster hole */ + } + } + } + } + } + else { /* Open an existing file */ + if (res == FR_OK) { /* Is the object exsiting? */ + if (dj.obj.attr & AM_DIR) { /* File open against a directory */ + res = FR_NO_FILE; + } else { + if ((mode & FA_WRITE) && (dj.obj.attr & AM_RDO)) { /* Write mode open against R/O file */ + res = FR_DENIED; + } + } + } + } + if (res == FR_OK) { + if (mode & FA_CREATE_ALWAYS) mode |= FA_MODIFIED; /* Set file change flag if created or overwritten */ + fp->dir_sect = fs->winsect; /* Pointer to the directory entry */ + fp->dir_ptr = dj.dir; +#if FF_FS_LOCK != 0 + fp->obj.lockid = inc_lock(&dj, (mode & ~FA_READ) ? 1 : 0); /* Lock the file for this session */ + if (fp->obj.lockid == 0) res = FR_INT_ERR; +#endif + } +#else /* R/O configuration */ + if (res == FR_OK) { + if (dj.fn[NSFLAG] & NS_NONAME) { /* Is it origin directory itself? */ + res = FR_INVALID_NAME; + } else { + if (dj.obj.attr & AM_DIR) { /* Is it a directory? */ + res = FR_NO_FILE; + } + } + } +#endif + + if (res == FR_OK) { +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + fp->obj.c_scl = dj.obj.sclust; /* Get containing directory info */ + fp->obj.c_size = ((DWORD)dj.obj.objsize & 0xFFFFFF00) | dj.obj.stat; + fp->obj.c_ofs = dj.blk_ofs; + init_alloc_info(fs, &fp->obj); + } else +#endif + { + fp->obj.sclust = ld_clust(fs, dj.dir); /* Get object allocation info */ + fp->obj.objsize = ld_dword(dj.dir + DIR_FileSize); + } +#if FF_USE_FASTSEEK + fp->cltbl = 0; /* Disable fast seek mode */ +#endif + fp->obj.fs = fs; /* Validate the file object */ + fp->obj.id = fs->id; + fp->flag = mode; /* Set file access mode */ + fp->err = 0; /* Clear error flag */ + fp->sect = 0; /* Invalidate current data sector */ + fp->fptr = 0; /* Set file pointer top of the file */ +#if !FF_FS_READONLY +#if !FF_FS_TINY + mem_set(fp->buf, 0, sizeof fp->buf); /* Clear sector buffer */ +#endif + if ((mode & FA_SEEKEND) && fp->obj.objsize > 0) { /* Seek to end of file if FA_OPEN_APPEND is specified */ + fp->fptr = fp->obj.objsize; /* Offset to seek */ + bcs = (DWORD)fs->csize * SS(fs); /* Cluster size in byte */ + clst = fp->obj.sclust; /* Follow the cluster chain */ + for (ofs = fp->obj.objsize; res == FR_OK && ofs > bcs; ofs -= bcs) { + clst = get_fat(&fp->obj, clst); + if (clst <= 1) res = FR_INT_ERR; + if (clst == 0xFFFFFFFF) res = FR_DISK_ERR; + } + fp->clust = clst; + if (res == FR_OK && ofs % SS(fs)) { /* Fill sector buffer if not on the sector boundary */ + if ((sc = clst2sect(fs, clst)) == 0) { + res = FR_INT_ERR; + } else { + fp->sect = sc + (DWORD)(ofs / SS(fs)); +#if !FF_FS_TINY + if (disk_read(fs->drv, fp->buf, fp->sect, 1) != RES_OK) res = FR_DISK_ERR; +#endif + } + } + } +#endif + } + + FREE_NAMBUF(); + } + + if (res != FR_OK) fp->obj.fs = 0; /* Invalidate file object on error */ + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Read File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_read ( + FIL* fp, /* Pointer to the file object */ + void* buff, /* Pointer to data buffer */ + UINT btr, /* Number of bytes to read */ + UINT* br /* Pointer to number of bytes read */ +) +{ + FRESULT res; + FATFS *fs; + DWORD clst, sect; + FSIZE_t remain; + UINT rcnt, cc, csect; + BYTE *rbuff = (BYTE*)buff; + + + *br = 0; /* Clear read byte counter */ + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); /* Check validity */ + if (!(fp->flag & FA_READ)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */ + remain = fp->obj.objsize - fp->fptr; + if (btr > remain) btr = (UINT)remain; /* Truncate btr by remaining bytes */ + + for ( ; btr; /* Repeat until btr bytes read */ + btr -= rcnt, *br += rcnt, rbuff += rcnt, fp->fptr += rcnt) { + if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */ + csect = (UINT)(fp->fptr / SS(fs) & (fs->csize - 1)); /* Sector offset in the cluster */ + if (csect == 0) { /* On the cluster boundary? */ + if (fp->fptr == 0) { /* On the top of the file? */ + clst = fp->obj.sclust; /* Follow cluster chain from the origin */ + } else { /* Middle or end of the file */ +#if FF_USE_FASTSEEK + if (fp->cltbl) { + clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */ + } else +#endif + { + clst = get_fat(&fp->obj, fp->clust); /* Follow cluster chain on the FAT */ + } + } + if (clst < 2) ABORT(fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); + fp->clust = clst; /* Update current cluster */ + } + sect = clst2sect(fs, fp->clust); /* Get current sector */ + if (sect == 0) ABORT(fs, FR_INT_ERR); + sect += csect; + cc = btr / SS(fs); /* When remaining bytes >= sector size, */ + if (cc > 0) { /* Read maximum contiguous sectors directly */ + if (csect + cc > fs->csize) { /* Clip at cluster boundary */ + cc = fs->csize - csect; + } + if (disk_read(fs->drv, rbuff, sect, cc) != RES_OK) ABORT(fs, FR_DISK_ERR); +#if !FF_FS_READONLY && FF_FS_MINIMIZE <= 2 /* Replace one of the read sectors with cached data if it contains a dirty sector */ +#if FF_FS_TINY + if (fs->wflag && fs->winsect - sect < cc) { + mem_cpy(rbuff + ((fs->winsect - sect) * SS(fs)), fs->win, SS(fs)); + } +#else + if ((fp->flag & FA_DIRTY) && fp->sect - sect < cc) { + mem_cpy(rbuff + ((fp->sect - sect) * SS(fs)), fp->buf, SS(fs)); + } +#endif +#endif + rcnt = SS(fs) * cc; /* Number of bytes transferred */ + continue; + } +#if !FF_FS_TINY + if (fp->sect != sect) { /* Load data sector if not in cache */ +#if !FF_FS_READONLY + if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */ + if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + if (disk_read(fs->drv, fp->buf, sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Fill sector cache */ + } +#endif + fp->sect = sect; + } + rcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes left in the sector */ + if (rcnt > btr) rcnt = btr; /* Clip it by btr if needed */ +#if FF_FS_TINY + if (move_window(fs, fp->sect) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window */ + mem_cpy(rbuff, fs->win + fp->fptr % SS(fs), rcnt); /* Extract partial sector */ +#else + mem_cpy(rbuff, fp->buf + fp->fptr % SS(fs), rcnt); /* Extract partial sector */ +#endif + } + + LEAVE_FF(fs, FR_OK); +} + + + + +#if !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Write File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_write ( + FIL* fp, /* Pointer to the file object */ + const void* buff, /* Pointer to the data to be written */ + UINT btw, /* Number of bytes to write */ + UINT* bw /* Pointer to number of bytes written */ +) +{ + FRESULT res; + FATFS *fs; + DWORD clst, sect; + UINT wcnt, cc, csect; + const BYTE *wbuff = (const BYTE*)buff; + + + *bw = 0; /* Clear write byte counter */ + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); /* Check validity */ + if (!(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */ + + /* Check fptr wrap-around (file size cannot reach 4 GiB at FAT volume) */ + if ((!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) && (DWORD)(fp->fptr + btw) < (DWORD)fp->fptr) { + btw = (UINT)(0xFFFFFFFF - (DWORD)fp->fptr); + } + + for ( ; btw; /* Repeat until all data written */ + btw -= wcnt, *bw += wcnt, wbuff += wcnt, fp->fptr += wcnt, fp->obj.objsize = (fp->fptr > fp->obj.objsize) ? fp->fptr : fp->obj.objsize) { + if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */ + csect = (UINT)(fp->fptr / SS(fs)) & (fs->csize - 1); /* Sector offset in the cluster */ + if (csect == 0) { /* On the cluster boundary? */ + if (fp->fptr == 0) { /* On the top of the file? */ + clst = fp->obj.sclust; /* Follow from the origin */ + if (clst == 0) { /* If no cluster is allocated, */ + clst = create_chain(&fp->obj, 0); /* create a new cluster chain */ + } + } else { /* On the middle or end of the file */ +#if FF_USE_FASTSEEK + if (fp->cltbl) { + clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */ + } else +#endif + { + clst = create_chain(&fp->obj, fp->clust); /* Follow or stretch cluster chain on the FAT */ + } + } + if (clst == 0) break; /* Could not allocate a new cluster (disk full) */ + if (clst == 1) ABORT(fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); + fp->clust = clst; /* Update current cluster */ + if (fp->obj.sclust == 0) fp->obj.sclust = clst; /* Set start cluster if the first write */ + } +#if FF_FS_TINY + if (fs->winsect == fp->sect && sync_window(fs) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Write-back sector cache */ +#else + if (fp->flag & FA_DIRTY) { /* Write-back sector cache */ + if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + sect = clst2sect(fs, fp->clust); /* Get current sector */ + if (sect == 0) ABORT(fs, FR_INT_ERR); + sect += csect; + cc = btw / SS(fs); /* When remaining bytes >= sector size, */ + if (cc > 0) { /* Write maximum contiguous sectors directly */ + if (csect + cc > fs->csize) { /* Clip at cluster boundary */ + cc = fs->csize - csect; + } + if (disk_write(fs->drv, wbuff, sect, cc) != RES_OK) ABORT(fs, FR_DISK_ERR); +#if FF_FS_MINIMIZE <= 2 +#if FF_FS_TINY + if (fs->winsect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write */ + mem_cpy(fs->win, wbuff + ((fs->winsect - sect) * SS(fs)), SS(fs)); + fs->wflag = 0; + } +#else + if (fp->sect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write */ + mem_cpy(fp->buf, wbuff + ((fp->sect - sect) * SS(fs)), SS(fs)); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif +#endif + wcnt = SS(fs) * cc; /* Number of bytes transferred */ + continue; + } +#if FF_FS_TINY + if (fp->fptr >= fp->obj.objsize) { /* Avoid silly cache filling on the growing edge */ + if (sync_window(fs) != FR_OK) ABORT(fs, FR_DISK_ERR); + fs->winsect = sect; + } +#else + if (fp->sect != sect && /* Fill sector cache with file data */ + fp->fptr < fp->obj.objsize && + disk_read(fs->drv, fp->buf, sect, 1) != RES_OK) { + ABORT(fs, FR_DISK_ERR); + } +#endif + fp->sect = sect; + } + wcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes left in the sector */ + if (wcnt > btw) wcnt = btw; /* Clip it by btw if needed */ +#if FF_FS_TINY + if (move_window(fs, fp->sect) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window */ + mem_cpy(fs->win + fp->fptr % SS(fs), wbuff, wcnt); /* Fit data to the sector */ + fs->wflag = 1; +#else + mem_cpy(fp->buf + fp->fptr % SS(fs), wbuff, wcnt); /* Fit data to the sector */ + fp->flag |= FA_DIRTY; +#endif + } + + fp->flag |= FA_MODIFIED; /* Set file change flag */ + + LEAVE_FF(fs, FR_OK); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Synchronize the File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_sync ( + FIL* fp /* Pointer to the file object */ +) +{ + FRESULT res; + FATFS *fs; + DWORD tm; + BYTE *dir; + + + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res == FR_OK) { + if (fp->flag & FA_MODIFIED) { /* Is there any change to the file? */ +#if !FF_FS_TINY + if (fp->flag & FA_DIRTY) { /* Write-back cached data if needed */ + if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) LEAVE_FF(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + /* Update the directory entry */ + tm = GET_FATTIME(); /* Modified time */ +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + res = fill_first_frag(&fp->obj); /* Fill first fragment on the FAT if needed */ + if (res == FR_OK) { + res = fill_last_frag(&fp->obj, fp->clust, 0xFFFFFFFF); /* Fill last fragment on the FAT if needed */ + } + if (res == FR_OK) { + DIR dj; + DEF_NAMBUF + + INIT_NAMBUF(fs); + res = load_obj_xdir(&dj, &fp->obj); /* Load directory entry block */ + if (res == FR_OK) { + fs->dirbuf[XDIR_Attr] |= AM_ARC; /* Set archive attribute to indicate that the file has been changed */ + fs->dirbuf[XDIR_GenFlags] = fp->obj.stat | 1; /* Update file allocation information */ + st_dword(fs->dirbuf + XDIR_FstClus, fp->obj.sclust); + st_qword(fs->dirbuf + XDIR_FileSize, fp->obj.objsize); + st_qword(fs->dirbuf + XDIR_ValidFileSize, fp->obj.objsize); + st_dword(fs->dirbuf + XDIR_ModTime, tm); /* Update modified time */ + fs->dirbuf[XDIR_ModTime10] = 0; + st_dword(fs->dirbuf + XDIR_AccTime, 0); + res = store_xdir(&dj); /* Restore it to the directory */ + if (res == FR_OK) { + res = sync_fs(fs); + fp->flag &= (BYTE)~FA_MODIFIED; + } + } + FREE_NAMBUF(); + } + } else +#endif + { + res = move_window(fs, fp->dir_sect); + if (res == FR_OK) { + dir = fp->dir_ptr; + dir[DIR_Attr] |= AM_ARC; /* Set archive attribute to indicate that the file has been changed */ + st_clust(fp->obj.fs, dir, fp->obj.sclust); /* Update file allocation information */ + st_dword(dir + DIR_FileSize, (DWORD)fp->obj.objsize); /* Update file size */ + st_dword(dir + DIR_ModTime, tm); /* Update modified time */ + st_word(dir + DIR_LstAccDate, 0); + fs->wflag = 1; + res = sync_fs(fs); /* Restore it to the directory */ + fp->flag &= (BYTE)~FA_MODIFIED; + } + } + } + } + + LEAVE_FF(fs, res); +} + +#endif /* !FF_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* Close File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_close ( + FIL* fp /* Pointer to the file object to be closed */ +) +{ + FRESULT res; + FATFS *fs; + +#if !FF_FS_READONLY + res = f_sync(fp); /* Flush cached data */ + if (res == FR_OK) +#endif + { + res = validate(&fp->obj, &fs); /* Lock volume */ + if (res == FR_OK) { +#if FF_FS_LOCK != 0 + res = dec_lock(fp->obj.lockid); /* Decrement file open counter */ + if (res == FR_OK) fp->obj.fs = 0; /* Invalidate file object */ +#else + fp->obj.fs = 0; /* Invalidate file object */ +#endif +#if FF_FS_REENTRANT + unlock_fs(fs, FR_OK); /* Unlock volume */ +#endif + } + } + return res; +} + + + + +#if FF_FS_RPATH >= 1 +/*-----------------------------------------------------------------------*/ +/* Change Current Directory or Current Drive, Get Current Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_chdir ( + FATFS *fs, + const TCHAR* path /* Pointer to the directory path */ +) +{ +#if FF_STR_VOLUME_ID == 2 + UINT i; +#endif + FRESULT res; + DIR dj; + DEF_NAMBUF + + + /* Get logical drive */ + res = find_volume(fs, 0); + if (res == FR_OK) { + dj.obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(&dj, path); /* Follow the path */ + if (res == FR_OK) { /* Follow completed */ + if (dj.fn[NSFLAG] & NS_NONAME) { /* Is it the start directory itself? */ + fs->cdir = dj.obj.sclust; +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + fs->cdc_scl = dj.obj.c_scl; + fs->cdc_size = dj.obj.c_size; + fs->cdc_ofs = dj.obj.c_ofs; + } +#endif + } else { + if (dj.obj.attr & AM_DIR) { /* It is a sub-directory */ +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + fs->cdir = ld_dword(fs->dirbuf + XDIR_FstClus); /* Sub-directory cluster */ + fs->cdc_scl = dj.obj.sclust; /* Save containing directory information */ + fs->cdc_size = ((DWORD)dj.obj.objsize & 0xFFFFFF00) | dj.obj.stat; + fs->cdc_ofs = dj.blk_ofs; + } else +#endif + { + fs->cdir = ld_clust(fs, dj.dir); /* Sub-directory cluster */ + } + } else { + res = FR_NO_PATH; /* Reached but a file */ + } + } + } + FREE_NAMBUF(); + if (res == FR_NO_FILE) res = FR_NO_PATH; +#if FF_STR_VOLUME_ID == 2 /* Also current drive is changed at Unix style volume ID */ + if (res == FR_OK) { + for (i = FF_VOLUMES - 1; i && fs != FatFs[i]; i--) ; /* Set current drive */ + CurrVol = (BYTE)i; + } +#endif + } + + LEAVE_FF(fs, res); +} + + +#if FF_FS_RPATH >= 2 +FRESULT f_getcwd ( + FATFS *fs, + TCHAR* buff, /* Pointer to the directory path */ + UINT len /* Size of buff in unit of TCHAR */ +) +{ + FRESULT res; + DIR dj; + UINT i, n; + DWORD ccl; + TCHAR *tp = buff; +#if FF_VOLUMES >= 2 + UINT vl; +#endif +#if FF_STR_VOLUME_ID + const char *vp; +#endif + FILINFO fno; + DEF_NAMBUF + + + /* Get logical drive */ + buff[0] = 0; /* Set null string to get current volume */ + res = find_volume(fs, 0); /* Get current volume */ + if (res == FR_OK) { + dj.obj.fs = fs; + INIT_NAMBUF(fs); + + /* Follow parent directories and create the path */ + i = len; /* Bottom of buffer (directory stack base) */ + if (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) { /* (Cannot do getcwd on exFAT and returns root path) */ + dj.obj.sclust = fs->cdir; /* Start to follow upper directory from current directory */ + while ((ccl = dj.obj.sclust) != 0) { /* Repeat while current directory is a sub-directory */ + res = dir_sdi(&dj, 1 * SZDIRE); /* Get parent directory */ + if (res != FR_OK) break; + res = move_window(fs, dj.sect); + if (res != FR_OK) break; + dj.obj.sclust = ld_clust(fs, dj.dir); /* Goto parent directory */ + res = dir_sdi(&dj, 0); + if (res != FR_OK) break; + do { /* Find the entry links to the child directory */ + res = DIR_READ_FILE(&dj); + if (res != FR_OK) break; + if (ccl == ld_clust(fs, dj.dir)) break; /* Found the entry */ + res = dir_next(&dj, 0); + } while (res == FR_OK); + if (res == FR_NO_FILE) res = FR_INT_ERR;/* It cannot be 'not found'. */ + if (res != FR_OK) break; + get_fileinfo(&dj, &fno); /* Get the directory name and push it to the buffer */ + for (n = 0; fno.fname[n]; n++) ; /* Name length */ + if (i < n + 1) { /* Insufficient space to store the path name? */ + res = FR_NOT_ENOUGH_CORE; break; + } + while (n) buff[--i] = fno.fname[--n]; /* Stack the name */ + buff[--i] = '/'; + } + } + if (res == FR_OK) { + if (i == len) buff[--i] = '/'; /* Is it the root-directory? */ +#if FF_VOLUMES >= 2 /* Put drive prefix */ + vl = 0; +#if FF_STR_VOLUME_ID >= 1 /* String volume ID */ + for (n = 0, vp = (const char*)VolumeStr[CurrVol]; vp[n]; n++) ; + if (i >= n + 2) { + if (FF_STR_VOLUME_ID == 2) *tp++ = (TCHAR)'/'; + for (vl = 0; vl < n; *tp++ = (TCHAR)vp[vl], vl++) ; + if (FF_STR_VOLUME_ID == 1) *tp++ = (TCHAR)':'; + vl++; + } +#else /* Numeric volume ID */ + if (i >= 3) { + *tp++ = (TCHAR)'0' + CurrVol; + *tp++ = (TCHAR)':'; + vl = 2; + } +#endif + if (vl == 0) res = FR_NOT_ENOUGH_CORE; +#endif + /* Add current directory path */ + if (res == FR_OK) { + do *tp++ = buff[i++]; while (i < len); /* Copy stacked path string */ + } + } + FREE_NAMBUF(); + } + + *tp = 0; + LEAVE_FF(fs, res); +} + +#endif /* FF_FS_RPATH >= 2 */ +#endif /* FF_FS_RPATH >= 1 */ + + + +#if FF_FS_MINIMIZE <= 2 +/*-----------------------------------------------------------------------*/ +/* Seek File Read/Write Pointer */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_lseek ( + FIL* fp, /* Pointer to the file object */ + FSIZE_t ofs /* File pointer from top of file */ +) +{ + FRESULT res; + FATFS *fs; + DWORD clst, bcs, nsect; + FSIZE_t ifptr; +#if FF_USE_FASTSEEK + DWORD cl, pcl, ncl, tcl, dsc, tlen, ulen, *tbl; +#endif + + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res == FR_OK) res = (FRESULT)fp->err; +#if FF_FS_EXFAT && !FF_FS_READONLY + if (res == FR_OK && fs->fs_type == FS_EXFAT) { + res = fill_last_frag(&fp->obj, fp->clust, 0xFFFFFFFF); /* Fill last fragment on the FAT if needed */ + } +#endif + if (res != FR_OK) LEAVE_FF(fs, res); + +#if FF_USE_FASTSEEK + if (fp->cltbl) { /* Fast seek */ + if (ofs == CREATE_LINKMAP) { /* Create CLMT */ + tbl = fp->cltbl; + tlen = *tbl++; ulen = 2; /* Given table size and required table size */ + cl = fp->obj.sclust; /* Origin of the chain */ + if (cl != 0) { + do { + /* Get a fragment */ + tcl = cl; ncl = 0; ulen += 2; /* Top, length and used items */ + do { + pcl = cl; ncl++; + cl = get_fat(&fp->obj, cl); + if (cl <= 1) ABORT(fs, FR_INT_ERR); + if (cl == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); + } while (cl == pcl + 1); + if (ulen <= tlen) { /* Store the length and top of the fragment */ + *tbl++ = ncl; *tbl++ = tcl; + } + } while (cl < fs->n_fatent); /* Repeat until end of chain */ + } + *fp->cltbl = ulen; /* Number of items used */ + if (ulen <= tlen) { + *tbl = 0; /* Terminate table */ + } else { + res = FR_NOT_ENOUGH_CORE; /* Given table size is smaller than required */ + } + } else { /* Fast seek */ + if (ofs > fp->obj.objsize) ofs = fp->obj.objsize; /* Clip offset at the file size */ + fp->fptr = ofs; /* Set file pointer */ + if (ofs > 0) { + fp->clust = clmt_clust(fp, ofs - 1); + dsc = clst2sect(fs, fp->clust); + if (dsc == 0) ABORT(fs, FR_INT_ERR); + dsc += (DWORD)((ofs - 1) / SS(fs)) & (fs->csize - 1); + if (fp->fptr % SS(fs) && dsc != fp->sect) { /* Refill sector cache if needed */ +#if !FF_FS_TINY +#if !FF_FS_READONLY + if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */ + if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + if (disk_read(fs->drv, fp->buf, dsc, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Load current sector */ +#endif + fp->sect = dsc; + } + } + } + } else +#endif + + /* Normal Seek */ + { +#if FF_FS_EXFAT + if (fs->fs_type != FS_EXFAT && ofs >= 0x100000000) ofs = 0xFFFFFFFF; /* Clip at 4 GiB - 1 if at FATxx */ +#endif + if (ofs > fp->obj.objsize && (FF_FS_READONLY || !(fp->flag & FA_WRITE))) { /* In read-only mode, clip offset with the file size */ + ofs = fp->obj.objsize; + } + ifptr = fp->fptr; + fp->fptr = nsect = 0; + if (ofs > 0) { + bcs = (DWORD)fs->csize * SS(fs); /* Cluster size (byte) */ + if (ifptr > 0 && + (ofs - 1) / bcs >= (ifptr - 1) / bcs) { /* When seek to same or following cluster, */ + fp->fptr = (ifptr - 1) & ~(FSIZE_t)(bcs - 1); /* start from the current cluster */ + ofs -= fp->fptr; + clst = fp->clust; + } else { /* When seek to back cluster, */ + clst = fp->obj.sclust; /* start from the first cluster */ +#if !FF_FS_READONLY + if (clst == 0) { /* If no cluster chain, create a new chain */ + clst = create_chain(&fp->obj, 0); + if (clst == 1) ABORT(fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); + fp->obj.sclust = clst; + } +#endif + fp->clust = clst; + } + if (clst != 0) { + while (ofs > bcs) { /* Cluster following loop */ + ofs -= bcs; fp->fptr += bcs; +#if !FF_FS_READONLY + if (fp->flag & FA_WRITE) { /* Check if in write mode or not */ + if (FF_FS_EXFAT && fp->fptr > fp->obj.objsize) { /* No FAT chain object needs correct objsize to generate FAT value */ + fp->obj.objsize = fp->fptr; + fp->flag |= FA_MODIFIED; + } + clst = create_chain(&fp->obj, clst); /* Follow chain with forceed stretch */ + if (clst == 0) { /* Clip file size in case of disk full */ + ofs = 0; break; + } + } else +#endif + { + clst = get_fat(&fp->obj, clst); /* Follow cluster chain if not in write mode */ + } + if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); + if (clst <= 1 || clst >= fs->n_fatent) ABORT(fs, FR_INT_ERR); + fp->clust = clst; + } + fp->fptr += ofs; + if (ofs % SS(fs)) { + nsect = clst2sect(fs, clst); /* Current sector */ + if (nsect == 0) ABORT(fs, FR_INT_ERR); + nsect += (DWORD)(ofs / SS(fs)); + } + } + } + if (!FF_FS_READONLY && fp->fptr > fp->obj.objsize) { /* Set file change flag if the file size is extended */ + fp->obj.objsize = fp->fptr; + fp->flag |= FA_MODIFIED; + } + if (fp->fptr % SS(fs) && nsect != fp->sect) { /* Fill sector cache if needed */ +#if !FF_FS_TINY +#if !FF_FS_READONLY + if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */ + if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + if (disk_read(fs->drv, fp->buf, nsect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Fill sector cache */ +#endif + fp->sect = nsect; + } + } + + LEAVE_FF(fs, res); +} + + + +#if FF_FS_MINIMIZE <= 1 +/*-----------------------------------------------------------------------*/ +/* Create a Directory Object */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_opendir ( + FATFS *fs, + DIR* dp, /* Pointer to directory object to create */ + const TCHAR* path /* Pointer to the directory path */ +) +{ + FRESULT res; + DEF_NAMBUF + + + if (!dp) return FR_INVALID_OBJECT; + + /* Get logical drive */ + res = find_volume(fs, 0); + if (res == FR_OK) { + dp->obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(dp, path); /* Follow the path to the directory */ + if (res == FR_OK) { /* Follow completed */ + if (!(dp->fn[NSFLAG] & NS_NONAME)) { /* It is not the origin directory itself */ + if (dp->obj.attr & AM_DIR) { /* This object is a sub-directory */ +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + dp->obj.c_scl = dp->obj.sclust; /* Get containing directory inforamation */ + dp->obj.c_size = ((DWORD)dp->obj.objsize & 0xFFFFFF00) | dp->obj.stat; + dp->obj.c_ofs = dp->blk_ofs; + init_alloc_info(fs, &dp->obj); /* Get object allocation info */ + } else +#endif + { + dp->obj.sclust = ld_clust(fs, dp->dir); /* Get object allocation info */ + } + } else { /* This object is a file */ + res = FR_NO_PATH; + } + } + if (res == FR_OK) { + dp->obj.id = fs->id; + res = dir_sdi(dp, 0); /* Rewind directory */ +#if FF_FS_LOCK != 0 + if (res == FR_OK) { + if (dp->obj.sclust != 0) { + dp->obj.lockid = inc_lock(dp, 0); /* Lock the sub directory */ + if (!dp->obj.lockid) res = FR_TOO_MANY_OPEN_FILES; + } else { + dp->obj.lockid = 0; /* Root directory need not to be locked */ + } + } +#endif + } + } + FREE_NAMBUF(); + if (res == FR_NO_FILE) res = FR_NO_PATH; + } + if (res != FR_OK) dp->obj.fs = 0; /* Invalidate the directory object if function faild */ + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Close Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_closedir ( + DIR *dp /* Pointer to the directory object to be closed */ +) +{ + FRESULT res; + FATFS *fs; + + + res = validate(&dp->obj, &fs); /* Check validity of the file object */ + if (res == FR_OK) { +#if FF_FS_LOCK != 0 + if (dp->obj.lockid) res = dec_lock(dp->obj.lockid); /* Decrement sub-directory open counter */ + if (res == FR_OK) dp->obj.fs = 0; /* Invalidate directory object */ +#else + dp->obj.fs = 0; /* Invalidate directory object */ +#endif +#if FF_FS_REENTRANT + unlock_fs(fs, FR_OK); /* Unlock volume */ +#endif + } + return res; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Read Directory Entries in Sequence */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_readdir ( + DIR* dp, /* Pointer to the open directory object */ + FILINFO* fno /* Pointer to file information to return */ +) +{ + FRESULT res; + FATFS *fs; + DEF_NAMBUF + + + res = validate(&dp->obj, &fs); /* Check validity of the directory object */ + if (res == FR_OK) { + if (!fno) { + res = dir_sdi(dp, 0); /* Rewind the directory object */ + } else { + INIT_NAMBUF(fs); + res = DIR_READ_FILE(dp); /* Read an item */ + if (res == FR_NO_FILE) res = FR_OK; /* Ignore end of directory */ + if (res == FR_OK) { /* A valid entry is found */ + get_fileinfo(dp, fno); /* Get the object information */ + res = dir_next(dp, 0); /* Increment index for next */ + if (res == FR_NO_FILE) res = FR_OK; /* Ignore end of directory now */ + } + FREE_NAMBUF(); + } + } + LEAVE_FF(fs, res); +} + + + +#if FF_USE_FIND +/*-----------------------------------------------------------------------*/ +/* Find Next File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_findnext ( + DIR* dp, /* Pointer to the open directory object */ + FILINFO* fno /* Pointer to the file information structure */ +) +{ + FRESULT res; + + + for (;;) { + res = f_readdir(dp, fno); /* Get a directory item */ + if (res != FR_OK || !fno || !fno->fname[0]) break; /* Terminate if any error or end of directory */ + if (pattern_matching(dp->pat, fno->fname, 0, 0)) break; /* Test for the file name */ +#if FF_USE_LFN && FF_USE_FIND == 2 + if (pattern_matching(dp->pat, fno->altname, 0, 0)) break; /* Test for alternative name if exist */ +#endif + } + return res; +} + + + +/*-----------------------------------------------------------------------*/ +/* Find First File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_findfirst ( + DIR* dp, /* Pointer to the blank directory object */ + FILINFO* fno, /* Pointer to the file information structure */ + const TCHAR* path, /* Pointer to the directory to open */ + const TCHAR* pattern /* Pointer to the matching pattern */ +) +{ + FRESULT res; + + + dp->pat = pattern; /* Save pointer to pattern string */ + res = f_opendir(dp, path); /* Open the target directory */ + if (res == FR_OK) { + res = f_findnext(dp, fno); /* Find the first item */ + } + return res; +} + +#endif /* FF_USE_FIND */ + + + +#if FF_FS_MINIMIZE == 0 +/*-----------------------------------------------------------------------*/ +/* Get File Status */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_stat ( + FATFS *fs, + const TCHAR* path, /* Pointer to the file path */ + FILINFO* fno /* Pointer to file information to return */ +) +{ + FRESULT res; + DIR dj; + DEF_NAMBUF + + + /* Get logical drive */ + res = find_volume(fs, 0); + dj.obj.fs = fs; + if (res == FR_OK) { + INIT_NAMBUF(dj.obj.fs); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK) { /* Follow completed */ + if (dj.fn[NSFLAG] & NS_NONAME) { /* It is origin directory */ + res = FR_INVALID_NAME; + } else { /* Found an object */ + if (fno) get_fileinfo(&dj, fno); + } + } + FREE_NAMBUF(); + } + + LEAVE_FF(dj.obj.fs, res); +} + + + +#if !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Get Number of Free Clusters */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_getfree ( + FATFS *fs, + DWORD* nclst /* Pointer to a variable to return number of free clusters */ +) +{ + FRESULT res; + DWORD nfree, clst, sect, stat; + UINT i; + FFOBJID obj; + + + /* Get logical drive */ + res = find_volume(fs, 0); + if (res == FR_OK) { + /* If free_clst is valid, return it without full FAT scan */ + if (fs->free_clst <= fs->n_fatent - 2) { + *nclst = fs->free_clst; + } else { + /* Scan FAT to obtain number of free clusters */ + nfree = 0; + if (fs->fs_type == FS_FAT12) { /* FAT12: Scan bit field FAT entries */ + clst = 2; obj.fs = fs; + do { + stat = get_fat(&obj, clst); + if (stat == 0xFFFFFFFF) { res = FR_DISK_ERR; break; } + if (stat == 1) { res = FR_INT_ERR; break; } + if (stat == 0) nfree++; + } while (++clst < fs->n_fatent); + } else { +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* exFAT: Scan allocation bitmap */ + BYTE bm; + UINT b; + + clst = fs->n_fatent - 2; /* Number of clusters */ + sect = fs->bitbase; /* Bitmap sector */ + i = 0; /* Offset in the sector */ + do { /* Counts numbuer of bits with zero in the bitmap */ + if (i == 0) { + res = move_window(fs, sect++); + if (res != FR_OK) break; + } + for (b = 8, bm = fs->win[i]; b && clst; b--, clst--) { + if (!(bm & 1)) nfree++; + bm >>= 1; + } + i = (i + 1) % SS(fs); + } while (clst); + } else +#endif + { /* FAT16/32: Scan WORD/DWORD FAT entries */ + clst = fs->n_fatent; /* Number of entries */ + sect = fs->fatbase; /* Top of the FAT */ + i = 0; /* Offset in the sector */ + do { /* Counts numbuer of entries with zero in the FAT */ + if (i == 0) { + res = move_window(fs, sect++); + if (res != FR_OK) break; + } + if (fs->fs_type == FS_FAT16) { + if (ld_word(fs->win + i) == 0) nfree++; + i += 2; + } else { + if ((ld_dword(fs->win + i) & 0x0FFFFFFF) == 0) nfree++; + i += 4; + } + i %= SS(fs); + } while (--clst); + } + } + *nclst = nfree; /* Return the free clusters */ + fs->free_clst = nfree; /* Now free_clst is valid */ + fs->fsi_flag |= 1; /* FAT32: FSInfo is to be updated */ + } + } + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Truncate File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_truncate ( + FIL* fp /* Pointer to the file object */ +) +{ + FRESULT res; + FATFS *fs; + DWORD ncl; + + + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); + if (!(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */ + + if (fp->fptr < fp->obj.objsize) { /* Process when fptr is not on the eof */ + if (fp->fptr == 0) { /* When set file size to zero, remove entire cluster chain */ + res = remove_chain(&fp->obj, fp->obj.sclust, 0); + fp->obj.sclust = 0; + } else { /* When truncate a part of the file, remove remaining clusters */ + ncl = get_fat(&fp->obj, fp->clust); + res = FR_OK; + if (ncl == 0xFFFFFFFF) res = FR_DISK_ERR; + if (ncl == 1) res = FR_INT_ERR; + if (res == FR_OK && ncl < fs->n_fatent) { + res = remove_chain(&fp->obj, ncl, fp->clust); + } + } + fp->obj.objsize = fp->fptr; /* Set file size to current read/write point */ + fp->flag |= FA_MODIFIED; +#if !FF_FS_TINY + if (res == FR_OK && (fp->flag & FA_DIRTY)) { + if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) { + res = FR_DISK_ERR; + } else { + fp->flag &= (BYTE)~FA_DIRTY; + } + } +#endif + if (res != FR_OK) ABORT(fs, res); + } + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Delete a File/Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_unlink ( + FATFS *fs, + const TCHAR* path /* Pointer to the file or directory path */ +) +{ + FRESULT res; + DIR dj, sdj; + DWORD dclst = 0; +#if FF_FS_EXFAT + FFOBJID obj; +#endif + DEF_NAMBUF + + + /* Get logical drive */ + res = find_volume(fs, FA_WRITE); + if (res == FR_OK) { + dj.obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(&dj, path); /* Follow the file path */ + if (FF_FS_RPATH && res == FR_OK && (dj.fn[NSFLAG] & NS_DOT)) { + res = FR_INVALID_NAME; /* Cannot remove dot entry */ + } +#if FF_FS_LOCK != 0 + if (res == FR_OK) res = chk_lock(&dj, 2); /* Check if it is an open object */ +#endif + if (res == FR_OK) { /* The object is accessible */ + if (dj.fn[NSFLAG] & NS_NONAME) { + res = FR_INVALID_NAME; /* Cannot remove the origin directory */ + } else { + if (dj.obj.attr & AM_RDO) { + res = FR_DENIED; /* Cannot remove R/O object */ + } + } + if (res == FR_OK) { +#if FF_FS_EXFAT + obj.fs = fs; + if (fs->fs_type == FS_EXFAT) { + init_alloc_info(fs, &obj); + dclst = obj.sclust; + } else +#endif + { + dclst = ld_clust(fs, dj.dir); + } + if (dj.obj.attr & AM_DIR) { /* Is it a sub-directory? */ +#if FF_FS_RPATH != 0 + if (dclst == fs->cdir) { /* Is it the current directory? */ + res = FR_DENIED; + } else +#endif + { + sdj.obj.fs = fs; /* Open the sub-directory */ + sdj.obj.sclust = dclst; +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + sdj.obj.objsize = obj.objsize; + sdj.obj.stat = obj.stat; + } +#endif + res = dir_sdi(&sdj, 0); + if (res == FR_OK) { + res = DIR_READ_FILE(&sdj); /* Test if the directory is empty */ + if (res == FR_OK) res = FR_DENIED; /* Not empty? */ + if (res == FR_NO_FILE) res = FR_OK; /* Empty? */ + } + } + } + } + if (res == FR_OK) { + res = dir_remove(&dj); /* Remove the directory entry */ + if (res == FR_OK && dclst != 0) { /* Remove the cluster chain if exist */ +#if FF_FS_EXFAT + res = remove_chain(&obj, dclst, 0); +#else + res = remove_chain(&dj.obj, dclst, 0); +#endif + } + if (res == FR_OK) res = sync_fs(fs); + } + } + FREE_NAMBUF(); + } + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Create a Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_mkdir ( + FATFS *fs, + const TCHAR* path /* Pointer to the directory path */ +) +{ + FRESULT res; + DIR dj; + FFOBJID sobj; + DWORD dcl, pcl, tm; + DEF_NAMBUF + + + res = find_volume(fs, FA_WRITE); /* Get logical drive */ + if (res == FR_OK) { + dj.obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK) res = FR_EXIST; /* Name collision? */ + if (FF_FS_RPATH && res == FR_NO_FILE && (dj.fn[NSFLAG] & NS_DOT)) { /* Invalid name? */ + res = FR_INVALID_NAME; + } + if (res == FR_NO_FILE) { /* It is clear to create a new directory */ + sobj.fs = fs; /* New object id to create a new chain */ + dcl = create_chain(&sobj, 0); /* Allocate a cluster for the new directory */ + res = FR_OK; + if (dcl == 0) res = FR_DENIED; /* No space to allocate a new cluster? */ + if (dcl == 1) res = FR_INT_ERR; /* Any insanity? */ + if (dcl == 0xFFFFFFFF) res = FR_DISK_ERR; /* Disk error? */ + tm = GET_FATTIME(); + if (res == FR_OK) { + res = dir_clear(fs, dcl); /* Clean up the new table */ + if (res == FR_OK) { + if (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) { /* Create dot entries (FAT only) */ + mem_set(fs->win + DIR_Name, ' ', 11); /* Create "." entry */ + fs->win[DIR_Name] = '.'; + fs->win[DIR_Attr] = AM_DIR; + st_dword(fs->win + DIR_ModTime, tm); + st_clust(fs, fs->win, dcl); + mem_cpy(fs->win + SZDIRE, fs->win, SZDIRE); /* Create ".." entry */ + fs->win[SZDIRE + 1] = '.'; pcl = dj.obj.sclust; + st_clust(fs, fs->win + SZDIRE, pcl); + fs->wflag = 1; + } + res = dir_register(&dj); /* Register the object to the parent directoy */ + } + } + if (res == FR_OK) { +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* Initialize directory entry block */ + st_dword(fs->dirbuf + XDIR_ModTime, tm); /* Created time */ + st_dword(fs->dirbuf + XDIR_FstClus, dcl); /* Table start cluster */ + st_dword(fs->dirbuf + XDIR_FileSize, (DWORD)fs->csize * SS(fs)); /* File size needs to be valid */ + st_dword(fs->dirbuf + XDIR_ValidFileSize, (DWORD)fs->csize * SS(fs)); + fs->dirbuf[XDIR_GenFlags] = 3; /* Initialize the object flag */ + fs->dirbuf[XDIR_Attr] = AM_DIR; /* Attribute */ + res = store_xdir(&dj); + } else +#endif + { + st_dword(dj.dir + DIR_ModTime, tm); /* Created time */ + st_clust(fs, dj.dir, dcl); /* Table start cluster */ + dj.dir[DIR_Attr] = AM_DIR; /* Attribute */ + fs->wflag = 1; + } + if (res == FR_OK) { + res = sync_fs(fs); + } + } else { + remove_chain(&sobj, dcl, 0); /* Could not register, remove the allocated cluster */ + } + } + FREE_NAMBUF(); + } + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Rename a File/Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_rename ( + FATFS *fs, + const TCHAR* path_old, /* Pointer to the object name to be renamed */ + const TCHAR* path_new /* Pointer to the new name */ +) +{ + FRESULT res; + DIR djo, djn; + BYTE buf[FF_FS_EXFAT ? SZDIRE * 2 : SZDIRE], *dir; + DWORD dw; + DEF_NAMBUF + + + res = find_volume(fs, FA_WRITE); /* Get logical drive of the old object */ + if (res == FR_OK) { + djo.obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(&djo, path_old); /* Check old object */ + if (res == FR_OK && (djo.fn[NSFLAG] & (NS_DOT | NS_NONAME))) res = FR_INVALID_NAME; /* Check validity of name */ +#if FF_FS_LOCK != 0 + if (res == FR_OK) { + res = chk_lock(&djo, 2); + } +#endif + if (res == FR_OK) { /* Object to be renamed is found */ +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* At exFAT volume */ + BYTE nf, nn; + WORD nh; + + mem_cpy(buf, fs->dirbuf, SZDIRE * 2); /* Save 85+C0 entry of old object */ + mem_cpy(&djn, &djo, sizeof djo); + res = follow_path(&djn, path_new); /* Make sure if new object name is not in use */ + if (res == FR_OK) { /* Is new name already in use by any other object? */ + res = (djn.obj.sclust == djo.obj.sclust && djn.dptr == djo.dptr) ? FR_NO_FILE : FR_EXIST; + } + if (res == FR_NO_FILE) { /* It is a valid path and no name collision */ + res = dir_register(&djn); /* Register the new entry */ + if (res == FR_OK) { + nf = fs->dirbuf[XDIR_NumSec]; nn = fs->dirbuf[XDIR_NumName]; + nh = ld_word(fs->dirbuf + XDIR_NameHash); + mem_cpy(fs->dirbuf, buf, SZDIRE * 2); /* Restore 85+C0 entry */ + fs->dirbuf[XDIR_NumSec] = nf; fs->dirbuf[XDIR_NumName] = nn; + st_word(fs->dirbuf + XDIR_NameHash, nh); + if (!(fs->dirbuf[XDIR_Attr] & AM_DIR)) fs->dirbuf[XDIR_Attr] |= AM_ARC; /* Set archive attribute if it is a file */ +/* Start of critical section where an interruption can cause a cross-link */ + res = store_xdir(&djn); + } + } + } else +#endif + { /* At FAT/FAT32 volume */ + mem_cpy(buf, djo.dir, SZDIRE); /* Save directory entry of the object */ + mem_cpy(&djn, &djo, sizeof (DIR)); /* Duplicate the directory object */ + res = follow_path(&djn, path_new); /* Make sure if new object name is not in use */ + if (res == FR_OK) { /* Is new name already in use by any other object? */ + res = (djn.obj.sclust == djo.obj.sclust && djn.dptr == djo.dptr) ? FR_NO_FILE : FR_EXIST; + } + if (res == FR_NO_FILE) { /* It is a valid path and no name collision */ + res = dir_register(&djn); /* Register the new entry */ + if (res == FR_OK) { + dir = djn.dir; /* Copy directory entry of the object except name */ + mem_cpy(dir + 13, buf + 13, SZDIRE - 13); + dir[DIR_Attr] = buf[DIR_Attr]; + if (!(dir[DIR_Attr] & AM_DIR)) dir[DIR_Attr] |= AM_ARC; /* Set archive attribute if it is a file */ + fs->wflag = 1; + if ((dir[DIR_Attr] & AM_DIR) && djo.obj.sclust != djn.obj.sclust) { /* Update .. entry in the sub-directory if needed */ + dw = clst2sect(fs, ld_clust(fs, dir)); + if (dw == 0) { + res = FR_INT_ERR; + } else { +/* Start of critical section where an interruption can cause a cross-link */ + res = move_window(fs, dw); + dir = fs->win + SZDIRE * 1; /* Ptr to .. entry */ + if (res == FR_OK && dir[1] == '.') { + st_clust(fs, dir, djn.obj.sclust); + fs->wflag = 1; + } + } + } + } + } + } + if (res == FR_OK) { + res = dir_remove(&djo); /* Remove old entry */ + if (res == FR_OK) { + res = sync_fs(fs); + } + } +/* End of the critical section */ + } + FREE_NAMBUF(); + } + + LEAVE_FF(fs, res); +} + +#endif /* !FF_FS_READONLY */ +#endif /* FF_FS_MINIMIZE == 0 */ +#endif /* FF_FS_MINIMIZE <= 1 */ +#endif /* FF_FS_MINIMIZE <= 2 */ + + + +#if FF_USE_CHMOD && !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Change Attribute */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_chmod ( + FATFS *fs, + const TCHAR* path, /* Pointer to the file path */ + BYTE attr, /* Attribute bits */ + BYTE mask /* Attribute mask to change */ +) +{ + FRESULT res; + DIR dj; + DEF_NAMBUF + + + res = find_volume(fs, FA_WRITE); /* Get logical drive */ + if (res == FR_OK) { + dj.obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK && (dj.fn[NSFLAG] & (NS_DOT | NS_NONAME))) res = FR_INVALID_NAME; /* Check object validity */ + if (res == FR_OK) { + mask &= AM_RDO|AM_HID|AM_SYS|AM_ARC; /* Valid attribute mask */ +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + fs->dirbuf[XDIR_Attr] = (attr & mask) | (fs->dirbuf[XDIR_Attr] & (BYTE)~mask); /* Apply attribute change */ + res = store_xdir(&dj); + } else +#endif + { + dj.dir[DIR_Attr] = (attr & mask) | (dj.dir[DIR_Attr] & (BYTE)~mask); /* Apply attribute change */ + fs->wflag = 1; + } + if (res == FR_OK) { + res = sync_fs(fs); + } + } + FREE_NAMBUF(); + } + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Change Timestamp */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_utime ( + FATFS *fs, + const TCHAR* path, /* Pointer to the file/directory name */ + const FILINFO* fno /* Pointer to the timestamp to be set */ +) +{ + FRESULT res; + DIR dj; + DEF_NAMBUF + + + res = find_volume(fs, FA_WRITE); /* Get logical drive */ + if (res == FR_OK) { + dj.obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK && (dj.fn[NSFLAG] & (NS_DOT | NS_NONAME))) res = FR_INVALID_NAME; /* Check object validity */ + if (res == FR_OK) { +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + st_dword(fs->dirbuf + XDIR_ModTime, (DWORD)fno->fdate << 16 | fno->ftime); + res = store_xdir(&dj); + } else +#endif + { + st_dword(dj.dir + DIR_ModTime, (DWORD)fno->fdate << 16 | fno->ftime); + fs->wflag = 1; + } + if (res == FR_OK) { + res = sync_fs(fs); + } + } + FREE_NAMBUF(); + } + + LEAVE_FF(fs, res); +} + +#endif /* FF_USE_CHMOD && !FF_FS_READONLY */ + + + +#if FF_USE_LABEL +/*-----------------------------------------------------------------------*/ +/* Get Volume Label */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_getlabel ( + FATFS *fs, + TCHAR* label, /* Buffer to store the volume label */ + DWORD* vsn /* Variable to store the volume serial number */ +) +{ + FRESULT res; + DIR dj; + UINT si, di; + WCHAR wc; + + /* Get logical drive */ + res = find_volume(fs, 0); + + /* Get volume label */ + if (res == FR_OK && label) { + dj.obj.fs = fs; dj.obj.sclust = 0; /* Open root directory */ + res = dir_sdi(&dj, 0); + if (res == FR_OK) { + res = DIR_READ_LABEL(&dj); /* Find a volume label entry */ + if (res == FR_OK) { +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + WCHAR hs; + + for (si = di = hs = 0; si < dj.dir[XDIR_NumLabel]; si++) { /* Extract volume label from 83 entry */ + wc = ld_word(dj.dir + XDIR_Label + si * 2); + if (hs == 0 && IsSurrogate(wc)) { /* Is the code a surrogate? */ + hs = wc; continue; + } + wc = put_utf((DWORD)hs << 16 | wc, &label[di], 4); + if (wc == 0) { di = 0; break; } + di += wc; + hs = 0; + } + if (hs != 0) di = 0; /* Broken surrogate pair? */ + label[di] = 0; + } else +#endif + { + si = di = 0; /* Extract volume label from AM_VOL entry */ + while (si < 11) { + wc = dj.dir[si++]; +#if FF_USE_LFN && FF_LFN_UNICODE >= 1 /* Unicode output */ + if (dbc_1st((BYTE)wc) && si < 11) wc = wc << 8 | dj.dir[si++]; /* Is it a DBC? */ + wc = ff_oem2uni(wc, CODEPAGE); /* Convert it into Unicode */ + if (wc != 0) wc = put_utf(wc, &label[di], 4); /* Put it in Unicode */ + if (wc == 0) { di = 0; break; } + di += wc; +#else /* ANSI/OEM output */ + label[di++] = (TCHAR)wc; +#endif + } + do { /* Truncate trailing spaces */ + label[di] = 0; + if (di == 0) break; + } while (label[--di] == ' '); + } + } + } + if (res == FR_NO_FILE) { /* No label entry and return nul string */ + label[0] = 0; + res = FR_OK; + } + } + + /* Get volume serial number */ + if (res == FR_OK && vsn) { + res = move_window(fs, fs->volbase); + if (res == FR_OK) { + switch (fs->fs_type) { + case FS_EXFAT: + di = BPB_VolIDEx; break; + + case FS_FAT32: + di = BS_VolID32; break; + + default: + di = BS_VolID; + } + *vsn = ld_dword(fs->win + di); + } + } + + LEAVE_FF(fs, res); +} + + + +#if !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Set Volume Label */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_setlabel ( + FATFS *fs, + const TCHAR* label /* Volume label to set with heading logical drive number */ +) +{ + FRESULT res; + DIR dj; + BYTE dirvn[22]; + UINT di; + WCHAR wc; + static const char badchr[] = "+.,;=[]/\\\"*:<>\?|\x7F"; /* [0..] for FAT, [7..] for exFAT */ +#if FF_USE_LFN + DWORD dc; +#endif + + /* Get logical drive */ + res = find_volume(fs, FA_WRITE); + if (res != FR_OK) LEAVE_FF(fs, res); + +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + mem_set(dirvn, 0, 22); + di = 0; + while ((UINT)*label >= ' ') { /* Create volume label */ + dc = tchar2uni(&label); /* Get a Unicode character */ + if (dc >= 0x10000) { + if (dc == 0xFFFFFFFF || di >= 10) { /* Wrong surrogate or buffer overflow */ + dc = 0; + } else { + st_word(dirvn + di * 2, (WCHAR)(dc >> 16)); di++; + } + } + if (dc == 0 || chk_chr(badchr + 7, (int)dc) || di >= 11) { /* Check validity of the volume label */ + LEAVE_FF(fs, FR_INVALID_NAME); + } + st_word(dirvn + di * 2, (WCHAR)dc); di++; + } + } else +#endif + { /* On the FAT/FAT32 volume */ + mem_set(dirvn, ' ', 11); + di = 0; + while ((UINT)*label >= ' ') { /* Create volume label */ +#if FF_USE_LFN + dc = tchar2uni(&label); + wc = (dc < 0x10000) ? ff_uni2oem(ff_wtoupper(dc), CODEPAGE) : 0; +#else /* ANSI/OEM input */ + wc = (BYTE)*label++; + if (dbc_1st((BYTE)wc)) wc = dbc_2nd((BYTE)*label) ? wc << 8 | (BYTE)*label++ : 0; + if (IsLower(wc)) wc -= 0x20; /* To upper ASCII characters */ +#if FF_CODE_PAGE == 0 + if (ExCvt && wc >= 0x80) wc = ExCvt[wc - 0x80]; /* To upper extended characters (SBCS cfg) */ +#elif FF_CODE_PAGE < 900 + if (wc >= 0x80) wc = ExCvt[wc - 0x80]; /* To upper extended characters (SBCS cfg) */ +#endif +#endif + if (wc == 0 || chk_chr(badchr + 0, (int)wc) || di >= (UINT)((wc >= 0x100) ? 10 : 11)) { /* Reject invalid characters for volume label */ + LEAVE_FF(fs, FR_INVALID_NAME); + } + if (wc >= 0x100) dirvn[di++] = (BYTE)(wc >> 8); + dirvn[di++] = (BYTE)wc; + } + if (dirvn[0] == DDEM) LEAVE_FF(fs, FR_INVALID_NAME); /* Reject illegal name (heading DDEM) */ + while (di && dirvn[di - 1] == ' ') di--; /* Snip trailing spaces */ + } + + /* Set volume label */ + dj.obj.fs = fs; dj.obj.sclust = 0; /* Open root directory */ + res = dir_sdi(&dj, 0); + if (res == FR_OK) { + res = DIR_READ_LABEL(&dj); /* Get volume label entry */ + if (res == FR_OK) { + if (FF_FS_EXFAT && fs->fs_type == FS_EXFAT) { + dj.dir[XDIR_NumLabel] = (BYTE)di; /* Change the volume label */ + mem_cpy(dj.dir + XDIR_Label, dirvn, 22); + } else { + if (di != 0) { + mem_cpy(dj.dir, dirvn, 11); /* Change the volume label */ + } else { + dj.dir[DIR_Name] = DDEM; /* Remove the volume label */ + } + } + fs->wflag = 1; + res = sync_fs(fs); + } else { /* No volume label entry or an error */ + if (res == FR_NO_FILE) { + res = FR_OK; + if (di != 0) { /* Create a volume label entry */ + res = dir_alloc(&dj, 1); /* Allocate an entry */ + if (res == FR_OK) { + mem_set(dj.dir, 0, SZDIRE); /* Clean the entry */ + if (FF_FS_EXFAT && fs->fs_type == FS_EXFAT) { + dj.dir[XDIR_Type] = ET_VLABEL; /* Create volume label entry */ + dj.dir[XDIR_NumLabel] = (BYTE)di; + mem_cpy(dj.dir + XDIR_Label, dirvn, 22); + } else { + dj.dir[DIR_Attr] = AM_VOL; /* Create volume label entry */ + mem_cpy(dj.dir, dirvn, 11); + } + fs->wflag = 1; + res = sync_fs(fs); + } + } + } + } + } + + LEAVE_FF(fs, res); +} + +#endif /* !FF_FS_READONLY */ +#endif /* FF_USE_LABEL */ + + + +#if FF_USE_EXPAND && !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Allocate a Contiguous Blocks to the File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_expand ( + FIL* fp, /* Pointer to the file object */ + FSIZE_t fsz, /* File size to be expanded to */ + BYTE opt /* Operation mode 0:Find and prepare or 1:Find and allocate */ +) +{ + FRESULT res; + FATFS *fs; + DWORD n, clst, stcl, scl, ncl, tcl, lclst; + + + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); + if (fsz == 0 || fp->obj.objsize != 0 || !(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED); +#if FF_FS_EXFAT + if (fs->fs_type != FS_EXFAT && fsz >= 0x100000000) LEAVE_FF(fs, FR_DENIED); /* Check if in size limit */ +#endif + n = (DWORD)fs->csize * SS(fs); /* Cluster size */ + tcl = (DWORD)(fsz / n) + ((fsz & (n - 1)) ? 1 : 0); /* Number of clusters required */ + stcl = fs->last_clst; lclst = 0; + if (stcl < 2 || stcl >= fs->n_fatent) stcl = 2; + +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + scl = find_bitmap(fs, stcl, tcl); /* Find a contiguous cluster block */ + if (scl == 0) res = FR_DENIED; /* No contiguous cluster block was found */ + if (scl == 0xFFFFFFFF) res = FR_DISK_ERR; + if (res == FR_OK) { /* A contiguous free area is found */ + if (opt) { /* Allocate it now */ + res = change_bitmap(fs, scl, tcl, 1); /* Mark the cluster block 'in use' */ + lclst = scl + tcl - 1; + } else { /* Set it as suggested point for next allocation */ + lclst = scl - 1; + } + } + } else +#endif + { + scl = clst = stcl; ncl = 0; + for (;;) { /* Find a contiguous cluster block */ + n = get_fat(&fp->obj, clst); + if (++clst >= fs->n_fatent) clst = 2; + if (n == 1) { res = FR_INT_ERR; break; } + if (n == 0xFFFFFFFF) { res = FR_DISK_ERR; break; } + if (n == 0) { /* Is it a free cluster? */ + if (++ncl == tcl) break; /* Break if a contiguous cluster block is found */ + } else { + scl = clst; ncl = 0; /* Not a free cluster */ + } + if (clst == stcl) { res = FR_DENIED; break; } /* No contiguous cluster? */ + } + if (res == FR_OK) { /* A contiguous free area is found */ + if (opt) { /* Allocate it now */ + for (clst = scl, n = tcl; n; clst++, n--) { /* Create a cluster chain on the FAT */ + res = put_fat(fs, clst, (n == 1) ? 0xFFFFFFFF : clst + 1); + if (res != FR_OK) break; + lclst = clst; + } + } else { /* Set it as suggested point for next allocation */ + lclst = scl - 1; + } + } + } + + if (res == FR_OK) { + fs->last_clst = lclst; /* Set suggested start cluster to start next */ + if (opt) { /* Is it allocated now? */ + fp->obj.sclust = scl; /* Update object allocation information */ + fp->obj.objsize = fsz; + if (FF_FS_EXFAT) fp->obj.stat = 2; /* Set status 'contiguous chain' */ + fp->flag |= FA_MODIFIED; + if (fs->free_clst <= fs->n_fatent - 2) { /* Update FSINFO */ + fs->free_clst -= tcl; + fs->fsi_flag |= 1; + } + } + } + + LEAVE_FF(fs, res); +} + +#endif /* FF_USE_EXPAND && !FF_FS_READONLY */ + + + +#if FF_USE_FORWARD +/*-----------------------------------------------------------------------*/ +/* Forward Data to the Stream Directly */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_forward ( + FIL* fp, /* Pointer to the file object */ + UINT (*func)(const BYTE*,UINT), /* Pointer to the streaming function */ + UINT btf, /* Number of bytes to forward */ + UINT* bf /* Pointer to number of bytes forwarded */ +) +{ + FRESULT res; + FATFS *fs; + DWORD clst, sect; + FSIZE_t remain; + UINT rcnt, csect; + BYTE *dbuf; + + + *bf = 0; /* Clear transfer byte counter */ + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); + if (!(fp->flag & FA_READ)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */ + + remain = fp->obj.objsize - fp->fptr; + if (btf > remain) btf = (UINT)remain; /* Truncate btf by remaining bytes */ + + for ( ; btf && (*func)(0, 0); /* Repeat until all data transferred or stream goes busy */ + fp->fptr += rcnt, *bf += rcnt, btf -= rcnt) { + csect = (UINT)(fp->fptr / SS(fs) & (fs->csize - 1)); /* Sector offset in the cluster */ + if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */ + if (csect == 0) { /* On the cluster boundary? */ + clst = (fp->fptr == 0) ? /* On the top of the file? */ + fp->obj.sclust : get_fat(&fp->obj, fp->clust); + if (clst <= 1) ABORT(fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); + fp->clust = clst; /* Update current cluster */ + } + } + sect = clst2sect(fs, fp->clust); /* Get current data sector */ + if (sect == 0) ABORT(fs, FR_INT_ERR); + sect += csect; +#if FF_FS_TINY + if (move_window(fs, sect) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window to the file data */ + dbuf = fs->win; +#else + if (fp->sect != sect) { /* Fill sector cache with file data */ +#if !FF_FS_READONLY + if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */ + if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + if (disk_read(fs->drv, fp->buf, sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + } + dbuf = fp->buf; +#endif + fp->sect = sect; + rcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes left in the sector */ + if (rcnt > btf) rcnt = btf; /* Clip it by btr if needed */ + rcnt = (*func)(dbuf + ((UINT)fp->fptr % SS(fs)), rcnt); /* Forward the file data */ + if (rcnt == 0) ABORT(fs, FR_INT_ERR); + } + + LEAVE_FF(fs, FR_OK); +} +#endif /* FF_USE_FORWARD */ + + + +#if FF_USE_MKFS && !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Create an FAT/exFAT volume */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_mkfs ( + FATFS *fs, + BYTE opt, /* Format option */ + DWORD au, /* Size of allocation unit (cluster) [byte] */ + void* work, /* Pointer to working buffer (null: use heap memory) */ + UINT len /* Size of working buffer [byte] */ +) +{ + const UINT n_fats = 1; /* Number of FATs for FAT/FAT32 volume (1 or 2) */ + const UINT n_rootdir = 512; /* Number of root directory entries for FAT volume */ + static const WORD cst[] = {1, 4, 16, 64, 256, 512, 0}; /* Cluster size boundary for FAT volume (4Ks unit) */ + static const WORD cst32[] = {1, 2, 4, 8, 16, 32, 0}; /* Cluster size boundary for FAT32 volume (128Ks unit) */ + BYTE fmt, sys, *buf, *pte, part; void *pdrv; + WORD ss; /* Sector size */ + DWORD szb_buf, sz_buf, sz_blk, n_clst, pau, sect, nsect, n; + DWORD b_vol, b_fat, b_data; /* Base LBA for volume, fat, data */ + DWORD sz_vol, sz_rsv, sz_fat, sz_dir; /* Size for volume, fat, dir, data */ + UINT i; + DSTATUS stat; +#if FF_USE_TRIM || FF_FS_EXFAT + DWORD tbl[3]; +#endif + + + /* Check mounted drive and clear work area */ + fs->fs_type = 0; /* Clear mounted volume */ + pdrv = fs->drv; /* Physical drive */ + part = LD2PT(fs); /* Partition (0:create as new, 1-4:get from partition table) */ + + /* Check physical drive status */ + disk_ioctl(pdrv, IOCTL_INIT, &stat); + if (stat & STA_NOINIT) return FR_NOT_READY; + if (stat & STA_PROTECT) return FR_WRITE_PROTECTED; + if (disk_ioctl(pdrv, GET_BLOCK_SIZE, &sz_blk) != RES_OK || !sz_blk || sz_blk > 32768 || (sz_blk & (sz_blk - 1))) sz_blk = 1; /* Erase block to align data area */ +#if FF_MAX_SS != FF_MIN_SS /* Get sector size of the medium if variable sector size cfg. */ + if (disk_ioctl(pdrv, GET_SECTOR_SIZE, &ss) != RES_OK) return FR_DISK_ERR; + if (ss > FF_MAX_SS || ss < FF_MIN_SS || (ss & (ss - 1))) return FR_DISK_ERR; +#else + ss = FF_MAX_SS; +#endif + if ((au != 0 && au < ss) || au > 0x1000000 || (au & (au - 1))) return FR_INVALID_PARAMETER; /* Check if au is valid */ + au /= ss; /* Cluster size in unit of sector */ + + /* Get working buffer */ +#if FF_USE_LFN == 3 + if (!work) { /* Use heap memory for working buffer */ + for (szb_buf = MAX_MALLOC, buf = 0; szb_buf >= ss && (buf = ff_memalloc(szb_buf)) == 0; szb_buf /= 2) ; + sz_buf = szb_buf / ss; /* Size of working buffer (sector) */ + } else +#endif + { + buf = (BYTE*)work; /* Working buffer */ + sz_buf = len / ss; /* Size of working buffer (sector) */ + szb_buf = sz_buf * ss; /* Size of working buffer (byte) */ + } + if (!buf || sz_buf == 0) return FR_NOT_ENOUGH_CORE; + + /* Determine where the volume to be located (b_vol, sz_vol) */ + if (FF_MULTI_PARTITION && part != 0) { + /* Get partition information from partition table in the MBR */ + if (disk_read(pdrv, buf, 0, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Load MBR */ + if (ld_word(buf + BS_55AA) != 0xAA55) LEAVE_MKFS(FR_MKFS_ABORTED); /* Check if MBR is valid */ + pte = buf + (MBR_Table + (part - 1) * SZ_PTE); + if (pte[PTE_System] == 0) LEAVE_MKFS(FR_MKFS_ABORTED); /* No partition? */ + b_vol = ld_dword(pte + PTE_StLba); /* Get volume start sector */ + sz_vol = ld_dword(pte + PTE_SizLba); /* Get volume size */ + } else { + /* Create a single-partition in this function */ + if (disk_ioctl(pdrv, GET_SECTOR_COUNT, &sz_vol) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + b_vol = (opt & FM_SFD) ? 0 : 63; /* Volume start sector */ + if (sz_vol < b_vol) LEAVE_MKFS(FR_MKFS_ABORTED); + sz_vol -= b_vol; /* Volume size */ + } + if (sz_vol < 22) LEAVE_MKFS(FR_MKFS_ABORTED); /* Check if volume size is >=22s (minimum for ss=4096) */ + + /* Pre-determine the FAT type */ + do { + if (FF_FS_EXFAT && (opt & FM_EXFAT)) { /* exFAT possible? */ + if ((opt & FM_ANY) == FM_EXFAT || sz_vol >= 0x4000000 || au > 128) { /* exFAT only, vol >= 64Ms or au > 128s ? */ + fmt = FS_EXFAT; break; + } + } + if (au > 128) LEAVE_MKFS(FR_INVALID_PARAMETER); /* Too large au for FAT/FAT32 */ + if (opt & FM_FAT32) { /* FAT32 possible? */ + if ((opt & FM_ANY) == FM_FAT32 || !(opt & FM_FAT)) { /* FAT32 only or no-FAT? */ + fmt = FS_FAT32; break; + } + } + if (!(opt & FM_FAT)) LEAVE_MKFS(FR_INVALID_PARAMETER); /* no-FAT? */ + fmt = FS_FAT16; + } while (0); + +#if FF_FS_EXFAT + if (fmt == FS_EXFAT) { /* Create an exFAT volume */ + DWORD szb_bit, szb_case, sum, nb, cl; + WCHAR ch, si; + UINT j, st; + BYTE b; + + if (sz_vol < 0x1000) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too small volume? */ +#if FF_USE_TRIM + tbl[0] = b_vol; tbl[1] = b_vol + sz_vol - 1; /* Inform the device the volume area may be erased */ + disk_ioctl(pdrv, CTRL_TRIM, tbl); +#endif + /* Determine FAT location, data location and number of clusters */ + if (au == 0) { /* au auto-selection */ + au = 8; + if (sz_vol >= 0x80000) au = 64; /* >= 512Ks */ + if (sz_vol >= 0x4000000) au = 256; /* >= 64Ms */ + } + b_fat = b_vol + 32; /* FAT start at offset 32 */ + sz_fat = ((sz_vol / au + 2) * 4 + ss - 1) / ss; /* Number of FAT sectors */ + b_data = (b_fat + sz_fat + sz_blk - 1) & ~(sz_blk - 1); /* Align data area to the erase block boundary */ + if (b_data - b_vol >= sz_vol / 2) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too small volume? */ + n_clst = (sz_vol - (b_data - b_vol)) / au; /* Number of clusters */ + if (n_clst <16) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too few clusters? */ + if (n_clst > MAX_EXFAT) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too many clusters? */ + + szb_bit = (n_clst + 7) / 8; /* Size of allocation bitmap */ + tbl[0] = (szb_bit + au * ss - 1) / (au * ss); /* Number of allocation bitmap clusters */ + + /* Create a compressed up-case table */ + sect = b_data + au * tbl[0]; /* Table start sector */ + sum = 0; /* Table checksum to be stored in the 82 entry */ + st = 0; si = 0; i = 0; j = 0; szb_case = 0; + do { + switch (st) { + case 0: + ch = (WCHAR)ff_wtoupper(si); /* Get an up-case char */ + if (ch != si) { + si++; break; /* Store the up-case char if exist */ + } + for (j = 1; (WCHAR)(si + j) && (WCHAR)(si + j) == ff_wtoupper((WCHAR)(si + j)); j++) ; /* Get run length of no-case block */ + if (j >= 128) { + ch = 0xFFFF; st = 2; break; /* Compress the no-case block if run is >= 128 */ + } + st = 1; /* Do not compress short run */ + /* go to next case */ + case 1: + ch = si++; /* Fill the short run */ + if (--j == 0) st = 0; + break; + + default: + ch = (WCHAR)j; si += (WCHAR)j; /* Number of chars to skip */ + st = 0; + } + sum = xsum32(buf[i + 0] = (BYTE)ch, sum); /* Put it into the write buffer */ + sum = xsum32(buf[i + 1] = (BYTE)(ch >> 8), sum); + i += 2; szb_case += 2; + if (si == 0 || i == szb_buf) { /* Write buffered data when buffer full or end of process */ + n = (i + ss - 1) / ss; + if (disk_write(pdrv, buf, sect, n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + sect += n; i = 0; + } + } while (si); + tbl[1] = (szb_case + au * ss - 1) / (au * ss); /* Number of up-case table clusters */ + tbl[2] = 1; /* Number of root dir clusters */ + + /* Initialize the allocation bitmap */ + sect = b_data; nsect = (szb_bit + ss - 1) / ss; /* Start of bitmap and number of sectors */ + nb = tbl[0] + tbl[1] + tbl[2]; /* Number of clusters in-use by system */ + do { + mem_set(buf, 0, szb_buf); + for (i = 0; nb >= 8 && i < szb_buf; buf[i++] = 0xFF, nb -= 8) ; + for (b = 1; nb != 0 && i < szb_buf; buf[i] |= b, b <<= 1, nb--) ; + n = (nsect > sz_buf) ? sz_buf : nsect; /* Write the buffered data */ + if (disk_write(pdrv, buf, sect, n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + sect += n; nsect -= n; + } while (nsect); + + /* Initialize the FAT */ + sect = b_fat; nsect = sz_fat; /* Start of FAT and number of FAT sectors */ + j = nb = cl = 0; + do { + mem_set(buf, 0, szb_buf); i = 0; /* Clear work area and reset write index */ + if (cl == 0) { /* Set entry 0 and 1 */ + st_dword(buf + i, 0xFFFFFFF8); i += 4; cl++; + st_dword(buf + i, 0xFFFFFFFF); i += 4; cl++; + } + do { /* Create chains of bitmap, up-case and root dir */ + while (nb != 0 && i < szb_buf) { /* Create a chain */ + st_dword(buf + i, (nb > 1) ? cl + 1 : 0xFFFFFFFF); + i += 4; cl++; nb--; + } + if (nb == 0 && j < 3) nb = tbl[j++]; /* Next chain */ + } while (nb != 0 && i < szb_buf); + n = (nsect > sz_buf) ? sz_buf : nsect; /* Write the buffered data */ + if (disk_write(pdrv, buf, sect, n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + sect += n; nsect -= n; + } while (nsect); + + /* Initialize the root directory */ + mem_set(buf, 0, szb_buf); + buf[SZDIRE * 0 + 0] = ET_VLABEL; /* Volume label entry */ + buf[SZDIRE * 1 + 0] = ET_BITMAP; /* Bitmap entry */ + st_dword(buf + SZDIRE * 1 + 20, 2); /* cluster */ + st_dword(buf + SZDIRE * 1 + 24, szb_bit); /* size */ + buf[SZDIRE * 2 + 0] = ET_UPCASE; /* Up-case table entry */ + st_dword(buf + SZDIRE * 2 + 4, sum); /* sum */ + st_dword(buf + SZDIRE * 2 + 20, 2 + tbl[0]); /* cluster */ + st_dword(buf + SZDIRE * 2 + 24, szb_case); /* size */ + sect = b_data + au * (tbl[0] + tbl[1]); nsect = au; /* Start of the root directory and number of sectors */ + do { /* Fill root directory sectors */ + n = (nsect > sz_buf) ? sz_buf : nsect; + if (disk_write(pdrv, buf, sect, n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + mem_set(buf, 0, ss); + sect += n; nsect -= n; + } while (nsect); + + /* Create two set of the exFAT VBR blocks */ + sect = b_vol; + for (n = 0; n < 2; n++) { + /* Main record (+0) */ + mem_set(buf, 0, ss); + mem_cpy(buf + BS_JmpBoot, "\xEB\x76\x90" "EXFAT ", 11); /* Boot jump code (x86), OEM name */ + st_dword(buf + BPB_VolOfsEx, b_vol); /* Volume offset in the physical drive [sector] */ + st_dword(buf + BPB_TotSecEx, sz_vol); /* Volume size [sector] */ + st_dword(buf + BPB_FatOfsEx, b_fat - b_vol); /* FAT offset [sector] */ + st_dword(buf + BPB_FatSzEx, sz_fat); /* FAT size [sector] */ + st_dword(buf + BPB_DataOfsEx, b_data - b_vol); /* Data offset [sector] */ + st_dword(buf + BPB_NumClusEx, n_clst); /* Number of clusters */ + st_dword(buf + BPB_RootClusEx, 2 + tbl[0] + tbl[1]); /* Root dir cluster # */ + st_dword(buf + BPB_VolIDEx, GET_FATTIME()); /* VSN */ + st_word(buf + BPB_FSVerEx, 0x100); /* Filesystem version (1.00) */ + for (buf[BPB_BytsPerSecEx] = 0, i = ss; i >>= 1; buf[BPB_BytsPerSecEx]++) ; /* Log2 of sector size [byte] */ + for (buf[BPB_SecPerClusEx] = 0, i = au; i >>= 1; buf[BPB_SecPerClusEx]++) ; /* Log2 of cluster size [sector] */ + buf[BPB_NumFATsEx] = 1; /* Number of FATs */ + buf[BPB_DrvNumEx] = 0x80; /* Drive number (for int13) */ + st_word(buf + BS_BootCodeEx, 0xFEEB); /* Boot code (x86) */ + st_word(buf + BS_55AA, 0xAA55); /* Signature (placed here regardless of sector size) */ + for (i = sum = 0; i < ss; i++) { /* VBR checksum */ + if (i != BPB_VolFlagEx && i != BPB_VolFlagEx + 1 && i != BPB_PercInUseEx) sum = xsum32(buf[i], sum); + } + if (disk_write(pdrv, buf, sect++, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + /* Extended bootstrap record (+1..+8) */ + mem_set(buf, 0, ss); + st_word(buf + ss - 2, 0xAA55); /* Signature (placed at end of sector) */ + for (j = 1; j < 9; j++) { + for (i = 0; i < ss; sum = xsum32(buf[i++], sum)) ; /* VBR checksum */ + if (disk_write(pdrv, buf, sect++, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + } + /* OEM/Reserved record (+9..+10) */ + mem_set(buf, 0, ss); + for ( ; j < 11; j++) { + for (i = 0; i < ss; sum = xsum32(buf[i++], sum)) ; /* VBR checksum */ + if (disk_write(pdrv, buf, sect++, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + } + /* Sum record (+11) */ + for (i = 0; i < ss; i += 4) st_dword(buf + i, sum); /* Fill with checksum value */ + if (disk_write(pdrv, buf, sect++, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + } + + } else +#endif /* FF_FS_EXFAT */ + { /* Create an FAT/FAT32 volume */ + do { + pau = au; + /* Pre-determine number of clusters and FAT sub-type */ + if (fmt == FS_FAT32) { /* FAT32 volume */ + if (pau == 0) { /* au auto-selection */ + n = sz_vol / 0x20000; /* Volume size in unit of 128KS */ + for (i = 0, pau = 1; cst32[i] && cst32[i] <= n; i++, pau <<= 1) ; /* Get from table */ + } + n_clst = sz_vol / pau; /* Number of clusters */ + sz_fat = (n_clst * 4 + 8 + ss - 1) / ss; /* FAT size [sector] */ + sz_rsv = 32; /* Number of reserved sectors */ + sz_dir = 0; /* No static directory */ + if (n_clst <= MAX_FAT16 || n_clst > MAX_FAT32) LEAVE_MKFS(FR_MKFS_ABORTED); + } else { /* FAT volume */ + if (pau == 0) { /* au auto-selection */ + n = sz_vol / 0x1000; /* Volume size in unit of 4KS */ + for (i = 0, pau = 1; cst[i] && cst[i] <= n; i++, pau <<= 1) ; /* Get from table */ + } + n_clst = sz_vol / pau; + if (n_clst > MAX_FAT12) { + n = n_clst * 2 + 4; /* FAT size [byte] */ + } else { + fmt = FS_FAT12; + n = (n_clst * 3 + 1) / 2 + 3; /* FAT size [byte] */ + } + sz_fat = (n + ss - 1) / ss; /* FAT size [sector] */ + sz_rsv = 1; /* Number of reserved sectors */ + sz_dir = (DWORD)n_rootdir * SZDIRE / ss; /* Rootdir size [sector] */ + } + b_fat = b_vol + sz_rsv; /* FAT base */ + b_data = b_fat + sz_fat * n_fats + sz_dir; /* Data base */ + + /* Align data base to erase block boundary (for flash memory media) */ + n = ((b_data + sz_blk - 1) & ~(sz_blk - 1)) - b_data; /* Next nearest erase block from current data base */ + if (fmt == FS_FAT32) { /* FAT32: Move FAT base */ + sz_rsv += n; b_fat += n; + } else { /* FAT: Expand FAT size */ + sz_fat += n / n_fats; + } + + /* Determine number of clusters and final check of validity of the FAT sub-type */ + if (sz_vol < b_data + pau * 16 - b_vol) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too small volume */ + n_clst = (sz_vol - sz_rsv - sz_fat * n_fats - sz_dir) / pau; + if (fmt == FS_FAT32) { + if (n_clst <= MAX_FAT16) { /* Too few clusters for FAT32 */ + if (au == 0 && (au = pau / 2) != 0) continue; /* Adjust cluster size and retry */ + LEAVE_MKFS(FR_MKFS_ABORTED); + } + } + if (fmt == FS_FAT16) { + if (n_clst > MAX_FAT16) { /* Too many clusters for FAT16 */ + if (au == 0 && (pau * 2) <= 64) { + au = pau * 2; continue; /* Adjust cluster size and retry */ + } + if ((opt & FM_FAT32)) { + fmt = FS_FAT32; continue; /* Switch type to FAT32 and retry */ + } + if (au == 0 && (au = pau * 2) <= 128) continue; /* Adjust cluster size and retry */ + LEAVE_MKFS(FR_MKFS_ABORTED); + } + if (n_clst <= MAX_FAT12) { /* Too few clusters for FAT16 */ + if (au == 0 && (au = pau * 2) <= 128) continue; /* Adjust cluster size and retry */ + LEAVE_MKFS(FR_MKFS_ABORTED); + } + } + if (fmt == FS_FAT12 && n_clst > MAX_FAT12) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too many clusters for FAT12 */ + + /* Ok, it is the valid cluster configuration */ + break; + } while (1); + +#if FF_USE_TRIM + tbl[0] = b_vol; tbl[1] = b_vol + sz_vol - 1; /* Inform the device the volume area can be erased */ + disk_ioctl(pdrv, CTRL_TRIM, tbl); +#endif + /* Create FAT VBR */ + mem_set(buf, 0, ss); + mem_cpy(buf + BS_JmpBoot, "\xEB\xFE\x90" "MSDOS5.0", 11);/* Boot jump code (x86), OEM name */ + st_word(buf + BPB_BytsPerSec, ss); /* Sector size [byte] */ + buf[BPB_SecPerClus] = (BYTE)pau; /* Cluster size [sector] */ + st_word(buf + BPB_RsvdSecCnt, (WORD)sz_rsv); /* Size of reserved area */ + buf[BPB_NumFATs] = (BYTE)n_fats; /* Number of FATs */ + st_word(buf + BPB_RootEntCnt, (WORD)((fmt == FS_FAT32) ? 0 : n_rootdir)); /* Number of root directory entries */ + if (sz_vol < 0x10000) { + st_word(buf + BPB_TotSec16, (WORD)sz_vol); /* Volume size in 16-bit LBA */ + } else { + st_dword(buf + BPB_TotSec32, sz_vol); /* Volume size in 32-bit LBA */ + } + buf[BPB_Media] = 0xF8; /* Media descriptor byte */ + st_word(buf + BPB_SecPerTrk, 63); /* Number of sectors per track (for int13) */ + st_word(buf + BPB_NumHeads, 255); /* Number of heads (for int13) */ + st_dword(buf + BPB_HiddSec, b_vol); /* Volume offset in the physical drive [sector] */ + if (fmt == FS_FAT32) { + st_dword(buf + BS_VolID32, GET_FATTIME()); /* VSN */ + st_dword(buf + BPB_FATSz32, sz_fat); /* FAT size [sector] */ + st_dword(buf + BPB_RootClus32, 2); /* Root directory cluster # (2) */ + st_word(buf + BPB_FSInfo32, 1); /* Offset of FSINFO sector (VBR + 1) */ + st_word(buf + BPB_BkBootSec32, 6); /* Offset of backup VBR (VBR + 6) */ + buf[BS_DrvNum32] = 0x80; /* Drive number (for int13) */ + buf[BS_BootSig32] = 0x29; /* Extended boot signature */ + mem_cpy(buf + BS_VolLab32, "NO NAME " "FAT32 ", 19); /* Volume label, FAT signature */ + } else { + st_dword(buf + BS_VolID, GET_FATTIME()); /* VSN */ + st_word(buf + BPB_FATSz16, (WORD)sz_fat); /* FAT size [sector] */ + buf[BS_DrvNum] = 0x80; /* Drive number (for int13) */ + buf[BS_BootSig] = 0x29; /* Extended boot signature */ + mem_cpy(buf + BS_VolLab, "NO NAME " "FAT ", 19); /* Volume label, FAT signature */ + } + st_word(buf + BS_55AA, 0xAA55); /* Signature (offset is fixed here regardless of sector size) */ + if (disk_write(pdrv, buf, b_vol, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Write it to the VBR sector */ + + /* Create FSINFO record if needed */ + if (fmt == FS_FAT32) { + disk_write(pdrv, buf, b_vol + 6, 1); /* Write backup VBR (VBR + 6) */ + mem_set(buf, 0, ss); + st_dword(buf + FSI_LeadSig, 0x41615252); + st_dword(buf + FSI_StrucSig, 0x61417272); + st_dword(buf + FSI_Free_Count, n_clst - 1); /* Number of free clusters */ + st_dword(buf + FSI_Nxt_Free, 2); /* Last allocated cluster# */ + st_word(buf + BS_55AA, 0xAA55); + disk_write(pdrv, buf, b_vol + 7, 1); /* Write backup FSINFO (VBR + 7) */ + disk_write(pdrv, buf, b_vol + 1, 1); /* Write original FSINFO (VBR + 1) */ + } + + /* Initialize FAT area */ + mem_set(buf, 0, (UINT)szb_buf); + sect = b_fat; /* FAT start sector */ + for (i = 0; i < n_fats; i++) { /* Initialize FATs each */ + if (fmt == FS_FAT32) { + st_dword(buf + 0, 0xFFFFFFF8); /* Entry 0 */ + st_dword(buf + 4, 0xFFFFFFFF); /* Entry 1 */ + st_dword(buf + 8, 0x0FFFFFFF); /* Entry 2 (root directory) */ + } else { + st_dword(buf + 0, (fmt == FS_FAT12) ? 0xFFFFF8 : 0xFFFFFFF8); /* Entry 0 and 1 */ + } + nsect = sz_fat; /* Number of FAT sectors */ + do { /* Fill FAT sectors */ + n = (nsect > sz_buf) ? sz_buf : nsect; + if (disk_write(pdrv, buf, sect, (UINT)n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + mem_set(buf, 0, ss); + sect += n; nsect -= n; + } while (nsect); + } + + /* Initialize root directory (fill with zero) */ + nsect = (fmt == FS_FAT32) ? pau : sz_dir; /* Number of root directory sectors */ + do { + n = (nsect > sz_buf) ? sz_buf : nsect; + if (disk_write(pdrv, buf, sect, (UINT)n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + sect += n; nsect -= n; + } while (nsect); + } + + /* Determine system ID in the partition table */ + if (FF_FS_EXFAT && fmt == FS_EXFAT) { + sys = 0x07; /* HPFS/NTFS/exFAT */ + } else { + if (fmt == FS_FAT32) { + sys = 0x0C; /* FAT32X */ + } else { + if (sz_vol >= 0x10000) { + sys = 0x06; /* FAT12/16 (large) */ + } else { + sys = (fmt == FS_FAT16) ? 0x04 : 0x01; /* FAT16 : FAT12 */ + } + } + } + + /* Update partition information */ + if (FF_MULTI_PARTITION && part != 0) { /* Created in the existing partition */ + /* Update system ID in the partition table */ + if (disk_read(pdrv, buf, 0, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Read the MBR */ + buf[MBR_Table + (part - 1) * SZ_PTE + PTE_System] = sys; /* Set system ID */ + if (disk_write(pdrv, buf, 0, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Write it back to the MBR */ + } else { /* Created as a new single partition */ + if (!(opt & FM_SFD)) { /* Create partition table if in FDISK format */ + mem_set(buf, 0, ss); + st_word(buf + BS_55AA, 0xAA55); /* MBR signature */ + pte = buf + MBR_Table; /* Create partition table for single partition in the drive */ + pte[PTE_Boot] = 0; /* Boot indicator */ + pte[PTE_StHead] = 1; /* Start head */ + pte[PTE_StSec] = 1; /* Start sector */ + pte[PTE_StCyl] = 0; /* Start cylinder */ + pte[PTE_System] = sys; /* System type */ + n = (b_vol + sz_vol) / (63 * 255); /* (End CHS may be invalid) */ + pte[PTE_EdHead] = 254; /* End head */ + pte[PTE_EdSec] = (BYTE)(((n >> 2) & 0xC0) | 63); /* End sector */ + pte[PTE_EdCyl] = (BYTE)n; /* End cylinder */ + st_dword(pte + PTE_StLba, b_vol); /* Start offset in LBA */ + st_dword(pte + PTE_SizLba, sz_vol); /* Size in sectors */ + if (disk_write(pdrv, buf, 0, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Write it to the MBR */ + } + } + + if (disk_ioctl(pdrv, CTRL_SYNC, 0) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + + LEAVE_MKFS(FR_OK); +} + + + +#if FF_MULTI_PARTITION +/*-----------------------------------------------------------------------*/ +/* Create Partition Table on the Physical Drive */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_fdisk ( + void *pdrv, /* Physical drive number */ + const DWORD* szt, /* Pointer to the size table for each partitions */ + void* work /* Pointer to the working buffer (null: use heap memory) */ +) +{ + UINT i, n, sz_cyl, tot_cyl, b_cyl, e_cyl, p_cyl; + BYTE s_hd, e_hd, *p, *buf = (BYTE*)work; + DSTATUS stat; + DWORD sz_disk, sz_part, s_part; + FRESULT res; + + + disk_ioctl(pdrv, IOCTL_INIT, &stat); + if (stat & STA_NOINIT) return FR_NOT_READY; + if (stat & STA_PROTECT) return FR_WRITE_PROTECTED; + if (disk_ioctl(pdrv, GET_SECTOR_COUNT, &sz_disk)) return FR_DISK_ERR; + + buf = (BYTE*)work; +#if FF_USE_LFN == 3 + if (!buf) buf = ff_memalloc(FF_MAX_SS); /* Use heap memory for working buffer */ +#endif + if (!buf) return FR_NOT_ENOUGH_CORE; + + /* Determine the CHS without any consideration of the drive geometry */ + for (n = 16; n < 256 && sz_disk / n / 63 > 1024; n *= 2) ; + if (n == 256) n--; + e_hd = (BYTE)(n - 1); + sz_cyl = 63 * n; + tot_cyl = sz_disk / sz_cyl; + + /* Create partition table */ + mem_set(buf, 0, FF_MAX_SS); + p = buf + MBR_Table; b_cyl = 0; + for (i = 0; i < 4; i++, p += SZ_PTE) { + p_cyl = (szt[i] <= 100U) ? (DWORD)tot_cyl * szt[i] / 100 : szt[i] / sz_cyl; /* Number of cylinders */ + if (p_cyl == 0) continue; + s_part = (DWORD)sz_cyl * b_cyl; + sz_part = (DWORD)sz_cyl * p_cyl; + if (i == 0) { /* Exclude first track of cylinder 0 */ + s_hd = 1; + s_part += 63; sz_part -= 63; + } else { + s_hd = 0; + } + e_cyl = b_cyl + p_cyl - 1; /* End cylinder */ + if (e_cyl >= tot_cyl) LEAVE_MKFS(FR_INVALID_PARAMETER); + + /* Set partition table */ + p[1] = s_hd; /* Start head */ + p[2] = (BYTE)(((b_cyl >> 2) & 0xC0) | 1); /* Start sector */ + p[3] = (BYTE)b_cyl; /* Start cylinder */ + p[4] = 0x07; /* System type (temporary setting) */ + p[5] = e_hd; /* End head */ + p[6] = (BYTE)(((e_cyl >> 2) & 0xC0) | 63); /* End sector */ + p[7] = (BYTE)e_cyl; /* End cylinder */ + st_dword(p + 8, s_part); /* Start sector in LBA */ + st_dword(p + 12, sz_part); /* Number of sectors */ + + /* Next partition */ + b_cyl += p_cyl; + } + st_word(p, 0xAA55); /* MBR signature (always at offset 510) */ + + /* Write it to the MBR */ + res = (disk_write(pdrv, buf, 0, 1) == RES_OK && disk_ioctl(pdrv, CTRL_SYNC, 0) == RES_OK) ? FR_OK : FR_DISK_ERR; + LEAVE_MKFS(res); +} + +#endif /* FF_MULTI_PARTITION */ +#endif /* FF_USE_MKFS && !FF_FS_READONLY */ + + + +#if FF_CODE_PAGE == 0 +/*-----------------------------------------------------------------------*/ +/* Set Active Codepage for the Path Name */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_setcp ( + WORD cp /* Value to be set as active code page */ +) +{ + static const WORD validcp[] = { 437, 720, 737, 771, 775, 850, 852, 857, 860, 861, 862, 863, 864, 865, 866, 869, 932, 936, 949, 950, 0}; + static const BYTE* const tables[] = {Ct437, Ct720, Ct737, Ct771, Ct775, Ct850, Ct852, Ct857, Ct860, Ct861, Ct862, Ct863, Ct864, Ct865, Ct866, Ct869, Dc932, Dc936, Dc949, Dc950, 0}; + UINT i; + + + for (i = 0; validcp[i] != 0 && validcp[i] != cp; i++) ; /* Find the code page */ + if (validcp[i] != cp) return FR_INVALID_PARAMETER; /* Not found? */ + + CodePage = cp; + if (cp >= 900) { /* DBCS */ + ExCvt = 0; + DbcTbl = tables[i]; + } else { /* SBCS */ + ExCvt = tables[i]; + DbcTbl = 0; + } + return FR_OK; +} +#endif /* FF_CODE_PAGE == 0 */ + diff --git a/components/3rd_party/omv/omv/ports/linux/oofatfs/ff.h b/components/3rd_party/omv/omv/ports/linux/oofatfs/ff.h new file mode 100644 index 00000000..d4a4ac66 --- /dev/null +++ b/components/3rd_party/omv/omv/ports/linux/oofatfs/ff.h @@ -0,0 +1,401 @@ +/* This file is part of ooFatFs, a customised version of FatFs + * See https://github.com/micropython/oofatfs for details + */ + +/*----------------------------------------------------------------------------/ +/ FatFs - Generic FAT Filesystem module R0.13c / +/-----------------------------------------------------------------------------/ +/ +/ Copyright (C) 2018, ChaN, all right reserved. +/ +/ FatFs module is an open source software. Redistribution and use of FatFs in +/ source and binary forms, with or without modification, are permitted provided +/ that the following condition is met: + +/ 1. Redistributions of source code must retain the above copyright notice, +/ this condition and the following disclaimer. +/ +/ This software is provided by the copyright holder and contributors "AS IS" +/ and any warranties related to this software are DISCLAIMED. +/ The copyright owner or contributors be NOT LIABLE for any damages caused +/ by use of this software. +/ +/----------------------------------------------------------------------------*/ + + +#ifndef FF_DEFINED +#define FF_DEFINED 86604 /* Revision ID */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "ffconf.h" /* FatFs configuration options */ + +#if FF_DEFINED != FFCONF_DEF +#error Wrong configuration file (ffconf.h). +#endif + + +/* Integer types used for FatFs API */ + +#if defined(_WIN32) /* Main development platform */ +#define FF_INTDEF 2 +#include +typedef unsigned __int64 QWORD; +#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus) /* C99 or later */ +#define FF_INTDEF 2 +#include +typedef unsigned int UINT; /* int must be 16-bit or 32-bit */ +typedef unsigned char BYTE; /* char must be 8-bit */ +typedef uint16_t WORD; /* 16-bit unsigned integer */ +typedef uint16_t WCHAR; /* 16-bit unsigned integer */ +typedef uint32_t DWORD; /* 32-bit unsigned integer */ +typedef uint64_t QWORD; /* 64-bit unsigned integer */ +#else /* Earlier than C99 */ +#define FF_INTDEF 1 +typedef unsigned int UINT; /* int must be 16-bit or 32-bit */ +typedef unsigned char BYTE; /* char must be 8-bit */ +typedef unsigned short WORD; /* 16-bit unsigned integer */ +typedef unsigned short WCHAR; /* 16-bit unsigned integer */ +typedef unsigned long DWORD; /* 32-bit unsigned integer */ +#endif + + +/* Definitions of volume management */ + +#if FF_STR_VOLUME_ID +#ifndef FF_VOLUME_STRS +extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */ +#endif +#endif + + + +/* Type of path name strings on FatFs API */ + +#ifndef _INC_TCHAR +#define _INC_TCHAR + +#if FF_USE_LFN && FF_LFN_UNICODE == 1 /* Unicode in UTF-16 encoding */ +typedef WCHAR TCHAR; +#define _T(x) L ## x +#define _TEXT(x) L ## x +#elif FF_USE_LFN && FF_LFN_UNICODE == 2 /* Unicode in UTF-8 encoding */ +typedef char TCHAR; +#define _T(x) u8 ## x +#define _TEXT(x) u8 ## x +#elif FF_USE_LFN && FF_LFN_UNICODE == 3 /* Unicode in UTF-32 encoding */ +typedef DWORD TCHAR; +#define _T(x) U ## x +#define _TEXT(x) U ## x +#elif FF_USE_LFN && (FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3) +#error Wrong FF_LFN_UNICODE setting +#else /* ANSI/OEM code in SBCS/DBCS */ +typedef char TCHAR; +#define _T(x) x +#define _TEXT(x) x +#endif + +#endif + + + +/* Type of file size variables */ + +#if FF_FS_EXFAT +#if FF_INTDEF != 2 +#error exFAT feature wants C99 or later +#endif +typedef QWORD FSIZE_t; +#else +typedef DWORD FSIZE_t; +#endif + + + +/* Filesystem object structure (FATFS) */ + +typedef struct { + void *drv; // block device underlying this filesystem +#if FF_MULTI_PARTITION /* Multiple partition configuration */ + BYTE part; // Partition: 0:Auto detect, 1-4:Forced partition +#endif + BYTE fs_type; /* Filesystem type (0:not mounted) */ + BYTE n_fats; /* Number of FATs (1 or 2) */ + BYTE wflag; /* win[] flag (b0:dirty) */ + BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */ + WORD id; /* Volume mount ID */ + WORD n_rootdir; /* Number of root directory entries (FAT12/16) */ + WORD csize; /* Cluster size [sectors] */ +#if FF_MAX_SS != FF_MIN_SS + WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */ +#endif +#if FF_USE_LFN + WCHAR* lfnbuf; /* LFN working buffer */ +#endif +#if FF_FS_EXFAT + BYTE* dirbuf; /* Directory entry block scratchpad buffer for exFAT */ +#endif +#if FF_FS_REENTRANT + FF_SYNC_t sobj; /* Identifier of sync object */ +#endif +#if !FF_FS_READONLY + DWORD last_clst; /* Last allocated cluster */ + DWORD free_clst; /* Number of free clusters */ +#endif +#if FF_FS_RPATH + DWORD cdir; /* Current directory start cluster (0:root) */ +#if FF_FS_EXFAT + DWORD cdc_scl; /* Containing directory start cluster (invalid when cdir is 0) */ + DWORD cdc_size; /* b31-b8:Size of containing directory, b7-b0: Chain status */ + DWORD cdc_ofs; /* Offset in the containing directory (invalid when cdir is 0) */ +#endif +#endif + DWORD n_fatent; /* Number of FAT entries (number of clusters + 2) */ + DWORD fsize; /* Size of an FAT [sectors] */ + DWORD volbase; /* Volume base sector */ + DWORD fatbase; /* FAT base sector */ + DWORD dirbase; /* Root directory base sector/cluster */ + DWORD database; /* Data base sector */ +#if FF_FS_EXFAT + DWORD bitbase; /* Allocation bitmap base sector */ +#endif + DWORD winsect; /* Current sector appearing in the win[] */ + BYTE win[FF_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */ +} FATFS; + + + +/* Object ID and allocation information (FFOBJID) */ + +typedef struct { + FATFS* fs; /* Pointer to the hosting volume of this object */ + WORD id; /* Hosting volume mount ID */ + BYTE attr; /* Object attribute */ + BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */ + DWORD sclust; /* Object data start cluster (0:no cluster or root directory) */ + FSIZE_t objsize; /* Object size (valid when sclust != 0) */ +#if FF_FS_EXFAT + DWORD n_cont; /* Size of first fragment - 1 (valid when stat == 3) */ + DWORD n_frag; /* Size of last fragment needs to be written to FAT (valid when not zero) */ + DWORD c_scl; /* Containing directory start cluster (valid when sclust != 0) */ + DWORD c_size; /* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */ + DWORD c_ofs; /* Offset in the containing directory (valid when file object and sclust != 0) */ +#endif +#if FF_FS_LOCK + UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */ +#endif +} FFOBJID; + + + +/* File object structure (FIL) */ + +typedef struct { + FFOBJID obj; /* Object identifier (must be the 1st member to detect invalid object pointer) */ + BYTE flag; /* File status flags */ + BYTE err; /* Abort flag (error code) */ + FSIZE_t fptr; /* File read/write pointer (Zeroed on file open) */ + DWORD clust; /* Current cluster of fpter (invalid when fptr is 0) */ + DWORD sect; /* Sector number appearing in buf[] (0:invalid) */ +#if !FF_FS_READONLY + DWORD dir_sect; /* Sector number containing the directory entry (not used at exFAT) */ + BYTE* dir_ptr; /* Pointer to the directory entry in the win[] (not used at exFAT) */ +#endif +#if FF_USE_FASTSEEK + DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */ +#endif +#if !FF_FS_TINY + BYTE buf[FF_MAX_SS]; /* File private data read/write window */ +#endif +} FIL; + + + +/* Directory object structure (FF_DIR) */ + +typedef struct { + FFOBJID obj; /* Object identifier */ + DWORD dptr; /* Current read/write offset */ + DWORD clust; /* Current cluster */ + DWORD sect; /* Current sector (0:Read operation has terminated) */ + BYTE* dir; /* Pointer to the directory item in the win[] */ + BYTE fn[12]; /* SFN (in/out) {body[8],ext[3],status[1]} */ +#if FF_USE_LFN + DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */ +#endif +#if FF_USE_FIND + const TCHAR* pat; /* Pointer to the name matching pattern */ +#endif +} FF_DIR; + + + +/* File information structure (FILINFO) */ + +typedef struct { + FSIZE_t fsize; /* File size */ + WORD fdate; /* Modified date */ + WORD ftime; /* Modified time */ + BYTE fattrib; /* File attribute */ +#if FF_USE_LFN + TCHAR altname[FF_SFN_BUF + 1];/* Altenative file name */ + TCHAR fname[FF_LFN_BUF + 1]; /* Primary file name */ +#else + TCHAR fname[12 + 1]; /* File name */ +#endif +} FILINFO; + + + +/* File function return code (FRESULT) */ + +typedef enum { + FR_OK = 0, /* (0) Succeeded */ + FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */ + FR_INT_ERR, /* (2) Assertion failed */ + FR_NOT_READY, /* (3) The physical drive cannot work */ + FR_NO_FILE, /* (4) Could not find the file */ + FR_NO_PATH, /* (5) Could not find the path */ + FR_INVALID_NAME, /* (6) The path name format is invalid */ + FR_DENIED, /* (7) Access denied due to prohibited access or directory full */ + FR_EXIST, /* (8) Access denied due to prohibited access */ + FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */ + FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */ + FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */ + FR_NOT_ENABLED, /* (12) The volume has no work area */ + FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */ + FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */ + FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */ + FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */ + FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */ + FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > FF_FS_LOCK */ + FR_INVALID_PARAMETER /* (19) Given parameter is invalid */ +} FRESULT; + + + +/*--------------------------------------------------------------*/ +/* FatFs module application interface */ + +FRESULT f_open (FATFS *fs, FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */ +FRESULT f_close (FIL* fp); /* Close an open file object */ +FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */ +FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */ +FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */ +FRESULT f_truncate (FIL* fp); /* Truncate the file */ +FRESULT f_sync (FIL* fp); /* Flush cached data of the writing file */ +FRESULT f_opendir (FATFS *fs, FF_DIR* dp, const TCHAR* path); /* Open a directory */ +FRESULT f_closedir (FF_DIR* dp); /* Close an open directory */ +FRESULT f_readdir (FF_DIR* dp, FILINFO* fno); /* Read a directory item */ +FRESULT f_findfirst (FF_DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */ +FRESULT f_findnext (FF_DIR* dp, FILINFO* fno); /* Find next file */ +FRESULT f_mkdir (FATFS *fs, const TCHAR* path); /* Create a sub directory */ +FRESULT f_unlink (FATFS *fs, const TCHAR* path); /* Delete an existing file or directory */ +FRESULT f_rename (FATFS *fs, const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */ +FRESULT f_stat (FATFS *fs, const TCHAR* path, FILINFO* fno); /* Get file status */ +FRESULT f_chmod (FATFS *fs, const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */ +FRESULT f_utime (FATFS *fs, const TCHAR* path, const FILINFO* fno); /* Change timestamp of a file/dir */ +FRESULT f_chdir (FATFS *fs, const TCHAR* path); /* Change current directory */ +FRESULT f_getcwd (FATFS *fs, TCHAR* buff, UINT len); /* Get current directory */ +FRESULT f_getfree (FATFS *fs, DWORD* nclst); /* Get number of free clusters on the drive */ +FRESULT f_getlabel (FATFS *fs, TCHAR* label, DWORD* vsn); /* Get volume label */ +FRESULT f_setlabel (FATFS *fs, const TCHAR* label); /* Set volume label */ +FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */ +FRESULT f_expand (FIL* fp, FSIZE_t szf, BYTE opt); /* Allocate a contiguous block to the file */ +FRESULT f_mount (FATFS* fs); /* Mount/Unmount a logical drive */ +FRESULT f_umount (FATFS* fs); /* Unmount a logical drive */ +FRESULT f_mkfs (FATFS *fs, BYTE opt, DWORD au, void* work, UINT len); /* Create a FAT volume */ +FRESULT f_fdisk (void *pdrv, const DWORD* szt, void* work); /* Divide a physical drive into some partitions */ +FRESULT f_setcp (WORD cp); /* Set current code page */ + +#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize)) +#define f_error(fp) ((fp)->err) +#define f_tell(fp) ((fp)->fptr) +#define f_size(fp) ((fp)->obj.objsize) +#define f_rewind(fp) f_lseek((fp), 0) +#define f_rewinddir(dp) f_readdir((dp), 0) +#define f_rmdir(path) f_unlink(path) +#define f_unmount(path) f_mount(0, path, 0) + +#ifndef EOF +#define EOF (-1) +#endif + + + + +/*--------------------------------------------------------------*/ +/* Additional user defined functions */ + +/* RTC function */ +#if !FF_FS_READONLY && !FF_FS_NORTC +DWORD get_fattime (void); +#endif + +/* LFN support functions */ +#if FF_USE_LFN >= 1 /* Code conversion (defined in unicode.c) */ +WCHAR ff_oem2uni (WCHAR oem, WORD cp); /* OEM code to Unicode conversion */ +WCHAR ff_uni2oem (DWORD uni, WORD cp); /* Unicode to OEM code conversion */ +DWORD ff_wtoupper (DWORD uni); /* Unicode upper-case conversion */ +#endif +#if FF_USE_LFN == 3 /* Dynamic memory allocation */ +void* ff_memalloc (UINT msize); /* Allocate memory block */ +void ff_memfree (void* mblock); /* Free memory block */ +#endif + +/* Sync functions */ +#if FF_FS_REENTRANT +int ff_cre_syncobj (FATFS *fatfs, FF_SYNC_t* sobj); /* Create a sync object */ +int ff_req_grant (FF_SYNC_t sobj); /* Lock sync object */ +void ff_rel_grant (FF_SYNC_t sobj); /* Unlock sync object */ +int ff_del_syncobj (FF_SYNC_t sobj); /* Delete a sync object */ +#endif + + + + +/*--------------------------------------------------------------*/ +/* Flags and offset address */ + + +/* File access mode and open method flags (3rd argument of f_open) */ +#define FA_READ 0x01 +#define FA_WRITE 0x02 +#define FA_OPEN_EXISTING 0x00 +#define FA_CREATE_NEW 0x04 +#define FA_CREATE_ALWAYS 0x08 +#define FA_OPEN_ALWAYS 0x10 +#define FA_OPEN_APPEND 0x30 + +/* Fast seek controls (2nd argument of f_lseek) */ +#define CREATE_LINKMAP ((FSIZE_t)0 - 1) + +/* Format options (2nd argument of f_mkfs) */ +#define FM_FAT 0x01 +#define FM_FAT32 0x02 +#define FM_EXFAT 0x04 +#define FM_ANY 0x07 +#define FM_SFD 0x08 + +/* Filesystem type (FATFS.fs_type) */ +#define FS_FAT12 1 +#define FS_FAT16 2 +#define FS_FAT32 3 +#define FS_EXFAT 4 + +/* File attribute bits for directory entry (FILINFO.fattrib) */ +#define AM_RDO 0x01 /* Read only */ +#define AM_HID 0x02 /* Hidden */ +#define AM_SYS 0x04 /* System */ +#define AM_DIR 0x10 /* Directory */ +#define AM_ARC 0x20 /* Archive */ + + +#ifdef __cplusplus +} +#endif + +#endif /* FF_DEFINED */ diff --git a/components/3rd_party/omv/omv/ports/linux/oofatfs/ffconf.h b/components/3rd_party/omv/omv/ports/linux/oofatfs/ffconf.h new file mode 100644 index 00000000..a2e3d2ca --- /dev/null +++ b/components/3rd_party/omv/omv/ports/linux/oofatfs/ffconf.h @@ -0,0 +1,372 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Original file from: + * FatFs - FAT file system module configuration file R0.13c (C)ChaN, 2018 + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2019 Damien P. George + * + * 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. + */ + + +/*---------------------------------------------------------------------------/ +/ FatFs Functional Configurations +/---------------------------------------------------------------------------*/ + +#define FFCONF_DEF 86604 /* Revision ID */ + +/*---------------------------------------------------------------------------/ +/ Function Configurations +/---------------------------------------------------------------------------*/ + +#define FF_FS_READONLY 0 +/* This option switches read-only configuration. (0:Read/Write or 1:Read-only) +/ Read-only configuration removes writing API functions, f_write(), f_sync(), +/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree() +/ and optional writing functions as well. */ + + +#define FF_FS_MINIMIZE 0 +/* This option defines minimization level to remove some basic API functions. +/ +/ 0: Basic functions are fully enabled. +/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename() +/ are removed. +/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1. +/ 3: f_lseek() function is removed in addition to 2. */ + + +#define FF_USE_STRFUNC 1 +/* This option switches string functions, f_gets(), f_putc(), f_puts() and f_printf(). +/ +/ 0: Disable string functions. +/ 1: Enable without LF-CRLF conversion. +/ 2: Enable with LF-CRLF conversion. */ + + +#define FF_USE_FIND 0 +/* This option switches filtered directory read functions, f_findfirst() and +/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */ + + +#define FF_USE_MKFS 1 +/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */ + + +#define FF_USE_FASTSEEK 0 +/* This option switches fast seek function. (0:Disable or 1:Enable) */ + + +#define FF_USE_EXPAND 0 +/* This option switches f_expand function. (0:Disable or 1:Enable) */ + + +#define FF_USE_CHMOD 1 +/* This option switches attribute manipulation functions, f_chmod() and f_utime(). +/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */ + + +#ifdef MICROPY_FATFS_USE_LABEL +#define FF_USE_LABEL (MICROPY_FATFS_USE_LABEL) +#else +#define FF_USE_LABEL 0 +#endif + +/* This option switches volume label functions, f_getlabel() and f_setlabel(). +/ (0:Disable or 1:Enable) */ + + +#define FF_USE_FORWARD 0 +/* This option switches f_forward() function. (0:Disable or 1:Enable) */ + + +/*---------------------------------------------------------------------------/ +/ Locale and Namespace Configurations +/---------------------------------------------------------------------------*/ + +#ifdef MICROPY_FATFS_LFN_CODE_PAGE +#define FF_CODE_PAGE MICROPY_FATFS_LFN_CODE_PAGE +#else +#define FF_CODE_PAGE 437 +#endif + +/* This option specifies the OEM code page to be used on the target system. +/ Incorrect code page setting can cause a file open failure. +/ +/ 437 - U.S. +/ 720 - Arabic +/ 737 - Greek +/ 771 - KBL +/ 775 - Baltic +/ 850 - Latin 1 +/ 852 - Latin 2 +/ 855 - Cyrillic +/ 857 - Turkish +/ 860 - Portuguese +/ 861 - Icelandic +/ 862 - Hebrew +/ 863 - Canadian French +/ 864 - Arabic +/ 865 - Nordic +/ 866 - Russian +/ 869 - Greek 2 +/ 932 - Japanese (DBCS) +/ 936 - Simplified Chinese (DBCS) +/ 949 - Korean (DBCS) +/ 950 - Traditional Chinese (DBCS) +/ 0 - Include all code pages above and configured by f_setcp() +*/ + + +#ifdef MICROPY_FATFS_ENABLE_LFN +#define FF_USE_LFN (MICROPY_FATFS_ENABLE_LFN) +#else +#define FF_USE_LFN 0 +#endif +#ifdef MICROPY_FATFS_MAX_LFN +#define FF_MAX_LFN (MICROPY_FATFS_MAX_LFN) +#else +#define FF_MAX_LFN 255 +#endif +/* The FF_USE_LFN switches the support for LFN (long file name). +/ +/ 0: Disable LFN. FF_MAX_LFN has no effect. +/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe. +/ 2: Enable LFN with dynamic working buffer on the STACK. +/ 3: Enable LFN with dynamic working buffer on the HEAP. +/ +/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN function +/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and +/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled. +/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can +/ be in range of 12 to 255. It is recommended to be set 255 to fully support LFN +/ specification. +/ When use stack for the working buffer, take care on stack overflow. When use heap +/ memory for the working buffer, memory management functions, ff_memalloc() and +/ ff_memfree() in ffsystem.c, need to be added to the project. */ + + +#define FF_LFN_UNICODE 0 +/* This option switches the character encoding on the API when LFN is enabled. +/ +/ 0: ANSI/OEM in current CP (TCHAR = char) +/ 1: Unicode in UTF-16 (TCHAR = WCHAR) +/ 2: Unicode in UTF-8 (TCHAR = char) +/ 3: Unicode in UTF-32 (TCHAR = DWORD) +/ +/ Also behavior of string I/O functions will be affected by this option. +/ When LFN is not enabled, this option has no effect. */ + + +#define FF_LFN_BUF 255 +#define FF_SFN_BUF 12 +/* This set of options defines size of file name members in the FILINFO structure +/ which is used to read out directory items. These values should be suffcient for +/ the file names to read. The maximum possible length of the read file name depends +/ on character encoding. When LFN is not enabled, these options have no effect. */ + + +#define FF_STRF_ENCODE 3 +/* When FF_LFN_UNICODE >= 1 with LFN enabled, string I/O functions, f_gets(), +/ f_putc(), f_puts and f_printf() convert the character encoding in it. +/ This option selects assumption of character encoding ON THE FILE to be +/ read/written via those functions. +/ +/ 0: ANSI/OEM in current CP +/ 1: Unicode in UTF-16LE +/ 2: Unicode in UTF-16BE +/ 3: Unicode in UTF-8 +*/ + + +#ifdef MICROPY_FATFS_RPATH +#define FF_FS_RPATH (MICROPY_FATFS_RPATH) +#else +#define FF_FS_RPATH 0 +#endif +/* This option configures support for relative path. +/ +/ 0: Disable relative path and remove related functions. +/ 1: Enable relative path. f_chdir() and f_chdrive() are available. +/ 2: f_getcwd() function is available in addition to 1. +*/ + + +/*---------------------------------------------------------------------------/ +/ Drive/Volume Configurations +/---------------------------------------------------------------------------*/ + +#define FF_VOLUMES 1 +/* Number of volumes (logical drives) to be used. (1-10) */ + + +#define FF_STR_VOLUME_ID 0 +#define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3" +/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings. +/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive +/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each +/ logical drives. Number of items must not be less than FF_VOLUMES. Valid +/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are +/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is +/ not defined, a user defined volume string table needs to be defined as: +/ +/ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",... +*/ + + +#ifdef MICROPY_FATFS_MULTI_PARTITION +#define FF_MULTI_PARTITION (MICROPY_FATFS_MULTI_PARTITION) +#else +#define FF_MULTI_PARTITION 0 +#endif +/* This option switches support for multiple volumes on the physical drive. +/ By default (0), each logical drive number is bound to the same physical drive +/ number and only an FAT volume found on the physical drive will be mounted. +/ When this function is enabled (1), each logical drive number can be bound to +/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk() +/ funciton will be available. */ + + +#define FF_MIN_SS 512 +#ifdef MICROPY_FATFS_MAX_SS +#define FF_MAX_SS (MICROPY_FATFS_MAX_SS) +#else +#define FF_MAX_SS 512 +#endif +/* This set of options configures the range of sector size to be supported. (512, +/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and +/ harddisk. But a larger value may be required for on-board flash memory and some +/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured +/ for variable sector size mode and disk_ioctl() function needs to implement +/ GET_SECTOR_SIZE command. */ + + +#define FF_USE_TRIM 0 +/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable) +/ To enable Trim function, also CTRL_TRIM command should be implemented to the +/ disk_ioctl() function. */ + + +#define FF_FS_NOFSINFO 0 +/* If you need to know correct free space on the FAT32 volume, set bit 0 of this +/ option, and f_getfree() function at first time after volume mount will force +/ a full FAT scan. Bit 1 controls the use of last allocated cluster number. +/ +/ bit0=0: Use free cluster count in the FSINFO if available. +/ bit0=1: Do not trust free cluster count in the FSINFO. +/ bit1=0: Use last allocated cluster number in the FSINFO if available. +/ bit1=1: Do not trust last allocated cluster number in the FSINFO. +*/ + + + +/*---------------------------------------------------------------------------/ +/ System Configurations +/---------------------------------------------------------------------------*/ + +#define FF_FS_TINY 1 +/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny) +/ At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes. +/ Instead of private sector buffer eliminated from the file object, common sector +/ buffer in the filesystem object (FATFS) is used for the file data transfer. */ + + +#ifdef MICROPY_FATFS_EXFAT +#define FF_FS_EXFAT (MICROPY_FATFS_EXFAT) +#else +#define FF_FS_EXFAT 0 +#endif +/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable) +/ To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1) +/ Note that enabling exFAT discards ANSI C (C89) compatibility. */ + + +#ifdef MICROPY_FATFS_NORTC +#define FF_FS_NORTC (MICROPY_FATFS_NORTC) +#else +#define FF_FS_NORTC 0 +#endif +#define FF_NORTC_MON 1 +#define FF_NORTC_MDAY 1 +#define FF_NORTC_YEAR 2018 +/* The option FF_FS_NORTC switches timestamp functiton. If the system does not have +/ any RTC function or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable +/ the timestamp function. Every object modified by FatFs will have a fixed timestamp +/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time. +/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be +/ added to the project to read current time form real-time clock. FF_NORTC_MON, +/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect. +/ These options have no effect at read-only configuration (FF_FS_READONLY = 1). */ + + +#define FF_FS_LOCK 0 +/* The option FF_FS_LOCK switches file lock function to control duplicated file open +/ and illegal operation to open objects. This option must be 0 when FF_FS_READONLY +/ is 1. +/ +/ 0: Disable file lock function. To avoid volume corruption, application program +/ should avoid illegal open, remove and rename to the open objects. +/ >0: Enable file lock function. The value defines how many files/sub-directories +/ can be opened simultaneously under file lock control. Note that the file +/ lock control is independent of re-entrancy. */ + + +#ifdef MICROPY_FATFS_REENTRANT +#define FF_FS_REENTRANT (MICROPY_FATFS_REENTRANT) +#else +#define FF_FS_REENTRANT 0 +#endif + +// milliseconds +#ifdef MICROPY_FATFS_TIMEOUT +#define FF_FS_TIMEOUT (MICROPY_FATFS_TIMEOUT) +#else +#define FF_FS_TIMEOUT 1000 +#endif + +#ifdef MICROPY_FATFS_SYNC_T +#define FF_SYNC_t MICROPY_FATFS_SYNC_T +#else +#define FF_SYNC_t HANDLE +#endif +/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs +/ module itself. Note that regardless of this option, file access to different +/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs() +/ and f_fdisk() function, are always not re-entrant. Only file/directory access +/ to the same volume is under control of this function. +/ +/ 0: Disable re-entrancy. FF_FS_TIMEOUT and FF_SYNC_t have no effect. +/ 1: Enable re-entrancy. Also user provided synchronization handlers, +/ ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj() +/ function, must be added to the project. Samples are available in +/ option/syscall.c. +/ +/ The FF_FS_TIMEOUT defines timeout period in unit of time tick. +/ The FF_SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*, +/ SemaphoreHandle_t and etc. A header file for O/S definitions needs to be +/ included somewhere in the scope of ff.h. */ + +// Legacy definitions +#define _MAX_SS FF_MAX_SS +#define _MIN_SS FF_MIN_SS +#define _MAX_LFN FF_MAX_LFN +/*--- End of configuration options ---*/ diff --git a/components/3rd_party/omv/omv/ports/linux/oofatfs/ffunicode.c b/components/3rd_party/omv/omv/ports/linux/oofatfs/ffunicode.c new file mode 100644 index 00000000..4153f913 --- /dev/null +++ b/components/3rd_party/omv/omv/ports/linux/oofatfs/ffunicode.c @@ -0,0 +1,627 @@ +/*------------------------------------------------------------------------*/ +/* Unicode handling functions for FatFs R0.13c */ +/*------------------------------------------------------------------------*/ +/* This module will occupy a huge memory in the .const section when the / +/ FatFs is configured for LFN with DBCS. If the system has any Unicode / +/ utilitiy for the code conversion, this module should be modified to use / +/ that function to avoid silly memory consumption. / +/-------------------------------------------------------------------------*/ +/* +/ Copyright (C) 2018, ChaN, all right reserved. +/ +/ FatFs module is an open source software. Redistribution and use of FatFs in +/ source and binary forms, with or without modification, are permitted provided +/ that the following condition is met: +/ +/ 1. Redistributions of source code must retain the above copyright notice, +/ this condition and the following disclaimer. +/ +/ This software is provided by the copyright holder and contributors "AS IS" +/ and any warranties related to this software are DISCLAIMED. +/ The copyright owner or contributors be NOT LIABLE for any damages caused +/ by use of this software. +*/ + + +#include "ff.h" + +#if FF_USE_LFN /* This module will be blanked at non-LFN configuration */ + +#if FF_DEFINED != 86604 /* Revision ID */ +#error Wrong include file (ff.h). +#endif + +#define MERGE2(a, b) a ## b +#define CVTBL(tbl, cp) MERGE2(tbl, cp) + + +/*------------------------------------------------------------------------*/ +/* Code Conversion Tables */ +/*------------------------------------------------------------------------*/ + +#if FF_CODE_PAGE == 437 || FF_CODE_PAGE == 0 +static const WCHAR uc437[] = { /* CP437(U.S.) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 720 || FF_CODE_PAGE == 0 +static const WCHAR uc720[] = { /* CP720(Arabic) to Unicode conversion table */ + 0x0000, 0x0000, 0x00E9, 0x00E2, 0x0000, 0x00E0, 0x0000, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0651, 0x0652, 0x00F4, 0x00A4, 0x0640, 0x00FB, 0x00F9, 0x0621, 0x0622, 0x0623, 0x0624, 0x00A3, 0x0625, 0x0626, 0x0627, + 0x0628, 0x0629, 0x062A, 0x062B, 0x062C, 0x062D, 0x062E, 0x062F, 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x0636, 0x0637, 0x0638, 0x0639, 0x063A, 0x0641, 0x00B5, 0x0642, 0x0643, 0x0644, 0x0645, 0x0646, 0x0647, 0x0648, 0x0649, 0x064A, + 0x2261, 0x064B, 0x064C, 0x064D, 0x064E, 0x064F, 0x0650, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 737 || FF_CODE_PAGE == 0 +static const WCHAR uc737[] = { /* CP737(Greek) to Unicode conversion table */ + 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F, 0x03A0, + 0x03A1, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, 0x03B8, + 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, 0x03C0, 0x03C1, 0x03C3, 0x03C2, 0x03C4, 0x03C5, 0x03C6, 0x03C7, 0x03C8, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03C9, 0x03AC, 0x03AD, 0x03AE, 0x03CA, 0x03AF, 0x03CC, 0x03CD, 0x03CB, 0x03CE, 0x0386, 0x0388, 0x0389, 0x038A, 0x038C, 0x038E, + 0x038F, 0x00B1, 0x2265, 0x2264, 0x03AA, 0x03AB, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 771 || FF_CODE_PAGE == 0 +static const WCHAR uc771[] = { /* CP771(KBL) to Unicode conversion table */ + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x2558, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x0104, 0x0105, 0x010C, 0x010D, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F, + 0x0118, 0x0119, 0x0116, 0x0117, 0x012E, 0x012F, 0x0160, 0x0161, 0x0172, 0x0173, 0x016A, 0x016B, 0x017D, 0x017E, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 775 || FF_CODE_PAGE == 0 +static const WCHAR uc775[] = { /* CP775(Baltic) to Unicode conversion table */ + 0x0106, 0x00FC, 0x00E9, 0x0101, 0x00E4, 0x0123, 0x00E5, 0x0107, 0x0142, 0x0113, 0x0156, 0x0157, 0x012B, 0x0179, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x014D, 0x00F6, 0x0122, 0x00A2, 0x015A, 0x015B, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x00D7, 0x00A4, + 0x0100, 0x012A, 0x00F3, 0x017B, 0x017C, 0x017A, 0x201D, 0x00A6, 0x00A9, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x0141, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x0104, 0x010C, 0x0118, 0x0116, 0x2563, 0x2551, 0x2557, 0x255D, 0x012E, 0x0160, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x0172, 0x016A, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x017D, + 0x0105, 0x010D, 0x0119, 0x0117, 0x012F, 0x0161, 0x0173, 0x016B, 0x017E, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x00D3, 0x00DF, 0x014C, 0x0143, 0x00F5, 0x00D5, 0x00B5, 0x0144, 0x0136, 0x0137, 0x013B, 0x013C, 0x0146, 0x0112, 0x0145, 0x2019, + 0x00AD, 0x00B1, 0x201C, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x201E, 0x00B0, 0x2219, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 850 || FF_CODE_PAGE == 0 +static const WCHAR uc850[] = { /* CP850(Latin 1) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x00D7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x00C0, 0x00A9, 0x2563, 0x2551, 0x2557, 0x255D, 0x00A2, 0x00A5, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x00E3, 0x00C3, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, + 0x00F0, 0x00D0, 0x00CA, 0x00CB, 0x00C8, 0x0131, 0x00CD, 0x00CE, 0x00CF, 0x2518, 0x250C, 0x2588, 0x2584, 0x00A6, 0x00CC, 0x2580, + 0x00D3, 0x00DF, 0x00D4, 0x00D2, 0x00F5, 0x00D5, 0x00B5, 0x00FE, 0x00DE, 0x00DA, 0x00DB, 0x00D9, 0x00FD, 0x00DD, 0x00AF, 0x00B4, + 0x00AD, 0x00B1, 0x2017, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x00B8, 0x00B0, 0x00A8, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 852 || FF_CODE_PAGE == 0 +static const WCHAR uc852[] = { /* CP852(Latin 2) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x016F, 0x0107, 0x00E7, 0x0142, 0x00EB, 0x0150, 0x0151, 0x00EE, 0x0179, 0x00C4, 0x0106, + 0x00C9, 0x0139, 0x013A, 0x00F4, 0x00F6, 0x013D, 0x013E, 0x015A, 0x015B, 0x00D6, 0x00DC, 0x0164, 0x0165, 0x0141, 0x00D7, 0x010D, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x0104, 0x0105, 0x017D, 0x017E, 0x0118, 0x0119, 0x00AC, 0x017A, 0x010C, 0x015F, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x011A, 0x015E, 0x2563, 0x2551, 0x2557, 0x255D, 0x017B, 0x017C, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x0102, 0x0103, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, + 0x0111, 0x0110, 0x010E, 0x00CB, 0x010F, 0x0147, 0x00CD, 0x00CE, 0x011B, 0x2518, 0x250C, 0x2588, 0x2584, 0x0162, 0x016E, 0x2580, + 0x00D3, 0x00DF, 0x00D4, 0x0143, 0x0144, 0x0148, 0x0160, 0x0161, 0x0154, 0x00DA, 0x0155, 0x0170, 0x00FD, 0x00DD, 0x0163, 0x00B4, + 0x00AD, 0x02DD, 0x02DB, 0x02C7, 0x02D8, 0x00A7, 0x00F7, 0x00B8, 0x00B0, 0x00A8, 0x02D9, 0x0171, 0x0158, 0x0159, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 855 || FF_CODE_PAGE == 0 +static const WCHAR uc855[] = { /* CP855(Cyrillic) to Unicode conversion table */ + 0x0452, 0x0402, 0x0453, 0x0403, 0x0451, 0x0401, 0x0454, 0x0404, 0x0455, 0x0405, 0x0456, 0x0406, 0x0457, 0x0407, 0x0458, 0x0408, + 0x0459, 0x0409, 0x045A, 0x040A, 0x045B, 0x040B, 0x045C, 0x040C, 0x045E, 0x040E, 0x045F, 0x040F, 0x044E, 0x042E, 0x044A, 0x042A, + 0x0430, 0x0410, 0x0431, 0x0411, 0x0446, 0x0426, 0x0434, 0x0414, 0x0435, 0x0415, 0x0444, 0x0424, 0x0433, 0x0413, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x0445, 0x0425, 0x0438, 0x0418, 0x2563, 0x2551, 0x2557, 0x255D, 0x0439, 0x0419, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x043A, 0x041A, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, + 0x043B, 0x041B, 0x043C, 0x041C, 0x043D, 0x041D, 0x043E, 0x041E, 0x043F, 0x2518, 0x250C, 0x2588, 0x2584, 0x041F, 0x044F, 0x2580, + 0x042F, 0x0440, 0x0420, 0x0441, 0x0421, 0x0442, 0x0422, 0x0443, 0x0423, 0x0436, 0x0416, 0x0432, 0x0412, 0x044C, 0x042C, 0x2116, + 0x00AD, 0x044B, 0x042B, 0x0437, 0x0417, 0x0448, 0x0428, 0x044D, 0x042D, 0x0449, 0x0429, 0x0447, 0x0427, 0x00A7, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 857 || FF_CODE_PAGE == 0 +static const WCHAR uc857[] = { /* CP857(Turkish) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x0131, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x0130, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x015E, 0x015F, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x011E, 0x011F, 0x00BF, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x00C0, 0x00A9, 0x2563, 0x2551, 0x2557, 0x255D, 0x00A2, 0x00A5, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x00E3, 0x00C3, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, + 0x00BA, 0x00AA, 0x00CA, 0x00CB, 0x00C8, 0x0000, 0x00CD, 0x00CE, 0x00CF, 0x2518, 0x250C, 0x2588, 0x2584, 0x00A6, 0x00CC, 0x2580, + 0x00D3, 0x00DF, 0x00D4, 0x00D2, 0x00F5, 0x00D5, 0x00B5, 0x0000, 0x00D7, 0x00DA, 0x00DB, 0x00D9, 0x00EC, 0x00FF, 0x00AF, 0x00B4, + 0x00AD, 0x00B1, 0x0000, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x00B8, 0x00B0, 0x00A8, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 860 || FF_CODE_PAGE == 0 +static const WCHAR uc860[] = { /* CP860(Portuguese) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E3, 0x00E0, 0x00C1, 0x00E7, 0x00EA, 0x00CA, 0x00E8, 0x00CD, 0x00D4, 0x00EC, 0x00C3, 0x00C2, + 0x00C9, 0x00C0, 0x00C8, 0x00F4, 0x00F5, 0x00F2, 0x00DA, 0x00F9, 0x00CC, 0x00D5, 0x00DC, 0x00A2, 0x00A3, 0x00D9, 0x20A7, 0x00D3, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x00D2, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x2558, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 861 || FF_CODE_PAGE == 0 +static const WCHAR uc861[] = { /* CP861(Icelandic) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E6, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00D0, 0x00F0, 0x00DE, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00FE, 0x00FB, 0x00DD, 0x00FD, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x20A7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00C1, 0x00CD, 0x00D3, 0x00DA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 862 || FF_CODE_PAGE == 0 +static const WCHAR uc862[] = { /* CP862(Hebrew) to Unicode conversion table */ + 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8, 0x05D9, 0x05DA, 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF, + 0x05E0, 0x05E1, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6, 0x05E7, 0x05E8, 0x05E9, 0x05EA, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 863 || FF_CODE_PAGE == 0 +static const WCHAR uc863[] = { /* CP863(Canadian French) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00C2, 0x00E0, 0x00B6, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x2017, 0x00C0, + 0x00C9, 0x00C8, 0x00CA, 0x00F4, 0x00CB, 0x00CF, 0x00FB, 0x00F9, 0x00A4, 0x00D4, 0x00DC, 0x00A2, 0x00A3, 0x00D9, 0x00DB, 0x0192, + 0x00A6, 0x00B4, 0x00F3, 0x00FA, 0x00A8, 0x00BB, 0x00B3, 0x00AF, 0x00CE, 0x3210, 0x00AC, 0x00BD, 0x00BC, 0x00BE, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2219, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 864 || FF_CODE_PAGE == 0 +static const WCHAR uc864[] = { /* CP864(Arabic) to Unicode conversion table */ + 0x00B0, 0x00B7, 0x2219, 0x221A, 0x2592, 0x2500, 0x2502, 0x253C, 0x2524, 0x252C, 0x251C, 0x2534, 0x2510, 0x250C, 0x2514, 0x2518, + 0x03B2, 0x221E, 0x03C6, 0x00B1, 0x00BD, 0x00BC, 0x2248, 0x00AB, 0x00BB, 0xFEF7, 0xFEF8, 0x0000, 0x0000, 0xFEFB, 0xFEFC, 0x0000, + 0x00A0, 0x00AD, 0xFE82, 0x00A3, 0x00A4, 0xFE84, 0x0000, 0x20AC, 0xFE8E, 0xFE8F, 0xFE95, 0xFE99, 0x060C, 0xFE9D, 0xFEA1, 0xFEA5, + 0x0660, 0x0661, 0x0662, 0x0663, 0x0664, 0x0665, 0x0666, 0x0667, 0x0668, 0x0669, 0xFED1, 0x061B, 0xFEB1, 0xFEB5, 0xFEB9, 0x061F, + 0x00A2, 0xFE80, 0xFE81, 0xFE83, 0xFE85, 0xFECA, 0xFE8B, 0xFE8D, 0xFE91, 0xFE93, 0xFE97, 0xFE9B, 0xFE9F, 0xFEA3, 0xFEA7, 0xFEA9, + 0xFEAB, 0xFEAD, 0xFEAF, 0xFEB3, 0xFEB7, 0xFEBB, 0xFEBF, 0xFEC1, 0xFEC5, 0xFECB, 0xFECF, 0x00A6, 0x00AC, 0x00F7, 0x00D7, 0xFEC9, + 0x0640, 0xFED3, 0xFED7, 0xFEDB, 0xFEDF, 0xFEE3, 0xFEE7, 0xFEEB, 0xFEED, 0xFEEF, 0xFEF3, 0xFEBD, 0xFECC, 0xFECE, 0xFECD, 0xFEE1, + 0xFE7D, 0x0651, 0xFEE5, 0xFEE9, 0xFEEC, 0xFEF0, 0xFEF2, 0xFED0, 0xFED5, 0xFEF5, 0xFEF6, 0xFEDD, 0xFED9, 0xFEF1, 0x25A0, 0x0000 +}; +#endif +#if FF_CODE_PAGE == 865 || FF_CODE_PAGE == 0 +static const WCHAR uc865[] = { /* CP865(Nordic) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, + 0x00C5, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x20A7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00A4, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x2558, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 866 || FF_CODE_PAGE == 0 +static const WCHAR uc866[] = { /* CP866(Russian) to Unicode conversion table */ + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F, + 0x0401, 0x0451, 0x0404, 0x0454, 0x0407, 0x0457, 0x040E, 0x045E, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x2116, 0x00A4, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 869 || FF_CODE_PAGE == 0 +static const WCHAR uc869[] = { /* CP869(Greek 2) to Unicode conversion table */ + 0x00B7, 0x00B7, 0x00B7, 0x00B7, 0x00B7, 0x00B7, 0x0386, 0x00B7, 0x00B7, 0x00AC, 0x00A6, 0x2018, 0x2019, 0x0388, 0x2015, 0x0389, + 0x038A, 0x03AA, 0x038C, 0x00B7, 0x00B7, 0x038E, 0x03AB, 0x00A9, 0x038F, 0x00B2, 0x00B3, 0x03AC, 0x00A3, 0x03AD, 0x03AE, 0x03AF, + 0x03CA, 0x0390, 0x03CC, 0x03CD, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x00BD, 0x0398, 0x0399, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x039A, 0x039B, 0x039C, 0x039D, 0x2563, 0x2551, 0x2557, 0x255D, 0x039E, 0x039F, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x0A30, 0x03A1, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x03A3, + 0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, 0x03B1, 0x03B2, 0x03B3, 0x2518, 0x250C, 0x2588, 0x2584, 0x03B4, 0x03B5, 0x2580, + 0x03B6, 0x03B7, 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, 0x03C0, 0x03C1, 0x03C3, 0x03C2, 0x03C4, 0x0384, + 0x00AD, 0x00B1, 0x03C5, 0x03C6, 0x03C7, 0x00A7, 0x03C8, 0x0385, 0x00B0, 0x00A8, 0x03C9, 0x03CB, 0x03B0, 0x03CE, 0x25A0, 0x00A0 +}; +#endif + + + + +/*------------------------------------------------------------------------*/ +/* OEM <==> Unicode conversions for static code page configuration */ +/* SBCS fixed code page */ +/*------------------------------------------------------------------------*/ + +#if FF_CODE_PAGE != 0 && FF_CODE_PAGE < 900 +WCHAR ff_uni2oem ( /* Returns OEM code character, zero on error */ + DWORD uni, /* UTF-16 encoded character to be converted */ + WORD cp /* Code page for the conversion */ +) +{ + WCHAR c = 0; + const WCHAR *p = CVTBL(uc, FF_CODE_PAGE); + + + if (uni < 0x80) { /* ASCII? */ + c = (WCHAR)uni; + + } else { /* Non-ASCII */ + if (uni < 0x10000 && cp == FF_CODE_PAGE) { /* Is it in BMP and valid code page? */ + for (c = 0; c < 0x80 && uni != p[c]; c++) ; + c = (c + 0x80) & 0xFF; + } + } + + return c; +} + +WCHAR ff_oem2uni ( /* Returns Unicode character, zero on error */ + WCHAR oem, /* OEM code to be converted */ + WORD cp /* Code page for the conversion */ +) +{ + WCHAR c = 0; + const WCHAR *p = CVTBL(uc, FF_CODE_PAGE); + + + if (oem < 0x80) { /* ASCII? */ + c = oem; + + } else { /* Extended char */ + if (cp == FF_CODE_PAGE) { /* Is it a valid code page? */ + if (oem < 0x100) c = p[oem - 0x80]; + } + } + + return c; +} + +#endif + + + +/*------------------------------------------------------------------------*/ +/* OEM <==> Unicode conversions for static code page configuration */ +/* DBCS fixed code page */ +/*------------------------------------------------------------------------*/ + +#if FF_CODE_PAGE >= 900 +WCHAR ff_uni2oem ( /* Returns OEM code character, zero on error */ + DWORD uni, /* UTF-16 encoded character to be converted */ + WORD cp /* Code page for the conversion */ +) +{ + const WCHAR *p; + WCHAR c = 0, uc; + UINT i = 0, n, li, hi; + + + if (uni < 0x80) { /* ASCII? */ + c = (WCHAR)uni; + + } else { /* Non-ASCII */ + if (uni < 0x10000 && cp == FF_CODE_PAGE) { /* Is it in BMP and valid code page? */ + uc = (WCHAR)uni; + p = CVTBL(uni2oem, FF_CODE_PAGE); + hi = sizeof CVTBL(uni2oem, FF_CODE_PAGE) / 4 - 1; + li = 0; + for (n = 16; n; n--) { + i = li + (hi - li) / 2; + if (uc == p[i * 2]) break; + if (uc > p[i * 2]) { + li = i; + } else { + hi = i; + } + } + if (n != 0) c = p[i * 2 + 1]; + } + } + + return c; +} + + +WCHAR ff_oem2uni ( /* Returns Unicode character, zero on error */ + WCHAR oem, /* OEM code to be converted */ + WORD cp /* Code page for the conversion */ +) +{ + const WCHAR *p; + WCHAR c = 0; + UINT i = 0, n, li, hi; + + + if (oem < 0x80) { /* ASCII? */ + c = oem; + + } else { /* Extended char */ + if (cp == FF_CODE_PAGE) { /* Is it valid code page? */ + p = CVTBL(oem2uni, FF_CODE_PAGE); + hi = sizeof CVTBL(oem2uni, FF_CODE_PAGE) / 4 - 1; + li = 0; + for (n = 16; n; n--) { + i = li + (hi - li) / 2; + if (oem == p[i * 2]) break; + if (oem > p[i * 2]) { + li = i; + } else { + hi = i; + } + } + if (n != 0) c = p[i * 2 + 1]; + } + } + + return c; +} +#endif + + + +/*------------------------------------------------------------------------*/ +/* OEM <==> Unicode conversions for dynamic code page configuration */ +/*------------------------------------------------------------------------*/ + +#if FF_CODE_PAGE == 0 + +static const WORD cp_code[] = { 437, 720, 737, 771, 775, 850, 852, 855, 857, 860, 861, 862, 863, 864, 865, 866, 869, 0}; +static const WCHAR* const cp_table[] = {uc437, uc720, uc737, uc771, uc775, uc850, uc852, uc855, uc857, uc860, uc861, uc862, uc863, uc864, uc865, uc866, uc869, 0}; + + +WCHAR ff_uni2oem ( /* Returns OEM code character, zero on error */ + DWORD uni, /* UTF-16 encoded character to be converted */ + WORD cp /* Code page for the conversion */ +) +{ + const WCHAR *p; + WCHAR c = 0, uc; + UINT i, n, li, hi; + + + if (uni < 0x80) { /* ASCII? */ + c = (WCHAR)uni; + + } else { /* Non-ASCII */ + if (uni < 0x10000) { /* Is it in BMP? */ + uc = (WCHAR)uni; + p = 0; + if (cp < 900) { /* SBCS */ + for (i = 0; cp_code[i] != 0 && cp_code[i] != cp; i++) ; /* Get conversion table */ + p = cp_table[i]; + if (p) { /* Is it valid code page ? */ + for (c = 0; c < 0x80 && uc != p[c]; c++) ; /* Find OEM code in the table */ + c = (c + 0x80) & 0xFF; + } + } else { /* DBCS */ + switch (cp) { /* Get conversion table */ + case 932 : p = uni2oem932; hi = sizeof uni2oem932 / 4 - 1; break; + case 936 : p = uni2oem936; hi = sizeof uni2oem936 / 4 - 1; break; + case 949 : p = uni2oem949; hi = sizeof uni2oem949 / 4 - 1; break; + case 950 : p = uni2oem950; hi = sizeof uni2oem950 / 4 - 1; break; + } + if (p) { /* Is it valid code page? */ + li = 0; + for (n = 16; n; n--) { /* Find OEM code */ + i = li + (hi - li) / 2; + if (uc == p[i * 2]) break; + if (uc > p[i * 2]) { + li = i; + } else { + hi = i; + } + } + if (n != 0) c = p[i * 2 + 1]; + } + } + } + } + + return c; +} + + +WCHAR ff_oem2uni ( /* Returns Unicode character, zero on error */ + WCHAR oem, /* OEM code to be converted (DBC if >=0x100) */ + WORD cp /* Code page for the conversion */ +) +{ + const WCHAR *p; + WCHAR c = 0; + UINT i, n, li, hi; + + + if (oem < 0x80) { /* ASCII? */ + c = oem; + + } else { /* Extended char */ + p = 0; + if (cp < 900) { /* SBCS */ + for (i = 0; cp_code[i] != 0 && cp_code[i] != cp; i++) ; /* Get table */ + p = cp_table[i]; + if (p) { /* Is it a valid CP ? */ + if (oem < 0x100) c = p[oem - 0x80]; + } + } else { /* DBCS */ + switch (cp) { + case 932 : p = oem2uni932; hi = sizeof oem2uni932 / 4 - 1; break; + case 936 : p = oem2uni936; hi = sizeof oem2uni936 / 4 - 1; break; + case 949 : p = oem2uni949; hi = sizeof oem2uni949 / 4 - 1; break; + case 950 : p = oem2uni950; hi = sizeof oem2uni950 / 4 - 1; break; + } + if (p) { + li = 0; + for (n = 16; n; n--) { + i = li + (hi - li) / 2; + if (oem == p[i * 2]) break; + if (oem > p[i * 2]) { + li = i; + } else { + hi = i; + } + } + if (n != 0) c = p[i * 2 + 1]; + } + } + } + + return c; +} +#endif + + + +/*------------------------------------------------------------------------*/ +/* Unicode up-case conversion */ +/*------------------------------------------------------------------------*/ + +DWORD ff_wtoupper ( /* Returns up-converted code point */ + DWORD uni /* Unicode code point to be up-converted */ +) +{ + const WORD *p; + WORD uc, bc, nc, cmd; + static const WORD cvt1[] = { /* Compressed up conversion table for U+0000 - U+0FFF */ + /* Basic Latin */ + 0x0061,0x031A, + /* Latin-1 Supplement */ + 0x00E0,0x0317, + 0x00F8,0x0307, + 0x00FF,0x0001,0x0178, + /* Latin Extended-A */ + 0x0100,0x0130, + 0x0132,0x0106, + 0x0139,0x0110, + 0x014A,0x012E, + 0x0179,0x0106, + /* Latin Extended-B */ + 0x0180,0x004D,0x0243,0x0181,0x0182,0x0182,0x0184,0x0184,0x0186,0x0187,0x0187,0x0189,0x018A,0x018B,0x018B,0x018D,0x018E,0x018F,0x0190,0x0191,0x0191,0x0193,0x0194,0x01F6,0x0196,0x0197,0x0198,0x0198,0x023D,0x019B,0x019C,0x019D,0x0220,0x019F,0x01A0,0x01A0,0x01A2,0x01A2,0x01A4,0x01A4,0x01A6,0x01A7,0x01A7,0x01A9,0x01AA,0x01AB,0x01AC,0x01AC,0x01AE,0x01AF,0x01AF,0x01B1,0x01B2,0x01B3,0x01B3,0x01B5,0x01B5,0x01B7,0x01B8,0x01B8,0x01BA,0x01BB,0x01BC,0x01BC,0x01BE,0x01F7,0x01C0,0x01C1,0x01C2,0x01C3,0x01C4,0x01C5,0x01C4,0x01C7,0x01C8,0x01C7,0x01CA,0x01CB,0x01CA, + 0x01CD,0x0110, + 0x01DD,0x0001,0x018E, + 0x01DE,0x0112, + 0x01F3,0x0003,0x01F1,0x01F4,0x01F4, + 0x01F8,0x0128, + 0x0222,0x0112, + 0x023A,0x0009,0x2C65,0x023B,0x023B,0x023D,0x2C66,0x023F,0x0240,0x0241,0x0241, + 0x0246,0x010A, + /* IPA Extensions */ + 0x0253,0x0040,0x0181,0x0186,0x0255,0x0189,0x018A,0x0258,0x018F,0x025A,0x0190,0x025C,0x025D,0x025E,0x025F,0x0193,0x0261,0x0262,0x0194,0x0264,0x0265,0x0266,0x0267,0x0197,0x0196,0x026A,0x2C62,0x026C,0x026D,0x026E,0x019C,0x0270,0x0271,0x019D,0x0273,0x0274,0x019F,0x0276,0x0277,0x0278,0x0279,0x027A,0x027B,0x027C,0x2C64,0x027E,0x027F,0x01A6,0x0281,0x0282,0x01A9,0x0284,0x0285,0x0286,0x0287,0x01AE,0x0244,0x01B1,0x01B2,0x0245,0x028D,0x028E,0x028F,0x0290,0x0291,0x01B7, + /* Greek, Coptic */ + 0x037B,0x0003,0x03FD,0x03FE,0x03FF, + 0x03AC,0x0004,0x0386,0x0388,0x0389,0x038A, + 0x03B1,0x0311, + 0x03C2,0x0002,0x03A3,0x03A3, + 0x03C4,0x0308, + 0x03CC,0x0003,0x038C,0x038E,0x038F, + 0x03D8,0x0118, + 0x03F2,0x000A,0x03F9,0x03F3,0x03F4,0x03F5,0x03F6,0x03F7,0x03F7,0x03F9,0x03FA,0x03FA, + /* Cyrillic */ + 0x0430,0x0320, + 0x0450,0x0710, + 0x0460,0x0122, + 0x048A,0x0136, + 0x04C1,0x010E, + 0x04CF,0x0001,0x04C0, + 0x04D0,0x0144, + /* Armenian */ + 0x0561,0x0426, + + 0x0000 /* EOT */ + }; + static const WORD cvt2[] = { /* Compressed up conversion table for U+1000 - U+FFFF */ + /* Phonetic Extensions */ + 0x1D7D,0x0001,0x2C63, + /* Latin Extended Additional */ + 0x1E00,0x0196, + 0x1EA0,0x015A, + /* Greek Extended */ + 0x1F00,0x0608, + 0x1F10,0x0606, + 0x1F20,0x0608, + 0x1F30,0x0608, + 0x1F40,0x0606, + 0x1F51,0x0007,0x1F59,0x1F52,0x1F5B,0x1F54,0x1F5D,0x1F56,0x1F5F, + 0x1F60,0x0608, + 0x1F70,0x000E,0x1FBA,0x1FBB,0x1FC8,0x1FC9,0x1FCA,0x1FCB,0x1FDA,0x1FDB,0x1FF8,0x1FF9,0x1FEA,0x1FEB,0x1FFA,0x1FFB, + 0x1F80,0x0608, + 0x1F90,0x0608, + 0x1FA0,0x0608, + 0x1FB0,0x0004,0x1FB8,0x1FB9,0x1FB2,0x1FBC, + 0x1FCC,0x0001,0x1FC3, + 0x1FD0,0x0602, + 0x1FE0,0x0602, + 0x1FE5,0x0001,0x1FEC, + 0x1FF3,0x0001,0x1FFC, + /* Letterlike Symbols */ + 0x214E,0x0001,0x2132, + /* Number forms */ + 0x2170,0x0210, + 0x2184,0x0001,0x2183, + /* Enclosed Alphanumerics */ + 0x24D0,0x051A, + 0x2C30,0x042F, + /* Latin Extended-C */ + 0x2C60,0x0102, + 0x2C67,0x0106, 0x2C75,0x0102, + /* Coptic */ + 0x2C80,0x0164, + /* Georgian Supplement */ + 0x2D00,0x0826, + /* Full-width */ + 0xFF41,0x031A, + + 0x0000 /* EOT */ + }; + + + if (uni < 0x10000) { /* Is it in BMP? */ + uc = (WORD)uni; + p = uc < 0x1000 ? cvt1 : cvt2; + for (;;) { + bc = *p++; /* Get the block base */ + if (bc == 0 || uc < bc) break; /* Not matched? */ + nc = *p++; cmd = nc >> 8; nc &= 0xFF; /* Get processing command and block size */ + if (uc < bc + nc) { /* In the block? */ + switch (cmd) { + case 0: uc = p[uc - bc]; break; /* Table conversion */ + case 1: uc -= (uc - bc) & 1; break; /* Case pairs */ + case 2: uc -= 16; break; /* Shift -16 */ + case 3: uc -= 32; break; /* Shift -32 */ + case 4: uc -= 48; break; /* Shift -48 */ + case 5: uc -= 26; break; /* Shift -26 */ + case 6: uc += 8; break; /* Shift +8 */ + case 7: uc -= 80; break; /* Shift -80 */ + case 8: uc -= 0x1C60; break; /* Shift -0x1C60 */ + } + break; + } + if (cmd == 0) p += nc; /* Skip table if needed */ + } + uni = uc; + } + + return uni; +} + + +#endif /* #if FF_USE_LFN */ diff --git a/components/3rd_party/omv/omv/ports/linux/py/gc.h b/components/3rd_party/omv/omv/ports/linux/py/gc.h new file mode 100644 index 00000000..f49075c0 --- /dev/null +++ b/components/3rd_party/omv/omv/ports/linux/py/gc.h @@ -0,0 +1,4 @@ +#ifndef __GC_H +#define __GC_H + +#endif // __GC_H \ No newline at end of file diff --git a/components/3rd_party/omv/omv/ports/linux/py/mphal.h b/components/3rd_party/omv/omv/ports/linux/py/mphal.h new file mode 100644 index 00000000..2500b62b --- /dev/null +++ b/components/3rd_party/omv/omv/ports/linux/py/mphal.h @@ -0,0 +1,7 @@ +#ifndef __MPHAL_H +#define __MPHAL_H + +#include "mpconfig.h" +#include "obj.h" + +#endif // __MPHAL_H \ No newline at end of file diff --git a/components/3rd_party/omv/omv/ports/linux/py/mpprint.c b/components/3rd_party/omv/omv/ports/linux/py/mpprint.c new file mode 100644 index 00000000..37ac3fd1 --- /dev/null +++ b/components/3rd_party/omv/omv/ports/linux/py/mpprint.c @@ -0,0 +1,57 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2015 Damien P. George + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include "mpprint.h" + +int mp_print_str(const mp_print_t *print, const char *str) { + return 0; +} + +int mp_print_strn(const mp_print_t *print, const char *str, size_t len, int flags, char fill, int width) { + return 0; +} + +#if MICROPY_PY_BUILTINS_FLOAT +int mp_print_float(const mp_print_t *print, mp_float_t f, char fmt, int flags, char fill, int width, int prec) { + return 0; +} +#endif + +int mp_printf(const mp_print_t *print, const char *fmt, ...) { + return 0; +} + +#ifdef va_start +int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args) { + return 0; +} +#endif \ No newline at end of file diff --git a/components/3rd_party/omv/omv/ports/linux/py/mpprint.h b/components/3rd_party/omv/omv/ports/linux/py/mpprint.h new file mode 100644 index 00000000..ee3cc6d5 --- /dev/null +++ b/components/3rd_party/omv/omv/ports/linux/py/mpprint.h @@ -0,0 +1,80 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * + * 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. + */ +#ifndef MICROPY_INCLUDED_PY_MPPRINT_H +#define MICROPY_INCLUDED_PY_MPPRINT_H + +#define PF_FLAG_LEFT_ADJUST (0x001) +#define PF_FLAG_SHOW_SIGN (0x002) +#define PF_FLAG_SPACE_SIGN (0x004) +#define PF_FLAG_NO_TRAILZ (0x008) +#define PF_FLAG_SHOW_PREFIX (0x010) +#define PF_FLAG_SHOW_COMMA (0x020) +#define PF_FLAG_PAD_AFTER_SIGN (0x040) +#define PF_FLAG_CENTER_ADJUST (0x080) +#define PF_FLAG_ADD_PERCENT (0x100) +#define PF_FLAG_SHOW_OCTAL_LETTER (0x200) + +#if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES +#define MP_PYTHON_PRINTER &mp_sys_stdout_print +#else +#define MP_PYTHON_PRINTER &mp_plat_print +#endif + +typedef void (*mp_print_strn_t)(void *data, const char *str, size_t len); + +typedef struct _mp_print_t { + void *data; + mp_print_strn_t print_strn; +} mp_print_t; + +typedef struct _mp_print_ext_t { + mp_print_t base; + const char *item_separator; + const char *key_separator; +} mp_print_ext_t; + +#define MP_PRINT_GET_EXT(print) ((mp_print_ext_t *)print) + +// All (non-debug) prints go through one of the two interfaces below. +// 1) Wrapper for platform print function, which wraps MP_PLAT_PRINT_STRN. +extern const mp_print_t mp_plat_print; +#if MICROPY_PY_IO && MICROPY_PY_SYS_STDFILES +// 2) Wrapper for printing to sys.stdout. +extern const mp_print_t mp_sys_stdout_print; +#endif + +int mp_print_str(const mp_print_t *print, const char *str); +int mp_print_strn(const mp_print_t *print, const char *str, size_t len, int flags, char fill, int width); +#if MICROPY_PY_BUILTINS_FLOAT +int mp_print_float(const mp_print_t *print, mp_float_t f, char fmt, int flags, char fill, int width, int prec); +#endif + +int mp_printf(const mp_print_t *print, const char *fmt, ...); +#ifdef va_start +int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args); +#endif + +#endif // MICROPY_INCLUDED_PY_MPPRINT_H diff --git a/components/3rd_party/omv/omv/ports/linux/py/nlr.h b/components/3rd_party/omv/omv/ports/linux/py/nlr.h new file mode 100644 index 00000000..e69de29b diff --git a/components/3rd_party/omv/omv/ports/linux/py/obj.c b/components/3rd_party/omv/omv/ports/linux/py/obj.c new file mode 100644 index 00000000..0d2f1e88 --- /dev/null +++ b/components/3rd_party/omv/omv/ports/linux/py/obj.c @@ -0,0 +1,4 @@ +#include "obj.h" + +int mp_type_OSError = 1; +int mp_type_MemoryError = 2; diff --git a/components/3rd_party/omv/omv/ports/linux/py/obj.h b/components/3rd_party/omv/omv/ports/linux/py/obj.h new file mode 100644 index 00000000..ee339e3d --- /dev/null +++ b/components/3rd_party/omv/omv/ports/linux/py/obj.h @@ -0,0 +1,7 @@ +#ifndef __OBJ_H +#define __OBJ_H + +extern int mp_type_OSError; +extern int mp_type_MemoryError; + +#endif // __OBJ_H \ No newline at end of file diff --git a/components/3rd_party/omv/omv/ports/linux/py/runtime.c b/components/3rd_party/omv/omv/ports/linux/py/runtime.c new file mode 100644 index 00000000..c369fa59 --- /dev/null +++ b/components/3rd_party/omv/omv/ports/linux/py/runtime.c @@ -0,0 +1,24 @@ +#include "runtime.h" +#include "stdlib.h" +#include "stdio.h" + +void mp_raise_msg(const int *exc_type, const char * msg) { + printf("Exception raised: %s\n", msg); + exit(1); +} + +void mp_raise_msg_varg(const int *exc_type, const char * fmt, ...) { + printf("Exception raised\n"); + exit(1); +} + +void *mp_obj_new_exception_msg(const void *exc_type, const char *msg) +{ + return NULL; +} + +void nlr_jump(void *val) +{ + printf("nlr_jump\n"); + while (1); +} \ No newline at end of file diff --git a/components/3rd_party/omv/omv/ports/linux/py/runtime.h b/components/3rd_party/omv/omv/ports/linux/py/runtime.h new file mode 100644 index 00000000..ebcae442 --- /dev/null +++ b/components/3rd_party/omv/omv/ports/linux/py/runtime.h @@ -0,0 +1,21 @@ +#ifndef __RUNTIME_H +#define __RUNTIME_H + +#include +#include + +#if __cplusplus +extern "C" { +#endif + +#define MP_ERROR_TEXT(s) (s) +#define MP_STACK_CHECK() +void mp_raise_msg(const int *exc_type, const char * msg); +void mp_raise_msg_varg(const int *exc_type, const char * fmt, ...); +void *mp_obj_new_exception_msg(const void *exc_type, const char *msg); + +#if __cplusplus +} +#endif + +#endif // __RUNTIME_H \ No newline at end of file diff --git a/components/3rd_party/omv/omv/ports/linux/py/stackctrl.h b/components/3rd_party/omv/omv/ports/linux/py/stackctrl.h new file mode 100644 index 00000000..e69de29b diff --git a/components/3rd_party/omv/omv/sensors/frogeye2020.c b/components/3rd_party/omv/omv/sensors/frogeye2020.c new file mode 100644 index 00000000..601d1a6b --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/frogeye2020.c @@ -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. + * + * FrogEye2020 driver. + */ +#include "omv_boardconfig.h" +#if (OMV_ENABLE_FROGEYE2020 == 1) + +#include "sensor.h" + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) { + return (pixformat == PIXFORMAT_GRAYSCALE) ? 0 : -1; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) { + return (framesize == FRAMESIZE_QVGA) ? 0 : -1; +} + +int frogeye2020_init(sensor_t *sensor) { + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->hw_flags.pixck = 1; + sensor->hw_flags.gs_bpp = 1; + return 0; +} + +#endif // (OMV_ENABLE_FROGEYE2020 == 1) diff --git a/components/3rd_party/omv/omv/sensors/frogeye2020.h b/components/3rd_party/omv/omv/sensors/frogeye2020.h new file mode 100644 index 00000000..2125eecd --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/frogeye2020.h @@ -0,0 +1,15 @@ +/* + * 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. + * + * FrogEye2020 driver. + */ +#ifndef __FROGEYE2020_H__ +#define __FROGEYE2020_H__ +#define FROGEYE2020_XCLK_FREQ (5000000) +int frogeye2020_init(sensor_t *sensor); +#endif // __FROGEYE2020_H__ diff --git a/components/3rd_party/omv/omv/sensors/gc2145.c b/components/3rd_party/omv/omv/sensors/gc2145.c new file mode 100644 index 00000000..857055f0 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/gc2145.c @@ -0,0 +1,1009 @@ +/* + * 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. + * + * GC2145 driver. + */ +#include "omv_boardconfig.h" +#if (OMV_ENABLE_GC2145 == 1) + +#include +#include +#include + +#include "omv_i2c.h" +#include "sensor.h" +#include "gc2145.h" +#include "gc2145_regs.h" +#include "py/mphal.h" + +#define BLANK_LINES 16 +#define DUMMY_LINES 16 + +#define BLANK_COLUMNS 0 +#define DUMMY_COLUMNS 8 + +#define SENSOR_WIDTH 1616 +#define SENSOR_HEIGHT 1248 + +#define ACTIVE_SENSOR_WIDTH (SENSOR_WIDTH - BLANK_COLUMNS - (2 * DUMMY_COLUMNS)) +#define ACTIVE_SENSOR_HEIGHT (SENSOR_HEIGHT - BLANK_LINES - (2 * DUMMY_LINES)) + +#define DUMMY_WIDTH_BUFFER 16 +#define DUMMY_HEIGHT_BUFFER 8 + +static int16_t readout_x = 0; +static int16_t readout_y = 0; + +static uint16_t readout_w = ACTIVE_SENSOR_WIDTH; +static uint16_t readout_h = ACTIVE_SENSOR_HEIGHT; + +static bool fov_wide = false; + +// SLAVE ADDR 0x78 +static const uint8_t default_regs[][2] = { + {0xfe, 0xf0}, + {0xfe, 0xf0}, + {0xfe, 0xf0}, + {0xfc, 0x06}, + {0xf6, 0x00}, + {0xf7, 0x1d}, + {0xf8, 0x85}, + {0xfa, 0x00}, + {0xf9, 0xfe}, + {0xf2, 0x00}, + ///////////////////////////////////////////////// + //////////////////ISP reg////////////////////// + //////////////////////////////////////////////////// + {0xfe, 0x00}, + {0x03, 0x04}, + {0x04, 0xe2}, + + {0x09, 0x00}, // row start + {0x0a, 0x00}, + + {0x0b, 0x00}, // col start + {0x0c, 0x00}, + + {0x0d, 0x04}, // Window height + {0x0e, 0xc0}, + + {0x0f, 0x06}, // Window width + {0x10, 0x52}, + + {0x99, 0x11}, // Subsample + {0x9a, 0x0E}, // Subsample mode + + {0x12, 0x2e}, // +#if (OMV_GC2145_ROTATE == 1) + {0x17, 0x17}, // Analog Mode 1 (vflip/mirror[1:0]) +#else + {0x17, 0x14}, // Analog Mode 1 (vflip/mirror[1:0]) +#endif + {0x18, 0x22}, // Analog Mode 2 + {0x19, 0x0e}, + {0x1a, 0x01}, + {0x1b, 0x4b}, + {0x1c, 0x07}, + {0x1d, 0x10}, + {0x1e, 0x88}, + {0x1f, 0x78}, + {0x20, 0x03}, + {0x21, 0x40}, + {0x22, 0xa0}, + {0x24, 0x16}, + {0x25, 0x01}, + {0x26, 0x10}, + {0x2d, 0x60}, + {0x30, 0x01}, + {0x31, 0x90}, + {0x33, 0x06}, + {0x34, 0x01}, + {0x80, 0x7f}, + {0x81, 0x26}, + {0x82, 0xfa}, + {0x83, 0x00}, + {0x84, 0x06}, //RGB565 + {0x86, 0x23}, + {0x88, 0x03}, + {0x89, 0x03}, + {0x85, 0x08}, + {0x8a, 0x00}, + {0x8b, 0x00}, + {0xb0, 0x55}, + {0xc3, 0x00}, + {0xc4, 0x80}, + {0xc5, 0x90}, + {0xc6, 0x3b}, + {0xc7, 0x46}, + {0xec, 0x06}, + {0xed, 0x04}, + {0xee, 0x60}, + {0xef, 0x90}, + {0xb6, 0x01}, + + {0x90, 0x01}, // Enable crop + {0x91, 0x00}, // Y offset + {0x92, 0x00}, + {0x93, 0x00}, // X offset + {0x94, 0x00}, + {0x95, 0x02}, // Window height + {0x96, 0x58}, + {0x97, 0x03}, // Window width + {0x98, 0x20}, + {0x99, 0x22}, // Subsample + {0x9a, 0x0E}, // Subsample mode + + {0x9b, 0x00}, + {0x9c, 0x00}, + {0x9d, 0x00}, + {0x9e, 0x00}, + {0x9f, 0x00}, + {0xa0, 0x00}, + {0xa1, 0x00}, + {0xa2, 0x00}, + ///////////////////////////////////////// + /////////// BLK //////////////////////// + ///////////////////////////////////////// + {0xfe, 0x00}, + {0x40, 0x42}, + {0x41, 0x00}, + {0x43, 0x5b}, + {0x5e, 0x00}, + {0x5f, 0x00}, + {0x60, 0x00}, + {0x61, 0x00}, + {0x62, 0x00}, + {0x63, 0x00}, + {0x64, 0x00}, + {0x65, 0x00}, + {0x66, 0x20}, + {0x67, 0x20}, + {0x68, 0x20}, + {0x69, 0x20}, + {0x76, 0x00}, + {0x6a, 0x08}, + {0x6b, 0x08}, + {0x6c, 0x08}, + {0x6d, 0x08}, + {0x6e, 0x08}, + {0x6f, 0x08}, + {0x70, 0x08}, + {0x71, 0x08}, + {0x76, 0x00}, + {0x72, 0xf0}, + {0x7e, 0x3c}, + {0x7f, 0x00}, + {0xfe, 0x02}, + {0x48, 0x15}, + {0x49, 0x00}, + {0x4b, 0x0b}, + {0xfe, 0x00}, + //////////////////////////////////////// + /////////// AEC //////////////////////// + //////////////////////////////////////// + {0xfe, 0x01}, + {0x01, 0x04}, + {0x02, 0xc0}, + {0x03, 0x04}, + {0x04, 0x90}, + {0x05, 0x30}, + {0x06, 0x90}, + {0x07, 0x30}, + {0x08, 0x80}, + {0x09, 0x00}, + {0x0a, 0x82}, + {0x0b, 0x11}, + {0x0c, 0x10}, + {0x11, 0x10}, + {0x13, 0x68}, //7b->68 bob + {0x17, 0x00}, + {0x1c, 0x11}, + {0x1e, 0x61}, + {0x1f, 0x35}, + {0x20, 0x40}, + {0x22, 0x40}, + {0x23, 0x20}, + {0xfe, 0x02}, + {0x0f, 0x04}, + {0xfe, 0x01}, + {0x12, 0x30}, //35 + {0x15, 0xb0}, + {0x10, 0x31}, + {0x3e, 0x28}, + {0x3f, 0xb0}, + {0x40, 0x90}, + {0x41, 0x0f}, + ///////////////////////////// + //////// INTPEE ///////////// + ///////////////////////////// + {0xfe, 0x02}, + {0x90, 0x6c}, + {0x91, 0x03}, + {0x92, 0xcb}, + {0x94, 0x33}, + {0x95, 0x84}, + {0x97, 0x65}, // 54->65 bob + {0xa2, 0x11}, + {0xfe, 0x00}, + ///////////////////////////// + //////// DNDD/////////////// + ///////////////////////////// + {0xfe, 0x02}, + {0x80, 0xc1}, + {0x81, 0x08}, + {0x82, 0x05}, //05 + {0x83, 0x08}, //08 + {0x84, 0x0a}, + {0x86, 0xf0}, + {0x87, 0x50}, + {0x88, 0x15}, + {0x89, 0xb0}, + {0x8a, 0x30}, + {0x8b, 0x10}, + ///////////////////////////////////////// + /////////// ASDE //////////////////////// + ///////////////////////////////////////// + {0xfe, 0x01}, + {0x21, 0x04}, + {0xfe, 0x02}, + {0xa3, 0x50}, + {0xa4, 0x20}, + {0xa5, 0x40}, + {0xa6, 0x80}, + {0xab, 0x40}, + {0xae, 0x0c}, + {0xb3, 0x46}, + {0xb4, 0x64}, + {0xb6, 0x38}, + {0xb7, 0x01}, //01 + {0xb9, 0x2b}, //2b + {0x3c, 0x04}, //04 + {0x3d, 0x15}, //15 + {0x4b, 0x06}, //06 + {0x4c, 0x20}, + {0xfe, 0x00}, + ///////////////////////////////////////// + /////////// GAMMA //////////////////////// + ///////////////////////////////////////// + ///////////////////gamma1//////////////////// + {0xfe, 0x02}, + {0x10, 0x09}, + {0x11, 0x0d}, + {0x12, 0x13}, + {0x13, 0x19}, + {0x14, 0x27}, + {0x15, 0x37}, + {0x16, 0x45}, + {0x17, 0x53}, + {0x18, 0x69}, + {0x19, 0x7d}, + {0x1a, 0x8f}, + {0x1b, 0x9d}, + {0x1c, 0xa9}, + {0x1d, 0xbd}, + {0x1e, 0xcd}, + {0x1f, 0xd9}, + {0x20, 0xe3}, + {0x21, 0xea}, + {0x22, 0xef}, + {0x23, 0xf5}, + {0x24, 0xf9}, + {0x25, 0xff}, + {0xfe, 0x00}, + {0xc6, 0x20}, + {0xc7, 0x2b}, + ///////////////////gamma2//////////////////// + {0xfe, 0x02}, + {0x26, 0x0f}, + {0x27, 0x14}, + {0x28, 0x19}, + {0x29, 0x1e}, + {0x2a, 0x27}, + {0x2b, 0x33}, + {0x2c, 0x3b}, + {0x2d, 0x45}, + {0x2e, 0x59}, + {0x2f, 0x69}, + {0x30, 0x7c}, + {0x31, 0x89}, + {0x32, 0x98}, + {0x33, 0xae}, + {0x34, 0xc0}, + {0x35, 0xcf}, + {0x36, 0xda}, + {0x37, 0xe2}, + {0x38, 0xe9}, + {0x39, 0xf3}, + {0x3a, 0xf9}, + {0x3b, 0xff}, + /////////////////////////////////////////////// + ///////////YCP /////////////////////// + /////////////////////////////////////////////// + {0xfe, 0x02}, + {0xd1, 0x32}, //32->2d + {0xd2, 0x32}, //32->2d bob + {0xd3, 0x40}, + {0xd6, 0xf0}, + {0xd7, 0x10}, + {0xd8, 0xda}, + {0xdd, 0x14}, + {0xde, 0x86}, + {0xed, 0x80}, //80 + {0xee, 0x00}, //00 + {0xef, 0x3f}, + {0xd8, 0xd8}, + ///////////////////abs///////////////// + {0xfe, 0x01}, + {0x9f, 0x40}, + ///////////////////////////////////////////// + //////////////////////// LSC /////////////// + ////////////////////////////////////////// + {0xfe, 0x01}, + {0xc2, 0x14}, + {0xc3, 0x0d}, + {0xc4, 0x0c}, + {0xc8, 0x15}, + {0xc9, 0x0d}, + {0xca, 0x0a}, + {0xbc, 0x24}, + {0xbd, 0x10}, + {0xbe, 0x0b}, + {0xb6, 0x25}, + {0xb7, 0x16}, + {0xb8, 0x15}, + {0xc5, 0x00}, + {0xc6, 0x00}, + {0xc7, 0x00}, + {0xcb, 0x00}, + {0xcc, 0x00}, + {0xcd, 0x00}, + {0xbf, 0x07}, + {0xc0, 0x00}, + {0xc1, 0x00}, + {0xb9, 0x00}, + {0xba, 0x00}, + {0xbb, 0x00}, + {0xaa, 0x01}, + {0xab, 0x01}, + {0xac, 0x00}, + {0xad, 0x05}, + {0xae, 0x06}, + {0xaf, 0x0e}, + {0xb0, 0x0b}, + {0xb1, 0x07}, + {0xb2, 0x06}, + {0xb3, 0x17}, + {0xb4, 0x0e}, + {0xb5, 0x0e}, + {0xd0, 0x09}, + {0xd1, 0x00}, + {0xd2, 0x00}, + {0xd6, 0x08}, + {0xd7, 0x00}, + {0xd8, 0x00}, + {0xd9, 0x00}, + {0xda, 0x00}, + {0xdb, 0x00}, + {0xd3, 0x0a}, + {0xd4, 0x00}, + {0xd5, 0x00}, + {0xa4, 0x00}, + {0xa5, 0x00}, + {0xa6, 0x77}, + {0xa7, 0x77}, + {0xa8, 0x77}, + {0xa9, 0x77}, + {0xa1, 0x80}, + {0xa2, 0x80}, + + {0xfe, 0x01}, + {0xdf, 0x0d}, + {0xdc, 0x25}, + {0xdd, 0x30}, + {0xe0, 0x77}, + {0xe1, 0x80}, + {0xe2, 0x77}, + {0xe3, 0x90}, + {0xe6, 0x90}, + {0xe7, 0xa0}, + {0xe8, 0x90}, + {0xe9, 0xa0}, + {0xfe, 0x00}, + /////////////////////////////////////////////// + /////////// AWB//////////////////////// + /////////////////////////////////////////////// + {0xfe, 0x01}, + {0x4f, 0x00}, + {0x4f, 0x00}, + {0x4b, 0x01}, + {0x4f, 0x00}, + + {0x4c, 0x01}, // D75 + {0x4d, 0x71}, + {0x4e, 0x01}, + {0x4c, 0x01}, + {0x4d, 0x91}, + {0x4e, 0x01}, + {0x4c, 0x01}, + {0x4d, 0x70}, + {0x4e, 0x01}, + {0x4c, 0x01}, // D65 + {0x4d, 0x90}, + {0x4e, 0x02}, + {0x4c, 0x01}, + {0x4d, 0xb0}, + {0x4e, 0x02}, + {0x4c, 0x01}, + {0x4d, 0x8f}, + {0x4e, 0x02}, + {0x4c, 0x01}, + {0x4d, 0x6f}, + {0x4e, 0x02}, + {0x4c, 0x01}, + {0x4d, 0xaf}, + {0x4e, 0x02}, + {0x4c, 0x01}, + {0x4d, 0xd0}, + {0x4e, 0x02}, + {0x4c, 0x01}, + {0x4d, 0xf0}, + {0x4e, 0x02}, + {0x4c, 0x01}, + {0x4d, 0xcf}, + {0x4e, 0x02}, + {0x4c, 0x01}, + {0x4d, 0xef}, + {0x4e, 0x02}, + {0x4c, 0x01}, //D50 + {0x4d, 0x6e}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x8e}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0xae}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0xce}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x4d}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x6d}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x8d}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0xad}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0xcd}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x4c}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x6c}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x8c}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0xac}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0xcc}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0xcb}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x4b}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x6b}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0x8b}, + {0x4e, 0x03}, + {0x4c, 0x01}, + {0x4d, 0xab}, + {0x4e, 0x03}, + {0x4c, 0x01}, //CWF + {0x4d, 0x8a}, + {0x4e, 0x04}, + {0x4c, 0x01}, + {0x4d, 0xaa}, + {0x4e, 0x04}, + {0x4c, 0x01}, + {0x4d, 0xca}, + {0x4e, 0x04}, + {0x4c, 0x01}, + {0x4d, 0xca}, + {0x4e, 0x04}, + {0x4c, 0x01}, + {0x4d, 0xc9}, + {0x4e, 0x04}, + {0x4c, 0x01}, + {0x4d, 0x8a}, + {0x4e, 0x04}, + {0x4c, 0x01}, + {0x4d, 0x89}, + {0x4e, 0x04}, + {0x4c, 0x01}, + {0x4d, 0xa9}, + {0x4e, 0x04}, + {0x4c, 0x02}, //tl84 + {0x4d, 0x0b}, + {0x4e, 0x05}, + {0x4c, 0x02}, + {0x4d, 0x0a}, + {0x4e, 0x05}, + {0x4c, 0x01}, + {0x4d, 0xeb}, + {0x4e, 0x05}, + {0x4c, 0x01}, + {0x4d, 0xea}, + {0x4e, 0x05}, + {0x4c, 0x02}, + {0x4d, 0x09}, + {0x4e, 0x05}, + {0x4c, 0x02}, + {0x4d, 0x29}, + {0x4e, 0x05}, + {0x4c, 0x02}, + {0x4d, 0x2a}, + {0x4e, 0x05}, + {0x4c, 0x02}, + {0x4d, 0x4a}, + {0x4e, 0x05}, + {0x4c, 0x02}, + {0x4d, 0x8a}, + {0x4e, 0x06}, + {0x4c, 0x02}, + {0x4d, 0x49}, + {0x4e, 0x06}, + {0x4c, 0x02}, + {0x4d, 0x69}, + {0x4e, 0x06}, + {0x4c, 0x02}, + {0x4d, 0x89}, + {0x4e, 0x06}, + {0x4c, 0x02}, + {0x4d, 0xa9}, + {0x4e, 0x06}, + {0x4c, 0x02}, + {0x4d, 0x48}, + {0x4e, 0x06}, + {0x4c, 0x02}, + {0x4d, 0x68}, + {0x4e, 0x06}, + {0x4c, 0x02}, + {0x4d, 0x69}, + {0x4e, 0x06}, + {0x4c, 0x02}, //H + {0x4d, 0xca}, + {0x4e, 0x07}, + {0x4c, 0x02}, + {0x4d, 0xc9}, + {0x4e, 0x07}, + {0x4c, 0x02}, + {0x4d, 0xe9}, + {0x4e, 0x07}, + {0x4c, 0x03}, + {0x4d, 0x09}, + {0x4e, 0x07}, + {0x4c, 0x02}, + {0x4d, 0xc8}, + {0x4e, 0x07}, + {0x4c, 0x02}, + {0x4d, 0xe8}, + {0x4e, 0x07}, + {0x4c, 0x02}, + {0x4d, 0xa7}, + {0x4e, 0x07}, + {0x4c, 0x02}, + {0x4d, 0xc7}, + {0x4e, 0x07}, + {0x4c, 0x02}, + {0x4d, 0xe7}, + {0x4e, 0x07}, + {0x4c, 0x03}, + {0x4d, 0x07}, + {0x4e, 0x07}, + + {0x4f, 0x01}, + {0x50, 0x80}, + {0x51, 0xa8}, + {0x52, 0x47}, + {0x53, 0x38}, + {0x54, 0xc7}, + {0x56, 0x0e}, + {0x58, 0x08}, + {0x5b, 0x00}, + {0x5c, 0x74}, + {0x5d, 0x8b}, + {0x61, 0xdb}, + {0x62, 0xb8}, + {0x63, 0x86}, + {0x64, 0xc0}, + {0x65, 0x04}, + {0x67, 0xa8}, + {0x68, 0xb0}, + {0x69, 0x00}, + {0x6a, 0xa8}, + {0x6b, 0xb0}, + {0x6c, 0xaf}, + {0x6d, 0x8b}, + {0x6e, 0x50}, + {0x6f, 0x18}, + {0x73, 0xf0}, + {0x70, 0x0d}, + {0x71, 0x60}, + {0x72, 0x80}, + {0x74, 0x01}, + {0x75, 0x01}, + {0x7f, 0x0c}, + {0x76, 0x70}, + {0x77, 0x58}, + {0x78, 0xa0}, + {0x79, 0x5e}, + {0x7a, 0x54}, + {0x7b, 0x58}, + {0xfe, 0x00}, + ////////////////////////////////////////// + ///////////CC//////////////////////// + ////////////////////////////////////////// + {0xfe, 0x02}, + {0xc0, 0x01}, + {0xc1, 0x44}, + {0xc2, 0xfd}, + {0xc3, 0x04}, + {0xc4, 0xF0}, + {0xc5, 0x48}, + {0xc6, 0xfd}, + {0xc7, 0x46}, + {0xc8, 0xfd}, + {0xc9, 0x02}, + {0xca, 0xe0}, + {0xcb, 0x45}, + {0xcc, 0xec}, + {0xcd, 0x48}, + {0xce, 0xf0}, + {0xcf, 0xf0}, + {0xe3, 0x0c}, + {0xe4, 0x4b}, + {0xe5, 0xe0}, + ////////////////////////////////////////// + ///////////ABS //////////////////// + ////////////////////////////////////////// + {0xfe, 0x01}, + {0x9f, 0x40}, + {0xfe, 0x00}, + + ////////////////////////////////////// + /////////// OUTPUT //////////////// + ////////////////////////////////////// + {0xfe, 0x00}, + {0xf2, 0x0f}, + + ///////////////dark sun//////////////////// + {0xfe, 0x02}, + {0x40, 0xbf}, + {0x46, 0xcf}, + {0xfe, 0x00}, + + //////////////frame rate control///////// + {0xfe, 0x00}, + {0x05, 0x01}, // HBLANK + {0x06, 0x1C}, + {0x07, 0x00}, // VBLANK + {0x08, 0x32}, + {0x11, 0x00}, // SH Delay + {0x12, 0x1D}, + {0x13, 0x00}, // St + {0x14, 0x00}, // Et + + {0xfe, 0x01}, + {0x3c, 0x00}, + {0x3d, 0x04}, + {0xfe, 0x00}, + + {0x00, 0x00}, +}; + +static int reset(sensor_t *sensor) { + int ret = 0; + + readout_x = 0; + readout_y = 0; + + readout_w = ACTIVE_SENSOR_WIDTH; + readout_h = ACTIVE_SENSOR_HEIGHT; + + fov_wide = false; + + // Write default registers + for (int i = 0; default_regs[i][0]; i++) { + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, default_regs[i][0], default_regs[i][1]); + } + + // Delay 10 ms + mp_hal_delay_ms(10); + + return ret; +} + +static int sleep(sensor_t *sensor, int enable) { + int ret = 0; + + if (enable) { + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0xF2, 0x0); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0xF7, 0x10); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0xFC, 0x01); + } else { + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0xF2, 0x0F); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0xF7, 0x1d); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0xFC, 0x06); + } + + return ret; +} + +static int read_reg(sensor_t *sensor, uint16_t reg_addr) { + uint8_t reg_data; + if (omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, reg_addr, ®_data) != 0) { + return -1; + } + return reg_data; +} + +static int write_reg(sensor_t *sensor, uint16_t reg_addr, uint16_t reg_data) { + return omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, reg_addr, reg_data); +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) { + int ret = 0; + uint8_t reg; + + // P0 regs + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0xFE, 0x00); + + // Read current output format reg + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_OUTPUT_FMT, ®); + + switch (pixformat) { + case PIXFORMAT_RGB565: + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, + REG_OUTPUT_FMT, REG_OUTPUT_SET_FMT(reg, REG_OUTPUT_FMT_RGB565)); + break; + case PIXFORMAT_YUV422: + case PIXFORMAT_GRAYSCALE: + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, + REG_OUTPUT_FMT, REG_OUTPUT_SET_FMT(reg, REG_OUTPUT_FMT_YCBYCR)); + break; + case PIXFORMAT_BAYER: + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, + REG_OUTPUT_FMT, REG_OUTPUT_SET_FMT(reg, REG_OUTPUT_FMT_BAYER)); + break; + default: + return -1; + } + + return ret; +} + +static int set_window(sensor_t *sensor, uint16_t reg, uint16_t x, uint16_t y, uint16_t w, uint16_t h) { + int ret = 0; + + // P0 regs + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0xFE, 0x00); + + // Y/row offset + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, reg++, y >> 8); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, reg++, y & 0xff); + + // X/col offset + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, reg++, x >> 8); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, reg++, x & 0xff); + + // Window height + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, reg++, h >> 8); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, reg++, h & 0xff); + + // Window width + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, reg++, w >> 8); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, reg++, w & 0xff); + + return ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) { + int ret = 0; + + uint16_t w = resolution[framesize][0]; + uint16_t h = resolution[framesize][1]; + + // Invalid resolution. + if ((w > ACTIVE_SENSOR_WIDTH) || (h > ACTIVE_SENSOR_HEIGHT)) { + return -1; + } + + // Step 0: Clamp readout settings. + + readout_w = IM_MAX(readout_w, w); + readout_h = IM_MAX(readout_h, h); + + int readout_x_max = (ACTIVE_SENSOR_WIDTH - readout_w) / 2; + int readout_y_max = (ACTIVE_SENSOR_HEIGHT - readout_h) / 2; + readout_x = IM_MAX(IM_MIN(readout_x, readout_x_max), -readout_x_max); + readout_y = IM_MAX(IM_MIN(readout_y, readout_y_max), -readout_y_max); + + // Step 1: Determine sub-readout window. + + uint16_t ratio = fast_floorf(IM_MIN(readout_w / ((float) w), readout_h / ((float) h))); + + // Limit the maximum amount of scaling allowed to keep the frame rate up. + ratio = IM_MIN(ratio, (fov_wide ? 5 : 3)); + + if (!(ratio % 2)) { + // camera outputs messed up bayer images at even ratios for some reason... + ratio -= 1; + } + + uint16_t sub_readout_w = w * ratio; + uint16_t sub_readout_h = h * ratio; + + // Step 2: Determine horizontal and vertical start and end points. + + uint16_t sensor_w = sub_readout_w + DUMMY_WIDTH_BUFFER; // camera hardware needs dummy pixels to sync + uint16_t sensor_h = sub_readout_h + DUMMY_HEIGHT_BUFFER; // camera hardware needs dummy lines to sync + + uint16_t sensor_x = IM_MAX(IM_MIN((((ACTIVE_SENSOR_WIDTH - sensor_w) / 4) - (readout_x / 2)) * 2, + ACTIVE_SENSOR_WIDTH - sensor_w), -(DUMMY_WIDTH_BUFFER / 2)) + DUMMY_COLUMNS; // must be multiple of 2 + + uint16_t sensor_y = IM_MAX(IM_MIN((((ACTIVE_SENSOR_HEIGHT - sensor_h) / 4) - (readout_y / 2)) * 2, + ACTIVE_SENSOR_HEIGHT - sensor_h), -(DUMMY_HEIGHT_BUFFER / 2)) + DUMMY_LINES; // must be multiple of 2 + + // Step 3: Write regs. + + // Set Readout window first. + ret |= set_window(sensor, 0x09, sensor_x, sensor_y, sensor_w, sensor_h); + + // Set cropping window next. + ret |= set_window(sensor, 0x91, 0, 0, w, h); + + // Enable crop + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0x90, 0x01); + + // Set Sub-sampling ratio and mode + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0x99, ((ratio << 4) | (ratio))); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0x9A, 0x0E); + + return ret; +} + +static int set_hmirror(sensor_t *sensor, int enable) { + int ret = 0; + uint8_t reg; + + // P0 regs + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0xFE, 0x00); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_AMODE1, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_AMODE1, REG_AMODE1_SET_HMIRROR(reg, enable)); + return ret; +} + +static int set_vflip(sensor_t *sensor, int enable) { + int ret = 0; + uint8_t reg; + + // P0 regs + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0xFE, 0x00); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_AMODE1, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_AMODE1, REG_AMODE1_SET_VMIRROR(reg, enable)); + return ret; +} + +static int set_auto_exposure(sensor_t *sensor, int enable, int exposure_us) { + int ret = 0; + uint8_t reg; + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0xFE, 0x00); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, 0xb6, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0xb6, (reg & 0xFE) | (enable & 0x01)); + return ret; +} + +static int set_auto_whitebal(sensor_t *sensor, int enable, float r_gain_db, float g_gain_db, float b_gain_db) { + int ret = 0; + uint8_t reg; + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0xFE, 0x00); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, 0x82, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0x82, (reg & 0xFD) | ((enable & 0x01) << 1)); + return ret; +} + +static int ioctl(sensor_t *sensor, int request, va_list ap) { + int ret = 0; + + switch (request) { + case IOCTL_SET_READOUT_WINDOW: { + int tmp_readout_x = va_arg(ap, int); + int tmp_readout_y = va_arg(ap, int); + int tmp_readout_w = IM_MAX(IM_MIN(va_arg(ap, int), ACTIVE_SENSOR_WIDTH), + resolution[sensor->framesize][0]); + int tmp_readout_h = IM_MAX(IM_MIN(va_arg(ap, int), ACTIVE_SENSOR_HEIGHT), + resolution[sensor->framesize][1]); + int readout_x_max = (ACTIVE_SENSOR_WIDTH - tmp_readout_w) / 2; + int readout_y_max = (ACTIVE_SENSOR_HEIGHT - tmp_readout_h) / 2; + tmp_readout_x = IM_MAX(IM_MIN(tmp_readout_x, readout_x_max), -readout_x_max); + tmp_readout_y = IM_MAX(IM_MIN(tmp_readout_y, readout_y_max), -readout_y_max); + bool changed = (tmp_readout_x != readout_x) || (tmp_readout_y != readout_y) || + (tmp_readout_w != readout_w) || (tmp_readout_h != readout_h); + readout_x = tmp_readout_x; + readout_y = tmp_readout_y; + readout_w = tmp_readout_w; + readout_h = tmp_readout_h; + if (changed && (sensor->framesize != FRAMESIZE_INVALID)) { + set_framesize(sensor, sensor->framesize); + } + break; + } + case IOCTL_GET_READOUT_WINDOW: { + *va_arg(ap, int *) = readout_x; + *va_arg(ap, int *) = readout_y; + *va_arg(ap, int *) = readout_w; + *va_arg(ap, int *) = readout_h; + break; + } + case IOCTL_SET_FOV_WIDE: { + fov_wide = va_arg(ap, int); + break; + } + case IOCTL_GET_FOV_WIDE: { + *va_arg(ap, int *) = fov_wide; + break; + } + default: { + ret = -1; + break; + } + } + + return ret; +} + +int gc2145_init(sensor_t *sensor) { + // Initialize sensor structure. + sensor->reset = reset; + sensor->sleep = sleep; + sensor->read_reg = read_reg; + sensor->write_reg = write_reg; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + sensor->set_auto_exposure = set_auto_exposure; + sensor->set_auto_whitebal = set_auto_whitebal; + sensor->ioctl = ioctl; + + // Set sensor flags + sensor->hw_flags.vsync = 0; + sensor->hw_flags.hsync = 0; + sensor->hw_flags.pixck = 1; + sensor->hw_flags.fsync = 0; + sensor->hw_flags.jpege = 0; + sensor->hw_flags.gs_bpp = 2; + sensor->hw_flags.rgb_swap = 1; + sensor->hw_flags.bayer = SENSOR_HW_FLAGS_BAYER_GBRG; + + return 0; +} +#endif // (OMV_ENABLE_GC2145 == 1) diff --git a/components/3rd_party/omv/omv/sensors/gc2145.h b/components/3rd_party/omv/omv/sensors/gc2145.h new file mode 100644 index 00000000..60aec045 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/gc2145.h @@ -0,0 +1,15 @@ +/* + * 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. + * + * GC2145 driver. + */ +#ifndef __GC2145_H__ +#define __GC2145_H__ +#define GC2145_XCLK_FREQ (12000000) +int gc2145_init(sensor_t *sensor); +#endif // __GC2145_H__ diff --git a/components/3rd_party/omv/omv/sensors/gc2145_regs.h b/components/3rd_party/omv/omv/sensors/gc2145_regs.h new file mode 100644 index 00000000..c70e3c58 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/gc2145_regs.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. + * + * GC2145 register definitions. + */ +#ifndef __GC2145_REGS_H__ +#define __GC2145_REGS_H__ + +#define REG_AMODE1 (0x17) +#define REG_AMODE1_DEF (0x14) +#define REG_AMODE1_SET_HMIRROR(r, x) ((r & 0xFE) | ((x & 1) << 0)) +#define REG_AMODE1_SET_VMIRROR(r, x) ((r & 0xFD) | ((x & 1) << 1)) + +#define REG_OUTPUT_FMT (0x84) +#define REG_OUTPUT_FMT_RGB565 (0x06) +#define REG_OUTPUT_FMT_YCBYCR (0x02) +#define REG_OUTPUT_FMT_BAYER (0x17) +#define REG_OUTPUT_SET_FMT(r, x) ((r & 0xE0) | (x)) + +#define REG_SYNC_MODE (0x86) +#define REG_SYNC_MODE_DEF (0x03) +#define REG_SYNC_MODE_COL_SWITCH (0x10) +#define REG_SYNC_MODE_ROW_SWITCH (0x20) +#endif //__GC2145_REGS_H__ diff --git a/components/3rd_party/omv/omv/sensors/hm01b0.c b/components/3rd_party/omv/omv/sensors/hm01b0.c new file mode 100644 index 00000000..f06d32a7 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/hm01b0.c @@ -0,0 +1,545 @@ +/* + * 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. + * + * HM01B0 driver. + */ +#include "omv_boardconfig.h" +#if (OMV_ENABLE_HM01B0 == 1) + +#include +#include +#include +#include "omv_i2c.h" +#include "sensor.h" +#include "hm01b0.h" +#include "hm01b0_regs.h" +#include "py/mphal.h" + +#define HIMAX_BOOT_RETRY (10) +#define HIMAX_LINE_LEN_PCK_FULL 0x178 +#define HIMAX_FRAME_LENGTH_FULL 0x109 + +#define HIMAX_LINE_LEN_PCK_QVGA 0x178 +#define HIMAX_FRAME_LENGTH_QVGA 0x104 + +#define HIMAX_LINE_LEN_PCK_QQVGA 0x178 +#define HIMAX_FRAME_LENGTH_QQVGA 0x084 + +static const uint16_t default_regs[][2] = { + {BLC_TGT, 0x08}, // BLC target :8 at 8 bit mode + {BLC2_TGT, 0x08}, // BLI target :8 at 8 bit mode + {0x3044, 0x0A}, // Increase CDS time for settling + {0x3045, 0x00}, // Make symmetric for cds_tg and rst_tg + {0x3047, 0x0A}, // Increase CDS time for settling + {0x3050, 0xC0}, // Make negative offset up to 4x + {0x3051, 0x42}, + {0x3052, 0x50}, + {0x3053, 0x00}, + {0x3054, 0x03}, // tuning sf sig clamping as lowest + {0x3055, 0xF7}, // tuning dsun + {0x3056, 0xF8}, // increase adc nonoverlap clk + {0x3057, 0x29}, // increase adc pwr for missing code + {0x3058, 0x1F}, // turn on dsun + {0x3059, 0x1E}, + {0x3064, 0x00}, + {0x3065, 0x04}, // pad pull 0 + {ANA_Register_17, 0x00}, // Disable internal oscillator + + {BLC_CFG, 0x43}, // BLC_on, IIR + + {0x1001, 0x43}, // BLC dithering en + {0x1002, 0x43}, // blc_darkpixel_thd + {0x0350, 0x7F}, // Dgain Control + {BLI_EN, 0x01}, // BLI enable + {0x1003, 0x00}, // BLI Target [Def: 0x20] + + {DPC_CTRL, 0x01}, // DPC option 0: DPC off 1 : mono 3 : bayer1 5 : bayer2 + {0x1009, 0xA0}, // cluster hot pixel th + {0x100A, 0x60}, // cluster cold pixel th + {SINGLE_THR_HOT, 0x90}, // single hot pixel th + {SINGLE_THR_COLD, 0x40}, // single cold pixel th + {0x1012, 0x00}, // Sync. shift disable + {STATISTIC_CTRL, 0x07}, // AE stat en | MD LROI stat en | magic + {0x2003, 0x00}, + {0x2004, 0x1C}, + {0x2007, 0x00}, + {0x2008, 0x58}, + {0x200B, 0x00}, + {0x200C, 0x7A}, + {0x200F, 0x00}, + {0x2010, 0xB8}, + {0x2013, 0x00}, + {0x2014, 0x58}, + {0x2017, 0x00}, + {0x2018, 0x9B}, + + {AE_CTRL, 0x01}, //Automatic Exposure + {AE_TARGET_MEAN, 0x64}, //AE target mean [Def: 0x3C] + {AE_MIN_MEAN, 0x0A}, //AE min target mean [Def: 0x0A] + {CONVERGE_IN_TH, 0x03}, //Converge in threshold [Def: 0x03] + {CONVERGE_OUT_TH, 0x05}, //Converge out threshold [Def: 0x05] + {MAX_INTG_H, (HIMAX_FRAME_LENGTH_QVGA - 2) >> 8}, //Maximum INTG High Byte [Def: 0x01] + {MAX_INTG_L, (HIMAX_FRAME_LENGTH_QVGA - 2) & 0xFF}, //Maximum INTG Low Byte [Def: 0x54] + {MAX_AGAIN_FULL, 0x04}, //Maximum Analog gain in full frame mode [Def: 0x03] + {MAX_AGAIN_BIN2, 0x04}, //Maximum Analog gain in bin2 mode [Def: 0x04] + {MAX_DGAIN, 0xC0}, + + {INTEGRATION_H, 0x01}, //Integration H [Def: 0x01] + {INTEGRATION_L, 0x08}, //Integration L [Def: 0x08] + {ANALOG_GAIN, 0x00}, //Analog Global Gain [Def: 0x00] + {DAMPING_FACTOR, 0x20}, //Damping Factor [Def: 0x20] + {DIGITAL_GAIN_H, 0x01}, //Digital Gain High [Def: 0x01] + {DIGITAL_GAIN_L, 0x00}, //Digital Gain Low [Def: 0x00] + + {FS_CTRL, 0x00}, //Flicker Control + + {FS_60HZ_H, 0x00}, + {FS_60HZ_L, 0x3C}, + {FS_50HZ_H, 0x00}, + {FS_50HZ_L, 0x32}, + + {MD_CTRL, 0x00}, + {FRAME_LEN_LINES_H, HIMAX_FRAME_LENGTH_QVGA >> 8}, + {FRAME_LEN_LINES_L, HIMAX_FRAME_LENGTH_QVGA & 0xFF}, + {LINE_LEN_PCK_H, HIMAX_LINE_LEN_PCK_QVGA >> 8}, + {LINE_LEN_PCK_L, HIMAX_LINE_LEN_PCK_QVGA & 0xFF}, + {QVGA_WIN_EN, 0x01}, // Enable QVGA window readout + {0x0383, 0x01}, + {0x0387, 0x01}, + {0x0390, 0x00}, + {0x3011, 0x70}, + {0x3059, 0x02}, + {OSC_CLK_DIV, 0x0B}, + {IMG_ORIENTATION, 0x00}, // change the orientation + {0x0104, 0x01}, + + //============= End of regs marker ================== + {0x0000, 0x00}, +}; + +static int reset(sensor_t *sensor) { + // Reset sensor. + uint8_t reg = 0xff; + for (int retry = HIMAX_BOOT_RETRY; retry >= 0 && reg != HIMAX_MODE_STANDBY; retry--) { + if (omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, SW_RESET, HIMAX_RESET) != 0) { + return -1; + } + + mp_hal_delay_ms(1); + + if (omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, MODE_SELECT, ®) != 0) { + return -1; + } + + if (reg == HIMAX_MODE_STANDBY) { + break; + } else if (retry == 0) { + return -1; + } + + mp_hal_delay_ms(10); + } + + // Write default registers + int ret = 0; + for (int i = 0; default_regs[i][0] && ret == 0; i++) { + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, default_regs[i][0], default_regs[i][1]); + } + + // Set PCLK polarity. + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, PCLK_POLARITY, (0x20 | PCLK_FALLING_EDGE)); + + // Set mode to streaming + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MODE_SELECT, HIMAX_MODE_STREAMING); + + return ret; +} + +static int read_reg(sensor_t *sensor, uint16_t reg_addr) { + uint8_t reg_data; + if (omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, reg_addr, ®_data) != 0) { + return -1; + } + return reg_data; +} + +static int write_reg(sensor_t *sensor, uint16_t reg_addr, uint16_t reg_data) { + return omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, reg_addr, reg_data); +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) { + int ret = 0; + switch (pixformat) { + case PIXFORMAT_BAYER: + case PIXFORMAT_GRAYSCALE: + break; + default: + return -1; + } + + return ret; +} + +static const uint16_t FULL_regs[][2] = { + {0x0383, 0x01}, + {0x0387, 0x01}, + {0x0390, 0x00}, + {QVGA_WIN_EN, 0x00},// Disable QVGA window readout + {MAX_INTG_H, (HIMAX_FRAME_LENGTH_FULL - 2) >> 8}, + {MAX_INTG_L, (HIMAX_FRAME_LENGTH_FULL - 2) & 0xFF}, + {FRAME_LEN_LINES_H, (HIMAX_FRAME_LENGTH_FULL >> 8)}, + {FRAME_LEN_LINES_L, (HIMAX_FRAME_LENGTH_FULL & 0xFF)}, + {LINE_LEN_PCK_H, (HIMAX_LINE_LEN_PCK_FULL >> 8)}, + {LINE_LEN_PCK_L, (HIMAX_LINE_LEN_PCK_FULL & 0xFF)}, + {GRP_PARAM_HOLD, 0x01}, + //============= End of regs marker ================== + {0x0000, 0x00}, + +}; + +static const uint16_t QVGA_regs[][2] = { + {0x0383, 0x01}, + {0x0387, 0x01}, + {0x0390, 0x00}, + {QVGA_WIN_EN, 0x01},// Enable QVGA window readout + {MAX_INTG_H, (HIMAX_FRAME_LENGTH_QVGA - 2) >> 8}, + {MAX_INTG_L, (HIMAX_FRAME_LENGTH_QVGA - 2) & 0xFF}, + {FRAME_LEN_LINES_H, (HIMAX_FRAME_LENGTH_QVGA >> 8)}, + {FRAME_LEN_LINES_L, (HIMAX_FRAME_LENGTH_QVGA & 0xFF)}, + {LINE_LEN_PCK_H, (HIMAX_LINE_LEN_PCK_QVGA >> 8)}, + {LINE_LEN_PCK_L, (HIMAX_LINE_LEN_PCK_QVGA & 0xFF)}, + {GRP_PARAM_HOLD, 0x01}, + //============= End of regs marker ================== + {0x0000, 0x00}, + +}; + +static const uint16_t QQVGA_regs[][2] = { + {0x0383, 0x03}, + {0x0387, 0x03}, + {0x0390, 0x03}, + {QVGA_WIN_EN, 0x01},// Enable QVGA window readout + {MAX_INTG_H, (HIMAX_FRAME_LENGTH_QQVGA - 2) >> 8}, + {MAX_INTG_L, (HIMAX_FRAME_LENGTH_QQVGA - 2) & 0xFF}, + {FRAME_LEN_LINES_H, (HIMAX_FRAME_LENGTH_QQVGA >> 8)}, + {FRAME_LEN_LINES_L, (HIMAX_FRAME_LENGTH_QQVGA & 0xFF)}, + {LINE_LEN_PCK_H, (HIMAX_LINE_LEN_PCK_QQVGA >> 8)}, + {LINE_LEN_PCK_L, (HIMAX_LINE_LEN_PCK_QQVGA & 0xFF)}, + {GRP_PARAM_HOLD, 0x01}, + //============= End of regs marker ================== + {0x0000, 0x00}, +}; + +static int set_framesize(sensor_t *sensor, framesize_t framesize) { + int ret = 0; + uint16_t w = resolution[framesize][0]; + uint16_t h = resolution[framesize][1]; + + switch (framesize) { + case FRAMESIZE_320X320: + for (int i = 0; FULL_regs[i][0] && ret == 0; i++) { + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, FULL_regs[i][0], FULL_regs[i][1]); + } + break; + case FRAMESIZE_QVGA: + for (int i = 0; QVGA_regs[i][0] && ret == 0; i++) { + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, QVGA_regs[i][0], QVGA_regs[i][1]); + } + break; + case FRAMESIZE_QQVGA: + for (int i = 0; QQVGA_regs[i][0] && ret == 0; i++) { + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, QQVGA_regs[i][0], QQVGA_regs[i][1]); + } + break; + default: + if (w > 320 || h > 320) { + ret = -1; + } + + } + + return ret; +} + +static int set_framerate(sensor_t *sensor, int framerate) { + uint8_t osc_div = 0; + bool highres = false; + + if (sensor->framesize == FRAMESIZE_INVALID + || sensor->framesize == FRAMESIZE_QVGA + || sensor->framesize == FRAMESIZE_320X320) { + highres = true; + } + + if (framerate <= 15) { + osc_div = (highres == true) ? 0x01 : 0x00; + } else if (framerate <= 30) { + osc_div = (highres == true) ? 0x02 : 0x01; + } else if (framerate <= 60) { + osc_div = (highres == true) ? 0x03 : 0x02; + } else { + osc_div = 0x03; // Set to the max possible FPS at this resolution. + } + return omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, OSC_CLK_DIV, 0x08 | osc_div); +} + +static int set_brightness(sensor_t *sensor, int level) { + uint8_t ae_mean; + // Simulate brightness levels by setting AE loop target mean. + switch (level) { + case 0: + ae_mean = 60; + break; + case 1: + ae_mean = 80; + break; + case 2: + ae_mean = 100; + break; + case 3: + ae_mean = 127; + break; + default: + ae_mean = 60; + } + return omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AE_TARGET_MEAN, ae_mean); +} + +static int set_gainceiling(sensor_t *sensor, gainceiling_t gainceiling) { + int ret = 0; + int gain = 0x0; + switch (gainceiling) { + case GAINCEILING_2X: + gain = 0x01; + break; + case GAINCEILING_4X: + gain = 0x02; + break; + case GAINCEILING_8X: + gain = 0x03; + break; + case GAINCEILING_16X: + gain = 0x04; + break; + default: + return -1; + } + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MAX_AGAIN_FULL, gain); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MAX_AGAIN_BIN2, gain); + return ret; +} + +static int set_colorbar(sensor_t *sensor, int enable) { + return omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TEST_PATTERN_MODE, enable & 0x1); +} + +static int set_auto_gain(sensor_t *sensor, int enable, float gain_db, float gain_db_ceiling) { + int ret = 0; + if ((enable == 0) && (!isnanf(gain_db)) && (!isinff(gain_db))) { + gain_db = IM_MAX(IM_MIN(gain_db, 24.0f), 0.0f); + int gain = fast_ceilf(logf(expf((gain_db / 20.0f) * M_LN10)) / M_LN2); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AE_CTRL, 0); // Must disable AE + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, ANALOG_GAIN, ((gain & 0x7) << 4)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, GRP_PARAM_HOLD, 0x01); + } else if ((enable != 0) && (!isnanf(gain_db_ceiling)) && (!isinff(gain_db_ceiling))) { + gain_db_ceiling = IM_MAX(IM_MIN(gain_db_ceiling, 24.0f), 0.0f); + int gain = fast_ceilf(logf(expf((gain_db_ceiling / 20.0f) * M_LN10) / M_LN2)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MAX_AGAIN_FULL, (gain & 0x7)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MAX_AGAIN_BIN2, (gain & 0x7)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AE_CTRL, 1); + } + return ret; +} + +static int get_gain_db(sensor_t *sensor, float *gain_db) { + uint8_t gain; + if (omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, ANALOG_GAIN, &gain) != 0) { + return -1; + } + *gain_db = fast_floorf(log10f(1 << (gain >> 4)) * 20.0f); + return 0; +} + +static int get_vt_pix_clk(sensor_t *sensor, uint32_t *vt_pix_clk) { + uint8_t reg; + if (omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, OSC_CLK_DIV, ®) != 0) { + return -1; + } + // 00 -> MCLK / 8 + // 01 -> MCLK / 4 + // 10 -> MCLK / 2 + // 11 -> MCLK / 1 + uint32_t vt_sys_div = 8 / (1 << (reg & 0x03)); + + // vt_pix_clk = MCLK / vt_sys_div + *vt_pix_clk = OMV_XCLK_FREQUENCY / vt_sys_div; + return 0; +} + +static int set_auto_exposure(sensor_t *sensor, int enable, int exposure_us) { + int ret = 0; + + if (enable) { + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AE_CTRL, 1); + } else { + uint32_t line_len; + uint32_t frame_len; + uint32_t coarse_int; + uint32_t vt_pix_clk = 0; + + switch (sensor->framesize) { + case FRAMESIZE_320X320: + line_len = HIMAX_LINE_LEN_PCK_FULL; + frame_len = HIMAX_FRAME_LENGTH_FULL; + break; + case FRAMESIZE_QVGA: + line_len = HIMAX_LINE_LEN_PCK_QVGA; + frame_len = HIMAX_FRAME_LENGTH_QVGA; + break; + case FRAMESIZE_QQVGA: + line_len = HIMAX_LINE_LEN_PCK_QQVGA; + frame_len = HIMAX_FRAME_LENGTH_QQVGA; + break; + default: + return -1; + } + + ret |= get_vt_pix_clk(sensor, &vt_pix_clk); + coarse_int = fast_roundf(exposure_us * (vt_pix_clk / 1000000.0f) / line_len); + + if (coarse_int < 2) { + coarse_int = 2; + } else if (coarse_int > (frame_len - 2)) { + coarse_int = frame_len - 2; + } + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AE_CTRL, 0); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, INTEGRATION_H, coarse_int >> 8); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, INTEGRATION_L, coarse_int & 0xff); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, GRP_PARAM_HOLD, 0x01); + } + + return ret; +} + +static int get_exposure_us(sensor_t *sensor, int *exposure_us) { + int ret = 0; + uint32_t line_len; + uint32_t coarse_int = 0; + uint32_t vt_pix_clk = 0; + if (sensor->framesize == FRAMESIZE_QVGA) { + line_len = HIMAX_LINE_LEN_PCK_QVGA; + } else { + line_len = HIMAX_LINE_LEN_PCK_QQVGA; + } + ret |= get_vt_pix_clk(sensor, &vt_pix_clk); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, INTEGRATION_H, &((uint8_t *) &coarse_int)[1]); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, INTEGRATION_L, &((uint8_t *) &coarse_int)[0]); + *exposure_us = fast_roundf(coarse_int * line_len / (vt_pix_clk / 1000000.0f)); + return ret; +} + +static int set_hmirror(sensor_t *sensor, int enable) { + uint8_t reg; + int ret = omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, IMG_ORIENTATION, ®); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, IMG_ORIENTATION, HIMAX_SET_HMIRROR(reg, enable)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, GRP_PARAM_HOLD, 0x01); + return ret; +} + +static int set_vflip(sensor_t *sensor, int enable) { + uint8_t reg; + int ret = omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, IMG_ORIENTATION, ®); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, IMG_ORIENTATION, HIMAX_SET_VMIRROR(reg, enable)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, GRP_PARAM_HOLD, 0x01); + return ret; +} + +static int ioctl(sensor_t *sensor, int request, va_list ap) { + int ret = 0; + + switch (request) { + case IOCTL_HIMAX_OSC_ENABLE: { + uint32_t enable = va_arg(ap, uint32_t); + ret = omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, ANA_Register_17, enable ? 1:0); + mp_hal_delay_ms(100); + break; + } + + case IOCTL_HIMAX_MD_ENABLE: { + uint32_t enable = va_arg(ap, uint32_t); + ret = omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MD_CTRL, enable ? 1:0); + break; + } + + case IOCTL_HIMAX_MD_WINDOW: { + uint32_t x1 = va_arg(ap, uint32_t); + uint32_t y1 = va_arg(ap, uint32_t); + uint32_t x2 = va_arg(ap, uint32_t) + x1; + uint32_t y2 = va_arg(ap, uint32_t) + y1; + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MD_LROI_X_START_H, (x1 >> 8)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MD_LROI_X_START_L, (x1 & 0xff)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MD_LROI_Y_START_H, (y1 >> 8)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MD_LROI_Y_START_L, (y1 & 0xff)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MD_LROI_X_END_H, (x2 >> 8)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MD_LROI_X_END_L, (x2 & 0xff)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MD_LROI_Y_END_H, (y2 >> 8)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MD_LROI_Y_END_L, (y2 & 0xff)); + break; + } + + case IOCTL_HIMAX_MD_THRESHOLD: { + uint32_t threshold = va_arg(ap, uint32_t); + ret = omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MD_THL, threshold); + break; + } + + case IOCTL_HIMAX_MD_CLEAR: { + ret = omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, I2C_CLEAR, 1); + break; + } + + default: { + ret = -1; + break; + } + } + + return ret; +} + +int hm01b0_init(sensor_t *sensor) { + // Initialize sensor structure. + sensor->reset = reset; + sensor->read_reg = read_reg; + sensor->write_reg = write_reg; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_framerate = set_framerate; + sensor->set_brightness = set_brightness; + sensor->set_gainceiling = set_gainceiling; + sensor->set_colorbar = set_colorbar; + sensor->set_auto_gain = set_auto_gain; + sensor->get_gain_db = get_gain_db; + sensor->set_auto_exposure = set_auto_exposure; + sensor->get_exposure_us = get_exposure_us; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + sensor->ioctl = ioctl; + + // Set sensor flags + sensor->hw_flags.vsync = 0; + sensor->hw_flags.hsync = 0; + sensor->hw_flags.pixck = 0; + sensor->hw_flags.fsync = 0; + sensor->hw_flags.jpege = 0; + sensor->hw_flags.gs_bpp = 1; + + return 0; +} +#endif //(OMV_ENABLE_HM01B0 == 1) diff --git a/components/3rd_party/omv/omv/sensors/hm01b0.h b/components/3rd_party/omv/omv/sensors/hm01b0.h new file mode 100644 index 00000000..201a7bba --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/hm01b0.h @@ -0,0 +1,15 @@ +/* + * 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. + * + * HM01B0 driver. + */ +#ifndef __HM01B0_H__ +#define __HM01B0_H__ +#define HM01B0_XCLK_FREQ (6000000) +int hm01b0_init(sensor_t *sensor); +#endif // __HM01B0_H__ diff --git a/components/3rd_party/omv/omv/sensors/hm01b0_regs.h b/components/3rd_party/omv/omv/sensors/hm01b0_regs.h new file mode 100644 index 00000000..3f0f7c18 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/hm01b0_regs.h @@ -0,0 +1,126 @@ +/* + * 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. + * + * HM01B0 register definitions. + */ +#ifndef __REG_REGS_H__ +#define __REG_REGS_H__ + +// Read only registers +#define MODEL_ID_H 0x0000 +#define MODEL_ID_L 0x0001 +#define FRAME_COUNT 0x0005 +#define PIXEL_ORDER 0x0006 +// Sensor mode control +#define MODE_SELECT 0x0100 +#define IMG_ORIENTATION 0x0101 +#define SW_RESET 0x0103 +#define GRP_PARAM_HOLD 0x0104 +// Sensor exposure gain control +#define INTEGRATION_H 0x0202 +#define INTEGRATION_L 0x0203 +#define ANALOG_GAIN 0x0205 +#define DIGITAL_GAIN_H 0x020E +#define DIGITAL_GAIN_L 0x020F +// Frame timing control +#define FRAME_LEN_LINES_H 0x0340 +#define FRAME_LEN_LINES_L 0x0341 +#define LINE_LEN_PCK_H 0x0342 +#define LINE_LEN_PCK_L 0x0343 +// Binning mode control +#define READOUT_X 0x0383 +#define READOUT_Y 0x0387 +#define BINNING_MODE 0x0390 +// Test pattern control +#define TEST_PATTERN_MODE 0x0601 +// Black level control +#define BLC_CFG 0x1000 +#define BLC_TGT 0x1003 +#define BLI_EN 0x1006 +#define BLC2_TGT 0x1007 +// Sensor reserved +#define DPC_CTRL 0x1008 +#define SINGLE_THR_HOT 0x100B +#define SINGLE_THR_COLD 0x100C +// VSYNC,HSYNC and pixel shift register +#define VSYNC_HSYNC_PIXEL_SHIFT_EN 0x1012 +// Automatic exposure gain control +#define AE_CTRL 0x2100 +#define AE_TARGET_MEAN 0x2101 +#define AE_MIN_MEAN 0x2102 +#define CONVERGE_IN_TH 0x2103 +#define CONVERGE_OUT_TH 0x2104 +#define MAX_INTG_H 0x2105 +#define MAX_INTG_L 0x2106 +#define MIN_INTG 0x2107 +#define MAX_AGAIN_FULL 0x2108 +#define MAX_AGAIN_BIN2 0x2109 +#define MIN_AGAIN 0x210A +#define MAX_DGAIN 0x210B +#define MIN_DGAIN 0x210C +#define DAMPING_FACTOR 0x210D +#define FS_CTRL 0x210E +#define FS_60HZ_H 0x210F +#define FS_60HZ_L 0x2110 +#define FS_50HZ_H 0x2111 +#define FS_50HZ_L 0x2112 +#define FS_HYST_TH 0x2113 +// Motion detection control +#define MD_CTRL 0x2150 +#define I2C_CLEAR 0x2153 +#define WMEAN_DIFF_TH_H 0x2155 +#define WMEAN_DIFF_TH_M 0x2156 +#define WMEAN_DIFF_TH_L 0x2157 +#define MD_THH 0x2158 +#define MD_THM1 0x2159 +#define MD_THM2 0x215A +#define MD_THL 0x215B +#define STATISTIC_CTRL 0x2000 +#define MD_LROI_X_START_H 0x2011 +#define MD_LROI_X_START_L 0x2012 +#define MD_LROI_Y_START_H 0x2013 +#define MD_LROI_Y_START_L 0x2014 +#define MD_LROI_X_END_H 0x2015 +#define MD_LROI_X_END_L 0x2016 +#define MD_LROI_Y_END_H 0x2017 +#define MD_LROI_Y_END_L 0x2018 +#define MD_INTERRUPT 0x2160 +// Sensor timing control +#define QVGA_WIN_EN 0x3010 +#define SIX_BIT_MODE_EN 0x3011 +#define PMU_AUTOSLEEP_FRAMECNT 0x3020 +#define ADVANCE_VSYNC 0x3022 +#define ADVANCE_HSYNC 0x3023 +#define EARLY_GAIN 0x3035 +// IO and clock control +#define BIT_CONTROL 0x3059 +#define OSC_CLK_DIV 0x3060 +#define ANA_Register_11 0x3061 +#define IO_DRIVE_STR 0x3062 +#define IO_DRIVE_STR2 0x3063 +#define ANA_Register_14 0x3064 +#define OUTPUT_PIN_STATUS_CONTROL 0x3065 +#define ANA_Register_17 0x3067 +#define PCLK_POLARITY 0x3068 +/* + * Useful value of Himax registers + */ +#define HIMAX_RESET 0x01 +#define HIMAX_MODE_STANDBY 0x00 +#define HIMAX_MODE_STREAMING 0x01 // I2C triggered streaming enable +#define HIMAX_MODE_STREAMING_NFRAMES 0x03 // Output N frames +#define HIMAX_MODE_STREAMING_TRIG 0x05 // Hardware Trigger +#define HIMAX_SET_HMIRROR(r, x) ((r & 0xFE) | ((x & 1) << 0)) +#define HIMAX_SET_VMIRROR(r, x) ((r & 0xFD) | ((x & 1) << 1)) + +#define PCLK_RISING_EDGE 0x00 +#define PCLK_FALLING_EDGE 0x01 +#define AE_CTRL_ENABLE 0x00 +#define AE_CTRL_DISABLE 0x01 + +#endif //__REG_REGS_H__ diff --git a/components/3rd_party/omv/omv/sensors/hm0360.c b/components/3rd_party/omv/omv/sensors/hm0360.c new file mode 100644 index 00000000..84cc2554 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/hm0360.c @@ -0,0 +1,774 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2022 Ibrahim Abdelkader + * Copyright (c) 2013-2022 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * HM0360 driver. + */ +#include "omv_boardconfig.h" +#if (OMV_ENABLE_HM0360 == 1) + +#include +#include +#include +#include "omv_i2c.h" +#include "sensor.h" +#include "hm0360.h" +#include "hm0360_regs.h" +#include "py/mphal.h" + +#define HIMAX_BOOT_RETRY (10) +#define HIMAX_LINE_LEN_PCK_VGA 0x300 +#define HIMAX_FRAME_LENGTH_VGA 0x214 + +#define HIMAX_LINE_LEN_PCK_QVGA 0x178 +#define HIMAX_FRAME_LENGTH_QVGA 0x109 + +#define HIMAX_LINE_LEN_PCK_QQVGA 0x178 +#define HIMAX_FRAME_LENGTH_QQVGA 0x084 + +#define HIMAX_MD_ROI_VGA_W 40 +#define HIMAX_MD_ROI_VGA_H 30 + +#define HIMAX_MD_ROI_QVGA_W 20 +#define HIMAX_MD_ROI_QVGA_H 15 + +#define HIMAX_MD_ROI_QQVGA_W 10 +#define HIMAX_MD_ROI_QQVGA_H 8 + +static const uint16_t default_regs[][2] = { + {SW_RESET, 0x00}, + {MONO_MODE, 0x00}, + {MONO_MODE_ISP, 0x01}, + {MONO_MODE_SEL, 0x01}, + + // BLC control + {0x1000, 0x01}, + {0x1003, 0x04}, + {BLC_TGT, 0x04}, + {0x1007, 0x01}, + {0x1008, 0x04}, + {BLC2_TGT, 0x04}, + {MONO_CTRL, 0x01}, + + // Output format control + {OPFM_CTRL, 0x0C}, + + // Reserved regs + {0x101D, 0x00}, + {0x101E, 0x01}, + {0x101F, 0x00}, + {0x1020, 0x01}, + {0x1021, 0x00}, + + {CMPRS_CTRL, 0x00}, + {CMPRS_01, 0x09}, + {CMPRS_02, 0x12}, + {CMPRS_03, 0x23}, + {CMPRS_04, 0x31}, + {CMPRS_05, 0x3E}, + {CMPRS_06, 0x4B}, + {CMPRS_07, 0x56}, + {CMPRS_08, 0x5E}, + {CMPRS_09, 0x65}, + {CMPRS_10, 0x72}, + {CMPRS_11, 0x7F}, + {CMPRS_12, 0x8C}, + {CMPRS_13, 0x98}, + {CMPRS_14, 0xB2}, + {CMPRS_15, 0xCC}, + {CMPRS_16, 0xE6}, + + {0x3112, 0x00}, // PCLKO_polarity falling + + {PLL1_CONFIG, 0x08}, // Core = 24MHz PCLKO = 24MHz I2C = 12MHz + {PLL2_CONFIG, 0x0A}, // MIPI pre-dev (default) + {PLL3_CONFIG, 0x77}, // PMU/MIPI pre-dev (default) + + {PMU_CFG_3, 0x08}, // Disable context switching + {PAD_REGISTER_07, 0x00}, // PCLKO_polarity falling + + {AE_CTRL, 0x5F}, // Automatic Exposure (NOTE: Auto framerate enabled) + {AE_CTRL1, 0x00}, + {T_DAMPING, 0x20}, // AE T damping factor + {N_DAMPING, 0x00}, // AE N damping factor + {AE_TARGET_MEAN, 0x64}, // AE target + {AE_MIN_MEAN, 0x0A}, // AE min target mean + {AE_TARGET_ZONE, 0x23}, // AE target zone + {CONVERGE_IN_TH, 0x03}, // AE converge in threshold + {CONVERGE_OUT_TH, 0x05}, // AE converge out threshold + {MAX_INTG_H, (HIMAX_FRAME_LENGTH_QVGA - 4) >> 8}, + {MAX_INTG_L, (HIMAX_FRAME_LENGTH_QVGA - 4) & 0xFF}, + + {MAX_AGAIN, 0x04}, // Maximum analog gain + {MAX_DGAIN_H, 0x03}, + {MAX_DGAIN_L, 0x3F}, + {INTEGRATION_H, 0x01}, + {INTEGRATION_L, 0x08}, + + {MD_CTRL, 0x6A}, + {MD_TH_MIN, 0x01}, + {MD_BLOCK_NUM_TH, 0x01}, + {MD_CTRL1, 0x06}, + {PULSE_MODE, 0x00}, // Interrupt in level mode. + {ROI_START_END_V, 0xF0}, + {ROI_START_END_H, 0xF0}, + + {FRAME_LEN_LINES_H, HIMAX_FRAME_LENGTH_QVGA >> 8}, + {FRAME_LEN_LINES_L, HIMAX_FRAME_LENGTH_QVGA & 0xFF}, + {LINE_LEN_PCK_H, HIMAX_LINE_LEN_PCK_QVGA >> 8}, + {LINE_LEN_PCK_L, HIMAX_LINE_LEN_PCK_QVGA & 0xFF}, + {H_SUBSAMPLE, 0x01}, + {V_SUBSAMPLE, 0x01}, + {BINNING_MODE, 0x00}, + {WIN_MODE, 0x00}, + {IMG_ORIENTATION, 0x00}, + {COMMAND_UPDATE, 0x01}, + + /// SYNC function config. + {0x3010, 0x00}, + {0x3013, 0x01}, + {0x3019, 0x00}, + {0x301A, 0x00}, + {0x301B, 0x20}, + {0x301C, 0xFF}, + + // PREMETER config. + {0x3026, 0x03}, + {0x3027, 0x81}, + {0x3028, 0x01}, + {0x3029, 0x00}, + {0x302A, 0x30}, + {0x302E, 0x00}, + {0x302F, 0x00}, + + // Magic regs 🪄. + {0x302B, 0x2A}, + {0x302C, 0x00}, + {0x302D, 0x03}, + {0x3031, 0x01}, + {0x3051, 0x00}, + {0x305C, 0x03}, + {0x3060, 0x00}, + {0x3061, 0xFA}, + {0x3062, 0xFF}, + {0x3063, 0xFF}, + {0x3064, 0xFF}, + {0x3065, 0xFF}, + {0x3066, 0xFF}, + {0x3067, 0xFF}, + {0x3068, 0xFF}, + {0x3069, 0xFF}, + {0x306A, 0xFF}, + {0x306B, 0xFF}, + {0x306C, 0xFF}, + {0x306D, 0xFF}, + {0x306E, 0xFF}, + {0x306F, 0xFF}, + {0x3070, 0xFF}, + {0x3071, 0xFF}, + {0x3072, 0xFF}, + {0x3073, 0xFF}, + {0x3074, 0xFF}, + {0x3075, 0xFF}, + {0x3076, 0xFF}, + {0x3077, 0xFF}, + {0x3078, 0xFF}, + {0x3079, 0xFF}, + {0x307A, 0xFF}, + {0x307B, 0xFF}, + {0x307C, 0xFF}, + {0x307D, 0xFF}, + {0x307E, 0xFF}, + {0x307F, 0xFF}, + {0x3080, 0x01}, + {0x3081, 0x01}, + {0x3082, 0x03}, + {0x3083, 0x20}, + {0x3084, 0x00}, + {0x3085, 0x20}, + {0x3086, 0x00}, + {0x3087, 0x20}, + {0x3088, 0x00}, + {0x3089, 0x04}, + {0x3094, 0x02}, + {0x3095, 0x02}, + {0x3096, 0x00}, + {0x3097, 0x02}, + {0x3098, 0x00}, + {0x3099, 0x02}, + {0x309E, 0x05}, + {0x309F, 0x02}, + {0x30A0, 0x02}, + {0x30A1, 0x00}, + {0x30A2, 0x08}, + {0x30A3, 0x00}, + {0x30A4, 0x20}, + {0x30A5, 0x04}, + {0x30A6, 0x02}, + {0x30A7, 0x02}, + {0x30A8, 0x01}, + {0x30A9, 0x00}, + {0x30AA, 0x02}, + {0x30AB, 0x34}, + {0x30B0, 0x03}, + {0x30C4, 0x10}, + {0x30C5, 0x01}, + {0x30C6, 0xBF}, + {0x30C7, 0x00}, + {0x30C8, 0x00}, + {0x30CB, 0xFF}, + {0x30CC, 0xFF}, + {0x30CD, 0x7F}, + {0x30CE, 0x7F}, + {0x30D3, 0x01}, + {0x30D4, 0xFF}, + {0x30D5, 0x00}, + {0x30D6, 0x40}, + {0x30D7, 0x00}, + {0x30D8, 0xA7}, + {0x30D9, 0x05}, + {0x30DA, 0x01}, + {0x30DB, 0x40}, + {0x30DC, 0x00}, + {0x30DD, 0x27}, + {0x30DE, 0x05}, + {0x30DF, 0x07}, + {0x30E0, 0x40}, + {0x30E1, 0x00}, + {0x30E2, 0x27}, + {0x30E3, 0x05}, + {0x30E4, 0x47}, + {0x30E5, 0x30}, + {0x30E6, 0x00}, + {0x30E7, 0x27}, + {0x30E8, 0x05}, + {0x30E9, 0x87}, + {0x30EA, 0x30}, + {0x30EB, 0x00}, + {0x30EC, 0x27}, + {0x30ED, 0x05}, + {0x30EE, 0x00}, + {0x30EF, 0x40}, + {0x30F0, 0x00}, + {0x30F1, 0xA7}, + {0x30F2, 0x05}, + {0x30F3, 0x01}, + {0x30F4, 0x40}, + {0x30F5, 0x00}, + {0x30F6, 0x27}, + {0x30F7, 0x05}, + {0x30F8, 0x07}, + {0x30F9, 0x40}, + {0x30FA, 0x00}, + {0x30FB, 0x27}, + {0x30FC, 0x05}, + {0x30FD, 0x47}, + {0x30FE, 0x30}, + {0x30FF, 0x00}, + {0x3100, 0x27}, + {0x3101, 0x05}, + {0x3102, 0x87}, + {0x3103, 0x30}, + {0x3104, 0x00}, + {0x3105, 0x27}, + {0x3106, 0x05}, + {0x310B, 0x10}, + {0x3113, 0xA0}, + {0x3114, 0x67}, + {0x3115, 0x42}, + {0x3116, 0x10}, + {0x3117, 0x0A}, + {0x3118, 0x3F}, + {0x311C, 0x10}, + {0x311D, 0x06}, + {0x311E, 0x0F}, + {0x311F, 0x0E}, + {0x3120, 0x0D}, + {0x3121, 0x0F}, + {0x3122, 0x00}, + {0x3123, 0x1D}, + {0x3126, 0x03}, + {0x3128, 0x57}, + {0x312A, 0x11}, + {0x312B, 0x41}, + {0x312E, 0x00}, + {0x312F, 0x00}, + {0x3130, 0x0C}, + {0x3141, 0x2A}, + {0x3142, 0x9F}, + {0x3147, 0x18}, + {0x3149, 0x18}, + {0x314B, 0x01}, + {0x3150, 0x50}, + {0x3152, 0x00}, + {0x3156, 0x2C}, + {0x315A, 0x0A}, + {0x315B, 0x2F}, + {0x315C, 0xE0}, + {0x315F, 0x02}, + {0x3160, 0x1F}, + {0x3163, 0x1F}, + {0x3164, 0x7F}, + {0x3165, 0x7F}, + {0x317B, 0x94}, + {0x317C, 0x00}, + {0x317D, 0x02}, + {0x318C, 0x00}, + + {COMMAND_UPDATE, 0x01}, + {0x0000, 0x00}, +}; + +static int reset(sensor_t *sensor) { + // Reset sensor. + uint8_t reg = 0xff; + for (int retry = HIMAX_BOOT_RETRY; retry >= 0 && reg != HIMAX_MODE_STANDBY; retry--) { + if (omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, SW_RESET, HIMAX_RESET) != 0) { + return -1; + } + + mp_hal_delay_ms(1); + + if (omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, MODE_SELECT, ®) != 0) { + return -1; + } + + if (reg == HIMAX_MODE_STANDBY) { + break; + } else if (retry == 0) { + return -1; + } + + mp_hal_delay_ms(10); + } + + // Write default registers + int ret = 0; + for (int i = 0; default_regs[i][0] && ret == 0; i++) { + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, default_regs[i][0], default_regs[i][1]); + } + + // Set mode to streaming + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MODE_SELECT, HIMAX_MODE_STREAMING); + + return ret; +} + +static int read_reg(sensor_t *sensor, uint16_t reg_addr) { + uint8_t reg_data; + if (omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, reg_addr, ®_data) != 0) { + return -1; + } + return reg_data; +} + +static int write_reg(sensor_t *sensor, uint16_t reg_addr, uint16_t reg_data) { + return omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, reg_addr, reg_data); +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) { + switch (pixformat) { + case PIXFORMAT_BAYER: + case PIXFORMAT_GRAYSCALE: + return 0; + default: + return -1; + } +} + +static const uint16_t VGA_regs[][2] = { + {PLL1_CONFIG, 0x08}, // Core = 24MHz PCLKO = 24MHz I2C = 12MHz + {H_SUBSAMPLE, 0x00}, + {V_SUBSAMPLE, 0x00}, + {BINNING_MODE, 0x00}, + {WIN_MODE, 0x00}, + {MAX_INTG_H, (HIMAX_FRAME_LENGTH_VGA - 4) >> 8}, + {MAX_INTG_L, (HIMAX_FRAME_LENGTH_VGA - 4) & 0xFF}, + {FRAME_LEN_LINES_H, (HIMAX_FRAME_LENGTH_VGA >> 8)}, + {FRAME_LEN_LINES_L, (HIMAX_FRAME_LENGTH_VGA & 0xFF)}, + {LINE_LEN_PCK_H, (HIMAX_LINE_LEN_PCK_VGA >> 8)}, + {LINE_LEN_PCK_L, (HIMAX_LINE_LEN_PCK_VGA & 0xFF)}, + {ROI_START_END_H, 0xF0}, + {ROI_START_END_V, 0xE0}, + {COMMAND_UPDATE, 0x01}, + {0x0000, 0x00}, +}; + +static const uint16_t QVGA_regs[][2] = { + {PLL1_CONFIG, 0x09}, // Core = 12MHz PCLKO = 24MHz I2C = 12MHz + {H_SUBSAMPLE, 0x01}, + {V_SUBSAMPLE, 0x01}, + {BINNING_MODE, 0x00}, + {WIN_MODE, 0x00}, + {MAX_INTG_H, (HIMAX_FRAME_LENGTH_QVGA - 4) >> 8}, + {MAX_INTG_L, (HIMAX_FRAME_LENGTH_QVGA - 4) & 0xFF}, + {FRAME_LEN_LINES_H, (HIMAX_FRAME_LENGTH_QVGA >> 8)}, + {FRAME_LEN_LINES_L, (HIMAX_FRAME_LENGTH_QVGA & 0xFF)}, + {LINE_LEN_PCK_H, (HIMAX_LINE_LEN_PCK_QVGA >> 8)}, + {LINE_LEN_PCK_L, (HIMAX_LINE_LEN_PCK_QVGA & 0xFF)}, + {ROI_START_END_H, 0xF0}, + {ROI_START_END_V, 0xE0}, + {COMMAND_UPDATE, 0x01}, + {0x0000, 0x00}, +}; + +static const uint16_t QQVGA_regs[][2] = { + {PLL1_CONFIG, 0x09}, // Core = 12MHz PCLKO = 24MHz I2C = 12MHz + {H_SUBSAMPLE, 0x02}, + {V_SUBSAMPLE, 0x02}, + {BINNING_MODE, 0x00}, + {WIN_MODE, 0x00}, + {MAX_INTG_H, (HIMAX_FRAME_LENGTH_QQVGA - 4) >> 8}, + {MAX_INTG_L, (HIMAX_FRAME_LENGTH_QQVGA - 4) & 0xFF}, + {FRAME_LEN_LINES_H, (HIMAX_FRAME_LENGTH_QQVGA >> 8)}, + {FRAME_LEN_LINES_L, (HIMAX_FRAME_LENGTH_QQVGA & 0xFF)}, + {LINE_LEN_PCK_H, (HIMAX_LINE_LEN_PCK_QQVGA >> 8)}, + {LINE_LEN_PCK_L, (HIMAX_LINE_LEN_PCK_QQVGA & 0xFF)}, + {ROI_START_END_H, 0xF0}, + {ROI_START_END_V, 0xD0}, + {COMMAND_UPDATE, 0x01}, + {0x0000, 0x00}, +}; + +static int set_framesize(sensor_t *sensor, framesize_t framesize) { + int ret = 0; + switch (framesize) { + case FRAMESIZE_VGA: + for (int i = 0; VGA_regs[i][0] && ret == 0; i++) { + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, VGA_regs[i][0], VGA_regs[i][1]); + } + break; + case FRAMESIZE_QVGA: + for (int i = 0; QVGA_regs[i][0] && ret == 0; i++) { + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, QVGA_regs[i][0], QVGA_regs[i][1]); + } + break; + case FRAMESIZE_QQVGA: + for (int i = 0; QQVGA_regs[i][0] && ret == 0; i++) { + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, QQVGA_regs[i][0], QQVGA_regs[i][1]); + } + break; + default: + ret = -1; + } + return ret; +} + +static int set_framerate(sensor_t *sensor, int framerate) { + int ret = 0; + uint8_t pll_cfg = 0; + uint8_t osc_div = 0; + bool highres = false; + + if (sensor->framesize == FRAMESIZE_VGA) { + highres = true; + } + + if (framerate <= 10) { + osc_div = (highres == true) ? 0x03 : 0x03; + } else if (framerate <= 15) { + osc_div = (highres == true) ? 0x02 : 0x03; + } else if (framerate <= 30) { + osc_div = (highres == true) ? 0x01 : 0x02; + } else { + // Set to the max possible FPS at this resolution. + osc_div = (highres == true) ? 0x00 : 0x01; + } + + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, PLL1_CONFIG, &pll_cfg); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, PLL1_CONFIG, (pll_cfg & 0xFC) | osc_div); + return ret; +} + +static int set_brightness(sensor_t *sensor, int level) { + uint8_t ae_mean; + // Simulate brightness levels by setting AE loop target mean. + switch (level) { + case 1: + ae_mean = 150; + break; + case 2: + ae_mean = 200; + break; + case 3: + ae_mean = 250; + break; + case 0: + default: + ae_mean = 100; + break; + } + return omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AE_TARGET_MEAN, ae_mean); +} + +static int set_gainceiling(sensor_t *sensor, gainceiling_t gainceiling) { + int ret = 0; + int gain = 0x0; + switch (gainceiling) { + case GAINCEILING_2X: + gain = 0x01; + break; + case GAINCEILING_4X: + gain = 0x02; + break; + case GAINCEILING_8X: + gain = 0x03; + break; + case GAINCEILING_16X: + gain = 0x04; + break; + default: + return -1; + } + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MAX_AGAIN, (gain & 0x07)); + return ret; +} + +static int set_colorbar(sensor_t *sensor, int enable) { + return omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TEST_PATTERN_MODE, enable & 0x1); +} + +static int set_auto_gain(sensor_t *sensor, int enable, float gain_db, float gain_db_ceiling) { + uint8_t ae_ctrl = 0; + int ret = omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AE_CTRL, &ae_ctrl); + if (!enable && (!isnanf(gain_db)) && (!isinff(gain_db))) { + gain_db = IM_MAX(IM_MIN(gain_db, 24.0f), 0.0f); + uint8_t gain = fast_ceilf(logf(expf((gain_db / 20.0f) * M_LN10)) / M_LN2); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AE_CTRL, (ae_ctrl & 0xFE)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, ANALOG_GAIN, ((gain & 0x7) << 4)); + } else if (enable && (!isnanf(gain_db_ceiling)) && (!isinff(gain_db_ceiling))) { + gain_db_ceiling = IM_MAX(IM_MIN(gain_db_ceiling, 24.0f), 0.0f); + uint8_t gain = fast_ceilf(logf(expf((gain_db_ceiling / 20.0f) * M_LN10)) / M_LN2); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MAX_AGAIN, (gain & 0x07)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AE_CTRL, (ae_ctrl | 0x01)); + } + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, COMMAND_UPDATE, 0x01); + return ret; +} + +static int get_gain_db(sensor_t *sensor, float *gain_db) { + uint8_t gain; + if (omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, ANALOG_GAIN, &gain) != 0) { + return -1; + } + *gain_db = fast_floorf(log10f(1 << (gain >> 4)) * 20.0f); + return 0; +} + +static int get_vt_pix_clk(sensor_t *sensor, uint32_t *vt_pix_clk) { + uint8_t reg; + if (omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, PLL1_CONFIG, ®) != 0) { + return -1; + } + // 00 -> MCLK / 1 + // 01 -> MCLK / 2 + // 10 -> MCLK / 4 + // 11 -> MCLK / 8 + uint32_t vt_sys_div = (1 << (reg & 0x03)); + + // vt_pix_clk = MCLK / vt_sys_div + *vt_pix_clk = OMV_XCLK_FREQUENCY / vt_sys_div; + return 0; +} + +static int set_auto_exposure(sensor_t *sensor, int enable, int exposure_us) { + uint8_t ae_ctrl = 0; + int ret = omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AE_CTRL, &ae_ctrl); + + if (enable) { + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AE_CTRL, (ae_ctrl | 0x01)); + } else { + uint32_t line_len; + uint32_t frame_len; + uint32_t coarse_int; + uint32_t vt_pix_clk = 0; + + switch (sensor->framesize) { + case FRAMESIZE_VGA: + line_len = HIMAX_LINE_LEN_PCK_VGA; + frame_len = HIMAX_FRAME_LENGTH_VGA; + break; + case FRAMESIZE_QVGA: + line_len = HIMAX_LINE_LEN_PCK_QVGA; + frame_len = HIMAX_FRAME_LENGTH_QVGA; + break; + case FRAMESIZE_QQVGA: + line_len = HIMAX_LINE_LEN_PCK_QQVGA; + frame_len = HIMAX_FRAME_LENGTH_QQVGA; + break; + default: + return -1; + } + + ret |= get_vt_pix_clk(sensor, &vt_pix_clk); + coarse_int = fast_roundf(exposure_us * (vt_pix_clk / 1000000.0f) / line_len); + + if (coarse_int < 2) { + coarse_int = 2; + } else if (coarse_int > (frame_len - 4)) { + coarse_int = frame_len - 4; + } + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AE_CTRL, (ae_ctrl & 0xFE)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, INTEGRATION_H, coarse_int >> 8); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, INTEGRATION_L, coarse_int & 0xff); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, COMMAND_UPDATE, 0x01); + } + + return ret; +} + +static int get_exposure_us(sensor_t *sensor, int *exposure_us) { + int ret = 0; + uint32_t line_len; + uint32_t coarse_int = 0; + uint32_t vt_pix_clk = 0; + switch (sensor->framesize) { + case FRAMESIZE_VGA: + line_len = HIMAX_LINE_LEN_PCK_VGA; + break; + case FRAMESIZE_QVGA: + line_len = HIMAX_LINE_LEN_PCK_QVGA; + break; + case FRAMESIZE_QQVGA: + line_len = HIMAX_LINE_LEN_PCK_QQVGA; + break; + default: + return -1; + } + ret |= get_vt_pix_clk(sensor, &vt_pix_clk); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, INTEGRATION_H, &((uint8_t *) &coarse_int)[1]); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, INTEGRATION_L, &((uint8_t *) &coarse_int)[0]); + *exposure_us = fast_roundf(coarse_int * line_len / (vt_pix_clk / 1000000.0f)); + return ret; +} + +static int set_hmirror(sensor_t *sensor, int enable) { + uint8_t reg; + int ret = omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, IMG_ORIENTATION, ®); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, IMG_ORIENTATION, HIMAX_SET_HMIRROR(reg, enable)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, COMMAND_UPDATE, 0x01); + return ret; +} + +static int set_vflip(sensor_t *sensor, int enable) { + uint8_t reg; + int ret = omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, IMG_ORIENTATION, ®); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, IMG_ORIENTATION, HIMAX_SET_VMIRROR(reg, enable)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, COMMAND_UPDATE, 0x01); + return ret; +} + +static int ioctl(sensor_t *sensor, int request, va_list ap) { + int ret = 0; + + switch (request) { + case IOCTL_HIMAX_OSC_ENABLE: { + break; + } + + case IOCTL_HIMAX_MD_ENABLE: { + int ret = 0; + uint8_t md_ctrl = 0; + uint32_t enable = va_arg(ap, uint32_t) & 0x01; + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, MD_CTRL, &md_ctrl); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MD_CTRL, (md_ctrl & 0xFE) | enable); + break; + } + + case IOCTL_HIMAX_MD_WINDOW: { + int ret = 0; + int32_t roi_w = 0; + int32_t roi_h = 0; + int32_t roi_max_h = 14; + + int32_t x1 = va_arg(ap, int32_t); + int32_t y1 = va_arg(ap, int32_t); + int32_t x2 = va_arg(ap, int32_t) + x1; + int32_t y2 = va_arg(ap, int32_t) + y1; + + switch (sensor->framesize) { + case FRAMESIZE_VGA: + roi_w = HIMAX_MD_ROI_VGA_W; + roi_h = HIMAX_MD_ROI_VGA_H; + roi_max_h = 14; + break; + case FRAMESIZE_QVGA: + roi_w = HIMAX_MD_ROI_QVGA_W; + roi_h = HIMAX_MD_ROI_QVGA_H; + roi_max_h = 14; + break; + case FRAMESIZE_QQVGA: + roi_w = HIMAX_MD_ROI_QQVGA_W; + roi_h = HIMAX_MD_ROI_QQVGA_H; + roi_max_h = 13; + break; + default: + return -1; + } + + x1 = MAX((x1 / roi_w - 1), 0); + y1 = MAX((y1 / roi_h - 1), 0); + x2 = MIN((x2 / roi_w) + !!(x2 % roi_w), 0xF); + y2 = MIN((y2 / roi_h) + !!(y2 % roi_h), roi_max_h); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, ROI_START_END_H, ((x2 & 0xF) << 4) | (x1 & 0x0F)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, ROI_START_END_V, ((y2 & 0xF) << 4) | (y1 & 0x0F)); + break; + } + + case IOCTL_HIMAX_MD_THRESHOLD: { + uint32_t threshold = va_arg(ap, uint32_t) & 0x3F; + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MD_TH_STR_L, threshold); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MD_TH_STR_H, threshold); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MD_LIGHT_COEF, threshold); + break; + } + + case IOCTL_HIMAX_MD_CLEAR: { + ret = omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, INT_CLEAR, (1 << 3)); + break; + } + + default: { + ret = -1; + break; + } + } + + return ret; +} + +int hm0360_init(sensor_t *sensor) { + // Initialize sensor structure. + sensor->reset = reset; + sensor->read_reg = read_reg; + sensor->write_reg = write_reg; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_framerate = set_framerate; + sensor->set_brightness = set_brightness; + sensor->set_gainceiling = set_gainceiling; + sensor->set_colorbar = set_colorbar; + sensor->set_auto_gain = set_auto_gain; + sensor->get_gain_db = get_gain_db; + sensor->set_auto_exposure = set_auto_exposure; + sensor->get_exposure_us = get_exposure_us; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + sensor->ioctl = ioctl; + + // Set sensor flags + sensor->hw_flags.vsync = 0; + sensor->hw_flags.hsync = 0; + sensor->hw_flags.pixck = 0; + sensor->hw_flags.fsync = 0; + sensor->hw_flags.jpege = 0; + sensor->hw_flags.gs_bpp = 1; + + return 0; +} +#endif //(OMV_ENABLE_HM0360 == 1) diff --git a/components/3rd_party/omv/omv/sensors/hm0360.h b/components/3rd_party/omv/omv/sensors/hm0360.h new file mode 100644 index 00000000..f42bec09 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/hm0360.h @@ -0,0 +1,16 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2022 Ibrahim Abdelkader + * Copyright (c) 2013-2022 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * HM0360 driver. + */ +#ifndef __HM0360_H__ +#define __HM0360_H__ +// This sensor uses an internal oscillator. +#define HM0360_XCLK_FREQ (0) +int hm0360_init(sensor_t *sensor); +#endif // __HM0360_H__ diff --git a/components/3rd_party/omv/omv/sensors/hm0360_regs.h b/components/3rd_party/omv/omv/sensors/hm0360_regs.h new file mode 100644 index 00000000..d57aa83d --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/hm0360_regs.h @@ -0,0 +1,154 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2022 Ibrahim Abdelkader + * Copyright (c) 2013-2022 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * HM0360 register definitions. + */ +#ifndef __REG_REGS_H__ +#define __REG_REGS_H__ + +// Read only registers +#define MODEL_ID_H 0x0000 +#define MODEL_ID_L 0x0001 +#define SILICON_REV 0x0002 +#define FRAME_COUNT_H 0x0005 +#define FRAME_COUNT_L 0x0006 +#define PIXEL_ORDER 0x0007 +// Sensor mode control +#define MODE_SELECT 0x0100 +#define IMG_ORIENTATION 0x0101 +#define EMBEDDED_LINE_EN 0x0102 +#define SW_RESET 0x0103 +#define COMMAND_UPDATE 0x0104 +// Sensor exposure gain control +#define INTEGRATION_H 0x0202 +#define INTEGRATION_L 0x0203 +#define ANALOG_GAIN 0x0205 +#define DIGITAL_GAIN_H 0x020E +#define DIGITAL_GAIN_L 0x020F +// Clock control +#define PLL1_CONFIG 0x0300 +#define PLL2_CONFIG 0x0301 +#define PLL3_CONFIG 0x0302 +// Frame timing control +#define FRAME_LEN_LINES_H 0x0340 +#define FRAME_LEN_LINES_L 0x0341 +#define LINE_LEN_PCK_H 0x0342 +#define LINE_LEN_PCK_L 0x0343 +// Monochrome programming +#define MONO_MODE 0x0370 +#define MONO_MODE_ISP 0x0371 +#define MONO_MODE_SEL 0x0372 +// Binning mode control +#define H_SUBSAMPLE 0x0380 +#define V_SUBSAMPLE 0x0381 +#define BINNING_MODE 0x0382 +// Test pattern control +#define TEST_PATTERN_MODE 0x0601 +// Black level control +#define BLC_TGT 0x1004 +#define BLC2_TGT 0x1009 +#define MONO_CTRL 0x100A +// VSYNC / HSYNC / pixel shift registers +#define OPFM_CTRL 0x1014 +// Tone mapping registers +#define CMPRS_CTRL 0x102F +#define CMPRS_01 0x1030 +#define CMPRS_02 0x1031 +#define CMPRS_03 0x1032 +#define CMPRS_04 0x1033 +#define CMPRS_05 0x1034 +#define CMPRS_06 0x1035 +#define CMPRS_07 0x1036 +#define CMPRS_08 0x1037 +#define CMPRS_09 0x1038 +#define CMPRS_10 0x1039 +#define CMPRS_11 0x103A +#define CMPRS_12 0x103B +#define CMPRS_13 0x103C +#define CMPRS_14 0x103D +#define CMPRS_15 0x103E +#define CMPRS_16 0x103F +// Automatic exposure control +#define AE_CTRL 0x2000 +#define AE_CTRL1 0x2001 +#define CNT_ORGH_H 0x2002 +#define CNT_ORGH_L 0x2003 +#define CNT_ORGV_H 0x2004 +#define CNT_ORGV_L 0x2005 +#define CNT_STH_H 0x2006 +#define CNT_STH_L 0x2007 +#define CNT_STV_H 0x2008 +#define CNT_STV_L 0x2009 +#define CTRL_PG_SKIPCNT 0x200A +#define BV_WIN_WEIGHT_EN 0x200D +#define MAX_INTG_H 0x2029 +#define MAX_INTG_L 0x202A +#define MAX_AGAIN 0x202B +#define MAX_DGAIN_H 0x202C +#define MAX_DGAIN_L 0x202D +#define MIN_INTG 0x202E +#define MIN_AGAIN 0x202F +#define MIN_DGAIN 0x2030 +#define T_DAMPING 0x2031 +#define N_DAMPING 0x2032 +#define ALC_TH 0x2033 +#define AE_TARGET_MEAN 0x2034 +#define AE_MIN_MEAN 0x2035 +#define AE_TARGET_ZONE 0x2036 +#define CONVERGE_IN_TH 0x2037 +#define CONVERGE_OUT_TH 0x2038 +#define FS_CTRL 0x203B +#define FS_60HZ_H 0x203C +#define FS_60HZ_L 0x203D +#define FS_50HZ_H 0x203E +#define FS_50HZ_L 0x203F +#define FRAME_CNT_TH 0x205B +#define AE_MEAN 0x205D +#define AE_CONVERGE 0x2060 +#define AE_BLI_TGT 0x2070 +// Interrupt control +#define PULSE_MODE 0x2061 +#define PULSE_TH_H 0x2062 +#define PULSE_TH_L 0x2063 +#define INT_INDIC 0x2064 +#define INT_CLEAR 0x2065 +// Motion detection control +#define MD_CTRL 0x2080 +#define ROI_START_END_V 0x2081 +#define ROI_START_END_H 0x2082 +#define MD_TH_MIN 0x2083 +#define MD_TH_STR_L 0x2084 +#define MD_TH_STR_H 0x2085 +#define MD_LIGHT_COEF 0x2099 +#define MD_BLOCK_NUM_TH 0x209B +#define MD_LATENCY 0x209C +#define MD_LATENCY_TH 0x209D +#define MD_CTRL1 0x209E +// Context switch control registers +#define PMU_CFG_3 0x3024 +#define PMU_CFG_4 0x3025 +// Operation mode control +#define WIN_MODE 0x3030 +// IO and clock control +#define PAD_REGISTER_07 0x3112 + +// Register bits/values +#define HIMAX_RESET 0x01 +#define HIMAX_MODE_STANDBY 0x00 +#define HIMAX_MODE_STREAMING 0x01 // I2C triggered streaming enable +#define HIMAX_MODE_STREAMING_NFRAMES 0x03 // Output N frames +#define HIMAX_MODE_STREAMING_TRIG 0x05 // Hardware Trigger +#define HIMAX_SET_HMIRROR(r, x) ((r & 0xFE) | ((x & 1) << 0)) +#define HIMAX_SET_VMIRROR(r, x) ((r & 0xFD) | ((x & 1) << 1)) + +#define PCLK_RISING_EDGE 0x00 +#define PCLK_FALLING_EDGE 0x01 +#define AE_CTRL_ENABLE 0x00 +#define AE_CTRL_DISABLE 0x01 + +#endif //__REG_REGS_H__ diff --git a/components/3rd_party/omv/omv/sensors/lepton.c b/components/3rd_party/omv/omv/sensors/lepton.c new file mode 100644 index 00000000..cafe96e2 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/lepton.c @@ -0,0 +1,539 @@ +/* + * 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. + * + * Lepton driver. + */ +#include "omv_boardconfig.h" +#if (OMV_ENABLE_LEPTON == 1) + +#include +#include "sensor.h" +#include "vospi.h" +#include "py/mphal.h" +#include "omv_common.h" +#include "omv_gpio.h" +#include "omv_i2c.h" +#include "framebuffer.h" + +#include "LEPTON_SDK.h" +#include "LEPTON_AGC.h" +#include "LEPTON_SYS.h" +#include "LEPTON_VID.h" +#include "LEPTON_OEM.h" +#include "LEPTON_RAD.h" +#include "LEPTON_I2C_Reg.h" + +#define LEPTON_BOOT_TIMEOUT (1000) +#define LEPTON_SNAPSHOT_RETRY (3) +#define LEPTON_SNAPSHOT_TIMEOUT (10000) + +// Min/Max temperatures in Celsius. +#define LEPTON_MIN_TEMP_NORM (-10.0f) +#define LEPTON_MIN_TEMP_HIGH (-10.0f) +#define LEPTON_MIN_TEMP_DEFAULT (-10.0f) + +#define LEPTON_MAX_TEMP_NORM (140.0f) +#define LEPTON_MAX_TEMP_HIGH (600.0f) +#define LEPTON_MAX_TEMP_DEFAULT (40.0f) + +typedef struct lepton_state { + int h_res; + int v_res; + bool vflip; + bool hmirror; + float min_temp; + float max_temp; + bool radiometry; + bool high_temp_mode; + bool measurement_mode; + LEP_CAMERA_PORT_DESC_T port; +} lepton_state_t; + +extern uint16_t _vospi_buf[]; +static lepton_state_t lepton; + +static int lepton_reset(sensor_t *sensor, bool measurement_mode, bool high_temp_mode); + +static int sleep(sensor_t *sensor, int enable) { + if (enable) { + omv_gpio_write(DCMI_POWER_PIN, 0); + mp_hal_delay_ms(100); + } else { + omv_gpio_write(DCMI_POWER_PIN, 1); + mp_hal_delay_ms(100); + } + + return 0; +} + +static int read_reg(sensor_t *sensor, uint16_t reg_addr) { + uint16_t reg_data; + if (omv_i2c_readw2(&sensor->i2c_bus, sensor->slv_addr, reg_addr, ®_data)) { + return -1; + } + return reg_data; +} + +static int write_reg(sensor_t *sensor, uint16_t reg_addr, uint16_t reg_data) { + return omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, reg_addr, reg_data); +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) { + return ((pixformat != PIXFORMAT_GRAYSCALE) && (pixformat != PIXFORMAT_RGB565)) ? -1 : 0; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) { + return 0; +} + +static int set_contrast(sensor_t *sensor, int level) { + return 0; +} + +static int set_brightness(sensor_t *sensor, int level) { + return 0; +} + +static int set_saturation(sensor_t *sensor, int level) { + return 0; +} + +static int set_gainceiling(sensor_t *sensor, gainceiling_t gainceiling) { + return 0; +} + +static int set_quality(sensor_t *sensor, int quality) { + return 0; +} + +static int set_colorbar(sensor_t *sensor, int enable) { + return 0; +} + +static int set_special_effect(sensor_t *sensor, sde_t sde) { + return 0; +} + +static int set_auto_gain(sensor_t *sensor, int enable, float gain_db, float gain_db_ceiling) { + return 0; +} + +static int get_gain_db(sensor_t *sensor, float *gain_db) { + return 0; +} + +static int set_auto_exposure(sensor_t *sensor, int enable, int exposure_us) { + return 0; +} + +static int get_exposure_us(sensor_t *sensor, int *exposure_us) { + return 0; +} + +static int set_auto_whitebal(sensor_t *sensor, int enable, float r_gain_db, float g_gain_db, float b_gain_db) { + return 0; +} + +static int get_rgb_gain_db(sensor_t *sensor, float *r_gain_db, float *g_gain_db, float *b_gain_db) { + return 0; +} + +static int set_hmirror(sensor_t *sensor, int enable) { + lepton.hmirror = enable; + return 0; +} + +static int set_vflip(sensor_t *sensor, int enable) { + lepton.vflip = enable; + return 0; +} + +static int set_lens_correction(sensor_t *sensor, int enable, int radi, int coef) { + return 0; +} + +static int ioctl(sensor_t *sensor, int request, va_list ap) { + int ret = 0; + + if ((!lepton.h_res) || (!lepton.v_res)) { + return -1; + } + + switch (request) { + case IOCTL_LEPTON_GET_WIDTH: { + int *width = va_arg(ap, int *); + *width = lepton.h_res; + break; + } + case IOCTL_LEPTON_GET_HEIGHT: { + int *height = va_arg(ap, int *); + *height = lepton.v_res; + break; + } + case IOCTL_LEPTON_GET_RADIOMETRY: { + int *type = va_arg(ap, int *); + *type = lepton.radiometry; + break; + } + case IOCTL_LEPTON_GET_REFRESH: { + int *refresh = va_arg(ap, int *); + *refresh = (lepton.h_res == 80) ? 27 : 9; + break; + } + case IOCTL_LEPTON_GET_RESOLUTION: { + int *resolution = va_arg(ap, int *); + *resolution = 14; + break; + } + case IOCTL_LEPTON_RUN_COMMAND: { + int command = va_arg(ap, int); + ret = (LEP_RunCommand(&lepton.port, command) == LEP_OK) ? 0 : -1; + break; + } + case IOCTL_LEPTON_SET_ATTRIBUTE: { + int command = va_arg(ap, int); + uint16_t *data = va_arg(ap, uint16_t *); + size_t data_len = va_arg(ap, size_t); + ret = (LEP_SetAttribute(&lepton.port, command, (LEP_ATTRIBUTE_T_PTR) data, data_len) == LEP_OK) ? 0 : -1; + break; + } + case IOCTL_LEPTON_GET_ATTRIBUTE: { + int command = va_arg(ap, int); + uint16_t *data = va_arg(ap, uint16_t *); + size_t data_len = va_arg(ap, size_t); + ret = (LEP_GetAttribute(&lepton.port, command, (LEP_ATTRIBUTE_T_PTR) data, data_len) == LEP_OK) ? 0 : -1; + break; + } + case IOCTL_LEPTON_GET_FPA_TEMPERATURE: { + int *temp = va_arg(ap, int *); + LEP_SYS_FPA_TEMPERATURE_KELVIN_T tfpa; + ret = (LEP_GetSysFpaTemperatureKelvin(&lepton.port, &tfpa) == LEP_OK) ? 0 : -1; + *temp = tfpa; + break; + } + case IOCTL_LEPTON_GET_AUX_TEMPERATURE: { + int *temp = va_arg(ap, int *); + LEP_SYS_AUX_TEMPERATURE_KELVIN_T taux; + ret = (LEP_GetSysAuxTemperatureKelvin(&lepton.port, &taux) == LEP_OK) ? 0 : -1; + *temp = taux; + break; + } + case IOCTL_LEPTON_SET_MEASUREMENT_MODE: { + int measurement_mode_in = va_arg(ap, int); + int high_temp_mode_in = va_arg(ap, int); + if (lepton.measurement_mode != measurement_mode_in) { + lepton.measurement_mode = measurement_mode_in; + lepton.high_temp_mode = high_temp_mode_in; + ret = lepton_reset(sensor, lepton.measurement_mode, lepton.high_temp_mode); + } + break; + } + case IOCTL_LEPTON_GET_MEASUREMENT_MODE: { + int *measurement_mode_out = va_arg(ap, int *); + int *high_temp_mode_out = va_arg(ap, int *); + *measurement_mode_out = lepton.measurement_mode; + *high_temp_mode_out = lepton.high_temp_mode; + break; + } + case IOCTL_LEPTON_SET_MEASUREMENT_RANGE: { + float *arg_min_temp = va_arg(ap, float *); + float *arg_max_temp = va_arg(ap, float *); + float min_temp_range = (lepton.high_temp_mode) ? LEPTON_MIN_TEMP_HIGH : LEPTON_MIN_TEMP_NORM; + float max_temp_range = (lepton.high_temp_mode) ? LEPTON_MAX_TEMP_HIGH : LEPTON_MAX_TEMP_NORM; + lepton.min_temp = IM_MAX(IM_MIN(*arg_min_temp, *arg_max_temp), min_temp_range); + lepton.max_temp = IM_MIN(IM_MAX(*arg_max_temp, *arg_min_temp), max_temp_range); + break; + } + case IOCTL_LEPTON_GET_MEASUREMENT_RANGE: { + float *ptr_min_temp = va_arg(ap, float *); + float *ptr_max_temp = va_arg(ap, float *); + *ptr_min_temp = lepton.min_temp; + *ptr_max_temp = lepton.max_temp; + break; + } + default: { + ret = -1; + break; + } + } + + return ret; +} + +static int lepton_reset(sensor_t *sensor, bool measurement_mode, bool high_temp_mode) { + omv_gpio_write(DCMI_POWER_PIN, 0); + mp_hal_delay_ms(10); + + omv_gpio_write(DCMI_POWER_PIN, 1); + mp_hal_delay_ms(10); + + omv_gpio_write(DCMI_RESET_PIN, 0); + mp_hal_delay_ms(10); + + omv_gpio_write(DCMI_RESET_PIN, 1); + mp_hal_delay_ms(1000); + + LEP_RAD_ENABLE_E rad; + LEP_AGC_ROI_T roi; + memset(&lepton.port, 0, sizeof(LEP_CAMERA_PORT_DESC_T)); + + for (mp_uint_t start = mp_hal_ticks_ms(); ; mp_hal_delay_ms(1)) { + if (LEP_OpenPort(&sensor->i2c_bus, LEP_CCI_TWI, 0, &lepton.port) == LEP_OK) { + break; + } + if ((mp_hal_ticks_ms() - start) >= LEPTON_BOOT_TIMEOUT) { + return -1; + } + } + + for (mp_uint_t start = mp_hal_ticks_ms(); ; mp_hal_delay_ms(1)) { + LEP_SDK_BOOT_STATUS_E status; + if (LEP_GetCameraBootStatus(&lepton.port, &status) != LEP_OK) { + return -1; + } + if (status == LEP_BOOT_STATUS_BOOTED) { + break; + } + if ((mp_hal_ticks_ms() - start) >= LEPTON_BOOT_TIMEOUT) { + return -1; + } + } + + for (mp_uint_t start = mp_hal_ticks_ms(); ; mp_hal_delay_ms(1)) { + LEP_UINT16 status; + if (LEP_DirectReadRegister(&lepton.port, LEP_I2C_STATUS_REG, &status) != LEP_OK) { + return -1; + } + if (!(status & LEP_I2C_STATUS_BUSY_BIT_MASK)) { + break; + } + if ((mp_hal_ticks_ms() - start) >= LEPTON_BOOT_TIMEOUT) { + return -1; + } + } + + if (LEP_GetRadEnableState(&lepton.port, &rad) != LEP_OK + || LEP_GetAgcROI(&lepton.port, &roi) != LEP_OK) { + return -1; + } + + // Use the low gain mode to enable high temperature readings (~450C) on Lepton 3.5 + LEP_SYS_GAIN_MODE_E gain_mode = lepton.high_temp_mode ? LEP_SYS_GAIN_MODE_LOW : LEP_SYS_GAIN_MODE_HIGH; + if (LEP_SetSysGainMode(&lepton.port, gain_mode) != LEP_OK) { + return -1; + } + + if (!lepton.measurement_mode) { + if (LEP_SetRadEnableState(&lepton.port, LEP_RAD_DISABLE) != LEP_OK + || LEP_SetAgcEnableState(&lepton.port, LEP_AGC_ENABLE) != LEP_OK + || LEP_SetAgcCalcEnableState(&lepton.port, LEP_AGC_ENABLE) != LEP_OK) { + return -1; + } + } + + lepton.h_res = roi.endCol + 1; + lepton.v_res = roi.endRow + 1; + lepton.radiometry = (rad == LEP_RAD_ENABLE); + return 0; +} + +static int reset(sensor_t *sensor) { + static bool vospi_initialized = false; + + memset(&lepton, 0, sizeof(lepton_state_t)); + lepton.min_temp = LEPTON_MIN_TEMP_DEFAULT; + lepton.max_temp = LEPTON_MAX_TEMP_DEFAULT; + + if (lepton_reset(sensor, false, false) != 0) { + return -1; + } + + if (vospi_initialized == false) { + if (vospi_init(lepton.v_res, _vospi_buf) != 0) { + return -1; + } + vospi_initialized = true; + } + + return 0; +} + +static int snapshot(sensor_t *sensor, image_t *image, uint32_t flags) { + framebuffer_update_jpeg_buffer(); + + if (MAIN_FB()->n_buffers != 1) { + framebuffer_set_buffers(1); + } + + if (sensor_check_framebuffer_size(sensor) == -1) { + return -1; + } + + if ((!lepton.h_res) || (!lepton.v_res) || (!sensor->framesize) || (!sensor->pixformat)) { + return -1; + } + + framebuffer_free_current_buffer(); + vbuffer_t *buffer = framebuffer_get_tail(FB_NO_FLAGS); + + if (!buffer) { + return -1; + } + + for (int i = 0; i < LEPTON_SNAPSHOT_RETRY; i++) { + if (vospi_snapshot(LEPTON_SNAPSHOT_TIMEOUT) == 0) { + break; + } + if (i + 1 == LEPTON_SNAPSHOT_RETRY) { + return -1; + } + // The FLIR lepton might have crashed so reset it (it does this). + if (lepton_reset(sensor, lepton.measurement_mode, lepton.high_temp_mode) != 0) { + return -1; + } + } + + MAIN_FB()->w = MAIN_FB()->u; + MAIN_FB()->h = MAIN_FB()->v; + MAIN_FB()->pixfmt = sensor->pixformat; + + framebuffer_init_image(image); + + float x_scale = resolution[sensor->framesize][0] / ((float) lepton.h_res); + float y_scale = resolution[sensor->framesize][1] / ((float) lepton.v_res); + // MAX == KeepAspectRationByExpanding - MIN == KeepAspectRatio + float scale = IM_MAX(x_scale, y_scale), scale_inv = 1.0f / scale; + int x_offset = (resolution[sensor->framesize][0] - (lepton.h_res * scale)) / 2; + int y_offset = (resolution[sensor->framesize][1] - (lepton.v_res * scale)) / 2; + // The code below upscales the source image to the requested frame size + // and then crops it to the window set by the user. + + LEP_SYS_FPA_TEMPERATURE_KELVIN_T kelvin; + if (lepton.measurement_mode && (!lepton.radiometry)) { + if (LEP_GetSysFpaTemperatureKelvin(&lepton.port, &kelvin) != LEP_OK) { + return -1; + } + } + + for (int y = y_offset, yy = fast_ceilf(lepton.v_res * scale) + y_offset; y < yy; y++) { + if ((MAIN_FB()->y <= y) && (y < (MAIN_FB()->y + MAIN_FB()->v))) { + // user window cropping + + uint16_t *row_ptr = _vospi_buf + (fast_floorf(y * scale_inv) * lepton.h_res); + + for (int x = x_offset, xx = fast_ceilf(lepton.h_res * scale) + x_offset; x < xx; x++) { + if ((MAIN_FB()->x <= x) && (x < (MAIN_FB()->x + MAIN_FB()->u))) { + // user window cropping + + // Value is the 14/16-bit value from the FLIR IR camera. + // However, with AGC enabled only the bottom 8-bits are non-zero. + int value = row_ptr[fast_floorf(x * scale_inv)]; + + if (lepton.measurement_mode) { + // Need to convert 14/16-bits to 8-bits ourselves... + if (!lepton.radiometry) { + value = (value - 8192) + kelvin; + } + float celsius = (value * 0.01f) - 273.15f; + celsius = IM_MAX(IM_MIN(celsius, lepton.max_temp), lepton.min_temp); + value = IM_MAX(IM_MIN(IM_DIV(((celsius - lepton.min_temp) * 255), + (lepton.max_temp - lepton.min_temp)), 255), 0); + } + + int t_x = x - MAIN_FB()->x; + int t_y = y - MAIN_FB()->y; + + if (lepton.hmirror) { + t_x = MAIN_FB()->u - t_x - 1; + } + if (lepton.vflip) { + t_y = MAIN_FB()->v - t_y - 1; + } + + switch (sensor->pixformat) { + case PIXFORMAT_GRAYSCALE: { + IMAGE_PUT_GRAYSCALE_PIXEL(image, t_x, t_y, value & 0xFF); + break; + } + case PIXFORMAT_RGB565: { + IMAGE_PUT_RGB565_PIXEL(image, t_x, t_y, sensor->color_palette[value & 0xFF]); + break; + } + default: { + break; + } + } + } + } + } + } + + return 0; +} + +int lepton_init(sensor_t *sensor) { + sensor->reset = reset; + sensor->sleep = sleep; + sensor->snapshot = snapshot; + sensor->read_reg = read_reg; + sensor->write_reg = write_reg; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_contrast = set_contrast; + sensor->set_brightness = set_brightness; + sensor->set_saturation = set_saturation; + sensor->set_gainceiling = set_gainceiling; + sensor->set_quality = set_quality; + sensor->set_colorbar = set_colorbar; + sensor->set_special_effect = set_special_effect; + sensor->set_auto_gain = set_auto_gain; + sensor->get_gain_db = get_gain_db; + sensor->set_auto_exposure = set_auto_exposure; + sensor->get_exposure_us = get_exposure_us; + sensor->set_auto_whitebal = set_auto_whitebal; + sensor->get_rgb_gain_db = get_rgb_gain_db; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + sensor->set_lens_correction = set_lens_correction; + sensor->ioctl = ioctl; + + sensor->hw_flags.vsync = 1; + sensor->hw_flags.hsync = 0; + sensor->hw_flags.pixck = 0; + sensor->hw_flags.fsync = 0; + sensor->hw_flags.jpege = 0; + sensor->hw_flags.gs_bpp = 1; + + if (reset(sensor) != 0) { + return -1; + } + + LEP_OEM_PART_NUMBER_T part; + if (LEP_GetOemFlirPartNumber(&lepton.port, &part) != LEP_OK) { + return -1; + } + + // 500 == Lepton + // xxxx == Version + // 01/00 == Shutter/NoShutter + if (!strncmp(part.value, "500-0771", 8)) { + sensor->chip_id_w = LEPTON_3_5; + } else if (!strncmp(part.value, "500-0726", 8)) { + sensor->chip_id_w = LEPTON_3_0; + } else if (!strncmp(part.value, "500-0763", 8)) { + sensor->chip_id_w = LEPTON_2_5; + } else if (!strncmp(part.value, "500-0659", 8)) { + sensor->chip_id_w = LEPTON_2_0; + } else if (!strncmp(part.value, "500-0690", 8)) { + sensor->chip_id_w = LEPTON_1_6; + } else if (!strncmp(part.value, "500-0643", 8)) { + sensor->chip_id_w = LEPTON_1_5; + } + return 0; +} +#endif // (OMV_ENABLE_LEPTON == 1) diff --git a/components/3rd_party/omv/omv/sensors/lepton.h b/components/3rd_party/omv/omv/sensors/lepton.h new file mode 100644 index 00000000..b0b4e2ed --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/lepton.h @@ -0,0 +1,15 @@ +/* + * 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. + * + * Lepton driver. + */ +#ifndef __LEPTON_H__ +#define __LEPTON_H__ +#define LEPTON_XCLK_FREQ 24000000 +int lepton_init(sensor_t *sensor); +#endif // __LEPTON_H__ diff --git a/components/3rd_party/omv/omv/sensors/mt9m114.c b/components/3rd_party/omv/omv/sensors/mt9m114.c new file mode 100644 index 00000000..da39d84f --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/mt9m114.c @@ -0,0 +1,1001 @@ +/* + * 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. + * + * MT9M114 driver. + */ +#include "omv_boardconfig.h" +#if (OMV_ENABLE_MT9M114 == 1) + +#include +#include +#include + +#include "omv_i2c.h" +#include "sensor.h" +#include "mt9m114.h" +#include "mt9m114_regs.h" +#include "py/mphal.h" + +#define DUMMY_LINES 8 +#define DUMMY_COLUMNS 8 + +#define SENSOR_WIDTH 1296 +#define SENSOR_HEIGHT 976 + +#define ACTIVE_SENSOR_WIDTH (SENSOR_WIDTH - (2 * DUMMY_COLUMNS)) +#define ACTIVE_SENSOR_HEIGHT (SENSOR_HEIGHT - (2 * DUMMY_LINES)) + +#define DUMMY_WIDTH_BUFFER 8 +#define DUMMY_HEIGHT_BUFFER 8 + +static int16_t readout_x = 0; +static int16_t readout_y = 0; + +static uint16_t readout_w = ACTIVE_SENSOR_WIDTH; +static uint16_t readout_h = ACTIVE_SENSOR_HEIGHT; + +static const uint16_t default_regs[][2] = { + // Sensor Optimization + {0x316A, 0x8270}, + {0x316C, 0x8270}, + {0x3ED0, 0x2305}, + {0x3ED2, 0x77CF}, + {0x316E, 0x8202}, + {0x3180, 0x87FF}, + {0x30D4, 0x6080}, + {0xA802, 0x0008}, + + // Errata 1 (Column Bands) + {0x3E14, 0xFF39} +}; + +static const uint8_t patch_0202[] = { + 0xd0, 0x00, 0x70, 0xcf, 0xff, 0xff, 0xc5, 0xd4, 0x90, 0x3a, 0x21, 0x44, 0x0c, 0x00, 0x21, 0x86, + 0x0f, 0xf3, 0xb8, 0x44, 0xb9, 0x48, 0xe0, 0x82, 0x20, 0xcc, 0x80, 0xe2, 0x21, 0xcc, 0x80, 0xa2, + 0x21, 0xcc, 0x80, 0xe2, 0xf4, 0x04, 0xd8, 0x01, 0xf0, 0x03, 0xd8, 0x00, 0x7e, 0xe0, 0xc0, 0xf1, + 0x08, 0xba, 0x06, 0x00, 0xc1, 0xa1, 0x76, 0xcf, 0xff, 0xff, 0xc1, 0x30, 0x6e, 0x04, 0xc0, 0x40, + 0x71, 0xcf, 0xff, 0xff, 0xc7, 0x90, 0x81, 0x03, 0x77, 0xcf, 0xff, 0xff, 0xc7, 0xc0, 0xe0, 0x01, + 0xa1, 0x03, 0xd8, 0x00, 0x0c, 0x6a, 0x04, 0xe0, 0xb8, 0x9e, 0x75, 0x08, 0x8e, 0x1c, 0x08, 0x09, + 0x01, 0x91, 0xd8, 0x01, 0xae, 0x1d, 0xe5, 0x80, 0x20, 0xca, 0x00, 0x22, 0x20, 0xcf, 0x05, 0x22, + 0x0c, 0x5c, 0x04, 0xe2, 0x21, 0xca, 0x00, 0x62, 0xe5, 0x80, 0xd9, 0x01, 0x79, 0xc0, 0xd8, 0x00, + 0x0b, 0xe6, 0x04, 0xe0, 0xb8, 0x9e, 0x70, 0xcf, 0xff, 0xff, 0xc8, 0xd4, 0x90, 0x02, 0x08, 0x57, + 0x02, 0x5e, 0xff, 0xdc, 0xe0, 0x80, 0x25, 0xcc, 0x90, 0x22, 0xf2, 0x25, 0x17, 0x00, 0x10, 0x8a, + 0x73, 0xcf, 0xff, 0x00, 0x31, 0x74, 0x93, 0x07, 0x2a, 0x04, 0x10, 0x3e, 0x93, 0x28, 0x29, 0x42, + 0x71, 0x40, 0x2a, 0x04, 0x10, 0x7e, 0x93, 0x49, 0x29, 0x42, 0x71, 0x41, 0x2a, 0x04, 0x10, 0xbe, + 0x93, 0x4a, 0x29, 0x42, 0x71, 0x4b, 0x2a, 0x04, 0x10, 0xbe, 0x13, 0x0c, 0x01, 0x0a, 0x29, 0x42, + 0x71, 0x42, 0x22, 0x50, 0x13, 0xca, 0x1b, 0x0c, 0x02, 0x84, 0xb3, 0x07, 0xb3, 0x28, 0x1b, 0x12, + 0x02, 0xc4, 0xb3, 0x4a, 0xed, 0x88, 0x71, 0xcf, 0xff, 0x00, 0x31, 0x74, 0x91, 0x06, 0xb8, 0x8f, + 0xb1, 0x06, 0x21, 0x0a, 0x83, 0x40, 0xc0, 0x00, 0x21, 0xca, 0x00, 0x62, 0x20, 0xf0, 0x00, 0x40, + 0x0b, 0x02, 0x03, 0x20, 0xd9, 0x01, 0x07, 0xf1, 0x05, 0xe0, 0xc0, 0xa1, 0x78, 0xe0, 0xc0, 0xf1, + 0x71, 0xcf, 0xff, 0xff, 0xc7, 0xc0, 0xd8, 0x40, 0xa9, 0x00, 0x71, 0xcf, 0xff, 0xff, 0xd0, 0x2c, + 0xd8, 0x1e, 0x0a, 0x5a, 0x04, 0xe0, 0xda, 0x00, 0xd8, 0x00, 0xc0, 0xd1, 0x7e, 0xe0 +}; +static const uint8_t patch_0302[] = { + 0xd1, 0x2c, 0x70, 0xcf, 0xff, 0xff, 0xc5, 0xd4, 0x90, 0x3a, 0x21, 0x44, 0x0c, 0x00, 0x21, 0x86, + 0x0f, 0xf3, 0xb8, 0x44, 0x26, 0x2f, 0xf0, 0x08, 0xb9, 0x48, 0x21, 0xcc, 0x80, 0x21, 0xd8, 0x01, + 0xf2, 0x03, 0xd8, 0x00, 0x7e, 0xe0, 0xc0, 0xf1, 0x71, 0xcf, 0xff, 0xff, 0xc6, 0x10, 0x91, 0x0e, + 0x20, 0x8c, 0x80, 0x14, 0xf4, 0x18, 0x91, 0x0f, 0x20, 0x8c, 0x80, 0x0f, 0xf4, 0x14, 0x91, 0x16, + 0x20, 0x8c, 0x80, 0x0a, 0xf4, 0x10, 0x91, 0x17, 0x20, 0x8c, 0x88, 0x07, 0xf4, 0x0c, 0x91, 0x18, + 0x20, 0x86, 0x0f, 0xf3, 0xb8, 0x48, 0x08, 0x0d, 0x00, 0x90, 0xff, 0xea, 0xe0, 0x81, 0xd8, 0x01, + 0xf2, 0x03, 0xd8, 0x00, 0xc0, 0xd1, 0x7e, 0xe0, 0x78, 0xe0, 0xc0, 0xf1, 0x71, 0xcf, 0xff, 0xff, + 0xc6, 0x10, 0x91, 0x0e, 0x20, 0x8c, 0x80, 0x0a, 0xf4, 0x18, 0x91, 0x0f, 0x20, 0x8c, 0x88, 0x07, + 0xf4, 0x14, 0x91, 0x16, 0x20, 0x8c, 0x80, 0x0a, 0xf4, 0x10, 0x91, 0x17, 0x20, 0x8c, 0x88, 0x07, + 0xf4, 0x0c, 0x91, 0x18, 0x20, 0x86, 0x0f, 0xf3, 0xb8, 0x48, 0x08, 0x0d, 0x00, 0x90, 0xff, 0xd9, + 0xe0, 0x80, 0xd8, 0x01, 0xf2, 0x03, 0xd8, 0x00, 0xf1, 0xdf, 0x90, 0x40, 0x71, 0xcf, 0xff, 0xff, + 0xc5, 0xd4, 0xb1, 0x5a, 0x90, 0x41, 0x73, 0xcf, 0xff, 0xff, 0xc7, 0xd0, 0xb1, 0x40, 0x90, 0x42, + 0xb1, 0x41, 0x90, 0x43, 0xb1, 0x42, 0x90, 0x44, 0xb1, 0x43, 0x90, 0x45, 0xb1, 0x47, 0x90, 0x46, + 0xb1, 0x48, 0x90, 0x47, 0xb1, 0x4b, 0x90, 0x48, 0xb1, 0x4c, 0x90, 0x49, 0x19, 0x58, 0x00, 0x84, + 0x90, 0x4a, 0x19, 0x5a, 0x00, 0x84, 0x88, 0x56, 0x1b, 0x36, 0x80, 0x82, 0x88, 0x57, 0x1b, 0x37, + 0x80, 0x82, 0x90, 0x4c, 0x19, 0xa7, 0x00, 0x9c, 0x88, 0x1a, 0x7f, 0xe0, 0x1b, 0x54, 0x80, 0x02, + 0x78, 0xe0, 0x71, 0xcf, 0xff, 0xff, 0xc3, 0x50, 0xd8, 0x28, 0xa9, 0x0b, 0x81, 0x00, 0x01, 0xc5, + 0x03, 0x20, 0xd9, 0x00, 0x78, 0xe0, 0x22, 0x0a, 0x1f, 0x80, 0xff, 0xff, 0xd4, 0xe0, 0xc0, 0xf1, + 0x08, 0x11, 0x00, 0x51, 0x22, 0x40, 0x12, 0x00, 0xff, 0xe1, 0xd8, 0x01, 0xf0, 0x06, 0x22, 0x40, + 0x19, 0x00, 0xff, 0xde, 0xd8, 0x02, 0x1a, 0x05, 0x10, 0x02, 0xff, 0xf2, 0xf1, 0x95, 0xc0, 0xf1, + 0x0e, 0x7e, 0x05, 0xc0, 0x75, 0xcf, 0xff, 0xff, 0xc8, 0x4c, 0x95, 0x02, 0x77, 0xcf, 0xff, 0xff, + 0xc3, 0x44, 0x20, 0x44, 0x00, 0x8e, 0xb8, 0xa1, 0x09, 0x26, 0x03, 0xe0, 0xb5, 0x02, 0x95, 0x02, + 0x95, 0x2e, 0x7e, 0x05, 0xb5, 0xc2, 0x70, 0xcf, 0xff, 0xff, 0xc6, 0x10, 0x09, 0x9a, 0x04, 0xa0, + 0xb0, 0x26, 0x0e, 0x02, 0x05, 0x60, 0xde, 0x00, 0x0a, 0x12, 0x03, 0x20, 0xb7, 0xc4, 0x0b, 0x36, + 0x03, 0xa0, 0x70, 0xc9, 0x95, 0x02, 0x76, 0x08, 0xb8, 0xa8, 0xb5, 0x02, 0x70, 0xcf, 0x00, 0x00, + 0x55, 0x36, 0x78, 0x60, 0x26, 0x86, 0x1f, 0xfb, 0x95, 0x02, 0x78, 0xc5, 0x06, 0x31, 0x05, 0xe0, + 0xb5, 0x02, 0x72, 0xcf, 0xff, 0xff, 0xc5, 0xd4, 0x92, 0x3a, 0x73, 0xcf, 0xff, 0xff, 0xc7, 0xd0, + 0xb0, 0x20, 0x92, 0x20, 0xb0, 0x21, 0x92, 0x21, 0xb0, 0x22, 0x92, 0x22, 0xb0, 0x23, 0x92, 0x23, + 0xb0, 0x24, 0x92, 0x27, 0xb0, 0x25, 0x92, 0x28, 0xb0, 0x26, 0x92, 0x2b, 0xb0, 0x27, 0x92, 0x2c, + 0xb0, 0x28, 0x12, 0x58, 0x01, 0x01, 0xb0, 0x29, 0x12, 0x5a, 0x01, 0x01, 0xb0, 0x2a, 0x13, 0x36, + 0x80, 0x81, 0xa8, 0x36, 0x13, 0x37, 0x80, 0x81, 0xa8, 0x37, 0x12, 0xa7, 0x07, 0x01, 0xb0, 0x2c, + 0x13, 0x54, 0x80, 0x81, 0x7f, 0xe0, 0xa8, 0x3a, 0x78, 0xe0, 0xc0, 0xf1, 0x0d, 0xc2, 0x05, 0xc0, + 0x76, 0x08, 0x09, 0xbb, 0x00, 0x10, 0x75, 0xcf, 0xff, 0xff, 0xd4, 0xe0, 0x8d, 0x21, 0x8d, 0x00, + 0x21, 0x53, 0x00, 0x03, 0xb8, 0xc0, 0x8d, 0x45, 0x0b, 0x23, 0x00, 0x00, 0xea, 0x8f, 0x09, 0x15, + 0x00, 0x1e, 0xff, 0x81, 0xe8, 0x08, 0x25, 0x40, 0x19, 0x00, 0xff, 0xde, 0x8d, 0x00, 0xb8, 0x80, + 0xf0, 0x04, 0x8d, 0x00, 0xb8, 0xa0, 0xad, 0x00, 0x8d, 0x05, 0xe0, 0x81, 0x20, 0xcc, 0x80, 0xa2, + 0xdf, 0x00, 0xf4, 0x0a, 0x71, 0xcf, 0xff, 0xff, 0xc8, 0x4c, 0x91, 0x02, 0x77, 0x08, 0xb8, 0xa6, + 0x27, 0x86, 0x1f, 0xfe, 0xb1, 0x02, 0x0b, 0x42, 0x01, 0x80, 0x0e, 0x3e, 0x01, 0x80, 0x0f, 0x4a, + 0x01, 0x60, 0x70, 0xc9, 0x8d, 0x05, 0xe0, 0x81, 0x20, 0xcc, 0x80, 0xa2, 0xf4, 0x29, 0x76, 0xcf, + 0xff, 0xff, 0xc8, 0x4c, 0x08, 0x2d, 0x00, 0x51, 0x70, 0xcf, 0xff, 0xff, 0xc9, 0x0c, 0x88, 0x05, + 0x09, 0xb6, 0x03, 0x60, 0xd9, 0x08, 0x20, 0x99, 0x08, 0x02, 0x96, 0x34, 0xb5, 0x03, 0x79, 0x02, + 0x15, 0x23, 0x10, 0x80, 0xb6, 0x34, 0xe0, 0x01, 0x1d, 0x23, 0x10, 0x02, 0xf0, 0x0b, 0x96, 0x34, + 0x95, 0x03, 0x60, 0x38, 0xb6, 0x14, 0x15, 0x3f, 0x10, 0x80, 0xe0, 0x01, 0x1d, 0x3f, 0x10, 0x02, + 0xff, 0xa4, 0x96, 0x02, 0x7f, 0x05, 0xd8, 0x00, 0xb6, 0xe2, 0xad, 0x05, 0x05, 0x11, 0x05, 0xe0, + 0xd8, 0x00, 0xc0, 0xf1, 0x0c, 0xfe, 0x05, 0xc0, 0x0a, 0x96, 0x05, 0xa0, 0x76, 0x08, 0x0c, 0x22, + 0x02, 0x40, 0xe0, 0x80, 0x20, 0xca, 0x0f, 0x82, 0x00, 0x00, 0x19, 0x0b, 0x0c, 0x60, 0x05, 0xa2, + 0x21, 0xca, 0x00, 0x22, 0x0c, 0x56, 0x02, 0x40, 0xe8, 0x06, 0x0e, 0x0e, 0x02, 0x20, 0x70, 0xc9, + 0xf0, 0x48, 0x08, 0x96, 0x04, 0x40, 0x0e, 0x96, 0x04, 0x00, 0x09, 0x66, 0x03, 0x80, 0x75, 0xcf, + 0xff, 0xff, 0xd4, 0xe0, 0x8d, 0x00, 0x08, 0x4d, 0x00, 0x1e, 0xff, 0x47, 0x08, 0x0d, 0x00, 0x50, + 0xff, 0x57, 0x08, 0x41, 0x00, 0x51, 0x8d, 0x04, 0x95, 0x21, 0xe0, 0x64, 0x79, 0x0c, 0x70, 0x2f, + 0x0c, 0xe2, 0x05, 0xe0, 0xd9, 0x64, 0x72, 0xcf, 0xff, 0xff, 0xc7, 0x00, 0x92, 0x35, 0x08, 0x11, + 0x00, 0x43, 0xff, 0x3d, 0x08, 0x0d, 0x00, 0x51, 0xd8, 0x01, 0xff, 0x77, 0xf0, 0x25, 0x95, 0x01, + 0x92, 0x35, 0x09, 0x11, 0x00, 0x03, 0xff, 0x49, 0x08, 0x0d, 0x00, 0x51, 0xd8, 0x00, 0xff, 0x72, + 0xf0, 0x1b, 0x08, 0x86, 0x03, 0xe0, 0xd8, 0x01, 0x0e, 0xf6, 0x03, 0xc0, 0x0f, 0x52, 0x03, 0x40, + 0x0d, 0xba, 0x02, 0x00, 0x0a, 0xf6, 0x04, 0x40, 0x0c, 0x22, 0x04, 0x00, 0x0d, 0x72, 0x04, 0x40, + 0x0d, 0xc2, 0x02, 0x00, 0x09, 0x72, 0x04, 0x40, 0x0d, 0x3a, 0x02, 0x20, 0xd8, 0x20, 0x0b, 0xfa, + 0x02, 0x60, 0x70, 0xc9, 0x04, 0x51, 0x05, 0xc0, 0x78, 0xe0, 0xd9, 0x00, 0xf0, 0x0a, 0x70, 0xcf, + 0xff, 0xff, 0xd5, 0x20, 0x78, 0x35, 0x80, 0x41, 0x80, 0x00, 0xe1, 0x02, 0xa0, 0x40, 0x09, 0xf1, + 0x81, 0x14, 0x71, 0xcf, 0xff, 0xff, 0xd4, 0xe0, 0x70, 0xcf, 0xff, 0xff, 0xc5, 0x94, 0xb0, 0x3a, + 0x7f, 0xe0, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x02, 0x00, 0x03, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x03, 0xcd, 0x05, 0x0d, 0x01, 0xc5, 0x03, 0xb3, 0x00, 0xe0, 0x01, 0xe3, + 0x02, 0x80, 0x01, 0xe0, 0x01, 0x09, 0x00, 0x80, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc9, 0xb4, 0xff, 0xff, 0xd3, 0x24, 0xff, 0xff, + 0xca, 0x34, 0xff, 0xff, 0xd3, 0xec +}; +static const uint8_t patch_0402[] = { + 0xd5, 0x30, 0xc0, 0xf1, 0x0b, 0xba, 0x05, 0xc0, 0xc1, 0xa2, 0x75, 0xcf, 0xff, 0xff, 0xc7, 0xc0, + 0xde, 0x00, 0x1c, 0x05, 0x33, 0x82, 0xc6, 0x61, 0x08, 0xc3, 0x00, 0x40, 0xe1, 0xd1, 0xf2, 0x17, + 0xe1, 0xd4, 0xf4, 0x5b, 0x8d, 0x01, 0x08, 0xb3, 0x00, 0x1e, 0x1c, 0x00, 0x3f, 0x80, 0x00, 0x0c, + 0x35, 0x00, 0x71, 0x8b, 0x0f, 0xbe, 0x04, 0xe0, 0x70, 0xc9, 0x08, 0x06, 0x05, 0x20, 0x70, 0xc9, + 0x95, 0x25, 0x70, 0xcf, 0xff, 0x00, 0x00, 0x00, 0xb0, 0x39, 0xf0, 0x47, 0x8d, 0x01, 0x08, 0x27, + 0x00, 0xdf, 0x8d, 0x02, 0x08, 0x1d, 0x00, 0x1e, 0x8d, 0x02, 0xad, 0x01, 0x8d, 0x01, 0xb8, 0xa3, + 0xad, 0x01, 0x70, 0xcf, 0xff, 0x00, 0x00, 0x00, 0x90, 0x19, 0xb5, 0x05, 0xf0, 0x05, 0x8d, 0x01, + 0xb8, 0xa0, 0xad, 0x01, 0x8d, 0x01, 0x08, 0x11, 0x00, 0xde, 0x8d, 0x01, 0xb8, 0xa3, 0xad, 0x01, + 0x0d, 0x02, 0x04, 0xa0, 0xd8, 0x01, 0x8d, 0x01, 0x08, 0x53, 0x00, 0x1e, 0x85, 0x01, 0x0b, 0x5e, + 0x05, 0xe0, 0x21, 0x8a, 0x04, 0x1f, 0x95, 0x24, 0x29, 0x05, 0x00, 0x3e, 0x1c, 0x00, 0x3e, 0x40, + 0x71, 0x8b, 0x0f, 0x4e, 0x04, 0xe0, 0xd8, 0x00, 0x0f, 0x9a, 0x04, 0xe0, 0xd8, 0x00, 0xd8, 0x00, + 0x0f, 0xb6, 0x04, 0xe0, 0xd9, 0x01, 0x8d, 0x01, 0x08, 0x23, 0x00, 0x5f, 0x20, 0x8a, 0x00, 0x1c, + 0x0b, 0xe2, 0x04, 0xe0, 0xd9, 0x00, 0xd8, 0x0d, 0xb8, 0x0a, 0x0b, 0xd6, 0x04, 0xe0, 0xd9, 0x00, + 0xd8, 0x90, 0x0b, 0xea, 0x04, 0xe0, 0xd9, 0x01, 0xd8, 0x00, 0x02, 0xf5, 0x05, 0xe0, 0xc0, 0xa2, + 0x78, 0xe0, 0xc0, 0xf1, 0x73, 0xcf, 0xff, 0xff, 0xc7, 0xc0, 0x8b, 0x41, 0x0a, 0x0d, 0x00, 0xde, + 0x8b, 0x41, 0x0a, 0x0f, 0x00, 0x5e, 0x72, 0xcf, 0x00, 0x00, 0x40, 0x3e, 0x7a, 0x40, 0xf0, 0x02, + 0xd8, 0x00, 0xc0, 0xd1, 0x7e, 0xe0, 0xc0, 0xf1, 0xc5, 0xe1, 0x75, 0xcf, 0xff, 0xff, 0xc7, 0xc0, + 0xd9, 0x00, 0xf0, 0x09, 0x70, 0xcf, 0xff, 0xff, 0xd7, 0x40, 0x78, 0x35, 0x80, 0x41, 0x80, 0x00, + 0xe1, 0x02, 0xa0, 0x40, 0x09, 0xf3, 0x80, 0x94, 0x71, 0xcf, 0xff, 0xff, 0xd6, 0xd0, 0xd8, 0x03, + 0x0d, 0x1e, 0x04, 0xa0, 0xda, 0x00, 0x70, 0xcf, 0xff, 0xff, 0xd5, 0x30, 0x0c, 0x22, 0x03, 0x00, + 0xe8, 0x87, 0x70, 0xcf, 0xff, 0xff, 0xd6, 0xe8, 0x09, 0x4a, 0x02, 0x40, 0x21, 0x8a, 0x0a, 0x0f, + 0xb5, 0x24, 0xd9, 0x04, 0xad, 0x23, 0x1d, 0x04, 0x1f, 0x80, 0x01, 0x6e, 0x36, 0x00, 0x02, 0x79, + 0x05, 0xc0, 0xc0, 0xf1, 0x71, 0xcf, 0xff, 0xff, 0xc3, 0x50, 0x11, 0x09, 0x00, 0xc0, 0x73, 0xcf, + 0xff, 0xff, 0xc1, 0x64, 0xe0, 0xd2, 0x72, 0xcf, 0xff, 0xff, 0xc7, 0xc0, 0xf4, 0x12, 0x8b, 0x08, + 0x08, 0x21, 0x03, 0xd1, 0x8a, 0x01, 0x08, 0x19, 0x00, 0xdf, 0xd8, 0x54, 0xa9, 0x0b, 0xd8, 0x00, + 0xa9, 0x08, 0x8a, 0x21, 0xb9, 0x83, 0xaa, 0x21, 0xaa, 0x0c, 0x0b, 0xe6, 0x04, 0x80, 0xf1, 0xb2, + 0x78, 0xe0, 0xc0, 0xf1, 0x70, 0xcf, 0xff, 0xff, 0xc7, 0xc0, 0x88, 0x01, 0xb8, 0xe0, 0x0f, 0xb4, + 0xff, 0xc2, 0x0e, 0x9a, 0x02, 0x40, 0xf1, 0xa6, 0x78, 0xe0, 0xc0, 0xf1, 0x71, 0xcf, 0xff, 0xff, + 0xc7, 0xc0, 0x89, 0x61, 0x72, 0xcf, 0xff, 0xff, 0xc3, 0x50, 0x0b, 0x45, 0x00, 0xde, 0x08, 0x41, + 0x08, 0x11, 0x89, 0x0c, 0xe0, 0x01, 0xa9, 0x0c, 0x89, 0x6c, 0x89, 0x03, 0x0b, 0x0d, 0x00, 0x03, + 0x89, 0x02, 0x08, 0x2d, 0x00, 0x9e, 0x89, 0x02, 0x08, 0x13, 0x00, 0x9e, 0x89, 0x02, 0xb8, 0xa2, + 0xa9, 0x02, 0x89, 0x01, 0xb8, 0x82, 0xf0, 0x03, 0x89, 0x01, 0xb8, 0xa2, 0xa9, 0x01, 0xd8, 0x50, + 0xaa, 0x0b, 0xd9, 0x00, 0xaa, 0x2a, 0xaa, 0x28, 0x0c, 0xca, 0x02, 0xe0, 0x82, 0x00, 0xd8, 0x00, + 0xf1, 0x7a, 0xff, 0xff, 0xca, 0xd0, 0xff, 0xff, 0xd6, 0x10 +}; +static const uint8_t patch_0502[] = { + 0xd7, 0x48, 0x72, 0xcf, 0xff, 0xff, 0xc7, 0xc0, 0x08, 0x27, 0x00, 0x40, 0xe1, 0xd1, 0xf4, 0x0f, + 0x8a, 0x01, 0x08, 0x1b, 0x00, 0xdf, 0x70, 0xcf, 0xff, 0xff, 0xc6, 0x44, 0x88, 0x08, 0xb8, 0xe4, + 0x8a, 0x0e, 0x20, 0xcf, 0x00, 0x62, 0x20, 0xd0, 0x00, 0x61, 0xaa, 0x0e, 0x7f, 0xe0, 0xd8, 0x00, + 0x78, 0xe0, 0xc0, 0xf1, 0x09, 0x6e, 0x05, 0xc0, 0x75, 0xcf, 0xff, 0xff, 0xc7, 0xc0, 0x70, 0xcf, + 0x00, 0x00, 0x55, 0x36, 0x78, 0x60, 0xc1, 0xa1, 0x95, 0xca, 0x77, 0xcf, 0xff, 0xff, 0xc8, 0xd4, + 0x9f, 0x1c, 0x08, 0x27, 0x00, 0x12, 0xd9, 0x08, 0x0d, 0x56, 0x03, 0x20, 0xda, 0x05, 0x9f, 0x3c, + 0x09, 0x13, 0x0f, 0x82, 0x00, 0x00, 0x0b, 0x00, 0xb8, 0x2a, 0x7e, 0x0c, 0x76, 0x2f, 0xf0, 0x0f, + 0x7e, 0x0c, 0x29, 0x41, 0x72, 0x8e, 0xf0, 0x0b, 0x78, 0x13, 0xd9, 0x08, 0x0d, 0x32, 0x03, 0x20, + 0xda, 0x05, 0x71, 0x08, 0x09, 0x52, 0x05, 0xe0, 0x70, 0xc9, 0x76, 0x08, 0x8d, 0x0f, 0x08, 0x25, + 0x00, 0x1e, 0x70, 0xcf, 0xff, 0xff, 0xc8, 0x2f, 0x0d, 0x1e, 0x04, 0x00, 0x28, 0x05, 0x03, 0xbe, + 0x70, 0xcf, 0xff, 0xff, 0xc9, 0x14, 0x90, 0x33, 0xb9, 0x24, 0x09, 0x2a, 0x05, 0xe0, 0x70, 0x2f, + 0x76, 0x08, 0x8d, 0x0e, 0x08, 0x83, 0x00, 0x5e, 0x8d, 0x01, 0x08, 0x7b, 0x00, 0xde, 0x8d, 0x0e, + 0x77, 0xcf, 0xff, 0xff, 0xc8, 0x4c, 0xb8, 0xa0, 0xad, 0x0e, 0x8f, 0x27, 0x8f, 0x49, 0x22, 0x02, + 0x80, 0x40, 0x00, 0x08, 0x00, 0x03, 0x21, 0x02, 0x00, 0x80, 0x78, 0x0e, 0xc0, 0x40, 0x8f, 0x08, + 0x78, 0x2c, 0x70, 0x2f, 0xe0, 0x63, 0x08, 0xfe, 0x05, 0xe0, 0xd9, 0x64, 0x97, 0x20, 0x78, 0x0d, + 0xb9, 0xc1, 0x21, 0x42, 0x80, 0x02, 0x22, 0xca, 0x00, 0x62, 0xc3, 0x00, 0x72, 0x59, 0x08, 0x17, + 0x00, 0xe3, 0xd9, 0x00, 0x70, 0xcf, 0xff, 0xff, 0xd8, 0xba, 0x88, 0x00, 0xe0, 0x80, 0x22, 0xcc, + 0x90, 0x22, 0xf2, 0x02, 0xd9, 0x01, 0x26, 0x2f, 0xf0, 0x47, 0xf2, 0x08, 0x8d, 0x0e, 0xb8, 0x80, + 0xad, 0x0e, 0xa5, 0xc4, 0x8d, 0x02, 0xb8, 0x82, 0xad, 0x02, 0x70, 0xcf, 0xff, 0xff, 0xd8, 0xba, + 0xa8, 0x40, 0xf0, 0x02, 0xa5, 0xc4, 0x00, 0x79, 0x05, 0xe0, 0xc0, 0xa1, 0x78, 0xe0, 0xc0, 0xf1, + 0xc5, 0xe1, 0x75, 0xcf, 0xff, 0xff, 0xc7, 0xc0, 0xd9, 0x00, 0xf0, 0x09, 0x70, 0xcf, 0xff, 0xff, + 0xd8, 0xbc, 0x78, 0x35, 0x80, 0x41, 0x80, 0x00, 0xe1, 0x02, 0xa0, 0x40, 0x09, 0xf3, 0x80, 0x94, + 0x70, 0xcf, 0xff, 0xff, 0xd7, 0x48, 0x09, 0xde, 0x03, 0x00, 0x21, 0x8a, 0x00, 0x14, 0xb5, 0x2a, + 0x00, 0x51, 0x05, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xcb, 0x54, 0xff, 0xff, 0xd7, 0x78 +}; +static const uint8_t awb_ccm[] = { + 0xc8, 0x92, 0x02, 0x67, 0xff, 0x1a, 0xff, 0xb3, 0xff, 0x80, 0x01, 0x66, 0x00, 0x03, 0xff, 0x9a, + 0xfe, 0xb4, 0x02, 0x4d, 0x01, 0xbf, 0xff, 0x01, 0xff, 0xf3, 0xff, 0x75, 0x01, 0x98, 0xff, 0xfd, + 0xff, 0x9a, 0xfe, 0xe7, 0x02, 0xa8, 0x01, 0xd9, 0xff, 0x26, 0xff, 0xf3, 0xff, 0xb3, 0x01, 0x32, + 0xff, 0xe8, 0xff, 0xda, 0xfe, 0xcd, 0x02, 0xc2 +}; +static const uint8_t awb_weights[] = { + 0xc8, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe7, 0x24, 0x15, 0x83, 0x20, 0x45, 0x03, 0xff, + 0x00, 0x7c +}; +static const uint16_t cpipe_regs_8_bit_a[] = { + 0xC92A, // CAM_LL_START_SATURATION + 0xC92B, // CAM_LL_END_SATURATION + 0xC92C, // CAM_LL_START_DESATURATION + 0xC92D, // CAM_LL_END_DESATURATION + 0xC92E, // CAM_LL_START_DEMOSAIC + 0xC92F, // CAM_LL_START_AP_GAIN + 0xC930, // CAM_LL_START_AP_THRESH + 0xC931, // CAM_LL_STOP_DEMOSAIC + 0xC932, // CAM_LL_STOP_AP_GAIN + 0xC933, // CAM_LL_STOP_AP_THRESH + 0xC934, // CAM_LL_START_NR_RED + 0xC935, // CAM_LL_START_NR_GREEN + 0xC936, // CAM_LL_START_NR_BLUE + 0xC937, // CAM_LL_START_NR_THRESH + 0xC938, // CAM_LL_STOP_NR_RED + 0xC939, // CAM_LL_STOP_NR_GREEN + 0xC93A, // CAM_LL_STOP_NR_BLUE + 0xC93B, // CAM_LL_STOP_NR_THRESH + 0xC942, // CAM_LL_START_CONTRAST_GRADIENT + 0xC943, // CAM_LL_STOP_CONTRAST_GRADIENT + 0xC944, // CAM_LL_START_CONTRAST_LUMA_PERCENTAGE + 0xC945, // CAM_LL_STOP_CONTRAST_LUMA_PERCENTAGE + 0xC950, // CAM_LL_CLUSTER_DC_GATE_PERCENTAGE + 0xC951, // CAM_LL_SUMMING_SENSITIVITY_FACTOR + 0xC87B, // CAM_AET_TARGET_AVERAGE_LUMA_DARK + 0xC878, // CAM_AET_AEMODE + 0xB42A, // CCM_DELTA_GAIN + 0xA80A, // AE_TRACK_AE_TRACKING_DAMPENING_SPEED +}; + +static const uint16_t cpipe_regs_8_bit_d[] = { + 0x80, // CAM_LL_START_SATURATION + 0x4B, // CAM_LL_END_SATURATION + 0x00, // CAM_LL_START_DESATURATION + 0xFF, // CAM_LL_END_DESATURATION + 0x3C, // CAM_LL_START_DEMOSAIC + 0x02, // CAM_LL_START_AP_GAIN + 0x06, // CAM_LL_START_AP_THRESH + 0x64, // CAM_LL_STOP_DEMOSAIC + 0x01, // CAM_LL_STOP_AP_GAIN + 0x0C, // CAM_LL_STOP_AP_THRESH + 0x3C, // CAM_LL_START_NR_RED + 0x3C, // CAM_LL_START_NR_GREEN + 0x3C, // CAM_LL_START_NR_BLUE + 0x0F, // CAM_LL_START_NR_THRESH + 0x64, // CAM_LL_STOP_NR_RED + 0x64, // CAM_LL_STOP_NR_GREEN + 0x64, // CAM_LL_STOP_NR_BLUE + 0x32, // CAM_LL_STOP_NR_THRESH + 0x38, // CAM_LL_START_CONTRAST_GRADIENT + 0x30, // CAM_LL_STOP_CONTRAST_GRADIENT + 0x50, // CAM_LL_START_CONTRAST_LUMA_PERCENTAGE + 0x19, // CAM_LL_STOP_CONTRAST_LUMA_PERCENTAGE + 0x05, // CAM_LL_CLUSTER_DC_GATE_PERCENTAGE + 0x40, // CAM_LL_SUMMING_SENSITIVITY_FACTOR + 0x1B, // CAM_AET_TARGET_AVERAGE_LUMA_DARK + 0x00, // CAM_AET_AEMODE (was 0x0E) + 0x05, // CCM_DELTA_GAIN + 0x20 // AE_TRACK_AE_TRACKING_DAMPENING_SPEED +}; + +static const uint16_t cpipe_regs_16_bit[][2] = { + {0xC926, 0x0020}, // CAM_LL_START_BRIGHTNESS + {0xC928, 0x009A}, // CAM_LL_STOP_BRIGHTNESS + {0xC946, 0x0070}, // CAM_LL_START_GAIN_METRIC + {0xC948, 0x00F3}, // CAM_LL_STOP_GAIN_METRIC + {0xC952, 0x0020}, // CAM_LL_START_TARGET_LUMA_BM + {0xC954, 0x009A}, // CAM_LL_STOP_TARGET_LUMA_BM + {0xC93C, 0x0020}, // CAM_LL_START_CONTRAST_BM + {0xC93E, 0x009A}, // CAM_LL_STOP_CONTRAST_BM + {0xC940, 0x00DC}, // CAM_LL_GAMMA + {0xC94A, 0x0230}, // CAM_LL_START_FADE_TO_BLACK_LUMA + {0xC94C, 0x0010}, // CAM_LL_STOP_FADE_TO_BLACK_LUMA + {0xC94E, 0x01CD}, // CAM_LL_CLUSTER_DC_TH_BM + {0xC890, 0x0080}, // CAM_AET_TARGET_GAIN + {0xC886, 0x0100}, // CAM_AET_AE_MAX_VIRT_AGAIN + {0xC87C, 0x005A} // CAM_AET_BLACK_CLIPPING_TARGET +}; + +static int host_command(sensor_t *sensor, uint16_t command) { + if (omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_HOST_COMMAND, (command | MT9M114_HC_OK)) != 0) { + return -1; + } + + for (mp_uint_t start = mp_hal_ticks_ms();; mp_hal_delay_ms(MT9M114_HC_DELAY)) { + uint16_t reg_data; + + if (omv_i2c_readw2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_HOST_COMMAND, ®_data) != 0) { + return -1; + } + + if ((reg_data & command) == 0) { + return (reg_data & MT9M114_HC_OK) ? 0 : -1; + } + + if ((mp_hal_ticks_ms() - start) >= MT9M114_HC_TIMEOUT) { + return -1; + } + } + + return 0; +} + +static int load_patch(sensor_t *sensor, const uint8_t *patch, size_t patch_len, + uint16_t patch_loader_address, uint16_t patch_id, uint32_t patch_firmware_id) { + int ret = 0; + // Patch address is stashed in the first two bytes. + uint16_t patch_address = (((uint16_t) patch[0]) << 8) | patch[1]; + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_XMDA_ACCESS_CTL_STAT, patch_address >> 15); + ret |= + omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_XMDA_PHYSICAL_ADDRESS_ACCESS, patch_address & 0x7FFF); + ret |= omv_i2c_write_bytes(&sensor->i2c_bus, sensor->slv_addr, (uint8_t *) patch, patch_len, OMV_I2C_XFER_NO_FLAGS); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_XMDA_LOGIC_ADDRESS_ACCESS, 0x0000); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_PATCHLDR_LOADER_ADDRESS, patch_loader_address); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_PATCHLDR_PATCH_ID, patch_id); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_PATCHLDR_FIRMWARE_ID_HI, patch_firmware_id >> 16); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_PATCHLDR_FIRMWARE_ID_LO, patch_firmware_id); + + ret |= host_command(sensor, MT9M114_HC_APPLY_PATCH); + + uint8_t reg_data; + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_PATCHLDR_APPLY_STATUS, ®_data); + ret |= (reg_data == 0) ? 0: -1; + + return ret; +} + +static int load_awb_cmm(sensor_t *sensor) { + return omv_i2c_write_bytes(&sensor->i2c_bus, sensor->slv_addr, (uint8_t *) awb_ccm, sizeof(awb_ccm), OMV_I2C_XFER_NO_FLAGS); +} + +static int load_awb(sensor_t *sensor) { + int ret = 0; + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_CAM_AWB_XSCALE, 0x03); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_CAM_AWB_YSCALE, 0x02); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_CAM_AWB_Y_SHIFT_PRE_ADJ, 0x003C); + ret |= omv_i2c_write_bytes(&sensor->i2c_bus, sensor->slv_addr, + (uint8_t *) awb_weights, sizeof(awb_weights), OMV_I2C_XFER_NO_FLAGS); + + for (int i = 0xC90C; i <= 0xC911; i++) { + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, i, 0x80); + } + + return ret; +} + +static int load_cpipe(sensor_t *sensor) { + int ret = 0; + + int a_size = sizeof(cpipe_regs_8_bit_a) / sizeof(cpipe_regs_8_bit_a[0]); + int d_size = sizeof(cpipe_regs_8_bit_d) / sizeof(cpipe_regs_8_bit_d[0]); + + for (int i = 0, ii = IM_MIN(a_size, d_size); i < ii; i++) { + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, cpipe_regs_8_bit_a[i], cpipe_regs_8_bit_d[i]); + } + + for (int i = 0; i < (sizeof(cpipe_regs_16_bit) / sizeof(cpipe_regs_16_bit[0])); i++) { + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, cpipe_regs_16_bit[i][0], cpipe_regs_16_bit[i][1]); + } + + return ret; +} + +static int set_system_state(sensor_t *sensor, uint8_t state) { + if (omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SYSMGR_NEXT_STATE, state) != 0) { + return -1; + } + + return (host_command(sensor, MT9M114_HC_SET_STATE) == 0) ? 0 : -1; +} + +static int change_config(sensor_t *sensor) { + return set_system_state(sensor, MT9M114_SS_ENTER_CONFIG_CHANGE); +} + +static int refresh(sensor_t *sensor) { + int ret = host_command(sensor, MT9M114_HC_REFRESH); + + if (ret == 0) { + return ret; + } + + uint8_t reg_data; + + if (omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SEQ_ERROR_CODE, ®_data) != 0) { + return -1; + } + + return -reg_data; +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat); +static int set_framesize(sensor_t *sensor, framesize_t framesize); +static int reset(sensor_t *sensor) { + int ret = 0; + readout_x = 0; + readout_y = 0; + + readout_w = ACTIVE_SENSOR_WIDTH; + readout_h = ACTIVE_SENSOR_HEIGHT; + + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SYSCTL, MT9M114_SYSCTL_SOFT_RESET); + mp_hal_delay_ms(1); + + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SYSCTL, 0); + mp_hal_delay_ms(45); + + for (mp_uint_t start = mp_hal_ticks_ms();; mp_hal_delay_ms(MT9M114_HC_DELAY)) { + uint16_t reg_data; + + if (omv_i2c_readw2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_HOST_COMMAND, ®_data) != 0) { + return -1; + } + + if ((reg_data & MT9M114_HC_SET_STATE) != 0) { + break; + } + + if ((mp_hal_ticks_ms() - start) >= MT9M114_HC_TIMEOUT) { + return -1; + } + } + + for (mp_uint_t start = mp_hal_ticks_ms();; mp_hal_delay_ms(MT9M114_HC_DELAY)) { + uint8_t reg_data; + + if (omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SYSMGR_CURRENT_STATE, ®_data) != 0) { + return -1; + } + + if (reg_data != MT9M114_SS_STANDBY) { + break; + } + + if ((mp_hal_ticks_ms() - start) >= MT9M114_HC_TIMEOUT) { + return -1; + } + } + + // Errata 2 (Black Frame Output) + uint16_t reg; + ret |= omv_i2c_readw2(&sensor->i2c_bus, sensor->slv_addr, 0x301A, ®); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, 0x301A, reg | (1 << 9)); + + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_CAM_SYSCTL_PLL_DIVIDER_M_N, + (sensor_get_xclk_frequency() == MT9M114_XCLK_FREQ) + ? 0x120 // xclk=24MHz, m=32, n=1, sensor=48MHz, bus=76.8MHz + : 0x448); // xclk=25MHz, m=72, n=4, sensor=45MHz, bus=72MHz + + for (int i = 0; i < (sizeof(default_regs) / sizeof(default_regs[0])); i++) { + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, default_regs[i][0], default_regs[i][1]); + } + + ret |= set_framesize(sensor, FRAMESIZE_SXGAM); + + // Black level correction fix + ret |= load_patch(sensor, patch_0202, sizeof(patch_0202), 0x010c, 0x0202, 0x41030202); + // Adaptive Sensitivity + ret |= load_patch(sensor, patch_0302, sizeof(patch_0302), 0x04b4, 0x0302, 0x41030202); + // System Idle Control (auto wakeup from standby) + ret |= load_patch(sensor, patch_0402, sizeof(patch_0402), 0x0634, 0x0402, 0x41030202); + // Ambient light sensor + ret |= load_patch(sensor, patch_0502, sizeof(patch_0502), 0x0884, 0x0502, 0x41030202); + + ret |= load_awb_cmm(sensor); + ret |= load_awb(sensor); + ret |= load_cpipe(sensor); + + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_CAM_PORT_OUTPUT_CONTROL, 0x8008); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_PAD_SLEW, 0x0777); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SENSOR_CONTROL_READ_MODE, + MT9M114_SENSOR_CONTROL_READ_MODE_HMIRROR | MT9M114_SENSOR_CONTROL_READ_MODE_VFLIP); + + ret |= change_config(sensor); + + return ret; +} + +static int sleep(sensor_t *sensor, int enable) { + uint8_t state = MT9M114_SS_LEAVE_STANDBY; + uint8_t new_state = MT9M114_SS_STREAMING; + + if (enable) { + state = MT9M114_SS_ENTER_STANDBY; + new_state = MT9M114_SS_STANDBY; + } + + if (set_system_state(sensor, state) != 0) { + return -1; + } + + for (mp_uint_t start = mp_hal_ticks_ms();; mp_hal_delay_ms(MT9M114_HC_DELAY)) { + uint8_t reg_data; + + if (omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SYSMGR_CURRENT_STATE, ®_data) != 0) { + return -1; + } + + if (reg_data == new_state) { + return 0; + } + + if ((mp_hal_ticks_ms() - start) >= MT9M114_HC_TIMEOUT) { + return -1; + } + } +} + + +static int read_reg(sensor_t *sensor, uint16_t reg_addr) { + uint16_t reg_data; + + if (omv_i2c_readw2(&sensor->i2c_bus, sensor->slv_addr, reg_addr, ®_data) != 0) { + return -1; + } + + return reg_data; +} + +static int write_reg(sensor_t *sensor, uint16_t reg_addr, uint16_t reg_data) { + return omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, reg_addr, reg_data); +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) { + uint16_t reg = 0; + + switch (pixformat) { + case PIXFORMAT_GRAYSCALE: + case PIXFORMAT_YUV422: + reg = MT9M114_OUTPUT_FORMAT_YUV | MT9M114_OUTPUT_FORMAT_SWAP_BYTES; + break; + case PIXFORMAT_RGB565: + reg = MT9M114_OUTPUT_FORMAT_RGB | MT9M114_OUTPUT_FORMAT_RGB565 | MT9M114_OUTPUT_FORMAT_SWAP_BYTES; + break; + case PIXFORMAT_BAYER: + if ((sensor->framesize != FRAMESIZE_INVALID) + && (sensor->framesize != FRAMESIZE_VGA) + && (sensor->framesize != FRAMESIZE_SXGAM)) { + return -1; + } + reg = MT9M114_OUTPUT_FORMAT_BAYER | MT9M114_OUTPUT_FORMAT_RAW_BAYER_10; + break; + default: + return -1; + } + + if (omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_CAM_OUTPUT_FORMAT, reg) != 0) { + return -1; + } + + return change_config(sensor); +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) { + int ret = 0; + uint16_t w = resolution[framesize][0]; + uint16_t h = resolution[framesize][1]; + + if ((sensor->pixformat == PIXFORMAT_BAYER) && ((framesize != FRAMESIZE_VGA) && (framesize != FRAMESIZE_SXGAM))) { + return -1; + } + + if ((w > ACTIVE_SENSOR_WIDTH) || (h > ACTIVE_SENSOR_HEIGHT)) { + return -1; + } + + // Step 0: Clamp readout settings. + + readout_w = IM_MAX(readout_w, w); + readout_h = IM_MAX(readout_h, h); + + int readout_x_max = (ACTIVE_SENSOR_WIDTH - readout_w) / 2; + int readout_y_max = (ACTIVE_SENSOR_HEIGHT - readout_h) / 2; + readout_x = IM_MAX(IM_MIN(readout_x, readout_x_max), -readout_x_max); + readout_y = IM_MAX(IM_MIN(readout_y, readout_y_max), -readout_y_max); + + // Step 1: Determine readout area and subsampling amount. + + uint16_t read_mode; + + if (omv_i2c_readw2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SENSOR_CONTROL_READ_MODE, &read_mode) != 0) { + return -1; + } + + int read_mode_div = 1; + read_mode &= ~(MT9M114_SENSOR_CONTROL_READ_MODE_HBIN_MASK | MT9M114_SENSOR_CONTROL_READ_MODE_VBIN_MASK); + + if ((w <= (readout_w / 2)) && (h <= (readout_h / 2))) { + read_mode_div = 2; + read_mode |= MT9M114_SENSOR_CONTROL_READ_MODE_HBIN | MT9M114_SENSOR_CONTROL_READ_MODE_VBIN; + } + + // Step 2: Determine horizontal and vertical start and end points. + + uint16_t sensor_w = readout_w + DUMMY_WIDTH_BUFFER; // camera hardware needs dummy pixels to sync + uint16_t sensor_h = readout_h + DUMMY_HEIGHT_BUFFER; // camera hardware needs dummy lines to sync + + uint16_t sensor_ws = IM_MAX(IM_MIN((((ACTIVE_SENSOR_WIDTH - sensor_w) / 4) + (readout_x / 2)) * 2, + ACTIVE_SENSOR_WIDTH - sensor_w), + -(DUMMY_WIDTH_BUFFER / 2)) + DUMMY_COLUMNS; // must be multiple of 2 + uint16_t sensor_we = sensor_ws + sensor_w - 1; + + int sensor_ws_mod = sensor_ws % (read_mode_div * 4); // multiple 4/8 + if (sensor_ws_mod) { + sensor_ws -= sensor_ws_mod; + sensor_we += read_mode_div; + } + + uint16_t sensor_hs = IM_MAX(IM_MIN((((ACTIVE_SENSOR_HEIGHT - sensor_h) / 4) - (readout_y / 2)) * 2, + ACTIVE_SENSOR_HEIGHT - sensor_h), + -(DUMMY_HEIGHT_BUFFER / 2)) + DUMMY_LINES; // must be multiple of 2 + uint16_t sensor_he = sensor_hs + sensor_h - 1; + + int sensor_hs_mod = sensor_hs % (read_mode_div * 4); // multiple 4/8 + if (sensor_hs_mod) { + sensor_hs -= sensor_hs_mod; + sensor_he += read_mode_div; + } + + // Step 3: Determine scaling window offset. + + float ratio = IM_MIN((readout_w / read_mode_div) / ((float) w), (readout_h / read_mode_div) / ((float) h)); + + uint16_t w_mul = w * ratio; + uint16_t h_mul = h * ratio; + uint16_t x_off = ((readout_w / read_mode_div) - w_mul) / 2; + uint16_t y_off = ((readout_h / read_mode_div) - h_mul) / 2; + + // Step 4: Write regs. + + uint16_t frame_length_lines = (readout_h / read_mode_div) + ((read_mode_div == 2) ? 40 : 39) + DUMMY_HEIGHT_BUFFER; + uint16_t line_length_pck = (readout_w / read_mode_div) + ((read_mode_div == 2) ? 534 : 323) + DUMMY_WIDTH_BUFFER; + + // Errata 4 (Cam_port_clock_slowdown issues/limitations) + if (!(line_length_pck % 5)) { + line_length_pck += 1; + } + + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SENSOR_CFG_Y_ADDR_START, sensor_hs); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SENSOR_CFG_X_ADDR_START, sensor_ws); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SENSOR_CFG_Y_ADDR_END, sensor_he); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SENSOR_CFG_X_ADDR_END, sensor_we); + + int pixclk = (sensor_get_xclk_frequency() == MT9M114_XCLK_FREQ) ? 48000000 : 45000000; + + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SENSOR_CFG_PIXCLK, pixclk >> 16); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SENSOR_CFG_PIXCLK + 2, pixclk); + + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SENSOR_CFG_FINE_INTEG_TIME_MIN, + (read_mode_div == 2) ? 451 : 219); // figured this out by checking register wizard against datasheet + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SENSOR_CFG_FINE_INTEG_TIME_MAX, + (read_mode_div == 2) ? 947 : 1480); // figured this out by checking register wizard against datasheet + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SENSOR_CFG_FRAME_LENGTH_LINES, + frame_length_lines); // figured this out by checking register wizard against datasheet + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SENSOR_CFG_LINE_LENGTH_PCK, + line_length_pck); // figured this out by checking register wizard against datasheet + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SENSOR_CFG_FINE_CORRECTION, + (read_mode_div == 2) ? 224 : 96); // figured this out by checking register wizard against datasheet + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SENSOR_CFG_CPIPE_LAST_ROW, + (readout_h / read_mode_div) + 3); // figured this out by checking register wizard against datasheet + + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SENSOR_CONTROL_READ_MODE, read_mode); + + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_CROP_WINDOW_X_OFFSET, x_off); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_CROP_WINDOW_Y_OFFSET, y_off); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_CROP_WINDOW_WIDTH, w_mul); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_CROP_WINDOW_HEIGHT, h_mul); + + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_CAM_OUTPUT_WIDTH, w); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_CAM_OUTPUT_HEIGHT, h); + + float rate = (((float) pixclk) / (frame_length_lines * line_length_pck)) * 256; + + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_CAM_AET_MAX_FRAME_RATE, fast_ceilf(rate)); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_CAM_AET_MIN_FRAME_RATE, fast_floorf(rate / 2.f)); + + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_AWB_CLIP_WINDOW_X_START, 0); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_AWB_CLIP_WINDOW_Y_START, 0); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_AWB_CLIP_WINDOW_X_END, w - 1); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_AWB_CLIP_WINDOW_Y_END, h - 1); + + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_AE_INITIAL_WINDOW_X_START, 0); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_AE_INITIAL_WINDOW_Y_START, 0); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_AE_INITIAL_WINDOW_X_END, (w / 5) - 1); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_AE_INITIAL_WINDOW_Y_END, (h / 5) - 1); + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_AUTO_BINNING_MODE, 0); + ret |= omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_LL_ALGO, 0); + + return change_config(sensor); +} + +static int set_framerate(sensor_t *sensor, int framerate) { + if (omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_CAM_AET_MAX_FRAME_RATE, framerate * 256) != 0) { + return -1; + } + + if (omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_CAM_AET_MIN_FRAME_RATE, framerate * 128) != 0) { + return -1; + } + + return change_config(sensor); +} + +static int set_contrast(sensor_t *sensor, int level) { + // -16 to +16 + int new_level = (level > 0) ? (level * 2) : level; + + if ((new_level < -16) || (32 < new_level)) { + return -1; + } + + if (omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_UVC_CONTRAST_CONTROL, new_level + 32) != 0) { + return -1; + } + + return refresh(sensor); +} + +static int set_brightness(sensor_t *sensor, int level) { + // -16 to +16 + int new_level = level * 2; + + if ((new_level < -32) || (32 < new_level)) { + return -1; + } + + if (omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_UVC_BRIGHTNESS_CONTROL, new_level + 55) != 0) { + return -1; + } + + return refresh(sensor); +} + +static int set_saturation(sensor_t *sensor, int level) { + // -16 to +16 + int new_level = level * 8; + + if ((new_level < -128) || (128 < new_level)) { + return -1; + } + + new_level = IM_MIN(new_level, 127); + + if (omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_UVC_SATURATION_CONTROL, new_level + 128) != 0) { + return -1; + } + + return refresh(sensor); +} + +static int set_gainceiling(sensor_t *sensor, gainceiling_t gainceiling) { + return 0; +} + +static int set_colorbar(sensor_t *sensor, int enable) { + if (omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_CAM_MODE_SELECT, enable ? 2 : 0) != 0) { + return -1; + } + + return change_config(sensor); +} + +static int set_auto_gain(sensor_t *sensor, int enable, float gain_db, float gain_db_ceiling) { + if (omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_UVC_AE_MODE_CONTROL, enable ? 0x2 : 0x1) != 0) { + return -1; + } + + if ((enable == 0) && (!isnanf(gain_db)) && (!isinff(gain_db))) { + int gain = IM_MAX(IM_MIN(expf((gain_db / 20.0f) * M_LN10) * 32.0f, 0xffff), 0x0000); + + if (omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_UVC_GAIN_CONTROL, gain) != 0) { + return -1; + } + } + + return refresh(sensor); +} + +static int get_gain_db(sensor_t *sensor, float *gain_db) { + uint16_t gain; + + if (omv_i2c_readw2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_UVC_GAIN_CONTROL, &gain) != 0) { + return -1; + } + + *gain_db = 20.0f * log10f(gain / 32.0f); + + return 0; +} + +static int set_auto_exposure(sensor_t *sensor, int enable, int exposure_us) { + if (omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_UVC_AE_MODE_CONTROL, enable ? 0x2 : 0x1) != 0) { + return -1; + } + + if ((enable == 0) && (exposure_us >= 0)) { + if (omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_UVC_MANUAL_EXPOSURE_CONFIG, 0x1) != 0) { + return -1; + } + + int exposure_100_us = exposure_us / 100; + + if (omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_UVC_EXPOSURE_TIME_ABSOLUTE_CTRL, + exposure_100_us >> 16) != 0) { + return -1; + } + + if (omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_UVC_EXPOSURE_TIME_ABSOLUTE_CTRL + 2, + exposure_100_us) != 0) { + return -1; + } + } + + return refresh(sensor); +} + +static int get_exposure_us(sensor_t *sensor, int *exposure_us) { + uint16_t reg_h, reg_l; + + if (omv_i2c_readw2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_UVC_EXPOSURE_TIME_ABSOLUTE_CTRL, ®_h) != 0) { + return -1; + } + + if (omv_i2c_readw2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_UVC_EXPOSURE_TIME_ABSOLUTE_CTRL + 2, ®_l) != 0) { + return -1; + } + + *exposure_us = ((reg_h << 16) | reg_l) * 100; + + return 0; +} + +static int set_auto_whitebal(sensor_t *sensor, int enable, float r_gain_db, float g_gain_db, float b_gain_db) { + if (omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_UVC_WHITE_BALANCE_AUTO_CONTROL, + enable ? 0x1 : 0x0) != 0) { + return -1; + } + + return 0; +} + +static int get_rgb_gain_db(sensor_t *sensor, float *r_gain_db, float *g_gain_db, float *b_gain_db) { + *r_gain_db = 0; + *g_gain_db = 0; + *b_gain_db = 0; + + return 0; +} + +static int set_hmirror(sensor_t *sensor, int enable) { + uint16_t reg_data; + + if (omv_i2c_readw2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SENSOR_CONTROL_READ_MODE, ®_data) != 0) { + return -1; + } + + reg_data = (reg_data & (~MT9M114_SENSOR_CONTROL_READ_MODE_HMIRROR)) | + (enable ? 0x0 : MT9M114_SENSOR_CONTROL_READ_MODE_HMIRROR); // inverted + + if (omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SENSOR_CONTROL_READ_MODE, reg_data) != 0) { + return -1; + } + + return change_config(sensor); +} + +static int set_vflip(sensor_t *sensor, int enable) { + uint16_t reg_data; + + if (omv_i2c_readw2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SENSOR_CONTROL_READ_MODE, ®_data) != 0) { + return -1; + } + + reg_data = (reg_data & (~MT9M114_SENSOR_CONTROL_READ_MODE_VFLIP)) | + (enable ? 0x0 : MT9M114_SENSOR_CONTROL_READ_MODE_VFLIP); // inverted + + if (omv_i2c_writew2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_SENSOR_CONTROL_READ_MODE, reg_data) != 0) { + return -1; + } + + return change_config(sensor); +} + +static int set_special_effect(sensor_t *sensor, sde_t sde) { + switch (sde) { + case SDE_NEGATIVE: + if (omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_CAM_SFX_CONTROL, 0x3) != 0) { + return -1; + } + break; + case SDE_NORMAL: + if (omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, MT9M114_REG_CAM_SFX_CONTROL, 0x0) != 0) { + return -1; + } + break; + default: + return -1; + } + + return refresh(sensor); +} + +static int ioctl(sensor_t *sensor, int request, va_list ap) { + int ret = 0; + + switch (request) { + case IOCTL_SET_READOUT_WINDOW: { + int tmp_readout_x = va_arg(ap, int); + int tmp_readout_y = va_arg(ap, int); + int tmp_readout_w = IM_MAX(IM_MIN(va_arg(ap, int), ACTIVE_SENSOR_WIDTH), resolution[sensor->framesize][0]); + int tmp_readout_h = IM_MAX(IM_MIN(va_arg(ap, int), ACTIVE_SENSOR_HEIGHT), resolution[sensor->framesize][1]); + int readout_x_max = (ACTIVE_SENSOR_WIDTH - tmp_readout_w) / 2; + int readout_y_max = (ACTIVE_SENSOR_HEIGHT - tmp_readout_h) / 2; + tmp_readout_x = IM_MAX(IM_MIN(tmp_readout_x, readout_x_max), -readout_x_max); + tmp_readout_y = IM_MAX(IM_MIN(tmp_readout_y, readout_y_max), -readout_y_max); + bool changed = (tmp_readout_x != readout_x) || (tmp_readout_y != readout_y) || + (tmp_readout_w != readout_w) || (tmp_readout_h != readout_h); + readout_x = tmp_readout_x; + readout_y = tmp_readout_y; + readout_w = tmp_readout_w; + readout_h = tmp_readout_h; + if (changed && (sensor->framesize != FRAMESIZE_INVALID)) { + ret |= set_framesize(sensor, sensor->framesize); + } + break; + } + case IOCTL_GET_READOUT_WINDOW: { + *va_arg(ap, int *) = readout_x; + *va_arg(ap, int *) = readout_y; + *va_arg(ap, int *) = readout_w; + *va_arg(ap, int *) = readout_h; + break; + } + default: { + ret = -1; + break; + } + } + + return ret; +} + +int mt9m114_init(sensor_t *sensor) { + // Initialize sensor structure. + sensor->reset = reset; + sensor->sleep = sleep; + sensor->read_reg = read_reg; + sensor->write_reg = write_reg; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_framerate = set_framerate; + sensor->set_contrast = set_contrast; + sensor->set_brightness = set_brightness; + sensor->set_saturation = set_saturation; + sensor->set_gainceiling = set_gainceiling; + sensor->set_colorbar = set_colorbar; + sensor->set_auto_gain = set_auto_gain; + sensor->get_gain_db = get_gain_db; + sensor->set_auto_exposure = set_auto_exposure; + sensor->get_exposure_us = get_exposure_us; + sensor->set_auto_whitebal = set_auto_whitebal; + sensor->get_rgb_gain_db = get_rgb_gain_db; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + sensor->set_special_effect = set_special_effect; + sensor->ioctl = ioctl; + + // Set sensor flags + sensor->hw_flags.vsync = 0; + sensor->hw_flags.hsync = 0; + sensor->hw_flags.pixck = 0; + sensor->hw_flags.fsync = 0; + sensor->hw_flags.jpege = 0; + sensor->hw_flags.gs_bpp = 2; + sensor->hw_flags.rgb_swap = 0; + sensor->hw_flags.bayer = SENSOR_HW_FLAGS_BAYER_GBRG; + sensor->hw_flags.yuv_order = SENSOR_HW_FLAGS_YVU422; + + return 0; +} + +#endif // (OMV_ENABLE_MT9M114 == 1) diff --git a/components/3rd_party/omv/omv/sensors/mt9m114.h b/components/3rd_party/omv/omv/sensors/mt9m114.h new file mode 100644 index 00000000..261e1ed1 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/mt9m114.h @@ -0,0 +1,15 @@ +/* + * 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. + * + * MT9M114 driver. + */ +#ifndef __MT9M114_H__ +#define __MT9M114_H__ +#define MT9M114_XCLK_FREQ (24000000) +int mt9m114_init(sensor_t *sensor); +#endif // __MT9M114_H__ diff --git a/components/3rd_party/omv/omv/sensors/mt9m114_regs.h b/components/3rd_party/omv/omv/sensors/mt9m114_regs.h new file mode 100644 index 00000000..97dd7ac1 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/mt9m114_regs.h @@ -0,0 +1,145 @@ +/* + * 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. + * + * MT9M114 driver. + */ +#ifndef __REG_REGS_H__ +#define __REG_REGS_H__ + +#define MT9M114_REG_PAD_SLEW (0x001E) + +#define MT9M114_REG_SYSCTL (0x001A) +#define MT9M114_SYSCTL_SOFT_RESET (1 << 0) + +#define MT9M114_REG_HOST_COMMAND (0x0080) +#define MT9M114_HC_APPLY_PATCH (0x0001) +#define MT9M114_HC_SET_STATE (0x0002) +#define MT9M114_HC_REFRESH (0x0004) +#define MT9M114_HC_WAIT_FOR_EVENT (0x0008) +#define MT9M114_HC_OK (0x8000) + +#define MT9M114_HC_DELAY (1) +#define MT9M114_HC_TIMEOUT (100) + +#define MT9M114_SS_ENTER_CONFIG_CHANGE (0x28) +#define MT9M114_SS_STREAMING (0x31) +#define MT9M114_SS_START_STREAMING (0x34) +#define MT9M114_SS_ENTER_SUSPEND (0x40) +#define MT9M114_SS_SUSPENDED (0x41) +#define MT9M114_SS_ENTER_STANDBY (0x50) +#define MT9M114_SS_STANDBY (0x52) +#define MT9M114_SS_LEAVE_STANDBY (0x54) + +#define MT9M114_REG_XMDA_ACCESS_CTL_STAT (0x0982) +#define MT9M114_REG_XMDA_PHYSICAL_ADDRESS_ACCESS (0x098A) +#define MT9M114_REG_XMDA_LOGIC_ADDRESS_ACCESS (0x098E) + +#define MT9M114_REG_SEQ_ERROR_CODE (0x8406) + +#define MT9M114_REG_CAM_OUTPUT_FORMAT (0xC86C) +#define MT9M114_OUTPUT_FORMAT_SWAP_RB (1 << 0) +#define MT9M114_OUTPUT_FORMAT_SWAP_BYTES (1 << 1) +#define MT9M114_OUTPUT_FORMAT_MONO (1 << 2) +#define MT9M114_OUTPUT_FORMAT_BT656 (1 << 3) +#define MT9M114_OUTPUT_FORMAT_BT656_FIXED (1 << 4) +#define MT9M114_OUTPUT_FORMAT_YUV (0 << 8) +#define MT9M114_OUTPUT_FORMAT_RGB (1 << 8) +#define MT9M114_OUTPUT_FORMAT_BAYER (2 << 8) +#define MT9M114_OUTPUT_FORMAT_RAW_BAYER_10 (0 << 10) +#define MT9M114_OUTPUT_FORMAT_RAW_BAYER_10_PRE (1 << 10) +#define MT9M114_OUTPUT_FORMAT_RAW_BAYER_10_POST (2 << 10) +#define MT9M114_OUTPUT_FORMAT_PROCESSED_BAYER (3 << 10) +#define MT9M114_OUTPUT_FORMAT_RGB565 (0 << 12) +#define MT9M114_OUTPUT_FORMAT_RGB555 (1 << 12) +#define MT9M114_OUTPUT_FORMAT_XRGB444 (2 << 12) +#define MT9M114_OUTPUT_FORMAT_RGB444X (3 << 12) + +#define MT9M114_REG_LL_ALGO (0xBC04) + +#define MT9M114_REG_SENSOR_CFG_Y_ADDR_START (0xC800) +#define MT9M114_REG_SENSOR_CFG_X_ADDR_START (0xC802) +#define MT9M114_REG_SENSOR_CFG_Y_ADDR_END (0xC804) +#define MT9M114_REG_SENSOR_CFG_X_ADDR_END (0xC806) +#define MT9M114_REG_SENSOR_CFG_PIXCLK (0xC808) + +#define MT9M114_REG_SENSOR_CFG_FINE_INTEG_TIME_MIN (0xC80E) +#define MT9M114_REG_SENSOR_CFG_FINE_INTEG_TIME_MAX (0xC810) +#define MT9M114_REG_SENSOR_CFG_FRAME_LENGTH_LINES (0xC812) +#define MT9M114_REG_SENSOR_CFG_LINE_LENGTH_PCK (0xC814) +#define MT9M114_REG_SENSOR_CFG_FINE_CORRECTION (0xC816) +#define MT9M114_REG_SENSOR_CFG_CPIPE_LAST_ROW (0xC818) + +#define MT9M114_REG_SENSOR_CONTROL_READ_MODE (0xC834) +#define MT9M114_SENSOR_CONTROL_READ_MODE_HMIRROR (0x1) +#define MT9M114_SENSOR_CONTROL_READ_MODE_VFLIP (0x2) +#define MT9M114_SENSOR_CONTROL_READ_MODE_HBIN_MASK (0x30) +#define MT9M114_SENSOR_CONTROL_READ_MODE_HBIN (0x30) +#define MT9M114_SENSOR_CONTROL_READ_MODE_VBIN_MASK (0x300) +#define MT9M114_SENSOR_CONTROL_READ_MODE_VBIN (0x300) + +#define MT9M114_REG_CAM_MODE_SELECT (0xC84C) + +#define MT9M114_REG_CROP_WINDOW_X_OFFSET (0xC854) +#define MT9M114_REG_CROP_WINDOW_Y_OFFSET (0xC856) +#define MT9M114_REG_CROP_WINDOW_WIDTH (0xC858) +#define MT9M114_REG_CROP_WINDOW_HEIGHT (0xC85A) + +#define MT9M114_REG_CAM_OUTPUT_WIDTH (0xC868) +#define MT9M114_REG_CAM_OUTPUT_HEIGHT (0xC86A) + +#define MT9M114_REG_CAM_SFX_CONTROL (0xC874) + +#define MT9M114_REG_CAM_AET_MAX_FRAME_RATE (0xC88C) +#define MT9M114_REG_CAM_AET_MIN_FRAME_RATE (0xC88E) + +#define MT9M114_REG_CAM_AWB_XSCALE (0xC8F2) +#define MT9M114_REG_CAM_AWB_YSCALE (0xC8F3) + +#define MT9M114_REG_CAM_AWB_X_SHIFT_PRE_ADJ (0xC904) +#define MT9M114_REG_CAM_AWB_Y_SHIFT_PRE_ADJ (0xC906) + +#define MT9M114_REG_AWB_CLIP_WINDOW_X_START (0xC914) +#define MT9M114_REG_AWB_CLIP_WINDOW_Y_START (0xC916) +#define MT9M114_REG_AWB_CLIP_WINDOW_X_END (0xC918) +#define MT9M114_REG_AWB_CLIP_WINDOW_Y_END (0xC91A) + +#define MT9M114_REG_CAM_LL_START_SATURATION (0xC92A) + +#define MT9M114_REG_AE_INITIAL_WINDOW_X_START (0xC91C) +#define MT9M114_REG_AE_INITIAL_WINDOW_Y_START (0xC91E) +#define MT9M114_REG_AE_INITIAL_WINDOW_X_END (0xC920) +#define MT9M114_REG_AE_INITIAL_WINDOW_Y_END (0xC922) + +#define MT9M114_REG_CAM_SYSCTL_PLL_DIVIDER_M_N (0xC980) +#define MT9M114_REG_CAM_PORT_OUTPUT_CONTROL (0xC984) + +#define MT9M114_REG_UVC_AE_MODE_CONTROL (0xCC00) +#define MT9M114_REG_UVC_WHITE_BALANCE_AUTO_CONTROL (0xCC01) +#define MT9M114_REG_UVC_EXPOSURE_TIME_ABSOLUTE_CTRL (0xCC04) +#define MT9M114_REG_UVC_BRIGHTNESS_CONTROL (0xCC0A) +#define MT9M114_REG_UVC_CONTRAST_CONTROL (0xCC0C) +#define MT9M114_REG_UVC_GAIN_CONTROL (0xCC0E) +#define MT9M114_REG_UVC_SATURATION_CONTROL (0xCC12) +#define MT9M114_REG_UVC_MANUAL_EXPOSURE_CONFIG (0xCC20) + +#define MT9M114_REG_SYSMGR_NEXT_STATE (0xDC00) +#define MT9M114_REG_SYSMGR_CURRENT_STATE (0xDC01) +#define MT9M114_REG_SYSMGR_CMD_STATUS (0xDC02) + +#define MT9M114_REG_PATCHLDR_LOADER_ADDRESS (0xE000) +#define MT9M114_REG_PATCHLDR_PATCH_ID (0xE002) +#define MT9M114_REG_PATCHLDR_FIRMWARE_ID_HI (0xE004) +#define MT9M114_REG_PATCHLDR_FIRMWARE_ID_LO (0xE006) +#define MT9M114_REG_PATCHLDR_APPLY_STATUS (0xE008) + +#define MT9M114_REG_AUTO_BINNING_MODE (0xE801) + +#define MT9M114_REG_CMD_HANDLE_WAIT_EVENT_ID (0xFC00) +#define MT9M114_REG_CMD_HANDLE_NUM_EVENTS (0xFC02) + +#endif //__REG_REGS_H__ diff --git a/components/3rd_party/omv/omv/sensors/mt9v0xx.c b/components/3rd_party/omv/omv/sensors/mt9v0xx.c new file mode 100644 index 00000000..fb47683b --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/mt9v0xx.c @@ -0,0 +1,553 @@ +/* + * 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. + * + * MT9V0XX driver. + */ +#include "omv_boardconfig.h" +#if (OMV_ENABLE_MT9V0XX == 1) + +#include +#include +#include + +#include "omv_i2c.h" +#include "sensor.h" +#include "mt9v0xx.h" +#include "mt9v0xx_regs.h" +#include "py/mphal.h" + +#define ACTIVE_SENSOR_WIDTH (752) +#define ACTIVE_SENSOR_HEIGHT (480) + +#define MONO_CFA_ID (0) +#define RCCC_CFA_ID (5) +#define BAYER_CFA_ID (6) + +static int16_t readout_x = 0; +static int16_t readout_y = 0; + +static enum { + MONO_CFA, RCCC_CFA, BAYER_CFA +} +cfa_type = MONO_CFA; + +static bool is_mt9v0x2(sensor_t *sensor) { + return (sensor->chip_id_w == MT9V0X2_ID) || (sensor->chip_id_w == MT9V0X2_C_ID); +} + +static bool is_mt9v0x4(sensor_t *sensor) { + return (sensor->chip_id_w == MT9V0X4_ID) || (sensor->chip_id_w == MT9V0X4_C_ID); +} + +static int reset(sensor_t *sensor) { + int ret = 0; + readout_x = 0; + readout_y = 0; + + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_RESET, MT9V0XX_RESET_SOFT_RESET); + + if (is_mt9v0x4(sensor)) { + uint16_t chip_control; + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_CHIP_CONTROL, &chip_control); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_CHIP_CONTROL, + (chip_control & (~MT9V0X4_CHIP_CONTROL_RESERVED))); + } + + uint16_t read_mode; + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_READ_MODE, &read_mode); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_READ_MODE, + read_mode | MT9V0XX_READ_MODE_ROW_FLIP | MT9V0XX_READ_MODE_COL_FLIP); + + if (is_mt9v0x4(sensor)) { + // We have to copy the differences from context A into context B registers so that we can + // ping-pong between them seamlessly... + + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_READ_MODE_B, &read_mode); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_READ_MODE_B, + read_mode | MT9V0XX_READ_MODE_ROW_FLIP | MT9V0XX_READ_MODE_COL_FLIP); + + uint16_t shutter_width1; + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_SHUTTER_WIDTH1, &shutter_width1); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_SHUTTER_WIDTH1_B, shutter_width1); + + uint16_t shutter_width2; + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_SHUTTER_WIDTH2, &shutter_width2); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_SHUTTER_WIDTH2_B, shutter_width2); + + uint16_t shutter_control; + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_SHUTTER_WIDTH_CONTROL, &shutter_control); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_SHUTTER_WIDTH_CONTROL_B, shutter_control); + + uint16_t voltage_level_1; + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_V1_CONTROL, &voltage_level_1); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_V1_CONTROL_B, voltage_level_1); + + uint16_t voltage_level_2; + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_V2_CONTROL, &voltage_level_2); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_V2_CONTROL_B, voltage_level_2); + + uint16_t voltage_level_3; + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_V3_CONTROL, &voltage_level_3); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_V3_CONTROL_B, voltage_level_3); + + uint16_t voltage_level_4; + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_V4_CONTROL, &voltage_level_4); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_V4_CONTROL_B, voltage_level_4); + + uint16_t analog_gain; + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_ANALOG_GAIN, &analog_gain); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_ANALOG_GAIN_B, analog_gain); + + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_PIXEL_OPERATION_MODE, + 0); + + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_ADC_COMPANDING_MODE, + MT9V0XX_ADC_COMPANDING_MODE_LINEAR | MT9V0X4_ADC_COMPANDING_MODE_LINEAR_B); + + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_ROW_NOISE_CORR_CONTROL, + MT9V0X4_ROW_NOISE_CORR_ENABLE | MT9V0X4_ROW_NOISE_CORR_ENABLE_B); + + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_AEC_AGC_ENABLE, + MT9V0XX_AEC_ENABLE | MT9V0X4_AEC_ENABLE_B | MT9V0XX_AGC_ENABLE | MT9V0X4_AGC_ENABLE_B); + } + + if (is_mt9v0x2(sensor)) { + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0X2_PIXEL_CLOCK, MT9V0XX_PIXEL_CLOCK_INV_PXL_CLK); + } + + return ret; +} + +static int read_reg(sensor_t *sensor, uint16_t reg_addr) { + uint16_t reg_data; + if (omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, reg_addr, ®_data) != 0) { + return -1; + } + return reg_data; +} + +static int write_reg(sensor_t *sensor, uint16_t reg_addr, uint16_t reg_data) { + return omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, reg_addr, reg_data); +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) { + switch (cfa_type) { + case BAYER_CFA: { + if (pixformat != PIXFORMAT_BAYER) { + return -1; + } + return 0; + } + default: { + if (pixformat != PIXFORMAT_GRAYSCALE) { + return -1; + } + return 0; + } + } +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) { + uint16_t chip_control, read_mode; + int ret = 0; + uint16_t w = resolution[framesize][0]; + uint16_t h = resolution[framesize][1]; + + if ((w > ACTIVE_SENSOR_WIDTH) || (h > ACTIVE_SENSOR_HEIGHT)) { + return -1; + } + + if ((cfa_type == BAYER_CFA) && (w % 16)) { + // Must be a multiple of 16 in bayer mode. + return -1; + } + + if (omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_CHIP_CONTROL, &chip_control) != 0) { + return -1; + } + + // EDIT: WORKS BETTER TO STAY IN CONTEXT A + // + // Determine which context to switch to... + int context = 1; // chip_control & MT9V0X4_CHIP_CONTROL_CONTEXT; + int read_mode_addr = context ? MT9V0XX_READ_MODE : MT9V0X4_READ_MODE_B; + int col_start_addr = context ? MT9V0XX_COL_START : MT9V0X4_COL_START_B; + int row_start_addr = context ? MT9V0XX_ROW_START : MT9V0X4_ROW_START_B; + int window_height_addr = context ? MT9V0XX_WINDOW_HEIGHT : MT9V0X4_WINDOW_HEIGHT_B; + int window_width_addr = context ? MT9V0XX_WINDOW_WIDTH : MT9V0X4_WINDOW_WIDTH_B; + int horizontal_blanking_addr = context ? MT9V0XX_HORIZONTAL_BLANKING : MT9V0X4_HORIZONTAL_BLANKING_B; + + if (omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, read_mode_addr, &read_mode) != 0) { + return -1; + } + + int read_mode_mul = 1; + read_mode &= 0xFFF0; + + if (cfa_type != BAYER_CFA) { + if ((w <= (ACTIVE_SENSOR_WIDTH / 4)) && (h <= (ACTIVE_SENSOR_HEIGHT / 4))) { + read_mode_mul = 4; + read_mode |= MT9V0XX_READ_MODE_COL_BIN_4 | MT9V0XX_READ_MODE_ROW_BIN_4; + } else if ((w <= (ACTIVE_SENSOR_WIDTH / 2)) && (h <= (ACTIVE_SENSOR_HEIGHT / 2))) { + read_mode_mul = 2; + read_mode |= MT9V0XX_READ_MODE_COL_BIN_2 | MT9V0XX_READ_MODE_ROW_BIN_2; + } + } + + int readout_x_max = (ACTIVE_SENSOR_WIDTH - (w * read_mode_mul)) / 2; + int readout_y_max = (ACTIVE_SENSOR_HEIGHT - (h * read_mode_mul)) / 2; + readout_x = IM_MAX(IM_MIN(readout_x, readout_x_max), -readout_x_max); + readout_y = IM_MAX(IM_MIN(readout_y, readout_y_max), -readout_y_max); + + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, col_start_addr, + readout_x_max - readout_x + MT9V0XX_COL_START_MIN); // sensor is mirrored by default + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, row_start_addr, + readout_y_max - readout_y + MT9V0XX_ROW_START_MIN); // sensor is mirrored by default + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, window_width_addr, w * read_mode_mul); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, window_height_addr, h * read_mode_mul); + + // Notes: 1. The MT9V0XX uses column parallel analog-digital converters, thus short row timing is not possible. + // The minimum total row time is 690 columns (horizontal width + horizontal blanking). The minimum + // horizontal blanking is 61. When the window width is set below 627, horizontal blanking + // must be increased. + // + // The STM32H7 needs more than 94+(752-640) clocks between rows otherwise it can't keep up with the pixel rate. + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, horizontal_blanking_addr, + MT9V0XX_HORIZONTAL_BLANKING_DEF + (ACTIVE_SENSOR_WIDTH - IM_MIN(w * read_mode_mul, 640))); + + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, read_mode_addr, read_mode); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_PIXEL_COUNT, (w * h) / 8); + + if (is_mt9v0x4(sensor)) { + // We need more setup time for the pixel_clk at the full data rate... + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_PIXEL_CLOCK, + (read_mode_mul == 1) ? MT9V0XX_PIXEL_CLOCK_INV_PXL_CLK : 0); + } + + // EDIT: WORKS BETTER TO STAY IN CONTEXT A + // + // Flip the context. + // ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_CHIP_CONTROL, + // chip_control ^ MT9V0X4_CHIP_CONTROL_CONTEXT); + + return ret; +} + +static int set_colorbar(sensor_t *sensor, int enable) { + int mask = (is_mt9v0x4(sensor)) + ? (MT9V0X4_ROW_NOISE_CORR_ENABLE | MT9V0X4_ROW_NOISE_CORR_ENABLE_B) + : MT9V0X2_ROW_NOISE_CORR_ENABLE; + uint16_t reg; + int ret = omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_TEST_PATTERN, ®); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_TEST_PATTERN, + (reg & (~(MT9V0XX_TEST_PATTERN_ENABLE | MT9V0XX_TEST_PATTERN_GRAY_MASK))) + | ((enable != 0) ? (MT9V0XX_TEST_PATTERN_ENABLE | MT9V0XX_TEST_PATTERN_GRAY_VERTICAL) : 0)); + ret = omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_ROW_NOISE_CORR_CONTROL, ®); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_ROW_NOISE_CORR_CONTROL, + (reg & (~mask)) | ((enable == 0) ? mask : 0)); + if (!sensor->disable_delays) { + ret |= sensor->snapshot(sensor, NULL, 0); // Force shadow mode register to update... + } + return ret; +} + +static int set_auto_gain(sensor_t *sensor, int enable, float gain_db, float gain_db_ceiling) { + int agc_mask = (is_mt9v0x4(sensor)) + ? (MT9V0XX_AGC_ENABLE | MT9V0X4_AGC_ENABLE_B) + : MT9V0XX_AGC_ENABLE; + uint16_t reg; + int ret = omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_AEC_AGC_ENABLE, ®); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_AEC_AGC_ENABLE, + (reg & (~agc_mask)) | ((enable != 0) ? agc_mask : 0)); + if (!sensor->disable_delays) { + ret |= sensor->snapshot(sensor, NULL, 0); // Force shadow mode register to update... + } + + if ((enable == 0) && (!isnanf(gain_db)) && (!isinff(gain_db))) { + int gain = IM_MAX(IM_MIN(fast_roundf(expf((gain_db / 20.0f) * M_LN10) * 16.0f), 64), 16); + + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_ANALOG_GAIN, ®); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_ANALOG_GAIN, (reg & 0xFF80) | gain); + + if (is_mt9v0x4(sensor)) { + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_ANALOG_GAIN_B, ®); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_ANALOG_GAIN_B, (reg & 0xFF80) | gain); + } + } else if ((enable != 0) && (!isnanf(gain_db_ceiling)) && (!isinff(gain_db_ceiling))) { + int gain_ceiling = IM_MAX(IM_MIN(fast_roundf(expf((gain_db_ceiling / 20.0f) * M_LN10) * 16.0f), 64), 16); + int max_gain = (is_mt9v0x4(sensor)) ? MT9V0X4_MAX_GAIN : MT9V0X2_MAX_GAIN; + + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, max_gain, ®); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, max_gain, (reg & 0xFF80) | gain_ceiling); + } + + return ret; +} + +static int get_gain_db(sensor_t *sensor, float *gain_db) { + uint16_t chip_control, reg, gain; + int ret = omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_CHIP_CONTROL, &chip_control); + int context = chip_control & MT9V0X4_CHIP_CONTROL_CONTEXT; + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_AEC_AGC_ENABLE, ®); + + if (reg & (context ? MT9V0X4_AGC_ENABLE_B : MT9V0XX_AGC_ENABLE)) { + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_AGC_GAIN_OUTPUT, &gain); + } else { + int analog_gain = context ? MT9V0X4_ANALOG_GAIN_B : MT9V0XX_ANALOG_GAIN; + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, analog_gain, &gain); + } + + *gain_db = 20.0f * log10f((gain & 0x7F) / 16.0f); + return ret; +} + +static int set_auto_exposure(sensor_t *sensor, int enable, int exposure_us) { + int aec_mask = (is_mt9v0x4(sensor)) + ? (MT9V0XX_AEC_ENABLE | MT9V0X4_AEC_ENABLE_B) + : MT9V0XX_AEC_ENABLE; + uint16_t chip_control, reg, read_mode_reg, row_time_0, row_time_1; + int ret = omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_CHIP_CONTROL, &chip_control); + int context = chip_control & MT9V0X4_CHIP_CONTROL_CONTEXT; + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_AEC_AGC_ENABLE, ®); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_AEC_AGC_ENABLE, + (reg & (~aec_mask)) | ((enable != 0) ? aec_mask : 0)); + if (!sensor->disable_delays) { + ret |= sensor->snapshot(sensor, NULL, 0); // Force shadow mode register to update... + } + int read_mode = context ? MT9V0X4_READ_MODE_B : MT9V0XX_READ_MODE; + int window_width = context ? MT9V0X4_WINDOW_WIDTH_B : MT9V0XX_WINDOW_WIDTH; + int horizontal_blanking = context ? MT9V0X4_HORIZONTAL_BLANKING_B : MT9V0XX_HORIZONTAL_BLANKING; + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, read_mode, &read_mode_reg); + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, window_width, &row_time_0); + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, horizontal_blanking, &row_time_1); + + int clock = sensor_get_xclk_frequency(); + + int exposure = IM_MIN(exposure_us, MICROSECOND_CLKS / 2) * (clock / MICROSECOND_CLKS); + int row_time = row_time_0 + row_time_1; + int coarse_time = exposure / row_time; + int fine_time = exposure % row_time; + + // Fine shutter time is global. + if ((enable == 0) && (exposure_us >= 0)) { + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_TOTAL_SHUTTER_WIDTH, coarse_time); + + if (is_mt9v0x4(sensor)) { + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_FINE_SHUTTER_WIDTH_TOTAL, fine_time); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_TOTAL_SHUTTER_WIDTH_B, coarse_time); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_FINE_SHUTTER_WIDTH_TOTAL_B, fine_time); + } + } else if ((enable != 0) && (exposure_us >= 0)) { + int max_expose = (is_mt9v0x4(sensor)) ? MT9V0X4_MAX_EXPOSE : MT9V0X2_MAX_EXPOSE; + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, max_expose, coarse_time); + + if (is_mt9v0x4(sensor)) { + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_FINE_SHUTTER_WIDTH_TOTAL, fine_time); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_FINE_SHUTTER_WIDTH_TOTAL_B, fine_time); + } + } + + return ret; +} + +static int get_exposure_us(sensor_t *sensor, int *exposure_us) { + uint16_t chip_control, reg, read_mode_reg, row_time_0, row_time_1, int_pixels = 0, int_rows = 0; + int ret = omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_CHIP_CONTROL, &chip_control); + int context = chip_control & MT9V0X4_CHIP_CONTROL_CONTEXT; + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_AEC_AGC_ENABLE, ®); + + int read_mode = context ? MT9V0X4_READ_MODE_B : MT9V0XX_READ_MODE; + int window_width = context ? MT9V0X4_WINDOW_WIDTH_B : MT9V0XX_WINDOW_WIDTH; + int horizontal_blanking = context ? MT9V0X4_HORIZONTAL_BLANKING_B : MT9V0XX_HORIZONTAL_BLANKING; + int fine_shutter_width_total = context ? MT9V0X4_FINE_SHUTTER_WIDTH_TOTAL_B : MT9V0X4_FINE_SHUTTER_WIDTH_TOTAL; + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, read_mode, &read_mode_reg); + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, window_width, &row_time_0); + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, horizontal_blanking, &row_time_1); + if (is_mt9v0x4(sensor)) { + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, fine_shutter_width_total, &int_pixels); + } + + int clock = sensor_get_xclk_frequency(); + + if (reg & (context ? MT9V0X4_AEC_ENABLE_B : MT9V0XX_AEC_ENABLE)) { + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_AEC_EXPOSURE_OUTPUT, &int_rows); + } else { + int total_shutter_width = context ? MT9V0X4_TOTAL_SHUTTER_WIDTH_B : MT9V0XX_TOTAL_SHUTTER_WIDTH; + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, total_shutter_width, &int_rows); + } + + *exposure_us = ((int_rows * (row_time_0 + row_time_1)) + int_pixels) / (clock / MICROSECOND_CLKS); + return ret; +} + +static int set_hmirror(sensor_t *sensor, int enable) { + uint16_t read_mode; + int ret = omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_READ_MODE, &read_mode); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_READ_MODE, // inverted behavior + (read_mode & (~MT9V0XX_READ_MODE_COL_FLIP)) | ((enable == 0) ? MT9V0XX_READ_MODE_COL_FLIP : 0)); + + if (is_mt9v0x4(sensor)) { + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_READ_MODE_B, &read_mode); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_READ_MODE_B, // inverted behavior + (read_mode & (~MT9V0XX_READ_MODE_COL_FLIP)) | ((enable == 0) ? MT9V0XX_READ_MODE_COL_FLIP : 0)); + } + + if (!sensor->disable_delays) { + ret |= sensor->snapshot(sensor, NULL, 0); // Force shadow mode register to update... + } + return ret; +} + +static int set_vflip(sensor_t *sensor, int enable) { + uint16_t read_mode; + int ret = omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_READ_MODE, &read_mode); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_READ_MODE, // inverted behavior + (read_mode & (~MT9V0XX_READ_MODE_ROW_FLIP)) | ((enable == 0) ? MT9V0XX_READ_MODE_ROW_FLIP : 0)); + + if (is_mt9v0x4(sensor)) { + ret |= omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_READ_MODE_B, &read_mode); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0X4_READ_MODE_B, // inverted behavior + (read_mode & (~MT9V0XX_READ_MODE_ROW_FLIP)) | ((enable == 0) ? MT9V0XX_READ_MODE_ROW_FLIP : 0)); + } + + if (!sensor->disable_delays) { + ret |= sensor->snapshot(sensor, NULL, 0); // Force shadow mode register to update... + } + return ret; +} + +static int ioctl(sensor_t *sensor, int request, va_list ap) { + int ret = 0; + uint16_t chip_control; + + // The MT9V0XX does not have a hardware scaler so the readout w/h must be equal to the + // framesize w/h. + int tmp_readout_w = resolution[sensor->framesize][0]; + int tmp_readout_h = resolution[sensor->framesize][1]; + if (sensor->framesize == FRAMESIZE_INVALID) { + tmp_readout_w = ACTIVE_SENSOR_WIDTH; + tmp_readout_h = ACTIVE_SENSOR_HEIGHT; + } + + if (cfa_type != BAYER_CFA) { + if ((tmp_readout_w <= (ACTIVE_SENSOR_WIDTH / 4)) && (tmp_readout_h <= (ACTIVE_SENSOR_HEIGHT / 4))) { + tmp_readout_w *= 4; + tmp_readout_h *= 4; + } else if ((tmp_readout_w <= (ACTIVE_SENSOR_WIDTH / 2)) && (tmp_readout_h <= (ACTIVE_SENSOR_HEIGHT / 2))) { + tmp_readout_w *= 2; + tmp_readout_h *= 2; + } + } + + switch (request) { + case IOCTL_SET_READOUT_WINDOW: { + int tmp_readout_x = va_arg(ap, int); + int tmp_readout_y = va_arg(ap, int); + int readout_x_max = (ACTIVE_SENSOR_WIDTH - tmp_readout_w) / 2; + int readout_y_max = (ACTIVE_SENSOR_HEIGHT - tmp_readout_h) / 2; + tmp_readout_x = IM_MAX(IM_MIN(tmp_readout_x, readout_x_max), -readout_x_max); + tmp_readout_y = IM_MAX(IM_MIN(tmp_readout_y, readout_y_max), -readout_y_max); + bool changed = (tmp_readout_x != readout_x) || + (tmp_readout_y != readout_y); + readout_x = tmp_readout_x; + readout_y = tmp_readout_y; + if (changed && (sensor->framesize != FRAMESIZE_INVALID)) { + ret |= set_framesize(sensor, sensor->framesize); + } + break; + } + case IOCTL_GET_READOUT_WINDOW: { + *va_arg(ap, int *) = readout_x; + *va_arg(ap, int *) = readout_y; + *va_arg(ap, int *) = tmp_readout_w; + *va_arg(ap, int *) = tmp_readout_h; + break; + } + case IOCTL_SET_TRIGGERED_MODE: { + int enable = va_arg(ap, int); + ret = omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_CHIP_CONTROL, &chip_control); + ret |= omv_i2c_writew(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_CHIP_CONTROL, + (chip_control & (~MT9V0XX_CHIP_CONTROL_MODE_MASK)) + | ((enable != 0) ? MT9V0XX_CHIP_CONTROL_SNAP_MODE : MT9V0XX_CHIP_CONTROL_MASTER_MODE)); + if (!sensor->disable_delays) { + ret |= sensor->snapshot(sensor, NULL, 0); // Force shadow mode register to update... + } + break; + } + case IOCTL_GET_TRIGGERED_MODE: { + int *enable = va_arg(ap, int *); + ret = omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_CHIP_CONTROL, &chip_control); + if (ret >= 0) { + *enable = ((chip_control & MT9V0XX_CHIP_CONTROL_MODE_MASK) == MT9V0XX_CHIP_CONTROL_SNAP_MODE); + } + break; + } + default: { + ret = -1; + break; + } + } + + return ret; +} + +int mt9v0xx_init(sensor_t *sensor) { + // Initialize sensor structure. + sensor->reset = reset; + sensor->read_reg = read_reg; + sensor->write_reg = write_reg; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_colorbar = set_colorbar; + sensor->set_auto_gain = set_auto_gain; + sensor->get_gain_db = get_gain_db; + sensor->set_auto_exposure = set_auto_exposure; + sensor->get_exposure_us = get_exposure_us; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + sensor->ioctl = ioctl; + + // Set sensor flags + sensor->hw_flags.vsync = 0; + sensor->hw_flags.hsync = 0; + sensor->hw_flags.pixck = 0; + sensor->hw_flags.fsync = 1; + sensor->hw_flags.jpege = 0; + sensor->hw_flags.gs_bpp = 1; + sensor->hw_flags.bayer = SENSOR_HW_FLAGS_BAYER_BGGR; + + uint16_t cfa_type_reg; + int ret = omv_i2c_readw(&sensor->i2c_bus, sensor->slv_addr, MT9V0XX_CFA_ID_REG, &cfa_type_reg); + switch ((cfa_type_reg >> 9) & 0x7) { + case BAYER_CFA_ID: { + cfa_type = BAYER_CFA; + switch (sensor->chip_id_w) { + case MT9V0X2_ID: { + sensor->chip_id_w = MT9V0X2_C_ID; + break; + } + case MT9V0X4_ID: { + sensor->chip_id_w = MT9V0X4_C_ID; + break; + } + default: { + break; + } + } + break; + } + default: { + cfa_type = MONO_CFA; + break; + } + } + + return ret; +} + +#endif // (OMV_ENABLE_MT9V0XX == 1) diff --git a/components/3rd_party/omv/omv/sensors/mt9v0xx.h b/components/3rd_party/omv/omv/sensors/mt9v0xx.h new file mode 100644 index 00000000..a6338f17 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/mt9v0xx.h @@ -0,0 +1,17 @@ +/* + * 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. + * + * MT9V0XX driver. + */ +#ifndef __MT9V0XX_H__ +#define __MT9V0XX_H__ +#ifndef MT9V0XX_XCLK_FREQ +#define MT9V0XX_XCLK_FREQ 26666666 +#endif +int mt9v0xx_init(sensor_t *sensor); +#endif // __MT9V0XX_H__ diff --git a/components/3rd_party/omv/omv/sensors/mt9v0xx_regs.h b/components/3rd_party/omv/omv/sensors/mt9v0xx_regs.h new file mode 100644 index 00000000..2de3f1b9 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/mt9v0xx_regs.h @@ -0,0 +1,171 @@ +/* + * 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. + * + * MT9V0XX driver. + */ + +#define MT9V0XX_CHIP_VERSION (0x00) + +#define MT9V0XX_COL_START (0x01) +#define MT9V0X4_COL_START_B (0xC9) +#define MT9V0XX_COL_START_MIN (1) +#define MT9V0XX_COL_START_MAX (752) + +#define MT9V0XX_ROW_START (0x02) +#define MT9V0X4_ROW_START_B (0xCA) +#define MT9V0XX_ROW_START_MIN (4) +#define MT9V0XX_ROW_START_MAX (482) + +#define MT9V0XX_WINDOW_HEIGHT (0x03) +#define MT9V0X4_WINDOW_HEIGHT_B (0xCB) +#define MT9V0XX_WINDOW_HEIGHT_MIN (1) +#define MT9V0XX_WINDOW_HEIGHT_MAX (480) + +#define MT9V0XX_WINDOW_WIDTH (0x04) +#define MT9V0X4_WINDOW_WIDTH_B (0xCC) +#define MT9V0XX_WINDOW_WIDTH_MIN (1) +#define MT9V0XX_WINDOW_WIDTH_MAX (752) + +#define MT9V0XX_HORIZONTAL_BLANKING (0x05) +#define MT9V0X4_HORIZONTAL_BLANKING_B (0xCD) +#define MT9V0X2_HORIZONTAL_BLANKING_MIN (43) +#define MT9V0XX_HORIZONTAL_BLANKING_MIN_1 (61) +#define MT9V0XX_HORIZONTAL_BLANKING_MIN_2 (71) +#define MT9V0XX_HORIZONTAL_BLANKING_MIN_4 (91) +#define MT9V0XX_HORIZONTAL_BLANKING_MAX (1023) +#define MT9V0XX_HORIZONTAL_BLANKING_DEF (94) + +#define MT9V0XX_VERTICAL_BLANKING (0x06) +#define MT9V0X4_VERTICAL_BLANKING_B (0xCE) +#define MT9V0XX_VERTICAL_BLANKING_MIN (4) +#define MT9V0XX_VERTICAL_BLANKING_MAX (3000) +#define MT9V0XX_VERTICAL_BLANKING_DEF (45) + +#define MT9V0XX_CHIP_CONTROL (0x07) +#define MT9V0XX_CHIP_CONTROL_MASTER_MODE (1 << 3) +#define MT9V0XX_CHIP_CONTROL_SNAP_MODE (3 << 3) +#define MT9V0XX_CHIP_CONTROL_MODE_MASK (3 << 3) +#define MT9V0XX_CHIP_CONTROL_DOUT_ENABLE (1 << 7) +#define MT9V0XX_CHIP_CONTROL_SEQUENTIAL (1 << 8) +#define MT9V0X4_CHIP_CONTROL_RESERVED (1 << 9) +#define MT9V0X4_CHIP_CONTROL_CONTEXT (1 << 15) + +#define MT9V0XX_SHUTTER_WIDTH1 (0x08) +#define MT9V0X4_SHUTTER_WIDTH1_B (0xCF) + +#define MT9V0XX_SHUTTER_WIDTH2 (0x09) +#define MT9V0X4_SHUTTER_WIDTH2_B (0xD0) + +#define MT9V0XX_SHUTTER_WIDTH_CONTROL (0x0A) +#define MT9V0X4_SHUTTER_WIDTH_CONTROL_B (0xD1) + +#define MT9V0XX_TOTAL_SHUTTER_WIDTH (0x0B) +#define MT9V0X4_TOTAL_SHUTTER_WIDTH_B (0xD2) +#define MT9V0XX_TOTAL_SHUTTER_WIDTH_MIN (1) +#define MT9V0XX_TOTAL_SHUTTER_WIDTH_MAX (32767) + +#define MT9V0XX_RESET (0x0C) +#define MT9V0XX_RESET_SOFT_RESET (1 << 0) + +#define MT9V0XX_READ_MODE (0x0D) +#define MT9V0X4_READ_MODE_B (0x0E) +#define MT9V0XX_READ_MODE_ROW_BIN_2 (1 << 0) +#define MT9V0XX_READ_MODE_ROW_BIN_4 (1 << 1) +#define MT9V0XX_READ_MODE_COL_BIN_2 (1 << 2) +#define MT9V0XX_READ_MODE_COL_BIN_4 (1 << 3) +#define MT9V0XX_READ_MODE_ROW_FLIP (1 << 4) +#define MT9V0XX_READ_MODE_COL_FLIP (1 << 5) +#define MT9V0XX_READ_MODE_DARK_COLS (1 << 6) +#define MT9V0XX_READ_MODE_DARK_ROWS (1 << 7) + +#define MT9V0XX_PIXEL_OPERATION_MODE (0x0F) +#define MT9V0X4_PIXEL_OPERATION_MODE_HDR (1 << 0) +#define MT9V0X4_PIXEL_OPERATION_MODE_COLOR (1 << 1) +#define MT9V0X4_PIXEL_OPERATION_MODE_HDR_B (1 << 8) + +#define MT9V0XX_ADC_COMPANDING_MODE (0x1C) +#define MT9V0XX_ADC_COMPANDING_MODE_LINEAR (2 << 0) +#define MT9V0X4_ADC_COMPANDING_MODE_LINEAR_B (2 << 8) + +#define MT9V0XX_ANALOG_GAIN (0x35) +#define MT9V0X4_ANALOG_GAIN_B (0x36) +#define MT9V0XX_ANALOG_GAIN_MIN (16) +#define MT9V0XX_ANALOG_GAIN_MAX (64) + +#define MT9V0XX_V1_CONTROL (0x31) +#define MT9V0X4_V1_CONTROL_B (0x39) + +#define MT9V0XX_V2_CONTROL (0x32) +#define MT9V0X4_V2_CONTROL_B (0x3A) + +#define MT9V0XX_V3_CONTROL (0x33) +#define MT9V0X4_V3_CONTROL_B (0x3B) + +#define MT9V0XX_V4_CONTROL (0x34) +#define MT9V0X4_V4_CONTROL_B (0x3C) + +#define MT9V0XX_FRAME_DARK_AVERAGE (0x42) + +#define MT9V0XX_DARK_AVG_THRESH (0x46) +#define MT9V0XX_DARK_AVG_LOW_THRESH_MASK (255 << 0) +#define MT9V0XX_DARK_AVG_LOW_THRESH_SHIFT (0) +#define MT9V0XX_DARK_AVG_HIGH_THRESH_MASK (255 << 8) +#define MT9V0XX_DARK_AVG_HIGH_THRESH_SHIFT (8) + +#define MT9V0XX_ROW_NOISE_CORR_CONTROL (0x70) +#define MT9V0X2_ROW_NOISE_CORR_ENABLE (1 << 5) +#define MT9V0X4_ROW_NOISE_CORR_ENABLE (1 << 0) +#define MT9V0X4_ROW_NOISE_CORR_ENABLE_B (1 << 8) +#define MT9V0X2_ROW_NOISE_CORR_USE_BLK_AVG (1 << 11) +#define MT9V0X4_ROW_NOISE_CORR_USE_BLK_AVG (1 << 1) +#define MT9V0X4_ROW_NOISE_CORR_USE_BLK_AVG_B (1 << 9) + +#define MT9V0X2_PIXEL_CLOCK (0x74) +#define MT9V0X4_PIXEL_CLOCK (0x72) +#define MT9V0XX_PIXEL_CLOCK_INV_LINE (1 << 0) +#define MT9V0XX_PIXEL_CLOCK_INV_FRAME (1 << 1) +#define MT9V0XX_PIXEL_CLOCK_XOR_LINE (1 << 2) +#define MT9V0XX_PIXEL_CLOCK_CONT_LINE (1 << 3) +#define MT9V0XX_PIXEL_CLOCK_INV_PXL_CLK (1 << 4) + +#define MT9V0XX_TEST_PATTERN (0x7F) +#define MT9V0XX_TEST_PATTERN_DATA_MASK (1023 << 0) +#define MT9V0XX_TEST_PATTERN_DATA_SHIFT (0) +#define MT9V0XX_TEST_PATTERN_USE_DATA (1 << 10) +#define MT9V0XX_TEST_PATTERN_GRAY_MASK (3 << 11) +#define MT9V0XX_TEST_PATTERN_GRAY_NONE (0 << 11) +#define MT9V0XX_TEST_PATTERN_GRAY_VERTICAL (1 << 11) +#define MT9V0XX_TEST_PATTERN_GRAY_HORIZONTAL (2 << 11) +#define MT9V0XX_TEST_PATTERN_GRAY_DIAGONAL (3 << 11) +#define MT9V0XX_TEST_PATTERN_ENABLE (1 << 13) +#define MT9V0XX_TEST_PATTERN_FLIP (1 << 14) + +#define MT9V0XX_AEC_AGC_ENABLE (0xAF) +#define MT9V0XX_AEC_ENABLE (1 << 0) +#define MT9V0X4_AEC_ENABLE_B (1 << 8) +#define MT9V0XX_AGC_ENABLE (1 << 1) +#define MT9V0X4_AGC_ENABLE_B (1 << 9) + +#define MT9V0XX_THERMAL_INFO (0xC1) + +#define MT9V0XX_CFA_ID_REG (0x6B) + +#define MT9V0X2_MAX_GAIN (0x36) +#define MT9V0X4_MAX_GAIN (0xAB) + +#define MT9V0X2_MAX_EXPOSE (0xBD) +#define MT9V0X4_MAX_EXPOSE (0xAD) + +#define MT9V0XX_PIXEL_COUNT (0xB0) +#define MT9V0XX_AGC_GAIN_OUTPUT (0xBA) +#define MT9V0XX_AEC_EXPOSURE_OUTPUT (0xBB) + +#define MT9V0X4_FINE_SHUTTER_WIDTH_TOTAL (0xD5) +#define MT9V0X4_FINE_SHUTTER_WIDTH_TOTAL_B (0xD8) + +#define MICROSECOND_CLKS (1000000) diff --git a/components/3rd_party/omv/omv/sensors/ov2640.c b/components/3rd_party/omv/omv/sensors/ov2640.c new file mode 100644 index 00000000..048002a9 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/ov2640.c @@ -0,0 +1,880 @@ +/* + * 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. + * + * OV2640 driver. + */ +#include "omv_boardconfig.h" +#if (OMV_ENABLE_OV2640 == 1) + +#include +#include +#include + +#include "omv_i2c.h" +#include "sensor.h" +#include "ov2640.h" +#include "ov2640_regs.h" +#include "py/mphal.h" + +#define CIF_WIDTH (400) +#define CIF_HEIGHT (296) + +#define SVGA_WIDTH (800) +#define SVGA_HEIGHT (600) + +#define UXGA_WIDTH (1600) +#define UXGA_HEIGHT (1200) + +static const uint8_t default_regs[][2] = { + +// From Linux Driver. + + {BANK_SEL, BANK_SEL_DSP}, + {0x2c, 0xff}, + {0x2e, 0xdf}, + {BANK_SEL, BANK_SEL_SENSOR}, + {0x3c, 0x32}, + {CLKRC, CLKRC_DOUBLE}, + {COM2, COM2_OUT_DRIVE_3x}, + {REG04, REG04_SET(REG04_HFLIP_IMG | REG04_VFLIP_IMG | REG04_VREF_EN | REG04_HREF_EN)}, + {COM8, COM8_SET(COM8_BNDF_EN | COM8_AGC_EN | COM8_AEC_EN)}, + {COM9, COM9_AGC_SET(COM9_AGC_GAIN_8x)}, + {0x2c, 0x0c}, + {0x33, 0x78}, + {0x3a, 0x33}, + {0x3b, 0xfb}, + {0x3e, 0x00}, + {0x43, 0x11}, + {0x16, 0x10}, + {0x39, 0x02}, + {0x35, 0x88}, + {0x22, 0x0a}, + {0x37, 0x40}, + {0x23, 0x00}, + {ARCOM2, 0xa0}, + {0x06, 0x02}, + {0x06, 0x88}, + {0x07, 0xc0}, + {0x0d, 0xb7}, + {0x0e, 0x01}, + {0x4c, 0x00}, + {0x4a, 0x81}, + {0x21, 0x99}, + {AEW, 0x40}, + {AEB, 0x38}, + {VV, VV_AGC_TH_SET(0x08, 0x02)}, + {0x5c, 0x00}, + {0x63, 0x00}, + {FLL, 0x22}, + {COM3, COM3_BAND_SET(COM3_BAND_AUTO)}, + {REG5D, 0x55}, + {REG5E, 0x7d}, + {REG5F, 0x7d}, + {REG60, 0x55}, + {HISTO_LOW, 0x70}, + {HISTO_HIGH, 0x80}, + {0x7c, 0x05}, + {0x20, 0x80}, + {0x28, 0x30}, + {0x6c, 0x00}, + {0x6d, 0x80}, + {0x6e, 0x00}, + {0x70, 0x02}, + {0x71, 0x94}, + {0x73, 0xc1}, + {0x3d, 0x34}, + {COM7, COM7_RES_UXGA | COM7_ZOOM_EN}, + {0x5a, 0x57}, + {COM25, 0x00}, + {BD50, 0xbb}, + {BD60, 0x9c}, + {BANK_SEL, BANK_SEL_DSP}, + {0xe5, 0x7f}, + {MC_BIST, MC_BIST_RESET | MC_BIST_BOOT_ROM_SEL}, + {0x41, 0x24}, + {RESET, RESET_JPEG | RESET_DVP}, + {0x76, 0xff}, + {0x33, 0xa0}, + {0x42, 0x20}, + {0x43, 0x18}, + {0x4c, 0x00}, + {CTRL3, CTRL3_BPC_EN | CTRL3_WPC_EN | 0x10}, + {0x88, 0x3f}, + {0xd7, 0x03}, + {0xd9, 0x10}, + {R_DVP_SP, R_DVP_SP_AUTO_MODE | 0x2}, + {0xc8, 0x08}, + {0xc9, 0x80}, + {BPADDR, 0x00}, + {BPDATA, 0x00}, + {BPADDR, 0x03}, + {BPDATA, 0x48}, + {BPDATA, 0x48}, + {BPADDR, 0x08}, + {BPDATA, 0x20}, + {BPDATA, 0x10}, + {BPDATA, 0x0e}, + {0x90, 0x00}, + {0x91, 0x0e}, + {0x91, 0x1a}, + {0x91, 0x31}, + {0x91, 0x5a}, + {0x91, 0x69}, + {0x91, 0x75}, + {0x91, 0x7e}, + {0x91, 0x88}, + {0x91, 0x8f}, + {0x91, 0x96}, + {0x91, 0xa3}, + {0x91, 0xaf}, + {0x91, 0xc4}, + {0x91, 0xd7}, + {0x91, 0xe8}, + {0x91, 0x20}, + {0x92, 0x00}, + {0x93, 0x06}, + {0x93, 0xe3}, + {0x93, 0x03}, + {0x93, 0x03}, + {0x93, 0x00}, + {0x93, 0x02}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x93, 0x00}, + {0x96, 0x00}, + {0x97, 0x08}, + {0x97, 0x19}, + {0x97, 0x02}, + {0x97, 0x0c}, + {0x97, 0x24}, + {0x97, 0x30}, + {0x97, 0x28}, + {0x97, 0x26}, + {0x97, 0x02}, + {0x97, 0x98}, + {0x97, 0x80}, + {0x97, 0x00}, + {0x97, 0x00}, + {0xa4, 0x00}, + {0xa8, 0x00}, + {0xc5, 0x11}, + {0xc6, 0x51}, + {0xbf, 0x80}, + {0xc7, 0x10}, /* simple AWB */ + {0xb6, 0x66}, + {0xb8, 0xA5}, + {0xb7, 0x64}, + {0xb9, 0x7C}, + {0xb3, 0xaf}, + {0xb4, 0x97}, + {0xb5, 0xFF}, + {0xb0, 0xC5}, + {0xb1, 0x94}, + {0xb2, 0x0f}, + {0xc4, 0x5c}, + {0xa6, 0x00}, + {0xa7, 0x20}, + {0xa7, 0xd8}, + {0xa7, 0x1b}, + {0xa7, 0x31}, + {0xa7, 0x00}, + {0xa7, 0x18}, + {0xa7, 0x20}, + {0xa7, 0xd8}, + {0xa7, 0x19}, + {0xa7, 0x31}, + {0xa7, 0x00}, + {0xa7, 0x18}, + {0xa7, 0x20}, + {0xa7, 0xd8}, + {0xa7, 0x19}, + {0xa7, 0x31}, + {0xa7, 0x00}, + {0xa7, 0x18}, + {0x7f, 0x00}, + {0xe5, 0x1f}, + {0xe1, 0x77}, + {0xdd, 0x7f}, + {CTRL0, CTRL0_YUV422 | CTRL0_YUV_EN | CTRL0_RGB_EN}, + +// OpenMV Custom. + + {BANK_SEL, BANK_SEL_SENSOR}, + {0x0f, 0x4b}, + {COM1, 0x8f}, + +// End. + + {0x00, 0x00}, +}; + +// Looks really bad. +//static const uint8_t cif_regs[][2] = { +// {BANK_SEL, BANK_SEL_SENSOR}, +// {COM7, COM7_RES_CIF}, +// {COM1, 0x06 | 0x80}, +// {HSTART, 0x11}, +// {HSTOP, 0x43}, +// {VSTART, 0x01}, // 0x01 fixes issue with garbage pixels in the image... +// {VSTOP, 0x97}, +// {REG32, 0x09}, +// {BANK_SEL, BANK_SEL_DSP}, +// {RESET, RESET_DVP}, +// {SIZEL, SIZEL_HSIZE8_11_SET(CIF_WIDTH) | SIZEL_HSIZE8_SET(CIF_WIDTH) | SIZEL_VSIZE8_SET(CIF_HEIGHT)}, +// {HSIZE8, HSIZE8_SET(CIF_WIDTH)}, +// {VSIZE8, VSIZE8_SET(CIF_HEIGHT)}, +// {CTRL2, CTRL2_DCW_EN | CTRL2_SDE_EN | CTRL2_UV_AVG_EN | CTRL2_CMX_EN | CTRL2_UV_ADJ_EN}, +// {0, 0}, +//}; + +static const uint8_t svga_regs[][2] = { + {BANK_SEL, BANK_SEL_SENSOR}, + {COM7, COM7_RES_SVGA}, + {COM1, 0x0A | 0x80}, + {HSTART, 0x11}, + {HSTOP, 0x43}, + {VSTART, 0x01}, // 0x01 fixes issue with garbage pixels in the image... + {VSTOP, 0x97}, + {REG32, 0x09}, + {BANK_SEL, BANK_SEL_DSP}, + {RESET, RESET_DVP}, + {SIZEL, SIZEL_HSIZE8_11_SET(SVGA_WIDTH) | SIZEL_HSIZE8_SET(SVGA_WIDTH) | SIZEL_VSIZE8_SET(SVGA_HEIGHT)}, + {HSIZE8, HSIZE8_SET(SVGA_WIDTH)}, + {VSIZE8, VSIZE8_SET(SVGA_HEIGHT)}, + {CTRL2, CTRL2_DCW_EN | CTRL2_SDE_EN | CTRL2_UV_AVG_EN | CTRL2_CMX_EN | CTRL2_UV_ADJ_EN}, + {0, 0}, +}; + +static const uint8_t uxga_regs[][2] = { + {BANK_SEL, BANK_SEL_SENSOR}, + {COM7, COM7_RES_UXGA}, + {COM1, 0x0F | 0x80}, + {HSTART, 0x11}, + {HSTOP, 0x75}, + {VSTART, 0x01}, + {VSTOP, 0x97}, + {REG32, 0x36}, + {BANK_SEL, BANK_SEL_DSP}, + {RESET, RESET_DVP}, + {SIZEL, SIZEL_HSIZE8_11_SET(UXGA_WIDTH) | SIZEL_HSIZE8_SET(UXGA_WIDTH) | SIZEL_VSIZE8_SET(UXGA_HEIGHT)}, + {HSIZE8, HSIZE8_SET(UXGA_WIDTH)}, + {VSIZE8, VSIZE8_SET(UXGA_HEIGHT)}, + {CTRL2, CTRL2_DCW_EN | CTRL2_SDE_EN | CTRL2_UV_AVG_EN | CTRL2_CMX_EN | CTRL2_UV_ADJ_EN}, + {0, 0}, +}; + +static const uint8_t yuv422_regs[][2] = { + {BANK_SEL, BANK_SEL_DSP}, + {R_BYPASS, R_BYPASS_DSP_EN}, + {IMAGE_MODE, IMAGE_MODE_YUV422}, + {0xd7, 0x03}, + {0x33, 0xa0}, + {0xe5, 0x1f}, + {0xe1, 0x67}, + {RESET, 0x00}, + {R_BYPASS, R_BYPASS_DSP_EN}, + {0, 0}, +}; + +static const uint8_t rgb565_regs[][2] = { + {BANK_SEL, BANK_SEL_DSP}, + {R_BYPASS, R_BYPASS_DSP_EN}, + {IMAGE_MODE, IMAGE_MODE_RGB565}, + {0xd7, 0x03}, + {RESET, 0x00}, + {R_BYPASS, R_BYPASS_DSP_EN}, + {0, 0}, +}; + +static const uint8_t bayer_regs[][2] = { + {BANK_SEL, BANK_SEL_DSP}, + {R_BYPASS, R_BYPASS_DSP_EN}, + {IMAGE_MODE, IMAGE_MODE_RAW10}, + {0xd7, 0x03}, + {RESET, 0x00}, + {R_BYPASS, R_BYPASS_DSP_EN}, + {0, 0}, +}; + +static const uint8_t jpeg_regs[][2] = { + {BANK_SEL, BANK_SEL_DSP}, + {R_BYPASS, R_BYPASS_DSP_EN}, + {IMAGE_MODE, IMAGE_MODE_JPEG_EN}, + {0xd7, 0x03}, + {RESET, 0x00}, + {R_BYPASS, R_BYPASS_DSP_EN}, + {0, 0}, +}; + +#define NUM_BRIGHTNESS_LEVELS (5) +static const uint8_t brightness_regs[NUM_BRIGHTNESS_LEVELS + 1][5] = { + {BPADDR, BPDATA, BPADDR, BPDATA, BPDATA}, + {0x00, 0x04, 0x09, 0x00, 0x00}, /* -2 */ + {0x00, 0x04, 0x09, 0x10, 0x00}, /* -1 */ + {0x00, 0x04, 0x09, 0x20, 0x00}, /* 0 */ + {0x00, 0x04, 0x09, 0x30, 0x00}, /* +1 */ + {0x00, 0x04, 0x09, 0x40, 0x00}, /* +2 */ +}; + +#define NUM_CONTRAST_LEVELS (5) +static const uint8_t contrast_regs[NUM_CONTRAST_LEVELS + 1][7] = { + {BPADDR, BPDATA, BPADDR, BPDATA, BPDATA, BPDATA, BPDATA}, + {0x00, 0x04, 0x07, 0x20, 0x18, 0x34, 0x06}, /* -2 */ + {0x00, 0x04, 0x07, 0x20, 0x1c, 0x2a, 0x06}, /* -1 */ + {0x00, 0x04, 0x07, 0x20, 0x20, 0x20, 0x06}, /* 0 */ + {0x00, 0x04, 0x07, 0x20, 0x24, 0x16, 0x06}, /* +1 */ + {0x00, 0x04, 0x07, 0x20, 0x28, 0x0c, 0x06}, /* +2 */ +}; + +#define NUM_SATURATION_LEVELS (5) +static const uint8_t saturation_regs[NUM_SATURATION_LEVELS + 1][5] = { + {BPADDR, BPDATA, BPADDR, BPDATA, BPDATA}, + {0x00, 0x02, 0x03, 0x28, 0x28}, /* -2 */ + {0x00, 0x02, 0x03, 0x38, 0x38}, /* -1 */ + {0x00, 0x02, 0x03, 0x48, 0x48}, /* 0 */ + {0x00, 0x02, 0x03, 0x58, 0x58}, /* +1 */ + {0x00, 0x02, 0x03, 0x68, 0x68}, /* +2 */ +}; + +static int reset(sensor_t *sensor) { + // Reset all registers + int ret = omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BANK_SEL, BANK_SEL_SENSOR); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM7, COM7_SRST); + + // Delay 5 ms + mp_hal_delay_ms(5); + + // Write default registers + for (int i = 0; default_regs[i][0]; i++) { + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, default_regs[i][0], default_regs[i][1]); + } + + // Delay 300 ms + if (!sensor->disable_delays) { + mp_hal_delay_ms(300); + } + + return ret; +} + +static int sleep(sensor_t *sensor, int enable) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM2, ®); + + if (enable) { + reg |= COM2_STDBY; + } else { + reg &= ~COM2_STDBY; + } + + // Write back register + return omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM2, reg) | ret; +} + +static int read_reg(sensor_t *sensor, uint16_t reg_addr) { + uint8_t reg_data; + if (omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, reg_addr, ®_data) != 0) { + return -1; + } + return reg_data; +} + +static int write_reg(sensor_t *sensor, uint16_t reg_addr, uint16_t reg_data) { + return omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, reg_addr, reg_data); +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) { + const uint8_t(*regs)[2]; + int ret = 0; + + switch (pixformat) { + case PIXFORMAT_RGB565: + regs = rgb565_regs; + break; + case PIXFORMAT_YUV422: + case PIXFORMAT_GRAYSCALE: + regs = yuv422_regs; + break; + case PIXFORMAT_BAYER: + regs = bayer_regs; + break; + case PIXFORMAT_JPEG: + regs = jpeg_regs; + break; + default: + return -1; + } + + // Write registers + for (int i = 0; regs[i][0]; i++) { + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, regs[i][0], regs[i][1]); + } + + return ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) { + const uint8_t(*regs)[2]; + uint16_t sensor_w = 0; + uint16_t sensor_h = 0; + int ret = 0; + uint16_t w = resolution[framesize][0]; + uint16_t h = resolution[framesize][1]; + + if ((w % 4) || (h % 4) || (w > UXGA_WIDTH) || (h > UXGA_HEIGHT)) { + // w/h must be divisible by 4 + return -1; + } + + // Looks really bad. + /* if ((w <= CIF_WIDTH) && (h <= CIF_HEIGHT)) { + regs = cif_regs; + sensor_w = CIF_WIDTH; + sensor_h = CIF_HEIGHT; + } else */if ((w <= SVGA_WIDTH) && (h <= SVGA_HEIGHT)) { + regs = svga_regs; + sensor_w = SVGA_WIDTH; + sensor_h = SVGA_HEIGHT; + } else { + regs = uxga_regs; + sensor_w = UXGA_WIDTH; + sensor_h = UXGA_HEIGHT; + } + + // Write setup registers + for (int i = 0; regs[i][0]; i++) { + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, regs[i][0], regs[i][1]); + } + + uint64_t tmp_div = IM_MIN(sensor_w / w, sensor_h / h); + uint16_t log_div = IM_MIN(IM_LOG2(tmp_div) - 1, 3); + uint16_t div = 1 << log_div; + uint16_t w_mul = w * div; + uint16_t h_mul = h * div; + uint16_t x_off = (sensor_w - w_mul) / 2; + uint16_t y_off = (sensor_h - h_mul) / 2; + + ret |= + omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, CTRLI, + CTRLI_LP_DP | CTRLI_V_DIV_SET(log_div) | CTRLI_H_DIV_SET(log_div)); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, HSIZE, HSIZE_SET(w_mul)); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, VSIZE, VSIZE_SET(h_mul)); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, XOFFL, XOFFL_SET(x_off)); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, YOFFL, YOFFL_SET(y_off)); + ret |= omv_i2c_writeb(&sensor->i2c_bus, + sensor->slv_addr, + VHYX, + VHYX_HSIZE_SET(w_mul) | VHYX_VSIZE_SET(h_mul) | VHYX_XOFF_SET(x_off) | VHYX_YOFF_SET(y_off)); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, TEST, TEST_HSIZE_SET(w_mul)); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, ZMOW, ZMOW_OUTW_SET(w)); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, ZMOH, ZMOH_OUTH_SET(h)); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, ZMHH, ZMHH_OUTW_SET(w) | ZMHH_OUTH_SET(h)); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, R_DVP_SP, div); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, RESET, 0x00); + + return ret; +} + +static int set_contrast(sensor_t *sensor, int level) { + int ret = 0; + + level += (NUM_CONTRAST_LEVELS / 2) + 1; + if (level <= 0 || level > NUM_CONTRAST_LEVELS) { + return -1; + } + + /* Switch to DSP register bank */ + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BANK_SEL, BANK_SEL_DSP); + + /* Write contrast registers */ + for (int i = 0; i < sizeof(contrast_regs[0]) / sizeof(contrast_regs[0][0]); i++) { + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, contrast_regs[0][i], contrast_regs[level][i]); + } + + return ret; +} + +static int set_brightness(sensor_t *sensor, int level) { + int ret = 0; + + level += (NUM_BRIGHTNESS_LEVELS / 2) + 1; + if (level <= 0 || level > NUM_BRIGHTNESS_LEVELS) { + return -1; + } + + /* Switch to DSP register bank */ + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BANK_SEL, BANK_SEL_DSP); + + /* Write brightness registers */ + for (int i = 0; i < sizeof(brightness_regs[0]) / sizeof(brightness_regs[0][0]); i++) { + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, brightness_regs[0][i], brightness_regs[level][i]); + } + + return ret; +} + +static int set_saturation(sensor_t *sensor, int level) { + int ret = 0; + + level += (NUM_SATURATION_LEVELS / 2) + 1; + if (level <= 0 || level > NUM_SATURATION_LEVELS) { + return -1; + } + + /* Switch to DSP register bank */ + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BANK_SEL, BANK_SEL_DSP); + + /* Write saturation registers */ + for (int i = 0; i < sizeof(saturation_regs[0]) / sizeof(saturation_regs[0][0]); i++) { + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, saturation_regs[0][i], saturation_regs[level][i]); + } + + return ret; +} + +static int set_gainceiling(sensor_t *sensor, gainceiling_t gainceiling) { + int ret = 0; + + /* Switch to SENSOR register bank */ + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BANK_SEL, BANK_SEL_SENSOR); + + /* Write gain ceiling register */ + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM9, COM9_AGC_SET(gainceiling)); + + return ret; +} + +static int set_quality(sensor_t *sensor, int qs) { + int ret = 0; + + /* Switch to DSP register bank */ + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BANK_SEL, BANK_SEL_DSP); + + /* Write QS register */ + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, QS, qs); + + return ret; +} + +static int set_colorbar(sensor_t *sensor, int enable) { + uint8_t reg; + int ret = omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BANK_SEL, BANK_SEL_SENSOR); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM7, ®); + + if (enable) { + reg |= COM7_COLOR_BAR; + } else { + reg &= ~COM7_COLOR_BAR; + } + + return omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM7, reg) | ret; +} + +static int set_auto_gain(sensor_t *sensor, int enable, float gain_db, float gain_db_ceiling) { + uint8_t reg; + int ret = omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BANK_SEL, BANK_SEL_SENSOR); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM8, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM8, (reg & (~COM8_AGC_EN)) | ((enable != 0) ? COM8_AGC_EN : 0)); + + if ((enable == 0) && (!isnanf(gain_db)) && (!isinff(gain_db))) { + float gain = IM_MAX(IM_MIN(expf((gain_db / 20.0f) * M_LN10), 32.0f), 1.0f); + + int gain_temp = fast_ceilf(logf(IM_MAX(gain / 2.0f, 1.0f)) / M_LN2); + int gain_hi = 0xF >> (4 - gain_temp); + int gain_lo = IM_MIN(fast_roundf(((gain / (1 << gain_temp)) - 1.0f) * 16.0f), 15); + + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, GAIN, (gain_hi << 4) | (gain_lo << 0)); + } else if ((enable != 0) && (!isnanf(gain_db_ceiling)) && (!isinff(gain_db_ceiling))) { + float gain_ceiling = IM_MAX(IM_MIN(expf((gain_db_ceiling / 20.0f) * M_LN10), 128.0f), 2.0f); + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM9, ®); + ret |= + omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM9, + (reg & 0x1F) | ((fast_ceilf(logf(gain_ceiling) / M_LN2) - 1) << 5)); + } + + return ret; +} + +static int get_gain_db(sensor_t *sensor, float *gain_db) { + uint8_t reg, gain; + int ret = omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BANK_SEL, BANK_SEL_SENSOR); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM8, ®); + + // DISABLED + // if (reg & COM8_AGC_EN) { + // ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM8, reg & (~COM8_AGC_EN)); + // } + // DISABLED + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, GAIN, &gain); + + // DISABLED + // if (reg & COM8_AGC_EN) { + // ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM8, reg | COM8_AGC_EN); + // } + // DISABLED + + int hi_gain = 1 << (((gain >> 7) & 1) + ((gain >> 6) & 1) + ((gain >> 5) & 1) + ((gain >> 4) & 1)); + float lo_gain = 1.0f + (((gain >> 0) & 0xF) / 16.0f); + *gain_db = 20.0f * log10f(hi_gain * lo_gain); + + return ret; +} + +static int set_auto_exposure(sensor_t *sensor, int enable, int exposure_us) { + uint8_t reg; + int ret = omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BANK_SEL, BANK_SEL_SENSOR); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM8, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM8, COM8_SET_AEC(reg, (enable != 0))); + + if ((enable == 0) && (exposure_us >= 0)) { + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM7, ®); + int t_line = 0; + + if (COM7_GET_RES(reg) == COM7_RES_UXGA) { + t_line = 1600 + 322; + } + if (COM7_GET_RES(reg) == COM7_RES_SVGA) { + t_line = 800 + 390; + } + if (COM7_GET_RES(reg) == COM7_RES_CIF) { + t_line = 400 + 195; + } + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, CLKRC, ®); + int pll_mult = ((reg & CLKRC_DOUBLE) ? 2 : 1) * 3; + int clk_rc = (reg & CLKRC_DIVIDER_MASK) + 2; + + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BANK_SEL, BANK_SEL_DSP); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, IMAGE_MODE, ®); + int t_pclk = 0; + + if (IMAGE_MODE_GET_FMT(reg) == IMAGE_MODE_YUV422) { + t_pclk = 2; + } + if (IMAGE_MODE_GET_FMT(reg) == IMAGE_MODE_RAW10) { + t_pclk = 1; + } + if (IMAGE_MODE_GET_FMT(reg) == IMAGE_MODE_RGB565) { + t_pclk = 2; + } + + int exposure = + IM_MAX(IM_MIN(((exposure_us * (((OMV_XCLK_FREQUENCY / clk_rc) * pll_mult) / 1000000)) / t_pclk) / t_line, 0xFFFF), + 0x0000); + + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BANK_SEL, BANK_SEL_SENSOR); + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG04, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG04, (reg & 0xFC) | ((exposure >> 0) & 0x3)); + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, AEC, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, AEC, (reg & 0x00) | ((exposure >> 2) & 0xFF)); + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG45, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG45, (reg & 0xC0) | ((exposure >> 10) & 0x3F)); + } + + return ret; +} + +static int get_exposure_us(sensor_t *sensor, int *exposure_us) { + uint8_t reg, aec_10, aec_92, aec_1510; + int ret = omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BANK_SEL, BANK_SEL_SENSOR); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM8, ®); + + // DISABLED + // if (reg & COM8_AEC_EN) { + // ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM8, reg & (~COM8_AEC_EN)); + // } + // DISABLED + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG04, &aec_10); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, AEC, &aec_92); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG45, &aec_1510); + + // DISABLED + // if (reg & COM8_AEC_EN) { + // ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM8, reg | COM8_AEC_EN); + // } + // DISABLED + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM7, ®); + int t_line = 0; + + if (COM7_GET_RES(reg) == COM7_RES_UXGA) { + t_line = 1600 + 322; + } + if (COM7_GET_RES(reg) == COM7_RES_SVGA) { + t_line = 800 + 390; + } + if (COM7_GET_RES(reg) == COM7_RES_CIF) { + t_line = 400 + 195; + } + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, CLKRC, ®); + int pll_mult = ((reg & CLKRC_DOUBLE) ? 2 : 1) * 3; + int clk_rc = (reg & CLKRC_DIVIDER_MASK) + 2; + + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BANK_SEL, BANK_SEL_DSP); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, IMAGE_MODE, ®); + int t_pclk = 0; + + if (IMAGE_MODE_GET_FMT(reg) == IMAGE_MODE_YUV422) { + t_pclk = 2; + } + if (IMAGE_MODE_GET_FMT(reg) == IMAGE_MODE_RAW10) { + t_pclk = 1; + } + if (IMAGE_MODE_GET_FMT(reg) == IMAGE_MODE_RGB565) { + t_pclk = 2; + } + + uint16_t exposure = ((aec_1510 & 0x3F) << 10) + ((aec_92 & 0xFF) << 2) + ((aec_10 & 0x3) << 0); + *exposure_us = (exposure * t_line * t_pclk) / (((OMV_XCLK_FREQUENCY / clk_rc) * pll_mult) / 1000000); + + return ret; +} + +static int set_auto_whitebal(sensor_t *sensor, int enable, float r_gain_db, float g_gain_db, float b_gain_db) { + uint8_t reg; + int ret = omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BANK_SEL, BANK_SEL_DSP); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, CTRL1, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, CTRL1, (reg & (~CTRL1_AWB)) | ((enable != 0) ? CTRL1_AWB : 0)); + + if ((enable == 0) && (!isnanf(r_gain_db)) && (!isnanf(g_gain_db)) && (!isnanf(b_gain_db)) + && (!isinff(r_gain_db)) && (!isinff(g_gain_db)) && (!isinff(b_gain_db))) { + } + + return ret; +} + +static int get_rgb_gain_db(sensor_t *sensor, float *r_gain_db, float *g_gain_db, float *b_gain_db) { + uint8_t reg; + int ret = omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BANK_SEL, BANK_SEL_DSP); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, CTRL1, ®); + + // DISABLED + // if (reg & CTRL1_AWB) { + // ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, CTRL1, reg & (~CTRL1_AWB)); + // } + // DISABLED + + // DISABLED + // if (reg & CTRL1_AWB) { + // ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, CTRL1, reg | CTRL1_AWB); + // } + // DISABLED + + *r_gain_db = NAN; + *g_gain_db = NAN; + *b_gain_db = NAN; + + return ret; +} + +static int set_hmirror(sensor_t *sensor, int enable) { + uint8_t reg; + int ret = omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BANK_SEL, BANK_SEL_SENSOR); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG04, ®); + + if (!enable) { + // Already mirrored. + reg |= REG04_HFLIP_IMG; + } else { + reg &= ~REG04_HFLIP_IMG; + } + + return omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG04, reg) | ret; +} + +static int set_vflip(sensor_t *sensor, int enable) { + uint8_t reg; + int ret = omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BANK_SEL, BANK_SEL_SENSOR); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG04, ®); + + if (!enable) { + // Already flipped. + reg |= REG04_VFLIP_IMG | REG04_VREF_EN; + } else { + reg &= ~(REG04_VFLIP_IMG | REG04_VREF_EN); + } + + return omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG04, reg) | ret; +} + +static int set_special_effect(sensor_t *sensor, sde_t sde) { + int ret = 0; + + switch (sde) { + case SDE_NEGATIVE: + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BANK_SEL, BANK_SEL_DSP); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BPADDR, 0x00); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BPDATA, 0x40); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BPADDR, 0x05); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BPDATA, 0x80); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BPDATA, 0x80); + break; + case SDE_NORMAL: + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BANK_SEL, BANK_SEL_DSP); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BPADDR, 0x00); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BPDATA, 0x00); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BPADDR, 0x05); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BPDATA, 0x80); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BPDATA, 0x80); + break; + default: + return -1; + } + + return ret; +} + +int ov2640_init(sensor_t *sensor) { + // Initialize sensor structure. + sensor->reset = reset; + sensor->sleep = sleep; + sensor->read_reg = read_reg; + sensor->write_reg = write_reg; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_contrast = set_contrast; + sensor->set_brightness = set_brightness; + sensor->set_saturation = set_saturation; + sensor->set_gainceiling = set_gainceiling; + sensor->set_quality = set_quality; + sensor->set_colorbar = set_colorbar; + sensor->set_auto_gain = set_auto_gain; + sensor->get_gain_db = get_gain_db; + sensor->set_auto_exposure = set_auto_exposure; + sensor->get_exposure_us = get_exposure_us; + sensor->set_auto_whitebal = set_auto_whitebal; + sensor->get_rgb_gain_db = get_rgb_gain_db; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + sensor->set_special_effect = set_special_effect; + + // Set sensor flags + sensor->hw_flags.vsync = 0; + sensor->hw_flags.hsync = 0; + sensor->hw_flags.pixck = 1; + sensor->hw_flags.fsync = 0; + sensor->hw_flags.jpege = 1; + sensor->hw_flags.jpeg_mode = 3; + sensor->hw_flags.gs_bpp = 2; + sensor->hw_flags.rgb_swap = 0; + sensor->hw_flags.yuv_order = SENSOR_HW_FLAGS_YVU422; + + return 0; +} +#endif // (OMV_ENABLE_OV2640 == 1) diff --git a/components/3rd_party/omv/omv/sensors/ov2640.h b/components/3rd_party/omv/omv/sensors/ov2640.h new file mode 100644 index 00000000..a2628d74 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/ov2640.h @@ -0,0 +1,15 @@ +/* + * 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. + * + * OV2640 driver. + */ +#ifndef __OV2640_H__ +#define __OV2640_H__ +#define OV2640_XCLK_FREQ 24000000 +int ov2640_init(sensor_t *sensor); +#endif // __OV2640_H__ diff --git a/components/3rd_party/omv/omv/sensors/ov2640_regs.h b/components/3rd_party/omv/omv/sensors/ov2640_regs.h new file mode 100644 index 00000000..85735b95 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/ov2640_regs.h @@ -0,0 +1,245 @@ +/* + * 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. + * + * OV2640 register definitions. + */ +#ifndef __REG_REGS_H__ +#define __REG_REGS_H__ + +/* DSP register bank FF=0x00*/ + +#define QS 0x44 +#define HSIZE 0x51 +#define VSIZE 0x52 +#define XOFFL 0x53 +#define YOFFL 0x54 +#define VHYX 0x55 +#define DPRP 0x56 +#define TEST 0x57 +#define ZMOW 0x5A +#define ZMOH 0x5B +#define ZMHH 0x5C +#define BPADDR 0x7C +#define BPDATA 0x7D +#define SIZEL 0x8C +#define HSIZE8 0xC0 +#define VSIZE8 0xC1 +#define CTRL1 0xC3 +#define MS_SP 0xF0 +#define SS_ID 0xF7 +#define SS_CTRL 0xF7 +#define MC_AL 0xFA +#define MC_AH 0xFB +#define MC_D 0xFC +#define P_CMD 0xFD +#define P_STATUS 0xFE + +#define CTRLI 0x50 +#define CTRLI_LP_DP 0x80 +#define CTRLI_ROUND 0x40 + +#define CTRL0 0xC2 +#define CTRL0_AEC_EN 0x80 +#define CTRL0_AEC_SEL 0x40 +#define CTRL0_STAT_SEL 0x20 +#define CTRL0_VFIRST 0x10 +#define CTRL0_YUV422 0x08 +#define CTRL0_YUV_EN 0x04 +#define CTRL0_RGB_EN 0x02 +#define CTRL0_RAW_EN 0x01 + +#define CTRL2 0x86 +#define CTRL2_DCW_EN 0x20 +#define CTRL2_SDE_EN 0x10 +#define CTRL2_UV_ADJ_EN 0x08 +#define CTRL2_UV_AVG_EN 0x04 +#define CTRL2_CMX_EN 0x01 + +#define CTRL3 0x87 +#define CTRL3_BPC_EN 0x80 +#define CTRL3_WPC_EN 0x40 +#define R_DVP_SP 0xD3 +#define R_DVP_SP_AUTO_MODE 0x80 + +#define R_BYPASS 0x05 +#define R_BYPASS_DSP_EN 0x00 +#define R_BYPASS_DSP_BYPAS 0x01 + +#define IMAGE_MODE 0xDA +#define IMAGE_MODE_Y8_DVP_EN 0x40 +#define IMAGE_MODE_JPEG_EN 0x10 +#define IMAGE_MODE_YUV422 0x00 +#define IMAGE_MODE_RAW10 0x04 +#define IMAGE_MODE_RGB565 0x09 +#define IMAGE_MODE_HREF_VSYNC 0x02 +#define IMAGE_MODE_LBYTE_FIRST 0x01 +#define IMAGE_MODE_GET_FMT(x) ((x) & 0xC) + +#define RESET 0xE0 +#define RESET_MICROC 0x40 +#define RESET_SCCB 0x20 +#define RESET_JPEG 0x10 +#define RESET_DVP 0x04 +#define RESET_IPU 0x02 +#define RESET_CIF 0x01 + +#define MC_BIST 0xF9 +#define MC_BIST_RESET 0x80 +#define MC_BIST_BOOT_ROM_SEL 0x40 +#define MC_BIST_12KB_SEL 0x20 +#define MC_BIST_12KB_MASK 0x30 +#define MC_BIST_512KB_SEL 0x08 +#define MC_BIST_512KB_MASK 0x0C +#define MC_BIST_BUSY_BIT_R 0x02 +#define MC_BIST_MC_RES_ONE_SH_W 0x02 +#define MC_BIST_LAUNCH 0x01 + +#define BANK_SEL 0xFF +#define BANK_SEL_DSP 0x00 +#define BANK_SEL_SENSOR 0x01 + +/* Sensor register bank FF=0x01*/ + +#define GAIN 0x00 +#define COM1 0x03 +#define REG_PID 0x0A +#define REG_VER 0x0B +#define COM4 0x0D +#define AEC 0x10 + +#define CLKRC 0x11 +#define CLKRC_DOUBLE 0x82 +#define CLKRC_DIVIDER_MASK 0x3F + +#define COM10 0x15 +#define HSTART 0x17 +#define HSTOP 0x18 +#define VSTART 0x19 +#define VSTOP 0x1A +#define MIDH 0x1C +#define MIDL 0x1D +#define AEW 0x24 +#define AEB 0x25 +#define REG2A 0x2A +#define FRARL 0x2B +#define ADDVSL 0x2D +#define ADDVSH 0x2E +#define YAVG 0x2F +#define HSDY 0x30 +#define HEDY 0x31 +#define ARCOM2 0x34 +#define REG45 0x45 +#define FLL 0x46 +#define FLH 0x47 +#define COM19 0x48 +#define ZOOMS 0x49 +#define COM22 0x4B +#define COM25 0x4E +#define BD50 0x4F +#define BD60 0x50 +#define REG5D 0x5D +#define REG5E 0x5E +#define REG5F 0x5F +#define REG60 0x60 +#define HISTO_LOW 0x61 +#define HISTO_HIGH 0x62 + +#define REG04 0x04 +#define REG04_DEFAULT 0x28 +#define REG04_HFLIP_IMG 0x80 +#define REG04_VFLIP_IMG 0x40 +#define REG04_VREF_EN 0x10 +#define REG04_HREF_EN 0x08 +#define REG04_SET(x) (REG04_DEFAULT | x) + +#define REG08 0x08 +#define COM2 0x09 +#define COM2_STDBY 0x10 +#define COM2_OUT_DRIVE_1x 0x00 +#define COM2_OUT_DRIVE_2x 0x01 +#define COM2_OUT_DRIVE_3x 0x02 +#define COM2_OUT_DRIVE_4x 0x03 + +#define COM3 0x0C +#define COM3_DEFAULT 0x38 +#define COM3_BAND_50Hz 0x04 +#define COM3_BAND_60Hz 0x00 +#define COM3_BAND_AUTO 0x02 +#define COM3_BAND_SET(x) (COM3_DEFAULT | x) + +#define COM7 0x12 +#define COM7_SRST 0x80 +#define COM7_RES_UXGA 0x00 /* UXGA */ +#define COM7_RES_SVGA 0x40 /* SVGA */ +#define COM7_RES_CIF 0x20 /* CIF */ +#define COM7_ZOOM_EN 0x04 /* Enable Zoom */ +#define COM7_COLOR_BAR 0x02 /* Enable Color Bar Test */ +#define COM7_GET_RES(x) ((x) & 0x70) + +#define COM8 0x13 +#define COM8_DEFAULT 0xC0 +#define COM8_BNDF_EN 0x20 /* Enable Banding filter */ +#define COM8_AGC_EN 0x04 /* AGC Auto/Manual control selection */ +#define COM8_AEC_EN 0x01 /* Auto/Manual Exposure control */ +#define COM8_SET(x) (COM8_DEFAULT | x) +#define COM8_SET_AEC(r, x) (((r) & 0xFE) | ((x) & 1)) + +#define COM9 0x14 /* AGC gain ceiling */ +#define COM9_DEFAULT 0x08 +#define COM9_AGC_GAIN_2x 0x00 /* AGC: 2x */ +#define COM9_AGC_GAIN_4x 0x01 /* AGC: 4x */ +#define COM9_AGC_GAIN_8x 0x02 /* AGC: 8x */ +#define COM9_AGC_GAIN_16x 0x03 /* AGC: 16x */ +#define COM9_AGC_GAIN_32x 0x04 /* AGC: 32x */ +#define COM9_AGC_GAIN_64x 0x05 /* AGC: 64x */ +#define COM9_AGC_GAIN_128x 0x06 /* AGC: 128x */ +#define COM9_AGC_SET(x) (COM9_DEFAULT | (x << 5)) + +#define CTRL1_AWB 0x08 /* Enable AWB */ + +#define VV 0x26 +#define VV_AGC_TH_SET(h, l) ((h << 4) | (l & 0x0F)) + +#define REG32 0x32 +#define REG32_UXGA 0x36 +#define REG32_SVGA 0x09 +#define REG32_CIF 0x00 + +#define VAL_SET(x, mask, rshift, lshift) ((((x) >> rshift) & mask) << lshift) + +#define CTRLI_V_DIV_SET(x) VAL_SET(x, 0x3, 0, 3) +#define CTRLI_H_DIV_SET(x) VAL_SET(x, 0x3, 0, 0) + +#define SIZEL_HSIZE8_11_SET(x) VAL_SET(x, 0x1, 11, 6) +#define SIZEL_HSIZE8_SET(x) VAL_SET(x, 0x7, 0, 3) +#define SIZEL_VSIZE8_SET(x) VAL_SET(x, 0x7, 0, 0) + +#define HSIZE8_SET(x) VAL_SET(x, 0xFF, 3, 0) +#define VSIZE8_SET(x) VAL_SET(x, 0xFF, 3, 0) + +#define HSIZE_SET(x) VAL_SET(x, 0xFF, 2, 0) +#define VSIZE_SET(x) VAL_SET(x, 0xFF, 2, 0) + +#define XOFFL_SET(x) VAL_SET(x, 0xFF, 0, 0) +#define YOFFL_SET(x) VAL_SET(x, 0xFF, 0, 0) + +#define VHYX_VSIZE_SET(x) VAL_SET(x, 0x1, (8 + 2), 7) +#define VHYX_HSIZE_SET(x) VAL_SET(x, 0x1, (8 + 2), 3) +#define VHYX_YOFF_SET(x) VAL_SET(x, 0x3, 8, 4) +#define VHYX_XOFF_SET(x) VAL_SET(x, 0x3, 8, 0) + +#define TEST_HSIZE_SET(x) VAL_SET(x, 0x1, (9 + 2), 7) + +#define ZMOW_OUTW_SET(x) VAL_SET(x, 0xFF, 2, 0) +#define ZMOH_OUTH_SET(x) VAL_SET(x, 0xFF, 2, 0) + +#define ZMHH_ZSPEED_SET(x) VAL_SET(x, 0x0F, 0, 4) +#define ZMHH_OUTH_SET(x) VAL_SET(x, 0x1, (8 + 2), 2) +#define ZMHH_OUTW_SET(x) VAL_SET(x, 0x3, (8 + 2), 0) + +#endif //__REG_REGS_H__ diff --git a/components/3rd_party/omv/omv/sensors/ov5640.c b/components/3rd_party/omv/omv/sensors/ov5640.c new file mode 100644 index 00000000..32f10c7a --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/ov5640.c @@ -0,0 +1,1459 @@ +/* + * 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. + * + * OV5640 driver. + */ +#include "omv_boardconfig.h" +#if (OMV_ENABLE_OV5640 == 1) + +#include +#include +#include + +#include "omv_i2c.h" +#include "sensor.h" +#include "ov5640.h" +#include "ov5640_regs.h" +#include "py/mphal.h" + +#define BLANK_LINES 8 +#define DUMMY_LINES 6 + +#define BLANK_COLUMNS 0 +#define DUMMY_COLUMNS 16 + +#define SENSOR_WIDTH 2624 +#define SENSOR_HEIGHT 1964 + +#define ACTIVE_SENSOR_WIDTH (SENSOR_WIDTH - BLANK_COLUMNS - (2 * DUMMY_COLUMNS)) +#define ACTIVE_SENSOR_HEIGHT (SENSOR_HEIGHT - BLANK_LINES - (2 * DUMMY_LINES)) + +#define DUMMY_WIDTH_BUFFER 16 +#define DUMMY_HEIGHT_BUFFER 8 + +#define HSYNC_TIME 252 +#define VYSNC_TIME 24 + +static int16_t readout_x = 0; +static int16_t readout_y = 0; + +static uint16_t readout_w = ACTIVE_SENSOR_WIDTH; +static uint16_t readout_h = ACTIVE_SENSOR_HEIGHT; + +static uint16_t hts_target = 0; + +static const uint8_t default_regs[][3] = { + +// https://github.com/ArduCAM/Arduino/blob/master/ArduCAM/ov5640_regs.h + + { 0x47, 0x40, 0x20 }, + { 0x40, 0x50, 0x6e }, + { 0x40, 0x51, 0x8f }, + { 0x30, 0x08, 0x42 }, + { 0x31, 0x03, 0x03 }, + { 0x30, 0x17, 0xff }, // { 0x30, 0x17, 0x7f }, + { 0x30, 0x18, 0xff }, + { 0x30, 0x2c, 0x02 }, + { 0x31, 0x08, 0x01 }, + { 0x36, 0x30, 0x2e }, + { 0x36, 0x32, 0xe2 }, + { 0x36, 0x33, 0x23 }, + { 0x36, 0x21, 0xe0 }, + { 0x37, 0x04, 0xa0 }, + { 0x37, 0x03, 0x5a }, + { 0x37, 0x15, 0x78 }, + { 0x37, 0x17, 0x01 }, + { 0x37, 0x0b, 0x60 }, + { 0x37, 0x05, 0x1a }, + { 0x39, 0x05, 0x02 }, + { 0x39, 0x06, 0x10 }, + { 0x39, 0x01, 0x0a }, + { 0x37, 0x31, 0x12 }, + { 0x36, 0x00, 0x08 }, + { 0x36, 0x01, 0x33 }, + { 0x30, 0x2d, 0x60 }, + { 0x36, 0x20, 0x52 }, + { 0x37, 0x1b, 0x20 }, + { 0x47, 0x1c, 0x50 }, + { 0x3a, 0x18, 0x00 }, + { 0x3a, 0x19, 0xf8 }, + { 0x36, 0x35, 0x1c }, + { 0x36, 0x34, 0x40 }, + { 0x36, 0x22, 0x01 }, + { 0x3c, 0x04, 0x28 }, + { 0x3c, 0x05, 0x98 }, + { 0x3c, 0x06, 0x00 }, + { 0x3c, 0x07, 0x08 }, + { 0x3c, 0x08, 0x00 }, + { 0x3c, 0x09, 0x1c }, + { 0x3c, 0x0a, 0x9c }, + { 0x3c, 0x0b, 0x40 }, + { 0x38, 0x20, 0x47 }, // { 0x38, 0x20, 0x41 }, + { 0x38, 0x21, 0x01 }, + { 0x38, 0x00, 0x00 }, + { 0x38, 0x01, 0x00 }, + { 0x38, 0x02, 0x00 }, + { 0x38, 0x03, 0x04 }, + { 0x38, 0x04, 0x0a }, + { 0x38, 0x05, 0x3f }, + { 0x38, 0x06, 0x07 }, + { 0x38, 0x07, 0x9b }, + { 0x38, 0x08, 0x05 }, + { 0x38, 0x09, 0x00 }, + { 0x38, 0x0a, 0x03 }, + { 0x38, 0x0b, 0xc0 }, + { 0x38, 0x10, 0x00 }, + { 0x38, 0x11, 0x10 }, + { 0x38, 0x12, 0x00 }, + { 0x38, 0x13, 0x06 }, + { 0x38, 0x14, 0x31 }, + { 0x38, 0x15, 0x31 }, + { 0x30, 0x34, 0x1a }, + { 0x30, 0x35, 0x11 }, // { 0x30, 0x35, 0x21 }, + { 0x30, 0x36, OMV_OV5640_PLL_CTRL2 }, // { 0x30, 0x36, 0x46 }, + { 0x30, 0x37, OMV_OV5640_PLL_CTRL3 }, + { 0x30, 0x38, 0x00 }, + { 0x30, 0x39, 0x00 }, + { 0x38, 0x0c, 0x07 }, + { 0x38, 0x0d, 0x68 }, + { 0x38, 0x0e, 0x03 }, + { 0x38, 0x0f, 0xd8 }, + { 0x3c, 0x01, 0xb4 }, + { 0x3c, 0x00, 0x04 }, + { 0x3a, 0x08, 0x00 }, + { 0x3a, 0x09, 0x93 }, + { 0x3a, 0x0e, 0x06 }, + { 0x3a, 0x0a, 0x00 }, + { 0x3a, 0x0b, 0x7b }, + { 0x3a, 0x0d, 0x08 }, + { 0x3a, 0x00, 0x38 }, // { 0x3a, 0x00, 0x3c }, + { 0x3a, 0x02, 0x05 }, + { 0x3a, 0x03, 0xc4 }, + { 0x3a, 0x14, 0x05 }, + { 0x3a, 0x15, 0xc4 }, + { 0x36, 0x18, 0x00 }, + { 0x36, 0x12, 0x29 }, + { 0x37, 0x08, 0x64 }, + { 0x37, 0x09, 0x52 }, + { 0x37, 0x0c, 0x03 }, + { 0x40, 0x01, 0x02 }, + { 0x40, 0x04, 0x02 }, + { 0x30, 0x00, 0x00 }, + { 0x30, 0x02, 0x1c }, + { 0x30, 0x04, 0xff }, + { 0x30, 0x06, 0xc3 }, + { 0x30, 0x0e, 0x58 }, + { 0x30, 0x2e, 0x00 }, + { 0x43, 0x00, 0x30 }, + { 0x50, 0x1f, 0x00 }, + { 0x47, 0x13, 0x04 }, // { 0x47, 0x13, 0x03 }, + { 0x44, 0x07, 0x04 }, + { 0x46, 0x0b, 0x35 }, + { 0x46, 0x0c, 0x22 }, + { 0x38, 0x24, 0x02 }, // { 0x38, 0x24, 0x01 }, + { 0x50, 0x01, 0xa3 }, + { 0x34, 0x06, 0x01 }, + { 0x34, 0x00, 0x06 }, + { 0x34, 0x01, 0x80 }, + { 0x34, 0x02, 0x04 }, + { 0x34, 0x03, 0x00 }, + { 0x34, 0x04, 0x06 }, + { 0x34, 0x05, 0x00 }, + { 0x51, 0x80, 0xff }, + { 0x51, 0x81, 0xf2 }, + { 0x51, 0x82, 0x00 }, + { 0x51, 0x83, 0x14 }, + { 0x51, 0x84, 0x25 }, + { 0x51, 0x85, 0x24 }, + { 0x51, 0x86, 0x16 }, + { 0x51, 0x87, 0x16 }, + { 0x51, 0x88, 0x16 }, + { 0x51, 0x89, 0x62 }, + { 0x51, 0x8a, 0x62 }, + { 0x51, 0x8b, 0xf0 }, + { 0x51, 0x8c, 0xb2 }, + { 0x51, 0x8d, 0x50 }, + { 0x51, 0x8e, 0x30 }, + { 0x51, 0x8f, 0x30 }, + { 0x51, 0x90, 0x50 }, + { 0x51, 0x91, 0xf8 }, + { 0x51, 0x92, 0x04 }, + { 0x51, 0x93, 0x70 }, + { 0x51, 0x94, 0xf0 }, + { 0x51, 0x95, 0xf0 }, + { 0x51, 0x96, 0x03 }, + { 0x51, 0x97, 0x01 }, + { 0x51, 0x98, 0x04 }, + { 0x51, 0x99, 0x12 }, + { 0x51, 0x9a, 0x04 }, + { 0x51, 0x9b, 0x00 }, + { 0x51, 0x9c, 0x06 }, + { 0x51, 0x9d, 0x82 }, + { 0x51, 0x9e, 0x38 }, + { 0x53, 0x81, 0x1e }, + { 0x53, 0x82, 0x5b }, + { 0x53, 0x83, 0x14 }, + { 0x53, 0x84, 0x06 }, + { 0x53, 0x85, 0x82 }, + { 0x53, 0x86, 0x88 }, + { 0x53, 0x87, 0x7c }, + { 0x53, 0x88, 0x60 }, + { 0x53, 0x89, 0x1c }, + { 0x53, 0x8a, 0x01 }, + { 0x53, 0x8b, 0x98 }, + { 0x53, 0x00, 0x08 }, + { 0x53, 0x01, 0x30 }, + { 0x53, 0x02, 0x3f }, + { 0x53, 0x03, 0x10 }, + { 0x53, 0x04, 0x08 }, + { 0x53, 0x05, 0x30 }, + { 0x53, 0x06, 0x18 }, + { 0x53, 0x07, 0x28 }, + { 0x53, 0x09, 0x08 }, + { 0x53, 0x0a, 0x30 }, + { 0x53, 0x0b, 0x04 }, + { 0x53, 0x0c, 0x06 }, + { 0x54, 0x80, 0x01 }, + { 0x54, 0x81, 0x06 }, + { 0x54, 0x82, 0x12 }, + { 0x54, 0x83, 0x24 }, + { 0x54, 0x84, 0x4a }, + { 0x54, 0x85, 0x58 }, + { 0x54, 0x86, 0x65 }, + { 0x54, 0x87, 0x72 }, + { 0x54, 0x88, 0x7d }, + { 0x54, 0x89, 0x88 }, + { 0x54, 0x8a, 0x92 }, + { 0x54, 0x8b, 0xa3 }, + { 0x54, 0x8c, 0xb2 }, + { 0x54, 0x8d, 0xc8 }, + { 0x54, 0x8e, 0xdd }, + { 0x54, 0x8f, 0xf0 }, + { 0x54, 0x90, 0x15 }, + { 0x55, 0x80, 0x06 }, + { 0x55, 0x83, 0x40 }, + { 0x55, 0x84, 0x20 }, + { 0x55, 0x89, 0x10 }, + { 0x55, 0x8a, 0x00 }, + { 0x55, 0x8b, 0xf8 }, + { 0x50, 0x00, 0x27 }, // { 0x50, 0x00, 0xa7 }, + { 0x58, 0x00, 0x20 }, + { 0x58, 0x01, 0x19 }, + { 0x58, 0x02, 0x17 }, + { 0x58, 0x03, 0x16 }, + { 0x58, 0x04, 0x18 }, + { 0x58, 0x05, 0x21 }, + { 0x58, 0x06, 0x0F }, + { 0x58, 0x07, 0x0A }, + { 0x58, 0x08, 0x07 }, + { 0x58, 0x09, 0x07 }, + { 0x58, 0x0a, 0x0A }, + { 0x58, 0x0b, 0x0C }, + { 0x58, 0x0c, 0x0A }, + { 0x58, 0x0d, 0x03 }, + { 0x58, 0x0e, 0x01 }, + { 0x58, 0x0f, 0x01 }, + { 0x58, 0x10, 0x03 }, + { 0x58, 0x11, 0x09 }, + { 0x58, 0x12, 0x0A }, + { 0x58, 0x13, 0x03 }, + { 0x58, 0x14, 0x01 }, + { 0x58, 0x15, 0x01 }, + { 0x58, 0x16, 0x03 }, + { 0x58, 0x17, 0x08 }, + { 0x58, 0x18, 0x10 }, + { 0x58, 0x19, 0x0A }, + { 0x58, 0x1a, 0x06 }, + { 0x58, 0x1b, 0x06 }, + { 0x58, 0x1c, 0x08 }, + { 0x58, 0x1d, 0x0E }, + { 0x58, 0x1e, 0x22 }, + { 0x58, 0x1f, 0x18 }, + { 0x58, 0x20, 0x13 }, + { 0x58, 0x21, 0x12 }, + { 0x58, 0x22, 0x16 }, + { 0x58, 0x23, 0x1E }, + { 0x58, 0x24, 0x64 }, + { 0x58, 0x25, 0x2A }, + { 0x58, 0x26, 0x2C }, + { 0x58, 0x27, 0x2A }, + { 0x58, 0x28, 0x46 }, + { 0x58, 0x29, 0x2A }, + { 0x58, 0x2a, 0x26 }, + { 0x58, 0x2b, 0x24 }, + { 0x58, 0x2c, 0x26 }, + { 0x58, 0x2d, 0x2A }, + { 0x58, 0x2e, 0x28 }, + { 0x58, 0x2f, 0x42 }, + { 0x58, 0x30, 0x40 }, + { 0x58, 0x31, 0x42 }, + { 0x58, 0x32, 0x08 }, + { 0x58, 0x33, 0x28 }, + { 0x58, 0x34, 0x26 }, + { 0x58, 0x35, 0x24 }, + { 0x58, 0x36, 0x26 }, + { 0x58, 0x37, 0x2A }, + { 0x58, 0x38, 0x44 }, + { 0x58, 0x39, 0x4A }, + { 0x58, 0x3a, 0x2C }, + { 0x58, 0x3b, 0x2a }, + { 0x58, 0x3c, 0x46 }, + { 0x58, 0x3d, 0xCE }, + { 0x56, 0x88, 0x11 }, // { 0x56, 0x88, 0x22 }, + { 0x56, 0x89, 0x11 }, // { 0x56, 0x89, 0x22 }, + { 0x56, 0x8a, 0x11 }, // { 0x56, 0x8a, 0x42 }, + { 0x56, 0x8b, 0x11 }, // { 0x56, 0x8b, 0x24 }, + { 0x56, 0x8c, 0x11 }, // { 0x56, 0x8c, 0x42 }, + { 0x56, 0x8d, 0x11 }, // { 0x56, 0x8d, 0x24 }, + { 0x56, 0x8e, 0x11 }, // { 0x56, 0x8e, 0x22 }, + { 0x56, 0x8f, 0x11 }, // { 0x56, 0x8f, 0x22 }, + { 0x50, 0x25, 0x00 }, + { 0x3a, 0x0f, 0x42 }, // { 0x3a, 0x0f, 0x30 }, + { 0x3a, 0x10, 0x38 }, // { 0x3a, 0x10, 0x28 }, + { 0x3a, 0x1b, 0x44 }, // { 0x3a, 0x1b, 0x30 }, + { 0x3a, 0x1e, 0x36 }, // { 0x3a, 0x1e, 0x28 }, + { 0x3a, 0x11, 0x60 }, // { 0x3a, 0x11, 0x61 }, + { 0x3a, 0x1f, 0x10 }, + { 0x40, 0x05, 0x1a }, + { 0x34, 0x06, 0x00 }, + { 0x35, 0x03, 0x00 }, + { 0x30, 0x08, 0x02 }, + +// OpenMV Custom. + + { 0x3a, 0x02, 0x07 }, + { 0x3a, 0x03, 0xae }, + { 0x3a, 0x08, 0x01 }, + { 0x3a, 0x09, 0x27 }, + { 0x3a, 0x0a, 0x00 }, + { 0x3a, 0x0b, 0xf6 }, + { 0x3a, 0x0e, 0x06 }, + { 0x3a, 0x0d, 0x08 }, + { 0x3a, 0x14, 0x07 }, + { 0x3a, 0x15, 0xae }, + { 0x44, 0x01, 0x0d }, // | Read SRAM enable when blanking | Read SRAM at first blanking + { 0x47, 0x23, 0x03 }, // DVP JPEG Mode456 Skip Line Number + +// End. + + { 0x00, 0x00, 0x00 } +}; + +#if (OMV_ENABLE_OV5640_AF == 1) +static const uint8_t af_firmware_regs[] = { + 0x02, 0x0f, 0xd6, 0x02, 0x0a, 0x39, 0xc2, 0x01, 0x22, 0x22, 0x00, 0x02, 0x0f, 0xb2, 0xe5, 0x1f, + 0x70, 0x72, 0xf5, 0x1e, 0xd2, 0x35, 0xff, 0xef, 0x25, 0xe0, 0x24, 0x4e, 0xf8, 0xe4, 0xf6, 0x08, + 0xf6, 0x0f, 0xbf, 0x34, 0xf2, 0x90, 0x0e, 0x93, 0xe4, 0x93, 0xff, 0xe5, 0x4b, 0xc3, 0x9f, 0x50, + 0x04, 0x7f, 0x05, 0x80, 0x02, 0x7f, 0xfb, 0x78, 0xbd, 0xa6, 0x07, 0x12, 0x0f, 0x04, 0x40, 0x04, + 0x7f, 0x03, 0x80, 0x02, 0x7f, 0x30, 0x78, 0xbc, 0xa6, 0x07, 0xe6, 0x18, 0xf6, 0x08, 0xe6, 0x78, + 0xb9, 0xf6, 0x78, 0xbc, 0xe6, 0x78, 0xba, 0xf6, 0x78, 0xbf, 0x76, 0x33, 0xe4, 0x08, 0xf6, 0x78, + 0xb8, 0x76, 0x01, 0x75, 0x4a, 0x02, 0x78, 0xb6, 0xf6, 0x08, 0xf6, 0x74, 0xff, 0x78, 0xc1, 0xf6, + 0x08, 0xf6, 0x75, 0x1f, 0x01, 0x78, 0xbc, 0xe6, 0x75, 0xf0, 0x05, 0xa4, 0xf5, 0x4b, 0x12, 0x0a, + 0xff, 0xc2, 0x37, 0x22, 0x78, 0xb8, 0xe6, 0xd3, 0x94, 0x00, 0x40, 0x02, 0x16, 0x22, 0xe5, 0x1f, + 0xb4, 0x05, 0x23, 0xe4, 0xf5, 0x1f, 0xc2, 0x01, 0x78, 0xb6, 0xe6, 0xfe, 0x08, 0xe6, 0xff, 0x78, + 0x4e, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0xa2, 0x37, 0xe4, 0x33, 0xf5, 0x3c, 0x90, 0x30, 0x28, 0xf0, + 0x75, 0x1e, 0x10, 0xd2, 0x35, 0x22, 0xe5, 0x4b, 0x75, 0xf0, 0x05, 0x84, 0x78, 0xbc, 0xf6, 0x90, + 0x0e, 0x8c, 0xe4, 0x93, 0xff, 0x25, 0xe0, 0x24, 0x0a, 0xf8, 0xe6, 0xfc, 0x08, 0xe6, 0xfd, 0x78, + 0xbc, 0xe6, 0x25, 0xe0, 0x24, 0x4e, 0xf8, 0xa6, 0x04, 0x08, 0xa6, 0x05, 0xef, 0x12, 0x0f, 0x0b, + 0xd3, 0x78, 0xb7, 0x96, 0xee, 0x18, 0x96, 0x40, 0x0d, 0x78, 0xbc, 0xe6, 0x78, 0xb9, 0xf6, 0x78, + 0xb6, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x90, 0x0e, 0x8c, 0xe4, 0x93, 0x12, 0x0f, 0x0b, 0xc3, 0x78, + 0xc2, 0x96, 0xee, 0x18, 0x96, 0x50, 0x0d, 0x78, 0xbc, 0xe6, 0x78, 0xba, 0xf6, 0x78, 0xc1, 0xa6, + 0x06, 0x08, 0xa6, 0x07, 0x78, 0xb6, 0xe6, 0xfe, 0x08, 0xe6, 0xc3, 0x78, 0xc2, 0x96, 0xff, 0xee, + 0x18, 0x96, 0x78, 0xc3, 0xf6, 0x08, 0xa6, 0x07, 0x90, 0x0e, 0x95, 0xe4, 0x18, 0x12, 0x0e, 0xe9, + 0x40, 0x02, 0xd2, 0x37, 0x78, 0xbc, 0xe6, 0x08, 0x26, 0x08, 0xf6, 0xe5, 0x1f, 0x64, 0x01, 0x70, + 0x4a, 0xe6, 0xc3, 0x78, 0xc0, 0x12, 0x0e, 0xdf, 0x40, 0x05, 0x12, 0x0e, 0xda, 0x40, 0x39, 0x12, + 0x0f, 0x02, 0x40, 0x04, 0x7f, 0xfe, 0x80, 0x02, 0x7f, 0x02, 0x78, 0xbd, 0xa6, 0x07, 0x78, 0xb9, + 0xe6, 0x24, 0x03, 0x78, 0xbf, 0xf6, 0x78, 0xb9, 0xe6, 0x24, 0xfd, 0x78, 0xc0, 0xf6, 0x12, 0x0f, + 0x02, 0x40, 0x06, 0x78, 0xc0, 0xe6, 0xff, 0x80, 0x04, 0x78, 0xbf, 0xe6, 0xff, 0x78, 0xbe, 0xa6, + 0x07, 0x75, 0x1f, 0x02, 0x78, 0xb8, 0x76, 0x01, 0x02, 0x02, 0x4a, 0xe5, 0x1f, 0x64, 0x02, 0x60, + 0x03, 0x02, 0x02, 0x2a, 0x78, 0xbe, 0xe6, 0xff, 0xc3, 0x78, 0xc0, 0x12, 0x0e, 0xe0, 0x40, 0x08, + 0x12, 0x0e, 0xda, 0x50, 0x03, 0x02, 0x02, 0x28, 0x12, 0x0f, 0x02, 0x40, 0x04, 0x7f, 0xff, 0x80, + 0x02, 0x7f, 0x01, 0x78, 0xbd, 0xa6, 0x07, 0x78, 0xb9, 0xe6, 0x04, 0x78, 0xbf, 0xf6, 0x78, 0xb9, + 0xe6, 0x14, 0x78, 0xc0, 0xf6, 0x18, 0x12, 0x0f, 0x04, 0x40, 0x04, 0xe6, 0xff, 0x80, 0x02, 0x7f, + 0x00, 0x78, 0xbf, 0xa6, 0x07, 0xd3, 0x08, 0xe6, 0x64, 0x80, 0x94, 0x80, 0x40, 0x04, 0xe6, 0xff, + 0x80, 0x02, 0x7f, 0x00, 0x78, 0xc0, 0xa6, 0x07, 0xc3, 0x18, 0xe6, 0x64, 0x80, 0x94, 0xb3, 0x50, + 0x04, 0xe6, 0xff, 0x80, 0x02, 0x7f, 0x33, 0x78, 0xbf, 0xa6, 0x07, 0xc3, 0x08, 0xe6, 0x64, 0x80, + 0x94, 0xb3, 0x50, 0x04, 0xe6, 0xff, 0x80, 0x02, 0x7f, 0x33, 0x78, 0xc0, 0xa6, 0x07, 0x12, 0x0f, + 0x02, 0x40, 0x06, 0x78, 0xc0, 0xe6, 0xff, 0x80, 0x04, 0x78, 0xbf, 0xe6, 0xff, 0x78, 0xbe, 0xa6, + 0x07, 0x75, 0x1f, 0x03, 0x78, 0xb8, 0x76, 0x01, 0x80, 0x20, 0xe5, 0x1f, 0x64, 0x03, 0x70, 0x26, + 0x78, 0xbe, 0xe6, 0xff, 0xc3, 0x78, 0xc0, 0x12, 0x0e, 0xe0, 0x40, 0x05, 0x12, 0x0e, 0xda, 0x40, + 0x09, 0x78, 0xb9, 0xe6, 0x78, 0xbe, 0xf6, 0x75, 0x1f, 0x04, 0x78, 0xbe, 0xe6, 0x75, 0xf0, 0x05, + 0xa4, 0xf5, 0x4b, 0x02, 0x0a, 0xff, 0xe5, 0x1f, 0xb4, 0x04, 0x10, 0x90, 0x0e, 0x94, 0xe4, 0x78, + 0xc3, 0x12, 0x0e, 0xe9, 0x40, 0x02, 0xd2, 0x37, 0x75, 0x1f, 0x05, 0x22, 0x30, 0x01, 0x03, 0x02, + 0x04, 0xc0, 0x30, 0x02, 0x03, 0x02, 0x04, 0xc0, 0x90, 0x51, 0xa5, 0xe0, 0x78, 0x93, 0xf6, 0xa3, + 0xe0, 0x08, 0xf6, 0xa3, 0xe0, 0x08, 0xf6, 0xe5, 0x1f, 0x70, 0x3c, 0x75, 0x1e, 0x20, 0xd2, 0x35, + 0x12, 0x0c, 0x7a, 0x78, 0x7e, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x78, 0x8b, 0xa6, 0x09, 0x18, 0x76, + 0x01, 0x12, 0x0c, 0x5b, 0x78, 0x4e, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x78, 0x8b, 0xe6, 0x78, 0x6e, + 0xf6, 0x75, 0x1f, 0x01, 0x78, 0x93, 0xe6, 0x78, 0x90, 0xf6, 0x78, 0x94, 0xe6, 0x78, 0x91, 0xf6, + 0x78, 0x95, 0xe6, 0x78, 0x92, 0xf6, 0x22, 0x79, 0x90, 0xe7, 0xd3, 0x78, 0x93, 0x96, 0x40, 0x05, + 0xe7, 0x96, 0xff, 0x80, 0x08, 0xc3, 0x79, 0x93, 0xe7, 0x78, 0x90, 0x96, 0xff, 0x78, 0x88, 0x76, + 0x00, 0x08, 0xa6, 0x07, 0x79, 0x91, 0xe7, 0xd3, 0x78, 0x94, 0x96, 0x40, 0x05, 0xe7, 0x96, 0xff, + 0x80, 0x08, 0xc3, 0x79, 0x94, 0xe7, 0x78, 0x91, 0x96, 0xff, 0x12, 0x0c, 0x8e, 0x79, 0x92, 0xe7, + 0xd3, 0x78, 0x95, 0x96, 0x40, 0x05, 0xe7, 0x96, 0xff, 0x80, 0x08, 0xc3, 0x79, 0x95, 0xe7, 0x78, + 0x92, 0x96, 0xff, 0x12, 0x0c, 0x8e, 0x12, 0x0c, 0x5b, 0x78, 0x8a, 0xe6, 0x25, 0xe0, 0x24, 0x4e, + 0xf8, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x78, 0x8a, 0xe6, 0x24, 0x6e, 0xf8, 0xa6, 0x09, 0x78, 0x8a, + 0xe6, 0x24, 0x01, 0xff, 0xe4, 0x33, 0xfe, 0xd3, 0xef, 0x94, 0x0f, 0xee, 0x64, 0x80, 0x94, 0x80, + 0x40, 0x04, 0x7f, 0x00, 0x80, 0x05, 0x78, 0x8a, 0xe6, 0x04, 0xff, 0x78, 0x8a, 0xa6, 0x07, 0xe5, + 0x1f, 0xb4, 0x01, 0x0a, 0xe6, 0x60, 0x03, 0x02, 0x04, 0xc0, 0x75, 0x1f, 0x02, 0x22, 0x12, 0x0c, + 0x7a, 0x78, 0x80, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x12, 0x0c, 0x7a, 0x78, 0x82, 0xa6, 0x06, 0x08, + 0xa6, 0x07, 0x78, 0x6e, 0xe6, 0x78, 0x8c, 0xf6, 0x78, 0x6e, 0xe6, 0x78, 0x8d, 0xf6, 0x7f, 0x01, + 0xef, 0x25, 0xe0, 0x24, 0x4f, 0xf9, 0xc3, 0x78, 0x81, 0xe6, 0x97, 0x18, 0xe6, 0x19, 0x97, 0x50, + 0x0a, 0x12, 0x0c, 0x82, 0x78, 0x80, 0xa6, 0x04, 0x08, 0xa6, 0x05, 0x74, 0x6e, 0x2f, 0xf9, 0x78, + 0x8c, 0xe6, 0xc3, 0x97, 0x50, 0x08, 0x74, 0x6e, 0x2f, 0xf8, 0xe6, 0x78, 0x8c, 0xf6, 0xef, 0x25, + 0xe0, 0x24, 0x4f, 0xf9, 0xd3, 0x78, 0x83, 0xe6, 0x97, 0x18, 0xe6, 0x19, 0x97, 0x40, 0x0a, 0x12, + 0x0c, 0x82, 0x78, 0x82, 0xa6, 0x04, 0x08, 0xa6, 0x05, 0x74, 0x6e, 0x2f, 0xf9, 0x78, 0x8d, 0xe6, + 0xd3, 0x97, 0x40, 0x08, 0x74, 0x6e, 0x2f, 0xf8, 0xe6, 0x78, 0x8d, 0xf6, 0x0f, 0xef, 0x64, 0x10, + 0x70, 0x9e, 0xc3, 0x79, 0x81, 0xe7, 0x78, 0x83, 0x96, 0xff, 0x19, 0xe7, 0x18, 0x96, 0x78, 0x84, + 0xf6, 0x08, 0xa6, 0x07, 0xc3, 0x79, 0x8c, 0xe7, 0x78, 0x8d, 0x96, 0x08, 0xf6, 0xd3, 0x79, 0x81, + 0xe7, 0x78, 0x7f, 0x96, 0x19, 0xe7, 0x18, 0x96, 0x40, 0x05, 0x09, 0xe7, 0x08, 0x80, 0x06, 0xc3, + 0x79, 0x7f, 0xe7, 0x78, 0x81, 0x96, 0xff, 0x19, 0xe7, 0x18, 0x96, 0xfe, 0x78, 0x86, 0xa6, 0x06, + 0x08, 0xa6, 0x07, 0x79, 0x8c, 0xe7, 0xd3, 0x78, 0x8b, 0x96, 0x40, 0x05, 0xe7, 0x96, 0xff, 0x80, + 0x08, 0xc3, 0x79, 0x8b, 0xe7, 0x78, 0x8c, 0x96, 0xff, 0x78, 0x8f, 0xa6, 0x07, 0xe5, 0x1f, 0x64, + 0x02, 0x70, 0x69, 0x90, 0x0e, 0x91, 0x93, 0xff, 0x18, 0xe6, 0xc3, 0x9f, 0x50, 0x72, 0x12, 0x0c, + 0x4a, 0x12, 0x0c, 0x2f, 0x90, 0x0e, 0x8e, 0x12, 0x0c, 0x38, 0x78, 0x80, 0x12, 0x0c, 0x6b, 0x7b, + 0x04, 0x12, 0x0c, 0x1d, 0xc3, 0x12, 0x06, 0x45, 0x50, 0x56, 0x90, 0x0e, 0x92, 0xe4, 0x93, 0xff, + 0x78, 0x8f, 0xe6, 0x9f, 0x40, 0x02, 0x80, 0x11, 0x90, 0x0e, 0x90, 0xe4, 0x93, 0xff, 0xd3, 0x78, + 0x89, 0xe6, 0x9f, 0x18, 0xe6, 0x94, 0x00, 0x40, 0x03, 0x75, 0x1f, 0x05, 0x12, 0x0c, 0x4a, 0x12, + 0x0c, 0x2f, 0x90, 0x0e, 0x8f, 0x12, 0x0c, 0x38, 0x78, 0x7e, 0x12, 0x0c, 0x6b, 0x7b, 0x40, 0x12, + 0x0c, 0x1d, 0xd3, 0x12, 0x06, 0x45, 0x40, 0x18, 0x75, 0x1f, 0x05, 0x22, 0xe5, 0x1f, 0xb4, 0x05, + 0x0f, 0xd2, 0x01, 0xc2, 0x02, 0xe4, 0xf5, 0x1f, 0xf5, 0x1e, 0xd2, 0x35, 0xd2, 0x33, 0xd2, 0x36, + 0x22, 0xef, 0x8d, 0xf0, 0xa4, 0xa8, 0xf0, 0xcf, 0x8c, 0xf0, 0xa4, 0x28, 0xce, 0x8d, 0xf0, 0xa4, + 0x2e, 0xfe, 0x22, 0xbc, 0x00, 0x0b, 0xbe, 0x00, 0x29, 0xef, 0x8d, 0xf0, 0x84, 0xff, 0xad, 0xf0, + 0x22, 0xe4, 0xcc, 0xf8, 0x75, 0xf0, 0x08, 0xef, 0x2f, 0xff, 0xee, 0x33, 0xfe, 0xec, 0x33, 0xfc, + 0xee, 0x9d, 0xec, 0x98, 0x40, 0x05, 0xfc, 0xee, 0x9d, 0xfe, 0x0f, 0xd5, 0xf0, 0xe9, 0xe4, 0xce, + 0xfd, 0x22, 0xed, 0xf8, 0xf5, 0xf0, 0xee, 0x84, 0x20, 0xd2, 0x1c, 0xfe, 0xad, 0xf0, 0x75, 0xf0, + 0x08, 0xef, 0x2f, 0xff, 0xed, 0x33, 0xfd, 0x40, 0x07, 0x98, 0x50, 0x06, 0xd5, 0xf0, 0xf2, 0x22, + 0xc3, 0x98, 0xfd, 0x0f, 0xd5, 0xf0, 0xea, 0x22, 0xe8, 0x8f, 0xf0, 0xa4, 0xcc, 0x8b, 0xf0, 0xa4, + 0x2c, 0xfc, 0xe9, 0x8e, 0xf0, 0xa4, 0x2c, 0xfc, 0x8a, 0xf0, 0xed, 0xa4, 0x2c, 0xfc, 0xea, 0x8e, + 0xf0, 0xa4, 0xcd, 0xa8, 0xf0, 0x8b, 0xf0, 0xa4, 0x2d, 0xcc, 0x38, 0x25, 0xf0, 0xfd, 0xe9, 0x8f, + 0xf0, 0xa4, 0x2c, 0xcd, 0x35, 0xf0, 0xfc, 0xeb, 0x8e, 0xf0, 0xa4, 0xfe, 0xa9, 0xf0, 0xeb, 0x8f, + 0xf0, 0xa4, 0xcf, 0xc5, 0xf0, 0x2e, 0xcd, 0x39, 0xfe, 0xe4, 0x3c, 0xfc, 0xea, 0xa4, 0x2d, 0xce, + 0x35, 0xf0, 0xfd, 0xe4, 0x3c, 0xfc, 0x22, 0x75, 0xf0, 0x08, 0x75, 0x82, 0x00, 0xef, 0x2f, 0xff, + 0xee, 0x33, 0xfe, 0xcd, 0x33, 0xcd, 0xcc, 0x33, 0xcc, 0xc5, 0x82, 0x33, 0xc5, 0x82, 0x9b, 0xed, + 0x9a, 0xec, 0x99, 0xe5, 0x82, 0x98, 0x40, 0x0c, 0xf5, 0x82, 0xee, 0x9b, 0xfe, 0xed, 0x9a, 0xfd, + 0xec, 0x99, 0xfc, 0x0f, 0xd5, 0xf0, 0xd6, 0xe4, 0xce, 0xfb, 0xe4, 0xcd, 0xfa, 0xe4, 0xcc, 0xf9, + 0xa8, 0x82, 0x22, 0xb8, 0x00, 0xc1, 0xb9, 0x00, 0x59, 0xba, 0x00, 0x2d, 0xec, 0x8b, 0xf0, 0x84, + 0xcf, 0xce, 0xcd, 0xfc, 0xe5, 0xf0, 0xcb, 0xf9, 0x78, 0x18, 0xef, 0x2f, 0xff, 0xee, 0x33, 0xfe, + 0xed, 0x33, 0xfd, 0xec, 0x33, 0xfc, 0xeb, 0x33, 0xfb, 0x10, 0xd7, 0x03, 0x99, 0x40, 0x04, 0xeb, + 0x99, 0xfb, 0x0f, 0xd8, 0xe5, 0xe4, 0xf9, 0xfa, 0x22, 0x78, 0x18, 0xef, 0x2f, 0xff, 0xee, 0x33, + 0xfe, 0xed, 0x33, 0xfd, 0xec, 0x33, 0xfc, 0xc9, 0x33, 0xc9, 0x10, 0xd7, 0x05, 0x9b, 0xe9, 0x9a, + 0x40, 0x07, 0xec, 0x9b, 0xfc, 0xe9, 0x9a, 0xf9, 0x0f, 0xd8, 0xe0, 0xe4, 0xc9, 0xfa, 0xe4, 0xcc, + 0xfb, 0x22, 0x75, 0xf0, 0x10, 0xef, 0x2f, 0xff, 0xee, 0x33, 0xfe, 0xed, 0x33, 0xfd, 0xcc, 0x33, + 0xcc, 0xc8, 0x33, 0xc8, 0x10, 0xd7, 0x07, 0x9b, 0xec, 0x9a, 0xe8, 0x99, 0x40, 0x0a, 0xed, 0x9b, + 0xfd, 0xec, 0x9a, 0xfc, 0xe8, 0x99, 0xf8, 0x0f, 0xd5, 0xf0, 0xda, 0xe4, 0xcd, 0xfb, 0xe4, 0xcc, + 0xfa, 0xe4, 0xc8, 0xf9, 0x22, 0xeb, 0x9f, 0xf5, 0xf0, 0xea, 0x9e, 0x42, 0xf0, 0xe9, 0x9d, 0x42, + 0xf0, 0xe8, 0x9c, 0x45, 0xf0, 0x22, 0xe8, 0x60, 0x0f, 0xec, 0xc3, 0x13, 0xfc, 0xed, 0x13, 0xfd, + 0xee, 0x13, 0xfe, 0xef, 0x13, 0xff, 0xd8, 0xf1, 0x22, 0xe8, 0x60, 0x0f, 0xef, 0xc3, 0x33, 0xff, + 0xee, 0x33, 0xfe, 0xed, 0x33, 0xfd, 0xec, 0x33, 0xfc, 0xd8, 0xf1, 0x22, 0xe4, 0x93, 0xfc, 0x74, + 0x01, 0x93, 0xfd, 0x74, 0x02, 0x93, 0xfe, 0x74, 0x03, 0x93, 0xff, 0x22, 0xe6, 0xfb, 0x08, 0xe6, + 0xf9, 0x08, 0xe6, 0xfa, 0x08, 0xe6, 0xcb, 0xf8, 0x22, 0xec, 0xf6, 0x08, 0xed, 0xf6, 0x08, 0xee, + 0xf6, 0x08, 0xef, 0xf6, 0x22, 0xa4, 0x25, 0x82, 0xf5, 0x82, 0xe5, 0xf0, 0x35, 0x83, 0xf5, 0x83, + 0x22, 0xd0, 0x83, 0xd0, 0x82, 0xf8, 0xe4, 0x93, 0x70, 0x12, 0x74, 0x01, 0x93, 0x70, 0x0d, 0xa3, + 0xa3, 0x93, 0xf8, 0x74, 0x01, 0x93, 0xf5, 0x82, 0x88, 0x83, 0xe4, 0x73, 0x74, 0x02, 0x93, 0x68, + 0x60, 0xef, 0xa3, 0xa3, 0xa3, 0x80, 0xdf, 0x90, 0x38, 0x04, 0x78, 0x52, 0x12, 0x0b, 0xfd, 0x90, + 0x38, 0x00, 0xe0, 0xfe, 0xa3, 0xe0, 0xfd, 0xed, 0xff, 0xc3, 0x12, 0x0b, 0x9e, 0x90, 0x38, 0x10, + 0x12, 0x0b, 0x92, 0x90, 0x38, 0x06, 0x78, 0x54, 0x12, 0x0b, 0xfd, 0x90, 0x38, 0x02, 0xe0, 0xfe, + 0xa3, 0xe0, 0xfd, 0xed, 0xff, 0xc3, 0x12, 0x0b, 0x9e, 0x90, 0x38, 0x12, 0x12, 0x0b, 0x92, 0xa3, + 0xe0, 0xb4, 0x31, 0x07, 0x78, 0x52, 0x79, 0x52, 0x12, 0x0c, 0x13, 0x90, 0x38, 0x14, 0xe0, 0xb4, + 0x71, 0x15, 0x78, 0x52, 0xe6, 0xfe, 0x08, 0xe6, 0x78, 0x02, 0xce, 0xc3, 0x13, 0xce, 0x13, 0xd8, + 0xf9, 0x79, 0x53, 0xf7, 0xee, 0x19, 0xf7, 0x90, 0x38, 0x15, 0xe0, 0xb4, 0x31, 0x07, 0x78, 0x54, + 0x79, 0x54, 0x12, 0x0c, 0x13, 0x90, 0x38, 0x15, 0xe0, 0xb4, 0x71, 0x15, 0x78, 0x54, 0xe6, 0xfe, + 0x08, 0xe6, 0x78, 0x02, 0xce, 0xc3, 0x13, 0xce, 0x13, 0xd8, 0xf9, 0x79, 0x55, 0xf7, 0xee, 0x19, + 0xf7, 0x79, 0x52, 0x12, 0x0b, 0xd9, 0x09, 0x12, 0x0b, 0xd9, 0xaf, 0x47, 0x12, 0x0b, 0xb2, 0xe5, + 0x44, 0xfb, 0x7a, 0x00, 0xfd, 0x7c, 0x00, 0x12, 0x04, 0xd3, 0x78, 0x5a, 0xa6, 0x06, 0x08, 0xa6, + 0x07, 0xaf, 0x45, 0x12, 0x0b, 0xb2, 0xad, 0x03, 0x7c, 0x00, 0x12, 0x04, 0xd3, 0x78, 0x56, 0xa6, + 0x06, 0x08, 0xa6, 0x07, 0xaf, 0x48, 0x78, 0x54, 0x12, 0x0b, 0xb4, 0xe5, 0x43, 0xfb, 0xfd, 0x7c, + 0x00, 0x12, 0x04, 0xd3, 0x78, 0x5c, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0xaf, 0x46, 0x7e, 0x00, 0x78, + 0x54, 0x12, 0x0b, 0xb6, 0xad, 0x03, 0x7c, 0x00, 0x12, 0x04, 0xd3, 0x78, 0x58, 0xa6, 0x06, 0x08, + 0xa6, 0x07, 0xc3, 0x78, 0x5b, 0xe6, 0x94, 0x08, 0x18, 0xe6, 0x94, 0x00, 0x50, 0x05, 0x76, 0x00, + 0x08, 0x76, 0x08, 0xc3, 0x78, 0x5d, 0xe6, 0x94, 0x08, 0x18, 0xe6, 0x94, 0x00, 0x50, 0x05, 0x76, + 0x00, 0x08, 0x76, 0x08, 0x78, 0x5a, 0x12, 0x0b, 0xc6, 0xff, 0xd3, 0x78, 0x57, 0xe6, 0x9f, 0x18, + 0xe6, 0x9e, 0x40, 0x0e, 0x78, 0x5a, 0xe6, 0x13, 0xfe, 0x08, 0xe6, 0x78, 0x57, 0x12, 0x0c, 0x08, + 0x80, 0x04, 0x7e, 0x00, 0x7f, 0x00, 0x78, 0x5e, 0x12, 0x0b, 0xbe, 0xff, 0xd3, 0x78, 0x59, 0xe6, + 0x9f, 0x18, 0xe6, 0x9e, 0x40, 0x0e, 0x78, 0x5c, 0xe6, 0x13, 0xfe, 0x08, 0xe6, 0x78, 0x59, 0x12, + 0x0c, 0x08, 0x80, 0x04, 0x7e, 0x00, 0x7f, 0x00, 0xe4, 0xfc, 0xfd, 0x78, 0x62, 0x12, 0x06, 0x99, + 0x78, 0x5a, 0x12, 0x0b, 0xc6, 0x78, 0x57, 0x26, 0xff, 0xee, 0x18, 0x36, 0xfe, 0x78, 0x66, 0x12, + 0x0b, 0xbe, 0x78, 0x59, 0x26, 0xff, 0xee, 0x18, 0x36, 0xfe, 0xe4, 0xfc, 0xfd, 0x78, 0x6a, 0x12, + 0x06, 0x99, 0x12, 0x0b, 0xce, 0x78, 0x66, 0x12, 0x06, 0x8c, 0xd3, 0x12, 0x06, 0x45, 0x40, 0x08, + 0x12, 0x0b, 0xce, 0x78, 0x66, 0x12, 0x06, 0x99, 0x78, 0x54, 0x12, 0x0b, 0xd0, 0x78, 0x6a, 0x12, + 0x06, 0x8c, 0xd3, 0x12, 0x06, 0x45, 0x40, 0x0a, 0x78, 0x54, 0x12, 0x0b, 0xd0, 0x78, 0x6a, 0x12, + 0x06, 0x99, 0x78, 0x61, 0xe6, 0x90, 0x60, 0x01, 0xf0, 0x78, 0x65, 0xe6, 0xa3, 0xf0, 0x78, 0x69, + 0xe6, 0xa3, 0xf0, 0x78, 0x55, 0xe6, 0xa3, 0xf0, 0x7d, 0x01, 0x78, 0x61, 0x12, 0x0b, 0xe9, 0x24, + 0x01, 0x12, 0x0b, 0xa6, 0x78, 0x65, 0x12, 0x0b, 0xe9, 0x24, 0x02, 0x12, 0x0b, 0xa6, 0x78, 0x69, + 0x12, 0x0b, 0xe9, 0x24, 0x03, 0x12, 0x0b, 0xa6, 0x78, 0x6d, 0x12, 0x0b, 0xe9, 0x24, 0x04, 0x12, + 0x0b, 0xa6, 0x0d, 0xbd, 0x05, 0xd4, 0xc2, 0x0e, 0xc2, 0x06, 0x22, 0x85, 0x08, 0x41, 0x90, 0x30, + 0x24, 0xe0, 0xf5, 0x3d, 0xa3, 0xe0, 0xf5, 0x3e, 0xa3, 0xe0, 0xf5, 0x3f, 0xa3, 0xe0, 0xf5, 0x40, + 0xa3, 0xe0, 0xf5, 0x3c, 0xd2, 0x34, 0xe5, 0x41, 0x12, 0x06, 0xb1, 0x09, 0x31, 0x03, 0x09, 0x35, + 0x04, 0x09, 0x3b, 0x05, 0x09, 0x3e, 0x06, 0x09, 0x41, 0x07, 0x09, 0x4a, 0x08, 0x09, 0x5b, 0x12, + 0x09, 0x73, 0x18, 0x09, 0x89, 0x19, 0x09, 0x5e, 0x1a, 0x09, 0x6a, 0x1b, 0x09, 0xad, 0x80, 0x09, + 0xb2, 0x81, 0x0a, 0x1d, 0x8f, 0x0a, 0x09, 0x90, 0x0a, 0x1d, 0x91, 0x0a, 0x1d, 0x92, 0x0a, 0x1d, + 0x93, 0x0a, 0x1d, 0x94, 0x0a, 0x1d, 0x98, 0x0a, 0x17, 0x9f, 0x0a, 0x1a, 0xec, 0x00, 0x00, 0x0a, + 0x38, 0x12, 0x0f, 0x74, 0x22, 0x12, 0x0f, 0x74, 0xd2, 0x03, 0x22, 0xd2, 0x03, 0x22, 0xc2, 0x03, + 0x22, 0xa2, 0x37, 0xe4, 0x33, 0xf5, 0x3c, 0x02, 0x0a, 0x1d, 0xc2, 0x01, 0xc2, 0x02, 0xc2, 0x03, + 0x12, 0x0d, 0x0d, 0x75, 0x1e, 0x70, 0xd2, 0x35, 0x02, 0x0a, 0x1d, 0x02, 0x0a, 0x04, 0x85, 0x40, + 0x4a, 0x85, 0x3c, 0x4b, 0x12, 0x0a, 0xff, 0x02, 0x0a, 0x1d, 0x85, 0x4a, 0x40, 0x85, 0x4b, 0x3c, + 0x02, 0x0a, 0x1d, 0xe4, 0xf5, 0x22, 0xf5, 0x23, 0x85, 0x40, 0x31, 0x85, 0x3f, 0x30, 0x85, 0x3e, + 0x2f, 0x85, 0x3d, 0x2e, 0x12, 0x0f, 0x46, 0x80, 0x1f, 0x75, 0x22, 0x00, 0x75, 0x23, 0x01, 0x74, + 0xff, 0xf5, 0x2d, 0xf5, 0x2c, 0xf5, 0x2b, 0xf5, 0x2a, 0x12, 0x0f, 0x46, 0x85, 0x2d, 0x40, 0x85, + 0x2c, 0x3f, 0x85, 0x2b, 0x3e, 0x85, 0x2a, 0x3d, 0xe4, 0xf5, 0x3c, 0x80, 0x70, 0x12, 0x0f, 0x16, + 0x80, 0x6b, 0x85, 0x3d, 0x45, 0x85, 0x3e, 0x46, 0xe5, 0x47, 0xc3, 0x13, 0xff, 0xe5, 0x45, 0xc3, + 0x9f, 0x50, 0x02, 0x8f, 0x45, 0xe5, 0x48, 0xc3, 0x13, 0xff, 0xe5, 0x46, 0xc3, 0x9f, 0x50, 0x02, + 0x8f, 0x46, 0xe5, 0x47, 0xc3, 0x13, 0xff, 0xfd, 0xe5, 0x45, 0x2d, 0xfd, 0xe4, 0x33, 0xfc, 0xe5, + 0x44, 0x12, 0x0f, 0x90, 0x40, 0x05, 0xe5, 0x44, 0x9f, 0xf5, 0x45, 0xe5, 0x48, 0xc3, 0x13, 0xff, + 0xfd, 0xe5, 0x46, 0x2d, 0xfd, 0xe4, 0x33, 0xfc, 0xe5, 0x43, 0x12, 0x0f, 0x90, 0x40, 0x05, 0xe5, + 0x43, 0x9f, 0xf5, 0x46, 0x12, 0x06, 0xd7, 0x80, 0x14, 0x85, 0x40, 0x48, 0x85, 0x3f, 0x47, 0x85, + 0x3e, 0x46, 0x85, 0x3d, 0x45, 0x80, 0x06, 0x02, 0x06, 0xd7, 0x12, 0x0d, 0x7e, 0x90, 0x30, 0x24, + 0xe5, 0x3d, 0xf0, 0xa3, 0xe5, 0x3e, 0xf0, 0xa3, 0xe5, 0x3f, 0xf0, 0xa3, 0xe5, 0x40, 0xf0, 0xa3, + 0xe5, 0x3c, 0xf0, 0x90, 0x30, 0x23, 0xe4, 0xf0, 0x22, 0xc0, 0xe0, 0xc0, 0x83, 0xc0, 0x82, 0xc0, + 0xd0, 0x90, 0x3f, 0x0c, 0xe0, 0xf5, 0x32, 0xe5, 0x32, 0x30, 0xe3, 0x74, 0x30, 0x36, 0x66, 0x90, + 0x60, 0x19, 0xe0, 0xf5, 0x0a, 0xa3, 0xe0, 0xf5, 0x0b, 0x90, 0x60, 0x1d, 0xe0, 0xf5, 0x14, 0xa3, + 0xe0, 0xf5, 0x15, 0x90, 0x60, 0x21, 0xe0, 0xf5, 0x0c, 0xa3, 0xe0, 0xf5, 0x0d, 0x90, 0x60, 0x29, + 0xe0, 0xf5, 0x0e, 0xa3, 0xe0, 0xf5, 0x0f, 0x90, 0x60, 0x31, 0xe0, 0xf5, 0x10, 0xa3, 0xe0, 0xf5, + 0x11, 0x90, 0x60, 0x39, 0xe0, 0xf5, 0x12, 0xa3, 0xe0, 0xf5, 0x13, 0x30, 0x01, 0x06, 0x30, 0x33, + 0x03, 0xd3, 0x80, 0x01, 0xc3, 0x92, 0x09, 0x30, 0x02, 0x06, 0x30, 0x33, 0x03, 0xd3, 0x80, 0x01, + 0xc3, 0x92, 0x0a, 0x30, 0x33, 0x0c, 0x30, 0x03, 0x09, 0x20, 0x02, 0x06, 0x20, 0x01, 0x03, 0xd3, + 0x80, 0x01, 0xc3, 0x92, 0x0b, 0x90, 0x30, 0x01, 0xe0, 0x44, 0x40, 0xf0, 0xe0, 0x54, 0xbf, 0xf0, + 0xe5, 0x32, 0x30, 0xe1, 0x14, 0x30, 0x34, 0x11, 0x90, 0x30, 0x22, 0xe0, 0xf5, 0x08, 0xe4, 0xf0, + 0x30, 0x00, 0x03, 0xd3, 0x80, 0x01, 0xc3, 0x92, 0x08, 0xe5, 0x32, 0x30, 0xe5, 0x12, 0x90, 0x56, + 0xa1, 0xe0, 0xf5, 0x09, 0x30, 0x31, 0x09, 0x30, 0x05, 0x03, 0xd3, 0x80, 0x01, 0xc3, 0x92, 0x0d, + 0x90, 0x3f, 0x0c, 0xe5, 0x32, 0xf0, 0xd0, 0xd0, 0xd0, 0x82, 0xd0, 0x83, 0xd0, 0xe0, 0x32, 0x90, + 0x0e, 0x7e, 0xe4, 0x93, 0xfe, 0x74, 0x01, 0x93, 0xff, 0xc3, 0x90, 0x0e, 0x7c, 0x74, 0x01, 0x93, + 0x9f, 0xff, 0xe4, 0x93, 0x9e, 0xfe, 0xe4, 0x8f, 0x3b, 0x8e, 0x3a, 0xf5, 0x39, 0xf5, 0x38, 0xab, + 0x3b, 0xaa, 0x3a, 0xa9, 0x39, 0xa8, 0x38, 0xaf, 0x4b, 0xfc, 0xfd, 0xfe, 0x12, 0x05, 0x28, 0x12, + 0x0d, 0xe1, 0xe4, 0x7b, 0xff, 0xfa, 0xf9, 0xf8, 0x12, 0x05, 0xb3, 0x12, 0x0d, 0xe1, 0x90, 0x0e, + 0x69, 0xe4, 0x12, 0x0d, 0xf6, 0x12, 0x0d, 0xe1, 0xe4, 0x85, 0x4a, 0x37, 0xf5, 0x36, 0xf5, 0x35, + 0xf5, 0x34, 0xaf, 0x37, 0xae, 0x36, 0xad, 0x35, 0xac, 0x34, 0xa3, 0x12, 0x0d, 0xf6, 0x8f, 0x37, + 0x8e, 0x36, 0x8d, 0x35, 0x8c, 0x34, 0xe5, 0x3b, 0x45, 0x37, 0xf5, 0x3b, 0xe5, 0x3a, 0x45, 0x36, + 0xf5, 0x3a, 0xe5, 0x39, 0x45, 0x35, 0xf5, 0x39, 0xe5, 0x38, 0x45, 0x34, 0xf5, 0x38, 0xe4, 0xf5, + 0x22, 0xf5, 0x23, 0x85, 0x3b, 0x31, 0x85, 0x3a, 0x30, 0x85, 0x39, 0x2f, 0x85, 0x38, 0x2e, 0x02, + 0x0f, 0x46, 0xe0, 0xa3, 0xe0, 0x75, 0xf0, 0x02, 0xa4, 0xff, 0xae, 0xf0, 0xc3, 0x08, 0xe6, 0x9f, + 0xf6, 0x18, 0xe6, 0x9e, 0xf6, 0x22, 0xff, 0xe5, 0xf0, 0x34, 0x60, 0x8f, 0x82, 0xf5, 0x83, 0xec, + 0xf0, 0x22, 0x78, 0x52, 0x7e, 0x00, 0xe6, 0xfc, 0x08, 0xe6, 0xfd, 0x02, 0x04, 0xc1, 0xe4, 0xfc, + 0xfd, 0x12, 0x06, 0x99, 0x78, 0x5c, 0xe6, 0xc3, 0x13, 0xfe, 0x08, 0xe6, 0x13, 0x22, 0x78, 0x52, + 0xe6, 0xfe, 0x08, 0xe6, 0xff, 0xe4, 0xfc, 0xfd, 0x22, 0xe7, 0xc4, 0xf8, 0x54, 0xf0, 0xc8, 0x68, + 0xf7, 0x09, 0xe7, 0xc4, 0x54, 0x0f, 0x48, 0xf7, 0x22, 0xe6, 0xfc, 0xed, 0x75, 0xf0, 0x04, 0xa4, + 0x22, 0x12, 0x06, 0x7c, 0x8f, 0x48, 0x8e, 0x47, 0x8d, 0x46, 0x8c, 0x45, 0x22, 0xe0, 0xfe, 0xa3, + 0xe0, 0xfd, 0xee, 0xf6, 0xed, 0x08, 0xf6, 0x22, 0x13, 0xff, 0xc3, 0xe6, 0x9f, 0xff, 0x18, 0xe6, + 0x9e, 0xfe, 0x22, 0xe6, 0xc3, 0x13, 0xf7, 0x08, 0xe6, 0x13, 0x09, 0xf7, 0x22, 0xad, 0x39, 0xac, + 0x38, 0xfa, 0xf9, 0xf8, 0x12, 0x05, 0x28, 0x8f, 0x3b, 0x8e, 0x3a, 0x8d, 0x39, 0x8c, 0x38, 0xab, + 0x37, 0xaa, 0x36, 0xa9, 0x35, 0xa8, 0x34, 0x22, 0x93, 0xff, 0xe4, 0xfc, 0xfd, 0xfe, 0x12, 0x05, + 0x28, 0x8f, 0x37, 0x8e, 0x36, 0x8d, 0x35, 0x8c, 0x34, 0x22, 0x78, 0x84, 0xe6, 0xfe, 0x08, 0xe6, + 0xff, 0xe4, 0x8f, 0x37, 0x8e, 0x36, 0xf5, 0x35, 0xf5, 0x34, 0x22, 0x90, 0x0e, 0x8c, 0xe4, 0x93, + 0x25, 0xe0, 0x24, 0x0a, 0xf8, 0xe6, 0xfe, 0x08, 0xe6, 0xff, 0x22, 0xe6, 0xfe, 0x08, 0xe6, 0xff, + 0xe4, 0x8f, 0x3b, 0x8e, 0x3a, 0xf5, 0x39, 0xf5, 0x38, 0x22, 0x78, 0x4e, 0xe6, 0xfe, 0x08, 0xe6, + 0xff, 0x22, 0xef, 0x25, 0xe0, 0x24, 0x4e, 0xf8, 0xe6, 0xfc, 0x08, 0xe6, 0xfd, 0x22, 0x78, 0x89, + 0xef, 0x26, 0xf6, 0x18, 0xe4, 0x36, 0xf6, 0x22, 0x75, 0x89, 0x03, 0x75, 0xa8, 0x01, 0x75, 0xb8, + 0x04, 0x75, 0x34, 0xff, 0x75, 0x35, 0x0e, 0x75, 0x36, 0x15, 0x75, 0x37, 0x0d, 0x12, 0x0e, 0x9a, + 0x12, 0x00, 0x09, 0x12, 0x0f, 0x16, 0x12, 0x00, 0x06, 0xd2, 0x00, 0xd2, 0x34, 0xd2, 0xaf, 0x75, + 0x34, 0xff, 0x75, 0x35, 0x0e, 0x75, 0x36, 0x49, 0x75, 0x37, 0x03, 0x12, 0x0e, 0x9a, 0x30, 0x08, + 0x09, 0xc2, 0x34, 0x12, 0x08, 0xcb, 0xc2, 0x08, 0xd2, 0x34, 0x30, 0x0b, 0x09, 0xc2, 0x36, 0x12, + 0x02, 0x6c, 0xc2, 0x0b, 0xd2, 0x36, 0x30, 0x09, 0x09, 0xc2, 0x36, 0x12, 0x00, 0x0e, 0xc2, 0x09, + 0xd2, 0x36, 0x30, 0x0e, 0x03, 0x12, 0x06, 0xd7, 0x30, 0x35, 0xd3, 0x90, 0x30, 0x29, 0xe5, 0x1e, + 0xf0, 0xb4, 0x10, 0x05, 0x90, 0x30, 0x23, 0xe4, 0xf0, 0xc2, 0x35, 0x80, 0xc1, 0xe4, 0xf5, 0x4b, + 0x90, 0x0e, 0x7a, 0x93, 0xff, 0xe4, 0x8f, 0x37, 0xf5, 0x36, 0xf5, 0x35, 0xf5, 0x34, 0xaf, 0x37, + 0xae, 0x36, 0xad, 0x35, 0xac, 0x34, 0x90, 0x0e, 0x6a, 0x12, 0x0d, 0xf6, 0x8f, 0x37, 0x8e, 0x36, + 0x8d, 0x35, 0x8c, 0x34, 0x90, 0x0e, 0x72, 0x12, 0x06, 0x7c, 0xef, 0x45, 0x37, 0xf5, 0x37, 0xee, + 0x45, 0x36, 0xf5, 0x36, 0xed, 0x45, 0x35, 0xf5, 0x35, 0xec, 0x45, 0x34, 0xf5, 0x34, 0xe4, 0xf5, + 0x22, 0xf5, 0x23, 0x85, 0x37, 0x31, 0x85, 0x36, 0x30, 0x85, 0x35, 0x2f, 0x85, 0x34, 0x2e, 0x12, + 0x0f, 0x46, 0xe4, 0xf5, 0x22, 0xf5, 0x23, 0x90, 0x0e, 0x72, 0x12, 0x0d, 0xea, 0x12, 0x0f, 0x46, + 0xe4, 0xf5, 0x22, 0xf5, 0x23, 0x90, 0x0e, 0x6e, 0x12, 0x0d, 0xea, 0x02, 0x0f, 0x46, 0xe5, 0x40, + 0x24, 0xf2, 0xf5, 0x37, 0xe5, 0x3f, 0x34, 0x43, 0xf5, 0x36, 0xe5, 0x3e, 0x34, 0xa2, 0xf5, 0x35, + 0xe5, 0x3d, 0x34, 0x28, 0xf5, 0x34, 0xe5, 0x37, 0xff, 0xe4, 0xfe, 0xfd, 0xfc, 0x78, 0x18, 0x12, + 0x06, 0x69, 0x8f, 0x40, 0x8e, 0x3f, 0x8d, 0x3e, 0x8c, 0x3d, 0xe5, 0x37, 0x54, 0xa0, 0xff, 0xe5, + 0x36, 0xfe, 0xe4, 0xfd, 0xfc, 0x78, 0x07, 0x12, 0x06, 0x56, 0x78, 0x10, 0x12, 0x0f, 0x9a, 0xe4, + 0xff, 0xfe, 0xe5, 0x35, 0xfd, 0xe4, 0xfc, 0x78, 0x0e, 0x12, 0x06, 0x56, 0x12, 0x0f, 0x9d, 0xe4, + 0xff, 0xfe, 0xfd, 0xe5, 0x34, 0xfc, 0x78, 0x18, 0x12, 0x06, 0x56, 0x78, 0x08, 0x12, 0x0f, 0x9a, + 0x22, 0x8f, 0x3b, 0x8e, 0x3a, 0x8d, 0x39, 0x8c, 0x38, 0x22, 0x12, 0x06, 0x7c, 0x8f, 0x31, 0x8e, + 0x30, 0x8d, 0x2f, 0x8c, 0x2e, 0x22, 0x93, 0xf9, 0xf8, 0x02, 0x06, 0x69, 0x00, 0x00, 0x00, 0x00, + 0x12, 0x01, 0x17, 0x08, 0x31, 0x15, 0x53, 0x54, 0x44, 0x20, 0x20, 0x20, 0x20, 0x20, 0x13, 0x01, + 0x10, 0x01, 0x56, 0x40, 0x1a, 0x30, 0x29, 0x7e, 0x00, 0x30, 0x04, 0x20, 0xdf, 0x30, 0x05, 0x40, + 0xbf, 0x50, 0x03, 0x00, 0xfd, 0x50, 0x27, 0x01, 0xfe, 0x60, 0x00, 0x11, 0x00, 0x3f, 0x05, 0x30, + 0x00, 0x3f, 0x06, 0x22, 0x00, 0x3f, 0x01, 0x2a, 0x00, 0x3f, 0x02, 0x00, 0x00, 0x36, 0x06, 0x07, + 0x00, 0x3f, 0x0b, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x40, 0xbf, 0x30, 0x01, 0x00, + 0xbf, 0x30, 0x29, 0x70, 0x00, 0x3a, 0x00, 0x00, 0xff, 0x3a, 0x00, 0x00, 0xff, 0x36, 0x03, 0x36, + 0x02, 0x41, 0x44, 0x58, 0x20, 0x18, 0x10, 0x0a, 0x04, 0x04, 0x00, 0x03, 0xff, 0x64, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x06, 0x06, 0x00, 0x03, 0x51, 0x00, 0x7a, + 0x50, 0x3c, 0x28, 0x1e, 0x10, 0x10, 0x50, 0x2d, 0x28, 0x16, 0x10, 0x10, 0x02, 0x00, 0x10, 0x0c, + 0x10, 0x04, 0x0c, 0x6e, 0x06, 0x05, 0x00, 0xa5, 0x5a, 0x00, 0xae, 0x35, 0xaf, 0x36, 0xe4, 0xfd, + 0xed, 0xc3, 0x95, 0x37, 0x50, 0x33, 0x12, 0x0f, 0xe2, 0xe4, 0x93, 0xf5, 0x38, 0x74, 0x01, 0x93, + 0xf5, 0x39, 0x45, 0x38, 0x60, 0x23, 0x85, 0x39, 0x82, 0x85, 0x38, 0x83, 0xe0, 0xfc, 0x12, 0x0f, + 0xe2, 0x74, 0x03, 0x93, 0x52, 0x04, 0x12, 0x0f, 0xe2, 0x74, 0x02, 0x93, 0x42, 0x04, 0x85, 0x39, + 0x82, 0x85, 0x38, 0x83, 0xec, 0xf0, 0x0d, 0x80, 0xc7, 0x22, 0x78, 0xbe, 0xe6, 0xd3, 0x08, 0xff, + 0xe6, 0x64, 0x80, 0xf8, 0xef, 0x64, 0x80, 0x98, 0x22, 0x93, 0xff, 0x7e, 0x00, 0xe6, 0xfc, 0x08, + 0xe6, 0xfd, 0x12, 0x04, 0xc1, 0x78, 0xc1, 0xe6, 0xfc, 0x08, 0xe6, 0xfd, 0xd3, 0xef, 0x9d, 0xee, + 0x9c, 0x22, 0x78, 0xbd, 0xd3, 0xe6, 0x64, 0x80, 0x94, 0x80, 0x22, 0x25, 0xe0, 0x24, 0x0a, 0xf8, + 0xe6, 0xfe, 0x08, 0xe6, 0xff, 0x22, 0xe5, 0x3c, 0xd3, 0x94, 0x00, 0x40, 0x0b, 0x90, 0x0e, 0x88, + 0x12, 0x0b, 0xf1, 0x90, 0x0e, 0x86, 0x80, 0x09, 0x90, 0x0e, 0x82, 0x12, 0x0b, 0xf1, 0x90, 0x0e, + 0x80, 0xe4, 0x93, 0xf5, 0x44, 0xa3, 0xe4, 0x93, 0xf5, 0x43, 0xd2, 0x06, 0x30, 0x06, 0x03, 0xd3, + 0x80, 0x01, 0xc3, 0x92, 0x0e, 0x22, 0xa2, 0xaf, 0x92, 0x32, 0xc2, 0xaf, 0xe5, 0x23, 0x45, 0x22, + 0x90, 0x0e, 0x5d, 0x60, 0x0e, 0x12, 0x0f, 0xcb, 0xe0, 0xf5, 0x2c, 0x12, 0x0f, 0xc8, 0xe0, 0xf5, + 0x2d, 0x80, 0x0c, 0x12, 0x0f, 0xcb, 0xe5, 0x30, 0xf0, 0x12, 0x0f, 0xc8, 0xe5, 0x31, 0xf0, 0xa2, + 0x32, 0x92, 0xaf, 0x22, 0xd2, 0x01, 0xc2, 0x02, 0xe4, 0xf5, 0x1f, 0xf5, 0x1e, 0xd2, 0x35, 0xd2, + 0x33, 0xd2, 0x36, 0xd2, 0x01, 0xc2, 0x02, 0xf5, 0x1f, 0xf5, 0x1e, 0xd2, 0x35, 0xd2, 0x33, 0x22, + 0xfb, 0xd3, 0xed, 0x9b, 0x74, 0x80, 0xf8, 0x6c, 0x98, 0x22, 0x12, 0x06, 0x69, 0xe5, 0x40, 0x2f, + 0xf5, 0x40, 0xe5, 0x3f, 0x3e, 0xf5, 0x3f, 0xe5, 0x3e, 0x3d, 0xf5, 0x3e, 0xe5, 0x3d, 0x3c, 0xf5, + 0x3d, 0x22, 0xc0, 0xe0, 0xc0, 0x83, 0xc0, 0x82, 0x90, 0x3f, 0x0d, 0xe0, 0xf5, 0x33, 0xe5, 0x33, + 0xf0, 0xd0, 0x82, 0xd0, 0x83, 0xd0, 0xe0, 0x32, 0x90, 0x0e, 0x5f, 0xe4, 0x93, 0xfe, 0x74, 0x01, + 0x93, 0xf5, 0x82, 0x8e, 0x83, 0x22, 0x78, 0x7f, 0xe4, 0xf6, 0xd8, 0xfd, 0x75, 0x81, 0xcd, 0x02, + 0x0c, 0x98, 0x8f, 0x82, 0x8e, 0x83, 0x75, 0xf0, 0x04, 0xed, 0x02, 0x06, 0xa5 +}; + +static const uint8_t af_firmware_command_regs[][3] = { + + {0x30, 0x22, 0x03}, + {0x30, 0x23, 0x00}, + {0x30, 0x24, 0x00}, + {0x30, 0x25, 0x00}, + {0x30, 0x26, 0x00}, + {0x30, 0x27, 0x00}, + {0x30, 0x28, 0x00}, + {0x30, 0x29, 0x7f}, + + {0x00, 0x00, 0x00} +}; +#endif + +#define NUM_BRIGHTNESS_LEVELS (9) + +#define NUM_CONTRAST_LEVELS (7) +static const uint8_t contrast_regs[NUM_CONTRAST_LEVELS][1] = { + {0x14}, /* -3 */ + {0x18}, /* -2 */ + {0x1C}, /* -1 */ + {0x00}, /* +0 */ + {0x10}, /* +1 */ + {0x18}, /* +2 */ + {0x1C}, /* +3 */ +}; + +#define NUM_SATURATION_LEVELS (7) +static const uint8_t saturation_regs[NUM_SATURATION_LEVELS][6] = { + {0x0c, 0x30, 0x3d, 0x3e, 0x3d, 0x01}, /* -3 */ + {0x10, 0x3d, 0x4d, 0x4e, 0x4d, 0x01}, /* -2 */ + {0x15, 0x52, 0x66, 0x68, 0x66, 0x02}, /* -1 */ + {0x1a, 0x66, 0x80, 0x82, 0x80, 0x02}, /* +0 */ + {0x1f, 0x7a, 0x9a, 0x9c, 0x9a, 0x02}, /* +1 */ + {0x24, 0x8f, 0xb3, 0xb6, 0xb3, 0x03}, /* +2 */ + {0x2b, 0xab, 0xd6, 0xda, 0xd6, 0x04}, /* +3 */ +}; + +static int reset(sensor_t *sensor) { + int ret = 0; + readout_x = 0; + readout_y = 0; + + readout_w = ACTIVE_SENSOR_WIDTH; + readout_h = ACTIVE_SENSOR_HEIGHT; + + hts_target = 0; + + // Reset all registers + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, SCCB_SYSTEM_CTRL_1, 0x11); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, SYSTEM_CTROL0, 0x82); + + // Delay 5 ms + mp_hal_delay_ms(5); + + // Write default registers + for (int i = 0; default_regs[i][0]; i++) { + int addr = (default_regs[i][0] << 8) | (default_regs[i][1] << 0); + int data = default_regs[i][2]; + + #if (OMV_OV5640_REV_Y_CHECK == 1) + // Rev V (480 MHz / 20) -> 24 MHz PCLK / 3 * 100 = 800 MHz / 10 = 80 MHz PCLK. + // Rev Y (400 MHz / 16) -> 25 MHz PCLK / 3 * 84 = 700 MHz / 10 = 70 MHz PCLK. + if (HAL_GetREVID() < 0x2003) { + // Is this REV Y? + if (addr == SC_PLL_CONTRL2) { + data = OMV_OV5640_REV_Y_CTRL2; + } + if (addr == SC_PLL_CONTRL3) { + data = OMV_OV5640_REV_Y_CTRL3; + } + } + #endif + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, addr, data); + } + + #if (OMV_ENABLE_OV5640_AF == 1) + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, SYSTEM_RESET_00, 0x20); // force mcu reset + + // Write firmware + uint16_t fw_addr = __REV16(MCU_FIRMWARE_BASE); + ret |= omv_i2c_write_bytes(&sensor->i2c_bus, sensor->slv_addr, (uint8_t *) &fw_addr, 2, OMV_I2C_XFER_SUSPEND); + ret |= omv_i2c_write_bytes(&sensor->i2c_bus, + sensor->slv_addr, + (uint8_t *) af_firmware_regs, + sizeof(af_firmware_regs), + OMV_I2C_XFER_NO_FLAGS); + + for (int i = 0; af_firmware_command_regs[i][0]; i++) { + ret |= + omv_i2c_writeb2(&sensor->i2c_bus, + sensor->slv_addr, + (af_firmware_command_regs[i][0] << 8) | (af_firmware_command_regs[i][1] << 0), + af_firmware_command_regs[i][2]); + } + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, SYSTEM_RESET_00, 0x00); // release mcu reset + #endif + + // Delay 300 ms + if (!sensor->disable_delays) { + mp_hal_delay_ms(300); + } + + return ret; +} + +static int sleep(sensor_t *sensor, int enable) { + uint8_t reg; + if (enable) { + reg = 0x42; + } else { + reg = 0x02; + } + + return omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, SYSTEM_CTROL0, reg); +} + +static int read_reg(sensor_t *sensor, uint16_t reg_addr) { + uint8_t reg_data; + if (omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, reg_addr, ®_data) != 0) { + return -1; + } + return reg_data; +} + +static int write_reg(sensor_t *sensor, uint16_t reg_addr, uint16_t reg_data) { + return omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, reg_addr, reg_data); +} + +// HTS (Horizontal Time) is the readout width plus the HSYNC_TIME time. However, if this value gets +// too low the OV5640 will crash. The minimum was determined empirically with testing... +// Additionally, when the image width gets too large we need to slow down the line transfer rate by +// increasing HTS so that DCMI_DMAConvCpltUser() can keep up with the data rate. +// +// WARNING! IF YOU CHANGE ANYTHING HERE RETEST WITH **ALL** RESOLUTIONS FOR THE AFFECTED MODE! +static int calculate_hts(sensor_t *sensor, uint16_t width) { + uint16_t hts = hts_target; + + if ((sensor->pixformat == PIXFORMAT_GRAYSCALE) || (sensor->pixformat == PIXFORMAT_BAYER) || + (sensor->pixformat == PIXFORMAT_JPEG)) { + if (width <= 1280) { + hts = IM_MAX((width * 2) + 8, hts_target); + } + } else { + if (width > 640) { + hts = IM_MAX((width * 2) + 8, hts_target); + } + } + + if (width <= 640) { + hts += 160; // Fix image quality at low resolutions. + + } + return IM_MAX(hts + HSYNC_TIME, (SENSOR_WIDTH + HSYNC_TIME) / 2); // Fix to prevent crashing. +} + +// VTS (Vertical Time) is the readout height plus the VYSNC_TIME time. However, if this value gets +// too low the OV5640 will crash. The minimum was determined empirically with testing... +// +// WARNING! IF YOU CHANGE ANYTHING HERE RETEST WITH **ALL** RESOLUTIONS FOR THE AFFECTED MODE! +static int calculate_vts(sensor_t *sensor, uint16_t readout_height) { + return IM_MAX(readout_height + VYSNC_TIME, (SENSOR_HEIGHT + VYSNC_TIME) / 8); // Fix to prevent crashing. +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) { + uint8_t reg; + int ret = 0; + + // Not a multiple of 8. The JPEG encoder on the OV5640 can't handle this. + if ((pixformat == PIXFORMAT_JPEG) && ((resolution[sensor->framesize][0] % 8) || (resolution[sensor->framesize][1] % 8))) { + return -1; + } + + // Readout speed too fast. The DCMI_DMAConvCpltUser() line callback overhead is too much to handle the line transfer speed. + // If we were to slow the pixclk down these resolutions would work. As of right now, the image shakes and scrolls with + // the current line transfer speed. Note that there's an overhead to the DCMI_DMAConvCpltUser() function. It's not the + // memory copy operation that's too slow. It's that there's too much overhead in the DCMI_DMAConvCpltUser() method + // to even have time to start the line transfer. If it were possible to slow the line readout speed of the OV5640 + // this would enable these resolutions below. However, there's nothing in the datasheet that when modified does this. + if (((pixformat == PIXFORMAT_GRAYSCALE) || (pixformat == PIXFORMAT_BAYER) || (pixformat == PIXFORMAT_JPEG)) + && ((sensor->framesize == FRAMESIZE_QQCIF) + || (sensor->framesize == FRAMESIZE_QQSIF) + || (sensor->framesize == FRAMESIZE_HQQQVGA) + || (sensor->framesize == FRAMESIZE_HQQVGA))) { + return -1; + } + + switch (pixformat) { + case PIXFORMAT_GRAYSCALE: + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, FORMAT_CONTROL, 0x10); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, FORMAT_CONTROL_MUX, 0x00); + break; + case PIXFORMAT_RGB565: + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, FORMAT_CONTROL, 0x6F); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, FORMAT_CONTROL_MUX, 0x01); + break; + case PIXFORMAT_YUV422: + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, FORMAT_CONTROL, 0x30); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, FORMAT_CONTROL_MUX, 0x00); + break; + case PIXFORMAT_BAYER: + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, FORMAT_CONTROL, 0x00); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, FORMAT_CONTROL_MUX, 0x01); + break; + case PIXFORMAT_JPEG: + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, FORMAT_CONTROL, 0x30); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, FORMAT_CONTROL_MUX, 0x00); + break; + default: + return -1; + } + + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_TC_REG_21, ®); + ret |= + omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_TC_REG_21, + (reg & 0xDF) | ((pixformat == PIXFORMAT_JPEG) ? 0x20 : 0x00)); + + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, SYSTEM_RESET_02, ®); + ret |= + omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, SYSTEM_RESET_02, + (reg & 0xE3) | ((pixformat == PIXFORMAT_JPEG) ? 0x00 : 0x1C)); + + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, CLOCK_ENABLE_02, ®); + ret |= + omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, CLOCK_ENABLE_02, + (reg & 0xD7) | ((pixformat == PIXFORMAT_JPEG) ? 0x28 : 0x00)); + + if (hts_target) { + uint16_t sensor_hts = calculate_hts(sensor, resolution[sensor->framesize][0]); + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_HTS_H, sensor_hts >> 8); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_HTS_L, sensor_hts); + } + + return ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) { + uint8_t reg; + int ret = 0; + uint16_t w = resolution[framesize][0]; + uint16_t h = resolution[framesize][1]; + + // Not a multiple of 8. The JPEG encoder on the OV5640 can't handle this. + if ((sensor->pixformat == PIXFORMAT_JPEG) && ((w % 8) || (h % 8))) { + return -1; + } + + // Readout speed too fast. The DCMI_DMAConvCpltUser() line callback overhead is too much to handle the line transfer speed. + // If we were to slow the pixclk down these resolutions would work. As of right now, the image shakes and scrolls with + // the current line transfer speed. Note that there's an overhead to the DCMI_DMAConvCpltUser() function. It's not the + // memory copy operation that's too slow. It's that there's too much overhead in the DCMI_DMAConvCpltUser() method + // to even have time to start the line transfer. If it were possible to slow the line readout speed of the OV5640 + // this would enable these resolutions below. However, there's nothing in the datasheet that when modified does this. + if (((sensor->pixformat == PIXFORMAT_GRAYSCALE) || (sensor->pixformat == PIXFORMAT_BAYER) || + (sensor->pixformat == PIXFORMAT_JPEG)) + && ((framesize == FRAMESIZE_QQCIF) + || (framesize == FRAMESIZE_QQSIF) + || (framesize == FRAMESIZE_HQQQVGA) + || (framesize == FRAMESIZE_HQQVGA))) { + return -1; + } + + // Generally doesn't work for anything. + if (framesize == FRAMESIZE_QQQQVGA) { + return -1; + } + + // Invalid resolution. + if ((w > ACTIVE_SENSOR_WIDTH) || (h > ACTIVE_SENSOR_HEIGHT)) { + return -1; + } + + // Step 0: Clamp readout settings. + + readout_w = IM_MAX(readout_w, w); + readout_h = IM_MAX(readout_h, h); + + int readout_x_max = (ACTIVE_SENSOR_WIDTH - readout_w) / 2; + int readout_y_max = (ACTIVE_SENSOR_HEIGHT - readout_h) / 2; + readout_x = IM_MAX(IM_MIN(readout_x, readout_x_max), -readout_x_max); + readout_y = IM_MAX(IM_MIN(readout_y, readout_y_max), -readout_y_max); + + // Step 1: Determine readout area and subsampling amount. + + uint16_t sensor_div = 0; + + if ((w > (readout_w / 2)) || (h > (readout_h / 2))) { + sensor_div = 1; + } else { + sensor_div = 2; + } + + // Step 2: Determine horizontal and vertical start and end points. + + uint16_t sensor_w = readout_w + DUMMY_WIDTH_BUFFER; // camera hardware needs dummy pixels to sync + uint16_t sensor_h = readout_h + DUMMY_HEIGHT_BUFFER; // camera hardware needs dummy lines to sync + + uint16_t sensor_ws = + IM_MAX(IM_MIN((((ACTIVE_SENSOR_WIDTH - sensor_w) / 4) + (readout_x / 2)) * 2, ACTIVE_SENSOR_WIDTH - sensor_w), + -(DUMMY_WIDTH_BUFFER / 2)) + DUMMY_COLUMNS; // must be multiple of 2 + uint16_t sensor_we = sensor_ws + sensor_w - 1; + + uint16_t sensor_hs = + IM_MAX(IM_MIN((((ACTIVE_SENSOR_HEIGHT - sensor_h) / 4) - (readout_y / 2)) * 2, ACTIVE_SENSOR_HEIGHT - sensor_h), + -(DUMMY_HEIGHT_BUFFER / 2)) + DUMMY_LINES; // must be multiple of 2 + uint16_t sensor_he = sensor_hs + sensor_h - 1; + + // Step 3: Determine scaling window offset. + + float ratio = IM_MIN((readout_w / sensor_div) / ((float) w), (readout_h / sensor_div) / ((float) h)); + + uint16_t w_mul = w * ratio; + uint16_t h_mul = h * ratio; + uint16_t x_off = ((sensor_w / sensor_div) - w_mul) / 2; + uint16_t y_off = ((sensor_h / sensor_div) - h_mul) / 2; + + // Step 4: Compute total frame time. + + hts_target = sensor_w / sensor_div; + + uint16_t sensor_hts = calculate_hts(sensor, w); + uint16_t sensor_vts = calculate_vts(sensor, sensor_h / sensor_div); + + uint16_t sensor_x_inc = (((sensor_div * 2) - 1) << 4) | (1 << 0); // odd[7:4]/even[3:0] pixel inc on the bayer pattern + uint16_t sensor_y_inc = (((sensor_div * 2) - 1) << 4) | (1 << 0); // odd[7:4]/even[3:0] pixel inc on the bayer pattern + + // Step 5: Write regs. + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_HS_H, sensor_ws >> 8); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_HS_L, sensor_ws); + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_VS_H, sensor_hs >> 8); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_VS_L, sensor_hs); + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_HW_H, sensor_we >> 8); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_HW_L, sensor_we); + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_VH_H, sensor_he >> 8); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_VH_L, sensor_he); + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_DVPHO_H, w >> 8); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_DVPHO_L, w); + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_DVPVO_H, h >> 8); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_DVPVO_L, h); + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_HTS_H, sensor_hts >> 8); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_HTS_L, sensor_hts); + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_VTS_H, sensor_vts >> 8); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_VTS_L, sensor_vts); + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_HOFFSET_H, x_off >> 8); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_HOFFSET_L, x_off); + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_VOFFSET_H, y_off >> 8); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_VOFFSET_L, y_off); + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_X_INC, sensor_x_inc); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_Y_INC, sensor_y_inc); + + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_TC_REG_20, ®); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_TC_REG_20, (reg & 0xFE) | (sensor_div > 1)); + + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_TC_REG_21, ®); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_TC_REG_21, (reg & 0xFE) | (sensor_div > 1)); + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, VFIFO_HSIZE_H, w >> 8); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, VFIFO_HSIZE_L, w); + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, VFIFO_VSIZE_H, h >> 8); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, VFIFO_VSIZE_L, h); + + return ret; +} + +static int set_contrast(sensor_t *sensor, int level) { + int ret = 0; + + int new_level = level + (NUM_CONTRAST_LEVELS / 2); + if (new_level < 0 || new_level >= NUM_CONTRAST_LEVELS) { + return -1; + } + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x3212, 0x03); // start group 3 + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5586, (new_level + 5) << 2); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5585, contrast_regs[new_level][0]); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x3212, 0x13); // end group 3 + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x3212, 0xa3); // launch group 3 + + return ret; +} + +static int set_brightness(sensor_t *sensor, int level) { + int ret = 0; + + int new_level = level + (NUM_BRIGHTNESS_LEVELS / 2); + if (new_level < 0 || new_level >= NUM_BRIGHTNESS_LEVELS) { + return -1; + } + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x3212, 0x03); // start group 3 + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5587, abs(level) << 4); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5588, (level < 0) ? 0x09 : 0x01); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x3212, 0x13); // end group 3 + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x3212, 0xa3); // launch group 3 + + return ret; +} + +static int set_saturation(sensor_t *sensor, int level) { + int ret = 0; + + int new_level = level + (NUM_SATURATION_LEVELS / 2); + if (new_level < 0 || new_level >= NUM_SATURATION_LEVELS) { + return -1; + } + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x3212, 0x03); // start group 3 + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5581, 0x1c); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5582, 0x5a); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5583, 0x06); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5584, saturation_regs[new_level][0]); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5585, saturation_regs[new_level][1]); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5586, saturation_regs[new_level][2]); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5587, saturation_regs[new_level][3]); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5588, saturation_regs[new_level][4]); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5589, saturation_regs[new_level][5]); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x558b, 0x98); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x558a, 0x01); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x3212, 0x13); // end group 3 + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x3212, 0xa3); // launch group 3 + + return ret; +} + +static int set_gainceiling(sensor_t *sensor, gainceiling_t gainceiling) { + uint8_t reg; + int ret = 0; + + int new_gainceiling = 16 << (gainceiling + 1); + if (new_gainceiling >= 1024) { + return -1; + } + + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AEC_GAIN_CEILING_H, ®); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AEC_GAIN_CEILING_H, (reg & 0xFC) | (new_gainceiling >> 8)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AEC_GAIN_CEILING_L, new_gainceiling); + + return ret; +} + +static int set_quality(sensor_t *sensor, int qs) { + uint8_t reg; + int ret = omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, JPEG_CTRL07, ®); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, JPEG_CTRL07, (reg & 0xC0) | (qs >> 2)); + + return ret; +} + +static int set_colorbar(sensor_t *sensor, int enable) { + uint8_t reg; + int ret = omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, PRE_ISP_TEST, ®); + return omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, PRE_ISP_TEST, (reg & 0x7F) | (enable ? 0x80 : 0x00)) | ret; +} + +static int set_auto_gain(sensor_t *sensor, int enable, float gain_db, float gain_db_ceiling) { + uint8_t reg; + int ret = omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AEC_PK_MANUAL, ®); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AEC_PK_MANUAL, (reg & 0xFD) | ((enable == 0) << 1)); + + if ((enable == 0) && (!isnanf(gain_db)) && (!isinff(gain_db))) { + int gain = IM_MAX(IM_MIN(fast_roundf(expf((gain_db / 20.0f) * M_LN10) * 16.0f), 1023), 0); + + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AEC_PK_REAL_GAIN_H, ®); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AEC_PK_REAL_GAIN_H, (reg & 0xFC) | (gain >> 8)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AEC_PK_REAL_GAIN_L, gain); + } else if ((enable != 0) && (!isnanf(gain_db_ceiling)) && (!isinff(gain_db_ceiling))) { + int gain_ceiling = IM_MAX(IM_MIN(fast_roundf(expf((gain_db_ceiling / 20.0f) * M_LN10) * 16.0f), 1023), 0); + + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AEC_GAIN_CEILING_H, ®); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AEC_GAIN_CEILING_H, (reg & 0xFC) | (gain_ceiling >> 8)); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AEC_GAIN_CEILING_L, gain_ceiling); + } + + return ret; +} + +static int get_gain_db(sensor_t *sensor, float *gain_db) { + uint8_t gainh, gainl; + + int ret = omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AEC_PK_REAL_GAIN_H, &gainh); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AEC_PK_REAL_GAIN_L, &gainl); + + *gain_db = 20.0f * log10f((((gainh & 0x3) << 8) | gainl) / 16.0f); + + return ret; +} + +static int calc_pclk_freq(uint8_t sc_pll_ctrl_0, + uint8_t sc_pll_ctrl_1, + uint8_t sc_pll_ctrl_2, + uint8_t sc_pll_ctrl_3, + uint8_t sys_root_div) { + uint32_t pclk_freq = sensor_get_xclk_frequency(); + pclk_freq /= ((sc_pll_ctrl_3 & 0x10) != 0x00) ? 2 : 1; + pclk_freq /= ((sc_pll_ctrl_0 & 0x0F) == 0x0A) ? 10 : 8; + switch (sc_pll_ctrl_3 & 0x0F) { + case 0: pclk_freq /= 1; break; + case 1: pclk_freq /= 2; break; + case 2: pclk_freq /= 3; break; + case 3: pclk_freq /= 4; break; + case 4: pclk_freq /= 6; break; + case 5: pclk_freq /= 8; break; + default: pclk_freq /= 3; break; + } + pclk_freq *= sc_pll_ctrl_2; + sc_pll_ctrl_1 >>= 4; + pclk_freq /= sc_pll_ctrl_1; + switch (sys_root_div & 0x30) { + case 0x00: pclk_freq /= 1; break; + case 0x10: pclk_freq /= 2; break; + case 0x20: pclk_freq /= 4; break; + case 0x30: pclk_freq /= 8; break; + default: pclk_freq /= 1; break; + } + return (int) pclk_freq; +} + +static int set_auto_exposure(sensor_t *sensor, int enable, int exposure_us) { + uint8_t reg, spc0, spc1, spc2, spc3, sysrootdiv, hts_h, hts_l, vts_h, vts_l; + int ret = omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AEC_PK_MANUAL, ®); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AEC_PK_MANUAL, (reg & 0xFE) | ((enable == 0) << 0)); + + if ((enable == 0) && (exposure_us >= 0)) { + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, SC_PLL_CONTRL0, &spc0); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, SC_PLL_CONTRL1, &spc1); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, SC_PLL_CONTRL2, &spc2); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, SC_PLL_CONTRL3, &spc3); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, SYSTEM_ROOT_DIVIDER, &sysrootdiv); + + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_HTS_H, &hts_h); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_HTS_L, &hts_l); + + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_VTS_H, &vts_h); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_VTS_L, &vts_l); + + uint16_t hts = (hts_h << 8) | hts_l; + uint16_t vts = (vts_h << 8) | vts_l; + + int pclk_freq = calc_pclk_freq(spc0, spc1, spc2, spc3, sysrootdiv); + int clocks_per_us = pclk_freq / 1000000; + int exposure = IM_MAX(IM_MIN((exposure_us * clocks_per_us) / hts, 0xFFFF), 0x0000); + + int new_vts = IM_MAX(exposure, vts); + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AEC_PK_EXPOSURE_0, exposure >> 12); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AEC_PK_EXPOSURE_1, exposure >> 4); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AEC_PK_EXPOSURE_2, exposure << 4); + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_VTS_H, new_vts >> 8); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_VTS_L, new_vts); + } + + return ret; +} + +static int get_exposure_us(sensor_t *sensor, int *exposure_us) { + uint8_t spc0, spc1, spc2, spc3, sysrootdiv, aec_0, aec_1, aec_2, hts_h, hts_l, vts_h, vts_l; + int ret = 0; + + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, SC_PLL_CONTRL0, &spc0); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, SC_PLL_CONTRL1, &spc1); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, SC_PLL_CONTRL2, &spc2); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, SC_PLL_CONTRL3, &spc3); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, SYSTEM_ROOT_DIVIDER, &sysrootdiv); + + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AEC_PK_EXPOSURE_0, &aec_0); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AEC_PK_EXPOSURE_1, &aec_1); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AEC_PK_EXPOSURE_2, &aec_2); + + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_HTS_H, &hts_h); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_HTS_L, &hts_l); + + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_VTS_H, &vts_h); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_VTS_L, &vts_l); + + uint32_t aec = ((aec_0 << 16) | (aec_1 << 8) | aec_2) >> 4; + uint16_t hts = (hts_h << 8) | hts_l; + uint16_t vts = (vts_h << 8) | vts_l; + + aec = IM_MIN(aec, vts); + + int pclk_freq = calc_pclk_freq(spc0, spc1, spc2, spc3, sysrootdiv); + int clocks_per_us = pclk_freq / 1000000; + *exposure_us = (aec * hts) / clocks_per_us; + + return ret; +} + +static int set_auto_whitebal(sensor_t *sensor, int enable, float r_gain_db, float g_gain_db, float b_gain_db) { + uint8_t reg; + int ret = omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AWB_MANUAL_CONTROL, ®); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AWB_MANUAL_CONTROL, (reg & 0xFE) | (enable == 0)); + + if ((enable == 0) && (!isnanf(r_gain_db)) && (!isnanf(g_gain_db)) && (!isnanf(b_gain_db)) + && (!isinff(r_gain_db)) && (!isinff(g_gain_db)) && (!isinff(b_gain_db))) { + + int r_gain = IM_MAX(IM_MIN(fast_roundf(expf((r_gain_db / 20.0f) * M_LN10)), 4095), 0); + int g_gain = IM_MAX(IM_MIN(fast_roundf(expf((g_gain_db / 20.0f) * M_LN10)), 4095), 0); + int b_gain = IM_MAX(IM_MIN(fast_roundf(expf((b_gain_db / 20.0f) * M_LN10)), 4095), 0); + + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AWB_R_GAIN_H, r_gain >> 8); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AWB_R_GAIN_L, r_gain); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AWB_G_GAIN_H, g_gain >> 8); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AWB_G_GAIN_L, g_gain); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AWB_B_GAIN_H, b_gain >> 8); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AWB_B_GAIN_L, b_gain); + } + + return ret; +} + +static int get_rgb_gain_db(sensor_t *sensor, float *r_gain_db, float *g_gain_db, float *b_gain_db) { + uint8_t redh, redl, greenh, greenl, blueh, bluel; + + int ret = omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AWB_R_GAIN_H, &redh); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AWB_R_GAIN_L, &redl); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AWB_G_GAIN_H, &greenh); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AWB_G_GAIN_L, &greenl); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AWB_B_GAIN_H, &blueh); + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AWB_B_GAIN_L, &bluel); + + *r_gain_db = 20.0f * log10f(((redh & 0xF) << 8) | redl); + *g_gain_db = 20.0f * log10f(((greenh & 0xF) << 8) | greenl); + *b_gain_db = 20.0f * log10f(((blueh & 0xF) << 8) | bluel); + + return ret; +} + +static int set_auto_blc(sensor_t *sensor, int enable, int *regs) { + uint8_t reg; + int ret = omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, BLC_CTRL_00, ®); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, BLC_CTRL_00, (reg & 0xFE) | (enable != 0)); + + if ((enable == 0) && (regs != NULL)) { + for (uint32_t i = 0; i < sensor->hw_flags.blc_size; i++) { + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, BLACK_LEVEL_00_H + i, regs[i]); + } + } + + return ret; +} + +static int get_blc_regs(sensor_t *sensor, int *regs) { + int ret = 0; + + for (uint32_t i = 0; i < sensor->hw_flags.blc_size; i++) { + uint8_t reg; + ret |= omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, BLACK_LEVEL_00_H + i, ®); + regs[i] = reg; + } + + return ret; +} + +static int set_hmirror(sensor_t *sensor, int enable) { + uint8_t reg; + int ret = omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_TC_REG_21, ®); + if (enable) { + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_TC_REG_21, reg | 0x06); + } else { + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_TC_REG_21, reg & 0xF9); + } + return ret; +} + +static int set_vflip(sensor_t *sensor, int enable) { + uint8_t reg; + int ret = omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_TC_REG_20, ®); + if (!enable) { + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_TC_REG_20, reg | 0x06); + } else { + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, TIMING_TC_REG_20, reg & 0xF9); + } + return ret; +} + +static int set_special_effect(sensor_t *sensor, sde_t sde) { + int ret = 0; + + switch (sde) { + case SDE_NEGATIVE: + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x3212, 0x03); // start group 3 + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5580, 0x40); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5003, 0x08); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5583, 0x40); // sat U + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5584, 0x10); // sat V + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x3212, 0x13); // end group 3 + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x3212, 0xa3); // latch group 3 + break; + case SDE_NORMAL: + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x3212, 0x03); // start group 3 + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5580, 0x06); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5583, 0x40); // sat U + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5584, 0x10); // sat V + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x5003, 0x08); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x3212, 0x13); // end group 3 + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, 0x3212, 0xa3); // latch group 3 + break; + default: + return -1; + } + + return ret; +} + +static int set_lens_correction(sensor_t *sensor, int enable, int radi, int coef) { + uint8_t reg; + int ret = omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, ISP_CONTROL_00, ®); + return omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, ISP_CONTROL_00, (reg & 0x7F) | (enable ? 0x80 : 0x00)) | ret; +} + +static int ioctl(sensor_t *sensor, int request, va_list ap) { + int ret = 0; + uint8_t reg; + + switch (request) { + case IOCTL_SET_READOUT_WINDOW: { + int tmp_readout_x = va_arg(ap, int); + int tmp_readout_y = va_arg(ap, int); + int tmp_readout_w = IM_MAX(IM_MIN(va_arg(ap, int), ACTIVE_SENSOR_WIDTH), resolution[sensor->framesize][0]); + int tmp_readout_h = IM_MAX(IM_MIN(va_arg(ap, int), ACTIVE_SENSOR_HEIGHT), resolution[sensor->framesize][1]); + int readout_x_max = (ACTIVE_SENSOR_WIDTH - tmp_readout_w) / 2; + int readout_y_max = (ACTIVE_SENSOR_HEIGHT - tmp_readout_h) / 2; + tmp_readout_x = IM_MAX(IM_MIN(tmp_readout_x, readout_x_max), -readout_x_max); + tmp_readout_y = IM_MAX(IM_MIN(tmp_readout_y, readout_y_max), -readout_y_max); + bool changed = (tmp_readout_x != readout_x) || (tmp_readout_y != readout_y) || (tmp_readout_w != readout_w) || + (tmp_readout_h != readout_h); + readout_x = tmp_readout_x; + readout_y = tmp_readout_y; + readout_w = tmp_readout_w; + readout_h = tmp_readout_h; + if (changed && (sensor->framesize != FRAMESIZE_INVALID)) { + set_framesize(sensor, sensor->framesize); + } + break; + } + case IOCTL_GET_READOUT_WINDOW: { + *va_arg(ap, int *) = readout_x; + *va_arg(ap, int *) = readout_y; + *va_arg(ap, int *) = readout_w; + *va_arg(ap, int *) = readout_h; + break; + } + #if (OMV_ENABLE_OV5640_AF == 1) + case IOCTL_TRIGGER_AUTO_FOCUS: { + ret = omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AF_CMD_MAIN, 0x03); + break; + } + case IOCTL_PAUSE_AUTO_FOCUS: { + ret = omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AF_CMD_MAIN, 0x06); + break; + } + case IOCTL_RESET_AUTO_FOCUS: { + ret = omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AF_CMD_MAIN, 0x08); + break; + } + case IOCTL_WAIT_ON_AUTO_FOCUS: { + mp_uint_t start_tick = mp_hal_ticks_ms(), delay_ms = va_arg(ap, uint32_t); + for (;;) { + ret = omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AF_CMD_ACK, ®); + if ((ret < 0) || (!reg)) { + break; + } + if ((mp_hal_ticks_ms() - start_tick) >= delay_ms) { + return -1; + } + mp_hal_delay_ms(1); + } + break; + } + #endif + case IOCTL_SET_NIGHT_MODE: { + int enable = va_arg(ap, int); + ret = omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AEC_CTRL_00, ®); + ret |= omv_i2c_writeb2(&sensor->i2c_bus, sensor->slv_addr, AEC_CTRL_00, + (reg & 0xFB) | ((enable != 0) << 2)); + break; + } + case IOCTL_GET_NIGHT_MODE: { + int *enable = va_arg(ap, int *); + ret = omv_i2c_readb2(&sensor->i2c_bus, sensor->slv_addr, AEC_CTRL_00, ®); + if (ret >= 0) { + *enable = reg & 0x4; + } + break; + } + default: { + ret = -1; + break; + } + } + + return ret; +} + +int ov5640_init(sensor_t *sensor) { + // Initialize sensor structure. + sensor->reset = reset; + sensor->sleep = sleep; + sensor->read_reg = read_reg; + sensor->write_reg = write_reg; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_contrast = set_contrast; + sensor->set_brightness = set_brightness; + sensor->set_saturation = set_saturation; + sensor->set_gainceiling = set_gainceiling; + sensor->set_quality = set_quality; + sensor->set_colorbar = set_colorbar; + sensor->set_auto_gain = set_auto_gain; + sensor->get_gain_db = get_gain_db; + sensor->set_auto_exposure = set_auto_exposure; + sensor->get_exposure_us = get_exposure_us; + sensor->set_auto_whitebal = set_auto_whitebal; + sensor->get_rgb_gain_db = get_rgb_gain_db; + sensor->set_auto_blc = set_auto_blc; + sensor->get_blc_regs = get_blc_regs; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + sensor->set_special_effect = set_special_effect; + sensor->set_lens_correction = set_lens_correction; + sensor->ioctl = ioctl; + + // Set sensor flags + sensor->hw_flags.vsync = 1; + sensor->hw_flags.hsync = 0; + sensor->hw_flags.pixck = 1; + sensor->hw_flags.fsync = 0; + sensor->hw_flags.jpege = 1; + sensor->hw_flags.jpeg_mode = 4; + sensor->hw_flags.gs_bpp = 1; + sensor->hw_flags.rgb_swap = 0; + sensor->hw_flags.yuv_order = SENSOR_HW_FLAGS_YVU422; + sensor->hw_flags.blc_size = 8; + + return 0; +} +#endif // (OMV_ENABLE_OV5640 == 1) diff --git a/components/3rd_party/omv/omv/sensors/ov5640.h b/components/3rd_party/omv/omv/sensors/ov5640.h new file mode 100644 index 00000000..74596e59 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/ov5640.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. + * + * OV5640 driver. + */ +#ifndef __OV5640_H__ +#define __OV5640_H__ +int ov5640_init(sensor_t *sensor); +#endif // __OV5640_H__ diff --git a/components/3rd_party/omv/omv/sensors/ov5640_regs.h b/components/3rd_party/omv/omv/sensors/ov5640_regs.h new file mode 100644 index 00000000..927db4c2 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/ov5640_regs.h @@ -0,0 +1,127 @@ +/* + * 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. + * + * OV5640 register definitions. + */ +#ifndef __REG_REGS_H__ +#define __REG_REGS_H__ + +#define SYSTEM_RESET_00 0x3000 +#define SYSTEM_RESET_01 0x3001 +#define SYSTEM_RESET_02 0x3002 + +#define CLOCK_ENABLE_02 0x3006 + +#define SYSTEM_CTROL0 0x3008 + +#define AF_CMD_MAIN 0x3022 +#define AF_CMD_ACK 0x3023 +#define AF_FW_STATUS 0x3029 + +#define SC_PLL_CONTRL0 0x3034 +#define SC_PLL_CONTRL1 0x3035 +#define SC_PLL_CONTRL2 0x3036 +#define SC_PLL_CONTRL3 0x3037 + +#define SCCB_SYSTEM_CTRL_1 0x3103 +#define SYSTEM_ROOT_DIVIDER 0x3108 + +#define AWB_R_GAIN_H 0x3400 +#define AWB_R_GAIN_L 0x3401 + +#define AWB_G_GAIN_H 0x3402 +#define AWB_G_GAIN_L 0x3403 + +#define AWB_B_GAIN_H 0x3404 +#define AWB_B_GAIN_L 0x3405 + +#define AWB_MANUAL_CONTROL 0x3406 + +#define AEC_PK_EXPOSURE_0 0x3500 +#define AEC_PK_EXPOSURE_1 0x3501 +#define AEC_PK_EXPOSURE_2 0x3502 + +#define AEC_PK_MANUAL 0x3503 + +#define AEC_PK_REAL_GAIN_H 0x350A +#define AEC_PK_REAL_GAIN_L 0x350B + +#define TIMING_HS_H 0x3800 +#define TIMING_HS_L 0x3801 + +#define TIMING_VS_H 0x3802 +#define TIMING_VS_L 0x3803 + +#define TIMING_HW_H 0x3804 +#define TIMING_HW_L 0x3805 + +#define TIMING_VH_H 0x3806 +#define TIMING_VH_L 0x3807 + +#define TIMING_DVPHO_H 0x3808 +#define TIMING_DVPHO_L 0x3809 + +#define TIMING_DVPVO_H 0x380A +#define TIMING_DVPVO_L 0x380B + +#define TIMING_HTS_H 0x380C +#define TIMING_HTS_L 0x380D + +#define TIMING_VTS_H 0x380E +#define TIMING_VTS_L 0x380F + +#define TIMING_HOFFSET_H 0x3810 +#define TIMING_HOFFSET_L 0x3811 + +#define TIMING_VOFFSET_H 0x3812 +#define TIMING_VOFFSET_L 0x3813 + +#define TIMING_X_INC 0x3814 +#define TIMING_Y_INC 0x3815 + +#define TIMING_TC_REG_20 0x3820 +#define TIMING_TC_REG_21 0x3821 + +#define AEC_CTRL_00 0x3A00 + +#define AEC_GAIN_CEILING_H 0x3A18 +#define AEC_GAIN_CEILING_L 0x3A18 + +#define BLC_CTRL_00 0x4000 + +#define BLACK_LEVEL_00_H 0x402C +#define BLACK_LEVEL_00_L 0x402D + +#define BLACK_LEVEL_01_H 0x402E +#define BLACK_LEVEL_01_L 0x402F + +#define BLACK_LEVEL_10_H 0x4030 +#define BLACK_LEVEL_10_L 0x4031 + +#define BLACK_LEVEL_11_H 0x4032 +#define BLACK_LEVEL_11_L 0x4033 + +#define FORMAT_CONTROL 0x4300 + +#define VFIFO_HSIZE_H 0x4602 +#define VFIFO_HSIZE_L 0x4603 + +#define VFIFO_VSIZE_H 0x4604 +#define VFIFO_VSIZE_L 0x4605 + +#define JPEG_CTRL07 0x4407 + +#define ISP_CONTROL_00 0x5300 + +#define FORMAT_CONTROL_MUX 0x501F + +#define PRE_ISP_TEST 0x503D + +#define MCU_FIRMWARE_BASE 0x8000 + +#endif //__REG_REGS_H__ diff --git a/components/3rd_party/omv/omv/sensors/ov7670.c b/components/3rd_party/omv/omv/sensors/ov7670.c new file mode 100644 index 00000000..d03818ee --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/ov7670.c @@ -0,0 +1,463 @@ +/* + * 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. + * + * OV7670 driver. + */ +#include "omv_boardconfig.h" +#if (OMV_ENABLE_OV7670 == 1) + +#include +#include +#include + +#include "omv_i2c.h" +#include "sensor.h" +#include "ov7670.h" +#include "ov7670_regs.h" +#include "py/mphal.h" + +static const uint8_t default_regs[][2] = { + // OV7670 reference registers + #if (OMV_OV7670_CLKRC == 0) + { CLKRC, CLKRC_PRESCALER_BYPASS }, + #else + { CLKRC, OMV_OV7670_CLKRC }, + #endif + { TSLB, 0x04 }, + { COM7, 0x00 }, + { HSTART, 0x13 }, + { HSTOP, 0x01 }, + { HREF, 0xb6 }, + { VSTART, 0x02 }, + { VSTOP, 0x7a }, + { VREF, 0x0a }, + + { COM3, 0x00 }, + { COM14, 0x00 }, + { SCALING_XSC, 0x3a }, + { SCALING_YSC, 0x35 }, + { SCALING_DCWCTR, 0x11 }, + { SCALING_PCLK_DIV, 0xf0 }, + { SCALING_PCLK, 0x02 }, + + /* Gamma curve values */ + { SLOP, 0x20 }, + { GAM1, 0x10 }, + { GAM2, 0x1e }, + { GAM3, 0x35 }, + { GAM4, 0x5a }, + { GAM5, 0x69 }, + { GAM6, 0x76 }, + { GAM7, 0x80 }, + { GAM8, 0x88 }, + { GAM9, 0x8f }, + { GAM10, 0x96 }, + { GAM11, 0xa3 }, + { GAM12, 0xaf }, + { GAM13, 0xc4 }, + { GAM14, 0xd7 }, + { GAM15, 0xe8 }, + + /* AGC and AEC parameters */ + { COM8, COM8_FAST_AEC | COM8_AEC_STEP | COM8_BANDF_EN }, + { GAIN, 0x00 }, + { AECH, 0x00 }, + { COM4, 0x40 }, + { COM9, 0x18 }, + { BD50MAX, 0x05 }, + { BD60MAX, 0x07 }, + { AEW, 0x95 }, + { AEB, 0x33 }, + { VPT, 0xe3 }, + { HAECC1, 0x78 }, + { HAECC2, 0x68 }, + { 0xa1, 0x03 }, + { HAECC3, 0xd8 }, + { HAECC4, 0xd8 }, + { HAECC5, 0xf0 }, + { HAECC6, 0x90 }, + { HAECC7, 0x94 }, + { COM8, COM8_FAST_AEC | COM8_AEC_STEP | COM8_BANDF_EN | COM8_AGC_EN | COM8_AEC_EN }, + + /* Almost all of these are magic "reserved" values. */ + { COM5, 0x61 }, + { COM6, 0x4b }, + { 0x16, 0x02 }, + { 0x21, 0x02 }, + { 0x22, 0x91 }, + { 0x29, 0x07 }, + { 0x33, 0x0b }, + { 0x35, 0x0b }, + { 0x37, 0x1d }, + { 0x38, 0x71 }, + { 0x39, 0x2a }, + { COM12, 0x78 }, + { 0x4d, 0x40 }, + { 0x4e, 0x20 }, + { GFIX, 0x00 }, + { 0x6b, 0x00 }, // PLL Bypass + { 0x74, 0x10 }, + { 0x8d, 0x4f }, + { 0x8e, 0x00 }, + { 0x8f, 0x00 }, + { 0x90, 0x00 }, + { 0x91, 0x00 }, + { 0x96, 0x00 }, + { 0x9a, 0x00 }, + { 0xb0, 0x84 }, + { 0xb1, 0x0c }, + { 0xb2, 0x0e }, + { 0xb3, 0x82 }, + { 0xb8, 0x0a }, + { 0x43, 0x0a }, + { 0x44, 0xf0 }, + { 0x45, 0x34 }, + { 0x46, 0x58 }, + { 0x47, 0x28 }, + { 0x48, 0x3a }, + { 0x59, 0x88 }, + { 0x5a, 0x88 }, + { 0x5b, 0x44 }, + { 0x5c, 0x67 }, + { 0x5d, 0x49 }, + { 0x5e, 0x0e }, + { 0x6c, 0x0a }, + { 0x6d, 0x55 }, + { 0x6e, 0x11 }, + { 0x6f, 0x9e }, + { 0x6a, 0x40 }, + { BLUE, 0x40 }, + { RED, 0x60 }, + { COM8, COM8_FAST_AEC | COM8_AEC_STEP | COM8_BANDF_EN | COM8_AGC_EN | COM8_AEC_EN | COM8_AWB_EN }, + + /* Matrix coefficients */ + { MTX1, 0x80 }, + { MTX2, 0x80 }, + { MTX3, 0x00 }, + { MTX4, 0x22 }, + { MTX5, 0x5e }, + { MTX6, 0x80 }, + { MTXS, 0x9e }, + + { COM16, COM16_AWB_GAIN_EN }, + { EDGE, 0x00 }, + { 0x75, 0x05 }, + { 0x76, 0xe1 }, + { 0x4c, 0x00 }, + { 0x77, 0x01 }, + { COM13, 0xc3 }, + { 0x4b, 0x09 }, + { 0xc9, 0x60 }, + { COM16, 0x38 }, + { 0x56, 0x40 }, + + { 0x34, 0x11 }, + { COM11, COM11_AEC_BANDF | COM11_HZAUTO_EN }, + { 0xa4, 0x88 }, + { 0x96, 0x00 }, + { 0x97, 0x30 }, + { 0x98, 0x20 }, + { 0x99, 0x30 }, + { 0x9a, 0x84 }, + { 0x9b, 0x29 }, + { 0x9c, 0x03 }, + { 0x9d, 0x4c }, + { 0x9e, 0x3f }, + { 0x78, 0x04 }, + { 0x79, 0x01 }, + { 0xc8, 0xf0 }, + { 0x79, 0x0f }, + { 0xc8, 0x00 }, + { 0x79, 0x10 }, + { 0xc8, 0x7e }, + { 0x79, 0x0a }, + { 0xc8, 0x80 }, + { 0x79, 0x0b }, + { 0xc8, 0x01 }, + { 0x79, 0x0c }, + { 0xc8, 0x0f }, + { 0x79, 0x0d }, + { 0xc8, 0x20 }, + { 0x79, 0x09 }, + { 0xc8, 0x80 }, + { 0x79, 0x02 }, + { 0xc8, 0xc0 }, + { 0x79, 0x03 }, + { 0xc8, 0x40 }, + { 0x79, 0x05 }, + { 0xc8, 0x30 }, + { 0x79, 0x26 }, + + { 0xFF, 0xFF }, +}; + +#if (OMV_OV7670_VERSION == 75) +static const uint8_t rgb565_regs[][2] = { + { COM7, COM7_RGB_FMT }, /* Selects RGB mode */ + { RGB444, 0x00 }, /* No RGB444 please */ + { COM1, 0x00 }, /* CCIR601 */ + { COM15, COM15_OUT_00_FF | COM15_FMT_RGB565}, + { COM9, 0x38 }, /* 16x gain ceiling; 0x8 is reserved bit */ + { 0x4f, 0xb3 }, /* "matrix coefficient 1" */ + { 0x50, 0xb3 }, /* "matrix coefficient 2" */ + { 0x51, 0x00 }, /* "matrix Coefficient 3" */ + { 0x52, 0x3d }, /* "matrix coefficient 4" */ + { 0x53, 0xa7 }, /* "matrix coefficient 5" */ + { 0x54, 0xe4 }, /* "matrix coefficient 6" */ + { COM13, COM13_GAMMA_EN | COM13_UVSAT_AUTO }, + { 0xFF, 0xFF } +}; +#elif (OMV_OV7670_VERSION == 70) +static const uint8_t rgb565_regs[][2] = { + { COM7, COM7_RGB_FMT }, /* Selects RGB mode */ + { RGB444, 0 }, /* No RGB444 please */ + { COM1, 0x0 }, /* CCIR601 */ + { COM15, COM15_FMT_RGB565 | COM15_OUT_00_FF}, + { COM9, 0x6A }, /* 128x gain ceiling; 0x8 is reserved bit */ + { MTX1, 0xb3 }, /* "matrix coefficient 1" */ + { MTX2, 0xb3 }, /* "matrix coefficient 2" */ + { MTX3, 0 }, /* vb */ + { MTX4, 0x3d }, /* "matrix coefficient 4" */ + { MTX5, 0xa7 }, /* "matrix coefficient 5" */ + { MTX6, 0xe4 }, /* "matrix coefficient 6" */ + { COM13, COM13_UVSAT_AUTO }, + { 0xFF, 0xFF } +}; +#else +#error "OV767x sensor is Not defined." +#endif + +// TODO: These registers probably need to be fixed too. +static const uint8_t yuv422_regs[][2] = { + { COM7, 0x00 }, /* Selects YUV mode */ + { RGB444, 0x00 }, /* No RGB444 please */ + { COM1, 0x00 }, /* CCIR601 */ + { COM15, COM15_OUT_00_FF }, + { COM9, 0x48 }, /* 32x gain ceiling; 0x8 is reserved bit */ + { 0x4f, 0x80 }, /* "matrix coefficient 1" */ + { 0x50, 0x80 }, /* "matrix coefficient 2" */ + { 0x51, 0x00 }, /* vb */ + { 0x52, 0x22 }, /* "matrix coefficient 4" */ + { 0x53, 0x5e }, /* "matrix coefficient 5" */ + { 0x54, 0x80 }, /* "matrix coefficient 6" */ + { COM13, COM13_GAMMA_EN | COM13_UVSAT_AUTO }, + { 0xFF, 0xFF } +}; + +static const uint8_t vga_regs[][2] = { + { COM3, 0x00 }, + { COM14, 0x00 }, + { 0x72, 0x11 }, // downsample by 4 + { 0x73, 0xf0 }, // divide by 4 + { HSTART, 0x13 }, + { HSTOP, 0x01 }, + { HREF, 0xb6 }, + { VSTART, 0x03 }, + { VSTOP, 0x00 }, + { VREF, 0x0a }, + { 0xFF, 0xFF }, +}; + +#if (OMV_OV7670_VERSION == 75) +static const uint8_t qvga_regs[][2] = { + { COM3, COM3_DCW_EN }, + { COM14, 0x11 }, // Divide by 2 + { 0x72, 0x22 }, // This has no effect on OV7675 + { 0x73, 0xf2 }, // This has no effect on OV7675 + { HSTART, 0x15 }, + { HSTOP, 0x03 }, + { HREF, 0xC0 }, + { VSTART, 0x03 }, + { VSTOP, 0x7B }, + { VREF, 0xF0 }, + { 0xFF, 0xFF }, +}; +#else +static const uint8_t qvga_regs[][2] = { + { COM3, COM3_DCW_EN }, + { COM14, 0x19 }, + { 0x72, 0x11 }, // downsample by 2 + { 0x73, 0xf1 }, // divide by 2 + { HSTART, 0x15 }, + { HSTOP, 0x04 }, + { HREF, 0xa4 }, + { VSTART, 0x02 }, + { VSTOP, 0x7a }, + { VREF, 0x0a }, + { 0XFF, 0XFF }, +}; +#endif + +#if (OMV_OV7670_VERSION == 75) +static const uint8_t qqvga_regs[][2] = { + { COM3, COM3_DCW_EN }, + { COM14, 0x11 }, // Divide by 2 + { 0x72, 0x22 }, // This has no effect on OV7675 + { 0x73, 0xf2 }, // This has no effect on OV7675 + { HSTART, 0x16 }, + { HSTOP, 0x04 }, + { HREF, 0xa4 }, + { VSTART, 0x22 }, + { VSTOP, 0x7a }, + { VREF, 0xfa }, + { 0xFF, 0xFF }, +}; +#else +static const uint8_t qqvga_regs[][2] = { + { COM3, COM3_DCW_EN }, + { COM14, 0x1a}, + { 0x72, 0x22 }, // downsample by 4 + { 0x73, 0xf2 }, // divide by 4 + { HSTART, 0x16 }, + { HSTOP, 0x04 }, + { HREF, 0xa4 }, + { VSTART, 0x02 }, + { VSTOP, 0x7a }, + { VREF, 0x0a }, + { 0XFF, 0XFF }, +}; +#endif + +static int reset(sensor_t *sensor) { + // Reset all registers + int ret = omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM7, COM7_RESET); + + // Delay 2 ms + mp_hal_delay_ms(2); + + // Write default registers + for (int i = 0; default_regs[i][0] != 0xff; i++) { + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, default_regs[i][0], default_regs[i][1]); + } + + // Delay 300 ms + if (!sensor->disable_delays) { + mp_hal_delay_ms(300); + } + + return ret; +} + +static int sleep(sensor_t *sensor, int enable) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM2, ®); + + if (enable) { + reg |= COM2_SOFT_SLEEP; + } else { + reg &= ~COM2_SOFT_SLEEP; + } + + // Write back register + return omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM2, reg) | ret; +} + +static int read_reg(sensor_t *sensor, uint16_t reg_addr) { + uint8_t reg_data; + if (omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, reg_addr, ®_data) != 0) { + return -1; + } + return reg_data; +} + +static int write_reg(sensor_t *sensor, uint16_t reg_addr, uint16_t reg_data) { + return omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, reg_addr, reg_data); +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) { + int ret = 0; + const uint8_t(*regs)[2]; + + switch (pixformat) { + case PIXFORMAT_RGB565: + regs = rgb565_regs; + break; + case PIXFORMAT_YUV422: + case PIXFORMAT_GRAYSCALE: + regs = yuv422_regs; + break; + default: + return -1; + } + + // Write pixel format registers + for (int i = 0; regs[i][0] != 0xff; i++) { + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, regs[i][0], regs[i][1]); + } + + return ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) { + int ret = 0; + const uint8_t(*regs)[2]; + + switch (framesize) { + case FRAMESIZE_VGA: + regs = vga_regs; + break; + case FRAMESIZE_QVGA: + regs = qvga_regs; + break; + case FRAMESIZE_QQVGA: + regs = qqvga_regs; + break; + default: + return -1; + } + + // Write pixel format registers + for (int i = 0; regs[i][0] != 0xFF; i++) { + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, regs[i][0], regs[i][1]); + } + return ret; +} + +static int set_hmirror(sensor_t *sensor, int enable) { + uint8_t val; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, MVFP, &val); + ret |= + omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, MVFP, + enable ? (val | MVFP_MIRROR) : (val & (~MVFP_MIRROR))); + + return ret; +} + +static int set_vflip(sensor_t *sensor, int enable) { + uint8_t val; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, MVFP, &val); + ret |= + omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, MVFP, + enable ? (val | MVFP_VFLIP) : (val & (~MVFP_VFLIP))); + + return ret; +} +int ov7670_init(sensor_t *sensor) { + // Initialize sensor structure. + sensor->reset = reset; + sensor->sleep = sleep; + sensor->read_reg = read_reg; + sensor->write_reg = write_reg; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + + // Set sensor flags + sensor->hw_flags.vsync = 1; + sensor->hw_flags.hsync = 0; + sensor->hw_flags.pixck = 1; + sensor->hw_flags.fsync = 0; + sensor->hw_flags.jpege = 0; + sensor->hw_flags.gs_bpp = 2; + sensor->hw_flags.rgb_swap = 1; + sensor->hw_flags.yuv_order = SENSOR_HW_FLAGS_YVU422; + + return 0; +} +#endif // (OMV_ENABLE_OV7670 == 1) diff --git a/components/3rd_party/omv/omv/sensors/ov7670.h b/components/3rd_party/omv/omv/sensors/ov7670.h new file mode 100644 index 00000000..5b582ebb --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/ov7670.h @@ -0,0 +1,15 @@ +/* + * 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. + * + * OV7670 driver. + */ +#ifndef __OV7670_H__ +#define __OV7670_H__ +#define OV7670_XCLK_FREQ 24000000 +int ov7670_init(sensor_t *sensor); +#endif // __OV7725_H__ diff --git a/components/3rd_party/omv/omv/sensors/ov7670_regs.h b/components/3rd_party/omv/omv/sensors/ov7670_regs.h new file mode 100644 index 00000000..2a24fa22 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/ov7670_regs.h @@ -0,0 +1,357 @@ +/* + * 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. + * + * OV7670 register definitions. + */ +#ifndef __REG_REGS_H__ +#define __REG_REGS_H__ + +#define GAIN 0x00 /* AGC - Gain control gain setting */ +#define BLUE 0x01 /* AWB - Blue channel gain setting */ +#define RED 0x02 /* AWB - Red channel gain setting */ +#define VREF 0x03 /* Vertical Frame Control */ +#define COM1 0x04 /* Common Control 1 */ +#define BAVE 0x05 /* U/B Average Level */ +#define GBAVE 0x06 /* Y/Gb Average Level */ +#define AECHH 0x07 /* Exposure Value - AEC MSB 5 bits */ +#define RAVE 0x08 /* V/R Average Level */ + +#define COM2 0x09 /* Common Control 2 */ +#define COM2_SOFT_SLEEP 0x10 /* Soft sleep mode */ +#define COM2_OUT_DRIVE_1x 0x00 /* Output drive capability 1x */ +#define COM2_OUT_DRIVE_2x 0x01 /* Output drive capability 2x */ +#define COM2_OUT_DRIVE_3x 0x02 /* Output drive capability 3x */ +#define COM2_OUT_DRIVE_4x 0x03 /* Output drive capability 4x */ + +#define PID 0x0A /* Product ID Number MSB */ +#define VER 0x0B /* Product ID Number LSB */ + +#define COM3 0x0C /* Common Control 3 */ +#define COM3_SWAP_MSB 0x40 /* Output data MSB and LSB swap */ +#define COM3_TRI_CLOCK 0x20 /* Tri-state option for output clock at power-down period */ +#define COM3_TRI_DATA 0x10 /* Tri-state option for output data at power-down period */ +#define COM3_SCALE_EN 0x08 /* Scale enable */ +#define COM3_DCW_EN 0x04 /* DCW enable */ + +#define COM4 0x0D /* Common Control 4 */ +#define COM4_AEC_FULL 0x00 /* AEC evaluate full window */ +#define COM4_AEC_1_2 0x10 /* AEC evaluate 1/2 window */ +#define COM4_AEC_1_4 0x20 /* AEC evaluate 1/4 window */ +#define COM4_AEC_2_3 0x30 /* AEC evaluate 2/3 window */ + +#define COM5 0x0E /* Reserved */ + +#define COM6 0x0F /* Common Control 6 */ +#define COM6_HREF_BLACK_EN 0x80 /* Enable HREF at optical black */ +#define COM6_RESET_TIMING 0x02 /* Reset all timing when format changes */ + +#define AECH 0x10 /* AEC[9:2] (see registers AECHH for AEC[15:10] and COM1 for AEC[1:0]) */ + +#define CLKRC 0x11 /* Internal Clock */ +#define CLKRC_PRESCALER_BYPASS 0x40 /* Use external clock directly */ +#define CLKRC_PRESCALER 0x3F /* Internal clock pre-scaler */ + +#define COM7 0x12 /* Common Control 7 */ +#define COM7_RESET 0x80 /* SCCB Register Reset */ +#define COM7_RES_VGA 0x00 /* Resolution VGA */ +#define COM7_RES_CIF 0x20 /* Resolution CIF */ +#define COM7_RES_QVGA 0x10 /* Resolution QVGA */ +#define COM7_RES_QCIF 0x08 /* Resolution QCIF */ +#define COM7_RGB_FMT 0x04 /* Output format RGB */ +#define COM7_CBAR_EN 0x02 /* Color bar selection */ +#define COM7_FMT_RAW 0x01 /* Output format Bayer RAW */ + +#define COM8 0x13 /* Common Control 8 */ +#define COM8_FAST_AEC 0x80 /* Enable fast AGC/AEC algorithm */ +#define COM8_AEC_STEP 0x40 /* AEC - Step size unlimited step size */ +#define COM8_BANDF_EN 0x20 /* Banding filter ON/OFF */ +#define COM8_AGC_EN 0x04 /* AGC Enable */ +#define COM8_AWB_EN 0x02 /* AWB Enable */ +#define COM8_AEC_EN 0x01 /* AEC Enable */ + +#define COM9 0x14 /* Common Control 9 */ +#define COM9_AGC_GAIN_2x 0x00 /* Automatic Gain Ceiling 2x */ +#define COM9_AGC_GAIN_4x 0x10 /* Automatic Gain Ceiling 4x */ +#define COM9_AGC_GAIN_8x 0x20 /* Automatic Gain Ceiling 8x */ +#define COM9_AGC_GAIN_16x 0x30 /* Automatic Gain Ceiling 16x */ +#define COM9_AGC_GAIN_32x 0x40 /* Automatic Gain Ceiling 32x */ +#define COM9_AGC_GAIN_64x 0x50 /* Automatic Gain Ceiling 64x */ +#define COM9_AGC_GAIN_128x 0x60 /* Automatic Gain Ceiling 128x */ +#define COM9_SET_AGC(r, x) ((r & 0x8F) | ((x & 0x07) << 4)) + +#define COM10 0x15 /* Common Control 10 */ +#define COM10_HSYNC_EN 0x40 /* HREF changes to HSYNC */ +#define COM10_PCLK_FREE 0x00 /* PCLK output option: free running PCLK */ +#define COM10_PCLK_MASK 0x20 /* PCLK output option: masked during horizontal blank */ +#define COM10_PCLK_REV 0x10 /* PCLK reverse */ +#define COM10_HREF_REV 0x08 /* HREF reverse */ +#define COM10_VSYNC_FALLING 0x00 /* VSYNC changes on falling edge of PCLK */ +#define COM10_VSYNC_RISING 0x04 /* VSYNC changes on rising edge of PCLK */ +#define COM10_VSYNC_NEG 0x02 /* VSYNC negative */ +#define COM10_HSYNC_NEG 0x01 /* HSYNC negative */ + +#define HSTART 0x17 /* Output Format - Horizontal Frame (HREF column) start high 8-bit */ +#define HSTOP 0x18 /* Output Format - Horizontal Frame (HREF column) end high 8-bit */ +#define VSTART 0x19 /* Output Format - Vertical Frame (row) start high 8-bit */ +#define VSTOP 0x1A /* Output Format - Vertical Frame (row) end high 8-bit */ +#define PSHFT 0x1B /* Data Format - Pixel Delay Select (delays timing of the D[7:0]) */ +#define MIDH 0x1C /* Manufacturer ID Byte - High */ +#define MIDL 0x1D /* Manufacturer ID Byte - Low */ + +#define MVFP 0x1E /* Mirror/VFlip Enable */ +#define MVFP_MIRROR 0x20 /* Mirror image */ +#define MVFP_VFLIP 0x10 /* VFlip image */ +#define MVFP_BLACK_SUN_EN 0x04 /* Black sun enable */ + +#define LAEC 0x1F /* Reserved */ + +#define ADCCTR0 0x20 /* ADC Control */ +#define ADCCTR0_ADC_RANGE_1X 0x00 /* ADC range adjustment 1x range */ +#define ADCCTR0_ADC_RANGE_1_5X 0x08 /* ADC range adjustment 1.5x range */ +#define ADCCTR0_ADC_REF_0_8 0x00 /* ADC reference adjustment 0.8x */ +#define ADCCTR0_ADC_REF_1_0 0x04 /* ADC reference adjustment 1.0x */ +#define ADCCTR0_ADC_REF_1_2 0x07 /* ADC reference adjustment 1.2x */ + +#define ADCCTR1 0x21 /* Reserved */ +#define ADCCTR2 0x22 /* Reserved */ +#define ADCCTR3 0x23 /* Reserved */ + +#define AEW 0x24 /* AGC/AEC - Stable Operating Region (Upper Limit) */ +#define AEB 0x25 /* AGC/AEC - Stable Operating Region (Lower Limit) */ +#define VPT 0x26 /* AGC/AEC Fast Mode Operating Region */ +#define BBIAS 0x27 /* B Channel Signal Output Bias (effective only when COM6[3] = 1) */ +#define GBIAS 0x28 /* Gb Channel Signal Output Bias (effective only when COM6[3] = 1) */ +#define EXHCH 0x2A /* Dummy Pixel Insert MSB */ +#define EXHCL 0x2B /* Dummy Pixel Insert LSB */ +#define RBIAS 0x2C /* R Channel Signal Output Bias (effective only when COM6[3] = 1) */ +#define ADVFL 0x2D /* LSB of insert dummy lines in vertical direction (1 bit equals 1 line) */ +#define ADVFH 0x2E /* MSB of insert dummy lines in vertical direction */ +#define YAVE 0x2F /* Y/G Channel Average Value */ + +#define HSYST 0x30 /* HSYNC Rising Edge Delay (low 8 bits) */ +#define HSYEN 0x31 /* HSYNC Falling Edge Delay (low 8 bits) */ +#define HREF 0x32 /* HREF Control */ +#define CHLF 0x33 /* Array Current Control */ +#define ARBLM 0x34 /* Array Reference Control */ +#define ADC 0x37 /* ADC Control */ +#define ACOM 0x38 /* ADC and Analog Common Mode Control */ +#define OFON 0x39 /* ADC Offset Control */ + +#define TSLB 0x3A /* Line Buffer Test Option */ +#define TSLB_NEGATIVE 0x20 /* Negative image enable */ +#define TSLB_UV_NORMAL 0x00 /* UV output value normal */ +#define TSLB_UV_FIXED 0x10 /* UV output value set in registers MANU and MANV */ +#define TSLB_AUTO_WINDOW 0x01 /* automatically sets output window when resolution changes */ + +#define COM11 0x3B /* Common Control 11 */ +#define COM11_AFR 0x80 /* Auto frame rate control ON/OFF selection (night mode) */ +#define COM11_AFR_0 0x00 /* No reduction of frame rate */ +#define COM11_AFR_1_2 0x20 /* Max reduction to 1/2 frame rate */ +#define COM11_AFR_1_4 0x40 /* Max reduction to 1/4 frame rate */ +#define COM11_AFR_1_8 0x60 /* Max reduction to 1/8 frame rate */ +#define COM11_HZAUTO_EN 0x10 /* Enable 50/60 Hz auto detection */ +#define COM11_BANDF_SELECT 0x08 /* Banding filter value selection */ +#define COM11_AEC_BANDF 0x02 /* Enable AEC below banding value */ + +#define COM12 0x3C /* Common Control 12 */ +#define COM12_HREF_EN 0x80 /* Always has HREF */ + +#define COM13 0x3D /* Common Control 13 */ +#define COM13_GAMMA_EN 0x80 /* Gamma enable */ +#define COM13_UVSAT_AUTO 0x40 /* UV saturation level - UV auto adjustment. */ +#define COM13_UV_SWAP 0x01 /* UV swap */ + +#define COM14 0x3E /* Common Control 14 */ +#define COM14_DCW_EN 0x10 /* DCW and scaling PCLK */ +#define COM14_MANUAL_SCALE 0x08 /* Manual scaling enable for pre-defined resolutions */ +#define COM14_PCLK_DIV_1 0x00 /* PCLK divider. Divided by 1 */ +#define COM14_PCLK_DIV_2 0x01 /* PCLK divider. Divided by 2 */ +#define COM14_PCLK_DIV_4 0x02 /* PCLK divider. Divided by 4 */ +#define COM14_PCLK_DIV_8 0x03 /* PCLK divider. Divided by 8 */ +#define COM14_PCLK_DIV_16 0x04 /* PCLK divider. Divided by 16 */ + +#define EDGE 0x3F /* Edge Enhancement Adjustment */ +#define EDGE_FACTOR_MASK 0x1F /* Edge enhancement factor */ + +#define COM15 0x40 /* Common Control 15 */ +#define COM15_OUT_10_F0 0x00 /* Output data range 10 to F0 */ +#define COM15_OUT_01_FE 0x80 /* Output data range 01 to FE */ +#define COM15_OUT_00_FF 0xC0 /* Output data range 00 to FF */ +#define COM15_FMT_RGB_NORMAL 0x00 /* Normal RGB normal output */ +#define COM15_FMT_RGB565 0x10 /* Normal RGB 565 output */ +#define COM15_FMT_RGB555 0x30 /* Normal RGB 555 output */ + +#define COM16 0x41 /* Common Control 16 */ +#define COM16_EDGE_EN 0x20 /* Enable edge enhancement threshold auto-adjustment */ +#define COM16_DENOISE_EN 0x10 /* De-noise threshold auto-adjustment */ +#define COM16_AWB_GAIN_EN 0x08 /* AWB gain enable */ +#define COM16_COLOR_MTX_EN 0x02 /* Color matrix coefficient double option */ + +#define COM17 0x42 /* Common Control 17 */ +#define COM17_AEC_FULL 0x00 /* AEC evaluate full window */ +#define COM17_AEC_1_2 0x40 /* AEC evaluate 1/2 window */ +#define COM17_AEC_1_4 0x80 /* AEC evaluate 1/4 window */ +#define COM17_AEC_2_3 0xC0 /* AEC evaluate 2/3 window */ +#define COM17_DSP_CBAR_EN 0x08 /* DSP color bar enable */ + +#define AWBC1 0x43 /* Reserved */ +#define AWBC2 0x44 /* Reserved */ +#define AWBC3 0x45 /* Reserved */ +#define AWBC4 0x46 /* Reserved */ +#define AWBC5 0x47 /* Reserved */ +#define AWBC6 0x48 /* Reserved */ + +#define REG4B 0x4B /* REG4B */ +#define REG4B_UV_AVG_EN 0x01 /* UV average enable */ + +#define DNSTH 0x4C /* De-noise Threshold*/ + +#define MTX1 0x4F /* Matrix Coefficient 1 */ +#define MTX2 0x50 /* Matrix Coefficient 2 */ +#define MTX3 0x51 /* Matrix Coefficient 3 */ +#define MTX4 0x52 /* Matrix Coefficient 4 */ +#define MTX5 0x53 /* Matrix Coefficient 5 */ +#define MTX6 0x54 /* Matrix Coefficient 6 */ + +#define BRIGHTNESS 0x55 /* Brightness Control */ +#define CONTRAST 0x56 /* Contrast Control */ +#define CONTRAST_CENTER 0x57 /* Contrast Center */ +#define MTXS 0x58 /* Matrix Coefficient Sign for coefficient 5 to 0*/ +#define LCC1 0x62 /* Lens Correction Option 1 */ +#define LCC2 0x63 /* Lens Correction Option 2 */ +#define LCC3 0x64 /* Lens Correction Option 3 */ +#define LCC4 0x65 /* Lens Correction Option 4 */ +#define LCC5 0x66 /* Lens Correction Control */ +#define LCC5_LC_EN 0x01 /* Lens Correction Enable */ +#define LCC5_LC_CTRL 0x04 /* Lens correction control select */ + +#define MANU 0x67 /* Manual U Value (effective only when register TSLB[4] is high) */ +#define MANV 0x68 /* Manual V Value (effective only when register TSLB[4] is high) */ + +#define GFIX 0x69 /* Fix Gain Control */ +#define GGAIN 0x6A /* G Channel AWB Gain */ + +#define DBLV 0x6B /* PLL control */ +#define DBLV_PLL_BYPASS 0x00 /* Bypass PLL */ +#define DBLV_PLL_4x 0x40 /* Input clock 4x */ +#define DBLV_PLL_6x 0x80 /* Input clock 8x */ +#define DBLV_PLL_8x 0xC0 /* Input clock 16x */ +#define DBLV_REG_BYPASS 0x10 /* Bypass internal regulator */ + +#define AWBCTR3 0x6C /* AWB Control 3 */ +#define AWBCTR2 0x6D /* AWB Control 2 */ +#define AWBCTR1 0x6E /* AWB Control 1 */ +#define AWBCTR0 0x6F /* AWB Control 0 */ + +#define SCALING_XSC 0x70 /* Test Pattern[0] */ +#define SCALING_YSC 0x71 /* Test Pattern[1] */ +#define SCALING_DCWCTR 0x72 /* DCW control parameter */ + +#define SCALING_PCLK_DIV 0x73 /* Clock divider control for DSP.*/ +#define SCALING_PCLK_DIV_BYPASS 0x08 /* Bypass clock divider for DSP scale control */ +#define SCALING_PCLK_DIV_1 0x00 /* Divided by 1 */ +#define SCALING_PCLK_DIV_2 0x01 /* Divided by 2 */ +#define SCALING_PCLK_DIV_4 0x02 /* Divided by 4 */ +#define SCALING_PCLK_DIV_8 0x03 /* Divided by 8 */ +#define SCALING_PCLK_DIV_16 0x04 /* Divided by 16 */ + +#define REG74 0x74 /* REG74 */ +#define REG74_DGAIN_EN 0x10 /* Digital gain control by REG74[1:0] */ +#define REG74_DGAIN_BYPASS 0x00 /* Bypass */ +#define REG74_DGAIN_1X 0x01 /* 1x */ +#define REG74_DGAIN_2X 0x02 /* 2x */ +#define REG74_DGAIN_4X 0x03 /* 4x */ + +#define REG75 0x75 /* REG75 */ +#define REG75_EDGE_LOWER 0x1F /* Edge enhancement lower limit */ + +#define REG76 0x76 /* REG76 */ +#define REG76_WHITE_PIX_CORR 0x80 /* White pixel correction enable */ +#define REG76_BLACK_PIX_CORR 0x40 /* Black pixel correction enable */ +#define REG76_EDGE_HIGHER 0x1F /* Edge enhancement higher limit */ + +#define REG77 0x77 /* Offset, de-noise range control */ + +#define SLOP 0x7A /* Gamma Curve Highest Segment Slope */ +#define GAM1 0x7B /* Gamma Curve 1st Segment Input End Point 0x04 Output Value */ +#define GAM2 0x7C /* Gamma Curve 2nd Segment Input End Point 0x08 Output Value */ +#define GAM3 0x7D /* Gamma Curve 3rd Segment Input End Point 0x10 Output Value */ +#define GAM4 0x7E /* Gamma Curve 4th Segment Input End Point 0x20 Output Value */ +#define GAM5 0x7F /* Gamma Curve 5th Segment Input End Point 0x28 Output Value */ +#define GAM6 0x80 /* Gamma Curve 6th Segment Input End Point 0x30 Output Value */ +#define GAM7 0x81 /* Gamma Curve 7th Segment Input End Point 0x38 Output Value */ +#define GAM8 0x82 /* Gamma Curve 8th Segment Input End Point 0x40 Output Value */ +#define GAM9 0x83 /* Gamma Curve 9th Segment Input Enpd Point 0x48 Output Value */ +#define GAM10 0x84 /* Gamma Curve 10th Segment Input End Point 0x50 Output Value */ +#define GAM11 0x85 /* Gamma Curve 11th Segment Input End Point 0x60 Output Value */ +#define GAM12 0x86 /* Gamma Curve 12th Segment Input End Point 0x70 Output Value */ +#define GAM13 0x87 /* Gamma Curve 13th Segment Input End Point 0x90 Output Value */ +#define GAM14 0x88 /* Gamma Curve 14th Segment Input End Point 0xB0 Output Value */ +#define GAM15 0x89 /* Gamma Curve 15th Segment Input End Point 0xD0 Output Value */ + +#define RGB444 0x8C /* REG444 */ +#define RGB444_RGB444_EN 0x02 /* RGB444 enable, effective only when COM15[4] is high */ +#define RGB444_RGB444_FMT 0x01 /* RGB444 word format. 0 = xR GB 1 = RG Bx */ + +#define DM_LNL 0x92 /* Dummy Line low 8 bits */ +#define DM_LNH 0x93 /* Dummy Line high 8 bits */ +#define LCC6 0x94 /* RW Lens Correction Option 6 (effective only when LCC5[2] is high) */ +#define LCC7 0x95 /* RW Lens Correction Option 7 (effective only when LCC5[2] is high) */ + +#define BD50ST 0x9D /* Hz Banding Filter Value (effective only when COM8[5] is high and COM11[3] is high) */ +#define BD60ST 0x9E /* Hz Banding Filter Value (effective only when COM8[5] is high and COM11[3] is low) */ +#define HAECC1 0x9F /* Histogram-based AEC/AGC Control 1 */ +#define HAECC2 0xA0 /* Histogram-based AEC/AGC Control 2 */ + +#define SCALING_PCLK 0xA2 /* Pixel Clock Delay */ +#define SCALING_PCLK_DELAY 0x7F /* Scaling output delay */ + +#define NT_CTRL 0xA4 /* Auto frame rate adjustment control */ +#define NT_CTRL_AFR_HALF 0x08 /* Reduce frame rate by half */ +#define NT_CTRL_AFR_PT_2X 0x00 /* Insert dummy row at 2x gain */ +#define NT_CTRL_AFR_PT_4X 0x01 /* Insert dummy row at 4x gain */ +#define NT_CTRL_AFR_PT_8X 0x02 /* Insert dummy row at 8x gain */ + +#define BD50MAX 0xA5 /* 50Hz Banding Step Limit */ +#define HAECC3 0xA6 /* Histogram-based AEC/AGC Control 3 */ +#define HAECC4 0xA7 /* Histogram-based AEC/AGC Control 4 */ +#define HAECC5 0xA8 /* Histogram-based AEC/AGC Control 5 */ +#define HAECC6 0xA9 /* Histogram-based AEC/AGC Control 6 */ + +#define HAECC7 0xAA /* AEC algorithm selection */ +#define HAECC7_AEC_AVG 0x00 /* Average-based AEC algorithm */ +#define HAECC7_AEC_HIST 0x80 /* Histogram-based AEC algorithm */ + +#define BD60MAX 0xAB /* 60Hz Banding Step Limit */ + +#define STR_OPT 0xAC /* Strobe Control */ +#define STR_OPT_EN 0x80 /* Strobe Enable */ +#define STR_OPT_CTRL 0x40 /* R/G/B gain controlled by STR_R (0xAD) STR_G (0xAE) STR_B (0xAF) for LED output frame */ +#define STR_OPT_XENON_1ROW 0x00 /* Xenon mode option 1 row */ +#define STR_OPT_XENON_2ROW 0x10 /* Xenon mode option 2 rows */ +#define STR_OPT_XENON_3ROW 0x20 /* Xenon mode option 3 rows */ +#define STR_OPT_XENON_4ROW 0x30 /* Xenon mode option 4 rows */ +#define STR_OPT_MODE_XENON 0x00 /* Mode select */ +#define STR_OPT_MODE_LED1 0x01 /* Mode select */ +#define STR_OPT_MODE_LED2 0x02 /* Mode select */ + +#define STR_R 0xAD /* R Gain for LED Output Frame */ +#define STR_G 0xAE /* G Gain for LED Output Frame */ +#define STR_B 0xAF /* B Gain for LED Output Frame */ + +#define ABLC1 0xB1 /* ABLC Control */ +#define ABLC1_EN 0x04 /* Enable ABLC function */ + +#define THL_ST 0xB3 /* ABLC Target */ + +#define AD_CHB 0xBE /* Blue Channel Black Level Compensation */ +#define AD_CHR 0xBF /* Red Channel Black Level Compensation */ +#define AD_CHGB 0xC0 /* Gb Channel Black Level Compensation */ +#define AD_CHGR 0xC1 /* Gr Channel Black Level Compensation */ +#define SATCTR 0xC9 /* UV Saturation Control */ +#endif //__REG_REGS_H__ diff --git a/components/3rd_party/omv/omv/sensors/ov7690.c b/components/3rd_party/omv/omv/sensors/ov7690.c new file mode 100644 index 00000000..f11bdd77 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/ov7690.c @@ -0,0 +1,622 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2020 Ibrahim Abdelkader + * Copyright (c) 2013-2020 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV7690 driver. + */ +#include "omv_boardconfig.h" +#if (OMV_ENABLE_OV7690 == 1) + +#include +#include +#include + +#include "omv_i2c.h" +#include "sensor.h" +#include "ov7690.h" +#include "ov7690_regs.h" +#include "py/mphal.h" + +static const uint8_t default_regs[][2] = { + +// From App Note. + + {0x0c, 0xd6}, + {0x48, 0x42}, + {0x27, 0x80}, + {0x64, 0x10}, + {0x68, 0xb4}, + {0x69, 0x12}, + {0x2f, 0x60}, + {0x41, 0x43}, + {0x44, 0x24}, + {0x4b, 0x0e}, + {0x4c, 0x7b}, + {0x4d, 0x0a}, + {0x29, 0x50}, + {0x1b, 0x19}, + {0x39, 0x80}, + {0x80, 0x7f}, + {0x81, 0xff}, + {0x91, 0x20}, + {0x21, 0x44}, + {0x11, 0x01}, + {0x12, 0x04}, // {0x12, 0x00}, + {0x82, 0x07}, // {0x82, 0x03}, + {0xd0, 0x48}, + {0x2b, 0x38}, + {0x15, 0x14}, + {0x16, 0x03}, + {0x17, 0x69}, + {0x18, 0xa4}, + {0x19, 0x0b}, // {0x19, 0x0c}, + {0x1a, 0xf6}, + {0x3e, 0x30}, + {0xc8, 0x02}, + {0xc9, 0x80}, + {0xca, 0x01}, + {0xcb, 0xe0}, + {0xcc, 0x02}, + {0xcd, 0x80}, + {0xce, 0x01}, + {0xcf, 0xe0}, + {0x80, 0x7f}, + {0x85, 0x10}, + {0x86, 0x00}, + {0x87, 0x00}, + {0x88, 0x00}, + {0x89, 0x35}, + {0x8a, 0x30}, + {0x8b, 0x33}, + {0xbb, 0xbe}, + {0xbc, 0xc0}, + {0xbd, 0x02}, + {0xbe, 0x16}, + {0xbf, 0xc2}, + {0xc0, 0xd9}, + {0xc1, 0x1e}, + {0xb4, 0x36}, + {0xb5, 0x06}, + {0xb7, 0x00}, + {0xb6, 0x04}, + {0xb8, 0x06}, + {0xb9, 0x02}, + {0xba, 0x00}, + {0x24, 0x78}, + {0x25, 0x68}, + {0x26, 0xb4}, + {0x81, 0xff}, + {0x5a, 0x30}, + {0x5b, 0xa5}, + {0x5c, 0x30}, + {0x5d, 0x20}, + {0xa3, 0x05}, + {0xa4, 0x10}, + {0xa5, 0x25}, + {0xa6, 0x46}, + {0xa7, 0x57}, + {0xa8, 0x64}, + {0xa9, 0x70}, + {0xaa, 0x7c}, + {0xab, 0x87}, + {0xac, 0x90}, + {0xad, 0x9f}, + {0xae, 0xac}, + {0xaf, 0xc1}, + {0xb0, 0xd5}, + {0xb1, 0xe7}, + {0xb2, 0x21}, + {0x8c, 0x5c}, + {0x8d, 0x11}, + {0x8e, 0x12}, + {0x8f, 0x19}, + {0x90, 0x50}, + {0x91, 0x21}, + {0x92, 0x9c}, + {0x93, 0x9b}, + {0x94, 0x0c}, + {0x95, 0x0d}, + {0x96, 0xff}, + {0x97, 0x00}, + {0x98, 0x3f}, + {0x99, 0x30}, + {0x9a, 0x4d}, + {0x9b, 0x3d}, + {0x9c, 0xf0}, + {0x9d, 0xf0}, + {0x9e, 0xf0}, + {0x9f, 0xff}, + {0xa0, 0x5f}, + {0xa1, 0x61}, + {0xa2, 0x0c}, + {0x14, 0x21}, + {0x13, 0xf7}, + +// OpenMV Custom. + + // Frame Rate Adjustment for 24 Mhz input clock - 30 fps, PCLK = 24Mhz + {0x11, 0x00}, + {0x29, 0x50}, + {0x2a, 0x30}, + {0x2b, 0x08}, + {0x2c, 0x00}, + {0x15, 0x00}, + {0x2d, 0x00}, + {0x2e, 0x00}, + + // Night Mode with Auto Frame Rate - For 24Mhz/26Mhz Clock Input - 30fps ~ 3.75 night mode for 60Hz light environment + {0x11, 0x00}, + {0x15, 0xcc}, + + // Banding Filter Settings for 24MHz Input Clock - 30fps for 50/60Hz light frequency + {0x13, 0xef}, // banding filter enable + {0x50, 0x99}, // 50Hz banding filter + {0x51, 0x7f}, // 60Hz banding filter + {0x21, 0x34}, // 3 step for 50hz, 4 step for 60hz + {0x14, 0xb2}, // Auto detect banding filter + + // Simple White Balance + {0x13, 0xef}, // AWB on + {0x8e, 0x92}, // enable simple AWB + +// End. + + {0x00, 0x00}, +}; + +#define NUM_CONTRAST_LEVELS (9) +static const uint8_t contrast_regs[NUM_CONTRAST_LEVELS][1] = { + {0xd0}, /* -4 */ + {0x80}, /* -3 */ + {0x48}, /* -2 */ + {0x20}, /* -1 */ + {0x00}, /* 0 */ + {0x00}, /* +1 */ + {0x00}, /* +2 */ + {0x00}, /* +3 */ + {0x00}, /* +4 */ +}; + +#define NUM_SATURATION_LEVELS (9) + +static int reset(sensor_t *sensor) { + // Reset all registers + int ret = omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG12, 0x80); + + // Delay 2 ms + mp_hal_delay_ms(2); + + // Write default registers + for (int i = 0; default_regs[i][0]; i++) { + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, default_regs[i][0], default_regs[i][1]); + } + + // Delay 300 ms + if (!sensor->disable_delays) { + mp_hal_delay_ms(300); + } + + return ret; +} + +static int sleep(sensor_t *sensor, int enable) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG0E, ®); + + if (enable) { + reg |= 0x8; + } else { + reg &= ~0x8; + } + + // Write back register + return omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG0E, reg) | ret; +} + +static int read_reg(sensor_t *sensor, uint16_t reg_addr) { + uint8_t reg_data; + if (omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, reg_addr, ®_data) != 0) { + return -1; + } + return reg_data; +} + +static int write_reg(sensor_t *sensor, uint16_t reg_addr, uint16_t reg_data) { + return omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, reg_addr, reg_data); +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) { + if ((pixformat == PIXFORMAT_BAYER) && sensor->framesize && (sensor->framesize != FRAMESIZE_VGA)) { + // bayer for vga only + return -1; + } + + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG12, ®); + + switch (pixformat) { + case PIXFORMAT_RGB565: + reg = (reg & 0xFC) | 0x2; + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG3E, 0x30); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG82, 0x07); + break; + case PIXFORMAT_YUV422: + case PIXFORMAT_GRAYSCALE: + reg = (reg & 0xFC) | 0x0; + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG3E, 0x30); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG82, 0x07); + break; + case PIXFORMAT_BAYER: + reg = (reg & 0xFC) | 0x1; + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG3E, 0x20); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG82, 0x00); + break; + default: + return -1; + } + + // Write back register + return omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG12, reg) | ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) { + uint8_t reg; + int ret = 0; + uint16_t w = resolution[framesize][0]; + uint16_t h = resolution[framesize][1]; + bool vflip; + + if (((w > 640) || (h > 480)) + || (sensor->pixformat && (sensor->pixformat == PIXFORMAT_BAYER) && (framesize != FRAMESIZE_VGA))) { + // bayer for vga only + return -1; + } + + // Sample VFLIP + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG0C, ®); + vflip = !(reg & 0x80); + + if ((w <= 320) && (h <= 240)) { + // Set QVGA Resolution + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG12, ®); + reg = (reg & 0xBF) | 0x40; + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG12, reg); + + // Set QVGA Window Size + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG19, 0x03 - vflip); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REGC8, 0x02); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REGC9, 0x80); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REGCA, 0x00); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REGCB, 0xF0); + } else { + // Set VGA Resolution + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG12, ®); + reg = (reg & 0xBF) | 0x00; + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG12, reg); + + // Set VGA Window Size + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG19, 0x0b - vflip); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REGC8, 0x02); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REGC9, 0x80); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REGCA, 0x01); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REGCB, 0xE0); + } + + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REGCC, w >> 8); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REGCD, w); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REGCE, h >> 8); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REGCF, h); + + return ret; +} + +static int set_contrast(sensor_t *sensor, int level) { + uint8_t reg; + int ret = 0; + int new_level = NUM_CONTRAST_LEVELS / 2; + if (new_level < 0 || new_level >= NUM_CONTRAST_LEVELS) { + return -1; + } + + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0xd5, 0x20); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0xd4, 0x10 + (4 * new_level)); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0xd3, contrast_regs[new_level][0]); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REGD2, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REGD2, (reg & 0xFB) | ((level != 0) << 2)); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, 0xdc, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0xdc, (level < 0) ? (reg | 0x04) : (reg & 0xFB)); + return ret; +} + +static int set_brightness(sensor_t *sensor, int level) { + return 0; +} + +static int set_saturation(sensor_t *sensor, int level) { + uint8_t reg; + int ret = 0; + int new_level = NUM_SATURATION_LEVELS / 2; + if (new_level < 0 || new_level >= NUM_SATURATION_LEVELS) { + return -1; + } + + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REGD8, 0x10 * new_level); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REGD9, 0x10 * new_level); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REGD2, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REGD2, (reg & 0xFD) | ((level != 0) << 1)); + return ret; +} + +static int set_gainceiling(sensor_t *sensor, gainceiling_t gainceiling) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG14, ®); + + // Set gain ceiling + reg = (reg & 0x8F) | (gainceiling << 4); + return omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG14, reg) | ret; +} + +static int set_quality(sensor_t *sensor, int qs) { + return 0; +} + +static int set_colorbar(sensor_t *sensor, int enable) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG82, ®); + + // Enable colorbars + reg = (reg & 0xF7) | ((enable != 0) << 3); + return omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG82, reg) | ret; +} + +static int set_auto_gain(sensor_t *sensor, int enable, float gain_db, float gain_db_ceiling) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG13, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG13, (reg & 0xFB) | ((enable != 0) << 2)); + + if ((enable == 0) && (!isnanf(gain_db)) && (!isinff(gain_db))) { + float gain = IM_MAX(IM_MIN(expf((gain_db / 20.0f) * M_LN10), 128.0f), 1.0f); + + int gain_temp = fast_ceilf(logf(IM_MAX(gain / 2.0f, 1.0f)) / M_LN2); + int gain_hi = 0x3F >> (6 - gain_temp); + int gain_lo = IM_MIN(fast_roundf(((gain / (1 << gain_temp)) - 1.0f) * 16.0f), 15); + + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, GAIN, (gain_hi << 4) | (gain_lo << 0)); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG15, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG15, (reg & 0xFC) | (gain_hi >> 4)); + } else if ((enable != 0) && (!isnanf(gain_db_ceiling)) && (!isinff(gain_db_ceiling))) { + float gain_ceiling = IM_MAX(IM_MIN(expf((gain_db_ceiling / 20.0f) * M_LN10), 128.0f), 2.0f); + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG14, ®); + ret |= + omv_i2c_writeb(&sensor->i2c_bus, + sensor->slv_addr, + REG14, + (reg & 0x8F) | ((fast_ceilf(logf(gain_ceiling) / M_LN2) - 1) << 4)); + } + + return ret; +} + +static int get_gain_db(sensor_t *sensor, float *gain_db) { + uint8_t gain, reg15; + int ret = 0; + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, GAIN, &gain); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG15, ®15); + + int hi_gain = 1 << + (((reg15 >> + 1) & 1) + + ((reg15 >> 0) & 1) + ((gain >> 7) & 1) + ((gain >> 6) & 1) + ((gain >> 5) & 1) + ((gain >> 4) & 1)); + float lo_gain = 1.0f + (((gain >> 0) & 0xF) / 16.0f); + *gain_db = 20.0f * log10f(hi_gain * lo_gain); + + return ret; +} + +static int set_auto_exposure(sensor_t *sensor, int enable, int exposure_us) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG13, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG13, (reg & 0xFE) | ((enable != 0) << 0)); + + if ((enable == 0) && (exposure_us >= 0)) { + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG12, ®); + int t_line = (reg & 0x40) ? (320 + 456) : (640 + 136); + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG3E, ®); + int t_pclk = (reg & 0x10) ? 2 : 1; + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, PLL, ®); + int pll_div = reg >> 6, pll_mult = 1; + + switch ((reg >> 4) & 0x3) { + case 0: pll_div = 1; break; + case 1: pll_mult = 4; break; + case 2: pll_mult = 6; break; + case 3: pll_mult = 8; break; + } + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, CLKRC, ®); + int clk_rc; + + if (reg & 0x40) { + clk_rc = 1; + } else { + clk_rc = (reg & 0x3F) + 1; + } + + int exposure = + IM_MAX(IM_MIN(((exposure_us * ((((OV7690_XCLK_FREQ / clk_rc) * pll_mult) / pll_div) / 1000000)) / t_pclk) / t_line, + 0xFFFF), 0x0000); + + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, AECL, ((exposure >> 0) & 0xFF)); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, AECH, ((exposure >> 8) & 0xFF)); + } + + return ret; +} + +static int get_exposure_us(sensor_t *sensor, int *exposure_us) { + uint8_t aec_l, aec_h, reg; + int ret = 0; + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, AECL, &aec_l); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, AECH, &aec_h); + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG12, ®); + int t_line = (reg & 0x40) ? (320 + 456) : (640 + 136); + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG3E, ®); + int t_pclk = (reg & 0x10) ? 2 : 1; + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, PLL, ®); + int pll_div = reg >> 6, pll_mult = 1; + + switch ((reg >> 4) & 0x3) { + case 0: pll_div = 1; break; + case 1: pll_mult = 4; break; + case 2: pll_mult = 6; break; + case 3: pll_mult = 8; break; + } + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, CLKRC, ®); + int clk_rc; + + if (reg & 0x40) { + clk_rc = 1; + } else { + clk_rc = (reg & 0x3F) + 1; + } + + *exposure_us = + (((aec_h << 8) + (aec_l << 0)) * t_line * t_pclk) / ((((OV7690_XCLK_FREQ / clk_rc) * pll_mult) / pll_div) / 1000000); + + return ret; +} + +static int set_auto_whitebal(sensor_t *sensor, int enable, float r_gain_db, float g_gain_db, float b_gain_db) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG13, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG13, (reg & 0xFD) | ((enable != 0) << 1)); + + if ((enable == 0) && (!isnanf(r_gain_db)) && (!isnanf(g_gain_db)) && (!isnanf(b_gain_db)) + && (!isinff(r_gain_db)) && (!isinff(g_gain_db)) && (!isinff(b_gain_db))) { + + int r_gain = IM_MAX(IM_MIN(fast_roundf(expf((r_gain_db / 20.0f) * M_LN10)), 255), 0); + int g_gain = IM_MAX(IM_MIN(fast_roundf(expf((g_gain_db / 20.0f) * M_LN10)), 255), 0); + int b_gain = IM_MAX(IM_MIN(fast_roundf(expf((b_gain_db / 20.0f) * M_LN10)), 255), 0); + + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BGAIN, b_gain); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, RGAIN, r_gain); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, GGAIN, g_gain); + } + + return ret; +} + +static int get_rgb_gain_db(sensor_t *sensor, float *r_gain_db, float *g_gain_db, float *b_gain_db) { + uint8_t blue, red, green; + int ret = 0; + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, BGAIN, &blue); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, RGAIN, &red); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, GGAIN, &green); + + *r_gain_db = 20.0f * log10f(red); + *g_gain_db = 20.0f * log10f(green); + *b_gain_db = 20.0f * log10f(blue); + + return ret; +} + +static int set_hmirror(sensor_t *sensor, int enable) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG0C, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG0C, (reg & 0xBF) | ((enable == 0) << 6)); + + return ret; +} + +static int set_vflip(sensor_t *sensor, int enable) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG0C, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG0C, (reg & 0x7F) | ((enable == 0) << 7)); + // Apply new vertical flip setting. + ret |= set_framesize(sensor, sensor->framesize); + + return ret; +} + +static int set_special_effect(sensor_t *sensor, sde_t sde) { + int ret = 0; + + switch (sde) { + case SDE_NEGATIVE: + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0x28, 0x80); + break; + case SDE_NORMAL: + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, 0x28, 0x00); + break; + default: + return -1; + } + + return ret; +} + +static int set_lens_correction(sensor_t *sensor, int enable, int radi, int coef) { + int ret = 0; + + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, LCC0, (enable != 0) << 4); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, LCC1, radi); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, LCC4, coef); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, LCC5, coef); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, LCC6, coef); + + return ret; +} + +int ov7690_init(sensor_t *sensor) { + // Initialize sensor structure. + sensor->reset = reset; + sensor->sleep = sleep; + sensor->read_reg = read_reg; + sensor->write_reg = write_reg; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_contrast = set_contrast; + sensor->set_brightness = set_brightness; + sensor->set_saturation = set_saturation; + sensor->set_gainceiling = set_gainceiling; + sensor->set_quality = set_quality; + sensor->set_colorbar = set_colorbar; + sensor->set_auto_gain = set_auto_gain; + sensor->get_gain_db = get_gain_db; + sensor->set_auto_exposure = set_auto_exposure; + sensor->get_exposure_us = get_exposure_us; + sensor->set_auto_whitebal = set_auto_whitebal; + sensor->get_rgb_gain_db = get_rgb_gain_db; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + sensor->set_special_effect = set_special_effect; + sensor->set_lens_correction = set_lens_correction; + + // Set sensor flags + sensor->hw_flags.vsync = 1; + sensor->hw_flags.hsync = 0; + sensor->hw_flags.pixck = 1; + sensor->hw_flags.fsync = 0; + sensor->hw_flags.jpege = 0; + sensor->hw_flags.gs_bpp = 2; + sensor->hw_flags.rgb_swap = 1; + + return 0; +} +#endif //(OMV_ENABLE_OV7690 == 1) diff --git a/components/3rd_party/omv/omv/sensors/ov7690.h b/components/3rd_party/omv/omv/sensors/ov7690.h new file mode 100644 index 00000000..dc5cc1b8 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/ov7690.h @@ -0,0 +1,15 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2020 Ibrahim Abdelkader + * Copyright (c) 2013-2020 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV7690 driver. + */ +#ifndef __OV7690_H__ +#define __OV7690_H__ +#define OV7690_XCLK_FREQ 24000000 +int ov7690_init(sensor_t *sensor); +#endif // __OV7690_H__ diff --git a/components/3rd_party/omv/omv/sensors/ov7690_regs.h b/components/3rd_party/omv/omv/sensors/ov7690_regs.h new file mode 100644 index 00000000..357552fe --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/ov7690_regs.h @@ -0,0 +1,48 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2020 Ibrahim Abdelkader + * Copyright (c) 2013-2020 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OV7690 register definitions. + */ +#ifndef __REG_REGS_H__ +#define __REG_REGS_H__ + +#define GAIN 0x00 +#define BGAIN 0x01 +#define RGAIN 0x02 +#define GGAIN 0x03 +#define REG0C 0x0C +#define REG0E 0x0E +#define AECH 0x0F +#define AECL 0x10 +#define CLKRC 0x11 +#define REG12 0x12 +#define REG13 0x13 +#define REG14 0x14 +#define REG15 0x15 +#define REG19 0x19 +#define PLL 0x29 +#define REG3E 0x3E +#define REG82 0x82 +#define LCC0 0x85 +#define LCC1 0x86 +#define LCC4 0x89 +#define LCC5 0x8A +#define LCC6 0x8B +#define REGC8 0xC8 +#define REGC9 0xC9 +#define REGCA 0xCA +#define REGCB 0xCB +#define REGCC 0xCC +#define REGCD 0xCD +#define REGCE 0xCE +#define REGCF 0xCF +#define REGD2 0xD2 +#define REGD8 0xD8 +#define REGD9 0xD9 + +#endif //__REG_REGS_H__ diff --git a/components/3rd_party/omv/omv/sensors/ov7725.c b/components/3rd_party/omv/omv/sensors/ov7725.c new file mode 100644 index 00000000..ef808aa5 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/ov7725.c @@ -0,0 +1,693 @@ +/* + * 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. + * + * OV7725 driver. + */ +#include "omv_boardconfig.h" +#if (OMV_ENABLE_OV7725 == 1) + +#include +#include +#include + +#include "omv_i2c.h" +#include "sensor.h" +#include "ov7725.h" +#include "ov7725_regs.h" +#include "py/mphal.h" + +static const uint8_t default_regs[][2] = { + +// From App Note. + + {COM12, 0x03}, + {HSTART, 0x22}, + {HSIZE, 0xa4}, + {VSTART, 0x07}, + {VSIZE, 0xf0}, + {HREF, 0x00}, + {HOUTSIZE, 0xa0}, + {VOUTSIZE, 0xf0}, + {EXHCH, 0x00}, + {CLKRC, 0xC0}, // {CLKRC, 0x01}, + + {TGT_B, 0x7f}, + {FIXGAIN, 0x09}, + {AWB_CTRL0, 0xe0}, + {DSP_CTRL1, 0xff}, + {DSP_CTRL2, 0x20 | DSP_CTRL2_VDCW_EN | DSP_CTRL2_HDCW_EN | DSP_CTRL2_VZOOM_EN | DSP_CTRL2_HZOOM_EN}, // {DSP_CTRL2, 0x20}, + {DSP_CTRL3, 0x00}, + {DSP_CTRL4, 0x48}, + + {COM8, 0xf0}, + {COM4, OMV_OV7725_PLL_CONFIG}, // {COM4, 0x41}, + {COM6, 0xc5}, + {COM9, 0x11}, + {BDBASE, 0x7f}, + {BDSTEP, 0x03}, + {AEW, 0x40}, + {AEB, 0x30}, + {VPT, 0xa1}, + {EXHCL, 0x00}, + {AWB_CTRL3, 0xaa}, + {COM8, 0xff}, + + {EDGE1, 0x05}, + {DNSOFF, 0x01}, + {EDGE2, 0x03}, + {EDGE3, 0x00}, + {MTX1, 0xb0}, + {MTX2, 0x9d}, + {MTX3, 0x13}, + {MTX4, 0x16}, + {MTX5, 0x7b}, + {MTX6, 0x91}, + {MTX_CTRL, 0x1e}, + {BRIGHTNESS, 0x08}, + {CONTRAST, 0x20}, + {UVADJ0, 0x81}, + {SDE, SDE_CONT_BRIGHT_EN | SDE_SATURATION_EN}, + + {GAM1, 0x0c}, + {GAM2, 0x16}, + {GAM3, 0x2a}, + {GAM4, 0x4e}, + {GAM5, 0x61}, + {GAM6, 0x6f}, + {GAM7, 0x7b}, + {GAM8, 0x86}, + {GAM9, 0x8e}, + {GAM10, 0x97}, + {GAM11, 0xa4}, + {GAM12, 0xaf}, + {GAM13, 0xc5}, + {GAM14, 0xd7}, + {GAM15, 0xe8}, + {SLOP, 0x20}, + + {DM_LNL, 0x00}, + {BDBASE, OMV_OV7725_BANDING}, // {BDBASE, 0x7f} + {BDSTEP, 0x03}, + + {LC_RADI, 0x10}, + {LC_COEF, 0x10}, + {LC_COEFB, 0x14}, + {LC_COEFR, 0x17}, + {LC_CTR, 0x01}, // {LC_CTR, 0x05}, + + {COM5, 0xf5}, // {COM5, 0x65}, + +// OpenMV Custom. + + {COM7, COM7_FMT_RGB565}, + +// End. + + {0x00, 0x00}, +}; + +#define NUM_BRIGHTNESS_LEVELS (9) +static const uint8_t brightness_regs[NUM_BRIGHTNESS_LEVELS][2] = { + {0x38, 0x0e}, /* -4 */ + {0x28, 0x0e}, /* -3 */ + {0x18, 0x0e}, /* -2 */ + {0x08, 0x0e}, /* -1 */ + {0x08, 0x06}, /* 0 */ + {0x18, 0x06}, /* +1 */ + {0x28, 0x06}, /* +2 */ + {0x38, 0x06}, /* +3 */ + {0x48, 0x06}, /* +4 */ +}; + +#define NUM_CONTRAST_LEVELS (9) +static const uint8_t contrast_regs[NUM_CONTRAST_LEVELS][1] = { + {0x10}, /* -4 */ + {0x14}, /* -3 */ + {0x18}, /* -2 */ + {0x1C}, /* -1 */ + {0x20}, /* 0 */ + {0x24}, /* +1 */ + {0x28}, /* +2 */ + {0x2C}, /* +3 */ + {0x30}, /* +4 */ +}; + +#define NUM_SATURATION_LEVELS (9) +static const uint8_t saturation_regs[NUM_SATURATION_LEVELS][2] = { + {0x00, 0x00}, /* -4 */ + {0x10, 0x10}, /* -3 */ + {0x20, 0x20}, /* -2 */ + {0x30, 0x30}, /* -1 */ + {0x40, 0x40}, /* 0 */ + {0x50, 0x50}, /* +1 */ + {0x60, 0x60}, /* +2 */ + {0x70, 0x70}, /* +3 */ + {0x80, 0x80}, /* +4 */ +}; + +static int reset(sensor_t *sensor) { + // Reset all registers + int ret = omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM7, COM7_RESET); + + // Delay 2 ms + mp_hal_delay_ms(2); + + // Write default registers + for (int i = 0; default_regs[i][0]; i++) { + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, default_regs[i][0], default_regs[i][1]); + } + + // Delay 300 ms + if (!sensor->disable_delays) { + mp_hal_delay_ms(300); + } + + return ret; +} + +static int sleep(sensor_t *sensor, int enable) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM2, ®); + + if (enable) { + reg |= COM2_SOFT_SLEEP; + } else { + reg &= ~COM2_SOFT_SLEEP; + } + + // Write back register + return omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM2, reg) | ret; +} + +static int read_reg(sensor_t *sensor, uint16_t reg_addr) { + uint8_t reg_data; + if (omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, reg_addr, ®_data) != 0) { + return -1; + } + return reg_data; +} + +static int write_reg(sensor_t *sensor, uint16_t reg_addr, uint16_t reg_data) { + return omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, reg_addr, reg_data); +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM7, ®); + + switch (pixformat) { + case PIXFORMAT_RGB565: + reg = COM7_SET_FMT(reg, COM7_FMT_RGB); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, DSP_CTRL4, DSP_CTRL4_YUV_RGB); + break; + case PIXFORMAT_YUV422: + case PIXFORMAT_GRAYSCALE: + reg = COM7_SET_FMT(reg, COM7_FMT_YUV); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, DSP_CTRL4, DSP_CTRL4_YUV_RGB); + break; + case PIXFORMAT_BAYER: + reg = COM7_SET_FMT(reg, COM7_FMT_P_BAYER); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, DSP_CTRL4, DSP_CTRL4_RAW8); + break; + default: + return -1; + } + + // Write back register + return omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM7, reg) | ret; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) { + uint8_t reg; + int ret = 0; + uint16_t w = resolution[framesize][0]; + uint16_t h = resolution[framesize][1]; + bool vflip; + + if ((w > 640) || (h > 480)) { + return -1; + } + + // Write MSBs + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, HOUTSIZE, w >> 2); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, VOUTSIZE, h >> 1); + + // Write LSBs + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, EXHCH, ((w & 0x3) | ((h & 0x1) << 2))); + + // Sample VFLIP + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM3, ®); + vflip = reg & COM3_VFLIP; + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, HREF, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, HREF, (reg & 0xBF) | (vflip ? 0x40 : 0x00)); + + if ((w <= 320) && (h <= 240)) { + // Set QVGA Resolution + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM7, ®); + reg = COM7_SET_RES(reg, COM7_RES_QVGA); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM7, reg); + + // Set QVGA Window Size + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, HSTART, 0x3F); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, HSIZE, 0x50); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, VSTART, 0x03 - vflip); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, VSIZE, 0x78); + + // Enable auto-scaling/zooming factors + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, DSPAUTO, 0xFF); + } else { + // Set VGA Resolution + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM7, ®); + reg = COM7_SET_RES(reg, COM7_RES_VGA); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM7, reg); + + // Set VGA Window Size + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, HSTART, 0x23); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, HSIZE, 0xA0); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, VSTART, 0x07 - vflip); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, VSIZE, 0xF0); + + // Disable auto-scaling/zooming factors + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, DSPAUTO, 0xF3); + + // Clear auto-scaling/zooming factors + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, SCAL0, 0x00); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, SCAL1, 0x40); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, SCAL2, 0x40); + } + + return ret; +} + +static int set_contrast(sensor_t *sensor, int level) { + level += (NUM_CONTRAST_LEVELS / 2); + if (level < 0 || level >= NUM_CONTRAST_LEVELS) { + return -1; + } + + return omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, CONTRAST, contrast_regs[level][0]); +} + +static int set_brightness(sensor_t *sensor, int level) { + int ret = 0; + level += (NUM_BRIGHTNESS_LEVELS / 2); + if (level < 0 || level >= NUM_BRIGHTNESS_LEVELS) { + return -1; + } + + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BRIGHTNESS, brightness_regs[level][0]); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, SIGN_BIT, brightness_regs[level][1]); + return ret; +} + +static int set_saturation(sensor_t *sensor, int level) { + int ret = 0; + level += (NUM_SATURATION_LEVELS / 2); + if (level < 0 || level >= NUM_SATURATION_LEVELS) { + return -1; + } + + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, USAT, saturation_regs[level][0]); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, VSAT, saturation_regs[level][1]); + return ret; +} + +static int set_gainceiling(sensor_t *sensor, gainceiling_t gainceiling) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM9, ®); + + // Set gain ceiling + reg = COM9_SET_AGC(reg, gainceiling); + return omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM9, reg) | ret; +} + +static int set_colorbar(sensor_t *sensor, int enable) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM3, ®); + + // Enable colorbar test pattern output + reg = COM3_SET_CBAR(reg, enable); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM3, reg); + + // Enable DSP colorbar output + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, DSP_CTRL3, ®); + reg = DSP_CTRL3_SET_CBAR(reg, enable); + return omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, DSP_CTRL3, reg) | ret; +} + +static int set_auto_gain(sensor_t *sensor, int enable, float gain_db, float gain_db_ceiling) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM8, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM8, COM8_SET_AGC(reg, (enable != 0))); + + if ((enable == 0) && (!isnanf(gain_db)) && (!isinff(gain_db))) { + float gain = IM_MAX(IM_MIN(expf((gain_db / 20.0f) * M_LN10), 32.0f), 1.0f); + + int gain_temp = fast_ceilf(logf(IM_MAX(gain / 2.0f, 1.0f)) / M_LN2); + int gain_hi = 0xF >> (4 - gain_temp); + int gain_lo = IM_MIN(fast_roundf(((gain / (1 << gain_temp)) - 1.0f) * 16.0f), 15); + + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, GAIN, (gain_hi << 4) | (gain_lo << 0)); + } else if ((enable != 0) && (!isnanf(gain_db_ceiling)) && (!isinff(gain_db_ceiling))) { + float gain_ceiling = IM_MAX(IM_MIN(expf((gain_db_ceiling / 20.0f) * M_LN10), 32.0f), 2.0f); + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM9, ®); + ret |= + omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM9, + (reg & 0x8F) | ((fast_ceilf(logf(gain_ceiling) / M_LN2) - 1) << 4)); + } + + return ret; +} + +static int get_gain_db(sensor_t *sensor, float *gain_db) { + uint8_t reg, gain; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM8, ®); + + // DISABLED + // if (reg & COM8_AGC_EN) { + // ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM8, COM8_SET_AGC(reg, 0)); + // } + // DISABLED + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, GAIN, &gain); + + // DISABLED + // if (reg & COM8_AGC_EN) { + // ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM8, COM8_SET_AGC(reg, 1)); + // } + // DISABLED + + int hi_gain = 1 << (((gain >> 7) & 1) + ((gain >> 6) & 1) + ((gain >> 5) & 1) + ((gain >> 4) & 1)); + float lo_gain = 1.0f + (((gain >> 0) & 0xF) / 16.0f); + *gain_db = 20.0f * log10f(hi_gain * lo_gain); + + return ret; +} + +static int set_auto_exposure(sensor_t *sensor, int enable, int exposure_us) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM8, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM8, COM8_SET_AEC(reg, (enable != 0))); + + if ((enable == 0) && (exposure_us >= 0)) { + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM7, ®); + + int t_line = (reg & COM7_RES_QVGA) ? (320 + 256) : (640 + 144); + int t_pclk = (COM7_GET_FMT(reg) == COM7_FMT_P_BAYER) ? 1 : 2; + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM4, ®); + int pll_mult = 0; + + if (COM4_GET_PLL(reg) == COM4_PLL_BYPASS) { + pll_mult = 1; + } + if (COM4_GET_PLL(reg) == COM4_PLL_4x) { + pll_mult = 4; + } + if (COM4_GET_PLL(reg) == COM4_PLL_6x) { + pll_mult = 6; + } + if (COM4_GET_PLL(reg) == COM4_PLL_8x) { + pll_mult = 8; + } + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, CLKRC, ®); + int clk_rc = 0; + + if (reg & CLKRC_NO_PRESCALE) { + clk_rc = 1; + } else { + clk_rc = ((reg & CLKRC_PRESCALER) + 1) * 2; + } + + int exposure = + IM_MAX(IM_MIN(((exposure_us * (((OMV_XCLK_FREQUENCY / clk_rc) * pll_mult) / 1000000)) / t_pclk) / t_line, 0xFFFF), + 0x0000); + + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, AEC, ((exposure >> 0) & 0xFF)); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, AECH, ((exposure >> 8) & 0xFF)); + } + + return ret; +} + +static int get_exposure_us(sensor_t *sensor, int *exposure_us) { + uint8_t reg, aec_l, aec_h; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM8, ®); + + // DISABLED + // if (reg & COM8_AEC_EN) { + // ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM8, COM8_SET_AEC(reg, 0)); + // } + // DISABLED + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, AEC, &aec_l); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, AECH, &aec_h); + + // DISABLED + // if (reg & COM8_AEC_EN) { + // ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM8, COM8_SET_AEC(reg, 1)); + // } + // DISABLED + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM7, ®); + + int t_line = (reg & COM7_RES_QVGA) ? (320 + 256) : (640 + 144); + int t_pclk = (COM7_GET_FMT(reg) == COM7_FMT_P_BAYER) ? 1 : 2; + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM4, ®); + int pll_mult = 0; + + if (COM4_GET_PLL(reg) == COM4_PLL_BYPASS) { + pll_mult = 1; + } + if (COM4_GET_PLL(reg) == COM4_PLL_4x) { + pll_mult = 4; + } + if (COM4_GET_PLL(reg) == COM4_PLL_6x) { + pll_mult = 6; + } + if (COM4_GET_PLL(reg) == COM4_PLL_8x) { + pll_mult = 8; + } + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, CLKRC, ®); + int clk_rc = 0; + + if (reg & CLKRC_NO_PRESCALE) { + clk_rc = 1; + } else { + clk_rc = ((reg & CLKRC_PRESCALER) + 1) * 2; + } + + *exposure_us = (((aec_h << 8) + (aec_l << 0)) * t_line * t_pclk) / (((OMV_XCLK_FREQUENCY / clk_rc) * pll_mult) / 1000000); + + return ret; +} + +static int set_auto_whitebal(sensor_t *sensor, int enable, float r_gain_db, float g_gain_db, float b_gain_db) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM8, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM8, COM8_SET_AWB(reg, (enable != 0))); + + if ((enable == 0) && (!isnanf(r_gain_db)) && (!isnanf(g_gain_db)) && (!isnanf(b_gain_db)) + && (!isinff(r_gain_db)) && (!isinff(g_gain_db)) && (!isinff(b_gain_db))) { + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, AWB_CTRL1, ®); + float gain_div = (reg & 0x2) ? 64.0f : 128.0f; + + int r_gain = IM_MAX(IM_MIN(fast_roundf(expf((r_gain_db / 20.0f) * M_LN10) * gain_div), 255), 0); + int g_gain = IM_MAX(IM_MIN(fast_roundf(expf((g_gain_db / 20.0f) * M_LN10) * gain_div), 255), 0); + int b_gain = IM_MAX(IM_MIN(fast_roundf(expf((b_gain_db / 20.0f) * M_LN10) * gain_div), 255), 0); + + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, BLUE, b_gain); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, RED, r_gain); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, GREEN, g_gain); + } + + return ret; +} + +static int get_rgb_gain_db(sensor_t *sensor, float *r_gain_db, float *g_gain_db, float *b_gain_db) { + uint8_t reg, blue, red, green; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM8, ®); + + // DISABLED + // if (reg & COM8_AWB_EN) { + // ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM8, COM8_SET_AWB(reg, 0)); + // } + // DISABLED + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, BLUE, &blue); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, RED, &red); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, GREEN, &green); + + // DISABLED + // if (reg & COM8_AWB_EN) { + // ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM8, COM8_SET_AWB(reg, 1)); + // } + // DISABLED + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, AWB_CTRL1, ®); + float gain_div = (reg & 0x2) ? 64.0f : 128.0f; + + *r_gain_db = 20.0f * log10f(red / gain_div); + *g_gain_db = 20.0f * log10f(green / gain_div); + *b_gain_db = 20.0f * log10f(blue / gain_div); + + return ret; +} + +static int set_auto_blc(sensor_t *sensor, int enable, int *regs) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM13, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM13, COM13_SET_BLC(reg, (enable != 0))); + + if ((enable == 0) && (regs != NULL)) { + for (uint32_t i = 0; i < sensor->hw_flags.blc_size; i++) { + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, ADOFF_B + i, regs[i]); + } + } + + return ret; +} + +static int get_blc_regs(sensor_t *sensor, int *regs) { + int ret = 0; + + for (uint32_t i = 0; i < sensor->hw_flags.blc_size; i++) { + uint8_t reg; + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, ADOFF_B + i, ®); + regs[i] = reg; + } + + return ret; +} + +static int set_hmirror(sensor_t *sensor, int enable) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM3, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM3, COM3_SET_MIRROR(reg, enable)); + + return ret; +} + +static int set_vflip(sensor_t *sensor, int enable) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM3, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM3, COM3_SET_FLIP(reg, enable)); + // Apply new vertical flip setting. + ret |= set_framesize(sensor, sensor->framesize); + + return ret; +} + +static int set_special_effect(sensor_t *sensor, sde_t sde) { + int ret = 0; + + switch (sde) { + case SDE_NEGATIVE: + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, SDE, 0x46); + break; + case SDE_NORMAL: + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, SDE, 0x06); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, UFIX, 0x80); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, VFIX, 0x80); + break; + default: + return -1; + } + + return ret; +} + +static int set_lens_correction(sensor_t *sensor, int enable, int radi, int coef) { + int ret = 0; + + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, LC_CTR, (enable & 0x01)); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, LC_RADI, radi); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, LC_COEF, coef); + + return ret; +} + +static int ioctl(sensor_t *sensor, int request, va_list ap) { + int ret = 0; + uint8_t reg; + + switch (request) { + case IOCTL_SET_NIGHT_MODE: { + int enable = va_arg(ap, int); + ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM5, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, COM5, COM5_SET_AFR(reg, (enable != 0))); + if (enable == 0) { + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, ADVFL, 0); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, ADVFH, 0); + } + break; + } + case IOCTL_GET_NIGHT_MODE: { + int *enable = va_arg(ap, int *); + ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, COM5, ®); + if (ret >= 0) { + *enable = reg & COM5_AFR; + } + break; + } + default: { + ret = -1; + break; + } + } + + return ret; +} + +int ov7725_init(sensor_t *sensor) { + // Initialize sensor structure. + sensor->reset = reset; + sensor->sleep = sleep; + sensor->read_reg = read_reg; + sensor->write_reg = write_reg; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_contrast = set_contrast; + sensor->set_brightness = set_brightness; + sensor->set_saturation = set_saturation; + sensor->set_gainceiling = set_gainceiling; + sensor->set_colorbar = set_colorbar; + sensor->set_auto_gain = set_auto_gain; + sensor->get_gain_db = get_gain_db; + sensor->set_auto_exposure = set_auto_exposure; + sensor->get_exposure_us = get_exposure_us; + sensor->set_auto_whitebal = set_auto_whitebal; + sensor->get_rgb_gain_db = get_rgb_gain_db; + sensor->set_auto_blc = set_auto_blc; + sensor->get_blc_regs = get_blc_regs; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + sensor->set_special_effect = set_special_effect; + sensor->set_lens_correction = set_lens_correction; + sensor->ioctl = ioctl; + + // Set sensor flags + sensor->hw_flags.vsync = 1; + sensor->hw_flags.hsync = 0; + sensor->hw_flags.pixck = 1; + sensor->hw_flags.fsync = 0; + sensor->hw_flags.jpege = 0; + sensor->hw_flags.gs_bpp = 2; + sensor->hw_flags.rgb_swap = 1; + sensor->hw_flags.yuv_order = SENSOR_HW_FLAGS_YVU422; + sensor->hw_flags.blc_size = 8; + + return 0; +} +#endif // (OMV_ENABLE_OV7725 == 1) diff --git a/components/3rd_party/omv/omv/sensors/ov7725.h b/components/3rd_party/omv/omv/sensors/ov7725.h new file mode 100644 index 00000000..17da4c80 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/ov7725.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. + * + * OV7725 driver. + */ +#ifndef __OV7725_H__ +#define __OV7725_H__ +int ov7725_init(sensor_t *sensor); +#endif // __OV7725_H__ diff --git a/components/3rd_party/omv/omv/sensors/ov7725_regs.h b/components/3rd_party/omv/omv/sensors/ov7725_regs.h new file mode 100644 index 00000000..dea8ee1c --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/ov7725_regs.h @@ -0,0 +1,349 @@ +/* + * 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. + * + * OV7725 register definitions. + */ +#ifndef __REG_REGS_H__ +#define __REG_REGS_H__ + +#define GAIN 0x00 /* AGC - Gain control gain setting */ +#define BLUE 0x01 /* AWB - Blue channel gain setting */ +#define RED 0x02 /* AWB - Red channel gain setting */ +#define GREEN 0x03 /* AWB - Green channel gain setting */ +#define BAVG 0x05 /* U/B Average Level */ +#define GAVG 0x06 /* Y/Gb Average Level */ +#define RAVG 0x07 /* V/R Average Level */ +#define AECH 0x08 /* Exposure Value - AEC MSBs */ + +#define COM2 0x09 /* Common Control 2 */ +#define COM2_SOFT_SLEEP 0x10 /* Soft sleep mode */ +#define COM2_OUT_DRIVE_1x 0x00 /* Output drive capability 1x */ +#define COM2_OUT_DRIVE_2x 0x01 /* Output drive capability 2x */ +#define COM2_OUT_DRIVE_3x 0x02 /* Output drive capability 3x */ +#define COM2_OUT_DRIVE_4x 0x03 /* Output drive capability 4x */ + +#define PID 0x0A /* Product ID Number MSB */ +#define VER 0x0B /* Product ID Number LSB */ + +#define COM3 0x0C /* Common Control 3 */ +#define COM3_VFLIP 0x80 /* Vertical flip image ON/OFF selection */ +#define COM3_MIRROR 0x40 /* Horizontal mirror image ON/OFF selection */ +#define COM3_SWAP_BR 0x20 /* Swap B/R output sequence in RGB output mode */ +#define COM3_SWAP_YUV 0x10 /* Swap Y/UV output sequence in YUV output mode */ +#define COM3_SWAP_MSB 0x08 /* Swap output MSB/LSB */ +#define COM3_TRI_CLOCK 0x04 /* Tri-state option for output clock at power-down period */ +#define COM3_TRI_DATA 0x02 /* Tri-state option for output data at power-down period */ +#define COM3_COLOR_BAR 0x01 /* Sensor color bar test pattern output enable */ +#define COM3_SET_CBAR(r, x) ((r & 0xFE) | ((x & 1) << 0)) +#define COM3_SET_MIRROR(r, x) ((r & 0xBF) | ((x & 1) << 6)) +#define COM3_SET_FLIP(r, x) ((r & 0x7F) | ((x & 1) << 7)) +#define COM3_GET_CBAR(r) ((r >> 0) & 1) +#define COM3_GET_MIRROR(r) ((r >> 6) & 1) +#define COM3_GET_FLIP(r) ((r >> 7) & 1) + +#define COM4 0x0D /* Common Control 4 */ +#define COM4_PLL_BYPASS 0x00 /* Bypass PLL */ +#define COM4_PLL_4x 0x40 /* PLL frequency 4x */ +#define COM4_PLL_6x 0x80 /* PLL frequency 6x */ +#define COM4_PLL_8x 0xC0 /* PLL frequency 8x */ +#define COM4_AEC_FULL 0x00 /* AEC evaluate full window */ +#define COM4_AEC_1_2 0x10 /* AEC evaluate 1/2 window */ +#define COM4_AEC_1_4 0x20 /* AEC evaluate 1/4 window */ +#define COM4_AEC_2_3 0x30 /* AEC evaluate 2/3 window */ +#define COM4_GET_PLL(r) (r & 0xC0) + +#define COM5 0x0E /* Common Control 5 */ +#define COM5_AFR 0x80 /* Auto frame rate control ON/OFF selection (night mode) */ +#define COM5_AFR_SPEED 0x40 /* Auto frame rate control speed selection */ +#define COM5_AFR_0 0x00 /* No reduction of frame rate */ +#define COM5_AFR_1_2 0x10 /* Max reduction to 1/2 frame rate */ +#define COM5_AFR_1_4 0x20 /* Max reduction to 1/4 frame rate */ +#define COM5_AFR_1_8 0x30 /* Max reduction to 1/8 frame rate */ +#define COM5_AFR_4x 0x04 /* Add frame when AGC reaches 4x gain */ +#define COM5_AFR_8x 0x08 /* Add frame when AGC reaches 8x gain */ +#define COM5_AFR_16x 0x0c /* Add frame when AGC reaches 16x gain */ +#define COM5_AEC_NO_LIMIT 0x01 /* No limit to AEC increase step */ +#define COM5_SET_AFR(r, x) ((r & 0x7F) | ((x & 0x1) << 7)) + +#define COM6 0x0F /* Common Control 6 */ +#define COM6_AUTO_WINDOW 0x01 /* Auto window setting ON/OFF selection when format changes */ + +#define AEC 0x10 /* AEC[7:0] (see register AECH for AEC[15:8]) */ + +#define CLKRC 0x11 /* Internal Clock */ +#define CLKRC_NO_PRESCALE 0x40 /* Use external clock directly */ +#define CLKRC_PRESCALER 0x3F /* Internal clock pre-scaler */ + +#define COM7 0x12 /* Common Control 7 */ +#define COM7_RESET 0x80 /* SCCB Register Reset */ +#define COM7_RES_VGA 0x00 /* Resolution VGA */ +#define COM7_RES_QVGA 0x40 /* Resolution QVGA */ +#define COM7_BT656 0x20 /* BT.656 protocol ON/OFF */ +#define COM7_SENSOR_RAW 0x10 /* Sensor RAW */ +#define COM7_FMT_GBR422 0x00 /* RGB output format GBR422 */ +#define COM7_FMT_RGB565 0x04 /* RGB output format RGB565 */ +#define COM7_FMT_RGB555 0x08 /* RGB output format RGB555 */ +#define COM7_FMT_RGB444 0x0C /* RGB output format RGB444 */ +#define COM7_FMT_YUV 0x00 /* Output format YUV */ +#define COM7_FMT_P_BAYER 0x01 /* Output format Processed Bayer RAW */ +#define COM7_FMT_RGB 0x02 /* Output format RGB */ +#define COM7_FMT_R_BAYER 0x03 /* Output format Bayer RAW */ +#define COM7_SET_FMT(r, x) ((r & 0xFC) | ((x & 0x3) << 0)) +#define COM7_SET_RES(r, x) ((r & 0xBF) | (x)) +#define COM7_GET_FMT(r) (r & 0x03) + +#define COM8 0x13 /* Common Control 8 */ +#define COM8_FAST_AUTO 0x80 /* Enable fast AGC/AEC algorithm */ +#define COM8_STEP_VSYNC 0x00 /* AEC - Step size limited to vertical blank */ +#define COM8_STEP_UNLIMIT 0x40 /* AEC - Step size unlimited step size */ +#define COM8_BANDF_EN 0x20 /* Banding filter ON/OFF */ +#define COM8_AEC_BANDF 0x10 /* Enable AEC below banding value */ +#define COM8_AEC_FINE_EN 0x08 /* Fine AEC ON/OFF control */ +#define COM8_AGC_EN 0x04 /* AGC Enable */ +#define COM8_AWB_EN 0x02 /* AWB Enable */ +#define COM8_AEC_EN 0x01 /* AEC Enable */ +#define COM8_SET_AGC(r, x) ((r & 0xFB) | ((x & 0x1) << 2)) +#define COM8_SET_AWB(r, x) ((r & 0xFD) | ((x & 0x1) << 1)) +#define COM8_SET_AEC(r, x) ((r & 0x7E) | ((x & 0x1) << 7) | ((x & 0x1) << 0)) + +#define COM9 0x14 /* Common Control 9 */ +#define COM9_HISTO_AVG 0x80 /* Histogram or average based AEC/AGC selection */ +#define COM9_AGC_GAIN_2x 0x00 /* Automatic Gain Ceiling 2x */ +#define COM9_AGC_GAIN_4x 0x10 /* Automatic Gain Ceiling 4x */ +#define COM9_AGC_GAIN_8x 0x20 /* Automatic Gain Ceiling 8x */ +#define COM9_AGC_GAIN_16x 0x30 /* Automatic Gain Ceiling 16x */ +#define COM9_AGC_GAIN_32x 0x40 /* Automatic Gain Ceiling 32x */ +#define COM9_DROP_VSYNC 0x04 /* Drop VSYNC output of corrupt frame */ +#define COM9_DROP_HREF 0x02 /* Drop HREF output of corrupt frame */ +#define COM9_SET_AGC(r, x) ((r & 0x8F) | ((x & 0x07) << 4)) + +#define COM10 0x15 /* Common Control 10 */ +#define COM10_NEGATIVE 0x80 /* Output negative data */ +#define COM10_HSYNC_EN 0x40 /* HREF changes to HSYNC */ +#define COM10_PCLK_FREE 0x00 /* PCLK output option: free running PCLK */ +#define COM10_PCLK_MASK 0x20 /* PCLK output option: masked during horizontal blank */ +#define COM10_PCLK_REV 0x10 /* PCLK reverse */ +#define COM10_HREF_REV 0x08 /* HREF reverse */ +#define COM10_VSYNC_FALLING 0x00 /* VSYNC changes on falling edge of PCLK */ +#define COM10_VSYNC_RISING 0x04 /* VSYNC changes on rising edge of PCLK */ +#define COM10_VSYNC_NEG 0x02 /* VSYNC negative */ +#define COM10_OUT_RANGE_8 0x01 /* Output data range: Full range */ +#define COM10_OUT_RANGE_10 0x00 /* Output data range: Data from [10] to [F0] (8 MSBs) */ + +#define REG16 0x16 /* Register 16 */ +#define REG16_BIT_SHIFT 0x80 /* Bit shift test pattern options */ + +#define HSTART 0x17 /* Horizontal Frame (HREF column) Start 8 MSBs (2 LSBs are at HREF[5:4]) */ +#define HSIZE 0x18 /* Horizontal Sensor Size (2 LSBs are at HREF[1:0]) */ +#define VSTART 0x19 /* Vertical Frame (row) Start 8 MSBs (1 LSB is at HREF[6]) */ +#define VSIZE 0x1A /* Vertical Sensor Size (1 LSB is at HREF[2]) */ +#define PSHFT 0x1B /* Data Format - Pixel Delay Select */ +#define MIDH 0x1C /* Manufacturer ID Byte - High */ +#define MIDL 0x1D /* Manufacturer ID Byte - Low */ +#define LAEC 0x1F /* Fine AEC Value - defines exposure value less than one row period */ + +#define COM11 0x20 /* Common Control 11 */ +#define COM11_SNGL_FRAME_EN 0x02 /* Single frame ON/OFF selection */ +#define COM11_SNGL_XFR_TRIG 0x01 /* Single frame transfer trigger */ + +#define BDBASE 0x22 /* Banding Filter Minimum AEC Value */ +#define BDSTEP 0x23 /* Banding Filter Maximum Step */ +#define AEW 0x24 /* AGC/AEC - Stable Operating Region (Upper Limit) */ +#define AEB 0x25 /* AGC/AEC - Stable Operating Region (Lower Limit) */ +#define VPT 0x26 /* AGC/AEC Fast Mode Operating Region */ +#define REG28 0x28 /* Selection on the number of dummy rows, N */ +#define HOUTSIZE 0x29 /* Horizontal Data Output Size MSBs (2 LSBs at register EXHCH[1:0]) */ +#define EXHCH 0x2A /* Dummy Pixel Insert MSB */ +#define EXHCL 0x2B /* Dummy Pixel Insert LSB */ +#define VOUTSIZE 0x2C /* Vertical Data Output Size MSBs (LSB at register EXHCH[2]) */ +#define ADVFL 0x2D /* LSB of Insert Dummy Rows in Vertical Sync (1 bit equals 1 row) */ +#define ADVFH 0x2E /* MSB of Insert Dummy Rows in Vertical Sync */ +#define YAVE 0x2F /* Y/G Channel Average Value */ +#define LUMHTH 0x30 /* Histogram AEC/AGC Luminance High Level Threshold */ +#define LUMLTH 0x31 /* Histogram AEC/AGC Luminance Low Level Threshold */ +#define HREF 0x32 /* Image Start and Size Control */ +#define DM_LNL 0x33 /* Dummy Row Low 8 Bits */ +#define DM_LNH 0x34 /* Dummy Row High 8 Bits */ +#define ADOFF_B 0x35 /* AD Offset Compensation Value for B Channel */ +#define ADOFF_R 0x36 /* AD Offset Compensation Value for R Channel */ +#define ADOFF_GB 0x37 /* AD Offset Compensation Value for GB Channel */ +#define ADOFF_GR 0x38 /* AD Offset Compensation Value for GR Channel */ +#define OFF_B 0x39 /* AD Offset Compensation Value for B Channel */ +#define OFF_R 0x3A /* AD Offset Compensation Value for R Channel */ +#define OFF_GB 0x3B /* AD Offset Compensation Value for GB Channel */ +#define OFF_GR 0x3C /* AD Offset Compensation Value for GR Channel */ + +#define COM12 0x3D /* DC offset compensation for analog process */ + +#define COM13 0x3E /* Common Control 13 */ +#define COM13_BLC_EN 0x80 /* BLC enable */ +#define COM13_ADC_EN 0x40 /* ADC channel BLC ON/OFF control */ +#define COM13_ANALOG_BLC 0x20 /* Analog processing channel BLC ON/OFF control */ +#define COM13_ABLC_GAIN_EN 0x04 /* ABLC gain trigger enable */ +#define COM13_SET_BLC(r, x) ((r & 0x7F) | ((x & 0x1) << 7)) + +#define COM14 0x3F /* Common Control 14 */ +#define COM15 0x40 /* Common Control 15 */ +#define COM16 0x41 /* Common Control 16 */ +#define TGT_B 0x42 /* BLC Blue Channel Target Value */ +#define TGT_R 0x43 /* BLC Red Channel Target Value */ +#define TGT_GB 0x44 /* BLC Gb Channel Target Value */ +#define TGT_GR 0x45 /* BLC Gr Channel Target Value */ + +#define LC_CTR 0x46 /* Lens Correction Control */ +#define LC_CTR_RGB_COMP_1 0x00 /* R, G, and B channel compensation coefficient is set by LC_COEF (0x49) */ +#define LC_CTR_RGB_COMP_3 0x04 /* R, G, and B channel compensation coefficient is set by registers + LC_COEFB (0x4B), LC_COEF (0x49), and LC_COEFR (0x4C), respectively */ +#define LC_CTR_EN 0x01 /* Lens correction enable */ +#define LC_XC 0x47 /* X Coordinate of Lens Correction Center Relative to Array Center */ +#define LC_YC 0x48 /* Y Coordinate of Lens Correction Center Relative to Array Center */ +#define LC_COEF 0x49 /* Lens Correction Coefficient */ +#define LC_RADI 0x4A /* Lens Correction Radius */ +#define LC_COEFB 0x4B /* Lens Correction B Channel Compensation Coefficient */ +#define LC_COEFR 0x4C /* Lens Correction R Channel Compensation Coefficient */ + +#define FIXGAIN 0x4D /* Analog Fix Gain Amplifier */ +#define AREF0 0x4E /* Sensor Reference Control */ +#define AREF1 0x4F /* Sensor Reference Current Control */ +#define AREF2 0x50 /* Analog Reference Control */ +#define AREF3 0x51 /* ADC Reference Control */ +#define AREF4 0x52 /* ADC Reference Control */ +#define AREF5 0x53 /* ADC Reference Control */ +#define AREF6 0x54 /* Analog Reference Control */ +#define AREF7 0x55 /* Analog Reference Control */ +#define UFIX 0x60 /* U Channel Fixed Value Output */ +#define VFIX 0x61 /* V Channel Fixed Value Output */ +#define AWBB_BLK 0x62 /* AWB Option for Advanced AWB */ + +#define AWB_CTRL0 0x63 /* AWB Control Byte 0 */ +#define AWB_CTRL0_GAIN_EN 0x80 /* AWB gain enable */ +#define AWB_CTRL0_CALC_EN 0x40 /* AWB calculate enable */ +#define AWB_CTRL0_WBC_MASK 0x0F /* WBC threshold 2 */ + +#define DSP_CTRL1 0x64 /* DSP Control Byte 1 */ +#define DSP_CTRL1_FIFO_EN 0x80 /* FIFO enable/disable selection */ +#define DSP_CTRL1_UV_EN 0x40 /* UV adjust function ON/OFF selection */ +#define DSP_CTRL1_SDE_EN 0x20 /* SDE enable */ +#define DSP_CTRL1_MTRX_EN 0x10 /* Color matrix ON/OFF selection */ +#define DSP_CTRL1_INTRP_EN 0x08 /* Interpolation ON/OFF selection */ +#define DSP_CTRL1_GAMMA_EN 0x04 /* Gamma function ON/OFF selection */ +#define DSP_CTRL1_BLACK_EN 0x02 /* Black defect auto correction ON/OFF */ +#define DSP_CTRL1_WHITE_EN 0x01 /* White defect auto correction ON/OFF */ + +#define DSP_CTRL2 0x65 /* DSP Control Byte 2 */ +#define DSP_CTRL2_VDCW_EN 0x08 /* Vertical DCW enable */ +#define DSP_CTRL2_HDCW_EN 0x04 /* Horizontal DCW enable */ +#define DSP_CTRL2_VZOOM_EN 0x02 /* Vertical zoom out enable */ +#define DSP_CTRL2_HZOOM_EN 0x01 /* Horizontal zoom out enable */ + +#define DSP_CTRL3 0x66 /* DSP Control Byte 3 */ +#define DSP_CTRL3_UV_EN 0x80 /* UV output sequence option */ +#define DSP_CTRL3_CBAR_EN 0x20 /* DSP color bar ON/OFF selection */ +#define DSP_CTRL3_FIFO_EN 0x08 /* FIFO power down ON/OFF selection */ +#define DSP_CTRL3_SCAL1_PWDN 0x04 /* Scaling module power down control 1 */ +#define DSP_CTRL3_SCAL2_PWDN 0x02 /* Scaling module power down control 2 */ +#define DSP_CTRL3_INTRP_PWDN 0x01 /* Interpolation module power down control */ +#define DSP_CTRL3_SET_CBAR(r, x) ((r & 0xDF) | ((x & 1) << 5)) + +#define DSP_CTRL4 0x67 /* DSP Control Byte 4 */ +#define DSP_CTRL4_YUV_RGB 0x00 /* Output selection YUV or RGB */ +#define DSP_CTRL4_RAW8 0x02 /* Output selection RAW8 */ +#define DSP_CTRL4_RAW10 0x03 /* Output selection RAW10 */ + +#define AWB_BIAS 0x68 /* AWB BLC Level Clip */ +#define AWB_CTRL1 0x69 /* AWB Control 1 */ +#define AWB_CTRL2 0x6A /* AWB Control 2 */ + +#define AWB_CTRL3 0x6B /* AWB Control 3 */ +#define AWB_CTRL3_ADVANCED 0x80 /* AWB mode select - Advanced AWB */ +#define AWB_CTRL3_SIMPLE 0x00 /* AWB mode select - Simple AWB */ + +#define AWB_CTRL4 0x6C /* AWB Control 4 */ +#define AWB_CTRL5 0x6D /* AWB Control 5 */ +#define AWB_CTRL6 0x6E /* AWB Control 6 */ +#define AWB_CTRL7 0x6F /* AWB Control 7 */ +#define AWB_CTRL8 0x70 /* AWB Control 8 */ +#define AWB_CTRL9 0x71 /* AWB Control 9 */ +#define AWB_CTRL10 0x72 /* AWB Control 10 */ +#define AWB_CTRL11 0x73 /* AWB Control 11 */ +#define AWB_CTRL12 0x74 /* AWB Control 12 */ +#define AWB_CTRL13 0x75 /* AWB Control 13 */ +#define AWB_CTRL14 0x76 /* AWB Control 14 */ +#define AWB_CTRL15 0x77 /* AWB Control 15 */ +#define AWB_CTRL16 0x78 /* AWB Control 16 */ +#define AWB_CTRL17 0x79 /* AWB Control 17 */ +#define AWB_CTRL18 0x7A /* AWB Control 18 */ +#define AWB_CTRL19 0x7B /* AWB Control 19 */ +#define AWB_CTRL20 0x7C /* AWB Control 20 */ +#define AWB_CTRL21 0x7D /* AWB Control 21 */ +#define GAM1 0x7E /* Gamma Curve 1st Segment Input End Point 0x04 Output Value */ +#define GAM2 0x7F /* Gamma Curve 2nd Segment Input End Point 0x08 Output Value */ +#define GAM3 0x80 /* Gamma Curve 3rd Segment Input End Point 0x10 Output Value */ +#define GAM4 0x81 /* Gamma Curve 4th Segment Input End Point 0x20 Output Value */ +#define GAM5 0x82 /* Gamma Curve 5th Segment Input End Point 0x28 Output Value */ +#define GAM6 0x83 /* Gamma Curve 6th Segment Input End Point 0x30 Output Value */ +#define GAM7 0x84 /* Gamma Curve 7th Segment Input End Point 0x38 Output Value */ +#define GAM8 0x85 /* Gamma Curve 8th Segment Input End Point 0x40 Output Value */ +#define GAM9 0x86 /* Gamma Curve 9th Segment Input Enpd Point 0x48 Output Value */ +#define GAM10 0x87 /* Gamma Curve 10th Segment Input End Point 0x50 Output Value */ +#define GAM11 0x88 /* Gamma Curve 11th Segment Input End Point 0x60 Output Value */ +#define GAM12 0x89 /* Gamma Curve 12th Segment Input End Point 0x70 Output Value */ +#define GAM13 0x8A /* Gamma Curve 13th Segment Input End Point 0x90 Output Value */ +#define GAM14 0x8B /* Gamma Curve 14th Segment Input End Point 0xB0 Output Value */ +#define GAM15 0x8C /* Gamma Curve 15th Segment Input End Point 0xD0 Output Value */ +#define SLOP 0x8D /* Gamma Curve Highest Segment Slope */ +#define DNSTH 0x8E /* De-noise Threshold */ +#define EDGE0 0x8F /* Edge Enhancement Strength Control */ +#define EDGE1 0x90 /* Edge Enhancement Threshold Control */ +#define DNSOFF 0x91 /* Auto De-noise Threshold Control */ +#define EDGE2 0x92 /* Edge Enhancement Strength Upper Limit */ +#define EDGE3 0x93 /* Edge Enhancement Strength Upper Limit */ +#define MTX1 0x94 /* Matrix Coefficient 1 */ +#define MTX2 0x95 /* Matrix Coefficient 2 */ +#define MTX3 0x96 /* Matrix Coefficient 3 */ +#define MTX4 0x97 /* Matrix Coefficient 4 */ +#define MTX5 0x98 /* Matrix Coefficient 5 */ +#define MTX6 0x99 /* Matrix Coefficient 6 */ + +#define MTX_CTRL 0x9A /* Matrix Control */ +#define MTX_CTRL_DBL_EN 0x80 /* Matrix double ON/OFF selection */ + +#define BRIGHTNESS 0x9B /* Brightness Control */ +#define CONTRAST 0x9C /* Contrast Gain */ +#define UVADJ0 0x9E /* Auto UV Adjust Control 0 */ +#define UVADJ1 0x9F /* Auto UV Adjust Control 1 */ +#define SCAL0 0xA0 /* DCW Ratio Control */ +#define SCAL1 0xA1 /* Horizontal Zoom Out Control */ +#define SCAL2 0xA2 /* Vertical Zoom Out Control */ +#define FIFODLYM 0xA3 /* FIFO Manual Mode Delay Control */ +#define FIFODLYA 0xA4 /* FIFO Auto Mode Delay Control */ + +#define SDE 0xA6 /* Special Digital Effect Control */ +#define SDE_NEGATIVE_EN 0x40 /* Negative image enable */ +#define SDE_GRAYSCALE_EN 0x20 /* Gray scale image enable */ +#define SDE_V_FIXED_EN 0x10 /* V fixed value enable */ +#define SDE_U_FIXED_EN 0x08 /* U fixed value enable */ +#define SDE_CONT_BRIGHT_EN 0x04 /* Contrast/Brightness enable */ +#define SDE_SATURATION_EN 0x02 /* Saturation enable */ +#define SDE_HUE_EN 0x01 /* Hue enable */ + +#define USAT 0xA7 /* U Component Saturation Gain */ +#define VSAT 0xA8 /* V Component Saturation Gain */ +#define HUECOS 0xA9 /* Cosine value × 0x80 */ +#define HUESIN 0xAA /* Sine value × 0x80 */ +#define SIGN_BIT 0xAB /* Sign Bit for Hue and Brightness */ + +#define DSPAUTO 0xAC /* DSP Auto Function ON/OFF Control */ +#define DSPAUTO_AWB_EN 0x80 /* AWB auto threshold control */ +#define DSPAUTO_DENOISE_EN 0x40 /* De-noise auto threshold control */ +#define DSPAUTO_EDGE_EN 0x20 /* Sharpness (edge enhancement) auto strength control */ +#define DSPAUTO_UV_EN 0x10 /* UV adjust auto slope control */ +#define DSPAUTO_SCAL0_EN 0x08 /* Auto scaling factor control (register SCAL0 (0xA0)) */ +#define DSPAUTO_SCAL1_EN 0x04 /* Auto scaling factor control (registers SCAL1 (0xA1 and SCAL2 (0xA2)) */ + +#endif //__REG_REGS_H__ diff --git a/components/3rd_party/omv/omv/sensors/ov9650.c b/components/3rd_party/omv/omv/sensors/ov9650.c new file mode 100644 index 00000000..a08f03d8 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/ov9650.c @@ -0,0 +1,572 @@ +/* + * 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. + * + * OV9650 driver. + */ +#include "omv_boardconfig.h" +#if (OMV_ENABLE_OV9650 == 1) + +#include +#include +#include + +#include "omv_i2c.h" +#include "sensor.h" +#include "ov9650.h" +#include "py/mphal.h" +#include "ov9650_regs.h" + +#define NUM_BR_LEVELS 7 + +static const uint8_t default_regs[][2] = { + /* See Implementation Guide */ + {REG_COM2, 0x01}, /* Output drive x2 */ + {REG_COM5, 0x00}, /* System clock */ + {REG_CLKRC, 0x81}, /* Clock control 30 FPS*/ + {REG_MVFP, 0x10}, /* Mirror/VFlip */ + + {REG_OFON, 0x43}, /* Power down register */ + {REG_ACOM38, 0x12}, /* reserved */ + {REG_ADC, 0x91}, /* reserved */ + {REG_RSVD35, 0x81}, /* reserved */ + + /* Default QQVGA-RGB565 */ + {REG_COM7, 0x14}, /* QVGA/RGB565 */ + {REG_COM1, 0x24}, /* QQVGA/Skip Option */ + {REG_COM3, 0x04}, /* Vario Pixels */ + {REG_COM4, 0x80}, /* Vario Pixels */ + {REG_COM15, 0xD0}, /* Output range 0x00-0xFF/RGB565*/ + + /* YUV fmt /Special Effects Controls */ + {REG_TSLB, 0x01}, /* YUYV/DBLC Enable/Bitwise reverse*/ + {REG_MANU, 0x80}, /* Manual U */ + {REG_MANV, 0x80}, /* Manual V */ + + /* Dummy pixels settings */ + {REG_EXHCH, 0x00}, /* Dummy Pixel Insert MSB */ + {REG_EXHCL, 0x00}, /* Dummy Pixel Insert LSB */ + + {REG_ADVFH, 0x00}, /* Dummy Pixel Insert MSB */ + {REG_ADVFL, 0x00}, /* Dummy Pixel Insert LSB */ + + /* See Implementation Guide Section 3.4.1.2 */ + {REG_COM8, 0xA7}, /* Enable Fast Mode AEC/Enable Banding Filter/AGC/AWB/AEC */ + {0x60, 0x8C}, /* Normal AWB, 0x0C for Advanced AWB */ + {REG_AEW, 0x70}, /* AGC/AEC Threshold Upper Limit */ + {REG_AEB, 0x64}, /* AGC/AEC Threshold Lower Limit */ + {REG_VPT, 0xC3}, /* Fast AEC operating region */ + + + /* See OV9650 Implementation Guide */ + {REG_COM11, 0x01}, /* Night Mode-Automatic/Manual Banding Filter */ + {REG_MBD, 0x1a}, /* MBD[7:0] Manual banding filter LSB */ + {REG_HV, 0x0A}, /* HV[0] Manual banding filter MSB */ + {REG_COM12, 0x04}, /* HREF options/ UV average */ + {REG_COM9, 0x20}, /* Gain ceiling [6:4]/Over-Exposure */ + {REG_COM16, 0x02}, /* Color matrix coeff double option */ + {REG_COM13, 0x10}, /* Gamma/Colour Matrix/UV delay */ + {REG_COM23, 0x00}, /* Disable Color bar/Analog Color Gain */ + {REG_PSHFT, 0x00}, /* Pixel delay after HREF */ + {REG_COM10, 0x00}, /* Slave mode, HREF vs HSYNC, signals negate */ + {REG_EDGE, 0xa6}, /* Edge enhancement threshold and factor */ + {REG_COM6, 0x43}, /* HREF & ADBLC options */ + {REG_COM22, 0x20}, /* Edge enhancement/Denoising */ + + /* Some registers discovered with probing */ + {REG_COM21, 0x00}, /* COM21[3] Digital Zoom */ + {REG_GRCOM, 0x24}, /* Enable Internal Regulator */ + {0xaa, 0x00}, /* some edge effect 0x80 */ + {0xab, 0x00}, /* makes image blurry 0x40 */ + +#if 0 + /* When AEC is not used */ + {REG_AECH, 0x00}, /* Exposure Value MSB */ + {REG_AECHM, 0x00}, /* Exposure Value LSB */ +#endif + +#if 0 + /* Windowing Settings */ + {REG_HSTART, 0x1d}, /* Horiz start high bits */ + {REG_HSTOP, 0xbd}, /* Horiz stop high bits */ + {REG_HREF, 0xbf}, /* HREF pieces */ + + {REG_VSTART, 0x00}, /* Vert start high bits */ + {REG_VSTOP, 0x80}, /* Vert stop high bits */ + {REG_VREF, 0x12}, /* Pieces of GAIN, VSTART, VSTOP */ +#endif + +#if 1 + /* gamma curve p */ + {0x6c, 0x40}, + {0x6d, 0x30}, + {0x6e, 0x4b}, + {0x6f, 0x60}, + {0x70, 0x70}, + {0x71, 0x70}, + {0x72, 0x70}, + {0x73, 0x70}, + {0x74, 0x60}, + {0x75, 0x60}, + {0x76, 0x50}, + {0x77, 0x48}, + {0x78, 0x3a}, + {0x79, 0x2e}, + {0x7a, 0x28}, + {0x7b, 0x22}, + + /* Gamma curve T */ + {0x7c, 0x04}, + {0x7d, 0x07}, + {0x7e, 0x10}, + {0x7f, 0x28}, + {0x80, 0x36}, + {0x81, 0x44}, + {0x82, 0x52}, + {0x83, 0x60}, + {0x84, 0x6c}, + {0x85, 0x78}, + {0x86, 0x8c}, + {0x87, 0x9e}, + {0x88, 0xbb}, + {0x89, 0xd2}, + {0x8a, 0xe6}, + + /* Reserved Registers, see OV965x App Note */ + {0x16, 0x07}, + {0x96, 0x04}, + {0x8e, 0x00}, + {0x94, 0x88}, + {0x95, 0x88}, + {0x5c, 0x96}, + {0x5d, 0x96}, + {0x5e, 0x10}, + {0x59, 0xeb}, + {0x5a, 0x9c}, + {0x5b, 0x55}, +#endif + + /* NULL reg */ + {0x00, 0x00} +}; + +static const uint8_t rgb565_regs[][2] = { + /* See Implementation Guide */ + {REG_COM3, 0x04}, /* Vario Pixels */ + {REG_COM4, 0x80}, /* Vario Pixels */ + {REG_COM15, 0xD0}, /* Output range 0x00-0xFF/RGB565*/ + + /* RGB color matrix */ + {REG_MTX1, 0x71}, + {REG_MTX2, 0x3e}, + {REG_MTX3, 0x0c}, + + {REG_MTX4, 0x33}, + {REG_MTX5, 0x72}, + {REG_MTX6, 0x00}, + + {REG_MTX7, 0x2b}, + {REG_MTX8, 0x66}, + {REG_MTX9, 0xd2}, + {REG_MTXS, 0x65}, + + /* NULL reg */ + {0x00, 0x00} +}; + +static const uint8_t yuv422_regs[][2] = { + /* See Implementation Guide */ + {REG_COM3, 0x04}, /* Vario Pixels */ + {REG_COM4, 0x80}, /* Vario Pixels */ + {REG_COM15, 0xC0}, /* Output range 0x00-0xFF */ + + /* YUV color matrix */ + {REG_MTX1, 0x3a}, + {REG_MTX2, 0x3d}, + {REG_MTX3, 0x03}, + + {REG_MTX4, 0x12}, + {REG_MTX5, 0x26}, + {REG_MTX6, 0x38}, + + {REG_MTX7, 0x40}, + {REG_MTX8, 0x40}, + {REG_MTX9, 0x40}, + {REG_MTXS, 0x0d}, + + /* NULL reg */ + {0x00, 0x00} +}; + +static int reset(sensor_t *sensor) { + int i = 0; + const uint8_t(*regs)[2] = default_regs; + + /* Reset all registers */ + omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_COM7, 0x80); + + /* delay n ms */ + mp_hal_delay_ms(10); + + /* Write initial registers */ + while (regs[i][0]) { + omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, regs[i][0], regs[i][1]); + i++; + } + + return 0; +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) { + int i = 0; + const uint8_t(*regs)[2]; + uint8_t com7 = 0; /* framesize/RGB */ + + /* read pixel format reg */ + omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_COM7, &com7); + + switch (pixformat) { + case PIXFORMAT_RGB565: + com7 |= REG_COM7_RGB; + regs = rgb565_regs; + break; + case PIXFORMAT_YUV422: + com7 &= (~REG_COM7_RGB); + regs = yuv422_regs; + break; + case PIXFORMAT_GRAYSCALE: + com7 &= (~REG_COM7_RGB); + regs = yuv422_regs; + break; + default: + return -1; + } + + /* Set pixel format */ + omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_COM7, com7); + + /* Write pixel format registers */ + while (regs[i][0]) { + omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, regs[i][0], regs[i][1]); + i++; + } + + return 0; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) { + uint8_t com7 = 0; /* framesize/RGB */ + uint8_t com1 = 0; /* Skip option */ + + /* read COM7 RGB bit */ + omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_COM7, &com7); + com7 &= REG_COM7_RGB; + + switch (framesize) { + case FRAMESIZE_QQCIF: + com7 |= REG_COM7_QCIF; + com1 |= REG_COM1_QQCIF | REG_COM1_SKIP2; + break; + case FRAMESIZE_QQVGA: + com7 |= REG_COM7_QVGA; + com1 |= REG_COM1_QQVGA | REG_COM1_SKIP2; + break; + case FRAMESIZE_QCIF: + com7 |= REG_COM7_QCIF; + break; + default: + return -1; + } + + /* write the frame size registers */ + omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_COM1, com1); + omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_COM7, com7); + + return 0; +} + +static int set_brightness(sensor_t *sensor, int level) { + int i; + + static uint8_t regs[NUM_BR_LEVELS + 1][3] = { + { REG_AEW, REG_AEB, REG_VPT }, + { 0x1c, 0x12, 0x50 }, /* -3 */ + { 0x3d, 0x30, 0x71 }, /* -2 */ + { 0x50, 0x44, 0x92 }, /* -1 */ + { 0x70, 0x64, 0xc3 }, /* 0 */ + { 0x90, 0x84, 0xd4 }, /* +1 */ + { 0xc4, 0xbf, 0xf9 }, /* +2 */ + { 0xd8, 0xd0, 0xfa }, /* +3 */ + }; + + level += (NUM_BR_LEVELS / 2 + 1); + if (level < 0 || level > NUM_BR_LEVELS) { + return -1; + } + + for (i = 0; i < 3; i++) { + omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, regs[0][i], regs[level][i]); + } + + return 0; +} + +static int set_gainceiling(sensor_t *sensor, gainceiling_t gainceiling) { + /* Write gain ceiling register */ + omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_COM9, (gainceiling << 4)); + return 0; +} + +static int set_auto_gain(sensor_t *sensor, int enable, float gain_db, float gain_db_ceiling) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_COM8, ®); + ret |= + omv_i2c_writeb(&sensor->i2c_bus, + sensor->slv_addr, + REG_COM8, + (reg & (~REG_COM8_AGC)) | ((enable != 0) ? REG_COM8_AGC : 0)); + + if ((enable == 0) && (!isnanf(gain_db)) && (!isinf(gain_db))) { + float gain = IM_MAX(IM_MIN(expf((gain_db / 20.0f) * M_LN10), 128.0f), 1.0f); + + int gain_temp = fast_ceilf(logf(IM_MAX(gain / 2.0f, 1.0f)) / M_LN2); + int gain_hi = 0x3F >> (6 - gain_temp); + int gain_lo = IM_MIN(fast_roundf(((gain / (1 << gain_temp)) - 1.0f) * 16.0f), 15); + + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_GAIN, ((gain_hi & 0x0F) << 4) | (gain_lo << 0)); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_VREF, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_VREF, ((gain_hi & 0x30) << 2) | (reg & 0x3F)); + } else if ((enable != 0) && (!isnanf(gain_db_ceiling)) && (!isinf(gain_db_ceiling))) { + float gain_ceiling = IM_MAX(IM_MIN(expf((gain_db_ceiling / 20.0f) * M_LN10), 128.0f), 2.0f); + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_COM9, ®); + ret |= + omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_COM9, + (reg & 0x8F) | ((fast_ceilf(logf(gain_ceiling) / M_LN2) - 1) << 4)); + } + + return ret; +} + +static int get_gain_db(sensor_t *sensor, float *gain_db) { + uint8_t reg, gain_lo, gain_hi; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_COM8, ®); + + // DISABLED + // if (reg & REG_COM8_AGC) { + // ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_COM8, reg & (~REG_COM8_AGC)); + // } + // DISABLED + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_GAIN, &gain_lo); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_VREF, &gain_hi); + + // DISABLED + // if (reg & REG_COM8_AGC) { + // ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_COM8, reg | REG_COM8_AGC); + // } + // DISABLED + + int gain = ((gain_hi & 0xC0) << 2) | gain_lo; + int hi_gain = 1 << + (((gain >> + 9) & 1) + + ((gain >> 8) & 1) + ((gain >> 7) & 1) + ((gain >> 6) & 1) + ((gain >> 5) & 1) + ((gain >> 4) & 1)); + float lo_gain = 1.0f + (((gain >> 0) & 0xF) / 16.0f); + *gain_db = 20.0f * log10f(hi_gain * lo_gain); + + return ret; +} + +static int set_auto_exposure(sensor_t *sensor, int enable, int exposure_us) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_COM8, ®); + ret |= + omv_i2c_writeb(&sensor->i2c_bus, + sensor->slv_addr, + REG_COM8, + (reg & (~REG_COM8_AEC)) | ((enable != 0) ? REG_COM8_AEC : 0)); + + if ((enable == 0) && (exposure_us >= 0)) { + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_COM7, ®); + int t_line = 0, t_pclk = (reg & REG_COM7_RGB) ? 2 : 1; + + if (reg & REG_COM7_VGA) { + t_line = 640 + 160; + } + if (reg & REG_COM7_CIF) { + t_line = 352 + 168; + } + if (reg & REG_COM7_QVGA) { + t_line = 320 + 80; + } + if (reg & REG_COM7_QCIF) { + t_line = 176 + 84; + } + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_CLKRC, ®); + int pll_mult = (reg & REG_CLKRC_DOUBLE) ? 2 : 1; + int clk_rc = ((reg & REG_CLKRC_DIVIDER_MASK) + 1) * 2; + + int exposure = + IM_MAX(IM_MIN(((exposure_us * (((OMV_XCLK_FREQUENCY / clk_rc) * pll_mult) / 1000000)) / t_pclk) / t_line, 0xFFFF), + 0x0000); + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_COM1, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_COM1, (reg & 0xFC) | ((exposure >> 0) & 0x3)); + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_AECH, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_AECH, (reg & 0x00) | ((exposure >> 2) & 0xFF)); + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_AECHM, ®); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_AECHM, (reg & 0xC0) | ((exposure >> 10) & 0x3F)); + } + + return ret; +} + +static int get_exposure_us(sensor_t *sensor, int *exposure_us) { + uint8_t reg, aec_10, aec_92, aec_1510; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_COM8, ®); + + // DISABLED + // if (reg & REG_COM8_AEC) { + // ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_COM8, reg & (~REG_COM8_AEC)); + // } + // DISABLED + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_COM1, &aec_10); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_AECH, &aec_92); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_AECHM, &aec_1510); + + // DISABLED + // if (reg & REG_COM8_AEC) { + // ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_COM8, reg | REG_COM8_AEC); + // } + // DISABLED + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_COM7, ®); + int t_line = 0, t_pclk = (reg & REG_COM7_RGB) ? 2 : 1; + + if (reg & REG_COM7_VGA) { + t_line = 640 + 160; + } + if (reg & REG_COM7_CIF) { + t_line = 352 + 168; + } + if (reg & REG_COM7_QVGA) { + t_line = 320 + 80; + } + if (reg & REG_COM7_QCIF) { + t_line = 176 + 84; + } + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_CLKRC, ®); + int pll_mult = (reg & REG_CLKRC_DOUBLE) ? 2 : 1; + int clk_rc = ((reg & REG_CLKRC_DIVIDER_MASK) + 1) * 2; + + uint16_t exposure = ((aec_1510 & 0x3F) << 10) + ((aec_92 & 0xFF) << 2) + ((aec_10 & 0x3) << 0); + *exposure_us = (exposure * t_line * t_pclk) / (((OMV_XCLK_FREQUENCY / clk_rc) * pll_mult) / 1000000); + + return ret; +} + +static int set_auto_whitebal(sensor_t *sensor, int enable, float r_gain_db, float g_gain_db, float b_gain_db) { + uint8_t reg; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_COM8, ®); + ret |= + omv_i2c_writeb(&sensor->i2c_bus, + sensor->slv_addr, + REG_COM8, + (reg & (~REG_COM8_AWB)) | ((enable != 0) ? REG_COM8_AWB : 0)); + + if ((enable == 0) && (!isnanf(r_gain_db)) && (!isnanf(g_gain_db)) && (!isnanf(b_gain_db)) + && (!isinff(r_gain_db)) && (!isinff(g_gain_db)) && (!isinff(b_gain_db))) { + int r_gain = IM_MAX(IM_MIN(fast_roundf(expf((r_gain_db / 20.0f) * M_LN10) * 128.0f), 255), 0); + int b_gain = IM_MAX(IM_MIN(fast_roundf(expf((b_gain_db / 20.0f) * M_LN10) * 128.0f), 255), 0); + + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_BLUE, b_gain); + ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_RED, r_gain); + } + + return ret; +} + +static int get_rgb_gain_db(sensor_t *sensor, float *r_gain_db, float *g_gain_db, float *b_gain_db) { + uint8_t reg, blue, red; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_COM8, ®); + + // DISABLED + // if (reg & REG_COM8_AWB) { + // ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_COM8, reg & (~REG_COM8_AWB)); + // } + // DISABLED + + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_BLUE, &blue); + ret |= omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_RED, &red); + + // DISABLED + // if (reg & REG_COM8_AWB) { + // ret |= omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_COM8, reg | REG_COM8_AWB); + // } + // DISABLED + + *r_gain_db = 20.0f * log10f(red / 128.0f); + *b_gain_db = 20.0f * log10f(blue / 128.0f); + + return ret; +} + +static int set_hmirror(sensor_t *sensor, int enable) { + uint8_t val; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_MVFP, &val); + ret |= + omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_MVFP, + enable ? (val | REG_MVFP_HMIRROR) : (val & (~REG_MVFP_HMIRROR))); + + return ret; +} + +static int set_vflip(sensor_t *sensor, int enable) { + uint8_t val; + int ret = omv_i2c_readb(&sensor->i2c_bus, sensor->slv_addr, REG_MVFP, &val); + ret |= + omv_i2c_writeb(&sensor->i2c_bus, sensor->slv_addr, REG_MVFP, + enable ? (val | REG_MVFP_VFLIP) : (val & (~REG_MVFP_VFLIP))); + + return ret; +} + +int ov9650_init(sensor_t *sensor) { + // Initialize sensor structure. + sensor->reset = reset; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_brightness = set_brightness; + sensor->set_gainceiling = set_gainceiling; + sensor->set_auto_gain = set_auto_gain; + sensor->get_gain_db = get_gain_db; + sensor->set_auto_exposure = set_auto_exposure; + sensor->get_exposure_us = get_exposure_us; + sensor->set_auto_whitebal = set_auto_whitebal; + sensor->get_rgb_gain_db = get_rgb_gain_db; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + + // Set sensor flags + sensor->hw_flags.vsync = 1; + sensor->hw_flags.hsync = 0; + sensor->hw_flags.pixck = 1; + sensor->hw_flags.fsync = 0; + sensor->hw_flags.jpege = 0; + sensor->hw_flags.gs_bpp = 2; + sensor->hw_flags.rgb_swap = 1; + sensor->hw_flags.yuv_order = SENSOR_HW_FLAGS_YVU422; + + return 0; +} +#endif // (OMV_ENABLE_OV9650 == 1) diff --git a/components/3rd_party/omv/omv/sensors/ov9650.h b/components/3rd_party/omv/omv/sensors/ov9650.h new file mode 100644 index 00000000..b3fb28d4 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/ov9650.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. + * + * OV9650 driver. + */ +#ifndef __OV9650_H__ +#define __OV9650_H__ +int ov9650_init(sensor_t *sensor); +#endif // __OV9650_H__ diff --git a/components/3rd_party/omv/omv/sensors/ov9650_regs.h b/components/3rd_party/omv/omv/sensors/ov9650_regs.h new file mode 100644 index 00000000..2c3be4cd --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/ov9650_regs.h @@ -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. + * + * OV9650 register definitions. + */ +#ifndef __REG_REGS_H__ +#define __REG_REGS_H__ + +#define REG_GAIN 0x00 +#define REG_BLUE 0x01 +#define REG_RED 0x02 +#define REG_VREF 0x03 +#define REG_COM1 0x04 +#define REG_BAVE 0x05 +#define REG_GEAVE 0x06 +#define REG_RAVE 0x08 +#define REG_COM2 0x09 +#define REG_PID 0x0A +#define REG_VER 0x0B +#define REG_COM3 0x0C +#define REG_COM4 0x0D +#define REG_COM5 0x0E +#define REG_COM6 0x0F +#define REG_AECH 0x10 +#define REG_CLKRC 0x11 +#define REG_COM7 0x12 +#define REG_COM8 0x13 +#define REG_COM9 0x14 +#define REG_COM10 0x15 +#define REG_RSVD16 0x16 +#define REG_HSTART 0x17 +#define REG_HSTOP 0x18 +#define REG_VSTART 0x19 +#define REG_VSTOP 0x1A +#define REG_PSHFT 0x1B +#define REG_MIDH 0x1C +#define REG_MIDL 0x1D +#define REG_MVFP 0x1E +#define REG_BOS 0x20 +#define REG_GBOS 0x21 +#define REG_GROS 0x22 +#define REG_ROS 0x23 +#define REG_AEW 0x24 +#define REG_AEB 0x25 +#define REG_VPT 0x26 +#define REG_BBIAS 0x27 +#define REG_GbBIAS 0x28 +#define REG_GRCOM 0x29 +#define REG_EXHCH 0x2A +#define REG_EXHCL 0x2B +#define REG_RBIAS 0x2C +#define REG_ADVFL 0x2D +#define REG_ADVFH 0x2E +#define REG_YAVE 0x2F +#define REG_HSYST 0x30 +#define REG_HSYEN 0x31 +#define REG_HREF 0x32 +#define REG_CHLF 0x33 +#define REG_ARBLM 0x34 +#define REG_RSVD35 0x35 +#define REG_RSVD36 0x36 +#define REG_ADC 0x37 +#define REG_ACOM38 0x38 +#define REG_OFON 0x39 +#define REG_TSLB 0x3A +#define REG_COM11 0x3B +#define REG_COM12 0x3C +#define REG_COM13 0x3D +#define REG_COM14 0x3E +#define REG_EDGE 0x3F +#define REG_COM15 0x40 +#define REG_COM16 0x41 +#define REG_COM17 0x42 +#define REG_MTX1 0x4F +#define REG_MTX2 0x50 +#define REG_MTX3 0x51 +#define REG_MTX4 0x52 +#define REG_MTX5 0x53 +#define REG_MTX6 0x54 +#define REG_MTX7 0x55 +#define REG_MTX8 0x56 +#define REG_MTX9 0x57 +#define REG_MTXS 0x58 +#define REG_LCC1 0x62 +#define REG_LCC2 0x63 +#define REG_LCC3 0x64 +#define REG_LCC4 0x65 +#define REG_LCC5 0x66 +#define REG_MANU 0x67 +#define REG_MANV 0x68 +#define REG_HV 0x69 +#define REG_MBD 0x6A +#define REG_DBLV 0x6B +#define REG_COM21 0x8B +#define REG_COM22 0x8C +#define REG_COM23 0x8D +#define REG_COM24 0x8E +#define REG_DBLC1 0x8F +#define REG_DBLC_B 0x90 +#define REG_DBLC_R 0x91 +#define REG_DMLNL 0x92 +#define REG_DMLNH 0x93 +#define REG_LCCFB 0x9D +#define REG_LCCFR 0x9E +#define REG_DBLC_GB 0x9F +#define REG_DBLC_GR 0xA0 +#define REG_AECHM 0xA1 +#define REG_COM25 0xA4 +#define REG_COM26 0xA5 +#define REG_GGAIN 0xA6 +#define REG_VGAST 0xA7 + +/* register bits */ + +#define REG_COM1_QQCIF (1 << 5) +#define REG_COM1_QQVGA (1 << 5) +#define REG_COM1_SKIP2 (1 << 2) +#define REG_COM1_SKIP3 (1 << 3) +#define REG_COM7_RGB (1 << 2) +#define REG_COM7_QCIF (1 << 3) +#define REG_COM7_QVGA (1 << 4) +#define REG_COM7_CIF (1 << 5) +#define REG_COM7_VGA (1 << 6) +#define REG_COM8_AGC (1 << 2) +#define REG_COM8_AWB (1 << 1) +#define REG_COM8_AEC (1 << 0) +#define REG_MVFP_HMIRROR (1 << 5) +#define REG_MVFP_VFLIP (1 << 4) +#define REG_CLKRC_DOUBLE (1 << 8) +#define REG_CLKRC_DIVIDER_MASK (0x3F) + +#endif //__REG_REGS_H__ diff --git a/components/3rd_party/omv/omv/sensors/paj6100.c b/components/3rd_party/omv/omv/sensors/paj6100.c new file mode 100644 index 00000000..3e75c0ae --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/paj6100.c @@ -0,0 +1,748 @@ +/* + * File: paj6100.c + * Created Date: Tuesday, May 25th 2021, 10:45:35 am + * Author: Lake Fu + * ----- + * Last Modified: Tuesday June 15th 2021 2:54:43 pm + * Modified By: Lake Fu at + * ----- + * MIT License + * + * Copyright (c) 2021 Pixart Inc. + * + * 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. + * ----- + * HISTORY: + * Date By Comments + * ---------- --- ---------------------------------------------------------- + */ +#include "omv_boardconfig.h" +#if (OMV_ENABLE_PAJ6100 == 1) +#include +#include +#include "py/mphal.h" + +#include "sensor.h" +#include "framebuffer.h" +#include "omv_gpio.h" + +#include "paj6100.h" +#include "paj6100_reg.h" +#include "pixspi.h" + +#define CACHE_BANK + +#ifdef DEBUG +// For dump initial result before connect to IDE, +// we use a global variable to store it. +static int8_t init_res; +#endif + +static int16_t bank_cache = -1; + +// Exposure time related parameters + +#define QVGA_MAX_EXPO_PA 85161 +#define QQVGA_MAX_EXPO_PA 25497 + +#define R_AE_MinGain 1 +#define R_AE_MaxGain 8 + +static float R_FrameTime = 6000000 / 30; +static long R_AE_MaxExpoTime; +static long R_AE_MinExpoTime = 120 / 6 + 0.5f; + +#define CAL_MAX_EXPO_TIME(PA) (fast_roundf(((float) (R_FrameTime - PA)) / 6)) + +#define L_TARGET 127 +#define AE_LOCK_RANGE_IN 8 +#define AE_LOCK_RANGE_OUT 16 + +static uint32_t l_total = 0; +static uint32_t l_target_total = 0; +static uint32_t lt_lockrange_in_ubound, lt_lockrange_in_lbound; +static uint32_t lt_lockrange_out_ubound, lt_lockrange_out_lbound; +static bool ae_converged_flag = false; + +static uint8_t skip_frame = 0; + +static bool is_ae_enabled = true; +static int exp_us_cache = -1; +// Exposure time related parameters - + +static int set_auto_gain(sensor_t *sensor, int enable, + float gain_db, float gain_db_ceiling); +static int get_gain_db(sensor_t *sensor, float *gain_db); + +static int bank_switch(uint8_t bank) { + #ifdef CACHE_BANK + if (bank_cache == bank) { + return 0; + } + #endif + + if (pixspi_regs_write(REG_BANK_SWITCH, &bank, 1)) { + printf("Bank switch failed.\n"); + return -1; + } + bank_cache = bank; + return 0; +} + +static int read_regs_w_bank(uint8_t bank, uint8_t addr, uint8_t *buff, uint16_t len) { + if (bank_switch(bank)) { + return -1; + } + return pixspi_regs_read(addr, buff, len); +} + +static int write_regs_w_bank(uint8_t bank, uint8_t addr, uint8_t *buff, uint16_t len) { + if (bank_switch(bank)) { + return -1; + } + return pixspi_regs_write(addr, buff, len); +} + +static int init_sensor(sensor_t *sensor) { +#define ta_seq low_active_R_ABC_Avg_UBx50_T_BLACINV_EnHx1_T_SIG_REFx1 + + int arr_size = sizeof(ta_seq) / sizeof(ta_seq[0]); + for (int i = 0; i < arr_size; i += 2) { + #ifndef DEBUG + int + #endif + init_res = pixspi_regs_write(ta_seq[i], &ta_seq[i + 1], 1); + if (init_res) { + return init_res; + } + } + + // Clear bank cache. + bank_cache = -1; + return 0; +} +//----------------------------------------------------------------- +static int set_exposure(sensor_t *sensor, int exp_us, bool protected) { + // 3642T + static const uint32_t EXPOSURE_BASE = 3642 / (PAJ6100_XCLK_FREQ / 1000000.0f) + 0.5f; + int ret; + uint32_t cmd_expo /* 24-bit available */; + uint16_t exp_offset; + + #ifdef DEBUG_AE + printf("set_exposure() = %d\n", exp_us); + #endif + if (exp_us == 0) { + return 0; + } + + if (exp_us >= EXPOSURE_BASE) { + int param; + if (sensor->framesize == FRAMESIZE_QVGA) { + param = 88803; + } else if (sensor->framesize == FRAMESIZE_QQVGA) { + param = 29139; // QQVGA + } else { + param = 88803; + } + + int max_cmd_expo = R_FrameTime - param; + cmd_expo = (exp_us - EXPOSURE_BASE) * ((float) PAJ6100_XCLK_FREQ / 1000000) + 0.5f; + if (protected) { + if (cmd_expo > max_cmd_expo) { + cmd_expo = max_cmd_expo; + exp_us = (cmd_expo + 3642) / ((float) PAJ6100_XCLK_FREQ / 1000000) + 0.5f; + printf("Exposure time overflow, reset to %dus.\n", exp_us); + } + } + exp_offset = 0; + } else { + cmd_expo = 0; + exp_offset = (EXPOSURE_BASE - exp_us) * ((float) PAJ6100_XCLK_FREQ / 1000000) + 0.5; + } + #ifdef DEBUG_AE + printf("target - cmd_exp: %ld, exp_offset: %d\n", cmd_expo, exp_offset); + #endif + uint8_t buff[3] = { + (uint8_t) (cmd_expo & 0xff), + (uint8_t) ((cmd_expo >> 8) & 0xff), + (uint8_t) ((cmd_expo >> 16) & 0xff), + }; + ret = write_regs_w_bank(BANK_0, REG_CMD_EXPO_L, buff, 3); + + buff[0] = (uint8_t) (exp_offset & 0xff); + buff[1] = (uint8_t) ((exp_offset >> 8) & 0xff); + ret |= write_regs_w_bank(BANK_0, REG_EXP_OFFSET_L, buff, 2); + buff[0] = 1; + ret |= write_regs_w_bank(BANK_1, REG_ISP_UPDATE, buff, 1); + + if (ret) { + printf("Failed to write cmd_expo or exp_offset.\n"); + return -1; + } + exp_us_cache = exp_us; + return ret; +} + +static int get_exposure(sensor_t *sensor) { + int ret; + uint8_t buff[3] = {}; + uint32_t cmd_expo /* 24-bit available */; + uint16_t exp_offset; + + if (exp_us_cache >= 0) { + return exp_us_cache; + } + + ret = read_regs_w_bank(BANK_0, REG_CMD_EXPO_L, buff, 3); + if (ret) { + printf("Read cmd_expo failed.\n"); + return -1; + } + cmd_expo = buff[0] + (((uint32_t) buff[1]) << 8) + (((uint32_t) buff[2]) << 16); + + ret = read_regs_w_bank(BANK_0, REG_EXP_OFFSET_L, buff, 2); + if (ret) { + printf("Read exp_offset failed.\n"); + return -1; + } + exp_offset = buff[0] + (((uint16_t) buff[1]) << 8); + + #ifdef DEBUG_AE + printf("cmd_exp: %ld, exp_offset: %d\n", cmd_expo, exp_offset); + #endif + + if (exp_offset == 0) { + exp_us_cache = (cmd_expo + 3642) / (PAJ6100_XCLK_FREQ / 1000000); + } else { + exp_us_cache = (3642 - exp_offset) / (PAJ6100_XCLK_FREQ / 1000000); + } + + return exp_us_cache; +} + +static void blc_freeze(bool enable) { + uint8_t tmp; + tmp = enable?2:0; + write_regs_w_bank(0, 0x0E, &tmp, 1); + tmp = 1; + write_regs_w_bank(1, 0x00, &tmp, 1); // Update flag +} + +static void auto_exposure(sensor_t *sensor) { + if (!is_ae_enabled) { + return; + } + + if (skip_frame) { + --skip_frame; + return; + } + + if ((lt_lockrange_in_lbound <= l_total) && (l_total <= lt_lockrange_in_ubound)) { + ae_converged_flag = true; + } else if ((l_total > lt_lockrange_out_ubound) || (l_total < lt_lockrange_out_lbound)) { + ae_converged_flag = false; + } + blc_freeze(ae_converged_flag); + + if (ae_converged_flag == false) { + float gain_db = 0; + if (get_gain_db(sensor, &gain_db)) { + printf("Get Gain DB failed.\n"); + return; + } + uint32_t gain; + if (gain_db < 6) { + // 1x gain + gain = 1; + } else if (gain_db < 12) { + // 2x gain + gain = 2; + } else if (gain_db < 18) { + // 4x gain + gain = 4; + } else { + // 8x gain + gain = 8; + } + + int expo = get_exposure(sensor); + if (expo < 0) { + printf("Get exposure failed.\n"); + return; + } + #ifdef DEBUG_AE + printf("Current Gain: %ldx, Expo: %d\n", gain, expo); + #endif + + uint32_t GEP = gain * expo; + float l_ratio = ((float) l_target_total) / l_total; + float GEP_target = GEP * l_ratio; + uint32_t expo_target = IM_MIN(IM_MAX(fast_roundf(GEP_target / R_AE_MinGain), R_AE_MinExpoTime), R_AE_MaxExpoTime); + + #ifdef DEBUG_AE + printf("GEP: %ld ", GEP); + printf("GEP_target: %d (x1000), L_Ratio: %d (x1000)\n", + (int) (GEP_target * 1000), (int) (l_ratio * 1000)); + printf("1st Expo Target: %ld (max: %ld) ", expo_target, R_AE_MaxExpoTime); + #endif + uint32_t gain_target; + float tmp_gain = GEP_target / expo_target; + if (tmp_gain <= 1) { + gain_target = 1; + } else if (tmp_gain <= 2) { + gain_target = 2; + } else if (tmp_gain <= 4) { + gain_target = 4; + } else { + gain_target = 8; + } + //gain_target = IM_MIN(IM_MAX(fast_ceilf(GEP_target/expo_target), R_AE_MinGain), R_AE_MaxGain); + gain_db = 20 * log10f((float) gain_target); + expo_target = IM_MIN(IM_MAX(fast_roundf(GEP_target / gain_target), R_AE_MinExpoTime), R_AE_MaxExpoTime); + #ifdef DEBUG_AE + printf("Gain Target: %ld (%d DB (x1000))\n", gain_target, (int) (gain_db * 1000)); + printf("Final Expo Target: %ld (max: %ld) \n", expo_target, R_AE_MaxExpoTime); + #endif + set_exposure(sensor, expo_target, false); + set_auto_gain(sensor, false, gain_db, 0); + skip_frame = 0; + } +} +//----------------------------------------------------------------- +static int sleep(sensor_t *sensor, int enable) { + int ret; + uint8_t val; + + if (enable) { + ret = read_regs_w_bank(BANK_1, REG_CMD_SENSOR_MODE, &val, 1); + if (ret) { + printf("Failed to read REG_CMD_SENSOR_MODE.\n"); + return -1; + } + val |= (1 << 7); + ret = write_regs_w_bank(BANK_1, REG_CMD_SENSOR_MODE, &val, 1); + if (ret) { + printf("Failed to write REG_CMD_SENSOR_MODE.\n"); + return -1; + } + // Sleep 30ms + mp_hal_delay_ms(30); + ret = read_regs_w_bank(BANK_1, REG_CMD_LPM_ENH, &val, 1); + if (ret) { + printf("Failed to read REG_CMD_SENSOR_MODE.\n"); + return -1; + } + val |= (1 << 6); + ret = write_regs_w_bank(BANK_1, REG_CMD_LPM_ENH, &val, 1); + if (ret) { + printf("Failed to write REG_CMD_LPM_ENH.\n"); + return -1; + } + } else { + ret = read_regs_w_bank(BANK_1, REG_CMD_SENSOR_MODE, &val, 1); + if (ret) { + #ifdef DEBUG + printf("Failed to read REG_CMD_SENSOR_MODE.\n"); + #endif + return -1; + } + val &= ~(1 << 7); + val &= ~(1 << 6); + ret = write_regs_w_bank(BANK_1, REG_CMD_SENSOR_MODE, &val, 1); + if (ret) { + #ifdef DEBUG + printf("Failed to write REG_CMD_SENSOR_MODE.\n"); + #endif + return -1; + } + } + return 0; +} + +static int read_reg(sensor_t *sensor, uint16_t reg_addr) { + uint8_t data; + + if (pixspi_regs_read((uint8_t) reg_addr, &data, 1)) { + return -1; + } + return data; +} + +static int write_reg(sensor_t *sensor, uint16_t reg_addr, uint16_t reg_data) { + uint8_t data = reg_data; + + // Clear bank cache. + bank_cache = -1; + + return pixspi_regs_write((uint8_t) reg_addr, &data, 1); +} + +static int set_pixformat(sensor_t *sensor, pixformat_t pixformat) { + if (pixformat != PIXFORMAT_GRAYSCALE) { + return -1; + } + return 0; +} + +static int set_framesize(sensor_t *sensor, framesize_t framesize) { + int ret = 0; + + uint16_t w = resolution[framesize][0]; + uint16_t h = resolution[framesize][1]; + + uint8_t aavg_VnH, abc_start_line, voffset, abc_sample_size; + ret |= read_regs_w_bank(BANK_0, REG_CMD_AAVG_V /* REG_CMD_AAVG_H */, &aavg_VnH, 1); + ret |= read_regs_w_bank(BANK_0, REG_ABC_START_LINE, &abc_start_line, 1); + ret |= read_regs_w_bank(BANK_1, REG_ABC_SAMPLE_SIZE, &abc_sample_size, 1); + + if (ret) { + printf("Failed to read Average mode registers.\n"); + return -1; + } + + switch (framesize) { + case FRAMESIZE_QVGA: + aavg_VnH &= ~((1 << 3) | (1 << 2)); + abc_start_line = (abc_start_line & ~(0x07 << 1)) | (2 << 1); + voffset = 2; + abc_sample_size = (abc_sample_size & ~(0x07)) | 6; + + R_AE_MaxExpoTime = CAL_MAX_EXPO_TIME(QVGA_MAX_EXPO_PA); + break; + case FRAMESIZE_QQVGA: + aavg_VnH |= (1 << 3) | (1 << 2); + abc_start_line = (abc_start_line & ~(0x07 << 1)) | (1 << 1); + voffset = 1; + abc_sample_size = (abc_sample_size & ~(0x07)) | 5; + + R_AE_MaxExpoTime = CAL_MAX_EXPO_TIME(QQVGA_MAX_EXPO_PA); + break; + default: + printf("PAJ6100 only support QVGA & QQVGA now.\n"); + return -1; + } + + ret |= write_regs_w_bank(BANK_0, REG_CMD_AAVG_V /* REG_CMD_AAVG_H */, &aavg_VnH, 1); + ret |= write_regs_w_bank(BANK_0, REG_ABC_START_LINE, &abc_start_line, 1); + ret |= write_regs_w_bank(BANK_1, REG_CP_WOI_VOFFSET, &voffset, 1); + ret |= write_regs_w_bank(BANK_1, REG_ABC_SAMPLE_SIZE, &abc_sample_size, 1); + + if (ret) { + printf("Failed to write Average mode registers.\n"); + return -1; + } + + l_target_total = L_TARGET * w * h; + lt_lockrange_in_ubound = (L_TARGET + AE_LOCK_RANGE_IN) * w * h; + lt_lockrange_in_lbound = (L_TARGET - AE_LOCK_RANGE_IN) * w * h; + lt_lockrange_out_ubound = (L_TARGET + AE_LOCK_RANGE_OUT) * w * h; + lt_lockrange_out_lbound = (L_TARGET - AE_LOCK_RANGE_OUT) * w * h; + + return 0; +} + +static int set_contrast(sensor_t *sensor, int level) { + return 0; +} + +static int set_brightness(sensor_t *sensor, int level) { + return 0; +} + +static int set_saturation(sensor_t *sensor, int level) { + return 0; +} + +static int set_gainceiling(sensor_t *sensor, gainceiling_t gainceiling) { + return 0; +} + +static int set_special_effect(sensor_t *sensor, sde_t sde) { + return 0; +} + +static int set_auto_gain(sensor_t *sensor, int enable, + float gain_db, float gain_db_ceiling) { + if (enable) { + return 0; + } + int ret = 0; + uint8_t val, fgh, ggh; + + if (gain_db < 6) { + // 1x gain + fgh = 0; + ggh = 0; + } else if (gain_db < 12) { + // 2x gain + fgh = 0; + ggh = 1; + } else if (gain_db < 18) { + // 4x gain + fgh = 2; + ggh = 1; + } else { + // 8x gain + fgh = 3; + ggh = 1; + } + + ret = read_regs_w_bank(BANK_0, REG_FGH, &val, 1); + fgh = (val & ~(3 << 4)) | (fgh << 4); // fgh[5:4] = 0 + ret |= read_regs_w_bank(BANK_0, REG_GGH, &val, 1); + ggh = (val & ~(1 << 7)) | (ggh << 7); // ggh[7] = 1 + if (ret) { + printf("Failed to read FGH or GGH.\n"); + } else { + write_regs_w_bank(BANK_0, REG_FGH, &fgh, 1); + write_regs_w_bank(BANK_0, REG_GGH, &ggh, 1); + val = 1; + write_regs_w_bank(BANK_1, REG_ISP_UPDATE, &val, 1); + } + + return 0; +} + +static int get_gain_db(sensor_t *sensor, float *gain_db) { + int ret = 0; + uint8_t val; + uint8_t fgh, ggh; + + ret |= read_regs_w_bank(BANK_0, REG_FGH, &val, 1); + fgh = (val >> 4) & 0x03; + ret |= read_regs_w_bank(BANK_0, REG_GGH, &val, 1); + ggh = (val >> 7) & 0x01; + + if (ret) { + printf("Failed to read FGH or GGH.\n"); + return -1; + } + + if (ggh == 0 && fgh == 0) { + *gain_db = 0; + } else if (ggh == 1) { + switch (fgh) { + case 0: + *gain_db = 6; + break; + case 2: + *gain_db = 12; + break; + case 3: + *gain_db = 18; + break; + default: { + printf("Read a undefined FGH value (%d).\n", fgh); + return -1; + } + } + } + + return 0; +} + +static int set_auto_exposure(sensor_t *sensor, int enable, int exposure_us) { + if (enable) { + is_ae_enabled = true; + return 0; + } else { + is_ae_enabled = false; + return set_exposure(sensor, exposure_us, false); + } +} + +static int get_exposure_us(sensor_t *sensor, int *exposure_us) { + int ret = get_exposure(sensor); + if (ret >= 0) { + *exposure_us = ret; + return 0; + } + return ret; +} + +static int set_auto_whitebal(sensor_t *sensor, int enable, float r_gain_db, float g_gain_db, float b_gain_db) { + return 0; +} + +static int get_rgb_gain_db(sensor_t *sensor, float *r_gain_db, float *g_gain_db, float *b_gain_db) { + return 0; +} + +static int set_hmirror(sensor_t *sensor, int enable) { + int ret; + uint8_t val; + ret = read_regs_w_bank(BANK_0, REG_CMD_HSYNC_INV, &val, 1); + if (ret) { + printf("Failed to read REG_CMD_HSYNC_INV.\n"); + return -1; + } + + val = enable == 1?val | (0x01):val & ~(0x01); + + ret = write_regs_w_bank(BANK_0, REG_CMD_HSYNC_INV, &val, 1); + ret = write_regs_w_bank(BANK_1, REG_ISP_UPDATE, &val, 1); + if (ret) { + printf("Failed to write REG_CMD_HSYNC_INV.\n"); + } + return ret; +} +static int set_vflip(sensor_t *sensor, int enable) { + int ret; + uint8_t val; + ret = read_regs_w_bank(BANK_0, REG_CMD_VSYNC_INV, &val, 1); + if (ret) { + printf("Failed to read REG_CMD_VSYNC_INV.\n"); + return -1; + } + + val = enable == 1?val | (0x01 << 1):val & ~(0x01 << 1); + + ret = write_regs_w_bank(BANK_0, REG_CMD_VSYNC_INV, &val, 1); + ret = write_regs_w_bank(BANK_1, REG_ISP_UPDATE, &val, 1); + if (ret) { + printf("Failed to write REG_CMD_VSYNC_INV.\n"); + } + return ret; +} +static int set_lens_correction(sensor_t *sensor, int enable, int radi, int coef) { + return 0; +} +static int reset(sensor_t *sensor) { + int ret; + bank_cache = -1; + exp_us_cache = -1; + + #ifdef DEBUG + uint8_t part_id_l = 0, part_id_h = 0; + read_regs_w_bank(0, 0x00, &part_id_l, 1); + read_regs_w_bank(0, 0x01, &part_id_h, 1); + printf("Part ID 0x%x 0x%x\n", part_id_l, part_id_h); + printf("init_res: %d\n", init_res); + #endif + + // Re-init sensor every time. + init_sensor(sensor); + + // Fetch default R_Frame_Time + uint8_t buff[3] = {}; + ret = read_regs_w_bank(BANK_0, REG_FRAME_TIME_L, buff, 3); + if (ret) { + printf("Read cmd_expo failed.\n"); + } else { + R_FrameTime = buff[0] + (((uint32_t) buff[1]) << 8) + (((uint32_t) buff[2]) << 16); + } + + // Default Gain 2x + uint8_t val, fgh, ggh; + ret = read_regs_w_bank(BANK_0, REG_FGH, &val, 1); + fgh = (val & ~(3 << 4)); // fgh[5:4] = 0 + ret |= read_regs_w_bank(BANK_0, REG_GGH, &val, 1); + ggh = (val & ~(1 << 7)) | (0x01 << 7); // ggh[7] = 1 + if (ret) { + printf("Failed to read FGH or GGH.\n"); + } else { + write_regs_w_bank(BANK_0, REG_FGH, &fgh, 1); + write_regs_w_bank(BANK_0, REG_GGH, &ggh, 1); + val = 1; + write_regs_w_bank(BANK_1, REG_ISP_UPDATE, &val, 1); + } + return 0; +} + +static int paj6100_snapshot(sensor_t *sensor, image_t *image, uint32_t flags) { + int res = sensor_snapshot(sensor, image, flags); + if (res == 0) { + l_total = 0; + int iml = image->h * image->w; + for (int i = 0; i < iml; ++i) { + l_total += image->data[i]; + } + // PAJ6100 doesn't support HW auto-exposure, + // we provide a software implementation. + auto_exposure(sensor); + } + + return res; +} + +int paj6100_init(sensor_t *sensor) { + // Initialize sensor structure. + sensor->reset = reset; + sensor->sleep = sleep; + sensor->read_reg = read_reg; + sensor->write_reg = write_reg; + sensor->set_pixformat = set_pixformat; + sensor->set_framesize = set_framesize; + sensor->set_contrast = set_contrast; + sensor->set_brightness = set_brightness; + sensor->set_saturation = set_saturation; + sensor->set_gainceiling = set_gainceiling; + sensor->set_auto_gain = set_auto_gain; + sensor->get_gain_db = get_gain_db; + sensor->set_auto_exposure = set_auto_exposure; + sensor->get_exposure_us = get_exposure_us; + sensor->set_auto_whitebal = set_auto_whitebal; + + sensor->get_rgb_gain_db = get_rgb_gain_db; + sensor->set_hmirror = set_hmirror; + sensor->set_vflip = set_vflip; + sensor->set_special_effect = set_special_effect; + sensor->set_lens_correction = set_lens_correction; + + sensor->snapshot = paj6100_snapshot; + + // Set sensor flags + sensor->hw_flags.vsync = 1; + sensor->hw_flags.hsync = 1; + sensor->hw_flags.pixck = 1; + sensor->hw_flags.fsync = 0; + sensor->hw_flags.jpege = 0; + sensor->hw_flags.gs_bpp = 1; + + init_sensor(sensor); + + return 0; +} + +bool paj6100_detect(sensor_t *sensor) { + int ret = 0; + uint8_t part_id_l, part_id_h; + + omv_gpio_write(DCMI_RESET_PIN, 1); + mp_hal_delay_ms(10); + + if (!pixspi_init()) { + printf("Initial pixspi failed.\n"); + return false; + } + + ret |= pixspi_regs_read(0x00, &part_id_l, 1); + ret |= pixspi_regs_read(0x01, &part_id_h, 1); + #ifdef DEBUG + printf("Part ID 0x%x 0x%x\n", part_id_l, part_id_h); + #endif + if (ret == 0 && (part_id_l == 0x00 && part_id_h == 0x61)) { + return true; // Got you. + } + + pixspi_release(); + return false; +} +#endif //(OMV_ENABLE_PAJ6100 == 1) diff --git a/components/3rd_party/omv/omv/sensors/paj6100.h b/components/3rd_party/omv/omv/sensors/paj6100.h new file mode 100644 index 00000000..0a8874db --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/paj6100.h @@ -0,0 +1,16 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2019 Lake Fu + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * PAJ6100 driver. + * + */ +#ifndef __PAJ6100_H__ +#define __PAJ6100_H__ + +#define PAJ6100_XCLK_FREQ 6000000 + +bool paj6100_detect(sensor_t *sensor); +int paj6100_init(sensor_t *sensor); +#endif diff --git a/components/3rd_party/omv/omv/sensors/paj6100_reg.h b/components/3rd_party/omv/omv/sensors/paj6100_reg.h new file mode 100644 index 00000000..14cc24e1 --- /dev/null +++ b/components/3rd_party/omv/omv/sensors/paj6100_reg.h @@ -0,0 +1,146 @@ +/* + * File: paj6100u6_reg.h + * Created Date: Tuesday, May 25th 2021, 10:45:35 am + * Author: Lake Fu + * ----- + * Last Modified: Sunday May 30th 2021 2:39:45 pm + * Modified By: Lake Fu at + * ----- + * MIT License + * + * Copyright (c) 2021 Pixart Inc. + * + * 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. + * ----- + * HISTORY: + * Date By Comments + * ---------- --- ---------------------------------------------------------- + */ + +#include + +#define REG_BANK_SWITCH 0x7F + +// ======================================= +#define BANK_0 0x00 +// --------------------------------------- +#define REG_CMD_HSYNC_INV 0X07 // bit7 +#define REG_CMD_VSYNC_INV 0X07 // bit6 + +#define REG_CMD_AAVG_V 0X07 // bit3 +#define REG_CMD_AAVG_H 0X07 // bit2 + +#define REG_ABC_START_LINE 0x0D // bit3:1 + + +#define REG_FGH 0x11 // bit5:4 +#define REG_GGH 0x12 // bit7 + +#define REG_EXP_OFFSET_L 0x18 +#define REG_EXP_OFFSET_H 0x19 +#define REG_CMD_EXPO_L 0x48 +#define REG_CMD_EXPO_H 0x49 +#define REG_CMD_EXPO_2H 0x4A + +#define REG_FRAME_TIME_L 0x7A +#define REG_FRAME_TIME_H 0x7B +#define REG_FRAME_TIME_2H 0x7C +// ======================================= +#define BANK_1 0x01 +// --------------------------------------- +#define REG_ISP_UPDATE 0x00 + +#define REG_CMD_SENSOR_MODE 0X23 // bit7 +#define REG_CMD_LPM_ENH 0x23 // bit6 + +#define REG_CP_WOI_VOFFSET 0x45 +#define REG_ABC_SAMPLE_SIZE 0x46 // bit2:0 + + +// 20180504 +static const uint8_t low_active_R_ABC_Avg_UBx50_T_BLACINV_EnHx1_T_SIG_REFx1[] = { +//address, value + 0x7F, 0x00, + 0x0C, 0x1D, // software reset + 0x7F, 0x00, + 0x07, 0x00, // low active + 0x08, 0x03, + 0x0B, 0x0E, + 0x0D, 0xC5, + 0x0F, 0x32, //R_ABC_Avg_UB=50 + 0x11, 0x41, //R_global=1 + 0x13, 0xD2, + 0x14, 0xFE, + 0x15, 0x00, // R_fg_fast=0 + 0x17, 0x02, //03 + 0x1A, 0x07, + 0x1B, 0x08, + 0x20, 0x00, //R_fast_powerdn_WakeupTime=0 + 0x25, 0x78, + 0x29, 0x28, + 0x2B, 0x06, + 0x2F, 0x0E, + 0x30, 0x0E, + 0x34, 0x0F, + 0x35, 0x0F, + 0x3A, 0x28, + 0x45, 0x17, + 0x46, 0x17, + 0x48, 0x10, //EXP + 0x49, 0x27, //EXP + 0x4A, 0x00, //EXP + 0x4D, 0x0D, + 0x4E, 0x20, + 0x62, 0x12, + 0x64, 0x02, + 0x67, 0x0A, + 0x69, 0x0A, + 0x6C, 0x0B, + 0x6E, 0x0B, + 0x71, 0x0A, + 0x73, 0x1D, + 0x75, 0x1E, + 0x77, 0x0B, + 0x7F, 0x01, + 0x01, 0x14, + 0x02, 0x02, // + 0x04, 0x96, + 0x05, 0x03, + 0x06, 0x46, + 0x0D, 0x9F, //T_BLACINV_EnH=1 + 0x0E, 0x11, //T_SIG_REF=1 + 0x0F, 0x48, + 0x10, 0x10, //T_vcom_lvl=0 + 0x11, 0x00, + 0x12, 0x05, + 0x15, 0x00, + 0x16, 0x01, //schmitt trigger + 0x17, 0x67, + 0x18, 0xD0, //T_vrt_lvl=T_vrb_lvl=2 + 0x21, 0x14, + 0x22, 0x80, // + 0x2F, 0x30, + 0x35, 0x64, + 0x39, 0x03, + 0x3A, 0x03, + 0x46, 0x06, + 0x4A, 0x00, //FX2 + 0x4B, 0x00, //FX2 + 0x00, 0x01, //updated flag +}; diff --git a/components/3rd_party/omv/omv/templates/main_py.h b/components/3rd_party/omv/omv/templates/main_py.h new file mode 100644 index 00000000..3201886f --- /dev/null +++ b/components/3rd_party/omv/omv/templates/main_py.h @@ -0,0 +1,15 @@ +static const char fresh_main_py[] = +"# main.py -- put your code here!\n" +"import pyb, time\n" +"led = pyb.LED(3)\n" +"usb = pyb.USB_VCP()\n" +"while (usb.isconnected()==False):\n" +" led.on()\n" +" time.sleep_ms(150)\n" +" led.off()\n" +" time.sleep_ms(100)\n" +" led.on()\n" +" time.sleep_ms(150)\n" +" led.off()\n" +" time.sleep_ms(600)\n" +; diff --git a/components/3rd_party/omv/omv/templates/readme_txt.h b/components/3rd_party/omv/omv/templates/readme_txt.h new file mode 100644 index 00000000..d3fe78c6 --- /dev/null +++ b/components/3rd_party/omv/omv/templates/readme_txt.h @@ -0,0 +1,15 @@ +static const char fresh_readme_txt[] = +"Thank you for supporting the OpenMV project!\r\n" +"\r\n" +"To download the IDE, please visit:\r\n" +"https://openmv.io/pages/download\r\n" +"\r\n" +"For tutorials and documentation, please visit:\r\n" +"http://docs.openmv.io/\r\n" +"\r\n" +"For technical support and projects, please visit the forums:\r\n" +"http://forums.openmv.io/\r\n" +"\r\n" +"Please use github to report bugs and issues:\r\n" +"https://github.com/openmv/openmv\r\n" +; diff --git a/components/vision/src/maix_image_ops.cpp b/components/vision/src/maix_image_ops.cpp index 9aaf5b5d..fa9644c9 100644 --- a/components/vision/src/maix_image_ops.cpp +++ b/components/vision/src/maix_image_ops.cpp @@ -806,7 +806,7 @@ namespace maix::image { } } - if (unsharp == false) { + if (unsharp) { kernel[((n / 2) * n) + (n / 2)] -= m * 2; m = -m; } @@ -853,7 +853,7 @@ namespace maix::image { kernel[((n / 2) * n) + (n / 2)] += m; m = kernel[((n / 2) * n) + (n / 2)]; - if (sharpen == false) { + if (sharpen) { kernel[((n / 2) * n) + (n / 2)] += m; } diff --git a/examples/image_method/main/src/main.cpp b/examples/image_method/main/src/main.cpp index 221c4155..dea6a2c6 100644 --- a/examples/image_method/main/src/main.cpp +++ b/examples/image_method/main/src/main.cpp @@ -1,5433 +1,144 @@ -#include "stdio.h" -#include "main.h" -#include "maix_util.hpp" + #include "maix_basic.hpp" -#include "maix_image.hpp" -#include "maix_time.hpp" -#include "maix_display.hpp" #include "maix_camera.hpp" -#include "csignal" -#include -#include +#include "maix_display.hpp" +#include "main.h" + +#include +#include #include +#include #include -#include using namespace maix; -#define CAMERA_ENABLE 1 -#define DISPLAY_ENABLE 1 -#define INPUT_IMG_W 640 -#define INPUT_IMG_H 480 -#define PRINTF_IMG_EN 0 - -#define TEST_MEANPOOL 0 // OK -#define TEST_MIDPOINTPOOL 0 // OK -#define TEST_COMPRESS 0 // OK -#define TEST_CLEAR 0 // OK -#define TEST_MASK_RECTANGE 0 // OK -#define TEST_MASK_CIRCLE 0 // OK -#define TEST_MASK_ELLIPSE 0 // OK -#define TEST_BINARY 0 // OK -#define TEST_INVERT 0 // OK -#define TEST_B_AND 0 // OK -#define TEST_B_NAND 0 // OK -#define TEST_B_OR 0 // OK -#define TEST_B_NOR 0 // OK -#define TEST_B_XOR 0 // OK -#define TEST_B_XNOR 0 // OK -#define TEST_AWB 0 // NOT OK -#define TEST_CCM 0 // OK -#define TEST_GAMMA 0 // OK -#define TEST_NEGATE 0 // OK -#define TEST_REPLACE 0 // OK -#define TEST_ADD 0 // OK -#define TEST_SUB 0 // OK -#define TEST_MUL 0 // OK -#define TEST_DIV 0 // OK -#define TEST_MIN 0 // OK -#define TEST_MAX 0 // OK -#define TEST_DIFFERENCE 0 // OK -#define TEST_BLEND 0 // OK -#define TEST_HISTEQ 0 // OK -#define TEST_MEAN 0 // OK -#define TEST_MEDIAN 0 // OK -#define TEST_MODE 0 // OK -#define TEST_MIDPOINT 0 // OK FIXME: valgrind check error:Invalid read of size 1 -#define TEST_MORPH 0 // OK -#define TEST_GAUSSIAN 0 // OK -#define TEST_LAPLACIAN 0 // OK -#define TEST_BILATERAL 0 // OK -#define TEST_LINPOLAR 0 // OK -#define TEST_LOGPOLAR 0 // OK -#define TEST_LENS_COOR 0 // NOT OK -#define TEST_ROTATION_COOR 0 // NOT OK -#define TEST_GET_HISTOGRAM 0 // OK -#define TEST_GET_STATISTICS 0 // OK -#define TEST_GET_REGRESSION 0 // OK -#define TEST_FLOOD_FILL 0 // OK -#define TEST_ERODE 0 // OK -#define TEST_DILATE 0 // OK -#define TEST_OPEN 0 // OK -#define TEST_CLOSE 0 // OK -#define TEST_TOP_HAT 0 // OK -#define TEST_BLACK_HAT 0 // OK -#define TEST_FIND_BLOBS 0 // OK -#define TEST_FIND_LINES 0 // OK -#define TEST_FIND_LINE_SEGMENTS 0 // OK -#define TEST_FIND_CIRCLES 0 // OK -#define TEST_FIND_RECTS 0 // OK -#define TEST_FIND_QRCODES 0 // OK -#define TEST_FIND_APRILTAGS 1 // OK -#define TEST_FIND_DATAMATRICES 0 // OK -#define TEST_FIND_BARCODES 0 // OK -#define TEST_FIND_DISPLACEMENT 0 // NOT OK -#define TEST_FIND_TEMPLATE 0 // OK -#define TEST_FIND_FEATURES 0 // NOT OK -#define TEST_FIND_LBP 0 // NOT OK -#define TEST_FIND_KEYPOINTS 0 // NOT OK -#define TEST_FIND_EDGES 0 // OK -#define TEST_FIND_HOG 0 // NOT OK -#define TEST_STERO_DISPARITY 0 // NOT OK - -void print_image(image::Image &img); -const char *test_640x480_png = "test.jpg"; +typedef struct { + char name[50]; + int (*func)(image::Image *); +} image_method_t; + +typedef struct { + int cam_w; + int cam_h; + image::Format cam_fmt; + int cam_fps; + int cam_buffnum; + int image_method_idx; + std::vector method_list; +} priv_t; + +static priv_t priv; +static int cmd_init(int argc, char* argv[]); +int test_gaussion(image::Image *img); int _main(int argc, char* argv[]) { - uint64_t __attribute__((unused)) start_time; - int a = 0; - int b = 20; - printf("%d", b / a); - // 1. Create camera and display object -#if CAMERA_ENABLE - camera::Camera cam = camera::Camera(); -#else - image::Image * __attribute__((unused)) test_cam_img = image::load(test_640x480_png, image::Format::FMT_RGB888); -#endif + err::check_bool_raise(!cmd_init(argc, argv), "cmd get init param failed!"); -#if DISPLAY_ENABLE + camera::Camera cam = camera::Camera(priv.cam_w ,priv.cam_h, priv.cam_fmt, nullptr, priv.cam_fps, priv.cam_buffnum); display::Display disp = display::Display(); -#endif - - /* Test image::Image::mean_pool */ -#if TEST_MEANPOOL - { -#if 1 - { - image::Image *img_copy; - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->mean_pool(2, 2); - log::info("mean pool gray image cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_mean_pool.png"); - - start_time = time::ticks_us(); - img_copy = img->mean_pool(2, 2, true); - log::info("mean pool gray image copy cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_mean_pool_copy.png"); - - delete img_copy; - delete img; - } -#endif - -#if 1 - { - image::Image *img, *img_copy; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - - img = src_img->copy(); - // img = src_img->resize(6, 4); - // print_image(*img); - start_time = time::ticks_us(); - img->mean_pool(2, 2); - log::info("mean pool rgb888 image cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_mean_pool.png"); - // print_image(*img); - delete img; - - img = src_img->copy(); - // img = src_img->resize(6, 4); - // print_image(*img); - start_time = time::ticks_us(); - img_copy = img->mean_pool(2, 2, true); - log::info("mean pool rgb888 image copy cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img_copy->save("out_rgb888_640x480_mean_pool_copy.png"); - // print_image(*img_copy); - delete img; - delete img_copy; - delete src_img; - } -#endif - } -#endif - - /* Test image::Image::midpoint_pool */ -#if TEST_MIDPOINTPOOL - { -#if 1 - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->midpoint_pool(2, 2, 0.5); - log::info("midpoint pool gray image cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_midpoint_pool.png"); - delete img; - } -#endif - -#if 1 - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->midpoint_pool(2, 2, 0.5); - log::info("midpoint pool rgb888 image cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_midpoint_pool.png"); - delete img; - } -#endif - } -#endif - - /* Test compress */ -#if TEST_COMPRESS - { - fs::File f = fs::File(); -#if 1 - { - image::Image *jpg; - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - jpg = img->compress(); - log::info("compress gray image cost %d us\r\n", (int)(time::ticks_us() - start_time)); - - f.open("./out_gray_640x480_compress.jpg", "w+"); - f.write(jpg->data(), jpg->data_size()); - f.close(); - - delete img; - delete jpg; - } -#endif - -#if 1 - { - image::Image *jpg; - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - jpg = img->compress(); - log::info("compress rgb888 image cost %d us\r\n", (int)(time::ticks_us() - start_time)); - - f.open("./out_rgb888_640x480_compress.jpg", "w+"); - f.write(jpg->data(), jpg->data_size()); - f.close(); - - delete img; - delete jpg; - } -#endif - } -#endif + log::info("camera and display open success\n"); + log::info("camera size: %dx%d\n", cam.width(), cam.height()); + log::info("disp size: %dx%d\n", disp.width(), disp.height()); -#if TEST_CLEAR + uint64_t last_ms = time::time_ms(); + uint64_t last_loop_used_ms = 0; + while(!app::need_exit()) { -#if 1 - { - image::Image *img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->copy(); - start_time = time::ticks_us(); - img->clear(); - log::info("clear gray image cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_clear.png"); - delete img; - - img = src_img->copy(); - start_time = time::ticks_us(); - img->clear(&mask_img); - log::info("clear gray image with mask cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_clear_mask.png"); - delete img; - - delete src_img; - } -#endif - -#if 1 - { - image::Image *img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->copy(); - start_time = time::ticks_us(); - img->clear(); - log::info("clear rgb888 image cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_clear.png"); - delete img; + image::Image *img = cam.read(); + err::check_null_raise(img, "camera read failed"); - img = src_img->copy(); - start_time = time::ticks_us(); - img->clear(&mask_img); - log::info("clear rgb888 image with mask cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_clear_mask.png"); - delete img; - - delete src_img; + if (priv.method_list[priv.image_method_idx].func) { + priv.method_list[priv.image_method_idx].func(img); } -#endif - } -#endif - -#if TEST_MASK_RECTANGE - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->mask_rectange(); - log::info("img.mask_rectange() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_mask_rectange.png"); - delete img; - } - - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->mask_rectange(); - log::info("img.mask_rectange() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_mask_rectange.png"); - delete img; - } -#endif - -#if TEST_MASK_CIRCLE - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->mask_circle(); - log::info("img.mask_circle() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_mask_circle.png"); - delete img; - } - - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->mask_circle(); - log::info("img.mask_circle() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_mask_circle.png"); - delete img; - } -#endif - -#if TEST_MASK_ELLIPSE - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->mask_ellipse(); - log::info("img.mask_ellipse() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_mask_ellipse.png"); - delete img; - } - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->mask_ellipse(); - log::info("img.mask_ellipse() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_mask_ellipse.png"); + disp.show(*img); delete img; - } -#endif - -#if TEST_BINARY - { -#if 0 - { - image::Image *img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - std::vector> thresholds = {{13, 24}}; - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - start_time = time::ticks_us(); - img->binary(thresholds); - log::info("gray img->binary(thresholds) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_binary.png"); - delete img; - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - start_time = time::ticks_us(); - img->binary(thresholds, false, false, &mask_img); - log::info("gray img->binary(thresholds, false, false, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_binary_mask.png"); - delete img; - - delete src_img; - } -#endif - -#if 1 - { - image::Image *img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - std::vector> thresholds = {{0, 100, 20, 80, 20, 80}}; - - // img = src_img->copy(); - // cv::Mat bgrImage(img->height(), img->width(), CV_8UC((int)image::fmt_size[img->format()]), img->data()); - // cv::Mat labImage; - // start_time = time::ticks_us(); - // cv::cvtColor(bgrImage, labImage, cv::COLOR_BGR2Lab); - // log::info("rgb888 cv::cvtColor(bgrImage, labImage, cv::COLOR_BGR2Lab) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - // // cv::threshold(mat, binaryImage, 128, 255, cv2.THRESH_BINARY); - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - start_time = time::ticks_us(); - img->binary(thresholds); - log::info("rgb888 img->binary(thresholds) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_binary.png"); - delete img; - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - start_time = time::ticks_us(); - img->binary(thresholds, false, false, &mask_img); - log::info("rgb888 img->binary(thresholds, false, false, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_binary_mask.png"); - delete img; - - delete src_img; - } -#endif - } -#endif - -#if TEST_INVERT - { -#if 1 - { - image::Image *img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - - // img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - // print_image(*img); - img = src_img->copy(); - start_time = time::ticks_us(); - img->invert(); - log::info("gray img->invert() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_invert.png"); - // print_image(*img); - - delete img; - delete src_img; - } -#endif - -#if 1 - { - image::Image *img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - - // img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - // print_image(*img); - img = src_img->copy(); - start_time = time::ticks_us(); - img->invert(); - log::info("rgb888 img->invert() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_invert.png"); - // print_image(*img); - - delete img; - delete src_img; - } -#endif - } -#endif - -#if TEST_B_AND - { -#if 1 - { - image::Image *img, *other_img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_and(other_img); - log::info("gray img->b_and(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_b_and.png"); - print_image(*img); - delete img; - delete other_img; - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_and(other_img, &mask_img); - log::info("gray img->b_and(other_img, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_b_and.png"); - print_image(*img); - delete img; - delete other_img; - - delete src_img; - } -#endif - -#if 1 - { - image::Image *img, *other_img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_and(other_img); - log::info("rgb888 img->b_and(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_b_and.png"); - print_image(*img); - delete img; - delete other_img; - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_and(other_img, &mask_img); - log::info("rgb888 img->b_and(other_img, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_b_and_mask.png"); - print_image(*img); - delete img; - delete other_img; - - delete src_img; - } -#endif - } -#endif - -#if TEST_B_NAND - { -#if 1 - { - image::Image *img, *other_img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_nand(other_img); - log::info("gray img->b_nand(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_b_nand.png"); - print_image(*img); - delete img; - delete other_img; - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_nand(other_img, &mask_img); - log::info("gray img->b_nand(other_img, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_b_nand.png"); - print_image(*img); - delete img; - delete other_img; - - delete src_img; - } -#endif - -#if 1 - { - image::Image *img, *other_img;; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_nand(other_img); - log::info("rgb888 img->b_nand(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_b_nand.png"); - print_image(*img); - delete img; - delete other_img; - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_nand(other_img, &mask_img); - log::info("rgb888 img->b_nand(other_img, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_b_nand_mask.png"); - print_image(*img); - delete img; - delete other_img; - - delete src_img; - } -#endif - } -#endif - -#if TEST_B_OR - { -#if 1 - { - image::Image *img, *other_img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_or(other_img); - log::info("gray img->b_or(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_b_or.png"); - print_image(*img); - delete img; - delete other_img; - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_or(other_img, &mask_img); - log::info("gray img->b_or(other_img, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_b_or.png"); - print_image(*img); - delete img; - delete other_img; - - delete src_img; - } -#endif - -#if 1 - { - image::Image *img, *other_img;; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_or(other_img); - log::info("rgb888 img->b_or(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_b_or.png"); - print_image(*img); - delete img; - delete other_img; - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_or(other_img, &mask_img); - log::info("rgb888 img->b_or(other_img, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_b_or_mask.png"); - print_image(*img); - delete img; - delete other_img; - - delete src_img; - } -#endif - } -#endif - -#if TEST_B_NOR - { -#if 1 - { - image::Image *img, *other_img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_nor(other_img); - log::info("gray img->b_nor(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_b_nor.png"); - print_image(*img); - delete img; - delete other_img; - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_nor(other_img, &mask_img); - log::info("gray img->b_nor(other_img, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_b_nor.png"); - print_image(*img); - delete img; - delete other_img; - - delete src_img; - } -#endif - -#if 1 - { - image::Image *img, *other_img;; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_nor(other_img); - log::info("rgb888 img->b_nor(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_b_nor.png"); - print_image(*img); - delete img; - delete other_img; - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_nor(other_img, &mask_img); - log::info("rgb888 img->b_nor(other_img, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_b_nor_mask.png"); - print_image(*img); - delete img; - delete other_img; - - delete src_img; - } -#endif + log::info("loop used: %ld (ms), fps: %.2f", last_loop_used_ms, 1000.0f / last_loop_used_ms); + last_loop_used_ms = time::ticks_ms() - last_ms; + last_ms = time::ticks_ms(); } -#endif - -#if TEST_B_XOR - { -#if 1 - { - image::Image *img, *other_img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_xor(other_img); - log::info("gray img->b_xor(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_b_xor.png"); - print_image(*img); - delete img; - delete other_img; - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_xor(other_img, &mask_img); - log::info("gray img->b_xor(other_img, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_b_xor.png"); - print_image(*img); - delete img; - delete other_img; - - delete src_img; - } -#endif - -#if 1 - { - image::Image *img, *other_img;; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_xor(other_img); - log::info("rgb888 img->b_xor(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_b_xor.png"); - print_image(*img); - delete img; - delete other_img; + return 0; +} - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); +int main(int argc, char* argv[]) +{ + // Catch signal and process + sys::register_default_signal_handle(); - start_time = time::ticks_us(); - img->b_xor(other_img, &mask_img); - log::info("rgb888 img->b_xor(other_img, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_b_xor_mask.png"); - print_image(*img); - delete img; - delete other_img; + // Use CATCH_EXCEPTION_RUN_RETURN to catch exception, + // if we don't catch exception, when program throw exception, the objects will not be destructed. + // So we catch exception here to let resources be released(call objects' destructor) before exit. + CATCH_EXCEPTION_RUN_RETURN(_main, -1, argc, argv); +} - delete src_img; - } -#endif +static void cmd_helper(void) +{ + for (size_t i = 0; i < priv.method_list.size(); i ++) { + log::info("[%d] %s\r\n", i, priv.method_list[i].name); } -#endif - -#if TEST_B_XNOR - { -#if 1 - { - image::Image *img, *other_img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_xnor(other_img); - log::info("gray img->b_xnor(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_b_xnor.png"); - print_image(*img); - delete img; - delete other_img; - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_xnor(other_img, &mask_img); - log::info("gray img->b_xnor(other_img, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_b_xnor.png"); - print_image(*img); - delete img; - delete other_img; - - delete src_img; - } -#endif - -#if 1 - { - image::Image *img, *other_img;; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); - - start_time = time::ticks_us(); - img->b_xnor(other_img); - log::info("rgb888 img->b_xnor(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_b_xnor.png"); - print_image(*img); - delete img; - delete other_img; - - img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - other_img = src_img->resize(INPUT_IMG_W, INPUT_IMG_H); - print_image(*img); + log::info("Input "); + log::info(" = 0, measn image::FMT_RGB888"); + log::info(" = 12, measn image::FMT_GRAYSCALE"); + log::info("Example: ./image_method 0 320 240 0 60 2 // test gaussion"); +} - start_time = time::ticks_us(); - img->b_xnor(other_img, &mask_img); - log::info("rgb888 img->b_xnor(other_img, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_b_xnor_mask.png"); - print_image(*img); - delete img; - delete other_img; +static int cmd_init(int argc, char* argv[]) +{ + priv.cam_w = 320; + priv.cam_h = 240; + priv.cam_fmt = image::FMT_RGB888; + priv.cam_fps = 60; + priv.cam_buffnum = 2; - delete src_img; - } -#endif - } -#endif + // Config image method + priv.method_list.push_back(image_method_t{"no method", NULL}); + priv.method_list.push_back(image_method_t{"gaussian", test_gaussion}); -#if TEST_AWB - { - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->awb(); - log::info("img.awb() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_awb.png"); - delete img; + // Get init param + if (argc > 1) { + if (!strcmp(argv[1], "-h")) { + cmd_helper(); + exit(0); + } else { + priv.image_method_idx = atoi(argv[1]); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->awb(true); - log::info("img.mode(true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_awb_max.png"); - delete img; } - } -#endif - -#if TEST_CCM - { - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - std::vector color_matrix_3x3= { - 1, 0, 0.5, - 0, 1, 0.5, - 0, 0, 0 - }; - img->ccm(color_matrix_3x3); - log::info("img.ccm(color_matrix_3x3) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_ccm_3x3.png"); - delete img; - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - std::vector color_matrix_4x3= { - 1, 0, 0.5, - 0, 1, 0.5, - 0, 0, 0, - -50, 20, 20 - }; - img->ccm(color_matrix_4x3); - log::info("img.ccm(color_matrix_4x3) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_ccm_4x3.png"); - delete img; - } } -#endif - -#if TEST_GAMMA - { - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->gamma(0.2); - log::info("img.gamma(0.2) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_gamma_0.2.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->gamma(0.2, 2); - log::info("img.gamma(0.2, 2) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_gamma0.2_contrast2.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->gamma(0.2, 2, 0.8); - log::info("img.gamma(0.2, 2, 0.8) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_gamma0.2_contrast2_brightness0.8.png"); - delete img; - } - - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->gamma(0.2); - log::info("img.gamma(0.2) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_gamma_0.2.png"); - delete img; - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->gamma(0.2, 2); - log::info("img.gamma(0.2, 2) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_gamma0.2_contrast2.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->gamma(0.2, 2, 0.8); - log::info("img.gamma(0.2, 2, 0.8) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_gamma0.2_contrast2_brightness0.8.png"); - delete img; - } + if (argc > 2) { + priv.cam_w = atoi(argv[2]); } -#endif - -#if TEST_NEGATE - { - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->negate(); - log::info("img.negate cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_negate.png"); - delete img; - } - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->negate(); - log::info("img.negate() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_negate.png"); - delete img; - } + if (argc > 3) { + priv.cam_h = atoi(argv[3]); } -#endif - -#if TEST_REPLACE - { - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image src_img(img->width(), img->height(), image::Format::FMT_GRAYSCALE); - image::Image mask_img(img->width(), img->height(), image::Format::FMT_GRAYSCALE); - src_img.draw_rect(0, 0, src_img.width(), src_img.height(), 255, -1); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - delete img; - // hmirror without other image - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->replace(NULL, true); - log::info("img.replace(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_replace_hmirror_without_other.png"); - delete img; - - // hmirror - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - src_img.replace(img, true); - log::info("img.replace(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - src_img.save("out_gray_640x480_replace_hmirror.png"); - delete img; - - // vflip - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - src_img.replace(img, false, true); - log::info("img.replace(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - src_img.save("out_gray_640x480_replace_vflip.png"); - delete img; - - // flip=false, hmirror=false, transpose=false - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - src_img.replace(img); - log::info("img.replace(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - src_img.save("out_gray_640x480_replace_rot0.png"); - delete img; - - // flip=true, hmirror=true, transpose=false - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - src_img.replace(img, true, true); - log::info("img.replace(img, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - src_img.save("out_gray_640x480_replace_rot180.png"); - delete img; - - // flip=true, hmirror=false, transpose=true - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - src_img.replace(img, true, false, true, &mask_img); - log::info("img.replace(img, true, false, true, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - src_img.save("out_gray_640x480_replace_rot90.png"); - delete img; - - // flip=false, hmirror=true, transpose=true - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image src_img2(img->width(), img->height(), image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - src_img2.replace(img, false, true, true, &mask_img); - log::info("img.replace(img, false, true, true, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - src_img2.save("out_gray_640x480_replace_rot270.png"); - delete img; - } - - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image src_img(img->width(), img->height(), image::Format::FMT_RGB888); - image::Image mask_img(img->width(), img->height(), image::Format::FMT_RGB888); - src_img.draw_rect(0, 0, src_img.width(), src_img.height(), 255, -1); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - delete img; - - // hmirror without other image - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->replace(NULL, true); - log::info("img.replace(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_replace_hmirror_without_other.png"); - delete img; - - // hmirror - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - src_img.replace(img, true); - log::info("img.replace(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - src_img.save("out_rgb888_640x480_replace_hmirror.png"); - delete img; - - // vflip - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - src_img.replace(img, false, true); - log::info("img.replace(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - src_img.save("out_rgb888_640x480_replace_vflip.png"); - delete img; - - // flip=false, hmirror=false, transpose=false - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - src_img.replace(img); - log::info("img.replace(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - src_img.save("out_rgb888_640x480_replace_rot0.png"); - delete img; - - // flip=true, hmirror=true, transpose=false - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - src_img.replace(img, true, true); - log::info("img.replace(img, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - src_img.save("out_rgb888_640x480_replace_rot180.png"); - delete img; - - // flip=true, hmirror=false, transpose=true - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - src_img.replace(img, true, false, true, &mask_img); - log::info("img.replace(img, true, false, true, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - src_img.save("out_rgb888_640x480_replace_rot90.png"); - delete img; - - // flip=false, hmirror=true, transpose=true - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image src_img2(img->width(), img->height(), image::Format::FMT_RGB888); - start_time = time::ticks_us(); - src_img2.replace(img, false, true, true, &mask_img); - log::info("img.replace(img, false, true, true, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - src_img2.save("out_rgb888_640x480_replace_rot270.png"); - delete img; - } + if (argc > 4) { + priv.cam_fmt = image::Format(atoi(argv[4])); } -#endif -#if TEST_ADD - { -#if 1 - { - image::Image *img, *other_img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->add(other_img); - log::info("gray img->add(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_add.png"); - delete img; - delete other_img; - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->add(other_img, &mask_img); - log::info("gray img->add(other_img, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_add_mask.png"); - delete img; - delete other_img; - - delete src_img; - } -#endif - -#if 1 - { - image::Image *img, *other_img;; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->add(other_img); - log::info("rgb888 img->add(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_add.png"); - delete img; - delete other_img; - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->add(other_img, &mask_img); - log::info("rgb888 img->add(other_img, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_add_mask.png"); - delete img; - delete other_img; - - delete src_img; - } -#endif + if (argc > 5) { + priv.cam_fps = atoi(argv[5]); } -#endif - -#if TEST_SUB - { -#if 1 - { - image::Image *img, *other_img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->sub(other_img); - log::info("gray img->sub(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_sub.png"); - delete img; - delete other_img; - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->sub(other_img, true, &mask_img); - log::info("gray img->sub(other_img, true, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_sub_true_mask.png"); - delete img; - delete other_img; - - delete src_img; - } -#endif -#if 1 - { - image::Image *img, *other_img;; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->sub(other_img); - log::info("rgb888 img->sub(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_sub.png"); - delete img; - delete other_img; - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->sub(other_img, true, &mask_img); - log::info("rgb888 img->sub(other_img, true, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_sub_true_mask.png"); - delete img; - delete other_img; - - delete src_img; - } -#endif + if (argc > 6) { + priv.cam_buffnum = atoi(argv[6]); } -#endif - -#if TEST_MUL - { -#if 1 - { - image::Image *img, *other_img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->mul(other_img); - log::info("gray img->mul(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_mul.png"); - delete img; - delete other_img; - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->mul(other_img, true, &mask_img); - log::info("gray img->mul(other_img, true, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_mul_true_mask.png"); - delete img; - delete other_img; - delete src_img; - } -#endif - -#if 1 - { - image::Image *img, *other_img;; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->mul(other_img); - log::info("rgb888 img->mul(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_mul.png"); - delete img; - delete other_img; - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->mul(other_img, true, &mask_img); - log::info("rgb888 img->mul(other_img, true, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_mul_true_mask.png"); - delete img; - delete other_img; - - delete src_img; - } -#endif + if (priv.image_method_idx >= (int)priv.method_list.size()) { + log::info("Method: %d not found", priv.image_method_idx); + log::info("Try:"); + cmd_helper(); + exit(0); + } else { + log::info("Use method: %d %s", priv.image_method_idx, priv.method_list[priv.image_method_idx].name); } -#endif - -#if TEST_DIV - { -#if 1 - { - image::Image *img, *other_img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->div(other_img); - log::info("gray img->div(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_div.png"); - delete img; - delete other_img; - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->div(other_img, false, true, &mask_img); - log::info("gray img->div(other_img, false, true, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_div_false_true_mask.png"); - delete img; - delete other_img; - - delete src_img; - } -#endif - -#if 1 - { - image::Image *img, *other_img;; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->div(other_img); - log::info("rgb888 img->div(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_div.png"); - delete img; - delete other_img; - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->div(other_img, false, true, &mask_img); - log::info("rgb888 img->div(other_img, false, true, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_div_false_true_mask.png"); - delete img; - delete other_img; - - delete src_img; - } -#endif - } -#endif - -#if TEST_MIN - { -#if 1 - { - image::Image *img, *other_img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->min(other_img); - log::info("gray img->min(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_min.png"); - delete img; - delete other_img; - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->min(other_img, &mask_img); - log::info("gray img->min(other_img, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_min_mask.png"); - delete img; - delete other_img; - - delete src_img; - } -#endif - -#if 1 - { - image::Image *img, *other_img;; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->min(other_img); - log::info("rgb888 img->min(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_min.png"); - delete img; - delete other_img; - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->min(other_img, &mask_img); - log::info("rgb888 img->min(other_img, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_min_mask.png"); - delete img; - delete other_img; - - delete src_img; - } -#endif - } -#endif - -#if TEST_MAX - { -#if 1 - { - image::Image *img, *other_img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->max(other_img); - log::info("gray img->max(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_max.png"); - delete img; - delete other_img; - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->max(other_img, &mask_img); - log::info("gray img->max(other_img, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_max_mask.png"); - delete img; - delete other_img; - - delete src_img; - } -#endif - -#if 1 - { - image::Image *img, *other_img;; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->max(other_img); - log::info("rgb888 img->max(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_max.png"); - delete img; - delete other_img; - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->max(other_img, &mask_img); - log::info("rgb888 img->max(other_img, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_max_mask.png"); - delete img; - delete other_img; - - delete src_img; - } -#endif - } -#endif - -#if TEST_DIFFERENCE - { -#if 1 - { - image::Image *img, *other_img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->difference(other_img); - log::info("gray img->difference(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_difference.png"); - delete img; - delete other_img; - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->difference(other_img, &mask_img); - log::info("gray img->difference(other_img, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_difference_mask.png"); - delete img; - delete other_img; - - delete src_img; - } -#endif - -#if 1 - { - image::Image *img, *other_img;; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->difference(other_img); - log::info("rgb888 img->difference(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_difference.png"); - delete img; - delete other_img; - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->difference(other_img, &mask_img); - log::info("rgb888 img->difference(other_img, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_difference_mask.png"); - delete img; - delete other_img; - - delete src_img; - } -#endif - } -#endif - -#if TEST_BLEND - { -#if 1 - { - image::Image *img, *other_img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->blend(other_img); - log::info("gray img->blend(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_blend.png"); - delete img; - delete other_img; - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->blend(other_img, 64, &mask_img); - log::info("gray img->blend(other_img, 64, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_blend_64_mask.png"); - delete img; - delete other_img; - - delete src_img; - } -#endif - -#if 1 - { - image::Image *img, *other_img; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->blend(other_img); - log::info("rgb888 img->blend(other_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_blend.png"); - delete img; - delete other_img; - - img = src_img->copy(); - other_img = src_img->copy(); - start_time = time::ticks_us(); - img->blend(other_img, 64, &mask_img); - log::info("rgb888 img->blend(other_img, 64, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_blend_64_mask.png"); - delete img; - delete other_img; - - delete src_img; - } -#endif - } -#endif - -#if TEST_HISTEQ - { - { - image::Image *gray_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - gray_img->histeq(); - log::info("gray_img.histeq() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - gray_img->save("out_gray_640x480_histeq.png"); - delete gray_img; - - gray_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - gray_img->histeq(true); - log::info("gray_img.histeq(true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - gray_img->save("out_gray_640x480_histeq_adaptive.png"); - delete gray_img; - - gray_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - gray_img->histeq(false, 10); - log::info("gray_img.histeq(true, 10) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - gray_img->save("out_gray_640x480_histeq_clip_limit_10.png"); - delete gray_img; - - gray_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img = image::Image(gray_img->width(), gray_img->height(), image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(0, 0, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - start_time = time::ticks_us(); - gray_img->histeq(false, 10, &mask_img); - log::info("gray_img.histeq(true, 10, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - gray_img->save("out_gray_640x480_histeq_mask.png"); - delete gray_img; - } - - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->histeq(); - log::info("img.histeq() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_histeq.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->histeq(true); - log::info("img.histeq(true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_histeq_adaptive.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->histeq(false, 10); - log::info("img.histeq(true, 10) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_histeq_clip_limit_10.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img = image::Image(img->width(), img->height(), image::Format::FMT_RGB888); - mask_img.draw_rect(0, 0, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - start_time = time::ticks_us(); - img->histeq(false, 10, &mask_img); - log::info("img.histeq(true, 10, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_histeq_mask.png"); - delete img; - } - } -#endif - -#if TEST_MEAN - { - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->mean(2); - log::info("img.mean() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_64x64_mean.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->mean(2, true); - log::info("img.mean(true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_64x64_mean_thr_true.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->mean(2, true, 10); - log::info("img.mean(true, 10) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_64x64_mean_oft_10.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->mean(2, true, -10); - log::info("img.mean(true, -10) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_64x64_mean_oft_neg_10.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img = image::Image(img->width(), img->height(), image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(0, 0, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - start_time = time::ticks_us(); - img->mean(2, true, 0, true, &mask_img); - log::info("img.mean(true, 10, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_64x64_mean_invert_and_mask.png"); - delete img; - } - - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->mean(2); - log::info("img.mean() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_64x64_mean.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->mean(2, true); - log::info("img.mean(true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_64x64_mean_thr_true.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->mean(2, true, 10); - log::info("img.mean(true, 10) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_64x64_mean_oft_10.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->mean(2, true, -10); - log::info("img.mean(true, -10) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_64x64_mean_oft_neg_10.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img = image::Image(img->width(), img->height(), image::Format::FMT_RGB888); - mask_img.draw_rect(0, 0, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - start_time = time::ticks_us(); - img->mean(2, true, 0, true, &mask_img); - log::info("img.mean(true, 10, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_64x64_mean_invert_and_mask.png"); - delete img; - } - } -#endif - -#if TEST_MEDIAN - { - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->median(2); - log::info("img.median(2) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_64x64_median.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->median(2, 0.1); - log::info("img.median(2, 0.1) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_64x64_median_1.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->median(2, 0.9); - log::info("img.median(2, 0.9) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_64x64_median_9.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->median(2, 0.5, true); - log::info("img.median(2, 0.5, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_64x64_median_thr_en.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->median(2, 0.5, true, 10); - log::info("img.median(2, 0.5, true, 10) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_64x64_median_oft_10.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->median(2, 0.5, true, -10); - log::info("img.median(2, 0.5, true, -10) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_64x64_median_oft_neg_10.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img = image::Image(img->width(), img->height(), image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - start_time = time::ticks_us(); - img->median(2, 0.5, true, 0, true, &mask_img); - log::info("img.median(2, 0.5, true, 0, true, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_64x64_median_invert_and_mask.png"); - delete img; - } - - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->median(2); - log::info("img.median(2) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_64x64_median.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->median(2, 0.1); - log::info("img.median(2, 0.1) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_64x64_median_1.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->median(2, 0.9); - log::info("img.median(2, 0.9) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_64x64_median_9.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->median(2, 0.5, true); - log::info("img.median(2, 0.5, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_64x64_median_thr_en.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->median(2, 0.5, true, 10); - log::info("img.median(2, 0.5, true, 10) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_64x64_median_oft_10.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->median(2, 0.5, true, -10); - log::info("img.median(2, 0.5, true, -10) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_64x64_median_oft_neg_10.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img = image::Image(img->width(), img->height(), image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - start_time = time::ticks_us(); - img->median(2, 0.5, true, 0, true, &mask_img); - log::info("img.median(2, 0.5, true, 0, true, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_64x64_median_invert_and_mask.png"); - delete img; - } - } -#endif - -#if TEST_MODE - { - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->mode(2); - log::info("img.mode(2) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_64x64_mode.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->mode(2, true); - log::info("img.mode(2, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_64x64_mode_thr_en.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->mode(2, true, 10); - log::info("img.mode(2, true, 10) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_64x64_mode_oft_10.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->mode(2, true, -10); - log::info("img.mode(2, true, -10) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_64x64_mode_oft_neg_10.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img = image::Image(img->width(), img->height(), image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - start_time = time::ticks_us(); - img->mode(2, true, 0, true, &mask_img); - log::info("img.mode(2, true, 0, true, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_64x64_mode_invert_and_mask.png"); - delete img; - } - - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->mode(2); - log::info("img.mode(2) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_64x64_mode.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->mode(2, true); - log::info("img.mode(2, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_64x64_mode_thr_en.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->mode(2, true, 10); - log::info("img.mode(2, true, 10) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_64x64_mode_oft_10.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->mode(2, true, -10); - log::info("img.mode(2, true, -10) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_64x64_mode_oft_neg_10.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img = image::Image(img->width(), img->height(), image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - start_time = time::ticks_us(); - img->mode(2, true, 0, true, &mask_img); - log::info("img.mode(2, true, 0, true, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_64x64_mode_invert_and_mask.png"); - delete img; - } - } -#endif - -#if TEST_MIDPOINT - { - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(img->width(), img->height(), image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - start_time = time::ticks_us(); - img->midpoint(2); - log::info("img.midpoint(2) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_midpoint.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->midpoint(2, 0.2); - log::info("img.midpoint(2, 0.2) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_midpoint_bias0.2.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->midpoint(2, 0.2, true); - log::info("img.midpoint(2, 0.2, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_midpoint_bias0.2_thr.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->midpoint(2, 0.2, true, -20); - log::info("img.midpoint(2, 0.2, true, -20) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_midpoint_bias0.2_thr_oft20.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->midpoint(2, 0.2, true, -20, true, &mask_img); - log::info("img.midpoint(2, 0.2, true, -20, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_midpoint_bias0.2_thr_oft20_invert.png"); - delete img; - } - - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(img->width(), img->height(), image::Format::FMT_RGB888); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - start_time = time::ticks_us(); - img->midpoint(2); - log::info("img.midpoint(2) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_midpoint.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->midpoint(2, 0.2); - log::info("img.midpoint(2, 0.2) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_midpoint_bias0.2.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->midpoint(2, 0.2, true); - log::info("img.midpoint(2, 0.2, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_midpoint_bias0.2_thr.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->midpoint(2, 0.2, true, -20); - log::info("img.midpoint(2, 0.2, true, -20) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_midpoint_bias0.2_thr_oft20.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->midpoint(2, 0.2, true, -20, true, &mask_img); - log::info("img.midpoint(2, 0.2, true, -20, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_midpoint_bias0.2_thr_oft20_invert.png"); - delete img; - } - } -#endif - -#if TEST_MORPH - { - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(img->width(), img->height(), image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - std::vector kernel = { -1, -2, -1, - -2, 12, -2, - -1, -2, -1}; - - start_time = time::ticks_us(); - img->morph(1, kernel); - log::info("img.morph(1, kernel) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_morph_kernel3x3.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->morph(1, kernel, 200); - log::info("img.morph(1, kernel, 200) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_morph_kernel3x3_mul200.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->morph(1, kernel, 200, 200); - log::info("img.morph(1, kernel, 200, 200) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_morph_kernel3x3_mul200_add20.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->morph(1, kernel, 200, 200, true); - log::info("img.morph(1, kernel, 200, 200, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_morph_kernel3x3_mul200_add200_thr.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->morph(1, kernel, 200, 200, true, 20); - log::info("img.morph(1, kernel, 200, 200, true, 20) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_morph_kernel3x3_mul200_add200_thr_oft20.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->morph(1, kernel, 200, 200, true, 20, true, &mask_img); - log::info("img.morph(1, kernel, 200, 200, true, 20, true, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_morph_kernel3x3_mul200_add200_thr_oft20_invert.png"); - delete img; - } - - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(img->width(), img->height(), image::Format::FMT_RGB888); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - std::vector kernel = { -1, -2, -1, - -2, 12, -2, - -1, -2, -1}; - - start_time = time::ticks_us(); - img->morph(1, kernel); - log::info("img.morph(1, kernel) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_morph_kernel3x3.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->morph(1, kernel, 200); - log::info("img.morph(1, kernel, 200) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_morph_kernel3x3_mul200.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->morph(1, kernel, 200, 200); - log::info("img.morph(1, kernel, 200, 200) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_morph_kernel3x3_mul200_add20.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->morph(1, kernel, 200, 200, true); - log::info("img.morph(1, kernel, 200, 200, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_morph_kernel3x3_mul200_add200_thr.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->morph(1, kernel, 200, 200, true, 20); - log::info("img.morph(1, kernel, 200, 200, true, 20) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_morph_kernel3x3_mul200_add200_thr_oft20.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->morph(1, kernel, 200, 200, true, 20, true, &mask_img); - log::info("img.morph(1, kernel, 200, 200, true, 20, true, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_morph_kernel3x3_mul200_add200_thr_oft20_invert.png"); - delete img; - } - } -#endif - -#if TEST_GAUSSIAN - { - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(img->width(), img->height(), image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - start_time = time::ticks_us(); - img->gaussian(2); - log::info("img.gaussian(2) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_gaussian_size2.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->gaussian(2, true); - log::info("img.gaussian(2, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_gaussian_size2_unsharp.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->gaussian(2, true, 0.005); - log::info("img.gaussian(2, true, 0.005) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_gaussian_size2_unsharp_mul200.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->gaussian(2, true, 0.005, 200); - log::info("img.gaussian(2, 200) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_gaussian_size2_unsharp_mul200_add200.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->gaussian(2, true, 0.005, 200, true); - log::info("img.gaussian(2, 200) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_gaussian_size2_unsharp_mul200_add200_thr.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->gaussian(2, true, 0.005, 200, true, 20); - log::info("img.gaussian(2, 200) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_gaussian_size2_unsharp_mul200_add200_thr_oft20.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->gaussian(2, true, 0.005, 200, true, 20, true, &mask_img); - log::info("img.gaussian(2, 200) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_gaussian_size2_unsharp_mul200_add200_thr_oft20_invert.png"); - delete img; - } - - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(img->width(), img->height(), image::Format::FMT_RGB888); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - start_time = time::ticks_us(); - img->gaussian(1); - log::info("img.gaussian(2) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_gaussian_size2.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->gaussian(2, true); - log::info("img.gaussian(2, 200) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_gaussian_size2_unsharp.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->gaussian(2, true, 0.005); - log::info("img.gaussian(2, 200) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_gaussian_size2_unsharp_mul200.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->gaussian(2, true, 0.005, 200); - log::info("img.gaussian(2, 200) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_gaussian_size2_unsharp_mul200_add200.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->gaussian(2, true, 0.005, 200, true); - log::info("img.gaussian(2, 200) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_gaussian_size2_unsharp_mul200_add200_thr.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->gaussian(2, true, 0.005, 200, true, 20); - log::info("img.gaussian(2, 200) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_gaussian_size2_unsharp_mul200_add200_thr_oft20.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->gaussian(2, true, 0.005, 200, true, 20, true, &mask_img); - log::info("img.gaussian(2, 200) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_gaussian_size2_unsharp_mul200_add200_thr_oft20_invert.png"); - delete img; - } - } -#endif - -#if TEST_LAPLACIAN - { - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(img->width(), img->height(), image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - start_time = time::ticks_us(); - img->laplacian(2); - log::info("img.laplacian(2) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_laplacian_size2.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->laplacian(2, true); - log::info("img.laplacian(2, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_laplacian_size2_sharpen.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->laplacian(2, true, 0.005); - log::info("img.laplacian(2, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_laplacian_size2_sharpen_mul200.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->laplacian(2, true, 0.005, 200); - log::info("img.laplacian(2, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_laplacian_size2_sharpen_mul200_add200.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->laplacian(2, true, 0.005, 200, true); - log::info("img.laplacian(2, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_laplacian_size2_sharpen_mul200_add200_thr.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->laplacian(2, true, 0.005, 200, true, 20); - log::info("img.laplacian(2, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_laplacian_size2_sharpen_mul200_add200_thr_oft20.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->laplacian(2, true, 0.005, 200, true, 20, true, &mask_img); - log::info("img.laplacian(2, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_laplacian_size2_sharpen_mul200_add200_thr_oft20_invert.png"); - delete img; - } - - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(img->width(), img->height(), image::Format::FMT_RGB888); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - start_time = time::ticks_us(); - img->laplacian(1); - log::info("img.laplacian(1) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_laplacian_size2.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->laplacian(1, true); - log::info("img.laplacian(1, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_laplacian_size2_sharpen.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->laplacian(1, true, 200); - log::info("img.laplacian(1, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_laplacian_size2_sharpen_mul200.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->laplacian(1, true, 200, 200); - log::info("img.laplacian(1, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_laplacian_size2_sharpen_mul200_add200.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->laplacian(1, true, 200, 200, true); - log::info("img.laplacian(1, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_laplacian_size2_sharpen_mul200_add200_thr.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->laplacian(1, true, 200, 200, true, 20); - log::info("img.laplacian(1, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_laplacian_size2_sharpen_mul200_add200_thr_oft20.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->laplacian(1, true, 200, 200, true, 20, true, &mask_img); - log::info("img.laplacian(1, true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_laplacian_size2_sharpen_mul200_add200_thr_oft20_invert.png"); - delete img; - } - } -#endif - -#if TEST_BILATERAL - { - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(img->width(), img->height(), image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - start_time = time::ticks_us(); - img->bilateral(2); - log::info("img.bilateral(2) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_bilateral_size2.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->bilateral(1, 0.5); - log::info("img.bilateral(1, 0.5) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_bilateral_size2_color0.5.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->bilateral(1, 0.5, 0.1); - log::info("img.bilateral(1, 0.5, 0.1) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_bilateral_size2_color0.1_sigma0.1.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->bilateral(1, 0.1, 1, true); - log::info("img.bilateral(1, 0.1, 1, true)) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_bilateral_size2_color0.1_sigma1_thr.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->bilateral(1, 0.1, 1, true, 5); - log::info("img.bilateral(1, 0.1, 1, true, 5) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_bilateral_size2_color0.1_sigma1_thr_oft20.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->bilateral(1, 0.1, 1, true, 5, true, &mask_img); - log::info("img.bilateral(1, 0.1, 1, true, 5, true, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_bilateral_size2_color0.1_sigma1_thr_oft20_invert.png"); - delete img; - } - - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(img->width(), img->height(), image::Format::FMT_RGB888); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - start_time = time::ticks_us(); - img->bilateral(2); - log::info("img.bilateral(2) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_bilateral_size2.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->bilateral(1, 0.5); - log::info("img.bilateral(1, 0.5) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_bilateral_size2_color0.5.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->bilateral(1, 0.5, 0.1); - log::info("img.bilateral(1, 0.5, 0.1) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_bilateral_size2_color0.5_sigma0.1.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->bilateral(1, 0.1, 1, true); - log::info("img.bilateral(1, 0.1, 1, true)) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_bilateral_size2_color0.1_sigma1_thr.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->bilateral(1, 0.1, 1, true, 5); - log::info("img.bilateral(1, 0.1, 1, true, 5) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_bilateral_size2_color0.1_sigma1_thr_oft5.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->bilateral(1, 0.1, 1, true, 5, true, &mask_img); - log::info("img.bilateral(1, 0.1, 1, true, 5, true, &mask_img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_bilateral_size2_color0.1_sigma1_thr_oft5_invert.png"); - delete img; - } - } -#endif - -#if TEST_LINPOLAR - { - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - - start_time = time::ticks_us(); - img->linpolar(); - log::info("img.linpolar() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_linpolar.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->linpolar(true); - log::info("img.linpolar(true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_linpolar_reverse.png"); - delete img; - } - - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->linpolar(); - log::info("img.linpolar() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_linpolar.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - start_time = time::ticks_us(); - img->linpolar(true); - log::info("img.linpolar(true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_linpolar_reverse.png"); - delete img; - } - } -#endif - -#if TEST_LOGPOLAR - { - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(img->width(), img->height(), image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - start_time = time::ticks_us(); - img->logpolar(); - log::info("img.logpolar() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_logpolar.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->logpolar(true); - log::info("img.logpolar(true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_logpolar_reverse.png"); - delete img; - } - - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(img->width(), img->height(), image::Format::FMT_RGB888); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - start_time = time::ticks_us(); - img->logpolar(); - log::info("img.logpolar() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_logpolar.png"); - delete img; - - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - start_time = time::ticks_us(); - img->logpolar(true); - log::info("img.logpolar(true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_logpolar_reverse.png"); - delete img; - } - } -#endif - -#if TEST_LENS_COOR - { - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(img->width(), img->height(), image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - start_time = time::ticks_us(); - img->lens_corr(); - log::info("img.lens_corr() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_lens_corr.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->lens_corr(2.0); - log::info("img.lens_corr(true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_lens_corr_strength2.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->lens_corr(2.0, 0.5); - log::info("img.lens_corr(true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_lens_corr_strength2_zoom0.5.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - img->lens_corr(2.0, 0.5, 1, 1); - log::info("img.lens_corr(true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_lens_corr_strength2_zoom0.5_x1,y1.png"); - delete img; - } - - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(img->width(), img->height(), image::Format::FMT_RGB888); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - start_time = time::ticks_us(); - img->lens_corr(); - log::info("img.lens_corr() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_lens_corr.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->lens_corr(2.0); - log::info("img.lens_corr(true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_lens_corr_strength2.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->lens_corr(2.0, 0.5); - log::info("img.lens_corr(true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_lens_corr_strength2_zoom0.5.png"); - delete img; - - start_time = time::ticks_us(); - img = image::load(test_640x480_png, image::Format::FMT_RGB888); - img->lens_corr(2.0, 0.5, 1, 1); - log::info("img.lens_corr(true) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_lens_corr_strength2_zoom0.5_x1,y1.png"); - delete img; - } - } -#endif - -#if TEST_ROTATION_COOR - { - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(img->width(), img->height(), image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - double x_rot = 0; - double y_rot = 0; - double z_rot = 0; - double x_trans = 0; - double y_trans = 0; - double zoom = 1; - double fov = 60; - std::vector corners = {}; - - start_time = time::ticks_us(); - img->rotation_corr(); - log::info("img.rotation_corr() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_rotation_corr.png"); - delete img; - - start_time = time::ticks_us(); - img->rotation_corr(x_rot, y_rot, z_rot, x_trans, y_trans, zoom, fov, corners); - log::info("img.rotation_corr(x_rot, y_rot, z_rot, x_trans, y_trans) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_rotation_corr_input.png"); - delete img; - } - - { - image::Image *img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(img->width(), img->height(), image::Format::FMT_RGB888); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - double x_rot = 0; - double y_rot = 0; - double z_rot = 0; - double x_trans = 0; - double y_trans = 0; - double zoom = 1; - double fov = 60; - std::vector corners = {}; - - start_time = time::ticks_us(); - img->rotation_corr(); - log::info("img.rotation_corr() cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_rotation_corr.png"); - delete img; - - start_time = time::ticks_us(); - img->rotation_corr(x_rot, y_rot, z_rot, x_trans, y_trans, zoom, fov, corners); - log::info("img.rotation_corr(x_rot, y_rot, z_rot, x_trans, y_trans) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_rotation_corr_input.png"); - delete img; - } - } -#endif - -#if TEST_GET_HISTOGRAM - { -#if 0 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - image::Image *new_gray_img = img->to_format(image::Format::FMT_GRAYSCALE); - delete img; - img = new_gray_img; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - std::vector> thresholds = {{100, 200}}; - bool invert = false; - int bins = 256; - int l_bins = 256; - int a_bins = 256; - int b_bins = 256; - image::Image difference = image::Image(img->width(), img->height(), image::Format::FMT_GRAYSCALE); - difference.draw_rect(0, 0, difference.width(), difference.height(), 255, -1); - - std::map> hist; - start_time = time::ticks_us(); - hist = img->get_histogram(thresholds, invert, roi, bins, l_bins, a_bins, b_bins, NULL); - log::info("gray get histogram cost %d us\r\n", (int)(time::ticks_us() - start_time)); - - image::Image *new_rgb_img = img->to_format(image::Format::FMT_RGB888); - delete img; - img = new_rgb_img; - - // Process results - { - int start_x = 0, start_y = 0, hist_max_w = 512, hist_max_h = 200; - int rect_w = hist_max_w / bins; - std::vector data = hist["L"]; - for (int i = 0; i < data.size(); i ++) { - int height = data[i] * hist_max_h; - img->draw_rect(start_x + i * rect_w, start_y, rect_w, height, image::Color::from_rgb(255, 0, 0), -1); - } - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - std::vector> thresholds = {{0, 100}}; - bool invert = false; - int bins = 256; - int l_bins = 100; - int a_bins = 256; - int b_bins = 256; - image::Image difference = image::Image(img->width(), img->height(), image::Format::FMT_GRAYSCALE); - difference.draw_rect(0, 0, difference.width(), difference.height(), 255, -1); - - std::map> hist; - start_time = time::ticks_us(); - hist = img->get_histogram(thresholds, invert, roi, bins, l_bins, a_bins, b_bins, NULL); - log::info("rgb888 get histogram cost %d us\r\n", (int)(time::ticks_us() - start_time)); - - // Process results - { - int start_x = 0, start_y = 0, rect_w = 2, hist_max_h = 200; - std::vector data = hist["L"]; - for (size_t i = 0; i < data.size(); i ++) { - int height = data[i] * hist_max_h; - img->draw_rect(start_x + i * rect_w, start_y, rect_w, height, image::Color::from_rgb(255, 0, 0), -1); - } - } - - { - int start_x = 0, start_y = 100, rect_w = 2, hist_max_h = 200; - std::vector data = hist["A"]; - for (size_t i = 0; i < data.size(); i ++) { - int height = data[i] * hist_max_h; - img->draw_rect(start_x + i * rect_w, start_y, rect_w, height, image::Color::from_rgb(0, 255, 0), -1); - } - } - - { - int start_x = 0, start_y = 200, rect_w = 2, hist_max_h = 200; - std::vector data = hist["B"]; - for (size_t i = 0; i < data.size(); i ++) { - int height = data[i] * hist_max_h; - img->draw_rect(start_x + i * rect_w, start_y, rect_w, height, image::Color::from_rgb(0, 0, 255), -1); - } - } - - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - } -#endif - -#if TEST_GET_STATISTICS - { -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - image::Image *new_gray_img = img->to_format(image::Format::FMT_GRAYSCALE); - delete img; - img = new_gray_img; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - std::vector> thresholds = {{0, 100}}; - bool invert = false; - int bins = 256; - int l_bins = 256; - int a_bins = 256; - int b_bins = 256; - - image::Statistics statistics; - start_time = time::ticks_us(); - statistics = img->get_statistics(thresholds, invert, roi, bins, l_bins, a_bins, b_bins, NULL); - log::info("gray get statistics cost %d us\r\n", (int)(time::ticks_us() - start_time)); - - std::map> hist; - hist = img->get_histogram(thresholds, invert, roi, bins, l_bins, a_bins, b_bins, NULL); - image::Image *new_rgb_img = img->to_format(image::Format::FMT_RGB888); - delete img; - img = new_rgb_img; - - // Process results - { - int start_x = 0, start_y = 0, hist_max_w = 512, hist_max_h = 200; - int rect_w = hist_max_w / bins; - std::vector data = hist["L"]; - for (int i = 0; i < (int)data.size(); i ++) { - int height = data[i] * hist_max_h; - img->draw_rect(start_x + i * rect_w, start_y, rect_w, height, image::Color::from_rgb(255, 0, 0), -1); - } - } - log::info("L: mean %d, median %d, mode %d, std_dev %d, min %d, max %d, lq %d, uq %d\r\n", - statistics.l_mean(), statistics.l_median(), statistics.l_mode(), statistics.l_std_dev(), - statistics.l_min(), statistics.l_max(), statistics.l_lq(), statistics.l_uq()); - log::info("A: mean %d, median %d, mode %d, std_dev %d, min %d, max %d, lq %d, uq %d\r\n", - statistics.a_mean(), statistics.a_median(), statistics.a_mode(), statistics.a_std_dev(), - statistics.a_min(), statistics.a_max(), statistics.a_lq(), statistics.a_uq()); - log::info("B: mean %d, median %d, mode %d, std_dev %d, min %d, max %d, lq %d, uq %d\r\n", - statistics.b_mean(), statistics.b_median(), statistics.b_mode(), statistics.b_std_dev(), - statistics.b_min(), statistics.b_max(), statistics.b_lq(), statistics.b_uq()); - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - std::vector> thresholds = {{0, 100}}; - bool invert = false; - int bins = 256; - int l_bins = 100; - int a_bins = 256; - int b_bins = 256; - image::Statistics statistics; - start_time = time::ticks_us(); - statistics = img->get_statistics(thresholds, invert, roi, bins, l_bins, a_bins, b_bins, NULL); - log::info("rgb888 get statistics cost %d us\r\n", (int)(time::ticks_us() - start_time)); - - std::map> hist; - hist = img->get_histogram(thresholds, invert, roi, bins, l_bins, a_bins, b_bins, NULL); - // Process results - { - int start_x = 0, start_y = 0, rect_w = 2, hist_max_h = 200; - std::vector data = hist["L"]; - for (size_t i = 0; i < data.size(); i ++) { - int height = data[i] * hist_max_h; - img->draw_rect(start_x + i * rect_w, start_y, rect_w, height, image::Color::from_rgb(255, 0, 0), -1); - } - } - - { - int start_x = 0, start_y = 100, rect_w = 2, hist_max_h = 200; - std::vector data = hist["A"]; - for (size_t i = 0; i < data.size(); i ++) { - int height = data[i] * hist_max_h; - img->draw_rect(start_x + i * rect_w, start_y, rect_w, height, image::Color::from_rgb(0, 255, 0), -1); - } - } - - { - int start_x = 0, start_y = 200, rect_w = 2, hist_max_h = 200; - std::vector data = hist["B"]; - for (size_t i = 0; i < data.size(); i ++) { - int height = data[i] * hist_max_h; - img->draw_rect(start_x + i * rect_w, start_y, rect_w, height, image::Color::from_rgb(0, 0, 255), -1); - } - } - - log::info("L: mean %d, median %d, mode %d, std_dev %d, min %d, max %d, lq %d, uq %d\r\n", - statistics.l_mean(), statistics.l_median(), statistics.l_mode(), statistics.l_std_dev(), - statistics.l_min(), statistics.l_max(), statistics.l_lq(), statistics.l_uq()); - log::info("A: mean %d, median %d, mode %d, std_dev %d, min %d, max %d, lq %d, uq %d\r\n", - statistics.a_mean(), statistics.a_median(), statistics.a_mode(), statistics.a_std_dev(), - statistics.a_min(), statistics.a_max(), statistics.a_lq(), statistics.a_uq()); - log::info("B: mean %d, median %d, mode %d, std_dev %d, min %d, max %d, lq %d, uq %d\r\n", - statistics.b_mean(), statistics.b_median(), statistics.b_mode(), statistics.b_std_dev(), - statistics.b_min(), statistics.b_max(), statistics.b_lq(), statistics.b_uq()); - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - } -#endif - -#if TEST_GET_REGRESSION -#if 0 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - image::Image *new_gray_img = img->to_format(image::Format::FMT_GRAYSCALE); - delete img; - img = new_gray_img; - - img->binary({{20, 80}}, true); - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - std::vector> thresholds = {{0, 80, -70, -10, 0, 30}}; // GREEN - bool invert = false; - int x_stride = 1; - int y_stride = 1; - int area_threshold = 100; - int pixels_threshold = 100; - int robust = false; - std::vector lines; - start_time = time::ticks_us(); - lines = img->get_regression(thresholds, invert, roi, x_stride, y_stride, area_threshold, pixels_threshold, robust); - log::info("gray get regression %d cost %d us\r\n", lines.size(), (int)(time::ticks_us() - start_time)); - - image::Image *new_rgb_img = img->to_format(image::Format::FMT_RGB888); - delete img; - img = new_rgb_img; - - // Process results - for (auto &l : lines) { - img->draw_line(l.x1(), l.y1(), l.x2(), l.y2(), image::Color::from_rgb(200, 0, 0), 2); - img->draw_string(l.x2() + 5, l.y2() + 5, "len: " + std::to_string(l.length()), image::Color::from_rgb(200, 0, 0)); - - int theta = l.theta(); - int rho = l.rho(); - int X = std::cos(theta * M_PI / 180) * rho; - int Y = std::sin(theta * M_PI / 180) * rho; - img->draw_line(0, 0, X, Y, image::Color::from_rgb(200, 0, 0), 2); - img->draw_string(X + 5, Y + 5, std::to_string(theta) + "," + std::to_string(rho), image::Color::from_rgb(200, 0, 0)); - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - std::vector> thresholds = {{0, 80, 30, 100, -120, -60}}; // BLUE - img->binary(thresholds, true); - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - - bool invert = false; - int x_stride = 2; - int y_stride = 1; - int area_threshold = 100; - int pixels_threshold = 100; - int robust = false; - std::vector lines; - start_time = time::ticks_us(); - lines = img->get_regression(thresholds, invert, roi, x_stride, y_stride, area_threshold, pixels_threshold, robust); - log::info("rgb888 get regression %d cost %d us\r\n", lines.size(), (int)(time::ticks_us() - start_time)); - - // Process results - for (auto &l : lines) { - img->draw_line(l.x1(), l.y1(), l.x2(), l.y2(), image::Color::from_rgb(200, 0, 0), 2); - img->draw_string(l.x2() + 5, l.y2() + 5, "len: " + std::to_string(l.length()), image::Color::from_rgb(200, 0, 0)); - - int theta = l.theta(); - int rho = l.rho(); - int X = std::cos(theta * M_PI / 180) * rho; - int Y = std::sin(theta * M_PI / 180) * rho; - img->draw_line(0, 0, X, Y, image::Color::from_rgb(200, 0, 0), 2); - img->draw_string(X + 5, Y + 5, std::to_string(theta) + "," + std::to_string(rho), image::Color::from_rgb(200, 0, 0)); - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif -#endif - -#if TEST_FLOOD_FILL - { - { - image::Image *img = NULL; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - // - img = src_img->copy(); - start_time = time::ticks_us(); - img->flood_fill(100, 100); - log::info("img.flood_fill(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_flood_fill.png"); - delete img; - - img = src_img->copy(); - start_time = time::ticks_us(); - img->flood_fill(100, 100, 0.10, 0.10, image::COLOR_WHITE); - log::info("img.flood_fill(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_flood_fill_seed_floating.png"); - delete img; - - img = src_img->copy(); - start_time = time::ticks_us(); - img->flood_fill(100, 100, 0.10, 0.10, image::COLOR_WHITE, true, true, &mask_img); - log::info("img.flood_fill(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_flood_fill_seed_floating_invert.png"); - delete img; - - delete src_img; - } - - { - image::Image *img = NULL; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(src_img->width(), src_img->height(), image::Format::FMT_RGB888); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - // - img = src_img->copy(); - start_time = time::ticks_us(); - img->flood_fill(100, 100); - log::info("img.flood_fill(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_flood_fill.png"); - delete img; - - img = src_img->copy(); - start_time = time::ticks_us(); - img->flood_fill(100, 100, 0.10, 0.10, image::COLOR_WHITE); - log::info("img.flood_fill(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_flood_fill_seed_floating.png"); - delete img; - - img = src_img->copy(); - start_time = time::ticks_us(); - img->flood_fill(100, 100, 0, 0, image::COLOR_WHITE, false, false, &mask_img); - log::info("img.flood_fill(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_flood_fill_seed_floating_invert.png"); - delete img; - - delete src_img; - } - } -#endif - -#if TEST_ERODE - { - { - image::Image *img = NULL; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - // - img = src_img->copy(); - img->binary({{20, 80}}); - start_time = time::ticks_us(); - img->erode(2, -1, &mask_img); - log::info("gray img.erode(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_erode_mask.png"); - delete img; - - delete src_img; - } - - { - image::Image *img = NULL; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(src_img->width(), src_img->height(), image::Format::FMT_RGB888); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - // - img = src_img->copy(); - img->binary({{10, 80}}); - start_time = time::ticks_us(); - img->erode(2); - log::info("rgb888 img.erode(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_erode.png"); - delete img; - - // - img = src_img->copy(); - img->binary({{10, 80}}); - start_time = time::ticks_us(); - img->erode(2, -1, &mask_img); - log::info("rgb888 img.erode(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_erode_mask.png"); - delete img; - - delete src_img; - } - } -#endif - -#if TEST_DILATE - { - { - image::Image *img = NULL; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - // - img = src_img->copy(); - img->binary({{20, 80}}); - start_time = time::ticks_us(); - img->dilate(2, 0, &mask_img); - log::info("gray img.dilate(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_dilate_mask.png"); - delete img; - - delete src_img; - } - - { - image::Image *img = NULL; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(src_img->width(), src_img->height(), image::Format::FMT_RGB888); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - // - img = src_img->copy(); - img->binary({{10, 80}}); - start_time = time::ticks_us(); - img->dilate(2, 0); - log::info("rgb888 img.dilate(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_dilate.png"); - delete img; - - // - img = src_img->copy(); - img->binary({{10, 80}}); - start_time = time::ticks_us(); - img->dilate(2, 0, &mask_img); - log::info("rgb888 img.dilate(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_dilate_mask.png"); - delete img; - - delete src_img; - } - } -#endif - -#if TEST_OPEN - { - { - image::Image *img = NULL; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - // - img = src_img->copy(); - img->binary({{20, 80}}); - start_time = time::ticks_us(); - img->open(2, 0, &mask_img); - log::info("gray img.open(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_open.png"); - delete img; - - delete src_img; - } - - { - image::Image *img = NULL; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(src_img->width(), src_img->height(), image::Format::FMT_RGB888); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - // - img = src_img->copy(); - img->binary({{10, 80}}); - start_time = time::ticks_us(); - img->open(2, 0); - log::info("rgb888 img.open(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_open.png"); - delete img; - - // - img = src_img->copy(); - img->binary({{10, 80}}); - start_time = time::ticks_us(); - img->open(2, 0, &mask_img); - log::info("rgb888 img.open(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_open_mask.png"); - delete img; - - delete src_img; - } - } -#endif - -#if TEST_CLOSE - { - { - image::Image *img = NULL; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - // - img = src_img->copy(); - img->binary({{20, 80}}); - start_time = time::ticks_us(); - img->close(2, 0, &mask_img); - log::info("gray img.close(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_close.png"); - delete img; - - delete src_img; - } - - { - image::Image *img = NULL; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(src_img->width(), src_img->height(), image::Format::FMT_RGB888); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - // - img = src_img->copy(); - img->binary({{10, 80}}); - start_time = time::ticks_us(); - img->close(2, 0); - log::info("rgb888 img.close(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_close.png"); - delete img; - - // - img = src_img->copy(); - img->binary({{10, 80}}); - start_time = time::ticks_us(); - img->close(2, 0, &mask_img); - log::info("rgb888 img.close(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_close_mask.png"); - delete img; - - delete src_img; - } - } -#endif - -#if TEST_TOP_HAT - { - { - image::Image *img = NULL; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - // - img = src_img->copy(); - img->binary({{20, 80}}); - start_time = time::ticks_us(); - img->top_hat(2, 0, &mask_img); - log::info("gray img.top_hat(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_top_hat.png"); - delete img; - - delete src_img; - } - - { - image::Image *img = NULL; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(src_img->width(), src_img->height(), image::Format::FMT_RGB888); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - // - img = src_img->copy(); - img->binary({{10, 80}}); - start_time = time::ticks_us(); - img->top_hat(2, 0); - log::info("rgb888 img.top_hat(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_top_hat.png"); - delete img; - - // - img = src_img->copy(); - img->binary({{10, 80}}); - start_time = time::ticks_us(); - img->top_hat(2, 0, &mask_img); - log::info("rgb888 img.top_hat(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_top_hat_mask.png"); - delete img; - - delete src_img; - } - } -#endif - -#if TEST_BLACK_HAT - { - { - image::Image *img = NULL; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_GRAYSCALE); - image::Image mask_img(INPUT_IMG_W, INPUT_IMG_H, image::Format::FMT_GRAYSCALE); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - // - img = src_img->copy(); - img->binary({{20, 80}}); - start_time = time::ticks_us(); - img->black_hat(2, 0, &mask_img); - log::info("gray img.black_hat(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_gray_640x480_black_hat.png"); - delete img; - - delete src_img; - } - - { - image::Image *img = NULL; - image::Image *src_img = image::load(test_640x480_png, image::Format::FMT_RGB888); - image::Image mask_img(src_img->width(), src_img->height(), image::Format::FMT_RGB888); - mask_img.draw_rect(mask_img.width() / 4, mask_img.height() / 4, mask_img.width() / 2, mask_img.height() / 2, 255, -1); - - // - img = src_img->copy(); - img->binary({{10, 80}}); - start_time = time::ticks_us(); - img->black_hat(2, 0); - log::info("rgb888 img.black_hat(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_black_hat.png"); - delete img; - - // - img = src_img->copy(); - img->binary({{10, 80}}); - start_time = time::ticks_us(); - img->black_hat(2, 0, &mask_img); - log::info("rgb888 img.black_hat(img) cost %d us\r\n", (int)(time::ticks_us() - start_time)); - img->save("out_rgb888_640x480_black_hat_mask.png"); - delete img; - - delete src_img; - } - } -#endif - -#if TEST_FIND_BLOBS - { -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - image::Image *new_gray_img = img->to_format(image::Format::FMT_GRAYSCALE); - delete img; - img = new_gray_img; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - std::vector> thresholds = {{0, 20}}; - bool invert = false; - int x_stride = 1; - int y_stride = 1; - int area_threshold = 100; - int pixels_threshold = 100; - int merge = true; - int margin = 0; - int x_hist_bins_max = 2; - int y_hist_bins_max = 2; - std::vector blobs; - start_time = time::ticks_us(); - blobs = img->find_blobs(thresholds, invert, roi, x_stride, y_stride, area_threshold, pixels_threshold, merge, margin, x_hist_bins_max, y_hist_bins_max); - log::info("find %d blobs cost %d us\r\n", blobs.size(), (int)(time::ticks_us() - start_time)); - - image::Image *new_rgb_img = img->to_format(image::Format::FMT_RGB888); - delete img; - img = new_rgb_img; - - // Process results - for (auto &a : blobs) { - // corners - std::vector> corners = a.corners(); - for (int i = 0; i < 4; i ++) { - img->draw_line(corners[i][0], corners[i][1], corners[(i + 1) % 4][0], corners[(i + 1) % 4][1], image::Color::from_rgb(255, 0, 0)); - } - img->draw_string(corners[0][0] + 5, corners[0][1] + 5, "corners area: " + std::to_string(a.area()), image::Color::from_rgb(255, 0, 0)); - - // mini_corners - std::vector> mini_corners = a.mini_corners(); - for (int i = 0; i < 4; i ++) { - img->draw_line(mini_corners[i][0], mini_corners[i][1], mini_corners[(i + 1) % 4][0], mini_corners[(i + 1) % 4][1], image::Color::from_rgb(0, 255, 0)); - } - img->draw_string(mini_corners[0][0] + 5, mini_corners[0][1] + 5, "mini_corners", image::Color::from_rgb(0, 255, 0)); - - // rect - std::vector rect = a.rect(); - img->draw_rect(rect[0], rect[1], rect[2], rect[3], image::Color::from_rgb(0, 0, 255)); - img->draw_string(rect[0] + 5, rect[1] + 5, "rect", image::Color::from_rgb(0, 0, 255)); - - // ... - img->draw_string(a.x() + a.w() + 5, a.y(), "(" + std::to_string(a.x()) + "," + std::to_string(a.y()) + ")", image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.cx(), a.cy(), "(" + std::to_string(a.cx()) + "," + std::to_string(a.cy()) + ")", image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() / 2, a.y(), std::to_string(a.w()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x(), a.y() + a.h() / 2, std::to_string(a.h()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 15, std::to_string(a.rotation_deg()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 30, "code:" + std::to_string(a.code()) + ", count:" + std::to_string(a.count()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 45, "perimeter:" + std::to_string(a.perimeter()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 60, "roundness:" + std::to_string(a.roundness()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 75, "elongation:" + std::to_string(a.elongation()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 90, "area:" + std::to_string(a.area()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 105, "density:" + std::to_string(a.density()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 120, "extent:" + std::to_string(a.extent()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 135, "compactness:" + std::to_string(a.compactness()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 150, "solidity:" + std::to_string(a.solidity()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 165, "convexity:" + std::to_string(a.convexity()), image::Color::from_rgb(0, 255, 0), 1); - - // major axis line - std::vector major_axis_line = a.major_axis_line(); - img->draw_line(major_axis_line[0], major_axis_line[1], major_axis_line[2], major_axis_line[3], image::Color::from_rgb(255, 0, 0), 1); - - // minor axis line - std::vector minor_axis_line = a.minor_axis_line(); - img->draw_line(minor_axis_line[0], minor_axis_line[1], minor_axis_line[2], minor_axis_line[3], image::Color::from_rgb(0, 0, 255), 1); - - // enclosing circle - std::vector enclosing_circle = a.enclosing_circle(); - img->draw_circle(enclosing_circle[0], enclosing_circle[1], enclosing_circle[2], image::Color::from_rgb(255, 0, 0), 1); - - // enclosing ellipse - std::vector enclosed_ellipse = a.enclosed_ellipse(); - img->draw_ellipse(enclosed_ellipse[0], enclosed_ellipse[1], enclosed_ellipse[2], enclosed_ellipse[3], enclosed_ellipse[4], 0, 360, image::Color::from_rgb(0, 0, 255), 1); - - // hist - std::vector x_hist_bins = a.x_hist_bins(); - std::vector y_hist_bins = a.y_hist_bins(); - // for (int i = 0; i < x_hist_bins.size(); i ++) { - // printf("[%d] (%d, %d)\r\n", i, x_hist_bins[i], y_hist_bins[i]); - // } - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - std::vector> thresholds = {{0, 80, -70, -10, 0, 30}}; // GREEN - bool invert = false; - int x_stride = 1; - int y_stride = 1; - int area_threshold = 100; - int pixels_threshold = 100; - int merge = true; - int margin = 0; - int x_hist_bins_max = 2; - int y_hist_bins_max = 2; - std::vector blobs; - start_time = time::ticks_us(); - blobs = img->find_blobs(thresholds, invert, roi, x_stride, y_stride, area_threshold, pixels_threshold, merge, margin, x_hist_bins_max, y_hist_bins_max); - log::info("find %d blobs cost %d us\r\n", blobs.size(), (int)(time::ticks_us() - start_time)); - - // Process results - for (auto &a : blobs) { - // corners - std::vector> corners = a.corners(); - for (int i = 0; i < 4; i ++) { - img->draw_line(corners[i][0], corners[i][1], corners[(i + 1) % 4][0], corners[(i + 1) % 4][1], image::Color::from_rgb(255, 0, 0)); - } - img->draw_string(corners[0][0] + 5, corners[0][1] + 5, "corners area: " + std::to_string(a.area()), image::Color::from_rgb(255, 0, 0)); - - // mini_corners - std::vector> mini_corners = a.mini_corners(); - for (int i = 0; i < 4; i ++) { - img->draw_line(mini_corners[i][0], mini_corners[i][1], mini_corners[(i + 1) % 4][0], mini_corners[(i + 1) % 4][1], image::Color::from_rgb(0, 255, 0)); - } - img->draw_string(mini_corners[0][0] + 5, mini_corners[0][1] + 5, "mini_corners", image::Color::from_rgb(0, 255, 0)); - - // rect - std::vector rect = a.rect(); - img->draw_rect(rect[0], rect[1], rect[2], rect[3], image::Color::from_rgb(0, 0, 255)); - img->draw_string(rect[0] + 5, rect[1] + 5, "rect", image::Color::from_rgb(0, 0, 255)); - - // ... - img->draw_string(a.x() + a.w() + 5, a.y(), "(" + std::to_string(a.x()) + "," + std::to_string(a.y()) + ")", image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.cx(), a.cy(), "(" + std::to_string(a.cx()) + "," + std::to_string(a.cy()) + ")", image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() / 2, a.y(), std::to_string(a.w()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x(), a.y() + a.h() / 2, std::to_string(a.h()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 15, std::to_string(a.rotation_deg()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 30, "code:" + std::to_string(a.code()) + ", count:" + std::to_string(a.count()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 45, "perimeter:" + std::to_string(a.perimeter()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 60, "roundness:" + std::to_string(a.roundness()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 75, "elongation:" + std::to_string(a.elongation()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 90, "area:" + std::to_string(a.area()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 105, "density:" + std::to_string(a.density()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 120, "extent:" + std::to_string(a.extent()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 135, "compactness:" + std::to_string(a.compactness()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 150, "solidity:" + std::to_string(a.solidity()), image::Color::from_rgb(0, 255, 0), 1); - img->draw_string(a.x() + a.w() + 5, a.y() + 165, "convexity:" + std::to_string(a.convexity()), image::Color::from_rgb(0, 255, 0), 1); - - // major axis line - std::vector major_axis_line = a.major_axis_line(); - img->draw_line(major_axis_line[0], major_axis_line[1], major_axis_line[2], major_axis_line[3], image::Color::from_rgb(255, 0, 0), 1); - - // minor axis line - std::vector minor_axis_line = a.minor_axis_line(); - img->draw_line(minor_axis_line[0], minor_axis_line[1], minor_axis_line[2], minor_axis_line[3], image::Color::from_rgb(0, 0, 255), 1); - - // enclosing circle - std::vector enclosing_circle = a.enclosing_circle(); - img->draw_circle(enclosing_circle[0], enclosing_circle[1], enclosing_circle[2], image::Color::from_rgb(255, 0, 0), 1); - - // enclosing ellipse - std::vector enclosed_ellipse = a.enclosed_ellipse(); - img->draw_ellipse(enclosed_ellipse[0], enclosed_ellipse[1], enclosed_ellipse[2], enclosed_ellipse[3], enclosed_ellipse[4], 0, 360, image::Color::from_rgb(0, 0, 255), 1); - - // hist - std::vector x_hist_bins = a.x_hist_bins(); - std::vector y_hist_bins = a.y_hist_bins(); - // for (int i = 0; i < x_hist_bins.size(); i ++) { - // printf("[%d] (%d, %d)\r\n", i, x_hist_bins[i], y_hist_bins[i]); - // } - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - } -#endif - -#if TEST_FIND_LINES - { -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - image::Image *new_gray_img = img->to_format(image::Format::FMT_GRAYSCALE); - delete img; - img = new_gray_img; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - int x_stride = 1; - int y_stride = 1; - int threshold = 2000; - int theta_margin = 30; - int rho_margin = 30; - std::vector lines; - start_time = time::ticks_us(); - lines = img->find_lines(roi, x_stride, y_stride, threshold, theta_margin, rho_margin); - log::info("find %d lines cost %d us\r\n", lines.size(), (int)(time::ticks_us() - start_time)); - - image::Image *new_rgb_img = img->to_format(image::Format::FMT_RGB888); - delete img; - img = new_rgb_img; - - // Process results - for (auto &l : lines) { - img->draw_line(l.x1(), l.y1(), l.x2(), l.y2(), image::Color::from_rgb(200, 0, 0), 2); - img->draw_string(l.x2() + 5, l.y2() + 5, "len: " + std::to_string(l.length()), image::Color::from_rgb(200, 0, 0)); - - int theta = l.theta(); - int rho = l.rho(); - int X = std::cos(theta * M_PI / 180) * rho; - int Y = std::sin(theta * M_PI / 180) * rho; - img->draw_line(0, 0, X, Y, image::Color::from_rgb(200, 0, 0), 2); - img->draw_string(X + 5, Y + 5, std::to_string(theta) + "," + std::to_string(rho), image::Color::from_rgb(200, 0, 0)); - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - int x_stride = 1; - int y_stride = 1; - int threshold = 2000; - int theta_margin = 30; - int rho_margin = 30; - std::vector lines; - start_time = time::ticks_us(); - lines = img->find_lines(roi, x_stride, y_stride, threshold, theta_margin, rho_margin); - log::info("find %d lines cost %d us\r\n", lines.size(), (int)(time::ticks_us() - start_time)); - - // Process results - for (auto &l : lines) { - img->draw_line(l.x1(), l.y1(), l.x2(), l.y2(), image::Color::from_rgb(200, 0, 0), 2); - img->draw_string(l.x2() + 5, l.y2() + 5, "len: " + std::to_string(l.length()), image::Color::from_rgb(200, 0, 0)); - - int theta = l.theta(); - int rho = l.rho(); - int X = std::cos(theta * M_PI / 180) * rho; - int Y = std::sin(theta * M_PI / 180) * rho; - img->draw_line(0, 0, X, Y, image::Color::from_rgb(200, 0, 0), 2); - img->draw_string(X + 5, Y + 5, std::to_string(theta) + "," + std::to_string(rho), image::Color::from_rgb(200, 0, 0)); - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - } -#endif - -#if TEST_FIND_LINE_SEGMENTS - { -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - image::Image *new_gray_img = img->to_format(image::Format::FMT_GRAYSCALE); - delete img; - img = new_gray_img; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - int merge_distance = 1; - int max_theta_difference = 20; - std::vector lines; - start_time = time::ticks_us(); - lines = img->find_line_segments(roi, merge_distance, max_theta_difference); - log::info("find %d lines cost %d us\r\n", lines.size(), (int)(time::ticks_us() - start_time)); - - image::Image *new_rgb_img = img->to_format(image::Format::FMT_RGB888); - delete img; - img = new_rgb_img; - - // Process results - for (auto &l : lines) { - img->draw_line(l.x1(), l.y1(), l.x2(), l.y2(), image::Color::from_rgb(200, 0, 0), 2); - img->draw_string(l.x2() + 5, l.y2() + 5, "len: " + std::to_string(l.length()), image::Color::from_rgb(200, 0, 0)); - - int theta = l.theta(); - int rho = l.rho(); - int X = std::cos(theta * M_PI / 180) * rho; - int Y = std::sin(theta * M_PI / 180) * rho; - img->draw_line(0, 0, X, Y, image::Color::from_rgb(200, 0, 0), 2); - img->draw_string(X + 5, Y + 5, std::to_string(theta) + "," + std::to_string(rho), image::Color::from_rgb(200, 0, 0)); - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - int merge_distance = 20; - int max_theta_difference = 20; - std::vector lines; - start_time = time::ticks_us(); - lines = img->find_line_segments(roi, merge_distance, max_theta_difference); - log::info("find %d lines cost %d us\r\n", lines.size(), (int)(time::ticks_us() - start_time)); - - // Process results - for (auto &l : lines) { - img->draw_line(l.x1(), l.y1(), l.x2(), l.y2(), image::Color::from_rgb(200, 0, 0), 2); - img->draw_string(l.x2() + 5, l.y2() + 5, "len: " + std::to_string(l.length()), image::Color::from_rgb(200, 0, 0)); - - int theta = l.theta(); - int rho = l.rho(); - int X = std::cos(theta * M_PI / 180) * rho; - int Y = std::sin(theta * M_PI / 180) * rho; - img->draw_line(0, 0, X, Y, image::Color::from_rgb(200, 0, 0), 2); - img->draw_string(X + 5, Y + 5, std::to_string(theta) + "," + std::to_string(rho), image::Color::from_rgb(200, 0, 0)); - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - } -#endif - -#if TEST_FIND_CIRCLES - { -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - image::Image *new_gray_img = img->to_format(image::Format::FMT_GRAYSCALE); - delete img; - img = new_gray_img; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - int x_stride = 1; - int y_stride = 1; - int threshold = 3000; - int x_margin = 10; - int y_margin = 10; - int r_margin = 10; - int r_min = 20; - int r_max = 50; - int r_step = 2; - std::vector circles; - start_time = time::ticks_us(); - circles = img->find_circles(roi, x_stride, y_stride, threshold, x_margin, y_margin, r_margin, r_min, r_max, r_step); - log::info("find %d circles cost %d us\r\n", circles.size(), (int)(time::ticks_us() - start_time)); - - image::Image *new_rgb_img = img->to_format(image::Format::FMT_RGB888); - delete img; - img = new_rgb_img; - - // Process results - for (auto &a : circles) { - img->draw_circle(a.x(), a.y(), a.r(), image::Color::from_rgb(255, 0, 0), 2); - img->draw_string(a.x() + a.r() + 5, a.y() + a.r() + 5, "r: " + std::to_string(a.r()) + "magnitude: " + std::to_string(a.magnitude()), image::Color::from_rgb(255, 0, 0)); - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - int x_stride = 1; - int y_stride = 1; - int threshold = 3000; - int x_margin = 10; - int y_margin = 10; - int r_margin = 10; - int r_min = 20; - int r_max = 50; - int r_step = 2; - std::vector circles; - start_time = time::ticks_us(); - circles = img->find_circles(roi, x_stride, y_stride, threshold, x_margin, y_margin, r_margin, r_min, r_max, r_step); - log::info("find %d circles cost %d us\r\n", circles.size(), (int)(time::ticks_us() - start_time)); - - // Process results - for (auto &a : circles) { - img->draw_circle(a.x(), a.y(), a.r(), image::Color::from_rgb(255, 0, 0), 2); - img->draw_string(a.x() + a.r() + 5, a.y() + a.r() + 5, "r: " + std::to_string(a.r()) + "magnitude: " + std::to_string(a.magnitude()), image::Color::from_rgb(255, 0, 0)); - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - } -#endif - -#if TEST_FIND_RECTS - { -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - image::Image *new_gray_img = img->to_format(image::Format::FMT_GRAYSCALE); - delete img; - img = new_gray_img; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - int threshold = 1000; - std::vector rects; - start_time = time::ticks_us(); - rects = img->find_rects(roi, threshold); - log::info("find %d rects cost %d us\r\n", rects.size(), (int)(time::ticks_us() - start_time)); - - image::Image *new_rgb_img = img->to_format(image::Format::FMT_RGB888); - delete img; - img = new_rgb_img; - - // Process results - for (auto &a : rects) { - // corners - std::vector> corners = a.corners(); - for (int i = 0; i < 4; i ++) { - img->draw_line(corners[i][0], corners[i][1], corners[(i + 1) % 4][0], corners[(i + 1) % 4][1], image::Color::from_rgb(255, 0, 0)); - } - - // rect - std::vector rect = a.rect(); - img->draw_rect(rect[0], rect[1], rect[2], rect[3], image::Color::from_rgb(0, 0, 255)); - img->draw_string(rect[0] + 5, rect[1] + 5, "rect", image::Color::from_rgb(0, 0, 255)); - img->draw_string(rect[0] + 5, rect[1] + 20, "magnitude: " + std::to_string(a.magnitude()), image::Color::from_rgb(0, 0, 255)); - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - int threshold = 1000; - std::vector rects; - start_time = time::ticks_us(); - rects = img->find_rects(roi, threshold); - log::info("find %d rects cost %d us\r\n", rects.size(), (int)(time::ticks_us() - start_time)); - - // Process results - for (auto &a : rects) { - // corners - std::vector> corners = a.corners(); - for (int i = 0; i < 4; i ++) { - img->draw_line(corners[i][0], corners[i][1], corners[(i + 1) % 4][0], corners[(i + 1) % 4][1], image::Color::from_rgb(255, 0, 0)); - } - - // rect - std::vector rect = a.rect(); - img->draw_rect(rect[0], rect[1], rect[2], rect[3], image::Color::from_rgb(0, 0, 255)); - img->draw_string(rect[0] + 5, rect[1] + 5, "rect", image::Color::from_rgb(0, 0, 255)); - img->draw_string(rect[0] + 5, rect[1] + 20, "magnitude: " + std::to_string(a.magnitude()), image::Color::from_rgb(0, 0, 255)); - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - } -#endif - -#if TEST_FIND_QRCODES - { -#if 0 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera - image::Image *img = src_img_temp; - if (!img) - continue; - - image::Image *new_gray_img = img->to_format(image::Format::FMT_GRAYSCALE); - delete img; - img = new_gray_img; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - std::vector qrcodes; - start_time = time::ticks_us(); - qrcodes = img->find_qrcodes(roi); - log::info("find %d qrcodes cost %d us\r\n", qrcodes.size(), (int)(time::ticks_us() - start_time)); - - image::Image *new_rgb_img = img->to_format(image::Format::FMT_RGB888); - delete img; - img = new_rgb_img; - - // Process results - for (auto &a : qrcodes) { - // corners - std::vector> corners = a.corners(); - for (int i = 0; i < 4; i ++) { - img->draw_line(corners[i][0], corners[i][1], corners[(i + 1) % 4][0], corners[(i + 1) % 4][1], image::Color::from_rgb(255, 0, 0)); - } - - // rect - std::vector rect = a.rect(); - img->draw_rect(rect[0], rect[1], rect[2], rect[3], image::Color::from_rgb(0, 0, 255)); - img->draw_string(rect[0] + 5, rect[1] + 5, "rect", image::Color::from_rgb(0, 0, 255)); - - // payload - img->draw_string(a.x() + a.w() + 5, rect[1] + 20, "payload: " + a.payload(), image::Color::from_rgb(0, 0, 255)); - - // version - img->draw_string(a.x() + a.w() + 5, rect[1] + 35, "version: " + std::to_string(a.version()), image::Color::from_rgb(0, 0, 255)); - img->draw_string(a.x() + a.w() + 5, rect[1] + 50, "ecc_level: " + std::to_string(a.ecc_level()), image::Color::from_rgb(0, 0, 255)); - img->draw_string(a.x() + a.w() + 5, rect[1] + 65, "mask: " + std::to_string(a.mask()), image::Color::from_rgb(0, 0, 255)); - img->draw_string(a.x() + a.w() + 5, rect[1] + 80, "data_type: " + std::to_string(a.data_type()), image::Color::from_rgb(0, 0, 255)); - img->draw_string(a.x() + a.w() + 5, rect[1] + 95, "eci: " + std::to_string(a.eci()), image::Color::from_rgb(0, 0, 255)); - if (a.is_numeric()) { - img->draw_string(a.x() + a.w() + 5, rect[1] + 110, "is numeric", image::Color::from_rgb(0, 0, 255)); - } else if (a.is_alphanumeric()) { - img->draw_string(a.x() + a.w() + 5, rect[1] + 110, "is alphanumeric", image::Color::from_rgb(0, 0, 255)); - } else if (a.is_binary()) { - img->draw_string(a.x() + a.w() + 5, rect[1] + 110, "is binary", image::Color::from_rgb(0, 0, 255)); - } else if (a.is_kanji()) { - img->draw_string(a.x() + a.w() + 5, rect[1] + 110, "is kanji", image::Color::from_rgb(0, 0, 255)); - } else { - img->draw_string(a.x() + a.w() + 5, rect[1] + 110, "is unknown", image::Color::from_rgb(0, 0, 255)); - } - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - std::vector qrcodes; - start_time = time::ticks_us(); - qrcodes = img->find_qrcodes(roi); - log::info("find %d qrcodes cost %d us\r\n", qrcodes.size(), (int)(time::ticks_us() - start_time)); - - // Process results - for (auto &a : qrcodes) { - // corners - std::vector> corners = a.corners(); - for (int i = 0; i < 4; i ++) { - img->draw_line(corners[i][0], corners[i][1], corners[(i + 1) % 4][0], corners[(i + 1) % 4][1], image::Color::from_rgb(255, 0, 0)); - } - - // rect - std::vector rect = a.rect(); - img->draw_rect(rect[0], rect[1], rect[2], rect[3], image::Color::from_rgb(0, 0, 255)); - img->draw_string(rect[0] + 5, rect[1] + 5, "rect", image::Color::from_rgb(0, 0, 255)); - - // payload - img->draw_string(a.x() + a.w() + 5, rect[1] + 20, "payload: " + a.payload(), image::Color::from_rgb(0, 0, 255)); - - // version - img->draw_string(a.x() + a.w() + 5, rect[1] + 35, "version: " + std::to_string(a.version()), image::Color::from_rgb(0, 0, 255)); - img->draw_string(a.x() + a.w() + 5, rect[1] + 50, "ecc_level: " + std::to_string(a.ecc_level()), image::Color::from_rgb(0, 0, 255)); - img->draw_string(a.x() + a.w() + 5, rect[1] + 65, "mask: " + std::to_string(a.mask()), image::Color::from_rgb(0, 0, 255)); - img->draw_string(a.x() + a.w() + 5, rect[1] + 80, "data_type: " + std::to_string(a.data_type()), image::Color::from_rgb(0, 0, 255)); - img->draw_string(a.x() + a.w() + 5, rect[1] + 95, "eci: " + std::to_string(a.eci()), image::Color::from_rgb(0, 0, 255)); - if (a.is_numeric()) { - img->draw_string(a.x() + a.w() + 5, rect[1] + 110, "is numeric", image::Color::from_rgb(0, 0, 255)); - } else if (a.is_alphanumeric()) { - img->draw_string(a.x() + a.w() + 5, rect[1] + 110, "is alphanumeric", image::Color::from_rgb(0, 0, 255)); - } else if (a.is_binary()) { - img->draw_string(a.x() + a.w() + 5, rect[1] + 110, "is binary", image::Color::from_rgb(0, 0, 255)); - } else if (a.is_kanji()) { - img->draw_string(a.x() + a.w() + 5, rect[1] + 110, "is kanji", image::Color::from_rgb(0, 0, 255)); - } else { - img->draw_string(a.x() + a.w() + 5, rect[1] + 110, "is unknown", image::Color::from_rgb(0, 0, 255)); - } - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - } -#endif - -#if TEST_FIND_APRILTAGS - { -#if 0 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - image::Image *new_gray_img = img->to_format(image::Format::FMT_GRAYSCALE); - delete img; - img = new_gray_img; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - image::ApriltagFamilies families = image::TAG36H11; - float fx = -1; - float fy = -1; - int cx = img->width() / 2; - int cy = img->height() / 2; - std::vector apriltags; - start_time = time::ticks_us(); - apriltags = img->find_apriltags(roi, families, fx, fy, cx, cy); - log::info("find %d apriltags cost %d us\r\n", apriltags.size(), (int)(time::ticks_us() - start_time)); - - image::Image *new_rgb_img = img->to_format(image::Format::FMT_RGB888); - delete img; - img = new_rgb_img; - - // Process results - for (auto &a : apriltags) { - // corners - std::vector> corners = a.corners(); - for (int i = 0; i < 4; i ++) { - img->draw_line(corners[i][0], corners[i][1], corners[(i + 1) % 4][0], corners[(i + 1) % 4][1], image::Color::from_rgb(255, 0, 0), 2); - } - - // rect - std::vector rect = a.rect(); - img->draw_rect(rect[0], rect[1], rect[2], rect[3], image::Color::from_rgb(0, 0, 255), 2); - img->draw_string(rect[0] + 5, rect[1] + 5, "rect", image::Color::from_rgb(0, 0, 255)); - - // apriltag - img->draw_string(a.x() + a.w() + 5, rect[1] + 20, "id: " + std::to_string(a.id()), image::Color::from_rgb(255, 0, 0)); - - // family - img->draw_string(a.x() + a.w() + 5, rect[1] + 35, "family: " + std::to_string(a.family()), image::Color::from_rgb(255, 0, 0)); - - // center coordinate - img->draw_string(a.cx(), a.cy(), "(" + std::to_string(a.cx()) + "," + std::to_string(a.cy()) + ")", image::Color::from_rgb(255, 0, 0)); - - // rotation - img->draw_string(a.cx(), a.cy() + 15, "rot: " + std::to_string(a.rotation()), image::Color::from_rgb(255, 0, 0)); - - // hamming - img->draw_string(a.cx(), a.cy() + 30, "hamming: " + std::to_string(a.hamming()), image::Color::from_rgb(255, 0, 0)); - - // goodness - img->draw_string(a.cx(), a.cy() + 45, "goodness: " + std::to_string(a.goodness()), image::Color::from_rgb(255, 0, 0)); - - // translation - img->draw_string(a.cx(), a.cy() + 60, "translation: (" + std::to_string(a.x_translation()) + "," + std::to_string(a.y_translation()) + "," + std::to_string(a.z_translation()) + ")", image::Color::from_rgb(255, 0, 0)); - - // rotation - img->draw_string(a.cx(), a.cy() + 75, "rotation: (" + std::to_string(a.x_rotation()) + "," + std::to_string(a.y_rotation()) + "," + std::to_string(a.z_rotation()) + ")", image::Color::from_rgb(255, 0, 0)); - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - // Process find - maix::image::ApriltagFamilies families = image::TAG36H11; - float fx = -1; - float fy = -1; - std::vector apriltags; - start_time = time::ticks_us(); - maix::image::Image *resize_img = img->resize(160, 120); - int cx = resize_img->width() / 2; - int cy = resize_img->height() / 2; - float x_scale = (float)img->width() / 160; - float y_scale = (float)img->height() / 120; - float w_scale = x_scale; - std::vector roi = {0, 0, resize_img->width(), resize_img->height()}; - apriltags = resize_img->find_apriltags(roi, families, fx, fy, cx, cy); - delete resize_img; - log::info("find %d apriltags cost %d us\r\n", apriltags.size(), (int)(time::ticks_us() - start_time)); - - static uint64_t last_us = 0; - log::info("loop cost %d us\r\n", (int)(time::ticks_us() - last_us)); - last_us = time::ticks_us(); - - // Process results - for (auto &a : apriltags) { - // corners - std::vector> corners = a.corners(); - for (int i = 0; i < 4; i ++) { - corners[i][0] = corners[i][0] * x_scale; - corners[i][1] = corners[i][1] * y_scale; - } - for (int i = 0; i < 4; i ++) { - img->draw_line(corners[i][0], corners[i][1], corners[(i + 1) % 4][0], corners[(i + 1) % 4][1], image::Color::from_rgb(255, 0, 0), 2); - } - - // rect - std::vector rect = a.rect(); - rect[0] = rect[0] * x_scale; - rect[1] = rect[1] * y_scale; - rect[2] = rect[2] * x_scale; - rect[3] = rect[3] * y_scale; - img->draw_rect(rect[0], rect[1], rect[2], rect[3], image::Color::from_rgb(0, 0, 255), 2); - img->draw_string(rect[0] + 5, rect[1] + 5, "rect", image::Color::from_rgb(0, 0, 255)); - - int x = a.x() * x_scale; - int w = a.w() * w_scale; - int cx = a.cx() * x_scale; - int cy = a.cy() * y_scale; - - // apriltag - img->draw_string(x + w + 5, rect[1] + 20, "id: " + std::to_string(a.id()), image::Color::from_rgb(255, 0, 0)); - - // family - img->draw_string(x + w + 5, rect[1] + 35, "family: " + std::to_string(a.family()), image::Color::from_rgb(255, 0, 0)); - - // center coordinate - img->draw_string(cx, cy, "(" + std::to_string(cx) + "," + std::to_string(cy) + ")", image::Color::from_rgb(255, 0, 0)); - - // rotation - img->draw_string(cx, cy + 15, "rot: " + std::to_string(a.rotation()), image::Color::from_rgb(255, 0, 0)); - - // hamming - img->draw_string(cx, cy + 30, "hamming: " + std::to_string(a.hamming()), image::Color::from_rgb(255, 0, 0)); - - // goodness - img->draw_string(cx, cy + 45, "goodness: " + std::to_string(a.goodness()), image::Color::from_rgb(255, 0, 0)); - - // translation - img->draw_string(cx, cy + 60, "translation: (" + std::to_string(a.x_translation()) + "," + std::to_string(a.y_translation()) + "," + std::to_string(a.z_translation()) + ")", image::Color::from_rgb(255, 0, 0)); - - // rotation - img->draw_string(cx, cy + 75, "rotation: (" + std::to_string(a.x_rotation()) + "," + std::to_string(a.y_rotation()) + "," + std::to_string(a.z_rotation()) + ")", image::Color::from_rgb(255, 0, 0)); - } - - // // Draw ROI - // img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - // img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - } -#endif - -#if TEST_FIND_DATAMATRICES - { -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - image::Image *new_gray_img = img->to_format(image::Format::FMT_GRAYSCALE); - delete img; - img = new_gray_img; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - int effort = 200; - std::vector datamatrices; - start_time = time::ticks_us(); - datamatrices = img->find_datamatrices(roi, effort); - log::info("find %d datamatrices cost %d us\r\n", datamatrices.size(), (int)(time::ticks_us() - start_time)); - - image::Image *new_rgb_img = img->to_format(image::Format::FMT_RGB888); - delete img; - img = new_rgb_img; - - // Process results - for (auto &a : datamatrices) { - // corners - std::vector> corners = a.corners(); - for (int i = 0; i < 4; i ++) { - img->draw_line(corners[i][0], corners[i][1], corners[(i + 1) % 4][0], corners[(i + 1) % 4][1], image::Color::from_rgb(255, 0, 0), 2); - } - - // rect - std::vector rect = a.rect(); - img->draw_rect(rect[0], rect[1], rect[2], rect[3], image::Color::from_rgb(0, 0, 255), 2); - img->draw_string(rect[0] + 5, rect[1] + 5, "rect", image::Color::from_rgb(0, 0, 255)); - - // payload - img->draw_string(a.x() + a.w() + 5, rect[1] + 20, "payload: " + a.payload(), image::Color::from_rgb(255, 0, 0)); - - // rotation - img->draw_string(a.x(), a.y() + 15, "rot: " + std::to_string(a.rotation()), image::Color::from_rgb(255, 0, 0)); - - // rows and columns - img->draw_string(a.x(), a.y() + 30, "rows: " + std::to_string(a.rows()) + ", columns: " + std::to_string(a.columns()), image::Color::from_rgb(255, 0, 0)); - - // capacity - img->draw_string(a.x(), a.y() + 45, "capacity: " + std::to_string(a.capacity()), image::Color::from_rgb(255, 0, 0)); - - // padding - img->draw_string(a.x(), a.y() + 60, "padding: " + std::to_string(a.padding()), image::Color::from_rgb(255, 0, 0)); - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - int effort = 200; - std::vector datamatrices; - start_time = time::ticks_us(); - datamatrices = img->find_datamatrices(roi, effort); - log::info("find %d datamatrices cost %d us\r\n", datamatrices.size(), (int)(time::ticks_us() - start_time)); - - // Process results - for (auto &a : datamatrices) { - // corners - std::vector> corners = a.corners(); - for (int i = 0; i < 4; i ++) { - img->draw_line(corners[i][0], corners[i][1], corners[(i + 1) % 4][0], corners[(i + 1) % 4][1], image::Color::from_rgb(255, 0, 0), 2); - } - - // rect - std::vector rect = a.rect(); - img->draw_rect(rect[0], rect[1], rect[2], rect[3], image::Color::from_rgb(0, 0, 255), 2); - img->draw_string(rect[0] + 5, rect[1] + 5, "rect", image::Color::from_rgb(0, 0, 255)); - - // payload - img->draw_string(a.x() + a.w() + 5, rect[1] + 20, "payload: " + a.payload(), image::Color::from_rgb(255, 0, 0)); - - // rotation - img->draw_string(a.x(), a.y() + 15, "rot: " + std::to_string(a.rotation()), image::Color::from_rgb(255, 0, 0)); - - // rows and columns - img->draw_string(a.x(), a.y() + 30, "rows: " + std::to_string(a.rows()) + ", columns: " + std::to_string(a.columns()), image::Color::from_rgb(255, 0, 0)); - - // capacity - img->draw_string(a.x(), a.y() + 45, "capacity: " + std::to_string(a.capacity()), image::Color::from_rgb(255, 0, 0)); - - // padding - img->draw_string(a.x(), a.y() + 60, "padding: " + std::to_string(a.padding()), image::Color::from_rgb(255, 0, 0)); - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - } -#endif - -#if TEST_FIND_BARCODES - { -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - image::Image *new_gray_img = img->to_format(image::Format::FMT_GRAYSCALE); - delete img; - img = new_gray_img; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - std::vector barcodes; - start_time = time::ticks_us(); - barcodes = img->find_barcodes(roi); - log::info("find %d barcodes cost %d us\r\n", barcodes.size(), (int)(time::ticks_us() - start_time)); - - image::Image *new_rgb_img = img->to_format(image::Format::FMT_RGB888); - delete img; - img = new_rgb_img; - - // Process results - for (auto &a : barcodes) { - // corners - std::vector> corners = a.corners(); - for (int i = 0; i < 4; i ++) { - img->draw_line(corners[i][0], corners[i][1], corners[(i + 1) % 4][0], corners[(i + 1) % 4][1], image::Color::from_rgb(255, 0, 0), 2); - } - - // rect - std::vector rect = a.rect(); - img->draw_rect(rect[0], rect[1], rect[2], rect[3], image::Color::from_rgb(0, 0, 255), 2); - img->draw_string(rect[0] + 5, rect[1] + 5, "rect", image::Color::from_rgb(0, 0, 255)); - - // payload - img->draw_string(a.x() + a.w() + 5, rect[1] + 20, "payload: " + a.payload(), image::Color::from_rgb(255, 0, 0)); - - // type - img->draw_string(a.x() + a.w() + 5, rect[1] + 35, "type: " + std::to_string(a.type()), image::Color::from_rgb(255, 0, 0)); - - // rotation - img->draw_string(a.x(), a.y() + 15, "rot: " + std::to_string(a.rotation()), image::Color::from_rgb(255, 0, 0)); - - // quality - img->draw_string(a.x(), a.y() + 30, "quality: " + std::to_string(a.quality()), image::Color::from_rgb(255, 0, 0)); - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - std::vector barcodes; - start_time = time::ticks_us(); - barcodes = img->find_barcodes(roi); - log::info("find %d barcodes cost %d us\r\n", barcodes.size(), (int)(time::ticks_us() - start_time)); - - // Process results - for (auto &a : barcodes) { - // corners - std::vector> corners = a.corners(); - for (int i = 0; i < 4; i ++) { - img->draw_line(corners[i][0], corners[i][1], corners[(i + 1) % 4][0], corners[(i + 1) % 4][1], image::Color::from_rgb(255, 0, 0), 2); - } - - // rect - std::vector rect = a.rect(); - img->draw_rect(rect[0], rect[1], rect[2], rect[3], image::Color::from_rgb(0, 0, 255), 2); - img->draw_string(rect[0] + 5, rect[1] + 5, "rect", image::Color::from_rgb(0, 0, 255)); - - // payload - img->draw_string(a.x() + a.w() + 5, rect[1] + 20, "payload: " + a.payload(), image::Color::from_rgb(255, 0, 0)); - - // type - img->draw_string(a.x() + a.w() + 5, rect[1] + 35, "type: " + std::to_string(a.type()), image::Color::from_rgb(255, 0, 0)); - - // rotation - img->draw_string(a.x(), a.y() + 15, "rot: " + std::to_string(a.rotation()), image::Color::from_rgb(255, 0, 0)); - - // quality - img->draw_string(a.x(), a.y() + 30, "quality: " + std::to_string(a.quality()), image::Color::from_rgb(255, 0, 0)); - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - } -#endif - -#if TEST_FIND_DISPLACEMENT - { - image::Image *template_image = NULL; - std::vector template_roi = {}; -#if 1 - { - log::info("Enter ctrl+c to snap an template image.\r\n"); - app::set_exit_flag(false); - while(1) - { -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (img) { - template_roi = {10, 10, img->width() - 20, img->height() - 20}; - - if (app::need_exit()) { - template_image = img->copy(); - log::info("Snap an template image.\r\n"); - delete img; - break; - } - - // Draw ROI - img->draw_string(template_roi[0] + 5, template_roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(template_roi[0], template_roi[1], template_roi[2], template_roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image - #if DISPLAY_ENABLE - disp.show(*img); -#endif - - // Free image data, important! - delete img; - } - } - } -#endif - -#if 1 - { - image::Image *new_template_img = template_image->to_format(image::Format::FMT_GRAYSCALE); - - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - image::Image *new_gray_img = img->to_format(image::Format::FMT_GRAYSCALE); - delete img; - img = new_gray_img; - - // Process find - // std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - std::vector roi = template_roi; - bool logpolar = true; - image::Displacement displacement; - start_time = time::ticks_us(); - displacement = img->find_displacement(*new_template_img, roi, template_roi, logpolar); - log::info("find %d displacement cost %d us\r\n", 1, (int)(time::ticks_us() - start_time)); - - image::Image *new_rgb_img = img->to_format(image::Format::FMT_RGB888); - delete img; - img = new_rgb_img; - - // Process results - img->draw_string(50, 50, "x: " + std::to_string(displacement.x_translation()), image::Color::from_rgb(255, 0, 0)); - img->draw_string(50, 65, "y: " + std::to_string(displacement.y_translation()), image::Color::from_rgb(255, 0, 0)); - img->draw_string(50, 80, "rotation: " + std::to_string(displacement.rotation()), image::Color::from_rgb(255, 0, 0)); - img->draw_string(50, 95, "scale: " + std::to_string(displacement.scale()), image::Color::from_rgb(255, 0, 0)); - img->draw_string(50, 110, "response: " + std::to_string(displacement.response()), image::Color::from_rgb(255, 0, 0)); - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - } -#endif - -#if TEST_FIND_TEMPLATE - { - image::Image *template_image = NULL; - std::vector template_roi = {}; -#if 1 - { - log::info("Enter ctrl+c to snap an template image.\r\n"); - app::set_exit_flag(false); - while(1) { -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (img) { - template_roi = {img->width() / 4, img->height() / 4, img->width() / 4, img->height() / 4}; - - if (app::need_exit()) { - // template_image = img->copy(); - template_image = img->crop(template_roi[0], template_roi[1], template_roi[2], template_roi[3]); - log::info("Snap an template image.\r\n"); - delete img; - break; - } - - // Draw ROI - img->draw_string(template_roi[0] + 5, template_roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(template_roi[0], template_roi[1], template_roi[2], template_roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image - #if DISPLAY_ENABLE - disp.show(*img); -#endif - - // Free image data, important! - delete img; - } - } - } -#endif - -#if 1 - { - image::Image *new_template_img = template_image->to_format(image::Format::FMT_GRAYSCALE); - - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - image::Image *new_gray_img = img->to_format(image::Format::FMT_GRAYSCALE); - delete img; - img = new_gray_img; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - float threshold = 0.7; - int step = 2; - image::TemplateMatch search = image::SEARCH_DS; - // image::TemplateMatch search = image::SEARCH_EX; - std::vector template_rect; - start_time = time::ticks_us(); - template_rect = img->find_template(*new_template_img, threshold, roi, step, search); - log::info("find %d template_roi cost %d us\r\n", template_roi.size() != 0 ? 1 : 0, (int)(time::ticks_us() - start_time)); - image::Image *new_rgb_img = img->to_format(image::Format::FMT_RGB888); - delete img; - img = new_rgb_img; - - // Process results - if (template_rect.size() >= 4) { - img->draw_rect(template_rect[0], template_rect[1], template_rect[2], template_rect[3], image::Color::from_rgb(0, 0, 255), 2); - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Draw template image - img->draw_image(0, 200, *new_template_img); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - - delete new_template_img; - } -#endif - -#if 1 - { - image::Image *new_template_img = template_image; - - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - float threshold = 0.5; - int step = 4; - // image::TemplateMatch search = image::SEARCH_DS; - image::TemplateMatch search = image::SEARCH_EX; - std::vector template_rect; - start_time = time::ticks_us(); - template_rect = img->find_template(*new_template_img, threshold, roi, step, search); - log::info("find %d template_roi cost %d us\r\n", template_roi.size() != 0 ? 1 : 0, (int)(time::ticks_us() - start_time)); - - // Process results - if (template_rect.size() >= 4) { - img->draw_rect(template_rect[0], template_rect[1], template_rect[2], template_rect[3], image::Color::from_rgb(0, 0, 255), 2); - } - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Draw template image - img->draw_image(0, 200, *new_template_img); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - if (template_image) - delete template_image; - } -#endif - -#if TEST_FIND_FEATURES - { - - } -#endif - -#if TEST_FIND_LBP - { -#if 1 - { - app::set_exit_flag(false); - int need_first_keypoint = 0; - image::LBPKeyPoint first_keypoint; - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - char key = get_key(); - if (key != 0) { - log::info("key %c pressed\r\n", key); - } - - image::Image *new_gray_img = img->to_format(image::Format::FMT_GRAYSCALE); - delete img; - img = new_gray_img; - - // Process find - std::vector roi = {img->width() / 2, img->height() / 2, img->width() / 4, img->height() / 4}; - std::vector first_keypoint_roi = {img->width() / 8, img->height() / 8, img->width() / 4, img->height() / 4}; - int distance = 0; - if (need_first_keypoint && key == 's') { - start_time = time::ticks_us(); - first_keypoint = img->find_lbp(first_keypoint_roi); - log::info("find first lbp cost %d us\r\n", (int)(time::ticks_us() - start_time)); - need_first_keypoint = 0; - } else if (!need_first_keypoint) { - start_time = time::ticks_us(); - image::LBPKeyPoint keypoint = img->find_lbp(roi); - log::info("find other lbp cost %d us\r\n", (int)(time::ticks_us() - start_time)); - // distance = img->match_lbp_descriptor(first_keypoint, keypoint); - } - - image::Image *new_rgb_img = img->to_format(image::Format::FMT_RGB888); - delete img; - img = new_rgb_img; - - // Process results - if (need_first_keypoint) { - img->draw_string(0, 0, "enter s to calculate first keypoint", image::Color::from_rgb(255, 0, 0)); - } - img->draw_string(50, 50, "distance: " + std::to_string(distance), image::Color::from_rgb(255, 0, 0)); - - // Draw ROI - img->draw_string(first_keypoint_roi[0] + 5, first_keypoint_roi[1] + 5, "First ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(first_keypoint_roi[0], first_keypoint_roi[1], first_keypoint_roi[2], first_keypoint_roi[3], image::Color::from_rgb(0, 255, 0)); - - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - } -#endif - -#if TEST_FIND_KEYPOINTS - { -#if 1 - { - app::set_exit_flag(false); - int need_first_keypoint = 0; - image::LBPKeyPoint first_keypoint; - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - char key = get_key(); - if (key != 0) { - log::info("key %c pressed\r\n", key); - } - - image::Image *new_gray_img = img->to_format(image::Format::FMT_GRAYSCALE); - delete img; - img = new_gray_img; - - // Process find - std::vector roi = {img->width() / 2, img->height() / 2, img->width() / 4, img->height() / 4}; - std::vector first_keypoint_roi = {img->width() / 8, img->height() / 8, img->width() / 4, img->height() / 4}; - int distance = 0; - if (need_first_keypoint && key == 's') { - start_time = time::ticks_us(); - first_keypoint = img->find_lbp(first_keypoint_roi); - log::info("find first lbp cost %d us\r\n", (int)(time::ticks_us() - start_time)); - need_first_keypoint = 0; - } else if (!need_first_keypoint) { - start_time = time::ticks_us(); - image::LBPKeyPoint keypoint = img->find_lbp(roi); - log::info("find other lbp cost %d us\r\n", (int)(time::ticks_us() - start_time)); - // distance = img->match_lbp_descriptor(first_keypoint, keypoint); - } - - image::Image *new_rgb_img = img->to_format(image::Format::FMT_RGB888); - delete img; - img = new_rgb_img; - - // Process results - if (need_first_keypoint) { - img->draw_string(0, 0, "enter s to calculate first keypoint", image::Color::from_rgb(255, 0, 0)); - } - img->draw_string(50, 50, "distance: " + std::to_string(distance), image::Color::from_rgb(255, 0, 0)); - - // Draw ROI - img->draw_string(first_keypoint_roi[0] + 5, first_keypoint_roi[1] + 5, "First ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(first_keypoint_roi[0], first_keypoint_roi[1], first_keypoint_roi[2], first_keypoint_roi[3], image::Color::from_rgb(0, 255, 0)); - - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - } -#endif - -#if TEST_FIND_EDGES - { -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - image::Image *new_gray_img = img->to_format(image::Format::FMT_GRAYSCALE); - delete img; - img = new_gray_img; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - image::EdgeDetector edge_type = image::EDGE_CANNY; - std::vector threshold = {100, 200}; - start_time = time::ticks_us(); - img = img->find_edges(edge_type, roi, threshold); - log::info("find edges cost %d us\r\n", (int)(time::ticks_us() - start_time)); - - image::Image *new_rgb_img = img->to_format(image::Format::FMT_RGB888); - delete img; - img = new_rgb_img; - - // Process results - // None - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - image::EdgeDetector edge_type = image::EDGE_CANNY; - std::vector threshold = {100, 200}; - start_time = time::ticks_us(); - img->find_edges(edge_type, roi, threshold); - log::info("find edges cost %d us\r\n", (int)(time::ticks_us() - start_time)); - - // Process results - // None - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - } -#endif - -#if TEST_FIND_HOG - { -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - image::Image *new_gray_img = img->to_format(image::Format::FMT_GRAYSCALE); - delete img; - img = new_gray_img; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - int size = 8; - start_time = time::ticks_us(); - img = img->find_hog(roi, size); - log::info("find hog cost %d us\r\n", (int)(time::ticks_us() - start_time)); - - image::Image *new_rgb_img = img->to_format(image::Format::FMT_RGB888); - delete img; - img = new_rgb_img; - - // Process results - // None - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - -#if 1 - { - app::set_exit_flag(false); - while(!app::need_exit()) - { - // Read image from the camera -#if CAMERA_ENABLE - image::Image *img = cam.read(); -#else - image::Image *img = test_cam_img->resize(INPUT_IMG_W, INPUT_IMG_H); -#endif - if (!img) - continue; - - // Process find - std::vector roi = {img->width() / 4, img->height() / 4, img->width() / 2, img->height() / 2}; - int size = 8; - start_time = time::ticks_us(); - img = img->find_hog(roi, size); - log::info("find edges cost %d us\r\n", (int)(time::ticks_us() - start_time)); - - // Process results - // None - - // Draw ROI - img->draw_string(roi[0] + 5, roi[1] + 5, "ROI", image::Color::from_rgb(0, 255, 0)); - img->draw_rect(roi[0], roi[1], roi[2], roi[3], image::Color::from_rgb(0, 255, 0)); - - // Show image -#if DISPLAY_ENABLE - disp.show(*img); -#endif - // Free image data, important! - delete img; - } - } -#endif - } -#endif - -#if TEST_STERO_DISPARITY - { - - } -#endif - + log::info("Use width:%d hieght:%d format:%d fps:%d buffer number:%d\r\n", priv.cam_w, priv.cam_h, priv.cam_fmt, priv.cam_fps, priv.cam_buffnum); return 0; } - -int main(int argc, char* argv[]) -{ - // Catch signal and process - sys::register_default_signal_handle(); - - // Use CATCH_EXCEPTION_RUN_RETURN to catch exception, - // if we don't catch exception, when program throw exception, the objects will not be destructed. - // So we catch exception here to let resources be released(call objects' destructor) before exit. - CATCH_EXCEPTION_RUN_RETURN(_main, -1, argc, argv); -} - -void print_image(image::Image &img) -{ -#if PRINTF_IMG_EN - printf("{\r\n"); - switch (img.format()) - { - case image::Format::FMT_GRAYSCALE: - for (int h = 0; h < img.height(); h++) - { - for (int w = 0; w < img.width(); w++) - { - std::vector pixel = img.get_pixel(w, h); - if (pixel.size() > 0) - printf("%3d ", pixel[0]); - } - printf("\n"); - } - break; - case image::Format::FMT_BGR888: - case image::Format::FMT_RGB888: - for (int h = 0; h < img.height(); h++) - { - for (int w = 0; w < img.width(); w++) - { - std::vector pixel = img.get_pixel(w, h, true); - if (pixel.size() > 2) - printf("(%3d, %3d, %3d) ", pixel[0], pixel[1], pixel[2]); - } - printf("\n"); - } - break; - case image::Format::FMT_BGR565: - case image::Format::FMT_RGB565: - for (int h = 0; h < img.height(); h++) - { - for (int w = 0; w < img.width(); w++) - { - std::vector pixel = img.get_pixel(w, h, true); - if (pixel.size() > 2) - printf("(%3d, %3d, %3d) ", pixel[0], pixel[1], pixel[2]); - } - printf("\n"); - } - break; - default : - printf("Not support format: %d\n", img.format()); - break; - } - printf("}\r\n"); -#endif -} \ No newline at end of file diff --git a/examples/image_method/main/src/test_image_gaussian.cpp b/examples/image_method/main/src/test_image_gaussian.cpp new file mode 100644 index 00000000..cc5a3241 --- /dev/null +++ b/examples/image_method/main/src/test_image_gaussian.cpp @@ -0,0 +1,8 @@ +#include "maix_vision.hpp" + +using namespace maix; + +int test_gaussion(image::Image *img) { + img->gaussian(2); + return 0; +} \ No newline at end of file