From 0c10dab3633d39230f2a08102077687bae1a56c3 Mon Sep 17 00:00:00 2001 From: "sixten.hugosson" Date: Sat, 17 Jun 2023 19:00:55 +0200 Subject: [PATCH] Initial commit --- .gitignore | 1 + code/build.bat | 12 + code/gen.cpp | 15 + code/gen_opengl.cpp | 140 + code/gen_ui.cpp | 127 + code/generated/vn_generated_ui.cpp | 564 ++ code/generated/vn_generated_ui.h | 42 + code/generated/vn_opengl_functions.h | 268 + code/opengl_render.cpp | 537 ++ code/opengl_render.h | 41 + code/third_party/stb_image.h | 7987 ++++++++++++++++++++++++++ code/third_party/stb_sprintf.h | 1942 +++++++ code/third_party/stb_truetype.h | 5077 ++++++++++++++++ code/vn.cpp | 62 + code/vn_animation_curve.cpp | 150 + code/vn_animation_curve.h | 49 + code/vn_core.h | 162 + code/vn_font.cpp | 224 + code/vn_font.h | 110 + code/vn_math.h | 114 + code/vn_memory.h | 222 + code/vn_opengl_defines.h | 611 ++ code/vn_platform.cpp | 69 + code/vn_platform.h | 212 + code/vn_render.cpp | 244 + code/vn_render.h | 91 + code/vn_string.h | 227 + code/vn_text_op.h | 295 + code/vn_theme_dark.h | 11 + code/vn_thread_context.h | 61 + code/vn_types.h | 198 + code/vn_ui.cpp | 842 +++ code/vn_ui.h | 203 + code/vn_ui_utils.cpp | 166 + code/vn_workspace.cpp | 758 +++ code/vn_workspace.h | 121 + code/vn_workspace_commands.cpp | 83 + code/vn_workspace_editor.cpp | 232 + code/vn_workspace_editor.h | 45 + code/vn_workspace_view.cpp | 419 ++ code/vn_workspace_view.h | 68 + code/win32_main.cpp | 679 +++ code/win32_main.h | 47 + code/win32_opengl.cpp | 120 + features.txt | 5 + fonts/Roboto-Bold.ttf | Bin 0 -> 128676 bytes fonts/Roboto-Regular.ttf | Bin 0 -> 168260 bytes fonts/icons.ttf | Bin 0 -> 13792 bytes fonts/liberation-mono.ttf | Bin 0 -> 108168 bytes project.4coder | 73 + todo.txt | 11 + 51 files changed, 23737 insertions(+) create mode 100644 .gitignore create mode 100644 code/build.bat create mode 100644 code/gen.cpp create mode 100644 code/gen_opengl.cpp create mode 100644 code/gen_ui.cpp create mode 100644 code/generated/vn_generated_ui.cpp create mode 100644 code/generated/vn_generated_ui.h create mode 100644 code/generated/vn_opengl_functions.h create mode 100644 code/opengl_render.cpp create mode 100644 code/opengl_render.h create mode 100644 code/third_party/stb_image.h create mode 100644 code/third_party/stb_sprintf.h create mode 100644 code/third_party/stb_truetype.h create mode 100644 code/vn.cpp create mode 100644 code/vn_animation_curve.cpp create mode 100644 code/vn_animation_curve.h create mode 100644 code/vn_core.h create mode 100644 code/vn_font.cpp create mode 100644 code/vn_font.h create mode 100644 code/vn_math.h create mode 100644 code/vn_memory.h create mode 100644 code/vn_opengl_defines.h create mode 100644 code/vn_platform.cpp create mode 100644 code/vn_platform.h create mode 100644 code/vn_render.cpp create mode 100644 code/vn_render.h create mode 100644 code/vn_string.h create mode 100644 code/vn_text_op.h create mode 100644 code/vn_theme_dark.h create mode 100644 code/vn_thread_context.h create mode 100644 code/vn_types.h create mode 100644 code/vn_ui.cpp create mode 100644 code/vn_ui.h create mode 100644 code/vn_ui_utils.cpp create mode 100644 code/vn_workspace.cpp create mode 100644 code/vn_workspace.h create mode 100644 code/vn_workspace_commands.cpp create mode 100644 code/vn_workspace_editor.cpp create mode 100644 code/vn_workspace_editor.h create mode 100644 code/vn_workspace_view.cpp create mode 100644 code/vn_workspace_view.h create mode 100644 code/win32_main.cpp create mode 100644 code/win32_main.h create mode 100644 code/win32_opengl.cpp create mode 100644 features.txt create mode 100644 fonts/Roboto-Bold.ttf create mode 100644 fonts/Roboto-Regular.ttf create mode 100644 fonts/icons.ttf create mode 100644 fonts/liberation-mono.ttf create mode 100644 project.4coder create mode 100644 todo.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d163863 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ \ No newline at end of file diff --git a/code/build.bat b/code/build.bat new file mode 100644 index 0000000..c3c8dfc --- /dev/null +++ b/code/build.bat @@ -0,0 +1,12 @@ +@echo off + +set CommonCompilerOptions=/Zi /FC /nologo /DVN_INTERNAL=1 /DVN_SLOW=1 /Oi /W4 /WL /WX /wd4201 /wd4305 /wd4244 /wd4100 /wd4505 + +pushd "../build/" + +REM cl %CommonCompilerOptions% ../code/gen.cpp +REM gen.exe + +cl %CommonCompilerOptions% ../code/vn.cpp /LD /link /export:VN_UpdateAndRender /incremental:no +cl %CommonCompilerOptions% ../code/win32_main.cpp /link user32.lib gdi32.lib winmm.lib opengl32.lib +popd \ No newline at end of file diff --git a/code/gen.cpp b/code/gen.cpp new file mode 100644 index 0000000..efbd7f5 --- /dev/null +++ b/code/gen.cpp @@ -0,0 +1,15 @@ +#include "vn_core.h" +#include + +#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0])) + +#include "gen_opengl.cpp" +#include "gen_ui.cpp" + +int main(int ArgumentCount, char **Arguments) +{ + GenOpenGL(); + GenUI(); + + return(0); +} \ No newline at end of file diff --git a/code/gen_opengl.cpp b/code/gen_opengl.cpp new file mode 100644 index 0000000..05aa922 --- /dev/null +++ b/code/gen_opengl.cpp @@ -0,0 +1,140 @@ +struct opengl_function +{ + char *Name; + char *Type; + char *Arguments; +}; + +struct opengl_function OpenGLFunctionEntries[] = +{ + {"BindTexture", "void", "GLenum target, GLuint texture"}, + {"BlendFunc", "void", "GLenum sfactor, GLenum dfactor"}, + {"Clear", "void", "GLbitfield mask"}, + {"ClearAccum", "void", "GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha"}, + {"ClearColor", "void", "GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha"}, + {"ClearDepth", "void", "GLclampd depth"}, + {"ClearIndex", "void", "GLfloat c"}, + {"ClearStencil", "void", "GLint s"}, + {"ClipPlane", "void", "GLenum plane, const GLdouble *equation"}, + {"CullFace", "void", "GLenum mode"}, + {"DeleteTextures", "void", "GLsizei n, const GLuint *textures"}, + {"Disable", "void", "GLenum cap"}, + {"DrawArrays", "void", "GLenum mode, GLint first, GLsizei count"}, + {"DrawBuffer", "void", "GLenum mode"}, + {"DrawElements", "void", "GLenum mode, GLsizei count, GLenum type, const GLvoid *indices"}, + {"DrawArraysInstanced", "void", "GLenum mode, GLint first, GLsizei count, GLsizei instancecount"}, + {"Enable", "void", "GLenum cap"}, + {"GenTextures", "void", "GLsizei n, GLuint *textures"}, + {"GetClipPlane", "void", "GLenum plane, GLdouble *equation"}, + {"GetDoublev", "void", "GLenum pname, GLdouble *params"}, + {"GetError", "GLenum", "void"}, + {"GetFloatv", "void", "GLenum pname, GLfloat *params"}, + {"GetIntegerv", "void", "GLenum pname, GLint *params"}, + {"GetPointerv", "void", "GLenum pname, GLvoid* *params"}, + {"GetString", "const GLubyte *", "GLenum name"}, + {"GetTexEnvfv", "void", "GLenum target, GLenum pname, GLfloat *params"}, + {"GetTexEnviv", "void", "GLenum target, GLenum pname, GLint *params"}, + {"GetTexGendv", "void", "GLenum coord, GLenum pname, GLdouble *params"}, + {"GetTexGenfv", "void", "GLenum coord, GLenum pname, GLfloat *params"}, + {"GetTexGeniv", "void", "GLenum coord, GLenum pname, GLint *params"}, + {"GetTexImage", "void", "GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels"}, + {"GetTexLevelParameterfv", "void", "GLenum target, GLint level, GLenum pname, GLfloat *params"}, + {"GetTexLevelParameteriv", "void", "GLenum target, GLint level, GLenum pname, GLint *params"}, + {"GetTexParameterfv", "void", "GLenum target, GLenum pname, GLfloat *params"}, + {"GetTexParameteriv", "void", "GLenum target, GLenum pname, GLint *params"}, + {"Hint", "void", "GLenum target, GLenum mode"}, + {"IsTexture", "GLboolean", "GLuint texture"}, + {"LineWidth", "void", "GLfloat width"}, + {"ListBase", "void", "GLuint base"}, + {"LoadName", "void", "GLuint name"}, + {"LogicOp", "void", "GLenum opcode"}, + {"PointSize", "void", "GLfloat size"}, + {"PolygonMode", "void", "GLenum face, GLenum mode"}, + {"Scissor", "void", "GLint x, GLint y, GLsizei width, GLsizei height"}, + {"TexImage1D", "void", "GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels"}, + {"TexImage2D", "void", "GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels"}, + {"TexParameterf", "void", "GLenum target, GLenum pname, GLfloat param"}, + {"TexParameterfv", "void", "GLenum target, GLenum pname, const GLfloat *params"}, + {"TexParameteri", "void", "GLenum target, GLenum pname, GLint param"}, + {"TexParameteriv", "void", "GLenum target, GLenum pname, const GLint *params"}, + {"TexSubImage1D", "void", "GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels"}, + {"TexSubImage2D", "void", "GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels"}, + {"CompressedTexImage2D", "void", "GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *pixels"}, + {"ActiveTexture", "void", "GLenum texture"}, + {"Viewport", "void", "GLint x, GLint y, GLsizei width, GLsizei height"}, + {"GenBuffers", "void", "GLsizei n, GLuint *buffers"}, + {"BindBuffer", "void", "GLenum target, GLuint buffer"}, + {"BufferData", "void", "GLenum target, GLsizeiptr size, const void *data, GLenum usage"}, + {"BufferSubData", "void", "GLenum target, GLintptr offset, GLsizeiptr size, const void *data"}, + {"GenVertexArrays", "void", "GLsizei n, GLuint *arrays"}, + {"BindVertexArray", "void", "GLenum array"}, + {"GetAttribLocation", "GLint", "GLuint program, const GLchar *name"}, + {"EnableVertexAttribArray", "void", "GLuint index"}, + {"DisableVertexAttribArray", "void", "GLuint index"}, + {"VertexAttribPointer", "void", "GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer"}, + {"VertexAttribIPointer", "void", "GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer"}, + {"VertexAttribLPointer", "void", "GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer"}, + {"VertexAttribDivisor", "void", "GLuint index, GLuint divisor"}, + {"CreateShader", "GLuint", "GLenum type"}, + {"ShaderSource", "void", "GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length"}, + {"CompileShader", "void", "GLuint shader"}, + {"DeleteShader", "void", "GLuint shader"}, + {"GetShaderiv", "void", "GLuint shader, GLenum pname, GLint *params"}, + {"GetShaderInfoLog", "void", "GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog"}, + {"CreateProgram", "GLuint", "void"}, + {"UseProgram", "void", "GLuint program"}, + {"AttachShader", "void", "GLuint program, GLuint shader"}, + {"DeleteProgram", "void", "GLuint program"}, + {"LinkProgram", "void", "GLuint program"}, + {"GetProgramiv", "void", "GLuint program, GLenum pname, GLint *params"}, + {"GetProgramInfoLog", "void", "GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog"}, + {"GetUniformLocation", "GLint", "GLuint program, const GLchar *name"}, + {"Uniform1i", "void", "GLint location, GLint v0"}, + {"Uniform2f", "void", "GLint location, GLfloat v0, GLfloat v1"}, + {"Uniform3f", "void", "GLint location, GLfloat v0, GLfloat v1, GLfloat v2"}, + {"UniformMatrix4fv", "void", "GLint location, GLsizei count, GLboolean transpose, const GLfloat *value"}, + {"DebugMessageCallback", "void", "void (*)(GLenum, GLenum, GLuint, GLenum, GLsizei, const GLchar *, const void *), const void *userParam"}, +}; + +static void GenOpenGL(void) +{ + FILE *Out = fopen("../code/generated/vn_opengl_functions.h", "wb"); + if(Out) + { + for(s32 Index = 0; + Index < ArrayCount(OpenGLFunctionEntries); + ++Index) + { + struct opengl_function *Function = OpenGLFunctionEntries + Index; + fprintf(Out, "typedef %s opengl_%s(%s);\n", Function->Type, Function->Name, Function->Arguments); + } + + fprintf(Out, "\n"); + + for(s32 Index = 0; + Index < ArrayCount(OpenGLFunctionEntries); + ++Index) + { + struct opengl_function *Function = OpenGLFunctionEntries + Index; + fprintf(Out, "global opengl_%s *gl%s = 0;\n", Function->Name, Function->Name); + } + + fprintf(Out, "\n"); + fprintf(Out, "extern void *OpenGL_LoadFunction(char *);\n"); + fprintf(Out, "\n"); + fprintf(Out, "static void OpenGL_LoadAllFunctions(void)\n"); + fprintf(Out, "{\n"); + + for(s32 Index = 0; + Index < ArrayCount(OpenGLFunctionEntries); + ++Index) + { + struct opengl_function *Function = OpenGLFunctionEntries + Index; + fprintf(Out, "\tgl%s = (opengl_%s *)OpenGL_LoadFunction(\"gl%s\");\n", Function->Name, Function->Name, Function->Name); + } + + fprintf(Out, "}\n"); + + fclose(Out); + } +} \ No newline at end of file diff --git a/code/gen_ui.cpp b/code/gen_ui.cpp new file mode 100644 index 0000000..a3377b2 --- /dev/null +++ b/code/gen_ui.cpp @@ -0,0 +1,127 @@ +struct ui_style_stack +{ + char *Name; + char *Type; + char *MemberName; +}; + +ui_style_stack UIStyleStacks[] = +{ + { "Parent", "ui_box *", "Parent" }, + { "Width", "ui_size", "SemanticSize[Axis2_X]" }, + { "Height", "ui_size", "SemanticSize[Axis2_Y]" }, + { "FixedX", "r32", "FixedP.E[Axis2_X]" }, + { "FixedY", "r32", "FixedP.E[Axis2_Y]" }, + { "TextColor", "v4", "TextColor" }, + { "BackgroundColor", "v4", "BackgroundColor" }, + { "BorderColor", "v4", "BorderColor" }, + { "BorderThickness", "r32", "BorderThickness" }, + { "LayoutAxis", "axis2", "LayoutAxis" }, + { "CornerRadius", "r32", "CornerRadius" }, + { "Font", "font_id", "Font" }, + { "FontSize", "r32", "FontSize" }, +}; + +static void GenUI(void) +{ + FILE *Out = fopen("../code/generated/vn_generated_ui.h", "wb"); + if(Out) + { + fprintf(Out, "struct ui_style_stacks\n"); + fprintf(Out, "{\n"); + for(s32 Index = 0; + Index < ArrayCount(UIStyleStacks); + ++Index) + { + ui_style_stack Stack = UIStyleStacks[Index]; + fprintf(Out, "\t%s %sStack[64];\n", Stack.Type, Stack.Name); + fprintf(Out, "\ts32 %sStackUsed;\n", Stack.Name); + fprintf(Out, "\tb32 AutoPop%s;\n", Stack.Name); + } + fprintf(Out, "};\n"); + fclose(Out); + } + + Out = fopen("../code/generated/vn_generated_ui.cpp", "wb"); + if(Out) + { + for(s32 Index = 0; + Index < ArrayCount(UIStyleStacks); + ++Index) + { + ui_style_stack Stack = UIStyleStacks[Index]; + fprintf(Out, "inline void UI_Push%s(%s Element)\n", Stack.Name, Stack.Type); + fprintf(Out, "{\n"); + fprintf(Out, "\tui *UI = UI_GetState();\n"); + fprintf(Out, "\tAssert(UI->Stacks.%sStackUsed + 1 < ArrayCount(UI->Stacks.%sStack));\n", Stack.Name, Stack.Name); + fprintf(Out, "\tUI->Stacks.%sStack[UI->Stacks.%sStackUsed++] = Element;\n", Stack.Name, Stack.Name); + fprintf(Out, "}\n"); + + fprintf(Out, "\n"); + + fprintf(Out, "inline void UI_Pop%s(void)\n", Stack.Name); + fprintf(Out, "{\n"); + fprintf(Out, "\tui *UI = UI_GetState();\n"); + fprintf(Out, "\tAssert(UI->Stacks.%sStackUsed > 0);\n", Stack.Name); + fprintf(Out, "\t--UI->Stacks.%sStackUsed;\n", Stack.Name, Stack.Name); + fprintf(Out, "}\n"); + + fprintf(Out, "\n"); + + fprintf(Out, "inline void UI_SetNext%s(%s Element)\n", Stack.Name, Stack.Type); + fprintf(Out, "{\n"); + fprintf(Out, "\tui *UI = UI_GetState();\n"); + fprintf(Out, "\tUI_Push%s(Element);\n", Stack.Name); + fprintf(Out, "\tUI->Stacks.AutoPop%s = true;\n", Stack.Name); + fprintf(Out, "}\n"); + + fprintf(Out, "\n"); + + fprintf(Out, "inline %s UI_First%s(void)\n", Stack.Type, Stack.Name); + fprintf(Out, "{\n"); + fprintf(Out, "\tui *UI = UI_GetState();\n"); + fprintf(Out, "\treturn(UI->Stacks.%sStack[0]);\n", Stack.Name); + fprintf(Out, "}\n"); + + fprintf(Out, "\n"); + + fprintf(Out, "inline %s UI_Top%s(void)\n", Stack.Type, Stack.Name); + fprintf(Out, "{\n"); + fprintf(Out, "\tui *UI = UI_GetState();\n"); + fprintf(Out, "\treturn(UI->Stacks.%sStack[UI->Stacks.%sStackUsed - 1]);\n", Stack.Name, Stack.Name); + fprintf(Out, "}\n"); + + fprintf(Out, "\n"); + + fprintf(Out, "#define UI_%s(Element) DeferLoop(UI_Push%s(Element), UI_Pop%s())\n", + Stack.Name, Stack.Name, Stack.Name); + + fprintf(Out, "\n"); + } + + fprintf(Out, "\n"); + + fprintf(Out, "inline void UI_ApplyStyles(ui_box *Box)\n"); + fprintf(Out, "{\n"); + fprintf(Out, "\tui *UI = UI_GetState();\n"); + for(s32 Index = 0; + Index < ArrayCount(UIStyleStacks); + ++Index) + { + ui_style_stack Stack = UIStyleStacks[Index]; + fprintf(Out, "\tAssert(UI->Stacks.%sStackUsed > 0);\n", Stack.Name); + fprintf(Out, "\tBox->%s = UI->Stacks.%sStack[UI->Stacks.%sStackUsed - 1];\n", + Stack.MemberName, Stack.Name, Stack.Name); + fprintf(Out, "\tif(UI->Stacks.AutoPop%s)\n", Stack.Name); + fprintf(Out, "\t{\n"); + fprintf(Out, "\t\tUI_Pop%s();\n", Stack.Name); + fprintf(Out, "\t\tUI->Stacks.AutoPop%s = false;\n", Stack.Name); + fprintf(Out, "\t}\n\n"); + } + + fprintf(Out, "}\n"); + + + fclose(Out); + } +} \ No newline at end of file diff --git a/code/generated/vn_generated_ui.cpp b/code/generated/vn_generated_ui.cpp new file mode 100644 index 0000000..83ca56b --- /dev/null +++ b/code/generated/vn_generated_ui.cpp @@ -0,0 +1,564 @@ +inline void UI_PushParent(ui_box * Element) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.ParentStackUsed + 1 < ArrayCount(UI->Stacks.ParentStack)); + UI->Stacks.ParentStack[UI->Stacks.ParentStackUsed++] = Element; +} + +inline void UI_PopParent(void) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.ParentStackUsed > 0); + --UI->Stacks.ParentStackUsed; +} + +inline void UI_SetNextParent(ui_box * Element) +{ + ui *UI = UI_GetState(); + UI_PushParent(Element); + UI->Stacks.AutoPopParent = true; +} + +inline ui_box * UI_FirstParent(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.ParentStack[0]); +} + +inline ui_box * UI_TopParent(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.ParentStack[UI->Stacks.ParentStackUsed - 1]); +} + +#define UI_Parent(Element) DeferLoop(UI_PushParent(Element), UI_PopParent()) + +inline void UI_PushWidth(ui_size Element) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.WidthStackUsed + 1 < ArrayCount(UI->Stacks.WidthStack)); + UI->Stacks.WidthStack[UI->Stacks.WidthStackUsed++] = Element; +} + +inline void UI_PopWidth(void) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.WidthStackUsed > 0); + --UI->Stacks.WidthStackUsed; +} + +inline void UI_SetNextWidth(ui_size Element) +{ + ui *UI = UI_GetState(); + UI_PushWidth(Element); + UI->Stacks.AutoPopWidth = true; +} + +inline ui_size UI_FirstWidth(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.WidthStack[0]); +} + +inline ui_size UI_TopWidth(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.WidthStack[UI->Stacks.WidthStackUsed - 1]); +} + +#define UI_Width(Element) DeferLoop(UI_PushWidth(Element), UI_PopWidth()) + +inline void UI_PushHeight(ui_size Element) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.HeightStackUsed + 1 < ArrayCount(UI->Stacks.HeightStack)); + UI->Stacks.HeightStack[UI->Stacks.HeightStackUsed++] = Element; +} + +inline void UI_PopHeight(void) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.HeightStackUsed > 0); + --UI->Stacks.HeightStackUsed; +} + +inline void UI_SetNextHeight(ui_size Element) +{ + ui *UI = UI_GetState(); + UI_PushHeight(Element); + UI->Stacks.AutoPopHeight = true; +} + +inline ui_size UI_FirstHeight(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.HeightStack[0]); +} + +inline ui_size UI_TopHeight(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.HeightStack[UI->Stacks.HeightStackUsed - 1]); +} + +#define UI_Height(Element) DeferLoop(UI_PushHeight(Element), UI_PopHeight()) + +inline void UI_PushFixedX(r32 Element) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.FixedXStackUsed + 1 < ArrayCount(UI->Stacks.FixedXStack)); + UI->Stacks.FixedXStack[UI->Stacks.FixedXStackUsed++] = Element; +} + +inline void UI_PopFixedX(void) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.FixedXStackUsed > 0); + --UI->Stacks.FixedXStackUsed; +} + +inline void UI_SetNextFixedX(r32 Element) +{ + ui *UI = UI_GetState(); + UI_PushFixedX(Element); + UI->Stacks.AutoPopFixedX = true; +} + +inline r32 UI_FirstFixedX(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.FixedXStack[0]); +} + +inline r32 UI_TopFixedX(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.FixedXStack[UI->Stacks.FixedXStackUsed - 1]); +} + +#define UI_FixedX(Element) DeferLoop(UI_PushFixedX(Element), UI_PopFixedX()) + +inline void UI_PushFixedY(r32 Element) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.FixedYStackUsed + 1 < ArrayCount(UI->Stacks.FixedYStack)); + UI->Stacks.FixedYStack[UI->Stacks.FixedYStackUsed++] = Element; +} + +inline void UI_PopFixedY(void) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.FixedYStackUsed > 0); + --UI->Stacks.FixedYStackUsed; +} + +inline void UI_SetNextFixedY(r32 Element) +{ + ui *UI = UI_GetState(); + UI_PushFixedY(Element); + UI->Stacks.AutoPopFixedY = true; +} + +inline r32 UI_FirstFixedY(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.FixedYStack[0]); +} + +inline r32 UI_TopFixedY(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.FixedYStack[UI->Stacks.FixedYStackUsed - 1]); +} + +#define UI_FixedY(Element) DeferLoop(UI_PushFixedY(Element), UI_PopFixedY()) + +inline void UI_PushTextColor(v4 Element) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.TextColorStackUsed + 1 < ArrayCount(UI->Stacks.TextColorStack)); + UI->Stacks.TextColorStack[UI->Stacks.TextColorStackUsed++] = Element; +} + +inline void UI_PopTextColor(void) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.TextColorStackUsed > 0); + --UI->Stacks.TextColorStackUsed; +} + +inline void UI_SetNextTextColor(v4 Element) +{ + ui *UI = UI_GetState(); + UI_PushTextColor(Element); + UI->Stacks.AutoPopTextColor = true; +} + +inline v4 UI_FirstTextColor(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.TextColorStack[0]); +} + +inline v4 UI_TopTextColor(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.TextColorStack[UI->Stacks.TextColorStackUsed - 1]); +} + +#define UI_TextColor(Element) DeferLoop(UI_PushTextColor(Element), UI_PopTextColor()) + +inline void UI_PushBackgroundColor(v4 Element) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.BackgroundColorStackUsed + 1 < ArrayCount(UI->Stacks.BackgroundColorStack)); + UI->Stacks.BackgroundColorStack[UI->Stacks.BackgroundColorStackUsed++] = Element; +} + +inline void UI_PopBackgroundColor(void) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.BackgroundColorStackUsed > 0); + --UI->Stacks.BackgroundColorStackUsed; +} + +inline void UI_SetNextBackgroundColor(v4 Element) +{ + ui *UI = UI_GetState(); + UI_PushBackgroundColor(Element); + UI->Stacks.AutoPopBackgroundColor = true; +} + +inline v4 UI_FirstBackgroundColor(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.BackgroundColorStack[0]); +} + +inline v4 UI_TopBackgroundColor(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.BackgroundColorStack[UI->Stacks.BackgroundColorStackUsed - 1]); +} + +#define UI_BackgroundColor(Element) DeferLoop(UI_PushBackgroundColor(Element), UI_PopBackgroundColor()) + +inline void UI_PushBorderColor(v4 Element) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.BorderColorStackUsed + 1 < ArrayCount(UI->Stacks.BorderColorStack)); + UI->Stacks.BorderColorStack[UI->Stacks.BorderColorStackUsed++] = Element; +} + +inline void UI_PopBorderColor(void) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.BorderColorStackUsed > 0); + --UI->Stacks.BorderColorStackUsed; +} + +inline void UI_SetNextBorderColor(v4 Element) +{ + ui *UI = UI_GetState(); + UI_PushBorderColor(Element); + UI->Stacks.AutoPopBorderColor = true; +} + +inline v4 UI_FirstBorderColor(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.BorderColorStack[0]); +} + +inline v4 UI_TopBorderColor(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.BorderColorStack[UI->Stacks.BorderColorStackUsed - 1]); +} + +#define UI_BorderColor(Element) DeferLoop(UI_PushBorderColor(Element), UI_PopBorderColor()) + +inline void UI_PushBorderThickness(r32 Element) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.BorderThicknessStackUsed + 1 < ArrayCount(UI->Stacks.BorderThicknessStack)); + UI->Stacks.BorderThicknessStack[UI->Stacks.BorderThicknessStackUsed++] = Element; +} + +inline void UI_PopBorderThickness(void) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.BorderThicknessStackUsed > 0); + --UI->Stacks.BorderThicknessStackUsed; +} + +inline void UI_SetNextBorderThickness(r32 Element) +{ + ui *UI = UI_GetState(); + UI_PushBorderThickness(Element); + UI->Stacks.AutoPopBorderThickness = true; +} + +inline r32 UI_FirstBorderThickness(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.BorderThicknessStack[0]); +} + +inline r32 UI_TopBorderThickness(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.BorderThicknessStack[UI->Stacks.BorderThicknessStackUsed - 1]); +} + +#define UI_BorderThickness(Element) DeferLoop(UI_PushBorderThickness(Element), UI_PopBorderThickness()) + +inline void UI_PushLayoutAxis(axis2 Element) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.LayoutAxisStackUsed + 1 < ArrayCount(UI->Stacks.LayoutAxisStack)); + UI->Stacks.LayoutAxisStack[UI->Stacks.LayoutAxisStackUsed++] = Element; +} + +inline void UI_PopLayoutAxis(void) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.LayoutAxisStackUsed > 0); + --UI->Stacks.LayoutAxisStackUsed; +} + +inline void UI_SetNextLayoutAxis(axis2 Element) +{ + ui *UI = UI_GetState(); + UI_PushLayoutAxis(Element); + UI->Stacks.AutoPopLayoutAxis = true; +} + +inline axis2 UI_FirstLayoutAxis(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.LayoutAxisStack[0]); +} + +inline axis2 UI_TopLayoutAxis(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.LayoutAxisStack[UI->Stacks.LayoutAxisStackUsed - 1]); +} + +#define UI_LayoutAxis(Element) DeferLoop(UI_PushLayoutAxis(Element), UI_PopLayoutAxis()) + +inline void UI_PushCornerRadius(r32 Element) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.CornerRadiusStackUsed + 1 < ArrayCount(UI->Stacks.CornerRadiusStack)); + UI->Stacks.CornerRadiusStack[UI->Stacks.CornerRadiusStackUsed++] = Element; +} + +inline void UI_PopCornerRadius(void) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.CornerRadiusStackUsed > 0); + --UI->Stacks.CornerRadiusStackUsed; +} + +inline void UI_SetNextCornerRadius(r32 Element) +{ + ui *UI = UI_GetState(); + UI_PushCornerRadius(Element); + UI->Stacks.AutoPopCornerRadius = true; +} + +inline r32 UI_FirstCornerRadius(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.CornerRadiusStack[0]); +} + +inline r32 UI_TopCornerRadius(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.CornerRadiusStack[UI->Stacks.CornerRadiusStackUsed - 1]); +} + +#define UI_CornerRadius(Element) DeferLoop(UI_PushCornerRadius(Element), UI_PopCornerRadius()) + +inline void UI_PushFont(font_id Element) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.FontStackUsed + 1 < ArrayCount(UI->Stacks.FontStack)); + UI->Stacks.FontStack[UI->Stacks.FontStackUsed++] = Element; +} + +inline void UI_PopFont(void) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.FontStackUsed > 0); + --UI->Stacks.FontStackUsed; +} + +inline void UI_SetNextFont(font_id Element) +{ + ui *UI = UI_GetState(); + UI_PushFont(Element); + UI->Stacks.AutoPopFont = true; +} + +inline font_id UI_FirstFont(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.FontStack[0]); +} + +inline font_id UI_TopFont(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.FontStack[UI->Stacks.FontStackUsed - 1]); +} + +#define UI_Font(Element) DeferLoop(UI_PushFont(Element), UI_PopFont()) + +inline void UI_PushFontSize(r32 Element) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.FontSizeStackUsed + 1 < ArrayCount(UI->Stacks.FontSizeStack)); + UI->Stacks.FontSizeStack[UI->Stacks.FontSizeStackUsed++] = Element; +} + +inline void UI_PopFontSize(void) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.FontSizeStackUsed > 0); + --UI->Stacks.FontSizeStackUsed; +} + +inline void UI_SetNextFontSize(r32 Element) +{ + ui *UI = UI_GetState(); + UI_PushFontSize(Element); + UI->Stacks.AutoPopFontSize = true; +} + +inline r32 UI_FirstFontSize(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.FontSizeStack[0]); +} + +inline r32 UI_TopFontSize(void) +{ + ui *UI = UI_GetState(); + return(UI->Stacks.FontSizeStack[UI->Stacks.FontSizeStackUsed - 1]); +} + +#define UI_FontSize(Element) DeferLoop(UI_PushFontSize(Element), UI_PopFontSize()) + + +inline void UI_ApplyStyles(ui_box *Box) +{ + ui *UI = UI_GetState(); + Assert(UI->Stacks.ParentStackUsed > 0); + Box->Parent = UI->Stacks.ParentStack[UI->Stacks.ParentStackUsed - 1]; + if(UI->Stacks.AutoPopParent) + { + UI_PopParent(); + UI->Stacks.AutoPopParent = false; + } + + Assert(UI->Stacks.WidthStackUsed > 0); + Box->SemanticSize[Axis2_X] = UI->Stacks.WidthStack[UI->Stacks.WidthStackUsed - 1]; + if(UI->Stacks.AutoPopWidth) + { + UI_PopWidth(); + UI->Stacks.AutoPopWidth = false; + } + + Assert(UI->Stacks.HeightStackUsed > 0); + Box->SemanticSize[Axis2_Y] = UI->Stacks.HeightStack[UI->Stacks.HeightStackUsed - 1]; + if(UI->Stacks.AutoPopHeight) + { + UI_PopHeight(); + UI->Stacks.AutoPopHeight = false; + } + + Assert(UI->Stacks.FixedXStackUsed > 0); + Box->FixedP.E[Axis2_X] = UI->Stacks.FixedXStack[UI->Stacks.FixedXStackUsed - 1]; + if(UI->Stacks.AutoPopFixedX) + { + UI_PopFixedX(); + UI->Stacks.AutoPopFixedX = false; + } + + Assert(UI->Stacks.FixedYStackUsed > 0); + Box->FixedP.E[Axis2_Y] = UI->Stacks.FixedYStack[UI->Stacks.FixedYStackUsed - 1]; + if(UI->Stacks.AutoPopFixedY) + { + UI_PopFixedY(); + UI->Stacks.AutoPopFixedY = false; + } + + Assert(UI->Stacks.TextColorStackUsed > 0); + Box->TextColor = UI->Stacks.TextColorStack[UI->Stacks.TextColorStackUsed - 1]; + if(UI->Stacks.AutoPopTextColor) + { + UI_PopTextColor(); + UI->Stacks.AutoPopTextColor = false; + } + + Assert(UI->Stacks.BackgroundColorStackUsed > 0); + Box->BackgroundColor = UI->Stacks.BackgroundColorStack[UI->Stacks.BackgroundColorStackUsed - 1]; + if(UI->Stacks.AutoPopBackgroundColor) + { + UI_PopBackgroundColor(); + UI->Stacks.AutoPopBackgroundColor = false; + } + + Assert(UI->Stacks.BorderColorStackUsed > 0); + Box->BorderColor = UI->Stacks.BorderColorStack[UI->Stacks.BorderColorStackUsed - 1]; + if(UI->Stacks.AutoPopBorderColor) + { + UI_PopBorderColor(); + UI->Stacks.AutoPopBorderColor = false; + } + + Assert(UI->Stacks.BorderThicknessStackUsed > 0); + Box->BorderThickness = UI->Stacks.BorderThicknessStack[UI->Stacks.BorderThicknessStackUsed - 1]; + if(UI->Stacks.AutoPopBorderThickness) + { + UI_PopBorderThickness(); + UI->Stacks.AutoPopBorderThickness = false; + } + + Assert(UI->Stacks.LayoutAxisStackUsed > 0); + Box->LayoutAxis = UI->Stacks.LayoutAxisStack[UI->Stacks.LayoutAxisStackUsed - 1]; + if(UI->Stacks.AutoPopLayoutAxis) + { + UI_PopLayoutAxis(); + UI->Stacks.AutoPopLayoutAxis = false; + } + + Assert(UI->Stacks.CornerRadiusStackUsed > 0); + Box->CornerRadius = UI->Stacks.CornerRadiusStack[UI->Stacks.CornerRadiusStackUsed - 1]; + if(UI->Stacks.AutoPopCornerRadius) + { + UI_PopCornerRadius(); + UI->Stacks.AutoPopCornerRadius = false; + } + + Assert(UI->Stacks.FontStackUsed > 0); + Box->Font = UI->Stacks.FontStack[UI->Stacks.FontStackUsed - 1]; + if(UI->Stacks.AutoPopFont) + { + UI_PopFont(); + UI->Stacks.AutoPopFont = false; + } + + Assert(UI->Stacks.FontSizeStackUsed > 0); + Box->FontSize = UI->Stacks.FontSizeStack[UI->Stacks.FontSizeStackUsed - 1]; + if(UI->Stacks.AutoPopFontSize) + { + UI_PopFontSize(); + UI->Stacks.AutoPopFontSize = false; + } + +} diff --git a/code/generated/vn_generated_ui.h b/code/generated/vn_generated_ui.h new file mode 100644 index 0000000..a4f025f --- /dev/null +++ b/code/generated/vn_generated_ui.h @@ -0,0 +1,42 @@ +struct ui_style_stacks +{ + ui_box * ParentStack[64]; + s32 ParentStackUsed; + b32 AutoPopParent; + ui_size WidthStack[64]; + s32 WidthStackUsed; + b32 AutoPopWidth; + ui_size HeightStack[64]; + s32 HeightStackUsed; + b32 AutoPopHeight; + r32 FixedXStack[64]; + s32 FixedXStackUsed; + b32 AutoPopFixedX; + r32 FixedYStack[64]; + s32 FixedYStackUsed; + b32 AutoPopFixedY; + v4 TextColorStack[64]; + s32 TextColorStackUsed; + b32 AutoPopTextColor; + v4 BackgroundColorStack[64]; + s32 BackgroundColorStackUsed; + b32 AutoPopBackgroundColor; + v4 BorderColorStack[64]; + s32 BorderColorStackUsed; + b32 AutoPopBorderColor; + r32 BorderThicknessStack[64]; + s32 BorderThicknessStackUsed; + b32 AutoPopBorderThickness; + axis2 LayoutAxisStack[64]; + s32 LayoutAxisStackUsed; + b32 AutoPopLayoutAxis; + r32 CornerRadiusStack[64]; + s32 CornerRadiusStackUsed; + b32 AutoPopCornerRadius; + font_id FontStack[64]; + s32 FontStackUsed; + b32 AutoPopFont; + r32 FontSizeStack[64]; + s32 FontSizeStackUsed; + b32 AutoPopFontSize; +}; diff --git a/code/generated/vn_opengl_functions.h b/code/generated/vn_opengl_functions.h new file mode 100644 index 0000000..53cbbcd --- /dev/null +++ b/code/generated/vn_opengl_functions.h @@ -0,0 +1,268 @@ +typedef void opengl_BindTexture(GLenum target, GLuint texture); +typedef void opengl_BlendFunc(GLenum sfactor, GLenum dfactor); +typedef void opengl_Clear(GLbitfield mask); +typedef void opengl_ClearAccum(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +typedef void opengl_ClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +typedef void opengl_ClearDepth(GLclampd depth); +typedef void opengl_ClearIndex(GLfloat c); +typedef void opengl_ClearStencil(GLint s); +typedef void opengl_ClipPlane(GLenum plane, const GLdouble *equation); +typedef void opengl_CullFace(GLenum mode); +typedef void opengl_DeleteTextures(GLsizei n, const GLuint *textures); +typedef void opengl_Disable(GLenum cap); +typedef void opengl_DrawArrays(GLenum mode, GLint first, GLsizei count); +typedef void opengl_DrawBuffer(GLenum mode); +typedef void opengl_DrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +typedef void opengl_DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount); +typedef void opengl_Enable(GLenum cap); +typedef void opengl_GenTextures(GLsizei n, GLuint *textures); +typedef void opengl_GetClipPlane(GLenum plane, GLdouble *equation); +typedef void opengl_GetDoublev(GLenum pname, GLdouble *params); +typedef GLenum opengl_GetError(void); +typedef void opengl_GetFloatv(GLenum pname, GLfloat *params); +typedef void opengl_GetIntegerv(GLenum pname, GLint *params); +typedef void opengl_GetPointerv(GLenum pname, GLvoid* *params); +typedef const GLubyte * opengl_GetString(GLenum name); +typedef void opengl_GetTexEnvfv(GLenum target, GLenum pname, GLfloat *params); +typedef void opengl_GetTexEnviv(GLenum target, GLenum pname, GLint *params); +typedef void opengl_GetTexGendv(GLenum coord, GLenum pname, GLdouble *params); +typedef void opengl_GetTexGenfv(GLenum coord, GLenum pname, GLfloat *params); +typedef void opengl_GetTexGeniv(GLenum coord, GLenum pname, GLint *params); +typedef void opengl_GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +typedef void opengl_GetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat *params); +typedef void opengl_GetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint *params); +typedef void opengl_GetTexParameterfv(GLenum target, GLenum pname, GLfloat *params); +typedef void opengl_GetTexParameteriv(GLenum target, GLenum pname, GLint *params); +typedef void opengl_Hint(GLenum target, GLenum mode); +typedef GLboolean opengl_IsTexture(GLuint texture); +typedef void opengl_LineWidth(GLfloat width); +typedef void opengl_ListBase(GLuint base); +typedef void opengl_LoadName(GLuint name); +typedef void opengl_LogicOp(GLenum opcode); +typedef void opengl_PointSize(GLfloat size); +typedef void opengl_PolygonMode(GLenum face, GLenum mode); +typedef void opengl_Scissor(GLint x, GLint y, GLsizei width, GLsizei height); +typedef void opengl_TexImage1D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void opengl_TexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void opengl_TexParameterf(GLenum target, GLenum pname, GLfloat param); +typedef void opengl_TexParameterfv(GLenum target, GLenum pname, const GLfloat *params); +typedef void opengl_TexParameteri(GLenum target, GLenum pname, GLint param); +typedef void opengl_TexParameteriv(GLenum target, GLenum pname, const GLint *params); +typedef void opengl_TexSubImage1D(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +typedef void opengl_TexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +typedef void opengl_CompressedTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *pixels); +typedef void opengl_ActiveTexture(GLenum texture); +typedef void opengl_Viewport(GLint x, GLint y, GLsizei width, GLsizei height); +typedef void opengl_GenBuffers(GLsizei n, GLuint *buffers); +typedef void opengl_BindBuffer(GLenum target, GLuint buffer); +typedef void opengl_BufferData(GLenum target, GLsizeiptr size, const void *data, GLenum usage); +typedef void opengl_BufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void *data); +typedef void opengl_GenVertexArrays(GLsizei n, GLuint *arrays); +typedef void opengl_BindVertexArray(GLenum array); +typedef GLint opengl_GetAttribLocation(GLuint program, const GLchar *name); +typedef void opengl_EnableVertexAttribArray(GLuint index); +typedef void opengl_DisableVertexAttribArray(GLuint index); +typedef void opengl_VertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +typedef void opengl_VertexAttribIPointer(GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer); +typedef void opengl_VertexAttribLPointer(GLuint index, GLint size, GLenum type, GLsizei stride, const void * pointer); +typedef void opengl_VertexAttribDivisor(GLuint index, GLuint divisor); +typedef GLuint opengl_CreateShader(GLenum type); +typedef void opengl_ShaderSource(GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +typedef void opengl_CompileShader(GLuint shader); +typedef void opengl_DeleteShader(GLuint shader); +typedef void opengl_GetShaderiv(GLuint shader, GLenum pname, GLint *params); +typedef void opengl_GetShaderInfoLog(GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef GLuint opengl_CreateProgram(void); +typedef void opengl_UseProgram(GLuint program); +typedef void opengl_AttachShader(GLuint program, GLuint shader); +typedef void opengl_DeleteProgram(GLuint program); +typedef void opengl_LinkProgram(GLuint program); +typedef void opengl_GetProgramiv(GLuint program, GLenum pname, GLint *params); +typedef void opengl_GetProgramInfoLog(GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef GLint opengl_GetUniformLocation(GLuint program, const GLchar *name); +typedef void opengl_Uniform1i(GLint location, GLint v0); +typedef void opengl_Uniform2f(GLint location, GLfloat v0, GLfloat v1); +typedef void opengl_Uniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void opengl_UniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void opengl_DebugMessageCallback(void (*)(GLenum, GLenum, GLuint, GLenum, GLsizei, const GLchar *, const void *), const void *userParam); + +global opengl_BindTexture *glBindTexture = 0; +global opengl_BlendFunc *glBlendFunc = 0; +global opengl_Clear *glClear = 0; +global opengl_ClearAccum *glClearAccum = 0; +global opengl_ClearColor *glClearColor = 0; +global opengl_ClearDepth *glClearDepth = 0; +global opengl_ClearIndex *glClearIndex = 0; +global opengl_ClearStencil *glClearStencil = 0; +global opengl_ClipPlane *glClipPlane = 0; +global opengl_CullFace *glCullFace = 0; +global opengl_DeleteTextures *glDeleteTextures = 0; +global opengl_Disable *glDisable = 0; +global opengl_DrawArrays *glDrawArrays = 0; +global opengl_DrawBuffer *glDrawBuffer = 0; +global opengl_DrawElements *glDrawElements = 0; +global opengl_DrawArraysInstanced *glDrawArraysInstanced = 0; +global opengl_Enable *glEnable = 0; +global opengl_GenTextures *glGenTextures = 0; +global opengl_GetClipPlane *glGetClipPlane = 0; +global opengl_GetDoublev *glGetDoublev = 0; +global opengl_GetError *glGetError = 0; +global opengl_GetFloatv *glGetFloatv = 0; +global opengl_GetIntegerv *glGetIntegerv = 0; +global opengl_GetPointerv *glGetPointerv = 0; +global opengl_GetString *glGetString = 0; +global opengl_GetTexEnvfv *glGetTexEnvfv = 0; +global opengl_GetTexEnviv *glGetTexEnviv = 0; +global opengl_GetTexGendv *glGetTexGendv = 0; +global opengl_GetTexGenfv *glGetTexGenfv = 0; +global opengl_GetTexGeniv *glGetTexGeniv = 0; +global opengl_GetTexImage *glGetTexImage = 0; +global opengl_GetTexLevelParameterfv *glGetTexLevelParameterfv = 0; +global opengl_GetTexLevelParameteriv *glGetTexLevelParameteriv = 0; +global opengl_GetTexParameterfv *glGetTexParameterfv = 0; +global opengl_GetTexParameteriv *glGetTexParameteriv = 0; +global opengl_Hint *glHint = 0; +global opengl_IsTexture *glIsTexture = 0; +global opengl_LineWidth *glLineWidth = 0; +global opengl_ListBase *glListBase = 0; +global opengl_LoadName *glLoadName = 0; +global opengl_LogicOp *glLogicOp = 0; +global opengl_PointSize *glPointSize = 0; +global opengl_PolygonMode *glPolygonMode = 0; +global opengl_Scissor *glScissor = 0; +global opengl_TexImage1D *glTexImage1D = 0; +global opengl_TexImage2D *glTexImage2D = 0; +global opengl_TexParameterf *glTexParameterf = 0; +global opengl_TexParameterfv *glTexParameterfv = 0; +global opengl_TexParameteri *glTexParameteri = 0; +global opengl_TexParameteriv *glTexParameteriv = 0; +global opengl_TexSubImage1D *glTexSubImage1D = 0; +global opengl_TexSubImage2D *glTexSubImage2D = 0; +global opengl_CompressedTexImage2D *glCompressedTexImage2D = 0; +global opengl_ActiveTexture *glActiveTexture = 0; +global opengl_Viewport *glViewport = 0; +global opengl_GenBuffers *glGenBuffers = 0; +global opengl_BindBuffer *glBindBuffer = 0; +global opengl_BufferData *glBufferData = 0; +global opengl_BufferSubData *glBufferSubData = 0; +global opengl_GenVertexArrays *glGenVertexArrays = 0; +global opengl_BindVertexArray *glBindVertexArray = 0; +global opengl_GetAttribLocation *glGetAttribLocation = 0; +global opengl_EnableVertexAttribArray *glEnableVertexAttribArray = 0; +global opengl_DisableVertexAttribArray *glDisableVertexAttribArray = 0; +global opengl_VertexAttribPointer *glVertexAttribPointer = 0; +global opengl_VertexAttribIPointer *glVertexAttribIPointer = 0; +global opengl_VertexAttribLPointer *glVertexAttribLPointer = 0; +global opengl_VertexAttribDivisor *glVertexAttribDivisor = 0; +global opengl_CreateShader *glCreateShader = 0; +global opengl_ShaderSource *glShaderSource = 0; +global opengl_CompileShader *glCompileShader = 0; +global opengl_DeleteShader *glDeleteShader = 0; +global opengl_GetShaderiv *glGetShaderiv = 0; +global opengl_GetShaderInfoLog *glGetShaderInfoLog = 0; +global opengl_CreateProgram *glCreateProgram = 0; +global opengl_UseProgram *glUseProgram = 0; +global opengl_AttachShader *glAttachShader = 0; +global opengl_DeleteProgram *glDeleteProgram = 0; +global opengl_LinkProgram *glLinkProgram = 0; +global opengl_GetProgramiv *glGetProgramiv = 0; +global opengl_GetProgramInfoLog *glGetProgramInfoLog = 0; +global opengl_GetUniformLocation *glGetUniformLocation = 0; +global opengl_Uniform1i *glUniform1i = 0; +global opengl_Uniform2f *glUniform2f = 0; +global opengl_Uniform3f *glUniform3f = 0; +global opengl_UniformMatrix4fv *glUniformMatrix4fv = 0; +global opengl_DebugMessageCallback *glDebugMessageCallback = 0; + +extern void *OpenGL_LoadFunction(char *); + +static void OpenGL_LoadAllFunctions(void) +{ + glBindTexture = (opengl_BindTexture *)OpenGL_LoadFunction("glBindTexture"); + glBlendFunc = (opengl_BlendFunc *)OpenGL_LoadFunction("glBlendFunc"); + glClear = (opengl_Clear *)OpenGL_LoadFunction("glClear"); + glClearAccum = (opengl_ClearAccum *)OpenGL_LoadFunction("glClearAccum"); + glClearColor = (opengl_ClearColor *)OpenGL_LoadFunction("glClearColor"); + glClearDepth = (opengl_ClearDepth *)OpenGL_LoadFunction("glClearDepth"); + glClearIndex = (opengl_ClearIndex *)OpenGL_LoadFunction("glClearIndex"); + glClearStencil = (opengl_ClearStencil *)OpenGL_LoadFunction("glClearStencil"); + glClipPlane = (opengl_ClipPlane *)OpenGL_LoadFunction("glClipPlane"); + glCullFace = (opengl_CullFace *)OpenGL_LoadFunction("glCullFace"); + glDeleteTextures = (opengl_DeleteTextures *)OpenGL_LoadFunction("glDeleteTextures"); + glDisable = (opengl_Disable *)OpenGL_LoadFunction("glDisable"); + glDrawArrays = (opengl_DrawArrays *)OpenGL_LoadFunction("glDrawArrays"); + glDrawBuffer = (opengl_DrawBuffer *)OpenGL_LoadFunction("glDrawBuffer"); + glDrawElements = (opengl_DrawElements *)OpenGL_LoadFunction("glDrawElements"); + glDrawArraysInstanced = (opengl_DrawArraysInstanced *)OpenGL_LoadFunction("glDrawArraysInstanced"); + glEnable = (opengl_Enable *)OpenGL_LoadFunction("glEnable"); + glGenTextures = (opengl_GenTextures *)OpenGL_LoadFunction("glGenTextures"); + glGetClipPlane = (opengl_GetClipPlane *)OpenGL_LoadFunction("glGetClipPlane"); + glGetDoublev = (opengl_GetDoublev *)OpenGL_LoadFunction("glGetDoublev"); + glGetError = (opengl_GetError *)OpenGL_LoadFunction("glGetError"); + glGetFloatv = (opengl_GetFloatv *)OpenGL_LoadFunction("glGetFloatv"); + glGetIntegerv = (opengl_GetIntegerv *)OpenGL_LoadFunction("glGetIntegerv"); + glGetPointerv = (opengl_GetPointerv *)OpenGL_LoadFunction("glGetPointerv"); + glGetString = (opengl_GetString *)OpenGL_LoadFunction("glGetString"); + glGetTexEnvfv = (opengl_GetTexEnvfv *)OpenGL_LoadFunction("glGetTexEnvfv"); + glGetTexEnviv = (opengl_GetTexEnviv *)OpenGL_LoadFunction("glGetTexEnviv"); + glGetTexGendv = (opengl_GetTexGendv *)OpenGL_LoadFunction("glGetTexGendv"); + glGetTexGenfv = (opengl_GetTexGenfv *)OpenGL_LoadFunction("glGetTexGenfv"); + glGetTexGeniv = (opengl_GetTexGeniv *)OpenGL_LoadFunction("glGetTexGeniv"); + glGetTexImage = (opengl_GetTexImage *)OpenGL_LoadFunction("glGetTexImage"); + glGetTexLevelParameterfv = (opengl_GetTexLevelParameterfv *)OpenGL_LoadFunction("glGetTexLevelParameterfv"); + glGetTexLevelParameteriv = (opengl_GetTexLevelParameteriv *)OpenGL_LoadFunction("glGetTexLevelParameteriv"); + glGetTexParameterfv = (opengl_GetTexParameterfv *)OpenGL_LoadFunction("glGetTexParameterfv"); + glGetTexParameteriv = (opengl_GetTexParameteriv *)OpenGL_LoadFunction("glGetTexParameteriv"); + glHint = (opengl_Hint *)OpenGL_LoadFunction("glHint"); + glIsTexture = (opengl_IsTexture *)OpenGL_LoadFunction("glIsTexture"); + glLineWidth = (opengl_LineWidth *)OpenGL_LoadFunction("glLineWidth"); + glListBase = (opengl_ListBase *)OpenGL_LoadFunction("glListBase"); + glLoadName = (opengl_LoadName *)OpenGL_LoadFunction("glLoadName"); + glLogicOp = (opengl_LogicOp *)OpenGL_LoadFunction("glLogicOp"); + glPointSize = (opengl_PointSize *)OpenGL_LoadFunction("glPointSize"); + glPolygonMode = (opengl_PolygonMode *)OpenGL_LoadFunction("glPolygonMode"); + glScissor = (opengl_Scissor *)OpenGL_LoadFunction("glScissor"); + glTexImage1D = (opengl_TexImage1D *)OpenGL_LoadFunction("glTexImage1D"); + glTexImage2D = (opengl_TexImage2D *)OpenGL_LoadFunction("glTexImage2D"); + glTexParameterf = (opengl_TexParameterf *)OpenGL_LoadFunction("glTexParameterf"); + glTexParameterfv = (opengl_TexParameterfv *)OpenGL_LoadFunction("glTexParameterfv"); + glTexParameteri = (opengl_TexParameteri *)OpenGL_LoadFunction("glTexParameteri"); + glTexParameteriv = (opengl_TexParameteriv *)OpenGL_LoadFunction("glTexParameteriv"); + glTexSubImage1D = (opengl_TexSubImage1D *)OpenGL_LoadFunction("glTexSubImage1D"); + glTexSubImage2D = (opengl_TexSubImage2D *)OpenGL_LoadFunction("glTexSubImage2D"); + glCompressedTexImage2D = (opengl_CompressedTexImage2D *)OpenGL_LoadFunction("glCompressedTexImage2D"); + glActiveTexture = (opengl_ActiveTexture *)OpenGL_LoadFunction("glActiveTexture"); + glViewport = (opengl_Viewport *)OpenGL_LoadFunction("glViewport"); + glGenBuffers = (opengl_GenBuffers *)OpenGL_LoadFunction("glGenBuffers"); + glBindBuffer = (opengl_BindBuffer *)OpenGL_LoadFunction("glBindBuffer"); + glBufferData = (opengl_BufferData *)OpenGL_LoadFunction("glBufferData"); + glBufferSubData = (opengl_BufferSubData *)OpenGL_LoadFunction("glBufferSubData"); + glGenVertexArrays = (opengl_GenVertexArrays *)OpenGL_LoadFunction("glGenVertexArrays"); + glBindVertexArray = (opengl_BindVertexArray *)OpenGL_LoadFunction("glBindVertexArray"); + glGetAttribLocation = (opengl_GetAttribLocation *)OpenGL_LoadFunction("glGetAttribLocation"); + glEnableVertexAttribArray = (opengl_EnableVertexAttribArray *)OpenGL_LoadFunction("glEnableVertexAttribArray"); + glDisableVertexAttribArray = (opengl_DisableVertexAttribArray *)OpenGL_LoadFunction("glDisableVertexAttribArray"); + glVertexAttribPointer = (opengl_VertexAttribPointer *)OpenGL_LoadFunction("glVertexAttribPointer"); + glVertexAttribIPointer = (opengl_VertexAttribIPointer *)OpenGL_LoadFunction("glVertexAttribIPointer"); + glVertexAttribLPointer = (opengl_VertexAttribLPointer *)OpenGL_LoadFunction("glVertexAttribLPointer"); + glVertexAttribDivisor = (opengl_VertexAttribDivisor *)OpenGL_LoadFunction("glVertexAttribDivisor"); + glCreateShader = (opengl_CreateShader *)OpenGL_LoadFunction("glCreateShader"); + glShaderSource = (opengl_ShaderSource *)OpenGL_LoadFunction("glShaderSource"); + glCompileShader = (opengl_CompileShader *)OpenGL_LoadFunction("glCompileShader"); + glDeleteShader = (opengl_DeleteShader *)OpenGL_LoadFunction("glDeleteShader"); + glGetShaderiv = (opengl_GetShaderiv *)OpenGL_LoadFunction("glGetShaderiv"); + glGetShaderInfoLog = (opengl_GetShaderInfoLog *)OpenGL_LoadFunction("glGetShaderInfoLog"); + glCreateProgram = (opengl_CreateProgram *)OpenGL_LoadFunction("glCreateProgram"); + glUseProgram = (opengl_UseProgram *)OpenGL_LoadFunction("glUseProgram"); + glAttachShader = (opengl_AttachShader *)OpenGL_LoadFunction("glAttachShader"); + glDeleteProgram = (opengl_DeleteProgram *)OpenGL_LoadFunction("glDeleteProgram"); + glLinkProgram = (opengl_LinkProgram *)OpenGL_LoadFunction("glLinkProgram"); + glGetProgramiv = (opengl_GetProgramiv *)OpenGL_LoadFunction("glGetProgramiv"); + glGetProgramInfoLog = (opengl_GetProgramInfoLog *)OpenGL_LoadFunction("glGetProgramInfoLog"); + glGetUniformLocation = (opengl_GetUniformLocation *)OpenGL_LoadFunction("glGetUniformLocation"); + glUniform1i = (opengl_Uniform1i *)OpenGL_LoadFunction("glUniform1i"); + glUniform2f = (opengl_Uniform2f *)OpenGL_LoadFunction("glUniform2f"); + glUniform3f = (opengl_Uniform3f *)OpenGL_LoadFunction("glUniform3f"); + glUniformMatrix4fv = (opengl_UniformMatrix4fv *)OpenGL_LoadFunction("glUniformMatrix4fv"); + glDebugMessageCallback = (opengl_DebugMessageCallback *)OpenGL_LoadFunction("glDebugMessageCallback"); +} diff --git a/code/opengl_render.cpp b/code/opengl_render.cpp new file mode 100644 index 0000000..48e92e7 --- /dev/null +++ b/code/opengl_render.cpp @@ -0,0 +1,537 @@ +// sixten: Functions implemented by the platform layer +static void *OpenGL_AllocateMemory(umm Size); +inline void *OpenGL_LoadFunction(char *Name); +static void OpenGL_DeallocateMemory(void *Memory); + +//- sixten: OpenGL Render Layer +static void OpenGL_DebugMessageCallback(GLenum Source, GLenum Type, GLuint ID, GLenum Severity, + GLsizei Length, const GLchar *Message, const void *UserParam) +{ + if(Severity == GL_DEBUG_SEVERITY_HIGH) + { + __debugbreak(); + } +} + +inline render_handle OpenGL_GetHandleFromTexture(opengl_texture Texture) +{ + render_handle Result = {}; + Result.U32[0] = Texture.ID; + Result.U32[1] = Texture.Format; + Result.U32[2] = Texture.Dim.x; + Result.U32[3] = Texture.Dim.y; + return(Result); +} + +inline opengl_texture OpenGL_GetTextureFromHandle(render_handle Handle) +{ + opengl_texture Result = {}; + Result.ID = Handle.U32[0]; + Result.Format = (render_texture_format)Handle.U32[1]; + Result.Dim.x = Handle.U32[2]; + Result.Dim.y = Handle.U32[3]; + return(Result); +} + +inline u32 OpenGL_GetInternalFormatFromTextureFormat(render_texture_format Format) +{ + u32 InternalFormat = GL_INVALID_ENUM; + + if(Format == Render_TextureFormat_R8) + { + InternalFormat = GL_RED; + } + else if(Format == Render_TextureFormat_RGB8) + { + InternalFormat = GL_RGB; + } + else if(Format == Render_TextureFormat_RGBA8) + { + InternalFormat = GL_RGBA; + } + else + { + InvalidCodepath; + } + + return(InternalFormat); +} + +global GLint Global_OpenGL_SwizzleMaskR8[4] = {GL_ONE, GL_ONE, GL_ONE, GL_RED}; +global GLint Global_OpenGL_SwizzleMaskRGB8[4] = {GL_RED, GL_GREEN, GL_BLUE, GL_ONE}; +global GLint Global_OpenGL_SwizzleMaskRGBA8[4] = {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA}; + +inline GLint *OpenGL_GetSwizzleFromTextureFormat(render_texture_format Format) +{ + GLint *Swizzle = 0; + if(Format == Render_TextureFormat_R8) + { + Swizzle = Global_OpenGL_SwizzleMaskR8; + } + else if(Format == Render_TextureFormat_RGB8) + { + Swizzle = Global_OpenGL_SwizzleMaskRGB8; + } + else if(Format == Render_TextureFormat_RGBA8) + { + Swizzle = Global_OpenGL_SwizzleMaskRGBA8; + } + else + { + InvalidCodepath; + } + + return(Swizzle); +} + +static RENDER_ALLOCATE_TEXTURE(OpenGL_AllocateTexture) +{ + opengl_texture Texture = {}; + Texture.Dim = Dim; + Texture.Format = Format; + + glGenTextures(1, &Texture.ID); + Assert(Texture.ID); + + u32 InternalFormat = OpenGL_GetInternalFormatFromTextureFormat(Format); + GLint *SwizzleMask = OpenGL_GetSwizzleFromTextureFormat(Format); + + glBindTexture(GL_TEXTURE_2D, Texture.ID); + glTexImage2D(GL_TEXTURE_2D, 0, InternalFormat, Dim.x, Dim.y, 0, InternalFormat, GL_UNSIGNED_BYTE, Data); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, SwizzleMask); + + render_handle Handle = OpenGL_GetHandleFromTexture(Texture); + return(Handle); +} + +static RENDER_FILL_REGION(OpenGL_FillRegion) +{ + opengl_texture Texture = OpenGL_GetTextureFromHandle(Handle); + + glBindTexture(GL_TEXTURE_2D, Texture.ID); + glTexSubImage2D(GL_TEXTURE_2D, 0, + DestP.x, DestP.y, DestDim.x, DestDim.y, + OpenGL_GetInternalFormatFromTextureFormat(Texture.Format), + GL_UNSIGNED_BYTE, Data); + glBindTexture(GL_TEXTURE_2D, 0); +} + +global char *OpenGL_ShaderHeader = +R"GLSL( +#version 330 core + +#define s32 int +#define u32 uint +#define v2s ivec2 +#define v2u uvec2 +#define r32 float +#define v2 vec2 +#define v3 vec3 +#define v4 vec4 +#define m4x4 mat4x4 + +#define V2 vec2 +#define V3 vec3 +#define V4 vec4 + +#define Min(a, b) min(a, b) +#define Max(a, b) max(a, b) +#define Clamp(x, min, max), clamp(x, min, max) +#define Clamp01(x), Clamp(x, 0, 1) +#define LinearBlend(A, B, C) mix(A, B, C) +#define AbsoluteValue(x) abs(x) +#define Length(x) length(x) +#define Pow(x, y) pow(x, y) + +)GLSL"; + +static u32 OpenGL_CompileShaderProgram(char *VertexSource, char *FragmentSource) +{ + u32 Result = 0; + + u32 VertexShader = glCreateShader(GL_VERTEX_SHADER); + char *VertexSources[] = + { + OpenGL_ShaderHeader, + VertexSource + }; + glShaderSource(VertexShader, ArrayCount(VertexSources), VertexSources, 0); + glCompileShader(VertexShader); + + s32 VertexCompilationStatus; + glGetShaderiv(VertexShader, GL_COMPILE_STATUS, &VertexCompilationStatus); + if(VertexCompilationStatus) + { + u32 FragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + char *FragmentSources[] = + { + OpenGL_ShaderHeader, + FragmentSource + }; + glShaderSource(FragmentShader, ArrayCount(FragmentSources), FragmentSources, 0); + glCompileShader(FragmentShader); + + s32 FragmentCompilationStatus; + glGetShaderiv(FragmentShader, GL_COMPILE_STATUS, &FragmentCompilationStatus); + if(FragmentCompilationStatus) + { + u32 Program = glCreateProgram(); + + glAttachShader(Program, VertexShader); + glAttachShader(Program, FragmentShader); + glLinkProgram(Program); + + glDeleteShader(VertexShader); + glDeleteShader(FragmentShader); + + s32 ProgramLinkStatus; + glGetProgramiv(Program, GL_LINK_STATUS, &ProgramLinkStatus); + if(ProgramLinkStatus) + { + Result = Program; + } + else + { + char InfoLog[1024]; + glGetProgramInfoLog(Program, ArrayCount(InfoLog), 0, InfoLog); + + InvalidCodepath; + } + } + else + { + char InfoLog[1024]; + glGetShaderInfoLog(FragmentShader, ArrayCount(InfoLog), 0, InfoLog); + + InvalidCodepath; + } + } + else + { + char InfoLog[1024]; + glGetShaderInfoLog(VertexShader, ArrayCount(InfoLog), 0, InfoLog); + + InvalidCodepath; + } + + return(Result); +} + +static quad_program OpenGL_CompileQuadProgram(void) +{ + char *VertexSource = + R"GLSL( +in v2 In_P; + in v2 In_SourceP; +in s32 In_TextureIndex; + in u32 In_Color; +in v2 In_ToCenter; +in v2 In_HalfSize; + in r32 In_CornerRadius; + in r32 In_EdgeSoftness; + in r32 In_BorderThickness; +)GLSL" + "uniform sampler2D TextureSamplers[" Stringify(MAX_BOUND_TEXTURES) "];" + R"GLSL( + uniform v2 Uniform_Resolution; + +flat out s32 TextureIndex; + out v2 DestP; + out v2 DestHalfSize; + out v2 DestCenter; + out v2 SourceP; + out v4 Color; + out r32 CornerRadius; + out r32 EdgeSoftness; + out r32 BorderThickness; + + void main() + { + DestP = In_P; + DestCenter = In_P + In_ToCenter; + DestHalfSize = In_HalfSize; + + SourceP = In_SourceP; + + v2 ScreenP = V2(DestP.x / Uniform_Resolution.x, DestP.y / Uniform_Resolution.y); + ScreenP = ScreenP*2 - 1; + ScreenP.y = -ScreenP.y; + + gl_Position = V4(ScreenP, 0, 1); + Color.r = r32((In_Color >> 24) & 255u)/255.0; + Color.g = r32((In_Color >> 16) & 255u)/255.0; + Color.b = r32((In_Color >> 8) & 255u)/255.0; + Color.a = r32((In_Color >> 0) & 255u)/255.0; + +TextureIndex = In_TextureIndex; + CornerRadius = In_CornerRadius; + EdgeSoftness = In_EdgeSoftness; + BorderThickness = In_BorderThickness; + })GLSL"; + + char *FragmentSource = + R"GLSL( + + flat in s32 TextureIndex; + in v2 DestP; + in v2 DestHalfSize; + in v2 DestCenter; +in v2 SourceP; +in v4 Color; + in r32 CornerRadius; + in r32 EdgeSoftness; + in r32 BorderThickness; +)GLSL" + + "uniform sampler2D TextureSamplers[" Stringify(MAX_BOUND_TEXTURES) "];" + + R"GLSL( + +out v4 Out_Color; + +r32 RoundedRect(v2 P, v2 Center, v2 HalfSize, r32 r) +{ +v2 d2 = AbsoluteValue(Center - P) - HalfSize + r; +r32 Result = Min(Max(d2.x, d2.y), 0) + Length(Max(d2, 0)) - r; +return(Result); +} + +void main() +{ +r32 SoftnessPadding = Max(0, EdgeSoftness*2-1); + +r32 Dist = RoundedRect(DestP, DestCenter, DestHalfSize - SoftnessPadding, CornerRadius); +r32 SDFFactor = 1 - smoothstep(0, 2*EdgeSoftness, Dist); + +r32 BorderFactor = 1; +if(BorderThickness != 0) +{ +v2 InteriorHalfSize = DestHalfSize - BorderThickness; + +r32 InteriorRadiusReduce = Min(InteriorHalfSize.x / DestHalfSize.x, InteriorHalfSize.y / DestHalfSize.y); +r32 InteriorCornerRadius = CornerRadius*InteriorRadiusReduce*InteriorRadiusReduce; + +r32 InsideDist = RoundedRect(DestP, DestCenter, InteriorHalfSize - SoftnessPadding, InteriorCornerRadius); +BorderFactor = smoothstep(0, 2*EdgeSoftness, InsideDist); +} + +v4 TextureFactor = texture(TextureSamplers[TextureIndex], SourceP); +Out_Color = Color*TextureFactor*BorderFactor*SDFFactor; +})GLSL"; + + quad_program Program = {}; + Program.ID = OpenGL_CompileShaderProgram(VertexSource, FragmentSource); + + Program.PID = glGetAttribLocation(Program.ID, "In_P"); + Program.SourcePID = glGetAttribLocation(Program.ID, "In_SourceP"); + Program.TextureIndexID = glGetAttribLocation(Program.ID, "In_TextureIndex"); + Program.ColorID = glGetAttribLocation(Program.ID, "In_Color"); + Program.ToCenterID = glGetAttribLocation(Program.ID, "In_ToCenter"); + Program.HalfSizeID = glGetAttribLocation(Program.ID, "In_HalfSize"); + Program.CornerRadiusID = glGetAttribLocation(Program.ID, "In_CornerRadius"); + Program.EdgeSoftnessID = glGetAttribLocation(Program.ID, "In_EdgeSoftness"); + Program.BorderThicknessID = glGetAttribLocation(Program.ID, "In_BorderThickness"); + + glUseProgram(Program.ID); + Program.UniformResolutionLocation = glGetUniformLocation(Program.ID, "Uniform_Resolution"); + + temporary_memory Scratch = GetScratch(0, 0); + for(s32 TextureIndex = 0; + TextureIndex < MAX_BOUND_TEXTURES; + ++TextureIndex) + { + string Name = PushFormat(Scratch.Arena, "TextureSamplers[%i]", TextureIndex); + s32 Location = glGetUniformLocation(Program.ID, (char *)Name.Data); + glUniform1i(Location, TextureIndex); + } + ReleaseScratch(Scratch); + + glUseProgram(0); + + return(Program); +} + +#define OpenGL_EnableFloatVertexAttribute(Index, Size, Type, type, Member)\ +if(Index != -1)\ +{\ +glEnableVertexAttribArray(Index);\ +glVertexAttribPointer(Index, Size, Type, GL_FALSE, sizeof(type), (void *)OffsetOf(type, Member));\ +} + +#define OpenGL_EnableIntegerVertexAttribute(Index, Size, Type, type, Member)\ +if(Index != -1)\ +{\ +glEnableVertexAttribArray(Index);\ +glVertexAttribIPointer(Index, Size, Type, sizeof(type), (void *)OffsetOf(type, Member));\ +} + +#define OpenGL_DisableVertexAttribute(Index)\ +if(Index != -1)\ +{\ +glDisableVertexAttribArray(Index);\ +} + +static void OpenGL_BeginProgram(quad_program *Program) +{ + glUseProgram(Program->ID); + + OpenGL_EnableFloatVertexAttribute(Program->PID, 2, GL_FLOAT, quad_vertex, P); + OpenGL_EnableFloatVertexAttribute(Program->SourcePID, 2, GL_FLOAT, quad_vertex, SourceP); + OpenGL_EnableIntegerVertexAttribute(Program->TextureIndexID, 1, GL_UNSIGNED_INT, quad_vertex, TextureIndex); + OpenGL_EnableIntegerVertexAttribute(Program->ColorID, 1, GL_UNSIGNED_INT, quad_vertex, Color); + OpenGL_EnableFloatVertexAttribute(Program->ToCenterID, 2, GL_FLOAT, quad_vertex, ToCenter); + OpenGL_EnableFloatVertexAttribute(Program->HalfSizeID, 2, GL_FLOAT, quad_vertex, HalfSize); + OpenGL_EnableFloatVertexAttribute(Program->CornerRadiusID, 1, GL_FLOAT, quad_vertex, CornerRadius); + OpenGL_EnableFloatVertexAttribute(Program->EdgeSoftnessID, 1, GL_FLOAT, quad_vertex, EdgeSoftness); + OpenGL_EnableFloatVertexAttribute(Program->BorderThicknessID, 1, GL_FLOAT, quad_vertex, BorderThickness); +} + +static void OpenGL_EndProgram(quad_program *Program) +{ + OpenGL_DisableVertexAttribute(Program->PID); + OpenGL_DisableVertexAttribute(Program->SourcePID); + OpenGL_DisableVertexAttribute(Program->TextureIndexID); + OpenGL_DisableVertexAttribute(Program->ColorID); + OpenGL_DisableVertexAttribute(Program->ToCenterID); + OpenGL_DisableVertexAttribute(Program->HalfSizeID); + OpenGL_DisableVertexAttribute(Program->CornerRadiusID); + OpenGL_DisableVertexAttribute(Program->EdgeSoftnessID); + OpenGL_DisableVertexAttribute(Program->BorderThicknessID); + + glUseProgram(0); +} + +static opengl_context OpenGL_SetupContext(vn_render_commands *RenderCommands, umm MaxPushBufferSize) +{ + opengl_context Context = {}; + + RenderCommands->MaxPushBufferSize = MaxPushBufferSize; + RenderCommands->PushBufferBase = (u8 *)OpenGL_AllocateMemory(RenderCommands->MaxPushBufferSize); + + RenderCommands->MaxQuadVertexCount = MAX_QUAD_COUNT*4; + RenderCommands->QuadVertexBase = (quad_vertex *)OpenGL_AllocateMemory(RenderCommands->MaxQuadVertexCount*sizeof(quad_vertex)); + + RenderCommands->MaxQuadIndexCount = MAX_QUAD_COUNT*6; + RenderCommands->QuadIndexBase = (s32 *)OpenGL_AllocateMemory(RenderCommands->MaxQuadIndexCount*sizeof(s32)); + + Context.QuadProgram = OpenGL_CompileQuadProgram(); + + glGenBuffers(1, &Context.VertexBuffer); + glGenBuffers(1, &Context.IndexBuffer); + + glBindBuffer(GL_ARRAY_BUFFER, Context.VertexBuffer); + glBufferData(GL_ARRAY_BUFFER, RenderCommands->MaxQuadVertexCount*sizeof(quad_vertex), 0, GL_STREAM_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, Context.IndexBuffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, RenderCommands->MaxQuadIndexCount*sizeof(s32), 0, GL_STREAM_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + u32 WhiteData = 0xFFFFFFFF; + RenderCommands->WhiteTexture = OpenGL_AllocateTexture(V2S(1, 1), Render_TextureFormat_RGBA8, &WhiteData); + + RenderCommands->AllocateTexture = OpenGL_AllocateTexture; + RenderCommands->FillRegion = OpenGL_FillRegion; + +#if VN_SLOW + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glDebugMessageCallback(OpenGL_DebugMessageCallback, 0); +#endif + + u32 DummyVertexArray; + glGenVertexArrays(1, &DummyVertexArray); + glBindVertexArray(DummyVertexArray); + + return(Context); +} + +static void OpenGL_BeginFrame(vn_render_commands *RenderCommands, v2 RenderDim) +{ + RenderCommands->PushBufferAt = RenderCommands->PushBufferBase; + RenderCommands->QuadVertexCount = 0; + RenderCommands->QuadIndexCount = 0; + + RenderCommands->RenderDim = RenderDim; +} + +static void OpenGL_EndFrame(opengl_context *Context, vn_render_commands *RenderCommands) +{ + glViewport(0, 0, RenderCommands->RenderDim.x, RenderCommands->RenderDim.y); + glScissor(0, 0, RenderCommands->RenderDim.x, RenderCommands->RenderDim.y); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glEnable(GL_SCISSOR_TEST); + + glBindBuffer(GL_ARRAY_BUFFER, Context->VertexBuffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, Context->IndexBuffer); + + void *VertexData = RenderCommands->QuadVertexBase; + umm VertexSize = RenderCommands->QuadVertexCount*sizeof(quad_vertex); + glBufferSubData(GL_ARRAY_BUFFER, 0, VertexSize, VertexData); + + void *IndexData = RenderCommands->QuadIndexBase; + umm IndexSize = RenderCommands->QuadIndexCount*sizeof(s32); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, IndexSize, IndexData); + + OpenGL_BeginProgram(&Context->QuadProgram); + glUniform2f(Context->QuadProgram.UniformResolutionLocation, + RenderCommands->RenderDim.x, RenderCommands->RenderDim.y); + + for(u8 *PushBufferAt = RenderCommands->PushBufferBase; + PushBufferAt < RenderCommands->PushBufferAt;) + { + render_command_header *Header = (render_command_header *)PushBufferAt; + PushBufferAt += sizeof(*Header); + + switch(Header->Type) + { + case Render_Command_render_command_clear: + { + render_command_clear *Command = (render_command_clear *)PushBufferAt; + PushBufferAt += sizeof(*Command); + + glClearColor(Command->Color.r, Command->Color.g, Command->Color.b, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + } break; + + case Render_Command_render_command_quads: + { + render_command_quads *Command = (render_command_quads *)PushBufferAt; + PushBufferAt += sizeof(*Command); + + for(s32 TextureIndex = 0; + TextureIndex < Command->TexturesUsed; + ++TextureIndex) + { + opengl_texture Texture = OpenGL_GetTextureFromHandle(Command->Textures[TextureIndex]); + glActiveTexture(GL_TEXTURE0 + TextureIndex); + glBindTexture(GL_TEXTURE_2D, Texture.ID); + } + + glDrawElements(GL_TRIANGLES, Command->QuadCount*6, GL_UNSIGNED_INT, 0); + + glBindTexture(GL_TEXTURE_2D, 0); + } break; + + case Render_Command_render_command_clip: + { + render_command_clip *Command = (render_command_clip *)PushBufferAt; + PushBufferAt += sizeof(*Command); + +#if 0 + glScissor(Command->ClipRect.P.x, RenderCommands->RenderDim.y - Command->ClipRect.P.y - Command->ClipRect.Dim.y, + Command->ClipRect.Dim.x, Command->ClipRect.Dim.y); +#endif + } break; + } + } + + OpenGL_EndProgram(&Context->QuadProgram); + + glBindBuffer(GL_ARRAY_BUFFER, 0); +} \ No newline at end of file diff --git a/code/opengl_render.h b/code/opengl_render.h new file mode 100644 index 0000000..438b8e9 --- /dev/null +++ b/code/opengl_render.h @@ -0,0 +1,41 @@ +/* date = May 7th 2023 9:49 am */ + +#ifndef OPENGL_RENDER_H +#define OPENGL_RENDER_H + +#include "vn_opengl_defines.h" +#include "generated/vn_opengl_functions.h" + +struct opengl_texture +{ + u32 ID; + render_texture_format Format; + v2s Dim; +}; + +struct quad_program +{ + u32 ID; + + u32 PID; + u32 SourcePID; + u32 TextureIndexID; + u32 ColorID; + u32 ToCenterID; + u32 HalfSizeID; + u32 CornerRadiusID; + u32 EdgeSoftnessID; + u32 BorderThicknessID; + + u32 UniformResolutionLocation; +}; + +struct opengl_context +{ + u32 VertexBuffer; + u32 IndexBuffer; + + quad_program QuadProgram; +}; + +#endif //OPENGL_RENDER_H diff --git a/code/third_party/stb_image.h b/code/third_party/stb_image.h new file mode 100644 index 0000000..5e807a0 --- /dev/null +++ b/code/third_party/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. +------------------------------------------------------------------------------ +*/ diff --git a/code/third_party/stb_sprintf.h b/code/third_party/stb_sprintf.h new file mode 100644 index 0000000..7b9ae00 --- /dev/null +++ b/code/third_party/stb_sprintf.h @@ -0,0 +1,1942 @@ +// stb_sprintf - v1.10 - public domain snprintf() implementation +// originally by Jeff Roberts / RAD Game Tools, 2015/10/20 +// http://github.com/nothings/stb +// +// allowed types: sc uidBboXx p AaGgEef n +// lengths : hh h ll j z t I64 I32 I +// +// Contributors: +// Fabian "ryg" Giesen (reformatting) +// github:aganm (attribute format) +// +// Contributors (bugfixes): +// github:d26435 +// github:trex78 +// github:account-login +// Jari Komppa (SI suffixes) +// Rohit Nirmal +// Marcin Wojdyr +// Leonard Ritter +// Stefano Zanotti +// Adam Allison +// Arvid Gerstmann +// Markus Kolb +// +// LICENSE: +// +// See end of file for license information. + +#ifndef STB_SPRINTF_H_INCLUDE +#define STB_SPRINTF_H_INCLUDE + +/* +Single file sprintf replacement. + +Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20. +Hereby placed in public domain. + +This is a full sprintf replacement that supports everything that +the C runtime sprintfs support, including float/double, 64-bit integers, +hex floats, field parameters (%*.*d stuff), length reads backs, etc. + +Why would you need this if sprintf already exists? Well, first off, +it's *much* faster (see below). It's also much smaller than the CRT +versions code-space-wise. We've also added some simple improvements +that are super handy (commas in thousands, callbacks at buffer full, +for example). Finally, the format strings for MSVC and GCC differ +for 64-bit integers (among other small things), so this lets you use +the same format strings in cross platform code. + +It uses the standard single file trick of being both the header file +and the source itself. If you just include it normally, you just get +the header file function definitions. To get the code, you include +it from a C or C++ file and define STB_SPRINTF_IMPLEMENTATION first. + +It only uses va_args macros from the C runtime to do it's work. It +does cast doubles to S64s and shifts and divides U64s, which does +drag in CRT code on most platforms. + +It compiles to roughly 8K with float support, and 4K without. +As a comparison, when using MSVC static libs, calling sprintf drags +in 16K. + +API: +==== +int stbsp_sprintf( char * buf, char const * fmt, ... ) +int stbsp_snprintf( char * buf, int count, char const * fmt, ... ) + Convert an arg list into a buffer. stbsp_snprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintf( char * buf, char const * fmt, va_list va ) +int stbsp_vsnprintf( char * buf, int count, char const * fmt, va_list va ) + Convert a va_list arg list into a buffer. stbsp_vsnprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintfcb( STBSP_SPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) + typedef char * STBSP_SPRINTFCB( char const * buf, void * user, int len ); + Convert into a buffer, calling back every STB_SPRINTF_MIN chars. + Your callback can then copy the chars out, print them or whatever. + This function is actually the workhorse for everything else. + The buffer you pass in must hold at least STB_SPRINTF_MIN characters. + // you return the next buffer to use or 0 to stop converting + +void stbsp_set_separators( char comma, char period ) + Set the comma and period characters to use. + +FLOATS/DOUBLES: +=============== +This code uses a internal float->ascii conversion method that uses +doubles with error correction (double-doubles, for ~105 bits of +precision). This conversion is round-trip perfect - that is, an atof +of the values output here will give you the bit-exact double back. + +One difference is that our insignificant digits will be different than +with MSVC or GCC (but they don't match each other either). We also +don't attempt to find the minimum length matching float (pre-MSVC15 +doesn't either). + +If you don't need float or doubles at all, define STB_SPRINTF_NOFLOAT +and you'll save 4K of code space. + +64-BIT INTS: +============ +This library also supports 64-bit integers and you can use MSVC style or +GCC style indicators (%I64d or %lld). It supports the C99 specifiers +for size_t and ptr_diff_t (%jd %zd) as well. + +EXTRAS: +======= +Like some GCCs, for integers and floats, you can use a ' (single quote) +specifier and commas will be inserted on the thousands: "%'d" on 12345 +would print 12,345. + +For integers and floats, you can use a "$" specifier and the number +will be converted to float and then divided to get kilo, mega, giga or +tera and then printed, so "%$d" 1000 is "1.0 k", "%$.2d" 2536000 is +"2.53 M", etc. For byte values, use two $:s, like "%$$d" to turn +2536000 to "2.42 Mi". If you prefer JEDEC suffixes to SI ones, use three +$:s: "%$$$d" -> "2.42 M". To remove the space between the number and the +suffix, add "_" specifier: "%_$d" -> "2.53M". + +In addition to octal and hexadecimal conversions, you can print +integers in binary: "%b" for 256 would print 100. + +PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): +=================================================================== +"%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) +"%24d" across all 32-bit ints (4.5x/4.2x faster) +"%x" across all 32-bit ints (4.5x/3.8x faster) +"%08x" across all 32-bit ints (4.3x/3.8x faster) +"%f" across e-10 to e+10 floats (7.3x/6.0x faster) +"%e" across e-10 to e+10 floats (8.1x/6.0x faster) +"%g" across e-10 to e+10 floats (10.0x/7.1x faster) +"%f" for values near e-300 (7.9x/6.5x faster) +"%f" for values near e+300 (10.0x/9.1x faster) +"%e" for values near e-300 (10.1x/7.0x faster) +"%e" for values near e+300 (9.2x/6.0x faster) +"%.320f" for values near e-300 (12.6x/11.2x faster) +"%a" for random values (8.6x/4.3x faster) +"%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster) +"%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster) +"%s%s%s" for 64 char strings (7.1x/7.3x faster) +"...512 char string..." ( 35.0x/32.5x faster!) +*/ + +#if defined(__clang__) +#if defined(__has_feature) && defined(__has_attribute) +#if __has_feature(address_sanitizer) +#if __has_attribute(__no_sanitize__) +#define STBSP__ASAN __attribute__((__no_sanitize__("address"))) +#elif __has_attribute(__no_sanitize_address__) +#define STBSP__ASAN __attribute__((__no_sanitize_address__)) +#elif __has_attribute(__no_address_safety_analysis__) +#define STBSP__ASAN __attribute__((__no_address_safety_analysis__)) +#endif +#endif +#endif +#elif defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) +#if defined(__SANITIZE_ADDRESS__) && __SANITIZE_ADDRESS__ +#define STBSP__ASAN __attribute__((__no_sanitize_address__)) +#endif +#endif + +#ifndef STBSP__ASAN +#define STBSP__ASAN +#endif + +#ifdef STB_SPRINTF_STATIC +#define STBSP__PUBLICDEC static +#define STBSP__PUBLICDEF static STBSP__ASAN +#else +#ifdef __cplusplus +#define STBSP__PUBLICDEC extern "C" +#define STBSP__PUBLICDEF extern "C" STBSP__ASAN +#else +#define STBSP__PUBLICDEC extern +#define STBSP__PUBLICDEF STBSP__ASAN +#endif +#endif + +#if defined(__has_attribute) +#if __has_attribute(format) +#define STBSP__ATTRIBUTE_FORMAT(fmt,va) __attribute__((format(printf,fmt,va))) +#endif +#endif + +#ifndef STBSP__ATTRIBUTE_FORMAT +#define STBSP__ATTRIBUTE_FORMAT(fmt,va) +#endif + +#ifdef _MSC_VER +#define STBSP__NOTUSED(v) (void)(v) +#else +#define STBSP__NOTUSED(v) (void)sizeof(v) +#endif + +#include // for va_arg(), va_list() +#include // size_t, ptrdiff_t + +#ifndef STB_SPRINTF_MIN +#define STB_SPRINTF_MIN 512 // how many characters per callback +#endif +typedef char *STBSP_SPRINTFCB(const char *buf, void *user, int len); + +#ifndef STB_SPRINTF_DECORATE +#define STB_SPRINTF_DECORATE(name) stbsp_##name // define this before including if you want to change the names +#endif + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(2,3); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(3,4); + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); + +#endif // STB_SPRINTF_H_INCLUDE + +#ifdef STB_SPRINTF_IMPLEMENTATION + +#define stbsp__uint32 unsigned int +#define stbsp__int32 signed int + +#ifdef _MSC_VER +#define stbsp__uint64 unsigned __int64 +#define stbsp__int64 signed __int64 +#else +#define stbsp__uint64 unsigned long long +#define stbsp__int64 signed long long +#endif +#define stbsp__uint16 unsigned short + +#ifndef stbsp__uintptr +#if defined(__ppc64__) || defined(__powerpc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) || defined(__s390x__) +#define stbsp__uintptr stbsp__uint64 +#else +#define stbsp__uintptr stbsp__uint32 +#endif +#endif + +#ifndef STB_SPRINTF_MSVC_MODE // used for MSVC2013 and earlier (MSVC2015 matches GCC) +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#define STB_SPRINTF_MSVC_MODE +#endif +#endif + +#ifdef STB_SPRINTF_NOUNALIGNED // define this before inclusion to force stbsp_sprintf to always use aligned accesses +#define STBSP__UNALIGNED(code) +#else +#define STBSP__UNALIGNED(code) code +#endif + +#ifndef STB_SPRINTF_NOFLOAT +// internal float utility functions +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits); +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value); +#define STBSP__SPECIAL 0x7000 +#endif + +static char stbsp__period = '.'; +static char stbsp__comma = ','; +static struct +{ + short temp; // force next field to be 2-byte aligned + char pair[201]; +} stbsp__digitpair = +{ + 0, + "00010203040506070809101112131415161718192021222324" + "25262728293031323334353637383940414243444546474849" + "50515253545556575859606162636465666768697071727374" + "75767778798081828384858687888990919293949596979899" +}; + +STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char pcomma, char pperiod) +{ + stbsp__period = pperiod; + stbsp__comma = pcomma; +} + +#define STBSP__LEFTJUST 1 +#define STBSP__LEADINGPLUS 2 +#define STBSP__LEADINGSPACE 4 +#define STBSP__LEADING_0X 8 +#define STBSP__LEADINGZERO 16 +#define STBSP__INTMAX 32 +#define STBSP__TRIPLET_COMMA 64 +#define STBSP__NEGATIVE 128 +#define STBSP__METRIC_SUFFIX 256 +#define STBSP__HALFWIDTH 512 +#define STBSP__METRIC_NOSPACE 1024 +#define STBSP__METRIC_1024 2048 +#define STBSP__METRIC_JEDEC 4096 + +static void stbsp__lead_sign(stbsp__uint32 fl, char *sign) +{ + sign[0] = 0; + if (fl & STBSP__NEGATIVE) { + sign[0] = 1; + sign[1] = '-'; + } else if (fl & STBSP__LEADINGSPACE) { + sign[0] = 1; + sign[1] = ' '; + } else if (fl & STBSP__LEADINGPLUS) { + sign[0] = 1; + sign[1] = '+'; + } +} + +static STBSP__ASAN stbsp__uint32 stbsp__strlen_limited(char const *s, stbsp__uint32 limit) +{ + char const * sn = s; + + // get up to 4-byte alignment + for (;;) { + if (((stbsp__uintptr)sn & 3) == 0) + break; + + if (!limit || *sn == 0) + return (stbsp__uint32)(sn - s); + + ++sn; + --limit; + } + + // scan over 4 bytes at a time to find terminating 0 + // this will intentionally scan up to 3 bytes past the end of buffers, + // but becase it works 4B aligned, it will never cross page boundaries + // (hence the STBSP__ASAN markup; the over-read here is intentional + // and harmless) + while (limit >= 4) { + stbsp__uint32 v = *(stbsp__uint32 *)sn; + // bit hack to find if there's a 0 byte in there + if ((v - 0x01010101) & (~v) & 0x80808080UL) + break; + + sn += 4; + limit -= 4; + } + + // handle the last few characters to find actual size + while (limit && *sn) { + ++sn; + --limit; + } + + return (stbsp__uint32)(sn - s); +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va) +{ + static char hex[] = "0123456789abcdefxp"; + static char hexu[] = "0123456789ABCDEFXP"; + char *bf; + char const *f; + int tlen = 0; + + bf = buf; + f = fmt; + for (;;) { + stbsp__int32 fw, pr, tz; + stbsp__uint32 fl; + + // macros for the callback buffer stuff +#define stbsp__chk_cb_bufL(bytes) \ +{ \ +int len = (int)(bf - buf); \ +if ((len + (bytes)) >= STB_SPRINTF_MIN) { \ +tlen += len; \ +if (0 == (bf = buf = callback(buf, user, len))) \ +goto done; \ +} \ +} +#define stbsp__chk_cb_buf(bytes) \ +{ \ +if (callback) { \ +stbsp__chk_cb_bufL(bytes); \ +} \ +} +#define stbsp__flush_cb() \ +{ \ +stbsp__chk_cb_bufL(STB_SPRINTF_MIN - 1); \ +} // flush if there is even one byte in the buffer +#define stbsp__cb_buf_clamp(cl, v) \ +cl = v; \ +if (callback) { \ +int lg = STB_SPRINTF_MIN - (int)(bf - buf); \ +if (cl > lg) \ +cl = lg; \ +} + + // fast copy everything up to the next % (or end of string) + for (;;) { + while (((stbsp__uintptr)f) & 3) { + schk1: + if (f[0] == '%') + goto scandd; + schk2: + if (f[0] == 0) + goto endfmt; + stbsp__chk_cb_buf(1); + *bf++ = f[0]; + ++f; + } + for (;;) { + // Check if the next 4 bytes contain %(0x25) or end of string. + // Using the 'hasless' trick: + // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord + stbsp__uint32 v, c; + v = *(stbsp__uint32 *)f; + c = (~v) & 0x80808080; + if (((v ^ 0x25252525) - 0x01010101) & c) + goto schk1; + if ((v - 0x01010101) & c) + goto schk2; + if (callback) + if ((STB_SPRINTF_MIN - (int)(bf - buf)) < 4) + goto schk1; +#ifdef STB_SPRINTF_NOUNALIGNED + if(((stbsp__uintptr)bf) & 3) { + bf[0] = f[0]; + bf[1] = f[1]; + bf[2] = f[2]; + bf[3] = f[3]; + } else +#endif + { + *(stbsp__uint32 *)bf = v; + } + bf += 4; + f += 4; + } + } + scandd: + + ++f; + + // ok, we have a percent, read the modifiers first + fw = 0; + pr = -1; + fl = 0; + tz = 0; + + // flags + for (;;) { + switch (f[0]) { + // if we have left justify + case '-': + fl |= STBSP__LEFTJUST; + ++f; + continue; + // if we have leading plus + case '+': + fl |= STBSP__LEADINGPLUS; + ++f; + continue; + // if we have leading space + case ' ': + fl |= STBSP__LEADINGSPACE; + ++f; + continue; + // if we have leading 0x + case '#': + fl |= STBSP__LEADING_0X; + ++f; + continue; + // if we have thousand commas + case '\'': + fl |= STBSP__TRIPLET_COMMA; + ++f; + continue; + // if we have kilo marker (none->kilo->kibi->jedec) + case '$': + if (fl & STBSP__METRIC_SUFFIX) { + if (fl & STBSP__METRIC_1024) { + fl |= STBSP__METRIC_JEDEC; + } else { + fl |= STBSP__METRIC_1024; + } + } else { + fl |= STBSP__METRIC_SUFFIX; + } + ++f; + continue; + // if we don't want space between metric suffix and number + case '_': + fl |= STBSP__METRIC_NOSPACE; + ++f; + continue; + // if we have leading zero + case '0': + fl |= STBSP__LEADINGZERO; + ++f; + goto flags_done; + default: goto flags_done; + } + } + flags_done: + + // get the field width + if (f[0] == '*') { + fw = va_arg(va, stbsp__uint32); + ++f; + } else { + while ((f[0] >= '0') && (f[0] <= '9')) { + fw = fw * 10 + f[0] - '0'; + f++; + } + } + // get the precision + if (f[0] == '.') { + ++f; + if (f[0] == '*') { + pr = va_arg(va, stbsp__uint32); + ++f; + } else { + pr = 0; + while ((f[0] >= '0') && (f[0] <= '9')) { + pr = pr * 10 + f[0] - '0'; + f++; + } + } + } + + // handle integer size overrides + switch (f[0]) { + // are we halfwidth? + case 'h': + fl |= STBSP__HALFWIDTH; + ++f; + if (f[0] == 'h') + ++f; // QUARTERWIDTH + break; + // are we 64-bit (unix style) + case 'l': + fl |= ((sizeof(long) == 8) ? STBSP__INTMAX : 0); + ++f; + if (f[0] == 'l') { + fl |= STBSP__INTMAX; + ++f; + } + break; + // are we 64-bit on intmax? (c99) + case 'j': + fl |= (sizeof(size_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit on size_t or ptrdiff_t? (c99) + case 'z': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + case 't': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit (msft style) + case 'I': + if ((f[1] == '6') && (f[2] == '4')) { + fl |= STBSP__INTMAX; + f += 3; + } else if ((f[1] == '3') && (f[2] == '2')) { + f += 3; + } else { + fl |= ((sizeof(void *) == 8) ? STBSP__INTMAX : 0); + ++f; + } + break; + default: break; + } + + // handle each replacement + switch (f[0]) { +#define STBSP__NUMSZ 512 // big enough for e308 (with commas) or e-307 + char num[STBSP__NUMSZ]; + char lead[8]; + char tail[8]; + char *s; + char const *h; + stbsp__uint32 l, n, cs; + stbsp__uint64 n64; +#ifndef STB_SPRINTF_NOFLOAT + double fv; +#endif + stbsp__int32 dp; + char const *sn; + + case 's': + // get the string + s = va_arg(va, char *); + if (s == 0) + s = (char *)"null"; + // get the length, limited to desired precision + // always limit to ~0u chars since our counts are 32b + l = stbsp__strlen_limited(s, (pr >= 0) ? pr : ~0u); + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + // copy the string in + goto scopy; + + //- sixten: CUSTOM CODE STARTS HERE!!! + case 'S': + { + string String = va_arg(va, string); + + s = (char *)String.Data; + sn = (const char *)(String.Data + String.Count); + Assert(String.Count <= U32_Max); + l = (unsigned int)String.Count; + + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + + goto scopy; + } break; + + case 'U': + { + u32 Codepoint = va_arg(va, u32); + u8 Data[5] = {}; + UTF8FromCodepoint(Data, Codepoint); + + s = (char *)Data; + l = stbsp__strlen_limited(s, (pr >= 0) ? pr : ~0u); + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + } break; + //- sixten: CUSTOM CODE ENDS HERE!!! + + case 'c': // char + // get the character + s = num + STBSP__NUMSZ - 1; + *s = (char)va_arg(va, int); + l = 1; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + + case 'n': // weird write-bytes specifier + { + int *d = va_arg(va, int *); + *d = tlen + (int)(bf - buf); + } break; + +#ifdef STB_SPRINTF_NOFLOAT + case 'A': // float + case 'a': // hex float + case 'G': // float + case 'g': // float + case 'E': // float + case 'e': // float + case 'f': // float + va_arg(va, double); // eat it + s = (char *)"No float"; + l = 8; + lead[0] = 0; + tail[0] = 0; + pr = 0; + cs = 0; + STBSP__NOTUSED(dp); + goto scopy; +#else + case 'A': // hex float + case 'a': // hex float + h = (f[0] == 'A') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_parts((stbsp__int64 *)&n64, &dp, fv)) + fl |= STBSP__NEGATIVE; + + s = num + 64; + + stbsp__lead_sign(fl, lead); + + if (dp == -1023) + dp = (n64) ? -1022 : 0; + else + n64 |= (((stbsp__uint64)1) << 52); + n64 <<= (64 - 56); + if (pr < 15) + n64 += ((((stbsp__uint64)8) << 56) >> (pr * 4)); + // add leading chars + +#ifdef STB_SPRINTF_MSVC_MODE + *s++ = '0'; + *s++ = 'x'; +#else + lead[1 + lead[0]] = '0'; + lead[2 + lead[0]] = 'x'; + lead[0] += 2; +#endif + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + if (pr) + *s++ = stbsp__period; + sn = s; + + // print the bits + n = pr; + if (n > 13) + n = 13; + if (pr > (stbsp__int32)n) + tz = pr - n; + pr = 0; + while (n--) { + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + } + + // print the expo + tail[1] = h[17]; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } else + tail[2] = '+'; + n = (dp >= 1000) ? 6 : ((dp >= 100) ? 5 : ((dp >= 10) ? 4 : 3)); + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + + dp = (int)(s - sn); + l = (int)(s - (num + 64)); + s = num + 64; + cs = 1 + (3 << 24); + goto scopy; + + case 'G': // float + case 'g': // float + h = (f[0] == 'G') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; + else if (pr == 0) + pr = 1; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, (pr - 1) | 0x80000000)) + fl |= STBSP__NEGATIVE; + + // clamp the precision and delete extra zeros after clamp + n = pr; + if (l > (stbsp__uint32)pr) + l = pr; + while ((l > 1) && (pr) && (sn[l - 1] == '0')) { + --pr; + --l; + } + + // should we use %e + if ((dp <= -4) || (dp > (stbsp__int32)n)) { + if (pr > (stbsp__int32)l) + pr = l - 1; + else if (pr) + --pr; // when using %e, there is one digit before the decimal + goto doexpfromg; + } + // this is the insane action to get the pr to match %g semantics for %f + if (dp > 0) { + pr = (dp < (stbsp__int32)l) ? l - dp : 0; + } else { + pr = -dp + ((pr > (stbsp__int32)l) ? (stbsp__int32) l : pr); + } + goto dofloatfromg; + + case 'E': // float + case 'e': // float + h = (f[0] == 'E') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr | 0x80000000)) + fl |= STBSP__NEGATIVE; + doexpfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + // handle leading chars + *s++ = sn[0]; + + if (pr) + *s++ = stbsp__period; + + // handle after decimal + if ((l - 1) > (stbsp__uint32)pr) + l = pr + 1; + for (n = 1; n < l; n++) + *s++ = sn[n]; + // trailing zeros + tz = pr - (l - 1); + pr = 0; + // dump expo + tail[1] = h[0xe]; + dp -= 1; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } else + tail[2] = '+'; +#ifdef STB_SPRINTF_MSVC_MODE + n = 5; +#else + n = (dp >= 100) ? 5 : 4; +#endif + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + cs = 1 + (3 << 24); // how many tens + goto flt_lead; + + case 'f': // float + fv = va_arg(va, double); + doafloat: + // do kilos + if (fl & STBSP__METRIC_SUFFIX) { + double divisor; + divisor = 1000.0f; + if (fl & STBSP__METRIC_1024) + divisor = 1024.0; + while (fl < 0x4000000) { + if ((fv < divisor) && (fv > -divisor)) + break; + fv /= divisor; + fl += 0x1000000; + } + } + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr)) + fl |= STBSP__NEGATIVE; + dofloatfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + + // handle the three decimal varieties + if (dp <= 0) { + stbsp__int32 i; + // handle 0.000*000xxxx + *s++ = '0'; + if (pr) + *s++ = stbsp__period; + n = -dp; + if ((stbsp__int32)n > pr) + n = pr; + i = n; + while (i) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + i -= 4; + } + while (i) { + *s++ = '0'; + --i; + } + if ((stbsp__int32)(l + n) > pr) + l = pr - n; + i = l; + while (i) { + *s++ = *sn++; + --i; + } + tz = pr - (n + l); + cs = 1 + (3 << 24); // how many tens did we write (for commas below) + } else { + cs = (fl & STBSP__TRIPLET_COMMA) ? ((600 - (stbsp__uint32)dp) % 3) : 0; + if ((stbsp__uint32)dp >= l) { + // handle xxxx000*000.0 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = sn[n]; + ++n; + if (n >= l) + break; + } + } + if (n < (stbsp__uint32)dp) { + n = dp - n; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (n) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --n; + } + while (n >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + n -= 4; + } + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = '0'; + --n; + } + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) { + *s++ = stbsp__period; + tz = pr; + } + } else { + // handle xxxxx.xxxx000*000 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = sn[n]; + ++n; + if (n >= (stbsp__uint32)dp) + break; + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) + *s++ = stbsp__period; + if ((l - dp) > (stbsp__uint32)pr) + l = pr + dp; + while (n < l) { + *s++ = sn[n]; + ++n; + } + tz = pr - (l - dp); + } + } + pr = 0; + + // handle k,m,g,t + if (fl & STBSP__METRIC_SUFFIX) { + char idx; + idx = 1; + if (fl & STBSP__METRIC_NOSPACE) + idx = 0; + tail[0] = idx; + tail[1] = ' '; + { + if (fl >> 24) { // SI kilo is 'k', JEDEC and SI kibits are 'K'. + if (fl & STBSP__METRIC_1024) + tail[idx + 1] = "_KMGT"[fl >> 24]; + else + tail[idx + 1] = "_kMGT"[fl >> 24]; + idx++; + // If printing kibits and not in jedec, add the 'i'. + if (fl & STBSP__METRIC_1024 && !(fl & STBSP__METRIC_JEDEC)) { + tail[idx + 1] = 'i'; + idx++; + } + tail[0] = idx; + } + } + }; + + flt_lead: + // get the length that we copied + l = (stbsp__uint32)(s - (num + 64)); + s = num + 64; + goto scopy; +#endif + + case 'B': // upper binary + case 'b': // lower binary + h = (f[0] == 'B') ? hexu : hex; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[0xb]; + } + l = (8 << 4) | (1 << 8); + goto radixnum; + + case 'o': // octal + h = hexu; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 1; + lead[1] = '0'; + } + l = (3 << 4) | (3 << 8); + goto radixnum; + + case 'p': // pointer + fl |= (sizeof(void *) == 8) ? STBSP__INTMAX : 0; + pr = sizeof(void *) * 2; + fl &= ~STBSP__LEADINGZERO; // 'p' only prints the pointer with zeros + // fall through - to X + + case 'X': // upper hex + case 'x': // lower hex + h = (f[0] == 'X') ? hexu : hex; + l = (4 << 4) | (4 << 8); + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[16]; + } + radixnum: + // get the number + if (fl & STBSP__INTMAX) + n64 = va_arg(va, stbsp__uint64); + else + n64 = va_arg(va, stbsp__uint32); + + s = num + STBSP__NUMSZ; + dp = 0; + // clear tail, and clear leading if value is zero + tail[0] = 0; + if (n64 == 0) { + lead[0] = 0; + if (pr == 0) { + l = 0; + cs = 0; + goto scopy; + } + } + // convert to string + for (;;) { + *--s = h[n64 & ((1 << (l >> 8)) - 1)]; + n64 >>= (l >> 8); + if (!((n64) || ((stbsp__int32)((num + STBSP__NUMSZ) - s) < pr))) + break; + if (fl & STBSP__TRIPLET_COMMA) { + ++l; + if ((l & 15) == ((l >> 4) & 15)) { + l &= ~15; + *--s = stbsp__comma; + } + } + }; + // get the tens and the comma pos + cs = (stbsp__uint32)((num + STBSP__NUMSZ) - s) + ((((l >> 4) & 15)) << 24); + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + // copy it + goto scopy; + + case 'u': // unsigned + case 'i': + case 'd': // integer + // get the integer and abs it + if (fl & STBSP__INTMAX) { + stbsp__int64 i64 = va_arg(va, stbsp__int64); + n64 = (stbsp__uint64)i64; + if ((f[0] != 'u') && (i64 < 0)) { + n64 = (stbsp__uint64)-i64; + fl |= STBSP__NEGATIVE; + } + } else { + stbsp__int32 i = va_arg(va, stbsp__int32); + n64 = (stbsp__uint32)i; + if ((f[0] != 'u') && (i < 0)) { + n64 = (stbsp__uint32)-i; + fl |= STBSP__NEGATIVE; + } + } + +#ifndef STB_SPRINTF_NOFLOAT + if (fl & STBSP__METRIC_SUFFIX) { + if (n64 < 1024) + pr = 0; + else if (pr == -1) + pr = 1; + fv = (double)(stbsp__int64)n64; + goto doafloat; + } +#endif + + // convert to string + s = num + STBSP__NUMSZ; + l = 0; + + for (;;) { + // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators) + char *o = s - 8; + if (n64 >= 100000000) { + n = (stbsp__uint32)(n64 % 100000000); + n64 /= 100000000; + } else { + n = (stbsp__uint32)n64; + n64 = 0; + } + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + do { + s -= 2; + *(stbsp__uint16 *)s = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + } while (n); + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } else { + *--s = (char)(n % 10) + '0'; + n /= 10; + } + } + if (n64 == 0) { + if ((s[0] == '0') && (s != (num + STBSP__NUMSZ))) + ++s; + break; + } + while (s != o) + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } else { + *--s = '0'; + } + } + + tail[0] = 0; + stbsp__lead_sign(fl, lead); + + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + if (l == 0) { + *--s = '0'; + l = 1; + } + cs = l + (3 << 24); + if (pr < 0) + pr = 0; + + scopy: + // get fw=leading/trailing space, pr=leading zeros + if (pr < (stbsp__int32)l) + pr = l; + n = pr + lead[0] + tail[0] + tz; + if (fw < (stbsp__int32)n) + fw = n; + fw -= n; + pr -= l; + + // handle right justify and leading zeros + if ((fl & STBSP__LEFTJUST) == 0) { + if (fl & STBSP__LEADINGZERO) // if leading zeros, everything is in pr + { + pr = (fw > pr) ? fw : pr; + fw = 0; + } else { + fl &= ~STBSP__TRIPLET_COMMA; // if no leading zeros, then no commas + } + } + + // copy the spaces and/or zeros + if (fw + pr) { + stbsp__int32 i; + stbsp__uint32 c; + + // copy leading spaces (or when doing %8.4d stuff) + if ((fl & STBSP__LEFTJUST) == 0) + while (fw > 0) { + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = ' '; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leader + sn = lead + 1; + while (lead[0]) { + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leading zeros + c = cs >> 24; + cs &= 0xffffff; + cs = (fl & STBSP__TRIPLET_COMMA) ? ((stbsp__uint32)(c - ((pr + cs) % (c + 1)))) : 0; + while (pr > 0) { + stbsp__cb_buf_clamp(i, pr); + pr -= i; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + } + while (i) { + if ((fl & STBSP__TRIPLET_COMMA) && (cs++ == c)) { + cs = 0; + *bf++ = stbsp__comma; + } else + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + } + + // copy leader if there is still one + sn = lead + 1; + while (lead[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy the string + n = l; + while (n) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, n); + n -= i; + STBSP__UNALIGNED(while (i >= 4) { + *(stbsp__uint32 volatile *)bf = *(stbsp__uint32 volatile *)s; + bf += 4; + s += 4; + i -= 4; + }) + while (i) { + *bf++ = *s++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy trailing zeros + while (tz) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tz); + tz -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy tail if there is one + sn = tail + 1; + while (tail[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tail[0]); + tail[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // handle the left justify + if (fl & STBSP__LEFTJUST) + if (fw > 0) { + while (fw) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i--) + *bf++ = ' '; + stbsp__chk_cb_buf(1); + } + } + break; + + default: // unknown, just copy code + s = num + STBSP__NUMSZ - 1; + *s = f[0]; + l = 1; + fw = fl = 0; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + } + ++f; + } + endfmt: + + if (!callback) + *bf = 0; + else + stbsp__flush_cb(); + + done: + return tlen + (int)(bf - buf); +} + +// cleanup +#undef STBSP__LEFTJUST +#undef STBSP__LEADINGPLUS +#undef STBSP__LEADINGSPACE +#undef STBSP__LEADING_0X +#undef STBSP__LEADINGZERO +#undef STBSP__INTMAX +#undef STBSP__TRIPLET_COMMA +#undef STBSP__NEGATIVE +#undef STBSP__METRIC_SUFFIX +#undef STBSP__NUMSZ +#undef stbsp__chk_cb_bufL +#undef stbsp__chk_cb_buf +#undef stbsp__flush_cb +#undef stbsp__cb_buf_clamp + +// ============================================================================ +// wrapper functions + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) +{ + int result; + va_list va; + va_start(va, fmt); + result = STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); + va_end(va); + return result; +} + +typedef struct stbsp__context { + char *buf; + int count; + int length; + char tmp[STB_SPRINTF_MIN]; +} stbsp__context; + +static char *stbsp__clamp_callback(const char *buf, void *user, int len) +{ + stbsp__context *c = (stbsp__context *)user; + c->length += len; + + if (len > c->count) + len = c->count; + + if (len) { + if (buf != c->buf) { + const char *s, *se; + char *d; + d = c->buf; + s = buf; + se = buf + len; + do { + *d++ = *s++; + } while (s < se); + } + c->buf += len; + c->count -= len; + } + + if (c->count <= 0) + return c->tmp; + return (c->count >= STB_SPRINTF_MIN) ? c->buf : c->tmp; // go direct into buffer if you can +} + +static char * stbsp__count_clamp_callback( const char * buf, void * user, int len ) +{ + stbsp__context * c = (stbsp__context*)user; + (void) sizeof(buf); + + c->length += len; + return c->tmp; // go direct into buffer if you can +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va ) +{ + stbsp__context c; + + if ( (count == 0) && !buf ) + { + c.length = 0; + + STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__count_clamp_callback, &c, c.tmp, fmt, va ); + } + else + { + int l; + + c.buf = buf; + c.count = count; + c.length = 0; + + STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__clamp_callback, &c, stbsp__clamp_callback(0,&c,0), fmt, va ); + + // zero-terminate + l = (int)( c.buf - buf ); + if ( l >= count ) // should never be greater, only equal (or less) than count + l = count - 1; + buf[l] = 0; + } + + return c.length; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) +{ + int result; + va_list va; + va_start(va, fmt); + + result = STB_SPRINTF_DECORATE(vsnprintf)(buf, count, fmt, va); + va_end(va); + + return result; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va) +{ + return STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); +} + +// ======================================================================= +// low level float utility functions + +#ifndef STB_SPRINTF_NOFLOAT + +// copies d to bits w/ strict aliasing (this compiles to nothing on /Ox) +#define STBSP__COPYFP(dest, src) \ +{ \ +int cn; \ +for (cn = 0; cn < 8; cn++) \ +((char *)&dest)[cn] = ((char *)&src)[cn]; \ +} + +// get float info +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value) +{ + double d; + stbsp__int64 b = 0; + + // load value and round at the frac_digits + d = value; + + STBSP__COPYFP(b, d); + + *bits = b & ((((stbsp__uint64)1) << 52) - 1); + *expo = (stbsp__int32)(((b >> 52) & 2047) - 1023); + + return (stbsp__int32)((stbsp__uint64) b >> 63); +} + +static double const stbsp__bot[23] = { + 1e+000, 1e+001, 1e+002, 1e+003, 1e+004, 1e+005, 1e+006, 1e+007, 1e+008, 1e+009, 1e+010, 1e+011, + 1e+012, 1e+013, 1e+014, 1e+015, 1e+016, 1e+017, 1e+018, 1e+019, 1e+020, 1e+021, 1e+022 +}; +static double const stbsp__negbot[22] = { + 1e-001, 1e-002, 1e-003, 1e-004, 1e-005, 1e-006, 1e-007, 1e-008, 1e-009, 1e-010, 1e-011, + 1e-012, 1e-013, 1e-014, 1e-015, 1e-016, 1e-017, 1e-018, 1e-019, 1e-020, 1e-021, 1e-022 +}; +static double const stbsp__negboterr[22] = { + -5.551115123125783e-018, -2.0816681711721684e-019, -2.0816681711721686e-020, -4.7921736023859299e-021, -8.1803053914031305e-022, 4.5251888174113741e-023, + 4.5251888174113739e-024, -2.0922560830128471e-025, -6.2281591457779853e-026, -3.6432197315497743e-027, 6.0503030718060191e-028, 2.0113352370744385e-029, + -3.0373745563400371e-030, 1.1806906454401013e-032, -7.7705399876661076e-032, 2.0902213275965398e-033, -7.1542424054621921e-034, -7.1542424054621926e-035, + 2.4754073164739869e-036, 5.4846728545790429e-037, 9.2462547772103625e-038, -4.8596774326570872e-039 +}; +static double const stbsp__top[13] = { + 1e+023, 1e+046, 1e+069, 1e+092, 1e+115, 1e+138, 1e+161, 1e+184, 1e+207, 1e+230, 1e+253, 1e+276, 1e+299 +}; +static double const stbsp__negtop[13] = { + 1e-023, 1e-046, 1e-069, 1e-092, 1e-115, 1e-138, 1e-161, 1e-184, 1e-207, 1e-230, 1e-253, 1e-276, 1e-299 +}; +static double const stbsp__toperr[13] = { + 8388608, + 6.8601809640529717e+028, + -7.253143638152921e+052, + -4.3377296974619174e+075, + -1.5559416129466825e+098, + -3.2841562489204913e+121, + -3.7745893248228135e+144, + -1.7356668416969134e+167, + -3.8893577551088374e+190, + -9.9566444326005119e+213, + 6.3641293062232429e+236, + -5.2069140800249813e+259, + -5.2504760255204387e+282 +}; +static double const stbsp__negtoperr[13] = { + 3.9565301985100693e-040, -2.299904345391321e-063, 3.6506201437945798e-086, 1.1875228833981544e-109, + -5.0644902316928607e-132, -6.7156837247865426e-155, -2.812077463003139e-178, -5.7778912386589953e-201, + 7.4997100559334532e-224, -4.6439668915134491e-247, -6.3691100762962136e-270, -9.436808465446358e-293, + 8.0970921678014997e-317 +}; + +#if defined(_MSC_VER) && (_MSC_VER <= 1200) +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, + 10000000000000000000U +}; +#define stbsp__tento19th ((stbsp__uint64)1000000000000000000) +#else +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000ULL, + 100000000000ULL, + 1000000000000ULL, + 10000000000000ULL, + 100000000000000ULL, + 1000000000000000ULL, + 10000000000000000ULL, + 100000000000000000ULL, + 1000000000000000000ULL, + 10000000000000000000ULL +}; +#define stbsp__tento19th (1000000000000000000ULL) +#endif + +#define stbsp__ddmulthi(oh, ol, xh, yh) \ +{ \ +double ahi = 0, alo, bhi = 0, blo; \ +stbsp__int64 bt; \ +oh = xh * yh; \ +STBSP__COPYFP(bt, xh); \ +bt &= ((~(stbsp__uint64)0) << 27); \ +STBSP__COPYFP(ahi, bt); \ +alo = xh - ahi; \ +STBSP__COPYFP(bt, yh); \ +bt &= ((~(stbsp__uint64)0) << 27); \ +STBSP__COPYFP(bhi, bt); \ +blo = yh - bhi; \ +ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo; \ +} + +#define stbsp__ddtoS64(ob, xh, xl) \ +{ \ +double ahi = 0, alo, vh, t; \ +ob = (stbsp__int64)xh; \ +vh = (double)ob; \ +ahi = (xh - vh); \ +t = (ahi - xh); \ +alo = (xh - (ahi - t)) - (vh + t); \ +ob += (stbsp__int64)(ahi + alo + xl); \ +} + +#define stbsp__ddrenorm(oh, ol) \ +{ \ +double s; \ +s = oh + ol; \ +ol = ol - (s - oh); \ +oh = s; \ +} + +#define stbsp__ddmultlo(oh, ol, xh, xl, yh, yl) ol = ol + (xh * yl + xl * yh); + +#define stbsp__ddmultlos(oh, ol, xh, yl) ol = ol + (xh * yl); + +static void stbsp__raise_to_power10(double *ohi, double *olo, double d, stbsp__int32 power) // power can be -323 to +350 +{ + double ph, pl; + if ((power >= 0) && (power <= 22)) { + stbsp__ddmulthi(ph, pl, d, stbsp__bot[power]); + } else { + stbsp__int32 e, et, eb; + double p2h, p2l; + + e = power; + if (power < 0) + e = -e; + et = (e * 0x2c9) >> 14; /* %23 */ + if (et > 13) + et = 13; + eb = e - (et * 23); + + ph = d; + pl = 0.0; + if (power < 0) { + if (eb) { + --eb; + stbsp__ddmulthi(ph, pl, d, stbsp__negbot[eb]); + stbsp__ddmultlos(ph, pl, d, stbsp__negboterr[eb]); + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__negtop[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__negtop[et], stbsp__negtoperr[et]); + ph = p2h; + pl = p2l; + } + } else { + if (eb) { + e = eb; + if (eb > 22) + eb = 22; + e -= eb; + stbsp__ddmulthi(ph, pl, d, stbsp__bot[eb]); + if (e) { + stbsp__ddrenorm(ph, pl); + stbsp__ddmulthi(p2h, p2l, ph, stbsp__bot[e]); + stbsp__ddmultlos(p2h, p2l, stbsp__bot[e], pl); + ph = p2h; + pl = p2l; + } + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__top[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__top[et], stbsp__toperr[et]); + ph = p2h; + pl = p2l; + } + } + } + stbsp__ddrenorm(ph, pl); + *ohi = ph; + *olo = pl; +} + +// given a float value, returns the significant bits in bits, and the position of the +// decimal point in decimal_pos. +/-INF and NAN are specified by special values +// returned in the decimal_pos parameter. +// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000 +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits) +{ + double d; + stbsp__int64 bits = 0; + stbsp__int32 expo, e, ng, tens; + + d = value; + STBSP__COPYFP(bits, d); + expo = (stbsp__int32)((bits >> 52) & 2047); + ng = (stbsp__int32)((stbsp__uint64) bits >> 63); + if (ng) + d = -d; + + if (expo == 2047) // is nan or inf? + { + *start = (bits & ((((stbsp__uint64)1) << 52) - 1)) ? "NaN" : "Inf"; + *decimal_pos = STBSP__SPECIAL; + *len = 3; + return ng; + } + + if (expo == 0) // is zero or denormal + { + if (((stbsp__uint64) bits << 1) == 0) // do zero + { + *decimal_pos = 1; + *start = out; + out[0] = '0'; + *len = 1; + return ng; + } + // find the right expo for denormals + { + stbsp__int64 v = ((stbsp__uint64)1) << 51; + while ((bits & v) == 0) { + --expo; + v >>= 1; + } + } + } + + // find the decimal exponent as well as the decimal bits of the value + { + double ph, pl; + + // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046 + tens = expo - 1023; + tens = (tens < 0) ? ((tens * 617) / 2048) : (((tens * 1233) / 4096) + 1); + + // move the significant bits into position and stick them into an int + stbsp__raise_to_power10(&ph, &pl, d, 18 - tens); + + // get full as much precision from double-double as possible + stbsp__ddtoS64(bits, ph, pl); + + // check if we undershot + if (((stbsp__uint64)bits) >= stbsp__tento19th) + ++tens; + } + + // now do the rounding in integer land + frac_digits = (frac_digits & 0x80000000) ? ((frac_digits & 0x7ffffff) + 1) : (tens + frac_digits); + if ((frac_digits < 24)) { + stbsp__uint32 dg = 1; + if ((stbsp__uint64)bits >= stbsp__powten[9]) + dg = 10; + while ((stbsp__uint64)bits >= stbsp__powten[dg]) { + ++dg; + if (dg == 20) + goto noround; + } + if (frac_digits < dg) { + stbsp__uint64 r; + // add 0.5 at the right position and round + e = dg - frac_digits; + if ((stbsp__uint32)e >= 24) + goto noround; + r = stbsp__powten[e]; + bits = bits + (r / 2); + if ((stbsp__uint64)bits >= stbsp__powten[dg]) + ++tens; + bits /= r; + } + noround:; + } + + // kill long trailing runs of zeros + if (bits) { + stbsp__uint32 n; + for (;;) { + if (bits <= 0xffffffff) + break; + if (bits % 1000) + goto donez; + bits /= 1000; + } + n = (stbsp__uint32)bits; + while ((n % 1000) == 0) + n /= 1000; + bits = n; + donez:; + } + + // convert to string + out += 64; + e = 0; + for (;;) { + stbsp__uint32 n; + char *o = out - 8; + // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned) + if (bits >= 100000000) { + n = (stbsp__uint32)(bits % 100000000); + bits /= 100000000; + } else { + n = (stbsp__uint32)bits; + bits = 0; + } + while (n) { + out -= 2; + *(stbsp__uint16 *)out = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + e += 2; + } + if (bits == 0) { + if ((e) && (out[0] == '0')) { + ++out; + --e; + } + break; + } + while (out != o) { + *--out = '0'; + ++e; + } + } + + *decimal_pos = tens; + *start = out; + *len = e; + return ng; +} + +#undef stbsp__ddmulthi +#undef stbsp__ddrenorm +#undef stbsp__ddmultlo +#undef stbsp__ddmultlos +#undef STBSP__SPECIAL +#undef STBSP__COPYFP + +#endif // STB_SPRINTF_NOFLOAT + +// clean up +#undef stbsp__uint16 +#undef stbsp__uint32 +#undef stbsp__int32 +#undef stbsp__uint64 +#undef stbsp__int64 +#undef STBSP__UNALIGNED + +#endif // STB_SPRINTF_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +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. +------------------------------------------------------------------------------ +*/ diff --git a/code/third_party/stb_truetype.h b/code/third_party/stb_truetype.h new file mode 100644 index 0000000..072e024 --- /dev/null +++ b/code/third_party/stb_truetype.h @@ -0,0 +1,5077 @@ +// stb_truetype.h - v1.26 - public domain +// authored from 2009-2021 by Sean Barrett / RAD Game Tools +// +// ======================================================================= +// +// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES +// +// This library does no range checking of the offsets found in the file, +// meaning an attacker can use it to read arbitrary memory. +// +// ======================================================================= +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe +// Cass Everitt Martins Mozeiko github:aloucks +// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam +// Brian Hook Omar Cornut github:vassvik +// Walter van Niftrik Ryan Griege +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. Brian Costabile +// Ken Voskuil (kaesve) +// +// VERSION HISTORY +// +// 1.26 (2021-08-28) fix broken rasterizer +// 1.25 (2021-07-11) many fixes +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char __buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION +// #define your own (u)stbtt_int8/16/32 before including to override this +#ifndef stbtt_uint8 +typedef unsigned char stbtt_uint8; +typedef signed char stbtt_int8; +typedef unsigned short stbtt_uint16; +typedef signed short stbtt_int16; +typedef unsigned int stbtt_uint32; +typedef signed int stbtt_int32; +#endif + +typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; +typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + +// e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h +#ifndef STBTT_ifloor +#include +#define STBTT_ifloor(x) ((int) floor(x)) +#define STBTT_iceil(x) ((int) ceil(x)) +#endif + +#ifndef STBTT_sqrt +#include +#define STBTT_sqrt(x) sqrt(x) +#define STBTT_pow(x,y) pow(x,y) +#endif + +#ifndef STBTT_fmod +#include +#define STBTT_fmod(x,y) fmod(x,y) +#endif + +#ifndef STBTT_cos +#include +#define STBTT_cos(x) cos(x) +#define STBTT_acos(x) acos(x) +#endif + +#ifndef STBTT_fabs +#include +#define STBTT_fabs(x) fabs(x) +#endif + +// #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h +#ifndef STBTT_malloc +#include +#define STBTT_malloc(x,u) ((void)(u),malloc(x)) +#define STBTT_free(x,u) ((void)(u),free(x)) +#endif + +#ifndef STBTT_assert +#include +#define STBTT_assert(x) assert(x) +#endif + +#ifndef STBTT_strlen +#include +#define STBTT_strlen(x) strlen(x) +#endif + +#ifndef STBTT_memcpy +#include +#define STBTT_memcpy memcpy +#define STBTT_memset memset +#endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + // private structure + typedef struct + { + unsigned char *data; + int cursor; + int size; + } stbtt__buf; + + ////////////////////////////////////////////////////////////////////////////// + // + // TEXTURE BAKING API + // + // If you use this API, you only have to call two functions ever. + // + + typedef struct + { + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + } stbtt_bakedchar; + + STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long + // if return is positive, the first unused row of the bitmap + // if return is negative, returns the negative of the number of characters that fit + // if return is 0, no characters fit and no rows were used + // This uses a very crappy packing. + + typedef struct + { + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right + } stbtt_aligned_quad; + + STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier + // Call GetBakedQuad with char_index = 'character - first_char', and it + // creates the quad you need to draw and advances the current position. + // + // The coordinate system used assumes y increases downwards. + // + // Characters will extend both above and below the current position; + // see discussion of "BASELINE" above. + // + // It's inefficient; you might want to c&p it and optimize it. + + STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); + // Query the font vertical metrics without having to create a font first. + + + ////////////////////////////////////////////////////////////////////////////// + // + // NEW TEXTURE BAKING API + // + // This provides options for packing multiple fonts into one atlas, not + // perfectly but better than nothing. + + typedef struct + { + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; + } stbtt_packedchar; + + typedef struct stbtt_pack_context stbtt_pack_context; + typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION + typedef struct stbrp_rect stbrp_rect; +#endif + + STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); + // Initializes a packing context stored in the passed-in stbtt_pack_context. + // Future calls using this context will pack characters into the bitmap passed + // in here: a 1-channel bitmap that is width * height. stride_in_bytes is + // the distance from one row to the next (or 0 to mean they are packed tightly + // together). "padding" is the amount of padding to leave between each + // character (normally you want '1' for bitmaps you'll use as textures with + // bilinear filtering). + // + // Returns 0 on failure, 1 on success. + + STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); + // Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + + STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); + // Creates character bitmaps from the font_index'th font found in fontdata (use + // font_index=0 if you don't know what that is). It creates num_chars_in_range + // bitmaps for characters with unicode values starting at first_unicode_char_in_range + // and increasing. Data for how to render them is stored in chardata_for_range; + // pass these to stbtt_GetPackedQuad to get back renderable quads. + // + // font_size is the full height of the character from ascender to descender, + // as computed by stbtt_ScaleForPixelHeight. To use a point size as computed + // by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() + // and pass that result as 'font_size': + // ..., 20 , ... // font max minus min y is 20 pixels tall + // ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + + typedef struct + { + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally + } stbtt_pack_range; + + STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); + // Creates character bitmaps from multiple ranges of characters stored in + // ranges. This will usually create a better-packed bitmap than multiple + // calls to stbtt_PackFontRange. Note that you can call this multiple + // times within a single PackBegin/PackEnd. + + STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); + // Oversampling a font increases the quality by allowing higher-quality subpixel + // positioning, and is especially valuable at smaller text sizes. + // + // This function sets the amount of oversampling for all following calls to + // stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given + // pack context. The default (no oversampling) is achieved by h_oversample=1 + // and v_oversample=1. The total number of pixels required is + // h_oversample*v_oversample larger than the default; for example, 2x2 + // oversampling requires 4x the storage of 1x1. For best results, render + // oversampled textures with bilinear filtering. Look at the readme in + // stb/tests/oversample for information about oversampled fonts + // + // To use with PackFontRangesGather etc., you must set it before calls + // call to PackFontRangesGatherRects. + + STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); + // If skip != 0, this tells stb_truetype to skip any codepoints for which + // there is no corresponding glyph. If skip=0, which is the default, then + // codepoints without a glyph recived the font's "missing character" glyph, + // typically an empty box by convention. + + STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + + STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); + STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); + STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); + // Calling these functions in sequence is roughly equivalent to calling + // stbtt_PackFontRanges(). If you more control over the packing of multiple + // fonts, or if you want to pack custom data into a font texture, take a look + // at the source to of stbtt_PackFontRanges() and create a custom version + // using these functions, e.g. call GatherRects multiple times, + // building up a single array of rects, then call PackRects once, + // then call RenderIntoRects repeatedly. This may result in a + // better packing than calling PackFontRanges multiple times + // (or it may not). + + // this is an opaque structure that you shouldn't mess with which holds + // all the context needed from PackBegin to PackEnd. + struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; + }; + + ////////////////////////////////////////////////////////////////////////////// + // + // FONT LOADING + // + // + + STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); + // This function will determine the number of fonts in a font file. TrueType + // collection (.ttc) files may contain multiple fonts, while TrueType font + // (.ttf) files only contain one font. The number of fonts can be used for + // indexing with the previous function where the index is between zero and one + // less than the total fonts. If an error occurs, -1 is returned. + + STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); + // Each .ttf/.ttc file may have more than one font. Each font has a sequential + // index number starting from 0. Call this function to get the font offset for + // a given index; it returns -1 if the index is out of range. A regular .ttf + // file will only define one font and it always be at offset 0, so it will + // return '0' for index 0, and -1 for all other indices. + + // The following structure is defined publicly so you can declare one on + // the stack or as a global or etc, but you should treat it as opaque. + struct stbtt_fontinfo + { + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict + }; + + STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); + // Given an offset into the file that defines a font, this function builds + // the necessary cached info for the rest of the system. You must allocate + // the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't + // need to do anything special to free it, because the contents are pure + // value data with no additional data structures. Returns 0 on failure. + + + ////////////////////////////////////////////////////////////////////////////// + // + // CHARACTER TO GLYPH-INDEX CONVERSIOn + + STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); + // If you're going to perform multiple operations on the same character + // and you want a speed-up, call this function with the character you're + // going to process, then use glyph-based functions instead of the + // codepoint-based functions. + // Returns 0 if the character codepoint is not defined in the font. + + + ////////////////////////////////////////////////////////////////////////////// + // + // CHARACTER PROPERTIES + // + + STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); + // computes a scale factor to produce a font whose "height" is 'pixels' tall. + // Height is measured as the distance from the highest ascender to the lowest + // descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics + // and computing: + // scale = pixels / (ascent - descent) + // so if you prefer to measure height by the ascent only, use a similar calculation. + + STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); + // computes a scale factor to produce a font whose EM size is mapped to + // 'pixels' tall. This is probably what traditional APIs compute, but + // I'm not positive. + + STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); + // ascent is the coordinate above the baseline the font extends; descent + // is the coordinate below the baseline the font extends (i.e. it is typically negative) + // lineGap is the spacing between one row's descent and the next row's ascent... + // so you should advance the vertical position by "*ascent - *descent + *lineGap" + // these are expressed in unscaled coordinates, so you must multiply by + // the scale factor for a given size + + STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); + // analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 + // table (specific to MS/Windows TTF files). + // + // Returns 1 on success (table present), 0 on failure. + + STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); + // the bounding box around all possible characters + + STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); + // leftSideBearing is the offset from the current horizontal position to the left edge of the character + // advanceWidth is the offset from the current horizontal position to the next horizontal position + // these are expressed in unscaled coordinates + + STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); + // an additional amount to add to the 'advance' value between ch1 and ch2 + + STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); + // Gets the bounding box of the visible part of the glyph, in unscaled coordinates + + STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); + STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); + STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + // as above, but takes one or more glyph indices for greater efficiency + + typedef struct stbtt_kerningentry + { + int glyph1; // use stbtt_FindGlyphIndex + int glyph2; + int advance; + } stbtt_kerningentry; + + STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); + STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); + // Retrieves a complete list of all of the kerning pairs provided by the font + // stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. + // The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) + + ////////////////////////////////////////////////////////////////////////////// + // + // GLYPH SHAPES (you probably don't need these, but they have to go before + // the bitmaps for C declaration-order reasons) + // + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) +#define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + + STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); + // returns non-zero if nothing is drawn for this glyph + + STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); + STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); + // returns # of vertices and fills *vertices with the pointer to them + // these are expressed in "unscaled" coordinates + // + // The shape is a series of contours. Each one starts with + // a STBTT_moveto, then consists of a series of mixed + // STBTT_lineto and STBTT_curveto segments. A lineto + // draws a line from previous endpoint to its x,y; a curveto + // draws a quadratic bezier from previous endpoint to + // its x,y, using cx,cy as the bezier control point. + + STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); + // frees the data allocated above + + STBTT_DEF unsigned char *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl); + STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); + STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); + // fills svg with the character's SVG data. + // returns data size or 0 if SVG not found. + + ////////////////////////////////////////////////////////////////////////////// + // + // BITMAP RENDERING + // + + STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); + // frees the bitmap allocated below + + STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); + // allocates a large-enough single-channel 8bpp bitmap and renders the + // specified character/glyph at the specified scale into it, with + // antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). + // *width & *height are filled out with the width & height of the bitmap, + // which is stored left-to-right, top-to-bottom. + // + // xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + + STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); + // the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel + // shift for the character + + STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); + // the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap + // in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap + // is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the + // width and height and positioning info for it first. + + STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); + // same as stbtt_MakeCodepointBitmap, but you can specify a subpixel + // shift for the character + + STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); + // same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering + // is performed (see stbtt_PackSetOversampling) + + STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); + // get the bbox of the bitmap centered around the glyph origin; so the + // bitmap width is ix1-ix0, height is iy1-iy0, and location to place + // the bitmap top left is (leftSideBearing*scale,iy0). + // (Note that the bitmap uses y-increases-down, but the shape uses + // y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + + STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + // same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel + // shift for the character + + // the following functions are equivalent to the above functions, but operate + // on glyph indices instead of Unicode codepoints (for efficiency) + STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); + STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); + STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); + STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); + STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); + STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); + STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + + // @TODO: don't expose this structure + typedef struct + { + int w,h,stride; + unsigned char *pixels; + } stbtt__bitmap; + + // rasterize a shape with quadratic beziers into a bitmap + STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + + ////////////////////////////////////////////////////////////////////////////// + // + // Signed Distance Function (or Field) rendering + + STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); + // frees the SDF bitmap allocated below + + STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); + STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); + // These functions compute a discretized SDF field for a single character, suitable for storing + // in a single-channel texture, sampling with bilinear filtering, and testing against + // larger than some threshold to produce scalable fonts. + // info -- the font + // scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap + // glyph/codepoint -- the character to generate the SDF for + // padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), + // which allows effects like bit outlines + // onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) + // pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) + // if positive, > onedge_value is inside; if negative, < onedge_value is inside + // width,height -- output height & width of the SDF bitmap (including padding) + // xoff,yoff -- output origin of the character + // return value -- a 2D array of bytes 0..255, width*height in size + // + // pixel_dist_scale & onedge_value are a scale & bias that allows you to make + // optimal use of the limited 0..255 for your application, trading off precision + // and special effects. SDF values outside the range 0..255 are clamped to 0..255. + // + // Example: + // scale = stbtt_ScaleForPixelHeight(22) + // padding = 5 + // onedge_value = 180 + // pixel_dist_scale = 180/5.0 = 36.0 + // + // This will create an SDF bitmap in which the character is about 22 pixels + // high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled + // shape, sample the SDF at each pixel and fill the pixel if the SDF value + // is greater than or equal to 180/255. (You'll actually want to antialias, + // which is beyond the scope of this example.) Additionally, you can compute + // offset outlines (e.g. to stroke the character border inside & outside, + // or only outside). For example, to fill outside the character up to 3 SDF + // pixels, you would compare against (180-36.0*3)/255 = 72/255. The above + // choice of variables maps a range from 5 pixels outside the shape to + // 2 pixels inside the shape to 0..255; this is intended primarily for apply + // outside effects only (the interior range is needed to allow proper + // antialiasing of the font at *smaller* sizes) + // + // The function computes the SDF analytically at each SDF pixel, not by e.g. + // building a higher-res bitmap and approximating it. In theory the quality + // should be as high as possible for an SDF of this size & representation, but + // unclear if this is true in practice (perhaps building a higher-res bitmap + // and computing from that can allow drop-out prevention). + // + // The algorithm has not been optimized at all, so expect it to be slow + // if computing lots of characters or very large sizes. + + + + ////////////////////////////////////////////////////////////////////////////// + // + // Finding the right font... + // + // You should really just solve this offline, keep your own tables + // of what font is what, and don't try to get it out of the .ttf file. + // That's because getting it out of the .ttf file is really hard, because + // the names in the file can appear in many possible encodings, in many + // possible languages, and e.g. if you need a case-insensitive comparison, + // the details of that depend on the encoding & language in a complex way + // (actually underspecified in truetype, but also gigantic). + // + // But you can use the provided functions in two possible ways: + // stbtt_FindMatchingFont() will use *case-sensitive* comparisons on + // unicode-encoded names to try to find the font you want; + // you can run this before calling stbtt_InitFont() + // + // stbtt_GetFontNameString() lets you get any of the various strings + // from the file yourself and do your own comparisons on them. + // You have to have called stbtt_InitFont() first. + + + STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); + // returns the offset (not index) of the font that matches, or -1 if none + // if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". + // if you use any other flag, use a font name like "Arial"; this checks + // the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + + STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); + // returns 1/0 whether the first string interpreted as utf8 is identical to + // the second string interpreted as big-endian utf16... useful for strings from next func + + STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); + // returns the string (which may be big-endian double byte, e.g. for unicode) + // and puts the length in bytes in *length. + // + // some of the values for the IDs are below; for more see the truetype spec: + // http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html + // http://www.microsoft.com/typography/otspec/name.htm + + enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 + }; + + enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 + }; + + enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 + }; + + enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 + }; + + enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D + }; + + enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 + }; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +// since most people won't use this, find this table the first time it's needed +static int stbtt__get_svg(stbtt_fontinfo *info) +{ + stbtt_uint32 t; + if (info->svg < 0) { + t = stbtt__find_table(info->data, info->fontstart, "SVG "); + if (t) { + stbtt_uint32 offset = ttULONG(info->data + t + 2); + info->svg = t + offset; + } else { + info->svg = 0; + } + } + return info->svg; +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + info->svg = -1; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start, last; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + last = ttUSHORT(data + endCount + 2*item); + if (unicode_codepoint < start || unicode_codepoint > last) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours < 0) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0 && vertices) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // FALLTHROUGH + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && b0 < 32) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) +{ + stbtt_uint8 *data = info->data + info->kern; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + return ttUSHORT(data+10); +} + +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) +{ + stbtt_uint8 *data = info->data + info->kern; + int k, length; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + length = ttUSHORT(data+10); + if (table_length < length) + length = table_length; + + for (k = 0; k < length; k++) + { + table[k].glyph1 = ttUSHORT(data+18+(k*6)); + table[k].glyph2 = ttUSHORT(data+20+(k*6)); + table[k].advance = ttSHORT(data+22+(k*6)); + } + + return length; +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch (coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + break; + } + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + break; + } + + default: return -1; // unsupported + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch (classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + break; + } + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + break; + } + + default: + return -1; // Unsupported definition type, return an error. + } + + // "All glyphs not assigned to a class fall into class 0". (OpenType spec) + return 0; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i, sti; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i= pairSetCount) return 0; + + needle=glyph2; + r=pairValueCount-1; + l=0; + + // Binary search. + while (l <= r) { + stbtt_uint16 secondGlyph; + stbtt_uint8 *pairValue; + m = (l + r) >> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } else + return 0; + break; + } + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats? + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + stbtt_uint8 *class1Records, *class2Records; + stbtt_int16 xAdvance; + + if (glyph1class < 0 || glyph1class >= class1Count) return 0; // malformed + if (glyph2class < 0 || glyph2class >= class2Count) return 0; // malformed + + class1Records = table + 16; + class2Records = class1Records + 2 * (glyph1class * class2Count); + xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } else + return 0; + break; + } + + default: + return 0; // Unsupported position format + } + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) +{ + int i; + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); + + int numEntries = ttUSHORT(svg_doc_list); + stbtt_uint8 *svg_docs = svg_doc_list + 2; + + for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) + return svg_doc; + } + return 0; +} + +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) +{ + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc; + + if (info->svg == 0) + return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if (svg_doc != NULL) { + *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); + return ttULONG(svg_doc + 8); + } else { + return 0; + } +} + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) +{ + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; +#if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; +#elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static float stbtt__sized_trapezoid_area(float height, float top_width, float bottom_width) +{ + STBTT_assert(top_width >= 0); + STBTT_assert(bottom_width >= 0); + return (top_width + bottom_width) / 2.0f * height; +} + +static float stbtt__position_trapezoid_area(float height, float tx0, float tx1, float bx0, float bx1) +{ + return stbtt__sized_trapezoid_area(height, tx1 - tx0, bx1 - bx0); +} + +static float stbtt__sized_triangle_area(float height, float width) +{ + return height * width / 2; +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = (sy1 - sy0) * e->direction; + STBTT_assert(x >= 0 && x < len); + scanline[x] += stbtt__position_trapezoid_area(height, x_top, x+1.0f, x_bottom, x+1.0f); + scanline_fill[x] += height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, y_final, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + STBTT_assert(dy >= 0); + STBTT_assert(dx >= 0); + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = y_top + dy * (x1+1 - x0); + + // compute intersection with y axis at x2 + y_final = y_top + dy * (x2 - x0); + + // x1 x_top x2 x_bottom + // y_top +------|-----+------------+------------+--------|---+------------+ + // | | | | | | + // | | | | | | + // sy0 | Txxxxx|............|............|............|............| + // y_crossing | *xxxxx.......|............|............|............| + // | | xxxxx..|............|............|............| + // | | /- xx*xxxx........|............|............| + // | | dy < | xxxxxx..|............|............| + // y_final | | \- | xx*xxx.........|............| + // sy1 | | | | xxxxxB...|............| + // | | | | | | + // | | | | | | + // y_bottom +------------+------------+------------+------------+------------+ + // + // goal is to measure the area covered by '.' in each pixel + + // if x2 is right at the right edge of x1, y_crossing can blow up, github #1057 + // @TODO: maybe test against sy1 rather than y_bottom? + if (y_crossing > y_bottom) + y_crossing = y_bottom; + + sign = e->direction; + + // area of the rectangle covered from sy0..y_crossing + area = sign * (y_crossing-sy0); + + // area of the triangle (x_top,sy0), (x1+1,sy0), (x1+1,y_crossing) + scanline[x1] += stbtt__sized_triangle_area(area, x1+1 - x_top); + + // check if final y_crossing is blown up; no test case for this + if (y_final > y_bottom) { + y_final = y_bottom; + dy = (y_final - y_crossing ) / (x2 - (x1+1)); // if denom=0, y_final = y_crossing, so y_final <= y_bottom + } + + // in second pixel, area covered by line segment found in first pixel + // is always a rectangle 1 wide * the height of that line segment; this + // is exactly what the variable 'area' stores. it also gets a contribution + // from the line segment within it. the THIRD pixel will get the first + // pixel's rectangle contribution, the second pixel's rectangle contribution, + // and its own contribution. the 'own contribution' is the same in every pixel except + // the leftmost and rightmost, a trapezoid that slides down in each pixel. + // the second pixel's contribution to the third pixel will be the + // rectangle 1 wide times the height change in the second pixel, which is dy. + + step = sign * dy * 1; // dy is dy/dx, change in y for every 1 change in x, + // which multiplied by 1-pixel-width is how much pixel area changes for each step in x + // so the area advances by 'step' every time + + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; // area of trapezoid is 1*step/2 + area += step; + } + STBTT_assert(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down + STBTT_assert(sy1 > y_final-0.01f); + + // area covered in the last pixel is the rectangle from all the pixels to the left, + // plus the trapezoid filled by the line segment in this pixel all the way to the right edge + scanline[x2] += area + sign * stbtt__position_trapezoid_area(sy1-y_final, (float) x2, x2+1.0f, x_bottom, x2+1.0f); + + // the rest of the line is filled based on the total height of the line segment in this pixel + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + // note though that this does happen some of the time because + // x_top and x_bottom can be extrapolated at the top & bottom of + // the shape and actually lie outside the bounding box + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; + error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + int missing_glyph_added = 0; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, missing_glyph = -1, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + + orig[0] = x; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + a*x^2 + b*x + c = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + if (scale == 0) return NULL; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + if (verts[i].type == STBTT_vline && precompute[i] != 0.0f) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + float dist,dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3] = {0.f,0.f,0.f}; + float px,py,t,it,dist2; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.25 (2021-07-11) many fixes +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +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. +------------------------------------------------------------------------------ +*/ diff --git a/code/vn.cpp b/code/vn.cpp new file mode 100644 index 0000000..c7d0c72 --- /dev/null +++ b/code/vn.cpp @@ -0,0 +1,62 @@ +#include "vn_platform.h" +#include "vn_platform.cpp" + +#include "vn_font.h" +#include "vn_text_op.h" +#include "vn_ui.h" +#include "vn_workspace.h" +#include "vn_theme_dark.h" +#include "vn_animation_curve.h" + +#include "vn_render.cpp" +#include "vn_font.cpp" +#include "vn_ui.cpp" +#include "vn_ui_utils.cpp" +#include "vn_animation_curve.cpp" +#include "vn_workspace.cpp" + +struct vn_state +{ + memory_arena Arena; + glyph_atlas *GlyphAtlas; + + ui UI; + workspace Workspace; + animation_curve_state AnimationCurveState; +}; + +VN_UPDATE_AND_RENDER(VN_UpdateAndRender) +{ + SetThreadContext(ThreadContext); + Platform = Memory->PlatformAPI; + + vn_state *State = Memory->State; + + if(!Memory->State) + { + State = Memory->State = BootstrapPushStruct(vn_state, Arena); + + State->GlyphAtlas = CreateGlyphAtlas(RenderCommands); + + Workspace_Init(&State->Workspace); + } + + AnimationCurve_NewFrame(&State->AnimationCurveState, Input->dtForFrame); + UI_NewFrame(&State->UI, Input->EventList, Input->MouseP); + + Workspace_Update(&State->Workspace, RenderCommands, Input, State->GlyphAtlas); + // sixten(NOTE): This allows us to check for + UI_SignalFromBox(UI_GetState()->ContainerNode); + + for(platform_event *Event = Input->EventList->First; + Event != 0; + Event = Event->Next) + { + Platform_ConsumeEvent(Input->EventList, Event); + } + + render_group Group = BeginRenderGroup(RenderCommands); + PushClear(&Group, V3(0.1, 0.1, 0.1)); + + UI_RenderFrame(&Group, State->GlyphAtlas); +} \ No newline at end of file diff --git a/code/vn_animation_curve.cpp b/code/vn_animation_curve.cpp new file mode 100644 index 0000000..883779f --- /dev/null +++ b/code/vn_animation_curve.cpp @@ -0,0 +1,150 @@ +global animation_curve_state *Global_AnimationCurveState = 0; + +inline animation_curve_state *AnimationCurve_GetState(void) +{ + return(Global_AnimationCurveState); +} + +inline void AnimationCurve_SetState(animation_curve_state *State) +{ + Global_AnimationCurveState = State; +} + +inline animation_curve_key AnimationCurve_GenerateKeyFromString(string String) +{ + animation_curve_key Key; + Key.Value = HashString(String); + + return(Key); +} + +static animation_curve_entry *AnimationCurve_GetEntryByKey(animation_curve_key Key, r32 Initial = 0) +{ + animation_curve_state *State = AnimationCurve_GetState(); + + u64 Hash = Key.Value; + u64 Slot = Hash % ArrayCount(State->Buckets); + + animation_curve_bucket *Bucket = State->Buckets + Slot; + + animation_curve_entry *Result = 0; + for(animation_curve_entry *Entry = Bucket->First; + Entry != 0; + Entry = Entry->Next) + { + if(AreEqual(Entry->Key, Key)) + { + Result = Entry; + break; + } + } + + if(!Result) + { + if(DLLIsEmpty(State->FirstFreeEntry)) + { + Result = PushStruct(&State->Arena, animation_curve_entry); + } + else + { + Result = State->FirstFreeEntry; + DLLRemove(State->FirstFreeEntry, State->LastFreeEntry, Result); + } + + DLLInsertLast(Bucket->First, Bucket->Last, Result); + + Result->Value = Initial; + } + + Result->Key = Key; + Result->LastFrameTouched = State->CurrentFrame; + + return(Result); +} + +inline r32 AnimationCurve_GetValue(string Name, r32 Initial) +{ + animation_curve_key Key = AnimationCurve_GenerateKeyFromString(Name); + animation_curve_entry *Entry = AnimationCurve_GetEntryByKey(Key, Initial); + + r32 Result = Entry->Value; + return(Result); +} + +inline void AnimationCurve_SetValue(string Name, r32 Value) +{ + animation_curve_key Key = AnimationCurve_GenerateKeyFromString(Name); + animation_curve_entry *Entry = AnimationCurve_GetEntryByKey(Key, Value); + + Entry->Value = Value; +} + +inline r32 AnimationCurve_AnimateValueDirect(r32 Target, r32 Duration, r32 *Value) +{ + animation_curve_state *State = AnimationCurve_GetState(); + + r32 Result = *Value; + + r32 Rate = 1.0 - Pow(2, -(10.0 / Duration * State->dtForFrame)); + *Value += (Target - *Value) * Rate; + + return(Result); +} + +inline r32 AnimationCurve_AnimateValue(r32 Target, r32 Initial, r32 Duration, string Name) +{ + animation_curve_key Key = AnimationCurve_GenerateKeyFromString(Name); + animation_curve_entry *Entry = AnimationCurve_GetEntryByKey(Key, Initial); + + r32 Result = AnimationCurve_AnimateValueDirect(Target, Duration, &Entry->Value); + return(Result); +} + +inline r32 AnimationCurve_AnimateValueF(r32 Target, r32 Initial, r32 Duration, char *Format, ...) +{ + temporary_memory Scratch = GetScratch(0, 0); + + va_list Arguments; + va_start(Arguments, Format); + string String = PushFormatVariadic(Scratch.Arena, Format, Arguments); + va_end(Arguments); + + r32 Result = AnimationCurve_AnimateValue(Target, Initial, Duration, String); + + ReleaseScratch(Scratch); + + return(Result); +} + +static void AnimationCurve_NewFrame(animation_curve_state *State, r32 dtForFrame) +{ + AnimationCurve_SetState(State); + State->dtForFrame = dtForFrame; + + // sixten: Prune untouched entries. + for(s32 BucketIndex = 0; + BucketIndex < ArrayCount(State->Buckets); + ++BucketIndex) + { + animation_curve_bucket *Bucket = State->Buckets + BucketIndex; + + animation_curve_entry *Entry = Bucket->First; + while(Entry != 0) + { + if(Entry->LastFrameTouched != State->CurrentFrame) + { + animation_curve_entry *ToRemove = Entry; + Entry = Entry->Next; + + DLLRemove(Bucket->First, Bucket->Last, ToRemove); + DLLInsertLast(State->FirstFreeEntry, State->LastFreeEntry, ToRemove); + } + else + { + Entry = Entry->Next; + } + } + } + + ++State->CurrentFrame; +} \ No newline at end of file diff --git a/code/vn_animation_curve.h b/code/vn_animation_curve.h new file mode 100644 index 0000000..f3031e6 --- /dev/null +++ b/code/vn_animation_curve.h @@ -0,0 +1,49 @@ +/* date = May 7th 2023 0:37 pm */ + +#ifndef VN_ANIMATION_CURVE_H +#define VN_ANIMATION_CURVE_H + +struct animation_curve_key +{ + u64 Value; +}; + +inline b32 AreEqual(animation_curve_key A, animation_curve_key B) +{ + b32 Result = (A.Value == B.Value); + return(Result); +} + +struct animation_curve_entry +{ + animation_curve_key Key; + u32 LastFrameTouched; + + r32 Value; + + animation_curve_entry *Next; + animation_curve_entry *Prev; +}; + +struct animation_curve_bucket +{ + animation_curve_entry *First; + animation_curve_entry *Last; +}; + +struct animation_curve_state +{ + memory_arena Arena; + + u32 CurrentFrame; + r32 dtForFrame; + + // sixten: Hash map + animation_curve_bucket Buckets[256]; + + // sixten: Free list + animation_curve_entry *FirstFreeEntry; + animation_curve_entry *LastFreeEntry; +}; + +#endif //VN_ANIMATION_CURVE_H diff --git a/code/vn_core.h b/code/vn_core.h new file mode 100644 index 0000000..d4a4544 --- /dev/null +++ b/code/vn_core.h @@ -0,0 +1,162 @@ +/* date = April 26th 2023 8:58 pm */ + +#ifndef VN_CORE_H +#define VN_CORE_H + +#define global static +#define persist static + +#pragma section(".roglob", read) +#define read_only __declspec(allocate(".roglob")) + +#define fallthrough + +#if VN_SLOW +#define Assert(Statement) if(!(Statement)) { *(int *)0 = 0; } +#else +#define Assert(Statement) +#endif + +#define CTAssert(Statement) static_assert(Statement) + +#define InvokeDebugger __debugbreak() +#define UnimplementedCodepath Assert(!"Unimplemented codepath") + +#define InvalidCodepath Assert(!"Invalid codepath") +#define InvalidDefaultCase default: { Assert(!"Invalid codepath"); } break + +#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0])) +#define OffsetOf(type, Member) (umm) &(((type *)0)->Member) + +#define Minimum(A, B) (((A)<(B))?(A):(B)) +#define Maximum(A, B) (((A)>(B))?(A):(B)) + +#define Clamp(Value, Min, Max) Minimum(Maximum(Value, Min), Max) +#define Clamp01(Value) Clamp(Value, 0, 1) + +#define DeferLoop(Start, End) for(s32 ___ = ((Start), 0); ___ == 0; ++___, (End)) + +#define _Stringify(x) #x +#define Stringify(x) _Stringify(x) + +#define IsNull(x) ((x) == 0) +#define SetNull(x) ((x) = 0) + +#define DLLInsert_NP(f,l,p,n,next,prev) \ +(IsNull(f) ? (((f) = (l) = (n)), SetNull((n)->next), SetNull((n)->prev)) :\ +IsNull(p) ? (SetNull((n)->prev), (n)->next = (f), (IsNull(f) ? (0) : ((f)->prev = (n))), (f) = (n)) :\ +((IsNull((p)->next) ? (0) : (((p)->next->prev) = (n))), (n)->next = (p)->next, (n)->prev = (p), (p)->next = (n),\ +((p) == (l) ? (l) = (n) : (0)))) + +#define DLLInsertLast_NP(f,l,n,next,prev) DLLInsert_NP(f,l,l,n,next,prev) +#define DLLInsertFirst_NP(f,l,n,next,prev) DLLInsert_NP(l,f,f,n,prev,next) + +#define DLLRemove_NP(f,l,n,next,prev)\ +(((f)==(n))?\ +((f)=(f)->next, (IsNull(f) ? (SetNull(l)) : SetNull((f)->prev))):\ +((l)==(n))?\ +((l)=(l)->prev, (IsNull(l) ? (SetNull(f)) : SetNull((l)->next))):\ +((IsNull((n)->next) ? (0) : ((n)->next->prev=(n)->prev)),\ +(IsNull((n)->prev) ? (0) : ((n)->prev->next=(n)->next)))) + +#define DLLInsertFirst(First, Last, Element) DLLInsertFirst_NP(First, Last, Element, Next, Prev) +#define DLLInsertLast(First, Last, Element) DLLInsertLast_NP(First, Last, Element, Next, Prev) +#define DLLRemove(First, Last, Element) DLLRemove_NP(First, Last, Element, Next, Prev) +#define DLLIsEmpty(First) ((First) == 0) + +#include "vn_types.h" +#include "vn_math.h" +#include "vn_string.h" + +#define STB_SPRINTF_IMPLEMENTATION +#include "third_party/stb_sprintf.h" + +inline void Copy(void *Dest_, void *Source_, umm Count) +{ + u8 *Dest = (u8 *)Dest_; + u8 *Source = (u8 *)Source_; + + while(Count--) + { + *Dest++ = *Source++; + } +} + +inline void ZeroSize(void *Dest_, umm Count) +{ + u8 *Dest = (u8 *)Dest_; + while(Count--) + { + *Dest++ = 0; + } +} + +inline void *U64ToPointer(u64 Value) +{ + void *Result = (void *)Value; + return(Result); +} + +inline u64 PointerToU64(void *Value) +{ + u64 Result = (u64)Value; + return(Result); +} + +inline u64 AtomicExchangeU64(u64 volatile *Value, u64 New) +{ + u64 Result = _InterlockedExchange64((__int64 volatile *)Value, New); + return(Result); +} + +inline u64 AtomicAddU64(u64 volatile *Value, u64 Addend) +{ + u64 Result = _InterlockedExchangeAdd64((__int64 volatile *)Value, Addend); + return(Result); +} + +inline void BeginTicketMutex(ticket_mutex *Mutex) +{ + u64 Ticket = AtomicAddU64(&Mutex->Ticket, 1); + while(Ticket != Mutex->Serving) { _mm_pause(); } +} + +inline void EndTicketMutex(ticket_mutex *Mutex) +{ + AtomicAddU64(&Mutex->Serving, 1); +} + +inline b32 InRange(range2_r32 Range, v2 P) +{ + b32 Result = ((P.x >= Range.Min.x) && + (P.y >= Range.Min.y) && + (P.x < Range.Max.x) && + (P.y < Range.Max.y)); + return(Result); +} + +inline range_s64 RangeS64(s64 A, s64 B) +{ + range_s64 Result = {Minimum(A, B), Maximum(A, B)}; + return(Result); +} + +inline range_r32 RangeR32(r32 A, r32 B) +{ + range_r32 Result = {Minimum(A, B), Maximum(A, B)}; + return(Result); +} + +inline range2_r32 Range2R32(v2 A, v2 B) +{ + range2_r32 Result = { Min(A, B), Max(A, B) }; + return(Result); +} + +inline v2 DimOfRange(range2_r32 Range) +{ + v2 Dim = Range.Max - Range.Min; + return(Dim); +} + +#endif //VN_CORE_H diff --git a/code/vn_font.cpp b/code/vn_font.cpp new file mode 100644 index 0000000..c4be53b --- /dev/null +++ b/code/vn_font.cpp @@ -0,0 +1,224 @@ +#define GLYPH_SUBPIXEL_SEGMENTS 3 + +inline s32 GetSubpixelSegmentAtP(r32 Value) +{ + s32 Result = (s32)(Value - Floor(Value))*GLYPH_SUBPIXEL_SEGMENTS; + return(Result); +} + +static void RasterizeGlyph(glyph_atlas *Atlas, font_id Font, glyph *Glyph, u32 Codepoint, r32 Size, s32 Subpixel) +{ + Glyph->Font = Font; + Glyph->Codepoint = Codepoint; + Glyph->Size = Size; + Glyph->Subpixel = Subpixel; + + Assert(Subpixel < GLYPH_SUBPIXEL_SEGMENTS); + + loaded_font *LoadedFont = Atlas->Fonts + Font; + stbtt_fontinfo *Info = &LoadedFont->Info; + + r32 Scale = stbtt_ScaleForMappingEmToPixels(Info, Size); + + s32 InternalIndex = (s32)(Glyph - Atlas->Glyphs); + s32 GlyphsPerRow = Atlas->BitmapSize / Atlas->GlyphSize; + + v2s BaseTextureOffset = V2S((InternalIndex % GlyphsPerRow)*Atlas->GlyphSize, + (InternalIndex / GlyphsPerRow)*Atlas->GlyphSize); + + int GlyphIndex = stbtt_FindGlyphIndex(Info, Codepoint); + + stbtt_GetGlyphBitmapBoxSubpixel(Info, GlyphIndex, Scale, Scale, + (r32)Subpixel/GLYPH_SUBPIXEL_SEGMENTS, 0, + &Glyph->P0.x, &Glyph->P0.y, &Glyph->P1.x, &Glyph->P1.y); + + ZeroSize(Atlas->BitmapBuffer, Atlas->GlyphSize*Atlas->GlyphSize); + stbtt_MakeGlyphBitmapSubpixel(Info, Atlas->BitmapBuffer, + Atlas->GlyphSize, Atlas->GlyphSize, Atlas->GlyphSize, + Scale, Scale, + (r32)Subpixel/GLYPH_SUBPIXEL_SEGMENTS, 0, + GlyphIndex); + + s32 Advance, LeftSideBearing; + stbtt_GetGlyphHMetrics(Info, GlyphIndex, &Advance, &LeftSideBearing); + Glyph->Advance = Advance*Scale; + Glyph->Offset.x = LeftSideBearing*Scale; + + Glyph->Offset.y = Glyph->P0.y + (LoadedFont->Ascent + LoadedFont->LineGap)*Scale; + + v2s Dim = Glyph->P1 - Glyph->P0; + + Glyph->P0 = BaseTextureOffset; + Glyph->P1 = BaseTextureOffset + Dim + V2S(2, 2); + + Atlas->RenderCommands->FillRegion(Atlas->Texture, + BaseTextureOffset, V2S(Atlas->GlyphSize, Atlas->GlyphSize), + Atlas->BitmapBuffer); +} + +static glyph *GetGlyph(glyph_atlas *Atlas, font_id Font, u32 Codepoint, r32 Size, s32 Subpixel) +{ + glyph *Glyph = 0; + + for(s32 GlyphIndex = 0; + GlyphIndex < Atlas->GlyphsUsed; + ++GlyphIndex) + { + glyph *At = Atlas->Glyphs + GlyphIndex; + if((At->Font == Font) && (At->Codepoint == Codepoint) && (At->Size == Size) && (At->Subpixel == Subpixel)) + { + Glyph = At; + break; + } + } + + if(Glyph) + { + DLLRemove_NP(Atlas->LRUFirst, Atlas->LRULast, Glyph, LRUNext, LRUPrev); + } + else + { + if(Atlas->GlyphsUsed < Atlas->MaxGlyphCount) + { + Glyph = Atlas->Glyphs + Atlas->GlyphsUsed++; + } + else + { + Glyph = Atlas->LRUFirst; + Assert(Glyph); + + DLLRemove_NP(Atlas->LRUFirst, Atlas->LRULast, Glyph, LRUNext, LRUPrev); + } + + RasterizeGlyph(Atlas, Font, Glyph, Codepoint, Size, Subpixel); + } + + DLLInsertLast_NP(Atlas->LRUFirst, Atlas->LRULast, Glyph, LRUNext, LRUPrev); + + return(Glyph); +} + +static glyph_atlas *CreateGlyphAtlas(vn_render_commands *RenderCommands, + s32 BitmapSize = DEFAULT_GLYPH_ATLAS_DIM, + s32 GlyphSize = MAX_GLYPH_SIZE) +{ + glyph_atlas *Atlas = BootstrapPushStruct(glyph_atlas, Arena); + + Atlas->BitmapSize = BitmapSize; + Atlas->GlyphSize = GlyphSize; + + Atlas->MaxGlyphCount = (DEFAULT_GLYPH_ATLAS_DIM / MAX_GLYPH_SIZE)*(DEFAULT_GLYPH_ATLAS_DIM / MAX_GLYPH_SIZE); + Atlas->Glyphs = PushArray(&Atlas->Arena, glyph, Atlas->MaxGlyphCount); + + Atlas->RenderCommands = RenderCommands; + Atlas->Texture = RenderCommands->AllocateTexture(V2S(BitmapSize, BitmapSize), Render_TextureFormat_R8, 0); + + Atlas->Fonts[Font_Regular].Data = Platform_ReadEntireFile(&Atlas->Arena, StrLit("../fonts/Roboto-Regular.ttf")); + Atlas->Fonts[Font_Bold].Data = Platform_ReadEntireFile(&Atlas->Arena, StrLit("../fonts/Roboto-Bold.ttf")); + Atlas->Fonts[Font_Monospace].Data = Platform_ReadEntireFile(&Atlas->Arena, StrLit("../fonts/Liberation-Mono.ttf")); + Atlas->Fonts[Font_Icons].Data = Platform_ReadEntireFile(&Atlas->Arena, StrLit("../fonts/icons.ttf")); + + for(s32 FontIndex = 0; + FontIndex < Font_Count; + ++FontIndex) + { + loaded_font *Font = Atlas->Fonts + FontIndex; + stbtt_InitFont(&Font->Info, + Font->Data.Data, + stbtt_GetFontOffsetForIndex(Font->Data.Data, 0)); + + stbtt_GetFontVMetrics(&Font->Info, &Font->Ascent, &Font->Descent, &Font->LineGap); + } + + Atlas->BitmapBuffer = PushArray(&Atlas->Arena, u8, GlyphSize*GlyphSize); + + return(Atlas); +} + +static void PushText(render_group *Group, glyph_atlas *Atlas, font_id Font, + v2 P, r32 Size, v4 Color, + string Text) +{ + r32 Oversample = 2; + + for(utf8_iterator Iter = IterateUTF8String(Text); + Iter.Codepoint != 0; + Advance(&Iter)) + { + u32 Codepoint = Iter.Codepoint; + + glyph *Glyph = GetGlyph(Atlas, Font, Codepoint, Size*Oversample, GetSubpixelSegmentAtP(P.x*Oversample)); + Assert(Glyph); + + v2 GlyphP = P; + GlyphP.x += Glyph->Offset.x/Oversample; + GlyphP.y += Glyph->Offset.y/Oversample; + + v2 RenderDim = V2(Glyph->P1 - Glyph->P0); + v2 Dim = RenderDim; + Dim.x /= Oversample; + Dim.y /= Oversample; + + PushTexturedQuad(Group, GlyphP, Dim, V2(Glyph->P0), RenderDim, Color, Color, Color, Color, 0, 0, 0, Atlas->Texture); + + P.x += Glyph->Advance/Oversample; + } +} + +static void PushTextF(render_group *Group, glyph_atlas *Atlas, font_id Font, + v2 P, r32 Size, v4 Color, + char *Format, ...) +{ + temporary_memory Scratch = GetScratch(0, 0); + + va_list Arguments; + va_start(Arguments, Format); + string String = PushFormatVariadic(Scratch.Arena, Format, Arguments); + va_end(Arguments); + + PushText(Group, Atlas, Font, P, Size, Color, String); + + ReleaseScratch(Scratch); +} + +inline r32 CalculateRasterizedTextWidth(glyph_atlas *Atlas, font_id Font, r32 Size, string Text) +{ + r32 Oversample = 2; + + r32 X = 0; + + for(utf8_iterator Iter = IterateUTF8String(Text); + Iter.Codepoint != 0; + Advance(&Iter)) + { + u32 Codepoint = Iter.Codepoint; + + glyph *Glyph = GetGlyph(Atlas, Font, Codepoint, Size*Oversample, GetSubpixelSegmentAtP(X*Oversample)); + Assert(Glyph); + + X += Glyph->Advance/Oversample; + } + + return(X); +} + +inline r32 CalculateRasterizedTextHeight(glyph_atlas *Atlas, font_id Font, r32 Size, string Text) +{ + r32 Scale = stbtt_ScaleForMappingEmToPixels(&Atlas->Fonts[Font].Info, Size)/ + stbtt_ScaleForPixelHeight(&Atlas->Fonts[Font].Info, Size); + + r32 Y = Size*Scale; + + for(utf8_iterator Iter = IterateUTF8String(Text); + Iter.Codepoint != 0; + Advance(&Iter)) + { + u32 Codepoint = Iter.Codepoint; + + if(Codepoint == '\n') + { + Y += Size*Scale; + } + } + return(Y); +} \ No newline at end of file diff --git a/code/vn_font.h b/code/vn_font.h new file mode 100644 index 0000000..a8e5c44 --- /dev/null +++ b/code/vn_font.h @@ -0,0 +1,110 @@ +/* date = May 7th 2023 10:16 am */ + +#ifndef VN_FONT_H +#define VN_FONT_H + +enum font_id +{ + Font_Regular, + Font_Bold, + Font_Monospace, + Font_Icons, + + Font_Count, +}; + +#define FontIcon_None 0x0000 +#define FontIcon_Pencil 0xe800 +#define FontIcon_Forward 0xe801 +#define FontIcon_Book 0xe802 +#define FontIcon_FolderOpen 0xe803 +#define FontIcon_Wrench 0xe804 +#define FontIcon_CW 0xe805 +#define FontIcon_CCW 0xe806 +#define FontIcon_ArrowsCW 0xe807 +#define FontIcon_ResizeVertical 0xe808 +#define FontIcon_ResizeHorizontal 0xe809 +#define FontIcon_Play 0xe80a +#define FontIcon_Stop 0xe80b +#define FontIcon_Floppy 0xe80c +#define FontIcon_Pause 0xe80d +#define FontIcon_Folder 0xe80e +#define FontIcon_Cog 0xe80f +#define FontIcon_Attention 0xe810 +#define FontIcon_Cancel 0xe811 +#define FontIcon_Filter 0xf0b0 +#define FontIcon_Menu 0xf0c9 +#define FontIcon_CircleEmpty 0xf10c +#define FontIcon_Circle 0xf111 +#define FontIcon_Reply 0xf112 +#define FontIcon_Terminal 0xf120 +#define FontIcon_Ellipsis 0xf141 +#define FontIcon_Document 0xf15b +#define FontIcon_DocumentText 0xf15c +#define FontIcon_Eyedropper 0xf1fb +#define FontIcon_WindowMaximize 0xf2d0 +#define FontIcon_WindowMinimize 0xf2d1 +#define FontIcon_WindowRestore 0xf2d2 +#define FontIcon_WindowClose 0xf2d4 +#define FontIcon_DownDir 0xe812 +#define FontIcon_UpDir 0xe813 +#define FontIcon_LeftDir 0xe814 +#define FontIcon_RightDir 0xe815 +#define FontIcon_TextAlignLeft 0xe816 +#define FontIcon_TextAlignCenter 0xe817 +#define FontIcon_TextAlignRight 0xe818 + +struct glyph +{ + glyph *LRUNext; + glyph *LRUPrev; + + font_id Font; + u32 Codepoint; + r32 Size; + s32 Subpixel; + + v2s P0; + v2s P1; + v2 Offset; + r32 Advance; +}; + +#define DEFAULT_GLYPH_ATLAS_DIM 1024 +#define MAX_GLYPH_SIZE 64 + +#define STB_TRUETYPE_IMPLEMENTATION +#include "third_party/stb_truetype.h" + +struct loaded_font +{ + stbtt_fontinfo Info; + string Data; + + s32 Ascent; + s32 Descent; + s32 LineGap; +}; + +struct glyph_atlas +{ + memory_arena Arena; + + s32 MaxGlyphCount; + s32 GlyphsUsed; + glyph *Glyphs; + + glyph *LRUFirst; + glyph *LRULast; + + vn_render_commands *RenderCommands; + render_handle Texture; + + u8 *BitmapBuffer; + s32 BitmapSize; + s32 GlyphSize; + + loaded_font Fonts[Font_Count]; +}; + +#endif //VN_FONT_H diff --git a/code/vn_math.h b/code/vn_math.h new file mode 100644 index 0000000..68b20cf --- /dev/null +++ b/code/vn_math.h @@ -0,0 +1,114 @@ +/* date = April 29th 2023 3:06 pm */ + +#ifndef VN_MATH_H +#define VN_MATH_H + +#include "math.h" + +inline r32 Floor(r32 Value) +{ + r32 Result = floorf(Value); + return(Result); +} + +inline r32 Pow(r32 Base, r32 Exponent) +{ + r32 Result = powf(Base, Exponent); + return(Result); +} + +inline r32 AbsoluteValue(r32 Value) +{ + r32 Result = fabsf(Value); + return(Result); +} + +inline b32 AreAlmostEqual(r32 A, r32 B) +{ + b32 Result = false; + + r32 Epsilon = 0.001; + if(AbsoluteValue(A - B) < Epsilon) + { + Result = true; + } + + return(Result); +} + +inline v2s operator+(v2s A, v2s B) +{ + v2s Result = {A.x + B.x, A.y + B.y}; + return(Result); +} + +inline v2s operator-(v2s A, v2s B) +{ + v2s Result = {A.x - B.x, A.y - B.y}; + return(Result); +} + +inline v2s operator+=(v2s &A, v2s B) { return A = A + B; } +inline v2s operator-=(v2s &A, v2s B) { return A = A - B; } + +inline v2 operator+(v2 A, v2 B) +{ + v2 Result = {A.x + B.x, A.y + B.y}; + return(Result); +} + +inline v2 operator-(v2 A, v2 B) +{ + v2 Result = {A.x - B.x, A.y - B.y}; + return(Result); +} + +inline v2 operator*(v2 A, r32 B) +{ + v2 Result = {A.x*B, A.y*B}; + return(Result); +} + +inline v2 operator+=(v2 &A, v2 B) { return A = A + B; } +inline v2 operator-=(v2 &A, v2 B) { return A = A - B; } + +inline v2 Min(v2 A, v2 B) +{ + v2 Result = V2(Minimum(A.x, B.x), Minimum(A.y, B.y)); + return(Result); +} + +inline v2 Max(v2 A, v2 B) +{ + v2 Result = V2(Maximum(A.x, B.x), Maximum(A.y, B.y)); + return(Result); +} + +inline v4 operator+(v4 A, v4 B) +{ + v4 Result = {A.x + B.x, A.y + B.y, A.z + B.z, A.w + B.w}; + return(Result); +} + +inline v4 operator-(v4 A, v4 B) +{ + v4 Result = {A.x - B.x, A.y - B.y, A.z - B.z, A.w - B.w}; + return(Result); +} + +inline v4 operator*(v4 A, r32 B) +{ + v4 Result = {A.x*B, A.y*B, A.z*B, A.w*B}; + return(Result); +} + +inline v4 operator+=(v4 &A, v4 B) { return A = A + B; } +inline v4 operator-=(v4 &A, v4 B) { return A = A - B; } + +inline v4 LinearBlend(v4 A, v4 B, r32 C) +{ + v4 Result = A + (B - A) * C; + return(Result); +} + +#endif //VN_MATH_H diff --git a/code/vn_memory.h b/code/vn_memory.h new file mode 100644 index 0000000..a27dd25 --- /dev/null +++ b/code/vn_memory.h @@ -0,0 +1,222 @@ +/* date = April 26th 2023 10:11 pm */ + +#ifndef VN_MEMORY_H +#define VN_MEMORY_H + +struct memory_arena +{ + platform_memory_block *CurrentBlock; + umm MinimumBlockSize; + s32 TemporaryMemoryCount; +}; + +struct temporary_memory +{ + memory_arena *Arena; + platform_memory_block *Block; + umm Used; +}; + +inline temporary_memory BeginTemporaryMemory(memory_arena *Arena) +{ + temporary_memory Result; + Result.Arena = Arena; + Result.Block = Arena->CurrentBlock; + Result.Used = Arena->CurrentBlock ? Arena->CurrentBlock->Used : 0; + + ++Arena->TemporaryMemoryCount; + + return(Result); +} + +inline void EndTemporaryMemory(temporary_memory Temp) +{ + memory_arena *Arena = Temp.Arena; + while(Arena->CurrentBlock != Temp.Block) + { + platform_memory_block *MemoryBlock = Arena->CurrentBlock; + Arena->CurrentBlock = MemoryBlock->ArenaPrev; + Platform.DeallocateMemory(MemoryBlock); + } + + if(Arena->CurrentBlock) + { + Assert(Arena->CurrentBlock->Used >= Temp.Used); + Arena->CurrentBlock->Used = Temp.Used; + } + + Assert(Arena->TemporaryMemoryCount > 0); + --Arena->TemporaryMemoryCount; +} + +static void Release(memory_arena *Arena) +{ + while(Arena->CurrentBlock != 0) + { + platform_memory_block *MemoryBlock = Arena->CurrentBlock; + b32 IsLastBlock = MemoryBlock->ArenaPrev == 0; + Arena->CurrentBlock = MemoryBlock->ArenaPrev; + Platform.DeallocateMemory(MemoryBlock); + + if(IsLastBlock) + { + break; + } + } +} + +enum arena_push_flag +{ + ArenaFlag_ClearToZero = 0x1, +}; + +struct arena_push_params +{ + u32 Flags; + u32 Alignment; +}; + +inline arena_push_params DefaultArenaParams(void) +{ + arena_push_params Params = {}; + Params.Flags = ArenaFlag_ClearToZero; + Params.Alignment = 4; + return(Params); +} + +inline arena_push_params NoClear(void) +{ + arena_push_params Params = DefaultArenaParams(); + Params.Flags &= ~ArenaFlag_ClearToZero; + return(Params); +} + +inline umm GetAlignmentOffset(memory_arena *Arena, umm Alignment) +{ + umm AlignmentOffset = 0; + + umm ResultPointer = (umm)Arena->CurrentBlock + Arena->CurrentBlock->Used; + umm AlignmentMask = Alignment - 1; + if(ResultPointer & AlignmentMask) + { + AlignmentOffset = Alignment - (ResultPointer & AlignmentMask); + } + + return(AlignmentOffset); +} + +inline umm GetEffectiveSizeFor(memory_arena *Arena, umm InitialSize, arena_push_params Params) +{ + umm Size = InitialSize; + + umm AlignmentOffset = GetAlignmentOffset(Arena, Params.Alignment); + Size += AlignmentOffset; + + return(Size); +} + +#define PushSize(Arena, InitialSize, ...) PushSize_(Arena, InitialSize, __VA_ARGS__) +#define PushStruct(Arena, type, ...) (type *)PushSize_(Arena, sizeof(type), __VA_ARGS__) +#define PushArray(Arena, type, Count, ...) (type *)PushSize_(Arena, sizeof(type)*Count, __VA_ARGS__) + +inline void *PushSize_(memory_arena *Arena, umm InitialSize, arena_push_params Params = DefaultArenaParams()) +{ + void *Result = 0; + + umm Size = 0; + if(Arena->CurrentBlock) + { + Size = GetEffectiveSizeFor(Arena, InitialSize, Params); + } + + if(!Arena->CurrentBlock || ((Arena->CurrentBlock->Used + Size) > Arena->CurrentBlock->Size)) + { + Size = InitialSize; + + if(!Arena->MinimumBlockSize) + { + Arena->MinimumBlockSize = 1024*1024; + } + + umm BlockSize = Maximum(Size, Arena->MinimumBlockSize); + + platform_memory_block *NewBlock = Platform.AllocateMemory(BlockSize); + NewBlock->ArenaPrev = Arena->CurrentBlock; + Arena->CurrentBlock = NewBlock; + } + + Assert((Arena->CurrentBlock->Used + Size) <= Arena->CurrentBlock->Size); + + umm AlignmentOffset = GetAlignmentOffset(Arena, Params.Alignment); + umm OffsetInBlock = Arena->CurrentBlock->Used + AlignmentOffset; + Result = Arena->CurrentBlock->Base + OffsetInBlock; + Arena->CurrentBlock->Used += Size; + + Assert(Size >= InitialSize); + Assert(Arena->CurrentBlock->Used <= Arena->CurrentBlock->Size); + + if(Params.Flags & ArenaFlag_ClearToZero) + { + ZeroSize(Result, InitialSize); + } + + return(Result); +} + +static void *BootstrapPushSize(umm Size, umm OffsetToArena) +{ + memory_arena Arena = {}; + void *Result = PushSize(&Arena, Size); + *(memory_arena *)((u8 *)Result + OffsetToArena) = Arena; + + return(Result); +} + +#define BootstrapPushStruct(type, Member) (type *)BootstrapPushSize(sizeof(type), OffsetOf(type, Member)) + +static string PushString(memory_arena *Arena, string String) +{ + string Result = MakeString(PushArray(Arena, char, String.Count + 1), String.Count); + Copy(Result.Data, String.Data, String.Count); + + Result.Data[Result.Count] = 0; + + return(Result); +} + +static string PushFormatVariadic(memory_arena *Arena, char *Format, va_list Arguments) +{ + va_list ArgumentsCopy; + va_copy(ArgumentsCopy, Arguments); + + string Result; + Result.Count = stbsp_vsnprintf(0, 0, Format, ArgumentsCopy); + Result.Data = (u8 *)PushSize(Arena, Result.Count + 1, NoClear()); + Result.Data[Result.Count] = 0; + + stbsp_vsnprintf((char *)Result.Data, (s32)Result.Count + 1, Format, Arguments); + + return(Result); +} + +inline string PushFormat(memory_arena *Arena, char *Format, ...) +{ + va_list Arguments; + va_start(Arguments, Format); + string Result = PushFormatVariadic(Arena, Format, Arguments); + va_end(Arguments); + + return(Result); +} + +inline string PushCString(memory_arena *Arena, char *CString) +{ + string Result; + Result.Count = StringLength(CString); + Result.Data = PushArray(Arena, u8, Result.Count); + Copy(Result.Data, CString, Result.Count); + + return(Result); +} + +#endif //VN_MEMORY_H diff --git a/code/vn_opengl_defines.h b/code/vn_opengl_defines.h new file mode 100644 index 0000000..8126266 --- /dev/null +++ b/code/vn_opengl_defines.h @@ -0,0 +1,611 @@ +/* date = March 18th 2023 7:14 pm */ + +#ifndef VN_OPENGL_DEFINES_H +#define VN_OPENGL_DEFINES_H + +typedef s8 GLbyte; +typedef r32 GLclampf; +typedef r64 GLclampd; +typedef s16 GLshort; +typedef u16 GLushort; +typedef void GLvoid; +typedef unsigned int GLenum; +typedef r32 GLfloat; +typedef r64 GLdouble; +typedef s32 GLfixed; +typedef unsigned int GLuint; +typedef size_t GLsizeiptr; +typedef intptr_t GLintptr; +typedef unsigned int GLbitfield; +typedef int GLint; +typedef char GLchar; +typedef u8 GLubyte; +typedef unsigned char GLboolean; +typedef int GLsizei; +typedef s32 GLclampx; + +#define GL_VERSION_1_1 1 +#define GL_ACCUM 0x0100 +#define GL_LOAD 0x0101 +#define GL_RETURN 0x0102 +#define GL_MULT 0x0103 +#define GL_ADD 0x0104 +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 +#define GL_CURRENT_BIT 0x00000001 +#define GL_POINT_BIT 0x00000002 +#define GL_LINE_BIT 0x00000004 +#define GL_POLYGON_BIT 0x00000008 +#define GL_POLYGON_STIPPLE_BIT 0x00000010 +#define GL_PIXEL_MODE_BIT 0x00000020 +#define GL_LIGHTING_BIT 0x00000040 +#define GL_FOG_BIT 0x00000080 +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_ACCUM_BUFFER_BIT 0x00000200 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_VIEWPORT_BIT 0x00000800 +#define GL_TRANSFORM_BIT 0x00001000 +#define GL_ENABLE_BIT 0x00002000 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_HINT_BIT 0x00008000 +#define GL_EVAL_BIT 0x00010000 +#define GL_LIST_BIT 0x00020000 +#define GL_TEXTURE_BIT 0x00040000 +#define GL_SCISSOR_BIT 0x00080000 +#define GL_ALL_ATTRIB_BITS 0x000fffff +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_QUADS 0x0007 +#define GL_QUAD_STRIP 0x0008 +#define GL_POLYGON 0x0009 +#define GL_ZERO 0 +#define GL_ONE 1 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA_SATURATE 0x0308 +#define GL_TRUE 1 +#define GL_FALSE 0 +#define GL_CLIP_PLANE0 0x3000 +#define GL_CLIP_PLANE1 0x3001 +#define GL_CLIP_PLANE2 0x3002 +#define GL_CLIP_PLANE3 0x3003 +#define GL_CLIP_PLANE4 0x3004 +#define GL_CLIP_PLANE5 0x3005 +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_2_BYTES 0x1407 +#define GL_3_BYTES 0x1408 +#define GL_4_BYTES 0x1409 +#define GL_DOUBLE 0x140A +#define GL_NONE 0 +#define GL_FRONT_LEFT 0x0400 +#define GL_FRONT_RIGHT 0x0401 +#define GL_BACK_LEFT 0x0402 +#define GL_BACK_RIGHT 0x0403 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_LEFT 0x0406 +#define GL_RIGHT 0x0407 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_AUX0 0x0409 +#define GL_AUX1 0x040A +#define GL_AUX2 0x040B +#define GL_AUX3 0x040C +#define GL_NO_ERROR 0 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_OPERATION 0x0502 +#define GL_STACK_OVERFLOW 0x0503 +#define GL_STACK_UNDERFLOW 0x0504 +#define GL_OUT_OF_MEMORY 0x0505 +#define GL_2D 0x0600 +#define GL_3D 0x0601 +#define GL_3D_COLOR 0x0602 +#define GL_3D_COLOR_TEXTURE 0x0603 +#define GL_4D_COLOR_TEXTURE 0x0604 +#define GL_PASS_THROUGH_TOKEN 0x0700 +#define GL_POINT_TOKEN 0x0701 +#define GL_LINE_TOKEN 0x0702 +#define GL_POLYGON_TOKEN 0x0703 +#define GL_BITMAP_TOKEN 0x0704 +#define GL_DRAW_PIXEL_TOKEN 0x0705 +#define GL_COPY_PIXEL_TOKEN 0x0706 +#define GL_LINE_RESET_TOKEN 0x0707 +#define GL_EXP 0x0800 +#define GL_EXP2 0x0801 +#define GL_CW 0x0900 +#define GL_CCW 0x0901 +#define GL_COEFF 0x0A00 +#define GL_ORDER 0x0A01 +#define GL_DOMAIN 0x0A02 +#define GL_CURRENT_COLOR 0x0B00 +#define GL_CURRENT_INDEX 0x0B01 +#define GL_CURRENT_NORMAL 0x0B02 +#define GL_CURRENT_TEXTURE_COORDS 0x0B03 +#define GL_CURRENT_RASTER_COLOR 0x0B04 +#define GL_CURRENT_RASTER_INDEX 0x0B05 +#define GL_CURRENT_RASTER_TEXTURE_COORDS 0x0B06 +#define GL_CURRENT_RASTER_POSITION 0x0B07 +#define GL_CURRENT_RASTER_POSITION_VALID 0x0B08 +#define GL_CURRENT_RASTER_DISTANCE 0x0B09 +#define GL_POINT_SMOOTH 0x0B10 +#define GL_POINT_SIZE 0x0B11 +#define GL_POINT_SIZE_RANGE 0x0B12 +#define GL_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_LINE_SMOOTH 0x0B20 +#define GL_LINE_WIDTH 0x0B21 +#define GL_LINE_WIDTH_RANGE 0x0B22 +#define GL_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_LINE_STIPPLE 0x0B24 +#define GL_LINE_STIPPLE_PATTERN 0x0B25 +#define GL_LINE_STIPPLE_REPEAT 0x0B26 +#define GL_LIST_MODE 0x0B30 +#define GL_MAX_LIST_NESTING 0x0B31 +#define GL_LIST_BASE 0x0B32 +#define GL_LIST_INDEX 0x0B33 +#define GL_POLYGON_MODE 0x0B40 +#define GL_POLYGON_SMOOTH 0x0B41 +#define GL_POLYGON_STIPPLE 0x0B42 +#define GL_EDGE_FLAG 0x0B43 +#define GL_CULL_FACE 0x0B44 +#define GL_CULL_FACE_MODE 0x0B45 +#define GL_FRONT_FACE 0x0B46 +#define GL_LIGHTING 0x0B50 +#define GL_LIGHT_MODEL_LOCAL_VIEWER 0x0B51 +#define GL_LIGHT_MODEL_TWO_SIDE 0x0B52 +#define GL_LIGHT_MODEL_AMBIENT 0x0B53 +#define GL_SHADE_MODEL 0x0B54 +#define GL_COLOR_MATERIAL_FACE 0x0B55 +#define GL_COLOR_MATERIAL_PARAMETER 0x0B56 +#define GL_COLOR_MATERIAL 0x0B57 +#define GL_FOG 0x0B60 +#define GL_FOG_INDEX 0x0B61 +#define GL_FOG_DENSITY 0x0B62 +#define GL_FOG_START 0x0B63 +#define GL_FOG_END 0x0B64 +#define GL_FOG_MODE 0x0B65 +#define GL_FOG_COLOR 0x0B66 +#define GL_DEPTH_RANGE 0x0B70 +#define GL_DEPTH_TEST 0x0B71 +#define GL_DEPTH_WRITEMASK 0x0B72 +#define GL_DEPTH_CLEAR_VALUE 0x0B73 +#define GL_DEPTH_FUNC 0x0B74 +#define GL_ACCUM_CLEAR_VALUE 0x0B80 +#define GL_STENCIL_TEST 0x0B90 +#define GL_STENCIL_CLEAR_VALUE 0x0B91 +#define GL_STENCIL_FUNC 0x0B92 +#define GL_STENCIL_VALUE_MASK 0x0B93 +#define GL_STENCIL_FAIL 0x0B94 +#define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95 +#define GL_STENCIL_PASS_DEPTH_PASS 0x0B96 +#define GL_STENCIL_REF 0x0B97 +#define GL_STENCIL_WRITEMASK 0x0B98 +#define GL_MATRIX_MODE 0x0BA0 +#define GL_NORMALIZE 0x0BA1 +#define GL_VIEWPORT 0x0BA2 +#define GL_MODELVIEW_STACK_DEPTH 0x0BA3 +#define GL_PROJECTION_STACK_DEPTH 0x0BA4 +#define GL_TEXTURE_STACK_DEPTH 0x0BA5 +#define GL_MODELVIEW_MATRIX 0x0BA6 +#define GL_PROJECTION_MATRIX 0x0BA7 +#define GL_TEXTURE_MATRIX 0x0BA8 +#define GL_ATTRIB_STACK_DEPTH 0x0BB0 +#define GL_CLIENT_ATTRIB_STACK_DEPTH 0x0BB1 +#define GL_ALPHA_TEST 0x0BC0 +#define GL_ALPHA_TEST_FUNC 0x0BC1 +#define GL_ALPHA_TEST_REF 0x0BC2 +#define GL_DITHER 0x0BD0 +#define GL_BLEND_DST 0x0BE0 +#define GL_BLEND_SRC 0x0BE1 +#define GL_BLEND 0x0BE2 +#define GL_LOGIC_OP_MODE 0x0BF0 +#define GL_INDEX_LOGIC_OP 0x0BF1 +#define GL_COLOR_LOGIC_OP 0x0BF2 +#define GL_AUX_BUFFERS 0x0C00 +#define GL_DRAW_BUFFER 0x0C01 +#define GL_READ_BUFFER 0x0C02 +#define GL_SCISSOR_BOX 0x0C10 +#define GL_SCISSOR_TEST 0x0C11 +#define GL_INDEX_CLEAR_VALUE 0x0C20 +#define GL_INDEX_WRITEMASK 0x0C21 +#define GL_COLOR_CLEAR_VALUE 0x0C22 +#define GL_COLOR_WRITEMASK 0x0C23 +#define GL_INDEX_MODE 0x0C30 +#define GL_RGBA_MODE 0x0C31 +#define GL_DOUBLEBUFFER 0x0C32 +#define GL_STEREO 0x0C33 +#define GL_RENDER_MODE 0x0C40 +#define GL_PERSPECTIVE_CORRECTION_HINT 0x0C50 +#define GL_POINT_SMOOTH_HINT 0x0C51 +#define GL_LINE_SMOOTH_HINT 0x0C52 +#define GL_POLYGON_SMOOTH_HINT 0x0C53 +#define GL_FOG_HINT 0x0C54 +#define GL_TEXTURE_GEN_S 0x0C60 +#define GL_TEXTURE_GEN_T 0x0C61 +#define GL_TEXTURE_GEN_R 0x0C62 +#define GL_TEXTURE_GEN_Q 0x0C63 +#define GL_PIXEL_MAP_I_TO_I 0x0C70 +#define GL_PIXEL_MAP_S_TO_S 0x0C71 +#define GL_PIXEL_MAP_I_TO_R 0x0C72 +#define GL_PIXEL_MAP_I_TO_G 0x0C73 +#define GL_PIXEL_MAP_I_TO_B 0x0C74 +#define GL_PIXEL_MAP_I_TO_A 0x0C75 +#define GL_PIXEL_MAP_R_TO_R 0x0C76 +#define GL_PIXEL_MAP_G_TO_G 0x0C77 +#define GL_PIXEL_MAP_B_TO_B 0x0C78 +#define GL_PIXEL_MAP_A_TO_A 0x0C79 +#define GL_PIXEL_MAP_I_TO_I_SIZE 0x0CB0 +#define GL_PIXEL_MAP_S_TO_S_SIZE 0x0CB1 +#define GL_PIXEL_MAP_I_TO_R_SIZE 0x0CB2 +#define GL_PIXEL_MAP_I_TO_G_SIZE 0x0CB3 +#define GL_PIXEL_MAP_I_TO_B_SIZE 0x0CB4 +#define GL_PIXEL_MAP_I_TO_A_SIZE 0x0CB5 +#define GL_PIXEL_MAP_R_TO_R_SIZE 0x0CB6 +#define GL_PIXEL_MAP_G_TO_G_SIZE 0x0CB7 +#define GL_PIXEL_MAP_B_TO_B_SIZE 0x0CB8 +#define GL_PIXEL_MAP_A_TO_A_SIZE 0x0CB9 +#define GL_UNPACK_SWAP_BYTES 0x0CF0 +#define GL_UNPACK_LSB_FIRST 0x0CF1 +#define GL_UNPACK_ROW_LENGTH 0x0CF2 +#define GL_UNPACK_SKIP_ROWS 0x0CF3 +#define GL_UNPACK_SKIP_PIXELS 0x0CF4 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_PACK_SWAP_BYTES 0x0D00 +#define GL_PACK_LSB_FIRST 0x0D01 +#define GL_PACK_ROW_LENGTH 0x0D02 +#define GL_PACK_SKIP_ROWS 0x0D03 +#define GL_PACK_SKIP_PIXELS 0x0D04 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_MAP_COLOR 0x0D10 +#define GL_MAP_STENCIL 0x0D11 +#define GL_INDEX_SHIFT 0x0D12 +#define GL_INDEX_OFFSET 0x0D13 +#define GL_RED_SCALE 0x0D14 +#define GL_RED_BIAS 0x0D15 +#define GL_ZOOM_X 0x0D16 +#define GL_ZOOM_Y 0x0D17 +#define GL_GREEN_SCALE 0x0D18 +#define GL_GREEN_BIAS 0x0D19 +#define GL_BLUE_SCALE 0x0D1A +#define GL_BLUE_BIAS 0x0D1B +#define GL_ALPHA_SCALE 0x0D1C +#define GL_ALPHA_BIAS 0x0D1D +#define GL_DEPTH_SCALE 0x0D1E +#define GL_DEPTH_BIAS 0x0D1F +#define GL_MAX_EVAL_ORDER 0x0D30 +#define GL_MAX_LIGHTS 0x0D31 +#define GL_MAX_CLIP_PLANES 0x0D32 +#define GL_MAX_TEXTURE_SIZE 0x0D33 +#define GL_MAX_PIXEL_MAP_TABLE 0x0D34 +#define GL_MAX_ATTRIB_STACK_DEPTH 0x0D35 +#define GL_MAX_MODELVIEW_STACK_DEPTH 0x0D36 +#define GL_MAX_NAME_STACK_DEPTH 0x0D37 +#define GL_MAX_PROJECTION_STACK_DEPTH 0x0D38 +#define GL_MAX_TEXTURE_STACK_DEPTH 0x0D39 +#define GL_MAX_VIEWPORT_DIMS 0x0D3A +#define GL_MAX_CLIENT_ATTRIB_STACK_DEPTH 0x0D3B +#define GL_SUBPIXEL_BITS 0x0D50 +#define GL_INDEX_BITS 0x0D51 +#define GL_RED_BITS 0x0D52 +#define GL_GREEN_BITS 0x0D53 +#define GL_BLUE_BITS 0x0D54 +#define GL_ALPHA_BITS 0x0D55 +#define GL_DEPTH_BITS 0x0D56 +#define GL_STENCIL_BITS 0x0D57 +#define GL_ACCUM_RED_BITS 0x0D58 +#define GL_ACCUM_GREEN_BITS 0x0D59 +#define GL_ACCUM_BLUE_BITS 0x0D5A +#define GL_ACCUM_ALPHA_BITS 0x0D5B +#define GL_NAME_STACK_DEPTH 0x0D70 +#define GL_AUTO_NORMAL 0x0D80 +#define GL_MAP1_COLOR_4 0x0D90 +#define GL_MAP1_INDEX 0x0D91 +#define GL_MAP1_NORMAL 0x0D92 +#define GL_MAP1_TEXTURE_COORD_1 0x0D93 +#define GL_MAP1_TEXTURE_COORD_2 0x0D94 +#define GL_MAP1_TEXTURE_COORD_3 0x0D95 +#define GL_MAP1_TEXTURE_COORD_4 0x0D96 +#define GL_MAP1_VERTEX_3 0x0D97 +#define GL_MAP1_VERTEX_4 0x0D98 +#define GL_MAP2_COLOR_4 0x0DB0 +#define GL_MAP2_INDEX 0x0DB1 +#define GL_MAP2_NORMAL 0x0DB2 +#define GL_MAP2_TEXTURE_COORD_1 0x0DB3 +#define GL_MAP2_TEXTURE_COORD_2 0x0DB4 +#define GL_MAP2_TEXTURE_COORD_3 0x0DB5 +#define GL_MAP2_TEXTURE_COORD_4 0x0DB6 +#define GL_MAP2_VERTEX_3 0x0DB7 +#define GL_MAP2_VERTEX_4 0x0DB8 +#define GL_MAP1_GRID_DOMAIN 0x0DD0 +#define GL_MAP1_GRID_SEGMENTS 0x0DD1 +#define GL_MAP2_GRID_DOMAIN 0x0DD2 +#define GL_MAP2_GRID_SEGMENTS 0x0DD3 +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_FEEDBACK_BUFFER_POINTER 0x0DF0 +#define GL_FEEDBACK_BUFFER_SIZE 0x0DF1 +#define GL_FEEDBACK_BUFFER_TYPE 0x0DF2 +#define GL_SELECTION_BUFFER_POINTER 0x0DF3 +#define GL_SELECTION_BUFFER_SIZE 0x0DF4 +#define GL_TEXTURE_WIDTH 0x1000 +#define GL_TEXTURE_HEIGHT 0x1001 +#define GL_TEXTURE_INTERNAL_FORMAT 0x1003 +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_TEXTURE_BORDER 0x1005 +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 +#define GL_LIGHT0 0x4000 +#define GL_LIGHT1 0x4001 +#define GL_LIGHT2 0x4002 +#define GL_LIGHT3 0x4003 +#define GL_LIGHT4 0x4004 +#define GL_LIGHT5 0x4005 +#define GL_LIGHT6 0x4006 +#define GL_LIGHT7 0x4007 +#define GL_AMBIENT 0x1200 +#define GL_DIFFUSE 0x1201 +#define GL_SPECULAR 0x1202 +#define GL_POSITION 0x1203 +#define GL_SPOT_DIRECTION 0x1204 +#define GL_SPOT_EXPONENT 0x1205 +#define GL_SPOT_CUTOFF 0x1206 +#define GL_CONSTANT_ATTENUATION 0x1207 +#define GL_LINEAR_ATTENUATION 0x1208 +#define GL_QUADRATIC_ATTENUATION 0x1209 +#define GL_COMPILE 0x1300 +#define GL_COMPILE_AND_EXECUTE 0x1301 +#define GL_CLEAR 0x1500 +#define GL_AND 0x1501 +#define GL_AND_REVERSE 0x1502 +#define GL_COPY 0x1503 +#define GL_AND_INVERTED 0x1504 +#define GL_NOOP 0x1505 +#define GL_XOR 0x1506 +#define GL_OR 0x1507 +#define GL_NOR 0x1508 +#define GL_EQUIV 0x1509 +#define GL_INVERT 0x150A +#define GL_OR_REVERSE 0x150B +#define GL_COPY_INVERTED 0x150C +#define GL_OR_INVERTED 0x150D +#define GL_NAND 0x150E +#define GL_SET 0x150F +#define GL_EMISSION 0x1600 +#define GL_SHININESS 0x1601 +#define GL_AMBIENT_AND_DIFFUSE 0x1602 +#define GL_COLOR_INDEXES 0x1603 +#define GL_MODELVIEW 0x1700 +#define GL_PROJECTION 0x1701 +#define GL_TEXTURE 0x1702 +#define GL_COLOR 0x1800 +#define GL_DEPTH 0x1801 +#define GL_STENCIL 0x1802 +#define GL_COLOR_INDEX 0x1900 +#define GL_STENCIL_INDEX 0x1901 +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_RED 0x1903 +#define GL_GREEN 0x1904 +#define GL_BLUE 0x1905 +#define GL_ALPHA 0x1906 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_LUMINANCE 0x1909 +#define GL_LUMINANCE_ALPHA 0x190A +#define GL_BITMAP 0x1A00 +#define GL_POINT 0x1B00 +#define GL_LINE 0x1B01 +#define GL_FILL 0x1B02 +#define GL_RENDER 0x1C00 +#define GL_FEEDBACK 0x1C01 +#define GL_SELECT 0x1C02 +#define GL_FLAT 0x1D00 +#define GL_SMOOTH 0x1D01 +#define GL_KEEP 0x1E00 +#define GL_REPLACE 0x1E01 +#define GL_INCR 0x1E02 +#define GL_DECR 0x1E03 +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 +#define GL_S 0x2000 +#define GL_T 0x2001 +#define GL_R 0x2002 +#define GL_Q 0x2003 +#define GL_MODULATE 0x2100 +#define GL_DECAL 0x2101 +#define GL_TEXTURE_ENV_MODE 0x2200 +#define GL_TEXTURE_ENV_COLOR 0x2201 +#define GL_TEXTURE_ENV 0x2300 +#define GL_EYE_LINEAR 0x2400 +#define GL_OBJECT_LINEAR 0x2401 +#define GL_SPHERE_MAP 0x2402 +#define GL_TEXTURE_GEN_MODE 0x2500 +#define GL_OBJECT_PLANE 0x2501 +#define GL_EYE_PLANE 0x2502 +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_CLAMP 0x2900 +#define GL_REPEAT 0x2901 +#define GL_CLIENT_PIXEL_STORE_BIT 0x00000001 +#define GL_CLIENT_VERTEX_ARRAY_BIT 0x00000002 +#define GL_CLIENT_ALL_ATTRIB_BITS 0xffffffff +#define GL_POLYGON_OFFSET_FACTOR 0x8038 +#define GL_POLYGON_OFFSET_UNITS 0x2A00 +#define GL_POLYGON_OFFSET_POINT 0x2A01 +#define GL_POLYGON_OFFSET_LINE 0x2A02 +#define GL_POLYGON_OFFSET_FILL 0x8037 +#define GL_ALPHA4 0x803B +#define GL_ALPHA8 0x803C +#define GL_ALPHA12 0x803D +#define GL_ALPHA16 0x803E +#define GL_LUMINANCE4 0x803F +#define GL_LUMINANCE8 0x8040 +#define GL_LUMINANCE12 0x8041 +#define GL_LUMINANCE16 0x8042 +#define GL_LUMINANCE4_ALPHA4 0x8043 +#define GL_LUMINANCE6_ALPHA2 0x8044 +#define GL_LUMINANCE8_ALPHA8 0x8045 +#define GL_LUMINANCE12_ALPHA4 0x8046 +#define GL_LUMINANCE12_ALPHA12 0x8047 +#define GL_LUMINANCE16_ALPHA16 0x8048 +#define GL_INTENSITY 0x8049 +#define GL_INTENSITY4 0x804A +#define GL_INTENSITY8 0x804B +#define GL_INTENSITY12 0x804C +#define GL_INTENSITY16 0x804D +#define GL_R3_G3_B2 0x2A10 +#define GL_RGB4 0x804F +#define GL_RGB5 0x8050 +#define GL_RGB8 0x8051 +#define GL_RGB10 0x8052 +#define GL_RGB12 0x8053 +#define GL_RGB16 0x8054 +#define GL_RGBA2 0x8055 +#define GL_RGBA4 0x8056 +#define GL_RGB5_A1 0x8057 +#define GL_RGBA8 0x8058 +#define GL_RGB10_A2 0x8059 +#define GL_RGBA12 0x805A +#define GL_RGBA16 0x805B +#define GL_TEXTURE_RED_SIZE 0x805C +#define GL_TEXTURE_GREEN_SIZE 0x805D +#define GL_TEXTURE_BLUE_SIZE 0x805E +#define GL_TEXTURE_ALPHA_SIZE 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE 0x8061 +#define GL_PROXY_TEXTURE_1D 0x8063 +#define GL_PROXY_TEXTURE_2D 0x8064 +#define GL_TEXTURE_PRIORITY 0x8066 +#define GL_TEXTURE_RESIDENT 0x8067 +#define GL_TEXTURE_BINDING_1D 0x8068 +#define GL_TEXTURE_BINDING_2D 0x8069 +#define GL_VERTEX_ARRAY 0x8074 +#define GL_NORMAL_ARRAY 0x8075 +#define GL_COLOR_ARRAY 0x8076 +#define GL_INDEX_ARRAY 0x8077 +#define GL_TEXTURE_COORD_ARRAY 0x8078 +#define GL_EDGE_FLAG_ARRAY 0x8079 +#define GL_VERTEX_ARRAY_SIZE 0x807A +#define GL_VERTEX_ARRAY_TYPE 0x807B +#define GL_VERTEX_ARRAY_STRIDE 0x807C +#define GL_NORMAL_ARRAY_TYPE 0x807E +#define GL_NORMAL_ARRAY_STRIDE 0x807F +#define GL_COLOR_ARRAY_SIZE 0x8081 +#define GL_COLOR_ARRAY_TYPE 0x8082 +#define GL_COLOR_ARRAY_STRIDE 0x8083 +#define GL_INDEX_ARRAY_TYPE 0x8085 +#define GL_INDEX_ARRAY_STRIDE 0x8086 +#define GL_TEXTURE_COORD_ARRAY_SIZE 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE 0x808A +#define GL_EDGE_FLAG_ARRAY_STRIDE 0x808C +#define GL_VERTEX_ARRAY_POINTER 0x808E +#define GL_NORMAL_ARRAY_POINTER 0x808F +#define GL_COLOR_ARRAY_POINTER 0x8090 +#define GL_INDEX_ARRAY_POINTER 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER 0x8093 +#define GL_V2F 0x2A20 +#define GL_V3F 0x2A21 +#define GL_C4UB_V2F 0x2A22 +#define GL_C4UB_V3F 0x2A23 +#define GL_C3F_V3F 0x2A24 +#define GL_N3F_V3F 0x2A25 +#define GL_C4F_N3F_V3F 0x2A26 +#define GL_T2F_V3F 0x2A27 +#define GL_T4F_V4F 0x2A28 +#define GL_T2F_C4UB_V3F 0x2A29 +#define GL_T2F_C3F_V3F 0x2A2A +#define GL_T2F_N3F_V3F 0x2A2B +#define GL_T2F_C4F_N3F_V3F 0x2A2C +#define GL_T4F_C4F_N3F_V4F 0x2A2D + +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_STATIC_DRAW 0x88E4 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_STREAM_DRAW 0x88E0 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 + +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_SWIZZLE_RGBA 0x8E46 + +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF + +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 + +#define GL_DEBUG_SEVERITY_HIGH 0x9146 +#define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242 +#define GL_DEBUG_OUTPUT 0x92E0 + + +#endif //VN_OPENGL_DEFINES_H diff --git a/code/vn_platform.cpp b/code/vn_platform.cpp new file mode 100644 index 0000000..5de73df --- /dev/null +++ b/code/vn_platform.cpp @@ -0,0 +1,69 @@ +inline void Platform_ConsumeEvent(platform_event_list *EventList, platform_event *Event) +{ + DLLRemove(EventList->First, EventList->Last, Event); +} + +inline b32 Platform_KeyPress(platform_event_list *EventList, platform_key Key, + platform_modifiers Modifiers = PlatformModifier_DoesNotMatter) +{ + b32 Result = false; + + for(platform_event *Event = EventList->First; + Event != 0; + Event = Event->Next) + { + if((Event->Type == PlatformEvent_Press) && + (Event->Key == Key) && + ((Modifiers == PlatformModifier_DoesNotMatter) || + (Event->Modifiers ^ Modifiers) == 0)) + { + Result = true; + Platform_ConsumeEvent(EventList, Event); + break; + } + } + + return(Result); +} + +inline b32 Platform_KeyRelease(platform_event_list *EventList, platform_key Key, + platform_modifiers Modifiers = PlatformModifier_DoesNotMatter) +{ + b32 Result = false; + + for(platform_event *Event = EventList->First; + Event != 0; + Event = Event->Next) + { + if((Event->Type == PlatformEvent_Release) && + (Event->Key == Key) && + ((Modifiers == PlatformModifier_DoesNotMatter) || + ((Event->Modifiers & Modifiers) != 0) || + ((Event->Modifiers == 0) && (Modifiers == 0)))) + { + Result = true; + Platform_ConsumeEvent(EventList, Event); + break; + } + } + + return(Result); +} + +static string Platform_ReadEntireFile(memory_arena *Arena, string Path) +{ + string Result = {}; + + platform_file_handle File = Platform.OpenFile(Path, PlatformAccess_Read); + if(File.IsValid) + { + Result.Count = Platform.GetFileSize(File); + + Result.Data = PushArray(Arena, u8, Result.Count); + Platform.ReadFile(File, Result.Data, 0, Result.Count); + + Platform.CloseFile(File); + } + + return(Result); +} \ No newline at end of file diff --git a/code/vn_platform.h b/code/vn_platform.h new file mode 100644 index 0000000..ed690ab --- /dev/null +++ b/code/vn_platform.h @@ -0,0 +1,212 @@ +/* date = April 26th 2023 5:12 pm */ + +#ifndef VN_PLATFORM_H +#define VN_PLATFORM_H + +#include "vn_core.h" + +// sixten: Services the platform provides to the game + +struct platform_memory_block +{ + u8 *Base; + u64 Size; + + u64 Used; + platform_memory_block *ArenaPrev; +}; + +#define PLATFORM_ALLOCATE_MEMORY(name) platform_memory_block *name(u64 Size) +typedef PLATFORM_ALLOCATE_MEMORY(platform_allocate_memory); + +#define PLATFORM_DEALLOCATE_MEMORY(name) b32 name(platform_memory_block *Block) +typedef PLATFORM_DEALLOCATE_MEMORY(platform_deallocate_memory); + +enum +{ + PlatformAccess_Read = 0x1, + PlatformAccess_Write = 0x2, +}; +typedef u32 platform_access_flags; + +struct platform_file_handle +{ + u64 Platform; + b32 IsValid; +}; + +#define PLATFORM_OPEN_FILE(name) platform_file_handle name(string Path, platform_access_flags FileAccess) +typedef PLATFORM_OPEN_FILE(platform_open_file); + +#define PLATFORM_CLOSE_FILE(name) void name(platform_file_handle Handle) +typedef PLATFORM_CLOSE_FILE(platform_close_file); + +#define PLATFORM_READ_FILE(name) void name(platform_file_handle Handle, void *Dest, u64 Offset, u64 Size) +typedef PLATFORM_READ_FILE(platform_read_file); + +#define PLATFORM_GET_FILE_SIZE(name) u64 name(platform_file_handle Handle) +typedef PLATFORM_GET_FILE_SIZE(platform_get_file_size); + +enum platform_cursor +{ + PlatformCursor_Arrow, + PlatformCursor_Cross, + PlatformCursor_Hand, + PlatformCursor_Help, + PlatformCursor_IBeam, + PlatformCursor_SlashedCircle, + PlatformCursor_ArrowAll, + PlatformCursor_ArrowNESW, + PlatformCursor_ArrowVertical, + PlatformCursor_ArrowNWSE, + PlatformCursor_ArrowHorizontal, + PlatformCursor_Wait, + + PlatformCursor_Count +}; + +#define PLATFORM_SET_CURSOR(name) void name(platform_cursor Cursor) +typedef PLATFORM_SET_CURSOR(platform_set_cursor); + +#define PLATFORM_TOGGLE_FULLSCREEN(name) void name(void) +typedef PLATFORM_TOGGLE_FULLSCREEN(platform_toggle_fullscreen); + +struct platform_api +{ + platform_allocate_memory *AllocateMemory; + platform_deallocate_memory *DeallocateMemory; + + platform_open_file *OpenFile; + platform_close_file *CloseFile; + platform_read_file *ReadFile; + platform_get_file_size *GetFileSize; + + platform_set_cursor *SetCursor; + platform_toggle_fullscreen *ToggleFullscreen; +}; + +enum platform_event_type +{ + PlatformEvent_Press, + PlatformEvent_Release, + PlatformEvent_Text, + PlatformEvent_MouseScroll, +}; + +enum platform_key +{ + Key_Invalid, + + Key_A, Key_B, Key_C, Key_D, + Key_E, Key_F, Key_G, Key_H, + Key_I, Key_J, Key_K, Key_L, + Key_M, Key_N, Key_O, Key_P, + Key_Q, Key_R, Key_S, Key_T, + Key_U, Key_V, Key_W, Key_X, + Key_Y, Key_Z, + + Key_F1, Key_F2, Key_F3, Key_F4, + Key_F5, Key_F6, Key_F7, Key_F8, + Key_F9, Key_F10, Key_F11, Key_F12, + + Key_Left, Key_Right, Key_Up, Key_Down, + + Key_Space, + + Key_PageUp, Key_PageDown, + Key_Home, Key_End, + + Key_Backspace, Key_Delete, + + Key_MouseLeft, Key_MouseMiddle, Key_MouseRight, +}; + +typedef u32 platform_modifiers; +enum +{ + PlatformModifier_Ctrl = (1 << 0), + PlatformModifier_Shift = (1 << 1), + PlatformModifier_Alt = (1 << 2), + + PlatformModifier_DoesNotMatter = -1 +}; + +struct platform_event +{ + platform_event *Next; + platform_event *Prev; + platform_event_type Type; + platform_modifiers Modifiers; + platform_key Key; + u32 Codepoint; + v2 P; + v2 Scroll; +}; + +struct platform_event_list +{ + platform_event *First; + platform_event *Last; +}; + +static platform_api Platform; + +// sixten: Services that the render backend provides to the game + +#include "vn_render.h" + +#define RENDER_ALLOCATE_TEXTURE(name) render_handle name(v2s Dim, render_texture_format Format, void *Data) +typedef RENDER_ALLOCATE_TEXTURE(render_allocate_texture); + +#define RENDER_FILL_REGION(name) void name(render_handle Handle, v2s DestP, v2s DestDim, void *Data) +typedef RENDER_FILL_REGION(render_fill_region); + +struct vn_render_commands +{ + render_allocate_texture *AllocateTexture; + render_fill_region *FillRegion; + + render_handle WhiteTexture; + + u64 MaxPushBufferSize; + u8 *PushBufferBase; + u8 *PushBufferAt; + + s32 MaxQuadVertexCount; + quad_vertex *QuadVertexBase; + s32 QuadVertexCount; + + s32 MaxQuadIndexCount; + s32 *QuadIndexBase; + s32 QuadIndexCount; + + v2 RenderDim; +}; + +#include "vn_memory.h" +#include "vn_string.h" +#include "vn_thread_context.h" + +// sixten: Services the game provides to the platform + +struct vn_input +{ + platform_event_list *EventList; + + v2 MouseP; + v2 dMouseP; + + r32 dtForFrame; +}; + +struct vn_memory +{ + platform_api PlatformAPI; + + struct vn_state *State; +}; + +#define VN_UPDATE_AND_RENDER(name) void name(thread_context *ThreadContext, vn_memory *Memory, vn_input *Input, vn_render_commands *RenderCommands) +typedef VN_UPDATE_AND_RENDER(vn_update_and_render); + +#endif //VN_PLATFORM_H diff --git a/code/vn_render.cpp b/code/vn_render.cpp new file mode 100644 index 0000000..f6cbe56 --- /dev/null +++ b/code/vn_render.cpp @@ -0,0 +1,244 @@ +inline b32 AreEqual(render_handle A, render_handle B) +{ + b32 Result = false; + if((A.U64[0] == B.U64[0]) && (A.U64[1] == B.U64[1]) && + (A.U64[2] == B.U64[2]) && (A.U64[3] == B.U64[3])) + { + Result = true; + } + return(Result); +} + +inline v2s GetTextureDim(render_handle Handle) +{ + v2s Result = V2S(Handle.U32[2], Handle.U32[3]); + return(Result); +} + +inline render_handle EmptyRenderHandle(void) +{ + render_handle Result = {}; + return(Result); +} + +#define PushCommand(Group, type) (type *)PushCommand_(Group, Render_Command_##type, sizeof(type)) +static void *PushCommand_(render_group *Group, render_command_type Type, u64 Size) +{ + void *Result = 0; + + vn_render_commands *Commands = Group->Commands; + + u64 TotalSize = Size + sizeof(render_command_header); + + u8 *PushBufferEnd = Commands->PushBufferBase + Commands->MaxPushBufferSize; + if((Commands->PushBufferAt + TotalSize) <= PushBufferEnd) + { + render_command_header *Header = (render_command_header *)Commands->PushBufferAt; + Header->Type = Type; + + Commands->PushBufferAt += sizeof(*Header); + + Group->CurrentCommand = Header; + + Result = Commands->PushBufferAt; + Commands->PushBufferAt += Size; + ZeroSize(Result, Size); + } + else + { + InvalidCodepath; + } + + return(Result); +} + +inline render_group BeginRenderGroup(vn_render_commands *Commands) +{ + render_group Group = {}; + Group.Commands = Commands; + Group.ClipStack[0].Max = Commands->RenderDim; + return(Group); +} + +inline void PushClear(render_group *Group, v3 Color) +{ + render_command_clear *Command = PushCommand(Group, render_command_clear); + Command->Color = Color; +} + +inline s32 GetTextureIndexForCommand(render_command_quads *Command, render_handle Handle) +{ + s32 Result = -1; + + for(s32 TextureIndex = 0; + TextureIndex < MAX_BOUND_TEXTURES; + ++TextureIndex) + { + if(AreEqual(Command->Textures[TextureIndex], EmptyRenderHandle())) + { + Assert(Command->TexturesUsed == TextureIndex); + + Command->Textures[TextureIndex] = Handle; + ++Command->TexturesUsed; + } + + if(AreEqual(Command->Textures[TextureIndex], Handle)) + { + Result = TextureIndex; + break; + } + } + + return(Result); +} + +inline u32 PackV4ToU32(v4 V) +{ + u32 Result = + ((u8)(Clamp01(V.x)*255.0)<<24) | + ((u8)(Clamp01(V.y)*255.0)<<16) | + ((u8)(Clamp01(V.z)*255.0)<<8) | + ((u8)(Clamp01(V.w)*255.0)<<0); + return(Result); +} + +inline void PushTexturedQuad(render_group *Group, + v2 P, v2 Dim, + v2 SourceP, v2 SourceDim, + v4 Color00, v4 Color10, v4 Color01, v4 Color11, + r32 CornerRadius, r32 EdgeSoftness, r32 BorderThickness, + render_handle Texture) +{ + vn_render_commands *Commands = Group->Commands; + + render_command_quads *Command = (render_command_quads *)(Group->CurrentCommand + 1); + s32 TextureIndex; + if(!(Group->CurrentCommand && Group->CurrentCommand->Type == Render_Command_render_command_quads && + (TextureIndex = GetTextureIndexForCommand(Command, Texture)) != -1)) + { + Command = PushCommand(Group, render_command_quads); + Command->QuadBufferIndex = Commands->QuadIndexCount; + TextureIndex = GetTextureIndexForCommand(Command, Texture); + } + + range2_r32 Clip = Group->ClipStack[Group->ClipStackUsed]; + range2_r32 Dest = Range2R32(P, P + Dim); + + v2 DestMin = Max(Dest.Min, Clip.Min); + v2 DestMax = Min(Dest.Max, Clip.Max); + + //if(InRange(Clip, P) || InRange(Clip, P + Dim)) + { + v2 HalfSize = Dim*0.5; + + v2 P00 = V2(DestMin.x, DestMin.y); + v2 P01 = V2(DestMin.x, DestMax.y); + v2 P10 = V2(DestMax.x, DestMin.y); + v2 P11 = V2(DestMax.x, DestMax.y); + v2 Center = P + HalfSize; + + u32 Width = Texture.U32[2]; + u32 Height = Texture.U32[3]; + + SourceP.x /= Width; + SourceP.y /= Height; + SourceDim.x /= Width; + SourceDim.y /= Height; + + v2 Source00 = SourceP; + v2 Source01 = SourceP + V2(0, SourceDim.y); + v2 Source10 = SourceP + V2(SourceDim.x, 0); + v2 Source11 = SourceP + SourceDim; + + s32 BaseVertex = Commands->QuadVertexCount; + + quad_vertex *Vertex = Commands->QuadVertexBase + Commands->QuadVertexCount++; + Vertex->P = P00; + Vertex->SourceP = Source00; + Vertex->TextureIndex = TextureIndex; + Vertex->Color = PackV4ToU32(Color00); + Vertex->ToCenter = Center - P00; + Vertex->HalfSize = HalfSize; + Vertex->CornerRadius = CornerRadius; + Vertex->EdgeSoftness = EdgeSoftness; + Vertex->BorderThickness = BorderThickness; + + Vertex = Commands->QuadVertexBase + Commands->QuadVertexCount++; + Vertex->P = P10; + Vertex->SourceP = Source10; + Vertex->TextureIndex = TextureIndex; + Vertex->Color = PackV4ToU32(Color10); + Vertex->ToCenter = Center - P10; + Vertex->HalfSize = HalfSize; + Vertex->CornerRadius = CornerRadius; + Vertex->EdgeSoftness = EdgeSoftness; + Vertex->BorderThickness = BorderThickness; + + Vertex = Commands->QuadVertexBase + Commands->QuadVertexCount++; + Vertex->P = P01; + Vertex->SourceP = Source01; + Vertex->TextureIndex = TextureIndex; + Vertex->Color = PackV4ToU32(Color01); + Vertex->ToCenter = Center - P01; + Vertex->HalfSize = HalfSize; + Vertex->CornerRadius = CornerRadius; + Vertex->EdgeSoftness = EdgeSoftness; + Vertex->BorderThickness = BorderThickness; + + Vertex = Commands->QuadVertexBase + Commands->QuadVertexCount++; + Vertex->P = P11; + Vertex->SourceP = Source11; + Vertex->TextureIndex = TextureIndex; + Vertex->Color = PackV4ToU32(Color11); + Vertex->ToCenter = Center - P11; + Vertex->HalfSize = HalfSize; + Vertex->CornerRadius = CornerRadius; + Vertex->EdgeSoftness = EdgeSoftness; + Vertex->BorderThickness = BorderThickness; + + Commands->QuadIndexBase[Commands->QuadIndexCount++] = BaseVertex + 0; + Commands->QuadIndexBase[Commands->QuadIndexCount++] = BaseVertex + 3; + Commands->QuadIndexBase[Commands->QuadIndexCount++] = BaseVertex + 1; + Commands->QuadIndexBase[Commands->QuadIndexCount++] = BaseVertex + 0; + Commands->QuadIndexBase[Commands->QuadIndexCount++] = BaseVertex + 2; + Commands->QuadIndexBase[Commands->QuadIndexCount++] = BaseVertex + 3; + + ++Command->QuadCount; + } +#if 0 + else + { + int BreakMe = 0; + } +#endif +} + +inline void PushQuad(render_group *Group, v2 P, v2 Dim, + v4 Color00, v4 Color01, v4 Color10, v4 Color11, + r32 CornerRadius, r32 EdgeSoftness, r32 BorderThickness) +{ + PushTexturedQuad(Group, P, Dim, V2(0, 0), V2(0, 0), Color00, Color01, Color10, Color11, + CornerRadius, EdgeSoftness, BorderThickness, Group->Commands->WhiteTexture); +} + +inline void PushQuad(render_group *Group, v2 P, v2 Dim, + v4 Color, + r32 CornerRadius, r32 EdgeSoftness, r32 BorderThickness) +{ + PushTexturedQuad(Group, P, Dim, V2(0, 0), V2(0, 0), Color, Color, Color, Color, + CornerRadius, EdgeSoftness, BorderThickness, Group->Commands->WhiteTexture); +} + +inline void PushClip(render_group *Group, range2_r32 Clip) +{ + Assert(Group->ClipStackUsed + 1 < ArrayCount(Group->ClipStack)); + + Group->ClipStack[++Group->ClipStackUsed] = Clip; +} + +inline void PopClip(render_group *Group) +{ + Assert(Group->ClipStackUsed > 0); + + Group->ClipStack[--Group->ClipStackUsed]; +} \ No newline at end of file diff --git a/code/vn_render.h b/code/vn_render.h new file mode 100644 index 0000000..eae4e40 --- /dev/null +++ b/code/vn_render.h @@ -0,0 +1,91 @@ +/* date = April 26th 2023 11:04 pm */ + +#ifndef VN_RENDER_H +#define VN_RENDER_H + +#define MAX_BOUND_TEXTURES 16 +#define MAX_QUAD_COUNT 16*1024 + +#define ColorFromHex(Value) V4((((Value) >> 24) & 0xFF) / 255.0, (((Value) >> 16) & 0xFF) / 255.0, (((Value) >> 8) & 0xFF) / 255.0, (((Value) >> 0) & 0xFF) / 255.0) + +read_only v4 Color_Black = V4(0, 0, 0, 1); +read_only v4 Color_White = V4(1, 1, 1, 1); +read_only v4 Color_Grey = V4(0.5, 0.5, 0.5, 1); +read_only v4 Color_Red = V4(1, 0, 0, 1); +read_only v4 Color_Green = V4(0, 1, 0, 1); +read_only v4 Color_Blue = V4(0, 0, 1, 1); +read_only v4 Color_Yellow = V4(1, 1, 0, 1); +read_only v4 Color_Magenta = V4(1, 0, 1, 1); +read_only v4 Color_Cyan = V4(0, 1, 1, 1); + +enum render_texture_format +{ + Render_TextureFormat_R8, + Render_TextureFormat_RGB8, + Render_TextureFormat_RGBA8, +}; + +struct render_handle +{ + union + { + u64 U64[4]; + u32 U32[8]; + }; +}; + +enum render_command_type +{ + Render_Command_render_command_clear, + Render_Command_render_command_quads, + Render_Command_render_command_clip, +}; + +struct render_command_clear +{ + v3 Color; +}; + +struct render_command_quads +{ + u64 QuadCount; + u64 QuadBufferIndex; + + s32 TexturesUsed; + render_handle Textures[16]; +}; + +struct render_command_clip +{ + range2_r32 ClipRect; +}; + +struct render_command_header +{ + render_command_type Type; +}; + +struct render_group +{ + struct vn_render_commands *Commands; + + render_command_header *CurrentCommand; + + range2_r32 ClipStack[64]; + s32 ClipStackUsed; +}; + +struct quad_vertex +{ + v2 P; + v2 SourceP; + u32 TextureIndex; + u32 Color; + v2 ToCenter; // sixten: ToCenter = Center - P + v2 HalfSize; + r32 CornerRadius; + r32 EdgeSoftness; + r32 BorderThickness; +}; + +#endif //VN_RENDER_H diff --git a/code/vn_string.h b/code/vn_string.h new file mode 100644 index 0000000..6ba7fc2 --- /dev/null +++ b/code/vn_string.h @@ -0,0 +1,227 @@ +/* date = May 7th 2023 9:01 pm */ + +#ifndef VN_STRING_H +#define VN_STRING_H + +inline b32 IsWhitespace(char C) +{ + b32 Result = ((C == ' ') || + (C == '\n') || + (C == '\t') || + (C == '\r')); + return(Result); +} + +inline s64 StringLength(char *String) +{ + s64 Result = 0; + while(*String++) + { + ++Result; + } + return(Result); +} + +inline u64 HashString(string String) +{ + u64 Result = 5731; + for(s64 Index = 0; + Index < String.Count; + ++Index) + { + Result += String.Data[Index]; + Result ^= Result << 13; + Result ^= Result >> 7; + Result ^= Result << 17; + } + + return(Result); +} + +inline string MakeStringFromCString(char *Data) +{ + string Result = {StringLength(Data), (u8 *)Data}; + return(Result); +} + +inline s64 FirstIndexOf(string String, char Char) +{ + s64 Result = -1; + for(s64 Index = 0; + Index < String.Count; + ++Index) + { + if(String.Data[Index] == Char) + { + Result = Index; + break; + } + } + return(Result); +} + +inline s64 LastIndexOf(string String, char Char) +{ + s64 Result = -1; + for(s64 Index = String.Count-1; + Index >= 0; + --Index) + { + if(String.Data[Index] == Char) + { + Result = Index; + break; + } + } + return(Result); +} + +inline b32 AreEqual(string A, string B) +{ + b32 Result = false; + if(A.Count == B.Count) + { + Result = true; + + for(s64 Index = 0; + Index < A.Count; + ++Index) + { + if(A.Data[Index] != B.Data[Index]) + { + Result = false; + break; + } + } + } + + return(Result); +} + +static s64 UTF8FromCodepoint(u8 *Out, u32 Codepoint) +{ + s64 Length = 0; + if(Codepoint <= 0x7F) + { + Out[0] = (u8)Codepoint; + Length = 1; + } + else if(Codepoint <= 0x7FF) + { + Out[0] = (0x3 << 6) | ((Codepoint >> 6) & 0x1F); + Out[1] = 0x80 | ( Codepoint & 0x3F); + Length = 2; + } + else if(Codepoint <= 0xFFFF) + { + Out[0] = (0x7 << 5) | ((Codepoint >> 12) & 0x0F); + Out[1] = 0x80 | ((Codepoint >> 6) & 0x3F); + Out[2] = 0x80 | ( Codepoint & 0x3F); + Length = 3; + } + else if(Codepoint <= 0x10FFFF) + { + Out[0] = (0xF << 4) | ((Codepoint >> 12) & 0x07); + Out[1] = 0x80 | ((Codepoint >> 12) & 0x3F); + Out[2] = 0x80 | ((Codepoint >> 6) & 0x3F); + Out[3] = 0x80 | ( Codepoint & 0x3F); + Length = 4; + } + else + { + Out[0] = '?'; + Length = 1; + } + + return(Length); +} + +inline s64 GetCodepointSize(u32 Codepoint) +{ + s64 Result = 0; + if(Codepoint <= 0x7F) + { + Result = 1; + } + else if(Codepoint <= 0x7FF) + { + Result = 2; + } + else if(Codepoint <= 0xFFFF) + { + Result = 3; + } + else if(Codepoint <= 0x10FFFF) + { + Result = 4; + } + else + { + Result = 1; + } + return(Result); +} + +// sixten(TODO): Remove this forward decl. +inline string PushCString(struct memory_arena *Arena, char *CString); + +inline string StringFromCodepoint(struct memory_arena *Arena, u32 Codepoint) +{ + char Buffer[5] = {}; + UTF8FromCodepoint((u8 *)Buffer, Codepoint); + + string Result = PushCString(Arena, Buffer); + return(Result); +} + +struct utf8_iterator +{ + string Data; + s64 Index; + + u32 Codepoint; +}; + +inline void Advance(utf8_iterator *Iter) +{ + u8 *At = Iter->Data.Data + Iter->Index; + + if(Iter->Index < Iter->Data.Count) + { + if((At[0] & 0x80) == 0x00) + { + Iter->Codepoint = (At[0] & 0x7F); + Iter->Index += 1; + } + else if((At[0] & 0xE0) == 0xC0) + { + Iter->Codepoint = ((At[0] & 0x1F) << 6)|(At[1] & 0x3F); + Iter->Index += 2; + } + else if((At[0] & 0xF0) == 0xE0) + { + Iter->Codepoint = ((At[0] & 0x0F) << 12)|((At[1] & 0x3F) << 6)|(At[2] & 0x3F); + Iter->Index += 3; + } + else if((Iter->Data.Data[Iter->Index] & 0xF8) == 0xF0) + { + Iter->Codepoint = ((At[0] & 0x0F) << 18)|((At[1] & 0x3F) << 12)|((At[2] & 0x3F) << 6)|(At[3] & 0x3F); + Iter->Index += 4; + } + } + else + { + Iter->Codepoint = 0; + } +} + +inline utf8_iterator IterateUTF8String(string String) +{ + utf8_iterator Iter = {}; + Iter.Data = String; + Advance(&Iter); + + return(Iter); +} + +#endif //VN_STRING_H diff --git a/code/vn_text_op.h b/code/vn_text_op.h new file mode 100644 index 0000000..1aa7fa0 --- /dev/null +++ b/code/vn_text_op.h @@ -0,0 +1,295 @@ +/* date = May 7th 2023 7:16 pm */ + +#ifndef VN_TEXT_OP_H +#define VN_TEXT_OP_H + +struct text_op +{ + range_s64 Range; + string ReplaceString; + string CopyString; + s64 NewCursor; + s64 NewMark; +}; + +struct text_edit_state +{ + s64 Cursor; + s64 Mark; +}; + +typedef u32 text_action_flags; +enum +{ + TextActionFlag_WordScan = (1<<0), + TextActionFlag_KeepMark = (1<<1), + TextActionFlag_Delete = (1<<2), + TextActionFlag_ZeroDeltaWithSelection = (1<<3), + TextActionFlag_DeltaPicksSelectionSide = (1<<4), +}; + +struct text_action +{ + text_action_flags Flags; + s64 Delta; + u32 Codepoint; +}; + +inline b32 IsValid(text_action *Action) +{ + b32 Result = !(Action->Flags == 0 && Action->Delta == 0 && Action->Codepoint == 0); + return(Result); +} + +static text_action SingleLineTextActionFromEvent(platform_event *Event) +{ + text_action Action = {}; + + if(Event->Type == PlatformEvent_Text) + { + if(Event->Codepoint != '\n') + { + Action.Codepoint = Event->Codepoint; + } + } + else if(Event->Type == PlatformEvent_Press) + { + if(Event->Modifiers & PlatformModifier_Ctrl) + { + Action.Flags |= TextActionFlag_WordScan; + } + if(Event->Modifiers & PlatformModifier_Shift) + { + Action.Flags |= TextActionFlag_KeepMark; + } + + switch(Event->Key) + { + case Key_Right: + { + Action.Delta = +1; + Action.Flags |= TextActionFlag_DeltaPicksSelectionSide; + } break; + + case Key_Left: + { + Action.Delta = -1; + Action.Flags |= TextActionFlag_DeltaPicksSelectionSide; + } break; + + case Key_Home: { Action.Delta = S64_Min; } break; + case Key_End: { Action.Delta = S64_Max; } break; + + case Key_Backspace: + { + Action.Delta = -1; + Action.Flags |= TextActionFlag_Delete|TextActionFlag_ZeroDeltaWithSelection; + } break; + + case Key_Delete: + { + Action.Delta = +1; + Action.Flags |= TextActionFlag_Delete|TextActionFlag_ZeroDeltaWithSelection; + } break; + + default: {} break; + } + } + return(Action); +} + +inline s64 CodepointScan(string String, s64 Index, s64 Delta) +{ + s64 Result = 0; + if(Delta > 0) + { + while(Index < String.Count && Delta) + { + u8 Base = String.Data[Index]; + s64 ToMove = 0; + + if((Base & 0x80) == 0x00) + { + ToMove = 1; + } + else if((Base & 0xE0) == 0xC0) + { + ToMove = 2; + } + else if((Base & 0xF0) == 0xE0) + { + ToMove = 3; + } + else if((Base & 0xF8) == 0xF0) + { + ToMove = 4; + } + + Result += ToMove; + Index += ToMove; + + --Delta; + } + } + else + { + Index -= 1; + + while(Index >= 0 && (Delta != 0)) + { + u8 Base = String.Data[Index]; + if(((Base & 0x80) == 0) || !((Base & 0xC0) == 0x80)) + { + ++Delta; + } + + --Result; + --Index; + } + } + + return(Result); +} + +inline b32 IsWordBoundary(string String, s64 Index) +{ + b32 Result; + if(Index > 0) + { + Result = IsWhitespace(String.Data[Index - 1]) && !(IsWhitespace(String.Data[Index])); + } + else + { + Result = true; + } + return(Result); +} + +static s64 WordScan(string String, s64 Index, s64 Delta) +{ + s64 Result = 0; + + while(Delta) + { + if(Delta > 0) + { + ++Index; + ++Result; + + while(Index < String.Count && !IsWordBoundary(String, Index)) + { + ++Index; + ++Result; + } + + if(Index > String.Count) + { + Result -= Index - String.Count; + Index = String.Count; + goto End; + } + + --Delta; + } + else + { + --Index; + --Result; + + while(Index >= 0 && !IsWordBoundary(String, Index)) + { + --Index; + --Result; + } + + if(Index < 0) + { + Result -= Index; + Index = 0; + goto End; + } + + ++Delta; + } + } + + End: + return(Result); +} + +static text_op TextOpFromAction(memory_arena *Arena, string String, + text_edit_state *State, text_action *Action) +{ + text_op Op = {}; + + Op.NewCursor = State->Cursor; + Op.NewMark = State->Mark; + Op.Range = RangeS64(0, 0); + Op.ReplaceString = StrLit(""); + + s64 Delta = 0; + if(Action->Flags & TextActionFlag_WordScan) + { + Delta = WordScan(String, State->Cursor, Action->Delta); + } + else + { + Delta = CodepointScan(String, State->Cursor, Action->Delta); + } + + if(State->Cursor != State->Mark && + Action->Flags & TextActionFlag_ZeroDeltaWithSelection) + { + Delta = 0; + } + + if(State->Cursor != State->Mark && + Action->Flags & TextActionFlag_DeltaPicksSelectionSide && + !(Action->Flags & TextActionFlag_KeepMark)) + { + Delta = 0; + if(Action->Delta > 0) + { + Op.NewCursor = Maximum(State->Cursor, State->Mark); + } + else if(Action->Delta < 0) + { + Op.NewCursor = Minimum(State->Cursor, State->Mark); + } + } + else + { + Op.NewCursor = State->Cursor + Delta; + } + + if(Action->Flags & TextActionFlag_Delete) + { + Op.Range = RangeS64(Op.NewCursor, Op.NewMark); + Op.NewCursor = Op.NewMark = Op.Range.Min; + } + + if(Action->Codepoint != 0) + { + Op.ReplaceString = StringFromCodepoint(Arena, Action->Codepoint); + + if(State->Cursor == State->Mark) + { + Op.NewCursor += Op.ReplaceString.Count; + Op.Range = RangeS64(State->Cursor, State->Cursor); + } + else + { + Op.NewCursor += Op.ReplaceString.Count; + Op.Range = RangeS64(State->Cursor, State->Mark); + } + } + + if(!(Action->Flags & TextActionFlag_KeepMark)) + { + Op.NewMark = Op.NewCursor; + } + + return(Op); +} + +#endif //VN_TEXT_OP_H diff --git a/code/vn_theme_dark.h b/code/vn_theme_dark.h new file mode 100644 index 0000000..f8a5418 --- /dev/null +++ b/code/vn_theme_dark.h @@ -0,0 +1,11 @@ +/* date = May 7th 2023 0:16 pm */ + +#ifndef VN_THEME_DARK_H +#define VN_THEME_DARK_H + +read_only v4 Theme_TextColor = V4(0.8, 0.8, 0.8, 1.0); +read_only v4 Theme_BackgroundColor = V4(0.1, 0.1, 0.1, 1.0); +read_only v4 Theme_BorderColor = V4(0.3, 0.3, 0.3, 1.0); +read_only v4 Theme_HighlightBorderColor = V4(0.6, 0.3, 0.1, 1.0); + +#endif //VN_THEME_DARK_H diff --git a/code/vn_thread_context.h b/code/vn_thread_context.h new file mode 100644 index 0000000..8896b7f --- /dev/null +++ b/code/vn_thread_context.h @@ -0,0 +1,61 @@ +/* date = April 30th 2023 11:35 am */ + +#ifndef VN_THREAD_CONTEXT_H +#define VN_THREAD_CONTEXT_H + +#define per_thread __declspec(thread) + +struct thread_context +{ + memory_arena Arenas[2]; +}; + +per_thread thread_context *ThreadLocal_ThreadContext = 0; + +inline void SetThreadContext(thread_context *Context) +{ + ThreadLocal_ThreadContext = Context; +} + +inline thread_context *GetThreadContext(void) +{ + return(ThreadLocal_ThreadContext); +} + +static temporary_memory GetScratch(memory_arena **Conflicts, u64 ConflictCount) +{ + temporary_memory Scratch = {}; + thread_context *Context = GetThreadContext(); + + for(u64 ArenaIndex = 0; + ArenaIndex < ArrayCount(Context->Arenas); + ++ArenaIndex) + { + b32 FoundConflict = false; + for(u64 ConflictIndex = 0; + ConflictIndex < ConflictCount; + ++ConflictIndex) + { + memory_arena *Conflict = Conflicts[ConflictIndex]; + if(Conflict == Context->Arenas + ArenaIndex) + { + FoundConflict = true; + break; + } + } + + if(!FoundConflict) + { + Scratch = BeginTemporaryMemory(Context->Arenas + ArenaIndex); + break; + } + } + + Assert(Scratch.Arena); + + return(Scratch); +} + +#define ReleaseScratch(Scratch) EndTemporaryMemory(Scratch) + +#endif //VN_THREAD_CONTEXT_H diff --git a/code/vn_types.h b/code/vn_types.h new file mode 100644 index 0000000..0d26acc --- /dev/null +++ b/code/vn_types.h @@ -0,0 +1,198 @@ +/* date = April 26th 2023 4:55 pm */ + +#ifndef VN_TYPES_H +#define VN_TYPES_H + +#include +#include +#include + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef int8_t s8; +typedef int16_t s16; +typedef int32_t s32; +typedef int64_t s64; +typedef float r32; +typedef double r64; +typedef u8 b8; +typedef u16 b16; +typedef u32 b32; +typedef uintptr_t umm; +typedef intptr_t smm; + +#define U8_Min 0x00 +#define U8_Max 0xFF +#define U16_Min 0x0000 +#define U16_Max 0xFFFF +#define U32_Min 0x00000000 +#define U32_Max 0xFFFFFFFF +#define U64_Min 0x0000000000000000 +#define U64_Max 0xFFFFFFFFFFFFFFFF +#define S8_Min 0x80 +#define S8_Max 0x7F +#define S16_Min 0x8000 +#define S16_Max 0x7FFF +#define S32_Min 0x80000000 +#define S32_Max 0x7FFFFFFF +#define S64_Min 0x8000000000000000 +#define S64_Max 0x7FFFFFFFFFFFFFFF + +struct string +{ + s64 Count; + u8 *Data; +}; + +struct string16 +{ + s64 Count; + s16 *Data; +}; + +typedef string buffer; +typedef string16 buffer16; + +#define StrLit(String) MakeString(String, ArrayCount(String) - 1) +#define Str16Lit(String) MakeString16(String, ArrayCount(String) - 1) + +inline string MakeString(char *Data, s64 Count) +{ + string Result = {Count, (u8 *)Data}; + return(Result); +} + +inline string MakeString16(wchar_t *Data, s64 Count) +{ + string Result = {Count, (u8 *)Data}; + return(Result); +} + +struct v2s +{ + s32 x, y; +}; + +inline v2s V2S(s32 x, s32 y) +{ + v2s Result = {x, y}; + return(Result); +} + +struct v2u +{ + u32 x, y; +}; + +inline v2u V2U(u32 x, u32 y) +{ + v2u Result = {x, y}; + return(Result); +} + +struct v2 +{ + union + { + struct + { + r32 x, y; + }; + r32 E[2]; + }; +}; + +inline v2 V2(r32 x, r32 y) +{ + v2 Result = {x, y}; + return(Result); +} + +inline v2 V2(v2s V) +{ + v2 Result = {(r32)V.x, (r32)V.y}; + return(Result); +} + +struct v3 +{ + union + { + struct + { + r32 x, y, z; + }; + struct + { + r32 r, g, b; + }; + }; +}; + +inline v3 V3(r32 x, r32 y, r32 z) +{ + v3 Result = {x, y, z}; + return(Result); +} + +struct v4 +{ + union + { + struct + { + r32 x, y, z, w; + }; + struct + { + r32 r, g, b, a; + }; + }; +}; + +inline v4 V4(r32 x, r32 y, r32 z, r32 w) +{ + v4 Result = {x, y, z, w}; + return(Result); +} + +enum axis2 +{ + Axis2_X, + Axis2_Y, + Axis2_Count, +}; + +inline axis2 Opposite(axis2 Axis) +{ + axis2 Result = (axis2)(!(u32)Axis); + return(Result); +} + +struct range_s64 +{ + s64 Min; + s64 Max; +}; + +struct range_r32 +{ + r32 Min; + r32 Max; +}; + +struct range2_r32 +{ + v2 Min; + v2 Max; +}; + +struct ticket_mutex +{ + u64 volatile Ticket; + u64 volatile Serving; +}; + +#endif //VN_TYPES_H diff --git a/code/vn_ui.cpp b/code/vn_ui.cpp new file mode 100644 index 0000000..996cdef --- /dev/null +++ b/code/vn_ui.cpp @@ -0,0 +1,842 @@ +per_thread ui *ThreadLocal_UI; + +inline void UI_SetState(ui *UI) +{ + ThreadLocal_UI = UI; +} + +inline ui *UI_GetState(void) +{ + return(ThreadLocal_UI); +} + +inline ui_key UI_GetHot(void) +{ + ui *UI = UI_GetState(); + return(UI->Hot); +} + +inline ui_key UI_GetActive(void) +{ + ui *UI = UI_GetState(); + return(UI->Active); +} + +inline void UI_SetDragStartP(v2 P) +{ + ui *UI = UI_GetState(); + UI->DragStartP = P; +} + +inline void UI_UpdateDragStartP(void) +{ + ui *UI = UI_GetState(); + UI->DragStartP = UI->MouseP; +} + +inline v2 UI_GetDragStartP(void) +{ + ui *UI = UI_GetState(); + return(UI->DragStartP); +} + +inline void UI_StoreDragV2(v2 DragData) +{ + ui *UI = UI_GetState(); + *(v2 *)&UI->DragData = DragData; +} + +inline v2 UI_GetDragV2(void) +{ + ui *UI = UI_GetState(); + v2 Result = *(v2 *)UI->DragData; + return(Result); +} + +inline void UI_StoreDragR32(r32 DragData) +{ + ui *UI = UI_GetState(); + *(r32 *)&UI->DragData = DragData; +} + +inline r32 UI_GetDragR32(void) +{ + ui *UI = UI_GetState(); + r32 Result = *(r32 *)UI->DragData; + return(Result); +} + +inline void UI_StoreDragPayload(void *Source) +{ + ui *UI = UI_GetState(); + Copy(UI->DragData, Source, sizeof(UI->DragData)); +} + +inline void UI_GetDragPayload(void *Dest) +{ + ui *UI = UI_GetState(); + Copy(Dest, UI->DragData, sizeof(UI->DragData)); +} + +inline void UI_StoreDragPointer(void *Data) +{ + ui *UI = UI_GetState(); + *(void **)&UI->DragData = Data; +} + +inline void *UI_GetDragDataPointer(void) +{ + ui *UI = UI_GetState(); + void *Result = *(void **)UI->DragData; + return(Result); +} + +inline ui_key UI_EmptyKey(void) +{ + ui_key Key = {}; + return(Key); +} + +inline ui_key UI_SeedKey(ui_key Key, ui_key Seed) +{ + ui_key Result = {((Key.Value + Seed.Value) << 5) + Key.Value}; + return(Result); +} + +inline ui_key UI_GenerateKeyFromString(string String) +{ + ui_key Key; + if(String.Count) + { + Key.Value = HashString(String); + } + else + { + Key = UI_EmptyKey(); + } + + return(Key); +} + +static string UI_GetBoxNameByKey(ui_key Key) +{ + ui *UI = UI_GetState(); + string Result = StrLit("Empty or Not Found"); + + if(!AreEqual(Key, UI_EmptyKey())) + { + for(s32 BucketIndex = 0; + BucketIndex < ArrayCount(UI->BoxBuckets); + ++BucketIndex) + { + ui_box_bucket *Bucket = UI->BoxBuckets + BucketIndex; + for(ui_box *Box = Bucket->First; + Box != 0; + Box = Box->HashNext) + { + if(AreEqual(Key, Box->Key)) + { + Result = Box->String; + goto FoundName; + } + } + } + } + + FoundName: + return(Result); +} + +inline ui_box *UI_GetBoxByKey(ui *UI, ui_key Key) +{ + u64 Hash = Key.Value; + u64 Slot = Hash % ArrayCount(UI->BoxBuckets); + + ui_box_bucket *Bucket = UI->BoxBuckets + Slot; + + ui_box *Result = 0; + if(!AreEqual(Key, UI_EmptyKey())) + { + for(ui_box *Box = Bucket->First; + Box != 0; + Box = Box->HashNext) + { + if(AreEqual(Box->Key, Key)) + { + Result = Box; + break; + } + } + } + + if(!Result) + { + // sixten: Check if we have already allocated a box + if(DLLIsEmpty(UI->FirstFreeBox)) + { + // sixten: If not, simply allocate one + Result = PushStruct(&UI->Arena, ui_box); + } + else + { + // sixten: If there exists an already allocated, remove it from the free list and use it. + Result = UI->FirstFreeBox; + DLLRemove_NP(UI->FirstFreeBox, UI->LastFreeBox, Result, HashNext, HashPrev); + } + + // sixten: Insert the box into the hashmap. + DLLInsertLast_NP(Bucket->First, Bucket->Last, Result, HashNext, HashPrev); + } + + Result->Key = Key; + + return(Result); +} + +#include "generated/vn_generated_ui.cpp" + +inline ui_box *UI_MakeBox(ui_box_flags Flags, string String) +{ + ui *UI = UI_GetState(); + + ui_box *Parent = UI_TopParent(); + + ui_key BaseKey = UI_GenerateKeyFromString(String); + ui_key Seed = UI_SeedKey(BaseKey, Parent ? Parent->Seed : UI_EmptyKey()); + ui_key Key = BaseKey.Value ? Seed : UI_EmptyKey(); + + // sixten: Check for duplicate keys. +#if VN_SLOW + if(Parent && !AreEqual(Key, UI_EmptyKey())) + { + for(ui_box *Child = Parent->First; + Child != 0; + Child = Child->Next) + { + Assert(!AreEqual(Child->Key, Key)); + } + } +#endif + + ui_box *Box = UI_GetBoxByKey(UI, Key); + Box->Seed = Seed; + + Box->First = Box->Last = Box->Next = Box->Prev = Box->Parent = 0; + + Box->ComputedRelativeP = V2(0, 0); + Box->ComputedDim = V2(0, 0); + + Box->LastFrameTouched = UI->CurrentFrame; + + Box->Flags = Flags; + Box->String = PushString(&UI->FrameArena, String); + + UI_ApplyStyles(Box); + + if(Parent) + { + DLLInsertLast(Parent->First, Parent->Last, Box); + } + + return(Box); +} + +inline ui_box *UI_MakeBoxF(ui_box_flags Flags, char *Format, ...) +{ + ui *UI = UI_GetState(); + + // sixten(TODO): Allocate on scratch, copy to frame arena + // (alternatively keep two versions of UI_MakeBox, to make sure everything works) + + va_list Arguments; + va_start(Arguments, Format); + string String = PushFormatVariadic(&UI->FrameArena, Format, Arguments); + va_end(Arguments); + + ui_box *Box = UI_MakeBox(Flags, String); + return(Box); +} + +inline void UI_SetNextHot(ui_key Key) +{ + ui *UI = UI_GetState(); + if(!UI->NextHotSet) + { + UI->NextHot = Key; + UI->NextHotSet = true; + } +} + +inline b32 UI_ChildrenContainsP(ui_box *Box, v2 P) +{ + b32 Result = false; + + if(Box->Flags & UI_BoxFlag_Clickable && InRange(Box->Rect, P)) + { + Result = true; + } + else + { + if(Box->First) + { + Result = UI_ChildrenContainsP(Box->First, P); + } + + if(!Result) + { + if(Box->Next) + { + Result = UI_ChildrenContainsP(Box->Next, P); + } + } + } + + return(Result); +} + +static ui_signal UI_SignalFromBox(ui_box *Box) +{ + ui *UI = UI_GetState(); + + ui_signal Signal = {}; + + Signal.Hovering = InRange(Box->Rect, UI->MouseP) && + !(Box->First && UI_ChildrenContainsP(Box->First, UI->MouseP)) && + !(Box->Next && UI_ChildrenContainsP(Box->Next, UI->MouseP)); + + // sixten: Make sure the tooltip is not overlapping. + { + // sixten: Are we the tooltip? + b32 FoundTooltip = false; + for(ui_box *Parent = Box->Parent; + Parent != 0; + Parent = Parent->Parent) + { + if(Parent == UI->TooltipNode) + { + FoundTooltip = true; + break; + } + } + + if(!FoundTooltip && UI->TooltipNode->First) + { + Signal.Hovering &= ~UI_ChildrenContainsP(UI->TooltipNode->First, UI->MouseP); + } + } + + if(Box->Flags & UI_BoxFlag_Clickable) + { + if(AreEqual(UI->Active, Box->Key)) + { + if(Platform_KeyRelease(UI->EventList, Key_MouseLeft)) + { + Signal.Clicked = Signal.Hovering; + Signal.Released = true; + + UI->Active = UI_EmptyKey(); + + if(!Signal.Hovering) + { + UI_SetNextHot(UI_EmptyKey()); + } + } + else + { + Signal.Dragging = true; + } + } + else + { + if(AreEqual(UI->Hot, Box->Key)) + { + if(Platform_KeyPress(UI->EventList, Key_MouseLeft)) + { + UI->Active = Box->Key; + UI->DragStartP = UI->MouseP; + + Signal.Dragging = true; + Signal.Pressed = true; + } + else if(!Signal.Hovering) + { + UI_SetNextHot(UI_EmptyKey()); + } + + if(Platform_KeyPress(UI->EventList, Key_MouseRight)) + { + Signal.PressedRight = true; + } + } + else + { + if(AreEqual(UI->Hot, UI_EmptyKey()) && Signal.Hovering) + { + UI_SetNextHot(Box->Key); + } + } + } + } + + Signal.MouseP = UI->MouseP; + Signal.DragDelta = UI->MouseP - UI->DragStartP; + Signal.Box = Box; + + return(Signal); +} + +static void UI_CalculateStandaloneSize(ui_box *Box, axis2 Axis, glyph_atlas *Atlas) +{ + for(ui_box *Child = Box->First; + Child != 0; + Child = Child->Next) + { + UI_CalculateStandaloneSize(Child, Axis, Atlas); + } + + if(Box->SemanticSize[Axis].Type == UI_SizeType_Pixels) + { + Box->ComputedDim.E[Axis] = Box->SemanticSize[Axis].Value; + } + else if(Box->SemanticSize[Axis].Type == UI_SizeType_TextContent) + { + if(Axis == Axis2_X) + { + Box->ComputedDim.E[Axis] = CalculateRasterizedTextWidth(Atlas, Box->Font, Box->FontSize, Box->String); + } + else if(Axis == Axis2_Y) + { + Box->ComputedDim.E[Axis] = CalculateRasterizedTextHeight(Atlas, Box->Font, Box->FontSize, Box->String); + } + else + { + InvalidCodepath; + } + + Box->ComputedDim.E[Axis] += Box->SemanticSize[Axis].Value; + } +} + +static void UI_CalculateUpwardsDependentSize(ui_box *Box, axis2 Axis) +{ + if(Box->SemanticSize[Axis].Type == UI_SizeType_PercentOfParent) + { + Box->ComputedDim.E[Axis] = Box->Parent->ComputedDim.E[Axis]*Box->SemanticSize[Axis].Value; + } + + for(ui_box *Child = Box->First; + Child != 0; + Child = Child->Next) + { + UI_CalculateUpwardsDependentSize(Child, Axis); + } +} + +static void UI_CalculateDownwardsDependentSize(ui_box *Box, axis2 Axis) +{ + for(ui_box *Child = Box->First; + Child != 0; + Child = Child->Next) + { + UI_CalculateDownwardsDependentSize(Child, Axis); + } + + if(Box->SemanticSize[Axis].Type == UI_SizeType_ChildrenSum) + { + for(ui_box *Child = Box->First; + Child; + Child = Child->Next) + { + if(Box->LayoutAxis == Axis) + { + Box->ComputedDim.E[Axis] += Child->ComputedDim.E[Axis]; + } + else + { + Box->ComputedDim.E[Axis] = Maximum(Box->ComputedDim.E[Axis], Child->ComputedDim.E[Axis]); + } + + Box->ComputedDim.E[Axis] *= Box->SemanticSize[Axis].Value; + } + } +} + +static void UI_SolveSizeViolations(ui_box *Box, axis2 Axis) +{ + r32 TotalSpace = 0; + r32 FixupBudget = 0; + + if(!(Box->Flags & (UI_BoxFlag_OverflowX<First; + Child != 0; + Child = Child->Next) + { + if(!(Child->Flags & (UI_BoxFlag_FloatingX<LayoutAxis) + { + TotalSpace += Child->ComputedDim.E[Axis]; + } + else + { + TotalSpace = Maximum(TotalSpace, Child->ComputedDim.E[Axis]); + } + FixupBudget += Child->ComputedDim.E[Axis] * (1 - Child->SemanticSize[Axis].Strictness); + } + } + + r32 Violation = TotalSpace - Box->ComputedDim.E[Axis]; + if(Violation > 0 && FixupBudget > 0) + { + for(ui_box *Child = Box->First; + Child != 0; + Child = Child->Next) + { + if(!(Child->Flags & (UI_BoxFlag_FloatingX<ComputedDim.E[Axis] * (1 - Child->SemanticSize[Axis].Strictness); + r32 ChildFixupSize = 0; + if(Axis == Box->LayoutAxis) + { + ChildFixupSize = ChildFixupBudget * (Violation / FixupBudget); + } + else + { + ChildFixupSize = Child->ComputedDim.E[Axis] - Box->ComputedDim.E[Axis]; + } + + ChildFixupSize = Clamp(ChildFixupSize, 0, ChildFixupBudget); + Child->ComputedDim.E[Axis] -= ChildFixupSize; + } + } + } + } + + if(Axis == Box->LayoutAxis) + { + r32 Position = 0; + for(ui_box *Child = Box->First; + Child; + Child = Child->Next) + { + if((Child->Flags & (UI_BoxFlag_FloatingX<ComputedRelativeP.E[Axis] = Child->FixedP.E[Axis]; + } + else + { + Child->ComputedRelativeP.E[Axis] = Position; + Position += Child->ComputedDim.E[Axis]; + } + } + } + else + { + for(ui_box *Child = Box->First; + Child; + Child = Child->Next) + { + if((Child->Flags & (UI_BoxFlag_FloatingX<ComputedRelativeP.E[Axis] = Child->FixedP.E[Axis]; + } + else + { + Child->ComputedRelativeP.E[Axis] = 0; + } + } + } + + for(ui_box *Child = Box->First; + Child; + Child = Child->Next) + { + Child->Rect.Min.E[Axis] = Box->Rect.Min.E[Axis] + Child->ComputedRelativeP.E[Axis]; + Child->Rect.Max.E[Axis] = Child->Rect.Min.E[Axis] + Child->ComputedDim.E[Axis]; + + UI_SolveSizeViolations(Child, Axis); + } +} + +inline void UI_BeginTooltip(void) +{ + ui *UI = UI_GetState(); + UI_PushParent(UI->TooltipNode); +} + +inline void UI_EndTooltip(void) +{ + UI_PopParent(); +} + +inline void UI_SetNextTooltip(void) +{ + ui *UI = UI_GetState(); + UI_SetNextParent(UI->TooltipNode); +} + +#define UI_Tooltip DeferLoop(UI_BeginTooltip(), UI_EndTooltip()) + +static void UI_DrawBox(ui_box *Box, render_group *Group, glyph_atlas *GlyphAtlas) +{ + ui *UI = UI_GetState(); + Box->HotTransition += ((r32)AreEqual(UI->Hot, Box->Key) - Box->HotTransition) * 0.6f; + Box->ActiveTransition += ((r32)AreEqual(UI->Active, Box->Key) - Box->ActiveTransition) * 0.6f; + + if(Box->Flags & UI_BoxFlag_DrawBackground) + { + PushQuad(Group, Box->Rect.Min, Box->ComputedDim, Box->BackgroundColor, Box->CornerRadius, 0, 0); + } + + if(Box->Flags & UI_BoxFlag_HotAnimation) + { + v4 Top = V4(1, 1, 1, 0.06F*Box->HotTransition); + v4 Bottom = V4(1, 1, 1, 0.0); + + PushQuad(Group, Box->Rect.Min, Box->ComputedDim, Top, Top, Bottom, Bottom, Box->CornerRadius, 0, 0); + } + + if(Box->Flags & UI_BoxFlag_ActiveAnimation) + { + v4 Top = V4(0, 0, 0, 0.5F*Box->ActiveTransition); + v4 Bottom = V4(0, 0, 0, 0.1F*Box->ActiveTransition); + + PushQuad(Group, Box->Rect.Min, Box->ComputedDim, Top, Top, Bottom, Bottom, Box->CornerRadius, 0, 0); + } + + if(Box->Flags & UI_BoxFlag_DrawText) + { + v2 TextDim = V2(CalculateRasterizedTextWidth(GlyphAtlas, Box->Font, Box->FontSize, Box->String), + CalculateRasterizedTextHeight(GlyphAtlas, Box->Font, Box->FontSize, Box->String)); + + v2 P = Box->Rect.Min + (Box->ComputedDim - TextDim)*0.5; + + PushText(Group, GlyphAtlas, Box->Font, P, Box->FontSize, Box->TextColor, Box->String); + } + + if(Box->DrawCallback) + { + Box->DrawCallback(Group, GlyphAtlas, Box, Box->DrawCallbackData); + } + +#if 0 // sixten: Render debug rects around boxes. + r32 R = (((Box->Key.Value >> 0) & ((1 << 22) - 1)) / (r32)((1 << 22) - 1)); + r32 G = (((Box->Key.Value >> 21) & ((1 << 22) - 1)) / (r32)((1 << 22) - 1)); + r32 B = (((Box->Key.Value >> 42) & ((1 << 22) - 1)) / (r32)((1 << 22) - 1)); + v4 Red = V4(R, G, B, 1); + PushQuad(Group, Box->Rect.Min, Box->ComputedDim, Red, Red, Red, Red, 0, 1.8, 1.8); +#endif + + if(Box->Flags & UI_BoxFlag_Clip) + { + PushClip(Group, Box->Rect); + } + + for(ui_box *Child = Box->First; + Child != 0; + Child = Child->Next) + { + if(Child->Flags & UI_BoxFlag_DrawDropShadow) + { + r32 ShadowRadius = 10; + v2 P = Child->Rect.Min - V2(ShadowRadius, ShadowRadius); + v2 Dim = Child->ComputedDim + V2(ShadowRadius, ShadowRadius)*2; + + v4 ShadowColor = V4(0, 0, 0, 0.7); + + PushQuad(Group, P, Dim, ShadowColor, 0, ShadowRadius, 0); + } + } + + for(ui_box *Child = Box->First; + Child != 0; + Child = Child->Next) + { + UI_DrawBox(Child, Group, GlyphAtlas); + } + + if(Box->Flags & UI_BoxFlag_Clip) + { + PopClip(Group); + } + + if(Box->Flags & UI_BoxFlag_DrawBorder) + { + PushQuad(Group, Box->Rect.Min, Box->ComputedDim, Box->BorderColor, Box->CornerRadius, 0.8, Box->BorderThickness); + } +} + +static void UI_BeginBuild(v2 ScreenDim) +{ + ui *UI = UI_GetState(); + + UI_PushParent(0); + UI_PushWidth(UI_Pixels(ScreenDim.x, 1)); + UI_PushHeight(UI_Pixels(ScreenDim.y, 1)); + UI_PushFixedX(0); + UI_PushFixedY(0); + UI_PushTextColor(Theme_TextColor); + UI_PushBackgroundColor(ColorFromHex(0x111111FF)); + UI_PushBorderColor(Theme_BorderColor); + UI_PushBorderThickness(1.8); + UI_PushLayoutAxis(Axis2_Y); + UI_PushCornerRadius(0); + UI_PushFont(Font_Regular); + UI_PushFontSize(15.0f); + + UI->RootNode = UI_MakeBox(UI_BoxFlag_DrawBackground, StrLit("UI Root Node")); + UI->Stacks.ParentStack[0] = UI->RootNode; + + UI->ContainerNode = UI_MakeBox(UI_BoxFlag_Clickable, StrLit("UI Container Node")); + UI->TooltipNode = UI_MakeBox(UI_BoxFlag_FloatingX|UI_BoxFlag_FloatingY, StrLit("UI Tooltip Node")); + + UI->Stacks.ParentStack[0] = UI->ContainerNode; + + UI->NextHotSet = false; +} + +static void UI_EndBuild(glyph_atlas *GlyphAtlas) +{ + ui *UI = UI_GetState(); + + if(UI->NextHotSet) + { + UI->Hot = UI->NextHot; + } + + UI_PopParent(); + UI_PopWidth(); + UI_PopHeight(); + UI_PopFixedX(); + UI_PopFixedY(); + UI_PopTextColor(); + UI_PopBackgroundColor(); + UI_PopBorderColor(); + UI_PopBorderThickness(); + UI_PopLayoutAxis(); + UI_PopCornerRadius(); + UI_PopFont(); + UI_PopFontSize(); + + UI_CalculateStandaloneSize(UI->RootNode, Axis2_X, GlyphAtlas); + UI_CalculateStandaloneSize(UI->RootNode, Axis2_Y, GlyphAtlas); + + UI_CalculateUpwardsDependentSize(UI->RootNode, Axis2_X); + UI_CalculateUpwardsDependentSize(UI->RootNode, Axis2_Y); + + UI_CalculateDownwardsDependentSize(UI->RootNode, Axis2_X); + UI_CalculateDownwardsDependentSize(UI->RootNode, Axis2_Y); + + UI_SolveSizeViolations(UI->RootNode, Axis2_X); + UI_SolveSizeViolations(UI->RootNode, Axis2_Y); +} + +static void UI_RenderFrame(render_group *Group, glyph_atlas *GlyphAtlas) +{ + ui *UI = UI_GetState(); + UI_DrawBox(UI->RootNode, Group, GlyphAtlas); +} + +inline void UI_ScanForHotAndActive(ui_box *Box, b32 *FoundHot, b32 *FoundActive) +{ + ui *UI = UI_GetState(); + + if(AreEqual(UI->Hot, Box->Key) && (Box->Flags & UI_BoxFlag_Clickable)) + { + *FoundHot = true; + } + if(AreEqual(UI->Active, Box->Key) && (Box->Flags & UI_BoxFlag_Clickable)) + { + *FoundActive = true; + } + + if(Box->First) + { + UI_ScanForHotAndActive(Box->First, FoundHot, FoundActive); + } + if(Box->Next) + { + UI_ScanForHotAndActive(Box->Next, FoundHot, FoundActive); + } +} + +static void UI_NewFrame(ui *UI, platform_event_list *EventList, v2 MouseP) +{ + UI_SetState(UI); + + if(UI->FrameMemory.Arena) + { + EndTemporaryMemory(UI->FrameMemory); + } + + UI->FrameMemory = BeginTemporaryMemory(&UI->FrameArena); + + UI->EventList = EventList; + UI->MouseP = MouseP; + + // sixten: Make sure that the hot and active boxes are valid. + if(UI->RootNode) + { + b32 FoundHot = false; + b32 FoundActive = false; + UI_ScanForHotAndActive(UI->RootNode, &FoundHot, &FoundActive); + + // sixten(TODO): Do we inform the builder code about this somehow? + if(!FoundHot) + { + UI->Hot = UI_EmptyKey(); + } + if(!FoundActive) + { + UI->Active = UI_EmptyKey(); + } + } + + // sixten: Prune uncached boxes. + ui_box_bucket *FirstBucket = UI->BoxBuckets; + for(ui_box *Box = FirstBucket->First; Box != 0;) + { + if(AreEqual(Box->Key, UI_EmptyKey())) + { + ui_box *ToRemove = Box; + Box = Box->HashNext; + + DLLRemove_NP(FirstBucket->First, FirstBucket->Last, ToRemove, HashNext, HashPrev); + *ToRemove = {}; + + DLLInsertLast_NP(UI->FirstFreeBox, UI->LastFreeBox, ToRemove, HashNext, HashPrev); + } + else + { + Box = Box->HashNext; + } + } + + // sixten: Prune any unused boxes. + for(s32 BucketIndex = 0; + BucketIndex < ArrayCount(UI->BoxBuckets); + ++BucketIndex) + { + ui_box_bucket *Bucket = UI->BoxBuckets + BucketIndex; + for(ui_box *Box = Bucket->First; Box != 0;) + { + if(Box->LastFrameTouched != UI->CurrentFrame) + { + ui_box *ToRemove = Box; + Box = Box->HashNext; + + DLLRemove_NP(Bucket->First, Bucket->Last, ToRemove, HashNext, HashPrev); + *ToRemove = {}; + + DLLInsertLast_NP(UI->FirstFreeBox, UI->LastFreeBox, ToRemove, HashNext, HashPrev); + } + else + { + Box = Box->HashNext; + } + } + } + + ++UI->CurrentFrame; +} diff --git a/code/vn_ui.h b/code/vn_ui.h new file mode 100644 index 0000000..62775e5 --- /dev/null +++ b/code/vn_ui.h @@ -0,0 +1,203 @@ +/* date = April 29th 2023 8:22 pm */ + +#ifndef VN_UI_H +#define VN_UI_H + +struct ui_key +{ + u64 Value; +}; + +inline b32 AreEqual(ui_key A, ui_key B) +{ + b32 Result = (A.Value == B.Value); + return(Result); +} + +enum +{ + UI_BoxFlag_Clickable = (1 << 0), + UI_BoxFlag_DrawText = (1 << 1), + UI_BoxFlag_DrawBorder = (1 << 2), + UI_BoxFlag_DrawBackground = (1 << 3), + UI_BoxFlag_DrawDropShadow = (1 << 4), + UI_BoxFlag_Clip = (1 << 5), + UI_BoxFlag_HotAnimation = (1 << 6), + UI_BoxFlag_ActiveAnimation = (1 << 7), + UI_BoxFlag_OverflowX = (1 << 8), + UI_BoxFlag_OverflowY = (1 << 9), + UI_BoxFlag_FloatingX = (1 << 10), + UI_BoxFlag_FloatingY = (1 << 11), +}; +typedef u32 ui_box_flags; + +enum ui_size_type +{ + UI_SizeType_Pixels, + UI_SizeType_TextContent, + UI_SizeType_PercentOfParent, + UI_SizeType_ChildrenSum, +}; + +struct ui_size +{ + ui_size_type Type; + r32 Value; + r32 Strictness; +}; + +inline ui_size UI_Pixels(r32 Value, r32 Strictness) +{ + ui_size Result = {UI_SizeType_Pixels, Value, Strictness}; + return(Result); +} + +inline ui_size UI_TextContent(r32 Value, r32 Strictness) +{ + ui_size Result = {UI_SizeType_TextContent, Value, Strictness}; + return(Result); +} + +inline ui_size UI_Percent(r32 Value, r32 Strictness) +{ + ui_size Result = {UI_SizeType_PercentOfParent, Value, Strictness}; + return(Result); +} + +inline ui_size UI_ChildrenSum(r32 Value, r32 Strictness) +{ + ui_size Result = {UI_SizeType_ChildrenSum, Value, Strictness}; + return(Result); +} + +typedef void ui_draw_callback(render_group *Group, glyph_atlas *Atlas, struct ui_box *Box, void *Data); + +struct ui_box +{ + ui_box *First; + ui_box *Last; + ui_box *Next; + ui_box *Prev; + ui_box *Parent; + + ui_box *HashNext; + ui_box *HashPrev; + + ui_key Key; + ui_key Seed; + u64 LastFrameTouched; + + ui_box_flags Flags; + string String; + ui_size SemanticSize[Axis2_Count]; + v2 FixedP; + v4 TextColor; + v4 BackgroundColor; + v4 BorderColor; + r32 BorderThickness; + axis2 LayoutAxis; + r32 CornerRadius; + font_id Font; + r32 FontSize; + + ui_draw_callback *DrawCallback; + void *DrawCallbackData; + + v2 ComputedRelativeP; + v2 ComputedDim; + + range2_r32 Rect; + + r32 HotTransition; + r32 ActiveTransition; +}; + +struct ui_box_bucket +{ + ui_box *First; + ui_box *Last; +}; + +struct ui_signal +{ + ui_box *Box; + v2 MouseP; + v2 DragDelta; + b8 Clicked; + b8 Pressed; + b8 PressedRight; + b8 Released; + b8 Hovering; + b8 Dragging; +}; + +#include "generated/vn_generated_ui.h" + +struct ui +{ + memory_arena Arena; + memory_arena FrameArena; + temporary_memory FrameMemory; + ui_box *FirstFreeBox; + ui_box *LastFreeBox; + + ui_box_bucket BoxBuckets[256]; + + ui_box *RootNode; + ui_box *TooltipNode; + ui_box *ContainerNode; + + u64 CurrentFrame; + ui_key Hot; + ui_key Active; + + ui_key NextHot; + b32 NextHotSet; + + platform_event_list *EventList; + v2 MouseP; + + u64 DragData[8]; + v2 DragStartP; + + ui_style_stacks Stacks; +}; + +//- sixten: State management +inline void UI_SetState(ui *UI); +inline ui *UI_GetState(void); + +inline ui_key UI_GetHot(void); +inline ui_key UI_GetActive(void); + +//- sixten: Drag helpers +inline void UI_SetDragStartP(v2 P); +inline void UI_UpdateDragStartP(void); +inline v2 UI_GetDragStartP(void); +inline void UI_StoreDragV2(v2 DragData); +inline v2 UI_GetDragV2(void); +inline void UI_StoreDragR32(r32 DragData); +inline r32 UI_GetDragR32(void); +inline void UI_StoreDragPayload(void *Data); // sixten(NOTE): Payload MUST be 64-bytes. +inline void UI_GetDragDataPayload(void *Data); +inline void UI_StoreDragPointer(void *Data); +inline void *UI_GetDragDataPointer(void); + +//- sixten: Key functions +inline ui_key UI_EmptyKey(void); +inline ui_key UI_SeedKey(ui_key Key, ui_key Seed); +inline ui_key UI_GenerateKeyFromString(string String); +static string UI_GetBoxNameByKey(ui_key Key); +inline ui_box *UI_GetBoxByKey(ui *UI, ui_key Key); + +//- sixten: Box creation +inline ui_box *UI_MakeBox(ui_box_flags Flags, string String); +inline ui_box *UI_MakeBoxF(ui_box_flags Flags, char *Format, ...); + +//- sixten: Building and rendering +static void UI_BeginBuild(ui *UI, v2 ScreenDim); +static void UI_EndBuild(glyph_atlas *GlyphAtlas); +static void UI_RenderFrame(render_group *RenderGroup, glyph_atlas *GlyphAtlas); +static void UI_NewFrame(ui *UI, platform_event_list *EventList, v2 MouseP); + +#endif //VN_UI_H diff --git a/code/vn_ui_utils.cpp b/code/vn_ui_utils.cpp new file mode 100644 index 0000000..5cb9e54 --- /dev/null +++ b/code/vn_ui_utils.cpp @@ -0,0 +1,166 @@ +// sixten: Rows and columns. +inline void UI_RowBegin(void) +{ + UI_SetNextLayoutAxis(Axis2_X); + ui_box *Box = UI_MakeBox(0, StrLit("")); + UI_PushParent(Box); +} + +inline void UI_RowEnd(void) +{ + UI_PopParent(); +} + +inline void UI_ColumnBegin(void) +{ + UI_SetNextLayoutAxis(Axis2_Y); + ui_box *Box = UI_MakeBox(0, StrLit("")); + UI_PushParent(Box); +} + +inline void UI_ColumnEnd(void) +{ + UI_PopParent(); +} + +#define UI_Row DeferLoop(UI_RowBegin(), UI_RowEnd()) +#define UI_Column DeferLoop(UI_ColumnBegin(), UI_ColumnEnd()) + +// sixten: Compositions +inline void UI_PushAxisSize(axis2 Axis, ui_size Size) +{ + if(Axis == Axis2_X) + { + UI_PushWidth(Size); + } + else + { + UI_PushHeight(Size); + } +} + +inline void UI_PopAxisSize(axis2 Axis) +{ + if(Axis == Axis2_X) + { + UI_PopWidth(); + } + else + { + UI_PopHeight(); + } +} + +inline void UI_SetNextAxisSize(axis2 Axis, ui_size Size) +{ + if(Axis == Axis2_X) + { + UI_SetNextWidth(Size); + } + else + { + UI_SetNextHeight(Size); + } +} + +#define UI_AxisSize(Axis, Size) DeferLoop(UI_PushAxisSize(Axis, Size), UI_PopAxisSize(Axis)) + +#define UI_Size(Width, Height) UI_Width(Width) UI_Height(Height) +#define UI_PushSize(Width, Height) UI_PushWidth(Width); UI_PushHeight(Height) +#define UI_PopSize() UI_PopWidth(); UI_PopHeight() +#define UI_SetNextSize(Width, Height) UI_SetNextWidth(Width); UI_SetNextHeight(Height) + +#define UI_FixedP(Value) UI_FixedX(Value.x) UI_FixedY(Value.y) +#define UI_SetNextFixedP(Value) UI_SetNextFixedX(Value.x); UI_SetNextFixedY(Value.y) + +// sixten: Spacing +static ui_box *UI_NamedSpacer(ui_size Size, string String) +{ + ui_box *Parent = UI_TopParent(); + UI_SetNextAxisSize(Parent->LayoutAxis, Size); + + ui_box *Box = UI_MakeBox(0, String); + return(Box); +} + +static ui_box *UI_NamedSpacerF(ui_size Size, char *Format, ...) +{ + temporary_memory Scratch = GetScratch(0, 0); + + va_list Arguments; + va_start(Arguments, Format); + string String = PushFormatVariadic(Scratch.Arena, Format, Arguments); + va_end(Arguments); + + ui_box *Box = UI_NamedSpacer(Size, String); + + ReleaseScratch(Scratch); + + return(Box); +} + +static void UI_Spacer(ui_size Size) +{ + UI_NamedSpacer(Size, StrLit("")); +} + +#define UI_Padding(Size) DeferLoop(UI_Spacer(Size), UI_Spacer(Size)) + +// sixten: Common widgets +static ui_box *UI_Label(string String) +{ + ui_box *Box = UI_MakeBox(UI_BoxFlag_DrawText, String); + return(Box); +} + +static ui_box *UI_LabelF(char *Format, ...) +{ + temporary_memory Scratch = GetScratch(0, 0); + + va_list Arguments; + va_start(Arguments, Format); + string String = PushFormatVariadic(Scratch.Arena, Format, Arguments); + va_end(Arguments); + + ui_box *Box = UI_MakeBox(UI_BoxFlag_DrawText, String); + + ReleaseScratch(Scratch); + + return(Box); +} + +static ui_signal UI_Button(string String) +{ + ui_box *Box = UI_MakeBox(UI_BoxFlag_DrawText| + UI_BoxFlag_DrawBackground| + UI_BoxFlag_DrawBorder| + UI_BoxFlag_HotAnimation| + UI_BoxFlag_ActiveAnimation| + UI_BoxFlag_Clickable, + String); + ui_signal Signal = UI_SignalFromBox(Box); + return(Signal); +} + +static ui_signal UI_ButtonF(char *Format, ...) +{ + temporary_memory Scratch = GetScratch(0, 0); + + va_list Arguments; + va_start(Arguments, Format); + string String = PushFormatVariadic(Scratch.Arena, Format, Arguments); + va_end(Arguments); + + ui_box *Box = UI_MakeBox(UI_BoxFlag_DrawText| + UI_BoxFlag_DrawBackground| + UI_BoxFlag_DrawBorder| + UI_BoxFlag_HotAnimation| + UI_BoxFlag_ActiveAnimation| + UI_BoxFlag_Clickable, + String); + ui_signal Signal = UI_SignalFromBox(Box); + + ReleaseScratch(Scratch); + + return(Signal); +} \ No newline at end of file diff --git a/code/vn_workspace.cpp b/code/vn_workspace.cpp new file mode 100644 index 0000000..1e76f6b --- /dev/null +++ b/code/vn_workspace.cpp @@ -0,0 +1,758 @@ +#include "vn_workspace_view.cpp" +#include "vn_workspace_editor.cpp" +#include "vn_workspace_commands.cpp" + +//- sixten: Commands +static void Workspace_IssueCommand(workspace *Workspace, workspace_command_sig *Sig, u64 Argument = 0) +{ + workspace_command *Result = 0; + + if(Workspace->FirstFreeCommand) + { + Result = Workspace->FirstFreeCommand; + DLLRemove(Workspace->FirstFreeCommand, Workspace->LastFreeCommand, Result); + } + + if(!Result) + { + Result = PushStruct(&Workspace->CommandArena, workspace_command); + } + + Result->Command = Sig; + Result->Argument = Argument; + DLLInsertLast(Workspace->FirstCommand, Workspace->LastCommand, Result); +} + +static void Workspace_ProcessCommands(workspace *Workspace) +{ + workspace_command *Command = Workspace->FirstCommand; + while(Command != 0) + { + Command->Command(Workspace, Command->Argument); + + workspace_command *ToRemove = Command; + Command = Command->Next; + + DLLRemove(Workspace->FirstCommand, Workspace->LastCommand, ToRemove); + ZeroSize(ToRemove, sizeof(workspace_command)); + DLLInsertLast(Workspace->FirstFreeCommand, Workspace->LastFreeCommand, ToRemove); + } +} + +static void Workspace_ProcessKeyBinds(workspace *Workspace) +{ + platform_event_list *EventList = Workspace->EventList; + + for(platform_event *Event = EventList->First; + Event != 0; + Event = Event->Next) + { + if(Event->Type == PlatformEvent_Press) + { + for(s32 KeybindIndex = 0; + KeybindIndex < Workspace->KeybindCount; + ++KeybindIndex) + { + workspace_keybind *Keybind = Workspace->Keybinds + KeybindIndex; + if((Event->Key == Keybind->Key) && (Event->Modifiers == Keybind->Modifiers)) + { + Workspace_IssueCommand(Workspace, Keybind->Command, Keybind->Argument); + } + } + } + } +} + + +//- sixten: Builder code +static ui_signal Workspace_BuildToolbarButton(workspace *Workspace, char *Text, toolbar_menu Menu) +{ + UI_SetNextWidth(UI_TextContent(20, 1)); + UI_SetNextHeight(UI_Pixels(30, 1)); + UI_SetNextCornerRadius(4); + UI_SetNextBackgroundColor(ColorFromHex(0x252728FF)); + + ui_box *Box = UI_MakeBoxF(UI_BoxFlag_DrawBackground | + UI_BoxFlag_DrawBorder | + UI_BoxFlag_DrawText | + UI_BoxFlag_HotAnimation | + UI_BoxFlag_ActiveAnimation | + UI_BoxFlag_Clickable, + Text); + + ui_signal Signal = UI_SignalFromBox(Box); + + if(Workspace->Menu == ToolbarMenu_None) + { + if(Signal.Clicked) + { + Workspace->Menu = Menu; + Workspace->MenuTransition = 0; + } + } + else + { + if(Signal.Hovering) + { + if(Workspace->Menu != Menu) + { + Workspace->MenuTransition = 0; + } + + Workspace->Menu = Menu; + Workspace->MenuP = V2(Box->Rect.Min.x, Box->Rect.Max.y); + } + } + + return(Signal); +} + +static ui_signal Workspace_BuildMenuItem(u32 Icon, char *Text, char *Shortcut) +{ + temporary_memory Scratch = GetScratch(0, 0); + + UI_SetNextLayoutAxis(Axis2_X); + + ui_box *Box = UI_MakeBoxF(UI_BoxFlag_DrawBackground | + UI_BoxFlag_DrawBorder | + UI_BoxFlag_HotAnimation | + UI_BoxFlag_ActiveAnimation | + UI_BoxFlag_Clickable, + "Menu Item %s", Text); + + UI_Parent(Box) + { + UI_Width(UI_Pixels(25, 1)) UI_Font(Font_Icons) UI_MakeBoxF(UI_BoxFlag_DrawText, "%U", Icon); + UI_Width(UI_TextContent(5, 1)) UI_MakeBoxF(UI_BoxFlag_DrawText, Text); + UI_Spacer(UI_Percent(1, 0)); + + UI_TextColor(V4(0.5, 0.5, 0.5, 1.0)) UI_Width(UI_TextContent(15, 1)) + UI_MakeBoxF(UI_BoxFlag_DrawText, Shortcut); + } + + ReleaseScratch(Scratch); + + ui_signal Signal = UI_SignalFromBox(Box); + return(Signal); +} + +static void Workspace_BuildToolbar(workspace *Workspace, r32 dtForFrame) +{ + UI_SetNextLayoutAxis(Axis2_X); + UI_SetNextHeight(UI_Pixels(30, 1)); + UI_SetNextBackgroundColor(Theme_BackgroundColor); + + ui_box *ToolbarBox = UI_MakeBoxF(UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawBorder, + "Workspace Toolbar"); + + UI_Parent(ToolbarBox) + { + Workspace_BuildToolbarButton(Workspace, "Panel", ToolbarMenu_Panel); + Workspace_BuildToolbarButton(Workspace, "View", ToolbarMenu_View); + Workspace_BuildToolbarButton(Workspace, "Window", ToolbarMenu_Window); + + UI_Spacer(UI_Percent(1, 0)); + } + + if(Workspace->Menu != ToolbarMenu_None) + { + r32 MenuTransition = Workspace->MenuTransition; + + UI_SetNextTooltip(); + UI_SetNextFixedX(Workspace->MenuP.x); + UI_SetNextFixedY(Workspace->MenuP.y); + UI_SetNextLayoutAxis(Axis2_Y); + UI_SetNextWidth(UI_Pixels(250, 1)); + UI_SetNextHeight(UI_ChildrenSum(MenuTransition, 1)); + ui_box *Dropdown = UI_MakeBoxF(UI_BoxFlag_DrawBackground | + UI_BoxFlag_DrawDropShadow | + UI_BoxFlag_Clip | + UI_BoxFlag_FloatingX | + UI_BoxFlag_FloatingY, + "Workspace Dropdown"); + + UI_Parent(Dropdown) + UI_BackgroundColor(V4(0.25, 0.25, 0.25, 1)) + UI_BorderColor(V4(0.45, 0.45, 0.45, 1)) + UI_CornerRadius(2) + UI_Size(UI_Percent(1, 1), UI_Pixels(25, 1)) + { + if(Workspace->Menu == ToolbarMenu_Panel) + { + if(Workspace_BuildMenuItem(FontIcon_ResizeHorizontal, "Split Horizontal", "Ctrl + P").Clicked) + { + Workspace_IssueCommand(Workspace, Workspace_Command_SplitPanelHorizontal); + Workspace->Menu = ToolbarMenu_None; + } + + if(Workspace_BuildMenuItem(FontIcon_ResizeVertical, "Split Vertical", "Ctrl + L").Clicked) + { + Workspace_IssueCommand(Workspace, Workspace_Command_SplitPanelVertical); + Workspace->Menu = ToolbarMenu_None; + } + } + else if(Workspace->Menu == ToolbarMenu_View) + { + workspace_panel *CurrentPanel = Workspace->CurrentPanel; + + if(Workspace_BuildMenuItem(FontIcon_None, "Welcome", "").Clicked) + { + workspace_view *NewView = Workspace_CreateNewView(Workspace_View_Startup, CurrentPanel); + DLLInsertLast(CurrentPanel->FirstView, CurrentPanel->LastView, NewView); + + Workspace->Menu = ToolbarMenu_None; + } + if(Workspace_BuildMenuItem(FontIcon_None, "Editor", "").Clicked) + { + workspace_view *NewView = Workspace_CreateNewView(Workspace_View_Editor, CurrentPanel); + DLLInsertLast(CurrentPanel->FirstView, CurrentPanel->LastView, NewView); + + Workspace->Menu = ToolbarMenu_None; + } + if(Workspace_BuildMenuItem(FontIcon_Wrench, "Settings", "").Clicked) + { + workspace_view *NewView = Workspace_CreateNewView(Workspace_View_Settings, CurrentPanel); + DLLInsertLast(CurrentPanel->FirstView, CurrentPanel->LastView, NewView); + + Workspace->Menu = ToolbarMenu_None; + } + } + else if(Workspace->Menu == ToolbarMenu_Window) + { + if(Workspace_BuildMenuItem(FontIcon_WindowMaximize, "ToggleFullscreen", "Alt + Enter").Clicked) + { + Platform.ToggleFullscreen(); + Workspace->Menu = ToolbarMenu_None; + } + } + + AnimationCurve_AnimateValueDirect(1, 0.1, &Workspace->MenuTransition); + } + + // sixten: Unless the mouse press was captured, we close the menu. + if(Platform_KeyPress(Workspace->EventList, Key_MouseLeft)) + { + Workspace->Menu = ToolbarMenu_None; + } + } +} + +//- sixten: Panels +static workspace_panel *Workspace_CreateNewPanel(workspace *Workspace, workspace_panel *Parent) +{ + workspace_panel *Result = 0; + + if(DLLIsEmpty(Workspace->FirstFreePanel)) + { + Result = PushStruct(&Workspace->PanelArena, workspace_panel); + } + else + { + Result = Workspace->FirstFreePanel; + DLLRemove(Workspace->FirstFreePanel, Workspace->LastFreePanel, Result); + + *Result = {}; + } + + Result->Parent = Parent; + + return(Result); +} + +static void Workspace_DeletePanel(workspace *Workspace, workspace_panel *Panel) +{ + if(Workspace->CurrentPanel == Panel) + { + Workspace->CurrentPanel = 0; + } + + *Panel = {}; + DLLInsertLast(Workspace->FirstFreePanel, Workspace->LastFreePanel, Panel); +} + +static void Workspace_SplitPanel(workspace *Workspace, workspace_panel *Panel, axis2 Axis) +{ + if(Panel) + { + workspace_panel *Parent = Panel->Parent; + + if(Parent && (Parent->SplitAxis == Axis)) + { + workspace_panel *NewPanel = Workspace_CreateNewPanel(Workspace, Parent); + NewPanel->PercentOfParent = Panel->PercentOfParent = Panel->PercentOfParent * 0.5; + + DLLInsert_NP(Parent->First, Parent->Last, Panel, NewPanel, Next, Prev); + } + else + { + workspace_panel *NewPanel = Workspace_CreateNewPanel(Workspace, Panel); + NewPanel->FirstView = Panel->FirstView; + NewPanel->LastView = Panel->LastView; + + // sixten: Update the parents of the children. + for(workspace_view *Child = NewPanel->FirstView; + Child != 0; + Child = Child->Next) + { + Child->Parent = NewPanel; + } + + NewPanel->CurrentView = Panel->CurrentView; + NewPanel->PercentOfParent = 0.5; + DLLInsertLast(Panel->First, Panel->Last, NewPanel); + + NewPanel = Workspace_CreateNewPanel(Workspace, Panel); + NewPanel->PercentOfParent = 0.5; + DLLInsertLast(Panel->First, Panel->Last, NewPanel); + + Panel->FirstView = 0; + Panel->LastView = 0; + Panel->CurrentView = 0; + Panel->SplitAxis = Axis; + + if(Workspace->CurrentPanel == Panel) + { + Workspace->CurrentPanel = Panel->First; + } + } + } +} + +inline void Workspace_BeginDrag(workspace *Workspace, workspace_drag_payload *Payload) +{ + // sixten(TODO): Right now, if you spam-click a draggable item, you can trigger this + // assertion. I don't know what I want to do about this at the moment, but I'm sure + // future me will have a soulution at hand. ^.^ + Assert(Workspace->DragPayloadState == Workspace_DragPayload_Inactive); + + Workspace->DragPayload = *Payload; + Workspace->DragPayloadState = Workspace_DragPayload_Active; +} + +inline b32 Workspace_GetDragPayload(workspace *Workspace, workspace_drag_payload *Dest) +{ + b32 Result = (Workspace->DragPayloadState != Workspace_DragPayload_Inactive); + *Dest = Workspace->DragPayload; + return(Result); +} + +static void Workspace_BuildTabItem(workspace *Workspace, workspace_panel *Panel, workspace_view *View) +{ + b32 ViewIsCurrent = (Panel->CurrentView == View); + b32 PanelIsCurrent = (Workspace->CurrentPanel == Panel); + + string Name = Workspace_GetViewName(View); + + v4 BackgroundColor = ViewIsCurrent ? (PanelIsCurrent ? Theme_HighlightBorderColor : Theme_BorderColor) : ColorFromHex(0x353738FF); + + UI_SetNextWidth(UI_ChildrenSum(1, 1)); + UI_SetNextHeight(UI_Percent(1, 1)); + UI_SetNextBackgroundColor(BackgroundColor); + UI_SetNextBorderColor(LinearBlend(UI_TopBackgroundColor(), Color_Grey, 0.5)); + UI_SetNextLayoutAxis(Axis2_X); + UI_SetNextCornerRadius(0.0); + + ui_box *TabBox = UI_MakeBoxF(UI_BoxFlag_DrawBackground | + UI_BoxFlag_DrawDropShadow | + UI_BoxFlag_HotAnimation | + UI_BoxFlag_ActiveAnimation | + UI_BoxFlag_Clickable, + "Workspace Panel Tab Item %S#%p", Name, View); + + UI_Parent(TabBox) + UI_Padding(UI_Pixels(5, 1)) + { + UI_Size(UI_TextContent(1, 1), UI_Percent(1, 1)) UI_Label(Name); + UI_Spacer(UI_Pixels(5, 1)); + + // sixten: Build close button + { + UI_SetNextFont(Font_Icons); + UI_SetNextSize(UI_TextContent(1, 1), UI_Percent(1, 1)); + ui_box *CloseBox = UI_MakeBoxF(UI_BoxFlag_DrawText|UI_BoxFlag_Clickable, "%U", FontIcon_Cancel); + + CloseBox->TextColor = LinearBlend(TabBox->BackgroundColor, Color_Black, + 0.3 - CloseBox->HotTransition*0.8); + + ui_signal Signal = UI_SignalFromBox(CloseBox); + if(Signal.Clicked) + { + DLLRemove(Panel->FirstView, Panel->LastView, View); + if(ViewIsCurrent) + { + Panel->CurrentView = Panel->FirstView; + } + + // sixten(TODO): Issue a "DeleteView" command. + } + } + } + + ui_signal Signal = UI_SignalFromBox(TabBox); + if(Signal.Clicked) + { + Workspace->CurrentPanel = Panel; + Panel->CurrentView = View; + } + + if(Signal.Dragging) + { + if(Signal.Pressed) + { + workspace_drag_payload Payload = {}; + Payload.View = View; + Payload.Key = TabBox->Key; + + Workspace_BeginDrag(Workspace, &Payload); + } + } +} + +static void Workspace_BuildPanelHeader(workspace *Workspace, workspace_panel *Panel) +{ + UI_SetNextLayoutAxis(Axis2_X); + UI_SetNextBackgroundColor(ColorFromHex(0x252728FF)); + UI_SetNextCornerRadius(0); + UI_SetNextSize(UI_Percent(1, 1), UI_Pixels(30, 1)); + + UI_Parent(UI_MakeBoxF(UI_BoxFlag_DrawBackground|UI_BoxFlag_Clip, "Workspace Panel Header")) + { + for(workspace_view *View = Panel->FirstView; + View != 0; + View = View->Next) + { + Workspace_BuildTabItem(Workspace, Panel, View); + } + + UI_Spacer(UI_Percent(1, 0)); + + // sixten: Panel Close Button + if(Panel != Workspace->RootPanel) + { + UI_SetNextSize(UI_Pixels(30, 1), UI_Pixels(30, 1)); + UI_SetNextFont(Font_Icons); + UI_SetNextBorderColor(ColorFromHex(0xA6514288)); + UI_SetNextBackgroundColor(ColorFromHex(0xC24630BB)); + UI_SetNextCornerRadius(4); + + ui_box *CloseBox = UI_MakeBoxF(UI_BoxFlag_HotAnimation | + UI_BoxFlag_ActiveAnimation | + //UI_BoxFlag_DrawBackground | + //UI_BoxFlag_DrawBorder | + UI_BoxFlag_DrawText | + UI_BoxFlag_Clickable, + "%U", FontIcon_Cancel); + + ui_signal Signal = UI_SignalFromBox(CloseBox); + if(Signal.Clicked) + { + Workspace_IssueCommand(Workspace, Workspace_Command_ClosePanel, PointerToU64(Panel)); + } + } + + UI_Spacer(UI_Pixels(2, 1)); + } +} + +static void Workspace_BuildPanel(workspace *Workspace, workspace_panel *Panel) +{ + // sixten: Fill remaining percent of parent. + workspace_panel *Parent = Panel->Parent; + if(Parent && Panel != Parent->First) + { + r32 TotalOfParent = 0; + for(workspace_panel *Child = Parent->First; + Child != 0; + Child = Child->Next) + { + if(Child != Panel) + { + TotalOfParent += Child->PercentOfParent; + } + } + + Panel->PercentOfParent = 1.0 - TotalOfParent; + } + + ui_box *PanelBox = UI_MakeBoxF(0, "Workspace Panel %p", Panel); + UI_Parent(PanelBox) + { + if(DLLIsEmpty(Panel->First)) + { + Workspace_BuildPanelHeader(Workspace, Panel); + + // sixten: Main body + { + b32 PanelIsCurrent = (Workspace->CurrentPanel == Panel); + + UI_PushSize(UI_Percent(1, 0), UI_Percent(1, 0)); + + r32 HighlightTransition = AnimationCurve_AnimateValueF(PanelIsCurrent, 0, 0.25, "Workspace Panel Highlight %p", Panel); + UI_SetNextBorderColor(LinearBlend(Theme_BorderColor, Theme_HighlightBorderColor, HighlightTransition)); + UI_SetNextBackgroundColor(Theme_BackgroundColor); + + ui_box *BodyBox = UI_MakeBoxF(UI_BoxFlag_DrawBorder | + UI_BoxFlag_DrawBackground | + UI_BoxFlag_Clip | + UI_BoxFlag_Clickable, + "Workspace Panel Body"); + UI_Parent(BodyBox) + { + if(Panel->FirstView) + { + if(!Panel->CurrentView) + { + Panel->CurrentView = Panel->FirstView; + } + } + else + { + UI_Column UI_Padding(UI_Percent(1, 0)) + UI_Height(UI_ChildrenSum(1, 1)) UI_Row UI_Padding(UI_Percent(1, 0)) + { + UI_Size(UI_TextContent(0, 1), UI_TextContent(10, 1)) + { + UI_LabelF("- empty -"); + } + } + } + + if(Panel->CurrentView) + { + ui_key CurrentActive = UI_GetActive(); + + Workspace_BuildView(Workspace, Panel->CurrentView); + + if(!AreEqual(CurrentActive, UI_GetActive())) + { + Workspace->CurrentPanel = Panel; + } + } + + // sixten: Draw dragged view overlay. + { + workspace_drag_payload Payload; + b32 DragActive = Workspace_GetDragPayload(Workspace, &Payload); + + b32 OverlayActive = (DragActive && (Payload.View->Parent != Panel) && + InRange(BodyBox->Rect, UI_GetState()->MouseP)); + + if(OverlayActive && Workspace->DragPayloadState == Workspace_DragPayload_Released) + { + // sixten(NOTE): I need to be careful here. If something else sees that a payload has + // been released and tries to act upon on it may lead to unwanted behaviour. Just + // keep that in mind. + + // sixten(NOTE): On that previous note, I could just: + Workspace->DragPayloadState = Workspace_DragPayload_Inactive; + + // sixten(TODO): Pull out the code below into separate functions. + + // sixten: Move view + workspace_view *View = Payload.View; + { + workspace_panel *OldParent = View->Parent; + b32 ViewWasCurrent = ((OldParent->CurrentView == View) && + (Workspace->CurrentPanel == OldParent)); + + // sixten: Detatch view + { + Assert(OldParent); + + if(OldParent->CurrentView == View) + { + OldParent->CurrentView = 0; + } + DLLRemove(OldParent->FirstView, OldParent->LastView, View); + } + + View->Parent = Panel; + DLLInsertLast(Panel->FirstView, Panel->LastView, View); + + if(ViewWasCurrent) + { + Workspace->CurrentPanel = Panel; + Panel->CurrentView = View; + } + } + } + + r32 OverlayTransition = AnimationCurve_AnimateValueF(OverlayActive, 0, 0.25, "Panel Drag Overlay %p", Panel); + + v4 OverlayColor = LinearBlend(Color_Grey, Theme_HighlightBorderColor, 0.75); + OverlayColor.a = 0.5*OverlayTransition; + + UI_SetNextBackgroundColor(OverlayColor); + UI_SetNextSize(UI_Percent(1, 1), UI_Percent(1, 1)); + + UI_MakeBoxF(UI_BoxFlag_DrawBackground | + UI_BoxFlag_FloatingX | + UI_BoxFlag_FloatingY, + "Workspace Panel Drag Hover"); + } + } + + ui_signal Signal = UI_SignalFromBox(BodyBox); + if(Signal.Pressed) + { + Workspace->CurrentPanel = Panel; + } + + UI_PopSize(); + } + } + else + { + UI_SetNextSize(UI_Percent(1, 0), UI_Percent(1, 0)); + UI_SetNextLayoutAxis(Panel->SplitAxis); + + UI_Parent(UI_MakeBoxF(0, "")) + { + s32 ChildCount = 0; + for(workspace_panel *Child = Panel->First; + Child != 0; + Child = Child->Next) + { + ++ChildCount; + } + + v2 PanelDim = DimOfRange(PanelBox->Rect); + + r32 PaddingSize = 5; + r32 PaddedSpace = (ChildCount - 1)*PaddingSize; + r32 PercentPaddedSpace = PaddedSpace / PanelDim.E[Panel->SplitAxis]; + r32 SizeScalar = 1.0 - PercentPaddedSpace; + + for(workspace_panel *Child = Panel->First; + Child != 0; + Child = Child->Next) + { + UI_SetNextAxisSize(Panel->SplitAxis, UI_Percent(Child->PercentOfParent*SizeScalar, 0)); + UI_SetNextAxisSize(Opposite(Panel->SplitAxis), UI_Percent(1, 0)); + Workspace_BuildPanel(Workspace, Child); + + if(Child->Next) + { + UI_SetNextAxisSize(Panel->SplitAxis, UI_Pixels(PaddingSize, 1)); + UI_SetNextAxisSize(Opposite(Panel->SplitAxis), UI_Percent(1, 0)); + ui_box *DragBox = UI_MakeBoxF(UI_BoxFlag_Clickable, "Workspace Panel Drag %p", Child); + + ui_signal Signal = UI_SignalFromBox(DragBox); + if(Signal.Hovering || Signal.Dragging) + { + Platform.SetCursor((Panel->SplitAxis == Axis2_X) ? + PlatformCursor_ArrowHorizontal : + PlatformCursor_ArrowVertical); + } + + if(Signal.Dragging) + { + if(Signal.Pressed) + { + UI_StoreDragR32(Child->PercentOfParent); + } + + r32 Delta = Signal.DragDelta.E[Panel->SplitAxis]/PanelDim.E[Panel->SplitAxis]; + + r32 StartOffset = UI_GetDragR32(); + r32 EndOffset = StartOffset + Delta; + + Child->PercentOfParent = Clamp(EndOffset, 0.05, 0.95); + } + } + } + } + } + } +} + +static void Workspace_BuildDragPayload(workspace *Workspace, vn_input *Input) +{ + workspace_drag_payload Payload; + if(Workspace_GetDragPayload(Workspace, &Payload)) + { + if(Workspace->DragPayloadState == Workspace_DragPayload_Released) + { + Workspace->DragPayloadState = Workspace_DragPayload_Inactive; + } + + if(AreEqual(Payload.Key, UI_GetActive())) + { + workspace_view *DraggedView = Payload.View; + UI_Tooltip + { + UI_SetNextWidth(UI_TextContent(5, 1)); + UI_SetNextHeight(UI_TextContent(5, 1)); + + UI_SetNextFixedX(Input->MouseP.x + 10); + UI_SetNextFixedY(Input->MouseP.y); + + UI_MakeBox(UI_BoxFlag_DrawBorder | + UI_BoxFlag_DrawBackground | + UI_BoxFlag_DrawText | + UI_BoxFlag_FloatingX | + UI_BoxFlag_FloatingY, + Workspace_GetViewName(DraggedView)); + } + } + else + { + if(Workspace->DragPayloadState == Workspace_DragPayload_Active) + { + Workspace->DragPayloadState = Workspace_DragPayload_Released; + } + } + } +} + +//- sixten: Workspace +static void Workspace_Init(workspace *Workspace) +{ + Workspace->RootPanel = Workspace->CurrentPanel = Workspace_CreateNewPanel(Workspace, 0); + + // sixten(TEMP): Add mock views. + { + workspace_view *View1 = Workspace_CreateNewView(Workspace_View_Startup, Workspace->RootPanel); + DLLInsertLast(Workspace->RootPanel->FirstView, Workspace->RootPanel->LastView, View1); + } + + // sixten: Setup keybinds + { +#define BIND_COMMAND(...) Workspace->Keybinds[Workspace->KeybindCount++] = {__VA_ARGS__} + BIND_COMMAND(Key_P, PlatformModifier_Ctrl, Workspace_Command_SplitPanelHorizontal); + BIND_COMMAND(Key_L, PlatformModifier_Ctrl, Workspace_Command_SplitPanelVertical); +#undef BIND_COMMAND + } +} + +static void Workspace_Update(workspace *Workspace, vn_render_commands *RenderCommands, + vn_input *Input, glyph_atlas *GlyphAtlas) +{ + Workspace->EventList = Input->EventList; + + // sixten: Process last frame's commands. + Workspace_ProcessKeyBinds(Workspace); + Workspace_ProcessCommands(Workspace); + + if(!Workspace->CurrentPanel) + { + Workspace->CurrentPanel = Workspace->RootPanel; + while(Workspace->CurrentPanel->First != 0) + { + Workspace->CurrentPanel = Workspace->CurrentPanel->First; + } + } + + // sixten: Build the UI. + UI_BeginBuild(RenderCommands->RenderDim); + { + Workspace_BuildToolbar(Workspace, Input->dtForFrame); + + UI_SetNextSize(UI_Percent(1, 1), UI_Percent(1, 0)); + Workspace_BuildPanel(Workspace, Workspace->RootPanel); + + Workspace_BuildDragPayload(Workspace, Input); + } + + UI_EndBuild(GlyphAtlas); +} \ No newline at end of file diff --git a/code/vn_workspace.h b/code/vn_workspace.h new file mode 100644 index 0000000..db2d598 --- /dev/null +++ b/code/vn_workspace.h @@ -0,0 +1,121 @@ +/* date = May 5th 2023 10:12 am */ + +#ifndef VN_WORKSPACE_H +#define VN_WORKSPACE_H + +#include "vn_workspace_editor.h" + +// sixten(TODO): Remove this type entirely. +enum toolbar_menu +{ + ToolbarMenu_None = 0, + ToolbarMenu_Panel, + ToolbarMenu_View, + ToolbarMenu_Window, +}; + +struct workspace_panel +{ + workspace_panel *First; + workspace_panel *Last; + workspace_panel *Next; + workspace_panel *Prev; + workspace_panel *Parent; + + struct workspace_view *FirstView; + struct workspace_view *LastView; + + struct workspace_view *CurrentView; + + axis2 SplitAxis; + r32 PercentOfParent; +}; + +#include "vn_workspace_view.h" + +#define WORKSPACE_COMMAND(name, ...) void name(workspace *Workspace, u64 Argument) +typedef WORKSPACE_COMMAND(workspace_command_sig); + +struct workspace_command +{ + workspace_command_sig *Command; + u64 Argument; + + workspace_command *Next; + workspace_command *Prev; +}; + +struct workspace_keybind +{ + platform_key Key; + platform_modifiers Modifiers; + workspace_command_sig *Command; + u64 Argument; +}; + +enum workspace_drag_payload_state +{ + Workspace_DragPayload_Inactive = 0, + Workspace_DragPayload_Active, + Workspace_DragPayload_Released, +}; + +struct workspace_drag_payload +{ + ui_key Key; + workspace_view *View; +}; + +struct workspace +{ + platform_event_list *EventList; + + // sixten: Command Allocation + memory_arena CommandArena; + workspace_command *FirstFreeCommand; + workspace_command *LastFreeCommand; + + // sixten: Command List + workspace_command *FirstCommand; + workspace_command *LastCommand; + + // sixten: Keybinds + workspace_keybind Keybinds[256]; + s32 KeybindCount; + + // sixten: Panels + memory_arena PanelArena; + workspace_panel *FirstFreePanel; + workspace_panel *LastFreePanel; + + workspace_drag_payload_state DragPayloadState; + workspace_drag_payload DragPayload; + + toolbar_menu Menu; + v2 MenuP; + r32 MenuTransition; + + workspace_panel *RootPanel; + workspace_panel *CurrentPanel; +}; + +//- sixten: Commands +static void Workspace_IssueCommand(workspace *Workspace, workspace_command_sig *Sig, u64 Argument); +static void Workspace_ProcessCommands(workspace *Workspace); + +//- sixten: Panels +static workspace_panel *Workspace_CreateNewPanel(workspace *Workspace, workspace_panel *Parent); +static void Workspace_DeletePanel(workspace *Workspace, workspace_panel *Panel); +static void Workspace_SplitPanel(workspace *Workspace, workspace_panel *Panel, axis2 Axis); + +//- sixten: Builder code +static ui_signal Workspace_BuildToolbarButton(workspace *Workspace, char *Text, toolbar_menu Menu); +static ui_signal Workspace_BuildMenuItem(u32 Icon, char *Text, char *Shortcut); +static void Workspace_BuildToolbar(workspace *Workspace, r32 dtForFrame); + +//- sixten: Workspace +static void Workspace_Init(workspace *Workspace); +static void Workspace_Update(workspace *Workspace, vn_render_commands *RenderCommands, + vn_input *Input, glyph_atlas *GlyphAtlas); + +#endif //VN_WORKSPACE_H diff --git a/code/vn_workspace_commands.cpp b/code/vn_workspace_commands.cpp new file mode 100644 index 0000000..0f2bf7b --- /dev/null +++ b/code/vn_workspace_commands.cpp @@ -0,0 +1,83 @@ +WORKSPACE_COMMAND(Workspace_Command_SplitPanelHorizontal) +{ + Workspace_SplitPanel(Workspace, Workspace->CurrentPanel, Axis2_X); +} + +WORKSPACE_COMMAND(Workspace_Command_SplitPanelVertical) +{ + Workspace_SplitPanel(Workspace, Workspace->CurrentPanel, Axis2_Y); +} + +WORKSPACE_COMMAND(Workspace_Command_ClosePanel) +{ + workspace_panel *Panel = (workspace_panel *)Argument; + if(!Panel) + { + Panel = Workspace->CurrentPanel; + } + + workspace_panel *Parent = Panel->Parent; + + Assert(Parent); + Assert(Panel != Workspace->RootPanel); + + DLLRemove(Parent->First, Parent->Last, Panel); + + b32 OneChildRemains = (Parent->First == Parent->Last); + if(OneChildRemains) + { + workspace_panel *Child = Parent->First; + Assert(DLLIsEmpty(Parent->FirstView)); + + Parent->FirstView = Child->FirstView; + Parent->LastView = Child->LastView; + Parent->First = Child->First; + Parent->Last = Child->Last; + Parent->SplitAxis = Child->SplitAxis; + + // sixten: Update the parents of the children. + for(workspace_view *View = Parent->FirstView; + View != 0; + View = View->Next) + { + View->Parent = Parent; + } + for(workspace_panel *ParentChild = Parent->First; + ParentChild != 0; + ParentChild = ParentChild->Next) + { + ParentChild->Parent = Parent; + } + + DLLRemove(Parent->First, Parent->Last, Child); + Workspace_DeletePanel(Workspace, Child); + } + else + { + s32 ChildCount = 0; + for(workspace_panel *Child = Parent->First; + Child != 0; + Child = Child->Next) + { + ++ChildCount; + } + + r32 ToAppend = Panel->PercentOfParent / ChildCount; + for(workspace_panel *Child = Parent->First; + Child != 0; + Child = Child->Next) + { + Child->PercentOfParent += ToAppend; + } + } + + // sixten: Delete all child views. + for(workspace_view *Child = Panel->FirstView; + Child != 0; + Child = Child->Next) + { + //Workspace_DeleteView(Child); + } + + Workspace_DeletePanel(Workspace, Panel); +} \ No newline at end of file diff --git a/code/vn_workspace_editor.cpp b/code/vn_workspace_editor.cpp new file mode 100644 index 0000000..1d11466 --- /dev/null +++ b/code/vn_workspace_editor.cpp @@ -0,0 +1,232 @@ +//- sixten: Managing nodes +static workspace_editor_node *Workspace_GetNewEditorNode(workspace_view *View) +{ + Assert(View->Type == Workspace_View_Editor); + + workspace_view_editor *Editor = (workspace_view_editor *)View->Data; + + workspace_editor_node *Result = 0; + + if(DLLIsEmpty(Editor->FirstFreeNode)) + { + Result = PushStruct(&View->Arena, workspace_editor_node); + } + else + { + Result = Editor->FirstFreeNode; + DLLRemove(Editor->FirstFreeNode, Editor->LastFreeNode, Result); + } + + if(Result) + { + *Result = {}; + DLLInsertLast(Editor->FirstNode, Editor->LastNode, Result); + } + + return(Result); +} + +//- sixten: Transformations +inline r32 Workspace_ViewToWorld(r32 Offset, r32 Scale, r32 Dim, r32 P) +{ + r32 Result = (P - Dim*0.5)*(1.0/Scale) - Offset; + return(Result); +} + +inline r32 Workspace_WorldToView(r32 Offset, r32 Scale, r32 Dim, r32 P) +{ + r32 Result = (P + Offset)*Scale + Dim*0.5; + return(Result); +} + +inline v2 Workspace_ViewToWorld(v2 Offset, r32 Scale, v2 Dim, v2 P) +{ + v2 Result = (P - Dim*0.5)*(1.0/Scale) - Offset; + return(Result); +} + +inline v2 Workspace_WorldToView(v2 Offset, r32 Scale, v2 Dim, v2 P) +{ + v2 Result = (P + Offset)*Scale + Dim*0.5; + return(Result); +} + +inline r32 Workspace_CalculateScaleFromZoomLevel(r32 ZoomLevel) +{ + r32 PixelsPerUnit = 100.0; + + r32 Scale = PixelsPerUnit*Pow(1.25, ZoomLevel); + return(Scale); +} + +//- sixten: Commands + + +//- sixten: Builder code +static void Workspace_EditorDrawCallback(render_group *Group, glyph_atlas *Atlas, ui_box *Box, void *Data) +{ + workspace_view_editor *Editor = (workspace_view_editor *)Data; + + r32 Scale = Editor->Scale; + + v4 LineColor = Theme_BorderColor; + + v2 Dim = DimOfRange(Box->Rect); + + s32 VerticalLineCount = Dim.x / Scale + 4; + for(s32 LineIndex = -VerticalLineCount/2; + LineIndex < VerticalLineCount/2; + ++LineIndex) + { + r32 OffsetX = Workspace_WorldToView(Editor->Offset.x, Scale, Dim.x, LineIndex - (s32)Editor->Offset.x); + PushQuad(Group, Box->Rect.Min + V2(OffsetX, 0), V2(1.5, Dim.y), LineColor, 0, 1.2, 0); + } + + s32 HorizontalLineCount = Dim.y / Scale + 4; + for(s32 LineIndex = -HorizontalLineCount/2; + LineIndex < HorizontalLineCount/2; + ++LineIndex) + { + r32 OffsetY = Workspace_WorldToView(Editor->Offset.y, Scale, Dim.y, LineIndex - (s32)Editor->Offset.y); + + PushQuad(Group, Box->Rect.Min + V2(0, OffsetY), V2(Dim.x, 1.5), LineColor, 0, 1.2, 0); + } +} + +static void Workspace_BuildEditor(workspace *Workspace, workspace_view *View) +{ + workspace_view_editor *Editor = (workspace_view_editor *)View->Data; + + // sixten(TODO): These should be able to have a strictness of 1, but they can't. Fix that. + UI_SetNextWidth(UI_Percent(1, 0)); + UI_SetNextHeight(UI_Percent(1, 0)); + + ui_box *EditorBox = UI_MakeBoxF(UI_BoxFlag_Clip|UI_BoxFlag_Clickable, "Workspace Editor %p", View); + EditorBox->DrawCallback = Workspace_EditorDrawCallback; + EditorBox->DrawCallbackData = Editor; + + r32 AnimatedZoomLevel = AnimationCurve_AnimateValueF(Editor->ZoomLevel, 0, 0.25, "Workspace Editor Zoom"); + Editor->Scale = Workspace_CalculateScaleFromZoomLevel(AnimatedZoomLevel); + + v2 EditorDim = DimOfRange(EditorBox->Rect); + + // sixten: Build the node boxes. + for(workspace_editor_node *Node = Editor->FirstNode; + Node != 0; + Node = Node->Next) + { + v2 ViewDim = V2(2, 1.5)*Editor->Scale; + v2 ViewP = Workspace_WorldToView(Editor->Offset, Editor->Scale, EditorDim, Node->P) - ViewDim*0.5; + + UI_SetNextSize(UI_Pixels(ViewDim.x, 1), UI_Pixels(ViewDim.y, 1)); + UI_SetNextFixedP(ViewP); + UI_SetNextLayoutAxis(Axis2_Y); + + Node->Box = UI_MakeBoxF(UI_BoxFlag_DrawBackground | + UI_BoxFlag_DrawBorder | + UI_BoxFlag_FloatingX | + UI_BoxFlag_FloatingY | + UI_BoxFlag_DrawDropShadow, + "Workspace Editor Node %p", Node); + + UI_Parent(Node->Box) + { + UI_SetNextBackgroundColor(LinearBlend(Theme_BackgroundColor, Color_Black, 0.3)); + + UI_SetNextSize(UI_Percent(1, 1), UI_Pixels(20, 1)); + Node->TitleBox = UI_MakeBoxF(UI_BoxFlag_DrawBackground | + UI_BoxFlag_DrawBorder | + UI_BoxFlag_Clickable, + "Workspace Editor Node Title"); + + UI_Parent(Node->TitleBox) + { + UI_Width(UI_TextContent(7, 0)) UI_Font(Font_Bold) UI_LabelF("Node"); + } + } + } + + // sixten: Get input from boxes. + { + workspace_editor_node *Next = 0; + for(workspace_editor_node *Node = Editor->FirstNode; + Node != 0; + Node = Next) + { + Next = Node->Next; + + ui_signal Signal = UI_SignalFromBox(Node->TitleBox); + if(Signal.Dragging) + { + if(Signal.Pressed) + { + UI_StoreDragV2(Node->P); + } + + v2 StartP = UI_GetDragV2(); + v2 EndP = StartP + Signal.DragDelta*(1.0 / Editor->Scale); + + Node->P = EndP; + } + + if(Signal.Dragging || Signal.Hovering) + { + Platform.SetCursor(PlatformCursor_ArrowAll); + } + } + } + + // sixten: Process the movement of the node. + { + } + + // sixten: Process panning and zooming of the editor. + ui_signal Signal = UI_SignalFromBox(EditorBox); + { + if(Signal.Dragging) + { + if(Signal.Pressed) + { + UI_StoreDragV2(Editor->Offset); + } + + v2 StartOffset = UI_GetDragV2(); + v2 EndOffset = StartOffset + Signal.DragDelta*(1.0 / Editor->Scale); + + Editor->Offset = EndOffset; + + // sixten: Update node positions, as to not get a one frame delay. + for(workspace_editor_node *Node = 0; + Node != 0; + Node = Node->Next) + { + v2 ViewDim = V2(2, 1.5)*Editor->Scale; + v2 ViewP = Workspace_WorldToView(Editor->Offset, Editor->Scale, EditorDim, Node->P) - ViewDim*0.5; + + Node->Box->FixedP = ViewP; + } + } + } + + for(platform_event *Event = Workspace->EventList->First; + Event != 0; + Event = Event->Next) + { + if(Event->Type == PlatformEvent_MouseScroll) + { + if(Signal.Dragging) + { + UI_StoreDragV2(Editor->Offset); + UI_UpdateDragStartP(); + } + + Editor->ZoomLevel = Clamp(Editor->ZoomLevel + Event->Scroll.y, -4, 5); + } + } + + // sixten: Process shortcuts. + if(Platform_KeyPress(Workspace->EventList, Key_Space, PlatformModifier_Ctrl)) + { + Workspace_GetNewEditorNode(View); + } +} diff --git a/code/vn_workspace_editor.h b/code/vn_workspace_editor.h new file mode 100644 index 0000000..a1a8507 --- /dev/null +++ b/code/vn_workspace_editor.h @@ -0,0 +1,45 @@ +/* date = May 13th 2023 6:20 pm */ + +#ifndef VN_WORKSPACE_EDITOR_H +#define VN_WORKSPACE_EDITOR_H + +/* sixten(NOTE): Node types +* +* Text(String, opt. Character) -> Node +* Menu(Strings[0..n]) -> Nodes[0..n] +* +*/ + +enum workspace_editor_node_type +{ + Workspace_EditorNode_None, + Workspace_EditorNode_Text, + Workspace_EditorNode_Menu, +}; + +struct workspace_editor_node +{ + workspace_editor_node_type Type; + + workspace_editor_node *Next; + workspace_editor_node *Prev; + + ui_box *Box; + ui_box *TitleBox; + + v2 P; +}; + +//- sixten: Managing nodes +static workspace_editor_node *Workspace_GetNewEditorNode(struct workspace_view *View); + +//- sixten: Transformations +inline v2 Workspace_ViewToWorld(v2 Offset, r32 Scale, v2 Dim, v2 P); +inline v2 Workspace_WorldToView(v2 Offset, r32 Scale, v2 Dim, v2 P); +inline r32 Workspace_CalculateScaleFromZoomLevel(r32 ZoomLevel); + +//- sixten: Builder code +static void Workspace_EditorDrawCallback(render_group *Group, glyph_atlas *Atlas, ui_box *Box, void *Data); +static void Workspace_BuildEditor(struct workspace *Workspace, struct workspace_view *View); + +#endif //VN_WORKSPACE_EDITOR_H diff --git a/code/vn_workspace_view.cpp b/code/vn_workspace_view.cpp new file mode 100644 index 0000000..c886a7f --- /dev/null +++ b/code/vn_workspace_view.cpp @@ -0,0 +1,419 @@ +//- sixten: Views +inline workspace_view *Workspace_CreateNewView(workspace_view_type Type, workspace_panel *Parent) +{ + workspace_view *View = BootstrapPushStruct(workspace_view, Arena); + View->Type = Type; + View->Parent = Parent; + + switch(View->Type) + { + case Workspace_View_Editor: + { View->Data = PushSize(&View->Arena, sizeof(workspace_view_editor)); } break; + case Workspace_View_CommandPalette: + { View->Data = PushSize(&View->Arena, sizeof(workspace_view_command_palette)); } break; + case Workspace_View_Settings: + { View->Data = PushSize(&View->Arena, sizeof(workspace_view_settings)); } break; + } + + return(View); +} + +inline void Workspace_DestroyView(workspace_view *View) +{ + // sixten(NOTE): This function does not ensure that the view is not being used anywhere else. + Release(&View->Arena); +} + +inline b32 Workspace_ViewIsCurrent(workspace *Workspace, workspace_view *View) +{ + b32 Result = (Workspace->CurrentPanel && Workspace->CurrentPanel->CurrentView == View); + return(Result); +} + +inline string Workspace_GetViewName(workspace_view *View) +{ + string Result = StrLit("Unnamed view"); + switch(View->Type) + { + case Workspace_View_Startup: { Result = StrLit("Welcome"); } break; + case Workspace_View_Editor: { Result = StrLit("Editor"); } break; + case Workspace_View_Settings: { Result = StrLit("Settings"); } break; + } + + return(Result); +} + +//- sixten: Builder code +static void Workspace_ViewListerInputCallback(render_group *Group, glyph_atlas *Atlas, ui_box *Box, void *Data) +{ + workspace_view_command_palette *CommandPalette = (workspace_view_command_palette *)Data; + string ToCursor = MakeString((char *)Box->String.Data, CommandPalette->ListerInputEditState.Cursor); + string ToMark = MakeString((char *)Box->String.Data, CommandPalette->ListerInputEditState.Mark); + + r32 TargetCursorX = CalculateRasterizedTextWidth(Atlas, Box->Font, Box->FontSize, ToCursor); + r32 TargetMarkerX = CalculateRasterizedTextWidth(Atlas, Box->Font, Box->FontSize, ToMark); + + r32 CursorX = AnimationCurve_AnimateValueF(TargetCursorX, 0, 0.175, "Workspace View Input Cursor %p", Box); + r32 MarkerX = AnimationCurve_AnimateValueF(TargetMarkerX, 0, 0.175, "Workspace View Input Mark %p", Box); + + v2 BoxDim = DimOfRange(Box->Rect); + + // sixten: Draw selection + { + v2 Offset = V2(7.5, (BoxDim.y - Box->FontSize) / 2); + v2 Dim = V2(0, Box->FontSize); + if(CursorX > MarkerX) + { + Offset.x += MarkerX; + Dim.x = CursorX - MarkerX; + } + else + { + Offset.x += CursorX; + Dim.x = MarkerX - CursorX; + } + + v2 P = Box->Rect.Min + Offset; + v4 Color = V4(0.4, 0.7, 0.8, 0.3); + PushQuad(Group, P, Dim, Color, 0, 0, 0); + } + + // sixten: Draw cursor + if(CommandPalette->ListerFieldSelected) + { + range_r32 CursorSpan = RangeR32(CursorX, TargetCursorX); + r32 Height = Box->FontSize + 4; + v2 Offset = V2(7.5F + CursorSpan.Min, (BoxDim.y - Height) / 2); + v2 Dim = V2(1.25F + CursorSpan.Max - CursorSpan.Min, Height); + + v2 P = Box->Rect.Min + Offset; + v4 Color = V4(0.3, 1, 0.3, 0.7); + PushQuad(Group, P, Dim, Color, 0, 0, 0); + } +} + +static void Workspace_BuildViewTypeLister(workspace *Workspace, workspace_view *View) +{ + workspace_view_command_palette *CommandPalette = (workspace_view_command_palette *)View->Data; + + temporary_memory Scratch = GetScratch(0, 0); + + UI_Size(UI_Percent(1, 1), UI_Percent(1, 1)) + UI_Parent(UI_MakeBox(0, StrLit(""))) + { + UI_Spacer(UI_Pixels(1.25, 1)); + UI_CornerRadius(4) + UI_BackgroundColor(V4(0.5, 0.2, 0.3, 1.0)) + UI_LayoutAxis(Axis2_X) + UI_Height(UI_Pixels(30, 1)) + UI_Parent(UI_MakeBoxF(UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawDropShadow, "Workspace View Lister Header")) + { + UI_Spacer(UI_Pixels(5, 1)); + + UI_Width(UI_TextContent(10, 1)) UI_LabelF("Open View:"); + + UI_Spacer(UI_Pixels(15, 1)); + + // sixten: Input Field. + { + r32 SelectedTransition = AnimationCurve_AnimateValueF(CommandPalette->ListerFieldSelected ? 1.0 : 0.0, + 0, 0.125, "View Input Field %p", View); + + v4 BorderColor = UI_TopBackgroundColor()*2; + BorderColor.w = SelectedTransition; + + UI_SetNextBorderColor(BorderColor); + + UI_SetNextBackgroundColor(LinearBlend(V4(0, 0, 0, 1), UI_TopBackgroundColor(), 0.5)); + UI_SetNextWidth(UI_Percent(1, 0)); + + ui_box *InputBox = UI_MakeBoxF(UI_BoxFlag_DrawBackground | + UI_BoxFlag_Clickable | + UI_BoxFlag_DrawBorder, + "View Type Lister Input"); + + UI_Parent(InputBox) + { + UI_SetNextWidth(UI_TextContent(15, 1)); + + ui_box *InputTextBox = UI_MakeBox(UI_BoxFlag_DrawText, StrLit("Workspace View Lister")); + InputTextBox->String = MakeString(CommandPalette->ListerInput, CommandPalette->ListerInputUsed); + InputTextBox->DrawCallback = Workspace_ViewListerInputCallback; + InputTextBox->DrawCallbackData = View; + } + + UI_Spacer(UI_Pixels(4, 1)); + + if(CommandPalette->ListerFieldSelected) + { + for(platform_event *Event = Workspace->EventList->First; + Event != 0; + Event = Event->Next) + { + if(Event->Type == PlatformEvent_Press || Event->Type == PlatformEvent_Text) + { + text_action Action = SingleLineTextActionFromEvent(Event); + if(IsValid(&Action)) + { + text_op Op = TextOpFromAction(Scratch.Arena, MakeString(CommandPalette->ListerInput, CommandPalette->ListerInputUsed), + &CommandPalette->ListerInputEditState, &Action); + + string Left = MakeString(CommandPalette->ListerInput, Op.Range.Min); + string Right = MakeString(CommandPalette->ListerInput + Op.Range.Max, CommandPalette->ListerInputUsed - Op.Range.Max); + + u64 NewStringSize = Left.Count + Right.Count + Op.ReplaceString.Count; + char *NewString = PushArray(Scratch.Arena, char, NewStringSize); + Copy(NewString, Left.Data, Left.Count); + Copy(NewString + Left.Count, Op.ReplaceString.Data, Op.ReplaceString.Count); + Copy(NewString + Left.Count + Op.ReplaceString.Count, Right.Data, Right.Count); + + CommandPalette->ListerInputUsed = Minimum(ArrayCount(CommandPalette->ListerInput), NewStringSize); + Copy(CommandPalette->ListerInput, NewString, CommandPalette->ListerInputUsed); + + CommandPalette->ListerInputEditState.Cursor = Minimum(Op.NewCursor, CommandPalette->ListerInputUsed); + CommandPalette->ListerInputEditState.Mark = Minimum(Op.NewMark, CommandPalette->ListerInputUsed); + } + } + } + } + + if(UI_SignalFromBox(InputBox).Pressed) + { + CommandPalette->ListerFieldSelected = true; + } + + ui *UI = UI_GetState(); + + if(!(AreEqual(UI->Active, UI_EmptyKey()) || AreEqual(UI->Active, InputBox->Key))) + { + CommandPalette->ListerFieldSelected = false; + } + } + } + + UI_Spacer(UI_Pixels(15, 1)); + + { + UI_SetNextCornerRadius(10); + UI_SetNextBackgroundColor(V4(0.15, 0.15, 0.15, 1.0)); + UI_SetNextBorderColor(V4(0.35, 0.35, 0.35, 1.0)); + UI_SetNextSize(UI_Percent(1, 1), UI_Pixels(100, 1)); + UI_SetNextLayoutAxis(Axis2_Y); + + ui_box *ButtonBox = UI_MakeBox(UI_BoxFlag_DrawBackground | + UI_BoxFlag_DrawBorder | + UI_BoxFlag_HotAnimation | + UI_BoxFlag_ActiveAnimation | + UI_BoxFlag_Clickable, + StrLit("Type Lister Box")); + + UI_SignalFromBox(ButtonBox); + } + } + + ReleaseScratch(Scratch); +} + +static void Workspace_BuildSettingsTabButton(workspace_view_settings *Settings, char *Name, workspace_settings_category Category) +{ + b32 IsSelected = (Settings->Category == Category); + + v4 Color = LinearBlend(Theme_TextColor, Theme_HighlightBorderColor, AnimationCurve_AnimateValueF(IsSelected, 0, 0.3, "Workspace Settings %s %p", Name, Settings)); + + UI_SetNextFont(Font_Bold); + UI_SetNextHeight(UI_TextContent(0, 1)); + UI_SetNextTextColor(Color); + + ui_box *Box = UI_MakeBoxF(UI_BoxFlag_DrawText | + UI_BoxFlag_Clickable, + Name); + + ui_signal Signal = UI_SignalFromBox(Box); + if(Signal.Hovering) + { + Platform.SetCursor(PlatformCursor_Hand); + } + if(Signal.Clicked) + { + Settings->Category = Category; + } +} + +static void UI_DropdownSelection(char **Alternatives, s32 AlternativeCount, b32 *Open, s32 *Selected) +{ + UI_SetNextLayoutAxis(Axis2_X); + UI_Parent(UI_MakeBoxF(0, "")) + { + UI_LabelF("Refresh Rate:"); + UI_Spacer(UI_Pixels(10, 1)); + + UI_SetNextWidth(UI_Pixels(200, 1)); + UI_SetNextCornerRadius(4); + UI_SetNextLayoutAxis(Axis2_X); + ui_box *DropdownBox = UI_MakeBoxF(UI_BoxFlag_DrawBackground | + UI_BoxFlag_DrawBorder | + UI_BoxFlag_HotAnimation | + UI_BoxFlag_ActiveAnimation | + UI_BoxFlag_Clickable, + "Dropdown"); + UI_Parent(DropdownBox) + { + UI_Width(UI_Percent(1, 0)) UI_LabelF(Alternatives[*Selected]); + UI_BackgroundColor(Theme_BorderColor) UI_Width(UI_Pixels(1, 1)) UI_MakeBoxF(UI_BoxFlag_DrawBackground, ""); + UI_Width(UI_Pixels(25, 1)) UI_Font(Font_Icons) UI_LabelF("%U", FontIcon_DownDir); + } + + ui_signal Signal = UI_SignalFromBox(DropdownBox); + if(Signal.Pressed) + { + *Open = true; + } + + if(*Open) + { + UI_Tooltip + { + UI_SetNextFixedP(V2(DropdownBox->Rect.Min.x, DropdownBox->Rect.Max.y)); + UI_SetNextCornerRadius(4); + UI_SetNextWidth(UI_Pixels(200, 1)); + UI_SetNextHeight(UI_ChildrenSum(AnimationCurve_AnimateValueF(1, 0, 0.3, "UI Dropdown %p%p", Alternatives, Open), 1)); + UI_Parent(UI_MakeBoxF(UI_BoxFlag_Clip | + UI_BoxFlag_DrawDropShadow | + UI_BoxFlag_FloatingX | + UI_BoxFlag_FloatingY, "Dropdown Contents")) + { + for(s64 Index = 0; + Index < AlternativeCount; + ++Index) + { + UI_Width(UI_Percent(1, 1)) UI_ButtonF(Alternatives[Index]); + } + } + } + } + } +} + +static void Workspace_BuildSettings(workspace *Workspace, workspace_view *View) +{ + workspace_view_settings *Settings = (workspace_view_settings *)View->Data; + + UI_Height(UI_ChildrenSum(1, 1)) + UI_Column UI_Padding(UI_Pixels(50, 0)) + UI_Row UI_Padding(UI_Pixels(50, 0)) + { + UI_Size(UI_TextContent(0, 1), UI_TextContent(10, 1)) + UI_Font(Font_Bold) UI_FontSize(36) + UI_LabelF("Settings"); + } + + UI_LayoutAxis(Axis2_X) + UI_Size(UI_Percent(1, 0), UI_Percent(1, 0)) + UI_Parent(UI_MakeBoxF(0, "")) + { + UI_Width(UI_Pixels(300, 1)) + UI_Parent(UI_MakeBoxF(0, "Navigation")) + { + UI_Row UI_Padding(UI_Pixels(50, 1)) + UI_Width(UI_Percent(1, 0)) UI_Column UI_Padding(UI_Percent(1, 0)) + UI_Height(UI_ChildrenSum(1, 1)) UI_LayoutAxis(Axis2_Y) UI_Parent(UI_MakeBoxF(0, "")) + { + Workspace_BuildSettingsTabButton(Settings, "All", Workspace_Settings_All); + UI_Spacer(UI_Pixels(30, 1)); + Workspace_BuildSettingsTabButton(Settings, "General", Workspace_Settings_General); + UI_Spacer(UI_Pixels(30, 1)); + Workspace_BuildSettingsTabButton(Settings, "Developer", Workspace_Settings_Developer); + + UI_Spacer(UI_Pixels(150, 1)); + } + } + + UI_CornerRadius(5) + UI_Width(UI_Pixels(1.25, 1)) + UI_BackgroundColor(Color_Grey) + UI_MakeBoxF(UI_BoxFlag_DrawBackground, "Separator"); + + UI_Padding(UI_Pixels(70, 0)) + UI_LayoutAxis(Axis2_Y) + UI_Width(UI_Percent(1, 0)) + UI_Parent(UI_MakeBoxF(0, "Tab")) + UI_Size(UI_TextContent(0, 1), UI_TextContent(10, 1)) + { + workspace_settings_category Category = Settings->Category; + if(!Category || (Category == Workspace_Settings_General)) + { + UI_Font(Font_Bold) UI_FontSize(36) UI_LabelF("General"); + + char *Alternatives[] = {"60 Hz", "120 Hz", "144 Hz", "Uncapped", "V-Sync"}; + + persist b32 DropdownOpen = false; + persist s32 DropdownSelected = 0; + UI_DropdownSelection(Alternatives, ArrayCount(Alternatives), &DropdownOpen, &DropdownSelected); + + UI_Spacer(UI_Pixels(50, 1)); + } + + if(!Category || (Category == Workspace_Settings_Developer)) + { + UI_Font(Font_Bold) UI_FontSize(36) UI_LabelF("Developer"); + UI_LabelF("Render UI Debug Rects:"); + UI_LabelF("Render FPS Counter:"); + + UI_CornerRadius(4) + UI_Size(UI_TextContent(20, 1), UI_TextContent(10, 1)) + UI_ButtonF("Hello Line Paint Color Design Address Brightness"); + + UI_Spacer(UI_Pixels(50, 1)); + } + } + + } + + UI_Spacer(UI_Pixels(50, 1)); +} + +static void Workspace_BuildView(workspace *Workspace, workspace_view *View) +{ + r32 ViewHighlightTransition = + AnimationCurve_AnimateValueF(Workspace_ViewIsCurrent(Workspace, View), 0, 0.25, "Workspace View Highlight %p", View); + UI_SetNextBorderColor(LinearBlend(Theme_BorderColor, Theme_HighlightBorderColor, ViewHighlightTransition)); + UI_SetNextCornerRadius(3); + + ui_box *ViewBox = UI_MakeBoxF(UI_BoxFlag_Clickable | + UI_BoxFlag_DrawBackground | + UI_BoxFlag_DrawBorder | + UI_BoxFlag_Clip, + "Workspace View %p", View); + + UI_Parent(ViewBox) + UI_Size(UI_Percent(1, 0), UI_Percent(1, 0)) + { + if(View->Type == Workspace_View_Startup) + { + UI_Row UI_Padding(UI_Pixels(50, 0)) + UI_Width(UI_ChildrenSum(1, 1)) UI_Column UI_Padding(UI_Pixels(50, 0)) + { + UI_Size(UI_TextContent(0, 1), UI_TextContent(10, 1)) + { + UI_Font(Font_Bold) UI_FontSize(36) + UI_LabelF("Welcome to VN"); + UI_TextColor(Theme_BorderColor) UI_LabelF("An impractical way to make a game"); + } + } + } + else if(View->Type == Workspace_View_CommandPalette) + { + Workspace_BuildViewTypeLister(Workspace, View); + } + else if(View->Type == Workspace_View_Editor) + { + Workspace_BuildEditor(Workspace, View); + } + else if(View->Type == Workspace_View_Settings) + { + Workspace_BuildSettings(Workspace, View); + } + } + + UI_SignalFromBox(ViewBox); +} diff --git a/code/vn_workspace_view.h b/code/vn_workspace_view.h new file mode 100644 index 0000000..a41cf73 --- /dev/null +++ b/code/vn_workspace_view.h @@ -0,0 +1,68 @@ +/* date = June 11th 2023 1:20 pm */ + +#ifndef VN_WORKSPACE_VIEW_H +#define VN_WORKSPACE_VIEW_H + +struct workspace_view +{ + memory_arena Arena; + enum workspace_view_type Type; + + workspace_panel *Parent; + workspace_view *Next; + workspace_view *Prev; + + void *Data; +}; + +enum workspace_view_type +{ + Workspace_View_Startup, + Workspace_View_Editor, + Workspace_View_CommandPalette, + Workspace_View_Settings, +}; + +struct workspace_view_editor +{ + v2 Offset; + r32 ZoomLevel; + r32 Scale; // sixten(NOTE): Read-only, based on the zoom level + workspace_editor_node *FirstNode; + workspace_editor_node *LastNode; + workspace_editor_node *FirstFreeNode; + workspace_editor_node *LastFreeNode; +}; + +struct workspace_view_command_palette +{ + b32 ListerFieldSelected; + char ListerInput[128]; + s32 ListerInputUsed; + text_edit_state ListerInputEditState; +}; + +enum workspace_settings_category +{ + Workspace_Settings_All, + Workspace_Settings_General, + Workspace_Settings_Developer, +}; + +struct workspace_view_settings +{ + workspace_settings_category Category; +}; + +//- sixten: Views +inline workspace_view *Workspace_CreateNewView(workspace_view_type Type, workspace_panel *Parent); +inline void Workspace_DestroyView(workspace_view *View); +inline b32 Workspace_ViewIsCurrent(workspace *Workspace, workspace_view *View); +inline string Workspace_GetViewName(workspace_view *View); + +//- sixten: Builder code +static void Workspace_ViewListerInputCallback(render_group *Group, glyph_atlas *Atlas, ui_box *Box, void *Data); +static void Workspace_BuildViewTypeLister(workspace *Workspace, workspace_view *View); +static void Workspace_BuildView(workspace *Workspace, workspace_view *View); + +#endif //VN_WORKSPACE_VIEW_H diff --git a/code/win32_main.cpp b/code/win32_main.cpp new file mode 100644 index 0000000..cb2414b --- /dev/null +++ b/code/win32_main.cpp @@ -0,0 +1,679 @@ +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include "windows.h" +#include "timeapi.h" + +#include "vn_platform.h" + +#include "win32_main.h" +#include "win32_opengl.cpp" + +global b32 Global_Running; +global win32_state Global_Win32State; +global WINDOWPLACEMENT Global_WindowPosition = {sizeof(Global_WindowPosition)};; + +static void Win32_PlatformError(char *Message, bool IsFatal) +{ + MessageBoxA(0, Message, "nv - Platform Error", MB_OK|(IsFatal?MB_ICONSTOP:MB_ICONEXCLAMATION)); + + if(IsFatal) + { + ExitProcess((UINT)-1); + } +} + +static PLATFORM_ALLOCATE_MEMORY(Win32_AllocateMemory) +{ + win32_state *State = &Global_Win32State; + + umm TotalSize = Size + sizeof(win32_memory_block); + + win32_memory_block *Block = + (win32_memory_block *)VirtualAlloc(0, TotalSize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); + + Assert(Block); + Block->Block.Base = (u8 *)Block + sizeof(win32_memory_block); + Block->Block.Size = Size; + + win32_memory_block *Sentinel = &State->MemorySentinel; + Block->Next = Sentinel; + + BeginTicketMutex(&State->MemoryMutex); + Block->Prev = Sentinel->Prev; + Block->Prev->Next = Block; + Block->Next->Prev = Block; + EndTicketMutex(&State->MemoryMutex); + + platform_memory_block *Result = &Block->Block; + return(Result); +} + +static PLATFORM_DEALLOCATE_MEMORY(Win32_DeallocateMemory) +{ + win32_state *State = &Global_Win32State; + + win32_memory_block *Win32Block = (win32_memory_block *)Block; + + if(Block) + { + BeginTicketMutex(&State->MemoryMutex); + Win32Block->Prev->Next = Win32Block->Next; + Win32Block->Next->Prev = Win32Block->Prev; + EndTicketMutex(&State->MemoryMutex); + } + + BOOL Result = VirtualFree(Block, 0, MEM_RELEASE); + return(Result); +} + +static PLATFORM_OPEN_FILE(Win32_OpenFile) +{ + DWORD DesiredAccess = 0; + if(FileAccess & PlatformAccess_Read) + { + DesiredAccess |= GENERIC_READ; + } + if(FileAccess & PlatformAccess_Write) + { + DesiredAccess |= GENERIC_WRITE; + } + + DWORD CreationAttributes = 0; + if(FileAccess & PlatformAccess_Read) + { + CreationAttributes = OPEN_EXISTING; + } + + string FullPath = Path; + + HANDLE File = CreateFileA((char *)Path.Data, DesiredAccess, 0, 0, CreationAttributes, 0, 0); + + platform_file_handle Result = {}; + Result.Platform = (u64)File; + Result.IsValid = (File != INVALID_HANDLE_VALUE); + + return(Result); +} + +static PLATFORM_CLOSE_FILE(Win32_CloseFile) +{ + HANDLE File = (HANDLE)Handle.Platform; + if(File != INVALID_HANDLE_VALUE) + { + CloseHandle(File); + } +} + +static PLATFORM_READ_FILE(Win32_ReadFile) +{ + HANDLE File = (HANDLE)Handle.Platform; + if(File != INVALID_HANDLE_VALUE) + { + DWORD BytesRead; + ReadFile(File, Dest, Size, &BytesRead, 0); + + Assert(BytesRead == Size); + } +} + +static PLATFORM_GET_FILE_SIZE(Win32_GetFileSize) +{ + u64 Result = 0; + + HANDLE File = (HANDLE)Handle.Platform; + if(File != INVALID_HANDLE_VALUE) + { + LARGE_INTEGER FileSize; + GetFileSizeEx(File, &FileSize); + + Result = FileSize.QuadPart; + } + + return(Result); +} + +static PLATFORM_SET_CURSOR(Win32_SetCursor) +{ + Global_Win32State.Cursor = Cursor; +} + +inline u64 Win32_GetWallClock(void) +{ + LARGE_INTEGER Query; + QueryPerformanceCounter(&Query); + + u64 Result = Query.QuadPart; + return(Result); +} + +inline r64 Win32_GetSecondsElapsed(u64 Start, u64 End) +{ + u64 Elapsed = End - Start; + + r64 Result = (r64)Elapsed/(r64)Global_Win32State.PerformanceFrequency; + return(Result); +} + +inline FILETIME Win32_GetLastWriteTime(char *Path) +{ + FILETIME Result = {}; + + HANDLE File = CreateFileA(Path, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); + if(File) + { + FILETIME Creation, LastAccess, LastWrite; + GetFileTime(File, &Creation, &LastAccess, &LastWrite); + + Result = LastWrite; + + CloseHandle(File); + } + + return(Result); +} + +static win32_loaded_code Win32_LoadCode(void) +{ + win32_loaded_code Code = {}; + + win32_state *State = &Global_Win32State; + + if(CopyFile(State->DLLPath, State->TempDLLPath, FALSE)) + { + Code.LastWriteTime = Win32_GetLastWriteTime(State->DLLPath); + + Code.DLL = LoadLibraryA(State->TempDLLPath); + if(Code.DLL) + { + Code.UpdateAndRender = (vn_update_and_render *)GetProcAddress(Code.DLL, "VN_UpdateAndRender"); + if(Code.UpdateAndRender) + { + Code.IsValid = true; + } + } + } + + return(Code); +} + +static void Win32_UnloadCode(win32_loaded_code *Code) +{ + if(Code->DLL) + { + FreeLibrary(Code->DLL); + } + + *Code = {}; +} + +static void Win32_UpdateCode(win32_loaded_code *Code) +{ + win32_state *State = &Global_Win32State; + + FILETIME LastWriteTime = Win32_GetLastWriteTime(State->DLLPath); + if(CompareFileTime(&Code->LastWriteTime, &LastWriteTime) != 0) + { + Win32_UnloadCode(Code); + *Code = Win32_LoadCode(); + } +} + +static PLATFORM_TOGGLE_FULLSCREEN(Win32_ToggleFullscreen) +{ + HWND Window = Global_Win32State.Window; + + DWORD Style = GetWindowLong(Window, GWL_STYLE); + if(Style & WS_OVERLAPPEDWINDOW) + { + MONITORINFO MonitorInfo = {sizeof(MonitorInfo)}; + if(GetWindowPlacement(Window, &Global_WindowPosition) && + GetMonitorInfo(MonitorFromWindow(Window, MONITOR_DEFAULTTOPRIMARY), &MonitorInfo)) + { + SetWindowPos(Window, HWND_TOP, + MonitorInfo.rcMonitor.left, MonitorInfo.rcMonitor.top, + MonitorInfo.rcMonitor.right - MonitorInfo.rcMonitor.left, + MonitorInfo.rcMonitor.bottom - MonitorInfo.rcMonitor.top, + SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + } + } + else + { + SetWindowPlacement(Window, &Global_WindowPosition); + SetWindowPos(Window, 0, 0, 0, 0, 0, + SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOOWNERZORDER|SWP_FRAMECHANGED); + } +} + +inline v2 Win32_GetMouseP(HWND Window) +{ + POINT Point; + GetCursorPos(&Point); + ScreenToClient(Window, &Point); + + v2 Result = V2(Point.x, Point.y); + return(Result); +} + +inline v2 Win32_GetWindowDim(HWND Window) +{ + RECT ClientRect; + GetClientRect(Window, &ClientRect); + + v2 Result = V2(ClientRect.right - ClientRect.left, ClientRect.bottom - ClientRect.top); + return(Result); +} + +inline platform_modifiers Win32_GetModifiers(void) +{ + platform_modifiers Modifiers = 0; + + if(GetKeyState(VK_CONTROL) & 0x8000) + { + Modifiers |= PlatformModifier_Ctrl; + } + if(GetKeyState(VK_SHIFT) & 0x8000) + { + Modifiers |= PlatformModifier_Shift; + } + if(GetKeyState(VK_MENU) & 0x8000) + { + Modifiers |= PlatformModifier_Alt; + } + + return(Modifiers); +} + +static LRESULT Win32_WindowCallback(HWND Window, UINT Message, WPARAM WParam, LPARAM LParam) +{ + LRESULT Result = 0; + + win32_state *State = &Global_Win32State; + + temporary_memory Scratch = GetScratch(0, 0); + + platform_event *Event = 0; + + b32 ButtonIsUp = false; + axis2 ScrollAxis = Axis2_Y; + + switch(Message) + { + case WM_CLOSE: + { + Global_Running = false; + } break; + + case WM_WINDOWPOSCHANGED: + { + // TODO(casey): For now, we are setting the window styles in here + // because sometimes Windows can reposition our window out of fullscreen + // without going through our ToggleFullscreen(), and we want to put our + // title bar and border back when it does! + + WINDOWPOS *NewPos = (WINDOWPOS *)LParam; + + b32 BecomingFullscreen = false; + MONITORINFO MonitorInfo = {sizeof(MonitorInfo)}; + if(GetMonitorInfo(MonitorFromWindow(Window, MONITOR_DEFAULTTOPRIMARY), + &MonitorInfo)) + { + s32 MonWidth = (MonitorInfo.rcMonitor.right - MonitorInfo.rcMonitor.left); + s32 MonHeight = (MonitorInfo.rcMonitor.bottom - MonitorInfo.rcMonitor.top); + BecomingFullscreen = ((MonitorInfo.rcMonitor.left == NewPos->x) && + (MonitorInfo.rcMonitor.top == NewPos->y) && + (MonWidth == NewPos->cx) && + (MonHeight == NewPos->cy)); + } + + DWORD OldStyle = GetWindowLong(Window, GWL_STYLE); + DWORD FullscreenStyle = OldStyle & ~WS_OVERLAPPEDWINDOW; + DWORD WindowedStyle = OldStyle | WS_OVERLAPPEDWINDOW; + DWORD NewStyle = (BecomingFullscreen) ? FullscreenStyle : WindowedStyle; + + if(NewStyle != OldStyle) + { + SetWindowLong(Window, GWL_STYLE, NewStyle); + } + + Result = DefWindowProcA(Window, Message, WParam, LParam); + } break; + + case WM_MOUSEHWHEEL: + { + ScrollAxis = Axis2_X; + } fallthrough; + case WM_MOUSEWHEEL: + { + Event = PushStruct(&State->EventArena, platform_event); + Event->Type = PlatformEvent_MouseScroll; + Event->Scroll.E[ScrollAxis] = GET_WHEEL_DELTA_WPARAM(WParam) / 120.0; + } break; + + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + { + ButtonIsUp = true; + } fallthrough; + + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + { + platform_event_type Type = ButtonIsUp ? PlatformEvent_Release : PlatformEvent_Press; + + platform_key Key = Key_Invalid; + switch(Message) + { + case WM_LBUTTONUP: case WM_LBUTTONDOWN: { Key = Key_MouseLeft; } break; + case WM_MBUTTONUP: case WM_MBUTTONDOWN: { Key = Key_MouseMiddle; } break; + case WM_RBUTTONUP: case WM_RBUTTONDOWN: { Key = Key_MouseRight; } break; + } + + Event = PushStruct(&State->EventArena, platform_event); + Event->Type = Type; + Event->Key = Key; + Event->P = Win32_GetMouseP(Window); + } break; + + case WM_SYSKEYUP: + case WM_KEYUP: + { + ButtonIsUp = true; + } fallthrough; + case WM_SYSKEYDOWN: + case WM_KEYDOWN: + { + platform_event_type Type = ButtonIsUp ? PlatformEvent_Release : PlatformEvent_Press; + + u32 VKCode = (u32)WParam; + platform_key Key = Key_Invalid; + + if(VKCode >= 'A' && VKCode <= 'Z') + { + Key = (platform_key)(Key_A + (VKCode - 'A')); + } + else if(VKCode >= VK_F1 && VKCode <= VK_F12) + { + Key = (platform_key)(Key_F1 + (VKCode - VK_F1)); + } + else if(VKCode == VK_LEFT) { Key = Key_Left; } + else if(VKCode == VK_RIGHT) { Key = Key_Right; } + else if(VKCode == VK_UP) { Key = Key_Up; } + else if(VKCode == VK_DOWN) { Key = Key_Down; } + else if(VKCode == VK_SPACE) { Key = Key_Space; } + else if(VKCode == VK_PRIOR) { Key = Key_PageUp; } + else if(VKCode == VK_NEXT) { Key = Key_PageDown; } + else if(VKCode == VK_HOME) { Key = Key_Home; } + else if(VKCode == VK_END) { Key = Key_End; } + else if(VKCode == VK_BACK) { Key = Key_Backspace; } + else if(VKCode == VK_DELETE) { Key = Key_Delete; } + + if(Key != Key_Invalid) + { + Event = PushStruct(&State->EventArena, platform_event); + Event->Type = Type; + Event->Key = Key; + } + + if(!ButtonIsUp && (VKCode == VK_RETURN) && (LParam & (1 << 29))) + { + Win32_ToggleFullscreen(); + } + else + { + Result = DefWindowProc(Window, Message, WParam, LParam); + } + + } break; + + case WM_CHAR: + { + u32 Codepoint = (u32)WParam; + if(Codepoint == '\r') + { + Codepoint = '\n'; + } + + if((Codepoint >= 32 && Codepoint != 127) || Codepoint == '\t' || Codepoint == '\n') + { + Event = PushStruct(&State->EventArena, platform_event); + Event->Type = PlatformEvent_Text; + Event->Codepoint = Codepoint; + } + } break; + + case WM_SETCURSOR: + { + range2_r32 WindowRect = {}; + WindowRect.Max = Win32_GetWindowDim(Window); + if(InRange(WindowRect, Win32_GetMouseP(Window))) + { + persist HCURSOR CursorTable[PlatformCursor_Count]; + persist b32 CursorTableLoaded = false; + if(!CursorTableLoaded) + { + CursorTable[PlatformCursor_Arrow] = LoadCursor(0, IDC_ARROW); + CursorTable[PlatformCursor_Cross] = LoadCursor(0, IDC_CROSS); + CursorTable[PlatformCursor_Hand] = LoadCursor(0, IDC_HAND); + CursorTable[PlatformCursor_Help] = LoadCursor(0, IDC_HELP); + CursorTable[PlatformCursor_IBeam] = LoadCursor(0, IDC_IBEAM); + CursorTable[PlatformCursor_SlashedCircle] = LoadCursor(0, IDC_NO); + CursorTable[PlatformCursor_ArrowAll] = LoadCursor(0, IDC_SIZEALL); + CursorTable[PlatformCursor_ArrowNESW] = LoadCursor(0, IDC_SIZENESW); + CursorTable[PlatformCursor_ArrowVertical] = LoadCursor(0, IDC_SIZENS); + CursorTable[PlatformCursor_ArrowNWSE] = LoadCursor(0, IDC_SIZENWSE); + CursorTable[PlatformCursor_ArrowHorizontal] = LoadCursor(0, IDC_SIZEWE); + CursorTable[PlatformCursor_Wait] = LoadCursor(0, IDC_WAIT); + + CursorTableLoaded = true; + } + + SetCursor(CursorTable[Global_Win32State.Cursor]); + } + else + { + DefWindowProc(Window, Message, WParam, LParam); + } + } break; + + default: + { + Result = DefWindowProc(Window, Message, WParam, LParam); + } break; + } + + if(Event) + { + Event->Modifiers = Win32_GetModifiers(); + DLLInsertLast(State->EventList.First, State->EventList.Last, Event); + } + + ReleaseScratch(Scratch); + + return(Result); +} + +static void Win32_ProcessInput(vn_input *Input, HWND Window, r32 dtForFrame) +{ + win32_state *State = &Global_Win32State; + + { + if(State->EventArenaTemp.Arena) + { + EndTemporaryMemory(State->EventArenaTemp); + } + + State->EventArenaTemp = BeginTemporaryMemory(&State->EventArena); + } + + MSG Message; + while(PeekMessage(&Message, Window, 0, 0, PM_REMOVE)) + { + TranslateMessage(&Message); + DispatchMessageA(&Message); + } + + Input->EventList = &State->EventList; + + v2 NewMouseP = Win32_GetMouseP(Window); + v2 OldMouseP = Input->MouseP; + + Input->dMouseP = NewMouseP - OldMouseP; + Input->MouseP = NewMouseP; + + Input->dtForFrame = dtForFrame; +} + +static void Win32_EnforceFrameRate(u64 FrameBegin, r32 TargetFrameRate) +{ + win32_state *State = &Global_Win32State; + + r64 TimeElapsed = Win32_GetSecondsElapsed(FrameBegin, Win32_GetWallClock()); + r64 Target = 1.0 / TargetFrameRate; + + r64 ToSleep = Target - TimeElapsed; + if(ToSleep > 0) + { + u64 MSToSleep = (u64)Floor(ToSleep*1000); + if(State->SleepIsGranular) + { + Sleep(MSToSleep); + } + + while(ToSleep > 0) + { + TimeElapsed = Win32_GetSecondsElapsed(FrameBegin, Win32_GetWallClock()); + ToSleep = Target - TimeElapsed; + } + } +} + +inline void Win32_GetRelevantPaths(win32_state *State) +{ + GetModuleFileName(0, State->EXEPath, ArrayCount(State->EXEPath)); + if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + Win32_PlatformError("Path to executable is too long. Try running the game from another directory", true); + } + + s64 OnePastLastSlash = LastIndexOf(MakeStringFromCString(State->EXEPath), '\\') + 1; + + string DLLName = StrLit("vn.dll"); + Copy(State->DLLPath, State->EXEPath, OnePastLastSlash); + Copy(State->DLLPath+OnePastLastSlash, DLLName.Data, DLLName.Count); + + string TempDLLName = StrLit("temp_vn.dll"); + Copy(State->TempDLLPath, State->EXEPath, OnePastLastSlash); + Copy(State->TempDLLPath+OnePastLastSlash, TempDLLName.Data, TempDLLName.Count); +} + +int WinMain(HINSTANCE Instance, HINSTANCE PreviousInstance, LPSTR CommandLine, int ShowCommand) +{ + thread_context ThreadContext = {}; + SetThreadContext(&ThreadContext); + + // sixten: Setup Win32 platform state. + { + win32_state *State = &Global_Win32State; + Win32_GetRelevantPaths(State); + + LARGE_INTEGER FrequencyQuery; + QueryPerformanceFrequency(&FrequencyQuery); + State->PerformanceFrequency = FrequencyQuery.QuadPart; + + State->MemorySentinel.Next = &State->MemorySentinel; + State->MemorySentinel.Prev = &State->MemorySentinel; + + State->SleepIsGranular = (timeBeginPeriod(1) != TIMERR_NOERROR); + } + + // sixten: Setup platform layer + { + Platform.AllocateMemory = Win32_AllocateMemory; + Platform.DeallocateMemory = Win32_DeallocateMemory; + Platform.OpenFile = Win32_OpenFile; + Platform.CloseFile = Win32_CloseFile; + Platform.ReadFile = Win32_ReadFile; + Platform.GetFileSize = Win32_GetFileSize; + Platform.SetCursor = Win32_SetCursor; + Platform.ToggleFullscreen = Win32_ToggleFullscreen; + } + + WNDCLASS WindowClass = {}; + WindowClass.lpszClassName = "vn-window-class"; + WindowClass.lpfnWndProc = Win32_WindowCallback; + WindowClass.hCursor = LoadCursorA(0, IDC_ARROW); + WindowClass.style = CS_OWNDC; + + if(RegisterClassA(&WindowClass)) + { + HWND Window = CreateWindowEx(0, + WindowClass.lpszClassName, + "vn - June 2023 Build", + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + 0, 0, Instance, 0); + if(Window) + { + Global_Win32State.Window = Window; + + vn_input Input = {}; + + // sixten: Setup OpenGL + vn_render_commands RenderCommands = {}; + HDC DeviceContext = GetDC(Window); + Win32_CreateOpenGLContext(DeviceContext); + opengl_context OpenGLContext = OpenGL_SetupContext(&RenderCommands, 16*1024); + + vn_memory Memory = {}; + Memory.PlatformAPI = Platform; + + win32_loaded_code LoadedCode = Win32_LoadCode(); + + ShowWindow(Window, SW_SHOWNORMAL); + + u64 CurrentTime = Win32_GetWallClock(); + + Global_Running = true; + while(Global_Running) + { + u64 NewTime = Win32_GetWallClock(); + r64 dtForFrame = Win32_GetSecondsElapsed(CurrentTime, NewTime); + CurrentTime = NewTime; + + Win32_ProcessInput(&Input, Window, dtForFrame); + + Win32_SetCursor(PlatformCursor_Arrow); + + // sixten: Update and render frame. + { + Win32_UpdateCode(&LoadedCode); + + OpenGL_BeginFrame(&RenderCommands, Win32_GetWindowDim(Window)); + + if(LoadedCode.IsValid) + { + LoadedCode.UpdateAndRender(&ThreadContext, &Memory, &Input, &RenderCommands); + } + + OpenGL_EndFrame(&OpenGLContext, &RenderCommands); + wglSwapLayerBuffers(DeviceContext, WGL_SWAP_MAIN_PLANE); + } + + Win32_EnforceFrameRate(CurrentTime, 144.0); + } + } + else + { + Win32_PlatformError("Unable to create window.", true); + } + } + else + { + Win32_PlatformError("Unable to register window class.", true); + } + + return(0); +} \ No newline at end of file diff --git a/code/win32_main.h b/code/win32_main.h new file mode 100644 index 0000000..1ca0be0 --- /dev/null +++ b/code/win32_main.h @@ -0,0 +1,47 @@ +/* date = May 7th 2023 9:54 am */ + +#ifndef WIN32_MAIN_H +#define WIN32_MAIN_H + +struct win32_memory_block +{ + platform_memory_block Block; + win32_memory_block *Next; + win32_memory_block *Prev; + u64 Padding[2]; +}; + +CTAssert(sizeof(win32_memory_block) == 64); + +struct win32_state +{ + win32_memory_block MemorySentinel; + ticket_mutex MemoryMutex; + + u64 PerformanceFrequency; + b32 SleepIsGranular; + + HWND Window; + + memory_arena EventArena; + temporary_memory EventArenaTemp; + platform_event_list EventList; + + char EXEPath[512]; + char DLLPath[512]; + char TempDLLPath[512]; + + platform_cursor Cursor; +}; + +struct win32_loaded_code +{ + HMODULE DLL; + FILETIME LastWriteTime; + + vn_update_and_render *UpdateAndRender; + + b32 IsValid; +}; + +#endif //WIN32_MAIN_H diff --git a/code/win32_opengl.cpp b/code/win32_opengl.cpp new file mode 100644 index 0000000..50b7ca1 --- /dev/null +++ b/code/win32_opengl.cpp @@ -0,0 +1,120 @@ +inline void *OpenGL_LoadFunction(char *Name) +{ + void *Result = (void *)wglGetProcAddress(Name); + if((Result == 0) || (Result == (void *)1) || (Result == (void *)2) || (Result == (void *)3)) + { + HMODULE Module = LoadLibraryA("opengl32.dll"); + Result = (void *)GetProcAddress(Module, Name); + } + return(Result); +} + +static void *OpenGL_AllocateMemory(umm Size) +{ + void *Result = VirtualAlloc(0, Size, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE); + return(Result); +} + +static void OpenGL_DeallocateMemory(void *Memory) +{ + VirtualFree(Memory, 0, MEM_RELEASE); +} + +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_TYPE_RGBA_ARB 0x202B +#define WGL_COLOR_BITS_ARB 0x2014 +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001 +#define WGL_SAMPLE_BUFFERS_ARB 0x2041 +#define WGL_SAMPLES_ARB 0x2042 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 + +typedef BOOL wgl_choose_pixel_format_arb(HDC hdc, + const int *piAttribIList, + const FLOAT *pfAttribFList, + UINT nMaxFormats, + int *piFormats, + UINT *nNumFormats); + +typedef HGLRC wgl_create_context_attribs_arb(HDC hDC, HGLRC hShareContext, + const int *attribList); + +typedef BOOL wgl_make_context_current_arb(HDC hDrawDC, HDC hReadDC, HGLRC hglrc); + +typedef BOOL wgl_swap_interval_ext(int interval); + +static wgl_choose_pixel_format_arb *wglChoosePixelFormatARB; +static wgl_create_context_attribs_arb *wglCreateContextAttribsARB; +static wgl_make_context_current_arb *wglMakeContextCurrentARB; +static wgl_swap_interval_ext *wglSwapIntervalEXT; + +#include "opengl_render.h" +#include "opengl_render.cpp" + +static void Win32_CreateOpenGLContext(HDC DeviceContext) +{ + PIXELFORMATDESCRIPTOR PixelFormatDesc = {}; + PixelFormatDesc.nSize = sizeof(PixelFormatDesc); + PixelFormatDesc.nVersion = 1; + PixelFormatDesc.dwFlags = PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER; + PixelFormatDesc.iPixelType = PFD_TYPE_RGBA; + PixelFormatDesc.cColorBits = 32; + PixelFormatDesc.cDepthBits = 24; + PixelFormatDesc.cStencilBits = 8; + PixelFormatDesc.iLayerType = PFD_MAIN_PLANE; + + int PixelFormat = ChoosePixelFormat(DeviceContext, &PixelFormatDesc); + Assert(PixelFormat); + + SetPixelFormat(DeviceContext, PixelFormat, &PixelFormatDesc); + + HGLRC DummyContext = wglCreateContext(DeviceContext); + wglMakeCurrent(DeviceContext, DummyContext); + + wglChoosePixelFormatARB = (wgl_choose_pixel_format_arb *)OpenGL_LoadFunction("wglChoosePixelFormatARB"); + wglCreateContextAttribsARB = (wgl_create_context_attribs_arb *)OpenGL_LoadFunction("wglCreateContextAttribsARB"); + wglMakeContextCurrentARB = (wgl_make_context_current_arb *)OpenGL_LoadFunction("wglMakeContextCurrentARB"); + wglSwapIntervalEXT = (wgl_swap_interval_ext *)OpenGL_LoadFunction("wglSwapIntervalEXT"); + + int AttribList[] = + { + WGL_DRAW_TO_WINDOW_ARB, 1, + WGL_SUPPORT_OPENGL_ARB, 1, + WGL_DOUBLE_BUFFER_ARB, 1, + WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, + WGL_COLOR_BITS_ARB, 32, + WGL_DEPTH_BITS_ARB, 24, + WGL_STENCIL_BITS_ARB, 8, + WGL_SAMPLE_BUFFERS_ARB, true, + WGL_SAMPLES_ARB, 16, + 0 + }; + + UINT NumFormats; + wglChoosePixelFormatARB(DeviceContext, AttribList, 0, 1, &PixelFormat, &NumFormats); + + int ContextAttribs[] = + { + WGL_CONTEXT_MAJOR_VERSION_ARB, 3, + WGL_CONTEXT_MINOR_VERSION_ARB, 3, + WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_DEBUG_BIT_ARB, + 0 + }; + + HGLRC RenderContext = wglCreateContextAttribsARB(DeviceContext, DummyContext, ContextAttribs); + + wglMakeCurrent(DeviceContext, RenderContext); + wglDeleteContext(DummyContext); + wglSwapIntervalEXT(1); + + OpenGL_LoadAllFunctions(); +} \ No newline at end of file diff --git a/features.txt b/features.txt new file mode 100644 index 0000000..c96553e --- /dev/null +++ b/features.txt @@ -0,0 +1,5 @@ +vn - release 1 +-------------- + +Right, so we need to have some tools to make a visual novel. In lieu of actual GOOD tools, we will deal with subpar ones instead. Thusly, this first release will only include the bearest tools to get you started, but it should be enough for some basic (text only) scenes. + diff --git a/fonts/Roboto-Bold.ttf b/fonts/Roboto-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7a876e28e8e43ac9ab91fe361228c5e4c4e5253e GIT binary patch literal 128676 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

S&jh1n&JeeMOkU4{=4@cYr6k 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≤FHXCuU9Qce=@vmZ{%9^5??OaVQJ8!4u8-w;yt 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 Ho}T{;Du~7~ literal 0 HcmV?d00001 diff --git a/fonts/liberation-mono.ttf b/fonts/liberation-mono.ttf new file mode 100644 index 0000000000000000000000000000000000000000..61af64ea608fe1eeda010ab3d863a40ba838a6ac GIT binary patch literal 108168 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 aJdFa8m#^_-H;0g?HeZv&!&iQi?*6|EVpiq= literal 0 HcmV?d00001 diff --git a/project.4coder b/project.4coder new file mode 100644 index 0000000..4b5b64f --- /dev/null +++ b/project.4coder @@ -0,0 +1,73 @@ +version(1); + +project_name = "vn"; + +patterns = +{ + "*.c", + "*.cpp", + "*.jai", + "*.odin", + "*.zig", + "*.h", + "*.inc", + "*.bat", + "*.sh", + "*.4coder", + "*.txt", +}; + +blacklist_patterns = +{ + ".*", +}; + +load_paths = +{ + { + { {"."}, .recursive = true, .relative = true }, .os = "win" + }, +}; + +command_list = +{ + { + .name = "build", + .out = "*compilation*", + .footer_panel = true, + .save_dirty_files = true, + .cursor_at_end = false, + .cmd = + { + { "build.bat", .os = "win" }, + }, + }, + + { + .name = "run", + .out = "*compilation*", + .footer_panel = true, + .save_dirty_files = true, + .cursor_at_end = false, + .cmd = + { + { "build\\win32_main.exe", .os = "win" }, + }, + }, + + { + .name = "cloc", + .out = "*compilation*", + .footer_panel = true, + .save_dirty_files = true, + .cursor_at_end = false, + .cmd = + { + { "cloc ./code/*.*", .os = "win" }, + }, + }, + +}; + +fkey_command[2] = "run"; +fkey_command[3] = "cloc"; diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..3baabdf --- /dev/null +++ b/todo.txt @@ -0,0 +1,11 @@ +This is a list of things that needs doing in a SUGGESTED order. + +* UI + - Draggable panels + - Fix weird grey draggable arena between panels at the headers. + - Settings / Preferences view. (Including saving and loading of these settings/preferences) + +* Rendering + - Fix texture clipping + - Control over each corner when rounding +