aboutsummaryrefslogtreecommitdiff
path: root/subprojects/d2tk/nanovg
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/d2tk/nanovg')
-rw-r--r--subprojects/d2tk/nanovg/.gitignore28
-rw-r--r--subprojects/d2tk/nanovg/LICENSE.txt18
-rw-r--r--subprojects/d2tk/nanovg/README.md120
-rw-r--r--subprojects/d2tk/nanovg/example/LICENSE_OFL.txt92
-rw-r--r--subprojects/d2tk/nanovg/example/NotoEmoji-Regular.ttfbin0 -> 418804 bytes
-rwxr-xr-xsubprojects/d2tk/nanovg/example/Roboto-Bold.ttfbin0 -> 135820 bytes
-rwxr-xr-xsubprojects/d2tk/nanovg/example/Roboto-Light.ttfbin0 -> 140276 bytes
-rwxr-xr-xsubprojects/d2tk/nanovg/example/Roboto-Regular.ttfbin0 -> 145348 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/demo.c1226
-rw-r--r--subprojects/d2tk/nanovg/example/demo.h26
-rw-r--r--subprojects/d2tk/nanovg/example/entypo.ttfbin0 -> 35392 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/example_fbo.c272
-rw-r--r--subprojects/d2tk/nanovg/example/example_gl2.c162
-rw-r--r--subprojects/d2tk/nanovg/example/example_gl3.c198
-rw-r--r--subprojects/d2tk/nanovg/example/example_gles2.c155
-rw-r--r--subprojects/d2tk/nanovg/example/example_gles3.c155
-rw-r--r--subprojects/d2tk/nanovg/example/images.txt13
-rw-r--r--subprojects/d2tk/nanovg/example/images/image1.jpgbin0 -> 25760 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image10.jpgbin0 -> 3439 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image11.jpgbin0 -> 3818 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image12.jpgbin0 -> 5452 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image2.jpgbin0 -> 24091 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image3.jpgbin0 -> 29282 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image4.jpgbin0 -> 23830 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image5.jpgbin0 -> 27131 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image6.jpgbin0 -> 25116 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image7.jpgbin0 -> 25590 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image8.jpgbin0 -> 24607 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/images/image9.jpgbin0 -> 4035 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/perf.c186
-rw-r--r--subprojects/d2tk/nanovg/example/perf.h46
-rw-r--r--subprojects/d2tk/nanovg/example/screenshot-01.pngbin0 -> 989424 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/screenshot-02.pngbin0 -> 229443 bytes
-rw-r--r--subprojects/d2tk/nanovg/example/stb_image_write.h511
-rw-r--r--subprojects/d2tk/nanovg/obsolete/nanovg_gl2.h952
-rw-r--r--subprojects/d2tk/nanovg/obsolete/nanovg_gl3.h957
-rw-r--r--subprojects/d2tk/nanovg/obsolete/obsolete.md6
-rw-r--r--subprojects/d2tk/nanovg/premake4.lua226
-rw-r--r--subprojects/d2tk/nanovg/src/fontstash.h1768
-rw-r--r--subprojects/d2tk/nanovg/src/nanovg.c2913
-rw-r--r--subprojects/d2tk/nanovg/src/nanovg.h685
-rw-r--r--subprojects/d2tk/nanovg/src/nanovg_gl.h1649
-rw-r--r--subprojects/d2tk/nanovg/src/nanovg_gl_utils.h154
-rw-r--r--subprojects/d2tk/nanovg/src/stb_image.h6614
-rw-r--r--subprojects/d2tk/nanovg/src/stb_truetype.h3249
45 files changed, 22381 insertions, 0 deletions
diff --git a/subprojects/d2tk/nanovg/.gitignore b/subprojects/d2tk/nanovg/.gitignore
new file mode 100644
index 0000000..70f534e
--- /dev/null
+++ b/subprojects/d2tk/nanovg/.gitignore
@@ -0,0 +1,28 @@
+## Compiled source #
+*.com
+*.class
+*.dll
+*.exe
+*.o
+*.so
+test
+
+## Logs and databases #
+*.log
+*.sql
+*.sqlite
+
+## OS generated files #
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+
+## Build dir
+build/*
+
+## xcode specific
+*xcuserdata*
diff --git a/subprojects/d2tk/nanovg/LICENSE.txt b/subprojects/d2tk/nanovg/LICENSE.txt
new file mode 100644
index 0000000..2a03a1a
--- /dev/null
+++ b/subprojects/d2tk/nanovg/LICENSE.txt
@@ -0,0 +1,18 @@
+Copyright (c) 2013 Mikko Mononen memon@inside.org
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+claim that you wrote the original software. If you use this software
+in a product, an acknowledgment in the product documentation would be
+appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+
diff --git a/subprojects/d2tk/nanovg/README.md b/subprojects/d2tk/nanovg/README.md
new file mode 100644
index 0000000..4f9709b
--- /dev/null
+++ b/subprojects/d2tk/nanovg/README.md
@@ -0,0 +1,120 @@
+NanoVG
+==========
+
+NanoVG is small antialiased vector graphics rendering library for OpenGL. It has lean API modeled after HTML5 canvas API. It is aimed to be a practical and fun toolset for building scalable user interfaces and visualizations.
+
+## Screenshot
+
+![screenshot of some text rendered witht the sample program](/example/screenshot-01.png?raw=true)
+
+Usage
+=====
+
+The NanoVG API is modeled loosely on HTML5 canvas API. If you know canvas, you're up to speed with NanoVG in no time.
+
+## Creating drawing context
+
+The drawing context is created using platform specific constructor function. If you're using the OpenGL 2.0 back-end the context is created as follows:
+```C
+#define NANOVG_GL2_IMPLEMENTATION // Use GL2 implementation.
+#include "nanovg_gl.h"
+...
+struct NVGcontext* vg = nvgCreateGL2(NVG_ANTIALIAS | NVG_STENCIL_STROKES);
+```
+
+The first parameter defines flags for creating the renderer.
+
+- `NVG_ANTIALIAS` means that the renderer adjusts the geometry to include anti-aliasing. If you're using MSAA, you can omit this flags.
+- `NVG_STENCIL_STROKES` means that the render uses better quality rendering for (overlapping) strokes. The quality is mostly visible on wider strokes. If you want speed, you can omit this flag.
+
+Currently there is an OpenGL back-end for NanoVG: [nanovg_gl.h](/src/nanovg_gl.h) for OpenGL 2.0, OpenGL ES 2.0, OpenGL 3.2 core profile and OpenGL ES 3. The implementation can be chosen using a define as in above example. See the header file and examples for further info.
+
+*NOTE:* The render target you're rendering to must have stencil buffer.
+
+## Drawing shapes with NanoVG
+
+Drawing a simple shape using NanoVG consists of four steps: 1) begin a new shape, 2) define the path to draw, 3) set fill or stroke, 4) and finally fill or stroke the path.
+
+```C
+nvgBeginPath(vg);
+nvgRect(vg, 100,100, 120,30);
+nvgFillColor(vg, nvgRGBA(255,192,0,255));
+nvgFill(vg);
+```
+
+Calling `nvgBeginPath()` will clear any existing paths and start drawing from blank slate. There are number of number of functions to define the path to draw, such as rectangle, rounded rectangle and ellipse, or you can use the common moveTo, lineTo, bezierTo and arcTo API to compose the paths step by step.
+
+## Understanding Composite Paths
+
+Because of the way the rendering backend is build in NanoVG, drawing a composite path, that is path consisting from multiple paths defining holes and fills, is a bit more involved. NanoVG uses even-odd filling rule and by default the paths are wound in counter clockwise order. Keep that in mind when drawing using the low level draw API. In order to wind one of the predefined shapes as a hole, you should call `nvgPathWinding(vg, NVG_HOLE)`, or `nvgPathWinding(vg, NVG_CW)` _after_ defining the path.
+
+``` C
+nvgBeginPath(vg);
+nvgRect(vg, 100,100, 120,30);
+nvgCircle(vg, 120,120, 5);
+nvgPathWinding(vg, NVG_HOLE); // Mark circle as a hole.
+nvgFillColor(vg, nvgRGBA(255,192,0,255));
+nvgFill(vg);
+```
+
+## Rendering is wrong, what to do?
+
+- make sure you have created NanoVG context using one of the `nvgCreatexxx()` calls
+- make sure you have initialised OpenGL with *stencil buffer*
+- make sure you have cleared stencil buffer
+- make sure all rendering calls happen between `nvgBeginFrame()` and `nvgEndFrame()`
+- to enable more checks for OpenGL errors, add `NVG_DEBUG` flag to `nvgCreatexxx()`
+- if the problem still persists, please report an issue!
+
+## OpenGL state touched by the backend
+
+The OpenGL back-end touches following states:
+
+When textures are uploaded or updated, the following pixel store is set to defaults: `GL_UNPACK_ALIGNMENT`, `GL_UNPACK_ROW_LENGTH`, `GL_UNPACK_SKIP_PIXELS`, `GL_UNPACK_SKIP_ROWS`. Texture binding is also affected. Texture updates can happen when the user loads images, or when new font glyphs are added. Glyphs are added as needed between calls to `nvgBeginFrame()` and `nvgEndFrame()`.
+
+The data for the whole frame is buffered and flushed in `nvgEndFrame()`. The following code illustrates the OpenGL state touched by the rendering code:
+```C
+ glUseProgram(prog);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable(GL_CULL_FACE);
+ glCullFace(GL_BACK);
+ glFrontFace(GL_CCW);
+ glEnable(GL_BLEND);
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_SCISSOR_TEST);
+ glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ glStencilMask(0xffffffff);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+ glStencilFunc(GL_ALWAYS, 0, 0xffffffff);
+ glActiveTexture(GL_TEXTURE0);
+ glBindBuffer(GL_UNIFORM_BUFFER, buf);
+ glBindVertexArray(arr);
+ glBindBuffer(GL_ARRAY_BUFFER, buf);
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glUniformBlockBinding(... , GLNVG_FRAG_BINDING);
+```
+
+## API Reference
+
+See the header file [nanovg.h](/src/nanovg.h) for API reference.
+
+## Ports
+
+- [DX11 port](https://github.com/cmaughan/nanovg) by [Chris Maughan](https://github.com/cmaughan)
+- [Metal port](https://github.com/ollix/MetalNanoVG) by [Olli Wang](https://github.com/olliwang)
+- [bgfx port](https://github.com/bkaradzic/bgfx/tree/master/examples/20-nanovg) by [Branimir Karadžić](https://github.com/bkaradzic)
+
+## Projects using NanoVG
+
+- [Processing API simulation by vinjn](https://github.com/vinjn/island/blob/master/examples/01-processing/sketch2d.h)
+- [NanoVG for .NET, C# P/Invoke binding](https://github.com/sbarisic/nanovg_dotnet)
+
+## License
+The library is licensed under [zlib license](LICENSE.txt)
+
+## Discussions
+[NanoVG mailing list](https://groups.google.com/forum/#!forum/nanovg)
+
+## Links
+Uses [stb_truetype](http://nothings.org) (or, optionally, [freetype](http://freetype.org)) for font rendering.
+Uses [stb_image](http://nothings.org) for image loading.
diff --git a/subprojects/d2tk/nanovg/example/LICENSE_OFL.txt b/subprojects/d2tk/nanovg/example/LICENSE_OFL.txt
new file mode 100644
index 0000000..d952d62
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/LICENSE_OFL.txt
@@ -0,0 +1,92 @@
+This Font Software is licensed under the SIL Open Font License,
+Version 1.1.
+
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font
+creation efforts of academic and linguistic communities, and to
+provide a free and open framework in which fonts may be shared and
+improved in partnership with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply to
+any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software
+components as distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to,
+deleting, or substituting -- in part or in whole -- any of the
+components of the Original Version, by changing formats or by porting
+the Font Software to a new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed,
+modify, redistribute, and sell modified and unmodified copies of the
+Font Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components, in
+Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the
+corresponding Copyright Holder. This restriction only applies to the
+primary font name as presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created using
+the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/subprojects/d2tk/nanovg/example/NotoEmoji-Regular.ttf b/subprojects/d2tk/nanovg/example/NotoEmoji-Regular.ttf
new file mode 100644
index 0000000..19b7bad
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/NotoEmoji-Regular.ttf
Binary files differ
diff --git a/subprojects/d2tk/nanovg/example/Roboto-Bold.ttf b/subprojects/d2tk/nanovg/example/Roboto-Bold.ttf
new file mode 100755
index 0000000..aaf374d
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/Roboto-Bold.ttf
Binary files differ
diff --git a/subprojects/d2tk/nanovg/example/Roboto-Light.ttf b/subprojects/d2tk/nanovg/example/Roboto-Light.ttf
new file mode 100755
index 0000000..664e1b2
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/Roboto-Light.ttf
Binary files differ
diff --git a/subprojects/d2tk/nanovg/example/Roboto-Regular.ttf b/subprojects/d2tk/nanovg/example/Roboto-Regular.ttf
new file mode 100755
index 0000000..3e6e2e7
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/Roboto-Regular.ttf
Binary files differ
diff --git a/subprojects/d2tk/nanovg/example/demo.c b/subprojects/d2tk/nanovg/example/demo.c
new file mode 100644
index 0000000..cfad99e
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/demo.c
@@ -0,0 +1,1226 @@
+#include "demo.h"
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#ifdef NANOVG_GLEW
+# include <GL/glew.h>
+#endif
+#include <GLFW/glfw3.h>
+#include "nanovg.h"
+#define STB_IMAGE_WRITE_IMPLEMENTATION
+#include "stb_image_write.h"
+
+
+#ifdef _MSC_VER
+#define snprintf _snprintf
+#elif !defined(__MINGW32__)
+#include <iconv.h>
+#endif
+
+#define ICON_SEARCH 0x1F50D
+#define ICON_CIRCLED_CROSS 0x2716
+#define ICON_CHEVRON_RIGHT 0xE75E
+#define ICON_CHECK 0x2713
+#define ICON_LOGIN 0xE740
+#define ICON_TRASH 0xE729
+
+//static float minf(float a, float b) { return a < b ? a : b; }
+static float maxf(float a, float b) { return a > b ? a : b; }
+//static float absf(float a) { return a >= 0.0f ? a : -a; }
+static float clampf(float a, float mn, float mx) { return a < mn ? mn : (a > mx ? mx : a); }
+
+// Returns 1 if col.rgba is 0.0f,0.0f,0.0f,0.0f, 0 otherwise
+int isBlack(NVGcolor col)
+{
+ if( col.r == 0.0f && col.g == 0.0f && col.b == 0.0f && col.a == 0.0f )
+ {
+ return 1;
+ }
+ return 0;
+}
+
+static char* cpToUTF8(int cp, char* str)
+{
+ int n = 0;
+ if (cp < 0x80) n = 1;
+ else if (cp < 0x800) n = 2;
+ else if (cp < 0x10000) n = 3;
+ else if (cp < 0x200000) n = 4;
+ else if (cp < 0x4000000) n = 5;
+ else if (cp <= 0x7fffffff) n = 6;
+ str[n] = '\0';
+ switch (n) {
+ case 6: str[5] = 0x80 | (cp & 0x3f); cp = cp >> 6; cp |= 0x4000000;
+ case 5: str[4] = 0x80 | (cp & 0x3f); cp = cp >> 6; cp |= 0x200000;
+ case 4: str[3] = 0x80 | (cp & 0x3f); cp = cp >> 6; cp |= 0x10000;
+ case 3: str[2] = 0x80 | (cp & 0x3f); cp = cp >> 6; cp |= 0x800;
+ case 2: str[1] = 0x80 | (cp & 0x3f); cp = cp >> 6; cp |= 0xc0;
+ case 1: str[0] = cp;
+ }
+ return str;
+}
+
+
+void drawWindow(NVGcontext* vg, const char* title, float x, float y, float w, float h)
+{
+ float cornerRadius = 3.0f;
+ NVGpaint shadowPaint;
+ NVGpaint headerPaint;
+
+ nvgSave(vg);
+// nvgClearState(vg);
+
+ // Window
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x,y, w,h, cornerRadius);
+ nvgFillColor(vg, nvgRGBA(28,30,34,192));
+// nvgFillColor(vg, nvgRGBA(0,0,0,128));
+ nvgFill(vg);
+
+ // Drop shadow
+ shadowPaint = nvgBoxGradient(vg, x,y+2, w,h, cornerRadius*2, 10, nvgRGBA(0,0,0,128), nvgRGBA(0,0,0,0));
+ nvgBeginPath(vg);
+ nvgRect(vg, x-10,y-10, w+20,h+30);
+ nvgRoundedRect(vg, x,y, w,h, cornerRadius);
+ nvgPathWinding(vg, NVG_HOLE);
+ nvgFillPaint(vg, shadowPaint);
+ nvgFill(vg);
+
+ // Header
+ headerPaint = nvgLinearGradient(vg, x,y,x,y+15, nvgRGBA(255,255,255,8), nvgRGBA(0,0,0,16));
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+1,y+1, w-2,30, cornerRadius-1);
+ nvgFillPaint(vg, headerPaint);
+ nvgFill(vg);
+ nvgBeginPath(vg);
+ nvgMoveTo(vg, x+0.5f, y+0.5f+30);
+ nvgLineTo(vg, x+0.5f+w-1, y+0.5f+30);
+ nvgStrokeColor(vg, nvgRGBA(0,0,0,32));
+ nvgStroke(vg);
+
+ nvgFontSize(vg, 18.0f);
+ nvgFontFace(vg, "sans-bold");
+ nvgTextAlign(vg,NVG_ALIGN_CENTER|NVG_ALIGN_MIDDLE);
+
+ nvgFontBlur(vg,2);
+ nvgFillColor(vg, nvgRGBA(0,0,0,128));
+ nvgText(vg, x+w/2,y+16+1, title, NULL);
+
+ nvgFontBlur(vg,0);
+ nvgFillColor(vg, nvgRGBA(220,220,220,160));
+ nvgText(vg, x+w/2,y+16, title, NULL);
+
+ nvgRestore(vg);
+}
+
+void drawSearchBox(NVGcontext* vg, const char* text, float x, float y, float w, float h)
+{
+ NVGpaint bg;
+ char icon[8];
+ float cornerRadius = h/2-1;
+
+ // Edit
+ bg = nvgBoxGradient(vg, x,y+1.5f, w,h, h/2,5, nvgRGBA(0,0,0,16), nvgRGBA(0,0,0,92));
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x,y, w,h, cornerRadius);
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+
+/* nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+0.5f,y+0.5f, w-1,h-1, cornerRadius-0.5f);
+ nvgStrokeColor(vg, nvgRGBA(0,0,0,48));
+ nvgStroke(vg);*/
+
+ nvgFontSize(vg, h*1.3f);
+ nvgFontFace(vg, "icons");
+ nvgFillColor(vg, nvgRGBA(255,255,255,64));
+ nvgTextAlign(vg,NVG_ALIGN_CENTER|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+h*0.55f, y+h*0.55f, cpToUTF8(ICON_SEARCH,icon), NULL);
+
+ nvgFontSize(vg, 20.0f);
+ nvgFontFace(vg, "sans");
+ nvgFillColor(vg, nvgRGBA(255,255,255,32));
+
+ nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+h*1.05f,y+h*0.5f,text, NULL);
+
+ nvgFontSize(vg, h*1.3f);
+ nvgFontFace(vg, "icons");
+ nvgFillColor(vg, nvgRGBA(255,255,255,32));
+ nvgTextAlign(vg,NVG_ALIGN_CENTER|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+w-h*0.55f, y+h*0.55f, cpToUTF8(ICON_CIRCLED_CROSS,icon), NULL);
+}
+
+void drawDropDown(NVGcontext* vg, const char* text, float x, float y, float w, float h)
+{
+ NVGpaint bg;
+ char icon[8];
+ float cornerRadius = 4.0f;
+
+ bg = nvgLinearGradient(vg, x,y,x,y+h, nvgRGBA(255,255,255,16), nvgRGBA(0,0,0,16));
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+1,y+1, w-2,h-2, cornerRadius-1);
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+0.5f,y+0.5f, w-1,h-1, cornerRadius-0.5f);
+ nvgStrokeColor(vg, nvgRGBA(0,0,0,48));
+ nvgStroke(vg);
+
+ nvgFontSize(vg, 20.0f);
+ nvgFontFace(vg, "sans");
+ nvgFillColor(vg, nvgRGBA(255,255,255,160));
+ nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+h*0.3f,y+h*0.5f,text, NULL);
+
+ nvgFontSize(vg, h*1.3f);
+ nvgFontFace(vg, "icons");
+ nvgFillColor(vg, nvgRGBA(255,255,255,64));
+ nvgTextAlign(vg,NVG_ALIGN_CENTER|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+w-h*0.5f, y+h*0.5f, cpToUTF8(ICON_CHEVRON_RIGHT,icon), NULL);
+}
+
+void drawLabel(NVGcontext* vg, const char* text, float x, float y, float w, float h)
+{
+ NVG_NOTUSED(w);
+
+ nvgFontSize(vg, 18.0f);
+ nvgFontFace(vg, "sans");
+ nvgFillColor(vg, nvgRGBA(255,255,255,128));
+
+ nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x,y+h*0.5f,text, NULL);
+}
+
+void drawEditBoxBase(NVGcontext* vg, float x, float y, float w, float h)
+{
+ NVGpaint bg;
+ // Edit
+ bg = nvgBoxGradient(vg, x+1,y+1+1.5f, w-2,h-2, 3,4, nvgRGBA(255,255,255,32), nvgRGBA(32,32,32,32));
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+1,y+1, w-2,h-2, 4-1);
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+0.5f,y+0.5f, w-1,h-1, 4-0.5f);
+ nvgStrokeColor(vg, nvgRGBA(0,0,0,48));
+ nvgStroke(vg);
+}
+
+void drawEditBox(NVGcontext* vg, const char* text, float x, float y, float w, float h)
+{
+
+ drawEditBoxBase(vg, x,y, w,h);
+
+ nvgFontSize(vg, 20.0f);
+ nvgFontFace(vg, "sans");
+ nvgFillColor(vg, nvgRGBA(255,255,255,64));
+ nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+h*0.3f,y+h*0.5f,text, NULL);
+}
+
+void drawEditBoxNum(NVGcontext* vg,
+ const char* text, const char* units, float x, float y, float w, float h)
+{
+ float uw;
+
+ drawEditBoxBase(vg, x,y, w,h);
+
+ uw = nvgTextBounds(vg, 0,0, units, NULL, NULL);
+
+ nvgFontSize(vg, 18.0f);
+ nvgFontFace(vg, "sans");
+ nvgFillColor(vg, nvgRGBA(255,255,255,64));
+ nvgTextAlign(vg,NVG_ALIGN_RIGHT|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+w-h*0.3f,y+h*0.5f,units, NULL);
+
+ nvgFontSize(vg, 20.0f);
+ nvgFontFace(vg, "sans");
+ nvgFillColor(vg, nvgRGBA(255,255,255,128));
+ nvgTextAlign(vg,NVG_ALIGN_RIGHT|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+w-uw-h*0.5f,y+h*0.5f,text, NULL);
+}
+
+void drawCheckBox(NVGcontext* vg, const char* text, float x, float y, float w, float h)
+{
+ NVGpaint bg;
+ char icon[8];
+ NVG_NOTUSED(w);
+
+ nvgFontSize(vg, 18.0f);
+ nvgFontFace(vg, "sans");
+ nvgFillColor(vg, nvgRGBA(255,255,255,160));
+
+ nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+28,y+h*0.5f,text, NULL);
+
+ bg = nvgBoxGradient(vg, x+1,y+(int)(h*0.5f)-9+1, 18,18, 3,3, nvgRGBA(0,0,0,32), nvgRGBA(0,0,0,92));
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+1,y+(int)(h*0.5f)-9, 18,18, 3);
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+
+ nvgFontSize(vg, 40);
+ nvgFontFace(vg, "icons");
+ nvgFillColor(vg, nvgRGBA(255,255,255,128));
+ nvgTextAlign(vg,NVG_ALIGN_CENTER|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+9+2, y+h*0.5f, cpToUTF8(ICON_CHECK,icon), NULL);
+}
+
+void drawButton(NVGcontext* vg, int preicon, const char* text, float x, float y, float w, float h, NVGcolor col)
+{
+ NVGpaint bg;
+ char icon[8];
+ float cornerRadius = 4.0f;
+ float tw = 0, iw = 0;
+
+ bg = nvgLinearGradient(vg, x,y,x,y+h, nvgRGBA(255,255,255,isBlack(col)?16:32), nvgRGBA(0,0,0,isBlack(col)?16:32));
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+1,y+1, w-2,h-2, cornerRadius-1);
+ if (!isBlack(col)) {
+ nvgFillColor(vg, col);
+ nvgFill(vg);
+ }
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+0.5f,y+0.5f, w-1,h-1, cornerRadius-0.5f);
+ nvgStrokeColor(vg, nvgRGBA(0,0,0,48));
+ nvgStroke(vg);
+
+ nvgFontSize(vg, 20.0f);
+ nvgFontFace(vg, "sans-bold");
+ tw = nvgTextBounds(vg, 0,0, text, NULL, NULL);
+ if (preicon != 0) {
+ nvgFontSize(vg, h*1.3f);
+ nvgFontFace(vg, "icons");
+ iw = nvgTextBounds(vg, 0,0, cpToUTF8(preicon,icon), NULL, NULL);
+ iw += h*0.15f;
+ }
+
+ if (preicon != 0) {
+ nvgFontSize(vg, h*1.3f);
+ nvgFontFace(vg, "icons");
+ nvgFillColor(vg, nvgRGBA(255,255,255,96));
+ nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE);
+ nvgText(vg, x+w*0.5f-tw*0.5f-iw*0.75f, y+h*0.5f, cpToUTF8(preicon,icon), NULL);
+ }
+
+ nvgFontSize(vg, 20.0f);
+ nvgFontFace(vg, "sans-bold");
+ nvgTextAlign(vg,NVG_ALIGN_LEFT|NVG_ALIGN_MIDDLE);
+ nvgFillColor(vg, nvgRGBA(0,0,0,160));
+ nvgText(vg, x+w*0.5f-tw*0.5f+iw*0.25f,y+h*0.5f-1,text, NULL);
+ nvgFillColor(vg, nvgRGBA(255,255,255,160));
+ nvgText(vg, x+w*0.5f-tw*0.5f+iw*0.25f,y+h*0.5f,text, NULL);
+}
+
+void drawSlider(NVGcontext* vg, float pos, float x, float y, float w, float h)
+{
+ NVGpaint bg, knob;
+ float cy = y+(int)(h*0.5f);
+ float kr = (int)(h*0.25f);
+
+ nvgSave(vg);
+// nvgClearState(vg);
+
+ // Slot
+ bg = nvgBoxGradient(vg, x,cy-2+1, w,4, 2,2, nvgRGBA(0,0,0,32), nvgRGBA(0,0,0,128));
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x,cy-2, w,4, 2);
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+
+ // Knob Shadow
+ bg = nvgRadialGradient(vg, x+(int)(pos*w),cy+1, kr-3,kr+3, nvgRGBA(0,0,0,64), nvgRGBA(0,0,0,0));
+ nvgBeginPath(vg);
+ nvgRect(vg, x+(int)(pos*w)-kr-5,cy-kr-5,kr*2+5+5,kr*2+5+5+3);
+ nvgCircle(vg, x+(int)(pos*w),cy, kr);
+ nvgPathWinding(vg, NVG_HOLE);
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+
+ // Knob
+ knob = nvgLinearGradient(vg, x,cy-kr,x,cy+kr, nvgRGBA(255,255,255,16), nvgRGBA(0,0,0,16));
+ nvgBeginPath(vg);
+ nvgCircle(vg, x+(int)(pos*w),cy, kr-1);
+ nvgFillColor(vg, nvgRGBA(40,43,48,255));
+ nvgFill(vg);
+ nvgFillPaint(vg, knob);
+ nvgFill(vg);
+
+ nvgBeginPath(vg);
+ nvgCircle(vg, x+(int)(pos*w),cy, kr-0.5f);
+ nvgStrokeColor(vg, nvgRGBA(0,0,0,92));
+ nvgStroke(vg);
+
+ nvgRestore(vg);
+}
+
+void drawEyes(NVGcontext* vg, float x, float y, float w, float h, float mx, float my, float t)
+{
+ NVGpaint gloss, bg;
+ float ex = w *0.23f;
+ float ey = h * 0.5f;
+ float lx = x + ex;
+ float ly = y + ey;
+ float rx = x + w - ex;
+ float ry = y + ey;
+ float dx,dy,d;
+ float br = (ex < ey ? ex : ey) * 0.5f;
+ float blink = 1 - pow(sinf(t*0.5f),200)*0.8f;
+
+ bg = nvgLinearGradient(vg, x,y+h*0.5f,x+w*0.1f,y+h, nvgRGBA(0,0,0,32), nvgRGBA(0,0,0,16));
+ nvgBeginPath(vg);
+ nvgEllipse(vg, lx+3.0f,ly+16.0f, ex,ey);
+ nvgEllipse(vg, rx+3.0f,ry+16.0f, ex,ey);
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+
+ bg = nvgLinearGradient(vg, x,y+h*0.25f,x+w*0.1f,y+h, nvgRGBA(220,220,220,255), nvgRGBA(128,128,128,255));
+ nvgBeginPath(vg);
+ nvgEllipse(vg, lx,ly, ex,ey);
+ nvgEllipse(vg, rx,ry, ex,ey);
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+
+ dx = (mx - rx) / (ex * 10);
+ dy = (my - ry) / (ey * 10);
+ d = sqrtf(dx*dx+dy*dy);
+ if (d > 1.0f) {
+ dx /= d; dy /= d;
+ }
+ dx *= ex*0.4f;
+ dy *= ey*0.5f;
+ nvgBeginPath(vg);
+ nvgEllipse(vg, lx+dx,ly+dy+ey*0.25f*(1-blink), br,br*blink);
+ nvgFillColor(vg, nvgRGBA(32,32,32,255));
+ nvgFill(vg);
+
+ dx = (mx - rx) / (ex * 10);
+ dy = (my - ry) / (ey * 10);
+ d = sqrtf(dx*dx+dy*dy);
+ if (d > 1.0f) {
+ dx /= d; dy /= d;
+ }
+ dx *= ex*0.4f;
+ dy *= ey*0.5f;
+ nvgBeginPath(vg);
+ nvgEllipse(vg, rx+dx,ry+dy+ey*0.25f*(1-blink), br,br*blink);
+ nvgFillColor(vg, nvgRGBA(32,32,32,255));
+ nvgFill(vg);
+
+ gloss = nvgRadialGradient(vg, lx-ex*0.25f,ly-ey*0.5f, ex*0.1f,ex*0.75f, nvgRGBA(255,255,255,128), nvgRGBA(255,255,255,0));
+ nvgBeginPath(vg);
+ nvgEllipse(vg, lx,ly, ex,ey);
+ nvgFillPaint(vg, gloss);
+ nvgFill(vg);
+
+ gloss = nvgRadialGradient(vg, rx-ex*0.25f,ry-ey*0.5f, ex*0.1f,ex*0.75f, nvgRGBA(255,255,255,128), nvgRGBA(255,255,255,0));
+ nvgBeginPath(vg);
+ nvgEllipse(vg, rx,ry, ex,ey);
+ nvgFillPaint(vg, gloss);
+ nvgFill(vg);
+}
+
+void drawGraph(NVGcontext* vg, float x, float y, float w, float h, float t)
+{
+ NVGpaint bg;
+ float samples[6];
+ float sx[6], sy[6];
+ float dx = w/5.0f;
+ int i;
+
+ samples[0] = (1+sinf(t*1.2345f+cosf(t*0.33457f)*0.44f))*0.5f;
+ samples[1] = (1+sinf(t*0.68363f+cosf(t*1.3f)*1.55f))*0.5f;
+ samples[2] = (1+sinf(t*1.1642f+cosf(t*0.33457)*1.24f))*0.5f;
+ samples[3] = (1+sinf(t*0.56345f+cosf(t*1.63f)*0.14f))*0.5f;
+ samples[4] = (1+sinf(t*1.6245f+cosf(t*0.254f)*0.3f))*0.5f;
+ samples[5] = (1+sinf(t*0.345f+cosf(t*0.03f)*0.6f))*0.5f;
+
+ for (i = 0; i < 6; i++) {
+ sx[i] = x+i*dx;
+ sy[i] = y+h*samples[i]*0.8f;
+ }
+
+ // Graph background
+ bg = nvgLinearGradient(vg, x,y,x,y+h, nvgRGBA(0,160,192,0), nvgRGBA(0,160,192,64));
+ nvgBeginPath(vg);
+ nvgMoveTo(vg, sx[0], sy[0]);
+ for (i = 1; i < 6; i++)
+ nvgBezierTo(vg, sx[i-1]+dx*0.5f,sy[i-1], sx[i]-dx*0.5f,sy[i], sx[i],sy[i]);
+ nvgLineTo(vg, x+w, y+h);
+ nvgLineTo(vg, x, y+h);
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+
+ // Graph line
+ nvgBeginPath(vg);
+ nvgMoveTo(vg, sx[0], sy[0]+2);
+ for (i = 1; i < 6; i++)
+ nvgBezierTo(vg, sx[i-1]+dx*0.5f,sy[i-1]+2, sx[i]-dx*0.5f,sy[i]+2, sx[i],sy[i]+2);
+ nvgStrokeColor(vg, nvgRGBA(0,0,0,32));
+ nvgStrokeWidth(vg, 3.0f);
+ nvgStroke(vg);
+
+ nvgBeginPath(vg);
+ nvgMoveTo(vg, sx[0], sy[0]);
+ for (i = 1; i < 6; i++)
+ nvgBezierTo(vg, sx[i-1]+dx*0.5f,sy[i-1], sx[i]-dx*0.5f,sy[i], sx[i],sy[i]);
+ nvgStrokeColor(vg, nvgRGBA(0,160,192,255));
+ nvgStrokeWidth(vg, 3.0f);
+ nvgStroke(vg);
+
+ // Graph sample pos
+ for (i = 0; i < 6; i++) {
+ bg = nvgRadialGradient(vg, sx[i],sy[i]+2, 3.0f,8.0f, nvgRGBA(0,0,0,32), nvgRGBA(0,0,0,0));
+ nvgBeginPath(vg);
+ nvgRect(vg, sx[i]-10, sy[i]-10+2, 20,20);
+ nvgFillPaint(vg, bg);
+ nvgFill(vg);
+ }
+
+ nvgBeginPath(vg);
+ for (i = 0; i < 6; i++)
+ nvgCircle(vg, sx[i], sy[i], 4.0f);
+ nvgFillColor(vg, nvgRGBA(0,160,192,255));
+ nvgFill(vg);
+ nvgBeginPath(vg);
+ for (i = 0; i < 6; i++)
+ nvgCircle(vg, sx[i], sy[i], 2.0f);
+ nvgFillColor(vg, nvgRGBA(220,220,220,255));
+ nvgFill(vg);
+
+ nvgStrokeWidth(vg, 1.0f);
+}
+
+void drawSpinner(NVGcontext* vg, float cx, float cy, float r, float t)
+{
+ float a0 = 0.0f + t*6;
+ float a1 = NVG_PI + t*6;
+ float r0 = r;
+ float r1 = r * 0.75f;
+ float ax,ay, bx,by;
+ NVGpaint paint;
+
+ nvgSave(vg);
+
+ nvgBeginPath(vg);
+ nvgArc(vg, cx,cy, r0, a0, a1, NVG_CW);
+ nvgArc(vg, cx,cy, r1, a1, a0, NVG_CCW);
+ nvgClosePath(vg);
+ ax = cx + cosf(a0) * (r0+r1)*0.5f;
+ ay = cy + sinf(a0) * (r0+r1)*0.5f;
+ bx = cx + cosf(a1) * (r0+r1)*0.5f;
+ by = cy + sinf(a1) * (r0+r1)*0.5f;
+ paint = nvgLinearGradient(vg, ax,ay, bx,by, nvgRGBA(0,0,0,0), nvgRGBA(0,0,0,128));
+ nvgFillPaint(vg, paint);
+ nvgFill(vg);
+
+ nvgRestore(vg);
+}
+
+void drawThumbnails(NVGcontext* vg, float x, float y, float w, float h, const int* images, int nimages, float t)
+{
+ float cornerRadius = 3.0f;
+ NVGpaint shadowPaint, imgPaint, fadePaint;
+ float ix,iy,iw,ih;
+ float thumb = 60.0f;
+ float arry = 30.5f;
+ int imgw, imgh;
+ float stackh = (nimages/2) * (thumb+10) + 10;
+ int i;
+ float u = (1+cosf(t*0.5f))*0.5f;
+ float u2 = (1-cosf(t*0.2f))*0.5f;
+ float scrollh, dv;
+
+ nvgSave(vg);
+// nvgClearState(vg);
+
+ // Drop shadow
+ shadowPaint = nvgBoxGradient(vg, x,y+4, w,h, cornerRadius*2, 20, nvgRGBA(0,0,0,128), nvgRGBA(0,0,0,0));
+ nvgBeginPath(vg);
+ nvgRect(vg, x-10,y-10, w+20,h+30);
+ nvgRoundedRect(vg, x,y, w,h, cornerRadius);
+ nvgPathWinding(vg, NVG_HOLE);
+ nvgFillPaint(vg, shadowPaint);
+ nvgFill(vg);
+
+ // Window
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x,y, w,h, cornerRadius);
+ nvgMoveTo(vg, x-10,y+arry);
+ nvgLineTo(vg, x+1,y+arry-11);
+ nvgLineTo(vg, x+1,y+arry+11);
+ nvgFillColor(vg, nvgRGBA(200,200,200,255));
+ nvgFill(vg);
+
+ nvgSave(vg);
+ nvgScissor(vg, x,y,w,h);
+ nvgTranslate(vg, 0, -(stackh - h)*u);
+
+ dv = 1.0f / (float)(nimages-1);
+
+ for (i = 0; i < nimages; i++) {
+ float tx, ty, v, a;
+ tx = x+10;
+ ty = y+10;
+ tx += (i%2) * (thumb+10);
+ ty += (i/2) * (thumb+10);
+ nvgImageSize(vg, images[i], &imgw, &imgh);
+ if (imgw < imgh) {
+ iw = thumb;
+ ih = iw * (float)imgh/(float)imgw;
+ ix = 0;
+ iy = -(ih-thumb)*0.5f;
+ } else {
+ ih = thumb;
+ iw = ih * (float)imgw/(float)imgh;
+ ix = -(iw-thumb)*0.5f;
+ iy = 0;
+ }
+
+ v = i * dv;
+ a = clampf((u2-v) / dv, 0, 1);
+
+ if (a < 1.0f)
+ drawSpinner(vg, tx+thumb/2,ty+thumb/2, thumb*0.25f, t);
+
+ imgPaint = nvgImagePattern(vg, tx+ix, ty+iy, iw,ih, 0.0f/180.0f*NVG_PI, images[i], a);
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, tx,ty, thumb,thumb, 5);
+ nvgFillPaint(vg, imgPaint);
+ nvgFill(vg);
+
+ shadowPaint = nvgBoxGradient(vg, tx-1,ty, thumb+2,thumb+2, 5, 3, nvgRGBA(0,0,0,128), nvgRGBA(0,0,0,0));
+ nvgBeginPath(vg);
+ nvgRect(vg, tx-5,ty-5, thumb+10,thumb+10);
+ nvgRoundedRect(vg, tx,ty, thumb,thumb, 6);
+ nvgPathWinding(vg, NVG_HOLE);
+ nvgFillPaint(vg, shadowPaint);
+ nvgFill(vg);
+
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, tx+0.5f,ty+0.5f, thumb-1,thumb-1, 4-0.5f);
+ nvgStrokeWidth(vg,1.0f);
+ nvgStrokeColor(vg, nvgRGBA(255,255,255,192));
+ nvgStroke(vg);
+ }
+ nvgRestore(vg);
+
+ // Hide fades
+ fadePaint = nvgLinearGradient(vg, x,y,x,y+6, nvgRGBA(200,200,200,255), nvgRGBA(200,200,200,0));
+ nvgBeginPath(vg);
+ nvgRect(vg, x+4,y,w-8,6);
+ nvgFillPaint(vg, fadePaint);
+ nvgFill(vg);
+
+ fadePaint = nvgLinearGradient(vg, x,y+h,x,y+h-6, nvgRGBA(200,200,200,255), nvgRGBA(200,200,200,0));
+ nvgBeginPath(vg);
+ nvgRect(vg, x+4,y+h-6,w-8,6);
+ nvgFillPaint(vg, fadePaint);
+ nvgFill(vg);
+
+ // Scroll bar
+ shadowPaint = nvgBoxGradient(vg, x+w-12+1,y+4+1, 8,h-8, 3,4, nvgRGBA(0,0,0,32), nvgRGBA(0,0,0,92));
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+w-12,y+4, 8,h-8, 3);
+ nvgFillPaint(vg, shadowPaint);
+// nvgFillColor(vg, nvgRGBA(255,0,0,128));
+ nvgFill(vg);
+
+ scrollh = (h/stackh) * (h-8);
+ shadowPaint = nvgBoxGradient(vg, x+w-12-1,y+4+(h-8-scrollh)*u-1, 8,scrollh, 3,4, nvgRGBA(220,220,220,255), nvgRGBA(128,128,128,255));
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, x+w-12+1,y+4+1 + (h-8-scrollh)*u, 8-2,scrollh-2, 2);
+ nvgFillPaint(vg, shadowPaint);
+// nvgFillColor(vg, nvgRGBA(0,0,0,128));
+ nvgFill(vg);
+
+ nvgRestore(vg);
+}
+
+void drawColorwheel(NVGcontext* vg, float x, float y, float w, float h, float t)
+{
+ int i;
+ float r0, r1, ax,ay, bx,by, cx,cy, aeps, r;
+ float hue = sinf(t * 0.12f);
+ NVGpaint paint;
+
+ nvgSave(vg);
+
+/* nvgBeginPath(vg);
+ nvgRect(vg, x,y,w,h);
+ nvgFillColor(vg, nvgRGBA(255,0,0,128));
+ nvgFill(vg);*/
+
+ cx = x + w*0.5f;
+ cy = y + h*0.5f;
+ r1 = (w < h ? w : h) * 0.5f - 5.0f;
+ r0 = r1 - 20.0f;
+ aeps = 0.5f / r1; // half a pixel arc length in radians (2pi cancels out).
+
+ for (i = 0; i < 6; i++) {
+ float a0 = (float)i / 6.0f * NVG_PI * 2.0f - aeps;
+ float a1 = (float)(i+1.0f) / 6.0f * NVG_PI * 2.0f + aeps;
+ nvgBeginPath(vg);
+ nvgArc(vg, cx,cy, r0, a0, a1, NVG_CW);
+ nvgArc(vg, cx,cy, r1, a1, a0, NVG_CCW);
+ nvgClosePath(vg);
+ ax = cx + cosf(a0) * (r0+r1)*0.5f;
+ ay = cy + sinf(a0) * (r0+r1)*0.5f;
+ bx = cx + cosf(a1) * (r0+r1)*0.5f;
+ by = cy + sinf(a1) * (r0+r1)*0.5f;
+ paint = nvgLinearGradient(vg, ax,ay, bx,by, nvgHSLA(a0/(NVG_PI*2),1.0f,0.55f,255), nvgHSLA(a1/(NVG_PI*2),1.0f,0.55f,255));
+ nvgFillPaint(vg, paint);
+ nvgFill(vg);
+ }
+
+ nvgBeginPath(vg);
+ nvgCircle(vg, cx,cy, r0-0.5f);
+ nvgCircle(vg, cx,cy, r1+0.5f);
+ nvgStrokeColor(vg, nvgRGBA(0,0,0,64));
+ nvgStrokeWidth(vg, 1.0f);
+ nvgStroke(vg);
+
+ // Selector
+ nvgSave(vg);
+ nvgTranslate(vg, cx,cy);
+ nvgRotate(vg, hue*NVG_PI*2);
+
+ // Marker on
+ nvgStrokeWidth(vg, 2.0f);
+ nvgBeginPath(vg);
+ nvgRect(vg, r0-1,-3,r1-r0+2,6);
+ nvgStrokeColor(vg, nvgRGBA(255,255,255,192));
+ nvgStroke(vg);
+
+ paint = nvgBoxGradient(vg, r0-3,-5,r1-r0+6,10, 2,4, nvgRGBA(0,0,0,128), nvgRGBA(0,0,0,0));
+ nvgBeginPath(vg);
+ nvgRect(vg, r0-2-10,-4-10,r1-r0+4+20,8+20);
+ nvgRect(vg, r0-2,-4,r1-r0+4,8);
+ nvgPathWinding(vg, NVG_HOLE);
+ nvgFillPaint(vg, paint);
+ nvgFill(vg);
+
+ // Center triangle
+ r = r0 - 6;
+ ax = cosf(120.0f/180.0f*NVG_PI) * r;
+ ay = sinf(120.0f/180.0f*NVG_PI) * r;
+ bx = cosf(-120.0f/180.0f*NVG_PI) * r;
+ by = sinf(-120.0f/180.0f*NVG_PI) * r;
+ nvgBeginPath(vg);
+ nvgMoveTo(vg, r,0);
+ nvgLineTo(vg, ax,ay);
+ nvgLineTo(vg, bx,by);
+ nvgClosePath(vg);
+ paint = nvgLinearGradient(vg, r,0, ax,ay, nvgHSLA(hue,1.0f,0.5f,255), nvgRGBA(255,255,255,255));
+ nvgFillPaint(vg, paint);
+ nvgFill(vg);
+ paint = nvgLinearGradient(vg, (r+ax)*0.5f,(0+ay)*0.5f, bx,by, nvgRGBA(0,0,0,0), nvgRGBA(0,0,0,255));
+ nvgFillPaint(vg, paint);
+ nvgFill(vg);
+ nvgStrokeColor(vg, nvgRGBA(0,0,0,64));
+ nvgStroke(vg);
+
+ // Select circle on triangle
+ ax = cosf(120.0f/180.0f*NVG_PI) * r*0.3f;
+ ay = sinf(120.0f/180.0f*NVG_PI) * r*0.4f;
+ nvgStrokeWidth(vg, 2.0f);
+ nvgBeginPath(vg);
+ nvgCircle(vg, ax,ay,5);
+ nvgStrokeColor(vg, nvgRGBA(255,255,255,192));
+ nvgStroke(vg);
+
+ paint = nvgRadialGradient(vg, ax,ay, 7,9, nvgRGBA(0,0,0,64), nvgRGBA(0,0,0,0));
+ nvgBeginPath(vg);
+ nvgRect(vg, ax-20,ay-20,40,40);
+ nvgCircle(vg, ax,ay,7);
+ nvgPathWinding(vg, NVG_HOLE);
+ nvgFillPaint(vg, paint);
+ nvgFill(vg);
+
+ nvgRestore(vg);
+
+ nvgRestore(vg);
+}
+
+void drawLines(NVGcontext* vg, float x, float y, float w, float h, float t)
+{
+ int i, j;
+ float pad = 5.0f, s = w/9.0f - pad*2;
+ float pts[4*2], fx, fy;
+ int joins[3] = {NVG_MITER, NVG_ROUND, NVG_BEVEL};
+ int caps[3] = {NVG_BUTT, NVG_ROUND, NVG_SQUARE};
+ NVG_NOTUSED(h);
+
+ nvgSave(vg);
+ pts[0] = -s*0.25f + cosf(t*0.3f) * s*0.5f;
+ pts[1] = sinf(t*0.3f) * s*0.5f;
+ pts[2] = -s*0.25;
+ pts[3] = 0;
+ pts[4] = s*0.25f;
+ pts[5] = 0;
+ pts[6] = s*0.25f + cosf(-t*0.3f) * s*0.5f;
+ pts[7] = sinf(-t*0.3f) * s*0.5f;
+
+ for (i = 0; i < 3; i++) {
+ for (j = 0; j < 3; j++) {
+ fx = x + s*0.5f + (i*3+j)/9.0f*w + pad;
+ fy = y - s*0.5f + pad;
+
+ nvgLineCap(vg, caps[i]);
+ nvgLineJoin(vg, joins[j]);
+
+ nvgStrokeWidth(vg, s*0.3f);
+ nvgStrokeColor(vg, nvgRGBA(0,0,0,160));
+ nvgBeginPath(vg);
+ nvgMoveTo(vg, fx+pts[0], fy+pts[1]);
+ nvgLineTo(vg, fx+pts[2], fy+pts[3]);
+ nvgLineTo(vg, fx+pts[4], fy+pts[5]);
+ nvgLineTo(vg, fx+pts[6], fy+pts[7]);
+ nvgStroke(vg);
+
+ nvgLineCap(vg, NVG_BUTT);
+ nvgLineJoin(vg, NVG_BEVEL);
+
+ nvgStrokeWidth(vg, 1.0f);
+ nvgStrokeColor(vg, nvgRGBA(0,192,255,255));
+ nvgBeginPath(vg);
+ nvgMoveTo(vg, fx+pts[0], fy+pts[1]);
+ nvgLineTo(vg, fx+pts[2], fy+pts[3]);
+ nvgLineTo(vg, fx+pts[4], fy+pts[5]);
+ nvgLineTo(vg, fx+pts[6], fy+pts[7]);
+ nvgStroke(vg);
+ }
+ }
+
+
+ nvgRestore(vg);
+}
+
+int loadDemoData(NVGcontext* vg, DemoData* data)
+{
+ int i;
+
+ if (vg == NULL)
+ return -1;
+
+ for (i = 0; i < 12; i++) {
+ char file[128];
+ snprintf(file, 128, "../example/images/image%d.jpg", i+1);
+ data->images[i] = nvgCreateImage(vg, file, 0);
+ if (data->images[i] == 0) {
+ printf("Could not load %s.\n", file);
+ return -1;
+ }
+ }
+
+ data->fontIcons = nvgCreateFont(vg, "icons", "../example/entypo.ttf");
+ if (data->fontIcons == -1) {
+ printf("Could not add font icons.\n");
+ return -1;
+ }
+ data->fontNormal = nvgCreateFont(vg, "sans", "../example/Roboto-Regular.ttf");
+ if (data->fontNormal == -1) {
+ printf("Could not add font italic.\n");
+ return -1;
+ }
+ data->fontBold = nvgCreateFont(vg, "sans-bold", "../example/Roboto-Bold.ttf");
+ if (data->fontBold == -1) {
+ printf("Could not add font bold.\n");
+ return -1;
+ }
+ data->fontEmoji = nvgCreateFont(vg, "emoji", "../example/NotoEmoji-Regular.ttf");
+ if (data->fontEmoji == -1) {
+ printf("Could not add font emoji.\n");
+ return -1;
+ }
+ nvgAddFallbackFontId(vg, data->fontNormal, data->fontEmoji);
+ nvgAddFallbackFontId(vg, data->fontBold, data->fontEmoji);
+
+ return 0;
+}
+
+void freeDemoData(NVGcontext* vg, DemoData* data)
+{
+ int i;
+
+ if (vg == NULL)
+ return;
+
+ for (i = 0; i < 12; i++)
+ nvgDeleteImage(vg, data->images[i]);
+}
+
+void drawParagraph(NVGcontext* vg, float x, float y, float width, float height, float mx, float my)
+{
+ NVGtextRow rows[3];
+ NVGglyphPosition glyphs[100];
+ const char* text = "This is longer chunk of text.\n \n Would have used lorem ipsum but she was busy jumping over the lazy dog with the fox and all the men who came to the aid of the party.🎉";
+ const char* start;
+ const char* end;
+ int nrows, i, nglyphs, j, lnum = 0;
+ float lineh;
+ float caretx, px;
+ float bounds[4];
+ float a;
+ float gx,gy;
+ int gutter = 0;
+ NVG_NOTUSED(height);
+
+ nvgSave(vg);
+
+ nvgFontSize(vg, 18.0f);
+ nvgFontFace(vg, "sans");
+ nvgTextAlign(vg, NVG_ALIGN_LEFT|NVG_ALIGN_TOP);
+ nvgTextMetrics(vg, NULL, NULL, &lineh);
+
+ // The text break API can be used to fill a large buffer of rows,
+ // or to iterate over the text just few lines (or just one) at a time.
+ // The "next" variable of the last returned item tells where to continue.
+ start = text;
+ end = text + strlen(text);
+ while ((nrows = nvgTextBreakLines(vg, start, end, width, rows, 3))) {
+ for (i = 0; i < nrows; i++) {
+ NVGtextRow* row = &rows[i];
+ int hit = mx > x && mx < (x+width) && my >= y && my < (y+lineh);
+
+ nvgBeginPath(vg);
+ nvgFillColor(vg, nvgRGBA(255,255,255,hit?64:16));
+ nvgRect(vg, x, y, row->width, lineh);
+ nvgFill(vg);
+
+ nvgFillColor(vg, nvgRGBA(255,255,255,255));
+ nvgText(vg, x, y, row->start, row->end);
+
+ if (hit) {
+ caretx = (mx < x+row->width/2) ? x : x+row->width;
+ px = x;
+ nglyphs = nvgTextGlyphPositions(vg, x, y, row->start, row->end, glyphs, 100);
+ for (j = 0; j < nglyphs; j++) {
+ float x0 = glyphs[j].x;
+ float x1 = (j+1 < nglyphs) ? glyphs[j+1].x : x+row->width;
+ float gx = x0 * 0.3f + x1 * 0.7f;
+ if (mx >= px && mx < gx)
+ caretx = glyphs[j].x;
+ px = gx;
+ }
+ nvgBeginPath(vg);
+ nvgFillColor(vg, nvgRGBA(255,192,0,255));
+ nvgRect(vg, caretx, y, 1, lineh);
+ nvgFill(vg);
+
+ gutter = lnum+1;
+ gx = x - 10;
+ gy = y + lineh/2;
+ }
+ lnum++;
+ y += lineh;
+ }
+ // Keep going...
+ start = rows[nrows-1].next;
+ }
+
+ if (gutter) {
+ char txt[16];
+ snprintf(txt, sizeof(txt), "%d", gutter);
+ nvgFontSize(vg, 13.0f);
+ nvgTextAlign(vg, NVG_ALIGN_RIGHT|NVG_ALIGN_MIDDLE);
+
+ nvgTextBounds(vg, gx,gy, txt, NULL, bounds);
+
+ nvgBeginPath(vg);
+ nvgFillColor(vg, nvgRGBA(255,192,0,255));
+ nvgRoundedRect(vg, (int)bounds[0]-4,(int)bounds[1]-2, (int)(bounds[2]-bounds[0])+8, (int)(bounds[3]-bounds[1])+4, ((int)(bounds[3]-bounds[1])+4)/2-1);
+ nvgFill(vg);
+
+ nvgFillColor(vg, nvgRGBA(32,32,32,255));
+ nvgText(vg, gx,gy, txt, NULL);
+ }
+
+ y += 20.0f;
+
+ nvgFontSize(vg, 13.0f);
+ nvgTextAlign(vg, NVG_ALIGN_LEFT|NVG_ALIGN_TOP);
+ nvgTextLineHeight(vg, 1.2f);
+
+ nvgTextBoxBounds(vg, x,y, 150, "Hover your mouse over the text to see calculated caret position.", NULL, bounds);
+
+ // Fade the tooltip out when close to it.
+ gx = fabsf((mx - (bounds[0]+bounds[2])*0.5f) / (bounds[0] - bounds[2]));
+ gy = fabsf((my - (bounds[1]+bounds[3])*0.5f) / (bounds[1] - bounds[3]));
+ a = maxf(gx, gy) - 0.5f;
+ a = clampf(a, 0, 1);
+ nvgGlobalAlpha(vg, a);
+
+ nvgBeginPath(vg);
+ nvgFillColor(vg, nvgRGBA(220,220,220,255));
+ nvgRoundedRect(vg, bounds[0]-2,bounds[1]-2, (int)(bounds[2]-bounds[0])+4, (int)(bounds[3]-bounds[1])+4, 3);
+ px = (int)((bounds[2]+bounds[0])/2);
+ nvgMoveTo(vg, px,bounds[1] - 10);
+ nvgLineTo(vg, px+7,bounds[1]+1);
+ nvgLineTo(vg, px-7,bounds[1]+1);
+ nvgFill(vg);
+
+ nvgFillColor(vg, nvgRGBA(0,0,0,220));
+ nvgTextBox(vg, x,y, 150, "Hover your mouse over the text to see calculated caret position.", NULL);
+
+ nvgRestore(vg);
+}
+
+void drawWidths(NVGcontext* vg, float x, float y, float width)
+{
+ int i;
+
+ nvgSave(vg);
+
+ nvgStrokeColor(vg, nvgRGBA(0,0,0,255));
+
+ for (i = 0; i < 20; i++) {
+ float w = (i+0.5f)*0.1f;
+ nvgStrokeWidth(vg, w);
+ nvgBeginPath(vg);
+ nvgMoveTo(vg, x,y);
+ nvgLineTo(vg, x+width,y+width*0.3f);
+ nvgStroke(vg);
+ y += 10;
+ }
+
+ nvgRestore(vg);
+}
+
+void drawCaps(NVGcontext* vg, float x, float y, float width)
+{
+ int i;
+ int caps[3] = {NVG_BUTT, NVG_ROUND, NVG_SQUARE};
+ float lineWidth = 8.0f;
+
+ nvgSave(vg);
+
+ nvgBeginPath(vg);
+ nvgRect(vg, x-lineWidth/2, y, width+lineWidth, 40);
+ nvgFillColor(vg, nvgRGBA(255,255,255,32));
+ nvgFill(vg);
+
+ nvgBeginPath(vg);
+ nvgRect(vg, x, y, width, 40);
+ nvgFillColor(vg, nvgRGBA(255,255,255,32));
+ nvgFill(vg);
+
+ nvgStrokeWidth(vg, lineWidth);
+ for (i = 0; i < 3; i++) {
+ nvgLineCap(vg, caps[i]);
+ nvgStrokeColor(vg, nvgRGBA(0,0,0,255));
+ nvgBeginPath(vg);
+ nvgMoveTo(vg, x, y + i*10 + 5);
+ nvgLineTo(vg, x+width, y + i*10 + 5);
+ nvgStroke(vg);
+ }
+
+ nvgRestore(vg);
+}
+
+void drawScissor(NVGcontext* vg, float x, float y, float t)
+{
+ nvgSave(vg);
+
+ // Draw first rect and set scissor to it's area.
+ nvgTranslate(vg, x, y);
+ nvgRotate(vg, nvgDegToRad(5));
+ nvgBeginPath(vg);
+ nvgRect(vg, -20,-20,60,40);
+ nvgFillColor(vg, nvgRGBA(255,0,0,255));
+ nvgFill(vg);
+ nvgScissor(vg, -20,-20,60,40);
+
+ // Draw second rectangle with offset and rotation.
+ nvgTranslate(vg, 40,0);
+ nvgRotate(vg, t);
+
+ // Draw the intended second rectangle without any scissoring.
+ nvgSave(vg);
+ nvgResetScissor(vg);
+ nvgBeginPath(vg);
+ nvgRect(vg, -20,-10,60,30);
+ nvgFillColor(vg, nvgRGBA(255,128,0,64));
+ nvgFill(vg);
+ nvgRestore(vg);
+
+ // Draw second rectangle with combined scissoring.
+ nvgIntersectScissor(vg, -20,-10,60,30);
+ nvgBeginPath(vg);
+ nvgRect(vg, -20,-10,60,30);
+ nvgFillColor(vg, nvgRGBA(255,128,0,255));
+ nvgFill(vg);
+
+ nvgRestore(vg);
+}
+
+void renderDemo(NVGcontext* vg, float mx, float my, float width, float height,
+ float t, int blowup, DemoData* data)
+{
+ float x,y,popy;
+
+ drawEyes(vg, width - 250, 50, 150, 100, mx, my, t);
+ drawParagraph(vg, width - 450, 50, 150, 100, mx, my);
+ drawGraph(vg, 0, height/2, width, height/2, t);
+ drawColorwheel(vg, width - 300, height - 300, 250.0f, 250.0f, t);
+
+ // Line joints
+ drawLines(vg, 120, height-50, 600, 50, t);
+
+ // Line caps
+ drawWidths(vg, 10, 50, 30);
+
+ // Line caps
+ drawCaps(vg, 10, 300, 30);
+
+ drawScissor(vg, 50, height-80, t);
+
+ nvgSave(vg);
+ if (blowup) {
+ nvgRotate(vg, sinf(t*0.3f)*5.0f/180.0f*NVG_PI);
+ nvgScale(vg, 2.0f, 2.0f);
+ }
+
+ // Widgets
+ drawWindow(vg, "Widgets `n Stuff", 50, 50, 300, 400);
+ x = 60; y = 95;
+ drawSearchBox(vg, "Search", x,y,280,25);
+ y += 40;
+ drawDropDown(vg, "Effects", x,y,280,28);
+ popy = y + 14;
+ y += 45;
+
+ // Form
+ drawLabel(vg, "Login", x,y, 280,20);
+ y += 25;
+ drawEditBox(vg, "Email", x,y, 280,28);
+ y += 35;
+ drawEditBox(vg, "Password", x,y, 280,28);
+ y += 38;
+ drawCheckBox(vg, "Remember me", x,y, 140,28);
+ drawButton(vg, ICON_LOGIN, "Sign in", x+138, y, 140, 28, nvgRGBA(0,96,128,255));
+ y += 45;
+
+ // Slider
+ drawLabel(vg, "Diameter", x,y, 280,20);
+ y += 25;
+ drawEditBoxNum(vg, "123.00", "px", x+180,y, 100,28);
+ drawSlider(vg, 0.4f, x,y, 170,28);
+ y += 55;
+
+ drawButton(vg, ICON_TRASH, "Delete", x, y, 160, 28, nvgRGBA(128,16,8,255));
+ drawButton(vg, 0, "Cancel", x+170, y, 110, 28, nvgRGBA(0,0,0,0));
+
+ // Thumbnails box
+ drawThumbnails(vg, 365, popy-30, 160, 300, data->images, 12, t);
+
+ nvgRestore(vg);
+}
+
+static int mini(int a, int b) { return a < b ? a : b; }
+
+static void unpremultiplyAlpha(unsigned char* image, int w, int h, int stride)
+{
+ int x,y;
+
+ // Unpremultiply
+ for (y = 0; y < h; y++) {
+ unsigned char *row = &image[y*stride];
+ for (x = 0; x < w; x++) {
+ int r = row[0], g = row[1], b = row[2], a = row[3];
+ if (a != 0) {
+ row[0] = (int)mini(r*255/a, 255);
+ row[1] = (int)mini(g*255/a, 255);
+ row[2] = (int)mini(b*255/a, 255);
+ }
+ row += 4;
+ }
+ }
+
+ // Defringe
+ for (y = 0; y < h; y++) {
+ unsigned char *row = &image[y*stride];
+ for (x = 0; x < w; x++) {
+ int r = 0, g = 0, b = 0, a = row[3], n = 0;
+ if (a == 0) {
+ if (x-1 > 0 && row[-1] != 0) {
+ r += row[-4];
+ g += row[-3];
+ b += row[-2];
+ n++;
+ }
+ if (x+1 < w && row[7] != 0) {
+ r += row[4];
+ g += row[5];
+ b += row[6];
+ n++;
+ }
+ if (y-1 > 0 && row[-stride+3] != 0) {
+ r += row[-stride];
+ g += row[-stride+1];
+ b += row[-stride+2];
+ n++;
+ }
+ if (y+1 < h && row[stride+3] != 0) {
+ r += row[stride];
+ g += row[stride+1];
+ b += row[stride+2];
+ n++;
+ }
+ if (n > 0) {
+ row[0] = r/n;
+ row[1] = g/n;
+ row[2] = b/n;
+ }
+ }
+ row += 4;
+ }
+ }
+}
+
+static void setAlpha(unsigned char* image, int w, int h, int stride, unsigned char a)
+{
+ int x, y;
+ for (y = 0; y < h; y++) {
+ unsigned char* row = &image[y*stride];
+ for (x = 0; x < w; x++)
+ row[x*4+3] = a;
+ }
+}
+
+static void flipHorizontal(unsigned char* image, int w, int h, int stride)
+{
+ int i = 0, j = h-1, k;
+ while (i < j) {
+ unsigned char* ri = &image[i * stride];
+ unsigned char* rj = &image[j * stride];
+ for (k = 0; k < w*4; k++) {
+ unsigned char t = ri[k];
+ ri[k] = rj[k];
+ rj[k] = t;
+ }
+ i++;
+ j--;
+ }
+}
+
+void saveScreenShot(int w, int h, int premult, const char* name)
+{
+ unsigned char* image = (unsigned char*)malloc(w*h*4);
+ if (image == NULL)
+ return;
+ glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image);
+ if (premult)
+ unpremultiplyAlpha(image, w, h, w*4);
+ else
+ setAlpha(image, w, h, w*4, 255);
+ flipHorizontal(image, w, h, w*4);
+ stbi_write_png(name, w, h, 4, image, w*4);
+ free(image);
+}
diff --git a/subprojects/d2tk/nanovg/example/demo.h b/subprojects/d2tk/nanovg/example/demo.h
new file mode 100644
index 0000000..aace449
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/demo.h
@@ -0,0 +1,26 @@
+#ifndef DEMO_H
+#define DEMO_H
+
+#include "nanovg.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct DemoData {
+ int fontNormal, fontBold, fontIcons, fontEmoji;
+ int images[12];
+};
+typedef struct DemoData DemoData;
+
+int loadDemoData(NVGcontext* vg, DemoData* data);
+void freeDemoData(NVGcontext* vg, DemoData* data);
+void renderDemo(NVGcontext* vg, float mx, float my, float width, float height, float t, int blowup, DemoData* data);
+
+void saveScreenShot(int w, int h, int premult, const char* name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // DEMO_H
diff --git a/subprojects/d2tk/nanovg/example/entypo.ttf b/subprojects/d2tk/nanovg/example/entypo.ttf
new file mode 100644
index 0000000..fc305d2
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/entypo.ttf
Binary files differ
diff --git a/subprojects/d2tk/nanovg/example/example_fbo.c b/subprojects/d2tk/nanovg/example/example_fbo.c
new file mode 100644
index 0000000..cff4ed2
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/example_fbo.c
@@ -0,0 +1,272 @@
+//
+// Copyright (c) 2013 Mikko Mononen memon@inside.org
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+//
+
+#include <stdio.h>
+#ifdef NANOVG_GLEW
+# include <GL/glew.h>
+#endif
+#ifdef __APPLE__
+# define GLFW_INCLUDE_GLCOREARB
+#endif
+#include <GLFW/glfw3.h>
+#include "nanovg.h"
+#define NANOVG_GL3_IMPLEMENTATION
+#include "nanovg_gl.h"
+#include "nanovg_gl_utils.h"
+#include "perf.h"
+
+void renderPattern(NVGcontext* vg, NVGLUframebuffer* fb, float t, float pxRatio)
+{
+ int winWidth, winHeight;
+ int fboWidth, fboHeight;
+ int pw, ph, x, y;
+ float s = 20.0f;
+ float sr = (cosf(t)+1)*0.5f;
+ float r = s * 0.6f * (0.2f + 0.8f * sr);
+
+ if (fb == NULL) return;
+
+ nvgImageSize(vg, fb->image, &fboWidth, &fboHeight);
+ winWidth = (int)(fboWidth / pxRatio);
+ winHeight = (int)(fboHeight / pxRatio);
+
+ // Draw some stuff to an FBO as a test
+ nvgluBindFramebuffer(fb);
+ glViewport(0, 0, fboWidth, fboHeight);
+ glClearColor(0, 0, 0, 0);
+ glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+ nvgBeginFrame(vg, winWidth, winHeight, pxRatio);
+
+ pw = (int)ceilf(winWidth / s);
+ ph = (int)ceilf(winHeight / s);
+
+ nvgBeginPath(vg);
+ for (y = 0; y < ph; y++) {
+ for (x = 0; x < pw; x++) {
+ float cx = (x+0.5f) * s;
+ float cy = (y+0.5f) * s;
+ nvgCircle(vg, cx,cy, r);
+ }
+ }
+ nvgFillColor(vg, nvgRGBA(220,160,0,200));
+ nvgFill(vg);
+
+ nvgEndFrame(vg);
+ nvgluBindFramebuffer(NULL);
+}
+
+int loadFonts(NVGcontext* vg)
+{
+ int font;
+ font = nvgCreateFont(vg, "sans", "../example/Roboto-Regular.ttf");
+ if (font == -1) {
+ printf("Could not add font regular.\n");
+ return -1;
+ }
+ font = nvgCreateFont(vg, "sans-bold", "../example/Roboto-Bold.ttf");
+ if (font == -1) {
+ printf("Could not add font bold.\n");
+ return -1;
+ }
+ return 0;
+}
+
+void errorcb(int error, const char* desc)
+{
+ printf("GLFW error %d: %s\n", error, desc);
+}
+
+static void key(GLFWwindow* window, int key, int scancode, int action, int mods)
+{
+ NVG_NOTUSED(scancode);
+ NVG_NOTUSED(mods);
+ if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
+ glfwSetWindowShouldClose(window, GL_TRUE);
+}
+
+int main()
+{
+ GLFWwindow* window;
+ NVGcontext* vg = NULL;
+ GPUtimer gpuTimer;
+ PerfGraph fps, cpuGraph, gpuGraph;
+ double prevt = 0, cpuTime = 0;
+ NVGLUframebuffer* fb = NULL;
+ int winWidth, winHeight;
+ int fbWidth, fbHeight;
+ float pxRatio;
+
+ if (!glfwInit()) {
+ printf("Failed to init GLFW.");
+ return -1;
+ }
+
+ initGraph(&fps, GRAPH_RENDER_FPS, "Frame Time");
+ initGraph(&cpuGraph, GRAPH_RENDER_MS, "CPU Time");
+ initGraph(&gpuGraph, GRAPH_RENDER_MS, "GPU Time");
+
+ glfwSetErrorCallback(errorcb);
+#ifndef _WIN32 // don't require this on win32, and works with more cards
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
+ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
+ glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+#endif
+ glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, 1);
+
+#ifdef DEMO_MSAA
+ glfwWindowHint(GLFW_SAMPLES, 4);
+#endif
+ window = glfwCreateWindow(1000, 600, "NanoVG", NULL, NULL);
+// window = glfwCreateWindow(1000, 600, "NanoVG", glfwGetPrimaryMonitor(), NULL);
+ if (!window) {
+ glfwTerminate();
+ return -1;
+ }
+
+ glfwSetKeyCallback(window, key);
+
+ glfwMakeContextCurrent(window);
+#ifdef NANOVG_GLEW
+ glewExperimental = GL_TRUE;
+ if(glewInit() != GLEW_OK) {
+ printf("Could not init glew.\n");
+ return -1;
+ }
+ // GLEW generates GL error because it calls glGetString(GL_EXTENSIONS), we'll consume it here.
+ glGetError();
+#endif
+
+#ifdef DEMO_MSAA
+ vg = nvgCreateGL3(NVG_STENCIL_STROKES | NVG_DEBUG);
+#else
+ vg = nvgCreateGL3(NVG_ANTIALIAS | NVG_STENCIL_STROKES | NVG_DEBUG);
+#endif
+ if (vg == NULL) {
+ printf("Could not init nanovg.\n");
+ return -1;
+ }
+
+ // Create hi-dpi FBO for hi-dpi screens.
+ glfwGetWindowSize(window, &winWidth, &winHeight);
+ glfwGetFramebufferSize(window, &fbWidth, &fbHeight);
+ // Calculate pixel ration for hi-dpi devices.
+ pxRatio = (float)fbWidth / (float)winWidth;
+
+ // The image pattern is tiled, set repeat on x and y.
+ fb = nvgluCreateFramebuffer(vg, (int)(100*pxRatio), (int)(100*pxRatio), NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY);
+ if (fb == NULL) {
+ printf("Could not create FBO.\n");
+ return -1;
+ }
+
+ if (loadFonts(vg) == -1) {
+ printf("Could not load fonts\n");
+ return -1;
+ }
+
+ glfwSwapInterval(0);
+
+ initGPUTimer(&gpuTimer);
+
+ glfwSetTime(0);
+ prevt = glfwGetTime();
+
+ while (!glfwWindowShouldClose(window))
+ {
+ double mx, my, t, dt;
+ float gpuTimes[3];
+ int i, n;
+
+ t = glfwGetTime();
+ dt = t - prevt;
+ prevt = t;
+
+ startGPUTimer(&gpuTimer);
+
+ glfwGetCursorPos(window, &mx, &my);
+ glfwGetWindowSize(window, &winWidth, &winHeight);
+ glfwGetFramebufferSize(window, &fbWidth, &fbHeight);
+ // Calculate pixel ration for hi-dpi devices.
+ pxRatio = (float)fbWidth / (float)winWidth;
+
+ renderPattern(vg, fb, t, pxRatio);
+
+ // Update and render
+ glViewport(0, 0, fbWidth, fbHeight);
+ glClearColor(0.3f, 0.3f, 0.32f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+ nvgBeginFrame(vg, winWidth, winHeight, pxRatio);
+
+ // Use the FBO as image pattern.
+ if (fb != NULL) {
+ NVGpaint img = nvgImagePattern(vg, 0, 0, 100, 100, 0, fb->image, 1.0f);
+ nvgSave(vg);
+
+ for (i = 0; i < 20; i++) {
+ nvgBeginPath(vg);
+ nvgRect(vg, 10 + i*30,10, 10, winHeight-20);
+ nvgFillColor(vg, nvgHSLA(i/19.0f, 0.5f, 0.5f, 255));
+ nvgFill(vg);
+ }
+
+ nvgBeginPath(vg);
+ nvgRoundedRect(vg, 140 + sinf(t*1.3f)*100, 140 + cosf(t*1.71244f)*100, 250, 250, 20);
+ nvgFillPaint(vg, img);
+ nvgFill(vg);
+ nvgStrokeColor(vg, nvgRGBA(220,160,0,255));
+ nvgStrokeWidth(vg, 3.0f);
+ nvgStroke(vg);
+
+ nvgRestore(vg);
+ }
+
+ renderGraph(vg, 5,5, &fps);
+ renderGraph(vg, 5+200+5,5, &cpuGraph);
+ if (gpuTimer.supported)
+ renderGraph(vg, 5+200+5+200+5,5, &gpuGraph);
+
+ nvgEndFrame(vg);
+
+ // Measure the CPU time taken excluding swap buffers (as the swap may wait for GPU)
+ cpuTime = glfwGetTime() - t;
+
+ updateGraph(&fps, dt);
+ updateGraph(&cpuGraph, cpuTime);
+
+ // We may get multiple results.
+ n = stopGPUTimer(&gpuTimer, gpuTimes, 3);
+ for (i = 0; i < n; i++)
+ updateGraph(&gpuGraph, gpuTimes[i]);
+
+ glfwSwapBuffers(window);
+ glfwPollEvents();
+ }
+
+ nvgluDeleteFramebuffer(fb);
+
+ nvgDeleteGL3(vg);
+
+ printf("Average Frame Time: %.2f ms\n", getGraphAverage(&fps) * 1000.0f);
+ printf(" CPU Time: %.2f ms\n", getGraphAverage(&cpuGraph) * 1000.0f);
+ printf(" GPU Time: %.2f ms\n", getGraphAverage(&gpuGraph) * 1000.0f);
+
+ glfwTerminate();
+ return 0;
+}
diff --git a/subprojects/d2tk/nanovg/example/example_gl2.c b/subprojects/d2tk/nanovg/example/example_gl2.c
new file mode 100644
index 0000000..7fd5621
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/example_gl2.c
@@ -0,0 +1,162 @@
+//
+// Copyright (c) 2013 Mikko Mononen memon@inside.org
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+//
+
+#include <stdio.h>
+#ifdef NANOVG_GLEW
+# include <GL/glew.h>
+#endif
+#define GLFW_INCLUDE_GLEXT
+#include <GLFW/glfw3.h>
+#include "nanovg.h"
+#define NANOVG_GL2_IMPLEMENTATION
+#include "nanovg_gl.h"
+#include "demo.h"
+#include "perf.h"
+
+
+void errorcb(int error, const char* desc)
+{
+ printf("GLFW error %d: %s\n", error, desc);
+}
+
+int blowup = 0;
+int screenshot = 0;
+int premult = 0;
+
+static void key(GLFWwindow* window, int key, int scancode, int action, int mods)
+{
+ NVG_NOTUSED(scancode);
+ NVG_NOTUSED(mods);
+ if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
+ glfwSetWindowShouldClose(window, GL_TRUE);
+ if (key == GLFW_KEY_SPACE && action == GLFW_PRESS)
+ blowup = !blowup;
+ if (key == GLFW_KEY_S && action == GLFW_PRESS)
+ screenshot = 1;
+ if (key == GLFW_KEY_P && action == GLFW_PRESS)
+ premult = !premult;
+}
+
+int main()
+{
+ GLFWwindow* window;
+ DemoData data;
+ NVGcontext* vg = NULL;
+ PerfGraph fps;
+ double prevt = 0;
+
+ if (!glfwInit()) {
+ printf("Failed to init GLFW.");
+ return -1;
+ }
+
+ initGraph(&fps, GRAPH_RENDER_FPS, "Frame Time");
+
+ glfwSetErrorCallback(errorcb);
+
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+#ifdef DEMO_MSAA
+ glfwWindowHint(GLFW_SAMPLES, 4);
+#endif
+
+ window = glfwCreateWindow(1000, 600, "NanoVG", NULL, NULL);
+// window = glfwCreateWindow(1000, 600, "NanoVG", glfwGetPrimaryMonitor(), NULL);
+ if (!window) {
+ glfwTerminate();
+ return -1;
+ }
+
+ glfwSetKeyCallback(window, key);
+
+ glfwMakeContextCurrent(window);
+#ifdef NANOVG_GLEW
+ if(glewInit() != GLEW_OK) {
+ printf("Could not init glew.\n");
+ return -1;
+ }
+#endif
+
+#ifdef DEMO_MSAA
+ vg = nvgCreateGL2(NVG_STENCIL_STROKES | NVG_DEBUG);
+#else
+ vg = nvgCreateGL2(NVG_ANTIALIAS | NVG_STENCIL_STROKES | NVG_DEBUG);
+#endif
+ if (vg == NULL) {
+ printf("Could not init nanovg.\n");
+ return -1;
+ }
+
+ if (loadDemoData(vg, &data) == -1)
+ return -1;
+
+ glfwSwapInterval(0);
+
+ glfwSetTime(0);
+ prevt = glfwGetTime();
+
+ while (!glfwWindowShouldClose(window))
+ {
+ double mx, my, t, dt;
+ int winWidth, winHeight;
+ int fbWidth, fbHeight;
+ float pxRatio;
+
+ t = glfwGetTime();
+ dt = t - prevt;
+ prevt = t;
+ updateGraph(&fps, dt);
+
+ glfwGetCursorPos(window, &mx, &my);
+ glfwGetWindowSize(window, &winWidth, &winHeight);
+ glfwGetFramebufferSize(window, &fbWidth, &fbHeight);
+
+ // Calculate pixel ration for hi-dpi devices.
+ pxRatio = (float)fbWidth / (float)winWidth;
+
+ // Update and render
+ glViewport(0, 0, fbWidth, fbHeight);
+ if (premult)
+ glClearColor(0,0,0,0);
+ else
+ glClearColor(0.3f, 0.3f, 0.32f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+ nvgBeginFrame(vg, winWidth, winHeight, pxRatio);
+
+ renderDemo(vg, mx,my, winWidth,winHeight, t, blowup, &data);
+ renderGraph(vg, 5,5, &fps);
+
+ nvgEndFrame(vg);
+
+ if (screenshot) {
+ screenshot = 0;
+ saveScreenShot(fbWidth, fbHeight, premult, "dump.png");
+ }
+
+ glfwSwapBuffers(window);
+ glfwPollEvents();
+ }
+
+ freeDemoData(vg, &data);
+
+ nvgDeleteGL2(vg);
+
+ glfwTerminate();
+ return 0;
+}
diff --git a/subprojects/d2tk/nanovg/example/example_gl3.c b/subprojects/d2tk/nanovg/example/example_gl3.c
new file mode 100644
index 0000000..409a145
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/example_gl3.c
@@ -0,0 +1,198 @@
+//
+// Copyright (c) 2013 Mikko Mononen memon@inside.org
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+//
+
+#include <stdio.h>
+#ifdef NANOVG_GLEW
+# include <GL/glew.h>
+#endif
+#ifdef __APPLE__
+# define GLFW_INCLUDE_GLCOREARB
+#endif
+#define GLFW_INCLUDE_GLEXT
+#include <GLFW/glfw3.h>
+#include "nanovg.h"
+#define NANOVG_GL3_IMPLEMENTATION
+#include "nanovg_gl.h"
+#include "demo.h"
+#include "perf.h"
+
+
+void errorcb(int error, const char* desc)
+{
+ printf("GLFW error %d: %s\n", error, desc);
+}
+
+int blowup = 0;
+int screenshot = 0;
+int premult = 0;
+
+static void key(GLFWwindow* window, int key, int scancode, int action, int mods)
+{
+ NVG_NOTUSED(scancode);
+ NVG_NOTUSED(mods);
+ if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
+ glfwSetWindowShouldClose(window, GL_TRUE);
+ if (key == GLFW_KEY_SPACE && action == GLFW_PRESS)
+ blowup = !blowup;
+ if (key == GLFW_KEY_S && action == GLFW_PRESS)
+ screenshot = 1;
+ if (key == GLFW_KEY_P && action == GLFW_PRESS)
+ premult = !premult;
+}
+
+int main()
+{
+ GLFWwindow* window;
+ DemoData data;
+ NVGcontext* vg = NULL;
+ GPUtimer gpuTimer;
+ PerfGraph fps, cpuGraph, gpuGraph;
+ double prevt = 0, cpuTime = 0;
+
+ if (!glfwInit()) {
+ printf("Failed to init GLFW.");
+ return -1;
+ }
+
+ initGraph(&fps, GRAPH_RENDER_FPS, "Frame Time");
+ initGraph(&cpuGraph, GRAPH_RENDER_MS, "CPU Time");
+ initGraph(&gpuGraph, GRAPH_RENDER_MS, "GPU Time");
+
+ glfwSetErrorCallback(errorcb);
+#ifndef _WIN32 // don't require this on win32, and works with more cards
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
+ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
+ glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+#endif
+ glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, 1);
+
+#ifdef DEMO_MSAA
+ glfwWindowHint(GLFW_SAMPLES, 4);
+#endif
+ window = glfwCreateWindow(1000, 600, "NanoVG", NULL, NULL);
+// window = glfwCreateWindow(1000, 600, "NanoVG", glfwGetPrimaryMonitor(), NULL);
+ if (!window) {
+ glfwTerminate();
+ return -1;
+ }
+
+ glfwSetKeyCallback(window, key);
+
+ glfwMakeContextCurrent(window);
+#ifdef NANOVG_GLEW
+ glewExperimental = GL_TRUE;
+ if(glewInit() != GLEW_OK) {
+ printf("Could not init glew.\n");
+ return -1;
+ }
+ // GLEW generates GL error because it calls glGetString(GL_EXTENSIONS), we'll consume it here.
+ glGetError();
+#endif
+
+#ifdef DEMO_MSAA
+ vg = nvgCreateGL3(NVG_STENCIL_STROKES | NVG_DEBUG);
+#else
+ vg = nvgCreateGL3(NVG_ANTIALIAS | NVG_STENCIL_STROKES | NVG_DEBUG);
+#endif
+ if (vg == NULL) {
+ printf("Could not init nanovg.\n");
+ return -1;
+ }
+
+ if (loadDemoData(vg, &data) == -1)
+ return -1;
+
+ glfwSwapInterval(0);
+
+ initGPUTimer(&gpuTimer);
+
+ glfwSetTime(0);
+ prevt = glfwGetTime();
+
+ while (!glfwWindowShouldClose(window))
+ {
+ double mx, my, t, dt;
+ int winWidth, winHeight;
+ int fbWidth, fbHeight;
+ float pxRatio;
+ float gpuTimes[3];
+ int i, n;
+
+ t = glfwGetTime();
+ dt = t - prevt;
+ prevt = t;
+
+ startGPUTimer(&gpuTimer);
+
+ glfwGetCursorPos(window, &mx, &my);
+ glfwGetWindowSize(window, &winWidth, &winHeight);
+ glfwGetFramebufferSize(window, &fbWidth, &fbHeight);
+ // Calculate pixel ration for hi-dpi devices.
+ pxRatio = (float)fbWidth / (float)winWidth;
+
+ // Update and render
+ glViewport(0, 0, fbWidth, fbHeight);
+ if (premult)
+ glClearColor(0,0,0,0);
+ else
+ glClearColor(0.3f, 0.3f, 0.32f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+ nvgBeginFrame(vg, winWidth, winHeight, pxRatio);
+
+ renderDemo(vg, mx,my, winWidth,winHeight, t, blowup, &data);
+
+ renderGraph(vg, 5,5, &fps);
+ renderGraph(vg, 5+200+5,5, &cpuGraph);
+ if (gpuTimer.supported)
+ renderGraph(vg, 5+200+5+200+5,5, &gpuGraph);
+
+ nvgEndFrame(vg);
+
+ // Measure the CPU time taken excluding swap buffers (as the swap may wait for GPU)
+ cpuTime = glfwGetTime() - t;
+
+ updateGraph(&fps, dt);
+ updateGraph(&cpuGraph, cpuTime);
+
+ // We may get multiple results.
+ n = stopGPUTimer(&gpuTimer, gpuTimes, 3);
+ for (i = 0; i < n; i++)
+ updateGraph(&gpuGraph, gpuTimes[i]);
+
+ if (screenshot) {
+ screenshot = 0;
+ saveScreenShot(fbWidth, fbHeight, premult, "dump.png");
+ }
+
+ glfwSwapBuffers(window);
+ glfwPollEvents();
+ }
+
+ freeDemoData(vg, &data);
+
+ nvgDeleteGL3(vg);
+
+ printf("Average Frame Time: %.2f ms\n", getGraphAverage(&fps) * 1000.0f);
+ printf(" CPU Time: %.2f ms\n", getGraphAverage(&cpuGraph) * 1000.0f);
+ printf(" GPU Time: %.2f ms\n", getGraphAverage(&gpuGraph) * 1000.0f);
+
+ glfwTerminate();
+ return 0;
+}
diff --git a/subprojects/d2tk/nanovg/example/example_gles2.c b/subprojects/d2tk/nanovg/example/example_gles2.c
new file mode 100644
index 0000000..ed78838
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/example_gles2.c
@@ -0,0 +1,155 @@
+//
+// Copyright (c) 2013 Mikko Mononen memon@inside.org
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+//
+
+#include <stdio.h>
+#define GLFW_INCLUDE_ES2
+#define GLFW_INCLUDE_GLEXT
+#include <GLFW/glfw3.h>
+#include "nanovg.h"
+#define NANOVG_GLES2_IMPLEMENTATION
+#include "nanovg_gl.h"
+#include "nanovg_gl_utils.h"
+#include "demo.h"
+#include "perf.h"
+
+
+void errorcb(int error, const char* desc)
+{
+ printf("GLFW error %d: %s\n", error, desc);
+}
+
+int blowup = 0;
+int screenshot = 0;
+int premult = 0;
+
+static void key(GLFWwindow* window, int key, int scancode, int action, int mods)
+{
+ NVG_NOTUSED(scancode);
+ NVG_NOTUSED(mods);
+ if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
+ glfwSetWindowShouldClose(window, GL_TRUE);
+ if (key == GLFW_KEY_SPACE && action == GLFW_PRESS)
+ blowup = !blowup;
+ if (key == GLFW_KEY_S && action == GLFW_PRESS)
+ screenshot = 1;
+ if (key == GLFW_KEY_P && action == GLFW_PRESS)
+ premult = !premult;
+}
+
+int main()
+{
+ GLFWwindow* window;
+ DemoData data;
+ NVGcontext* vg = NULL;
+ PerfGraph fps;
+ double prevt = 0;
+
+ if (!glfwInit()) {
+ printf("Failed to init GLFW.");
+ return -1;
+ }
+
+ initGraph(&fps, GRAPH_RENDER_FPS, "Frame Time");
+
+ glfwSetErrorCallback(errorcb);
+
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+
+ window = glfwCreateWindow(1000, 600, "NanoVG", NULL, NULL);
+// window = glfwCreateWindow(1000, 600, "NanoVG", glfwGetPrimaryMonitor(), NULL);
+ if (!window) {
+ glfwTerminate();
+ return -1;
+ }
+
+ glfwSetKeyCallback(window, key);
+
+ glfwMakeContextCurrent(window);
+
+ vg = nvgCreateGLES2(NVG_ANTIALIAS | NVG_STENCIL_STROKES | NVG_DEBUG);
+ if (vg == NULL) {
+ printf("Could not init nanovg.\n");
+ return -1;
+ }
+
+ if (loadDemoData(vg, &data) == -1)
+ return -1;
+
+ glfwSwapInterval(0);
+
+ glfwSetTime(0);
+ prevt = glfwGetTime();
+
+ while (!glfwWindowShouldClose(window))
+ {
+ double mx, my, t, dt;
+ int winWidth, winHeight;
+ int fbWidth, fbHeight;
+ float pxRatio;
+
+ t = glfwGetTime();
+ dt = t - prevt;
+ prevt = t;
+ updateGraph(&fps, dt);
+
+ glfwGetCursorPos(window, &mx, &my);
+ glfwGetWindowSize(window, &winWidth, &winHeight);
+ glfwGetFramebufferSize(window, &fbWidth, &fbHeight);
+ // Calculate pixel ration for hi-dpi devices.
+ pxRatio = (float)fbWidth / (float)winWidth;
+
+ // Update and render
+ glViewport(0, 0, fbWidth, fbHeight);
+ if (premult)
+ glClearColor(0,0,0,0);
+ else
+ glClearColor(0.3f, 0.3f, 0.32f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable(GL_CULL_FACE);
+ glDisable(GL_DEPTH_TEST);
+
+ nvgBeginFrame(vg, winWidth, winHeight, pxRatio);
+
+ renderDemo(vg, mx,my, winWidth,winHeight, t, blowup, &data);
+ renderGraph(vg, 5,5, &fps);
+
+ nvgEndFrame(vg);
+
+ if (screenshot) {
+ screenshot = 0;
+ saveScreenShot(fbWidth, fbHeight, premult, "dump.png");
+ }
+
+ glEnable(GL_DEPTH_TEST);
+
+ glfwSwapBuffers(window);
+ glfwPollEvents();
+ }
+
+ freeDemoData(vg, &data);
+
+ nvgDeleteGLES2(vg);
+
+ glfwTerminate();
+ return 0;
+}
diff --git a/subprojects/d2tk/nanovg/example/example_gles3.c b/subprojects/d2tk/nanovg/example/example_gles3.c
new file mode 100644
index 0000000..4a6084c
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/example_gles3.c
@@ -0,0 +1,155 @@
+//
+// Copyright (c) 2013 Mikko Mononen memon@inside.org
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+//
+
+#include <stdio.h>
+#define GLFW_INCLUDE_ES3
+#define GLFW_INCLUDE_GLEXT
+#include <GLFW/glfw3.h>
+#include "nanovg.h"
+#define NANOVG_GLES3_IMPLEMENTATION
+#include "nanovg_gl.h"
+#include "nanovg_gl_utils.h"
+#include "demo.h"
+#include "perf.h"
+
+
+void errorcb(int error, const char* desc)
+{
+ printf("GLFW error %d: %s\n", error, desc);
+}
+
+int blowup = 0;
+int screenshot = 0;
+int premult = 0;
+
+static void key(GLFWwindow* window, int key, int scancode, int action, int mods)
+{
+ NVG_NOTUSED(scancode);
+ NVG_NOTUSED(mods);
+ if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
+ glfwSetWindowShouldClose(window, GL_TRUE);
+ if (key == GLFW_KEY_SPACE && action == GLFW_PRESS)
+ blowup = !blowup;
+ if (key == GLFW_KEY_S && action == GLFW_PRESS)
+ screenshot = 1;
+ if (key == GLFW_KEY_P && action == GLFW_PRESS)
+ premult = !premult;
+}
+
+int main()
+{
+ GLFWwindow* window;
+ DemoData data;
+ NVGcontext* vg = NULL;
+ PerfGraph fps;
+ double prevt = 0;
+
+ if (!glfwInit()) {
+ printf("Failed to init GLFW.");
+ return -1;
+ }
+
+ initGraph(&fps, GRAPH_RENDER_FPS, "Frame Time");
+
+ glfwSetErrorCallback(errorcb);
+
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+
+ window = glfwCreateWindow(1000, 600, "NanoVG", NULL, NULL);
+// window = glfwCreateWindow(1000, 600, "NanoVG", glfwGetPrimaryMonitor(), NULL);
+ if (!window) {
+ glfwTerminate();
+ return -1;
+ }
+
+ glfwSetKeyCallback(window, key);
+
+ glfwMakeContextCurrent(window);
+
+ vg = nvgCreateGLES3(NVG_ANTIALIAS | NVG_STENCIL_STROKES | NVG_DEBUG);
+ if (vg == NULL) {
+ printf("Could not init nanovg.\n");
+ return -1;
+ }
+
+ if (loadDemoData(vg, &data) == -1)
+ return -1;
+
+ glfwSwapInterval(0);
+
+ glfwSetTime(0);
+ prevt = glfwGetTime();
+
+ while (!glfwWindowShouldClose(window))
+ {
+ double mx, my, t, dt;
+ int winWidth, winHeight;
+ int fbWidth, fbHeight;
+ float pxRatio;
+
+ t = glfwGetTime();
+ dt = t - prevt;
+ prevt = t;
+ updateGraph(&fps, dt);
+
+ glfwGetCursorPos(window, &mx, &my);
+ glfwGetWindowSize(window, &winWidth, &winHeight);
+ glfwGetFramebufferSize(window, &fbWidth, &fbHeight);
+ // Calculate pixel ration for hi-dpi devices.
+ pxRatio = (float)fbWidth / (float)winWidth;
+
+ // Update and render
+ glViewport(0, 0, fbWidth, fbHeight);
+ if (premult)
+ glClearColor(0,0,0,0);
+ else
+ glClearColor(0.3f, 0.3f, 0.32f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable(GL_CULL_FACE);
+ glDisable(GL_DEPTH_TEST);
+
+ nvgBeginFrame(vg, winWidth, winHeight, pxRatio);
+
+ renderDemo(vg, mx,my, winWidth,winHeight, t, blowup, &data);
+ renderGraph(vg, 5,5, &fps);
+
+ nvgEndFrame(vg);
+
+ glEnable(GL_DEPTH_TEST);
+
+ if (screenshot) {
+ screenshot = 0;
+ saveScreenShot(fbWidth, fbHeight, premult, "dump.png");
+ }
+
+ glfwSwapBuffers(window);
+ glfwPollEvents();
+ }
+
+ freeDemoData(vg, &data);
+
+ nvgDeleteGLES3(vg);
+
+ glfwTerminate();
+ return 0;
+}
diff --git a/subprojects/d2tk/nanovg/example/images.txt b/subprojects/d2tk/nanovg/example/images.txt
new file mode 100644
index 0000000..96ad626
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/images.txt
@@ -0,0 +1,13 @@
+Image credits
+http://cuteoverload.com/2013/11/05/mom-taxi-xvi-birthday-party/
+http://cuteoverload.com/2013/11/05/benson-hedges-private-eye-in-the-case-of-the-crafty-craftsman/
+http://cuteoverload.com/2013/11/05/no-underwater-ballets/
+http://cuteoverload.com/2013/11/05/every-nose-has-a-story/
+http://cuteoverload.com/2013/11/04/nosevember-nozzle-nose/
+http://cuteoverload.com/2013/11/04/this-just-in-super-strength-cute/
+http://cuteoverload.com/2013/11/03/have-a-bunderful-sunday/
+http://cuteoverload.com/2013/11/02/caturday-sense-a-common-theme-here/
+http://cuteoverload.com/2013/11/01/nosevember-1st-24-hours-of-noses-1148pm-pt/
+http://cuteoverload.com/2013/04/02/there-might-be-something-cuter-than-this/
+http://cuteoverload.com/2013/07/17/snorting-micro-peeg-gets-belleh-rubs-interwebs-explode/
+http://cuteoverload.com/2013/08/07/bark-in-the-park-v3-0/ \ No newline at end of file
diff --git a/subprojects/d2tk/nanovg/example/images/image1.jpg b/subprojects/d2tk/nanovg/example/images/image1.jpg
new file mode 100644
index 0000000..c2ce39b
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/images/image1.jpg
Binary files differ
diff --git a/subprojects/d2tk/nanovg/example/images/image10.jpg b/subprojects/d2tk/nanovg/example/images/image10.jpg
new file mode 100644
index 0000000..d443fb0
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/images/image10.jpg
Binary files differ
diff --git a/subprojects/d2tk/nanovg/example/images/image11.jpg b/subprojects/d2tk/nanovg/example/images/image11.jpg
new file mode 100644
index 0000000..7429fe5
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/images/image11.jpg
Binary files differ
diff --git a/subprojects/d2tk/nanovg/example/images/image12.jpg b/subprojects/d2tk/nanovg/example/images/image12.jpg
new file mode 100644
index 0000000..eb0369d
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/images/image12.jpg
Binary files differ
diff --git a/subprojects/d2tk/nanovg/example/images/image2.jpg b/subprojects/d2tk/nanovg/example/images/image2.jpg
new file mode 100644
index 0000000..1db15ab
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/images/image2.jpg
Binary files differ
diff --git a/subprojects/d2tk/nanovg/example/images/image3.jpg b/subprojects/d2tk/nanovg/example/images/image3.jpg
new file mode 100644
index 0000000..884f9f2
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/images/image3.jpg
Binary files differ
diff --git a/subprojects/d2tk/nanovg/example/images/image4.jpg b/subprojects/d2tk/nanovg/example/images/image4.jpg
new file mode 100644
index 0000000..f6e1039
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/images/image4.jpg
Binary files differ
diff --git a/subprojects/d2tk/nanovg/example/images/image5.jpg b/subprojects/d2tk/nanovg/example/images/image5.jpg
new file mode 100644
index 0000000..d952d16
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/images/image5.jpg
Binary files differ
diff --git a/subprojects/d2tk/nanovg/example/images/image6.jpg b/subprojects/d2tk/nanovg/example/images/image6.jpg
new file mode 100644
index 0000000..f098087
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/images/image6.jpg
Binary files differ
diff --git a/subprojects/d2tk/nanovg/example/images/image7.jpg b/subprojects/d2tk/nanovg/example/images/image7.jpg
new file mode 100644
index 0000000..623b4cb
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/images/image7.jpg
Binary files differ
diff --git a/subprojects/d2tk/nanovg/example/images/image8.jpg b/subprojects/d2tk/nanovg/example/images/image8.jpg
new file mode 100644
index 0000000..123b6da
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/images/image8.jpg
Binary files differ
diff --git a/subprojects/d2tk/nanovg/example/images/image9.jpg b/subprojects/d2tk/nanovg/example/images/image9.jpg
new file mode 100644
index 0000000..045fadb
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/images/image9.jpg
Binary files differ
diff --git a/subprojects/d2tk/nanovg/example/perf.c b/subprojects/d2tk/nanovg/example/perf.c
new file mode 100644
index 0000000..a74dc3c
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/perf.c
@@ -0,0 +1,186 @@
+#include "perf.h"
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#ifdef NANOVG_GLEW
+# include <GL/glew.h>
+#endif
+#include <GLFW/glfw3.h>
+#include "nanovg.h"
+
+#ifdef _MSC_VER
+#define snprintf _snprintf
+#elif !defined(__MINGW32__)
+#include <iconv.h>
+#endif
+
+// timer query support
+#ifndef GL_ARB_timer_query
+#define GL_TIME_ELAPSED 0x88BF
+//typedef void (APIENTRY *pfnGLGETQUERYOBJECTUI64V)(GLuint id, GLenum pname, GLuint64* params);
+//pfnGLGETQUERYOBJECTUI64V glGetQueryObjectui64v = 0;
+#endif
+
+void initGPUTimer(GPUtimer* timer)
+{
+ memset(timer, 0, sizeof(*timer));
+
+/* timer->supported = glfwExtensionSupported("GL_ARB_timer_query");
+ if (timer->supported) {
+#ifndef GL_ARB_timer_query
+ glGetQueryObjectui64v = (pfnGLGETQUERYOBJECTUI64V)glfwGetProcAddress("glGetQueryObjectui64v");
+ printf("glGetQueryObjectui64v=%p\n", glGetQueryObjectui64v);
+ if (!glGetQueryObjectui64v) {
+ timer->supported = GL_FALSE;
+ return;
+ }
+#endif
+ glGenQueries(GPU_QUERY_COUNT, timer->queries);
+ }*/
+}
+
+void startGPUTimer(GPUtimer* timer)
+{
+ if (!timer->supported)
+ return;
+ glBeginQuery(GL_TIME_ELAPSED, timer->queries[timer->cur % GPU_QUERY_COUNT] );
+ timer->cur++;
+}
+
+int stopGPUTimer(GPUtimer* timer, float* times, int maxTimes)
+{
+ NVG_NOTUSED(times);
+ NVG_NOTUSED(maxTimes);
+ GLint available = 1;
+ int n = 0;
+ if (!timer->supported)
+ return 0;
+
+ glEndQuery(GL_TIME_ELAPSED);
+ while (available && timer->ret <= timer->cur) {
+ // check for results if there are any
+ glGetQueryObjectiv(timer->queries[timer->ret % GPU_QUERY_COUNT], GL_QUERY_RESULT_AVAILABLE, &available);
+ if (available) {
+/* GLuint64 timeElapsed = 0;
+ glGetQueryObjectui64v(timer->queries[timer->ret % GPU_QUERY_COUNT], GL_QUERY_RESULT, &timeElapsed);
+ timer->ret++;
+ if (n < maxTimes) {
+ times[n] = (float)((double)timeElapsed * 1e-9);
+ n++;
+ }*/
+ }
+ }
+ return n;
+}
+
+
+void initGraph(PerfGraph* fps, int style, const char* name)
+{
+ memset(fps, 0, sizeof(PerfGraph));
+ fps->style = style;
+ strncpy(fps->name, name, sizeof(fps->name));
+ fps->name[sizeof(fps->name)-1] = '\0';
+}
+
+void updateGraph(PerfGraph* fps, float frameTime)
+{
+ fps->head = (fps->head+1) % GRAPH_HISTORY_COUNT;
+ fps->values[fps->head] = frameTime;
+}
+
+float getGraphAverage(PerfGraph* fps)
+{
+ int i;
+ float avg = 0;
+ for (i = 0; i < GRAPH_HISTORY_COUNT; i++) {
+ avg += fps->values[i];
+ }
+ return avg / (float)GRAPH_HISTORY_COUNT;
+}
+
+void renderGraph(NVGcontext* vg, float x, float y, PerfGraph* fps)
+{
+ int i;
+ float avg, w, h;
+ char str[64];
+
+ avg = getGraphAverage(fps);
+
+ w = 200;
+ h = 35;
+
+ nvgBeginPath(vg);
+ nvgRect(vg, x,y, w,h);
+ nvgFillColor(vg, nvgRGBA(0,0,0,128));
+ nvgFill(vg);
+
+ nvgBeginPath(vg);
+ nvgMoveTo(vg, x, y+h);
+ if (fps->style == GRAPH_RENDER_FPS) {
+ for (i = 0; i < GRAPH_HISTORY_COUNT; i++) {
+ float v = 1.0f / (0.00001f + fps->values[(fps->head+i) % GRAPH_HISTORY_COUNT]);
+ float vx, vy;
+ if (v > 80.0f) v = 80.0f;
+ vx = x + ((float)i/(GRAPH_HISTORY_COUNT-1)) * w;
+ vy = y + h - ((v / 80.0f) * h);
+ nvgLineTo(vg, vx, vy);
+ }
+ } else if (fps->style == GRAPH_RENDER_PERCENT) {
+ for (i = 0; i < GRAPH_HISTORY_COUNT; i++) {
+ float v = fps->values[(fps->head+i) % GRAPH_HISTORY_COUNT] * 1.0f;
+ float vx, vy;
+ if (v > 100.0f) v = 100.0f;
+ vx = x + ((float)i/(GRAPH_HISTORY_COUNT-1)) * w;
+ vy = y + h - ((v / 100.0f) * h);
+ nvgLineTo(vg, vx, vy);
+ }
+ } else {
+ for (i = 0; i < GRAPH_HISTORY_COUNT; i++) {
+ float v = fps->values[(fps->head+i) % GRAPH_HISTORY_COUNT] * 1000.0f;
+ float vx, vy;
+ if (v > 20.0f) v = 20.0f;
+ vx = x + ((float)i/(GRAPH_HISTORY_COUNT-1)) * w;
+ vy = y + h - ((v / 20.0f) * h);
+ nvgLineTo(vg, vx, vy);
+ }
+ }
+ nvgLineTo(vg, x+w, y+h);
+ nvgFillColor(vg, nvgRGBA(255,192,0,128));
+ nvgFill(vg);
+
+ nvgFontFace(vg, "sans");
+
+ if (fps->name[0] != '\0') {
+ nvgFontSize(vg, 14.0f);
+ nvgTextAlign(vg, NVG_ALIGN_LEFT|NVG_ALIGN_TOP);
+ nvgFillColor(vg, nvgRGBA(240,240,240,192));
+ nvgText(vg, x+3,y+1, fps->name, NULL);
+ }
+
+ if (fps->style == GRAPH_RENDER_FPS) {
+ nvgFontSize(vg, 18.0f);
+ nvgTextAlign(vg,NVG_ALIGN_RIGHT|NVG_ALIGN_TOP);
+ nvgFillColor(vg, nvgRGBA(240,240,240,255));
+ sprintf(str, "%.2f FPS", 1.0f / avg);
+ nvgText(vg, x+w-3,y+1, str, NULL);
+
+ nvgFontSize(vg, 15.0f);
+ nvgTextAlign(vg,NVG_ALIGN_RIGHT|NVG_ALIGN_BOTTOM);
+ nvgFillColor(vg, nvgRGBA(240,240,240,160));
+ sprintf(str, "%.2f ms", avg * 1000.0f);
+ nvgText(vg, x+w-3,y+h-1, str, NULL);
+ }
+ else if (fps->style == GRAPH_RENDER_PERCENT) {
+ nvgFontSize(vg, 18.0f);
+ nvgTextAlign(vg,NVG_ALIGN_RIGHT|NVG_ALIGN_TOP);
+ nvgFillColor(vg, nvgRGBA(240,240,240,255));
+ sprintf(str, "%.1f %%", avg * 1.0f);
+ nvgText(vg, x+w-3,y+1, str, NULL);
+ } else {
+ nvgFontSize(vg, 18.0f);
+ nvgTextAlign(vg,NVG_ALIGN_RIGHT|NVG_ALIGN_TOP);
+ nvgFillColor(vg, nvgRGBA(240,240,240,255));
+ sprintf(str, "%.2f ms", avg * 1000.0f);
+ nvgText(vg, x+w-3,y+1, str, NULL);
+ }
+}
diff --git a/subprojects/d2tk/nanovg/example/perf.h b/subprojects/d2tk/nanovg/example/perf.h
new file mode 100644
index 0000000..3ca67b2
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/perf.h
@@ -0,0 +1,46 @@
+#ifndef PERF_H
+#define PERF_H
+
+#include "nanovg.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum GraphrenderStyle {
+ GRAPH_RENDER_FPS,
+ GRAPH_RENDER_MS,
+ GRAPH_RENDER_PERCENT,
+};
+
+#define GRAPH_HISTORY_COUNT 100
+struct PerfGraph {
+ int style;
+ char name[32];
+ float values[GRAPH_HISTORY_COUNT];
+ int head;
+};
+typedef struct PerfGraph PerfGraph;
+
+void initGraph(PerfGraph* fps, int style, const char* name);
+void updateGraph(PerfGraph* fps, float frameTime);
+void renderGraph(NVGcontext* vg, float x, float y, PerfGraph* fps);
+float getGraphAverage(PerfGraph* fps);
+
+#define GPU_QUERY_COUNT 5
+struct GPUtimer {
+ int supported;
+ int cur, ret;
+ unsigned int queries[GPU_QUERY_COUNT];
+};
+typedef struct GPUtimer GPUtimer;
+
+void initGPUTimer(GPUtimer* timer);
+void startGPUTimer(GPUtimer* timer);
+int stopGPUTimer(GPUtimer* timer, float* times, int maxTimes);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PERF_H \ No newline at end of file
diff --git a/subprojects/d2tk/nanovg/example/screenshot-01.png b/subprojects/d2tk/nanovg/example/screenshot-01.png
new file mode 100644
index 0000000..d8febe9
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/screenshot-01.png
Binary files differ
diff --git a/subprojects/d2tk/nanovg/example/screenshot-02.png b/subprojects/d2tk/nanovg/example/screenshot-02.png
new file mode 100644
index 0000000..7cfa4bc
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/screenshot-02.png
Binary files differ
diff --git a/subprojects/d2tk/nanovg/example/stb_image_write.h b/subprojects/d2tk/nanovg/example/stb_image_write.h
new file mode 100644
index 0000000..5de3159
--- /dev/null
+++ b/subprojects/d2tk/nanovg/example/stb_image_write.h
@@ -0,0 +1,511 @@
+/* stbiw-0.92 - public domain - http://nothings.org/stb/stb_image_write.h
+ writes out PNG/BMP/TGA images to C stdio - Sean Barrett 2010
+ no warranty implied; use at your own risk
+
+
+Before including,
+
+ #define STB_IMAGE_WRITE_IMPLEMENTATION
+
+in the file that you want to have the implementation.
+
+
+ABOUT:
+
+ This header file is a library for writing images to C stdio. It could be
+ adapted to write to memory or a general streaming interface; let me know.
+
+ The PNG output is not optimal; it is 20-50% larger than the file
+ written by a decent optimizing implementation. This library is designed
+ for source code compactness and simplicitly, not optimal image file size
+ or run-time performance.
+
+USAGE:
+
+ There are three functions, one for each image file format:
+
+ int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes);
+ int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data);
+ int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data);
+
+ Each function returns 0 on failure and non-0 on success.
+
+ The functions create an image file defined by the parameters. The image
+ is a rectangle of pixels stored from left-to-right, top-to-bottom.
+ Each pixel contains 'comp' channels of data stored interleaved with 8-bits
+ per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is
+ monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall.
+ The *data pointer points to the first byte of the top-left-most pixel.
+ For PNG, "stride_in_bytes" is the distance in bytes from the first byte of
+ a row of pixels to the first byte of the next row of pixels.
+
+ PNG creates output files with the same number of components as the input.
+ The BMP and TGA formats expand Y to RGB in the file format. BMP does not
+ output alpha.
+
+ PNG supports writing rectangles of data even when the bytes storing rows of
+ data are not consecutive in memory (e.g. sub-rectangles of a larger image),
+ by supplying the stride between the beginning of adjacent rows. The other
+ formats do not. (Thus you cannot write a native-format BMP through the BMP
+ writer, both because it is in BGR order and because it may have padding
+ at the end of the line.)
+*/
+
+#ifndef INCLUDE_STB_IMAGE_WRITE_H
+#define INCLUDE_STB_IMAGE_WRITE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes);
+extern int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data);
+extern int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif//INCLUDE_STB_IMAGE_WRITE_H
+
+#ifdef STB_IMAGE_WRITE_IMPLEMENTATION
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+typedef unsigned int stbiw_uint32;
+typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1];
+
+static void writefv(FILE *f, const char *fmt, va_list v)
+{
+ while (*fmt) {
+ switch (*fmt++) {
+ case ' ': break;
+ case '1': { unsigned char x = (unsigned char) va_arg(v, int); fputc(x,f); break; }
+ case '2': { int x = va_arg(v,int); unsigned char b[2];
+ b[0] = (unsigned char) x; b[1] = (unsigned char) (x>>8);
+ fwrite(b,2,1,f); break; }
+ case '4': { stbiw_uint32 x = va_arg(v,int); unsigned char b[4];
+ b[0]=(unsigned char)x; b[1]=(unsigned char)(x>>8);
+ b[2]=(unsigned char)(x>>16); b[3]=(unsigned char)(x>>24);
+ fwrite(b,4,1,f); break; }
+ default:
+ assert(0);
+ return;
+ }
+ }
+}
+
+static void write3(FILE *f, unsigned char a, unsigned char b, unsigned char c)
+{
+ unsigned char arr[3];
+ arr[0] = a, arr[1] = b, arr[2] = c;
+ fwrite(arr, 3, 1, f);
+}
+
+static void write_pixels(FILE *f, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad)
+{
+ unsigned char bg[3] = { 255, 0, 255}, px[3];
+ stbiw_uint32 zero = 0;
+ int i,j,k, j_end;
+
+ if (y <= 0)
+ return;
+
+ if (vdir < 0)
+ j_end = -1, j = y-1;
+ else
+ j_end = y, j = 0;
+
+ for (; j != j_end; j += vdir) {
+ for (i=0; i < x; ++i) {
+ unsigned char *d = (unsigned char *) data + (j*x+i)*comp;
+ if (write_alpha < 0)
+ fwrite(&d[comp-1], 1, 1, f);
+ switch (comp) {
+ case 1:
+ case 2: write3(f, d[0],d[0],d[0]);
+ break;
+ case 4:
+ if (!write_alpha) {
+ // composite against pink background
+ for (k=0; k < 3; ++k)
+ px[k] = bg[k] + ((d[k] - bg[k]) * d[3])/255;
+ write3(f, px[1-rgb_dir],px[1],px[1+rgb_dir]);
+ break;
+ }
+ /* FALLTHROUGH */
+ case 3:
+ write3(f, d[1-rgb_dir],d[1],d[1+rgb_dir]);
+ break;
+ }
+ if (write_alpha > 0)
+ fwrite(&d[comp-1], 1, 1, f);
+ }
+ fwrite(&zero,scanline_pad,1,f);
+ }
+}
+
+static int outfile(char const *filename, int rgb_dir, int vdir, int x, int y, int comp, void *data, int alpha, int pad, const char *fmt, ...)
+{
+ FILE *f;
+ if (y < 0 || x < 0) return 0;
+ f = fopen(filename, "wb");
+ if (f) {
+ va_list v;
+ va_start(v, fmt);
+ writefv(f, fmt, v);
+ va_end(v);
+ write_pixels(f,rgb_dir,vdir,x,y,comp,data,alpha,pad);
+ fclose(f);
+ }
+ return f != NULL;
+}
+
+int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data)
+{
+ int pad = (-x*3) & 3;
+ return outfile(filename,-1,-1,x,y,comp,(void *) data,0,pad,
+ "11 4 22 4" "4 44 22 444444",
+ 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header
+ 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header
+}
+
+int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data)
+{
+ int has_alpha = !(comp & 1);
+ return outfile(filename, -1,-1, x, y, comp, (void *) data, has_alpha, 0,
+ "111 221 2222 11", 0,0,2, 0,0,0, 0,0,x,y, 24+8*has_alpha, 8*has_alpha);
+}
+
+// stretchy buffer; stbi__sbpush() == vector<>::push_back() -- stbi__sbcount() == vector<>::size()
+#define stbi__sbraw(a) ((int *) (a) - 2)
+#define stbi__sbm(a) stbi__sbraw(a)[0]
+#define stbi__sbn(a) stbi__sbraw(a)[1]
+
+#define stbi__sbneedgrow(a,n) ((a)==0 || stbi__sbn(a)+n >= stbi__sbm(a))
+#define stbi__sbmaybegrow(a,n) (stbi__sbneedgrow(a,(n)) ? stbi__sbgrow(a,n) : 0)
+#define stbi__sbgrow(a,n) stbi__sbgrowf((void **) &(a), (n), sizeof(*(a)))
+
+#define stbi__sbpush(a, v) (stbi__sbmaybegrow(a,1), (a)[stbi__sbn(a)++] = (v))
+#define stbi__sbcount(a) ((a) ? stbi__sbn(a) : 0)
+#define stbi__sbfree(a) ((a) ? free(stbi__sbraw(a)),0 : 0)
+
+static void *stbi__sbgrowf(void **arr, int increment, int itemsize)
+{
+ int m = *arr ? 2*stbi__sbm(*arr)+increment : increment+1;
+ void *p = realloc(*arr ? stbi__sbraw(*arr) : 0, itemsize * m + sizeof(int)*2);
+ assert(p);
+ if (p) {
+ if (!*arr) ((int *) p)[1] = 0;
+ *arr = (void *) ((int *) p + 2);
+ stbi__sbm(*arr) = m;
+ }
+ return *arr;
+}
+
+static unsigned char *stbi__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount)
+{
+ while (*bitcount >= 8) {
+ stbi__sbpush(data, (unsigned char) *bitbuffer);
+ *bitbuffer >>= 8;
+ *bitcount -= 8;
+ }
+ return data;
+}
+
+static int stbi__zlib_bitrev(int code, int codebits)
+{
+ int res=0;
+ while (codebits--) {
+ res = (res << 1) | (code & 1);
+ code >>= 1;
+ }
+ return res;
+}
+
+static unsigned int stbi__zlib_countm(unsigned char *a, unsigned char *b, int limit)
+{
+ int i;
+ for (i=0; i < limit && i < 258; ++i)
+ if (a[i] != b[i]) break;
+ return i;
+}
+
+static unsigned int stbi__zhash(unsigned char *data)
+{
+ stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16);
+ hash ^= hash << 3;
+ hash += hash >> 5;
+ hash ^= hash << 4;
+ hash += hash >> 17;
+ hash ^= hash << 25;
+ hash += hash >> 6;
+ return hash;
+}
+
+#define stbi__zlib_flush() (out = stbi__zlib_flushf(out, &bitbuf, &bitcount))
+#define stbi__zlib_add(code,codebits) \
+ (bitbuf |= (code) << bitcount, bitcount += (codebits), stbi__zlib_flush())
+#define stbi__zlib_huffa(b,c) stbi__zlib_add(stbi__zlib_bitrev(b,c),c)
+// default huffman tables
+#define stbi__zlib_huff1(n) stbi__zlib_huffa(0x30 + (n), 8)
+#define stbi__zlib_huff2(n) stbi__zlib_huffa(0x190 + (n)-144, 9)
+#define stbi__zlib_huff3(n) stbi__zlib_huffa(0 + (n)-256,7)
+#define stbi__zlib_huff4(n) stbi__zlib_huffa(0xc0 + (n)-280,8)
+#define stbi__zlib_huff(n) ((n) <= 143 ? stbi__zlib_huff1(n) : (n) <= 255 ? stbi__zlib_huff2(n) : (n) <= 279 ? stbi__zlib_huff3(n) : stbi__zlib_huff4(n))
+#define stbi__zlib_huffb(n) ((n) <= 143 ? stbi__zlib_huff1(n) : stbi__zlib_huff2(n))
+
+#define stbi__ZHASH 16384
+
+unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality)
+{
+ static unsigned short lengthc[] = { 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, 259 };
+ static unsigned char lengtheb[]= { 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 };
+ static unsigned short distc[] = { 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, 32768 };
+ static unsigned char disteb[] = { 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 };
+ unsigned int bitbuf=0;
+ int i,j, bitcount=0;
+ unsigned char *out = NULL;
+ unsigned char **hash_table[stbi__ZHASH]; // 64KB on the stack!
+ if (quality < 5) quality = 5;
+
+ stbi__sbpush(out, 0x78); // DEFLATE 32K window
+ stbi__sbpush(out, 0x5e); // FLEVEL = 1
+ stbi__zlib_add(1,1); // BFINAL = 1
+ stbi__zlib_add(1,2); // BTYPE = 1 -- fixed huffman
+
+ for (i=0; i < stbi__ZHASH; ++i)
+ hash_table[i] = NULL;
+
+ i=0;
+ while (i < data_len-3) {
+ // hash next 3 bytes of data to be compressed
+ int h = stbi__zhash(data+i)&(stbi__ZHASH-1), best=3;
+ unsigned char *bestloc = 0;
+ unsigned char **hlist = hash_table[h];
+ int n = stbi__sbcount(hlist);
+ for (j=0; j < n; ++j) {
+ if (hlist[j]-data > i-32768) { // if entry lies within window
+ int d = stbi__zlib_countm(hlist[j], data+i, data_len-i);
+ if (d >= best) best=d,bestloc=hlist[j];
+ }
+ }
+ // when hash table entry is too long, delete half the entries
+ if (hash_table[h] && stbi__sbn(hash_table[h]) == 2*quality) {
+ memcpy(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality);
+ stbi__sbn(hash_table[h]) = quality;
+ }
+ stbi__sbpush(hash_table[h],data+i);
+
+ if (bestloc) {
+ // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal
+ h = stbi__zhash(data+i+1)&(stbi__ZHASH-1);
+ hlist = hash_table[h];
+ n = stbi__sbcount(hlist);
+ for (j=0; j < n; ++j) {
+ if (hlist[j]-data > i-32767) {
+ int e = stbi__zlib_countm(hlist[j], data+i+1, data_len-i-1);
+ if (e > best) { // if next match is better, bail on current match
+ bestloc = NULL;
+ break;
+ }
+ }
+ }
+ }
+
+ if (bestloc) {
+ int d = data+i - bestloc; // distance back
+ assert(d <= 32767 && best <= 258);
+ for (j=0; best > lengthc[j+1]-1; ++j);
+ stbi__zlib_huff(j+257);
+ if (lengtheb[j]) stbi__zlib_add(best - lengthc[j], lengtheb[j]);
+ for (j=0; d > distc[j+1]-1; ++j);
+ stbi__zlib_add(stbi__zlib_bitrev(j,5),5);
+ if (disteb[j]) stbi__zlib_add(d - distc[j], disteb[j]);
+ i += best;
+ } else {
+ stbi__zlib_huffb(data[i]);
+ ++i;
+ }
+ }
+ // write out final bytes
+ for (;i < data_len; ++i)
+ stbi__zlib_huffb(data[i]);
+ stbi__zlib_huff(256); // end of block
+ // pad with 0 bits to byte boundary
+ while (bitcount)
+ stbi__zlib_add(0,1);
+
+ for (i=0; i < stbi__ZHASH; ++i)
+ (void) stbi__sbfree(hash_table[i]);
+
+ {
+ // compute adler32 on input
+ unsigned int i=0, s1=1, s2=0, blocklen = data_len % 5552;
+ int j=0;
+ while (j < data_len) {
+ for (i=0; i < blocklen; ++i) s1 += data[j+i], s2 += s1;
+ s1 %= 65521, s2 %= 65521;
+ j += blocklen;
+ blocklen = 5552;
+ }
+ stbi__sbpush(out, (unsigned char) (s2 >> 8));
+ stbi__sbpush(out, (unsigned char) s2);
+ stbi__sbpush(out, (unsigned char) (s1 >> 8));
+ stbi__sbpush(out, (unsigned char) s1);
+ }
+ *out_len = stbi__sbn(out);
+ // make returned pointer freeable
+ memmove(stbi__sbraw(out), out, *out_len);
+ return (unsigned char *) stbi__sbraw(out);
+}
+
+unsigned int stbi__crc32(unsigned char *buffer, int len)
+{
+ static unsigned int crc_table[256];
+ unsigned int crc = ~0u;
+ int i,j;
+ if (crc_table[1] == 0)
+ for(i=0; i < 256; i++)
+ for (crc_table[i]=i, j=0; j < 8; ++j)
+ crc_table[i] = (crc_table[i] >> 1) ^ (crc_table[i] & 1 ? 0xedb88320 : 0);
+ for (i=0; i < len; ++i)
+ crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)];
+ return ~crc;
+}
+
+#define stbi__wpng4(o,a,b,c,d) ((o)[0]=(unsigned char)(a),(o)[1]=(unsigned char)(b),(o)[2]=(unsigned char)(c),(o)[3]=(unsigned char)(d),(o)+=4)
+#define stbi__wp32(data,v) stbi__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v));
+#define stbi__wptag(data,s) stbi__wpng4(data, s[0],s[1],s[2],s[3])
+
+static void stbi__wpcrc(unsigned char **data, int len)
+{
+ unsigned int crc = stbi__crc32(*data - len - 4, len+4);
+ stbi__wp32(*data, crc);
+}
+
+static unsigned char stbi__paeth(int a, int b, int c)
+{
+ int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c);
+ if (pa <= pb && pa <= pc) return (unsigned char) a;
+ if (pb <= pc) return (unsigned char) b;
+ return (unsigned char) c;
+}
+
+unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len)
+{
+ int ctype[5] = { -1, 0, 4, 2, 6 };
+ unsigned char sig[8] = { 137,80,78,71,13,10,26,10 };
+ unsigned char *out,*o, *filt, *zlib;
+ signed char *line_buffer;
+ int i,j,k,p,zlen;
+
+ if (stride_bytes == 0)
+ stride_bytes = x * n;
+
+ filt = (unsigned char *) malloc((x*n+1) * y); if (!filt) return 0;
+ line_buffer = (signed char *) malloc(x * n); if (!line_buffer) { free(filt); return 0; }
+ for (j=0; j < y; ++j) {
+ static int mapping[] = { 0,1,2,3,4 };
+ static int firstmap[] = { 0,1,0,5,6 };
+ int *mymap = j ? mapping : firstmap;
+ int best = 0, bestval = 0x7fffffff;
+ for (p=0; p < 2; ++p) {
+ for (k= p?best:0; k < 5; ++k) {
+ int type = mymap[k],est=0;
+ unsigned char *z = pixels + stride_bytes*j;
+ for (i=0; i < n; ++i)
+ switch (type) {
+ case 0: line_buffer[i] = z[i]; break;
+ case 1: line_buffer[i] = z[i]; break;
+ case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break;
+ case 3: line_buffer[i] = z[i] - (z[i-stride_bytes]>>1); break;
+ case 4: line_buffer[i] = (signed char) (z[i] - stbi__paeth(0,z[i-stride_bytes],0)); break;
+ case 5: line_buffer[i] = z[i]; break;
+ case 6: line_buffer[i] = z[i]; break;
+ }
+ for (i=n; i < x*n; ++i) {
+ switch (type) {
+ case 0: line_buffer[i] = z[i]; break;
+ case 1: line_buffer[i] = z[i] - z[i-n]; break;
+ case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break;
+ case 3: line_buffer[i] = z[i] - ((z[i-n] + z[i-stride_bytes])>>1); break;
+ case 4: line_buffer[i] = z[i] - stbi__paeth(z[i-n], z[i-stride_bytes], z[i-stride_bytes-n]); break;
+ case 5: line_buffer[i] = z[i] - (z[i-n]>>1); break;
+ case 6: line_buffer[i] = z[i] - stbi__paeth(z[i-n], 0,0); break;
+ }
+ }
+ if (p) break;
+ for (i=0; i < x*n; ++i)
+ est += abs((signed char) line_buffer[i]);
+ if (est < bestval) { bestval = est; best = k; }
+ }
+ }
+ // when we get here, best contains the filter type, and line_buffer contains the data
+ filt[j*(x*n+1)] = (unsigned char) best;
+ memcpy(filt+j*(x*n+1)+1, line_buffer, x*n);
+ }
+ free(line_buffer);
+ zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, 8); // increase 8 to get smaller but use more memory
+ free(filt);
+ if (!zlib) return 0;
+
+ // each tag requires 12 bytes of overhead
+ out = (unsigned char *) malloc(8 + 12+13 + 12+zlen + 12);
+ if (!out) return 0;
+ *out_len = 8 + 12+13 + 12+zlen + 12;
+
+ o=out;
+ memcpy(o,sig,8); o+= 8;
+ stbi__wp32(o, 13); // header length
+ stbi__wptag(o, "IHDR");
+ stbi__wp32(o, x);
+ stbi__wp32(o, y);
+ *o++ = 8;
+ *o++ = (unsigned char) ctype[n];
+ *o++ = 0;
+ *o++ = 0;
+ *o++ = 0;
+ stbi__wpcrc(&o,13);
+
+ stbi__wp32(o, zlen);
+ stbi__wptag(o, "IDAT");
+ memcpy(o, zlib, zlen); o += zlen; free(zlib);
+ stbi__wpcrc(&o, zlen);
+
+ stbi__wp32(o,0);
+ stbi__wptag(o, "IEND");
+ stbi__wpcrc(&o,0);
+
+ assert(o == out + *out_len);
+
+ return out;
+}
+
+int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes)
+{
+ FILE *f;
+ int len;
+ unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len);
+ if (!png) return 0;
+ f = fopen(filename, "wb");
+ if (!f) { free(png); return 0; }
+ fwrite(png, 1, len, f);
+ fclose(f);
+ free(png);
+ return 1;
+}
+#endif // STB_IMAGE_WRITE_IMPLEMENTATION
+
+/* Revision history
+
+ 0.92 (2010-08-01)
+ casts to unsigned char to fix warnings
+ 0.91 (2010-07-17)
+ first public release
+ 0.90 first internal release
+*/
diff --git a/subprojects/d2tk/nanovg/obsolete/nanovg_gl2.h b/subprojects/d2tk/nanovg/obsolete/nanovg_gl2.h
new file mode 100644
index 0000000..ad8883a
--- /dev/null
+++ b/subprojects/d2tk/nanovg/obsolete/nanovg_gl2.h
@@ -0,0 +1,952 @@
+//
+// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+//
+#ifndef NANOVG_GL2_H
+#define NANOVG_GL2_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define NVG_ANTIALIAS 1
+
+#ifdef NANOVG_GLES2_IMPLEMENTATION
+# ifndef NANOVG_GLES2
+# define NANOVG_GLES2
+# endif
+# ifndef NANOVG_GL2_IMPLEMENTATION
+# define NANOVG_GL2_IMPLEMENTATION
+# endif
+#endif
+
+#ifdef NANOVG_GLES2
+
+struct NVGcontext* nvgCreateGLES2(int atlasw, int atlash, int edgeaa);
+void nvgDeleteGLES2(struct NVGcontext* ctx);
+
+#else
+
+struct NVGcontext* nvgCreateGL2(int atlasw, int atlash, int edgeaa);
+void nvgDeleteGL2(struct NVGcontext* ctx);
+
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+#ifdef NANOVG_GL2_IMPLEMENTATION
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include "nanovg.h"
+
+enum GLNVGuniformLoc {
+ GLNVG_LOC_VIEWSIZE,
+ GLNVG_LOC_SCISSORMAT,
+ GLNVG_LOC_SCISSOREXT,
+ GLNVG_LOC_SCISSORSCALE,
+ GLNVG_LOC_PAINTMAT,
+ GLNVG_LOC_EXTENT,
+ GLNVG_LOC_RADIUS,
+ GLNVG_LOC_FEATHER,
+ GLNVG_LOC_INNERCOL,
+ GLNVG_LOC_OUTERCOL,
+ GLNVG_LOC_STROKEMULT,
+ GLNVG_LOC_TEX,
+ GLNVG_LOC_TEXTYPE,
+ GLNVG_LOC_TYPE,
+ GLNVG_MAX_LOCS
+};
+
+enum GLNVGshaderType {
+ NSVG_SHADER_FILLGRAD,
+ NSVG_SHADER_FILLIMG,
+ NSVG_SHADER_SIMPLE,
+ NSVG_SHADER_IMG
+};
+
+struct GLNVGshader {
+ GLuint prog;
+ GLuint frag;
+ GLuint vert;
+ GLint loc[GLNVG_MAX_LOCS];
+};
+
+struct GLNVGtexture {
+ int id;
+ GLuint tex;
+ int width, height;
+ int type;
+};
+
+struct GLNVGcontext {
+ struct GLNVGshader shader;
+ struct GLNVGtexture* textures;
+ float viewWidth, viewHeight;
+ int ntextures;
+ int ctextures;
+ int textureId;
+ GLuint vertBuf;
+ int edgeAntiAlias;
+};
+
+static struct GLNVGtexture* glnvg__allocTexture(struct GLNVGcontext* gl)
+{
+ struct GLNVGtexture* tex = NULL;
+ int i;
+
+ for (i = 0; i < gl->ntextures; i++) {
+ if (gl->textures[i].id == 0) {
+ tex = &gl->textures[i];
+ break;
+ }
+ }
+ if (tex == NULL) {
+ if (gl->ntextures+1 > gl->ctextures) {
+ gl->ctextures = (gl->ctextures == 0) ? 2 : gl->ctextures*2;
+ gl->textures = (struct GLNVGtexture*)realloc(gl->textures, sizeof(struct GLNVGtexture)*gl->ctextures);
+ if (gl->textures == NULL) return NULL;
+ }
+ tex = &gl->textures[gl->ntextures++];
+ }
+
+ memset(tex, 0, sizeof(*tex));
+ tex->id = ++gl->textureId;
+
+ return tex;
+}
+
+static struct GLNVGtexture* glnvg__findTexture(struct GLNVGcontext* gl, int id)
+{
+ int i;
+ for (i = 0; i < gl->ntextures; i++)
+ if (gl->textures[i].id == id)
+ return &gl->textures[i];
+ return NULL;
+}
+
+static int glnvg__deleteTexture(struct GLNVGcontext* gl, int id)
+{
+ int i;
+ for (i = 0; i < gl->ntextures; i++) {
+ if (gl->textures[i].id == id) {
+ if (gl->textures[i].tex != 0)
+ glDeleteTextures(1, &gl->textures[i].tex);
+ memset(&gl->textures[i], 0, sizeof(gl->textures[i]));
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void glnvg__dumpShaderError(GLuint shader, const char* name, const char* type)
+{
+ char str[512+1];
+ int len = 0;
+ glGetShaderInfoLog(shader, 512, &len, str);
+ if (len > 512) len = 512;
+ str[len] = '\0';
+ printf("Shader %s/%s error:\n%s\n", name, type, str);
+}
+
+static void glnvg__dumpProgramError(GLuint prog, const char* name)
+{
+ char str[512+1];
+ int len = 0;
+ glGetProgramInfoLog(prog, 512, &len, str);
+ if (len > 512) len = 512;
+ str[len] = '\0';
+ printf("Program %s error:\n%s\n", name, str);
+}
+
+static int glnvg__checkError(const char* str)
+{
+ GLenum err = glGetError();
+ if (err != GL_NO_ERROR) {
+ printf("Error %08x after %s\n", err, str);
+ return 1;
+ }
+ return 0;
+}
+
+static int glnvg__createShader(struct GLNVGshader* shader, const char* name, const char* vshader, const char* fshader)
+{
+ GLint status;
+ GLuint prog, vert, frag;
+
+ memset(shader, 0, sizeof(*shader));
+
+ prog = glCreateProgram();
+ vert = glCreateShader(GL_VERTEX_SHADER);
+ frag = glCreateShader(GL_FRAGMENT_SHADER);
+ glShaderSource(vert, 1, &vshader, 0);
+ glShaderSource(frag, 1, &fshader, 0);
+
+ glCompileShader(vert);
+ glGetShaderiv(vert, GL_COMPILE_STATUS, &status);
+ if (status != GL_TRUE) {
+ glnvg__dumpShaderError(vert, name, "vert");
+ return 0;
+ }
+
+ glCompileShader(frag);
+ glGetShaderiv(frag, GL_COMPILE_STATUS, &status);
+ if (status != GL_TRUE) {
+ glnvg__dumpShaderError(frag, name, "frag");
+ return 0;
+ }
+
+ glAttachShader(prog, vert);
+ glAttachShader(prog, frag);
+
+ glBindAttribLocation(prog, 0, "vertex");
+ glBindAttribLocation(prog, 1, "tcoord");
+ glBindAttribLocation(prog, 2, "color");
+
+ glLinkProgram(prog);
+ glGetProgramiv(prog, GL_LINK_STATUS, &status);
+ if (status != GL_TRUE) {
+ glnvg__dumpProgramError(prog, name);
+ return 0;
+ }
+
+ shader->prog = prog;
+ shader->vert = vert;
+ shader->frag = frag;
+
+ return 1;
+}
+
+static void glnvg__deleteShader(struct GLNVGshader* shader)
+{
+ if (shader->prog != 0)
+ glDeleteProgram(shader->prog);
+ if (shader->vert != 0)
+ glDeleteShader(shader->vert);
+ if (shader->frag != 0)
+ glDeleteShader(shader->frag);
+}
+
+static void glnvg__getUniforms(struct GLNVGshader* shader)
+{
+ shader->loc[GLNVG_LOC_VIEWSIZE] = glGetUniformLocation(shader->prog, "viewSize");
+ shader->loc[GLNVG_LOC_SCISSORMAT] = glGetUniformLocation(shader->prog, "scissorMat");
+ shader->loc[GLNVG_LOC_SCISSOREXT] = glGetUniformLocation(shader->prog, "scissorExt");
+ shader->loc[GLNVG_LOC_SCISSORSCALE] = glGetUniformLocation(shader->prog, "scissorScale");
+ shader->loc[GLNVG_LOC_PAINTMAT] = glGetUniformLocation(shader->prog, "paintMat");
+ shader->loc[GLNVG_LOC_EXTENT] = glGetUniformLocation(shader->prog, "extent");
+ shader->loc[GLNVG_LOC_RADIUS] = glGetUniformLocation(shader->prog, "radius");
+ shader->loc[GLNVG_LOC_FEATHER] = glGetUniformLocation(shader->prog, "feather");
+ shader->loc[GLNVG_LOC_INNERCOL] = glGetUniformLocation(shader->prog, "innerCol");
+ shader->loc[GLNVG_LOC_OUTERCOL] = glGetUniformLocation(shader->prog, "outerCol");
+ shader->loc[GLNVG_LOC_STROKEMULT] = glGetUniformLocation(shader->prog, "strokeMult");
+ shader->loc[GLNVG_LOC_TEX] = glGetUniformLocation(shader->prog, "tex");
+ shader->loc[GLNVG_LOC_TEXTYPE] = glGetUniformLocation(shader->prog, "texType");
+ shader->loc[GLNVG_LOC_TYPE] = glGetUniformLocation(shader->prog, "type");
+}
+
+static int glnvg__renderCreate(void* uptr)
+{
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+
+ static const char* fillVertShader =
+#ifdef NANOVG_GLES2
+ "#version 100\n"
+ "precision mediump float;\n"
+#endif
+ "uniform vec2 viewSize;\n"
+ "attribute vec2 vertex;\n"
+ "attribute vec2 tcoord;\n"
+ "attribute vec4 color;\n"
+ "varying vec2 ftcoord;\n"
+ "varying vec4 fcolor;\n"
+ "varying vec2 fpos;\n"
+ "void main(void) {\n"
+ " ftcoord = tcoord;\n"
+ " fcolor = color;\n"
+ " fpos = vertex;\n"
+ " gl_Position = vec4(2.0*vertex.x/viewSize.x - 1.0, 1.0 - 2.0*vertex.y/viewSize.y, 0, 1);\n"
+ "}\n";
+
+ static const char* fillFragShaderEdgeAA =
+#ifdef NANOVG_GLES2
+ "#version 100\n"
+ "precision mediump float;\n"
+#endif
+ "uniform mat3 scissorMat;\n"
+ "uniform vec2 scissorExt;\n"
+ "uniform vec2 scissorScale;\n"
+ "uniform mat3 paintMat;\n"
+ "uniform vec2 extent;\n"
+ "uniform float radius;\n"
+ "uniform float feather;\n"
+ "uniform vec4 innerCol;\n"
+ "uniform vec4 outerCol;\n"
+ "uniform float strokeMult;\n"
+ "uniform sampler2D tex;\n"
+ "uniform int texType;\n"
+ "uniform int type;\n"
+ "varying vec2 ftcoord;\n"
+ "varying vec4 fcolor;\n"
+ "varying vec2 fpos;\n"
+ "\n"
+ "float sdroundrect(vec2 pt, vec2 ext, float rad) {\n"
+ " vec2 ext2 = ext - vec2(rad,rad);\n"
+ " vec2 d = abs(pt) - ext2;\n"
+ " return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rad;\n"
+ "}\n"
+ "\n"
+ "// Scissoring\n"
+ "float scissorMask(vec2 p) {\n"
+ " vec2 sc = (abs((scissorMat * vec3(p,1.0)).xy) - scissorExt);\n"
+ " sc = vec2(0.5,0.5) - sc * scissorScale;\n"
+ " return clamp(sc.x,0.0,1.0) * clamp(sc.y,0.0,1.0);\n"
+ "}\n"
+ "\n"
+ "// Stroke - from [0..1] to clipped pyramid, where the slope is 1px.\n"
+ "float strokeMask() {\n"
+ " return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult) * ftcoord.y;\n"
+ "}\n"
+ "\n"
+ "void main(void) {\n"
+ " if (type == 0) {\n"
+ " float scissor = scissorMask(fpos);\n"
+ " float strokeAlpha = strokeMask();\n"
+ " // Calculate gradient color using box gradient\n"
+ " vec2 pt = (paintMat * vec3(fpos,1.0)).xy;\n"
+ " float d = clamp((sdroundrect(pt, extent, radius) + feather*0.5) / feather, 0.0, 1.0);\n"
+ " vec4 color = mix(innerCol,outerCol,d);\n"
+ " // Combine alpha\n"
+ " color.w *= strokeAlpha * scissor;\n"
+ " gl_FragColor = color;\n"
+ " } else if (type == 1) {\n"
+ " float scissor = scissorMask(fpos);\n"
+ " float strokeAlpha = strokeMask();\n"
+ " // Calculate color fron texture\n"
+ " vec2 pt = (paintMat * vec3(fpos,1.0)).xy / extent;\n"
+ " vec4 color = texture2D(tex, pt);\n"
+ " color = texType == 0 ? color : vec4(1,1,1,color.x);\n"
+ " // Combine alpha\n"
+ " color.w *= strokeAlpha * scissor;\n"
+ " gl_FragColor = color;\n"
+ " } else if (type == 2) {\n"
+ " gl_FragColor = vec4(1,1,1,1);\n"
+ " } else if (type == 3) {\n"
+ " vec4 color = texture2D(tex, ftcoord);\n"
+ " color = texType == 0 ? color : vec4(1,1,1,color.x);\n"
+ " gl_FragColor = color * fcolor;\n"
+ " }\n"
+ "}\n";
+
+ static const char* fillFragShader =
+#ifdef NANOVG_GLES2
+ "#version 100\n"
+ "precision mediump float;\n"
+#endif
+ "uniform mat3 scissorMat;\n"
+ "uniform vec2 scissorExt;\n"
+ "uniform vec2 scissorScale;\n"
+ "uniform mat3 paintMat;\n"
+ "uniform vec2 extent;\n"
+ "uniform float radius;\n"
+ "uniform float feather;\n"
+ "uniform vec4 innerCol;\n"
+ "uniform vec4 outerCol;\n"
+ "uniform float strokeMult;\n"
+ "uniform sampler2D tex;\n"
+ "uniform int texType;\n"
+ "uniform int type;\n"
+ "varying vec2 ftcoord;\n"
+ "varying vec4 fcolor;\n"
+ "varying vec2 fpos;\n"
+ "\n"
+ "float sdroundrect(vec2 pt, vec2 ext, float rad) {\n"
+ " vec2 ext2 = ext - vec2(rad,rad);\n"
+ " vec2 d = abs(pt) - ext2;\n"
+ " return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rad;\n"
+ "}\n"
+ "\n"
+ "// Scissoring\n"
+ "float scissorMask(vec2 p) {\n"
+ " vec2 sc = (abs((scissorMat * vec3(p,1.0)).xy) - scissorExt);\n"
+ " sc = vec2(0.5,0.5) - sc * scissorScale;\n"
+ " return clamp(sc.x,0.0,1.0) * clamp(sc.y,0.0,1.0);\n"
+ "}\n"
+ "\n"
+ "void main(void) {\n"
+ " if (type == 0) {\n"
+ " float scissor = scissorMask(fpos);\n"
+ " // Calculate gradient color using box gradient\n"
+ " vec2 pt = (paintMat * vec3(fpos,1.0)).xy;\n"
+ " float d = clamp((sdroundrect(pt, extent, radius) + feather*0.5) / feather, 0.0, 1.0);\n"
+ " vec4 color = mix(innerCol,outerCol,d);\n"
+ " // Combine alpha\n"
+ " color.w *= scissor;\n"
+ " gl_FragColor = color;\n"
+ " } else if (type == 1) {\n"
+ " float scissor = scissorMask(fpos);\n"
+ " // Calculate color fron texture\n"
+ " vec2 pt = (paintMat * vec3(fpos,1.0)).xy / extent;\n"
+ " vec4 color = texture2D(tex, pt);\n"
+ " color = texType == 0 ? color : vec4(1,1,1,color.x);\n"
+ " // Combine alpha\n"
+ " color.w *= scissor;\n"
+ " gl_FragColor = color;\n"
+ " } else if (type == 2) {\n"
+ " gl_FragColor = vec4(1,1,1,1);\n"
+ " } else if (type == 3) {\n"
+ " vec4 color = texture2D(tex, ftcoord);\n"
+ " color = texType == 0 ? color : vec4(1,1,1,color.x);\n"
+ " gl_FragColor = color * fcolor;\n"
+ " }\n"
+ "}\n";
+
+ glnvg__checkError("init");
+
+ if (gl->edgeAntiAlias) {
+ if (glnvg__createShader(&gl->shader, "shader", fillVertShader, fillFragShaderEdgeAA) == 0)
+ return 0;
+ } else {
+ if (glnvg__createShader(&gl->shader, "shader", fillVertShader, fillFragShader) == 0)
+ return 0;
+ }
+
+ glnvg__checkError("uniform locations");
+ glnvg__getUniforms(&gl->shader);
+
+ // Create dynamic vertex array
+ glGenBuffers(1, &gl->vertBuf);
+
+ glnvg__checkError("done");
+
+ return 1;
+}
+
+static int glnvg__renderCreateTexture(void* uptr, int type, int w, int h, const unsigned char* data)
+{
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+ struct GLNVGtexture* tex = glnvg__allocTexture(gl);
+ if (tex == NULL) return 0;
+ glGenTextures(1, &tex->tex);
+ tex->width = w;
+ tex->height = h;
+ tex->type = type;
+ glBindTexture(GL_TEXTURE_2D, tex->tex);
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT,1);
+
+ if (type == NVG_TEXTURE_RGBA)
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ else
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w, h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ if (glnvg__checkError("create tex"))
+ return 0;
+
+ return tex->id;
+}
+
+static int glnvg__renderDeleteTexture(void* uptr, int image)
+{
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+ return glnvg__deleteTexture(gl, image);
+}
+
+static int glnvg__renderUpdateTexture(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data)
+{
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+ struct GLNVGtexture* tex = glnvg__findTexture(gl, image);
+
+ if (tex == NULL) return 0;
+ glBindTexture(GL_TEXTURE_2D, tex->tex);
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT,1);
+#ifdef NANOVG_GLES2
+ // No support for all of unpack, need to update a whole row at a time.
+ if (tex->type == NVG_TEXTURE_RGBA)
+ data += y*tex->width*4;
+ else
+ data += y*tex->width;
+ x = 0;
+ w = tex->width;
+#else
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->width);
+ glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
+ glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
+#endif
+
+ if (tex->type == NVG_TEXTURE_RGBA)
+ glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ else
+ glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
+
+ return 1;
+}
+
+static int glnvg__renderGetTextureSize(void* uptr, int image, int* w, int* h)
+{
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+ struct GLNVGtexture* tex = glnvg__findTexture(gl, image);
+ if (tex == NULL) return 0;
+ *w = tex->width;
+ *h = tex->height;
+ return 1;
+}
+
+static void glnvg__xformIdentity(float* t)
+{
+ t[0] = 1.0f; t[1] = 0.0f;
+ t[2] = 0.0f; t[3] = 1.0f;
+ t[4] = 0.0f; t[5] = 0.0f;
+}
+
+static void glnvg__xformInverse(float* inv, float* t)
+{
+ double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1];
+ if (det > -1e-6 && det < 1e-6) {
+ glnvg__xformIdentity(t);
+ return;
+ }
+ invdet = 1.0 / det;
+ inv[0] = (float)(t[3] * invdet);
+ inv[2] = (float)(-t[2] * invdet);
+ inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet);
+ inv[1] = (float)(-t[1] * invdet);
+ inv[3] = (float)(t[0] * invdet);
+ inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet);
+}
+
+static void glnvg__xformToMat3x3(float* m3, float* t)
+{
+ m3[0] = t[0];
+ m3[1] = t[1];
+ m3[2] = 0.0f;
+ m3[3] = t[2];
+ m3[4] = t[3];
+ m3[5] = 0.0f;
+ m3[6] = t[4];
+ m3[7] = t[5];
+ m3[8] = 1.0f;
+}
+
+static int glnvg__setupPaint(struct GLNVGcontext* gl, struct NVGpaint* paint, struct NVGscissor* scissor, float width, float fringe)
+{
+ struct NVGcolor innerCol;
+ struct NVGcolor outerCol;
+ struct GLNVGtexture* tex = NULL;
+ float invxform[6], paintMat[9], scissorMat[9];
+ float scissorx = 0, scissory = 0;
+ float scissorsx = 0, scissorsy = 0;
+
+ innerCol = paint->innerColor;
+ outerCol = paint->outerColor;
+
+ glnvg__xformInverse(invxform, paint->xform);
+ glnvg__xformToMat3x3(paintMat, invxform);
+
+ if (scissor->extent[0] < 0.5f || scissor->extent[1] < 0.5f) {
+ memset(scissorMat, 0, sizeof(scissorMat));
+ scissorx = 1.0f;
+ scissory = 1.0f;
+ scissorsx = 1.0f;
+ scissorsy = 1.0f;
+ } else {
+ glnvg__xformInverse(invxform, scissor->xform);
+ glnvg__xformToMat3x3(scissorMat, invxform);
+ scissorx = scissor->extent[0];
+ scissory = scissor->extent[1];
+ scissorsx = sqrtf(scissor->xform[0]*scissor->xform[0] + scissor->xform[2]*scissor->xform[2]) / fringe;
+ scissorsy = sqrtf(scissor->xform[1]*scissor->xform[1] + scissor->xform[3]*scissor->xform[3]) / fringe;
+ }
+
+ if (paint->image != 0) {
+ tex = glnvg__findTexture(gl, paint->image);
+ if (tex == NULL) return 0;
+ glUseProgram(gl->shader.prog);
+ glUniform1i(gl->shader.loc[GLNVG_LOC_TYPE], NSVG_SHADER_FILLIMG);
+ glUniform2f(gl->shader.loc[GLNVG_LOC_VIEWSIZE], gl->viewWidth, gl->viewHeight);
+ glUniformMatrix3fv(gl->shader.loc[GLNVG_LOC_SCISSORMAT], 1, GL_FALSE, scissorMat);
+ glUniform2f(gl->shader.loc[GLNVG_LOC_SCISSOREXT], scissorx, scissory);
+ glUniform2f(gl->shader.loc[GLNVG_LOC_SCISSORSCALE], scissorsx, scissorsy);
+ glUniformMatrix3fv(gl->shader.loc[GLNVG_LOC_PAINTMAT], 1, GL_FALSE, paintMat);
+ glUniform2f(gl->shader.loc[GLNVG_LOC_EXTENT], paint->extent[0], paint->extent[1]);
+ glUniform1f(gl->shader.loc[GLNVG_LOC_STROKEMULT], (width*0.5f + fringe*0.5f)/fringe);
+ glUniform1i(gl->shader.loc[GLNVG_LOC_TEX], 0);
+ glUniform1i(gl->shader.loc[GLNVG_LOC_TEXTYPE], tex->type == NVG_TEXTURE_RGBA ? 0 : 1);
+ glnvg__checkError("tex paint loc");
+ glBindTexture(GL_TEXTURE_2D, tex->tex);
+ glnvg__checkError("tex paint tex");
+ } else {
+ glUseProgram(gl->shader.prog);
+ glUniform1i(gl->shader.loc[GLNVG_LOC_TYPE], NSVG_SHADER_FILLGRAD);
+ glUniform2f(gl->shader.loc[GLNVG_LOC_VIEWSIZE], gl->viewWidth, gl->viewHeight);
+ glUniformMatrix3fv(gl->shader.loc[GLNVG_LOC_SCISSORMAT], 1, GL_FALSE, scissorMat);
+ glUniform2f(gl->shader.loc[GLNVG_LOC_SCISSOREXT], scissorx, scissory);
+ glUniform2f(gl->shader.loc[GLNVG_LOC_SCISSORSCALE], scissorsx, scissorsy);
+ glUniformMatrix3fv(gl->shader.loc[GLNVG_LOC_PAINTMAT], 1, GL_FALSE, paintMat);
+ glUniform2f(gl->shader.loc[GLNVG_LOC_EXTENT], paint->extent[0], paint->extent[1]);
+ glUniform1f(gl->shader.loc[GLNVG_LOC_RADIUS], paint->radius);
+ glUniform1f(gl->shader.loc[GLNVG_LOC_FEATHER], paint->feather);
+ glUniform4fv(gl->shader.loc[GLNVG_LOC_INNERCOL], 1, innerCol.rgba);
+ glUniform4fv(gl->shader.loc[GLNVG_LOC_OUTERCOL], 1, outerCol.rgba);
+ glUniform1f(gl->shader.loc[GLNVG_LOC_STROKEMULT], (width*0.5f + fringe*0.5f)/fringe);
+ glnvg__checkError("grad paint loc");
+ }
+ return 1;
+}
+
+static void glnvg__renderViewport(void* uptr, int width, int height, int alphaBlend)
+{
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+ gl->viewWidth = (float)width;
+ gl->viewHeight = (float)height;
+
+ glEnable(GL_BLEND);
+ glEnable(GL_CULL_FACE);
+ glDisable(GL_DEPTH_TEST);
+
+ if (alphaBlend == NVG_PREMULTIPLIED_ALPHA)
+ glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ else
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+}
+
+
+static void glnvg__renderFlush(void* uptr, int alphaBlend)
+{
+// struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+ NVG_NOTUSED(uptr);
+ NVG_NOTUSED(alphaBlend);
+}
+
+static int glnvg__maxVertCount(const struct NVGpath* paths, int npaths)
+{
+ int i, count = 0;
+ for (i = 0; i < npaths; i++) {
+ count += paths[i].nfill;
+ count += paths[i].nstroke;
+ }
+ return count;
+}
+
+static void glnvg__uploadPaths(const struct NVGpath* paths, int npaths)
+{
+ const struct NVGpath* path;
+ int i, n = 0;
+ for (i = 0; i < npaths; i++) {
+ path = &paths[i];
+ if (path->nfill > 0) {
+ glBufferSubData(GL_ARRAY_BUFFER, n*sizeof(struct NVGvertex), path->nfill * sizeof(struct NVGvertex), &path->fill[0].x);
+ n += path->nfill;
+ }
+ if (path->nstroke > 0) {
+ glBufferSubData(GL_ARRAY_BUFFER, n*sizeof(struct NVGvertex), path->nstroke * sizeof(struct NVGvertex), &path->stroke[0].x);
+ n += path->nstroke;
+ }
+ }
+}
+
+static void glnvg__renderFill(void* uptr, struct NVGpaint* paint, struct NVGscissor* scissor, float fringe,
+ const float* bounds, const struct NVGpath* paths, int npaths)
+{
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+ const struct NVGpath* path;
+ int i, n, offset, maxCount;
+
+ if (gl->shader.prog == 0)
+ return;
+
+ maxCount = glnvg__maxVertCount(paths, npaths);
+ glBindBuffer(GL_ARRAY_BUFFER, gl->vertBuf);
+ glBufferData(GL_ARRAY_BUFFER, maxCount * sizeof(struct NVGvertex), NULL, GL_STREAM_DRAW);
+ glnvg__uploadPaths(paths, npaths);
+
+ if (npaths == 1 && paths[0].convex) {
+
+ glEnable(GL_CULL_FACE);
+
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(1);
+ glnvg__setupPaint(gl, paint, scissor, fringe, fringe);
+
+ glDisable(GL_CULL_FACE);
+ n = 0;
+ for (i = 0; i < npaths; i++) {
+ path = &paths[i];
+ offset = n * sizeof(struct NVGvertex);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(size_t)offset);
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(offset + 2*sizeof(float)));
+ glDrawArrays(GL_TRIANGLE_FAN, 0, path->nfill);
+ n += path->nfill + path->nstroke;
+ }
+
+ glEnable(GL_CULL_FACE);
+
+ if (gl->edgeAntiAlias) {
+ // Draw fringes
+ n = 0;
+ for (i = 0; i < npaths; i++) {
+ path = &paths[i];
+ offset = (n + path->nfill) * sizeof(struct NVGvertex);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(size_t)offset);
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(offset + 2*sizeof(float)));
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, path->nstroke);
+ n += path->nfill + path->nstroke;
+ }
+ }
+
+ glUseProgram(0);
+ glDisableVertexAttribArray(0);
+ glDisableVertexAttribArray(1);
+
+ } else {
+ float quad[6*2] = {
+ bounds[0], bounds[3], bounds[2], bounds[3], bounds[2], bounds[1],
+ bounds[0], bounds[3], bounds[2], bounds[1], bounds[0], bounds[1],
+ };
+
+ glEnable(GL_CULL_FACE);
+
+ glBindBuffer(GL_ARRAY_BUFFER, gl->vertBuf);
+
+ // Draw shapes
+ glDisable(GL_BLEND);
+ glEnable(GL_STENCIL_TEST);
+ glStencilMask(0xff);
+ glStencilFunc(GL_ALWAYS, 0, ~0U);
+ glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+
+ glUseProgram(gl->shader.prog);
+ glUniform1i(gl->shader.loc[GLNVG_LOC_TYPE], NSVG_SHADER_SIMPLE);
+ glUniform2f(gl->shader.loc[GLNVG_LOC_VIEWSIZE], gl->viewWidth, gl->viewHeight);
+ glnvg__checkError("fill solid loc");
+
+ glEnableVertexAttribArray(0);
+
+ glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
+ glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
+ glDisable(GL_CULL_FACE);
+ n = 0;
+ for (i = 0; i < npaths; i++) {
+ path = &paths[i];
+ offset = n * sizeof(struct NVGvertex);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(size_t)offset);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, path->nfill);
+ n += path->nfill + path->nstroke;
+ }
+
+ glEnable(GL_CULL_FACE);
+
+ // Draw aliased off-pixels
+ glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ glEnable(GL_BLEND);
+
+ glEnableVertexAttribArray(1);
+ glnvg__setupPaint(gl, paint, scissor, fringe, fringe);
+
+ if (gl->edgeAntiAlias) {
+ glStencilFunc(GL_EQUAL, 0x00, 0xff);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+
+ // Draw fringes
+ n = 0;
+ for (i = 0; i < npaths; i++) {
+ path = &paths[i];
+ offset = (n + path->nfill) * sizeof(struct NVGvertex);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(size_t)offset);
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(offset + 2*sizeof(float)));
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, path->nstroke);
+ n += path->nfill + path->nstroke;
+ }
+ }
+
+ // Draw fill
+ glStencilFunc(GL_NOTEQUAL, 0x0, 0xff);
+ glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
+
+ glDisableVertexAttribArray(1);
+
+ glBufferSubData(GL_ARRAY_BUFFER, 0, 6 * 2*sizeof(float), quad);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), (const GLvoid*)0);
+ glVertexAttrib2f(1, 0.5f, 1.0f);
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+
+ glUseProgram(0);
+
+ glDisableVertexAttribArray(0);
+
+ glDisable(GL_STENCIL_TEST);
+ }
+}
+
+static void glnvg__renderStroke(void* uptr, struct NVGpaint* paint, struct NVGscissor* scissor, float fringe,
+ float width, const struct NVGpath* paths, int npaths)
+{
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+ const struct NVGpath* path;
+ int i, n, offset, maxCount;
+
+ if (gl->shader.prog == 0)
+ return;
+
+ glnvg__setupPaint(gl, paint, scissor, width, fringe);
+
+ glEnable(GL_CULL_FACE);
+
+ maxCount = glnvg__maxVertCount(paths, npaths);
+ glBindBuffer(GL_ARRAY_BUFFER, gl->vertBuf);
+ glBufferData(GL_ARRAY_BUFFER, maxCount * sizeof(struct NVGvertex), NULL, GL_STREAM_DRAW);
+ glnvg__uploadPaths(paths, npaths);
+
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(1);
+
+ // Draw Strokes
+ n = 0;
+ for (i = 0; i < npaths; i++) {
+ path = &paths[i];
+ offset = (n + path->nfill) * sizeof(struct NVGvertex);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(size_t)offset);
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(offset + 2*sizeof(float)));
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, path->nstroke);
+ n += path->nfill + path->nstroke;
+ }
+
+ glDisableVertexAttribArray(0);
+ glDisableVertexAttribArray(1);
+
+ glUseProgram(0);
+}
+
+static void glnvg__renderTriangles(void* uptr, struct NVGpaint* paint, struct NVGscissor* scissor,
+ const struct NVGvertex* verts, int nverts)
+{
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+ struct GLNVGtexture* tex = glnvg__findTexture(gl, paint->image);
+ struct NVGcolor color;
+ NVG_NOTUSED(scissor);
+
+ if (gl->shader.prog == 0)
+ return;
+
+ if (tex != NULL) {
+ glBindTexture(GL_TEXTURE_2D, tex->tex);
+ }
+
+ glUseProgram(gl->shader.prog);
+ glUniform1i(gl->shader.loc[GLNVG_LOC_TYPE], NSVG_SHADER_IMG);
+ glUniform2f(gl->shader.loc[GLNVG_LOC_VIEWSIZE], gl->viewWidth, gl->viewHeight);
+ glUniform1i(gl->shader.loc[GLNVG_LOC_TEX], 0);
+ glUniform1i(gl->shader.loc[GLNVG_LOC_TEXTYPE], (tex != NULL && tex->type == NVG_TEXTURE_RGBA) ? 0 : 1);
+ glnvg__checkError("tris solid img loc");
+
+ glBindBuffer(GL_ARRAY_BUFFER, gl->vertBuf);
+ glBufferData(GL_ARRAY_BUFFER, nverts * sizeof(struct NVGvertex), verts, GL_STREAM_DRAW);
+
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)0);
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(2 * sizeof(float)));
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(1);
+
+ color = paint->innerColor;
+ glVertexAttrib4fv(2, color.rgba);
+
+ glDrawArrays(GL_TRIANGLES, 0, nverts);
+
+ glDisableVertexAttribArray(0);
+ glDisableVertexAttribArray(1);
+}
+
+static void glnvg__renderDelete(void* uptr)
+{
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+ int i;
+ if (gl == NULL) return;
+
+ glnvg__deleteShader(&gl->shader);
+
+ for (i = 0; i < gl->ntextures; i++) {
+ if (gl->textures[i].tex != 0)
+ glDeleteTextures(1, &gl->textures[i].tex);
+ }
+ free(gl->textures);
+
+ free(gl);
+}
+
+
+#ifdef NANOVG_GLES2
+struct NVGcontext* nvgCreateGLES2(int atlasw, int atlash, int edgeaa)
+#else
+struct NVGcontext* nvgCreateGL2(int atlasw, int atlash, int edgeaa)
+#endif
+{
+ struct NVGparams params;
+ struct NVGcontext* ctx = NULL;
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)malloc(sizeof(struct GLNVGcontext));
+ if (gl == NULL) goto error;
+ memset(gl, 0, sizeof(struct GLNVGcontext));
+
+ memset(&params, 0, sizeof(params));
+ params.renderCreate = glnvg__renderCreate;
+ params.renderCreateTexture = glnvg__renderCreateTexture;
+ params.renderDeleteTexture = glnvg__renderDeleteTexture;
+ params.renderUpdateTexture = glnvg__renderUpdateTexture;
+ params.renderGetTextureSize = glnvg__renderGetTextureSize;
+ params.renderViewport = glnvg__renderViewport;
+ params.renderFlush = glnvg__renderFlush;
+ params.renderFill = glnvg__renderFill;
+ params.renderStroke = glnvg__renderStroke;
+ params.renderTriangles = glnvg__renderTriangles;
+ params.renderDelete = glnvg__renderDelete;
+ params.userPtr = gl;
+ params.atlasWidth = atlasw;
+ params.atlasHeight = atlash;
+ params.edgeAntiAlias = edgeaa;
+
+ gl->edgeAntiAlias = edgeaa;
+
+ ctx = nvgCreateInternal(&params);
+ if (ctx == NULL) goto error;
+
+ return ctx;
+
+error:
+ // 'gl' is freed by nvgDeleteInternal.
+ if (ctx != NULL) nvgDeleteInternal(ctx);
+ return NULL;
+}
+
+#ifdef NANOVG_GLES2
+void nvgDeleteGLES2(struct NVGcontext* ctx)
+#else
+void nvgDeleteGL2(struct NVGcontext* ctx)
+#endif
+{
+ nvgDeleteInternal(ctx);
+}
+
+#endif
diff --git a/subprojects/d2tk/nanovg/obsolete/nanovg_gl3.h b/subprojects/d2tk/nanovg/obsolete/nanovg_gl3.h
new file mode 100644
index 0000000..971fb6b
--- /dev/null
+++ b/subprojects/d2tk/nanovg/obsolete/nanovg_gl3.h
@@ -0,0 +1,957 @@
+//
+// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+//
+#ifndef NANOVG_GL3_H
+#define NANOVG_GL3_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define NVG_ANTIALIAS 1
+
+#ifdef NANOVG_GLES3_IMPLEMENTATION
+# ifndef NANOVG_GLES3
+# define NANOVG_GLES3
+# endif
+# ifndef NANOVG_GL3_IMPLEMENTATION
+# define NANOVG_GL3_IMPLEMENTATION
+# endif
+#endif
+
+#ifdef NANOVG_GLES3
+
+struct NVGcontext* nvgCreateGLES3(int atlasw, int atlash, int edgeaa);
+void nvgDeleteGLES3(struct NVGcontext* ctx);
+
+#else
+
+struct NVGcontext* nvgCreateGL3(int atlasw, int atlash, int edgeaa);
+void nvgDeleteGL3(struct NVGcontext* ctx);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+#ifdef NANOVG_GL3_IMPLEMENTATION
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include "nanovg.h"
+
+enum GLNVGuniformLoc {
+ GLNVG_LOC_VIEWSIZE,
+ GLNVG_LOC_SCISSORMAT,
+ GLNVG_LOC_SCISSOREXT,
+ GLNVG_LOC_SCISSORSCALE,
+ GLNVG_LOC_PAINTMAT,
+ GLNVG_LOC_EXTENT,
+ GLNVG_LOC_RADIUS,
+ GLNVG_LOC_FEATHER,
+ GLNVG_LOC_INNERCOL,
+ GLNVG_LOC_OUTERCOL,
+ GLNVG_LOC_STROKEMULT,
+ GLNVG_LOC_TEX,
+ GLNVG_LOC_TEXTYPE,
+ GLNVG_LOC_TYPE,
+ GLNVG_MAX_LOCS
+};
+
+enum GLNVGshaderType {
+ NSVG_SHADER_FILLGRAD,
+ NSVG_SHADER_FILLIMG,
+ NSVG_SHADER_SIMPLE,
+ NSVG_SHADER_IMG
+};
+
+struct GLNVGshader {
+ GLuint prog;
+ GLuint frag;
+ GLuint vert;
+ GLint loc[GLNVG_MAX_LOCS];
+};
+
+struct GLNVGtexture {
+ int id;
+ GLuint tex;
+ int width, height;
+ int type;
+};
+
+struct GLNVGcontext {
+ struct GLNVGshader shader;
+ struct GLNVGtexture* textures;
+ float viewWidth, viewHeight;
+ int ntextures;
+ int ctextures;
+ int textureId;
+ GLuint vertArr;
+ GLuint vertBuf;
+ int edgeAntiAlias;
+};
+
+static struct GLNVGtexture* glnvg__allocTexture(struct GLNVGcontext* gl)
+{
+ struct GLNVGtexture* tex = NULL;
+ int i;
+
+ for (i = 0; i < gl->ntextures; i++) {
+ if (gl->textures[i].id == 0) {
+ tex = &gl->textures[i];
+ break;
+ }
+ }
+ if (tex == NULL) {
+ if (gl->ntextures+1 > gl->ctextures) {
+ gl->ctextures = (gl->ctextures == 0) ? 2 : gl->ctextures*2;
+ gl->textures = (struct GLNVGtexture*)realloc(gl->textures, sizeof(struct GLNVGtexture)*gl->ctextures);
+ if (gl->textures == NULL) return NULL;
+ }
+ tex = &gl->textures[gl->ntextures++];
+ }
+
+ memset(tex, 0, sizeof(*tex));
+ tex->id = ++gl->textureId;
+
+ return tex;
+}
+
+static struct GLNVGtexture* glnvg__findTexture(struct GLNVGcontext* gl, int id)
+{
+ int i;
+ for (i = 0; i < gl->ntextures; i++)
+ if (gl->textures[i].id == id)
+ return &gl->textures[i];
+ return NULL;
+}
+
+static int glnvg__deleteTexture(struct GLNVGcontext* gl, int id)
+{
+ int i;
+ for (i = 0; i < gl->ntextures; i++) {
+ if (gl->textures[i].id == id) {
+ if (gl->textures[i].tex != 0)
+ glDeleteTextures(1, &gl->textures[i].tex);
+ memset(&gl->textures[i], 0, sizeof(gl->textures[i]));
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void glnvg__dumpShaderError(GLuint shader, const char* name, const char* type)
+{
+ char str[512+1];
+ int len = 0;
+ glGetShaderInfoLog(shader, 512, &len, str);
+ if (len > 512) len = 512;
+ str[len] = '\0';
+ printf("Shader %s/%s error:\n%s\n", name, type, str);
+}
+
+static void glnvg__dumpProgramError(GLuint prog, const char* name)
+{
+ char str[512+1];
+ int len = 0;
+ glGetProgramInfoLog(prog, 512, &len, str);
+ if (len > 512) len = 512;
+ str[len] = '\0';
+ printf("Program %s error:\n%s\n", name, str);
+}
+
+static int glnvg__checkError(const char* str)
+{
+ GLenum err = glGetError();
+ if (err != GL_NO_ERROR) {
+ printf("Error %08x after %s\n", err, str);
+ return 1;
+ }
+ return 0;
+}
+
+static int glnvg__createShader(struct GLNVGshader* shader, const char* name, const char* vshader, const char* fshader)
+{
+ GLint status;
+ GLuint prog, vert, frag;
+
+ memset(shader, 0, sizeof(*shader));
+
+ prog = glCreateProgram();
+ vert = glCreateShader(GL_VERTEX_SHADER);
+ frag = glCreateShader(GL_FRAGMENT_SHADER);
+ glShaderSource(vert, 1, &vshader, 0);
+ glShaderSource(frag, 1, &fshader, 0);
+
+ glCompileShader(vert);
+ glGetShaderiv(vert, GL_COMPILE_STATUS, &status);
+ if (status != GL_TRUE) {
+ glnvg__dumpShaderError(vert, name, "vert");
+ return 0;
+ }
+
+ glCompileShader(frag);
+ glGetShaderiv(frag, GL_COMPILE_STATUS, &status);
+ if (status != GL_TRUE) {
+ glnvg__dumpShaderError(frag, name, "frag");
+ return 0;
+ }
+
+ glAttachShader(prog, vert);
+ glAttachShader(prog, frag);
+
+ glBindAttribLocation(prog, 0, "vertex");
+ glBindAttribLocation(prog, 1, "tcoord");
+ glBindAttribLocation(prog, 2, "color");
+
+ glLinkProgram(prog);
+ glGetProgramiv(prog, GL_LINK_STATUS, &status);
+ if (status != GL_TRUE) {
+ glnvg__dumpProgramError(prog, name);
+ return 0;
+ }
+
+ shader->prog = prog;
+ shader->vert = vert;
+ shader->frag = frag;
+
+ return 1;
+}
+
+static void glnvg__deleteShader(struct GLNVGshader* shader)
+{
+ if (shader->prog != 0)
+ glDeleteProgram(shader->prog);
+ if (shader->vert != 0)
+ glDeleteShader(shader->vert);
+ if (shader->frag != 0)
+ glDeleteShader(shader->frag);
+}
+
+static void glnvg__getUniforms(struct GLNVGshader* shader)
+{
+ shader->loc[GLNVG_LOC_VIEWSIZE] = glGetUniformLocation(shader->prog, "viewSize");
+ shader->loc[GLNVG_LOC_SCISSORMAT] = glGetUniformLocation(shader->prog, "scissorMat");
+ shader->loc[GLNVG_LOC_SCISSOREXT] = glGetUniformLocation(shader->prog, "scissorExt");
+ shader->loc[GLNVG_LOC_SCISSORSCALE] = glGetUniformLocation(shader->prog, "scissorScale");
+ shader->loc[GLNVG_LOC_PAINTMAT] = glGetUniformLocation(shader->prog, "paintMat");
+ shader->loc[GLNVG_LOC_EXTENT] = glGetUniformLocation(shader->prog, "extent");
+ shader->loc[GLNVG_LOC_RADIUS] = glGetUniformLocation(shader->prog, "radius");
+ shader->loc[GLNVG_LOC_FEATHER] = glGetUniformLocation(shader->prog, "feather");
+ shader->loc[GLNVG_LOC_INNERCOL] = glGetUniformLocation(shader->prog, "innerCol");
+ shader->loc[GLNVG_LOC_OUTERCOL] = glGetUniformLocation(shader->prog, "outerCol");
+ shader->loc[GLNVG_LOC_STROKEMULT] = glGetUniformLocation(shader->prog, "strokeMult");
+ shader->loc[GLNVG_LOC_TEX] = glGetUniformLocation(shader->prog, "tex");
+ shader->loc[GLNVG_LOC_TEXTYPE] = glGetUniformLocation(shader->prog, "texType");
+ shader->loc[GLNVG_LOC_TYPE] = glGetUniformLocation(shader->prog, "type");
+}
+
+static int glnvg__renderCreate(void* uptr)
+{
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+
+ static const char* fillVertShader =
+#ifdef NANOVG_GLES3
+ "#version 300 es\n"
+ "precision mediump float;\n"
+#else
+ "#version 150 core\n"
+#endif
+ "uniform vec2 viewSize;\n"
+ "in vec2 vertex;\n"
+ "in vec2 tcoord;\n"
+ "in vec4 color;\n"
+ "out vec2 ftcoord;\n"
+ "out vec4 fcolor;\n"
+ "out vec2 fpos;\n"
+ "void main(void) {\n"
+ " ftcoord = tcoord;\n"
+ " fcolor = color;\n"
+ " fpos = vertex;\n"
+ " gl_Position = vec4(2.0*vertex.x/viewSize.x - 1.0, 1.0 - 2.0*vertex.y/viewSize.y, 0, 1);\n"
+ "}\n";
+
+ static const char* fillFragShaderEdgeAA =
+#ifdef NANOVG_GLES3
+ "#version 300 es\n"
+ "precision mediump float;\n"
+#else
+ "#version 150 core\n"
+#endif
+ "uniform mat3 scissorMat;\n"
+ "uniform vec2 scissorExt;\n"
+ "uniform vec2 scissorScale;\n"
+ "uniform mat3 paintMat;\n"
+ "uniform vec2 extent;\n"
+ "uniform float radius;\n"
+ "uniform float feather;\n"
+ "uniform vec4 innerCol;\n"
+ "uniform vec4 outerCol;\n"
+ "uniform float strokeMult;\n"
+ "uniform sampler2D tex;\n"
+ "uniform int texType;\n"
+ "uniform int type;\n"
+ "in vec2 ftcoord;\n"
+ "in vec4 fcolor;\n"
+ "in vec2 fpos;\n"
+ "out vec4 outColor;\n"
+ "\n"
+ "float sdroundrect(vec2 pt, vec2 ext, float rad) {\n"
+ " vec2 ext2 = ext - vec2(rad,rad);\n"
+ " vec2 d = abs(pt) - ext2;\n"
+ " return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rad;\n"
+ "}\n"
+ "\n"
+ "// Scissoring\n"
+ "float scissorMask(vec2 p) {\n"
+ " vec2 sc = (abs((scissorMat * vec3(p,1.0)).xy) - scissorExt);\n"
+ " sc = vec2(0.5,0.5) - sc * scissorScale;\n"
+ " return clamp(sc.x,0.0,1.0) * clamp(sc.y,0.0,1.0);\n"
+ "}\n"
+ "\n"
+ "// Stroke - from [0..1] to clipped pyramid, where the slope is 1px.\n"
+ "float strokeMask() {\n"
+ " return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult) * ftcoord.y;\n"
+ "}\n"
+ "\n"
+ "void main(void) {\n"
+ " if (type == 0) { // Gradient\n"
+ " float scissor = scissorMask(fpos);\n"
+ " float strokeAlpha = strokeMask();\n"
+ " // Calculate gradient color using box gradient\n"
+ " vec2 pt = (paintMat * vec3(fpos,1.0)).xy;\n"
+ " float d = clamp((sdroundrect(pt, extent, radius) + feather*0.5) / feather, 0.0, 1.0);\n"
+ " vec4 color = mix(innerCol,outerCol,d);\n"
+ " // Combine alpha\n"
+ " color.w *= strokeAlpha * scissor;\n"
+ " outColor = color;\n"
+ " } else if (type == 1) { // Image\n"
+ " float scissor = scissorMask(fpos);\n"
+ " float strokeAlpha = strokeMask();\n"
+ " // Calculate color fron texture\n"
+ " vec2 pt = (paintMat * vec3(fpos,1.0)).xy / extent;\n"
+ " vec4 color = texture(tex, pt);\n"
+ " color = texType == 0 ? color : vec4(1,1,1,color.x);\n"
+ " // Combine alpha\n"
+ " color.w *= strokeAlpha * scissor;\n"
+ " outColor = color;\n"
+ " } else if (type == 2) { // Stencil fill\n"
+ " outColor = vec4(1,1,1,1);\n"
+ " } else if (type == 3) { // Textured tris\n"
+ " vec4 color = texture(tex, ftcoord);\n"
+ " color = texType == 0 ? color : vec4(1,1,1,color.x);\n"
+ " outColor = color * fcolor;\n"
+ " }\n"
+ "}\n";
+
+ static const char* fillFragShader =
+#ifdef NANOVG_GLES3
+ "#version 300 es\n"
+ "precision mediump float;\n"
+#else
+ "#version 150 core\n"
+#endif
+ "uniform mat3 scissorMat;\n"
+ "uniform vec2 scissorExt;\n"
+ "uniform vec2 scissorScale;\n"
+ "uniform mat3 paintMat;\n"
+ "uniform vec2 extent;\n"
+ "uniform float radius;\n"
+ "uniform float feather;\n"
+ "uniform vec4 innerCol;\n"
+ "uniform vec4 outerCol;\n"
+ "uniform float strokeMult;\n"
+ "uniform sampler2D tex;\n"
+ "uniform int texType;\n"
+ "uniform int type;\n"
+ "in vec2 ftcoord;\n"
+ "in vec4 fcolor;\n"
+ "in vec2 fpos;\n"
+ "out vec4 outColor;\n"
+ "\n"
+ "float sdroundrect(vec2 pt, vec2 ext, float rad) {\n"
+ " vec2 ext2 = ext - vec2(rad,rad);\n"
+ " vec2 d = abs(pt) - ext2;\n"
+ " return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rad;\n"
+ "}\n"
+ "\n"
+ "// Scissoring\n"
+ "float scissorMask(vec2 p) {\n"
+ " vec2 sc = (abs((scissorMat * vec3(p,1.0)).xy) - scissorExt);\n"
+ " sc = vec2(0.5,0.5) - sc * scissorScale;\n"
+ " return clamp(sc.x,0.0,1.0) * clamp(sc.y,0.0,1.0);\n"
+ "}\n"
+ "\n"
+ "void main(void) {\n"
+ " if (type == 0) { // Gradient\n"
+ " float scissor = scissorMask(fpos);\n"
+ " // Calculate gradient color using box gradient\n"
+ " vec2 pt = (paintMat * vec3(fpos,1.0)).xy;\n"
+ " float d = clamp((sdroundrect(pt, extent, radius) + feather*0.5) / feather, 0.0, 1.0);\n"
+ " vec4 color = mix(innerCol,outerCol,d);\n"
+ " // Combine alpha\n"
+ " color.w *= scissor;\n"
+ " outColor = color;\n"
+ " } else if (type == 1) { // Image\n"
+ " float scissor = scissorMask(fpos);\n"
+ " // Calculate color fron texture\n"
+ " vec2 pt = (paintMat * vec3(fpos,1.0)).xy / extent;\n"
+ " vec4 color = texture(tex, pt);\n"
+ " color = texType == 0 ? color : vec4(1,1,1,color.x);\n"
+ " // Combine alpha\n"
+ " color.w *= scissor;\n"
+ " outColor = color;\n"
+ " } else if (type == 2) { // Stencil fill\n"
+ " outColor = vec4(1,1,1,1);\n"
+ " } else if (type == 3) { // Textured tris\n"
+ " vec4 color = texture(tex, ftcoord);\n"
+ " color = texType == 0 ? color : vec4(1,1,1,color.x);\n"
+ " outColor = color * fcolor;\n"
+ " }\n"
+ "}\n";
+
+ glnvg__checkError("init");
+
+ if (gl->edgeAntiAlias) {
+ if (glnvg__createShader(&gl->shader, "shader", fillVertShader, fillFragShaderEdgeAA) == 0)
+ return 0;
+ } else {
+ if (glnvg__createShader(&gl->shader, "shader", fillVertShader, fillFragShader) == 0)
+ return 0;
+ }
+
+ glnvg__checkError("uniform locations");
+ glnvg__getUniforms(&gl->shader);
+
+ // Create dynamic vertex array
+ glGenVertexArrays(1, &gl->vertArr);
+ glGenBuffers(1, &gl->vertBuf);
+
+ glnvg__checkError("done");
+
+ return 1;
+}
+
+static int glnvg__renderCreateTexture(void* uptr, int type, int w, int h, const unsigned char* data)
+{
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+ struct GLNVGtexture* tex = glnvg__allocTexture(gl);
+ if (tex == NULL) return 0;
+ glGenTextures(1, &tex->tex);
+ tex->width = w;
+ tex->height = h;
+ tex->type = type;
+ glBindTexture(GL_TEXTURE_2D, tex->tex);
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT,1);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->width);
+ glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
+ glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
+
+ if (type == NVG_TEXTURE_RGBA)
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ else
+#ifdef NANOVG_GLES3
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, data);
+#else
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, data);
+#endif
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ if (glnvg__checkError("create tex"))
+ return 0;
+
+ return tex->id;
+}
+
+static int glnvg__renderDeleteTexture(void* uptr, int image)
+{
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+ return glnvg__deleteTexture(gl, image);
+}
+
+static int glnvg__renderUpdateTexture(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data)
+{
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+ struct GLNVGtexture* tex = glnvg__findTexture(gl, image);
+
+ if (tex == NULL) return 0;
+ glBindTexture(GL_TEXTURE_2D, tex->tex);
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT,1);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->width);
+ glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
+ glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
+
+ if (tex->type == NVG_TEXTURE_RGBA)
+ glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ else
+ glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_RED, GL_UNSIGNED_BYTE, data);
+
+ return 1;
+}
+
+static int glnvg__renderGetTextureSize(void* uptr, int image, int* w, int* h)
+{
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+ struct GLNVGtexture* tex = glnvg__findTexture(gl, image);
+ if (tex == NULL) return 0;
+ *w = tex->width;
+ *h = tex->height;
+ return 1;
+}
+
+static void glnvg__xformIdentity(float* t)
+{
+ t[0] = 1.0f; t[1] = 0.0f;
+ t[2] = 0.0f; t[3] = 1.0f;
+ t[4] = 0.0f; t[5] = 0.0f;
+}
+
+static void glnvg__xformInverse(float* inv, float* t)
+{
+ double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1];
+ if (det > -1e-6 && det < 1e-6) {
+ glnvg__xformIdentity(t);
+ return;
+ }
+ invdet = 1.0 / det;
+ inv[0] = (float)(t[3] * invdet);
+ inv[2] = (float)(-t[2] * invdet);
+ inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet);
+ inv[1] = (float)(-t[1] * invdet);
+ inv[3] = (float)(t[0] * invdet);
+ inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet);
+}
+
+static void glnvg__xformToMat3x3(float* m3, float* t)
+{
+ m3[0] = t[0];
+ m3[1] = t[1];
+ m3[2] = 0.0f;
+ m3[3] = t[2];
+ m3[4] = t[3];
+ m3[5] = 0.0f;
+ m3[6] = t[4];
+ m3[7] = t[5];
+ m3[8] = 1.0f;
+}
+
+static int glnvg__setupPaint(struct GLNVGcontext* gl, struct NVGpaint* paint, struct NVGscissor* scissor, float width, float fringe)
+{
+ struct NVGcolor innerCol;
+ struct NVGcolor outerCol;
+ struct GLNVGtexture* tex = NULL;
+ float invxform[6], paintMat[9], scissorMat[9];
+ float scissorx = 0, scissory = 0;
+ float scissorsx = 0, scissorsy = 0;
+
+ innerCol = paint->innerColor;
+ outerCol = paint->outerColor;
+
+ glnvg__xformInverse(invxform, paint->xform);
+ glnvg__xformToMat3x3(paintMat, invxform);
+
+ if (scissor->extent[0] < 0.5f || scissor->extent[1] < 0.5f) {
+ memset(scissorMat, 0, sizeof(scissorMat));
+ scissorx = 1.0f;
+ scissory = 1.0f;
+ scissorsx = 1.0f;
+ scissorsy = 1.0f;
+ } else {
+ glnvg__xformInverse(invxform, scissor->xform);
+ glnvg__xformToMat3x3(scissorMat, invxform);
+ scissorx = scissor->extent[0];
+ scissory = scissor->extent[1];
+ scissorsx = sqrtf(scissor->xform[0]*scissor->xform[0] + scissor->xform[2]*scissor->xform[2]) / fringe;
+ scissorsy = sqrtf(scissor->xform[1]*scissor->xform[1] + scissor->xform[3]*scissor->xform[3]) / fringe;
+ }
+
+ if (paint->image != 0) {
+ tex = glnvg__findTexture(gl, paint->image);
+ if (tex == NULL) return 0;
+ glUseProgram(gl->shader.prog);
+ glUniform1i(gl->shader.loc[GLNVG_LOC_TYPE], NSVG_SHADER_FILLIMG);
+ glUniform2f(gl->shader.loc[GLNVG_LOC_VIEWSIZE], gl->viewWidth, gl->viewHeight);
+ glUniformMatrix3fv(gl->shader.loc[GLNVG_LOC_SCISSORMAT], 1, GL_FALSE, scissorMat);
+ glUniform2f(gl->shader.loc[GLNVG_LOC_SCISSOREXT], scissorx, scissory);
+ glUniform2f(gl->shader.loc[GLNVG_LOC_SCISSORSCALE], scissorsx, scissorsy);
+ glUniformMatrix3fv(gl->shader.loc[GLNVG_LOC_PAINTMAT], 1, GL_FALSE, paintMat);
+ glUniform2f(gl->shader.loc[GLNVG_LOC_EXTENT], paint->extent[0], paint->extent[1]);
+ glUniform1f(gl->shader.loc[GLNVG_LOC_STROKEMULT], (width*0.5f + fringe*0.5f)/fringe);
+ glUniform1i(gl->shader.loc[GLNVG_LOC_TEX], 0);
+ glUniform1i(gl->shader.loc[GLNVG_LOC_TEXTYPE], tex->type == NVG_TEXTURE_RGBA ? 0 : 1);
+ glnvg__checkError("tex paint loc");
+ glBindTexture(GL_TEXTURE_2D, tex->tex);
+ glnvg__checkError("tex paint tex");
+ } else {
+ glUseProgram(gl->shader.prog);
+ glUniform1i(gl->shader.loc[GLNVG_LOC_TYPE], NSVG_SHADER_FILLGRAD);
+ glUniform2f(gl->shader.loc[GLNVG_LOC_VIEWSIZE], gl->viewWidth, gl->viewHeight);
+ glUniformMatrix3fv(gl->shader.loc[GLNVG_LOC_SCISSORMAT], 1, GL_FALSE, scissorMat);
+ glUniform2f(gl->shader.loc[GLNVG_LOC_SCISSOREXT], scissorx, scissory);
+ glUniform2f(gl->shader.loc[GLNVG_LOC_SCISSORSCALE], scissorsx, scissorsy);
+ glUniformMatrix3fv(gl->shader.loc[GLNVG_LOC_PAINTMAT], 1, GL_FALSE, paintMat);
+ glUniform2f(gl->shader.loc[GLNVG_LOC_EXTENT], paint->extent[0], paint->extent[1]);
+ glUniform1f(gl->shader.loc[GLNVG_LOC_RADIUS], paint->radius);
+ glUniform1f(gl->shader.loc[GLNVG_LOC_FEATHER], paint->feather);
+ glUniform4fv(gl->shader.loc[GLNVG_LOC_INNERCOL], 1, innerCol.rgba);
+ glUniform4fv(gl->shader.loc[GLNVG_LOC_OUTERCOL], 1, outerCol.rgba);
+ glUniform1f(gl->shader.loc[GLNVG_LOC_STROKEMULT], (width*0.5f + fringe*0.5f)/fringe);
+ glnvg__checkError("grad paint loc");
+ }
+ return 1;
+}
+
+static void glnvg__renderViewport(void* uptr, int width, int height, int alphaBlend)
+{
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+ gl->viewWidth = (float)width;
+ gl->viewHeight = (float)height;
+
+ if (alphaBlend == NVG_PREMULTIPLIED_ALPHA)
+ glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ else
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+}
+
+static void glnvg__renderFlush(void* uptr, int alphaBlend)
+{
+// struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+ NVG_NOTUSED(uptr);
+ NVG_NOTUSED(alphaBlend);
+}
+
+static int glnvg__maxVertCount(const struct NVGpath* paths, int npaths)
+{
+ int i, count = 0;
+ for (i = 0; i < npaths; i++) {
+ count += paths[i].nfill;
+ count += paths[i].nstroke;
+ }
+ return count;
+}
+
+static void glnvg__uploadPaths(const struct NVGpath* paths, int npaths)
+{
+ const struct NVGpath* path;
+ int i, n = 0;
+ for (i = 0; i < npaths; i++) {
+ path = &paths[i];
+ if (path->nfill > 0) {
+ glBufferSubData(GL_ARRAY_BUFFER, n*sizeof(struct NVGvertex), path->nfill * sizeof(struct NVGvertex), &path->fill[0].x);
+ n += path->nfill;
+ }
+ if (path->nstroke > 0) {
+ glBufferSubData(GL_ARRAY_BUFFER, n*sizeof(struct NVGvertex), path->nstroke * sizeof(struct NVGvertex), &path->stroke[0].x);
+ n += path->nstroke;
+ }
+ }
+}
+
+static void glnvg__renderFill(void* uptr, struct NVGpaint* paint, struct NVGscissor* scissor, float fringe,
+ const float* bounds, const struct NVGpath* paths, int npaths)
+{
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+ const struct NVGpath* path;
+ int i, n, offset, maxCount;
+
+ if (gl->shader.prog == 0)
+ return;
+
+ maxCount = glnvg__maxVertCount(paths, npaths);
+ glBindVertexArray(gl->vertArr);
+ glBindBuffer(GL_ARRAY_BUFFER, gl->vertBuf);
+ glBufferData(GL_ARRAY_BUFFER, maxCount * sizeof(struct NVGvertex), NULL, GL_STREAM_DRAW);
+ glnvg__uploadPaths(paths, npaths);
+
+ if (npaths == 1 && paths[0].convex) {
+
+ glEnable(GL_CULL_FACE);
+
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(1);
+ glnvg__setupPaint(gl, paint, scissor, fringe, fringe);
+
+ glDisable(GL_CULL_FACE);
+ n = 0;
+ for (i = 0; i < npaths; i++) {
+ path = &paths[i];
+ offset = n * sizeof(struct NVGvertex);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(size_t)offset);
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(offset + 2*sizeof(float)));
+ glDrawArrays(GL_TRIANGLE_FAN, 0, path->nfill);
+ n += path->nfill + path->nstroke;
+ }
+
+ glEnable(GL_CULL_FACE);
+
+ if (gl->edgeAntiAlias) {
+ // Draw fringes
+ n = 0;
+ for (i = 0; i < npaths; i++) {
+ path = &paths[i];
+ offset = (n + path->nfill) * sizeof(struct NVGvertex);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(size_t)offset);
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(offset + 2*sizeof(float)));
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, path->nstroke);
+ n += path->nfill + path->nstroke;
+ }
+ }
+
+ glUseProgram(0);
+ glDisableVertexAttribArray(0);
+ glDisableVertexAttribArray(1);
+
+ } else {
+
+ glEnable(GL_CULL_FACE);
+
+ glBindVertexArray(gl->vertArr);
+ glBindBuffer(GL_ARRAY_BUFFER, gl->vertBuf);
+
+ // Draw shapes
+ glDisable(GL_BLEND);
+ glEnable(GL_STENCIL_TEST);
+ glStencilMask(0xff);
+ glStencilFunc(GL_ALWAYS, 0, ~0);
+ glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+
+ glUseProgram(gl->shader.prog);
+ glUniform1i(gl->shader.loc[GLNVG_LOC_TYPE], NSVG_SHADER_SIMPLE);
+ glUniform2f(gl->shader.loc[GLNVG_LOC_VIEWSIZE], gl->viewWidth, gl->viewHeight);
+ glnvg__checkError("fill solid loc");
+
+ glEnableVertexAttribArray(0);
+
+ glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
+ glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
+ glDisable(GL_CULL_FACE);
+ n = 0;
+ for (i = 0; i < npaths; i++) {
+ path = &paths[i];
+ offset = n * sizeof(struct NVGvertex);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(size_t)offset);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, path->nfill);
+ n += path->nfill + path->nstroke;
+ }
+
+ glEnable(GL_CULL_FACE);
+
+ // Draw aliased off-pixels
+ glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ glEnable(GL_BLEND);
+
+ glEnableVertexAttribArray(1);
+ glnvg__setupPaint(gl, paint, scissor, fringe, fringe);
+
+ if (gl->edgeAntiAlias) {
+ glStencilFunc(GL_EQUAL, 0x00, 0xff);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+
+ // Draw fringes
+ n = 0;
+ for (i = 0; i < npaths; i++) {
+ path = &paths[i];
+ offset = (n + path->nfill) * sizeof(struct NVGvertex);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(size_t)offset);
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(offset + 2*sizeof(float)));
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, path->nstroke);
+ n += path->nfill + path->nstroke;
+ }
+ }
+
+ // Draw fill
+ glStencilFunc(GL_NOTEQUAL, 0x0, 0xff);
+ glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
+
+ glDisableVertexAttribArray(1);
+
+ float quad[6*2] = {
+ bounds[0], bounds[3], bounds[2], bounds[3], bounds[2], bounds[1],
+ bounds[0], bounds[3], bounds[2], bounds[1], bounds[0], bounds[1],
+ };
+ glBufferSubData(GL_ARRAY_BUFFER, 0, 6 * 2*sizeof(float), quad);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), (const GLvoid*)0);
+ glVertexAttrib2f(1, 0.5f, 1.0f);
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+
+ glUseProgram(0);
+
+ glDisableVertexAttribArray(0);
+
+ glDisable(GL_STENCIL_TEST);
+ }
+}
+
+static void glnvg__renderStroke(void* uptr, struct NVGpaint* paint, struct NVGscissor* scissor, float fringe,
+ float width, const struct NVGpath* paths, int npaths)
+{
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+ const struct NVGpath* path;
+ int i, n, offset, maxCount;
+
+ if (gl->shader.prog == 0)
+ return;
+
+ glnvg__setupPaint(gl, paint, scissor, width, fringe);
+
+ glEnable(GL_CULL_FACE);
+
+ maxCount = glnvg__maxVertCount(paths, npaths);
+ glBindVertexArray(gl->vertArr);
+ glBindBuffer(GL_ARRAY_BUFFER, gl->vertBuf);
+ glBufferData(GL_ARRAY_BUFFER, maxCount * sizeof(struct NVGvertex), NULL, GL_STREAM_DRAW);
+ glnvg__uploadPaths(paths, npaths);
+
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(1);
+
+ // Draw Strokes
+ n = 0;
+ for (i = 0; i < npaths; i++) {
+ path = &paths[i];
+ offset = (n + path->nfill) * sizeof(struct NVGvertex);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(size_t)offset);
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(offset + 2*sizeof(float)));
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, path->nstroke);
+ n += path->nfill + path->nstroke;
+ }
+
+ glDisableVertexAttribArray(0);
+ glDisableVertexAttribArray(1);
+
+ glUseProgram(0);
+}
+
+static void glnvg__renderTriangles(void* uptr, struct NVGpaint* paint, struct NVGscissor* scissor,
+ const struct NVGvertex* verts, int nverts)
+{
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+ struct GLNVGtexture* tex = glnvg__findTexture(gl, paint->image);
+ struct NVGcolor color;
+ NVG_NOTUSED(scissor);
+
+ if (gl->shader.prog == 0)
+ return;
+
+ if (tex != NULL) {
+ glBindTexture(GL_TEXTURE_2D, tex->tex);
+ }
+
+ glUseProgram(gl->shader.prog);
+ glUniform1i(gl->shader.loc[GLNVG_LOC_TYPE], NSVG_SHADER_IMG);
+ glUniform2f(gl->shader.loc[GLNVG_LOC_VIEWSIZE], gl->viewWidth, gl->viewHeight);
+ glUniform1i(gl->shader.loc[GLNVG_LOC_TEX], 0);
+ glUniform1i(gl->shader.loc[GLNVG_LOC_TEXTYPE], tex->type == NVG_TEXTURE_RGBA ? 0 : 1);
+ glnvg__checkError("tris solid img loc");
+
+ glBindVertexArray(gl->vertArr);
+ glBindBuffer(GL_ARRAY_BUFFER, gl->vertBuf);
+ glBufferData(GL_ARRAY_BUFFER, nverts * sizeof(struct NVGvertex), verts, GL_STREAM_DRAW);
+
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)0);
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(struct NVGvertex), (const GLvoid*)(2 * sizeof(float)));
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(1);
+
+ color = paint->innerColor;
+ glVertexAttrib4fv(2, color.rgba);
+
+ glDrawArrays(GL_TRIANGLES, 0, nverts);
+
+ glDisableVertexAttribArray(0);
+ glDisableVertexAttribArray(1);
+}
+
+static void glnvg__renderDelete(void* uptr)
+{
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)uptr;
+ int i;
+ if (gl == NULL) return;
+
+ glnvg__deleteShader(&gl->shader);
+
+ for (i = 0; i < gl->ntextures; i++) {
+ if (gl->textures[i].tex != 0)
+ glDeleteTextures(1, &gl->textures[i].tex);
+ }
+ free(gl->textures);
+
+ free(gl);
+}
+
+
+#ifdef NANOVG_GLES3
+struct NVGcontext* nvgCreateGLES3(int atlasw, int atlash, int edgeaa)
+#else
+struct NVGcontext* nvgCreateGL3(int atlasw, int atlash, int edgeaa)
+#endif
+{
+ struct NVGparams params;
+ struct NVGcontext* ctx = NULL;
+ struct GLNVGcontext* gl = (struct GLNVGcontext*)malloc(sizeof(struct GLNVGcontext));
+ if (gl == NULL) goto error;
+ memset(gl, 0, sizeof(struct GLNVGcontext));
+
+ memset(&params, 0, sizeof(params));
+ params.renderCreate = glnvg__renderCreate;
+ params.renderCreateTexture = glnvg__renderCreateTexture;
+ params.renderDeleteTexture = glnvg__renderDeleteTexture;
+ params.renderUpdateTexture = glnvg__renderUpdateTexture;
+ params.renderGetTextureSize = glnvg__renderGetTextureSize;
+ params.renderViewport = glnvg__renderViewport;
+ params.renderFlush = glnvg__renderFlush;
+ params.renderFill = glnvg__renderFill;
+ params.renderStroke = glnvg__renderStroke;
+ params.renderTriangles = glnvg__renderTriangles;
+ params.renderDelete = glnvg__renderDelete;
+ params.userPtr = gl;
+ params.atlasWidth = atlasw;
+ params.atlasHeight = atlash;
+ params.edgeAntiAlias = edgeaa;
+
+ gl->edgeAntiAlias = edgeaa;
+
+ ctx = nvgCreateInternal(&params);
+ if (ctx == NULL) goto error;
+
+ return ctx;
+
+error:
+ // 'gl' is freed by nvgDeleteInternal.
+ if (ctx != NULL) nvgDeleteInternal(ctx);
+ return NULL;
+}
+
+#ifdef NANOVG_GLES3
+void nvgDeleteGLES3(struct NVGcontext* ctx)
+#else
+void nvgDeleteGL3(struct NVGcontext* ctx)
+#endif
+{
+ nvgDeleteInternal(ctx);
+}
+
+#endif
diff --git a/subprojects/d2tk/nanovg/obsolete/obsolete.md b/subprojects/d2tk/nanovg/obsolete/obsolete.md
new file mode 100644
index 0000000..1dee5d1
--- /dev/null
+++ b/subprojects/d2tk/nanovg/obsolete/obsolete.md
@@ -0,0 +1,6 @@
+The files in this folder will be removed in near future.
+
+- nanovg_gl2.h and nanovg_gl3.h
+ - These were the first GL2 and GL3 backends
+ - an optimized version of the gl3 backed was build and later GL2 support was added to it
+ - the new combined backend has superseded the individual backends \ No newline at end of file
diff --git a/subprojects/d2tk/nanovg/premake4.lua b/subprojects/d2tk/nanovg/premake4.lua
new file mode 100644
index 0000000..0f86168
--- /dev/null
+++ b/subprojects/d2tk/nanovg/premake4.lua
@@ -0,0 +1,226 @@
+
+local action = _ACTION or ""
+
+solution "nanovg"
+ location ( "build" )
+ configurations { "Debug", "Release" }
+ platforms {"native", "x64", "x32"}
+
+ project "nanovg"
+ language "C"
+ kind "StaticLib"
+ includedirs { "src" }
+ files { "src/*.c" }
+ targetdir("build")
+ defines { "_CRT_SECURE_NO_WARNINGS" } --,"FONS_USE_FREETYPE" } Uncomment to compile with FreeType support
+
+ configuration "Debug"
+ defines { "DEBUG" }
+ flags { "Symbols", "ExtraWarnings"}
+
+ configuration "Release"
+ defines { "NDEBUG" }
+ flags { "Optimize", "ExtraWarnings"}
+
+ project "example_gl2"
+
+ kind "ConsoleApp"
+ language "C"
+ files { "example/example_gl2.c", "example/demo.c", "example/perf.c" }
+ includedirs { "src", "example" }
+ targetdir("build")
+ links { "nanovg" }
+
+ configuration { "linux" }
+ linkoptions { "`pkg-config --libs glfw3`" }
+ links { "GL", "GLU", "m", "GLEW" }
+ defines { "NANOVG_GLEW" }
+
+ configuration { "windows" }
+ links { "glfw3", "gdi32", "winmm", "user32", "GLEW", "glu32","opengl32", "kernel32" }
+ defines { "NANOVG_GLEW", "_CRT_SECURE_NO_WARNINGS" }
+
+ configuration { "macosx" }
+ links { "glfw3" }
+ linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo", "-framework Carbon" }
+
+ configuration "Debug"
+ defines { "DEBUG" }
+ flags { "Symbols", "ExtraWarnings"}
+
+ configuration "Release"
+ defines { "NDEBUG" }
+ flags { "Optimize", "ExtraWarnings"}
+
+ project "example_gl3"
+ kind "ConsoleApp"
+ language "C"
+ files { "example/example_gl3.c", "example/demo.c", "example/perf.c" }
+ includedirs { "src", "example" }
+ targetdir("build")
+ links { "nanovg" }
+
+ configuration { "linux" }
+ linkoptions { "`pkg-config --libs glfw3`" }
+ links { "GL", "GLU", "m", "GLEW" }
+ defines { "NANOVG_GLEW" }
+
+ configuration { "windows" }
+ links { "glfw3", "gdi32", "winmm", "user32", "GLEW", "glu32","opengl32", "kernel32" }
+ defines { "NANOVG_GLEW", "_CRT_SECURE_NO_WARNINGS" }
+
+ configuration { "macosx" }
+ links { "glfw3" }
+ linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo", "-framework Carbon" }
+
+ configuration "Debug"
+ defines { "DEBUG" }
+ flags { "Symbols", "ExtraWarnings"}
+
+ configuration "Release"
+ defines { "NDEBUG" }
+ flags { "Optimize", "ExtraWarnings"}
+
+ project "example_gl2_msaa"
+ kind "ConsoleApp"
+ language "C"
+ defines { "DEMO_MSAA" }
+ files { "example/example_gl2.c", "example/demo.c", "example/perf.c" }
+ includedirs { "src", "example" }
+ targetdir("build")
+ links { "nanovg" }
+
+ configuration { "linux" }
+ linkoptions { "`pkg-config --libs glfw3`" }
+ links { "GL", "GLU", "m", "GLEW" }
+ defines { "NANOVG_GLEW" }
+
+ configuration { "windows" }
+ links { "glfw3", "gdi32", "winmm", "user32", "GLEW", "glu32","opengl32", "kernel32" }
+ defines { "NANOVG_GLEW", "_CRT_SECURE_NO_WARNINGS" }
+
+ configuration { "macosx" }
+ links { "glfw3" }
+ linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo", "-framework Carbon" }
+
+ configuration "Debug"
+ defines { "DEBUG" }
+ flags { "Symbols", "ExtraWarnings"}
+
+ configuration "Release"
+ defines { "NDEBUG" }
+ flags { "Optimize", "ExtraWarnings"}
+
+ project "example_gl3_msaa"
+ kind "ConsoleApp"
+ language "C"
+ defines { "DEMO_MSAA" }
+ files { "example/example_gl3.c", "example/demo.c", "example/perf.c" }
+ includedirs { "src", "example" }
+ targetdir("build")
+ links { "nanovg" }
+
+ configuration { "linux" }
+ linkoptions { "`pkg-config --libs glfw3`" }
+ links { "GL", "GLU", "m", "GLEW" }
+ defines { "NANOVG_GLEW" }
+
+ configuration { "windows" }
+ links { "glfw3", "gdi32", "winmm", "user32", "GLEW", "glu32","opengl32", "kernel32" }
+ defines { "NANOVG_GLEW", "_CRT_SECURE_NO_WARNINGS" }
+
+ configuration { "macosx" }
+ links { "glfw3" }
+ linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo", "-framework Carbon" }
+
+ configuration "Debug"
+ defines { "DEBUG" }
+ flags { "Symbols", "ExtraWarnings"}
+
+ configuration "Release"
+ defines { "NDEBUG" }
+ flags { "Optimize", "ExtraWarnings"}
+
+ project "example_fbo"
+ kind "ConsoleApp"
+ language "C"
+ files { "example/example_fbo.c", "example/perf.c" }
+ includedirs { "src", "example" }
+ targetdir("build")
+ links { "nanovg" }
+
+ configuration { "linux" }
+ linkoptions { "`pkg-config --libs glfw3`" }
+ links { "GL", "GLU", "m", "GLEW" }
+
+ configuration { "windows" }
+ links { "glfw3", "gdi32", "winmm", "user32", "GLEW", "glu32","opengl32", "kernel32" }
+ defines { "NANOVG_GLEW", "_CRT_SECURE_NO_WARNINGS" }
+
+ configuration { "macosx" }
+ links { "glfw3" }
+ linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo", "-framework Carbon" }
+
+ configuration "Debug"
+ defines { "DEBUG" }
+ flags { "Symbols", "ExtraWarnings"}
+
+ configuration "Release"
+ defines { "NDEBUG" }
+ flags { "Optimize", "ExtraWarnings"}
+
+ project "example_gles2"
+ kind "ConsoleApp"
+ language "C"
+ files { "example/example_gles2.c", "example/demo.c", "example/perf.c" }
+ includedirs { "src", "example" }
+ targetdir("build")
+ links { "nanovg" }
+
+ configuration { "linux" }
+ linkoptions { "`pkg-config --libs glfw3`" }
+ links { "GL", "GLU", "m", "GLEW" }
+
+ configuration { "windows" }
+ links { "glfw3", "gdi32", "winmm", "user32", "GLEW", "glu32","opengl32", "kernel32" }
+ defines { "NANOVG_GLEW", "_CRT_SECURE_NO_WARNINGS" }
+
+ configuration { "macosx" }
+ links { "glfw3" }
+ linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo", "-framework Carbon" }
+
+ configuration "Debug"
+ defines { "DEBUG" }
+ flags { "Symbols", "ExtraWarnings"}
+
+ configuration "Release"
+ defines { "NDEBUG" }
+ flags { "Optimize", "ExtraWarnings"}
+
+ project "example_gles3"
+ kind "ConsoleApp"
+ language "C"
+ files { "example/example_gles3.c", "example/demo.c", "example/perf.c" }
+ includedirs { "src", "example" }
+ targetdir("build")
+ links { "nanovg" }
+
+ configuration { "linux" }
+ linkoptions { "`pkg-config --libs glfw3`" }
+ links { "GL", "GLU", "m", "GLEW" }
+
+ configuration { "windows" }
+ links { "glfw3", "gdi32", "winmm", "user32", "GLEW", "glu32","opengl32", "kernel32" }
+ defines { "NANOVG_GLEW", "_CRT_SECURE_NO_WARNINGS" }
+
+ configuration { "macosx" }
+ links { "glfw3" }
+ linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo", "-framework Carbon" }
+
+ configuration "Debug"
+ defines { "DEBUG" }
+ flags { "Symbols", "ExtraWarnings"}
+
+ configuration "Release"
+ defines { "NDEBUG" }
+ flags { "Optimize", "ExtraWarnings"}
diff --git a/subprojects/d2tk/nanovg/src/fontstash.h b/subprojects/d2tk/nanovg/src/fontstash.h
new file mode 100644
index 0000000..9df68b3
--- /dev/null
+++ b/subprojects/d2tk/nanovg/src/fontstash.h
@@ -0,0 +1,1768 @@
+//
+// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+//
+
+#ifndef FONS_H
+#define FONS_H
+
+#define FONS_INVALID -1
+
+enum FONSflags {
+ FONS_ZERO_TOPLEFT = 1,
+ FONS_ZERO_BOTTOMLEFT = 2,
+};
+
+enum FONSalign {
+ // Horizontal align
+ FONS_ALIGN_LEFT = 1<<0, // Default
+ FONS_ALIGN_CENTER = 1<<1,
+ FONS_ALIGN_RIGHT = 1<<2,
+ // Vertical align
+ FONS_ALIGN_TOP = 1<<3,
+ FONS_ALIGN_MIDDLE = 1<<4,
+ FONS_ALIGN_BOTTOM = 1<<5,
+ FONS_ALIGN_BASELINE = 1<<6, // Default
+};
+
+enum FONSglyphBitmap {
+ FONS_GLYPH_BITMAP_OPTIONAL = 1,
+ FONS_GLYPH_BITMAP_REQUIRED = 2,
+};
+
+enum FONSerrorCode {
+ // Font atlas is full.
+ FONS_ATLAS_FULL = 1,
+ // Scratch memory used to render glyphs is full, requested size reported in 'val', you may need to bump up FONS_SCRATCH_BUF_SIZE.
+ FONS_SCRATCH_FULL = 2,
+ // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES.
+ FONS_STATES_OVERFLOW = 3,
+ // Trying to pop too many states fonsPopState().
+ FONS_STATES_UNDERFLOW = 4,
+};
+
+struct FONSparams {
+ int width, height;
+ unsigned char flags;
+ void* userPtr;
+ int (*renderCreate)(void* uptr, int width, int height);
+ int (*renderResize)(void* uptr, int width, int height);
+ void (*renderUpdate)(void* uptr, int* rect, const unsigned char* data);
+ void (*renderDraw)(void* uptr, const float* verts, const float* tcoords, const unsigned int* colors, int nverts);
+ void (*renderDelete)(void* uptr);
+};
+typedef struct FONSparams FONSparams;
+
+struct FONSquad
+{
+ float x0,y0,s0,t0;
+ float x1,y1,s1,t1;
+};
+typedef struct FONSquad FONSquad;
+
+struct FONStextIter {
+ float x, y, nextx, nexty, scale, spacing;
+ unsigned int codepoint;
+ short isize, iblur;
+ struct FONSfont* font;
+ int prevGlyphIndex;
+ const char* str;
+ const char* next;
+ const char* end;
+ unsigned int utf8state;
+ int bitmapOption;
+};
+typedef struct FONStextIter FONStextIter;
+
+typedef struct FONScontext FONScontext;
+
+// Constructor and destructor.
+FONScontext* fonsCreateInternal(FONSparams* params);
+void fonsDeleteInternal(FONScontext* s);
+
+void fonsSetErrorCallback(FONScontext* s, void (*callback)(void* uptr, int error, int val), void* uptr);
+// Returns current atlas size.
+void fonsGetAtlasSize(FONScontext* s, int* width, int* height);
+// Expands the atlas size.
+int fonsExpandAtlas(FONScontext* s, int width, int height);
+// Resets the whole stash.
+int fonsResetAtlas(FONScontext* stash, int width, int height);
+
+// Add fonts
+int fonsAddFont(FONScontext* s, const char* name, const char* path);
+int fonsAddFontMem(FONScontext* s, const char* name, unsigned char* data, int ndata, int freeData);
+int fonsGetFontByName(FONScontext* s, const char* name);
+
+// State handling
+void fonsPushState(FONScontext* s);
+void fonsPopState(FONScontext* s);
+void fonsClearState(FONScontext* s);
+
+// State setting
+void fonsSetSize(FONScontext* s, float size);
+void fonsSetColor(FONScontext* s, unsigned int color);
+void fonsSetSpacing(FONScontext* s, float spacing);
+void fonsSetBlur(FONScontext* s, float blur);
+void fonsSetAlign(FONScontext* s, int align);
+void fonsSetFont(FONScontext* s, int font);
+
+// Draw text
+float fonsDrawText(FONScontext* s, float x, float y, const char* string, const char* end);
+
+// Measure text
+float fonsTextBounds(FONScontext* s, float x, float y, const char* string, const char* end, float* bounds);
+void fonsLineBounds(FONScontext* s, float y, float* miny, float* maxy);
+void fonsVertMetrics(FONScontext* s, float* ascender, float* descender, float* lineh);
+
+// Text iterator
+int fonsTextIterInit(FONScontext* stash, FONStextIter* iter, float x, float y, const char* str, const char* end, int bitmapOption);
+int fonsTextIterNext(FONScontext* stash, FONStextIter* iter, struct FONSquad* quad);
+
+// Pull texture changes
+const unsigned char* fonsGetTextureData(FONScontext* stash, int* width, int* height);
+int fonsValidateTexture(FONScontext* s, int* dirty);
+
+// Draws the stash texture for debugging
+void fonsDrawDebug(FONScontext* s, float x, float y);
+
+#endif // FONTSTASH_H
+
+
+#ifdef FONTSTASH_IMPLEMENTATION
+
+#define FONS_NOTUSED(v) (void)sizeof(v)
+
+#ifdef FONS_USE_FREETYPE
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_ADVANCES_H
+#include <math.h>
+
+struct FONSttFontImpl {
+ FT_Face font;
+};
+typedef struct FONSttFontImpl FONSttFontImpl;
+
+static FT_Library ftLibrary;
+
+int fons__tt_init(FONScontext *context)
+{
+ FT_Error ftError;
+ FONS_NOTUSED(context);
+ ftError = FT_Init_FreeType(&ftLibrary);
+ return ftError == 0;
+}
+
+int fons__tt_done(FONScontext *context)
+{
+ FT_Error ftError;
+ FONS_NOTUSED(context);
+ ftError = FT_Done_FreeType(ftLibrary);
+ return ftError == 0;
+}
+
+int fons__tt_loadFont(FONScontext *context, FONSttFontImpl *font, unsigned char *data, int dataSize)
+{
+ FT_Error ftError;
+ FONS_NOTUSED(context);
+
+ //font->font.userdata = stash;
+ ftError = FT_New_Memory_Face(ftLibrary, (const FT_Byte*)data, dataSize, 0, &font->font);
+ return ftError == 0;
+}
+
+void fons__tt_getFontVMetrics(FONSttFontImpl *font, int *ascent, int *descent, int *lineGap)
+{
+ *ascent = font->font->ascender;
+ *descent = font->font->descender;
+ *lineGap = font->font->height - (*ascent - *descent);
+}
+
+float fons__tt_getPixelHeightScale(FONSttFontImpl *font, float size)
+{
+ return size / (font->font->ascender - font->font->descender);
+}
+
+int fons__tt_getGlyphIndex(FONSttFontImpl *font, int codepoint)
+{
+ return FT_Get_Char_Index(font->font, codepoint);
+}
+
+int fons__tt_buildGlyphBitmap(FONSttFontImpl *font, int glyph, float size, float scale,
+ int *advance, int *lsb, int *x0, int *y0, int *x1, int *y1)
+{
+ FT_Error ftError;
+ FT_GlyphSlot ftGlyph;
+ FT_Fixed advFixed;
+ FONS_NOTUSED(scale);
+
+ ftError = FT_Set_Pixel_Sizes(font->font, 0, (FT_UInt)(size * (float)font->font->units_per_EM / (float)(font->font->ascender - font->font->descender)));
+ if (ftError) return 0;
+ ftError = FT_Load_Glyph(font->font, glyph, FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT);
+ if (ftError) return 0;
+ ftError = FT_Get_Advance(font->font, glyph, FT_LOAD_NO_SCALE, &advFixed);
+ if (ftError) return 0;
+ ftGlyph = font->font->glyph;
+ *advance = (int)advFixed;
+ *lsb = (int)ftGlyph->metrics.horiBearingX;
+ *x0 = ftGlyph->bitmap_left;
+ *x1 = *x0 + ftGlyph->bitmap.width;
+ *y0 = -ftGlyph->bitmap_top;
+ *y1 = *y0 + ftGlyph->bitmap.rows;
+ return 1;
+}
+
+void fons__tt_renderGlyphBitmap(FONSttFontImpl *font, unsigned char *output, int outWidth, int outHeight, int outStride,
+ float scaleX, float scaleY, int glyph)
+{
+ FT_GlyphSlot ftGlyph = font->font->glyph;
+ int ftGlyphOffset = 0;
+ int x, y;
+ FONS_NOTUSED(outWidth);
+ FONS_NOTUSED(outHeight);
+ FONS_NOTUSED(scaleX);
+ FONS_NOTUSED(scaleY);
+ FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap
+
+ for ( y = 0; y < ftGlyph->bitmap.rows; y++ ) {
+ for ( x = 0; x < ftGlyph->bitmap.width; x++ ) {
+ output[(y * outStride) + x] = ftGlyph->bitmap.buffer[ftGlyphOffset++];
+ }
+ }
+}
+
+int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2)
+{
+ FT_Vector ftKerning;
+ FT_Get_Kerning(font->font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning);
+ return (int)((ftKerning.x + 32) >> 6); // Round up and convert to integer
+}
+
+#else
+
+#define STB_TRUETYPE_IMPLEMENTATION
+static void* fons__tmpalloc(size_t size, void* up);
+static void fons__tmpfree(void* ptr, void* up);
+#define STBTT_malloc(x,u) fons__tmpalloc(x,u)
+#define STBTT_free(x,u) fons__tmpfree(x,u)
+#include "stb_truetype.h"
+
+struct FONSttFontImpl {
+ stbtt_fontinfo font;
+};
+typedef struct FONSttFontImpl FONSttFontImpl;
+
+int fons__tt_init(FONScontext *context)
+{
+ FONS_NOTUSED(context);
+ return 1;
+}
+
+int fons__tt_done(FONScontext *context)
+{
+ FONS_NOTUSED(context);
+ return 1;
+}
+
+int fons__tt_loadFont(FONScontext *context, FONSttFontImpl *font, unsigned char *data, int dataSize)
+{
+ int stbError;
+ FONS_NOTUSED(dataSize);
+
+ font->font.userdata = context;
+ stbError = stbtt_InitFont(&font->font, data, 0);
+ return stbError;
+}
+
+void fons__tt_getFontVMetrics(FONSttFontImpl *font, int *ascent, int *descent, int *lineGap)
+{
+ stbtt_GetFontVMetrics(&font->font, ascent, descent, lineGap);
+}
+
+float fons__tt_getPixelHeightScale(FONSttFontImpl *font, float size)
+{
+ return stbtt_ScaleForPixelHeight(&font->font, size);
+}
+
+int fons__tt_getGlyphIndex(FONSttFontImpl *font, int codepoint)
+{
+ return stbtt_FindGlyphIndex(&font->font, codepoint);
+}
+
+int fons__tt_buildGlyphBitmap(FONSttFontImpl *font, int glyph, float size, float scale,
+ int *advance, int *lsb, int *x0, int *y0, int *x1, int *y1)
+{
+ FONS_NOTUSED(size);
+ stbtt_GetGlyphHMetrics(&font->font, glyph, advance, lsb);
+ stbtt_GetGlyphBitmapBox(&font->font, glyph, scale, scale, x0, y0, x1, y1);
+ return 1;
+}
+
+void fons__tt_renderGlyphBitmap(FONSttFontImpl *font, unsigned char *output, int outWidth, int outHeight, int outStride,
+ float scaleX, float scaleY, int glyph)
+{
+ stbtt_MakeGlyphBitmap(&font->font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph);
+}
+
+int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2)
+{
+ return stbtt_GetGlyphKernAdvance(&font->font, glyph1, glyph2);
+}
+
+#endif
+
+#ifndef FONS_SCRATCH_BUF_SIZE
+# define FONS_SCRATCH_BUF_SIZE 96000
+#endif
+#ifndef FONS_HASH_LUT_SIZE
+# define FONS_HASH_LUT_SIZE 256
+#endif
+#ifndef FONS_INIT_FONTS
+# define FONS_INIT_FONTS 4
+#endif
+#ifndef FONS_INIT_GLYPHS
+# define FONS_INIT_GLYPHS 256
+#endif
+#ifndef FONS_INIT_ATLAS_NODES
+# define FONS_INIT_ATLAS_NODES 256
+#endif
+#ifndef FONS_VERTEX_COUNT
+# define FONS_VERTEX_COUNT 1024
+#endif
+#ifndef FONS_MAX_STATES
+# define FONS_MAX_STATES 20
+#endif
+#ifndef FONS_MAX_FALLBACKS
+# define FONS_MAX_FALLBACKS 20
+#endif
+
+static unsigned int fons__hashint(unsigned int a)
+{
+ a += ~(a<<15);
+ a ^= (a>>10);
+ a += (a<<3);
+ a ^= (a>>6);
+ a += ~(a<<11);
+ a ^= (a>>16);
+ return a;
+}
+
+static int fons__mini(int a, int b)
+{
+ return a < b ? a : b;
+}
+
+static int fons__maxi(int a, int b)
+{
+ return a > b ? a : b;
+}
+
+struct FONSglyph
+{
+ unsigned int codepoint;
+ int index;
+ int next;
+ short size, blur;
+ short x0,y0,x1,y1;
+ short xadv,xoff,yoff;
+};
+typedef struct FONSglyph FONSglyph;
+
+struct FONSfont
+{
+ FONSttFontImpl font;
+ char name[64];
+ unsigned char* data;
+ int dataSize;
+ unsigned char freeData;
+ float ascender;
+ float descender;
+ float lineh;
+ FONSglyph* glyphs;
+ int cglyphs;
+ int nglyphs;
+ int lut[FONS_HASH_LUT_SIZE];
+ int fallbacks[FONS_MAX_FALLBACKS];
+ int nfallbacks;
+};
+typedef struct FONSfont FONSfont;
+
+struct FONSstate
+{
+ int font;
+ int align;
+ float size;
+ unsigned int color;
+ float blur;
+ float spacing;
+};
+typedef struct FONSstate FONSstate;
+
+struct FONSatlasNode {
+ short x, y, width;
+};
+typedef struct FONSatlasNode FONSatlasNode;
+
+struct FONSatlas
+{
+ int width, height;
+ FONSatlasNode* nodes;
+ int nnodes;
+ int cnodes;
+};
+typedef struct FONSatlas FONSatlas;
+
+struct FONScontext
+{
+ FONSparams params;
+ float itw,ith;
+ unsigned char* texData;
+ int dirtyRect[4];
+ FONSfont** fonts;
+ FONSatlas* atlas;
+ int cfonts;
+ int nfonts;
+ float verts[FONS_VERTEX_COUNT*2];
+ float tcoords[FONS_VERTEX_COUNT*2];
+ unsigned int colors[FONS_VERTEX_COUNT];
+ int nverts;
+ unsigned char* scratch;
+ int nscratch;
+ FONSstate states[FONS_MAX_STATES];
+ int nstates;
+ void (*handleError)(void* uptr, int error, int val);
+ void* errorUptr;
+};
+
+#ifdef STB_TRUETYPE_IMPLEMENTATION
+
+static void* fons__tmpalloc(size_t size, void* up)
+{
+ unsigned char* ptr;
+ FONScontext* stash = (FONScontext*)up;
+
+ // 16-byte align the returned pointer
+ size = (size + 0xf) & ~0xf;
+
+ if (stash->nscratch+(int)size > FONS_SCRATCH_BUF_SIZE) {
+ if (stash->handleError)
+ stash->handleError(stash->errorUptr, FONS_SCRATCH_FULL, stash->nscratch+(int)size);
+ return NULL;
+ }
+ ptr = stash->scratch + stash->nscratch;
+ stash->nscratch += (int)size;
+ return ptr;
+}
+
+static void fons__tmpfree(void* ptr, void* up)
+{
+ (void)ptr;
+ (void)up;
+ // empty
+}
+
+#endif // STB_TRUETYPE_IMPLEMENTATION
+
+// Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de>
+// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
+
+#define FONS_UTF8_ACCEPT 0
+#define FONS_UTF8_REJECT 12
+
+static unsigned int fons__decutf8(unsigned int* state, unsigned int* codep, unsigned int byte)
+{
+ static const unsigned char utf8d[] = {
+ // The first part of the table maps bytes to character classes that
+ // to reduce the size of the transition table and create bitmasks.
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 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,7,7,7,7,7,7,7,7,
+ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,
+
+ // The second part is a transition table that maps a combination
+ // of a state of the automaton and a character class to a state.
+ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
+ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
+ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
+ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
+ 12,36,12,12,12,12,12,12,12,12,12,12,
+ };
+
+ unsigned int type = utf8d[byte];
+
+ *codep = (*state != FONS_UTF8_ACCEPT) ?
+ (byte & 0x3fu) | (*codep << 6) :
+ (0xff >> type) & (byte);
+
+ *state = utf8d[256 + *state + type];
+ return *state;
+}
+
+// Atlas based on Skyline Bin Packer by Jukka Jylänki
+
+static void fons__deleteAtlas(FONSatlas* atlas)
+{
+ if (atlas == NULL) return;
+ if (atlas->nodes != NULL) free(atlas->nodes);
+ free(atlas);
+}
+
+static FONSatlas* fons__allocAtlas(int w, int h, int nnodes)
+{
+ FONSatlas* atlas = NULL;
+
+ // Allocate memory for the font stash.
+ atlas = (FONSatlas*)malloc(sizeof(FONSatlas));
+ if (atlas == NULL) goto error;
+ memset(atlas, 0, sizeof(FONSatlas));
+
+ atlas->width = w;
+ atlas->height = h;
+
+ // Allocate space for skyline nodes
+ atlas->nodes = (FONSatlasNode*)malloc(sizeof(FONSatlasNode) * nnodes);
+ if (atlas->nodes == NULL) goto error;
+ memset(atlas->nodes, 0, sizeof(FONSatlasNode) * nnodes);
+ atlas->nnodes = 0;
+ atlas->cnodes = nnodes;
+
+ // Init root node.
+ atlas->nodes[0].x = 0;
+ atlas->nodes[0].y = 0;
+ atlas->nodes[0].width = (short)w;
+ atlas->nnodes++;
+
+ return atlas;
+
+error:
+ if (atlas) fons__deleteAtlas(atlas);
+ return NULL;
+}
+
+static int fons__atlasInsertNode(FONSatlas* atlas, int idx, int x, int y, int w)
+{
+ int i;
+ // Insert node
+ if (atlas->nnodes+1 > atlas->cnodes) {
+ atlas->cnodes = atlas->cnodes == 0 ? 8 : atlas->cnodes * 2;
+ atlas->nodes = (FONSatlasNode*)realloc(atlas->nodes, sizeof(FONSatlasNode) * atlas->cnodes);
+ if (atlas->nodes == NULL)
+ return 0;
+ }
+ for (i = atlas->nnodes; i > idx; i--)
+ atlas->nodes[i] = atlas->nodes[i-1];
+ atlas->nodes[idx].x = (short)x;
+ atlas->nodes[idx].y = (short)y;
+ atlas->nodes[idx].width = (short)w;
+ atlas->nnodes++;
+
+ return 1;
+}
+
+static void fons__atlasRemoveNode(FONSatlas* atlas, int idx)
+{
+ int i;
+ if (atlas->nnodes == 0) return;
+ for (i = idx; i < atlas->nnodes-1; i++)
+ atlas->nodes[i] = atlas->nodes[i+1];
+ atlas->nnodes--;
+}
+
+static void fons__atlasExpand(FONSatlas* atlas, int w, int h)
+{
+ // Insert node for empty space
+ if (w > atlas->width)
+ fons__atlasInsertNode(atlas, atlas->nnodes, atlas->width, 0, w - atlas->width);
+ atlas->width = w;
+ atlas->height = h;
+}
+
+static void fons__atlasReset(FONSatlas* atlas, int w, int h)
+{
+ atlas->width = w;
+ atlas->height = h;
+ atlas->nnodes = 0;
+
+ // Init root node.
+ atlas->nodes[0].x = 0;
+ atlas->nodes[0].y = 0;
+ atlas->nodes[0].width = (short)w;
+ atlas->nnodes++;
+}
+
+static int fons__atlasAddSkylineLevel(FONSatlas* atlas, int idx, int x, int y, int w, int h)
+{
+ int i;
+
+ // Insert new node
+ if (fons__atlasInsertNode(atlas, idx, x, y+h, w) == 0)
+ return 0;
+
+ // Delete skyline segments that fall under the shadow of the new segment.
+ for (i = idx+1; i < atlas->nnodes; i++) {
+ if (atlas->nodes[i].x < atlas->nodes[i-1].x + atlas->nodes[i-1].width) {
+ int shrink = atlas->nodes[i-1].x + atlas->nodes[i-1].width - atlas->nodes[i].x;
+ atlas->nodes[i].x += (short)shrink;
+ atlas->nodes[i].width -= (short)shrink;
+ if (atlas->nodes[i].width <= 0) {
+ fons__atlasRemoveNode(atlas, i);
+ i--;
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ // Merge same height skyline segments that are next to each other.
+ for (i = 0; i < atlas->nnodes-1; i++) {
+ if (atlas->nodes[i].y == atlas->nodes[i+1].y) {
+ atlas->nodes[i].width += atlas->nodes[i+1].width;
+ fons__atlasRemoveNode(atlas, i+1);
+ i--;
+ }
+ }
+
+ return 1;
+}
+
+static int fons__atlasRectFits(FONSatlas* atlas, int i, int w, int h)
+{
+ // Checks if there is enough space at the location of skyline span 'i',
+ // and return the max height of all skyline spans under that at that location,
+ // (think tetris block being dropped at that position). Or -1 if no space found.
+ int x = atlas->nodes[i].x;
+ int y = atlas->nodes[i].y;
+ int spaceLeft;
+ if (x + w > atlas->width)
+ return -1;
+ spaceLeft = w;
+ while (spaceLeft > 0) {
+ if (i == atlas->nnodes) return -1;
+ y = fons__maxi(y, atlas->nodes[i].y);
+ if (y + h > atlas->height) return -1;
+ spaceLeft -= atlas->nodes[i].width;
+ ++i;
+ }
+ return y;
+}
+
+static int fons__atlasAddRect(FONSatlas* atlas, int rw, int rh, int* rx, int* ry)
+{
+ int besth = atlas->height, bestw = atlas->width, besti = -1;
+ int bestx = -1, besty = -1, i;
+
+ // Bottom left fit heuristic.
+ for (i = 0; i < atlas->nnodes; i++) {
+ int y = fons__atlasRectFits(atlas, i, rw, rh);
+ if (y != -1) {
+ if (y + rh < besth || (y + rh == besth && atlas->nodes[i].width < bestw)) {
+ besti = i;
+ bestw = atlas->nodes[i].width;
+ besth = y + rh;
+ bestx = atlas->nodes[i].x;
+ besty = y;
+ }
+ }
+ }
+
+ if (besti == -1)
+ return 0;
+
+ // Perform the actual packing.
+ if (fons__atlasAddSkylineLevel(atlas, besti, bestx, besty, rw, rh) == 0)
+ return 0;
+
+ *rx = bestx;
+ *ry = besty;
+
+ return 1;
+}
+
+static void fons__addWhiteRect(FONScontext* stash, int w, int h)
+{
+ int x, y, gx, gy;
+ unsigned char* dst;
+ if (fons__atlasAddRect(stash->atlas, w, h, &gx, &gy) == 0)
+ return;
+
+ // Rasterize
+ dst = &stash->texData[gx + gy * stash->params.width];
+ for (y = 0; y < h; y++) {
+ for (x = 0; x < w; x++)
+ dst[x] = 0xff;
+ dst += stash->params.width;
+ }
+
+ stash->dirtyRect[0] = fons__mini(stash->dirtyRect[0], gx);
+ stash->dirtyRect[1] = fons__mini(stash->dirtyRect[1], gy);
+ stash->dirtyRect[2] = fons__maxi(stash->dirtyRect[2], gx+w);
+ stash->dirtyRect[3] = fons__maxi(stash->dirtyRect[3], gy+h);
+}
+
+FONScontext* fonsCreateInternal(FONSparams* params)
+{
+ FONScontext* stash = NULL;
+
+ // Allocate memory for the font stash.
+ stash = (FONScontext*)malloc(sizeof(FONScontext));
+ if (stash == NULL) goto error;
+ memset(stash, 0, sizeof(FONScontext));
+
+ stash->params = *params;
+
+ // Allocate scratch buffer.
+ stash->scratch = (unsigned char*)malloc(FONS_SCRATCH_BUF_SIZE);
+ if (stash->scratch == NULL) goto error;
+
+ // Initialize implementation library
+ if (!fons__tt_init(stash)) goto error;
+
+ if (stash->params.renderCreate != NULL) {
+ if (stash->params.renderCreate(stash->params.userPtr, stash->params.width, stash->params.height) == 0)
+ goto error;
+ }
+
+ stash->atlas = fons__allocAtlas(stash->params.width, stash->params.height, FONS_INIT_ATLAS_NODES);
+ if (stash->atlas == NULL) goto error;
+
+ // Allocate space for fonts.
+ stash->fonts = (FONSfont**)malloc(sizeof(FONSfont*) * FONS_INIT_FONTS);
+ if (stash->fonts == NULL) goto error;
+ memset(stash->fonts, 0, sizeof(FONSfont*) * FONS_INIT_FONTS);
+ stash->cfonts = FONS_INIT_FONTS;
+ stash->nfonts = 0;
+
+ // Create texture for the cache.
+ stash->itw = 1.0f/stash->params.width;
+ stash->ith = 1.0f/stash->params.height;
+ stash->texData = (unsigned char*)malloc(stash->params.width * stash->params.height);
+ if (stash->texData == NULL) goto error;
+ memset(stash->texData, 0, stash->params.width * stash->params.height);
+
+ stash->dirtyRect[0] = stash->params.width;
+ stash->dirtyRect[1] = stash->params.height;
+ stash->dirtyRect[2] = 0;
+ stash->dirtyRect[3] = 0;
+
+ // Add white rect at 0,0 for debug drawing.
+ fons__addWhiteRect(stash, 2,2);
+
+ fonsPushState(stash);
+ fonsClearState(stash);
+
+ return stash;
+
+error:
+ fonsDeleteInternal(stash);
+ return NULL;
+}
+
+static FONSstate* fons__getState(FONScontext* stash)
+{
+ return &stash->states[stash->nstates-1];
+}
+
+int fonsAddFallbackFont(FONScontext* stash, int base, int fallback)
+{
+ FONSfont* baseFont = stash->fonts[base];
+ if (baseFont->nfallbacks < FONS_MAX_FALLBACKS) {
+ baseFont->fallbacks[baseFont->nfallbacks++] = fallback;
+ return 1;
+ }
+ return 0;
+}
+
+void fonsSetSize(FONScontext* stash, float size)
+{
+ fons__getState(stash)->size = size;
+}
+
+void fonsSetColor(FONScontext* stash, unsigned int color)
+{
+ fons__getState(stash)->color = color;
+}
+
+void fonsSetSpacing(FONScontext* stash, float spacing)
+{
+ fons__getState(stash)->spacing = spacing;
+}
+
+void fonsSetBlur(FONScontext* stash, float blur)
+{
+ fons__getState(stash)->blur = blur;
+}
+
+void fonsSetAlign(FONScontext* stash, int align)
+{
+ fons__getState(stash)->align = align;
+}
+
+void fonsSetFont(FONScontext* stash, int font)
+{
+ fons__getState(stash)->font = font;
+}
+
+void fonsPushState(FONScontext* stash)
+{
+ if (stash->nstates >= FONS_MAX_STATES) {
+ if (stash->handleError)
+ stash->handleError(stash->errorUptr, FONS_STATES_OVERFLOW, 0);
+ return;
+ }
+ if (stash->nstates > 0)
+ memcpy(&stash->states[stash->nstates], &stash->states[stash->nstates-1], sizeof(FONSstate));
+ stash->nstates++;
+}
+
+void fonsPopState(FONScontext* stash)
+{
+ if (stash->nstates <= 1) {
+ if (stash->handleError)
+ stash->handleError(stash->errorUptr, FONS_STATES_UNDERFLOW, 0);
+ return;
+ }
+ stash->nstates--;
+}
+
+void fonsClearState(FONScontext* stash)
+{
+ FONSstate* state = fons__getState(stash);
+ state->size = 12.0f;
+ state->color = 0xffffffff;
+ state->font = 0;
+ state->blur = 0;
+ state->spacing = 0;
+ state->align = FONS_ALIGN_LEFT | FONS_ALIGN_BASELINE;
+}
+
+static void fons__freeFont(FONSfont* font)
+{
+ if (font == NULL) return;
+ if (font->glyphs) free(font->glyphs);
+ if (font->freeData && font->data) free(font->data);
+ free(font);
+}
+
+static int fons__allocFont(FONScontext* stash)
+{
+ FONSfont* font = NULL;
+ if (stash->nfonts+1 > stash->cfonts) {
+ stash->cfonts = stash->cfonts == 0 ? 8 : stash->cfonts * 2;
+ stash->fonts = (FONSfont**)realloc(stash->fonts, sizeof(FONSfont*) * stash->cfonts);
+ if (stash->fonts == NULL)
+ return -1;
+ }
+ font = (FONSfont*)malloc(sizeof(FONSfont));
+ if (font == NULL) goto error;
+ memset(font, 0, sizeof(FONSfont));
+
+ font->glyphs = (FONSglyph*)malloc(sizeof(FONSglyph) * FONS_INIT_GLYPHS);
+ if (font->glyphs == NULL) goto error;
+ font->cglyphs = FONS_INIT_GLYPHS;
+ font->nglyphs = 0;
+
+ stash->fonts[stash->nfonts++] = font;
+ return stash->nfonts-1;
+
+error:
+ fons__freeFont(font);
+
+ return FONS_INVALID;
+}
+
+int fonsAddFont(FONScontext* stash, const char* name, const char* path)
+{
+ FILE* fp = 0;
+ int dataSize = 0;
+ size_t readed;
+ unsigned char* data = NULL;
+
+ // Read in the font data.
+ fp = fopen(path, "rb");
+ if (fp == NULL) goto error;
+ fseek(fp,0,SEEK_END);
+ dataSize = (int)ftell(fp);
+ fseek(fp,0,SEEK_SET);
+ data = (unsigned char*)malloc(dataSize);
+ if (data == NULL) goto error;
+ readed = fread(data, 1, dataSize, fp);
+ fclose(fp);
+ fp = 0;
+ if (readed != dataSize) goto error;
+
+ return fonsAddFontMem(stash, name, data, dataSize, 1);
+
+error:
+ if (data) free(data);
+ if (fp) fclose(fp);
+ return FONS_INVALID;
+}
+
+int fonsAddFontMem(FONScontext* stash, const char* name, unsigned char* data, int dataSize, int freeData)
+{
+ int i, ascent, descent, fh, lineGap;
+ FONSfont* font;
+
+ int idx = fons__allocFont(stash);
+ if (idx == FONS_INVALID)
+ return FONS_INVALID;
+
+ font = stash->fonts[idx];
+
+ strncpy(font->name, name, sizeof(font->name));
+ font->name[sizeof(font->name)-1] = '\0';
+
+ // Init hash lookup.
+ for (i = 0; i < FONS_HASH_LUT_SIZE; ++i)
+ font->lut[i] = -1;
+
+ // Read in the font data.
+ font->dataSize = dataSize;
+ font->data = data;
+ font->freeData = (unsigned char)freeData;
+
+ // Init font
+ stash->nscratch = 0;
+ if (!fons__tt_loadFont(stash, &font->font, data, dataSize)) goto error;
+
+ // Store normalized line height. The real line height is got
+ // by multiplying the lineh by font size.
+ fons__tt_getFontVMetrics( &font->font, &ascent, &descent, &lineGap);
+ fh = ascent - descent;
+ font->ascender = (float)ascent / (float)fh;
+ font->descender = (float)descent / (float)fh;
+ font->lineh = (float)(fh + lineGap) / (float)fh;
+
+ return idx;
+
+error:
+ fons__freeFont(font);
+ stash->nfonts--;
+ return FONS_INVALID;
+}
+
+int fonsGetFontByName(FONScontext* s, const char* name)
+{
+ int i;
+ for (i = 0; i < s->nfonts; i++) {
+ if (strcmp(s->fonts[i]->name, name) == 0)
+ return i;
+ }
+ return FONS_INVALID;
+}
+
+
+static FONSglyph* fons__allocGlyph(FONSfont* font)
+{
+ if (font->nglyphs+1 > font->cglyphs) {
+ font->cglyphs = font->cglyphs == 0 ? 8 : font->cglyphs * 2;
+ font->glyphs = (FONSglyph*)realloc(font->glyphs, sizeof(FONSglyph) * font->cglyphs);
+ if (font->glyphs == NULL) return NULL;
+ }
+ font->nglyphs++;
+ return &font->glyphs[font->nglyphs-1];
+}
+
+
+// Based on Exponential blur, Jani Huhtanen, 2006
+
+#define APREC 16
+#define ZPREC 7
+
+static void fons__blurCols(unsigned char* dst, int w, int h, int dstStride, int alpha)
+{
+ int x, y;
+ for (y = 0; y < h; y++) {
+ int z = 0; // force zero border
+ for (x = 1; x < w; x++) {
+ z += (alpha * (((int)(dst[x]) << ZPREC) - z)) >> APREC;
+ dst[x] = (unsigned char)(z >> ZPREC);
+ }
+ dst[w-1] = 0; // force zero border
+ z = 0;
+ for (x = w-2; x >= 0; x--) {
+ z += (alpha * (((int)(dst[x]) << ZPREC) - z)) >> APREC;
+ dst[x] = (unsigned char)(z >> ZPREC);
+ }
+ dst[0] = 0; // force zero border
+ dst += dstStride;
+ }
+}
+
+static void fons__blurRows(unsigned char* dst, int w, int h, int dstStride, int alpha)
+{
+ int x, y;
+ for (x = 0; x < w; x++) {
+ int z = 0; // force zero border
+ for (y = dstStride; y < h*dstStride; y += dstStride) {
+ z += (alpha * (((int)(dst[y]) << ZPREC) - z)) >> APREC;
+ dst[y] = (unsigned char)(z >> ZPREC);
+ }
+ dst[(h-1)*dstStride] = 0; // force zero border
+ z = 0;
+ for (y = (h-2)*dstStride; y >= 0; y -= dstStride) {
+ z += (alpha * (((int)(dst[y]) << ZPREC) - z)) >> APREC;
+ dst[y] = (unsigned char)(z >> ZPREC);
+ }
+ dst[0] = 0; // force zero border
+ dst++;
+ }
+}
+
+
+static void fons__blur(FONScontext* stash, unsigned char* dst, int w, int h, int dstStride, int blur)
+{
+ int alpha;
+ float sigma;
+ (void)stash;
+
+ if (blur < 1)
+ return;
+ // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity)
+ sigma = (float)blur * 0.57735f; // 1 / sqrt(3)
+ alpha = (int)((1<<APREC) * (1.0f - expf(-2.3f / (sigma+1.0f))));
+ fons__blurRows(dst, w, h, dstStride, alpha);
+ fons__blurCols(dst, w, h, dstStride, alpha);
+ fons__blurRows(dst, w, h, dstStride, alpha);
+ fons__blurCols(dst, w, h, dstStride, alpha);
+// fons__blurrows(dst, w, h, dstStride, alpha);
+// fons__blurcols(dst, w, h, dstStride, alpha);
+}
+
+static FONSglyph* fons__getGlyph(FONScontext* stash, FONSfont* font, unsigned int codepoint,
+ short isize, short iblur, int bitmapOption)
+{
+ int i, g, advance, lsb, x0, y0, x1, y1, gw, gh, gx, gy, x, y;
+ float scale;
+ FONSglyph* glyph = NULL;
+ unsigned int h;
+ float size = isize/10.0f;
+ int pad, added;
+ unsigned char* bdst;
+ unsigned char* dst;
+ FONSfont* renderFont = font;
+
+ if (isize < 2) return NULL;
+ if (iblur > 20) iblur = 20;
+ pad = iblur+2;
+
+ // Reset allocator.
+ stash->nscratch = 0;
+
+ // Find code point and size.
+ h = fons__hashint(codepoint) & (FONS_HASH_LUT_SIZE-1);
+ i = font->lut[h];
+ while (i != -1) {
+ if (font->glyphs[i].codepoint == codepoint && font->glyphs[i].size == isize && font->glyphs[i].blur == iblur) {
+ glyph = &font->glyphs[i];
+ if (bitmapOption == FONS_GLYPH_BITMAP_OPTIONAL || (glyph->x0 >= 0 && glyph->y0 >= 0)) {
+ return glyph;
+ }
+ // At this point, glyph exists but the bitmap data is not yet created.
+ break;
+ }
+ i = font->glyphs[i].next;
+ }
+
+ // Create a new glyph or rasterize bitmap data for a cached glyph.
+ g = fons__tt_getGlyphIndex(&font->font, codepoint);
+ // Try to find the glyph in fallback fonts.
+ if (g == 0) {
+ for (i = 0; i < font->nfallbacks; ++i) {
+ FONSfont* fallbackFont = stash->fonts[font->fallbacks[i]];
+ int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont->font, codepoint);
+ if (fallbackIndex != 0) {
+ g = fallbackIndex;
+ renderFont = fallbackFont;
+ break;
+ }
+ }
+ // It is possible that we did not find a fallback glyph.
+ // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph.
+ }
+ scale = fons__tt_getPixelHeightScale(&renderFont->font, size);
+ fons__tt_buildGlyphBitmap(&renderFont->font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1);
+ gw = x1-x0 + pad*2;
+ gh = y1-y0 + pad*2;
+
+ // Determines the spot to draw glyph in the atlas.
+ if (bitmapOption == FONS_GLYPH_BITMAP_REQUIRED) {
+ // Find free spot for the rect in the atlas
+ added = fons__atlasAddRect(stash->atlas, gw, gh, &gx, &gy);
+ if (added == 0 && stash->handleError != NULL) {
+ // Atlas is full, let the user to resize the atlas (or not), and try again.
+ stash->handleError(stash->errorUptr, FONS_ATLAS_FULL, 0);
+ added = fons__atlasAddRect(stash->atlas, gw, gh, &gx, &gy);
+ }
+ if (added == 0) return NULL;
+ } else {
+ // Negative coordinate indicates there is no bitmap data created.
+ gx = -1;
+ gy = -1;
+ }
+
+ // Init glyph.
+ if (glyph == NULL) {
+ glyph = fons__allocGlyph(font);
+ glyph->codepoint = codepoint;
+ glyph->size = isize;
+ glyph->blur = iblur;
+ glyph->next = 0;
+
+ // Insert char to hash lookup.
+ glyph->next = font->lut[h];
+ font->lut[h] = font->nglyphs-1;
+ }
+ glyph->index = g;
+ glyph->x0 = (short)gx;
+ glyph->y0 = (short)gy;
+ glyph->x1 = (short)(glyph->x0+gw);
+ glyph->y1 = (short)(glyph->y0+gh);
+ glyph->xadv = (short)(scale * advance * 10.0f);
+ glyph->xoff = (short)(x0 - pad);
+ glyph->yoff = (short)(y0 - pad);
+
+ if (bitmapOption == FONS_GLYPH_BITMAP_OPTIONAL) {
+ return glyph;
+ }
+
+ // Rasterize
+ dst = &stash->texData[(glyph->x0+pad) + (glyph->y0+pad) * stash->params.width];
+ fons__tt_renderGlyphBitmap(&renderFont->font, dst, gw-pad*2,gh-pad*2, stash->params.width, scale, scale, g);
+
+ // Make sure there is one pixel empty border.
+ dst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width];
+ for (y = 0; y < gh; y++) {
+ dst[y*stash->params.width] = 0;
+ dst[gw-1 + y*stash->params.width] = 0;
+ }
+ for (x = 0; x < gw; x++) {
+ dst[x] = 0;
+ dst[x + (gh-1)*stash->params.width] = 0;
+ }
+
+ // Debug code to color the glyph background
+/* unsigned char* fdst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width];
+ for (y = 0; y < gh; y++) {
+ for (x = 0; x < gw; x++) {
+ int a = (int)fdst[x+y*stash->params.width] + 20;
+ if (a > 255) a = 255;
+ fdst[x+y*stash->params.width] = a;
+ }
+ }*/
+
+ // Blur
+ if (iblur > 0) {
+ stash->nscratch = 0;
+ bdst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width];
+ fons__blur(stash, bdst, gw, gh, stash->params.width, iblur);
+ }
+
+ stash->dirtyRect[0] = fons__mini(stash->dirtyRect[0], glyph->x0);
+ stash->dirtyRect[1] = fons__mini(stash->dirtyRect[1], glyph->y0);
+ stash->dirtyRect[2] = fons__maxi(stash->dirtyRect[2], glyph->x1);
+ stash->dirtyRect[3] = fons__maxi(stash->dirtyRect[3], glyph->y1);
+
+ return glyph;
+}
+
+static void fons__getQuad(FONScontext* stash, FONSfont* font,
+ int prevGlyphIndex, FONSglyph* glyph,
+ float scale, float spacing, float* x, float* y, FONSquad* q)
+{
+ float rx,ry,xoff,yoff,x0,y0,x1,y1;
+
+ if (prevGlyphIndex != -1) {
+ float adv = fons__tt_getGlyphKernAdvance(&font->font, prevGlyphIndex, glyph->index) * scale;
+ *x += (int)(adv + spacing + 0.5f);
+ }
+
+ // Each glyph has 2px border to allow good interpolation,
+ // one pixel to prevent leaking, and one to allow good interpolation for rendering.
+ // Inset the texture region by one pixel for correct interpolation.
+ xoff = (short)(glyph->xoff+1);
+ yoff = (short)(glyph->yoff+1);
+ x0 = (float)(glyph->x0+1);
+ y0 = (float)(glyph->y0+1);
+ x1 = (float)(glyph->x1-1);
+ y1 = (float)(glyph->y1-1);
+
+ if (stash->params.flags & FONS_ZERO_TOPLEFT) {
+ rx = (float)(int)(*x + xoff);
+ ry = (float)(int)(*y + yoff);
+
+ q->x0 = rx;
+ q->y0 = ry;
+ q->x1 = rx + x1 - x0;
+ q->y1 = ry + y1 - y0;
+
+ q->s0 = x0 * stash->itw;
+ q->t0 = y0 * stash->ith;
+ q->s1 = x1 * stash->itw;
+ q->t1 = y1 * stash->ith;
+ } else {
+ rx = (float)(int)(*x + xoff);
+ ry = (float)(int)(*y - yoff);
+
+ q->x0 = rx;
+ q->y0 = ry;
+ q->x1 = rx + x1 - x0;
+ q->y1 = ry - y1 + y0;
+
+ q->s0 = x0 * stash->itw;
+ q->t0 = y0 * stash->ith;
+ q->s1 = x1 * stash->itw;
+ q->t1 = y1 * stash->ith;
+ }
+
+ *x += (int)(glyph->xadv / 10.0f + 0.5f);
+}
+
+static void fons__flush(FONScontext* stash)
+{
+ // Flush texture
+ if (stash->dirtyRect[0] < stash->dirtyRect[2] && stash->dirtyRect[1] < stash->dirtyRect[3]) {
+ if (stash->params.renderUpdate != NULL)
+ stash->params.renderUpdate(stash->params.userPtr, stash->dirtyRect, stash->texData);
+ // Reset dirty rect
+ stash->dirtyRect[0] = stash->params.width;
+ stash->dirtyRect[1] = stash->params.height;
+ stash->dirtyRect[2] = 0;
+ stash->dirtyRect[3] = 0;
+ }
+
+ // Flush triangles
+ if (stash->nverts > 0) {
+ if (stash->params.renderDraw != NULL)
+ stash->params.renderDraw(stash->params.userPtr, stash->verts, stash->tcoords, stash->colors, stash->nverts);
+ stash->nverts = 0;
+ }
+}
+
+static __inline void fons__vertex(FONScontext* stash, float x, float y, float s, float t, unsigned int c)
+{
+ stash->verts[stash->nverts*2+0] = x;
+ stash->verts[stash->nverts*2+1] = y;
+ stash->tcoords[stash->nverts*2+0] = s;
+ stash->tcoords[stash->nverts*2+1] = t;
+ stash->colors[stash->nverts] = c;
+ stash->nverts++;
+}
+
+static float fons__getVertAlign(FONScontext* stash, FONSfont* font, int align, short isize)
+{
+ if (stash->params.flags & FONS_ZERO_TOPLEFT) {
+ if (align & FONS_ALIGN_TOP) {
+ return font->ascender * (float)isize/10.0f;
+ } else if (align & FONS_ALIGN_MIDDLE) {
+ return (font->ascender + font->descender) / 2.0f * (float)isize/10.0f;
+ } else if (align & FONS_ALIGN_BASELINE) {
+ return 0.0f;
+ } else if (align & FONS_ALIGN_BOTTOM) {
+ return font->descender * (float)isize/10.0f;
+ }
+ } else {
+ if (align & FONS_ALIGN_TOP) {
+ return -font->ascender * (float)isize/10.0f;
+ } else if (align & FONS_ALIGN_MIDDLE) {
+ return -(font->ascender + font->descender) / 2.0f * (float)isize/10.0f;
+ } else if (align & FONS_ALIGN_BASELINE) {
+ return 0.0f;
+ } else if (align & FONS_ALIGN_BOTTOM) {
+ return -font->descender * (float)isize/10.0f;
+ }
+ }
+ return 0.0;
+}
+
+float fonsDrawText(FONScontext* stash,
+ float x, float y,
+ const char* str, const char* end)
+{
+ FONSstate* state = fons__getState(stash);
+ unsigned int codepoint;
+ unsigned int utf8state = 0;
+ FONSglyph* glyph = NULL;
+ FONSquad q;
+ int prevGlyphIndex = -1;
+ short isize = (short)(state->size*10.0f);
+ short iblur = (short)state->blur;
+ float scale;
+ FONSfont* font;
+ float width;
+
+ if (stash == NULL) return x;
+ if (state->font < 0 || state->font >= stash->nfonts) return x;
+ font = stash->fonts[state->font];
+ if (font->data == NULL) return x;
+
+ scale = fons__tt_getPixelHeightScale(&font->font, (float)isize/10.0f);
+
+ if (end == NULL)
+ end = str + strlen(str);
+
+ // Align horizontally
+ if (state->align & FONS_ALIGN_LEFT) {
+ // empty
+ } else if (state->align & FONS_ALIGN_RIGHT) {
+ width = fonsTextBounds(stash, x,y, str, end, NULL);
+ x -= width;
+ } else if (state->align & FONS_ALIGN_CENTER) {
+ width = fonsTextBounds(stash, x,y, str, end, NULL);
+ x -= width * 0.5f;
+ }
+ // Align vertically.
+ y += fons__getVertAlign(stash, font, state->align, isize);
+
+ for (; str != end; ++str) {
+ if (fons__decutf8(&utf8state, &codepoint, *(const unsigned char*)str))
+ continue;
+ glyph = fons__getGlyph(stash, font, codepoint, isize, iblur, FONS_GLYPH_BITMAP_REQUIRED);
+ if (glyph != NULL) {
+ fons__getQuad(stash, font, prevGlyphIndex, glyph, scale, state->spacing, &x, &y, &q);
+
+ if (stash->nverts+6 > FONS_VERTEX_COUNT)
+ fons__flush(stash);
+
+ fons__vertex(stash, q.x0, q.y0, q.s0, q.t0, state->color);
+ fons__vertex(stash, q.x1, q.y1, q.s1, q.t1, state->color);
+ fons__vertex(stash, q.x1, q.y0, q.s1, q.t0, state->color);
+
+ fons__vertex(stash, q.x0, q.y0, q.s0, q.t0, state->color);
+ fons__vertex(stash, q.x0, q.y1, q.s0, q.t1, state->color);
+ fons__vertex(stash, q.x1, q.y1, q.s1, q.t1, state->color);
+ }
+ prevGlyphIndex = glyph != NULL ? glyph->index : -1;
+ }
+ fons__flush(stash);
+
+ return x;
+}
+
+int fonsTextIterInit(FONScontext* stash, FONStextIter* iter,
+ float x, float y, const char* str, const char* end, int bitmapOption)
+{
+ FONSstate* state = fons__getState(stash);
+ float width;
+
+ memset(iter, 0, sizeof(*iter));
+
+ if (stash == NULL) return 0;
+ if (state->font < 0 || state->font >= stash->nfonts) return 0;
+ iter->font = stash->fonts[state->font];
+ if (iter->font->data == NULL) return 0;
+
+ iter->isize = (short)(state->size*10.0f);
+ iter->iblur = (short)state->blur;
+ iter->scale = fons__tt_getPixelHeightScale(&iter->font->font, (float)iter->isize/10.0f);
+
+ // Align horizontally
+ if (state->align & FONS_ALIGN_LEFT) {
+ // empty
+ } else if (state->align & FONS_ALIGN_RIGHT) {
+ width = fonsTextBounds(stash, x,y, str, end, NULL);
+ x -= width;
+ } else if (state->align & FONS_ALIGN_CENTER) {
+ width = fonsTextBounds(stash, x,y, str, end, NULL);
+ x -= width * 0.5f;
+ }
+ // Align vertically.
+ y += fons__getVertAlign(stash, iter->font, state->align, iter->isize);
+
+ if (end == NULL)
+ end = str + strlen(str);
+
+ iter->x = iter->nextx = x;
+ iter->y = iter->nexty = y;
+ iter->spacing = state->spacing;
+ iter->str = str;
+ iter->next = str;
+ iter->end = end;
+ iter->codepoint = 0;
+ iter->prevGlyphIndex = -1;
+ iter->bitmapOption = bitmapOption;
+
+ return 1;
+}
+
+int fonsTextIterNext(FONScontext* stash, FONStextIter* iter, FONSquad* quad)
+{
+ FONSglyph* glyph = NULL;
+ const char* str = iter->next;
+ iter->str = iter->next;
+
+ if (str == iter->end)
+ return 0;
+
+ for (; str != iter->end; str++) {
+ if (fons__decutf8(&iter->utf8state, &iter->codepoint, *(const unsigned char*)str))
+ continue;
+ str++;
+ // Get glyph and quad
+ iter->x = iter->nextx;
+ iter->y = iter->nexty;
+ glyph = fons__getGlyph(stash, iter->font, iter->codepoint, iter->isize, iter->iblur, iter->bitmapOption);
+ // If the iterator was initialized with FONS_GLYPH_BITMAP_OPTIONAL, then the UV coordinates of the quad will be invalid.
+ if (glyph != NULL)
+ fons__getQuad(stash, iter->font, iter->prevGlyphIndex, glyph, iter->scale, iter->spacing, &iter->nextx, &iter->nexty, quad);
+ iter->prevGlyphIndex = glyph != NULL ? glyph->index : -1;
+ break;
+ }
+ iter->next = str;
+
+ return 1;
+}
+
+void fonsDrawDebug(FONScontext* stash, float x, float y)
+{
+ int i;
+ int w = stash->params.width;
+ int h = stash->params.height;
+ float u = w == 0 ? 0 : (1.0f / w);
+ float v = h == 0 ? 0 : (1.0f / h);
+
+ if (stash->nverts+6+6 > FONS_VERTEX_COUNT)
+ fons__flush(stash);
+
+ // Draw background
+ fons__vertex(stash, x+0, y+0, u, v, 0x0fffffff);
+ fons__vertex(stash, x+w, y+h, u, v, 0x0fffffff);
+ fons__vertex(stash, x+w, y+0, u, v, 0x0fffffff);
+
+ fons__vertex(stash, x+0, y+0, u, v, 0x0fffffff);
+ fons__vertex(stash, x+0, y+h, u, v, 0x0fffffff);
+ fons__vertex(stash, x+w, y+h, u, v, 0x0fffffff);
+
+ // Draw texture
+ fons__vertex(stash, x+0, y+0, 0, 0, 0xffffffff);
+ fons__vertex(stash, x+w, y+h, 1, 1, 0xffffffff);
+ fons__vertex(stash, x+w, y+0, 1, 0, 0xffffffff);
+
+ fons__vertex(stash, x+0, y+0, 0, 0, 0xffffffff);
+ fons__vertex(stash, x+0, y+h, 0, 1, 0xffffffff);
+ fons__vertex(stash, x+w, y+h, 1, 1, 0xffffffff);
+
+ // Drawbug draw atlas
+ for (i = 0; i < stash->atlas->nnodes; i++) {
+ FONSatlasNode* n = &stash->atlas->nodes[i];
+
+ if (stash->nverts+6 > FONS_VERTEX_COUNT)
+ fons__flush(stash);
+
+ fons__vertex(stash, x+n->x+0, y+n->y+0, u, v, 0xc00000ff);
+ fons__vertex(stash, x+n->x+n->width, y+n->y+1, u, v, 0xc00000ff);
+ fons__vertex(stash, x+n->x+n->width, y+n->y+0, u, v, 0xc00000ff);
+
+ fons__vertex(stash, x+n->x+0, y+n->y+0, u, v, 0xc00000ff);
+ fons__vertex(stash, x+n->x+0, y+n->y+1, u, v, 0xc00000ff);
+ fons__vertex(stash, x+n->x+n->width, y+n->y+1, u, v, 0xc00000ff);
+ }
+
+ fons__flush(stash);
+}
+
+float fonsTextBounds(FONScontext* stash,
+ float x, float y,
+ const char* str, const char* end,
+ float* bounds)
+{
+ FONSstate* state = fons__getState(stash);
+ unsigned int codepoint;
+ unsigned int utf8state = 0;
+ FONSquad q;
+ FONSglyph* glyph = NULL;
+ int prevGlyphIndex = -1;
+ short isize = (short)(state->size*10.0f);
+ short iblur = (short)state->blur;
+ float scale;
+ FONSfont* font;
+ float startx, advance;
+ float minx, miny, maxx, maxy;
+
+ if (stash == NULL) return 0;
+ if (state->font < 0 || state->font >= stash->nfonts) return 0;
+ font = stash->fonts[state->font];
+ if (font->data == NULL) return 0;
+
+ scale = fons__tt_getPixelHeightScale(&font->font, (float)isize/10.0f);
+
+ // Align vertically.
+ y += fons__getVertAlign(stash, font, state->align, isize);
+
+ minx = maxx = x;
+ miny = maxy = y;
+ startx = x;
+
+ if (end == NULL)
+ end = str + strlen(str);
+
+ for (; str != end; ++str) {
+ if (fons__decutf8(&utf8state, &codepoint, *(const unsigned char*)str))
+ continue;
+ glyph = fons__getGlyph(stash, font, codepoint, isize, iblur, FONS_GLYPH_BITMAP_OPTIONAL);
+ if (glyph != NULL) {
+ fons__getQuad(stash, font, prevGlyphIndex, glyph, scale, state->spacing, &x, &y, &q);
+ if (q.x0 < minx) minx = q.x0;
+ if (q.x1 > maxx) maxx = q.x1;
+ if (stash->params.flags & FONS_ZERO_TOPLEFT) {
+ if (q.y0 < miny) miny = q.y0;
+ if (q.y1 > maxy) maxy = q.y1;
+ } else {
+ if (q.y1 < miny) miny = q.y1;
+ if (q.y0 > maxy) maxy = q.y0;
+ }
+ }
+ prevGlyphIndex = glyph != NULL ? glyph->index : -1;
+ }
+
+ advance = x - startx;
+
+ // Align horizontally
+ if (state->align & FONS_ALIGN_LEFT) {
+ // empty
+ } else if (state->align & FONS_ALIGN_RIGHT) {
+ minx -= advance;
+ maxx -= advance;
+ } else if (state->align & FONS_ALIGN_CENTER) {
+ minx -= advance * 0.5f;
+ maxx -= advance * 0.5f;
+ }
+
+ if (bounds) {
+ bounds[0] = minx;
+ bounds[1] = miny;
+ bounds[2] = maxx;
+ bounds[3] = maxy;
+ }
+
+ return advance;
+}
+
+void fonsVertMetrics(FONScontext* stash,
+ float* ascender, float* descender, float* lineh)
+{
+ FONSfont* font;
+ FONSstate* state = fons__getState(stash);
+ short isize;
+
+ if (stash == NULL) return;
+ if (state->font < 0 || state->font >= stash->nfonts) return;
+ font = stash->fonts[state->font];
+ isize = (short)(state->size*10.0f);
+ if (font->data == NULL) return;
+
+ if (ascender)
+ *ascender = font->ascender*isize/10.0f;
+ if (descender)
+ *descender = font->descender*isize/10.0f;
+ if (lineh)
+ *lineh = font->lineh*isize/10.0f;
+}
+
+void fonsLineBounds(FONScontext* stash, float y, float* miny, float* maxy)
+{
+ FONSfont* font;
+ FONSstate* state = fons__getState(stash);
+ short isize;
+
+ if (stash == NULL) return;
+ if (state->font < 0 || state->font >= stash->nfonts) return;
+ font = stash->fonts[state->font];
+ isize = (short)(state->size*10.0f);
+ if (font->data == NULL) return;
+
+ y += fons__getVertAlign(stash, font, state->align, isize);
+
+ if (stash->params.flags & FONS_ZERO_TOPLEFT) {
+ *miny = y - font->ascender * (float)isize/10.0f;
+ *maxy = *miny + font->lineh*isize/10.0f;
+ } else {
+ *maxy = y + font->descender * (float)isize/10.0f;
+ *miny = *maxy - font->lineh*isize/10.0f;
+ }
+}
+
+const unsigned char* fonsGetTextureData(FONScontext* stash, int* width, int* height)
+{
+ if (width != NULL)
+ *width = stash->params.width;
+ if (height != NULL)
+ *height = stash->params.height;
+ return stash->texData;
+}
+
+int fonsValidateTexture(FONScontext* stash, int* dirty)
+{
+ if (stash->dirtyRect[0] < stash->dirtyRect[2] && stash->dirtyRect[1] < stash->dirtyRect[3]) {
+ dirty[0] = stash->dirtyRect[0];
+ dirty[1] = stash->dirtyRect[1];
+ dirty[2] = stash->dirtyRect[2];
+ dirty[3] = stash->dirtyRect[3];
+ // Reset dirty rect
+ stash->dirtyRect[0] = stash->params.width;
+ stash->dirtyRect[1] = stash->params.height;
+ stash->dirtyRect[2] = 0;
+ stash->dirtyRect[3] = 0;
+ return 1;
+ }
+ return 0;
+}
+
+void fonsDeleteInternal(FONScontext* stash)
+{
+ int i;
+ if (stash == NULL) return;
+
+ if (stash->params.renderDelete)
+ stash->params.renderDelete(stash->params.userPtr);
+
+ for (i = 0; i < stash->nfonts; ++i)
+ fons__freeFont(stash->fonts[i]);
+
+ if (stash->atlas) fons__deleteAtlas(stash->atlas);
+ if (stash->fonts) free(stash->fonts);
+ if (stash->texData) free(stash->texData);
+ if (stash->scratch) free(stash->scratch);
+ free(stash);
+ fons__tt_done(stash);
+}
+
+void fonsSetErrorCallback(FONScontext* stash, void (*callback)(void* uptr, int error, int val), void* uptr)
+{
+ if (stash == NULL) return;
+ stash->handleError = callback;
+ stash->errorUptr = uptr;
+}
+
+void fonsGetAtlasSize(FONScontext* stash, int* width, int* height)
+{
+ if (stash == NULL) return;
+ *width = stash->params.width;
+ *height = stash->params.height;
+}
+
+int fonsExpandAtlas(FONScontext* stash, int width, int height)
+{
+ int i, maxy = 0;
+ unsigned char* data = NULL;
+ if (stash == NULL) return 0;
+
+ width = fons__maxi(width, stash->params.width);
+ height = fons__maxi(height, stash->params.height);
+
+ if (width == stash->params.width && height == stash->params.height)
+ return 1;
+
+ // Flush pending glyphs.
+ fons__flush(stash);
+
+ // Create new texture
+ if (stash->params.renderResize != NULL) {
+ if (stash->params.renderResize(stash->params.userPtr, width, height) == 0)
+ return 0;
+ }
+ // Copy old texture data over.
+ data = (unsigned char*)malloc(width * height);
+ if (data == NULL)
+ return 0;
+ for (i = 0; i < stash->params.height; i++) {
+ unsigned char* dst = &data[i*width];
+ unsigned char* src = &stash->texData[i*stash->params.width];
+ memcpy(dst, src, stash->params.width);
+ if (width > stash->params.width)
+ memset(dst+stash->params.width, 0, width - stash->params.width);
+ }
+ if (height > stash->params.height)
+ memset(&data[stash->params.height * width], 0, (height - stash->params.height) * width);
+
+ free(stash->texData);
+ stash->texData = data;
+
+ // Increase atlas size
+ fons__atlasExpand(stash->atlas, width, height);
+
+ // Add existing data as dirty.
+ for (i = 0; i < stash->atlas->nnodes; i++)
+ maxy = fons__maxi(maxy, stash->atlas->nodes[i].y);
+ stash->dirtyRect[0] = 0;
+ stash->dirtyRect[1] = 0;
+ stash->dirtyRect[2] = stash->params.width;
+ stash->dirtyRect[3] = maxy;
+
+ stash->params.width = width;
+ stash->params.height = height;
+ stash->itw = 1.0f/stash->params.width;
+ stash->ith = 1.0f/stash->params.height;
+
+ return 1;
+}
+
+int fonsResetAtlas(FONScontext* stash, int width, int height)
+{
+ int i, j;
+ if (stash == NULL) return 0;
+
+ // Flush pending glyphs.
+ fons__flush(stash);
+
+ // Create new texture
+ if (stash->params.renderResize != NULL) {
+ if (stash->params.renderResize(stash->params.userPtr, width, height) == 0)
+ return 0;
+ }
+
+ // Reset atlas
+ fons__atlasReset(stash->atlas, width, height);
+
+ // Clear texture data.
+ stash->texData = (unsigned char*)realloc(stash->texData, width * height);
+ if (stash->texData == NULL) return 0;
+ memset(stash->texData, 0, width * height);
+
+ // Reset dirty rect
+ stash->dirtyRect[0] = width;
+ stash->dirtyRect[1] = height;
+ stash->dirtyRect[2] = 0;
+ stash->dirtyRect[3] = 0;
+
+ // Reset cached glyphs
+ for (i = 0; i < stash->nfonts; i++) {
+ FONSfont* font = stash->fonts[i];
+ font->nglyphs = 0;
+ for (j = 0; j < FONS_HASH_LUT_SIZE; j++)
+ font->lut[j] = -1;
+ }
+
+ stash->params.width = width;
+ stash->params.height = height;
+ stash->itw = 1.0f/stash->params.width;
+ stash->ith = 1.0f/stash->params.height;
+
+ // Add white rect at 0,0 for debug drawing.
+ fons__addWhiteRect(stash, 2,2);
+
+ return 1;
+}
+
+
+#endif
diff --git a/subprojects/d2tk/nanovg/src/nanovg.c b/subprojects/d2tk/nanovg/src/nanovg.c
new file mode 100644
index 0000000..1ab49f0
--- /dev/null
+++ b/subprojects/d2tk/nanovg/src/nanovg.c
@@ -0,0 +1,2913 @@
+//
+// Copyright (c) 2013 Mikko Mononen memon@inside.org
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <memory.h>
+
+#include "nanovg.h"
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmisleading-indentation"
+#pragma GCC diagnostic ignored "-Wimplicit-fallthrough="
+#pragma GCC diagnostic ignored "-Wshift-negative-value"
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wsign-compare"
+#define FONTSTASH_IMPLEMENTATION
+#include "fontstash.h"
+#define STB_IMAGE_IMPLEMENTATION
+#include "stb_image.h"
+#pragma GCC diagnostic pop
+
+#ifdef _MSC_VER
+#pragma warning(disable: 4100) // unreferenced formal parameter
+#pragma warning(disable: 4127) // conditional expression is constant
+#pragma warning(disable: 4204) // nonstandard extension used : non-constant aggregate initializer
+#pragma warning(disable: 4706) // assignment within conditional expression
+#endif
+
+#define NVG_INIT_FONTIMAGE_SIZE 512
+#define NVG_MAX_FONTIMAGE_SIZE 2048
+#define NVG_MAX_FONTIMAGES 4
+
+#define NVG_INIT_COMMANDS_SIZE 256
+#define NVG_INIT_POINTS_SIZE 128
+#define NVG_INIT_PATHS_SIZE 16
+#define NVG_INIT_VERTS_SIZE 256
+#define NVG_MAX_STATES 32
+
+#define NVG_KAPPA90 0.5522847493f // Length proportional to radius of a cubic bezier handle for 90deg arcs.
+
+#define NVG_COUNTOF(arr) (sizeof(arr) / sizeof(0[arr]))
+
+
+enum NVGcommands {
+ NVG_MOVETO = 0,
+ NVG_LINETO = 1,
+ NVG_BEZIERTO = 2,
+ NVG_CLOSE = 3,
+ NVG_WINDING = 4,
+};
+
+enum NVGpointFlags
+{
+ NVG_PT_CORNER = 0x01,
+ NVG_PT_LEFT = 0x02,
+ NVG_PT_BEVEL = 0x04,
+ NVG_PR_INNERBEVEL = 0x08,
+};
+
+struct NVGstate {
+ NVGcompositeOperationState compositeOperation;
+ int shapeAntiAlias;
+ NVGpaint fill;
+ NVGpaint stroke;
+ float strokeWidth;
+ float miterLimit;
+ int lineJoin;
+ int lineCap;
+ float alpha;
+ float xform[6];
+ NVGscissor scissor;
+ float fontSize;
+ float letterSpacing;
+ float lineHeight;
+ float fontBlur;
+ int textAlign;
+ int fontId;
+};
+typedef struct NVGstate NVGstate;
+
+struct NVGpoint {
+ float x,y;
+ float dx, dy;
+ float len;
+ float dmx, dmy;
+ unsigned char flags;
+};
+typedef struct NVGpoint NVGpoint;
+
+struct NVGpathCache {
+ NVGpoint* points;
+ int npoints;
+ int cpoints;
+ NVGpath* paths;
+ int npaths;
+ int cpaths;
+ NVGvertex* verts;
+ int nverts;
+ int cverts;
+ float bounds[4];
+};
+typedef struct NVGpathCache NVGpathCache;
+
+struct NVGcontext {
+ NVGparams params;
+ float* commands;
+ int ccommands;
+ int ncommands;
+ float commandx, commandy;
+ NVGstate states[NVG_MAX_STATES];
+ int nstates;
+ NVGpathCache* cache;
+ float tessTol;
+ float distTol;
+ float fringeWidth;
+ float devicePxRatio;
+ struct FONScontext* fs;
+ int fontImages[NVG_MAX_FONTIMAGES];
+ int fontImageIdx;
+ int drawCallCount;
+ int fillTriCount;
+ int strokeTriCount;
+ int textTriCount;
+};
+
+static float nvg__sqrtf(float a) { return sqrtf(a); }
+static float nvg__modf(float a, float b) { return fmodf(a, b); }
+static float nvg__sinf(float a) { return sinf(a); }
+static float nvg__cosf(float a) { return cosf(a); }
+static float nvg__tanf(float a) { return tanf(a); }
+static float nvg__atan2f(float a,float b) { return atan2f(a, b); }
+static float nvg__acosf(float a) { return acosf(a); }
+
+static int nvg__mini(int a, int b) { return a < b ? a : b; }
+static int nvg__maxi(int a, int b) { return a > b ? a : b; }
+static int nvg__clampi(int a, int mn, int mx) { return a < mn ? mn : (a > mx ? mx : a); }
+static float nvg__minf(float a, float b) { return a < b ? a : b; }
+static float nvg__maxf(float a, float b) { return a > b ? a : b; }
+static float nvg__absf(float a) { return a >= 0.0f ? a : -a; }
+static float nvg__signf(float a) { return a >= 0.0f ? 1.0f : -1.0f; }
+static float nvg__clampf(float a, float mn, float mx) { return a < mn ? mn : (a > mx ? mx : a); }
+static float nvg__cross(float dx0, float dy0, float dx1, float dy1) { return dx1*dy0 - dx0*dy1; }
+
+static float nvg__normalize(float *x, float* y)
+{
+ float d = nvg__sqrtf((*x)*(*x) + (*y)*(*y));
+ if (d > 1e-6f) {
+ float id = 1.0f / d;
+ *x *= id;
+ *y *= id;
+ }
+ return d;
+}
+
+
+static void nvg__deletePathCache(NVGpathCache* c)
+{
+ if (c == NULL) return;
+ if (c->points != NULL) free(c->points);
+ if (c->paths != NULL) free(c->paths);
+ if (c->verts != NULL) free(c->verts);
+ free(c);
+}
+
+static NVGpathCache* nvg__allocPathCache(void)
+{
+ NVGpathCache* c = (NVGpathCache*)malloc(sizeof(NVGpathCache));
+ if (c == NULL) goto error;
+ memset(c, 0, sizeof(NVGpathCache));
+
+ c->points = (NVGpoint*)malloc(sizeof(NVGpoint)*NVG_INIT_POINTS_SIZE);
+ if (!c->points) goto error;
+ c->npoints = 0;
+ c->cpoints = NVG_INIT_POINTS_SIZE;
+
+ c->paths = (NVGpath*)malloc(sizeof(NVGpath)*NVG_INIT_PATHS_SIZE);
+ if (!c->paths) goto error;
+ c->npaths = 0;
+ c->cpaths = NVG_INIT_PATHS_SIZE;
+
+ c->verts = (NVGvertex*)malloc(sizeof(NVGvertex)*NVG_INIT_VERTS_SIZE);
+ if (!c->verts) goto error;
+ c->nverts = 0;
+ c->cverts = NVG_INIT_VERTS_SIZE;
+
+ return c;
+error:
+ nvg__deletePathCache(c);
+ return NULL;
+}
+
+static void nvg__setDevicePixelRatio(NVGcontext* ctx, float ratio)
+{
+ ctx->tessTol = 0.25f / ratio;
+ ctx->distTol = 0.01f / ratio;
+ ctx->fringeWidth = 1.0f / ratio;
+ ctx->devicePxRatio = ratio;
+}
+
+static NVGcompositeOperationState nvg__compositeOperationState(int op)
+{
+ int sfactor, dfactor;
+
+ if (op == NVG_SOURCE_OVER)
+ {
+ sfactor = NVG_ONE;
+ dfactor = NVG_ONE_MINUS_SRC_ALPHA;
+ }
+ else if (op == NVG_SOURCE_IN)
+ {
+ sfactor = NVG_DST_ALPHA;
+ dfactor = NVG_ZERO;
+ }
+ else if (op == NVG_SOURCE_OUT)
+ {
+ sfactor = NVG_ONE_MINUS_DST_ALPHA;
+ dfactor = NVG_ZERO;
+ }
+ else if (op == NVG_ATOP)
+ {
+ sfactor = NVG_DST_ALPHA;
+ dfactor = NVG_ONE_MINUS_SRC_ALPHA;
+ }
+ else if (op == NVG_DESTINATION_OVER)
+ {
+ sfactor = NVG_ONE_MINUS_DST_ALPHA;
+ dfactor = NVG_ONE;
+ }
+ else if (op == NVG_DESTINATION_IN)
+ {
+ sfactor = NVG_ZERO;
+ dfactor = NVG_SRC_ALPHA;
+ }
+ else if (op == NVG_DESTINATION_OUT)
+ {
+ sfactor = NVG_ZERO;
+ dfactor = NVG_ONE_MINUS_SRC_ALPHA;
+ }
+ else if (op == NVG_DESTINATION_ATOP)
+ {
+ sfactor = NVG_ONE_MINUS_DST_ALPHA;
+ dfactor = NVG_SRC_ALPHA;
+ }
+ else if (op == NVG_LIGHTER)
+ {
+ sfactor = NVG_ONE;
+ dfactor = NVG_ONE;
+ }
+ else if (op == NVG_COPY)
+ {
+ sfactor = NVG_ONE;
+ dfactor = NVG_ZERO;
+ }
+ else if (op == NVG_XOR)
+ {
+ sfactor = NVG_ONE_MINUS_DST_ALPHA;
+ dfactor = NVG_ONE_MINUS_SRC_ALPHA;
+ }
+ else
+ {
+ sfactor = NVG_ONE;
+ dfactor = NVG_ZERO;
+ }
+
+ NVGcompositeOperationState state;
+ state.srcRGB = sfactor;
+ state.dstRGB = dfactor;
+ state.srcAlpha = sfactor;
+ state.dstAlpha = dfactor;
+ return state;
+}
+
+static NVGstate* nvg__getState(NVGcontext* ctx)
+{
+ return &ctx->states[ctx->nstates-1];
+}
+
+NVGcontext* nvgCreateInternal(NVGparams* params)
+{
+ FONSparams fontParams;
+ NVGcontext* ctx = (NVGcontext*)malloc(sizeof(NVGcontext));
+ int i;
+ if (ctx == NULL) goto error;
+ memset(ctx, 0, sizeof(NVGcontext));
+
+ ctx->params = *params;
+ for (i = 0; i < NVG_MAX_FONTIMAGES; i++)
+ ctx->fontImages[i] = 0;
+
+ ctx->commands = (float*)malloc(sizeof(float)*NVG_INIT_COMMANDS_SIZE);
+ if (!ctx->commands) goto error;
+ ctx->ncommands = 0;
+ ctx->ccommands = NVG_INIT_COMMANDS_SIZE;
+
+ ctx->cache = nvg__allocPathCache();
+ if (ctx->cache == NULL) goto error;
+
+ nvgSave(ctx);
+ nvgReset(ctx);
+
+ nvg__setDevicePixelRatio(ctx, 1.0f);
+
+ if (ctx->params.renderCreate(ctx->params.userPtr) == 0) goto error;
+
+ // Init font rendering
+ memset(&fontParams, 0, sizeof(fontParams));
+ fontParams.width = NVG_INIT_FONTIMAGE_SIZE;
+ fontParams.height = NVG_INIT_FONTIMAGE_SIZE;
+ fontParams.flags = FONS_ZERO_TOPLEFT;
+ fontParams.renderCreate = NULL;
+ fontParams.renderUpdate = NULL;
+ fontParams.renderDraw = NULL;
+ fontParams.renderDelete = NULL;
+ fontParams.userPtr = NULL;
+ ctx->fs = fonsCreateInternal(&fontParams);
+ if (ctx->fs == NULL) goto error;
+
+ // Create font texture
+ ctx->fontImages[0] = ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_ALPHA, fontParams.width, fontParams.height, 0, NULL);
+ if (ctx->fontImages[0] == 0) goto error;
+ ctx->fontImageIdx = 0;
+
+ return ctx;
+
+error:
+ nvgDeleteInternal(ctx);
+ return 0;
+}
+
+NVGparams* nvgInternalParams(NVGcontext* ctx)
+{
+ return &ctx->params;
+}
+
+void nvgDeleteInternal(NVGcontext* ctx)
+{
+ int i;
+ if (ctx == NULL) return;
+ if (ctx->commands != NULL) free(ctx->commands);
+ if (ctx->cache != NULL) nvg__deletePathCache(ctx->cache);
+
+ if (ctx->fs)
+ fonsDeleteInternal(ctx->fs);
+
+ for (i = 0; i < NVG_MAX_FONTIMAGES; i++) {
+ if (ctx->fontImages[i] != 0) {
+ nvgDeleteImage(ctx, ctx->fontImages[i]);
+ ctx->fontImages[i] = 0;
+ }
+ }
+
+ if (ctx->params.renderDelete != NULL)
+ ctx->params.renderDelete(ctx->params.userPtr);
+
+ free(ctx);
+}
+
+void nvgBeginFrame(NVGcontext* ctx, float windowWidth, float windowHeight, float devicePixelRatio)
+{
+/* printf("Tris: draws:%d fill:%d stroke:%d text:%d TOT:%d\n",
+ ctx->drawCallCount, ctx->fillTriCount, ctx->strokeTriCount, ctx->textTriCount,
+ ctx->fillTriCount+ctx->strokeTriCount+ctx->textTriCount);*/
+
+ ctx->nstates = 0;
+ nvgSave(ctx);
+ nvgReset(ctx);
+
+ nvg__setDevicePixelRatio(ctx, devicePixelRatio);
+
+ ctx->params.renderViewport(ctx->params.userPtr, windowWidth, windowHeight, devicePixelRatio);
+
+ ctx->drawCallCount = 0;
+ ctx->fillTriCount = 0;
+ ctx->strokeTriCount = 0;
+ ctx->textTriCount = 0;
+}
+
+void nvgCancelFrame(NVGcontext* ctx)
+{
+ ctx->params.renderCancel(ctx->params.userPtr);
+}
+
+void nvgEndFrame(NVGcontext* ctx)
+{
+ ctx->params.renderFlush(ctx->params.userPtr);
+ if (ctx->fontImageIdx != 0) {
+ int fontImage = ctx->fontImages[ctx->fontImageIdx];
+ int i, j, iw, ih;
+ // delete images that smaller than current one
+ if (fontImage == 0)
+ return;
+ nvgImageSize(ctx, fontImage, &iw, &ih);
+ for (i = j = 0; i < ctx->fontImageIdx; i++) {
+ if (ctx->fontImages[i] != 0) {
+ int nw, nh;
+ nvgImageSize(ctx, ctx->fontImages[i], &nw, &nh);
+ if (nw < iw || nh < ih)
+ nvgDeleteImage(ctx, ctx->fontImages[i]);
+ else
+ ctx->fontImages[j++] = ctx->fontImages[i];
+ }
+ }
+ // make current font image to first
+ ctx->fontImages[j++] = ctx->fontImages[0];
+ ctx->fontImages[0] = fontImage;
+ ctx->fontImageIdx = 0;
+ // clear all images after j
+ for (i = j; i < NVG_MAX_FONTIMAGES; i++)
+ ctx->fontImages[i] = 0;
+ }
+}
+
+NVGcolor nvgRGB(unsigned char r, unsigned char g, unsigned char b)
+{
+ return nvgRGBA(r,g,b,255);
+}
+
+NVGcolor nvgRGBf(float r, float g, float b)
+{
+ return nvgRGBAf(r,g,b,1.0f);
+}
+
+NVGcolor nvgRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+{
+ NVGcolor color;
+ // Use longer initialization to suppress warning.
+ color.r = r / 255.0f;
+ color.g = g / 255.0f;
+ color.b = b / 255.0f;
+ color.a = a / 255.0f;
+ return color;
+}
+
+NVGcolor nvgRGBAf(float r, float g, float b, float a)
+{
+ NVGcolor color;
+ // Use longer initialization to suppress warning.
+ color.r = r;
+ color.g = g;
+ color.b = b;
+ color.a = a;
+ return color;
+}
+
+NVGcolor nvgTransRGBA(NVGcolor c, unsigned char a)
+{
+ c.a = a / 255.0f;
+ return c;
+}
+
+NVGcolor nvgTransRGBAf(NVGcolor c, float a)
+{
+ c.a = a;
+ return c;
+}
+
+NVGcolor nvgLerpRGBA(NVGcolor c0, NVGcolor c1, float u)
+{
+ int i;
+ float oneminu;
+ NVGcolor cint = {{{0}}};
+
+ u = nvg__clampf(u, 0.0f, 1.0f);
+ oneminu = 1.0f - u;
+ for( i = 0; i <4; i++ )
+ {
+ cint.rgba[i] = c0.rgba[i] * oneminu + c1.rgba[i] * u;
+ }
+
+ return cint;
+}
+
+NVGcolor nvgHSL(float h, float s, float l)
+{
+ return nvgHSLA(h,s,l,255);
+}
+
+static float nvg__hue(float h, float m1, float m2)
+{
+ if (h < 0) h += 1;
+ if (h > 1) h -= 1;
+ if (h < 1.0f/6.0f)
+ return m1 + (m2 - m1) * h * 6.0f;
+ else if (h < 3.0f/6.0f)
+ return m2;
+ else if (h < 4.0f/6.0f)
+ return m1 + (m2 - m1) * (2.0f/3.0f - h) * 6.0f;
+ return m1;
+}
+
+NVGcolor nvgHSLA(float h, float s, float l, unsigned char a)
+{
+ float m1, m2;
+ NVGcolor col;
+ h = nvg__modf(h, 1.0f);
+ if (h < 0.0f) h += 1.0f;
+ s = nvg__clampf(s, 0.0f, 1.0f);
+ l = nvg__clampf(l, 0.0f, 1.0f);
+ m2 = l <= 0.5f ? (l * (1 + s)) : (l + s - l * s);
+ m1 = 2 * l - m2;
+ col.r = nvg__clampf(nvg__hue(h + 1.0f/3.0f, m1, m2), 0.0f, 1.0f);
+ col.g = nvg__clampf(nvg__hue(h, m1, m2), 0.0f, 1.0f);
+ col.b = nvg__clampf(nvg__hue(h - 1.0f/3.0f, m1, m2), 0.0f, 1.0f);
+ col.a = a/255.0f;
+ return col;
+}
+
+void nvgTransformIdentity(float* t)
+{
+ t[0] = 1.0f; t[1] = 0.0f;
+ t[2] = 0.0f; t[3] = 1.0f;
+ t[4] = 0.0f; t[5] = 0.0f;
+}
+
+void nvgTransformTranslate(float* t, float tx, float ty)
+{
+ t[0] = 1.0f; t[1] = 0.0f;
+ t[2] = 0.0f; t[3] = 1.0f;
+ t[4] = tx; t[5] = ty;
+}
+
+void nvgTransformScale(float* t, float sx, float sy)
+{
+ t[0] = sx; t[1] = 0.0f;
+ t[2] = 0.0f; t[3] = sy;
+ t[4] = 0.0f; t[5] = 0.0f;
+}
+
+void nvgTransformRotate(float* t, float a)
+{
+ float cs = nvg__cosf(a), sn = nvg__sinf(a);
+ t[0] = cs; t[1] = sn;
+ t[2] = -sn; t[3] = cs;
+ t[4] = 0.0f; t[5] = 0.0f;
+}
+
+void nvgTransformSkewX(float* t, float a)
+{
+ t[0] = 1.0f; t[1] = 0.0f;
+ t[2] = nvg__tanf(a); t[3] = 1.0f;
+ t[4] = 0.0f; t[5] = 0.0f;
+}
+
+void nvgTransformSkewY(float* t, float a)
+{
+ t[0] = 1.0f; t[1] = nvg__tanf(a);
+ t[2] = 0.0f; t[3] = 1.0f;
+ t[4] = 0.0f; t[5] = 0.0f;
+}
+
+void nvgTransformMultiply(float* t, const float* s)
+{
+ float t0 = t[0] * s[0] + t[1] * s[2];
+ float t2 = t[2] * s[0] + t[3] * s[2];
+ float t4 = t[4] * s[0] + t[5] * s[2] + s[4];
+ t[1] = t[0] * s[1] + t[1] * s[3];
+ t[3] = t[2] * s[1] + t[3] * s[3];
+ t[5] = t[4] * s[1] + t[5] * s[3] + s[5];
+ t[0] = t0;
+ t[2] = t2;
+ t[4] = t4;
+}
+
+void nvgTransformPremultiply(float* t, const float* s)
+{
+ float s2[6];
+ memcpy(s2, s, sizeof(float)*6);
+ nvgTransformMultiply(s2, t);
+ memcpy(t, s2, sizeof(float)*6);
+}
+
+int nvgTransformInverse(float* inv, const float* t)
+{
+ double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1];
+ if (det > -1e-6 && det < 1e-6) {
+ nvgTransformIdentity(inv);
+ return 0;
+ }
+ invdet = 1.0 / det;
+ inv[0] = (float)(t[3] * invdet);
+ inv[2] = (float)(-t[2] * invdet);
+ inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet);
+ inv[1] = (float)(-t[1] * invdet);
+ inv[3] = (float)(t[0] * invdet);
+ inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet);
+ return 1;
+}
+
+void nvgTransformPoint(float* dx, float* dy, const float* t, float sx, float sy)
+{
+ *dx = sx*t[0] + sy*t[2] + t[4];
+ *dy = sx*t[1] + sy*t[3] + t[5];
+}
+
+float nvgDegToRad(float deg)
+{
+ return deg / 180.0f * NVG_PI;
+}
+
+float nvgRadToDeg(float rad)
+{
+ return rad / NVG_PI * 180.0f;
+}
+
+static void nvg__setPaintColor(NVGpaint* p, NVGcolor color)
+{
+ memset(p, 0, sizeof(*p));
+ nvgTransformIdentity(p->xform);
+ p->radius = 0.0f;
+ p->feather = 1.0f;
+ p->innerColor = color;
+ p->outerColor = color;
+}
+
+
+// State handling
+void nvgSave(NVGcontext* ctx)
+{
+ if (ctx->nstates >= NVG_MAX_STATES)
+ return;
+ if (ctx->nstates > 0)
+ memcpy(&ctx->states[ctx->nstates], &ctx->states[ctx->nstates-1], sizeof(NVGstate));
+ ctx->nstates++;
+}
+
+void nvgRestore(NVGcontext* ctx)
+{
+ if (ctx->nstates <= 1)
+ return;
+ ctx->nstates--;
+}
+
+void nvgReset(NVGcontext* ctx)
+{
+ NVGstate* state = nvg__getState(ctx);
+ memset(state, 0, sizeof(*state));
+
+ nvg__setPaintColor(&state->fill, nvgRGBA(255,255,255,255));
+ nvg__setPaintColor(&state->stroke, nvgRGBA(0,0,0,255));
+ state->compositeOperation = nvg__compositeOperationState(NVG_SOURCE_OVER);
+ state->shapeAntiAlias = 1;
+ state->strokeWidth = 1.0f;
+ state->miterLimit = 10.0f;
+ state->lineCap = NVG_BUTT;
+ state->lineJoin = NVG_MITER;
+ state->alpha = 1.0f;
+ nvgTransformIdentity(state->xform);
+
+ state->scissor.extent[0] = -1.0f;
+ state->scissor.extent[1] = -1.0f;
+
+ state->fontSize = 16.0f;
+ state->letterSpacing = 0.0f;
+ state->lineHeight = 1.0f;
+ state->fontBlur = 0.0f;
+ state->textAlign = NVG_ALIGN_LEFT | NVG_ALIGN_BASELINE;
+ state->fontId = 0;
+}
+
+// State setting
+void nvgShapeAntiAlias(NVGcontext* ctx, int enabled)
+{
+ NVGstate* state = nvg__getState(ctx);
+ state->shapeAntiAlias = enabled;
+}
+
+void nvgStrokeWidth(NVGcontext* ctx, float width)
+{
+ NVGstate* state = nvg__getState(ctx);
+ state->strokeWidth = width;
+}
+
+void nvgMiterLimit(NVGcontext* ctx, float limit)
+{
+ NVGstate* state = nvg__getState(ctx);
+ state->miterLimit = limit;
+}
+
+void nvgLineCap(NVGcontext* ctx, int cap)
+{
+ NVGstate* state = nvg__getState(ctx);
+ state->lineCap = cap;
+}
+
+void nvgLineJoin(NVGcontext* ctx, int join)
+{
+ NVGstate* state = nvg__getState(ctx);
+ state->lineJoin = join;
+}
+
+void nvgGlobalAlpha(NVGcontext* ctx, float alpha)
+{
+ NVGstate* state = nvg__getState(ctx);
+ state->alpha = alpha;
+}
+
+void nvgTransform(NVGcontext* ctx, float a, float b, float c, float d, float e, float f)
+{
+ NVGstate* state = nvg__getState(ctx);
+ float t[6] = { a, b, c, d, e, f };
+ nvgTransformPremultiply(state->xform, t);
+}
+
+void nvgResetTransform(NVGcontext* ctx)
+{
+ NVGstate* state = nvg__getState(ctx);
+ nvgTransformIdentity(state->xform);
+}
+
+void nvgTranslate(NVGcontext* ctx, float x, float y)
+{
+ NVGstate* state = nvg__getState(ctx);
+ float t[6];
+ nvgTransformTranslate(t, x,y);
+ nvgTransformPremultiply(state->xform, t);
+}
+
+void nvgRotate(NVGcontext* ctx, float angle)
+{
+ NVGstate* state = nvg__getState(ctx);
+ float t[6];
+ nvgTransformRotate(t, angle);
+ nvgTransformPremultiply(state->xform, t);
+}
+
+void nvgSkewX(NVGcontext* ctx, float angle)
+{
+ NVGstate* state = nvg__getState(ctx);
+ float t[6];
+ nvgTransformSkewX(t, angle);
+ nvgTransformPremultiply(state->xform, t);
+}
+
+void nvgSkewY(NVGcontext* ctx, float angle)
+{
+ NVGstate* state = nvg__getState(ctx);
+ float t[6];
+ nvgTransformSkewY(t, angle);
+ nvgTransformPremultiply(state->xform, t);
+}
+
+void nvgScale(NVGcontext* ctx, float x, float y)
+{
+ NVGstate* state = nvg__getState(ctx);
+ float t[6];
+ nvgTransformScale(t, x,y);
+ nvgTransformPremultiply(state->xform, t);
+}
+
+void nvgCurrentTransform(NVGcontext* ctx, float* xform)
+{
+ NVGstate* state = nvg__getState(ctx);
+ if (xform == NULL) return;
+ memcpy(xform, state->xform, sizeof(float)*6);
+}
+
+void nvgStrokeColor(NVGcontext* ctx, NVGcolor color)
+{
+ NVGstate* state = nvg__getState(ctx);
+ nvg__setPaintColor(&state->stroke, color);
+}
+
+void nvgStrokePaint(NVGcontext* ctx, NVGpaint paint)
+{
+ NVGstate* state = nvg__getState(ctx);
+ state->stroke = paint;
+ nvgTransformMultiply(state->stroke.xform, state->xform);
+}
+
+void nvgFillColor(NVGcontext* ctx, NVGcolor color)
+{
+ NVGstate* state = nvg__getState(ctx);
+ nvg__setPaintColor(&state->fill, color);
+}
+
+void nvgFillPaint(NVGcontext* ctx, NVGpaint paint)
+{
+ NVGstate* state = nvg__getState(ctx);
+ state->fill = paint;
+ nvgTransformMultiply(state->fill.xform, state->xform);
+}
+
+int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags)
+{
+ int w, h, n, image;
+ unsigned char* img;
+ stbi_set_unpremultiply_on_load(1);
+ stbi_convert_iphone_png_to_rgb(1);
+ img = stbi_load(filename, &w, &h, &n, 4);
+ if (img == NULL) {
+// printf("Failed to load %s - %s\n", filename, stbi_failure_reason());
+ return 0;
+ }
+ image = nvgCreateImageRGBA(ctx, w, h, imageFlags, img);
+ stbi_image_free(img);
+ return image;
+}
+
+int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int ndata)
+{
+ int w, h, n, image;
+ unsigned char* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4);
+ if (img == NULL) {
+// printf("Failed to load %s - %s\n", filename, stbi_failure_reason());
+ return 0;
+ }
+ image = nvgCreateImageRGBA(ctx, w, h, imageFlags, img);
+ stbi_image_free(img);
+ return image;
+}
+
+int nvgCreateImageRGBA(NVGcontext* ctx, int w, int h, int imageFlags, const unsigned char* data)
+{
+ return ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_RGBA, w, h, imageFlags, data);
+}
+
+void nvgUpdateImage(NVGcontext* ctx, int image, const unsigned char* data)
+{
+ int w, h;
+ ctx->params.renderGetTextureSize(ctx->params.userPtr, image, &w, &h);
+ ctx->params.renderUpdateTexture(ctx->params.userPtr, image, 0,0, w,h, data);
+}
+
+void nvgImageSize(NVGcontext* ctx, int image, int* w, int* h)
+{
+ ctx->params.renderGetTextureSize(ctx->params.userPtr, image, w, h);
+}
+
+void nvgDeleteImage(NVGcontext* ctx, int image)
+{
+ ctx->params.renderDeleteTexture(ctx->params.userPtr, image);
+}
+
+NVGpaint nvgLinearGradient(NVGcontext* ctx,
+ float sx, float sy, float ex, float ey,
+ NVGcolor icol, NVGcolor ocol)
+{
+ NVGpaint p;
+ float dx, dy, d;
+ const float large = 1e5;
+ NVG_NOTUSED(ctx);
+ memset(&p, 0, sizeof(p));
+
+ // Calculate transform aligned to the line
+ dx = ex - sx;
+ dy = ey - sy;
+ d = sqrtf(dx*dx + dy*dy);
+ if (d > 0.0001f) {
+ dx /= d;
+ dy /= d;
+ } else {
+ dx = 0;
+ dy = 1;
+ }
+
+ p.xform[0] = dy; p.xform[1] = -dx;
+ p.xform[2] = dx; p.xform[3] = dy;
+ p.xform[4] = sx - dx*large; p.xform[5] = sy - dy*large;
+
+ p.extent[0] = large;
+ p.extent[1] = large + d*0.5f;
+
+ p.radius = 0.0f;
+
+ p.feather = nvg__maxf(1.0f, d);
+
+ p.innerColor = icol;
+ p.outerColor = ocol;
+
+ return p;
+}
+
+NVGpaint nvgRadialGradient(NVGcontext* ctx,
+ float cx, float cy, float inr, float outr,
+ NVGcolor icol, NVGcolor ocol)
+{
+ NVGpaint p;
+ float r = (inr+outr)*0.5f;
+ float f = (outr-inr);
+ NVG_NOTUSED(ctx);
+ memset(&p, 0, sizeof(p));
+
+ nvgTransformIdentity(p.xform);
+ p.xform[4] = cx;
+ p.xform[5] = cy;
+
+ p.extent[0] = r;
+ p.extent[1] = r;
+
+ p.radius = r;
+
+ p.feather = nvg__maxf(1.0f, f);
+
+ p.innerColor = icol;
+ p.outerColor = ocol;
+
+ return p;
+}
+
+NVGpaint nvgBoxGradient(NVGcontext* ctx,
+ float x, float y, float w, float h, float r, float f,
+ NVGcolor icol, NVGcolor ocol)
+{
+ NVGpaint p;
+ NVG_NOTUSED(ctx);
+ memset(&p, 0, sizeof(p));
+
+ nvgTransformIdentity(p.xform);
+ p.xform[4] = x+w*0.5f;
+ p.xform[5] = y+h*0.5f;
+
+ p.extent[0] = w*0.5f;
+ p.extent[1] = h*0.5f;
+
+ p.radius = r;
+
+ p.feather = nvg__maxf(1.0f, f);
+
+ p.innerColor = icol;
+ p.outerColor = ocol;
+
+ return p;
+}
+
+
+NVGpaint nvgImagePattern(NVGcontext* ctx,
+ float cx, float cy, float w, float h, float angle,
+ int image, float alpha)
+{
+ NVGpaint p;
+ NVG_NOTUSED(ctx);
+ memset(&p, 0, sizeof(p));
+
+ nvgTransformRotate(p.xform, angle);
+ p.xform[4] = cx;
+ p.xform[5] = cy;
+
+ p.extent[0] = w;
+ p.extent[1] = h;
+
+ p.image = image;
+
+ p.innerColor = p.outerColor = nvgRGBAf(1,1,1,alpha);
+
+ return p;
+}
+
+// Scissoring
+void nvgScissor(NVGcontext* ctx, float x, float y, float w, float h)
+{
+ NVGstate* state = nvg__getState(ctx);
+
+ w = nvg__maxf(0.0f, w);
+ h = nvg__maxf(0.0f, h);
+
+ nvgTransformIdentity(state->scissor.xform);
+ state->scissor.xform[4] = x+w*0.5f;
+ state->scissor.xform[5] = y+h*0.5f;
+ nvgTransformMultiply(state->scissor.xform, state->xform);
+
+ state->scissor.extent[0] = w*0.5f;
+ state->scissor.extent[1] = h*0.5f;
+}
+
+static void nvg__isectRects(float* dst,
+ float ax, float ay, float aw, float ah,
+ float bx, float by, float bw, float bh)
+{
+ float minx = nvg__maxf(ax, bx);
+ float miny = nvg__maxf(ay, by);
+ float maxx = nvg__minf(ax+aw, bx+bw);
+ float maxy = nvg__minf(ay+ah, by+bh);
+ dst[0] = minx;
+ dst[1] = miny;
+ dst[2] = nvg__maxf(0.0f, maxx - minx);
+ dst[3] = nvg__maxf(0.0f, maxy - miny);
+}
+
+void nvgIntersectScissor(NVGcontext* ctx, float x, float y, float w, float h)
+{
+ NVGstate* state = nvg__getState(ctx);
+ float pxform[6], invxorm[6];
+ float rect[4];
+ float ex, ey, tex, tey;
+
+ // If no previous scissor has been set, set the scissor as current scissor.
+ if (state->scissor.extent[0] < 0) {
+ nvgScissor(ctx, x, y, w, h);
+ return;
+ }
+
+ // Transform the current scissor rect into current transform space.
+ // If there is difference in rotation, this will be approximation.
+ memcpy(pxform, state->scissor.xform, sizeof(float)*6);
+ ex = state->scissor.extent[0];
+ ey = state->scissor.extent[1];
+ nvgTransformInverse(invxorm, state->xform);
+ nvgTransformMultiply(pxform, invxorm);
+ tex = ex*nvg__absf(pxform[0]) + ey*nvg__absf(pxform[2]);
+ tey = ex*nvg__absf(pxform[1]) + ey*nvg__absf(pxform[3]);
+
+ // Intersect rects.
+ nvg__isectRects(rect, pxform[4]-tex,pxform[5]-tey,tex*2,tey*2, x,y,w,h);
+
+ nvgScissor(ctx, rect[0], rect[1], rect[2], rect[3]);
+}
+
+void nvgResetScissor(NVGcontext* ctx)
+{
+ NVGstate* state = nvg__getState(ctx);
+ memset(state->scissor.xform, 0, sizeof(state->scissor.xform));
+ state->scissor.extent[0] = -1.0f;
+ state->scissor.extent[1] = -1.0f;
+}
+
+// Global composite operation.
+void nvgGlobalCompositeOperation(NVGcontext* ctx, int op)
+{
+ NVGstate* state = nvg__getState(ctx);
+ state->compositeOperation = nvg__compositeOperationState(op);
+}
+
+void nvgGlobalCompositeBlendFunc(NVGcontext* ctx, int sfactor, int dfactor)
+{
+ nvgGlobalCompositeBlendFuncSeparate(ctx, sfactor, dfactor, sfactor, dfactor);
+}
+
+void nvgGlobalCompositeBlendFuncSeparate(NVGcontext* ctx, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha)
+{
+ NVGcompositeOperationState op;
+ op.srcRGB = srcRGB;
+ op.dstRGB = dstRGB;
+ op.srcAlpha = srcAlpha;
+ op.dstAlpha = dstAlpha;
+
+ NVGstate* state = nvg__getState(ctx);
+ state->compositeOperation = op;
+}
+
+static int nvg__ptEquals(float x1, float y1, float x2, float y2, float tol)
+{
+ float dx = x2 - x1;
+ float dy = y2 - y1;
+ return dx*dx + dy*dy < tol*tol;
+}
+
+static float nvg__distPtSeg(float x, float y, float px, float py, float qx, float qy)
+{
+ float pqx, pqy, dx, dy, d, t;
+ pqx = qx-px;
+ pqy = qy-py;
+ dx = x-px;
+ dy = y-py;
+ d = pqx*pqx + pqy*pqy;
+ t = pqx*dx + pqy*dy;
+ if (d > 0) t /= d;
+ if (t < 0) t = 0;
+ else if (t > 1) t = 1;
+ dx = px + t*pqx - x;
+ dy = py + t*pqy - y;
+ return dx*dx + dy*dy;
+}
+
+static void nvg__appendCommands(NVGcontext* ctx, float* vals, int nvals)
+{
+ NVGstate* state = nvg__getState(ctx);
+ int i;
+
+ if (ctx->ncommands+nvals > ctx->ccommands) {
+ float* commands;
+ int ccommands = ctx->ncommands+nvals + ctx->ccommands/2;
+ commands = (float*)realloc(ctx->commands, sizeof(float)*ccommands);
+ if (commands == NULL) return;
+ ctx->commands = commands;
+ ctx->ccommands = ccommands;
+ }
+
+ if ((int)vals[0] != NVG_CLOSE && (int)vals[0] != NVG_WINDING) {
+ ctx->commandx = vals[nvals-2];
+ ctx->commandy = vals[nvals-1];
+ }
+
+ // transform commands
+ i = 0;
+ while (i < nvals) {
+ int cmd = (int)vals[i];
+ switch (cmd) {
+ case NVG_MOVETO:
+ nvgTransformPoint(&vals[i+1],&vals[i+2], state->xform, vals[i+1],vals[i+2]);
+ i += 3;
+ break;
+ case NVG_LINETO:
+ nvgTransformPoint(&vals[i+1],&vals[i+2], state->xform, vals[i+1],vals[i+2]);
+ i += 3;
+ break;
+ case NVG_BEZIERTO:
+ nvgTransformPoint(&vals[i+1],&vals[i+2], state->xform, vals[i+1],vals[i+2]);
+ nvgTransformPoint(&vals[i+3],&vals[i+4], state->xform, vals[i+3],vals[i+4]);
+ nvgTransformPoint(&vals[i+5],&vals[i+6], state->xform, vals[i+5],vals[i+6]);
+ i += 7;
+ break;
+ case NVG_CLOSE:
+ i++;
+ break;
+ case NVG_WINDING:
+ i += 2;
+ break;
+ default:
+ i++;
+ }
+ }
+
+ memcpy(&ctx->commands[ctx->ncommands], vals, nvals*sizeof(float));
+
+ ctx->ncommands += nvals;
+}
+
+
+static void nvg__clearPathCache(NVGcontext* ctx)
+{
+ ctx->cache->npoints = 0;
+ ctx->cache->npaths = 0;
+}
+
+static NVGpath* nvg__lastPath(NVGcontext* ctx)
+{
+ if (ctx->cache->npaths > 0)
+ return &ctx->cache->paths[ctx->cache->npaths-1];
+ return NULL;
+}
+
+static void nvg__addPath(NVGcontext* ctx)
+{
+ NVGpath* path;
+ if (ctx->cache->npaths+1 > ctx->cache->cpaths) {
+ NVGpath* paths;
+ int cpaths = ctx->cache->npaths+1 + ctx->cache->cpaths/2;
+ paths = (NVGpath*)realloc(ctx->cache->paths, sizeof(NVGpath)*cpaths);
+ if (paths == NULL) return;
+ ctx->cache->paths = paths;
+ ctx->cache->cpaths = cpaths;
+ }
+ path = &ctx->cache->paths[ctx->cache->npaths];
+ memset(path, 0, sizeof(*path));
+ path->first = ctx->cache->npoints;
+ path->winding = NVG_CCW;
+
+ ctx->cache->npaths++;
+}
+
+static NVGpoint* nvg__lastPoint(NVGcontext* ctx)
+{
+ if (ctx->cache->npoints > 0)
+ return &ctx->cache->points[ctx->cache->npoints-1];
+ return NULL;
+}
+
+static void nvg__addPoint(NVGcontext* ctx, float x, float y, int flags)
+{
+ NVGpath* path = nvg__lastPath(ctx);
+ NVGpoint* pt;
+ if (path == NULL) return;
+
+ if (path->count > 0 && ctx->cache->npoints > 0) {
+ pt = nvg__lastPoint(ctx);
+ if (nvg__ptEquals(pt->x,pt->y, x,y, ctx->distTol)) {
+ pt->flags |= flags;
+ return;
+ }
+ }
+
+ if (ctx->cache->npoints+1 > ctx->cache->cpoints) {
+ NVGpoint* points;
+ int cpoints = ctx->cache->npoints+1 + ctx->cache->cpoints/2;
+ points = (NVGpoint*)realloc(ctx->cache->points, sizeof(NVGpoint)*cpoints);
+ if (points == NULL) return;
+ ctx->cache->points = points;
+ ctx->cache->cpoints = cpoints;
+ }
+
+ pt = &ctx->cache->points[ctx->cache->npoints];
+ memset(pt, 0, sizeof(*pt));
+ pt->x = x;
+ pt->y = y;
+ pt->flags = (unsigned char)flags;
+
+ ctx->cache->npoints++;
+ path->count++;
+}
+
+static void nvg__closePath(NVGcontext* ctx)
+{
+ NVGpath* path = nvg__lastPath(ctx);
+ if (path == NULL) return;
+ path->closed = 1;
+}
+
+static void nvg__pathWinding(NVGcontext* ctx, int winding)
+{
+ NVGpath* path = nvg__lastPath(ctx);
+ if (path == NULL) return;
+ path->winding = winding;
+}
+
+static float nvg__getAverageScale(float *t)
+{
+ float sx = sqrtf(t[0]*t[0] + t[2]*t[2]);
+ float sy = sqrtf(t[1]*t[1] + t[3]*t[3]);
+ return (sx + sy) * 0.5f;
+}
+
+static NVGvertex* nvg__allocTempVerts(NVGcontext* ctx, int nverts)
+{
+ if (nverts > ctx->cache->cverts) {
+ NVGvertex* verts;
+ int cverts = (nverts + 0xff) & ~0xff; // Round up to prevent allocations when things change just slightly.
+ verts = (NVGvertex*)realloc(ctx->cache->verts, sizeof(NVGvertex)*cverts);
+ if (verts == NULL) return NULL;
+ ctx->cache->verts = verts;
+ ctx->cache->cverts = cverts;
+ }
+
+ return ctx->cache->verts;
+}
+
+static float nvg__triarea2(float ax, float ay, float bx, float by, float cx, float cy)
+{
+ float abx = bx - ax;
+ float aby = by - ay;
+ float acx = cx - ax;
+ float acy = cy - ay;
+ return acx*aby - abx*acy;
+}
+
+static float nvg__polyArea(NVGpoint* pts, int npts)
+{
+ int i;
+ float area = 0;
+ for (i = 2; i < npts; i++) {
+ NVGpoint* a = &pts[0];
+ NVGpoint* b = &pts[i-1];
+ NVGpoint* c = &pts[i];
+ area += nvg__triarea2(a->x,a->y, b->x,b->y, c->x,c->y);
+ }
+ return area * 0.5f;
+}
+
+static void nvg__polyReverse(NVGpoint* pts, int npts)
+{
+ NVGpoint tmp;
+ int i = 0, j = npts-1;
+ while (i < j) {
+ tmp = pts[i];
+ pts[i] = pts[j];
+ pts[j] = tmp;
+ i++;
+ j--;
+ }
+}
+
+
+static void nvg__vset(NVGvertex* vtx, float x, float y, float u, float v)
+{
+ vtx->x = x;
+ vtx->y = y;
+ vtx->u = u;
+ vtx->v = v;
+}
+
+static void nvg__tesselateBezier(NVGcontext* ctx,
+ float x1, float y1, float x2, float y2,
+ float x3, float y3, float x4, float y4,
+ int level, int type)
+{
+ float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234;
+ float dx,dy,d2,d3;
+
+ if (level > 10) return;
+
+ x12 = (x1+x2)*0.5f;
+ y12 = (y1+y2)*0.5f;
+ x23 = (x2+x3)*0.5f;
+ y23 = (y2+y3)*0.5f;
+ x34 = (x3+x4)*0.5f;
+ y34 = (y3+y4)*0.5f;
+ x123 = (x12+x23)*0.5f;
+ y123 = (y12+y23)*0.5f;
+
+ dx = x4 - x1;
+ dy = y4 - y1;
+ d2 = nvg__absf(((x2 - x4) * dy - (y2 - y4) * dx));
+ d3 = nvg__absf(((x3 - x4) * dy - (y3 - y4) * dx));
+
+ if ((d2 + d3)*(d2 + d3) < ctx->tessTol * (dx*dx + dy*dy)) {
+ nvg__addPoint(ctx, x4, y4, type);
+ return;
+ }
+
+/* if (nvg__absf(x1+x3-x2-x2) + nvg__absf(y1+y3-y2-y2) + nvg__absf(x2+x4-x3-x3) + nvg__absf(y2+y4-y3-y3) < ctx->tessTol) {
+ nvg__addPoint(ctx, x4, y4, type);
+ return;
+ }*/
+
+ x234 = (x23+x34)*0.5f;
+ y234 = (y23+y34)*0.5f;
+ x1234 = (x123+x234)*0.5f;
+ y1234 = (y123+y234)*0.5f;
+
+ nvg__tesselateBezier(ctx, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, 0);
+ nvg__tesselateBezier(ctx, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type);
+}
+
+static void nvg__flattenPaths(NVGcontext* ctx)
+{
+ NVGpathCache* cache = ctx->cache;
+// NVGstate* state = nvg__getState(ctx);
+ NVGpoint* last;
+ NVGpoint* p0;
+ NVGpoint* p1;
+ NVGpoint* pts;
+ NVGpath* path;
+ int i, j;
+ float* cp1;
+ float* cp2;
+ float* p;
+ float area;
+
+ if (cache->npaths > 0)
+ return;
+
+ // Flatten
+ i = 0;
+ while (i < ctx->ncommands) {
+ int cmd = (int)ctx->commands[i];
+ switch (cmd) {
+ case NVG_MOVETO:
+ nvg__addPath(ctx);
+ p = &ctx->commands[i+1];
+ nvg__addPoint(ctx, p[0], p[1], NVG_PT_CORNER);
+ i += 3;
+ break;
+ case NVG_LINETO:
+ p = &ctx->commands[i+1];
+ nvg__addPoint(ctx, p[0], p[1], NVG_PT_CORNER);
+ i += 3;
+ break;
+ case NVG_BEZIERTO:
+ last = nvg__lastPoint(ctx);
+ if (last != NULL) {
+ cp1 = &ctx->commands[i+1];
+ cp2 = &ctx->commands[i+3];
+ p = &ctx->commands[i+5];
+ nvg__tesselateBezier(ctx, last->x,last->y, cp1[0],cp1[1], cp2[0],cp2[1], p[0],p[1], 0, NVG_PT_CORNER);
+ }
+ i += 7;
+ break;
+ case NVG_CLOSE:
+ nvg__closePath(ctx);
+ i++;
+ break;
+ case NVG_WINDING:
+ nvg__pathWinding(ctx, (int)ctx->commands[i+1]);
+ i += 2;
+ break;
+ default:
+ i++;
+ }
+ }
+
+ cache->bounds[0] = cache->bounds[1] = 1e6f;
+ cache->bounds[2] = cache->bounds[3] = -1e6f;
+
+ // Calculate the direction and length of line segments.
+ for (j = 0; j < cache->npaths; j++) {
+ path = &cache->paths[j];
+ pts = &cache->points[path->first];
+
+ // If the first and last points are the same, remove the last, mark as closed path.
+ p0 = &pts[path->count-1];
+ p1 = &pts[0];
+ if (nvg__ptEquals(p0->x,p0->y, p1->x,p1->y, ctx->distTol)) {
+ path->count--;
+ p0 = &pts[path->count-1];
+ path->closed = 1;
+ }
+
+ // Enforce winding.
+ if (path->count > 2) {
+ area = nvg__polyArea(pts, path->count);
+ if (path->winding == NVG_CCW && area < 0.0f)
+ nvg__polyReverse(pts, path->count);
+ if (path->winding == NVG_CW && area > 0.0f)
+ nvg__polyReverse(pts, path->count);
+ }
+
+ for(i = 0; i < path->count; i++) {
+ // Calculate segment direction and length
+ p0->dx = p1->x - p0->x;
+ p0->dy = p1->y - p0->y;
+ p0->len = nvg__normalize(&p0->dx, &p0->dy);
+ // Update bounds
+ cache->bounds[0] = nvg__minf(cache->bounds[0], p0->x);
+ cache->bounds[1] = nvg__minf(cache->bounds[1], p0->y);
+ cache->bounds[2] = nvg__maxf(cache->bounds[2], p0->x);
+ cache->bounds[3] = nvg__maxf(cache->bounds[3], p0->y);
+ // Advance
+ p0 = p1++;
+ }
+ }
+}
+
+static int nvg__curveDivs(float r, float arc, float tol)
+{
+ float da = acosf(r / (r + tol)) * 2.0f;
+ return nvg__maxi(2, (int)ceilf(arc / da));
+}
+
+static void nvg__chooseBevel(int bevel, NVGpoint* p0, NVGpoint* p1, float w,
+ float* x0, float* y0, float* x1, float* y1)
+{
+ if (bevel) {
+ *x0 = p1->x + p0->dy * w;
+ *y0 = p1->y - p0->dx * w;
+ *x1 = p1->x + p1->dy * w;
+ *y1 = p1->y - p1->dx * w;
+ } else {
+ *x0 = p1->x + p1->dmx * w;
+ *y0 = p1->y + p1->dmy * w;
+ *x1 = p1->x + p1->dmx * w;
+ *y1 = p1->y + p1->dmy * w;
+ }
+}
+
+static NVGvertex* nvg__roundJoin(NVGvertex* dst, NVGpoint* p0, NVGpoint* p1,
+ float lw, float rw, float lu, float ru, int ncap,
+ float fringe)
+{
+ int i, n;
+ float dlx0 = p0->dy;
+ float dly0 = -p0->dx;
+ float dlx1 = p1->dy;
+ float dly1 = -p1->dx;
+ NVG_NOTUSED(fringe);
+
+ if (p1->flags & NVG_PT_LEFT) {
+ float lx0,ly0,lx1,ly1,a0,a1;
+ nvg__chooseBevel(p1->flags & NVG_PR_INNERBEVEL, p0, p1, lw, &lx0,&ly0, &lx1,&ly1);
+ a0 = atan2f(-dly0, -dlx0);
+ a1 = atan2f(-dly1, -dlx1);
+ if (a1 > a0) a1 -= NVG_PI*2;
+
+ nvg__vset(dst, lx0, ly0, lu,1); dst++;
+ nvg__vset(dst, p1->x - dlx0*rw, p1->y - dly0*rw, ru,1); dst++;
+
+ n = nvg__clampi((int)ceilf(((a0 - a1) / NVG_PI) * ncap), 2, ncap);
+ for (i = 0; i < n; i++) {
+ float u = i/(float)(n-1);
+ float a = a0 + u*(a1-a0);
+ float rx = p1->x + cosf(a) * rw;
+ float ry = p1->y + sinf(a) * rw;
+ nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++;
+ nvg__vset(dst, rx, ry, ru,1); dst++;
+ }
+
+ nvg__vset(dst, lx1, ly1, lu,1); dst++;
+ nvg__vset(dst, p1->x - dlx1*rw, p1->y - dly1*rw, ru,1); dst++;
+
+ } else {
+ float rx0,ry0,rx1,ry1,a0,a1;
+ nvg__chooseBevel(p1->flags & NVG_PR_INNERBEVEL, p0, p1, -rw, &rx0,&ry0, &rx1,&ry1);
+ a0 = atan2f(dly0, dlx0);
+ a1 = atan2f(dly1, dlx1);
+ if (a1 < a0) a1 += NVG_PI*2;
+
+ nvg__vset(dst, p1->x + dlx0*rw, p1->y + dly0*rw, lu,1); dst++;
+ nvg__vset(dst, rx0, ry0, ru,1); dst++;
+
+ n = nvg__clampi((int)ceilf(((a1 - a0) / NVG_PI) * ncap), 2, ncap);
+ for (i = 0; i < n; i++) {
+ float u = i/(float)(n-1);
+ float a = a0 + u*(a1-a0);
+ float lx = p1->x + cosf(a) * lw;
+ float ly = p1->y + sinf(a) * lw;
+ nvg__vset(dst, lx, ly, lu,1); dst++;
+ nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++;
+ }
+
+ nvg__vset(dst, p1->x + dlx1*rw, p1->y + dly1*rw, lu,1); dst++;
+ nvg__vset(dst, rx1, ry1, ru,1); dst++;
+
+ }
+ return dst;
+}
+
+static NVGvertex* nvg__bevelJoin(NVGvertex* dst, NVGpoint* p0, NVGpoint* p1,
+ float lw, float rw, float lu, float ru, float fringe)
+{
+ float rx0,ry0,rx1,ry1;
+ float lx0,ly0,lx1,ly1;
+ float dlx0 = p0->dy;
+ float dly0 = -p0->dx;
+ float dlx1 = p1->dy;
+ float dly1 = -p1->dx;
+ NVG_NOTUSED(fringe);
+
+ if (p1->flags & NVG_PT_LEFT) {
+ nvg__chooseBevel(p1->flags & NVG_PR_INNERBEVEL, p0, p1, lw, &lx0,&ly0, &lx1,&ly1);
+
+ nvg__vset(dst, lx0, ly0, lu,1); dst++;
+ nvg__vset(dst, p1->x - dlx0*rw, p1->y - dly0*rw, ru,1); dst++;
+
+ if (p1->flags & NVG_PT_BEVEL) {
+ nvg__vset(dst, lx0, ly0, lu,1); dst++;
+ nvg__vset(dst, p1->x - dlx0*rw, p1->y - dly0*rw, ru,1); dst++;
+
+ nvg__vset(dst, lx1, ly1, lu,1); dst++;
+ nvg__vset(dst, p1->x - dlx1*rw, p1->y - dly1*rw, ru,1); dst++;
+ } else {
+ rx0 = p1->x - p1->dmx * rw;
+ ry0 = p1->y - p1->dmy * rw;
+
+ nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++;
+ nvg__vset(dst, p1->x - dlx0*rw, p1->y - dly0*rw, ru,1); dst++;
+
+ nvg__vset(dst, rx0, ry0, ru,1); dst++;
+ nvg__vset(dst, rx0, ry0, ru,1); dst++;
+
+ nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++;
+ nvg__vset(dst, p1->x - dlx1*rw, p1->y - dly1*rw, ru,1); dst++;
+ }
+
+ nvg__vset(dst, lx1, ly1, lu,1); dst++;
+ nvg__vset(dst, p1->x - dlx1*rw, p1->y - dly1*rw, ru,1); dst++;
+
+ } else {
+ nvg__chooseBevel(p1->flags & NVG_PR_INNERBEVEL, p0, p1, -rw, &rx0,&ry0, &rx1,&ry1);
+
+ nvg__vset(dst, p1->x + dlx0*lw, p1->y + dly0*lw, lu,1); dst++;
+ nvg__vset(dst, rx0, ry0, ru,1); dst++;
+
+ if (p1->flags & NVG_PT_BEVEL) {
+ nvg__vset(dst, p1->x + dlx0*lw, p1->y + dly0*lw, lu,1); dst++;
+ nvg__vset(dst, rx0, ry0, ru,1); dst++;
+
+ nvg__vset(dst, p1->x + dlx1*lw, p1->y + dly1*lw, lu,1); dst++;
+ nvg__vset(dst, rx1, ry1, ru,1); dst++;
+ } else {
+ lx0 = p1->x + p1->dmx * lw;
+ ly0 = p1->y + p1->dmy * lw;
+
+ nvg__vset(dst, p1->x + dlx0*lw, p1->y + dly0*lw, lu,1); dst++;
+ nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++;
+
+ nvg__vset(dst, lx0, ly0, lu,1); dst++;
+ nvg__vset(dst, lx0, ly0, lu,1); dst++;
+
+ nvg__vset(dst, p1->x + dlx1*lw, p1->y + dly1*lw, lu,1); dst++;
+ nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++;
+ }
+
+ nvg__vset(dst, p1->x + dlx1*lw, p1->y + dly1*lw, lu,1); dst++;
+ nvg__vset(dst, rx1, ry1, ru,1); dst++;
+ }
+
+ return dst;
+}
+
+static NVGvertex* nvg__buttCapStart(NVGvertex* dst, NVGpoint* p,
+ float dx, float dy, float w, float d,
+ float aa, float u0, float u1)
+{
+ float px = p->x - dx*d;
+ float py = p->y - dy*d;
+ float dlx = dy;
+ float dly = -dx;
+ nvg__vset(dst, px + dlx*w - dx*aa, py + dly*w - dy*aa, u0,0); dst++;
+ nvg__vset(dst, px - dlx*w - dx*aa, py - dly*w - dy*aa, u1,0); dst++;
+ nvg__vset(dst, px + dlx*w, py + dly*w, u0,1); dst++;
+ nvg__vset(dst, px - dlx*w, py - dly*w, u1,1); dst++;
+ return dst;
+}
+
+static NVGvertex* nvg__buttCapEnd(NVGvertex* dst, NVGpoint* p,
+ float dx, float dy, float w, float d,
+ float aa, float u0, float u1)
+{
+ float px = p->x + dx*d;
+ float py = p->y + dy*d;
+ float dlx = dy;
+ float dly = -dx;
+ nvg__vset(dst, px + dlx*w, py + dly*w, u0,1); dst++;
+ nvg__vset(dst, px - dlx*w, py - dly*w, u1,1); dst++;
+ nvg__vset(dst, px + dlx*w + dx*aa, py + dly*w + dy*aa, u0,0); dst++;
+ nvg__vset(dst, px - dlx*w + dx*aa, py - dly*w + dy*aa, u1,0); dst++;
+ return dst;
+}
+
+
+static NVGvertex* nvg__roundCapStart(NVGvertex* dst, NVGpoint* p,
+ float dx, float dy, float w, int ncap,
+ float aa, float u0, float u1)
+{
+ int i;
+ float px = p->x;
+ float py = p->y;
+ float dlx = dy;
+ float dly = -dx;
+ NVG_NOTUSED(aa);
+ for (i = 0; i < ncap; i++) {
+ float a = i/(float)(ncap-1)*NVG_PI;
+ float ax = cosf(a) * w, ay = sinf(a) * w;
+ nvg__vset(dst, px - dlx*ax - dx*ay, py - dly*ax - dy*ay, u0,1); dst++;
+ nvg__vset(dst, px, py, 0.5f,1); dst++;
+ }
+ nvg__vset(dst, px + dlx*w, py + dly*w, u0,1); dst++;
+ nvg__vset(dst, px - dlx*w, py - dly*w, u1,1); dst++;
+ return dst;
+}
+
+static NVGvertex* nvg__roundCapEnd(NVGvertex* dst, NVGpoint* p,
+ float dx, float dy, float w, int ncap,
+ float aa, float u0, float u1)
+{
+ int i;
+ float px = p->x;
+ float py = p->y;
+ float dlx = dy;
+ float dly = -dx;
+ NVG_NOTUSED(aa);
+ nvg__vset(dst, px + dlx*w, py + dly*w, u0,1); dst++;
+ nvg__vset(dst, px - dlx*w, py - dly*w, u1,1); dst++;
+ for (i = 0; i < ncap; i++) {
+ float a = i/(float)(ncap-1)*NVG_PI;
+ float ax = cosf(a) * w, ay = sinf(a) * w;
+ nvg__vset(dst, px, py, 0.5f,1); dst++;
+ nvg__vset(dst, px - dlx*ax + dx*ay, py - dly*ax + dy*ay, u0,1); dst++;
+ }
+ return dst;
+}
+
+
+static void nvg__calculateJoins(NVGcontext* ctx, float w, int lineJoin, float miterLimit)
+{
+ NVGpathCache* cache = ctx->cache;
+ int i, j;
+ float iw = 0.0f;
+
+ if (w > 0.0f) iw = 1.0f / w;
+
+ // Calculate which joins needs extra vertices to append, and gather vertex count.
+ for (i = 0; i < cache->npaths; i++) {
+ NVGpath* path = &cache->paths[i];
+ NVGpoint* pts = &cache->points[path->first];
+ NVGpoint* p0 = &pts[path->count-1];
+ NVGpoint* p1 = &pts[0];
+ int nleft = 0;
+
+ path->nbevel = 0;
+
+ for (j = 0; j < path->count; j++) {
+ float dlx0, dly0, dlx1, dly1, dmr2, cross, limit;
+ dlx0 = p0->dy;
+ dly0 = -p0->dx;
+ dlx1 = p1->dy;
+ dly1 = -p1->dx;
+ // Calculate extrusions
+ p1->dmx = (dlx0 + dlx1) * 0.5f;
+ p1->dmy = (dly0 + dly1) * 0.5f;
+ dmr2 = p1->dmx*p1->dmx + p1->dmy*p1->dmy;
+ if (dmr2 > 0.000001f) {
+ float scale = 1.0f / dmr2;
+ if (scale > 600.0f) {
+ scale = 600.0f;
+ }
+ p1->dmx *= scale;
+ p1->dmy *= scale;
+ }
+
+ // Clear flags, but keep the corner.
+ p1->flags = (p1->flags & NVG_PT_CORNER) ? NVG_PT_CORNER : 0;
+
+ // Keep track of left turns.
+ cross = p1->dx * p0->dy - p0->dx * p1->dy;
+ if (cross > 0.0f) {
+ nleft++;
+ p1->flags |= NVG_PT_LEFT;
+ }
+
+ // Calculate if we should use bevel or miter for inner join.
+ limit = nvg__maxf(1.01f, nvg__minf(p0->len, p1->len) * iw);
+ if ((dmr2 * limit*limit) < 1.0f)
+ p1->flags |= NVG_PR_INNERBEVEL;
+
+ // Check to see if the corner needs to be beveled.
+ if (p1->flags & NVG_PT_CORNER) {
+ if ((dmr2 * miterLimit*miterLimit) < 1.0f || lineJoin == NVG_BEVEL || lineJoin == NVG_ROUND) {
+ p1->flags |= NVG_PT_BEVEL;
+ }
+ }
+
+ if ((p1->flags & (NVG_PT_BEVEL | NVG_PR_INNERBEVEL)) != 0)
+ path->nbevel++;
+
+ p0 = p1++;
+ }
+
+ path->convex = (nleft == path->count) ? 1 : 0;
+ }
+}
+
+
+static int nvg__expandStroke(NVGcontext* ctx, float w, float fringe, int lineCap, int lineJoin, float miterLimit)
+{
+ NVGpathCache* cache = ctx->cache;
+ NVGvertex* verts;
+ NVGvertex* dst;
+ int cverts, i, j;
+ float aa = fringe;//ctx->fringeWidth;
+ float u0 = 0.0f, u1 = 1.0f;
+ int ncap = nvg__curveDivs(w, NVG_PI, ctx->tessTol); // Calculate divisions per half circle.
+
+ w += aa * 0.5f;
+
+ // Disable the gradient used for antialiasing when antialiasing is not used.
+ if (aa == 0.0f) {
+ u0 = 0.5f;
+ u1 = 0.5f;
+ }
+
+ nvg__calculateJoins(ctx, w, lineJoin, miterLimit);
+
+ // Calculate max vertex usage.
+ cverts = 0;
+ for (i = 0; i < cache->npaths; i++) {
+ NVGpath* path = &cache->paths[i];
+ int loop = (path->closed == 0) ? 0 : 1;
+ if (lineJoin == NVG_ROUND)
+ cverts += (path->count + path->nbevel*(ncap+2) + 1) * 2; // plus one for loop
+ else
+ cverts += (path->count + path->nbevel*5 + 1) * 2; // plus one for loop
+ if (loop == 0) {
+ // space for caps
+ if (lineCap == NVG_ROUND) {
+ cverts += (ncap*2 + 2)*2;
+ } else {
+ cverts += (3+3)*2;
+ }
+ }
+ }
+
+ verts = nvg__allocTempVerts(ctx, cverts);
+ if (verts == NULL) return 0;
+
+ for (i = 0; i < cache->npaths; i++) {
+ NVGpath* path = &cache->paths[i];
+ NVGpoint* pts = &cache->points[path->first];
+ NVGpoint* p0;
+ NVGpoint* p1;
+ int s, e, loop;
+ float dx, dy;
+
+ path->fill = 0;
+ path->nfill = 0;
+
+ // Calculate fringe or stroke
+ loop = (path->closed == 0) ? 0 : 1;
+ dst = verts;
+ path->stroke = dst;
+
+ if (loop) {
+ // Looping
+ p0 = &pts[path->count-1];
+ p1 = &pts[0];
+ s = 0;
+ e = path->count;
+ } else {
+ // Add cap
+ p0 = &pts[0];
+ p1 = &pts[1];
+ s = 1;
+ e = path->count-1;
+ }
+
+ if (loop == 0) {
+ // Add cap
+ dx = p1->x - p0->x;
+ dy = p1->y - p0->y;
+ nvg__normalize(&dx, &dy);
+ if (lineCap == NVG_BUTT)
+ dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa, u0, u1);
+ else if (lineCap == NVG_BUTT || lineCap == NVG_SQUARE)
+ dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa, u0, u1);
+ else if (lineCap == NVG_ROUND)
+ dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa, u0, u1);
+ }
+
+ for (j = s; j < e; ++j) {
+ if ((p1->flags & (NVG_PT_BEVEL | NVG_PR_INNERBEVEL)) != 0) {
+ if (lineJoin == NVG_ROUND) {
+ dst = nvg__roundJoin(dst, p0, p1, w, w, u0, u1, ncap, aa);
+ } else {
+ dst = nvg__bevelJoin(dst, p0, p1, w, w, u0, u1, aa);
+ }
+ } else {
+ nvg__vset(dst, p1->x + (p1->dmx * w), p1->y + (p1->dmy * w), u0,1); dst++;
+ nvg__vset(dst, p1->x - (p1->dmx * w), p1->y - (p1->dmy * w), u1,1); dst++;
+ }
+ p0 = p1++;
+ }
+
+ if (loop) {
+ // Loop it
+ nvg__vset(dst, verts[0].x, verts[0].y, u0,1); dst++;
+ nvg__vset(dst, verts[1].x, verts[1].y, u1,1); dst++;
+ } else {
+ // Add cap
+ dx = p1->x - p0->x;
+ dy = p1->y - p0->y;
+ nvg__normalize(&dx, &dy);
+ if (lineCap == NVG_BUTT)
+ dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa, u0, u1);
+ else if (lineCap == NVG_BUTT || lineCap == NVG_SQUARE)
+ dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa, u0, u1);
+ else if (lineCap == NVG_ROUND)
+ dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa, u0, u1);
+ }
+
+ path->nstroke = (int)(dst - verts);
+
+ verts = dst;
+ }
+
+ return 1;
+}
+
+static int nvg__expandFill(NVGcontext* ctx, float w, int lineJoin, float miterLimit)
+{
+ NVGpathCache* cache = ctx->cache;
+ NVGvertex* verts;
+ NVGvertex* dst;
+ int cverts, convex, i, j;
+ float aa = ctx->fringeWidth;
+ int fringe = w > 0.0f;
+
+ nvg__calculateJoins(ctx, w, lineJoin, miterLimit);
+
+ // Calculate max vertex usage.
+ cverts = 0;
+ for (i = 0; i < cache->npaths; i++) {
+ NVGpath* path = &cache->paths[i];
+ cverts += path->count + path->nbevel + 1;
+ if (fringe)
+ cverts += (path->count + path->nbevel*5 + 1) * 2; // plus one for loop
+ }
+
+ verts = nvg__allocTempVerts(ctx, cverts);
+ if (verts == NULL) return 0;
+
+ convex = cache->npaths == 1 && cache->paths[0].convex;
+
+ for (i = 0; i < cache->npaths; i++) {
+ NVGpath* path = &cache->paths[i];
+ NVGpoint* pts = &cache->points[path->first];
+ NVGpoint* p0;
+ NVGpoint* p1;
+ float rw, lw, woff;
+ float ru, lu;
+
+ // Calculate shape vertices.
+ woff = 0.5f*aa;
+ dst = verts;
+ path->fill = dst;
+
+ if (fringe) {
+ // Looping
+ p0 = &pts[path->count-1];
+ p1 = &pts[0];
+ for (j = 0; j < path->count; ++j) {
+ if (p1->flags & NVG_PT_BEVEL) {
+ float dlx0 = p0->dy;
+ float dly0 = -p0->dx;
+ float dlx1 = p1->dy;
+ float dly1 = -p1->dx;
+ if (p1->flags & NVG_PT_LEFT) {
+ float lx = p1->x + p1->dmx * woff;
+ float ly = p1->y + p1->dmy * woff;
+ nvg__vset(dst, lx, ly, 0.5f,1); dst++;
+ } else {
+ float lx0 = p1->x + dlx0 * woff;
+ float ly0 = p1->y + dly0 * woff;
+ float lx1 = p1->x + dlx1 * woff;
+ float ly1 = p1->y + dly1 * woff;
+ nvg__vset(dst, lx0, ly0, 0.5f,1); dst++;
+ nvg__vset(dst, lx1, ly1, 0.5f,1); dst++;
+ }
+ } else {
+ nvg__vset(dst, p1->x + (p1->dmx * woff), p1->y + (p1->dmy * woff), 0.5f,1); dst++;
+ }
+ p0 = p1++;
+ }
+ } else {
+ for (j = 0; j < path->count; ++j) {
+ nvg__vset(dst, pts[j].x, pts[j].y, 0.5f,1);
+ dst++;
+ }
+ }
+
+ path->nfill = (int)(dst - verts);
+ verts = dst;
+
+ // Calculate fringe
+ if (fringe) {
+ lw = w + woff;
+ rw = w - woff;
+ lu = 0;
+ ru = 1;
+ dst = verts;
+ path->stroke = dst;
+
+ // Create only half a fringe for convex shapes so that
+ // the shape can be rendered without stenciling.
+ if (convex) {
+ lw = woff; // This should generate the same vertex as fill inset above.
+ lu = 0.5f; // Set outline fade at middle.
+ }
+
+ // Looping
+ p0 = &pts[path->count-1];
+ p1 = &pts[0];
+
+ for (j = 0; j < path->count; ++j) {
+ if ((p1->flags & (NVG_PT_BEVEL | NVG_PR_INNERBEVEL)) != 0) {
+ dst = nvg__bevelJoin(dst, p0, p1, lw, rw, lu, ru, ctx->fringeWidth);
+ } else {
+ nvg__vset(dst, p1->x + (p1->dmx * lw), p1->y + (p1->dmy * lw), lu,1); dst++;
+ nvg__vset(dst, p1->x - (p1->dmx * rw), p1->y - (p1->dmy * rw), ru,1); dst++;
+ }
+ p0 = p1++;
+ }
+
+ // Loop it
+ nvg__vset(dst, verts[0].x, verts[0].y, lu,1); dst++;
+ nvg__vset(dst, verts[1].x, verts[1].y, ru,1); dst++;
+
+ path->nstroke = (int)(dst - verts);
+ verts = dst;
+ } else {
+ path->stroke = NULL;
+ path->nstroke = 0;
+ }
+ }
+
+ return 1;
+}
+
+
+// Draw
+void nvgBeginPath(NVGcontext* ctx)
+{
+ ctx->ncommands = 0;
+ nvg__clearPathCache(ctx);
+}
+
+void nvgMoveTo(NVGcontext* ctx, float x, float y)
+{
+ float vals[] = { NVG_MOVETO, x, y };
+ nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals));
+}
+
+void nvgLineTo(NVGcontext* ctx, float x, float y)
+{
+ float vals[] = { NVG_LINETO, x, y };
+ nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals));
+}
+
+void nvgBezierTo(NVGcontext* ctx, float c1x, float c1y, float c2x, float c2y, float x, float y)
+{
+ float vals[] = { NVG_BEZIERTO, c1x, c1y, c2x, c2y, x, y };
+ nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals));
+}
+
+void nvgQuadTo(NVGcontext* ctx, float cx, float cy, float x, float y)
+{
+ float x0 = ctx->commandx;
+ float y0 = ctx->commandy;
+ float vals[] = { NVG_BEZIERTO,
+ x0 + 2.0f/3.0f*(cx - x0), y0 + 2.0f/3.0f*(cy - y0),
+ x + 2.0f/3.0f*(cx - x), y + 2.0f/3.0f*(cy - y),
+ x, y };
+ nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals));
+}
+
+void nvgArcTo(NVGcontext* ctx, float x1, float y1, float x2, float y2, float radius)
+{
+ float x0 = ctx->commandx;
+ float y0 = ctx->commandy;
+ float dx0,dy0, dx1,dy1, a, d, cx,cy, a0,a1;
+ int dir;
+
+ if (ctx->ncommands == 0) {
+ return;
+ }
+
+ // Handle degenerate cases.
+ if (nvg__ptEquals(x0,y0, x1,y1, ctx->distTol) ||
+ nvg__ptEquals(x1,y1, x2,y2, ctx->distTol) ||
+ nvg__distPtSeg(x1,y1, x0,y0, x2,y2) < ctx->distTol*ctx->distTol ||
+ radius < ctx->distTol) {
+ nvgLineTo(ctx, x1,y1);
+ return;
+ }
+
+ // Calculate tangential circle to lines (x0,y0)-(x1,y1) and (x1,y1)-(x2,y2).
+ dx0 = x0-x1;
+ dy0 = y0-y1;
+ dx1 = x2-x1;
+ dy1 = y2-y1;
+ nvg__normalize(&dx0,&dy0);
+ nvg__normalize(&dx1,&dy1);
+ a = nvg__acosf(dx0*dx1 + dy0*dy1);
+ d = radius / nvg__tanf(a/2.0f);
+
+// printf("a=%f° d=%f\n", a/NVG_PI*180.0f, d);
+
+ if (d > 10000.0f) {
+ nvgLineTo(ctx, x1,y1);
+ return;
+ }
+
+ if (nvg__cross(dx0,dy0, dx1,dy1) > 0.0f) {
+ cx = x1 + dx0*d + dy0*radius;
+ cy = y1 + dy0*d + -dx0*radius;
+ a0 = nvg__atan2f(dx0, -dy0);
+ a1 = nvg__atan2f(-dx1, dy1);
+ dir = NVG_CW;
+// printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f);
+ } else {
+ cx = x1 + dx0*d + -dy0*radius;
+ cy = y1 + dy0*d + dx0*radius;
+ a0 = nvg__atan2f(-dx0, dy0);
+ a1 = nvg__atan2f(dx1, -dy1);
+ dir = NVG_CCW;
+// printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f);
+ }
+
+ nvgArc(ctx, cx, cy, radius, a0, a1, dir);
+}
+
+void nvgClosePath(NVGcontext* ctx)
+{
+ float vals[] = { NVG_CLOSE };
+ nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals));
+}
+
+void nvgPathWinding(NVGcontext* ctx, int dir)
+{
+ float vals[] = { NVG_WINDING, (float)dir };
+ nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals));
+}
+
+void nvgArc(NVGcontext* ctx, float cx, float cy, float r, float a0, float a1, int dir)
+{
+ float a = 0, da = 0, hda = 0, kappa = 0;
+ float dx = 0, dy = 0, x = 0, y = 0, tanx = 0, tany = 0;
+ float px = 0, py = 0, ptanx = 0, ptany = 0;
+ float vals[3 + 5*7 + 100];
+ int i, ndivs, nvals;
+ int move = ctx->ncommands > 0 ? NVG_LINETO : NVG_MOVETO;
+
+ // Clamp angles
+ da = a1 - a0;
+ if (dir == NVG_CW) {
+ if (nvg__absf(da) >= NVG_PI*2) {
+ da = NVG_PI*2;
+ } else {
+ while (da < 0.0f) da += NVG_PI*2;
+ }
+ } else {
+ if (nvg__absf(da) >= NVG_PI*2) {
+ da = -NVG_PI*2;
+ } else {
+ while (da > 0.0f) da -= NVG_PI*2;
+ }
+ }
+
+ // Split arc into max 90 degree segments.
+ ndivs = nvg__maxi(1, nvg__mini((int)(nvg__absf(da) / (NVG_PI*0.5f) + 0.5f), 5));
+ hda = (da / (float)ndivs) / 2.0f;
+ kappa = nvg__absf(4.0f / 3.0f * (1.0f - nvg__cosf(hda)) / nvg__sinf(hda));
+
+ if (dir == NVG_CCW)
+ kappa = -kappa;
+
+ nvals = 0;
+ for (i = 0; i <= ndivs; i++) {
+ a = a0 + da * (i/(float)ndivs);
+ dx = nvg__cosf(a);
+ dy = nvg__sinf(a);
+ x = cx + dx*r;
+ y = cy + dy*r;
+ tanx = -dy*r*kappa;
+ tany = dx*r*kappa;
+
+ if (i == 0) {
+ vals[nvals++] = (float)move;
+ vals[nvals++] = x;
+ vals[nvals++] = y;
+ } else {
+ vals[nvals++] = NVG_BEZIERTO;
+ vals[nvals++] = px+ptanx;
+ vals[nvals++] = py+ptany;
+ vals[nvals++] = x-tanx;
+ vals[nvals++] = y-tany;
+ vals[nvals++] = x;
+ vals[nvals++] = y;
+ }
+ px = x;
+ py = y;
+ ptanx = tanx;
+ ptany = tany;
+ }
+
+ nvg__appendCommands(ctx, vals, nvals);
+}
+
+void nvgRect(NVGcontext* ctx, float x, float y, float w, float h)
+{
+ float vals[] = {
+ NVG_MOVETO, x,y,
+ NVG_LINETO, x,y+h,
+ NVG_LINETO, x+w,y+h,
+ NVG_LINETO, x+w,y,
+ NVG_CLOSE
+ };
+ nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals));
+}
+
+void nvgRoundedRect(NVGcontext* ctx, float x, float y, float w, float h, float r)
+{
+ nvgRoundedRectVarying(ctx, x, y, w, h, r, r, r, r);
+}
+
+void nvgRoundedRectVarying(NVGcontext* ctx, float x, float y, float w, float h, float radTopLeft, float radTopRight, float radBottomRight, float radBottomLeft)
+{
+ if(radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) {
+ nvgRect(ctx, x, y, w, h);
+ return;
+ } else {
+ float halfw = nvg__absf(w)*0.5f;
+ float halfh = nvg__absf(h)*0.5f;
+ float rxBL = nvg__minf(radBottomLeft, halfw) * nvg__signf(w), ryBL = nvg__minf(radBottomLeft, halfh) * nvg__signf(h);
+ float rxBR = nvg__minf(radBottomRight, halfw) * nvg__signf(w), ryBR = nvg__minf(radBottomRight, halfh) * nvg__signf(h);
+ float rxTR = nvg__minf(radTopRight, halfw) * nvg__signf(w), ryTR = nvg__minf(radTopRight, halfh) * nvg__signf(h);
+ float rxTL = nvg__minf(radTopLeft, halfw) * nvg__signf(w), ryTL = nvg__minf(radTopLeft, halfh) * nvg__signf(h);
+ float vals[] = {
+ NVG_MOVETO, x, y + ryTL,
+ NVG_LINETO, x, y + h - ryBL,
+ NVG_BEZIERTO, x, y + h - ryBL*(1 - NVG_KAPPA90), x + rxBL*(1 - NVG_KAPPA90), y + h, x + rxBL, y + h,
+ NVG_LINETO, x + w - rxBR, y + h,
+ NVG_BEZIERTO, x + w - rxBR*(1 - NVG_KAPPA90), y + h, x + w, y + h - ryBR*(1 - NVG_KAPPA90), x + w, y + h - ryBR,
+ NVG_LINETO, x + w, y + ryTR,
+ NVG_BEZIERTO, x + w, y + ryTR*(1 - NVG_KAPPA90), x + w - rxTR*(1 - NVG_KAPPA90), y, x + w - rxTR, y,
+ NVG_LINETO, x + rxTL, y,
+ NVG_BEZIERTO, x + rxTL*(1 - NVG_KAPPA90), y, x, y + ryTL*(1 - NVG_KAPPA90), x, y + ryTL,
+ NVG_CLOSE
+ };
+ nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals));
+ }
+}
+
+void nvgEllipse(NVGcontext* ctx, float cx, float cy, float rx, float ry)
+{
+ float vals[] = {
+ NVG_MOVETO, cx-rx, cy,
+ NVG_BEZIERTO, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry,
+ NVG_BEZIERTO, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy,
+ NVG_BEZIERTO, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry,
+ NVG_BEZIERTO, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy,
+ NVG_CLOSE
+ };
+ nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals));
+}
+
+void nvgCircle(NVGcontext* ctx, float cx, float cy, float r)
+{
+ nvgEllipse(ctx, cx,cy, r,r);
+}
+
+void nvgDebugDumpPathCache(NVGcontext* ctx)
+{
+ const NVGpath* path;
+ int i, j;
+
+ printf("Dumping %d cached paths\n", ctx->cache->npaths);
+ for (i = 0; i < ctx->cache->npaths; i++) {
+ path = &ctx->cache->paths[i];
+ printf(" - Path %d\n", i);
+ if (path->nfill) {
+ printf(" - fill: %d\n", path->nfill);
+ for (j = 0; j < path->nfill; j++)
+ printf("%f\t%f\n", path->fill[j].x, path->fill[j].y);
+ }
+ if (path->nstroke) {
+ printf(" - stroke: %d\n", path->nstroke);
+ for (j = 0; j < path->nstroke; j++)
+ printf("%f\t%f\n", path->stroke[j].x, path->stroke[j].y);
+ }
+ }
+}
+
+void nvgFill(NVGcontext* ctx)
+{
+ NVGstate* state = nvg__getState(ctx);
+ const NVGpath* path;
+ NVGpaint fillPaint = state->fill;
+ int i;
+
+ nvg__flattenPaths(ctx);
+ if (ctx->params.edgeAntiAlias && state->shapeAntiAlias)
+ nvg__expandFill(ctx, ctx->fringeWidth, NVG_MITER, 2.4f);
+ else
+ nvg__expandFill(ctx, 0.0f, NVG_MITER, 2.4f);
+
+ // Apply global alpha
+ fillPaint.innerColor.a *= state->alpha;
+ fillPaint.outerColor.a *= state->alpha;
+
+ ctx->params.renderFill(ctx->params.userPtr, &fillPaint, state->compositeOperation, &state->scissor, ctx->fringeWidth,
+ ctx->cache->bounds, ctx->cache->paths, ctx->cache->npaths);
+
+ // Count triangles
+ for (i = 0; i < ctx->cache->npaths; i++) {
+ path = &ctx->cache->paths[i];
+ ctx->fillTriCount += path->nfill-2;
+ ctx->fillTriCount += path->nstroke-2;
+ ctx->drawCallCount += 2;
+ }
+}
+
+void nvgStroke(NVGcontext* ctx)
+{
+ NVGstate* state = nvg__getState(ctx);
+ float scale = nvg__getAverageScale(state->xform);
+ float strokeWidth = nvg__clampf(state->strokeWidth * scale, 0.0f, 200.0f);
+ NVGpaint strokePaint = state->stroke;
+ const NVGpath* path;
+ int i;
+
+
+ if (strokeWidth < ctx->fringeWidth) {
+ // If the stroke width is less than pixel size, use alpha to emulate coverage.
+ // Since coverage is area, scale by alpha*alpha.
+ float alpha = nvg__clampf(strokeWidth / ctx->fringeWidth, 0.0f, 1.0f);
+ strokePaint.innerColor.a *= alpha*alpha;
+ strokePaint.outerColor.a *= alpha*alpha;
+ strokeWidth = ctx->fringeWidth;
+ }
+
+ // Apply global alpha
+ strokePaint.innerColor.a *= state->alpha;
+ strokePaint.outerColor.a *= state->alpha;
+
+ nvg__flattenPaths(ctx);
+
+ if (ctx->params.edgeAntiAlias && state->shapeAntiAlias)
+ nvg__expandStroke(ctx, strokeWidth*0.5f, ctx->fringeWidth, state->lineCap, state->lineJoin, state->miterLimit);
+ else
+ nvg__expandStroke(ctx, strokeWidth*0.5f, 0.0f, state->lineCap, state->lineJoin, state->miterLimit);
+
+ ctx->params.renderStroke(ctx->params.userPtr, &strokePaint, state->compositeOperation, &state->scissor, ctx->fringeWidth,
+ strokeWidth, ctx->cache->paths, ctx->cache->npaths);
+
+ // Count triangles
+ for (i = 0; i < ctx->cache->npaths; i++) {
+ path = &ctx->cache->paths[i];
+ ctx->strokeTriCount += path->nstroke-2;
+ ctx->drawCallCount++;
+ }
+}
+
+// Add fonts
+int nvgCreateFont(NVGcontext* ctx, const char* name, const char* path)
+{
+ return fonsAddFont(ctx->fs, name, path);
+}
+
+int nvgCreateFontMem(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData)
+{
+ return fonsAddFontMem(ctx->fs, name, data, ndata, freeData);
+}
+
+int nvgFindFont(NVGcontext* ctx, const char* name)
+{
+ if (name == NULL) return -1;
+ return fonsGetFontByName(ctx->fs, name);
+}
+
+
+int nvgAddFallbackFontId(NVGcontext* ctx, int baseFont, int fallbackFont)
+{
+ if(baseFont == -1 || fallbackFont == -1) return 0;
+ return fonsAddFallbackFont(ctx->fs, baseFont, fallbackFont);
+}
+
+int nvgAddFallbackFont(NVGcontext* ctx, const char* baseFont, const char* fallbackFont)
+{
+ return nvgAddFallbackFontId(ctx, nvgFindFont(ctx, baseFont), nvgFindFont(ctx, fallbackFont));
+}
+
+// State setting
+void nvgFontSize(NVGcontext* ctx, float size)
+{
+ NVGstate* state = nvg__getState(ctx);
+ state->fontSize = size;
+}
+
+void nvgFontBlur(NVGcontext* ctx, float blur)
+{
+ NVGstate* state = nvg__getState(ctx);
+ state->fontBlur = blur;
+}
+
+void nvgTextLetterSpacing(NVGcontext* ctx, float spacing)
+{
+ NVGstate* state = nvg__getState(ctx);
+ state->letterSpacing = spacing;
+}
+
+void nvgTextLineHeight(NVGcontext* ctx, float lineHeight)
+{
+ NVGstate* state = nvg__getState(ctx);
+ state->lineHeight = lineHeight;
+}
+
+void nvgTextAlign(NVGcontext* ctx, int align)
+{
+ NVGstate* state = nvg__getState(ctx);
+ state->textAlign = align;
+}
+
+void nvgFontFaceId(NVGcontext* ctx, int font)
+{
+ NVGstate* state = nvg__getState(ctx);
+ state->fontId = font;
+}
+
+void nvgFontFace(NVGcontext* ctx, const char* font)
+{
+ NVGstate* state = nvg__getState(ctx);
+ state->fontId = fonsGetFontByName(ctx->fs, font);
+}
+
+static float nvg__quantize(float a, float d)
+{
+ return ((int)(a / d + 0.5f)) * d;
+}
+
+static float nvg__getFontScale(NVGstate* state)
+{
+ return nvg__minf(nvg__quantize(nvg__getAverageScale(state->xform), 0.01f), 4.0f);
+}
+
+static void nvg__flushTextTexture(NVGcontext* ctx)
+{
+ int dirty[4];
+
+ if (fonsValidateTexture(ctx->fs, dirty)) {
+ int fontImage = ctx->fontImages[ctx->fontImageIdx];
+ // Update texture
+ if (fontImage != 0) {
+ int iw, ih;
+ const unsigned char* data = fonsGetTextureData(ctx->fs, &iw, &ih);
+ int x = dirty[0];
+ int y = dirty[1];
+ int w = dirty[2] - dirty[0];
+ int h = dirty[3] - dirty[1];
+ ctx->params.renderUpdateTexture(ctx->params.userPtr, fontImage, x,y, w,h, data);
+ }
+ }
+}
+
+static int nvg__allocTextAtlas(NVGcontext* ctx)
+{
+ int iw, ih;
+ nvg__flushTextTexture(ctx);
+ if (ctx->fontImageIdx >= NVG_MAX_FONTIMAGES-1)
+ return 0;
+ // if next fontImage already have a texture
+ if (ctx->fontImages[ctx->fontImageIdx+1] != 0)
+ nvgImageSize(ctx, ctx->fontImages[ctx->fontImageIdx+1], &iw, &ih);
+ else { // calculate the new font image size and create it.
+ nvgImageSize(ctx, ctx->fontImages[ctx->fontImageIdx], &iw, &ih);
+ if (iw > ih)
+ ih *= 2;
+ else
+ iw *= 2;
+ if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE)
+ iw = ih = NVG_MAX_FONTIMAGE_SIZE;
+ ctx->fontImages[ctx->fontImageIdx+1] = ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_ALPHA, iw, ih, 0, NULL);
+ }
+ ++ctx->fontImageIdx;
+ fonsResetAtlas(ctx->fs, iw, ih);
+ return 1;
+}
+
+static void nvg__renderText(NVGcontext* ctx, NVGvertex* verts, int nverts)
+{
+ NVGstate* state = nvg__getState(ctx);
+ NVGpaint paint = state->fill;
+
+ // Render triangles.
+ paint.image = ctx->fontImages[ctx->fontImageIdx];
+
+ // Apply global alpha
+ paint.innerColor.a *= state->alpha;
+ paint.outerColor.a *= state->alpha;
+
+ ctx->params.renderTriangles(ctx->params.userPtr, &paint, state->compositeOperation, &state->scissor, verts, nverts);
+
+ ctx->drawCallCount++;
+ ctx->textTriCount += nverts/3;
+}
+
+float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* end)
+{
+ NVGstate* state = nvg__getState(ctx);
+ FONStextIter iter, prevIter;
+ FONSquad q;
+ NVGvertex* verts;
+ float scale = nvg__getFontScale(state) * ctx->devicePxRatio;
+ float invscale = 1.0f / scale;
+ int cverts = 0;
+ int nverts = 0;
+
+ if (end == NULL)
+ end = string + strlen(string);
+
+ if (state->fontId == FONS_INVALID) return x;
+
+ fonsSetSize(ctx->fs, state->fontSize*scale);
+ fonsSetSpacing(ctx->fs, state->letterSpacing*scale);
+ fonsSetBlur(ctx->fs, state->fontBlur*scale);
+ fonsSetAlign(ctx->fs, state->textAlign);
+ fonsSetFont(ctx->fs, state->fontId);
+
+ cverts = nvg__maxi(2, (int)(end - string)) * 6; // conservative estimate.
+ verts = nvg__allocTempVerts(ctx, cverts);
+ if (verts == NULL) return x;
+
+ fonsTextIterInit(ctx->fs, &iter, x*scale, y*scale, string, end, FONS_GLYPH_BITMAP_REQUIRED);
+ prevIter = iter;
+ while (fonsTextIterNext(ctx->fs, &iter, &q)) {
+ float c[4*2];
+ if (iter.prevGlyphIndex == -1) { // can not retrieve glyph?
+ if (nverts != 0) {
+ nvg__renderText(ctx, verts, nverts);
+ nverts = 0;
+ }
+ if (!nvg__allocTextAtlas(ctx))
+ break; // no memory :(
+ iter = prevIter;
+ fonsTextIterNext(ctx->fs, &iter, &q); // try again
+ if (iter.prevGlyphIndex == -1) // still can not find glyph?
+ break;
+ }
+ prevIter = iter;
+ // Transform corners.
+ nvgTransformPoint(&c[0],&c[1], state->xform, q.x0*invscale, q.y0*invscale);
+ nvgTransformPoint(&c[2],&c[3], state->xform, q.x1*invscale, q.y0*invscale);
+ nvgTransformPoint(&c[4],&c[5], state->xform, q.x1*invscale, q.y1*invscale);
+ nvgTransformPoint(&c[6],&c[7], state->xform, q.x0*invscale, q.y1*invscale);
+ // Create triangles
+ if (nverts+6 <= cverts) {
+ nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); nverts++;
+ nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); nverts++;
+ nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); nverts++;
+ nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); nverts++;
+ nvg__vset(&verts[nverts], c[6], c[7], q.s0, q.t1); nverts++;
+ nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); nverts++;
+ }
+ }
+
+ // TODO: add back-end bit to do this just once per frame.
+ nvg__flushTextTexture(ctx);
+
+ nvg__renderText(ctx, verts, nverts);
+
+ return iter.nextx / scale;
+}
+
+void nvgTextBox(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end)
+{
+ NVGstate* state = nvg__getState(ctx);
+ NVGtextRow rows[2];
+ int nrows = 0, i;
+ int oldAlign = state->textAlign;
+ int haling = state->textAlign & (NVG_ALIGN_LEFT | NVG_ALIGN_CENTER | NVG_ALIGN_RIGHT);
+ int valign = state->textAlign & (NVG_ALIGN_TOP | NVG_ALIGN_MIDDLE | NVG_ALIGN_BOTTOM | NVG_ALIGN_BASELINE);
+ float lineh = 0;
+
+ if (state->fontId == FONS_INVALID) return;
+
+ nvgTextMetrics(ctx, NULL, NULL, &lineh);
+
+ state->textAlign = NVG_ALIGN_LEFT | valign;
+
+ while ((nrows = nvgTextBreakLines(ctx, string, end, breakRowWidth, rows, 2))) {
+ for (i = 0; i < nrows; i++) {
+ NVGtextRow* row = &rows[i];
+ if (haling & NVG_ALIGN_LEFT)
+ nvgText(ctx, x, y, row->start, row->end);
+ else if (haling & NVG_ALIGN_CENTER)
+ nvgText(ctx, x + breakRowWidth*0.5f - row->width*0.5f, y, row->start, row->end);
+ else if (haling & NVG_ALIGN_RIGHT)
+ nvgText(ctx, x + breakRowWidth - row->width, y, row->start, row->end);
+ y += lineh * state->lineHeight;
+ }
+ string = rows[nrows-1].next;
+ }
+
+ state->textAlign = oldAlign;
+}
+
+int nvgTextGlyphPositions(NVGcontext* ctx, float x, float y, const char* string, const char* end, NVGglyphPosition* positions, int maxPositions)
+{
+ NVGstate* state = nvg__getState(ctx);
+ float scale = nvg__getFontScale(state) * ctx->devicePxRatio;
+ float invscale = 1.0f / scale;
+ FONStextIter iter, prevIter;
+ FONSquad q;
+ int npos = 0;
+
+ if (state->fontId == FONS_INVALID) return 0;
+
+ if (end == NULL)
+ end = string + strlen(string);
+
+ if (string == end)
+ return 0;
+
+ fonsSetSize(ctx->fs, state->fontSize*scale);
+ fonsSetSpacing(ctx->fs, state->letterSpacing*scale);
+ fonsSetBlur(ctx->fs, state->fontBlur*scale);
+ fonsSetAlign(ctx->fs, state->textAlign);
+ fonsSetFont(ctx->fs, state->fontId);
+
+ fonsTextIterInit(ctx->fs, &iter, x*scale, y*scale, string, end, FONS_GLYPH_BITMAP_OPTIONAL);
+ prevIter = iter;
+ while (fonsTextIterNext(ctx->fs, &iter, &q)) {
+ if (iter.prevGlyphIndex < 0 && nvg__allocTextAtlas(ctx)) { // can not retrieve glyph?
+ iter = prevIter;
+ fonsTextIterNext(ctx->fs, &iter, &q); // try again
+ }
+ prevIter = iter;
+ positions[npos].str = iter.str;
+ positions[npos].x = iter.x * invscale;
+ positions[npos].minx = nvg__minf(iter.x, q.x0) * invscale;
+ positions[npos].maxx = nvg__maxf(iter.nextx, q.x1) * invscale;
+ npos++;
+ if (npos >= maxPositions)
+ break;
+ }
+
+ return npos;
+}
+
+enum NVGcodepointType {
+ NVG_SPACE,
+ NVG_NEWLINE,
+ NVG_CHAR,
+ NVG_CJK_CHAR,
+};
+
+int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, float breakRowWidth, NVGtextRow* rows, int maxRows)
+{
+ NVGstate* state = nvg__getState(ctx);
+ float scale = nvg__getFontScale(state) * ctx->devicePxRatio;
+ float invscale = 1.0f / scale;
+ FONStextIter iter, prevIter;
+ FONSquad q;
+ int nrows = 0;
+ float rowStartX = 0;
+ float rowWidth = 0;
+ float rowMinX = 0;
+ float rowMaxX = 0;
+ const char* rowStart = NULL;
+ const char* rowEnd = NULL;
+ const char* wordStart = NULL;
+ float wordStartX = 0;
+ float wordMinX = 0;
+ const char* breakEnd = NULL;
+ float breakWidth = 0;
+ float breakMaxX = 0;
+ int type = NVG_SPACE, ptype = NVG_SPACE;
+ unsigned int pcodepoint = 0;
+
+ if (maxRows == 0) return 0;
+ if (state->fontId == FONS_INVALID) return 0;
+
+ if (end == NULL)
+ end = string + strlen(string);
+
+ if (string == end) return 0;
+
+ fonsSetSize(ctx->fs, state->fontSize*scale);
+ fonsSetSpacing(ctx->fs, state->letterSpacing*scale);
+ fonsSetBlur(ctx->fs, state->fontBlur*scale);
+ fonsSetAlign(ctx->fs, state->textAlign);
+ fonsSetFont(ctx->fs, state->fontId);
+
+ breakRowWidth *= scale;
+
+ fonsTextIterInit(ctx->fs, &iter, 0, 0, string, end, FONS_GLYPH_BITMAP_OPTIONAL);
+ prevIter = iter;
+ while (fonsTextIterNext(ctx->fs, &iter, &q)) {
+ if (iter.prevGlyphIndex < 0 && nvg__allocTextAtlas(ctx)) { // can not retrieve glyph?
+ iter = prevIter;
+ fonsTextIterNext(ctx->fs, &iter, &q); // try again
+ }
+ prevIter = iter;
+ switch (iter.codepoint) {
+ case 9: // \t
+ case 11: // \v
+ case 12: // \f
+ case 32: // space
+ case 0x00a0: // NBSP
+ type = NVG_SPACE;
+ break;
+ case 10: // \n
+ type = pcodepoint == 13 ? NVG_SPACE : NVG_NEWLINE;
+ break;
+ case 13: // \r
+ type = pcodepoint == 10 ? NVG_SPACE : NVG_NEWLINE;
+ break;
+ case 0x0085: // NEL
+ type = NVG_NEWLINE;
+ break;
+ default:
+ if ((iter.codepoint >= 0x4E00 && iter.codepoint <= 0x9FFF) ||
+ (iter.codepoint >= 0x3000 && iter.codepoint <= 0x30FF) ||
+ (iter.codepoint >= 0xFF00 && iter.codepoint <= 0xFFEF) ||
+ (iter.codepoint >= 0x1100 && iter.codepoint <= 0x11FF) ||
+ (iter.codepoint >= 0x3130 && iter.codepoint <= 0x318F) ||
+ (iter.codepoint >= 0xAC00 && iter.codepoint <= 0xD7AF))
+ type = NVG_CJK_CHAR;
+ else
+ type = NVG_CHAR;
+ break;
+ }
+
+ if (type == NVG_NEWLINE) {
+ // Always handle new lines.
+ rows[nrows].start = rowStart != NULL ? rowStart : iter.str;
+ rows[nrows].end = rowEnd != NULL ? rowEnd : iter.str;
+ rows[nrows].width = rowWidth * invscale;
+ rows[nrows].minx = rowMinX * invscale;
+ rows[nrows].maxx = rowMaxX * invscale;
+ rows[nrows].next = iter.next;
+ nrows++;
+ if (nrows >= maxRows)
+ return nrows;
+ // Set null break point
+ breakEnd = rowStart;
+ breakWidth = 0.0;
+ breakMaxX = 0.0;
+ // Indicate to skip the white space at the beginning of the row.
+ rowStart = NULL;
+ rowEnd = NULL;
+ rowWidth = 0;
+ rowMinX = rowMaxX = 0;
+ } else {
+ if (rowStart == NULL) {
+ // Skip white space until the beginning of the line
+ if (type == NVG_CHAR || type == NVG_CJK_CHAR) {
+ // The current char is the row so far
+ rowStartX = iter.x;
+ rowStart = iter.str;
+ rowEnd = iter.next;
+ rowWidth = iter.nextx - rowStartX; // q.x1 - rowStartX;
+ rowMinX = q.x0 - rowStartX;
+ rowMaxX = q.x1 - rowStartX;
+ wordStart = iter.str;
+ wordStartX = iter.x;
+ wordMinX = q.x0 - rowStartX;
+ // Set null break point
+ breakEnd = rowStart;
+ breakWidth = 0.0;
+ breakMaxX = 0.0;
+ }
+ } else {
+ float nextWidth = iter.nextx - rowStartX;
+
+ // track last non-white space character
+ if (type == NVG_CHAR || type == NVG_CJK_CHAR) {
+ rowEnd = iter.next;
+ rowWidth = iter.nextx - rowStartX;
+ rowMaxX = q.x1 - rowStartX;
+ }
+ // track last end of a word
+ if (((ptype == NVG_CHAR || ptype == NVG_CJK_CHAR) && type == NVG_SPACE) || type == NVG_CJK_CHAR) {
+ breakEnd = iter.str;
+ breakWidth = rowWidth;
+ breakMaxX = rowMaxX;
+ }
+ // track last beginning of a word
+ if ((ptype == NVG_SPACE && (type == NVG_CHAR || type == NVG_CJK_CHAR)) || type == NVG_CJK_CHAR) {
+ wordStart = iter.str;
+ wordStartX = iter.x;
+ wordMinX = q.x0 - rowStartX;
+ }
+
+ // Break to new line when a character is beyond break width.
+ if ((type == NVG_CHAR || type == NVG_CJK_CHAR) && nextWidth > breakRowWidth) {
+ // The run length is too long, need to break to new line.
+ if (breakEnd == rowStart) {
+ // The current word is longer than the row length, just break it from here.
+ rows[nrows].start = rowStart;
+ rows[nrows].end = iter.str;
+ rows[nrows].width = rowWidth * invscale;
+ rows[nrows].minx = rowMinX * invscale;
+ rows[nrows].maxx = rowMaxX * invscale;
+ rows[nrows].next = iter.str;
+ nrows++;
+ if (nrows >= maxRows)
+ return nrows;
+ rowStartX = iter.x;
+ rowStart = iter.str;
+ rowEnd = iter.next;
+ rowWidth = iter.nextx - rowStartX;
+ rowMinX = q.x0 - rowStartX;
+ rowMaxX = q.x1 - rowStartX;
+ wordStart = iter.str;
+ wordStartX = iter.x;
+ wordMinX = q.x0 - rowStartX;
+ } else {
+ // Break the line from the end of the last word, and start new line from the beginning of the new.
+ rows[nrows].start = rowStart;
+ rows[nrows].end = breakEnd;
+ rows[nrows].width = breakWidth * invscale;
+ rows[nrows].minx = rowMinX * invscale;
+ rows[nrows].maxx = breakMaxX * invscale;
+ rows[nrows].next = wordStart;
+ nrows++;
+ if (nrows >= maxRows)
+ return nrows;
+ rowStartX = wordStartX;
+ rowStart = wordStart;
+ rowEnd = iter.next;
+ rowWidth = iter.nextx - rowStartX;
+ rowMinX = wordMinX;
+ rowMaxX = q.x1 - rowStartX;
+ // No change to the word start
+ }
+ // Set null break point
+ breakEnd = rowStart;
+ breakWidth = 0.0;
+ breakMaxX = 0.0;
+ }
+ }
+ }
+
+ pcodepoint = iter.codepoint;
+ ptype = type;
+ }
+
+ // Break the line from the end of the last word, and start new line from the beginning of the new.
+ if (rowStart != NULL) {
+ rows[nrows].start = rowStart;
+ rows[nrows].end = rowEnd;
+ rows[nrows].width = rowWidth * invscale;
+ rows[nrows].minx = rowMinX * invscale;
+ rows[nrows].maxx = rowMaxX * invscale;
+ rows[nrows].next = end;
+ nrows++;
+ }
+
+ return nrows;
+}
+
+float nvgTextBounds(NVGcontext* ctx, float x, float y, const char* string, const char* end, float* bounds)
+{
+ NVGstate* state = nvg__getState(ctx);
+ float scale = nvg__getFontScale(state) * ctx->devicePxRatio;
+ float invscale = 1.0f / scale;
+ float width;
+
+ if (state->fontId == FONS_INVALID) return 0;
+
+ fonsSetSize(ctx->fs, state->fontSize*scale);
+ fonsSetSpacing(ctx->fs, state->letterSpacing*scale);
+ fonsSetBlur(ctx->fs, state->fontBlur*scale);
+ fonsSetAlign(ctx->fs, state->textAlign);
+ fonsSetFont(ctx->fs, state->fontId);
+
+ width = fonsTextBounds(ctx->fs, x*scale, y*scale, string, end, bounds);
+ if (bounds != NULL) {
+ // Use line bounds for height.
+ fonsLineBounds(ctx->fs, y*scale, &bounds[1], &bounds[3]);
+ bounds[0] *= invscale;
+ bounds[1] *= invscale;
+ bounds[2] *= invscale;
+ bounds[3] *= invscale;
+ }
+ return width * invscale;
+}
+
+void nvgTextBoxBounds(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end, float* bounds)
+{
+ NVGstate* state = nvg__getState(ctx);
+ NVGtextRow rows[2];
+ float scale = nvg__getFontScale(state) * ctx->devicePxRatio;
+ float invscale = 1.0f / scale;
+ int nrows = 0, i;
+ int oldAlign = state->textAlign;
+ int haling = state->textAlign & (NVG_ALIGN_LEFT | NVG_ALIGN_CENTER | NVG_ALIGN_RIGHT);
+ int valign = state->textAlign & (NVG_ALIGN_TOP | NVG_ALIGN_MIDDLE | NVG_ALIGN_BOTTOM | NVG_ALIGN_BASELINE);
+ float lineh = 0, rminy = 0, rmaxy = 0;
+ float minx, miny, maxx, maxy;
+
+ if (state->fontId == FONS_INVALID) {
+ if (bounds != NULL)
+ bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0f;
+ return;
+ }
+
+ nvgTextMetrics(ctx, NULL, NULL, &lineh);
+
+ state->textAlign = NVG_ALIGN_LEFT | valign;
+
+ minx = maxx = x;
+ miny = maxy = y;
+
+ fonsSetSize(ctx->fs, state->fontSize*scale);
+ fonsSetSpacing(ctx->fs, state->letterSpacing*scale);
+ fonsSetBlur(ctx->fs, state->fontBlur*scale);
+ fonsSetAlign(ctx->fs, state->textAlign);
+ fonsSetFont(ctx->fs, state->fontId);
+ fonsLineBounds(ctx->fs, 0, &rminy, &rmaxy);
+ rminy *= invscale;
+ rmaxy *= invscale;
+
+ while ((nrows = nvgTextBreakLines(ctx, string, end, breakRowWidth, rows, 2))) {
+ for (i = 0; i < nrows; i++) {
+ NVGtextRow* row = &rows[i];
+ float rminx, rmaxx, dx = 0;
+ // Horizontal bounds
+ if (haling & NVG_ALIGN_LEFT)
+ dx = 0;
+ else if (haling & NVG_ALIGN_CENTER)
+ dx = breakRowWidth*0.5f - row->width*0.5f;
+ else if (haling & NVG_ALIGN_RIGHT)
+ dx = breakRowWidth - row->width;
+ rminx = x + row->minx + dx;
+ rmaxx = x + row->maxx + dx;
+ minx = nvg__minf(minx, rminx);
+ maxx = nvg__maxf(maxx, rmaxx);
+ // Vertical bounds.
+ miny = nvg__minf(miny, y + rminy);
+ maxy = nvg__maxf(maxy, y + rmaxy);
+
+ y += lineh * state->lineHeight;
+ }
+ string = rows[nrows-1].next;
+ }
+
+ state->textAlign = oldAlign;
+
+ if (bounds != NULL) {
+ bounds[0] = minx;
+ bounds[1] = miny;
+ bounds[2] = maxx;
+ bounds[3] = maxy;
+ }
+}
+
+void nvgTextMetrics(NVGcontext* ctx, float* ascender, float* descender, float* lineh)
+{
+ NVGstate* state = nvg__getState(ctx);
+ float scale = nvg__getFontScale(state) * ctx->devicePxRatio;
+ float invscale = 1.0f / scale;
+
+ if (state->fontId == FONS_INVALID) return;
+
+ fonsSetSize(ctx->fs, state->fontSize*scale);
+ fonsSetSpacing(ctx->fs, state->letterSpacing*scale);
+ fonsSetBlur(ctx->fs, state->fontBlur*scale);
+ fonsSetAlign(ctx->fs, state->textAlign);
+ fonsSetFont(ctx->fs, state->fontId);
+
+ fonsVertMetrics(ctx->fs, ascender, descender, lineh);
+ if (ascender != NULL)
+ *ascender *= invscale;
+ if (descender != NULL)
+ *descender *= invscale;
+ if (lineh != NULL)
+ *lineh *= invscale;
+}
+// vim: ft=c nu noet ts=4
diff --git a/subprojects/d2tk/nanovg/src/nanovg.h b/subprojects/d2tk/nanovg/src/nanovg.h
new file mode 100644
index 0000000..2255d6a
--- /dev/null
+++ b/subprojects/d2tk/nanovg/src/nanovg.h
@@ -0,0 +1,685 @@
+//
+// Copyright (c) 2013 Mikko Mononen memon@inside.org
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+//
+
+#ifndef NANOVG_H
+#define NANOVG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define NVG_PI 3.14159265358979323846264338327f
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable: 4201) // nonstandard extension used : nameless struct/union
+#endif
+
+typedef struct NVGcontext NVGcontext;
+
+struct NVGcolor {
+ union {
+ float rgba[4];
+ struct {
+ float r,g,b,a;
+ };
+ };
+};
+typedef struct NVGcolor NVGcolor;
+
+struct NVGpaint {
+ float xform[6];
+ float extent[2];
+ float radius;
+ float feather;
+ NVGcolor innerColor;
+ NVGcolor outerColor;
+ int image;
+};
+typedef struct NVGpaint NVGpaint;
+
+enum NVGwinding {
+ NVG_CCW = 1, // Winding for solid shapes
+ NVG_CW = 2, // Winding for holes
+};
+
+enum NVGsolidity {
+ NVG_SOLID = 1, // CCW
+ NVG_HOLE = 2, // CW
+};
+
+enum NVGlineCap {
+ NVG_BUTT,
+ NVG_ROUND,
+ NVG_SQUARE,
+ NVG_BEVEL,
+ NVG_MITER,
+};
+
+enum NVGalign {
+ // Horizontal align
+ NVG_ALIGN_LEFT = 1<<0, // Default, align text horizontally to left.
+ NVG_ALIGN_CENTER = 1<<1, // Align text horizontally to center.
+ NVG_ALIGN_RIGHT = 1<<2, // Align text horizontally to right.
+ // Vertical align
+ NVG_ALIGN_TOP = 1<<3, // Align text vertically to top.
+ NVG_ALIGN_MIDDLE = 1<<4, // Align text vertically to middle.
+ NVG_ALIGN_BOTTOM = 1<<5, // Align text vertically to bottom.
+ NVG_ALIGN_BASELINE = 1<<6, // Default, align text vertically to baseline.
+};
+
+enum NVGblendFactor {
+ NVG_ZERO = 1<<0,
+ NVG_ONE = 1<<1,
+ NVG_SRC_COLOR = 1<<2,
+ NVG_ONE_MINUS_SRC_COLOR = 1<<3,
+ NVG_DST_COLOR = 1<<4,
+ NVG_ONE_MINUS_DST_COLOR = 1<<5,
+ NVG_SRC_ALPHA = 1<<6,
+ NVG_ONE_MINUS_SRC_ALPHA = 1<<7,
+ NVG_DST_ALPHA = 1<<8,
+ NVG_ONE_MINUS_DST_ALPHA = 1<<9,
+ NVG_SRC_ALPHA_SATURATE = 1<<10,
+};
+
+enum NVGcompositeOperation {
+ NVG_SOURCE_OVER,
+ NVG_SOURCE_IN,
+ NVG_SOURCE_OUT,
+ NVG_ATOP,
+ NVG_DESTINATION_OVER,
+ NVG_DESTINATION_IN,
+ NVG_DESTINATION_OUT,
+ NVG_DESTINATION_ATOP,
+ NVG_LIGHTER,
+ NVG_COPY,
+ NVG_XOR,
+};
+
+struct NVGcompositeOperationState {
+ int srcRGB;
+ int dstRGB;
+ int srcAlpha;
+ int dstAlpha;
+};
+typedef struct NVGcompositeOperationState NVGcompositeOperationState;
+
+struct NVGglyphPosition {
+ const char* str; // Position of the glyph in the input string.
+ float x; // The x-coordinate of the logical glyph position.
+ float minx, maxx; // The bounds of the glyph shape.
+};
+typedef struct NVGglyphPosition NVGglyphPosition;
+
+struct NVGtextRow {
+ const char* start; // Pointer to the input text where the row starts.
+ const char* end; // Pointer to the input text where the row ends (one past the last character).
+ const char* next; // Pointer to the beginning of the next row.
+ float width; // Logical width of the row.
+ float minx, maxx; // Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending.
+};
+typedef struct NVGtextRow NVGtextRow;
+
+enum NVGimageFlags {
+ NVG_IMAGE_GENERATE_MIPMAPS = 1<<0, // Generate mipmaps during creation of the image.
+ NVG_IMAGE_REPEATX = 1<<1, // Repeat image in X direction.
+ NVG_IMAGE_REPEATY = 1<<2, // Repeat image in Y direction.
+ NVG_IMAGE_FLIPY = 1<<3, // Flips (inverses) image in Y direction when rendered.
+ NVG_IMAGE_PREMULTIPLIED = 1<<4, // Image data has premultiplied alpha.
+ NVG_IMAGE_NEAREST = 1<<5, // Image interpolation is Nearest instead Linear
+};
+
+// Begin drawing a new frame
+// Calls to nanovg drawing API should be wrapped in nvgBeginFrame() & nvgEndFrame()
+// nvgBeginFrame() defines the size of the window to render to in relation currently
+// set viewport (i.e. glViewport on GL backends). Device pixel ration allows to
+// control the rendering on Hi-DPI devices.
+// For example, GLFW returns two dimension for an opened window: window size and
+// frame buffer size. In that case you would set windowWidth/Height to the window size
+// devicePixelRatio to: frameBufferWidth / windowWidth.
+void nvgBeginFrame(NVGcontext* ctx, float windowWidth, float windowHeight, float devicePixelRatio);
+
+// Cancels drawing the current frame.
+void nvgCancelFrame(NVGcontext* ctx);
+
+// Ends drawing flushing remaining render state.
+void nvgEndFrame(NVGcontext* ctx);
+
+//
+// Composite operation
+//
+// The composite operations in NanoVG are modeled after HTML Canvas API, and
+// the blend func is based on OpenGL (see corresponding manuals for more info).
+// The colors in the blending state have premultiplied alpha.
+
+// Sets the composite operation. The op parameter should be one of NVGcompositeOperation.
+void nvgGlobalCompositeOperation(NVGcontext* ctx, int op);
+
+// Sets the composite operation with custom pixel arithmetic. The parameters should be one of NVGblendFactor.
+void nvgGlobalCompositeBlendFunc(NVGcontext* ctx, int sfactor, int dfactor);
+
+// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately. The parameters should be one of NVGblendFactor.
+void nvgGlobalCompositeBlendFuncSeparate(NVGcontext* ctx, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha);
+
+//
+// Color utils
+//
+// Colors in NanoVG are stored as unsigned ints in ABGR format.
+
+// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f).
+NVGcolor nvgRGB(unsigned char r, unsigned char g, unsigned char b);
+
+// Returns a color value from red, green, blue values. Alpha will be set to 1.0f.
+NVGcolor nvgRGBf(float r, float g, float b);
+
+
+// Returns a color value from red, green, blue and alpha values.
+NVGcolor nvgRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
+
+// Returns a color value from red, green, blue and alpha values.
+NVGcolor nvgRGBAf(float r, float g, float b, float a);
+
+
+// Linearly interpolates from color c0 to c1, and returns resulting color value.
+NVGcolor nvgLerpRGBA(NVGcolor c0, NVGcolor c1, float u);
+
+// Sets transparency of a color value.
+NVGcolor nvgTransRGBA(NVGcolor c0, unsigned char a);
+
+// Sets transparency of a color value.
+NVGcolor nvgTransRGBAf(NVGcolor c0, float a);
+
+// Returns color value specified by hue, saturation and lightness.
+// HSL values are all in range [0..1], alpha will be set to 255.
+NVGcolor nvgHSL(float h, float s, float l);
+
+// Returns color value specified by hue, saturation and lightness and alpha.
+// HSL values are all in range [0..1], alpha in range [0..255]
+NVGcolor nvgHSLA(float h, float s, float l, unsigned char a);
+
+//
+// State Handling
+//
+// NanoVG contains state which represents how paths will be rendered.
+// The state contains transform, fill and stroke styles, text and font styles,
+// and scissor clipping.
+
+// Pushes and saves the current render state into a state stack.
+// A matching nvgRestore() must be used to restore the state.
+void nvgSave(NVGcontext* ctx);
+
+// Pops and restores current render state.
+void nvgRestore(NVGcontext* ctx);
+
+// Resets current render state to default values. Does not affect the render state stack.
+void nvgReset(NVGcontext* ctx);
+
+//
+// Render styles
+//
+// Fill and stroke render style can be either a solid color or a paint which is a gradient or a pattern.
+// Solid color is simply defined as a color value, different kinds of paints can be created
+// using nvgLinearGradient(), nvgBoxGradient(), nvgRadialGradient() and nvgImagePattern().
+//
+// Current render style can be saved and restored using nvgSave() and nvgRestore().
+
+// Sets whether to draw antialias for nvgStroke() and nvgFill(). It's enabled by default.
+void nvgShapeAntiAlias(NVGcontext* ctx, int enabled);
+
+// Sets current stroke style to a solid color.
+void nvgStrokeColor(NVGcontext* ctx, NVGcolor color);
+
+// Sets current stroke style to a paint, which can be a one of the gradients or a pattern.
+void nvgStrokePaint(NVGcontext* ctx, NVGpaint paint);
+
+// Sets current fill style to a solid color.
+void nvgFillColor(NVGcontext* ctx, NVGcolor color);
+
+// Sets current fill style to a paint, which can be a one of the gradients or a pattern.
+void nvgFillPaint(NVGcontext* ctx, NVGpaint paint);
+
+// Sets the miter limit of the stroke style.
+// Miter limit controls when a sharp corner is beveled.
+void nvgMiterLimit(NVGcontext* ctx, float limit);
+
+// Sets the stroke width of the stroke style.
+void nvgStrokeWidth(NVGcontext* ctx, float size);
+
+// Sets how the end of the line (cap) is drawn,
+// Can be one of: NVG_BUTT (default), NVG_ROUND, NVG_SQUARE.
+void nvgLineCap(NVGcontext* ctx, int cap);
+
+// Sets how sharp path corners are drawn.
+// Can be one of NVG_MITER (default), NVG_ROUND, NVG_BEVEL.
+void nvgLineJoin(NVGcontext* ctx, int join);
+
+// Sets the transparency applied to all rendered shapes.
+// Already transparent paths will get proportionally more transparent as well.
+void nvgGlobalAlpha(NVGcontext* ctx, float alpha);
+
+//
+// Transforms
+//
+// The paths, gradients, patterns and scissor region are transformed by an transformation
+// matrix at the time when they are passed to the API.
+// The current transformation matrix is a affine matrix:
+// [sx kx tx]
+// [ky sy ty]
+// [ 0 0 1]
+// Where: sx,sy define scaling, kx,ky skewing, and tx,ty translation.
+// The last row is assumed to be 0,0,1 and is not stored.
+//
+// Apart from nvgResetTransform(), each transformation function first creates
+// specific transformation matrix and pre-multiplies the current transformation by it.
+//
+// Current coordinate system (transformation) can be saved and restored using nvgSave() and nvgRestore().
+
+// Resets current transform to a identity matrix.
+void nvgResetTransform(NVGcontext* ctx);
+
+// Premultiplies current coordinate system by specified matrix.
+// The parameters are interpreted as matrix as follows:
+// [a c e]
+// [b d f]
+// [0 0 1]
+void nvgTransform(NVGcontext* ctx, float a, float b, float c, float d, float e, float f);
+
+// Translates current coordinate system.
+void nvgTranslate(NVGcontext* ctx, float x, float y);
+
+// Rotates current coordinate system. Angle is specified in radians.
+void nvgRotate(NVGcontext* ctx, float angle);
+
+// Skews the current coordinate system along X axis. Angle is specified in radians.
+void nvgSkewX(NVGcontext* ctx, float angle);
+
+// Skews the current coordinate system along Y axis. Angle is specified in radians.
+void nvgSkewY(NVGcontext* ctx, float angle);
+
+// Scales the current coordinate system.
+void nvgScale(NVGcontext* ctx, float x, float y);
+
+// Stores the top part (a-f) of the current transformation matrix in to the specified buffer.
+// [a c e]
+// [b d f]
+// [0 0 1]
+// There should be space for 6 floats in the return buffer for the values a-f.
+void nvgCurrentTransform(NVGcontext* ctx, float* xform);
+
+
+// The following functions can be used to make calculations on 2x3 transformation matrices.
+// A 2x3 matrix is represented as float[6].
+
+// Sets the transform to identity matrix.
+void nvgTransformIdentity(float* dst);
+
+// Sets the transform to translation matrix matrix.
+void nvgTransformTranslate(float* dst, float tx, float ty);
+
+// Sets the transform to scale matrix.
+void nvgTransformScale(float* dst, float sx, float sy);
+
+// Sets the transform to rotate matrix. Angle is specified in radians.
+void nvgTransformRotate(float* dst, float a);
+
+// Sets the transform to skew-x matrix. Angle is specified in radians.
+void nvgTransformSkewX(float* dst, float a);
+
+// Sets the transform to skew-y matrix. Angle is specified in radians.
+void nvgTransformSkewY(float* dst, float a);
+
+// Sets the transform to the result of multiplication of two transforms, of A = A*B.
+void nvgTransformMultiply(float* dst, const float* src);
+
+// Sets the transform to the result of multiplication of two transforms, of A = B*A.
+void nvgTransformPremultiply(float* dst, const float* src);
+
+// Sets the destination to inverse of specified transform.
+// Returns 1 if the inverse could be calculated, else 0.
+int nvgTransformInverse(float* dst, const float* src);
+
+// Transform a point by given transform.
+void nvgTransformPoint(float* dstx, float* dsty, const float* xform, float srcx, float srcy);
+
+// Converts degrees to radians and vice versa.
+float nvgDegToRad(float deg);
+float nvgRadToDeg(float rad);
+
+//
+// Images
+//
+// NanoVG allows you to load jpg, png, psd, tga, pic and gif files to be used for rendering.
+// In addition you can upload your own image. The image loading is provided by stb_image.
+// The parameter imageFlags is combination of flags defined in NVGimageFlags.
+
+// Creates image by loading it from the disk from specified file name.
+// Returns handle to the image.
+int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags);
+
+// Creates image by loading it from the specified chunk of memory.
+// Returns handle to the image.
+int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int ndata);
+
+// Creates image from specified image data.
+// Returns handle to the image.
+int nvgCreateImageRGBA(NVGcontext* ctx, int w, int h, int imageFlags, const unsigned char* data);
+
+// Updates image data specified by image handle.
+void nvgUpdateImage(NVGcontext* ctx, int image, const unsigned char* data);
+
+// Returns the dimensions of a created image.
+void nvgImageSize(NVGcontext* ctx, int image, int* w, int* h);
+
+// Deletes created image.
+void nvgDeleteImage(NVGcontext* ctx, int image);
+
+//
+// Paints
+//
+// NanoVG supports four types of paints: linear gradient, box gradient, radial gradient and image pattern.
+// These can be used as paints for strokes and fills.
+
+// Creates and returns a linear gradient. Parameters (sx,sy)-(ex,ey) specify the start and end coordinates
+// of the linear gradient, icol specifies the start color and ocol the end color.
+// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint().
+NVGpaint nvgLinearGradient(NVGcontext* ctx, float sx, float sy, float ex, float ey,
+ NVGcolor icol, NVGcolor ocol);
+
+// Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering
+// drop shadows or highlights for boxes. Parameters (x,y) define the top-left corner of the rectangle,
+// (w,h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
+// the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.
+// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint().
+NVGpaint nvgBoxGradient(NVGcontext* ctx, float x, float y, float w, float h,
+ float r, float f, NVGcolor icol, NVGcolor ocol);
+
+// Creates and returns a radial gradient. Parameters (cx,cy) specify the center, inr and outr specify
+// the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.
+// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint().
+NVGpaint nvgRadialGradient(NVGcontext* ctx, float cx, float cy, float inr, float outr,
+ NVGcolor icol, NVGcolor ocol);
+
+// Creates and returns an image patter. Parameters (ox,oy) specify the left-top location of the image pattern,
+// (ex,ey) the size of one image, angle rotation around the top-left corner, image is handle to the image to render.
+// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint().
+NVGpaint nvgImagePattern(NVGcontext* ctx, float ox, float oy, float ex, float ey,
+ float angle, int image, float alpha);
+
+//
+// Scissoring
+//
+// Scissoring allows you to clip the rendering into a rectangle. This is useful for various
+// user interface cases like rendering a text edit or a timeline.
+
+// Sets the current scissor rectangle.
+// The scissor rectangle is transformed by the current transform.
+void nvgScissor(NVGcontext* ctx, float x, float y, float w, float h);
+
+// Intersects current scissor rectangle with the specified rectangle.
+// The scissor rectangle is transformed by the current transform.
+// Note: in case the rotation of previous scissor rect differs from
+// the current one, the intersection will be done between the specified
+// rectangle and the previous scissor rectangle transformed in the current
+// transform space. The resulting shape is always rectangle.
+void nvgIntersectScissor(NVGcontext* ctx, float x, float y, float w, float h);
+
+// Reset and disables scissoring.
+void nvgResetScissor(NVGcontext* ctx);
+
+//
+// Paths
+//
+// Drawing a new shape starts with nvgBeginPath(), it clears all the currently defined paths.
+// Then you define one or more paths and sub-paths which describe the shape. The are functions
+// to draw common shapes like rectangles and circles, and lower level step-by-step functions,
+// which allow to define a path curve by curve.
+//
+// NanoVG uses even-odd fill rule to draw the shapes. Solid shapes should have counter clockwise
+// winding and holes should have counter clockwise order. To specify winding of a path you can
+// call nvgPathWinding(). This is useful especially for the common shapes, which are drawn CCW.
+//
+// Finally you can fill the path using current fill style by calling nvgFill(), and stroke it
+// with current stroke style by calling nvgStroke().
+//
+// The curve segments and sub-paths are transformed by the current transform.
+
+// Clears the current path and sub-paths.
+void nvgBeginPath(NVGcontext* ctx);
+
+// Starts new sub-path with specified point as first point.
+void nvgMoveTo(NVGcontext* ctx, float x, float y);
+
+// Adds line segment from the last point in the path to the specified point.
+void nvgLineTo(NVGcontext* ctx, float x, float y);
+
+// Adds cubic bezier segment from last point in the path via two control points to the specified point.
+void nvgBezierTo(NVGcontext* ctx, float c1x, float c1y, float c2x, float c2y, float x, float y);
+
+// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
+void nvgQuadTo(NVGcontext* ctx, float cx, float cy, float x, float y);
+
+// Adds an arc segment at the corner defined by the last path point, and two specified points.
+void nvgArcTo(NVGcontext* ctx, float x1, float y1, float x2, float y2, float radius);
+
+// Closes current sub-path with a line segment.
+void nvgClosePath(NVGcontext* ctx);
+
+// Sets the current sub-path winding, see NVGwinding and NVGsolidity.
+void nvgPathWinding(NVGcontext* ctx, int dir);
+
+// Creates new circle arc shaped sub-path. The arc center is at cx,cy, the arc radius is r,
+// and the arc is drawn from angle a0 to a1, and swept in direction dir (NVG_CCW, or NVG_CW).
+// Angles are specified in radians.
+void nvgArc(NVGcontext* ctx, float cx, float cy, float r, float a0, float a1, int dir);
+
+// Creates new rectangle shaped sub-path.
+void nvgRect(NVGcontext* ctx, float x, float y, float w, float h);
+
+// Creates new rounded rectangle shaped sub-path.
+void nvgRoundedRect(NVGcontext* ctx, float x, float y, float w, float h, float r);
+
+// Creates new rounded rectangle shaped sub-path with varying radii for each corner.
+void nvgRoundedRectVarying(NVGcontext* ctx, float x, float y, float w, float h, float radTopLeft, float radTopRight, float radBottomRight, float radBottomLeft);
+
+// Creates new ellipse shaped sub-path.
+void nvgEllipse(NVGcontext* ctx, float cx, float cy, float rx, float ry);
+
+// Creates new circle shaped sub-path.
+void nvgCircle(NVGcontext* ctx, float cx, float cy, float r);
+
+// Fills the current path with current fill style.
+void nvgFill(NVGcontext* ctx);
+
+// Fills the current path with current stroke style.
+void nvgStroke(NVGcontext* ctx);
+
+
+//
+// Text
+//
+// NanoVG allows you to load .ttf files and use the font to render text.
+//
+// The appearance of the text can be defined by setting the current text style
+// and by specifying the fill color. Common text and font settings such as
+// font size, letter spacing and text align are supported. Font blur allows you
+// to create simple text effects such as drop shadows.
+//
+// At render time the font face can be set based on the font handles or name.
+//
+// Font measure functions return values in local space, the calculations are
+// carried in the same resolution as the final rendering. This is done because
+// the text glyph positions are snapped to the nearest pixels sharp rendering.
+//
+// The local space means that values are not rotated or scale as per the current
+// transformation. For example if you set font size to 12, which would mean that
+// line height is 16, then regardless of the current scaling and rotation, the
+// returned line height is always 16. Some measures may vary because of the scaling
+// since aforementioned pixel snapping.
+//
+// While this may sound a little odd, the setup allows you to always render the
+// same way regardless of scaling. I.e. following works regardless of scaling:
+//
+// const char* txt = "Text me up.";
+// nvgTextBounds(vg, x,y, txt, NULL, bounds);
+// nvgBeginPath(vg);
+// nvgRoundedRect(vg, bounds[0],bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]);
+// nvgFill(vg);
+//
+// Note: currently only solid color fill is supported for text.
+
+// Creates font by loading it from the disk from specified file name.
+// Returns handle to the font.
+int nvgCreateFont(NVGcontext* ctx, const char* name, const char* filename);
+
+// Creates font by loading it from the specified memory chunk.
+// Returns handle to the font.
+int nvgCreateFontMem(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData);
+
+// Finds a loaded font of specified name, and returns handle to it, or -1 if the font is not found.
+int nvgFindFont(NVGcontext* ctx, const char* name);
+
+// Adds a fallback font by handle.
+int nvgAddFallbackFontId(NVGcontext* ctx, int baseFont, int fallbackFont);
+
+// Adds a fallback font by name.
+int nvgAddFallbackFont(NVGcontext* ctx, const char* baseFont, const char* fallbackFont);
+
+// Sets the font size of current text style.
+void nvgFontSize(NVGcontext* ctx, float size);
+
+// Sets the blur of current text style.
+void nvgFontBlur(NVGcontext* ctx, float blur);
+
+// Sets the letter spacing of current text style.
+void nvgTextLetterSpacing(NVGcontext* ctx, float spacing);
+
+// Sets the proportional line height of current text style. The line height is specified as multiple of font size.
+void nvgTextLineHeight(NVGcontext* ctx, float lineHeight);
+
+// Sets the text align of current text style, see NVGalign for options.
+void nvgTextAlign(NVGcontext* ctx, int align);
+
+// Sets the font face based on specified id of current text style.
+void nvgFontFaceId(NVGcontext* ctx, int font);
+
+// Sets the font face based on specified name of current text style.
+void nvgFontFace(NVGcontext* ctx, const char* font);
+
+// Draws text string at specified location. If end is specified only the sub-string up to the end is drawn.
+float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* end);
+
+// Draws multi-line text string at specified location wrapped at the specified width. If end is specified only the sub-string up to the end is drawn.
+// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
+// Words longer than the max width are slit at nearest character (i.e. no hyphenation).
+void nvgTextBox(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end);
+
+// Measures the specified text string. Parameter bounds should be a pointer to float[4],
+// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax]
+// Returns the horizontal advance of the measured text (i.e. where the next character should drawn).
+// Measured values are returned in local coordinate space.
+float nvgTextBounds(NVGcontext* ctx, float x, float y, const char* string, const char* end, float* bounds);
+
+// Measures the specified multi-text string. Parameter bounds should be a pointer to float[4],
+// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax]
+// Measured values are returned in local coordinate space.
+void nvgTextBoxBounds(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end, float* bounds);
+
+// Calculates the glyph x positions of the specified text. If end is specified only the sub-string will be used.
+// Measured values are returned in local coordinate space.
+int nvgTextGlyphPositions(NVGcontext* ctx, float x, float y, const char* string, const char* end, NVGglyphPosition* positions, int maxPositions);
+
+// Returns the vertical metrics based on the current text style.
+// Measured values are returned in local coordinate space.
+void nvgTextMetrics(NVGcontext* ctx, float* ascender, float* descender, float* lineh);
+
+// Breaks the specified text into lines. If end is specified only the sub-string will be used.
+// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
+// Words longer than the max width are slit at nearest character (i.e. no hyphenation).
+int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, float breakRowWidth, NVGtextRow* rows, int maxRows);
+
+//
+// Internal Render API
+//
+enum NVGtexture {
+ NVG_TEXTURE_ALPHA = 0x01,
+ NVG_TEXTURE_RGBA = 0x02,
+};
+
+struct NVGscissor {
+ float xform[6];
+ float extent[2];
+};
+typedef struct NVGscissor NVGscissor;
+
+struct NVGvertex {
+ float x,y,u,v;
+};
+typedef struct NVGvertex NVGvertex;
+
+struct NVGpath {
+ int first;
+ int count;
+ unsigned char closed;
+ int nbevel;
+ NVGvertex* fill;
+ int nfill;
+ NVGvertex* stroke;
+ int nstroke;
+ int winding;
+ int convex;
+};
+typedef struct NVGpath NVGpath;
+
+struct NVGparams {
+ void* userPtr;
+ int edgeAntiAlias;
+ int (*renderCreate)(void* uptr);
+ int (*renderCreateTexture)(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data);
+ int (*renderDeleteTexture)(void* uptr, int image);
+ int (*renderUpdateTexture)(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data);
+ int (*renderGetTextureSize)(void* uptr, int image, int* w, int* h);
+ void (*renderViewport)(void* uptr, float width, float height, float devicePixelRatio);
+ void (*renderCancel)(void* uptr);
+ void (*renderFlush)(void* uptr);
+ void (*renderFill)(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe, const float* bounds, const NVGpath* paths, int npaths);
+ void (*renderStroke)(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe, float strokeWidth, const NVGpath* paths, int npaths);
+ void (*renderTriangles)(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, const NVGvertex* verts, int nverts);
+ void (*renderDelete)(void* uptr);
+};
+typedef struct NVGparams NVGparams;
+
+// Constructor and destructor, called by the render back-end.
+NVGcontext* nvgCreateInternal(NVGparams* params);
+void nvgDeleteInternal(NVGcontext* ctx);
+
+NVGparams* nvgInternalParams(NVGcontext* ctx);
+
+// Debug function to dump cached path data.
+void nvgDebugDumpPathCache(NVGcontext* ctx);
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#define NVG_NOTUSED(v) for (;;) { (void)(1 ? (void)0 : ( (void)(v) ) ); break; }
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // NANOVG_H
diff --git a/subprojects/d2tk/nanovg/src/nanovg_gl.h b/subprojects/d2tk/nanovg/src/nanovg_gl.h
new file mode 100644
index 0000000..5292181
--- /dev/null
+++ b/subprojects/d2tk/nanovg/src/nanovg_gl.h
@@ -0,0 +1,1649 @@
+//
+// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+//
+#ifndef NANOVG_GL_H
+#define NANOVG_GL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Create flags
+
+enum NVGcreateFlags {
+ // Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA).
+ NVG_ANTIALIAS = 1<<0,
+ // Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little
+ // slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once.
+ NVG_STENCIL_STROKES = 1<<1,
+ // Flag indicating that additional debug checks are done.
+ NVG_DEBUG = 1<<2,
+};
+
+#if defined NANOVG_GL2_IMPLEMENTATION
+# define NANOVG_GL2 1
+# define NANOVG_GL_IMPLEMENTATION 1
+#elif defined NANOVG_GL3_IMPLEMENTATION
+# define NANOVG_GL3 1
+# define NANOVG_GL_IMPLEMENTATION 1
+# define NANOVG_GL_USE_UNIFORMBUFFER 1
+#elif defined NANOVG_GLES2_IMPLEMENTATION
+# define NANOVG_GLES2 1
+# define NANOVG_GL_IMPLEMENTATION 1
+#elif defined NANOVG_GLES3_IMPLEMENTATION
+# define NANOVG_GLES3 1
+# define NANOVG_GL_IMPLEMENTATION 1
+#endif
+
+#define NANOVG_GL_USE_STATE_FILTER (1)
+
+// Creates NanoVG contexts for different OpenGL (ES) versions.
+// Flags should be combination of the create flags above.
+
+#if defined NANOVG_GL2
+
+NVGcontext* nvgCreateGL2(int flags);
+void nvgDeleteGL2(NVGcontext* ctx);
+
+int nvglCreateImageFromHandleGL2(NVGcontext* ctx, GLuint textureId, int w, int h, int flags);
+GLuint nvglImageHandleGL2(NVGcontext* ctx, int image);
+
+#endif
+
+#if defined NANOVG_GL3
+
+NVGcontext* nvgCreateGL3(int flags);
+void nvgDeleteGL3(NVGcontext* ctx);
+
+int nvglCreateImageFromHandleGL3(NVGcontext* ctx, GLuint textureId, int w, int h, int flags);
+GLuint nvglImageHandleGL3(NVGcontext* ctx, int image);
+
+#endif
+
+#if defined NANOVG_GLES2
+
+NVGcontext* nvgCreateGLES2(int flags);
+void nvgDeleteGLES2(NVGcontext* ctx);
+
+int nvglCreateImageFromHandleGLES2(NVGcontext* ctx, GLuint textureId, int w, int h, int flags);
+GLuint nvglImageHandleGLES2(NVGcontext* ctx, int image);
+
+#endif
+
+#if defined NANOVG_GLES3
+
+NVGcontext* nvgCreateGLES3(int flags);
+void nvgDeleteGLES3(NVGcontext* ctx);
+
+int nvglCreateImageFromHandleGLES3(NVGcontext* ctx, GLuint textureId, int w, int h, int flags);
+GLuint nvglImageHandleGLES3(NVGcontext* ctx, int image);
+
+#endif
+
+// These are additional flags on top of NVGimageFlags.
+enum NVGimageFlagsGL {
+ NVG_IMAGE_NODELETE = 1<<16, // Do not delete GL texture handle.
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* NANOVG_GL_H */
+
+#ifdef NANOVG_GL_IMPLEMENTATION
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include "nanovg.h"
+
+enum GLNVGuniformLoc {
+ GLNVG_LOC_VIEWSIZE,
+ GLNVG_LOC_TEX,
+ GLNVG_LOC_FRAG,
+ GLNVG_MAX_LOCS
+};
+
+enum GLNVGshaderType {
+ NSVG_SHADER_FILLGRAD,
+ NSVG_SHADER_FILLIMG,
+ NSVG_SHADER_SIMPLE,
+ NSVG_SHADER_IMG
+};
+
+#if NANOVG_GL_USE_UNIFORMBUFFER
+enum GLNVGuniformBindings {
+ GLNVG_FRAG_BINDING = 0,
+};
+#endif
+
+struct GLNVGshader {
+ GLuint prog;
+ GLuint frag;
+ GLuint vert;
+ GLint loc[GLNVG_MAX_LOCS];
+};
+typedef struct GLNVGshader GLNVGshader;
+
+struct GLNVGtexture {
+ int id;
+ GLuint tex;
+ int width, height;
+ int type;
+ int flags;
+};
+typedef struct GLNVGtexture GLNVGtexture;
+
+struct GLNVGblend
+{
+ GLenum srcRGB;
+ GLenum dstRGB;
+ GLenum srcAlpha;
+ GLenum dstAlpha;
+};
+typedef struct GLNVGblend GLNVGblend;
+
+enum GLNVGcallType {
+ GLNVG_NONE = 0,
+ GLNVG_FILL,
+ GLNVG_CONVEXFILL,
+ GLNVG_STROKE,
+ GLNVG_TRIANGLES,
+};
+
+struct GLNVGcall {
+ int type;
+ int image;
+ int pathOffset;
+ int pathCount;
+ int triangleOffset;
+ int triangleCount;
+ int uniformOffset;
+ GLNVGblend blendFunc;
+};
+typedef struct GLNVGcall GLNVGcall;
+
+struct GLNVGpath {
+ int fillOffset;
+ int fillCount;
+ int strokeOffset;
+ int strokeCount;
+};
+typedef struct GLNVGpath GLNVGpath;
+
+struct GLNVGfragUniforms {
+ #if NANOVG_GL_USE_UNIFORMBUFFER
+ float scissorMat[12]; // matrices are actually 3 vec4s
+ float paintMat[12];
+ struct NVGcolor innerCol;
+ struct NVGcolor outerCol;
+ float scissorExt[2];
+ float scissorScale[2];
+ float extent[2];
+ float radius;
+ float feather;
+ float strokeMult;
+ float strokeThr;
+ int texType;
+ int type;
+ #else
+ // note: after modifying layout or size of uniform array,
+ // don't forget to also update the fragment shader source!
+ #define NANOVG_GL_UNIFORMARRAY_SIZE 11
+ union {
+ struct {
+ float scissorMat[12]; // matrices are actually 3 vec4s
+ float paintMat[12];
+ struct NVGcolor innerCol;
+ struct NVGcolor outerCol;
+ float scissorExt[2];
+ float scissorScale[2];
+ float extent[2];
+ float radius;
+ float feather;
+ float strokeMult;
+ float strokeThr;
+ float texType;
+ float type;
+ };
+ float uniformArray[NANOVG_GL_UNIFORMARRAY_SIZE][4];
+ };
+ #endif
+};
+typedef struct GLNVGfragUniforms GLNVGfragUniforms;
+
+struct GLNVGcontext {
+ GLNVGshader shader;
+ GLNVGtexture* textures;
+ float view[2];
+ int ntextures;
+ int ctextures;
+ int textureId;
+ GLuint vertBuf;
+#if defined NANOVG_GL3
+ GLuint vertArr;
+#endif
+#if NANOVG_GL_USE_UNIFORMBUFFER
+ GLuint fragBuf;
+#endif
+ int fragSize;
+ int flags;
+
+ // Per frame buffers
+ GLNVGcall* calls;
+ int ccalls;
+ int ncalls;
+ GLNVGpath* paths;
+ int cpaths;
+ int npaths;
+ struct NVGvertex* verts;
+ int cverts;
+ int nverts;
+ unsigned char* uniforms;
+ int cuniforms;
+ int nuniforms;
+
+ // cached state
+ #if NANOVG_GL_USE_STATE_FILTER
+ GLuint boundTexture;
+ GLuint stencilMask;
+ GLenum stencilFunc;
+ GLint stencilFuncRef;
+ GLuint stencilFuncMask;
+ GLNVGblend blendFunc;
+ #endif
+};
+typedef struct GLNVGcontext GLNVGcontext;
+
+static int glnvg__maxi(int a, int b) { return a > b ? a : b; }
+
+#ifdef NANOVG_GLES2
+static unsigned int glnvg__nearestPow2(unsigned int num)
+{
+ unsigned n = num > 0 ? num - 1 : 0;
+ n |= n >> 1;
+ n |= n >> 2;
+ n |= n >> 4;
+ n |= n >> 8;
+ n |= n >> 16;
+ n++;
+ return n;
+}
+#endif
+
+static void glnvg__bindTexture(GLNVGcontext* gl, GLuint tex)
+{
+#if NANOVG_GL_USE_STATE_FILTER
+ if (gl->boundTexture != tex) {
+ gl->boundTexture = tex;
+ glBindTexture(GL_TEXTURE_2D, tex);
+ }
+#else
+ glBindTexture(GL_TEXTURE_2D, tex);
+#endif
+}
+
+static void glnvg__stencilMask(GLNVGcontext* gl, GLuint mask)
+{
+#if NANOVG_GL_USE_STATE_FILTER
+ if (gl->stencilMask != mask) {
+ gl->stencilMask = mask;
+ glStencilMask(mask);
+ }
+#else
+ glStencilMask(mask);
+#endif
+}
+
+static void glnvg__stencilFunc(GLNVGcontext* gl, GLenum func, GLint ref, GLuint mask)
+{
+#if NANOVG_GL_USE_STATE_FILTER
+ if ((gl->stencilFunc != func) ||
+ (gl->stencilFuncRef != ref) ||
+ (gl->stencilFuncMask != mask)) {
+
+ gl->stencilFunc = func;
+ gl->stencilFuncRef = ref;
+ gl->stencilFuncMask = mask;
+ glStencilFunc(func, ref, mask);
+ }
+#else
+ glStencilFunc(func, ref, mask);
+#endif
+}
+static void glnvg__blendFuncSeparate(GLNVGcontext* gl, const GLNVGblend* blend)
+{
+#if NANOVG_GL_USE_STATE_FILTER
+ if ((gl->blendFunc.srcRGB != blend->srcRGB) ||
+ (gl->blendFunc.dstRGB != blend->dstRGB) ||
+ (gl->blendFunc.srcAlpha != blend->srcAlpha) ||
+ (gl->blendFunc.dstAlpha != blend->dstAlpha)) {
+
+ gl->blendFunc = *blend;
+ glBlendFuncSeparate(blend->srcRGB, blend->dstRGB, blend->srcAlpha,blend->dstAlpha);
+ }
+#else
+ glBlendFuncSeparate(blend->srcRGB, blend->dstRGB, blend->srcAlpha,blend->dstAlpha);
+#endif
+}
+
+static GLNVGtexture* glnvg__allocTexture(GLNVGcontext* gl)
+{
+ GLNVGtexture* tex = NULL;
+ int i;
+
+ for (i = 0; i < gl->ntextures; i++) {
+ if (gl->textures[i].id == 0) {
+ tex = &gl->textures[i];
+ break;
+ }
+ }
+ if (tex == NULL) {
+ if (gl->ntextures+1 > gl->ctextures) {
+ GLNVGtexture* textures;
+ int ctextures = glnvg__maxi(gl->ntextures+1, 4) + gl->ctextures/2; // 1.5x Overallocate
+ textures = (GLNVGtexture*)realloc(gl->textures, sizeof(GLNVGtexture)*ctextures);
+ if (textures == NULL) return NULL;
+ gl->textures = textures;
+ gl->ctextures = ctextures;
+ }
+ tex = &gl->textures[gl->ntextures++];
+ }
+
+ memset(tex, 0, sizeof(*tex));
+ tex->id = ++gl->textureId;
+
+ return tex;
+}
+
+static GLNVGtexture* glnvg__findTexture(GLNVGcontext* gl, int id)
+{
+ int i;
+ for (i = 0; i < gl->ntextures; i++)
+ if (gl->textures[i].id == id)
+ return &gl->textures[i];
+ return NULL;
+}
+
+static int glnvg__deleteTexture(GLNVGcontext* gl, int id)
+{
+ int i;
+ for (i = 0; i < gl->ntextures; i++) {
+ if (gl->textures[i].id == id) {
+ if (gl->textures[i].tex != 0 && (gl->textures[i].flags & NVG_IMAGE_NODELETE) == 0)
+ glDeleteTextures(1, &gl->textures[i].tex);
+ memset(&gl->textures[i], 0, sizeof(gl->textures[i]));
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void glnvg__dumpShaderError(GLuint shader, const char* name, const char* type)
+{
+ GLchar str[512+1];
+ GLsizei len = 0;
+ glGetShaderInfoLog(shader, 512, &len, str);
+ if (len > 512) len = 512;
+ str[len] = '\0';
+ printf("Shader %s/%s error:\n%s\n", name, type, str);
+}
+
+static void glnvg__dumpProgramError(GLuint prog, const char* name)
+{
+ GLchar str[512+1];
+ GLsizei len = 0;
+ glGetProgramInfoLog(prog, 512, &len, str);
+ if (len > 512) len = 512;
+ str[len] = '\0';
+ printf("Program %s error:\n%s\n", name, str);
+}
+
+static void glnvg__checkError(GLNVGcontext* gl, const char* str)
+{
+ GLenum err;
+ if ((gl->flags & NVG_DEBUG) == 0) return;
+ err = glGetError();
+ if (err != GL_NO_ERROR) {
+ printf("Error %08x after %s\n", err, str);
+ return;
+ }
+}
+
+static int glnvg__createShader(GLNVGshader* shader, const char* name, const char* header, const char* opts, const char* vshader, const char* fshader)
+{
+ GLint status;
+ GLuint prog, vert, frag;
+ const char* str[3];
+ str[0] = header;
+ str[1] = opts != NULL ? opts : "";
+
+ memset(shader, 0, sizeof(*shader));
+
+ prog = glCreateProgram();
+ vert = glCreateShader(GL_VERTEX_SHADER);
+ frag = glCreateShader(GL_FRAGMENT_SHADER);
+ str[2] = vshader;
+ glShaderSource(vert, 3, str, 0);
+ str[2] = fshader;
+ glShaderSource(frag, 3, str, 0);
+
+ glCompileShader(vert);
+ glGetShaderiv(vert, GL_COMPILE_STATUS, &status);
+ if (status != GL_TRUE) {
+ glnvg__dumpShaderError(vert, name, "vert");
+ return 0;
+ }
+
+ glCompileShader(frag);
+ glGetShaderiv(frag, GL_COMPILE_STATUS, &status);
+ if (status != GL_TRUE) {
+ glnvg__dumpShaderError(frag, name, "frag");
+ return 0;
+ }
+
+ glAttachShader(prog, vert);
+ glAttachShader(prog, frag);
+
+ glBindAttribLocation(prog, 0, "vertex");
+ glBindAttribLocation(prog, 1, "tcoord");
+
+ glLinkProgram(prog);
+ glGetProgramiv(prog, GL_LINK_STATUS, &status);
+ if (status != GL_TRUE) {
+ glnvg__dumpProgramError(prog, name);
+ return 0;
+ }
+
+ shader->prog = prog;
+ shader->vert = vert;
+ shader->frag = frag;
+
+ return 1;
+}
+
+static void glnvg__deleteShader(GLNVGshader* shader)
+{
+ if (shader->prog != 0)
+ glDeleteProgram(shader->prog);
+ if (shader->vert != 0)
+ glDeleteShader(shader->vert);
+ if (shader->frag != 0)
+ glDeleteShader(shader->frag);
+}
+
+static void glnvg__getUniforms(GLNVGshader* shader)
+{
+ shader->loc[GLNVG_LOC_VIEWSIZE] = glGetUniformLocation(shader->prog, "viewSize");
+ shader->loc[GLNVG_LOC_TEX] = glGetUniformLocation(shader->prog, "tex");
+
+#if NANOVG_GL_USE_UNIFORMBUFFER
+ shader->loc[GLNVG_LOC_FRAG] = glGetUniformBlockIndex(shader->prog, "frag");
+#else
+ shader->loc[GLNVG_LOC_FRAG] = glGetUniformLocation(shader->prog, "frag");
+#endif
+}
+
+static int glnvg__renderCreate(void* uptr)
+{
+ GLNVGcontext* gl = (GLNVGcontext*)uptr;
+ int align = 4;
+
+ // TODO: mediump float may not be enough for GLES2 in iOS.
+ // see the following discussion: https://github.com/memononen/nanovg/issues/46
+ static const char* shaderHeader =
+#if defined NANOVG_GL2
+ "#define NANOVG_GL2 1\n"
+#elif defined NANOVG_GL3
+ "#version 140\n"
+ "#define NANOVG_GL3 1\n"
+#elif defined NANOVG_GLES2
+ "#version 100\n"
+ "#define NANOVG_GL2 1\n"
+#elif defined NANOVG_GLES3
+ "#version 300 es\n"
+ "#define NANOVG_GL3 1\n"
+#endif
+
+#if NANOVG_GL_USE_UNIFORMBUFFER
+ "#define USE_UNIFORMBUFFER 1\n"
+#else
+ "#define UNIFORMARRAY_SIZE 11\n"
+#endif
+ "\n";
+
+ static const char* fillVertShader =
+ "#ifdef NANOVG_GL3\n"
+ " uniform vec2 viewSize;\n"
+ " in vec2 vertex;\n"
+ " in vec2 tcoord;\n"
+ " out vec2 ftcoord;\n"
+ " out vec2 fpos;\n"
+ "#else\n"
+ " uniform vec2 viewSize;\n"
+ " attribute vec2 vertex;\n"
+ " attribute vec2 tcoord;\n"
+ " varying vec2 ftcoord;\n"
+ " varying vec2 fpos;\n"
+ "#endif\n"
+ "void main(void) {\n"
+ " ftcoord = tcoord;\n"
+ " fpos = vertex;\n"
+ " gl_Position = vec4(2.0*vertex.x/viewSize.x - 1.0, 1.0 - 2.0*vertex.y/viewSize.y, 0, 1);\n"
+ "}\n";
+
+ static const char* fillFragShader =
+ "#ifdef GL_ES\n"
+ "#if defined(GL_FRAGMENT_PRECISION_HIGH) || defined(NANOVG_GL3)\n"
+ " precision highp float;\n"
+ "#else\n"
+ " precision mediump float;\n"
+ "#endif\n"
+ "#endif\n"
+ "#ifdef NANOVG_GL3\n"
+ "#ifdef USE_UNIFORMBUFFER\n"
+ " layout(std140) uniform frag {\n"
+ " mat3 scissorMat;\n"
+ " mat3 paintMat;\n"
+ " vec4 innerCol;\n"
+ " vec4 outerCol;\n"
+ " vec2 scissorExt;\n"
+ " vec2 scissorScale;\n"
+ " vec2 extent;\n"
+ " float radius;\n"
+ " float feather;\n"
+ " float strokeMult;\n"
+ " float strokeThr;\n"
+ " int texType;\n"
+ " int type;\n"
+ " };\n"
+ "#else\n" // NANOVG_GL3 && !USE_UNIFORMBUFFER
+ " uniform vec4 frag[UNIFORMARRAY_SIZE];\n"
+ "#endif\n"
+ " uniform sampler2D tex;\n"
+ " in vec2 ftcoord;\n"
+ " in vec2 fpos;\n"
+ " out vec4 outColor;\n"
+ "#else\n" // !NANOVG_GL3
+ " uniform vec4 frag[UNIFORMARRAY_SIZE];\n"
+ " uniform sampler2D tex;\n"
+ " varying vec2 ftcoord;\n"
+ " varying vec2 fpos;\n"
+ "#endif\n"
+ "#ifndef USE_UNIFORMBUFFER\n"
+ " #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz)\n"
+ " #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz)\n"
+ " #define innerCol frag[6]\n"
+ " #define outerCol frag[7]\n"
+ " #define scissorExt frag[8].xy\n"
+ " #define scissorScale frag[8].zw\n"
+ " #define extent frag[9].xy\n"
+ " #define radius frag[9].z\n"
+ " #define feather frag[9].w\n"
+ " #define strokeMult frag[10].x\n"
+ " #define strokeThr frag[10].y\n"
+ " #define texType int(frag[10].z)\n"
+ " #define type int(frag[10].w)\n"
+ "#endif\n"
+ "\n"
+ "float sdroundrect(vec2 pt, vec2 ext, float rad) {\n"
+ " vec2 ext2 = ext - vec2(rad,rad);\n"
+ " vec2 d = abs(pt) - ext2;\n"
+ " return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rad;\n"
+ "}\n"
+ "\n"
+ "// Scissoring\n"
+ "float scissorMask(vec2 p) {\n"
+ " vec2 sc = (abs((scissorMat * vec3(p,1.0)).xy) - scissorExt);\n"
+ " sc = vec2(0.5,0.5) - sc * scissorScale;\n"
+ " return clamp(sc.x,0.0,1.0) * clamp(sc.y,0.0,1.0);\n"
+ "}\n"
+ "#ifdef EDGE_AA\n"
+ "// Stroke - from [0..1] to clipped pyramid, where the slope is 1px.\n"
+ "float strokeMask() {\n"
+ " return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult) * min(1.0, ftcoord.y);\n"
+ "}\n"
+ "#endif\n"
+ "\n"
+ "void main(void) {\n"
+ " vec4 result;\n"
+ " float scissor = scissorMask(fpos);\n"
+ "#ifdef EDGE_AA\n"
+ " float strokeAlpha = strokeMask();\n"
+ " if (strokeAlpha < strokeThr) discard;\n"
+ "#else\n"
+ " float strokeAlpha = 1.0;\n"
+ "#endif\n"
+ " if (type == 0) { // Gradient\n"
+ " // Calculate gradient color using box gradient\n"
+ " vec2 pt = (paintMat * vec3(fpos,1.0)).xy;\n"
+ " float d = clamp((sdroundrect(pt, extent, radius) + feather*0.5) / feather, 0.0, 1.0);\n"
+ " vec4 color = mix(innerCol,outerCol,d);\n"
+ " // Combine alpha\n"
+ " color *= strokeAlpha * scissor;\n"
+ " result = color;\n"
+ " } else if (type == 1) { // Image\n"
+ " // Calculate color fron texture\n"
+ " vec2 pt = (paintMat * vec3(fpos,1.0)).xy / extent;\n"
+ "#ifdef NANOVG_GL3\n"
+ " vec4 color = texture(tex, pt);\n"
+ "#else\n"
+ " vec4 color = texture2D(tex, pt);\n"
+ "#endif\n"
+ " if (texType == 1) color = vec4(color.xyz*color.w,color.w);"
+ " if (texType == 2) color = vec4(color.x);"
+ " // Apply color tint and alpha.\n"
+ " color *= innerCol;\n"
+ " // Combine alpha\n"
+ " color *= strokeAlpha * scissor;\n"
+ " result = color;\n"
+ " } else if (type == 2) { // Stencil fill\n"
+ " result = vec4(1,1,1,1);\n"
+ " } else if (type == 3) { // Textured tris\n"
+ "#ifdef NANOVG_GL3\n"
+ " vec4 color = texture(tex, ftcoord);\n"
+ "#else\n"
+ " vec4 color = texture2D(tex, ftcoord);\n"
+ "#endif\n"
+ " if (texType == 1) color = vec4(color.xyz*color.w,color.w);"
+ " if (texType == 2) color = vec4(color.x);"
+ " color *= scissor;\n"
+ " result = color * innerCol;\n"
+ " }\n"
+ "#ifdef NANOVG_GL3\n"
+ " outColor = result;\n"
+ "#else\n"
+ " gl_FragColor = result;\n"
+ "#endif\n"
+ "}\n";
+
+ glnvg__checkError(gl, "init");
+
+ if (gl->flags & NVG_ANTIALIAS) {
+ if (glnvg__createShader(&gl->shader, "shader", shaderHeader, "#define EDGE_AA 1\n", fillVertShader, fillFragShader) == 0)
+ return 0;
+ } else {
+ if (glnvg__createShader(&gl->shader, "shader", shaderHeader, NULL, fillVertShader, fillFragShader) == 0)
+ return 0;
+ }
+
+ glnvg__checkError(gl, "uniform locations");
+ glnvg__getUniforms(&gl->shader);
+
+ // Create dynamic vertex array
+#if defined NANOVG_GL3
+ glGenVertexArrays(1, &gl->vertArr);
+#endif
+ glGenBuffers(1, &gl->vertBuf);
+
+#if NANOVG_GL_USE_UNIFORMBUFFER
+ // Create UBOs
+ glUniformBlockBinding(gl->shader.prog, gl->shader.loc[GLNVG_LOC_FRAG], GLNVG_FRAG_BINDING);
+ glGenBuffers(1, &gl->fragBuf);
+ glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &align);
+#endif
+ gl->fragSize = sizeof(GLNVGfragUniforms) + align - sizeof(GLNVGfragUniforms) % align;
+
+ glnvg__checkError(gl, "create done");
+
+ glFinish();
+
+ return 1;
+}
+
+static int glnvg__renderCreateTexture(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data)
+{
+ GLNVGcontext* gl = (GLNVGcontext*)uptr;
+ GLNVGtexture* tex = glnvg__allocTexture(gl);
+
+ if (tex == NULL) return 0;
+
+#ifdef NANOVG_GLES2
+ // Check for non-power of 2.
+ if (glnvg__nearestPow2(w) != (unsigned int)w || glnvg__nearestPow2(h) != (unsigned int)h) {
+ // No repeat
+ if ((imageFlags & NVG_IMAGE_REPEATX) != 0 || (imageFlags & NVG_IMAGE_REPEATY) != 0) {
+ printf("Repeat X/Y is not supported for non power-of-two textures (%d x %d)\n", w, h);
+ imageFlags &= ~(NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY);
+ }
+ // No mips.
+ if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS) {
+ printf("Mip-maps is not support for non power-of-two textures (%d x %d)\n", w, h);
+ imageFlags &= ~NVG_IMAGE_GENERATE_MIPMAPS;
+ }
+ }
+#endif
+
+ glGenTextures(1, &tex->tex);
+ tex->width = w;
+ tex->height = h;
+ tex->type = type;
+ tex->flags = imageFlags;
+ glnvg__bindTexture(gl, tex->tex);
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT,1);
+#ifndef NANOVG_GLES2
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->width);
+ glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
+ glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
+#endif
+
+#if defined (NANOVG_GL2)
+ // GL 1.4 and later has support for generating mipmaps using a tex parameter.
+ if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS) {
+ glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
+ }
+#endif
+
+ if (type == NVG_TEXTURE_RGBA)
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ else
+#if defined(NANOVG_GLES2) || defined (NANOVG_GL2)
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w, h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
+#elif defined(NANOVG_GLES3)
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, data);
+#else
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, data);
+#endif
+
+ if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS) {
+ if (imageFlags & NVG_IMAGE_NEAREST) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
+ } else {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ }
+ } else {
+ if (imageFlags & NVG_IMAGE_NEAREST) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ } else {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ }
+ }
+
+ if (imageFlags & NVG_IMAGE_NEAREST) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ } else {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ }
+
+ if (imageFlags & NVG_IMAGE_REPEATX)
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ else
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+
+ if (imageFlags & NVG_IMAGE_REPEATY)
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ else
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+#ifndef NANOVG_GLES2
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
+ glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
+#endif
+
+ // The new way to build mipmaps on GLES and GL3
+#if !defined(NANOVG_GL2)
+ if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS) {
+ glGenerateMipmap(GL_TEXTURE_2D);
+ }
+#endif
+
+ glnvg__checkError(gl, "create tex");
+ glnvg__bindTexture(gl, 0);
+
+ return tex->id;
+}
+
+
+static int glnvg__renderDeleteTexture(void* uptr, int image)
+{
+ GLNVGcontext* gl = (GLNVGcontext*)uptr;
+ return glnvg__deleteTexture(gl, image);
+}
+
+static int glnvg__renderUpdateTexture(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data)
+{
+ GLNVGcontext* gl = (GLNVGcontext*)uptr;
+ GLNVGtexture* tex = glnvg__findTexture(gl, image);
+
+ if (tex == NULL) return 0;
+ glnvg__bindTexture(gl, tex->tex);
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT,1);
+
+#ifndef NANOVG_GLES2
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->width);
+ glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
+ glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
+#else
+ // No support for all of skip, need to update a whole row at a time.
+ if (tex->type == NVG_TEXTURE_RGBA)
+ data += y*tex->width*4;
+ else
+ data += y*tex->width;
+ x = 0;
+ w = tex->width;
+#endif
+
+ if (tex->type == NVG_TEXTURE_RGBA)
+ glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ else
+#if defined(NANOVG_GLES2) || defined(NANOVG_GL2)
+ glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
+#else
+ glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_RED, GL_UNSIGNED_BYTE, data);
+#endif
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+#ifndef NANOVG_GLES2
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
+ glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
+#endif
+
+ glnvg__bindTexture(gl, 0);
+
+ return 1;
+}
+
+static int glnvg__renderGetTextureSize(void* uptr, int image, int* w, int* h)
+{
+ GLNVGcontext* gl = (GLNVGcontext*)uptr;
+ GLNVGtexture* tex = glnvg__findTexture(gl, image);
+ if (tex == NULL) return 0;
+ *w = tex->width;
+ *h = tex->height;
+ return 1;
+}
+
+static void glnvg__xformToMat3x4(float* m3, float* t)
+{
+ m3[0] = t[0];
+ m3[1] = t[1];
+ m3[2] = 0.0f;
+ m3[3] = 0.0f;
+ m3[4] = t[2];
+ m3[5] = t[3];
+ m3[6] = 0.0f;
+ m3[7] = 0.0f;
+ m3[8] = t[4];
+ m3[9] = t[5];
+ m3[10] = 1.0f;
+ m3[11] = 0.0f;
+}
+
+static NVGcolor glnvg__premulColor(NVGcolor c)
+{
+ c.r *= c.a;
+ c.g *= c.a;
+ c.b *= c.a;
+ return c;
+}
+
+static int glnvg__convertPaint(GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGpaint* paint,
+ NVGscissor* scissor, float width, float fringe, float strokeThr)
+{
+ GLNVGtexture* tex = NULL;
+ float invxform[6];
+
+ memset(frag, 0, sizeof(*frag));
+
+ frag->innerCol = glnvg__premulColor(paint->innerColor);
+ frag->outerCol = glnvg__premulColor(paint->outerColor);
+
+ if (scissor->extent[0] < -0.5f || scissor->extent[1] < -0.5f) {
+ memset(frag->scissorMat, 0, sizeof(frag->scissorMat));
+ frag->scissorExt[0] = 1.0f;
+ frag->scissorExt[1] = 1.0f;
+ frag->scissorScale[0] = 1.0f;
+ frag->scissorScale[1] = 1.0f;
+ } else {
+ nvgTransformInverse(invxform, scissor->xform);
+ glnvg__xformToMat3x4(frag->scissorMat, invxform);
+ frag->scissorExt[0] = scissor->extent[0];
+ frag->scissorExt[1] = scissor->extent[1];
+ frag->scissorScale[0] = sqrtf(scissor->xform[0]*scissor->xform[0] + scissor->xform[2]*scissor->xform[2]) / fringe;
+ frag->scissorScale[1] = sqrtf(scissor->xform[1]*scissor->xform[1] + scissor->xform[3]*scissor->xform[3]) / fringe;
+ }
+
+ memcpy(frag->extent, paint->extent, sizeof(frag->extent));
+ frag->strokeMult = (width*0.5f + fringe*0.5f) / fringe;
+ frag->strokeThr = strokeThr;
+
+ if (paint->image != 0) {
+ tex = glnvg__findTexture(gl, paint->image);
+ if (tex == NULL) return 0;
+ if ((tex->flags & NVG_IMAGE_FLIPY) != 0) {
+ float m1[6], m2[6];
+ nvgTransformTranslate(m1, 0.0f, frag->extent[1] * 0.5f);
+ nvgTransformMultiply(m1, paint->xform);
+ nvgTransformScale(m2, 1.0f, -1.0f);
+ nvgTransformMultiply(m2, m1);
+ nvgTransformTranslate(m1, 0.0f, -frag->extent[1] * 0.5f);
+ nvgTransformMultiply(m1, m2);
+ nvgTransformInverse(invxform, m1);
+ } else {
+ nvgTransformInverse(invxform, paint->xform);
+ }
+ frag->type = NSVG_SHADER_FILLIMG;
+
+ #if NANOVG_GL_USE_UNIFORMBUFFER
+ if (tex->type == NVG_TEXTURE_RGBA)
+ frag->texType = (tex->flags & NVG_IMAGE_PREMULTIPLIED) ? 0 : 1;
+ else
+ frag->texType = 2;
+ #else
+ if (tex->type == NVG_TEXTURE_RGBA)
+ frag->texType = (tex->flags & NVG_IMAGE_PREMULTIPLIED) ? 0.0f : 1.0f;
+ else
+ frag->texType = 2.0f;
+ #endif
+// printf("frag->texType = %d\n", frag->texType);
+ } else {
+ frag->type = NSVG_SHADER_FILLGRAD;
+ frag->radius = paint->radius;
+ frag->feather = paint->feather;
+ nvgTransformInverse(invxform, paint->xform);
+ }
+
+ glnvg__xformToMat3x4(frag->paintMat, invxform);
+
+ return 1;
+}
+
+static GLNVGfragUniforms* nvg__fragUniformPtr(GLNVGcontext* gl, int i);
+
+static void glnvg__setUniforms(GLNVGcontext* gl, int uniformOffset, int image)
+{
+#if NANOVG_GL_USE_UNIFORMBUFFER
+ glBindBufferRange(GL_UNIFORM_BUFFER, GLNVG_FRAG_BINDING, gl->fragBuf, uniformOffset, sizeof(GLNVGfragUniforms));
+#else
+ GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset);
+ glUniform4fv(gl->shader.loc[GLNVG_LOC_FRAG], NANOVG_GL_UNIFORMARRAY_SIZE, &(frag->uniformArray[0][0]));
+#endif
+
+ if (image != 0) {
+ GLNVGtexture* tex = glnvg__findTexture(gl, image);
+ glnvg__bindTexture(gl, tex != NULL ? tex->tex : 0);
+ glnvg__checkError(gl, "tex paint tex");
+ } else {
+ glnvg__bindTexture(gl, 0);
+ }
+}
+
+static void glnvg__renderViewport(void* uptr, float width, float height, float devicePixelRatio)
+{
+ NVG_NOTUSED(devicePixelRatio);
+ GLNVGcontext* gl = (GLNVGcontext*)uptr;
+ gl->view[0] = width;
+ gl->view[1] = height;
+}
+
+static void glnvg__fill(GLNVGcontext* gl, GLNVGcall* call)
+{
+ GLNVGpath* paths = &gl->paths[call->pathOffset];
+ int i, npaths = call->pathCount;
+
+ // Draw shapes
+ glEnable(GL_STENCIL_TEST);
+ glnvg__stencilMask(gl, 0xff);
+ glnvg__stencilFunc(gl, GL_ALWAYS, 0, 0xff);
+ glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+
+ // set bindpoint for solid loc
+ glnvg__setUniforms(gl, call->uniformOffset, 0);
+ glnvg__checkError(gl, "fill simple");
+
+ glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
+ glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
+ glDisable(GL_CULL_FACE);
+ for (i = 0; i < npaths; i++)
+ glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
+ glEnable(GL_CULL_FACE);
+
+ // Draw anti-aliased pixels
+ glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+
+ glnvg__setUniforms(gl, call->uniformOffset + gl->fragSize, call->image);
+ glnvg__checkError(gl, "fill fill");
+
+ if (gl->flags & NVG_ANTIALIAS) {
+ glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+ // Draw fringes
+ for (i = 0; i < npaths; i++)
+ glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
+ }
+
+ // Draw fill
+ glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x0, 0xff);
+ glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
+ glDrawArrays(GL_TRIANGLE_STRIP, call->triangleOffset, call->triangleCount);
+
+ glDisable(GL_STENCIL_TEST);
+}
+
+static void glnvg__convexFill(GLNVGcontext* gl, GLNVGcall* call)
+{
+ GLNVGpath* paths = &gl->paths[call->pathOffset];
+ int i, npaths = call->pathCount;
+
+ glnvg__setUniforms(gl, call->uniformOffset, call->image);
+ glnvg__checkError(gl, "convex fill");
+
+ for (i = 0; i < npaths; i++) {
+ glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount);
+ // Draw fringes
+ if (paths[i].strokeCount > 0) {
+ glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
+ }
+ }
+}
+
+static void glnvg__stroke(GLNVGcontext* gl, GLNVGcall* call)
+{
+ GLNVGpath* paths = &gl->paths[call->pathOffset];
+ int npaths = call->pathCount, i;
+
+ if (gl->flags & NVG_STENCIL_STROKES) {
+
+ glEnable(GL_STENCIL_TEST);
+ glnvg__stencilMask(gl, 0xff);
+
+ // Fill the stroke base without overlap
+ glnvg__stencilFunc(gl, GL_EQUAL, 0x0, 0xff);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
+ glnvg__setUniforms(gl, call->uniformOffset + gl->fragSize, call->image);
+ glnvg__checkError(gl, "stroke fill 0");
+ for (i = 0; i < npaths; i++)
+ glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
+
+ // Draw anti-aliased pixels.
+ glnvg__setUniforms(gl, call->uniformOffset, call->image);
+ glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+ for (i = 0; i < npaths; i++)
+ glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
+
+ // Clear stencil buffer.
+ glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+ glnvg__stencilFunc(gl, GL_ALWAYS, 0x0, 0xff);
+ glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
+ glnvg__checkError(gl, "stroke fill 1");
+ for (i = 0; i < npaths; i++)
+ glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
+ glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+
+ glDisable(GL_STENCIL_TEST);
+
+// glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset + gl->fragSize), paint, scissor, strokeWidth, fringe, 1.0f - 0.5f/255.0f);
+
+ } else {
+ glnvg__setUniforms(gl, call->uniformOffset, call->image);
+ glnvg__checkError(gl, "stroke fill");
+ // Draw Strokes
+ for (i = 0; i < npaths; i++)
+ glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount);
+ }
+}
+
+static void glnvg__triangles(GLNVGcontext* gl, GLNVGcall* call)
+{
+ glnvg__setUniforms(gl, call->uniformOffset, call->image);
+ glnvg__checkError(gl, "triangles fill");
+
+ glDrawArrays(GL_TRIANGLES, call->triangleOffset, call->triangleCount);
+}
+
+static void glnvg__renderCancel(void* uptr) {
+ GLNVGcontext* gl = (GLNVGcontext*)uptr;
+ gl->nverts = 0;
+ gl->npaths = 0;
+ gl->ncalls = 0;
+ gl->nuniforms = 0;
+}
+
+static GLenum glnvg_convertBlendFuncFactor(int factor)
+{
+ if (factor == NVG_ZERO)
+ return GL_ZERO;
+ if (factor == NVG_ONE)
+ return GL_ONE;
+ if (factor == NVG_SRC_COLOR)
+ return GL_SRC_COLOR;
+ if (factor == NVG_ONE_MINUS_SRC_COLOR)
+ return GL_ONE_MINUS_SRC_COLOR;
+ if (factor == NVG_DST_COLOR)
+ return GL_DST_COLOR;
+ if (factor == NVG_ONE_MINUS_DST_COLOR)
+ return GL_ONE_MINUS_DST_COLOR;
+ if (factor == NVG_SRC_ALPHA)
+ return GL_SRC_ALPHA;
+ if (factor == NVG_ONE_MINUS_SRC_ALPHA)
+ return GL_ONE_MINUS_SRC_ALPHA;
+ if (factor == NVG_DST_ALPHA)
+ return GL_DST_ALPHA;
+ if (factor == NVG_ONE_MINUS_DST_ALPHA)
+ return GL_ONE_MINUS_DST_ALPHA;
+ if (factor == NVG_SRC_ALPHA_SATURATE)
+ return GL_SRC_ALPHA_SATURATE;
+ return GL_INVALID_ENUM;
+}
+
+static GLNVGblend glnvg__blendCompositeOperation(NVGcompositeOperationState op)
+{
+ GLNVGblend blend;
+ blend.srcRGB = glnvg_convertBlendFuncFactor(op.srcRGB);
+ blend.dstRGB = glnvg_convertBlendFuncFactor(op.dstRGB);
+ blend.srcAlpha = glnvg_convertBlendFuncFactor(op.srcAlpha);
+ blend.dstAlpha = glnvg_convertBlendFuncFactor(op.dstAlpha);
+ if (blend.srcRGB == GL_INVALID_ENUM || blend.dstRGB == GL_INVALID_ENUM || blend.srcAlpha == GL_INVALID_ENUM || blend.dstAlpha == GL_INVALID_ENUM)
+ {
+ blend.srcRGB = GL_ONE;
+ blend.dstRGB = GL_ONE_MINUS_SRC_ALPHA;
+ blend.srcAlpha = GL_ONE;
+ blend.dstAlpha = GL_ONE_MINUS_SRC_ALPHA;
+ }
+ return blend;
+}
+
+static void glnvg__renderFlush(void* uptr)
+{
+ GLNVGcontext* gl = (GLNVGcontext*)uptr;
+ int i;
+
+ if (gl->ncalls > 0) {
+
+ // Setup require GL state.
+ glUseProgram(gl->shader.prog);
+
+ glEnable(GL_CULL_FACE);
+ glCullFace(GL_BACK);
+ glFrontFace(GL_CCW);
+ glEnable(GL_BLEND);
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_SCISSOR_TEST);
+ glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ glStencilMask(0xffffffff);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+ glStencilFunc(GL_ALWAYS, 0, 0xffffffff);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ #if NANOVG_GL_USE_STATE_FILTER
+ gl->boundTexture = 0;
+ gl->stencilMask = 0xffffffff;
+ gl->stencilFunc = GL_ALWAYS;
+ gl->stencilFuncRef = 0;
+ gl->stencilFuncMask = 0xffffffff;
+ gl->blendFunc.srcRGB = GL_INVALID_ENUM;
+ gl->blendFunc.srcAlpha = GL_INVALID_ENUM;
+ gl->blendFunc.dstRGB = GL_INVALID_ENUM;
+ gl->blendFunc.dstAlpha = GL_INVALID_ENUM;
+ #endif
+
+#if NANOVG_GL_USE_UNIFORMBUFFER
+ // Upload ubo for frag shaders
+ glBindBuffer(GL_UNIFORM_BUFFER, gl->fragBuf);
+ glBufferData(GL_UNIFORM_BUFFER, gl->nuniforms * gl->fragSize, gl->uniforms, GL_STREAM_DRAW);
+#endif
+
+ // Upload vertex data
+#if defined NANOVG_GL3
+ glBindVertexArray(gl->vertArr);
+#endif
+ glBindBuffer(GL_ARRAY_BUFFER, gl->vertBuf);
+ glBufferData(GL_ARRAY_BUFFER, gl->nverts * sizeof(NVGvertex), gl->verts, GL_STREAM_DRAW);
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(1);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(NVGvertex), (const GLvoid*)(size_t)0);
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(NVGvertex), (const GLvoid*)(0 + 2*sizeof(float)));
+
+ // Set view and texture just once per frame.
+ glUniform1i(gl->shader.loc[GLNVG_LOC_TEX], 0);
+ glUniform2fv(gl->shader.loc[GLNVG_LOC_VIEWSIZE], 1, gl->view);
+
+#if NANOVG_GL_USE_UNIFORMBUFFER
+ glBindBuffer(GL_UNIFORM_BUFFER, gl->fragBuf);
+#endif
+
+ for (i = 0; i < gl->ncalls; i++) {
+ GLNVGcall* call = &gl->calls[i];
+ glnvg__blendFuncSeparate(gl,&call->blendFunc);
+ if (call->type == GLNVG_FILL)
+ glnvg__fill(gl, call);
+ else if (call->type == GLNVG_CONVEXFILL)
+ glnvg__convexFill(gl, call);
+ else if (call->type == GLNVG_STROKE)
+ glnvg__stroke(gl, call);
+ else if (call->type == GLNVG_TRIANGLES)
+ glnvg__triangles(gl, call);
+ }
+
+ glDisableVertexAttribArray(0);
+ glDisableVertexAttribArray(1);
+#if defined NANOVG_GL3
+ glBindVertexArray(0);
+#endif
+ glDisable(GL_CULL_FACE);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glUseProgram(0);
+ glnvg__bindTexture(gl, 0);
+ }
+
+ // Reset calls
+ gl->nverts = 0;
+ gl->npaths = 0;
+ gl->ncalls = 0;
+ gl->nuniforms = 0;
+}
+
+static int glnvg__maxVertCount(const NVGpath* paths, int npaths)
+{
+ int i, count = 0;
+ for (i = 0; i < npaths; i++) {
+ count += paths[i].nfill;
+ count += paths[i].nstroke;
+ }
+ return count;
+}
+
+static GLNVGcall* glnvg__allocCall(GLNVGcontext* gl)
+{
+ GLNVGcall* ret = NULL;
+ if (gl->ncalls+1 > gl->ccalls) {
+ GLNVGcall* calls;
+ int ccalls = glnvg__maxi(gl->ncalls+1, 128) + gl->ccalls/2; // 1.5x Overallocate
+ calls = (GLNVGcall*)realloc(gl->calls, sizeof(GLNVGcall) * ccalls);
+ if (calls == NULL) return NULL;
+ gl->calls = calls;
+ gl->ccalls = ccalls;
+ }
+ ret = &gl->calls[gl->ncalls++];
+ memset(ret, 0, sizeof(GLNVGcall));
+ return ret;
+}
+
+static int glnvg__allocPaths(GLNVGcontext* gl, int n)
+{
+ int ret = 0;
+ if (gl->npaths+n > gl->cpaths) {
+ GLNVGpath* paths;
+ int cpaths = glnvg__maxi(gl->npaths + n, 128) + gl->cpaths/2; // 1.5x Overallocate
+ paths = (GLNVGpath*)realloc(gl->paths, sizeof(GLNVGpath) * cpaths);
+ if (paths == NULL) return -1;
+ gl->paths = paths;
+ gl->cpaths = cpaths;
+ }
+ ret = gl->npaths;
+ gl->npaths += n;
+ return ret;
+}
+
+static int glnvg__allocVerts(GLNVGcontext* gl, int n)
+{
+ int ret = 0;
+ if (gl->nverts+n > gl->cverts) {
+ NVGvertex* verts;
+ int cverts = glnvg__maxi(gl->nverts + n, 4096) + gl->cverts/2; // 1.5x Overallocate
+ verts = (NVGvertex*)realloc(gl->verts, sizeof(NVGvertex) * cverts);
+ if (verts == NULL) return -1;
+ gl->verts = verts;
+ gl->cverts = cverts;
+ }
+ ret = gl->nverts;
+ gl->nverts += n;
+ return ret;
+}
+
+static int glnvg__allocFragUniforms(GLNVGcontext* gl, int n)
+{
+ int ret = 0, structSize = gl->fragSize;
+ if (gl->nuniforms+n > gl->cuniforms) {
+ unsigned char* uniforms;
+ int cuniforms = glnvg__maxi(gl->nuniforms+n, 128) + gl->cuniforms/2; // 1.5x Overallocate
+ uniforms = (unsigned char*)realloc(gl->uniforms, structSize * cuniforms);
+ if (uniforms == NULL) return -1;
+ gl->uniforms = uniforms;
+ gl->cuniforms = cuniforms;
+ }
+ ret = gl->nuniforms * structSize;
+ gl->nuniforms += n;
+ return ret;
+}
+
+static GLNVGfragUniforms* nvg__fragUniformPtr(GLNVGcontext* gl, int i)
+{
+ return (GLNVGfragUniforms*)&gl->uniforms[i];
+}
+
+static void glnvg__vset(NVGvertex* vtx, float x, float y, float u, float v)
+{
+ vtx->x = x;
+ vtx->y = y;
+ vtx->u = u;
+ vtx->v = v;
+}
+
+static void glnvg__renderFill(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe,
+ const float* bounds, const NVGpath* paths, int npaths)
+{
+ GLNVGcontext* gl = (GLNVGcontext*)uptr;
+ GLNVGcall* call = glnvg__allocCall(gl);
+ NVGvertex* quad;
+ GLNVGfragUniforms* frag;
+ int i, maxverts, offset;
+
+ if (call == NULL) return;
+
+ call->type = GLNVG_FILL;
+ call->triangleCount = 4;
+ call->pathOffset = glnvg__allocPaths(gl, npaths);
+ if (call->pathOffset == -1) goto error;
+ call->pathCount = npaths;
+ call->image = paint->image;
+ call->blendFunc = glnvg__blendCompositeOperation(compositeOperation);
+
+ if (npaths == 1 && paths[0].convex)
+ {
+ call->type = GLNVG_CONVEXFILL;
+ call->triangleCount = 0; // Bounding box fill quad not needed for convex fill
+ }
+
+ // Allocate vertices for all the paths.
+ maxverts = glnvg__maxVertCount(paths, npaths) + call->triangleCount;
+ offset = glnvg__allocVerts(gl, maxverts);
+ if (offset == -1) goto error;
+
+ for (i = 0; i < npaths; i++) {
+ GLNVGpath* copy = &gl->paths[call->pathOffset + i];
+ const NVGpath* path = &paths[i];
+ memset(copy, 0, sizeof(GLNVGpath));
+ if (path->nfill > 0) {
+ copy->fillOffset = offset;
+ copy->fillCount = path->nfill;
+ memcpy(&gl->verts[offset], path->fill, sizeof(NVGvertex) * path->nfill);
+ offset += path->nfill;
+ }
+ if (path->nstroke > 0) {
+ copy->strokeOffset = offset;
+ copy->strokeCount = path->nstroke;
+ memcpy(&gl->verts[offset], path->stroke, sizeof(NVGvertex) * path->nstroke);
+ offset += path->nstroke;
+ }
+ }
+
+ // Setup uniforms for draw calls
+ if (call->type == GLNVG_FILL) {
+ // Quad
+ call->triangleOffset = offset;
+ quad = &gl->verts[call->triangleOffset];
+ glnvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f);
+ glnvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f);
+ glnvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f);
+ glnvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f);
+
+ call->uniformOffset = glnvg__allocFragUniforms(gl, 2);
+ if (call->uniformOffset == -1) goto error;
+ // Simple shader for stencil
+ frag = nvg__fragUniformPtr(gl, call->uniformOffset);
+ memset(frag, 0, sizeof(*frag));
+ frag->strokeThr = -1.0f;
+ frag->type = NSVG_SHADER_SIMPLE;
+ // Fill shader
+ glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset + gl->fragSize), paint, scissor, fringe, fringe, -1.0f);
+ } else {
+ call->uniformOffset = glnvg__allocFragUniforms(gl, 1);
+ if (call->uniformOffset == -1) goto error;
+ // Fill shader
+ glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset), paint, scissor, fringe, fringe, -1.0f);
+ }
+
+ return;
+
+error:
+ // We get here if call alloc was ok, but something else is not.
+ // Roll back the last call to prevent drawing it.
+ if (gl->ncalls > 0) gl->ncalls--;
+}
+
+static void glnvg__renderStroke(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe,
+ float strokeWidth, const NVGpath* paths, int npaths)
+{
+ GLNVGcontext* gl = (GLNVGcontext*)uptr;
+ GLNVGcall* call = glnvg__allocCall(gl);
+ int i, maxverts, offset;
+
+ if (call == NULL) return;
+
+ call->type = GLNVG_STROKE;
+ call->pathOffset = glnvg__allocPaths(gl, npaths);
+ if (call->pathOffset == -1) goto error;
+ call->pathCount = npaths;
+ call->image = paint->image;
+ call->blendFunc = glnvg__blendCompositeOperation(compositeOperation);
+
+ // Allocate vertices for all the paths.
+ maxverts = glnvg__maxVertCount(paths, npaths);
+ offset = glnvg__allocVerts(gl, maxverts);
+ if (offset == -1) goto error;
+
+ for (i = 0; i < npaths; i++) {
+ GLNVGpath* copy = &gl->paths[call->pathOffset + i];
+ const NVGpath* path = &paths[i];
+ memset(copy, 0, sizeof(GLNVGpath));
+ if (path->nstroke) {
+ copy->strokeOffset = offset;
+ copy->strokeCount = path->nstroke;
+ memcpy(&gl->verts[offset], path->stroke, sizeof(NVGvertex) * path->nstroke);
+ offset += path->nstroke;
+ }
+ }
+
+ if (gl->flags & NVG_STENCIL_STROKES) {
+ // Fill shader
+ call->uniformOffset = glnvg__allocFragUniforms(gl, 2);
+ if (call->uniformOffset == -1) goto error;
+
+ glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
+ glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset + gl->fragSize), paint, scissor, strokeWidth, fringe, 1.0f - 0.5f/255.0f);
+
+ } else {
+ // Fill shader
+ call->uniformOffset = glnvg__allocFragUniforms(gl, 1);
+ if (call->uniformOffset == -1) goto error;
+ glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
+ }
+
+ return;
+
+error:
+ // We get here if call alloc was ok, but something else is not.
+ // Roll back the last call to prevent drawing it.
+ if (gl->ncalls > 0) gl->ncalls--;
+}
+
+static void glnvg__renderTriangles(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor,
+ const NVGvertex* verts, int nverts)
+{
+ GLNVGcontext* gl = (GLNVGcontext*)uptr;
+ GLNVGcall* call = glnvg__allocCall(gl);
+ GLNVGfragUniforms* frag;
+
+ if (call == NULL) return;
+
+ call->type = GLNVG_TRIANGLES;
+ call->image = paint->image;
+ call->blendFunc = glnvg__blendCompositeOperation(compositeOperation);
+
+ // Allocate vertices for all the paths.
+ call->triangleOffset = glnvg__allocVerts(gl, nverts);
+ if (call->triangleOffset == -1) goto error;
+ call->triangleCount = nverts;
+
+ memcpy(&gl->verts[call->triangleOffset], verts, sizeof(NVGvertex) * nverts);
+
+ // Fill shader
+ call->uniformOffset = glnvg__allocFragUniforms(gl, 1);
+ if (call->uniformOffset == -1) goto error;
+ frag = nvg__fragUniformPtr(gl, call->uniformOffset);
+ glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, 1.0f, -1.0f);
+ frag->type = NSVG_SHADER_IMG;
+
+ return;
+
+error:
+ // We get here if call alloc was ok, but something else is not.
+ // Roll back the last call to prevent drawing it.
+ if (gl->ncalls > 0) gl->ncalls--;
+}
+
+static void glnvg__renderDelete(void* uptr)
+{
+ GLNVGcontext* gl = (GLNVGcontext*)uptr;
+ int i;
+ if (gl == NULL) return;
+
+ glnvg__deleteShader(&gl->shader);
+
+#if NANOVG_GL3
+#if NANOVG_GL_USE_UNIFORMBUFFER
+ if (gl->fragBuf != 0)
+ glDeleteBuffers(1, &gl->fragBuf);
+#endif
+ if (gl->vertArr != 0)
+ glDeleteVertexArrays(1, &gl->vertArr);
+#endif
+ if (gl->vertBuf != 0)
+ glDeleteBuffers(1, &gl->vertBuf);
+
+ for (i = 0; i < gl->ntextures; i++) {
+ if (gl->textures[i].tex != 0 && (gl->textures[i].flags & NVG_IMAGE_NODELETE) == 0)
+ glDeleteTextures(1, &gl->textures[i].tex);
+ }
+ free(gl->textures);
+
+ free(gl->paths);
+ free(gl->verts);
+ free(gl->uniforms);
+ free(gl->calls);
+
+ free(gl);
+}
+
+
+#if defined NANOVG_GL2
+NVGcontext* nvgCreateGL2(int flags)
+#elif defined NANOVG_GL3
+NVGcontext* nvgCreateGL3(int flags)
+#elif defined NANOVG_GLES2
+NVGcontext* nvgCreateGLES2(int flags)
+#elif defined NANOVG_GLES3
+NVGcontext* nvgCreateGLES3(int flags)
+#endif
+{
+ NVGparams params;
+ NVGcontext* ctx = NULL;
+ GLNVGcontext* gl = (GLNVGcontext*)malloc(sizeof(GLNVGcontext));
+ if (gl == NULL) goto error;
+ memset(gl, 0, sizeof(GLNVGcontext));
+
+ memset(&params, 0, sizeof(params));
+ params.renderCreate = glnvg__renderCreate;
+ params.renderCreateTexture = glnvg__renderCreateTexture;
+ params.renderDeleteTexture = glnvg__renderDeleteTexture;
+ params.renderUpdateTexture = glnvg__renderUpdateTexture;
+ params.renderGetTextureSize = glnvg__renderGetTextureSize;
+ params.renderViewport = glnvg__renderViewport;
+ params.renderCancel = glnvg__renderCancel;
+ params.renderFlush = glnvg__renderFlush;
+ params.renderFill = glnvg__renderFill;
+ params.renderStroke = glnvg__renderStroke;
+ params.renderTriangles = glnvg__renderTriangles;
+ params.renderDelete = glnvg__renderDelete;
+ params.userPtr = gl;
+ params.edgeAntiAlias = flags & NVG_ANTIALIAS ? 1 : 0;
+
+ gl->flags = flags;
+
+ ctx = nvgCreateInternal(&params);
+ if (ctx == NULL) goto error;
+
+ return ctx;
+
+error:
+ // 'gl' is freed by nvgDeleteInternal.
+ if (ctx != NULL) nvgDeleteInternal(ctx);
+ return NULL;
+}
+
+#if defined NANOVG_GL2
+void nvgDeleteGL2(NVGcontext* ctx)
+#elif defined NANOVG_GL3
+void nvgDeleteGL3(NVGcontext* ctx)
+#elif defined NANOVG_GLES2
+void nvgDeleteGLES2(NVGcontext* ctx)
+#elif defined NANOVG_GLES3
+void nvgDeleteGLES3(NVGcontext* ctx)
+#endif
+{
+ nvgDeleteInternal(ctx);
+}
+
+#if defined NANOVG_GL2
+int nvglCreateImageFromHandleGL2(NVGcontext* ctx, GLuint textureId, int w, int h, int imageFlags)
+#elif defined NANOVG_GL3
+int nvglCreateImageFromHandleGL3(NVGcontext* ctx, GLuint textureId, int w, int h, int imageFlags)
+#elif defined NANOVG_GLES2
+int nvglCreateImageFromHandleGLES2(NVGcontext* ctx, GLuint textureId, int w, int h, int imageFlags)
+#elif defined NANOVG_GLES3
+int nvglCreateImageFromHandleGLES3(NVGcontext* ctx, GLuint textureId, int w, int h, int imageFlags)
+#endif
+{
+ GLNVGcontext* gl = (GLNVGcontext*)nvgInternalParams(ctx)->userPtr;
+ GLNVGtexture* tex = glnvg__allocTexture(gl);
+
+ if (tex == NULL) return 0;
+
+ tex->type = NVG_TEXTURE_RGBA;
+ tex->tex = textureId;
+ tex->flags = imageFlags;
+ tex->width = w;
+ tex->height = h;
+
+ return tex->id;
+}
+
+#if defined NANOVG_GL2
+GLuint nvglImageHandleGL2(NVGcontext* ctx, int image)
+#elif defined NANOVG_GL3
+GLuint nvglImageHandleGL3(NVGcontext* ctx, int image)
+#elif defined NANOVG_GLES2
+GLuint nvglImageHandleGLES2(NVGcontext* ctx, int image)
+#elif defined NANOVG_GLES3
+GLuint nvglImageHandleGLES3(NVGcontext* ctx, int image)
+#endif
+{
+ GLNVGcontext* gl = (GLNVGcontext*)nvgInternalParams(ctx)->userPtr;
+ GLNVGtexture* tex = glnvg__findTexture(gl, image);
+ return tex->tex;
+}
+
+#endif /* NANOVG_GL_IMPLEMENTATION */
diff --git a/subprojects/d2tk/nanovg/src/nanovg_gl_utils.h b/subprojects/d2tk/nanovg/src/nanovg_gl_utils.h
new file mode 100644
index 0000000..f7384d8
--- /dev/null
+++ b/subprojects/d2tk/nanovg/src/nanovg_gl_utils.h
@@ -0,0 +1,154 @@
+//
+// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+//
+#ifndef NANOVG_GL_UTILS_H
+#define NANOVG_GL_UTILS_H
+
+struct NVGLUframebuffer {
+ NVGcontext* ctx;
+ GLuint fbo;
+ GLuint rbo;
+ GLuint texture;
+ int image;
+};
+typedef struct NVGLUframebuffer NVGLUframebuffer;
+
+// Helper function to create GL frame buffer to render to.
+void nvgluBindFramebuffer(NVGLUframebuffer* fb);
+NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imageFlags);
+void nvgluDeleteFramebuffer(NVGLUframebuffer* fb);
+
+#endif // NANOVG_GL_UTILS_H
+
+#ifdef NANOVG_GL_IMPLEMENTATION
+
+#if defined(NANOVG_GL3) || defined(NANOVG_GLES2) || defined(NANOVG_GLES3)
+// FBO is core in OpenGL 3>.
+# define NANOVG_FBO_VALID 1
+#elif defined(NANOVG_GL2)
+// On OS X including glext defines FBO on GL2 too.
+# ifdef __APPLE__
+# include <OpenGL/glext.h>
+# define NANOVG_FBO_VALID 1
+# endif
+#endif
+
+static GLint defaultFBO = -1;
+
+NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imageFlags)
+{
+#ifdef NANOVG_FBO_VALID
+ GLint defaultFBO;
+ GLint defaultRBO;
+ NVGLUframebuffer* fb = NULL;
+
+ glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO);
+ glGetIntegerv(GL_RENDERBUFFER_BINDING, &defaultRBO);
+
+ fb = (NVGLUframebuffer*)malloc(sizeof(NVGLUframebuffer));
+ if (fb == NULL) goto error;
+ memset(fb, 0, sizeof(NVGLUframebuffer));
+
+ fb->image = nvgCreateImageRGBA(ctx, w, h, imageFlags | NVG_IMAGE_FLIPY | NVG_IMAGE_PREMULTIPLIED, NULL);
+
+#if defined NANOVG_GL2
+ fb->texture = nvglImageHandleGL2(ctx, fb->image);
+#elif defined NANOVG_GL3
+ fb->texture = nvglImageHandleGL3(ctx, fb->image);
+#elif defined NANOVG_GLES2
+ fb->texture = nvglImageHandleGLES2(ctx, fb->image);
+#elif defined NANOVG_GLES3
+ fb->texture = nvglImageHandleGLES3(ctx, fb->image);
+#endif
+
+ fb->ctx = ctx;
+
+ // frame buffer object
+ glGenFramebuffers(1, &fb->fbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, fb->fbo);
+
+ // render buffer object
+ glGenRenderbuffers(1, &fb->rbo);
+ glBindRenderbuffer(GL_RENDERBUFFER, fb->rbo);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, w, h);
+
+ // combine all
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->texture, 0);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb->rbo);
+
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+#ifdef GL_DEPTH24_STENCIL8
+ // If GL_STENCIL_INDEX8 is not supported, try GL_DEPTH24_STENCIL8 as a fallback.
+ // Some graphics cards require a depth buffer along with a stencil.
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, w, h);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->texture, 0);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb->rbo);
+
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+#endif // GL_DEPTH24_STENCIL8
+ goto error;
+ }
+
+ glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
+ glBindRenderbuffer(GL_RENDERBUFFER, defaultRBO);
+ return fb;
+error:
+ glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
+ glBindRenderbuffer(GL_RENDERBUFFER, defaultRBO);
+ nvgluDeleteFramebuffer(fb);
+ return NULL;
+#else
+ NVG_NOTUSED(ctx);
+ NVG_NOTUSED(w);
+ NVG_NOTUSED(h);
+ NVG_NOTUSED(imageFlags);
+ return NULL;
+#endif
+}
+
+void nvgluBindFramebuffer(NVGLUframebuffer* fb)
+{
+#ifdef NANOVG_FBO_VALID
+ if (defaultFBO == -1) glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO);
+ glBindFramebuffer(GL_FRAMEBUFFER, fb != NULL ? fb->fbo : defaultFBO);
+#else
+ NVG_NOTUSED(fb);
+#endif
+}
+
+void nvgluDeleteFramebuffer(NVGLUframebuffer* fb)
+{
+#ifdef NANOVG_FBO_VALID
+ if (fb == NULL) return;
+ if (fb->fbo != 0)
+ glDeleteFramebuffers(1, &fb->fbo);
+ if (fb->rbo != 0)
+ glDeleteRenderbuffers(1, &fb->rbo);
+ if (fb->image >= 0)
+ nvgDeleteImage(fb->ctx, fb->image);
+ fb->ctx = NULL;
+ fb->fbo = 0;
+ fb->rbo = 0;
+ fb->texture = 0;
+ fb->image = -1;
+ free(fb);
+#else
+ NVG_NOTUSED(fb);
+#endif
+}
+
+#endif // NANOVG_GL_IMPLEMENTATION
diff --git a/subprojects/d2tk/nanovg/src/stb_image.h b/subprojects/d2tk/nanovg/src/stb_image.h
new file mode 100644
index 0000000..e06f7a1
--- /dev/null
+++ b/subprojects/d2tk/nanovg/src/stb_image.h
@@ -0,0 +1,6614 @@
+/* stb_image - v2.10 - public domain image loader - http://nothings.org/stb_image.h
+ 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-bit-per-channel (16 bpc not supported)
+
+ 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.
+
+
+ Revision 2.00 release notes:
+
+ - Progressive JPEG is now supported.
+
+ - PPM and PGM binary formats are now supported, thanks to Ken Miller.
+
+ - x86 platforms now make use of SSE2 SIMD instructions for
+ JPEG decoding, and ARM platforms can use NEON SIMD if requested.
+ This work was done by Fabian "ryg" Giesen. SSE2 is used by
+ default, but NEON must be enabled explicitly; see docs.
+
+ With other JPEG optimizations included in this version, we see
+ 2x speedup on a JPEG on an x86 machine, and a 1.5x speedup
+ on a JPEG on an ARM machine, relative to previous versions of this
+ library. The same results will not obtain for all JPGs and for all
+ x86/ARM machines. (Note that progressive JPEGs are significantly
+ slower to decode than regular JPEGs.) This doesn't mean that this
+ is the fastest JPEG decoder in the land; rather, it brings it
+ closer to parity with standard libraries. If you want the fastest
+ decode, look elsewhere. (See "Philosophy" section of docs below.)
+
+ See final bullet items below for more info on SIMD.
+
+ - Added STBI_MALLOC, STBI_REALLOC, and STBI_FREE macros for replacing
+ the memory allocator. Unlike other STBI libraries, these macros don't
+ support a context parameter, so if you need to pass a context in to
+ the allocator, you'll have to store it in a global or a thread-local
+ variable.
+
+ - Split existing STBI_NO_HDR flag into two flags, STBI_NO_HDR and
+ STBI_NO_LINEAR.
+ STBI_NO_HDR: suppress implementation of .hdr reader format
+ STBI_NO_LINEAR: suppress high-dynamic-range light-linear float API
+
+ - 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)
+
+ Note that you can define multiples of these, and you will get all
+ of them ("only x" and "only y" is interpreted to mean "only x&y").
+
+ - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still
+ want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB
+
+ - Compilation of all SIMD code can be suppressed with
+ #define STBI_NO_SIMD
+ It should not be necessary to disable SIMD unless you have issues
+ compiling (e.g. using an x86 compiler which doesn't support SSE
+ intrinsics or that doesn't support the method used to detect
+ SSE2 support at run-time), and even those can be reported as
+ bugs so I can refine the built-in compile-time checking to be
+ smarter.
+
+ - The old STBI_SIMD system which allowed installing a user-defined
+ IDCT etc. has been removed. If you need this, don't upgrade. My
+ assumption is that almost nobody was doing this, and those who
+ were will find the built-in SIMD more satisfactory anyway.
+
+ - RGB values computed for JPEG images are slightly different from
+ previous versions of stb_image. (This is due to using less
+ integer precision in SIMD.) The C code has been adjusted so
+ that the same RGB values will be computed regardless of whether
+ SIMD support is available, so your app should always produce
+ consistent results. But these results are slightly different from
+ previous versions. (Specifically, about 3% of available YCbCr values
+ will compute different RGB results from pre-1.49 versions by +-1;
+ most of the deviating values are one smaller in the G channel.)
+
+ - If you must produce consistent results with previous versions of
+ stb_image, #define STBI_JPEG_OLD and you will get the same results
+ you used to; however, you will not get the SIMD speedups for
+ the YCbCr-to-RGB conversion step (although you should still see
+ significant JPEG speedup from the other changes).
+
+ Please note that STBI_JPEG_OLD is a temporary feature; it will be
+ removed in future versions of the library. It is only intended for
+ near-term back-compatibility use.
+
+
+ Latest revision history:
+ 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
+ 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA
+ 2.07 (2015-09-13) partial animated GIF support
+ limited 16-bit PSD support
+ minor bugs, code cleanup, and compiler warnings
+ 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) additional corruption checking
+ stbi_set_flip_vertically_on_load
+ fix NEON support; fix mingw support
+ 2.02 (2015-01-19) fix incorrect assert, fix warning
+ 2.01 (2015-01-17) fix various warnings
+ 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG
+ 2.00 (2014-12-25) optimize JPEG, including x86 SSE2 & ARM NEON SIMD
+ progressive JPEG
+ PGM/PPM support
+ STBI_MALLOC,STBI_REALLOC,STBI_FREE
+ STBI_NO_*, STBI_ONLY_*
+ GIF bugfix
+ 1.48 (2014-12-14) fix incorrectly-named assert()
+ 1.47 (2014-12-14) 1/2/4-bit PNG support (both grayscale and paletted)
+ optimize PNG
+ fix bug in interlaced PNG with user-specified channel count
+
+ 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)
+ urraka@github (animated gif) Junggon Kim (PNM comments)
+ Daniel Gibson (16-bit TGA)
+
+ Optimizations & bugfixes
+ Fabian "ryg" Giesen
+ Arseny Kapoulkine
+
+ Bug & warning fixes
+ Marc LeBlanc David Woo Guillaume George Martins Mozeiko
+ Christpher Lloyd Martin Golini Jerry Jansson Joseph Thomson
+ Dave Moore Roy Eltham Hayaki Saito Phil Jordan
+ Won Chun Luke Graham Johan Duparc Nathan Reed
+ the Horde3D community Thomas Ruf Ronny Chevalier Nick Verigakis
+ Janez Zemva John Bartholomew Michal Cichon svdijk@github
+ Jonathan Blow Ken Hamada Tero Hanninen Baldur Karlsson
+ Laurent Gomila Cort Stratton Sergio Gonzalez romigrou@github
+ Aruelien Pocheville Thibault Reuille Cass Everitt
+ Ryamond Barbiero Paul Du Bois Engin Manap
+ Blazej Dariusz Roszkowski
+ Michaelangel007@github
+
+
+LICENSE
+
+This software is in the public domain. Where that dedication is not
+recognized, you are granted a perpetual, irrevocable license to copy,
+distribute, and modify this file as you see fit.
+
+*/
+
+#ifndef STBI_INCLUDE_STB_IMAGE_H
+#define STBI_INCLUDE_STB_IMAGE_H
+
+// DOCUMENTATION
+//
+// Limitations:
+// - no 16-bit-per-channel PNG
+// - no 12-bit-per-channel JPEG
+// - no JPEGs with arithmetic coding
+// - no 1-bit BMP
+// - 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 *comp -- outputs # of image components in image file
+// int req_comp -- 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 'req_comp' if req_comp is non-zero, or *comp otherwise.
+// If req_comp is non-zero, *comp has the number of components that _would_
+// have been output otherwise. E.g. if you set req_comp to 4, you will always
+// get RGBA output, but you can check *comp 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, *comp 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.
+//
+// ===========================================================================
+//
+// 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
+// make more explicit reasons why performance can't be emphasized.
+//
+// - Portable ("ease of use")
+// - Small 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.
+//
+// The output of the JPEG decoder is slightly different from versions where
+// SIMD support was introduced (that is, for versions before 1.49). The
+// difference is only +-1 in the 8-bit RGB channels, and only on a small
+// fraction of pixels. You can force the pre-1.49 behavior by defining
+// STBI_JPEG_OLD, but this will disable some of the SIMD decoding path
+// and hence cost some performance.
+//
+// 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 now supports loading HDR images in general, and currently
+// the Radiance .HDR file format, although the support is provided
+// generically. 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:
+//
+// By default we convert iphone-formatted PNGs back to RGB, even though
+// they are internally encoded differently. You can disable this conversion
+// by by calling stbi_convert_iphone_png_to_rgb(0), in which case
+// you will always just get the native iphone "format" through (which
+// is BGR stored in RGB).
+//
+// 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).
+//
+
+
+#ifndef STBI_NO_STDIO
+#include <stdio.h>
+#endif // STBI_NO_STDIO
+
+#define STBI_VERSION 1
+
+enum
+{
+ STBI_default = 0, // only used for req_comp
+
+ STBI_grey = 1,
+ STBI_grey_alpha = 2,
+ STBI_rgb = 3,
+ STBI_rgb_alpha = 4
+};
+
+typedef unsigned char stbi_uc;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef STB_IMAGE_STATIC
+#define STBIDEF static
+#else
+#define STBIDEF extern
+#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;
+
+STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp);
+STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *comp, int 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);
+
+#ifndef STBI_NO_STDIO
+STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp);
+// for stbi_load_from_file, file pointer is left pointing immediately after image
+#endif
+
+#ifndef STBI_NO_LINEAR
+ STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp);
+ STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp);
+ STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp);
+
+ #ifndef STBI_NO_STDIO
+ STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp);
+ #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
+// NOT 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);
+
+#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);
+
+#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);
+
+// 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 <stdarg.h>
+#include <stddef.h> // ptrdiff_t on osx
+#include <stdlib.h>
+#include <string.h>
+
+#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR)
+#include <math.h> // ldexp
+#endif
+
+#ifndef STBI_NO_STDIO
+#include <stdio.h>
+#endif
+
+#ifndef STBI_ASSERT
+#include <assert.h>
+#define STBI_ASSERT(x) assert(x)
+#endif
+
+
+#ifndef _MSC_VER
+ #ifdef __cplusplus
+ #define stbi_inline inline
+ #else
+ #define stbi_inline
+ #endif
+#else
+ #define stbi_inline __forceinline
+#endif
+
+
+#ifdef _MSC_VER
+typedef unsigned short stbi__uint16;
+typedef signed short stbi__int16;
+typedef unsigned int stbi__uint32;
+typedef signed int stbi__int32;
+#else
+#include <stdint.h>
+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) >> (32 - (y))))
+#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(STBI__X64_TARGET)) && !defined(__SSE2__) && !defined(STBI_NO_SIMD)
+// NOTE: not clear do we actually need this for the 64-bit path?
+// gcc doesn't support sse2 intrinsics unless you compile with -msse2,
+// (but compiling with -msse2 allows the compiler to use SSE2 everywhere;
+// this is just broken and gcc are jerks for not fixing it properly
+// http://www.virtualdub.org/blog/pivot/entry.php?id=363 )
+#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)
+#define STBI_SSE2
+#include <emmintrin.h>
+
+#ifdef _MSC_VER
+
+#if _MSC_VER >= 1400 // not VC6
+#include <intrin.h> // __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
+
+static int stbi__sse2_available()
+{
+ int info3 = stbi__cpuid3();
+ return ((info3 >> 26) & 1) != 0;
+}
+#else // assume GCC-style if not VC++
+#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16)))
+
+static int stbi__sse2_available()
+{
+#if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 // GCC 4.8 or later
+ // GCC 4.8+ has a nice way to do this
+ return __builtin_cpu_supports("sse2");
+#else
+ // portable way to do this, preferably without using GCC inline ASM?
+ // just bail for now.
+ return 0;
+#endif
+}
+#endif
+#endif
+
+// ARM NEON
+#if defined(STBI_NO_SIMD) && defined(STBI_NEON)
+#undef STBI_NEON
+#endif
+
+#ifdef STBI_NEON
+#include <arm_neon.h>
+// assume GCC or Clang on ARM targets
+#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16)))
+#endif
+
+#ifndef STBI_SIMD_ALIGN
+#define STBI_SIMD_ALIGN(type, name) type name
+#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];
+
+ 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->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->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)
+{
+ fseek((FILE*) user, n, SEEK_CUR);
+}
+
+static int stbi__stdio_eof(void *user)
+{
+ return feof((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;
+}
+
+#ifndef STBI_NO_JPEG
+static int stbi__jpeg_test(stbi__context *s);
+static stbi_uc *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
+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 stbi_uc *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
+static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_BMP
+static int stbi__bmp_test(stbi__context *s);
+static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
+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 stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
+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 stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
+static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp);
+#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);
+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 stbi_uc *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
+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 stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, 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 stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
+static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+// this is not threadsafe
+static const char *stbi__g_failure_reason;
+
+STBIDEF const char *stbi_failure_reason(void)
+{
+ return stbi__g_failure_reason;
+}
+
+static int stbi__err(const char *str)
+{
+ stbi__g_failure_reason = str;
+ return 0;
+}
+
+static void *stbi__malloc(size_t size)
+{
+ return STBI_MALLOC(size);
+}
+
+// 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 = 0;
+
+STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip)
+{
+ stbi__vertically_flip_on_load = flag_true_if_should_flip;
+}
+
+static unsigned char *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ #ifndef STBI_NO_JPEG
+ if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp);
+ #endif
+ #ifndef STBI_NO_PNG
+ if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp);
+ #endif
+ #ifndef STBI_NO_BMP
+ if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp);
+ #endif
+ #ifndef STBI_NO_GIF
+ if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp);
+ #endif
+ #ifndef STBI_NO_PSD
+ if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp);
+ #endif
+ #ifndef STBI_NO_PIC
+ if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp);
+ #endif
+ #ifndef STBI_NO_PNM
+ if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp);
+ #endif
+
+ #ifndef STBI_NO_HDR
+ if (stbi__hdr_test(s)) {
+ float *hdr = stbi__hdr_load(s, x,y,comp,req_comp);
+ 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);
+ #endif
+
+ return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt");
+}
+
+static unsigned char *stbi__load_flip(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ unsigned char *result = stbi__load_main(s, x, y, comp, req_comp);
+
+ if (stbi__vertically_flip_on_load && result != NULL) {
+ int w = *x, h = *y;
+ int depth = req_comp ? req_comp : *comp;
+ int row,col,z;
+ stbi_uc temp;
+
+ // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once
+ for (row = 0; row < (h>>1); row++) {
+ for (col = 0; col < w; col++) {
+ for (z = 0; z < depth; z++) {
+ temp = result[(row * w + col) * depth + z];
+ result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z];
+ result[((h - row - 1) * w + col) * depth + z] = temp;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+#ifndef STBI_NO_HDR
+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 w = *x, h = *y;
+ int depth = req_comp ? req_comp : *comp;
+ int row,col,z;
+ float temp;
+
+ // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once
+ for (row = 0; row < (h>>1); row++) {
+ for (col = 0; col < w; col++) {
+ for (z = 0; z < depth; z++) {
+ temp = result[(row * w + col) * depth + z];
+ result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z];
+ result[((h - row - 1) * w + col) * depth + z] = temp;
+ }
+ }
+ }
+ }
+}
+#endif
+
+#ifndef STBI_NO_STDIO
+
+static FILE *stbi__fopen(char const *filename, char const *mode)
+{
+ FILE *f;
+#if 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_flip(&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;
+}
+#endif //!STBI_NO_STDIO
+
+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_flip(&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_flip(&s,x,y,comp,req_comp);
+}
+
+#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)) {
+ float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp);
+ if (hdr_data)
+ stbi__float_postprocess(hdr_data,x,y,comp,req_comp);
+ return hdr_data;
+ }
+ #endif
+ data = stbi__load_flip(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
+ stbi__context s;
+ stbi__start_file(&s,f);
+ return stbi__hdr_test(&s);
+ #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);
+ 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;
+}
+
+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;
+}
+
+static void stbi__skip(stbi__context *s, int n)
+{
+ 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;
+}
+
+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;
+}
+
+static int stbi__get16be(stbi__context *s)
+{
+ int z = stbi__get8(s);
+ return (z << 8) + stbi__get8(s);
+}
+
+static stbi__uint32 stbi__get32be(stbi__context *s)
+{
+ stbi__uint32 z = stbi__get16be(s);
+ return (z << 16) + stbi__get16be(s);
+}
+
+#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);
+ return z + (stbi__get16le(s) << 16);
+}
+#endif
+
+#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// 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);
+}
+
+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(req_comp * x * y);
+ 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 COMBO(a,b) ((a)*8+(b))
+ #define CASE(a,b) case 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 (COMBO(img_n, req_comp)) {
+ CASE(1,2) dest[0]=src[0], dest[1]=255; break;
+ CASE(1,3) dest[0]=dest[1]=dest[2]=src[0]; break;
+ CASE(1,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; break;
+ CASE(2,1) dest[0]=src[0]; break;
+ CASE(2,3) dest[0]=dest[1]=dest[2]=src[0]; break;
+ CASE(2,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; break;
+ CASE(3,4) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; break;
+ CASE(3,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break;
+ CASE(3,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; break;
+ CASE(4,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break;
+ CASE(4,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; break;
+ CASE(4,3) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; break;
+ default: STBI_ASSERT(0);
+ }
+ #undef CASE
+ }
+
+ STBI_FREE(data);
+ return good;
+}
+
+#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 = (float *) stbi__malloc(x * y * comp * sizeof(float));
+ 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 (k < comp) output[i*comp + k] = data[i*comp+k]/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 = (stbi_uc *) stbi__malloc(x * y * comp);
+ 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_uc 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 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,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);
+ 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 >= (1 << 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 += (-1 << 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 << 8) + (run << 4) + (len + magbits));
+ }
+ }
+ }
+}
+
+static void stbi__grow_buffer_unsafe(stbi__jpeg *j)
+{
+ do {
+ int b = j->nomore ? 0 : stbi__get8(j->s);
+ if (b == 0xff) {
+ int c = stbi__get8(j->s);
+ 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 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];
+ 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<<n) + 1
+static int const stbi__jbias[16] = {0,-1,-3,-7,-15,-31,-63,-127,-255,-511,-1023,-2047,-4095,-8191,-16383,-32767};
+
+// combined JPEG 'receive' and JPEG 'extend', since baseline
+// always extends everything it receives.
+stbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n)
+{
+ unsigned int k;
+ int sgn;
+ if (j->code_bits < n) stbi__grow_buffer_unsafe(j);
+
+ sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB
+ k = stbi_lrot(j->code_buffer, n);
+ STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask)));
+ j->code_buffer = k & ~stbi__bmask[n];
+ k &= stbi__bmask[n];
+ j->code_bits -= n;
+ return k + (stbi__jbias[n] & ~sgn);
+}
+
+// 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);
+ 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);
+ 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 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_uc *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) 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;
+ dc = j->img_comp[b].dc_pred + diff;
+ j->img_comp[b].dc_pred = dc;
+ 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
+ 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);
+ diff = t ? stbi__extend_receive(j, t) : 0;
+
+ dc = j->img_comp[b].dc_pred + diff;
+ j->img_comp[b].dc_pred = dc;
+ data[0] = (short) (dc << 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
+ j->code_buffer <<= s;
+ j->code_bits -= s;
+ zig = stbi__jpeg_dezigzag[k++];
+ data[zig] = (short) ((r >> 8) << 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) << 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) << 12)
+
+// 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] << 2;
+ 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);
+ 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 = 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_uc *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;
+ int t = q & 15,i;
+ if (p != 0) 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__get8(z->s);
+ L -= 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];
+ }
+ 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) {
+ stbi__skip(z->s, stbi__get16be(z->s)-2);
+ return 1;
+ }
+ return 0;
+}
+
+// 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__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
+ c = stbi__get8(s);
+ if (c != 3 && c != 1) return stbi__err("bad component count","Corrupt JPEG"); // JFIF requires
+ 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");
+
+ for (i=0; i < s->img_n; ++i) {
+ z->img_comp[i].id = stbi__get8(s);
+ if (z->img_comp[i].id != i+1) // JFIF requires
+ if (z->img_comp[i].id != i) // some version of jpegtran outputs non-JFIF-compliant files!
+ return stbi__err("bad component ID","Corrupt JPEG");
+ 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 ((1 << 30) / s->img_x / s->img_n < s->img_y) 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;
+ }
+
+ // 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;
+ 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
+ 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].raw_data = stbi__malloc(z->img_comp[i].w2 * z->img_comp[i].h2+15);
+
+ if (z->img_comp[i].raw_data == NULL) {
+ for(--i; i >= 0; --i) {
+ STBI_FREE(z->img_comp[i].raw_data);
+ z->img_comp[i].raw_data = NULL;
+ }
+ return 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);
+ z->img_comp[i].linebuf = NULL;
+ if (z->progressive) {
+ z->img_comp[i].coeff_w = (z->img_comp[i].w2 + 7) >> 3;
+ z->img_comp[i].coeff_h = (z->img_comp[i].h2 + 7) >> 3;
+ z->img_comp[i].raw_coeff = STBI_MALLOC(z->img_comp[i].coeff_w * z->img_comp[i].coeff_h * 64 * sizeof(short) + 15);
+ z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15);
+ } else {
+ z->img_comp[i].coeff = 0;
+ z->img_comp[i].raw_coeff = 0;
+ }
+ }
+
+ 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->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;
+}
+
+// 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 ) {
+ // handle 0s at the end of image data from IP Kamera 9060
+ while (!stbi__at_eof(j->s)) {
+ int x = stbi__get8(j->s);
+ if (x == 255) {
+ j->marker = stbi__get8(j->s);
+ break;
+ } else if (x != 0) {
+ return stbi__err("junk before marker", "Corrupt JPEG");
+ }
+ }
+ // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0
+ }
+ } else {
+ if (!stbi__process_marker(j, m)) return 0;
+ }
+ 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;
+}
+
+#ifdef STBI_JPEG_OLD
+// this is the same YCbCr-to-RGB calculation that stb_image has used
+// historically before the algorithm changes in 1.49
+#define float2fixed(x) ((int) ((x) * 65536 + 0.5))
+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] << 16) + 32768; // rounding
+ int r,g,b;
+ int cr = pcr[i] - 128;
+ int cb = pcb[i] - 128;
+ r = y_fixed + cr*float2fixed(1.40200f);
+ g = y_fixed - cr*float2fixed(0.71414f) - cb*float2fixed(0.34414f);
+ b = y_fixed + cb*float2fixed(1.77200f);
+ r >>= 16;
+ g >>= 16;
+ b >>= 16;
+ 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;
+ }
+}
+#else
+// 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 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* float2fixed(1.40200f);
+ g = y_fixed + (cr*-float2fixed(0.71414f)) + ((cb*-float2fixed(0.34414f)) & 0xffff0000);
+ b = y_fixed + cb* 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
+
+#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* float2fixed(1.40200f);
+ g = y_fixed + cr*-float2fixed(0.71414f) + ((cb*-float2fixed(0.34414f)) & 0xffff0000);
+ b = y_fixed + cb* 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;
+ #ifndef STBI_JPEG_OLD
+ j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd;
+ #endif
+ j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd;
+ }
+#endif
+
+#ifdef STBI_NEON
+ j->idct_block_kernel = stbi__idct_simd;
+ #ifndef STBI_JPEG_OLD
+ j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd;
+ #endif
+ 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)
+{
+ int i;
+ for (i=0; i < j->s->img_n; ++i) {
+ if (j->img_comp[i].raw_data) {
+ STBI_FREE(j->img_comp[i].raw_data);
+ j->img_comp[i].raw_data = NULL;
+ j->img_comp[i].data = NULL;
+ }
+ if (j->img_comp[i].raw_coeff) {
+ STBI_FREE(j->img_comp[i].raw_coeff);
+ j->img_comp[i].raw_coeff = 0;
+ j->img_comp[i].coeff = 0;
+ }
+ if (j->img_comp[i].linebuf) {
+ STBI_FREE(j->img_comp[i].linebuf);
+ j->img_comp[i].linebuf = NULL;
+ }
+ }
+}
+
+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;
+
+static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp)
+{
+ int n, decode_n;
+ 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;
+
+ if (z->s->img_n == 3 && n < 3)
+ decode_n = 1;
+ else
+ decode_n = z->s->img_n;
+
+ // resample and color-convert
+ {
+ int k;
+ unsigned int i,j;
+ stbi_uc *output;
+ stbi_uc *coutput[4];
+
+ 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(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) {
+ 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 {
+ 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; // report original components, not output
+ return output;
+ }
+}
+
+static unsigned char *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ stbi__jpeg j;
+ j.s = s;
+ stbi__setup_jpeg(&j);
+ return load_jpeg_image(&j, x,y,comp,req_comp);
+}
+
+static int stbi__jpeg_test(stbi__context *s)
+{
+ int r;
+ stbi__jpeg j;
+ j.s = s;
+ stbi__setup_jpeg(&j);
+ r = stbi__decode_jpeg_header(&j, STBI__SCAN_type);
+ stbi__rewind(s);
+ 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;
+ return 1;
+}
+
+static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp)
+{
+ stbi__jpeg j;
+ j.s = s;
+ return stbi__jpeg_info_raw(&j, x, y, comp);
+}
+#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)
+
+// 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[288];
+ stbi__uint16 value[288];
+} 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, 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 stbi_uc stbi__zget8(stbi__zbuf *z)
+{
+ if (z->zbuffer >= z->zbuffer_end) return 0;
+ return *z->zbuffer++;
+}
+
+static void stbi__fill_bits(stbi__zbuf *z)
+{
+ do {
+ STBI_ASSERT(z->code_buffer < (1U << z->num_bits));
+ 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];
+ STBI_ASSERT(z->size[b] == s);
+ 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) 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;
+ int cur, limit, old_limit;
+ z->zout = zout;
+ if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG");
+ cur = (int) (z->zout - z->zout_start);
+ limit = old_limit = (int) (z->zout_end - z->zout_start);
+ while (cur + n > limit)
+ 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 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 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 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 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;
+ }
+ 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) return stbi__err("bad huffman code","Corrupt PNG");
+ 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 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;
+
+ 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 < hlit + hdist) {
+ 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 if (c == 16) {
+ c = stbi__zreceive(a,2)+3;
+ memset(lencodes+n, lencodes[n-1], c);
+ n += c;
+ } else if (c == 17) {
+ c = stbi__zreceive(a,3)+3;
+ memset(lencodes+n, 0, c);
+ n += c;
+ } else {
+ STBI_ASSERT(c == 18);
+ c = stbi__zreceive(a,7)+11;
+ memset(lencodes+n, 0, c);
+ n += c;
+ }
+ }
+ if (n != hlit+hdist) 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_uncomperssed_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;
+ }
+ STBI_ASSERT(a->num_bits == 0);
+ // 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 ((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;
+}
+
+// @TODO: should statically initialize these for optimal thread safety
+static stbi_uc stbi__zdefault_length[288], stbi__zdefault_distance[32];
+static void stbi__init_zdefaults(void)
+{
+ 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_uncomperssed_block(a)) return 0;
+ } else if (type == 3) {
+ return 0;
+ } else {
+ if (type == 1) {
+ // use fixed code lengths
+ if (!stbi__zdefault_distance[31]) stbi__init_zdefaults();
+ if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) 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 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;
+} 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 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)
+{
+ stbi__context *s = a->s;
+ stbi__uint32 i,j,stride = x*out_n;
+ stbi__uint32 img_len, img_width_bytes;
+ int k;
+ int img_n = s->img_n; // copy it into a local for later
+
+ STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1);
+ a->out = (stbi_uc *) stbi__malloc(x * y * out_n); // extra bytes to write off the end into
+ if (!a->out) return stbi__err("outofmem", "Out of memory");
+
+ img_width_bytes = (((img_n * x * depth) + 7) >> 3);
+ img_len = (img_width_bytes + 1) * y;
+ if (s->img_x == x && s->img_y == y) {
+ if (raw_len != img_len) return stbi__err("not enough pixels","Corrupt PNG");
+ } else { // interlaced:
+ 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 = cur - stride;
+ int filter = *raw++;
+ int filter_bytes = img_n;
+ int width = x;
+ if (filter > 4)
+ return stbi__err("invalid filter","Corrupt PNG");
+
+ if (depth < 8) {
+ STBI_ASSERT(img_width_bytes <= x);
+ 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;
+ }
+
+ // 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 {
+ 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)*img_n;
+ #define 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;
+ CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); 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] + cur[k-filter_bytes])>>1)); break;
+ CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); break;
+ CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); break;
+ CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); break;
+ }
+ #undef CASE
+ raw += nk;
+ } else {
+ STBI_ASSERT(img_n+1 == out_n);
+ #define CASE(f) \
+ case f: \
+ for (i=x-1; i >= 1; --i, cur[img_n]=255,raw+=img_n,cur+=out_n,prior+=out_n) \
+ for (k=0; k < img_n; ++k)
+ switch (filter) {
+ CASE(STBI__F_none) cur[k] = raw[k]; break;
+ CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k-out_n]); 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] + cur[k-out_n])>>1)); break;
+ CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-out_n],prior[k],prior[k-out_n])); break;
+ CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k-out_n] >> 1)); break;
+ CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-out_n],0,0)); break;
+ }
+ #undef CASE
+ }
+ }
+
+ // 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];
+ }
+ }
+ }
+ }
+ }
+
+ 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)
+{
+ 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(a->s->img_x * a->s->img_y * out_n);
+ 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_n + out_x*out_n,
+ a->out + (j*x+i)*out_n, out_n);
+ }
+ }
+ 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__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(pixel_count * pal_img_n);
+ 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 = 0;
+static int stbi__de_iphone_flag = 0;
+
+STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply)
+{
+ stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply;
+}
+
+STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert)
+{
+ stbi__de_iphone_flag = flag_true_if_should_convert;
+}
+
+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) {
+ p[0] = p[2] * 255 / a;
+ p[1] = p[1] * 255 / a;
+ p[2] = t * 255 / 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) (((a) << 24) + ((b) << 16) + ((c) << 8) + (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];
+ stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0;
+ int first=1,k,interlace=0, color=0, depth=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); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)");
+ s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)");
+ depth = stbi__get8(s); if (depth != 1 && depth != 2 && depth != 4 && depth != 8) return stbi__err("1/2/4/8-bit only","PNG not supported: 1/2/4/8-bit only");
+ color = stbi__get8(s); if (color > 6) 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");
+ if (scan == STBI__SCAN_header) return 1;
+ } 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");
+ // if 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;
+ for (k=0; k < s->img_n; ++k)
+ tc[k] = (stbi_uc) (stbi__get16be(s) & 255) * stbi__depth_scale_table[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) { s->img_n = pal_img_n; return 1; }
+ 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 * 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, depth, color, interlace)) return 0;
+ if (has_trans)
+ 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;
+ }
+ STBI_FREE(z->expanded); z->expanded = NULL;
+ 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 unsigned char *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp)
+{
+ unsigned char *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)) {
+ result = p->out;
+ p->out = NULL;
+ if (req_comp && req_comp != p->s->img_out_n) {
+ result = stbi__convert_format(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_out_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 unsigned char *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ stbi__png p;
+ p.s = s;
+ return stbi__do_png(&p, x,y,comp,req_comp);
+}
+
+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);
+}
+#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, z >>= 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;
+}
+
+static int stbi__shiftsigned(int v, int shift, int bits)
+{
+ int result;
+ int z=0;
+
+ if (shift < 0) v <<= -shift;
+ else v >>= shift;
+ result = v;
+
+ z = bits;
+ while (z < 8) {
+ result += v >> z;
+ z += bits;
+ }
+ return result;
+}
+
+typedef struct
+{
+ int bpp, offset, hsz;
+ unsigned int mr,mg,mb,ma, all_a;
+} stbi__bmp_data;
+
+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);
+
+ 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 (info->bpp == 1) return stbi__errpuc("monochrome", "BMP type not supported: 1-bit");
+ if (hsz != 12) {
+ int compress = stbi__get32le(s);
+ if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE");
+ 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) {
+ info->mr = info->mg = info->mb = 0;
+ if (compress == 0) {
+ 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 {
+ info->mr = 31u << 10;
+ info->mg = 31u << 5;
+ info->mb = 31u << 0;
+ }
+ } else if (compress == 3) {
+ info->mr = stbi__get32le(s);
+ info->mg = stbi__get32le(s);
+ info->mb = stbi__get32le(s);
+ // 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 {
+ 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);
+ 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 stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ 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;
+
+ 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);
+
+ 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 - 14 - 24) / 3;
+ } else {
+ if (info.bpp < 16)
+ psize = (info.offset - 14 - info.hsz) >> 2;
+ }
+
+ 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
+
+ out = (stbi_uc *) stbi__malloc(target * s->img_x * s->img_y);
+ 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 - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4));
+ 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;
+ 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 - 14 - 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);
+ }
+ 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));
+ 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;
+ // else: fall-through
+ case 15: if(is_rgb16) *is_rgb16 = 1;
+ return STBI_rgb;
+ case 24: // fall-through
+ 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
+void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out)
+{
+ stbi__uint16 px = 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] = (r * 255)/31;
+ out[1] = (g * 255)/31;
+ out[2] = (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 stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ // 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];
+ int RLE_count = 0;
+ int RLE_repeating = 0;
+ int read_next_pixel = 1;
+
+ // 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;
+
+ tga_data = (unsigned char*)stbi__malloc( (size_t)tga_width * tga_height * tga_comp );
+ 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)
+ {
+ // any data to skip? (offset usually = 0)
+ stbi__skip(s, tga_palette_start );
+ // load the palette
+ tga_palette = (unsigned char*)stbi__malloc( tga_palette_len * tga_comp );
+ 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;
+ // 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 stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ int pixelCount;
+ int channelCount, compression;
+ int channel, i, count, len;
+ int bitdepth;
+ int w,h;
+ stbi_uc *out;
+
+ // 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);
+
+ // 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");
+
+ // Create the destination image.
+ 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 preceeded 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.
+ count = 0;
+ while (count < pixelCount) {
+ len = stbi__get8(s);
+ if (len == 128) {
+ // No-op.
+ } else if (len < 128) {
+ // Copy next len+1 bytes literally.
+ len++;
+ 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 ^= 0x0FF;
+ len += 2;
+ val = stbi__get8(s);
+ count += len;
+ while (len) {
+ *p = val;
+ p += 4;
+ len--;
+ }
+ }
+ }
+ }
+ }
+
+ } 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 value for each pixel in the image.
+
+ // Read the data by channel.
+ for (channel = 0; channel < 4; channel++) {
+ stbi_uc *p;
+
+ p = out + channel;
+ if (channel >= channelCount) {
+ // Fill this channel with default data.
+ stbi_uc val = channel == 3 ? 255 : 0;
+ for (i = 0; i < pixelCount; i++, p += 4)
+ *p = val;
+ } else {
+ // Read the data.
+ if (bitdepth == 16) {
+ 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);
+ }
+ }
+ }
+ }
+
+ if (req_comp && req_comp != 4) {
+ 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; y<height; ++y) {
+ int packet_idx;
+
+ for(packet_idx=0; packet_idx < num_packets; ++packet_idx) {
+ stbi__pic_packet *packet = &packets[packet_idx];
+ stbi_uc *dest = result+y*width*4;
+
+ switch (packet->type) {
+ default:
+ return stbi__errpuc("bad format","packet has bad compression type");
+
+ case 0: {//uncompressed
+ int x;
+
+ for(x=0;x<width;++x, dest+=4)
+ if (!stbi__readval(s,packet->channel,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; i<count; ++i,dest+=4)
+ stbi__copyval(packet->channel,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;i<count;++i, dest += 4)
+ stbi__copyval(packet->channel,dest,value);
+ } else { // Raw
+ ++count;
+ if (count>left) return stbi__errpuc("bad file","scanline overrun");
+
+ for(i=0;i<count;++i, dest+=4)
+ if (!stbi__readval(s,packet->channel,dest))
+ return 0;
+ }
+ left-=count;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+static stbi_uc *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp)
+{
+ stbi_uc *result;
+ int i, x,y;
+
+ for (i=0; i<92; ++i)
+ stbi__get8(s);
+
+ x = stbi__get16be(s);
+ y = stbi__get16be(s);
+ if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)");
+ if ((1 << 28) / x < y) return stbi__errpuc("too large", "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(x*y*4);
+ 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, *old_out; // output buffer (always 4 components)
+ int flags, bgindex, ratio, transparent, eflags, delay;
+ stbi_uc pal[256][4];
+ stbi_uc lpal[256][4];
+ stbi__gif_lzw codes[4096];
+ 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;
+} 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 (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;
+ if (!stbi__gif_header(s, &g, comp, 1)) {
+ stbi__rewind( s );
+ return 0;
+ }
+ if (x) *x = g.w;
+ if (y) *y = g.h;
+ return 1;
+}
+
+static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code)
+{
+ stbi_uc *p, *c;
+
+ // 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;
+
+ p = &g->out[g->cur_x + g->cur_y];
+ c = &g->color_table[g->codes[code].suffix * 4];
+
+ if (c[3] >= 128) {
+ 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 > 4096) 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");
+ }
+ }
+ }
+}
+
+static void stbi__fill_gif_background(stbi__gif *g, int x0, int y0, int x1, int y1)
+{
+ int x, y;
+ stbi_uc *c = g->pal[g->bgindex];
+ for (y = y0; y < y1; y += 4 * g->w) {
+ for (x = x0; x < x1; x += 4) {
+ stbi_uc *p = &g->out[y + x];
+ p[0] = c[2];
+ p[1] = c[1];
+ p[2] = c[0];
+ p[3] = 0;
+ }
+ }
+}
+
+// this function is designed to support animated gifs, although stb_image doesn't support it
+static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp)
+{
+ int i;
+ stbi_uc *prev_out = 0;
+
+ if (g->out == 0 && !stbi__gif_header(s, g, comp,0))
+ return 0; // stbi__g_failure_reason set by stbi__gif_header
+
+ prev_out = g->out;
+ g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h);
+ if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory");
+
+ switch ((g->eflags & 0x1C) >> 2) {
+ case 0: // unspecified (also always used on 1st frame)
+ stbi__fill_gif_background(g, 0, 0, 4 * g->w, 4 * g->w * g->h);
+ break;
+ case 1: // do not dispose
+ if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h);
+ g->old_out = prev_out;
+ break;
+ case 2: // dispose to background
+ if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h);
+ stbi__fill_gif_background(g, g->start_x, g->start_y, g->max_x, g->max_y);
+ break;
+ case 3: // dispose to previous
+ if (g->old_out) {
+ for (i = g->start_y; i < g->max_y; i += 4 * g->w)
+ memcpy(&g->out[i + g->start_x], &g->old_out[i + g->start_x], g->max_x - g->start_x);
+ }
+ break;
+ }
+
+ for (;;) {
+ switch (stbi__get8(s)) {
+ case 0x2C: /* Image Descriptor */
+ {
+ int prev_trans = -1;
+ 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;
+
+ 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) {
+ if (g->transparent >= 0 && (g->eflags & 0x01)) {
+ prev_trans = g->pal[g->transparent][3];
+ g->pal[g->transparent][3] = 0;
+ }
+ 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 == NULL) return NULL;
+
+ if (prev_trans != -1)
+ g->pal[g->transparent][3] = (stbi_uc) prev_trans;
+
+ return o;
+ }
+
+ case 0x21: // Comment Extension.
+ {
+ int len;
+ if (stbi__get8(s) == 0xF9) { // Graphic Control Extension.
+ len = stbi__get8(s);