From eeb4e7dba09784293fd7677450ee0003d59fc300 Mon Sep 17 00:00:00 2001 From: Relintai Date: Mon, 18 Dec 2023 17:36:43 +0100 Subject: [PATCH] Added some of the utils from rcpp_fw. --- core/SCsub | 19 + core/containers/queue.h | 229 +++ core/containers/vector.h | 287 ++++ core/error_list.h | 93 ++ core/error_macros.h | 216 +++ core/image/image.cpp | 3005 ++++++++++++++++++++++++++++++++++ core/image/image.h | 310 ++++ core/int_types.h | 57 + core/log/logger.cpp | 205 +++ core/log/logger.h | 77 + core/memory.h | 14 + core/nodes/node.cpp | 122 ++ core/nodes/node.h | 52 + core/nodes/node_tree.cpp | 60 + core/nodes/node_tree.h | 35 + core/object.cpp | 11 + core/object.h | 93 ++ core/os/directory.cpp | 240 +++ core/os/directory.h | 55 + core/os/tinydir/COPYING | 26 + core/os/tinydir/README.md | 79 + core/os/tinydir/package.json | 17 + core/os/tinydir/tinydir.h | 831 ++++++++++ core/reference.cpp | 54 + core/reference.h | 201 +++ core/signal.cpp | 118 ++ core/signal.h | 172 ++ core/string.cpp | 2528 ++++++++++++++++++++++++++++ core/string.h | 280 ++++ core/typedefs.h | 354 ++++ core/variant.cpp | 624 +++++++ core/variant.h | 120 ++ 32 files changed, 10584 insertions(+) create mode 100644 core/SCsub create mode 100644 core/containers/queue.h create mode 100644 core/containers/vector.h create mode 100644 core/error_list.h create mode 100644 core/error_macros.h create mode 100644 core/image/image.cpp create mode 100644 core/image/image.h create mode 100644 core/int_types.h create mode 100644 core/log/logger.cpp create mode 100644 core/log/logger.h create mode 100644 core/memory.h create mode 100644 core/nodes/node.cpp create mode 100644 core/nodes/node.h create mode 100644 core/nodes/node_tree.cpp create mode 100644 core/nodes/node_tree.h create mode 100644 core/object.cpp create mode 100644 core/object.h create mode 100644 core/os/directory.cpp create mode 100644 core/os/directory.h create mode 100644 core/os/tinydir/COPYING create mode 100644 core/os/tinydir/README.md create mode 100644 core/os/tinydir/package.json create mode 100644 core/os/tinydir/tinydir.h create mode 100644 core/reference.cpp create mode 100644 core/reference.h create mode 100644 core/signal.cpp create mode 100644 core/signal.h create mode 100644 core/string.cpp create mode 100644 core/string.h create mode 100644 core/typedefs.h create mode 100644 core/variant.cpp create mode 100644 core/variant.h diff --git a/core/SCsub b/core/SCsub new file mode 100644 index 0000000..bcd7833 --- /dev/null +++ b/core/SCsub @@ -0,0 +1,19 @@ +#!/usr/bin/env python + +Import("env") + +env.core_sources = [] + +env.add_source_files(env.core_sources, "*.cpp") +env.add_source_files(env.core_sources, "./math/*.cpp") +env.add_source_files(env.core_sources, "./containers/*.cpp") +env.add_source_files(env.core_sources, "./log/*.cpp") +env.add_source_files(env.core_sources, "./os/*.cpp") +env.add_source_files(env.core_sources, "./image/*.cpp") +env.add_source_files(env.core_sources, "./threading/*.cpp") +env.add_source_files(env.core_sources, "./settings/*.cpp") +env.add_source_files(env.core_sources, "./nodes/*.cpp") + +# Build it all as a library +lib = env.add_library("core", env.core_sources) +env.Prepend(LIBS=[lib]) diff --git a/core/containers/queue.h b/core/containers/queue.h new file mode 100644 index 0000000..b7f82a0 --- /dev/null +++ b/core/containers/queue.h @@ -0,0 +1,229 @@ +#ifndef QUEUE_H +#define QUEUE_H + +#include "core/error_macros.h" + +#include "vector.h" + +template +class Queue +{ +public: + void enqueue(const T &job); + T dequeue(); + const T &peek() const; + + bool is_empty() const; + bool is_full() const; + + int size() const; + int capacity() const; + void ensure_capacity(const int capacity); + void resize(const int s); + + void pack(); + + Queue(); + Queue(int prealloc); + Queue(int prealloc, int grow_by); + ~Queue(); + +private: + T* _data; + int _head; + int _tail; + + int _capacity; + int _grow_size; +}; + +template +void Queue::pack() +{ + if (_head == _tail) + { + _head = 0; + _tail = 0; + return; + } + + if (_head == 0 && _tail == _capacity) + { + resize(_capacity + _grow_size); + return; + } + + int j = 0; + + for (int i = _head; i < _tail; ++i) + { + _data[j++] = _data[i]; + } + + _head = 0; + _tail = j; + +} + +template +void Queue::enqueue(const T &job) +{ + if (_tail == _capacity) + { + pack(); + } + + _data[_tail++] = job; +} + +template +T Queue::dequeue() +{ + CRASH_COND(is_empty()); + + return _data[_head++]; +} + +template +const T &Queue::peek() const +{ + CRASH_COND(is_empty()); + + return _data[_head]; +} + +template +bool Queue::is_empty() const +{ + return _head == _tail; +} + +template +bool Queue::is_full() const +{ + return (_head == 0) && (_tail == _capacity); +} + +template +int Queue::size() const +{ + return _tail - _head; +} + +template +int Queue::capacity() const +{ + return _capacity; +} + +template +void Queue::ensure_capacity(const int capacity) +{ + if (_capacity < capacity) + { + resize(capacity); + } +} + +template +void Queue::resize(const int s) +{ + if (s == 0 && _data) + { + delete[] _data; + + _data = nullptr; + + _capacity = s; + + return; + } + + if (!_data) + { + _data = new T[s]; + _capacity = s; + return; + } + + if (is_empty()) + { + delete[] _data; + + _data = new T[s]; + + _capacity = s; + + return; + } + + if (!is_full()) + { + pack(); + } + + T* nd = new T[s]; + + int to = s > _tail ? _tail : s; + + for (int i = 0; i < to; ++i) + { + nd[i] = _data[i]; + } + + delete[] _data; + _data = nd; + _capacity = s; +} + +template +Queue::Queue() +{ + _data = nullptr; + + _head = 0; + _tail = 0; + + _capacity = 20; + _grow_size = 10; + resize(_capacity); +} + +template +Queue::Queue(int prealloc) +{ + _data = nullptr; + + _head = 0; + _tail = 0; + + _capacity = prealloc; + _grow_size = 10; + resize(_capacity); +} + +template +Queue::Queue(int prealloc, int grow_by) +{ + _data = nullptr; + + _head = 0; + _tail = 0; + + _capacity = prealloc; + _grow_size = grow_by; + resize(_capacity); +} + +template +Queue::~Queue() +{ + if (_data) + { + delete[] _data; + + _data = nullptr; + } +} + +#endif diff --git a/core/containers/vector.h b/core/containers/vector.h new file mode 100644 index 0000000..5dc9ab0 --- /dev/null +++ b/core/containers/vector.h @@ -0,0 +1,287 @@ +#ifndef VECTOR_H +#define VECTOR_H + +#include "core/error_macros.h" + +template +class Vector { + +public: + void push_back(const T &element); + void pop_back(); + void remove(const int index); + void remove_keep_order(const int index); + void erase(const T &element); + void clear(); + bool empty() const; + T get(const int index); + const T &get(const int index) const; + void set(const int index, const T &value); + void swap(const int index1, const int index2); + + void sort_inc(); + void sort_dec(); + + int size() const; + int capacity() const; + void ensure_capacity(const int capacity); + void resize(const int s); + void append_array(const Vector &other); + int find(const T &val) const; + + T *dataw(); + const T *data() const; + + const T &operator[](const int index) const; + T &operator[](const int index); + + Vector &operator=(const Vector &other); + + Vector(); + Vector(int prealloc); + Vector(int prealloc, int grow_by); + Vector(const Vector &other); + ~Vector(); + +private: + T *_data; + int _actual_size; + int _size; + int _grow_by; +}; + +template +void Vector::push_back(const T &element) { + ensure_capacity(_size + 1); + + _data[_size++] = element; +} + +template +void Vector::pop_back() { + if (_size == 0) { + return; + } + + --_size; +} + +template +void Vector::remove(const int index) { + _data[index] = _data[_size - 1]; + + --_size; +} + +template +void Vector::remove_keep_order(const int index) { + --_size; + + for (int i = index; i < _size; ++i) { + _data[i] = _data[i + 1]; + } +} + +template +void Vector::erase(const T &element) { + int index = find(element); + + if (index != -1) { + remove(index); + } +} + +template +void Vector::clear() { + _size = 0; +} + +template +bool Vector::empty() const { + return _size == 0; +} + +template +T Vector::get(const int index) { + return _data[index]; +} + +template +const T &Vector::get(const int index) const { + return _data[index]; +} + +template +void Vector::set(const int index, const T &value) { + _data[index] = value; +} + +template +void Vector::swap(const int index1, const int index2) { + T e = _data[index1]; + _data[index1] = _data[index2]; + _data[index2] = e; +} + +template +void Vector::sort_inc() { + for (int i = 0; i < _size; ++i) { + for (int j = i + 1; j < _size; ++j) { + if (_data[j] < _data[i]) { + swap(i, j); + } + } + } +} + +template +void Vector::sort_dec() { + for (int i = 0; i < _size; ++i) { + for (int j = i + 1; j < _size; ++j) { + if (_data[j] > _data[i]) { + swap(i, j); + } + } + } +} + +template +int Vector::size() const { + return _size; +} + +template +int Vector::capacity() const { + return _actual_size; +} + +template +void Vector::ensure_capacity(const int capacity) { + if (capacity <= _actual_size) { + return; + } + + int tsize = capacity + _grow_by; + + T *nd = new T[tsize]; + + if (_data) { + for (int i = 0; i < _size; ++i) { + nd[i] = _data[i]; + } + + delete[] _data; + } + + _data = nd; + _actual_size = tsize; +} + +template +void Vector::resize(const int s) { + ensure_capacity(s); + + _size = s; +} + +template +void Vector::append_array(const Vector &other) { + ensure_capacity(_size + other._size); + + for (int i = 0; i < other._size; ++i) { + _data[_size++] = other._data[i]; + } +} + +template +int Vector::find(const T &val) const { + for (int i = 0; i < _size; ++i) { + if (_data[i] == val) { + return i; + } + } + + return -1; +} + +template +T *Vector::dataw() { + return _data; +} + +template +const T *Vector::data() const { + return _data; +} + +template +const T &Vector::operator[](const int index) const { + return _data[index]; +} + +template +T &Vector::operator[](const int index) { + return _data[index]; +} + +template +Vector &Vector::operator=(const Vector &other) { + resize(0); + ensure_capacity(other.size()); + append_array(other); + + return *this; +} + +template +Vector::Vector() { + _data = nullptr; + _actual_size = 0; + _size = 0; + _grow_by = 100; +} + +template +Vector::Vector(int prealloc) { + _data = nullptr; + _actual_size = 0; + _size = 0; + _grow_by = 100; + + ensure_capacity(prealloc); +} + +template +Vector::Vector(int prealloc, int grow_by) { + _data = nullptr; + _actual_size = 0; + _size = 0; + _grow_by = grow_by; + + ensure_capacity(prealloc); +} + +template +Vector::Vector(const Vector &other) { + _actual_size = other._actual_size; + _size = other._size; + _grow_by = other._grow_by; + + if (other._data) { + _data = new T[_actual_size]; + + for (int i = 0; i < _size; ++i) { + _data[i] = other._data[i]; + } + } +} + +template +Vector::~Vector() { + if (_data) { + delete[] _data; + _data = nullptr; + } +} + +#endif \ No newline at end of file diff --git a/core/error_list.h b/core/error_list.h new file mode 100644 index 0000000..777d787 --- /dev/null +++ b/core/error_list.h @@ -0,0 +1,93 @@ +/*************************************************************************/ +/* error_list.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 ERROR_LIST_H +#define ERROR_LIST_H + +/** Error List. Please never compare an error against FAILED + * Either do result != OK , or !result. This way, Error fail + * values can be more detailed in the future. + * + * This is a generic error list, mainly for organizing a language of returning errors. + */ + +enum Error { + OK, // (0) + FAILED, ///< Generic fail error + ERR_UNAVAILABLE, ///< What is requested is unsupported/unavailable + ERR_UNCONFIGURED, ///< The object being used hasn't been properly set up yet + ERR_UNAUTHORIZED, ///< Missing credentials for requested resource + ERR_PARAMETER_RANGE_ERROR, ///< Parameter given out of range (5) + ERR_OUT_OF_MEMORY, ///< Out of memory + ERR_FILE_NOT_FOUND, + ERR_FILE_BAD_DRIVE, + ERR_FILE_BAD_PATH, + ERR_FILE_NO_PERMISSION, // (10) + ERR_FILE_ALREADY_IN_USE, + ERR_FILE_CANT_OPEN, + ERR_FILE_CANT_WRITE, + ERR_FILE_CANT_READ, + ERR_FILE_UNRECOGNIZED, // (15) + ERR_FILE_CORRUPT, + ERR_FILE_MISSING_DEPENDENCIES, + ERR_FILE_EOF, + ERR_CANT_OPEN, ///< Can't open a resource/socket/file + ERR_CANT_CREATE, // (20) + ERR_QUERY_FAILED, + ERR_ALREADY_IN_USE, + ERR_LOCKED, ///< resource is locked + ERR_TIMEOUT, + ERR_CANT_CONNECT, // (25) + ERR_CANT_RESOLVE, + ERR_CONNECTION_ERROR, + ERR_CANT_ACQUIRE_RESOURCE, + ERR_CANT_FORK, + ERR_INVALID_DATA, ///< Data passed is invalid (30) + ERR_INVALID_PARAMETER, ///< Parameter passed is invalid + ERR_ALREADY_EXISTS, ///< When adding, item already exists + ERR_DOES_NOT_EXIST, ///< When retrieving/erasing, if item does not exist + ERR_DATABASE_CANT_READ, ///< database is full + ERR_DATABASE_CANT_WRITE, ///< database is full (35) + ERR_COMPILATION_FAILED, + ERR_METHOD_NOT_FOUND, + ERR_LINK_FAILED, + ERR_SCRIPT_FAILED, + ERR_CYCLIC_LINK, // (40) + ERR_INVALID_DECLARATION, + ERR_DUPLICATE_SYMBOL, + ERR_PARSE_ERROR, + ERR_BUSY, + ERR_SKIP, // (45) + ERR_HELP, ///< user requested help!! + ERR_BUG, ///< a bug in the software certainly happened, due to a double check failing or unexpected behavior. + ERR_PRINTER_ON_FIRE, /// the parallel port printer is engulfed in flames +}; + +#endif diff --git a/core/error_macros.h b/core/error_macros.h new file mode 100644 index 0000000..aa4369e --- /dev/null +++ b/core/error_macros.h @@ -0,0 +1,216 @@ +#ifndef ERROR_MACROS_H +#define ERROR_MACROS_H + +#include "core/log/logger.h" +#include "typedefs.h" + +// Based on Godot Engine's error_macros.h +// MIT License +// Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. +// Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). + +#ifdef _MSC_VER +#define GENERATE_TRAP \ + __debugbreak(); \ + /* Avoid warning about control paths */ \ + for (;;) { \ + } +#else +#define GENERATE_TRAP __builtin_trap(); +#endif + +// template methods for the variadic log macros. Add more as needed. +template +_FORCE_INLINE_ void _RLOG_MACRO_TEMPLATE_FUNC(STR str, A p0) { + str->append(p0); +} + +template +_FORCE_INLINE_ void _RLOG_MACRO_TEMPLATE_FUNC(STR str, A p0, B p1) { + str->append(p0); + str->push_back(' '); + str->append(p1); +} + +template +_FORCE_INLINE_ void _RLOG_MACRO_TEMPLATE_FUNC(STR str, A p0, B p1, C p2) { + str->append(p0); + str->push_back(' '); + str->append(p1); + str->push_back(' '); + str->append(p2); +} + +template +_FORCE_INLINE_ void _RLOG_MACRO_TEMPLATE_FUNC(STR str, A p0, B p1, C p2, D p3) { + str->append(p0); + str->push_back(' '); + str->append(p1); + str->push_back(' '); + str->append(p2); + str->push_back(' '); + str->append(p3); +} + +template +_FORCE_INLINE_ void _RLOG_MACRO_TEMPLATE_FUNC(STR str, A p0, B p1, C p2, D p3, E p4) { + str->append(p0); + str->push_back(' '); + str->append(p1); + str->push_back(' '); + str->append(p2); + str->push_back(' '); + str->append(p3); + str->push_back(' '); + str->append(p4); +} + +#define RPRINT_TRACE(str) \ + RLogger::print_trace(__FUNCTION__, __FILE__, __LINE__, str); + +#define RLOG_TRACE(...) \ + { \ + String *_rlogger_string_ptr = RLogger::get_trace_string_ptr(__FUNCTION__, __FILE__, __LINE__); \ + _RLOG_MACRO_TEMPLATE_FUNC(_rlogger_string_ptr, __VA_ARGS__); \ + RLogger::log_ret_ptr(_rlogger_string_ptr); \ + } + +#define RPRINT_MSG(str) \ + RLogger::print_message(__FUNCTION__, __FILE__, __LINE__, str); + +#define RLOG_MSG(...) \ + { \ + String *_rlogger_string_ptr = RLogger::get_message_string_ptr(__FUNCTION__, __FILE__, __LINE__); \ + _RLOG_MACRO_TEMPLATE_FUNC(_rlogger_string_ptr, __VA_ARGS__); \ + RLogger::log_ret_ptr(_rlogger_string_ptr); \ + } + +#define RPRINT_WARN(str) \ + RLogger::print_warning(__FUNCTION__, __FILE__, __LINE__, str); + +#define RLOG_WARN(...) \ + { \ + String *_rlogger_string_ptr = RLogger::get_warning_string_ptr(__FUNCTION__, __FILE__, __LINE__); \ + _RLOG_MACRO_TEMPLATE_FUNC(_rlogger_string_ptr, __VA_ARGS__); \ + RLogger::log_ret_ptr(_rlogger_string_ptr); \ + } + +#define RPRINT_ERR(str) \ + RLogger::print_error(__FUNCTION__, __FILE__, __LINE__, str); + +#define RLOG_ERR(...) \ + { \ + String *_rlogger_string_ptr = RLogger::get_error_string_ptr(__FUNCTION__, __FILE__, __LINE__); \ + _RLOG_MACRO_TEMPLATE_FUNC(_rlogger_string_ptr, __VA_ARGS__); \ + RLogger::log_ret_ptr(_rlogger_string_ptr); \ + } + +#define ERR_FAIL_MSG(msg) \ + RLogger::log_error(__FUNCTION__, __FILE__, __LINE__, msg); \ + return; + +#define ERR_FAIL_V_MSG(val, msg) \ + RLogger::log_error(__FUNCTION__, __FILE__, __LINE__, msg); \ + return val; + +#define ERR_FAIL_INDEX(index, size) \ + if ((index < 0) || (index >= size)) { \ + RLogger::log_index_error(__FUNCTION__, __FILE__, __LINE__, index, size, ""); \ + return; \ + } else \ + ((void)0) + +#define ERR_FAIL_INDEX_MSG(index, size, msg) \ + if ((index < 0) || (index >= size)) { \ + RLogger::log_index_error(__FUNCTION__, __FILE__, __LINE__, index, size, msg); \ + return; \ + } else \ + ((void)0) + +#define ERR_FAIL_INDEX_V(index, size, val) \ + if ((index < 0) || (index >= size)) { \ + RLogger::log_index_error(__FUNCTION__, __FILE__, __LINE__, index, size, ""); \ + return val; \ + } else \ + ((void)0) + +#define ERR_FAIL_INDEX_V_MSG(index, size, val, msg) \ + if ((index < 0) || (index >= size)) { \ + RLogger::log_index_error(__FUNCTION__, __FILE__, __LINE__, index, size, msg); \ + return val; \ + } else \ + ((void)0) + +#define ERR_FAIL_COND(cond) \ + if (cond) { \ + RLogger::log_error(__FUNCTION__, __FILE__, __LINE__, "ERR_FAIL_COND: \"" #cond "\" is true!"); \ + return; \ + } else \ + ((void)0) + +#define ERR_FAIL_COND_MSG(cond, msg) \ + if (cond) { \ + RLogger::log_error(__FUNCTION__, __FILE__, __LINE__, msg); \ + return; \ + } else \ + ((void)0) + +#define ERR_FAIL_COND_V(cond, val) \ + if (cond) { \ + RLogger::log_error(__FUNCTION__, __FILE__, __LINE__, "ERR_FAIL_COND: \"" #cond "\" is true!"); \ + return val; \ + } else \ + ((void)0) + +#define ERR_FAIL_COND_V_MSG(cond, val, msg) \ + if (cond) { \ + RLogger::log_error(__FUNCTION__, __FILE__, __LINE__, msg); \ + return val; \ + } else \ + ((void)0) + +#define ERR_CONTINUE(cond) \ + if (cond) { \ + RLogger::log_error(__FUNCTION__, __FILE__, __LINE__, "ERR_CONTINUE: \"" #cond "\" is true!"); \ + continue; \ + } else \ + ((void)0) + +#define ERR_CONTINUE_MSG(cond, msg) \ + if (cond) { \ + RLogger::log_error(__FUNCTION__, __FILE__, __LINE__, msg); \ + continue; \ + } else \ + ((void)0) + +#define ERR_CONTINUE_ACTION(cond, action) \ + if (cond) { \ + RLogger::log_error(__FUNCTION__, __FILE__, __LINE__, "ERR_CONTINUE: \"" #cond "\" is true!"); \ + action; \ + continue; \ + } else \ + ((void)0) + +#define ERR_CONTINUE_ACTION_MSG(cond, action, msg) \ + if (cond) { \ + RLogger::log_error(__FUNCTION__, __FILE__, __LINE__, msg); \ + action; \ + continue; \ + } else \ + ((void)0) + +#define CRASH_INDEX(index, size) \ + if ((index < 0) || (index >= size)) { \ + RLogger::log_index_error(__FUNCTION__, __FILE__, __LINE__, index, size, "CRASH!"); \ + GENERATE_TRAP \ + } else \ + ((void)0) + +#define CRASH_COND(cond) \ + if (cond) { \ + RLogger::log_error(__FUNCTION__, __FILE__, __LINE__, "CRASH_COND: \"" #cond "\" is true!"); \ + GENERATE_TRAP \ + } else \ + ((void)0) + +#endif diff --git a/core/image/image.cpp b/core/image/image.cpp new file mode 100644 index 0000000..7f38daa --- /dev/null +++ b/core/image/image.cpp @@ -0,0 +1,3005 @@ +/*************************************************************************/ +/* image.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "image.h" + +#include "core/error_macros.h" +#include "core/math/math.h" +#include "core/math/vector3.h" +#include "memory.h" +#include +#include +#include + +const char *Image::format_names[Image::FORMAT_MAX] = { + "Lum8", // luminance + "LumAlpha8", // luminance-alpha + "Red8", + "RedGreen", + "RGB8", + "RGBA8", + "RGBA4444", + "RGBA5551", + "RFloat", // float + "RGFloat", + "RGBFloat", + "RGBAFloat", + "RHalf", // half float + "RGHalf", + "RGBHalf", + "RGBAHalf", + "RGBE9995", + "DXT1 RGB8", // s3tc + "DXT3 RGBA8", + "DXT5 RGBA8", + "RGTC Red8", + "RGTC RedGreen8", + "BPTC_RGBA", + "BPTC_RGBF", + "BPTC_RGBFU", + "PVRTC2", // pvrtc + "PVRTC2A", + "PVRTC4", + "PVRTC4A", + "ETC", // etc1 + "ETC2_R11", // etc2 + "ETC2_R11S", // signed", NOT srgb. + "ETC2_RG11", + "ETC2_RG11S", + "ETC2_RGB8", + "ETC2_RGBA8", + "ETC2_RGB8A1", + +}; + +void Image::_put_pixelb(int p_x, int p_y, uint32_t p_pixel_size, uint8_t *p_data, const uint8_t *p_pixel) { + uint32_t ofs = (p_y * width + p_x) * p_pixel_size; + memcpy(p_data + ofs, p_pixel, p_pixel_size); +} + +void Image::_get_pixelb(int p_x, int p_y, uint32_t p_pixel_size, const uint8_t *p_data, uint8_t *p_pixel) { + uint32_t ofs = (p_y * width + p_x) * p_pixel_size; + memcpy(p_pixel, p_data + ofs, p_pixel_size); +} + +int Image::get_format_pixel_size(Format p_format) { + switch (p_format) { + case FORMAT_L8: + return 1; // luminance + case FORMAT_LA8: + return 2; // luminance-alpha + case FORMAT_R8: + return 1; + case FORMAT_RG8: + return 2; + case FORMAT_RGB8: + return 3; + case FORMAT_RGBA8: + return 4; + case FORMAT_RGBA4444: + return 2; + case FORMAT_RGBA5551: + return 2; + case FORMAT_RF: + return 4; // float + case FORMAT_RGF: + return 8; + case FORMAT_RGBF: + return 12; + case FORMAT_RGBAF: + return 16; + case FORMAT_RH: + return 2; // half float + case FORMAT_RGH: + return 4; + case FORMAT_RGBH: + return 6; + case FORMAT_RGBAH: + return 8; + case FORMAT_RGBE9995: + return 4; + case FORMAT_DXT1: + return 1; // s3tc bc1 + case FORMAT_DXT3: + return 1; // bc2 + case FORMAT_DXT5: + return 1; // bc3 + case FORMAT_RGTC_R: + return 1; // bc4 + case FORMAT_RGTC_RG: + return 1; // bc5 + case FORMAT_BPTC_RGBA: + return 1; // btpc bc6h + case FORMAT_BPTC_RGBF: + return 1; // float / + case FORMAT_BPTC_RGBFU: + return 1; // unsigned float + case FORMAT_PVRTC2: + return 1; // pvrtc + case FORMAT_PVRTC2A: + return 1; + case FORMAT_PVRTC4: + return 1; + case FORMAT_PVRTC4A: + return 1; + case FORMAT_ETC: + return 1; // etc1 + case FORMAT_ETC2_R11: + return 1; // etc2 + case FORMAT_ETC2_R11S: + return 1; // signed: return 1; NOT srgb. + case FORMAT_ETC2_RG11: + return 1; + case FORMAT_ETC2_RG11S: + return 1; + case FORMAT_ETC2_RGB8: + return 1; + case FORMAT_ETC2_RGBA8: + return 1; + case FORMAT_ETC2_RGB8A1: + return 1; + case FORMAT_MAX: { + } + } + return 0; +} + +void Image::get_format_min_pixel_size(Format p_format, int &r_w, int &r_h) { + switch (p_format) { + case FORMAT_DXT1: // s3tc bc1 + case FORMAT_DXT3: // bc2 + case FORMAT_DXT5: // bc3 + case FORMAT_RGTC_R: // bc4 + case FORMAT_RGTC_RG: { // bc5 case case FORMAT_DXT1: + + r_w = 4; + r_h = 4; + } break; + case FORMAT_PVRTC2: + case FORMAT_PVRTC2A: { + r_w = 16; + r_h = 8; + } break; + case FORMAT_PVRTC4A: + case FORMAT_PVRTC4: { + r_w = 8; + r_h = 8; + } break; + case FORMAT_ETC: { + r_w = 4; + r_h = 4; + } break; + case FORMAT_BPTC_RGBA: + case FORMAT_BPTC_RGBF: + case FORMAT_BPTC_RGBFU: { + r_w = 4; + r_h = 4; + } break; + case FORMAT_ETC2_R11: // etc2 + case FORMAT_ETC2_R11S: // signed: NOT srgb. + case FORMAT_ETC2_RG11: + case FORMAT_ETC2_RG11S: + case FORMAT_ETC2_RGB8: + case FORMAT_ETC2_RGBA8: + case FORMAT_ETC2_RGB8A1: { + r_w = 4; + r_h = 4; + + } break; + + default: { + r_w = 1; + r_h = 1; + } break; + } +} + +int Image::get_format_pixel_rshift(Format p_format) { + if (p_format == FORMAT_DXT1 || p_format == FORMAT_RGTC_R || p_format == FORMAT_PVRTC4 || p_format == FORMAT_PVRTC4A || p_format == FORMAT_ETC || p_format == FORMAT_ETC2_R11 || p_format == FORMAT_ETC2_R11S || p_format == FORMAT_ETC2_RGB8 || p_format == FORMAT_ETC2_RGB8A1) { + return 1; + } else if (p_format == FORMAT_PVRTC2 || p_format == FORMAT_PVRTC2A) { + return 2; + } else { + return 0; + } +} + +int Image::get_format_block_size(Format p_format) { + switch (p_format) { + case FORMAT_DXT1: // s3tc bc1 + case FORMAT_DXT3: // bc2 + case FORMAT_DXT5: // bc3 + case FORMAT_RGTC_R: // bc4 + case FORMAT_RGTC_RG: { // bc5 case case FORMAT_DXT1: + + return 4; + } + case FORMAT_PVRTC2: + case FORMAT_PVRTC2A: { + return 4; + } + case FORMAT_PVRTC4A: + case FORMAT_PVRTC4: { + return 4; + } + case FORMAT_ETC: { + return 4; + } + case FORMAT_BPTC_RGBA: + case FORMAT_BPTC_RGBF: + case FORMAT_BPTC_RGBFU: { + return 4; + } + case FORMAT_ETC2_R11: // etc2 + case FORMAT_ETC2_R11S: // signed: NOT srgb. + case FORMAT_ETC2_RG11: + case FORMAT_ETC2_RG11S: + case FORMAT_ETC2_RGB8: + case FORMAT_ETC2_RGBA8: + case FORMAT_ETC2_RGB8A1: { + return 4; + } + default: { + } + } + + return 1; +} + +void Image::_get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_width, int &r_height) const { + int w = width; + int h = height; + int ofs = 0; + + int pixel_size = get_format_pixel_size(format); + int pixel_rshift = get_format_pixel_rshift(format); + int block = get_format_block_size(format); + int minw, minh; + get_format_min_pixel_size(format, minw, minh); + + for (int i = 0; i < p_mipmap; i++) { + int bw = w % block != 0 ? w + (block - w % block) : w; + int bh = h % block != 0 ? h + (block - h % block) : h; + + int s = bw * bh; + + s *= pixel_size; + s >>= pixel_rshift; + ofs += s; + w = MAX(minw, w >> 1); + h = MAX(minh, h >> 1); + } + + r_offset = ofs; + r_width = w; + r_height = h; +} + +int Image::get_mipmap_offset(int p_mipmap) const { + ERR_FAIL_INDEX_V(p_mipmap, get_mipmap_count() + 1, -1); + + int ofs, w, h; + _get_mipmap_offset_and_size(p_mipmap, ofs, w, h); + return ofs; +} + +void Image::get_mipmap_offset_and_size(int p_mipmap, int &r_ofs, int &r_size) const { + int ofs, w, h; + _get_mipmap_offset_and_size(p_mipmap, ofs, w, h); + int ofs2; + _get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w, h); + r_ofs = ofs; + r_size = ofs2 - ofs; +} + +void Image::get_mipmap_offset_size_and_dimensions(int p_mipmap, int &r_ofs, int &r_size, int &w, int &h) const { + int ofs; + _get_mipmap_offset_and_size(p_mipmap, ofs, w, h); + int ofs2, w2, h2; + _get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w2, h2); + r_ofs = ofs; + r_size = ofs2 - ofs; +} + +int Image::get_width() const { + return width; +} + +int Image::get_height() const { + return height; +} + +Vector2 Image::get_size() const { + return Vector2(width, height); +} + +bool Image::has_mipmaps() const { + return mipmaps; +} + +int Image::get_mipmap_count() const { + if (mipmaps) { + return get_image_required_mipmaps(width, height, format); + } else { + return 0; + } +} + +// using template generates perfectly optimized code due to constant expression reduction and unused variable removal present in all compilers +template +static void _convert(int p_width, int p_height, const uint8_t *p_src, uint8_t *p_dst) { + uint32_t max_bytes = MAX(read_bytes, write_bytes); + + for (int y = 0; y < p_height; y++) { + for (int x = 0; x < p_width; x++) { + const uint8_t *rofs = &p_src[((y * p_width) + x) * (read_bytes + (read_alpha ? 1 : 0))]; + uint8_t *wofs = &p_dst[((y * p_width) + x) * (write_bytes + (write_alpha ? 1 : 0))]; + + uint8_t rgba[4]; + + if (read_gray) { + rgba[0] = rofs[0]; + rgba[1] = rofs[0]; + rgba[2] = rofs[0]; + } else { + for (uint32_t i = 0; i < max_bytes; i++) { + rgba[i] = (i < read_bytes) ? rofs[i] : 0; + } + } + + if (read_alpha || write_alpha) { + rgba[3] = read_alpha ? rofs[read_bytes] : 255; + } + + if (write_gray) { + // TODO: not correct grayscale, should use fixed point version of actual weights + wofs[0] = uint8_t((uint16_t(rofs[0]) + uint16_t(rofs[1]) + uint16_t(rofs[2])) / 3); + } else { + for (uint32_t i = 0; i < write_bytes; i++) { + wofs[i] = rgba[i]; + } + } + + if (write_alpha) { + wofs[write_bytes] = rgba[3]; + } + } + } +} + +void Image::convert(Format p_new_format) { + if (data.size() == 0) { + return; + } + + if (p_new_format == format) { + return; + } + + ERR_FAIL_COND_MSG(write_lock, "Cannot convert image when it is locked."); + + if (format > FORMAT_RGBE9995 || p_new_format > FORMAT_RGBE9995) { + ERR_FAIL_MSG("Cannot convert to <-> from compressed formats. Use compress() and decompress() instead."); + + } else if (format > FORMAT_RGBA8 || p_new_format > FORMAT_RGBA8) { + // use put/set pixel which is slower but works with non byte formats + Image new_img(width, height, false, p_new_format); + lock(); + new_img.lock(); + + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + new_img.set_pixel(i, j, get_pixel(i, j)); + } + } + + unlock(); + new_img.unlock(); + + if (has_mipmaps()) { + new_img.generate_mipmaps(); + } + + _copy_internals_from(new_img); + + return; + } + + Image new_img(width, height, false, p_new_format); + + write_lock = true; + const uint8_t *rptr = data.data(); + uint8_t *wptr = new_img.data.dataw(); + + int conversion_type = format | p_new_format << 8; + + switch (conversion_type) { + case FORMAT_L8 | (FORMAT_LA8 << 8): + _convert<1, false, 1, true, true, true>(width, height, rptr, wptr); + break; + case FORMAT_L8 | (FORMAT_R8 << 8): + _convert<1, false, 1, false, true, false>(width, height, rptr, wptr); + break; + case FORMAT_L8 | (FORMAT_RG8 << 8): + _convert<1, false, 2, false, true, false>(width, height, rptr, wptr); + break; + case FORMAT_L8 | (FORMAT_RGB8 << 8): + _convert<1, false, 3, false, true, false>(width, height, rptr, wptr); + break; + case FORMAT_L8 | (FORMAT_RGBA8 << 8): + _convert<1, false, 3, true, true, false>(width, height, rptr, wptr); + break; + case FORMAT_LA8 | (FORMAT_L8 << 8): + _convert<1, true, 1, false, true, true>(width, height, rptr, wptr); + break; + case FORMAT_LA8 | (FORMAT_R8 << 8): + _convert<1, true, 1, false, true, false>(width, height, rptr, wptr); + break; + case FORMAT_LA8 | (FORMAT_RG8 << 8): + _convert<1, true, 2, false, true, false>(width, height, rptr, wptr); + break; + case FORMAT_LA8 | (FORMAT_RGB8 << 8): + _convert<1, true, 3, false, true, false>(width, height, rptr, wptr); + break; + case FORMAT_LA8 | (FORMAT_RGBA8 << 8): + _convert<1, true, 3, true, true, false>(width, height, rptr, wptr); + break; + case FORMAT_R8 | (FORMAT_L8 << 8): + _convert<1, false, 1, false, false, true>(width, height, rptr, wptr); + break; + case FORMAT_R8 | (FORMAT_LA8 << 8): + _convert<1, false, 1, true, false, true>(width, height, rptr, wptr); + break; + case FORMAT_R8 | (FORMAT_RG8 << 8): + _convert<1, false, 2, false, false, false>(width, height, rptr, wptr); + break; + case FORMAT_R8 | (FORMAT_RGB8 << 8): + _convert<1, false, 3, false, false, false>(width, height, rptr, wptr); + break; + case FORMAT_R8 | (FORMAT_RGBA8 << 8): + _convert<1, false, 3, true, false, false>(width, height, rptr, wptr); + break; + case FORMAT_RG8 | (FORMAT_L8 << 8): + _convert<2, false, 1, false, false, true>(width, height, rptr, wptr); + break; + case FORMAT_RG8 | (FORMAT_LA8 << 8): + _convert<2, false, 1, true, false, true>(width, height, rptr, wptr); + break; + case FORMAT_RG8 | (FORMAT_R8 << 8): + _convert<2, false, 1, false, false, false>(width, height, rptr, wptr); + break; + case FORMAT_RG8 | (FORMAT_RGB8 << 8): + _convert<2, false, 3, false, false, false>(width, height, rptr, wptr); + break; + case FORMAT_RG8 | (FORMAT_RGBA8 << 8): + _convert<2, false, 3, true, false, false>(width, height, rptr, wptr); + break; + case FORMAT_RGB8 | (FORMAT_L8 << 8): + _convert<3, false, 1, false, false, true>(width, height, rptr, wptr); + break; + case FORMAT_RGB8 | (FORMAT_LA8 << 8): + _convert<3, false, 1, true, false, true>(width, height, rptr, wptr); + break; + case FORMAT_RGB8 | (FORMAT_R8 << 8): + _convert<3, false, 1, false, false, false>(width, height, rptr, wptr); + break; + case FORMAT_RGB8 | (FORMAT_RG8 << 8): + _convert<3, false, 2, false, false, false>(width, height, rptr, wptr); + break; + case FORMAT_RGB8 | (FORMAT_RGBA8 << 8): + _convert<3, false, 3, true, false, false>(width, height, rptr, wptr); + break; + case FORMAT_RGBA8 | (FORMAT_L8 << 8): + _convert<3, true, 1, false, false, true>(width, height, rptr, wptr); + break; + case FORMAT_RGBA8 | (FORMAT_LA8 << 8): + _convert<3, true, 1, true, false, true>(width, height, rptr, wptr); + break; + case FORMAT_RGBA8 | (FORMAT_R8 << 8): + _convert<3, true, 1, false, false, false>(width, height, rptr, wptr); + break; + case FORMAT_RGBA8 | (FORMAT_RG8 << 8): + _convert<3, true, 2, false, false, false>(width, height, rptr, wptr); + break; + case FORMAT_RGBA8 | (FORMAT_RGB8 << 8): + _convert<3, true, 3, false, false, false>(width, height, rptr, wptr); + break; + } + + write_lock = false; + + bool gen_mipmaps = mipmaps; + + _copy_internals_from(new_img); + + if (gen_mipmaps) { + generate_mipmaps(); + } +} + +Image::Format Image::get_format() const { + return format; +} + +static double _bicubic_interp_kernel(double x) { + x = ABS(x); + + double bc = 0; + + if (x <= 1) { + bc = (1.5 * x - 2.5) * x * x + 1; + } else if (x < 2) { + bc = ((-0.5 * x + 2.5) * x - 4) * x + 2; + } + + return bc; +} + +template +static void _scale_cubic(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) { + // get source image size + int width = p_src_width; + int height = p_src_height; + double xfac = (double)width / p_dst_width; + double yfac = (double)height / p_dst_height; + // coordinates of source points and coefficients + double ox, oy, dx, dy, k1, k2; + int ox1, oy1, ox2, oy2; + // destination pixel values + // width and height decreased by 1 + int ymax = height - 1; + int xmax = width - 1; + // temporary pointer + + for (uint32_t y = 0; y < p_dst_height; y++) { + // Y coordinates + oy = (double)y * yfac - 0.5f; + oy1 = (int)oy; + dy = oy - (double)oy1; + + for (uint32_t x = 0; x < p_dst_width; x++) { + // X coordinates + ox = (double)x * xfac - 0.5f; + ox1 = (int)ox; + dx = ox - (double)ox1; + + // initial pixel value + + T *__restrict dst = ((T *)p_dst) + (y * p_dst_width + x) * CC; + + double color[CC]; + for (int i = 0; i < CC; i++) { + color[i] = 0; + } + + for (int n = -1; n < 3; n++) { + // get Y coefficient + k1 = _bicubic_interp_kernel(dy - (double)n); + + oy2 = oy1 + n; + if (oy2 < 0) { + oy2 = 0; + } + if (oy2 > ymax) { + oy2 = ymax; + } + + for (int m = -1; m < 3; m++) { + // get X coefficient + k2 = k1 * _bicubic_interp_kernel((double)m - dx); + + ox2 = ox1 + m; + if (ox2 < 0) { + ox2 = 0; + } + if (ox2 > xmax) { + ox2 = xmax; + } + + // get pixel of original image + const T *__restrict p = ((T *)p_src) + (oy2 * p_src_width + ox2) * CC; + + for (int i = 0; i < CC; i++) { + if (sizeof(T) == 2) { // half float + color[i] = Math::half_to_float(p[i]); + } else { + color[i] += p[i] * k2; + } + } + } + } + + for (int i = 0; i < CC; i++) { + if (sizeof(T) == 1) { // byte + dst[i] = CLAMP(Math::fast_ftoi(color[i]), 0, 255); + } else if (sizeof(T) == 2) { // half float + dst[i] = Math::make_half_float(color[i]); + } else { + dst[i] = color[i]; + } + } + } + } +} + +template +static void _scale_bilinear(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) { + enum { + FRAC_BITS = 8, + FRAC_LEN = (1 << FRAC_BITS), + FRAC_HALF = (FRAC_LEN >> 1), + FRAC_MASK = FRAC_LEN - 1 + }; + + for (uint32_t i = 0; i < p_dst_height; i++) { + // Add 0.5 in order to interpolate based on pixel center + uint32_t src_yofs_up_fp = (i + 0.5) * p_src_height * FRAC_LEN / p_dst_height; + // Calculate nearest src pixel center above current, and truncate to get y index + uint32_t src_yofs_up = src_yofs_up_fp >= FRAC_HALF ? (src_yofs_up_fp - FRAC_HALF) >> FRAC_BITS : 0; + uint32_t src_yofs_down = (src_yofs_up_fp + FRAC_HALF) >> FRAC_BITS; + if (src_yofs_down >= p_src_height) { + src_yofs_down = p_src_height - 1; + } + // Calculate distance to pixel center of src_yofs_up + uint32_t src_yofs_frac = src_yofs_up_fp & FRAC_MASK; + src_yofs_frac = src_yofs_frac >= FRAC_HALF ? src_yofs_frac - FRAC_HALF : src_yofs_frac + FRAC_HALF; + + uint32_t y_ofs_up = src_yofs_up * p_src_width * CC; + uint32_t y_ofs_down = src_yofs_down * p_src_width * CC; + + for (uint32_t j = 0; j < p_dst_width; j++) { + uint32_t src_xofs_left_fp = (j + 0.5) * p_src_width * FRAC_LEN / p_dst_width; + uint32_t src_xofs_left = src_xofs_left_fp >= FRAC_HALF ? (src_xofs_left_fp - FRAC_HALF) >> FRAC_BITS : 0; + uint32_t src_xofs_right = (src_xofs_left_fp + FRAC_HALF) >> FRAC_BITS; + if (src_xofs_right >= p_src_width) { + src_xofs_right = p_src_width - 1; + } + uint32_t src_xofs_frac = src_xofs_left_fp & FRAC_MASK; + src_xofs_frac = src_xofs_frac >= FRAC_HALF ? src_xofs_frac - FRAC_HALF : src_xofs_frac + FRAC_HALF; + + src_xofs_left *= CC; + src_xofs_right *= CC; + + for (uint32_t l = 0; l < CC; l++) { + if (sizeof(T) == 1) { // uint8 + uint32_t p00 = p_src[y_ofs_up + src_xofs_left + l] << FRAC_BITS; + uint32_t p10 = p_src[y_ofs_up + src_xofs_right + l] << FRAC_BITS; + uint32_t p01 = p_src[y_ofs_down + src_xofs_left + l] << FRAC_BITS; + uint32_t p11 = p_src[y_ofs_down + src_xofs_right + l] << FRAC_BITS; + + uint32_t interp_up = p00 + (((p10 - p00) * src_xofs_frac) >> FRAC_BITS); + uint32_t interp_down = p01 + (((p11 - p01) * src_xofs_frac) >> FRAC_BITS); + uint32_t interp = interp_up + (((interp_down - interp_up) * src_yofs_frac) >> FRAC_BITS); + interp >>= FRAC_BITS; + p_dst[i * p_dst_width * CC + j * CC + l] = interp; + } else if (sizeof(T) == 2) { // half float + + float xofs_frac = float(src_xofs_frac) / (1 << FRAC_BITS); + float yofs_frac = float(src_yofs_frac) / (1 << FRAC_BITS); + const T *src = ((const T *)p_src); + T *dst = ((T *)p_dst); + + float p00 = Math::half_to_float(src[y_ofs_up + src_xofs_left + l]); + float p10 = Math::half_to_float(src[y_ofs_up + src_xofs_right + l]); + float p01 = Math::half_to_float(src[y_ofs_down + src_xofs_left + l]); + float p11 = Math::half_to_float(src[y_ofs_down + src_xofs_right + l]); + + float interp_up = p00 + (p10 - p00) * xofs_frac; + float interp_down = p01 + (p11 - p01) * xofs_frac; + float interp = interp_up + ((interp_down - interp_up) * yofs_frac); + + dst[i * p_dst_width * CC + j * CC + l] = Math::make_half_float(interp); + } else if (sizeof(T) == 4) { // float + + float xofs_frac = float(src_xofs_frac) / (1 << FRAC_BITS); + float yofs_frac = float(src_yofs_frac) / (1 << FRAC_BITS); + const T *src = ((const T *)p_src); + T *dst = ((T *)p_dst); + + float p00 = src[y_ofs_up + src_xofs_left + l]; + float p10 = src[y_ofs_up + src_xofs_right + l]; + float p01 = src[y_ofs_down + src_xofs_left + l]; + float p11 = src[y_ofs_down + src_xofs_right + l]; + + float interp_up = p00 + (p10 - p00) * xofs_frac; + float interp_down = p01 + (p11 - p01) * xofs_frac; + float interp = interp_up + ((interp_down - interp_up) * yofs_frac); + + dst[i * p_dst_width * CC + j * CC + l] = interp; + } + } + } + } +} + +template +static void _scale_nearest(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) { + for (uint32_t i = 0; i < p_dst_height; i++) { + uint32_t src_yofs = i * p_src_height / p_dst_height; + uint32_t y_ofs = src_yofs * p_src_width * CC; + + for (uint32_t j = 0; j < p_dst_width; j++) { + uint32_t src_xofs = j * p_src_width / p_dst_width; + src_xofs *= CC; + + for (uint32_t l = 0; l < CC; l++) { + const T *src = ((const T *)p_src); + T *dst = ((T *)p_dst); + + T p = src[y_ofs + src_xofs + l]; + dst[i * p_dst_width * CC + j * CC + l] = p; + } + } + } +} + +#define LANCZOS_TYPE 3 + +static float _lanczos(float p_x) { + return Math::abs(p_x) >= LANCZOS_TYPE ? 0 : Math::sincn(p_x) * Math::sincn(p_x / LANCZOS_TYPE); +} + +template +static void _scale_lanczos(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) { + int32_t src_width = p_src_width; + int32_t src_height = p_src_height; + int32_t dst_height = p_dst_height; + int32_t dst_width = p_dst_width; + + uint32_t buffer_size = src_height * dst_width * CC; + float *buffer = memnew_arr(float, buffer_size); // Store the first pass in a buffer + + { // FIRST PASS (horizontal) + + float x_scale = float(src_width) / float(dst_width); + + float scale_factor = MAX(x_scale, 1); // A larger kernel is required only when downscaling + int32_t half_kernel = LANCZOS_TYPE * scale_factor; + + float *kernel = memnew_arr(float, half_kernel * 2); + + for (int32_t buffer_x = 0; buffer_x < dst_width; buffer_x++) { + // The corresponding point on the source image + float src_x = (buffer_x + 0.5f) * x_scale; // Offset by 0.5 so it uses the pixel's center + int32_t start_x = MAX(0, int32_t(src_x) - half_kernel + 1); + int32_t end_x = MIN(src_width - 1, int32_t(src_x) + half_kernel); + + // Create the kernel used by all the pixels of the column + for (int32_t target_x = start_x; target_x <= end_x; target_x++) { + kernel[target_x - start_x] = _lanczos((target_x + 0.5f - src_x) / scale_factor); + } + + for (int32_t buffer_y = 0; buffer_y < src_height; buffer_y++) { + float pixel[CC] = { 0 }; + float weight = 0; + + for (int32_t target_x = start_x; target_x <= end_x; target_x++) { + float lanczos_val = kernel[target_x - start_x]; + weight += lanczos_val; + + const T *__restrict src_data = ((const T *)p_src) + (buffer_y * src_width + target_x) * CC; + + for (uint32_t i = 0; i < CC; i++) { + if (sizeof(T) == 2) { // half float + pixel[i] += Math::half_to_float(src_data[i]) * lanczos_val; + } else { + pixel[i] += src_data[i] * lanczos_val; + } + } + } + + float *dst_data = ((float *)buffer) + (buffer_y * dst_width + buffer_x) * CC; + + for (uint32_t i = 0; i < CC; i++) { + dst_data[i] = pixel[i] / weight; // Normalize the sum of all the samples + } + } + } + + memdelete_arr(kernel); + } // End of first pass + + { // SECOND PASS (vertical + result) + + float y_scale = float(src_height) / float(dst_height); + + float scale_factor = MAX(y_scale, 1); + int32_t half_kernel = LANCZOS_TYPE * scale_factor; + + float *kernel = memnew_arr(float, half_kernel * 2); + + for (int32_t dst_y = 0; dst_y < dst_height; dst_y++) { + float buffer_y = (dst_y + 0.5f) * y_scale; + int32_t start_y = MAX(0, int32_t(buffer_y) - half_kernel + 1); + int32_t end_y = MIN(src_height - 1, int32_t(buffer_y) + half_kernel); + + for (int32_t target_y = start_y; target_y <= end_y; target_y++) { + kernel[target_y - start_y] = _lanczos((target_y + 0.5f - buffer_y) / scale_factor); + } + + for (int32_t dst_x = 0; dst_x < dst_width; dst_x++) { + float pixel[CC] = { 0 }; + float weight = 0; + + for (int32_t target_y = start_y; target_y <= end_y; target_y++) { + float lanczos_val = kernel[target_y - start_y]; + weight += lanczos_val; + + float *buffer_data = ((float *)buffer) + (target_y * dst_width + dst_x) * CC; + + for (uint32_t i = 0; i < CC; i++) { + pixel[i] += buffer_data[i] * lanczos_val; + } + } + + T *dst_data = ((T *)p_dst) + (dst_y * dst_width + dst_x) * CC; + + for (uint32_t i = 0; i < CC; i++) { + pixel[i] /= weight; + + if (sizeof(T) == 1) { // byte + dst_data[i] = CLAMP(Math::fast_ftoi(pixel[i]), 0, 255); + } else if (sizeof(T) == 2) { // half float + dst_data[i] = Math::make_half_float(pixel[i]); + } else { // float + dst_data[i] = pixel[i]; + } + } + } + } + + memdelete_arr(kernel); + } // End of second pass + + memdelete_arr(buffer); +} + +static void _overlay(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, float p_alpha, uint32_t p_width, uint32_t p_height, uint32_t p_pixel_size) { + uint16_t alpha = MIN((uint16_t)(p_alpha * 256.0f), 256); + + for (uint32_t i = 0; i < p_width * p_height * p_pixel_size; i++) { + p_dst[i] = (p_dst[i] * (256 - alpha) + p_src[i] * alpha) >> 8; + } +} + +bool Image::is_size_po2() const { + return uint32_t(width) == next_power_of_2(width) && uint32_t(height) == next_power_of_2(height); +} + +void Image::resize_to_po2(bool p_square, Interpolation p_interpolation) { + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot resize in compressed or custom image formats."); + + int w = next_power_of_2(width); + int h = next_power_of_2(height); + if (p_square) { + w = h = MAX(w, h); + } + + if (w == width && h == height) { + if (!p_square || w == h) { + return; // nothing to do + } + } + + resize(w, h, p_interpolation); +} + +void Image::resize(int p_width, int p_height, Interpolation p_interpolation) { + ERR_FAIL_COND_MSG(data.size() == 0, "Cannot resize image before creating it, use create() or create_from_data() first."); + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot resize in compressed or custom image formats."); + ERR_FAIL_COND_MSG(write_lock, "Cannot resize image when it is locked."); + + bool mipmap_aware = p_interpolation == INTERPOLATE_TRILINEAR /* || p_interpolation == INTERPOLATE_TRICUBIC */; + + ERR_FAIL_COND_MSG(p_width <= 0, "Image width must be greater than 0."); + ERR_FAIL_COND_MSG(p_height <= 0, "Image height must be greater than 0."); + ERR_FAIL_COND_MSG(p_width > MAX_WIDTH, "Image width cannot be greater than " + String::num(MAX_WIDTH) + "."); + ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT, "Image height cannot be greater than " + String::num(MAX_HEIGHT) + "."); + + if (p_width == width && p_height == height) { + return; + } + + Image dst(p_width, p_height, false, format); + + // Setup mipmap-aware scaling + Image dst2; + int mip1 = 0; + int mip2 = 0; + float mip1_weight = 0; + if (mipmap_aware) { + float avg_scale = ((float)p_width / width + (float)p_height / height) * 0.5f; + if (avg_scale >= 1.0f) { + mipmap_aware = false; + } else { + float level = Math::log(1.0f / avg_scale) / Math::log(2.0f); + mip1 = CLAMP((int)Math::floor(level), 0, get_mipmap_count()); + mip2 = CLAMP((int)Math::ceil(level), 0, get_mipmap_count()); + mip1_weight = 1.0f - (level - mip1); + } + } + bool interpolate_mipmaps = mipmap_aware && mip1 != mip2; + if (interpolate_mipmaps) { + dst2.create(p_width, p_height, false, format); + } + + bool had_mipmaps = mipmaps; + if (interpolate_mipmaps && !had_mipmaps) { + generate_mipmaps(); + } + // -- + + write_lock = true; + + const unsigned char *r_ptr = data.data(); + unsigned char *w_ptr = dst.data.dataw(); + + switch (p_interpolation) { + case INTERPOLATE_NEAREST: { + if (format >= FORMAT_L8 && format <= FORMAT_RGBA8) { + switch (get_format_pixel_size(format)) { + case 1: + _scale_nearest<1, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 2: + _scale_nearest<2, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 3: + _scale_nearest<3, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 4: + _scale_nearest<4, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + } + } else if (format >= FORMAT_RF && format <= FORMAT_RGBAF) { + switch (get_format_pixel_size(format)) { + case 4: + _scale_nearest<1, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 8: + _scale_nearest<2, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 12: + _scale_nearest<3, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 16: + _scale_nearest<4, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + } + + } else if (format >= FORMAT_RH && format <= FORMAT_RGBAH) { + switch (get_format_pixel_size(format)) { + case 2: + _scale_nearest<1, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 4: + _scale_nearest<2, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 6: + _scale_nearest<3, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 8: + _scale_nearest<4, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + } + } + + } break; + case INTERPOLATE_BILINEAR: + case INTERPOLATE_TRILINEAR: { + for (int i = 0; i < 2; ++i) { + int src_width; + int src_height; + const unsigned char *src_ptr; + + if (!mipmap_aware) { + if (i == 0) { + // Standard behavior + src_width = width; + src_height = height; + src_ptr = r_ptr; + } else { + // No need for a second iteration + break; + } + } else { + if (i == 0) { + // Read from the first mipmap that will be interpolated + // (if both levels are the same, we will not interpolate, but at least we'll sample from the right level) + int offs; + _get_mipmap_offset_and_size(mip1, offs, src_width, src_height); + src_ptr = r_ptr + offs; + } else if (!interpolate_mipmaps) { + // No need generate a second image + break; + } else { + // Switch to read from the second mipmap that will be interpolated + int offs; + _get_mipmap_offset_and_size(mip2, offs, src_width, src_height); + src_ptr = r_ptr + offs; + // Switch to write to the second destination image + w_ptr = dst2.data.dataw(); + } + } + + if (format >= FORMAT_L8 && format <= FORMAT_RGBA8) { + switch (get_format_pixel_size(format)) { + case 1: + _scale_bilinear<1, uint8_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + case 2: + _scale_bilinear<2, uint8_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + case 3: + _scale_bilinear<3, uint8_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + case 4: + _scale_bilinear<4, uint8_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + } + } else if (format >= FORMAT_RF && format <= FORMAT_RGBAF) { + switch (get_format_pixel_size(format)) { + case 4: + _scale_bilinear<1, float>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + case 8: + _scale_bilinear<2, float>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + case 12: + _scale_bilinear<3, float>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + case 16: + _scale_bilinear<4, float>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + } + } else if (format >= FORMAT_RH && format <= FORMAT_RGBAH) { + switch (get_format_pixel_size(format)) { + case 2: + _scale_bilinear<1, uint16_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + case 4: + _scale_bilinear<2, uint16_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + case 6: + _scale_bilinear<3, uint16_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + case 8: + _scale_bilinear<4, uint16_t>(src_ptr, w_ptr, src_width, src_height, p_width, p_height); + break; + } + } + } + + if (interpolate_mipmaps) { + // Switch to read again from the first scaled mipmap to overlay it over the second + _overlay(dst.data.data(), w_ptr, mip1_weight, p_width, p_height, get_format_pixel_size(format)); + } + + } break; + case INTERPOLATE_CUBIC: { + if (format >= FORMAT_L8 && format <= FORMAT_RGBA8) { + switch (get_format_pixel_size(format)) { + case 1: + _scale_cubic<1, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 2: + _scale_cubic<2, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 3: + _scale_cubic<3, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 4: + _scale_cubic<4, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + } + } else if (format >= FORMAT_RF && format <= FORMAT_RGBAF) { + switch (get_format_pixel_size(format)) { + case 4: + _scale_cubic<1, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 8: + _scale_cubic<2, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 12: + _scale_cubic<3, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 16: + _scale_cubic<4, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + } + } else if (format >= FORMAT_RH && format <= FORMAT_RGBAH) { + switch (get_format_pixel_size(format)) { + case 2: + _scale_cubic<1, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 4: + _scale_cubic<2, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 6: + _scale_cubic<3, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 8: + _scale_cubic<4, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + } + } + } break; + case INTERPOLATE_LANCZOS: { + if (format >= FORMAT_L8 && format <= FORMAT_RGBA8) { + switch (get_format_pixel_size(format)) { + case 1: + _scale_lanczos<1, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 2: + _scale_lanczos<2, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 3: + _scale_lanczos<3, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 4: + _scale_lanczos<4, uint8_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + } + } else if (format >= FORMAT_RF && format <= FORMAT_RGBAF) { + switch (get_format_pixel_size(format)) { + case 4: + _scale_lanczos<1, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 8: + _scale_lanczos<2, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 12: + _scale_lanczos<3, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 16: + _scale_lanczos<4, float>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + } + } else if (format >= FORMAT_RH && format <= FORMAT_RGBAH) { + switch (get_format_pixel_size(format)) { + case 2: + _scale_lanczos<1, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 4: + _scale_lanczos<2, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 6: + _scale_lanczos<3, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + case 8: + _scale_lanczos<4, uint16_t>(r_ptr, w_ptr, width, height, p_width, p_height); + break; + } + } + } break; + } + + write_lock = false; + + if (interpolate_mipmaps) { + dst._copy_internals_from(dst2); + } + + if (had_mipmaps) { + dst.generate_mipmaps(); + } + + _copy_internals_from(dst); +} + +void Image::crop_from_point(int p_x, int p_y, int p_width, int p_height) { + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot crop in compressed or custom image formats."); + ERR_FAIL_COND_MSG(write_lock, "Cannot modify image when it is locked."); + ERR_FAIL_COND_MSG(p_x < 0, "Start x position cannot be smaller than 0."); + ERR_FAIL_COND_MSG(p_y < 0, "Start y position cannot be smaller than 0."); + ERR_FAIL_COND_MSG(p_width <= 0, "Width of image must be greater than 0."); + ERR_FAIL_COND_MSG(p_height <= 0, "Height of image must be greater than 0."); + ERR_FAIL_COND_MSG(p_x + p_width > MAX_WIDTH, "End x position cannot be greater than " + String::num(MAX_WIDTH) + "."); + ERR_FAIL_COND_MSG(p_y + p_height > MAX_HEIGHT, "End y position cannot be greater than " + String::num(MAX_HEIGHT) + "."); + + /* to save memory, cropping should be done in-place, however, since this function + will most likely either not be used much, or in critical areas, for now it won't, because + it's a waste of time. */ + + if (p_width == width && p_height == height && p_x == 0 && p_y == 0) { + return; + } + + uint8_t pdata[16]; // largest is 16 + uint32_t pixel_size = get_format_pixel_size(format); + + Image dst(p_width, p_height, false, format); + + { + write_lock = true; + + int m_h = p_y + p_height; + int m_w = p_x + p_width; + for (int y = p_y; y < m_h; y++) { + for (int x = p_x; x < m_w; x++) { + if ((x >= width || y >= height)) { + for (uint32_t i = 0; i < pixel_size; i++) { + pdata[i] = 0; + } + } else { + _get_pixelb(x, y, pixel_size, data.data(), pdata); + } + + dst._put_pixelb(x - p_x, y - p_y, pixel_size, dst.data.dataw(), pdata); + } + } + + write_lock = false; + } + + if (has_mipmaps()) { + dst.generate_mipmaps(); + } + _copy_internals_from(dst); +} + +void Image::crop(int p_width, int p_height) { + crop_from_point(0, 0, p_width, p_height); +} + +void Image::flip_y() { + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot flip_y in compressed or custom image formats."); + + bool used_mipmaps = has_mipmaps(); + if (used_mipmaps) { + clear_mipmaps(); + } + + { + write_lock = true; + + uint8_t up[16]; + uint8_t down[16]; + uint32_t pixel_size = get_format_pixel_size(format); + + for (int y = 0; y < height / 2; y++) { + for (int x = 0; x < width; x++) { + _get_pixelb(x, y, pixel_size, data.data(), up); + _get_pixelb(x, height - y - 1, pixel_size, data.data(), down); + + _put_pixelb(x, height - y - 1, pixel_size, data.dataw(), up); + _put_pixelb(x, y, pixel_size, data.dataw(), down); + } + } + + write_lock = false; + } + + if (used_mipmaps) { + generate_mipmaps(); + } +} + +void Image::flip_x() { + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot flip_x in compressed or custom image formats."); + + bool used_mipmaps = has_mipmaps(); + if (used_mipmaps) { + clear_mipmaps(); + } + + { + write_lock = true; + + uint8_t up[16]; + uint8_t down[16]; + uint32_t pixel_size = get_format_pixel_size(format); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width / 2; x++) { + _get_pixelb(x, y, pixel_size, data.data(), up); + _get_pixelb(width - x - 1, y, pixel_size, data.data(), down); + + _put_pixelb(width - x - 1, y, pixel_size, data.dataw(), up); + _put_pixelb(x, y, pixel_size, data.dataw(), down); + } + } + + write_lock = false; + } + + if (used_mipmaps) { + generate_mipmaps(); + } +} + +int Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps) { + int size = 0; + int w = p_width; + int h = p_height; + int mm = 0; + + int pixsize = get_format_pixel_size(p_format); + int pixshift = get_format_pixel_rshift(p_format); + int block = get_format_block_size(p_format); + // technically, you can still compress up to 1 px no matter the format, so commenting this + // int minw, minh; + // get_format_min_pixel_size(p_format, minw, minh); + int minw = 1, minh = 1; + + while (true) { + int bw = w % block != 0 ? w + (block - w % block) : w; + int bh = h % block != 0 ? h + (block - h % block) : h; + + int s = bw * bh; + + s *= pixsize; + s >>= pixshift; + + size += s; + + if (p_mipmaps >= 0 && mm == p_mipmaps) { + break; + } + + if (p_mipmaps >= 0) { + w = MAX(minw, w >> 1); + h = MAX(minh, h >> 1); + } else { + if (w == minw && h == minh) { + break; + } + w = MAX(minw, w >> 1); + h = MAX(minh, h >> 1); + } + mm++; + }; + + r_mipmaps = mm; + return size; +} + +bool Image::_can_modify(Format p_format) const { + return p_format <= FORMAT_RGBE9995; +} + +template +static void _generate_po2_mipmap(const Component *p_src, Component *p_dst, uint32_t p_width, uint32_t p_height) { + // fast power of 2 mipmap generation + uint32_t dst_w = MAX(p_width >> 1, 1); + uint32_t dst_h = MAX(p_height >> 1, 1); + + int right_step = (p_width == 1) ? 0 : CC; + int down_step = (p_height == 1) ? 0 : (p_width * CC); + + for (uint32_t i = 0; i < dst_h; i++) { + const Component *rup_ptr = &p_src[i * 2 * down_step]; + const Component *rdown_ptr = rup_ptr + down_step; + Component *dst_ptr = &p_dst[i * dst_w * CC]; + uint32_t count = dst_w; + + while (count) { + count--; + for (int j = 0; j < CC; j++) { + average_func(dst_ptr[j], rup_ptr[j], rup_ptr[j + right_step], rdown_ptr[j], rdown_ptr[j + right_step]); + } + + if (renormalize) { + renormalize_func(dst_ptr); + } + + dst_ptr += CC; + rup_ptr += right_step * 2; + rdown_ptr += right_step * 2; + } + } +} + +void Image::shrink_x2() { + ERR_FAIL_COND(!_can_modify(format)); + ERR_FAIL_COND_MSG(write_lock, "Cannot modify image when it is locked."); + ERR_FAIL_COND(data.size() == 0); + + if (mipmaps) { + // just use the lower mipmap as base and copy all + Vector new_img; + + int ofs = get_mipmap_offset(1); + + int new_size = data.size() - ofs; + new_img.resize(new_size); + ERR_FAIL_COND(new_img.size() == 0); + + { + write_lock = true; + memcpy(new_img.dataw(), &data.data()[ofs], new_size); + write_lock = false; + } + + width = MAX(width / 2, 1); + height = MAX(height / 2, 1); + data = new_img; + + } else { + Vector new_img; + + ERR_FAIL_COND(!_can_modify(format)); + int ps = get_format_pixel_size(format); + new_img.resize((width / 2) * (height / 2) * ps); + ERR_FAIL_COND(new_img.size() == 0); + ERR_FAIL_COND(data.size() == 0); + + { + write_lock = true; + + switch (format) { + case FORMAT_L8: + case FORMAT_R8: + _generate_po2_mipmap(data.data(), new_img.dataw(), width, height); + break; + case FORMAT_LA8: + _generate_po2_mipmap(data.data(), new_img.dataw(), width, height); + break; + case FORMAT_RG8: + _generate_po2_mipmap(data.data(), new_img.dataw(), width, height); + break; + case FORMAT_RGB8: + _generate_po2_mipmap(data.data(), new_img.dataw(), width, height); + break; + case FORMAT_RGBA8: + _generate_po2_mipmap(data.data(), new_img.dataw(), width, height); + break; + + case FORMAT_RF: + _generate_po2_mipmap(reinterpret_cast(data.data()), reinterpret_cast(new_img.dataw()), width, height); + break; + case FORMAT_RGF: + _generate_po2_mipmap(reinterpret_cast(data.data()), reinterpret_cast(new_img.dataw()), width, height); + break; + case FORMAT_RGBF: + _generate_po2_mipmap(reinterpret_cast(data.data()), reinterpret_cast(new_img.dataw()), width, height); + break; + case FORMAT_RGBAF: + _generate_po2_mipmap(reinterpret_cast(data.data()), reinterpret_cast(new_img.dataw()), width, height); + break; + + case FORMAT_RH: + _generate_po2_mipmap(reinterpret_cast(data.data()), reinterpret_cast(new_img.dataw()), width, height); + break; + case FORMAT_RGH: + _generate_po2_mipmap(reinterpret_cast(data.data()), reinterpret_cast(new_img.dataw()), width, height); + break; + case FORMAT_RGBH: + _generate_po2_mipmap(reinterpret_cast(data.data()), reinterpret_cast(new_img.dataw()), width, height); + break; + case FORMAT_RGBAH: + _generate_po2_mipmap(reinterpret_cast(data.data()), reinterpret_cast(new_img.dataw()), width, height); + break; + + case FORMAT_RGBE9995: + _generate_po2_mipmap(reinterpret_cast(data.data()), reinterpret_cast(new_img.dataw()), width, height); + break; + default: { + } + } + + write_lock = false; + } + + width /= 2; + height /= 2; + data = new_img; + } +} + +void Image::normalize() { + bool used_mipmaps = has_mipmaps(); + if (used_mipmaps) { + clear_mipmaps(); + } + + lock(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + Color c = get_pixel(x, y); + Vector3 v(c.r * 2.0 - 1.0, c.g * 2.0 - 1.0, c.b * 2.0 - 1.0); + v.normalize(); + c.r = v.x * 0.5 + 0.5; + c.g = v.y * 0.5 + 0.5; + c.b = v.z * 0.5 + 0.5; + set_pixel(x, y, c); + } + } + + unlock(); + + if (used_mipmaps) { + generate_mipmaps(true); + } +} + +int Image::generate_mipmaps(bool p_renormalize) { + ERR_FAIL_COND_V_MSG(!_can_modify(format), 1, "Cannot generate mipmaps in compressed or custom image formats."); + ERR_FAIL_COND_V_MSG(write_lock, 1, "Cannot modify image when it is locked."); + ERR_FAIL_COND_V_MSG(format == FORMAT_RGBA4444 || format == FORMAT_RGBA5551, 1, "Cannot generate mipmaps in custom image formats."); + ERR_FAIL_COND_V_MSG(width == 0 || height == 0, 2, "Cannot generate mipmaps with width or height equal to 0."); + + int mmcount; + + int size = _get_dst_image_size(width, height, format, mmcount); + + data.resize(size); + + uint8_t *wp = data.dataw(); + + int prev_ofs = 0; + int prev_h = height; + int prev_w = width; + + for (int i = 1; i <= mmcount; i++) { + int ofs, w, h; + _get_mipmap_offset_and_size(i, ofs, w, h); + + switch (format) { + case FORMAT_L8: + case FORMAT_R8: + _generate_po2_mipmap(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + break; + case FORMAT_LA8: + case FORMAT_RG8: + _generate_po2_mipmap(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + break; + case FORMAT_RGB8: + if (p_renormalize) { + _generate_po2_mipmap(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + } else { + _generate_po2_mipmap(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + } + + break; + case FORMAT_RGBA8: + if (p_renormalize) { + _generate_po2_mipmap(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + } else { + _generate_po2_mipmap(&wp[prev_ofs], &wp[ofs], prev_w, prev_h); + } + break; + case FORMAT_RF: + _generate_po2_mipmap(reinterpret_cast(&wp[prev_ofs]), reinterpret_cast(&wp[ofs]), prev_w, prev_h); + break; + case FORMAT_RGF: + _generate_po2_mipmap(reinterpret_cast(&wp[prev_ofs]), reinterpret_cast(&wp[ofs]), prev_w, prev_h); + break; + case FORMAT_RGBF: + if (p_renormalize) { + _generate_po2_mipmap(reinterpret_cast(&wp[prev_ofs]), reinterpret_cast(&wp[ofs]), prev_w, prev_h); + } else { + _generate_po2_mipmap(reinterpret_cast(&wp[prev_ofs]), reinterpret_cast(&wp[ofs]), prev_w, prev_h); + } + + break; + case FORMAT_RGBAF: + if (p_renormalize) { + _generate_po2_mipmap(reinterpret_cast(&wp[prev_ofs]), reinterpret_cast(&wp[ofs]), prev_w, prev_h); + } else { + _generate_po2_mipmap(reinterpret_cast(&wp[prev_ofs]), reinterpret_cast(&wp[ofs]), prev_w, prev_h); + } + + break; + case FORMAT_RH: + _generate_po2_mipmap(reinterpret_cast(&wp[prev_ofs]), reinterpret_cast(&wp[ofs]), prev_w, prev_h); + break; + case FORMAT_RGH: + _generate_po2_mipmap(reinterpret_cast(&wp[prev_ofs]), reinterpret_cast(&wp[ofs]), prev_w, prev_h); + break; + case FORMAT_RGBH: + if (p_renormalize) { + _generate_po2_mipmap(reinterpret_cast(&wp[prev_ofs]), reinterpret_cast(&wp[ofs]), prev_w, prev_h); + } else { + _generate_po2_mipmap(reinterpret_cast(&wp[prev_ofs]), reinterpret_cast(&wp[ofs]), prev_w, prev_h); + } + + break; + case FORMAT_RGBAH: + if (p_renormalize) { + _generate_po2_mipmap(reinterpret_cast(&wp[prev_ofs]), reinterpret_cast(&wp[ofs]), prev_w, prev_h); + } else { + _generate_po2_mipmap(reinterpret_cast(&wp[prev_ofs]), reinterpret_cast(&wp[ofs]), prev_w, prev_h); + } + + break; + case FORMAT_RGBE9995: + if (p_renormalize) { + _generate_po2_mipmap(reinterpret_cast(&wp[prev_ofs]), reinterpret_cast(&wp[ofs]), prev_w, prev_h); + } else { + _generate_po2_mipmap(reinterpret_cast(&wp[prev_ofs]), reinterpret_cast(&wp[ofs]), prev_w, prev_h); + } + + break; + default: { + } + } + + prev_ofs = ofs; + prev_w = w; + prev_h = h; + } + + mipmaps = true; + + return 0; +} + +void Image::clear_mipmaps() { + if (!mipmaps) { + return; + } + + if (empty()) { + return; + } + + int ofs, w, h; + _get_mipmap_offset_and_size(1, ofs, w, h); + data.resize(ofs); + + mipmaps = false; +} + +bool Image::empty() const { + return (data.size() == 0); +} + +Vector Image::get_data() const { + return data; +} + +const uint8_t *Image::datar() const { + return data.data(); +} +uint8_t *Image::dataw() { + return data.dataw(); +} +int Image::get_data_size() const { + return data.size(); +} + +void Image::create(int p_width, int p_height, bool p_use_mipmaps, Format p_format) { + ERR_FAIL_COND_MSG(p_width <= 0, "Image width must be greater than 0."); + ERR_FAIL_COND_MSG(p_height <= 0, "Image height must be greater than 0."); + ERR_FAIL_COND_MSG(p_width > MAX_WIDTH, "Image width cannot be greater than " + String::num(MAX_WIDTH) + "."); + ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT, "Image height cannot be greater than " + String::num(MAX_HEIGHT) + "."); + ERR_FAIL_COND_MSG(write_lock, "Cannot create image when it is locked."); + ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "Image format out of range, please see Image's Format enum."); + + int mm = 0; + int size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); + data.resize(size); + { + write_lock = true; + memset(data.dataw(), 0, size); + write_lock = false; + } + + width = p_width; + height = p_height; + mipmaps = p_use_mipmaps; + format = p_format; +} + +void Image::create(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector &p_data) { + ERR_FAIL_COND_MSG(p_width <= 0, "Image width must be greater than 0."); + ERR_FAIL_COND_MSG(p_height <= 0, "Image height must be greater than 0."); + ERR_FAIL_COND_MSG(p_width > MAX_WIDTH, "Image width cannot be greater than " + String::num(MAX_WIDTH) + "."); + ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT, "Image height cannot be greater than " + String::num(MAX_HEIGHT) + "."); + ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "Image format out of range, please see Image's Format enum."); + + int mm; + int size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); + + ERR_FAIL_COND_MSG(p_data.size() != size, "Expected data size of " + String::num(size) + " bytes in Image::create(), got instead " + String::num(p_data.size()) + " bytes."); + + height = p_height; + width = p_width; + format = p_format; + data = p_data; + mipmaps = p_use_mipmaps; +} + +void Image::create(const char **p_xpm) { + int size_width = 0; + int size_height = 0; + int pixelchars = 0; + mipmaps = false; + bool has_alpha = false; + + enum Status { + READING_HEADER, + READING_COLORS, + READING_PIXELS, + DONE + }; + + Status status = READING_HEADER; + int line = 0; + + std::map colormap; + int colormap_size = 0; + uint32_t pixel_size = 0; + uint8_t *w_ptr; + + while (status != DONE) { + const char *line_ptr = p_xpm[line]; + + switch (status) { + case READING_HEADER: { + String line_str = line_ptr; + line_str.replace("\t", " "); + + size_width = line_str.get_slice(' ', 0).to_int(); + size_height = line_str.get_slice(' ', 1).to_int(); + colormap_size = line_str.get_slice(' ', 2).to_int(); + pixelchars = line_str.get_slice(' ', 3).to_int(); + ERR_FAIL_COND(colormap_size > 32766); + ERR_FAIL_COND(pixelchars > 5); + ERR_FAIL_COND(size_width > 32767); + ERR_FAIL_COND(size_height > 32767); + status = READING_COLORS; + } break; + case READING_COLORS: { + String colorstring; + for (int i = 0; i < pixelchars; i++) { + colorstring += *line_ptr; + line_ptr++; + } + // skip spaces + while (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == 0) { + if (*line_ptr == 0) { + break; + } + line_ptr++; + } + if (*line_ptr == 'c') { + line_ptr++; + while (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == 0) { + if (*line_ptr == 0) { + break; + } + line_ptr++; + } + + if (*line_ptr == '#') { + line_ptr++; + uint8_t col_r = 0; + uint8_t col_g = 0; + uint8_t col_b = 0; + // uint8_t col_a=255; + + for (int i = 0; i < 6; i++) { + char v = line_ptr[i]; + + if (v >= '0' && v <= '9') { + v -= '0'; + } else if (v >= 'A' && v <= 'F') { + v = (v - 'A') + 10; + } else if (v >= 'a' && v <= 'f') { + v = (v - 'a') + 10; + } else { + break; + } + + switch (i) { + case 0: + col_r = v << 4; + break; + case 1: + col_r |= v; + break; + case 2: + col_g = v << 4; + break; + case 3: + col_g |= v; + break; + case 4: + col_b = v << 4; + break; + case 5: + col_b |= v; + break; + }; + } + + // magenta mask + if (col_r == 255 && col_g == 0 && col_b == 255) { + colormap[colorstring] = Color(0, 0, 0, 0); + has_alpha = true; + } else { + colormap[colorstring] = Color(col_r / 255.0, col_g / 255.0, col_b / 255.0, 1.0); + } + } + } + if (line == colormap_size) { + status = READING_PIXELS; + create(size_width, size_height, false, has_alpha ? FORMAT_RGBA8 : FORMAT_RGB8); + w_ptr = data.dataw(); + pixel_size = has_alpha ? 4 : 3; + } + } break; + case READING_PIXELS: { + int y = line - colormap_size - 1; + for (int x = 0; x < size_width; x++) { + char pixelstr[6] = { 0, 0, 0, 0, 0, 0 }; + for (int i = 0; i < pixelchars; i++) { + pixelstr[i] = line_ptr[x * pixelchars + i]; + } + + Color *colorptr = &colormap[pixelstr]; + ERR_FAIL_COND(!colorptr); + uint8_t pixel[4]; + for (uint32_t i = 0; i < pixel_size; i++) { + pixel[i] = CLAMP((*colorptr)[i] * 255, 0, 255); + } + _put_pixelb(x, y, pixel_size, w_ptr, pixel); + } + + if (y == (size_height - 1)) { + status = DONE; + } + } break; + default: { + } + } + + line++; + } +} +#define DETECT_ALPHA_MAX_THRESHOLD 254 +#define DETECT_ALPHA_MIN_THRESHOLD 2 + +#define DETECT_ALPHA(m_value) \ + { \ + uint8_t value = m_value; \ + if (value < DETECT_ALPHA_MIN_THRESHOLD) \ + bit = true; \ + else if (value < DETECT_ALPHA_MAX_THRESHOLD) { \ + detected = true; \ + break; \ + } \ + } + +#define DETECT_NON_ALPHA(m_value) \ + { \ + uint8_t value = m_value; \ + if (value > 0) { \ + detected = true; \ + break; \ + } \ + } + +bool Image::is_invisible() const { + if (format == FORMAT_L8 || + format == FORMAT_RGB8 || format == FORMAT_RG8) { + return false; + } + + int len = data.size(); + + if (len == 0) { + return true; + } + + int w, h; + _get_mipmap_offset_and_size(1, len, w, h); + + const unsigned char *data_ptr = data.data(); + + bool detected = false; + + switch (format) { + case FORMAT_LA8: { + for (int i = 0; i < (len >> 1); i++) { + DETECT_NON_ALPHA(data_ptr[(i << 1) + 1]); + } + + } break; + case FORMAT_RGBA8: { + for (int i = 0; i < (len >> 2); i++) { + DETECT_NON_ALPHA(data_ptr[(i << 2) + 3]) + } + + } break; + + case FORMAT_PVRTC2A: + case FORMAT_PVRTC4A: + case FORMAT_DXT3: + case FORMAT_DXT5: { + detected = true; + } break; + default: { + } + } + + return !detected; +} + +Image::AlphaMode Image::detect_alpha() const { + int len = data.size(); + + if (len == 0) { + return ALPHA_NONE; + } + + int w, h; + _get_mipmap_offset_and_size(1, len, w, h); + + const unsigned char *data_ptr = data.data(); + + bool bit = false; + bool detected = false; + + switch (format) { + case FORMAT_LA8: { + for (int i = 0; i < (len >> 1); i++) { + DETECT_ALPHA(data_ptr[(i << 1) + 1]); + } + + } break; + case FORMAT_RGBA8: { + for (int i = 0; i < (len >> 2); i++) { + DETECT_ALPHA(data_ptr[(i << 2) + 3]) + } + + } break; + case FORMAT_PVRTC2A: + case FORMAT_PVRTC4A: + case FORMAT_DXT3: + case FORMAT_DXT5: { + detected = true; + } break; + default: { + } + } + + if (detected) { + return ALPHA_BLEND; + } else if (bit) { + return ALPHA_BIT; + } else { + return ALPHA_NONE; + } +} + +int Image::get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps) { + int mm; + return _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmaps ? -1 : 0); +} + +int Image::get_image_required_mipmaps(int p_width, int p_height, Format p_format) { + int mm; + _get_dst_image_size(p_width, p_height, p_format, mm, -1); + return mm; +} + +int Image::get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap) { + if (p_mipmap <= 0) { + return 0; + } + int mm; + return _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmap - 1); +} + +bool Image::is_compressed() const { + return format > FORMAT_RGBE9995; +} + +Image::Image(const char **p_xpm) { + width = 0; + height = 0; + mipmaps = false; + format = FORMAT_L8; + + create(p_xpm); +} + +Image::Image(int p_width, int p_height, bool p_use_mipmaps, Format p_format) { + width = 0; + height = 0; + mipmaps = p_use_mipmaps; + format = FORMAT_L8; + + create(p_width, p_height, p_use_mipmaps, p_format); +} + +Image::Image(int p_width, int p_height, bool p_mipmaps, Format p_format, const Vector &p_data) { + width = 0; + height = 0; + mipmaps = p_mipmaps; + format = FORMAT_L8; + + create(p_width, p_height, p_mipmaps, p_format, p_data); +} + +Rect2 Image::get_used_rect() const { + if (format != FORMAT_LA8 && format != FORMAT_RGBA8 && format != FORMAT_RGBAF && format != FORMAT_RGBAH && format != FORMAT_RGBA4444 && format != FORMAT_RGBA5551) { + return Rect2(Vector2(), Vector2(width, height)); + } + + int len = data.size(); + + if (len == 0) { + return Rect2(); + } + + const_cast(this)->lock(); + int minx = 0xFFFFFF, miny = 0xFFFFFFF; + int maxx = -1, maxy = -1; + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + if (!(get_pixel(i, j).a > 0)) { + continue; + } + if (i > maxx) { + maxx = i; + } + if (j > maxy) { + maxy = j; + } + if (i < minx) { + minx = i; + } + if (j < miny) { + miny = j; + } + } + } + + const_cast(this)->unlock(); + + if (maxx == -1) { + return Rect2(); + } else { + return Rect2(minx, miny, maxx - minx + 1, maxy - miny + 1); + } +} + +Ref Image::get_rect(const Rect2 &p_area) const { + Ref img = new Image(p_area.x, p_area.y, mipmaps, format); + img->blit_rect(Ref((Image *)this), p_area, Vector2(0, 0)); + return img; +} + +void Image::blit_rect(const Ref &p_src, const Rect2 &p_src_rect, const Vector2 &p_dest) { + ERR_FAIL_COND_MSG(p_src.is_null(), "It's not a reference to a valid Image object."); + int dsize = data.size(); + int srcdsize = p_src->data.size(); + ERR_FAIL_COND(dsize == 0); + ERR_FAIL_COND(srcdsize == 0); + ERR_FAIL_COND(format != p_src->format); + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot blit_rect in compressed or custom image formats."); + + Rect2i clipped_src_rect = Rect2i(0, 0, p_src->width, p_src->height).clip(p_src_rect); + + if (p_dest.x < 0) { + clipped_src_rect.x = ABS(p_dest.x); + } + if (p_dest.y < 0) { + clipped_src_rect.y = ABS(p_dest.y); + } + + if (clipped_src_rect.w <= 0 || clipped_src_rect.h <= 0) { + return; + } + + Vector2 src_underscan = Vector2(MIN(0, p_src_rect.x), MIN(0, p_src_rect.y)); + Rect2i dest_rect = Rect2i(0, 0, width, height).clip(Rect2i(p_dest - src_underscan, clipped_src_rect.size())); + + write_lock = true; + uint8_t *dst_data_ptr = data.dataw(); + + const uint8_t *src_data_ptr = p_src->data.data(); + + int pixel_size = get_format_pixel_size(format); + + for (int i = 0; i < dest_rect.h; i++) { + for (int j = 0; j < dest_rect.w; j++) { + int src_x = clipped_src_rect.x + j; + int src_y = clipped_src_rect.y + i; + + int dst_x = dest_rect.x + j; + int dst_y = dest_rect.y + i; + + const uint8_t *src = &src_data_ptr[(src_y * p_src->width + src_x) * pixel_size]; + uint8_t *dst = &dst_data_ptr[(dst_y * width + dst_x) * pixel_size]; + + for (int k = 0; k < pixel_size; k++) { + dst[k] = src[k]; + } + } + } + + write_lock = false; +} + +void Image::blit_rect_mask(const Ref &p_src, const Ref &p_mask, const Rect2 &p_src_rect, const Vector2 &p_dest) { + ERR_FAIL_COND_MSG(p_src.is_null(), "It's not a reference to a valid Image object."); + ERR_FAIL_COND_MSG(p_mask.is_null(), "It's not a reference to a valid Image object."); + int dsize = data.size(); + int srcdsize = p_src->data.size(); + int maskdsize = p_mask->data.size(); + ERR_FAIL_COND(dsize == 0); + ERR_FAIL_COND(srcdsize == 0); + ERR_FAIL_COND(maskdsize == 0); + ERR_FAIL_COND_MSG(p_src->width != p_mask->width, "Source image width is different from mask width."); + ERR_FAIL_COND_MSG(p_src->height != p_mask->height, "Source image height is different from mask height."); + ERR_FAIL_COND(format != p_src->format); + + Rect2i clipped_src_rect = Rect2i(0, 0, p_src->width, p_src->height).clip(p_src_rect); + + if (p_dest.x < 0) { + clipped_src_rect.x = ABS(p_dest.x); + } + if (p_dest.y < 0) { + clipped_src_rect.y = ABS(p_dest.y); + } + + if (clipped_src_rect.w <= 0 || clipped_src_rect.h <= 0) { + return; + } + + Vector2 src_underscan = Vector2(MIN(0, p_src_rect.x), MIN(0, p_src_rect.y)); + Rect2i dest_rect = Rect2i(0, 0, width, height).clip(Rect2i(p_dest - src_underscan, clipped_src_rect.size())); + + write_lock = true; + + uint8_t *dst_data_ptr = data.dataw(); + + const uint8_t *src_data_ptr = p_src->data.data(); + + int pixel_size = get_format_pixel_size(format); + + Ref msk = p_mask; + msk->lock(); + + for (int i = 0; i < dest_rect.h; i++) { + for (int j = 0; j < dest_rect.w; j++) { + int src_x = clipped_src_rect.x + j; + int src_y = clipped_src_rect.y + i; + + if (msk->get_pixel(src_x, src_y).a != 0) { + int dst_x = dest_rect.x + j; + int dst_y = dest_rect.y + i; + + const uint8_t *src = &src_data_ptr[(src_y * p_src->width + src_x) * pixel_size]; + uint8_t *dst = &dst_data_ptr[(dst_y * width + dst_x) * pixel_size]; + + for (int k = 0; k < pixel_size; k++) { + dst[k] = src[k]; + } + } + } + } + + msk->unlock(); + + write_lock = false; +} + +void Image::blend_rect(const Ref &p_src, const Rect2 &p_src_rect, const Vector2 &p_dest) { + ERR_FAIL_COND_MSG(p_src.is_null(), "It's not a reference to a valid Image object."); + int dsize = data.size(); + int srcdsize = p_src->data.size(); + ERR_FAIL_COND(dsize == 0); + ERR_FAIL_COND(srcdsize == 0); + ERR_FAIL_COND(format != p_src->format); + + Rect2i clipped_src_rect = Rect2i(0, 0, p_src->width, p_src->height).clip(p_src_rect); + + if (p_dest.x < 0) { + clipped_src_rect.x = ABS(p_dest.x); + } + if (p_dest.y < 0) { + clipped_src_rect.y = ABS(p_dest.y); + } + + if (clipped_src_rect.w <= 0 || clipped_src_rect.h <= 0) { + return; + } + + Vector2 src_underscan = Vector2(MIN(0, p_src_rect.x), MIN(0, p_src_rect.y)); + Rect2i dest_rect = Rect2i(0, 0, width, height).clip(Rect2i(p_dest - src_underscan, clipped_src_rect.size())); + + lock(); + Ref img = p_src; + img->lock(); + + for (int i = 0; i < dest_rect.h; i++) { + for (int j = 0; j < dest_rect.w; j++) { + int src_x = clipped_src_rect.x + j; + int src_y = clipped_src_rect.y + i; + + int dst_x = dest_rect.x + j; + int dst_y = dest_rect.y + i; + + Color sc = img->get_pixel(src_x, src_y); + if (sc.a != 0) { + Color dc = get_pixel(dst_x, dst_y); + dc = dc.blend(sc); + set_pixel(dst_x, dst_y, dc); + } + } + } + + img->unlock(); + unlock(); +} + +void Image::blend_rect_mask(const Ref &p_src, const Ref &p_mask, const Rect2 &p_src_rect, const Vector2 &p_dest) { + ERR_FAIL_COND_MSG(p_src.is_null(), "It's not a reference to a valid Image object."); + ERR_FAIL_COND_MSG(p_mask.is_null(), "It's not a reference to a valid Image object."); + int dsize = data.size(); + int srcdsize = p_src->data.size(); + int maskdsize = p_mask->data.size(); + ERR_FAIL_COND(dsize == 0); + ERR_FAIL_COND(srcdsize == 0); + ERR_FAIL_COND(maskdsize == 0); + ERR_FAIL_COND_MSG(p_src->width != p_mask->width, "Source image width is different from mask width."); + ERR_FAIL_COND_MSG(p_src->height != p_mask->height, "Source image height is different from mask height."); + ERR_FAIL_COND(format != p_src->format); + + Rect2i clipped_src_rect = Rect2i(0, 0, p_src->width, p_src->height).clip(p_src_rect); + + if (p_dest.x < 0) { + clipped_src_rect.x = ABS(p_dest.x); + } + if (p_dest.y < 0) { + clipped_src_rect.y = ABS(p_dest.y); + } + + if (clipped_src_rect.w <= 0 || clipped_src_rect.h <= 0) { + return; + } + + Vector2 src_underscan = Vector2(MIN(0, p_src_rect.x), MIN(0, p_src_rect.y)); + Rect2i dest_rect = Rect2i(0, 0, width, height).clip(Rect2i(p_dest - src_underscan, clipped_src_rect.size())); + + lock(); + Ref img = p_src; + Ref msk = p_mask; + img->lock(); + msk->lock(); + + for (int i = 0; i < dest_rect.h; i++) { + for (int j = 0; j < dest_rect.w; j++) { + int src_x = clipped_src_rect.x + j; + int src_y = clipped_src_rect.y + i; + + // If the mask's pixel is transparent then we skip it + // Color c = msk->get_pixel(src_x, src_y); + // if (c.a == 0) continue; + if (msk->get_pixel(src_x, src_y).a != 0) { + int dst_x = dest_rect.x + j; + int dst_y = dest_rect.y + i; + + Color sc = img->get_pixel(src_x, src_y); + if (sc.a != 0) { + Color dc = get_pixel(dst_x, dst_y); + dc = dc.blend(sc); + set_pixel(dst_x, dst_y, dc); + } + } + } + } + + msk->unlock(); + img->unlock(); + unlock(); +} + +// Repeats `p_pixel` `p_count` times in consecutive memory. +// Results in the original pixel and `p_count - 1` subsequent copies of it. +void Image::_repeat_pixel_over_subsequent_memory(uint8_t *p_pixel, int p_pixel_size, int p_count) { + int offset = 1; + for (int stride = 1; offset + stride <= p_count; stride *= 2) { + memcpy(p_pixel + offset * p_pixel_size, p_pixel, stride * p_pixel_size); + offset += stride; + } + if (offset < p_count) { + memcpy(p_pixel + offset * p_pixel_size, p_pixel, (p_count - offset) * p_pixel_size); + } +} + +void Image::fill(const Color &p_color) { + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot fill in compressed or custom image formats."); + + lock(); + + uint8_t *dst_data_ptr = data.dataw(); + + int pixel_size = get_format_pixel_size(format); + + // Put first pixel with the format-aware API. + set_pixel(0, 0, p_color); + + _repeat_pixel_over_subsequent_memory(dst_data_ptr, pixel_size, width * height); + + unlock(); +} + +void Image::fill_rect(const Rect2 &p_rect, const Color &p_color) { + ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot fill rect in compressed or custom image formats."); + + Rect2i r = Rect2i(0, 0, width, height).clip(p_rect.abs()); + if (r.has_no_area()) { + return; + } + + lock(); + + uint8_t *dst_data_ptr = data.dataw(); + + int pixel_size = get_format_pixel_size(format); + + // Put first pixel with the format-aware API. + uint8_t *rect_first_pixel_ptr = &dst_data_ptr[(r.y * width + r.x) * pixel_size]; + set_pixelv(r.position(), p_color); + + if (r.x == width) { + // No need to fill rows separately. + _repeat_pixel_over_subsequent_memory(rect_first_pixel_ptr, pixel_size, width * r.h); + } else { + _repeat_pixel_over_subsequent_memory(rect_first_pixel_ptr, pixel_size, r.w); + for (int y = 1; y < r.h; y++) { + memcpy(rect_first_pixel_ptr + y * width * pixel_size, rect_first_pixel_ptr, r.w * pixel_size); + } + } + + unlock(); +} + +void Image::lock() { + ERR_FAIL_COND(data.size() == 0); + write_lock = true; +} + +void Image::unlock() { + write_lock = false; +} + +Color Image::get_pixelv(const Vector2 &p_src) const { + return get_pixel(p_src.x, p_src.y); +} + +Color Image::get_pixel(int p_x, int p_y) const { + const uint8_t *ptr = data.data(); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_V_MSG(!ptr, Color(), "Image must be locked with 'lock()' before using get_pixel()."); + + ERR_FAIL_INDEX_V(p_x, width, Color()); + ERR_FAIL_INDEX_V(p_y, height, Color()); + +#endif + + uint32_t ofs = p_y * width + p_x; + + switch (format) { + case FORMAT_L8: { + float l = ptr[ofs] / 255.0; + return Color(l, l, l, 1); + } + case FORMAT_LA8: { + float l = ptr[ofs * 2 + 0] / 255.0; + float a = ptr[ofs * 2 + 1] / 255.0; + return Color(l, l, l, a); + } + case FORMAT_R8: { + float r = ptr[ofs] / 255.0; + return Color(r, 0, 0, 1); + } + case FORMAT_RG8: { + float r = ptr[ofs * 2 + 0] / 255.0; + float g = ptr[ofs * 2 + 1] / 255.0; + return Color(r, g, 0, 1); + } + case FORMAT_RGB8: { + float r = ptr[ofs * 3 + 0] / 255.0; + float g = ptr[ofs * 3 + 1] / 255.0; + float b = ptr[ofs * 3 + 2] / 255.0; + return Color(r, g, b, 1); + } + case FORMAT_RGBA8: { + float r = ptr[ofs * 4 + 0] / 255.0; + float g = ptr[ofs * 4 + 1] / 255.0; + float b = ptr[ofs * 4 + 2] / 255.0; + float a = ptr[ofs * 4 + 3] / 255.0; + return Color(r, g, b, a); + } + case FORMAT_RGBA4444: { + uint16_t u = ((uint16_t *)ptr)[ofs]; + float r = ((u >> 12) & 0xF) / 15.0; + float g = ((u >> 8) & 0xF) / 15.0; + float b = ((u >> 4) & 0xF) / 15.0; + float a = (u & 0xF) / 15.0; + return Color(r, g, b, a); + } + case FORMAT_RGBA5551: { + uint16_t u = ((uint16_t *)ptr)[ofs]; + float r = ((u >> 11) & 0x1F) / 15.0; + float g = ((u >> 6) & 0x1F) / 15.0; + float b = ((u >> 1) & 0x1F) / 15.0; + float a = (u & 0x1) / 1.0; + return Color(r, g, b, a); + } + case FORMAT_RF: { + float r = ((float *)ptr)[ofs]; + return Color(r, 0, 0, 1); + } + case FORMAT_RGF: { + float r = ((float *)ptr)[ofs * 2 + 0]; + float g = ((float *)ptr)[ofs * 2 + 1]; + return Color(r, g, 0, 1); + } + case FORMAT_RGBF: { + float r = ((float *)ptr)[ofs * 3 + 0]; + float g = ((float *)ptr)[ofs * 3 + 1]; + float b = ((float *)ptr)[ofs * 3 + 2]; + return Color(r, g, b, 1); + } + case FORMAT_RGBAF: { + float r = ((float *)ptr)[ofs * 4 + 0]; + float g = ((float *)ptr)[ofs * 4 + 1]; + float b = ((float *)ptr)[ofs * 4 + 2]; + float a = ((float *)ptr)[ofs * 4 + 3]; + return Color(r, g, b, a); + } + case FORMAT_RH: { + uint16_t r = ((uint16_t *)ptr)[ofs]; + return Color(Math::half_to_float(r), 0, 0, 1); + } + case FORMAT_RGH: { + uint16_t r = ((uint16_t *)ptr)[ofs * 2 + 0]; + uint16_t g = ((uint16_t *)ptr)[ofs * 2 + 1]; + return Color(Math::half_to_float(r), Math::half_to_float(g), 0, 1); + } + case FORMAT_RGBH: { + uint16_t r = ((uint16_t *)ptr)[ofs * 3 + 0]; + uint16_t g = ((uint16_t *)ptr)[ofs * 3 + 1]; + uint16_t b = ((uint16_t *)ptr)[ofs * 3 + 2]; + return Color(Math::half_to_float(r), Math::half_to_float(g), Math::half_to_float(b), 1); + } + case FORMAT_RGBAH: { + uint16_t r = ((uint16_t *)ptr)[ofs * 4 + 0]; + uint16_t g = ((uint16_t *)ptr)[ofs * 4 + 1]; + uint16_t b = ((uint16_t *)ptr)[ofs * 4 + 2]; + uint16_t a = ((uint16_t *)ptr)[ofs * 4 + 3]; + return Color(Math::half_to_float(r), Math::half_to_float(g), Math::half_to_float(b), Math::half_to_float(a)); + } + case FORMAT_RGBE9995: { + return Color::from_rgbe9995(((uint32_t *)ptr)[ofs]); + } + default: { + ERR_FAIL_V_MSG(Color(), "Can't get_pixel() on compressed image, sorry."); + } + } +} + +void Image::set_pixelv(const Vector2 &p_dst, const Color &p_color) { + set_pixel(p_dst.x, p_dst.y, p_color); +} + +void Image::set_pixel(int p_x, int p_y, const Color &p_color) { + uint8_t *ptr = data.dataw(); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_MSG(!ptr, "Image must be locked with 'lock()' before using set_pixel()."); + + ERR_FAIL_INDEX(p_x, width); + ERR_FAIL_INDEX(p_y, height); + +#endif + + uint32_t ofs = p_y * width + p_x; + + switch (format) { + case FORMAT_L8: { + ptr[ofs] = uint8_t(CLAMP(p_color.get_v() * 255.0, 0, 255)); + } break; + case FORMAT_LA8: { + ptr[ofs * 2 + 0] = uint8_t(CLAMP(p_color.get_v() * 255.0, 0, 255)); + ptr[ofs * 2 + 1] = uint8_t(CLAMP(p_color.a * 255.0, 0, 255)); + } break; + case FORMAT_R8: { + ptr[ofs] = uint8_t(CLAMP(p_color.r * 255.0, 0, 255)); + } break; + case FORMAT_RG8: { + ptr[ofs * 2 + 0] = uint8_t(CLAMP(p_color.r * 255.0, 0, 255)); + ptr[ofs * 2 + 1] = uint8_t(CLAMP(p_color.g * 255.0, 0, 255)); + } break; + case FORMAT_RGB8: { + ptr[ofs * 3 + 0] = uint8_t(CLAMP(p_color.r * 255.0, 0, 255)); + ptr[ofs * 3 + 1] = uint8_t(CLAMP(p_color.g * 255.0, 0, 255)); + ptr[ofs * 3 + 2] = uint8_t(CLAMP(p_color.b * 255.0, 0, 255)); + } break; + case FORMAT_RGBA8: { + ptr[ofs * 4 + 0] = uint8_t(CLAMP(p_color.r * 255.0, 0, 255)); + ptr[ofs * 4 + 1] = uint8_t(CLAMP(p_color.g * 255.0, 0, 255)); + ptr[ofs * 4 + 2] = uint8_t(CLAMP(p_color.b * 255.0, 0, 255)); + ptr[ofs * 4 + 3] = uint8_t(CLAMP(p_color.a * 255.0, 0, 255)); + + } break; + case FORMAT_RGBA4444: { + uint16_t rgba = 0; + + rgba = uint16_t(CLAMP(p_color.r * 15.0, 0, 15)) << 12; + rgba |= uint16_t(CLAMP(p_color.g * 15.0, 0, 15)) << 8; + rgba |= uint16_t(CLAMP(p_color.b * 15.0, 0, 15)) << 4; + rgba |= uint16_t(CLAMP(p_color.a * 15.0, 0, 15)); + + ((uint16_t *)ptr)[ofs] = rgba; + + } break; + case FORMAT_RGBA5551: { + uint16_t rgba = 0; + + rgba = uint16_t(CLAMP(p_color.r * 31.0, 0, 31)) << 11; + rgba |= uint16_t(CLAMP(p_color.g * 31.0, 0, 31)) << 6; + rgba |= uint16_t(CLAMP(p_color.b * 31.0, 0, 31)) << 1; + rgba |= uint16_t(p_color.a > 0.5 ? 1 : 0); + + ((uint16_t *)ptr)[ofs] = rgba; + + } break; + case FORMAT_RF: { + ((float *)ptr)[ofs] = p_color.r; + } break; + case FORMAT_RGF: { + ((float *)ptr)[ofs * 2 + 0] = p_color.r; + ((float *)ptr)[ofs * 2 + 1] = p_color.g; + } break; + case FORMAT_RGBF: { + ((float *)ptr)[ofs * 3 + 0] = p_color.r; + ((float *)ptr)[ofs * 3 + 1] = p_color.g; + ((float *)ptr)[ofs * 3 + 2] = p_color.b; + } break; + case FORMAT_RGBAF: { + ((float *)ptr)[ofs * 4 + 0] = p_color.r; + ((float *)ptr)[ofs * 4 + 1] = p_color.g; + ((float *)ptr)[ofs * 4 + 2] = p_color.b; + ((float *)ptr)[ofs * 4 + 3] = p_color.a; + } break; + case FORMAT_RH: { + ((uint16_t *)ptr)[ofs] = Math::make_half_float(p_color.r); + } break; + case FORMAT_RGH: { + ((uint16_t *)ptr)[ofs * 2 + 0] = Math::make_half_float(p_color.r); + ((uint16_t *)ptr)[ofs * 2 + 1] = Math::make_half_float(p_color.g); + } break; + case FORMAT_RGBH: { + ((uint16_t *)ptr)[ofs * 3 + 0] = Math::make_half_float(p_color.r); + ((uint16_t *)ptr)[ofs * 3 + 1] = Math::make_half_float(p_color.g); + ((uint16_t *)ptr)[ofs * 3 + 2] = Math::make_half_float(p_color.b); + } break; + case FORMAT_RGBAH: { + ((uint16_t *)ptr)[ofs * 4 + 0] = Math::make_half_float(p_color.r); + ((uint16_t *)ptr)[ofs * 4 + 1] = Math::make_half_float(p_color.g); + ((uint16_t *)ptr)[ofs * 4 + 2] = Math::make_half_float(p_color.b); + ((uint16_t *)ptr)[ofs * 4 + 3] = Math::make_half_float(p_color.a); + } break; + case FORMAT_RGBE9995: { + ((uint32_t *)ptr)[ofs] = p_color.to_rgbe9995(); + + } break; + default: { + ERR_FAIL_MSG("Can't set_pixel() on compressed image, sorry."); + } + } +} + +Image::DetectChannels Image::get_detected_channels() { + ERR_FAIL_COND_V(data.size() == 0, DETECTED_RGBA); + ERR_FAIL_COND_V(is_compressed(), DETECTED_RGBA); + bool r = false, g = false, b = false, a = false, c = false; + lock(); + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + Color col = get_pixel(i, j); + + if (col.r > 0.001) { + r = true; + } + if (col.g > 0.001) { + g = true; + } + if (col.b > 0.001) { + b = true; + } + if (col.a < 0.999) { + a = true; + } + + if (col.r != col.b || col.r != col.g || col.b != col.g) { + c = true; + } + } + } + + unlock(); + + if (!c && !a) { + return DETECTED_L; + } + if (!c && a) { + return DETECTED_LA; + } + + if (r && !g && !b && !a) { + return DETECTED_R; + } + + if (r && g && !b && !a) { + return DETECTED_RG; + } + + if (r && g && b && !a) { + return DETECTED_RGB; + } + + return DETECTED_RGBA; +} + +void Image::optimize_channels() { + switch (get_detected_channels()) { + case DETECTED_L: + convert(FORMAT_L8); + break; + case DETECTED_LA: + convert(FORMAT_LA8); + break; + case DETECTED_R: + convert(FORMAT_R8); + break; + case DETECTED_RG: + convert(FORMAT_RG8); + break; + case DETECTED_RGB: + convert(FORMAT_RGB8); + break; + case DETECTED_RGBA: + convert(FORMAT_RGBA8); + break; + } +} + +void Image::normalmap_to_xy() { + convert(Image::FORMAT_RGBA8); + + { + write_lock = true; + + int len = data.size() / 4; + unsigned char *data_ptr = data.dataw(); + + for (int i = 0; i < len; i++) { + data_ptr[(i << 2) + 3] = data_ptr[(i << 2) + 0]; // x to w + data_ptr[(i << 2) + 0] = data_ptr[(i << 2) + 1]; // y to xz + data_ptr[(i << 2) + 2] = data_ptr[(i << 2) + 1]; + } + + write_lock = false; + } + + convert(Image::FORMAT_LA8); +} + +Ref Image::rgbe_to_srgb() { + if (data.size() == 0) { + return Ref(); + } + + ERR_FAIL_COND_V(format != FORMAT_RGBE9995, Ref()); + + Ref new_image; + new_image.instance(); + new_image->create(width, height, false, Image::FORMAT_RGB8); + + lock(); + + new_image->lock(); + + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + new_image->set_pixel(col, row, get_pixel(col, row).to_srgb()); + } + } + + unlock(); + new_image->unlock(); + + if (has_mipmaps()) { + new_image->generate_mipmaps(); + } + + return new_image; +} + +void Image::bumpmap_to_normalmap(float bump_scale) { + ERR_FAIL_COND(!_can_modify(format)); + ERR_FAIL_COND_MSG(write_lock, "Cannot modify image when it is locked."); + convert(Image::FORMAT_RF); + + Vector result_image; // rgba output + result_image.resize(width * height * 4); + + { + write_lock = true; + + unsigned char *write_ptr = result_image.dataw(); + float *read_ptr = (float *)data.dataw(); + + for (int ty = 0; ty < height; ty++) { + int py = ty + 1; + if (py >= height) { + py -= height; + } + + for (int tx = 0; tx < width; tx++) { + int px = tx + 1; + if (px >= width) { + px -= width; + } + float here = read_ptr[ty * width + tx]; + float to_right = read_ptr[ty * width + px]; + float above = read_ptr[py * width + tx]; + Vector3 up = Vector3(0, 1, (here - above) * bump_scale); + Vector3 across = Vector3(1, 0, (to_right - here) * bump_scale); + + Vector3 normal = across.cross(up); + normal.normalize(); + + write_ptr[((ty * width + tx) << 2) + 0] = (127.5 + normal.x * 127.5); + write_ptr[((ty * width + tx) << 2) + 1] = (127.5 + normal.y * 127.5); + write_ptr[((ty * width + tx) << 2) + 2] = (127.5 + normal.z * 127.5); + write_ptr[((ty * width + tx) << 2) + 3] = 255; + } + } + + write_lock = false; + } + format = FORMAT_RGBA8; + data = result_image; +} + +void Image::srgb_to_linear() { + if (data.size() == 0) { + return; + } + + static const uint8_t srgb2lin[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 22, 22, 23, 23, 24, 24, 25, 26, 26, 27, 27, 28, 29, 29, 30, 31, 31, 32, 33, 33, 34, 35, 36, 36, 37, 38, 38, 39, 40, 41, 42, 42, 43, 44, 45, 46, 47, 47, 48, 49, 50, 51, 52, 53, 54, 55, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 70, 71, 72, 73, 74, 75, 76, 77, 78, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 92, 93, 94, 95, 97, 98, 99, 101, 102, 103, 105, 106, 107, 109, 110, 112, 113, 114, 116, 117, 119, 120, 122, 123, 125, 126, 128, 129, 131, 132, 134, 135, 137, 139, 140, 142, 144, 145, 147, 148, 150, 152, 153, 155, 157, 159, 160, 162, 164, 166, 167, 169, 171, 173, 175, 176, 178, 180, 182, 184, 186, 188, 190, 192, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 218, 220, 222, 224, 226, 228, 230, 232, 235, 237, 239, 241, 243, 245, 248, 250, 252, 255 }; + + ERR_FAIL_COND(format != FORMAT_RGB8 && format != FORMAT_RGBA8); + + if (format == FORMAT_RGBA8) { + write_lock = true; + + int len = data.size() / 4; + unsigned char *data_ptr = data.dataw(); + + for (int i = 0; i < len; i++) { + data_ptr[(i << 2) + 0] = srgb2lin[data_ptr[(i << 2) + 0]]; + data_ptr[(i << 2) + 1] = srgb2lin[data_ptr[(i << 2) + 1]]; + data_ptr[(i << 2) + 2] = srgb2lin[data_ptr[(i << 2) + 2]]; + } + + write_lock = false; + } else if (format == FORMAT_RGB8) { + write_lock = true; + + int len = data.size() / 3; + unsigned char *data_ptr = data.dataw(); + + for (int i = 0; i < len; i++) { + data_ptr[(i * 3) + 0] = srgb2lin[data_ptr[(i * 3) + 0]]; + data_ptr[(i * 3) + 1] = srgb2lin[data_ptr[(i * 3) + 1]]; + data_ptr[(i * 3) + 2] = srgb2lin[data_ptr[(i * 3) + 2]]; + } + + write_lock = false; + } +} + +void Image::premultiply_alpha() { + if (data.size() == 0) { + return; + } + + if (format != FORMAT_RGBA8) { + return; // not needed + } + + write_lock = true; + + unsigned char *data_ptr = data.dataw(); + + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + uint8_t *ptr = &data_ptr[(i * width + j) * 4]; + + ptr[0] = (uint16_t(ptr[0]) * uint16_t(ptr[3])) >> 8; + ptr[1] = (uint16_t(ptr[1]) * uint16_t(ptr[3])) >> 8; + ptr[2] = (uint16_t(ptr[2]) * uint16_t(ptr[3])) >> 8; + } + } + + write_lock = false; +} + +void Image::fix_alpha_edges() { + ERR_FAIL_COND(!_can_modify(format)); + ERR_FAIL_COND_MSG(write_lock, "Cannot modify image when it is locked."); + + if (data.size() == 0) { + return; + } + + if (format != FORMAT_RGBA8) { + return; // not needed + } + + write_lock = true; + + Vector dcopy = data; + const uint8_t *srcptr = dcopy.data(); + + unsigned char *data_ptr = data.dataw(); + + const int max_radius = 4; + const int alpha_threshold = 20; + const int max_dist = 0x7FFFFFFF; + + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + const uint8_t *rptr = &srcptr[(i * width + j) * 4]; + uint8_t *wptr = &data_ptr[(i * width + j) * 4]; + + if (rptr[3] >= alpha_threshold) { + continue; + } + + int closest_dist = max_dist; + uint8_t closest_color[3]; + + int from_x = MAX(0, j - max_radius); + int to_x = MIN(width - 1, j + max_radius); + int from_y = MAX(0, i - max_radius); + int to_y = MIN(height - 1, i + max_radius); + + for (int k = from_y; k <= to_y; k++) { + for (int l = from_x; l <= to_x; l++) { + int dy = i - k; + int dx = j - l; + int dist = dy * dy + dx * dx; + if (dist >= closest_dist) { + continue; + } + + const uint8_t *rp2 = &srcptr[(k * width + l) << 2]; + + if (rp2[3] < alpha_threshold) { + continue; + } + + closest_dist = dist; + closest_color[0] = rp2[0]; + closest_color[1] = rp2[1]; + closest_color[2] = rp2[2]; + } + } + + if (closest_dist != max_dist) { + wptr[0] = closest_color[0]; + wptr[1] = closest_color[1]; + wptr[2] = closest_color[2]; + } + } + } + + write_lock = false; +} + +String Image::get_format_name(Format p_format) { + ERR_FAIL_INDEX_V(p_format, FORMAT_MAX, String()); + return format_names[p_format]; +} + +void Image::average_4_uint8(uint8_t &p_out, const uint8_t &p_a, const uint8_t &p_b, const uint8_t &p_c, const uint8_t &p_d) { + p_out = static_cast((p_a + p_b + p_c + p_d + 2) >> 2); +} + +void Image::average_4_float(float &p_out, const float &p_a, const float &p_b, const float &p_c, const float &p_d) { + p_out = (p_a + p_b + p_c + p_d) * 0.25f; +} + +void Image::average_4_half(uint16_t &p_out, const uint16_t &p_a, const uint16_t &p_b, const uint16_t &p_c, const uint16_t &p_d) { + p_out = Math::make_half_float((Math::half_to_float(p_a) + Math::half_to_float(p_b) + Math::half_to_float(p_c) + Math::half_to_float(p_d)) * 0.25f); +} + +void Image::average_4_rgbe9995(uint32_t &p_out, const uint32_t &p_a, const uint32_t &p_b, const uint32_t &p_c, const uint32_t &p_d) { + p_out = ((Color::from_rgbe9995(p_a) + Color::from_rgbe9995(p_b) + Color::from_rgbe9995(p_c) + Color::from_rgbe9995(p_d)) * 0.25f).to_rgbe9995(); +} + +void Image::renormalize_uint8(uint8_t *p_rgb) { + Vector3 n(p_rgb[0] / 255.0, p_rgb[1] / 255.0, p_rgb[2] / 255.0); + n *= 2.0; + n -= Vector3(1, 1, 1); + n.normalize(); + n += Vector3(1, 1, 1); + n *= 0.5; + n *= 255; + p_rgb[0] = CLAMP(int(n.x), 0, 255); + p_rgb[1] = CLAMP(int(n.y), 0, 255); + p_rgb[2] = CLAMP(int(n.z), 0, 255); +} + +void Image::renormalize_float(float *p_rgb) { + Vector3 n(p_rgb[0], p_rgb[1], p_rgb[2]); + n.normalize(); + p_rgb[0] = n.x; + p_rgb[1] = n.y; + p_rgb[2] = n.z; +} + +void Image::renormalize_half(uint16_t *p_rgb) { + Vector3 n(Math::half_to_float(p_rgb[0]), Math::half_to_float(p_rgb[1]), Math::half_to_float(p_rgb[2])); + n.normalize(); + p_rgb[0] = Math::make_half_float(n.x); + p_rgb[1] = Math::make_half_float(n.y); + p_rgb[2] = Math::make_half_float(n.z); +} + +void Image::renormalize_rgbe9995(uint32_t *p_rgb) { + // Never used +} + +Ref Image::duplicate(bool p_subresources) const { + Ref copy; + copy.instance(); + copy->_copy_internals_from(*this); + return copy; +} + +Image::Image() { + width = 0; + height = 0; + mipmaps = false; + format = FORMAT_L8; + + write_lock = false; +} + +Image::~Image() { + write_lock = false; +} diff --git a/core/image/image.h b/core/image/image.h new file mode 100644 index 0000000..1958fc3 --- /dev/null +++ b/core/image/image.h @@ -0,0 +1,310 @@ +/*************************************************************************/ +/* image.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 IMAGE_H +#define IMAGE_H + +#include "core/math/color.h" +#include "core/math/rect2.h" +#include "core/containers/vector.h" +#include "core/resource.h" +#include "core/math/rect2i.h" +#include "core/math/vector2i.h" + +class Image : public Resource { + RCPP_OBJECT(Image, Resource); + +public: + enum { + MAX_WIDTH = 16384, // force a limit somehow + MAX_HEIGHT = 16384 // force a limit somehow + }; + + enum Format { + + FORMAT_L8, //luminance + FORMAT_LA8, //luminance-alpha + FORMAT_R8, + FORMAT_RG8, + FORMAT_RGB8, + FORMAT_RGBA8, + FORMAT_RGBA4444, + FORMAT_RGBA5551, + FORMAT_RF, //float + FORMAT_RGF, + FORMAT_RGBF, + FORMAT_RGBAF, + FORMAT_RH, //half float + FORMAT_RGH, + FORMAT_RGBH, + FORMAT_RGBAH, + FORMAT_RGBE9995, + FORMAT_DXT1, //s3tc bc1 + FORMAT_DXT3, //bc2 + FORMAT_DXT5, //bc3 + FORMAT_RGTC_R, + FORMAT_RGTC_RG, + FORMAT_BPTC_RGBA, //btpc bc7 + FORMAT_BPTC_RGBF, //float bc6h + FORMAT_BPTC_RGBFU, //unsigned float bc6hu + FORMAT_PVRTC2, //pvrtc + FORMAT_PVRTC2A, + FORMAT_PVRTC4, + FORMAT_PVRTC4A, + FORMAT_ETC, //etc1 + FORMAT_ETC2_R11, //etc2 + FORMAT_ETC2_R11S, //signed, NOT srgb. + FORMAT_ETC2_RG11, + FORMAT_ETC2_RG11S, + FORMAT_ETC2_RGB8, + FORMAT_ETC2_RGBA8, + FORMAT_ETC2_RGB8A1, + FORMAT_MAX + }; + + static const char *format_names[FORMAT_MAX]; + enum Interpolation { + + INTERPOLATE_NEAREST, + INTERPOLATE_BILINEAR, + INTERPOLATE_CUBIC, + INTERPOLATE_TRILINEAR, + INTERPOLATE_LANCZOS, + /* INTERPOLATE_TRICUBIC, */ + /* INTERPOLATE GAUSS */ + }; + + enum CompressSource { + COMPRESS_SOURCE_GENERIC, + COMPRESS_SOURCE_SRGB, + COMPRESS_SOURCE_NORMAL, + COMPRESS_SOURCE_LAYERED, + COMPRESS_SOURCE_MAX, + }; + + bool write_lock; + +private: + void _create_empty(int p_width, int p_height, bool p_use_mipmaps, Format p_format) { + create(p_width, p_height, p_use_mipmaps, p_format); + } + + void _create_from_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector &p_data) { + create(p_width, p_height, p_use_mipmaps, p_format, p_data); + } + + Format format; + Vector data; + int width, height; + bool mipmaps; + + void _copy_internals_from(const Image &p_image) { + format = p_image.format; + width = p_image.width; + height = p_image.height; + mipmaps = p_image.mipmaps; + data = p_image.data; + } + + _FORCE_INLINE_ void _get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_width, int &r_height) const; //get where the mipmap begins in data + + static int _get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps = -1); + bool _can_modify(Format p_format) const; + + _FORCE_INLINE_ void _put_pixelb(int p_x, int p_y, uint32_t p_pixel_size, uint8_t *p_data, const uint8_t *p_pixel); + _FORCE_INLINE_ void _get_pixelb(int p_x, int p_y, uint32_t p_pixel_size, const uint8_t *p_data, uint8_t *p_pixel); + + _FORCE_INLINE_ void _repeat_pixel_over_subsequent_memory(uint8_t *p_pixel, int p_pixel_size, int p_count); + + static void average_4_uint8(uint8_t &p_out, const uint8_t &p_a, const uint8_t &p_b, const uint8_t &p_c, const uint8_t &p_d); + static void average_4_float(float &p_out, const float &p_a, const float &p_b, const float &p_c, const float &p_d); + static void average_4_half(uint16_t &p_out, const uint16_t &p_a, const uint16_t &p_b, const uint16_t &p_c, const uint16_t &p_d); + static void average_4_rgbe9995(uint32_t &p_out, const uint32_t &p_a, const uint32_t &p_b, const uint32_t &p_c, const uint32_t &p_d); + static void renormalize_uint8(uint8_t *p_rgb); + static void renormalize_float(float *p_rgb); + static void renormalize_half(uint16_t *p_rgb); + static void renormalize_rgbe9995(uint32_t *p_rgb); + +public: + int get_width() const; ///< Get image width + int get_height() const; ///< Get image height + Vector2 get_size() const; + bool has_mipmaps() const; + int get_mipmap_count() const; + + /** + * Convert the image to another format, conversion only to raw byte format + */ + void convert(Format p_new_format); + + /** + * Get the current image format. + */ + Format get_format() const; + + int get_mipmap_offset(int p_mipmap) const; //get where the mipmap begins in data + void get_mipmap_offset_and_size(int p_mipmap, int &r_ofs, int &r_size) const; //get where the mipmap begins in data + void get_mipmap_offset_size_and_dimensions(int p_mipmap, int &r_ofs, int &r_size, int &w, int &h) const; //get where the mipmap begins in data + + /** + * Resize the image, using the preferred interpolation method. + */ + void resize_to_po2(bool p_square = false, Interpolation p_interpolation = INTERPOLATE_BILINEAR); + void resize(int p_width, int p_height, Interpolation p_interpolation = INTERPOLATE_BILINEAR); + void shrink_x2(); + bool is_size_po2() const; + /** + * Crop the image to a specific size, if larger, then the image is filled by black + */ + void crop_from_point(int p_x, int p_y, int p_width, int p_height); + void crop(int p_width, int p_height); + + void flip_x(); + void flip_y(); + + /** + * Generate a mipmap to an image (creates an image 1/4 the size, with averaging of 4->1) + */ + int generate_mipmaps(bool p_renormalize = false); + + void clear_mipmaps(); + void normalize(); //for normal maps + + /** + * Create a new image of a given size and format. Current image will be lost + */ + void create(int p_width, int p_height, bool p_use_mipmaps, Format p_format); + void create(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector &p_data); + + void create(const char **p_xpm); + /** + * returns true when the image is empty (0,0) in size + */ + bool empty() const; + + Vector get_data() const; + const uint8_t* datar() const; + uint8_t* dataw(); + int get_data_size() const; + + Image(); + Image(int p_width, int p_height, bool p_use_mipmaps, Format p_format); + Image(int p_width, int p_height, bool p_mipmaps, Format p_format, const Vector &p_data); + + enum AlphaMode { + ALPHA_NONE, + ALPHA_BIT, + ALPHA_BLEND + }; + + AlphaMode detect_alpha() const; + bool is_invisible() const; + + static int get_format_pixel_size(Format p_format); + static int get_format_pixel_rshift(Format p_format); + static int get_format_block_size(Format p_format); + static void get_format_min_pixel_size(Format p_format, int &r_w, int &r_h); + + static int get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps = false); + static int get_image_required_mipmaps(int p_width, int p_height, Format p_format); + static int get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap); + + enum CompressMode { + COMPRESS_S3TC, + COMPRESS_PVRTC2, + COMPRESS_PVRTC4, + COMPRESS_ETC, + COMPRESS_ETC2, + COMPRESS_BPTC, + COMPRESS_MAX, + }; + + bool is_compressed() const; + + void fix_alpha_edges(); + void premultiply_alpha(); + void srgb_to_linear(); + void normalmap_to_xy(); + Ref rgbe_to_srgb(); + void bumpmap_to_normalmap(float bump_scale = 1.0); + + void blit_rect(const Ref &p_src, const Rect2 &p_src_rect, const Vector2 &p_dest); + void blit_rect_mask(const Ref &p_src, const Ref &p_mask, const Rect2 &p_src_rect, const Vector2 &p_dest); + void blend_rect(const Ref &p_src, const Rect2 &p_src_rect, const Vector2 &p_dest); + void blend_rect_mask(const Ref &p_src, const Ref &p_mask, const Rect2 &p_src_rect, const Vector2 &p_dest); + void fill(const Color &p_color); + void fill_rect(const Rect2 &p_rect, const Color &p_color); + + Rect2 get_used_rect() const; + Ref get_rect(const Rect2 &p_area) const; + + static void set_compress_bc_func(void (*p_compress_func)(Image *, float, CompressSource)); + static void set_compress_bptc_func(void (*p_compress_func)(Image *, float, CompressSource)); + static String get_format_name(Format p_format); + + Image(const char **p_xpm); + + virtual Ref duplicate(bool p_subresources = false) const; + + void lock(); + void unlock(); + + //this is used for compression + enum DetectChannels { + DETECTED_L, + DETECTED_LA, + DETECTED_R, + DETECTED_RG, + DETECTED_RGB, + DETECTED_RGBA, + }; + + DetectChannels get_detected_channels(); + void optimize_channels(); + + Color get_pixelv(const Vector2 &p_src) const; + Color get_pixel(int p_x, int p_y) const; + void set_pixelv(const Vector2 &p_dst, const Color &p_color); + void set_pixel(int p_x, int p_y, const Color &p_color); + + void copy_internals_from(const Ref &p_image) { + ERR_FAIL_COND(p_image.is_null()); + + format = p_image->format; + width = p_image->width; + height = p_image->height; + mipmaps = p_image->mipmaps; + data = p_image->data; + } + + ~Image(); +}; + +#endif diff --git a/core/int_types.h b/core/int_types.h new file mode 100644 index 0000000..c419cf9 --- /dev/null +++ b/core/int_types.h @@ -0,0 +1,57 @@ +/*************************************************************************/ +/* int_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifdef _MSC_VER + +typedef signed __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef signed __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef signed __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + +#else + +#ifdef NO_STDINT_H +typedef unsigned char uint8_t; +typedef signed char int8_t; +typedef unsigned short uint16_t; +typedef signed short int16_t; +typedef unsigned int uint32_t; +typedef signed int int32_t; +typedef long long int64_t; +typedef unsigned long long uint64_t; +#else +#include +#endif + +#endif diff --git a/core/log/logger.cpp b/core/log/logger.cpp new file mode 100644 index 0000000..d9149be --- /dev/null +++ b/core/log/logger.cpp @@ -0,0 +1,205 @@ + +#include "logger.h" + +#include "core/string.h" +#include + +#include "logger.h" +#include +#include + + +void RLogger::print_trace(const String &str) { + print_trace(str.data()); +} +void RLogger::print_trace(const char *str) { + printf("T %s\n", str); +} +void RLogger::print_trace(const char *p_function, const char *p_file, int p_line, const char *str) { + printf("T | %s::%s:%d | %s\n", p_file, p_function, p_line, str); +} +void RLogger::print_trace(const char *p_function, const char *p_file, int p_line, const String &str) { + printf("T | %s::%s:%d | %s\n", p_file, p_function, p_line, str.c_str()); +} + +void RLogger::print_message(const String &str) { + print_message(str.data()); +} +void RLogger::print_message(const char *str) { + printf("M %s\n", str); +} +void RLogger::print_message(const char *p_function, const char *p_file, int p_line, const char *str) { + printf("M | %s::%s:%d | %s\n", p_file, p_function, p_line, str); +} +void RLogger::print_message(const char *p_function, const char *p_file, int p_line, const String &str) { + printf("M | %s::%s:%d | %s\n", p_file, p_function, p_line, str.c_str()); +} + +void RLogger::print_warning(const String &str) { + print_warning(str.data()); +} +void RLogger::print_warning(const char *str) { + printf("W %s\n", str); +} +void RLogger::print_warning(const char *p_function, const char *p_file, int p_line, const char *str) { + printf("W | %s::%s:%d | %s\n", p_file, p_function, p_line, str); +} +void RLogger::print_warning(const char *p_function, const char *p_file, int p_line, const String &str) { + printf("W | %s::%s:%d | %s\n", p_file, p_function, p_line, str.c_str()); +} + +void RLogger::print_error(const String &str) { + print_error(str.data()); +} +void RLogger::print_error(const char *str) { + printf("E %s\n", str); +} + +void RLogger::print_error(const char *p_function, const char *p_file, int p_line, const char *str) { + printf("E | %s::%s:%d | %s\n", p_file, p_function, p_line, str); +} +void RLogger::print_error(const char *p_function, const char *p_file, int p_line, const String &str) { + printf("E | %s::%s:%d | %s\n", p_file, p_function, p_line, str.c_str()); +} +void RLogger::print_msg_error(const char *p_function, const char *p_file, int p_line, const char *p_msg, const char *str) { + printf("E | %s::%s:%d | :: %s. %s\n", p_file, p_function, p_line, str, p_msg); +} +void RLogger::print_index_error(const char *p_function, const char *p_file, int p_line, const int index, const int size, const char *str) { + printf("E (INDEX) | %s::%s:%d | :: index: %d/%d. %s\n", p_file, p_function, p_line, index, size, str); +} + +void RLogger::log_trace(const String &str) { + log_trace(str.data()); +} +void RLogger::log_trace(const char *str) { + printf("T %s\n", str); +} +void RLogger::log_trace(const char *p_function, const char *p_file, int p_line, const char *str) { + printf("T | %s::%s:%d | %s\n", p_file, p_function, p_line, str); +} +void RLogger::log_trace(const char *p_function, const char *p_file, int p_line, const String &str) { + printf("T | %s::%s:%d | %s\n", p_file, p_function, p_line, str.c_str()); +} + +void RLogger::log_message(const String &str) { + log_message(str.data()); +} +void RLogger::log_message(const char *str) { + printf("M %s\n", str); +} +void RLogger::log_message(const char *p_function, const char *p_file, int p_line, const char *str) { + printf("M | %s::%s:%d | %s\n", p_file, p_function, p_line, str); +} +void RLogger::log_message(const char *p_function, const char *p_file, int p_line, const String &str) { + printf("M | %s::%s:%d | %s\n", p_file, p_function, p_line, str.c_str()); +} + +void RLogger::log_warning(const String &str) { + log_warning(str.data()); +} +void RLogger::log_warning(const char *str) { + printf("W %s\n", str); +} +void RLogger::log_warning(const char *p_function, const char *p_file, int p_line, const char *str) { + printf("W | %s::%s:%d | %s\n", p_file, p_function, p_line, str); +} +void RLogger::log_warning(const char *p_function, const char *p_file, int p_line, const String &str) { + printf("W | %s::%s:%d | %s\n", p_file, p_function, p_line, str.c_str()); +} + +void RLogger::log_error(const String &str) { + log_error(str.data()); +} +void RLogger::log_error(const char *str) { + printf("E %s\n", str); +} + +void RLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *str) { + printf("E | %s::%s:%d | %s\n", p_file, p_function, p_line, str); +} +void RLogger::log_error(const char *p_function, const char *p_file, int p_line, const String &str) { + printf("E | %s::%s:%d | %s\n", p_file, p_function, p_line, str.c_str()); +} +void RLogger::log_msg_error(const char *p_function, const char *p_file, int p_line, const char *p_msg, const char *str) { + printf("E | %s::%s:%d | :: %s. %s\n", p_file, p_function, p_line, str, p_msg); +} +void RLogger::log_index_error(const char *p_function, const char *p_file, int p_line, const int index, const int size, const char *str) { + printf("E (INDEX) | %s::%s:%d | :: index: %d/%d. %s\n", p_file, p_function, p_line, index, size, str); +} + +String *RLogger::get_string_ptr(const int p_default_size) { + return new String(p_default_size); +} +String *RLogger::get_string_ptr(const char *p_function, const char *p_file, int p_line, const int p_default_size) { + String *s = new String(p_default_size); + + s->append_str(p_function); + s->append_str("::"); + s->append_str(p_file); + s->append_str(":"); + s->append_str(String::num(p_line)); + s->append_str(" | "); + + return s; +} +String *RLogger::get_string_ptr(const char *p_prefix, const char *p_function, const char *p_file, int p_line, const int p_default_size) { + String *s = new String(p_default_size); + + s->append_str(p_prefix); + s->append_str(" | "); + s->append_str(p_function); + s->append_str("::"); + s->append_str(p_file); + s->append_str(":"); + s->append_str(String::num(p_line)); + s->append_str(" | "); + + return s; +} +void RLogger::return_string_ptr(String *str) { + delete str; +} + +String *RLogger::get_trace_string_ptr(const int p_default_size) { + String *str = get_string_ptr(p_default_size); + str->append_str("T "); + return str; +} +String *RLogger::get_message_string_ptr(const int p_default_size) { + String *str = get_string_ptr(p_default_size); + str->append_str("M "); + return str; +} +String *RLogger::get_warning_string_ptr(const int p_default_size) { + String *str = get_string_ptr(p_default_size); + str->append_str("W "); + return str; +} +String *RLogger::get_error_string_ptr(const int p_default_size) { + String *str = get_string_ptr(p_default_size); + str->append_str("E "); + return str; +} + +String *RLogger::get_trace_string_ptr(const char *p_function, const char *p_file, int p_line, const int p_default_size) { + return get_string_ptr("T", p_function, p_file, p_line, p_default_size); +} +String *RLogger::get_message_string_ptr(const char *p_function, const char *p_file, int p_line, const int p_default_size) { + return get_string_ptr("M", p_function, p_file, p_line, p_default_size); +} +String *RLogger::get_warning_string_ptr(const char *p_function, const char *p_file, int p_line, const int p_default_size) { + return get_string_ptr("W", p_function, p_file, p_line, p_default_size); +} +String *RLogger::get_error_string_ptr(const char *p_function, const char *p_file, int p_line, const int p_default_size) { + return get_string_ptr("E", p_function, p_file, p_line, p_default_size); +} + +void RLogger::log_ptr(String *str) { + printf("%s\n", str->data()); +} + +void RLogger::log_ret_ptr(String *str) { + log_ptr(str); + + return_string_ptr(str); +} diff --git a/core/log/logger.h b/core/log/logger.h new file mode 100644 index 0000000..c45aa32 --- /dev/null +++ b/core/log/logger.h @@ -0,0 +1,77 @@ + +#ifndef LOGGER_H +#define LOGGER_H + +#include +#include +#include +#include + +class String; + +class RLogger { +public: + static void print_trace(const String &str); + static void print_trace(const char *str); + static void print_trace(const char *p_function, const char *p_file, int p_line, const String &str); + static void print_trace(const char *p_function, const char *p_file, int p_line, const char *str); + + static void print_message(const String &str); + static void print_message(const char *str); + static void print_message(const char *p_function, const char *p_file, int p_line, const String &str); + static void print_message(const char *p_function, const char *p_file, int p_line, const char *str); + + static void print_warning(const String &str); + static void print_warning(const char *str); + static void print_warning(const char *p_function, const char *p_file, int p_line, const String &str); + static void print_warning(const char *p_function, const char *p_file, int p_line, const char *str); + + static void print_error(const String &str); + static void print_error(const char *str); + static void print_error(const char *p_function, const char *p_file, int p_line, const char *str); + static void print_error(const char *p_function, const char *p_file, int p_line, const String &str); + static void print_msg_error(const char *p_function, const char *p_file, int p_line, const char *p_msg, const char *str); + static void print_index_error(const char *p_function, const char *p_file, int p_line, const int index, const int size, const char *str); + + static void log_trace(const String &str); + static void log_trace(const char *str); + static void log_trace(const char *p_function, const char *p_file, int p_line, const String &str); + static void log_trace(const char *p_function, const char *p_file, int p_line, const char *str); + + static void log_message(const String &str); + static void log_message(const char *str); + static void log_message(const char *p_function, const char *p_file, int p_line, const String &str); + static void log_message(const char *p_function, const char *p_file, int p_line, const char *str); + + static void log_warning(const String &str); + static void log_warning(const char *str); + static void log_warning(const char *p_function, const char *p_file, int p_line, const String &str); + static void log_warning(const char *p_function, const char *p_file, int p_line, const char *str); + + static void log_error(const String &str); + static void log_error(const char *str); + static void log_error(const char *p_function, const char *p_file, int p_line, const char *str); + static void log_error(const char *p_function, const char *p_file, int p_line, const String &str); + static void log_msg_error(const char *p_function, const char *p_file, int p_line, const char *p_msg, const char *str); + static void log_index_error(const char *p_function, const char *p_file, int p_line, const int index, const int size, const char *str); + + static String *get_string_ptr(const int p_default_size = 100); + static String *get_string_ptr(const char *p_function, const char *p_file, int p_line, const int p_default_size = 300); + static String *get_string_ptr(const char *p_prefix, const char *p_function, const char *p_file, int p_line, const int p_default_size = 300); + static void return_string_ptr(String *str); + + static String *get_trace_string_ptr(const int p_default_size = 100); + static String *get_message_string_ptr(const int p_default_size = 100); + static String *get_warning_string_ptr(const int p_default_size = 100); + static String *get_error_string_ptr(const int p_default_size = 100); + + static String *get_trace_string_ptr(const char *p_function, const char *p_file, int p_line, const int p_default_size = 300); + static String *get_message_string_ptr(const char *p_function, const char *p_file, int p_line, const int p_default_size = 300); + static String *get_warning_string_ptr(const char *p_function, const char *p_file, int p_line, const int p_default_size = 300); + static String *get_error_string_ptr(const char *p_function, const char *p_file, int p_line, const int p_default_size = 300); + + static void log_ptr(String *str); + static void log_ret_ptr(String *str); +}; + +#endif diff --git a/core/memory.h b/core/memory.h new file mode 100644 index 0000000..2eeb64e --- /dev/null +++ b/core/memory.h @@ -0,0 +1,14 @@ +#ifndef MEMORY_H +#define MEMORY_H + +//Simple memnew and memdelete macros so stuff that I took from the godotengine can use it. +//Not yet sure whether to use their allocator or not. +//This will be here until I decide. + +#define memnew(m_class) new m_class +#define memdelete(instance) delete instance +#define memnew_arr(m_class, size) new m_class[size] +#define memdelete_arr(instance) delete[] instance + +#endif + diff --git a/core/nodes/node.cpp b/core/nodes/node.cpp new file mode 100644 index 0000000..a27d330 --- /dev/null +++ b/core/nodes/node.cpp @@ -0,0 +1,122 @@ + +#include "node.h" + +#include "node_tree.h" + +bool Node::is_in_tree() const { + return _in_tree; +} + +Node *Node::get_parent() { + return _parent; +} +void Node::set_parent(Node *parent) { + if (_parent == parent) { + return; + } + + if (_parent) { + notification(NOTIFICATION_UNPARENTED); + } + + _parent = parent; + + if (_parent) { + notification(NOTIFICATION_PARENTED); + } +} + +int Node::get_child_count() { + return _children.size(); +} +Node *Node::get_child(int index) { + return _children[index]; +} +void Node::add_child(Node *child) { + ERR_FAIL_COND(!child); + ERR_FAIL_COND(child->get_parent()); + + _children.push_back(child); + child->set_parent(this); + + if (_in_tree) { + child->set_tree(_tree); + child->notification(NOTIFICATION_EXIT_TREE); + } + + notification(NOTIFICATION_CHILD_ADDED); +} +void Node::remove_child_index(int index) { + Node *c = _children[index]; + + _children.remove_keep_order(index); + c->set_parent(nullptr); + + notification(NOTIFICATION_CHILD_REMOVED); +} +void Node::remove_child(Node *child) { + ERR_FAIL_COND(!child); + + for (int i = 0; i < _children.size(); ++i) { + Node *c = _children[i]; + + if (c == child) { + _children.remove_keep_order(i); + child->set_parent(nullptr); + notification(NOTIFICATION_CHILD_REMOVED); + return; + } + } +} + +NodeTree *Node::get_tree() { + return _tree; +} + +void Node::set_tree(NodeTree *tree) { + if (_tree) { + _in_tree = false; + _notification(NOTIFICATION_EXIT_TREE); + } + + _tree = tree; + + if (_tree) { + _in_tree = true; + } + + for (int i = 0; i < _children.size(); ++i) { + _children[i]->set_tree(tree); + } + + if (_tree) { + _notification(NOTIFICATION_ENTER_TREE); + } + +} + +void Node::notification(const int what) { + _notification(what); + + for (int i = 0; i < _children.size(); ++i) { + _children[i]->notification(what); + } +} +void Node::_notification(const int what) { +} + +Node::Node() : + Object() { + + _in_tree = false; + _parent = nullptr; + _tree = nullptr; +} + +Node::~Node() { + for (int i = 0; i < _children.size(); ++i) { + delete _children[i]; + } + + _children.clear(); +} \ No newline at end of file diff --git a/core/nodes/node.h b/core/nodes/node.h new file mode 100644 index 0000000..7cf9a5b --- /dev/null +++ b/core/nodes/node.h @@ -0,0 +1,52 @@ +#ifndef NODE_H +#define NODE_H + +#include "core/object.h" +#include "core/containers/vector.h" + +class NodeTree; + +class Node : public Object { + RCPP_OBJECT(Node, Object); + +public: + enum { + NOTIFICATION_ENTER_TREE = 0, + NOTIFICATION_EXIT_TREE = 1, + NOTIFICATION_PARENTED = 2, + NOTIFICATION_UNPARENTED = 3, + NOTIFICATION_CHILD_ADDED = 4, + NOTIFICATION_CHILD_REMOVED = 5, + NOTIFICATION_CHILD_MOVED = 6, + NOTIFICATION_UPDATE = 7, + NOTIFICATION_TREE_WRITE_LOCKED = 8, + }; + + bool is_in_tree() const; + + Node *get_parent(); + void set_parent(Node *parent); + + int get_child_count(); + Node *get_child(int index); + void add_child(Node *child); + void remove_child_index(int index); + void remove_child(Node *child); + + NodeTree *get_tree(); + void set_tree(NodeTree *tree); + + virtual void notification(const int what); + virtual void _notification(const int what); + + Node(); + ~Node(); + +protected: + bool _in_tree; + Node * _parent; + Vector _children; + NodeTree *_tree; +}; + +#endif \ No newline at end of file diff --git a/core/nodes/node_tree.cpp b/core/nodes/node_tree.cpp new file mode 100644 index 0000000..c3b4664 --- /dev/null +++ b/core/nodes/node_tree.cpp @@ -0,0 +1,60 @@ + +#include "node_tree.h" + +#include "node.h" + +Node *NodeTree::get_root() { + return _root_node; +} + +void NodeTree::set_root(Node *root) { + if (_root_node) { + _root_node->set_tree(nullptr); + } + + _root_node = root; + + if (_root_node) { + _root_node->set_tree(this); + } +} + +void NodeTree::update() { + if (!_root_node) { + return; + } + + _root_node->notification(Node::NOTIFICATION_UPDATE); + + if (_write_lock_requested) { + _rw_lock.write_lock(); + _root_node->notification(Node::NOTIFICATION_TREE_WRITE_LOCKED); + _rw_lock.write_unlock(); + + _write_lock_requested = false; + } +} + +void NodeTree::_send_update() { + if (_root_node) { + _root_node->notification(Node::NOTIFICATION_UPDATE); + } +} + +float NodeTree::get_update_delta_time() { + return 0; +} + +NodeTree::NodeTree() : + Object() { + + _root_node = nullptr; + _update_interval = 0; +} + +NodeTree::~NodeTree() { + if (_root_node) { + delete _root_node; + _root_node = nullptr; + } +} \ No newline at end of file diff --git a/core/nodes/node_tree.h b/core/nodes/node_tree.h new file mode 100644 index 0000000..0833671 --- /dev/null +++ b/core/nodes/node_tree.h @@ -0,0 +1,35 @@ +#ifndef NODE_TREE_H +#define NODE_TREE_H + +#include "core/object.h" +#include "core/threading/rw_lock.h" + +class Node; + +class NodeTree : public Object { + RCPP_OBJECT(NodeTree, Object); + +public: + Node *get_root(); + virtual void set_root(Node *root); + + virtual void update(); + + void request_write_lock(); + + virtual float get_update_delta_time(); + + NodeTree(); + ~NodeTree(); + +protected: + virtual void _send_update(); + + Node *_root_node; + float _update_interval; + + bool _write_lock_requested; + RWLock _rw_lock; +}; + +#endif \ No newline at end of file diff --git a/core/object.cpp b/core/object.cpp new file mode 100644 index 0000000..e164e9f --- /dev/null +++ b/core/object.cpp @@ -0,0 +1,11 @@ +#include "object.h" + +#include "database/database.h" + +Object::Object() { + db = nullptr; +} + +Object::~Object() { + +} \ No newline at end of file diff --git a/core/object.h b/core/object.h new file mode 100644 index 0000000..fc2013c --- /dev/null +++ b/core/object.h @@ -0,0 +1,93 @@ +#ifndef OBJECT_H +#define OBJECT_H + +#include "core/string.h" +#include "core/containers/vector.h" + +class Database; + +//taken from GodotEngine's object.h +#define RCPP_OBJECT(m_class, m_inherits) \ +private: \ + void operator=(const m_class &p_rval) {} \ + \ +public: \ + virtual String get_class() const override { \ + return String(#m_class); \ + } \ + static void *get_class_ptr_static() { \ + static int ptr; \ + return &ptr; \ + } \ + static String get_class_static() { \ + return String(#m_class); \ + } \ + static String get_parent_class_static() { \ + return m_inherits::get_class_static(); \ + } \ + static void get_inheritance_list_static(Vector *p_inheritance_list) { \ + m_inherits::get_inheritance_list_static(p_inheritance_list); \ + p_inheritance_list->push_back(String(#m_class)); \ + } \ + static String inherits_static() { \ + return String(#m_inherits); \ + } \ + virtual bool is_class(const String &p_class) const override { return (p_class == (#m_class)) ? true : m_inherits::is_class(p_class); } \ + virtual bool is_class_ptr(void *p_ptr) const override { return (p_ptr == get_class_ptr_static()) ? true : m_inherits::is_class_ptr(p_ptr); } \ + \ + static void get_valid_parents_static(Vector *p_parents) { \ + if (m_class::_get_valid_parents_static != m_inherits::_get_valid_parents_static) { \ + m_class::_get_valid_parents_static(p_parents); \ + } \ + \ + m_inherits::get_valid_parents_static(p_parents); \ + } \ + \ +private: + +class Object { +public: + Database *db; + + virtual String get_class() const { return "Object"; } + static void *get_class_ptr_static() { + static int ptr; + return &ptr; + } + + static String get_class_static() { return "Object"; } + static String get_parent_class_static() { return String(); } + + static void get_inheritance_list_static(Vector *p_inheritance_list) { p_inheritance_list->push_back("Object"); } + + virtual bool is_class(const String &p_class) const { return (p_class == "Object"); } + virtual bool is_class_ptr(void *p_ptr) const { return get_class_ptr_static() == p_ptr; } + + static void get_valid_parents_static(Vector *p_parents) {} + static void _get_valid_parents_static(Vector *p_parents) {} + + Object(); + virtual ~Object(); + + template + static T *cast_to(Object *p_object) { + if (!p_object) + return NULL; + if (p_object->is_class_ptr(T::get_class_ptr_static())) + return static_cast(p_object); + else + return NULL; + } + + template + static const T *cast_to(const Object *p_object) { + if (!p_object) + return NULL; + if (p_object->is_class_ptr(T::get_class_ptr_static())) + return static_cast(p_object); + else + return NULL; + } +}; + +#endif \ No newline at end of file diff --git a/core/os/directory.cpp b/core/os/directory.cpp new file mode 100644 index 0000000..7dd2a8b --- /dev/null +++ b/core/os/directory.cpp @@ -0,0 +1,240 @@ +#include "directory.h" + +Error Directory::open_dir(const String &path, bool skip_specials) { + if (_dir_open) { + return ERR_CANT_ACQUIRE_RESOURCE; + } + + _skip_specials = skip_specials; + + if (tinydir_open(&_dir, path.c_str()) == -1) { + return FAILED; + } + + _dir_open = true; + + return OK; +} + +Error Directory::open_dir(const char *path, bool skip_specials) { + if (_dir_open) { + return ERR_CANT_ACQUIRE_RESOURCE; + } + + _skip_specials = skip_specials; + + if (tinydir_open(&_dir, path) == -1) { + return FAILED; + } + + _dir_open = true; + + return OK; +} + +void Directory::close_dir() { + if (!_dir_open) { + return; + } + + tinydir_close(&_dir); + + _dir_open = false; +} + +bool Directory::has_next() { + return _dir.has_next; +} +bool Directory::read() { + _read_file_result = tinydir_readfile(&_dir, &_file); + + return _read_file_result != -1; +} +bool Directory::next() { + if (!_dir.has_next) { + return false; + } + + bool rres = read(); + while (!rres && _dir.has_next) { + tinydir_next(&_dir); + rres = read(); + } + + if (!rres) { + return false; + } + + if (_dir.has_next) { + tinydir_next(&_dir); + } + + if (_skip_specials && current_is_dir() && current_is_special_dir()) { + return next(); + } + + return true; +} + +bool Directory::current_is_ok() { + return _read_file_result == 01; +} +String Directory::current_get_name() { + return String(_file.name); +} +String Directory::current_get_path() { + return String(_file.path); +} +String Directory::current_get_extension() { + return String(_file.extension); +} +const char *Directory::current_get_name_cstr() { + return _file.name; +} +const char *Directory::current_get_path_cstr() { + return _file.path; +} +const char *Directory::current_get_extension_cstr() { + return _file.extension; +} +bool Directory::current_is_file() { + return !_file.is_dir; +} +bool Directory::current_is_dir() { + return _file.is_dir; +} +bool Directory::current_is_special_dir() { + if (_file.name[0] == '.' && _file.name[1] == '\0' || _file.name[0] == '.' && _file.name[1] == '.') { + return true; + } + + return false; +} + +String Directory::read_file(const String &path) { + FILE *f = fopen(path.c_str(), "r"); + + String fd; + + ERR_FAIL_COND_V_MSG(!f, fd, "Error opening file! " + path); + + fseek(f, 0, SEEK_END); + long fsize = ftell(f); + fseek(f, 0, SEEK_SET); /* same as rewind(f); */ + + fd.resize(fsize); + + fread(fd.dataw(), 1, fsize, f); + fclose(f); + + return fd; +} + +Error Directory::read_file_into(const String &path, String *str) { + if (!str) { + return ERR_PARAMETER_RANGE_ERROR; + } + + FILE *f = fopen(path.c_str(), "r"); + + if (!f) { + return ERR_FILE_CANT_OPEN; + } + + fseek(f, 0, SEEK_END); + long fsize = ftell(f); + fseek(f, 0, SEEK_SET); /* same as rewind(f); */ + + str->resize(fsize); + + fread(str->dataw(), 1, fsize, f); + fclose(f); + + return OK; +} + +Vector Directory::read_file_bin(const String &path) { + FILE *f = fopen(path.c_str(), "rb"); + + Vector fd; + + ERR_FAIL_COND_V_MSG(!f, fd, "Error opening file! " + path); + + fseek(f, 0, SEEK_END); + long fsize = ftell(f); + fseek(f, 0, SEEK_SET); /* same as rewind(f); */ + + fd.resize(fsize); + + fread(fd.dataw(), 1, fsize, f); + fclose(f); + + return fd; +} + +Error Directory::read_file_into_bin(const String &path, Vector *data) { + if (!data) { + return ERR_PARAMETER_RANGE_ERROR; + } + + FILE *f = fopen(path.c_str(), "rb"); + + if (!f) { + return ERR_FILE_CANT_OPEN; + } + + fseek(f, 0, SEEK_END); + long fsize = ftell(f); + fseek(f, 0, SEEK_SET); /* same as rewind(f); */ + + data->resize(fsize); + + fread(data->dataw(), 1, fsize, f); + fclose(f); + + return OK; +} + +Error Directory::write_file(const String &path, const String &str) { + FILE *f = fopen(path.c_str(), "w"); + + if (!f) { + return ERR_FILE_CANT_OPEN; + } + + fwrite(str.data(), sizeof(char), str.size(), f); + fclose(f); + + return OK; +} + +Error Directory::write_file_bin(const String &path, const Vector &data) { + FILE *f = fopen(path.c_str(), "wb"); + + if (!f) { + return ERR_FILE_CANT_OPEN; + } + + fwrite(data.data(), sizeof(uint8_t), data.size(), f); + fclose(f); + + return OK; +} + +bool Directory::is_dir_open() { + return _dir_open; +} +bool Directory::is_dir_closed() { + return !_dir_open; +} + +Directory::Directory() { + _skip_specials = true; + _read_file_result = 0; + _dir_open = false; +} +Directory::~Directory() { + if (is_dir_open()) { + close_dir(); + } +} diff --git a/core/os/directory.h b/core/os/directory.h new file mode 100644 index 0000000..9325df4 --- /dev/null +++ b/core/os/directory.h @@ -0,0 +1,55 @@ +#ifndef DIRECTORY_H +#define DIRECTORY_H + +#include "core/string.h" +#include "core/error_list.h" +#include + +#include "core/reference.h" + +class Directory : public Reference { + RCPP_OBJECT(Directory, Reference); +public: + Error open_dir(const String &path, bool skip_specials = true); + Error open_dir(const char *path, bool skip_specials = true); + void close_dir(); + + bool has_next(); + bool read(); + bool next(); + + bool current_is_ok(); + String current_get_name(); + String current_get_path(); + String current_get_extension(); + const char *current_get_name_cstr(); + const char *current_get_path_cstr(); + const char *current_get_extension_cstr(); + bool current_is_file(); + bool current_is_dir(); + bool current_is_special_dir(); + + String read_file(const String &path); + Error read_file_into(const String &path, String *str); + Vector read_file_bin(const String &path); + Error read_file_into_bin(const String &path, Vector *data); + + Error write_file(const String &path, const String &str); + Error write_file_bin(const String &path, const Vector &data); + + bool is_dir_open(); + bool is_dir_closed(); + + Directory(); + virtual ~Directory(); + +private: + bool _skip_specials; + int _read_file_result; + tinydir_dir _dir; + tinydir_file _file; + + bool _dir_open; +}; + +#endif diff --git a/core/os/tinydir/COPYING b/core/os/tinydir/COPYING new file mode 100644 index 0000000..0d31329 --- /dev/null +++ b/core/os/tinydir/COPYING @@ -0,0 +1,26 @@ +Copyright (c) 2013-2016, tinydir authors: +- Cong Xu +- Lautis Sun +- Baudouin Feildel +- Andargor +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. + +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. diff --git a/core/os/tinydir/README.md b/core/os/tinydir/README.md new file mode 100644 index 0000000..8622c94 --- /dev/null +++ b/core/os/tinydir/README.md @@ -0,0 +1,79 @@ +TinyDir +======= +[![Build Status](https://travis-ci.org/cxong/tinydir.svg?branch=master)](https://travis-ci.org/cxong/tinydir) +[![Release](http://img.shields.io/github/release/cxong/tinydir.svg)](https://github.com/cxong/tinydir/releases/latest) + +Lightweight, portable and easy to integrate C directory and file reader. TinyDir wraps dirent for POSIX and FindFirstFile for Windows. + +Windows unicode is supported by defining `UNICODE` and `_UNICODE` before including `tinydir.h`. + +Example +======= + +There are two methods. Error checking omitted: + +```C +tinydir_dir dir; +tinydir_open(&dir, "/path/to/dir"); + +while (dir.has_next) +{ + tinydir_file file; + tinydir_readfile(&dir, &file); + + printf("%s", file.name); + if (file.is_dir) + { + printf("/"); + } + printf("\n"); + + tinydir_next(&dir); +} + +tinydir_close(&dir); +``` + +```C +tinydir_dir dir; +int i; +tinydir_open_sorted(&dir, "/path/to/dir"); + +for (i = 0; i < dir.n_files; i++) +{ + tinydir_file file; + tinydir_readfile_n(&dir, &file, i); + + printf("%s", file.name); + if (file.is_dir) + { + printf("/"); + } + printf("\n"); +} + +tinydir_close(&dir); +``` + +See the `/samples` folder for more examples, including an interactive command-line directory navigator. + +Language +======== + +ANSI C, or C90. + +Platforms +========= + +POSIX and Windows supported. Open to the possibility of supporting other platforms. + +License +======= + +Simplified BSD; if you use tinydir you can comply by including `tinydir.h` or `COPYING` somewhere in your package. + +Known Limitations +================= + +- Limited path and filename sizes +- [Possible race condition bug if folder being read has changing content](https://github.com/cxong/tinydir/issues/13) diff --git a/core/os/tinydir/package.json b/core/os/tinydir/package.json new file mode 100644 index 0000000..5ad2e9e --- /dev/null +++ b/core/os/tinydir/package.json @@ -0,0 +1,17 @@ +{ + "name": "tinydir", + "description": "Lightweight, portable and easy to integrate C directory and file reader", + "license": "BSD-2-Clause", + "keywords": [ + "dir", + "directory", + "file", + "reader", + "filesystem" + ], + "src": [ + "tinydir.h" + ], + "version": "1.2.4", + "repo": "cxong/tinydir" +} diff --git a/core/os/tinydir/tinydir.h b/core/os/tinydir/tinydir.h new file mode 100644 index 0000000..e08eb84 --- /dev/null +++ b/core/os/tinydir/tinydir.h @@ -0,0 +1,831 @@ +/* +Copyright (c) 2013-2019, tinydir authors: +- Cong Xu +- Lautis Sun +- Baudouin Feildel +- Andargor +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. + +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. +*/ +#ifndef TINYDIR_H +#define TINYDIR_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if ((defined _UNICODE) && !(defined UNICODE)) +#define UNICODE +#endif + +#if ((defined UNICODE) && !(defined _UNICODE)) +#define _UNICODE +#endif + +#include +#include +#include +#ifdef _MSC_VER +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include +# include +# pragma warning(push) +# pragma warning (disable : 4996) +#else +# include +# include +# include +# include +#endif +#ifdef __MINGW32__ +# include +#endif + + +/* types */ + +/* Windows UNICODE wide character support */ +#if defined _MSC_VER || defined __MINGW32__ +# define _tinydir_char_t TCHAR +# define TINYDIR_STRING(s) _TEXT(s) +# define _tinydir_strlen _tcslen +# define _tinydir_strcpy _tcscpy +# define _tinydir_strcat _tcscat +# define _tinydir_strcmp _tcscmp +# define _tinydir_strrchr _tcsrchr +# define _tinydir_strncmp _tcsncmp +#else +# define _tinydir_char_t char +# define TINYDIR_STRING(s) s +# define _tinydir_strlen strlen +# define _tinydir_strcpy strcpy +# define _tinydir_strcat strcat +# define _tinydir_strcmp strcmp +# define _tinydir_strrchr strrchr +# define _tinydir_strncmp strncmp +#endif + +#if (defined _MSC_VER || defined __MINGW32__) +# include +# define _TINYDIR_PATH_MAX MAX_PATH +#elif defined __linux__ +# include +# ifdef PATH_MAX +# define _TINYDIR_PATH_MAX PATH_MAX +# endif +#elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) +# include +# if defined(BSD) +# include +# ifdef PATH_MAX +# define _TINYDIR_PATH_MAX PATH_MAX +# endif +# endif +#endif + +#ifndef _TINYDIR_PATH_MAX +#define _TINYDIR_PATH_MAX 4096 +#endif + +#ifdef _MSC_VER +/* extra chars for the "\\*" mask */ +# define _TINYDIR_PATH_EXTRA 2 +#else +# define _TINYDIR_PATH_EXTRA 0 +#endif + +#define _TINYDIR_FILENAME_MAX 256 + +#if (defined _MSC_VER || defined __MINGW32__) +#define _TINYDIR_DRIVE_MAX 3 +#endif + +#ifdef _MSC_VER +# define _TINYDIR_FUNC static __inline +#elif !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L +# define _TINYDIR_FUNC static __inline__ +#else +# define _TINYDIR_FUNC static inline +#endif + +/* readdir_r usage; define TINYDIR_USE_READDIR_R to use it (if supported) */ +#ifdef TINYDIR_USE_READDIR_R + +/* readdir_r is a POSIX-only function, and may not be available under various + * environments/settings, e.g. MinGW. Use readdir fallback */ +#if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || _SVID_SOURCE ||\ + _POSIX_SOURCE +# define _TINYDIR_HAS_READDIR_R +#endif +#if _POSIX_C_SOURCE >= 200112L +# define _TINYDIR_HAS_FPATHCONF +# include +#endif +#if _BSD_SOURCE || _SVID_SOURCE || \ + (_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700) +# define _TINYDIR_HAS_DIRFD +# include +#endif +#if defined _TINYDIR_HAS_FPATHCONF && defined _TINYDIR_HAS_DIRFD &&\ + defined _PC_NAME_MAX +# define _TINYDIR_USE_FPATHCONF +#endif +#if defined __MINGW32__ || !defined _TINYDIR_HAS_READDIR_R ||\ + !(defined _TINYDIR_USE_FPATHCONF || defined NAME_MAX) +# define _TINYDIR_USE_READDIR +#endif + +/* Use readdir by default */ +#else +# define _TINYDIR_USE_READDIR +#endif + +/* MINGW32 has two versions of dirent, ASCII and UNICODE*/ +#ifndef _MSC_VER +#if (defined __MINGW32__) && (defined _UNICODE) +#define _TINYDIR_DIR _WDIR +#define _tinydir_dirent _wdirent +#define _tinydir_opendir _wopendir +#define _tinydir_readdir _wreaddir +#define _tinydir_closedir _wclosedir +#else +#define _TINYDIR_DIR DIR +#define _tinydir_dirent dirent +#define _tinydir_opendir opendir +#define _tinydir_readdir readdir +#define _tinydir_closedir closedir +#endif +#endif + +/* Allow user to use a custom allocator by defining _TINYDIR_MALLOC and _TINYDIR_FREE. */ +#if defined(_TINYDIR_MALLOC) && defined(_TINYDIR_FREE) +#elif !defined(_TINYDIR_MALLOC) && !defined(_TINYDIR_FREE) +#else +#error "Either define both alloc and free or none of them!" +#endif + +#if !defined(_TINYDIR_MALLOC) + #define _TINYDIR_MALLOC(_size) malloc(_size) + #define _TINYDIR_FREE(_ptr) free(_ptr) +#endif /* !defined(_TINYDIR_MALLOC) */ + +typedef struct tinydir_file +{ + _tinydir_char_t path[_TINYDIR_PATH_MAX]; + _tinydir_char_t name[_TINYDIR_FILENAME_MAX]; + _tinydir_char_t *extension; + int is_dir; + int is_reg; + +#ifndef _MSC_VER +#ifdef __MINGW32__ + struct _stat _s; +#else + struct stat _s; +#endif +#endif +} tinydir_file; + +typedef struct tinydir_dir +{ + _tinydir_char_t path[_TINYDIR_PATH_MAX]; + int has_next; + size_t n_files; + + tinydir_file *_files; +#ifdef _MSC_VER + HANDLE _h; + WIN32_FIND_DATA _f; +#else + _TINYDIR_DIR *_d; + struct _tinydir_dirent *_e; +#ifndef _TINYDIR_USE_READDIR + struct _tinydir_dirent *_ep; +#endif +#endif +} tinydir_dir; + + +/* declarations */ + +_TINYDIR_FUNC +int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path); +_TINYDIR_FUNC +int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path); +_TINYDIR_FUNC +void tinydir_close(tinydir_dir *dir); + +_TINYDIR_FUNC +int tinydir_next(tinydir_dir *dir); +_TINYDIR_FUNC +int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file); +_TINYDIR_FUNC +int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i); +_TINYDIR_FUNC +int tinydir_open_subdir_n(tinydir_dir *dir, size_t i); + +_TINYDIR_FUNC +int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path); +_TINYDIR_FUNC +void _tinydir_get_ext(tinydir_file *file); +_TINYDIR_FUNC +int _tinydir_file_cmp(const void *a, const void *b); +#ifndef _MSC_VER +#ifndef _TINYDIR_USE_READDIR +_TINYDIR_FUNC +size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp); +#endif +#endif + + +/* definitions*/ + +_TINYDIR_FUNC +int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path) +{ +#ifndef _MSC_VER +#ifndef _TINYDIR_USE_READDIR + int error; + int size; /* using int size */ +#endif +#else + _tinydir_char_t path_buf[_TINYDIR_PATH_MAX]; +#endif + _tinydir_char_t *pathp; + + if (dir == NULL || path == NULL || _tinydir_strlen(path) == 0) + { + errno = EINVAL; + return -1; + } + if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) + { + errno = ENAMETOOLONG; + return -1; + } + + /* initialise dir */ + dir->_files = NULL; +#ifdef _MSC_VER + dir->_h = INVALID_HANDLE_VALUE; +#else + dir->_d = NULL; +#ifndef _TINYDIR_USE_READDIR + dir->_ep = NULL; +#endif +#endif + tinydir_close(dir); + + _tinydir_strcpy(dir->path, path); + /* Remove trailing slashes */ + pathp = &dir->path[_tinydir_strlen(dir->path) - 1]; + while (pathp != dir->path && (*pathp == TINYDIR_STRING('\\') || *pathp == TINYDIR_STRING('/'))) + { + *pathp = TINYDIR_STRING('\0'); + pathp++; + } +#ifdef _MSC_VER + _tinydir_strcpy(path_buf, dir->path); + _tinydir_strcat(path_buf, TINYDIR_STRING("\\*")); +#if (defined WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) + dir->_h = FindFirstFileEx(path_buf, FindExInfoStandard, &dir->_f, FindExSearchNameMatch, NULL, 0); +#else + dir->_h = FindFirstFile(path_buf, &dir->_f); +#endif + if (dir->_h == INVALID_HANDLE_VALUE) + { + errno = ENOENT; +#else + dir->_d = _tinydir_opendir(path); + if (dir->_d == NULL) + { +#endif + goto bail; + } + + /* read first file */ + dir->has_next = 1; +#ifndef _MSC_VER +#ifdef _TINYDIR_USE_READDIR + dir->_e = _tinydir_readdir(dir->_d); +#else + /* allocate dirent buffer for readdir_r */ + size = _tinydir_dirent_buf_size(dir->_d); /* conversion to int */ + if (size == -1) return -1; + dir->_ep = (struct _tinydir_dirent*)_TINYDIR_MALLOC(size); + if (dir->_ep == NULL) return -1; + + error = readdir_r(dir->_d, dir->_ep, &dir->_e); + if (error != 0) return -1; +#endif + if (dir->_e == NULL) + { + dir->has_next = 0; + } +#endif + + return 0; + +bail: + tinydir_close(dir); + return -1; +} + +_TINYDIR_FUNC +int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path) +{ + /* Count the number of files first, to pre-allocate the files array */ + size_t n_files = 0; + if (tinydir_open(dir, path) == -1) + { + return -1; + } + while (dir->has_next) + { + n_files++; + if (tinydir_next(dir) == -1) + { + goto bail; + } + } + tinydir_close(dir); + + if (n_files == 0 || tinydir_open(dir, path) == -1) + { + return -1; + } + + dir->n_files = 0; + dir->_files = (tinydir_file *)_TINYDIR_MALLOC(sizeof *dir->_files * n_files); + if (dir->_files == NULL) + { + goto bail; + } + while (dir->has_next) + { + tinydir_file *p_file; + dir->n_files++; + + p_file = &dir->_files[dir->n_files - 1]; + if (tinydir_readfile(dir, p_file) == -1) + { + goto bail; + } + + if (tinydir_next(dir) == -1) + { + goto bail; + } + + /* Just in case the number of files has changed between the first and + second reads, terminate without writing into unallocated memory */ + if (dir->n_files == n_files) + { + break; + } + } + + qsort(dir->_files, dir->n_files, sizeof(tinydir_file), _tinydir_file_cmp); + + return 0; + +bail: + tinydir_close(dir); + return -1; +} + +_TINYDIR_FUNC +void tinydir_close(tinydir_dir *dir) +{ + if (dir == NULL) + { + return; + } + + memset(dir->path, 0, sizeof(dir->path)); + dir->has_next = 0; + dir->n_files = 0; + _TINYDIR_FREE(dir->_files); + dir->_files = NULL; +#ifdef _MSC_VER + if (dir->_h != INVALID_HANDLE_VALUE) + { + FindClose(dir->_h); + } + dir->_h = INVALID_HANDLE_VALUE; +#else + if (dir->_d) + { + _tinydir_closedir(dir->_d); + } + dir->_d = NULL; + dir->_e = NULL; +#ifndef _TINYDIR_USE_READDIR + _TINYDIR_FREE(dir->_ep); + dir->_ep = NULL; +#endif +#endif +} + +_TINYDIR_FUNC +int tinydir_next(tinydir_dir *dir) +{ + if (dir == NULL) + { + errno = EINVAL; + return -1; + } + if (!dir->has_next) + { + errno = ENOENT; + return -1; + } + +#ifdef _MSC_VER + if (FindNextFile(dir->_h, &dir->_f) == 0) +#else +#ifdef _TINYDIR_USE_READDIR + dir->_e = _tinydir_readdir(dir->_d); +#else + if (dir->_ep == NULL) + { + return -1; + } + if (readdir_r(dir->_d, dir->_ep, &dir->_e) != 0) + { + return -1; + } +#endif + if (dir->_e == NULL) +#endif + { + dir->has_next = 0; +#ifdef _MSC_VER + if (GetLastError() != ERROR_SUCCESS && + GetLastError() != ERROR_NO_MORE_FILES) + { + tinydir_close(dir); + errno = EIO; + return -1; + } +#endif + } + + return 0; +} + +_TINYDIR_FUNC +int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file) +{ + const _tinydir_char_t *filename; + if (dir == NULL || file == NULL) + { + errno = EINVAL; + return -1; + } +#ifdef _MSC_VER + if (dir->_h == INVALID_HANDLE_VALUE) +#else + if (dir->_e == NULL) +#endif + { + errno = ENOENT; + return -1; + } + filename = +#ifdef _MSC_VER + dir->_f.cFileName; +#else + dir->_e->d_name; +#endif + if (_tinydir_strlen(dir->path) + + _tinydir_strlen(filename) + 1 + _TINYDIR_PATH_EXTRA >= + _TINYDIR_PATH_MAX) + { + /* the path for the file will be too long */ + errno = ENAMETOOLONG; + return -1; + } + if (_tinydir_strlen(filename) >= _TINYDIR_FILENAME_MAX) + { + errno = ENAMETOOLONG; + return -1; + } + + _tinydir_strcpy(file->path, dir->path); + if (_tinydir_strcmp(dir->path, TINYDIR_STRING("/")) != 0) + _tinydir_strcat(file->path, TINYDIR_STRING("/")); + _tinydir_strcpy(file->name, filename); + _tinydir_strcat(file->path, filename); +#ifndef _MSC_VER +#ifdef __MINGW32__ + if (_tstat( +#elif (defined _BSD_SOURCE) || (defined _DEFAULT_SOURCE) \ + || ((defined _XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500)) \ + || ((defined _POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L)) + if (lstat( +#else + if (stat( +#endif + file->path, &file->_s) == -1) + { + return -1; + } +#endif + _tinydir_get_ext(file); + + file->is_dir = +#ifdef _MSC_VER + !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); +#else + S_ISDIR(file->_s.st_mode); +#endif + file->is_reg = +#ifdef _MSC_VER + !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) || + ( + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) && + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) && +#ifdef FILE_ATTRIBUTE_INTEGRITY_STREAM + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_INTEGRITY_STREAM) && +#endif +#ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NO_SCRUB_DATA) && +#endif + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) && + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY)); +#else + S_ISREG(file->_s.st_mode); +#endif + + return 0; +} + +_TINYDIR_FUNC +int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i) +{ + if (dir == NULL || file == NULL) + { + errno = EINVAL; + return -1; + } + if (i >= dir->n_files) + { + errno = ENOENT; + return -1; + } + + memcpy(file, &dir->_files[i], sizeof(tinydir_file)); + _tinydir_get_ext(file); + + return 0; +} + +_TINYDIR_FUNC +int tinydir_open_subdir_n(tinydir_dir *dir, size_t i) +{ + _tinydir_char_t path[_TINYDIR_PATH_MAX]; + if (dir == NULL) + { + errno = EINVAL; + return -1; + } + if (i >= dir->n_files || !dir->_files[i].is_dir) + { + errno = ENOENT; + return -1; + } + + _tinydir_strcpy(path, dir->_files[i].path); + tinydir_close(dir); + if (tinydir_open_sorted(dir, path) == -1) + { + return -1; + } + + return 0; +} + +/* Open a single file given its path */ +_TINYDIR_FUNC +int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path) +{ + tinydir_dir dir; + int result = 0; + int found = 0; + _tinydir_char_t dir_name_buf[_TINYDIR_PATH_MAX]; + _tinydir_char_t file_name_buf[_TINYDIR_FILENAME_MAX]; + _tinydir_char_t *dir_name; + _tinydir_char_t *base_name; +#if (defined _MSC_VER || defined __MINGW32__) + _tinydir_char_t drive_buf[_TINYDIR_PATH_MAX]; + _tinydir_char_t ext_buf[_TINYDIR_FILENAME_MAX]; +#endif + + if (file == NULL || path == NULL || _tinydir_strlen(path) == 0) + { + errno = EINVAL; + return -1; + } + if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) + { + errno = ENAMETOOLONG; + return -1; + } + + /* Get the parent path */ +#if (defined _MSC_VER || defined __MINGW32__) +#if ((defined _MSC_VER) && (_MSC_VER >= 1400)) + errno = _tsplitpath_s( + path, + drive_buf, _TINYDIR_DRIVE_MAX, + dir_name_buf, _TINYDIR_FILENAME_MAX, + file_name_buf, _TINYDIR_FILENAME_MAX, + ext_buf, _TINYDIR_FILENAME_MAX); +#else + _tsplitpath( + path, + drive_buf, + dir_name_buf, + file_name_buf, + ext_buf); +#endif + + if (errno) + { + return -1; + } + +/* _splitpath_s not work fine with only filename and widechar support */ +#ifdef _UNICODE + if (drive_buf[0] == L'\xFEFE') + drive_buf[0] = '\0'; + if (dir_name_buf[0] == L'\xFEFE') + dir_name_buf[0] = '\0'; +#endif + + /* Emulate the behavior of dirname by returning "." for dir name if it's + empty */ + if (drive_buf[0] == '\0' && dir_name_buf[0] == '\0') + { + _tinydir_strcpy(dir_name_buf, TINYDIR_STRING(".")); + } + /* Concatenate the drive letter and dir name to form full dir name */ + _tinydir_strcat(drive_buf, dir_name_buf); + dir_name = drive_buf; + /* Concatenate the file name and extension to form base name */ + _tinydir_strcat(file_name_buf, ext_buf); + base_name = file_name_buf; +#else + _tinydir_strcpy(dir_name_buf, path); + dir_name = dirname(dir_name_buf); + _tinydir_strcpy(file_name_buf, path); + base_name = basename(file_name_buf); +#endif + + /* Special case: if the path is a root dir, open the parent dir as the file */ +#if (defined _MSC_VER || defined __MINGW32__) + if (_tinydir_strlen(base_name) == 0) +#else + if ((_tinydir_strcmp(base_name, TINYDIR_STRING("/"))) == 0) +#endif + { + memset(file, 0, sizeof * file); + file->is_dir = 1; + file->is_reg = 0; + _tinydir_strcpy(file->path, dir_name); + file->extension = file->path + _tinydir_strlen(file->path); + return 0; + } + + /* Open the parent directory */ + if (tinydir_open(&dir, dir_name) == -1) + { + return -1; + } + + /* Read through the parent directory and look for the file */ + while (dir.has_next) + { + if (tinydir_readfile(&dir, file) == -1) + { + result = -1; + goto bail; + } + if (_tinydir_strcmp(file->name, base_name) == 0) + { + /* File found */ + found = 1; + break; + } + tinydir_next(&dir); + } + if (!found) + { + result = -1; + errno = ENOENT; + } + +bail: + tinydir_close(&dir); + return result; +} + +_TINYDIR_FUNC +void _tinydir_get_ext(tinydir_file *file) +{ + _tinydir_char_t *period = _tinydir_strrchr(file->name, TINYDIR_STRING('.')); + if (period == NULL) + { + file->extension = &(file->name[_tinydir_strlen(file->name)]); + } + else + { + file->extension = period + 1; + } +} + +_TINYDIR_FUNC +int _tinydir_file_cmp(const void *a, const void *b) +{ + const tinydir_file *fa = (const tinydir_file *)a; + const tinydir_file *fb = (const tinydir_file *)b; + if (fa->is_dir != fb->is_dir) + { + return -(fa->is_dir - fb->is_dir); + } + return _tinydir_strncmp(fa->name, fb->name, _TINYDIR_FILENAME_MAX); +} + +#ifndef _MSC_VER +#ifndef _TINYDIR_USE_READDIR +/* +The following authored by Ben Hutchings +from https://womble.decadent.org.uk/readdir_r-advisory.html +*/ +/* Calculate the required buffer size (in bytes) for directory * +* entries read from the given directory handle. Return -1 if this * +* this cannot be done. * +* * +* This code does not trust values of NAME_MAX that are less than * +* 255, since some systems (including at least HP-UX) incorrectly * +* define it to be a smaller value. */ +_TINYDIR_FUNC +size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp) +{ + long name_max; + size_t name_end; + /* parameter may be unused */ + (void)dirp; + +#if defined _TINYDIR_USE_FPATHCONF + name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX); + if (name_max == -1) +#if defined(NAME_MAX) + name_max = (NAME_MAX > 255) ? NAME_MAX : 255; +#else + return (size_t)(-1); +#endif +#elif defined(NAME_MAX) + name_max = (NAME_MAX > 255) ? NAME_MAX : 255; +#else +#error "buffer size for readdir_r cannot be determined" +#endif + name_end = (size_t)offsetof(struct _tinydir_dirent, d_name) + name_max + 1; + return (name_end > sizeof(struct _tinydir_dirent) ? + name_end : sizeof(struct _tinydir_dirent)); +} +#endif +#endif + +#ifdef __cplusplus +} +#endif + +# if defined (_MSC_VER) +# pragma warning(pop) +# endif + +#endif diff --git a/core/reference.cpp b/core/reference.cpp new file mode 100644 index 0000000..8de3642 --- /dev/null +++ b/core/reference.cpp @@ -0,0 +1,54 @@ +#include "reference.h" + +bool Reference::init_ref() { + if (reference()) { + if (!is_referenced() && refcount_init.unref()) { + unreference(); // first referencing is already 1, so compensate for the ref above + } + + return true; + } else { + return false; + } +} + +int Reference::reference_get_count() const { + return refcount.get(); +} + +bool Reference::reference() { + uint32_t rc_val = refcount.refval(); + bool success = rc_val != 0; + + return success; +} + +bool Reference::unreference() { + uint32_t rc_val = refcount.unrefval(); + bool die = rc_val == 0; + + return die; +} + +Reference::Reference() : + Object() { + refcount.init(); + refcount_init.init(); +} + +Reference::~Reference() { +} + +/* +void WeakRef::set_obj(Object *p_object) { + //ref = p_object ? p_object->get_instance_id() : 0; +} + +void WeakRef::set_ref(const REF &p_ref) { + //ref = p_ref.is_valid() ? p_ref->get_instance_id() : 0; +} + +WeakRef::WeakRef() : + ref(0) { +} +*/ \ No newline at end of file diff --git a/core/reference.h b/core/reference.h new file mode 100644 index 0000000..2871ed7 --- /dev/null +++ b/core/reference.h @@ -0,0 +1,201 @@ +#ifndef REFERENCE_H +#define REFERENCE_H + +// Most of the code is from the godot engine's reference.h +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ + + +#include "safe_refcount.h" +#include "object.h" +#include "object_id.h" +#include "memory.h" + +class Reference : public Object { + RCPP_OBJECT(Reference, Object); + +public: + /*_FORCE_INLINE_*/ bool is_referenced() const { return refcount_init.get() != 1; } + bool init_ref(); + bool reference(); // returns false if refcount is at zero and didn't get increased + bool unreference(); + int reference_get_count() const; + + Reference(); + virtual ~Reference(); + +private: + SafeRefCount refcount; + SafeRefCount refcount_init; +}; + +template +class Ref { + T *reference; + + void ref(const Ref &p_from) { + if (p_from.reference == reference) { + return; + } + + unref(); + + reference = p_from.reference; + if (reference) { + reference->reference(); + } + } + + void ref_pointer(T *p_ref) { + //ERR_FAIL_COND(!p_ref); + + if (p_ref->init_ref()) { + reference = p_ref; + } + } + + //virtual Reference * get_reference() const { return reference; } +public: + _FORCE_INLINE_ bool operator==(const T *p_ptr) const { + return reference == p_ptr; + } + _FORCE_INLINE_ bool operator!=(const T *p_ptr) const { + return reference != p_ptr; + } + + _FORCE_INLINE_ bool operator<(const Ref &p_r) const { + return reference < p_r.reference; + } + _FORCE_INLINE_ bool operator==(const Ref &p_r) const { + return reference == p_r.reference; + } + _FORCE_INLINE_ bool operator!=(const Ref &p_r) const { + return reference != p_r.reference; + } + + _FORCE_INLINE_ T *operator->() { + return reference; + } + + _FORCE_INLINE_ T *operator*() { + return reference; + } + + _FORCE_INLINE_ const T *operator->() const { + return reference; + } + + _FORCE_INLINE_ const T *ptr() const { + return reference; + } + _FORCE_INLINE_ T *ptr() { + return reference; + } + + _FORCE_INLINE_ const T *operator*() const { + return reference; + } + + void operator=(const Ref &p_from) { + ref(p_from); + } + + template + void operator=(const Ref &p_from) { + Reference *refb = const_cast(static_cast(p_from.ptr())); + if (!refb) { + unref(); + return; + } + Ref r; + r.reference = Object::cast_to(refb); + ref(r); + r.reference = nullptr; + } + + template + void reference_ptr(T_Other *p_ptr) { + if (reference == p_ptr) { + return; + } + unref(); + + T *r = Object::cast_to(p_ptr); + if (r) { + ref_pointer(r); + } + } + + Ref(const Ref &p_from) { + reference = nullptr; + ref(p_from); + } + + template + Ref(const Ref &p_from) { + reference = nullptr; + Reference *refb = const_cast(static_cast(p_from.ptr())); + if (!refb) { + unref(); + return; + } + Ref r; + r.reference = Object::cast_to(refb); + ref(r); + r.reference = nullptr; + } + + Ref(T *p_reference) { + reference = nullptr; + if (p_reference) { + ref_pointer(p_reference); + } + } + + inline bool is_valid() const { return reference != nullptr; } + inline bool is_null() const { return reference == nullptr; } + + void unref() { + //TODO this should be moved to mutexes, since this engine does not really + // do a lot of referencing on references and stuff + // mutexes will avoid more crashes? + + if (reference && reference->unreference()) { + memdelete(reference); + } + reference = nullptr; + } + + void instance() { + ref(memnew(T)); + } + + Ref() { + reference = nullptr; + } + + ~Ref() { + unref(); + } +}; + +typedef Ref REF; + +/* +class WeakRef : public Reference { + RCPP_OBJECT(WeakRef, Reference); + + ObjectID ref; + +protected: + static void _bind_methods(); + +public: + void set_obj(Object *p_object); + void set_ref(const REF &p_ref); + + WeakRef(); +}; +*/ + +#endif \ No newline at end of file diff --git a/core/signal.cpp b/core/signal.cpp new file mode 100644 index 0000000..d0f294c --- /dev/null +++ b/core/signal.cpp @@ -0,0 +1,118 @@ +#include "signal.h" + +void Signal::connect_static(void (*func)(Signal *)) { + StaticSignalEntry *se = new StaticSignalEntry(); + se->func = func; + + entries.push_back(se); +} +void Signal::disconnect_static(void (*func)(Signal *)) { + for (int i = 0; i < entries.size(); ++i) { + SignalEntry *e = entries[i]; + + if (e->type == SIGNAL_ENTRY_TYPE_STATIC) { + StaticSignalEntry *se = static_cast(e); + + if (se->func == func) { + entries.remove_keep_order(i); + return; + } + } + } +} +bool Signal::is_connected_static(void (*func)(Signal *)) { + for (int i = 0; i < entries.size(); ++i) { + SignalEntry *e = entries[i]; + + if (e->type == SIGNAL_ENTRY_TYPE_STATIC) { + StaticSignalEntry *se = static_cast(e); + + if (se->func == func) { + return true; + } + } + } + + return false; +} + +void Signal::emit(Object *p_emitter) { + emitter = p_emitter; + + for (int i = 0; i < entries.size(); ++i) { + entries[i]->call(this); + } +} + +void Signal::emit(Object *p_emitter, const Variant &p1) { + emitter = p_emitter; + + params.push_back(p1); + + for (int i = 0; i < entries.size(); ++i) { + entries[i]->call(this); + } + + params.clear(); +} +void Signal::emit(Object *p_emitter, const Variant &p1, const Variant &p2) { + emitter = p_emitter; + + params.push_back(p1); + params.push_back(p2); + + for (int i = 0; i < entries.size(); ++i) { + entries[i]->call(this); + } + + params.clear(); +} +void Signal::emit(Object *p_emitter, const Variant &p1, const Variant &p2, const Variant &p3) { + emitter = p_emitter; + + params.push_back(p1); + params.push_back(p2); + params.push_back(p3); + + for (int i = 0; i < entries.size(); ++i) { + entries[i]->call(this); + } + + params.clear(); +} + +void Signal::emit(Object *p_emitter, const Variant &p1, const Variant &p2, const Variant &p3, const Variant &p4) { + emitter = p_emitter; + + params.push_back(p1); + params.push_back(p2); + params.push_back(p3); + params.push_back(p4); + + for (int i = 0; i < entries.size(); ++i) { + entries[i]->call(this); + } + + params.clear(); +} + +void Signal::emit(Object *p_emitter, const Variant &p1, const Variant &p2, const Variant &p3, const Variant &p4, const Variant &p5) { + emitter = p_emitter; + + params.push_back(p1); + params.push_back(p2); + params.push_back(p3); + params.push_back(p4); + params.push_back(p5); + + for (int i = 0; i < entries.size(); ++i) { + entries[i]->call(this); + } + + params.clear(); +} + +Signal::Signal() { +} +Signal::~Signal() { +} \ No newline at end of file diff --git a/core/signal.h b/core/signal.h new file mode 100644 index 0000000..32a3cb7 --- /dev/null +++ b/core/signal.h @@ -0,0 +1,172 @@ +#ifndef SIGNAL_H +#define SIGNAL_H + +#include "core/containers/vector.h" +#include "core/string.h" +#include "core/variant.h" + +#include "reference.h" + +class Signal { +public: + Object *emitter; + Vector params; + Vector static_data; + + template + void connect(T *obj, void (*func)(T*, Signal *)); + template + void disconnect(T *obj, void (*func)(T*, Signal *)); + template + bool is_connected(T *obj, void (*func)(T*, Signal *)); + + void connect_static(void (*func)(Signal *)); + void disconnect_static(void (*func)(Signal *)); + bool is_connected_static(void (*func)(Signal *)); + + void emit(Object *p_emitter); + void emit(Object *p_emitter, const Variant &p1); + void emit(Object *p_emitter, const Variant &p1, const Variant &p2); + void emit(Object *p_emitter, const Variant &p1, const Variant &p2, const Variant &p3); + void emit(Object *p_emitter, const Variant &p1, const Variant &p2, const Variant &p3, const Variant &p4); + void emit(Object *p_emitter, const Variant &p1, const Variant &p2, const Variant &p3, const Variant &p4, const Variant &p5); + + Signal(); + ~Signal(); + +protected: + enum SignalEntryType { + SIGNAL_ENTRY_TYPE_NONE = 0, + SIGNAL_ENTRY_TYPE_STATIC = 1, + SIGNAL_ENTRY_TYPE_CLASS = 2, + }; + + struct SignalEntry { + SignalEntryType type; + + virtual void call(Signal *s) { + } + + SignalEntry() { + type = SIGNAL_ENTRY_TYPE_NONE; + } + }; + + struct StaticSignalEntry : public SignalEntry { + void (*func)(Signal *); + + virtual void call(Signal *s) { + func(s); + } + + StaticSignalEntry() { + type = SIGNAL_ENTRY_TYPE_STATIC; + func = nullptr; + } + }; + + struct ClassSignalEntry : public SignalEntry { + + virtual void* get_obj_ptr() { + return nullptr; + } + + virtual void* get_func_ptr() { + return nullptr; + } + + ClassSignalEntry() { + type = SIGNAL_ENTRY_TYPE_CLASS; + } + }; + + template + struct ClassSignalEntrySpec : public ClassSignalEntry { + union { + T* obj; + void* obj_ptr; + }; + union { + void (*func)(T*, Signal *); + void* func_ptr; + }; + + virtual void call(Signal *s) { + func(obj, s); + } + + void* get_obj_ptr() { + return obj_ptr; + } + + void* get_func_ptr() { + return func_ptr; + } + + ClassSignalEntrySpec() { + obj = nullptr; + func = nullptr; + } + }; + +protected: + Vector entries; +}; + +template +void Signal::connect(T *obj, void (*func)(T*, Signal *)) { + ClassSignalEntrySpec *ce = new ClassSignalEntrySpec(); + ce->obj = obj; + ce->func = func; + + entries.push_back(ce); +} + +template +void Signal::disconnect(T *obj, void (*func)(T*, Signal *)) { + ClassSignalEntrySpec t; + t.obj = obj; + t.func = func; + + void* obj_ptr = t.obj_ptr; + void* func_ptr = t.func_ptr; + + for (int i = 0; i < entries.size(); ++i) { + SignalEntry *e = entries[i]; + + if (e->type == SIGNAL_ENTRY_TYPE_CLASS) { + ClassSignalEntry *se = static_cast(e); + + if (se->get_obj_ptr() == obj_ptr && se->get_func_ptr() == func_ptr) { + entries.remove_keep_order(i); + return; + } + } + } +} + +template +bool Signal::is_connected(T *obj, void (*func)(T*, Signal *)) { + ClassSignalEntrySpec t; + t.obj = obj; + t.func = func; + + void* obj_ptr = t.obj_ptr; + void* func_ptr = t.func_ptr; + + for (int i = 0; i < entries.size(); ++i) { + SignalEntry *e = entries[i]; + + if (e->type == SIGNAL_ENTRY_TYPE_CLASS) { + ClassSignalEntry *se = static_cast(e); + + if (se->get_obj_ptr() == obj_ptr && se->get_func_ptr() == func_ptr) { + return true; + } + } + } + + return false; +} + +#endif \ No newline at end of file diff --git a/core/string.cpp b/core/string.cpp new file mode 100644 index 0000000..ef69c5d --- /dev/null +++ b/core/string.cpp @@ -0,0 +1,2528 @@ +#include "string.h" + +#include "core/math/math.h" +#include "error_macros.h" +#include "variant.h" +#include +#include +#include + +static const int MAX_DECIMALS = 32; + +void String::push_back(const char element) { + ensure_capacity(_size + 1); + + _data[_size++] = element; + _data[_size] = '\0'; +} + +void String::push_back(const wchar_t element) { + ensure_capacity(_size + 1); + + _data[_size++] = element; + _data[_size] = '\0'; +} + +void String::pop_back() { + if (_size == 0) { + return; + } + + --_size; + + _data[_size] = '\0'; +} + +void String::remove(const int index) { + _data[index] = _data[_size - 1]; + + --_size; + + _data[_size] = '\0'; +} + +void String::erase(const char element) { + int index = find(element); + + if (index != -1) { + remove(index); + } +} + +void String::erase(const int start_index, const int length) { + ERR_FAIL_INDEX(start_index, _size); + + int sil = start_index + length; + + if (sil > _size) { + sil = _size; + } + + for (int i = start_index; i < _size; ++i) { + _data[i] = _data[sil + i]; + } + + _size -= length; + + _data[_size] = '\0'; +} + +void String::clear() { + _size = 0; +} + +bool String::empty() const { + return _size == 0; +} + +char String::get(const int index) { + return _data[index]; +} + +const char String::get(const int index) const { + return _data[index]; +} + +void String::set(const int index, const char value) { + _data[index] = value; +} + +int String::size() const { + return _size; +} + +int String::capacity() const { + return _actual_size; +} + +void String::ensure_capacity(const int capacity) { + if (capacity < _actual_size) { + return; + } + + int tsize = capacity + _grow_by; + + char *nd = new char[tsize]; + + if (_data) { + for (int i = 0; i < _size; ++i) { + nd[i] = _data[i]; + } + + delete[] _data; + } + + _data = nd; + _actual_size = tsize; +} + +void String::resize(const int s) { + ensure_capacity(s + 1); // +1 for the null terminator + + _size = s; + + _data[_size] = '\0'; +} + +int String::find(const char val, const int from) const { + for (int i = from; i < _size; ++i) { + if (_data[i] == val) { + return i; + } + } + + return -1; +} + +int String::find(const String &val, const int from) const { + int ve = _size - val.size() + 1; + + for (int i = from; i < ve; ++i) { + bool found = true; + for (int j = 0; j < val.size(); ++j) { + if (_data[i + j] != val[j]) { + found = false; + break; + } + } + + if (found) { + return i; + } + } + + return -1; +} + +int String::find_reversed(const char val, const int from) const { + if (_size == 0) { + return -1; + } + + int f; + + if (from < 0) { + f = _size - 1; + } else { + f = from - 1; + } + + for (int i = f; i >= 0; --i) { + if (_data[i] == val) { + return i; + } + } + + return -1; +} + +int String::find_reversed(const String &val, const int from) const { + if (_size == 0) { + return -1; + } + + if (_size < val.size()) { + return -1; + } + + int ve; + + if (from < 0) { + ve = _size - val.size() - 1; + } else { + ve = from - 1; + } + + for (int i = ve; i >= 0; --i) { + bool found = true; + for (int j = 0; j < val.size(); ++j) { + if (_data[i + j] != val[j]) { + found = false; + break; + } + } + + if (found) { + return i; + } + } + + return -1; +} + +void String::get_substr(char *into_buf, const int start_index, const int len) { + ERR_FAIL_INDEX(start_index + len - 1, _size); + + int j = 0; + for (int i = start_index; i < start_index + len; ++i) { + into_buf[j++] = _data[i]; + } +} + +void String::get_substr_nt(char *into_buf, const int start_index, const int len) { + ERR_FAIL_INDEX(start_index + len - 1, _size); + + int j = 0; + for (int i = start_index; i < start_index + len; ++i) { + into_buf[j++] = _data[i]; + } + + into_buf[len + 1] = '\0'; +} + +String String::substr(const int start_index, const int len) const { + if (len == 0) { + return String(); + } + + ERR_FAIL_INDEX_V(start_index, _size, String()); + + int sil = start_index + len; + + ERR_FAIL_INDEX_V(sil, _size + 1, String()); + + String str; + str.ensure_capacity(len + 1); + for (int i = start_index; i < sil; ++i) { + str._data[str._size++] = _data[i]; + } + + str._data[str._size] = '\0'; + + return str; +} + +String String::substr_index(const int start_index, const int end_index) const { + ERR_FAIL_INDEX_V(start_index, _size + 1, String()); + ERR_FAIL_INDEX_V(end_index, _size + 1, String()); + ERR_FAIL_COND_V(start_index > end_index, String()); + + String str; + str.ensure_capacity(end_index - start_index + 1); + for (int i = start_index; i < end_index; ++i) { + str._data[str._size++] = _data[i]; + } + + str._data[str._size] = '\0'; + + return str; +} + +bool String::contains(const char val) const { + return find(val) != -1; +} +bool String::contains(const String &val) const { + return find(val) != -1; +} + +bool String::is_word_at(const int index, const char *str) const { + ERR_FAIL_INDEX_V(index, _size, false); + + int i = 0; + + while (str[i] != '\0') { + int iind = index + i; + + if (iind >= _size) { + return false; + } + + if (_data[iind] != str[i]) { + return false; + } + + ++i; + } + + return true; +} +bool String::is_word_at(const int index, const String &val) const { + ERR_FAIL_INDEX_V(index, _size, false); + + if (index + val.size() >= _size) { + return false; + } + + for (int i = 0; i < val.size(); ++i) { + int iind = index + i; + + if (_data[iind] != val[i]) { + return false; + } + } + + return true; +} + +void String::replace_from(const int start_index, const int length, const String &with) { + ERR_FAIL_INDEX(start_index, _size); + + int sil = start_index + length; + + ERR_FAIL_INDEX(sil, _size + 1); + + if (length < with.size()) { + int loffs = with.size() - length; + + ensure_capacity(_size + loffs + 1); + + _size += loffs; + _data[_size] = '\0'; + + for (int i = _size - 1; i > start_index + loffs; --i) { + _data[i] = _data[i - loffs]; + } + } else if (length > with.size()) { + int loffs = length - with.size(); + + for (int i = start_index + with.size(); i < _size; ++i) { + _data[i] = _data[i + loffs]; + } + + _size -= loffs; + } else { + for (int i = 0; i < length; ++i) { + _data[i + start_index] = with._data[i]; + } + } +} + +void String::replace(const String &find_str, const String &with) { + if (empty()) { + return; + } + + if (find_str.empty()) + return; + + int start_pos = 0; + while ((start_pos = find(find_str, start_pos)) != -1) { + replace_from(start_pos, find_str.size(), with); + start_pos += with.size(); + } +} + +void String::replace(const String &find_str, const String &with, const int count) { + if (empty()) { + return; + } + + if (find_str.empty()) + return; + + int c = 0; + + int start_pos = 0; + while ((start_pos = find(find_str, start_pos)) != -1) { + replace_from(start_pos, find_str.size(), with); + start_pos += with.size(); + + ++c; + + if (c == count) { + return; + } + } +} + +int String::compare(const String &other) const { + int cmp_size = MIN(size(), other.size()); + + for (int i = 0; i < cmp_size; ++i) { + if (_data[i] < other._data[i]) { + return 1; + } else if (_data[i] > other._data[i]) { + return 2; + } + } + + if (size() < other.size()) { + return 1; + } else if (size() > other.size()) { + return 2; + } else { + return 0; + } +} + +int String::first_difference_index(const String &other) const { + int c = size() < other.size() ? size() : other.size(); + + for (int i = 0; i < c; ++i) { + if (_data[i] != other._data[i]) { + return i; + } + } + + return c; +} + +void String::to_lower() { + for (int i = 0; i < _size; ++i) { + char c = _data[i]; + + if (c >= 56 && c <= 90) { + _data[i] = c + 32; + } + } +} +String String::as_lower() const { + String a = *this; + + a.to_lower(); + + return a; +} + +void String::trim() { + trim_end(); + trim_beginning(); +} +void String::trim_beginning() { + if (_size == 0) { + return; + } + + bool found = false; + int last_index = 0; + for (int i = 0; i < _size; ++i) { + char c = _data[i]; + + if (c == ' ' || c == '\n' || c == '\t' || c == '\r') { + found = true; + last_index == i; + } else { + break; + } + } + + if (!found) { + return; + } + + ++last_index; + + if (last_index == _size) { + _size = 0; + _data[_size] = '\0'; + return; + } + + for (int i = 0; i < _size - last_index; ++i) { + _data[i] = _data[i + last_index]; + } + + _size -= last_index; + + _data[_size] = '\0'; +} +void String::trim_end() { + if (_size == 0) { + return; + } + + int last_index = _size; + for (int i = _size - 1; i <= 0; --i) { + char c = _data[i]; + + if (c == ' ' || c == '\n' || c == '\t' || c == '\r') { + last_index == i; + } else { + break; + } + } + + if (last_index == _size) { + return; + } + + _data[last_index] = '\0'; + + _size = last_index; +} + +bool String::ends_with(const char c) const { + if (_size == 0) { + return false; + } + + return _data[_size - 1] == c; +} +bool String::ends_with(const String &str) const { + if (str.size() == 0) { + // maybe this should be false? + return true; + } + + if (_size < str.size()) { + return false; + } + + int diff = _size - str.size(); + + for (int i = str.size() - 1; i >= 0; --i) { + if (_data[i + diff] != str._data[i]) { + return false; + } + } + + return true; +} + +bool String::starts_with(const char c) const { + if (_size == 0) { + return false; + } + + return _data[0] == c; +} + +bool String::starts_with(const String &str) const { + if (str.size() == 0) { + // maybe this should be false? + return true; + } + + if (_size < str.size()) { + return false; + } + + for (int i = 0; i < str.size(); ++i) { + if (_data[i] != str._data[i]) { + return false; + } + } + + return true; +} + +int String::get_slice_count(const char splitter) const { + int count = 1; + + for (int i = 0; i < _size; ++i) { + if (_data[i] == splitter) { + ++count; + } + } + + return count; +} +int String::get_slice_count(const String &splitter) const { + int count = 1; + + int n = find(splitter, n); + while (n != -1) { + ++count; + n = find(splitter, n); + } + + return count; +} +String String::get_slice(const char splitter, int index) { + if (_size == 0) { + return ""; + } + + int count = 0; + + int start_index = 0; + + for (int i = 0; i < _size; ++i) { + if (_data[i] == splitter) { + ++count; + + if (count == index) { + start_index = i + 1; + } + + if (count == index + 1) { + return substr_index(start_index, i); + } + } + } + + return substr_index(start_index, _size); +} +String String::get_slice(const String &splitter, int index) { + if (_size == 0) { + return ""; + } + + int count = 0; + + int start_index = 0; + + int n = find(splitter, n); + while (n != -1) { + ++count; + n = find(splitter, n); + + if (count == index) { + start_index = n + splitter.size(); + } + + if (count == index + 1) { + return substr_index(start_index, n); + } + } + + return substr_index(start_index, _size); +} + +Vector String::split(const char splitter) const { + Vector v; + + if (_size == 0) { + return v; + } + + int start_index = 0; + + for (int i = 1; i < _size; ++i) { + if (_data[i] == splitter) { + + if (start_index == i) { + v.push_back(String()); + } else { + v.push_back(substr_index(start_index, i)); + } + + start_index = i + 1; + } + } + + if (start_index < _size - 1) { + v.push_back(substr_index(start_index, _size)); + } + + return v; +} +Vector String::split(const String &splitter) const { + Vector v; + + if (_size == 0) { + return v; + } + + int start_index = 0; + + int n = 0; + while (n != -1) { + n = find(splitter, n); + + v.push_back(substr_index(start_index, n)); + + start_index = n + splitter.size(); + } + + if (start_index < _size - 1) { + v.push_back(substr_index(start_index, _size)); + } + + return v; +} + +uint8_t String::read_uint8_bytes_at(int &index, bool advance_index) { + ERR_FAIL_INDEX_V(index, _size, 0); + + if (advance_index) { + return static_cast(_data[index++]); + } else { + return static_cast(_data[index]); + } +} +uint16_t String::read_uint16_bytes_at(int &index, bool advance_index) { + ERR_FAIL_INDEX_V(index + 1, _size, 0); + + char carr[3]; + char *p = carr; + + // printf("%u %u\n", static_cast(p[0]), static_cast(p[1])); + + get_substr_nt(p, index, 2); + + const uint16_t *vp = static_cast((void *)p); + + if (advance_index) { + index += 2; + } + + return *vp; +} +uint32_t String::read_uint32_bytes_at(int &index, bool advance_index) { + ERR_FAIL_INDEX_V(index + 3, _size, 0); + + char carr[5]; + char *p = carr; + + get_substr_nt(p, index, 4); + + const uint32_t *vp = static_cast((void *)p); + + if (advance_index) { + index += 4; + } + + return *vp; +} +uint64_t String::read_uint64_bytes_at(int &index, bool advance_index) { + ERR_FAIL_INDEX_V(index + 7, _size, 0); + + char carr[9]; + char *p = carr; + + get_substr_nt(p, index, 8); + + const uint64_t *vp = static_cast((void *)p); + + if (advance_index) { + index += 8; + } + + return *vp; +} + +int8_t String::read_int8_bytes_at(int &index, bool advance_index) { + ERR_FAIL_INDEX_V(index, _size, 0); + + if (advance_index) { + return static_cast(_data[index++]); + } else { + return static_cast(_data[index]); + } +} +int16_t String::read_int16_bytes_at(int &index, bool advance_index) { + ERR_FAIL_INDEX_V(index + 1, _size, 0); + + char carr[3]; + char *p = carr; + + // printf("%u %u\n", static_cast(p[0]), static_cast(p[1])); + + get_substr_nt(p, index, 2); + + const int16_t *vp = static_cast((void *)p); + + if (advance_index) { + index += 2; + } + + return *vp; +} +int32_t String::read_int32_bytes_at(int &index, bool advance_index) { + ERR_FAIL_INDEX_V(index + 3, _size, 0); + + char carr[5]; + char *p = carr; + + get_substr_nt(p, index, 4); + + const int32_t *vp = static_cast((void *)p); + + if (advance_index) { + index += 4; + } + + return *vp; +} +int64_t String::read_int64_bytes_at(int &index, bool advance_index) { + ERR_FAIL_INDEX_V(index + 7, _size, 0); + + char carr[9]; + char *p = carr; + + get_substr_nt(p, index, 8); + + const int64_t *vp = static_cast((void *)p); + + if (advance_index) { + index += 8; + } + + return *vp; +} + +void String::append_uint8_bytes(const uint8_t val) { + ensure_capacity(_size + 1); + + _data[_size++] = val; + _data[_size] = '\0'; +} +void String::append_uint16_bytes(const uint16_t val) { + ensure_capacity(_size + 2); + + const char *vp = static_cast((void *)&val); + + // printf("a %u %u\n", static_cast(vp[0]), static_cast(vp[1])); + + memcpy(&_data[_size], vp, 2); + + _size += 2; + + _data[_size] = '\0'; +} +void String::append_uint32_bytes(const uint32_t val) { + ensure_capacity(_size + 4); + + const char *vp = static_cast((void *)&val); + + memcpy(&_data[_size], vp, 4); + + _size += 4; + + _data[_size] = '\0'; +} +void String::append_uint64_bytes(const uint64_t val) { + ensure_capacity(_size + 8); + + const char *vp = static_cast((void *)&val); + + memcpy(&_data[_size], vp, 8); + + _size += 8; + + _data[_size] = '\0'; +} + +void String::append_int8_bytes(const int8_t val) { + ensure_capacity(_size + 1); + + _data[_size++] = val; + _data[_size] = '\0'; +} +void String::append_int16_bytes(const int16_t val) { + ensure_capacity(_size + 2); + + const char *vp = static_cast((void *)&val); + + // printf("a %u %u\n", static_cast(vp[0]), static_cast(vp[1])); + + memcpy(&_data[_size], vp, 2); + + _size += 2; + + _data[_size] = '\0'; +} +void String::append_int32_bytes(const int32_t val) { + ensure_capacity(_size + 4); + + const char *vp = static_cast((void *)&val); + + memcpy(&_data[_size], vp, 4); + + _size += 4; + + _data[_size] = '\0'; +} +void String::append_int64_bytes(const int64_t val) { + ensure_capacity(_size + 8); + + const char *vp = static_cast((void *)&val); + + memcpy(&_data[_size], vp, 8); + + _size += 8; + + _data[_size] = '\0'; +} + +float String::read_float_bytes_at(int &index, bool advance_index) { + ERR_FAIL_INDEX_V(index + 3, _size, 0); + + char carr[5]; + char *p = carr; + + get_substr_nt(p, index, 4); + + const float *vp = static_cast((void *)p); + + if (advance_index) { + index += 4; + } + + return *vp; +} +void String::append_float_bytes(const float val) { + ensure_capacity(_size + 4); + + const char *vp = static_cast((void *)&val); + + memcpy(&_data[_size], vp, 4); + + _size += 4; + + _data[_size] = '\0'; +} + +double String::read_double_bytes_at(int &index, bool advance_index) { + ERR_FAIL_INDEX_V(index + 7, _size, 0); + + char carr[9]; + char *p = carr; + + get_substr_nt(p, index, 8); + + const double *vp = static_cast((void *)p); + + if (advance_index) { + index += 8; + } + + return *vp; +} +void String::append_double_bytes(const double val) { + ensure_capacity(_size + 8); + + const char *vp = static_cast((void *)&val); + + memcpy(&_data[_size], vp, 8); + + _size += 8; + + _data[_size] = '\0'; +} + +void String::append_str(const char *str) { + if (str == nullptr) { + return; + } + + int i = 0; + + while (str[i] != '\0') { + push_back(str[i++]); + } +} + +void String::append_str(const wchar_t *str) { + if (str == nullptr) { + return; + } + + int i = 0; + + while (str[i] != '\0') { + push_back(str[i++]); + } +} + +void String::append_str(const String &other) { + ensure_capacity(_size + other._size + 1); // +1 for the null terminator + + for (int i = 0; i < other._size; ++i) { + _data[_size++] = other._data[i]; + } + + _data[_size] = '\0'; +} + +void String::append_str(const std::string &str) { + ensure_capacity(_size + str.size() + 1); // +1 for the null terminator + + for (int i = 0; i < str.size(); ++i) { + _data[_size++] = str[i]; + } + + _data[_size] = '\0'; +} + +void String::append_str(const String &other, const int from) { + if (other.size() <= from) { + return; + } + + ensure_capacity(_size + other._size + 1 - from); // +1 for the null terminator + + for (int i = from; i < other._size; ++i) { + _data[_size++] = other._data[i]; + } + + _data[_size] = '\0'; +} +void String::append_str(const std::string &str, const int from) { + if (str.size() <= from) { + return; + } + + ensure_capacity(_size + str.size() + 1 - from); // +1 for the null terminator + + for (int i = from; i < str.size(); ++i) { + _data[_size++] = str[i]; + } + + _data[_size] = '\0'; +} + +void String::append_repeat(const char *str, const int times) { + for (int i = 0; i < times; ++i) { + append_str(str); + } +} +void String::append_repeat(const String &other, const int times) { + for (int i = 0; i < times; ++i) { + append_str(other); + } +} + +void String::append_path(const char *path) { + if (path[0] == '\0') { + return; + } + + if (_size == 0) { + append_str(path); + return; + } + + int sindex = 0; + char ch = path[sindex]; + while (ch == '/' || ch == '\\') { + if (ch == '\0') { + return; + } + + ch = path[++sindex]; + } + + // /////folder + // ^ (sindex) + + if (ends_with('/') || ends_with('\\')) { + append_str(&path[sindex]); + } else { + if (sindex > 0) { + append_str(&path[sindex - 1]); + } else { + if (_actual_size > 0) { + _data[_size++] = DEFAULT_DIRECTORY_SEPARATOR; + append_str(&path[sindex]); + } else { + push_back(DEFAULT_DIRECTORY_SEPARATOR); + append_str(&path[sindex]); + } + } + } +} +void String::append_path(const String &path) { + if (path._size == 0) { + return; + } + + if (_size == 0) { + append_str(path); + return; + } + + int sindex = 0; + int ts = path.size() - 1; + char ch = path[sindex]; + while (ch == '/' || ch == '\\') { + if (sindex == ts) { + return; + } + + ch = path[++sindex]; + } + + // /////folder + // ^ (sindex) + + if (ends_with('/') || ends_with('\\')) { + append_str(path, sindex); + } else { + if (sindex > 0) { + append_str(path, sindex - 1); + } else { + if (_actual_size > 0) { + _data[_size++] = DEFAULT_DIRECTORY_SEPARATOR; + append_str(path, sindex); + } else { + push_back(DEFAULT_DIRECTORY_SEPARATOR); + append_str(path, sindex); + } + } + } +} + +void String::path_clean_end_slash() { + // _size > 1, so if root is given ("/"), it will not be removed + + while (_size > 1 && (ends_with('/') || ends_with('\\'))) { + pop_back(); + } +} +void String::path_ensure_end_slash() { + // Don't add if empty string, as it would make it root on linux, which can easily become a serious bug + if (_size == 0) { + return; + } + + if (!(ends_with('/') || ends_with('\\'))) { + push_back(DEFAULT_DIRECTORY_SEPARATOR); + } +} + +String String::path_get_basename() const { + if (_size == 0) { + return String(); + } + + int ssind = _size - 1; + while (ssind > 0 && (_data[ssind] != '/' && _data[ssind] != '\\')) { + --ssind; + } + + if (ssind == _size - 1) { + return String(); + } + + ++ssind; + + return substr_index(ssind, _size); +} + +String String::path_get_last_segment() const { + if (_size == 0) { + return String(); + } + + int seind = _size - 1; + while (seind > 0 && (_data[seind] == '/' || _data[seind] == '\\')) { + --seind; + } + + if (seind == 0) { + return _data[0]; + } + + int ssind = seind - 1; + while (ssind > 0 && (_data[ssind] != '/' && _data[ssind] != '\\')) { + --ssind; + } + + ++ssind; + + return substr_index(ssind, seind); +} + +String String::path_get_prev_dir() const { + if (_size == 0) { + return String(); + } + + int seind = _size - 1; + while (seind > 0 && (_data[seind] == '/' || _data[seind] == '\\')) { + --seind; + } + + if (seind == 0) { + // ///////// + // or + // a/////// + // no prev dir + + return String("/"); + } + + // fol/fol2/fol3// + // ^ (seind) + + while (seind > 0 && (_data[seind] != '/' || _data[seind] != '\\')) { + --seind; + } + + // fol/fol2/fol3// + // ^ (seind) + + --seind; + + if (seind <= 0) { + return String("/"); + } + + return substr_index(0, seind); +} + +String String::file_get_extension() const { + int dind = find_reversed('.'); + + if (dind == -1) { + return String(); + } + + return substr_index(dind + 1, _size); +} + +void String::to_html_special_chars() { + replace("&", "&"); + replace("\"", """); + replace("'", "'"); + replace("<", "<"); + replace(">", ">"); +} + +void String::from_html_special_chars() { + replace("&", "&"); + replace(""", "\""); + replace("'", "'"); + replace("<", "<"); + replace(">", ">"); +} + +void String::newline_to_br() { + replace("\r\n", "
"); + replace("\n", "
"); +} + +bool String::to_bool() const { + if (_size == 0) { + return 0; + } + + if (is_numeric()) { + return to_int() != 0; + } + + return as_lower() == "true"; +} + +float String::to_float() const { + if (_size == 0) { + return 0; + } + + return atof(c_str()); +} + +double String::to_double() const { + if (_size == 0) { + return 0; + } + + return atof(c_str()); +} + +int String::to_int() const { + if (_size == 0) { + return 0; + } + + return atoi(c_str()); +} + +bool String::is_bool() const { + if (_size == 0) { + return false; + } + + if (_size == 1) { + if (_data[0] == '0') { + return true; + } else if (_data[0] == '1') { + return true; + } + + return false; + } + + if (_size == 4) { + String l = as_lower(); + + if (l[0] == 't' && l[1] == 'r' && l[2] == 'u' && l[3] == 'e') { + return true; + } else { + return false; + } + } + + if (_size == 5) { + String l = as_lower(); + + if (l[0] == 'f' && l[1] == 'a' && l[2] == 'l' && l[3] == 's' && l[3] == 'e') { + return true; + } else { + return false; + } + } + + return false; +} + +bool String::is_numeric() const { + if (_size == 0) { + return false; + } + + int starti = 0; + + if (_data[0] == '-') { + starti += 1; + } + + bool had_dot = false; + for (int i = starti; i < _size; ++i) { + if (_data[i] == '.') { + if (!had_dot) { + had_dot = true; + continue; + } else { + return false; + } + } + + char c = _data[i]; + + if (c < '0' || c > '9') { + return false; + } + } + + return true; +} + +bool String::is_int() const { + if (_size == 0) { + return false; + } + + int starti = 0; + + if (_data[0] == '-') { + starti += 1; + } + + for (int i = starti; i < _size; ++i) { + char c = _data[i]; + + if (c < '0' || c > '9') { + return false; + } + } + + return true; +} + +bool String::is_uint() const { + if (_size == 0) { + return false; + } + + for (int i = 0; i < _size; ++i) { + char c = _data[i]; + + if (c < '0' || c > '9') { + return false; + } + } + + return true; +} + +bool String::is_zero() const { + if (_size == 0) { + return false; + } + + int starti = 0; + + if (_data[0] == '-') { + starti += 1; + } + + bool had_dot = false; + for (int i = starti; i < _size; ++i) { + if (_data[i] == '.') { + if (!had_dot) { + had_dot = true; + continue; + } else { + return false; + } + } + + char c = _data[i]; + + if (c != '0') { + return false; + } + } + + return true; +} + +uint32_t String::to_uint() const { + return static_cast(atoll(c_str())); +} + +std::string String::to_string() const { + return std::string(c_str()); +} + +void String::print() const { + if (_size == 0) { + ::printf("\n"); + return; + } + + ::printf("%s\n", c_str()); +} + +// Generic set of append helpers +void String::append(const char *str) { + append_str(str); +} +void String::append(const wchar_t *str) { + append_str(str); +} +void String::append(const String &other) { + append_str(other); +} +void String::append(const std::string &str) { + append_str(str); +} +void String::append(const char chr) { + push_back(chr); +} +void String::append(const wchar_t chr) { + push_back(chr); +} +void String::append(const int num) { + append_str(String::num(num)); +} +void String::append(const unsigned int num) { + append_str(String::num(num)); +} +void String::append(const float num) { + append_str(String::num(num)); +} +void String::append(const double num) { + append_str(String::num(num)); +} +void String::append(const Variant &variant) { + append_str(variant.to_string()); +} + +String String::bool_num(bool val) { + if (val) { + return String("1", 2); + } else { + return String("0", 2); + } +} +String String::bool_str(bool val) { + if (val) { + return String("true", 5); + } else { + return String("false", 6); + } +} + +// Taken from the Godot Engine (MIT License) +// Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. +// Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). +String String::num(double p_num, int p_decimals) { + if (Math::is_nan(p_num)) { + return "nan"; + } + + if (Math::is_inf(p_num)) { + if (signbit(p_num)) { + return "-inf"; + } else { + return "inf"; + } + } + + if (p_decimals < 0) { + p_decimals = 14; + const double abs_num = Math::abs(p_num); + if (abs_num > 10) { + // We want to align the digits to the above sane default, so we only + // need to subtract log10 for numbers with a positive power of ten. + p_decimals -= (int)floor(log10(abs_num)); + } + } + if (p_decimals > MAX_DECIMALS) { + p_decimals = MAX_DECIMALS; + } + + char fmt[7]; + fmt[0] = '%'; + fmt[1] = '.'; + + if (p_decimals < 0) { + fmt[1] = 'l'; + fmt[2] = 'f'; + fmt[3] = 0; + } else if (p_decimals < 10) { + fmt[2] = '0' + p_decimals; + fmt[3] = 'l'; + fmt[4] = 'f'; + fmt[5] = 0; + } else { + fmt[2] = '0' + (p_decimals / 10); + fmt[3] = '0' + (p_decimals % 10); + fmt[4] = 'l'; + fmt[5] = 'f'; + fmt[6] = 0; + } + char buf[256]; + +#if defined(__GNUC__) || defined(_MSC_VER) + snprintf(buf, 256, fmt, p_num); +#else + sprintf(buf, fmt, p_num); +#endif + + buf[255] = 0; + // destroy trailing zeroes + { + bool period = false; + int z = 0; + while (buf[z]) { + if (buf[z] == '.') { + period = true; + } + z++; + } + + if (period) { + z--; + while (z > 0) { + if (buf[z] == '0') { + buf[z] = 0; + } else if (buf[z] == '.') { + buf[z] = 0; + break; + } else { + break; + } + + z--; + } + } + } + + return buf; +} + +// Taken from the Godot Engine (MIT License) +// Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. +// Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). +String String::num_int64(int64_t p_num, int base, bool capitalize_hex) { + bool sign = p_num < 0; + + int64_t n = p_num; + + int chars = 0; + do { + n /= base; + chars++; + } while (n); + + if (sign) { + chars++; + } + + String s; + s.resize(chars + 1); + char *c = s.dataw(); + c[chars] = 0; + n = p_num; + do { + int mod = Math::absi(n % base); + if (mod >= 10) { + char a = (capitalize_hex ? 'A' : 'a'); + c[--chars] = a + (mod - 10); + } else { + c[--chars] = '0' + mod; + } + + n /= base; + } while (n); + + if (sign) { + c[0] = '-'; + } + + return s; +} + +// Taken from the Godot Engine (MIT License) +// Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. +// Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). +String String::num_uint64(uint64_t p_num, int base, bool capitalize_hex) { + uint64_t n = p_num; + + int chars = 0; + do { + n /= base; + chars++; + } while (n); + + String s; + s.resize(chars + 1); + char *c = s.dataw(); + c[chars] = 0; + n = p_num; + do { + int mod = n % base; + if (mod >= 10) { + char a = (capitalize_hex ? 'A' : 'a'); + c[--chars] = a + (mod - 10); + } else { + c[--chars] = '0' + mod; + } + + n /= base; + } while (n); + + return s; +} + +// Taken from the Godot Engine (MIT License) +// Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. +// Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). +String String::num_real(double p_num, bool p_trailing) { + if (Math::is_nan(p_num)) { + return "nan"; + } + + if (Math::is_inf(p_num)) { + if (signbit(p_num)) { + return "-inf"; + } else { + return "inf"; + } + } + + String s; + String sd; + + // Integer part. + + bool neg = p_num < 0; + p_num = Math::abs(p_num); + int64_t intn = (int64_t)p_num; + + // Decimal part. + + if (intn != p_num) { + double dec = p_num - (double)intn; + + int digit = 0; + +#ifdef REAL_T_IS_DOUBLE + int decimals = 14; + double tolerance = 1e-14; +#else + int decimals = 6; + double tolerance = 1e-6; +#endif + // We want to align the digits to the above sane default, so we only + // need to subtract log10 for numbers with a positive power of ten. + if (p_num > 10) { + decimals -= (int)floor(log10(p_num)); + } + + if (decimals > MAX_DECIMALS) { + decimals = MAX_DECIMALS; + } + + // In case the value ends up ending in "99999", we want to add a + // tiny bit to the value we're checking when deciding when to stop, + // so we multiply by slightly above 1 (1 + 1e-7 or 1e-15). + double check_multiplier = 1 + tolerance / 10; + + int64_t dec_int = 0; + int64_t dec_max = 0; + + while (true) { + dec *= 10.0; + dec_int = dec_int * 10 + (int64_t)dec % 10; + dec_max = dec_max * 10 + 9; + digit++; + + if ((dec - (double)(int64_t)(dec * check_multiplier)) < tolerance) { + break; + } + + if (digit == decimals) { + break; + } + } + + dec *= 10; + int last = (int64_t)dec % 10; + + if (last > 5) { + if (dec_int == dec_max) { + dec_int = 0; + intn++; + } else { + dec_int++; + } + } + + String decimal; + for (int i = 0; i < digit; i++) { + char num[2] = { 0, 0 }; + num[0] = '0' + dec_int % 10; + decimal = num + decimal; + dec_int /= 10; + } + sd = '.' + decimal; + } else if (p_trailing) { + sd = ".0"; + } else { + sd = ""; + } + + if (intn == 0) { + s = "0"; + } else { + while (intn) { + char32_t num = '0' + (intn % 10); + intn /= 10; + s = num + s; + } + } + + s = s + sd; + if (neg) { + s = "-" + s; + } + return s; +} + +// Taken from the Godot Engine (MIT License) +// Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. +// Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). +String String::num_scientific(double p_num) { + if (Math::is_nan(p_num)) { + return "nan"; + } + + if (Math::is_inf(p_num)) { + if (signbit(p_num)) { + return "-inf"; + } else { + return "inf"; + } + } + + char buf[256]; + +#if defined(__GNUC__) || defined(_MSC_VER) + +#if defined(__MINGW32__) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT) + // MinGW requires _set_output_format() to conform to C99 output for printf + unsigned int old_exponent_format = _set_output_format(_TWO_DIGIT_EXPONENT); +#endif + snprintf(buf, 256, "%lg", p_num); + +#if defined(__MINGW32__) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT) + _set_output_format(old_exponent_format); +#endif + +#else + sprintf(buf, "%.16lg", p_num); +#endif + + buf[255] = 0; + + return buf; +} + +// Taken from the Godot Engine (MIT License) +// Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. +// Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). +String String::ascii(bool p_allow_extended) const { + if (!size()) { + return String(); + } + + String cs; + cs.resize(size()); + + for (int i = 0; i < size(); i++) { + cs[i] = operator[](i); + } + + return cs; +} + +// Taken from the Godot Engine (MIT License) +// Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. +// Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). +String String::utf8(const char *p_utf8, int p_len) { + String ret; + ret.parse_utf8(p_utf8, p_len); + + return ret; +}; + +// Taken from the Godot Engine (MIT License) +// Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. +// Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). +bool String::parse_utf8(const char *p_utf8, int p_len) { + //#define _UNICERROR(m_err) print_line("Unicode error: " + String(m_err)); + + if (!p_utf8) { + return true; + } + + String aux; + + int cstr_size = 0; + int str_size = 0; + + /* HANDLE BOM (Byte Order Mark) */ + if (p_len < 0 || p_len >= 3) { + bool has_bom = uint8_t(p_utf8[0]) == 0xEF && uint8_t(p_utf8[1]) == 0xBB && uint8_t(p_utf8[2]) == 0xBF; + if (has_bom) { + // just skip it + if (p_len >= 0) { + p_len -= 3; + } + p_utf8 += 3; + } + } + + { + const char *ptrtmp = p_utf8; + const char *ptrtmp_limit = &p_utf8[p_len]; + int skip = 0; + while (ptrtmp != ptrtmp_limit && *ptrtmp) { + if (skip == 0) { + uint8_t c = *ptrtmp >= 0 ? *ptrtmp : uint8_t(256 + *ptrtmp); + + /* Determine the number of characters in sequence */ + if ((c & 0x80) == 0) { + skip = 0; + } else if ((c & 0xE0) == 0xC0) { + skip = 1; + } else if ((c & 0xF0) == 0xE0) { + skip = 2; + } else if ((c & 0xF8) == 0xF0) { + skip = 3; + } else if ((c & 0xFC) == 0xF8) { + skip = 4; + } else if ((c & 0xFE) == 0xFC) { + skip = 5; + } else { + RLOG_ERR("UNICODE_ERROR: invalid skip\n"); + return true; // invalid utf8 + } + + if (skip == 1 && (c & 0x1E) == 0) { + // printf("overlong rejected\n"); + RLOG_ERR("UNICODE_ERROR: overlong rejected\n"); + return true; // reject overlong + } + + str_size++; + + } else { + --skip; + } + + cstr_size++; + ptrtmp++; + } + + if (skip) { + RLOG_ERR("UNICODE_ERROR: no space left\n"); + return true; // not enough spac + } + } + + if (str_size == 0) { + clear(); + return false; + } + + ensure_capacity(str_size + 1); + _size = str_size; + + char *dst = dataw(); + dst[str_size] = 0; + + while (cstr_size) { + int len = 0; + + /* Determine the number of characters in sequence */ + if ((*p_utf8 & 0x80) == 0) { + len = 1; + } else if ((*p_utf8 & 0xE0) == 0xC0) { + len = 2; + } else if ((*p_utf8 & 0xF0) == 0xE0) { + len = 3; + } else if ((*p_utf8 & 0xF8) == 0xF0) { + len = 4; + } else if ((*p_utf8 & 0xFC) == 0xF8) { + len = 5; + } else if ((*p_utf8 & 0xFE) == 0xFC) { + len = 6; + } else { + RLOG_ERR("UNICODE_ERROR: invalid len\n"); + + return true; // invalid UTF8 + } + + if (len > cstr_size) { + RLOG_ERR("UNICODE_ERROR: no space left\n"); + return true; // not enough space + } + + if (len == 2 && (*p_utf8 & 0x1E) == 0) { + // printf("overlong rejected\n"); + RLOG_ERR("UNICODE_ERROR: no space left\n"); + return true; // reject overlong + } + + /* Convert the first character */ + + uint32_t unichar = 0; + + if (len == 1) { + unichar = *p_utf8; + } else { + unichar = (0xFF >> (len + 1)) & *p_utf8; + + for (int i = 1; i < len; i++) { + if ((p_utf8[i] & 0xC0) != 0x80) { + RLOG_ERR("UNICODE_ERROR: invalid utf8\n"); + return true; // invalid utf8 + } + if (unichar == 0 && i == 2 && ((p_utf8[i] & 0x7F) >> (7 - len)) == 0) { + RLOG_ERR("UNICODE_ERROR: invalid utf8 overlong\n"); + return true; // no overlong + } + unichar = (unichar << 6) | (p_utf8[i] & 0x3F); + } + } + + // printf("char %i, len %i\n",unichar,len); + if (sizeof(wchar_t) == 2 && unichar > 0xFFFF) { + unichar = ' '; // too long for windows + } + + *(dst++) = unichar; + cstr_size -= len; + p_utf8 += len; + } + + return false; +} + +// Taken from the Godot Engine (MIT License) +// Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. +// Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). +String String::utf8() const { + int l = size(); + if (!l) { + return String(); + } + + const char *d = data(); + int fl = 0; + for (int i = 0; i < l; i++) { + uint32_t c = d[i]; + if (c <= 0x7f) { // 7 bits. + fl += 1; + } else if (c <= 0x7ff) { // 11 bits + fl += 2; + } else if (c <= 0xffff) { // 16 bits + fl += 3; + } else if (c <= 0x001fffff) { // 21 bits + fl += 4; + + } else if (c <= 0x03ffffff) { // 26 bits + fl += 5; + } else if (c <= 0x7fffffff) { // 31 bits + fl += 6; + } + } + + String utf8s; + if (fl == 0) { + return utf8s; + } + + utf8s.ensure_capacity(fl + 1); + utf8s._size = fl; + uint8_t *cdst = (uint8_t *)utf8s.dataw(); + +#define APPEND_CHAR(m_c) *(cdst++) = m_c + + for (int i = 0; i < l; i++) { + uint32_t c = d[i]; + + if (c <= 0x7f) { // 7 bits. + APPEND_CHAR(c); + } else if (c <= 0x7ff) { // 11 bits + + APPEND_CHAR(uint32_t(0xc0 | ((c >> 6) & 0x1f))); // Top 5 bits. + APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits. + } else if (c <= 0xffff) { // 16 bits + + APPEND_CHAR(uint32_t(0xe0 | ((c >> 12) & 0x0f))); // Top 4 bits. + APPEND_CHAR(uint32_t(0x80 | ((c >> 6) & 0x3f))); // Middle 6 bits. + APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits. + } else if (c <= 0x001fffff) { // 21 bits + + APPEND_CHAR(uint32_t(0xf0 | ((c >> 18) & 0x07))); // Top 3 bits. + APPEND_CHAR(uint32_t(0x80 | ((c >> 12) & 0x3f))); // Upper middle 6 bits. + APPEND_CHAR(uint32_t(0x80 | ((c >> 6) & 0x3f))); // Lower middle 6 bits. + APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits. + } else if (c <= 0x03ffffff) { // 26 bits + + APPEND_CHAR(uint32_t(0xf8 | ((c >> 24) & 0x03))); // Top 2 bits. + APPEND_CHAR(uint32_t(0x80 | ((c >> 18) & 0x3f))); // Upper middle 6 bits. + APPEND_CHAR(uint32_t(0x80 | ((c >> 12) & 0x3f))); // middle 6 bits. + APPEND_CHAR(uint32_t(0x80 | ((c >> 6) & 0x3f))); // Lower middle 6 bits. + APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits. + } else if (c <= 0x7fffffff) { // 31 bits + + APPEND_CHAR(uint32_t(0xfc | ((c >> 30) & 0x01))); // Top 1 bit. + APPEND_CHAR(uint32_t(0x80 | ((c >> 24) & 0x3f))); // Upper upper middle 6 bits. + APPEND_CHAR(uint32_t(0x80 | ((c >> 18) & 0x3f))); // Lower upper middle 6 bits. + APPEND_CHAR(uint32_t(0x80 | ((c >> 12) & 0x3f))); // Upper lower middle 6 bits. + APPEND_CHAR(uint32_t(0x80 | ((c >> 6) & 0x3f))); // Lower lower middle 6 bits. + APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits. + } + } +#undef APPEND_CHAR + *cdst = 0; // trailing zero + + return utf8s; +} + +// Taken from the Godot Engine (MIT License) +// Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. +// Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). +uint32_t String::hash(const wchar_t *p_cstr, int p_len) { + uint32_t hashv = 5381; + for (int i = 0; i < p_len; i++) { + hashv = ((hashv << 5) + hashv) + p_cstr[i]; /* hash * 33 + c */ + } + + return hashv; +} + +// Taken from the Godot Engine (MIT License) +// Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. +// Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). +uint32_t String::hash(const wchar_t *p_cstr) { + uint32_t hashv = 5381; + uint32_t c; + + while ((c = *p_cstr++)) { + hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ + } + + return hashv; +} + +// Taken from the Godot Engine (MIT License) +// Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. +// Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). +uint32_t String::hash(const char *p_cstr) { + uint32_t hashv = 5381; + uint32_t c; + + while ((c = *p_cstr++)) { + hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ + } + + return hashv; +} + +// Taken from the Godot Engine (MIT License) +// Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. +// Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). +uint32_t String::hash(const char *p_cstr, int p_len) { + uint32_t hashv = 5381; + for (int i = 0; i < p_len; i++) { + hashv = ((hashv << 5) + hashv) + p_cstr[i]; /* hash * 33 + c */ + } + + return hashv; +} + +// Taken from the Godot Engine (MIT License) +// Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. +// Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). +uint32_t String::hash() const { + /* simple djb2 hashing */ + + const char *chr = c_str(); + uint32_t hashv = 5381; + uint32_t c; + + while ((c = *chr++)) { + hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ + } + + return hashv; +} + +// Taken from the Godot Engine (MIT License) +// Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. +// Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). +uint64_t String::hash64() const { + /* simple djb2 hashing */ + + const char *chr = c_str(); + uint64_t hashv = 5381; + uint64_t c; + + while ((c = *chr++)) { + hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ + } + + return hashv; +} + +char *String::c_str() { + return _data; +} + +const char *String::c_str() const { + return _data; +} + +char *String::dataw() { + return _data; +} + +const char *String::data() const { + return _data; +} + +const char String::operator[](const int index) const { + return _data[index]; +} + +char &String::operator[](const int index) { + return _data[index]; +} + +String &String::operator+=(const String &b) { + ensure_capacity(_size + b._size + 1); // +1 for the null terminator + + for (int i = 0; i < b._size; ++i) { + _data[_size++] = b._data[i]; + } + + _data[_size] = '\0'; + + return *this; +} + +String &String::operator+=(const char chr) { + push_back(chr); + + return *this; +} + +String &String::operator+=(const char *p_c_str) { + int i = 0; + while (p_c_str[i] != '\0') { + push_back(p_c_str[i]); + ++i; + } + + return *this; +} + +String &String::operator+=(const std::string &b) { + append_str(b); + + return *this; +} +String operator+(String lhs, const String &rhs) { + lhs.append_str(rhs); + + return lhs; +} + +String operator+(String lhs, const char *rhs) { + lhs.append_str(rhs); + + return lhs; +} + +String operator+(String lhs, const char rhs) { + lhs.push_back(rhs); + + return lhs; +} + +String operator+(String lhs, const std::string &rhs) { + lhs.append_str(rhs); + + return lhs; +} + +bool operator==(const String &a, const String &b) { + if (a._size != b._size) { + return false; + } + + for (int i = 0; i < a._size; ++i) { + if (a[i] != b[i]) { + return false; + } + } + + return true; +} + +bool operator!=(const String &a, const String &b) { + return !(a == b); +} + +bool operator==(const String &a, const char *b) { + if (a._size == 0) { + return b[0] == '\0'; + } + + int i = 0; + while (i < a._size && b[i] != '\0') { + if (a[i] != b[i]) { + return false; + } + + ++i; + } + + if (i != a._size) { + return false; + } + + return true; +} + +bool operator!=(const String &a, const char *b) { + return !(a == b); +} + +bool operator==(const char *b, const String &a) { + if (a._size == 0) { + return b[0] == '\0'; + } + + int i = 0; + while (i < a._size && b[i] != '\0') { + if (a[i] != b[i]) { + return false; + } + + ++i; + } + + if (i != a._size) { + return false; + } + + return true; +} + +bool operator!=(const char *b, const String &a) { + return !(a == b); +} + +bool operator==(const String &a, const wchar_t *b) { + if (a._size == 0) { + return b[0] == '\0'; + } + + int i = 0; + while (i < a._size && b[i] != '\0') { + if (a[i] != b[i]) { + return false; + } + + ++i; + } + + if (i != a._size) { + return false; + } + + return true; +} +bool operator!=(const String &a, const wchar_t *b) { + return !(a == b); +} + +bool operator==(const wchar_t *b, const String &a) { + if (a._size == 0) { + return b[0] == '\0'; + } + + int i = 0; + while (i < a._size && b[i] != '\0') { + if (a[i] != b[i]) { + return false; + } + + ++i; + } + + if (i != a._size) { + return false; + } + + return true; +} +bool operator!=(const wchar_t *b, const String &a) { + return !(a == b); +} + +bool operator==(const String &a, std::string &b) { + if (a._size != b.size()) { + return false; + } + + char *bp = &b[0]; + + for (int i = 0; i < a._size; ++i) { + if (a[i] != bp[i]) { + return false; + } + } + + return true; +} +bool operator!=(const String &a, std::string &b) { + return !(a == b); +} + +bool operator==(std::string &b, const String &a) { + return (a == b); +} +bool operator!=(std::string &b, const String &a) { + return !(a == b); +} + +bool operator<(const String &a, const String &b) { + return a.compare(b) == 1; +} +bool operator>(const String &a, const String &b) { + return a.compare(b) == 2; +} +bool operator<=(const String &a, const String &b) { + int c = a.compare(b); + + return c == 0 || c == 1; +} +bool operator>=(const String &a, const String &b) { + int c = a.compare(b); + + return c == 0 || c == 2; +} + +String &String::operator=(const String &other) { + clear(); + + append_str(other); + + return *this; +} + +String &String::operator=(const std::string &other) { + clear(); + + append_str(other); + + return *this; +} + +String &String::operator=(const char *other) { + clear(); + + append_str(other); + + return *this; +} + +String &String::operator=(const wchar_t *other) { + clear(); + + append_str(other); + + return *this; +} + +String::String() { + _data = nullptr; + _actual_size = 0; + _size = 0; + _grow_by = 100; + + ensure_capacity(100); + + _data[0] = '\0'; +} + +String::String(const String &other) { + _data = nullptr; + _actual_size = 0; + _size = 0; + _grow_by = 100; + + //+1 for the null terminator in case its needed + ensure_capacity(other.size() + 1); + + for (int i = 0; i < other._size; ++i) { + _data[i] = other._data[i]; + } + _size = other._size; + _data[other._size] = '\0'; +} + +String::String(const String &other, int grow_by) { + _data = nullptr; + _actual_size = 0; + _size = 0; + _grow_by = grow_by; + + //+1 for the null terminator in case its needed + ensure_capacity(other.size() + 1); + + for (int i = 0; i < other._size; ++i) { + _data[i] = other._data[i]; + } + + _size = other._size; + + _data[_size] = '\0'; +} + +String::String(const char *p_c_str) { + _data = nullptr; + _actual_size = 0; + _size = 0; + _grow_by = 100; + + append_str(p_c_str); +} + +String::String(const char *p_c_str, const int grow_by) { + _data = nullptr; + _actual_size = 0; + _size = 0; + _grow_by = grow_by; + + append_str(p_c_str); +} + +String::String(const wchar_t *p_c_str) { + _data = nullptr; + _actual_size = 0; + _size = 0; + _grow_by = 100; + + append_str(p_c_str); +} + +String::String(int prealloc) { + _data = nullptr; + _actual_size = 0; + _size = 0; + _grow_by = 100; + + ensure_capacity(prealloc); + + _data[0] = '\0'; +} + +String::String(int prealloc, int grow_by) { + _data = nullptr; + _actual_size = 0; + _size = 0; + _grow_by = grow_by; + + ensure_capacity(prealloc); + + _data[0] = '\0'; +} + +String::String(const std::string &str) { + _data = nullptr; + _actual_size = 0; + _size = 0; + _grow_by = 100; + + append_str(str); +} + +String::~String() { + if (_data) { + delete[] _data; + _data = nullptr; + } +} diff --git a/core/string.h b/core/string.h new file mode 100644 index 0000000..680c54f --- /dev/null +++ b/core/string.h @@ -0,0 +1,280 @@ +#ifndef STRING_H +#define STRING_H + +#include + +#include + +#include "core/containers/vector.h" + +#ifndef DEFAULT_DIRECTORY_SEPARATOR +#define DEFAULT_DIRECTORY_SEPARATOR '/' +#endif + +class Variant; + +// TODO move to wchar_t! + +class String { +public: + void push_back(const char element); + void push_back(const wchar_t element); + void pop_back(); + void remove(const int index); + void erase(const char element); + void erase(const int start_index, const int length); + void clear(); + bool empty() const; + char get(const int index); + const char get(const int index) const; + void set(const int index, const char value); + + int size() const; + int capacity() const; + void ensure_capacity(const int capacity); + void resize(const int s); + int find(const char val, const int from = 0) const; + int find(const String &val, const int from = 0) const; + int find_reversed(const char val, const int from = -1) const; + int find_reversed(const String &val, const int from = -1) const; + void get_substr(char *into_buf, const int start_index, const int len); + void get_substr_nt(char *into_buf, const int start_index, const int len); + String substr(const int start_index, const int len) const; + String substr_index(const int start_index, const int end_index) const; //end_index is not included + bool contains(const char val) const; + bool contains(const String &val) const; + + bool is_word_at(const int index, const char *str) const; + bool is_word_at(const int index, const String &val) const; + + void replace_from(const int start_index, const int length, const String &with); + void replace(const String &find_str, const String &with); + void replace(const String &find_str, const String &with, const int count); + + int compare(const String &other) const; + + int first_difference_index(const String &other) const; + + void to_lower(); + String as_lower() const; + + void trim(); + void trim_beginning(); + void trim_end(); + + bool ends_with(const char c) const; + bool ends_with(const String &str) const; + + bool starts_with(const char c) const; + bool starts_with(const String &str) const; + + int get_slice_count(const char splitter) const; + int get_slice_count(const String &splitter) const; + String get_slice(const char splitter, int index); + String get_slice(const String &splitter, int index); + + Vector split(const char splitter) const; + Vector split(const String &splitter) const; + + uint8_t read_uint8_bytes_at(int &index, bool advance_index = true); + uint16_t read_uint16_bytes_at(int &index, bool advance_index = true); + uint32_t read_uint32_bytes_at(int &index, bool advance_index = true); + uint64_t read_uint64_bytes_at(int &index, bool advance_index = true); + + int8_t read_int8_bytes_at(int &index, bool advance_index = true); + int16_t read_int16_bytes_at(int &index, bool advance_index = true); + int32_t read_int32_bytes_at(int &index, bool advance_index = true); + int64_t read_int64_bytes_at(int &index, bool advance_index = true); + + void append_uint8_bytes(const uint8_t val); + void append_uint16_bytes(const uint16_t val); + void append_uint32_bytes(const uint32_t val); + void append_uint64_bytes(const uint64_t val); + + void append_int8_bytes(const int8_t val); + void append_int16_bytes(const int16_t val); + void append_int32_bytes(const int32_t val); + void append_int64_bytes(const int64_t val); + + float read_float_bytes_at(int &index, bool advance_index = true); + void append_float_bytes(const float val); + double read_double_bytes_at(int &index, bool advance_index = true); + void append_double_bytes(const double val); + + void append_str(const char *str); + void append_str(const wchar_t *str); + void append_str(const String &other); + void append_str(const std::string &str); + void append_str(const String &other, const int from); + void append_str(const std::string &str, const int from); + + void append_repeat(const char *str, const int times); + void append_repeat(const String &other, const int times); + + void append_path(const char *path); + void append_path(const String &path); + void path_clean_end_slash(); + void path_ensure_end_slash(); + String path_get_basename() const; + String path_get_last_segment() const; + String path_get_prev_dir() const; + String file_get_extension() const; + + void to_html_special_chars(); + void from_html_special_chars(); + void newline_to_br(); + + bool to_bool() const; + float to_float() const; + double to_double() const; + int to_int() const; + + bool is_bool() const; + bool is_numeric() const; + bool is_int() const; + bool is_uint() const; + bool is_zero() const; + + uint32_t to_uint() const; + std::string to_string() const; + void print() const; + + // Generic set of append helpers + void append(const char *str); + void append(const wchar_t *str); + void append(const String &other); + void append(const std::string &str); + void append(const char chr); + void append(const wchar_t chr); + void append(const int num); + void append(const unsigned int num); + void append(const float num); + void append(const double num); + void append(const Variant &variant); + + static String bool_num(bool val); + static String bool_str(bool val); + + // Taken from the Godot Engine (MIT License) + // Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. + // Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). + static String num(double p_num, int p_decimals = -1); + static String num_scientific(double p_num); + static String num_real(double p_num, bool p_trailing = true); + static String num_int64(int64_t p_num, int base = 10, bool capitalize_hex = false); + static String num_uint64(uint64_t p_num, int base = 10, bool capitalize_hex = false); + static String chr(char32_t p_char); + + // Taken from the Godot Engine (MIT License) + // Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. + // Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). + String ascii(bool p_allow_extended = false) const; + String utf8() const; + bool parse_utf8(const char *p_utf8, int p_len = -1); // return true on error + static String utf8(const char *p_utf8, int p_len = -1); + + // Taken from the Godot Engine (MIT License) + // Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. + // Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). + static uint32_t hash(const wchar_t *p_cstr, int p_len); /* hash the string */ + static uint32_t hash(const wchar_t *p_cstr); /* hash the string */ + static uint32_t hash(const char *p_cstr, int p_len); /* hash the string */ + static uint32_t hash(const char *p_cstr); /* hash the string */ + uint32_t hash() const; /* hash the string */ + uint64_t hash64() const; /* hash the string */ + + char *c_str(); + const char *c_str() const; + + char *dataw(); + const char *data() const; + + const char operator[](const int index) const; + char &operator[](const int index); + + String &operator+=(const String &b); + String &operator+=(const char chr); + String &operator+=(const char *p_c_str); + String &operator+=(const std::string &b); + + friend String operator+(String lhs, const String &rhs); + friend String operator+(String lhs, const char *rhs); + friend String operator+(String lhs, const char rhs); + friend String operator+(String lhs, const std::string &rhs); + + friend bool operator==(const String &a, const String &b); + friend bool operator!=(const String &a, const String &b); + + friend bool operator==(const String &a, const char *b); + friend bool operator!=(const String &a, const char *b); + + friend bool operator==(const char *b, const String &a); + friend bool operator!=(const char *b, const String &a); + + friend bool operator==(const String &a, const wchar_t *b); + friend bool operator!=(const String &a, const wchar_t *b); + + friend bool operator==(const wchar_t *b, const String &a); + friend bool operator!=(const wchar_t *b, const String &a); + + friend bool operator==(const String &a, std::string &b); + friend bool operator!=(const String &a, std::string &b); + + friend bool operator==(std::string &b, const String &a); + friend bool operator!=(std::string &b, const String &a); + + friend bool operator<(const String &a, const String &b); + friend bool operator>(const String &a, const String &b); + friend bool operator<=(const String &a, const String &b); + friend bool operator>=(const String &a, const String &b); + + operator std::string() { return to_string(); } + operator std::string() const { return to_string(); } + + String &operator=(const String &other); + String &operator=(const std::string &other); + String &operator=(const char *other); + String &operator=(const wchar_t *other); + + String(); + String(const String &other); + String(const String &other, const int grow_by); + String(const char *p_c_str); + String(const char *p_c_str, const int grow_by); + String(const wchar_t *p_c_str); + String(const int prealloc); + String(const int prealloc, const int grow_by); + String(const std::string &str); + ~String(); + +private: + char *_data; + int _actual_size; + int _size; + int _grow_by; +}; + +// Taken from the Godot Engine (MIT License) +// Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. +// Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). +template +bool is_str_less(const L *l_ptr, const R *r_ptr) { + while (true) { + if (*l_ptr == 0 && *r_ptr == 0) { + return false; + } else if (*l_ptr == 0) { + return true; + } else if (*r_ptr == 0) { + return false; + } else if (*l_ptr < *r_ptr) { + return true; + } else if (*l_ptr > *r_ptr) { + return false; + } + + l_ptr++; + r_ptr++; + } +} + +#endif diff --git a/core/typedefs.h b/core/typedefs.h new file mode 100644 index 0000000..f712d43 --- /dev/null +++ b/core/typedefs.h @@ -0,0 +1,354 @@ +/*************************************************************************/ +/* typedefs.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 TYPEDEFS_H +#define TYPEDEFS_H + +#include + +/** + * Basic definitions and simple functions to be used everywhere. + */ + +//#include "platform_config.h" + +#ifndef _STR +#define _STR(m_x) #m_x +#define _MKSTR(m_x) _STR(m_x) +#endif + +//should always inline no matter what +#ifndef _ALWAYS_INLINE_ + +#if defined(__GNUC__) && (__GNUC__ >= 4) +#define _ALWAYS_INLINE_ __attribute__((always_inline)) inline +#elif defined(__llvm__) +#define _ALWAYS_INLINE_ __attribute__((always_inline)) inline +#elif defined(_MSC_VER) +#define _ALWAYS_INLINE_ __forceinline +#else +#define _ALWAYS_INLINE_ inline +#endif + +#endif + +//should always inline, except in some cases because it makes debugging harder +#ifndef _FORCE_INLINE_ + +#ifdef DISABLE_FORCED_INLINE +#define _FORCE_INLINE_ inline +#else +#define _FORCE_INLINE_ _ALWAYS_INLINE_ +#endif + +#endif + +//custom, gcc-safe offsetof, because gcc complains a lot. +template +T *_nullptr() { + T *t = NULL; + return t; +} + +#define OFFSET_OF(st, m) \ + ((size_t)((char *)&(_nullptr()->m) - (char *)0)) +/** + * Some platforms (devices) don't define NULL + */ + +#ifndef NULL +#define NULL 0 +#endif + +/** + * Windows badly defines a lot of stuff we'll never use. Undefine it. + */ + +#ifdef _WIN32 +#undef min // override standard definition +#undef max // override standard definition +#undef ERROR // override (really stupid) wingdi.h standard definition +#undef DELETE // override (another really stupid) winnt.h standard definition +#undef MessageBox // override winuser.h standard definition +#undef MIN // override standard definition +#undef MAX // override standard definition +#undef CLAMP // override standard definition +#undef Error +#undef OK +#undef CONNECT_DEFERRED // override from Windows SDK, clashes with Object enum +#endif + +#include "int_types.h" + +//#include "core/error_list.h" + +/** Generic ABS function, for math uses please use Math::abs */ + +#ifndef ABS +#define ABS(m_v) (((m_v) < 0) ? (-(m_v)) : (m_v)) +#endif + +#define ABSDIFF(x, y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y))) + +#ifndef SGN +#define SGN(m_v) (((m_v) < 0) ? (-1.0) : (+1.0)) +#endif + +#ifndef MIN +#define MIN(m_a, m_b) (((m_a) < (m_b)) ? (m_a) : (m_b)) +#endif + +#ifndef MAX +#define MAX(m_a, m_b) (((m_a) > (m_b)) ? (m_a) : (m_b)) +#endif + +#ifndef CLAMP +#define CLAMP(m_a, m_min, m_max) (((m_a) < (m_min)) ? (m_min) : (((m_a) > (m_max)) ? m_max : m_a)) +#endif + +/** Generic swap template */ +#ifndef SWAP + +#define SWAP(m_x, m_y) __swap_tmpl((m_x), (m_y)) +template +inline void __swap_tmpl(T &x, T &y) { + T aux = x; + x = y; + y = aux; +} + +#endif //swap + +/* clang-format off */ +#define HEX2CHR(m_hex) \ + ((m_hex >= '0' && m_hex <= '9') ? (m_hex - '0') : \ + ((m_hex >= 'A' && m_hex <= 'F') ? (10 + m_hex - 'A') : \ + ((m_hex >= 'a' && m_hex <= 'f') ? (10 + m_hex - 'a') : 0))) +/* clang-format on */ + +// Macro to check whether we are compiled by clang +// and we have a specific builtin +#if defined(__llvm__) && defined(__has_builtin) +#define _llvm_has_builtin(x) __has_builtin(x) +#else +#define _llvm_has_builtin(x) 0 +#endif + +#if (defined(__GNUC__) && (__GNUC__ >= 5)) || _llvm_has_builtin(__builtin_mul_overflow) +#define _mul_overflow __builtin_mul_overflow +#endif + +#if (defined(__GNUC__) && (__GNUC__ >= 5)) || _llvm_has_builtin(__builtin_add_overflow) +#define _add_overflow __builtin_add_overflow +#endif + +/** Function to find the next power of 2 to an integer */ + +static _FORCE_INLINE_ unsigned int next_power_of_2(unsigned int x) { + if (x == 0) { + return 0; + } + + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + + return ++x; +} + +static _FORCE_INLINE_ unsigned int previous_power_of_2(unsigned int x) { + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return x - (x >> 1); +} + +static _FORCE_INLINE_ unsigned int closest_power_of_2(unsigned int x) { + unsigned int nx = next_power_of_2(x); + unsigned int px = previous_power_of_2(x); + return (nx - x) > (x - px) ? px : nx; +} + +// We need this definition inside the function below. +static inline int get_shift_from_power_of_2(unsigned int p_pixel); + +template +static _FORCE_INLINE_ T nearest_power_of_2_templated(T x) { + --x; + + // The number of operations on x is the base two logarithm + // of the p_number of bits in the type. Add three to account + // for sizeof(T) being in bytes. + size_t num = get_shift_from_power_of_2(sizeof(T)) + 3; + + // If the compiler is smart, it unrolls this loop + // If its dumb, this is a bit slow. + for (size_t i = 0; i < num; i++) { + x |= x >> (1 << i); + } + + return ++x; +} + +/** Function to find the nearest (bigger) power of 2 to an integer */ + +static inline unsigned int nearest_shift(unsigned int p_number) { + for (int i = 30; i >= 0; i--) { + if (p_number & (1 << i)) { + return i + 1; + } + } + + return 0; +} + +/** get a shift value from a power of 2 */ +static inline int get_shift_from_power_of_2(unsigned int p_pixel) { + // return a GL_TEXTURE_SIZE_ENUM + + for (unsigned int i = 0; i < 32; i++) { + if (p_pixel == (unsigned int)(1 << i)) { + return i; + } + } + + return -1; +} + +/** Swap 16 bits value for endianness */ +#if defined(__GNUC__) || _llvm_has_builtin(__builtin_bswap16) +#define BSWAP16(x) __builtin_bswap16(x) +#else +static inline uint16_t BSWAP16(uint16_t x) { + return (x >> 8) | (x << 8); +} +#endif + +/** Swap 32 bits value for endianness */ +#if defined(__GNUC__) || _llvm_has_builtin(__builtin_bswap32) +#define BSWAP32(x) __builtin_bswap32(x) +#else +static inline uint32_t BSWAP32(uint32_t x) { + return ((x << 24) | ((x << 8) & 0x00FF0000) | ((x >> 8) & 0x0000FF00) | (x >> 24)); +} +#endif + +/** Swap 64 bits value for endianness */ +#if defined(__GNUC__) || _llvm_has_builtin(__builtin_bswap64) +#define BSWAP64(x) __builtin_bswap64(x) +#else +static inline uint64_t BSWAP64(uint64_t x) { + x = (x & 0x00000000FFFFFFFF) << 32 | (x & 0xFFFFFFFF00000000) >> 32; + x = (x & 0x0000FFFF0000FFFF) << 16 | (x & 0xFFFF0000FFFF0000) >> 16; + x = (x & 0x00FF00FF00FF00FF) << 8 | (x & 0xFF00FF00FF00FF00) >> 8; + return x; +} +#endif + +/** When compiling with RTTI, we can add an "extra" + * layer of safeness in many operations, so dynamic_cast + * is used besides casting by enum. + */ + +template +struct Comparator { + _ALWAYS_INLINE_ bool operator()(const T &p_a, const T &p_b) const { return (p_a < p_b); } +}; + +void _global_lock(); +void _global_unlock(); + +struct _GlobalLock { + _GlobalLock() { _global_lock(); } + ~_GlobalLock() { _global_unlock(); } +}; + +#define GLOBAL_LOCK_FUNCTION _GlobalLock _global_lock_; + +#ifdef NO_SAFE_CAST +#define SAFE_CAST static_cast +#else +#define SAFE_CAST dynamic_cast +#endif + +#define MT_SAFE + +#define __STRX(m_index) #m_index +#define __STR(m_index) __STRX(m_index) + +#ifdef __GNUC__ +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) +#else +#define likely(x) x +#define unlikely(x) x +#endif + +#if defined(__GNUC__) +#define _PRINTF_FORMAT_ATTRIBUTE_2_0 __attribute__((format(printf, 2, 0))) +#define _PRINTF_FORMAT_ATTRIBUTE_2_3 __attribute__((format(printf, 2, 3))) +#else +#define _PRINTF_FORMAT_ATTRIBUTE_2_0 +#define _PRINTF_FORMAT_ATTRIBUTE_2_3 +#endif + +/** This is needed due to a strange OpenGL API that expects a pointer + * type for an argument that is actually an offset. + */ +#define CAST_INT_TO_UCHAR_PTR(ptr) ((uint8_t *)(uintptr_t)(ptr)) + +/** Hint for compilers that this fallthrough in a switch is intentional. + * Can be replaced by [[fallthrough]] annotation if we move to C++17. + * Including conditional support for it for people who set -std=c++17 + * themselves. + * Requires a trailing semicolon when used. + */ +#if __cplusplus >= 201703L +#define FALLTHROUGH [[fallthrough]] +#elif defined(__GNUC__) && __GNUC__ >= 7 +#define FALLTHROUGH __attribute__((fallthrough)) +#elif defined(__llvm__) && __cplusplus >= 201103L && defined(__has_feature) +#if __has_feature(cxx_attributes) && defined(__has_warning) +#if __has_warning("-Wimplicit-fallthrough") +#define FALLTHROUGH [[clang::fallthrough]] +#endif +#endif +#endif + +#ifndef FALLTHROUGH +#define FALLTHROUGH +#endif + +#endif // TYPEDEFS_H diff --git a/core/variant.cpp b/core/variant.cpp new file mode 100644 index 0000000..4dafbb5 --- /dev/null +++ b/core/variant.cpp @@ -0,0 +1,624 @@ +#include "variant.h" + +#include "core/math/math.h" +#include "core/reference.h" + +Variant::Type Variant::get_type() const { + return _type; +} + +void Variant::clear() { + switch (_type) { + case TYPE_NULL: + break; + case TYPE_BOOL: + _bool = false; + break; + case TYPE_INT: + _int = 0; + break; + case TYPE_UINT: + _uint = 0; + break; + case TYPE_FLOAT: + _float = 0; + break; + case TYPE_STRING: + if (_string->owner) { + delete _string->string; + } + + delete _string; + _string = nullptr; + + break; + case TYPE_OBJECT: + delete _object; + _object = nullptr; + + break; + case TYPE_POINTER: + _pointer = nullptr; + + break; + default: + break; + } + + _type = TYPE_NULL; +} +void Variant::zero() { + switch (_type) { + case TYPE_NULL: + break; + case TYPE_BOOL: + _bool = false; + break; + case TYPE_INT: + _int = 0; + break; + case TYPE_UINT: + _uint = 0; + break; + case TYPE_FLOAT: + _float = 0; + break; + case TYPE_STRING: + _string->string->resize(0); + break; + case TYPE_OBJECT: + _object->object = nullptr; + _object->reference.unref(); + break; + case TYPE_POINTER: + _pointer = nullptr; + break; + default: + break; + } +} + +void Variant::parse(const String &str) { + if (str.is_int()) { + set_int(str.to_int()); + return; + } + + if (str.is_uint()) { + set_uint(str.to_uint()); + return; + } + + if (str.is_numeric()) { + set_float(str.to_float()); + return; + } + + if (str.is_bool()) { + set_bool(str.to_bool()); + return; + } + + set_string(str); +} +Variant Variant::parse_string(const String &str) { + Variant v = Variant(); + + v.parse(str); + + return v; +} + +bool Variant::is_null() const { + return _type == TYPE_NULL; +} +bool Variant::is_bool() const { + return _type == TYPE_BOOL; +} +bool Variant::is_int() const { + return _type == TYPE_INT; +} +bool Variant::is_uint() const { + return _type == TYPE_UINT; +} +bool Variant::is_float() const { + return _type == TYPE_FLOAT; +} +bool Variant::is_numeric() const { + return _type == TYPE_INT || _type == TYPE_UINT || _type == TYPE_FLOAT; +} +bool Variant::is_string() const { + return _type == TYPE_STRING; +} +bool Variant::is_object() const { + return _type == TYPE_OBJECT; +} +bool Variant::is_pointer() const { + return _type == TYPE_POINTER; +} +bool Variant::is_reference() const { + if (_type == TYPE_OBJECT) { + return _object->reference.is_valid(); + } + + return false; +} + +bool Variant::is_primitive_type() const { + return _type == TYPE_BOOL || _type == TYPE_INT || _type == TYPE_UINT || _type == TYPE_FLOAT; +} + +bool Variant::is_simple_type() const { + return _type == TYPE_BOOL || _type == TYPE_INT || _type == TYPE_UINT || _type == TYPE_FLOAT || _type == TYPE_STRING; +} + +bool Variant::to_bool() const { + return _bool; +} +int Variant::to_int() const { + switch (_type) { + case TYPE_NULL: + return 0; + case TYPE_BOOL: + if (_bool) { + return 1; + } else { + return 0; + } + case TYPE_INT: + case TYPE_UINT: + return _int; + case TYPE_FLOAT: + return static_cast(_int); + case TYPE_STRING: + return _string->string->to_int(); + case TYPE_OBJECT: + case TYPE_POINTER: + // just read the value of the pointer as int + // Could return 1 or 0, but this is almost the same, but hopefully it's more useful + return _int; + default: + return 0; + } +} +uint64_t Variant::to_uint() const { + switch (_type) { + case TYPE_NULL: + return 0; + case TYPE_BOOL: + if (_bool) { + return 1; + } else { + return 0; + } + case TYPE_INT: + case TYPE_UINT: + return _uint; + case TYPE_FLOAT: + return static_cast(_float); + case TYPE_STRING: + return _string->string->to_uint(); + case TYPE_OBJECT: + case TYPE_POINTER: + // just read the value of the pointer as uint + // Could return 1 or 0, but this is almost the same, but hopefully it's more useful + return _uint; + default: + return 0; + } +} +float Variant::to_float() const { + switch (_type) { + case TYPE_NULL: + return 0; + case TYPE_BOOL: + if (_bool) { + return 0; + } else { + return 1; + } + case TYPE_INT: + return static_cast(_int); + case TYPE_UINT: + return static_cast(_uint); + case TYPE_FLOAT: + return _float; + case TYPE_STRING: + return _string->string->to_float(); + case TYPE_OBJECT: + case TYPE_POINTER: + if (_uint) { + return 1; + } else { + return 0; + } + default: + return 0; + } +} +String Variant::to_string() const { + switch (_type) { + case TYPE_NULL: + return "NULL"; + case TYPE_BOOL: + if (Math::is_zero_approx(_float)) { + return "false"; + } else { + return "true"; + } + case TYPE_INT: + return String::num(_int); + case TYPE_UINT: + return String::num(_uint); + case TYPE_FLOAT: + return String::num(_float); + case TYPE_STRING: + return *(_string->string); + case TYPE_OBJECT: + case TYPE_POINTER: + if (_uint) { + return "[ Object ]"; + } else { + return "[ Object (NULL) ]"; + } + default: + return ""; + } +} +Object *Variant::to_object() const { + switch (_type) { + case TYPE_NULL: + return nullptr; + case TYPE_BOOL: + return nullptr; + case TYPE_INT: + return nullptr; + case TYPE_UINT: + return nullptr; + case TYPE_FLOAT: + return nullptr; + case TYPE_STRING: + return nullptr; + case TYPE_OBJECT: + return _object->object; + case TYPE_POINTER: + return nullptr; + default: + return nullptr; + } +} +Reference *Variant::to_reference() const { + switch (_type) { + case TYPE_NULL: + return nullptr; + case TYPE_BOOL: + return nullptr; + case TYPE_INT: + return nullptr; + case TYPE_UINT: + return nullptr; + case TYPE_FLOAT: + return nullptr; + case TYPE_STRING: + return nullptr; + case TYPE_OBJECT: + return Object::cast_to(_object->object); + case TYPE_POINTER: + return nullptr; + default: + return nullptr; + } +} +void *Variant::to_pointer() const { + switch (_type) { + case TYPE_NULL: + return nullptr; + case TYPE_BOOL: + return nullptr; + case TYPE_INT: + return nullptr; + case TYPE_UINT: + return nullptr; + case TYPE_FLOAT: + return nullptr; + case TYPE_STRING: + return nullptr; + case TYPE_OBJECT: + return nullptr; + case TYPE_POINTER: + return _pointer; + default: + return nullptr; + } +} +String *Variant::get_string_ptr() const { + if (_type == TYPE_STRING) { + return _string->string; + } + + return nullptr; +} +bool Variant::is_string_owned() const { + if (_type == TYPE_STRING) { + return _string->owner; + } + + return false; +} + +void Variant::set_null() { + clear(); + + _type = TYPE_NULL; +} +void Variant::set_bool(const bool value) { + clear(); + + _type = TYPE_BOOL; + _bool = value; +} +void Variant::set_int(const int value) { + clear(); + + _type = TYPE_INT; + _int = value; +} +void Variant::set_uint(const uint64_t value) { + clear(); + + _type = TYPE_UINT; + _uint = value; +} +void Variant::set_float(const float value) { + clear(); + + _type = TYPE_FLOAT; + _float = value; +} +void Variant::set_float(const double value) { + clear(); + + _type = TYPE_FLOAT; + _float = value; +} +void Variant::set_string(String *value, const bool copy) { + clear(); + + if (!value) { + return; + } + + _type = TYPE_STRING; + + _string = new StringData(); + + if (copy) { + _string->string = new String(*value); + _string->owner = true; + } else { + _string->string = value; + _string->owner = false; + } +} +void Variant::set_string(const String &value, const bool copy) { + clear(); + + _type = TYPE_STRING; + + _string = new StringData(); + + if (copy) { + _string->string = new String(value); + _string->owner = true; + } else { + _string->string = &const_cast(value); + _string->owner = false; + } +} +void Variant::set_object(Object *value) { + clear(); + + if (!value) { + return; + } + + _object = new ObjectData(); + _object->object = value; + + if (value->is_class_ptr(Reference::get_class_ptr_static())) { + _object->reference = Ref(Object::cast_to(value)); + } +} +void Variant::set_pointer(void *value) { + clear(); + + _type = TYPE_POINTER; + _pointer = value; +} +void Variant::set_variant(const Variant &value) { + clear(); + + switch (value._type) { + case TYPE_NULL: + break; + case TYPE_BOOL: + _bool = value._bool; + break; + case TYPE_INT: + _int = value._int; + break; + case TYPE_UINT: + _uint = value._uint; + break; + case TYPE_FLOAT: + _float = value._float; + break; + case TYPE_STRING: + set_string(value._string->string, true); + break; + case TYPE_OBJECT: + set_object(value._object->object); + break; + case TYPE_POINTER: + _pointer = value._pointer; + default: + break; + } + + _type = value._type; +} + +Variant::operator bool() const { + return to_bool(); +} +Variant::operator int() const { + return to_int(); +} +Variant::operator uint64_t() const { + return to_uint(); +} +Variant::operator float() const { + return to_float(); +} +Variant::operator double() const { + return to_float(); +} +Variant::operator String() const { + return to_string(); +} +Variant::operator Object *() const { + return to_object(); +} +Variant::operator Reference *() const { + return to_reference(); +} +Variant::operator void *() const { + return to_pointer(); +} + +void Variant::operator=(const Variant &other) { + set_variant(other); +} +bool Variant::operator==(const Variant &other) const { + if (_type != other._type) { + return false; + } + + switch (_type) { + case TYPE_NULL: + return true; + case TYPE_BOOL: + return _bool == other._bool; + case TYPE_INT: + return _int == other._int; + case TYPE_UINT: + return _uint == other._uint; + case TYPE_FLOAT: + return _float == other._float; + case TYPE_STRING: + return (*_string->string) == (*other._string->string); + case TYPE_OBJECT: + return (_object->object) == (other._object->object); + case TYPE_POINTER: + return _pointer == other._pointer; + default: + break; + } + + return false; +} +bool Variant::operator!=(const Variant &other) const { + return !(operator==(other)); +} +bool Variant::operator<(const Variant &other) const { + switch (_type) { + case TYPE_NULL: { + if (other.is_null()) { + return false; + } else { + return true; + } + } + case TYPE_BOOL: { + return _bool < other.to_bool(); + } + case TYPE_INT: + return _int < other.to_int(); + return _int == other._int; + case TYPE_UINT: + return _uint < other.to_uint(); + return _uint == other._uint; + case TYPE_FLOAT: + return _float < other.to_float(); + return _float == other._float; + case TYPE_STRING: + return (*_string->string) < other.to_string(); + case TYPE_OBJECT: + return (_object->object) < other.to_object(); + case TYPE_POINTER: + return _pointer < other.to_pointer(); + default: + break; + } + + return false; +} + +Variant::Variant(const bool value) { + _type = TYPE_BOOL; + + _bool = value; +} +Variant::Variant(const int value) { + _type = TYPE_INT; + + _int = value; +} +Variant::Variant(const uint64_t value) { + _type = TYPE_UINT; + + _uint = value; +} +Variant::Variant(const float value) { + _type = TYPE_FLOAT; + + _float = value; +} +Variant::Variant(const double value) { + _type = TYPE_FLOAT; + + _float = value; +} +Variant::Variant(String *value, bool copy) { + _type = TYPE_NULL; + + set_string(value, copy); +} +Variant::Variant(const String &value, bool copy) { + _type = TYPE_NULL; + + set_string(value, copy); +} +Variant::Variant(Object *value) { + _type = TYPE_NULL; + + set_object(value); +} +Variant::Variant(void *value) { + _type = TYPE_NULL; + + set_pointer(value); +} +Variant::Variant(const Variant &value) { + _type = TYPE_NULL; + + set_variant(value); +} + +Variant::Variant() { + _type = TYPE_NULL; +} + +Variant::~Variant() { + clear(); +} \ No newline at end of file diff --git a/core/variant.h b/core/variant.h new file mode 100644 index 0000000..8720915 --- /dev/null +++ b/core/variant.h @@ -0,0 +1,120 @@ +#ifndef VARIANT_H +#define VARIANT_H + +#include "core/object.h" +#include "core/reference.h" +#include "core/string.h" + +class Variant { + +public: + enum Type { + TYPE_NULL = 0, + TYPE_BOOL, + TYPE_INT, + TYPE_UINT, + TYPE_FLOAT, + TYPE_STRING, + TYPE_OBJECT, + TYPE_POINTER, + }; + + Type get_type() const; + + void clear(); + void zero(); + + void parse(const String &str); + static Variant parse_string(const String &str); + + bool is_null() const; + bool is_bool() const; + bool is_int() const; + bool is_uint() const; + bool is_float() const; + bool is_numeric() const; + bool is_string() const; + bool is_object() const; + bool is_pointer() const; + bool is_reference() const; + + bool is_primitive_type() const; + bool is_simple_type() const; + + bool to_bool() const; + int to_int() const; + uint64_t to_uint() const; + float to_float() const; + String to_string() const; + Object *to_object() const; + Reference *to_reference() const; + void *to_pointer() const; + String *get_string_ptr() const; + bool is_string_owned() const; + + void set_null(); + void set_bool(const bool value); + void set_int(const int value); + void set_uint(const uint64_t value); + void set_float(const float value); + void set_float(const double value); + void set_string(String *value, bool copy = false); + void set_string(const String &value, bool copy = true); + void set_object(Object *value); + void set_pointer(void *value); + void set_variant(const Variant &value); + + operator bool() const; + operator int() const; + operator uint64_t() const; + operator float() const; + operator double() const; + operator String() const; + operator Object *() const; + operator Reference *() const; + operator void *() const; + + void operator=(const Variant &other); + bool operator==(const Variant &other) const; + bool operator!=(const Variant &other) const; + bool operator<(const Variant &other) const; + + Variant(const bool value); + Variant(const int value); + Variant(const uint64_t value); + Variant(const float value); + Variant(const double value); + Variant(String *value, const bool copy = false); + Variant(const String &value, const bool copy = true); + Variant(Object *value); + Variant(void *value); + Variant(const Variant &value); + + Variant(); + ~Variant(); + +private: + struct StringData { + bool owner; + String *string; + }; + + struct ObjectData { + Object *object; + Ref reference; + }; + + union { + bool _bool; + int _int; + uint64_t _uint; + float _float; + StringData *_string; + ObjectData *_object; + void *_pointer; + }; + + Type _type; +}; + +#endif \ No newline at end of file