Browse Source

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 tuần trước cách đây
mục cha
commit
884feb8ab4
86 tập tin đã thay đổi với 15488 bổ sung và 58 xóa
  1. 204 0
      modula2/ANALYSIS.md
  2. 2 2
      modula2/README.md
  3. BIN
      modula2/examples/Essai1/Essai1
  4. 3 8
      modula2/examples/Essai1/Essai1.mod
  5. BIN
      modula2/examples/Essai2/Essai2
  6. 11 3
      modula2/examples/Essai2/Essai2.mod
  7. BIN
      modula2/examples/Essai3/essai3
  8. 14 0
      modula2/examples/Essai3/essai3.mod
  9. 64 0
      modula2/examples/Essai4/essai4.mod
  10. BIN
      modula2/examples/clavier/clavier
  11. 1 4
      modula2/examples/clavier/clavier.mod
  12. BIN
      modula2/examples/clip/clip
  13. BIN
      modula2/examples/demo/demo
  14. 62 23
      modula2/examples/demo/demo.mod
  15. BIN
      modula2/examples/flags/flags
  16. 214 0
      modula2/examples/flags/flags.mod
  17. BIN
      modula2/examples/headless/headless
  18. 22 0
      modula2/examples/headless/headless.mod
  19. BIN
      modula2/examples/hello/hello
  20. 20 0
      modula2/examples/hello/hello.mod
  21. 11 0
      modula2/examples/opengl/gl.def
  22. BIN
      modula2/examples/opengl/opengl
  23. 51 0
      modula2/examples/opengl/opengl.mod
  24. BIN
      modula2/examples/shader/shader
  25. 36 0
      modula2/examples/shader/shader.mod
  26. BIN
      modula2/examples/unicode/update
  27. 51 2
      modula2/examples/unicode/update.mod
  28. BIN
      modula2/helper.o
  29. 38 16
      modula2/tigr.def
  30. BIN
      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. BIN
      tigr-master/ci/5x7.png
  36. 15 0
      tigr-master/ci/Makefile
  37. BIN
      tigr-master/ci/ch.png
  38. 399 0
      tigr-master/ci/ci.c
  39. BIN
      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. BIN
      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. BIN
      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. BIN
      tigr-master/tigr.o
  85. BIN
      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
   
-  gm2 -c helper.mod
+  gm2 -fiso -c helper.mod
 
 * 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
 

BIN
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 InOut IMPORT Write, WriteLn, WriteString, WriteCard;
 FROM RealInOut IMPORT WriteShortReal;
-FROM DynamicStrings IMPORT String, InitString;
 FROM Delay IMPORT Delay;
 
 VAR 
     screen : TigrPtr;
     redColor, blueColor, greenColor, blueColorHalf : TPixelType;
-    message1, message2, message3 : String;
 
 
 BEGIN
-    message1 := InitString("Hello");
-    message2 := InitString("Bonjour tout le monde!");
-    message3 := InitString("How are you all?");
     WITH redColor DO 
         r := 255;
         g := 0;
@@ -43,7 +38,7 @@ BEGIN
         b := 0;
         a := 255;
     END;
-    screen := tigrWindow(1000, 800, message1, 0);
+    screen := tigrWindow(1000, 800, "Hello", 0);
      WriteShortReal(tigrTime(),8); WriteLn;
     WHILE (NOT (tigrClosed(screen) > 0)) OR (tigrKeyDown(screen, TK_ESCAPE) > 0) DO 
         tigrClear(screen, tigrRGB(80H, 90H, 0A0H));
@@ -52,8 +47,8 @@ BEGIN
             tigrLine(screen, 100, 100, 300, 500, blueColor );
         END;
         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);
         tigrCircle(screen, 600, 600,50,greenColor);
         tigrRect(screen, 250,250,250,250,blueColorHalf);

BIN
modula2/examples/Essai2/Essai2


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

@@ -3,8 +3,16 @@ MODULE Essai2;
 IMPORT helper, tigr;
 
 VAR
-    a : tigr.TPixelType;
+    a      : tigr.TPixelType;
+    screen : tigr.TigrPtr;
 
 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.

BIN
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.

BIN
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;
 
 FROM helper IMPORT tigrRGB, tigrRGBA;
-FROM DynamicStrings IMPORT String, InitString;
 FROM InOut IMPORT Write, WriteLn, WriteCard,WriteString, WriteHex;
 FROM Delay IMPORT Delay;
 
 VAR
     touche : CARDINAL;
     ecran : TigrPtr;
-    titre : String;
 
 BEGIN
-    titre := InitString("你好");
-    ecran := tigrWindow(320, 240, titre, 0);
+    ecran := tigrWindow(320, 240, "你好", 0);
     WHILE NOT (tigrClosed(ecran ) > 0) DO
         tigrClear(ecran, tigrRGB(80H, 90H, 0A0H));
         WriteHex(tigrReadChar(ecran), 7); WriteLn;

