Initial commit.

This commit is contained in:
Micheal Smith 2025-12-13 00:22:27 -06:00
commit b7eafbf244
No known key found for this signature in database
GPG key ID: E40750BFE6702504
13 changed files with 23276 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.cache

3191
cJSON.c Normal file

File diff suppressed because it is too large Load diff

306
cJSON.h Normal file
View file

@ -0,0 +1,306 @@
/*
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
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 cJSON__h
#define cJSON__h
#ifdef __cplusplus
extern "C"
{
#endif
#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
#define __WINDOWS__
#endif
#ifdef __WINDOWS__
/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options:
CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
For *nix builds that support visibility attribute, you can define similar behavior by
setting default visibility to hidden by adding
-fvisibility=hidden (for gcc)
or
-xldscope=hidden (for sun cc)
to CFLAGS
then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
*/
#define CJSON_CDECL __cdecl
#define CJSON_STDCALL __stdcall
/* export symbols by default, this is necessary for copy pasting the C and header file */
#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_EXPORT_SYMBOLS
#endif
#if defined(CJSON_HIDE_SYMBOLS)
#define CJSON_PUBLIC(type) type CJSON_STDCALL
#elif defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL
#elif defined(CJSON_IMPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL
#endif
#else /* !__WINDOWS__ */
#define CJSON_CDECL
#define CJSON_STDCALL
#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
#else
#define CJSON_PUBLIC(type) type
#endif
#endif
/* project version */
#define CJSON_VERSION_MAJOR 1
#define CJSON_VERSION_MINOR 7
#define CJSON_VERSION_PATCH 19
#include <stddef.h>
/* cJSON Types: */
#define cJSON_Invalid (0)
#define cJSON_False (1 << 0)
#define cJSON_True (1 << 1)
#define cJSON_NULL (1 << 2)
#define cJSON_Number (1 << 3)
#define cJSON_String (1 << 4)
#define cJSON_Array (1 << 5)
#define cJSON_Object (1 << 6)
#define cJSON_Raw (1 << 7) /* raw json */
#define cJSON_IsReference 256
#define cJSON_StringIsConst 512
/* The cJSON structure: */
typedef struct cJSON
{
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *next;
struct cJSON *prev;
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
struct cJSON *child;
/* The type of the item, as above. */
int type;
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
char *valuestring;
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
int valueint;
/* The item's number, if type==cJSON_Number */
double valuedouble;
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
char *string;
} cJSON;
typedef struct cJSON_Hooks
{
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
void *(CJSON_CDECL *malloc_fn)(size_t sz);
void (CJSON_CDECL *free_fn)(void *ptr);
} cJSON_Hooks;
typedef int cJSON_bool;
/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
* This is to prevent stack overflows. */
#ifndef CJSON_NESTING_LIMIT
#define CJSON_NESTING_LIMIT 1000
#endif
/* Limits the length of circular references can be before cJSON rejects to parse them.
* This is to prevent stack overflows. */
#ifndef CJSON_CIRCULAR_LIMIT
#define CJSON_CIRCULAR_LIMIT 10000
#endif
/* returns the version of cJSON as a string */
CJSON_PUBLIC(const char*) cJSON_Version(void);
/* Supply malloc, realloc and free functions to cJSON */
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
/* Render a cJSON entity to text for transfer/storage. */
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
/* Render a cJSON entity to text for transfer/storage without any formatting. */
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
/* Delete a cJSON entity and all subentities. */
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);
/* Returns the number of items in an array (or object). */
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
/* Get item "string" from object. Case insensitive. */
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
/* Check item type and return its value */
CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item);
CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item);
/* These functions check the type of an item */
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
/* These calls create a cJSON item of the appropriate type. */
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
/* raw json */
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
/* Create a string where valuestring references a string so
* it will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
/* Create an object/array that only references it's elements so
* they will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
/* These utilities create an Array of count items.
* The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);
/* Append item to the specified array/object. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
* WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
* writing to `item->string` */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
/* Remove/Detach items from Arrays/Objects. */
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
/* Update array items. */
CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
/* Duplicate a cJSON item */
CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
* need to be released. With recurse!=0, it will duplicate any children connected to the item.
* The item->next and ->prev pointers are always zero on return from Duplicate. */
/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
* case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);
/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings.
* The input pointer json cannot point to a read-only address area, such as a string constant,
* but should point to a readable and writable address area. */
CJSON_PUBLIC(void) cJSON_Minify(char *json);
/* Helper functions for creating and adding items to an object at the same time.
* They return the added item or NULL on failure. */
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
/* helper for the cJSON_SetNumberValue macro */
CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */
CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring);
/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/
#define cJSON_SetBoolValue(object, boolValue) ( \
(object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \
(object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \
cJSON_Invalid\
)
/* Macro for iterating over an array or object */
#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
CJSON_PUBLIC(void) cJSON_free(void *object);
#ifdef __cplusplus
}
#endif
#endif

7988
deps/stb_image.h vendored Normal file

File diff suppressed because it is too large Load diff

