瀏覽代碼

Fix tigr Modula-2 binding and complete the examples

tigr.def:
- complete the EXPORT UNQUALIFIED list so every declaration is importable
- TIGR_BLEND_ALPHA = 1 (was -1)
- replace the TCodepageType enum with TCP_ASCII/TCP_1252/TCP_UTF32 constants
  (M2 enums cannot hold 1252/12001)
- bind C char* parameters as ARRAY OF CHAR and raw char* results as ADDRESS
  instead of DynamicStrings.String

Examples:
- clip compiles unchanged now that titles accept string literals
- flags no longer crashes: toggle pointer initialized, key toggling,
  modeChange computed (BITSET casts), loop bounds fixed
- update.mod and Essai2.mod completed from stubs
- port the remaining original-tree examples: headless, hello, opengl
  (with a raw-GL module gl.def), shader

Other:
- add modula2/ANALYSIS.md documenting all findings and fixes
- vendor the tigr C source tree (tigr-master/) replacing tigr-master.zip
  (the stale pre-fix M2 copies it contained are left out; modula2/ is
  authoritative)
Eric Streit 3 周之前
父節點
當前提交
884feb8ab4
共有 86 個文件被更改,包括 15488 次插入 和 58 次删除
  1. 204 0
      modula2/ANALYSIS.md
  2. 2 2
      modula2/README.md
  3. 二進制
      modula2/examples/Essai1/Essai1
  4. 3 8
      modula2/examples/Essai1/Essai1.mod
  5. 二進制
      modula2/examples/Essai2/Essai2
  6. 11 3
      modula2/examples/Essai2/Essai2.mod
  7. 二進制
      modula2/examples/Essai3/essai3
  8. 14 0
      modula2/examples/Essai3/essai3.mod
  9. 64 0
      modula2/examples/Essai4/essai4.mod
  10. 二進制
      modula2/examples/clavier/clavier
  11. 1 4
      modula2/examples/clavier/clavier.mod
  12. 二進制
      modula2/examples/clip/clip
  13. 二進制
      modula2/examples/demo/demo
  14. 62 23
      modula2/examples/demo/demo.mod
  15. 二進制
      modula2/examples/flags/flags
  16. 214 0
      modula2/examples/flags/flags.mod
  17. 二進制
      modula2/examples/headless/headless
  18. 22 0
      modula2/examples/headless/headless.mod
  19. 二進制
      modula2/examples/hello/hello
  20. 20 0
      modula2/examples/hello/hello.mod
  21. 11 0
      modula2/examples/opengl/gl.def
  22. 二進制
      modula2/examples/opengl/opengl
  23. 51 0
      modula2/examples/opengl/opengl.mod
  24. 二進制
      modula2/examples/shader/shader
  25. 36 0
      modula2/examples/shader/shader.mod
  26. 二進制
      modula2/examples/unicode/update
  27. 51 2
      modula2/examples/unicode/update.mod
  28. 二進制
      modula2/helper.o
  29. 38 16
      modula2/tigr.def
  30. 二進制
      tigr-master.zip
  31. 8 0
      tigr-master/.clang-format
  32. 44 0
      tigr-master/.github/workflows/build.yml
  33. 111 0
      tigr-master/README.md
  34. 10 0
      tigr-master/UNLICENSE
  35. 二進制
      tigr-master/ci/5x7.png
  36. 15 0
      tigr-master/ci/Makefile
  37. 二進制
      tigr-master/ci/ch.png
  38. 399 0
      tigr-master/ci/ci.c
  39. 二進制
      tigr-master/ci/reference.png
  40. 11 0
      tigr-master/examples/.gitignore
  41. 14 0
      tigr-master/examples/clip/Makefile
  42. 37 0
      tigr-master/examples/clip/clip.c
  43. 14 0
      tigr-master/examples/demo/Makefile
  44. 151 0
      tigr-master/examples/demo/demo.cpp
  45. 2 0
      tigr-master/examples/demo/greeting.txt
  46. 二進制
      tigr-master/examples/demo/squinkle.png
  47. 14 0
      tigr-master/examples/flags/Makefile
  48. 97 0
      tigr-master/examples/flags/flags.c
  49. 4 0
      tigr-master/examples/headless/Makefile
  50. 10 0
      tigr-master/examples/headless/headless.c
  51. 14 0
      tigr-master/examples/hello/Makefile
  52. 12 0
      tigr-master/examples/hello/hello.c
  53. 14 0
      tigr-master/examples/opengl/Makefile
  54. 49 0
      tigr-master/examples/opengl/opengl.c
  55. 14 0
      tigr-master/examples/shader/Makefile
  56. 30 0
      tigr-master/examples/shader/shader.c
  57. 9 0
      tigr-master/src/Makefile
  58. 38 0
      tigr-master/src/bundle.pl
  59. 二進制
      tigr-master/src/font.png
  60. 31 0
      tigr-master/src/incbin.pl
  61. 20 0
      tigr-master/src/tigr_amalgamated.c
  62. 18 0
      tigr-master/src/tigr_amalgamated_one_header.h
  63. 767 0
      tigr-master/src/tigr_android.c
  64. 43 0
      tigr-master/src/tigr_android.h
  65. 405 0
      tigr-master/src/tigr_bitmaps.c
  66. 247 0
      tigr-master/src/tigr_font.h
  67. 460 0
      tigr-master/src/tigr_gl.c
  68. 12 0
      tigr-master/src/tigr_glsl_hdr.h
  69. 248 0
      tigr-master/src/tigr_inflate.c
  70. 155 0
      tigr-master/src/tigr_internal.h
  71. 530 0
      tigr-master/src/tigr_ios.c
  72. 685 0
      tigr-master/src/tigr_linux.c
  73. 289 0
      tigr-master/src/tigr_loadpng.c
  74. 54 0
      tigr-master/src/tigr_objc.h
  75. 1130 0
      tigr-master/src/tigr_osx.c
  76. 281 0
      tigr-master/src/tigr_print.c
  77. 190 0
      tigr-master/src/tigr_savepng.c
  78. 38 0
      tigr-master/src/tigr_upscale_gl_fs.h
  79. 24 0
      tigr-master/src/tigr_upscale_gl_vs.h
  80. 141 0
      tigr-master/src/tigr_utils.c
  81. 811 0
      tigr-master/src/tigr_win.c
  82. 6609 0
      tigr-master/tigr.c
  83. 366 0
      tigr-master/tigr.h
  84. 二進制
      tigr-master/tigr.o
  85. 二進制
      tigr-master/tigr.png
  86. 9 0
      tigr-master/unicode-test.c

+ 204 - 0
modula2/ANALYSIS.md

@@ -0,0 +1,204 @@
+# Analysis of the tigr-m2 project (2026-09-16)
+
+A GNU Modula-2 (`gm2 -fiso`) binding of the tigr graphic library, plus a few
+test programs in `modula2/examples`. The C library is vendored in
+`tigr-master/` (v3.2).
+
+## How it is built
+
+From `modula2/` (working directory must contain `tigr.c`, `tigr.def`,
+`helper.mod`, `helper.def`):
+
+    gcc -c tigr.c
+    gm2 -fiso -c helper.mod
+    gm2 -fiso -I. helper.o tigr.o <example>.mod -o <example> -lGL -lX11
+
+The `-s` flag shown in the README is optional and worked fine in testing.
+
+## What works
+
+Verified by building and running on this machine (X11 session):
+
+- `Essai1`, `demo`, `clip`, `clavier`, `flags`, `update`, `hello`, `shader`
+  all compile, link and run as *windows*; `headless` runs without a window and
+  writes a correct 320x240 PNG; `opengl` opens two windows and drives the
+  raw GL path (magenta `glClearColor`/`glClear` visible in `Window2`).
+  `Essai2` (qualified imports) and `Essai3` build. `essai4.mod` is
+  not a tigr example: it is a standalone PIM-style bit-twiddling test
+  (`SHIFT`/`AND`/`OR` on `CARDINAL`) that the ISO dialect does not accept -
+  left as is.
+- Window creation, `tigrClear`, `tigrFill`, `tigrLine`, `tigrRect`,
+  `tigrCircle`, `tigrFillCircle`, `tigrClip`, `tigrBlit`, `tigrBlitAlpha`,
+  `tigrMouse`, `tigrKeyDown`, `tigrReadChar`, `tigrTime`, `tigrTextWidth`,
+  `tigrTextHeight`, `tigrPrint`, `tigrLoadImage`, `tigrReadFile`.
+- `TPixelType` passed/returned by value matches the C ABI (checked with
+  `tigrGet`/`tigrPlot`).
+- The `Tigr` record layout (`tigr.def`) matches the C `struct Tigr`
+  (`tigr.h`) field-for-field.
+- `tfont` and the built-in font work; text metrics are correct.
+
+## Bugs found
+
+### BUG 1 - incomplete EXPORT UNQUALIFIED list (fixed 2026-09-16)
+
+Only ~35 of ~110 identifiers declared in `tigr.def` were in the
+`EXPORT UNQUALIFIED` list, so the rest could not be imported at all
+(GM2 rejects them, even with qualified names). Affected:
+
+- Functions: `tigrBeginOpenGL`, `tigrSetPostShader`, `tigrGet`, `tigrPlot`,
+  `tigrBlitTint`, `tigrBlitMode`, `tigrLoadFont`, `tigrFreeFont`,
+  `tigrTouch`, `tigrScrollWheel`, `tigrShowKeyboard`, `tigrLoadImageMem`,
+  `tigrSaveImage`, `tigrInflate`, `tigrDecodeUTF8`.
+- Types: `TigrType`, `intPtr`, `shortRealPtr`, `TigrFontTypePtr`,
+  `TigrFontType`, `TigrGlyphType`, `TCodepageType`, `TigrTouchPoint`,
+  `TigrTouchPointPtr`.
+- Constants: `TIGR_FIXED`, `TIGR_KEEP_ALPHA`, `TIGR_BLEND_ALPHA` and almost
+  all `TK_*` key codes (only ESCAPE, SPACE, LEFT, RIGHT were exported).
+
+All of them are now in the export list (verified: `tigrGet`, `intPtr`,
+`tigrLoadFont`, ... now compile in both qualified and unqualified form;
+`demo`, `flags`, `clavier`, `Essai1`, `Essai2`, `Essai3` still build).
+
+### BUG 2 - `TIGR_BLEND_ALPHA` has the wrong value  (fixed 2026-09-16)
+
+- `tigr.def`: `TIGR_BLEND_ALPHA = -1`
+- `tigr.h`: `TIGR_BLEND_ALPHA = 1` (enum, default blit mode)
+
+`blitMode` is used arithmetically in `tigr.c` (lines 470, 591, 643), so a
+client passing `-1` would break alpha blending. Fix: it is now set to `1`.
+This also made the constant usable (it is exported).
+
+### BUG 3 - `TCodepageType` enum values are wrong  (fixed 2026-09-16)
+
+- `tigr.def`: `TCodepageType = (TCP_ASCII, TCP_1252, TCP_UTF32)` -> 0, 1, 2
+- `tigr.h`: `TCP_ASCII = 0, TCP_1252 = 1252, TCP_UTF32 = 12001`
+
+A Modula-2 enumerated type cannot hold non-consecutive ordinals, so the
+enumeration was replaced by plain constants:
+
+    TCP_ASCII   = 0;
+    TCP_1252    = 1252;
+    TCP_UTF32   = 12001;
+
+`tigrLoadFont` takes a `CARDINAL` codepage, so clients can now pass the
+correct values. The `TCodepageType` type and the stray `TCodepage` variable
+were removed from `tigr.def`.
+
+### BUG 4 - `DynamicStrings.String` used as a C `const char*`  (fixed 2026-09-16)
+
+A GNU Modula-2 `String` (from `DynamicStrings`) is a *record* whose character
+buffer is at offset 0 but is **not NUL-terminated** and is capped at 127
+bytes (`MaxBuf`). Passing an `InitString` result to a C function that expects
+a `const char*` only worked while the freshly-allocated memory happened to be
+zeroed.
+
+Fix applied in `tigr.def` (GNU Modula-2 maps `ARRAY OF CHAR` in a `FOR "C"`
+module onto `char*`/`const char*`, including NUL-terminated string literals):
+
+| procedure | before | after |
+|---|---|---|
+| `tigrWindow`  | `title: String`            | `title: ARRAY OF CHAR` |
+| `tigrPrint`   | `text: String`             | `text: ARRAY OF CHAR` |
+| `tigrReadFile`| returns `String` (raw `char*`) | returns `ADDRESS` |
+| `tigrDecodeUTF8` | `text: String; ... ): String` | `text: ADDRESS; cp: intPtr): ADDRESS` |
+| `tigrEncodeUTF8` | `text: String; ... ): String` | `text: ADDRESS; cp: INTEGER): ADDRESS` |
+
+Consequences for users:
+
+- Window title and `tigrPrint` text are now ordinary string literals /
+  `ARRAY OF CHAR` (e.g. `tigrWindow(320, 240, "Clip", 0)`), instead of
+  requiring `InitString`.
+- `tigrReadFile` returns a NUL-terminated `char*` as `ADDRESS`. To use the
+  contents in M2, wrap it: `InitStringCharStar(raw)` then `CopyOut(arr, s)`
+  (used this way in `demo.mod`).
+- `tigrDecodeUTF8`/`tigrEncodeUTF8` operate on `ADDRESS` buffers (pass
+  `ADR(buffer)`); they return the next position as `ADDRESS`. Do not use them
+  with `DynamicStrings.String` values.
+
+The `FROM DynamicStrings IMPORT String;` import was removed from `tigr.def`
+(no longer needed).
+
+All examples were updated to the new API:
+`demo.mod`, `Essai1.mod`, `clavier.mod`, `flags.mod` (they were passing
+`InitString(...)` results before), and `clip.mod` now compiles as-is because
+its string literal/constant arguments are accepted directly.
+
+### BUG 5 - example programs (fixed 2026-09-16)
+
+- `examples/clip/clip.mod` did not compile (`tigrWindow(320, 240, "Clip", 0)`
+  passed a string literal where `String` was expected). This was the same
+  `String`/`char*` mismatch as BUG 4, so fixing `tigr.def` fixed
+  `clip.mod` and it now compiles and runs unchanged.
+- `examples/flags/flags.mod` crashed immediately at old line 184: the
+  `toggle` (`TogglePtr`) variable was never initialized (dereferences NIL),
+  `modeChange` was never set, the loop ran `0..numToggles` (one too many
+  iterations), keys never un-set `checked`, and window recreation only ran
+  when `flags > newFlags`. Fixed:
+  - `toggle := CAST(TogglePtr, ADR(toggles[i]));` inside the loop.
+  - loop is `FOR i := 0 TO numToggles - 1`.
+  - pressing a toggle key flips it: `toggle^.checked := 1 - toggle^.checked`.
+  - recreate on any change (`flags # newFlags`).
+  - `modeFlags := TIGR_AUTO + TIGR_RETINA` and
+    `modeChange := (flags AND modeFlags) # (newFlags AND modeFlags)`; since
+    ISO rejects `CARDINAL` `AND`/`OR`, bit masking is done via
+    `CAST(BITSET, n) * CAST(BITSET, mask)` (set intersection).
+  - `drawToggle`'s `stride` parameter is `CARDINAL` (was `INTEGER`, receiving
+    a `CARDINAL`).
+  - loop condition now `AND` (window open AND ESC not pressed) so ESC quits,
+    matching the C example.
+- `examples/unicode/update.mod` was an unfinished stub (missing `:` in the
+  VAR block, empty body). Completed into a working bouncing-ball example that
+  updates position each frame on the tigr window (keeps the original
+  `x/y/posx/posy` variable names).
+- `examples/Essai2/Essai2.mod` was a stub. Completed into a minimal window
+  test that exercises the qualified-import style (`IMPORT helper, tigr`):
+  builds a `TPixelType` with `helper.tigrRGB(80H, 90H, 0A0H)`, clears the
+  window with it, and closes on ESC.
+
+### Examples from the original tigr tree (ported and tested 2026-09-16)
+
+The original `tigr-master/` tree (sources identical to the bundled copy) also
+shipped `headless`, `hello`, `opengl` and `shader`. `modula2/examples/` had
+empty placeholders for `headless/`, `opengl/` and `shader/`; `hello/` did not
+exist. All four were ported and verified:
+
+- `headless/headless.mod` - off-screen bitmap (`tigrBitmap`) + `tigrPrint` +
+  `tigrSaveImage("headless.png", bmp)`; no window at all. Produces a valid
+  320x240 RGBA PNG whose corner pixel is exactly `tigrRGB(80H, 90H, 0A0H)`
+  = (128, 144, 160) and which contains the rendered white text.
+  Note: C returns `!err`, i.e. **1 = success** - check `result = 0` for
+  failure, not `# 0`.
+- `hello/hello.mod` - the trivial windowed "Hello, world." (blue clear +
+  white text). Runs fine.
+- `opengl/opengl.mod` - two windows; `Window2` clears the raw GL
+  framebuffer magenta via `tigrBeginOpenGL` + `glClearColor(1,0,1,1)` +
+  `glClear(GL_COLOR_BUFFER_BIT)` and then blits tigr text over it. Verified
+  by screenshot: the window is dominated by `#FF00FF`. The GL functions are
+  called through a tiny `DEFINITION MODULE FOR "C" gl;` (`opengl/gl.def`).
+- `shader/shader.mod` - `tigrSetPostShader` with the GLSL `fxShader` string
+  + `tigrSetPostFX`/`tigrTime` loop ("Shady" window). Needs
+  `size := HIGH(fxShader)` (gm2 computes the literal's length - there is no
+  `LENGTH`/`strlen` for `ARRAY OF CHAR` in ISO M2).
+
+GM2 gotchas encountered:
+
+- `DEFINITION MODULE FOR "C" gl;` puts `FOR "C"` *before* the module name
+  (the order `MODULE gl FOR "C";` only produces syntax warnings).
+- A `FOR "C"` definition without `EXPORT UNQUALIFIED` is mangled as
+  `<module>_<proc>` (e.g. `gl_glClear`), so it cannot reach a plain C
+  function like `glClear`; with `EXPORT UNQUALIFIED glClear, ...;` GM2
+  generates the plain C names and links straight against `-lGL`.
+- ISO Modula-2 has no `CARDINAL`/`INTEGER` bitwise `AND`/`OR`/`SHIFT`; use
+  `CAST(BITSET, n) * ...` (intersection) / `+` (union).
+- Adjacent string literals are **not** concatenated under ISO - keep strings
+  on one line (done for the `fxShader`).
+- `SHORTREAL` literals (`1.0`) pass straight into C `float` parameters.
+
+## Notes / minor oddities
+
+- `tigr.def` still declares stray global variables (`Tigr : TigrType`,
+  `TigrGlyph`, `TigrFont`) that do not correspond to anything in the C
+  library. They are not exported; they could be removed (`TCodepage` was
+  removed along with BUG 3).
+- `Essai1` uses `tigrError` to quit once the window is closed; on Linux
+  `tigrError` prints to stderr and calls `exit(1)`.

+ 2 - 2
modula2/README.md

@@ -21,9 +21,9 @@ To compile the examples, use this command :
 
 
 * Compiling helper.o
 * Compiling helper.o
   
   
-  gm2 -c helper.mod
+  gm2 -fiso -c helper.mod
 
 
 * Compiling an example
 * Compiling an example
   
   
-  gm2 -fiso ../../tigr.o ../../helper.o program.mod -o program -s -lGLU -lGL -lX1
+  gm2 -fiso -I ../../ ../../tigr.o ../../helper.o program.mod -o program -s -lGL -lX11
 
 

二進制
modula2/examples/Essai1/Essai1


+ 3 - 8
modula2/examples/Essai1/Essai1.mod

@@ -6,19 +6,14 @@ FROM tigr IMPORT TigrPtr,tigrWindow,tigrFree,tigrClosed, tigrClear, tigrUpdate,
 FROM helper IMPORT tigrRGB, tigrRGBA;
 FROM helper IMPORT tigrRGB, tigrRGBA;
 FROM InOut IMPORT Write, WriteLn, WriteString, WriteCard;
 FROM InOut IMPORT Write, WriteLn, WriteString, WriteCard;
 FROM RealInOut IMPORT WriteShortReal;
 FROM RealInOut IMPORT WriteShortReal;
-FROM DynamicStrings IMPORT String, InitString;
 FROM Delay IMPORT Delay;
 FROM Delay IMPORT Delay;
 
 
 VAR 
 VAR 
     screen : TigrPtr;
     screen : TigrPtr;
     redColor, blueColor, greenColor, blueColorHalf : TPixelType;
     redColor, blueColor, greenColor, blueColorHalf : TPixelType;
-    message1, message2, message3 : String;
 
 
 
 
 BEGIN
 BEGIN
-    message1 := InitString("Hello");
-    message2 := InitString("Bonjour tout le monde!");
-    message3 := InitString("How are you all?");
     WITH redColor DO 
     WITH redColor DO 
         r := 255;
         r := 255;
         g := 0;
         g := 0;
@@ -43,7 +38,7 @@ BEGIN
         b := 0;
         b := 0;
         a := 255;
         a := 255;
     END;
     END;
-    screen := tigrWindow(1000, 800, message1, 0);
+    screen := tigrWindow(1000, 800, "Hello", 0);
      WriteShortReal(tigrTime(),8); WriteLn;
      WriteShortReal(tigrTime(),8); WriteLn;
     WHILE (NOT (tigrClosed(screen) > 0)) OR (tigrKeyDown(screen, TK_ESCAPE) > 0) DO 
     WHILE (NOT (tigrClosed(screen) > 0)) OR (tigrKeyDown(screen, TK_ESCAPE) > 0) DO 
         tigrClear(screen, tigrRGB(80H, 90H, 0A0H));
         tigrClear(screen, tigrRGB(80H, 90H, 0A0H));
@@ -52,8 +47,8 @@ BEGIN
             tigrLine(screen, 100, 100, 300, 500, blueColor );
             tigrLine(screen, 100, 100, 300, 500, blueColor );
         END;
         END;
         Write ( CHR(tigrReadChar(screen)));
         Write ( CHR(tigrReadChar(screen)));
-        tigrPrint(screen, tfont, 10, 10, redColor, message2);
-        tigrPrint(screen, tfont, 150, 150, blueColor, message3);
+        tigrPrint(screen, tfont, 10, 10, redColor, "Bonjour tout le monde!");
+        tigrPrint(screen, tfont, 150, 150, blueColor, "How are you all?");
         tigrFill(screen,200,200,250,250,blueColorHalf);
         tigrFill(screen,200,200,250,250,blueColorHalf);
         tigrCircle(screen, 600, 600,50,greenColor);
         tigrCircle(screen, 600, 600,50,greenColor);
         tigrRect(screen, 250,250,250,250,blueColorHalf);
         tigrRect(screen, 250,250,250,250,blueColorHalf);

二進制
modula2/examples/Essai2/Essai2


+ 11 - 3
modula2/examples/Essai2/Essai2.mod

@@ -3,8 +3,16 @@ MODULE Essai2;
 IMPORT helper, tigr;
 IMPORT helper, tigr;
 
 
 VAR
 VAR
-    a : tigr.TPixelType;
+    a      : tigr.TPixelType;
+    screen : tigr.TigrPtr;
 
 
 BEGIN
 BEGIN
- a := helper.tigrRGB(80H, 90H, 0A0H)
-END Essai2.
+    a := helper.tigrRGB(80H, 90H, 0A0H);
+    screen := tigr.tigrWindow(200, 200, "Essai2", 0);
+    tigr.tigrClear(screen, a);
+    tigr.tigrUpdate(screen);
+    WHILE (NOT (tigr.tigrClosed(screen) > 0)) AND
+          (NOT (tigr.tigrKeyDown(screen, tigr.TK_ESCAPE) > 0)) DO
+    END;
+    tigr.tigrFree(screen)
+END Essai2.

二進制
modula2/examples/Essai3/essai3


+ 14 - 0
modula2/examples/Essai3/essai3.mod

@@ -0,0 +1,14 @@
+MODULE essai3;
+
+FROM StrIO IMPORT WriteString, WriteLn ;
+FROM NumberIO IMPORT WriteCard;
+FROM SYSTEM IMPORT CAST;
+
+VAR 
+    x : BITSET;
+
+BEGIN
+    x := {1,0};
+    WriteCard(CAST(CARDINAL, x), 0);
+    WriteLn;
+END essai3.

+ 64 - 0
modula2/examples/Essai4/essai4.mod

@@ -0,0 +1,64 @@
+MODULE essai4;
+
+IMPORT	InOut;
+
+
+TYPE	
+  BitFunction		= (set, clr);
+	BitResult		= (Set, Clear, Err);
+
+VAR	
+  i, j, k, n			: CARDINAL;
+
+
+PROCEDURE BitTest (pos, val : CARDINAL) : BitResult;
+
+VAR	mask, res	: CARDINAL;
+
+BEGIN
+  IF  pos >= 0  THEN
+    mask := SHIFT(1,pos);
+    res := val AND mask;
+    IF  res = 0  THEN  RETURN Clear  ELSE  RETURN Set  END
+  END;
+  RETURN Err
+END BitTest;
+
+
+PROCEDURE Bit (f : BitFunction; pos, val : CARDINAL) : CARDINAL;
+
+VAR	mask, res		: CARDINAL;
+
+BEGIN
+  mask := SHIFT(1,pos);
+  CASE  f  OF
+     set : res := val OR mask
+   | clr : res := val AND (NOT mask)
+  END;
+  RETURN res
+END Bit;
+
+
+BEGIN
+  i := 64;
+  j := 127;
+  k := 48;
+  FOR  n := 0  TO  15  DO
+    InOut.WriteString ("Bit ");
+    InOut.WriteCard (n, 2);
+    InOut.WriteString (" in 127 is ");
+    (* IF  BitTest (n, j) = Set  THEN  
+      InOut.WriteString ("Set.")
+    ELSE
+      InOut.WriteString ("Clear")
+    END; *)
+    InOut.WriteLn
+  END;
+  (* IF  BitTest (3, j) = Set  THEN  InOut.WriteString ("Bit 3 in 127 is set")  END; *)
+  InOut.WriteLn;
+  (* IF  BitTest (5, i) = Clear  THEN  InOut.WriteString ("Bit 5 in 64 is clear")  END; *)
+  InOut.WriteLn;
+  k := VAL (CARDINAL, Bit (set, 1, k));
+  InOut.WriteCard (k, 6);
+  InOut.WriteLn;
+END essai4.

二進制
modula2/examples/clavier/clavier


+ 1 - 4
modula2/examples/clavier/clavier.mod

@@ -8,18 +8,15 @@ FROM tigr IMPORT TigrPtr,tigrWindow,tigrFree,tigrClosed, tigrClear, tigrUpdate,
                  tigrEncodeUTF8, TK_RIGHT,tigrKeyHeld, TK_SPACE, TK_LEFT;
                  tigrEncodeUTF8, TK_RIGHT,tigrKeyHeld, TK_SPACE, TK_LEFT;
 
 
 FROM helper IMPORT tigrRGB, tigrRGBA;
 FROM helper IMPORT tigrRGB, tigrRGBA;
-FROM DynamicStrings IMPORT String, InitString;
 FROM InOut IMPORT Write, WriteLn, WriteCard,WriteString, WriteHex;
 FROM InOut IMPORT Write, WriteLn, WriteCard,WriteString, WriteHex;
 FROM Delay IMPORT Delay;
 FROM Delay IMPORT Delay;
 
 
 VAR
 VAR
     touche : CARDINAL;
     touche : CARDINAL;
     ecran : TigrPtr;
     ecran : TigrPtr;
-    titre : String;
 
 
 BEGIN
 BEGIN
-    titre := InitString("你好");
-    ecran := tigrWindow(320, 240, titre, 0);
+    ecran := tigrWindow(320, 240, "你好", 0);
     WHILE NOT (tigrClosed(ecran ) > 0) DO
     WHILE NOT (tigrClosed(ecran ) > 0) DO
         tigrClear(ecran, tigrRGB(80H, 90H, 0A0H));
         tigrClear(ecran, tigrRGB(80H, 90H, 0A0H));
         WriteHex(tigrReadChar(ecran), 7); WriteLn;
         WriteHex(tigrReadChar(ecran), 7); WriteLn;

二進制
modula2/examples/clip/clip


二進制
modula2/examples/demo/demo


+ 62 - 23
modula2/examples/demo/demo.mod

@@ -8,11 +8,12 @@ FROM tigr IMPORT TigrPtr,tigrWindow,tigrFree,tigrClosed, tigrClear, tigrUpdate,
                  tigrEncodeUTF8, TK_RIGHT,tigrKeyHeld, TK_SPACE, TK_LEFT;
                  tigrEncodeUTF8, TK_RIGHT,tigrKeyHeld, TK_SPACE, TK_LEFT;
 
 
 FROM helper IMPORT tigrRGB, tigrRGBA;
 FROM helper IMPORT tigrRGB, tigrRGBA;
-FROM DynamicStrings IMPORT String, InitString;
+FROM DynamicStrings IMPORT String, InitStringCharStar, CopyOut;
 FROM InOut IMPORT Write, WriteLn, WriteCard,WriteString;
 FROM InOut IMPORT Write, WriteLn, WriteCard,WriteString;
 FROM Delay IMPORT Delay;
 FROM Delay IMPORT Delay;
+FROM SYSTEM IMPORT ADDRESS;
 
 
-
+FROM SMathLib0 IMPORT exp;
     
     
 
 
 
 
@@ -21,46 +22,83 @@ VAR
     remaining           : SHORTREAL;
     remaining           : SHORTREAL;
     backdrop, screen    : TigrPtr;    
     backdrop, screen    : TigrPtr;    
     squinkle            : TigrPtr; 
     squinkle            : TigrPtr; 
-    greeting            : String;
+    greeting            : ARRAY[0..1023] OF CHAR;
+    greetingRaw         : ADDRESS;
+    greetingStr         : String;
     prevx, prevy, prev  : CARDINAL;
     prevx, prevy, prev  : CARDINAL;
     chars               : ARRAY[0..16] OF CHAR;
     chars               : ARRAY[0..16] OF CHAR;
-    tempString          : String;
     n                   : CARDINAL;
     n                   : CARDINAL;
     dt                  : SHORTREAL;
     dt                  : SHORTREAL;
     x, y ,b             : CARDINAL;
     x, y ,b             : CARDINAL;
-    message, message1   : String;
     c                   : CARDINAL;
     c                   : CARDINAL;
     playerx, playery,
     playerx, playery,
-    playerxs, playerys  : INTEGER;
+    playerxs, playerys  : CARDINAL;
 
 
     PROCEDURE update (dt : SHORTREAL);
     PROCEDURE update (dt : SHORTREAL);
-
+    (* in Modula2, we cannot mix integer and real *)
     VAR
     VAR
         oldx, oldy : INTEGER;
         oldx, oldy : INTEGER;
+        fplayerx, fplayery,
+        fplayerxs, fplayerys  : SHORTREAL;
+        fdx, fdy : SHORTREAL; 
+        foldx, foldy : SHORTREAL;
 
 
     BEGIN
     BEGIN
         IF (remaining > 0.0) THEN 
         IF (remaining > 0.0) THEN 
             remaining := remaining - dt;
             remaining := remaining - dt;
         END;
         END;
          (* Read the keyboard and move the player. *)
          (* Read the keyboard and move the player. *)
-        WriteString("Position départ ->"); 
-        WriteCard(playerxs, 6); WriteString("**");WriteCard(playerys, 6); WriteLn;
+        
         IF (standing OR (tigrKeyDown(screen, TK_SPACE) >0)) THEN
         IF (standing OR (tigrKeyDown(screen, TK_SPACE) >0)) THEN
-            playerys := playerys - 200;
+            IF (playerys - 200) >= 0 THEN
+                playerys := playerys - 200;
+            ELSE
+               playerys := 0;
+            END; 
         END;
         END;
          IF ((tigrKeyHeld(screen, TK_LEFT) > 0) OR (tigrKeyHeld(screen, ORD('a')) > 0)) THEN
          IF ((tigrKeyHeld(screen, TK_LEFT) > 0) OR (tigrKeyHeld(screen, ORD('a')) > 0)) THEN
-            WriteString("Touche gauche"); WriteLn;
-            playerxs := playerxs - 10;
+            IF (playerxs - 10) >= 0 THEN
+                playerxs := playerxs - 10
+            ELSE
+                playerxs := 0
+            END;
         END;
         END;
         IF ((tigrKeyHeld(screen, TK_RIGHT) > 0) OR (tigrKeyHeld(screen, ORD('d')) > 0)) THEN
         IF ((tigrKeyHeld(screen, TK_RIGHT) > 0) OR (tigrKeyHeld(screen, ORD('d')) > 0)) THEN
-        WriteString("Toiuche droite"); WriteLn;
-            playerxs := playerxs + 10;
+            IF (playerxs + 10) <= 320 THEN
+                playerxs := playerxs + 10
+            ELSE
+                playerxs := 320
+            END;
         END;
         END;
-        WriteString("Nouvelle Position ->");
-        WriteCard(playerxs, 6); WriteString("**");WriteCard(playerys, 6); WriteLn;
-        Delay(1000);
-
+       
+        (* conversion part betwwen CARDINAL and REAL*)
+        fplayerxs := FLOAT(playerxs);
+        fplayerys := FLOAT(playerys);
+        (* end conversion *)
         
         
+        foldx := fplayerx;
+        foldy := fplayery;
+
+        (* Apply simply physics. *)
+        fplayerxs := fplayerxs * exp(-10.0 * dt);
+        fplayerys := fplayery * exp(-2.0 * dt);
+        fplayerys := fplayerys + (dt * 200.0);
+        fplayerx := fplayerx + (dt * fplayerxs);
+        fplayery := fplayery + (dt * fplayerys);
+
+        (* Apply collision. *)
+        playerx := TRUNC(fplayerx);
+        IF playerx < 8 THEN
+            playerx := 8;
+            playerxs := 0;
+    END;
+
+        IF (playerx > screen^.w - 8) THEN
+            playerx := screen^.w - 8;
+            playerxs := 0;
+    END;
+
+
     END update;
     END update;
 
 
 BEGIN
 BEGIN
@@ -77,10 +115,12 @@ BEGIN
     END;
     END;
     
     
     (* Load some UTF-8 text. *)
     (* Load some UTF-8 text. *)
-    greeting := tigrReadFile("greeting.txt", 0);
-    IF (greeting = NIL) THEN
+    greetingRaw := tigrReadFile("greeting.txt", 0);
+    IF (greetingRaw = NIL) THEN
         tigrError(0, "Cannot load greeting.txt");
         tigrError(0, "Cannot load greeting.txt");
     END;
     END;
+    greetingStr := InitStringCharStar(greetingRaw);
+    CopyOut(greeting, greetingStr);
     (* Make a window and an off-screen backdrop. *)
     (* Make a window and an off-screen backdrop. *)
     screen := tigrWindow(320, 240, greeting, TIGR_2X);
     screen := tigrWindow(320, 240, greeting, TIGR_2X);
     backdrop := tigrBitmap(screen^.w, screen^.h);
     backdrop := tigrBitmap(screen^.w, screen^.h);
@@ -124,12 +164,11 @@ BEGIN
 
 
         (* Composite the backdrop and sprite onto the screen. *)
         (* Composite the backdrop and sprite onto the screen. *)
         tigrBlit(screen, backdrop, 0, 0, 0, 0, backdrop^.w, backdrop^.h);
         tigrBlit(screen, backdrop, 0, 0, 0, 0, backdrop^.w, backdrop^.h);
-        tigrBlitAlpha(screen, squinkle, playerx - INTEGER(squinkle^.w / 2), playery - INTEGER(squinkle^.h), 0, 0, 
+        tigrBlitAlpha(screen, squinkle, playerx - CARDINAL(squinkle^.w / 2), playery - CARDINAL(squinkle^.h), 0, 0, 
                     squinkle^.w, squinkle^.h, 1.0);
                     squinkle^.w, squinkle^.h, 1.0);
 
 
-        message := InitString("A D + SPACE");
         tigrPrint(screen, tfont, 10, 10,  tigrRGBA(0C0H, 0D0H, 0FFH, 0C0H), greeting);
         tigrPrint(screen, tfont, 10, 10,  tigrRGBA(0C0H, 0D0H, 0FFH, 0C0H), greeting);
-        tigrPrint(screen, tfont, 10, 222, tigrRGBA(0FFH, 0FFH, 0FFH, 0FFH), message);
+        tigrPrint(screen, tfont, 10, 222, tigrRGBA(0FFH, 0FFH, 0FFH, 0FFH), "A D + SPACE");
 
 
         (* Grab any chars and add them to our buffer. *)
         (* Grab any chars and add them to our buffer. *)
         LOOP
         LOOP

二進制
modula2/examples/flags/flags


+ 214 - 0
modula2/examples/flags/flags.mod

@@ -0,0 +1,214 @@
+MODULE flags;
+
+(* Compilation : 
+    gm2 -fiso -I../.. ../../tigr.o ../../helper.o flags.mod -o flags -s -lGLU -lGL -lX11
+*)
+
+FROM tigr IMPORT TigrPtr,tigrWindow,tigrFree,tigrClosed, tigrClear, tigrUpdate, TPixelType,
+                tigrLine, TK_ESCAPE, tigrKeyDown, tigrReadChar, tfont, tigrPrint,tigrFill,
+                tigrCircle, tigrRect, tigrFillCircle,  tigrTime, tigrError, tigrClip, 
+                tigrFillRect, tigrTextWidth, tigrTextHeight, tigrLoadImage, tigrReadFile,
+                tigrBitmap, TIGR_2X, tigrSetPostFX, tigrMouse,tigrBlit, tigrBlitAlpha,
+                 tigrEncodeUTF8, TK_RIGHT,tigrKeyHeld, TK_SPACE, TK_LEFT, TIGR_AUTO,
+                 TIGR_RETINA, TIGR_FULLSCREEN, TIGR_3X, TIGR_4X, TIGR_NOCURSOR;
+
+FROM helper IMPORT tigrRGB, tigrRGBA;
+FROM InOut IMPORT Write, WriteLn, WriteCard,WriteString;
+FROM NumberIO IMPORT CardToStr;
+FROM Strings IMPORT Concat;
+FROM SYSTEM IMPORT ADR, CAST;
+
+
+TYPE 
+    ToggleType = RECORD
+                    text    : ARRAY[0..100] OF CHAR;
+                    checked : CARDINAL;
+                    value   : CARDINAL;
+                    key     : CARDINAL;
+                    color   : TPixelType;
+                END;
+    TogglePtr   = POINTER TO ToggleType;
+
+VAR 
+    Toggle              : ToggleType;
+    lineColor           : TPixelType;
+    flags               : CARDINAL; 
+    initialW            : CARDINAL; 
+    initialH            : CARDINAL;
+    win                 : TigrPtr;
+    white,yellow,black  : TPixelType;
+    toggles             : ARRAY[0..6] OF ToggleType;
+
+    PROCEDURE makeDemoWindow(w,h : CARDINAL; flags: CARDINAL): TigrPtr;
+
+    BEGIN
+        RETURN tigrWindow(w, h, "Flag tester", flags);
+    END makeDemoWindow;
+
+
+    PROCEDURE drawDemoWindow(win : TigrPtr);
+
+        VAR
+            winw, winh : ARRAY[0..5] OF CHAR;
+            resultStr  : ARRAY[0..11] OF CHAR;
+
+    BEGIN 
+        lineColor := tigrRGB(100, 100, 100);
+        tigrLine(win, 0, 0, win^.w - 1, win^.h - 1, lineColor);
+        tigrLine(win, 0, win^.h - 1, win^.w - 1, 0, lineColor);
+        tigrRect(win, 0, 0, win^.w, win^.h, tigrRGB(200, 10, 10));
+        CardToStr(win^.w, 5, winw);
+        CardToStr(win^.h, 5,winh);
+        Concat(winw, winh, resultStr);
+        tigrPrint(win, tfont, 5, 5, tigrRGB(20, 200, 0), resultStr);
+    END drawDemoWindow;
+
+    PROCEDURE drawToggle(bmp: TigrPtr; toggle: TogglePtr; x, y: CARDINAL; stride: CARDINAL);
+
+        VAR
+            height, width : CARDINAL;
+            yOffset, xOffset : CARDINAL;
+            lineColor : TPixelType;
+
+    BEGIN
+        height  := tigrTextHeight(tfont, toggle^.text);
+        width   := tigrTextWidth(tfont, toggle^.text);
+        yOffset := stride / 2;
+        xOffset := width / 2; 
+        
+        tigrPrint(bmp, tfont, x + xOffset, y + yOffset, toggle^.color, toggle^.text);
+   
+        IF toggle^.checked > 0 THEN
+            yOffset := yOffset + height
+        ELSE
+            yOffset := yOffset / 3
+        END;
+        lineColor := toggle^.color;
+        lineColor.a := 240;
+        tigrLine(bmp, x + xOffset, y + yOffset, x + xOffset + width, y + yOffset, lineColor);
+    END drawToggle;
+
+
+        VAR
+            numToggles,
+            stepY, 
+            toggleY, 
+            toggleX : CARDINAL; 
+            newFlags : CARDINAL;
+            i : CARDINAL;
+            toggle : TogglePtr;
+            w,h : CARDINAL;
+            modeFlags, modeChange : CARDINAL;
+
+BEGIN
+    flags := 0;
+    initialW := 400;
+    initialH := 400;
+
+    win := makeDemoWindow(initialW, initialH, flags);
+    white := tigrRGB(255, 255, 255);
+    yellow := tigrRGB(255, 255, 0);
+    black := tigrRGB(0, 0, 0);
+
+    WITH toggles[0] DO
+        text := "(A)UTO"; 
+        checked := 1;
+        value := TIGR_AUTO;
+        key :=  ORD('A'); 
+        color :=  white;
+    END;
+    (* { "(R)ETINA", 0, TIGR_RETINA, 'R', white }, *)
+    WITH toggles[1] DO
+        text := "(R)ETINA"; 
+        checked := 0;
+        value := TIGR_RETINA;
+        key :=  ORD('R'); 
+        color :=  white;
+    END;
+    (* { "(F)ULLSCREEN", 0, TIGR_FULLSCREEN, 'F', white }, *)
+     WITH toggles[2] DO
+        text := "(F)ULLSCREEN"; 
+        checked := 0;
+        value := TIGR_FULLSCREEN;
+        key :=  ORD('F'); 
+        color :=  white;
+    END;
+    (*  { "(2)X", 0, TIGR_2X, '2', yellow }, *)
+        WITH toggles[3] DO
+        text := "(2)X"; 
+        checked := 0;
+        value := TIGR_2X;
+        key :=  ORD('2'); 
+        color :=  yellow;
+    END;
+    (* { "(3)X", 0, TIGR_3X, '3', yellow }, *)
+    WITH toggles[4] DO
+        text := "(3)X"; 
+        checked := 0;
+        value := TIGR_3X;
+        key :=  ORD('3'); 
+        color :=  yellow;
+    END;
+    (*  { "(4)X", 0, TIGR_4X, '4', yellow }, *)
+     
+    WITH toggles[5] DO
+        text := "(4)X"; 
+        checked := 0;
+        value := TIGR_4X;
+        key :=  ORD('4'); 
+        color :=  yellow;
+    END;
+    (*  { "(N)OCURSOR", 0, TIGR_NOCURSOR, 'N', white } *)
+    WITH toggles[6] DO
+        text := "(N)OCURSOR"; 
+        checked := 0;
+        value := TIGR_NOCURSOR;
+        key :=  ORD('N'); 
+        color :=  white;
+    END;
+
+    WHILE (NOT (tigrClosed(win) > 0)) AND (NOT (tigrKeyDown(win, TK_ESCAPE) > 0)) DO 
+        tigrClear(win, black);
+
+        drawDemoWindow(win);
+
+        numToggles := 7;
+        stepY := win^.h / numToggles;
+        toggleY := 0;
+        toggleX := win^.w / 2;
+
+        newFlags := 0;
+        FOR i := 0 TO numToggles - 1 DO
+                toggleY := toggleY + stepY;
+                toggle := CAST(TogglePtr, ADR(toggles[i]));
+                IF (tigrKeyDown(win, toggle^.key) > 0) THEN
+                    toggle^.checked := 1 - toggle^.checked;
+                END;
+                newFlags := newFlags + toggle^.checked * toggle^.value;
+                drawToggle(win, toggle, toggleX, toggleY, stepY);
+        END;
+
+        IF (flags # newFlags) THEN
+                modeFlags := TIGR_AUTO + TIGR_RETINA;
+                IF (VAL(CARDINAL, CAST(BITSET, flags) * CAST(BITSET, modeFlags))
+                    # VAL(CARDINAL, CAST(BITSET, newFlags) * CAST(BITSET, modeFlags))) THEN
+                    modeChange := 1;
+                ELSE
+                    modeChange := 0;
+                END;
+                flags := newFlags;
+
+                IF modeChange > 0 THEN
+                    w := initialW;
+                    h := initialH
+                ELSE
+                    w :=  win^.w;
+                    h :=  win^.h
+                END;
+                tigrFree(win);
+                win := makeDemoWindow(w, h, flags);
+        END;
+        tigrUpdate(win);
+    END;
+    tigrFree(win);
+END flags.

二進制
modula2/examples/headless/headless


+ 22 - 0
modula2/examples/headless/headless.mod

@@ -0,0 +1,22 @@
+MODULE headless;
+
+FROM tigr IMPORT TigrPtr, tigrBitmap, tigrClear, tigrPrint, tigrSaveImage,
+                tigrFree, tfont;
+FROM helper IMPORT tigrRGB;
+FROM InOut IMPORT WriteString, WriteLn;
+
+VAR
+    bmp    : TigrPtr;
+    result : INTEGER;
+
+BEGIN
+    bmp := tigrBitmap(320, 240);
+    tigrClear(bmp, tigrRGB(80H, 90H, 0A0H));
+    tigrPrint(bmp, tfont, 120, 110, tigrRGB(0FFH, 0FFH, 0FFH), "Hello, world.");
+    result := tigrSaveImage("headless.png", bmp);
+    IF result = 0 THEN
+        WriteString("tigrSaveImage failed");
+        WriteLn
+    END;
+    tigrFree(bmp);
+END headless.

二進制
modula2/examples/hello/hello


+ 20 - 0
modula2/examples/hello/hello.mod

@@ -0,0 +1,20 @@
+MODULE hello;
+
+FROM tigr IMPORT TigrPtr, tigrWindow, tigrFree, tigrClosed, tigrClear,
+                tigrUpdate, TPixelType, tigrPrint, tfont, TK_ESCAPE, tigrKeyDown;
+FROM helper IMPORT tigrRGB;
+
+VAR
+    screen : TigrPtr;
+    bg     : TPixelType;
+
+BEGIN
+    screen := tigrWindow(320, 240, "Hello", 0);
+    bg := tigrRGB(80H, 90H, 0A0H);
+    WHILE (NOT (tigrClosed(screen) > 0)) AND (NOT (tigrKeyDown(screen, TK_ESCAPE) > 0)) DO
+        tigrClear(screen, bg);
+        tigrPrint(screen, tfont, 120, 110, tigrRGB(0FFH, 0FFH, 0FFH), "Hello, world.");
+        tigrUpdate(screen);
+    END;
+    tigrFree(screen);
+END hello.

+ 11 - 0
modula2/examples/opengl/gl.def

@@ -0,0 +1,11 @@
+DEFINITION MODULE FOR "C" gl;
+
+EXPORT UNQUALIFIED glClear, glClearColor, GL_COLOR_BUFFER_BIT;
+
+CONST
+    GL_COLOR_BUFFER_BIT = 4000H;
+
+PROCEDURE glClear(mask : CARDINAL);
+PROCEDURE glClearColor(r, g, b, a : SHORTREAL);
+
+END gl.

二進制
modula2/examples/opengl/opengl


+ 51 - 0
modula2/examples/opengl/opengl.mod

@@ -0,0 +1,51 @@
+MODULE opengl;
+
+FROM tigr IMPORT TigrPtr, tigrWindow, tigrFree, tigrClosed, tigrClear,
+                tigrUpdate, TPixelType, tigrPrint, tfont, TK_ESCAPE, tigrKeyDown,
+                tigrBeginOpenGL;
+FROM helper IMPORT tigrRGB, tigrRGBA;
+FROM gl IMPORT glClear, glClearColor, GL_COLOR_BUFFER_BIT;
+
+VAR
+    win1, win2 : TigrPtr;
+
+BEGIN
+    win1 := tigrWindow(320, 240, "Window1", 0);
+    win2 := tigrWindow(320, 240, "Window2", 0);
+    WHILE (win1 # NIL) OR (win2 # NIL) DO
+        IF win1 # NIL THEN
+            tigrClear(win1, tigrRGB(80H, 90H, 0A0H));
+            tigrPrint(win1, tfont, 120, 110, tigrRGB(0FFH, 0FFH, 0FFH),
+                      "Hello, world #1.");
+            tigrUpdate(win1);
+
+            IF (tigrClosed(win1) > 0) OR (tigrKeyDown(win1, TK_ESCAPE) > 0) THEN
+                tigrFree(win1);
+                win1 := NIL;
+            END;
+        END;
+
+        IF win2 # NIL THEN
+            IF tigrBeginOpenGL(win2) # 0 THEN
+                glClearColor(1.0, 0.0, 1.0, 1.0);
+                glClear(GL_COLOR_BUFFER_BIT);
+            END;
+
+            tigrClear(win2, tigrRGBA(0, 0, 0, 0));
+            tigrPrint(win2, tfont, 120, 110, tigrRGB(0FFH, 0FFH, 0FFH),
+                      "Hello, world #2.");
+            tigrUpdate(win2);
+
+            IF (tigrClosed(win2) > 0) OR (tigrKeyDown(win2, TK_ESCAPE) > 0) THEN
+                tigrFree(win2);
+                win2 := NIL;
+            END;
+        END;
+    END;
+    IF win1 # NIL THEN
+        tigrFree(win1);
+    END;
+    IF win2 # NIL THEN
+        tigrFree(win2);
+    END;
+END opengl.

二進制
modula2/examples/shader/shader


+ 36 - 0
modula2/examples/shader/shader.mod

@@ -0,0 +1,36 @@
+MODULE shader;
+
+FROM tigr IMPORT TigrPtr, tigrWindow, tigrFree, tigrClosed, tigrClear,
+                tigrUpdate, tigrPrint, tfont, TK_ESCAPE, tigrKeyDown,
+                tigrSetPostShader, tigrSetPostFX, tigrTime;
+FROM helper IMPORT tigrRGB;
+
+CONST
+    fxShader = "void fxShader(out vec4 color, in vec2 uv) {vec2 tex_size = vec2(textureSize(image, 0));vec4 c = texture(image, (floor(uv * tex_size) + 0.5 * sin(parameters.x)) / tex_size);color = c;}";
+
+VAR
+    screen          : TigrPtr;
+    duration, phase : SHORTREAL;
+    p               : SHORTREAL;
+
+BEGIN
+    screen := tigrWindow(320, 240, "Shady", 0);
+    tigrSetPostShader(screen, fxShader, HIGH(fxShader));
+
+    duration := 1.0;
+    phase    := 0.0;
+
+    WHILE (NOT (tigrClosed(screen) > 0)) AND (NOT (tigrKeyDown(screen, TK_ESCAPE) > 0)) DO
+        phase := phase + tigrTime();
+        WHILE phase > duration DO
+            phase := phase - duration
+        END;
+        p := 6.28 * phase / duration;
+        tigrSetPostFX(screen, p, 0.0, 0.0, 0.0);
+        tigrClear(screen, tigrRGB(80H, 90H, 0A0H));
+        tigrPrint(screen, tfont, 120, 110, tigrRGB(0FFH, 0FFH, 0FFH),
+                  "Shady business");
+        tigrUpdate(screen);
+    END;
+    tigrFree(screen);
+END shader.

二進制
modula2/examples/unicode/update


+ 51 - 2
modula2/examples/unicode/update.mod

@@ -1,9 +1,58 @@
 MODULE update;
 MODULE update;
 
 
+(* Bouncing ball demo: per-frame state updates on the tigr real-time
+   window. *)
+
+FROM tigr IMPORT TigrPtr, tigrWindow, tigrFree, tigrClosed, tigrClear,
+                tigrUpdate, TPixelType, tigrFillCircle,
+                TK_ESCAPE, tigrKeyDown;
+FROM helper IMPORT tigrRGB;
+
 VAR
 VAR
-    x,y,posx, posy  CARDINAL;
-    c : CARDINAL;
+    screen          : TigrPtr;
+    posx, posy      : INTEGER;
+    velx, vely      : INTEGER;
+    radius          : INTEGER;
+    w, h            : CARDINAL;
+    black, ball     : TPixelType;
 
 
 BEGIN
 BEGIN
+    screen := tigrWindow(320, 240, "update", 0);
+    posx   := 160;
+    posy   := 120;
+    velx   := 3;
+    vely   := 2;
+    radius := 20;
+    black  := tigrRGB(0, 0, 0);
+    ball   := tigrRGB(230, 60, 60);
+
+    WHILE (NOT (tigrClosed(screen) > 0)) AND (NOT (tigrKeyDown(screen, TK_ESCAPE) > 0)) DO
+        w := screen^.w;
+        h := screen^.h;
+
+        posx := posx + velx;
+        posy := posy + vely;
+
+        IF (posx - radius) <= 0 THEN
+            posx  := radius;
+            velx  := -velx;
+        END;
+        IF (posy - radius) <= 0 THEN
+            posy  := radius;
+            vely  := -vely;
+        END;
+        IF (posx + radius) >= INTEGER(w) THEN
+            posx  := INTEGER(w) - radius;
+            velx  := -velx;
+        END;
+        IF (posy + radius) >= INTEGER(h) THEN
+            posy  := INTEGER(h) - radius;
+            vely  := -vely;
+        END;
 
 
+        tigrClear(screen, black);
+        tigrFillCircle(screen, posx, posy, radius, ball);
+        tigrUpdate(screen);
+    END;
+    tigrFree(screen);
 END update.
 END update.

二進制
modula2/helper.o


+ 38 - 16
modula2/tigr.def

@@ -2,14 +2,33 @@
 DEFINITION MODULE FOR "C" tigr;
 DEFINITION MODULE FOR "C" tigr;
 
 
 FROM SYSTEM IMPORT ADDRESS,BYTE;
 FROM SYSTEM IMPORT ADDRESS,BYTE;
-FROM DynamicStrings IMPORT String;
 
 
-EXPORT UNQUALIFIED tigrWindow, TigrPtr, tigrFree, tigrClosed, 
-            tigrClear,tigrUpdate,TPixelPtr,TPixelType,tigrLine, TK_ESCAPE, tigrKeyDown, 
-            tigrReadChar, tfont, tigrPrint,tigrFill,tigrCircle, tigrRect, tigrFillCircle,
-            tigrTime, tigrError, tigrClip, tigrFillRect, tigrTextWidth, tigrTextHeight,
-            tigrLoadImage,tigrReadFile, tigrBitmap, TIGR_2X ,tigrSetPostFX, tigrMouse,
-            tigrBlit, tigrBlitAlpha ,tigrEncodeUTF8, TK_RIGHT,tigrKeyHeld, TK_SPACE, TK_LEFT;
+EXPORT UNQUALIFIED tigrWindow, TigrPtr, TigrType, tigrFree, tigrClosed, 
+            tigrClear, tigrUpdate, TPixelPtr, TPixelType, intPtr, shortRealPtr,
+            tigrLine, tigrKeyDown, tigrReadChar, tfont, tigrPrint, tigrFill,
+            tigrCircle, tigrRect, tigrFillCircle, tigrTime, tigrError, tigrClip,
+            tigrFillRect, tigrTextWidth, tigrTextHeight, tigrLoadImage, tigrReadFile,
+            tigrBitmap, tigrSetPostFX, tigrMouse, tigrBlit, tigrBlitAlpha,
+            tigrEncodeUTF8, tigrKeyHeld, tigrBeginOpenGL, tigrSetPostShader,
+            tigrGet, tigrPlot, tigrBlitTint, tigrBlitMode, tigrLoadFont, tigrFreeFont,
+            tigrTouch, tigrScrollWheel, tigrShowKeyboard, tigrLoadImageMem,
+            tigrSaveImage, tigrInflate, tigrDecodeUTF8,
+            TIGR_FIXED, TIGR_AUTO, TIGR_2X, TIGR_3X, TIGR_4X, TIGR_RETINA,
+            TIGR_NOCURSOR, TIGR_FULLSCREEN,
+            TIGR_KEEP_ALPHA, TIGR_BLEND_ALPHA,
+            TigrGlyphType, TigrFontType, TigrFontTypePtr,
+            TCP_ASCII, TCP_1252, TCP_UTF32,
+            TigrTouchPoint, TigrTouchPointPtr,
+            TK_PAD0, TK_PAD1, TK_PAD2, TK_PAD3, TK_PAD4, TK_PAD5, TK_PAD6, TK_PAD7,
+            TK_PAD8, TK_PAD9, TK_PADMUL, TK_PADADD, TK_PADENTER, TK_PADSUB,
+            TK_PADDOT, TK_PADDIV, TK_F1, TK_F2, TK_F3, TK_F4, TK_F5, TK_F6, TK_F7,
+            TK_F8, TK_F9, TK_F10, TK_F11, TK_F12, TK_BACKSPACE, TK_TAB, TK_RETURN,
+            TK_SHIFT, TK_CONTROL, TK_ALT, TK_PAUSE, TK_CAPSLOCK, TK_ESCAPE,
+            TK_SPACE, TK_PAGEUP, TK_PAGEDN, TK_END, TK_HOME, TK_LEFT, TK_UP,
+            TK_RIGHT, TK_DOWN, TK_INSERT, TK_DELETE, TK_LWIN, TK_RWIN, TK_NUMLOCK,
+            TK_SCROLL, TK_LSHIFT, TK_RSHIFT, TK_LCONTROL, TK_RCONTROL, TK_LALT,
+            TK_RALT, TK_SEMICOLON, TK_EQUALS, TK_COMMA, TK_MINUS, TK_DOT, TK_SLASH,
+            TK_BACKTICK, TK_LSQUARE, TK_BACKSLASH, TK_RSQUARE, TK_TICK;
 
 
 CONST
 CONST
     TIGR_FIXED      =   0;    (* window's bitmap is a fixed size (default) *)
     TIGR_FIXED      =   0;    (* window's bitmap is a fixed size (default) *)
@@ -57,7 +76,7 @@ VAR Tigr : TigrType;
  this means that the Tigr bitmap will change size if the window is moved between
  this means that the Tigr bitmap will change size if the window is moved between
  retina and non-retina screens. *)
  retina and non-retina screens. *)
 
 
-PROCEDURE tigrWindow( w, h: CARDINAL; title : String; flags : INTEGER): TigrPtr;
+PROCEDURE tigrWindow( w, h: CARDINAL; title : ARRAY OF CHAR; flags : CARDINAL): TigrPtr;
 
 
 (* Creates an empty off-screen bitmap. *)
 (* Creates an empty off-screen bitmap. *)
 PROCEDURE tigrBitmap(w,h : CARDINAL): TigrPtr;
 PROCEDURE tigrBitmap(w,h : CARDINAL): TigrPtr;
@@ -192,7 +211,7 @@ PROCEDURE tigrBlitTint(dest, src: TigrPtr; dx, dy, sx, sy, w, h: CARDINAL; tint:
  (*TIGR_BLEND_ALPHA Blend destination alpha (default) *)
  (*TIGR_BLEND_ALPHA Blend destination alpha (default) *)
 CONST
 CONST
     TIGR_KEEP_ALPHA     =   0;
     TIGR_KEEP_ALPHA     =   0;
-    TIGR_BLEND_ALPHA    =   -1;  
+    TIGR_BLEND_ALPHA    =   1;  
 
 
  (* Set destination bitmap blend mode for blit operations. *)
  (* Set destination bitmap blend mode for blit operations. *)
 PROCEDURE tigrBlitMode(dest : TigrPtr; mode: CARDINAL);
 PROCEDURE tigrBlitMode(dest : TigrPtr; mode: CARDINAL);
@@ -212,13 +231,16 @@ TYPE
                             glyphs: POINTER TO TigrGlyphType;
                             glyphs: POINTER TO TigrGlyphType;
                         END;
                         END;
     
     
-    TCodepageType   =   (TCP_ASCII, TCP_1252, TCP_UTF32);
-                        
+
+CONST
+    TCP_ASCII   = 0;     (* Regular 7-bit ASCII *)
+    TCP_1252    = 1252;  (* Windows 1252 *)
+    TCP_UTF32   = 12001; (* Unicode subset *)
+
 
 
 VAR
 VAR
     TigrGlyph:  TigrGlyphType;
     TigrGlyph:  TigrGlyphType;
     TigrFont:   TigrFontType;
     TigrFont:   TigrFontType;
-    TCodepage:  TCodepageType;
 
 
 
 
  (* Loads a font from a bitmap font sheet.
  (* Loads a font from a bitmap font sheet.
@@ -245,7 +267,7 @@ PROCEDURE tigrFreeFont(font: TigrFontTypePtr);
  NOTE:
  NOTE:
   This uses the target bitmap blit mode.
   This uses the target bitmap blit mode.
   See tigrBlitTint for details. *)
   See tigrBlitTint for details. *)
-PROCEDURE tigrPrint(dest: TigrPtr; font: TigrFontTypePtr; x, y: CARDINAL; color: TPixelType; text : String; ...);
+PROCEDURE tigrPrint(dest: TigrPtr; font: TigrFontTypePtr; x, y: CARDINAL; color: TPixelType; text : ARRAY OF CHAR; ...);
 
 
  (* Returns the width/height of a string. *)
  (* Returns the width/height of a string. *)
 PROCEDURE tigrTextWidth(font: TigrFontTypePtr; text: ARRAY OF CHAR): INTEGER;
 PROCEDURE tigrTextWidth(font: TigrFontTypePtr; text: ARRAY OF CHAR): INTEGER;
@@ -397,16 +419,16 @@ PROCEDURE tigrError(bmp: TigrPtr; message: ARRAY OF CHAR; ...);
  On error, returns NULL and sets errno.
  On error, returns NULL and sets errno.
  TIGR will automatically append a NUL terminator byte
  TIGR will automatically append a NUL terminator byte
  to the end (not included in the length) *)
  to the end (not included in the length) *)
-PROCEDURE tigrReadFile(fileName: ARRAY OF CHAR; length: intPtr): String;
+PROCEDURE tigrReadFile(fileName: ARRAY OF CHAR; length: intPtr): ADDRESS;
 
 
  (* Decompresses DEFLATEd zip/zlib data into a buffer.
  (* Decompresses DEFLATEd zip/zlib data into a buffer.
  Returns non-zero on success. *)
  Returns non-zero on success. *)
 PROCEDURE tigrInflate(out: ADDRESS; outlen: CARDINAL; in: ADDRESS; inlen: CARDINAL);
 PROCEDURE tigrInflate(out: ADDRESS; outlen: CARDINAL; in: ADDRESS; inlen: CARDINAL);
 
 
  (* Decodes a single UTF8 codepoint and returns the next pointer. *)
  (* Decodes a single UTF8 codepoint and returns the next pointer. *)
-PROCEDURE tigrDecodeUTF8(text: String; cp: intPtr): String;
+PROCEDURE tigrDecodeUTF8(text: ADDRESS; cp: intPtr): ADDRESS;
 
 
  (* Encodes a single UTF8 codepoint and returns the next pointer. *)
  (* Encodes a single UTF8 codepoint and returns the next pointer. *)
-PROCEDURE tigrEncodeUTF8(text: String; cp: INTEGER): String;
+PROCEDURE tigrEncodeUTF8(text: ADDRESS; cp: INTEGER): ADDRESS;
 
 
 END tigr.
 END tigr.

二進制
tigr-master.zip


+ 8 - 0
tigr-master/.clang-format

@@ -0,0 +1,8 @@
+BasedOnStyle: Chromium
+IndentWidth: 4
+AllowAllArgumentsOnNextLine: true
+Cpp11BracedListStyle: false
+ColumnLimit: 120
+AllowShortFunctionsOnASingleLine: None
+AllowShortLambdasOnASingleLine: None
+SortIncludes: false

+ 44 - 0
tigr-master/.github/workflows/build.yml

@@ -0,0 +1,44 @@
+name: Smoke tests
+
+on:
+  push:
+    branches-ignore:
+      - 'test-*'
+
+jobs:
+  build:
+    name: Build
+    runs-on: ${{matrix.os}}
+    strategy:
+      matrix:
+        os: [macos-latest, ubuntu-latest, windows-latest]
+    steps:
+    - name: Check out code
+      uses: actions/checkout@v1
+
+    - name: Install Linux dependencies
+      if: startsWith(matrix.os, 'ubuntu')
+      run: sudo apt install libgl-dev libglu1-mesa-dev xvfb
+
+    - name: Compile
+      shell: bash
+      working-directory: ./ci
+      run: make
+
+    - name: Run tests (Linux)
+      if: startsWith(matrix.os, 'ubuntu')
+      shell: bash
+      working-directory: ./ci
+      run: xvfb-run ./ci
+
+    - name: Run tests (macOS)
+      if: startsWith(matrix.os, 'macos')
+      shell: bash
+      working-directory: ./ci
+      run: ./ci 1
+
+    - name: Run tests (Windows)
+      if: startsWith(matrix.os, 'windows')
+      shell: bash
+      working-directory: ./ci
+      run: ./ci.exe 0

+ 111 - 0
tigr-master/README.md

@@ -0,0 +1,111 @@
+# TIGR - TIny GRaphics library
+
+![](./tigr.png)
+
+TIGR is a tiny cross-platform graphics library,
+providing a unified API for Windows, macOS, Linux, iOS and Android.
+
+TIGR's core is a simple framebuffer library.
+On top of that, we provide a few helpers for the common tasks that 2D programs generally need:
+
+ - Bitmap-backed windows.
+ - Direct access to bitmaps, no locking.
+ - Basic drawing helpers (plot, line, blitter).
+ - Text output using bitmap fonts.
+ - Mouse, touch and keyboard input.
+ - PNG loading and saving.
+ - Easy pixel shader access.
+
+TIGR is designed to be small and independent.
+The 'hello world' example is less than 100kB:
+
+| *Platform* | *Size* |
+| --- | --- |
+| windows x86_64 | 48k |
+| linux x86_64 | 43k |
+| macOS arm64 | 90k |
+| macOS x86_64 | 74k |
+
+There are no additional libraries to include; everything is baked right into your program.
+
+TIGR is free to copy with no restrictions; see [tigr.h](tigr.h).
+
+## How do I program with TIGR?
+![](./demo.gif)
+
+Here's an example Hello World program. For more information, just read [tigr.h](tigr.h) to see the APIs available.
+
+```C
+#include "tigr.h"
+
+int main(int argc, char *argv[])
+{
+    Tigr *screen = tigrWindow(320, 240, "Hello", 0);
+    while (!tigrClosed(screen))
+    {
+        tigrClear(screen, tigrRGB(0x80, 0x90, 0xa0));
+        tigrPrint(screen, tfont, 120, 110, tigrRGB(0xff, 0xff, 0xff), "Hello, world.");
+        tigrUpdate(screen);
+    }
+    tigrFree(screen);
+    return 0;
+}
+```
+
+## How to set up TIGR
+
+### Desktop (Windows, macOS, Linux)
+
+TIGR is supplied as a single .c and corresponding .h file.
+
+To use it, you just drop them right into your project.
+
+1. Grab  **tigr.c** and **tigr.h**
+2. Throw them into your project.
+3. Link with
+    - -lopengl32 and -lgdi32 on Windows
+    - -framework OpenGL and -framework Cocoa on macOS
+    - -lGLU -lGL -lX11 on Linux
+4. You're done!
+
+### Android
+
+Due to the complex lifecycle and packaging of Android apps
+(there is no such thing as a single source file Android app),
+a tiny wrapper around TIGR is needed. Still - the TIGR API stays the same!
+
+To keep TIGR as tiny and focused as it is, the Android wrapper lives in a separate repo.
+
+To get started on Android, head over to the [TIMOGR](https://github.com/erkkah/timogr) repo and continue there.
+
+### iOS
+
+On iOS, TIGR is implemented as an app delegate, which can be used in your app with just a few lines of code.
+
+Building an iOS app usually requires quite a bit of tinkering in Xcode just to get up and running. To get up and running **fast**, there is an iOS starter project with a completely commandline-based tool chain, and VS Code configurations for debugging.
+
+To get started on iOS, head over to the [TIMOGRiOS](https://github.com/erkkah/timogrios) repo and continue there.
+
+> NOTE: TIGR is included in TIMOGR and TIMOGRiOS, there is no need to install TIGR separately.
+
+## Fonts and shaders
+
+### Custom fonts
+
+TIGR comes with a built-in bitmap font, accessed by the `tfont` variable. Custom fonts can be loaded from bitmaps using `tigrLoadFont`. A font bitmap contains rows of characters separated by same-colored borders. TIGR assumes that the borders use the same color as the top-left pixel in the bitmap. Each character is assumed to be drawn in white on a transparent background to make tinting work.
+
+Use the [tigrfont](https://github.com/erkkah/tigrfont) tool to create your own bitmap fonts from TTF or BDF font files.
+
+Since TIGR version 3.1, unicode-encoded font sheets are supported, making it possible to render any glyph in your fonts. Text is still just rendered LTR, though.
+
+### Custom pixel shaders
+
+TIGR uses a built-in pixel shader that provides a couple of stock effects as controlled by `tigrSetPostFX`.
+These stock effects can be replaced by calling `tigrSetPostShader` with a custom shader.
+The custom shader is in the form of a shader function: `void fxShader(out vec4 color, in vec2 uv)` and has access to the four parameters from `tigrSetPostFX` as a `uniform vec4` called `parameters`.
+
+See the [shader example](examples/shader/shader.c) for more details.
+
+## Known issues
+
+On macOS, seemingly depending on SDK version and if you use TIGR in an Xcode project, you need to define `OBJC_OLD_DISPATCH_PROTOTYPES` to avoid problems with `objc_msgSend` prototypes.

+ 10 - 0
tigr-master/UNLICENSE

@@ -0,0 +1,10 @@
+This is free and unencumbered software released into the public domain.
+
+Our intent is that anyone is free to copy and use this software,
+for any purpose, in any form, and by any means.
+
+The authors dedicate any and all copyright interest in the software
+to the public domain, at their own expense for the betterment of mankind.
+
+The software is provided "as is", without any kind of warranty, including
+any implied warranty. If it breaks, you get to keep both pieces.

二進制
tigr-master/ci/5x7.png


+ 15 - 0
tigr-master/ci/Makefile

@@ -0,0 +1,15 @@
+CFLAGS += -I..
+ifeq ($(OS),Windows_NT)
+	LDFLAGS += -s -lopengl32 -lgdi32
+	EXT = .exe
+else
+	UNAME_S := $(shell uname -s)
+	ifeq ($(UNAME_S),Darwin)
+		LDFLAGS += -framework OpenGL -framework Cocoa
+	else ifeq ($(UNAME_S),Linux)
+		LDFLAGS += -s -lGLU -lGL -lX11
+	endif
+endif
+
+ci : ci.c ../tigr.c
+	gcc -Wall -pedantic $^ -o $@$(EXT) $(CFLAGS) $(LDFLAGS)

二進制
tigr-master/ci/ch.png


+ 399 - 0
tigr-master/ci/ci.c

@@ -0,0 +1,399 @@
+//
+// TIGR massage test, runs through most API functions
+// and performs basic sanity checks.
+//
+// Epilepsy warning: the tests will open windows quickly,
+// causing multicolored intense flashing!
+//
+
+#include "tigr.h"
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef _WIN32
+#include <winsock2.h>
+#include <GL/gl.h>
+#elif defined __linux__
+#include <GL/gl.h>
+#else
+#define GL_SILENCE_DEPRECATION
+#include <OpenGL/gl3.h>
+#endif
+
+void windowWithFlags(int flags) {
+    Tigr* win = tigrWindow(100, 100, "CI", flags);
+    tigrFill(win, 0, 0, win->w, win->h, tigrRGB(flags >> 1, 64, flags >> 1));
+    tigrUpdate(win);
+    assert(!tigrClosed(win));
+    tigrFree(win);
+}
+
+void windowBasics() {
+    windowWithFlags(0);
+}
+
+void windowFlags() {
+    int flagsMax = TIGR_FULLSCREEN * 2 - 1;
+    for (int flags = 0; flags < flagsMax; flags++) {
+        windowWithFlags(flags);
+    }
+}
+
+void offscreen() {
+    Tigr* bmp = tigrBitmap(100, 100);
+    assert(bmp != 0);
+    assert(bmp->w == 100);
+    assert(bmp->h == 100);
+    bmp->pix[100 * 100 - 1] = tigrRGBA(1, 2, 3, 4);
+    tigrFree(bmp);
+}
+
+static TPixel colors[5] = {
+    { 0xff, 0x0, 0x0, 0xff },  { 0xff, 0xff, 0, 0xff }, { 0xff, 0x0, 0xff, 0xff },
+    { 0x0, 0xff, 0xff, 0xff }, { 0x0, 0x0, 0x0, 0xff },
+};
+
+void drawFauxSierpinski(Tigr* bmp) {
+    int scale = 255 / bmp->w + 1;
+    for (int x = 0; x < bmp->w; x++) {
+        for (int y = 0; y < bmp->h; y++) {
+            int c = (((x & y) + (x ^ y)) * scale) & 0xff;
+            tigrPlot(bmp, x, y, tigrRGBA(c, c, c, 220));
+        }
+    }
+}
+
+void drawTestPattern(Tigr* bmp) {
+    int midW = bmp->w / 2;
+    int midH = bmp->h / 2;
+
+    const char* msg = "TIGR test Xy";
+    int textHeight = tigrTextHeight(tfont, msg);
+    int textWidth = tigrTextWidth(tfont, msg);
+
+    tigrFill(bmp, 0, 0, bmp->w, bmp->h, colors[0]);
+    tigrLine(bmp, 0, 0, midW, midH, colors[1]);
+    tigrFill(bmp, midW, midH, midW, midH, colors[3]);
+    tigrRect(bmp, midW, midH, midW, midH, colors[2]);
+
+    tigrLine(bmp, 0, 10 + textHeight, bmp->w - 1, 10 + textHeight, colors[2]);
+    tigrLine(bmp, 10 + textWidth, 0, 10 + textWidth, bmp->h - 1, colors[2]);
+
+    tigrPrint(bmp, tfont, 10, 10, colors[1], "%s v%d", msg, 76);
+    tigrPrint(bmp, tfont, 12, 12 + textHeight, colors[2], "%s v%d", msg, 76);
+    tigrPrint(bmp, tfont, 14, 14 + 2 * textHeight, colors[3], "%s v%d", msg, 76);
+
+    Tigr* fontImage = tigrLoadImage("5x7.png");
+    assert(fontImage != 0);
+    TigrFont* font = tigrLoadFont(fontImage, TCP_ASCII);
+    assert(font != 0);
+    tigrPrint(bmp, font, 10, midH - 10, colors[1], "*** TEENY TINY FONT ***");
+    tigrFreeFont(font);
+
+    fontImage = tigrLoadImage("ch.png");
+    assert(fontImage != 0);
+    font = tigrLoadFont(fontImage, TCP_UTF32);
+    assert(font != 0);
+    tigrPrint(bmp, font, 10, midH - 40, colors[4], "你好,世界!");
+    tigrFreeFont(font);
+
+    Tigr* img = tigrLoadImage("../tigr.png");
+    tigrBlit(bmp, img, midW + 1, midH + 1, 42, 125, 70, 42);
+    tigrBlitTint(bmp, img, midW + 11, midH + 16, 42, 125, 70, 42, colors[2]);
+    tigrBlitAlpha(bmp, img, midW + 21, midH + 31, 42, 125, 70, 42, 0.5);
+
+    Tigr* sierp = tigrBitmap(50, 50);
+    drawFauxSierpinski(sierp);
+
+    tigrBlitAlpha(bmp, sierp, 0, midH, 0, 0, sierp->w, sierp->h, 1);
+    tigrBlitMode(bmp, TIGR_KEEP_ALPHA);
+    tigrBlitAlpha(bmp, sierp, sierp->w, midH + sierp->h, 0, 0, sierp->w, sierp->h, 1);
+}
+
+void assertPixelsEqual(TPixel c1, TPixel c2) {
+    assert(c1.r == c2.r);
+    assert(c1.g == c2.g);
+    assert(c1.b == c2.b);
+    assert(c1.a == c2.a);
+}
+
+void assertBitmapsEqual(Tigr* a, Tigr* b) {
+    assert(a->w == b->w);
+    assert(a->h == b->h);
+
+    for (int x = 0; x < a->w; x++) {
+        for (int y = 0; y < a->h; y++) {
+            TPixel c1 = tigrGet(a, x, y);
+            TPixel c2 = tigrGet(b, x, y);
+            assertPixelsEqual(c1, c2);
+        }
+    }
+}
+
+void verifyLineContract() {
+    TPixel bg = tigrRGB(0, 0, 255);
+    TPixel fg = tigrRGB(255, 0, 0);
+
+    Tigr* bmp = tigrBitmap(10, 10);
+
+    {
+        // Single pixel line
+
+        tigrClear(bmp, bg);
+        tigrLine(bmp, 0, 0, 0, 1, fg);
+
+        TPixel firstPixel = tigrGet(bmp, 0, 0);
+        assertPixelsEqual(firstPixel, fg);
+
+        TPixel lastPixel = tigrGet(bmp, 0, 1);
+        assertPixelsEqual(lastPixel, bg);
+    }
+
+    {
+        // Diagonal line, first pixel inclusive, last pixel exclusive
+
+        tigrClear(bmp, bg);
+        tigrLine(bmp, 0, 0, 9, 9, fg);
+
+        TPixel firstPixel = tigrGet(bmp, 0, 0);
+        assertPixelsEqual(firstPixel, fg);
+
+        TPixel lastPixel = tigrGet(bmp, 9, 9);
+        assertPixelsEqual(lastPixel, bg);
+
+        TPixel nextToLastPixel = tigrGet(bmp, 8, 8);
+        assertPixelsEqual(nextToLastPixel, fg);
+    }
+
+    tigrFree(bmp);
+}
+
+void verifyRectContract() {
+    TPixel bg = tigrRGB(0, 0, 255);
+    TPixel fg = tigrRGBA(255, 0, 0, 100);
+
+    Tigr* ref = tigrBitmap(10, 10);
+    tigrClear(ref, bg);
+
+    Tigr* bmp = tigrBitmap(10, 10);
+
+    {
+        // Zero size rect
+
+        tigrClear(bmp, bg);
+        tigrRect(bmp, 0, 0, 0, 0, fg);
+
+        assertBitmapsEqual(bmp, ref);
+    }
+
+    {
+        // Zero width rect
+
+        tigrClear(bmp, bg);
+        tigrRect(bmp, 0, 0, 0, 5, fg);
+
+        assertBitmapsEqual(bmp, ref);
+    }
+
+    {
+        // Zero height rect
+
+        tigrClear(bmp, bg);
+        tigrRect(bmp, 0, 0, 5, 0, fg);
+
+        assertBitmapsEqual(bmp, ref);
+    }
+
+    {
+        // 2 pixel rect
+
+        tigrClear(ref, bg);
+        tigrPlot(ref, 0, 0, fg);
+        tigrPlot(ref, 0, 1, fg);
+        tigrPlot(ref, 1, 0, fg);
+        tigrPlot(ref, 1, 1, fg);
+
+        tigrClear(bmp, bg);
+        tigrRect(bmp, 0, 0, 2, 2, fg);
+
+        assertBitmapsEqual(bmp, ref);
+    }
+
+    {
+        // 2x1 pixel rect
+
+        tigrClear(ref, bg);
+        tigrPlot(ref, 0, 0, fg);
+        tigrPlot(ref, 1, 0, fg);
+
+        tigrClear(bmp, bg);
+        tigrRect(bmp, 0, 0, 2, 1, fg);
+
+        assertBitmapsEqual(bmp, ref);
+    }
+
+    {
+        // 1x2 pixel rect
+
+        tigrClear(ref, bg);
+        tigrPlot(ref, 0, 0, fg);
+        tigrPlot(ref, 0, 1, fg);
+
+        tigrClear(bmp, bg);
+        tigrRect(bmp, 0, 0, 1, 2, fg);
+
+        assertBitmapsEqual(bmp, ref);
+    }
+
+    {
+        // 1 pixel rect
+
+        tigrClear(ref, bg);
+        tigrPlot(ref, 1, 1, fg);
+
+        tigrClear(bmp, bg);
+        tigrRect(bmp, 1, 1, 1, 1, fg);
+
+        assertBitmapsEqual(bmp, ref);
+    }
+
+    tigrFree(bmp);
+    tigrFree(ref);
+}
+
+void verifyDrawing() {
+    verifyLineContract();
+    verifyRectContract();
+
+    Tigr* bmp = tigrBitmap(200, 200);
+    drawTestPattern(bmp);
+#ifdef WRITE_REFERENCE
+    tigrSaveImage("reference.png", bmp);
+#endif
+    Tigr* loaded = tigrLoadImage("reference.png");
+    assertBitmapsEqual(bmp, loaded);
+}
+
+void directOpenGL() {
+    Tigr* win = tigrWindow(100, 100, "CI", 0);
+    assert(tigrBeginOpenGL(win));
+
+    glClearColor(1, 1, 0, 1);
+    glClear(GL_COLOR_BUFFER_BIT);
+
+    tigrUpdate(win);
+    tigrFree(win);
+}
+
+void customShader() {
+    Tigr* win = tigrWindow(100, 100, "CI", 0);
+
+    const char shader[] =
+        "void fxShader(out vec4 color, in vec2 uv) {"
+        "   vec2 tex_size = vec2(textureSize(image, 0));"
+        "   vec4 c = texture(image, (floor(uv * tex_size) + 0.5 * sin(parameters.x)) / tex_size);"
+        "   color = c;"
+        "}\n";
+
+    tigrSetPostShader(win, shader, sizeof(shader) - 1);
+    tigrSetPostFX(win, 3.14 / 2, 0, 0, 0);
+    tigrUpdate(win);
+    tigrFree(win);
+}
+
+void timing() {
+    float elapsed = tigrTime();
+    assert(elapsed == 0);
+
+    Tigr* win = tigrWindow(100, 100, "CI", 0);
+    tigrUpdate(win);
+
+    elapsed = tigrTime();
+    assert(elapsed > 0 && elapsed < 1);
+}
+
+void input() {
+    Tigr* win = tigrWindow(100, 100, "CI", 0);
+    tigrUpdate(win);
+
+    assert(tigrKeyHeld(win, TK_CONTROL) == tigrKeyDown(win, TK_CONTROL));
+    assert(tigrReadChar(win) == 0);
+
+    int nothing = 100000;
+    int x = nothing;
+    int y = nothing;
+    int buttons = nothing;
+    tigrMouse(win, &x, &y, &buttons);
+    assert(buttons != nothing);
+    assert(x != nothing);
+    assert(y != nothing);
+
+    TigrTouchPoint point;
+    int touches = tigrTouch(win, &point, 1);
+    assert(touches <= 1);
+}
+
+void unicode() {
+    const int codePoints[] = { 0x00C4, 0x1F308, 'a' };
+    const char utf8String[] = "Ä🌈a";
+
+    int decoded = 0;
+    const int* codePoint = codePoints;
+    const char* utf8Char = utf8String;
+    const char* lastChar = utf8Char;
+    while (*utf8Char != 0 && (utf8Char = tigrDecodeUTF8(utf8Char, &decoded)) != 0) {
+        assert(*codePoint == decoded);
+
+        char buf[32];
+        int len = tigrEncodeUTF8(buf, decoded) - buf;
+        assert(strncmp(buf, lastChar, len) == 0);
+
+        codePoint++;
+        lastChar = utf8Char;
+    }
+}
+
+typedef struct Test {
+    const char* title;
+    void (*test)(void);
+    int level;
+} Test;
+
+int main(int argc, char* argv[]) {
+    int limit = 1000;
+
+    if (argc > 1) {
+        limit = atoi(argv[1]);
+    }
+
+    Test tests[] = { { "Create offscreen", offscreen, 0 },
+                     { "Drawing API", verifyDrawing, 0 },
+                     { "Window basics", windowBasics, 1 },
+                     { "Unicode", unicode, 0 },
+                     { "Timing", timing, 1 },
+                     { "Custom fx shader", customShader, 2 },
+                     { "Direct OpenGL calls", directOpenGL, 2 },
+                     { "Input processing", input, 1 },
+                     { 0 } };
+
+    for (Test* test = tests; test->title != 0; test++) {
+        printf("%s...", test->title);
+        if (test->level > limit) {
+            printf("skipped\n");
+        } else {
+            test->test();
+            printf("OK\n");
+        }
+    }
+
+    if (argc == 2 && strcmp(argv[1], "full") == 0) {
+        printf("Full window flag test...");
+        windowFlags();
+        printf("OK\n");
+    }
+
+    printf("*** All tests pass OK\n");
+    return 0;
+}

二進制
tigr-master/ci/reference.png


+ 11 - 0
tigr-master/examples/.gitignore

@@ -0,0 +1,11 @@
+clip/clip
+demo/demo
+flags/flags
+headless/headless
+headless/headless.png
+hello/hello
+opengl/opengl
+shader/shader
+
+*.o
+*.exe

+ 14 - 0
tigr-master/examples/clip/Makefile

@@ -0,0 +1,14 @@
+CFLAGS += -I../.. -Os
+ifeq ($(OS),Windows_NT)
+	LDFLAGS += -s -lopengl32 -lgdi32
+else
+	UNAME_S := $(shell uname -s)
+	ifeq ($(UNAME_S),Darwin)
+		LDFLAGS += -framework OpenGL -framework Cocoa
+	else ifeq ($(UNAME_S),Linux)
+		LDFLAGS += -s -lGLU -lGL -lX11
+	endif
+endif
+
+clip : clip.c ../../tigr.c
+	gcc $^ -o $@ $(CFLAGS) $(LDFLAGS)

+ 37 - 0
tigr-master/examples/clip/clip.c

@@ -0,0 +1,37 @@
+#include "tigr.h"
+
+int main(int argc, char* argv[]) {
+    Tigr* screen = tigrWindow(320, 240, "Clip", 0);
+
+    TPixel c0 = tigrRGB(55, 55, 55);
+    TPixel c1 = tigrRGB(255, 255, 255);
+    TPixel c2 = tigrRGB(100, 200, 100);
+    TPixel c3 = tigrRGBA(100, 100, 200, 150);
+
+    while (!tigrClosed(screen) && !tigrKeyDown(screen, TK_ESCAPE)) {
+        tigrClear(screen, c0);
+
+        int cx = screen->w / 2;
+        int cy = screen->h / 2;
+        int w = 100;
+        int d = 50;
+
+        tigrClip(screen, cx - d, cy - d, w, w);
+        tigrFill(screen, cx - d, cy - d, w, w, c1);
+
+        tigrRect(screen, cx - w, cy - w, w, w, c2);
+        tigrFillRect(screen, cx - w, cy - w, w, w, c3);
+
+        tigrCircle(screen, cx + d, cy - d, d, c2);
+        tigrFillCircle(screen, cx + d, cy - d, d, c3);
+
+        const char* message = "Half a thought is also a thought";
+        int tw = tigrTextWidth(tfont, message);
+        int th = tigrTextHeight(tfont, message);
+        tigrPrint(screen, tfont, cx - tw / 2, cy + d - th / 2, c2, message);
+
+        tigrUpdate(screen);
+    }
+    tigrFree(screen);
+    return 0;
+}

+ 14 - 0
tigr-master/examples/demo/Makefile

@@ -0,0 +1,14 @@
+CFLAGS += -I../..
+ifeq ($(OS),Windows_NT)
+	LDFLAGS += -s -lopengl32 -lgdi32
+else
+	UNAME_S := $(shell uname -s)
+	ifeq ($(UNAME_S),Darwin)
+		LDFLAGS += -framework OpenGL -framework Cocoa
+	else ifeq ($(UNAME_S),Linux)
+		LDFLAGS += -s -lGLU -lGL -lX11
+	endif
+endif
+
+demo : demo.cpp ../../tigr.c
+	g++ $^ -Os -o $@ $(CFLAGS) $(LDFLAGS)

+ 151 - 0
tigr-master/examples/demo/demo.cpp

@@ -0,0 +1,151 @@
+clear// A small test program to exercise most of TIGR's features.
+
+#include "tigr.h"
+#include <math.h>
+
+float playerx = 160, playery = 200;
+float playerxs = 0, playerys = 0;
+bool standing = true;
+float remaining = 0;
+Tigr *backdrop, *screen;
+
+// Some simple platformer-esque physics.
+// I do not necessarily recommend this as a good way of implementing a platformer :)
+void update(float dt) {
+    if (remaining > 0)
+        remaining -= dt;
+
+    // Read the keyboard and move the player.
+    if (standing && tigrKeyDown(screen, TK_SPACE))
+        playerys -= 200;
+    if (tigrKeyHeld(screen, TK_LEFT) || tigrKeyHeld(screen, 'A'))
+        playerxs -= 10;
+    if (tigrKeyHeld(screen, TK_RIGHT) || tigrKeyHeld(screen, 'D'))
+        playerxs += 10;
+
+    float oldx = playerx, oldy = playery;
+
+    // Apply simply physics.
+    playerxs *= exp(-10.0f * dt);
+    playerys *= exp(-2.0f * dt);
+    playerys += dt * 200.0f;
+    playerx += dt * playerxs;
+    playery += dt * playerys;
+
+    // Apply collision.
+    if (playerx < 8) {
+        playerx = 8;
+        playerxs = 0;
+    }
+
+    if (playerx > screen->w - 8) {
+        playerx = screen->w - 8.0f;
+        playerxs = 0;
+    }
+
+    // Apply playfield collision and stepping.
+    float dx = (playerx - oldx) / 10;
+    float dy = (playery - oldy) / 10;
+    standing = false;
+    for (int n = 0; n < 10; n++) {
+        TPixel p = tigrGet(backdrop, (int)oldx, (int)oldy - 1);
+        if (p.r == 0 && p.g == 0 && p.b == 0)
+            oldy -= 1;
+        p = tigrGet(backdrop, (int)oldx, (int)oldy);
+        if (p.r == 0 && p.g == 0 && p.b == 0 && playerys > 0) {
+            playerys = 0;
+            dy = 0;
+            standing = true;
+        }
+        oldx += dx;
+        oldy += dy;
+    }
+
+    playerx = oldx;
+    playery = oldy;
+}
+
+int main(int argc, char* argv[]) {
+    // Load our sprite.
+    Tigr* squinkle = tigrLoadImage("squinkle.png");
+    if (!squinkle)
+        tigrError(0, "Cannot load squinkle.png");
+
+    // Load some UTF-8 text.
+    char* greeting = (char*)tigrReadFile("greeting.txt", 0);
+    if (!greeting)
+        tigrError(0, "Cannot load greeting.txt");
+
+    // Make a window and an off-screen backdrop.
+    screen = tigrWindow(320, 240, greeting, TIGR_2X);
+    backdrop = tigrBitmap(screen->w, screen->h);
+
+    // Fill in the background.
+    tigrClear(backdrop, tigrRGB(80, 180, 255));
+    tigrFill(backdrop, 0, 200, 320, 40, tigrRGB(60, 120, 60));
+    tigrFill(backdrop, 0, 200, 320, 3, tigrRGB(0, 0, 0));
+    tigrLine(backdrop, 0, 201, 320, 201, tigrRGB(255, 255, 255));
+
+    // Enable post fx
+    tigrSetPostFX(screen, 1, 1, 1, 2.0f);
+
+    int prevx = 0, prevy = 0, prev = 0;
+
+    // Maintain a list of characters entered.
+    int chars[16];
+    for (int n = 0; n < 16; n++)
+        chars[n] = '_';
+
+    // Repeat till they close the window.
+    while (!tigrClosed(screen) && !tigrKeyDown(screen, TK_ESCAPE)) {
+        // Update the game.
+        float dt = tigrTime();
+        update(dt);
+
+        // Read the mouse and draw lines when pressed.
+        int x, y, b;
+        tigrMouse(screen, &x, &y, &b);
+        if (b & 1) {
+            if (prev)
+                tigrLine(backdrop, prevx, prevy, x, y, tigrRGB(0, 0, 0));
+            prevx = x;
+            prevy = y;
+            prev = 1;
+        } else {
+            prev = 0;
+        }
+
+        // Composite the backdrop and sprite onto the screen.
+        tigrBlit(screen, backdrop, 0, 0, 0, 0, backdrop->w, backdrop->h);
+        tigrBlitAlpha(screen, squinkle, (int)playerx - squinkle->w / 2, (int)playery - squinkle->h, 0, 0, squinkle->w,
+                      squinkle->h, 1.0f);
+
+        tigrPrint(screen, tfont, 10, 10, tigrRGBA(0xc0, 0xd0, 0xff, 0xc0), greeting);
+        tigrPrint(screen, tfont, 10, 222, tigrRGBA(0xff, 0xff, 0xff, 0xff), "A D + SPACE");
+
+        // Grab any chars and add them to our buffer.
+        for (;;) {
+            int c = tigrReadChar(screen);
+            if (c == 0)
+                break;
+            for (int n = 1; n < 16; n++)
+                chars[n - 1] = chars[n];
+            chars[15] = c;
+        }
+
+        // Print out the character buffer too.
+        char tmp[100], *p = tmp;
+        for (int n = 0; n < 16; n++)
+            p = tigrEncodeUTF8(p, chars[n]);
+        *p = 0;
+        tigrPrint(screen, tfont, 160, 222, tigrRGB(255, 255, 255), "Chars: %s", tmp);
+
+        // Update the window.
+        tigrUpdate(screen);
+    }
+
+    tigrFree(squinkle);
+    tigrFree(backdrop);
+    tigrFree(screen);
+    return 0;
+}

+ 2 - 0
tigr-master/examples/demo/greeting.txt

@@ -0,0 +1,2 @@
+¡Hola! Välkommen!
+You can draw in here with the mouse.

二進制
tigr-master/examples/demo/squinkle.png


+ 14 - 0
tigr-master/examples/flags/Makefile

@@ -0,0 +1,14 @@
+CFLAGS += -I../..
+ifeq ($(OS),Windows_NT)
+	LDFLAGS += -s -lopengl32 -lgdi32
+else
+	UNAME_S := $(shell uname -s)
+	ifeq ($(UNAME_S),Darwin)
+		LDFLAGS += -framework OpenGL -framework Cocoa
+	else ifeq ($(UNAME_S),Linux)
+		LDFLAGS += -s -lGLU -lGL -lX11
+	endif
+endif
+
+flags : flags.c ../../tigr.c
+	gcc $^ -Os -o $@ $(CFLAGS) $(LDFLAGS)

+ 97 - 0
tigr-master/examples/flags/flags.c

@@ -0,0 +1,97 @@
+#include "tigr.h"
+
+Tigr* makeDemoWindow(int w, int h, int flags) {
+    return tigrWindow(w, h, "Flag tester", flags);
+}
+
+void drawDemoWindow(Tigr* win) {
+    TPixel lineColor = tigrRGB(100, 100, 100);
+    tigrLine(win, 0, 0, win->w - 1, win->h - 1, lineColor);
+    tigrLine(win, 0, win->h - 1, win->w - 1, 0, lineColor);
+    tigrRect(win, 0, 0, win->w, win->h, tigrRGB(200, 10, 10));
+    tigrPrint(win, tfont, 5, 5, tigrRGB(20, 200, 0), "%dx%d", win->w, win->h);
+}
+
+typedef struct Toggle {
+    const char* text;
+    int checked;
+    int value;
+    int key;
+    TPixel color;
+} Toggle;
+
+void drawToggle(Tigr* bmp, Toggle* toggle, int x, int y, int stride) {
+    int height = tigrTextHeight(tfont, toggle->text);
+    int width = tigrTextWidth(tfont, toggle->text);
+
+    int yOffset = stride / 2;
+    int xOffset = width / -2;
+
+    tigrPrint(bmp, tfont, x + xOffset, y + yOffset, toggle->color, toggle->text);
+
+    yOffset += toggle->checked ? height : height / 3;
+    TPixel lineColor = toggle->color;
+    lineColor.a = 240;
+    tigrLine(bmp, x + xOffset, y + yOffset, x + xOffset + width, y + yOffset, lineColor);
+}
+
+int main(int argc, char* argv[]) {
+    int flags = 0;
+    int initialW = 400;
+    int initialH = 400;
+    Tigr* win = makeDemoWindow(initialW, initialH, flags);
+    TPixel white = tigrRGB(255, 255, 255);
+    TPixel yellow = tigrRGB(255, 255, 0);
+    TPixel black = tigrRGB(0, 0, 0);
+
+    Toggle toggles[] = {
+        //
+        { "(A)UTO", 0, TIGR_AUTO, 'A', white },
+        { "(R)ETINA", 0, TIGR_RETINA, 'R', white },
+        { "(F)ULLSCREEN", 0, TIGR_FULLSCREEN, 'F', white },
+        { "(2)X", 0, TIGR_2X, '2', yellow },
+        { "(3)X", 0, TIGR_3X, '3', yellow },
+        { "(4)X", 0, TIGR_4X, '4', yellow },
+        { "(N)OCURSOR", 0, TIGR_NOCURSOR, 'N', white },
+    };
+
+    while (!tigrClosed(win) && !tigrKeyDown(win, TK_ESCAPE)) {
+        tigrClear(win, black);
+
+        drawDemoWindow(win);
+
+        int numToggles = sizeof(toggles) / sizeof(*toggles);
+        int stepY = win->h / numToggles;
+        int toggleY = 0;
+        int toggleX = win->w / 2;
+
+        {
+            int newFlags = 0;
+            for (int i = 0; i < numToggles; i++, toggleY += stepY) {
+                Toggle* toggle = toggles + i;
+                if (tigrKeyDown(win, toggle->key)) {
+                    toggle->checked ^= 1;
+                }
+                newFlags += toggle->checked * toggle->value;
+                drawToggle(win, toggle, toggleX, toggleY, stepY);
+            }
+
+            if (flags != newFlags) {
+                int modeFlags = TIGR_AUTO | TIGR_RETINA;
+                int modeChange = (flags & modeFlags) != (newFlags & modeFlags);
+                flags = newFlags;
+
+                int w = modeChange ? initialW : win->w;
+                int h = modeChange ? initialH : win->h;
+                tigrFree(win);
+                win = makeDemoWindow(w, h, flags);
+            }
+        }
+
+        tigrUpdate(win);
+    }
+
+    tigrFree(win);
+
+    return 0;
+}

+ 4 - 0
tigr-master/examples/headless/Makefile

@@ -0,0 +1,4 @@
+CFLAGS += -I../.. -Wall -DTIGR_HEADLESS
+
+headless : headless.c ../../tigr.c
+	gcc $^ -Os -o $@ $(CFLAGS)

+ 10 - 0
tigr-master/examples/headless/headless.c

@@ -0,0 +1,10 @@
+#include "tigr.h"
+
+int main(int argc, char* argv[]) {
+    Tigr* bmp = tigrBitmap(320, 240);    
+    tigrClear(bmp, tigrRGB(0x80, 0x90, 0xa0));
+    tigrPrint(bmp, tfont, 120, 110, tigrRGB(0xff, 0xff, 0xff), "Hello, world.");
+    tigrSaveImage("headless.png", bmp);
+    tigrFree(bmp);
+    return 0;
+}

+ 14 - 0
tigr-master/examples/hello/Makefile

@@ -0,0 +1,14 @@
+CFLAGS += -I../..
+ifeq ($(OS),Windows_NT)
+	LDFLAGS += -s -lopengl32 -lgdi32
+else
+	UNAME_S := $(shell uname -s)
+	ifeq ($(UNAME_S),Darwin)
+		LDFLAGS += -framework OpenGL -framework Cocoa
+	else ifeq ($(UNAME_S),Linux)
+		LDFLAGS += -s -lGLU -lGL -lX11
+	endif
+endif
+
+hello : hello.c ../../tigr.c
+	gcc $^ -Os -o $@ $(CFLAGS) $(LDFLAGS)

+ 12 - 0
tigr-master/examples/hello/hello.c

@@ -0,0 +1,12 @@
+#include "tigr.h"
+
+int main(int argc, char* argv[]) {
+    Tigr* screen = tigrWindow(320, 240, "Hello", 0);
+    while (!tigrClosed(screen) && !tigrKeyDown(screen, TK_ESCAPE)) {
+        tigrClear(screen, tigrRGB(0x80, 0x90, 0xa0));
+        tigrPrint(screen, tfont, 120, 110, tigrRGB(0xff, 0xff, 0xff), "Hello, world.");
+        tigrUpdate(screen);
+    }
+    tigrFree(screen);
+    return 0;
+}

+ 14 - 0
tigr-master/examples/opengl/Makefile

@@ -0,0 +1,14 @@
+CFLAGS += -I../..
+ifeq ($(OS),Windows_NT)
+	LDFLAGS += -s -lopengl32 -lgdi32
+else
+	UNAME_S := $(shell uname -s)
+	ifeq ($(UNAME_S),Darwin)
+		LDFLAGS += -framework OpenGL -framework Cocoa
+	else
+		LDFLAGS += -s -lGLU -lGL -lX11
+	endif
+endif
+
+opengl: opengl.c ../../tigr.c
+	gcc $^ -Os -o $@ $(CFLAGS) $(LDFLAGS)

+ 49 - 0
tigr-master/examples/opengl/opengl.c

@@ -0,0 +1,49 @@
+#include "tigr.h"
+#include <stdlib.h>
+#ifdef _WIN32
+#include <winsock2.h>
+#include <GL/gl.h>
+#elif defined __linux__
+#include <GL/gl.h>
+#else
+#define GL_SILENCE_DEPRECATION
+#include <OpenGL/gl3.h>
+#endif
+
+int main(int argc, char* argv[]) {
+    Tigr* win1 = tigrWindow(320, 240, "Window1", 0);
+    Tigr* win2 = tigrWindow(320, 240, "Window2", 0);
+    while (win1 || win2) {
+        if (win1) {
+            tigrClear(win1, tigrRGB(0x80, 0x90, 0xa0));
+            tigrPrint(win1, tfont, 120, 110, tigrRGB(0xff, 0xff, 0xff), "Hello, world #1.");
+            tigrUpdate(win1);
+
+            if (tigrClosed(win1) || tigrKeyDown(win1, TK_ESCAPE)) {
+                tigrFree(win1);
+                win1 = NULL;
+            }
+        }
+
+        if (win2) {
+            if (tigrBeginOpenGL(win2)) {
+                glClearColor(1, 0, 1, 1);
+                glClear(GL_COLOR_BUFFER_BIT);
+            }
+
+            tigrClear(win2, tigrRGBA(0x00, 0x00, 0x00, 0x00));
+            tigrPrint(win2, tfont, 120, 110, tigrRGB(0xff, 0xff, 0xff), "Hello, world #2.");
+            tigrUpdate(win2);
+
+            if (tigrClosed(win2) || tigrKeyDown(win2, TK_ESCAPE)) {
+                tigrFree(win2);
+                win2 = NULL;
+            }
+        }
+    }
+    if (win1)
+        tigrFree(win1);
+    if (win2)
+        tigrFree(win2);
+    return 0;
+}

+ 14 - 0
tigr-master/examples/shader/Makefile

@@ -0,0 +1,14 @@
+CFLAGS += -I../..
+ifeq ($(OS),Windows_NT)
+	LDFLAGS += -s -lopengl32 -lgdi32
+else
+	UNAME_S := $(shell uname -s)
+	ifeq ($(UNAME_S),Darwin)
+		LDFLAGS += -framework OpenGL -framework Cocoa
+	else ifeq ($(UNAME_S),Linux)
+		LDFLAGS += -s -lGLU -lGL -lX11
+	endif
+endif
+
+shader : shader.c ../../tigr.c
+	gcc $^ -Os -o $@ $(CFLAGS) $(LDFLAGS)

+ 30 - 0
tigr-master/examples/shader/shader.c

@@ -0,0 +1,30 @@
+#include "tigr.h"
+#include "math.h"
+
+const char fxShader[] =
+    "void fxShader(out vec4 color, in vec2 uv) {"
+    "   vec2 tex_size = vec2(textureSize(image, 0));"
+    "   vec4 c = texture(image, (floor(uv * tex_size) + 0.5 * sin(parameters.x)) / tex_size);"
+    "   color = c;"
+    "}";
+
+int main(int argc, char* argv[]) {
+    Tigr* screen = tigrWindow(320, 240, "Shady", 0);
+    tigrSetPostShader(screen, fxShader, sizeof(fxShader) - 1);
+
+    float duration = 1;
+    float phase = 0;
+    while (!tigrClosed(screen) && !tigrKeyDown(screen, TK_ESCAPE)) {
+        phase += tigrTime();
+        while (phase > duration) {
+            phase -= duration;
+        }
+        float p = 6.28 * phase / duration;
+        tigrSetPostFX(screen, p, 0, 0, 0);
+        tigrClear(screen, tigrRGB(0x80, 0x90, 0xa0));
+        tigrPrint(screen, tfont, 120, 110, tigrRGB(0xff, 0xff, 0xff), "Shady business");
+        tigrUpdate(screen);
+    }
+    tigrFree(screen);
+    return 0;
+}

+ 9 - 0
tigr-master/src/Makefile

@@ -0,0 +1,9 @@
+all : ../tigr.c
+
+.PHONY : ../tigr.c
+
+tigr_font.h : font.png incbin.pl
+	perl incbin.pl $@ $< tigr_font
+
+../tigr.c : tigr_font.h bundle.pl
+	perl bundle.pl $@ tigr_amalgamated.c

+ 38 - 0
tigr-master/src/bundle.pl

@@ -0,0 +1,38 @@
+#!/usr/bin/perl
+
+use File::Spec;
+
+my $out = shift @ARGV or die;
+my $root = shift @ARGV or die;
+($volume,$dir,$file) = File::Spec->splitpath($root);
+
+open OUT, ">$out" or die "$out: $!\n";
+my %done;
+
+sub inline
+{
+	my ($name) = @_;
+	return 0 if ($done{$name});
+	$done{$name} = 1;
+	print " - $name\n";
+	print OUT "//////// Start of inlined file: $name ////////\n\n";
+	open my $IN, $name or die "$name: $!\n";
+	while (<$IN>) {
+		if (/^#include \"(.+)\"/ && -f $1 && !inline($1)) {
+			print OUT "//$_";
+		} else {
+			print OUT "$_";
+		}
+	}
+	close $IN;
+	print OUT "\n//////// End of inlined file: $name ////////\n\n";
+	return 1;
+}
+
+if(!$volume.$dir eq "")
+{
+	chdir $volume.$dir;
+}
+inline($file);
+
+close OUT;

二進制
tigr-master/src/font.png


+ 31 - 0
tigr-master/src/incbin.pl

@@ -0,0 +1,31 @@
+#!/usr/bin/perl
+
+sub usage {
+	die "incbin.pl out.c in.bin symbol\n";
+}
+
+my $out = shift @ARGV or usage();
+my $in = shift @ARGV or usage();
+my $symbol = shift @ARGV or usage();
+
+open OUT, ">$out" or die "$out: $!\n";
+open IN, "$in" or die "$in: $!\n";
+binmode(IN);
+print OUT "// Auto-generated by incbin.pl from $in\n\n";
+print OUT "const unsigned char ${symbol}[] = {\n";
+my $i = 0;
+while (read(IN, my $byte, 1)) {
+	print OUT "\t" if ($i == 0);
+	printf OUT "0x%02x", (unpack 'C', $byte);
+	print OUT "," unless eof IN;
+	$i++;
+	if ($i == 16) {
+		$i = 0;
+		print OUT "\n";
+	}
+}
+print OUT " };\n\n";
+print OUT "int ${symbol}_size = (int)sizeof($symbol);\n\n";
+close IN;
+close OUT;
+

+ 20 - 0
tigr-master/src/tigr_amalgamated.c

@@ -0,0 +1,20 @@
+
+#include "tigr.h"
+
+#include "tigr_internal.h"
+#include "tigr_upscale_gl_vs.h"
+#include "tigr_upscale_gl_fs.h"
+
+#include "tigr_bitmaps.c"
+#include "tigr_loadpng.c"
+#include "tigr_savepng.c"
+#include "tigr_inflate.c"
+#include "tigr_print.c"
+#include "tigr_win.c"
+#include "tigr_osx.c"
+#include "tigr_ios.c"
+#include "tigr_android.h"
+#include "tigr_linux.c"
+#include "tigr_android.c"
+#include "tigr_gl.c"
+#include "tigr_utils.c"

+ 18 - 0
tigr-master/src/tigr_amalgamated_one_header.h

@@ -0,0 +1,18 @@
+/*
+Like most of stb libraries, this include needs to be included in one
+compilation unit with TIGR_IMPLEMENTATION defined.
+*/
+
+#include "../tigr.h"
+
+#ifdef TIGR_IMPLEMENTATION
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "tigr_amalgamated.c"
+
+#ifdef __cplusplus
+}
+#endif
+#endif

+ 767 - 0
tigr-master/src/tigr_android.c

@@ -0,0 +1,767 @@
+#ifndef TIGR_HEADLESS
+
+#include "tigr_internal.h"
+
+#ifdef __ANDROID__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <android/log.h>
+#include <android/native_window.h>
+#include <android/input.h>
+
+#ifndef NDEBUG
+#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "tigr", __VA_ARGS__))
+#else
+#define LOGD(...) ((void)0)
+#endif
+#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "tigr", __VA_ARGS__))
+#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "tigr", __VA_ARGS__))
+
+typedef struct {
+    TigrTouchPoint points[MAX_TOUCH_POINTS];
+    int numPoints;
+} InputState;
+
+/// Global state
+static struct {
+    ANativeWindow* window;
+    InputState inputState;
+    EGLDisplay display;
+    EGLSurface surface;
+    EGLint screenW;
+    EGLint screenH;
+    EGLConfig config;
+    double lastTime;
+    int closed;
+} gState = {
+    .window = 0,
+    .inputState = {
+        .numPoints = 0,
+    },
+    .display = EGL_NO_DISPLAY,
+    .surface = EGL_NO_SURFACE,
+    .screenW = 0,
+    .screenH = 0,
+    .config = 0,
+    .lastTime = 0,
+    .closed = 0,
+};
+
+static const EGLint contextAttribs[] = { EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 0, EGL_NONE };
+
+static EGLConfig getGLConfig(EGLDisplay display) {
+    EGLConfig config = 0;
+
+    const EGLint attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8,
+                               EGL_NONE };
+    EGLint numConfigs;
+
+    eglChooseConfig(display, attribs, NULL, 0, &numConfigs);
+    EGLConfig* supportedConfigs = (EGLConfig*)malloc(sizeof(EGLConfig) * numConfigs);
+    eglChooseConfig(display, attribs, supportedConfigs, numConfigs, &numConfigs);
+
+    int i = 0;
+    for (; i < numConfigs; i++) {
+        EGLConfig* cfg = supportedConfigs[i];
+        EGLint r, g, b, d;
+        if (eglGetConfigAttrib(display, cfg, EGL_RED_SIZE, &r) &&
+            eglGetConfigAttrib(display, cfg, EGL_GREEN_SIZE, &g) &&
+            eglGetConfigAttrib(display, cfg, EGL_BLUE_SIZE, &b) &&
+            eglGetConfigAttrib(display, cfg, EGL_DEPTH_SIZE, &d) && r == 8 && g == 8 && b == 8 && d == 0) {
+            config = supportedConfigs[i];
+            break;
+        }
+    }
+
+    if (i == numConfigs) {
+        config = supportedConfigs[0];
+    }
+
+    if (config == NULL) {
+        tigrError(NULL, "Unable to initialize EGLConfig");
+    }
+
+    free(supportedConfigs);
+
+    return config;
+}
+
+/// Android interface, called from main thread
+
+void tigr_android_create() {
+    gState.closed = 0;
+    gState.window = 0;
+    gState.inputState.numPoints = 0;
+    gState.surface = EGL_NO_SURFACE;
+    gState.lastTime = 0;
+
+    if (gState.display == EGL_NO_DISPLAY) {
+        gState.display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+        EGLBoolean status = eglInitialize(gState.display, NULL, NULL);
+        if (!status) {
+            tigrError(NULL, "Failed to init EGL");
+        }
+        gState.config = getGLConfig(gState.display);
+    }
+}
+
+void tigr_android_destroy() {
+    eglTerminate(gState.display);
+    gState.display = EGL_NO_DISPLAY;
+}
+
+/// Internals ///
+
+static void logEglError() {
+    int error = eglGetError();
+    switch (error) {
+        case EGL_BAD_DISPLAY:
+            LOGE("EGL error: Bad display");
+            break;
+        case EGL_NOT_INITIALIZED:
+            LOGE("EGL error: Not initialized");
+            break;
+        case EGL_BAD_NATIVE_WINDOW:
+            LOGE("EGL error: Bad native window");
+            break;
+        case EGL_BAD_ALLOC:
+            LOGE("EGL error: Bad alloc");
+            break;
+        case EGL_BAD_MATCH:
+            LOGE("EGL error: Bad match");
+            break;
+        default:
+            LOGE("EGL error: %d", error);
+    }
+}
+
+static void setupOpenGL() {
+    LOGD("setupOpenGL");
+    assert(gState.surface == EGL_NO_SURFACE);
+    assert(gState.window != 0);
+
+    gState.surface = eglCreateWindowSurface(gState.display, gState.config, gState.window, NULL);
+    if (gState.surface == EGL_NO_SURFACE) {
+        logEglError();
+    }
+    assert(gState.surface != EGL_NO_SURFACE);
+    eglQuerySurface(gState.display, gState.surface, EGL_WIDTH, &gState.screenW);
+    eglQuerySurface(gState.display, gState.surface, EGL_HEIGHT, &gState.screenH);
+    LOGD("Screen is %d x %d", gState.screenW, gState.screenH);
+}
+
+static void tearDownOpenGL() {
+    LOGD("tearDownOpenGL");
+    if (gState.display != EGL_NO_DISPLAY) {
+        eglMakeCurrent(gState.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+
+        if (gState.surface != EGL_NO_SURFACE) {
+            LOGD("eglDestroySurface");
+            if (!eglDestroySurface(gState.display, gState.surface)) {
+                logEglError();
+            }
+            gState.surface = EGL_NO_SURFACE;
+        }
+    }
+}
+
+static int tigrKeyFromAndroidKey(int key) {
+    switch (key) {
+        case AKEYCODE_Q:
+            return 'Q';
+        case AKEYCODE_W:
+            return 'W';
+        case AKEYCODE_E:
+            return 'E';
+        case AKEYCODE_R:
+            return 'R';
+        case AKEYCODE_T:
+            return 'T';
+        case AKEYCODE_Y:
+            return 'Y';
+        case AKEYCODE_U:
+            return 'U';
+        case AKEYCODE_I:
+            return 'I';
+        case AKEYCODE_O:
+            return 'O';
+        case AKEYCODE_P:
+            return 'P';
+
+        case AKEYCODE_A:
+            return 'A';
+        case AKEYCODE_S:
+            return 'S';
+        case AKEYCODE_D:
+            return 'D';
+        case AKEYCODE_F:
+            return 'F';
+        case AKEYCODE_G:
+            return 'G';
+        case AKEYCODE_H:
+            return 'H';
+        case AKEYCODE_J:
+            return 'J';
+        case AKEYCODE_K:
+            return 'K';
+        case AKEYCODE_L:
+            return 'L';
+
+        case AKEYCODE_Z:
+            return 'Z';
+        case AKEYCODE_X:
+            return 'X';
+        case AKEYCODE_C:
+            return 'C';
+        case AKEYCODE_V:
+            return 'V';
+        case AKEYCODE_B:
+            return 'B';
+        case AKEYCODE_N:
+            return 'N';
+        case AKEYCODE_M:
+            return 'M';
+
+        case AKEYCODE_0:
+            return '0';
+        case AKEYCODE_1:
+            return '1';
+        case AKEYCODE_2:
+            return '2';
+        case AKEYCODE_3:
+            return '3';
+        case AKEYCODE_4:
+            return '4';
+        case AKEYCODE_5:
+            return '5';
+        case AKEYCODE_6:
+            return '6';
+        case AKEYCODE_7:
+            return '7';
+        case AKEYCODE_8:
+            return '8';
+        case AKEYCODE_9:
+            return '9';
+
+        case AKEYCODE_NUMPAD_0:
+            return TK_PAD0;
+        case AKEYCODE_NUMPAD_1:
+            return TK_PAD1;
+        case AKEYCODE_NUMPAD_2:
+            return TK_PAD2;
+        case AKEYCODE_NUMPAD_3:
+            return TK_PAD3;
+        case AKEYCODE_NUMPAD_4:
+            return TK_PAD4;
+        case AKEYCODE_NUMPAD_5:
+            return TK_PAD5;
+        case AKEYCODE_NUMPAD_6:
+            return TK_PAD6;
+        case AKEYCODE_NUMPAD_7:
+            return TK_PAD7;
+        case AKEYCODE_NUMPAD_8:
+            return TK_PAD8;
+        case AKEYCODE_NUMPAD_9:
+            return TK_PAD9;
+
+        case AKEYCODE_NUMPAD_MULTIPLY:
+            return TK_PADMUL;
+        case AKEYCODE_NUMPAD_DIVIDE:
+            return TK_PADDIV;
+        case AKEYCODE_NUMPAD_ADD:
+            return TK_PADADD;
+        case AKEYCODE_NUMPAD_SUBTRACT:
+            return TK_PADSUB;
+        case AKEYCODE_NUMPAD_ENTER:
+            return TK_PADENTER;
+        case AKEYCODE_NUMPAD_DOT:
+            return TK_PADDOT;
+
+        case AKEYCODE_F1:
+            return TK_F1;
+        case AKEYCODE_F2:
+            return TK_F2;
+        case AKEYCODE_F3:
+            return TK_F3;
+        case AKEYCODE_F4:
+            return TK_F4;
+        case AKEYCODE_F5:
+            return TK_F5;
+        case AKEYCODE_F6:
+            return TK_F6;
+        case AKEYCODE_F7:
+            return TK_F7;
+        case AKEYCODE_F8:
+            return TK_F8;
+        case AKEYCODE_F9:
+            return TK_F9;
+        case AKEYCODE_F10:
+            return TK_F10;
+        case AKEYCODE_F11:
+            return TK_F11;
+        case AKEYCODE_F12:
+            return TK_F12;
+
+        case AKEYCODE_SHIFT_LEFT:
+            return TK_LSHIFT;
+        case AKEYCODE_SHIFT_RIGHT:
+            return TK_RSHIFT;
+        case AKEYCODE_CTRL_LEFT:
+            return TK_LCONTROL;
+        case AKEYCODE_CTRL_RIGHT:
+            return TK_RCONTROL;
+        case AKEYCODE_ALT_LEFT:
+            return TK_LALT;
+        case AKEYCODE_ALT_RIGHT:
+            return TK_RALT;
+        case AKEYCODE_META_LEFT:
+            return TK_LWIN;
+        case AKEYCODE_META_RIGHT:
+            return TK_RWIN;
+
+        case AKEYCODE_DEL:
+            return TK_BACKSPACE;
+        case AKEYCODE_TAB:
+            return TK_TAB;
+        case AKEYCODE_ENTER:
+            return TK_RETURN;
+        case AKEYCODE_CAPS_LOCK:
+            return TK_CAPSLOCK;
+        case AKEYCODE_ESCAPE:
+        case AKEYCODE_BACK:
+            return TK_ESCAPE;
+        case AKEYCODE_SPACE:
+            return TK_SPACE;
+
+        case AKEYCODE_PAGE_UP:
+            return TK_PAGEUP;
+        case AKEYCODE_PAGE_DOWN:
+            return TK_PAGEDN;
+        case AKEYCODE_MOVE_END:
+            return TK_END;
+        case AKEYCODE_MOVE_HOME:
+            return TK_HOME;
+        case AKEYCODE_DPAD_LEFT:
+            return TK_LEFT;
+        case AKEYCODE_DPAD_RIGHT:
+            return TK_RIGHT;
+        case AKEYCODE_DPAD_UP:
+            return TK_UP;
+        case AKEYCODE_DPAD_DOWN:
+            return TK_DOWN;
+
+        case AKEYCODE_INSERT:
+            return TK_INSERT;
+        case AKEYCODE_FORWARD_DEL:
+            return TK_DELETE;
+        case AKEYCODE_NUM_LOCK:
+            return TK_NUMLOCK;
+        case AKEYCODE_SCROLL_LOCK:
+            return TK_SCROLL;
+
+        case AKEYCODE_SEMICOLON:
+            return TK_SEMICOLON;
+        case AKEYCODE_EQUALS:
+            return TK_EQUALS;
+        case AKEYCODE_COMMA:
+            return TK_COMMA;
+        case AKEYCODE_MINUS:
+            return TK_MINUS;
+        case AKEYCODE_PERIOD:
+            return TK_DOT;
+        case AKEYCODE_SLASH:
+            return TK_SLASH;
+        case AKEYCODE_BACKSLASH:
+            return TK_BACKSLASH;
+        case AKEYCODE_GRAVE:
+            return TK_BACKTICK;
+        case AKEYCODE_APOSTROPHE:
+            return TK_TICK;
+        case AKEYCODE_LEFT_BRACKET:
+            return TK_LSQUARE;
+        case AKEYCODE_RIGHT_BRACKET:
+            return TK_RSQUARE;
+
+        default:
+            return 0;
+    }
+}
+
+static int processInputEvent(AInputEvent* event, TigrInternal* win) {
+    if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
+        int32_t action = AMotionEvent_getAction(event);
+        int32_t actionCode = action & AMOTION_EVENT_ACTION_MASK;
+
+        size_t touchPoints = AMotionEvent_getPointerCount(event);
+        size_t releasedIndex = -1;
+        if (actionCode == AMOTION_EVENT_ACTION_POINTER_UP) {
+            releasedIndex =
+                (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+        }
+        size_t targetPointCount = 0;
+
+        for (size_t i = 0; i < touchPoints && i < MAX_TOUCH_POINTS; i++) {
+            if (i == releasedIndex) {
+                continue;
+            }
+            gState.inputState.points[targetPointCount].x = AMotionEvent_getX(event, i);
+            gState.inputState.points[targetPointCount].y = AMotionEvent_getY(event, i);
+            targetPointCount++;
+        }
+
+        if (actionCode == AMOTION_EVENT_ACTION_UP || actionCode == AMOTION_EVENT_ACTION_CANCEL) {
+            gState.inputState.numPoints = 0;
+        } else {
+            gState.inputState.numPoints = targetPointCount;
+        }
+        return 1;
+    } else if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) {
+        if (!win) {
+            return 1;
+        }
+        int32_t deviceID = AInputEvent_getDeviceId(event);
+        // KeyCharacterMap#VIRTUAL_KEYBOARD == -1
+        if (deviceID != -1) {
+            return 1;
+        }
+        int32_t action = AKeyEvent_getAction(event);
+        int32_t keyCode = AKeyEvent_getKeyCode(event);
+        int key = tigrKeyFromAndroidKey(keyCode);
+        // We pass the character in the scancode field from the Java side
+        int32_t unicodeChar = AKeyEvent_getScanCode(event);
+
+        if (action == AKEY_EVENT_ACTION_DOWN) {
+            win->keys[key] = 1;
+            win->lastChar = unicodeChar;
+        } else if (action == AKEY_EVENT_ACTION_UP) {
+            win->released[key] = 1;
+        }
+        return 1;
+    }
+    return 0;
+}
+
+static int handleEvent(AndroidEvent event, void* userData) {
+    switch (event.type) {
+        case AE_WINDOW_CREATED:
+            gState.window = event.window;
+            setupOpenGL();
+            break;
+
+        case AE_WINDOW_DESTROYED:
+            tearDownOpenGL();
+            gState.window = 0;
+            break;
+
+        case AE_INPUT:
+            return processInputEvent(event.inputEvent, (TigrInternal*)userData);
+
+        case AE_RESUME:
+            gState.lastTime = event.time;
+            break;
+
+        case AE_CLOSE:
+            gState.closed = 1;
+            break;
+
+        default:
+            LOGE("Unhandled event type: %d", event.type);
+            return 0;
+    }
+
+    return 1;
+}
+
+static int processEvents(TigrInternal* win) {
+    if (gState.closed) {
+        return 0;
+    }
+
+    while (android_pollEvent(handleEvent, win)) {
+        if (gState.closed) {
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
+static Tigr* refreshWindow(Tigr* bmp) {
+    if (bmp->handle == gState.window) {
+        return bmp;
+    }
+
+    bmp->handle = gState.window;
+    if (gState.window == 0) {
+        return 0;
+    }
+
+    TigrInternal* win = tigrInternal(bmp);
+
+    int scale = 1;
+    if (win->flags & TIGR_AUTO) {
+        // Always use a 1:1 pixel size.
+        scale = 1;
+    } else {
+        // See how big we can make it and still fit on-screen.
+        scale = tigrCalcScale(bmp->w, bmp->h, gState.screenW, gState.screenH);
+    }
+
+    win->scale = tigrEnforceScale(scale, win->flags);
+
+    return bmp;
+}
+
+/// TIGR interface implementation, called from render thread ///
+
+Tigr* tigrWindow(int w, int h, const char* title, int flags) {
+    while (gState.window == NULL) {
+        if (!processEvents(0)) {
+            return NULL;
+        }
+    }
+
+    EGLContext context = eglCreateContext(gState.display, gState.config, NULL, contextAttribs);
+
+    int scale = 1;
+    if (flags & TIGR_AUTO) {
+        // Always use a 1:1 pixel size.
+        scale = 1;
+    } else {
+        // See how big we can make it and still fit on-screen.
+        scale = tigrCalcScale(w, h, gState.screenW, gState.screenH);
+    }
+
+    scale = tigrEnforceScale(scale, flags);
+
+    Tigr* bmp = tigrBitmap2(w, h, sizeof(TigrInternal));
+    bmp->handle = (void*)gState.window;
+
+    TigrInternal* win = tigrInternal(bmp);
+    win->context = context;
+
+    win->shown = 0;
+    win->closed = 0;
+    win->scale = scale;
+
+    win->lastChar = 0;
+    win->flags = flags;
+    win->p1 = win->p2 = win->p3 = 0;
+    win->p4 = 1;
+    win->widgetsWanted = 0;
+    win->widgetAlpha = 0;
+    win->widgetsScale = 0;
+    win->widgets = 0;
+    win->gl.gl_legacy = 0;
+
+    memset(win->keys, 0, 256);
+    memset(win->prev, 0, 256);
+    memset(win->released, 0, 256);
+
+    tigrPosition(bmp, win->scale, bmp->w, bmp->h, win->pos);
+
+    if (eglMakeCurrent(gState.display, gState.surface, gState.surface, context) == EGL_FALSE) {
+        LOGE("Unable to eglMakeCurrent");
+        return 0;
+    }
+
+    tigrGAPICreate(bmp);
+
+    return bmp;
+}
+
+int tigrClosed(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    return win->closed;
+}
+
+int tigrGAPIBegin(Tigr* bmp) {
+    assert(gState.display != EGL_NO_DISPLAY);
+    assert(gState.surface != EGL_NO_SURFACE);
+
+    TigrInternal* win = tigrInternal(bmp);
+    if (eglMakeCurrent(gState.display, gState.surface, gState.surface, win->context) == EGL_FALSE) {
+        return -1;
+    }
+    return 0;
+}
+
+int tigrGAPIEnd(Tigr* bmp) {
+    (void)bmp;
+    eglMakeCurrent(gState.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+    return 0;
+}
+
+int tigrKeyDown(Tigr* bmp, int key) {
+    TigrInternal* win;
+    assert(key < 256);
+    win = tigrInternal(bmp);
+    return win->keys[key] && !win->prev[key];
+}
+
+int tigrKeyHeld(Tigr* bmp, int key) {
+    TigrInternal* win;
+    assert(key < 256);
+    win = tigrInternal(bmp);
+    return win->keys[key];
+}
+
+int tigrReadChar(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    int c = win->lastChar;
+    win->lastChar = 0;
+    return c;
+}
+
+static void tigrUpdateModifiers(TigrInternal* win) {
+    win->keys[TK_SHIFT] = win->keys[TK_LSHIFT] || win->keys[TK_RSHIFT];
+    win->keys[TK_CONTROL] = win->keys[TK_LCONTROL] || win->keys[TK_RCONTROL];
+    win->keys[TK_ALT] = win->keys[TK_LALT] || win->keys[TK_RALT];
+}
+
+static int toWindowX(TigrInternal* win, int x) {
+    return (x - win->pos[0]) / win->scale;
+}
+
+static int toWindowY(TigrInternal* win, int y) {
+    return (y - win->pos[1]) / win->scale;
+}
+
+void tigrUpdate(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    memcpy(win->prev, win->keys, 256);
+    for (int i = 0; i < 256; i++) {
+        win->keys[i] ^= win->released[i];
+        win->released[i] = 0;
+    }
+
+    if (!processEvents(win)) {
+        win->closed = 1;
+        return;
+    }
+
+    tigrUpdateModifiers(win);
+
+    if (gState.window == 0) {
+        return;
+    }
+
+    bmp = refreshWindow(bmp);
+    if (bmp == 0) {
+        return;
+    }
+
+    win->numTouchPoints = gState.inputState.numPoints;
+    for (int i = 0; i < win->numTouchPoints; i++) {
+        win->touchPoints[i].x = toWindowX(win, gState.inputState.points[i].x);
+        win->touchPoints[i].y = toWindowY(win, gState.inputState.points[i].y);
+    }
+
+    win->mouseButtons = win->numTouchPoints;
+    if (win->mouseButtons > 0) {
+        win->mouseX = win->touchPoints[0].x;
+        win->mouseY = win->touchPoints[0].y;
+    }
+
+    if (win->flags & TIGR_AUTO) {
+        tigrResize(bmp, gState.screenW / win->scale, gState.screenH / win->scale);
+    } else {
+        win->scale = tigrEnforceScale(tigrCalcScale(bmp->w, bmp->h, gState.screenW, gState.screenH), win->flags);
+    }
+
+    tigrPosition(bmp, win->scale, gState.screenW, gState.screenH, win->pos);
+    tigrGAPIBegin(bmp);
+    tigrGAPIPresent(bmp, gState.screenW, gState.screenH);
+    android_swap(gState.display, gState.surface);
+    tigrGAPIEnd(bmp);
+}
+
+void tigrFree(Tigr* bmp) {
+    if (bmp->handle) {
+        TigrInternal* win = tigrInternal(bmp);
+
+        eglMakeCurrent(gState.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+        if (win->context != EGL_NO_CONTEXT) {
+            // Win closed means app windows has closed, and the call would fail.
+            if (!win->closed) {
+                tigrGAPIDestroy(bmp);
+            }
+            eglDestroyContext(gState.display, win->context);
+        }
+
+        win->context = EGL_NO_CONTEXT;
+    }
+    free(bmp->pix);
+    free(bmp);
+}
+
+void tigrError(Tigr* bmp, const char* message, ...) {
+    char tmp[1024];
+
+    va_list args;
+    va_start(args, message);
+    vsnprintf(tmp, sizeof(tmp), message, args);
+    tmp[sizeof(tmp) - 1] = 0;
+    va_end(args);
+
+    LOGE("tigr fatal error: %s\n", tmp);
+
+    exit(1);
+}
+
+float tigrTime() {
+    struct timespec ts;
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+
+    double now = (double)ts.tv_sec + (ts.tv_nsec / 1000000000.0);
+    double elapsed = gState.lastTime == 0 ? 0 : now - gState.lastTime;
+    gState.lastTime = now;
+
+    return (float)elapsed;
+}
+
+void tigrMouse(Tigr* bmp, int* x, int* y, int* buttons) {
+    TigrInternal* win = tigrInternal(bmp);
+    if (x) {
+        *x = win->mouseX;
+    }
+    if (y) {
+        *y = win->mouseY;
+    }
+    if (buttons) {
+        *buttons = win->mouseButtons;
+    }
+}
+
+int tigrTouch(Tigr* bmp, TigrTouchPoint* points, int maxPoints) {
+    TigrInternal* win = tigrInternal(bmp);
+    for (int i = 0; i < maxPoints && i < win->numTouchPoints; i++) {
+        points[i] = win->touchPoints[i];
+    }
+    return maxPoints < win->numTouchPoints ? maxPoints : win->numTouchPoints;
+}
+
+void tigrMouseWheel(Tigr* bmp, float* x, float* y) {
+    if (x) {
+        *x = 0;
+    }
+    if (y) {
+        *y = 0;
+    }
+}
+
+void* tigrReadFile(const char* fileName, int* length) {
+    if (length != 0) {
+        *length = 0;
+    }
+
+    void* asset = android_loadAsset(fileName, length);
+    return asset;
+}
+
+#endif  // __ANDROID__
+#endif // #ifndef TIGR_HEADLESS

+ 43 - 0
tigr-master/src/tigr_android.h

@@ -0,0 +1,43 @@
+#ifndef TIGR_ANDROID_H
+#define TIGR_ANDROID_H
+#ifdef __ANDROID__
+
+#include <android/input.h>
+#include <android/native_window.h>
+#include <EGL/egl.h>
+
+typedef enum {
+    AE_INPUT,
+    AE_WINDOW_CREATED,
+    AE_WINDOW_DESTROYED,
+    AE_RESUME,
+    AE_CLOSE,
+} AndroidEventType;
+
+typedef struct {
+    AndroidEventType type;
+    AInputEvent* inputEvent;
+    ANativeWindow* window;
+    double time;
+} AndroidEvent;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/// Calls from TIGR to Android side, render thread
+extern int android_pollEvent(int (*eventHandler)(AndroidEvent, void*), void*);
+extern void android_swap(EGLDisplay display, EGLSurface surface);
+extern void* android_loadAsset(const char* filename, int* outLength);
+extern void android_showKeyboard(int show);
+
+/// Calls from Android to TIGR side, main thread
+void tigr_android_create();
+void tigr_android_destroy();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // __ANDROID__
+#endif  // TIGR_ANDROID_H

+ 405 - 0
tigr-master/src/tigr_bitmaps.c

@@ -0,0 +1,405 @@
+#include "tigr_internal.h"
+#include <stdlib.h>
+#include <string.h>
+
+// Expands 0-255 into 0-256
+#define EXPAND(X) ((X) + ((X) > 0))
+
+#define CLIP0(CX, X, X2, W) \
+    if (X < CX) {           \
+        int D = CX - X;     \
+        W -= D;             \
+        X2 += D;            \
+        X += D;             \
+    }
+#define CLIP1(X, DW, W) \
+    if (X + W > DW)     \
+        W = DW - X;
+#define CLIP()                  \
+    CLIP0(dst->cx, dx, sx, w);  \
+    CLIP0(dst->cy, dy, sy, h);  \
+    CLIP0(0, sx, dx, w);        \
+    CLIP0(0, sy, dy, h);        \
+    CLIP1(dx, dst->cx + cw, w); \
+    CLIP1(dy, dst->cy + ch, h); \
+    CLIP1(sx, src->w, w);       \
+    CLIP1(sy, src->h, h);       \
+    if (w <= 0 || h <= 0)       \
+    return
+
+Tigr* tigrBitmap2(int w, int h, int extra) {
+    Tigr* tigr = (Tigr*)calloc(1, sizeof(Tigr) + extra);
+    tigr->w = w;
+    tigr->h = h;
+    tigr->cw = -1;
+    tigr->ch = -1;
+    tigr->pix = (TPixel*)calloc(w * h, sizeof(TPixel));
+    tigr->blitMode = TIGR_BLEND_ALPHA;
+    return tigr;
+}
+
+Tigr* tigrBitmap(int w, int h) {
+    return tigrBitmap2(w, h, 0);
+}
+
+#ifdef TIGR_HEADLESS
+void tigrFree(Tigr* bmp) {
+    free(bmp->pix);
+    free(bmp);
+}
+#endif // TIGR_HEADLESS
+
+
+void tigrResize(Tigr* bmp, int w, int h) {
+    if (bmp->w == w && bmp->h == h) {
+        return;
+    }
+
+    int y, cw, ch;
+    TPixel* newpix = (TPixel*)calloc(w * h, sizeof(TPixel));
+    cw = (w < bmp->w) ? w : bmp->w;
+    ch = (h < bmp->h) ? h : bmp->h;
+
+    // Copy any old data across.
+    for (y = 0; y < ch; y++)
+        memcpy(newpix + y * w, bmp->pix + y * bmp->w, cw * sizeof(TPixel));
+
+    free(bmp->pix);
+    bmp->pix = newpix;
+    bmp->w = w;
+    bmp->h = h;
+}
+
+#ifndef TIGR_HEADLESS
+int tigrCalcScale(int bmpW, int bmpH, int areaW, int areaH) {
+    if (bmpW == 0 || bmpH == 0) {
+        tigrError(0, "Invalid bitmap size, (%dx%d)", bmpW, bmpH);
+    }
+    // We want it as big as possible in the window, but still
+    // maintaining the correct aspect ratio, and always
+    // having an integer pixel size.
+    int scale = 0;
+    for (;;) {
+        scale++;
+        if (bmpW * scale > areaW || bmpH * scale > areaH) {
+            scale--;
+            break;
+        }
+    }
+    return (scale > 1) ? scale : 1;
+}
+#endif // TIGR_HEADLESS
+
+int tigrEnforceScale(int scale, int flags) {
+    if ((flags & TIGR_4X) && scale < 4)
+        scale = 4;
+    if ((flags & TIGR_3X) && scale < 3)
+        scale = 3;
+    if ((flags & TIGR_2X) && scale < 2)
+        scale = 2;
+    return scale;
+}
+
+void tigrPosition(Tigr* bmp, int scale, int windowW, int windowH, int out[4]) {
+    // Center the image on screen at this scale.
+    out[0] = (windowW - bmp->w * scale) / 2;
+    out[1] = (windowH - bmp->h * scale) / 2;
+    out[2] = out[0] + bmp->w * scale;
+    out[3] = out[1] + bmp->h * scale;
+}
+
+void tigrClear(Tigr* bmp, TPixel color) {
+    int count = bmp->w * bmp->h;
+    int n;
+    for (n = 0; n < count; n++)
+        bmp->pix[n] = color;
+}
+
+void tigrFill(Tigr* bmp, int x, int y, int w, int h, TPixel color) {
+    TPixel* td;
+    int dt, i;
+
+    if (x < 0) {
+        w += x;
+        x = 0;
+    }
+    if (y < 0) {
+        h += y;
+        y = 0;
+    }
+    if (x + w > bmp->w) {
+        w = bmp->w - x;
+    }
+    if (y + h > bmp->h) {
+        h = bmp->h - y;
+    }
+    if (w <= 0 || h <= 0)
+        return;
+
+    td = &bmp->pix[y * bmp->w + x];
+    dt = bmp->w;
+    do {
+        for (i = 0; i < w; i++)
+            td[i] = color;
+        td += dt;
+    } while (--h);
+}
+
+void tigrLine(Tigr* bmp, int x0, int y0, int x1, int y1, TPixel color) {
+    int sx, sy, dx, dy, err, e2;
+    dx = abs(x1 - x0);
+    dy = abs(y1 - y0);
+    if (x0 < x1)
+        sx = 1;
+    else
+        sx = -1;
+    if (y0 < y1)
+        sy = 1;
+    else
+        sy = -1;
+    err = dx - dy;
+
+    do {
+        tigrPlot(bmp, x0, y0, color);
+        e2 = 2 * err;
+        if (e2 > -dy) {
+            err -= dy;
+            x0 += sx;
+        }
+        if (e2 < dx) {
+            err += dx;
+            y0 += sy;
+        }
+    } while (x0 != x1 || y0 != y1);
+}
+
+void tigrFillRect(Tigr* bmp, int x, int y, int w, int h, TPixel color) {
+    x += 1;
+    y += 1;
+    w -= 2;
+    h -= 2;
+
+    int cx = bmp->cx;
+    int cy = bmp->cy;
+    int cw = bmp->cw >= 0 ? bmp->cw : bmp->w;
+    int ch = bmp->ch >= 0 ? bmp->ch : bmp->h;
+
+    if (x < cx) {
+        w += (x - cx);
+        x = cx;
+    }
+    if (y < cy) {
+        h += (y - cy);
+        y = cy;
+    }
+    if (x + w > cx + cw) {
+        w -= (x + w) - (cx + cw);
+    }
+    if (y + h > cy + ch) {
+        h -= (y + h) - (cy + ch);
+    }
+    if (w <= 0 || h <= 0)
+        return;
+
+    TPixel* td = &bmp->pix[y * bmp->w + x];
+    int dt = bmp->w;
+    int xa = EXPAND(color.a);
+    int a = xa * xa;
+
+    do {
+        for (int i = 0; i < w; i++) {
+            td[i].r += (unsigned char)((color.r - td[i].r) * a >> 16);
+            td[i].g += (unsigned char)((color.g - td[i].g) * a >> 16);
+            td[i].b += (unsigned char)((color.b - td[i].b) * a >> 16);
+            td[i].a += (bmp->blitMode) * (unsigned char)((color.a - td[i].a) * a >> 16);
+        }
+        td += dt;
+    } while (--h);
+}
+
+void tigrRect(Tigr* bmp, int x, int y, int w, int h, TPixel color) {
+    int x1, y1;
+    if (w <= 0 || h <= 0) {
+        return;
+    }
+
+    if (w == 1) {
+        tigrLine(bmp, x, y, x, y + h, color);
+    } else if (h == 1) {
+        tigrLine(bmp, x, y, x + w, y, color);
+    } else {
+        x1 = x + w - 1;
+        y1 = y + h - 1;
+        tigrLine(bmp, x, y, x1, y, color);
+        tigrLine(bmp, x1, y, x1, y1, color);
+        tigrLine(bmp, x1, y1, x, y1, color);
+        tigrLine(bmp, x, y1, x, y, color);
+    }
+}
+
+void tigrFillCircle(Tigr* bmp, int x0, int y0, int r, TPixel color) {
+    if (r <= 0) {
+        return;
+    }
+
+    int E = 1 - r;
+    int dx = 0;
+    int dy = -2 * r;
+    int x = 0;
+    int y = r;
+
+    tigrLine(bmp, x0 - r + 1, y0, x0 + r, y0, color);
+
+    while (x < y - 1) {
+        x++;
+
+        if (E >= 0) {
+            y--;
+            dy += 2;
+            E += dy;
+            tigrLine(bmp, x0 - x + 1, y0 + y, x0 + x, y0 + y, color);
+            tigrLine(bmp, x0 - x + 1, y0 - y, x0 + x, y0 - y, color);
+        }
+
+        dx += 2;
+        E += dx + 1;
+
+        if (x != y) {
+            tigrLine(bmp, x0 - y + 1, y0 + x, x0 + y, y0 + x, color);
+            tigrLine(bmp, x0 - y + 1, y0 - x, x0 + y, y0 - x, color);
+        }
+    }
+}
+
+void tigrCircle(Tigr* bmp, int x0, int y0, int r, TPixel color) {
+    int E = 1 - r;
+    int dx = 0;
+    int dy = -2 * r;
+    int x = 0;
+    int y = r;
+
+    tigrPlot(bmp, x0, y0 + r, color);
+    tigrPlot(bmp, x0, y0 - r, color);
+    tigrPlot(bmp, x0 + r, y0, color);
+    tigrPlot(bmp, x0 - r, y0, color);
+
+    while (x < y - 1) {
+        x++;
+
+        if (E >= 0) {
+            y--;
+            dy += 2;
+            E += dy;
+        }
+
+        dx += 2;
+        E += dx + 1;
+
+        tigrPlot(bmp, x0 + x, y0 + y, color);
+        tigrPlot(bmp, x0 - x, y0 + y, color);
+        tigrPlot(bmp, x0 + x, y0 - y, color);
+        tigrPlot(bmp, x0 - x, y0 - y, color);
+
+        if (x != y) {
+            tigrPlot(bmp, x0 + y, y0 + x, color);
+            tigrPlot(bmp, x0 - y, y0 + x, color);
+            tigrPlot(bmp, x0 + y, y0 - x, color);
+            tigrPlot(bmp, x0 - y, y0 - x, color);
+        }
+    }
+}
+
+TPixel tigrGet(Tigr* bmp, int x, int y) {
+    TPixel empty = { 0, 0, 0, 0 };
+    if (x >= 0 && y >= 0 && x < bmp->w && y < bmp->h)
+        return bmp->pix[y * bmp->w + x];
+    return empty;
+}
+
+void tigrPlot(Tigr* bmp, int x, int y, TPixel pix) {
+    int xa, i, a;
+
+    int cx = bmp->cx;
+    int cy = bmp->cy;
+    int cw = bmp->cw >= 0 ? bmp->cw : bmp->w;
+    int ch = bmp->ch >= 0 ? bmp->ch : bmp->h;
+
+    if (x >= cx && y >= cy && x < cx + cw && y < cy + ch) {
+        xa = EXPAND(pix.a);
+        a = xa * xa;
+        i = y * bmp->w + x;
+
+        bmp->pix[i].r += (unsigned char)((pix.r - bmp->pix[i].r) * a >> 16);
+        bmp->pix[i].g += (unsigned char)((pix.g - bmp->pix[i].g) * a >> 16);
+        bmp->pix[i].b += (unsigned char)((pix.b - bmp->pix[i].b) * a >> 16);
+        bmp->pix[i].a += (bmp->blitMode) * (unsigned char)((pix.a - bmp->pix[i].a) * a >> 16);
+    }
+}
+
+void tigrClip(Tigr* bmp, int cx, int cy, int cw, int ch) {
+    bmp->cx = cx;
+    bmp->cy = cy;
+    bmp->cw = cw;
+    bmp->ch = ch;
+}
+
+void tigrBlit(Tigr* dst, Tigr* src, int dx, int dy, int sx, int sy, int w, int h) {
+    int cw = dst->cw >= 0 ? dst->cw : dst->w;
+    int ch = dst->ch >= 0 ? dst->ch : dst->h;
+
+    CLIP();
+
+    TPixel* ts = &src->pix[sy * src->w + sx];
+    TPixel* td = &dst->pix[dy * dst->w + dx];
+    int st = src->w;
+    int dt = dst->w;
+    do {
+        memcpy(td, ts, w * sizeof(TPixel));
+        ts += st;
+        td += dt;
+    } while (--h);
+}
+
+void tigrBlitTint(Tigr* dst, Tigr* src, int dx, int dy, int sx, int sy, int w, int h, TPixel tint) {
+    int cw = dst->cw >= 0 ? dst->cw : dst->w;
+    int ch = dst->ch >= 0 ? dst->ch : dst->h;
+
+    CLIP();
+
+    int xr = EXPAND(tint.r);
+    int xg = EXPAND(tint.g);
+    int xb = EXPAND(tint.b);
+    int xa = EXPAND(tint.a);
+
+    TPixel* ts = &src->pix[sy * src->w + sx];
+    TPixel* td = &dst->pix[dy * dst->w + dx];
+    int st = src->w;
+    int dt = dst->w;
+    do {
+        for (int x = 0; x < w; x++) {
+            unsigned r = (xr * ts[x].r) >> 8;
+            unsigned g = (xg * ts[x].g) >> 8;
+            unsigned b = (xb * ts[x].b) >> 8;
+            unsigned a = xa * EXPAND(ts[x].a);
+            td[x].r += (unsigned char)((r - td[x].r) * a >> 16);
+            td[x].g += (unsigned char)((g - td[x].g) * a >> 16);
+            td[x].b += (unsigned char)((b - td[x].b) * a >> 16);
+            td[x].a += (dst->blitMode) * (unsigned char)((ts[x].a - td[x].a) * a >> 16);
+        }
+        ts += st;
+        td += dt;
+    } while (--h);
+}
+
+void tigrBlitAlpha(Tigr* dst, Tigr* src, int dx, int dy, int sx, int sy, int w, int h, float alpha) {
+    alpha = (alpha < 0) ? 0 : (alpha > 1 ? 1 : alpha);
+    tigrBlitTint(dst, src, dx, dy, sx, sy, w, h, tigrRGBA(0xff, 0xff, 0xff, (unsigned char)(alpha * 255)));
+}
+
+void tigrBlitMode(Tigr* dst, int mode) {
+    dst->blitMode = mode;
+}
+
+#undef CLIP0
+#undef CLIP1
+#undef CLIP

+ 247 - 0
tigr-master/src/tigr_font.h

@@ -0,0 +1,247 @@
+// Auto-generated by incbin.pl from font.png
+
+const unsigned char tigr_font[] = {
+	0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52,
+	0x00,0x00,0x00,0xfd,0x00,0x00,0x00,0x5c,0x08,0x03,0x00,0x00,0x00,0x92,0xab,0x43,
+	0x85,0x00,0x00,0x03,0x00,0x50,0x4c,0x54,0x45,0x5f,0x53,0x87,0x00,0x00,0x00,0x54,
+	0x54,0x54,0xff,0xff,0xfa,0x31,0x2a,0x42,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x4a,0x44,0xb7,0x5a,0x00,0x00,0x00,
+	0x01,0x74,0x52,0x4e,0x53,0x00,0x40,0xe6,0xd8,0x66,0x00,0x00,0x00,0x01,0x62,0x4b,
+	0x47,0x44,0x00,0x88,0x05,0x1d,0x48,0x00,0x00,0x00,0x09,0x70,0x48,0x59,0x73,0x00,
+	0x00,0x0b,0x13,0x00,0x00,0x0b,0x13,0x01,0x00,0x9a,0x9c,0x18,0x00,0x00,0x00,0x07,
+	0x74,0x49,0x4d,0x45,0x07,0xe5,0x04,0x1c,0x16,0x0f,0x2b,0x56,0x9d,0x54,0x3b,0x00,
+	0x00,0x0b,0x7c,0x49,0x44,0x41,0x54,0x78,0xda,0xed,0x5c,0x89,0x9a,0xec,0xac,0x0a,
+	0x04,0xf5,0xfd,0x9f,0xf9,0x4e,0x94,0xa5,0x40,0x93,0x98,0xee,0x3e,0x73,0xe7,0xfb,
+	0x67,0xce,0xd2,0x66,0x31,0x6a,0x29,0x51,0x4a,0x20,0xd4,0x7e,0xf3,0x1f,0x6a,0x34,
+	0xfe,0xb4,0x5a,0xf9,0xf8,0x7f,0x24,0x54,0xf9,0xf8,0xf7,0x75,0xeb,0xeb,0x97,0xe0,
+	0xd4,0x73,0x92,0x64,0xd7,0xab,0x90,0xa4,0xc3,0x91,0x59,0xb2,0x56,0x29,0x50,0xce,
+	0xed,0xfa,0x71,0x74,0x94,0xd7,0x2f,0xd8,0xf5,0x7c,0x0e,0xf9,0xb1,0x74,0x69,0x15,
+	0x13,0x14,0xfd,0x09,0xf4,0xd2,0x08,0x3e,0xfe,0xf6,0x53,0x86,0x9c,0xa5,0x8d,0xcc,
+	0xd2,0x45,0x9a,0x2c,0xd0,0x7f,0x65,0x63,0x45,0xc1,0xa3,0x00,0xae,0x78,0x7d,0xb4,
+	0xb9,0x9f,0xf3,0x57,0xb9,0x7a,0xfd,0x38,0xc3,0x7c,0x90,0x36,0xad,0xba,0x68,0x75,
+	0x1c,0x8a,0x7e,0x05,0x3d,0x1f,0xcf,0xfa,0x18,0x0c,0xd8,0xad,0xff,0x96,0x9e,0xa3,
+	0xd7,0xc0,0x36,0xec,0x90,0xe9,0x0c,0x7d,0x1d,0x68,0x6c,0x6c,0x7b,0x01,0x5f,0xa5,
+	0x0d,0x74,0x23,0x25,0xe9,0xf5,0xd1,0xbb,0xbd,0x22,0x62,0xc1,0xa5,0xd7,0x53,0x2a,
+	0x02,0x38,0x9a,0x35,0x04,0xa4,0x8f,0x90,0x16,0xf9,0x0a,0x7a,0xfa,0x7a,0xb6,0x8f,
+	0xe9,0xd1,0x09,0x63,0x6c,0x8f,0x46,0x70,0xd1,0x7e,0x1d,0x08,0x07,0x20,0x41,0xdf,
+	0x81,0x15,0x6b,0x03,0x11,0x1e,0xf6,0x3e,0xb1,0xde,0xec,0xc3,0x22,0x12,0x31,0x46,
+	0x4d,0x9f,0xfd,0xaa,0x41,0x7b,0x5d,0xf3,0xb3,0xe7,0xe3,0x5a,0x3d,0x1d,0xbd,0xd6,
+	0xff,0x1f,0xe0,0xa9,0x4b,0x48,0x2f,0x6c,0x0c,0xc2,0x78,0xe4,0x25,0xf4,0x32,0x6a,
+	0x2a,0xf9,0xac,0xaf,0xeb,0xa8,0xa6,0x8e,0x54,0x87,0x27,0x8c,0xfd,0xd1,0x5f,0x63,
+	0xb8,0xbc,0x39,0xe4,0xa3,0x63,0x12,0x2e,0x6f,0x49,0xbf,0xfe,0xd5,0x60,0xc6,0xf7,
+	0x3d,0x4b,0xba,0xe6,0xb3,0x73,0xd6,0xeb,0xa3,0x11,0x2a,0xec,0x15,0x5f,0x83,0xf1,
+	0xc8,0x53,0xf4,0xec,0xaf,0x50,0x92,0xfc,0xa3,0xdc,0x31,0x24,0x7a,0x8e,0xe8,0x0f,
+	0xc1,0x18,0xd5,0xae,0x24,0x5f,0x85,0xa6,0x04,0xf4,0xde,0x8d,0x84,0xa8,0xb3,0xa4,
+	0x6b,0x3e,0x3b,0xf7,0xfb,0x0d,0x85,0x7d,0x74,0xb6,0x74,0xe8,0xa8,0xe2,0x8d,0xb1,
+	0x07,0xc9,0xaf,0xa3,0xdc,0xca,0x3a,0x24,0x32,0xa8,0x8e,0x9e,0x48,0x90,0x68,0x43,
+	0x1b,0x4e,0xa3,0xa9,0xf5,0x36,0xeb,0x15,0x5a,0xa3,0xca,0x69,0xc8,0x37,0xbd,0xf7,
+	0x2a,0xec,0x63,0xce,0x97,0x66,0xf4,0xf4,0x33,0x92,0xdf,0xfb,0x13,0x3b,0x43,0xc6,
+	0x9e,0x6d,0xe2,0x8f,0x2b,0xde,0x84,0x5e,0x32,0xfb,0xdc,0x80,0x92,0x2e,0xd7,0x4d,
+	0x06,0xf0,0xfd,0x96,0xc9,0x05,0x9f,0xb7,0x74,0xcc,0x4d,0xc7,0x73,0x30,0xe7,0x17,
+	0xa2,0xd7,0xc7,0x1e,0x24,0x7f,0x4c,0x7e,0x36,0x9e,0x76,0x7a,0x48,0x98,0xbe,0xd0,
+	0x32,0xf3,0xe4,0xf5,0x1e,0x06,0xa3,0xe7,0x28,0x92,0xb9,0x67,0x65,0x11,0x52,0x4d,
+	0xf5,0xfa,0x91,0x3d,0x9c,0x5b,0xfe,0x42,0xf8,0x3c,0xe4,0x6f,0xd6,0xf1,0xa1,0x01,
+	0x2f,0xa3,0xcf,0x05,0x9d,0x9d,0x7a,0xce,0x36,0x67,0x6a,0x51,0x1a,0xda,0x69,0x01,
+	0xef,0xa6,0xcd,0x3a,0x3e,0xa3,0x7f,0x4d,0xf2,0x3f,0x83,0x3e,0x4a,0xc3,0x3f,0x44,
+	0x9f,0x4b,0x4f,0x55,0xfd,0x7f,0xd0,0xaf,0x0e,0x7f,0x32,0xfa,0xdf,0xce,0x72,0x54,
+	0x01,0xa5,0xac,0x6a,0xa5,0xf3,0x3c,0x65,0xfb,0x7d,0x5d,0x88,0xe0,0x7a,0x5a,0xc0,
+	0x74,0xe1,0xca,0xeb,0xf9,0x65,0x85,0x34,0x55,0x3c,0xad,0x7c,0x34,0x1f,0x60,0x53,
+	0xaa,0x2f,0x3c,0x35,0x90,0x92,0x21,0x20,0x63,0x3d,0x63,0xd5,0x66,0xce,0x48,0x05,
+	0x67,0x12,0x82,0x64,0x44,0xab,0x64,0xd3,0x8c,0xd9,0x16,0xac,0x88,0xde,0xf5,0xfd,
+	0xdb,0x8a,0x30,0xd5,0x82,0xf7,0xd1,0x83,0xd6,0x58,0x7c,0xd9,0xe5,0x26,0x7a,0x9b,
+	0xa3,0xef,0x7a,0x98,0x6a,0x33,0x53,0xe1,0xbc,0x44,0x11,0xc9,0x48,0x56,0x66,0xc6,
+	0xea,0x4d,0xc2,0x18,0x0b,0xa2,0x8f,0xc2,0xb3,0xaa,0x30,0x8d,0xa0,0xe8,0xcc,0xa6,
+	0x25,0xad,0xd1,0x33,0x56,0x55,0x55,0xdd,0x57,0x32,0xc6,0xa6,0x60,0x1e,0x60,0x61,
+	0x4d,0x6e,0x83,0xbf,0xaa,0x2e,0x07,0x8d,0x0b,0xad,0x76,0x71,0x52,0xc5,0x9a,0x43,
+	0x2f,0x98,0xa6,0x9d,0xd0,0x9b,0x9a,0xae,0xe5,0xd8,0x98,0xdf,0xa0,0x77,0xed,0x86,
+	0x61,0x11,0x3f,0x45,0x4f,0x2c,0x55,0xb9,0xfe,0xe4,0x10,0x1a,0x39,0xfa,0x63,0x35,
+	0xf4,0x35,0x59,0xd0,0x93,0x6b,0xb2,0x31,0x4d,0xe8,0x59,0x15,0x57,0x4d,0xeb,0x44,
+	0x4a,0xa6,0xb1,0x0f,0xe8,0x95,0xcb,0xc5,0x57,0x06,0xbb,0x3b,0x14,0xc8,0x71,0x57,
+	0x64,0x81,0xde,0xe9,0x93,0xd0,0x4f,0xe1,0x4f,0xd0,0xf1,0x42,0x07,0x08,0x95,0x59,
+	0x40,0xdf,0xe5,0xe6,0xec,0xb5,0x4a,0x95,0x26,0xc9,0x0f,0x20,0x16,0xe8,0xf9,0x72,
+	0xd6,0x43,0xb4,0xda,0xd6,0x58,0x20,0xd7,0xc4,0x0f,0x16,0x73,0x9b,0x6f,0x10,0x60,
+	0x3f,0x39,0xc3,0x10,0x9a,0xbd,0x46,0xcf,0x75,0x28,0xb0,0xb4,0xf1,0x1a,0xce,0xef,
+	0xfd,0x0a,0x3d,0x8e,0xf9,0x2e,0x7a,0x7b,0x35,0x72,0xaf,0xac,0xd0,0x03,0x6f,0xb0,
+	0x07,0x0a,0x9f,0xf1,0x28,0x25,0x43,0x4b,0xf4,0xc4,0x75,0xb5,0x72,0x05,0xb2,0x91,
+	0x49,0x4a,0x24,0x1f,0x79,0x99,0x61,0x10,0xbb,0x3c,0xc9,0xc5,0x99,0xf9,0x84,0xcd,
+	0xe4,0xfb,0x8a,0xc9,0xce,0xd9,0x2a,0x28,0x1c,0x27,0x00,0x1f,0x7b,0x5f,0xf1,0x74,
+	0x7b,0x0d,0x09,0x58,0x3f,0x6b,0x43,0x33,0x76,0x52,0x61,0x24,0xa4,0x24,0x72,0x12,
+	0x48,0x4a,0x24,0x1f,0xe1,0x39,0x21,0x2b,0x85,0xe6,0xeb,0xfd,0xaa,0x1e,0x20,0xab,
+	0xc1,0x02,0x75,0xc7,0x2b,0xb2,0x20,0x16,0xba,0x15,0xcf,0x43,0x81,0xb2,0x17,0xc6,
+	0xbe,0xdf,0x33,0xaa,0x2e,0x42,0x05,0x19,0x68,0x9c,0xdc,0x75,0x4d,0xf7,0xdf,0x28,
+	0xa4,0xab,0xf4,0x3a,0x83,0xb7,0xf1,0x79,0x81,0xeb,0x1c,0x8d,0xce,0xd4,0xf2,0x9f,
+	0x87,0xfe,0x9d,0x02,0xff,0xd0,0x3f,0x44,0xff,0xbb,0x59,0x8e,0x4f,0x85,0xbe,0x96,
+	0x41,0x17,0xe1,0x6d,0x51,0x1f,0x73,0x6e,0x53,0x2d,0xe3,0xe2,0xf6,0xea,0x58,0x37,
+	0x57,0x1b,0x9e,0x88,0x4e,0x0b,0x3b,0x9e,0x78,0xf7,0x53,0xe8,0x6b,0xc5,0x49,0xc9,
+	0x16,0x2a,0x99,0x57,0x93,0xc2,0xf4,0xed,0xe8,0x75,0xb5,0x6c,0x1c,0x87,0xec,0x12,
+	0x7d,0xdf,0xb5,0x5d,0x51,0x4b,0xa3,0xaa,0xf1,0x36,0x30,0x58,0xd3,0x13,0x60,0xec,
+	0x51,0xbf,0x36,0xb5,0xe7,0x82,0xc1,0x42,0x41,0x14,0xaa,0xb9,0x64,0x74,0xb3,0xa2,
+	0xdc,0xc8,0x1b,0xe3,0x64,0xf6,0x0e,0x7d,0x57,0x1b,0x16,0x06,0xb3,0x48,0x61,0x59,
+	0xfb,0x74,0x71,0x39,0xa0,0x07,0xfd,0x1a,0x38,0x1d,0xef,0x33,0x5a,0xac,0x66,0x0f,
+	0x3d,0xb0,0x59,0x35,0x49,0xd5,0x3a,0xec,0x6f,0x9d,0xd6,0xdd,0xec,0x6c,0x99,0x89,
+	0x28,0xaa,0x9e,0xab,0x6d,0x8b,0x13,0xb2,0x01,0xe8,0x5d,0xbf,0x46,0x4e,0x77,0xab,
+	0x42,0x33,0x58,0xce,0xee,0xf9,0x6c,0xa2,0xb3,0x66,0x53,0x85,0xb1,0x2f,0x9d,0xcc,
+	0x1e,0xdb,0xca,0x77,0xe8,0x67,0xd2,0xc5,0x99,0xdc,0x5d,0x37,0x2b,0x8c,0x3d,0xa0,
+	0x37,0x4e,0xb7,0x45,0x20,0xd4,0x72,0x76,0xc7,0xe8,0x92,0x66,0x4e,0xc6,0xeb,0xa2,
+	0xe4,0x93,0x6c,0xcb,0x6f,0xa0,0xa7,0x6b,0xf4,0xb4,0x3b,0xf6,0x4e,0x70,0xae,0x59,
+	0xcd,0x92,0xe5,0x98,0xa1,0xfb,0x84,0xd3,0x4c,0x2f,0x12,0x98,0x7c,0x56,0xe8,0xef,
+	0xdf,0xfb,0x28,0xf9,0xc8,0x6f,0xdc,0x66,0xaa,0xec,0x02,0xc8,0xc6,0x29,0x7a,0x9b,
+	0x21,0xae,0x58,0x4d,0xb4,0xc9,0x22,0xcb,0x11,0x12,0x83,0xb5,0xb2,0xed,0x5c,0xf0,
+	0x4c,0x82,0x60,0xd6,0x43,0xdb,0x7e,0x79,0x80,0x9e,0xd0,0xc8,0x62,0xfc,0xc6,0x2e,
+	0x83,0x2d,0xa5,0x2c,0xc8,0x88,0xac,0x78,0x89,0x9f,0x5c,0xb0,0x1a,0xe5,0xf3,0x58,
+	0x90,0x36,0x43,0x48,0xcc,0x9a,0xe3,0x94,0xc0,0xaa,0xfc,0x71,0x36,0x86,0xd3,0x92,
+	0x8d,0x69,0x0b,0xfd,0xf6,0x46,0x7e,0x36,0x3e,0x92,0x5b,0xb4,0xd3,0xa5,0x8b,0x05,
+	0x1a,0xd5,0x8a,0xfc,0xd4,0x5d,0xed,0xa9,0x98,0x4c,0xda,0x5b,0xae,0xfd,0xe3,0xe8,
+	0xd1,0xf8,0xe8,0xa6,0xf4,0x7c,0xe9,0xdf,0xa3,0x8f,0xed,0xf8,0x2e,0xf4,0x68,0x7c,
+	0xb4,0x0a,0xa7,0x4b,0x17,0xe8,0x4d,0xd2,0x97,0x8c,0x76,0x1b,0x7d,0x6c,0xc7,0x6b,
+	0xe8,0xff,0x3c,0xd6,0xa4,0x8f,0x80,0x2c,0xa4,0xfe,0x86,0x6d,0x65,0x77,0xc0,0x60,
+	0xb3,0xf4,0x8f,0x53,0xd5,0xed,0x45,0x83,0x1d,0xc5,0xf8,0x8d,0x16,0x1d,0x5e,0x2a,
+	0xa3,0x3a,0xbc,0x30,0x23,0xca,0x56,0xb4,0x39,0xbe,0x24,0x6b,0x04,0xb4,0x2d,0x0c,
+	0xb5,0xfa,0x97,0x0d,0x17,0x84,0x2d,0x4b,0x16,0x03,0xfa,0xd9,0x27,0x61,0x89,0x5e,
+	0xd4,0xc8,0xe1,0xc9,0xe4,0x9e,0x0e,0x3e,0x05,0x0e,0x99,0xf6,0x1b,0xcf,0xd1,0xbb,
+	0xcf,0x58,0x51,0xa7,0x19,0xa8,0xe5,0x04,0xbd,0xe4,0x10,0x9f,0x83,0x5b,0xf4,0x5d,
+	0xc9,0xe7,0x80,0x9e,0x16,0xe8,0x27,0xc7,0x8b,0x32,0x3c,0x28,0x64,0xf7,0xd4,0xfa,
+	0xd0,0x37,0x75,0x15,0xb0,0x49,0x01,0xba,0x7c,0x70,0x05,0xcb,0x86,0xba,0x44,0x24,
+	0xf4,0xa2,0xab,0x8e,0x71,0x3c,0x7a,0x20,0xd4,0x32,0x54,0xbe,0x68,0x64,0x9c,0x28,
+	0xd2,0x3d,0xbf,0x17,0x52,0xc1,0xcd,0x50,0xae,0x24,0x5f,0x8d,0x69,0x66,0xad,0x13,
+	0x2f,0x0f,0xdc,0xdc,0x1f,0xcc,0x4a,0x76,0xa6,0x87,0xa3,0xa3,0x71,0x90,0xfe,0x0f,
+	0x5c,0x3e,0xc6,0xea,0xdd,0x74,0x11,0x07,0xb5,0x05,0xde,0x1b,0x82,0x71,0x24,0x25,
+	0x58,0xc3,0x2e,0x29,0x9a,0x95,0x29,0x7e,0xc3,0x1a,0x09,0x06,0xbc,0x2d,0xf4,0x2c,
+	0x96,0x19,0x05,0xc4,0x9b,0x92,0x4f,0xe6,0x8d,0x68,0x8f,0x89,0xe7,0x94,0x4a,0x7e,
+	0x91,0x42,0xab,0x90,0x0d,0xeb,0xb2,0x93,0x55,0xb0,0x05,0xc7,0x96,0x65,0x23,0x74,
+	0x04,0x5c,0xcd,0x77,0xff,0xb2,0xbe,0x71,0xcd,0xc9,0xc5,0x6f,0x6b,0xec,0x89,0x6e,
+	0x25,0x3f,0x6f,0x95,0x1b,0x7a,0x99,0x66,0x44,0xc1,0x57,0xa7,0x4e,0x93,0x7c,0xf3,
+	0xcd,0x6b,0x0b,0xa7,0x87,0xb5,0xdb,0xd0,0x7a,0xe1,0x45,0x1e,0xe4,0x46,0x57,0x33,
+	0x28,0x2b,0x7a,0xa9,0x8e,0x77,0xfc,0xf6,0x64,0xec,0x75,0x7a,0xb6,0x4d,0x82,0x31,
+	0x19,0xe9,0xdb,0x89,0x86,0x82,0x28,0xf9,0xd5,0x69,0xb4,0x28,0xf8,0x3c,0xbd,0xf7,
+	0xe2,0x4a,0x76,0x8d,0x7e,0x39,0xe7,0xcf,0x93,0x8f,0xf2,0x20,0x53,0xf7,0x8d,0x6c,
+	0x08,0x7a,0xab,0x6e,0xc7,0x7b,0x89,0xd4,0xfe,0xb9,0x8d,0x3e,0x4a,0xbe,0xd8,0x7f,
+	0x0b,0x30,0x14,0xa5,0x1f,0x32,0xd7,0x85,0x59,0xef,0x5d,0xf4,0xea,0x16,0xeb,0xf6,
+	0xda,0xba,0xf2,0x86,0xe3,0x7d,0xf4,0xa6,0xa6,0x35,0x73,0x9a,0x35,0xbd,0xbd,0x8c,
+	0x49,0x09,0xcd,0x24,0x9e,0x9a,0x33,0x9b,0xb9,0xb3,0x0d,0x27,0x37,0x61,0x47,0xb2,
+	0xe2,0xf9,0xd1,0x07,0xd0,0x4b,0x2d,0x60,0xd9,0x09,0xc4,0xcc,0x88,0xda,0xa6,0xef,
+	0x56,0xd0,0x76,0xdc,0xac,0xd4,0x0c,0xf2,0xc6,0x56,0x79,0x6e,0x69,0xb8,0xda,0xf0,
+	0xe8,0x7d,0xf4,0xab,0x5a,0xae,0x94,0xf4,0xe7,0xba,0xde,0x55,0xc5,0x7f,0xe8,0xff,
+	0x4b,0xe8,0xff,0x58,0x0e,0x86,0x52,0x10,0xaa,0xfe,0x94,0xdd,0x14,0xec,0x9e,0xfa,
+	0xae,0xc3,0x2e,0x66,0x5f,0x72,0x2c,0x52,0xa6,0xa1,0xfd,0x87,0x28,0x6e,0x0c,0xe3,
+	0x46,0x49,0xf0,0xf7,0x26,0xa0,0x49,0xdc,0x12,0x7d,0x91,0x1f,0x64,0x56,0x44,0xee,
+	0x42,0x94,0x8e,0x36,0x86,0x7f,0x8d,0x5e,0x75,0x70,0xb6,0xb8,0x1a,0x82,0x68,0x90,
+	0xb1,0xe9,0x5e,0x86,0x66,0xc7,0xe2,0x23,0xaf,0xf1,0x01,0xc9,0x7b,0x18,0xb3,0xa8,
+	0x5a,0x02,0x81,0x2c,0xac,0x81,0x0d,0x11,0x3d,0x46,0x02,0x50,0xe8,0x28,0xb6,0xf8,
+	0x99,0x80,0x94,0x59,0xba,0x04,0x8e,0x1e,0xa1,0xaf,0x40,0x22,0x84,0xa1,0xcd,0xce,
+	0x19,0x83,0xd5,0xa9,0xdb,0xd3,0xf0,0x76,0x63,0x73,0x8c,0x18,0xe0,0xc3,0xd4,0xd1,
+	0x86,0xd2,0x8b,0xc8,0x44,0x3c,0x94,0x26,0xbb,0x76,0x5c,0x2e,0xd1,0xb3,0x4b,0x9c,
+	0xc0,0x6f,0x03,0x68,0x91,0x72,0xa6,0xa3,0xfb,0x25,0xff,0x24,0x26,0xcb,0x76,0xb4,
+	0x39,0x99,0x0f,0x1a,0x6b,0x70,0x8e,0x52,0xd0,0xae,0x28,0x8a,0xba,0x57,0xd4,0x96,
+	0x61,0x7b,0xfb,0xcd,0x94,0x41,0x54,0xd5,0x2d,0x8c,0x47,0xb5,0xd2,0xb1,0xb3,0x99,
+	0xb6,0xee,0xb9,0xc5,0xb7,0xc1,0xdf,0x37,0x89,0x9e,0xf2,0xdd,0x85,0xa3,0x1c,0x67,
+	0xda,0xaa,0x67,0x3e,0x40,0x6f,0x5e,0x98,0xa5,0xd5,0x13,0x17,0x54,0x16,0x65,0xce,
+	0xa9,0x2c,0x0f,0xd9,0x6b,0xb5,0xa2,0x44,0xca,0xbb,0x21,0x82,0xde,0xe3,0x52,0x18,
+	0xf7,0xa3,0x0a,0x69,0x18,0x4f,0x0d,0x1b,0xd0,0x61,0xaf,0x3a,0xa3,0xd7,0x2a,0x8a,
+	0xbf,0x61,0x43,0xd9,0x53,0x57,0x43,0xeb,0x30,0xeb,0xee,0x5b,0x85,0x67,0x1a,0x7b,
+	0x67,0x52,0x34,0xb9,0xa0,0x5a,0x34,0x88,0xb1,0x61,0xf1,0x76,0x13,0xa7,0x18,0x95,
+	0x48,0x88,0x12,0x9c,0xc7,0xde,0x26,0x43,0xe9,0x27,0xd8,0x80,0xf6,0x5d,0x5a,0xd6,
+	0x50,0xa7,0x02,0xa3,0x33,0xc2,0xc3,0x5c,0xb8,0x47,0x2f,0x6b,0xfd,0x76,0x54,0x3c,
+	0x9c,0xe8,0x2d,0xc9,0x9f,0x23,0x4a,0xfa,0x4c,0xc8,0xd5,0xd9,0x30,0xb3,0x9f,0xb9,
+	0x44,0xba,0x43,0x64,0x1f,0x2d,0xce,0xdb,0xbd,0x1e,0xf6,0x11,0xbd,0xdd,0xd1,0xd1,
+	0x34,0xbf,0xf7,0x90,0x0b,0xde,0xec,0xe1,0x66,0x7b,0x76,0xf4,0x0c,0x3d,0x27,0xb4,
+	0xac,0x32,0xec,0x63,0xaf,0xee,0x68,0x46,0x38,0xc3,0x56,0xd3,0x88,0xe7,0x0b,0x30,
+	0x31,0x0b,0x4c,0x86,0x81,0x26,0x3b,0x7a,0x70,0x51,0x3b,0x45,0x2f,0x9b,0x1d,0x22,
+	0x40,0xbe,0x84,0xe4,0x23,0xa1,0x7b,0x0f,0xa2,0x11,0x6b,0x70,0x4d,0x2b,0x3a,0x8a,
+	0xec,0x01,0x57,0x8c,0x24,0x27,0x9d,0xcd,0x81,0x02,0x31,0x8b,0x5f,0x86,0xfd,0x8b,
+	0x38,0xf6,0x6e,0xbe,0x09,0xdb,0x5d,0x0b,0x35,0x51,0x05,0x88,0x2c,0x30,0x2c,0x1e,
+	0xa9,0xc7,0xe2,0x83,0x98,0xac,0x2b,0xaf,0x89,0xc6,0x97,0xf6,0x8a,0x33,0xf4,0xab,
+	0xc2,0xda,0x12,0x52,0xf2,0x1d,0x41,0x1d,0xe4,0x04,0x3d,0x11,0xc6,0x65,0xc5,0xa3,
+	0x8d,0xcd,0xad,0x8c,0x7e,0x1e,0xa6,0xa5,0x2e,0xfe,0x8f,0xd0,0xa3,0x99,0xc3,0x03,
+	0x1a,0xef,0xd0,0x9f,0x1c,0x6d,0x6c,0x6e,0x5d,0x6d,0x2d,0x7d,0x3f,0xfa,0x65,0x85,
+	0xac,0x2f,0xf9,0x43,0xf4,0x6a,0xea,0xf9,0x63,0x39,0x57,0xd6,0x0c,0xb6,0x50,0xf9,
+	0x90,0xd6,0xf4,0xed,0x81,0xe0,0x4a,0x45,0x18,0xeb,0x1f,0x1e,0x05,0x2d,0x16,0x62,
+	0x03,0xfc,0x8e,0xea,0xba,0x35,0x7f,0x93,0x60,0xbb,0x21,0xd3,0x70,0x9f,0x3c,0xe8,
+	0x0f,0x60,0x54,0x05,0xb6,0x7b,0xac,0xd2,0xbe,0x39,0x6d,0x31,0xc7,0x6e,0x36,0xd7,
+	0xc0,0xcc,0x19,0xbd,0xbb,0x90,0xb3,0x07,0x6b,0x1b,0x6c,0xdd,0xf2,0x92,0x3e,0xd0,
+	0x3b,0x4d,0x96,0x45,0x78,0x58,0xb7,0xcc,0x36,0x1b,0x32,0xa1,0x3f,0x79,0xd0,0x5b,
+	0x8e,0x31,0xe4,0xe4,0x5e,0x5a,0x8b,0xcf,0x40,0xdc,0xa5,0x11,0x3d,0x31,0x68,0xed,
+	0x67,0xc9,0xea,0x97,0x16,0x7e,0x31,0x5b,0x0d,0x10,0xe5,0x5a,0x62,0xc9,0xa2,0x27,
+	0x4b,0x49,0x29,0xe1,0xb9,0x4a,0x9b,0x14,0xd1,0xd1,0x3f,0x72,0x27,0xb3,0x32,0xa2,
+	0xcb,0x09,0x78,0xb8,0x4d,0x09,0x9b,0xe1,0x2a,0xfe,0x56,0x8f,0xcc,0x7a,0x18,0xa1,
+	0x55,0xb9,0x82,0xec,0x94,0x2d,0xb7,0x20,0xed,0x6f,0xb5,0x3a,0x88,0xb5,0x72,0xfb,
+	0xd9,0x14,0x30,0xa1,0x00,0xdd,0xfa,0x75,0x92,0xd0,0xc9,0x2f,0x87,0x2f,0x2e,0x3c,
+	0x68,0x80,0x45,0x86,0x57,0xae,0x53,0x0c,0xc8,0xd2,0x9f,0x0f,0xdd,0x8e,0x14,0x3d,
+	0xeb,0xd8,0xe7,0x30,0xb1,0x9d,0x34,0x47,0x65,0x99,0x3f,0xce,0x94,0xd0,0x55,0x1f,
+	0xec,0x45,0x68,0xe5,0x21,0x64,0x73,0xec,0xdc,0x42,0x8f,0xa1,0x4a,0xb2,0x5f,0xff,
+	0xb2,0xe4,0xeb,0xd8,0x97,0x29,0x5e,0x69,0x9d,0xec,0xa0,0xe7,0x47,0xc2,0x27,0x31,
+	0x75,0x22,0x3b,0x1b,0xe8,0xe1,0x93,0x0f,0xd2,0x6e,0x44,0xff,0x54,0xf2,0x29,0x7f,
+	0x3e,0xe0,0xc5,0x59,0x6f,0x1d,0x85,0x73,0x9b,0x8a,0x39,0x97,0x6b,0x0a,0xc2,0x3b,
+	0x4b,0xd1,0x02,0xc9,0xb6,0xe1,0x34,0xde,0x7b,0xf0,0x3b,0x7b,0x90,0x9a,0x2b,0x9b,
+	0xd3,0xa2,0x72,0x9e,0xac,0x7e,0xa7,0x70,0xa0,0xe9,0x1b,0x03,0x67,0xa9,0x19,0xf7,
+	0x39,0x85,0xeb,0x9c,0x3e,0xe0,0xc1,0x3a,0xc5,0x9f,0x2d,0xd4,0x5e,0x0f,0x88,0x20,
+	0xca,0x3a,0xc0,0x45,0xd2,0x76,0x55,0xe0,0x07,0x7e,0xec,0xf4,0x72,0xcb,0xfd,0xca,
+	0x9b,0xe8,0x91,0xbd,0xfe,0x3e,0xf4,0x77,0xb0,0x7f,0x3a,0xfa,0x3f,0x96,0x73,0x49,
+	0x2e,0x4c,0xdd,0x5e,0xb0,0x9c,0x4d,0x9e,0x21,0x9a,0x7e,0xa3,0xe0,0x6e,0x66,0x04,
+	0xc9,0xa9,0xd1,0x37,0x70,0x1c,0x74,0x66,0xdb,0x20,0x17,0xca,0x56,0x2e,0xd1,0x5f,
+	0xf3,0x0c,0xd9,0xed,0x77,0x2f,0x37,0x09,0x90,0x57,0xdd,0xa5,0x3c,0x22,0x5b,0xfc,
+	0x16,0xc7,0x41,0x67,0xb6,0x17,0x58,0x8e,0x2b,0x7b,0xfb,0xcf,0x8c,0x65,0x5a,0xbc,
+	0xdc,0x64,0xc7,0x4e,0xdc,0xa3,0x7a,0xdf,0x3e,0x21,0x59,0x66,0xa8,0x79,0x8d,0xe3,
+	0xe0,0x8e,0x17,0xc4,0xe4,0x3c,0x4b,0x9f,0xea,0x87,0x1e,0x2a,0x86,0x01,0x49,0xf8,
+	0x51,0x8f,0xdd,0xa2,0x2a,0x32,0x96,0xe7,0x1c,0x27,0xa2,0x0f,0x21,0x08,0xdb,0xa9,
+	0x13,0x13,0x5e,0x7c,0x42,0x6a,0xf1,0x89,0x89,0x84,0xde,0xe9,0xd6,0x43,0x3d,0x97,
+	0x3d,0x48,0x66,0x8f,0xe3,0x84,0xc0,0x92,0x09,0xfd,0xab,0x7a,0x7e,0xe4,0x78,0xf3,
+	0xf7,0x48,0x52,0x3a,0xa3,0xa7,0x47,0x8d,0x07,0xa5,0x5d,0xbf,0x0f,0xc3,0x9f,0x41,
+	0xff,0x82,0x9e,0xff,0xf8,0x99,0xcf,0xa1,0xb7,0x20,0x9c,0xf2,0x01,0xf4,0x2f,0x4b,
+	0xfe,0x0e,0x21,0x01,0xe3,0x6f,0x44,0x3f,0x7d,0xf2,0x60,0xb7,0x28,0x77,0x60,0xde,
+	0xe4,0x38,0x5c,0x13,0xab,0xd2,0x0b,0xe2,0xaf,0xb7,0x08,0xc2,0xb9,0x4d,0x8d,0x47,
+	0xd4,0x1d,0x62,0x12,0x02,0x66,0xd0,0xc1,0xec,0x3b,0x38,0x0e,0x7b,0xa4,0x57,0xb8,
+	0x90,0x7d,0x37,0xde,0x64,0x39,0x7b,0x1f,0xc6,0x6a,0xdf,0xac,0xe7,0xc6,0x70,0x3a,
+	0x4a,0x2b,0xde,0xe7,0x58,0xce,0xcf,0x44,0x8f,0x7b,0x10,0x9c,0x6c,0x3c,0x9f,0x64,
+	0x39,0x3f,0x13,0x3d,0x7e,0x3d,0xa4,0x24,0x1b,0xcf,0xef,0x66,0x39,0xff,0x03,0xca,
+	0x1b,0xaa,0xdf,0x6a,0xd1,0xba,0xa4,0x00,0x00,0x00,0x00,0x49,0x45,0x4e,0x44,0xae,
+	0x42,0x60,0x82 };
+
+int tigr_font_size = (int)sizeof(tigr_font);
+

+ 460 - 0
tigr-master/src/tigr_gl.c

@@ -0,0 +1,460 @@
+#ifndef TIGR_HEADLESS
+
+#include "tigr_internal.h"
+#include <assert.h>
+
+#ifdef TIGR_GAPI_GL
+#if __linux__
+#if __ANDROID__
+#include <GLES3/gl3.h>
+#else
+#define GLX_GLXEXT_PROTOTYPES
+#include <GL/glext.h>
+#endif
+#endif
+extern const char tigr_upscale_gl_vs[], tigr_upscale_gl_fs[], tigr_default_fx_gl_fs[];
+extern const int tigr_upscale_gl_vs_size, tigr_upscale_gl_fs_size, tigr_default_fx_gl_fs_size;
+
+#ifdef _WIN32
+
+#ifdef TIGR_GAPI_GL_WIN_USE_GLEXT
+#include <glext.h>
+#include <wglext.h>
+#else  // short version of glext.h and wglext.h so we don't need to depend on them
+#ifndef APIENTRY
+#define APIENTRY
+#endif
+#ifndef APIENTRYP
+#define APIENTRYP APIENTRY*
+#endif
+typedef ptrdiff_t GLsizeiptr;
+#define GL_COMPILE_STATUS 0x8B81
+#define GL_LINK_STATUS 0x8B82
+#define GL_ARRAY_BUFFER 0x8892
+#define GL_STATIC_DRAW 0x88E4
+#define GL_VERTEX_SHADER 0x8B31
+#define GL_FRAGMENT_SHADER 0x8B30
+#define GL_BGRA 0x80E1
+#define GL_TEXTURE0 0x84C0
+typedef void(APIENTRYP PFNGLGENVERTEXARRAYSPROC)(GLsizei n, GLuint* arrays);
+typedef void(APIENTRYP PFNGLGENBUFFERSARBPROC)(GLsizei n, GLuint* buffers);
+typedef void(APIENTRYP PFNGLBINDBUFFERPROC)(GLenum target, GLuint buffer);
+typedef void(APIENTRYP PFNGLBUFFERDATAPROC)(GLenum target, GLsizeiptr size, const void* data, GLenum usage);
+typedef void(APIENTRYP PFNGLBINDVERTEXARRAYPROC)(GLuint array);
+typedef void(APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC)(GLuint index);
+typedef void(APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC)(GLuint index,
+                                                     GLint size,
+                                                     GLenum type,
+                                                     GLboolean normalized,
+                                                     GLsizei stride,
+                                                     const void* pointer);
+typedef GLuint(APIENTRYP PFNGLCREATESHADERPROC)(GLenum type);
+typedef char GLchar;
+typedef void(APIENTRYP PFNGLSHADERSOURCEPROC)(GLuint shader,
+                                              GLsizei count,
+                                              const GLchar* const* string,
+                                              const GLint* length);
+typedef void(APIENTRYP PFNGLCOMPILESHADERPROC)(GLuint shader);
+typedef GLuint(APIENTRYP PFNGLCREATEPROGRAMPROC)(void);
+typedef void(APIENTRYP PFNGLATTACHSHADERPROC)(GLuint program, GLuint shader);
+typedef void(APIENTRYP PFNGLLINKPROGRAMPROC)(GLuint program);
+typedef void(APIENTRYP PFNGLDELETESHADERPROC)(GLuint shader);
+typedef void(APIENTRYP PFNGLDELETEPROGRAMPROC)(GLuint program);
+typedef void(APIENTRYP PFNGLGETSHADERIVPROC)(GLuint shader, GLenum pname, GLint* params);
+typedef void(APIENTRYP PFNGLGETSHADERINFOLOGPROC)(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog);
+typedef void(APIENTRYP PFNGLGETPROGRAMIVPROC)(GLuint program, GLenum pname, GLint* params);
+typedef void(APIENTRYP PFNGLGETPROGRAMINFOLOGPROC)(GLuint program, GLsizei bufSize, GLsizei* length, GLchar* infoLog);
+typedef void(APIENTRYP PFNGLUSEPROGRAMPROC)(GLuint program);
+typedef GLint(APIENTRYP PFNGLGETUNIFORMLOCATIONPROC)(GLuint program, const GLchar* name);
+typedef void(APIENTRYP PFNGLUNIFORM4FPROC)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
+typedef void(APIENTRYP PFNGLUNIFORMMATRIX4FVPROC)(GLint location,
+                                                  GLsizei count,
+                                                  GLboolean transpose,
+                                                  const GLfloat* value);
+typedef void(APIENTRYP PFNGLACTIVETEXTUREPROC)(GLenum texture);
+#define WGL_DRAW_TO_WINDOW_ARB 0x2001
+#define WGL_SUPPORT_OPENGL_ARB 0x2010
+#define WGL_DOUBLE_BUFFER_ARB 0x2011
+#define WGL_PIXEL_TYPE_ARB 0x2013
+#define WGL_COLOR_BITS_ARB 0x2014
+#define WGL_DEPTH_BITS_ARB 0x2022
+#define WGL_STENCIL_BITS_ARB 0x2023
+#define WGL_TYPE_RGBA_ARB 0x202B
+#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
+#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
+typedef BOOL(WINAPI* PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC hdc,
+                                                     const int* piAttribIList,
+                                                     const FLOAT* pfAttribFList,
+                                                     UINT nMaxFormats,
+                                                     int* piFormats,
+                                                     UINT* nNumFormats);
+typedef HGLRC(WINAPI* PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hDC, HGLRC hShareContext, const int* attribList);
+#endif
+
+PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormat;
+PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribs;
+PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
+PFNGLGENBUFFERSARBPROC glGenBuffers;
+PFNGLBINDBUFFERPROC glBindBuffer;
+PFNGLBUFFERDATAPROC glBufferData;
+PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
+PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray;
+PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer;
+PFNGLCREATESHADERPROC glCreateShader;
+PFNGLSHADERSOURCEPROC glShaderSource;
+PFNGLCOMPILESHADERPROC glCompileShader;
+PFNGLCREATEPROGRAMPROC glCreateProgram;
+PFNGLATTACHSHADERPROC glAttachShader;
+PFNGLLINKPROGRAMPROC glLinkProgram;
+PFNGLDELETESHADERPROC glDeleteShader;
+PFNGLDELETEPROGRAMPROC glDeleteProgram;
+PFNGLGETSHADERIVPROC glGetShaderiv;
+PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
+PFNGLGETPROGRAMIVPROC glGetProgramiv;
+PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
+PFNGLUSEPROGRAMPROC glUseProgram;
+PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
+PFNGLUNIFORM4FPROC glUniform4f;
+PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv;
+PFNGLACTIVETEXTUREPROC glActiveTexture;
+int tigrGL11Init(Tigr* bmp) {
+    int pixel_format;
+    TigrInternal* win = tigrInternal(bmp);
+    GLStuff* gl = &win->gl;
+    PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR),
+                                  1,
+                                  PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER | PFD_SWAP_EXCHANGE,
+                                  PFD_TYPE_RGBA,
+                                  32,  // color bits
+                                  0,
+                                  0,
+                                  0,
+                                  0,
+                                  0,
+                                  0,
+                                  0,
+                                  0,
+                                  0,
+                                  0,
+                                  0,
+                                  0,
+                                  0,
+                                  24,  // depth
+                                  8,   // stencil
+                                  0,
+                                  PFD_MAIN_PLANE,  // is it ignored ?
+                                  0,
+                                  0,
+                                  0,
+                                  0 };
+    if (!(gl->dc = GetDC((HWND)bmp->handle))) {
+        tigrError(bmp, "Cannot create OpenGL device context.\n");
+        return -1;
+    }
+    if (!(pixel_format = ChoosePixelFormat(gl->dc, &pfd))) {
+        tigrError(bmp, "Cannot choose OpenGL pixel format.\n");
+        return -1;
+    }
+    if (!SetPixelFormat(gl->dc, pixel_format, &pfd)) {
+        tigrError(bmp, "Cannot set OpenGL pixel format.\n");
+        return -1;
+    }
+    if (!(gl->hglrc = wglCreateContext(gl->dc))) {
+        tigrError(bmp, "Cannot create OpenGL context.\n");
+        return -1;
+    }
+    if (!wglMakeCurrent(gl->dc, gl->hglrc)) {
+        tigrError(bmp, "Cannot activate OpenGL context.\n");
+        return -1;
+    }
+    gl->gl_legacy = 1;
+    return 0;
+}
+int tigrGL33Init(Tigr* bmp) {
+    int pixel_format;
+    UINT num_formats;
+    TigrInternal* win = tigrInternal(bmp);
+    GLStuff* gl = &win->gl;
+
+    wglChoosePixelFormat = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
+    wglCreateContextAttribs = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");
+    glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)wglGetProcAddress("glGenVertexArrays");
+    glGenBuffers = (PFNGLGENBUFFERSARBPROC)wglGetProcAddress("glGenBuffers");
+    glBindBuffer = (PFNGLBINDBUFFERPROC)wglGetProcAddress("glBindBuffer");
+    glBufferData = (PFNGLBUFFERDATAPROC)wglGetProcAddress("glBufferData");
+    glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)wglGetProcAddress("glBindVertexArray");
+    glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)wglGetProcAddress("glEnableVertexAttribArray");
+    glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)wglGetProcAddress("glVertexAttribPointer");
+    glCreateShader = (PFNGLCREATESHADERPROC)wglGetProcAddress("glCreateShader");
+    glShaderSource = (PFNGLSHADERSOURCEPROC)wglGetProcAddress("glShaderSource");
+    glCompileShader = (PFNGLCOMPILESHADERPROC)wglGetProcAddress("glCompileShader");
+    glCreateProgram = (PFNGLCREATEPROGRAMPROC)wglGetProcAddress("glCreateProgram");
+    glAttachShader = (PFNGLATTACHSHADERPROC)wglGetProcAddress("glAttachShader");
+    glLinkProgram = (PFNGLLINKPROGRAMPROC)wglGetProcAddress("glLinkProgram");
+    glDeleteShader = (PFNGLDELETESHADERPROC)wglGetProcAddress("glDeleteShader");
+    glDeleteProgram = (PFNGLDELETEPROGRAMPROC)wglGetProcAddress("glDeleteProgram");
+    glGetShaderiv = (PFNGLGETSHADERIVPROC)wglGetProcAddress("glGetShaderiv");
+    glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)wglGetProcAddress("glGetShaderInfoLog");
+    glGetProgramiv = (PFNGLGETPROGRAMIVPROC)wglGetProcAddress("glGetProgramiv");
+    glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)wglGetProcAddress("glGetProgramInfoLog");
+    glUseProgram = (PFNGLUSEPROGRAMPROC)wglGetProcAddress("glUseProgram");
+    glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)wglGetProcAddress("glGetUniformLocation");
+    glUniform4f = (PFNGLUNIFORM4FPROC)wglGetProcAddress("glUniform4f");
+    glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC)wglGetProcAddress("glUniformMatrix4fv");
+    glActiveTexture = (PFNGLACTIVETEXTUREPROC)wglGetProcAddress("glActiveTexture");
+
+    if (!wglChoosePixelFormat || !wglCreateContextAttribs) {
+        tigrError(bmp, "Cannot create OpenGL context.\n");
+        return -1;
+    }
+    const int attribList[] = { WGL_DRAW_TO_WINDOW_ARB,
+                               GL_TRUE,
+                               WGL_SUPPORT_OPENGL_ARB,
+                               GL_TRUE,
+                               WGL_DOUBLE_BUFFER_ARB,
+                               GL_TRUE,
+                               WGL_PIXEL_TYPE_ARB,
+                               WGL_TYPE_RGBA_ARB,
+                               WGL_COLOR_BITS_ARB,
+                               32,
+                               WGL_DEPTH_BITS_ARB,
+                               24,
+                               WGL_STENCIL_BITS_ARB,
+                               8,
+                               0 };
+    int attribs[] = { WGL_CONTEXT_MAJOR_VERSION_ARB, 3, WGL_CONTEXT_MINOR_VERSION_ARB, 3, 0 };
+    if (!wglChoosePixelFormat(gl->dc, attribList, NULL, 1, &pixel_format, &num_formats)) {
+        tigrError(bmp, "Cannot choose OpenGL pixel format.\n");
+        return -1;
+    }
+    if (!(gl->hglrc = wglCreateContextAttribs(gl->dc, gl->hglrc, attribs))) {
+        tigrError(bmp, "Cannot create OpenGL context attribs.\n");
+        return -1;
+    }
+    if (!wglMakeCurrent(gl->dc, gl->hglrc)) {
+        tigrError(bmp, "Cannot activate OpenGL context.\n");
+        return -1;
+    }
+    gl->gl_legacy = 0;
+    return 0;
+}
+#endif
+
+void tigrCheckGLError(const char* state) {
+    GLenum err = glGetError();
+    if (err != GL_NO_ERROR) {
+        tigrError(NULL, "got GL error %x when doing %s\n", err, state);
+    }
+}
+
+void tigrCheckShaderErrors(GLuint object) {
+    GLint success;
+    GLchar info[2048];
+    glGetShaderiv(object, GL_COMPILE_STATUS, &success);
+    if (!success) {
+        glGetShaderInfoLog(object, sizeof(info), NULL, info);
+        tigrError(NULL, "shader compile error : %s\n", info);
+    }
+}
+
+void tigrCheckProgramErrors(GLuint object) {
+    GLint success;
+    GLchar info[2048];
+    glGetProgramiv(object, GL_LINK_STATUS, &success);
+    if (!success) {
+        glGetProgramInfoLog(object, sizeof(info), NULL, info);
+        tigrError(NULL, "shader link error : %s\n", info);
+    }
+}
+
+void tigrCreateShaderProgram(GLStuff* gl, const char* fxSource, int fxSize) {
+    if (gl->program != 0) {
+        glDeleteProgram(gl->program);
+        gl->program = 0;
+    }
+
+    GLuint vs = glCreateShader(GL_VERTEX_SHADER);
+    const char* vs_source = (const char*)&tigr_upscale_gl_vs;
+    glShaderSource(vs, 1, &vs_source, &tigr_upscale_gl_vs_size);
+    glCompileShader(vs);
+    tigrCheckShaderErrors(vs);
+
+    GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
+    const char* fs_sources[] = {
+        (const char*)tigr_upscale_gl_fs,
+        fxSource,
+    };
+    const int fs_lengths[] = {
+        tigr_upscale_gl_fs_size,
+        fxSize,
+    };
+    glShaderSource(fs, 2, fs_sources, fs_lengths);
+    glCompileShader(fs);
+    tigrCheckShaderErrors(fs);
+
+    gl->program = glCreateProgram();
+    glAttachShader(gl->program, vs);
+    glAttachShader(gl->program, fs);
+    glLinkProgram(gl->program);
+    tigrCheckProgramErrors(gl->program);
+    glDeleteShader(vs);
+    glDeleteShader(fs);
+
+    gl->uniform_projection = glGetUniformLocation(gl->program, "projection");
+    gl->uniform_model = glGetUniformLocation(gl->program, "model");
+    gl->uniform_parameters = glGetUniformLocation(gl->program, "parameters");
+}
+
+void tigrGAPICreate(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    GLStuff* gl = &win->gl;
+    GLuint VBO;
+    GLfloat vertices[] = { // pos      uv
+                           0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+                           0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f
+    };
+
+#ifdef _WIN32
+    if (tigrGL11Init(bmp))
+        return;
+    tigrGL33Init(bmp);
+#endif
+
+    if (!gl->gl_legacy) {
+        // create vao
+        glGenVertexArrays(1, &gl->vao);
+        glGenBuffers(1, &VBO);
+        glBindBuffer(GL_ARRAY_BUFFER, VBO);
+        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
+        glBindVertexArray(gl->vao);
+        glEnableVertexAttribArray(0);
+        glEnableVertexAttribArray(1);
+        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), NULL);
+        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), NULL);
+
+        // create program
+        tigrCreateShaderProgram(gl, tigr_default_fx_gl_fs, tigr_default_fx_gl_fs_size);
+    }
+
+    // create textures
+    if (gl->gl_legacy) {
+        glEnable(GL_TEXTURE_2D);
+    }
+    glGenTextures(2, gl->tex);
+    for (int i = 0; i < 2; ++i) {
+        glBindTexture(GL_TEXTURE_2D, gl->tex[i]);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl->gl_legacy ? GL_NEAREST : GL_LINEAR);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl->gl_legacy ? GL_NEAREST : GL_LINEAR);
+        glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+    }
+
+    tigrCheckGLError("initialization");
+}
+
+void tigrGAPIDestroy(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    GLStuff* gl = &win->gl;
+
+    if (tigrGAPIBegin(bmp) < 0) {
+        tigrError(bmp, "Cannot activate OpenGL context.\n");
+        return;
+    }
+
+    if (!gl->gl_legacy) {
+        glDeleteTextures(2, gl->tex);
+        glDeleteProgram(gl->program);
+    }
+
+    tigrCheckGLError("destroy");
+
+    if (tigrGAPIEnd(bmp) < 0) {
+        tigrError(bmp, "Cannot deactivate OpenGL context.\n");
+        return;
+    }
+}
+
+void tigrGAPIDraw(int legacy, GLuint uniform_model, GLuint tex, Tigr* bmp, int x1, int y1, int x2, int y2) {
+    glBindTexture(GL_TEXTURE_2D, tex);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, bmp->w, bmp->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, bmp->pix);
+
+    if (!legacy) {
+        float sx = (float)(x2 - x1);
+        float sy = (float)(y2 - y1);
+        float tx = (float)x1;
+        float ty = (float)y1;
+
+        float model[16] = { sx, 0.0f, 0.0f, 0.0f, 0.0f, sy, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, tx, ty, 0.0f, 1.0f };
+
+        glUniformMatrix4fv(uniform_model, 1, GL_FALSE, model);
+        glDrawArrays(GL_TRIANGLES, 0, 6);
+    } else {
+#if !(__APPLE__ || __ANDROID__)
+        glBegin(GL_QUADS);
+        glTexCoord2f(1.0f, 0.0f);
+        glVertex2i(x2, y1);
+        glTexCoord2f(0.0f, 0.0f);
+        glVertex2i(x1, y1);
+        glTexCoord2f(0.0f, 1.0f);
+        glVertex2i(x1, y2);
+        glTexCoord2f(1.0f, 1.0f);
+        glVertex2i(x2, y2);
+        glEnd();
+#else
+        assert(0);
+#endif
+    }
+}
+
+void tigrGAPIPresent(Tigr* bmp, int w, int h) {
+    TigrInternal* win = tigrInternal(bmp);
+    GLStuff* gl = &win->gl;
+
+    glViewport(0, 0, w, h);
+    if (!gl->gl_user_opengl_rendering) {
+        glClearColor(0, 0, 0, 1);
+        glClear(GL_COLOR_BUFFER_BIT);
+    }
+
+    if (!gl->gl_legacy) {
+        float projection[16] = { 2.0f / w, 0.0f, 0.0f, 0.0f, 0.0f,  -2.0f / h, 0.0f, 0.0f,
+                                 0.0f,     0.0f, 1.0f, 0.0f, -1.0f, 1.0f,      0.0f, 1.0f };
+
+        glActiveTexture(GL_TEXTURE0);
+        glBindVertexArray(gl->vao);
+        glUseProgram(gl->program);
+        glUniformMatrix4fv(gl->uniform_projection, 1, GL_FALSE, projection);
+        glUniform4f(gl->uniform_parameters, win->p1, win->p2, win->p3, win->p4);
+    } else {
+#if !(__APPLE__ || __ANDROID__)
+        glMatrixMode(GL_PROJECTION);
+        glLoadIdentity();
+        glOrtho(0, w, h, 0, -1.0f, 1.0f);
+        glEnable(GL_TEXTURE_2D);
+#else
+        assert(0);
+#endif
+    }
+
+    if (gl->gl_user_opengl_rendering) {
+        glEnable(GL_BLEND);
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+    } else {
+        glDisable(GL_BLEND);
+    }
+    tigrGAPIDraw(gl->gl_legacy, gl->uniform_model, gl->tex[0], bmp, win->pos[0], win->pos[1], win->pos[2], win->pos[3]);
+
+    if (win->widgetsScale > 0) {
+        glEnable(GL_BLEND);
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+        tigrGAPIDraw(gl->gl_legacy, gl->uniform_model, gl->tex[1], win->widgets,
+                     (int)(w - win->widgets->w * win->widgetsScale), 0, w, (int)(win->widgets->h * win->widgetsScale));
+    }
+
+    tigrCheckGLError("present");
+
+    gl->gl_user_opengl_rendering = 0;
+}
+
+#endif
+#endif // #ifndef TIGR_HEADLESS

+ 12 - 0
tigr-master/src/tigr_glsl_hdr.h

@@ -0,0 +1,12 @@
+#ifndef __TIGR_GLSL_HDR_H__
+#define __TIGR_GLSL_HDR_H__
+
+#if __ANDROID__ || __IOS__
+#define GLSL_VERSION_HEADER \
+    "#version 300 es\n"     \
+    "precision mediump float;\n"
+#else
+#define GLSL_VERSION_HEADER "#version 330 core\n"
+#endif
+
+#endif  // __TIGR_GLSL_HDR_H__

+ 248 - 0
tigr-master/src/tigr_inflate.c

@@ -0,0 +1,248 @@
+#include "tigr_internal.h"
+#include <stdlib.h>
+#include <setjmp.h>
+
+typedef struct {
+    unsigned bits, count;
+    const unsigned char *in, *inend;
+    unsigned char *out, *outend;
+    jmp_buf jmp;
+    unsigned litcodes[288], distcodes[32], lencodes[19];
+    int tlit, tdist, tlen;
+} State;
+
+#define FAIL() longjmp(s->jmp, 1)
+#define CHECK(X) \
+    if (!(X))    \
+    FAIL()
+
+// Built-in DEFLATE standard tables.
+static char order[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
+static char lenBits[29 + 2] = { 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 lenBase[29 + 2] = { 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 char distBits[30 + 2] = { 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, 0, 0 };
+static int distBase[30 + 2] = {
+    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
+};
+
+// Table to bit-reverse a byte.
+static const unsigned char reverseTable[256] = {
+#define R2(n) n, n + 128, n + 64, n + 192
+#define R4(n) R2(n), R2(n + 32), R2(n + 16), R2(n + 48)
+#define R6(n) R4(n), R4(n + 8), R4(n + 4), R4(n + 12)
+    R6(0), R6(2), R6(1), R6(3)
+};
+
+static unsigned rev16(unsigned n) {
+    return (reverseTable[n & 0xff] << 8) | reverseTable[(n >> 8) & 0xff];
+}
+
+static int bits(State* s, int n) {
+    int v = s->bits & ((1 << n) - 1);
+    s->bits >>= n;
+    s->count -= n;
+    while (s->count < 16) {
+        CHECK(s->in != s->inend);
+        s->bits |= (*s->in++) << s->count;
+        s->count += 8;
+    }
+    return v;
+}
+
+static unsigned char* emit(State* s, int len) {
+    s->out += len;
+    CHECK(s->out <= s->outend);
+    return s->out - len;
+}
+
+static void copy(State* s, const unsigned char* src, int len) {
+    unsigned char* dest = emit(s, len);
+    while (len--)
+        *dest++ = *src++;
+}
+
+static int build(State* s, unsigned* tree, unsigned char* lens, unsigned int symcount) {
+    unsigned int codes[16], first[16], counts[16] = { 0 };
+
+    // Frequency count.
+    for (unsigned int n = 0; n < symcount; n++)
+        counts[lens[n]]++;
+
+    // Distribute codes.
+    counts[0] = codes[0] = first[0] = 0;
+    for (unsigned int n = 1; n <= 15; n++) {
+        codes[n] = (codes[n - 1] + counts[n - 1]) << 1;
+        first[n] = first[n - 1] + counts[n - 1];
+    }
+    CHECK(first[15] + counts[15] <= symcount);
+
+    // Insert keys into the tree for each symbol.
+    for (unsigned int n = 0; n < symcount; n++) {
+        int len = lens[n];
+        if (len != 0) {
+            unsigned code = codes[len]++, slot = first[len]++;
+            tree[slot] = (code << (32 - len)) | (n << 4) | len;
+        }
+    }
+
+    return first[15];
+}
+
+static int decode(State* s, unsigned tree[], int max) {
+    // Find the next prefix code.
+    unsigned lo = 0, hi = max, key;
+    unsigned search = (rev16(s->bits) << 16) | 0xffff;
+    while (lo < hi) {
+        unsigned guess = (lo + hi) / 2;
+        if (search < tree[guess])
+            hi = guess;
+        else
+            lo = guess + 1;
+    }
+
+    // Pull out the key and check it.
+    key = tree[lo - 1];
+    CHECK(((search ^ key) >> (32 - (key & 0xf))) == 0);
+
+    bits(s, key & 0xf);
+    return (key >> 4) & 0xfff;
+}
+
+static void run(State* s, int sym) {
+    int length = bits(s, lenBits[sym]) + lenBase[sym];
+    int dsym = decode(s, s->distcodes, s->tdist);
+    int offs = bits(s, distBits[dsym]) + distBase[dsym];
+    copy(s, s->out - offs, length);
+}
+
+static void block(State* s) {
+    for (;;) {
+        int sym = decode(s, s->litcodes, s->tlit);
+        if (sym < 256)
+            *emit(s, 1) = (unsigned char)sym;
+        else if (sym > 256)
+            run(s, sym - 257);
+        else
+            break;
+    }
+}
+
+static void stored(State* s) {
+    // Uncompressed data block.
+    int len;
+    bits(s, s->count & 7);
+    len = bits(s, 16);
+    CHECK(((len ^ s->bits) & 0xffff) == 0xffff);
+    CHECK(s->in + len <= s->inend);
+
+    copy(s, s->in, len);
+    s->in += len;
+    bits(s, 16);
+}
+
+static void fixed(State* s) {
+    // Fixed set of Huffman codes.
+    int n;
+    unsigned char lens[288 + 32];
+    for (n = 0; n <= 143; n++)
+        lens[n] = 8;
+    for (n = 144; n <= 255; n++)
+        lens[n] = 9;
+    for (n = 256; n <= 279; n++)
+        lens[n] = 7;
+    for (n = 280; n <= 287; n++)
+        lens[n] = 8;
+    for (n = 0; n < 32; n++)
+        lens[288 + n] = 5;
+
+    // Build lit/dist trees.
+    s->tlit = build(s, s->litcodes, lens, 288);
+    s->tdist = build(s, s->distcodes, lens + 288, 32);
+}
+
+static void dynamic(State* s) {
+    int n, i, nlit, ndist, nlen;
+    unsigned char lenlens[19] = { 0 }, lens[288 + 32];
+    nlit = 257 + bits(s, 5);
+    ndist = 1 + bits(s, 5);
+    nlen = 4 + bits(s, 4);
+    for (n = 0; n < nlen; n++)
+        lenlens[(int) order[n]] = (unsigned char)bits(s, 3);
+
+    // Build the tree for decoding code lengths.
+    s->tlen = build(s, s->lencodes, lenlens, 19);
+
+    // Decode code lengths.
+    for (n = 0; n < nlit + ndist;) {
+        int sym = decode(s, s->lencodes, s->tlen);
+        switch (sym) {
+            case 16:
+                for (i = 3 + bits(s, 2); i; i--, n++)
+                    lens[n] = lens[n - 1];
+                break;
+            case 17:
+                for (i = 3 + bits(s, 3); i; i--, n++)
+                    lens[n] = 0;
+                break;
+            case 18:
+                for (i = 11 + bits(s, 7); i; i--, n++)
+                    lens[n] = 0;
+                break;
+            default:
+                lens[n++] = (unsigned char)sym;
+                break;
+        }
+    }
+
+    // Build lit/dist trees.
+    s->tlit = build(s, s->litcodes, lens, nlit);
+    s->tdist = build(s, s->distcodes, lens + nlit, ndist);
+}
+
+int tigrInflate(void* out, unsigned outlen, const void* in, unsigned inlen) {
+    int last;
+    State* s = (State*)calloc(1, sizeof(State));
+
+    // We assume we can buffer 2 extra bytes from off the end of 'in'.
+    s->in = (unsigned char*)in;
+    s->inend = s->in + inlen + 2;
+    s->out = (unsigned char*)out;
+    s->outend = s->out + outlen;
+    s->bits = 0;
+    s->count = 0;
+    bits(s, 0);
+
+    if (setjmp(s->jmp) == 1) {
+        free(s);
+        return 0;
+    }
+
+    do {
+        last = bits(s, 1);
+        switch (bits(s, 2)) {
+            case 0:
+                stored(s);
+                break;
+            case 1:
+                fixed(s);
+                block(s);
+                break;
+            case 2:
+                dynamic(s);
+                block(s);
+                break;
+            case 3:
+                FAIL();
+        }
+    } while (!last);
+
+    free(s);
+    return 1;
+}
+
+#undef CHECK
+#undef FAIL

+ 155 - 0
tigr-master/src/tigr_internal.h

@@ -0,0 +1,155 @@
+// can't use pragma once here because this file probably will endup in .c
+#ifndef __TIGR_INTERNAL_H__
+#define __TIGR_INTERNAL_H__
+
+#define _CRT_SECURE_NO_WARNINGS NOPE
+
+// Graphics configuration.
+#ifndef TIGR_HEADLESS
+#define TIGR_GAPI_GL
+
+// Calculates the biggest scale that a bitmap can fit into an area at.
+int tigrCalcScale(int bmpW, int bmpH, int areaW, int areaH);
+#endif
+
+// Creates a new bitmap, with extra payload bytes.
+Tigr* tigrBitmap2(int w, int h, int extra);
+
+// Resizes an existing bitmap.
+void tigrResize(Tigr* bmp, int w, int h);
+
+// Calculates a new scale, taking minimum-scale flags into account.
+int tigrEnforceScale(int scale, int flags);
+
+// Calculates the correct position for a bitmap to fit into a window.
+void tigrPosition(Tigr* bmp, int scale, int windowW, int windowH, int out[4]);
+
+// ----------------------------------------------------------
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif
+
+#if !defined(TIGR_HEADLESS) && __linux__ && !__ANDROID__
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#endif
+
+#ifdef __APPLE__
+#include "TargetConditionals.h"
+#if TARGET_OS_IPHONE
+#define __IOS__ 1
+#else
+#define __MACOS__ 1
+#endif
+#endif
+
+#ifdef TIGR_GAPI_GL
+#if __MACOS__
+#define GL_SILENCE_DEPRECATION
+#include <OpenGL/gl3.h>
+#endif
+#ifdef _WIN32
+#include <GL/gl.h>
+#endif
+#if __linux__ && !__ANDROID__
+#define GL_GLEXT_PROTOTYPES
+#include <GL/gl.h>
+#include <GL/glx.h>
+#endif
+#if __ANDROID__
+#include <EGL/egl.h>
+#include <GLES3/gl3.h>
+#endif
+#if __IOS__
+#define GLES_SILENCE_DEPRECATION
+#include <OpenGLES/ES3/gl.h>
+#endif
+
+typedef struct {
+#ifdef _WIN32
+    HGLRC hglrc;
+    HDC dc;
+#endif
+#ifdef __APPLE__
+    void* glContext;
+#endif
+    GLuint tex[2];
+    GLuint vao;
+    GLuint program;
+    GLuint uniform_projection;
+    GLuint uniform_model;
+    GLuint uniform_parameters;
+    int gl_legacy;
+    int gl_user_opengl_rendering;
+} GLStuff;
+#endif
+
+#define MAX_TOUCH_POINTS 10
+
+typedef struct {
+    int shown, closed;
+#ifdef TIGR_GAPI_GL
+    GLStuff gl;
+#ifdef _WIN32
+    wchar_t* wtitle;
+    DWORD dwStyle;
+    RECT oldPos;
+#endif
+#ifdef __linux__
+#if __ANDROID__
+    EGLContext context;
+#else
+    Display* dpy;
+    Window win;
+    GLXContext glc;
+    XIC ic;
+#endif  // __ANDROID__
+#endif  // __linux__
+#endif  // TIGR_GAPI_GL
+
+    Tigr* widgets;
+    int widgetsWanted;
+    unsigned char widgetAlpha;
+    float widgetsScale;
+
+    float p1, p2, p3, p4;
+
+    int flags;
+    int scale;
+    int pos[4];
+    int lastChar;
+    char keys[256], prev[256];
+#if defined(__ANDROID__)
+    char released[256];
+#endif  // __ANDROID__
+#if defined(__MACOS__)
+    int mouseInView;
+    int mouseButtons;
+    double frameTime;
+#endif  // __MACOS__
+#if defined(__linux__) || defined(__IOS__)
+    int mouseButtons;
+    int mouseX;
+    int mouseY;
+#endif  // __linux__ __IOS__
+#if defined(__ANDROID__) || defined(__IOS__)
+    int numTouchPoints;
+    TigrTouchPoint touchPoints[MAX_TOUCH_POINTS];
+#endif  // __ANDROID__ __IOS__
+#if defined(_WIN32) || defined(__linux__) || defined(__MACOS__)
+    float scrollDeltaX;
+    float scrollDeltaY;
+#endif  // _WIN32 __linux__ __MACOS__
+} TigrInternal;
+// ----------------------------------------------------------
+
+TigrInternal* tigrInternal(Tigr* bmp);
+
+void tigrGAPICreate(Tigr* bmp);
+void tigrGAPIDestroy(Tigr* bmp);
+int tigrGAPIBegin(Tigr* bmp);
+int tigrGAPIEnd(Tigr* bmp);
+void tigrGAPIPresent(Tigr* bmp, int w, int h);
+
+#endif

+ 530 - 0
tigr-master/src/tigr_ios.c

@@ -0,0 +1,530 @@
+#ifndef TIGR_HEADLESS
+
+#include "tigr_internal.h"
+#include "tigr_objc.h"
+
+#ifdef __IOS__
+
+#include <CoreGraphics/CoreGraphics.h>
+#include <objc/message.h>
+#include <objc/objc.h>
+#include <objc/runtime.h>
+#include <dispatch/dispatch.h>
+#include <os/log.h>
+#include <time.h>
+#include <stdatomic.h>
+
+id makeNSString(const char* str) {
+    return objc_msgSend_t(id, const char*)(class("NSString"), sel("stringWithUTF8String:"), str);
+}
+
+id joinNSStrings(id a, id b) {
+    return objc_msgSend_t(id, id)(a, sel("stringByAppendingString:"), b);
+}
+
+const char* UTF8StringFromNSString(id a) {
+    return objc_msgSend_t(const char*)(a, sel("UTF8String"));
+}
+
+extern id UIApplication;
+static int NSQualityOfServiceUserInteractive = 0x21;
+
+typedef struct {
+    TigrTouchPoint points[MAX_TOUCH_POINTS];
+    int numPoints;
+} InputState;
+
+typedef struct {
+    int keyCode;
+    int codePoint;
+} KeyEvent;
+
+enum {
+    KBD_HIDDEN = 0,
+    KBD_SHOWREQ,
+    KBD_HIDEREQ,
+    KBD_SHOWN,
+};
+
+/// Global state
+static struct {
+    InputState inputState;
+    id viewController;
+    id view;
+    id context;
+    id frameCondition;
+    int screenW;
+    int screenH;
+    double scaleFactor;
+    double timeSinceLastDraw;
+    int renderReadFd;
+    int mainWriteFd;
+    _Atomic(int) keyboardState;
+} gState = {
+    .inputState = {
+        .numPoints = 0,
+    },
+    .viewController = 0,
+    .view = 0,
+    .context = 0,
+    .frameCondition = 0,
+    .screenW = 0,
+    .screenH = 0,
+    .scaleFactor = 1,
+    .timeSinceLastDraw = 0,
+    .renderReadFd = 0,
+    .mainWriteFd = 0,
+    .keyboardState = ATOMIC_VAR_INIT(KBD_HIDDEN),
+};
+
+typedef enum {
+    SET_INPUT,
+    KEY_EVENT,
+} TigrMessage;
+
+typedef struct TigrMessageData {
+    TigrMessage message;
+    union {
+        InputState inputState;
+        KeyEvent keyEvent;
+    };
+} TigrMessageData;
+
+static id autoreleasePool = NULL;
+
+void writeToRenderThread(const TigrMessageData* message) {
+    if (write(gState.mainWriteFd, message, sizeof(TigrMessageData)) != sizeof(TigrMessageData)) {
+        os_log_error(OS_LOG_DEFAULT, "Failed to write message to render thread: %{public}s", strerror(errno));
+    }
+}
+
+int readFromMainThread(TigrMessageData* message) {
+    int result = read(gState.renderReadFd, message, sizeof(TigrMessageData));
+    if (result == -1 && errno == EAGAIN) {
+        return 0;
+    }
+    if (result != sizeof(TigrMessageData)) {
+        os_log_error(OS_LOG_DEFAULT, "Failed to read message from main thread: %{public}s", strerror(errno));
+        return 0;
+    }
+    return 1;
+}
+
+void viewWillTransitionToSize(id self, SEL _sel, CGSize size, id transitionCoordinator) {
+    // No animation, just set them
+    gState.screenW = size.width * gState.scaleFactor;
+    gState.screenH = size.height * gState.scaleFactor;
+    struct objc_super super = {
+        self,
+        objc_getClass("GLKViewController"),
+    };
+    objc_msgSendSuper_t(void, CGSize, id)(&super, _sel, size, transitionCoordinator);
+}
+
+BOOL prefersStatusBarHidden(id self, SEL _sel) {
+    return YES;
+}
+
+BOOL hasText(id self, SEL _sel) {
+    return NO;
+}
+
+void tigrShowKeyboard(int show) {
+    int expected = show ? KBD_HIDDEN : KBD_SHOWN;
+    int desired = show ? KBD_SHOWREQ : KBD_HIDEREQ;
+    atomic_compare_exchange_weak(&gState.keyboardState, &expected, desired);
+}
+
+void insertText(id self, SEL _sel, id text) {
+    const char* inserted = UTF8StringFromNSString(text);
+    int codePoint = 0;
+
+    do {
+        inserted = tigrDecodeUTF8(inserted, &codePoint);
+        if (codePoint != 0) {
+            KeyEvent event;
+            event.codePoint = codePoint;
+            event.keyCode = (codePoint < 128) ? codePoint : 0;
+
+            TigrMessageData message = {
+                .message = KEY_EVENT,
+                .keyEvent = event,
+            };
+            writeToRenderThread(&message);
+        }
+    } while (*inserted != 0);
+}
+
+void deleteBackward(id self, SEL _sel) {
+    KeyEvent event;
+    event.codePoint = 0;
+    event.keyCode = 8;  // BS
+
+    TigrMessageData message = { .message = KEY_EVENT,
+                                .keyEvent = {
+                                    .codePoint = 0,
+                                    .keyCode = 8,
+                                } };
+    writeToRenderThread(&message);
+}
+
+BOOL canBecomeFirstResponder(id self, SEL _sel) {
+    return YES;
+}
+
+BOOL canResignFirstResponder(id self, SEL _sel) {
+    return YES;
+}
+
+enum RenderState { SWAPPED = 5150, RENDERED };
+
+BOOL didFinishLaunchingWithOptions(id self, SEL _sel, id application, id options) {
+    id screen = objc_msgSend_id(class("UIScreen"), sel("mainScreen"));
+    CGRect bounds = objc_msgSend_t(CGRect)(screen, sel("bounds"));
+    CGSize size = bounds.size;
+
+    id window = objc_alloc("UIWindow");
+    window = objc_msgSend_t(id, CGRect)(window, sel("initWithFrame:"), bounds);
+
+    Class ViewController = makeClass("TigrViewController", "GLKViewController");
+    addMethod(ViewController, "viewWillTransitionToSize:withTransitionCoordinator:", viewWillTransitionToSize,
+              "v@:{CGSize}@");
+    addMethod(ViewController, "prefersStatusBarHidden", prefersStatusBarHidden, "c@:");
+    id vc = objc_msgSend_t(id)((id)ViewController, sel("alloc"));
+    vc = objc_msgSend_id(vc, sel("init"));
+    gState.viewController = vc;
+    objc_msgSend_t(void, int)(vc, sel("setPreferredFramesPerSecond:"), 60);
+    int framesPerSecond = objc_msgSend_t(int)(vc, sel("framesPerSecond"));
+
+    id context = objc_alloc("EAGLContext");
+    static int kEAGLRenderingAPIOpenGLES3 = 3;
+    context = objc_msgSend_t(id, int)(context, sel("initWithAPI:"), kEAGLRenderingAPIOpenGLES3);
+    gState.context = context;
+
+    Class View = makeClass("TigrView", "GLKView");
+    addMethod(View, "insertText:", insertText, "v@:@");
+    addMethod(View, "deleteBackward", deleteBackward, "v@:");
+    addMethod(View, "hasText", hasText, "c@:");
+    addMethod(View, "canBecomeFirstResponder", canBecomeFirstResponder, "c@:");
+    addMethod(View, "canResignFirstResponder", canResignFirstResponder, "c@:");
+
+    Protocol* UIKeyInput = objc_getProtocol("UIKeyInput");
+    class_addProtocol(View, UIKeyInput);
+
+    id view = objc_msgSend_id((id)View, sel("alloc"));
+    view = objc_msgSend_t(id, CGRect, id)(view, sel("initWithFrame:context:"), bounds, context);
+    gState.view = view;
+    objc_msgSend_t(void, BOOL)(view, sel("setMultipleTouchEnabled:"), YES);
+    objc_msgSend_t(void, id)(view, sel("setDelegate:"), self);
+    objc_msgSend_t(void, id)(vc, sel("setView:"), view);
+    objc_msgSend_t(void, id)(vc, sel("setDelegate:"), self);
+    objc_msgSend_t(void, id)(window, sel("setRootViewController:"), vc);
+    objc_msgSend_t(void)(window, sel("makeKeyAndVisible"));
+
+    gState.scaleFactor = objc_msgSend_t(double)(view, sel("contentScaleFactor"));
+    gState.screenW = size.width * gState.scaleFactor;
+    gState.screenH = size.height * gState.scaleFactor;
+
+    gState.frameCondition = objc_msgSend_t(id, int)(objc_alloc("NSConditionLock"), sel("initWithCondition:"), RENDERED);
+    objc_msgSend_t(void, int)(gState.frameCondition, sel("lockWhenCondition:"), RENDERED);
+
+    id renderThread = objc_msgSend_t(id, id, SEL, id)(objc_alloc("NSThread"), sel("initWithTarget:selector:object:"),
+                                                      self, sel("renderMain"), NULL);
+    objc_msgSend_t(void, int)(renderThread, sel("setQualityOfService:"), NSQualityOfServiceUserInteractive);
+    objc_msgSend_t(void, id)(renderThread, sel("setName:"), makeNSString("Tigr Render Thread"));
+    objc_msgSend_void(renderThread, sel("start"));
+
+    return YES;
+}
+
+void waitForFrame() {
+    objc_msgSend_t(void, id)(class("EAGLContext"), sel("setCurrentContext:"), 0);
+    objc_msgSend_t(void, int)(gState.frameCondition, sel("unlockWithCondition:"), RENDERED);
+    objc_msgSend_t(void, int)(gState.frameCondition, sel("lockWhenCondition:"), SWAPPED);
+    objc_msgSend_t(void, id)(class("EAGLContext"), sel("setCurrentContext:"), gState.context);
+}
+
+void processKeyboardRequest() {
+    int showReq = KBD_SHOWREQ;
+    int hideReq = KBD_HIDEREQ;
+
+    if (atomic_compare_exchange_weak(&gState.keyboardState, &showReq, KBD_SHOWN)) {
+        objc_msgSend_t(BOOL)(gState.view, sel("becomeFirstResponder"));
+    } else if (atomic_compare_exchange_weak(&gState.keyboardState, &hideReq, KBD_HIDDEN)) {
+        objc_msgSend_t(BOOL)(gState.view, sel("resignFirstResponder"));
+    }
+}
+
+void drawInRect(id _self, SEL _sel, id view, CGRect rect) {
+    gState.timeSinceLastDraw = objc_msgSend_t(double)(gState.viewController, sel("timeSinceLastDraw"));
+    objc_msgSend_t(void, int)(gState.frameCondition, sel("unlockWithCondition:"), SWAPPED);
+    objc_msgSend_t(void, int)(gState.frameCondition, sel("lockWhenCondition:"), RENDERED);
+    objc_msgSend_t(void, id)(class("EAGLContext"), sel("setCurrentContext:"), gState.context);
+
+    processKeyboardRequest();
+}
+
+extern void tigrMain();
+
+void renderMain(id _self, SEL _sel) {
+    objc_msgSend_t(void, int)(gState.frameCondition, sel("lockWhenCondition:"), SWAPPED);
+    objc_msgSend_t(void, id)(class("EAGLContext"), sel("setCurrentContext:"), gState.context);
+    tigrMain();
+}
+
+enum {
+    UITouchPhaseBegan,
+    UITouchPhaseMoved,
+    UITouchPhaseStationary,
+    UITouchPhaseEnded,
+    UITouchPhaseCancelled,
+};
+
+void touches(id self, SEL sel, id touches, id event) {
+    id allTouches = objc_msgSend_t(id)(event, sel("allTouches"));
+    id enumerator = objc_msgSend_t(id)(allTouches, sel("objectEnumerator"));
+    id touch = 0;
+    InputState input = {
+        .numPoints = 0,
+    };
+    while ((touch = objc_msgSend_t(id)(enumerator, sel("nextObject")))) {
+        CGPoint location = objc_msgSend_t(CGPoint, id)(touch, sel("locationInView:"), NULL);
+        int phase = objc_msgSend_t(int)(touch, sel("phase"));
+        switch (phase) {
+            case UITouchPhaseBegan:
+            case UITouchPhaseMoved:
+            case UITouchPhaseStationary:
+                input.points[input.numPoints].x = location.x * gState.scaleFactor;
+                input.points[input.numPoints].y = location.y * gState.scaleFactor;
+                input.numPoints++;
+                break;
+        }
+        if (input.numPoints >= MAX_TOUCH_POINTS) {
+            break;
+        }
+    }
+    TigrMessageData message = {
+        .message = SET_INPUT,
+        .inputState = input,
+    };
+    writeToRenderThread(&message);
+}
+
+Class tigrAppDelegate() {
+    static Class delegateClass = 0;
+    if (delegateClass != 0) {
+        return delegateClass;
+    }
+
+    id application = objc_msgSend_id(class("UIApplication"), sel("sharedApplication"));
+    delegateClass = makeClass("TigrAppDelegate", "UIResponder");
+    addMethod(delegateClass, "application:didFinishLaunchingWithOptions:", didFinishLaunchingWithOptions, "c@:@@");
+    addMethod(delegateClass, "touchesBegan:withEvent:", touches, "v@:@@");
+    addMethod(delegateClass, "touchesMoved:withEvent:", touches, "v@:@@");
+    addMethod(delegateClass, "touchesEnded:withEvent:", touches, "v@:@@");
+    addMethod(delegateClass, "touchesCancelled:withEvent:", touches, "v@:@@");
+    addMethod(delegateClass, "glkView:drawInRect:", drawInRect, "v@:@{CGRect}");
+    addMethod(delegateClass, "renderMain", renderMain, "v@:");
+    objc_registerClassPair(delegateClass);
+
+    int fds[2];
+    if (pipe(fds) != 0) {
+        tigrError(0, "Failed to create message pipe");
+    }
+    int flags = fcntl(fds[0], F_GETFL, 0);
+    fcntl(fds[0], F_SETFL, flags | O_NONBLOCK);
+
+    gState.renderReadFd = fds[0];
+    gState.mainWriteFd = fds[1];
+    return delegateClass;
+}
+
+Tigr* tigrWindow(int w, int h, const char* title, int flags) {
+    int scale = 1;
+    if (flags & TIGR_AUTO) {
+        // Always use a 1:1 pixel size.
+        scale = 1;
+    } else {
+        // See how big we can make it and still fit on-screen.
+        scale = tigrCalcScale(w, h, gState.screenW, gState.screenH);
+    }
+
+    scale = tigrEnforceScale(scale, flags);
+    Tigr* bmp = tigrBitmap2(w, h, sizeof(TigrInternal));
+    bmp->handle = (void*)4711;
+    TigrInternal* win = tigrInternal(bmp);
+    win->shown = 0;
+    win->closed = 0;
+    win->scale = scale;
+
+    win->lastChar = 0;
+    win->flags = flags;
+    win->p1 = win->p2 = win->p3 = 0;
+    win->p4 = 1;
+    win->widgetsWanted = 0;
+    win->widgetAlpha = 0;
+    win->widgetsScale = 0;
+    win->widgets = 0;
+    win->gl.gl_legacy = 0;
+
+    tigrPosition(bmp, win->scale, bmp->w, bmp->h, win->pos);
+    tigrGAPICreate(bmp);
+
+    return bmp;
+}
+
+void processEvents(TigrInternal* win) {
+    memset(win->keys, 0, 255);
+
+    TigrMessageData data;
+
+    while (readFromMainThread(&data)) {
+        switch (data.message) {
+            case SET_INPUT:
+                gState.inputState = data.inputState;
+                break;
+            case KEY_EVENT:
+                win->keys[data.keyEvent.keyCode] = 1;
+                win->lastChar = data.keyEvent.codePoint;
+                break;
+        }
+    }
+}
+
+static int toWindowX(TigrInternal* win, int x) {
+    return (x - win->pos[0]) / win->scale;
+}
+
+static int toWindowY(TigrInternal* win, int y) {
+    return (y - win->pos[1]) / win->scale;
+}
+
+void tigrUpdate(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+
+    processEvents(win);
+
+    win->numTouchPoints = gState.inputState.numPoints;
+    for (int i = 0; i < win->numTouchPoints; i++) {
+        win->touchPoints[i].x = toWindowX(win, gState.inputState.points[i].x);
+        win->touchPoints[i].y = toWindowY(win, gState.inputState.points[i].y);
+    }
+
+    win->mouseButtons = win->numTouchPoints;
+    if (win->mouseButtons > 0) {
+        win->mouseX = win->touchPoints[0].x;
+        win->mouseY = win->touchPoints[0].y;
+    }
+
+    if (win->flags & TIGR_AUTO) {
+        tigrResize(bmp, gState.screenW / win->scale, gState.screenH / win->scale);
+    } else {
+        win->scale = tigrEnforceScale(tigrCalcScale(bmp->w, bmp->h, gState.screenW, gState.screenH), win->flags);
+    }
+
+    tigrPosition(bmp, win->scale, gState.screenW, gState.screenH, win->pos);
+    tigrGAPIPresent(bmp, gState.screenW, gState.screenH);
+    waitForFrame();
+}
+
+int tigrClosed(Tigr* bmp) {
+    return 0;
+}
+
+void tigrError(Tigr* bmp, const char* message, ...) {
+    char tmp[1024];
+
+    va_list args;
+    va_start(args, message);
+    vsnprintf(tmp, sizeof(tmp), message, args);
+    tmp[sizeof(tmp) - 1] = 0;
+    va_end(args);
+
+    os_log_error(OS_LOG_DEFAULT, "tigr fatal error: %{public}s\n", tmp);
+
+    exit(1);
+}
+
+float tigrTime() {
+    return (float)gState.timeSinceLastDraw;
+}
+
+void tigrMouse(Tigr* bmp, int* x, int* y, int* buttons) {
+    TigrInternal* win = tigrInternal(bmp);
+    if (x) {
+        *x = win->mouseX;
+    }
+    if (y) {
+        *y = win->mouseY;
+    }
+    if (buttons) {
+        *buttons = win->mouseButtons;
+    }
+}
+
+int tigrTouch(Tigr* bmp, TigrTouchPoint* points, int maxPoints) {
+    TigrInternal* win = tigrInternal(bmp);
+    for (int i = 0; i < maxPoints && i < win->numTouchPoints; i++) {
+        points[i] = win->touchPoints[i];
+    }
+    return maxPoints < win->numTouchPoints ? maxPoints : win->numTouchPoints;
+}
+
+void tigrMouseWheel(Tigr* bmp, float* x, float* y) {
+    if (x) {
+        *x = 0;
+    }
+    if (y) {
+        *y = 0;
+    }
+}
+
+void tigrFree(Tigr* bmp) {
+    if (bmp->handle) {
+        TigrInternal* win = tigrInternal(bmp);
+    }
+    free(bmp->pix);
+    free(bmp);
+}
+
+int tigrGAPIBegin(Tigr* bmp) {
+    (void)bmp;
+    return 0;
+}
+
+int tigrGAPIEnd(Tigr* bmp) {
+    (void)bmp;
+    return 0;
+}
+
+int tigrKeyDown(Tigr* bmp, int key) {
+    TigrInternal* win;
+    assert(key < 256);
+    win = tigrInternal(bmp);
+    return win->keys[key];
+}
+
+int tigrKeyHeld(Tigr* bmp, int key) {
+    return tigrKeyDown(bmp, key);
+}
+
+int tigrReadChar(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    int c = win->lastChar;
+    win->lastChar = 0;
+    return c;
+}
+
+extern void* _tigrReadFile(const char* fileName, int* length);
+
+void* tigrReadFile(const char* fileName, int* length) {
+    id mainBundle = objc_msgSend_id(class("NSBundle"), sel("mainBundle"));
+    id resourcePath = objc_msgSend_id(mainBundle, sel("resourcePath"));
+    resourcePath = joinNSStrings(resourcePath, makeNSString("/"));
+    resourcePath = joinNSStrings(resourcePath, makeNSString(fileName));
+    return _tigrReadFile(UTF8StringFromNSString(resourcePath), length);
+}
+
+#endif  // __IOS__
+
+#endif  // #ifndef TIGR_HEADLESS

+ 685 - 0
tigr-master/src/tigr_linux.c

@@ -0,0 +1,685 @@
+#ifndef TIGR_HEADLESS
+
+#include "tigr_internal.h"
+
+#if __linux__ && !__ANDROID__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdarg.h>
+#include <sys/time.h>
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#include <X11/Xlocale.h>
+#include <X11/XKBlib.h>
+#include <X11/Xatom.h>
+#include <GL/glx.h>
+
+static Display* dpy;
+static Window root;
+static XVisualInfo* vi;
+static Atom wmDeleteMessage;
+static XIM inputMethod;
+static GLXFBConfig fbConfig;
+
+static PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = 0;
+
+static void tearDownX11Stuff() {
+    XCloseDisplay(dpy);
+}
+
+static void initX11Stuff() {
+    static int done = 0;
+    if (!done) {
+        dpy = XOpenDisplay(NULL);
+        if (dpy == NULL) {
+            tigrError(0, "Cannot connect to X server");
+        }
+
+        root = DefaultRootWindow(dpy);
+
+        static int attribList[] = { GLX_RENDER_TYPE,
+                                    GLX_RGBA_BIT,
+                                    GLX_DRAWABLE_TYPE,
+                                    GLX_WINDOW_BIT,
+                                    GLX_DOUBLEBUFFER,
+                                    1,
+                                    GLX_RED_SIZE,
+                                    1,
+                                    GLX_GREEN_SIZE,
+                                    1,
+                                    GLX_BLUE_SIZE,
+                                    1,
+                                    None };
+
+        int fbcCount = 0;
+        GLXFBConfig* fbc = glXChooseFBConfig(dpy, DefaultScreen(dpy), attribList, &fbcCount);
+        if (!fbc) {
+            tigrError(0, "Failed to choose FB config");
+        }
+        fbConfig = fbc[0];
+        XFree(fbc);
+
+        vi = glXGetVisualFromFBConfig(dpy, fbConfig);
+        if (vi == NULL) {
+            tigrError(0, "No appropriate visual found");
+        }
+
+        GLXContext tmpCtx = glXCreateContext(dpy, vi, 0, GL_TRUE);
+        glXCreateContextAttribsARB =
+            (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddressARB((const GLubyte*)"glXCreateContextAttribsARB");
+        glXDestroyContext(dpy, tmpCtx);
+        if (!glXCreateContextAttribsARB) {
+            tigrError(0, "Failed to get glXCreateContextAttribsARB");
+        }
+
+        inputMethod = XOpenIM(dpy, NULL, NULL, NULL);
+        if (inputMethod == NULL) {
+            tigrError(0, "Failed to create input method");
+        }
+
+        wmDeleteMessage = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
+
+        atexit(tearDownX11Stuff);
+
+        done = 1;
+    }
+}
+
+static int hasGLXExtension(Display* display, const char* wanted) {
+    const char* extensions = glXQueryExtensionsString(display, DefaultScreen(display));
+    char* dup = strdup(extensions);
+    char* found = 0;
+
+    for (char* start = dup;; start = 0) {
+        found = strtok(start, " ");
+        if (found == 0 || strcmp(found, wanted) == 0) {
+            break;
+        }
+    }
+
+    free(dup);
+    return found != 0;
+}
+
+static void setupVSync(Display* display, Window win) {
+    if (hasGLXExtension(display, "GLX_EXT_swap_control")) {
+        PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT =
+            (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddressARB((const GLubyte*)"glXSwapIntervalEXT");
+        if (glXSwapIntervalEXT) {
+            glXSwapIntervalEXT(display, win, 1);
+        }
+    } else if (hasGLXExtension(display, "GLX_MESA_swap_control")) {
+        PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA =
+            (PFNGLXSWAPINTERVALMESAPROC)glXGetProcAddressARB((const GLubyte*)"glXSwapIntervalMESA");
+        if (glXSwapIntervalMESA) {
+            glXSwapIntervalMESA(1);
+        }
+    } else if (hasGLXExtension(display, "GLX_SGI_swap_control")) {
+        PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI =
+            (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB((const GLubyte*)"glXSwapIntervalSGI");
+        if (glXSwapIntervalSGI) {
+            glXSwapIntervalSGI(1);
+        }
+    }
+}
+
+static void tigrHideCursor(TigrInternal* win) {
+    Cursor invisibleCursor;
+    Pixmap bitmapNoData;
+    XColor black;
+    static char noData[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+    black.red = black.green = black.blue = 0;
+
+    bitmapNoData = XCreateBitmapFromData(win->dpy, win->win, noData, 8, 8);
+    invisibleCursor = XCreatePixmapCursor(win->dpy, bitmapNoData, bitmapNoData, &black, &black, 0, 0);
+    XDefineCursor(win->dpy, win->win, invisibleCursor);
+    XFreeCursor(win->dpy, invisibleCursor);
+    XFreePixmap(win->dpy, bitmapNoData);
+}
+
+typedef struct {
+    unsigned long flags;
+    unsigned long functions;
+    unsigned long decorations;
+    long inputMode;
+    unsigned long status;
+} WindowHints;
+
+Tigr* tigrWindow(int w, int h, const char* title, int flags) {
+    Tigr* bmp = 0;
+    Colormap cmap;
+    XSetWindowAttributes swa;
+    Window xwin;
+    GLXContext glc;
+    XIC ic;
+    int scale;
+
+    initX11Stuff();
+
+    if (flags & TIGR_AUTO) {
+        // Always use a 1:1 pixel size, unless downscaled by tigrEnforceScale below.
+        scale = 1;
+    } else {
+        // See how big we can make it and still fit on-screen.
+        Screen* screen = DefaultScreenOfDisplay(dpy);
+        int maxW = WidthOfScreen(screen);
+        int maxH = HeightOfScreen(screen);
+        scale = tigrCalcScale(w, h, maxW, maxH);
+    }
+
+    scale = tigrEnforceScale(scale, flags);
+
+    cmap = XCreateColormap(dpy, root, vi->visual, AllocNone);
+    swa.colormap = cmap;
+    swa.event_mask = StructureNotifyMask;
+
+    // Create window of wanted size
+    xwin = XCreateWindow(dpy, root, 0, 0, w * scale, h * scale, 0, vi->depth, InputOutput, vi->visual,
+                         CWColormap | CWEventMask, &swa);
+    XMapWindow(dpy, xwin);
+
+    if (flags & TIGR_FULLSCREEN) {
+        // https://superuser.com/questions/1680077/does-x11-actually-have-a-native-fullscreen-mode
+        Atom wm_state = XInternAtom(dpy, "_NET_WM_STATE", true);
+        Atom wm_fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", true);
+        XChangeProperty(dpy, xwin, wm_state, XA_ATOM, 32, PropModeReplace, (unsigned char*)&wm_fullscreen, 1);
+    } else {
+        // Wait for window to get mapped
+        for (;;) {
+            XEvent e;
+            XNextEvent(dpy, &e);
+            if (e.type == MapNotify) {
+                break;
+            }
+        }
+
+        // Reset size if we did not get the window size we wanted above.
+        XWindowAttributes wa;
+        XGetWindowAttributes(dpy, xwin, &wa);
+        scale = tigrCalcScale(w, h, wa.width, wa.height);
+        scale = tigrEnforceScale(scale, flags);
+        XResizeWindow(dpy, xwin, w * scale, h * scale);
+    }
+
+    // Enable mouse events reporting (needed for mouse wheel events)
+    XSelectInput(dpy, xwin, ButtonPressMask);
+
+    XTextProperty prop;
+    int result = Xutf8TextListToTextProperty(dpy, (char**)&title, 1, XUTF8StringStyle, &prop);
+    if (result == Success) {
+        Atom wmName = XInternAtom(dpy, "_NET_WM_NAME", 0);
+        XSetTextProperty(dpy, xwin, &prop, wmName);
+        XFree(prop.value);
+    }
+
+    ic = XCreateIC(inputMethod, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, xwin, NULL);
+    if (ic == NULL) {
+        printf("Failed to create input context\n");
+        exit(0);
+    }
+    XSetICFocus(ic);
+
+    XSetWMProtocols(dpy, xwin, &wmDeleteMessage, 1);
+
+    int contextAttributes[] = { GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MINOR_VERSION_ARB, 3, None };
+    glc = glXCreateContextAttribsARB(dpy, fbConfig, NULL, GL_TRUE, contextAttributes);
+    glXMakeCurrent(dpy, xwin, glc);
+
+    setupVSync(dpy, xwin);
+
+    bmp = tigrBitmap2(w, h, sizeof(TigrInternal));
+    bmp->handle = (void*)xwin;
+
+    TigrInternal* win = tigrInternal(bmp);
+    win->win = xwin;
+    win->dpy = dpy;
+    win->glc = glc;
+    win->ic = ic;
+
+    win->shown = 0;
+    win->closed = 0;
+    win->scale = scale;
+
+    win->lastChar = 0;
+    win->flags = flags;
+    win->p1 = win->p2 = win->p3 = 0;
+    win->p4 = 1;
+    win->widgetsWanted = 0;
+    win->widgetAlpha = 0;
+    win->widgetsScale = 0;
+    win->widgets = 0;
+    win->gl.gl_legacy = 0;
+
+    memset(win->keys, 0, 256);
+    memset(win->prev, 0, 256);
+
+    if (flags & TIGR_NOCURSOR) {
+        tigrHideCursor(win);
+    }
+
+    tigrPosition(bmp, win->scale, bmp->w, bmp->h, win->pos);
+    tigrGAPICreate(bmp);
+    tigrGAPIBegin(bmp);
+
+    return bmp;
+}
+
+int tigrClosed(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    return win->win == 0;
+}
+
+int tigrGAPIBegin(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    return glXMakeCurrent(win->dpy, win->win, win->glc) ? 0 : -1;
+}
+
+int tigrGAPIEnd(Tigr* bmp) {
+    (void)bmp;
+    return glXMakeCurrent(NULL, 0, 0) ? 0 : -1;
+}
+
+int tigrKeyDown(Tigr* bmp, int key) {
+    TigrInternal* win;
+    assert(key < 256);
+    win = tigrInternal(bmp);
+    return win->keys[key] && !win->prev[key];
+}
+
+int tigrKeyHeld(Tigr* bmp, int key) {
+    TigrInternal* win;
+    assert(key < 256);
+    win = tigrInternal(bmp);
+    return win->keys[key];
+}
+
+int tigrReadChar(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    int c = win->lastChar;
+    win->lastChar = 0;
+    return c;
+}
+
+uint8_t tigrKeyFromX11(KeySym sym) {
+    if (sym >= 'a' && sym <= 'z') {
+        return (uint8_t)sym - ('a' - 'A');
+    }
+
+    if (sym >= '0' && sym <= '9') {
+        return (uint8_t)sym;
+    }
+
+    switch (sym) {
+        case XK_KP_0:
+            return TK_PAD0;
+        case XK_KP_1:
+            return TK_PAD1;
+        case XK_KP_2:
+            return TK_PAD2;
+        case XK_KP_3:
+            return TK_PAD3;
+        case XK_KP_4:
+            return TK_PAD4;
+        case XK_KP_5:
+            return TK_PAD5;
+        case XK_KP_6:
+            return TK_PAD6;
+        case XK_KP_7:
+            return TK_PAD7;
+        case XK_KP_8:
+            return TK_PAD8;
+        case XK_KP_9:
+            return TK_PAD9;
+
+        case XK_KP_Multiply:
+            return TK_PADMUL;
+        case XK_KP_Divide:
+            return TK_PADDIV;
+        case XK_KP_Add:
+            return TK_PADADD;
+        case XK_KP_Subtract:
+            return TK_PADSUB;
+        case XK_KP_Decimal:
+            return TK_PADDOT;
+        case XK_KP_Enter:
+            return TK_PADENTER;
+
+        case XK_F1:
+            return TK_F1;
+        case XK_F2:
+            return TK_F2;
+        case XK_F3:
+            return TK_F3;
+        case XK_F4:
+            return TK_F4;
+        case XK_F5:
+            return TK_F5;
+        case XK_F6:
+            return TK_F6;
+        case XK_F7:
+            return TK_F7;
+        case XK_F8:
+            return TK_F8;
+        case XK_F9:
+            return TK_F9;
+        case XK_F10:
+            return TK_F10;
+        case XK_F11:
+            return TK_F11;
+        case XK_F12:
+            return TK_F12;
+
+        case XK_BackSpace:
+            return TK_BACKSPACE;
+        case XK_Tab:
+            return TK_TAB;
+        case XK_Return:
+            return TK_RETURN;
+        case XK_Pause:
+            return TK_PAUSE;
+        case XK_Caps_Lock:
+            return TK_CAPSLOCK;
+        case XK_Escape:
+            return TK_ESCAPE;
+        case XK_space:
+            return TK_SPACE;
+
+        case XK_Page_Up:
+            return TK_PAGEUP;
+        case XK_Page_Down:
+            return TK_PAGEDN;
+        case XK_End:
+            return TK_END;
+        case XK_Home:
+            return TK_HOME;
+        case XK_Left:
+            return TK_LEFT;
+        case XK_Up:
+            return TK_UP;
+        case XK_Right:
+            return TK_RIGHT;
+        case XK_Down:
+            return TK_DOWN;
+        case XK_Insert:
+            return TK_INSERT;
+        case XK_Delete:
+            return TK_DELETE;
+
+        case XK_Meta_L:
+            return TK_LWIN;
+        case XK_Meta_R:
+            return TK_RWIN;
+        case XK_Num_Lock:
+            return TK_NUMLOCK;
+        case XK_Scroll_Lock:
+            return TK_SCROLL;
+        case XK_Shift_L:
+            return TK_LSHIFT;
+        case XK_Shift_R:
+            return TK_RSHIFT;
+        case XK_Control_L:
+            return TK_LCONTROL;
+        case XK_Control_R:
+            return TK_RCONTROL;
+        case XK_Alt_L:
+            return TK_LALT;
+        case XK_Alt_R:
+            return TK_RALT;
+
+        case XK_semicolon:
+            return TK_SEMICOLON;
+        case XK_equal:
+            return TK_EQUALS;
+        case XK_comma:
+            return TK_COMMA;
+        case XK_minus:
+            return TK_MINUS;
+        case XK_period:
+            return TK_DOT;
+        case XK_slash:
+            return TK_SLASH;
+        case XK_grave:
+            return TK_BACKTICK;
+        case XK_bracketleft:
+            return TK_LSQUARE;
+        case XK_backslash:
+            return TK_BACKSLASH;
+        case XK_bracketright:
+            return TK_RSQUARE;
+        case XK_apostrophe:
+            return TK_TICK;
+    }
+    return 0;
+}
+
+static void tigrUpdateModifiers(TigrInternal* win) {
+    win->keys[TK_SHIFT] = win->keys[TK_LSHIFT] || win->keys[TK_RSHIFT];
+    win->keys[TK_CONTROL] = win->keys[TK_LCONTROL] || win->keys[TK_RCONTROL];
+    win->keys[TK_ALT] = win->keys[TK_LALT] || win->keys[TK_RALT];
+}
+
+static void tigrInterpretChar(TigrInternal* win, Window root, unsigned int keycode, unsigned int mask) {
+    XKeyEvent event;
+    memset(&event, 0, sizeof(event));
+    event.type = KeyPress;
+    event.display = win->dpy;
+    event.root = root;
+    event.window = win->win;
+    event.state = mask;
+    event.keycode = keycode;
+    char inputTextUTF8[10];
+    Status status = 0;
+    Xutf8LookupString(win->ic, &event, inputTextUTF8, sizeof(inputTextUTF8), NULL, &status);
+
+    if (status == XLookupChars) {
+        tigrDecodeUTF8(inputTextUTF8, &win->lastChar);
+    }
+}
+
+static void tigrProcessInput(TigrInternal* win, int winWidth, int winHeight) {
+    {
+        Window focused;
+        int revertTo;
+        XGetInputFocus(win->dpy, &focused, &revertTo);
+
+        if (win->win != focused) {
+            return;
+        }
+    }
+
+    Window root;
+    Window child;
+    int rootX;
+    int rootY;
+    int winX;
+    int winY;
+    unsigned int mask;
+
+    if (XQueryPointer(win->dpy, win->win, &root, &child, &rootX, &rootY, &winX, &winY, &mask)) {
+        static unsigned int prevButtons;
+        unsigned int buttons = mask & (Button1Mask | Button2Mask | Button3Mask);
+
+        win->mouseX = (winX - win->pos[0]) / win->scale;
+        win->mouseY = (winY - win->pos[1]) / win->scale;
+
+        if (buttons != prevButtons && (winX > 0 && winX < winWidth) && (winY > 0 && winY < winHeight)) {
+            win->mouseButtons = (buttons & Button1Mask)       ? 1
+                                : 0 | (buttons & Button3Mask) ? 2
+                                : 0 | (buttons & Button2Mask) ? 4
+                                                              : 0;
+        }
+        prevButtons = buttons;
+    }
+
+    // Retrieve mouse wheel events - Cannot be done in the XQueryPointer call
+    // -> https://comp.unix.programmer.narkive.com/eOnjkQ6L/xlib-xquerypointer-and-button4mask-button5mask#post2
+    // Button4 = WheelUp / Button5 = WheelDown
+    XEvent mouseButtonEvent;
+    while (XPending(win->dpy)) {
+        XPeekEvent(win->dpy, &mouseButtonEvent);
+        if (mouseButtonEvent.xany.type == ButtonPress) {
+            switch (mouseButtonEvent.xbutton.button) {
+                case Button4:
+                    win->scrollDeltaY += 1;
+                    break;
+                case Button5:
+                    win->scrollDeltaY -= 1;
+                    break;
+                case 6:
+                    win->scrollDeltaX += 1;
+                    break;
+                case 7:
+                    win->scrollDeltaX -= 1;
+                    break;
+            }
+            XNextEvent(win->dpy, &mouseButtonEvent);
+        } else {
+            break;
+        }
+    }
+
+    static char prevKeys[32];
+    char keys[32];
+    XQueryKeymap(win->dpy, keys);
+    for (int i = 0; i < 32; i++) {
+        char thisBlock = keys[i];
+        char prevBlock = prevKeys[i];
+        if (thisBlock != prevBlock) {
+            for (int j = 0; j < 8; j++) {
+                int thisBit = thisBlock & 1;
+                int prevBit = prevBlock & 1;
+                thisBlock >>= 1;
+                prevBlock >>= 1;
+                if (thisBit != prevBit) {
+                    int keyCode = 8 * i + j;
+                    KeySym keySym = XkbKeycodeToKeysym(win->dpy, keyCode, 0, 0);
+                    if (keySym != NoSymbol) {
+                        int key = tigrKeyFromX11(keySym);
+                        win->keys[key] = thisBit;
+                        tigrUpdateModifiers(win);
+
+                        if (thisBit) {
+                            tigrInterpretChar(win, root, keyCode, mask);
+                        }
+                    }
+                }
+            }
+        }
+    }
+    memcpy(prevKeys, keys, 32);
+
+    XEvent event;
+    while (XCheckTypedWindowEvent(win->dpy, win->win, ClientMessage, &event)) {
+        if (event.xclient.data.l[0] == wmDeleteMessage) {
+            glXMakeCurrent(win->dpy, None, NULL);
+            glXDestroyContext(win->dpy, win->glc);
+            XDestroyWindow(win->dpy, win->win);
+            win->win = 0;
+        }
+    }
+    XFlush(win->dpy);
+}
+
+void tigrUpdate(Tigr* bmp) {
+    XWindowAttributes gwa;
+
+    TigrInternal* win = tigrInternal(bmp);
+
+    memcpy(win->prev, win->keys, 256);
+    win->scrollDeltaX = 0;
+    win->scrollDeltaY = 0;
+
+    XGetWindowAttributes(win->dpy, win->win, &gwa);
+
+    if (win->flags & TIGR_AUTO)
+        tigrResize(bmp, gwa.width / win->scale, gwa.height / win->scale);
+    else
+        win->scale = tigrEnforceScale(tigrCalcScale(bmp->w, bmp->h, gwa.width, gwa.height), win->flags);
+
+    tigrPosition(bmp, win->scale, gwa.width, gwa.height, win->pos);
+    glXMakeCurrent(win->dpy, win->win, win->glc);
+    tigrGAPIPresent(bmp, gwa.width, gwa.height);
+    glXSwapBuffers(win->dpy, win->win);
+
+    tigrProcessInput(win, gwa.width, gwa.height);
+}
+
+void tigrFree(Tigr* bmp) {
+    if (bmp->handle) {
+        TigrInternal* win = tigrInternal(bmp);
+        if (win->win) {
+            glXMakeCurrent(win->dpy, None, NULL);
+            glXDestroyContext(win->dpy, win->glc);
+            XDestroyWindow(win->dpy, win->win);
+            win->win = 0;
+        }
+    }
+    free(bmp->pix);
+    free(bmp);
+}
+
+void tigrError(Tigr* bmp, const char* message, ...) {
+    char tmp[1024];
+
+    va_list args;
+    va_start(args, message);
+    vsnprintf(tmp, sizeof(tmp), message, args);
+    tmp[sizeof(tmp) - 1] = 0;
+    va_end(args);
+
+    printf("tigr fatal error: %s\n", tmp);
+
+    exit(1);
+}
+
+float tigrTime() {
+    static double lastTime = 0;
+
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+
+    double now = (double)tv.tv_sec + (tv.tv_usec / 1000000.0);
+    double elapsed = lastTime == 0 ? 0 : now - lastTime;
+    lastTime = now;
+
+    return (float)elapsed;
+}
+
+void tigrMouse(Tigr* bmp, int* x, int* y, int* buttons) {
+    TigrInternal* win = tigrInternal(bmp);
+    if (x) {
+        *x = win->mouseX;
+    }
+    if (y) {
+        *y = win->mouseY;
+    }
+    if (buttons) {
+        *buttons = win->mouseButtons;
+    }
+}
+
+int tigrTouch(Tigr* bmp, TigrTouchPoint* points, int maxPoints) {
+    int buttons = 0;
+    if (maxPoints > 0) {
+        tigrMouse(bmp, &points->x, &points->y, &buttons);
+    }
+    return buttons ? 1 : 0;
+}
+
+void tigrScrollWheel(Tigr* bmp, float* x, float* y) {
+    TigrInternal* win;
+    win = tigrInternal(bmp);
+    if (x) {
+        *x = win->scrollDeltaX;
+    }
+    if (y) {
+        *y = win->scrollDeltaY;
+    }
+}
+
+#endif  // __linux__ && !__ANDROID__
+
+#endif  // #ifndef TIGR_HEADLESS

+ 289 - 0
tigr-master/src/tigr_loadpng.c

@@ -0,0 +1,289 @@
+#include "tigr_internal.h"
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+typedef struct {
+    const unsigned char *p, *end;
+} PNG;
+
+static unsigned get32(const unsigned char* v) {
+    return (v[0] << 24) | (v[1] << 16) | (v[2] << 8) | v[3];
+}
+
+static const unsigned char* find(PNG* png, const char* chunk, unsigned minlen) {
+    const unsigned char* start;
+    while (png->p < png->end) {
+        unsigned len = get32(png->p + 0);
+        start = png->p;
+        png->p += len + 12;
+        if (memcmp(start + 4, chunk, 4) == 0 && len >= minlen && png->p <= png->end)
+            return start + 8;
+    }
+
+    return NULL;
+}
+
+static unsigned char paeth(unsigned char a, unsigned char b, unsigned char c) {
+    int p = a + b - c;
+    int pa = abs(p - a), pb = abs(p - b), pc = abs(p - c);
+    return (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c;
+}
+
+static int rowBytes(int w, int bipp) {
+    int rowBits = w * bipp;
+    return rowBits / 8 + ((rowBits % 8) ? 1 : 0);
+}
+
+static int unfilter(int w, int h, int bipp, unsigned char* raw) {
+    int len = rowBytes(w, bipp);
+    int bpp = rowBytes(1, bipp);
+    int x, y;
+    unsigned char* first = (unsigned char*)malloc(len + 1);
+    memset(first, 0, len + 1);
+    unsigned char* prev = first;
+    for (y = 0; y < h; y++, prev = raw, raw += len) {
+#define LOOP(A, B)            \
+    for (x = 0; x < bpp; x++) \
+        raw[x] += A;          \
+    for (; x < len; x++)      \
+        raw[x] += B;          \
+    break
+        switch (*raw++) {
+            case 0:
+                break;
+            case 1:
+                LOOP(0, raw[x - bpp]);
+            case 2:
+                LOOP(prev[x], prev[x]);
+            case 3:
+                LOOP(prev[x] / 2, (raw[x - bpp] + prev[x]) / 2);
+            case 4:
+                LOOP(prev[x], paeth(raw[x - bpp], prev[x], prev[x - bpp]));
+            default:
+                return 0;
+        }
+#undef LOOP
+    }
+    free(first);
+    return 1;
+}
+
+static void convert(int bypp, int w, int h, const unsigned char* src, TPixel* dest, const unsigned char* trns) {
+    int x, y;
+    for (y = 0; y < h; y++) {
+        src++;  // skip filter byte
+        for (x = 0; x < w; x++, src += bypp) {
+            switch (bypp) {
+                case 1: {
+                    unsigned char c = src[0];
+                    if (trns && c == *trns) {
+                        *dest++ = tigrRGBA(c, c, c, 0);
+                        break;
+                    } else {
+                        *dest++ = tigrRGB(c, c, c);
+                        break;
+                    }
+                }
+                case 2:
+                    *dest++ = tigrRGBA(src[0], src[0], src[0], src[1]);
+                    break;
+                case 3: {
+                    unsigned char r = src[0];
+                    unsigned char g = src[1];
+                    unsigned char b = src[2];
+                    if (trns && trns[1] == r && trns[3] == g && trns[5] == b) {
+                        *dest++ = tigrRGBA(r, g, b, 0);
+                        break;
+                    } else {
+                        *dest++ = tigrRGB(r, g, b);
+                        break;
+                    }
+                }
+                case 4:
+                    *dest++ = tigrRGBA(src[0], src[1], src[2], src[3]);
+                    break;
+            }
+        }
+    }
+}
+
+static void depalette(int w,
+                      int h,
+                      unsigned char* src,
+                      TPixel* dest,
+                      int bipp,
+                      const unsigned char* plte,
+                      const unsigned char* trns,
+                      int trnsSize) {
+    int x, y, c;
+    unsigned char alpha;
+    int mask = 0, len = 0;
+
+    switch (bipp) {
+        case 4:
+            mask = 15;
+            len = 1;
+            break;
+        case 2:
+            mask = 3;
+            len = 3;
+            break;
+        case 1:
+            mask = 1;
+            len = 7;
+    }
+
+    for (y = 0; y < h; y++) {
+        src++;  // skip filter byte
+        for (x = 0; x < w; x++) {
+            if (bipp == 8) {
+                c = *src++;
+            } else {
+                int pos = x & len;
+                c = (src[0] >> ((len - pos) * bipp)) & mask;
+                if (pos == len) {
+                    src++;
+                }
+            }
+            alpha = 255;
+            if (c < trnsSize) {
+                alpha = trns[c];
+            }
+            *dest++ = tigrRGBA(plte[c * 3 + 0], plte[c * 3 + 1], plte[c * 3 + 2], alpha);
+        }
+    }
+}
+
+#define FAIL()          \
+    {                   \
+        errno = EINVAL; \
+        goto err;       \
+    }
+#define CHECK(X) \
+    if (!(X))    \
+    FAIL()
+
+static int outsize(Tigr* bmp, int bipp) {
+    return (rowBytes(bmp->w, bipp) + 1) * bmp->h;
+}
+
+static Tigr* tigrLoadPng(PNG* png) {
+    const unsigned char *ihdr, *idat, *plte, *trns, *first;
+    int trnsSize = 0;
+    int depth, ctype, bipp;
+    int datalen = 0;
+    unsigned char *data = NULL, *out;
+    Tigr* bmp = NULL;
+
+    CHECK(memcmp(png->p, "\211PNG\r\n\032\n", 8) == 0);  // PNG signature
+    png->p += 8;
+    first = png->p;
+
+    // Read IHDR
+    ihdr = find(png, "IHDR", 13);
+    CHECK(ihdr);
+    depth = ihdr[8];
+    ctype = ihdr[9];
+    switch (ctype) {
+        case 0:
+            bipp = depth;
+            break;  // greyscale
+        case 2:
+            bipp = 3 * depth;
+            break;  // RGB
+        case 3:
+            bipp = depth;
+            break;  // paletted
+        case 4:
+            bipp = 2 * depth;
+            break;  // grey+alpha
+        case 6:
+            bipp = 4 * depth;
+            break;  // RGBA
+        default:
+            FAIL();
+    }
+
+    // Allocate bitmap (+1 width to save room for stupid PNG filter bytes)
+    bmp = tigrBitmap(get32(ihdr + 0) + 1, get32(ihdr + 4));
+    CHECK(bmp);
+    bmp->w--;
+
+    // We support 8-bit color components and 1, 2, 4 and 8 bit palette formats.
+    // No interlacing, or wacky filter types.
+    CHECK((depth != 16) && ihdr[10] == 0 && ihdr[11] == 0 && ihdr[12] == 0);
+
+    // Join IDAT chunks.
+    for (idat = find(png, "IDAT", 0); idat; idat = find(png, "IDAT", 0)) {
+        unsigned len = get32(idat - 8);
+        data = (unsigned char*)realloc(data, datalen + len);
+        if (!data)
+            break;
+
+        memcpy(data + datalen, idat, len);
+        datalen += len;
+    }
+
+    // Find palette.
+    png->p = first;
+    plte = find(png, "PLTE", 0);
+
+    // Find transparency info.
+    png->p = first;
+    trns = find(png, "tRNS", 0);
+    if (trns) {
+        trnsSize = get32(trns - 8);
+    }
+
+    CHECK(data && datalen >= 6);
+    CHECK((data[0] & 0x0f) == 0x08     // compression method (RFC 1950)
+          && (data[0] & 0xf0) <= 0x70  // window size
+          && (data[1] & 0x20) == 0);   // preset dictionary present
+
+    out = (unsigned char*)bmp->pix + outsize(bmp, 32) - outsize(bmp, bipp);
+    CHECK(tigrInflate(out, outsize(bmp, bipp), data + 2, datalen - 6));
+    CHECK(unfilter(bmp->w, bmp->h, bipp, out));
+
+    if (ctype == 3) {
+        CHECK(plte);
+        depalette(bmp->w, bmp->h, out, bmp->pix, bipp, plte, trns, trnsSize);
+    } else {
+        CHECK(bipp % 8 == 0);
+        convert(bipp / 8, bmp->w, bmp->h, out, bmp->pix, trns);
+    }
+
+    free(data);
+    return bmp;
+
+err:
+    if (data)
+        free(data);
+    if (bmp)
+        tigrFree(bmp);
+    return NULL;
+}
+
+#undef CHECK
+#undef FAIL
+
+Tigr* tigrLoadImageMem(const void* data, int length) {
+    PNG png;
+    png.p = (unsigned char*)data;
+    png.end = (unsigned char*)data + length;
+    return tigrLoadPng(&png);
+}
+
+Tigr* tigrLoadImage(const char* fileName) {
+    int len;
+    void* data;
+    Tigr* bmp;
+
+    data = tigrReadFile(fileName, &len);
+    if (!data)
+        return NULL;
+
+    bmp = tigrLoadImageMem(data, len);
+    free(data);
+    return bmp;
+}

+ 54 - 0
tigr-master/src/tigr_objc.h

@@ -0,0 +1,54 @@
+#ifndef TIGR_OBJC_H
+#define TIGR_OBJC_H
+
+#if defined(__IOS__) || defined(__MACOS__)
+
+#if defined(__OBJC__) && __has_feature(objc_arc)
+#error "Can't compile as objective-c code!"
+#endif
+
+// ABI is a bit different between platforms
+#ifdef __arm64__
+#define abi_objc_msgSend_stret objc_msgSend
+#else
+#define abi_objc_msgSend_stret objc_msgSend_stret
+#endif
+#ifdef __i386__
+#define abi_objc_msgSend_fpret objc_msgSend_fpret
+#else
+#define abi_objc_msgSend_fpret objc_msgSend
+#endif
+
+#define objc_msgSendSuper_t(RET, ...) ((RET(*)(struct objc_super*, SEL, ##__VA_ARGS__))objc_msgSendSuper)
+#define objc_msgSend_t(RET, ...) ((RET(*)(id, SEL, ##__VA_ARGS__))objc_msgSend)
+#define objc_msgSend_stret_t(RET, ...) ((RET(*)(id, SEL, ##__VA_ARGS__))abi_objc_msgSend_stret)
+#define objc_msgSend_id objc_msgSend_t(id)
+#define objc_msgSend_void objc_msgSend_t(void)
+#define objc_msgSend_void_id objc_msgSend_t(void, id)
+#define objc_msgSend_void_bool objc_msgSend_t(void, bool)
+
+#define sel(NAME) sel_registerName(NAME)
+#define class(NAME) ((id)objc_getClass(NAME))
+#define makeClass(NAME, SUPER) objc_allocateClassPair((Class)objc_getClass(SUPER), NAME, 0)
+
+// Check here to get the signature right: https://nshipster.com/type-encodings/
+#define addMethod(CLASS, NAME, IMPL, SIGNATURE)                       \
+    if (!class_addMethod(CLASS, sel(NAME), (IMP)(IMPL), (SIGNATURE))) \
+    assert(false)
+
+#define addIvar(CLASS, NAME, SIZE, SIGNATURE)                           \
+    if (!class_addIvar(CLASS, NAME, SIZE, rint(log2(SIZE)), SIGNATURE)) \
+    assert(false)
+
+#define objc_alloc(CLASS) objc_msgSend_id(class(CLASS), sel("alloc"))
+
+#if __LP64__ || NS_BUILD_32_LIKE_64
+#define NSIntegerEncoding "q"
+#define NSUIntegerEncoding "L"
+#else
+#define NSIntegerEncoding "i"
+#define NSUIntegerEncoding "I"
+#endif
+
+#endif  // defined(__IOS__) || defined (__MACOS__)
+#endif  // TIGR_OBJC_H

+ 1130 - 0
tigr-master/src/tigr_osx.c

@@ -0,0 +1,1130 @@
+#ifndef TIGR_HEADLESS
+
+// originally based on https://github.com/jimon/osx_app_in_plain_c
+
+#include "tigr_internal.h"
+
+#if __MACOS__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
+#include "tigr_objc.h"
+#pragma clang diagnostic pop
+
+#include <assert.h>
+#include <limits.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreGraphics/CoreGraphics.h>
+#include <mach/mach_time.h>
+#include <objc/NSObjCRuntime.h>
+#include <objc/message.h>
+#include <objc/objc.h>
+#include <objc/runtime.h>
+
+#include <CoreGraphics/CGBase.h>
+#include <CoreGraphics/CGGeometry.h>
+
+typedef CGPoint NSPoint;
+typedef CGSize NSSize;
+typedef CGRect NSRect;
+
+enum {
+    NSKeyDown = 10,
+    NSKeyDownMask = 1 << NSKeyDown,
+    NSKeyUp = 11,
+    NSKeyUpMask = 1 << NSKeyUp,
+};
+
+NSUInteger NSAllEventMask = NSUIntegerMax;
+
+extern id NSApp;
+extern id const NSDefaultRunLoopMode;
+
+#define NSApplicationActivationPolicyRegular 0
+
+bool terminated = false;
+
+static double _tigrTimestamp = 0;
+
+double _currentMediaTime(void) {
+    uint64_t now = clock_gettime_nsec_np(CLOCK_UPTIME_RAW);
+    double current = now * 1.0E-9;
+    return current;
+}
+
+TigrInternal* _tigrInternalFromWindowDelegate(id wdg) {
+    Tigr* bmp = 0;
+    object_getInstanceVariable(wdg, "tigrHandle", (void**)&bmp);
+    return bmp ? tigrInternal(bmp) : NULL;
+}
+
+TigrInternal* _tigrInternalFromWindow(id window) {
+    if (!window)
+        return NULL;
+
+    id wdg = objc_msgSend_id(window, sel("delegate"));
+    if (!wdg)
+        return NULL;
+
+    return _tigrInternalFromWindowDelegate(wdg);
+}
+
+NSUInteger applicationShouldTerminate(id self, SEL sel, id sender) {
+    terminated = true;
+    return 0;
+}
+
+void windowWillClose(id self, SEL _sel, id notification) {
+    NSUInteger value = true;
+    object_setInstanceVariable(self, "closed", (void*)value);
+    object_setInstanceVariable(self, "tigrHandle", (void*)0);
+}
+
+void windowDidEnterFullScreen(id self, SEL _sel, id notification) {
+    NSUInteger value = true;
+    object_setInstanceVariable(self, "visible", (void*)value);
+}
+
+void windowDidResize(id self, SEL _sel, id notification) {
+    TigrInternal* win;
+    Tigr* bmp = 0;
+    object_getInstanceVariable(self, "tigrHandle", (void**)&bmp);
+    win = bmp ? tigrInternal(bmp) : NULL;
+    if (win) {
+        win->mouseButtons = 0;
+    }
+}
+
+void windowDidBecomeKey(id self, SEL _sel, id notification) {
+    TigrInternal* win;
+    Tigr* bmp = 0;
+    object_getInstanceVariable(self, "tigrHandle", (void**)&bmp);
+    win = bmp ? tigrInternal(bmp) : NULL;
+
+    if (win) {
+        memset(win->keys, 0, 256);
+        memset(win->prev, 0, 256);
+        win->lastChar = 0;
+        win->mouseButtons = 0;
+    }
+}
+
+void onFrame(id self, SEL _sel, id displayLink) {
+    double timestamp = objc_msgSend_t(double)(displayLink, sel("timestamp"));
+    TigrInternal* win = _tigrInternalFromWindowDelegate(self);
+    if (win == NULL) {
+        return;
+    }
+    win->frameTime = timestamp;
+}
+
+void mouseEntered(id self, SEL _sel, id event) {
+    id window = objc_msgSend_id(event, sel("window"));
+    TigrInternal* win = _tigrInternalFromWindow(window);
+    if (win) {
+        win->mouseInView = 1;
+        if (win->flags & TIGR_NOCURSOR) {
+            objc_msgSend_id(class("NSCursor"), sel("hide"));
+        }
+    }
+}
+
+void mouseExited(id self, SEL _sel, id event) {
+    id window = objc_msgSend_id(event, sel("window"));
+    TigrInternal* win = _tigrInternalFromWindow(window);
+    if (win) {
+        win->mouseInView = 0;
+        if (win->flags & TIGR_NOCURSOR) {
+            objc_msgSend_id(class("NSCursor"), sel("unhide"));
+        }
+    }
+}
+
+bool _tigrIsWindowClosed(id window) {
+    id wdg = objc_msgSend_id(window, sel("delegate"));
+    if (!wdg)
+        return false;
+    NSUInteger value = 0;
+    object_getInstanceVariable(wdg, "closed", (void**)&value);
+    return value ? true : false;
+}
+
+bool _tigrIsWindowVisible(id window) {
+    id wdg = objc_msgSend_id(window, sel("delegate"));
+    if (!wdg)
+        return false;
+    NSUInteger value = 0;
+    object_getInstanceVariable(wdg, "visible", (void**)&value);
+    return value ? true : false;
+}
+
+static bool tigrOSXInited = false;
+static id autoreleasePool = NULL;
+
+#ifdef DEBUG
+static void _showPools(const char* context) {
+    fprintf(stderr, "NSAutoreleasePool@%s:\n", context);
+    objc_msgSend(class("NSAutoreleasePool"), sel("showPools"));
+}
+#define showPools(x) _showPools((x))
+#else
+#define showPools(x)
+#endif
+
+static id pushPool(void) {
+    id pool = objc_msgSend_id(class("NSAutoreleasePool"), sel("alloc"));
+    return objc_msgSend_id(pool, sel("init"));
+}
+
+static void popPool(id pool) {
+    objc_msgSend_void(pool, sel("drain"));
+}
+
+void _tigrCleanupOSX(void) {
+    showPools("cleanup");
+    popPool(autoreleasePool);
+}
+
+void tigrInitOSX(void) {
+    if (tigrOSXInited)
+        return;
+
+    atexit(&_tigrCleanupOSX);
+
+    autoreleasePool = pushPool();
+
+    showPools("init start");
+
+    objc_msgSend_id(class("NSApplication"), sel("sharedApplication"));
+    objc_msgSend_t(void, NSInteger)(NSApp, sel("setActivationPolicy:"), NSApplicationActivationPolicyRegular);
+
+    Class appDelegateClass = makeClass("AppDelegate", "NSObject");
+    addMethod(appDelegateClass, "applicationShouldTerminate", applicationShouldTerminate, NSUIntegerEncoding "@:@");
+    id dgAlloc = objc_msgSend_id((id)appDelegateClass, sel("alloc"));
+    id dg = objc_msgSend_id(dgAlloc, sel("init"));
+
+    objc_msgSend_void_id(NSApp, sel("setDelegate:"), dg);
+    objc_msgSend_void(NSApp, sel("finishLaunching"));
+
+    id menuBar = objc_alloc("NSMenu");
+    menuBar = objc_msgSend_id(menuBar, sel("init"));
+
+    id appMenuItem = objc_alloc("NSMenuItem");
+    appMenuItem = objc_msgSend_id(appMenuItem, sel("init"));
+
+    objc_msgSend_void_id(menuBar, sel("addItem:"), appMenuItem);
+    objc_msgSend_t(id, id)(NSApp, sel("setMainMenu:"), menuBar);
+
+    id processInfo = objc_msgSend_id(class("NSProcessInfo"), sel("processInfo"));
+    id appName = objc_msgSend_id(processInfo, sel("processName"));
+
+    id appMenu = objc_alloc("NSMenu");
+    appMenu = objc_msgSend_t(id, id)(appMenu, sel("initWithTitle:"), appName);
+
+    id quitTitlePrefixString =
+        objc_msgSend_t(id, const char*)(class("NSString"), sel("stringWithUTF8String:"), "Quit ");
+    id quitTitle = objc_msgSend_t(id, id)(quitTitlePrefixString, sel("stringByAppendingString:"), appName);
+
+    id quitMenuItemKey = objc_msgSend_t(id, const char*)(class("NSString"), sel("stringWithUTF8String:"), "q");
+    id quitMenuItem = objc_alloc("NSMenuItem");
+    quitMenuItem = objc_msgSend_t(id, id, SEL, id)(quitMenuItem, sel("initWithTitle:action:keyEquivalent:"), quitTitle,
+                                                   sel("terminate:"), quitMenuItemKey);
+
+    objc_msgSend_void_id(appMenu, sel("addItem:"), quitMenuItem);
+    objc_msgSend_void_id(appMenuItem, sel("setSubmenu:"), appMenu);
+
+    tigrOSXInited = true;
+
+    showPools("init end");
+}
+
+void tigrError(Tigr* bmp, const char* message, ...) {
+    char tmp[1024];
+
+    va_list args;
+    va_start(args, message);
+    vsnprintf(tmp, sizeof(tmp), message, args);
+    tmp[sizeof(tmp) - 1] = 0;
+    va_end(args);
+
+    printf("tigr fatal error: %s\n", tmp);
+
+    exit(1);
+}
+
+NSSize _tigrContentBackingSize(id window) {
+    id contentView = objc_msgSend_id(window, sel("contentView"));
+    NSRect rect = objc_msgSend_stret_t(NSRect)(contentView, sel("frame"));
+    rect = objc_msgSend_stret_t(NSRect, NSRect)(contentView, sel("convertRectToBacking:"), rect);
+
+    return rect.size;
+}
+
+enum {
+    NSWindowStyleMaskTitled = 1 << 0,
+    NSWindowStyleMaskClosable = 1 << 1,
+    NSWindowStyleMaskMiniaturizable = 1 << 2,
+    NSWindowStyleMaskResizable = 1 << 3,
+    NSWindowStyleRegular = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable |
+                           NSWindowStyleMaskResizable,
+    NSWindowStyleMaskFullSizeContentView = 1 << 15
+};
+
+Tigr* tigrWindow(int w, int h, const char* title, int flags) {
+    Tigr* bmp;
+    TigrInternal* win;
+
+    tigrInitOSX();
+
+    NSUInteger windowStyleMask = NSWindowStyleRegular & ~NSWindowStyleMaskMiniaturizable;
+
+    // In AUTO mode, window follows requested size, unless downscaled by tigrEnforceScale below.
+    int windowScale = 1;
+
+    // In non-AUTO mode, see how big we can make it and still fit on-screen.
+    if ((flags & TIGR_AUTO) == 0) {
+        CGRect mainMonitor = CGDisplayBounds(CGMainDisplayID());
+        int maxW = CGRectGetWidth(mainMonitor);
+        int maxH = CGRectGetHeight(mainMonitor);
+        NSRect screen = { { 0, 0 }, { maxW, maxH } };
+        NSRect content = objc_msgSend_stret_t(NSRect, NSRect, NSUInteger)(
+            class("NSWindow"), sel("contentRectForFrameRect:styleMask:"), screen, windowStyleMask);
+        windowScale = tigrCalcScale(w, h, content.size.width, content.size.height);
+    }
+
+    windowScale = tigrEnforceScale(windowScale, flags);
+
+    NSRect rect = { { 0, 0 }, { w * windowScale, h * windowScale } };
+    id windowAlloc = objc_msgSend_id(class("NSWindow"), sel("alloc"));
+    id window = ((id(*)(id, SEL, NSRect, NSUInteger, NSUInteger, BOOL))objc_msgSend)(
+        windowAlloc, sel("initWithContentRect:styleMask:backing:defer:"), rect, windowStyleMask, 2, NO);
+
+    objc_msgSend_void_bool(window, sel("setReleasedWhenClosed:"), NO);
+
+    Class WindowDelegateClass = objc_allocateClassPair((Class)objc_getClass("NSObject"), "WindowDelegate", 0);
+    addIvar(WindowDelegateClass, "closed", sizeof(NSUInteger), NSUIntegerEncoding);
+    addIvar(WindowDelegateClass, "visible", sizeof(NSUInteger), NSUIntegerEncoding);
+    addIvar(WindowDelegateClass, "tigrHandle", sizeof(void*), "ˆv");
+    addMethod(WindowDelegateClass, "windowWillClose:", windowWillClose, "v@:@");
+    addMethod(WindowDelegateClass, "windowDidEnterFullScreen:", windowDidEnterFullScreen, "v@:@");
+    addMethod(WindowDelegateClass, "windowDidResize:", windowDidResize, "v@:@");
+    addMethod(WindowDelegateClass, "windowDidBecomeKey:", windowDidBecomeKey, "v@:@");
+    addMethod(WindowDelegateClass, "mouseEntered:", mouseEntered, "v@:@");
+    addMethod(WindowDelegateClass, "mouseExited:", mouseExited, "v@:@");
+    addMethod(WindowDelegateClass, "onFrame:", onFrame, "v@:@");
+
+    id wdgAlloc = objc_msgSend_id((id)WindowDelegateClass, sel("alloc"));
+    id wdg = objc_msgSend_id(wdgAlloc, sel("init"));
+
+    if (flags & TIGR_FULLSCREEN) {
+        objc_msgSend_void_id(window, sel("toggleFullScreen:"), window);
+        if (flags & TIGR_NOCURSOR) {
+            objc_msgSend_id(class("NSCursor"), sel("hide"));
+        }
+    } else {
+        NSUInteger value = true;
+        object_setInstanceVariable(wdg, "visible", (void*)value);
+    }
+
+    objc_msgSend_void_id(window, sel("setDelegate:"), wdg);
+
+    id contentView = objc_msgSend_id(window, sel("contentView"));
+
+    objc_msgSend_void_bool(contentView, sel("setWantsLayer:"), NO);
+
+    bool useDisplayLink = objc_msgSend_t(bool, SEL)(class("NSView"), sel("instancesRespondToSelector:"),
+                                                    sel("displayLinkWithTarget:selector:"));
+
+    if (useDisplayLink) {
+        // Use display link to pace frames, if possible
+        id link =
+            objc_msgSend_t(id, id, SEL)(contentView, sel("displayLinkWithTarget:selector:"), wdg, sel("onFrame:"));
+        id runLoop = objc_msgSend_id(class("NSRunLoop"), sel("currentRunLoop"));
+        objc_msgSend_t(void, id, id)(link, sel("addToRunLoop:forMode:"), runLoop, NSDefaultRunLoopMode);
+        objc_msgSend_void_bool(link, sel("setPaused:"), NO);
+    }
+
+    int wantsHighRes = (flags & TIGR_RETINA);
+    objc_msgSend_void_bool(contentView, sel("setWantsBestResolutionOpenGLSurface:"), wantsHighRes);
+
+    NSPoint point = { 20, 20 };
+    ((void (*)(id, SEL, NSPoint))objc_msgSend)(window, sel("cascadeTopLeftFromPoint:"), point);
+
+    id titleString = objc_msgSend_t(id, const char*)(class("NSString"), sel("stringWithUTF8String:"), title);
+    objc_msgSend_void_id(window, sel("setTitle:"), titleString);
+
+    uint32_t glAttributes[] = { 8, 24,  //	NSOpenGLPFAColorSize, 24,
+                                11, 8,  //	NSOpenGLPFAAlphaSize, 8,
+                                5,      //	NSOpenGLPFADoubleBuffer,
+                                73,     //	NSOpenGLPFAAccelerated,
+                                // 72,			//	NSOpenGLPFANoRecovery,
+                                // 55, 1,		//	NSOpenGLPFASampleBuffers, 1,
+                                // 56, 4,		//	NSOpenGLPFASamples, 4,
+                                99, 0x3200,  //	NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
+                                // 70, 0x00020400, // NSOpenGLPFARendererID, kCGLRendererGenericFloatID
+                                0 };
+
+    id pixelFormat = objc_alloc("NSOpenGLPixelFormat");
+    pixelFormat = objc_msgSend_t(id, const uint32_t*)(pixelFormat, sel("initWithAttributes:"), glAttributes);
+    objc_msgSend_void(pixelFormat, sel("autorelease"));
+
+    id openGLContext = objc_alloc("NSOpenGLContext");
+    openGLContext = objc_msgSend_t(id, id, id)(openGLContext, sel("initWithFormat:shareContext:"), pixelFormat, nil);
+
+    objc_msgSend_void_id(openGLContext, sel("setView:"), contentView);
+    objc_msgSend_void_id(window, sel("makeKeyAndOrderFront:"), window);
+    objc_msgSend_void_bool(window, sel("setAcceptsMouseMovedEvents:"), YES);
+
+    id blackColor = objc_msgSend_id(class("NSColor"), sel("blackColor"));
+    objc_msgSend_void_id(window, sel("setBackgroundColor:"), blackColor);
+
+    objc_msgSend_void_bool(NSApp, sel("activateIgnoringOtherApps:"), YES);
+
+    NSSize windowContentSize = _tigrContentBackingSize(window);
+
+    // In AUTO mode, always use a 1:1 pixel size, unless downscaled by tigrEnforceScale below.
+    int bitmapScale = 1;
+
+    // In non-AUTO mode, scale based on backing size
+    if ((flags & TIGR_AUTO) == 0) {
+        bitmapScale = tigrEnforceScale(tigrCalcScale(w, h, windowContentSize.width, windowContentSize.height), flags);
+    } else {
+        // In AUTO mode, bitmap size follows window size
+        w = windowContentSize.width / windowScale;
+        h = windowContentSize.height / windowScale;
+        bitmapScale = tigrEnforceScale(bitmapScale, flags);
+    }
+
+    bmp = tigrBitmap2(w, h, sizeof(TigrInternal));
+    bmp->handle = window;
+
+    // Set the handle
+    object_setInstanceVariable(wdg, "tigrHandle", (void*)bmp);
+
+    {
+#define NSTrackingMouseEnteredAndExited 1
+#define NSTrackingActiveInKeyWindow 0x20
+#define NSTrackingInVisibleRect 0x200
+
+        int trackingFlags = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect;
+        id trackingArea = objc_msgSend_id(class("NSTrackingArea"), sel("alloc"));
+        trackingArea = objc_msgSend_t(id, NSRect, int, id, id)(
+            trackingArea, sel("initWithRect:options:owner:userInfo:"), rect, trackingFlags, wdg, 0);
+        objc_msgSend_void_id(contentView, sel("addTrackingArea:"), trackingArea);
+    }
+
+    // Set up the Windows parts.
+    win = tigrInternal(bmp);
+    win->shown = 0;
+    win->closed = 0;
+    win->scale = bitmapScale;
+    win->lastChar = 0;
+    win->flags = flags;
+    win->p1 = win->p2 = win->p3 = 0;
+    win->p4 = 1;
+    win->widgetsWanted = 0;
+    win->widgetAlpha = 0;
+    win->widgetsScale = 0;
+    win->widgets = 0;
+    win->gl.gl_legacy = 0;
+    win->gl.glContext = openGLContext;
+    win->mouseButtons = 0;
+    win->mouseInView = 0;
+    win->frameTime = 0;
+
+    tigrPosition(bmp, win->scale, bmp->w, bmp->h, win->pos);
+
+    GLint swapInterval = 1;
+    NSInteger NSOpenGLCPSwapInterval = 222;
+
+    objc_msgSend_t(void, GLint*, NSInteger)(openGLContext, sel("setValues:forParameter:"), &swapInterval,
+                                            NSOpenGLCPSwapInterval);
+
+    objc_msgSend_void(openGLContext, sel("makeCurrentContext"));
+
+    tigrGAPICreate(bmp);
+
+    return bmp;
+}
+
+void tigrFree(Tigr* bmp) {
+    if (bmp->handle) {
+        TigrInternal* win = tigrInternal(bmp);
+        tigrGAPIDestroy(bmp);
+
+        id window = (id)bmp->handle;
+
+        if (!_tigrIsWindowClosed(window) && !terminated) {
+            objc_msgSend_void(window, sel("close"));
+        }
+
+        if (win->flags & TIGR_NOCURSOR) {
+            objc_msgSend_id(class("NSCursor"), sel("unhide"));
+        }
+
+        id wdg = objc_msgSend_id(window, sel("delegate"));
+        objc_msgSend_void(wdg, sel("release"));
+        objc_msgSend_void((id)win->gl.glContext, sel("release"));
+        objc_msgSend_void(window, sel("release"));
+    }
+    free(bmp->pix);
+    free(bmp);
+}
+
+uint8_t _tigrKeyFromOSX(uint16_t key) {
+    // from Carbon HIToolbox/Events.h
+    enum {
+        kVK_ANSI_A = 0x00,
+        kVK_ANSI_S = 0x01,
+        kVK_ANSI_D = 0x02,
+        kVK_ANSI_F = 0x03,
+        kVK_ANSI_H = 0x04,
+        kVK_ANSI_G = 0x05,
+        kVK_ANSI_Z = 0x06,
+        kVK_ANSI_X = 0x07,
+        kVK_ANSI_C = 0x08,
+        kVK_ANSI_V = 0x09,
+        kVK_ANSI_B = 0x0B,
+        kVK_ANSI_Q = 0x0C,
+        kVK_ANSI_W = 0x0D,
+        kVK_ANSI_E = 0x0E,
+        kVK_ANSI_R = 0x0F,
+        kVK_ANSI_Y = 0x10,
+        kVK_ANSI_T = 0x11,
+        kVK_ANSI_1 = 0x12,
+        kVK_ANSI_2 = 0x13,
+        kVK_ANSI_3 = 0x14,
+        kVK_ANSI_4 = 0x15,
+        kVK_ANSI_6 = 0x16,
+        kVK_ANSI_5 = 0x17,
+        kVK_ANSI_Equal = 0x18,
+        kVK_ANSI_9 = 0x19,
+        kVK_ANSI_7 = 0x1A,
+        kVK_ANSI_Minus = 0x1B,
+        kVK_ANSI_8 = 0x1C,
+        kVK_ANSI_0 = 0x1D,
+        kVK_ANSI_RightBracket = 0x1E,
+        kVK_ANSI_O = 0x1F,
+        kVK_ANSI_U = 0x20,
+        kVK_ANSI_LeftBracket = 0x21,
+        kVK_ANSI_I = 0x22,
+        kVK_ANSI_P = 0x23,
+        kVK_ANSI_L = 0x25,
+        kVK_ANSI_J = 0x26,
+        kVK_ANSI_Quote = 0x27,
+        kVK_ANSI_K = 0x28,
+        kVK_ANSI_Semicolon = 0x29,
+        kVK_ANSI_Backslash = 0x2A,
+        kVK_ANSI_Comma = 0x2B,
+        kVK_ANSI_Slash = 0x2C,
+        kVK_ANSI_N = 0x2D,
+        kVK_ANSI_M = 0x2E,
+        kVK_ANSI_Period = 0x2F,
+        kVK_ANSI_Grave = 0x32,
+        kVK_ANSI_KeypadDecimal = 0x41,
+        kVK_ANSI_KeypadMultiply = 0x43,
+        kVK_ANSI_KeypadPlus = 0x45,
+        kVK_ANSI_KeypadClear = 0x47,
+        kVK_ANSI_KeypadDivide = 0x4B,
+        kVK_ANSI_KeypadEnter = 0x4C,
+        kVK_ANSI_KeypadMinus = 0x4E,
+        kVK_ANSI_KeypadEquals = 0x51,
+        kVK_ANSI_Keypad0 = 0x52,
+        kVK_ANSI_Keypad1 = 0x53,
+        kVK_ANSI_Keypad2 = 0x54,
+        kVK_ANSI_Keypad3 = 0x55,
+        kVK_ANSI_Keypad4 = 0x56,
+        kVK_ANSI_Keypad5 = 0x57,
+        kVK_ANSI_Keypad6 = 0x58,
+        kVK_ANSI_Keypad7 = 0x59,
+        kVK_ANSI_Keypad8 = 0x5B,
+        kVK_ANSI_Keypad9 = 0x5C,
+        kVK_Return = 0x24,
+        kVK_Tab = 0x30,
+        kVK_Space = 0x31,
+        kVK_Delete = 0x33,
+        kVK_Escape = 0x35,
+        kVK_Command = 0x37,
+        kVK_Shift = 0x38,
+        kVK_CapsLock = 0x39,
+        kVK_Option = 0x3A,
+        kVK_Control = 0x3B,
+        kVK_RightShift = 0x3C,
+        kVK_RightOption = 0x3D,
+        kVK_RightControl = 0x3E,
+        kVK_Function = 0x3F,
+        kVK_F17 = 0x40,
+        kVK_VolumeUp = 0x48,
+        kVK_VolumeDown = 0x49,
+        kVK_Mute = 0x4A,
+        kVK_F18 = 0x4F,
+        kVK_F19 = 0x50,
+        kVK_F20 = 0x5A,
+        kVK_F5 = 0x60,
+        kVK_F6 = 0x61,
+        kVK_F7 = 0x62,
+        kVK_F3 = 0x63,
+        kVK_F8 = 0x64,
+        kVK_F9 = 0x65,
+        kVK_F11 = 0x67,
+        kVK_F13 = 0x69,
+        kVK_F16 = 0x6A,
+        kVK_F14 = 0x6B,
+        kVK_F10 = 0x6D,
+        kVK_F12 = 0x6F,
+        kVK_F15 = 0x71,
+        kVK_Help = 0x72,
+        kVK_Home = 0x73,
+        kVK_PageUp = 0x74,
+        kVK_ForwardDelete = 0x75,
+        kVK_F4 = 0x76,
+        kVK_End = 0x77,
+        kVK_F2 = 0x78,
+        kVK_PageDown = 0x79,
+        kVK_F1 = 0x7A,
+        kVK_LeftArrow = 0x7B,
+        kVK_RightArrow = 0x7C,
+        kVK_DownArrow = 0x7D,
+        kVK_UpArrow = 0x7E
+    };
+
+    switch (key) {
+        case kVK_ANSI_Q:
+            return 'Q';
+        case kVK_ANSI_W:
+            return 'W';
+        case kVK_ANSI_E:
+            return 'E';
+        case kVK_ANSI_R:
+            return 'R';
+        case kVK_ANSI_T:
+            return 'T';
+        case kVK_ANSI_Y:
+            return 'Y';
+        case kVK_ANSI_U:
+            return 'U';
+        case kVK_ANSI_I:
+            return 'I';
+        case kVK_ANSI_O:
+            return 'O';
+        case kVK_ANSI_P:
+            return 'P';
+        case kVK_ANSI_A:
+            return 'A';
+        case kVK_ANSI_S:
+            return 'S';
+        case kVK_ANSI_D:
+            return 'D';
+        case kVK_ANSI_F:
+            return 'F';
+        case kVK_ANSI_G:
+            return 'G';
+        case kVK_ANSI_H:
+            return 'H';
+        case kVK_ANSI_J:
+            return 'J';
+        case kVK_ANSI_K:
+            return 'K';
+        case kVK_ANSI_L:
+            return 'L';
+        case kVK_ANSI_Z:
+            return 'Z';
+        case kVK_ANSI_X:
+            return 'X';
+        case kVK_ANSI_C:
+            return 'C';
+        case kVK_ANSI_V:
+            return 'V';
+        case kVK_ANSI_B:
+            return 'B';
+        case kVK_ANSI_N:
+            return 'N';
+        case kVK_ANSI_M:
+            return 'M';
+        case kVK_ANSI_0:
+            return '0';
+        case kVK_ANSI_1:
+            return '1';
+        case kVK_ANSI_2:
+            return '2';
+        case kVK_ANSI_3:
+            return '3';
+        case kVK_ANSI_4:
+            return '4';
+        case kVK_ANSI_5:
+            return '5';
+        case kVK_ANSI_6:
+            return '6';
+        case kVK_ANSI_7:
+            return '7';
+        case kVK_ANSI_8:
+            return '8';
+        case kVK_ANSI_9:
+            return '9';
+        case kVK_ANSI_Keypad0:
+            return TK_PAD0;
+        case kVK_ANSI_Keypad1:
+            return TK_PAD1;
+        case kVK_ANSI_Keypad2:
+            return TK_PAD2;
+        case kVK_ANSI_Keypad3:
+            return TK_PAD3;
+        case kVK_ANSI_Keypad4:
+            return TK_PAD4;
+        case kVK_ANSI_Keypad5:
+            return TK_PAD5;
+        case kVK_ANSI_Keypad6:
+            return TK_PAD6;
+        case kVK_ANSI_Keypad7:
+            return TK_PAD7;
+        case kVK_ANSI_Keypad8:
+            return TK_PAD8;
+        case kVK_ANSI_Keypad9:
+            return TK_PAD9;
+        case kVK_ANSI_KeypadMultiply:
+            return TK_PADMUL;
+        case kVK_ANSI_KeypadPlus:
+            return TK_PADADD;
+        case kVK_ANSI_KeypadEnter:
+            return TK_PADENTER;
+        case kVK_ANSI_KeypadMinus:
+            return TK_PADSUB;
+        case kVK_ANSI_KeypadDecimal:
+            return TK_PADDOT;
+        case kVK_ANSI_KeypadDivide:
+            return TK_PADDIV;
+        case kVK_F1:
+            return TK_F1;
+        case kVK_F2:
+            return TK_F2;
+        case kVK_F3:
+            return TK_F3;
+        case kVK_F4:
+            return TK_F4;
+        case kVK_F5:
+            return TK_F5;
+        case kVK_F6:
+            return TK_F6;
+        case kVK_F7:
+            return TK_F7;
+        case kVK_F8:
+            return TK_F8;
+        case kVK_F9:
+            return TK_F9;
+        case kVK_F10:
+            return TK_F10;
+        case kVK_F11:
+            return TK_F11;
+        case kVK_F12:
+            return TK_F12;
+        case kVK_Shift:
+            return TK_LSHIFT;
+        case kVK_Control:
+            return TK_LCONTROL;
+        case kVK_Option:
+            return TK_LALT;
+        case kVK_CapsLock:
+            return TK_CAPSLOCK;
+        case kVK_Command:
+            return TK_LWIN;
+        case kVK_Command - 1:
+            return TK_RWIN;
+        case kVK_RightShift:
+            return TK_RSHIFT;
+        case kVK_RightControl:
+            return TK_RCONTROL;
+        case kVK_RightOption:
+            return TK_RALT;
+        case kVK_Delete:
+            return TK_BACKSPACE;
+        case kVK_Tab:
+            return TK_TAB;
+        case kVK_Return:
+            return TK_RETURN;
+        case kVK_Escape:
+            return TK_ESCAPE;
+        case kVK_Space:
+            return TK_SPACE;
+        case kVK_PageUp:
+            return TK_PAGEUP;
+        case kVK_PageDown:
+            return TK_PAGEDN;
+        case kVK_End:
+            return TK_END;
+        case kVK_Home:
+            return TK_HOME;
+        case kVK_LeftArrow:
+            return TK_LEFT;
+        case kVK_UpArrow:
+            return TK_UP;
+        case kVK_RightArrow:
+            return TK_RIGHT;
+        case kVK_DownArrow:
+            return TK_DOWN;
+        case kVK_Help:
+            return TK_INSERT;
+        case kVK_ForwardDelete:
+            return TK_DELETE;
+        case kVK_F14:
+            return TK_SCROLL;
+        case kVK_F15:
+            return TK_PAUSE;
+        case kVK_ANSI_KeypadClear:
+            return TK_NUMLOCK;
+        case kVK_ANSI_Semicolon:
+            return TK_SEMICOLON;
+        case kVK_ANSI_Equal:
+            return TK_EQUALS;
+        case kVK_ANSI_Comma:
+            return TK_COMMA;
+        case kVK_ANSI_Minus:
+            return TK_MINUS;
+        case kVK_ANSI_Slash:
+            return TK_SLASH;
+        case kVK_ANSI_Backslash:
+            return TK_BACKSLASH;
+        case kVK_ANSI_Grave:
+            return TK_BACKTICK;
+        case kVK_ANSI_Quote:
+            return TK_TICK;
+        case kVK_ANSI_LeftBracket:
+            return TK_LSQUARE;
+        case kVK_ANSI_RightBracket:
+            return TK_RSQUARE;
+        case kVK_ANSI_Period:
+            return TK_DOT;
+        default:
+            return 0;
+    }
+}
+
+void _tigrOnCocoaEvent(id event, id window) {
+    if (!event)
+        return;
+
+    TigrInternal* win = _tigrInternalFromWindow(window);
+    if (!win)  // just pipe the event
+    {
+        objc_msgSend_void_id(NSApp, sel("sendEvent:"), event);
+        return;
+    }
+
+    NSUInteger eventType = objc_msgSend_t(NSUInteger)(event, sel("type"));
+    switch (eventType) {
+        case 1:  // NSLeftMouseDown
+            if (win->mouseInView) {
+                win->mouseButtons |= 1;
+            }
+            break;
+        case 2:  // NSLeftMouseUp
+            win->mouseButtons &= ~1;
+            break;
+        case 3:  // NSRightMouseDown
+            if (win->mouseInView) {
+                win->mouseButtons |= 2;
+            }
+            break;
+        case 4:  // NSRightMouseUp
+            win->mouseButtons &= ~2;
+            break;
+        case 25:  // NSOtherMouseDown
+        {
+            // number == 2 is a middle button
+            NSInteger number = objc_msgSend_t(NSInteger)(event, sel("buttonNumber"));
+            if (number == 2 && win->mouseInView) {
+                win->mouseButtons |= 4;
+            }
+            break;
+        }
+        case 26:  // NSOtherMouseUp
+        {
+            NSInteger number = objc_msgSend_t(NSInteger)(event, sel("buttonNumber"));
+            if (number == 2)
+                win->mouseButtons &= ~4;
+            break;
+        }
+        case 22:  // NSScrollWheel
+        {
+            CGFloat deltaX = objc_msgSend_t(CGFloat)(event, sel("deltaX"));
+            win->scrollDeltaX += deltaX;
+            CGFloat deltaY = objc_msgSend_t(CGFloat)(event, sel("deltaY"));
+            win->scrollDeltaY += deltaY;
+            break;
+        }
+        case 12:  // NSFlagsChanged
+        {
+            NSUInteger modifiers = objc_msgSend_t(NSUInteger)(event, sel("modifierFlags"));
+
+            // based on NSEventModifierFlags and
+            // NSDeviceIndependentModifierFlagsMask
+            struct {
+                union {
+                    struct {
+                        uint8_t alpha_shift : 1;
+                        uint8_t shift : 1;
+                        uint8_t control : 1;
+                        uint8_t alternate : 1;
+                        uint8_t command : 1;
+                        uint8_t numeric_pad : 1;
+                        uint8_t help : 1;
+                        uint8_t function : 1;
+                    };
+                    uint8_t mask;
+                };
+            } keys;
+
+            keys.mask = (modifiers & 0xffff0000UL) >> 16;
+
+            // TODO L,R variation of keys?
+            win->keys[TK_CONTROL] = keys.alpha_shift;
+            win->keys[TK_SHIFT] = keys.shift;
+            win->keys[TK_CONTROL] = keys.control;
+            win->keys[TK_ALT] = keys.alternate;
+            win->keys[TK_LWIN] = keys.command;
+            win->keys[TK_RWIN] = keys.command;
+            break;
+        }
+        case 10:  // NSKeyDown
+        {
+            uint16_t keyCode = objc_msgSend_t(unsigned short)(event, sel("keyCode"));
+            int tigrKey = _tigrKeyFromOSX(keyCode);
+
+            // Ignore keyboard repeats
+            if (!win->keys[tigrKey]) {
+                win->keys[tigrKey] = 1;
+                id inputText = objc_msgSend_id(event, sel("characters"));
+                const char* inputTextUTF8 = objc_msgSend_t(const char*)(inputText, sel("UTF8String"));
+
+                int decoded = 0;
+                tigrDecodeUTF8(inputTextUTF8, &decoded);
+                if (decoded < 0xe000 || decoded > 0xf8ff) {
+                    win->lastChar = decoded;
+                }
+            }
+
+            // Pass through cmd+key
+            if (win->keys[TK_LWIN]) {
+                break;
+            }
+            return;
+        }
+        case 11:  // NSKeyUp
+        {
+            uint16_t keyCode = objc_msgSend_t(unsigned short)(event, sel("keyCode"));
+            win->keys[_tigrKeyFromOSX(keyCode)] = 0;
+            return;
+        }
+        default:
+            break;
+    }
+
+    objc_msgSend_void_id(NSApp, sel("sendEvent:"), event);
+}
+
+void tigrUpdate(Tigr* bmp) {
+    popPool(autoreleasePool);
+    autoreleasePool = pushPool();
+
+    TigrInternal* win;
+    id openGLContext;
+    id window;
+    win = tigrInternal(bmp);
+    window = (id)bmp->handle;
+    openGLContext = (id)win->gl.glContext;
+
+    if (terminated || _tigrIsWindowClosed(window)) {
+        return;
+    }
+
+    id keyWindow = objc_msgSend_id(NSApp, sel("keyWindow"));
+    unsigned long long eventMask = NSAllEventMask;
+
+    if (keyWindow == window) {
+        memcpy(win->prev, win->keys, 256);
+    } else {
+        eventMask &= ~(NSKeyDownMask | NSKeyUpMask);
+    }
+
+    win->scrollDeltaX = 0;
+    win->scrollDeltaY = 0;
+
+    id event = 0;
+    BOOL visible = 0;
+
+    double now = _currentMediaTime();
+    double passed = now - _tigrTimestamp;
+
+    do {
+        event = objc_msgSend_t(id, NSUInteger, id, id, BOOL)(
+            NSApp, sel("nextEventMatchingMask:untilDate:inMode:dequeue:"), eventMask, nil, NSDefaultRunLoopMode, YES);
+
+        if (event != 0) {
+            _tigrOnCocoaEvent(event, window);
+        } else {
+            visible = _tigrIsWindowVisible(window);
+        }
+    } while (event != 0 || !visible);
+
+    // The event processing loop above blocks during resize, which causes updates to freeze
+    // but real time keeps ticking. We pretend that the event processing took no time
+    // to avoid huge jumps in tigrTime.
+    if (_tigrTimestamp != 0) {
+        _tigrTimestamp = _currentMediaTime() - passed;
+    }
+
+    objc_msgSend_void(NSApp, sel("updateWindows"));
+    objc_msgSend_void(openGLContext, sel("update"));
+    tigrGAPIBegin(bmp);
+
+    NSSize windowSize = _tigrContentBackingSize(window);
+
+    if (win->flags & TIGR_AUTO)
+        tigrResize(bmp, windowSize.width / win->scale, windowSize.height / win->scale);
+    else
+        win->scale = tigrEnforceScale(tigrCalcScale(bmp->w, bmp->h, windowSize.width, windowSize.height), win->flags);
+
+    tigrPosition(bmp, win->scale, windowSize.width, windowSize.height, win->pos);
+    tigrGAPIPresent(bmp, windowSize.width, windowSize.height);
+    objc_msgSend_void(openGLContext, sel("flushBuffer"));
+    tigrGAPIEnd(bmp);
+
+    id runLoop = objc_msgSend_id(class("NSRunLoop"), sel("currentRunLoop"));
+    id until = objc_msgSend_t(id, float)(class("NSDate"), sel("dateWithTimeIntervalSinceNow:"), 0.005f);
+    double lastFrameTime = win->frameTime;
+    static const double cap = 1.0 / 60;
+
+    while (win->frameTime <= lastFrameTime) {
+        // Run the event loop for 5ms at a time..
+        objc_msgSend_t(void, id, id)(runLoop, sel("runMode:beforeDate:"), NSDefaultRunLoopMode, until);
+        // ..until we get a frame callback..
+        if (win->frameTime > lastFrameTime) {
+            break;
+        }
+        double now = _currentMediaTime();
+        double passed = now - lastFrameTime;
+        // ..or it's time to cap the wait at 60 fps
+        if (passed > cap) {
+            win->frameTime = now;
+            break;
+        }
+    }
+}
+
+int tigrGAPIBegin(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    objc_msgSend_void((id)win->gl.glContext, sel("makeCurrentContext"));
+    return 0;
+}
+
+int tigrGAPIEnd(Tigr* bmp) {
+    (void)bmp;
+    objc_msgSend_void(class("NSOpenGLContext"), sel("clearCurrentContext"));
+    return 0;
+}
+
+int tigrClosed(Tigr* bmp) {
+    return (terminated || _tigrIsWindowClosed((id)bmp->handle)) ? 1 : 0;
+}
+
+void tigrMouse(Tigr* bmp, int* x, int* y, int* buttons) {
+    TigrInternal* win;
+    id window;
+    win = tigrInternal(bmp);
+    window = (id)bmp->handle;
+
+    id windowContentView = objc_msgSend_id(window, sel("contentView"));
+    NSRect adjustFrame = objc_msgSend_stret_t(NSRect)(windowContentView, sel("frame"));
+
+    // NSPoint is small enough to fit a register, so no need for
+    // objc_msgSend_stret
+    NSPoint p = objc_msgSend_t(NSPoint)(window, sel("mouseLocationOutsideOfEventStream"));
+
+    // map input to content view rect
+    if (p.x < 0)
+        p.x = 0;
+    else if (p.x > adjustFrame.size.width)
+        p.x = adjustFrame.size.width;
+    if (p.y < 0)
+        p.y = 0;
+    else if (p.y > adjustFrame.size.height)
+        p.y = adjustFrame.size.height;
+
+    // map input to pixels
+    NSRect r = { p, { 0, 0 } };
+    r = objc_msgSend_stret_t(NSRect, NSRect)(windowContentView, sel("convertRectToBacking:"), r);
+    p = r.origin;
+
+    p.x = (p.x - win->pos[0]) / win->scale;
+    p.y = bmp->h - (p.y - win->pos[1]) / win->scale;
+
+    if (x)
+        *x = p.x;
+    if (y)
+        *y = p.y;
+
+    if (buttons) {
+        id keyWindow = objc_msgSend_id(NSApp, sel("keyWindow"));
+        *buttons = keyWindow != bmp->handle ? 0 : win->mouseButtons;
+    }
+}
+
+int tigrTouch(Tigr* bmp, TigrTouchPoint* points, int maxPoints) {
+    int buttons = 0;
+    if (maxPoints > 0) {
+        tigrMouse(bmp, &points[0].x, &points[1].y, &buttons);
+    }
+    return buttons ? 1 : 0;
+}
+
+void tigrScrollWheel(Tigr* bmp, float* x, float* y) {
+    TigrInternal* win;
+    win = tigrInternal(bmp);
+    if (x) {
+        *x = win->scrollDeltaX;
+    }
+    if (y) {
+        *y = win->scrollDeltaY;
+    }
+}
+
+int tigrKeyDown(Tigr* bmp, int key) {
+    TigrInternal* win;
+    assert(key < 256);
+    win = tigrInternal(bmp);
+    return (win->keys[key] != 0) && (win->prev[key] == 0);
+}
+
+int tigrKeyHeld(Tigr* bmp, int key) {
+    TigrInternal* win;
+    assert(key < 256);
+    win = tigrInternal(bmp);
+    return win->keys[key];
+}
+
+int tigrReadChar(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    int c = win->lastChar;
+    win->lastChar = 0;
+    return c;
+}
+
+float tigrTime(void) {
+    double elapsed = 0;
+
+    double now = _currentMediaTime();
+    if (_tigrTimestamp != 0) {
+        elapsed = now - _tigrTimestamp;
+    }
+    _tigrTimestamp = now;
+
+    return (float)elapsed;
+}
+
+#endif  // __MACOS__
+#endif  // #ifndef TIGR_HEADLESS

+ 281 - 0
tigr-master/src/tigr_print.c

@@ -0,0 +1,281 @@
+#include "tigr_internal.h"
+#include "tigr_font.h"
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <stdio.h>
+
+#ifdef _MSC_VER
+#define vsnprintf _vsnprintf
+#endif
+
+TigrFont tigrStockFont;
+TigrFont* tfont = &tigrStockFont;
+
+// Converts 8-bit codepage entries into Unicode code points.
+static int cp1252[] = {
+    0x20ac, 0xfffd, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152,
+    0xfffd, 0x017d, 0xfffd, 0xfffd, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x02dc, 0x2122,
+    0x0161, 0x203a, 0x0153, 0xfffd, 0x017e, 0x0178, 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6,
+    0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, 0x00b0, 0x00b1, 0x00b2, 0x00b3,
+    0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, 0x00c0,
+    0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd,
+    0x00ce, 0x00cf, 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da,
+    0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7,
+    0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4,
+    0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff,
+};
+
+static int border(Tigr* bmp, int x, int y) {
+    TPixel top = tigrGet(bmp, 0, 0);
+    TPixel c = tigrGet(bmp, x, y);
+    return (c.r == top.r && c.g == top.g && c.b == top.b) || x >= bmp->w || y >= bmp->h;
+}
+
+static void scan(Tigr* bmp, int* x, int* y, int* rowh) {
+    while (*y < bmp->h) {
+        if (*x >= bmp->w) {
+            *x = 0;
+            (*y) += *rowh;
+            *rowh = 1;
+        }
+        if (!border(bmp, *x, *y))
+            return;
+        (*x)++;
+    }
+}
+
+/*
+ * Watermarks are encoded vertically in the alpha channel using seven pixels
+ * starting at x, y. The first and last alpha values contain the magic values
+ * 0b10101010 and 0b01010101 respectively.
+ */
+static int readWatermark(Tigr* bmp, int x, int y, int* big, int* small) {
+    const int magicHeader = 0xAA;
+    const int magicFooter = 0x55;
+
+    unsigned char watermark[7];
+
+    for (int i = 0; i < 7; i++) {
+        TPixel c = tigrGet(bmp, x, y + i);
+        watermark[i] = c.a;
+    }
+
+    if (watermark[0] != magicHeader || watermark[6] != magicFooter) {
+        return 0;
+    }
+
+    *big = watermark[1] | (watermark[2] << 8) | (watermark[3] << 16) | (watermark[4] << 24);
+    *small = watermark[5];
+
+    return 1;
+}
+
+int tigrLoadGlyphs(TigrFont* font, int codepage) {
+    int x = 0;
+    int y = 0;
+    int w = 0;
+    int h = 0;
+    int rowh = 1;
+
+    TigrGlyph* g;
+    switch (codepage) {
+        case TCP_ASCII:
+            font->numGlyphs = 128 - 32;
+            break;
+        case TCP_1252:
+            font->numGlyphs = 256 - 32;
+            break;
+        case TCP_UTF32:
+            if (!readWatermark(font->bitmap, 0, 0, &font->numGlyphs, &rowh)) {
+                return 0;
+            }
+            h = rowh;
+            x = 1;
+            break;
+        default:
+            errno = EINVAL;
+            return 0;
+    }
+
+    font->glyphs = (TigrGlyph*)calloc(font->numGlyphs, sizeof(TigrGlyph));
+
+    for (int index = 0; index < font->numGlyphs; index++) {
+        // Look up the Unicode code point.
+        g = &font->glyphs[index];
+
+        if (codepage != TCP_UTF32) {
+            // Find the next glyph.
+            scan(font->bitmap, &x, &y, &rowh);
+
+            if (y >= font->bitmap->h) {
+                errno = EINVAL;
+                return 0;
+            }
+
+            // Scan the width and height
+            w = h = 0;
+            while (!border(font->bitmap, x + w, y)) {
+                w++;
+            }
+
+            while (!border(font->bitmap, x, y + h)) {
+                h++;
+            }
+        }
+
+        switch (codepage) {
+            case TCP_ASCII:
+                g->code = index + 32;
+                break;
+            case TCP_1252:
+                if (index < 96) {
+                    g->code = index + 32;
+                } else {
+                    g->code = cp1252[index - 96];
+                }
+                break;
+            case TCP_UTF32:
+                if (!readWatermark(font->bitmap, x, y, &g->code, &w)) {
+                    // Maybe we are at the end of a row?
+                    x = 0;
+                    y += rowh;
+                    if (!readWatermark(font->bitmap, x, y, &g->code, &w)) {
+                        return 0;
+                    }
+                }
+                x++;
+                break;
+            default:
+                return 0;
+        }
+
+        g->x = x;
+        g->y = y;
+        g->w = w;
+        g->h = h;
+        x += w;
+        if (h != font->glyphs[0].h) {
+            errno = EINVAL;
+            return 0;
+        }
+
+        if (h > rowh) {
+            rowh = h;
+        }
+    }
+
+    // Sort by code point.
+    for (int i = 1; i < font->numGlyphs; i++) {
+        int j = i;
+        TigrGlyph g = font->glyphs[i];
+        while (j > 0 && font->glyphs[j - 1].code > g.code) {
+            font->glyphs[j] = font->glyphs[j - 1];
+            j--;
+        }
+        font->glyphs[j] = g;
+    }
+
+    return 1;
+}
+
+TigrFont* tigrLoadFont(Tigr* bitmap, int codepage) {
+    TigrFont* font = (TigrFont*)calloc(1, sizeof(TigrFont));
+    font->bitmap = bitmap;
+    if (!tigrLoadGlyphs(font, codepage)) {
+        tigrFreeFont(font);
+        return NULL;
+    }
+    return font;
+}
+
+void tigrFreeFont(TigrFont* font) {
+    tigrFree(font->bitmap);
+    free(font->glyphs);
+    free(font);
+}
+
+static TigrGlyph* get(TigrFont* font, int code) {
+    unsigned lo = 0, hi = font->numGlyphs;
+    while (lo < hi) {
+        unsigned guess = (lo + hi) / 2;
+        if (code < font->glyphs[guess].code)
+            hi = guess;
+        else
+            lo = guess + 1;
+    }
+
+    if (lo == 0 || font->glyphs[lo - 1].code != code)
+        return &font->glyphs['?' - 32];
+    else
+        return &font->glyphs[lo - 1];
+}
+
+void tigrSetupFont(TigrFont* font) {
+    // Load the stock font if needed.
+    if (font == tfont && !tfont->bitmap) {
+        tfont->bitmap = tigrLoadImageMem(tigr_font, tigr_font_size);
+        tigrLoadGlyphs(tfont, 1252);
+    }
+}
+
+void tigrPrint(Tigr* dest, TigrFont* font, int x, int y, TPixel color, const char* text, ...) {
+    char tmp[1024];
+    TigrGlyph* g;
+    va_list args;
+    const char* p;
+    int start = x, c;
+
+    tigrSetupFont(font);
+
+    // Expand the formatting string.
+    va_start(args, text);
+    vsnprintf(tmp, sizeof(tmp), text, args);
+    tmp[sizeof(tmp) - 1] = 0;
+    va_end(args);
+
+    // Print each glyph.
+    p = tmp;
+    while (*p) {
+        p = tigrDecodeUTF8(p, &c);
+        if (c == '\r')
+            continue;
+        if (c == '\n') {
+            x = start;
+            y += tigrTextHeight(font, "");
+            continue;
+        }
+        g = get(font, c);
+        tigrBlitTint(dest, font->bitmap, x, y, g->x, g->y, g->w, g->h, color);
+        x += g->w;
+    }
+}
+
+int tigrTextWidth(TigrFont* font, const char* text) {
+    int x = 0, w = 0, c;
+    tigrSetupFont(font);
+
+    while (*text) {
+        text = tigrDecodeUTF8(text, &c);
+        if (c == '\n' || c == '\r') {
+            x = 0;
+        } else {
+            x += get(font, c)->w;
+            w = (x > w) ? x : w;
+        }
+    }
+    return w;
+}
+
+int tigrTextHeight(TigrFont* font, const char* text) {
+    int rowh, h, c;
+    tigrSetupFont(font);
+
+    h = rowh = get(font, 0)->h;
+    while (*text) {
+        text = tigrDecodeUTF8(text, &c);
+        if (c == '\n' && *text)
+            h += rowh;
+    }
+    return h;
+}

+ 190 - 0
tigr-master/src/tigr_savepng.c

@@ -0,0 +1,190 @@
+#include "tigr_internal.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+typedef struct {
+    unsigned crc, adler, bits, prev, runlen;
+    FILE* out;
+    unsigned crcTable[256];
+} Save;
+
+static const unsigned crctable[16] = { 0,          0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4,
+                                       0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
+                                       0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c };
+
+static void put(Save* s, unsigned v) {
+    fputc(v, s->out);
+    s->crc = (s->crc >> 4) ^ crctable[(s->crc & 15) ^ (v & 15)];
+    s->crc = (s->crc >> 4) ^ crctable[(s->crc & 15) ^ (v >> 4)];
+}
+
+static void updateAdler(Save* s, unsigned v) {
+    unsigned s1 = s->adler & 0xffff, s2 = (s->adler >> 16) & 0xffff;
+    s1 = (s1 + v) % 65521;
+    s2 = (s2 + s1) % 65521;
+    s->adler = (s2 << 16) + s1;
+}
+
+static void put32(Save* s, unsigned v) {
+    put(s, (v >> 24) & 0xff);
+    put(s, (v >> 16) & 0xff);
+    put(s, (v >> 8) & 0xff);
+    put(s, v & 0xff);
+}
+
+void putbits(Save* s, unsigned data, unsigned bitcount) {
+    while (bitcount--) {
+        unsigned prev = s->bits;
+        s->bits = (s->bits >> 1) | ((data & 1) << 7);
+        data >>= 1;
+        if (prev & 1) {
+            put(s, s->bits);
+            s->bits = 0x80;
+        }
+    }
+}
+
+void putbitsr(Save* s, unsigned data, unsigned bitcount) {
+    while (bitcount--)
+        putbits(s, data >> bitcount, 1);
+}
+
+static void begin(Save* s, const char* id, unsigned len) {
+    put32(s, len);
+    s->crc = 0xffffffff;
+    put(s, id[0]);
+    put(s, id[1]);
+    put(s, id[2]);
+    put(s, id[3]);
+}
+
+static void literal(Save* s, unsigned v) {
+    // Encode a literal/length using the built-in tables.
+    // Could do better with a custom table but whatever.
+    if (v < 144)
+        putbitsr(s, 0x030 + v - 0, 8);
+    else if (v < 256)
+        putbitsr(s, 0x190 + v - 144, 9);
+    else if (v < 280)
+        putbitsr(s, 0x000 + v - 256, 7);
+    else
+        putbitsr(s, 0x0c0 + v - 280, 8);
+}
+
+static void encodelen(Save* s, unsigned code, unsigned bits, unsigned len) {
+    literal(s, code + (len >> bits));
+    putbits(s, len, bits);
+    putbits(s, 0, 5);
+}
+
+static void endrun(Save* s) {
+    s->runlen--;
+    literal(s, s->prev);
+
+    if (s->runlen >= 67)
+        encodelen(s, 277, 4, s->runlen - 67);
+    else if (s->runlen >= 35)
+        encodelen(s, 273, 3, s->runlen - 35);
+    else if (s->runlen >= 19)
+        encodelen(s, 269, 2, s->runlen - 19);
+    else if (s->runlen >= 11)
+        encodelen(s, 265, 1, s->runlen - 11);
+    else if (s->runlen >= 3)
+        encodelen(s, 257, 0, s->runlen - 3);
+    else
+        while (s->runlen--)
+            literal(s, s->prev);
+}
+
+static void encodeByte(Save* s, unsigned char v) {
+    updateAdler(s, v);
+
+    // Simple RLE compression. We could do better by doing a search
+    // to find matches, but this works pretty well TBH.
+    if (s->prev == v && s->runlen < 115) {
+        s->runlen++;
+    } else {
+        if (s->runlen)
+            endrun(s);
+
+        s->prev = v;
+        s->runlen = 1;
+    }
+}
+
+static void savePngHeader(Save* s, Tigr* bmp) {
+    fwrite("\211PNG\r\n\032\n", 8, 1, s->out);
+    begin(s, "IHDR", 13);
+    put32(s, bmp->w);
+    put32(s, bmp->h);
+    put(s, 8);  // bit depth
+    put(s, 6);  // RGBA
+    put(s, 0);  // compression (deflate)
+    put(s, 0);  // filter (standard)
+    put(s, 0);  // interlace off
+    put32(s, ~s->crc);
+}
+
+static long savePngData(Save* s, Tigr* bmp, long dataPos) {
+    int x, y;
+    long dataSize;
+    begin(s, "IDAT", 0);
+    put(s, 0x08);      // zlib compression method
+    put(s, 0x1d);      // zlib compression flags
+    putbits(s, 3, 3);  // zlib last block + fixed dictionary
+    for (y = 0; y < bmp->h; y++) {
+        TPixel* row = &bmp->pix[y * bmp->w];
+        TPixel prev = tigrRGBA(0, 0, 0, 0);
+
+        encodeByte(s, 1);  // sub filter
+        for (x = 0; x < bmp->w; x++) {
+            encodeByte(s, row[x].r - prev.r);
+            encodeByte(s, row[x].g - prev.g);
+            encodeByte(s, row[x].b - prev.b);
+            encodeByte(s, row[x].a - prev.a);
+            prev = row[x];
+        }
+    }
+    endrun(s);
+    literal(s, 256);  // terminator
+    while (s->bits != 0x80)
+        putbits(s, 0, 1);
+    put32(s, s->adler);
+    dataSize = (ftell(s->out) - dataPos) - 8;
+    put32(s, ~s->crc);
+    return dataSize;
+}
+
+int tigrSaveImage(const char* fileName, Tigr* bmp) {
+    Save s;
+    long dataPos, dataSize, err;
+
+    // TODO - unicode?
+    FILE* out = fopen(fileName, "wb");
+    if (!out)
+        return 1;
+
+    s.out = out;
+    s.adler = 1;
+    s.bits = 0x80;
+    s.prev = 0xffff;
+    s.runlen = 0;
+
+    savePngHeader(&s, bmp);
+    dataPos = ftell(s.out);
+    dataSize = savePngData(&s, bmp, dataPos);
+
+    // End chunk.
+    begin(&s, "IEND", 0);
+    put32(&s, ~s.crc);
+
+    // Write back payload size.
+    fseek(out, dataPos, SEEK_SET);
+    put32(&s, dataSize);
+
+    err = ferror(out);
+    fclose(out);
+    return !err;
+}

+ 38 - 0
tigr-master/src/tigr_upscale_gl_fs.h

@@ -0,0 +1,38 @@
+#ifndef __TIGR_UPSCALE_GL_FS_H__
+#define __TIGR_UPSCALE_GL_FS_H__
+
+#include "tigr_glsl_hdr.h"
+
+// clang-format off
+const char tigr_upscale_gl_fs[] = {
+    GLSL_VERSION_HEADER
+    "in vec2 uv;"
+    "out vec4 color;"
+    "uniform sampler2D image;"
+    "uniform vec4 parameters;"
+    "void fxShader(out vec4 color, in vec2 coord);"
+    "void main()"
+    "{"
+    "   fxShader(color, uv);"
+    "}\n"
+};
+// clang-format on
+
+const int tigr_upscale_gl_fs_size = (int)sizeof(tigr_upscale_gl_fs) - 1;
+
+// clang-format off
+const char tigr_default_fx_gl_fs[] = {
+    "void fxShader(out vec4 color, in vec2 uv) {"
+    "   vec2 tex_size = vec2(textureSize(image, 0));"
+    "   vec2 uv_blur = mix(floor(uv * tex_size) + 0.5, uv * tex_size, parameters.xy) / tex_size;"
+    "   vec4 c = texture(image, uv_blur);"
+    "   c.rgb *= mix(0.5, 1.0 - fract(uv.y * tex_size.y), parameters.z) * 2.0; //scanline\n"
+    "   c = mix(vec4(0.5), c, parameters.w); //contrast\n"
+    "   color = c;"
+    "}"
+};
+// clang-format on
+
+const int tigr_default_fx_gl_fs_size = (int)sizeof(tigr_default_fx_gl_fs) - 1;
+
+#endif

+ 24 - 0
tigr-master/src/tigr_upscale_gl_vs.h

@@ -0,0 +1,24 @@
+#ifndef __TIGR_UPSCALE_GL_VS_H__
+#define __TIGR_UPSCALE_GL_VS_H__
+
+#include "tigr_glsl_hdr.h"
+
+// clang-format off
+const char tigr_upscale_gl_vs[] = {
+    GLSL_VERSION_HEADER
+    "layout (location = 0) in vec2 pos_in;"
+    "layout (location = 1) in vec2 uv_in;"
+    "out vec2 uv;"
+    "uniform mat4 model;"
+    "uniform mat4 projection;"
+    "void main()"
+    "{"
+    "   uv = uv_in;"
+    "   gl_Position = projection * model * vec4(pos_in, 0.0, 1.0);"
+    "}"
+};
+// clang-format on
+
+const int tigr_upscale_gl_vs_size = (int)sizeof(tigr_upscale_gl_vs) - 1;
+
+#endif

+ 141 - 0
tigr-master/src/tigr_utils.c

@@ -0,0 +1,141 @@
+#include "tigr_internal.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifndef __ANDROID__
+
+#ifdef __IOS__
+void* _tigrReadFile(const char* fileName, int* length) {
+#else
+void* tigrReadFile(const char* fileName, int* length) {
+#endif
+    // TODO - unicode?
+    FILE* file;
+    char* data;
+    size_t len;
+
+    if (length)
+        *length = 0;
+
+    file = fopen(fileName, "rb");
+    if (!file)
+        return NULL;
+
+    fseek(file, 0, SEEK_END);
+    len = ftell(file);
+    fseek(file, 0, SEEK_SET);
+
+    data = (char*)malloc(len + 1);
+    if (!data) {
+        fclose(file);
+        return NULL;
+    }
+
+    if (fread(data, 1, len, file) != len) {
+        free(data);
+        fclose(file);
+        return NULL;
+    }
+    data[len] = '\0';
+    fclose(file);
+
+    if (length)
+        *length = len;
+
+    return data;
+}
+
+#endif  // __ANDROID__
+
+// Reads a single UTF8 codepoint.
+const char* tigrDecodeUTF8(const char* text, int* cp) {
+    unsigned char c = *text++;
+    int extra = 0, min = 0;
+    *cp = 0;
+    if (c >= 0xf0) {
+        *cp = c & 0x07;
+        extra = 3;
+        min = 0x10000;
+    } else if (c >= 0xe0) {
+        *cp = c & 0x0f;
+        extra = 2;
+        min = 0x800;
+    } else if (c >= 0xc0) {
+        *cp = c & 0x1f;
+        extra = 1;
+        min = 0x80;
+    } else if (c >= 0x80) {
+        *cp = 0xfffd;
+    } else {
+        *cp = c;
+    }
+    while (extra--) {
+        c = *text++;
+        if ((c & 0xc0) != 0x80) {
+            *cp = 0xfffd;
+            break;
+        }
+        (*cp) = ((*cp) << 6) | (c & 0x3f);
+    }
+    if (*cp < min) {
+        *cp = 0xfffd;
+    }
+    return text;
+}
+
+char* tigrEncodeUTF8(char* text, int cp) {
+    if (cp < 0 || cp > 0x10ffff) {
+        cp = 0xfffd;
+    }
+
+#define EMIT(X, Y, Z) *text++ = X | ((cp >> Y) & Z)
+    if (cp < 0x80) {
+        EMIT(0x00, 0, 0x7f);
+    } else if (cp < 0x800) {
+        EMIT(0xc0, 6, 0x1f);
+        EMIT(0x80, 0, 0x3f);
+    } else if (cp < 0x10000) {
+        EMIT(0xe0, 12, 0xf);
+        EMIT(0x80, 6, 0x3f);
+        EMIT(0x80, 0, 0x3f);
+    } else {
+        EMIT(0xf0, 18, 0x7);
+        EMIT(0x80, 12, 0x3f);
+        EMIT(0x80, 6, 0x3f);
+        EMIT(0x80, 0, 0x3f);
+    }
+    return text;
+#undef EMIT
+}
+
+#ifndef TIGR_HEADLESS
+
+int tigrBeginOpenGL(Tigr* bmp) {
+#ifdef TIGR_GAPI_GL
+    TigrInternal* win = tigrInternal(bmp);
+    win->gl.gl_user_opengl_rendering = 1;
+    return tigrGAPIBegin(bmp) == 0;
+#else
+    return 0;
+#endif
+}
+
+void tigrSetPostShader(Tigr* bmp, const char* code, int size) {
+#ifdef TIGR_GAPI_GL
+    tigrGAPIBegin(bmp);
+    TigrInternal* win = tigrInternal(bmp);
+    GLStuff* gl = &win->gl;
+    tigrCreateShaderProgram(gl, code, size);
+    tigrGAPIEnd(bmp);
+#endif
+}
+
+void tigrSetPostFX(Tigr* bmp, float p1, float p2, float p3, float p4) {
+    TigrInternal* win = tigrInternal(bmp);
+    win->p1 = p1;
+    win->p2 = p2;
+    win->p3 = p3;
+    win->p4 = p4;
+}
+
+#endif // TIGR_HEADLESS

+ 811 - 0
tigr-master/src/tigr_win.c

@@ -0,0 +1,811 @@
+#ifndef TIGR_HEADLESS
+
+#include "tigr_internal.h"
+#include <assert.h>
+
+// not really windows stuff
+TigrInternal* tigrInternal(Tigr* bmp) {
+    assert(bmp->handle);
+    return (TigrInternal*)(bmp + 1);
+}
+
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <shellapi.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+
+#ifdef _MSC_VER
+#pragma comment(lib, "opengl32")  // glViewport
+#pragma comment(lib, "shell32")   // CommandLineToArgvW
+#pragma comment(lib, "user32")    // SetWindowLong
+#pragma comment(lib, "gdi32")     // ChoosePixelFormat
+#pragma comment(lib, "advapi32")  // RegSetValueEx
+#endif
+
+#define WIDGET_SCALE 3
+#define WIDGET_FADE 16
+
+int main(int argc, char* argv[]);
+
+#ifndef TIGR_DO_NOT_PRESERVE_WINDOW_POSITION
+HKEY tigrRegKey;
+#endif
+
+#ifdef __TINYC__
+#define CP_UTF8 65001
+int WINAPI MultiByteToWideChar();
+int WINAPI WideCharToMultiByte();
+#endif
+
+static wchar_t* unicode(const char* str) {
+    int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, 0, 0);
+    wchar_t* dest = (wchar_t*)malloc(sizeof(wchar_t) * len);
+    MultiByteToWideChar(CP_UTF8, 0, str, -1, dest, len);
+    return dest;
+}
+
+void tigrError(Tigr* bmp, const char* message, ...) {
+    char tmp[1024];
+
+    va_list args;
+    va_start(args, message);
+    _vsnprintf(tmp, sizeof(tmp), message, args);
+    tmp[sizeof(tmp) - 1] = 0;
+    va_end(args);
+
+    MessageBoxW(bmp ? (HWND)bmp->handle : NULL, unicode(tmp), bmp ? tigrInternal(bmp)->wtitle : L"Error",
+                MB_OK | MB_ICONERROR);
+    exit(1);
+}
+
+void tigrEnterBorderlessWindowed(Tigr* bmp) {
+    // Enter borderless windowed mode.
+    MONITORINFO mi = { sizeof(mi) };
+    TigrInternal* win = tigrInternal(bmp);
+
+    GetWindowRect((HWND)bmp->handle, &win->oldPos);
+
+    GetMonitorInfo(MonitorFromWindow((HWND)bmp->handle, MONITOR_DEFAULTTONEAREST), &mi);
+    win->dwStyle = WS_VISIBLE | WS_POPUP;
+    SetWindowLong((HWND)bmp->handle, GWL_STYLE, win->dwStyle);
+    SetWindowPos((HWND)bmp->handle, HWND_TOP, mi.rcMonitor.left, mi.rcMonitor.top,
+                 mi.rcMonitor.right - mi.rcMonitor.left, mi.rcMonitor.bottom - mi.rcMonitor.top, 0);
+}
+
+void tigrLeaveBorderlessWindowed(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+
+    win->dwStyle = WS_VISIBLE | WS_OVERLAPPEDWINDOW;
+    SetWindowLong((HWND)bmp->handle, GWL_STYLE, win->dwStyle);
+
+    SetWindowPos((HWND)bmp->handle, NULL, win->oldPos.left, win->oldPos.top, win->oldPos.right - win->oldPos.left,
+                 win->oldPos.bottom - win->oldPos.top, 0);
+}
+
+void tigrWinUpdateWidgets(Tigr* bmp, int dw, int dh) {
+    POINT pt;
+    int i, x, clicked = 0;
+    char str[8];
+    TPixel col;
+    TPixel off = tigrRGB(255, 255, 255);
+    TPixel on = tigrRGB(0, 200, 255);
+    TigrInternal* win = tigrInternal(bmp);
+    (void)dh;
+
+    tigrClear(win->widgets, tigrRGBA(0, 0, 0, 0));
+
+    if (!(win->dwStyle & WS_POPUP)) {
+        win->widgetsWanted = 0;
+        win->widgetAlpha = 0;
+        return;
+    }
+
+    // See if we want to be showing widgets or not.
+    GetCursorPos(&pt);
+    ScreenToClient((HWND)bmp->handle, &pt);
+    if (pt.y == 0)
+        win->widgetsWanted = 1;
+    if (pt.y > win->widgets->h * WIDGET_SCALE)
+        win->widgetsWanted = 0;
+
+    // Track the alpha.
+    if (win->widgetsWanted)
+        win->widgetAlpha = (win->widgetAlpha <= 255 - WIDGET_FADE) ? win->widgetAlpha + WIDGET_FADE : 255;
+    else
+        win->widgetAlpha = (win->widgetAlpha >= WIDGET_FADE) ? win->widgetAlpha - WIDGET_FADE : 0;
+
+    // Get relative coords.
+    pt.x -= (dw - win->widgets->w * WIDGET_SCALE);
+    pt.x /= WIDGET_SCALE;
+    pt.y /= WIDGET_SCALE;
+
+    tigrClear(win->widgets, tigrRGBA(0, 0, 0, win->widgetAlpha));
+
+    // Render it.
+    for (i = 0; i < 3; i++) {
+        switch (i) {
+            case 0:
+                str[0] = '_';
+                str[1] = 0;
+                break;  // "_" (minimize)
+            case 1:
+                str[0] = (char)0xEF;
+                str[1] = (char)0xBF;
+                str[2] = (char)0xBD;
+                str[3] = 0;
+                break;  // "[]" (maximize)
+            case 2:
+                str[0] = (char)0xC3;
+                str[1] = (char)0x97;
+                str[2] = 0;
+                break;  // "x" (close)
+        }
+        x = win->widgets->w + (i - 3) * 12;
+        if (i == 2)
+            off = tigrRGB(255, 0, 0);
+        if (pt.x >= x && pt.x < x + 10 && pt.y < win->widgets->h) {
+            col = on;
+            if (GetAsyncKeyState(VK_LBUTTON) & 0x8000)
+                clicked |= 1 << i;
+        } else {
+            col = off;
+        }
+        col.a = win->widgetAlpha;
+        tigrPrint(win->widgets, tfont, x, 2, col, str);
+    }
+
+    if (clicked & 1)
+        ShowWindow((HWND)bmp->handle, SW_MINIMIZE);
+    if (clicked & 2)
+        tigrLeaveBorderlessWindowed(bmp);
+    if (clicked & 4)
+        SendMessage((HWND)bmp->handle, WM_CLOSE, 0, 0);
+}
+
+void tigrUpdate(Tigr* bmp) {
+    MSG msg;
+    RECT rc;
+    int dw, dh;
+    TigrInternal* win = tigrInternal(bmp);
+
+    if (!win->shown) {
+        win->shown = 1;
+        UpdateWindow((HWND)bmp->handle);
+        ShowWindow((HWND)bmp->handle, SW_SHOW);
+    }
+
+    // Get the window size.
+    GetClientRect((HWND)bmp->handle, &rc);
+    dw = rc.right - rc.left;
+    dh = rc.bottom - rc.top;
+
+    // Update the widget overlay.
+    tigrWinUpdateWidgets(bmp, dw, dh);
+
+    if (!tigrGAPIBegin(bmp)) {
+        tigrGAPIPresent(bmp, dw, dh);
+        SwapBuffers(win->gl.dc);
+        tigrGAPIEnd(bmp);
+    }
+
+    memcpy(win->prev, win->keys, 256);
+    win->scrollDeltaX = 0;
+    win->scrollDeltaY = 0;
+
+    // Run the message pump.
+    while (PeekMessage(&msg, (HWND)bmp->handle, 0, 0, PM_REMOVE)) {
+        if (msg.message == WM_QUIT)
+            break;
+
+        TranslateMessage(&msg);
+        DispatchMessage(&msg);
+    }
+}
+
+typedef BOOL(APIENTRY* PFNWGLSWAPINTERVALFARPROC_)(int);
+static PFNWGLSWAPINTERVALFARPROC_ wglSwapIntervalEXT_ = 0;
+
+int tigrGAPIBegin(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+
+    return wglMakeCurrent(win->gl.dc, win->gl.hglrc) ? 0 : -1;
+}
+
+int tigrGAPIEnd(Tigr* bmp) {
+    (void)bmp;
+    return wglMakeCurrent(NULL, NULL) ? 0 : -1;
+}
+
+static BOOL UnadjustWindowRectEx(LPRECT prc, DWORD dwStyle, BOOL fMenu, DWORD dwExStyle) {
+    BOOL fRc;
+    RECT rc;
+    SetRectEmpty(&rc);
+    fRc = AdjustWindowRectEx(&rc, dwStyle, fMenu, dwExStyle);
+    if (fRc) {
+        prc->left -= rc.left;
+        prc->top -= rc.top;
+        prc->right -= rc.right;
+        prc->bottom -= rc.bottom;
+    }
+    return fRc;
+}
+
+LRESULT CALLBACK tigrWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
+    Tigr* bmp;
+    TigrInternal* win = NULL;
+    RECT rc;
+    int dw, dh;
+
+    GetClientRect(hWnd, &rc);
+    dw = rc.right - rc.left;
+    dh = rc.bottom - rc.top;
+
+    bmp = (Tigr*)GetPropW(hWnd, L"Tigr");
+    if (bmp)
+        win = tigrInternal(bmp);
+
+    switch (message) {
+        case WM_PAINT:
+            if (!tigrGAPIBegin(bmp)) {
+                tigrGAPIPresent(bmp, dw, dh);
+                SwapBuffers(win->gl.dc);
+                tigrGAPIEnd(bmp);
+            }
+            ValidateRect(hWnd, NULL);
+            break;
+        case WM_CLOSE:
+            if (win)
+                win->closed = 1;
+            break;
+        case WM_GETMINMAXINFO:
+            if (bmp) {
+                MINMAXINFO* info = (MINMAXINFO*)lParam;
+                RECT rc;
+                rc.left = 0;
+                rc.top = 0;
+                if (win->flags & TIGR_AUTO) {
+                    rc.right = 32;
+                    rc.bottom = 32;
+                } else {
+                    int minscale = tigrEnforceScale(1, win->flags);
+                    rc.right = bmp->w * minscale;
+                    rc.bottom = bmp->h * minscale;
+                }
+                AdjustWindowRectEx(&rc, win->dwStyle, FALSE, 0);
+                info->ptMinTrackSize.x = rc.right - rc.left;
+                info->ptMinTrackSize.y = rc.bottom - rc.top;
+            }
+            return 0;
+        case WM_SIZING:
+            if (win) {
+                // Calculate scale-constrained sizes.
+                RECT* rc = (RECT*)lParam;
+                int dx, dy;
+                UnadjustWindowRectEx(rc, win->dwStyle, FALSE, 0);
+                dx = (rc->right - rc->left) % win->scale;
+                dy = (rc->bottom - rc->top) % win->scale;
+                switch (wParam) {
+                    case WMSZ_LEFT:
+                        rc->left += dx;
+                        break;
+                    case WMSZ_RIGHT:
+                        rc->right -= dx;
+                        break;
+                    case WMSZ_TOP:
+                        rc->top += dy;
+                        break;
+                    case WMSZ_TOPLEFT:
+                        rc->left += dx;
+                        rc->top += dy;
+                        break;
+                    case WMSZ_TOPRIGHT:
+                        rc->right -= dx;
+                        rc->top += dy;
+                        break;
+                    case WMSZ_BOTTOM:
+                        rc->bottom -= dy;
+                        break;
+                    case WMSZ_BOTTOMLEFT:
+                        rc->left += dx;
+                        rc->bottom -= dy;
+                        break;
+                    case WMSZ_BOTTOMRIGHT:
+                        rc->right -= dx;
+                        rc->bottom -= dy;
+                        break;
+                }
+                AdjustWindowRectEx(rc, win->dwStyle, FALSE, 0);
+            }
+            return TRUE;
+        case WM_SIZE:
+            if (win) {
+                if (wParam != SIZE_MINIMIZED) {
+                    // Detect window size changes and update our bitmap accordingly.
+                    dw = LOWORD(lParam);
+                    dh = HIWORD(lParam);
+                    if (win->flags & TIGR_AUTO) {
+                        tigrResize(bmp, dw / win->scale, dh / win->scale);
+                    } else {
+                        win->scale = tigrEnforceScale(tigrCalcScale(bmp->w, bmp->h, dw, dh), win->flags);
+                    }
+                    tigrPosition(bmp, win->scale, dw, dh, win->pos);
+                }
+
+                // If someone tried to maximize us (e.g. via shortcut launch options),
+                // prefer instead to be borderless.
+                if (wParam == SIZE_MAXIMIZED) {
+                    ShowWindow((HWND)bmp->handle, SW_NORMAL);
+                    tigrEnterBorderlessWindowed(bmp);
+                }
+            }
+            return 0;
+#ifndef TIGR_DO_NOT_PRESERVE_WINDOW_POSITION
+        case WM_WINDOWPOSCHANGED: {
+            // Save our position.
+            WINDOWPLACEMENT wp = { sizeof(WINDOWPLACEMENT) };
+            GetWindowPlacement(hWnd, &wp);
+            if (win->dwStyle & WS_POPUP)
+                wp.showCmd = SW_MAXIMIZE;
+            RegSetValueExW(tigrRegKey, win->wtitle, 0, REG_BINARY, (BYTE*)&wp, sizeof(wp));
+            return DefWindowProcW(hWnd, message, wParam, lParam);
+        }
+#endif
+        case WM_ACTIVATE:
+            if (win) {
+                memset(win->keys, 0, 256);
+                memset(win->prev, 0, 256);
+                win->lastChar = 0;
+            }
+            return 0;
+        case WM_CHAR:
+            if (win) {
+                if (wParam == '\r') {
+                    wParam = '\n';
+                }
+                int repeating = (HIWORD(lParam) & KF_REPEAT) == KF_REPEAT;
+                if (!repeating) {
+                    win->lastChar = wParam;
+                }
+            }
+            return DefWindowProcW(hWnd, message, wParam, lParam);
+        case WM_MENUCHAR:
+            // Disable beep on Alt+Enter
+            if (LOWORD(wParam) == VK_RETURN)
+                return MNC_CLOSE << 16;
+            return DefWindowProcW(hWnd, message, wParam, lParam);
+        case WM_SYSKEYDOWN:
+            if (win) {
+                if (wParam == VK_RETURN) {
+                    // Alt+Enter
+                    if (win->dwStyle & WS_POPUP)
+                        tigrLeaveBorderlessWindowed(bmp);
+                    else
+                        tigrEnterBorderlessWindowed(bmp);
+                    return 0;
+                }
+            }
+            // fall-thru
+        case WM_KEYDOWN:
+            if (win)
+                win->keys[wParam] = 1;
+            return DefWindowProcW(hWnd, message, wParam, lParam);
+        case WM_SYSKEYUP:
+            // fall-thru
+        case WM_KEYUP:
+            if (win)
+                win->keys[wParam] = 0;
+            return DefWindowProcW(hWnd, message, wParam, lParam);
+        case WM_MOUSEWHEEL:
+            if (win)
+                win->scrollDeltaY += (float)GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
+            return DefWindowProcW(hWnd, message, wParam, lParam);
+        case WM_MOUSEHWHEEL:
+            if (win)
+                win->scrollDeltaX += (float)GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
+            return DefWindowProcW(hWnd, message, wParam, lParam);
+        default:
+            return DefWindowProcW(hWnd, message, wParam, lParam);
+    }
+    return 0;
+}
+
+Tigr* tigrWindow(int w, int h, const char* title, int flags) {
+    WNDCLASSEXW wcex = { 0 };
+    int maxW, maxH, scale;
+    HWND hWnd;
+    DWORD dwStyle;
+    RECT rc;
+    Tigr* bmp;
+    TigrInternal* win;
+#ifndef TIGR_DO_NOT_PRESERVE_WINDOW_POSITION
+    WINDOWPLACEMENT wp;
+    DWORD wpsize = sizeof(wp);
+#endif
+
+    wchar_t* wtitle = unicode(title);
+
+// Find our registry key.
+#ifndef TIGR_DO_NOT_PRESERVE_WINDOW_POSITION
+    RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\TIGR", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,
+                    &tigrRegKey, NULL);
+#endif
+
+    // Register a window class.
+    wcex.cbSize = sizeof(WNDCLASSEXW);
+    wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
+    wcex.lpfnWndProc = tigrWndProc;
+    wcex.hInstance = GetModuleHandle(NULL);
+    wcex.hIcon = NULL;
+    wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
+    wcex.lpszClassName = L"TIGR";
+    RegisterClassExW(&wcex);
+
+    if (flags & TIGR_AUTO) {
+        // Always use a 1:1 pixel size.
+        scale = 1;
+    } else {
+        // See how big we can make it and still fit on-screen.
+        maxW = GetSystemMetrics(SM_CXSCREEN) * 3 / 4;
+        maxH = GetSystemMetrics(SM_CYSCREEN) * 3 / 4;
+        scale = tigrCalcScale(w, h, maxW, maxH);
+    }
+
+    scale = tigrEnforceScale(scale, flags);
+
+    // Get the final window size.
+    dwStyle = WS_OVERLAPPEDWINDOW;
+    rc.left = 0;
+    rc.top = 0;
+    rc.right = w * scale;
+    rc.bottom = h * scale;
+    AdjustWindowRect(&rc, dwStyle, FALSE);
+
+    // Make a window.
+    hWnd = CreateWindowW(L"TIGR", wtitle, dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top,
+                         NULL, NULL, wcex.hInstance, NULL);
+    if (!hWnd)
+        ExitProcess(1);
+
+    if (flags & TIGR_NOCURSOR) {
+        ShowCursor(FALSE);
+    }
+
+    // Wrap a bitmap around it.
+    bmp = tigrBitmap2(w, h, sizeof(TigrInternal));
+    bmp->handle = hWnd;
+
+    // Set up the Windows parts.
+    win = tigrInternal(bmp);
+    win->dwStyle = dwStyle;
+    win->wtitle = wtitle;
+    win->shown = 0;
+    win->closed = 0;
+    win->scale = scale;
+    win->lastChar = 0;
+    win->flags = flags;
+
+    win->p1 = win->p2 = win->p3 = 0;
+    win->p4 = 1;
+
+    win->widgetsWanted = 0;
+    win->widgetAlpha = 0;
+    win->widgetsScale = WIDGET_SCALE;
+    win->widgets = tigrBitmap(40, 14);
+
+    SetPropW(hWnd, L"Tigr", bmp);
+
+    tigrGAPICreate(bmp);
+
+    if (flags & TIGR_FULLSCREEN) {
+        tigrEnterBorderlessWindowed(bmp);
+    } else {
+// Try and restore our window position.
+#ifndef TIGR_DO_NOT_PRESERVE_WINDOW_POSITION
+        if (RegQueryValueExW(tigrRegKey, wtitle, NULL, NULL, (BYTE*)&wp, &wpsize) == ERROR_SUCCESS) {
+            if (wp.showCmd == SW_MAXIMIZE)
+                tigrEnterBorderlessWindowed(bmp);
+            else
+                SetWindowPlacement(hWnd, &wp);
+        }
+#endif
+    }
+
+    wglSwapIntervalEXT_ = (PFNWGLSWAPINTERVALFARPROC_)wglGetProcAddress("wglSwapIntervalEXT");
+    if (wglSwapIntervalEXT_)
+        wglSwapIntervalEXT_(1);
+
+    return bmp;
+}
+
+void tigrFree(Tigr* bmp) {
+    if (bmp->handle) {
+        TigrInternal* win = tigrInternal(bmp);
+        tigrGAPIDestroy(bmp);
+
+        if (win->gl.hglrc && !wglDeleteContext(win->gl.hglrc)) {
+            tigrError(bmp, "Cannot delete OpenGL context.\n");
+        }
+        win->gl.hglrc = NULL;
+
+        if (win->gl.dc && !ReleaseDC((HWND)bmp->handle, win->gl.dc)) {
+            tigrError(bmp, "Cannot release OpenGL device context.\n");
+        }
+        win->gl.dc = NULL;
+
+        DestroyWindow((HWND)bmp->handle);
+        free(win->wtitle);
+        tigrFree(win->widgets);
+    }
+    free(bmp->pix);
+    free(bmp);
+}
+
+int tigrClosed(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    int val = win->closed;
+    win->closed = 0;
+    return val;
+}
+
+float tigrTime(void) {
+    static int first = 1;
+    static LARGE_INTEGER prev;
+
+    LARGE_INTEGER cnt, freq;
+    ULONGLONG diff;
+    QueryPerformanceCounter(&cnt);
+    QueryPerformanceFrequency(&freq);
+
+    if (first) {
+        first = 0;
+        prev = cnt;
+    }
+
+    diff = cnt.QuadPart - prev.QuadPart;
+    prev = cnt;
+    return (float)(diff / (double)freq.QuadPart);
+}
+
+void tigrMouse(Tigr* bmp, int* x, int* y, int* buttons) {
+    POINT pt;
+    TigrInternal* win;
+
+    win = tigrInternal(bmp);
+    GetCursorPos(&pt);
+    ScreenToClient((HWND)bmp->handle, &pt);
+    *x = (pt.x - win->pos[0]) / win->scale;
+    *y = (pt.y - win->pos[1]) / win->scale;
+    *buttons = 0;
+    if (GetFocus() != bmp->handle)
+        return;
+    if (GetAsyncKeyState(VK_LBUTTON) & 0x8000)
+        *buttons |= 1;
+    if (GetAsyncKeyState(VK_MBUTTON) & 0x8000)
+        *buttons |= 2;
+    if (GetAsyncKeyState(VK_RBUTTON) & 0x8000)
+        *buttons |= 4;
+}
+
+int tigrTouch(Tigr* bmp, TigrTouchPoint* points, int maxPoints) {
+    int buttons = 0;
+    if (maxPoints > 0) {
+        tigrMouse(bmp, &points[0].x, &points[1].y, &buttons);
+    }
+    return buttons ? 1 : 0;
+}
+
+void tigrScrollWheel(Tigr* bmp, float* x, float* y) {
+    TigrInternal* win;
+    win = tigrInternal(bmp);
+    if (x) {
+        *x = win->scrollDeltaX;
+    }
+    if (y) {
+        *y = win->scrollDeltaY;
+    }
+}
+
+static int tigrWinVK(int key) {
+    if (key >= 'A' && key <= 'Z')
+        return key;
+    if (key >= '0' && key <= '9')
+        return key;
+    switch (key) {
+        case TK_BACKSPACE:
+            return VK_BACK;
+        case TK_TAB:
+            return VK_TAB;
+        case TK_RETURN:
+            return VK_RETURN;
+        case TK_SHIFT:
+            return VK_SHIFT;
+        case TK_CONTROL:
+            return VK_CONTROL;
+        case TK_ALT:
+            return VK_MENU;
+        case TK_PAUSE:
+            return VK_PAUSE;
+        case TK_CAPSLOCK:
+            return VK_CAPITAL;
+        case TK_ESCAPE:
+            return VK_ESCAPE;
+        case TK_SPACE:
+            return VK_SPACE;
+        case TK_PAGEUP:
+            return VK_PRIOR;
+        case TK_PAGEDN:
+            return VK_NEXT;
+        case TK_END:
+            return VK_END;
+        case TK_HOME:
+            return VK_HOME;
+        case TK_LEFT:
+            return VK_LEFT;
+        case TK_UP:
+            return VK_UP;
+        case TK_RIGHT:
+            return VK_RIGHT;
+        case TK_DOWN:
+            return VK_DOWN;
+        case TK_INSERT:
+            return VK_INSERT;
+        case TK_DELETE:
+            return VK_DELETE;
+        case TK_LWIN:
+            return VK_LWIN;
+        case TK_RWIN:
+            return VK_RWIN;
+        // case TK_APPS: return VK_APPS; // this key doesn't exist on OS X
+        case TK_PAD0:
+            return VK_NUMPAD0;
+        case TK_PAD1:
+            return VK_NUMPAD1;
+        case TK_PAD2:
+            return VK_NUMPAD2;
+        case TK_PAD3:
+            return VK_NUMPAD3;
+        case TK_PAD4:
+            return VK_NUMPAD4;
+        case TK_PAD5:
+            return VK_NUMPAD5;
+        case TK_PAD6:
+            return VK_NUMPAD6;
+        case TK_PAD7:
+            return VK_NUMPAD7;
+        case TK_PAD8:
+            return VK_NUMPAD8;
+        case TK_PAD9:
+            return VK_NUMPAD9;
+        case TK_PADMUL:
+            return VK_MULTIPLY;
+        case TK_PADADD:
+            return VK_ADD;
+        case TK_PADENTER:
+            return VK_SEPARATOR;
+        case TK_PADSUB:
+            return VK_SUBTRACT;
+        case TK_PADDOT:
+            return VK_DECIMAL;
+        case TK_PADDIV:
+            return VK_DIVIDE;
+        case TK_F1:
+            return VK_F1;
+        case TK_F2:
+            return VK_F2;
+        case TK_F3:
+            return VK_F3;
+        case TK_F4:
+            return VK_F4;
+        case TK_F5:
+            return VK_F5;
+        case TK_F6:
+            return VK_F6;
+        case TK_F7:
+            return VK_F7;
+        case TK_F8:
+            return VK_F8;
+        case TK_F9:
+            return VK_F9;
+        case TK_F10:
+            return VK_F10;
+        case TK_F11:
+            return VK_F11;
+        case TK_F12:
+            return VK_F12;
+        case TK_NUMLOCK:
+            return VK_NUMLOCK;
+        case TK_SCROLL:
+            return VK_SCROLL;
+        case TK_LSHIFT:
+            return VK_LSHIFT;
+        case TK_RSHIFT:
+            return VK_RSHIFT;
+        case TK_LCONTROL:
+            return VK_LCONTROL;
+        case TK_RCONTROL:
+            return VK_RCONTROL;
+        case TK_LALT:
+            return VK_LMENU;
+        case TK_RALT:
+            return VK_RMENU;
+        case TK_SEMICOLON:
+            return VK_OEM_1;
+        case TK_EQUALS:
+            return VK_OEM_PLUS;
+        case TK_COMMA:
+            return VK_OEM_COMMA;
+        case TK_MINUS:
+            return VK_OEM_MINUS;
+        case TK_DOT:
+            return VK_OEM_PERIOD;
+        case TK_SLASH:
+            return VK_OEM_2;
+        case TK_BACKTICK:
+            return VK_OEM_3;
+        case TK_LSQUARE:
+            return VK_OEM_4;
+        case TK_BACKSLASH:
+            return VK_OEM_5;
+        case TK_RSQUARE:
+            return VK_OEM_6;
+        case TK_TICK:
+            return VK_OEM_7;
+    }
+    return 0;
+}
+
+int tigrKeyDown(Tigr* bmp, int key) {
+    TigrInternal* win;
+    int k = tigrWinVK(key);
+    if (GetFocus() != bmp->handle)
+        return 0;
+    win = tigrInternal(bmp);
+    return win->keys[k] && !win->prev[k];
+}
+
+int tigrKeyHeld(Tigr* bmp, int key) {
+    TigrInternal* win;
+    int k = tigrWinVK(key);
+    if (GetFocus() != bmp->handle)
+        return 0;
+    win = tigrInternal(bmp);
+    return win->keys[k];
+}
+
+int tigrReadChar(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    int c = win->lastChar;
+    win->lastChar = 0;
+    return c;
+}
+
+// We supply our own WinMain and just chain through to the user's
+// real entry point.
+#ifdef UNICODE
+int CALLBACK wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
+#else
+int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
+#endif
+{
+    int n, argc;
+    LPWSTR* wargv = CommandLineToArgvW(GetCommandLineW(), &argc);
+    char** argv = (char**)calloc(argc + 1, sizeof(int));
+
+    (void)hInstance;
+    (void)hPrevInstance;
+    (void)lpCmdLine;
+    (void)nCmdShow;
+
+    for (n = 0; n < argc; n++) {
+        int len = WideCharToMultiByte(CP_UTF8, 0, wargv[n], -1, 0, 0, NULL, NULL);
+        argv[n] = (char*)malloc(len);
+        WideCharToMultiByte(CP_UTF8, 0, wargv[n], -1, argv[n], len, NULL, NULL);
+    }
+    return main(argc, argv);
+}
+#endif
+
+#endif // #ifndef TIGR_HEADLESS

+ 6609 - 0
tigr-master/tigr.c

@@ -0,0 +1,6609 @@
+//////// Start of inlined file: tigr_amalgamated.c ////////
+
+
+#include "tigr.h"
+
+//////// Start of inlined file: tigr_internal.h ////////
+
+// can't use pragma once here because this file probably will endup in .c
+#ifndef __TIGR_INTERNAL_H__
+#define __TIGR_INTERNAL_H__
+
+#define _CRT_SECURE_NO_WARNINGS NOPE
+
+// Graphics configuration.
+#ifndef TIGR_HEADLESS
+#define TIGR_GAPI_GL
+
+// Calculates the biggest scale that a bitmap can fit into an area at.
+int tigrCalcScale(int bmpW, int bmpH, int areaW, int areaH);
+#endif
+
+// Creates a new bitmap, with extra payload bytes.
+Tigr* tigrBitmap2(int w, int h, int extra);
+
+// Resizes an existing bitmap.
+void tigrResize(Tigr* bmp, int w, int h);
+
+// Calculates a new scale, taking minimum-scale flags into account.
+int tigrEnforceScale(int scale, int flags);
+
+// Calculates the correct position for a bitmap to fit into a window.
+void tigrPosition(Tigr* bmp, int scale, int windowW, int windowH, int out[4]);
+
+// ----------------------------------------------------------
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif
+
+#if !defined(TIGR_HEADLESS) && __linux__ && !__ANDROID__
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#endif
+
+#ifdef __APPLE__
+#include "TargetConditionals.h"
+#if TARGET_OS_IPHONE
+#define __IOS__ 1
+#else
+#define __MACOS__ 1
+#endif
+#endif
+
+#ifdef TIGR_GAPI_GL
+#if __MACOS__
+#define GL_SILENCE_DEPRECATION
+#include <OpenGL/gl3.h>
+#endif
+#ifdef _WIN32
+#include <GL/gl.h>
+#endif
+#if __linux__ && !__ANDROID__
+#define GL_GLEXT_PROTOTYPES
+#include <GL/gl.h>
+#include <GL/glx.h>
+#endif
+#if __ANDROID__
+#include <EGL/egl.h>
+#include <GLES3/gl3.h>
+#endif
+#if __IOS__
+#define GLES_SILENCE_DEPRECATION
+#include <OpenGLES/ES3/gl.h>
+#endif
+
+typedef struct {
+#ifdef _WIN32
+    HGLRC hglrc;
+    HDC dc;
+#endif
+#ifdef __APPLE__
+    void* glContext;
+#endif
+    GLuint tex[2];
+    GLuint vao;
+    GLuint program;
+    GLuint uniform_projection;
+    GLuint uniform_model;
+    GLuint uniform_parameters;
+    int gl_legacy;
+    int gl_user_opengl_rendering;
+} GLStuff;
+#endif
+
+#define MAX_TOUCH_POINTS 10
+
+typedef struct {
+    int shown, closed;
+#ifdef TIGR_GAPI_GL
+    GLStuff gl;
+#ifdef _WIN32
+    wchar_t* wtitle;
+    DWORD dwStyle;
+    RECT oldPos;
+#endif
+#ifdef __linux__
+#if __ANDROID__
+    EGLContext context;
+#else
+    Display* dpy;
+    Window win;
+    GLXContext glc;
+    XIC ic;
+#endif  // __ANDROID__
+#endif  // __linux__
+#endif  // TIGR_GAPI_GL
+
+    Tigr* widgets;
+    int widgetsWanted;
+    unsigned char widgetAlpha;
+    float widgetsScale;
+
+    float p1, p2, p3, p4;
+
+    int flags;
+    int scale;
+    int pos[4];
+    int lastChar;
+    char keys[256], prev[256];
+#if defined(__ANDROID__)
+    char released[256];
+#endif  // __ANDROID__
+#if defined(__MACOS__)
+    int mouseInView;
+    int mouseButtons;
+    double frameTime;
+#endif  // __MACOS__
+#if defined(__linux__) || defined(__IOS__)
+    int mouseButtons;
+    int mouseX;
+    int mouseY;
+#endif  // __linux__ __IOS__
+#if defined(__ANDROID__) || defined(__IOS__)
+    int numTouchPoints;
+    TigrTouchPoint touchPoints[MAX_TOUCH_POINTS];
+#endif  // __ANDROID__ __IOS__
+#if defined(_WIN32) || defined(__linux__) || defined(__MACOS__)
+    float scrollDeltaX;
+    float scrollDeltaY;
+#endif  // _WIN32 __linux__ __MACOS__
+} TigrInternal;
+// ----------------------------------------------------------
+
+TigrInternal* tigrInternal(Tigr* bmp);
+
+void tigrGAPICreate(Tigr* bmp);
+void tigrGAPIDestroy(Tigr* bmp);
+int tigrGAPIBegin(Tigr* bmp);
+int tigrGAPIEnd(Tigr* bmp);
+void tigrGAPIPresent(Tigr* bmp, int w, int h);
+
+#endif
+
+//////// End of inlined file: tigr_internal.h ////////
+
+//////// Start of inlined file: tigr_upscale_gl_vs.h ////////
+
+#ifndef __TIGR_UPSCALE_GL_VS_H__
+#define __TIGR_UPSCALE_GL_VS_H__
+
+//////// Start of inlined file: tigr_glsl_hdr.h ////////
+
+#ifndef __TIGR_GLSL_HDR_H__
+#define __TIGR_GLSL_HDR_H__
+
+#if __ANDROID__ || __IOS__
+#define GLSL_VERSION_HEADER \
+    "#version 300 es\n"     \
+    "precision mediump float;\n"
+#else
+#define GLSL_VERSION_HEADER "#version 330 core\n"
+#endif
+
+#endif  // __TIGR_GLSL_HDR_H__
+
+//////// End of inlined file: tigr_glsl_hdr.h ////////
+
+
+// clang-format off
+const char tigr_upscale_gl_vs[] = {
+    GLSL_VERSION_HEADER
+    "layout (location = 0) in vec2 pos_in;"
+    "layout (location = 1) in vec2 uv_in;"
+    "out vec2 uv;"
+    "uniform mat4 model;"
+    "uniform mat4 projection;"
+    "void main()"
+    "{"
+    "   uv = uv_in;"
+    "   gl_Position = projection * model * vec4(pos_in, 0.0, 1.0);"
+    "}"
+};
+// clang-format on
+
+const int tigr_upscale_gl_vs_size = (int)sizeof(tigr_upscale_gl_vs) - 1;
+
+#endif
+
+//////// End of inlined file: tigr_upscale_gl_vs.h ////////
+
+//////// Start of inlined file: tigr_upscale_gl_fs.h ////////
+
+#ifndef __TIGR_UPSCALE_GL_FS_H__
+#define __TIGR_UPSCALE_GL_FS_H__
+
+//#include "tigr_glsl_hdr.h"
+
+// clang-format off
+const char tigr_upscale_gl_fs[] = {
+    GLSL_VERSION_HEADER
+    "in vec2 uv;"
+    "out vec4 color;"
+    "uniform sampler2D image;"
+    "uniform vec4 parameters;"
+    "void fxShader(out vec4 color, in vec2 coord);"
+    "void main()"
+    "{"
+    "   fxShader(color, uv);"
+    "}\n"
+};
+// clang-format on
+
+const int tigr_upscale_gl_fs_size = (int)sizeof(tigr_upscale_gl_fs) - 1;
+
+// clang-format off
+const char tigr_default_fx_gl_fs[] = {
+    "void fxShader(out vec4 color, in vec2 uv) {"
+    "   vec2 tex_size = vec2(textureSize(image, 0));"
+    "   vec2 uv_blur = mix(floor(uv * tex_size) + 0.5, uv * tex_size, parameters.xy) / tex_size;"
+    "   vec4 c = texture(image, uv_blur);"
+    "   c.rgb *= mix(0.5, 1.0 - fract(uv.y * tex_size.y), parameters.z) * 2.0; //scanline\n"
+    "   c = mix(vec4(0.5), c, parameters.w); //contrast\n"
+    "   color = c;"
+    "}"
+};
+// clang-format on
+
+const int tigr_default_fx_gl_fs_size = (int)sizeof(tigr_default_fx_gl_fs) - 1;
+
+#endif
+
+//////// End of inlined file: tigr_upscale_gl_fs.h ////////
+
+
+//////// Start of inlined file: tigr_bitmaps.c ////////
+
+//#include "tigr_internal.h"
+#include <stdlib.h>
+#include <string.h>
+
+// Expands 0-255 into 0-256
+#define EXPAND(X) ((X) + ((X) > 0))
+
+#define CLIP0(CX, X, X2, W) \
+    if (X < CX) {           \
+        int D = CX - X;     \
+        W -= D;             \
+        X2 += D;            \
+        X += D;             \
+    }
+#define CLIP1(X, DW, W) \
+    if (X + W > DW)     \
+        W = DW - X;
+#define CLIP()                  \
+    CLIP0(dst->cx, dx, sx, w);  \
+    CLIP0(dst->cy, dy, sy, h);  \
+    CLIP0(0, sx, dx, w);        \
+    CLIP0(0, sy, dy, h);        \
+    CLIP1(dx, dst->cx + cw, w); \
+    CLIP1(dy, dst->cy + ch, h); \
+    CLIP1(sx, src->w, w);       \
+    CLIP1(sy, src->h, h);       \
+    if (w <= 0 || h <= 0)       \
+    return
+
+Tigr* tigrBitmap2(int w, int h, int extra) {
+    Tigr* tigr = (Tigr*)calloc(1, sizeof(Tigr) + extra);
+    tigr->w = w;
+    tigr->h = h;
+    tigr->cw = -1;
+    tigr->ch = -1;
+    tigr->pix = (TPixel*)calloc(w * h, sizeof(TPixel));
+    tigr->blitMode = TIGR_BLEND_ALPHA;
+    return tigr;
+}
+
+Tigr* tigrBitmap(int w, int h) {
+    return tigrBitmap2(w, h, 0);
+}
+
+#ifdef TIGR_HEADLESS
+void tigrFree(Tigr* bmp) {
+    free(bmp->pix);
+    free(bmp);
+}
+#endif // TIGR_HEADLESS
+
+
+void tigrResize(Tigr* bmp, int w, int h) {
+    if (bmp->w == w && bmp->h == h) {
+        return;
+    }
+
+    int y, cw, ch;
+    TPixel* newpix = (TPixel*)calloc(w * h, sizeof(TPixel));
+    cw = (w < bmp->w) ? w : bmp->w;
+    ch = (h < bmp->h) ? h : bmp->h;
+
+    // Copy any old data across.
+    for (y = 0; y < ch; y++)
+        memcpy(newpix + y * w, bmp->pix + y * bmp->w, cw * sizeof(TPixel));
+
+    free(bmp->pix);
+    bmp->pix = newpix;
+    bmp->w = w;
+    bmp->h = h;
+}
+
+#ifndef TIGR_HEADLESS
+int tigrCalcScale(int bmpW, int bmpH, int areaW, int areaH) {
+    if (bmpW == 0 || bmpH == 0) {
+        tigrError(0, "Invalid bitmap size, (%dx%d)", bmpW, bmpH);
+    }
+    // We want it as big as possible in the window, but still
+    // maintaining the correct aspect ratio, and always
+    // having an integer pixel size.
+    int scale = 0;
+    for (;;) {
+        scale++;
+        if (bmpW * scale > areaW || bmpH * scale > areaH) {
+            scale--;
+            break;
+        }
+    }
+    return (scale > 1) ? scale : 1;
+}
+#endif // TIGR_HEADLESS
+
+int tigrEnforceScale(int scale, int flags) {
+    if ((flags & TIGR_4X) && scale < 4)
+        scale = 4;
+    if ((flags & TIGR_3X) && scale < 3)
+        scale = 3;
+    if ((flags & TIGR_2X) && scale < 2)
+        scale = 2;
+    return scale;
+}
+
+void tigrPosition(Tigr* bmp, int scale, int windowW, int windowH, int out[4]) {
+    // Center the image on screen at this scale.
+    out[0] = (windowW - bmp->w * scale) / 2;
+    out[1] = (windowH - bmp->h * scale) / 2;
+    out[2] = out[0] + bmp->w * scale;
+    out[3] = out[1] + bmp->h * scale;
+}
+
+void tigrClear(Tigr* bmp, TPixel color) {
+    int count = bmp->w * bmp->h;
+    int n;
+    for (n = 0; n < count; n++)
+        bmp->pix[n] = color;
+}
+
+void tigrFill(Tigr* bmp, int x, int y, int w, int h, TPixel color) {
+    TPixel* td;
+    int dt, i;
+
+    if (x < 0) {
+        w += x;
+        x = 0;
+    }
+    if (y < 0) {
+        h += y;
+        y = 0;
+    }
+    if (x + w > bmp->w) {
+        w = bmp->w - x;
+    }
+    if (y + h > bmp->h) {
+        h = bmp->h - y;
+    }
+    if (w <= 0 || h <= 0)
+        return;
+
+    td = &bmp->pix[y * bmp->w + x];
+    dt = bmp->w;
+    do {
+        for (i = 0; i < w; i++)
+            td[i] = color;
+        td += dt;
+    } while (--h);
+}
+
+void tigrLine(Tigr* bmp, int x0, int y0, int x1, int y1, TPixel color) {
+    int sx, sy, dx, dy, err, e2;
+    dx = abs(x1 - x0);
+    dy = abs(y1 - y0);
+    if (x0 < x1)
+        sx = 1;
+    else
+        sx = -1;
+    if (y0 < y1)
+        sy = 1;
+    else
+        sy = -1;
+    err = dx - dy;
+
+    do {
+        tigrPlot(bmp, x0, y0, color);
+        e2 = 2 * err;
+        if (e2 > -dy) {
+            err -= dy;
+            x0 += sx;
+        }
+        if (e2 < dx) {
+            err += dx;
+            y0 += sy;
+        }
+    } while (x0 != x1 || y0 != y1);
+}
+
+void tigrFillRect(Tigr* bmp, int x, int y, int w, int h, TPixel color) {
+    x += 1;
+    y += 1;
+    w -= 2;
+    h -= 2;
+
+    int cx = bmp->cx;
+    int cy = bmp->cy;
+    int cw = bmp->cw >= 0 ? bmp->cw : bmp->w;
+    int ch = bmp->ch >= 0 ? bmp->ch : bmp->h;
+
+    if (x < cx) {
+        w += (x - cx);
+        x = cx;
+    }
+    if (y < cy) {
+        h += (y - cy);
+        y = cy;
+    }
+    if (x + w > cx + cw) {
+        w -= (x + w) - (cx + cw);
+    }
+    if (y + h > cy + ch) {
+        h -= (y + h) - (cy + ch);
+    }
+    if (w <= 0 || h <= 0)
+        return;
+
+    TPixel* td = &bmp->pix[y * bmp->w + x];
+    int dt = bmp->w;
+    int xa = EXPAND(color.a);
+    int a = xa * xa;
+
+    do {
+        for (int i = 0; i < w; i++) {
+            td[i].r += (unsigned char)((color.r - td[i].r) * a >> 16);
+            td[i].g += (unsigned char)((color.g - td[i].g) * a >> 16);
+            td[i].b += (unsigned char)((color.b - td[i].b) * a >> 16);
+            td[i].a += (bmp->blitMode) * (unsigned char)((color.a - td[i].a) * a >> 16);
+        }
+        td += dt;
+    } while (--h);
+}
+
+void tigrRect(Tigr* bmp, int x, int y, int w, int h, TPixel color) {
+    int x1, y1;
+    if (w <= 0 || h <= 0) {
+        return;
+    }
+
+    if (w == 1) {
+        tigrLine(bmp, x, y, x, y + h, color);
+    } else if (h == 1) {
+        tigrLine(bmp, x, y, x + w, y, color);
+    } else {
+        x1 = x + w - 1;
+        y1 = y + h - 1;
+        tigrLine(bmp, x, y, x1, y, color);
+        tigrLine(bmp, x1, y, x1, y1, color);
+        tigrLine(bmp, x1, y1, x, y1, color);
+        tigrLine(bmp, x, y1, x, y, color);
+    }
+}
+
+void tigrFillCircle(Tigr* bmp, int x0, int y0, int r, TPixel color) {
+    if (r <= 0) {
+        return;
+    }
+
+    int E = 1 - r;
+    int dx = 0;
+    int dy = -2 * r;
+    int x = 0;
+    int y = r;
+
+    tigrLine(bmp, x0 - r + 1, y0, x0 + r, y0, color);
+
+    while (x < y - 1) {
+        x++;
+
+        if (E >= 0) {
+            y--;
+            dy += 2;
+            E += dy;
+            tigrLine(bmp, x0 - x + 1, y0 + y, x0 + x, y0 + y, color);
+            tigrLine(bmp, x0 - x + 1, y0 - y, x0 + x, y0 - y, color);
+        }
+
+        dx += 2;
+        E += dx + 1;
+
+        if (x != y) {
+            tigrLine(bmp, x0 - y + 1, y0 + x, x0 + y, y0 + x, color);
+            tigrLine(bmp, x0 - y + 1, y0 - x, x0 + y, y0 - x, color);
+        }
+    }
+}
+
+void tigrCircle(Tigr* bmp, int x0, int y0, int r, TPixel color) {
+    int E = 1 - r;
+    int dx = 0;
+    int dy = -2 * r;
+    int x = 0;
+    int y = r;
+
+    tigrPlot(bmp, x0, y0 + r, color);
+    tigrPlot(bmp, x0, y0 - r, color);
+    tigrPlot(bmp, x0 + r, y0, color);
+    tigrPlot(bmp, x0 - r, y0, color);
+
+    while (x < y - 1) {
+        x++;
+
+        if (E >= 0) {
+            y--;
+            dy += 2;
+            E += dy;
+        }
+
+        dx += 2;
+        E += dx + 1;
+
+        tigrPlot(bmp, x0 + x, y0 + y, color);
+        tigrPlot(bmp, x0 - x, y0 + y, color);
+        tigrPlot(bmp, x0 + x, y0 - y, color);
+        tigrPlot(bmp, x0 - x, y0 - y, color);
+
+        if (x != y) {
+            tigrPlot(bmp, x0 + y, y0 + x, color);
+            tigrPlot(bmp, x0 - y, y0 + x, color);
+            tigrPlot(bmp, x0 + y, y0 - x, color);
+            tigrPlot(bmp, x0 - y, y0 - x, color);
+        }
+    }
+}
+
+TPixel tigrGet(Tigr* bmp, int x, int y) {
+    TPixel empty = { 0, 0, 0, 0 };
+    if (x >= 0 && y >= 0 && x < bmp->w && y < bmp->h)
+        return bmp->pix[y * bmp->w + x];
+    return empty;
+}
+
+void tigrPlot(Tigr* bmp, int x, int y, TPixel pix) {
+    int xa, i, a;
+
+    int cx = bmp->cx;
+    int cy = bmp->cy;
+    int cw = bmp->cw >= 0 ? bmp->cw : bmp->w;
+    int ch = bmp->ch >= 0 ? bmp->ch : bmp->h;
+
+    if (x >= cx && y >= cy && x < cx + cw && y < cy + ch) {
+        xa = EXPAND(pix.a);
+        a = xa * xa;
+        i = y * bmp->w + x;
+
+        bmp->pix[i].r += (unsigned char)((pix.r - bmp->pix[i].r) * a >> 16);
+        bmp->pix[i].g += (unsigned char)((pix.g - bmp->pix[i].g) * a >> 16);
+        bmp->pix[i].b += (unsigned char)((pix.b - bmp->pix[i].b) * a >> 16);
+        bmp->pix[i].a += (bmp->blitMode) * (unsigned char)((pix.a - bmp->pix[i].a) * a >> 16);
+    }
+}
+
+void tigrClip(Tigr* bmp, int cx, int cy, int cw, int ch) {
+    bmp->cx = cx;
+    bmp->cy = cy;
+    bmp->cw = cw;
+    bmp->ch = ch;
+}
+
+void tigrBlit(Tigr* dst, Tigr* src, int dx, int dy, int sx, int sy, int w, int h) {
+    int cw = dst->cw >= 0 ? dst->cw : dst->w;
+    int ch = dst->ch >= 0 ? dst->ch : dst->h;
+
+    CLIP();
+
+    TPixel* ts = &src->pix[sy * src->w + sx];
+    TPixel* td = &dst->pix[dy * dst->w + dx];
+    int st = src->w;
+    int dt = dst->w;
+    do {
+        memcpy(td, ts, w * sizeof(TPixel));
+        ts += st;
+        td += dt;
+    } while (--h);
+}
+
+void tigrBlitTint(Tigr* dst, Tigr* src, int dx, int dy, int sx, int sy, int w, int h, TPixel tint) {
+    int cw = dst->cw >= 0 ? dst->cw : dst->w;
+    int ch = dst->ch >= 0 ? dst->ch : dst->h;
+
+    CLIP();
+
+    int xr = EXPAND(tint.r);
+    int xg = EXPAND(tint.g);
+    int xb = EXPAND(tint.b);
+    int xa = EXPAND(tint.a);
+
+    TPixel* ts = &src->pix[sy * src->w + sx];
+    TPixel* td = &dst->pix[dy * dst->w + dx];
+    int st = src->w;
+    int dt = dst->w;
+    do {
+        for (int x = 0; x < w; x++) {
+            unsigned r = (xr * ts[x].r) >> 8;
+            unsigned g = (xg * ts[x].g) >> 8;
+            unsigned b = (xb * ts[x].b) >> 8;
+            unsigned a = xa * EXPAND(ts[x].a);
+            td[x].r += (unsigned char)((r - td[x].r) * a >> 16);
+            td[x].g += (unsigned char)((g - td[x].g) * a >> 16);
+            td[x].b += (unsigned char)((b - td[x].b) * a >> 16);
+            td[x].a += (dst->blitMode) * (unsigned char)((ts[x].a - td[x].a) * a >> 16);
+        }
+        ts += st;
+        td += dt;
+    } while (--h);
+}
+
+void tigrBlitAlpha(Tigr* dst, Tigr* src, int dx, int dy, int sx, int sy, int w, int h, float alpha) {
+    alpha = (alpha < 0) ? 0 : (alpha > 1 ? 1 : alpha);
+    tigrBlitTint(dst, src, dx, dy, sx, sy, w, h, tigrRGBA(0xff, 0xff, 0xff, (unsigned char)(alpha * 255)));
+}
+
+void tigrBlitMode(Tigr* dst, int mode) {
+    dst->blitMode = mode;
+}
+
+#undef CLIP0
+#undef CLIP1
+#undef CLIP
+
+//////// End of inlined file: tigr_bitmaps.c ////////
+
+//////// Start of inlined file: tigr_loadpng.c ////////
+
+//#include "tigr_internal.h"
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+typedef struct {
+    const unsigned char *p, *end;
+} PNG;
+
+static unsigned get32(const unsigned char* v) {
+    return (v[0] << 24) | (v[1] << 16) | (v[2] << 8) | v[3];
+}
+
+static const unsigned char* find(PNG* png, const char* chunk, unsigned minlen) {
+    const unsigned char* start;
+    while (png->p < png->end) {
+        unsigned len = get32(png->p + 0);
+        start = png->p;
+        png->p += len + 12;
+        if (memcmp(start + 4, chunk, 4) == 0 && len >= minlen && png->p <= png->end)
+            return start + 8;
+    }
+
+    return NULL;
+}
+
+static unsigned char paeth(unsigned char a, unsigned char b, unsigned char c) {
+    int p = a + b - c;
+    int pa = abs(p - a), pb = abs(p - b), pc = abs(p - c);
+    return (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c;
+}
+
+static int rowBytes(int w, int bipp) {
+    int rowBits = w * bipp;
+    return rowBits / 8 + ((rowBits % 8) ? 1 : 0);
+}
+
+static int unfilter(int w, int h, int bipp, unsigned char* raw) {
+    int len = rowBytes(w, bipp);
+    int bpp = rowBytes(1, bipp);
+    int x, y;
+    unsigned char* first = (unsigned char*)malloc(len + 1);
+    memset(first, 0, len + 1);
+    unsigned char* prev = first;
+    for (y = 0; y < h; y++, prev = raw, raw += len) {
+#define LOOP(A, B)            \
+    for (x = 0; x < bpp; x++) \
+        raw[x] += A;          \
+    for (; x < len; x++)      \
+        raw[x] += B;          \
+    break
+        switch (*raw++) {
+            case 0:
+                break;
+            case 1:
+                LOOP(0, raw[x - bpp]);
+            case 2:
+                LOOP(prev[x], prev[x]);
+            case 3:
+                LOOP(prev[x] / 2, (raw[x - bpp] + prev[x]) / 2);
+            case 4:
+                LOOP(prev[x], paeth(raw[x - bpp], prev[x], prev[x - bpp]));
+            default:
+                return 0;
+        }
+#undef LOOP
+    }
+    free(first);
+    return 1;
+}
+
+static void convert(int bypp, int w, int h, const unsigned char* src, TPixel* dest, const unsigned char* trns) {
+    int x, y;
+    for (y = 0; y < h; y++) {
+        src++;  // skip filter byte
+        for (x = 0; x < w; x++, src += bypp) {
+            switch (bypp) {
+                case 1: {
+                    unsigned char c = src[0];
+                    if (trns && c == *trns) {
+                        *dest++ = tigrRGBA(c, c, c, 0);
+                        break;
+                    } else {
+                        *dest++ = tigrRGB(c, c, c);
+                        break;
+                    }
+                }
+                case 2:
+                    *dest++ = tigrRGBA(src[0], src[0], src[0], src[1]);
+                    break;
+                case 3: {
+                    unsigned char r = src[0];
+                    unsigned char g = src[1];
+                    unsigned char b = src[2];
+                    if (trns && trns[1] == r && trns[3] == g && trns[5] == b) {
+                        *dest++ = tigrRGBA(r, g, b, 0);
+                        break;
+                    } else {
+                        *dest++ = tigrRGB(r, g, b);
+                        break;
+                    }
+                }
+                case 4:
+                    *dest++ = tigrRGBA(src[0], src[1], src[2], src[3]);
+                    break;
+            }
+        }
+    }
+}
+
+static void depalette(int w,
+                      int h,
+                      unsigned char* src,
+                      TPixel* dest,
+                      int bipp,
+                      const unsigned char* plte,
+                      const unsigned char* trns,
+                      int trnsSize) {
+    int x, y, c;
+    unsigned char alpha;
+    int mask = 0, len = 0;
+
+    switch (bipp) {
+        case 4:
+            mask = 15;
+            len = 1;
+            break;
+        case 2:
+            mask = 3;
+            len = 3;
+            break;
+        case 1:
+            mask = 1;
+            len = 7;
+    }
+
+    for (y = 0; y < h; y++) {
+        src++;  // skip filter byte
+        for (x = 0; x < w; x++) {
+            if (bipp == 8) {
+                c = *src++;
+            } else {
+                int pos = x & len;
+                c = (src[0] >> ((len - pos) * bipp)) & mask;
+                if (pos == len) {
+                    src++;
+                }
+            }
+            alpha = 255;
+            if (c < trnsSize) {
+                alpha = trns[c];
+            }
+            *dest++ = tigrRGBA(plte[c * 3 + 0], plte[c * 3 + 1], plte[c * 3 + 2], alpha);
+        }
+    }
+}
+
+#define FAIL()          \
+    {                   \
+        errno = EINVAL; \
+        goto err;       \
+    }
+#define CHECK(X) \
+    if (!(X))    \
+    FAIL()
+
+static int outsize(Tigr* bmp, int bipp) {
+    return (rowBytes(bmp->w, bipp) + 1) * bmp->h;
+}
+
+static Tigr* tigrLoadPng(PNG* png) {
+    const unsigned char *ihdr, *idat, *plte, *trns, *first;
+    int trnsSize = 0;
+    int depth, ctype, bipp;
+    int datalen = 0;
+    unsigned char *data = NULL, *out;
+    Tigr* bmp = NULL;
+
+    CHECK(memcmp(png->p, "\211PNG\r\n\032\n", 8) == 0);  // PNG signature
+    png->p += 8;
+    first = png->p;
+
+    // Read IHDR
+    ihdr = find(png, "IHDR", 13);
+    CHECK(ihdr);
+    depth = ihdr[8];
+    ctype = ihdr[9];
+    switch (ctype) {
+        case 0:
+            bipp = depth;
+            break;  // greyscale
+        case 2:
+            bipp = 3 * depth;
+            break;  // RGB
+        case 3:
+            bipp = depth;
+            break;  // paletted
+        case 4:
+            bipp = 2 * depth;
+            break;  // grey+alpha
+        case 6:
+            bipp = 4 * depth;
+            break;  // RGBA
+        default:
+            FAIL();
+    }
+
+    // Allocate bitmap (+1 width to save room for stupid PNG filter bytes)
+    bmp = tigrBitmap(get32(ihdr + 0) + 1, get32(ihdr + 4));
+    CHECK(bmp);
+    bmp->w--;
+
+    // We support 8-bit color components and 1, 2, 4 and 8 bit palette formats.
+    // No interlacing, or wacky filter types.
+    CHECK((depth != 16) && ihdr[10] == 0 && ihdr[11] == 0 && ihdr[12] == 0);
+
+    // Join IDAT chunks.
+    for (idat = find(png, "IDAT", 0); idat; idat = find(png, "IDAT", 0)) {
+        unsigned len = get32(idat - 8);
+        data = (unsigned char*)realloc(data, datalen + len);
+        if (!data)
+            break;
+
+        memcpy(data + datalen, idat, len);
+        datalen += len;
+    }
+
+    // Find palette.
+    png->p = first;
+    plte = find(png, "PLTE", 0);
+
+    // Find transparency info.
+    png->p = first;
+    trns = find(png, "tRNS", 0);
+    if (trns) {
+        trnsSize = get32(trns - 8);
+    }
+
+    CHECK(data && datalen >= 6);
+    CHECK((data[0] & 0x0f) == 0x08     // compression method (RFC 1950)
+          && (data[0] & 0xf0) <= 0x70  // window size
+          && (data[1] & 0x20) == 0);   // preset dictionary present
+
+    out = (unsigned char*)bmp->pix + outsize(bmp, 32) - outsize(bmp, bipp);
+    CHECK(tigrInflate(out, outsize(bmp, bipp), data + 2, datalen - 6));
+    CHECK(unfilter(bmp->w, bmp->h, bipp, out));
+
+    if (ctype == 3) {
+        CHECK(plte);
+        depalette(bmp->w, bmp->h, out, bmp->pix, bipp, plte, trns, trnsSize);
+    } else {
+        CHECK(bipp % 8 == 0);
+        convert(bipp / 8, bmp->w, bmp->h, out, bmp->pix, trns);
+    }
+
+    free(data);
+    return bmp;
+
+err:
+    if (data)
+        free(data);
+    if (bmp)
+        tigrFree(bmp);
+    return NULL;
+}
+
+#undef CHECK
+#undef FAIL
+
+Tigr* tigrLoadImageMem(const void* data, int length) {
+    PNG png;
+    png.p = (unsigned char*)data;
+    png.end = (unsigned char*)data + length;
+    return tigrLoadPng(&png);
+}
+
+Tigr* tigrLoadImage(const char* fileName) {
+    int len;
+    void* data;
+    Tigr* bmp;
+
+    data = tigrReadFile(fileName, &len);
+    if (!data)
+        return NULL;
+
+    bmp = tigrLoadImageMem(data, len);
+    free(data);
+    return bmp;
+}
+
+//////// End of inlined file: tigr_loadpng.c ////////
+
+//////// Start of inlined file: tigr_savepng.c ////////
+
+//#include "tigr_internal.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+typedef struct {
+    unsigned crc, adler, bits, prev, runlen;
+    FILE* out;
+    unsigned crcTable[256];
+} Save;
+
+static const unsigned crctable[16] = { 0,          0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4,
+                                       0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
+                                       0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c };
+
+static void put(Save* s, unsigned v) {
+    fputc(v, s->out);
+    s->crc = (s->crc >> 4) ^ crctable[(s->crc & 15) ^ (v & 15)];
+    s->crc = (s->crc >> 4) ^ crctable[(s->crc & 15) ^ (v >> 4)];
+}
+
+static void updateAdler(Save* s, unsigned v) {
+    unsigned s1 = s->adler & 0xffff, s2 = (s->adler >> 16) & 0xffff;
+    s1 = (s1 + v) % 65521;
+    s2 = (s2 + s1) % 65521;
+    s->adler = (s2 << 16) + s1;
+}
+
+static void put32(Save* s, unsigned v) {
+    put(s, (v >> 24) & 0xff);
+    put(s, (v >> 16) & 0xff);
+    put(s, (v >> 8) & 0xff);
+    put(s, v & 0xff);
+}
+
+void putbits(Save* s, unsigned data, unsigned bitcount) {
+    while (bitcount--) {
+        unsigned prev = s->bits;
+        s->bits = (s->bits >> 1) | ((data & 1) << 7);
+        data >>= 1;
+        if (prev & 1) {
+            put(s, s->bits);
+            s->bits = 0x80;
+        }
+    }
+}
+
+void putbitsr(Save* s, unsigned data, unsigned bitcount) {
+    while (bitcount--)
+        putbits(s, data >> bitcount, 1);
+}
+
+static void begin(Save* s, const char* id, unsigned len) {
+    put32(s, len);
+    s->crc = 0xffffffff;
+    put(s, id[0]);
+    put(s, id[1]);
+    put(s, id[2]);
+    put(s, id[3]);
+}
+
+static void literal(Save* s, unsigned v) {
+    // Encode a literal/length using the built-in tables.
+    // Could do better with a custom table but whatever.
+    if (v < 144)
+        putbitsr(s, 0x030 + v - 0, 8);
+    else if (v < 256)
+        putbitsr(s, 0x190 + v - 144, 9);
+    else if (v < 280)
+        putbitsr(s, 0x000 + v - 256, 7);
+    else
+        putbitsr(s, 0x0c0 + v - 280, 8);
+}
+
+static void encodelen(Save* s, unsigned code, unsigned bits, unsigned len) {
+    literal(s, code + (len >> bits));
+    putbits(s, len, bits);
+    putbits(s, 0, 5);
+}
+
+static void endrun(Save* s) {
+    s->runlen--;
+    literal(s, s->prev);
+
+    if (s->runlen >= 67)
+        encodelen(s, 277, 4, s->runlen - 67);
+    else if (s->runlen >= 35)
+        encodelen(s, 273, 3, s->runlen - 35);
+    else if (s->runlen >= 19)
+        encodelen(s, 269, 2, s->runlen - 19);
+    else if (s->runlen >= 11)
+        encodelen(s, 265, 1, s->runlen - 11);
+    else if (s->runlen >= 3)
+        encodelen(s, 257, 0, s->runlen - 3);
+    else
+        while (s->runlen--)
+            literal(s, s->prev);
+}
+
+static void encodeByte(Save* s, unsigned char v) {
+    updateAdler(s, v);
+
+    // Simple RLE compression. We could do better by doing a search
+    // to find matches, but this works pretty well TBH.
+    if (s->prev == v && s->runlen < 115) {
+        s->runlen++;
+    } else {
+        if (s->runlen)
+            endrun(s);
+
+        s->prev = v;
+        s->runlen = 1;
+    }
+}
+
+static void savePngHeader(Save* s, Tigr* bmp) {
+    fwrite("\211PNG\r\n\032\n", 8, 1, s->out);
+    begin(s, "IHDR", 13);
+    put32(s, bmp->w);
+    put32(s, bmp->h);
+    put(s, 8);  // bit depth
+    put(s, 6);  // RGBA
+    put(s, 0);  // compression (deflate)
+    put(s, 0);  // filter (standard)
+    put(s, 0);  // interlace off
+    put32(s, ~s->crc);
+}
+
+static long savePngData(Save* s, Tigr* bmp, long dataPos) {
+    int x, y;
+    long dataSize;
+    begin(s, "IDAT", 0);
+    put(s, 0x08);      // zlib compression method
+    put(s, 0x1d);      // zlib compression flags
+    putbits(s, 3, 3);  // zlib last block + fixed dictionary
+    for (y = 0; y < bmp->h; y++) {
+        TPixel* row = &bmp->pix[y * bmp->w];
+        TPixel prev = tigrRGBA(0, 0, 0, 0);
+
+        encodeByte(s, 1);  // sub filter
+        for (x = 0; x < bmp->w; x++) {
+            encodeByte(s, row[x].r - prev.r);
+            encodeByte(s, row[x].g - prev.g);
+            encodeByte(s, row[x].b - prev.b);
+            encodeByte(s, row[x].a - prev.a);
+            prev = row[x];
+        }
+    }
+    endrun(s);
+    literal(s, 256);  // terminator
+    while (s->bits != 0x80)
+        putbits(s, 0, 1);
+    put32(s, s->adler);
+    dataSize = (ftell(s->out) - dataPos) - 8;
+    put32(s, ~s->crc);
+    return dataSize;
+}
+
+int tigrSaveImage(const char* fileName, Tigr* bmp) {
+    Save s;
+    long dataPos, dataSize, err;
+
+    // TODO - unicode?
+    FILE* out = fopen(fileName, "wb");
+    if (!out)
+        return 1;
+
+    s.out = out;
+    s.adler = 1;
+    s.bits = 0x80;
+    s.prev = 0xffff;
+    s.runlen = 0;
+
+    savePngHeader(&s, bmp);
+    dataPos = ftell(s.out);
+    dataSize = savePngData(&s, bmp, dataPos);
+
+    // End chunk.
+    begin(&s, "IEND", 0);
+    put32(&s, ~s.crc);
+
+    // Write back payload size.
+    fseek(out, dataPos, SEEK_SET);
+    put32(&s, dataSize);
+
+    err = ferror(out);
+    fclose(out);
+    return !err;
+}
+
+//////// End of inlined file: tigr_savepng.c ////////
+
+//////// Start of inlined file: tigr_inflate.c ////////
+
+//#include "tigr_internal.h"
+#include <stdlib.h>
+#include <setjmp.h>
+
+typedef struct {
+    unsigned bits, count;
+    const unsigned char *in, *inend;
+    unsigned char *out, *outend;
+    jmp_buf jmp;
+    unsigned litcodes[288], distcodes[32], lencodes[19];
+    int tlit, tdist, tlen;
+} State;
+
+#define FAIL() longjmp(s->jmp, 1)
+#define CHECK(X) \
+    if (!(X))    \
+    FAIL()
+
+// Built-in DEFLATE standard tables.
+static char order[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
+static char lenBits[29 + 2] = { 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 lenBase[29 + 2] = { 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 char distBits[30 + 2] = { 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, 0, 0 };
+static int distBase[30 + 2] = {
+    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
+};
+
+// Table to bit-reverse a byte.
+static const unsigned char reverseTable[256] = {
+#define R2(n) n, n + 128, n + 64, n + 192
+#define R4(n) R2(n), R2(n + 32), R2(n + 16), R2(n + 48)
+#define R6(n) R4(n), R4(n + 8), R4(n + 4), R4(n + 12)
+    R6(0), R6(2), R6(1), R6(3)
+};
+
+static unsigned rev16(unsigned n) {
+    return (reverseTable[n & 0xff] << 8) | reverseTable[(n >> 8) & 0xff];
+}
+
+static int bits(State* s, int n) {
+    int v = s->bits & ((1 << n) - 1);
+    s->bits >>= n;
+    s->count -= n;
+    while (s->count < 16) {
+        CHECK(s->in != s->inend);
+        s->bits |= (*s->in++) << s->count;
+        s->count += 8;
+    }
+    return v;
+}
+
+static unsigned char* emit(State* s, int len) {
+    s->out += len;
+    CHECK(s->out <= s->outend);
+    return s->out - len;
+}
+
+static void copy(State* s, const unsigned char* src, int len) {
+    unsigned char* dest = emit(s, len);
+    while (len--)
+        *dest++ = *src++;
+}
+
+static int build(State* s, unsigned* tree, unsigned char* lens, unsigned int symcount) {
+    unsigned int codes[16], first[16], counts[16] = { 0 };
+
+    // Frequency count.
+    for (unsigned int n = 0; n < symcount; n++)
+        counts[lens[n]]++;
+
+    // Distribute codes.
+    counts[0] = codes[0] = first[0] = 0;
+    for (unsigned int n = 1; n <= 15; n++) {
+        codes[n] = (codes[n - 1] + counts[n - 1]) << 1;
+        first[n] = first[n - 1] + counts[n - 1];
+    }
+    CHECK(first[15] + counts[15] <= symcount);
+
+    // Insert keys into the tree for each symbol.
+    for (unsigned int n = 0; n < symcount; n++) {
+        int len = lens[n];
+        if (len != 0) {
+            unsigned code = codes[len]++, slot = first[len]++;
+            tree[slot] = (code << (32 - len)) | (n << 4) | len;
+        }
+    }
+
+    return first[15];
+}
+
+static int decode(State* s, unsigned tree[], int max) {
+    // Find the next prefix code.
+    unsigned lo = 0, hi = max, key;
+    unsigned search = (rev16(s->bits) << 16) | 0xffff;
+    while (lo < hi) {
+        unsigned guess = (lo + hi) / 2;
+        if (search < tree[guess])
+            hi = guess;
+        else
+            lo = guess + 1;
+    }
+
+    // Pull out the key and check it.
+    key = tree[lo - 1];
+    CHECK(((search ^ key) >> (32 - (key & 0xf))) == 0);
+
+    bits(s, key & 0xf);
+    return (key >> 4) & 0xfff;
+}
+
+static void run(State* s, int sym) {
+    int length = bits(s, lenBits[sym]) + lenBase[sym];
+    int dsym = decode(s, s->distcodes, s->tdist);
+    int offs = bits(s, distBits[dsym]) + distBase[dsym];
+    copy(s, s->out - offs, length);
+}
+
+static void block(State* s) {
+    for (;;) {
+        int sym = decode(s, s->litcodes, s->tlit);
+        if (sym < 256)
+            *emit(s, 1) = (unsigned char)sym;
+        else if (sym > 256)
+            run(s, sym - 257);
+        else
+            break;
+    }
+}
+
+static void stored(State* s) {
+    // Uncompressed data block.
+    int len;
+    bits(s, s->count & 7);
+    len = bits(s, 16);
+    CHECK(((len ^ s->bits) & 0xffff) == 0xffff);
+    CHECK(s->in + len <= s->inend);
+
+    copy(s, s->in, len);
+    s->in += len;
+    bits(s, 16);
+}
+
+static void fixed(State* s) {
+    // Fixed set of Huffman codes.
+    int n;
+    unsigned char lens[288 + 32];
+    for (n = 0; n <= 143; n++)
+        lens[n] = 8;
+    for (n = 144; n <= 255; n++)
+        lens[n] = 9;
+    for (n = 256; n <= 279; n++)
+        lens[n] = 7;
+    for (n = 280; n <= 287; n++)
+        lens[n] = 8;
+    for (n = 0; n < 32; n++)
+        lens[288 + n] = 5;
+
+    // Build lit/dist trees.
+    s->tlit = build(s, s->litcodes, lens, 288);
+    s->tdist = build(s, s->distcodes, lens + 288, 32);
+}
+
+static void dynamic(State* s) {
+    int n, i, nlit, ndist, nlen;
+    unsigned char lenlens[19] = { 0 }, lens[288 + 32];
+    nlit = 257 + bits(s, 5);
+    ndist = 1 + bits(s, 5);
+    nlen = 4 + bits(s, 4);
+    for (n = 0; n < nlen; n++)
+        lenlens[(int) order[n]] = (unsigned char)bits(s, 3);
+
+    // Build the tree for decoding code lengths.
+    s->tlen = build(s, s->lencodes, lenlens, 19);
+
+    // Decode code lengths.
+    for (n = 0; n < nlit + ndist;) {
+        int sym = decode(s, s->lencodes, s->tlen);
+        switch (sym) {
+            case 16:
+                for (i = 3 + bits(s, 2); i; i--, n++)
+                    lens[n] = lens[n - 1];
+                break;
+            case 17:
+                for (i = 3 + bits(s, 3); i; i--, n++)
+                    lens[n] = 0;
+                break;
+            case 18:
+                for (i = 11 + bits(s, 7); i; i--, n++)
+                    lens[n] = 0;
+                break;
+            default:
+                lens[n++] = (unsigned char)sym;
+                break;
+        }
+    }
+
+    // Build lit/dist trees.
+    s->tlit = build(s, s->litcodes, lens, nlit);
+    s->tdist = build(s, s->distcodes, lens + nlit, ndist);
+}
+
+int tigrInflate(void* out, unsigned outlen, const void* in, unsigned inlen) {
+    int last;
+    State* s = (State*)calloc(1, sizeof(State));
+
+    // We assume we can buffer 2 extra bytes from off the end of 'in'.
+    s->in = (unsigned char*)in;
+    s->inend = s->in + inlen + 2;
+    s->out = (unsigned char*)out;
+    s->outend = s->out + outlen;
+    s->bits = 0;
+    s->count = 0;
+    bits(s, 0);
+
+    if (setjmp(s->jmp) == 1) {
+        free(s);
+        return 0;
+    }
+
+    do {
+        last = bits(s, 1);
+        switch (bits(s, 2)) {
+            case 0:
+                stored(s);
+                break;
+            case 1:
+                fixed(s);
+                block(s);
+                break;
+            case 2:
+                dynamic(s);
+                block(s);
+                break;
+            case 3:
+                FAIL();
+        }
+    } while (!last);
+
+    free(s);
+    return 1;
+}
+
+#undef CHECK
+#undef FAIL
+
+//////// End of inlined file: tigr_inflate.c ////////
+
+//////// Start of inlined file: tigr_print.c ////////
+
+//#include "tigr_internal.h"
+//////// Start of inlined file: tigr_font.h ////////
+
+// Auto-generated by incbin.pl from font.png
+
+const unsigned char tigr_font[] = {
+	0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52,
+	0x00,0x00,0x00,0xfd,0x00,0x00,0x00,0x5c,0x08,0x03,0x00,0x00,0x00,0x92,0xab,0x43,
+	0x85,0x00,0x00,0x03,0x00,0x50,0x4c,0x54,0x45,0x5f,0x53,0x87,0x00,0x00,0x00,0x54,
+	0x54,0x54,0xff,0xff,0xfa,0x31,0x2a,0x42,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,
+	0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,
+	0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,
+	0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x4a,0x44,0xb7,0x5a,0x00,0x00,0x00,
+	0x01,0x74,0x52,0x4e,0x53,0x00,0x40,0xe6,0xd8,0x66,0x00,0x00,0x00,0x01,0x62,0x4b,
+	0x47,0x44,0x00,0x88,0x05,0x1d,0x48,0x00,0x00,0x00,0x09,0x70,0x48,0x59,0x73,0x00,
+	0x00,0x0b,0x13,0x00,0x00,0x0b,0x13,0x01,0x00,0x9a,0x9c,0x18,0x00,0x00,0x00,0x07,
+	0x74,0x49,0x4d,0x45,0x07,0xe5,0x04,0x1c,0x16,0x0f,0x2b,0x56,0x9d,0x54,0x3b,0x00,
+	0x00,0x0b,0x7c,0x49,0x44,0x41,0x54,0x78,0xda,0xed,0x5c,0x89,0x9a,0xec,0xac,0x0a,
+	0x04,0xf5,0xfd,0x9f,0xf9,0x4e,0x94,0xa5,0x40,0x93,0x98,0xee,0x3e,0x73,0xe7,0xfb,
+	0x67,0xce,0xd2,0x66,0x31,0x6a,0x29,0x51,0x4a,0x20,0xd4,0x7e,0xf3,0x1f,0x6a,0x34,
+	0xfe,0xb4,0x5a,0xf9,0xf8,0x7f,0x24,0x54,0xf9,0xf8,0xf7,0x75,0xeb,0xeb,0x97,0xe0,
+	0xd4,0x73,0x92,0x64,0xd7,0xab,0x90,0xa4,0xc3,0x91,0x59,0xb2,0x56,0x29,0x50,0xce,
+	0xed,0xfa,0x71,0x74,0x94,0xd7,0x2f,0xd8,0xf5,0x7c,0x0e,0xf9,0xb1,0x74,0x69,0x15,
+	0x13,0x14,0xfd,0x09,0xf4,0xd2,0x08,0x3e,0xfe,0xf6,0x53,0x86,0x9c,0xa5,0x8d,0xcc,
+	0xd2,0x45,0x9a,0x2c,0xd0,0x7f,0x65,0x63,0x45,0xc1,0xa3,0x00,0xae,0x78,0x7d,0xb4,
+	0xb9,0x9f,0xf3,0x57,0xb9,0x7a,0xfd,0x38,0xc3,0x7c,0x90,0x36,0xad,0xba,0x68,0x75,
+	0x1c,0x8a,0x7e,0x05,0x3d,0x1f,0xcf,0xfa,0x18,0x0c,0xd8,0xad,0xff,0x96,0x9e,0xa3,
+	0xd7,0xc0,0x36,0xec,0x90,0xe9,0x0c,0x7d,0x1d,0x68,0x6c,0x6c,0x7b,0x01,0x5f,0xa5,
+	0x0d,0x74,0x23,0x25,0xe9,0xf5,0xd1,0xbb,0xbd,0x22,0x62,0xc1,0xa5,0xd7,0x53,0x2a,
+	0x02,0x38,0x9a,0x35,0x04,0xa4,0x8f,0x90,0x16,0xf9,0x0a,0x7a,0xfa,0x7a,0xb6,0x8f,
+	0xe9,0xd1,0x09,0x63,0x6c,0x8f,0x46,0x70,0xd1,0x7e,0x1d,0x08,0x07,0x20,0x41,0xdf,
+	0x81,0x15,0x6b,0x03,0x11,0x1e,0xf6,0x3e,0xb1,0xde,0xec,0xc3,0x22,0x12,0x31,0x46,
+	0x4d,0x9f,0xfd,0xaa,0x41,0x7b,0x5d,0xf3,0xb3,0xe7,0xe3,0x5a,0x3d,0x1d,0xbd,0xd6,
+	0xff,0x1f,0xe0,0xa9,0x4b,0x48,0x2f,0x6c,0x0c,0xc2,0x78,0xe4,0x25,0xf4,0x32,0x6a,
+	0x2a,0xf9,0xac,0xaf,0xeb,0xa8,0xa6,0x8e,0x54,0x87,0x27,0x8c,0xfd,0xd1,0x5f,0x63,
+	0xb8,0xbc,0x39,0xe4,0xa3,0x63,0x12,0x2e,0x6f,0x49,0xbf,0xfe,0xd5,0x60,0xc6,0xf7,
+	0x3d,0x4b,0xba,0xe6,0xb3,0x73,0xd6,0xeb,0xa3,0x11,0x2a,0xec,0x15,0x5f,0x83,0xf1,
+	0xc8,0x53,0xf4,0xec,0xaf,0x50,0x92,0xfc,0xa3,0xdc,0x31,0x24,0x7a,0x8e,0xe8,0x0f,
+	0xc1,0x18,0xd5,0xae,0x24,0x5f,0x85,0xa6,0x04,0xf4,0xde,0x8d,0x84,0xa8,0xb3,0xa4,
+	0x6b,0x3e,0x3b,0xf7,0xfb,0x0d,0x85,0x7d,0x74,0xb6,0x74,0xe8,0xa8,0xe2,0x8d,0xb1,
+	0x07,0xc9,0xaf,0xa3,0xdc,0xca,0x3a,0x24,0x32,0xa8,0x8e,0x9e,0x48,0x90,0x68,0x43,
+	0x1b,0x4e,0xa3,0xa9,0xf5,0x36,0xeb,0x15,0x5a,0xa3,0xca,0x69,0xc8,0x37,0xbd,0xf7,
+	0x2a,0xec,0x63,0xce,0x97,0x66,0xf4,0xf4,0x33,0x92,0xdf,0xfb,0x13,0x3b,0x43,0xc6,
+	0x9e,0x6d,0xe2,0x8f,0x2b,0xde,0x84,0x5e,0x32,0xfb,0xdc,0x80,0x92,0x2e,0xd7,0x4d,
+	0x06,0xf0,0xfd,0x96,0xc9,0x05,0x9f,0xb7,0x74,0xcc,0x4d,0xc7,0x73,0x30,0xe7,0x17,
+	0xa2,0xd7,0xc7,0x1e,0x24,0x7f,0x4c,0x7e,0x36,0x9e,0x76,0x7a,0x48,0x98,0xbe,0xd0,
+	0x32,0xf3,0xe4,0xf5,0x1e,0x06,0xa3,0xe7,0x28,0x92,0xb9,0x67,0x65,0x11,0x52,0x4d,
+	0xf5,0xfa,0x91,0x3d,0x9c,0x5b,0xfe,0x42,0xf8,0x3c,0xe4,0x6f,0xd6,0xf1,0xa1,0x01,
+	0x2f,0xa3,0xcf,0x05,0x9d,0x9d,0x7a,0xce,0x36,0x67,0x6a,0x51,0x1a,0xda,0x69,0x01,
+	0xef,0xa6,0xcd,0x3a,0x3e,0xa3,0x7f,0x4d,0xf2,0x3f,0x83,0x3e,0x4a,0xc3,0x3f,0x44,
+	0x9f,0x4b,0x4f,0x55,0xfd,0x7f,0xd0,0xaf,0x0e,0x7f,0x32,0xfa,0xdf,0xce,0x72,0x54,
+	0x01,0xa5,0xac,0x6a,0xa5,0xf3,0x3c,0x65,0xfb,0x7d,0x5d,0x88,0xe0,0x7a,0x5a,0xc0,
+	0x74,0xe1,0xca,0xeb,0xf9,0x65,0x85,0x34,0x55,0x3c,0xad,0x7c,0x34,0x1f,0x60,0x53,
+	0xaa,0x2f,0x3c,0x35,0x90,0x92,0x21,0x20,0x63,0x3d,0x63,0xd5,0x66,0xce,0x48,0x05,
+	0x67,0x12,0x82,0x64,0x44,0xab,0x64,0xd3,0x8c,0xd9,0x16,0xac,0x88,0xde,0xf5,0xfd,
+	0xdb,0x8a,0x30,0xd5,0x82,0xf7,0xd1,0x83,0xd6,0x58,0x7c,0xd9,0xe5,0x26,0x7a,0x9b,
+	0xa3,0xef,0x7a,0x98,0x6a,0x33,0x53,0xe1,0xbc,0x44,0x11,0xc9,0x48,0x56,0x66,0xc6,
+	0xea,0x4d,0xc2,0x18,0x0b,0xa2,0x8f,0xc2,0xb3,0xaa,0x30,0x8d,0xa0,0xe8,0xcc,0xa6,
+	0x25,0xad,0xd1,0x33,0x56,0x55,0x55,0xdd,0x57,0x32,0xc6,0xa6,0x60,0x1e,0x60,0x61,
+	0x4d,0x6e,0x83,0xbf,0xaa,0x2e,0x07,0x8d,0x0b,0xad,0x76,0x71,0x52,0xc5,0x9a,0x43,
+	0x2f,0x98,0xa6,0x9d,0xd0,0x9b,0x9a,0xae,0xe5,0xd8,0x98,0xdf,0xa0,0x77,0xed,0x86,
+	0x61,0x11,0x3f,0x45,0x4f,0x2c,0x55,0xb9,0xfe,0xe4,0x10,0x1a,0x39,0xfa,0x63,0x35,
+	0xf4,0x35,0x59,0xd0,0x93,0x6b,0xb2,0x31,0x4d,0xe8,0x59,0x15,0x57,0x4d,0xeb,0x44,
+	0x4a,0xa6,0xb1,0x0f,0xe8,0x95,0xcb,0xc5,0x57,0x06,0xbb,0x3b,0x14,0xc8,0x71,0x57,
+	0x64,0x81,0xde,0xe9,0x93,0xd0,0x4f,0xe1,0x4f,0xd0,0xf1,0x42,0x07,0x08,0x95,0x59,
+	0x40,0xdf,0xe5,0xe6,0xec,0xb5,0x4a,0x95,0x26,0xc9,0x0f,0x20,0x16,0xe8,0xf9,0x72,
+	0xd6,0x43,0xb4,0xda,0xd6,0x58,0x20,0xd7,0xc4,0x0f,0x16,0x73,0x9b,0x6f,0x10,0x60,
+	0x3f,0x39,0xc3,0x10,0x9a,0xbd,0x46,0xcf,0x75,0x28,0xb0,0xb4,0xf1,0x1a,0xce,0xef,
+	0xfd,0x0a,0x3d,0x8e,0xf9,0x2e,0x7a,0x7b,0x35,0x72,0xaf,0xac,0xd0,0x03,0x6f,0xb0,
+	0x07,0x0a,0x9f,0xf1,0x28,0x25,0x43,0x4b,0xf4,0xc4,0x75,0xb5,0x72,0x05,0xb2,0x91,
+	0x49,0x4a,0x24,0x1f,0x79,0x99,0x61,0x10,0xbb,0x3c,0xc9,0xc5,0x99,0xf9,0x84,0xcd,
+	0xe4,0xfb,0x8a,0xc9,0xce,0xd9,0x2a,0x28,0x1c,0x27,0x00,0x1f,0x7b,0x5f,0xf1,0x74,
+	0x7b,0x0d,0x09,0x58,0x3f,0x6b,0x43,0x33,0x76,0x52,0x61,0x24,0xa4,0x24,0x72,0x12,
+	0x48,0x4a,0x24,0x1f,0xe1,0x39,0x21,0x2b,0x85,0xe6,0xeb,0xfd,0xaa,0x1e,0x20,0xab,
+	0xc1,0x02,0x75,0xc7,0x2b,0xb2,0x20,0x16,0xba,0x15,0xcf,0x43,0x81,0xb2,0x17,0xc6,
+	0xbe,0xdf,0x33,0xaa,0x2e,0x42,0x05,0x19,0x68,0x9c,0xdc,0x75,0x4d,0xf7,0xdf,0x28,
+	0xa4,0xab,0xf4,0x3a,0x83,0xb7,0xf1,0x79,0x81,0xeb,0x1c,0x8d,0xce,0xd4,0xf2,0x9f,
+	0x87,0xfe,0x9d,0x02,0xff,0xd0,0x3f,0x44,0xff,0xbb,0x59,0x8e,0x4f,0x85,0xbe,0x96,
+	0x41,0x17,0xe1,0x6d,0x51,0x1f,0x73,0x6e,0x53,0x2d,0xe3,0xe2,0xf6,0xea,0x58,0x37,
+	0x57,0x1b,0x9e,0x88,0x4e,0x0b,0x3b,0x9e,0x78,0xf7,0x53,0xe8,0x6b,0xc5,0x49,0xc9,
+	0x16,0x2a,0x99,0x57,0x93,0xc2,0xf4,0xed,0xe8,0x75,0xb5,0x6c,0x1c,0x87,0xec,0x12,
+	0x7d,0xdf,0xb5,0x5d,0x51,0x4b,0xa3,0xaa,0xf1,0x36,0x30,0x58,0xd3,0x13,0x60,0xec,
+	0x51,0xbf,0x36,0xb5,0xe7,0x82,0xc1,0x42,0x41,0x14,0xaa,0xb9,0x64,0x74,0xb3,0xa2,
+	0xdc,0xc8,0x1b,0xe3,0x64,0xf6,0x0e,0x7d,0x57,0x1b,0x16,0x06,0xb3,0x48,0x61,0x59,
+	0xfb,0x74,0x71,0x39,0xa0,0x07,0xfd,0x1a,0x38,0x1d,0xef,0x33,0x5a,0xac,0x66,0x0f,
+	0x3d,0xb0,0x59,0x35,0x49,0xd5,0x3a,0xec,0x6f,0x9d,0xd6,0xdd,0xec,0x6c,0x99,0x89,
+	0x28,0xaa,0x9e,0xab,0x6d,0x8b,0x13,0xb2,0x01,0xe8,0x5d,0xbf,0x46,0x4e,0x77,0xab,
+	0x42,0x33,0x58,0xce,0xee,0xf9,0x6c,0xa2,0xb3,0x66,0x53,0x85,0xb1,0x2f,0x9d,0xcc,
+	0x1e,0xdb,0xca,0x77,0xe8,0x67,0xd2,0xc5,0x99,0xdc,0x5d,0x37,0x2b,0x8c,0x3d,0xa0,
+	0x37,0x4e,0xb7,0x45,0x20,0xd4,0x72,0x76,0xc7,0xe8,0x92,0x66,0x4e,0xc6,0xeb,0xa2,
+	0xe4,0x93,0x6c,0xcb,0x6f,0xa0,0xa7,0x6b,0xf4,0xb4,0x3b,0xf6,0x4e,0x70,0xae,0x59,
+	0xcd,0x92,0xe5,0x98,0xa1,0xfb,0x84,0xd3,0x4c,0x2f,0x12,0x98,0x7c,0x56,0xe8,0xef,
+	0xdf,0xfb,0x28,0xf9,0xc8,0x6f,0xdc,0x66,0xaa,0xec,0x02,0xc8,0xc6,0x29,0x7a,0x9b,
+	0x21,0xae,0x58,0x4d,0xb4,0xc9,0x22,0xcb,0x11,0x12,0x83,0xb5,0xb2,0xed,0x5c,0xf0,
+	0x4c,0x82,0x60,0xd6,0x43,0xdb,0x7e,0x79,0x80,0x9e,0xd0,0xc8,0x62,0xfc,0xc6,0x2e,
+	0x83,0x2d,0xa5,0x2c,0xc8,0x88,0xac,0x78,0x89,0x9f,0x5c,0xb0,0x1a,0xe5,0xf3,0x58,
+	0x90,0x36,0x43,0x48,0xcc,0x9a,0xe3,0x94,0xc0,0xaa,0xfc,0x71,0x36,0x86,0xd3,0x92,
+	0x8d,0x69,0x0b,0xfd,0xf6,0x46,0x7e,0x36,0x3e,0x92,0x5b,0xb4,0xd3,0xa5,0x8b,0x05,
+	0x1a,0xd5,0x8a,0xfc,0xd4,0x5d,0xed,0xa9,0x98,0x4c,0xda,0x5b,0xae,0xfd,0xe3,0xe8,
+	0xd1,0xf8,0xe8,0xa6,0xf4,0x7c,0xe9,0xdf,0xa3,0x8f,0xed,0xf8,0x2e,0xf4,0x68,0x7c,
+	0xb4,0x0a,0xa7,0x4b,0x17,0xe8,0x4d,0xd2,0x97,0x8c,0x76,0x1b,0x7d,0x6c,0xc7,0x6b,
+	0xe8,0xff,0x3c,0xd6,0xa4,0x8f,0x80,0x2c,0xa4,0xfe,0x86,0x6d,0x65,0x77,0xc0,0x60,
+	0xb3,0xf4,0x8f,0x53,0xd5,0xed,0x45,0x83,0x1d,0xc5,0xf8,0x8d,0x16,0x1d,0x5e,0x2a,
+	0xa3,0x3a,0xbc,0x30,0x23,0xca,0x56,0xb4,0x39,0xbe,0x24,0x6b,0x04,0xb4,0x2d,0x0c,
+	0xb5,0xfa,0x97,0x0d,0x17,0x84,0x2d,0x4b,0x16,0x03,0xfa,0xd9,0x27,0x61,0x89,0x5e,
+	0xd4,0xc8,0xe1,0xc9,0xe4,0x9e,0x0e,0x3e,0x05,0x0e,0x99,0xf6,0x1b,0xcf,0xd1,0xbb,
+	0xcf,0x58,0x51,0xa7,0x19,0xa8,0xe5,0x04,0xbd,0xe4,0x10,0x9f,0x83,0x5b,0xf4,0x5d,
+	0xc9,0xe7,0x80,0x9e,0x16,0xe8,0x27,0xc7,0x8b,0x32,0x3c,0x28,0x64,0xf7,0xd4,0xfa,
+	0xd0,0x37,0x75,0x15,0xb0,0x49,0x01,0xba,0x7c,0x70,0x05,0xcb,0x86,0xba,0x44,0x24,
+	0xf4,0xa2,0xab,0x8e,0x71,0x3c,0x7a,0x20,0xd4,0x32,0x54,0xbe,0x68,0x64,0x9c,0x28,
+	0xd2,0x3d,0xbf,0x17,0x52,0xc1,0xcd,0x50,0xae,0x24,0x5f,0x8d,0x69,0x66,0xad,0x13,
+	0x2f,0x0f,0xdc,0xdc,0x1f,0xcc,0x4a,0x76,0xa6,0x87,0xa3,0xa3,0x71,0x90,0xfe,0x0f,
+	0x5c,0x3e,0xc6,0xea,0xdd,0x74,0x11,0x07,0xb5,0x05,0xde,0x1b,0x82,0x71,0x24,0x25,
+	0x58,0xc3,0x2e,0x29,0x9a,0x95,0x29,0x7e,0xc3,0x1a,0x09,0x06,0xbc,0x2d,0xf4,0x2c,
+	0x96,0x19,0x05,0xc4,0x9b,0x92,0x4f,0xe6,0x8d,0x68,0x8f,0x89,0xe7,0x94,0x4a,0x7e,
+	0x91,0x42,0xab,0x90,0x0d,0xeb,0xb2,0x93,0x55,0xb0,0x05,0xc7,0x96,0x65,0x23,0x74,
+	0x04,0x5c,0xcd,0x77,0xff,0xb2,0xbe,0x71,0xcd,0xc9,0xc5,0x6f,0x6b,0xec,0x89,0x6e,
+	0x25,0x3f,0x6f,0x95,0x1b,0x7a,0x99,0x66,0x44,0xc1,0x57,0xa7,0x4e,0x93,0x7c,0xf3,
+	0xcd,0x6b,0x0b,0xa7,0x87,0xb5,0xdb,0xd0,0x7a,0xe1,0x45,0x1e,0xe4,0x46,0x57,0x33,
+	0x28,0x2b,0x7a,0xa9,0x8e,0x77,0xfc,0xf6,0x64,0xec,0x75,0x7a,0xb6,0x4d,0x82,0x31,
+	0x19,0xe9,0xdb,0x89,0x86,0x82,0x28,0xf9,0xd5,0x69,0xb4,0x28,0xf8,0x3c,0xbd,0xf7,
+	0xe2,0x4a,0x76,0x8d,0x7e,0x39,0xe7,0xcf,0x93,0x8f,0xf2,0x20,0x53,0xf7,0x8d,0x6c,
+	0x08,0x7a,0xab,0x6e,0xc7,0x7b,0x89,0xd4,0xfe,0xb9,0x8d,0x3e,0x4a,0xbe,0xd8,0x7f,
+	0x0b,0x30,0x14,0xa5,0x1f,0x32,0xd7,0x85,0x59,0xef,0x5d,0xf4,0xea,0x16,0xeb,0xf6,
+	0xda,0xba,0xf2,0x86,0xe3,0x7d,0xf4,0xa6,0xa6,0x35,0x73,0x9a,0x35,0xbd,0xbd,0x8c,
+	0x49,0x09,0xcd,0x24,0x9e,0x9a,0x33,0x9b,0xb9,0xb3,0x0d,0x27,0x37,0x61,0x47,0xb2,
+	0xe2,0xf9,0xd1,0x07,0xd0,0x4b,0x2d,0x60,0xd9,0x09,0xc4,0xcc,0x88,0xda,0xa6,0xef,
+	0x56,0xd0,0x76,0xdc,0xac,0xd4,0x0c,0xf2,0xc6,0x56,0x79,0x6e,0x69,0xb8,0xda,0xf0,
+	0xe8,0x7d,0xf4,0xab,0x5a,0xae,0x94,0xf4,0xe7,0xba,0xde,0x55,0xc5,0x7f,0xe8,0xff,
+	0x4b,0xe8,0xff,0x58,0x0e,0x86,0x52,0x10,0xaa,0xfe,0x94,0xdd,0x14,0xec,0x9e,0xfa,
+	0xae,0xc3,0x2e,0x66,0x5f,0x72,0x2c,0x52,0xa6,0xa1,0xfd,0x87,0x28,0x6e,0x0c,0xe3,
+	0x46,0x49,0xf0,0xf7,0x26,0xa0,0x49,0xdc,0x12,0x7d,0x91,0x1f,0x64,0x56,0x44,0xee,
+	0x42,0x94,0x8e,0x36,0x86,0x7f,0x8d,0x5e,0x75,0x70,0xb6,0xb8,0x1a,0x82,0x68,0x90,
+	0xb1,0xe9,0x5e,0x86,0x66,0xc7,0xe2,0x23,0xaf,0xf1,0x01,0xc9,0x7b,0x18,0xb3,0xa8,
+	0x5a,0x02,0x81,0x2c,0xac,0x81,0x0d,0x11,0x3d,0x46,0x02,0x50,0xe8,0x28,0xb6,0xf8,
+	0x99,0x80,0x94,0x59,0xba,0x04,0x8e,0x1e,0xa1,0xaf,0x40,0x22,0x84,0xa1,0xcd,0xce,
+	0x19,0x83,0xd5,0xa9,0xdb,0xd3,0xf0,0x76,0x63,0x73,0x8c,0x18,0xe0,0xc3,0xd4,0xd1,
+	0x86,0xd2,0x8b,0xc8,0x44,0x3c,0x94,0x26,0xbb,0x76,0x5c,0x2e,0xd1,0xb3,0x4b,0x9c,
+	0xc0,0x6f,0x03,0x68,0x91,0x72,0xa6,0xa3,0xfb,0x25,0xff,0x24,0x26,0xcb,0x76,0xb4,
+	0x39,0x99,0x0f,0x1a,0x6b,0x70,0x8e,0x52,0xd0,0xae,0x28,0x8a,0xba,0x57,0xd4,0x96,
+	0x61,0x7b,0xfb,0xcd,0x94,0x41,0x54,0xd5,0x2d,0x8c,0x47,0xb5,0xd2,0xb1,0xb3,0x99,
+	0xb6,0xee,0xb9,0xc5,0xb7,0xc1,0xdf,0x37,0x89,0x9e,0xf2,0xdd,0x85,0xa3,0x1c,0x67,
+	0xda,0xaa,0x67,0x3e,0x40,0x6f,0x5e,0x98,0xa5,0xd5,0x13,0x17,0x54,0x16,0x65,0xce,
+	0xa9,0x2c,0x0f,0xd9,0x6b,0xb5,0xa2,0x44,0xca,0xbb,0x21,0x82,0xde,0xe3,0x52,0x18,
+	0xf7,0xa3,0x0a,0x69,0x18,0x4f,0x0d,0x1b,0xd0,0x61,0xaf,0x3a,0xa3,0xd7,0x2a,0x8a,
+	0xbf,0x61,0x43,0xd9,0x53,0x57,0x43,0xeb,0x30,0xeb,0xee,0x5b,0x85,0x67,0x1a,0x7b,
+	0x67,0x52,0x34,0xb9,0xa0,0x5a,0x34,0x88,0xb1,0x61,0xf1,0x76,0x13,0xa7,0x18,0x95,
+	0x48,0x88,0x12,0x9c,0xc7,0xde,0x26,0x43,0xe9,0x27,0xd8,0x80,0xf6,0x5d,0x5a,0xd6,
+	0x50,0xa7,0x02,0xa3,0x33,0xc2,0xc3,0x5c,0xb8,0x47,0x2f,0x6b,0xfd,0x76,0x54,0x3c,
+	0x9c,0xe8,0x2d,0xc9,0x9f,0x23,0x4a,0xfa,0x4c,0xc8,0xd5,0xd9,0x30,0xb3,0x9f,0xb9,
+	0x44,0xba,0x43,0x64,0x1f,0x2d,0xce,0xdb,0xbd,0x1e,0xf6,0x11,0xbd,0xdd,0xd1,0xd1,
+	0x34,0xbf,0xf7,0x90,0x0b,0xde,0xec,0xe1,0x66,0x7b,0x76,0xf4,0x0c,0x3d,0x27,0xb4,
+	0xac,0x32,0xec,0x63,0xaf,0xee,0x68,0x46,0x38,0xc3,0x56,0xd3,0x88,0xe7,0x0b,0x30,
+	0x31,0x0b,0x4c,0x86,0x81,0x26,0x3b,0x7a,0x70,0x51,0x3b,0x45,0x2f,0x9b,0x1d,0x22,
+	0x40,0xbe,0x84,0xe4,0x23,0xa1,0x7b,0x0f,0xa2,0x11,0x6b,0x70,0x4d,0x2b,0x3a,0x8a,
+	0xec,0x01,0x57,0x8c,0x24,0x27,0x9d,0xcd,0x81,0x02,0x31,0x8b,0x5f,0x86,0xfd,0x8b,
+	0x38,0xf6,0x6e,0xbe,0x09,0xdb,0x5d,0x0b,0x35,0x51,0x05,0x88,0x2c,0x30,0x2c,0x1e,
+	0xa9,0xc7,0xe2,0x83,0x98,0xac,0x2b,0xaf,0x89,0xc6,0x97,0xf6,0x8a,0x33,0xf4,0xab,
+	0xc2,0xda,0x12,0x52,0xf2,0x1d,0x41,0x1d,0xe4,0x04,0x3d,0x11,0xc6,0x65,0xc5,0xa3,
+	0x8d,0xcd,0xad,0x8c,0x7e,0x1e,0xa6,0xa5,0x2e,0xfe,0x8f,0xd0,0xa3,0x99,0xc3,0x03,
+	0x1a,0xef,0xd0,0x9f,0x1c,0x6d,0x6c,0x6e,0x5d,0x6d,0x2d,0x7d,0x3f,0xfa,0x65,0x85,
+	0xac,0x2f,0xf9,0x43,0xf4,0x6a,0xea,0xf9,0x63,0x39,0x57,0xd6,0x0c,0xb6,0x50,0xf9,
+	0x90,0xd6,0xf4,0xed,0x81,0xe0,0x4a,0x45,0x18,0xeb,0x1f,0x1e,0x05,0x2d,0x16,0x62,
+	0x03,0xfc,0x8e,0xea,0xba,0x35,0x7f,0x93,0x60,0xbb,0x21,0xd3,0x70,0x9f,0x3c,0xe8,
+	0x0f,0x60,0x54,0x05,0xb6,0x7b,0xac,0xd2,0xbe,0x39,0x6d,0x31,0xc7,0x6e,0x36,0xd7,
+	0xc0,0xcc,0x19,0xbd,0xbb,0x90,0xb3,0x07,0x6b,0x1b,0x6c,0xdd,0xf2,0x92,0x3e,0xd0,
+	0x3b,0x4d,0x96,0x45,0x78,0x58,0xb7,0xcc,0x36,0x1b,0x32,0xa1,0x3f,0x79,0xd0,0x5b,
+	0x8e,0x31,0xe4,0xe4,0x5e,0x5a,0x8b,0xcf,0x40,0xdc,0xa5,0x11,0x3d,0x31,0x68,0xed,
+	0x67,0xc9,0xea,0x97,0x16,0x7e,0x31,0x5b,0x0d,0x10,0xe5,0x5a,0x62,0xc9,0xa2,0x27,
+	0x4b,0x49,0x29,0xe1,0xb9,0x4a,0x9b,0x14,0xd1,0xd1,0x3f,0x72,0x27,0xb3,0x32,0xa2,
+	0xcb,0x09,0x78,0xb8,0x4d,0x09,0x9b,0xe1,0x2a,0xfe,0x56,0x8f,0xcc,0x7a,0x18,0xa1,
+	0x55,0xb9,0x82,0xec,0x94,0x2d,0xb7,0x20,0xed,0x6f,0xb5,0x3a,0x88,0xb5,0x72,0xfb,
+	0xd9,0x14,0x30,0xa1,0x00,0xdd,0xfa,0x75,0x92,0xd0,0xc9,0x2f,0x87,0x2f,0x2e,0x3c,
+	0x68,0x80,0x45,0x86,0x57,0xae,0x53,0x0c,0xc8,0xd2,0x9f,0x0f,0xdd,0x8e,0x14,0x3d,
+	0xeb,0xd8,0xe7,0x30,0xb1,0x9d,0x34,0x47,0x65,0x99,0x3f,0xce,0x94,0xd0,0x55,0x1f,
+	0xec,0x45,0x68,0xe5,0x21,0x64,0x73,0xec,0xdc,0x42,0x8f,0xa1,0x4a,0xb2,0x5f,0xff,
+	0xb2,0xe4,0xeb,0xd8,0x97,0x29,0x5e,0x69,0x9d,0xec,0xa0,0xe7,0x47,0xc2,0x27,0x31,
+	0x75,0x22,0x3b,0x1b,0xe8,0xe1,0x93,0x0f,0xd2,0x6e,0x44,0xff,0x54,0xf2,0x29,0x7f,
+	0x3e,0xe0,0xc5,0x59,0x6f,0x1d,0x85,0x73,0x9b,0x8a,0x39,0x97,0x6b,0x0a,0xc2,0x3b,
+	0x4b,0xd1,0x02,0xc9,0xb6,0xe1,0x34,0xde,0x7b,0xf0,0x3b,0x7b,0x90,0x9a,0x2b,0x9b,
+	0xd3,0xa2,0x72,0x9e,0xac,0x7e,0xa7,0x70,0xa0,0xe9,0x1b,0x03,0x67,0xa9,0x19,0xf7,
+	0x39,0x85,0xeb,0x9c,0x3e,0xe0,0xc1,0x3a,0xc5,0x9f,0x2d,0xd4,0x5e,0x0f,0x88,0x20,
+	0xca,0x3a,0xc0,0x45,0xd2,0x76,0x55,0xe0,0x07,0x7e,0xec,0xf4,0x72,0xcb,0xfd,0xca,
+	0x9b,0xe8,0x91,0xbd,0xfe,0x3e,0xf4,0x77,0xb0,0x7f,0x3a,0xfa,0x3f,0x96,0x73,0x49,
+	0x2e,0x4c,0xdd,0x5e,0xb0,0x9c,0x4d,0x9e,0x21,0x9a,0x7e,0xa3,0xe0,0x6e,0x66,0x04,
+	0xc9,0xa9,0xd1,0x37,0x70,0x1c,0x74,0x66,0xdb,0x20,0x17,0xca,0x56,0x2e,0xd1,0x5f,
+	0xf3,0x0c,0xd9,0xed,0x77,0x2f,0x37,0x09,0x90,0x57,0xdd,0xa5,0x3c,0x22,0x5b,0xfc,
+	0x16,0xc7,0x41,0x67,0xb6,0x17,0x58,0x8e,0x2b,0x7b,0xfb,0xcf,0x8c,0x65,0x5a,0xbc,
+	0xdc,0x64,0xc7,0x4e,0xdc,0xa3,0x7a,0xdf,0x3e,0x21,0x59,0x66,0xa8,0x79,0x8d,0xe3,
+	0xe0,0x8e,0x17,0xc4,0xe4,0x3c,0x4b,0x9f,0xea,0x87,0x1e,0x2a,0x86,0x01,0x49,0xf8,
+	0x51,0x8f,0xdd,0xa2,0x2a,0x32,0x96,0xe7,0x1c,0x27,0xa2,0x0f,0x21,0x08,0xdb,0xa9,
+	0x13,0x13,0x5e,0x7c,0x42,0x6a,0xf1,0x89,0x89,0x84,0xde,0xe9,0xd6,0x43,0x3d,0x97,
+	0x3d,0x48,0x66,0x8f,0xe3,0x84,0xc0,0x92,0x09,0xfd,0xab,0x7a,0x7e,0xe4,0x78,0xf3,
+	0xf7,0x48,0x52,0x3a,0xa3,0xa7,0x47,0x8d,0x07,0xa5,0x5d,0xbf,0x0f,0xc3,0x9f,0x41,
+	0xff,0x82,0x9e,0xff,0xf8,0x99,0xcf,0xa1,0xb7,0x20,0x9c,0xf2,0x01,0xf4,0x2f,0x4b,
+	0xfe,0x0e,0x21,0x01,0xe3,0x6f,0x44,0x3f,0x7d,0xf2,0x60,0xb7,0x28,0x77,0x60,0xde,
+	0xe4,0x38,0x5c,0x13,0xab,0xd2,0x0b,0xe2,0xaf,0xb7,0x08,0xc2,0xb9,0x4d,0x8d,0x47,
+	0xd4,0x1d,0x62,0x12,0x02,0x66,0xd0,0xc1,0xec,0x3b,0x38,0x0e,0x7b,0xa4,0x57,0xb8,
+	0x90,0x7d,0x37,0xde,0x64,0x39,0x7b,0x1f,0xc6,0x6a,0xdf,0xac,0xe7,0xc6,0x70,0x3a,
+	0x4a,0x2b,0xde,0xe7,0x58,0xce,0xcf,0x44,0x8f,0x7b,0x10,0x9c,0x6c,0x3c,0x9f,0x64,
+	0x39,0x3f,0x13,0x3d,0x7e,0x3d,0xa4,0x24,0x1b,0xcf,0xef,0x66,0x39,0xff,0x03,0xca,
+	0x1b,0xaa,0xdf,0x6a,0xd1,0xba,0xa4,0x00,0x00,0x00,0x00,0x49,0x45,0x4e,0x44,0xae,
+	0x42,0x60,0x82 };
+
+int tigr_font_size = (int)sizeof(tigr_font);
+
+
+//////// End of inlined file: tigr_font.h ////////
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <stdio.h>
+
+#ifdef _MSC_VER
+#define vsnprintf _vsnprintf
+#endif
+
+TigrFont tigrStockFont;
+TigrFont* tfont = &tigrStockFont;
+
+// Converts 8-bit codepage entries into Unicode code points.
+static int cp1252[] = {
+    0x20ac, 0xfffd, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152,
+    0xfffd, 0x017d, 0xfffd, 0xfffd, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x02dc, 0x2122,
+    0x0161, 0x203a, 0x0153, 0xfffd, 0x017e, 0x0178, 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6,
+    0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, 0x00b0, 0x00b1, 0x00b2, 0x00b3,
+    0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, 0x00c0,
+    0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd,
+    0x00ce, 0x00cf, 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da,
+    0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7,
+    0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4,
+    0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff,
+};
+
+static int border(Tigr* bmp, int x, int y) {
+    TPixel top = tigrGet(bmp, 0, 0);
+    TPixel c = tigrGet(bmp, x, y);
+    return (c.r == top.r && c.g == top.g && c.b == top.b) || x >= bmp->w || y >= bmp->h;
+}
+
+static void scan(Tigr* bmp, int* x, int* y, int* rowh) {
+    while (*y < bmp->h) {
+        if (*x >= bmp->w) {
+            *x = 0;
+            (*y) += *rowh;
+            *rowh = 1;
+        }
+        if (!border(bmp, *x, *y))
+            return;
+        (*x)++;
+    }
+}
+
+/*
+ * Watermarks are encoded vertically in the alpha channel using seven pixels
+ * starting at x, y. The first and last alpha values contain the magic values
+ * 0b10101010 and 0b01010101 respectively.
+ */
+static int readWatermark(Tigr* bmp, int x, int y, int* big, int* small) {
+    const int magicHeader = 0xAA;
+    const int magicFooter = 0x55;
+
+    unsigned char watermark[7];
+
+    for (int i = 0; i < 7; i++) {
+        TPixel c = tigrGet(bmp, x, y + i);
+        watermark[i] = c.a;
+    }
+
+    if (watermark[0] != magicHeader || watermark[6] != magicFooter) {
+        return 0;
+    }
+
+    *big = watermark[1] | (watermark[2] << 8) | (watermark[3] << 16) | (watermark[4] << 24);
+    *small = watermark[5];
+
+    return 1;
+}
+
+int tigrLoadGlyphs(TigrFont* font, int codepage) {
+    int x = 0;
+    int y = 0;
+    int w = 0;
+    int h = 0;
+    int rowh = 1;
+
+    TigrGlyph* g;
+    switch (codepage) {
+        case TCP_ASCII:
+            font->numGlyphs = 128 - 32;
+            break;
+        case TCP_1252:
+            font->numGlyphs = 256 - 32;
+            break;
+        case TCP_UTF32:
+            if (!readWatermark(font->bitmap, 0, 0, &font->numGlyphs, &rowh)) {
+                return 0;
+            }
+            h = rowh;
+            x = 1;
+            break;
+        default:
+            errno = EINVAL;
+            return 0;
+    }
+
+    font->glyphs = (TigrGlyph*)calloc(font->numGlyphs, sizeof(TigrGlyph));
+
+    for (int index = 0; index < font->numGlyphs; index++) {
+        // Look up the Unicode code point.
+        g = &font->glyphs[index];
+
+        if (codepage != TCP_UTF32) {
+            // Find the next glyph.
+            scan(font->bitmap, &x, &y, &rowh);
+
+            if (y >= font->bitmap->h) {
+                errno = EINVAL;
+                return 0;
+            }
+
+            // Scan the width and height
+            w = h = 0;
+            while (!border(font->bitmap, x + w, y)) {
+                w++;
+            }
+
+            while (!border(font->bitmap, x, y + h)) {
+                h++;
+            }
+        }
+
+        switch (codepage) {
+            case TCP_ASCII:
+                g->code = index + 32;
+                break;
+            case TCP_1252:
+                if (index < 96) {
+                    g->code = index + 32;
+                } else {
+                    g->code = cp1252[index - 96];
+                }
+                break;
+            case TCP_UTF32:
+                if (!readWatermark(font->bitmap, x, y, &g->code, &w)) {
+                    // Maybe we are at the end of a row?
+                    x = 0;
+                    y += rowh;
+                    if (!readWatermark(font->bitmap, x, y, &g->code, &w)) {
+                        return 0;
+                    }
+                }
+                x++;
+                break;
+            default:
+                return 0;
+        }
+
+        g->x = x;
+        g->y = y;
+        g->w = w;
+        g->h = h;
+        x += w;
+        if (h != font->glyphs[0].h) {
+            errno = EINVAL;
+            return 0;
+        }
+
+        if (h > rowh) {
+            rowh = h;
+        }
+    }
+
+    // Sort by code point.
+    for (int i = 1; i < font->numGlyphs; i++) {
+        int j = i;
+        TigrGlyph g = font->glyphs[i];
+        while (j > 0 && font->glyphs[j - 1].code > g.code) {
+            font->glyphs[j] = font->glyphs[j - 1];
+            j--;
+        }
+        font->glyphs[j] = g;
+    }
+
+    return 1;
+}
+
+TigrFont* tigrLoadFont(Tigr* bitmap, int codepage) {
+    TigrFont* font = (TigrFont*)calloc(1, sizeof(TigrFont));
+    font->bitmap = bitmap;
+    if (!tigrLoadGlyphs(font, codepage)) {
+        tigrFreeFont(font);
+        return NULL;
+    }
+    return font;
+}
+
+void tigrFreeFont(TigrFont* font) {
+    tigrFree(font->bitmap);
+    free(font->glyphs);
+    free(font);
+}
+
+static TigrGlyph* get(TigrFont* font, int code) {
+    unsigned lo = 0, hi = font->numGlyphs;
+    while (lo < hi) {
+        unsigned guess = (lo + hi) / 2;
+        if (code < font->glyphs[guess].code)
+            hi = guess;
+        else
+            lo = guess + 1;
+    }
+
+    if (lo == 0 || font->glyphs[lo - 1].code != code)
+        return &font->glyphs['?' - 32];
+    else
+        return &font->glyphs[lo - 1];
+}
+
+void tigrSetupFont(TigrFont* font) {
+    // Load the stock font if needed.
+    if (font == tfont && !tfont->bitmap) {
+        tfont->bitmap = tigrLoadImageMem(tigr_font, tigr_font_size);
+        tigrLoadGlyphs(tfont, 1252);
+    }
+}
+
+void tigrPrint(Tigr* dest, TigrFont* font, int x, int y, TPixel color, const char* text, ...) {
+    char tmp[1024];
+    TigrGlyph* g;
+    va_list args;
+    const char* p;
+    int start = x, c;
+
+    tigrSetupFont(font);
+
+    // Expand the formatting string.
+    va_start(args, text);
+    vsnprintf(tmp, sizeof(tmp), text, args);
+    tmp[sizeof(tmp) - 1] = 0;
+    va_end(args);
+
+    // Print each glyph.
+    p = tmp;
+    while (*p) {
+        p = tigrDecodeUTF8(p, &c);
+        if (c == '\r')
+            continue;
+        if (c == '\n') {
+            x = start;
+            y += tigrTextHeight(font, "");
+            continue;
+        }
+        g = get(font, c);
+        tigrBlitTint(dest, font->bitmap, x, y, g->x, g->y, g->w, g->h, color);
+        x += g->w;
+    }
+}
+
+int tigrTextWidth(TigrFont* font, const char* text) {
+    int x = 0, w = 0, c;
+    tigrSetupFont(font);
+
+    while (*text) {
+        text = tigrDecodeUTF8(text, &c);
+        if (c == '\n' || c == '\r') {
+            x = 0;
+        } else {
+            x += get(font, c)->w;
+            w = (x > w) ? x : w;
+        }
+    }
+    return w;
+}
+
+int tigrTextHeight(TigrFont* font, const char* text) {
+    int rowh, h, c;
+    tigrSetupFont(font);
+
+    h = rowh = get(font, 0)->h;
+    while (*text) {
+        text = tigrDecodeUTF8(text, &c);
+        if (c == '\n' && *text)
+            h += rowh;
+    }
+    return h;
+}
+
+//////// End of inlined file: tigr_print.c ////////
+
+//////// Start of inlined file: tigr_win.c ////////
+
+#ifndef TIGR_HEADLESS
+
+//#include "tigr_internal.h"
+#include <assert.h>
+
+// not really windows stuff
+TigrInternal* tigrInternal(Tigr* bmp) {
+    assert(bmp->handle);
+    return (TigrInternal*)(bmp + 1);
+}
+
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <shellapi.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+
+#ifdef _MSC_VER
+#pragma comment(lib, "opengl32")  // glViewport
+#pragma comment(lib, "shell32")   // CommandLineToArgvW
+#pragma comment(lib, "user32")    // SetWindowLong
+#pragma comment(lib, "gdi32")     // ChoosePixelFormat
+#pragma comment(lib, "advapi32")  // RegSetValueEx
+#endif
+
+#define WIDGET_SCALE 3
+#define WIDGET_FADE 16
+
+int main(int argc, char* argv[]);
+
+#ifndef TIGR_DO_NOT_PRESERVE_WINDOW_POSITION
+HKEY tigrRegKey;
+#endif
+
+#ifdef __TINYC__
+#define CP_UTF8 65001
+int WINAPI MultiByteToWideChar();
+int WINAPI WideCharToMultiByte();
+#endif
+
+static wchar_t* unicode(const char* str) {
+    int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, 0, 0);
+    wchar_t* dest = (wchar_t*)malloc(sizeof(wchar_t) * len);
+    MultiByteToWideChar(CP_UTF8, 0, str, -1, dest, len);
+    return dest;
+}
+
+void tigrError(Tigr* bmp, const char* message, ...) {
+    char tmp[1024];
+
+    va_list args;
+    va_start(args, message);
+    _vsnprintf(tmp, sizeof(tmp), message, args);
+    tmp[sizeof(tmp) - 1] = 0;
+    va_end(args);
+
+    MessageBoxW(bmp ? (HWND)bmp->handle : NULL, unicode(tmp), bmp ? tigrInternal(bmp)->wtitle : L"Error",
+                MB_OK | MB_ICONERROR);
+    exit(1);
+}
+
+void tigrEnterBorderlessWindowed(Tigr* bmp) {
+    // Enter borderless windowed mode.
+    MONITORINFO mi = { sizeof(mi) };
+    TigrInternal* win = tigrInternal(bmp);
+
+    GetWindowRect((HWND)bmp->handle, &win->oldPos);
+
+    GetMonitorInfo(MonitorFromWindow((HWND)bmp->handle, MONITOR_DEFAULTTONEAREST), &mi);
+    win->dwStyle = WS_VISIBLE | WS_POPUP;
+    SetWindowLong((HWND)bmp->handle, GWL_STYLE, win->dwStyle);
+    SetWindowPos((HWND)bmp->handle, HWND_TOP, mi.rcMonitor.left, mi.rcMonitor.top,
+                 mi.rcMonitor.right - mi.rcMonitor.left, mi.rcMonitor.bottom - mi.rcMonitor.top, 0);
+}
+
+void tigrLeaveBorderlessWindowed(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+
+    win->dwStyle = WS_VISIBLE | WS_OVERLAPPEDWINDOW;
+    SetWindowLong((HWND)bmp->handle, GWL_STYLE, win->dwStyle);
+
+    SetWindowPos((HWND)bmp->handle, NULL, win->oldPos.left, win->oldPos.top, win->oldPos.right - win->oldPos.left,
+                 win->oldPos.bottom - win->oldPos.top, 0);
+}
+
+void tigrWinUpdateWidgets(Tigr* bmp, int dw, int dh) {
+    POINT pt;
+    int i, x, clicked = 0;
+    char str[8];
+    TPixel col;
+    TPixel off = tigrRGB(255, 255, 255);
+    TPixel on = tigrRGB(0, 200, 255);
+    TigrInternal* win = tigrInternal(bmp);
+    (void)dh;
+
+    tigrClear(win->widgets, tigrRGBA(0, 0, 0, 0));
+
+    if (!(win->dwStyle & WS_POPUP)) {
+        win->widgetsWanted = 0;
+        win->widgetAlpha = 0;
+        return;
+    }
+
+    // See if we want to be showing widgets or not.
+    GetCursorPos(&pt);
+    ScreenToClient((HWND)bmp->handle, &pt);
+    if (pt.y == 0)
+        win->widgetsWanted = 1;
+    if (pt.y > win->widgets->h * WIDGET_SCALE)
+        win->widgetsWanted = 0;
+
+    // Track the alpha.
+    if (win->widgetsWanted)
+        win->widgetAlpha = (win->widgetAlpha <= 255 - WIDGET_FADE) ? win->widgetAlpha + WIDGET_FADE : 255;
+    else
+        win->widgetAlpha = (win->widgetAlpha >= WIDGET_FADE) ? win->widgetAlpha - WIDGET_FADE : 0;
+
+    // Get relative coords.
+    pt.x -= (dw - win->widgets->w * WIDGET_SCALE);
+    pt.x /= WIDGET_SCALE;
+    pt.y /= WIDGET_SCALE;
+
+    tigrClear(win->widgets, tigrRGBA(0, 0, 0, win->widgetAlpha));
+
+    // Render it.
+    for (i = 0; i < 3; i++) {
+        switch (i) {
+            case 0:
+                str[0] = '_';
+                str[1] = 0;
+                break;  // "_" (minimize)
+            case 1:
+                str[0] = (char)0xEF;
+                str[1] = (char)0xBF;
+                str[2] = (char)0xBD;
+                str[3] = 0;
+                break;  // "[]" (maximize)
+            case 2:
+                str[0] = (char)0xC3;
+                str[1] = (char)0x97;
+                str[2] = 0;
+                break;  // "x" (close)
+        }
+        x = win->widgets->w + (i - 3) * 12;
+        if (i == 2)
+            off = tigrRGB(255, 0, 0);
+        if (pt.x >= x && pt.x < x + 10 && pt.y < win->widgets->h) {
+            col = on;
+            if (GetAsyncKeyState(VK_LBUTTON) & 0x8000)
+                clicked |= 1 << i;
+        } else {
+            col = off;
+        }
+        col.a = win->widgetAlpha;
+        tigrPrint(win->widgets, tfont, x, 2, col, str);
+    }
+
+    if (clicked & 1)
+        ShowWindow((HWND)bmp->handle, SW_MINIMIZE);
+    if (clicked & 2)
+        tigrLeaveBorderlessWindowed(bmp);
+    if (clicked & 4)
+        SendMessage((HWND)bmp->handle, WM_CLOSE, 0, 0);
+}
+
+void tigrUpdate(Tigr* bmp) {
+    MSG msg;
+    RECT rc;
+    int dw, dh;
+    TigrInternal* win = tigrInternal(bmp);
+
+    if (!win->shown) {
+        win->shown = 1;
+        UpdateWindow((HWND)bmp->handle);
+        ShowWindow((HWND)bmp->handle, SW_SHOW);
+    }
+
+    // Get the window size.
+    GetClientRect((HWND)bmp->handle, &rc);
+    dw = rc.right - rc.left;
+    dh = rc.bottom - rc.top;
+
+    // Update the widget overlay.
+    tigrWinUpdateWidgets(bmp, dw, dh);
+
+    if (!tigrGAPIBegin(bmp)) {
+        tigrGAPIPresent(bmp, dw, dh);
+        SwapBuffers(win->gl.dc);
+        tigrGAPIEnd(bmp);
+    }
+
+    memcpy(win->prev, win->keys, 256);
+    win->scrollDeltaX = 0;
+    win->scrollDeltaY = 0;
+
+    // Run the message pump.
+    while (PeekMessage(&msg, (HWND)bmp->handle, 0, 0, PM_REMOVE)) {
+        if (msg.message == WM_QUIT)
+            break;
+
+        TranslateMessage(&msg);
+        DispatchMessage(&msg);
+    }
+}
+
+typedef BOOL(APIENTRY* PFNWGLSWAPINTERVALFARPROC_)(int);
+static PFNWGLSWAPINTERVALFARPROC_ wglSwapIntervalEXT_ = 0;
+
+int tigrGAPIBegin(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+
+    return wglMakeCurrent(win->gl.dc, win->gl.hglrc) ? 0 : -1;
+}
+
+int tigrGAPIEnd(Tigr* bmp) {
+    (void)bmp;
+    return wglMakeCurrent(NULL, NULL) ? 0 : -1;
+}
+
+static BOOL UnadjustWindowRectEx(LPRECT prc, DWORD dwStyle, BOOL fMenu, DWORD dwExStyle) {
+    BOOL fRc;
+    RECT rc;
+    SetRectEmpty(&rc);
+    fRc = AdjustWindowRectEx(&rc, dwStyle, fMenu, dwExStyle);
+    if (fRc) {
+        prc->left -= rc.left;
+        prc->top -= rc.top;
+        prc->right -= rc.right;
+        prc->bottom -= rc.bottom;
+    }
+    return fRc;
+}
+
+LRESULT CALLBACK tigrWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
+    Tigr* bmp;
+    TigrInternal* win = NULL;
+    RECT rc;
+    int dw, dh;
+
+    GetClientRect(hWnd, &rc);
+    dw = rc.right - rc.left;
+    dh = rc.bottom - rc.top;
+
+    bmp = (Tigr*)GetPropW(hWnd, L"Tigr");
+    if (bmp)
+        win = tigrInternal(bmp);
+
+    switch (message) {
+        case WM_PAINT:
+            if (!tigrGAPIBegin(bmp)) {
+                tigrGAPIPresent(bmp, dw, dh);
+                SwapBuffers(win->gl.dc);
+                tigrGAPIEnd(bmp);
+            }
+            ValidateRect(hWnd, NULL);
+            break;
+        case WM_CLOSE:
+            if (win)
+                win->closed = 1;
+            break;
+        case WM_GETMINMAXINFO:
+            if (bmp) {
+                MINMAXINFO* info = (MINMAXINFO*)lParam;
+                RECT rc;
+                rc.left = 0;
+                rc.top = 0;
+                if (win->flags & TIGR_AUTO) {
+                    rc.right = 32;
+                    rc.bottom = 32;
+                } else {
+                    int minscale = tigrEnforceScale(1, win->flags);
+                    rc.right = bmp->w * minscale;
+                    rc.bottom = bmp->h * minscale;
+                }
+                AdjustWindowRectEx(&rc, win->dwStyle, FALSE, 0);
+                info->ptMinTrackSize.x = rc.right - rc.left;
+                info->ptMinTrackSize.y = rc.bottom - rc.top;
+            }
+            return 0;
+        case WM_SIZING:
+            if (win) {
+                // Calculate scale-constrained sizes.
+                RECT* rc = (RECT*)lParam;
+                int dx, dy;
+                UnadjustWindowRectEx(rc, win->dwStyle, FALSE, 0);
+                dx = (rc->right - rc->left) % win->scale;
+                dy = (rc->bottom - rc->top) % win->scale;
+                switch (wParam) {
+                    case WMSZ_LEFT:
+                        rc->left += dx;
+                        break;
+                    case WMSZ_RIGHT:
+                        rc->right -= dx;
+                        break;
+                    case WMSZ_TOP:
+                        rc->top += dy;
+                        break;
+                    case WMSZ_TOPLEFT:
+                        rc->left += dx;
+                        rc->top += dy;
+                        break;
+                    case WMSZ_TOPRIGHT:
+                        rc->right -= dx;
+                        rc->top += dy;
+                        break;
+                    case WMSZ_BOTTOM:
+                        rc->bottom -= dy;
+                        break;
+                    case WMSZ_BOTTOMLEFT:
+                        rc->left += dx;
+                        rc->bottom -= dy;
+                        break;
+                    case WMSZ_BOTTOMRIGHT:
+                        rc->right -= dx;
+                        rc->bottom -= dy;
+                        break;
+                }
+                AdjustWindowRectEx(rc, win->dwStyle, FALSE, 0);
+            }
+            return TRUE;
+        case WM_SIZE:
+            if (win) {
+                if (wParam != SIZE_MINIMIZED) {
+                    // Detect window size changes and update our bitmap accordingly.
+                    dw = LOWORD(lParam);
+                    dh = HIWORD(lParam);
+                    if (win->flags & TIGR_AUTO) {
+                        tigrResize(bmp, dw / win->scale, dh / win->scale);
+                    } else {
+                        win->scale = tigrEnforceScale(tigrCalcScale(bmp->w, bmp->h, dw, dh), win->flags);
+                    }
+                    tigrPosition(bmp, win->scale, dw, dh, win->pos);
+                }
+
+                // If someone tried to maximize us (e.g. via shortcut launch options),
+                // prefer instead to be borderless.
+                if (wParam == SIZE_MAXIMIZED) {
+                    ShowWindow((HWND)bmp->handle, SW_NORMAL);
+                    tigrEnterBorderlessWindowed(bmp);
+                }
+            }
+            return 0;
+#ifndef TIGR_DO_NOT_PRESERVE_WINDOW_POSITION
+        case WM_WINDOWPOSCHANGED: {
+            // Save our position.
+            WINDOWPLACEMENT wp = { sizeof(WINDOWPLACEMENT) };
+            GetWindowPlacement(hWnd, &wp);
+            if (win->dwStyle & WS_POPUP)
+                wp.showCmd = SW_MAXIMIZE;
+            RegSetValueExW(tigrRegKey, win->wtitle, 0, REG_BINARY, (BYTE*)&wp, sizeof(wp));
+            return DefWindowProcW(hWnd, message, wParam, lParam);
+        }
+#endif
+        case WM_ACTIVATE:
+            if (win) {
+                memset(win->keys, 0, 256);
+                memset(win->prev, 0, 256);
+                win->lastChar = 0;
+            }
+            return 0;
+        case WM_CHAR:
+            if (win) {
+                if (wParam == '\r') {
+                    wParam = '\n';
+                }
+                int repeating = (HIWORD(lParam) & KF_REPEAT) == KF_REPEAT;
+                if (!repeating) {
+                    win->lastChar = wParam;
+                }
+            }
+            return DefWindowProcW(hWnd, message, wParam, lParam);
+        case WM_MENUCHAR:
+            // Disable beep on Alt+Enter
+            if (LOWORD(wParam) == VK_RETURN)
+                return MNC_CLOSE << 16;
+            return DefWindowProcW(hWnd, message, wParam, lParam);
+        case WM_SYSKEYDOWN:
+            if (win) {
+                if (wParam == VK_RETURN) {
+                    // Alt+Enter
+                    if (win->dwStyle & WS_POPUP)
+                        tigrLeaveBorderlessWindowed(bmp);
+                    else
+                        tigrEnterBorderlessWindowed(bmp);
+                    return 0;
+                }
+            }
+            // fall-thru
+        case WM_KEYDOWN:
+            if (win)
+                win->keys[wParam] = 1;
+            return DefWindowProcW(hWnd, message, wParam, lParam);
+        case WM_SYSKEYUP:
+            // fall-thru
+        case WM_KEYUP:
+            if (win)
+                win->keys[wParam] = 0;
+            return DefWindowProcW(hWnd, message, wParam, lParam);
+        case WM_MOUSEWHEEL:
+            if (win)
+                win->scrollDeltaY += (float)GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
+            return DefWindowProcW(hWnd, message, wParam, lParam);
+        case WM_MOUSEHWHEEL:
+            if (win)
+                win->scrollDeltaX += (float)GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
+            return DefWindowProcW(hWnd, message, wParam, lParam);
+        default:
+            return DefWindowProcW(hWnd, message, wParam, lParam);
+    }
+    return 0;
+}
+
+Tigr* tigrWindow(int w, int h, const char* title, int flags) {
+    WNDCLASSEXW wcex = { 0 };
+    int maxW, maxH, scale;
+    HWND hWnd;
+    DWORD dwStyle;
+    RECT rc;
+    Tigr* bmp;
+    TigrInternal* win;
+#ifndef TIGR_DO_NOT_PRESERVE_WINDOW_POSITION
+    WINDOWPLACEMENT wp;
+    DWORD wpsize = sizeof(wp);
+#endif
+
+    wchar_t* wtitle = unicode(title);
+
+// Find our registry key.
+#ifndef TIGR_DO_NOT_PRESERVE_WINDOW_POSITION
+    RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\TIGR", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,
+                    &tigrRegKey, NULL);
+#endif
+
+    // Register a window class.
+    wcex.cbSize = sizeof(WNDCLASSEXW);
+    wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
+    wcex.lpfnWndProc = tigrWndProc;
+    wcex.hInstance = GetModuleHandle(NULL);
+    wcex.hIcon = NULL;
+    wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
+    wcex.lpszClassName = L"TIGR";
+    RegisterClassExW(&wcex);
+
+    if (flags & TIGR_AUTO) {
+        // Always use a 1:1 pixel size.
+        scale = 1;
+    } else {
+        // See how big we can make it and still fit on-screen.
+        maxW = GetSystemMetrics(SM_CXSCREEN) * 3 / 4;
+        maxH = GetSystemMetrics(SM_CYSCREEN) * 3 / 4;
+        scale = tigrCalcScale(w, h, maxW, maxH);
+    }
+
+    scale = tigrEnforceScale(scale, flags);
+
+    // Get the final window size.
+    dwStyle = WS_OVERLAPPEDWINDOW;
+    rc.left = 0;
+    rc.top = 0;
+    rc.right = w * scale;
+    rc.bottom = h * scale;
+    AdjustWindowRect(&rc, dwStyle, FALSE);
+
+    // Make a window.
+    hWnd = CreateWindowW(L"TIGR", wtitle, dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top,
+                         NULL, NULL, wcex.hInstance, NULL);
+    if (!hWnd)
+        ExitProcess(1);
+
+    if (flags & TIGR_NOCURSOR) {
+        ShowCursor(FALSE);
+    }
+
+    // Wrap a bitmap around it.
+    bmp = tigrBitmap2(w, h, sizeof(TigrInternal));
+    bmp->handle = hWnd;
+
+    // Set up the Windows parts.
+    win = tigrInternal(bmp);
+    win->dwStyle = dwStyle;
+    win->wtitle = wtitle;
+    win->shown = 0;
+    win->closed = 0;
+    win->scale = scale;
+    win->lastChar = 0;
+    win->flags = flags;
+
+    win->p1 = win->p2 = win->p3 = 0;
+    win->p4 = 1;
+
+    win->widgetsWanted = 0;
+    win->widgetAlpha = 0;
+    win->widgetsScale = WIDGET_SCALE;
+    win->widgets = tigrBitmap(40, 14);
+
+    SetPropW(hWnd, L"Tigr", bmp);
+
+    tigrGAPICreate(bmp);
+
+    if (flags & TIGR_FULLSCREEN) {
+        tigrEnterBorderlessWindowed(bmp);
+    } else {
+// Try and restore our window position.
+#ifndef TIGR_DO_NOT_PRESERVE_WINDOW_POSITION
+        if (RegQueryValueExW(tigrRegKey, wtitle, NULL, NULL, (BYTE*)&wp, &wpsize) == ERROR_SUCCESS) {
+            if (wp.showCmd == SW_MAXIMIZE)
+                tigrEnterBorderlessWindowed(bmp);
+            else
+                SetWindowPlacement(hWnd, &wp);
+        }
+#endif
+    }
+
+    wglSwapIntervalEXT_ = (PFNWGLSWAPINTERVALFARPROC_)wglGetProcAddress("wglSwapIntervalEXT");
+    if (wglSwapIntervalEXT_)
+        wglSwapIntervalEXT_(1);
+
+    return bmp;
+}
+
+void tigrFree(Tigr* bmp) {
+    if (bmp->handle) {
+        TigrInternal* win = tigrInternal(bmp);
+        tigrGAPIDestroy(bmp);
+
+        if (win->gl.hglrc && !wglDeleteContext(win->gl.hglrc)) {
+            tigrError(bmp, "Cannot delete OpenGL context.\n");
+        }
+        win->gl.hglrc = NULL;
+
+        if (win->gl.dc && !ReleaseDC((HWND)bmp->handle, win->gl.dc)) {
+            tigrError(bmp, "Cannot release OpenGL device context.\n");
+        }
+        win->gl.dc = NULL;
+
+        DestroyWindow((HWND)bmp->handle);
+        free(win->wtitle);
+        tigrFree(win->widgets);
+    }
+    free(bmp->pix);
+    free(bmp);
+}
+
+int tigrClosed(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    int val = win->closed;
+    win->closed = 0;
+    return val;
+}
+
+float tigrTime(void) {
+    static int first = 1;
+    static LARGE_INTEGER prev;
+
+    LARGE_INTEGER cnt, freq;
+    ULONGLONG diff;
+    QueryPerformanceCounter(&cnt);
+    QueryPerformanceFrequency(&freq);
+
+    if (first) {
+        first = 0;
+        prev = cnt;
+    }
+
+    diff = cnt.QuadPart - prev.QuadPart;
+    prev = cnt;
+    return (float)(diff / (double)freq.QuadPart);
+}
+
+void tigrMouse(Tigr* bmp, int* x, int* y, int* buttons) {
+    POINT pt;
+    TigrInternal* win;
+
+    win = tigrInternal(bmp);
+    GetCursorPos(&pt);
+    ScreenToClient((HWND)bmp->handle, &pt);
+    *x = (pt.x - win->pos[0]) / win->scale;
+    *y = (pt.y - win->pos[1]) / win->scale;
+    *buttons = 0;
+    if (GetFocus() != bmp->handle)
+        return;
+    if (GetAsyncKeyState(VK_LBUTTON) & 0x8000)
+        *buttons |= 1;
+    if (GetAsyncKeyState(VK_MBUTTON) & 0x8000)
+        *buttons |= 2;
+    if (GetAsyncKeyState(VK_RBUTTON) & 0x8000)
+        *buttons |= 4;
+}
+
+int tigrTouch(Tigr* bmp, TigrTouchPoint* points, int maxPoints) {
+    int buttons = 0;
+    if (maxPoints > 0) {
+        tigrMouse(bmp, &points[0].x, &points[1].y, &buttons);
+    }
+    return buttons ? 1 : 0;
+}
+
+void tigrScrollWheel(Tigr* bmp, float* x, float* y) {
+    TigrInternal* win;
+    win = tigrInternal(bmp);
+    if (x) {
+        *x = win->scrollDeltaX;
+    }
+    if (y) {
+        *y = win->scrollDeltaY;
+    }
+}
+
+static int tigrWinVK(int key) {
+    if (key >= 'A' && key <= 'Z')
+        return key;
+    if (key >= '0' && key <= '9')
+        return key;
+    switch (key) {
+        case TK_BACKSPACE:
+            return VK_BACK;
+        case TK_TAB:
+            return VK_TAB;
+        case TK_RETURN:
+            return VK_RETURN;
+        case TK_SHIFT:
+            return VK_SHIFT;
+        case TK_CONTROL:
+            return VK_CONTROL;
+        case TK_ALT:
+            return VK_MENU;
+        case TK_PAUSE:
+            return VK_PAUSE;
+        case TK_CAPSLOCK:
+            return VK_CAPITAL;
+        case TK_ESCAPE:
+            return VK_ESCAPE;
+        case TK_SPACE:
+            return VK_SPACE;
+        case TK_PAGEUP:
+            return VK_PRIOR;
+        case TK_PAGEDN:
+            return VK_NEXT;
+        case TK_END:
+            return VK_END;
+        case TK_HOME:
+            return VK_HOME;
+        case TK_LEFT:
+            return VK_LEFT;
+        case TK_UP:
+            return VK_UP;
+        case TK_RIGHT:
+            return VK_RIGHT;
+        case TK_DOWN:
+            return VK_DOWN;
+        case TK_INSERT:
+            return VK_INSERT;
+        case TK_DELETE:
+            return VK_DELETE;
+        case TK_LWIN:
+            return VK_LWIN;
+        case TK_RWIN:
+            return VK_RWIN;
+        // case TK_APPS: return VK_APPS; // this key doesn't exist on OS X
+        case TK_PAD0:
+            return VK_NUMPAD0;
+        case TK_PAD1:
+            return VK_NUMPAD1;
+        case TK_PAD2:
+            return VK_NUMPAD2;
+        case TK_PAD3:
+            return VK_NUMPAD3;
+        case TK_PAD4:
+            return VK_NUMPAD4;
+        case TK_PAD5:
+            return VK_NUMPAD5;
+        case TK_PAD6:
+            return VK_NUMPAD6;
+        case TK_PAD7:
+            return VK_NUMPAD7;
+        case TK_PAD8:
+            return VK_NUMPAD8;
+        case TK_PAD9:
+            return VK_NUMPAD9;
+        case TK_PADMUL:
+            return VK_MULTIPLY;
+        case TK_PADADD:
+            return VK_ADD;
+        case TK_PADENTER:
+            return VK_SEPARATOR;
+        case TK_PADSUB:
+            return VK_SUBTRACT;
+        case TK_PADDOT:
+            return VK_DECIMAL;
+        case TK_PADDIV:
+            return VK_DIVIDE;
+        case TK_F1:
+            return VK_F1;
+        case TK_F2:
+            return VK_F2;
+        case TK_F3:
+            return VK_F3;
+        case TK_F4:
+            return VK_F4;
+        case TK_F5:
+            return VK_F5;
+        case TK_F6:
+            return VK_F6;
+        case TK_F7:
+            return VK_F7;
+        case TK_F8:
+            return VK_F8;
+        case TK_F9:
+            return VK_F9;
+        case TK_F10:
+            return VK_F10;
+        case TK_F11:
+            return VK_F11;
+        case TK_F12:
+            return VK_F12;
+        case TK_NUMLOCK:
+            return VK_NUMLOCK;
+        case TK_SCROLL:
+            return VK_SCROLL;
+        case TK_LSHIFT:
+            return VK_LSHIFT;
+        case TK_RSHIFT:
+            return VK_RSHIFT;
+        case TK_LCONTROL:
+            return VK_LCONTROL;
+        case TK_RCONTROL:
+            return VK_RCONTROL;
+        case TK_LALT:
+            return VK_LMENU;
+        case TK_RALT:
+            return VK_RMENU;
+        case TK_SEMICOLON:
+            return VK_OEM_1;
+        case TK_EQUALS:
+            return VK_OEM_PLUS;
+        case TK_COMMA:
+            return VK_OEM_COMMA;
+        case TK_MINUS:
+            return VK_OEM_MINUS;
+        case TK_DOT:
+            return VK_OEM_PERIOD;
+        case TK_SLASH:
+            return VK_OEM_2;
+        case TK_BACKTICK:
+            return VK_OEM_3;
+        case TK_LSQUARE:
+            return VK_OEM_4;
+        case TK_BACKSLASH:
+            return VK_OEM_5;
+        case TK_RSQUARE:
+            return VK_OEM_6;
+        case TK_TICK:
+            return VK_OEM_7;
+    }
+    return 0;
+}
+
+int tigrKeyDown(Tigr* bmp, int key) {
+    TigrInternal* win;
+    int k = tigrWinVK(key);
+    if (GetFocus() != bmp->handle)
+        return 0;
+    win = tigrInternal(bmp);
+    return win->keys[k] && !win->prev[k];
+}
+
+int tigrKeyHeld(Tigr* bmp, int key) {
+    TigrInternal* win;
+    int k = tigrWinVK(key);
+    if (GetFocus() != bmp->handle)
+        return 0;
+    win = tigrInternal(bmp);
+    return win->keys[k];
+}
+
+int tigrReadChar(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    int c = win->lastChar;
+    win->lastChar = 0;
+    return c;
+}
+
+// We supply our own WinMain and just chain through to the user's
+// real entry point.
+#ifdef UNICODE
+int CALLBACK wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
+#else
+int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
+#endif
+{
+    int n, argc;
+    LPWSTR* wargv = CommandLineToArgvW(GetCommandLineW(), &argc);
+    char** argv = (char**)calloc(argc + 1, sizeof(int));
+
+    (void)hInstance;
+    (void)hPrevInstance;
+    (void)lpCmdLine;
+    (void)nCmdShow;
+
+    for (n = 0; n < argc; n++) {
+        int len = WideCharToMultiByte(CP_UTF8, 0, wargv[n], -1, 0, 0, NULL, NULL);
+        argv[n] = (char*)malloc(len);
+        WideCharToMultiByte(CP_UTF8, 0, wargv[n], -1, argv[n], len, NULL, NULL);
+    }
+    return main(argc, argv);
+}
+#endif
+
+#endif // #ifndef TIGR_HEADLESS
+//////// End of inlined file: tigr_win.c ////////
+
+//////// Start of inlined file: tigr_osx.c ////////
+
+#ifndef TIGR_HEADLESS
+
+// originally based on https://github.com/jimon/osx_app_in_plain_c
+
+//#include "tigr_internal.h"
+
+#if __MACOS__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
+//////// Start of inlined file: tigr_objc.h ////////
+
+#ifndef TIGR_OBJC_H
+#define TIGR_OBJC_H
+
+#if defined(__IOS__) || defined(__MACOS__)
+
+#if defined(__OBJC__) && __has_feature(objc_arc)
+#error "Can't compile as objective-c code!"
+#endif
+
+// ABI is a bit different between platforms
+#ifdef __arm64__
+#define abi_objc_msgSend_stret objc_msgSend
+#else
+#define abi_objc_msgSend_stret objc_msgSend_stret
+#endif
+#ifdef __i386__
+#define abi_objc_msgSend_fpret objc_msgSend_fpret
+#else
+#define abi_objc_msgSend_fpret objc_msgSend
+#endif
+
+#define objc_msgSendSuper_t(RET, ...) ((RET(*)(struct objc_super*, SEL, ##__VA_ARGS__))objc_msgSendSuper)
+#define objc_msgSend_t(RET, ...) ((RET(*)(id, SEL, ##__VA_ARGS__))objc_msgSend)
+#define objc_msgSend_stret_t(RET, ...) ((RET(*)(id, SEL, ##__VA_ARGS__))abi_objc_msgSend_stret)
+#define objc_msgSend_id objc_msgSend_t(id)
+#define objc_msgSend_void objc_msgSend_t(void)
+#define objc_msgSend_void_id objc_msgSend_t(void, id)
+#define objc_msgSend_void_bool objc_msgSend_t(void, bool)
+
+#define sel(NAME) sel_registerName(NAME)
+#define class(NAME) ((id)objc_getClass(NAME))
+#define makeClass(NAME, SUPER) objc_allocateClassPair((Class)objc_getClass(SUPER), NAME, 0)
+
+// Check here to get the signature right: https://nshipster.com/type-encodings/
+#define addMethod(CLASS, NAME, IMPL, SIGNATURE)                       \
+    if (!class_addMethod(CLASS, sel(NAME), (IMP)(IMPL), (SIGNATURE))) \
+    assert(false)
+
+#define addIvar(CLASS, NAME, SIZE, SIGNATURE)                           \
+    if (!class_addIvar(CLASS, NAME, SIZE, rint(log2(SIZE)), SIGNATURE)) \
+    assert(false)
+
+#define objc_alloc(CLASS) objc_msgSend_id(class(CLASS), sel("alloc"))
+
+#if __LP64__ || NS_BUILD_32_LIKE_64
+#define NSIntegerEncoding "q"
+#define NSUIntegerEncoding "L"
+#else
+#define NSIntegerEncoding "i"
+#define NSUIntegerEncoding "I"
+#endif
+
+#endif  // defined(__IOS__) || defined (__MACOS__)
+#endif  // TIGR_OBJC_H
+
+//////// End of inlined file: tigr_objc.h ////////
+
+#pragma clang diagnostic pop
+
+#include <assert.h>
+#include <limits.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreGraphics/CoreGraphics.h>
+#include <mach/mach_time.h>
+#include <objc/NSObjCRuntime.h>
+#include <objc/message.h>
+#include <objc/objc.h>
+#include <objc/runtime.h>
+
+#include <CoreGraphics/CGBase.h>
+#include <CoreGraphics/CGGeometry.h>
+
+typedef CGPoint NSPoint;
+typedef CGSize NSSize;
+typedef CGRect NSRect;
+
+enum {
+    NSKeyDown = 10,
+    NSKeyDownMask = 1 << NSKeyDown,
+    NSKeyUp = 11,
+    NSKeyUpMask = 1 << NSKeyUp,
+};
+
+NSUInteger NSAllEventMask = NSUIntegerMax;
+
+extern id NSApp;
+extern id const NSDefaultRunLoopMode;
+
+#define NSApplicationActivationPolicyRegular 0
+
+bool terminated = false;
+
+static double _tigrTimestamp = 0;
+
+double _currentMediaTime(void) {
+    uint64_t now = clock_gettime_nsec_np(CLOCK_UPTIME_RAW);
+    double current = now * 1.0E-9;
+    return current;
+}
+
+TigrInternal* _tigrInternalFromWindowDelegate(id wdg) {
+    Tigr* bmp = 0;
+    object_getInstanceVariable(wdg, "tigrHandle", (void**)&bmp);
+    return bmp ? tigrInternal(bmp) : NULL;
+}
+
+TigrInternal* _tigrInternalFromWindow(id window) {
+    if (!window)
+        return NULL;
+
+    id wdg = objc_msgSend_id(window, sel("delegate"));
+    if (!wdg)
+        return NULL;
+
+    return _tigrInternalFromWindowDelegate(wdg);
+}
+
+NSUInteger applicationShouldTerminate(id self, SEL sel, id sender) {
+    terminated = true;
+    return 0;
+}
+
+void windowWillClose(id self, SEL _sel, id notification) {
+    NSUInteger value = true;
+    object_setInstanceVariable(self, "closed", (void*)value);
+    object_setInstanceVariable(self, "tigrHandle", (void*)0);
+}
+
+void windowDidEnterFullScreen(id self, SEL _sel, id notification) {
+    NSUInteger value = true;
+    object_setInstanceVariable(self, "visible", (void*)value);
+}
+
+void windowDidResize(id self, SEL _sel, id notification) {
+    TigrInternal* win;
+    Tigr* bmp = 0;
+    object_getInstanceVariable(self, "tigrHandle", (void**)&bmp);
+    win = bmp ? tigrInternal(bmp) : NULL;
+    if (win) {
+        win->mouseButtons = 0;
+    }
+}
+
+void windowDidBecomeKey(id self, SEL _sel, id notification) {
+    TigrInternal* win;
+    Tigr* bmp = 0;
+    object_getInstanceVariable(self, "tigrHandle", (void**)&bmp);
+    win = bmp ? tigrInternal(bmp) : NULL;
+
+    if (win) {
+        memset(win->keys, 0, 256);
+        memset(win->prev, 0, 256);
+        win->lastChar = 0;
+        win->mouseButtons = 0;
+    }
+}
+
+void onFrame(id self, SEL _sel, id displayLink) {
+    double timestamp = objc_msgSend_t(double)(displayLink, sel("timestamp"));
+    TigrInternal* win = _tigrInternalFromWindowDelegate(self);
+    if (win == NULL) {
+        return;
+    }
+    win->frameTime = timestamp;
+}
+
+void mouseEntered(id self, SEL _sel, id event) {
+    id window = objc_msgSend_id(event, sel("window"));
+    TigrInternal* win = _tigrInternalFromWindow(window);
+    if (win) {
+        win->mouseInView = 1;
+        if (win->flags & TIGR_NOCURSOR) {
+            objc_msgSend_id(class("NSCursor"), sel("hide"));
+        }
+    }
+}
+
+void mouseExited(id self, SEL _sel, id event) {
+    id window = objc_msgSend_id(event, sel("window"));
+    TigrInternal* win = _tigrInternalFromWindow(window);
+    if (win) {
+        win->mouseInView = 0;
+        if (win->flags & TIGR_NOCURSOR) {
+            objc_msgSend_id(class("NSCursor"), sel("unhide"));
+        }
+    }
+}
+
+bool _tigrIsWindowClosed(id window) {
+    id wdg = objc_msgSend_id(window, sel("delegate"));
+    if (!wdg)
+        return false;
+    NSUInteger value = 0;
+    object_getInstanceVariable(wdg, "closed", (void**)&value);
+    return value ? true : false;
+}
+
+bool _tigrIsWindowVisible(id window) {
+    id wdg = objc_msgSend_id(window, sel("delegate"));
+    if (!wdg)
+        return false;
+    NSUInteger value = 0;
+    object_getInstanceVariable(wdg, "visible", (void**)&value);
+    return value ? true : false;
+}
+
+static bool tigrOSXInited = false;
+static id autoreleasePool = NULL;
+
+#ifdef DEBUG
+static void _showPools(const char* context) {
+    fprintf(stderr, "NSAutoreleasePool@%s:\n", context);
+    objc_msgSend(class("NSAutoreleasePool"), sel("showPools"));
+}
+#define showPools(x) _showPools((x))
+#else
+#define showPools(x)
+#endif
+
+static id pushPool(void) {
+    id pool = objc_msgSend_id(class("NSAutoreleasePool"), sel("alloc"));
+    return objc_msgSend_id(pool, sel("init"));
+}
+
+static void popPool(id pool) {
+    objc_msgSend_void(pool, sel("drain"));
+}
+
+void _tigrCleanupOSX(void) {
+    showPools("cleanup");
+    popPool(autoreleasePool);
+}
+
+void tigrInitOSX(void) {
+    if (tigrOSXInited)
+        return;
+
+    atexit(&_tigrCleanupOSX);
+
+    autoreleasePool = pushPool();
+
+    showPools("init start");
+
+    objc_msgSend_id(class("NSApplication"), sel("sharedApplication"));
+    objc_msgSend_t(void, NSInteger)(NSApp, sel("setActivationPolicy:"), NSApplicationActivationPolicyRegular);
+
+    Class appDelegateClass = makeClass("AppDelegate", "NSObject");
+    addMethod(appDelegateClass, "applicationShouldTerminate", applicationShouldTerminate, NSUIntegerEncoding "@:@");
+    id dgAlloc = objc_msgSend_id((id)appDelegateClass, sel("alloc"));
+    id dg = objc_msgSend_id(dgAlloc, sel("init"));
+
+    objc_msgSend_void_id(NSApp, sel("setDelegate:"), dg);
+    objc_msgSend_void(NSApp, sel("finishLaunching"));
+
+    id menuBar = objc_alloc("NSMenu");
+    menuBar = objc_msgSend_id(menuBar, sel("init"));
+
+    id appMenuItem = objc_alloc("NSMenuItem");
+    appMenuItem = objc_msgSend_id(appMenuItem, sel("init"));
+
+    objc_msgSend_void_id(menuBar, sel("addItem:"), appMenuItem);
+    objc_msgSend_t(id, id)(NSApp, sel("setMainMenu:"), menuBar);
+
+    id processInfo = objc_msgSend_id(class("NSProcessInfo"), sel("processInfo"));
+    id appName = objc_msgSend_id(processInfo, sel("processName"));
+
+    id appMenu = objc_alloc("NSMenu");
+    appMenu = objc_msgSend_t(id, id)(appMenu, sel("initWithTitle:"), appName);
+
+    id quitTitlePrefixString =
+        objc_msgSend_t(id, const char*)(class("NSString"), sel("stringWithUTF8String:"), "Quit ");
+    id quitTitle = objc_msgSend_t(id, id)(quitTitlePrefixString, sel("stringByAppendingString:"), appName);
+
+    id quitMenuItemKey = objc_msgSend_t(id, const char*)(class("NSString"), sel("stringWithUTF8String:"), "q");
+    id quitMenuItem = objc_alloc("NSMenuItem");
+    quitMenuItem = objc_msgSend_t(id, id, SEL, id)(quitMenuItem, sel("initWithTitle:action:keyEquivalent:"), quitTitle,
+                                                   sel("terminate:"), quitMenuItemKey);
+
+    objc_msgSend_void_id(appMenu, sel("addItem:"), quitMenuItem);
+    objc_msgSend_void_id(appMenuItem, sel("setSubmenu:"), appMenu);
+
+    tigrOSXInited = true;
+
+    showPools("init end");
+}
+
+void tigrError(Tigr* bmp, const char* message, ...) {
+    char tmp[1024];
+
+    va_list args;
+    va_start(args, message);
+    vsnprintf(tmp, sizeof(tmp), message, args);
+    tmp[sizeof(tmp) - 1] = 0;
+    va_end(args);
+
+    printf("tigr fatal error: %s\n", tmp);
+
+    exit(1);
+}
+
+NSSize _tigrContentBackingSize(id window) {
+    id contentView = objc_msgSend_id(window, sel("contentView"));
+    NSRect rect = objc_msgSend_stret_t(NSRect)(contentView, sel("frame"));
+    rect = objc_msgSend_stret_t(NSRect, NSRect)(contentView, sel("convertRectToBacking:"), rect);
+
+    return rect.size;
+}
+
+enum {
+    NSWindowStyleMaskTitled = 1 << 0,
+    NSWindowStyleMaskClosable = 1 << 1,
+    NSWindowStyleMaskMiniaturizable = 1 << 2,
+    NSWindowStyleMaskResizable = 1 << 3,
+    NSWindowStyleRegular = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable |
+                           NSWindowStyleMaskResizable,
+    NSWindowStyleMaskFullSizeContentView = 1 << 15
+};
+
+Tigr* tigrWindow(int w, int h, const char* title, int flags) {
+    Tigr* bmp;
+    TigrInternal* win;
+
+    tigrInitOSX();
+
+    NSUInteger windowStyleMask = NSWindowStyleRegular & ~NSWindowStyleMaskMiniaturizable;
+
+    // In AUTO mode, window follows requested size, unless downscaled by tigrEnforceScale below.
+    int windowScale = 1;
+
+    // In non-AUTO mode, see how big we can make it and still fit on-screen.
+    if ((flags & TIGR_AUTO) == 0) {
+        CGRect mainMonitor = CGDisplayBounds(CGMainDisplayID());
+        int maxW = CGRectGetWidth(mainMonitor);
+        int maxH = CGRectGetHeight(mainMonitor);
+        NSRect screen = { { 0, 0 }, { maxW, maxH } };
+        NSRect content = objc_msgSend_stret_t(NSRect, NSRect, NSUInteger)(
+            class("NSWindow"), sel("contentRectForFrameRect:styleMask:"), screen, windowStyleMask);
+        windowScale = tigrCalcScale(w, h, content.size.width, content.size.height);
+    }
+
+    windowScale = tigrEnforceScale(windowScale, flags);
+
+    NSRect rect = { { 0, 0 }, { w * windowScale, h * windowScale } };
+    id windowAlloc = objc_msgSend_id(class("NSWindow"), sel("alloc"));
+    id window = ((id(*)(id, SEL, NSRect, NSUInteger, NSUInteger, BOOL))objc_msgSend)(
+        windowAlloc, sel("initWithContentRect:styleMask:backing:defer:"), rect, windowStyleMask, 2, NO);
+
+    objc_msgSend_void_bool(window, sel("setReleasedWhenClosed:"), NO);
+
+    Class WindowDelegateClass = objc_allocateClassPair((Class)objc_getClass("NSObject"), "WindowDelegate", 0);
+    addIvar(WindowDelegateClass, "closed", sizeof(NSUInteger), NSUIntegerEncoding);
+    addIvar(WindowDelegateClass, "visible", sizeof(NSUInteger), NSUIntegerEncoding);
+    addIvar(WindowDelegateClass, "tigrHandle", sizeof(void*), "ˆv");
+    addMethod(WindowDelegateClass, "windowWillClose:", windowWillClose, "v@:@");
+    addMethod(WindowDelegateClass, "windowDidEnterFullScreen:", windowDidEnterFullScreen, "v@:@");
+    addMethod(WindowDelegateClass, "windowDidResize:", windowDidResize, "v@:@");
+    addMethod(WindowDelegateClass, "windowDidBecomeKey:", windowDidBecomeKey, "v@:@");
+    addMethod(WindowDelegateClass, "mouseEntered:", mouseEntered, "v@:@");
+    addMethod(WindowDelegateClass, "mouseExited:", mouseExited, "v@:@");
+    addMethod(WindowDelegateClass, "onFrame:", onFrame, "v@:@");
+
+    id wdgAlloc = objc_msgSend_id((id)WindowDelegateClass, sel("alloc"));
+    id wdg = objc_msgSend_id(wdgAlloc, sel("init"));
+
+    if (flags & TIGR_FULLSCREEN) {
+        objc_msgSend_void_id(window, sel("toggleFullScreen:"), window);
+        if (flags & TIGR_NOCURSOR) {
+            objc_msgSend_id(class("NSCursor"), sel("hide"));
+        }
+    } else {
+        NSUInteger value = true;
+        object_setInstanceVariable(wdg, "visible", (void*)value);
+    }
+
+    objc_msgSend_void_id(window, sel("setDelegate:"), wdg);
+
+    id contentView = objc_msgSend_id(window, sel("contentView"));
+
+    objc_msgSend_void_bool(contentView, sel("setWantsLayer:"), NO);
+
+    bool useDisplayLink = objc_msgSend_t(bool, SEL)(class("NSView"), sel("instancesRespondToSelector:"),
+                                                    sel("displayLinkWithTarget:selector:"));
+
+    if (useDisplayLink) {
+        // Use display link to pace frames, if possible
+        id link =
+            objc_msgSend_t(id, id, SEL)(contentView, sel("displayLinkWithTarget:selector:"), wdg, sel("onFrame:"));
+        id runLoop = objc_msgSend_id(class("NSRunLoop"), sel("currentRunLoop"));
+        objc_msgSend_t(void, id, id)(link, sel("addToRunLoop:forMode:"), runLoop, NSDefaultRunLoopMode);
+        objc_msgSend_void_bool(link, sel("setPaused:"), NO);
+    }
+
+    int wantsHighRes = (flags & TIGR_RETINA);
+    objc_msgSend_void_bool(contentView, sel("setWantsBestResolutionOpenGLSurface:"), wantsHighRes);
+
+    NSPoint point = { 20, 20 };
+    ((void (*)(id, SEL, NSPoint))objc_msgSend)(window, sel("cascadeTopLeftFromPoint:"), point);
+
+    id titleString = objc_msgSend_t(id, const char*)(class("NSString"), sel("stringWithUTF8String:"), title);
+    objc_msgSend_void_id(window, sel("setTitle:"), titleString);
+
+    uint32_t glAttributes[] = { 8, 24,  //	NSOpenGLPFAColorSize, 24,
+                                11, 8,  //	NSOpenGLPFAAlphaSize, 8,
+                                5,      //	NSOpenGLPFADoubleBuffer,
+                                73,     //	NSOpenGLPFAAccelerated,
+                                // 72,			//	NSOpenGLPFANoRecovery,
+                                // 55, 1,		//	NSOpenGLPFASampleBuffers, 1,
+                                // 56, 4,		//	NSOpenGLPFASamples, 4,
+                                99, 0x3200,  //	NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
+                                // 70, 0x00020400, // NSOpenGLPFARendererID, kCGLRendererGenericFloatID
+                                0 };
+
+    id pixelFormat = objc_alloc("NSOpenGLPixelFormat");
+    pixelFormat = objc_msgSend_t(id, const uint32_t*)(pixelFormat, sel("initWithAttributes:"), glAttributes);
+    objc_msgSend_void(pixelFormat, sel("autorelease"));
+
+    id openGLContext = objc_alloc("NSOpenGLContext");
+    openGLContext = objc_msgSend_t(id, id, id)(openGLContext, sel("initWithFormat:shareContext:"), pixelFormat, nil);
+
+    objc_msgSend_void_id(openGLContext, sel("setView:"), contentView);
+    objc_msgSend_void_id(window, sel("makeKeyAndOrderFront:"), window);
+    objc_msgSend_void_bool(window, sel("setAcceptsMouseMovedEvents:"), YES);
+
+    id blackColor = objc_msgSend_id(class("NSColor"), sel("blackColor"));
+    objc_msgSend_void_id(window, sel("setBackgroundColor:"), blackColor);
+
+    objc_msgSend_void_bool(NSApp, sel("activateIgnoringOtherApps:"), YES);
+
+    NSSize windowContentSize = _tigrContentBackingSize(window);
+
+    // In AUTO mode, always use a 1:1 pixel size, unless downscaled by tigrEnforceScale below.
+    int bitmapScale = 1;
+
+    // In non-AUTO mode, scale based on backing size
+    if ((flags & TIGR_AUTO) == 0) {
+        bitmapScale = tigrEnforceScale(tigrCalcScale(w, h, windowContentSize.width, windowContentSize.height), flags);
+    } else {
+        // In AUTO mode, bitmap size follows window size
+        w = windowContentSize.width / windowScale;
+        h = windowContentSize.height / windowScale;
+        bitmapScale = tigrEnforceScale(bitmapScale, flags);
+    }
+
+    bmp = tigrBitmap2(w, h, sizeof(TigrInternal));
+    bmp->handle = window;
+
+    // Set the handle
+    object_setInstanceVariable(wdg, "tigrHandle", (void*)bmp);
+
+    {
+#define NSTrackingMouseEnteredAndExited 1
+#define NSTrackingActiveInKeyWindow 0x20
+#define NSTrackingInVisibleRect 0x200
+
+        int trackingFlags = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect;
+        id trackingArea = objc_msgSend_id(class("NSTrackingArea"), sel("alloc"));
+        trackingArea = objc_msgSend_t(id, NSRect, int, id, id)(
+            trackingArea, sel("initWithRect:options:owner:userInfo:"), rect, trackingFlags, wdg, 0);
+        objc_msgSend_void_id(contentView, sel("addTrackingArea:"), trackingArea);
+    }
+
+    // Set up the Windows parts.
+    win = tigrInternal(bmp);
+    win->shown = 0;
+    win->closed = 0;
+    win->scale = bitmapScale;
+    win->lastChar = 0;
+    win->flags = flags;
+    win->p1 = win->p2 = win->p3 = 0;
+    win->p4 = 1;
+    win->widgetsWanted = 0;
+    win->widgetAlpha = 0;
+    win->widgetsScale = 0;
+    win->widgets = 0;
+    win->gl.gl_legacy = 0;
+    win->gl.glContext = openGLContext;
+    win->mouseButtons = 0;
+    win->mouseInView = 0;
+    win->frameTime = 0;
+
+    tigrPosition(bmp, win->scale, bmp->w, bmp->h, win->pos);
+
+    GLint swapInterval = 1;
+    NSInteger NSOpenGLCPSwapInterval = 222;
+
+    objc_msgSend_t(void, GLint*, NSInteger)(openGLContext, sel("setValues:forParameter:"), &swapInterval,
+                                            NSOpenGLCPSwapInterval);
+
+    objc_msgSend_void(openGLContext, sel("makeCurrentContext"));
+
+    tigrGAPICreate(bmp);
+
+    return bmp;
+}
+
+void tigrFree(Tigr* bmp) {
+    if (bmp->handle) {
+        TigrInternal* win = tigrInternal(bmp);
+        tigrGAPIDestroy(bmp);
+
+        id window = (id)bmp->handle;
+
+        if (!_tigrIsWindowClosed(window) && !terminated) {
+            objc_msgSend_void(window, sel("close"));
+        }
+
+        if (win->flags & TIGR_NOCURSOR) {
+            objc_msgSend_id(class("NSCursor"), sel("unhide"));
+        }
+
+        id wdg = objc_msgSend_id(window, sel("delegate"));
+        objc_msgSend_void(wdg, sel("release"));
+        objc_msgSend_void((id)win->gl.glContext, sel("release"));
+        objc_msgSend_void(window, sel("release"));
+    }
+    free(bmp->pix);
+    free(bmp);
+}
+
+uint8_t _tigrKeyFromOSX(uint16_t key) {
+    // from Carbon HIToolbox/Events.h
+    enum {
+        kVK_ANSI_A = 0x00,
+        kVK_ANSI_S = 0x01,
+        kVK_ANSI_D = 0x02,
+        kVK_ANSI_F = 0x03,
+        kVK_ANSI_H = 0x04,
+        kVK_ANSI_G = 0x05,
+        kVK_ANSI_Z = 0x06,
+        kVK_ANSI_X = 0x07,
+        kVK_ANSI_C = 0x08,
+        kVK_ANSI_V = 0x09,
+        kVK_ANSI_B = 0x0B,
+        kVK_ANSI_Q = 0x0C,
+        kVK_ANSI_W = 0x0D,
+        kVK_ANSI_E = 0x0E,
+        kVK_ANSI_R = 0x0F,
+        kVK_ANSI_Y = 0x10,
+        kVK_ANSI_T = 0x11,
+        kVK_ANSI_1 = 0x12,
+        kVK_ANSI_2 = 0x13,
+        kVK_ANSI_3 = 0x14,
+        kVK_ANSI_4 = 0x15,
+        kVK_ANSI_6 = 0x16,
+        kVK_ANSI_5 = 0x17,
+        kVK_ANSI_Equal = 0x18,
+        kVK_ANSI_9 = 0x19,
+        kVK_ANSI_7 = 0x1A,
+        kVK_ANSI_Minus = 0x1B,
+        kVK_ANSI_8 = 0x1C,
+        kVK_ANSI_0 = 0x1D,
+        kVK_ANSI_RightBracket = 0x1E,
+        kVK_ANSI_O = 0x1F,
+        kVK_ANSI_U = 0x20,
+        kVK_ANSI_LeftBracket = 0x21,
+        kVK_ANSI_I = 0x22,
+        kVK_ANSI_P = 0x23,
+        kVK_ANSI_L = 0x25,
+        kVK_ANSI_J = 0x26,
+        kVK_ANSI_Quote = 0x27,
+        kVK_ANSI_K = 0x28,
+        kVK_ANSI_Semicolon = 0x29,
+        kVK_ANSI_Backslash = 0x2A,
+        kVK_ANSI_Comma = 0x2B,
+        kVK_ANSI_Slash = 0x2C,
+        kVK_ANSI_N = 0x2D,
+        kVK_ANSI_M = 0x2E,
+        kVK_ANSI_Period = 0x2F,
+        kVK_ANSI_Grave = 0x32,
+        kVK_ANSI_KeypadDecimal = 0x41,
+        kVK_ANSI_KeypadMultiply = 0x43,
+        kVK_ANSI_KeypadPlus = 0x45,
+        kVK_ANSI_KeypadClear = 0x47,
+        kVK_ANSI_KeypadDivide = 0x4B,
+        kVK_ANSI_KeypadEnter = 0x4C,
+        kVK_ANSI_KeypadMinus = 0x4E,
+        kVK_ANSI_KeypadEquals = 0x51,
+        kVK_ANSI_Keypad0 = 0x52,
+        kVK_ANSI_Keypad1 = 0x53,
+        kVK_ANSI_Keypad2 = 0x54,
+        kVK_ANSI_Keypad3 = 0x55,
+        kVK_ANSI_Keypad4 = 0x56,
+        kVK_ANSI_Keypad5 = 0x57,
+        kVK_ANSI_Keypad6 = 0x58,
+        kVK_ANSI_Keypad7 = 0x59,
+        kVK_ANSI_Keypad8 = 0x5B,
+        kVK_ANSI_Keypad9 = 0x5C,
+        kVK_Return = 0x24,
+        kVK_Tab = 0x30,
+        kVK_Space = 0x31,
+        kVK_Delete = 0x33,
+        kVK_Escape = 0x35,
+        kVK_Command = 0x37,
+        kVK_Shift = 0x38,
+        kVK_CapsLock = 0x39,
+        kVK_Option = 0x3A,
+        kVK_Control = 0x3B,
+        kVK_RightShift = 0x3C,
+        kVK_RightOption = 0x3D,
+        kVK_RightControl = 0x3E,
+        kVK_Function = 0x3F,
+        kVK_F17 = 0x40,
+        kVK_VolumeUp = 0x48,
+        kVK_VolumeDown = 0x49,
+        kVK_Mute = 0x4A,
+        kVK_F18 = 0x4F,
+        kVK_F19 = 0x50,
+        kVK_F20 = 0x5A,
+        kVK_F5 = 0x60,
+        kVK_F6 = 0x61,
+        kVK_F7 = 0x62,
+        kVK_F3 = 0x63,
+        kVK_F8 = 0x64,
+        kVK_F9 = 0x65,
+        kVK_F11 = 0x67,
+        kVK_F13 = 0x69,
+        kVK_F16 = 0x6A,
+        kVK_F14 = 0x6B,
+        kVK_F10 = 0x6D,
+        kVK_F12 = 0x6F,
+        kVK_F15 = 0x71,
+        kVK_Help = 0x72,
+        kVK_Home = 0x73,
+        kVK_PageUp = 0x74,
+        kVK_ForwardDelete = 0x75,
+        kVK_F4 = 0x76,
+        kVK_End = 0x77,
+        kVK_F2 = 0x78,
+        kVK_PageDown = 0x79,
+        kVK_F1 = 0x7A,
+        kVK_LeftArrow = 0x7B,
+        kVK_RightArrow = 0x7C,
+        kVK_DownArrow = 0x7D,
+        kVK_UpArrow = 0x7E
+    };
+
+    switch (key) {
+        case kVK_ANSI_Q:
+            return 'Q';
+        case kVK_ANSI_W:
+            return 'W';
+        case kVK_ANSI_E:
+            return 'E';
+        case kVK_ANSI_R:
+            return 'R';
+        case kVK_ANSI_T:
+            return 'T';
+        case kVK_ANSI_Y:
+            return 'Y';
+        case kVK_ANSI_U:
+            return 'U';
+        case kVK_ANSI_I:
+            return 'I';
+        case kVK_ANSI_O:
+            return 'O';
+        case kVK_ANSI_P:
+            return 'P';
+        case kVK_ANSI_A:
+            return 'A';
+        case kVK_ANSI_S:
+            return 'S';
+        case kVK_ANSI_D:
+            return 'D';
+        case kVK_ANSI_F:
+            return 'F';
+        case kVK_ANSI_G:
+            return 'G';
+        case kVK_ANSI_H:
+            return 'H';
+        case kVK_ANSI_J:
+            return 'J';
+        case kVK_ANSI_K:
+            return 'K';
+        case kVK_ANSI_L:
+            return 'L';
+        case kVK_ANSI_Z:
+            return 'Z';
+        case kVK_ANSI_X:
+            return 'X';
+        case kVK_ANSI_C:
+            return 'C';
+        case kVK_ANSI_V:
+            return 'V';
+        case kVK_ANSI_B:
+            return 'B';
+        case kVK_ANSI_N:
+            return 'N';
+        case kVK_ANSI_M:
+            return 'M';
+        case kVK_ANSI_0:
+            return '0';
+        case kVK_ANSI_1:
+            return '1';
+        case kVK_ANSI_2:
+            return '2';
+        case kVK_ANSI_3:
+            return '3';
+        case kVK_ANSI_4:
+            return '4';
+        case kVK_ANSI_5:
+            return '5';
+        case kVK_ANSI_6:
+            return '6';
+        case kVK_ANSI_7:
+            return '7';
+        case kVK_ANSI_8:
+            return '8';
+        case kVK_ANSI_9:
+            return '9';
+        case kVK_ANSI_Keypad0:
+            return TK_PAD0;
+        case kVK_ANSI_Keypad1:
+            return TK_PAD1;
+        case kVK_ANSI_Keypad2:
+            return TK_PAD2;
+        case kVK_ANSI_Keypad3:
+            return TK_PAD3;
+        case kVK_ANSI_Keypad4:
+            return TK_PAD4;
+        case kVK_ANSI_Keypad5:
+            return TK_PAD5;
+        case kVK_ANSI_Keypad6:
+            return TK_PAD6;
+        case kVK_ANSI_Keypad7:
+            return TK_PAD7;
+        case kVK_ANSI_Keypad8:
+            return TK_PAD8;
+        case kVK_ANSI_Keypad9:
+            return TK_PAD9;
+        case kVK_ANSI_KeypadMultiply:
+            return TK_PADMUL;
+        case kVK_ANSI_KeypadPlus:
+            return TK_PADADD;
+        case kVK_ANSI_KeypadEnter:
+            return TK_PADENTER;
+        case kVK_ANSI_KeypadMinus:
+            return TK_PADSUB;
+        case kVK_ANSI_KeypadDecimal:
+            return TK_PADDOT;
+        case kVK_ANSI_KeypadDivide:
+            return TK_PADDIV;
+        case kVK_F1:
+            return TK_F1;
+        case kVK_F2:
+            return TK_F2;
+        case kVK_F3:
+            return TK_F3;
+        case kVK_F4:
+            return TK_F4;
+        case kVK_F5:
+            return TK_F5;
+        case kVK_F6:
+            return TK_F6;
+        case kVK_F7:
+            return TK_F7;
+        case kVK_F8:
+            return TK_F8;
+        case kVK_F9:
+            return TK_F9;
+        case kVK_F10:
+            return TK_F10;
+        case kVK_F11:
+            return TK_F11;
+        case kVK_F12:
+            return TK_F12;
+        case kVK_Shift:
+            return TK_LSHIFT;
+        case kVK_Control:
+            return TK_LCONTROL;
+        case kVK_Option:
+            return TK_LALT;
+        case kVK_CapsLock:
+            return TK_CAPSLOCK;
+        case kVK_Command:
+            return TK_LWIN;
+        case kVK_Command - 1:
+            return TK_RWIN;
+        case kVK_RightShift:
+            return TK_RSHIFT;
+        case kVK_RightControl:
+            return TK_RCONTROL;
+        case kVK_RightOption:
+            return TK_RALT;
+        case kVK_Delete:
+            return TK_BACKSPACE;
+        case kVK_Tab:
+            return TK_TAB;
+        case kVK_Return:
+            return TK_RETURN;
+        case kVK_Escape:
+            return TK_ESCAPE;
+        case kVK_Space:
+            return TK_SPACE;
+        case kVK_PageUp:
+            return TK_PAGEUP;
+        case kVK_PageDown:
+            return TK_PAGEDN;
+        case kVK_End:
+            return TK_END;
+        case kVK_Home:
+            return TK_HOME;
+        case kVK_LeftArrow:
+            return TK_LEFT;
+        case kVK_UpArrow:
+            return TK_UP;
+        case kVK_RightArrow:
+            return TK_RIGHT;
+        case kVK_DownArrow:
+            return TK_DOWN;
+        case kVK_Help:
+            return TK_INSERT;
+        case kVK_ForwardDelete:
+            return TK_DELETE;
+        case kVK_F14:
+            return TK_SCROLL;
+        case kVK_F15:
+            return TK_PAUSE;
+        case kVK_ANSI_KeypadClear:
+            return TK_NUMLOCK;
+        case kVK_ANSI_Semicolon:
+            return TK_SEMICOLON;
+        case kVK_ANSI_Equal:
+            return TK_EQUALS;
+        case kVK_ANSI_Comma:
+            return TK_COMMA;
+        case kVK_ANSI_Minus:
+            return TK_MINUS;
+        case kVK_ANSI_Slash:
+            return TK_SLASH;
+        case kVK_ANSI_Backslash:
+            return TK_BACKSLASH;
+        case kVK_ANSI_Grave:
+            return TK_BACKTICK;
+        case kVK_ANSI_Quote:
+            return TK_TICK;
+        case kVK_ANSI_LeftBracket:
+            return TK_LSQUARE;
+        case kVK_ANSI_RightBracket:
+            return TK_RSQUARE;
+        case kVK_ANSI_Period:
+            return TK_DOT;
+        default:
+            return 0;
+    }
+}
+
+void _tigrOnCocoaEvent(id event, id window) {
+    if (!event)
+        return;
+
+    TigrInternal* win = _tigrInternalFromWindow(window);
+    if (!win)  // just pipe the event
+    {
+        objc_msgSend_void_id(NSApp, sel("sendEvent:"), event);
+        return;
+    }
+
+    NSUInteger eventType = objc_msgSend_t(NSUInteger)(event, sel("type"));
+    switch (eventType) {
+        case 1:  // NSLeftMouseDown
+            if (win->mouseInView) {
+                win->mouseButtons |= 1;
+            }
+            break;
+        case 2:  // NSLeftMouseUp
+            win->mouseButtons &= ~1;
+            break;
+        case 3:  // NSRightMouseDown
+            if (win->mouseInView) {
+                win->mouseButtons |= 2;
+            }
+            break;
+        case 4:  // NSRightMouseUp
+            win->mouseButtons &= ~2;
+            break;
+        case 25:  // NSOtherMouseDown
+        {
+            // number == 2 is a middle button
+            NSInteger number = objc_msgSend_t(NSInteger)(event, sel("buttonNumber"));
+            if (number == 2 && win->mouseInView) {
+                win->mouseButtons |= 4;
+            }
+            break;
+        }
+        case 26:  // NSOtherMouseUp
+        {
+            NSInteger number = objc_msgSend_t(NSInteger)(event, sel("buttonNumber"));
+            if (number == 2)
+                win->mouseButtons &= ~4;
+            break;
+        }
+        case 22:  // NSScrollWheel
+        {
+            CGFloat deltaX = objc_msgSend_t(CGFloat)(event, sel("deltaX"));
+            win->scrollDeltaX += deltaX;
+            CGFloat deltaY = objc_msgSend_t(CGFloat)(event, sel("deltaY"));
+            win->scrollDeltaY += deltaY;
+            break;
+        }
+        case 12:  // NSFlagsChanged
+        {
+            NSUInteger modifiers = objc_msgSend_t(NSUInteger)(event, sel("modifierFlags"));
+
+            // based on NSEventModifierFlags and
+            // NSDeviceIndependentModifierFlagsMask
+            struct {
+                union {
+                    struct {
+                        uint8_t alpha_shift : 1;
+                        uint8_t shift : 1;
+                        uint8_t control : 1;
+                        uint8_t alternate : 1;
+                        uint8_t command : 1;
+                        uint8_t numeric_pad : 1;
+                        uint8_t help : 1;
+                        uint8_t function : 1;
+                    };
+                    uint8_t mask;
+                };
+            } keys;
+
+            keys.mask = (modifiers & 0xffff0000UL) >> 16;
+
+            // TODO L,R variation of keys?
+            win->keys[TK_CONTROL] = keys.alpha_shift;
+            win->keys[TK_SHIFT] = keys.shift;
+            win->keys[TK_CONTROL] = keys.control;
+            win->keys[TK_ALT] = keys.alternate;
+            win->keys[TK_LWIN] = keys.command;
+            win->keys[TK_RWIN] = keys.command;
+            break;
+        }
+        case 10:  // NSKeyDown
+        {
+            uint16_t keyCode = objc_msgSend_t(unsigned short)(event, sel("keyCode"));
+            int tigrKey = _tigrKeyFromOSX(keyCode);
+
+            // Ignore keyboard repeats
+            if (!win->keys[tigrKey]) {
+                win->keys[tigrKey] = 1;
+                id inputText = objc_msgSend_id(event, sel("characters"));
+                const char* inputTextUTF8 = objc_msgSend_t(const char*)(inputText, sel("UTF8String"));
+
+                int decoded = 0;
+                tigrDecodeUTF8(inputTextUTF8, &decoded);
+                if (decoded < 0xe000 || decoded > 0xf8ff) {
+                    win->lastChar = decoded;
+                }
+            }
+
+            // Pass through cmd+key
+            if (win->keys[TK_LWIN]) {
+                break;
+            }
+            return;
+        }
+        case 11:  // NSKeyUp
+        {
+            uint16_t keyCode = objc_msgSend_t(unsigned short)(event, sel("keyCode"));
+            win->keys[_tigrKeyFromOSX(keyCode)] = 0;
+            return;
+        }
+        default:
+            break;
+    }
+
+    objc_msgSend_void_id(NSApp, sel("sendEvent:"), event);
+}
+
+void tigrUpdate(Tigr* bmp) {
+    popPool(autoreleasePool);
+    autoreleasePool = pushPool();
+
+    TigrInternal* win;
+    id openGLContext;
+    id window;
+    win = tigrInternal(bmp);
+    window = (id)bmp->handle;
+    openGLContext = (id)win->gl.glContext;
+
+    if (terminated || _tigrIsWindowClosed(window)) {
+        return;
+    }
+
+    id keyWindow = objc_msgSend_id(NSApp, sel("keyWindow"));
+    unsigned long long eventMask = NSAllEventMask;
+
+    if (keyWindow == window) {
+        memcpy(win->prev, win->keys, 256);
+    } else {
+        eventMask &= ~(NSKeyDownMask | NSKeyUpMask);
+    }
+
+    win->scrollDeltaX = 0;
+    win->scrollDeltaY = 0;
+
+    id event = 0;
+    BOOL visible = 0;
+
+    double now = _currentMediaTime();
+    double passed = now - _tigrTimestamp;
+
+    do {
+        event = objc_msgSend_t(id, NSUInteger, id, id, BOOL)(
+            NSApp, sel("nextEventMatchingMask:untilDate:inMode:dequeue:"), eventMask, nil, NSDefaultRunLoopMode, YES);
+
+        if (event != 0) {
+            _tigrOnCocoaEvent(event, window);
+        } else {
+            visible = _tigrIsWindowVisible(window);
+        }
+    } while (event != 0 || !visible);
+
+    // The event processing loop above blocks during resize, which causes updates to freeze
+    // but real time keeps ticking. We pretend that the event processing took no time
+    // to avoid huge jumps in tigrTime.
+    if (_tigrTimestamp != 0) {
+        _tigrTimestamp = _currentMediaTime() - passed;
+    }
+
+    objc_msgSend_void(NSApp, sel("updateWindows"));
+    objc_msgSend_void(openGLContext, sel("update"));
+    tigrGAPIBegin(bmp);
+
+    NSSize windowSize = _tigrContentBackingSize(window);
+
+    if (win->flags & TIGR_AUTO)
+        tigrResize(bmp, windowSize.width / win->scale, windowSize.height / win->scale);
+    else
+        win->scale = tigrEnforceScale(tigrCalcScale(bmp->w, bmp->h, windowSize.width, windowSize.height), win->flags);
+
+    tigrPosition(bmp, win->scale, windowSize.width, windowSize.height, win->pos);
+    tigrGAPIPresent(bmp, windowSize.width, windowSize.height);
+    objc_msgSend_void(openGLContext, sel("flushBuffer"));
+    tigrGAPIEnd(bmp);
+
+    id runLoop = objc_msgSend_id(class("NSRunLoop"), sel("currentRunLoop"));
+    id until = objc_msgSend_t(id, float)(class("NSDate"), sel("dateWithTimeIntervalSinceNow:"), 0.005f);
+    double lastFrameTime = win->frameTime;
+    static const double cap = 1.0 / 60;
+
+    while (win->frameTime <= lastFrameTime) {
+        // Run the event loop for 5ms at a time..
+        objc_msgSend_t(void, id, id)(runLoop, sel("runMode:beforeDate:"), NSDefaultRunLoopMode, until);
+        // ..until we get a frame callback..
+        if (win->frameTime > lastFrameTime) {
+            break;
+        }
+        double now = _currentMediaTime();
+        double passed = now - lastFrameTime;
+        // ..or it's time to cap the wait at 60 fps
+        if (passed > cap) {
+            win->frameTime = now;
+            break;
+        }
+    }
+}
+
+int tigrGAPIBegin(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    objc_msgSend_void((id)win->gl.glContext, sel("makeCurrentContext"));
+    return 0;
+}
+
+int tigrGAPIEnd(Tigr* bmp) {
+    (void)bmp;
+    objc_msgSend_void(class("NSOpenGLContext"), sel("clearCurrentContext"));
+    return 0;
+}
+
+int tigrClosed(Tigr* bmp) {
+    return (terminated || _tigrIsWindowClosed((id)bmp->handle)) ? 1 : 0;
+}
+
+void tigrMouse(Tigr* bmp, int* x, int* y, int* buttons) {
+    TigrInternal* win;
+    id window;
+    win = tigrInternal(bmp);
+    window = (id)bmp->handle;
+
+    id windowContentView = objc_msgSend_id(window, sel("contentView"));
+    NSRect adjustFrame = objc_msgSend_stret_t(NSRect)(windowContentView, sel("frame"));
+
+    // NSPoint is small enough to fit a register, so no need for
+    // objc_msgSend_stret
+    NSPoint p = objc_msgSend_t(NSPoint)(window, sel("mouseLocationOutsideOfEventStream"));
+
+    // map input to content view rect
+    if (p.x < 0)
+        p.x = 0;
+    else if (p.x > adjustFrame.size.width)
+        p.x = adjustFrame.size.width;
+    if (p.y < 0)
+        p.y = 0;
+    else if (p.y > adjustFrame.size.height)
+        p.y = adjustFrame.size.height;
+
+    // map input to pixels
+    NSRect r = { p, { 0, 0 } };
+    r = objc_msgSend_stret_t(NSRect, NSRect)(windowContentView, sel("convertRectToBacking:"), r);
+    p = r.origin;
+
+    p.x = (p.x - win->pos[0]) / win->scale;
+    p.y = bmp->h - (p.y - win->pos[1]) / win->scale;
+
+    if (x)
+        *x = p.x;
+    if (y)
+        *y = p.y;
+
+    if (buttons) {
+        id keyWindow = objc_msgSend_id(NSApp, sel("keyWindow"));
+        *buttons = keyWindow != bmp->handle ? 0 : win->mouseButtons;
+    }
+}
+
+int tigrTouch(Tigr* bmp, TigrTouchPoint* points, int maxPoints) {
+    int buttons = 0;
+    if (maxPoints > 0) {
+        tigrMouse(bmp, &points[0].x, &points[1].y, &buttons);
+    }
+    return buttons ? 1 : 0;
+}
+
+void tigrScrollWheel(Tigr* bmp, float* x, float* y) {
+    TigrInternal* win;
+    win = tigrInternal(bmp);
+    if (x) {
+        *x = win->scrollDeltaX;
+    }
+    if (y) {
+        *y = win->scrollDeltaY;
+    }
+}
+
+int tigrKeyDown(Tigr* bmp, int key) {
+    TigrInternal* win;
+    assert(key < 256);
+    win = tigrInternal(bmp);
+    return (win->keys[key] != 0) && (win->prev[key] == 0);
+}
+
+int tigrKeyHeld(Tigr* bmp, int key) {
+    TigrInternal* win;
+    assert(key < 256);
+    win = tigrInternal(bmp);
+    return win->keys[key];
+}
+
+int tigrReadChar(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    int c = win->lastChar;
+    win->lastChar = 0;
+    return c;
+}
+
+float tigrTime(void) {
+    double elapsed = 0;
+
+    double now = _currentMediaTime();
+    if (_tigrTimestamp != 0) {
+        elapsed = now - _tigrTimestamp;
+    }
+    _tigrTimestamp = now;
+
+    return (float)elapsed;
+}
+
+#endif  // __MACOS__
+#endif  // #ifndef TIGR_HEADLESS
+
+//////// End of inlined file: tigr_osx.c ////////
+
+//////// Start of inlined file: tigr_ios.c ////////
+
+#ifndef TIGR_HEADLESS
+
+//#include "tigr_internal.h"
+//#include "tigr_objc.h"
+
+#ifdef __IOS__
+
+#include <CoreGraphics/CoreGraphics.h>
+#include <objc/message.h>
+#include <objc/objc.h>
+#include <objc/runtime.h>
+#include <dispatch/dispatch.h>
+#include <os/log.h>
+#include <time.h>
+#include <stdatomic.h>
+
+id makeNSString(const char* str) {
+    return objc_msgSend_t(id, const char*)(class("NSString"), sel("stringWithUTF8String:"), str);
+}
+
+id joinNSStrings(id a, id b) {
+    return objc_msgSend_t(id, id)(a, sel("stringByAppendingString:"), b);
+}
+
+const char* UTF8StringFromNSString(id a) {
+    return objc_msgSend_t(const char*)(a, sel("UTF8String"));
+}
+
+extern id UIApplication;
+static int NSQualityOfServiceUserInteractive = 0x21;
+
+typedef struct {
+    TigrTouchPoint points[MAX_TOUCH_POINTS];
+    int numPoints;
+} InputState;
+
+typedef struct {
+    int keyCode;
+    int codePoint;
+} KeyEvent;
+
+enum {
+    KBD_HIDDEN = 0,
+    KBD_SHOWREQ,
+    KBD_HIDEREQ,
+    KBD_SHOWN,
+};
+
+/// Global state
+static struct {
+    InputState inputState;
+    id viewController;
+    id view;
+    id context;
+    id frameCondition;
+    int screenW;
+    int screenH;
+    double scaleFactor;
+    double timeSinceLastDraw;
+    int renderReadFd;
+    int mainWriteFd;
+    _Atomic(int) keyboardState;
+} gState = {
+    .inputState = {
+        .numPoints = 0,
+    },
+    .viewController = 0,
+    .view = 0,
+    .context = 0,
+    .frameCondition = 0,
+    .screenW = 0,
+    .screenH = 0,
+    .scaleFactor = 1,
+    .timeSinceLastDraw = 0,
+    .renderReadFd = 0,
+    .mainWriteFd = 0,
+    .keyboardState = ATOMIC_VAR_INIT(KBD_HIDDEN),
+};
+
+typedef enum {
+    SET_INPUT,
+    KEY_EVENT,
+} TigrMessage;
+
+typedef struct TigrMessageData {
+    TigrMessage message;
+    union {
+        InputState inputState;
+        KeyEvent keyEvent;
+    };
+} TigrMessageData;
+
+static id autoreleasePool = NULL;
+
+void writeToRenderThread(const TigrMessageData* message) {
+    if (write(gState.mainWriteFd, message, sizeof(TigrMessageData)) != sizeof(TigrMessageData)) {
+        os_log_error(OS_LOG_DEFAULT, "Failed to write message to render thread: %{public}s", strerror(errno));
+    }
+}
+
+int readFromMainThread(TigrMessageData* message) {
+    int result = read(gState.renderReadFd, message, sizeof(TigrMessageData));
+    if (result == -1 && errno == EAGAIN) {
+        return 0;
+    }
+    if (result != sizeof(TigrMessageData)) {
+        os_log_error(OS_LOG_DEFAULT, "Failed to read message from main thread: %{public}s", strerror(errno));
+        return 0;
+    }
+    return 1;
+}
+
+void viewWillTransitionToSize(id self, SEL _sel, CGSize size, id transitionCoordinator) {
+    // No animation, just set them
+    gState.screenW = size.width * gState.scaleFactor;
+    gState.screenH = size.height * gState.scaleFactor;
+    struct objc_super super = {
+        self,
+        objc_getClass("GLKViewController"),
+    };
+    objc_msgSendSuper_t(void, CGSize, id)(&super, _sel, size, transitionCoordinator);
+}
+
+BOOL prefersStatusBarHidden(id self, SEL _sel) {
+    return YES;
+}
+
+BOOL hasText(id self, SEL _sel) {
+    return NO;
+}
+
+void tigrShowKeyboard(int show) {
+    int expected = show ? KBD_HIDDEN : KBD_SHOWN;
+    int desired = show ? KBD_SHOWREQ : KBD_HIDEREQ;
+    atomic_compare_exchange_weak(&gState.keyboardState, &expected, desired);
+}
+
+void insertText(id self, SEL _sel, id text) {
+    const char* inserted = UTF8StringFromNSString(text);
+    int codePoint = 0;
+
+    do {
+        inserted = tigrDecodeUTF8(inserted, &codePoint);
+        if (codePoint != 0) {
+            KeyEvent event;
+            event.codePoint = codePoint;
+            event.keyCode = (codePoint < 128) ? codePoint : 0;
+
+            TigrMessageData message = {
+                .message = KEY_EVENT,
+                .keyEvent = event,
+            };
+            writeToRenderThread(&message);
+        }
+    } while (*inserted != 0);
+}
+
+void deleteBackward(id self, SEL _sel) {
+    KeyEvent event;
+    event.codePoint = 0;
+    event.keyCode = 8;  // BS
+
+    TigrMessageData message = { .message = KEY_EVENT,
+                                .keyEvent = {
+                                    .codePoint = 0,
+                                    .keyCode = 8,
+                                } };
+    writeToRenderThread(&message);
+}
+
+BOOL canBecomeFirstResponder(id self, SEL _sel) {
+    return YES;
+}
+
+BOOL canResignFirstResponder(id self, SEL _sel) {
+    return YES;
+}
+
+enum RenderState { SWAPPED = 5150, RENDERED };
+
+BOOL didFinishLaunchingWithOptions(id self, SEL _sel, id application, id options) {
+    id screen = objc_msgSend_id(class("UIScreen"), sel("mainScreen"));
+    CGRect bounds = objc_msgSend_t(CGRect)(screen, sel("bounds"));
+    CGSize size = bounds.size;
+
+    id window = objc_alloc("UIWindow");
+    window = objc_msgSend_t(id, CGRect)(window, sel("initWithFrame:"), bounds);
+
+    Class ViewController = makeClass("TigrViewController", "GLKViewController");
+    addMethod(ViewController, "viewWillTransitionToSize:withTransitionCoordinator:", viewWillTransitionToSize,
+              "v@:{CGSize}@");
+    addMethod(ViewController, "prefersStatusBarHidden", prefersStatusBarHidden, "c@:");
+    id vc = objc_msgSend_t(id)((id)ViewController, sel("alloc"));
+    vc = objc_msgSend_id(vc, sel("init"));
+    gState.viewController = vc;
+    objc_msgSend_t(void, int)(vc, sel("setPreferredFramesPerSecond:"), 60);
+    int framesPerSecond = objc_msgSend_t(int)(vc, sel("framesPerSecond"));
+
+    id context = objc_alloc("EAGLContext");
+    static int kEAGLRenderingAPIOpenGLES3 = 3;
+    context = objc_msgSend_t(id, int)(context, sel("initWithAPI:"), kEAGLRenderingAPIOpenGLES3);
+    gState.context = context;
+
+    Class View = makeClass("TigrView", "GLKView");
+    addMethod(View, "insertText:", insertText, "v@:@");
+    addMethod(View, "deleteBackward", deleteBackward, "v@:");
+    addMethod(View, "hasText", hasText, "c@:");
+    addMethod(View, "canBecomeFirstResponder", canBecomeFirstResponder, "c@:");
+    addMethod(View, "canResignFirstResponder", canResignFirstResponder, "c@:");
+
+    Protocol* UIKeyInput = objc_getProtocol("UIKeyInput");
+    class_addProtocol(View, UIKeyInput);
+
+    id view = objc_msgSend_id((id)View, sel("alloc"));
+    view = objc_msgSend_t(id, CGRect, id)(view, sel("initWithFrame:context:"), bounds, context);
+    gState.view = view;
+    objc_msgSend_t(void, BOOL)(view, sel("setMultipleTouchEnabled:"), YES);
+    objc_msgSend_t(void, id)(view, sel("setDelegate:"), self);
+    objc_msgSend_t(void, id)(vc, sel("setView:"), view);
+    objc_msgSend_t(void, id)(vc, sel("setDelegate:"), self);
+    objc_msgSend_t(void, id)(window, sel("setRootViewController:"), vc);
+    objc_msgSend_t(void)(window, sel("makeKeyAndVisible"));
+
+    gState.scaleFactor = objc_msgSend_t(double)(view, sel("contentScaleFactor"));
+    gState.screenW = size.width * gState.scaleFactor;
+    gState.screenH = size.height * gState.scaleFactor;
+
+    gState.frameCondition = objc_msgSend_t(id, int)(objc_alloc("NSConditionLock"), sel("initWithCondition:"), RENDERED);
+    objc_msgSend_t(void, int)(gState.frameCondition, sel("lockWhenCondition:"), RENDERED);
+
+    id renderThread = objc_msgSend_t(id, id, SEL, id)(objc_alloc("NSThread"), sel("initWithTarget:selector:object:"),
+                                                      self, sel("renderMain"), NULL);
+    objc_msgSend_t(void, int)(renderThread, sel("setQualityOfService:"), NSQualityOfServiceUserInteractive);
+    objc_msgSend_t(void, id)(renderThread, sel("setName:"), makeNSString("Tigr Render Thread"));
+    objc_msgSend_void(renderThread, sel("start"));
+
+    return YES;
+}
+
+void waitForFrame() {
+    objc_msgSend_t(void, id)(class("EAGLContext"), sel("setCurrentContext:"), 0);
+    objc_msgSend_t(void, int)(gState.frameCondition, sel("unlockWithCondition:"), RENDERED);
+    objc_msgSend_t(void, int)(gState.frameCondition, sel("lockWhenCondition:"), SWAPPED);
+    objc_msgSend_t(void, id)(class("EAGLContext"), sel("setCurrentContext:"), gState.context);
+}
+
+void processKeyboardRequest() {
+    int showReq = KBD_SHOWREQ;
+    int hideReq = KBD_HIDEREQ;
+
+    if (atomic_compare_exchange_weak(&gState.keyboardState, &showReq, KBD_SHOWN)) {
+        objc_msgSend_t(BOOL)(gState.view, sel("becomeFirstResponder"));
+    } else if (atomic_compare_exchange_weak(&gState.keyboardState, &hideReq, KBD_HIDDEN)) {
+        objc_msgSend_t(BOOL)(gState.view, sel("resignFirstResponder"));
+    }
+}
+
+void drawInRect(id _self, SEL _sel, id view, CGRect rect) {
+    gState.timeSinceLastDraw = objc_msgSend_t(double)(gState.viewController, sel("timeSinceLastDraw"));
+    objc_msgSend_t(void, int)(gState.frameCondition, sel("unlockWithCondition:"), SWAPPED);
+    objc_msgSend_t(void, int)(gState.frameCondition, sel("lockWhenCondition:"), RENDERED);
+    objc_msgSend_t(void, id)(class("EAGLContext"), sel("setCurrentContext:"), gState.context);
+
+    processKeyboardRequest();
+}
+
+extern void tigrMain();
+
+void renderMain(id _self, SEL _sel) {
+    objc_msgSend_t(void, int)(gState.frameCondition, sel("lockWhenCondition:"), SWAPPED);
+    objc_msgSend_t(void, id)(class("EAGLContext"), sel("setCurrentContext:"), gState.context);
+    tigrMain();
+}
+
+enum {
+    UITouchPhaseBegan,
+    UITouchPhaseMoved,
+    UITouchPhaseStationary,
+    UITouchPhaseEnded,
+    UITouchPhaseCancelled,
+};
+
+void touches(id self, SEL sel, id touches, id event) {
+    id allTouches = objc_msgSend_t(id)(event, sel("allTouches"));
+    id enumerator = objc_msgSend_t(id)(allTouches, sel("objectEnumerator"));
+    id touch = 0;
+    InputState input = {
+        .numPoints = 0,
+    };
+    while ((touch = objc_msgSend_t(id)(enumerator, sel("nextObject")))) {
+        CGPoint location = objc_msgSend_t(CGPoint, id)(touch, sel("locationInView:"), NULL);
+        int phase = objc_msgSend_t(int)(touch, sel("phase"));
+        switch (phase) {
+            case UITouchPhaseBegan:
+            case UITouchPhaseMoved:
+            case UITouchPhaseStationary:
+                input.points[input.numPoints].x = location.x * gState.scaleFactor;
+                input.points[input.numPoints].y = location.y * gState.scaleFactor;
+                input.numPoints++;
+                break;
+        }
+        if (input.numPoints >= MAX_TOUCH_POINTS) {
+            break;
+        }
+    }
+    TigrMessageData message = {
+        .message = SET_INPUT,
+        .inputState = input,
+    };
+    writeToRenderThread(&message);
+}
+
+Class tigrAppDelegate() {
+    static Class delegateClass = 0;
+    if (delegateClass != 0) {
+        return delegateClass;
+    }
+
+    id application = objc_msgSend_id(class("UIApplication"), sel("sharedApplication"));
+    delegateClass = makeClass("TigrAppDelegate", "UIResponder");
+    addMethod(delegateClass, "application:didFinishLaunchingWithOptions:", didFinishLaunchingWithOptions, "c@:@@");
+    addMethod(delegateClass, "touchesBegan:withEvent:", touches, "v@:@@");
+    addMethod(delegateClass, "touchesMoved:withEvent:", touches, "v@:@@");
+    addMethod(delegateClass, "touchesEnded:withEvent:", touches, "v@:@@");
+    addMethod(delegateClass, "touchesCancelled:withEvent:", touches, "v@:@@");
+    addMethod(delegateClass, "glkView:drawInRect:", drawInRect, "v@:@{CGRect}");
+    addMethod(delegateClass, "renderMain", renderMain, "v@:");
+    objc_registerClassPair(delegateClass);
+
+    int fds[2];
+    if (pipe(fds) != 0) {
+        tigrError(0, "Failed to create message pipe");
+    }
+    int flags = fcntl(fds[0], F_GETFL, 0);
+    fcntl(fds[0], F_SETFL, flags | O_NONBLOCK);
+
+    gState.renderReadFd = fds[0];
+    gState.mainWriteFd = fds[1];
+    return delegateClass;
+}
+
+Tigr* tigrWindow(int w, int h, const char* title, int flags) {
+    int scale = 1;
+    if (flags & TIGR_AUTO) {
+        // Always use a 1:1 pixel size.
+        scale = 1;
+    } else {
+        // See how big we can make it and still fit on-screen.
+        scale = tigrCalcScale(w, h, gState.screenW, gState.screenH);
+    }
+
+    scale = tigrEnforceScale(scale, flags);
+    Tigr* bmp = tigrBitmap2(w, h, sizeof(TigrInternal));
+    bmp->handle = (void*)4711;
+    TigrInternal* win = tigrInternal(bmp);
+    win->shown = 0;
+    win->closed = 0;
+    win->scale = scale;
+
+    win->lastChar = 0;
+    win->flags = flags;
+    win->p1 = win->p2 = win->p3 = 0;
+    win->p4 = 1;
+    win->widgetsWanted = 0;
+    win->widgetAlpha = 0;
+    win->widgetsScale = 0;
+    win->widgets = 0;
+    win->gl.gl_legacy = 0;
+
+    tigrPosition(bmp, win->scale, bmp->w, bmp->h, win->pos);
+    tigrGAPICreate(bmp);
+
+    return bmp;
+}
+
+void processEvents(TigrInternal* win) {
+    memset(win->keys, 0, 255);
+
+    TigrMessageData data;
+
+    while (readFromMainThread(&data)) {
+        switch (data.message) {
+            case SET_INPUT:
+                gState.inputState = data.inputState;
+                break;
+            case KEY_EVENT:
+                win->keys[data.keyEvent.keyCode] = 1;
+                win->lastChar = data.keyEvent.codePoint;
+                break;
+        }
+    }
+}
+
+static int toWindowX(TigrInternal* win, int x) {
+    return (x - win->pos[0]) / win->scale;
+}
+
+static int toWindowY(TigrInternal* win, int y) {
+    return (y - win->pos[1]) / win->scale;
+}
+
+void tigrUpdate(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+
+    processEvents(win);
+
+    win->numTouchPoints = gState.inputState.numPoints;
+    for (int i = 0; i < win->numTouchPoints; i++) {
+        win->touchPoints[i].x = toWindowX(win, gState.inputState.points[i].x);
+        win->touchPoints[i].y = toWindowY(win, gState.inputState.points[i].y);
+    }
+
+    win->mouseButtons = win->numTouchPoints;
+    if (win->mouseButtons > 0) {
+        win->mouseX = win->touchPoints[0].x;
+        win->mouseY = win->touchPoints[0].y;
+    }
+
+    if (win->flags & TIGR_AUTO) {
+        tigrResize(bmp, gState.screenW / win->scale, gState.screenH / win->scale);
+    } else {
+        win->scale = tigrEnforceScale(tigrCalcScale(bmp->w, bmp->h, gState.screenW, gState.screenH), win->flags);
+    }
+
+    tigrPosition(bmp, win->scale, gState.screenW, gState.screenH, win->pos);
+    tigrGAPIPresent(bmp, gState.screenW, gState.screenH);
+    waitForFrame();
+}
+
+int tigrClosed(Tigr* bmp) {
+    return 0;
+}
+
+void tigrError(Tigr* bmp, const char* message, ...) {
+    char tmp[1024];
+
+    va_list args;
+    va_start(args, message);
+    vsnprintf(tmp, sizeof(tmp), message, args);
+    tmp[sizeof(tmp) - 1] = 0;
+    va_end(args);
+
+    os_log_error(OS_LOG_DEFAULT, "tigr fatal error: %{public}s\n", tmp);
+
+    exit(1);
+}
+
+float tigrTime() {
+    return (float)gState.timeSinceLastDraw;
+}
+
+void tigrMouse(Tigr* bmp, int* x, int* y, int* buttons) {
+    TigrInternal* win = tigrInternal(bmp);
+    if (x) {
+        *x = win->mouseX;
+    }
+    if (y) {
+        *y = win->mouseY;
+    }
+    if (buttons) {
+        *buttons = win->mouseButtons;
+    }
+}
+
+int tigrTouch(Tigr* bmp, TigrTouchPoint* points, int maxPoints) {
+    TigrInternal* win = tigrInternal(bmp);
+    for (int i = 0; i < maxPoints && i < win->numTouchPoints; i++) {
+        points[i] = win->touchPoints[i];
+    }
+    return maxPoints < win->numTouchPoints ? maxPoints : win->numTouchPoints;
+}
+
+void tigrMouseWheel(Tigr* bmp, float* x, float* y) {
+    if (x) {
+        *x = 0;
+    }
+    if (y) {
+        *y = 0;
+    }
+}
+
+void tigrFree(Tigr* bmp) {
+    if (bmp->handle) {
+        TigrInternal* win = tigrInternal(bmp);
+    }
+    free(bmp->pix);
+    free(bmp);
+}
+
+int tigrGAPIBegin(Tigr* bmp) {
+    (void)bmp;
+    return 0;
+}
+
+int tigrGAPIEnd(Tigr* bmp) {
+    (void)bmp;
+    return 0;
+}
+
+int tigrKeyDown(Tigr* bmp, int key) {
+    TigrInternal* win;
+    assert(key < 256);
+    win = tigrInternal(bmp);
+    return win->keys[key];
+}
+
+int tigrKeyHeld(Tigr* bmp, int key) {
+    return tigrKeyDown(bmp, key);
+}
+
+int tigrReadChar(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    int c = win->lastChar;
+    win->lastChar = 0;
+    return c;
+}
+
+extern void* _tigrReadFile(const char* fileName, int* length);
+
+void* tigrReadFile(const char* fileName, int* length) {
+    id mainBundle = objc_msgSend_id(class("NSBundle"), sel("mainBundle"));
+    id resourcePath = objc_msgSend_id(mainBundle, sel("resourcePath"));
+    resourcePath = joinNSStrings(resourcePath, makeNSString("/"));
+    resourcePath = joinNSStrings(resourcePath, makeNSString(fileName));
+    return _tigrReadFile(UTF8StringFromNSString(resourcePath), length);
+}
+
+#endif  // __IOS__
+
+#endif  // #ifndef TIGR_HEADLESS
+
+//////// End of inlined file: tigr_ios.c ////////
+
+//////// Start of inlined file: tigr_android.h ////////
+
+#ifndef TIGR_ANDROID_H
+#define TIGR_ANDROID_H
+#ifdef __ANDROID__
+
+#include <android/input.h>
+#include <android/native_window.h>
+#include <EGL/egl.h>
+
+typedef enum {
+    AE_INPUT,
+    AE_WINDOW_CREATED,
+    AE_WINDOW_DESTROYED,
+    AE_RESUME,
+    AE_CLOSE,
+} AndroidEventType;
+
+typedef struct {
+    AndroidEventType type;
+    AInputEvent* inputEvent;
+    ANativeWindow* window;
+    double time;
+} AndroidEvent;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/// Calls from TIGR to Android side, render thread
+extern int android_pollEvent(int (*eventHandler)(AndroidEvent, void*), void*);
+extern void android_swap(EGLDisplay display, EGLSurface surface);
+extern void* android_loadAsset(const char* filename, int* outLength);
+extern void android_showKeyboard(int show);
+
+/// Calls from Android to TIGR side, main thread
+void tigr_android_create();
+void tigr_android_destroy();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // __ANDROID__
+#endif  // TIGR_ANDROID_H
+
+//////// End of inlined file: tigr_android.h ////////
+
+//////// Start of inlined file: tigr_linux.c ////////
+
+#ifndef TIGR_HEADLESS
+
+//#include "tigr_internal.h"
+
+#if __linux__ && !__ANDROID__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdarg.h>
+#include <sys/time.h>
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#include <X11/Xlocale.h>
+#include <X11/XKBlib.h>
+#include <X11/Xatom.h>
+#include <GL/glx.h>
+
+static Display* dpy;
+static Window root;
+static XVisualInfo* vi;
+static Atom wmDeleteMessage;
+static XIM inputMethod;
+static GLXFBConfig fbConfig;
+
+static PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = 0;
+
+static void tearDownX11Stuff() {
+    XCloseDisplay(dpy);
+}
+
+static void initX11Stuff() {
+    static int done = 0;
+    if (!done) {
+        dpy = XOpenDisplay(NULL);
+        if (dpy == NULL) {
+            tigrError(0, "Cannot connect to X server");
+        }
+
+        root = DefaultRootWindow(dpy);
+
+        static int attribList[] = { GLX_RENDER_TYPE,
+                                    GLX_RGBA_BIT,
+                                    GLX_DRAWABLE_TYPE,
+                                    GLX_WINDOW_BIT,
+                                    GLX_DOUBLEBUFFER,
+                                    1,
+                                    GLX_RED_SIZE,
+                                    1,
+                                    GLX_GREEN_SIZE,
+                                    1,
+                                    GLX_BLUE_SIZE,
+                                    1,
+                                    None };
+
+        int fbcCount = 0;
+        GLXFBConfig* fbc = glXChooseFBConfig(dpy, DefaultScreen(dpy), attribList, &fbcCount);
+        if (!fbc) {
+            tigrError(0, "Failed to choose FB config");
+        }
+        fbConfig = fbc[0];
+        XFree(fbc);
+
+        vi = glXGetVisualFromFBConfig(dpy, fbConfig);
+        if (vi == NULL) {
+            tigrError(0, "No appropriate visual found");
+        }
+
+        GLXContext tmpCtx = glXCreateContext(dpy, vi, 0, GL_TRUE);
+        glXCreateContextAttribsARB =
+            (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddressARB((const GLubyte*)"glXCreateContextAttribsARB");
+        glXDestroyContext(dpy, tmpCtx);
+        if (!glXCreateContextAttribsARB) {
+            tigrError(0, "Failed to get glXCreateContextAttribsARB");
+        }
+
+        inputMethod = XOpenIM(dpy, NULL, NULL, NULL);
+        if (inputMethod == NULL) {
+            tigrError(0, "Failed to create input method");
+        }
+
+        wmDeleteMessage = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
+
+        atexit(tearDownX11Stuff);
+
+        done = 1;
+    }
+}
+
+static int hasGLXExtension(Display* display, const char* wanted) {
+    const char* extensions = glXQueryExtensionsString(display, DefaultScreen(display));
+    char* dup = strdup(extensions);
+    char* found = 0;
+
+    for (char* start = dup;; start = 0) {
+        found = strtok(start, " ");
+        if (found == 0 || strcmp(found, wanted) == 0) {
+            break;
+        }
+    }
+
+    free(dup);
+    return found != 0;
+}
+
+static void setupVSync(Display* display, Window win) {
+    if (hasGLXExtension(display, "GLX_EXT_swap_control")) {
+        PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT =
+            (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddressARB((const GLubyte*)"glXSwapIntervalEXT");
+        if (glXSwapIntervalEXT) {
+            glXSwapIntervalEXT(display, win, 1);
+        }
+    } else if (hasGLXExtension(display, "GLX_MESA_swap_control")) {
+        PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA =
+            (PFNGLXSWAPINTERVALMESAPROC)glXGetProcAddressARB((const GLubyte*)"glXSwapIntervalMESA");
+        if (glXSwapIntervalMESA) {
+            glXSwapIntervalMESA(1);
+        }
+    } else if (hasGLXExtension(display, "GLX_SGI_swap_control")) {
+        PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI =
+            (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB((const GLubyte*)"glXSwapIntervalSGI");
+        if (glXSwapIntervalSGI) {
+            glXSwapIntervalSGI(1);
+        }
+    }
+}
+
+static void tigrHideCursor(TigrInternal* win) {
+    Cursor invisibleCursor;
+    Pixmap bitmapNoData;
+    XColor black;
+    static char noData[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+    black.red = black.green = black.blue = 0;
+
+    bitmapNoData = XCreateBitmapFromData(win->dpy, win->win, noData, 8, 8);
+    invisibleCursor = XCreatePixmapCursor(win->dpy, bitmapNoData, bitmapNoData, &black, &black, 0, 0);
+    XDefineCursor(win->dpy, win->win, invisibleCursor);
+    XFreeCursor(win->dpy, invisibleCursor);
+    XFreePixmap(win->dpy, bitmapNoData);
+}
+
+typedef struct {
+    unsigned long flags;
+    unsigned long functions;
+    unsigned long decorations;
+    long inputMode;
+    unsigned long status;
+} WindowHints;
+
+Tigr* tigrWindow(int w, int h, const char* title, int flags) {
+    Tigr* bmp = 0;
+    Colormap cmap;
+    XSetWindowAttributes swa;
+    Window xwin;
+    GLXContext glc;
+    XIC ic;
+    int scale;
+
+    initX11Stuff();
+
+    if (flags & TIGR_AUTO) {
+        // Always use a 1:1 pixel size, unless downscaled by tigrEnforceScale below.
+        scale = 1;
+    } else {
+        // See how big we can make it and still fit on-screen.
+        Screen* screen = DefaultScreenOfDisplay(dpy);
+        int maxW = WidthOfScreen(screen);
+        int maxH = HeightOfScreen(screen);
+        scale = tigrCalcScale(w, h, maxW, maxH);
+    }
+
+    scale = tigrEnforceScale(scale, flags);
+
+    cmap = XCreateColormap(dpy, root, vi->visual, AllocNone);
+    swa.colormap = cmap;
+    swa.event_mask = StructureNotifyMask;
+
+    // Create window of wanted size
+    xwin = XCreateWindow(dpy, root, 0, 0, w * scale, h * scale, 0, vi->depth, InputOutput, vi->visual,
+                         CWColormap | CWEventMask, &swa);
+    XMapWindow(dpy, xwin);
+
+    if (flags & TIGR_FULLSCREEN) {
+        // https://superuser.com/questions/1680077/does-x11-actually-have-a-native-fullscreen-mode
+        Atom wm_state = XInternAtom(dpy, "_NET_WM_STATE", true);
+        Atom wm_fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", true);
+        XChangeProperty(dpy, xwin, wm_state, XA_ATOM, 32, PropModeReplace, (unsigned char*)&wm_fullscreen, 1);
+    } else {
+        // Wait for window to get mapped
+        for (;;) {
+            XEvent e;
+            XNextEvent(dpy, &e);
+            if (e.type == MapNotify) {
+                break;
+            }
+        }
+
+        // Reset size if we did not get the window size we wanted above.
+        XWindowAttributes wa;
+        XGetWindowAttributes(dpy, xwin, &wa);
+        scale = tigrCalcScale(w, h, wa.width, wa.height);
+        scale = tigrEnforceScale(scale, flags);
+        XResizeWindow(dpy, xwin, w * scale, h * scale);
+    }
+
+    // Enable mouse events reporting (needed for mouse wheel events)
+    XSelectInput(dpy, xwin, ButtonPressMask);
+
+    XTextProperty prop;
+    int result = Xutf8TextListToTextProperty(dpy, (char**)&title, 1, XUTF8StringStyle, &prop);
+    if (result == Success) {
+        Atom wmName = XInternAtom(dpy, "_NET_WM_NAME", 0);
+        XSetTextProperty(dpy, xwin, &prop, wmName);
+        XFree(prop.value);
+    }
+
+    ic = XCreateIC(inputMethod, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, xwin, NULL);
+    if (ic == NULL) {
+        printf("Failed to create input context\n");
+        exit(0);
+    }
+    XSetICFocus(ic);
+
+    XSetWMProtocols(dpy, xwin, &wmDeleteMessage, 1);
+
+    int contextAttributes[] = { GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MINOR_VERSION_ARB, 3, None };
+    glc = glXCreateContextAttribsARB(dpy, fbConfig, NULL, GL_TRUE, contextAttributes);
+    glXMakeCurrent(dpy, xwin, glc);
+
+    setupVSync(dpy, xwin);
+
+    bmp = tigrBitmap2(w, h, sizeof(TigrInternal));
+    bmp->handle = (void*)xwin;
+
+    TigrInternal* win = tigrInternal(bmp);
+    win->win = xwin;
+    win->dpy = dpy;
+    win->glc = glc;
+    win->ic = ic;
+
+    win->shown = 0;
+    win->closed = 0;
+    win->scale = scale;
+
+    win->lastChar = 0;
+    win->flags = flags;
+    win->p1 = win->p2 = win->p3 = 0;
+    win->p4 = 1;
+    win->widgetsWanted = 0;
+    win->widgetAlpha = 0;
+    win->widgetsScale = 0;
+    win->widgets = 0;
+    win->gl.gl_legacy = 0;
+
+    memset(win->keys, 0, 256);
+    memset(win->prev, 0, 256);
+
+    if (flags & TIGR_NOCURSOR) {
+        tigrHideCursor(win);
+    }
+
+    tigrPosition(bmp, win->scale, bmp->w, bmp->h, win->pos);
+    tigrGAPICreate(bmp);
+    tigrGAPIBegin(bmp);
+
+    return bmp;
+}
+
+int tigrClosed(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    return win->win == 0;
+}
+
+int tigrGAPIBegin(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    return glXMakeCurrent(win->dpy, win->win, win->glc) ? 0 : -1;
+}
+
+int tigrGAPIEnd(Tigr* bmp) {
+    (void)bmp;
+    return glXMakeCurrent(NULL, 0, 0) ? 0 : -1;
+}
+
+int tigrKeyDown(Tigr* bmp, int key) {
+    TigrInternal* win;
+    assert(key < 256);
+    win = tigrInternal(bmp);
+    return win->keys[key] && !win->prev[key];
+}
+
+int tigrKeyHeld(Tigr* bmp, int key) {
+    TigrInternal* win;
+    assert(key < 256);
+    win = tigrInternal(bmp);
+    return win->keys[key];
+}
+
+int tigrReadChar(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    int c = win->lastChar;
+    win->lastChar = 0;
+    return c;
+}
+
+uint8_t tigrKeyFromX11(KeySym sym) {
+    if (sym >= 'a' && sym <= 'z') {
+        return (uint8_t)sym - ('a' - 'A');
+    }
+
+    if (sym >= '0' && sym <= '9') {
+        return (uint8_t)sym;
+    }
+
+    switch (sym) {
+        case XK_KP_0:
+            return TK_PAD0;
+        case XK_KP_1:
+            return TK_PAD1;
+        case XK_KP_2:
+            return TK_PAD2;
+        case XK_KP_3:
+            return TK_PAD3;
+        case XK_KP_4:
+            return TK_PAD4;
+        case XK_KP_5:
+            return TK_PAD5;
+        case XK_KP_6:
+            return TK_PAD6;
+        case XK_KP_7:
+            return TK_PAD7;
+        case XK_KP_8:
+            return TK_PAD8;
+        case XK_KP_9:
+            return TK_PAD9;
+
+        case XK_KP_Multiply:
+            return TK_PADMUL;
+        case XK_KP_Divide:
+            return TK_PADDIV;
+        case XK_KP_Add:
+            return TK_PADADD;
+        case XK_KP_Subtract:
+            return TK_PADSUB;
+        case XK_KP_Decimal:
+            return TK_PADDOT;
+        case XK_KP_Enter:
+            return TK_PADENTER;
+
+        case XK_F1:
+            return TK_F1;
+        case XK_F2:
+            return TK_F2;
+        case XK_F3:
+            return TK_F3;
+        case XK_F4:
+            return TK_F4;
+        case XK_F5:
+            return TK_F5;
+        case XK_F6:
+            return TK_F6;
+        case XK_F7:
+            return TK_F7;
+        case XK_F8:
+            return TK_F8;
+        case XK_F9:
+            return TK_F9;
+        case XK_F10:
+            return TK_F10;
+        case XK_F11:
+            return TK_F11;
+        case XK_F12:
+            return TK_F12;
+
+        case XK_BackSpace:
+            return TK_BACKSPACE;
+        case XK_Tab:
+            return TK_TAB;
+        case XK_Return:
+            return TK_RETURN;
+        case XK_Pause:
+            return TK_PAUSE;
+        case XK_Caps_Lock:
+            return TK_CAPSLOCK;
+        case XK_Escape:
+            return TK_ESCAPE;
+        case XK_space:
+            return TK_SPACE;
+
+        case XK_Page_Up:
+            return TK_PAGEUP;
+        case XK_Page_Down:
+            return TK_PAGEDN;
+        case XK_End:
+            return TK_END;
+        case XK_Home:
+            return TK_HOME;
+        case XK_Left:
+            return TK_LEFT;
+        case XK_Up:
+            return TK_UP;
+        case XK_Right:
+            return TK_RIGHT;
+        case XK_Down:
+            return TK_DOWN;
+        case XK_Insert:
+            return TK_INSERT;
+        case XK_Delete:
+            return TK_DELETE;
+
+        case XK_Meta_L:
+            return TK_LWIN;
+        case XK_Meta_R:
+            return TK_RWIN;
+        case XK_Num_Lock:
+            return TK_NUMLOCK;
+        case XK_Scroll_Lock:
+            return TK_SCROLL;
+        case XK_Shift_L:
+            return TK_LSHIFT;
+        case XK_Shift_R:
+            return TK_RSHIFT;
+        case XK_Control_L:
+            return TK_LCONTROL;
+        case XK_Control_R:
+            return TK_RCONTROL;
+        case XK_Alt_L:
+            return TK_LALT;
+        case XK_Alt_R:
+            return TK_RALT;
+
+        case XK_semicolon:
+            return TK_SEMICOLON;
+        case XK_equal:
+            return TK_EQUALS;
+        case XK_comma:
+            return TK_COMMA;
+        case XK_minus:
+            return TK_MINUS;
+        case XK_period:
+            return TK_DOT;
+        case XK_slash:
+            return TK_SLASH;
+        case XK_grave:
+            return TK_BACKTICK;
+        case XK_bracketleft:
+            return TK_LSQUARE;
+        case XK_backslash:
+            return TK_BACKSLASH;
+        case XK_bracketright:
+            return TK_RSQUARE;
+        case XK_apostrophe:
+            return TK_TICK;
+    }
+    return 0;
+}
+
+static void tigrUpdateModifiers(TigrInternal* win) {
+    win->keys[TK_SHIFT] = win->keys[TK_LSHIFT] || win->keys[TK_RSHIFT];
+    win->keys[TK_CONTROL] = win->keys[TK_LCONTROL] || win->keys[TK_RCONTROL];
+    win->keys[TK_ALT] = win->keys[TK_LALT] || win->keys[TK_RALT];
+}
+
+static void tigrInterpretChar(TigrInternal* win, Window root, unsigned int keycode, unsigned int mask) {
+    XKeyEvent event;
+    memset(&event, 0, sizeof(event));
+    event.type = KeyPress;
+    event.display = win->dpy;
+    event.root = root;
+    event.window = win->win;
+    event.state = mask;
+    event.keycode = keycode;
+    char inputTextUTF8[10];
+    Status status = 0;
+    Xutf8LookupString(win->ic, &event, inputTextUTF8, sizeof(inputTextUTF8), NULL, &status);
+
+    if (status == XLookupChars) {
+        tigrDecodeUTF8(inputTextUTF8, &win->lastChar);
+    }
+}
+
+static void tigrProcessInput(TigrInternal* win, int winWidth, int winHeight) {
+    {
+        Window focused;
+        int revertTo;
+        XGetInputFocus(win->dpy, &focused, &revertTo);
+
+        if (win->win != focused) {
+            return;
+        }
+    }
+
+    Window root;
+    Window child;
+    int rootX;
+    int rootY;
+    int winX;
+    int winY;
+    unsigned int mask;
+
+    if (XQueryPointer(win->dpy, win->win, &root, &child, &rootX, &rootY, &winX, &winY, &mask)) {
+        static unsigned int prevButtons;
+        unsigned int buttons = mask & (Button1Mask | Button2Mask | Button3Mask);
+
+        win->mouseX = (winX - win->pos[0]) / win->scale;
+        win->mouseY = (winY - win->pos[1]) / win->scale;
+
+        if (buttons != prevButtons && (winX > 0 && winX < winWidth) && (winY > 0 && winY < winHeight)) {
+            win->mouseButtons = (buttons & Button1Mask)       ? 1
+                                : 0 | (buttons & Button3Mask) ? 2
+                                : 0 | (buttons & Button2Mask) ? 4
+                                                              : 0;
+        }
+        prevButtons = buttons;
+    }
+
+    // Retrieve mouse wheel events - Cannot be done in the XQueryPointer call
+    // -> https://comp.unix.programmer.narkive.com/eOnjkQ6L/xlib-xquerypointer-and-button4mask-button5mask#post2
+    // Button4 = WheelUp / Button5 = WheelDown
+    XEvent mouseButtonEvent;
+    while (XPending(win->dpy)) {
+        XPeekEvent(win->dpy, &mouseButtonEvent);
+        if (mouseButtonEvent.xany.type == ButtonPress) {
+            switch (mouseButtonEvent.xbutton.button) {
+                case Button4:
+                    win->scrollDeltaY += 1;
+                    break;
+                case Button5:
+                    win->scrollDeltaY -= 1;
+                    break;
+                case 6:
+                    win->scrollDeltaX += 1;
+                    break;
+                case 7:
+                    win->scrollDeltaX -= 1;
+                    break;
+            }
+            XNextEvent(win->dpy, &mouseButtonEvent);
+        } else {
+            break;
+        }
+    }
+
+    static char prevKeys[32];
+    char keys[32];
+    XQueryKeymap(win->dpy, keys);
+    for (int i = 0; i < 32; i++) {
+        char thisBlock = keys[i];
+        char prevBlock = prevKeys[i];
+        if (thisBlock != prevBlock) {
+            for (int j = 0; j < 8; j++) {
+                int thisBit = thisBlock & 1;
+                int prevBit = prevBlock & 1;
+                thisBlock >>= 1;
+                prevBlock >>= 1;
+                if (thisBit != prevBit) {
+                    int keyCode = 8 * i + j;
+                    KeySym keySym = XkbKeycodeToKeysym(win->dpy, keyCode, 0, 0);
+                    if (keySym != NoSymbol) {
+                        int key = tigrKeyFromX11(keySym);
+                        win->keys[key] = thisBit;
+                        tigrUpdateModifiers(win);
+
+                        if (thisBit) {
+                            tigrInterpretChar(win, root, keyCode, mask);
+                        }
+                    }
+                }
+            }
+        }
+    }
+    memcpy(prevKeys, keys, 32);
+
+    XEvent event;
+    while (XCheckTypedWindowEvent(win->dpy, win->win, ClientMessage, &event)) {
+        if (event.xclient.data.l[0] == wmDeleteMessage) {
+            glXMakeCurrent(win->dpy, None, NULL);
+            glXDestroyContext(win->dpy, win->glc);
+            XDestroyWindow(win->dpy, win->win);
+            win->win = 0;
+        }
+    }
+    XFlush(win->dpy);
+}
+
+void tigrUpdate(Tigr* bmp) {
+    XWindowAttributes gwa;
+
+    TigrInternal* win = tigrInternal(bmp);
+
+    memcpy(win->prev, win->keys, 256);
+    win->scrollDeltaX = 0;
+    win->scrollDeltaY = 0;
+
+    XGetWindowAttributes(win->dpy, win->win, &gwa);
+
+    if (win->flags & TIGR_AUTO)
+        tigrResize(bmp, gwa.width / win->scale, gwa.height / win->scale);
+    else
+        win->scale = tigrEnforceScale(tigrCalcScale(bmp->w, bmp->h, gwa.width, gwa.height), win->flags);
+
+    tigrPosition(bmp, win->scale, gwa.width, gwa.height, win->pos);
+    glXMakeCurrent(win->dpy, win->win, win->glc);
+    tigrGAPIPresent(bmp, gwa.width, gwa.height);
+    glXSwapBuffers(win->dpy, win->win);
+
+    tigrProcessInput(win, gwa.width, gwa.height);
+}
+
+void tigrFree(Tigr* bmp) {
+    if (bmp->handle) {
+        TigrInternal* win = tigrInternal(bmp);
+        if (win->win) {
+            glXMakeCurrent(win->dpy, None, NULL);
+            glXDestroyContext(win->dpy, win->glc);
+            XDestroyWindow(win->dpy, win->win);
+            win->win = 0;
+        }
+    }
+    free(bmp->pix);
+    free(bmp);
+}
+
+void tigrError(Tigr* bmp, const char* message, ...) {
+    char tmp[1024];
+
+    va_list args;
+    va_start(args, message);
+    vsnprintf(tmp, sizeof(tmp), message, args);
+    tmp[sizeof(tmp) - 1] = 0;
+    va_end(args);
+
+    printf("tigr fatal error: %s\n", tmp);
+
+    exit(1);
+}
+
+float tigrTime() {
+    static double lastTime = 0;
+
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+
+    double now = (double)tv.tv_sec + (tv.tv_usec / 1000000.0);
+    double elapsed = lastTime == 0 ? 0 : now - lastTime;
+    lastTime = now;
+
+    return (float)elapsed;
+}
+
+void tigrMouse(Tigr* bmp, int* x, int* y, int* buttons) {
+    TigrInternal* win = tigrInternal(bmp);
+    if (x) {
+        *x = win->mouseX;
+    }
+    if (y) {
+        *y = win->mouseY;
+    }
+    if (buttons) {
+        *buttons = win->mouseButtons;
+    }
+}
+
+int tigrTouch(Tigr* bmp, TigrTouchPoint* points, int maxPoints) {
+    int buttons = 0;
+    if (maxPoints > 0) {
+        tigrMouse(bmp, &points->x, &points->y, &buttons);
+    }
+    return buttons ? 1 : 0;
+}
+
+void tigrScrollWheel(Tigr* bmp, float* x, float* y) {
+    TigrInternal* win;
+    win = tigrInternal(bmp);
+    if (x) {
+        *x = win->scrollDeltaX;
+    }
+    if (y) {
+        *y = win->scrollDeltaY;
+    }
+}
+
+#endif  // __linux__ && !__ANDROID__
+
+#endif  // #ifndef TIGR_HEADLESS
+
+//////// End of inlined file: tigr_linux.c ////////
+
+//////// Start of inlined file: tigr_android.c ////////
+
+#ifndef TIGR_HEADLESS
+
+//#include "tigr_internal.h"
+
+#ifdef __ANDROID__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <android/log.h>
+#include <android/native_window.h>
+#include <android/input.h>
+
+#ifndef NDEBUG
+#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "tigr", __VA_ARGS__))
+#else
+#define LOGD(...) ((void)0)
+#endif
+#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "tigr", __VA_ARGS__))
+#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "tigr", __VA_ARGS__))
+
+typedef struct {
+    TigrTouchPoint points[MAX_TOUCH_POINTS];
+    int numPoints;
+} InputState;
+
+/// Global state
+static struct {
+    ANativeWindow* window;
+    InputState inputState;
+    EGLDisplay display;
+    EGLSurface surface;
+    EGLint screenW;
+    EGLint screenH;
+    EGLConfig config;
+    double lastTime;
+    int closed;
+} gState = {
+    .window = 0,
+    .inputState = {
+        .numPoints = 0,
+    },
+    .display = EGL_NO_DISPLAY,
+    .surface = EGL_NO_SURFACE,
+    .screenW = 0,
+    .screenH = 0,
+    .config = 0,
+    .lastTime = 0,
+    .closed = 0,
+};
+
+static const EGLint contextAttribs[] = { EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 0, EGL_NONE };
+
+static EGLConfig getGLConfig(EGLDisplay display) {
+    EGLConfig config = 0;
+
+    const EGLint attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8,
+                               EGL_NONE };
+    EGLint numConfigs;
+
+    eglChooseConfig(display, attribs, NULL, 0, &numConfigs);
+    EGLConfig* supportedConfigs = (EGLConfig*)malloc(sizeof(EGLConfig) * numConfigs);
+    eglChooseConfig(display, attribs, supportedConfigs, numConfigs, &numConfigs);
+
+    int i = 0;
+    for (; i < numConfigs; i++) {
+        EGLConfig* cfg = supportedConfigs[i];
+        EGLint r, g, b, d;
+        if (eglGetConfigAttrib(display, cfg, EGL_RED_SIZE, &r) &&
+            eglGetConfigAttrib(display, cfg, EGL_GREEN_SIZE, &g) &&
+            eglGetConfigAttrib(display, cfg, EGL_BLUE_SIZE, &b) &&
+            eglGetConfigAttrib(display, cfg, EGL_DEPTH_SIZE, &d) && r == 8 && g == 8 && b == 8 && d == 0) {
+            config = supportedConfigs[i];
+            break;
+        }
+    }
+
+    if (i == numConfigs) {
+        config = supportedConfigs[0];
+    }
+
+    if (config == NULL) {
+        tigrError(NULL, "Unable to initialize EGLConfig");
+    }
+
+    free(supportedConfigs);
+
+    return config;
+}
+
+/// Android interface, called from main thread
+
+void tigr_android_create() {
+    gState.closed = 0;
+    gState.window = 0;
+    gState.inputState.numPoints = 0;
+    gState.surface = EGL_NO_SURFACE;
+    gState.lastTime = 0;
+
+    if (gState.display == EGL_NO_DISPLAY) {
+        gState.display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+        EGLBoolean status = eglInitialize(gState.display, NULL, NULL);
+        if (!status) {
+            tigrError(NULL, "Failed to init EGL");
+        }
+        gState.config = getGLConfig(gState.display);
+    }
+}
+
+void tigr_android_destroy() {
+    eglTerminate(gState.display);
+    gState.display = EGL_NO_DISPLAY;
+}
+
+/// Internals ///
+
+static void logEglError() {
+    int error = eglGetError();
+    switch (error) {
+        case EGL_BAD_DISPLAY:
+            LOGE("EGL error: Bad display");
+            break;
+        case EGL_NOT_INITIALIZED:
+            LOGE("EGL error: Not initialized");
+            break;
+        case EGL_BAD_NATIVE_WINDOW:
+            LOGE("EGL error: Bad native window");
+            break;
+        case EGL_BAD_ALLOC:
+            LOGE("EGL error: Bad alloc");
+            break;
+        case EGL_BAD_MATCH:
+            LOGE("EGL error: Bad match");
+            break;
+        default:
+            LOGE("EGL error: %d", error);
+    }
+}
+
+static void setupOpenGL() {
+    LOGD("setupOpenGL");
+    assert(gState.surface == EGL_NO_SURFACE);
+    assert(gState.window != 0);
+
+    gState.surface = eglCreateWindowSurface(gState.display, gState.config, gState.window, NULL);
+    if (gState.surface == EGL_NO_SURFACE) {
+        logEglError();
+    }
+    assert(gState.surface != EGL_NO_SURFACE);
+    eglQuerySurface(gState.display, gState.surface, EGL_WIDTH, &gState.screenW);
+    eglQuerySurface(gState.display, gState.surface, EGL_HEIGHT, &gState.screenH);
+    LOGD("Screen is %d x %d", gState.screenW, gState.screenH);
+}
+
+static void tearDownOpenGL() {
+    LOGD("tearDownOpenGL");
+    if (gState.display != EGL_NO_DISPLAY) {
+        eglMakeCurrent(gState.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+
+        if (gState.surface != EGL_NO_SURFACE) {
+            LOGD("eglDestroySurface");
+            if (!eglDestroySurface(gState.display, gState.surface)) {
+                logEglError();
+            }
+            gState.surface = EGL_NO_SURFACE;
+        }
+    }
+}
+
+static int tigrKeyFromAndroidKey(int key) {
+    switch (key) {
+        case AKEYCODE_Q:
+            return 'Q';
+        case AKEYCODE_W:
+            return 'W';
+        case AKEYCODE_E:
+            return 'E';
+        case AKEYCODE_R:
+            return 'R';
+        case AKEYCODE_T:
+            return 'T';
+        case AKEYCODE_Y:
+            return 'Y';
+        case AKEYCODE_U:
+            return 'U';
+        case AKEYCODE_I:
+            return 'I';
+        case AKEYCODE_O:
+            return 'O';
+        case AKEYCODE_P:
+            return 'P';
+
+        case AKEYCODE_A:
+            return 'A';
+        case AKEYCODE_S:
+            return 'S';
+        case AKEYCODE_D:
+            return 'D';
+        case AKEYCODE_F:
+            return 'F';
+        case AKEYCODE_G:
+            return 'G';
+        case AKEYCODE_H:
+            return 'H';
+        case AKEYCODE_J:
+            return 'J';
+        case AKEYCODE_K:
+            return 'K';
+        case AKEYCODE_L:
+            return 'L';
+
+        case AKEYCODE_Z:
+            return 'Z';
+        case AKEYCODE_X:
+            return 'X';
+        case AKEYCODE_C:
+            return 'C';
+        case AKEYCODE_V:
+            return 'V';
+        case AKEYCODE_B:
+            return 'B';
+        case AKEYCODE_N:
+            return 'N';
+        case AKEYCODE_M:
+            return 'M';
+
+        case AKEYCODE_0:
+            return '0';
+        case AKEYCODE_1:
+            return '1';
+        case AKEYCODE_2:
+            return '2';
+        case AKEYCODE_3:
+            return '3';
+        case AKEYCODE_4:
+            return '4';
+        case AKEYCODE_5:
+            return '5';
+        case AKEYCODE_6:
+            return '6';
+        case AKEYCODE_7:
+            return '7';
+        case AKEYCODE_8:
+            return '8';
+        case AKEYCODE_9:
+            return '9';
+
+        case AKEYCODE_NUMPAD_0:
+            return TK_PAD0;
+        case AKEYCODE_NUMPAD_1:
+            return TK_PAD1;
+        case AKEYCODE_NUMPAD_2:
+            return TK_PAD2;
+        case AKEYCODE_NUMPAD_3:
+            return TK_PAD3;
+        case AKEYCODE_NUMPAD_4:
+            return TK_PAD4;
+        case AKEYCODE_NUMPAD_5:
+            return TK_PAD5;
+        case AKEYCODE_NUMPAD_6:
+            return TK_PAD6;
+        case AKEYCODE_NUMPAD_7:
+            return TK_PAD7;
+        case AKEYCODE_NUMPAD_8:
+            return TK_PAD8;
+        case AKEYCODE_NUMPAD_9:
+            return TK_PAD9;
+
+        case AKEYCODE_NUMPAD_MULTIPLY:
+            return TK_PADMUL;
+        case AKEYCODE_NUMPAD_DIVIDE:
+            return TK_PADDIV;
+        case AKEYCODE_NUMPAD_ADD:
+            return TK_PADADD;
+        case AKEYCODE_NUMPAD_SUBTRACT:
+            return TK_PADSUB;
+        case AKEYCODE_NUMPAD_ENTER:
+            return TK_PADENTER;
+        case AKEYCODE_NUMPAD_DOT:
+            return TK_PADDOT;
+
+        case AKEYCODE_F1:
+            return TK_F1;
+        case AKEYCODE_F2:
+            return TK_F2;
+        case AKEYCODE_F3:
+            return TK_F3;
+        case AKEYCODE_F4:
+            return TK_F4;
+        case AKEYCODE_F5:
+            return TK_F5;
+        case AKEYCODE_F6:
+            return TK_F6;
+        case AKEYCODE_F7:
+            return TK_F7;
+        case AKEYCODE_F8:
+            return TK_F8;
+        case AKEYCODE_F9:
+            return TK_F9;
+        case AKEYCODE_F10:
+            return TK_F10;
+        case AKEYCODE_F11:
+            return TK_F11;
+        case AKEYCODE_F12:
+            return TK_F12;
+
+        case AKEYCODE_SHIFT_LEFT:
+            return TK_LSHIFT;
+        case AKEYCODE_SHIFT_RIGHT:
+            return TK_RSHIFT;
+        case AKEYCODE_CTRL_LEFT:
+            return TK_LCONTROL;
+        case AKEYCODE_CTRL_RIGHT:
+            return TK_RCONTROL;
+        case AKEYCODE_ALT_LEFT:
+            return TK_LALT;
+        case AKEYCODE_ALT_RIGHT:
+            return TK_RALT;
+        case AKEYCODE_META_LEFT:
+            return TK_LWIN;
+        case AKEYCODE_META_RIGHT:
+            return TK_RWIN;
+
+        case AKEYCODE_DEL:
+            return TK_BACKSPACE;
+        case AKEYCODE_TAB:
+            return TK_TAB;
+        case AKEYCODE_ENTER:
+            return TK_RETURN;
+        case AKEYCODE_CAPS_LOCK:
+            return TK_CAPSLOCK;
+        case AKEYCODE_ESCAPE:
+        case AKEYCODE_BACK:
+            return TK_ESCAPE;
+        case AKEYCODE_SPACE:
+            return TK_SPACE;
+
+        case AKEYCODE_PAGE_UP:
+            return TK_PAGEUP;
+        case AKEYCODE_PAGE_DOWN:
+            return TK_PAGEDN;
+        case AKEYCODE_MOVE_END:
+            return TK_END;
+        case AKEYCODE_MOVE_HOME:
+            return TK_HOME;
+        case AKEYCODE_DPAD_LEFT:
+            return TK_LEFT;
+        case AKEYCODE_DPAD_RIGHT:
+            return TK_RIGHT;
+        case AKEYCODE_DPAD_UP:
+            return TK_UP;
+        case AKEYCODE_DPAD_DOWN:
+            return TK_DOWN;
+
+        case AKEYCODE_INSERT:
+            return TK_INSERT;
+        case AKEYCODE_FORWARD_DEL:
+            return TK_DELETE;
+        case AKEYCODE_NUM_LOCK:
+            return TK_NUMLOCK;
+        case AKEYCODE_SCROLL_LOCK:
+            return TK_SCROLL;
+
+        case AKEYCODE_SEMICOLON:
+            return TK_SEMICOLON;
+        case AKEYCODE_EQUALS:
+            return TK_EQUALS;
+        case AKEYCODE_COMMA:
+            return TK_COMMA;
+        case AKEYCODE_MINUS:
+            return TK_MINUS;
+        case AKEYCODE_PERIOD:
+            return TK_DOT;
+        case AKEYCODE_SLASH:
+            return TK_SLASH;
+        case AKEYCODE_BACKSLASH:
+            return TK_BACKSLASH;
+        case AKEYCODE_GRAVE:
+            return TK_BACKTICK;
+        case AKEYCODE_APOSTROPHE:
+            return TK_TICK;
+        case AKEYCODE_LEFT_BRACKET:
+            return TK_LSQUARE;
+        case AKEYCODE_RIGHT_BRACKET:
+            return TK_RSQUARE;
+
+        default:
+            return 0;
+    }
+}
+
+static int processInputEvent(AInputEvent* event, TigrInternal* win) {
+    if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
+        int32_t action = AMotionEvent_getAction(event);
+        int32_t actionCode = action & AMOTION_EVENT_ACTION_MASK;
+
+        size_t touchPoints = AMotionEvent_getPointerCount(event);
+        size_t releasedIndex = -1;
+        if (actionCode == AMOTION_EVENT_ACTION_POINTER_UP) {
+            releasedIndex =
+                (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+        }
+        size_t targetPointCount = 0;
+
+        for (size_t i = 0; i < touchPoints && i < MAX_TOUCH_POINTS; i++) {
+            if (i == releasedIndex) {
+                continue;
+            }
+            gState.inputState.points[targetPointCount].x = AMotionEvent_getX(event, i);
+            gState.inputState.points[targetPointCount].y = AMotionEvent_getY(event, i);
+            targetPointCount++;
+        }
+
+        if (actionCode == AMOTION_EVENT_ACTION_UP || actionCode == AMOTION_EVENT_ACTION_CANCEL) {
+            gState.inputState.numPoints = 0;
+        } else {
+            gState.inputState.numPoints = targetPointCount;
+        }
+        return 1;
+    } else if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) {
+        if (!win) {
+            return 1;
+        }
+        int32_t deviceID = AInputEvent_getDeviceId(event);
+        // KeyCharacterMap#VIRTUAL_KEYBOARD == -1
+        if (deviceID != -1) {
+            return 1;
+        }
+        int32_t action = AKeyEvent_getAction(event);
+        int32_t keyCode = AKeyEvent_getKeyCode(event);
+        int key = tigrKeyFromAndroidKey(keyCode);
+        // We pass the character in the scancode field from the Java side
+        int32_t unicodeChar = AKeyEvent_getScanCode(event);
+
+        if (action == AKEY_EVENT_ACTION_DOWN) {
+            win->keys[key] = 1;
+            win->lastChar = unicodeChar;
+        } else if (action == AKEY_EVENT_ACTION_UP) {
+            win->released[key] = 1;
+        }
+        return 1;
+    }
+    return 0;
+}
+
+static int handleEvent(AndroidEvent event, void* userData) {
+    switch (event.type) {
+        case AE_WINDOW_CREATED:
+            gState.window = event.window;
+            setupOpenGL();
+            break;
+
+        case AE_WINDOW_DESTROYED:
+            tearDownOpenGL();
+            gState.window = 0;
+            break;
+
+        case AE_INPUT:
+            return processInputEvent(event.inputEvent, (TigrInternal*)userData);
+
+        case AE_RESUME:
+            gState.lastTime = event.time;
+            break;
+
+        case AE_CLOSE:
+            gState.closed = 1;
+            break;
+
+        default:
+            LOGE("Unhandled event type: %d", event.type);
+            return 0;
+    }
+
+    return 1;
+}
+
+static int processEvents(TigrInternal* win) {
+    if (gState.closed) {
+        return 0;
+    }
+
+    while (android_pollEvent(handleEvent, win)) {
+        if (gState.closed) {
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
+static Tigr* refreshWindow(Tigr* bmp) {
+    if (bmp->handle == gState.window) {
+        return bmp;
+    }
+
+    bmp->handle = gState.window;
+    if (gState.window == 0) {
+        return 0;
+    }
+
+    TigrInternal* win = tigrInternal(bmp);
+
+    int scale = 1;
+    if (win->flags & TIGR_AUTO) {
+        // Always use a 1:1 pixel size.
+        scale = 1;
+    } else {
+        // See how big we can make it and still fit on-screen.
+        scale = tigrCalcScale(bmp->w, bmp->h, gState.screenW, gState.screenH);
+    }
+
+    win->scale = tigrEnforceScale(scale, win->flags);
+
+    return bmp;
+}
+
+/// TIGR interface implementation, called from render thread ///
+
+Tigr* tigrWindow(int w, int h, const char* title, int flags) {
+    while (gState.window == NULL) {
+        if (!processEvents(0)) {
+            return NULL;
+        }
+    }
+
+    EGLContext context = eglCreateContext(gState.display, gState.config, NULL, contextAttribs);
+
+    int scale = 1;
+    if (flags & TIGR_AUTO) {
+        // Always use a 1:1 pixel size.
+        scale = 1;
+    } else {
+        // See how big we can make it and still fit on-screen.
+        scale = tigrCalcScale(w, h, gState.screenW, gState.screenH);
+    }
+
+    scale = tigrEnforceScale(scale, flags);
+
+    Tigr* bmp = tigrBitmap2(w, h, sizeof(TigrInternal));
+    bmp->handle = (void*)gState.window;
+
+    TigrInternal* win = tigrInternal(bmp);
+    win->context = context;
+
+    win->shown = 0;
+    win->closed = 0;
+    win->scale = scale;
+
+    win->lastChar = 0;
+    win->flags = flags;
+    win->p1 = win->p2 = win->p3 = 0;
+    win->p4 = 1;
+    win->widgetsWanted = 0;
+    win->widgetAlpha = 0;
+    win->widgetsScale = 0;
+    win->widgets = 0;
+    win->gl.gl_legacy = 0;
+
+    memset(win->keys, 0, 256);
+    memset(win->prev, 0, 256);
+    memset(win->released, 0, 256);
+
+    tigrPosition(bmp, win->scale, bmp->w, bmp->h, win->pos);
+
+    if (eglMakeCurrent(gState.display, gState.surface, gState.surface, context) == EGL_FALSE) {
+        LOGE("Unable to eglMakeCurrent");
+        return 0;
+    }
+
+    tigrGAPICreate(bmp);
+
+    return bmp;
+}
+
+int tigrClosed(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    return win->closed;
+}
+
+int tigrGAPIBegin(Tigr* bmp) {
+    assert(gState.display != EGL_NO_DISPLAY);
+    assert(gState.surface != EGL_NO_SURFACE);
+
+    TigrInternal* win = tigrInternal(bmp);
+    if (eglMakeCurrent(gState.display, gState.surface, gState.surface, win->context) == EGL_FALSE) {
+        return -1;
+    }
+    return 0;
+}
+
+int tigrGAPIEnd(Tigr* bmp) {
+    (void)bmp;
+    eglMakeCurrent(gState.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+    return 0;
+}
+
+int tigrKeyDown(Tigr* bmp, int key) {
+    TigrInternal* win;
+    assert(key < 256);
+    win = tigrInternal(bmp);
+    return win->keys[key] && !win->prev[key];
+}
+
+int tigrKeyHeld(Tigr* bmp, int key) {
+    TigrInternal* win;
+    assert(key < 256);
+    win = tigrInternal(bmp);
+    return win->keys[key];
+}
+
+int tigrReadChar(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    int c = win->lastChar;
+    win->lastChar = 0;
+    return c;
+}
+
+static void tigrUpdateModifiers(TigrInternal* win) {
+    win->keys[TK_SHIFT] = win->keys[TK_LSHIFT] || win->keys[TK_RSHIFT];
+    win->keys[TK_CONTROL] = win->keys[TK_LCONTROL] || win->keys[TK_RCONTROL];
+    win->keys[TK_ALT] = win->keys[TK_LALT] || win->keys[TK_RALT];
+}
+
+static int toWindowX(TigrInternal* win, int x) {
+    return (x - win->pos[0]) / win->scale;
+}
+
+static int toWindowY(TigrInternal* win, int y) {
+    return (y - win->pos[1]) / win->scale;
+}
+
+void tigrUpdate(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    memcpy(win->prev, win->keys, 256);
+    for (int i = 0; i < 256; i++) {
+        win->keys[i] ^= win->released[i];
+        win->released[i] = 0;
+    }
+
+    if (!processEvents(win)) {
+        win->closed = 1;
+        return;
+    }
+
+    tigrUpdateModifiers(win);
+
+    if (gState.window == 0) {
+        return;
+    }
+
+    bmp = refreshWindow(bmp);
+    if (bmp == 0) {
+        return;
+    }
+
+    win->numTouchPoints = gState.inputState.numPoints;
+    for (int i = 0; i < win->numTouchPoints; i++) {
+        win->touchPoints[i].x = toWindowX(win, gState.inputState.points[i].x);
+        win->touchPoints[i].y = toWindowY(win, gState.inputState.points[i].y);
+    }
+
+    win->mouseButtons = win->numTouchPoints;
+    if (win->mouseButtons > 0) {
+        win->mouseX = win->touchPoints[0].x;
+        win->mouseY = win->touchPoints[0].y;
+    }
+
+    if (win->flags & TIGR_AUTO) {
+        tigrResize(bmp, gState.screenW / win->scale, gState.screenH / win->scale);
+    } else {
+        win->scale = tigrEnforceScale(tigrCalcScale(bmp->w, bmp->h, gState.screenW, gState.screenH), win->flags);
+    }
+
+    tigrPosition(bmp, win->scale, gState.screenW, gState.screenH, win->pos);
+    tigrGAPIBegin(bmp);
+    tigrGAPIPresent(bmp, gState.screenW, gState.screenH);
+    android_swap(gState.display, gState.surface);
+    tigrGAPIEnd(bmp);
+}
+
+void tigrFree(Tigr* bmp) {
+    if (bmp->handle) {
+        TigrInternal* win = tigrInternal(bmp);
+
+        eglMakeCurrent(gState.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+        if (win->context != EGL_NO_CONTEXT) {
+            // Win closed means app windows has closed, and the call would fail.
+            if (!win->closed) {
+                tigrGAPIDestroy(bmp);
+            }
+            eglDestroyContext(gState.display, win->context);
+        }
+
+        win->context = EGL_NO_CONTEXT;
+    }
+    free(bmp->pix);
+    free(bmp);
+}
+
+void tigrError(Tigr* bmp, const char* message, ...) {
+    char tmp[1024];
+
+    va_list args;
+    va_start(args, message);
+    vsnprintf(tmp, sizeof(tmp), message, args);
+    tmp[sizeof(tmp) - 1] = 0;
+    va_end(args);
+
+    LOGE("tigr fatal error: %s\n", tmp);
+
+    exit(1);
+}
+
+float tigrTime() {
+    struct timespec ts;
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+
+    double now = (double)ts.tv_sec + (ts.tv_nsec / 1000000000.0);
+    double elapsed = gState.lastTime == 0 ? 0 : now - gState.lastTime;
+    gState.lastTime = now;
+
+    return (float)elapsed;
+}
+
+void tigrMouse(Tigr* bmp, int* x, int* y, int* buttons) {
+    TigrInternal* win = tigrInternal(bmp);
+    if (x) {
+        *x = win->mouseX;
+    }
+    if (y) {
+        *y = win->mouseY;
+    }
+    if (buttons) {
+        *buttons = win->mouseButtons;
+    }
+}
+
+int tigrTouch(Tigr* bmp, TigrTouchPoint* points, int maxPoints) {
+    TigrInternal* win = tigrInternal(bmp);
+    for (int i = 0; i < maxPoints && i < win->numTouchPoints; i++) {
+        points[i] = win->touchPoints[i];
+    }
+    return maxPoints < win->numTouchPoints ? maxPoints : win->numTouchPoints;
+}
+
+void tigrMouseWheel(Tigr* bmp, float* x, float* y) {
+    if (x) {
+        *x = 0;
+    }
+    if (y) {
+        *y = 0;
+    }
+}
+
+void* tigrReadFile(const char* fileName, int* length) {
+    if (length != 0) {
+        *length = 0;
+    }
+
+    void* asset = android_loadAsset(fileName, length);
+    return asset;
+}
+
+#endif  // __ANDROID__
+#endif // #ifndef TIGR_HEADLESS
+
+//////// End of inlined file: tigr_android.c ////////
+
+//////// Start of inlined file: tigr_gl.c ////////
+
+#ifndef TIGR_HEADLESS
+
+//#include "tigr_internal.h"
+#include <assert.h>
+
+#ifdef TIGR_GAPI_GL
+#if __linux__
+#if __ANDROID__
+#include <GLES3/gl3.h>
+#else
+#define GLX_GLXEXT_PROTOTYPES
+#include <GL/glext.h>
+#endif
+#endif
+extern const char tigr_upscale_gl_vs[], tigr_upscale_gl_fs[], tigr_default_fx_gl_fs[];
+extern const int tigr_upscale_gl_vs_size, tigr_upscale_gl_fs_size, tigr_default_fx_gl_fs_size;
+
+#ifdef _WIN32
+
+#ifdef TIGR_GAPI_GL_WIN_USE_GLEXT
+#include <glext.h>
+#include <wglext.h>
+#else  // short version of glext.h and wglext.h so we don't need to depend on them
+#ifndef APIENTRY
+#define APIENTRY
+#endif
+#ifndef APIENTRYP
+#define APIENTRYP APIENTRY*
+#endif
+typedef ptrdiff_t GLsizeiptr;
+#define GL_COMPILE_STATUS 0x8B81
+#define GL_LINK_STATUS 0x8B82
+#define GL_ARRAY_BUFFER 0x8892
+#define GL_STATIC_DRAW 0x88E4
+#define GL_VERTEX_SHADER 0x8B31
+#define GL_FRAGMENT_SHADER 0x8B30
+#define GL_BGRA 0x80E1
+#define GL_TEXTURE0 0x84C0
+typedef void(APIENTRYP PFNGLGENVERTEXARRAYSPROC)(GLsizei n, GLuint* arrays);
+typedef void(APIENTRYP PFNGLGENBUFFERSARBPROC)(GLsizei n, GLuint* buffers);
+typedef void(APIENTRYP PFNGLBINDBUFFERPROC)(GLenum target, GLuint buffer);
+typedef void(APIENTRYP PFNGLBUFFERDATAPROC)(GLenum target, GLsizeiptr size, const void* data, GLenum usage);
+typedef void(APIENTRYP PFNGLBINDVERTEXARRAYPROC)(GLuint array);
+typedef void(APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC)(GLuint index);
+typedef void(APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC)(GLuint index,
+                                                     GLint size,
+                                                     GLenum type,
+                                                     GLboolean normalized,
+                                                     GLsizei stride,
+                                                     const void* pointer);
+typedef GLuint(APIENTRYP PFNGLCREATESHADERPROC)(GLenum type);
+typedef char GLchar;
+typedef void(APIENTRYP PFNGLSHADERSOURCEPROC)(GLuint shader,
+                                              GLsizei count,
+                                              const GLchar* const* string,
+                                              const GLint* length);
+typedef void(APIENTRYP PFNGLCOMPILESHADERPROC)(GLuint shader);
+typedef GLuint(APIENTRYP PFNGLCREATEPROGRAMPROC)(void);
+typedef void(APIENTRYP PFNGLATTACHSHADERPROC)(GLuint program, GLuint shader);
+typedef void(APIENTRYP PFNGLLINKPROGRAMPROC)(GLuint program);
+typedef void(APIENTRYP PFNGLDELETESHADERPROC)(GLuint shader);
+typedef void(APIENTRYP PFNGLDELETEPROGRAMPROC)(GLuint program);
+typedef void(APIENTRYP PFNGLGETSHADERIVPROC)(GLuint shader, GLenum pname, GLint* params);
+typedef void(APIENTRYP PFNGLGETSHADERINFOLOGPROC)(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog);
+typedef void(APIENTRYP PFNGLGETPROGRAMIVPROC)(GLuint program, GLenum pname, GLint* params);
+typedef void(APIENTRYP PFNGLGETPROGRAMINFOLOGPROC)(GLuint program, GLsizei bufSize, GLsizei* length, GLchar* infoLog);
+typedef void(APIENTRYP PFNGLUSEPROGRAMPROC)(GLuint program);
+typedef GLint(APIENTRYP PFNGLGETUNIFORMLOCATIONPROC)(GLuint program, const GLchar* name);
+typedef void(APIENTRYP PFNGLUNIFORM4FPROC)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
+typedef void(APIENTRYP PFNGLUNIFORMMATRIX4FVPROC)(GLint location,
+                                                  GLsizei count,
+                                                  GLboolean transpose,
+                                                  const GLfloat* value);
+typedef void(APIENTRYP PFNGLACTIVETEXTUREPROC)(GLenum texture);
+#define WGL_DRAW_TO_WINDOW_ARB 0x2001
+#define WGL_SUPPORT_OPENGL_ARB 0x2010
+#define WGL_DOUBLE_BUFFER_ARB 0x2011
+#define WGL_PIXEL_TYPE_ARB 0x2013
+#define WGL_COLOR_BITS_ARB 0x2014
+#define WGL_DEPTH_BITS_ARB 0x2022
+#define WGL_STENCIL_BITS_ARB 0x2023
+#define WGL_TYPE_RGBA_ARB 0x202B
+#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
+#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
+typedef BOOL(WINAPI* PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC hdc,
+                                                     const int* piAttribIList,
+                                                     const FLOAT* pfAttribFList,
+                                                     UINT nMaxFormats,
+                                                     int* piFormats,
+                                                     UINT* nNumFormats);
+typedef HGLRC(WINAPI* PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hDC, HGLRC hShareContext, const int* attribList);
+#endif
+
+PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormat;
+PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribs;
+PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
+PFNGLGENBUFFERSARBPROC glGenBuffers;
+PFNGLBINDBUFFERPROC glBindBuffer;
+PFNGLBUFFERDATAPROC glBufferData;
+PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
+PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray;
+PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer;
+PFNGLCREATESHADERPROC glCreateShader;
+PFNGLSHADERSOURCEPROC glShaderSource;
+PFNGLCOMPILESHADERPROC glCompileShader;
+PFNGLCREATEPROGRAMPROC glCreateProgram;
+PFNGLATTACHSHADERPROC glAttachShader;
+PFNGLLINKPROGRAMPROC glLinkProgram;
+PFNGLDELETESHADERPROC glDeleteShader;
+PFNGLDELETEPROGRAMPROC glDeleteProgram;
+PFNGLGETSHADERIVPROC glGetShaderiv;
+PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
+PFNGLGETPROGRAMIVPROC glGetProgramiv;
+PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
+PFNGLUSEPROGRAMPROC glUseProgram;
+PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
+PFNGLUNIFORM4FPROC glUniform4f;
+PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv;
+PFNGLACTIVETEXTUREPROC glActiveTexture;
+int tigrGL11Init(Tigr* bmp) {
+    int pixel_format;
+    TigrInternal* win = tigrInternal(bmp);
+    GLStuff* gl = &win->gl;
+    PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR),
+                                  1,
+                                  PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER | PFD_SWAP_EXCHANGE,
+                                  PFD_TYPE_RGBA,
+                                  32,  // color bits
+                                  0,
+                                  0,
+                                  0,
+                                  0,
+                                  0,
+                                  0,
+                                  0,
+                                  0,
+                                  0,
+                                  0,
+                                  0,
+                                  0,
+                                  0,
+                                  24,  // depth
+                                  8,   // stencil
+                                  0,
+                                  PFD_MAIN_PLANE,  // is it ignored ?
+                                  0,
+                                  0,
+                                  0,
+                                  0 };
+    if (!(gl->dc = GetDC((HWND)bmp->handle))) {
+        tigrError(bmp, "Cannot create OpenGL device context.\n");
+        return -1;
+    }
+    if (!(pixel_format = ChoosePixelFormat(gl->dc, &pfd))) {
+        tigrError(bmp, "Cannot choose OpenGL pixel format.\n");
+        return -1;
+    }
+    if (!SetPixelFormat(gl->dc, pixel_format, &pfd)) {
+        tigrError(bmp, "Cannot set OpenGL pixel format.\n");
+        return -1;
+    }
+    if (!(gl->hglrc = wglCreateContext(gl->dc))) {
+        tigrError(bmp, "Cannot create OpenGL context.\n");
+        return -1;
+    }
+    if (!wglMakeCurrent(gl->dc, gl->hglrc)) {
+        tigrError(bmp, "Cannot activate OpenGL context.\n");
+        return -1;
+    }
+    gl->gl_legacy = 1;
+    return 0;
+}
+int tigrGL33Init(Tigr* bmp) {
+    int pixel_format;
+    UINT num_formats;
+    TigrInternal* win = tigrInternal(bmp);
+    GLStuff* gl = &win->gl;
+
+    wglChoosePixelFormat = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
+    wglCreateContextAttribs = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");
+    glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)wglGetProcAddress("glGenVertexArrays");
+    glGenBuffers = (PFNGLGENBUFFERSARBPROC)wglGetProcAddress("glGenBuffers");
+    glBindBuffer = (PFNGLBINDBUFFERPROC)wglGetProcAddress("glBindBuffer");
+    glBufferData = (PFNGLBUFFERDATAPROC)wglGetProcAddress("glBufferData");
+    glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)wglGetProcAddress("glBindVertexArray");
+    glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)wglGetProcAddress("glEnableVertexAttribArray");
+    glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)wglGetProcAddress("glVertexAttribPointer");
+    glCreateShader = (PFNGLCREATESHADERPROC)wglGetProcAddress("glCreateShader");
+    glShaderSource = (PFNGLSHADERSOURCEPROC)wglGetProcAddress("glShaderSource");
+    glCompileShader = (PFNGLCOMPILESHADERPROC)wglGetProcAddress("glCompileShader");
+    glCreateProgram = (PFNGLCREATEPROGRAMPROC)wglGetProcAddress("glCreateProgram");
+    glAttachShader = (PFNGLATTACHSHADERPROC)wglGetProcAddress("glAttachShader");
+    glLinkProgram = (PFNGLLINKPROGRAMPROC)wglGetProcAddress("glLinkProgram");
+    glDeleteShader = (PFNGLDELETESHADERPROC)wglGetProcAddress("glDeleteShader");
+    glDeleteProgram = (PFNGLDELETEPROGRAMPROC)wglGetProcAddress("glDeleteProgram");
+    glGetShaderiv = (PFNGLGETSHADERIVPROC)wglGetProcAddress("glGetShaderiv");
+    glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)wglGetProcAddress("glGetShaderInfoLog");
+    glGetProgramiv = (PFNGLGETPROGRAMIVPROC)wglGetProcAddress("glGetProgramiv");
+    glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)wglGetProcAddress("glGetProgramInfoLog");
+    glUseProgram = (PFNGLUSEPROGRAMPROC)wglGetProcAddress("glUseProgram");
+    glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)wglGetProcAddress("glGetUniformLocation");
+    glUniform4f = (PFNGLUNIFORM4FPROC)wglGetProcAddress("glUniform4f");
+    glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC)wglGetProcAddress("glUniformMatrix4fv");
+    glActiveTexture = (PFNGLACTIVETEXTUREPROC)wglGetProcAddress("glActiveTexture");
+
+    if (!wglChoosePixelFormat || !wglCreateContextAttribs) {
+        tigrError(bmp, "Cannot create OpenGL context.\n");
+        return -1;
+    }
+    const int attribList[] = { WGL_DRAW_TO_WINDOW_ARB,
+                               GL_TRUE,
+                               WGL_SUPPORT_OPENGL_ARB,
+                               GL_TRUE,
+                               WGL_DOUBLE_BUFFER_ARB,
+                               GL_TRUE,
+                               WGL_PIXEL_TYPE_ARB,
+                               WGL_TYPE_RGBA_ARB,
+                               WGL_COLOR_BITS_ARB,
+                               32,
+                               WGL_DEPTH_BITS_ARB,
+                               24,
+                               WGL_STENCIL_BITS_ARB,
+                               8,
+                               0 };
+    int attribs[] = { WGL_CONTEXT_MAJOR_VERSION_ARB, 3, WGL_CONTEXT_MINOR_VERSION_ARB, 3, 0 };
+    if (!wglChoosePixelFormat(gl->dc, attribList, NULL, 1, &pixel_format, &num_formats)) {
+        tigrError(bmp, "Cannot choose OpenGL pixel format.\n");
+        return -1;
+    }
+    if (!(gl->hglrc = wglCreateContextAttribs(gl->dc, gl->hglrc, attribs))) {
+        tigrError(bmp, "Cannot create OpenGL context attribs.\n");
+        return -1;
+    }
+    if (!wglMakeCurrent(gl->dc, gl->hglrc)) {
+        tigrError(bmp, "Cannot activate OpenGL context.\n");
+        return -1;
+    }
+    gl->gl_legacy = 0;
+    return 0;
+}
+#endif
+
+void tigrCheckGLError(const char* state) {
+    GLenum err = glGetError();
+    if (err != GL_NO_ERROR) {
+        tigrError(NULL, "got GL error %x when doing %s\n", err, state);
+    }
+}
+
+void tigrCheckShaderErrors(GLuint object) {
+    GLint success;
+    GLchar info[2048];
+    glGetShaderiv(object, GL_COMPILE_STATUS, &success);
+    if (!success) {
+        glGetShaderInfoLog(object, sizeof(info), NULL, info);
+        tigrError(NULL, "shader compile error : %s\n", info);
+    }
+}
+
+void tigrCheckProgramErrors(GLuint object) {
+    GLint success;
+    GLchar info[2048];
+    glGetProgramiv(object, GL_LINK_STATUS, &success);
+    if (!success) {
+        glGetProgramInfoLog(object, sizeof(info), NULL, info);
+        tigrError(NULL, "shader link error : %s\n", info);
+    }
+}
+
+void tigrCreateShaderProgram(GLStuff* gl, const char* fxSource, int fxSize) {
+    if (gl->program != 0) {
+        glDeleteProgram(gl->program);
+        gl->program = 0;
+    }
+
+    GLuint vs = glCreateShader(GL_VERTEX_SHADER);
+    const char* vs_source = (const char*)&tigr_upscale_gl_vs;
+    glShaderSource(vs, 1, &vs_source, &tigr_upscale_gl_vs_size);
+    glCompileShader(vs);
+    tigrCheckShaderErrors(vs);
+
+    GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
+    const char* fs_sources[] = {
+        (const char*)tigr_upscale_gl_fs,
+        fxSource,
+    };
+    const int fs_lengths[] = {
+        tigr_upscale_gl_fs_size,
+        fxSize,
+    };
+    glShaderSource(fs, 2, fs_sources, fs_lengths);
+    glCompileShader(fs);
+    tigrCheckShaderErrors(fs);
+
+    gl->program = glCreateProgram();
+    glAttachShader(gl->program, vs);
+    glAttachShader(gl->program, fs);
+    glLinkProgram(gl->program);
+    tigrCheckProgramErrors(gl->program);
+    glDeleteShader(vs);
+    glDeleteShader(fs);
+
+    gl->uniform_projection = glGetUniformLocation(gl->program, "projection");
+    gl->uniform_model = glGetUniformLocation(gl->program, "model");
+    gl->uniform_parameters = glGetUniformLocation(gl->program, "parameters");
+}
+
+void tigrGAPICreate(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    GLStuff* gl = &win->gl;
+    GLuint VBO;
+    GLfloat vertices[] = { // pos      uv
+                           0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+                           0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f
+    };
+
+#ifdef _WIN32
+    if (tigrGL11Init(bmp))
+        return;
+    tigrGL33Init(bmp);
+#endif
+
+    if (!gl->gl_legacy) {
+        // create vao
+        glGenVertexArrays(1, &gl->vao);
+        glGenBuffers(1, &VBO);
+        glBindBuffer(GL_ARRAY_BUFFER, VBO);
+        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
+        glBindVertexArray(gl->vao);
+        glEnableVertexAttribArray(0);
+        glEnableVertexAttribArray(1);
+        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), NULL);
+        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), NULL);
+
+        // create program
+        tigrCreateShaderProgram(gl, tigr_default_fx_gl_fs, tigr_default_fx_gl_fs_size);
+    }
+
+    // create textures
+    if (gl->gl_legacy) {
+        glEnable(GL_TEXTURE_2D);
+    }
+    glGenTextures(2, gl->tex);
+    for (int i = 0; i < 2; ++i) {
+        glBindTexture(GL_TEXTURE_2D, gl->tex[i]);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl->gl_legacy ? GL_NEAREST : GL_LINEAR);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl->gl_legacy ? GL_NEAREST : GL_LINEAR);
+        glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+    }
+
+    tigrCheckGLError("initialization");
+}
+
+void tigrGAPIDestroy(Tigr* bmp) {
+    TigrInternal* win = tigrInternal(bmp);
+    GLStuff* gl = &win->gl;
+
+    if (tigrGAPIBegin(bmp) < 0) {
+        tigrError(bmp, "Cannot activate OpenGL context.\n");
+        return;
+    }
+
+    if (!gl->gl_legacy) {
+        glDeleteTextures(2, gl->tex);
+        glDeleteProgram(gl->program);
+    }
+
+    tigrCheckGLError("destroy");
+
+    if (tigrGAPIEnd(bmp) < 0) {
+        tigrError(bmp, "Cannot deactivate OpenGL context.\n");
+        return;
+    }
+}
+
+void tigrGAPIDraw(int legacy, GLuint uniform_model, GLuint tex, Tigr* bmp, int x1, int y1, int x2, int y2) {
+    glBindTexture(GL_TEXTURE_2D, tex);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, bmp->w, bmp->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, bmp->pix);
+
+    if (!legacy) {
+        float sx = (float)(x2 - x1);
+        float sy = (float)(y2 - y1);
+        float tx = (float)x1;
+        float ty = (float)y1;
+
+        float model[16] = { sx, 0.0f, 0.0f, 0.0f, 0.0f, sy, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, tx, ty, 0.0f, 1.0f };
+
+        glUniformMatrix4fv(uniform_model, 1, GL_FALSE, model);
+        glDrawArrays(GL_TRIANGLES, 0, 6);
+    } else {
+#if !(__APPLE__ || __ANDROID__)
+        glBegin(GL_QUADS);
+        glTexCoord2f(1.0f, 0.0f);
+        glVertex2i(x2, y1);
+        glTexCoord2f(0.0f, 0.0f);
+        glVertex2i(x1, y1);
+        glTexCoord2f(0.0f, 1.0f);
+        glVertex2i(x1, y2);
+        glTexCoord2f(1.0f, 1.0f);
+        glVertex2i(x2, y2);
+        glEnd();
+#else
+        assert(0);
+#endif
+    }
+}
+
+void tigrGAPIPresent(Tigr* bmp, int w, int h) {
+    TigrInternal* win = tigrInternal(bmp);
+    GLStuff* gl = &win->gl;
+
+    glViewport(0, 0, w, h);
+    if (!gl->gl_user_opengl_rendering) {
+        glClearColor(0, 0, 0, 1);
+        glClear(GL_COLOR_BUFFER_BIT);
+    }
+
+    if (!gl->gl_legacy) {
+        float projection[16] = { 2.0f / w, 0.0f, 0.0f, 0.0f, 0.0f,  -2.0f / h, 0.0f, 0.0f,
+                                 0.0f,     0.0f, 1.0f, 0.0f, -1.0f, 1.0f,      0.0f, 1.0f };
+
+        glActiveTexture(GL_TEXTURE0);
+        glBindVertexArray(gl->vao);
+        glUseProgram(gl->program);
+        glUniformMatrix4fv(gl->uniform_projection, 1, GL_FALSE, projection);
+        glUniform4f(gl->uniform_parameters, win->p1, win->p2, win->p3, win->p4);
+    } else {
+#if !(__APPLE__ || __ANDROID__)
+        glMatrixMode(GL_PROJECTION);
+        glLoadIdentity();
+        glOrtho(0, w, h, 0, -1.0f, 1.0f);
+        glEnable(GL_TEXTURE_2D);
+#else
+        assert(0);
+#endif
+    }
+
+    if (gl->gl_user_opengl_rendering) {
+        glEnable(GL_BLEND);
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+    } else {
+        glDisable(GL_BLEND);
+    }
+    tigrGAPIDraw(gl->gl_legacy, gl->uniform_model, gl->tex[0], bmp, win->pos[0], win->pos[1], win->pos[2], win->pos[3]);
+
+    if (win->widgetsScale > 0) {
+        glEnable(GL_BLEND);
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+        tigrGAPIDraw(gl->gl_legacy, gl->uniform_model, gl->tex[1], win->widgets,
+                     (int)(w - win->widgets->w * win->widgetsScale), 0, w, (int)(win->widgets->h * win->widgetsScale));
+    }
+
+    tigrCheckGLError("present");
+
+    gl->gl_user_opengl_rendering = 0;
+}
+
+#endif
+#endif // #ifndef TIGR_HEADLESS
+//////// End of inlined file: tigr_gl.c ////////
+
+//////// Start of inlined file: tigr_utils.c ////////
+
+//#include "tigr_internal.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifndef __ANDROID__
+
+#ifdef __IOS__
+void* _tigrReadFile(const char* fileName, int* length) {
+#else
+void* tigrReadFile(const char* fileName, int* length) {
+#endif
+    // TODO - unicode?
+    FILE* file;
+    char* data;
+    size_t len;
+
+    if (length)
+        *length = 0;
+
+    file = fopen(fileName, "rb");
+    if (!file)
+        return NULL;
+
+    fseek(file, 0, SEEK_END);
+    len = ftell(file);
+    fseek(file, 0, SEEK_SET);
+
+    data = (char*)malloc(len + 1);
+    if (!data) {
+        fclose(file);
+        return NULL;
+    }
+
+    if (fread(data, 1, len, file) != len) {
+        free(data);
+        fclose(file);
+        return NULL;
+    }
+    data[len] = '\0';
+    fclose(file);
+
+    if (length)
+        *length = len;
+
+    return data;
+}
+
+#endif  // __ANDROID__
+
+// Reads a single UTF8 codepoint.
+const char* tigrDecodeUTF8(const char* text, int* cp) {
+    unsigned char c = *text++;
+    int extra = 0, min = 0;
+    *cp = 0;
+    if (c >= 0xf0) {
+        *cp = c & 0x07;
+        extra = 3;
+        min = 0x10000;
+    } else if (c >= 0xe0) {
+        *cp = c & 0x0f;
+        extra = 2;
+        min = 0x800;
+    } else if (c >= 0xc0) {
+        *cp = c & 0x1f;
+        extra = 1;
+        min = 0x80;
+    } else if (c >= 0x80) {
+        *cp = 0xfffd;
+    } else {
+        *cp = c;
+    }
+    while (extra--) {
+        c = *text++;
+        if ((c & 0xc0) != 0x80) {
+            *cp = 0xfffd;
+            break;
+        }
+        (*cp) = ((*cp) << 6) | (c & 0x3f);
+    }
+    if (*cp < min) {
+        *cp = 0xfffd;
+    }
+    return text;
+}
+
+char* tigrEncodeUTF8(char* text, int cp) {
+    if (cp < 0 || cp > 0x10ffff) {
+        cp = 0xfffd;
+    }
+
+#define EMIT(X, Y, Z) *text++ = X | ((cp >> Y) & Z)
+    if (cp < 0x80) {
+        EMIT(0x00, 0, 0x7f);
+    } else if (cp < 0x800) {
+        EMIT(0xc0, 6, 0x1f);
+        EMIT(0x80, 0, 0x3f);
+    } else if (cp < 0x10000) {
+        EMIT(0xe0, 12, 0xf);
+        EMIT(0x80, 6, 0x3f);
+        EMIT(0x80, 0, 0x3f);
+    } else {
+        EMIT(0xf0, 18, 0x7);
+        EMIT(0x80, 12, 0x3f);
+        EMIT(0x80, 6, 0x3f);
+        EMIT(0x80, 0, 0x3f);
+    }
+    return text;
+#undef EMIT
+}
+
+#ifndef TIGR_HEADLESS
+
+int tigrBeginOpenGL(Tigr* bmp) {
+#ifdef TIGR_GAPI_GL
+    TigrInternal* win = tigrInternal(bmp);
+    win->gl.gl_user_opengl_rendering = 1;
+    return tigrGAPIBegin(bmp) == 0;
+#else
+    return 0;
+#endif
+}
+
+void tigrSetPostShader(Tigr* bmp, const char* code, int size) {
+#ifdef TIGR_GAPI_GL
+    tigrGAPIBegin(bmp);
+    TigrInternal* win = tigrInternal(bmp);
+    GLStuff* gl = &win->gl;
+    tigrCreateShaderProgram(gl, code, size);
+    tigrGAPIEnd(bmp);
+#endif
+}
+
+void tigrSetPostFX(Tigr* bmp, float p1, float p2, float p3, float p4) {
+    TigrInternal* win = tigrInternal(bmp);
+    win->p1 = p1;
+    win->p2 = p2;
+    win->p3 = p3;
+    win->p4 = p4;
+}
+
+#endif // TIGR_HEADLESS
+
+//////// End of inlined file: tigr_utils.c ////////
+
+
+//////// End of inlined file: tigr_amalgamated.c ////////
+

+ 366 - 0
tigr-master/tigr.h

@@ -0,0 +1,366 @@
+// TIGR - TIny GRaphics Library - v3.2
+//        ^^   ^^
+//
+// rawr.
+
+/*
+This is free and unencumbered software released into the public domain.
+
+Our intent is that anyone is free to copy and use this software,
+for any purpose, in any form, and by any means.
+
+The authors dedicate any and all copyright interest in the software
+to the public domain, at their own expense for the betterment of mankind.
+
+The software is provided "as is", without any kind of warranty, including
+any implied warranty. If it breaks, you get to keep both pieces.
+*/
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Compiler configuration.
+#ifdef _MSC_VER
+#define TIGR_INLINE static __forceinline
+#else
+#define TIGR_INLINE static inline
+#endif
+
+// Bitmaps ----------------------------------------------------------------
+
+// This struct contains one pixel.
+typedef struct {
+    unsigned char r, g, b, a;
+} TPixel;
+
+// Window flags.
+#define TIGR_FIXED      0   // window's bitmap is a fixed size (default)
+#define TIGR_AUTO       1   // window's bitmap is scaled with the window
+#define TIGR_2X         2   // always enforce (at least) 2X pixel scale
+#define TIGR_3X         4   // always enforce (at least) 3X pixel scale
+#define TIGR_4X         8   // always enforce (at least) 4X pixel scale
+#define TIGR_RETINA     16  // enable retina support on OS X
+#define TIGR_NOCURSOR   32  // hide cursor
+#define TIGR_FULLSCREEN 64  // start in full-screen mode
+
+// A Tigr bitmap.
+typedef struct Tigr {
+    int w, h;           // width/height (unscaled)
+    int cx, cy, cw, ch; // clip rect
+    TPixel *pix;        // pixel data
+    void *handle;       // OS window handle, NULL for off-screen bitmaps.
+    int blitMode;       // Target bitmap blit mode
+} Tigr;
+
+// Creates a new empty window with a given bitmap size.
+//
+// Title is UTF-8.
+//
+// In TIGR_FIXED mode, the window is made as large as possible to contain an integer-scaled
+// version of the bitmap while still fitting on the screen. Resizing the window will adapt
+// the scale in integer steps to fit the bitmap.
+//
+// In TIGR_AUTO mode, the initial window size is set to the bitmap size times the pixel
+// scale. Resizing the window will resize the bitmap using the specified scale.
+// For example, in forced 2X mode, the window will be twice as wide (and high) as the bitmap.
+//
+// Turning on TIGR_RETINA mode will request full backing resolution on OSX, meaning that
+// the effective window size might be integer scaled to a larger size. In TIGR_AUTO mode,
+// this means that the Tigr bitmap will change size if the window is moved between
+// retina and non-retina screens.
+//
+Tigr *tigrWindow(int w, int h, const char *title, int flags);
+
+// Creates an empty off-screen bitmap.
+Tigr *tigrBitmap(int w, int h);
+
+// Deletes a window/bitmap.
+void tigrFree(Tigr *bmp);
+
+// Returns non-zero if the user requested to close a window.
+int tigrClosed(Tigr *bmp);
+
+// Displays a window's contents on-screen and updates input.
+void tigrUpdate(Tigr *bmp);
+
+// Called before doing direct OpenGL calls and before tigrUpdate.
+// Returns non-zero if OpenGL is available.
+int tigrBeginOpenGL(Tigr *bmp);
+
+// Sets post shader for a window.
+// This replaces the built-in post-FX shader.
+void tigrSetPostShader(Tigr *bmp, const char* code, int size);
+
+// Sets post-FX properties for a window.
+//
+// The built-in post-FX shader uses the following parameters:
+// p1: hblur - use bilinear filtering along the x-axis (pixels)
+// p2: vblur - use bilinear filtering along the y-axis (pixels)
+// p3: scanlines - CRT scanlines effect (0-1)
+// p4: contrast - contrast boost (1 = no change, 2 = 2X contrast, etc)
+void tigrSetPostFX(Tigr *bmp, float p1, float p2, float p3, float p4);
+
+
+// Drawing ----------------------------------------------------------------
+
+// Helper for reading pixels.
+// For high performance, just access bmp->pix directly.
+TPixel tigrGet(Tigr *bmp, int x, int y);
+
+// Plots a pixel.
+// Clips and blends.
+// For high performance, just access bmp->pix directly.
+void tigrPlot(Tigr *bmp, int x, int y, TPixel pix);
+
+// Clears a bitmap to a color.
+// No blending, no clipping.
+void tigrClear(Tigr *bmp, TPixel color);
+
+// Fills a rectangular area.
+// No blending, no clipping.
+void tigrFill(Tigr *bmp, int x, int y, int w, int h, TPixel color);
+
+// Draws a line.
+// Start pixel is drawn, end pixel is not.
+// Clips and blends.
+void tigrLine(Tigr *bmp, int x0, int y0, int x1, int y1, TPixel color);
+
+// Draws an empty rectangle.
+// Drawing a 1x1 rectangle yields the same result as calling tigrPlot.
+// Clips and blends.
+void tigrRect(Tigr *bmp, int x, int y, int w, int h, TPixel color);
+
+// Fills a rectangle.
+// Fills the inside of the specified rectangular area.
+// Calling tigrRect followed by tigrFillRect using the same arguments
+// causes no overdrawing.
+// Clips and blends.
+void tigrFillRect(Tigr *bmp, int x, int y, int w, int h, TPixel color);
+
+// Draws a circle.
+// Drawing a zero radius circle yields the same result as calling tigrPlot.
+// Drawing a circle with radius one draws a circle three pixels wide.
+// Clips and blends.
+void tigrCircle(Tigr *bmp, int x, int y, int r, TPixel color);
+
+// Fills a circle.
+// Fills the inside of the specified circle.
+// Calling tigrCircle followed by tigrFillCircle using the same arguments
+// causes no overdrawing.
+// Filling a circle with zero radius has no effect.
+// Clips and blends.
+void tigrFillCircle(Tigr *bmp, int x, int y, int r, TPixel color);
+
+// Sets clip rect.
+// Set to (0, 0, -1, -1) to reset clipping to full bitmap.
+void tigrClip(Tigr *bmp, int cx, int cy, int cw, int ch);
+
+// Copies bitmap data.
+// dx/dy = dest co-ordinates
+// sx/sy = source co-ordinates
+// w/h   = width/height
+//
+// RGBAdest = RGBAsrc
+// Clips, does not blend.
+void tigrBlit(Tigr *dest, Tigr *src, int dx, int dy, int sx, int sy, int w, int h);
+
+// Same as tigrBlit, but alpha blends the source bitmap with the
+// target using per pixel alpha and the specified global alpha.
+//
+// Ablend = Asrc * alpha
+// RGBdest = RGBsrc * Ablend + RGBdest * (1 - Ablend)
+//
+// Blit mode == TIGR_KEEP_ALPHA:
+// Adest = Adest
+//
+// Blit mode == TIGR_BLEND_ALPHA:
+// Adest = Asrc * Ablend + Adest * (1 - Ablend)
+// Clips and blends.
+void tigrBlitAlpha(Tigr *dest, Tigr *src, int dx, int dy, int sx, int sy, int w, int h, float alpha);
+
+// Same as tigrBlit, but tints the source bitmap with a color
+// and alpha blends the resulting source with the destination.
+//
+// Rblend = Rsrc * Rtint
+// Gblend = Gsrc * Gtint
+// Bblend = Bsrc * Btint
+// Ablend = Asrc * Atint
+//
+// RGBdest = RGBblend * Ablend + RGBdest * (1 - Ablend)
+//
+// Blit mode == TIGR_KEEP_ALPHA:
+// Adest = Adest
+//
+// Blit mode == TIGR_BLEND_ALPHA:
+// Adest = Ablend * Ablend + Adest * (1 - Ablend)
+// Clips and blends.
+void tigrBlitTint(Tigr *dest, Tigr *src, int dx, int dy, int sx, int sy, int w, int h, TPixel tint);
+
+enum TIGRBlitMode {
+    TIGR_KEEP_ALPHA = 0,    // Keep destination alpha value
+    TIGR_BLEND_ALPHA = 1,   // Blend destination alpha (default)
+};
+
+// Set destination bitmap blend mode for blit operations.
+void tigrBlitMode(Tigr *dest, int mode);
+
+// Helper for making colors.
+TIGR_INLINE TPixel tigrRGB(unsigned char r, unsigned char g, unsigned char b)
+{
+    TPixel p; p.r = r; p.g = g; p.b = b; p.a = 0xff; return p;
+}
+
+// Helper for making colors.
+TIGR_INLINE TPixel tigrRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+{
+    TPixel p; p.r = r; p.g = g; p.b = b; p.a = a; return p;
+}
+
+
+// Font printing ----------------------------------------------------------
+
+typedef struct {
+    int code, x, y, w, h;
+} TigrGlyph;
+
+typedef struct {
+    Tigr *bitmap;
+    int numGlyphs;
+    TigrGlyph *glyphs;
+} TigrFont;
+
+typedef enum {
+    TCP_ASCII = 0,
+    TCP_1252 = 1252,
+    TCP_UTF32 = 12001
+} TCodepage;
+
+// Loads a font from a bitmap font sheet.
+// The loaded font takes ownership of the provided bitmap.
+//
+// Codepages:
+//
+//  TCP_ASCII   - Regular 7-bit ASCII
+//  TCP_1252    - Windows 1252
+//  TCP_UTF32   - Unicode subset
+//
+// For ASCII and 1252, the font bitmap should contain all characters
+// for the given codepage, excluding the first 32 control codes.
+//
+// For UTF32 - the font bitmap contains a subset of Unicode characters
+// and must be in the format generated by tigrFont for UTF32.
+//
+TigrFont *tigrLoadFont(Tigr *bitmap, int codepage);
+
+// Frees a font and associated font sheet.
+void tigrFreeFont(TigrFont *font);
+
+// Prints UTF-8 text onto a bitmap.
+// NOTE:
+//  This uses the target bitmap blit mode.
+//  See tigrBlitTint for details.
+void tigrPrint(Tigr *dest, TigrFont *font, int x, int y, TPixel color, const char *text, ...);
+
+// Returns the width/height of a string.
+int tigrTextWidth(TigrFont *font, const char *text);
+int tigrTextHeight(TigrFont *font, const char *text);
+
+// The built-in font.
+extern TigrFont *tfont;
+
+
+// User Input -------------------------------------------------------------
+
+// Key scancodes. For letters/numbers, use ASCII ('A'-'Z' and '0'-'9').
+typedef enum {
+    TK_PAD0=128,TK_PAD1,TK_PAD2,TK_PAD3,TK_PAD4,TK_PAD5,TK_PAD6,TK_PAD7,TK_PAD8,TK_PAD9,
+    TK_PADMUL,TK_PADADD,TK_PADENTER,TK_PADSUB,TK_PADDOT,TK_PADDIV,
+    TK_F1,TK_F2,TK_F3,TK_F4,TK_F5,TK_F6,TK_F7,TK_F8,TK_F9,TK_F10,TK_F11,TK_F12,
+    TK_BACKSPACE,TK_TAB,TK_RETURN,TK_SHIFT,TK_CONTROL,TK_ALT,TK_PAUSE,TK_CAPSLOCK,
+    TK_ESCAPE,TK_SPACE,TK_PAGEUP,TK_PAGEDN,TK_END,TK_HOME,TK_LEFT,TK_UP,TK_RIGHT,TK_DOWN,
+    TK_INSERT,TK_DELETE,TK_LWIN,TK_RWIN,TK_NUMLOCK,TK_SCROLL,TK_LSHIFT,TK_RSHIFT,
+    TK_LCONTROL,TK_RCONTROL,TK_LALT,TK_RALT,TK_SEMICOLON,TK_EQUALS,TK_COMMA,TK_MINUS,
+    TK_DOT,TK_SLASH,TK_BACKTICK,TK_LSQUARE,TK_BACKSLASH,TK_RSQUARE,TK_TICK
+} TKey;
+
+// Returns mouse input for a window.
+// The value set to "buttons" is a bit set where bits 0, 1 and 2
+// corresponds to the left, right and middle buttons.
+// A set bit indicates that a button is held.
+void tigrMouse(Tigr *bmp, int *x, int *y, int *buttons);
+
+typedef struct {
+    int x;
+    int y;
+} TigrTouchPoint;
+
+// Reads touch input for a window.
+// Returns number of touch points read.
+int tigrTouch(Tigr *bmp, TigrTouchPoint* points, int maxPoints);
+
+// Reads the delta of the scroll "wheel" in somewhat platform neutral
+// units where 1.0 corresponds to a "notch". The actual correlation between
+// physical movement and this number varies between platforms, input methods
+// and settings.
+void tigrScrollWheel(Tigr* bmp, float* x, float *y);
+
+// Reads the keyboard for a window.
+// Returns non-zero if a key is pressed/held.
+// tigrKeyDown tests for the initial press, tigrKeyHeld repeats each frame.
+int tigrKeyDown(Tigr *bmp, int key);
+int tigrKeyHeld(Tigr *bmp, int key);
+
+// Reads character input for a window.
+// Returns the Unicode value of the last key pressed, or 0 if none.
+int tigrReadChar(Tigr *bmp);
+
+// Show / hide virtual keyboard.
+// (Only available on iOS / Android)
+void tigrShowKeyboard(int show);
+
+
+// Bitmap I/O -------------------------------------------------------------
+
+// Loads a PNG, from either a file or memory. (fileName is UTF-8)
+// On error, returns NULL and sets errno.
+Tigr *tigrLoadImage(const char *fileName);
+Tigr *tigrLoadImageMem(const void *data, int length);
+
+// Saves a PNG to a file. (fileName is UTF-8)
+// On error, returns zero and sets errno.
+int tigrSaveImage(const char *fileName, Tigr *bmp);
+
+
+// Helpers ----------------------------------------------------------------
+
+// Returns the amount of time elapsed since tigrTime was last called,
+// or zero on the first call.
+float tigrTime(void);
+
+// Displays an error message and quits. (UTF-8)
+// 'bmp' can be NULL.
+void tigrError(Tigr *bmp, const char *message, ...);
+
+// Reads an entire file into memory. (fileName is UTF-8)
+// Free it yourself after with 'free'.
+// On error, returns NULL and sets errno.
+// TIGR will automatically append a NUL terminator byte
+// to the end (not included in the length)
+void *tigrReadFile(const char *fileName, int *length);
+
+// Decompresses DEFLATEd zip/zlib data into a buffer.
+// Returns non-zero on success.
+int tigrInflate(void *out, unsigned outlen, const void *in, unsigned inlen);
+
+// Decodes a single UTF8 codepoint and returns the next pointer.
+const char *tigrDecodeUTF8(const char *text, int *cp);
+
+// Encodes a single UTF8 codepoint and returns the next pointer.
+char *tigrEncodeUTF8(char *text, int cp);
+
+#ifdef __cplusplus
+}
+#endif

二進制
tigr-master/tigr.o


二進制
tigr-master/tigr.png


+ 9 - 0
tigr-master/unicode-test.c

@@ -0,0 +1,9 @@
+#include <stdio.h>
+#include <unicode/ustring.h>
+
+int main() {
+    UChar unicodeString[20];
+    u_uastrcpy(unicodeString, "Hello, ICU!");
+    printf("Unicode string: %S\n", unicodeString);
+    return 0;
+}