From 1876caaa9f0b712872dea234fc364f507ede6a15 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Sat, 6 Apr 2024 10:19:46 +0200 Subject: [PATCH 01/39] added new texteditor files with ImFileDialog, updated imgui to 1.90.1-docking --- .gitmodules | 5 +- CMakeLists.txt | 10 +- ext/ImFileDialog | 1 + ext/ImGuiColorTextEdit | 2 +- ext/imgui | 2 +- main.cpp | 3 +- stage.cpp | 10 +- stb_image.h | 7987 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 8010 insertions(+), 10 deletions(-) create mode 160000 ext/ImFileDialog create mode 100644 stb_image.h diff --git a/.gitmodules b/.gitmodules index 46af7c68..6f30a9eb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -24,7 +24,10 @@ url = https://github.com/libsdl-org/SDL.git [submodule "ext/ImGuiColorTextEdit"] path = ext/ImGuiColorTextEdit - url = https://github.com/sphaero/ImGuiColorTextEdit.git + url = https://github.com/pthom/ImGuiColorTextEdit.git [submodule "imgui"] path = ext/imgui url = https://github.com/ocornut/imgui.git +[submodule "ext/ImFileDialog"] + path = ext/ImFileDialog + url = https://github.com/dfranx/ImFileDialog diff --git a/CMakeLists.txt b/CMakeLists.txt index dcc8f6a5..9924212d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -185,6 +185,10 @@ add_custom_command( list(APPEND GZB_SOURCES main.cpp + app/App.hpp + app/Window.hpp + app/TextEditorWindow.hpp + app/TextEditorWindow.cpp ext/imgui/backends/imgui_impl_opengl3.cpp ext/imgui/backends/imgui_impl_sdl2.cpp ext/imgui/imconfig.h @@ -195,6 +199,8 @@ list(APPEND GZB_SOURCES ext/imgui/imgui_demo.cpp ImNodes.cpp ImNodesEz.cpp + ext/ImFileDialog/ImFileDialog.h + ext/ImFileDialog/ImFileDialog.cpp ext/ImGui-Addons/FileBrowser/ImGuiFileBrowser.cpp ext/ImGuiColorTextEdit/LanguageDefinitions.cpp ext/ImGuiColorTextEdit/TextEditor.cpp @@ -202,11 +208,13 @@ list(APPEND GZB_SOURCES ) list(APPEND GZB_INCLUDEDIRS + ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${Unwind_INCLUDE_DIRS} ${OPENVR_INCLUDE_DIR} ${libzmq_INCLUDE_DIRS} ${Python3_INCLUDE_DIRS} + ext/ ext/ImGuiColorTextEdit ext/imgui ext/imgui/backends @@ -246,7 +254,7 @@ endif (Unwind_FOUND) target_link_options(gazebosc PUBLIC ${Python3_LINK_OPTIONS}) target_compile_options(gazebosc PUBLIC ${SDL2_CFLAGS_OTHER} ) -target_compile_definitions(gazebosc PUBLIC -DIMGUI_DISABLE_OBSOLETE_FUNCTIONS=1) +target_compile_definitions(gazebosc PUBLIC -DIMGUI_DISABLE_OBSOLETE_FUNCTIONS=0) set(RESOURCE_DEST "./") set(RUNTIME_DEST "bin") diff --git a/ext/ImFileDialog b/ext/ImFileDialog new file mode 160000 index 00000000..a4753476 --- /dev/null +++ b/ext/ImFileDialog @@ -0,0 +1 @@ +Subproject commit a4753476b1bdb22566d6913924206326704dc2cf diff --git a/ext/ImGuiColorTextEdit b/ext/ImGuiColorTextEdit index 76069b71..06ce9419 160000 --- a/ext/ImGuiColorTextEdit +++ b/ext/ImGuiColorTextEdit @@ -1 +1 @@ -Subproject commit 76069b71f7d30a57e6befbae31d5a816c4f5fb5f +Subproject commit 06ce941998358cf8cb524421acb776337ef62125 diff --git a/ext/imgui b/ext/imgui index 458a1090..2dc85e6e 160000 --- a/ext/imgui +++ b/ext/imgui @@ -1 +1 @@ -Subproject commit 458a1090314a965dd37b02c918d83077a0142ad5 +Subproject commit 2dc85e6e438e6d7f485317c9b76dc153535fed16 diff --git a/main.cpp b/main.cpp index d3338ec9..00e4e53c 100644 --- a/main.cpp +++ b/main.cpp @@ -54,6 +54,7 @@ namespace fs = std::filesystem; #include "config.h" +#include "app/App.hpp" // Forward declare to keep main func on top for readability int SDLInit(SDL_Window** window, SDL_GLContext* gl_context, const char** glsl_version); @@ -525,7 +526,7 @@ ImGuiIO& ImGUIInit(SDL_Window* window, SDL_GLContext* gl_context, const char* gl font_cfg.PixelSnapH = true; font_cfg.SizePixels = 13.0f * 1.0f; font_cfg.EllipsisChar = (ImWchar)0x0085; - font_cfg.GlyphOffset.y = 1.0f * IM_FLOOR(font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units + font_cfg.GlyphOffset.y = 1.0f * IM_TRUNC(font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units char fontpath[PATH_MAX]; snprintf(fontpath, PATH_MAX, "%s/%s", GZB_GLOBAL.RESOURCESPATH, "misc/fonts/ProggyCleanSZ.ttf"); io.Fonts->AddFontFromFileTTF(fontpath, 13.0f, &font_cfg); diff --git a/stage.cpp b/stage.cpp index b3b0e2b3..d7fa9f3e 100644 --- a/stage.cpp +++ b/stage.cpp @@ -907,12 +907,12 @@ int UpdateActors(float deltaTime, bool * showLog) ImGui::SetNextWindowPos(ImVec2(0,0)); ImGuiIO &io = ImGui::GetIO(); - bool copy = ImGui::IsKeyPressedMap(ImGuiKey_C) && (io.KeySuper || io.KeyCtrl); - bool paste = ImGui::IsKeyPressedMap(ImGuiKey_V) && (io.KeySuper || io.KeyCtrl); + bool copy = ImGui::IsKeyPressed(ImGuiKey_C) && (io.KeySuper || io.KeyCtrl); + bool paste = ImGui::IsKeyPressed(ImGuiKey_V) && (io.KeySuper || io.KeyCtrl); bool dup = ( keyState[SDL_SCANCODE_D] == 1 && !D_PRESSED) && (io.KeySuper || io.KeyCtrl); - bool undo = ImGui::IsKeyPressedMap(ImGuiKey_Z) && (io.KeySuper || io.KeyCtrl); - bool redo = ImGui::IsKeyPressedMap(ImGuiKey_Y) && (io.KeySuper || io.KeyCtrl); - bool del = ImGui::IsKeyPressedMap(ImGuiKey_Delete) || (io.KeySuper && ImGui::IsKeyPressedMap(ImGuiKey_Backspace)); + bool undo = ImGui::IsKeyPressed(ImGuiKey_Z) && (io.KeySuper || io.KeyCtrl); + bool redo = ImGui::IsKeyPressed(ImGuiKey_Y) && (io.KeySuper || io.KeyCtrl); + bool del = ImGui::IsKeyPressed(ImGuiKey_Delete) || (io.KeySuper && ImGui::IsKeyPressed(ImGuiKey_Backspace)); D_PRESSED = keyState[SDL_SCANCODE_D] == 1; diff --git a/stb_image.h b/stb_image.h new file mode 100644 index 00000000..5e807a0a --- /dev/null +++ b/stb_image.h @@ -0,0 +1,7987 @@ +/* stb_image - v2.28 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + +LICENSE + + See end of file for license information. + +RECENT REVISION HISTORY: + + 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff + 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes + 2.26 (2020-07-13) many minor fixes + 2.25 (2020-02-02) fix warnings + 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically + 2.23 (2019-08-11) fix clang static analysis warning + 2.22 (2019-03-04) gif fixes, fix warnings + 2.21 (2019-02-25) fix typo in comment + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings + 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + Arseny Kapoulkine Simon Breuss (16-bit PNM) + John-Mark Allen + Carmelo J Fdez-Aguera + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski + Phil Jordan Dave Moore Roy Eltham + Hayaki Saito Nathan Reed Won Chun + Luke Graham Johan Duparc Nick Verigakis the Horde3D community + Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Eugene Golushkov Laurent Gomila Cort Stratton github:snagar + Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex + Cass Everitt Ryamond Barbiero github:grim210 + Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw + Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus + Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo + Julian Raschke Gregory Mullen Christian Floisand github:darealshinji + Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 + Brad Weinberger Matvey Cherevko github:mosra + Luca Sas Alexander Veselov Zack Middleton [reserved] + Ryan C. Gordon [reserved] [reserved] + DO NOT ADD YOUR NAME HERE + + Jacko Dirks + + To add your name to the credits, pick a random blank space in the middle and fill it. + 80% of merge conflicts on stb PRs are due to people adding their name at the end + of the credits. +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data); +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'desired_channels' if desired_channels is non-zero, or +// *channels_in_file otherwise. If desired_channels is non-zero, +// *channels_in_file has the number of components that _would_ have been +// output otherwise. E.g. if you set desired_channels to 4, you will always +// get RGBA output, but you can check *channels_in_file to see if it's trivially +// opaque because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *channels_in_file will be unchanged. The function +// stbi_failure_reason() can be queried for an extremely brief, end-user +// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS +// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// To query the width, height and component count of an image without having to +// decode the full file, you can use the stbi_info family of functions: +// +// int x,y,n,ok; +// ok = stbi_info(filename, &x, &y, &n); +// // returns ok=1 and sets x, y, n if image is a supported format, +// // 0 otherwise. +// +// Note that stb_image pervasively uses ints in its public API for sizes, +// including sizes of memory buffers. This is now part of the API and thus +// hard to change without causing breakage. As a result, the various image +// loaders all have certain limits on image size; these differ somewhat +// by format but generally boil down to either just under 2GB or just under +// 1GB. When the decoded image would be larger than this, stb_image decoding +// will fail. +// +// Additionally, stb_image will reject image files that have any of their +// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS, +// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit, +// the only way to have an image with such dimensions load correctly +// is for it to have a rather extreme aspect ratio. Either way, the +// assumption here is that such larger images are likely to be malformed +// or malicious. If you do need to load an image with individual dimensions +// larger than that, and it still fits in the overall size limit, you can +// #define STBI_MAX_DIMENSIONS on your own to be something larger. +// +// =========================================================================== +// +// UNICODE: +// +// If compiling for Windows and you wish to use Unicode filenames, compile +// with +// #define STBI_WINDOWS_UTF8 +// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert +// Windows wchar_t filenames to utf8. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy-to-use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// provide more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image supports loading HDR images in general, and currently the Radiance +// .HDR file format specifically. You can still load any file through the existing +// interface; if you attempt to load an HDR file, it will be automatically remapped +// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// We optionally support converting iPhone-formatted PNGs (which store +// premultiplied BGRA) back to RGB, even though they're internally encoded +// differently. To enable this conversion, call +// stbi_convert_iphone_png_to_rgb(1). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// +// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater +// than that size (in either width or height) without further processing. +// This is to let programs in the wild set an upper bound to prevent +// denial-of-service attacks on untrusted data, as one could generate a +// valid image of gigantic dimensions and force stb_image to allocate a +// huge block of memory and spend disproportionate time decoding it. By +// default this is set to (1 << 24), which is 16777216, but that's still +// very big. + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +#include +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef STBIDEF +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + +#ifdef STBI_WINDOWS_UTF8 +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// on most compilers (and ALL modern mainstream compilers) this is threadsafe +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// as above, but only applies to images loaded on the thread that calls the function +// this function is only available if your compiler supports thread-local variables; +// calling it will fail to link if your compiler doesn't +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply); +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert); +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + +#ifdef __cplusplus +#define STBI_EXTERN extern "C" +#else +#define STBI_EXTERN extern +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + +#ifndef STBI_NO_THREAD_LOCALS + #if defined(__cplusplus) && __cplusplus >= 201103L + #define STBI_THREAD_LOCAL thread_local + #elif defined(__GNUC__) && __GNUC__ < 5 + #define STBI_THREAD_LOCAL __thread + #elif defined(_MSC_VER) + #define STBI_THREAD_LOCAL __declspec(thread) + #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) + #define STBI_THREAD_LOCAL _Thread_local + #endif + + #ifndef STBI_THREAD_LOCAL + #if defined(__GNUC__) + #define STBI_THREAD_LOCAL __thread + #endif + #endif +#endif + +#if defined(_MSC_VER) || defined(__SYMBIAN32__) +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#endif + +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif + +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +#ifdef _MSC_VER +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name +#else +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +#ifndef STBI_MAX_DIMENSIONS +#define STBI_MAX_DIMENSIONS (1 << 24) +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + int callback_already_read; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + int ch; + fseek((FILE*) user, n, SEEK_CUR); + ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */ + if (ch != EOF) { + ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */ + } +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user) || ferror((FILE *) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__pnm_is16(stbi__context *s); +#endif + +static +#ifdef STBI_THREAD_LOCAL +STBI_THREAD_LOCAL +#endif +const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +#ifndef STBI_NO_FAILURE_STRINGS +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} +#endif + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} +#endif + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} +#endif + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow. +static int stbi__addints_valid(int a, int b) +{ + if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow + if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0. + return a <= INT_MAX - b; +} + +// returns 1 if the product of two signed shorts is valid, 0 on overflow. +static int stbi__mul2shorts_valid(short a, short b) +{ + if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow + if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid + if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN + return a >= SHRT_MIN / b; +} + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load_global = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_global = flag_true_if_should_flip; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global +#else +static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; + +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_local = flag_true_if_should_flip; + stbi__vertically_flip_on_load_set = 1; +} + +#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ + ? stbi__vertically_flip_on_load_local \ + : stbi__vertically_flip_on_load_global) +#endif // STBI_THREAD_LOCAL + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + // test the formats with a very explicit header first (at least a FOURCC + // or distinctive magic number first) + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #else + STBI_NOTUSED(bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + + // then the formats that can end up attempting to load with just 1 or 2 + // bytes matching expectations; these are prone to false positives, so + // try them later + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 8) { + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 16) { + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} + +#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); +#endif + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) +// nothing +#else +static void stbi__skip(stbi__context *s, int n) +{ + if (n == 0) return; // already there! + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) +// nothing +#else +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} +#endif + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + z += (stbi__uint32)stbi__get16le(s) << 16; + return z; +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + } + if (n < comp) { + for (i=0; i < x*y; ++i) { + output[i*comp + n] = data[i*comp + n]/255.0f; + } + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) { + for (j=0; j < count[i]; ++j) { + h->size[k++] = (stbi_uc) (i+1); + if(k >= 257) return stbi__err("bad size list","Corrupt JPEG"); + } + } + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + if(c < 0 || c >= 256) // symbol id out of bounds! + return -1; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing + + sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & (sgn - 1)); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG"); + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + diff = t ? stbi__extend_receive(j, t) : 0; + + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG"); + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * (1 << j->succ_low)); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * (1 << shift)); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift)); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + if(n > 256) return stbi__err("bad DHT header","Corrupt JPEG"); // Loop over i < n would write past end of values! + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios + // and I've never seen a non-corrupted JPEG file actually use them + for (i=0; i < s->img_n; ++i) { + if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG"); + if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG"); + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +static int stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) +{ + // some JPEGs have junk at end, skip over it but if we find what looks + // like a valid marker, resume there + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + while (x == 255) { // might be a marker + if (stbi__at_eof(j->s)) return STBI__MARKER_none; + x = stbi__get8(j->s); + if (x != 0x00 && x != 0xff) { + // not a stuffed zero or lead-in to another marker, looks + // like an actual marker, return it + return x; + } + // stuffed zero has x=0 now which ends the loop, meaning we go + // back to regular scan loop. + // repeated 0xff keeps trying to read the next byte of the marker. + } + } + return STBI__MARKER_none; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + j->marker = stbi__skip_jpeg_junk_at_end(j); + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + m = stbi__get_marker(j); + if (STBI__RESTART(m)) + m = stbi__get_marker(j); + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + m = stbi__get_marker(j); + } else { + if (!stbi__process_marker(j, m)) return 1; + m = stbi__get_marker(j); + } + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // nothing to do if no components requested; check this now to avoid + // accessing uninitialized coutput[0] later + if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; } + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__errpuc("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) +#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[STBI__ZNSYMS]; + stbi__uint16 value[STBI__ZNSYMS]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static int stbi__zeof(stbi__zbuf *z) +{ + return (z->zbuffer >= z->zbuffer_end); +} + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + return stbi__zeof(z) ? 0 : *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + if (z->code_buffer >= (1U << z->num_bits)) { + z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */ + return; + } + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s >= 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere! + if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) { + if (stbi__zeof(a)) { + return -1; /* report error for unexpected end of data. */ + } + stbi__fill_bits(a); + } + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + unsigned int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (unsigned int) (z->zout - z->zout_start); + limit = old_limit = (unsigned) (z->zout_end - z->zout_start); + if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory"); + while (cur + n > limit) { + if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory"); + limit *= 2; + } + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0 || z >= 30) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + } else if (c == 18) { + c = stbi__zreceive(a,7)+11; + } else { + return stbi__err("bad codelengths", "Corrupt PNG"); + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG"); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior; + int filter = *raw++; + + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG"); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else if (depth == 16) { + if (img_n != out_n) { + cur[filter_bytes] = 255; // first pixel top byte + cur[filter_bytes+1] = 255; // first pixel bottom byte + } + raw += filter_bytes; + cur += output_bytes; + prior += output_bytes; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*filter_bytes; + #define STBI__CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; + } + #undef STBI__CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define STBI__CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ + for (k=0; k < filter_bytes; ++k) + switch (filter) { + STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; + } + #undef STBI__CASE + + // the loop above sets the high byte of the pixels' alpha, but for + // 16 bit png files we also need the low byte set. we'll do that here. + if (depth == 16) { + cur = a->out + stride*j; // start at the beginning of the row again + for (i=0; i < x; ++i,cur+=output_bytes) { + cur[filter_bytes+1] = 255; + } + } + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } else if (depth == 16) { + // force the image data from big-endian to platform-native. + // this is done in a separate pass due to the decoding relying + // on the data being untouched, but could probably be done + // per-line during decode if care is taken. + stbi_uc *cur = a->out; + stbi__uint16 *cur16 = (stbi__uint16*)cur; + + for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { + *cur16 = (cur[0] << 8) | cur[1]; + } + } + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + if (!final) return stbi__err("outofmem", "Out of memory"); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load_global = 0; +static int stbi__de_iphone_flag_global = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_global = flag_true_if_should_convert; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global +#define stbi__de_iphone_flag stbi__de_iphone_flag_global +#else +static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; +static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; + +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; + stbi__unpremultiply_on_load_set = 1; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_local = flag_true_if_should_convert; + stbi__de_iphone_flag_set = 1; +} + +#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \ + ? stbi__unpremultiply_on_load_local \ + : stbi__unpremultiply_on_load_global) +#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \ + ? stbi__de_iphone_flag_local \ + : stbi__de_iphone_flag_global) +#endif // STBI_THREAD_LOCAL + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]={0}; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); + s->img_y = stbi__get32be(s); + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + } + // even with SCAN_header, have to scan to see if we have a tRNS + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now. + if (scan == STBI__SCAN_header) { ++s->img_n; return 1; } + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { + // header scan definitely stops at first IDAT + if (pal_img_n) + s->img_n = pal_img_n; + return 1; + } + if (c.length > (1u << 30)) return stbi__err("IDAT size limit", "IDAT section larger than 2^30 bytes"); + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth <= 8) + ri->bits_per_channel = 8; + else if (p->depth == 16) + ri->bits_per_channel = 16; + else + return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth"); + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) { n += 16; z >>= 16; } + if (z >= 0x00100) { n += 8; z >>= 8; } + if (z >= 0x00010) { n += 4; z >>= 4; } + if (z >= 0x00004) { n += 2; z >>= 2; } + if (z >= 0x00002) { n += 1;/* >>= 1;*/ } + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(unsigned int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; + int extra_read; +} stbi__bmp_data; + +static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress) +{ + // BI_BITFIELDS specifies masks explicitly, don't override + if (compress == 3) + return 1; + + if (compress == 0) { + if (info->bpp == 16) { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } else if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + // otherwise, use defaults, which is all-0 + info->mr = info->mg = info->mb = info->ma = 0; + } + return 1; + } + return 0; // error +} + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + info->extra_read = 14; + + if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP"); + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes + if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + stbi__bmp_set_mask_defaults(info, compress); + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->extra_read += 12; + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + // V4/V5 header + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs + stbi__bmp_set_mask_defaults(info, compress); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - info.extra_read - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - info.extra_read - info.hsz) >> 2; + } + if (psize == 0) { + // accept some number of extra bytes after the header, but if the offset points either to before + // the header ends or implies a large amount of extra data, reject the file as malformed + int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original); + int header_limit = 1024; // max we actually read is below 256 bytes currently. + int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size. + if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) { + return stbi__errpuc("bad header", "Corrupt BMP"); + } + // we established that bytes_read_so_far is positive and sensible. + // the first half of this test rejects offsets that are either too small positives, or + // negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn + // ensures the number computed in the second half of the test can't overflow. + if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } else { + stbi__skip(s, info.offset - bytes_read_so_far); + } + } + + if (info.bpp == 24 && ma == 0xff000000) + s->img_n = 3; + else + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - info.extra_read - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i]; p1[i] = p2[i]; p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + STBI_NOTUSED(tga_x_origin); // @TODO + STBI_NOTUSED(tga_y_origin); // @TODO + + if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + if (tga_palette_len == 0) { /* you have to have at least one entry! */ + STBI_FREE(tga_data); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + STBI_NOTUSED(tga_palette_start); + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + + if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + if (!result) return stbi__errpuc("outofmem", "Out of memory"); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!g) return stbi__err("outofmem", "Out of memory"); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + STBI_NOTUSED(req_comp); + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) + return stbi__errpuc("too large", "GIF image is too large"); + pcount = g->w * g->h; + g->out = (stbi_uc *) stbi__malloc(4 * pcount); + g->background = (stbi_uc *) stbi__malloc(4 * pcount); + g->history = (stbi_uc *) stbi__malloc(pcount); + if (!g->out || !g->background || !g->history) + return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "transparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to the color that was there the previous frame. + memset(g->out, 0x00, 4 * pcount); + memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) + memset(g->history, 0x00, pcount); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispose of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + // if the width of the specified rectangle is 0, that means + // we may not see *any* pixels or the image is malformed; + // to make sure this is caught, move the current y down to + // max_y (which is what out_gif_code checks). + if (w == 0) + g->cur_y = g->max_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (!o) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays) +{ + STBI_FREE(g->out); + STBI_FREE(g->history); + STBI_FREE(g->background); + + if (out) STBI_FREE(out); + if (delays && *delays) STBI_FREE(*delays); + return stbi__errpuc("outofmem", "Out of memory"); +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + int out_size = 0; + int delays_size = 0; + + STBI_NOTUSED(out_size); + STBI_NOTUSED(delays_size); + + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); + if (!tmp) + return stbi__load_gif_main_outofmem(&g, out, delays); + else { + out = (stbi_uc*) tmp; + out_size = layers * stride; + } + + if (delays) { + int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); + if (!new_delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + *delays = new_delays; + delays_size = layers * sizeof(int); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (!out) + return stbi__load_gif_main_outofmem(&g, out, delays); + out_size = layers * stride; + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + if (!*delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + delays_size = layers * sizeof(int); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + STBI_NOTUSED(ri); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } else if (g.out) { + // if there was an error and we allocated an image buffer, free it! + STBI_FREE(g.out); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + if (p == NULL) { + stbi__rewind( s ); + return 0; + } + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) { + if (info.bpp == 24 && info.ma == 0xff000000) + *comp = 3; + else + *comp = info.ma ? 4 : 3; + } + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + STBI_NOTUSED(stbi__get32be(s)); + STBI_NOTUSED(stbi__get32be(s)); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n); + if (ri->bits_per_channel == 0) + return 0; + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) { + STBI_FREE(out); + return stbi__errpuc("bad PNM", "PNM file truncated"); + } + + if (req_comp && req_comp != s->img_n) { + if (ri->bits_per_channel == 16) { + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y); + } else { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + } + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + if((value > 214748364) || (value == 214748364 && *c > '7')) + return stbi__err("integer parse overflow", "Parsing an integer in the PPM header overflowed a 32-bit int"); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + if(*x == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + if (*y == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + if (maxv > 65535) + return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images"); + else if (maxv > 255) + return 16; + else + return 8; +} + +static int stbi__pnm_is16(stbi__context *s) +{ + if (stbi__pnm_info(s, NULL, NULL, NULL) == 16) + return 1; + return 0; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_is16(s)) return 1; + #endif + return 0; +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ From 0ec37e55325f0a3434e7fda4b42b607042e4dd15 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Sat, 6 Apr 2024 21:41:11 +0200 Subject: [PATCH 02/39] convert texteditor handling to new texteditor --- ActorContainer.h | 7 +++--- ext/ImFileDialog | 2 +- stage.cpp | 60 +++++++++++++++++++++--------------------------- 3 files changed, 30 insertions(+), 39 deletions(-) diff --git a/ActorContainer.h b/ActorContainer.h index 80a05172..78355d5b 100644 --- a/ActorContainer.h +++ b/ActorContainer.h @@ -51,7 +51,7 @@ s_replace_char(char *str, char from, char to) } // OpenTextEditor forward declare -void OpenTextEditor(zfile_t* txtfile); +void OpenTextEditor(const char *filepath); /// A structure defining a connection between two slots of two actors. struct Connection @@ -702,8 +702,7 @@ struct ActorContainer { const char* zvalueStr = zconfig_value(zvalue); if (strlen(zvalueStr) && zsys_file_exists (zvalueStr) ) { - zfile_t* f = zfile_new(nullptr, zvalueStr); - OpenTextEditor(f); // could own the f pointer + OpenTextEditor(zvalueStr); // could own the f pointer } else zsys_error("no valid file to load: %s", zvalueStr); @@ -818,7 +817,7 @@ struct ActorContainer { zconfig_set_value(zvalue, "%s", fnamebuf); sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), p ); - OpenTextEditor(txtfile); // could own the f pointer + OpenTextEditor(zfile_filename(txtfile, NULL)); // could own the f pointer } } } diff --git a/ext/ImFileDialog b/ext/ImFileDialog index a4753476..269c9352 160000 --- a/ext/ImFileDialog +++ b/ext/ImFileDialog @@ -1 +1 @@ -Subproject commit a4753476b1bdb22566d6913924206326704dc2cf +Subproject commit 269c9352d5eef3dd72d63ccf0fbcb5fbf4e09c36 diff --git a/stage.cpp b/stage.cpp index d7fa9f3e..15cdf9ad 100644 --- a/stage.cpp +++ b/stage.cpp @@ -34,6 +34,7 @@ namespace fs = std::filesystem; #include "libsphactor.h" #include "ActorContainer.h" #include "actors.h" +#include "app/TextEditorWindow.hpp" #include "ext/ImGui-Addons/FileBrowser/ImGuiFileBrowser.h" #include "ext/ImGuiColorTextEdit/TextEditor.h" #include "config.h" @@ -193,7 +194,7 @@ struct textfile { bool open; bool changed; }; -std::vector open_text_files; +std::vector text_editors; bool txteditor_open = false; textfile* current_editor = nullptr; textfile* hardswap_editor = nullptr; @@ -478,45 +479,33 @@ void ShowLogWindow(ImGuiTextBuffer& buffer) { ImGui::PopID(); } -void OpenTextEditor(zfile_t* txtfile) +void OpenTextEditor(const char *filepath) { - static auto lang = TextEditor::LanguageDefinition::Python(); + //static auto lang = TextEditor::LanguageDefinition::Python(); - assert(txtfile); - textfile * found = nullptr; - for (auto it = open_text_files.begin(); it != open_text_files.end(); ++it) + gzb::TextEditorWindow *txtwin = NULL; + for ( auto w : text_editors ) { - if (streq(zfile_filename(it->file, NULL), zfile_filename(txtfile, nullptr))) + if ( w->associated_file == filepath ) { - found = &(*it); - break; // file already exists in the editor + txtwin = w; } } - if (found == nullptr) + if (txtwin == NULL) { - zsys_info("TextEditor: loading & new textfile"); - // we own the pointer now! - int rc = zfile_input(txtfile); - assert(rc == 0); - zchunk_t* data = zfile_read(txtfile, zfile_cursize(txtfile), 0); - TextEditor* editor = new TextEditor(); - editor->SetLanguageDefinition(lang); - editor->SetText((char*)zchunk_data(data)); - char* basename = s_basename(zfile_filename(txtfile, nullptr)); - // add the new file to our list of open files - open_text_files.push_back({ editor, txtfile, basename, true, false }); - hardswap_editor = &(open_text_files.back()); + txtwin = new gzb::TextEditorWindow(filepath); + text_editors.push_back(txtwin); } - else { - zsys_info("TextEditor: existing text file found, activating the correct tab?"); - hardswap_editor = found; - // cleanup not used txtfile - zfile_destroy(&txtfile); + else + { + txtwin->showing = true; + ImGuiWindow *win = ImGui::FindWindowByName(txtwin->window_name.c_str()); + ImGui::FocusWindow(win); } txteditor_open = true; } -void ShowTextEditor() +/*void ShowTextEditor() { if (ImGui::Begin("Texteditor", &txteditor_open, ImGuiWindowFlags_MenuBar)) { @@ -664,10 +653,10 @@ void ShowTextEditor() { // file is closed by gui do we need to save it? // this doesn't work, mTextChanged is set to false when rendered - /*if ( it->editor->IsTextChanged() ) - { - zsys_debug("need to save file %s", it->basename ); - }*/ + //if ( it->editor->IsTextChanged() ) + //{ + // zsys_debug("need to save file %s", it->basename ); + //} zsys_debug("remove file %s", it->basename); open_text_files.erase(it); current_editor = nullptr; @@ -680,7 +669,7 @@ void ShowTextEditor() } ImGui::End(); } - +*/ int RenderMenuBar( bool * showLog ) { static char* configFile = new char[64] { 0x0 }; static int keyFocus = 0; @@ -1227,7 +1216,10 @@ int UpdateActors(float deltaTime, bool * showLog) if (showAbout) ShowAboutWindow(&showAbout); - if (txteditor_open) ShowTextEditor(); + for (gzb::TextEditorWindow *w : text_editors ) + { + if (w->showing ) w->OnImGui(); + } #ifdef HAVE_IMGUI_DEMO // ImGui Demo window for dev purposes if (showDemo) ImGui::ShowDemoWindow(&showDemo); From 5109e1e9b79bb7b86f011df3719b5a7dfdaf3576 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Sun, 7 Apr 2024 10:23:19 +0200 Subject: [PATCH 03/39] added new class files --- app/App.hpp | 19 +++ app/TextEditorWindow.cpp | 326 +++++++++++++++++++++++++++++++++++++++ app/TextEditorWindow.hpp | 41 +++++ app/Window.hpp | 17 ++ 4 files changed, 403 insertions(+) create mode 100644 app/App.hpp create mode 100644 app/TextEditorWindow.cpp create mode 100644 app/TextEditorWindow.hpp create mode 100644 app/Window.hpp diff --git a/app/App.hpp b/app/App.hpp new file mode 100644 index 00000000..8bc3a327 --- /dev/null +++ b/app/App.hpp @@ -0,0 +1,19 @@ +#ifndef WINDOW_HPP +#define WINDOW_HPP +#include "TextEditorWindow.hpp" +#include + +namespace gzb { +struct App +{ + static App& getApp() { + static App app; + return app; + } + App() {}; + + ImGuiTextBuffer log_buffer; + std::vector text_editors; +}; +} +#endif // WINDOW_HPP diff --git a/app/TextEditorWindow.cpp b/app/TextEditorWindow.cpp new file mode 100644 index 00000000..09fb3f75 --- /dev/null +++ b/app/TextEditorWindow.cpp @@ -0,0 +1,326 @@ +// mostly ad(o|a)pted from scex +#include "TextEditorWindow.hpp" +#include "../ext/ImFileDialog/ImFileDialog.h" +#include +#include + +namespace gzb +{ +TextEditorWindow::TextEditorWindow(const char* filePath) +{ + editor = new TextEditor(); + if (filePath == nullptr) + { + // not sure if the pointer is a safe ID + window_name = "unsaved###" + std::to_string((long)this); + associated_file.clear(); + } + else + { + has_associated_file = true; + associated_file = std::string(filePath); + auto pathObject = std::filesystem::path(filePath); + window_name = pathObject.filename().string() + "###" + std::to_string((long)this); + std::ifstream t(filePath); + std::string str((std::istreambuf_iterator(t)), + std::istreambuf_iterator()); + editor->SetText(str); + editor->SetLanguageDefinition(TextEditor::LanguageDefinition::Python()); + } +} + +TextEditorWindow::~TextEditorWindow() +{ + delete editor; +} + +void TextEditorWindow::OnImGui() +{ + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 0.0f, 0.0f }); + ImGui::Begin(window_name.c_str(), &showing, + ImGuiWindowFlags_MenuBar | + ImGuiWindowFlags_NoSavedSettings | + (editor->GetUndoIndex() != undo_index_in_disk ? ImGuiWindowFlags_UnsavedDocument : 0x0)); + ImGui::PopStyleVar(); + + bool isFocused = ImGui::IsWindowFocused(); + bool requestingGoToLinePopup = false; + bool requestingFindPopup = false; + + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + if (has_associated_file && ImGui::MenuItem("Reload", "Ctrl+R")) + OnReloadCommand(); + if (ImGui::MenuItem("Load from")) + { + requesting_load_from = true; + OnLoadFromCommand(); + } + if (ImGui::MenuItem("Save", "Ctrl+S")) + OnSaveCommand(); + //if (this->has_associated_file && ImGui::MenuItem("Show in file explorer")) + //Utils::ShowInFileExplorer(this->associated_file); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Edit")) + { + bool ro = editor->IsReadOnly(); + if (ImGui::MenuItem("Read only", nullptr, &ro)) + editor->SetReadOnly(ro); + //bool ai = editor->IsAutoIndentEnabled(); + //if (ImGui::MenuItem("Auto indent on enter enabled", nullptr, &ai)) + // editor->SetAutoIndentEnabled(ai); + ImGui::Separator(); + + if (ImGui::MenuItem("Undo", "ALT-Backspace", nullptr, !ro && editor->CanUndo())) + editor->Undo(); + if (ImGui::MenuItem("Redo", "Ctrl+Y", nullptr, !ro && editor->CanRedo())) + editor->Redo(); + + ImGui::Separator(); + + if (ImGui::MenuItem("Copy", "Ctrl+C", nullptr, editor->HasSelection())) + editor->Copy(); + if (ImGui::MenuItem("Cut", "Ctrl+X", nullptr, !ro && editor->HasSelection())) + editor->Cut(); + if (ImGui::MenuItem("Paste", "Ctrl+V", nullptr, !ro && ImGui::GetClipboardText() != nullptr)) + editor->Paste(); + + ImGui::Separator(); + + if (ImGui::MenuItem("Select all", nullptr, nullptr)) + editor->SelectAll(); + + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("View")) + { + //ImGui::SliderInt("Font size", &codeFontSize, FontManager::GetMinCodeFontSize(), FontManager::GetMaxCodeFontSize()); + ImGui::SliderInt("Tab size", &tab_size, 1, 8); + ImGui::SliderFloat("Line spacing", &line_spacing, 1.0f, 2.0f); + editor->SetTabSize(tab_size); + //editor->Setline_spacing(line_spacing); + static bool showSpaces = editor->IsShowingWhitespaces(); + if (ImGui::MenuItem("Show spaces", nullptr, &showSpaces)) + editor->SetShowWhitespaces(!(editor->IsShowingWhitespaces())); + //static bool showLineNumbers = editor->IsShowLineNumbersEnabled(); + //if (ImGui::MenuItem("Show line numbers", nullptr, &showLineNumbers)) + // editor->SetShowLineNumbersEnabled(!(editor->IsShowLineNumbersEnabled())); + static bool showShortTabs = editor->IsShowingShortTabGlyphs(); + if (ImGui::MenuItem("Short tabs", nullptr, &showShortTabs)) + editor->SetShowShortTabGlyphs(!(editor->IsShowingShortTabGlyphs())); + /*if (ImGui::BeginMenu("Language")) + { + for (int i = (int)TextEditor::LanguageDefinitionId::None; i <= (int)TextEditor::LanguageDefinitionId::Hlsl; i++) + { + bool isSelected = i == (int)editor->GetLanguageDefinition(); + if (ImGui::MenuItem(languageDefinitionToName[(TextEditor::LanguageDefinitionId)i], nullptr, &isSelected)) + editor->SetLanguageDefinition((TextEditor::LanguageDefinitionId)i); + } + ImGui::EndMenu(); + }*/ + /*if (ImGui::BeginMenu("Color scheme")) + { + for (int i = (int)TextEditor::PaletteId::Dark; i <= (int)TextEditor::PaletteId::RetroBlue; i++) + { + bool isSelected = i == (int)editor->GetPalette(); + if (ImGui::MenuItem(colorPaletteToName[(TextEditor::PaletteId)i], nullptr, &isSelected)) + editor->SetPalette((TextEditor::PaletteId)i); + } + ImGui::EndMenu(); + }*/ + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Find")) + { + if (ImGui::MenuItem("Go to line", "Ctrl+G")) + requestingGoToLinePopup = true; + if (ImGui::MenuItem("Find", "Ctrl+F")) + requestingFindPopup = true; + ImGui::EndMenu(); + } + + int line, column; + TextEditor::Coordinates cursorCoord = editor->GetCursorPosition(); + line = cursorCoord.mLine; + column = cursorCoord.mColumn; + + //if (codeFontTopBar != nullptr) ImGui::PushFont(codeFontTopBar); + ImGui::Text("%6d/%-6d %6d lines | %s | %s", line + 1, column + 1, editor->GetTotalLines(), + editor->IsOverwrite() ? "Ovr" : "Ins", + editor->GetLanguageDefinitionName()); + //if (codeFontTopBar != nullptr) ImGui::PopFont(); + + ImGui::EndMenuBar(); + } + + if (ifd::FileDialog::Instance().IsDone(window_name + "LoadFromDialog")) { + if (ifd::FileDialog::Instance().HasResult()) { + std::string res = ifd::FileDialog::Instance().GetResult().u8string(); + printf("TextEditor LoadFrom[%s]\n", res.c_str()); + associated_file = res; + has_associated_file = true; + OnLoadFromCommand(); + } + ifd::FileDialog::Instance().Close(); + } + + if (ifd::FileDialog::Instance().IsDone(window_name + "SaveDialog")) { + if (ifd::FileDialog::Instance().HasResult()) { + std::string res = ifd::FileDialog::Instance().GetResult().u8string(); + printf("TextEditor Save[%s]\n", res.c_str()); + associated_file = res; + has_associated_file = true; + OnSaveCommand(); + } + ifd::FileDialog::Instance().Close(); + } + + //if (codeFontEditor != nullptr) ImGui::PushFont(codeFontEditor); + isFocused |= editor->Render("TextEditor", isFocused); + //if (codeFontEditor != nullptr) ImGui::PopFont(); + + if (isFocused) + { + bool ctrlPressed = ImGui::GetIO().KeyCtrl; + if (ctrlPressed) + { + if (ImGui::IsKeyDown(ImGuiKey_S)) + OnSaveCommand(); + if (ImGui::IsKeyDown(ImGuiKey_R)) + OnReloadCommand(); + if (ImGui::IsKeyDown(ImGuiKey_G)) + requestingGoToLinePopup = true; + if (ImGui::IsKeyDown(ImGuiKey_F)) + requestingFindPopup = true; + //if (ImGui::IsKeyPressed(ImGuiKey_Equal) || ImGui::GetIO().MouseWheel > 0.0f) + // requestingFontSizeIncrease = true; + //if (ImGui::IsKeyPressed(ImGuiKey_Minus) || ImGui::GetIO().MouseWheel < 0.0f) + // requestingFontSizeDecrease = true; + } + } + + if (requestingGoToLinePopup) ImGui::OpenPopup("go_to_line_popup"); + if (ImGui::BeginPopup("go_to_line_popup")) + { + static int targetLine; + ImGui::SetKeyboardFocusHere(); + ImGui::InputInt("Line", &targetLine); + if (ImGui::IsKeyDown(ImGuiKey_Enter) || ImGui::IsKeyDown(ImGuiKey_KeypadEnter)) + { + static int targetLineFixed; + targetLineFixed = targetLine < 1 ? 0 : targetLine - 1; + editor->ClearExtraCursors(); + editor->ClearSelections(); + //editor->SetSelection({ targetLineFixed, 0 }, { targetLineFixed, editor->GetLineMaxColumn(targetLineFixed) }); + CenterViewAtLine(targetLineFixed); + ImGui::CloseCurrentPopup(); + ImGui::GetIO().ClearInputKeys(); + } + else if (ImGui::IsKeyDown(ImGuiKey_Escape)) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + + if (requestingFindPopup) + ImGui::OpenPopup("find_popup"); + if (ImGui::BeginPopup("find_popup")) + { + ImGui::Checkbox("Case sensitive", &ctrlf_case_sensitive); + if (requestingFindPopup) + ImGui::SetKeyboardFocusHere(); + ImGui::InputText("To find", ctrlf_text_to_find, FIND_POPUP_TEXT_FIELD_LENGTH, ImGuiInputTextFlags_AutoSelectAll); + int toFindTextSize = strlen(ctrlf_text_to_find); + if ((ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter)) && toFindTextSize > 0) + { + editor->ClearExtraCursors(); + editor->SelectNextOccurrenceOf(ctrlf_text_to_find, toFindTextSize, ctrlf_case_sensitive); + TextEditor::Coordinates nextOccurrenceLine = editor->GetCursorPosition(); + CenterViewAtLine(nextOccurrenceLine.mLine); + } + //if (ImGui::Button("Find all") && toFindTextSize > 0) + // editor->SelectAllOccurrencesOf(ctrlf_text_to_find, toFindTextSize, ctrlf_case_sensitive); + else if (ImGui::IsKeyDown(ImGuiKey_Escape)) + ImGui::CloseCurrentPopup(); + + ImGui::EndPopup(); + } + + //if (requestingFontSizeIncrease && codeFontSize < FontManager::GetMaxCodeFontSize()) + // codeFontSize++; + //if (requestingFontSizeDecrease && codeFontSize > FontManager::GetMinCodeFontSize()) + // codeFontSize--; + + ImGui::End(); +} + +void TextEditorWindow::SetSelection(int startLine, int startChar, int endLine, int endChar) +{ + editor->SetCursorPosition(endLine, endChar); + editor->SetSelectionStart(TextEditor::Coordinates(startLine, startChar)); + editor->SetSelectionEnd(TextEditor::Coordinates(endLine, endChar)); +} + +void TextEditorWindow::CenterViewAtLine(int line) +{ + //editor->SetViewAtLine(line, TextEditor::SetViewAtLineMode::Centered); +} + +const char* TextEditorWindow::GetAssociatedFile() +{ + if (!has_associated_file) + return nullptr; + return associated_file.c_str(); +} + +// Commands + +void TextEditorWindow::OnReloadCommand() +{ + std::ifstream t(associated_file); + std::string str((std::istreambuf_iterator(t)), + std::istreambuf_iterator()); + editor->SetText(str); + undo_index_in_disk = 0; +} + +void TextEditorWindow::OnLoadFromCommand() +{ + if (requesting_load_from) + { + ifd::FileDialog::Instance().Open(window_name + "LoadFromDialog", "Load text from file", "*.py;*txt {.py,.txt}"); + requesting_load_from = false; + return; + } + std::ifstream t(associated_file); + std::string str((std::istreambuf_iterator(t)), + std::istreambuf_iterator()); + editor->SetText(str); + window_name = std::filesystem::path(associated_file).filename().string() + "###" + std::to_string((long)this); + //auto pathObject = std::filesystem::path(associated_file); + //auto lang = extensionToLanguageDefinition.find(pathObject.extension().string()); + //if (lang != extensionToLanguageDefinition.end()) + // editor->SetLanguageDefinition(extensionToLanguageDefinition[pathObject.extension().string()]); + undo_index_in_disk = -1; // assume they are loading text from some other file +} + +void TextEditorWindow::OnSaveCommand() +{ + if (!has_associated_file && associated_file.length() > 0) + { + ifd::FileDialog::Instance().Save(window_name+"SaveDialog", "Save text file", "*.py;*txt {.py,.txt}"); + return; + } + std::string textToSave = editor->GetText(); + window_name = std::filesystem::path(associated_file).filename().string() + "##" + std::to_string((long)this); + std::ofstream outFile(associated_file, std::ios::binary); + outFile << textToSave; + outFile.close(); + undo_index_in_disk = editor->GetUndoIndex(); +} + +} // namespace diff --git a/app/TextEditorWindow.hpp b/app/TextEditorWindow.hpp new file mode 100644 index 00000000..3a606a0a --- /dev/null +++ b/app/TextEditorWindow.hpp @@ -0,0 +1,41 @@ +#ifndef GZBTEXTEDITORWINDOW_HPP +#define GZBTEXTEDITORWINDOW_HPP + +#include "Window.hpp" +#include "../ext/ImGuiColorTextEdit/TextEditor.h" + +#define FIND_POPUP_TEXT_FIELD_LENGTH 128 + +namespace gzb +{ + +struct TextEditorWindow : public Window +{ + TextEditorWindow(const char* filePath = nullptr); + ~TextEditorWindow() override; + + void OnImGui() override; + + void SetSelection(int startLine, int startChar, int endLine, int endChar); + void CenterViewAtLine(int line); + const char* GetAssociatedFile(); + + void OnReloadCommand(); + void OnLoadFromCommand(); + void OnSaveCommand(); + + TextEditor *editor; + bool has_associated_file = false; + std::string associated_file; + int tab_size = 4; + float line_spacing = 1.0f; + int undo_index_in_disk = 0; + //int codeFontSize; + + char ctrlf_text_to_find[FIND_POPUP_TEXT_FIELD_LENGTH] = ""; + bool ctrlf_case_sensitive = false; + bool requesting_load_from = false; +}; + +} +#endif // GZBTEXTEDITORWINDOW_HPP diff --git a/app/Window.hpp b/app/Window.hpp new file mode 100644 index 00000000..40b6892f --- /dev/null +++ b/app/Window.hpp @@ -0,0 +1,17 @@ +#ifndef GZBWINDOW_HPP +#define GZBWINDOW_HPP + +#include + +namespace gzb { +struct Window +{ + Window() {}; + virtual ~Window() {}; + virtual void OnImGui() {}; + + bool showing = true; + std::string window_name = "noname"; +}; +} +#endif // GZBWINDOW_HPP From 7d6c8798bdeb67cab56abfbd6ef02ea08cfe712a Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Sun, 7 Apr 2024 19:45:01 +0200 Subject: [PATCH 04/39] update to v1.90.1 --- ext/ImGuiColorTextEdit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/ImGuiColorTextEdit b/ext/ImGuiColorTextEdit index 06ce9419..f17e98f1 160000 --- a/ext/ImGuiColorTextEdit +++ b/ext/ImGuiColorTextEdit @@ -1 +1 @@ -Subproject commit 06ce941998358cf8cb524421acb776337ef62125 +Subproject commit f17e98f1aeebb1522432e62aece9c9883b756999 From a9249103699250ec2aa71abe66b59cc025154550 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Sun, 7 Apr 2024 19:45:50 +0200 Subject: [PATCH 05/39] switch to sphaero's remote --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 6f30a9eb..0453b196 100644 --- a/.gitmodules +++ b/.gitmodules @@ -24,7 +24,7 @@ url = https://github.com/libsdl-org/SDL.git [submodule "ext/ImGuiColorTextEdit"] path = ext/ImGuiColorTextEdit - url = https://github.com/pthom/ImGuiColorTextEdit.git + url = https://github.com/sphaero/ImGuiColorTextEdit.git [submodule "imgui"] path = ext/imgui url = https://github.com/ocornut/imgui.git From 54f58321bc8e42c0b6aee0207830c8155366869b Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Sun, 7 Apr 2024 21:35:19 +0200 Subject: [PATCH 06/39] test windows Error C2589 --- ext/ImFileDialog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/ImFileDialog b/ext/ImFileDialog index 269c9352..c89aa80e 160000 --- a/ext/ImFileDialog +++ b/ext/ImFileDialog @@ -1 +1 @@ -Subproject commit 269c9352d5eef3dd72d63ccf0fbcb5fbf4e09c36 +Subproject commit c89aa80edba9bd87629850383fd1a13b0ebb4574 From 6e9fb203fbac711637a9bf60b940437dae5bcd6a Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Sun, 7 Apr 2024 22:16:28 +0200 Subject: [PATCH 07/39] remove old code, add new mainmenu entries --- stage.cpp | 194 +++++------------------------------------------------- 1 file changed, 15 insertions(+), 179 deletions(-) diff --git a/stage.cpp b/stage.cpp index 15cdf9ad..ad262ccf 100644 --- a/stage.cpp +++ b/stage.cpp @@ -186,18 +186,7 @@ void moveCwdIfNeeded() { } } -// Ability to load multiple text files -struct textfile { - TextEditor *editor; - zfile_t *file; - char *basename; - bool open; - bool changed; -}; std::vector text_editors; -bool txteditor_open = false; -textfile* current_editor = nullptr; -textfile* hardswap_editor = nullptr; #ifdef HAVE_IMGUI_DEMO // ImGui Demo window for dev purposes @@ -480,9 +469,7 @@ void ShowLogWindow(ImGuiTextBuffer& buffer) { } void OpenTextEditor(const char *filepath) -{ - //static auto lang = TextEditor::LanguageDefinition::Python(); - +{ gzb::TextEditorWindow *txtwin = NULL; for ( auto w : text_editors ) { @@ -502,174 +489,9 @@ void OpenTextEditor(const char *filepath) ImGuiWindow *win = ImGui::FindWindowByName(txtwin->window_name.c_str()); ImGui::FocusWindow(win); } - txteditor_open = true; } -/*void ShowTextEditor() -{ - if (ImGui::Begin("Texteditor", &txteditor_open, ImGuiWindowFlags_MenuBar)) { - - ImGui::BeginMenuBar(); - - if (ImGui::BeginMenu("File")) { - - bool file_selected = false; - - if (ImGui::MenuItem(ICON_FA_FOLDER_OPEN " Load")) - file_selected = true; - - if (file_selected) - ImGui::OpenPopup("Actor Open File"); - - // open the load dialog - if (actor_file_dialog.showFileDialog("Actor Open File", - imgui_addons::ImGuiFileBrowser::DialogMode::OPEN, - ImVec2(700, 310), - "*.*")) // TODO: perhaps add type hint for extensions? - { - zfile_t* f = zfile_new(nullptr, actor_file_dialog.selected_path.c_str()); - if (actor_file_dialog.selected_path.length() > 0 && f) - { - OpenTextEditor(f); - } - else - zsys_error("no valid file to load: %s", actor_file_dialog.selected_path.c_str()); - } - if (ImGui::MenuItem(ICON_FA_SAVE " Save")) { - if (current_editor != nullptr) { - // the zfile class is missing truncating files on write if the file is smaller than the old file - // see https://github.com/zeromq/czmq/issues/2203 - ssize_t oldSize = zfile_size(zfile_filename(current_editor->file, NULL)); - int rc = zfile_output(current_editor->file); - if (rc == 0) { - std::string text = current_editor->editor->GetText(); - zchunk_t* data = zchunk_frommem((void *)text.c_str(), strlen(text.c_str()), nullptr, nullptr); - int rc = zfile_write(current_editor->file, data, 0); - if (oldSize > text.length() ) - { -#ifdef __WINDOWS__ // truncate the file - if ( _chsize_s(fileno(zfile_handle(current_editor->file)), (__int64)text.length()) != 0 ) - { - zsys_error("Some error trying to truncate the file"); - } -#else - ftruncate(fileno(zfile_handle(current_editor->file)), text.length()); -#endif - } - zfile_close(current_editor->file); - if (rc != 0) { - zsys_info("ERROR WRITING TO FILE: %i", rc); - } - else - current_editor->changed = false; - } - } - } - // TODO: support save as? - //if (ImGui::MenuItem(ICON_FA_SAVE " Save As")) { - // - //} - ImGui::Separator(); - if (ImGui::MenuItem(ICON_FA_WINDOW_CLOSE " Exit")) { - //TODO: support checking if changes were made - txteditor_open = false; - } - - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Edit")) - { - bool ro = current_editor->editor->IsReadOnly(); - if (ImGui::MenuItem("Read-only mode", nullptr, &ro)) - current_editor->editor->SetReadOnly(ro); - ImGui::Separator(); - - if (ImGui::MenuItem("Undo", "ALT-Backspace", nullptr, !ro && current_editor->editor->CanUndo())) - current_editor->editor->Undo(); - if (ImGui::MenuItem("Redo", "Ctrl-Y", nullptr, !ro && current_editor->editor->CanRedo())) - current_editor->editor->Redo(); - - ImGui::Separator(); - - if (ImGui::MenuItem("Copy", "Ctrl-C", nullptr, current_editor->editor->HasSelection())) - current_editor->editor->Copy(); - if (ImGui::MenuItem("Cut", "Ctrl-X", nullptr, !ro && current_editor->editor->HasSelection())) - current_editor->editor->Cut(); - if (ImGui::MenuItem("Delete", "Del", nullptr, !ro && current_editor->editor->HasSelection())) - current_editor->editor->Delete(); - if (ImGui::MenuItem("Paste", "Ctrl-V", nullptr, !ro && ImGui::GetClipboardText() != nullptr)) - current_editor->editor->Paste(); - - ImGui::Separator(); - - if (ImGui::MenuItem("Select all", nullptr, nullptr)) - current_editor->editor->SetSelection(TextEditor::Coordinates(), TextEditor::Coordinates(current_editor->editor->GetTotalLines(), 0)); - - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("View")) - { - if (ImGui::MenuItem("Dark palette")) - current_editor->editor->SetPalette(TextEditor::GetDarkPalette()); - if (ImGui::MenuItem("Light palette")) - current_editor->editor->SetPalette(TextEditor::GetLightPalette()); - if (ImGui::MenuItem("Retro blue palette")) - current_editor->editor->SetPalette(TextEditor::GetRetroBluePalette()); - if (ImGui::MenuItem("Mariana palette")) - current_editor->editor->SetPalette(TextEditor::GetMarianaPalette()); - ImGui::EndMenu(); - } - ImGui::EndMenuBar(); - - ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_AutoSelectNewTabs; - if (ImGui::BeginTabBar("TextEditorTabBar", tab_bar_flags)) - { - for (auto it = open_text_files.begin(); it != open_text_files.end(); ++it) - { - ImGuiTabItemFlags flags = ImGuiTabItemFlags_None; - if (hardswap_editor && &(*it) == hardswap_editor) { - flags |= ImGuiTabItemFlags_SetSelected; - hardswap_editor = nullptr; - } - // TODO: when loading the textfile this will always - // result in text being changed. - if ( !it->changed && it->editor->IsTextChanged() ) it->changed = true; - - char filename[FILENAME_MAX]; - if ( it->changed ) - snprintf(filename, FILENAME_MAX, "%s*###%s", it->basename, it->basename); - else - snprintf(filename, FILENAME_MAX, "%s###%s", it->basename, it->basename); - - if (ImGui::BeginTabItem(filename, &it->open, flags)) - { - current_editor = &(*it); // set current textfile from active tab - current_editor->editor->Render("TextEditor");//, false, ImVec2(), false); - ImGui::EndTabItem(); - } - else if (!it->open) - { - // file is closed by gui do we need to save it? - // this doesn't work, mTextChanged is set to false when rendered - //if ( it->editor->IsTextChanged() ) - //{ - // zsys_debug("need to save file %s", it->basename ); - //} - zsys_debug("remove file %s", it->basename); - open_text_files.erase(it); - current_editor = nullptr; - break; - } - } - - ImGui::EndTabBar(); - } - } - ImGui::End(); -} -*/ int RenderMenuBar( bool * showLog ) { static char* configFile = new char[64] { 0x0 }; static int keyFocus = 0; @@ -708,6 +530,20 @@ int RenderMenuBar( bool * showLog ) { } if ( ImGui::BeginMenu("Tools") ) { + if ( ImGui::BeginMenu(ICON_FA_EDIT " Text Editor") ) + { + if ( ImGui::MenuItem(ICON_FA_FOLDER_OPEN " New editor") ) + { + text_editors.push_back(new gzb::TextEditorWindow()); + } + for (auto w : text_editors ) + { + std::string title = w->window_name + " " + ICON_FA_EYE; + if ( ImGui::MenuItem(w->window_name.c_str(), NULL, w->showing, true ) ) + w->showing = !w->showing; + } + ImGui::EndMenu(); + } if ( ImGui::MenuItem(ICON_FA_TERMINAL " Toggle Console") ) { *showLog = !(*showLog); } From cefa7ea46d6be0853d63c58599d1c7e7c8c99d7d Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Tue, 9 Apr 2024 10:20:32 +0200 Subject: [PATCH 08/39] fix initial window size too small --- app/TextEditorWindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/app/TextEditorWindow.cpp b/app/TextEditorWindow.cpp index 09fb3f75..00513c7f 100644 --- a/app/TextEditorWindow.cpp +++ b/app/TextEditorWindow.cpp @@ -36,6 +36,7 @@ TextEditorWindow::~TextEditorWindow() void TextEditorWindow::OnImGui() { + ImGui::SetNextWindowSize(ImVec2(0.f, 200), ImGuiCond_FirstUseEver); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 0.0f, 0.0f }); ImGui::Begin(window_name.c_str(), &showing, ImGuiWindowFlags_MenuBar | From 203c096f790d9c477e21403608b1a8d47c910170 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Tue, 9 Apr 2024 14:21:41 +0200 Subject: [PATCH 09/39] move code files around and introduce cmake source_group --- CMakeLists.txt | 12 +++++++----- actors.h | 21 --------------------- {Actors => actors}/.DS_Store | Bin {Actors => actors}/Device.cpp | 0 {Actors => actors}/Device.hpp | 0 {Actors => actors}/DeviceList.cpp | 0 {Actors => actors}/DeviceList.hpp | 0 {Actors => actors}/DmxActor.cpp | 0 {Actors => actors}/DmxActor.h | 0 {Actors => actors}/HTTPLaunchpod.c | 0 {Actors => actors}/HTTPLaunchpod.h | 0 {Actors => actors}/Midi2OSCActor.cpp | 0 {Actors => actors}/Midi2OSCActor.h | 0 {Actors => actors}/ModPlayerActor.cpp | 0 {Actors => actors}/ModPlayerActor.h | 0 {Actors => actors}/NatNet2OSCActor.cpp | 0 {Actors => actors}/NatNet2OSCActor.h | 0 {Actors => actors}/NatNetActor.cpp | 0 {Actors => actors}/NatNetActor.h | 0 {Actors => actors}/NatNetDataTypes.h | 0 {Actors => actors}/OSCInputActor.cpp | 0 {Actors => actors}/OSCInputActor.h | 0 {Actors => actors}/OSCOutputActor.cpp | 0 {Actors => actors}/OSCOutputActor.h | 0 {Actors => actors}/OpenVRActor.cpp | 0 {Actors => actors}/OpenVRActor.h | 0 {Actors => actors}/OscMultiOut.cpp | 0 {Actors => actors}/OscMultiOut.h | 0 {Actors => actors}/ProcessActor.cpp | 0 {Actors => actors}/ProcessActor.h | 0 {Actors => actors}/RecordActor.cpp | 0 {Actors => actors}/RecordActor.h | 0 {Actors => actors}/Sliders.cpp | 0 {Actors => actors}/Sliders.h | 0 {Actors => actors}/Utilities.cpp | 0 {Actors => actors}/Utilities.hpp | 0 actors/actors.h | 21 +++++++++++++++++++++ {Actors => actors}/hxcmod.c | 0 {Actors => actors}/hxcmod.h | 0 {Actors => actors}/pythonactor.c | 0 {Actors => actors}/pythonactor.h | 0 {Actors => actors}/pyzmsg.h | 0 stage.cpp | 2 +- 43 files changed, 29 insertions(+), 27 deletions(-) delete mode 100644 actors.h rename {Actors => actors}/.DS_Store (100%) rename {Actors => actors}/Device.cpp (100%) rename {Actors => actors}/Device.hpp (100%) rename {Actors => actors}/DeviceList.cpp (100%) rename {Actors => actors}/DeviceList.hpp (100%) rename {Actors => actors}/DmxActor.cpp (100%) rename {Actors => actors}/DmxActor.h (100%) rename {Actors => actors}/HTTPLaunchpod.c (100%) rename {Actors => actors}/HTTPLaunchpod.h (100%) rename {Actors => actors}/Midi2OSCActor.cpp (100%) rename {Actors => actors}/Midi2OSCActor.h (100%) rename {Actors => actors}/ModPlayerActor.cpp (100%) rename {Actors => actors}/ModPlayerActor.h (100%) rename {Actors => actors}/NatNet2OSCActor.cpp (100%) rename {Actors => actors}/NatNet2OSCActor.h (100%) rename {Actors => actors}/NatNetActor.cpp (100%) rename {Actors => actors}/NatNetActor.h (100%) rename {Actors => actors}/NatNetDataTypes.h (100%) rename {Actors => actors}/OSCInputActor.cpp (100%) rename {Actors => actors}/OSCInputActor.h (100%) rename {Actors => actors}/OSCOutputActor.cpp (100%) rename {Actors => actors}/OSCOutputActor.h (100%) rename {Actors => actors}/OpenVRActor.cpp (100%) rename {Actors => actors}/OpenVRActor.h (100%) rename {Actors => actors}/OscMultiOut.cpp (100%) rename {Actors => actors}/OscMultiOut.h (100%) rename {Actors => actors}/ProcessActor.cpp (100%) rename {Actors => actors}/ProcessActor.h (100%) rename {Actors => actors}/RecordActor.cpp (100%) rename {Actors => actors}/RecordActor.h (100%) rename {Actors => actors}/Sliders.cpp (100%) rename {Actors => actors}/Sliders.h (100%) rename {Actors => actors}/Utilities.cpp (100%) rename {Actors => actors}/Utilities.hpp (100%) create mode 100644 actors/actors.h rename {Actors => actors}/hxcmod.c (100%) rename {Actors => actors}/hxcmod.h (100%) rename {Actors => actors}/pythonactor.c (100%) rename {Actors => actors}/pythonactor.h (100%) rename {Actors => actors}/pyzmsg.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9924212d..7f01d6b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,7 +164,7 @@ if(WITH_DMX) endif(WITH_DMX) ### PROJECT SOURCES -aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/Actors ACTOR_SOURCES) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/actors ACTOR_SOURCES) # get git version include(GitVersion) @@ -185,6 +185,9 @@ add_custom_command( list(APPEND GZB_SOURCES main.cpp + stage.cpp + ImNodes.cpp + ImNodesEz.cpp app/App.hpp app/Window.hpp app/TextEditorWindow.hpp @@ -192,19 +195,18 @@ list(APPEND GZB_SOURCES ext/imgui/backends/imgui_impl_opengl3.cpp ext/imgui/backends/imgui_impl_sdl2.cpp ext/imgui/imconfig.h + ext/imgui/imgui.h ext/imgui/imgui.cpp ext/imgui/imgui_draw.cpp ext/imgui/imgui_widgets.cpp ext/imgui/imgui_tables.cpp ext/imgui/imgui_demo.cpp - ImNodes.cpp - ImNodesEz.cpp ext/ImFileDialog/ImFileDialog.h ext/ImFileDialog/ImFileDialog.cpp ext/ImGui-Addons/FileBrowser/ImGuiFileBrowser.cpp ext/ImGuiColorTextEdit/LanguageDefinitions.cpp ext/ImGuiColorTextEdit/TextEditor.cpp - stage.cpp + ${ACTOR_SOURCES} ) list(APPEND GZB_INCLUDEDIRS @@ -237,10 +239,10 @@ if (UNIX) ) endif() +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${GZB_SOURCES}) add_executable(gazebosc config.h ${GZB_SOURCES} - ${ACTOR_SOURCES} ) target_include_directories(gazebosc PUBLIC diff --git a/actors.h b/actors.h deleted file mode 100644 index 16c16187..00000000 --- a/actors.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef ACTORS_H -#define ACTORS_H - -#include "Actors/HTTPLaunchpod.h" -#include "Actors/OSCOutputActor.h" -#include "Actors/OscMultiOut.h" -#include "Actors/Midi2OSCActor.h" -#include "Actors/NatNetActor.h" -#include "Actors/NatNet2OSCActor.h" -#include "Actors/OpenVRActor.h" -#include "Actors/OSCInputActor.h" -#include "Actors/RecordActor.h" -#include "Actors/ModPlayerActor.h" -#include "Actors/ProcessActor.h" -#include "Actors/DmxActor.h" -#include "Actors/Sliders.h" -#ifdef PYTHON3_FOUND -#include "Actors/pythonactor.h" -#endif - -#endif diff --git a/Actors/.DS_Store b/actors/.DS_Store similarity index 100% rename from Actors/.DS_Store rename to actors/.DS_Store diff --git a/Actors/Device.cpp b/actors/Device.cpp similarity index 100% rename from Actors/Device.cpp rename to actors/Device.cpp diff --git a/Actors/Device.hpp b/actors/Device.hpp similarity index 100% rename from Actors/Device.hpp rename to actors/Device.hpp diff --git a/Actors/DeviceList.cpp b/actors/DeviceList.cpp similarity index 100% rename from Actors/DeviceList.cpp rename to actors/DeviceList.cpp diff --git a/Actors/DeviceList.hpp b/actors/DeviceList.hpp similarity index 100% rename from Actors/DeviceList.hpp rename to actors/DeviceList.hpp diff --git a/Actors/DmxActor.cpp b/actors/DmxActor.cpp similarity index 100% rename from Actors/DmxActor.cpp rename to actors/DmxActor.cpp diff --git a/Actors/DmxActor.h b/actors/DmxActor.h similarity index 100% rename from Actors/DmxActor.h rename to actors/DmxActor.h diff --git a/Actors/HTTPLaunchpod.c b/actors/HTTPLaunchpod.c similarity index 100% rename from Actors/HTTPLaunchpod.c rename to actors/HTTPLaunchpod.c diff --git a/Actors/HTTPLaunchpod.h b/actors/HTTPLaunchpod.h similarity index 100% rename from Actors/HTTPLaunchpod.h rename to actors/HTTPLaunchpod.h diff --git a/Actors/Midi2OSCActor.cpp b/actors/Midi2OSCActor.cpp similarity index 100% rename from Actors/Midi2OSCActor.cpp rename to actors/Midi2OSCActor.cpp diff --git a/Actors/Midi2OSCActor.h b/actors/Midi2OSCActor.h similarity index 100% rename from Actors/Midi2OSCActor.h rename to actors/Midi2OSCActor.h diff --git a/Actors/ModPlayerActor.cpp b/actors/ModPlayerActor.cpp similarity index 100% rename from Actors/ModPlayerActor.cpp rename to actors/ModPlayerActor.cpp diff --git a/Actors/ModPlayerActor.h b/actors/ModPlayerActor.h similarity index 100% rename from Actors/ModPlayerActor.h rename to actors/ModPlayerActor.h diff --git a/Actors/NatNet2OSCActor.cpp b/actors/NatNet2OSCActor.cpp similarity index 100% rename from Actors/NatNet2OSCActor.cpp rename to actors/NatNet2OSCActor.cpp diff --git a/Actors/NatNet2OSCActor.h b/actors/NatNet2OSCActor.h similarity index 100% rename from Actors/NatNet2OSCActor.h rename to actors/NatNet2OSCActor.h diff --git a/Actors/NatNetActor.cpp b/actors/NatNetActor.cpp similarity index 100% rename from Actors/NatNetActor.cpp rename to actors/NatNetActor.cpp diff --git a/Actors/NatNetActor.h b/actors/NatNetActor.h similarity index 100% rename from Actors/NatNetActor.h rename to actors/NatNetActor.h diff --git a/Actors/NatNetDataTypes.h b/actors/NatNetDataTypes.h similarity index 100% rename from Actors/NatNetDataTypes.h rename to actors/NatNetDataTypes.h diff --git a/Actors/OSCInputActor.cpp b/actors/OSCInputActor.cpp similarity index 100% rename from Actors/OSCInputActor.cpp rename to actors/OSCInputActor.cpp diff --git a/Actors/OSCInputActor.h b/actors/OSCInputActor.h similarity index 100% rename from Actors/OSCInputActor.h rename to actors/OSCInputActor.h diff --git a/Actors/OSCOutputActor.cpp b/actors/OSCOutputActor.cpp similarity index 100% rename from Actors/OSCOutputActor.cpp rename to actors/OSCOutputActor.cpp diff --git a/Actors/OSCOutputActor.h b/actors/OSCOutputActor.h similarity index 100% rename from Actors/OSCOutputActor.h rename to actors/OSCOutputActor.h diff --git a/Actors/OpenVRActor.cpp b/actors/OpenVRActor.cpp similarity index 100% rename from Actors/OpenVRActor.cpp rename to actors/OpenVRActor.cpp diff --git a/Actors/OpenVRActor.h b/actors/OpenVRActor.h similarity index 100% rename from Actors/OpenVRActor.h rename to actors/OpenVRActor.h diff --git a/Actors/OscMultiOut.cpp b/actors/OscMultiOut.cpp similarity index 100% rename from Actors/OscMultiOut.cpp rename to actors/OscMultiOut.cpp diff --git a/Actors/OscMultiOut.h b/actors/OscMultiOut.h similarity index 100% rename from Actors/OscMultiOut.h rename to actors/OscMultiOut.h diff --git a/Actors/ProcessActor.cpp b/actors/ProcessActor.cpp similarity index 100% rename from Actors/ProcessActor.cpp rename to actors/ProcessActor.cpp diff --git a/Actors/ProcessActor.h b/actors/ProcessActor.h similarity index 100% rename from Actors/ProcessActor.h rename to actors/ProcessActor.h diff --git a/Actors/RecordActor.cpp b/actors/RecordActor.cpp similarity index 100% rename from Actors/RecordActor.cpp rename to actors/RecordActor.cpp diff --git a/Actors/RecordActor.h b/actors/RecordActor.h similarity index 100% rename from Actors/RecordActor.h rename to actors/RecordActor.h diff --git a/Actors/Sliders.cpp b/actors/Sliders.cpp similarity index 100% rename from Actors/Sliders.cpp rename to actors/Sliders.cpp diff --git a/Actors/Sliders.h b/actors/Sliders.h similarity index 100% rename from Actors/Sliders.h rename to actors/Sliders.h diff --git a/Actors/Utilities.cpp b/actors/Utilities.cpp similarity index 100% rename from Actors/Utilities.cpp rename to actors/Utilities.cpp diff --git a/Actors/Utilities.hpp b/actors/Utilities.hpp similarity index 100% rename from Actors/Utilities.hpp rename to actors/Utilities.hpp diff --git a/actors/actors.h b/actors/actors.h new file mode 100644 index 00000000..eb7efd3c --- /dev/null +++ b/actors/actors.h @@ -0,0 +1,21 @@ +#ifndef ACTORS_H +#define ACTORS_H + +#include "HTTPLaunchpod.h" +#include "OSCOutputActor.h" +#include "OscMultiOut.h" +#include "Midi2OSCActor.h" +#include "NatNetActor.h" +#include "NatNet2OSCActor.h" +#include "OpenVRActor.h" +#include "OSCInputActor.h" +#include "RecordActor.h" +#include "ModPlayerActor.h" +#include "ProcessActor.h" +#include "DmxActor.h" +#include "Sliders.h" +#ifdef PYTHON3_FOUND +#include "pythonactor.h" +#endif + +#endif diff --git a/Actors/hxcmod.c b/actors/hxcmod.c similarity index 100% rename from Actors/hxcmod.c rename to actors/hxcmod.c diff --git a/Actors/hxcmod.h b/actors/hxcmod.h similarity index 100% rename from Actors/hxcmod.h rename to actors/hxcmod.h diff --git a/Actors/pythonactor.c b/actors/pythonactor.c similarity index 100% rename from Actors/pythonactor.c rename to actors/pythonactor.c diff --git a/Actors/pythonactor.h b/actors/pythonactor.h similarity index 100% rename from Actors/pythonactor.h rename to actors/pythonactor.h diff --git a/Actors/pyzmsg.h b/actors/pyzmsg.h similarity index 100% rename from Actors/pyzmsg.h rename to actors/pyzmsg.h diff --git a/stage.cpp b/stage.cpp index ad262ccf..d84ddca7 100644 --- a/stage.cpp +++ b/stage.cpp @@ -33,7 +33,7 @@ namespace fs = std::filesystem; #include "ImNodesEz.h" #include "libsphactor.h" #include "ActorContainer.h" -#include "actors.h" +#include "actors/actors.h" #include "app/TextEditorWindow.hpp" #include "ext/ImGui-Addons/FileBrowser/ImGuiFileBrowser.h" #include "ext/ImGuiColorTextEdit/TextEditor.h" From 50305c597165fe7873ade1a459c5040132319bf2 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Tue, 9 Apr 2024 14:28:32 +0200 Subject: [PATCH 10/39] change ImFiledialog remote --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 0453b196..8fb561ff 100644 --- a/.gitmodules +++ b/.gitmodules @@ -30,4 +30,4 @@ url = https://github.com/ocornut/imgui.git [submodule "ext/ImFileDialog"] path = ext/ImFileDialog - url = https://github.com/dfranx/ImFileDialog + url = https://github.com/sphaero/ImFileDialog From 21586676581d9d21366959e272fa49f12f567212 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Tue, 9 Apr 2024 14:33:26 +0200 Subject: [PATCH 11/39] delete apple rubbish --- actors/.DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 actors/.DS_Store diff --git a/actors/.DS_Store b/actors/.DS_Store deleted file mode 100644 index 3e3377e3ae203ec4faba5e148b56578a92526de4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKyH3ME5S)b+kw7RNlvh$A@duYE6i5_E`~ZqUG?pAe(z`)?Hq1VF7=!32U{~55 z-`>uhJ%#rUfGoB*r@#!rl&**;4P(=J^^xsFMp0~y1{-v^#|fHI_16jI?&E^2JIu*? z=dZCoZ@XsIb}QEK1?7-<{A^~69pDmMw0OYnkKD&kTw5SRDv%1K0;xbM@DCMW&sLl6 zIc7`+Qh`+9O#%Hs6uM#!>>cgb!Jx-?ou4SG@!9SY#1i8g*gJBDCQcvPG`PE zT@CCVoer^`xlZgXaYGT?o%v$rkm{H*6-Wh!3herDrv3kn{>}Y=NXk(vkP7@M1!S_k zS}yoW(OX9!r@gk&uj#MGS}SK590EAdR($lKuIMxCYGChZbmomt%!7dHl9memh5|1w C$tr;W From 7e18fff73c4cafbc7806e96da596dff2a8107783 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Tue, 9 Apr 2024 14:53:30 +0200 Subject: [PATCH 12/39] enable docking of imgui windows --- app/TextEditorWindow.cpp | 1 - main.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/TextEditorWindow.cpp b/app/TextEditorWindow.cpp index 00513c7f..bb0573e6 100644 --- a/app/TextEditorWindow.cpp +++ b/app/TextEditorWindow.cpp @@ -40,7 +40,6 @@ void TextEditorWindow::OnImGui() ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 0.0f, 0.0f }); ImGui::Begin(window_name.c_str(), &showing, ImGuiWindowFlags_MenuBar | - ImGuiWindowFlags_NoSavedSettings | (editor->GetUndoIndex() != undo_index_in_disk ? ImGuiWindowFlags_UnsavedDocument : 0x0)); ImGui::PopStyleVar(); diff --git a/main.cpp b/main.cpp index 00e4e53c..d8133298 100644 --- a/main.cpp +++ b/main.cpp @@ -504,7 +504,7 @@ ImGuiIO& ImGUIInit(SDL_Window* window, SDL_GLContext* gl_context, const char* gl IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_DockingEnable; // Setup Dear ImGui style ImGui::StyleColorsDark(); From 94fc47e40a60e4f7690d68c9bd89e879fafe09a2 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Tue, 9 Apr 2024 15:01:57 +0200 Subject: [PATCH 13/39] add ImFiledialog required texture func, fix opening dialog --- app/TextEditorWindow.cpp | 2 +- main.cpp | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/TextEditorWindow.cpp b/app/TextEditorWindow.cpp index bb0573e6..abcc8a71 100644 --- a/app/TextEditorWindow.cpp +++ b/app/TextEditorWindow.cpp @@ -310,7 +310,7 @@ void TextEditorWindow::OnLoadFromCommand() void TextEditorWindow::OnSaveCommand() { - if (!has_associated_file && associated_file.length() > 0) + if (!has_associated_file || associated_file == "") { ifd::FileDialog::Instance().Save(window_name+"SaveDialog", "Save text file", "*.py;*txt {.py,.txt}"); return; diff --git a/main.cpp b/main.cpp index d8133298..b79d8d63 100644 --- a/main.cpp +++ b/main.cpp @@ -31,6 +31,7 @@ #include "imgui_impl_opengl3.h" #include "imgui_internal.h" #include "fontawesome5.h" +#include "ext/ImFileDialog/ImFileDialog.h" #include #include #define SDL_MAIN_HANDLED @@ -544,6 +545,28 @@ ImGuiIO& ImGUIInit(SDL_Window* window, SDL_GLContext* gl_context, const char* gl //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphR> //IM_ASSERT(font != NULL); + // ImFileDialog requires you to set the CreateTexture and DeleteTexture + ifd::FileDialog::Instance().CreateTexture = [](uint8_t* data, int w, int h, char fmt) -> void* { + GLuint tex; + + glGenTextures(1, &tex); + glBindTexture(GL_TEXTURE_2D, tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, (fmt == 0) ? GL_BGRA : GL_RGBA, GL_UNSIGNED_BYTE, data); + glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); //Gl2 + //glGenerateMipmap(GL_TEXTURE_2D); //GL3 + glBindTexture(GL_TEXTURE_2D, 0); + + return (void*)tex; + }; + ifd::FileDialog::Instance().DeleteTexture = [](void* tex) { + GLuint texID = (GLuint)((uintptr_t)tex); + glDeleteTextures(1, &texID); + }; + return io; } From f5d3d7e8b1d8da6079b2f69d0655ef76db4fae06 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Tue, 9 Apr 2024 15:04:13 +0200 Subject: [PATCH 14/39] always set python language --- app/TextEditorWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/TextEditorWindow.cpp b/app/TextEditorWindow.cpp index abcc8a71..a391b0b0 100644 --- a/app/TextEditorWindow.cpp +++ b/app/TextEditorWindow.cpp @@ -25,8 +25,8 @@ TextEditorWindow::TextEditorWindow(const char* filePath) std::string str((std::istreambuf_iterator(t)), std::istreambuf_iterator()); editor->SetText(str); - editor->SetLanguageDefinition(TextEditor::LanguageDefinition::Python()); } + editor->SetLanguageDefinition(TextEditor::LanguageDefinition::Python()); } TextEditorWindow::~TextEditorWindow() From 30cdebcbbe9d027b0f573367c6d9126d1946be76 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Tue, 9 Apr 2024 17:43:29 +0200 Subject: [PATCH 15/39] warn before closing unsaved window --- app/TextEditorWindow.cpp | 26 ++++++++++++++++++++++++++ app/TextEditorWindow.hpp | 2 ++ stage.cpp | 11 +++++++++-- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/app/TextEditorWindow.cpp b/app/TextEditorWindow.cpp index a391b0b0..17a52b60 100644 --- a/app/TextEditorWindow.cpp +++ b/app/TextEditorWindow.cpp @@ -60,6 +60,9 @@ void TextEditorWindow::OnImGui() } if (ImGui::MenuItem("Save", "Ctrl+S")) OnSaveCommand(); + if (ImGui::MenuItem("Close", "Ctrl+W")) + requesting_close = true; + //if (this->has_associated_file && ImGui::MenuItem("Show in file explorer")) //Utils::ShowInFileExplorer(this->associated_file); ImGui::EndMenu(); @@ -249,6 +252,29 @@ void TextEditorWindow::OnImGui() ImGui::EndPopup(); } + if (requesting_close) + { + if (editor->GetUndoIndex() ) + ImGui::OpenPopup("requesting_close_popup"); + else + requesting_destroy = true; + if (ImGui::BeginPopup("requesting_close_popup")) + { + ImGui::Text("Usaved changes will be lost. Are you sure?"); + if ( ImGui::Button("Yes") ) + { + requesting_destroy = true; + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if ( ImGui::Button("No")) + { + requesting_close = false; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } +} //if (requestingFontSizeIncrease && codeFontSize < FontManager::GetMaxCodeFontSize()) // codeFontSize++; diff --git a/app/TextEditorWindow.hpp b/app/TextEditorWindow.hpp index 3a606a0a..aee8b8d0 100644 --- a/app/TextEditorWindow.hpp +++ b/app/TextEditorWindow.hpp @@ -35,6 +35,8 @@ struct TextEditorWindow : public Window char ctrlf_text_to_find[FIND_POPUP_TEXT_FIELD_LENGTH] = ""; bool ctrlf_case_sensitive = false; bool requesting_load_from = false; + bool requesting_close = false; + bool requesting_destroy = false; }; } diff --git a/stage.cpp b/stage.cpp index d84ddca7..9e977496 100644 --- a/stage.cpp +++ b/stage.cpp @@ -1052,10 +1052,17 @@ int UpdateActors(float deltaTime, bool * showLog) if (showAbout) ShowAboutWindow(&showAbout); - for (gzb::TextEditorWindow *w : text_editors ) + std::vector::iterator itr = text_editors.begin(); + while ( itr < text_editors.end() ) { - if (w->showing ) w->OnImGui(); + if ( (*itr)->showing ) + (*itr)->OnImGui(); + if ( (*itr)->requesting_destroy ) + itr = text_editors.erase(itr); + else + ++itr; } + #ifdef HAVE_IMGUI_DEMO // ImGui Demo window for dev purposes if (showDemo) ImGui::ShowDemoWindow(&showDemo); From 18eba9c3d9cda5fc76d90fd9e0e1b412655e62bb Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Wed, 10 Apr 2024 13:31:21 +0200 Subject: [PATCH 16/39] fix leaking windows, move to AboutWindow --- CMakeLists.txt | 2 + app/AboutWindow.cpp | 148 ++++++++++++++++++++++++++++++++++++++++++++ app/AboutWindow.hpp | 18 ++++++ stage.cpp | 128 ++++---------------------------------- 4 files changed, 181 insertions(+), 115 deletions(-) create mode 100644 app/AboutWindow.cpp create mode 100644 app/AboutWindow.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f01d6b4..d734917d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -192,6 +192,8 @@ list(APPEND GZB_SOURCES app/Window.hpp app/TextEditorWindow.hpp app/TextEditorWindow.cpp + app/AboutWindow.hpp + app/AboutWindow.cpp ext/imgui/backends/imgui_impl_opengl3.cpp ext/imgui/backends/imgui_impl_sdl2.cpp ext/imgui/imconfig.h diff --git a/app/AboutWindow.cpp b/app/AboutWindow.cpp new file mode 100644 index 00000000..2dd18827 --- /dev/null +++ b/app/AboutWindow.cpp @@ -0,0 +1,148 @@ +#include "AboutWindow.hpp" +#ifndef IMGUI_DEFINE_MATH_OPERATORS +# define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "config.h" +#include "imgui.h" +#include "libsphactor.h" +#include "czmq.h" +#include + +namespace gzb { + +/* Ref: https://github.com/ocornut/imgui/issues/3606 + * These guys are amazing! Just copied one of the entries + */ +#define V2 ImVec2 +#define F float +V2 conv(V2 v, F z, V2 sz, V2 o){return V2((v.x/z)*sz.x*5.f+sz.x*0.5f,(v.y/z)*sz.y*5.f+sz.y*0.5f)+o;} +V2 R(V2 v, F ng){ng*=0.1f;return V2(v.x*cosf(ng)-v.y*sinf(ng),v.x*sinf(ng)+v.y*cosf(ng));} +void FX(ImDrawList* d, V2 o, V2 b, V2 sz, ImVec4, F t) { + d->AddRectFilled(o,b,0xFF000000,0); + t*=4; + for (int i = 0; i < 20; i++) { + F z=21.-i-(t-floorf(t))*2.,ng=-t*2.1+z,ot0=-t+z*0.2,ot1=-t+(z+1.)*0.2,os=0.3; + V2 s[]={V2(cosf((t+z)*0.1)*0.2+1.,sinf((t+z)*0.1)*0.2+1.),V2(cosf((t+z+1.)*0.1)*0.2+1.,sinf((t+z+1.)*0.1)*0.2+1.)}; + V2 of[]={V2(cosf(ot0)*os,sinf(ot0)*os),V2(cosf(ot1)*os,sinf(ot1)*os)}; + V2 p[]={V2(-1,-1),V2(1,-1),V2(1,1),V2(-1,1)}; + ImVec2 pts[8];int j; + for (j=0;j<8;j++) { + int n = (j/4);pts[j]=conv(R(p[j%4]*s[n]+of[n],ng+n),(z+n)*2.,sz,o); + } + for (j=0;j<4;j++){ + V2 q[4]={pts[j],pts[(j+1)%4],pts[((j+1)%4)+4],pts[j+4]}; + F it=(((i&1)?0.5:0.6)+j*0.05)*((21.-z)/21.); + d->AddConvexPolyFilled(q,4,ImColor::HSV(0.6+sinf(t*0.03)*0.5,1,sqrtf(it))); + } + } +} + +void SelectableText(const char *buf) +{ + ImGui::PushID(buf); + ImGui::PushItemWidth(ImGui::GetColumnWidth()); + ImGui::GetStyleColorVec4(ImGuiCol_TableRowBg); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImGui::GetStyleColorVec4(ImGuiCol_TableRowBg)); //otherwise it is colored + ImGui::InputText("", (char *)buf, strlen(buf), ImGuiInputTextFlags_ReadOnly); + ImGui::PopItemWidth(); + ImGui::PopStyleColor(); + ImGui::PopID(); +} + +AboutWindow::AboutWindow() +{ + +} + +AboutWindow::~AboutWindow() +{ + if (nics) + ziflist_destroy(&nics); +} + +void AboutWindow::OnImGui() +{ + ImGuiIO& io = ImGui::GetIO(); + //TODO: try to center window, doesn't work somehow + ImGui::SetNextWindowPos(ImGui::GetWindowSize()/2, ImGuiCond_Once, ImVec2(0.5,0.5)); + ImGui::Begin("About", &showing, ImGuiWindowFlags_AlwaysAutoResize); + ImVec2 size(320.0f, 180.0f); + ImGui::InvisibleButton("canvas", size); + ImVec2 p0 = ImGui::GetItemRectMin(); + ImVec2 p1 = ImGui::GetItemRectMax(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->PushClipRect(p0, p1); + + ImVec4 mouse_data; + mouse_data.x = (io.MousePos.x - p0.x) / size.x; + mouse_data.y = (io.MousePos.y - p0.y) / size.y; + mouse_data.z = io.MouseDownDuration[0]; + mouse_data.w = io.MouseDownDuration[1]; + float time = (float)ImGui::GetTime(); + FX(draw_list, p0, p1, size, mouse_data, time); + draw_list->AddText(ImGui::GetFont(), ImGui::GetFontSize()*4.f, p0 + ImVec2(16,120), ImColor::HSV(mouse_data.x,mouse_data.x,0.9), "GazebOsc"); + //draw_list->AddText(p0 + size/2, IM_COL32(255,0,255,255), "GazebOsc"); + //draw_list->AddCircleFilled( p0 + size/2, 10.f, IM_COL32_WHITE, 8); + draw_list->PopClipRect(); + ImGui::Text("Gazebosc: " GIT_VERSION); + ImGui::Text("ZeroMQ: %i.%i.%i", ZMQ_VERSION_MAJOR, ZMQ_VERSION_MINOR, ZMQ_VERSION_PATCH); + ImGui::Text("CZMQ: %i.%i.%i", CZMQ_VERSION_MAJOR, CZMQ_VERSION_MINOR, CZMQ_VERSION_PATCH); + ImGui::Text("Sphactor: %i.%i.%i", SPHACTOR_VERSION_MAJOR, SPHACTOR_VERSION_MINOR, SPHACTOR_VERSION_PATCH); + ImGui::Text("Dear ImGui: %s", IMGUI_VERSION); + if (ImGui::CollapsingHeader("System Info")) + { + if (nics == NULL) + nics = ziflist_new(); + + static ImGuiTableFlags flags = ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg; + + if (ImGui::BeginTable("interfaces", 5, flags)) + { + ImGui::TableSetupColumn("Name"); + ImGui::TableSetupColumn("MacAddress"); + ImGui::TableSetupColumn("IpAddress"); + ImGui::TableSetupColumn("Netmask"); + ImGui::TableSetupColumn("Broadcast"); + ImGui::TableHeadersRow(); + + const char *name = ziflist_first (nics); + while (name) + { + ImGui::TableNextRow(); + // name + ImGui::TableSetColumnIndex(0); + SelectableText(name); + + // mac + char buf[32]; + ImGui::TableSetColumnIndex(1); + sprintf(buf, "%s", ziflist_mac(nics) ); + SelectableText(buf); + // address + ImGui::TableSetColumnIndex(2); + sprintf(buf, "%s", ziflist_address (nics)); + SelectableText(buf); + // netmask + ImGui::TableSetColumnIndex(3); + sprintf(buf, "%s", ziflist_netmask (nics)); + SelectableText(buf); + // broadcast + ImGui::TableSetColumnIndex(4); + //char buf[32]; + sprintf(buf, "%s", ziflist_broadcast (nics)); + SelectableText(buf); + + name = ziflist_next (nics); + } + ImGui::EndTable(); + } + if ( ImGui::Button("refresh interfaces") ) + { + ziflist_reload(nics); + } + + } + ImGui::End(); +} + +} // namespace diff --git a/app/AboutWindow.hpp b/app/AboutWindow.hpp new file mode 100644 index 00000000..6edece8e --- /dev/null +++ b/app/AboutWindow.hpp @@ -0,0 +1,18 @@ +#ifndef ABOUTWINDOW_HPP +#define ABOUTWINDOW_HPP + +#include "Window.hpp" +#include "czmq.h" + +namespace gzb { +class AboutWindow : public Window +{ +public: + AboutWindow(); + ~AboutWindow(); + void OnImGui(); + + ziflist_t *nics = NULL; // list of network interfaces +}; +} // namespace +#endif // ABOUTWINDOW_HPP diff --git a/stage.cpp b/stage.cpp index 9e977496..0653338d 100644 --- a/stage.cpp +++ b/stage.cpp @@ -35,6 +35,7 @@ namespace fs = std::filesystem; #include "ActorContainer.h" #include "actors/actors.h" #include "app/TextEditorWindow.hpp" +#include "app/AboutWindow.hpp" #include "ext/ImGui-Addons/FileBrowser/ImGuiFileBrowser.h" #include "ext/ImGuiColorTextEdit/TextEditor.h" #include "config.h" @@ -100,6 +101,9 @@ std::map max_actors_by_type; sph_stage_t *stage = NULL; static ziflist_t *nics = NULL; // list of network interfaces +// about window +static gzb::AboutWindow *about_win; + // File browser instance imgui_addons::ImGuiFileBrowser file_dialog; std::string editingFile = ""; @@ -322,119 +326,6 @@ void ShowConfigWindow(bool * showLog) { ImGui::End(); } - -/* Ref: https://github.com/ocornut/imgui/issues/3606 - * These guys are amazing! Just copied one of the entries - */ -#define V2 ImVec2 -#define F float -V2 conv(V2 v, F z, V2 sz, V2 o){return V2((v.x/z)*sz.x*5.f+sz.x*0.5f,(v.y/z)*sz.y*5.f+sz.y*0.5f)+o;} -V2 R(V2 v, F ng){ng*=0.1f;return V2(v.x*cosf(ng)-v.y*sinf(ng),v.x*sinf(ng)+v.y*cosf(ng));} -void FX(ImDrawList* d, V2 o, V2 b, V2 sz, ImVec4, F t) { - d->AddRectFilled(o,b,0xFF000000,0); - t*=4; - for (int i = 0; i < 20; i++) { - F z=21.-i-(t-floorf(t))*2.,ng=-t*2.1+z,ot0=-t+z*0.2,ot1=-t+(z+1.)*0.2,os=0.3; - V2 s[]={V2(cosf((t+z)*0.1)*0.2+1.,sinf((t+z)*0.1)*0.2+1.),V2(cosf((t+z+1.)*0.1)*0.2+1.,sinf((t+z+1.)*0.1)*0.2+1.)}; - V2 of[]={V2(cosf(ot0)*os,sinf(ot0)*os),V2(cosf(ot1)*os,sinf(ot1)*os)}; - V2 p[]={V2(-1,-1),V2(1,-1),V2(1,1),V2(-1,1)}; - ImVec2 pts[8];int j; - for (j=0;j<8;j++) { - int n = (j/4);pts[j]=conv(R(p[j%4]*s[n]+of[n],ng+n),(z+n)*2.,sz,o); - } - for (j=0;j<4;j++){ - V2 q[4]={pts[j],pts[(j+1)%4],pts[((j+1)%4)+4],pts[j+4]}; - F it=(((i&1)?0.5:0.6)+j*0.05)*((21.-z)/21.); - d->AddConvexPolyFilled(q,4,ImColor::HSV(0.6+sinf(t*0.03)*0.5,1,sqrtf(it))); - } - } -} - -void ShowAboutWindow(bool *open) -{ - ImGuiIO& io = ImGui::GetIO(); - //TODO: try to center window, doesn't work somehow - ImGui::SetNextWindowPos(ImGui::GetWindowSize()/2, ImGuiCond_Once, ImVec2(0.5,0.5)); - ImGui::Begin("About", open, ImGuiWindowFlags_AlwaysAutoResize); - ImVec2 size(320.0f, 180.0f); - ImGui::InvisibleButton("canvas", size); - ImVec2 p0 = ImGui::GetItemRectMin(); - ImVec2 p1 = ImGui::GetItemRectMax(); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - draw_list->PushClipRect(p0, p1); - - ImVec4 mouse_data; - mouse_data.x = (io.MousePos.x - p0.x) / size.x; - mouse_data.y = (io.MousePos.y - p0.y) / size.y; - mouse_data.z = io.MouseDownDuration[0]; - mouse_data.w = io.MouseDownDuration[1]; - float time = (float)ImGui::GetTime(); - FX(draw_list, p0, p1, size, mouse_data, time); - draw_list->AddText(ImGui::GetFont(), ImGui::GetFontSize()*4.f, p0 + ImVec2(16,120), ImColor::HSV(mouse_data.x,mouse_data.x,0.9), "GazebOsc"); - //draw_list->AddText(p0 + size/2, IM_COL32(255,0,255,255), "GazebOsc"); - //draw_list->AddCircleFilled( p0 + size/2, 10.f, IM_COL32_WHITE, 8); - draw_list->PopClipRect(); - ImGui::Text("Gazebosc: " GIT_VERSION); - ImGui::Text("ZeroMQ: %i.%i.%i", ZMQ_VERSION_MAJOR, ZMQ_VERSION_MINOR, ZMQ_VERSION_PATCH); - ImGui::Text("CZMQ: %i.%i.%i", CZMQ_VERSION_MAJOR, CZMQ_VERSION_MINOR, CZMQ_VERSION_PATCH); - ImGui::Text("Sphactor: %i.%i.%i", SPHACTOR_VERSION_MAJOR, SPHACTOR_VERSION_MINOR, SPHACTOR_VERSION_PATCH); - ImGui::Text("Dear ImGui: %s", IMGUI_VERSION); - if (ImGui::CollapsingHeader("System Info")) - { - if (nics == NULL) - nics = ziflist_new(); - - static ImGuiTableFlags flags = ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg; - - if (ImGui::BeginTable("interfaces", 5, flags)) - { - ImGui::TableSetupColumn("Name"); - ImGui::TableSetupColumn("MacAddress"); - ImGui::TableSetupColumn("IpAddress"); - ImGui::TableSetupColumn("Netmask"); - ImGui::TableSetupColumn("Broadcast"); - ImGui::TableHeadersRow(); - - const char *name = ziflist_first (nics); - while (name) - { - ImGui::TableNextRow(); - // name - ImGui::TableSetColumnIndex(0); - SelectableText(name); - - // mac - char buf[32]; - ImGui::TableSetColumnIndex(1); - sprintf(buf, "%s", ziflist_mac(nics) ); - SelectableText(buf); - // address - ImGui::TableSetColumnIndex(2); - sprintf(buf, "%s", ziflist_address (nics)); - SelectableText(buf); - // netmask - ImGui::TableSetColumnIndex(3); - sprintf(buf, "%s", ziflist_netmask (nics)); - SelectableText(buf); - // broadcast - ImGui::TableSetColumnIndex(4); - //char buf[32]; - sprintf(buf, "%s", ziflist_broadcast (nics)); - SelectableText(buf); - - name = ziflist_next (nics); - } - ImGui::EndTable(); - } - if ( ImGui::Button("refresh interfaces") ) - { - ziflist_reload(nics); - } - - } - ImGui::End(); -} - void ShowLogWindow(ImGuiTextBuffer& buffer) { static bool ScrollToBottom = true; @@ -716,6 +607,7 @@ inline ImU32 LerpImU32( ImU32 c1, ImU32 c2, int index, int total, float offset, int UpdateActors(float deltaTime, bool * showLog) { + about_win = new gzb::AboutWindow(); // about window static std::vector selectedActors; static std::vector actorClipboardType; static std::vector actorClipboardCapabilities; @@ -1050,19 +942,25 @@ int UpdateActors(float deltaTime, bool * showLog) } ImGui::End(); - if (showAbout) ShowAboutWindow(&showAbout); - + // text editor windows std::vector::iterator itr = text_editors.begin(); while ( itr < text_editors.end() ) { if ( (*itr)->showing ) (*itr)->OnImGui(); if ( (*itr)->requesting_destroy ) + { + delete(*itr); itr = text_editors.erase(itr); + } else ++itr; } + // about window + if (about_win->showing) + about_win->OnImGui(); + #ifdef HAVE_IMGUI_DEMO // ImGui Demo window for dev purposes if (showDemo) ImGui::ShowDemoWindow(&showDemo); From 8ad2baddbe239cfe38855ad23a11913e9c651949 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Wed, 10 Apr 2024 15:45:56 +0200 Subject: [PATCH 17/39] added more Window classes and moved some logic out of stage.cpp --- CMakeLists.txt | 2 ++ app/App.hpp | 43 ++++++++++++++++++++++++-- app/DemoWindow.hpp | 12 ++++++++ app/LogWindow.hpp | 53 +++++++++++++++++++++++++++++++ main.cpp | 41 +++++++++++++++--------- stage.cpp | 77 +++++++--------------------------------------- 6 files changed, 145 insertions(+), 83 deletions(-) create mode 100644 app/DemoWindow.hpp create mode 100644 app/LogWindow.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d734917d..10d643c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -194,6 +194,8 @@ list(APPEND GZB_SOURCES app/TextEditorWindow.cpp app/AboutWindow.hpp app/AboutWindow.cpp + app/LogWindow.hpp + app/DemoWindow.hpp ext/imgui/backends/imgui_impl_opengl3.cpp ext/imgui/backends/imgui_impl_sdl2.cpp ext/imgui/imconfig.h diff --git a/app/App.hpp b/app/App.hpp index 8bc3a327..c399bc6b 100644 --- a/app/App.hpp +++ b/app/App.hpp @@ -1,6 +1,9 @@ #ifndef WINDOW_HPP #define WINDOW_HPP #include "TextEditorWindow.hpp" +#include "AboutWindow.hpp" +#include "LogWindow.hpp" +#include "DemoWindow.hpp" #include namespace gzb { @@ -10,10 +13,46 @@ struct App static App app; return app; } - App() {}; + App(): log_win(&log_buffer) {}; + ~App() { + for (auto w : text_editors) + delete w; + }; ImGuiTextBuffer log_buffer; - std::vector text_editors; + std::vector text_editors; + AboutWindow about_win; + LogWindow log_win; + DemoWindow demo_win; }; + +static ImGuiTextBuffer& getLogBuffer(int fd=-1){ + static ImGuiTextBuffer sLogBuffer; // static log buffer for logger channel + static char huge_string_buf[4096]; + + if (fd !=-1) + read(fd, huge_string_buf, 4096); + if ( strlen( huge_string_buf ) > 0 ) { + sLogBuffer.appendf("%s", huge_string_buf); + memset(huge_string_buf,0,4096); + } + + return sLogBuffer; +} + +static void capture_stdio() +{ + static int out_pipe[2]; + int rc = pipe(out_pipe); + assert( rc == 0 ); + + long flags = fcntl(out_pipe[0], F_GETFL); + flags |= O_NONBLOCK; + fcntl(out_pipe[0], F_SETFL, flags); + + dup2(out_pipe[1], STDOUT_FILENO); + close(out_pipe[1]); +} + } #endif // WINDOW_HPP diff --git a/app/DemoWindow.hpp b/app/DemoWindow.hpp new file mode 100644 index 00000000..a3a850da --- /dev/null +++ b/app/DemoWindow.hpp @@ -0,0 +1,12 @@ +#ifndef DEMOWINDOW_HPP +#define DEMOWINDOW_HPP + +#include "Window.hpp" +#include "imgui.h" + +struct DemoWindow : public gzb::Window +{ + DemoWindow() { window_name = "Demo Window"; showing=false; }; + void OnImGui() { ImGui::ShowDemoWindow(&showing); }; +}; +#endif // DEMOWINDOW_HPP diff --git a/app/LogWindow.hpp b/app/LogWindow.hpp new file mode 100644 index 00000000..150a4a14 --- /dev/null +++ b/app/LogWindow.hpp @@ -0,0 +1,53 @@ +#ifndef LOGWINDOW_HPP +#define LOGWINDOW_HPP + +#include "app/Window.hpp" +#include "imgui.h" + +namespace gzb { +class LogWindow : public Window +{ +public: + LogWindow(ImGuiTextBuffer *log_buffer) + { + buffer = log_buffer; + window_name = "Log Console"; + }; + ~LogWindow() {}; + void OnImGui() + { + ImGui::PushID(123); + + ImGui::SetNextWindowSizeConstraints(ImVec2(100,100), ImVec2(1000,1000)); + ImGui::Begin(window_name.c_str()); + + if (ImGui::Button("Clear")) buffer->clear(); + ImGui::SameLine(); + if ( ImGui::Button("To Bottom") ) { + scroll_to_bottom = true; + } + + ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_HorizontalScrollbar); + + if ( !scroll_to_bottom && ImGui::GetScrollY() == ImGui::GetScrollMaxY() ) { + scroll_to_bottom = true; + } + + ImGui::TextUnformatted(buffer->begin()); + + if (scroll_to_bottom) + ImGui::SetScrollHereY(1.0f); + scroll_to_bottom = false; + + ImGui::EndChild(); + + ImGui::End(); + + ImGui::PopID(); + } + + bool scroll_to_bottom = true; + ImGuiTextBuffer *buffer; +}; +} // namespace +#endif // LOGWINDOW_HPP diff --git a/main.cpp b/main.cpp index b79d8d63..eb445e8b 100644 --- a/main.cpp +++ b/main.cpp @@ -106,17 +106,6 @@ bool logWindow = false; char huge_string_buf[4096]; int out_pipe[2]; -ImGuiTextBuffer& getBuffer(){ - static ImGuiTextBuffer sLogBuffer; // static log buffer for logger channel - - //read(out_pipe[0], huge_string_buf, 4096); - if ( strlen( huge_string_buf ) > 0 ) { - sLogBuffer.appendf("%s", huge_string_buf); - memset(huge_string_buf,0,4096); - } - - return sLogBuffer; -} /* Obtain a backtrace and print it to stdout. */ #if defined(HAVE_LIBUNWIND) @@ -573,6 +562,7 @@ ImGuiIO& ImGUIInit(SDL_Window* window, SDL_GLContext* gl_context, const char* gl void UILoop( SDL_Window* window, ImGuiIO& io ) { ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + gzb::App &app = gzb::App::getApp(); // Main loop unsigned int deltaTime = 0, oldTime = 0; while (!stop) @@ -592,7 +582,6 @@ void UILoop( SDL_Window* window, ImGuiIO& io ) { if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) stop = 1; } - // Start the Dear ImGui frame ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplSDL2_NewFrame(); @@ -615,6 +604,28 @@ void UILoop( SDL_Window* window, ImGuiIO& io ) { oldTime = SDL_GetTicks(); int rc = UpdateActors( ((float)deltaTime) / 1000, &logWindow); + // text editor windows + std::vector::iterator itr = app.text_editors.begin(); + while ( itr < app.text_editors.end() ) + { + if ( (*itr)->showing ) + (*itr)->OnImGui(); + if ( (*itr)->requesting_destroy ) + { + delete(*itr); + itr = app.text_editors.erase(itr); + } + else + ++itr; + } + if (app.about_win.showing) + app.about_win.OnImGui(); + if (app.log_win.showing) + app.log_win.OnImGui(); + if (app.demo_win.showing) + app.demo_win.OnImGui(); + + if ( rc == -1 ) { stop = 1; } @@ -625,9 +636,9 @@ void UILoop( SDL_Window* window, ImGuiIO& io ) { //ImGui::SetNextWindowPos(pos); //ShowConfigWindow(&logWindow); - if ( logWindow ) { - ShowLogWindow(getBuffer()); - } + //if ( logWindow ) { + // ShowLogWindow(getBuffer()); + //} // Rendering ImGui::Render(); diff --git a/stage.cpp b/stage.cpp index 0653338d..b49f0fe4 100644 --- a/stage.cpp +++ b/stage.cpp @@ -34,8 +34,7 @@ namespace fs = std::filesystem; #include "libsphactor.h" #include "ActorContainer.h" #include "actors/actors.h" -#include "app/TextEditorWindow.hpp" -#include "app/AboutWindow.hpp" +#include "app/App.hpp" #include "ext/ImGui-Addons/FileBrowser/ImGuiFileBrowser.h" #include "ext/ImGuiColorTextEdit/TextEditor.h" #include "config.h" @@ -190,8 +189,6 @@ void moveCwdIfNeeded() { } } -std::vector text_editors; - #ifdef HAVE_IMGUI_DEMO // ImGui Demo window for dev purposes bool showDemo = false; @@ -210,8 +207,6 @@ void SelectableText(const char *buf) ImGui::PopID(); } -bool showAbout = false; - void UpdateRegisteredActorsCache() { zhash_t *hash = sphactor_get_registered(); zlist_t *list = zhash_keys(hash); @@ -326,43 +321,10 @@ void ShowConfigWindow(bool * showLog) { ImGui::End(); } -void ShowLogWindow(ImGuiTextBuffer& buffer) { - static bool ScrollToBottom = true; - - ImGui::PushID(123); - - ImGui::SetNextWindowSizeConstraints(ImVec2(100,100), ImVec2(1000,1000)); - ImGui::Begin("Console"); - - if (ImGui::Button("Clear")) buffer.clear(); - ImGui::SameLine(); - if ( ImGui::Button("To Bottom") ) { - ScrollToBottom = true; - } - - ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_HorizontalScrollbar); - - if ( !ScrollToBottom && ImGui::GetScrollY() == ImGui::GetScrollMaxY() ) { - ScrollToBottom = true; - } - - ImGui::TextUnformatted(buffer.begin()); - - if (ScrollToBottom) - ImGui::SetScrollHereY(1.0f); - ScrollToBottom = false; - - ImGui::EndChild(); - - ImGui::End(); - - ImGui::PopID(); -} - void OpenTextEditor(const char *filepath) { gzb::TextEditorWindow *txtwin = NULL; - for ( auto w : text_editors ) + for ( auto w : gzb::App::getApp().text_editors ) { if ( w->associated_file == filepath ) { @@ -372,7 +334,7 @@ void OpenTextEditor(const char *filepath) if (txtwin == NULL) { txtwin = new gzb::TextEditorWindow(filepath); - text_editors.push_back(txtwin); + gzb::App::getApp().text_editors.push_back(txtwin); } else { @@ -425,9 +387,9 @@ int RenderMenuBar( bool * showLog ) { { if ( ImGui::MenuItem(ICON_FA_FOLDER_OPEN " New editor") ) { - text_editors.push_back(new gzb::TextEditorWindow()); + gzb::App::getApp().text_editors.push_back(new gzb::TextEditorWindow()); } - for (auto w : text_editors ) + for (auto w : gzb::App::getApp().text_editors ) { std::string title = w->window_name + " " + ICON_FA_EYE; if ( ImGui::MenuItem(w->window_name.c_str(), NULL, w->showing, true ) ) @@ -435,8 +397,8 @@ int RenderMenuBar( bool * showLog ) { } ImGui::EndMenu(); } - if ( ImGui::MenuItem(ICON_FA_TERMINAL " Toggle Console") ) { - *showLog = !(*showLog); + if ( ImGui::MenuItem(ICON_FA_TERMINAL " Toggle Log Console") ) { + gzb::App::getApp().log_win.showing = !gzb::App::getApp().log_win.showing; } #ifdef HAVE_IMGUI_DEMO if ( ImGui::MenuItem(ICON_FA_CARAVAN " Toggle Demo") ) { @@ -444,7 +406,7 @@ int RenderMenuBar( bool * showLog ) { } #endif if ( ImGui::MenuItem(ICON_FA_INFO " Toggle About") ) { - showAbout = !showAbout; + gzb::App::getApp().about_win.showing = !gzb::App::getApp().about_win.showing; } ImGui::EndMenu(); @@ -607,13 +569,15 @@ inline ImU32 LerpImU32( ImU32 c1, ImU32 c2, int index, int total, float offset, int UpdateActors(float deltaTime, bool * showLog) { - about_win = new gzb::AboutWindow(); // about window static std::vector selectedActors; static std::vector actorClipboardType; static std::vector actorClipboardCapabilities; static std::vector actorClipboardPositions; static bool D_PRESSED = false; + if (about_win == NULL) + about_win = new gzb::AboutWindow(); + int numKeys; byte * keyState = (byte*)SDL_GetKeyboardState(&numKeys); @@ -942,25 +906,6 @@ int UpdateActors(float deltaTime, bool * showLog) } ImGui::End(); - // text editor windows - std::vector::iterator itr = text_editors.begin(); - while ( itr < text_editors.end() ) - { - if ( (*itr)->showing ) - (*itr)->OnImGui(); - if ( (*itr)->requesting_destroy ) - { - delete(*itr); - itr = text_editors.erase(itr); - } - else - ++itr; - } - - // about window - if (about_win->showing) - about_win->OnImGui(); - #ifdef HAVE_IMGUI_DEMO // ImGui Demo window for dev purposes if (showDemo) ImGui::ShowDemoWindow(&showDemo); From c580b093266dfb817813e52a1489bdb5dca90a4f Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Thu, 11 Apr 2024 22:20:50 +0200 Subject: [PATCH 18/39] refactor stage and actor management, move helper methods to helper.* files, remove stage.cpp and ActorContainer.h --- ActorContainer.h | 1208 ------------------------------ CMakeLists.txt | 7 +- actors/pythonactor.c | 398 +++------- app/ActorContainer.cpp | 1138 ++++++++++++++++++++++++++++ app/ActorContainer.hpp | 122 +++ app/App.hpp | 220 +++++- stage.cpp => app/StageWindow.cpp | 1049 ++++++++++---------------- app/StageWindow.hpp | 97 +++ helpers.c | 246 ++++++ helpers.h | 25 + main.cpp | 75 +- 11 files changed, 2423 insertions(+), 2162 deletions(-) delete mode 100644 ActorContainer.h create mode 100644 app/ActorContainer.cpp create mode 100644 app/ActorContainer.hpp rename stage.cpp => app/StageWindow.cpp (66%) create mode 100644 app/StageWindow.hpp create mode 100644 helpers.c create mode 100644 helpers.h diff --git a/ActorContainer.h b/ActorContainer.h deleted file mode 100644 index 78355d5b..00000000 --- a/ActorContainer.h +++ /dev/null @@ -1,1208 +0,0 @@ -#ifndef ACTORCONTAINER_H -#define ACTORCONTAINER_H - -#include "libsphactor.h" -#include -#include -#include "ImNodes.h" -#include "ImNodesEz.h" -#include -#include -#include "imgui_internal.h" -#include "ext/ImGui-Addons/FileBrowser/ImGuiFileBrowser.h" -#include "fontawesome5.h" -#include "config.h" - -// actor file browser -imgui_addons::ImGuiFileBrowser actor_file_dialog; - -static char * -convert_to_relative_to_wd(const char *path) -{ - char wdpath[PATH_MAX]; - getcwd(wdpath, PATH_MAX); - -#if WIN32 - std::string pathStr = wdpath; - std::replace(pathStr.begin(), pathStr.end(), '\\', '/'); - strcpy(wdpath, pathStr.c_str()); -#endif - - const char *ret = strstr(path, wdpath); - if (ret == NULL) - return strdup(path); - // working dir is found in path so only return the relative path - assert(ret == path); - ssize_t wdpathlen = strlen( wdpath ) + 1;// working dir path + first dir delimiter - return strdup( &path[ wdpathlen ]); -} - -void -s_replace_char(char *str, char from, char to) -{ - ssize_t length = strlen(str); - for (int i = 0; i connections{}; - /// A list of input slots current actor has. - std::vector input_slots{}; - /// A list of output slots current actor has. - std::vector output_slots{}; - - /// Last received "lastActive" clock value - int64_t lastActive = 0; - - sphactor_t *actor; - zconfig_t *capabilities; - - ActorContainer(sphactor_t *actor) { - this->actor = actor; - this->title = sphactor_ask_actor_type(actor); - this->capabilities = zconfig_dup(sphactor_capability(actor)); - this->pos.x = sphactor_position_x(actor); - this->pos.y = sphactor_position_y(actor); - - // retrieve in-/output sockets - if (this->capabilities) - { - zconfig_t *insocket = zconfig_locate( this->capabilities, "inputs/input"); - if ( insocket != nullptr ) - { - zconfig_t *type = zconfig_locate(insocket, "type"); - assert(type); - - char* typeStr = zconfig_value(type); - if ( streq(typeStr, "OSC")) { - input_slots.push_back({ "OSC", ActorSlotOSC }); - } - else if ( streq(typeStr, "NatNet")) { - input_slots.push_back({ "NatNet", ActorSlotNatNet }); - } - else if ( streq(typeStr, "Any")) { - input_slots.push_back({ "Any", ActorSlotAny }); - } - else { - zsys_error("Unsupported input type: %s", typeStr); - } - } - - zconfig_t *outsocket = zconfig_locate( this->capabilities, "outputs/output"); - - if ( outsocket != nullptr ) - { - zconfig_t *type = zconfig_locate(outsocket, "type"); - assert(type); - - char* typeStr = zconfig_value(type); - if ( streq(typeStr, "OSC")) { - output_slots.push_back({ "OSC", ActorSlotOSC }); - } - else if ( streq(typeStr, "NatNet")) { - output_slots.push_back({ "NatNet", ActorSlotNatNet }); - } - else if ( streq(typeStr, "Any")) { - output_slots.push_back({ "Any", ActorSlotAny }); - } - else { - zsys_error("Unsupported output type: %s", typeStr); - } - } - } - else - { - input_slots.push_back({ "OSC", ActorSlotOSC }); - output_slots.push_back({ "OSC", ActorSlotOSC }); - } - //ParseConnections(); - //InitializeCapabilities(); //already done by sph_stage? - } - - ~ActorContainer() { - zconfig_destroy(&this->capabilities); - } - - void ParseConnections() { - if ( this->capabilities == NULL ) return; - - // get actor's connections - zlist_t *conn = sphactor_connections( this->actor ); - for ( char *connection = (char *)zlist_first(conn); connection != nullptr; connection = (char *)zlist_next(conn)) - { - Connection new_connection; - new_connection.input_node = this; - new_connection.output_node = FindActorContainerByEndpoint(connection); - assert(new_connection.input_node); - assert(new_connection.output_node); - ((ActorContainer*) new_connection.input_node)->connections.push_back(new_connection); - ((ActorContainer*) new_connection.output_node)->connections.push_back(new_connection); - } - } - - ActorContainer * - FindActorContainerByEndpoint(const char *endpoint) - { - return NULL; - } - - void InitializeCapabilities() { - if ( this->capabilities == NULL ) return; - - zconfig_t *root = zconfig_locate(this->capabilities, "capabilities"); - if ( root == NULL ) return; - - zconfig_t *data = zconfig_locate(root, "data"); - while( data != NULL ) { - HandleAPICalls(data); - data = zconfig_next(data); - } - } - - void SetCapabilities(const char* capabilities ) { - this->capabilities = zconfig_str_load(capabilities); - InitializeCapabilities(); - } - - char *itoa(int i) - { - char *str = (char *)malloc(10); - sprintf(str, "%d", i); - return str; - } - - char *ftoa(float f) - { - char *str = (char *)malloc(30); - sprintf(str, "%f", f); - return str; - } - -#define GZB_TOOLTIP_THRESHOLD 1.0f - void RenderTooltip(const char *help) - { - if ( strlen(help) ) - { - if (ImGui::IsItemHovered() && GImGui->HoveredIdTimer > GZB_TOOLTIP_THRESHOLD ) - { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 24.0f); - ImGui::TextUnformatted(help); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - } - } - - void HandleAPICalls(zconfig_t * data) { - zconfig_t *zapic = zconfig_locate(data, "api_call"); - if ( zapic ) { - zconfig_t *zapiv = zconfig_locate(data, "api_value"); - zconfig_t *zvalue = zconfig_locate(data, "value"); - - if (zapiv) - { - char * zapivStr = zconfig_value(zapiv); - char type = zapivStr[0]; - switch( type ) { - case 'b': { - char *buf = new char[4]; - const char *zvalueStr = zconfig_value(zvalue); - strcpy(buf, zvalueStr); - sphactor_ask_api(this->actor, zconfig_value(zapic), zapivStr, zvalueStr); - //SendAPI(zapic, zapiv, zvalue, &buf); - zstr_free(&buf); - } break; - case 'i': { - int ival; - ReadInt(&ival, zvalue); - sphactor_ask_api(this->actor, zconfig_value(zapic), zapivStr, itoa(ival)); - } break; - case 'f': { - float fval; - ReadFloat(&fval, zvalue); - //SendAPI(zapic, zapiv, zvalue, &fval); - sphactor_ask_api(this->actor, zconfig_value(zapic), zapivStr, ftoa(fval)); - } break; - case 's': { - const char *zvalueStr = zconfig_value(zvalue); - //SendAPI(zapic, zapiv, zvalue, const_cast(&zvalueStr)); - sphactor_ask_api(this->actor, zconfig_value(zapic), zapivStr, zvalueStr); - } break; - } - } - else if (zvalue) { - //assume string - //int ival; - //ReadInt(&ival, zvalue); - //zsock_send( sphactor_socket(this->actor), "si", zconfig_value(zapic), ival); - sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapic), zconfig_value(zvalue)); - } - } - } - - template - void SendAPI(zconfig_t *zapic, zconfig_t *zapiv, zconfig_t *zvalue, T * value) { - if ( !zapic ) return; - - if (zapiv) - { - std::string pic = "s"; - pic += zconfig_value(zapiv); - //zsys_info("Sending: %s", pic.c_str()); - //sphactor_ask_api( this->actor, pic.c_str(), zconfig_value(zapic), *value); - zsock_send( sphactor_socket(this->actor), pic.c_str(), zconfig_value(zapic), *value); - } - else - //sphactor_ask_api( this->actor, "ss", zconfig_value(zapic), *value); - zsock_send( sphactor_socket(this->actor), "si", zconfig_value(zapic), *value); - } - - - void Render(float deltaTime) { - //loop through each data element in capabilities - if ( this->capabilities == NULL ) return; - - zconfig_t *root = zconfig_locate(this->capabilities, "capabilities"); - if ( root == NULL ) return; - - zconfig_t *data = zconfig_locate(root, "data"); - while( data != NULL ) { - zconfig_t *name = zconfig_locate(data, "name"); - zconfig_t *type = zconfig_locate(data, "type"); - assert(name); - assert(type); - - char* nameStr = zconfig_value(name); - char* typeStr = zconfig_value(type); - if ( streq(typeStr, "int")) { - RenderInt( nameStr, data ); - } - else if ( streq(typeStr, "slider")) { - RenderSlider( nameStr, data ); - } - else if ( streq(typeStr, "float")) { - RenderFloat( nameStr, data ); - } - else if ( streq(typeStr, "string")) { - RenderString(nameStr, data); - } - else if ( streq(typeStr, "bool")) { - RenderBool( nameStr, data ); - } - else if ( streq(typeStr, "filename")) { - RenderFilename( nameStr, data ); - } - else if ( streq(typeStr, "mediacontrol")) { - RenderMediacontrol( nameStr, data ); - } - else if ( streq(typeStr, "list")) { - RenderMultilineString( nameStr, data ); - } - else if ( streq(typeStr, "trigger")) { - RenderTrigger( nameStr, data ); - } - - data = zconfig_next(data); - } - RenderCustomReport(); - } - - void RenderCustomReport() { - const int LABEL_WIDTH = 25; - const int VALUE_WIDTH = 50; - - sphactor_report_t * report = sphactor_report(actor); - lastActive = sphactor_report_send_time(report); - - assert(report); - zosc_t * customData = sphactor_report_custom(report); - if ( customData ) { - const char* address = zosc_address(customData); - //ImGui::Text("%s", address); - - char type = '0'; - const void *data = zosc_first(customData, &type); - bool name = true; - char* nameStr = nullptr; - while( data ) { - if ( name ) { - //expecting a name string for each piece of data - assert(type == 's'); - - int rc = zosc_pop_string(customData, &nameStr); - if (rc == 0 ) - { - ImGui::BeginGroup(); - if (!streq(nameStr, "lastActive")) { - ImGui::SetNextItemWidth(LABEL_WIDTH); - ImGui::Text("%s:", nameStr); - ImGui::SameLine(); - ImGui::SetNextItemWidth(VALUE_WIDTH); - } - } - } - else { - switch(type) { - case 's': { - char* value; - int rc = zosc_pop_string(customData, &value); - if( rc == 0) - { - ImGui::Text("%s", value); - zstr_free(&value); - } - } break; - case 'c': { - char value; - zosc_pop_char(customData, &value); - ImGui::Text("%c", value); - } break; - case 'i': { - int32_t value; - zosc_pop_int32(customData, &value); - ImGui::Text("%i", value); - } break; - case 'h': { - int64_t value; - zosc_pop_int64(customData, &value); - - if (streq(nameStr, "lastActive")) { - // render something to indicate the actor is currently active - lastActive = (int64_t)value; - int64_t diff = zclock_mono() - lastActive; - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - ImNodes::CanvasState* canvas = ImNodes::GetCurrentCanvas(); - if (diff < 100) { - draw_list->AddCircleFilled(canvas->Offset + pos * canvas->Zoom + ImVec2(5,5) * canvas->Zoom, 5 * canvas->Zoom , ImColor(0, 255, 0), 4); - } - else { - draw_list->AddCircleFilled(canvas->Offset + pos * canvas->Zoom + ImVec2(5, 5) * canvas->Zoom, 5 * canvas->Zoom, ImColor(127, 127, 127), 4); - } - } - else { - ImGui::Text("%lli", value); - } - } break; - case 'f': { - float value; - zosc_pop_float(customData, &value); - ImGui::Text("%f", value); - } break; - case 'd': { - double value; - zosc_pop_double(customData, &value); - ImGui::Text("%f", value); - } break; - case 'F': { - ImGui::Text("FALSE"); - } break; - case 'T': { - ImGui::Text("TRUE"); - } break; - } - - ImGui::EndGroup(); - - //free the nameStr here so we can use it up to this point - zstr_free(&nameStr); - } - - //flip expecting name or value - name = !name; - data = zosc_next(customData, &type); - } - } - } - - void SolvePadding( int* position ) { - if ( *position % 4 != 0 ) { - *position += 4 - *position % 4; - } - } - - template - void RenderValue(T *value, const byte * bytes, int *position) { - memcpy(value, bytes+*position, sizeof(T)); - *position += sizeof(T); - } - - // Make the UI compact because there are so many fields - static void PushStyleCompact() - { - ImGuiStyle& style = ImGui::GetStyle(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, (float)(int)(style.FramePadding.y * 0.60f))); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, (float)(int)(style.ItemSpacing.y * 0.60f))); - } - - static void PopStyleCompact() - { - ImGui::PopStyleVar(2); - } - void RenderList(const char *name, zconfig_t *data) - { - ImVec2 size = ImVec2(300,100); // what's a reasonable size? - ImGui::BeginChild("##", size, false, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoSavedSettings ); - // The list capability expects a tree of zconfig data. - // Every node is a column - zconfig_t *namec = zconfig_locate(data, "name"); - int colcount = 0; - zconfig_t *columnc = zconfig_child(data); - while (columnc) - { - // we only count nodes not leaves - if ( zconfig_child(columnc) ) - { - colcount++; - } - columnc = zconfig_next(columnc); - } - assert(colcount); - - // By default, if we don't enable ScrollX the sizing policy for each columns is "Stretch" - // Each columns maintain a sizing weight, and they will occupy all available width. - static ImGuiTableFlags flags = ImGuiTableFlags_Resizable ;//ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ContextMenuInBody | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY; - if (ImGui::BeginTable(zconfig_value(namec), colcount, flags)) - { - unsigned int dirty = 0; //track changes bitwise - columnc = zconfig_child(data); - // generate table header - while (columnc) - { - if ( zconfig_child(columnc) ) - { - char *name = zconfig_name(columnc); - ImGui::TableSetupColumn(name, ImGuiTableColumnFlags_None); - } - columnc = zconfig_next(columnc); - } - ImGui::TableHeadersRow(); - // generate entries - - // always end with an empty entry row - char *values[10] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL}; // capture input - columnc = zconfig_child(data); - ImGui::TableNextRow(); - int colidx = 0; - while (columnc) - { - zconfig_t *rowc = NULL; - if ( rowc = zconfig_child(columnc) ) - { - char *name = zconfig_name(columnc); - ImGui::TableSetColumnIndex(colidx); - zconfig_t *typec = zconfig_locate(columnc, "type"); - char *type = zconfig_value(typec); - ImGui::PushItemWidth( -1.0f ); - if ( streq(type, "intbla") ) - { - int value = 6200; - if ( ImGui::InputInt("##", &value, 1, 100,ImGuiInputTextFlags_EnterReturnsTrue ) ) - { - dirty = (1 << colidx) | colidx; - } - } - else - { - static char bla[256] = ""; - char label[256] = ""; - snprintf(label, 256, "##%s", name); - if ( ImGui::InputText(&label[0], &bla[0], 256, ImGuiInputTextFlags_None) ) - { - dirty = (1 << colidx) | colidx; - } - } - ImGui::PopItemWidth(); - colidx++; - } - columnc = zconfig_next(columnc); - } - ImGui::EndTable(); - - if (dirty) - { - // get current value, append new value if all input fields contain data - } - } - //PushStyleCompact(); - //ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags, ImGuiTableFlags_Resizable); - //ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", &flags, ImGuiTableFlags_BordersV); - //ImGui::SameLine(); HelpMarker("Using the _Resizable flag automatically enables the _BordersInnerV flag as well, this is why the resize borders are still showing when unchecking this."); - //PopStyleCompact(); - - - /* - if (ImGui::BeginTable("table1", 3, flags)) - { - ImGui::TableSetupColumn("name", ImGuiTableColumnFlags_None, 80.f); - ImGui::TableSetupColumn("ip", ImGuiTableColumnFlags_None, 80.f); - ImGui::TableSetupColumn("port", ImGuiTableColumnFlags_None, 60.f); - ImGui::TableHeadersRow(); - for (int row = 0; row < 3; row++) - { - ImGui::TableNextRow(); - - for (int column = 0; column < 3; column++) - { - ImGui::TableSetColumnIndex(column); - static int port = 0; - static char bla[256] = ""; - if (column == 2) - ImGui::InputInt("##port", &port); - else - ImGui::InputText("##Hello", &bla[0], 256); - } - } - ImGui::EndTable(); - }*/ - ImGui::EndChild(); - } - - void RenderMediacontrol(const char* name, zconfig_t *data) - { - if ( ImGui::Button(ICON_FA_BACKWARD) ) - //sphactor_ask_api(this->actor, "BACK", "", NULL); - zstr_send(sphactor_socket(this->actor), "BACK"); - ImGui::SameLine(); - if ( ImGui::Button(ICON_FA_PLAY) ) - zstr_send(sphactor_socket(this->actor), "PLAY"); - ImGui::SameLine(); - if ( ImGui::Button(ICON_FA_PAUSE) ) - zstr_send(sphactor_socket(this->actor), "PAUSE"); - } - - void RenderFilename(const char* name, zconfig_t *data) { - int max = MAX_STR_DEFAULT; - - zconfig_t * zvalue = zconfig_locate(data, "value"); - zconfig_t * ztype_hint = zconfig_locate(data, "type_hint"); - zconfig_t * zapic = zconfig_locate(data, "api_call"); - zconfig_t * zapiv = zconfig_locate(data, "api_value"); - zconfig_t * zoptions = zconfig_locate(data, "options"); - zconfig_t * zvalidfiles = zconfig_locate(data, "valid_files"); - assert(zvalue); - - zconfig_t * zmax = zconfig_locate(data, "max"); - - ReadInt( &max, zmax ); - - char buf[MAX_STR_DEFAULT]; - const char* zvalueStr = zconfig_value(zvalue); - strcpy(buf, zvalueStr); - char *p = &buf[0]; - bool file_selected = false; - - const char* zoptStr = "r"; - if ( zoptions != nullptr ) { - zoptStr = zconfig_value(zoptions); - } - const char *valid_files = zvalidfiles == nullptr ? "*.*" : zconfig_value(zvalidfiles); - - ImGui::SetNextItemWidth(180); - if ( ImGui::InputTextWithHint("", name, buf, max, ImGuiInputTextFlags_EnterReturnsTrue ) ) { - zconfig_set_value(zvalue, "%s", buf); - sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), p ); - //SendAPI(zapic, zapiv, zvalue, &(p)); - } - ImGui::SameLine(); - if ( ImGui::Button( ICON_FA_FOLDER_OPEN ) ) - file_selected = true; - - if ( file_selected ) - ImGui::OpenPopup("Actor Open File"); - - if ( actor_file_dialog.showFileDialog("Actor Open File", - streq(zoptStr, "rw" ) ? imgui_addons::ImGuiFileBrowser::DialogMode::SAVE : imgui_addons::ImGuiFileBrowser::DialogMode::OPEN, - ImVec2(700, 310), - valid_files) ) // TODO: perhaps add type hint for extensions? - { - char *path = convert_to_relative_to_wd(actor_file_dialog.selected_path.c_str()); - zconfig_set_value(zvalue, "%s", path ); - strcpy(buf, path); - //SendAPI(zapic, zapiv, zvalue, &(p) ); - zstr_free(&path); - sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), p ); - } - - zconfig_t *help = zconfig_locate(data, "help"); - const char *helpv = "Load a file"; - if (help) - { - helpv = zconfig_value(help); - } - RenderTooltip(helpv); - - // handle options - zconfig_t *opts = zconfig_locate(data, "options"); - // check if there are options - if (opts) - { - char *optsv = zconfig_value(opts); - // check if is an editable textfile - if (optsv && strchr(optsv, 't') != NULL && strchr(optsv, 'e') != NULL) - { - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_EDIT)) - { - zconfig_t* zvalue = zconfig_locate(data, "value"); - const char* zvalueStr = zconfig_value(zvalue); - if (strlen(zvalueStr) && zsys_file_exists (zvalueStr) ) - { - OpenTextEditor(zvalueStr); // could own the f pointer - } - else - zsys_error("no valid file to load: %s", zvalueStr); - } - RenderTooltip("Edit file in texteditor"); - } - if (optsv && strchr(optsv, 'c') != NULL && strchr(optsv, 't') != NULL) // we can create a new text file - { - // create new text files - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FILE)) - ImGui::OpenPopup("Create File?"); - - RenderTooltip("Create new file"); - // Always center this window when appearing - ImVec2 center = ImGui::GetMainViewport()->GetCenter(); - ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); - - if (ImGui::BeginPopupModal("Create File?", NULL, ImGuiWindowFlags_AlwaysAutoResize)) - { - ImGui::Text("Enter a filename:"); - ImGui::Separator(); - static char fnamebuf[PATH_MAX] = ""; - bool createFile = false; - if ( ImGui::InputText("filename", fnamebuf, PATH_MAX, ImGuiInputTextFlags_EnterReturnsTrue ) ) - { - if ( strlen(fnamebuf) ) - { - createFile = true; - } - ImGui::CloseCurrentPopup(); - } - char feedback[256] = "Enter a valid filename!"; - if (strlen(fnamebuf)) - { - // check if this filename conflicts - char path[PATH_MAX]; - getcwd(path, PATH_MAX); - char fullpath[PATH_MAX]; - sprintf (fullpath, "%s/%s", path, fnamebuf); - //zsys_info("filemode %i", zsys_file_mode(fullpath)); - int mode = zsys_file_mode(fullpath); - if ( mode != -1 ) - { - if ( S_ISDIR(mode) ) - sprintf( feedback, "Filename \'%s\' conflicts with an existing directory!", fnamebuf); - else - sprintf( feedback, "File %s exists and will be overwritten if continued", fnamebuf); - } - } - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 24.0f); - ImGui::Text(feedback); - ImGui::PopTextWrapPos(); - - if (ImGui::Button("OK", ImVec2(120, 0))) - { - if ( strlen(fnamebuf) ) - { - createFile = true; - } - ImGui::CloseCurrentPopup(); - } - ImGui::SetItemDefaultFocus(); - ImGui::SameLine(); - if (ImGui::Button("Cancel", ImVec2(120, 0))) - { - createFile = false; - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); - - if (createFile && strlen(fnamebuf)) - { - zchunk_t *file_data = NULL; - zconfig_t *tmpl = zconfig_locate(data, "file_template"); - if (tmpl) - { - char *tmplpath = zconfig_value(tmpl); - char fullpath[PATH_MAX]; - snprintf(fullpath, PATH_MAX-1, "%s/%s", GZB_GLOBAL.RESOURCESPATH, tmplpath); - if ( zsys_file_exists(fullpath) ) - { - zfile_t *tmplf = zfile_new(NULL, fullpath); - assert(tmplf); - int rc = zfile_input(tmplf); - assert(rc == 0); - file_data = zfile_read(tmplf, zsys_file_size (tmplpath), 0); - assert(file_data); - zfile_destroy(&tmplf); - } - else - { - file_data = zchunk_new ("\n", 1); - } - } - else - { - file_data = zchunk_new ("\n", 1); - } - // create the new textfile - char path[PATH_MAX]; - getcwd(path, PATH_MAX); - zfile_t *txtfile = zfile_new(path, fnamebuf); - assert(txtfile); - int rc = zfile_output(txtfile); - assert(rc == 0); - rc = zfile_write (txtfile, file_data, 0); - assert (rc == 0); - zchunk_destroy (&file_data); - zfile_close(txtfile); - //zfile_destroy(&txtfile); - zconfig_set_value(zvalue, "%s", fnamebuf); - sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), p ); - - OpenTextEditor(zfile_filename(txtfile, NULL)); // could own the f pointer - } - } - } - } - } - - void RenderBool(const char* name, zconfig_t *data) { - bool value; - - zconfig_t * zvalue = zconfig_locate(data, "value"); - zconfig_t * zapic = zconfig_locate(data, "api_call"); - zconfig_t * zapiv = zconfig_locate(data, "api_value"); - assert(zvalue); - char *apiv; - // set default call value - if (zapiv == nullptr) - apiv = "s"; - else - apiv = zconfig_value(zapiv); - - ReadBool( &value, zvalue); - - ImGui::SetNextItemWidth(100); - if ( ImGui::Checkbox( name, &value ) ) { - zconfig_set_value(zvalue, "%s", value ? "True" : "False"); - - char *buf = new char[6]; - const char *zvalueStr = zconfig_value(zvalue); - strcpy(buf, zvalueStr); - //SendAPI(zapic, zapiv, zvalue, &buf); - sphactor_ask_api(this->actor, zconfig_value(zapic), apiv, buf ); - zstr_free(&buf); - } - zconfig_t *help = zconfig_locate(data, "help"); - if (help) - { - char *helpv = zconfig_value(help); - RenderTooltip(helpv); - } - } - - void RenderInt(const char* name, zconfig_t *data) { - int value; - int min = 0, max = 0, step = 0; - - zconfig_t * zvalue = zconfig_locate(data, "value"); - zconfig_t * zapic = zconfig_locate(data, "api_call"); - zconfig_t * zapiv = zconfig_locate(data, "api_value"); - assert(zvalue); - - zconfig_t * zmin = zconfig_locate(data, "min"); - zconfig_t * zmax = zconfig_locate(data, "max"); - zconfig_t * zstep = zconfig_locate(data, "step"); - - ReadInt( &value, zvalue); - ReadInt( &min, zmin); - ReadInt( &max, zmax); - ReadInt( &step, zstep); - - ImGui::SetNextItemWidth(100); - - if ( ImGui::InputInt( name, &value, step, 100) ) { - if ( zmin ) { - if (value < min) value = min; - } - if ( zmax ) { - if ( value > max ) value = max; - } - - zconfig_set_value(zvalue, "%i", value); - //SendAPI(zapic, zapiv, zvalue, &value); - sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), itoa(value) ); - } - zconfig_t *help = zconfig_locate(data, "help"); - if (help) - { - char *helpv = zconfig_value(help); - RenderTooltip(helpv); - } - } - - void RenderSlider(const char* name, zconfig_t *data) { - - zconfig_t * zvalue = zconfig_locate(data, "value"); - zconfig_t * zapic = zconfig_locate(data, "api_call"); - zconfig_t * zapiv = zconfig_locate(data, "api_value"); - assert(zvalue); - - zconfig_t * zmin = zconfig_locate(data, "min"); - zconfig_t * zmax = zconfig_locate(data, "max"); - zconfig_t * zstep = zconfig_locate(data, "step"); - - ImGui::SetNextItemWidth(250); - ImGui::BeginGroup(); - ImGui::SetNextItemWidth(200); - - if ( streq(zconfig_value(zapiv), "f") ) - { - float value; - float min = 0., max = 0., step = 0.; - ReadFloat( &value, zvalue); - ReadFloat( &min, zmin); - ReadFloat( &max, zmax); - ReadFloat( &step, zstep); - - if ( ImGui::SliderFloat("", &value, min, max) ) { - zconfig_set_value(zvalue, "%f", value); - sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), ftoa(value) ); - } - ImGui::SameLine(); - ImGui::SetNextItemWidth(35); - if ( ImGui::InputFloat("##min", &min, step, 0.f, "%.2f", ImGuiInputTextFlags_EnterReturnsTrue ) ) - { - zconfig_set_value(zmin, "%f", min); - } - ImGui::SameLine(); - ImGui::SetNextItemWidth(35); - if ( ImGui::InputFloat( "##max", &max, step, 0.f, "%.2f", ImGuiInputTextFlags_EnterReturnsTrue ) ) - { - zconfig_set_value(zmax, "%f", max); - } - } - else - { - int value; - int min = 0, max = 0, step = 0; - ReadInt( &value, zvalue); - ReadInt( &min, zmin); - ReadInt( &max, zmax); - ReadInt( &step, zstep); - - if ( ImGui::SliderInt( "", &value, min, max) ) { - zconfig_set_value(zvalue, "%i", value); - sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), itoa(value) ); - } - ImGui::SameLine(); - ImGui::SetNextItemWidth(30); - if ( ImGui::InputInt( "##min", &min, 0, 0,ImGuiInputTextFlags_EnterReturnsTrue ) ) - { - zconfig_set_value(zmin, "%i", min); - } - ImGui::SameLine(); - ImGui::SetNextItemWidth(30); - if ( ImGui::InputInt( "##max", &max, 0, 0,ImGuiInputTextFlags_EnterReturnsTrue ) ) - { - zconfig_set_value(zmax, "%i", max); - } - } - - ImGui::EndGroup(); - zconfig_t *help = zconfig_locate(data, "help"); - if (help) - { - char *helpv = zconfig_value(help); - RenderTooltip(helpv); - } - } - - void RenderFloat(const char* name, zconfig_t *data) { - float value; - float min = 0, max = 0, step = 0; - - zconfig_t * zvalue = zconfig_locate(data, "value"); - zconfig_t * zapic = zconfig_locate(data, "api_call"); - zconfig_t * zapiv = zconfig_locate(data, "api_value"); - assert(zvalue); - - zconfig_t * zmin = zconfig_locate(data, "min"); - zconfig_t * zmax = zconfig_locate(data, "max"); - zconfig_t * zstep = zconfig_locate(data, "step"); - - ReadFloat( &value, zvalue); - ReadFloat( &min, zmin); - ReadFloat( &max, zmax); - ReadFloat( &step, zstep); - - ImGui::SetNextItemWidth(100); - if ( min != max ) { - if ( ImGui::SliderFloat( name, &value, min, max) ) { - zconfig_set_value(zvalue, "%f", value); - //SendAPI(zapic, zapiv, zvalue, &value); - sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), ftoa(value) ); - } - } - else { - if ( ImGui::InputFloat( name, &value, min, max) ) { - zconfig_set_value(zvalue, "%f", value); - //SendAPI(zapic, zapiv, zvalue, &value); - sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), ftoa(value) ); - } - } - zconfig_t *help = zconfig_locate(data, "help"); - if (help) - { - char *helpv = zconfig_value(help); - RenderTooltip(helpv); - } - } - - void RenderString(const char* name, zconfig_t *data) { - int max = MAX_STR_DEFAULT; - - zconfig_t * zvalue = zconfig_locate(data, "value"); - zconfig_t * zapic = zconfig_locate(data, "api_call"); - zconfig_t * zapiv = zconfig_locate(data, "api_value"); - assert(zvalue); - - zconfig_t * zmax = zconfig_locate(data, "max"); - - ReadInt( &max, zmax ); - - char buf[MAX_STR_DEFAULT]; - const char* zvalueStr = zconfig_value(zvalue); - strcpy(buf, zvalueStr); - char *p = &buf[0]; - - ImGui::SetNextItemWidth(200); - if ( ImGui::InputText(name, buf, max) ) { - zconfig_set_value(zvalue, "%s", buf); - sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), buf); - } - - zconfig_t *help = zconfig_locate(data, "help"); - if (help) - { - char *helpv = zconfig_value(help); - RenderTooltip(helpv); - } - } - - void RenderMultilineString(const char* name, zconfig_t *data) { - int max = MAX_STR_DEFAULT; - - zconfig_t * zvalue = zconfig_locate(data, "value"); - zconfig_t * zapic = zconfig_locate(data, "api_call"); - zconfig_t * zapiv = zconfig_locate(data, "api_value"); - assert(zvalue); - - zconfig_t * zmax = zconfig_locate(data, "max"); - - ReadInt( &max, zmax ); - - char buf[MAX_STR_DEFAULT]; - const char* zvalueStr = zconfig_value(zvalue); - strcpy(buf, zvalueStr); - s_replace_char(buf, ',', '\n'); // replace comma with newline if needed - char *p = &buf[0]; - - ImGui::SetNextItemWidth(200); - ImGui::InputTextMultiline("##source", p, max, ImVec2(0, ImGui::GetTextLineHeight() * 8), ImGuiInputTextFlags_EnterReturnsTrue); - if ( ImGui::IsItemEdited() || ImGui::IsItemDeactivatedAfterEdit()) - { - char *sendbuf = strdup(buf); - s_replace_char(sendbuf, '\n', ','); // We use ',' instead of newline since we can't save newlines in the stage save file - zconfig_set_value(zvalue, "%s", sendbuf); - sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), sendbuf); - zstr_free(&sendbuf); - } - - zconfig_t *help = zconfig_locate(data, "help"); - if (help) - { - char *helpv = zconfig_value(help); - RenderTooltip(helpv); - } - } - - void RenderTrigger(const char* name, zconfig_t *data) { - zconfig_t * zapic = zconfig_locate(data, "api_call"); - - ImGui::SetNextItemWidth(200); - if ( ImGui::Button(name) ) { - sphactor_ask_api(this->actor, zconfig_value(zapic), "", "" ); - } - zconfig_t *help = zconfig_locate(data, "help"); - if (help) - { - char *helpv = zconfig_value(help); - RenderTooltip(helpv); - } - } - - void ReadBool( bool *value, zconfig_t * data) { - if ( data != NULL ) { - *value = streq( zconfig_value(data), "True"); - } - } - - void ReadInt( int *value, zconfig_t * data) { - if ( data != NULL ) { - *value = atoi(zconfig_value(data)); - } - } - - void ReadFloat( float *value, zconfig_t * data) { - if ( data != NULL ) { - *value = atof(zconfig_value(data)); - } - } - - void CreateActor() { - } - - void DestroyActor() { - if ( actor ) - sphactor_destroy(&actor); - } - - void DeleteConnection(const Connection& connection) - { - for (auto it = connections.begin(); it != connections.end(); ++it) - { - if (connection == *it) - { - connections.erase(it); - break; - } - } - } - - //TODO: Add custom report data to this? - void SerializeActorData(zconfig_t *section) { - zconfig_t *xpos = zconfig_new("xpos", section); - zconfig_set_value(xpos, "%f", pos.x); - - zconfig_t *ypos = zconfig_new("ypos", section); - zconfig_set_value(ypos, "%f", pos.y); - - // Parse current state of data capabilities - if ( this->capabilities ) { - zconfig_t *root = zconfig_locate(this->capabilities, "capabilities"); - if ( root ) { - zconfig_t *data = zconfig_locate(root, "data"); - while ( data ) { - zconfig_t *name = zconfig_locate(data,"name"); - zconfig_t *value = zconfig_locate(data,"value"); - - if (value) // only store if there's a value - { - char *nameStr = zconfig_value(name); - char *valueStr = zconfig_value(value); - - zconfig_t *stored = zconfig_new(nameStr, section); - zconfig_set_value(stored, "%s", valueStr); - } - - data = zconfig_next(data); - } - } - } - } - - void DeserializeActorData( ImVector *args, ImVector::iterator it) { - char* xpos = *it; - it++; - char* ypos = *it; - it++; - - if ( it != args->end()) { - if ( this->capabilities ) { - zconfig_t *root = zconfig_locate(this->capabilities, "capabilities"); - if ( root ) { - zconfig_t *data = zconfig_locate(root, "data"); - while ( data && it != args->end() ) { - zconfig_t *value = zconfig_locate(data,"value"); - if (value) - { - char* valueStr = *it; - zconfig_set_value(value, "%s", valueStr); - - HandleAPICalls(data); - it++; - } - data = zconfig_next(data); - } - } - } - } - - pos.x = atof(xpos); - pos.y = atof(ypos); - - free(xpos); - free(ypos); - } -}; - -#endif diff --git a/CMakeLists.txt b/CMakeLists.txt index 10d643c2..0aa7777a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -185,17 +185,22 @@ add_custom_command( list(APPEND GZB_SOURCES main.cpp - stage.cpp ImNodes.cpp ImNodesEz.cpp + helpers.h + helpers.c app/App.hpp app/Window.hpp app/TextEditorWindow.hpp app/TextEditorWindow.cpp + app/StageWindow.hpp + app/StageWindow.cpp app/AboutWindow.hpp app/AboutWindow.cpp app/LogWindow.hpp app/DemoWindow.hpp + app/ActorContainer.hpp + app/ActorContainer.cpp ext/imgui/backends/imgui_impl_opengl3.cpp ext/imgui/backends/imgui_impl_sdl2.cpp ext/imgui/imconfig.h diff --git a/actors/pythonactor.c b/actors/pythonactor.c index fa87d484..7a382c74 100644 --- a/actors/pythonactor.c +++ b/actors/pythonactor.c @@ -1,4 +1,5 @@ #include "pythonactor.h" +#include "helpers.h" #include "pyzmsg.h" #include #include "config.h" @@ -9,7 +10,6 @@ #endif #ifdef __UTYPE_LINUX #include -#include "config.h" zmsg_t * s_pythonactor_set_file(pythonactor_t *self, const char *filename); @@ -122,43 +122,6 @@ s_init_inotify(pythonactor_t *self, const char *filename, sphactor_actor_t *acto } #endif -// https://stackoverflow.com/questions/2736753/how-to-remove-extension-from-file-name -static char * -s_remove_ext(const char* myStr) { - char *retStr; - char *lastExt; - if (myStr == NULL) return NULL; - if ((retStr = malloc (strlen (myStr) + 1)) == NULL) return NULL; - strcpy (retStr, myStr); - lastExt = strrchr (retStr, '.'); - if (lastExt != NULL) - *lastExt = '\0'; - return retStr; -} - -static char * -s_basename(char const *path) -{ - char *s = strrchr(path, '/'); - if (!s) - return strdup(path); - else - return strdup(s + 1); -} - -static bool -s_dir_exists(const wchar_t *pypath) -{ - // bleeh we need to convert to char as czmq only supports char paths - char path[PATH_MAX]; - wcstombs(path, pypath, PATH_MAX); - zfile_t *dir = zfile_new(NULL, path); - assert(dir); - bool ret = zfile_is_directory(dir); - zfile_destroy(&dir); - return ret; -} - static const char *pythonactorcapabilities = "capabilities\n" " data\n" @@ -179,6 +142,105 @@ static const char *pythonactorcapabilities = //TODO: Perhaps add NatNet output type so we can filter the data multiple times... " type = \"OSC\"\n"; +static PyObject * +s_py_zosc_tuple(pythonactor_t *self, zosc_t *oscmsg) +{ + assert(self); + assert(oscmsg); + const char *format = zosc_format(oscmsg); + + PyObject *rettuple = PyTuple_New((Py_ssize_t) strlen(format) ); + + char type = '0'; + Py_ssize_t pos = 0; + const void *data = zosc_first(oscmsg, &type); + while(data) + { + PyObject *o = NULL; + switch (type) + { + case('i'): + { + int32_t val = 9; + int rc = zosc_pop_int32(oscmsg, &val); + assert(rc == 0); + o = PyLong_FromLong((long)val); + break; + } + case('h'): + { + int64_t val = 0; + int rc = zosc_pop_int64(oscmsg, &val); + assert(rc == 0); + o = PyLong_FromLong((long)val); + break; + } + case('f'): + { + float flt_v = 0.f; + int rc = zosc_pop_float(oscmsg, &flt_v); + assert(rc == 0); + o = PyFloat_FromDouble((double)flt_v); + break; + } + case 'd': + { + double dbl_v = 0.; + int rc = zosc_pop_double(oscmsg, &dbl_v); + assert(rc == 0); + o = PyFloat_FromDouble(dbl_v); + break; + } + case 's': + { + char *str; + int rc = zosc_pop_string(oscmsg, &str); + assert(rc == 0); + o = PyUnicode_Decode(str, strlen(str), "ascii", "ignore"); + zstr_free(&str); + break; + } + case 'c': + { + char chr; + int rc = zosc_pop_char(oscmsg, &chr); + assert(rc == 0); + o = Py_BuildValue("C", chr); + break; + } + case 'F': + case 'T': + { + bool bl; + int rc = zosc_pop_bool(oscmsg, &bl); + assert(rc == 0); + if (bl) + o = Py_True; + else + o = Py_False; + Py_INCREF(o); + break; + } + default: + assert(0); + } + + if (o == NULL && PyErr_Occurred()) + { + PyErr_Print(); + } + else + { + int rc = PyTuple_SetItem(rettuple, pos, o); + assert(rc == 0); + } + + data = zosc_next(oscmsg, &type); + pos++; + } + return rettuple; +} + void s_py_set_timeout(pythonactor_t *self, sphactor_event_t *ev) { @@ -313,214 +375,6 @@ s_pythonactor_set_file(pythonactor_t *self, const char *filename) return NULL; } -static zosc_t * -s_py_zosc(PyObject *pAddress, PyObject *pData) -{ - assert( PyUnicode_Check(pAddress) ); - assert( PyList_Check(pData) ); - PyObject *stringbytes = PyUnicode_AsASCIIString(pAddress); - zosc_t *ret = zosc_new( PyBytes_AsString( stringbytes) ); - Py_DECREF(stringbytes); - - // iterate - for ( Py_ssize_t i=0;iactor = actor; + this->title = sphactor_ask_actor_type(actor); + this->capabilities = zconfig_dup(sphactor_capability(actor)); + this->pos.x = sphactor_position_x(actor); + this->pos.y = sphactor_position_y(actor); + + // retrieve in-/output sockets + if (this->capabilities) + { + zconfig_t *insocket = zconfig_locate( this->capabilities, "inputs/input"); + if ( insocket != nullptr ) + { + zconfig_t *type = zconfig_locate(insocket, "type"); + assert(type); + + char* typeStr = zconfig_value(type); + if ( streq(typeStr, "OSC")) { + input_slots.push_back({ "OSC", ActorSlotOSC }); + } + else if ( streq(typeStr, "NatNet")) { + input_slots.push_back({ "NatNet", ActorSlotNatNet }); + } + else if ( streq(typeStr, "Any")) { + input_slots.push_back({ "Any", ActorSlotAny }); + } + else { + zsys_error("Unsupported input type: %s", typeStr); + } + } + + zconfig_t *outsocket = zconfig_locate( this->capabilities, "outputs/output"); + + if ( outsocket != nullptr ) + { + zconfig_t *type = zconfig_locate(outsocket, "type"); + assert(type); + + char* typeStr = zconfig_value(type); + if ( streq(typeStr, "OSC")) { + output_slots.push_back({ "OSC", ActorSlotOSC }); + } + else if ( streq(typeStr, "NatNet")) { + output_slots.push_back({ "NatNet", ActorSlotNatNet }); + } + else if ( streq(typeStr, "Any")) { + output_slots.push_back({ "Any", ActorSlotAny }); + } + else { + zsys_error("Unsupported output type: %s", typeStr); + } + } + } + else + { + input_slots.push_back({ "OSC", ActorSlotOSC }); + output_slots.push_back({ "OSC", ActorSlotOSC }); + } + //ParseConnections(); + //InitializeCapabilities(); //already done by sph_stage? +} + +ActorContainer::~ActorContainer() { + zconfig_destroy(&this->capabilities); +} + +void ActorContainer::ParseConnections() { + if ( this->capabilities == NULL ) return; + + // get actor's connections + zlist_t *conn = sphactor_connections( this->actor ); + for ( char *connection = (char *)zlist_first(conn); connection != nullptr; connection = (char *)zlist_next(conn)) + { + Connection new_connection; + new_connection.input_node = this; + new_connection.output_node = FindActorContainerByEndpoint(connection); + assert(new_connection.input_node); + assert(new_connection.output_node); + ((ActorContainer*) new_connection.input_node)->connections.push_back(new_connection); + ((ActorContainer*) new_connection.output_node)->connections.push_back(new_connection); + } +} + +ActorContainer * +ActorContainer::FindActorContainerByEndpoint(const char *endpoint) +{ + return NULL; +} + +void +ActorContainer::InitializeCapabilities() { + if ( this->capabilities == NULL ) return; + + zconfig_t *root = zconfig_locate(this->capabilities, "capabilities"); + if ( root == NULL ) return; + + zconfig_t *data = zconfig_locate(root, "data"); + while( data != NULL ) { + HandleAPICalls(data); + data = zconfig_next(data); + } +} + +void +ActorContainer::SetCapabilities(const char* capabilities ) { + this->capabilities = zconfig_str_load(capabilities); + InitializeCapabilities(); +} + +#define GZB_TOOLTIP_THRESHOLD 1.0f +void +ActorContainer::RenderTooltip(const char *help) +{ + if ( strlen(help) ) + { + if (ImGui::IsItemHovered() && GImGui->HoveredIdTimer > GZB_TOOLTIP_THRESHOLD ) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 24.0f); + ImGui::TextUnformatted(help); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + } +} + +void +ActorContainer::HandleAPICalls(zconfig_t * data) { + zconfig_t *zapic = zconfig_locate(data, "api_call"); + if ( zapic ) { + zconfig_t *zapiv = zconfig_locate(data, "api_value"); + zconfig_t *zvalue = zconfig_locate(data, "value"); + + if (zapiv) + { + char * zapivStr = zconfig_value(zapiv); + char type = zapivStr[0]; + switch( type ) { + case 'b': { + char *buf = new char[4]; + const char *zvalueStr = zconfig_value(zvalue); + strcpy(buf, zvalueStr); + sphactor_ask_api(this->actor, zconfig_value(zapic), zapivStr, zvalueStr); + //SendAPI(zapic, zapiv, zvalue, &buf); + zstr_free(&buf); + } break; + case 'i': { + int ival; + ReadInt(&ival, zvalue); + sphactor_ask_api(this->actor, zconfig_value(zapic), zapivStr, itoa(ival)); + } break; + case 'f': { + float fval; + ReadFloat(&fval, zvalue); + //SendAPI(zapic, zapiv, zvalue, &fval); + sphactor_ask_api(this->actor, zconfig_value(zapic), zapivStr, ftoa(fval)); + } break; + case 's': { + const char *zvalueStr = zconfig_value(zvalue); + //SendAPI(zapic, zapiv, zvalue, const_cast(&zvalueStr)); + sphactor_ask_api(this->actor, zconfig_value(zapic), zapivStr, zvalueStr); + } break; + } + } + else if (zvalue) { + //assume string + //int ival; + //ReadInt(&ival, zvalue); + //zsock_send( sphactor_socket(this->actor), "si", zconfig_value(zapic), ival); + sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapic), zconfig_value(zvalue)); + } + } +} + +template +void +ActorContainer::SendAPI(zconfig_t *zapic, zconfig_t *zapiv, zconfig_t *zvalue, T * value) { + if ( !zapic ) return; + + if (zapiv) + { + std::string pic = "s"; + pic += zconfig_value(zapiv); + //zsys_info("Sending: %s", pic.c_str()); + //sphactor_ask_api( this->actor, pic.c_str(), zconfig_value(zapic), *value); + zsock_send( sphactor_socket(this->actor), pic.c_str(), zconfig_value(zapic), *value); + } + else + //sphactor_ask_api( this->actor, "ss", zconfig_value(zapic), *value); + zsock_send( sphactor_socket(this->actor), "si", zconfig_value(zapic), *value); +} + + +void +ActorContainer::Render(float deltaTime) { + //loop through each data element in capabilities + if ( this->capabilities == NULL ) return; + + zconfig_t *root = zconfig_locate(this->capabilities, "capabilities"); + if ( root == NULL ) return; + + zconfig_t *data = zconfig_locate(root, "data"); + while( data != NULL ) { + zconfig_t *name = zconfig_locate(data, "name"); + zconfig_t *type = zconfig_locate(data, "type"); + assert(name); + assert(type); + + char* nameStr = zconfig_value(name); + char* typeStr = zconfig_value(type); + if ( streq(typeStr, "int")) { + RenderInt( nameStr, data ); + } + else if ( streq(typeStr, "slider")) { + RenderSlider( nameStr, data ); + } + else if ( streq(typeStr, "float")) { + RenderFloat( nameStr, data ); + } + else if ( streq(typeStr, "string")) { + RenderString(nameStr, data); + } + else if ( streq(typeStr, "bool")) { + RenderBool( nameStr, data ); + } + else if ( streq(typeStr, "filename")) { + RenderFilename( nameStr, data ); + } + else if ( streq(typeStr, "mediacontrol")) { + RenderMediacontrol( nameStr, data ); + } + else if ( streq(typeStr, "list")) { + RenderMultilineString( nameStr, data ); + } + else if ( streq(typeStr, "trigger")) { + RenderTrigger( nameStr, data ); + } + + data = zconfig_next(data); + } + RenderCustomReport(); +} + +void +ActorContainer::RenderCustomReport() { + const int LABEL_WIDTH = 25; + const int VALUE_WIDTH = 50; + + sphactor_report_t * report = sphactor_report(actor); + lastActive = sphactor_report_send_time(report); + + assert(report); + zosc_t * customData = sphactor_report_custom(report); + if ( customData ) { + const char* address = zosc_address(customData); + //ImGui::Text("%s", address); + + char type = '0'; + const void *data = zosc_first(customData, &type); + bool name = true; + char* nameStr = nullptr; + while( data ) { + if ( name ) { + //expecting a name string for each piece of data + assert(type == 's'); + + int rc = zosc_pop_string(customData, &nameStr); + if (rc == 0 ) + { + ImGui::BeginGroup(); + if (!streq(nameStr, "lastActive")) { + ImGui::SetNextItemWidth(LABEL_WIDTH); + ImGui::Text("%s:", nameStr); + ImGui::SameLine(); + ImGui::SetNextItemWidth(VALUE_WIDTH); + } + } + } + else { + switch(type) { + case 's': { + char* value; + int rc = zosc_pop_string(customData, &value); + if( rc == 0) + { + ImGui::Text("%s", value); + zstr_free(&value); + } + } break; + case 'c': { + char value; + zosc_pop_char(customData, &value); + ImGui::Text("%c", value); + } break; + case 'i': { + int32_t value; + zosc_pop_int32(customData, &value); + ImGui::Text("%i", value); + } break; + case 'h': { + int64_t value; + zosc_pop_int64(customData, &value); + + if (streq(nameStr, "lastActive")) { + // render something to indicate the actor is currently active + lastActive = (int64_t)value; + int64_t diff = zclock_mono() - lastActive; + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImNodes::CanvasState* canvas = ImNodes::GetCurrentCanvas(); + if (diff < 100) { + draw_list->AddCircleFilled(canvas->Offset + pos * canvas->Zoom + ImVec2(5,5) * canvas->Zoom, 5 * canvas->Zoom , ImColor(0, 255, 0), 4); + } + else { + draw_list->AddCircleFilled(canvas->Offset + pos * canvas->Zoom + ImVec2(5, 5) * canvas->Zoom, 5 * canvas->Zoom, ImColor(127, 127, 127), 4); + } + } + else { + ImGui::Text("%li", value); + } + } break; + case 'f': { + float value; + zosc_pop_float(customData, &value); + ImGui::Text("%f", value); + } break; + case 'd': { + double value; + zosc_pop_double(customData, &value); + ImGui::Text("%f", value); + } break; + case 'F': { + ImGui::Text("FALSE"); + } break; + case 'T': { + ImGui::Text("TRUE"); + } break; + } + + ImGui::EndGroup(); + + //free the nameStr here so we can use it up to this point + zstr_free(&nameStr); + } + + //flip expecting name or value + name = !name; + data = zosc_next(customData, &type); + } + } +} + +void +ActorContainer::SolvePadding( int* position ) { + if ( *position % 4 != 0 ) { + *position += 4 - *position % 4; + } +} + +template +void +ActorContainer::RenderValue(T *value, const byte * bytes, int *position) { + memcpy(value, bytes+*position, sizeof(T)); + *position += sizeof(T); +} + +// Make the UI compact because there are so many fields +void +ActorContainer::PushStyleCompact() +{ + ImGuiStyle& style = ImGui::GetStyle(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, (float)(int)(style.FramePadding.y * 0.60f))); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, (float)(int)(style.ItemSpacing.y * 0.60f))); +} + +void +ActorContainer::PopStyleCompact() +{ + ImGui::PopStyleVar(2); +} + +void +ActorContainer::RenderList(const char *name, zconfig_t *data) +{ + ImVec2 size = ImVec2(300,100); // what's a reasonable size? + ImGui::BeginChild("##", size, false, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoSavedSettings ); + // The list capability expects a tree of zconfig data. + // Every node is a column + zconfig_t *namec = zconfig_locate(data, "name"); + int colcount = 0; + zconfig_t *columnc = zconfig_child(data); + while (columnc) + { + // we only count nodes not leaves + if ( zconfig_child(columnc) ) + { + colcount++; + } + columnc = zconfig_next(columnc); + } + assert(colcount); + + // By default, if we don't enable ScrollX the sizing policy for each columns is "Stretch" + // Each columns maintain a sizing weight, and they will occupy all available width. + static ImGuiTableFlags flags = ImGuiTableFlags_Resizable ;//ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ContextMenuInBody | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY; + if (ImGui::BeginTable(zconfig_value(namec), colcount, flags)) + { + unsigned int dirty = 0; //track changes bitwise + columnc = zconfig_child(data); + // generate table header + while (columnc) + { + if ( zconfig_child(columnc) ) + { + char *name = zconfig_name(columnc); + ImGui::TableSetupColumn(name, ImGuiTableColumnFlags_None); + } + columnc = zconfig_next(columnc); + } + ImGui::TableHeadersRow(); + // generate entries + + // always end with an empty entry row + char *values[10] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL}; // capture input + columnc = zconfig_child(data); + ImGui::TableNextRow(); + int colidx = 0; + while (columnc) + { + zconfig_t *rowc = zconfig_child(columnc); + if ( rowc ) + { + char *name = zconfig_name(columnc); + ImGui::TableSetColumnIndex(colidx); + zconfig_t *typec = zconfig_locate(columnc, "type"); + char *type = zconfig_value(typec); + ImGui::PushItemWidth( -1.0f ); + if ( streq(type, "intbla") ) + { + int value = 6200; + if ( ImGui::InputInt("##", &value, 1, 100,ImGuiInputTextFlags_EnterReturnsTrue ) ) + { + dirty = (1 << colidx) | colidx; + } + } + else + { + static char bla[256] = ""; + char label[256] = ""; + snprintf(label, 256, "##%s", name); + if ( ImGui::InputText(&label[0], &bla[0], 256, ImGuiInputTextFlags_None) ) + { + dirty = (1 << colidx) | colidx; + } + } + ImGui::PopItemWidth(); + colidx++; + } + columnc = zconfig_next(columnc); + } + ImGui::EndTable(); + + if (dirty) + { + // get current value, append new value if all input fields contain data + } + } + //PushStyleCompact(); + //ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags, ImGuiTableFlags_Resizable); + //ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", &flags, ImGuiTableFlags_BordersV); + //ImGui::SameLine(); HelpMarker("Using the _Resizable flag automatically enables the _BordersInnerV flag as well, this is why the resize borders are still showing when unchecking this."); + //PopStyleCompact(); + + + /* + if (ImGui::BeginTable("table1", 3, flags)) + { + ImGui::TableSetupColumn("name", ImGuiTableColumnFlags_None, 80.f); + ImGui::TableSetupColumn("ip", ImGuiTableColumnFlags_None, 80.f); + ImGui::TableSetupColumn("port", ImGuiTableColumnFlags_None, 60.f); + ImGui::TableHeadersRow(); + for (int row = 0; row < 3; row++) + { + ImGui::TableNextRow(); + + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + static int port = 0; + static char bla[256] = ""; + if (column == 2) + ImGui::InputInt("##port", &port); + else + ImGui::InputText("##Hello", &bla[0], 256); + } + } + ImGui::EndTable(); + }*/ + ImGui::EndChild(); +} + +void +ActorContainer::RenderMediacontrol(const char* name, zconfig_t *data) +{ + if ( ImGui::Button(ICON_FA_BACKWARD) ) + //sphactor_ask_api(this->actor, "BACK", "", NULL); + zstr_send(sphactor_socket(this->actor), "BACK"); + ImGui::SameLine(); + if ( ImGui::Button(ICON_FA_PLAY) ) + zstr_send(sphactor_socket(this->actor), "PLAY"); + ImGui::SameLine(); + if ( ImGui::Button(ICON_FA_PAUSE) ) + zstr_send(sphactor_socket(this->actor), "PAUSE"); +} + +void +ActorContainer::RenderFilename(const char* name, zconfig_t *data) { + int max = MAX_STR_DEFAULT; + + zconfig_t * zvalue = zconfig_locate(data, "value"); + zconfig_t * ztype_hint = zconfig_locate(data, "type_hint"); + zconfig_t * zapic = zconfig_locate(data, "api_call"); + zconfig_t * zapiv = zconfig_locate(data, "api_value"); + zconfig_t * zoptions = zconfig_locate(data, "options"); + zconfig_t * zvalidfiles = zconfig_locate(data, "valid_files"); + assert(zvalue); + + zconfig_t * zmax = zconfig_locate(data, "max"); + + ReadInt( &max, zmax ); + + char buf[MAX_STR_DEFAULT]; + const char* zvalueStr = zconfig_value(zvalue); + strcpy(buf, zvalueStr); + char *p = &buf[0]; + bool file_selected = false; + + const char* zoptStr = "r"; + if ( zoptions != nullptr ) { + zoptStr = zconfig_value(zoptions); + } + const char *valid_files = zvalidfiles == nullptr ? "*.*" : zconfig_value(zvalidfiles); + + ImGui::SetNextItemWidth(180); + if ( ImGui::InputTextWithHint("", name, buf, max, ImGuiInputTextFlags_EnterReturnsTrue ) ) { + zconfig_set_value(zvalue, "%s", buf); + sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), p ); + //SendAPI(zapic, zapiv, zvalue, &(p)); + } + ImGui::SameLine(); + if ( ImGui::Button( ICON_FA_FOLDER_OPEN ) ) + file_selected = true; + + if ( file_selected ) + ImGui::OpenPopup("Actor Open File"); + + if ( actor_file_dialog.showFileDialog("Actor Open File", + streq(zoptStr, "rw" ) ? imgui_addons::ImGuiFileBrowser::DialogMode::SAVE : imgui_addons::ImGuiFileBrowser::DialogMode::OPEN, + ImVec2(700, 310), + valid_files) ) // TODO: perhaps add type hint for extensions? + { + char *path = convert_to_relative_to_wd(actor_file_dialog.selected_path.c_str()); + zconfig_set_value(zvalue, "%s", path ); + strcpy(buf, path); + //SendAPI(zapic, zapiv, zvalue, &(p) ); + zstr_free(&path); + sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), p ); + } + + zconfig_t *help = zconfig_locate(data, "help"); + const char *helpv = "Load a file"; + if (help) + { + helpv = zconfig_value(help); + } + RenderTooltip(helpv); + + // handle options + zconfig_t *opts = zconfig_locate(data, "options"); + // check if there are options + if (opts) + { + char *optsv = zconfig_value(opts); + // check if is an editable textfile + if (optsv && strchr(optsv, 't') != NULL && strchr(optsv, 'e') != NULL) + { + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_EDIT)) + { + zconfig_t* zvalue = zconfig_locate(data, "value"); + const char* zvalueStr = zconfig_value(zvalue); + if (strlen(zvalueStr) && zsys_file_exists (zvalueStr) ) + { + OpenTextEditor(zvalueStr); // could own the f pointer + } + else + zsys_error("no valid file to load: %s", zvalueStr); + } + RenderTooltip("Edit file in texteditor"); + } + if (optsv && strchr(optsv, 'c') != NULL && strchr(optsv, 't') != NULL) // we can create a new text file + { + // create new text files + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FILE)) + ImGui::OpenPopup("Create File?"); + + RenderTooltip("Create new file"); + // Always center this window when appearing + ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + + if (ImGui::BeginPopupModal("Create File?", NULL, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::Text("Enter a filename:"); + ImGui::Separator(); + static char fnamebuf[PATH_MAX] = ""; + bool createFile = false; + if ( ImGui::InputText("filename", fnamebuf, PATH_MAX, ImGuiInputTextFlags_EnterReturnsTrue ) ) + { + if ( strlen(fnamebuf) ) + { + createFile = true; + } + ImGui::CloseCurrentPopup(); + } + char feedback[256] = "Enter a valid filename!"; + if (strlen(fnamebuf)) + { + // check if this filename conflicts + char path[PATH_MAX]; + getcwd(path, PATH_MAX); + char fullpath[PATH_MAX]; + sprintf (fullpath, "%s/%s", path, fnamebuf); + //zsys_info("filemode %i", zsys_file_mode(fullpath)); + int mode = zsys_file_mode(fullpath); + if ( mode != -1 ) + { + if ( S_ISDIR(mode) ) + sprintf( feedback, "Filename \'%s\' conflicts with an existing directory!", fnamebuf); + else + sprintf( feedback, "File %s exists and will be overwritten if continued", fnamebuf); + } + } + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 24.0f); + ImGui::Text("%s", feedback); + ImGui::PopTextWrapPos(); + + if (ImGui::Button("OK", ImVec2(120, 0))) + { + if ( strlen(fnamebuf) ) + { + createFile = true; + } + ImGui::CloseCurrentPopup(); + } + ImGui::SetItemDefaultFocus(); + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(120, 0))) + { + createFile = false; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + + if (createFile && strlen(fnamebuf)) + { + zchunk_t *file_data = NULL; + zconfig_t *tmpl = zconfig_locate(data, "file_template"); + if (tmpl) + { + char *tmplpath = zconfig_value(tmpl); + char fullpath[PATH_MAX]; + snprintf(fullpath, PATH_MAX-1, "%s/%s", GZB_GLOBAL.RESOURCESPATH, tmplpath); + if ( zsys_file_exists(fullpath) ) + { + zfile_t *tmplf = zfile_new(NULL, fullpath); + assert(tmplf); + int rc = zfile_input(tmplf); + assert(rc == 0); + file_data = zfile_read(tmplf, zsys_file_size (tmplpath), 0); + assert(file_data); + zfile_destroy(&tmplf); + } + else + { + file_data = zchunk_new ("\n", 1); + } + } + else + { + file_data = zchunk_new ("\n", 1); + } + // create the new textfile + char path[PATH_MAX]; + getcwd(path, PATH_MAX); + zfile_t *txtfile = zfile_new(path, fnamebuf); + assert(txtfile); + int rc = zfile_output(txtfile); + assert(rc == 0); + rc = zfile_write (txtfile, file_data, 0); + assert (rc == 0); + zchunk_destroy (&file_data); + zfile_close(txtfile); + //zfile_destroy(&txtfile); + zconfig_set_value(zvalue, "%s", fnamebuf); + sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), p ); + + OpenTextEditor(zfile_filename(txtfile, NULL)); // could own the f pointer + } + } + } + } +} + +void +ActorContainer::RenderBool(const char* name, zconfig_t *data) { + bool value; + + zconfig_t * zvalue = zconfig_locate(data, "value"); + zconfig_t * zapic = zconfig_locate(data, "api_call"); + zconfig_t * zapiv = zconfig_locate(data, "api_value"); + assert(zvalue); + const char *apiv; + // set default call value + if (zapiv == nullptr) + apiv = "s"; + else + apiv = zconfig_value(zapiv); + + ReadBool( &value, zvalue); + + ImGui::SetNextItemWidth(100); + if ( ImGui::Checkbox( name, &value ) ) { + zconfig_set_value(zvalue, "%s", value ? "True" : "False"); + + char *buf = new char[6]; + const char *zvalueStr = zconfig_value(zvalue); + strcpy(buf, zvalueStr); + //SendAPI(zapic, zapiv, zvalue, &buf); + sphactor_ask_api(this->actor, zconfig_value(zapic), apiv, buf ); + zstr_free(&buf); + } + zconfig_t *help = zconfig_locate(data, "help"); + if (help) + { + char *helpv = zconfig_value(help); + RenderTooltip(helpv); + } +} + +void +ActorContainer::RenderInt(const char* name, zconfig_t *data) { + int value; + int min = 0, max = 0, step = 0; + + zconfig_t * zvalue = zconfig_locate(data, "value"); + zconfig_t * zapic = zconfig_locate(data, "api_call"); + zconfig_t * zapiv = zconfig_locate(data, "api_value"); + assert(zvalue); + + zconfig_t * zmin = zconfig_locate(data, "min"); + zconfig_t * zmax = zconfig_locate(data, "max"); + zconfig_t * zstep = zconfig_locate(data, "step"); + + ReadInt( &value, zvalue); + ReadInt( &min, zmin); + ReadInt( &max, zmax); + ReadInt( &step, zstep); + + ImGui::SetNextItemWidth(100); + + if ( ImGui::InputInt( name, &value, step, 100) ) { + if ( zmin ) { + if (value < min) value = min; + } + if ( zmax ) { + if ( value > max ) value = max; + } + + zconfig_set_value(zvalue, "%i", value); + //SendAPI(zapic, zapiv, zvalue, &value); + sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), itoa(value) ); + } + zconfig_t *help = zconfig_locate(data, "help"); + if (help) + { + char *helpv = zconfig_value(help); + RenderTooltip(helpv); + } +} + +void +ActorContainer::RenderSlider(const char* name, zconfig_t *data) { + + zconfig_t * zvalue = zconfig_locate(data, "value"); + zconfig_t * zapic = zconfig_locate(data, "api_call"); + zconfig_t * zapiv = zconfig_locate(data, "api_value"); + assert(zvalue); + + zconfig_t * zmin = zconfig_locate(data, "min"); + zconfig_t * zmax = zconfig_locate(data, "max"); + zconfig_t * zstep = zconfig_locate(data, "step"); + + ImGui::SetNextItemWidth(250); + ImGui::BeginGroup(); + ImGui::SetNextItemWidth(200); + + if ( streq(zconfig_value(zapiv), "f") ) + { + float value; + float min = 0., max = 0., step = 0.; + ReadFloat( &value, zvalue); + ReadFloat( &min, zmin); + ReadFloat( &max, zmax); + ReadFloat( &step, zstep); + + if ( ImGui::SliderFloat("", &value, min, max) ) { + zconfig_set_value(zvalue, "%f", value); + sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), ftoa(value) ); + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(35); + if ( ImGui::InputFloat("##min", &min, step, 0.f, "%.2f", ImGuiInputTextFlags_EnterReturnsTrue ) ) + { + zconfig_set_value(zmin, "%f", min); + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(35); + if ( ImGui::InputFloat( "##max", &max, step, 0.f, "%.2f", ImGuiInputTextFlags_EnterReturnsTrue ) ) + { + zconfig_set_value(zmax, "%f", max); + } + } + else + { + int value; + int min = 0, max = 0, step = 0; + ReadInt( &value, zvalue); + ReadInt( &min, zmin); + ReadInt( &max, zmax); + ReadInt( &step, zstep); + + if ( ImGui::SliderInt( "", &value, min, max) ) { + zconfig_set_value(zvalue, "%i", value); + sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), itoa(value) ); + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(30); + if ( ImGui::InputInt( "##min", &min, 0, 0,ImGuiInputTextFlags_EnterReturnsTrue ) ) + { + zconfig_set_value(zmin, "%i", min); + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(30); + if ( ImGui::InputInt( "##max", &max, 0, 0,ImGuiInputTextFlags_EnterReturnsTrue ) ) + { + zconfig_set_value(zmax, "%i", max); + } + } + + ImGui::EndGroup(); + zconfig_t *help = zconfig_locate(data, "help"); + if (help) + { + char *helpv = zconfig_value(help); + RenderTooltip(helpv); + } +} + +void +ActorContainer::RenderFloat(const char* name, zconfig_t *data) { + float value; + float min = 0, max = 0, step = 0; + + zconfig_t * zvalue = zconfig_locate(data, "value"); + zconfig_t * zapic = zconfig_locate(data, "api_call"); + zconfig_t * zapiv = zconfig_locate(data, "api_value"); + assert(zvalue); + + zconfig_t * zmin = zconfig_locate(data, "min"); + zconfig_t * zmax = zconfig_locate(data, "max"); + zconfig_t * zstep = zconfig_locate(data, "step"); + + ReadFloat( &value, zvalue); + ReadFloat( &min, zmin); + ReadFloat( &max, zmax); + ReadFloat( &step, zstep); + + ImGui::SetNextItemWidth(100); + if ( min != max ) { + if ( ImGui::SliderFloat( name, &value, min, max) ) { + zconfig_set_value(zvalue, "%f", value); + //SendAPI(zapic, zapiv, zvalue, &value); + sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), ftoa(value) ); + } + } + else { + if ( ImGui::InputFloat( name, &value, min, max) ) { + zconfig_set_value(zvalue, "%f", value); + //SendAPI(zapic, zapiv, zvalue, &value); + sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), ftoa(value) ); + } + } + zconfig_t *help = zconfig_locate(data, "help"); + if (help) + { + char *helpv = zconfig_value(help); + RenderTooltip(helpv); + } +} + +void +ActorContainer::RenderString(const char* name, zconfig_t *data) { + int max = MAX_STR_DEFAULT; + + zconfig_t * zvalue = zconfig_locate(data, "value"); + zconfig_t * zapic = zconfig_locate(data, "api_call"); + zconfig_t * zapiv = zconfig_locate(data, "api_value"); + assert(zvalue); + + zconfig_t * zmax = zconfig_locate(data, "max"); + + ReadInt( &max, zmax ); + + char buf[MAX_STR_DEFAULT]; + const char* zvalueStr = zconfig_value(zvalue); + strcpy(buf, zvalueStr); + char *p = &buf[0]; + + ImGui::SetNextItemWidth(200); + if ( ImGui::InputText(name, buf, max) ) { + zconfig_set_value(zvalue, "%s", buf); + sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), buf); + } + + zconfig_t *help = zconfig_locate(data, "help"); + if (help) + { + char *helpv = zconfig_value(help); + RenderTooltip(helpv); + } +} + +void +ActorContainer::RenderMultilineString(const char* name, zconfig_t *data) { + int max = MAX_STR_DEFAULT; + + zconfig_t * zvalue = zconfig_locate(data, "value"); + zconfig_t * zapic = zconfig_locate(data, "api_call"); + zconfig_t * zapiv = zconfig_locate(data, "api_value"); + assert(zvalue); + + zconfig_t * zmax = zconfig_locate(data, "max"); + + ReadInt( &max, zmax ); + + char buf[MAX_STR_DEFAULT]; + const char* zvalueStr = zconfig_value(zvalue); + strcpy(buf, zvalueStr); + s_replace_char(buf, ',', '\n'); // replace comma with newline if needed + char *p = &buf[0]; + + ImGui::SetNextItemWidth(200); + ImGui::InputTextMultiline("##source", p, max, ImVec2(0, ImGui::GetTextLineHeight() * 8), ImGuiInputTextFlags_EnterReturnsTrue); + if ( ImGui::IsItemEdited() || ImGui::IsItemDeactivatedAfterEdit()) + { + char *sendbuf = strdup(buf); + s_replace_char(sendbuf, '\n', ','); // We use ',' instead of newline since we can't save newlines in the stage save file + zconfig_set_value(zvalue, "%s", sendbuf); + sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), sendbuf); + zstr_free(&sendbuf); + } + + zconfig_t *help = zconfig_locate(data, "help"); + if (help) + { + char *helpv = zconfig_value(help); + RenderTooltip(helpv); + } +} + +void +ActorContainer::RenderTrigger(const char* name, zconfig_t *data) { + zconfig_t * zapic = zconfig_locate(data, "api_call"); + + ImGui::SetNextItemWidth(200); + if ( ImGui::Button(name) ) { + sphactor_ask_api(this->actor, zconfig_value(zapic), "", "" ); + } + zconfig_t *help = zconfig_locate(data, "help"); + if (help) + { + char *helpv = zconfig_value(help); + RenderTooltip(helpv); + } +} + +void +ActorContainer::ReadBool( bool *value, zconfig_t * data) { + if ( data != NULL ) { + *value = streq( zconfig_value(data), "True"); + } +} + +void +ActorContainer::ReadInt( int *value, zconfig_t * data) { + if ( data != NULL ) { + *value = atoi(zconfig_value(data)); + } +} + +void +ActorContainer::ReadFloat( float *value, zconfig_t * data) { + if ( data != NULL ) { + *value = atof(zconfig_value(data)); + } +} + +void +ActorContainer::DestroyActor() { + if ( actor ) + sphactor_destroy(&actor); +} + +void +ActorContainer::DeleteConnection(const Connection& connection) +{ + for (auto it = connections.begin(); it != connections.end(); ++it) + { + if (connection == *it) + { + connections.erase(it); + break; + } + } +} + +//TODO: Add custom report data to this? +void +ActorContainer::SerializeActorData(zconfig_t *section) { + zconfig_t *xpos = zconfig_new("xpos", section); + zconfig_set_value(xpos, "%f", pos.x); + + zconfig_t *ypos = zconfig_new("ypos", section); + zconfig_set_value(ypos, "%f", pos.y); + + // Parse current state of data capabilities + if ( this->capabilities ) { + zconfig_t *root = zconfig_locate(this->capabilities, "capabilities"); + if ( root ) { + zconfig_t *data = zconfig_locate(root, "data"); + while ( data ) { + zconfig_t *name = zconfig_locate(data,"name"); + zconfig_t *value = zconfig_locate(data,"value"); + + if (value) // only store if there's a value + { + char *nameStr = zconfig_value(name); + char *valueStr = zconfig_value(value); + + zconfig_t *stored = zconfig_new(nameStr, section); + zconfig_set_value(stored, "%s", valueStr); + } + + data = zconfig_next(data); + } + } + } +} + +void +ActorContainer::DeserializeActorData( ImVector *args, ImVector::iterator it) { + char* xpos = *it; + it++; + char* ypos = *it; + it++; + + if ( it != args->end()) { + if ( this->capabilities ) { + zconfig_t *root = zconfig_locate(this->capabilities, "capabilities"); + if ( root ) { + zconfig_t *data = zconfig_locate(root, "data"); + while ( data && it != args->end() ) { + zconfig_t *value = zconfig_locate(data,"value"); + if (value) + { + char* valueStr = *it; + zconfig_set_value(value, "%s", valueStr); + + HandleAPICalls(data); + it++; + } + data = zconfig_next(data); + } + } + } + } + + pos.x = atof(xpos); + pos.y = atof(ypos); + + free(xpos); + free(ypos); +} + +void +ActorContainer::OpenTextEditor(const char *filepath) +{ + gzb::TextEditorWindow *txtwin = NULL; + for ( auto w : gzb::App::getApp().text_editors ) + { + if ( w->associated_file == filepath ) + { + txtwin = w; + } + } + if (txtwin == NULL) + { + txtwin = new gzb::TextEditorWindow(filepath); + gzb::App::getApp().text_editors.push_back(txtwin); + } + else + { + txtwin->showing = true; + ImGuiWindow *win = ImGui::FindWindowByName(txtwin->window_name.c_str()); + ImGui::FocusWindow(win); + } +} + +};// namespace + diff --git a/app/ActorContainer.hpp b/app/ActorContainer.hpp new file mode 100644 index 00000000..8d934132 --- /dev/null +++ b/app/ActorContainer.hpp @@ -0,0 +1,122 @@ +#ifndef ACTORCONTAINER_HPP +#define ACTORCONTAINER_HPP +#ifndef IMGUI_DEFINE_MATH_OPERATORS +# define IMGUI_DEFINE_MATH_OPERATORS +#endif +#define GZB_TOOLTIP_THRESHOLD 1.0f +#define MAX_STR_DEFAULT 256 + +#include "fontawesome5.h" +#include "config.h" +#include "helpers.h" +#include "imgui.h" +#include "imgui_internal.h" +#include "libsphactor.h" +#include "ImNodes.h" +#include "ImNodesEz.h" +#include "ext/ImGui-Addons/FileBrowser/ImGuiFileBrowser.h" + +namespace gzb { +// actor file browser + +/// A structure defining a connection between two slots of two actors. +struct Connection +{ + /// `id` that was passed to BeginNode() of input node. + void* input_node = nullptr; + /// Descriptor of input slot. + const char* input_slot = nullptr; + /// `id` that was passed to BeginNode() of output node. + void* output_node = nullptr; + /// Descriptor of output slot. + const char* output_slot = nullptr; + + bool operator==(const Connection& other) const + { + return input_node == other.input_node && + input_slot == other.input_slot && + output_node == other.output_node && + output_slot == other.output_slot; + } + + bool operator!=(const Connection& other) const + { + return !operator ==(other); + } +}; + +enum GActorSlotTypes +{ + ActorSlotAny = 1, // ID can not be 0 + ActorSlotPosition, + ActorSlotRotation, + ActorSlotMatrix, + ActorSlotInt, + ActorSlotOSC, + ActorSlotNatNet +}; + +struct ActorContainer +{ + /// Title which will be displayed at the center-top of the actor. + const char* title = nullptr; + /// Flag indicating that actor is selected by the user. + bool selected = false; + /// Actor position on the canvas. + ImVec2 pos{}; + /// List of actor connections. + std::vector connections{}; + /// A list of input slots current actor has. + std::vector input_slots{}; + /// A list of output slots current actor has. + std::vector output_slots{}; + + /// Last received "lastActive" clock value + int64_t lastActive = 0; + + sphactor_t *actor; + zconfig_t *capabilities; + + ActorContainer(sphactor_t *actor); + ~ActorContainer(); + void ParseConnections(); + ActorContainer *FindActorContainerByEndpoint(const char *endpoint); + void InitializeCapabilities(); + void SetCapabilities(const char* capabilities ); + void RenderTooltip(const char *help); + void HandleAPICalls(zconfig_t * data); + template + void SendAPI(zconfig_t *zapic, zconfig_t *zapiv, zconfig_t *zvalue, T * value); + void Render(float deltaTime); + void RenderCustomReport(); + void RenderList(const char *name, zconfig_t *data); + void RenderMediacontrol(const char* name, zconfig_t *data); + void RenderFilename(const char* name, zconfig_t *data); + void RenderBool(const char* name, zconfig_t *data); + void RenderInt(const char* name, zconfig_t *data); + void RenderSlider(const char* name, zconfig_t *data); + void RenderFloat(const char* name, zconfig_t *data); + void RenderString(const char* name, zconfig_t *data); + void RenderMultilineString(const char* name, zconfig_t *data); + void RenderTrigger(const char* name, zconfig_t *data); + void ReadBool( bool *value, zconfig_t * data); + void ReadInt( int *value, zconfig_t * data); + void ReadFloat( float *value, zconfig_t * data); + + void DestroyActor(); + void DeleteConnection(const Connection& connection); + void SerializeActorData(zconfig_t *section); + void DeserializeActorData( ImVector *args, ImVector::iterator it); + + void OpenTextEditor(const char *filepath); + void SolvePadding( int* position ); + template + void RenderValue(T *value, const byte * bytes, int *position); + // Make the UI compact because there are so many fields + static void PushStyleCompact(); + static void PopStyleCompact(); + +}; + +} // namespace +#endif // ACTORCONTAINER_HPP diff --git a/app/App.hpp b/app/App.hpp index c399bc6b..8370d81f 100644 --- a/app/App.hpp +++ b/app/App.hpp @@ -1,14 +1,33 @@ #ifndef WINDOW_HPP #define WINDOW_HPP +#ifndef IMGUI_DEFINE_MATH_OPERATORS +# define IMGUI_DEFINE_MATH_OPERATORS +# define IM_VEC2_CLASS_EXTRA +#endif +#include "StageWindow.hpp" #include "TextEditorWindow.hpp" #include "AboutWindow.hpp" #include "LogWindow.hpp" #include "DemoWindow.hpp" +#include "imgui.h" +#include "imgui_internal.h" +#include "ext/ImGui-Addons/FileBrowser/ImGuiFileBrowser.h" #include +#include "fontawesome5.h" +#include "config.h" +#include "libsphactor.h" namespace gzb { + struct App { + ImGuiTextBuffer log_buffer; + std::vector text_editors; + AboutWindow about_win; + LogWindow log_win; + DemoWindow demo_win; + StageWindow stage_win; + static App& getApp() { static App app; return app; @@ -19,11 +38,202 @@ struct App delete w; }; - ImGuiTextBuffer log_buffer; - std::vector text_editors; - AboutWindow about_win; - LogWindow log_win; - DemoWindow demo_win; + int OnImGuiMenuBar() + { + static char* configFile = new char[64] { 0x0 }; + static int keyFocus = 0; + MenuAction action = MenuAction_None; + + if ( keyFocus > 0 ) + keyFocus--; + + ImGui::BeginMainMenuBar(); + + if ( ImGui::BeginMenu("File") ) { + if ( ImGui::MenuItem(ICON_FA_FOLDER_OPEN " Load") ) { + //TODO: support checking if changes were made + action = MenuAction_Load; + } + if ( ImGui::MenuItem(ICON_FA_SAVE " Save") ) { + action = MenuAction_Save; + } + if ( ImGui::MenuItem(ICON_FA_SAVE " Save As") ) { + action = MenuAction_SaveAs; + } + ImGui::Separator(); + ImGui::Separator(); + if ( ImGui::MenuItem(ICON_FA_WINDOW_CLOSE " Exit") ) { + //TODO: support checking if changes were made + action = MenuAction_Exit; + } + + ImGui::EndMenu(); + } + + if ( ImGui::BeginMenu("Stage") ) { + if ( ImGui::MenuItem(ICON_FA_TRASH_ALT " Clear") ) { + action = MenuAction_Clear; + } + ImGui::EndMenu(); + } + + if ( ImGui::BeginMenu("Tools") ) { + if ( ImGui::BeginMenu(ICON_FA_EDIT " Text Editor") ) + { + if ( ImGui::MenuItem(ICON_FA_FOLDER_OPEN " New editor") ) + { + gzb::App::getApp().text_editors.push_back(new gzb::TextEditorWindow()); + } + for (auto w : gzb::App::getApp().text_editors ) + { + std::string title = w->window_name + " " + ICON_FA_EYE; + if ( ImGui::MenuItem(w->window_name.c_str(), NULL, w->showing, true ) ) + w->showing = !w->showing; + } + ImGui::EndMenu(); + } + if ( ImGui::MenuItem(ICON_FA_TERMINAL " Toggle Log Console") ) { + gzb::App::getApp().log_win.showing = !gzb::App::getApp().log_win.showing; + } +#ifdef HAVE_IMGUI_DEMO + if ( ImGui::MenuItem(ICON_FA_CARAVAN " Toggle Demo") ) { + gzb::App::getApp().demo_win.showing = !gzb::App::getApp().demo_win.showing; + } +#endif + if ( ImGui::MenuItem(ICON_FA_INFO " Toggle About") ) { + gzb::App::getApp().about_win.showing = !gzb::App::getApp().about_win.showing; + } + + ImGui::EndMenu(); + } + + //TODO: Display stage status (new, loaded, changed) + ImGui::Separator(); + char path[PATH_MAX]; + getcwd(path, PATH_MAX); + + ImGui::Separator(); + ImGui::TextColored( ImVec4(.3,.5,.3,1), ICON_FA_FOLDER ": %s", path); + + if ( ImGui::IsItemHovered() ) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 24.0f); + ImGui::TextUnformatted("Current working directory, double click to open"); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + + if ( ImGui::IsMouseDoubleClicked(0) ) + { + char cmd[PATH_MAX]; +#ifdef __WINDOWS__ + snprintf(cmd, PATH_MAX, "start explorer %s", path); + //snprintf(cmd, PATH_MAX, "explorer.exe %s", path); + //STARTUPINFO startupInfo = {0}; + //startupInfo.cb = sizeof(startupInfo); + //PROCESS_INFORMATION processInformation; + //CreateProcess(NULL, cmd, NULL, NULL, false, 0, NULL, NULL, &startupInfo, &processInformation); +#elif defined __UTYPE_LINUX + snprintf(cmd, PATH_MAX, "xdg-open %s &", path); +#else + snprintf(cmd, PATH_MAX, "open %s", path); +#endif + system(cmd); + } + } + + ImGui::Separator(); + + + if ( streq( stage_win.editing_file.c_str(), "" ) ) { + ImGui::TextColored( ImVec4(.7,.9,.7,1), ICON_FA_EDIT ": *Unsaved Stage*"); + } + else { + ImGui::TextColored( ImVec4(.7,.9,.7,1), ICON_FA_EDIT ": %s", stage_win.editing_file.c_str()); + } + + if (GZB_GLOBAL.UPDATE_AVAIL) + { + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 64); + ImGui::TextColored( ImVec4(.99,.9,.7,1), "update " ICON_FA_INFO_CIRCLE ); + if ( ImGui::IsItemHovered() ) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 24.0f); + ImGui::TextUnformatted("Gazebosc update available, click to download"); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + + if ( ImGui::IsMouseClicked(0) ) + { + char cmd[PATH_MAX]; + char url[60] = "https://pong.hku.nl/~buildbot/gazebosc/"; +#ifdef __WINDOWS__ + snprintf(cmd, PATH_MAX, "start %s", url); +#elif defined __UTYPE_LINUX + snprintf(cmd, PATH_MAX, "xdg-open %s &", url); +#else + snprintf(cmd, PATH_MAX, "open %s", url); +#endif + system(cmd); + } + } + } + + ImGui::EndMainMenuBar(); + + // Handle MenuActions + if ( action == MenuAction_Load) { + ImGui::OpenPopup("MenuAction_Load"); + keyFocus = 2; + } + else if ( action == MenuAction_Save ) { + if ( streq( stage_win.editing_file.c_str(), "" ) ) { + ImGui::OpenPopup("MenuAction_Save"); + } + else { + stage_win.Save(stage_win.editing_path.c_str()); + ImGui::CloseCurrentPopup(); + } + } + else if ( action == MenuAction_SaveAs ) { + ImGui::OpenPopup("MenuAction_Save"); + } + else if ( action == MenuAction_Clear ) { + stage_win.Clear(); + stage_win.Init(); + } + else if ( action == MenuAction_Exit ) { + return -1; + } + + /*if(file_dialog.showFileDialog("MenuAction_Load", imgui_addons::ImGuiFileBrowser::DialogMode::OPEN, ImVec2(700, 310), ".gzs")) + { + if (Load(file_dialog.selected_path.c_str())) { + editingFile = file_dialog.selected_fn; + editingPath = file_dialog.selected_path; + + moveCwdIfNeeded(); + } + } + + if(file_dialog.showFileDialog("MenuAction_Save", imgui_addons::ImGuiFileBrowser::DialogMode::SAVE, ImVec2(700, 310), ".gzs")) + { + if ( !Save(file_dialog.selected_path.c_str()) ) { + editingFile = ""; + editingPath = ""; + zsys_error("Saving file failed!"); + } + else { + editingFile = file_dialog.selected_fn; + editingPath = file_dialog.selected_path; + + moveCwdIfNeeded(); + } + }*/ + + return 0; + } }; static ImGuiTextBuffer& getLogBuffer(int fd=-1){ diff --git a/stage.cpp b/app/StageWindow.cpp similarity index 66% rename from stage.cpp rename to app/StageWindow.cpp index b49f0fe4..82794edd 100644 --- a/stage.cpp +++ b/app/StageWindow.cpp @@ -1,351 +1,224 @@ -// -// Copyright (c) 21021 Arnaud Loonstra/Aaron Oostdijk. -// -// 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 IMGUI_DEFINE_MATH_OPERATORS -# define IMGUI_DEFINE_MATH_OPERATORS -#endif -#include -#include #include -#include -#include -namespace fs = std::filesystem; -#include -#include "ImNodesEz.h" -#include "libsphactor.h" -#include "ActorContainer.h" -#include "actors/actors.h" -#include "app/App.hpp" -#include "ext/ImGui-Addons/FileBrowser/ImGuiFileBrowser.h" -#include "ext/ImGuiColorTextEdit/TextEditor.h" -#include "config.h" - -#include "ext/SDL/include/SDL_keyboard.h" -#include "ext/SDL/include/SDL_scancode.h" - -//enum for menu actions -enum MenuAction -{ - MenuAction_Save = 0, - MenuAction_Load, - MenuAction_Clear, - MenuAction_Exit, - MenuAction_SaveAs, - MenuAction_None -}; - -//enum for undo stack -enum UndoAction -{ - UndoAction_Create = 0, - UndoAction_Delete, - UndoAction_Disconnect, - UndoAction_Connect, - UndoAction_Invalid -}; - -struct UndoData { - UndoAction type = UndoAction_Invalid; - const char * title = nullptr; - const char * uuid = nullptr; - const char * endpoint = nullptr; - const char * input_slot = nullptr; - const char * output_slot = nullptr; - zconfig_t * sphactor_config = nullptr; - ImVec2 position; - - UndoData( UndoData * from ) { - type = from->type; - title = from->title ? strdup(from->title) : nullptr; - uuid = from->uuid ? strdup(from->uuid) : nullptr; - endpoint = from->endpoint ? strdup(from->endpoint) : nullptr; - input_slot = from->input_slot ? strdup(from->input_slot) : nullptr; - output_slot = from->output_slot ? strdup(from->output_slot) : nullptr; - if (from->sphactor_config != nullptr) - sphactor_config = zconfig_dup(from->sphactor_config); - position = from->position; - } - UndoData() {} -}; +#include "SDL.h" +#include "StageWindow.hpp" +#include "App.hpp" +#include "glm/glm/common.hpp" +#include "helpers.h" -std::stack undoStack; -std::stack redoStack; -// List of actor types and constructors -// Currently these are all internal dependencies, but we will need to create -// a "generic node" class for types defined outside of this codebase. -ImVector actor_types; -std::vector actors; -std::map max_actors_by_type; +namespace gzb { -sph_stage_t *stage = NULL; -static ziflist_t *nics = NULL; // list of network interfaces +inline ImU32 LerpImU32( ImU32 c1, ImU32 c2, int index, int total, float offset, bool doubleSided ) +{ + float t = (float)index / total; + t = glm::max(glm::min(t + offset, 1.0f), 0.0f); -// about window -static gzb::AboutWindow *about_win; + if ( doubleSided && t < .5f ) { + ImU32 col; + col = c1; + c1 = c2; + c2 = col; + } -// File browser instance -imgui_addons::ImGuiFileBrowser file_dialog; -std::string editingFile = ""; -std::string editingPath = ""; + unsigned char a1 = (c1 >> 24) & 0xff; + unsigned char a2 = (c2 >> 24) & 0xff; + unsigned char r1 = (c1 >> 16) & 0xff; + unsigned char r2 = (c2 >> 16) & 0xff; + unsigned char g1 = (c1 >> 8) & 0xff; + unsigned char g2 = (c2 >> 8) & 0xff; + unsigned char b1 = c1 & 0xff; + unsigned char b2 = c2 & 0xff; -bool Save( const char* configFile ); -bool Load( const char* configFile ); -void Clear(); -void Init(); -ActorContainer* Find( const char* endpoint ); + return (int) ((a2 - a1) * t + a1) << 24 | + (int) ((r2 - r1) * t + r1) << 16 | + (int) ((g2 - g1) * t + g1) << 8 | + (int) ((b2 - b1) * t + b1); +} -// undo stuff -void performUndo(UndoData &undo); -void RegisterCreateAction( ActorContainer * actor ); -void RegisterDeleteAction( ActorContainer * actor ); -void RegisterConnectAction(ActorContainer * in, ActorContainer * out, const char * input_slot, const char * output_slot); -void RegisterDisconnectAction(ActorContainer * in, ActorContainer * out, const char * input_slot, const char * output_slot); -void swapUndoType(UndoData * undo); - -// TODO: move this to something includable -static char* -s_basename(char const* path) +StageWindow::StageWindow() { - const char* s = strrchr(path, '/'); - if (!s) - return strdup(path); - else - return strdup(s + 1); + window_name = "Stage Window"; } -void moveCwdIfNeeded() { - // check if we are still in the working dir or if we should move to the new dir - char cwd[PATH_MAX]; - getcwd(cwd, PATH_MAX); - // editingPath not starting with cwd means we need to move to the new wd - if (editingPath.rfind(cwd, 0) != 0) { - // we're not in the current working dir! move files to editingPath - // moving if cwd was tmp dir (name contains _gzs_) - std::string cwds = std::string(cwd); - std::filesystem::path newcwds = std::filesystem::path(editingPath); - std::filesystem::path tmppath(GZB_GLOBAL.TMPPATH); - tmppath.append("_gzs_"); - if (cwds.rfind(tmppath.string(), 0) == 0) - { - // cwd is a tmp dir so we need to move files - // copy and delete for now - try - { - std::filesystem::copy(cwds, newcwds.parent_path(), std::filesystem::copy_options::overwrite_existing | std::filesystem::copy_options::recursive); - } - catch (std::exception& e) - { - zsys_error("Copy files to new working dir failed: %s", e.what()); - } - try - { - std::filesystem::remove_all(cwds); - } - catch (std::exception& e) - { - zsys_error("Removing old tmpdir failed: %s", e.what()); - } - } - else - { - // we copy recursively - // Recursively copies all files and folders from src to target and overwrites existing files in target. - try - { - std::filesystem::copy(cwds, newcwds.parent_path(), std::filesystem::copy_options::overwrite_existing | std::filesystem::copy_options::recursive); - } - catch (std::exception& e) - { - zsys_error("Copy files to new working dir failed: %s", e.what()); - } - } - std::filesystem::current_path(newcwds.parent_path()); -#ifdef PYTHON3_FOUND - std::error_code ec; // no exception - python_remove_path((const char *)cwd); - python_add_path(newcwds.parent_path().string().c_str()); -#endif - zsys_info("Working dir set to %s", newcwds.parent_path().c_str()); - } +StageWindow::~StageWindow() +{ } -#ifdef HAVE_IMGUI_DEMO -// ImGui Demo window for dev purposes -bool showDemo = false; -void ImGui::ShowDemoWindow(bool* p_open); -#endif - -void SelectableText(const char *buf) +void StageWindow::Init() { - ImGui::PushID(buf); - ImGui::PushItemWidth(ImGui::GetColumnWidth()); - ImGui::GetStyleColorVec4(ImGuiCol_TableRowBg); - ImGui::PushStyleColor(ImGuiCol_FrameBg, ImGui::GetStyleColorVec4(ImGuiCol_TableRowBg)); //otherwise it is colored - ImGui::InputText("", (char *)buf, strlen(buf), ImGuiInputTextFlags_ReadOnly); - ImGui::PopItemWidth(); - ImGui::PopStyleColor(); - ImGui::PopID(); + // initialise an empty stage with tmp working dir + assert(stage == NULL); + + // set a temporary random working dir for our stage + const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + char tmpdir[12] = "_gzs_xxxxxx"; + for (int i=5;i<12;i++) + { + int key = rand() % (int)(sizeof(charset)-1); + tmpdir[i] = charset[key]; + } + tmpdir[11] = 0; // null termination + std::filesystem::path tmppath(GZB_GLOBAL.TMPPATH); + tmppath.append(tmpdir); + std::error_code ec; + if ( ! std::filesystem::create_directory(tmppath, ec) ) + { + // TODO: what to do if creating the dir fails? + zsys_error("Creating tmp dir %s failed, this might mean trouble!!!", tmppath.string().c_str() ); + } + else + { + std::filesystem::current_path(tmppath); + zsys_info("Temporary stage dir is now at %s", tmppath.string().c_str()); + } + // clear active file as it needs saving to become a file first + editing_file = ""; + editing_path = ""; } -void UpdateRegisteredActorsCache() { - zhash_t *hash = sphactor_get_registered(); - zlist_t *list = zhash_keys(hash); - actor_types.clear(); +void StageWindow::Clear() +{ + undoStack = std::stack(); + redoStack = std::stack(); + + //delete all connections + for (auto it = actors.begin(); it != actors.end();) + { + ActorContainer* gActor = *it; - char* item = (char*)zlist_first(list); - while( item ) { - actor_types.push_back(item); - item = (char*)zlist_next(list); + for (auto& connection : gActor->connections) + { + //delete them once + if (connection.output_node == gActor) { + ((ActorContainer*) connection.input_node)->DeleteConnection(connection); + } + } + gActor->connections.clear(); + it++; } -} -void RegisterActors() { - // register stock actors - sph_stock_register_all(); - - sphactor_register("HTTPLaunchpod", &httplaunchpodactor_handler, zconfig_str_load(httplaunchpodactorcapabilities), &httplaunchpodactor_new_helper, NULL); // https://stackoverflow.com/questions/65957511/typedef-for-a-registering-a-constructor-function-in-c - sphactor_register( "OSC Output", OSCOutput::capabilities); - sphactor_register( "OSC Multi Output", OSCMultiOut::capabilities); - sphactor_register( "NatNet", NatNet::capabilities ); - sphactor_register( "NatNet2OSC", NatNet2OSC::capabilities ); - sphactor_register( "Midi2OSC", Midi2OSC::capabilities ); -#ifdef HAVE_OPENVR - sphactor_register("OpenVR", OpenVR::capabilities); -#endif - sphactor_register( "OSC Input", OSCInput::capabilities ); - sphactor_register("Record", Record::capabilities ); - sphactor_register( "ModPlayer", ModPlayerActor::capabilities ); - sphactor_register( "Process", ProcessActor::capabilities ); -#ifdef HAVE_OPENVR - sphactor_register( "DmxOut", DmxActor::capabilities ); - sphactor_register( "IntSlider", IntSlider::capabilities ); - sphactor_register( "FloatSlider", FloatSlider::capabilities ); -#endif + sph_stage_destroy(&stage); + // remove working dir if it's a temporary path + std::error_code ec; // no exception + std::filesystem::path cwd = std::filesystem::current_path(ec); #ifdef PYTHON3_FOUND - int rc = python_init(); - assert( rc == 0); - /* Check newer version: We should make this async as it slows startup" */ - PyGILState_STATE gstate; - gstate = PyGILState_Ensure(); - - PyObject *pUpdateBool = python_call_file_func("checkver", "check_github_newer_commit", "(s)", GIT_HASH); - if (pUpdateBool && PyObject_IsTrue(pUpdateBool)) - GZB_GLOBAL.UPDATE_AVAIL = true; - - PyGILState_Release(gstate); - /* End check newer version */ + python_remove_path(cwd.string().c_str()); #endif - //enforcable maximum actor counts - max_actors_by_type.insert(std::make_pair("NatNet", 1)); - max_actors_by_type.insert(std::make_pair("OpenVR", 1)); - - UpdateRegisteredActorsCache(); -} - -ActorContainer * CreateFromType( const char* typeStr, const char* uuidStr ) { - zuuid_t *uuid = zuuid_new(); - if ( uuidStr != NULL ) - zuuid_set_str(uuid, uuidStr); + // temporary change the working dir otherwise we cannot delete it on windows, Load or Init will reset it + std::filesystem::current_path(GZB_GLOBAL.TMPPATH); - sphactor_t *actor = sphactor_new_by_type(typeStr, uuidStr, uuid); - sphactor_ask_set_actor_type(actor, typeStr); - if ( stage == nullptr ) - stage = sph_stage_new("New Stage"); - sph_stage_add_actor(stage, actor); - return new ActorContainer(actor); -} + std::filesystem::path tmppath(GZB_GLOBAL.TMPPATH); + tmppath.append("_gzs_"); + if ( cwd.string().rfind(tmppath.string(), 0) == 0 ) + { + try + { + std::filesystem::remove_all(cwd); + } + catch (std::exception& e) + { + zsys_error("failed to remove working dir: %s", e.what()); + } + } + assert(stage == NULL); -int CountActorsOfType( const char* type ) { - int count = 0; - for (auto it = actors.begin(); it != actors.end(); it++) + //delete all actors + for (auto it = actors.begin(); it != actors.end();) { ActorContainer* actor = *it; - if ( streq( actor->title, type ) ) { - count++; - } + // sphactor is already destroyed by stage + delete actor; + it = actors.erase(it); } - return count; + // clear active file and set new path + editing_file = ""; + editing_path = ""; } -void ShowConfigWindow(bool * showLog) { - static char* configFile = new char[64]; - - //creates window - ImGui::Begin("Stage Settings", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove ); - - ImGui::Text("Save/load stage"); +bool StageWindow::Load( const char* configFile ) +{ + undoStack = std::stack(); + redoStack = std::stack(); - ImGui::InputText("Filename", configFile, 128); + // Clear current stage + // TODO: Maybe ask if people want to save first? + if (stage) + Clear(); - if (ImGui::Button("Save")) { // Buttons return true when clicked (most widgets return true when edited/activated) - Save(configFile); - } - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); - if (ImGui::Button("Load")) { // Buttons return true when clicked (most widgets return true when edited/activated) - Load(configFile); - } + stage = sph_stage_load(configFile); + if ( stage == NULL ) + return false; +#ifdef PYTHON3_FOUND + std::error_code ec; // no exception + std::filesystem::path cwd = std::filesystem::current_path(ec); + python_add_path(cwd.string().c_str()); +#endif - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); - if (ImGui::Button("Clear")) { // Buttons return true when clicked (most widgets return true when edited/activated) - Clear(); - Init(); - } + // clear active file as it needs saving to become a file first + editing_file = std::string(configFile); + editing_path = std::filesystem::path( std::filesystem::current_path() /= std::string(configFile)).string(); - ImGui::Checkbox("Show Log Window", showLog); + // Create a container for every actor + const zhash_t *stage_actors = sph_stage_actors(stage); + sphactor_t *actor = (sphactor_t *)zhash_first( (zhash_t *)stage_actors ); //zconfig_locate(configActors, "actor"); + while( actor != NULL ) + { + const char *uuidStr = zuuid_str(sphactor_ask_uuid(actor)); + const char *typeStr = sphactor_ask_actor_type(actor); + const char *endpointStr = sphactor_ask_endpoint(actor); - ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); - ImGui::End(); -} + ActorContainer *gActor = new ActorContainer( actor ); //CreateFromType(typeStr, uuidStr); -void OpenTextEditor(const char *filepath) -{ - gzb::TextEditorWindow *txtwin = NULL; - for ( auto w : gzb::App::getApp().text_editors ) + actors.push_back(gActor); + actor = (sphactor_t *)zhash_next((zhash_t *)stage_actors); + } + // loop actor containers to handle all connections + auto it = actors.begin();//(sphactor_t *)zhash_first( (zhash_t *)stage_actors ); //zconfig_locate(configActors, "actor"); + while( it != actors.end() ) { - if ( w->associated_file == filepath ) + ActorContainer *gActor = *it; + // get actor's connections + zlist_t *conn = sphactor_connections( gActor->actor ); + for ( char *connection = (char *)zlist_first(conn); connection != nullptr; connection = (char *)zlist_next(conn)) { - txtwin = w; + // find connect actor container + ActorContainer *peer_actor_container = Find(connection); + if (peer_actor_container != nullptr) + { + Connection new_connection; + new_connection.input_node = gActor; + new_connection.input_slot = gActor->input_slots[0].title; + new_connection.output_node = peer_actor_container; + new_connection.output_slot = peer_actor_container->output_slots[0].title; + ((ActorContainer*) new_connection.input_node)->connections.push_back(new_connection); + ((ActorContainer*) new_connection.output_node)->connections.push_back(new_connection); + } } + ++it; } - if (txtwin == NULL) - { - txtwin = new gzb::TextEditorWindow(filepath); - gzb::App::getApp().text_editors.push_back(txtwin); - } - else - { - txtwin->showing = true; - ImGuiWindow *win = ImGui::FindWindowByName(txtwin->window_name.c_str()); - ImGui::FocusWindow(win); - } + + return stage != NULL; } +bool StageWindow::Save( const char* configFile ) +{ + if ( actors.size() == 0 ) return false; + + // sync positions + auto it = actors.begin(); + while( it != actors.end() ) + { + ActorContainer *gActor = *it; + sphactor_set_position(gActor->actor, gActor->pos.x, gActor->pos.y); + ++it; + } -int RenderMenuBar( bool * showLog ) { + int rc = sph_stage_save_as( stage, configFile); + return rc == 0; +} +int StageWindow::RenderMenuBar() +{ + static imgui_addons::ImGuiFileBrowser file_dialog; static char* configFile = new char[64] { 0x0 }; static int keyFocus = 0; MenuAction action = MenuAction_None; @@ -402,7 +275,7 @@ int RenderMenuBar( bool * showLog ) { } #ifdef HAVE_IMGUI_DEMO if ( ImGui::MenuItem(ICON_FA_CARAVAN " Toggle Demo") ) { - showDemo = !showDemo; + gzb::App::getApp().demo_win.showing = !gzb::App::getApp().demo_win.showing; } #endif if ( ImGui::MenuItem(ICON_FA_INFO " Toggle About") ) { @@ -450,11 +323,11 @@ int RenderMenuBar( bool * showLog ) { ImGui::Separator(); - if ( streq( editingFile.c_str(), "" ) ) { + if ( streq( editing_file.c_str(), "" ) ) { ImGui::TextColored( ImVec4(.7,.9,.7,1), ICON_FA_EDIT ": *Unsaved Stage*"); } else { - ImGui::TextColored( ImVec4(.7,.9,.7,1), ICON_FA_EDIT ": %s", editingFile.c_str()); + ImGui::TextColored( ImVec4(.7,.9,.7,1), ICON_FA_EDIT ": %s", editing_file.c_str()); } if (GZB_GLOBAL.UPDATE_AVAIL) @@ -473,13 +346,13 @@ int RenderMenuBar( bool * showLog ) { { char cmd[PATH_MAX]; char url[60] = "https://pong.hku.nl/~buildbot/gazebosc/"; - #ifdef __WINDOWS__ +#ifdef __WINDOWS__ snprintf(cmd, PATH_MAX, "start %s", url); - #elif defined __UTYPE_LINUX +#elif defined __UTYPE_LINUX snprintf(cmd, PATH_MAX, "xdg-open %s &", url); - #else +#else snprintf(cmd, PATH_MAX, "open %s", url); - #endif +#endif system(cmd); } } @@ -493,11 +366,11 @@ int RenderMenuBar( bool * showLog ) { keyFocus = 2; } else if ( action == MenuAction_Save ) { - if ( streq( editingFile.c_str(), "" ) ) { + if ( streq( editing_file.c_str(), "" ) ) { ImGui::OpenPopup("MenuAction_Save"); } else { - Save(editingPath.c_str()); + Save(editing_path.c_str()); ImGui::CloseCurrentPopup(); } } @@ -515,8 +388,8 @@ int RenderMenuBar( bool * showLog ) { if(file_dialog.showFileDialog("MenuAction_Load", imgui_addons::ImGuiFileBrowser::DialogMode::OPEN, ImVec2(700, 310), ".gzs")) { if (Load(file_dialog.selected_path.c_str())) { - editingFile = file_dialog.selected_fn; - editingPath = file_dialog.selected_path; + editing_file = file_dialog.selected_fn; + editing_path = file_dialog.selected_path; moveCwdIfNeeded(); } @@ -525,13 +398,13 @@ int RenderMenuBar( bool * showLog ) { if(file_dialog.showFileDialog("MenuAction_Save", imgui_addons::ImGuiFileBrowser::DialogMode::SAVE, ImVec2(700, 310), ".gzs")) { if ( !Save(file_dialog.selected_path.c_str()) ) { - editingFile = ""; - editingPath = ""; + editing_file = ""; + editing_path = ""; zsys_error("Saving file failed!"); } else { - editingFile = file_dialog.selected_fn; - editingPath = file_dialog.selected_path; + editing_file = file_dialog.selected_fn; + editing_path = file_dialog.selected_path; moveCwdIfNeeded(); } @@ -540,34 +413,7 @@ int RenderMenuBar( bool * showLog ) { return 0; } -inline ImU32 LerpImU32( ImU32 c1, ImU32 c2, int index, int total, float offset, bool doubleSided ) -{ - float t = (float)index / total; - t = glm::max(glm::min(t + offset, 1.0f), 0.0f); - - if ( doubleSided && t < .5f ) { - ImU32 col; - col = c1; - c1 = c2; - c2 = col; - } - - unsigned char a1 = (c1 >> 24) & 0xff; - unsigned char a2 = (c2 >> 24) & 0xff; - unsigned char r1 = (c1 >> 16) & 0xff; - unsigned char r2 = (c2 >> 16) & 0xff; - unsigned char g1 = (c1 >> 8) & 0xff; - unsigned char g2 = (c2 >> 8) & 0xff; - unsigned char b1 = c1 & 0xff; - unsigned char b2 = c2 & 0xff; - - return (int) ((a2 - a1) * t + a1) << 24 | - (int) ((r2 - r1) * t + r1) << 16 | - (int) ((g2 - g1) * t + g1) << 8 | - (int) ((b2 - b1) * t + b1); -} - -int UpdateActors(float deltaTime, bool * showLog) +int StageWindow::UpdateActors(float deltaTime) { static std::vector selectedActors; static std::vector actorClipboardType; @@ -575,9 +421,6 @@ int UpdateActors(float deltaTime, bool * showLog) static std::vector actorClipboardPositions; static bool D_PRESSED = false; - if (about_win == NULL) - about_win = new gzb::AboutWindow(); - int numKeys; byte * keyState = (byte*)SDL_GetKeyboardState(&numKeys); @@ -757,7 +600,7 @@ int UpdateActors(float deltaTime, bool * showLog) selectedActors.clear(); if (ImGui::Begin("ImNodes", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_MenuBar )) { - rc = RenderMenuBar(showLog); + rc = RenderMenuBar(); // We probably need to keep some state, like positions of nodes/slots for rendering connections. ImNodes::Ez::BeginCanvas(); @@ -785,14 +628,14 @@ int UpdateActors(float deltaTime, bool * showLog) // Store new connections when they are created Connection new_connection; if (ImNodes::GetNewConnection(&new_connection.input_node, &new_connection.input_slot, - &new_connection.output_node, &new_connection.output_slot)) + &new_connection.output_node, &new_connection.output_slot)) { assert(new_connection.input_node); assert(new_connection.output_node); ((ActorContainer*) new_connection.input_node)->connections.push_back(new_connection); ((ActorContainer*) new_connection.output_node)->connections.push_back(new_connection); sphactor_ask_connect( ((ActorContainer*) new_connection.input_node)->actor, - sphactor_ask_endpoint( ((ActorContainer*) new_connection.output_node)->actor ) ); + sphactor_ask_endpoint( ((ActorContainer*) new_connection.output_node)->actor ) ); RegisterConnectAction((ActorContainer*)new_connection.input_node, (ActorContainer*)new_connection.output_node, new_connection.input_slot, new_connection.output_slot); } @@ -810,12 +653,12 @@ int UpdateActors(float deltaTime, bool * showLog) int oldId = draw_list->_VtxCurrentIdx; if (!ImNodes::Connection(connection.input_node, connection.input_slot, connection.output_node, - connection.output_slot)) + connection.output_slot)) { assert(connection.input_node); assert(connection.output_node); sphactor_ask_disconnect( ((ActorContainer*) connection.input_node)->actor, - sphactor_ask_endpoint( ((ActorContainer*) connection.output_node)->actor ) ); + sphactor_ask_endpoint( ((ActorContainer*) connection.output_node)->actor ) ); // Remove deleted connections ((ActorContainer*) connection.input_node)->DeleteConnection(connection); ((ActorContainer*) connection.output_node)->DeleteConnection(connection); @@ -858,7 +701,7 @@ int UpdateActors(float deltaTime, bool * showLog) ImNodes::Ez::EndNode(); if ( actor->selected) { - selectedActors.push_back(actor); + selectedActors.push_back(actor); } } @@ -906,329 +749,163 @@ int UpdateActors(float deltaTime, bool * showLog) } ImGui::End(); -#ifdef HAVE_IMGUI_DEMO - // ImGui Demo window for dev purposes - if (showDemo) ImGui::ShowDemoWindow(&showDemo); -#endif return rc; } -bool Save( const char* configFile ) { - if ( actors.size() == 0 ) return false; - - // sync positions - auto it = actors.begin(); - while( it != actors.end() ) +ActorContainer *StageWindow::Find(const char *endpoint) +{ + for (auto it = actors.begin(); it != actors.end();) { - ActorContainer *gActor = *it; - sphactor_set_position(gActor->actor, gActor->pos.x, gActor->pos.y); - ++it; - } + ActorContainer* actor = *it; + if ( streq( sphactor_ask_endpoint(actor->actor), endpoint)) { + return actor; + } - int rc = sph_stage_save_as( stage, configFile); - return rc == 0; + it++; + } + return nullptr; } -bool Load( const char* configFile ) +ActorContainer * StageWindow::CreateFromType( const char* typeStr, const char* uuidStr ) { - undoStack = std::stack(); - redoStack = std::stack(); - - // Clear current stage - // TODO: Maybe ask if people want to save first? - if (stage) - Clear(); - - stage = sph_stage_load(configFile); - if ( stage == NULL ) - return false; -#ifdef PYTHON3_FOUND - std::error_code ec; // no exception - std::filesystem::path cwd = std::filesystem::current_path(ec); - python_add_path(cwd.string().c_str()); -#endif - - // clear active file as it needs saving to become a file first - editingFile = std::string(configFile); - editingPath = std::filesystem::path( std::filesystem::current_path() /= std::string(configFile)).string(); - - // Create a container for every actor - const zhash_t *stage_actors = sph_stage_actors(stage); - sphactor_t *actor = (sphactor_t *)zhash_first( (zhash_t *)stage_actors ); //zconfig_locate(configActors, "actor"); - while( actor != NULL ) - { - const char *uuidStr = zuuid_str(sphactor_ask_uuid(actor)); - const char *typeStr = sphactor_ask_actor_type(actor); - const char *endpointStr = sphactor_ask_endpoint(actor); + zuuid_t *uuid = zuuid_new(); + if ( uuidStr != NULL ) + zuuid_set_str(uuid, uuidStr); - ActorContainer *gActor = new ActorContainer( actor ); //CreateFromType(typeStr, uuidStr); + sphactor_t *actor = sphactor_new_by_type(typeStr, uuidStr, uuid); + sphactor_ask_set_actor_type(actor, typeStr); + if ( stage == nullptr ) + stage = sph_stage_new("New Stage"); + sph_stage_add_actor(stage, actor); + return new ActorContainer(actor); +} - actors.push_back(gActor); - actor = (sphactor_t *)zhash_next((zhash_t *)stage_actors); - } - // loop actor containers to handle all connections - auto it = actors.begin();//(sphactor_t *)zhash_first( (zhash_t *)stage_actors ); //zconfig_locate(configActors, "actor"); - while( it != actors.end() ) +int StageWindow::CountActorsOfType( const char* type ) +{ + int count = 0; + for (auto it = actors.begin(); it != actors.end(); it++) { - ActorContainer *gActor = *it; - // get actor's connections - zlist_t *conn = sphactor_connections( gActor->actor ); - for ( char *connection = (char *)zlist_first(conn); connection != nullptr; connection = (char *)zlist_next(conn)) - { - // find connect actor container - ActorContainer *peer_actor_container = Find(connection); - if (peer_actor_container != nullptr) - { - Connection new_connection; - new_connection.input_node = gActor; - new_connection.input_slot = gActor->input_slots[0].title; - new_connection.output_node = peer_actor_container; - new_connection.output_slot = peer_actor_container->output_slots[0].title; - ((ActorContainer*) new_connection.input_node)->connections.push_back(new_connection); - ((ActorContainer*) new_connection.output_node)->connections.push_back(new_connection); - } + ActorContainer* actor = *it; + if ( streq( actor->title, type ) ) { + count++; } - ++it; } - - return stage != NULL; + return count; } -void Init() { - // initialise an empty stage with tmp working dir - assert(stage == NULL); - - // set a temporary random working dir for our stage - const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - char tmpdir[12] = "_gzs_xxxxxx"; - for (int i=5;i<12;i++) - { - int key = rand() % (int)(sizeof(charset)-1); - tmpdir[i] = charset[key]; - } - tmpdir[11] = 0; // null termination - std::filesystem::path tmppath(GZB_GLOBAL.TMPPATH); - tmppath.append(tmpdir); - std::error_code ec; - if ( ! std::filesystem::create_directory(tmppath, ec) ) - { - // TODO: what to do if creating the dir fails? - zsys_error("Creating tmp dir %s failed, this might mean trouble!!!", tmppath.string().c_str() ); - } - else - { - std::filesystem::current_path(tmppath); - zsys_info("Temporary stage dir is now at %s", tmppath.string().c_str()); +// undo stuff +void StageWindow::performUndo(UndoData &undo) +{ + switch( undo.type) { + case UndoAction_Delete: { + zsys_info("UNDO DELETE"); + sphactor_t * sphactor_actor = sphactor_load(undo.sphactor_config); + sph_stage_add_actor(stage, sphactor_actor); + + ActorContainer *actor = new ActorContainer( sphactor_actor ); + //zsys_info("UUID: %s", zuuid_str(sphactor_ask_uuid(sphactor_actor))); + actor->pos = undo.position; + actors.push_back(actor); + break; } - // clear active file as it needs saving to become a file first - editingFile = ""; - editingPath = ""; -} - -void Clear() { - - undoStack = std::stack(); - redoStack = std::stack(); - - //delete all connections - for (auto it = actors.begin(); it != actors.end();) - { - ActorContainer* gActor = *it; + case UndoAction_Create: { + zsys_info("UNDO CREATE"); + for( auto it = actors.begin(); it != actors.end(); it++) { + ActorContainer* actor = *it; + if ( streq( zuuid_str(sphactor_ask_uuid(actor->actor)), undo.uuid)) { + // Delete all our connections separately + actor->connections.clear(); + sph_stage_remove_actor(stage, zuuid_str(sphactor_ask_uuid(actor->actor))); - for (auto& connection : gActor->connections) - { - //delete them once - if (connection.output_node == gActor) { - ((ActorContainer*) connection.input_node)->DeleteConnection(connection); + delete actor; + actors.erase(it); + break; } } - gActor->connections.clear(); - it++; + break; } - - sph_stage_destroy(&stage); - // remove working dir if it's a temporary path - std::error_code ec; // no exception - std::filesystem::path cwd = std::filesystem::current_path(ec); -#ifdef PYTHON3_FOUND - python_remove_path(cwd.string().c_str()); -#endif - // temporary change the working dir otherwise we cannot delete it on windows, Load or Init will reset it - std::filesystem::current_path(GZB_GLOBAL.TMPPATH); - - std::filesystem::path tmppath(GZB_GLOBAL.TMPPATH); - tmppath.append("_gzs_"); - if ( cwd.string().rfind(tmppath.string(), 0) == 0 ) - { - try - { - std::filesystem::remove_all(cwd); - } - catch (std::exception& e) - { - zsys_error("failed to remove working dir: %s", e.what()); - } - } - assert(stage == NULL); - - //delete all actors - for (auto it = actors.begin(); it != actors.end();) - { - ActorContainer* actor = *it; - // sphactor is already destroyed by stage - delete actor; - it = actors.erase(it); - } - // clear active file and set new path - editingFile = ""; - editingPath = ""; -} - -ActorContainer* Find( const char* endpoint ) { - for (auto it = actors.begin(); it != actors.end();) - { - ActorContainer* actor = *it; - if ( streq( sphactor_ask_endpoint(actor->actor), endpoint)) { - return actor; + case UndoAction_Connect: { + zsys_info("UNDO CONNECT"); + ActorContainer *out = nullptr, *in = nullptr; + for( auto it = actors.begin(); it != actors.end(); it++) { + ActorContainer* actor = *it; + if ( streq( zuuid_str(sphactor_ask_uuid(actor->actor)), undo.uuid)) { + in = *it; + } + else if ( streq( sphactor_ask_endpoint(actor->actor), undo.endpoint)) { + out = *it; + } + if (out && in) break; } - it++; - } - - return nullptr; -} - -void performUndo(UndoData &undo) { - switch( undo.type) { - case UndoAction_Delete: { - zsys_info("UNDO DELETE"); - sphactor_t * sphactor_actor = sphactor_load(undo.sphactor_config); - sph_stage_add_actor(stage, sphactor_actor); - - ActorContainer *actor = new ActorContainer( sphactor_actor ); - //zsys_info("UUID: %s", zuuid_str(sphactor_ask_uuid(sphactor_actor))); - actor->pos = undo.position; - actors.push_back(actor); + if ( !out || !in ) { + zsys_info("COULD NOT FIND ONE OF THE NODES"); break; } - case UndoAction_Create: { - zsys_info("UNDO CREATE"); - for( auto it = actors.begin(); it != actors.end(); it++) { - ActorContainer* actor = *it; - if ( streq( zuuid_str(sphactor_ask_uuid(actor->actor)), undo.uuid)) { - // Delete all our connections separately - actor->connections.clear(); - sph_stage_remove_actor(stage, zuuid_str(sphactor_ask_uuid(actor->actor))); - - delete actor; - actors.erase(it); - break; - } + + // find and delete connections + for( auto it = in->connections.begin(); it != in->connections.end(); it++ ) { + ActorContainer *actor = (ActorContainer*)(*it).output_node; + if ( (*it).output_node == out) { + in->connections.erase(it); + break; } - break; } - case UndoAction_Connect: { - zsys_info("UNDO CONNECT"); - ActorContainer *out = nullptr, *in = nullptr; - for( auto it = actors.begin(); it != actors.end(); it++) { - ActorContainer* actor = *it; - if ( streq( zuuid_str(sphactor_ask_uuid(actor->actor)), undo.uuid)) { - in = *it; - } - else if ( streq( sphactor_ask_endpoint(actor->actor), undo.endpoint)) { - out = *it; - } - if (out && in) break; - } - - if ( !out || !in ) { - zsys_info("COULD NOT FIND ONE OF THE NODES"); + for( auto it = out->connections.begin(); it != out->connections.end(); it++ ) { + ActorContainer *actor = (ActorContainer*)(*it).input_node; + if ( (*it).input_node == in) { + out->connections.erase(it); break; } + } - // find and delete connections - for( auto it = in->connections.begin(); it != in->connections.end(); it++ ) { - ActorContainer *actor = (ActorContainer*)(*it).output_node; - if ( (*it).output_node == out) { - in->connections.erase(it); - break; - } + // disconnect + sphactor_ask_disconnect(in->actor, undo.endpoint); + + break; + } + case UndoAction_Disconnect: { + zsys_info("UNDO DISCONNECT"); + ActorContainer *out = nullptr, *in = nullptr; + for( auto it = actors.begin(); it != actors.end(); it++) { + ActorContainer* actor = *it; + if ( streq( zuuid_str(sphactor_ask_uuid(actor->actor)), undo.uuid)) { + in = *it; } - for( auto it = out->connections.begin(); it != out->connections.end(); it++ ) { - ActorContainer *actor = (ActorContainer*)(*it).input_node; - if ( (*it).input_node == in) { - out->connections.erase(it); - break; - } + else if ( streq( sphactor_ask_endpoint(actor->actor), undo.endpoint)) { + out = *it; } + if (out && in) break; + } - // disconnect - sphactor_ask_disconnect(in->actor, undo.endpoint); - + if ( !out || !in ) { + zsys_info("COULD NOT FIND ONE OF THE NODES"); break; } - case UndoAction_Disconnect: { - zsys_info("UNDO DISCONNECT"); - ActorContainer *out = nullptr, *in = nullptr; - for( auto it = actors.begin(); it != actors.end(); it++) { - ActorContainer* actor = *it; - if ( streq( zuuid_str(sphactor_ask_uuid(actor->actor)), undo.uuid)) { - in = *it; - } - else if ( streq( sphactor_ask_endpoint(actor->actor), undo.endpoint)) { - out = *it; - } - if (out && in) break; - } - - if ( !out || !in ) { - zsys_info("COULD NOT FIND ONE OF THE NODES"); - break; - } - //rebuild connection - Connection new_connection; - new_connection.input_slot = undo.input_slot; - new_connection.output_slot = undo.output_slot; - new_connection.input_node = in; - new_connection.output_node = out; + //rebuild connection + Connection new_connection; + new_connection.input_slot = undo.input_slot; + new_connection.output_slot = undo.output_slot; + new_connection.input_node = in; + new_connection.output_node = out; - in->connections.push_back(new_connection); - out->connections.push_back(new_connection); + in->connections.push_back(new_connection); + out->connections.push_back(new_connection); - //connect - sphactor_ask_connect(in->actor, undo.endpoint); + //connect + sphactor_ask_connect(in->actor, undo.endpoint); - break; - } - default: { - break; - } + break; + } + default: { + break; } -} - -void swapUndoType(UndoData * undo) { - switch(undo->type) { - case UndoAction_Connect: { - undo->type = UndoAction_Disconnect; - break; - } - case UndoAction_Disconnect: { - undo->type = UndoAction_Connect; - break; - } - case UndoAction_Create: { - undo->type = UndoAction_Delete; - break; - } - case UndoAction_Delete: { - undo->type = UndoAction_Create; - break; - } } } -void RegisterCreateAction( ActorContainer * actor ) { +void StageWindow::RegisterCreateAction( ActorContainer * actor ) +{ if ( redoStack.size() > 0 ) { redoStack = std::stack(); } @@ -1246,11 +923,12 @@ void RegisterCreateAction( ActorContainer * actor ) { else { undo.position = actor->pos; } - + undoStack.push(undo); } -void RegisterDeleteAction( ActorContainer * actor ) { +void StageWindow::RegisterDeleteAction( ActorContainer * actor ) +{ if ( redoStack.size() > 0 ) { redoStack = std::stack(); } @@ -1265,7 +943,8 @@ void RegisterDeleteAction( ActorContainer * actor ) { undoStack.push(undo); } -void RegisterConnectAction(ActorContainer * in, ActorContainer * out, const char * input_slot, const char * output_slot) { +void StageWindow::RegisterConnectAction(ActorContainer * in, ActorContainer * out, const char * input_slot, const char * output_slot) +{ if ( redoStack.size() > 0 ) { redoStack = std::stack(); } @@ -1279,7 +958,8 @@ void RegisterConnectAction(ActorContainer * in, ActorContainer * out, const char undoStack.push(undo); } -void RegisterDisconnectAction(ActorContainer * in, ActorContainer * out, const char * input_slot, const char * output_slot) { +void StageWindow::RegisterDisconnectAction(ActorContainer * in, ActorContainer * out, const char * input_slot, const char * output_slot) +{ if ( redoStack.size() > 0 ) { redoStack = std::stack(); } @@ -1292,3 +972,84 @@ void RegisterDisconnectAction(ActorContainer * in, ActorContainer * out, const c undo.output_slot = strdup(output_slot); undoStack.push(undo); } + +void StageWindow::swapUndoType(UndoData * undo) +{ + switch(undo->type) { + case UndoAction_Connect: { + undo->type = UndoAction_Disconnect; + break; + } + case UndoAction_Disconnect: { + undo->type = UndoAction_Connect; + break; + } + case UndoAction_Create: { + undo->type = UndoAction_Delete; + break; + } + case UndoAction_Delete: { + undo->type = UndoAction_Create; + break; + } + } +} + +void StageWindow::moveCwdIfNeeded() +{ + // check if we are still in the working dir or if we should move to the new dir + char cwd[PATH_MAX]; + getcwd(cwd, PATH_MAX); + // editingPath not starting with cwd means we need to move to the new wd + if (editing_path.rfind(cwd, 0) != 0) { + // we're not in the current working dir! move files to editingPath + // moving if cwd was tmp dir (name contains _gzs_) + std::string cwds = std::string(cwd); + std::filesystem::path newcwds = std::filesystem::path(editing_path); + std::filesystem::path tmppath(GZB_GLOBAL.TMPPATH); + tmppath.append("_gzs_"); + if (cwds.rfind(tmppath.string(), 0) == 0) + { + // cwd is a tmp dir so we need to move files + // copy and delete for now + try + { + std::filesystem::copy(cwds, newcwds.parent_path(), std::filesystem::copy_options::overwrite_existing | std::filesystem::copy_options::recursive); + } + catch (std::exception& e) + { + zsys_error("Copy files to new working dir failed: %s", e.what()); + } + try + { + std::filesystem::remove_all(cwds); + } + catch (std::exception& e) + { + zsys_error("Removing old tmpdir failed: %s", e.what()); + } + } + else + { + // we copy recursively + // Recursively copies all files and folders from src to target and overwrites existing files in target. + try + { + std::filesystem::copy(cwds, newcwds.parent_path(), std::filesystem::copy_options::overwrite_existing | std::filesystem::copy_options::recursive); + } + catch (std::exception& e) + { + zsys_error("Copy files to new working dir failed: %s", e.what()); + } + } + std::filesystem::current_path(newcwds.parent_path()); +#ifdef PYTHON3_FOUND + std::error_code ec; // no exception + python_remove_path((const char *)cwd); + python_add_path(newcwds.parent_path().string().c_str()); +#endif + zsys_info("Working dir set to %s", newcwds.parent_path().c_str()); + } +} + +} //namespace diff --git a/app/StageWindow.hpp b/app/StageWindow.hpp new file mode 100644 index 00000000..40158b3d --- /dev/null +++ b/app/StageWindow.hpp @@ -0,0 +1,97 @@ +#ifndef STAGEWINDOW_HPP +#define STAGEWINDOW_HPP +#ifndef IMGUI_DEFINE_MATH_OPERATORS +# define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "ActorContainer.hpp" +#include +#include +#include +#include +#include +#include "Window.hpp" +#include "libsphactor.h" + +namespace gzb { + +//enum for menu actions +enum MenuAction +{ + MenuAction_Save = 0, + MenuAction_Load, + MenuAction_Clear, + MenuAction_Exit, + MenuAction_SaveAs, + MenuAction_None +}; + +//enum for undo stack +enum UndoAction +{ + UndoAction_Create = 0, + UndoAction_Delete, + UndoAction_Disconnect, + UndoAction_Connect, + UndoAction_Invalid +}; + +struct UndoData { + UndoAction type = UndoAction_Invalid; + const char * title = nullptr; + const char * uuid = nullptr; + const char * endpoint = nullptr; + const char * input_slot = nullptr; + const char * output_slot = nullptr; + zconfig_t * sphactor_config = nullptr; + ImVec2 position; + + UndoData( UndoData * from ) { + type = from->type; + title = from->title ? strdup(from->title) : nullptr; + uuid = from->uuid ? strdup(from->uuid) : nullptr; + endpoint = from->endpoint ? strdup(from->endpoint) : nullptr; + input_slot = from->input_slot ? strdup(from->input_slot) : nullptr; + output_slot = from->output_slot ? strdup(from->output_slot) : nullptr; + if (from->sphactor_config != nullptr) + sphactor_config = zconfig_dup(from->sphactor_config); + position = from->position; + } + UndoData() {} +}; + +class StageWindow : public Window +{ +public: + sph_stage_t *stage = NULL; + std::string editing_file = ""; + std::string editing_path = ""; + ImVector actor_types; + std::vector actors; + std::map max_actors_by_type; + std::stack undoStack; + std::stack redoStack; + + StageWindow(); + ~StageWindow(); + void Init(); + void Clear(); + bool Load( const char* configFile ); + bool Save( const char* configFile ); + int RenderMenuBar(); + int UpdateActors(float deltaTime); + ActorContainer *Find(const char *endpoint); + ActorContainer * CreateFromType( const char* typeStr, const char* uuidStr ); + int CountActorsOfType( const char* type ); + + // undo stuff + void performUndo(UndoData &undo); + void RegisterCreateAction( ActorContainer * actor ); + void RegisterDeleteAction( ActorContainer * actor ); + void RegisterConnectAction(ActorContainer * in, ActorContainer * out, const char * input_slot, const char * output_slot); + void RegisterDisconnectAction(ActorContainer * in, ActorContainer * out, const char * input_slot, const char * output_slot); + void swapUndoType(UndoData * undo); + + void moveCwdIfNeeded(); +}; +} +#endif // STAGEWINDOW_HPP diff --git a/helpers.c b/helpers.c new file mode 100644 index 00000000..2b63ec23 --- /dev/null +++ b/helpers.c @@ -0,0 +1,246 @@ +#include "helpers.h" + +PyObject *python_call_file_func(const char *file, const char *func, const char *fmt, ...) +{ + PyObject *pName, *pModule, *pFunc, *pValue; + pValue = NULL; + pName = PyUnicode_DecodeFSDefault(file); + /* Error checking of pName left out */ + assert(pName); + pModule = PyImport_Import(pName); + Py_DECREF(pName); + + if (pModule != NULL) + { + pFunc = PyObject_GetAttrString(pModule, func); + /* pFunc is a new reference */ + + if (pFunc && PyCallable_Check(pFunc)) { + + va_list argsptr; + va_start(argsptr, fmt); + PyObject *pArgs = Py_VaBuildValue(fmt, argsptr); + assert(pArgs); + pValue = PyObject_CallObject(pFunc, pArgs); + //vprintf(fmt, argsptr); + va_end(argsptr); + if (pValue != NULL) { + printf("Result of call: %ld\n", PyLong_AsLong(pValue)); + Py_DECREF(pValue); + } + else { + Py_DECREF(pFunc); + Py_DECREF(pModule); + PyErr_Print(); + fprintf(stderr,"Call failed\n"); + } + } + else { + if (PyErr_Occurred()) + PyErr_Print(); + fprintf(stderr, "Cannot find function \"%s\"\n", func); + } + Py_XDECREF(pFunc); + Py_DECREF(pModule); + } + else + { + PyErr_Print(); + fprintf(stderr, "Failed to load \"%s\"\n", file); + } + return pValue; +} + +void python_add_path(const char *path) +{ + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + char *script = (char*)malloc(512 * sizeof(char)); + // Prints "Hello world!" on hello_world + snprintf(script, 512 * sizeof(char), + "import sys\n" + "if \"%s\" not in sys.path:\n" + "\tsys.path.append(r\"%s/site-packages\")\n" + "print(\"added path %s/site-packages to sys.path\")\n", + path, path, path); + int rc = PyRun_SimpleString(script); + assert(rc == 0); + PyGILState_Release(gstate); + free(script); +} + +void python_remove_path(const char *path) +{ + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + char *script = (char*)malloc(512 * sizeof(char)); + // Prints "Hello world!" on hello_world + snprintf(script, 512 * sizeof(char), + "import sys\n" + "try:\n" + "\tsys.path.remove(r\"%s/site-packages\")\n" + "except Exception:\n" + "\tpass\n" + "print(\"removed path %s/site-packages from sys.path\")\n", + path, path); + int rc = PyRun_SimpleString(script); + assert(rc == 0); + PyGILState_Release(gstate); + free(script); +} + +zosc_t * +s_py_zosc(PyObject *pAddress, PyObject *pData) +{ + assert( PyUnicode_Check(pAddress) ); + assert( PyList_Check(pData) ); + PyObject *stringbytes = PyUnicode_AsASCIIString(pAddress); + zosc_t *ret = zosc_new( PyBytes_AsString( stringbytes) ); + Py_DECREF(stringbytes); + + // iterate + for ( Py_ssize_t i=0;i #include"StackWalker.h" #endif - +#ifndef IMGUI_DEFINE_MATH_OPERATORS +# define IMGUI_DEFINE_MATH_OPERATORS +# define IM_VEC2_CLASS_EXTRA +#endif #include "imgui.h" #include "imgui_impl_sdl2.h" #include "imgui_impl_opengl3.h" @@ -55,6 +58,7 @@ namespace fs = std::filesystem; #include "config.h" +#include "actors/actors.h" #include "app/App.hpp" // Forward declare to keep main func on top for readability @@ -390,13 +394,13 @@ int main(int argc, char** argv) if ( stage_file ) { - if ( ! Load(stage_file)) + if ( ! gzb::App::getApp().stage_win.Load(stage_file)) { zsys_error("Failed loading %s", stage_file); } } else - Init(); // start with an empty stage + gzb::App::getApp().stage_win.Init(); // start with an empty stage // Blocking UI loop UILoop(window, io); @@ -409,7 +413,7 @@ int main(int argc, char** argv) if ( stage_file ) { - if ( ! Load(stage_file)) + if ( ! gzb::App::getApp().stage_win.Load(stage_file)) { zsys_error("Failed loading %s", stage_file); } @@ -432,7 +436,7 @@ int main(int argc, char** argv) } } - Clear(); + gzb::App::getApp().stage_win.Clear(); sphactor_dispose(); zstr_free(&GZB_GLOBAL.RESOURCESPATH); @@ -443,6 +447,63 @@ int main(int argc, char** argv) return 0; } +ImVector actor_types; +void UpdateRegisteredActorsCache() { + zhash_t *hash = sphactor_get_registered(); + zlist_t *list = zhash_keys(hash); + actor_types.clear(); + + char* item = (char*)zlist_first(list); + while( item ) { + actor_types.push_back(item); + item = (char*)zlist_next(list); + } +} + +std::map max_actors_by_type; +void RegisterActors() { + // register stock actors + sph_stock_register_all(); + + sphactor_register("HTTPLaunchpod", &httplaunchpodactor_handler, zconfig_str_load(httplaunchpodactorcapabilities), &httplaunchpodactor_new_helper, NULL); // https://stackoverflow.com/questions/65957511/typedef-for-a-registering-a-constructor-function-in-c + sphactor_register( "OSC Output", OSCOutput::capabilities); + sphactor_register( "OSC Multi Output", OSCMultiOut::capabilities); + sphactor_register( "NatNet", NatNet::capabilities ); + sphactor_register( "NatNet2OSC", NatNet2OSC::capabilities ); + sphactor_register( "Midi2OSC", Midi2OSC::capabilities ); +#ifdef HAVE_OPENVR + sphactor_register("OpenVR", OpenVR::capabilities); +#endif + sphactor_register( "OSC Input", OSCInput::capabilities ); + sphactor_register("Record", Record::capabilities ); + sphactor_register( "ModPlayer", ModPlayerActor::capabilities ); + sphactor_register( "Process", ProcessActor::capabilities ); +#ifdef HAVE_OPENVR + sphactor_register( "DmxOut", DmxActor::capabilities ); + sphactor_register( "IntSlider", IntSlider::capabilities ); + sphactor_register( "FloatSlider", FloatSlider::capabilities ); +#endif +#ifdef PYTHON3_FOUND + int rc = python_init(); + assert( rc == 0); + /* Check newer version: We should make this async as it slows startup" */ + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + + PyObject *pUpdateBool = python_call_file_func("checkver", "check_github_newer_commit", "(s)", GIT_HASH); + if (pUpdateBool && PyObject_IsTrue(pUpdateBool)) + GZB_GLOBAL.UPDATE_AVAIL = true; + + PyGILState_Release(gstate); + /* End check newer version */ +#endif + //enforcable maximum actor counts + max_actors_by_type.insert(std::make_pair("NatNet", 1)); + max_actors_by_type.insert(std::make_pair("OpenVR", 1)); + + UpdateRegisteredActorsCache(); +} + int SDLInit( SDL_Window** window, SDL_GLContext* gl_context, const char** glsl_version ) { // Setup SDL if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO) != 0) @@ -602,7 +663,9 @@ void UILoop( SDL_Window* window, ImGuiIO& io ) { //printf("fps %.2f %i\n", 1000./(SDL_GetTicks() - oldTime), deltaTime); /* For when we send msgs to the main thread */ oldTime = SDL_GetTicks(); - int rc = UpdateActors( ((float)deltaTime) / 1000, &logWindow); + + // root window + int rc = app.stage_win.UpdateActors(deltaTime); // text editor windows std::vector::iterator itr = app.text_editors.begin(); From a1695f2e95af5aadd4742cb11b857e252911200c Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Thu, 11 Apr 2024 22:31:26 +0200 Subject: [PATCH 19/39] remove obsolete code --- main.cpp | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/main.cpp b/main.cpp index 757dd4a6..3f502f4f 100644 --- a/main.cpp +++ b/main.cpp @@ -648,13 +648,6 @@ void UILoop( SDL_Window* window, ImGuiIO& io ) { ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); - //main window - // this will be our main workspace - - SDL_GetWindowSize(window, &w, &h); - ImVec2 size = ImVec2(w,h); - ImGui::SetNextWindowSize(size); - // Get time since last frame deltaTime = SDL_GetTicks() - oldTime; /* In here we can poll sockets */ @@ -664,6 +657,12 @@ void UILoop( SDL_Window* window, ImGuiIO& io ) { /* For when we send msgs to the main thread */ oldTime = SDL_GetTicks(); + //main window + // this will be our main workspace + SDL_GetWindowSize(window, &w, &h); + ImVec2 size = ImVec2(w,h); + ImGui::SetNextWindowSize(size); + // root window int rc = app.stage_win.UpdateActors(deltaTime); @@ -692,17 +691,6 @@ void UILoop( SDL_Window* window, ImGuiIO& io ) { if ( rc == -1 ) { stop = 1; } - // Save/load window - //size = ImVec2(350,135); - //ImVec2 pos = ImVec2(w - 400, 50); - //ImGui::SetNextWindowSize(size); - //ImGui::SetNextWindowPos(pos); - //ShowConfigWindow(&logWindow); - - //if ( logWindow ) { - // ShowLogWindow(getBuffer()); - //} - // Rendering ImGui::Render(); glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); From b3e218a4ad107518a9ee7ae444f8f8acf82d8830 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Fri, 12 Apr 2024 09:52:09 +0200 Subject: [PATCH 20/39] make windows happy because it already provides itoa --- app/ActorContainer.cpp | 6 +++--- helpers.c | 2 +- helpers.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/ActorContainer.cpp b/app/ActorContainer.cpp index 62e22962..b49ec646 100644 --- a/app/ActorContainer.cpp +++ b/app/ActorContainer.cpp @@ -154,7 +154,7 @@ ActorContainer::HandleAPICalls(zconfig_t * data) { case 'i': { int ival; ReadInt(&ival, zvalue); - sphactor_ask_api(this->actor, zconfig_value(zapic), zapivStr, itoa(ival)); + sphactor_ask_api(this->actor, zconfig_value(zapic), zapivStr, gzb_itoa(ival)); } break; case 'f': { float fval; @@ -786,7 +786,7 @@ ActorContainer::RenderInt(const char* name, zconfig_t *data) { zconfig_set_value(zvalue, "%i", value); //SendAPI(zapic, zapiv, zvalue, &value); - sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), itoa(value) ); + sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), gzb_itoa(value) ); } zconfig_t *help = zconfig_locate(data, "help"); if (help) @@ -849,7 +849,7 @@ ActorContainer::RenderSlider(const char* name, zconfig_t *data) { if ( ImGui::SliderInt( "", &value, min, max) ) { zconfig_set_value(zvalue, "%i", value); - sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), itoa(value) ); + sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), gzb_itoa(value) ); } ImGui::SameLine(); ImGui::SetNextItemWidth(30); diff --git a/helpers.c b/helpers.c index 2b63ec23..a86bb479 100644 --- a/helpers.c +++ b/helpers.c @@ -231,7 +231,7 @@ convert_to_relative_to_wd(const char *path) return strdup( &path[ wdpathlen ]); } -char *itoa(int i) +char *gzb_itoa(int i) { char *str = (char *)malloc(10); sprintf(str, "%d", i); diff --git a/helpers.h b/helpers.h index 7952b68e..3354ea9e 100644 --- a/helpers.h +++ b/helpers.h @@ -16,7 +16,7 @@ char *s_basename(char const *path); bool s_dir_exists(const wchar_t *pypath); void s_replace_char(char *str, char from, char to); char *convert_to_relative_to_wd(const char *path); -char *itoa(int i); +char *gzb_itoa(int i); char *ftoa(float f); #ifdef __cplusplus } From a0d3fd67dfa289d30ecbd89070038107c27aad5e Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Fri, 12 Apr 2024 11:09:20 +0200 Subject: [PATCH 21/39] not stdio redir on windows --- app/App.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/App.hpp b/app/App.hpp index 8370d81f..6090096a 100644 --- a/app/App.hpp +++ b/app/App.hpp @@ -252,6 +252,9 @@ static ImGuiTextBuffer& getLogBuffer(int fd=-1){ static void capture_stdio() { +#ifdef __WINDOWS__ + printf("WARNING: stdio redirection not implemented on Windows\n"); +#else static int out_pipe[2]; int rc = pipe(out_pipe); assert( rc == 0 ); @@ -262,6 +265,7 @@ static void capture_stdio() dup2(out_pipe[1], STDOUT_FILENO); close(out_pipe[1]); +#endif } } From 8d27487cf22bd94a21c31f8484b35842f667b034 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Fri, 12 Apr 2024 11:47:42 +0200 Subject: [PATCH 22/39] cleanup code a bit --- main.cpp | 89 +++++++++++++++++++------------------------------------- 1 file changed, 30 insertions(+), 59 deletions(-) diff --git a/main.cpp b/main.cpp index 3f502f4f..d4586087 100644 --- a/main.cpp +++ b/main.cpp @@ -34,9 +34,6 @@ #include "imgui_impl_opengl3.h" #include "imgui_internal.h" #include "fontawesome5.h" -#include "ext/ImFileDialog/ImFileDialog.h" -#include -#include #define SDL_MAIN_HANDLED #include #if defined(IMGUI_IMPL_OPENGL_ES2) @@ -47,10 +44,8 @@ #include #include - +#include #include - - #include #include #include @@ -58,6 +53,7 @@ namespace fs = std::filesystem; #include "config.h" +#include "ext/ImFileDialog/ImFileDialog.h" #include "actors/actors.h" #include "app/App.hpp" @@ -66,15 +62,17 @@ int SDLInit(SDL_Window** window, SDL_GLContext* gl_context, const char** glsl_ve ImGuiIO& ImGUIInit(SDL_Window* window, SDL_GLContext* gl_context, const char* glsl_version); void UILoop(SDL_Window* window, ImGuiIO& io ); void Cleanup(SDL_Window* window, SDL_GLContext* gl_context); - -void ShowConfigWindow(bool * showLog); -void ShowLogWindow(ImGuiTextBuffer&); -int UpdateActors(float deltaTime, bool * showLog); -bool Load(const char* fileName); -void Clear(); -void Init(); - -void RegisterActors(); +void register_actors(); +// Window variables +SDL_Window* window; +SDL_GLContext gl_context; +ImGuiIO io; +const char* glsl_version; +static ImWchar ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 }; +GZB_GLOBALS_t GZB_GLOBAL; +// Logging +char huge_string_buf[4096]; +int out_pipe[2]; // exit handlers et al volatile sig_atomic_t stop; @@ -96,23 +94,8 @@ static BOOL WINAPI s_exit_handler_fn (DWORD ctrltype) } #endif -// Window variables -SDL_Window* window; -SDL_GLContext gl_context; -ImGuiIO io; -const char* glsl_version; -static ImWchar ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 }; - -GZB_GLOBALS_t GZB_GLOBAL; - -// Logging -bool logWindow = false; -char huge_string_buf[4096]; -int out_pipe[2]; - -/* Obtain a backtrace and print it to stdout. */ +// Obtain a backtrace and print it to stdout. #if defined(HAVE_LIBUNWIND) - #define UNW_LOCAL_ONLY #include #include @@ -323,7 +306,6 @@ int main(int argc, char** argv) signal(SIGSEGV, print_backtrace); // Invalid memory reference signal(SIGABRT, print_backtrace); // Abort signal from abort(3) signal(SIGFPE, print_backtrace); // Floating-point exception - #if defined(__UNIX__) signal(SIGQUIT, sig_hand); signal(SIGTERM, sig_hand); @@ -350,7 +332,7 @@ int main(int argc, char** argv) set_global_resources(); set_global_temp(); GZB_GLOBAL.UPDATE_AVAIL = false; - RegisterActors(); + register_actors(); // Argument capture zargs_t *args = zargs_new(argc, argv); @@ -366,30 +348,18 @@ int main(int argc, char** argv) stop = 0; int loops = -1; int loopCount = 0; - /* - if (!headless) { - //TODO: Fix non-threadsafeness causing hangs on zsys_info calls during zactor_destroy - // capture stdout - // This approach uses a pipe to prevent multiple writes before reads overlapping - memset(huge_string_buf,0,4096); - int rc = pipe(out_pipe); - assert( rc == 0 ); - - long flags = fcntl(out_pipe[0], F_GETFL); - flags |= O_NONBLOCK; - fcntl(out_pipe[0], F_SETFL, flags); - - dup2(out_pipe[1], STDOUT_FILENO); - close(out_pipe[1]); + if (!headless) + { + memset(huge_string_buf,0,4096); + gzb::capture_stdio(); } - */ // try to init SDL and otherwise run headless - if ( !headless && SDLInit(&window, &gl_context, &glsl_version) == 0 ) { + if ( !headless && SDLInit(&window, &gl_context, &glsl_version) == 0 ) + { SDL_SetWindowTitle(window, "Gazebosc [" GIT_VERSION "]" ); - - zsys_info("VERSION: %s", glsl_version); + zsys_info("GLSL VERSION: %s", glsl_version); io = ImGUIInit(window, &gl_context, glsl_version); if ( stage_file ) @@ -417,12 +387,13 @@ int main(int argc, char** argv) { zsys_error("Failed loading %s", stage_file); } - while (!stop) { - if ( loops != -1 ) { + while (!stop) + { + if ( loops != -1 ) + { std::this_thread::sleep_for (std::chrono::milliseconds(1)); - if ( ++loopCount > loops ) { + if ( ++loopCount > loops ) break; - } } } } @@ -461,7 +432,7 @@ void UpdateRegisteredActorsCache() { } std::map max_actors_by_type; -void RegisterActors() { +void register_actors() { // register stock actors sph_stock_register_all(); @@ -650,11 +621,11 @@ void UILoop( SDL_Window* window, ImGuiIO& io ) { // Get time since last frame deltaTime = SDL_GetTicks() - oldTime; - /* In here we can poll sockets */ + // In here we can poll sockets + // For when we send msgs to the main thread if (deltaTime < 1000/30) SDL_Delay((1000/30)-deltaTime); //printf("fps %.2f %i\n", 1000./(SDL_GetTicks() - oldTime), deltaTime); - /* For when we send msgs to the main thread */ oldTime = SDL_GetTicks(); //main window From 20405c95d6209441a167390264515b9bd0644909 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Fri, 12 Apr 2024 11:48:05 +0200 Subject: [PATCH 23/39] can't mix cpp and c code for windows --- helpers.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/helpers.c b/helpers.c index a86bb479..02930a3e 100644 --- a/helpers.c +++ b/helpers.c @@ -217,9 +217,14 @@ convert_to_relative_to_wd(const char *path) getcwd(wdpath, PATH_MAX); #if WIN32 - std::string pathStr = wdpath; - std::replace(pathStr.begin(), pathStr.end(), '\\', '/'); - strcpy(wdpath, pathStr.c_str()); + for (int i = 0; wdpath[i] != '\0'; i++) { + if (wdpath[i] == '\\') { + wdpath[i] = '/'; + } + } + //std::string pathStr = wdpath; + //std::replace(pathStr.begin(), pathStr.end(), '\\', '/'); + //strcpy(wdpath, pathStr.c_str()); #endif const char *ret = strstr(path, wdpath); From c8197e1bd5be35c2a6d17e39af86ef3880f9dc60 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Fri, 12 Apr 2024 12:55:38 +0200 Subject: [PATCH 24/39] reenable stdout redir and capture --- app/App.hpp | 29 ++++++----------------------- app/LogWindow.hpp | 24 ++++++++++++++++++++---- main.cpp | 5 ++++- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/app/App.hpp b/app/App.hpp index 6090096a..c414c8ef 100644 --- a/app/App.hpp +++ b/app/App.hpp @@ -236,35 +236,18 @@ struct App } }; -static ImGuiTextBuffer& getLogBuffer(int fd=-1){ - static ImGuiTextBuffer sLogBuffer; // static log buffer for logger channel - static char huge_string_buf[4096]; - - if (fd !=-1) - read(fd, huge_string_buf, 4096); - if ( strlen( huge_string_buf ) > 0 ) { - sLogBuffer.appendf("%s", huge_string_buf); - memset(huge_string_buf,0,4096); - } - - return sLogBuffer; -} - -static void capture_stdio() +static void capture_stdio(int pipe_in, int pipe_out) { #ifdef __WINDOWS__ printf("WARNING: stdio redirection not implemented on Windows\n"); #else - static int out_pipe[2]; - int rc = pipe(out_pipe); - assert( rc == 0 ); - - long flags = fcntl(out_pipe[0], F_GETFL); + //TODO: Fix non-threadsafeness causing hangs on zsys_info calls during zactor_destroy + long flags = fcntl(pipe_in, F_GETFL); flags |= O_NONBLOCK; - fcntl(out_pipe[0], F_SETFL, flags); + fcntl(pipe_in, F_SETFL, flags); - dup2(out_pipe[1], STDOUT_FILENO); - close(out_pipe[1]); + dup2(pipe_out, STDOUT_FILENO); + close(pipe_out); #endif } diff --git a/app/LogWindow.hpp b/app/LogWindow.hpp index 150a4a14..8845aa2e 100644 --- a/app/LogWindow.hpp +++ b/app/LogWindow.hpp @@ -3,11 +3,15 @@ #include "app/Window.hpp" #include "imgui.h" +#include namespace gzb { -class LogWindow : public Window +struct LogWindow : public Window { -public: + int pipe_fd = -1; + bool scroll_to_bottom = true; + ImGuiTextBuffer *buffer; + LogWindow(ImGuiTextBuffer *log_buffer) { buffer = log_buffer; @@ -16,6 +20,7 @@ class LogWindow : public Window ~LogWindow() {}; void OnImGui() { + ReadStdOut(); ImGui::PushID(123); ImGui::SetNextWindowSizeConstraints(ImVec2(100,100), ImVec2(1000,1000)); @@ -46,8 +51,19 @@ class LogWindow : public Window ImGui::PopID(); } - bool scroll_to_bottom = true; - ImGuiTextBuffer *buffer; + int ReadStdOut() + { + static char huge_string_buf[4096]; + int rc = 0; + + if (pipe_fd !=-1) + rc = read(pipe_fd, huge_string_buf, 4096); + if ( rc > 0 && strlen( huge_string_buf ) > 0 ) { + buffer->appendf("%s", huge_string_buf); + memset(huge_string_buf,0,4096); + } + return rc; + } }; } // namespace #endif // LOGWINDOW_HPP diff --git a/main.cpp b/main.cpp index d4586087..b048d04b 100644 --- a/main.cpp +++ b/main.cpp @@ -352,7 +352,9 @@ int main(int argc, char** argv) if (!headless) { memset(huge_string_buf,0,4096); - gzb::capture_stdio(); + int rc = pipe(out_pipe); + assert( rc == 0 ); + gzb::capture_stdio(out_pipe[0], out_pipe[1]); } // try to init SDL and otherwise run headless @@ -595,6 +597,7 @@ void UILoop( SDL_Window* window, ImGuiIO& io ) { ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); gzb::App &app = gzb::App::getApp(); + app.log_win.pipe_fd = out_pipe[0]; // Main loop unsigned int deltaTime = 0, oldTime = 0; while (!stop) From 8ed00436700d4cb15d4782c896e7ee227a8d9a39 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Fri, 12 Apr 2024 14:17:27 +0200 Subject: [PATCH 25/39] no unistd.h on windows --- app/LogWindow.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/LogWindow.hpp b/app/LogWindow.hpp index 8845aa2e..80e52ffe 100644 --- a/app/LogWindow.hpp +++ b/app/LogWindow.hpp @@ -3,8 +3,9 @@ #include "app/Window.hpp" #include "imgui.h" +#ifdef __unix__ #include - +#endif namespace gzb { struct LogWindow : public Window { @@ -53,8 +54,9 @@ struct LogWindow : public Window int ReadStdOut() { - static char huge_string_buf[4096]; int rc = 0; +#ifdef __unix__ + static char huge_string_buf[4096]; if (pipe_fd !=-1) rc = read(pipe_fd, huge_string_buf, 4096); @@ -62,6 +64,7 @@ struct LogWindow : public Window buffer->appendf("%s", huge_string_buf); memset(huge_string_buf,0,4096); } +#endif return rc; } }; From 2339665927ce61312e5cf5eeb4716b4f019886e0 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Sat, 13 Apr 2024 22:51:20 +0200 Subject: [PATCH 26/39] replace ImGuiFileBrowser with ImFileDialog --- CMakeLists.txt | 1 - app/ActorContainer.cpp | 27 +++++++++++++++---- app/StageWindow.cpp | 57 ++++++++++++++++++++++------------------ app/TextEditorWindow.cpp | 2 +- 4 files changed, 55 insertions(+), 32 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0aa7777a..351e2901 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -212,7 +212,6 @@ list(APPEND GZB_SOURCES ext/imgui/imgui_demo.cpp ext/ImFileDialog/ImFileDialog.h ext/ImFileDialog/ImFileDialog.cpp - ext/ImGui-Addons/FileBrowser/ImGuiFileBrowser.cpp ext/ImGuiColorTextEdit/LanguageDefinitions.cpp ext/ImGuiColorTextEdit/TextEditor.cpp ${ACTOR_SOURCES} diff --git a/app/ActorContainer.cpp b/app/ActorContainer.cpp index b49ec646..84fff99f 100644 --- a/app/ActorContainer.cpp +++ b/app/ActorContainer.cpp @@ -1,10 +1,9 @@ #include "ActorContainer.hpp" #include "App.hpp" +#include "ext/ImFileDialog/ImFileDialog.h" namespace gzb { -imgui_addons::ImGuiFileBrowser actor_file_dialog; - ActorContainer::ActorContainer(sphactor_t *actor) { this->actor = actor; this->title = sphactor_ask_actor_type(actor); @@ -558,9 +557,27 @@ ActorContainer::RenderFilename(const char* name, zconfig_t *data) { file_selected = true; if ( file_selected ) - ImGui::OpenPopup("Actor Open File"); + { + char filter[1024]; + sprintf(filter, "Valid files: (%s){%s},.*", valid_files, valid_files); + ifd::FileDialog::Instance().Open(std::string(title) + "ActorOpenDialog", "Actor Open File", filter); + } - if ( actor_file_dialog.showFileDialog("Actor Open File", + if (ifd::FileDialog::Instance().IsDone(std::string(title) + "ActorOpenDialog")) + { + if (ifd::FileDialog::Instance().HasResult()) + { + std::string res = ifd::FileDialog::Instance().GetResult().u8string(); + char *path = convert_to_relative_to_wd(res.c_str()); + zconfig_set_value(zvalue, "%s", path ); + strcpy(buf, path); + //SendAPI(zapic, zapiv, zvalue, &(p) ); + zstr_free(&path); + sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), p ); + } + } + + /*if ( actor_file_dialog.showFileDialog("Actor Open File", streq(zoptStr, "rw" ) ? imgui_addons::ImGuiFileBrowser::DialogMode::SAVE : imgui_addons::ImGuiFileBrowser::DialogMode::OPEN, ImVec2(700, 310), valid_files) ) // TODO: perhaps add type hint for extensions? @@ -571,7 +588,7 @@ ActorContainer::RenderFilename(const char* name, zconfig_t *data) { //SendAPI(zapic, zapiv, zvalue, &(p) ); zstr_free(&path); sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), p ); - } + }*/ zconfig_t *help = zconfig_locate(data, "help"); const char *helpv = "Load a file"; diff --git a/app/StageWindow.cpp b/app/StageWindow.cpp index 82794edd..b9756314 100644 --- a/app/StageWindow.cpp +++ b/app/StageWindow.cpp @@ -4,7 +4,7 @@ #include "App.hpp" #include "glm/glm/common.hpp" #include "helpers.h" - +#include "ext/ImFileDialog/ImFileDialog.h" namespace gzb { @@ -218,7 +218,6 @@ bool StageWindow::Save( const char* configFile ) } int StageWindow::RenderMenuBar() { - static imgui_addons::ImGuiFileBrowser file_dialog; static char* configFile = new char[64] { 0x0 }; static int keyFocus = 0; MenuAction action = MenuAction_None; @@ -361,21 +360,21 @@ int StageWindow::RenderMenuBar() ImGui::EndMainMenuBar(); // Handle MenuActions - if ( action == MenuAction_Load) { - ImGui::OpenPopup("MenuAction_Load"); + if ( action == MenuAction_Load) + { + ifd::FileDialog::Instance().Open(window_name + "LoadStageDialog", "Load stage from file", "*.gzb; {.gzb}"); keyFocus = 2; } else if ( action == MenuAction_Save ) { if ( streq( editing_file.c_str(), "" ) ) { - ImGui::OpenPopup("MenuAction_Save"); + ifd::FileDialog::Instance().Save(window_name + "SaveStageDialog", "Save stage to file", "*.gzb; {.gzb}"); } else { Save(editing_path.c_str()); - ImGui::CloseCurrentPopup(); } } else if ( action == MenuAction_SaveAs ) { - ImGui::OpenPopup("MenuAction_Save"); + ifd::FileDialog::Instance().Save(window_name + "SaveStageDialog", "Save stage to file", "*.gzb; {.gzb}"); } else if ( action == MenuAction_Clear ) { Clear(); @@ -385,29 +384,37 @@ int StageWindow::RenderMenuBar() return -1; } - if(file_dialog.showFileDialog("MenuAction_Load", imgui_addons::ImGuiFileBrowser::DialogMode::OPEN, ImVec2(700, 310), ".gzs")) - { - if (Load(file_dialog.selected_path.c_str())) { - editing_file = file_dialog.selected_fn; - editing_path = file_dialog.selected_path; - - moveCwdIfNeeded(); + if (ifd::FileDialog::Instance().IsDone(window_name + "LoadStageDialog")) { + if (ifd::FileDialog::Instance().HasResult()) { + std::string res = ifd::FileDialog::Instance().GetResult().u8string(); + if (Load(res.c_str())) { + editing_file = ifd::FileDialog::Instance().GetResult().filename(); + editing_path = ifd::FileDialog::Instance().GetResult().parent_path(); + moveCwdIfNeeded(); + } } + ifd::FileDialog::Instance().Close(); } - if(file_dialog.showFileDialog("MenuAction_Save", imgui_addons::ImGuiFileBrowser::DialogMode::SAVE, ImVec2(700, 310), ".gzs")) + if (ifd::FileDialog::Instance().IsDone(window_name + "SaveStageDialog")) { - if ( !Save(file_dialog.selected_path.c_str()) ) { - editing_file = ""; - editing_path = ""; - zsys_error("Saving file failed!"); - } - else { - editing_file = file_dialog.selected_fn; - editing_path = file_dialog.selected_path; - - moveCwdIfNeeded(); + if (ifd::FileDialog::Instance().HasResult()) + { + std::string res = ifd::FileDialog::Instance().GetResult().u8string(); + if ( !Save(res.c_str()) ) + { + editing_file = ""; + editing_path = ""; + zsys_error("Saving file failed!"); + } + else + { + editing_file = ifd::FileDialog::Instance().GetResult().filename(); + editing_path = ifd::FileDialog::Instance().GetResult().parent_path(); + moveCwdIfNeeded(); + } } + ifd::FileDialog::Instance().Close(); } return 0; diff --git a/app/TextEditorWindow.cpp b/app/TextEditorWindow.cpp index 17a52b60..f3c9efdf 100644 --- a/app/TextEditorWindow.cpp +++ b/app/TextEditorWindow.cpp @@ -338,7 +338,7 @@ void TextEditorWindow::OnSaveCommand() { if (!has_associated_file || associated_file == "") { - ifd::FileDialog::Instance().Save(window_name+"SaveDialog", "Save text file", "*.py;*txt {.py,.txt}"); + ifd::FileDialog::Instance().Save(window_name+"SaveDialog", "Save text file", "*.py;*.txt {.py,.txt}"); return; } std::string textToSave = editor->GetText(); From 80ccf44c6037058d5af89f1b74015966a43601f0 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Sat, 13 Apr 2024 22:52:43 +0200 Subject: [PATCH 27/39] git remove ImGui-Addons submodule --- .gitmodules | 3 --- ext/ImGui-Addons | 1 - 2 files changed, 4 deletions(-) delete mode 160000 ext/ImGui-Addons diff --git a/.gitmodules b/.gitmodules index 8fb561ff..c31eba3c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,9 +7,6 @@ [submodule "ext/glm"] path = ext/glm url = https://github.com/g-truc/glm -[submodule "ext/ImGui-Addons"] - path = ext/ImGui-Addons - url = https://github.com/gallickgunner/ImGui-Addons [submodule "ext/StackWalker"] path = ext/StackWalker url = https://github.com/JochenKalmbach/StackWalker.git diff --git a/ext/ImGui-Addons b/ext/ImGui-Addons deleted file mode 160000 index 0b25588b..00000000 --- a/ext/ImGui-Addons +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0b25588ba842b93537f82ae84f27a75f604b04ce From 5454774e6213d4eaac3c819b1b456e7788bd5eb5 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Sat, 13 Apr 2024 23:03:03 +0200 Subject: [PATCH 28/39] remove ImGuiFileBrowser refs, set About window name --- app/AboutWindow.cpp | 2 +- app/ActorContainer.hpp | 2 +- app/App.hpp | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/AboutWindow.cpp b/app/AboutWindow.cpp index 2dd18827..d5813faa 100644 --- a/app/AboutWindow.cpp +++ b/app/AboutWindow.cpp @@ -51,7 +51,7 @@ void SelectableText(const char *buf) AboutWindow::AboutWindow() { - + window_name = "About"; } AboutWindow::~AboutWindow() diff --git a/app/ActorContainer.hpp b/app/ActorContainer.hpp index 8d934132..265fa1be 100644 --- a/app/ActorContainer.hpp +++ b/app/ActorContainer.hpp @@ -14,7 +14,7 @@ #include "libsphactor.h" #include "ImNodes.h" #include "ImNodesEz.h" -#include "ext/ImGui-Addons/FileBrowser/ImGuiFileBrowser.h" +#include namespace gzb { // actor file browser diff --git a/app/App.hpp b/app/App.hpp index c414c8ef..eca8779d 100644 --- a/app/App.hpp +++ b/app/App.hpp @@ -11,7 +11,6 @@ #include "DemoWindow.hpp" #include "imgui.h" #include "imgui_internal.h" -#include "ext/ImGui-Addons/FileBrowser/ImGuiFileBrowser.h" #include #include "fontawesome5.h" #include "config.h" From 8136696fd7d216f747e2386cd90d842cd1651d0a Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Sat, 13 Apr 2024 23:15:02 +0200 Subject: [PATCH 29/39] window not showing by default --- app/LogWindow.hpp | 2 +- app/Window.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/LogWindow.hpp b/app/LogWindow.hpp index 80e52ffe..2bd55e0a 100644 --- a/app/LogWindow.hpp +++ b/app/LogWindow.hpp @@ -25,7 +25,7 @@ struct LogWindow : public Window ImGui::PushID(123); ImGui::SetNextWindowSizeConstraints(ImVec2(100,100), ImVec2(1000,1000)); - ImGui::Begin(window_name.c_str()); + ImGui::Begin(window_name.c_str(), &showing); if (ImGui::Button("Clear")) buffer->clear(); ImGui::SameLine(); diff --git a/app/Window.hpp b/app/Window.hpp index 40b6892f..351e9ca5 100644 --- a/app/Window.hpp +++ b/app/Window.hpp @@ -10,7 +10,7 @@ struct Window virtual ~Window() {}; virtual void OnImGui() {}; - bool showing = true; + bool showing = false; std::string window_name = "noname"; }; } From 8cde00b20c54f12b7a5cf495f648373907afd8a9 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Sun, 14 Apr 2024 08:25:55 +0200 Subject: [PATCH 30/39] no path conversion on windows, force to u8string --- app/StageWindow.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/StageWindow.cpp b/app/StageWindow.cpp index b9756314..66a7e005 100644 --- a/app/StageWindow.cpp +++ b/app/StageWindow.cpp @@ -388,8 +388,8 @@ int StageWindow::RenderMenuBar() if (ifd::FileDialog::Instance().HasResult()) { std::string res = ifd::FileDialog::Instance().GetResult().u8string(); if (Load(res.c_str())) { - editing_file = ifd::FileDialog::Instance().GetResult().filename(); - editing_path = ifd::FileDialog::Instance().GetResult().parent_path(); + editing_file = ifd::FileDialog::Instance().GetResult().filename().u8string(); + editing_path = ifd::FileDialog::Instance().GetResult().parent_path().u8string(); moveCwdIfNeeded(); } } @@ -409,8 +409,8 @@ int StageWindow::RenderMenuBar() } else { - editing_file = ifd::FileDialog::Instance().GetResult().filename(); - editing_path = ifd::FileDialog::Instance().GetResult().parent_path(); + editing_file = ifd::FileDialog::Instance().GetResult().filename().u8string(); + editing_path = ifd::FileDialog::Instance().GetResult().parent_path().u8string(); moveCwdIfNeeded(); } } From ca94cd5aac95889ea73db362c0c100ecea1d9569 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Sun, 14 Apr 2024 08:28:10 +0200 Subject: [PATCH 31/39] remove old dialog code --- app/ActorContainer.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/app/ActorContainer.cpp b/app/ActorContainer.cpp index 84fff99f..eabbe04b 100644 --- a/app/ActorContainer.cpp +++ b/app/ActorContainer.cpp @@ -577,19 +577,6 @@ ActorContainer::RenderFilename(const char* name, zconfig_t *data) { } } - /*if ( actor_file_dialog.showFileDialog("Actor Open File", - streq(zoptStr, "rw" ) ? imgui_addons::ImGuiFileBrowser::DialogMode::SAVE : imgui_addons::ImGuiFileBrowser::DialogMode::OPEN, - ImVec2(700, 310), - valid_files) ) // TODO: perhaps add type hint for extensions? - { - char *path = convert_to_relative_to_wd(actor_file_dialog.selected_path.c_str()); - zconfig_set_value(zvalue, "%s", path ); - strcpy(buf, path); - //SendAPI(zapic, zapiv, zvalue, &(p) ); - zstr_free(&path); - sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), p ); - }*/ - zconfig_t *help = zconfig_locate(data, "help"); const char *helpv = "Load a file"; if (help) From 7d2c01ddcc28c6a7b8fba7a818370468481917a3 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Sun, 14 Apr 2024 18:59:28 +0200 Subject: [PATCH 32/39] only stdout redir on unix platforms --- main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.cpp b/main.cpp index b048d04b..e748acbb 100644 --- a/main.cpp +++ b/main.cpp @@ -352,9 +352,11 @@ int main(int argc, char** argv) if (!headless) { memset(huge_string_buf,0,4096); +#ifdef __UNIX__ int rc = pipe(out_pipe); assert( rc == 0 ); gzb::capture_stdio(out_pipe[0], out_pipe[1]); +#endif } // try to init SDL and otherwise run headless From e951ed01d83b4a18c187227d3229d7163b60bef1 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Mon, 15 Apr 2024 14:23:26 +0200 Subject: [PATCH 33/39] remove unused code --- main.cpp | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/main.cpp b/main.cpp index e748acbb..0720f014 100644 --- a/main.cpp +++ b/main.cpp @@ -71,7 +71,6 @@ const char* glsl_version; static ImWchar ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 }; GZB_GLOBALS_t GZB_GLOBAL; // Logging -char huge_string_buf[4096]; int out_pipe[2]; // exit handlers et al @@ -351,7 +350,6 @@ int main(int argc, char** argv) if (!headless) { - memset(huge_string_buf,0,4096); #ifdef __UNIX__ int rc = pipe(out_pipe); assert( rc == 0 ); @@ -422,19 +420,6 @@ int main(int argc, char** argv) return 0; } -ImVector actor_types; -void UpdateRegisteredActorsCache() { - zhash_t *hash = sphactor_get_registered(); - zlist_t *list = zhash_keys(hash); - actor_types.clear(); - - char* item = (char*)zlist_first(list); - while( item ) { - actor_types.push_back(item); - item = (char*)zlist_next(list); - } -} - std::map max_actors_by_type; void register_actors() { // register stock actors @@ -472,11 +457,6 @@ void register_actors() { PyGILState_Release(gstate); /* End check newer version */ #endif - //enforcable maximum actor counts - max_actors_by_type.insert(std::make_pair("NatNet", 1)); - max_actors_by_type.insert(std::make_pair("OpenVR", 1)); - - UpdateRegisteredActorsCache(); } int SDLInit( SDL_Window** window, SDL_GLContext* gl_context, const char** glsl_version ) { From 717cef7a1a6269bdcccdb8325f398a34bbbad4ca Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Mon, 15 Apr 2024 14:24:31 +0200 Subject: [PATCH 34/39] fix actor stage context menu --- app/StageWindow.cpp | 11 ++++++++++- app/StageWindow.hpp | 1 - 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/StageWindow.cpp b/app/StageWindow.cpp index 66a7e005..5523aebf 100644 --- a/app/StageWindow.cpp +++ b/app/StageWindow.cpp @@ -38,6 +38,10 @@ inline ImU32 LerpImU32( ImU32 c1, ImU32 c2, int index, int total, float offset, StageWindow::StageWindow() { window_name = "Stage Window"; + // very hackish still + // enforcable maximum actor counts + max_actors_by_type.insert(std::make_pair("NatNet", 1)); + max_actors_by_type.insert(std::make_pair("OpenVR", 1)); } StageWindow::~StageWindow() @@ -721,7 +725,11 @@ int StageWindow::UpdateActors(float deltaTime) if (ImGui::BeginPopup("NodesContextMenu")) { //TODO: Fetch updated available nodes? - for (const auto desc : actor_types) + + zlist_t *actor_types = zhash_keys(sphactor_get_registered()); + assert(actor_types); + char *desc = (char *)zlist_first(actor_types); + while(desc != NULL) { if (ImGui::MenuItem(desc)) { @@ -741,6 +749,7 @@ int StageWindow::UpdateActors(float deltaTime) RegisterCreateAction(actor); } } + desc = (char *)zlist_next(actor_types); } ImGui::Separator(); diff --git a/app/StageWindow.hpp b/app/StageWindow.hpp index 40158b3d..374399ae 100644 --- a/app/StageWindow.hpp +++ b/app/StageWindow.hpp @@ -65,7 +65,6 @@ class StageWindow : public Window sph_stage_t *stage = NULL; std::string editing_file = ""; std::string editing_path = ""; - ImVector actor_types; std::vector actors; std::map max_actors_by_type; std::stack undoStack; From c2b45a95aa381a57e8a1fb5c60c530b94e910da9 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Tue, 16 Apr 2024 10:44:45 +0200 Subject: [PATCH 35/39] prepare multiviewport code in main.cpp still buggy so disabled by def, slight cleanup of code --- app/App.hpp | 198 ---------------------------------------------------- main.cpp | 31 ++++++-- 2 files changed, 27 insertions(+), 202 deletions(-) diff --git a/app/App.hpp b/app/App.hpp index eca8779d..cd9ace62 100644 --- a/app/App.hpp +++ b/app/App.hpp @@ -36,205 +36,7 @@ struct App for (auto w : text_editors) delete w; }; - - int OnImGuiMenuBar() - { - static char* configFile = new char[64] { 0x0 }; - static int keyFocus = 0; - MenuAction action = MenuAction_None; - - if ( keyFocus > 0 ) - keyFocus--; - - ImGui::BeginMainMenuBar(); - - if ( ImGui::BeginMenu("File") ) { - if ( ImGui::MenuItem(ICON_FA_FOLDER_OPEN " Load") ) { - //TODO: support checking if changes were made - action = MenuAction_Load; - } - if ( ImGui::MenuItem(ICON_FA_SAVE " Save") ) { - action = MenuAction_Save; - } - if ( ImGui::MenuItem(ICON_FA_SAVE " Save As") ) { - action = MenuAction_SaveAs; - } - ImGui::Separator(); - ImGui::Separator(); - if ( ImGui::MenuItem(ICON_FA_WINDOW_CLOSE " Exit") ) { - //TODO: support checking if changes were made - action = MenuAction_Exit; - } - - ImGui::EndMenu(); - } - - if ( ImGui::BeginMenu("Stage") ) { - if ( ImGui::MenuItem(ICON_FA_TRASH_ALT " Clear") ) { - action = MenuAction_Clear; - } - ImGui::EndMenu(); - } - - if ( ImGui::BeginMenu("Tools") ) { - if ( ImGui::BeginMenu(ICON_FA_EDIT " Text Editor") ) - { - if ( ImGui::MenuItem(ICON_FA_FOLDER_OPEN " New editor") ) - { - gzb::App::getApp().text_editors.push_back(new gzb::TextEditorWindow()); - } - for (auto w : gzb::App::getApp().text_editors ) - { - std::string title = w->window_name + " " + ICON_FA_EYE; - if ( ImGui::MenuItem(w->window_name.c_str(), NULL, w->showing, true ) ) - w->showing = !w->showing; - } - ImGui::EndMenu(); - } - if ( ImGui::MenuItem(ICON_FA_TERMINAL " Toggle Log Console") ) { - gzb::App::getApp().log_win.showing = !gzb::App::getApp().log_win.showing; - } -#ifdef HAVE_IMGUI_DEMO - if ( ImGui::MenuItem(ICON_FA_CARAVAN " Toggle Demo") ) { - gzb::App::getApp().demo_win.showing = !gzb::App::getApp().demo_win.showing; - } -#endif - if ( ImGui::MenuItem(ICON_FA_INFO " Toggle About") ) { - gzb::App::getApp().about_win.showing = !gzb::App::getApp().about_win.showing; - } - - ImGui::EndMenu(); - } - - //TODO: Display stage status (new, loaded, changed) - ImGui::Separator(); - char path[PATH_MAX]; - getcwd(path, PATH_MAX); - - ImGui::Separator(); - ImGui::TextColored( ImVec4(.3,.5,.3,1), ICON_FA_FOLDER ": %s", path); - - if ( ImGui::IsItemHovered() ) - { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 24.0f); - ImGui::TextUnformatted("Current working directory, double click to open"); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - - if ( ImGui::IsMouseDoubleClicked(0) ) - { - char cmd[PATH_MAX]; -#ifdef __WINDOWS__ - snprintf(cmd, PATH_MAX, "start explorer %s", path); - //snprintf(cmd, PATH_MAX, "explorer.exe %s", path); - //STARTUPINFO startupInfo = {0}; - //startupInfo.cb = sizeof(startupInfo); - //PROCESS_INFORMATION processInformation; - //CreateProcess(NULL, cmd, NULL, NULL, false, 0, NULL, NULL, &startupInfo, &processInformation); -#elif defined __UTYPE_LINUX - snprintf(cmd, PATH_MAX, "xdg-open %s &", path); -#else - snprintf(cmd, PATH_MAX, "open %s", path); -#endif - system(cmd); - } - } - - ImGui::Separator(); - - - if ( streq( stage_win.editing_file.c_str(), "" ) ) { - ImGui::TextColored( ImVec4(.7,.9,.7,1), ICON_FA_EDIT ": *Unsaved Stage*"); - } - else { - ImGui::TextColored( ImVec4(.7,.9,.7,1), ICON_FA_EDIT ": %s", stage_win.editing_file.c_str()); - } - - if (GZB_GLOBAL.UPDATE_AVAIL) - { - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 64); - ImGui::TextColored( ImVec4(.99,.9,.7,1), "update " ICON_FA_INFO_CIRCLE ); - if ( ImGui::IsItemHovered() ) - { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 24.0f); - ImGui::TextUnformatted("Gazebosc update available, click to download"); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - - if ( ImGui::IsMouseClicked(0) ) - { - char cmd[PATH_MAX]; - char url[60] = "https://pong.hku.nl/~buildbot/gazebosc/"; -#ifdef __WINDOWS__ - snprintf(cmd, PATH_MAX, "start %s", url); -#elif defined __UTYPE_LINUX - snprintf(cmd, PATH_MAX, "xdg-open %s &", url); -#else - snprintf(cmd, PATH_MAX, "open %s", url); -#endif - system(cmd); - } - } - } - - ImGui::EndMainMenuBar(); - - // Handle MenuActions - if ( action == MenuAction_Load) { - ImGui::OpenPopup("MenuAction_Load"); - keyFocus = 2; - } - else if ( action == MenuAction_Save ) { - if ( streq( stage_win.editing_file.c_str(), "" ) ) { - ImGui::OpenPopup("MenuAction_Save"); - } - else { - stage_win.Save(stage_win.editing_path.c_str()); - ImGui::CloseCurrentPopup(); - } - } - else if ( action == MenuAction_SaveAs ) { - ImGui::OpenPopup("MenuAction_Save"); - } - else if ( action == MenuAction_Clear ) { - stage_win.Clear(); - stage_win.Init(); - } - else if ( action == MenuAction_Exit ) { - return -1; - } - - /*if(file_dialog.showFileDialog("MenuAction_Load", imgui_addons::ImGuiFileBrowser::DialogMode::OPEN, ImVec2(700, 310), ".gzs")) - { - if (Load(file_dialog.selected_path.c_str())) { - editingFile = file_dialog.selected_fn; - editingPath = file_dialog.selected_path; - - moveCwdIfNeeded(); - } - } - - if(file_dialog.showFileDialog("MenuAction_Save", imgui_addons::ImGuiFileBrowser::DialogMode::SAVE, ImVec2(700, 310), ".gzs")) - { - if ( !Save(file_dialog.selected_path.c_str()) ) { - editingFile = ""; - editingPath = ""; - zsys_error("Saving file failed!"); - } - else { - editingFile = file_dialog.selected_fn; - editingPath = file_dialog.selected_path; - - moveCwdIfNeeded(); - } - }*/ - - return 0; - } }; - static void capture_stdio(int pipe_in, int pipe_out) { #ifdef __WINDOWS__ diff --git a/main.cpp b/main.cpp index 0720f014..9d740a9d 100644 --- a/main.cpp +++ b/main.cpp @@ -33,7 +33,6 @@ #include "imgui_impl_sdl2.h" #include "imgui_impl_opengl3.h" #include "imgui_internal.h" -#include "fontawesome5.h" #define SDL_MAIN_HANDLED #include #if defined(IMGUI_IMPL_OPENGL_ES2) @@ -492,7 +491,6 @@ int SDLInit( SDL_Window** window, SDL_GLContext* gl_context, const char** glsl_v #endif // Create window with graphics context - //TODO: Make this a skippable part of the system... SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); @@ -510,11 +508,25 @@ ImGuiIO& ImGUIInit(SDL_Window* window, SDL_GLContext* gl_context, const char* gl IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_DockingEnable; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking + //io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows + //io.ConfigViewportsNoAutoMerge = true; + //io.ConfigViewportsNoTaskBarIcon = true; + // Setup Dear ImGui style ImGui::StyleColorsDark(); + // When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones. + ImGuiStyle& style = ImGui::GetStyle(); + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + style.WindowRounding = 0.0f; + style.Colors[ImGuiCol_WindowBg].w = 1.0f; + } + // Setup Platform/Renderer bindings ImGui_ImplSDL2_InitForOpenGL(window, gl_context); ImGui_ImplOpenGL3_Init(glsl_version); @@ -610,7 +622,7 @@ void UILoop( SDL_Window* window, ImGuiIO& io ) { // For when we send msgs to the main thread if (deltaTime < 1000/30) SDL_Delay((1000/30)-deltaTime); - //printf("fps %.2f %i\n", 1000./(SDL_GetTicks() - oldTime), deltaTime); + //printf("fps %.2f %i, Application average %.3f ms/frame (%.1f FPS)\n", 1000./(SDL_GetTicks() - oldTime), deltaTime, 1000.0f / io.Framerate, io.Framerate); oldTime = SDL_GetTicks(); //main window @@ -653,6 +665,17 @@ void UILoop( SDL_Window* window, ImGuiIO& io ) { glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w); glClear(GL_COLOR_BUFFER_BIT); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + // Update and Render additional Platform Windows + // (Platform functions may change the current OpenGL context, so we save/restore it to make it easier to paste this code elsewhere. + // For this specific demo app we could also call SDL_GL_MakeCurrent(window, gl_context) directly) + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + SDL_Window* backup_current_window = SDL_GL_GetCurrentWindow(); + SDL_GLContext backup_current_context = SDL_GL_GetCurrentContext(); + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + SDL_GL_MakeCurrent(backup_current_window, backup_current_context); + } SDL_GL_SwapWindow(window); } } From 4284e44afd820a36d44ca38b9d748f38ca71ab84 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Tue, 16 Apr 2024 11:08:19 +0200 Subject: [PATCH 36/39] remove deltaTime argument as it not used, move window calls to App class --- app/ActorContainer.cpp | 2 +- app/ActorContainer.hpp | 2 +- app/App.hpp | 31 +++++++++++++++++++++++++++++++ app/StageWindow.cpp | 4 ++-- app/StageWindow.hpp | 2 +- main.cpp | 26 +------------------------- 6 files changed, 37 insertions(+), 30 deletions(-) diff --git a/app/ActorContainer.cpp b/app/ActorContainer.cpp index eabbe04b..14d18b94 100644 --- a/app/ActorContainer.cpp +++ b/app/ActorContainer.cpp @@ -198,7 +198,7 @@ ActorContainer::SendAPI(zconfig_t *zapic, zconfig_t *zapiv, zconfig_t *zvalue, T void -ActorContainer::Render(float deltaTime) { +ActorContainer::Render() { //loop through each data element in capabilities if ( this->capabilities == NULL ) return; diff --git a/app/ActorContainer.hpp b/app/ActorContainer.hpp index 265fa1be..adb55816 100644 --- a/app/ActorContainer.hpp +++ b/app/ActorContainer.hpp @@ -87,7 +87,7 @@ struct ActorContainer void HandleAPICalls(zconfig_t * data); template void SendAPI(zconfig_t *zapic, zconfig_t *zapiv, zconfig_t *zvalue, T * value); - void Render(float deltaTime); + void Render(); void RenderCustomReport(); void RenderList(const char *name, zconfig_t *data); void RenderMediacontrol(const char* name, zconfig_t *data); diff --git a/app/App.hpp b/app/App.hpp index cd9ace62..36e83c92 100644 --- a/app/App.hpp +++ b/app/App.hpp @@ -36,6 +36,37 @@ struct App for (auto w : text_editors) delete w; }; + + int Update() { + // root window + int rc = stage_win.UpdateActors(); + if ( rc == -1) + return rc; + + // text editor windows + std::vector::iterator itr = text_editors.begin(); + while ( itr < text_editors.end() ) + { + if ( (*itr)->showing ) + (*itr)->OnImGui(); + if ( (*itr)->requesting_destroy ) + { + delete(*itr); + itr = text_editors.erase(itr); + } + else + ++itr; + } + if (about_win.showing) + about_win.OnImGui(); + if (log_win.showing) + log_win.OnImGui(); + if (demo_win.showing) + demo_win.OnImGui(); + + return rc; + }; + }; static void capture_stdio(int pipe_in, int pipe_out) { diff --git a/app/StageWindow.cpp b/app/StageWindow.cpp index 5523aebf..20c7db9a 100644 --- a/app/StageWindow.cpp +++ b/app/StageWindow.cpp @@ -424,7 +424,7 @@ int StageWindow::RenderMenuBar() return 0; } -int StageWindow::UpdateActors(float deltaTime) +int StageWindow::UpdateActors() { static std::vector selectedActors; static std::vector actorClipboardType; @@ -631,7 +631,7 @@ int StageWindow::UpdateActors(float deltaTime) ImNodes::Ez::InputSlots(actor->input_slots.data(), actor->input_slots.size()); // Custom node content may go here - actor->Render(deltaTime); + actor->Render(); // Render output slots second (order is important) ImNodes::Ez::OutputSlots(actor->output_slots.data(), actor->output_slots.size()); diff --git a/app/StageWindow.hpp b/app/StageWindow.hpp index 374399ae..a50a5bf9 100644 --- a/app/StageWindow.hpp +++ b/app/StageWindow.hpp @@ -77,7 +77,7 @@ class StageWindow : public Window bool Load( const char* configFile ); bool Save( const char* configFile ); int RenderMenuBar(); - int UpdateActors(float deltaTime); + int UpdateActors(); ActorContainer *Find(const char *endpoint); ActorContainer * CreateFromType( const char* typeStr, const char* uuidStr ); int CountActorsOfType( const char* type ); diff --git a/main.cpp b/main.cpp index 9d740a9d..ab62d09e 100644 --- a/main.cpp +++ b/main.cpp @@ -631,31 +631,7 @@ void UILoop( SDL_Window* window, ImGuiIO& io ) { ImVec2 size = ImVec2(w,h); ImGui::SetNextWindowSize(size); - // root window - int rc = app.stage_win.UpdateActors(deltaTime); - - // text editor windows - std::vector::iterator itr = app.text_editors.begin(); - while ( itr < app.text_editors.end() ) - { - if ( (*itr)->showing ) - (*itr)->OnImGui(); - if ( (*itr)->requesting_destroy ) - { - delete(*itr); - itr = app.text_editors.erase(itr); - } - else - ++itr; - } - if (app.about_win.showing) - app.about_win.OnImGui(); - if (app.log_win.showing) - app.log_win.OnImGui(); - if (app.demo_win.showing) - app.demo_win.OnImGui(); - - + int rc = app.Update(); if ( rc == -1 ) { stop = 1; } From 5e7eb9080eee9441adc5981e2b917bc8f711dd24 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Tue, 16 Apr 2024 11:37:37 +0200 Subject: [PATCH 37/39] added a screenshot --- README.md | 2 ++ dist/screenshot.png | Bin 0 -> 48153 bytes 2 files changed, 2 insertions(+) create mode 100644 dist/screenshot.png diff --git a/README.md b/README.md index 5aa566d4..79f87461 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ OSX/Linux: [![Build Status](https://api.travis-ci.org/hku-ect/gazebosc.png?branc Gazebosc is a high level actor model programming environment. You can visually create actors and author them using a nodal UI. Actors are running concurrently and can be programmed sequentially using C, C++ or Python. Actors communicate using the [OSC](https://en.wikipedia.org/wiki/Open_Sound_Control) serialisation format. +![screenshot](dist/screenshot.png?raw=true) + ## Prebuilt binaries CI tested binaries with bundled Python are available from: diff --git a/dist/screenshot.png b/dist/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..13bfe1e4509c4114e99a57ba58c2d2005824d6d5 GIT binary patch literal 48153 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�cd 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 ZAi%#YFw1tItZ4^QQ`S-{Rxl6#zW`^6ZJhuB literal 0 HcmV?d00001 From 75dbffff0c8409f93b53225a055bd86c60673d06 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Tue, 16 Apr 2024 12:58:29 +0200 Subject: [PATCH 38/39] fix goto line in texteditor --- app/TextEditorWindow.cpp | 16 +--------------- app/TextEditorWindow.hpp | 2 -- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/app/TextEditorWindow.cpp b/app/TextEditorWindow.cpp index f3c9efdf..169fbe9f 100644 --- a/app/TextEditorWindow.cpp +++ b/app/TextEditorWindow.cpp @@ -219,8 +219,7 @@ void TextEditorWindow::OnImGui() targetLineFixed = targetLine < 1 ? 0 : targetLine - 1; editor->ClearExtraCursors(); editor->ClearSelections(); - //editor->SetSelection({ targetLineFixed, 0 }, { targetLineFixed, editor->GetLineMaxColumn(targetLineFixed) }); - CenterViewAtLine(targetLineFixed); + editor->SetCursorPosition( targetLineFixed, 0, -1); ImGui::CloseCurrentPopup(); ImGui::GetIO().ClearInputKeys(); } @@ -243,7 +242,6 @@ void TextEditorWindow::OnImGui() editor->ClearExtraCursors(); editor->SelectNextOccurrenceOf(ctrlf_text_to_find, toFindTextSize, ctrlf_case_sensitive); TextEditor::Coordinates nextOccurrenceLine = editor->GetCursorPosition(); - CenterViewAtLine(nextOccurrenceLine.mLine); } //if (ImGui::Button("Find all") && toFindTextSize > 0) // editor->SelectAllOccurrencesOf(ctrlf_text_to_find, toFindTextSize, ctrlf_case_sensitive); @@ -284,18 +282,6 @@ void TextEditorWindow::OnImGui() ImGui::End(); } -void TextEditorWindow::SetSelection(int startLine, int startChar, int endLine, int endChar) -{ - editor->SetCursorPosition(endLine, endChar); - editor->SetSelectionStart(TextEditor::Coordinates(startLine, startChar)); - editor->SetSelectionEnd(TextEditor::Coordinates(endLine, endChar)); -} - -void TextEditorWindow::CenterViewAtLine(int line) -{ - //editor->SetViewAtLine(line, TextEditor::SetViewAtLineMode::Centered); -} - const char* TextEditorWindow::GetAssociatedFile() { if (!has_associated_file) diff --git a/app/TextEditorWindow.hpp b/app/TextEditorWindow.hpp index aee8b8d0..8a24798e 100644 --- a/app/TextEditorWindow.hpp +++ b/app/TextEditorWindow.hpp @@ -16,8 +16,6 @@ struct TextEditorWindow : public Window void OnImGui() override; - void SetSelection(int startLine, int startChar, int endLine, int endChar); - void CenterViewAtLine(int line); const char* GetAssociatedFile(); void OnReloadCommand(); From ddaf60c65481ff65b20c7492591bddf325d5003d Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Tue, 16 Apr 2024 13:03:46 +0200 Subject: [PATCH 39/39] fix texteditor noy showing on first click --- app/ActorContainer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/app/ActorContainer.cpp b/app/ActorContainer.cpp index 14d18b94..44d7f2b3 100644 --- a/app/ActorContainer.cpp +++ b/app/ActorContainer.cpp @@ -1128,6 +1128,7 @@ ActorContainer::OpenTextEditor(const char *filepath) if (txtwin == NULL) { txtwin = new gzb::TextEditorWindow(filepath); + txtwin->showing = true; gzb::App::getApp().text_editors.push_back(txtwin); } else