10651
deps/stb_image_resize2.h vendored Normal file

File diff suppressed because it is too large Load diff

113
fetch.c Normal file
View file

@ -0,0 +1,113 @@
#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "fetch.h"
static char curl_errbuf[CURL_ERROR_SIZE] = {'\0'};
struct fetch *fetch_init(void) {
CURLcode res;
CURL *handle;
struct fetch *fh = NULL;
/*
* TODO: Not sure if calling this possibly more than once can cause
* an issue or not.
*/
if ((res = curl_global_init(CURL_GLOBAL_DEFAULT)) != CURLE_OK) {
fprintf(stderr, "Could not initialize curl: %s\n", curl_easy_strerror(res));
exit(EXIT_FAILURE);
}
handle = curl_easy_init();
if ((res = curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, curl_errbuf)) !=
CURLE_OK) {
fprintf(stderr, "Error setting up error buffer: %s\n",
curl_easy_strerror(res));
curl_easy_cleanup(handle);
exit(EXIT_FAILURE);
}
if ((fh = malloc(sizeof *fh)) == NULL) {
fputs("Out of memory.\n", stderr);
exit(EXIT_FAILURE);
}
fh->handle = handle;
return fh;
}
void fetch_cleanup(struct fetch *restrict f) {
/* Don't even dignify this with a response */
if (f == NULL)
return;
curl_easy_cleanup(f->handle);
free(f);
}
size_t fetch_cb(void *ptr, size_t size, size_t nmemb, void *data) {
struct response *resp = (struct response *)data;
size_t real_size = size * nmemb;
if ((resp->data = realloc(resp->data, resp->size + real_size + 1)) == NULL) {
fputs("Out of memory.\n", stderr);
exit(EXIT_FAILURE);
}
printf("Wrote %zd bytes\n", real_size);
memcpy(resp->data + resp->size, ptr, real_size);
resp->size += real_size;
resp->data[resp->size] = '\0';
return real_size;
}
struct response *fetch(struct fetch *restrict f, CURLU *restrict url) {
struct response *resp = NULL;
if (curl_easy_setopt(f->handle, CURLOPT_WRITEFUNCTION, fetch_cb) != CURLE_OK)
goto call_fetch_fail;
if (curl_easy_setopt(f->handle, CURLOPT_CURLU, url) != CURLE_OK)
goto call_fetch_fail;
if ((resp = malloc(sizeof *resp)) == NULL) {
fputs("Out of memory.", stderr);
exit(EXIT_FAILURE);
}
memset(resp, 0, sizeof *resp);
if (curl_easy_setopt(f->handle, CURLOPT_WRITEDATA, (void *)resp) != CURLE_OK)
goto call_fetch_fail;
if (curl_easy_perform(f->handle) != CURLE_OK)
goto call_fetch_fail;
if (curl_easy_getinfo(f->handle, CURLINFO_CONTENT_TYPE,
&resp->content_type) != CURLE_OK)
goto call_fetch_fail;
printf("CONTENT TYPE: %s\n", resp->content_type);
return resp;
call_fetch_fail:
if (resp != NULL)
free(resp);
fputs(curl_errbuf, stderr);
return NULL;
}
void response_cleanup(struct response *resp) {
if (resp != NULL) {
free(resp->data);
free(resp);
}
}

27
fetch.h Normal file
View file

@ -0,0 +1,27 @@
/* NOT THREAD SAFE */
#ifndef _FETCH_H
#define _FETCH_H
#include <curl/curl.h>
#include <stdlib.h>
struct fetch {
CURL *handle;
};
struct response {
char *data;
size_t size;
char *content_type; /* Don't free. Managed by libcurl. */
};
typedef size_t (*CurlWriteCallback)(void *ptr, size_t size, size_t nmemb,
void *userdata);
struct fetch *fetch_init(void);
struct response *fetch(struct fetch *restrict f, CURLU *restrict url);
void fetch_cleanup(struct fetch *f);
void response_cleanup(struct response *resp);
#endif

231
image.cpp Normal file
View file