BIN
modula2/examples/clip/clip


BIN
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;
 
 FROM helper IMPORT tigrRGB, tigrRGBA;
-FROM DynamicStrings IMPORT String, InitString;
+FROM DynamicStrings IMPORT String, InitStringCharStar, CopyOut;
 FROM InOut IMPORT Write, WriteLn, WriteCard,WriteString;
 FROM Delay IMPORT Delay;
+FROM SYSTEM IMPORT ADDRESS;
 
-
+FROM SMathLib0 IMPORT exp;
     
 
 
@@ -21,46 +22,83 @@ VAR
     remaining           : SHORTREAL;
     backdrop, screen    : TigrPtr;    
     squinkle            : TigrPtr; 
-    greeting            : String;
+    greeting            : ARRAY[0..1023] OF CHAR;
+    greetingRaw         : ADDRESS;
+    greetingStr         : String;
     prevx, prevy, prev  : CARDINAL;
     chars               : ARRAY[0..16] OF CHAR;
-    tempString          : String;
     n                   : CARDINAL;
     dt                  : SHORTREAL;
     x, y ,b             : CARDINAL;
-    message, message1   : String;
     c                   : CARDINAL;
     playerx, playery,
-    playerxs, playerys  : INTEGER;
+    playerxs, playerys  : CARDINAL;
 
     PROCEDURE update (dt : SHORTREAL);
-
+    (* in Modula2, we cannot mix integer and real *)
     VAR
         oldx, oldy : INTEGER;
+        fplayerx, fplayery,
+        fplayerxs, fplayerys  : SHORTREAL;
+        fdx, fdy : SHORTREAL; 
+        foldx, foldy : SHORTREAL;
 
     BEGIN
         IF (remaining > 0.0) THEN 
             remaining := remaining - dt;
         END;
          (* 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
-            playerys := playerys - 200;
+            IF (playerys - 200) >= 0 THEN
+                playerys := playerys - 200;
+            ELSE
+               playerys := 0;
+            END; 
         END;
          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;
         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;
-        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;
 
 BEGIN
@@ -77,10 +115,12 @@ BEGIN
     END;
     
     (* 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");
     END;
+    greetingStr := InitStringCharStar(greetingRaw);
+    CopyOut(greeting, greetingStr);
     (* Make a window and an off-screen backdrop. *)
     screen := tigrWindow(320, 240, greeting, TIGR_2X);
     backdrop := tigrBitmap(screen^.w, screen^.h);
@@ -124,12 +164,11 @@ BEGIN
 
         (* Composite the backdrop and sprite onto the screen. *)
         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);
 
-        message := InitString("A D + SPACE");
         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. *)
         LOOP

BIN
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.

BIN
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.

BIN
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.

BIN
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.

BIN
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.

BIN
modula2/examples/unicode/update


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

@@ -1,9 +1,58 @@
 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
-    x,y,posx, posy  CARDINAL;
-    c : CARDINAL;
+    screen          : TigrPtr;
+    posx, posy      : INTEGER;
+    velx, vely      : INTEGER;
+    radius          : INTEGER;
+    w, h            : CARDINAL;
+    black, ball     : TPixelType;
 
 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.

BIN
modula2/helper.o


+ 38 - 16
modula2/tigr.def

@@ -2,14 +2,33 @@
 DEFINITION MODULE FOR "C" tigr;
 
 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
     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
  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. *)
 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) *)
 CONST
     TIGR_KEEP_ALPHA     =   0;
-    TIGR_BLEND_ALPHA    =   -1;  
+    TIGR_BLEND_ALPHA    =   1;  
 
  (* Set destination bitmap blend mode for blit operations. *)
 PROCEDURE tigrBlitMode(dest : TigrPtr; mode: CARDINAL);
@@ -212,13 +231,16 @@ TYPE
                             glyphs: POINTER TO TigrGlyphType;
                         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
     TigrGlyph:  TigrGlyphType;
     TigrFont:   TigrFontType;
-    TCodepage:  TCodepageType;
 
 
  (* Loads a font from a bitmap font sheet.
@@ -245,7 +267,7 @@ PROCEDURE tigrFreeFont(font: TigrFontTypePtr);
  NOTE:
   This uses the target bitmap blit mode.
   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. *)
 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.
  TIGR will automatically append a NUL terminator byte
  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.
  Returns non-zero on success. *)
 PROCEDURE tigrInflate(out: ADDRESS; outlen: CARDINAL; in: ADDRESS; inlen: CARDINAL);
 
  (* 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. *)
-PROCEDURE tigrEncodeUTF8(text: String; cp: INTEGER): String;
+PROCEDURE tigrEncodeUTF8(text: ADDRESS; cp: INTEGER): ADDRESS;
 
 END tigr.

BIN
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.

BIN
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)

BIN
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;
+}

BIN
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.

BIN
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;

BIN
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

BIN
tigr-master/tigr.o


BIN
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;
+}