@ -0,0 +1,231 @@
/*
* Copyright (c) 2017-2023, Stefan Haustein, Aaron Liu
*
* This file is free software: you may copy, redistribute 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 file 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 <https://www.gnu.org/licenses/>.
*
* Alternatively, you may copy, redistribute and/or modify this file under
* the terms of the Apache License, version 2.0:
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <array>
#include <cmath>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include "tiv_lib.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize2.h"
#include <cstring>
#include <sys/ioctl.h>
#include <unistd.h>
// Program exit code constants compatible with sysexits.h.
#define EXITCODE_OK 0
#define EXITCODE_COMMAND_LINE_USAGE_ERROR 64
#define EXITCODE_DATA_FORMAT_ERROR 65
#define EXITCODE_NO_INPUT_ERROR 66
void printTermColor(const int &flags, int r, int g, int b) {
r = clamp_byte(r);
g = clamp_byte(g);
b = clamp_byte(b);
bool bg = (flags & FLAG_BG) != 0;
if ((flags & FLAG_MODE_256) == 0) {
std::cout << (bg ? "\x1b[48;2;" : "\x1b[38;2;") << r << ';' << g << ';' << b
<< 'm';
return;
}
int ri = best_index(r, COLOR_STEPS, COLOR_STEP_COUNT);
int gi = best_index(g, COLOR_STEPS, COLOR_STEP_COUNT);
int bi = best_index(b, COLOR_STEPS, COLOR_STEP_COUNT);
int rq = COLOR_STEPS[ri];
int gq = COLOR_STEPS[gi];
int bq = COLOR_STEPS[bi];
int gray =
static_cast<int>(std::round(r * 0.2989f + g * 0.5870f + b * 0.1140f));
int gri = best_index(gray, GRAYSCALE_STEPS, GRAYSCALE_STEP_COUNT);
int grq = GRAYSCALE_STEPS[gri];
int color_index;
if (0.3 * sqr(rq - r) + 0.59 * sqr(gq - g) + 0.11 * sqr(bq - b) <
0.3 * sqr(grq - r) + 0.59 * sqr(grq - g) + 0.11 * sqr(grq - b)) {
color_index = 16 + 36 * ri + 6 * gi + bi;
} else {
color_index = 232 + gri; // 1..24 -> 232..255
}
std::cout << (bg ? "\x1B[48;5;" : "\u001B[38;5;") << color_index << "m";
}
void printCodepoint(int codepoint) {
if (codepoint < 128) {
std::cout << static_cast<char>(codepoint);
} else if (codepoint < 0x7ff) {
std::cout << static_cast<char>(0xc0 | (codepoint >> 6));
std::cout << static_cast<char>(0x80 | (codepoint & 0x3f));
} else if (codepoint < 0xffff) {
std::cout << static_cast<char>(0xe0 | (codepoint >> 12));
std::cout << static_cast<char>(0x80 | ((codepoint >> 6) & 0x3f));
std::cout << static_cast<char>(0x80 | (codepoint & 0x3f));
} else if (codepoint < 0x10ffff) {
std::cout << static_cast<char>(0xf0 | (codepoint >> 18));
std::cout << static_cast<char>(0x80 | ((codepoint >> 12) & 0x3f));
std::cout << static_cast<char>(0x80 | ((codepoint >> 6) & 0x3f));
std::cout << static_cast<char>(0x80 | (codepoint & 0x3f));
} else {
std::cerr << "ERROR";
}
}
void printImage(const unsigned char *data, int width, int height, int channels,
const int &flags) {
GetPixelFunction get_pixel = [&](int x, int y) -> unsigned long {
int index = (y * width + x) * channels;
return (((unsigned long)data[index]) << 16) |
(((unsigned long)data[index + 1]) << 8) |
(((unsigned long)data[index + 2]));
};
CharData lastCharData;
for (int y = 0; y <= height - 8; y += 8) {
for (int x = 0; x <= width - 4; x += 4) {
CharData charData =
flags & FLAG_NOOPT
? createCharData(get_pixel, x, y, 0x2584, 0x0000ffff)
: findCharData(get_pixel, x, y, flags);
if (x == 0 || charData.bgColor != lastCharData.bgColor)
printTermColor(flags | FLAG_BG, charData.bgColor[0],
charData.bgColor[1], charData.bgColor[2]);
if (x == 0 || charData.fgColor != lastCharData.fgColor)
printTermColor(flags | FLAG_FG, charData.fgColor[0],
charData.fgColor[1], charData.fgColor[2]);
printCodepoint(charData.codePoint);
lastCharData = charData;
}
std::cout << "\x1b[0m" << std::endl;
}
}
struct size {
size(unsigned int in_width, unsigned int in_height)
: width(in_width), height(in_height) {}
unsigned int width;
unsigned int height;
size scaled(double scale) { return size(width * scale, height * scale); }
size fitted_within(size container) {
double scale = std::min(container.width / static_cast<double>(width),
container.height / static_cast<double>(height));
return scaled(scale);
}
};
std::ostream &operator<<(std::ostream &stream, size sz) {
stream << sz.width << "x" << sz.height;
return stream;
}
static void render_and_print_image(unsigned char *data, int width, int height,
int channels) {
int max_width = 80;
int max_height = 24;
struct winsize w;
char err_buf[LINE_MAX];
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == -1) {
strerror_r(errno, err_buf, sizeof err_buf);
std::cerr << "Couldn't get terminal dimensions: " << err_buf << std::endl;
} else {
max_width = w.ws_col * 4;
max_height = w.ws_row * 8;
}
unsigned char *output_data = data;
int output_width = width;
int output_height = height;
std::vector<unsigned char> resized_data;
if (width > max_width || height > max_height) {
size new_size =
size(width, height).fitted_within(size(max_width, max_height));
output_width = new_size.width;
output_height = new_size.height;
resized_data.resize(output_width * output_height * channels);
stbir_resize_uint8_linear(data, width, height, 0, resized_data.data(),
output_width, output_height, 0,
(stbir_pixel_layout)channels);
output_data = resized_data.data();
}
int flags = 0;
printImage(output_data, output_width, output_height, channels, flags);
}
extern "C" {
void process_image(const char *path) {
int width, height, channels;
unsigned char *data = stbi_load(path, &width, &height, &channels, 3);
if (!data) {
std::cerr << "Error: '" << path << "' could not be loaded" << std::endl;
return;
}
// Force 3 channels for now
channels = 3;
render_and_print_image(data, width, height, channels);
stbi_image_free(data);
}
void process_image_mem(const unsigned char *buffer, size_t len) {
int width, height, channels;
unsigned char *data =
stbi_load_from_memory(buffer, (int)len, &width, &height, &channels, 3);
if (!data) {
std::cerr << "Error: Could not load image from memory" << std::endl;
return;
}
// Force 3 channels for now
channels = 3;
render_and_print_image(data, width, height, channels);
stbi_image_free(data);
}
}

17
image.h Normal file
View file

@ -0,0 +1,17 @@
#ifndef _IMAGE_H
#define _IMAGE_H
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
void process_image(const char *path);
void process_image_mem(const unsigned char *buffer, size_t len);
#ifdef __cplusplus
}
#endif
#endif /* _IMAGE_H */

37
meson.build Normal file
View file

@ -0,0 +1,37 @@
project(
'omdb',
'c', 'cpp',
meson_version : '>= 1.3.0',
version : '0.1',
default_options : ['warning_level=3', 'cpp_std=c++17', 'c_std=c11'],
)
curl = dependency('libcurl')
cpp_flags = []
c_flags = ['-DPOSIXLY_CORRECT', '-Wno-c99-extensions']
dependencies = [
curl
]
sources = [
'cJSON.c',
'fetch.c',
'image.cpp',
'omdb.c',
'tiv_lib.cpp'
]
add_project_arguments(cpp_flags, language : 'cpp')
add_project_arguments(c_flags, language : 'c')
exe = executable(
'omdb',
[sources],
dependencies : dependencies,
include_directories : include_directories('deps'),
install : true,
)
test('basic', exe)

219
omdb.c Normal file
View file

@ -0,0 +1,219 @@
#include <getopt.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <curl/curl.h>
#include "cJSON.h"
#include "fetch.h"
#include "image.h"
#define UNUSED(x) (void)x
#define OMDB_URL "http://www.omdbapi.com"
#define PROJECT_NAME "omdb"
/* Default to plain output. */
static enum _output_format {
OUTPUT_JSON,
OUTPUT_PLAIN,
OUTPUT_FANCY
} output_format = OUTPUT_PLAIN;
static struct option opts[] = {{"id", no_argument, NULL, 'i'},
{"json", no_argument, NULL, 'j'},
{"api-key", required_argument, NULL, 'k'},
{"search-type", required_argument, NULL, 't'},
{NULL, 0, NULL, 0}};
void print_usage(void) {
printf(
PROJECT_NAME
" [ikt] <TITLE-OR-ID>\n"
"\t-i/--id: Search by ID rather than title. (optional)\n"
"\t-j/--json: Output full JSON response.\n"
"\t-k/--api-key: OMDB API Key. (required)\n"
"\t-t/--search-type: Search type. (optional) [movie, series, episode]\n");
}
/* Set a URL query parameter. */
void set_param(CURLU *restrict url, const char *restrict key,
char *restrict value) {
CURLUcode err;
char param[2048];
snprintf(param, sizeof param, "%s=%s", key, value);
if ((err = curl_url_set(url, CURLUPART_QUERY, param,
CURLU_APPENDQUERY | CURLU_URLENCODE)) != CURLUE_OK) {
fprintf(stderr, "Error setting url param '%s': %s\n", key,
curl_url_strerror(err));
exit(EXIT_FAILURE);
}
}
/* Just print the url if needed for debugging. */
void print_url(CURLU *restrict url) {
char *url_string = NULL;
curl_url_get(url, CURLUPART_URL, &url_string, 0);
printf("%s\n", url_string);
curl_free(url_string);
}
/* Print entire JSON response as is. */
int print_title_json(const cJSON *const json) {
char *json_str = cJSON_Print(json);
if (!json_str) {
fputs("Couldn't output formatted JSON.\n", stderr);
return -1;
}
fputs(json_str, stdout);
putchar('\n');
free(json_str);
return 0;
}
/* Print some title information in plain text. */
int print_title_plain(const cJSON *json) {
cJSON *title = cJSON_GetObjectItem(json, "title");
cJSON *imdb_rating = cJSON_GetObjectItem(json, "imdbRating");
if (title == NULL || imdb_rating == NULL) {
printf("Could not find title or rating in response.\n");
return -1;
}
printf("Found: '%s' with rating of %s\n", title->valuestring,
imdb_rating->valuestring);
return 0;
}
int handle_response(struct response *restrict resp) {
cJSON *json = NULL;
if (strstr(resp->content_type, "application/json") == NULL) {
fprintf(stderr, "Unexpected response: %s\n", resp->content_type);
return -1;
}
json = cJSON_ParseWithLength(resp->data, resp->size);
if (json == NULL) {
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr != NULL) {
fprintf(stderr, "Error before: %s\n", error_ptr);
}
return -1;
}
switch (output_format) {
case OUTPUT_PLAIN:
print_title_plain(json);
break;
case OUTPUT_JSON:
print_title_json(json);
break;
case OUTPUT_FANCY:
/* Just print_title + image planned here for now */
print_title_plain(json);
break;
}
cJSON_free(json);
return 0;
}
int main(int argc, char **argv) {
int ch;
bool k_flag = false;
char search_mode[8] = "movie";
if (argc == 0) {
fputs("Argument required.\n", stderr);
print_usage();
exit(EXIT_FAILURE);
}
/* Just validate and add options directly to the request now. */
CURLU *url = curl_url();
CURLUcode ures;
if (url == NULL) {
fputs("Out of memory.\n", stderr);
exit(EXIT_FAILURE);
}
if ((ures = curl_url_set(url, CURLUPART_URL, OMDB_URL, 0)) != CURLUE_OK) {
fprintf(stderr, "Couldn't set url: %s\n", curl_url_strerror(ures));
exit(EXIT_FAILURE);
}
while ((ch = getopt_long(argc, argv, "hijk:t:", opts, NULL)) != -1) {
switch (ch) {
case 'i':
/* Lookup by ID */
set_param(url, "id", optarg);
break;
case 'j':
output_format = OUTPUT_JSON;
break;
case 'k':
/* API Key */
k_flag = true;
set_param(url, "apikey", optarg);
break;
case 't':
/* Type: movie, series, episode */
strlcpy(search_mode, optarg, sizeof search_mode);
break;
case '?':
fprintf(stderr, "Invalid option'%c'.\n", optopt);
exit(EXIT_FAILURE);
case 'h':
default:
print_usage();
exit(EXIT_FAILURE);
}
}
argc -= optind;
if (!k_flag) {
fputs("Missing required arguments.\n", stderr);
exit(EXIT_FAILURE);
}
printf("Looking for: %s\n", argv[optind]);
/* Set remaining params if any... */
set_param(url, "type", search_mode);
set_param(url, "r", "json");
/* Just testing... */
set_param(url, "t", argv[optind]);
/* Print it... */
print_url(url);
/*
* All of the call setup should be done via the URL so all that's left is to
* send the request and conditionally handle any response.
*/
struct fetch *f = fetch_init(); /* Check this */
struct response *resp = fetch(f, url);
handle_response(resp);
process_image("./test.jpg");
free(resp->data);
free(resp);
curl_url_cleanup(url);
fetch_cleanup(f);
return 0;
}

394
tiv_lib.cpp Normal file
View file

@ -0,0 +1,394 @@
/*
* Copyright (c) 2017-2023, Stefan Haustein, Aaron Liu
*
* This file is free software: you may copy, redistribute 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 file 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 <https://www.gnu.org/licenses/>.
*
* Alternatively, you may copy, redistribute and/or modify this file under
* the terms of the Apache License, version 2.0:
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "tiv_lib.h"
#include <algorithm>
#include <array>
#include <bitset>
#include <cmath>
#include <functional>
#include <map>
const int END_MARKER = 0;
// An interleaved map of 4x8 bit character bitmaps (each hex digit represents a
// row) to the corresponding Unicode character code point.
constexpr unsigned int BITMAPS[] = {
0x00000000, 0x00a0, 0,
// Block graphics
// 0xffff0000, 0x2580, 0, // upper 1/2; redundant with inverse lower 1/2
0x0000000f, 0x2581, 0, // lower 1/8
0x000000ff, 0x2582, 0, // lower 1/4
0x00000fff, 0x2583, 0,
0x0000ffff, 0x2584, 0, // lower 1/2
0x000fffff, 0x2585, 0,
0x00ffffff, 0x2586, 0, // lower 3/4
0x0fffffff, 0x2587, 0,
// 0xffffffff, 0x2588, // full; redundant with inverse space
0xeeeeeeee, 0x258a, 0, // left 3/4
0xcccccccc, 0x258c, 0, // left 1/2
0x88888888, 0x258e, 0, // left 1/4
0x0000cccc, 0x2596, 0, // quadrant lower left
0x00003333, 0x2597, 0, // quadrant lower right
0xcccc0000, 0x2598, 0, // quadrant upper left
// 0xccccffff, 0x2599, // 3/4 redundant with inverse 1/4
0xcccc3333, 0x259a, 0, // diagonal 1/2
// 0xffffcccc, 0x259b, // 3/4 redundant
// 0xffff3333, 0x259c, // 3/4 redundant
0x33330000, 0x259d, 0, // quadrant upper right
// 0x3333cccc, 0x259e, // 3/4 redundant
// 0x3333ffff, 0x259f, // 3/4 redundant
// Line drawing subset: no double lines, no complex light lines
0x000ff000, 0x2501, 0, // Heavy horizontal
0x66666666, 0x2503, 0, // Heavy vertical
0x00077666, 0x250f, 0, // Heavy down and right
0x000ee666, 0x2513, 0, // Heavy down and left
0x66677000, 0x2517, 0, // Heavy up and right
0x666ee000, 0x251b, 0, // Heavy up and left
0x66677666, 0x2523, 0, // Heavy vertical and right
0x666ee666, 0x252b, 0, // Heavy vertical and left
0x000ff666, 0x2533, 0, // Heavy down and horizontal
0x666ff000, 0x253b, 0, // Heavy up and horizontal
0x666ff666, 0x254b, 0, // Heavy cross
0x000cc000, 0x2578, 0, // Bold horizontal left
0x00066000, 0x2579, 0, // Bold horizontal up
0x00033000, 0x257a, 0, // Bold horizontal right
0x00066000, 0x257b, 0, // Bold horizontal down
0x06600660, 0x254f, 0, // Heavy double dash vertical
0x000f0000, 0x2500, 0, // Light horizontal
0x0000f000, 0x2500, 0, //
0x44444444, 0x2502, 0, // Light vertical
0x22222222, 0x2502, 0,
0x000e0000, 0x2574, 0, // light left
0x0000e000, 0x2574, 0, // light left
0x44440000, 0x2575, 0, // light up
0x22220000, 0x2575, 0, // light up
0x00030000, 0x2576, 0, // light right
0x00003000, 0x2576, 0, // light right
0x00004444, 0x2577, 0, // light down
0x00002222, 0x2577, 0, // light down
// Misc technical
0x44444444, 0x23a2, 0, // [ extension
0x22222222, 0x23a5, 0, // ] extension
0x0f000000, 0x23ba, 0, // Horizontal scanline 1
0x00f00000, 0x23bb, 0, // Horizontal scanline 3
0x00000f00, 0x23bc, 0, // Horizontal scanline 7
0x000000f0, 0x23bd, 0, // Horizontal scanline 9
// Geometrical shapes. Tricky because some of them are too wide.
// 0x00ffff00, 0x25fe, 0, // Black medium small square
0x00066000, 0x25aa, 0, // Black small square
// 0x11224488, 0x2571, 0, // diagonals
// 0x88442211, 0x2572, 0,
// 0x99666699, 0x2573, 0,
// 0x000137f0, 0x25e2, 0, // Triangles
// 0x0008cef0, 0x25e3, 0,
// 0x000fec80, 0x25e4, 0,
// 0x000f7310, 0x25e5, 0,
// Teletext / legacy graphics 3x2 block character codes.
// Using a 3-2-3 pattern consistently, perhaps we should create automatic
// variations....
0xccc00000, 0xfb00, FLAG_TELETEXT,
0x33300000, 0xfb01, FLAG_TELETEXT,
0xfff00000, 0xfb02, FLAG_TELETEXT,
0x000cc000, 0xfb03, FLAG_TELETEXT,
0xccccc000, 0xfb04, FLAG_TELETEXT,
0x333cc000, 0xfb05, FLAG_TELETEXT,
0xfffcc000, 0xfb06, FLAG_TELETEXT,
0x00033000, 0xfb07, FLAG_TELETEXT,
0xccc33000, 0xfb08, FLAG_TELETEXT,
0x33333000, 0xfb09, FLAG_TELETEXT,
0xfff33000, 0xfb0a, FLAG_TELETEXT,
0x000ff000, 0xfb0b, FLAG_TELETEXT,
0xcccff000, 0xfb0c, FLAG_TELETEXT,
0x333ff000, 0xfb0d, FLAG_TELETEXT,
0xfffff000, 0xfb0e, FLAG_TELETEXT,
0x00000ccc, 0xfb0f, FLAG_TELETEXT,
0xccc00ccc, 0xfb10, FLAG_TELETEXT,
0x33300ccc, 0xfb11, FLAG_TELETEXT,
0xfff00ccc, 0xfb12, FLAG_TELETEXT,
0x000ccccc, 0xfb13, FLAG_TELETEXT,
0x333ccccc, 0xfb14, FLAG_TELETEXT,
0xfffccccc, 0xfb15, FLAG_TELETEXT,
0x00033ccc, 0xfb16, FLAG_TELETEXT,
0xccc33ccc, 0xfb17, FLAG_TELETEXT,
0x33333ccc, 0xfb18, FLAG_TELETEXT,
0xfff33ccc, 0xfb19, FLAG_TELETEXT,
0x000ffccc, 0xfb1a, FLAG_TELETEXT,
0xcccffccc, 0xfb1b, FLAG_TELETEXT,
0x333ffccc, 0xfb1c, FLAG_TELETEXT,
0xfffffccc, 0xfb1d, FLAG_TELETEXT,
0x00000333, 0xfb1e, FLAG_TELETEXT,
0xccc00333, 0xfb1f, FLAG_TELETEXT,
0x33300333, 0x1b20, FLAG_TELETEXT,
0xfff00333, 0x1b21, FLAG_TELETEXT,
0x000cc333, 0x1b22, FLAG_TELETEXT,
0xccccc333, 0x1b23, FLAG_TELETEXT,
0x333cc333, 0x1b24, FLAG_TELETEXT,
0xfffcc333, 0x1b25, FLAG_TELETEXT,
0x00033333, 0x1b26, FLAG_TELETEXT,
0xccc33333, 0x1b27, FLAG_TELETEXT,
0xfff33333, 0x1b28, FLAG_TELETEXT,
0x000ff333, 0x1b29, FLAG_TELETEXT,
0xcccff333, 0x1b2a, FLAG_TELETEXT,
0x333ff333, 0x1b2b, FLAG_TELETEXT,
0xfffff333, 0x1b2c, FLAG_TELETEXT,
0x00000fff, 0x1b2d, FLAG_TELETEXT,
0xccc00fff, 0x1b2e, FLAG_TELETEXT,
0x33300fff, 0x1b2f, FLAG_TELETEXT,
0xfff00fff, 0x1b30, FLAG_TELETEXT,
0x000ccfff, 0x1b31, FLAG_TELETEXT,
0xcccccfff, 0x1b32, FLAG_TELETEXT,
0x333ccfff, 0x1b33, FLAG_TELETEXT,
0xfffccfff, 0x1b34, FLAG_TELETEXT,
0x00033fff, 0x1b35, FLAG_TELETEXT,
0xccc33fff, 0x1b36, FLAG_TELETEXT,
0x33333fff, 0x1b37, FLAG_TELETEXT,
0xfff33fff, 0x1b38, FLAG_TELETEXT,
0x000fffff, 0x1b39, FLAG_TELETEXT,
0xcccfffff, 0x1b3a, FLAG_TELETEXT,
0x333fffff, 0x1b3b, FLAG_TELETEXT,
0, END_MARKER, 0 // End marker
};
// The channel indices are 0, 1, 2 for R, G, B
unsigned char get_channel(unsigned long rgb, int index) {
return (unsigned char) ((rgb >> ((2 - index) * 8)) & 255);
}
CharData createCharData(GetPixelFunction get_pixel, int x0, int y0,
int codepoint, int pattern) {
CharData result;
result.codePoint = codepoint;
int fg_count = 0;
int bg_count = 0;
unsigned int mask = 0x80000000;
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 4; x++) {
int *avg;
if (pattern & mask) {
avg = result.fgColor.data();
fg_count++;
} else {
avg = result.bgColor.data();
bg_count++;
}
long rgb = get_pixel(x0 + x, y0 + y);
for (int i = 0; i < 3; i++) {
avg[i] += get_channel(rgb, i);
}
mask = mask >> 1;
}
}
// Calculate the average color value for each bucket
for (int i = 0; i < 3; i++) {
if (bg_count != 0) {
result.bgColor[i] /= bg_count;
}
if (fg_count != 0) {
result.fgColor[i] /= fg_count;
}
}
return result;
}
CharData findCharData(GetPixelFunction get_pixel, int x0, int y0,
const int &flags) {
int min[3] = {255, 255, 255};
int max[3] = {0};
std::map<long, int> count_per_color;
// Determine the minimum and maximum value for each color channel
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 4; x++) {
long color = 0;
long rgb = get_pixel(x0 + x, y0 + y);
for (int i = 0; i < 3; i++) {
int d = get_channel(rgb, i);
min[i] = std::min(min[i], d);
max[i] = std::max(max[i], d);
color = (color << 8) | d;
}
count_per_color[color]++;
}
}
std::multimap<int, long> color_per_count;
for (auto i = count_per_color.begin(); i != count_per_color.end(); ++i) {
color_per_count.insert(std::pair<int, long>(i->second, i->first));
}
auto iter = color_per_count.rbegin();
int count2 = iter->first;
long max_count_color_1 = iter->second;
long max_count_color_2 = max_count_color_1;
if ((++iter) != color_per_count.rend()) {
count2 += iter->first;
max_count_color_2 = iter->second;
}
unsigned int bits = 0;
bool direct = count2 > (8 * 4) / 2;
if (direct) {
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 4; x++) {
bits = bits << 1;
int d1 = 0;
int d2 = 0;
unsigned long rgb = get_pixel(x0 + x, y0 + y);
for (int i = 0; i < 3; i++) {
int shift = 16 - 8 * i;
int c1 = (max_count_color_1 >> shift) & 255;
int c2 = (max_count_color_2 >> shift) & 255;
int c = get_channel(rgb, i);
d1 += (c1 - c) * (c1 - c);
d2 += (c2 - c) * (c2 - c);
}
if (d1 > d2) {
bits |= 1;
}
}
}
} else {
// Determine the color channel with the greatest range.
int splitIndex = 0;
int bestSplit = 0;
for (int i = 0; i < 3; i++) {
if (max[i] - min[i] > bestSplit) {
bestSplit = max[i] - min[i];
splitIndex = i;
}
}
// We just split at the middle of the interval instead of computing the
// median.
int splitValue = min[splitIndex] + bestSplit / 2;
// Compute a bitmap using the given split and sum the color values for
// both buckets.
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 4; x++) {
bits = bits << 1;
if (get_channel(get_pixel(x0 + x, y0 + y),
splitIndex) > splitValue) {
bits |= 1;
}
}
}
}
// Find the best bitmap match by counting the bits that don't match,
// including the inverted bitmaps.
int best_diff = 8;
unsigned int best_pattern = 0x0000ffff;
int codepoint = 0x2584;
bool inverted = false;
for (int i = 0; BITMAPS[i + 1] != END_MARKER; i += 3) {
if ((BITMAPS[i + 2] & flags) != BITMAPS[i + 2]) {
continue;
}
unsigned int pattern = BITMAPS[i];
for (int j = 0; j < 2; j++) {
int diff = (std::bitset<32>(pattern ^ bits)).count();
if (diff < best_diff) {
best_pattern = BITMAPS[i]; // pattern might be inverted.
codepoint = BITMAPS[i + 1];
best_diff = diff;
inverted = best_pattern != pattern;
}
pattern = ~pattern;
}
}
if (direct) {
CharData result;
if (inverted) {
long tmp = max_count_color_1;
max_count_color_1 = max_count_color_2;
max_count_color_2 = tmp;
}
for (int i = 0; i < 3; i++) {
int shift = 16 - 8 * i;
result.fgColor[i] = (max_count_color_2 >> shift) & 255;
result.bgColor[i] = (max_count_color_1 >> shift) & 255;
result.codePoint = codepoint;
}
return result;
}
return createCharData(get_pixel, x0, y0, codepoint, best_pattern);
}
int clamp_byte(int value) {
return value < 0 ? 0 : (value > 255 ? 255 : value);
}
double sqr(double n) { return n * n; }
int best_index(int value, const int STEPS[], int count) {
int best_diff = std::abs(STEPS[0] - value);
int result = 0;
for (int i = 1; i < count; i++) {
int diff = std::abs(STEPS[i] - value);
if (diff < best_diff) {
result = i;
best_diff = diff;
}
}
return result;
}

101
tiv_lib.h Normal file
View file

@ -0,0 +1,101 @@
/*
* Copyright (c) 2017-2023, Stefan Haustein, Aaron Liu
*
* This file is free software: you may copy, redistribute 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 file 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 <https://www.gnu.org/licenses/>.
*
* Alternatively, you may copy, redistribute and/or modify this file under
* the terms of the Apache License, version 2.0:
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TIV_LIB_H
#define TIV_LIB_H
#include <array>
#include <functional>
// Implementation of flag representation for flags in the main() method
constexpr int FLAG_FG = 1;
constexpr int FLAG_BG = 2;
constexpr int FLAG_MODE_256 = 4; // Limit colors to 256-color mode
constexpr int FLAG_24BIT = 8; // 24-bit color mode
constexpr int FLAG_NOOPT = 16; // Only use the same half-block character
constexpr int FLAG_TELETEXT = 32; // Use teletext characters
// Color saturation value steps from 0 to 255
constexpr int COLOR_STEP_COUNT = 6;
constexpr int COLOR_STEPS[COLOR_STEP_COUNT] = {0, 0x5f, 0x87, 0xaf, 0xd7, 0xff};
// Grayscale saturation value steps from 0 to 255
constexpr int GRAYSCALE_STEP_COUNT = 24;
constexpr int GRAYSCALE_STEPS[GRAYSCALE_STEP_COUNT] = {
0x08, 0x12, 0x1c, 0x26, 0x30, 0x3a, 0x44, 0x4e, 0x58, 0x62, 0x6c, 0x76,
0x80, 0x8a, 0x94, 0x9e, 0xa8, 0xb2, 0xbc, 0xc6, 0xd0, 0xda, 0xe4, 0xee};
typedef std::function<unsigned long(int, int)> GetPixelFunction;
unsigned char get_channel(unsigned long rgb, int index);
int clamp_byte(int value);
int best_index(int value, const int STEPS[], int count);
double sqr(double n);
/**
* @brief Struct to represent a character to be drawn.
* @param fgColor RGB
* @param bgColor RGB
* @param codePoint The code point of the character to be drawn.
*/
struct CharData {
std::array<int, 3> fgColor = std::array<int, 3>{0, 0, 0};
std::array<int, 3> bgColor = std::array<int, 3>{0, 0, 0};
int codePoint;
};
// Return a CharData struct with the given code point and corresponding averag
// fg and bg colors.
CharData createCharData(GetPixelFunction get_pixel, int x0, int y0,
int codepoint, int pattern);
/**
* @brief Find the best character and colors
* for a 4x8 part of the image at the given position
*
* @param image
* @param x0
* @param y0
* @param flags
* @return CharData
*/
CharData findCharData(GetPixelFunction get_pixel, int x0, int y0,
const int &flags);
#endif