From 39cfa35bfd309683d72395211b627b054e4f64f5 Mon Sep 17 00:00:00 2001 From: Martin Fouilleul Date: Sun, 14 Aug 2022 18:19:40 +0200 Subject: [PATCH] initial commit --- .gitignore | 3 + build.sh | 81 + examples/test_app/build.sh | 11 + examples/test_app/main.c | 279 + resources/Andale Mono.ttf | Bin 0 -> 109700 bytes resources/Andale Mono_old.ttf | Bin 0 -> 109700 bytes resources/CMUTypewriter-Regular.ttf | Bin 0 -> 316760 bytes resources/Courier.ttf | Bin 0 -> 283100 bytes resources/OpenSansLatinSubset.ttf | Bin 0 -> 29928 bytes src/graphics.c | 4100 +++++++++++++ src/graphics.h | 228 + src/graphics_internal.h | 90 + src/metal_painter.mm | 419 ++ src/metal_shader.h | 49 + src/metal_shader.metal | 320 + src/metal_surface.mm | 231 + src/milepost.c | 29 + src/milepost.h | 31 + src/milepost.mm | 12 + src/mp_app.h | 413 ++ src/osx_app.h | 58 + src/osx_app.mm | 2401 ++++++++ src/platform/linux_clock.c | 193 + src/platform/osx_clock.c | 176 + src/platform/platform_base_allocator.h | 28 + src/platform/platform_clock.h | 34 + src/platform/platform_rng.h | 18 + src/platform/platform_socket.h | 161 + src/platform/platform_thread.h | 88 + src/platform/posix_socket.c | 515 ++ src/platform/posix_thread.c | 241 + src/platform/unix_base_allocator.c | 38 + src/platform/unix_rng.c | 60 + src/stb_image.h | 7440 ++++++++++++++++++++++++ src/stb_truetype.h | 5011 ++++++++++++++++ src/ui.c | 1332 +++++ src/ui.h | 212 + src/util/debug_log.c | 123 + src/util/debug_log.h | 97 + src/util/hash.c | 124 + src/util/hash.h | 29 + src/util/lists.h | 390 ++ src/util/macro_helpers.h | 129 + src/util/memory.c | 129 + src/util/memory.h | 106 + src/util/ringbuffer.c | 94 + src/util/ringbuffer.h | 40 + src/util/strings.c | 298 + src/util/strings.h | 96 + src/util/typedefs.h | 70 + src/util/utf8.c | 283 + src/util/utf8.h | 201 + todo.txt | 115 + 53 files changed, 26626 insertions(+) create mode 100644 .gitignore create mode 100755 build.sh create mode 100755 examples/test_app/build.sh create mode 100644 examples/test_app/main.c create mode 100644 resources/Andale Mono.ttf create mode 100644 resources/Andale Mono_old.ttf create mode 100644 resources/CMUTypewriter-Regular.ttf create mode 100644 resources/Courier.ttf create mode 100644 resources/OpenSansLatinSubset.ttf create mode 100644 src/graphics.c create mode 100644 src/graphics.h create mode 100644 src/graphics_internal.h create mode 100644 src/metal_painter.mm create mode 100644 src/metal_shader.h create mode 100644 src/metal_shader.metal create mode 100644 src/metal_surface.mm create mode 100644 src/milepost.c create mode 100644 src/milepost.h create mode 100644 src/milepost.mm create mode 100644 src/mp_app.h create mode 100644 src/osx_app.h create mode 100644 src/osx_app.mm create mode 100644 src/platform/linux_clock.c create mode 100644 src/platform/osx_clock.c create mode 100644 src/platform/platform_base_allocator.h create mode 100644 src/platform/platform_clock.h create mode 100644 src/platform/platform_rng.h create mode 100644 src/platform/platform_socket.h create mode 100644 src/platform/platform_thread.h create mode 100644 src/platform/posix_socket.c create mode 100644 src/platform/posix_thread.c create mode 100644 src/platform/unix_base_allocator.c create mode 100644 src/platform/unix_rng.c create mode 100644 src/stb_image.h create mode 100644 src/stb_truetype.h create mode 100644 src/ui.c create mode 100644 src/ui.h create mode 100644 src/util/debug_log.c create mode 100644 src/util/debug_log.h create mode 100644 src/util/hash.c create mode 100644 src/util/hash.h create mode 100644 src/util/lists.h create mode 100644 src/util/macro_helpers.h create mode 100644 src/util/memory.c create mode 100644 src/util/memory.h create mode 100644 src/util/ringbuffer.c create mode 100644 src/util/ringbuffer.h create mode 100644 src/util/strings.c create mode 100644 src/util/strings.h create mode 100644 src/util/typedefs.h create mode 100644 src/util/utf8.c create mode 100644 src/util/utf8.h create mode 100644 todo.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..72d1402 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +bin/* +*.metallib \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..a13d04e --- /dev/null +++ b/build.sh @@ -0,0 +1,81 @@ +#!/bin/bash + +DEBUG_FLAGS="-g -DDEBUG -DLOG_COMPILE_DEBUG" +#DEBUG_FLAGS="-O3" + +#-------------------------------------------------------------- +# set target +#-------------------------------------------------------------- + +target="$1" +if [ -z $target ] ; then + target='lib' +fi + +shaderFlagParam="$2" +#-------------------------------------------------------------- +# Detect OS and set environment variables accordingly +#-------------------------------------------------------------- +OS=$(uname -s) + +if [ $OS = "Darwin" ] ; then + #echo "Target '$target' for macOS" + CC=clang + CXX=clang++ + DYLIB_SUFFIX='dylib' + SYS_LIBS='' + FLAGS="-mmacos-version-min=10.15.4 -DMG_IMPLEMENTS_BACKEND_METAL -maes" + CFLAGS="-std=c11" + +elif [ $OS = "Linux" ] ; then + echo "Error: Linux is not supported yet" + exit -1 +else + echo "Error: Unsupported OS $OS" + exit -1 +fi + +#-------------------------------------------------------------- +# Set paths +#-------------------------------------------------------------- +BINDIR="./bin" +SRCDIR="./src" +RESDIR="./resources" +INCLUDES="-I$SRCDIR -I$SRCDIR/util -I$SRCDIR/platform" + +#-------------------------------------------------------------- +# Build +#-------------------------------------------------------------- + +if [ ! \( -e bin \) ] ; then + mkdir ./bin +fi + +if [ $target = 'lib' ] ; then + + # compile metal shader + xcrun -sdk macosx metal $shaderFlagParam -c -o $BINDIR/metal_shader.air $SRCDIR/metal_shader.metal + xcrun -sdk macosx metallib -o $RESDIR/metal_shader.metallib $BINDIR/metal_shader.air + + # compile milepost. We use one compilation unit for all C code, and one compilation + # unit for all ObjectiveC code + $CC $DEBUG_FLAGS -c -o $BINDIR/milepost_c.o $CFLAGS $FLAGS $INCLUDES $SRCDIR/milepost.c + $CC $DEBUG_FLAGS -c -o $BINDIR/milepost_objc.o $FLAGS $INCLUDES $SRCDIR/milepost.mm + + # build the static library + libtool -static -o $BINDIR/libmilepost.a $BINDIR/milepost_c.o $BINDIR/milepost_objc.o + +else + # additional targets + if [ $target = 'test' ] ; then + pushd examples/test_app + ./build.sh + popd + + elif [ $target = 'clean' ] ; then + rm -r ./bin + else + echo "unrecognized target $target" + exit -1 + fi +fi diff --git a/examples/test_app/build.sh b/examples/test_app/build.sh new file mode 100755 index 0000000..e62fb81 --- /dev/null +++ b/examples/test_app/build.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +BINDIR=../../bin +RESDIR=../../resources +SRCDIR=../../src + +INCLUDES="-I$SRCDIR -I$SRCDIR/util -I$SRCDIR/platform -I$SRCDIR/app" +LIBS="-L$BINDIR -lmilepost -framework Carbon -framework Cocoa -framework Metal -framework QuartzCore" +FLAGS="-mmacos-version-min=10.15.4 -DDEBUG -DLOG_COMPILE_DEBUG" + +clang -g $FLAGS $LIBS $INCLUDES -o $BINDIR/test_app main.c diff --git a/examples/test_app/main.c b/examples/test_app/main.c new file mode 100644 index 0000000..bd4f976 --- /dev/null +++ b/examples/test_app/main.c @@ -0,0 +1,279 @@ +/************************************************************//** +* +* @file: main.cpp +* @author: Martin Fouilleul +* @date: 30/07/2022 +* @revision: +* +*****************************************************************/ +#include +#include"milepost.h" + +#define LOG_SUBSYSTEM "Main" + +int main() +{ + LogLevel(LOG_LEVEL_DEBUG); + + mp_init(); + + mp_rect rect = {.x = 100, .y = 100, .w = 800, .h = 600}; + mp_window window = mp_window_create(rect, "test", 0); + + mg_init(); + ui_init(); +/* + mp_rect frame = {0, 0, 800, 600}; + mp_view view = mp_view_create(window, frame); + mg_surface surface = mg_surface_create_for_view(view, MG_BACKEND_METAL); +/*/ + mg_surface surface = mg_surface_create_for_window(window, MG_BACKEND_METAL); +//*/ + mg_canvas canvas = mg_canvas_create(surface, (mp_rect){0, 0, 800, 600}); + mg_image image = mg_image_create_from_file(canvas, str8_lit("Top512.png"), true); + + u8 colors[64]; + for(int i=0; i<64; i+=4) + { + colors[i] = 255; + colors[i+1] = 0; + colors[i+2] = 0; + colors[i+3] = 255; + } + mg_image image3 = mg_image_create_from_rgba8(canvas, 4, 4, colors); + + mg_image image2 = mg_image_create_from_file(canvas, str8_lit("triceratops.png"), true); + + //NOTE(martin): create font + char* fontPath = 0; + mp_app_get_resource_path("../resources/Andale Mono.ttf", &fontPath); + FILE* fontFile = fopen(fontPath, "r"); + free(fontPath); + if(!fontFile) + { + LOG_ERROR("Could not load font file '%s'\n", fontPath); + return(-1); + } + unsigned char* fontData = 0; + fseek(fontFile, 0, SEEK_END); + u32 fontDataSize = ftell(fontFile); + rewind(fontFile); + fontData = (unsigned char*)malloc(fontDataSize); + fread(fontData, 1, fontDataSize, fontFile); + fclose(fontFile); + + unicode_range ranges[5] = {UNICODE_RANGE_BASIC_LATIN, + UNICODE_RANGE_C1_CONTROLS_AND_LATIN_1_SUPPLEMENT, + UNICODE_RANGE_LATIN_EXTENDED_A, + UNICODE_RANGE_LATIN_EXTENDED_B, + UNICODE_RANGE_SPECIALS}; + + mg_font font = mg_font_create_from_memory(fontDataSize, fontData, 5, ranges); + free(fontData); + + mp_window_bring_to_front_and_focus(window); + + mp_pump_events(-1); + while(!mp_should_quit()) + { + mp_event event = {}; + mp_pump_events(0); + while(mp_next_event(&event)) + { + switch(event.type) + { + case MP_EVENT_KEYBOARD_CHAR: + { + printf("entered char %s\n", event.character.sequence); + } break; + + case MP_EVENT_WINDOW_CLOSE: + { + mp_do_quit(); + } break; + + case MP_EVENT_WINDOW_RESIZE: + { + /* + mp_rect frame = {0, 0, event.frame.rect.w, event.frame.rect.h}; + mp_view_set_frame(view, frame); + */ + mg_canvas_viewport(canvas, (mp_rect){0, 0, event.frame.rect.w, event.frame.rect.h}); + + } break; + + case MP_EVENT_FRAME: + { + } break; + + default: + break; + } + } + mg_surface_prepare(surface); + + mg_set_color_rgba(canvas, 1, 1, 1, 1); + mg_clear(canvas); + + mg_move_to(canvas, 0, 0); + mg_line_to(canvas, 400, 300); + mg_set_width(canvas, 5); + mg_set_color_rgba(canvas, 1, 0, 0, 1); + mg_stroke(canvas); + + mg_image_draw(canvas, image, (mp_rect){400, 300, 200, 200}); + + mg_rounded_image_draw(canvas, image2, (mp_rect){200, 20, 200, 200}, 50); + + mg_set_color_rgba(canvas, 0, 1, 0, 0.5); + mg_rectangle_fill(canvas, 800, 800, 400, 200); + + mg_set_font(canvas, font); + mg_set_font_size(canvas, 32); + mg_move_to(canvas, 500, 500); + mg_text_outlines(canvas, str8_lit("Hello, world!")); + mg_set_color_rgba(canvas, 0, 0, 0, 1); + mg_fill(canvas); + + mp_rect windowRect = mp_window_get_content_rect(window); + + ui_style defaultStyle = {.borderSize = 2, + .borderColor = {0, 0, 1, 1}, + .textColor = {0, 0, 0, 1}, + .font = font, + .fontSize = 32}; + + ui_style buttonStyle[UI_STYLE_SELECTOR_COUNT]; + for(int i=0; i1RWJ+YaSs&2H|sOghZu3ykeR$>qny3pn{O+dKv0@{+E_% z-x3lSB%W5^#HmJU&oAIU_22;JDW2x1IPxcT`mhFG!y0Hj8BXpY5_*wIH0h5zqv{A? z%?K4w)6KY|ksgt}xOH^32?(lM7c&Og$|?czUyw{BhFu#v^`KvC#nb2 zV}*n~e(`Lmfq;=R^hVp-&=VoOSg(%pjmLR#td!`<_)wV}1!C!_7dy3)NEj&# zwNVirSsE&;f>0AG>&V=W zzVxJ*@TX9{fs}QW!^srUqSWg;%~8+CFD_8(4V~)4d07m?L`FGW+@!RzpllW&Q_9v( z?QJN_A-F|!s*glDTzpPxAI;l@o*C1rOG_6E^@h;+ zb7qy!S~6=@NwKrIXi1TC#)5?_7tbm!TjER}(#_eYUq5e895S5a%1WH07R+C;WaYvV z=a2=97cN*_v}D$T`MsQj=FW90ca%F9mz0+*URF}vYta1SqPf8voMTE#m(DF(JfUQ9 zIodl@d-d+UB6yuYFIaloIjh`RI-#i)WP=&ziwUSl)}=MHZ07WF8QHE}2hO;%quuNyA79 znT_)=k$$~CmMlTL`9Sd^+*2$(A{-E25ne@lQFuvshUAc0q!eih(keWmm^hWT{4OV% zfhR3Q4~vynWoYFj$z%xWhP)5yhp(6P43#oaHx2|=f_xP2v>k+5%tiidALYtf2`R^&i^(!&RK3tHIE%Tz>7g9YFTyy3BcFwPoXW_{LL;7w z5jgSu5@nXtm68*%3y$x%ch6L6mta)?xi2RfqdaFRp2DFy0cVTR<1FPqCrQPfz42Y~ zoBP{g(dn74c#P@Su`a<=iU9QzWla~8Ik>uj%*65k7M@PB94jyj@F>IG97Fi*r{l>S zzCjq21Nwa2?GT%fYaD)yQD206`T1-xN4_5A7!`jS#|{p$a?G%OKEJN*IAxU=0K$A8 z3(@yV!tt&|Sv&r`G0IAh!qp{8eYx^9j!#R_{tl9Y?{ekat7C22pWh4pFFCv-SO7A|Y-o9z+8JYd==$|!UV7712;2}A=Lx<%JA2IUIQFq-v`kpak$BmybF@MtJ zd#4mkomN;xrq3uYnORykYxbPE^X4yDxM*?tlBLU*uUNV2zWXa4s9e3~!K$^@f2gTl zx4v#e{l-n3w>;GFaO2i(P0f!yy8W>okMDe9*X}(}KDBq>AD{lyGtcgS&VS(fgNI%? zeB|hhFa7!DSB|}U{4cM)e&UTcPo8?~uW$d)>380J?|t&O4?aBe(b;n!|NS55FMM+G z5)q|XO!{T$fGUzj9-x)e%m{HfO9e z-r3dJ+v#=Q;mmRlbmllKoqL>nozJ)=m&Fy~ig$H&^>R&hHM;i2vv^g!G2R?+i;s$r zi|-ciiO-8KiZ5|9x9oN$l0=qhNR$(;iH^iBi3y2468k1*CC*J;owzo!HgQ8@L*f&O z&m=yd_)_A_i6;`@PJB1<x`~5yUw{G{lcK_5aJ{|G5Vj`n;88$ zjD9UfzfssNQZZb-N1Q5dj$Ix5ozvp9JDtktQ=R=fM!);tMt`qsbI0gS7=2`XY-sd_ z@x{vMo&RO@qdP{wIdNy=(;cJ#D@K0~qranL^d((q-w=O+uGaQBA#GN$MG}w+Y5lIX zrS*F2*R5Z*9&J6+dbst4)@njNpBY5YOVyXQein79`ZMikF_%_+dXkV&cV04mI{eeD zPw%`q`{K$^3qNI-KDhLkPqu!t>ytV{KH1CfyA=1yqEDvbtoOydi~TQ-ybygM>VoM) z_yzq1%?0Uv;QUYLZ=C<~{72_o&VP0O>iNs(Kjn9wf8+eI^RJvgjM6LT_nhB*KIi

?WV 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℘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

>7EyB2eK_o`kkYsi~Pm&tX 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 MZ87Ow4J^Zd0fuWB)c^nh literal 0 HcmV?d00001 diff --git a/resources/Andale Mono_old.ttf b/resources/Andale Mono_old.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e766a6e18badf3aa063cce94ee2268d89b02a13d GIT binary patch literal 109700 zcmce1RWJ+YaSs&2H|sOghZu3ykeR$>qny3pn{O+dKv0@{+E_% z-x3lSB%W5^#HmJU&oAIU_22;JDW2x1IPxcT`mhFG!y0Hj8BXpY5_*wIH0h5zqv{A? z%?K4w)6KY|ksgt}xOH^32?(lM7c&Og$|?czUyw{BhFu#v^`KvC#nb2 zV}*n~e(`Lmfq;=R^hVp-&=VoOSg(%pjmLR#td!`<_)wV}1!C!_7dy3)NEj&# zwNVirSsE&;f>0AG>&V=W zzVxJ*@TX9{fs}QW!^srUqSWg;%~8+CFD_8(4V~)4d07m?L`FGW+@!RzpllW&Q_9v( z?QJN_A-F|!s*glDTzpPxAI;l@o*C1rOG_6E^@h;+ zb7qy!S~6=@NwKrIXi1TC#)5?_7tbm!TjER}(#_eYUq5e895S5a%1WH07R+C;WaYvV z=a2=97cN*_v}D$T`MsQj=FW90ca%F9mz0+*URF}vYta1SqPf8voMTE#m(DF(JfUQ9 zIodl@d-d+UB6yuYFIaloIjh`RI-#i)WP=&ziwUSl)}=MHZ07WF8QHE}2hO;%quuNyA79 znT_)=k$$~CmMlTL`9Sd^+*2$(A{-E25ne@lQFuvshUAc0q!eih(keWmm^hWT{4OV% zfhR3Q4~vynWoYFj$z%xWhP)5yhp(6P43#oaHx2|=f_xP2v>k+5%tiidALYtf2`R^&i^(!&RK3tHIE%Tz>7g9YFTyy3BcFwPoXW_{LL;7w z5jgSu5@nXtm68*%3y$x%ch6L6mta)?xi2RfqdaFRp2DFy0cVTR<1FPqCrQPfz42Y~ zoBP{g(dn74c#P@Su`a<=iU9QzWla~8Ik>uj%*65k7M@PB94jyj@F>IG97Fi*r{l>S zzCjq21Nwa2?GT%fYaD)yQD206`T1-xN4_5A7!`jS#|{p$a?G%OKEJN*IAxU=0K$A8 z3(@yV!tt&|Sv&r`G0IAh!qp{8eYx^9j!#R_{tl9Y?{ekat7C22pWh4pFFCv-SO7A|Y-o9z+8JYd==$|!UV7712;2}A=Lx<%JA2IUIQFq-v`kpak$BmybF@MtJ zd#4mkomN;xrq3uYnORykYxbPE^X4yDxM*?tlBLU*uUNV2zWXa4s9e3~!K$^@f2gTl zx4v#e{l-n3w>;GFaO2i(P0f!yy8W>okMDe9*X}(}KDBq>AD{lyGtcgS&VS(fgNI%? zeB|hhFa7!DSB|}U{4cM)e&UTcPo8?~uW$d)>380J?|t&O4?aBe(b;n!|NS55FMM+G z5)q|XO!{T$fGUzj9-x)e%m{HfO9e z-r3dJ+v#=Q;mmRlbmllKoqL>nozJ)=m&Fy~ig$H&^>R&hHM;i2vv^g!G2R?+i;s$r zi|-ciiO-8KiZ5|9x9oN$l0=qhNR$(;iH^iBi3y2468k1*CC*J;owzo!HgQ8@L*f&O z&m=yd_)_A_i6;`@PJB1<x`~5yUw{G{lcK_5aJ{|G5Vj`n;88$ zjD9UfzfssNQZZb-N1Q5dj$Ix5ozvp9JDtktQ=R=fM!);tMt`qsbI0gS7=2`XY-sd_ z@x{vMo&RO@qdP{wIdNy=(;cJ#D@K0~qranL^d((q-w=O+uGaQBA#GN$MG}w+Y5lIX zrS*F2*R5Z*9&J6+dbst4)@njNpBY5YOVyXQein79`ZMikF_%_+dXkV&cV04mI{eeD zPw%`q`{K$^3qNI-KDhLkPqu!t>ytV{KH1CfyA=1yqEDvbtoOydi~TQ-ybygM>VoM) z_yzq1%?0Uv;QUYLZ=C<~{72_o&VP0O>iNs(Kjn9wf8+eI^RJvgjM6LT_nhB*KIi

?WV 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℘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

>7EyB2eK_o`kkYsi~Pm&tX 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 MZ87Ow4J^Zd0fuWB)c^nh literal 0 HcmV?d00001 diff --git a/resources/CMUTypewriter-Regular.ttf b/resources/CMUTypewriter-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1a9fd6ce72619277ee81d8b115784ce3962b06d9 GIT binary patch literal 316760 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 literal 0 HcmV?d00001 diff --git a/resources/Courier.ttf b/resources/Courier.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d71bf9ffb71cace7e39cbb3181117710e848d068 GIT binary patch literal 283100 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

RUvepirEAhi$L)TGd^=saWP)B3t*~FF@8M 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�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 z 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 Nq^@UM{QsPH{~!GB{!jn_ literal 0 HcmV?d00001 diff --git a/src/graphics.c b/src/graphics.c new file mode 100644 index 0000000..40ad234 --- /dev/null +++ b/src/graphics.c @@ -0,0 +1,4100 @@ +/************************************************************//** +* +* @file: graphics.cpp +* @author: Martin Fouilleul +* @date: 01/08/2022 +* @revision: +* +*****************************************************************/ +#include + +#define STB_TRUETYPE_IMPLEMENTATION +#include"stb_truetype.h" + +#include"lists.h" +#include"memory.h" +#include"macro_helpers.h" +#include"graphics_internal.h" + +#define LOG_SUBSYSTEM "Graphics" + +//--------------------------------------------------------------- +// graphics canvas structs +//--------------------------------------------------------------- + +typedef enum { MG_PATH_MOVE, + MG_PATH_LINE, + MG_PATH_QUADRATIC, + MG_PATH_CUBIC } mg_path_elt_type; + +typedef struct mg_path_elt +{ + mg_path_elt_type type; + vec2 p[3]; + +} mg_path_elt; + +typedef struct mg_path_descriptor +{ + u32 startIndex; + u32 count; + vec2 startPoint; + +} mg_path_descriptor; + +typedef struct mg_attributes +{ + f32 width; + f32 tolerance; + mg_color color; + mg_joint_type joint; + f32 maxJointExcursion; + mg_cap_type cap; + + mg_font font; + f32 fontSize; + + mg_image image; + + mp_rect clip; + +} mg_attributes; + +typedef struct mg_rounded_rect +{ + f32 x; + f32 y; + f32 w; + f32 h; + f32 r; +} mg_rounded_rect; + +typedef enum { MG_CMD_CLEAR = 0, + MG_CMD_FILL, + MG_CMD_STROKE, + MG_CMD_RECT_FILL, + MG_CMD_RECT_STROKE, + MG_CMD_ROUND_RECT_FILL, + MG_CMD_ROUND_RECT_STROKE, + MG_CMD_ELLIPSE_FILL, + MG_CMD_ELLIPSE_STROKE, + MG_CMD_JUMP, + MG_CMD_MATRIX_PUSH, + MG_CMD_MATRIX_POP, + MG_CMD_CLIP_PUSH, + MG_CMD_CLIP_POP, + MG_CMD_IMAGE_DRAW, + MG_CMD_ROUNDED_IMAGE_DRAW, + } mg_primitive_cmd; + +typedef struct mg_primitive +{ + mg_primitive_cmd cmd; + mg_attributes attributes; + + union + { + mg_path_descriptor path; + mp_rect rect; + mg_rounded_rect roundedRect; + utf32 codePoint; + u32 jump; + mg_mat2x3 matrix; + }; + +} mg_primitive; + +typedef struct mg_glyph_map_entry +{ + unicode_range range; + u32 firstGlyphIndex; + +} mg_glyph_map_entry; + +typedef struct mg_glyph_info +{ + bool exists; + utf32 codePoint; + mg_path_descriptor pathDescriptor; + mg_text_extents extents; + //... + +} mg_glyph_info; + +const u32 MG_STREAM_MAX_COUNT = 128; +const u32 MG_IMAGE_MAX_COUNT = 128; + +typedef struct mg_stream_data +{ + list_elt freeListElt; + u32 generation; + u64 frame; + + u32 firstCommand; + u32 lastCommand; + u32 count; + + bool pendingJump; + +} mg_stream_data; + +typedef struct mg_image_data +{ + list_elt listElt; + u32 generation; + + mp_rect rect; + +} mg_image_data; + +const u32 MG_MATRIX_STACK_MAX_DEPTH = 64; +const u32 MG_CLIP_STACK_MAX_DEPTH = 64; +const u32 MG_MAX_PATH_ELEMENT_COUNT = 2<<20; +const u32 MG_MAX_PRIMITIVE_COUNT = 8<<10; + +typedef struct mg_canvas_data +{ + list_elt freeListElt; + u32 generation; + + mg_stream_data streams[MG_STREAM_MAX_COUNT]; + list_info streamsFreeList; + u64 frameCounter; + + mg_stream_data* currentStream; + + mg_mat2x3 transform; + mp_rect clip; + + mg_attributes attributes; + bool textFlip; + mg_path_elt pathElements[MG_MAX_PATH_ELEMENT_COUNT]; + mg_path_descriptor path; + vec2 subPathStartPoint; + vec2 subPathLastPoint; + + mg_mat2x3 matrixStack[MG_MATRIX_STACK_MAX_DEPTH]; + u32 matrixStackSize; + + mp_rect clipStack[MG_CLIP_STACK_MAX_DEPTH]; + u32 clipStackSize; + + u32 nextZIndex; + u32 primitiveCount; + mg_primitive primitives[MG_MAX_PRIMITIVE_COUNT]; + + u32 vertexCount; + u32 indexCount; + + mg_canvas_painter* painter; + + mg_image_data images[MG_IMAGE_MAX_COUNT]; + u32 imageNextIndex; + list_info imageFreeList; + + vec2 atlasPos; + u32 atlasLineHeight; + mg_image blankImage; + +} mg_canvas_data; + +typedef struct mg_font_info +{ + list_elt freeListElt; + u32 generation; + + u32 rangeCount; + u32 glyphCount; + u32 outlineCount; + mg_glyph_map_entry* glyphMap; + mg_glyph_info* glyphs; + mg_path_elt* outlines; + + f32 unitsPerEm; + mg_font_extents extents; + +} mg_font_info; + +//--------------------------------------------------------------- +// internal handle system +//--------------------------------------------------------------- + +const u32 MG_MAX_SURFACES = 256, + MG_MAX_CONTEXTS = 256, + MG_FONT_MAX_COUNT = 256; + +typedef struct mg_surface_slot +{ + list_elt freeListElt; + u32 generation; + mg_surface_info* surface; + +} mg_surface_slot; + +typedef struct mg_info +{ + bool init; + list_info surfaceFreeList; + list_info contextFreeList; + list_info fontFreeList; + u32 fontsNextIndex; + mg_surface_slot surfaceSlots[MG_MAX_SURFACES]; + mg_canvas_data contexts[MG_MAX_CONTEXTS]; + mg_font_info fonts[MG_FONT_MAX_COUNT]; + +} mg_info; + +static mg_info __mgInfo = {0}; + +void mg_init() +{ + if(!__mgInfo.init) + { + ListInit(&__mgInfo.surfaceFreeList); + for(int i=0; igeneration == UINT32_MAX) + { + LOG_ERROR("surface slot generation wrap around\n"); + } + #endif + slot->generation++; + ListPush(&__mgInfo.surfaceFreeList, &slot->freeListElt); +} + +mg_surface_slot* mg_surface_slot_from_handle(mg_surface surface) +{ + u32 index = surface.h>>32; + u32 generation = surface.h & 0xffffffff; + if(index >= MG_MAX_SURFACES) + { + return(0); + } + mg_surface_slot* slot = &__mgInfo.surfaceSlots[index]; + if(slot->generation != generation) + { + return(0); + } + else + { + return(slot); + } +} + +mg_surface_info* mg_surface_ptr_from_handle(mg_surface surface) +{ + mg_surface_slot* slot = mg_surface_slot_from_handle(surface); + if(slot) + { + return(slot->surface); + } + else + { + return(0); + } +} + +mg_surface mg_surface_handle_from_slot(mg_surface_slot* slot) +{ + DEBUG_ASSERT( (slot - __mgInfo.surfaceSlots) >= 0 + && (slot - __mgInfo.surfaceSlots) < MG_MAX_SURFACES); + + u64 h = ((u64)(slot - __mgInfo.surfaceSlots))<<32 + |((u64)(slot->generation)); + return((mg_surface){.h = h}); +} + +mg_surface mg_surface_alloc_handle(mg_surface_info* surface) +{ + mg_surface_slot* slot = mg_surface_slot_alloc(); + slot->surface = surface; + mg_surface handle = mg_surface_handle_from_slot(slot); + return(handle); +} + +mg_surface mg_surface_nil() +{ + return((mg_surface){.h = 0}); +} + +mg_canvas_data* mg_canvas_alloc() +{ + return(ListPopEntry(&__mgInfo.contextFreeList, mg_canvas_data, freeListElt)); +} + +void mg_canvas_recycle(mg_canvas_data* context) +{ + #ifdef DEBUG + if(context->generation == UINT32_MAX) + { + LOG_ERROR("graphics context generation wrap around\n"); + } + #endif + context->generation++; + ListPush(&__mgInfo.contextFreeList, &context->freeListElt); +} + +mg_canvas_data* mg_canvas_ptr_from_handle(mg_canvas handle) +{ + u32 index = handle.h>>32; + u32 generation = handle.h & 0xffffffff; + if(index >= MG_MAX_CONTEXTS) + { + return(0); + } + mg_canvas_data* context = &__mgInfo.contexts[index]; + if(context->generation != generation) + { + return(0); + } + else + { + return(context); + } +} + +mg_canvas mg_canvas_handle_from_ptr(mg_canvas_data* context) +{ + DEBUG_ASSERT( (context - __mgInfo.contexts) >= 0 + && (context - __mgInfo.contexts) < MG_MAX_CONTEXTS); + + u64 h = ((u64)(context - __mgInfo.contexts))<<32 + |((u64)(context->generation)); + return((mg_canvas){.h = h}); +} + +mg_canvas mg_canvas_nil() +{ + return((mg_canvas){.h = 0}); +} + +mg_image_data* mg_image_ptr_from_handle(mg_canvas_data* context, mg_image handle) +{ + u32 index = handle.h>>32; + u32 generation = handle.h & 0xffffffff; + + if(index >= MG_IMAGE_MAX_COUNT) + { + return(0); + } + mg_image_data* image = &context->images[index]; + if(image->generation != generation) + { + return(0); + } + else + { + return(image); + } +} + +//--------------------------------------------------------------- +// graphics surface API +//--------------------------------------------------------------- + +#ifdef MG_IMPLEMENTS_BACKEND_METAL +//NOTE: function is defined in metal_backend.mm +mg_surface mg_metal_surface_create_for_window(mp_window window); +mg_surface mg_metal_surface_create_for_view(mp_view view); +#endif //MG_IMPLEMENTS_BACKEND_METAL + +void mg_init(); + +mg_surface mg_surface_create_for_window(mp_window window, mg_backend_id backend) +{ + DEBUG_ASSERT(__mgInfo.init); + + mg_surface surface = mg_surface_nil(); + + switch(backend) + { + #ifdef MG_IMPLEMENTS_BACKEND_METAL + case MG_BACKEND_METAL: + surface = mg_metal_surface_create_for_window(window); + break; + #endif + + //... + + default: + break; + } + + return(surface); +} + +mg_surface mg_surface_create_for_view(mp_view view, mg_backend_id backend) +{ + DEBUG_ASSERT(__mgInfo.init); + + mg_surface surface = mg_surface_nil(); + + switch(backend) + { + #ifdef MG_IMPLEMENTS_BACKEND_METAL + case MG_BACKEND_METAL: + surface = mg_metal_surface_create_for_view(view); + break; + #endif + + //... + + default: + break; + } + + return(surface); +} + + +void mg_surface_destroy(mg_surface handle) +{ + DEBUG_ASSERT(__mgInfo.init); + + mg_surface_slot* slot = mg_surface_slot_from_handle(handle); + if(slot) + { + slot->surface->destroy(slot->surface); + mg_surface_slot_recycle(slot); + } +} + +void mg_surface_resize(mg_surface surface, int width, int height) +{ + DEBUG_ASSERT(__mgInfo.init); + + mg_surface_info* surfaceInfo = mg_surface_ptr_from_handle(surface); + if(surfaceInfo) + { + surfaceInfo->resize(surfaceInfo, width, height); + } +} + +void mg_surface_prepare(mg_surface surface) +{ + DEBUG_ASSERT(__mgInfo.init); + + mg_surface_info* surfaceInfo = mg_surface_ptr_from_handle(surface); + if(surfaceInfo) + { + surfaceInfo->prepare(surfaceInfo); + } +} + +void mg_surface_present(mg_surface surface) +{ + DEBUG_ASSERT(__mgInfo.init); + + mg_surface_info* surfaceInfo = mg_surface_ptr_from_handle(surface); + if(surfaceInfo) + { + surfaceInfo->present(surfaceInfo); + } +} + +vec2 mg_surface_size(mg_surface surface) +{ + DEBUG_ASSERT(__mgInfo.init); + vec2 res = {}; + mg_surface_info* surfaceInfo = mg_surface_ptr_from_handle(surface); + if(surfaceInfo) + { + res = surfaceInfo->getSize(surfaceInfo); + } + return(res); +} + + +//--------------------------------------------------------------- +// graphics stream handles +//--------------------------------------------------------------- +/* + Graphics command stream handles are invalidated when the command stream is appended to the current stream, + and at the end of the frame. + Thus command streams handles contain the index of the stream, the frame of the stream, and a generation + count inside that frame: + + 0 40 56 64 + +---------------------------------+------------------------+------------+ + | frameCounter | generation | index | + +---------------------------------+------------------------+------------+ + 40 bits 16 bits 8 bits + + (This gives use 2^40 frames, ie ~600 years at 60Hz, 65536 possible reuse per frames and max 256 simultaneous streams.) + + This way we can invalidate use of old handles in the same frame by incrementing the generation counter of the recycled stream, + and we can invalidate all previous handles at the end of a frame by just incrementing the frame counter of the graphics context. +*/ +mg_stream_data* mg_stream_alloc(mg_canvas_data* context) +{ + mg_stream_data* stream = ListPopEntry(&context->streamsFreeList, mg_stream_data, freeListElt); + if(stream) + { + stream->frame = context->frameCounter; + stream->pendingJump = false; + stream->count = 0; + stream->firstCommand = 0; + stream->lastCommand = 0; + } + return(stream); +} + +void mg_stream_recycle(mg_canvas_data* context, mg_stream_data* stream) +{ + #ifdef DEBUG + if(stream->generation == UINT32_MAX) + { + LOG_ERROR("graphics command stream generation wrap around\n"); + } + #endif + stream->generation++; + ListPush(&context->streamsFreeList, &stream->freeListElt); +} + +mg_stream_data* mg_stream_ptr_from_handle(mg_canvas_data* context, mg_stream handle) +{ + u32 index = handle.h>>56; + u32 generation = (handle.h>>40) & 0xffff; + u64 frame = handle.h & 0xffffffffff; + + if(index >= MG_STREAM_MAX_COUNT) + { + return(0); + } + mg_stream_data* stream = &context->streams[index]; + if( stream->generation != generation + ||stream->frame != context->frameCounter) + { + return(0); + } + else + { + return(stream); + } +} + +mg_stream mg_stream_handle_from_ptr(mg_canvas_data* context, mg_stream_data* stream) +{ + DEBUG_ASSERT( (stream - context->streams) >= 0 + && (stream - context->streams) < MG_STREAM_MAX_COUNT); + + u64 h = ((u64)(stream - context->streams))<<56 + |((u64)(stream->generation))<<40 + |((u64)(context->frameCounter)); + + return((mg_stream){.h = h}); +} + +mg_stream mg_stream_null_handle() +{ + return((mg_stream){.h = 0}); +} + +//--------------------------------------------------------------- +// internal graphics context functions +//--------------------------------------------------------------- + +mg_mat2x3 mg_mat2x3_mul_m(mg_mat2x3 lhs, mg_mat2x3 rhs) +{ + mg_mat2x3 res; + res.m[0] = lhs.m[0]*rhs.m[0] + lhs.m[1]*rhs.m[3]; + res.m[1] = lhs.m[0]*rhs.m[1] + lhs.m[1]*rhs.m[4]; + res.m[2] = lhs.m[0]*rhs.m[2] + lhs.m[1]*rhs.m[5] + lhs.m[2]; + res.m[3] = lhs.m[3]*rhs.m[0] + lhs.m[4]*rhs.m[3]; + res.m[4] = lhs.m[3]*rhs.m[1] + lhs.m[4]*rhs.m[4]; + res.m[5] = lhs.m[3]*rhs.m[2] + lhs.m[4]*rhs.m[5] + lhs.m[5]; + + return(res); +} + +vec2 mg_mat2x3_mul(mg_mat2x3 m, vec2 p) +{ + f32 x = p.x*m.m[0] + p.y*m.m[1] + m.m[2]; + f32 y = p.x*m.m[3] + p.y*m.m[4] + m.m[5]; + return((vec2){x, y}); +} + +void mg_reset_z_index(mg_canvas_data* context) +{ + context->nextZIndex = 1; +} + +u32 mg_get_next_z_index(mg_canvas_data* context) +{ + return(context->nextZIndex++); +} + +/////////////////////////////////////// WIP ///////////////////////////////////////////////////////////////////////// +u32 mg_vertices_base_index(mg_canvas_data* context) +{ + return(context->vertexCount); +} + +void mg_push_textured_vertex(mg_canvas_data* context, vec2 pos, vec4 cubic, vec2 uv, mg_color color, u64 zIndex) +{ + mgc_vertex_layout* layout = &context->painter->vertexLayout; + + DEBUG_ASSERT(context->vertexCount < layout->maxVertexCount); + + u32 offset = context->vertexCount; + + *(vec2*)(((char*)layout->posBuffer) + offset*layout->posStride) = pos; + *(vec4*)(((char*)layout->cubicBuffer) + offset*layout->cubicStride) = cubic; + *(vec2*)(((char*)layout->uvBuffer) + offset*layout->uvStride) = uv; + *(mg_color*)(((char*)layout->colorBuffer) + offset*layout->colorStride) = color; + *(u32*)(((char*)layout->zIndexBuffer) + offset*layout->zIndexStride) = zIndex; + *(mp_rect*)(((char*)layout->clipsBuffer) + offset*layout->clipsStride) = context->clip; + + context->vertexCount++; +} + +void mg_push_vertex(mg_canvas_data* context, vec2 pos, vec4 cubic, mg_color color, u64 zIndex) +{ + mg_push_textured_vertex(context, pos, cubic, (vec2){0, 0}, color, zIndex); +} + + +/////////////////////////////////////// WIP ///////////////////////////////////////////////////////////////////////// + +int* mg_reserve_indices(mg_canvas_data* context, u32 indexCount) +{ + mgc_vertex_layout* layout = &context->painter->vertexLayout; + + ASSERT(context->indexCount + indexCount < layout->maxIndexCount); + int* base = ((int*)layout->indexBuffer) + context->indexCount; + context->indexCount += indexCount; + return(base); +} + +//----------------------------------------------------------------------------------------------------------- +// Path Filling +//----------------------------------------------------------------------------------------------------------- +//NOTE(martin): forward declarations +void mg_render_fill_cubic(mg_canvas_data* context, vec2 p[4], u32 zIndex, mg_color color); + +//NOTE(martin): quadratics filling + +void mg_render_fill_quadratic(mg_canvas_data* context, vec2 p[3], u32 zIndex, mg_color color) +{ + u32 baseIndex = mg_vertices_base_index(context); + + i32* indices = mg_reserve_indices(context, 3); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, (vec2){p[0].x, p[0].y}), + (vec4){0, 0, 0, 1}, + color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, (vec2){p[1].x, p[1].y}), + (vec4){0.5, 0, 0.5, 1}, + color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, (vec2){p[2].x, p[2].y}), + (vec4){1, 1, 1, 1}, + color, + zIndex); + + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; +} + +//NOTE(martin): cubic filling + +void mg_split_and_fill_cubic(mg_canvas_data* context, vec2 p[4], f32 tSplit, u32 zIndex, mg_color color) +{ + int subVertexCount = 0; + int subIndexCount = 0; + + f32 OneMinusTSplit = 1-tSplit; + + vec2 q0 = {OneMinusTSplit*p[0].x + tSplit*p[1].x, + OneMinusTSplit*p[0].y + tSplit*p[1].y}; + + vec2 q1 = {OneMinusTSplit*p[1].x + tSplit*p[2].x, + OneMinusTSplit*p[1].y + tSplit*p[2].y}; + + vec2 q2 = {OneMinusTSplit*p[2].x + tSplit*p[3].x, + OneMinusTSplit*p[2].y + tSplit*p[3].y}; + + vec2 r0 = {OneMinusTSplit*q0.x + tSplit*q1.x, + OneMinusTSplit*q0.y + tSplit*q1.y}; + + vec2 r1 = {OneMinusTSplit*q1.x + tSplit*q2.x, + OneMinusTSplit*q1.y + tSplit*q2.y}; + + vec2 split = {OneMinusTSplit*r0.x + tSplit*r1.x, + OneMinusTSplit*r0.y + tSplit*r1.y};; + + vec2 subPointsLow[4] = {p[0], q0, r0, split}; + vec2 subPointsHigh[4] = {split, r1, q2, p[3]}; + + //NOTE(martin): add base triangle + u32 baseIndex = mg_vertices_base_index(context); + i32* indices = mg_reserve_indices(context, 3); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, (vec2){p[0].x, p[0].y}), + (vec4){1, 1, 1, 1}, + color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, (vec2){split.x, split.y}), + (vec4){1, 1, 1, 1}, + color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, (vec2){p[3].x, p[3].y}), + (vec4){1, 1, 1, 1}, + color, + zIndex); + + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + + mg_render_fill_cubic(context, subPointsLow, zIndex, color); + mg_render_fill_cubic(context, subPointsHigh, zIndex, color); + + return; +} + +void mg_render_fill_cubic(mg_canvas_data* context, vec2 p[4], u32 zIndex, mg_color color) +{ + LOG_DEBUG("graphics render fill cubic\n"); + + vec4 testCoords[4]; + + /*NOTE(martin): first convert the control points to power basis, multiplying by M3 + + | 1 0 0 0| + M3 = |-3 3 0 0| + | 3 -6 3 0| + |-1 3 -3 1| + ie: + c0 = p0 + c1 = -3*p0 + 3*p1 + c2 = 3*p0 - 6*p1 + 3*p2 + c3 = -p0 + 3*p1 - 3*p2 + p3 + */ + f32 c1x = 3.0*p[1].x - 3.0*p[0].x; + f32 c1y = 3.0*p[1].y - 3.0*p[0].y; + + f32 c2x = 3.0*p[0].x + 3.0*p[2].x - 6.0*p[1].x; + f32 c2y = 3.0*p[0].y + 3.0*p[2].y - 6.0*p[1].y; + + f32 c3x = 3.0*p[1].x - 3.0*p[2].x + p[3].x - p[0].x; + f32 c3y = 3.0*p[1].y - 3.0*p[2].y + p[3].y - p[0].y; + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //TODO(martin): we should do the tex coords computations in f64 and scale them to avoid f32 precision/range glitches in shader + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + c1x /= 10; + c1y /= 10; + c2x /= 10; + c2y /= 10; + c3x /= 10; + c3y /= 10; + + /*NOTE(martin): + now, compute determinants d0, d1, d2, d3, which gives the coefficients of the + inflection points polynomial: + + I(t, s) = d0*t^3 - 3*d1*t^2*s + 3*d2*t*s^2 - d3*s^3 + + The roots of this polynomial are the inflection points of the parametric curve, in homogeneous + coordinates (ie we can have an inflection point at inifinity with s=0). + + |x3 y3 w3| |x3 y3 w3| |x3 y3 w3| |x2 y2 w2| + d0 = det |x2 y2 w2| d1 = -det |x2 y2 w2| d2 = det |x1 y1 w1| d3 = -det |x1 y1 w1| + |x1 y1 w1| |x0 y0 w0| |x0 y0 w0| |x0 y0 w0| + + In our case, the pi.w equal 1 (no point at infinity), so _in_the_power_basis_, w1 = w2 = w3 = 0 and w0 = 1 + (which also means d0 = 0) + */ + + f32 d1 = c3y*c2x - c3x*c2y; + f32 d2 = c3x*c1y - c3y*c1x; + f32 d3 = c2y*c1x - c2x*c1y; + + //NOTE(martin): compute the second factor of the discriminant discr(I) = d1^2*(3*d2^2 - 4*d3*d1) + f32 discrFactor2 = 3.0*Square(d2) - 4.0*d3*d1; + + //NOTE(martin): each following case gives the number of roots, hence the category of the parametric curve + if(fabs(d1) < 0.1 && fabs(d2) < 0.1 && d3 != 0) + { + //NOTE(martin): quadratic degenerate case + LOG_DEBUG("quadratic curve\n"); + + //NOTE(martin): compute quadratic curve control point, which is at p0 + 1.5*(p1-p0) = 1.5*p1 - 0.5*p0 + vec2 quadControlPoints[3] = { p[0], + {1.5*p[1].x - 0.5*p[0].x, 1.5*p[1].y - 0.5*p[0].y}, + p[3]}; + + mg_render_fill_quadratic(context, quadControlPoints, zIndex, color); + return; + } + else if( (discrFactor2 > 0 && d1 != 0) + ||(discrFactor2 == 0 && d1 != 0)) + { + //NOTE(martin): serpentine curve or cusp with inflection at infinity + // (these two cases are handled the same way). + LOG_DEBUG("%s\n", (discrFactor2 > 0 && d1 != 0) ? "serpentine curve" : "cusp with inflection at infinity"); + + //NOTE(martin): compute the solutions (tl, sl), (tm, sm), and (tn, sn) of the inflection point equation + f32 tl = d2 + sqrt(discrFactor2/3); + f32 sl = 2*d1; + f32 tm = d2 - sqrt(discrFactor2/3); + f32 sm = sl; + + /*NOTE(martin): + the power basis coefficients of points k,l,m,n are collected into the rows of the 4x4 matrix F: + + | tl*tm tl^3 tm^3 1 | + | -sm*tl - sl*tm -3sl*tl^2 -3*sm*tm^2 0 | + | sl*sm 3*sl^2*tl 3*sm^2*tm 0 | + | 0 -sl^3 -sm^3 0 | + + This matrix is then multiplied by M3^(-1) on the left which yelds the bezier coefficients of k, l, m, n + which are assigned as a 4D texture coordinates to control points. + + + | 1 0 0 0 | + M3^(-1) = | 1 1/3 0 0 | + | 1 2/3 1/3 0 | + | 1 1 1 1 | + */ + testCoords[0].x = tl*tm; + testCoords[0].y = Cube(tl); + testCoords[0].z = Cube(tm); + + testCoords[1].x = tl*tm - (sm*tl + sl*tm)/3; + testCoords[1].y = Cube(tl) - sl*Square(tl); + testCoords[1].z = Cube(tm) - sm*Square(tm); + + testCoords[2].x = tl*tm - (sm*tl + sl*tm)*2/3 + sl*sm/3; + testCoords[2].y = Cube(tl) - 2*sl*Square(tl) + Square(sl)*tl; + testCoords[2].z = Cube(tm) - 2*sm*Square(tm) + Square(sm)*tm; + + testCoords[3].x = tl*tm - (sm*tl + sl*tm) + sl*sm; + testCoords[3].y = Cube(tl) - 3*sl*Square(tl) + 3*Square(sl)*tl - Cube(sl); + testCoords[3].z = Cube(tm) - 3*sm*Square(tm) + 3*Square(sm)*tm - Cube(sm); + } + else if(discrFactor2 < 0 && d1 != 0) + { + //NOTE(martin): loop curve + LOG_DEBUG("loop curve\n"); + + f32 td = d2 + sqrt(-discrFactor2); + f32 sd = 2*d1; + f32 te = d2 - sqrt(-discrFactor2); + f32 se = sd; + + //NOTE(martin): if one of the parameters (td/sd) or (te/se) is in the interval [0,1], the double point + // is inside the control points convex hull and would cause a shading anomaly. If this is + // the case, subdivide the curve at that point + + //TODO: study edge case where td/sd ~ 1 or 0 (which causes an infinite recursion in split and fill). + // quick fix for now is adding a little slop in the check... + + if(sd != 0 && td/sd < 0.99 && td/sd > 0.01) + { + LOG_DEBUG("split curve at first double point\n"); + mg_split_and_fill_cubic(context, p, td/sd, zIndex, color); + return; + } + if(se != 0 && te/se < 0.99 && te/se > 0.01) + { + LOG_DEBUG("split curve at second double point\n"); + mg_split_and_fill_cubic(context, p, te/se, zIndex, color); + return; + } + + /*NOTE(martin): + the power basis coefficients of points k,l,m,n are collected into the rows of the 4x4 matrix F: + + | td*te td^2*te td*te^2 1 | + | -se*td - sd*te -se*td^2 - 2sd*te*td -sd*te^2 - 2*se*td*te 0 | + | sd*se te*sd^2 + 2*se*td*sd td*se^2 + 2*sd*te*se 0 | + | 0 -sd^2*se -sd*se^2 0 | + + This matrix is then multiplied by M3^(-1) on the left which yelds the bezier coefficients of k, l, m, n + which are assigned as a 4D texture coordinates to control points. + + + | 1 0 0 0 | + M3^(-1) = | 1 1/3 0 0 | + | 1 2/3 1/3 0 | + | 1 1 1 1 | + */ + testCoords[0].x = td*te; + testCoords[0].y = Square(td)*te; + testCoords[0].z = td*Square(te); + + testCoords[1].x = td*te - (se*td + sd*te)/3.0; + testCoords[1].y = Square(td)*te - (se*Square(td) + 2.*sd*te*td)/3.0; + testCoords[1].z = td*Square(te) - (sd*Square(te) + 2*se*td*te)/3.0; + + testCoords[2].x = td*te - 2.0*(se*td + sd*te)/3.0 + sd*se/3.0; + testCoords[2].y = Square(td)*te - 2.0*(se*Square(td) + 2.0*sd*te*td)/3.0 + (te*Square(sd) + 2.0*se*td*sd)/3.0; + testCoords[2].z = td*Square(te) - 2.0*(sd*Square(te) + 2.0*se*td*te)/3.0 + (td*Square(se) + 2.0*sd*te*se)/3.0; + + testCoords[3].x = td*te - (se*td + sd*te) + sd*se; + testCoords[3].y = Square(td)*te - (se*Square(td) + 2.0*sd*te*td) + (te*Square(sd) + 2.0*se*td*sd) - Square(sd)*se; + testCoords[3].z = td*Square(te) - (sd*Square(te) + 2.0*se*td*te) + (td*Square(se) + 2.0*sd*te*se) - sd*Square(se); + } + else if(d1 == 0 && d2 != 0) + { + //NOTE(martin): cusp with cusp at infinity + LOG_DEBUG("cusp at infinity curve\n"); + + f32 tl = d3; + f32 sl = 3*d2; + + /*NOTE(martin): + the power basis coefficients of points k,l,m,n are collected into the rows of the 4x4 matrix F: + + | tl tl^3 1 1 | + | -sl -3sl*tl^2 0 0 | + | 0 3*sl^2*tl 0 0 | + | 0 -sl^3 0 0 | + + This matrix is then multiplied by M3^(-1) on the left which yelds the bezier coefficients of k, l, m, n + which are assigned as a 4D texture coordinates to control points. + + + | 1 0 0 0 | + M3^(-1) = | 1 1/3 0 0 | + | 1 2/3 1/3 0 | + | 1 1 1 1 | + */ + + testCoords[0].x = tl; + testCoords[0].y = Cube(tl); + testCoords[0].z = 1; + + testCoords[1].x = tl - sl/3; + testCoords[1].y = Cube(tl) - sl*Square(tl); + testCoords[1].z = 1; + + testCoords[2].x = tl - sl*2/3; + testCoords[2].y = Cube(tl) - 2*sl*Square(tl) + Square(sl)*tl; + testCoords[2].z = 1; + + testCoords[3].x = tl - sl; + testCoords[3].y = Cube(tl) - 3*sl*Square(tl) + 3*Square(sl)*tl - Cube(sl); + testCoords[3].z = 1; + } + else if(d1 == 0 && d2 == 0 && d3 == 0) + { + //NOTE(martin): line or point degenerate case, ignored + LOG_DEBUG("line or point curve (ignored)\n"); + return; + } + else + { + //TODO(martin): handle error ? put some epsilon slack on the conditions ? + LOG_DEBUG("none of the above...\n"); + ASSERT(0, "not implemented yet !"); + return; + } + + //NOTE(martin): compute convex hull indices using Gift wrapping / Jarvis' march algorithm + int convexHullIndices[4]; + int leftMostPointIndex = 0; + + for(int i=0; i<4; i++) + { + if(p[i].x < p[leftMostPointIndex].x) + { + leftMostPointIndex = i; + } + } + int currentPointIndex = leftMostPointIndex; + int i=0; + int convexHullCount = 0; + + do + { + convexHullIndices[i] = currentPointIndex; + convexHullCount++; + int bestGuessIndex = 0; + + for(int j=0; j<4; j++) + { + vec2 bestGuessEdge = {.x = p[bestGuessIndex].x - p[currentPointIndex].x, + .y = p[bestGuessIndex].y - p[currentPointIndex].y}; + + vec2 nextGuessEdge = {.x = p[j].x - p[currentPointIndex].x, + .y = p[j].y - p[currentPointIndex].y}; + + //NOTE(martin): if control point j is on the right of current edge, it is a best guess + // (in case of colinearity we choose the point which is farthest from the current point) + + f32 crossProduct = bestGuessEdge.x*nextGuessEdge.y - bestGuessEdge.y*nextGuessEdge.x; + + if( bestGuessIndex == currentPointIndex + || crossProduct < 0) + { + bestGuessIndex = j; + } + else if(crossProduct == 0) + { + + //NOTE(martin): if vectors v1, v2 are colinear and distinct, and ||v1|| > ||v2||, + // either abs(v1.x) > abs(v2.x) or abs(v1.y) > abs(v2.y) + // so we don't actually need to compute their norm to select the greatest + // (and if v1 and v2 are equal we don't have to update our best guess.) + + //TODO(martin): in case of colinearity we should rather select the edge that has the greatest dot product with last edge ?? + + if(fabs(nextGuessEdge.x) > fabs(bestGuessEdge.x) + || fabs(nextGuessEdge.y) > fabs(bestGuessEdge.y)) + { + bestGuessIndex = j; + } + } + } + i++; + currentPointIndex = bestGuessIndex; + + } while(currentPointIndex != leftMostPointIndex && i<4); + + //TODO: quick fix, maybe later cull degenerate hulls beforehand + if(convexHullCount <= 2) + { + //NOTE(martin): if convex hull has only two point, we have a degenerate cubic that displays nothing. + return; + } + + //NOTE(martin): rearrange convex hull to put p0 first + int startIndex = -1; + int orderedHullIndices[4]; + for(int i=0; i= 0) + { + orderedHullIndices[i-startIndex] = convexHullIndices[i]; + } + } + for(int i=0; i 0 + // ie the 4th coordinate s flips the inside/outside test. + // We affect s such that control points p1 and p2 are always outside the covered area. + + if(convexHullCount <= 3) + { + //NOTE(martin): the convex hull is a triangle + + //NOTE(martin): when we degenerate from 4 control points to a 3 points convex hull, this means one of the + // control points p1 or p2 could be inside the covered area. We want to compute the test for the + // control point which belongs to the convex hull, as we know it should be outside the covered area. + // + // Since there are 3 points in the hull and p0 is the first, and p3 belongs to the hull, this means we + // must select the point of the convex hull which is neither the first, nor p3. + + int testPointIndex = orderedHullIndices[1] == 3 ? orderedHullIndices[2] : orderedHullIndices[1]; + int outsideTest = 1; + if(Cube(testCoords[testPointIndex].x)-testCoords[testPointIndex].y*testCoords[testPointIndex].z < 0) + { + outsideTest = -1; + } + + u32 baseIndex = mg_vertices_base_index(context); + i32* indices = mg_reserve_indices(context, 3); + + for(int i=0; i<3; i++) + { + vec2 pos = mg_mat2x3_mul(context->transform, p[orderedHullIndices[i]]); + vec4 cubic = testCoords[orderedHullIndices[i]]; + cubic.w = outsideTest; + mg_push_vertex(context, pos, cubic, color, zIndex); + + indices[i] = baseIndex + i; + } + } + else if(orderedHullIndices[2] == 3) + { + //NOTE(martin): p1 and p2 are not on the same side of (p0,p3). The outside test can be different for + // the two triangles + int outsideTest1 = 1; + int outsideTest2 = 1; + + int testIndex = orderedHullIndices[1]; + if(Cube(testCoords[testIndex].x)-testCoords[testIndex].y*testCoords[testIndex].z < 0) + { + outsideTest1 = -1; + } + + testIndex = orderedHullIndices[3]; + if(Cube(testCoords[testIndex].x)-testCoords[testIndex].y*testCoords[testIndex].z < 0) + { + outsideTest2 = -1; + } + + u32 baseIndex = mg_vertices_base_index(context); + i32* indices = mg_reserve_indices(context, 6); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, p[orderedHullIndices[0]]), + (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[0]]), outsideTest1}, + color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, p[orderedHullIndices[1]]), + (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[1]]), outsideTest1}, + color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, p[orderedHullIndices[2]]), + (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[2]]), outsideTest1}, + color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, p[orderedHullIndices[0]]), + (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[0]]), outsideTest2}, + color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, p[orderedHullIndices[2]]), + (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[2]]), outsideTest2}, + color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, p[orderedHullIndices[3]]), + (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[3]]), outsideTest2}, + color, + zIndex); + + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex + 3; + indices[4] = baseIndex + 4; + indices[5] = baseIndex + 5; + } + else + { + //NOTE(martin): if p1 and p2 are on the same side of (p0,p3), the outside test is the same for both triangles + int outsideTest = 1; + if(Cube(testCoords[1].x)-testCoords[1].y*testCoords[1].z < 0) + { + outsideTest = -1; + } + + u32 baseIndex = mg_vertices_base_index(context); + i32* indices = mg_reserve_indices(context, 6); + + for(int i=0; i<4; i++) + { + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, p[orderedHullIndices[i]]), + (vec4){vec4_expand_xyz(testCoords[orderedHullIndices[i]]), outsideTest}, + color, + zIndex); + } + + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex + 0; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; + } +} + +//NOTE(martin): global path fill + +void mg_render_fill(mg_canvas_data* context, mg_path_elt* elements, mg_path_descriptor* path, u32 zIndex, mg_color color) +{ + u32 eltCount = path->count; + vec2 startPoint = path->startPoint; + vec2 endPoint = path->startPoint; + vec2 currentPoint = path->startPoint; + + for(int eltIndex=0; eltIndexp[0], elt->p[1], elt->p[2]}; + + switch(elt->type) + { + case MG_PATH_MOVE: + { + startPoint = elt->p[0]; + endPoint = elt->p[0]; + currentPoint = endPoint; + continue; + } break; + + case MG_PATH_LINE: + { + endPoint = controlPoints[1]; + } break; + + case MG_PATH_QUADRATIC: + { + mg_render_fill_quadratic(context, controlPoints, zIndex, color); + endPoint = controlPoints[2]; + + } break; + + case MG_PATH_CUBIC: + { + mg_render_fill_cubic(context, controlPoints, zIndex, color); + endPoint = controlPoints[3]; + } break; + } + + //NOTE(martin): now fill interior triangle + u32 baseIndex = mg_vertices_base_index(context); + int* indices = mg_reserve_indices(context, 3); + + vec2 pos[3]; + pos[0] = mg_mat2x3_mul(context->transform, startPoint); + pos[1] = mg_mat2x3_mul(context->transform, currentPoint); + pos[2] = mg_mat2x3_mul(context->transform, endPoint); + + vec4 cubic = {1, 1, 1, 1}; + + for(int i=0; i<3; i++) + { + mg_push_vertex(context, pos[i], cubic, color, zIndex); + indices[i] = baseIndex + i; + } + + currentPoint = endPoint; + } +} + +//----------------------------------------------------------------------------------------------------------- +// Path Stroking +//----------------------------------------------------------------------------------------------------------- + +void mg_render_stroke_line(mg_canvas_data* context, vec2 p[2], u32 zIndex, mg_attributes* attributes) +{ + //NOTE(martin): get normals multiplied by halfWidth + f32 halfW = attributes->width/2; + + vec2 n0 = {p[0].y - p[1].y, + p[1].x - p[0].x}; + f32 norm0 = sqrt(n0.x*n0.x + n0.y*n0.y); + n0.x *= halfW/norm0; + n0.y *= halfW/norm0; + + + mg_color color = attributes->color; + + u32 baseIndex = mg_vertices_base_index(context); + i32* indices = mg_reserve_indices(context, 6); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, (vec2){p[0].x + n0.x, p[0].y + n0.y}), + (vec4){1, 1, 1, 1}, + color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, (vec2){p[1].x + n0.x, p[1].y + n0.y}), + (vec4){1, 1, 1, 1}, + color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, (vec2){p[1].x - n0.x, p[1].y - n0.y}), + (vec4){1, 1, 1, 1}, + color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, (vec2){p[0].x - n0.x, p[0].y - n0.y}), + (vec4){1, 1, 1, 1}, + color, + zIndex); + + indices[0] = baseIndex; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; +} + +void mg_offset_hull(int count, vec2* p, vec2* result, f32 offset) +{ +////////////////////////////////////////////////////////////////////////////////////// +//WARN: quick fix for coincident middle control points + if(count == 4 && (p[1].x - p[2].x < 0.01) && (p[1].y - p[2].y < 0.01)) + { + vec2 hull3[3] = {p[0], p[1], p[3]}; + vec2 result3[3]; + mg_offset_hull(3, hull3, result3, offset); + result[0] = result3[0]; + result[1] = result3[1]; + result[2] = result3[1]; + result[3] = result3[2]; + return; + } +/////////////////////////////////////////////////////////////////////////////////////: + + //TODO(martin): review edge cases (coincident points ? colinear points ? control points pointing outward end point?) + //NOTE(martin): first offset control point is just the offset of first control point + vec2 n = {p[0].y - p[1].y, + p[1].x - p[0].x}; + f32 norm = sqrt(n.x*n.x + n.y*n.y); + n.x *= offset/norm; + n.y *= offset/norm; + + result[0].x = p[0].x + n.x; + result[0].y = p[0].y + n.y; + + //NOTE(martin): subsequent offset control points are the intersection of offset control lines + for(int i=1; iwidth); + mg_offset_hull(3, p, negativeOffsetHull, -0.5 * attributes->width); + + //NOTE(martin): the distance d between the offset curve and the path must be between w/2-tolerance and w/2+tolerance + // thus, by constraining tolerance to be at most, 0.5*width, we can rewrite this condition like this: + // + // (w/2-tolerance)^2 < d^2 < (w/2+tolerance)^2 + // + // we compute the maximum overshoot outside these bounds and split the curve at the corresponding parameter + + f32 tolerance = minimum(attributes->tolerance, 0.5 * attributes->width); + f32 d2LowBound = Square(0.5 * attributes->width - attributes->tolerance); + f32 d2HighBound = Square(0.5 * attributes->width + attributes->tolerance); + + f32 maxOvershoot = 0; + f32 maxOvershootParameter = 0; + + for(int i=0; i maxOvershoot) + { + maxOvershoot = overshoot; + maxOvershootParameter = t; + } + } + + if(maxOvershoot > 0) + { + //TODO(martin): split at maxErrorParameter and recurse + vec2 splitLeft[3]; + vec2 splitRight[3]; + mg_quadratic_split(p, maxOvershootParameter, splitLeft, splitRight); + mg_render_stroke_quadratic(context, splitLeft, zIndex, attributes); + mg_render_stroke_quadratic(context, splitRight, zIndex, attributes); + } + else + { + //NOTE(martin): push the actual fill commands for the offset contour + + u32 zIndex = mg_get_next_z_index(context); + + mg_render_fill_quadratic(context, positiveOffsetHull, zIndex, attributes->color); + mg_render_fill_quadratic(context, negativeOffsetHull, zIndex, attributes->color); + + //NOTE(martin): add base triangles + u32 baseIndex = mg_vertices_base_index(context); + i32* indices = mg_reserve_indices(context, 6); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, positiveOffsetHull[0]), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, positiveOffsetHull[2]), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, negativeOffsetHull[2]), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, negativeOffsetHull[0]), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex + 0; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; + } + #undef CHECK_SAMPLE_COUNT +} + +vec2 mg_cubic_get_point(vec2 p[4], f32 t) +{ + vec2 r; + + f32 oneMt = 1-t; + f32 oneMt2 = Square(oneMt); + f32 oneMt3 = oneMt2*oneMt; + f32 t2 = Square(t); + f32 t3 = t2*t; + + r.x = oneMt3*p[0].x + 3*oneMt2*t*p[1].x + 3*oneMt*t2*p[2].x + t3*p[3].x; + r.y = oneMt3*p[0].y + 3*oneMt2*t*p[1].y + 3*oneMt*t2*p[2].y + t3*p[3].y; + + return(r); +} + +void mg_cubic_split(vec2 p[4], f32 t, vec2 outLeft[4], vec2 outRight[4]) +{ + //NOTE(martin): split bezier curve p at parameter t, using De Casteljau's algorithm + // the q_n are the points along the hull's segments at parameter t + // the r_n are the points along the (q_n, q_n+1) segments at parameter t + // s is the split point. + + f32 oneMt = 1-t; + + vec2 q0 = {oneMt*p[0].x + t*p[1].x, + oneMt*p[0].y + t*p[1].y}; + + vec2 q1 = {oneMt*p[1].x + t*p[2].x, + oneMt*p[1].y + t*p[2].y}; + + vec2 q2 = {oneMt*p[2].x + t*p[3].x, + oneMt*p[2].y + t*p[3].y}; + + vec2 r0 = {oneMt*q0.x + t*q1.x, + oneMt*q0.y + t*q1.y}; + + vec2 r1 = {oneMt*q1.x + t*q2.x, + oneMt*q1.y + t*q2.y}; + + vec2 s = {oneMt*r0.x + t*r1.x, + oneMt*r0.y + t*r1.y};; + + outLeft[0] = p[0]; + outLeft[1] = q0; + outLeft[2] = r0; + outLeft[3] = s; + + outRight[0] = s; + outRight[1] = r1; + outRight[2] = q2; + outRight[3] = p[3]; +} + +void mg_render_stroke_cubic(mg_canvas_data* context, vec2 p[4], u32 zIndex, mg_attributes* attributes) +{ + #define CHECK_SAMPLE_COUNT 5 + f32 checkSamples[CHECK_SAMPLE_COUNT] = {1./6, 2./6, 3./6, 4./6, 5./6}; + + vec2 positiveOffsetHull[4]; + vec2 negativeOffsetHull[4]; + + mg_offset_hull(4, p, positiveOffsetHull, 0.5 * attributes->width); + mg_offset_hull(4, p, negativeOffsetHull, -0.5 * attributes->width); + + //NOTE(martin): the distance d between the offset curve and the path must be between w/2-tolerance and w/2+tolerance + // thus, by constraining tolerance to be at most, 0.5*width, we can rewrite this condition like this: + // + // (w/2-tolerance)^2 < d^2 < (w/2+tolerance)^2 + // + // we compute the maximum overshoot outside these bounds and split the curve at the corresponding parameter + + f32 tolerance = minimum(attributes->tolerance, 0.5 * attributes->width); + f32 d2LowBound = Square(0.5 * attributes->width - attributes->tolerance); + f32 d2HighBound = Square(0.5 * attributes->width + attributes->tolerance); + + f32 maxOvershoot = 0; + f32 maxOvershootParameter = 0; + + for(int i=0; i maxOvershoot) + { + maxOvershoot = overshoot; + maxOvershootParameter = t; + } + } + + if(maxOvershoot > 0) + { + //TODO(martin): split at maxErrorParameter and recurse + vec2 splitLeft[4]; + vec2 splitRight[4]; + mg_cubic_split(p, maxOvershootParameter, splitLeft, splitRight); + mg_render_stroke_cubic(context, splitLeft, zIndex, attributes); + mg_render_stroke_cubic(context, splitRight, zIndex, attributes); + } + else + { + //NOTE(martin): push the actual fill commands for the offset contour + + u32 zIndex = mg_get_next_z_index(context); + + mg_render_fill_cubic(context, positiveOffsetHull, zIndex, attributes->color); + mg_render_fill_cubic(context, negativeOffsetHull, zIndex, attributes->color); + + //NOTE(martin): add base triangles + u32 baseIndex = mg_vertices_base_index(context); + i32* indices = mg_reserve_indices(context, 6); + + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, positiveOffsetHull[0]), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, positiveOffsetHull[3]), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, negativeOffsetHull[3]), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, negativeOffsetHull[0]), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex + 0; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; + } + #undef CHECK_SAMPLE_COUNT +} + +void mg_stroke_cap(mg_canvas_data* context, vec2 p0, vec2 direction, mg_attributes* attributes) +{ + //NOTE(martin): compute the tangent and normal vectors (multiplied by half width) at the cap point + + f32 dn = sqrt(Square(direction.x) + Square(direction.y)); + f32 alpha = 0.5 * attributes->width/dn; + + vec2 n0 = {-alpha*direction.y, + alpha*direction.x}; + + vec2 m0 = {alpha*direction.x, + alpha*direction.y}; + + u32 zIndex = mg_get_next_z_index(context); + + u32 baseIndex = mg_vertices_base_index(context); + i32* indices = mg_reserve_indices(context, 6); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, (vec2){p0.x + n0.x, p0.y + n0.y}), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, (vec2){p0.x + n0.x + m0.x, p0.y + n0.y + m0.y}), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, (vec2){p0.x - n0.x + m0.x, p0.y - n0.y + m0.y}), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, (vec2){p0.x - n0.x, p0.y - n0.y}), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + indices[0] = baseIndex; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; +} + +void mg_stroke_joint(mg_canvas_data* context, + vec2 p0, + vec2 t0, + vec2 t1, + mg_attributes* attributes) +{ + //NOTE(martin): compute the normals at the joint point + f32 norm_t0 = sqrt(Square(t0.x) + Square(t0.y)); + f32 norm_t1 = sqrt(Square(t1.x) + Square(t1.y)); + + vec2 n0 = {-t0.y, t0.x}; + n0.x /= norm_t0; + n0.y /= norm_t0; + + vec2 n1 = {-t1.y, t1.x}; + n1.x /= norm_t1; + n1.y /= norm_t1; + + //NOTE(martin): the sign of the cross product determines if the normals are facing outwards or inwards the angle. + // we flip them to face outwards if needed + f32 crossZ = n0.x*n1.y - n0.y*n1.x; + if(crossZ > 0) + { + n0.x *= -1; + n0.y *= -1; + n1.x *= -1; + n1.y *= -1; + } + + u32 zIndex = mg_get_next_z_index(context); + + //NOTE(martin): use the same code as hull offset to find mitter point... + /*NOTE(martin): let vector u = (n0+n1) and vector v = pIntersect - p1 + then v = u * (2*offset / norm(u)^2) + (this can be derived from writing the pythagoras theorems in the triangles of the joint) + */ + f32 halfW = 0.5 * attributes->width; + vec2 u = {n0.x + n1.x, n0.y + n1.y}; + f32 uNormSquare = u.x*u.x + u.y*u.y; + f32 alpha = attributes->width / uNormSquare; + vec2 v = {u.x * alpha, u.y * alpha}; + + f32 excursionSquare = uNormSquare * Square(alpha - attributes->width/4); + + if( attributes->joint == MG_JOINT_MITER + && excursionSquare <= Square(attributes->maxJointExcursion)) + { + vec2 mitterPoint = {p0.x + v.x, p0.y + v.y}; + + u32 baseIndex = mg_vertices_base_index(context); + i32* indices = mg_reserve_indices(context, 6); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, p0), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, (vec2){p0.x + n0.x*halfW, p0.y + n0.y*halfW}), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, mitterPoint), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, (vec2){p0.x + n1.x*halfW, p0.y + n1.y*halfW}), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + indices[0] = baseIndex; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; + } + else + { + //NOTE(martin): add a bevel joint + u32 baseIndex = mg_vertices_base_index(context); + i32* indices = mg_reserve_indices(context, 3); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, p0), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, (vec2){p0.x + n0.x*halfW, p0.y + n0.y*halfW}), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + mg_push_vertex(context, + mg_mat2x3_mul(context->transform, (vec2){p0.x + n1.x*halfW, p0.y + n1.y*halfW}), + (vec4){1, 1, 1, 1}, + attributes->color, + zIndex); + + DEBUG_ASSERT(!isnan(n0.x) && !isnan(n0.y) && !isnan(n1.x) && !isnan(n1.y)); + + indices[0] = baseIndex; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + } +} + +void mg_render_stroke_element(mg_canvas_data* context, + mg_path_elt* element, + mg_attributes* attributes, + vec2 currentPoint, + vec2* startTangent, + vec2* endTangent, + vec2* endPoint) +{ + vec2 controlPoints[4] = {currentPoint, element->p[0], element->p[1], element->p[2]}; + int endPointIndex = 0; + u32 zIndex = mg_get_next_z_index(context); + + switch(element->type) + { + case MG_PATH_LINE: + mg_render_stroke_line(context, controlPoints, zIndex, attributes); + endPointIndex = 1; + break; + + case MG_PATH_QUADRATIC: + mg_render_stroke_quadratic(context, controlPoints, zIndex, attributes); + endPointIndex = 2; + break; + + case MG_PATH_CUBIC: + mg_render_stroke_cubic(context, controlPoints, zIndex, attributes); + endPointIndex = 3; + break; + + case MG_PATH_MOVE: + ASSERT(0, "should be unreachable"); + break; + } + + *startTangent = (vec2){.x = controlPoints[1].x - controlPoints[0].x, + .y = controlPoints[1].y - controlPoints[0].y}; + + *endTangent = (vec2){controlPoints[endPointIndex].x - controlPoints[endPointIndex-1].x, + controlPoints[endPointIndex].y - controlPoints[endPointIndex-1].y}; + + *endPoint = controlPoints[endPointIndex]; + +} + +u32 mg_render_stroke_subpath(mg_canvas_data* context, + mg_path_elt* elements, + mg_path_descriptor* path, + mg_attributes* attributes, + u32 startIndex, + vec2 startPoint) +{ + u32 eltCount = path->count; + DEBUG_ASSERT(startIndex < eltCount); + + vec2 currentPoint = startPoint; + vec2 endPoint = {0, 0}; + vec2 previousEndTangent = {0, 0}; + vec2 firstTangent = {0, 0}; + vec2 startTangent = {0, 0}; + vec2 endTangent = {0, 0}; + + //NOTE(martin): render first element and compute first tangent + mg_render_stroke_element(context, elements + startIndex, attributes, currentPoint, &startTangent, &endTangent, &endPoint); + + firstTangent = startTangent; + previousEndTangent = endTangent; + currentPoint = endPoint; + + //NOTE(martin): render subsequent elements along with their joints + u32 eltIndex = startIndex + 1; + for(; + eltIndexjoint != MG_JOINT_NONE) + { + mg_stroke_joint(context, currentPoint, previousEndTangent, startTangent, attributes); + } + previousEndTangent = endTangent; + currentPoint = endPoint; + } + u32 subPathEltCount = eltIndex - (startIndex+1); + + //NOTE(martin): draw end cap / joint. We ensure there's at least two segments to draw a closing joint + if( subPathEltCount > 1 + && startPoint.x == endPoint.x + && startPoint.y == endPoint.y) + { + if(attributes->joint != MG_JOINT_NONE) + { + //NOTE(martin): add a closing joint if the path is closed + mg_stroke_joint(context, endPoint, endTangent, firstTangent, attributes); + } + } + else if(attributes->cap == MG_CAP_SQUARE) + { + //NOTE(martin): add start and end cap + mg_stroke_cap(context, startPoint, (vec2){-startTangent.x, -startTangent.y}, attributes); + mg_stroke_cap(context, endPoint, startTangent, attributes); + } + + return(eltIndex); +} + + +void mg_render_stroke(mg_canvas_data* context, + mg_path_elt* elements, + mg_path_descriptor* path, + mg_attributes* attributes) +{ + u32 eltCount = path->count; + DEBUG_ASSERT(eltCount); + + vec2 startPoint = path->startPoint; + u32 startIndex = 0; + + while(startIndex < eltCount) + { + //NOTE(martin): eliminate leading moves + while(startIndex < eltCount && elements[startIndex].type == MG_PATH_MOVE) + { + startPoint = elements[startIndex].p[0]; + startIndex++; + } + + if(startIndex < eltCount) + { + startIndex = mg_render_stroke_subpath(context, elements, path, attributes, startIndex, startPoint); + } + } +} + +//----------------------------------------------------------------------------------------------------------- +// Fast shapes primitives +//----------------------------------------------------------------------------------------------------------- + +void mg_render_rectangle_fill(mg_canvas_data* context, mp_rect rect, mg_attributes* attributes) +{ + u32 baseIndex = mg_vertices_base_index(context); + i32* indices = mg_reserve_indices(context, 6); + + u32 zIndex = mg_get_next_z_index(context); + + vec2 points[4] = {{rect.x, rect.y}, + {rect.x + rect.w, rect.y}, + {rect.x + rect.w, rect.y + rect.h}, + {rect.x, rect.y + rect.h}}; + + vec4 cubic = {1, 1, 1, 1}; + for(int i=0; i<4; i++) + { + vec2 pos = mg_mat2x3_mul(context->transform, points[i]); + mg_push_vertex(context, pos, cubic, attributes->color, zIndex); + } + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex + 0; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; +} + +void mg_render_rectangle_stroke(mg_canvas_data* context, mp_rect rect, mg_attributes* attributes) +{ + //NOTE(martin): stroke a rectangle by fill two scaled rectangles with the same zIndex. + u32 baseIndex = mg_vertices_base_index(context); + i32* indices = mg_reserve_indices(context, 12); + + u32 zIndex = mg_get_next_z_index(context); + + //NOTE(martin): limit stroke width to the minimum dimension of the rectangle + f32 width = minimum(attributes->width, minimum(rect.w, rect.h)); + f32 halfW = width/2; + + vec2 outerPoints[4] = {{rect.x - halfW, rect.y - halfW}, + {rect.x + rect.w + halfW, rect.y - halfW}, + {rect.x + rect.w + halfW, rect.y + rect.h + halfW}, + {rect.x - halfW, rect.y + rect.h + halfW}}; + + vec2 innerPoints[4] = {{rect.x + halfW, rect.y + halfW}, + {rect.x + rect.w - halfW, rect.y + halfW}, + {rect.x + rect.w - halfW, rect.y + rect.h - halfW}, + {rect.x + halfW, rect.y + rect.h - halfW}}; + + vec4 cubic = {1, 1, 1, 1}; + for(int i=0; i<4; i++) + { + vec2 pos = mg_mat2x3_mul(context->transform, outerPoints[i]); + mg_push_vertex(context, pos, cubic, attributes->color, zIndex); + } + + for(int i=0; i<4; i++) + { + vec2 pos = mg_mat2x3_mul(context->transform, innerPoints[i]); + mg_push_vertex(context, pos, cubic, attributes->color, zIndex); + } + + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex + 0; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; + indices[6] = baseIndex + 4; + indices[7] = baseIndex + 5; + indices[8] = baseIndex + 6; + indices[9] = baseIndex + 4; + indices[10] = baseIndex + 6; + indices[11] = baseIndex + 7; +} + +void mg_render_fill_arc_corner(mg_canvas_data* context, f32 x, f32 y, f32 rx, f32 ry, u32 zIndex, mg_color color) +{ + //NOTE(martin): draw a precomputed arc corner, using a bezier approximation + u32 baseIndex = mg_vertices_base_index(context); + i32* indices = mg_reserve_indices(context, 6); + + static const vec4 cubics[4] = {{-3.76797, -9.76362, 5.47912, -1}, + {-4.19896, -9.45223, 7.534, -1}, + {-4.19896, -7.534, 9.45223, -1}, + {-3.76797, -5.47912, 9.76362, -1}}; + + f32 cx = rx*4*(sqrt(2)-1)/3; + f32 cy = ry*4*(sqrt(2)-1)/3; + + vec2 points[4] = {{x, y + ry}, + {x, y + ry - cy}, + {x + rx - cx, y}, + {x + rx, y}}; + + for(int i=0; i<4; i++) + { + vec2 pos = mg_mat2x3_mul(context->transform, points[i]); + mg_push_vertex(context, pos, cubics[i], color, zIndex); + } + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex + 0; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; +} + +void mg_render_rounded_rectangle_fill_with_z_index(mg_canvas_data* context, + mg_rounded_rect rect, + mg_attributes* attributes, + u32 zIndex) +{ + //NOTE(martin): draw a rounded rectangle by drawing a normal rectangle and 4 corners, + // approximating an arc by a precomputed bezier curve + + u32 baseIndex = mg_vertices_base_index(context); + i32* indices = mg_reserve_indices(context, 18); + + //NOTE(martin): inner cutted corner rectangle + vec2 points[8] = {{rect.x + rect.r, rect.y}, + {rect.x + rect.w - rect.r, rect.y}, + {rect.x + rect.w, rect.y + rect.r}, + {rect.x + rect.w, rect.y + rect.h - rect.r}, + {rect.x + rect.w - rect.r, rect.y + rect.h}, + {rect.x + rect.r, rect.y + rect.h}, + {rect.x, rect.y + rect.h - rect.r}, + {rect.x, rect.y + rect.r}}; + + vec4 cubic = {1, 1, 1, 1}; + + for(int i=0; i<8; i++) + { + vec2 pos = mg_mat2x3_mul(context->transform, points[i]); + mg_push_vertex(context, pos, cubic, attributes->color, zIndex); + } + + static const i32 fanIndices[18] = { 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 5, 6, 0, 6, 7 }; // inner fan + for(int i=0; i<18; i++) + { + indices[i] = fanIndices[i] + baseIndex; + } + + mg_render_fill_arc_corner(context, rect.x, rect.y, rect.r, rect.r, zIndex, attributes->color); + mg_render_fill_arc_corner(context, rect.x + rect.w, rect.y, -rect.r, rect.r, zIndex, attributes->color); + mg_render_fill_arc_corner(context, rect.x + rect.w, rect.y + rect.h, -rect.r, -rect.r, zIndex, attributes->color); + mg_render_fill_arc_corner(context, rect.x, rect.y + rect.h, rect.r, -rect.r, zIndex, attributes->color); +} + + +void mg_render_rounded_rectangle_fill(mg_canvas_data* context, + mg_rounded_rect rect, + mg_attributes* attributes) +{ + u32 zIndex = mg_get_next_z_index(context); + mg_render_rounded_rectangle_fill_with_z_index(context, rect, attributes, zIndex); +} + +void mg_render_rounded_rectangle_stroke(mg_canvas_data* context, + mg_rounded_rect rect, + mg_attributes* attributes) +{ + //NOTE(martin): stroke rounded rectangle by filling two scaled rounded rectangles with the same zIndex + f32 width = minimum(attributes->width, minimum(rect.w, rect.h)); + f32 halfW = width/2; + + mg_rounded_rect inner = {rect.x + halfW, rect.y + halfW, rect.w - width, rect.h - width, rect.r - halfW}; + mg_rounded_rect outer = {rect.x - halfW, rect.y - halfW, rect.w + width, rect.h + width, rect.r + halfW}; + + u32 zIndex = mg_get_next_z_index(context); + mg_render_rounded_rectangle_fill_with_z_index(context, outer, attributes, zIndex); + mg_render_rounded_rectangle_fill_with_z_index(context, inner, attributes, zIndex); +} + +void mg_render_ellipse_fill_with_z_index(mg_canvas_data* context, + mp_rect rect, + mg_attributes* attributes, + u32 zIndex) +{ + //NOTE(martin): draw a filled ellipse by drawing a diamond and 4 corners, + // approximating an arc by a precomputed bezier curve + + u32 baseIndex = mg_vertices_base_index(context); + i32* indices = mg_reserve_indices(context, 6); + + f32 rx = rect.w/2; + f32 ry = rect.h/2; + + //NOTE(martin): inner diamond + vec2 points[4] = {{rect.x, rect.y + ry}, + {rect.x + rx, rect.y}, + {rect.x + rect.w, rect.y + ry}, + {rect.x + rx, rect.y + rect.h}}; + + vec4 cubic = {1, 1, 1, 1}; + + for(int i=0; i<4; i++) + { + vec2 pos = mg_mat2x3_mul(context->transform, points[i]); + mg_push_vertex(context, pos, cubic, attributes->color, zIndex); + } + + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex + 0; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; + + mg_render_fill_arc_corner(context, rect.x, rect.y, rx, ry, zIndex, attributes->color); + mg_render_fill_arc_corner(context, rect.x + rect.w, rect.y, -rx, ry, zIndex, attributes->color); + mg_render_fill_arc_corner(context, rect.x + rect.w, rect.y + rect.h, -rx, -ry, zIndex, attributes->color); + mg_render_fill_arc_corner(context, rect.x, rect.y + rect.h, rx, -ry, zIndex, attributes->color); +} + +void mg_render_ellipse_fill(mg_canvas_data* context, mp_rect rect, mg_attributes* attributes) +{ + u32 zIndex = mg_get_next_z_index(context); + mg_render_ellipse_fill_with_z_index(context, rect, attributes, zIndex); +} + +void mg_render_ellipse_stroke(mg_canvas_data* context, mp_rect rect, mg_attributes* attributes) +{ + //NOTE(martin): stroke by filling two scaled ellipsis with the same zIndex + f32 width = minimum(attributes->width, minimum(rect.w, rect.h)); + f32 halfW = width/2; + + mp_rect inner = {rect.x + halfW, rect.y + halfW, rect.w - width, rect.h - width}; + mp_rect outer = {rect.x - halfW, rect.y - halfW, rect.w + width, rect.h + width}; + + u32 zIndex = mg_get_next_z_index(context); + mg_render_ellipse_fill_with_z_index(context, outer, attributes, zIndex); + mg_render_ellipse_fill_with_z_index(context, inner, attributes, zIndex); +} + +void mg_render_image(mg_canvas_data* context, mg_image image, mp_rect rect) +{ + mg_image_data* imageData = mg_image_ptr_from_handle(context, image); + if(!imageData) + { + return; + } + + u32 baseIndex = mg_vertices_base_index(context); + i32* indices = mg_reserve_indices(context, 6); + + u32 zIndex = mg_get_next_z_index(context); + + vec2 points[4] = {{rect.x, rect.y}, + {rect.x + rect.w, rect.y}, + {rect.x + rect.w, rect.y + rect.h}, + {rect.x, rect.y + rect.h}}; + + vec2 uv[4] = {{imageData->rect.x + 0.5, imageData->rect.y + 0.5}, + {imageData->rect.x + imageData->rect.w - 0.5, imageData->rect.y + 0.5}, + {imageData->rect.x + imageData->rect.w - 0.5, imageData->rect.y + imageData->rect.h - 0.5}, + {imageData->rect.x + 0.5, imageData->rect.y + imageData->rect.h - 0.5}}; + + vec4 cubic = {1, 1, 1, 1}; + mg_color color = {1, 1, 1, 1}; + + for(int i=0; i<4; i++) + { + vec2 transformedUV = {uv[i].x / MG_ATLAS_SIZE, uv[i].y / MG_ATLAS_SIZE}; + + vec2 pos = mg_mat2x3_mul(context->transform, points[i]); + mg_push_textured_vertex(context, pos, cubic, transformedUV, color, zIndex); + } + indices[0] = baseIndex + 0; + indices[1] = baseIndex + 1; + indices[2] = baseIndex + 2; + indices[3] = baseIndex + 0; + indices[4] = baseIndex + 2; + indices[5] = baseIndex + 3; +} + +void mg_render_rounded_image(mg_canvas_data* context, mg_image image, mg_rounded_rect rect, mg_attributes* attributes) +{ + mg_image_data* imageData = mg_image_ptr_from_handle(context, image); + if(!imageData) + { + return; + } + + //////////////////////////////////////////////////////////////////////////////// + //TODO: this does not work for rotated rectangles + //////////////////////////////////////////////////////////////////////////////// + vec2 uvMin = {(imageData->rect.x + 0.5) / MG_ATLAS_SIZE, (imageData->rect.y + 0.5) / MG_ATLAS_SIZE}; + vec2 uvMax = {(imageData->rect.x + imageData->rect.w - 0.5) / MG_ATLAS_SIZE, (imageData->rect.y + imageData->rect.h - 0.5) / MG_ATLAS_SIZE}; + mp_rect uvRect = {uvMin.x, uvMin.y, uvMax.x - uvMin.x, uvMax.y - uvMin.y}; + + vec2 pMin = mg_mat2x3_mul(context->transform, (vec2){rect.x, rect.y}); + vec2 pMax = mg_mat2x3_mul(context->transform, (vec2){rect.x + rect.w, rect.y + rect.h}); + mp_rect pRect = {pMin.x, pMin.y, pMax.x - pMin.x, pMax.y - pMin.y}; + + u32 startIndex = mg_vertices_base_index(context); + + mgc_vertex_layout* layout = &context->painter->vertexLayout; + + attributes->color = (mg_color){1, 1, 1, 1}; + mg_render_rounded_rectangle_fill(context, rect, attributes); + + u32 indexCount = mg_vertices_base_index(context) - startIndex; + + for(int i=0; iposBuffer) + index*layout->posStride); + vec2* uv = (vec2*)(((char*)layout->uvBuffer) + index*layout->uvStride); + + vec2 coordInBoundingSpace = {(pos->x - pRect.x)/pRect.w, + (pos->y - pRect.y)/pRect.h}; + + vec2 mappedUV = {uvRect.x + coordInBoundingSpace.x * uvRect.w, + uvRect.y + coordInBoundingSpace.y * uvRect.h}; + + *uv = mappedUV; + } + +} + +//----------------------------------------------------------------------------------------------------------- +// Graphics canvas +//----------------------------------------------------------------------------------------------------------- + +#ifdef MG_IMPLEMENTS_BACKEND_METAL + typedef struct mg_metal_surface mg_metal_surface; + mg_canvas_painter* mg_metal_painter_create_for_surface(mg_metal_surface* surface, mp_rect viewPort); +#endif + +mg_canvas_painter* mg_canvas_painter_create_for_surface(mg_surface surfaceHandle, mp_rect viewPort) +{ + mg_surface_info* surface = mg_surface_ptr_from_handle(surfaceHandle); + if(!surface) + { + return(0); + } + + switch(surface->backend) + { + case MG_BACKEND_METAL: + return(mg_metal_painter_create_for_surface((mg_metal_surface*)surface, viewPort)); + + default: + return(0); + } +} + +void mg_canvas_reset_streams(mg_canvas_data* context) +{ + ListInit(&context->streamsFreeList); + for(int i=0; istreamsFreeList, &context->streams[i].freeListElt); + context->streams[i].generation = 1; + } + context->currentStream = mg_stream_alloc(context); +} + +mg_canvas mg_canvas_create(mg_surface surface, mp_rect viewPort) +{ + mg_canvas_data* context = mg_canvas_alloc(); + if(!context) + { + return(mg_canvas_nil()); + } + + context->path.startIndex = 0; + context->path.count = 0; + context->nextZIndex = 1; + context->attributes.color = (mg_color){0, 0, 0, 1}; + context->attributes.tolerance = 1; + context->attributes.width = 10; + context->attributes.clip = (mp_rect){-FLT_MAX/2, -FLT_MAX/2, FLT_MAX, FLT_MAX}; + + context->transform = (mg_mat2x3){{1, 0, 0, + 0, 1, 0}}; + + mg_canvas_reset_streams(context); + + context->painter = mg_canvas_painter_create_for_surface(surface, viewPort); + if(!context->painter) + { + LOG_ERROR("Couldn't create painter for surface\n"); + mg_canvas_recycle(context); + return(mg_canvas_nil()); + } + + mg_canvas handle = mg_canvas_handle_from_ptr(context); + + //NOTE: create a blank image + u8 bytes[4] = {255, 255, 255, 255}; + context->blankImage = mg_image_create_from_rgba8(handle, 1, 1, bytes); + + return(handle); +} + +void mg_canvas_destroy(mg_canvas handle) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + mg_canvas_recycle(context); +} + +void mg_canvas_viewport(mg_canvas handle, mp_rect viewport) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + context->painter->setViewPort(context->painter, viewport); +} + +void mg_canvas_begin_draw(mg_canvas handle) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + context->vertexCount = 0; + context->indexCount = 0; + context->path.startIndex = 0; + context->path.count = 0; +} + +mg_mat2x3 mg_matrix_stack_top(mg_canvas_data* context) +{ + if(context->matrixStackSize == 0) + { + return((mg_mat2x3){1, 0, 0, + 0, 1, 0}); + } + else + { + return(context->matrixStack[context->matrixStackSize-1]); + } +} + +void mg_matrix_stack_push(mg_canvas_data* context, mg_mat2x3 transform) +{ + if(context->matrixStackSize >= MG_MATRIX_STACK_MAX_DEPTH) + { + LOG_ERROR("matrix stack overflow\n"); + } + else + { + context->matrixStack[context->matrixStackSize] = transform; + context->matrixStackSize++; + context->transform = transform; + } +} + +void mg_matrix_stack_pop(mg_canvas_data* context) +{ + if(context->matrixStackSize == 0) + { + LOG_ERROR("matrix stack underflow\n"); + } + else + { + context->matrixStackSize--; + context->transform = mg_matrix_stack_top(context); + } +} + + +mp_rect mg_clip_stack_top(mg_canvas_data* context) +{ + if(context->clipStackSize == 0) + { + return((mp_rect){-FLT_MAX/2, -FLT_MAX/2, FLT_MAX, FLT_MAX}); + } + else + { + return(context->clipStack[context->clipStackSize-1]); + } +} + +void mg_clip_stack_push(mg_canvas_data* context, mp_rect clip) +{ + if(context->clipStackSize >= MG_CLIP_STACK_MAX_DEPTH) + { + LOG_ERROR("clip stack overflow\n"); + } + else + { + context->clipStack[context->clipStackSize] = clip; + context->clipStackSize++; + context->clip = clip; + } +} + +void mg_clip_stack_pop(mg_canvas_data* context) +{ + if(context->clipStackSize == 0) + { + LOG_ERROR("clip stack underflow\n"); + } + else + { + context->clipStackSize--; + context->clip = mg_clip_stack_top(context); + } +} + +void mg_do_clip_push(mg_canvas_data* context, mp_rect clip) +{ + //NOTE(martin): transform clip + vec2 p0 = mg_mat2x3_mul(context->transform, (vec2){clip.x, clip.y}); + vec2 p1 = mg_mat2x3_mul(context->transform, (vec2){clip.x + clip.w, clip.y}); + vec2 p2 = mg_mat2x3_mul(context->transform, (vec2){clip.x + clip.w, clip.y + clip.h}); + vec2 p3 = mg_mat2x3_mul(context->transform, (vec2){clip.x, clip.y + clip.h}); + + f32 x0 = minimum(p0.x, minimum(p1.x, minimum(p2.x, p3.x))); + f32 y0 = minimum(p0.y, minimum(p1.y, minimum(p2.y, p3.y))); + f32 x1 = maximum(p0.x, maximum(p1.x, maximum(p2.x, p3.x))); + f32 y1 = maximum(p0.y, maximum(p1.y, maximum(p2.y, p3.y))); + + mp_rect current = mg_clip_stack_top(context); + + //NOTE(martin): intersect with current clip + x0 = maximum(current.x, x0); + y0 = maximum(current.y, y0); + x1 = minimum(current.x + current.w, x1); + y1 = minimum(current.y + current.h, y1); + + mp_rect r = {x0, y0, maximum(0, x1-x0), maximum(0, y1-y0)}; + mg_clip_stack_push(context, r); +} + +void mg_canvas_flush(mg_canvas contextHandle) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(contextHandle); + if(!context) + { + return; + } + + mg_color clearColor = {0, 0, 0, 1}; + + u32 count = context->primitiveCount; + + mg_stream_data* stream = context->currentStream; + DEBUG_ASSERT(stream); + DEBUG_ASSERT(stream->count <= count); + + u32 nextIndex = stream->firstCommand; + + mg_reset_z_index(context); + context->transform = (mg_mat2x3){1, 0, 0, + 0, 1, 0}; + context->clip = (mp_rect){-FLT_MAX/2, -FLT_MAX/2, FLT_MAX, FLT_MAX}; + + for(int i=0; icount; i++) + { + if(nextIndex >= count) + { + LOG_ERROR("invalid location '%i' in graphics command buffer would cause an overrun\n", nextIndex); + break; + } + mg_primitive* primitive = &(context->primitives[nextIndex]); + nextIndex++; + + switch(primitive->cmd) + { + case MG_CMD_CLEAR: + { + //NOTE(martin): clear buffers + context->vertexCount = 0; + context->indexCount = 0; + + clearColor = primitive->attributes.color; + } break; + + case MG_CMD_FILL: + { + u32 zIndex = mg_get_next_z_index(context); + mg_render_fill(context, + context->pathElements + primitive->path.startIndex, + &primitive->path, + zIndex, + primitive->attributes.color); + } break; + + case MG_CMD_STROKE: + { + mg_render_stroke(context, + context->pathElements + primitive->path.startIndex, + &primitive->path, + &primitive->attributes); + } break; + + + case MG_CMD_RECT_FILL: + mg_render_rectangle_fill(context, primitive->rect, &primitive->attributes); + break; + + case MG_CMD_RECT_STROKE: + mg_render_rectangle_stroke(context, primitive->rect, &primitive->attributes); + break; + + case MG_CMD_ROUND_RECT_FILL: + mg_render_rounded_rectangle_fill(context, primitive->roundedRect, &primitive->attributes); + break; + + case MG_CMD_ROUND_RECT_STROKE: + mg_render_rounded_rectangle_stroke(context, primitive->roundedRect, &primitive->attributes); + break; + + case MG_CMD_ELLIPSE_FILL: + mg_render_ellipse_fill(context, primitive->rect, &primitive->attributes); + break; + + case MG_CMD_ELLIPSE_STROKE: + mg_render_ellipse_stroke(context, primitive->rect, &primitive->attributes); + break; + + case MG_CMD_JUMP: + { + if(primitive->jump == ~0) + { + //NOTE(martin): normal end of stream marker + goto exit_command_loop; + } + else if(primitive->jump >= count) + { + LOG_ERROR("invalid jump location '%i' in graphics command buffer\n", primitive->jump); + goto exit_command_loop; + } + else + { + nextIndex = primitive->jump; + } + } break; + + case MG_CMD_MATRIX_PUSH: + { + mg_mat2x3 transform = mg_matrix_stack_top(context); + mg_matrix_stack_push(context, mg_mat2x3_mul_m(transform, primitive->matrix)); + } break; + + case MG_CMD_MATRIX_POP: + { + mg_matrix_stack_pop(context); + } break; + + case MG_CMD_CLIP_PUSH: + { + //TODO(martin): use only aligned rect and avoid this + mp_rect r = {primitive->rect.x, primitive->rect.y, primitive->rect.w, primitive->rect.h}; + mg_do_clip_push(context, r); + } break; + + case MG_CMD_CLIP_POP: + { + mg_clip_stack_pop(context); + } break; + + case MG_CMD_IMAGE_DRAW: + { + mg_render_image(context, primitive->attributes.image, primitive->rect); + } break; + + case MG_CMD_ROUNDED_IMAGE_DRAW: + { + mg_render_rounded_image(context, primitive->attributes.image, primitive->roundedRect, &primitive->attributes); + } break; + + } + } + exit_command_loop: ; + + if(context->painter && context->painter->drawBuffers) + { + context->painter->drawBuffers(context->painter, context->vertexCount, context->indexCount, clearColor); + } + + //NOTE(martin): clear buffers + context->primitiveCount = 0; + context->vertexCount = 0; + context->indexCount = 0; + + context->path.startIndex = 0; + context->path.count = 0; + + //NOTE(martin): invalidate all streams + context->frameCounter++; + mg_canvas_reset_streams(context); +} + +void mg_push_command(mg_canvas_data* context, mg_primitive primitive) +{ + //NOTE(martin): push primitive and updates current stream, eventually patching a pending jump. + ASSERT(context->primitiveCount < MG_MAX_PRIMITIVE_COUNT); + context->primitives[context->primitiveCount] = primitive; + context->primitiveCount++; + + mg_stream_data* stream = context->currentStream; + DEBUG_ASSERT(stream); + + u32 lastCommand = context->primitiveCount-1; + if(stream->pendingJump) + { + DEBUG_ASSERT(context->primitives[stream->lastCommand].cmd == MG_CMD_JUMP); + context->primitives[stream->lastCommand].jump = lastCommand; + stream->pendingJump = false; + } + stream->lastCommand = lastCommand; + stream->count++; +} + +//----------------------------------------------------------------------------------------------------------- +// Graphics command streams +//----------------------------------------------------------------------------------------------------------- + +mg_stream mg_stream_create(mg_canvas contextHandle) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(contextHandle); + if(!context) + { + return(mg_stream_null_handle()); + } + + mg_stream_data* stream = mg_stream_alloc(context); + if(!stream) + { + return(mg_stream_null_handle()); + } + + return(mg_stream_handle_from_ptr(context, stream)); +} + +mg_stream mg_stream_swap(mg_canvas contextHandle, mg_stream streamHandle) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(contextHandle); + if(!context) + { + return(mg_stream_null_handle()); + } + mg_stream_data* stream = mg_stream_ptr_from_handle(context, streamHandle); + if(!stream) + { + return(mg_stream_null_handle()); + } + + if(stream == context->currentStream) + { + //NOTE(martin): prevent swapping for itself + return(mg_stream_null_handle()); + } + + //NOTE(martin): push an unitialized jump to the current stream + mg_push_command(context, (mg_primitive){.cmd = MG_CMD_JUMP, .jump = ~0}); + context->currentStream->pendingJump = true; + + //NOTE(martin): set the new current stream + mg_stream_data* oldStream = context->currentStream; + context->currentStream = stream; + + if(!stream->count) + { + //NOTE(martin): if stream is new, set the first command to the current offset in command buffer + stream->firstCommand = context->primitiveCount; + stream->lastCommand = stream->firstCommand; + } + else + { + //NOTE(martin): else, we just make sure there is a pending jump to be patched when we push the next command + DEBUG_ASSERT(stream->pendingJump); + } + + return(mg_stream_handle_from_ptr(context, oldStream)); +} + +void mg_stream_append(mg_canvas contextHandle, mg_stream streamHandle) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(contextHandle); + if(!context) + { + return; + } + mg_stream_data* stream = mg_stream_ptr_from_handle(context, streamHandle); + if(!stream) + { + return; + } + if(stream == context->currentStream) + { + //NOTE(martin): prevent appending to itself + return; + } + + if(stream->count) + { + //NOTE(martin): push a jump set to the appended stream's first command + mg_push_command(context, (mg_primitive){.cmd = MG_CMD_JUMP, .jump = stream->firstCommand}); + + //NOTE(martin): update current stream last command and make sure it's a jump + context->currentStream->lastCommand = stream->lastCommand; + context->currentStream->count += stream->count; + context->currentStream->pendingJump = true; + + DEBUG_ASSERT(context->primitives[context->currentStream->lastCommand].cmd == MG_CMD_JUMP); + } + + //NOTE(martin): invalidate appended stream + mg_stream_recycle(context, stream); +} + +//----------------------------------------------------------------------------------------------------------- +// Fonts management +//----------------------------------------------------------------------------------------------------------- + +mg_font mg_font_nil() +{ + return((mg_font){0}); +} + +mg_font mg_font_create_from_memory(u32 size, byte* buffer, u32 rangeCount, unicode_range* ranges) +{ + mg_font_info* fontInfo = ListPopEntry(&__mgInfo.fontFreeList, mg_font_info, freeListElt); + if(!fontInfo) + { + if(__mgInfo.fontsNextIndex < MG_FONT_MAX_COUNT) + { + fontInfo = &(__mgInfo.fonts[__mgInfo.fontsNextIndex]); + __mgInfo.fontsNextIndex++; + memset(fontInfo, 0, sizeof(*fontInfo)); + fontInfo->generation = 1; + } + else + { + LOG_ERROR("can't allocate new font\n"); + return((mg_font){0}); + } + } + + stbtt_fontinfo stbttFontInfo; + stbtt_InitFont(&stbttFontInfo, buffer, 0); + + //NOTE(martin): load font metrics data + fontInfo->unitsPerEm = 1./stbtt_ScaleForMappingEmToPixels(&stbttFontInfo, 1); + + int ascent, descent, lineGap, x0, x1, y0, y1; + stbtt_GetFontVMetrics(&stbttFontInfo, &ascent, &descent, &lineGap); + stbtt_GetFontBoundingBox(&stbttFontInfo, &x0, &y0, &x1, &y1); + + fontInfo->extents.ascent = ascent; + fontInfo->extents.descent = -descent; + fontInfo->extents.leading = lineGap; + fontInfo->extents.width = x1 - x0; + + stbtt_GetCodepointBox(&stbttFontInfo, 'x', &x0, &y0, &x1, &y1); + fontInfo->extents.xHeight = y1 - y0; + + stbtt_GetCodepointBox(&stbttFontInfo, 'M', &x0, &y0, &x1, &y1); + fontInfo->extents.capHeight = y1 - y0; + + //NOTE(martin): load codepoint ranges + fontInfo->rangeCount = rangeCount; + fontInfo->glyphMap = malloc_array(mg_glyph_map_entry, rangeCount); + fontInfo->glyphCount = 0; + + for(int i=0; iglyphMap[i].range = ranges[i]; + fontInfo->glyphMap[i].firstGlyphIndex = fontInfo->glyphCount + 1; + fontInfo->glyphCount += ranges[i].count; + } + + fontInfo->glyphs = malloc_array(mg_glyph_info, fontInfo->glyphCount); + + //NOTE(martin): first do a count of outlines + int outlineCount = 0; + for(int rangeIndex=0; rangeIndexglyphMap[rangeIndex].range.firstCodePoint; + u32 firstGlyphIndex = fontInfo->glyphMap[rangeIndex].firstGlyphIndex; + u32 endGlyphIndex = firstGlyphIndex + fontInfo->glyphMap[rangeIndex].range.count; + + for(int glyphIndex = firstGlyphIndex; + glyphIndex < endGlyphIndex; glyphIndex++) + { + int stbttGlyphIndex = stbtt_FindGlyphIndex(&stbttFontInfo, codePoint); + if(stbttGlyphIndex == 0) + { + //NOTE(martin): the codepoint is not found in the font + codePoint++; + continue; + } + //NOTE(martin): load glyph outlines + stbtt_vertex* vertices = 0; + outlineCount += stbtt_GetGlyphShape(&stbttFontInfo, stbttGlyphIndex, &vertices); + stbtt_FreeShape(&stbttFontInfo, vertices); + codePoint++; + } + } + //NOTE(martin): allocate outlines + fontInfo->outlines = malloc_array(mg_path_elt, outlineCount); + fontInfo->outlineCount = 0; + + //NOTE(martin): load metrics and outlines + for(int rangeIndex=0; rangeIndexglyphMap[rangeIndex].range.firstCodePoint; + u32 firstGlyphIndex = fontInfo->glyphMap[rangeIndex].firstGlyphIndex; + u32 endGlyphIndex = firstGlyphIndex + fontInfo->glyphMap[rangeIndex].range.count; + + for(int glyphIndex = firstGlyphIndex; + glyphIndex < endGlyphIndex; glyphIndex++) + { + mg_glyph_info* glyph = &(fontInfo->glyphs[glyphIndex-1]); + + int stbttGlyphIndex = stbtt_FindGlyphIndex(&stbttFontInfo, codePoint); + if(stbttGlyphIndex == 0) + { + //NOTE(martin): the codepoint is not found in the font, we zero the glyph info + memset(glyph, 0, sizeof(*glyph)); + codePoint++; + continue; + } + + glyph->exists = true; + glyph->codePoint = codePoint; + + //NOTE(martin): load glyph metric + int xAdvance, xBearing, x0, y0, x1, y1; + stbtt_GetGlyphHMetrics(&stbttFontInfo, stbttGlyphIndex, &xAdvance, &xBearing); + stbtt_GetGlyphBox(&stbttFontInfo, stbttGlyphIndex, &x0, &y0, &x1, &y1); + + glyph->extents.xAdvance = (f32)xAdvance; + glyph->extents.yAdvance = 0; + glyph->extents.xBearing = (f32)xBearing; + glyph->extents.yBearing = y0; + + glyph->extents.width = x1 - x0; + glyph->extents.height = y1 - y0; + + //NOTE(martin): load glyph outlines + + stbtt_vertex* vertices = 0; + int vertexCount = stbtt_GetGlyphShape(&stbttFontInfo, stbttGlyphIndex, &vertices); + + glyph->pathDescriptor = (mg_path_descriptor){.startIndex = fontInfo->outlineCount, + .count = vertexCount, + .startPoint = {0, 0}}; + + mg_path_elt* elements = fontInfo->outlines + fontInfo->outlineCount; + fontInfo->outlineCount += vertexCount; + vec2 currentPos = {0, 0}; + + for(int vertIndex = 0; vertIndex < vertexCount; vertIndex++) + { + f32 x = vertices[vertIndex].x; + f32 y = vertices[vertIndex].y; + f32 cx = vertices[vertIndex].cx; + f32 cy = vertices[vertIndex].cy; + f32 cx1 = vertices[vertIndex].cx1; + f32 cy1 = vertices[vertIndex].cy1; + + switch(vertices[vertIndex].type) + { + case STBTT_vmove: + elements[vertIndex].type = MG_PATH_MOVE; + elements[vertIndex].p[0] = (vec2){x, y}; + break; + + case STBTT_vline: + elements[vertIndex].type = MG_PATH_LINE; + elements[vertIndex].p[0] = (vec2){x, y}; + break; + + case STBTT_vcurve: + { + elements[vertIndex].type = MG_PATH_QUADRATIC; + elements[vertIndex].p[0] = (vec2){cx, cy}; + elements[vertIndex].p[1] = (vec2){x, y}; + } break; + + case STBTT_vcubic: + elements[vertIndex].type = MG_PATH_CUBIC; + elements[vertIndex].p[0] = (vec2){cx, cy}; + elements[vertIndex].p[1] = (vec2){cx1, cy1}; + elements[vertIndex].p[2] = (vec2){x, y}; + break; + } + currentPos = (vec2){x, y}; + } + stbtt_FreeShape(&stbttFontInfo, vertices); + codePoint++; + } + } + u64 h = ((u64)(fontInfo - __mgInfo.fonts))<<32 | ((u64)fontInfo->generation); + return((mg_font){h}); +} + +mg_font_info* mg_get_font_info(mg_font fontHandle) +{ + u32 fontIndex = (u32)(fontHandle.h >> 32); + u32 fontGeneration = (u32)(fontHandle.h & 0xffffffff); + + if( fontIndex >= MG_FONT_MAX_COUNT + || fontIndex >= __mgInfo.fontsNextIndex + || fontGeneration != __mgInfo.fonts[fontIndex].generation) + { + LOG_WARNING("invalid font handle\n"); + return(0); + } + return(&(__mgInfo.fonts[fontIndex])); +} + +void mg_font_destroy(mg_font fontHandle) +{ + mg_font_info* fontInfo = mg_get_font_info(fontHandle); + if(!fontInfo) + { + return; + } + #ifdef DEBUG + if(fontInfo->generation == UINT32_MAX) + { + LOG_ERROR("font info generation wrap around\n"); + } + #endif + fontInfo->generation++; + + free(fontInfo->glyphMap); + free(fontInfo->glyphs); + free(fontInfo->outlines); + + ListPush(&__mgInfo.fontFreeList, &fontInfo->freeListElt); +} + +str32 mg_font_get_glyph_indices_from_font_info(mg_font_info* fontInfo, str32 codePoints, str32 backing) +{ + u64 count = minimum(codePoints.len, backing.len); + + for(int i = 0; irangeCount; rangeIndex++) + { + if(codePoints.ptr[i] >= fontInfo->glyphMap[rangeIndex].range.firstCodePoint + && codePoints.ptr[i] < (fontInfo->glyphMap[rangeIndex].range.firstCodePoint + fontInfo->glyphMap[rangeIndex].range.count)) + { + u32 rangeOffset = codePoints.ptr[i] - fontInfo->glyphMap[rangeIndex].range.firstCodePoint; + glyphIndex = fontInfo->glyphMap[rangeIndex].firstGlyphIndex + rangeOffset; + break; + } + } + if(glyphIndex && !fontInfo->glyphs[glyphIndex].exists) + { + backing.ptr[i] = 0; + } + backing.ptr[i] = glyphIndex; + } + str32 res = {.len = count, .ptr = backing.ptr}; + return(res); +} + +u32 mg_font_get_glyph_index_from_font_info(mg_font_info* fontInfo, utf32 codePoint) +{ + u32 glyphIndex = 0; + str32 codePoints = {1, &codePoint}; + str32 backing = {1, &glyphIndex}; + mg_font_get_glyph_indices_from_font_info(fontInfo, codePoints, backing); + return(glyphIndex); +} + +str32 mg_font_get_glyph_indices(mg_font font, str32 codePoints, str32 backing) +{ + mg_font_info* fontInfo = mg_get_font_info(font); + if(!fontInfo) + { + return((str32){}); + } + return(mg_font_get_glyph_indices_from_font_info(fontInfo, codePoints, backing)); +} + +str32 mg_font_push_glyph_indices(mg_font font, mem_arena* arena, str32 codePoints) +{ + u32* buffer = mem_arena_alloc_array(arena, u32, codePoints.len); + str32 backing = {codePoints.len, buffer}; + return(mg_font_get_glyph_indices(font, codePoints, backing)); +} + +u32 mg_font_get_glyph_index(mg_font font, utf32 codePoint) +{ + u32 glyphIndex = 0; + str32 codePoints = {1, &codePoint}; + str32 backing = {1, &glyphIndex}; + mg_font_get_glyph_indices(font, codePoints, backing); + return(glyphIndex); +} + +mg_glyph_info* mg_font_get_glyph_info(mg_font_info* fontInfo, u32 glyphIndex) +{ + DEBUG_ASSERT(glyphIndex); + DEBUG_ASSERT(glyphIndex < fontInfo->glyphCount); + return(&(fontInfo->glyphs[glyphIndex-1])); +} + +int mg_font_get_extents(mg_font font, mg_font_extents* outExtents) +{ + mg_font_info* fontInfo = mg_get_font_info(font); + if(!fontInfo) + { + return(-1); + } + *outExtents = fontInfo->extents; + return(0); +} + +f32 mg_font_get_scale_for_em_pixels(mg_font font, f32 emSize) +{ + mg_font_info* fontInfo = mg_get_font_info(font); + if(!fontInfo) + { + return(0); + } + return(emSize/fontInfo->unitsPerEm); +} + +void mg_font_get_glyph_extents_from_font_info(mg_font_info* fontInfo, + str32 glyphIndices, + mg_text_extents* outExtents) +{ + for(int i=0; i= fontInfo->glyphCount) + { + continue; + } + mg_glyph_info* glyph = mg_font_get_glyph_info(fontInfo, glyphIndices.ptr[i]); + outExtents[i] = glyph->extents; + } +} + +int mg_font_get_glyph_extents(mg_font font, str32 glyphIndices, mg_text_extents* outExtents) +{ + mg_font_info* fontInfo = mg_get_font_info(font); + if(!fontInfo) + { + return(-1); + } + mg_font_get_glyph_extents_from_font_info(fontInfo, glyphIndices, outExtents); + return(0); +} + +int mg_font_get_codepoint_extents(mg_font font, utf32 codePoint, mg_text_extents* outExtents) +{ + mg_font_info* fontInfo = mg_get_font_info(font); + if(!fontInfo) + { + return(-1); + } + u32 glyphIndex = 0; + str32 codePoints = {1, &codePoint}; + str32 backing = {1, &glyphIndex}; + str32 glyphs = mg_font_get_glyph_indices_from_font_info(fontInfo, codePoints, backing); + mg_font_get_glyph_extents_from_font_info(fontInfo, glyphs, outExtents); + return(0); +} + +mp_rect mg_text_bounding_box(mg_font font, f32 fontSize, str8 text) +{ + if(!text.len || !text.ptr) + { + return((mp_rect){}); + } + + mg_font_info* fontInfo = mg_get_font_info(font); + if(!fontInfo) + { + return((mp_rect){}); + } + + mem_arena* scratch = mem_scratch(); + str32 codePoints = utf8_push_to_codepoints(scratch, text); + str32 glyphIndices = mg_font_push_glyph_indices(font, scratch, codePoints); + + //NOTE(martin): find width of missing character + //TODO(martin): should cache that at font creation... + mg_text_extents missingGlyphExtents; + u32 missingGlyphIndex = mg_font_get_glyph_index_from_font_info(fontInfo, 0xfffd); + + if(missingGlyphIndex) + { + mg_font_get_glyph_extents_from_font_info(fontInfo, (str32){1, &missingGlyphIndex}, &missingGlyphExtents); + } + else + { + //NOTE(martin): could not find replacement glyph, try to get an 'x' to get a somewhat correct width + // to render an empty rectangle. Otherwise just render with the max font width + f32 boxWidth = fontInfo->extents.width * 0.8; + f32 xBearing = fontInfo->extents.width * 0.1; + f32 xAdvance = fontInfo->extents.width; + + missingGlyphIndex = mg_font_get_glyph_index_from_font_info(fontInfo, 'x'); + if(missingGlyphIndex) + { + mg_font_get_glyph_extents_from_font_info(fontInfo, (str32){1, &missingGlyphIndex}, &missingGlyphExtents); + } + else + { + missingGlyphExtents.xBearing = fontInfo->extents.width * 0.1; + missingGlyphExtents.yBearing = 0; + missingGlyphExtents.width = fontInfo->extents.width * 0.8; + missingGlyphExtents.xAdvance = fontInfo->extents.width; + missingGlyphExtents.yAdvance = 0; + } + } + + //NOTE(martin): accumulate text extents + f32 width = 0; + f32 x = 0; + f32 y = 0; + f32 lineHeight = fontInfo->extents.descent + fontInfo->extents.ascent; + + for(int i=0; i= fontInfo->glyphCount) + { + extents = missingGlyphExtents; + } + else + { + glyph = mg_font_get_glyph_info(fontInfo, glyphIndices.ptr[i]); + extents = glyph->extents; + } + x += extents.xAdvance; + y += extents.yAdvance; + + if(glyph && glyph->codePoint == '\n') + { + width = maximum(width, x); + x = 0; + y += lineHeight + fontInfo->extents.leading; + } + } + width = maximum(width, x); + + f32 fontScale = mg_font_get_scale_for_em_pixels(font, fontSize); + mp_rect rect = {0, -fontInfo->extents.ascent * fontScale, width * fontScale, (y + lineHeight) * fontScale }; + return(rect); +} + +//----------------------------------------------------------------------------------------------------------- +// Transform matrix settings +//----------------------------------------------------------------------------------------------------------- + +void mg_matrix_push(mg_canvas handle, mg_mat2x3 matrix) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + + mg_push_command(context, (mg_primitive){.cmd = MG_CMD_MATRIX_PUSH, .matrix = matrix}); +} + +void mg_matrix_pop(mg_canvas handle) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + mg_push_command(context, (mg_primitive){.cmd = MG_CMD_MATRIX_POP}); +} +//----------------------------------------------------------------------------------------------------------- +// Graphics attributes settings +//----------------------------------------------------------------------------------------------------------- + +void mg_set_color(mg_canvas handle, mg_color color) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + context->attributes.color = color; +} + +void mg_set_color_rgba(mg_canvas handle, f32 r, f32 g, f32 b, f32 a) +{ + mg_set_color(handle, (mg_color){r, g, b, a}); +} + +void mg_set_width(mg_canvas handle, f32 width) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + context->attributes.width = width; +} + +void mg_set_tolerance(mg_canvas handle, f32 tolerance) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + context->attributes.tolerance = tolerance; +} + +void mg_set_joint(mg_canvas handle, mg_joint_type joint) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + context->attributes.joint = joint; +} + +void mg_set_max_joint_excursion(mg_canvas handle, f32 maxJointExcursion) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + context->attributes.maxJointExcursion = maxJointExcursion; +} + +void mg_set_cap(mg_canvas handle, mg_cap_type cap) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + context->attributes.cap = cap; +} + +void mg_set_font(mg_canvas handle, mg_font font) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + context->attributes.font = font; +} + +void mg_set_font_size(mg_canvas handle, f32 fontSize) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + context->attributes.fontSize = fontSize; +} + +void mg_set_text_flip(mg_canvas handle, bool flip) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + context->textFlip = flip; +} + + + +mg_color mg_get_color(mg_canvas handle) +{ + mg_color color = {0}; + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(context) + { + color = context->attributes.color; + } + return(color); +} + +f32 mg_get_width(mg_canvas handle) +{ + f32 width = 0; + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(context) + { + width = context->attributes.width; + } + return(width); +} + +f32 mg_get_tolerance(mg_canvas handle) +{ + f32 tolerance = 0; + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(context) + { + tolerance = context->attributes.tolerance; + } + return(tolerance); +} + +mg_joint_type mg_get_joint(mg_canvas handle) +{ + mg_joint_type joint = 0; + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(context) + { + joint = context->attributes.joint; + } + return(joint); +} + +f32 mg_get_max_joint_excursion(mg_canvas handle) +{ + f32 maxJointExcursion = 0; + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(context) + { + maxJointExcursion = context->attributes.maxJointExcursion; + } + return(maxJointExcursion); +} + +mg_cap_type mg_get_cap(mg_canvas handle) +{ + mg_cap_type cap = 0; + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(context) + { + cap = context->attributes.cap; + } + return(cap); +} + +mg_font mg_get_font(mg_canvas handle) +{ + mg_font font = mg_font_nil(); + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(context) + { + font = context->attributes.font; + } + return(font); +} + +f32 mg_get_font_size(mg_canvas handle) +{ + f32 fontSize = 0; + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(context) + { + fontSize = context->attributes.fontSize; + } + return(fontSize); +} + +bool mg_get_text_flip(mg_canvas handle) +{ + bool flip = false; + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(context) + { + flip = context->textFlip; + } + return(flip); +} + + + +//----------------------------------------------------------------------------------------------------------- +// Clip +//----------------------------------------------------------------------------------------------------------- + +void mg_clip_push(mg_canvas handle, f32 x, f32 y, f32 w, f32 h) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + mg_push_command(context, (mg_primitive){.cmd = MG_CMD_CLIP_PUSH, + .rect = (mp_rect){x, y, w, h}}); +} + +void mg_clip_pop(mg_canvas handle) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + mg_push_command(context, (mg_primitive){.cmd = MG_CMD_CLIP_POP}); +} + +//----------------------------------------------------------------------------------------------------------- +// Path construction +//----------------------------------------------------------------------------------------------------------- + +void mg_new_path(mg_canvas_data* context) +{ + context->path.startIndex += context->path.count; + context->path.count = 0; + context->subPathStartPoint = context->subPathLastPoint; + context->path.startPoint = context->subPathStartPoint; +} + +void mg_clear(mg_canvas handle) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + mg_push_command(context, (mg_primitive){.cmd = MG_CMD_CLEAR, .attributes = context->attributes}); +} + +void mg_get_current_position(mg_canvas handle, f32* x, f32* y) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + *x = context->subPathLastPoint.x; + *y = context->subPathLastPoint.y; +} + +void mg_path_push_elements(mg_canvas_data* context, u32 count, mg_path_elt* elements) +{ + ASSERT(context->path.count + context->path.startIndex + count <= MG_MAX_PATH_ELEMENT_COUNT); + memcpy(context->pathElements + context->path.startIndex + context->path.count, elements, count*sizeof(mg_path_elt)); + context->path.count += count; +} + +void mg_path_push_element(mg_canvas_data* context, mg_path_elt elt) +{ + mg_path_push_elements(context, 1, &elt); +} + + + +void mg_move_to(mg_canvas handle, f32 x, f32 y) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + mg_path_push_element(context, ((mg_path_elt){.type = MG_PATH_MOVE, .p[0] = {x, y}})); + context->subPathStartPoint = (vec2){x, y}; + context->subPathLastPoint = (vec2){x, y}; +} + +void mg_line_to(mg_canvas handle, f32 x, f32 y) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + mg_path_push_element(context, ((mg_path_elt){.type = MG_PATH_LINE, .p[0] = {x, y}})); + context->subPathLastPoint = (vec2){x, y}; +} + +void mg_quadratic_to(mg_canvas handle, f32 x1, f32 y1, f32 x2, f32 y2) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + mg_path_push_element(context, ((mg_path_elt){.type = MG_PATH_QUADRATIC, .p = {{x1, y1}, {x2, y2}}})); + context->subPathLastPoint = (vec2){x2, y2}; +} + +void mg_cubic_to(mg_canvas handle, f32 x1, f32 y1, f32 x2, f32 y2, f32 x3, f32 y3) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + mg_path_push_element(context, ((mg_path_elt){.type = MG_PATH_CUBIC, .p = {{x1, y1}, {x2, y2}, {x3, y3}}})); + context->subPathLastPoint = (vec2){x3, y3}; +} + +void mg_close_path(mg_canvas handle) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + if( context->subPathStartPoint.x != context->subPathLastPoint.x + || context->subPathStartPoint.y != context->subPathLastPoint.y) + { + mg_line_to(handle, context->subPathStartPoint.x, context->subPathStartPoint.y); + } + context->subPathStartPoint = context->subPathLastPoint; +} + +mp_rect mg_glyph_outlines_from_font_info(mg_canvas_data* context, mg_font_info* fontInfo, str32 glyphIndices) +{ + mg_canvas contextHandle = mg_canvas_handle_from_ptr(context); + + f32 startX = context->subPathLastPoint.x; + f32 startY = context->subPathLastPoint.y; + f32 maxWidth = 0; + + f32 scale = context->attributes.fontSize/fontInfo->unitsPerEm; + + for(int i=0; isubPathLastPoint.x; + f32 yOffset = context->subPathLastPoint.y; + f32 flip = context->textFlip ? -1 : 1; + + if(!glyphIndex || glyphIndex >= fontInfo->glyphCount) + { + LOG_WARNING("code point is not present in font ranges\n"); + //NOTE(martin): try to find the replacement character + glyphIndex = mg_font_get_glyph_index_from_font_info(fontInfo, 0xfffd); + if(!glyphIndex) + { + //NOTE(martin): could not find replacement glyph, try to get an 'x' to get a somewhat correct width + // to render an empty rectangle. Otherwise just render with the max font width + f32 boxWidth = fontInfo->extents.width * 0.8; + f32 xBearing = fontInfo->extents.width * 0.1; + f32 xAdvance = fontInfo->extents.width; + + glyphIndex = mg_font_get_glyph_index_from_font_info(fontInfo, 'x'); + if(glyphIndex) + { + mg_glyph_info* glyph = &(fontInfo->glyphs[glyphIndex]); + boxWidth = glyph->extents.width; + xBearing = glyph->extents.xBearing; + xAdvance = glyph->extents.xAdvance; + } + f32 oldStrokeWidth = context->attributes.width; + + mg_set_width(contextHandle, boxWidth*0.005); + mg_rectangle_stroke(contextHandle, + xOffset + xBearing * scale, + yOffset, + boxWidth * scale * flip, + fontInfo->extents.capHeight*scale); + + mg_set_width(contextHandle, oldStrokeWidth); + mg_move_to(contextHandle, xOffset + xAdvance * scale, yOffset); + maxWidth = maximum(maxWidth, xOffset + xAdvance*scale - startX); + continue; + } + } + + mg_glyph_info* glyph = mg_font_get_glyph_info(fontInfo, glyphIndex); + + mg_path_push_elements(context, glyph->pathDescriptor.count, fontInfo->outlines + glyph->pathDescriptor.startIndex); + + mg_path_elt* elements = context->pathElements + context->path.count + context->path.startIndex - glyph->pathDescriptor.count; + for(int eltIndex=0; eltIndexpathDescriptor.count; eltIndex++) + { + for(int pIndex = 0; pIndex < 3; pIndex++) + { + elements[eltIndex].p[pIndex].x = elements[eltIndex].p[pIndex].x * scale + xOffset; + elements[eltIndex].p[pIndex].y = elements[eltIndex].p[pIndex].y * scale * flip + yOffset; + } + } + mg_move_to(contextHandle, xOffset + scale*glyph->extents.xAdvance, yOffset); + + maxWidth = maximum(maxWidth, xOffset + scale*glyph->extents.xAdvance - startX); + } + f32 lineHeight = (fontInfo->extents.ascent + fontInfo->extents.descent)*scale; + mp_rect box = {startX, startY, maxWidth, context->subPathLastPoint.y - startY + lineHeight }; + return(box); +} + +mp_rect mg_glyph_outlines(mg_canvas handle, str32 glyphIndices) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return((mp_rect){}); + } + mg_font_info* fontInfo = mg_get_font_info(context->attributes.font); + if(!fontInfo) + { + return((mp_rect){}); + } + return(mg_glyph_outlines_from_font_info(context, fontInfo, glyphIndices)); +} + +void mg_text_outlines(mg_canvas handle, str8 text) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + mg_font_info* fontInfo = mg_get_font_info(context->attributes.font); + if(!fontInfo) + { + return; + } + + mem_arena* scratch = mem_scratch(); + str32 codePoints = utf8_push_to_codepoints(scratch, text); + str32 glyphIndices = mg_font_push_glyph_indices(context->attributes.font, scratch, codePoints); + + mg_glyph_outlines_from_font_info(context, fontInfo, glyphIndices); +} + +//----------------------------------------------------------------------------------------------------------- +// Path primitives commands +//----------------------------------------------------------------------------------------------------------- + +void mg_fill(mg_canvas handle) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + if(context->path.count) + { + mg_push_command(context, ((mg_primitive){.cmd = MG_CMD_FILL, .path = context->path, .attributes = context->attributes})); + mg_new_path(context); + } +} + +void mg_stroke(mg_canvas handle) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + if(context->path.count) + { + mg_push_command(context, ((mg_primitive){.cmd = MG_CMD_STROKE, .path = context->path, .attributes = context->attributes})); + mg_new_path(context); + } +} + +//----------------------------------------------------------------------------------------------------------- +// Fast shapes primitives commands +//----------------------------------------------------------------------------------------------------------- + +void mg_rectangle_fill(mg_canvas handle, f32 x, f32 y, f32 w, f32 h) +{ +// DEBUG_ASSERT(w>=0 && h>=0); + + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + mg_push_command(context, + ((mg_primitive){.cmd = MG_CMD_RECT_FILL, .rect = (mp_rect){x, y, w, h}, .attributes = context->attributes})); +} +void mg_rectangle_stroke(mg_canvas handle, f32 x, f32 y, f32 w, f32 h) +{ +// DEBUG_ASSERT(w>=0 && h>=0); + + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + mg_push_command(context, + ((mg_primitive){.cmd = MG_CMD_RECT_STROKE, .rect = (mp_rect){x, y, w, h}, .attributes = context->attributes})); +} + +void mg_rounded_rectangle_fill(mg_canvas handle, f32 x, f32 y, f32 w, f32 h, f32 r) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + mg_push_command(context, + ((mg_primitive){.cmd = MG_CMD_ROUND_RECT_FILL, + .roundedRect = (mg_rounded_rect){x, y, w, h, r}, + .attributes = context->attributes})); +} + +void mg_rounded_rectangle_stroke(mg_canvas handle, f32 x, f32 y, f32 w, f32 h, f32 r) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + mg_push_command(context, + ((mg_primitive){.cmd = MG_CMD_ROUND_RECT_STROKE, + .roundedRect = (mg_rounded_rect){x, y, w, h, r}, + .attributes = context->attributes})); +} + +void mg_circle_fill(mg_canvas handle, f32 x, f32 y, f32 r) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + mg_push_command(context, + ((mg_primitive){.cmd = MG_CMD_ELLIPSE_FILL, + .rect = (mp_rect){x-r, y-r, 2*r, 2*r}, + .attributes = context->attributes})); +} + +void mg_circle_stroke(mg_canvas handle, f32 x, f32 y, f32 r) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + mg_push_command(context, + ((mg_primitive){.cmd = MG_CMD_ELLIPSE_STROKE, + .rect = (mp_rect){x-r, y-r, 2*r, 2*r}, + .attributes = context->attributes})); +} + +void mg_ellipse_fill(mg_canvas handle, f32 x, f32 y, f32 rx, f32 ry) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + mg_push_command(context, + ((mg_primitive){.cmd = MG_CMD_ELLIPSE_FILL, + .rect = (mp_rect){x-rx, y-ry, 2*rx, 2*ry}, + .attributes = context->attributes})); +} + +void mg_ellipse_stroke(mg_canvas handle, f32 x, f32 y, f32 rx, f32 ry) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + mg_push_command(context, + ((mg_primitive){.cmd = MG_CMD_ELLIPSE_STROKE, + .rect = (mp_rect){x-rx, y-ry, 2*rx, 2*ry}, + .attributes = context->attributes})); +} + +void mg_arc(mg_canvas handle, f32 x, f32 y, f32 r, f32 arcAngle, f32 startAngle) +{ + f32 endAngle = startAngle + arcAngle; + + while(startAngle < endAngle) + { + f32 smallAngle = minimum(endAngle - startAngle, M_PI/4.); + if(smallAngle < 0.001) + { + break; + } + + vec2 v0 = {cos(smallAngle/2), sin(smallAngle/2)}; + vec2 v1 = {(4-v0.x)/3, (1-v0.x)*(3-v0.x)/(3*v0.y)}; + vec2 v2 = {v1.x, -v1.y}; + vec2 v3 = {v0.x, -v0.y}; + + f32 rotAngle = smallAngle/2 + startAngle; + f32 rotCos = cos(rotAngle); + f32 rotSin = sin(rotAngle); + + mg_mat2x3 t = {r*rotCos, -r*rotSin, x, + r*rotSin, r*rotCos, y}; + + v0 = mg_mat2x3_mul(t, v0); + v1 = mg_mat2x3_mul(t, v1); + v2 = mg_mat2x3_mul(t, v2); + v3 = mg_mat2x3_mul(t, v3); + + mg_move_to(handle, v0.x, v0.y); + mg_cubic_to(handle, v1.x, v1.y, v2.x, v2.y, v3.x, v3.y); + + startAngle += smallAngle; + } +} + +//------------------------------------------------------------------------------------------ +//NOTE(martin): images +//------------------------------------------------------------------------------------------ + +#define STB_IMAGE_IMPLEMENTATION +#include"stb_image.h" + +mg_image mg_image_nil() { return((mg_image){0}); } + +mg_image mg_image_handle_from_ptr(mg_canvas_data* canvas, mg_image_data* imageData) +{ + DEBUG_ASSERT( (imageData - canvas->images) >= 0 + && (imageData - canvas->images) < MG_MAX_CONTEXTS); + + u64 h = ((u64)(imageData - canvas->images))<<32 + |((u64)(imageData->generation)); + return((mg_image){.h = h}); +} + +void mg_image_data_recycle(mg_canvas_data* canvas, mg_image_data* image) +{ + image->generation++; + ListPush(&canvas->imageFreeList, &image->listElt); +} + +mg_image mg_image_create_from_rgba8(mg_canvas handle, u32 width, u32 height, u8* bytes) +{ + mg_image image = mg_image_nil(); + + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(context) + { + mg_image_data* imageData = ListPopEntry(&context->imageFreeList, mg_image_data, listElt); + if(!imageData) + { + if(context->imageNextIndex < MG_IMAGE_MAX_COUNT) + { + imageData = &context->images[context->imageNextIndex]; + imageData->generation = 1; + context->imageNextIndex++; + } + else + { + LOG_ERROR("image pool full\n"); + } + } + + if(imageData) + { + if(context->atlasPos.x + width >= MG_ATLAS_SIZE) + { + context->atlasPos.x = 0; + context->atlasPos.y += context->atlasLineHeight; + } + if(context->atlasPos.x + width < MG_ATLAS_SIZE + && context->atlasPos.y + height < MG_ATLAS_SIZE) + { + imageData->rect = (mp_rect){context->atlasPos.x, + context->atlasPos.y, + width, + height}; + + context->atlasPos.x += width; + context->atlasLineHeight = maximum(context->atlasLineHeight, height); + + context->painter->atlasUpload(context->painter, imageData->rect, bytes); + image = mg_image_handle_from_ptr(context, imageData); + } + else + { + mg_image_data_recycle(context, imageData); + } + } + } + return(image); +} + +mg_image mg_image_create_from_data(mg_canvas canvas, str8 data, bool flip) +{ + mg_image image = mg_image_nil(); + int width, height, channels; + + stbi_set_flip_vertically_on_load(flip ? 1 : 0); + u8* pixels = stbi_load_from_memory((u8*)data.ptr, data.len, &width, &height, &channels, 4); + if(pixels) + { + image = mg_image_create_from_rgba8(canvas, width, height, pixels); + free(pixels); + } + return(image); +} + +mg_image mg_image_create_from_file(mg_canvas canvas, str8 path, bool flip) +{ + mg_image image = mg_image_nil(); + int width, height, channels; + + const char* cpath = str8_to_cstring(mem_scratch(), path); + + stbi_set_flip_vertically_on_load(flip ? 1 : 0); + u8* pixels = stbi_load(cpath, &width, &height, &channels, 4); + if(pixels) + { + image = mg_image_create_from_rgba8(canvas, width, height, pixels); + free(pixels); + } + return(image); +} + +void mg_image_destroy(mg_canvas handle, mg_image image) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + //TODO invalidate image handle, maybe free atlas area +} + +void mg_image_draw(mg_canvas handle, mg_image image, mp_rect rect) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + mg_primitive primitive = {.cmd = MG_CMD_IMAGE_DRAW, + .rect = (mp_rect){rect.x, rect.y, rect.h, rect.w}, + .attributes = context->attributes}; + primitive.attributes.image = image; + + mg_push_command(context, primitive); +} + +void mg_rounded_image_draw(mg_canvas handle, mg_image image, mp_rect rect, f32 roundness) +{ + mg_canvas_data* context = mg_canvas_ptr_from_handle(handle); + if(!context) + { + return; + } + mg_primitive primitive = {.cmd = MG_CMD_ROUNDED_IMAGE_DRAW, + .roundedRect = {rect.x, rect.y, rect.h, rect.w, roundness}, + .attributes = context->attributes}; + primitive.attributes.image = image; + + mg_push_command(context, primitive); +} + + +#undef LOG_SUBSYSTEM diff --git a/src/graphics.h b/src/graphics.h new file mode 100644 index 0000000..faebd49 --- /dev/null +++ b/src/graphics.h @@ -0,0 +1,228 @@ + /************************************************************//** +* +* @file: graphics.h +* @author: Martin Fouilleul +* @date: 01/08/2022 +* @revision: +* +*****************************************************************/ +#ifndef __GRAPHICS_H_ +#define __GRAPHICS_H_ + +#include"mp_app.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//------------------------------------------------------------------------------------------ +//NOTE(martin): graphics surface +//------------------------------------------------------------------------------------------ + +typedef struct mg_surface { u64 h; } mg_surface; + +typedef enum { MG_BACKEND_DUMMY, + MG_BACKEND_METAL, + //... + } mg_backend_id; + +void mg_init(); + +mg_surface mg_surface_nil(); +mg_surface mg_surface_create_for_window(mp_window window, mg_backend_id backend); +mg_surface mg_surface_create_for_view(mp_view view, mg_backend_id backend); +void mg_surface_destroy(mg_surface surface); + +void mg_surface_prepare(mg_surface surface); +void mg_surface_present(mg_surface surface); +void mg_surface_resize(mg_surface surface, int width, int height); + +vec2 mg_surface_size(mg_surface surface); + +//------------------------------------------------------------------------------------------ +//NOTE(martin): canvas drawing structs +//------------------------------------------------------------------------------------------ + +typedef struct mg_canvas { u64 h; } mg_canvas; +typedef struct mg_stream { u64 h; } mg_stream; +typedef struct mg_font { u64 h; } mg_font; + +typedef struct mg_mat2x3 +{ + f32 m[6]; +} mg_mat2x3; + +typedef enum { MG_COORDS_2D_DISPLAY_CENTER, + MG_COORDS_2D_DISPLAY_TOP_LEFT, + MG_COORDS_2D_DISPLAY_BOTTOM_LEFT } mg_coordinate_system; + +typedef struct mg_color +{ + union + { + struct + { + f32 r; + f32 g; + f32 b; + f32 a; + }; + f32 c[4]; + }; +} mg_color; + +typedef enum {MG_JOINT_MITER = 0, + MG_JOINT_BEVEL, + MG_JOINT_NONE } mg_joint_type; + +typedef enum {MG_CAP_NONE = 0, + MG_CAP_SQUARE } mg_cap_type; + +typedef struct mg_font_extents +{ + f32 ascent; // the extent above the baseline (by convention a positive value extends above the baseline) + f32 descent; // the extent below the baseline (by convention, positive value extends below the baseline) + f32 leading; // spacing between one row's descent and the next row's ascent + f32 xHeight; // height of the lower case letter 'x' + f32 capHeight; // height of the upper case letter 'M' + f32 width; // maximum width of the font + +} mg_font_extents; + +typedef struct mg_text_extents +{ + f32 xBearing; + f32 yBearing; + f32 width; + f32 height; + f32 xAdvance; + f32 yAdvance; + +} mg_text_extents; + +//------------------------------------------------------------------------------------------ +//NOTE(martin): canvas lifetime and command streams +//------------------------------------------------------------------------------------------ +mg_canvas mg_canvas_create(mg_surface surface, mp_rect viewPort); +void mg_canvas_destroy(mg_canvas context); +void mg_canvas_viewport(mg_canvas, mp_rect viewPort); +void mg_canvas_flush(mg_canvas context); + +mg_stream mg_stream_create(mg_canvas context); +mg_stream mg_stream_swap(mg_canvas context, mg_stream stream); +void mg_stream_append(mg_canvas context, mg_stream stream); + +//------------------------------------------------------------------------------------------ +//NOTE(martin): fonts management +//------------------------------------------------------------------------------------------ +mg_font mg_font_create_from_memory(u32 size, byte* buffer, u32 rangeCount, unicode_range* ranges); +void mg_font_destroy(mg_font font); + +//NOTE(martin): the following int valued functions return -1 if font is invalid or codepoint is not present in font// +//TODO(martin): add enum error codes + +int mg_font_get_extents(mg_font font, mg_font_extents* outExtents); +f32 mg_font_get_scale_for_em_pixels(mg_font font, f32 emSize); + +//NOTE(martin): if you need to process more than one codepoint, first convert your codepoints to glyph indices, then use the +// glyph index versions of the functions, which can take an array of glyph indices. + +str32 mg_font_get_glyph_indices(mg_font font, str32 codePoints, str32 backing); +str32 mg_font_push_glyph_indices(mg_font font, mem_arena* arena, str32 codePoints); +u32 mg_font_get_glyph_index(mg_font font, utf32 codePoint); + +int mg_font_get_codepoint_extents(mg_font font, utf32 codePoint, mg_text_extents* outExtents); + +int mg_font_get_glyph_extents(mg_font font, str32 glyphIndices, mg_text_extents* outExtents); + +mp_rect mg_text_bounding_box(mg_font font, f32 fontSize, str8 text); + +//------------------------------------------------------------------------------------------ +//NOTE(martin): matrix settings +//------------------------------------------------------------------------------------------ +void mg_matrix_push(mg_canvas context, mg_mat2x3 matrix); +void mg_matrix_pop(mg_canvas context); + +//------------------------------------------------------------------------------------------ +//NOTE(martin): clipping +//------------------------------------------------------------------------------------------ +void mg_clip_push(mg_canvas context, f32 x, f32 y, f32 w, f32 h); +void mg_clip_pop(mg_canvas context); + +//------------------------------------------------------------------------------------------ +//NOTE(martin): graphics attributes setting/getting +//------------------------------------------------------------------------------------------ +void mg_set_clear_color(mg_canvas context, mg_color color); +void mg_set_clear_color_rgba(mg_canvas context, f32 r, f32 g, f32 b, f32 a); +void mg_set_color(mg_canvas context, mg_color color); +void mg_set_color_rgba(mg_canvas context, f32 r, f32 g, f32 b, f32 a); +void mg_set_width(mg_canvas context, f32 width); +void mg_set_tolerance(mg_canvas context, f32 tolerance); +void mg_set_joint(mg_canvas context, mg_joint_type joint); +void mg_set_max_joint_excursion(mg_canvas context, f32 maxJointExcursion); +void mg_set_cap(mg_canvas context, mg_cap_type cap); +void mg_set_font(mg_canvas context, mg_font font); +void mg_set_font_size(mg_canvas context, f32 size); +void mg_set_text_flip(mg_canvas context, bool flip); + +mg_color mg_get_clear_color(mg_canvas context); +mg_color mg_get_color(mg_canvas context); +f32 mg_get_width(mg_canvas context); +f32 mg_get_tolerance(mg_canvas context); +mg_joint_type mg_get_joint(mg_canvas context); +f32 mg_get_max_joint_excursion(mg_canvas context); +mg_cap_type mg_get_cap(mg_canvas context); +mg_font mg_get_font(mg_canvas context); +f32 mg_get_font_size(mg_canvas context); +bool mg_get_text_flip(mg_canvas context); +//------------------------------------------------------------------------------------------ +//NOTE(martin): path construction +//------------------------------------------------------------------------------------------ +void mg_get_current_position(mg_canvas context, f32* x, f32* y); +void mg_move_to(mg_canvas context, f32 x, f32 y); +void mg_line_to(mg_canvas context, f32 x, f32 y); +void mg_quadratic_to(mg_canvas context, f32 x1, f32 y1, f32 x2, f32 y2); +void mg_cubic_to(mg_canvas context, f32 x1, f32 y1, f32 x2, f32 y2, f32 x3, f32 y3); +void mg_close_path(mg_canvas context); + +mp_rect mg_glyph_outlines(mg_canvas context, str32 glyphIndices); +void mg_text_outlines(mg_canvas context, str8 string); + +//------------------------------------------------------------------------------------------ +//NOTE(martin): clear/fill/stroke +//------------------------------------------------------------------------------------------ +void mg_clear(mg_canvas context); +void mg_fill(mg_canvas context); +void mg_stroke(mg_canvas context); + +//------------------------------------------------------------------------------------------ +//NOTE(martin): 'fast' shapes primitives +//------------------------------------------------------------------------------------------ +void mg_rectangle_fill(mg_canvas context, f32 x, f32 y, f32 w, f32 h); +void mg_rectangle_stroke(mg_canvas context, f32 x, f32 y, f32 w, f32 h); +void mg_rounded_rectangle_fill(mg_canvas context, f32 x, f32 y, f32 w, f32 h, f32 r); +void mg_rounded_rectangle_stroke(mg_canvas context, f32 x, f32 y, f32 w, f32 h, f32 r); +void mg_ellipse_fill(mg_canvas context, f32 x, f32 y, f32 rx, f32 ry); +void mg_ellipse_stroke(mg_canvas context, f32 x, f32 y, f32 rx, f32 ry); +void mg_circle_fill(mg_canvas context, f32 x, f32 y, f32 r); +void mg_circle_stroke(mg_canvas context, f32 x, f32 y, f32 r); +void mg_arc(mg_canvas handle, f32 x, f32 y, f32 r, f32 arcAngle, f32 startAngle); + +//------------------------------------------------------------------------------------------ +//NOTE(martin): images +//------------------------------------------------------------------------------------------ +typedef struct mg_image { u64 h; } mg_image; + +mg_image mg_image_create_from_rgba8(mg_canvas canvas, u32 width, u32 height, u8* pixels); +mg_image mg_image_create_from_data(mg_canvas canvas, str8 data, bool flip); +mg_image mg_image_create_from_file(mg_canvas canvas, str8 path, bool flip); + +void mg_image_drestroy(mg_canvas canvas, mg_image image); + +void mg_image_draw(mg_canvas canvas, mg_image image, mp_rect rect); +void mg_rounded_image_draw(mg_canvas handle, mg_image image, mp_rect rect, f32 roundness); + +#ifdef __cplusplus +} // extern "C" +#endif +#endif //__GRAPHICS_H_ diff --git a/src/graphics_internal.h b/src/graphics_internal.h new file mode 100644 index 0000000..2f5a215 --- /dev/null +++ b/src/graphics_internal.h @@ -0,0 +1,90 @@ +/************************************************************//** +* +* @file: graphics_internal.h +* @author: Martin Fouilleul +* @date: 01/08/2022 +* @revision: +* +*****************************************************************/ +#ifndef __GRAPHICS_INTERNAL_H_ +#define __GRAPHICS_INTERNAL_H_ + +#include"graphics.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct mg_surface_info mg_surface_info; + +typedef void (*mg_surface_prepare_proc)(mg_surface_info* surface); +typedef void (*mg_surface_present_proc)(mg_surface_info* surface); +typedef void (*mg_surface_resize_proc)(mg_surface_info* surface, u32 width, u32 height); +typedef vec2 (*mg_surface_get_size_proc)(mg_surface_info* surface); +typedef void (*mg_surface_destroy_proc)(mg_surface_info* surface); + +typedef struct mg_surface_info +{ + mg_backend_id backend; + mg_surface_destroy_proc destroy; + mg_surface_prepare_proc prepare; + mg_surface_present_proc present; + mg_surface_resize_proc resize; + mg_surface_get_size_proc getSize; + +} mg_surface_info; + +mg_surface mg_surface_alloc_handle(mg_surface_info* surface); + +typedef struct mgc_vertex_layout +{ + u32 maxVertexCount; + u32 maxIndexCount; + + void* posBuffer; + u32 posStride; + + void* cubicBuffer; + u32 cubicStride; + + void* uvBuffer; + u32 uvStride; + + void* colorBuffer; + u32 colorStride; + + void* zIndexBuffer; + u32 zIndexStride; + + void* clipsBuffer; + u32 clipsStride; + + void* indexBuffer; + u32 indexStride; + +} mgc_vertex_layout; + +typedef struct mg_canvas_painter mg_canvas_painter; +typedef void (*mgc_painter_destroy_proc)(mg_canvas_painter* painter); +typedef void (*mgc_painter_draw_buffers_proc)(mg_canvas_painter* painter, u32 vertexCount, u32 indexCount, mg_color clearColor); +typedef void (*mgc_painter_set_viewport_proc)(mg_canvas_painter* painter, mp_rect viewPort); +typedef void (*mgc_painter_atlas_upload_proc)(mg_canvas_painter* painter, mp_rect rect, u8* bytes); + +typedef struct mg_canvas_painter +{ + mg_surface_info* surface; + mgc_vertex_layout vertexLayout; + mgc_painter_destroy_proc destroy; + mgc_painter_draw_buffers_proc drawBuffers; + mgc_painter_set_viewport_proc setViewPort; + mgc_painter_atlas_upload_proc atlasUpload; + +} mg_canvas_painter; + +#define MG_ATLAS_SIZE 8192 + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif //__GRAPHICS_INTERNAL_H_ diff --git a/src/metal_painter.mm b/src/metal_painter.mm new file mode 100644 index 0000000..34af6a5 --- /dev/null +++ b/src/metal_painter.mm @@ -0,0 +1,419 @@ +/************************************************************//** +* +* @file: graphics.mm +* @author: Martin Fouilleul +* @date: 12/07/2020 +* @revision: +* +*****************************************************************/ +#import +#import +#include + +#include"graphics_internal.h" +#include"macro_helpers.h" +#include"osx_app.h" + +#include"metal_shader.h" + +#define LOG_SUBSYSTEM "Graphics" + +static const int MG_METAL_PAINTER_DEFAULT_BUFFER_LENGTH = 4<<20; + +typedef struct mg_metal_painter +{ + mg_canvas_painter interface; + + mg_metal_surface* surface; + + // permanent metal resources + id tilingPipeline; + id sortingPipeline; + id boxingPipeline; + id computePipeline; + id renderPipeline; + + mp_rect viewPort; + + // textures and buffers + id outTexture; + id atlasTexture; + id vertexBuffer; + id indexBuffer; + id tileCounters; + id tilesArray; + id triangleArray; + id boxArray; + +} mg_metal_painter; + +void mg_metal_painter_draw_buffers(mg_canvas_painter* interface, u32 vertexCount, u32 indexCount, mg_color clearColor) +{ + mg_metal_painter* painter = (mg_metal_painter*)interface; + mg_metal_surface* surface = painter->surface; + + @autoreleasepool + { + if(surface->commandBuffer == nil || surface->commandBuffer == nil) + { + mg_metal_surface_acquire_drawable_and_command_buffer(surface); + } + + ASSERT(indexCount * sizeof(i32) < [painter->indexBuffer length]); + + f32 scale = surface->metalLayer.contentsScale; + vector_uint2 viewportSize = {painter->viewPort.w * scale, painter->viewPort.h * scale}; + + //----------------------------------------------------------- + //NOTE(martin): encode the clear counter + //----------------------------------------------------------- + id blitEncoder = [surface->commandBuffer blitCommandEncoder]; + [blitEncoder fillBuffer: painter->tileCounters range: NSMakeRange(0, RENDERER_MAX_TILES*sizeof(uint)) value: 0]; + [blitEncoder endEncoding]; + + //----------------------------------------------------------- + //NOTE(martin): encode the boxing pass + //----------------------------------------------------------- + id boxEncoder = [surface->commandBuffer computeCommandEncoder]; + [boxEncoder setComputePipelineState: painter->boxingPipeline]; + [boxEncoder setBuffer: painter->vertexBuffer offset:0 atIndex: 0]; + [boxEncoder setBuffer: painter->indexBuffer offset:0 atIndex: 1]; + [boxEncoder setBuffer: painter->triangleArray offset:0 atIndex: 2]; + [boxEncoder setBuffer: painter->boxArray offset:0 atIndex: 3]; + [boxEncoder setBytes: &scale length: sizeof(float) atIndex: 4]; + + MTLSize boxGroupSize = MTLSizeMake(painter->boxingPipeline.maxTotalThreadsPerThreadgroup, 1, 1); + MTLSize boxGridSize = MTLSizeMake(indexCount/3, 1, 1); + + [boxEncoder dispatchThreads: boxGridSize threadsPerThreadgroup: boxGroupSize]; + [boxEncoder endEncoding]; + + //----------------------------------------------------------- + //NOTE(martin): encode the tiling pass + //----------------------------------------------------------- + + id tileEncoder = [surface->commandBuffer computeCommandEncoder]; + [tileEncoder setComputePipelineState: painter->tilingPipeline]; + [tileEncoder setBuffer: painter->boxArray offset:0 atIndex: 0]; + [tileEncoder setBuffer: painter->tileCounters offset:0 atIndex: 1]; + [tileEncoder setBuffer: painter->tilesArray offset:0 atIndex: 2]; + [tileEncoder setBytes: &viewportSize length: sizeof(vector_uint2) atIndex: 3]; + + [tileEncoder dispatchThreads: boxGridSize threadsPerThreadgroup: boxGroupSize]; + [tileEncoder endEncoding]; + + //----------------------------------------------------------- + //NOTE(martin): encode the sorting pass + //----------------------------------------------------------- + + id sortEncoder = [surface->commandBuffer computeCommandEncoder]; + [sortEncoder setComputePipelineState: painter->sortingPipeline]; + [sortEncoder setBuffer: painter->tileCounters offset:0 atIndex: 0]; + [sortEncoder setBuffer: painter->triangleArray offset:0 atIndex: 1]; + [sortEncoder setBuffer: painter->tilesArray offset:0 atIndex: 2]; + [sortEncoder setBytes: &viewportSize length: sizeof(vector_uint2) atIndex: 3]; + + u32 nTilesX = (viewportSize.x + RENDERER_TILE_SIZE - 1)/RENDERER_TILE_SIZE; + u32 nTilesY = (viewportSize.y + RENDERER_TILE_SIZE - 1)/RENDERER_TILE_SIZE; + + MTLSize sortGroupSize = MTLSizeMake(painter->boxingPipeline.maxTotalThreadsPerThreadgroup, 1, 1); + MTLSize sortGridSize = MTLSizeMake(nTilesX*nTilesY, 1, 1); + + [sortEncoder dispatchThreads: sortGridSize threadsPerThreadgroup: sortGroupSize]; + [sortEncoder endEncoding]; + + //----------------------------------------------------------- + //NOTE(martin): create compute encoder and encode commands + //----------------------------------------------------------- + vector_float4 clearColorVec4 = {clearColor.r, clearColor.g, clearColor.b, clearColor.a}; + + id encoder = [surface->commandBuffer computeCommandEncoder]; + [encoder setComputePipelineState:painter->computePipeline]; + [encoder setTexture: painter->outTexture atIndex: 0]; + [encoder setTexture: painter->atlasTexture atIndex: 1]; + [encoder setBuffer: painter->vertexBuffer offset:0 atIndex: 0]; + [encoder setBuffer: painter->tileCounters offset:0 atIndex: 1]; + [encoder setBuffer: painter->tilesArray offset:0 atIndex: 2]; + [encoder setBuffer: painter->triangleArray offset:0 atIndex: 3]; + [encoder setBuffer: painter->boxArray offset:0 atIndex: 4]; + [encoder setBytes: &clearColorVec4 length: sizeof(vector_float4) atIndex: 5]; + + //TODO: check that we don't exceed maxTotalThreadsPerThreadgroup + DEBUG_ASSERT(RENDERER_TILE_SIZE*RENDERER_TILE_SIZE <= painter->computePipeline.maxTotalThreadsPerThreadgroup); + MTLSize threadGridSize = MTLSizeMake(viewportSize.x, viewportSize.y, 1); + MTLSize threadGroupSize = MTLSizeMake(RENDERER_TILE_SIZE, RENDERER_TILE_SIZE, 1); + + [encoder dispatchThreads: threadGridSize threadsPerThreadgroup:threadGroupSize]; + [encoder endEncoding]; + + //----------------------------------------------------------- + //NOTE(martin): acquire drawable, create render encoder to blit texture to framebuffer + //----------------------------------------------------------- + + MTLViewport viewport = {painter->viewPort.x * scale, + painter->viewPort.y * scale, + painter->viewPort.w * scale, + painter->viewPort.h * scale, + 0, + 1}; + + MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor]; + renderPassDescriptor.colorAttachments[0].texture = surface->drawable.texture; + renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; + + id renderEncoder = [surface->commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; + [renderEncoder setViewport: viewport]; + [renderEncoder setRenderPipelineState: painter->renderPipeline]; + [renderEncoder setFragmentTexture: painter->outTexture atIndex: 0]; + [renderEncoder drawPrimitives: MTLPrimitiveTypeTriangle + vertexStart: 0 + vertexCount: 3 ]; + [renderEncoder endEncoding]; + } +} + +void mg_metal_painter_viewport(mg_canvas_painter* interface, mp_rect viewPort) +{ + mg_metal_painter* painter = (mg_metal_painter*)interface; + mg_metal_surface* surface = painter->surface; + + painter->viewPort = viewPort; + + @autoreleasepool + { + f32 scale = surface->metalLayer.contentsScale; + CGSize drawableSize = (CGSize){.width = viewPort.w * scale, .height = viewPort.h * scale}; + + [painter->outTexture release]; + + MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init]; + texDesc.textureType = MTLTextureType2D; + texDesc.storageMode = MTLStorageModePrivate; + texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite; + texDesc.pixelFormat = MTLPixelFormatBGRA8Unorm;// MTLPixelFormatBGRA8Unorm_sRGB; + texDesc.width = drawableSize.width; + texDesc.height = drawableSize.height; + + painter->outTexture = [surface->device newTextureWithDescriptor:texDesc]; + } +} + +void mg_metal_painter_update_vertex_layout(mg_metal_painter* painter) +{ + mg_metal_surface* surface = painter->surface; + + char* vertexBase = (char*)[painter->vertexBuffer contents]; + + painter->interface.vertexLayout = (mgc_vertex_layout){ + .maxVertexCount = MG_METAL_PAINTER_DEFAULT_BUFFER_LENGTH, + .maxIndexCount = MG_METAL_PAINTER_DEFAULT_BUFFER_LENGTH, + .posBuffer = vertexBase + offsetof(mg_vertex, pos), + .posStride = sizeof(mg_vertex), + .cubicBuffer = vertexBase + offsetof(mg_vertex, cubic), + .cubicStride = sizeof(mg_vertex), + .uvBuffer = vertexBase + offsetof(mg_vertex, uv), + .uvStride = sizeof(mg_vertex), + .colorBuffer = vertexBase + offsetof(mg_vertex, color), + .colorStride = sizeof(mg_vertex), + .zIndexBuffer = vertexBase + offsetof(mg_vertex, zIndex), + .zIndexStride = sizeof(mg_vertex), + .clipsBuffer = vertexBase + offsetof(mg_vertex, clip), + .clipsStride = sizeof(mg_vertex), + .indexBuffer = [painter->indexBuffer contents], + .indexStride = sizeof(int)}; +} + + +void mg_metal_painter_destroy(mg_canvas_painter* interface) +{ + mg_metal_painter* painter = (mg_metal_painter*)interface; + + @autoreleasepool + { + [painter->outTexture release]; + [painter->atlasTexture release]; + [painter->vertexBuffer release]; + [painter->indexBuffer release]; + [painter->tilesArray release]; + [painter->triangleArray release]; + [painter->boxArray release]; + [painter->computePipeline release]; + } +} + +void mg_metal_painter_atlas_upload(mg_canvas_painter* interface, mp_rect rect, u8* bytes) +{@autoreleasepool{ + mg_metal_painter* painter = (mg_metal_painter*)interface; + + MTLRegion region = MTLRegionMake2D(rect.x, rect.y, rect.w, rect.h); + [painter->atlasTexture replaceRegion:region + mipmapLevel:0 + withBytes:(void*)bytes + bytesPerRow: 4 * rect.w]; +}} + +extern "C" mg_canvas_painter* mg_metal_painter_create_for_surface(mg_metal_surface* surface, mp_rect viewPort) +{ + mg_metal_painter* painter = malloc_type(mg_metal_painter); + painter->surface = surface; + + //NOTE(martin): setup interface functions + painter->interface.drawBuffers = mg_metal_painter_draw_buffers; + painter->interface.setViewPort = mg_metal_painter_viewport; + painter->interface.destroy = mg_metal_painter_destroy; + painter->interface.atlasUpload = mg_metal_painter_atlas_upload; + + painter->viewPort = viewPort; + + @autoreleasepool + { + f32 scale = surface->metalLayer.contentsScale; + CGSize drawableSize = (CGSize){.width = viewPort.w * scale, .height = viewPort.h * scale}; + + //----------------------------------------------------------- + //NOTE(martin): create our output texture + //----------------------------------------------------------- + MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init]; + texDesc.textureType = MTLTextureType2D; + texDesc.storageMode = MTLStorageModePrivate; + texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite; + texDesc.pixelFormat = MTLPixelFormatBGRA8Unorm;// MTLPixelFormatBGRA8Unorm_sRGB; + texDesc.width = drawableSize.width; + texDesc.height = drawableSize.height; + + painter->outTexture = [surface->device newTextureWithDescriptor:texDesc]; + //TODO(martin): retain ? + + //----------------------------------------------------------- + //NOTE(martin): create our atlas texture + //----------------------------------------------------------- + texDesc.textureType = MTLTextureType2D; + texDesc.storageMode = MTLStorageModeManaged; + texDesc.usage = MTLTextureUsageShaderRead; + texDesc.pixelFormat = MTLPixelFormatRGBA8Unorm; //MTLPixelFormatBGRA8Unorm; + texDesc.width = MG_ATLAS_SIZE; + texDesc.height = MG_ATLAS_SIZE; + + painter->atlasTexture = [surface->device newTextureWithDescriptor:texDesc]; + + //----------------------------------------------------------- + //NOTE(martin): create buffers for vertex and index + //----------------------------------------------------------- + + MTLResourceOptions bufferOptions = MTLResourceCPUCacheModeWriteCombined + | MTLResourceStorageModeShared; + + painter->indexBuffer = [surface->device newBufferWithLength: MG_METAL_PAINTER_DEFAULT_BUFFER_LENGTH*sizeof(int) + options: bufferOptions]; + + painter->vertexBuffer = [surface->device newBufferWithLength: MG_METAL_PAINTER_DEFAULT_BUFFER_LENGTH*sizeof(mg_vertex) + options: bufferOptions]; + + painter->tilesArray = [surface->device newBufferWithLength: RENDERER_TILE_BUFFER_SIZE*sizeof(int)*RENDERER_MAX_TILES + options: MTLResourceStorageModePrivate]; + + painter->triangleArray = [surface->device newBufferWithLength: MG_METAL_PAINTER_DEFAULT_BUFFER_LENGTH*sizeof(mg_triangle_data) + options: MTLResourceStorageModePrivate]; + + painter->boxArray = [surface->device newBufferWithLength: MG_METAL_PAINTER_DEFAULT_BUFFER_LENGTH*sizeof(vector_float4) + options: MTLResourceStorageModePrivate]; + + //TODO(martin): retain ? + //----------------------------------------------------------- + //NOTE(martin): create and initialize tile counters + //----------------------------------------------------------- + painter->tileCounters = [surface->device newBufferWithLength: RENDERER_MAX_TILES*sizeof(uint) + options: MTLResourceStorageModePrivate]; + id commandBuffer = [surface->commandQueue commandBuffer]; + id blitEncoder = [commandBuffer blitCommandEncoder]; + [blitEncoder fillBuffer: painter->tileCounters range: NSMakeRange(0, RENDERER_MAX_TILES*sizeof(uint)) value: 0]; + [blitEncoder endEncoding]; + [commandBuffer commit]; + + //----------------------------------------------------------- + //NOTE(martin): load the library + //----------------------------------------------------------- + + //TODO(martin): filepath magic to find metallib path when not in the working directory + char* shaderPath = 0; + mp_app_get_resource_path("../resources/metal_shader.metallib", &shaderPath); + NSString* metalFileName = [[NSString alloc] initWithCString: shaderPath encoding: NSUTF8StringEncoding]; + free(shaderPath); + NSError* err = 0; + id library = [surface->device newLibraryWithFile: metalFileName error:&err]; + if(err != nil) + { + const char* errStr = [[err localizedDescription] UTF8String]; + LOG_ERROR("error : %s\n", errStr); + return(0); + } + id tilingFunction = [library newFunctionWithName:@"TileKernel"]; + id sortingFunction = [library newFunctionWithName:@"SortKernel"]; + id boxingFunction = [library newFunctionWithName:@"BoundingBoxKernel"]; + id computeFunction = [library newFunctionWithName:@"RenderKernel"]; + id vertexFunction = [library newFunctionWithName:@"VertexShader"]; + id fragmentFunction = [library newFunctionWithName:@"FragmentShader"]; + + //----------------------------------------------------------- + //NOTE(martin): setup our data layout and pipeline state + //----------------------------------------------------------- + NSError* error = NULL; + painter->computePipeline = [surface->device newComputePipelineStateWithFunction: computeFunction + error:&error]; + ASSERT(painter->computePipeline); + + MTLComputePipelineDescriptor* tilingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init]; + tilingPipelineDesc.computeFunction = tilingFunction; +// tilingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true; + + painter->tilingPipeline = [surface->device newComputePipelineStateWithDescriptor: tilingPipelineDesc + options: MTLPipelineOptionNone + reflection: nil + error: &error]; + + MTLComputePipelineDescriptor* sortingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init]; + sortingPipelineDesc.computeFunction = sortingFunction; +// sortingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true; + + painter->sortingPipeline = [surface->device newComputePipelineStateWithDescriptor: sortingPipelineDesc + options: MTLPipelineOptionNone + reflection: nil + error: &error]; + + MTLComputePipelineDescriptor* boxingPipelineDesc = [[MTLComputePipelineDescriptor alloc] init]; + boxingPipelineDesc.computeFunction = boxingFunction; +// boxingPipelineDesc.threadGroupSizeIsMultipleOfThreadExecutionWidth = true; + + painter->boxingPipeline = [surface->device newComputePipelineStateWithDescriptor: boxingPipelineDesc + options: MTLPipelineOptionNone + reflection: nil + error: &error]; + //----------------------------------------------------------- + //NOTE(martin): setup our render pipeline state + //----------------------------------------------------------- + // create and initialize the pipeline state descriptor + MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; + pipelineStateDescriptor.label = @"My simple pipeline"; + pipelineStateDescriptor.vertexFunction = vertexFunction; + pipelineStateDescriptor.fragmentFunction = fragmentFunction; + pipelineStateDescriptor.colorAttachments[0].pixelFormat = surface->metalLayer.pixelFormat; + + // create render pipeline + painter->renderPipeline = [surface->device newRenderPipelineStateWithDescriptor: pipelineStateDescriptor error:&err]; + if(err != nil) + { + const char* errStr = [[err localizedDescription] UTF8String]; + const char* descStr = [[err localizedFailureReason] UTF8String]; + const char* recovStr = [[err localizedRecoverySuggestion] UTF8String]; + LOG_ERROR("(%li) %s. %s. %s\n", [err code], errStr, descStr, recovStr); + return(0); + } + } + + mg_metal_painter_update_vertex_layout(painter); + + return((mg_canvas_painter*)painter); +} + + +#undef LOG_SUBSYSTEM diff --git a/src/metal_shader.h b/src/metal_shader.h new file mode 100644 index 0000000..67c4a27 --- /dev/null +++ b/src/metal_shader.h @@ -0,0 +1,49 @@ +/************************************************************//** +* +* @file: metal_shader.h +* @author: Martin Fouilleul +* @date: 01/08/2022 +* @revision: +* +*****************************************************************/ +#ifndef __METAL_RENDERER_H_ +#define __METAL_RENDERER_H_ + +#include + +#define RENDERER_TILE_BUFFER_SIZE 4096 +#define RENDERER_TILE_SIZE 16 +#define RENDERER_MAX_TILES 65536 + +#define RENDERER_DEBUG_TILE_VISITED 0xf00d +#define RENDERER_DEBUG_TILE_BUFFER_OVERFLOW 0xdead + +typedef struct mg_vertex +{ + vector_float2 pos; // position + vector_float4 cubic; // canonical implicit curve space coordinates + vector_float2 uv; // texture coordinates + vector_float4 color; + vector_float4 clip; + int zIndex; + +} mg_vertex; + +typedef struct mg_triangle_data +{ + uint i0; + uint i1; + uint i2; + uint zIndex; + + vector_float2 p0; + vector_float2 p1; + vector_float2 p2; + + int bias0; + int bias1; + int bias2; + +} mg_triangle_data; + +#endif //__METAL_RENDERER_H_ diff --git a/src/metal_shader.metal b/src/metal_shader.metal new file mode 100644 index 0000000..86a9b27 --- /dev/null +++ b/src/metal_shader.metal @@ -0,0 +1,320 @@ + +#include +#include + +#include"metal_shader.h" + +using namespace metal; + +struct vs_out +{ + float4 pos [[position]]; + float2 uv; +}; + +vertex vs_out VertexShader(ushort vid [[vertex_id]]) +{ + vs_out out; + out.uv = float2((vid << 1) & 2, vid & 2); + out.pos = float4(out.uv * float2(2, 2) + float2(-1, -1), 0, 1); + return(out); +} + +fragment float4 FragmentShader(vs_out i [[stage_in]], texture2d tex [[texture(0)]]) +{ + constexpr sampler smp(mip_filter::nearest, mag_filter::linear, min_filter::linear); + return(float4(tex.sample(smp, i.uv).rgb, 1)); +} + + +bool is_top_left(float2 a, float2 b) +{ + return( (a.y == b.y && b.x < a.x) + ||(b.y < a.y)); +} + +kernel void BoundingBoxKernel(constant mg_vertex* vertexBuffer [[buffer(0)]], + constant uint* indexBuffer [[buffer(1)]], + device mg_triangle_data* triangleArray [[buffer(2)]], + device float4* boxArray [[buffer(3)]], + constant float* contentsScaling [[buffer(4)]], + uint gid [[thread_position_in_grid]]) +{ + uint triangleIndex = gid; + uint vertexIndex = triangleIndex*3; + + uint i0 = indexBuffer[vertexIndex]; + uint i1 = indexBuffer[vertexIndex+1]; + uint i2 = indexBuffer[vertexIndex+2]; + + float2 p0 = vertexBuffer[i0].pos * contentsScaling[0]; + float2 p1 = vertexBuffer[i1].pos * contentsScaling[0]; + float2 p2 = vertexBuffer[i2].pos * contentsScaling[0]; + + //NOTE(martin): compute triangle bounding box + float2 boxMin = min(min(p0, p1), p2); + float2 boxMax = max(max(p0, p1), p2); + + //NOTE(martin): clip bounding box against clip rect + vector_float4 clip = contentsScaling[0]*vertexBuffer[indexBuffer[vertexIndex]].clip; + float2 clipMin(clip.x, clip.y); + float2 clipMax(clip.x + clip.z-1, clip.y + clip.w-1); + + //NOTE(martin): intersect with current clip + boxMin = max(boxMin, clipMin); + boxMax = min(boxMax, clipMax); + + //NOTE(martin): reorder triangle counter-clockwise and compute bias for each edge + float cw = (p1 - p0).x*(p2 - p0).y - (p1 - p0).y*(p2 - p0).x; + if(cw < 0) + { + uint tmpIndex = i1; + i1 = i2; + i2 = tmpIndex; + + float2 tmpPoint = p1; + p1 = p2; + p2 = tmpPoint; + } + int bias0 = is_top_left(p1, p2) ? 0 : -1; + int bias1 = is_top_left(p2, p0) ? 0 : -1; + int bias2 = is_top_left(p0, p1) ? 0 : -1; + + //NOTE(martin): fill triangle data + boxArray[triangleIndex] = float4(boxMin.x, boxMin.y, boxMax.x, boxMax.y); + + triangleArray[triangleIndex].zIndex = vertexBuffer[i0].zIndex; + triangleArray[triangleIndex].i0 = i0; + triangleArray[triangleIndex].i1 = i1; + triangleArray[triangleIndex].i2 = i2; + triangleArray[triangleIndex].p0 = p0; + triangleArray[triangleIndex].p1 = p1; + triangleArray[triangleIndex].p2 = p2; + triangleArray[triangleIndex].bias0 = bias0; + triangleArray[triangleIndex].bias1 = bias1; + triangleArray[triangleIndex].bias2 = bias2; +} + +kernel void TileKernel(const device float4* boxArray [[buffer(0)]], + device volatile atomic_uint* tileCounters [[buffer(1)]], + device uint* tilesArray [[buffer(2)]], + constant vector_uint2* viewport [[buffer(3)]], + uint gid [[thread_position_in_grid]]) +{ + uint2 tilesMatrixDim = (*viewport + RENDERER_TILE_SIZE - 1) / RENDERER_TILE_SIZE; + uint nTilesX = tilesMatrixDim.x; + uint nTilesY = tilesMatrixDim.y; + + uint triangleIndex = gid; + uint4 box = uint4(floor(boxArray[triangleIndex]/RENDERER_TILE_SIZE)); + uint xMin = max((uint)0, box.x); + uint yMin = max((uint)0, box.y); + uint xMax = min(box.z, nTilesX-1); + uint yMax = min(box.w, nTilesY-1); + + for(uint y = yMin; y <= yMax; y++) + { + for(uint x = xMin ; x <= xMax; x++) + { + uint tileIndex = y*nTilesX + x; + device uint* tileBuffer = tilesArray + tileIndex*RENDERER_TILE_BUFFER_SIZE; + uint counter = atomic_fetch_add_explicit(&(tileCounters[tileIndex]), 1, memory_order_relaxed); + tileBuffer[counter] = triangleIndex; + } + } +} + +kernel void SortKernel(const device uint* tileCounters [[buffer(0)]], + const device mg_triangle_data* triangleArray [[buffer(1)]], + device uint* tilesArray [[buffer(2)]], + constant vector_uint2* viewport [[buffer(3)]], + uint gid [[thread_position_in_grid]]) +{ + uint tileIndex = gid; + device uint* tileBuffer = tilesArray + tileIndex*RENDERER_TILE_BUFFER_SIZE; + uint tileBufferSize = tileCounters[tileIndex]; + + for(int eltIndex=0; eltIndex < (int)tileBufferSize; eltIndex++) + { + uint elt = tileBuffer[eltIndex]; + uint eltZIndex = triangleArray[elt].zIndex; + + int backIndex = eltIndex-1; + for(; backIndex >= 0; backIndex--) + { + uint backElt = tileBuffer[backIndex]; + uint backEltZIndex = triangleArray[backElt].zIndex; + if(eltZIndex >= backEltZIndex) + { + break; + } + else + { + tileBuffer[backIndex+1] = backElt; + } + } + tileBuffer[backIndex+1] = elt; + } +} + +float orient2d(float2 a, float2 b, float2 c) +{ + ////////////////////////////////////////////////////////////////////////////////////////// + //TODO(martin): FIX this. This is a **horrible** quick hack to fix the precision issues + // arising when a, b, and c are close. But it degrades when a, c, and c + // are big. The proper solution is to change the expression to avoid + // precision loss but I'm too busy/lazy to do it now. + ////////////////////////////////////////////////////////////////////////////////////////// + a *= 10; + b *= 10; + c *= 10; + return((b.x-a.x)*(c.y-a.y) - (b.y-a.y)*(c.x-a.x)); +} + +kernel void RenderKernel(texture2d outTexture [[texture(0)]], + texture2d texAtlas [[texture(1)]], + const device mg_vertex* vertexBuffer [[buffer(0)]], + device uint* tileCounters [[buffer(1)]], + const device uint* tilesArray [[buffer(2)]], + const device mg_triangle_data* triangleArray [[buffer(3)]], + const device float4* boxArray [[buffer(4)]], + constant vector_float4* clearColor [[buffer(5)]], + uint2 gid [[thread_position_in_grid]], + uint2 tgid [[threadgroup_position_in_grid]], + uint2 threadsPerThreadgroup [[threads_per_threadgroup]], + uint2 gridSize [[threads_per_grid]]) +{ + //TODO: guard against thread group size not equal to tile size? + const uint2 tilesMatrixDim = (gridSize + RENDERER_TILE_SIZE - 1) / RENDERER_TILE_SIZE; + const uint2 tilePos = tgid * threadsPerThreadgroup / RENDERER_TILE_SIZE; + const uint tileIndex = tilePos.y * tilesMatrixDim.x + tilePos.x; + const device uint* tileBuffer = tilesArray + tileIndex * RENDERER_TILE_BUFFER_SIZE; + + const uint tileBufferSize = tileCounters[tileIndex]; + +#ifdef RENDERER_DEBUG_TILES + //NOTE(martin): color code debug values and show the tile grid + if((gid.x % RENDERER_TILE_SIZE == 0) || (gid.y % RENDERER_TILE_SIZE == 0)) + { + outTexture.write(float4(0, 0, 0, 1), gid); + return; + } + if(tileBufferSize <= 0) + { + outTexture.write(float4(0, 1, 0, 1), gid); + return; + } +#endif + const float2 sampleOffsets[6] = { float2(5./12, 5./12), + float2(-3./12, 3./12), + float2(1./12, 1./12), + float2(3./12, -1./12), + float2(-5./12, -3./12), + float2(-1./12, -5./12)}; + + int zIndices[6]; + uint flipCounts[6]; + float4 pixelColors[6]; + float4 nextColors[6]; + for(int i=0; i<6; i++) + { + zIndices[i] = -1; + flipCounts[i] = 0; + pixelColors[i] = *clearColor; + nextColors[i] = *clearColor; + } + + for(uint tileBufferIndex=0; tileBufferIndex < tileBufferSize; tileBufferIndex++) + { + float4 box = boxArray[tileBuffer[tileBufferIndex]]; + const device mg_triangle_data* triangle = &triangleArray[tileBuffer[tileBufferIndex]]; + + float2 p0 = triangle->p0; + float2 p1 = triangle->p1; + float2 p2 = triangle->p2; + + int bias0 = triangle->bias0; + int bias1 = triangle->bias1; + int bias2 = triangle->bias2; + + const device mg_vertex* v0 = &(vertexBuffer[triangle->i0]); + const device mg_vertex* v1 = &(vertexBuffer[triangle->i1]); + const device mg_vertex* v2 = &(vertexBuffer[triangle->i2]); + + float4 cubic0 = v0->cubic; + float4 cubic1 = v1->cubic; + float4 cubic2 = v2->cubic; + + float2 uv0 = v0->uv; + float2 uv1 = v1->uv; + float2 uv2 = v2->uv; + + int zIndex = v0->zIndex; + float4 color = v0->color; + + for(int i=0; i<6; i++) + { + float2 samplePoint = (float2)gid + sampleOffsets[i]; + + //NOTE(martin): cull if pixel is outside box + if(samplePoint.x < box.x || samplePoint.x > box.z || samplePoint.y < box.y || samplePoint.y > box.w) + { + continue; + } + + float w0 = orient2d(p1, p2, samplePoint); + float w1 = orient2d(p2, p0, samplePoint); + float w2 = orient2d(p0, p1, samplePoint); + + if(((int)w0+bias0) >= 0 && ((int)w1+bias1) >= 0 && ((int)w2+bias2) >= 0) + { + float4 cubic = (cubic0*w0 + cubic1*w1 + cubic2*w2)/(w0+w1+w2); + float2 uv = (uv0*w0 + uv1*w1 + uv2*w2)/(w0+w1+w2); + + constexpr sampler smp(mip_filter::nearest, mag_filter::linear, min_filter::linear); + float4 texColor = texAtlas.sample(smp, uv); + + //TODO(martin): this is a quick and dirty fix for solid polygons where we use + // cubic = (1, 1, 1, 1) on all vertices, which can cause small errors to + // flip the sign. + // We should really use another value that always lead to <= 0, but we must + // make sure we never share these vertices with bezier shapes. + // Alternatively, an ugly (but maybe less than this one) solution would be + // to check if uvs are equal on all vertices of the triangle and always render + // those triangles. + float eps = 0.0001; + if(cubic.w*(cubic.x*cubic.x*cubic.x - cubic.y*cubic.z) <= eps) + { + if(zIndex == zIndices[i]) + { + flipCounts[i]++; + } + else + { + if(flipCounts[i] & 0x01) + { + pixelColors[i] = nextColors[i]; + } + + float4 nextCol = color*texColor; + nextColors[i] = pixelColors[i]*(1-nextCol.a) +nextCol.a*nextCol; + + zIndices[i] = zIndex; + flipCounts[i] = 1; + } + } + } + } + } + float4 out = float4(0, 0, 0, 0); + for(int i=0; i<6; i++) + { + if(flipCounts[i] & 0x01) + { + pixelColors[i] = nextColors[i]; + } + out += pixelColors[i]; + } + out = float4(out.xyz/6, 1); + outTexture.write(out, gid); +} diff --git a/src/metal_surface.mm b/src/metal_surface.mm new file mode 100644 index 0000000..d5ab513 --- /dev/null +++ b/src/metal_surface.mm @@ -0,0 +1,231 @@ +/************************************************************//** +* +* @file: graphics.mm +* @author: Martin Fouilleul +* @date: 12/07/2020 +* @revision: +* +*****************************************************************/ +#import +#import +#include + +#include"graphics_internal.h" +#include"macro_helpers.h" +#include"osx_app.h" + +#define LOG_SUBSYSTEM "Graphics" + +static const u32 MP_METAL_MAX_DRAWABLES_IN_FLIGHT = 3; + +typedef struct mg_metal_surface +{ + mg_surface_info interface; + + // permanent metal resources + NSView* view; + id device; + CAMetalLayer* metalLayer; + id commandQueue; + + // transient metal resources + id drawable; + id commandBuffer; + + dispatch_semaphore_t drawableSemaphore; + +} mg_metal_surface; + +void mg_metal_surface_acquire_drawable_and_command_buffer(mg_metal_surface* surface) +{@autoreleasepool{ + /*WARN(martin): this is super important + When the app is put in the background, it seems that if there are buffers in flight, the drawables to + can be leaked. This causes the gpu to allocate more and more drawables, until the app crashes. + (note: the drawable objects themselves are released once the app comes back to the forefront, but the + memory allocated in the GPU is never freed...) + + In background the gpu seems to create drawable if none is available instead of actually + blocking on nextDrawable. These drawable never get freed. + This is not a problem if our shader is fast enough, since a previous drawable becomes + available before we finish the frame. But we want to protect against it anyway + + The normal blocking mechanism of nextDrawable seems useless, so we implement our own scheme by + counting the number of drawables available with a semaphore that gets decremented here and + incremented in the presentedHandler of the drawable. + Thus we ensure that we don't consume more drawables than we are able to draw. + + //TODO: we _also_ should stop trying to render if we detect that the app is in the background + or occluded, but we can't count only on this because there is a potential race between the + notification of background mode and the rendering. + + //TODO: We should set a reasonable timeout and skip the frame and log an error in case we are stalled + for too long. + */ + dispatch_semaphore_wait(surface->drawableSemaphore, DISPATCH_TIME_FOREVER); + + surface->drawable = [surface->metalLayer nextDrawable]; + ASSERT(surface->drawable != nil); + + //TODO: make this a weak reference if we use ARC + dispatch_semaphore_t semaphore = surface->drawableSemaphore; + [surface->drawable addPresentedHandler:^(id drawable){ + dispatch_semaphore_signal(semaphore); + }]; + + //NOTE(martin): create a command buffer + surface->commandBuffer = [surface->commandQueue commandBuffer]; + + [surface->commandBuffer retain]; + [surface->drawable retain]; +}} + +void mg_metal_surface_prepare(mg_surface_info* interface) +{ + mg_metal_surface* surface = (mg_metal_surface*)interface; + mg_metal_surface_acquire_drawable_and_command_buffer(surface); +} + +void mg_metal_surface_present(mg_surface_info* interface) +{ + mg_metal_surface* surface = (mg_metal_surface*)interface; + @autoreleasepool + { + //NOTE(martin): present drawable and commit command buffer + [surface->commandBuffer presentDrawable: surface->drawable]; + [surface->commandBuffer commit]; + [surface->commandBuffer waitUntilCompleted]; + + //NOTE(martin): acquire next frame resources + [surface->commandBuffer release]; + surface->commandBuffer = nil; + [surface->drawable release]; + surface->drawable = nil; + } +} + +void mg_metal_surface_destroy(mg_surface_info* interface) +{ + mg_metal_surface* surface = (mg_metal_surface*)interface; + + @autoreleasepool + { + [surface->commandQueue release]; + [surface->metalLayer release]; + [surface->device release]; + } +} + +void mg_metal_surface_resize(mg_surface_info* interface, u32 width, u32 height) +{ + mg_metal_surface* surface = (mg_metal_surface*)interface; + + @autoreleasepool + { + //TODO(martin): actually detect scaling + CGRect frame = {{0, 0}, {width, height}}; + [surface->view setFrame: frame]; + f32 scale = surface->metalLayer.contentsScale; + CGSize drawableSize = (CGSize){.width = width * scale, .height = height * scale}; + surface->metalLayer.drawableSize = drawableSize; + } +} + +vec2 mg_metal_surface_get_size(mg_surface_info* interface) +{ + mg_metal_surface* surface = (mg_metal_surface*)interface; + + @autoreleasepool + { + //TODO(martin): actually detect scaling + CGRect frame = surface->view.frame; + return((vec2){frame.size.width, frame.size.height}); + } +} + +static const f32 MG_METAL_SURFACE_CONTENTS_SCALING = 2; + +extern "C" mg_surface mg_metal_surface_create_for_view(mp_view view) +{ + mp_view_data* viewData = mp_view_ptr_from_handle(view); + if(!viewData) + { + return(mg_surface_nil()); + } + else + { + mg_metal_surface* surface = (mg_metal_surface*)malloc(sizeof(mg_metal_surface)); + + //NOTE(martin): setup interface functions + surface->interface.backend = MG_BACKEND_METAL; + surface->interface.destroy = mg_metal_surface_destroy; + surface->interface.prepare = mg_metal_surface_prepare; + surface->interface.present = mg_metal_surface_present; + surface->interface.resize = mg_metal_surface_resize; + surface->interface.getSize = mg_metal_surface_get_size; + + @autoreleasepool + { + surface->drawableSemaphore = dispatch_semaphore_create(MP_METAL_MAX_DRAWABLES_IN_FLIGHT); + + //----------------------------------------------------------- + //NOTE(martin): create a metal device and a metal layer and + //----------------------------------------------------------- + surface->device = MTLCreateSystemDefaultDevice(); + [surface->device retain]; + surface->metalLayer = [CAMetalLayer layer]; + [surface->metalLayer retain]; + surface->metalLayer.device = surface->device; + + surface->view = viewData->nsView; + + //----------------------------------------------------------- + //NOTE(martin): set the size and scaling + //----------------------------------------------------------- + CGSize size = surface->view.bounds.size; + size.width *= MG_METAL_SURFACE_CONTENTS_SCALING; + size.height *= MG_METAL_SURFACE_CONTENTS_SCALING; + surface->metalLayer.drawableSize = size; + surface->metalLayer.contentsScale = MG_METAL_SURFACE_CONTENTS_SCALING; + + surface->metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm; + + surface->view.wantsLayer = YES; + surface->view.layer = surface->metalLayer; + + //NOTE(martin): handling resizing + surface->view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize; + surface->metalLayer.autoresizingMask = kCALayerHeightSizable | kCALayerWidthSizable; + surface->metalLayer.needsDisplayOnBoundsChange = YES; + + //----------------------------------------------------------- + //NOTE(martin): create a command queue + //----------------------------------------------------------- + surface->commandQueue = [surface->device newCommandQueue]; + [surface->commandQueue retain]; + + //NOTE(martin): command buffer and drawable are set on demand and at the end of each present() call + surface->drawable = nil; + surface->commandBuffer = nil; + } + + mg_surface handle = mg_surface_alloc_handle((mg_surface_info*)surface); + viewData->surface = handle; + + return(handle); + } +} + +extern "C" mg_surface mg_metal_surface_create_for_window(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(!windowData) + { + return(mg_surface_nil()); + } + else + { + return(mg_metal_surface_create_for_view(windowData->mainView)); + } +} + +#undef LOG_SUBSYSTEM diff --git a/src/milepost.c b/src/milepost.c new file mode 100644 index 0000000..7c4da4d --- /dev/null +++ b/src/milepost.c @@ -0,0 +1,29 @@ +/************************************************************//** +* +* @file: milepost.c +* @author: Martin Fouilleul +* @date: 13/02/2021 +* @revision: +* +*****************************************************************/ + +#include"util/debug_log.c" +#include"memory.c" +#include"strings.c" +#include"ringbuffer.c" +#include"util/utf8.c" +#include"util/hash.c" + +#include"platform/unix_base_allocator.c" + +#include"graphics.c" +#include"ui.c" + +//TODO: guard these under platform-specific #ifdefs +#include"platform/osx_clock.c" +/* + +#include"platform/unix_rng.c" +#include"platform/posix_thread.c" +#include"platform/posix_socket.c" +*/ diff --git a/src/milepost.h b/src/milepost.h new file mode 100644 index 0000000..e4603be --- /dev/null +++ b/src/milepost.h @@ -0,0 +1,31 @@ +/************************************************************//** +* +* @file: milepost.h +* @author: Martin Fouilleul +* @date: 13/02/2021 +* @revision: +* +*****************************************************************/ +#ifndef __MILEPOST_H_ +#define __MILEPOST_H_ + + +#include"typedefs.h" +#include"macro_helpers.h" +#include"debug_log.h" +#include"lists.h" +#include"memory.h" +#include"strings.h" +#include"utf8.h" + +#include"platform_clock.h" +/* +#include"platform_rng.h" +#include"platform_socket.h" +#include"platform_thread.h" +*/ +#include"mp_app.h" +#include"graphics.h" +#include"ui.h" + +#endif //__MILEPOST_H_ diff --git a/src/milepost.mm b/src/milepost.mm new file mode 100644 index 0000000..98124c6 --- /dev/null +++ b/src/milepost.mm @@ -0,0 +1,12 @@ +/************************************************************//** +* +* @file: milepost.mm +* @author: Martin Fouilleul +* @date: 13/02/2021 +* @revision: +* +*****************************************************************/ + +#include"osx_app.mm" +#include"metal_surface.mm" +#include"metal_painter.mm" diff --git a/src/mp_app.h b/src/mp_app.h new file mode 100644 index 0000000..ab54328 --- /dev/null +++ b/src/mp_app.h @@ -0,0 +1,413 @@ +/************************************************************//** +* +* @file: platform_app.h +* @author: Martin Fouilleul +* @date: 16/05/2020 +* @revision: +* +*****************************************************************/ +#ifndef __PLATFORM_APP_H_ +#define __PLATFORM_APP_H_ + +#include"typedefs.h" +#include"utf8.h" +#include"lists.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//-------------------------------------------------------------------- +// Typedefs, enums and constants +//-------------------------------------------------------------------- + +typedef struct mp_window { u64 h; } mp_window; +typedef struct mp_view { u64 h; } mp_view; + +typedef enum { MP_MOUSE_CURSOR_ARROW, + MP_MOUSE_CURSOR_RESIZE_0, + MP_MOUSE_CURSOR_RESIZE_90, + MP_MOUSE_CURSOR_RESIZE_45, + MP_MOUSE_CURSOR_RESIZE_135, + MP_MOUSE_CURSOR_TEXT } mp_mouse_cursor; + +typedef i32 mp_window_style; +static const mp_window_style MP_WINDOW_STYLE_NO_TITLE = 0x01<<0, + MP_WINDOW_STYLE_FIXED_SIZE = 0x01<<1, + MP_WINDOW_STYLE_NO_CLOSE = 0x01<<2, + MP_WINDOW_STYLE_NO_MINIFY = 0x01<<3, + MP_WINDOW_STYLE_NO_FOCUS = 0x01<<4, + MP_WINDOW_STYLE_FLOAT = 0x01<<5, + MP_WINDOW_STYLE_POPUPMENU = 0x01<<6, + MP_WINDOW_STYLE_NO_BUTTONS = 0x01<<7; + +typedef enum { MP_EVENT_NONE, + MP_EVENT_KEYBOARD_MODS, + MP_EVENT_KEYBOARD_KEY, + MP_EVENT_KEYBOARD_CHAR, + MP_EVENT_MOUSE_BUTTON, + MP_EVENT_MOUSE_MOVE, + MP_EVENT_MOUSE_WHEEL, + MP_EVENT_MOUSE_ENTER, + MP_EVENT_MOUSE_LEAVE, + MP_EVENT_WINDOW_RESIZE, + MP_EVENT_WINDOW_MOVE, + MP_EVENT_WINDOW_FOCUS, + MP_EVENT_WINDOW_UNFOCUS, + MP_EVENT_WINDOW_HIDE, + MP_EVENT_WINDOW_SHOW, + MP_EVENT_WINDOW_CLOSE, + MP_EVENT_CLIPBOARD, + MP_EVENT_PATHDROP, + MP_EVENT_FRAME, + MP_EVENT_QUIT } mp_event_type; + +typedef enum { MP_KEY_NO_ACTION, + MP_KEY_PRESS, + MP_KEY_RELEASE, + MP_KEY_REPEAT } mp_key_action; + +typedef i32 mp_key_code; + +static const mp_key_code MP_KEY_UNKNOWN = -1, + MP_KEY_SPACE = 32, + MP_KEY_APOSTROPHE = 39, // ' + MP_KEY_COMMA = 44, // , + MP_KEY_MINUS = 45, // - + MP_KEY_PERIOD = 46, // . + MP_KEY_SLASH = 47, // / + MP_KEY_0 = 48, + MP_KEY_1 = 49, + MP_KEY_2 = 50, + MP_KEY_3 = 51, + MP_KEY_4 = 52, + MP_KEY_5 = 53, + MP_KEY_6 = 54, + MP_KEY_7 = 55, + MP_KEY_8 = 56, + MP_KEY_9 = 57, + MP_KEY_SEMICOLON = 59, // ; + MP_KEY_EQUAL = 61, // = + MP_KEY_A = 65, + MP_KEY_B = 66, + MP_KEY_C = 67, + MP_KEY_D = 68, + MP_KEY_E = 69, + MP_KEY_F = 70, + MP_KEY_G = 71, + MP_KEY_H = 72, + MP_KEY_I = 73, + MP_KEY_J = 74, + MP_KEY_K = 75, + MP_KEY_L = 76, + MP_KEY_M = 77, + MP_KEY_N = 78, + MP_KEY_O = 79, + MP_KEY_P = 80, + MP_KEY_Q = 81, + MP_KEY_R = 82, + MP_KEY_S = 83, + MP_KEY_T = 84, + MP_KEY_U = 85, + MP_KEY_V = 86, + MP_KEY_W = 87, + MP_KEY_X = 88, + MP_KEY_Y = 89, + MP_KEY_Z = 90, + MP_KEY_LEFT_BRACKET = 91, // [ + MP_KEY_BACKSLASH = 92, // \ */ + MP_KEY_RIGHT_BRACKET = 93, // ] + MP_KEY_GRAVE_ACCENT = 96, // ` + MP_KEY_WORLD_1 = 161, // non-US #1 + MP_KEY_WORLD_2 = 162, // non-US #2 + MP_KEY_ESCAPE = 256, + MP_KEY_ENTER = 257, + MP_KEY_TAB = 258, + MP_KEY_BACKSPACE = 259, + MP_KEY_INSERT = 260, + MP_KEY_DELETE = 261, + MP_KEY_RIGHT = 262, + MP_KEY_LEFT = 263, + MP_KEY_DOWN = 264, + MP_KEY_UP = 265, + MP_KEY_PAGE_UP = 266, + MP_KEY_PAGE_DOWN = 267, + MP_KEY_HOME = 268, + MP_KEY_END = 269, + MP_KEY_CAPS_LOCK = 280, + MP_KEY_SCROLL_LOCK = 281, + MP_KEY_NUM_LOCK = 282, + MP_KEY_PRINT_SCREEN = 283, + MP_KEY_PAUSE = 284, + MP_KEY_F1 = 290, + MP_KEY_F2 = 291, + MP_KEY_F3 = 292, + MP_KEY_F4 = 293, + MP_KEY_F5 = 294, + MP_KEY_F6 = 295, + MP_KEY_F7 = 296, + MP_KEY_F8 = 297, + MP_KEY_F9 = 298, + MP_KEY_F10 = 299, + MP_KEY_F11 = 300, + MP_KEY_F12 = 301, + MP_KEY_F13 = 302, + MP_KEY_F14 = 303, + MP_KEY_F15 = 304, + MP_KEY_F16 = 305, + MP_KEY_F17 = 306, + MP_KEY_F18 = 307, + MP_KEY_F19 = 308, + MP_KEY_F20 = 309, + MP_KEY_F21 = 310, + MP_KEY_F22 = 311, + MP_KEY_F23 = 312, + MP_KEY_F24 = 313, + MP_KEY_F25 = 314, + MP_KEY_KP_0 = 320, + MP_KEY_KP_1 = 321, + MP_KEY_KP_2 = 322, + MP_KEY_KP_3 = 323, + MP_KEY_KP_4 = 324, + MP_KEY_KP_5 = 325, + MP_KEY_KP_6 = 326, + MP_KEY_KP_7 = 327, + MP_KEY_KP_8 = 328, + MP_KEY_KP_9 = 329, + MP_KEY_KP_DECIMAL = 330, + MP_KEY_KP_DIVIDE = 331, + MP_KEY_KP_MULTIPLY = 332, + MP_KEY_KP_SUBTRACT = 333, + MP_KEY_KP_ADD = 334, + MP_KEY_KP_ENTER = 335, + MP_KEY_KP_EQUAL = 336, + MP_KEY_LEFT_SHIFT = 340, + MP_KEY_LEFT_CONTROL = 341, + MP_KEY_LEFT_ALT = 342, + MP_KEY_LEFT_SUPER = 343, + MP_KEY_RIGHT_SHIFT = 344, + MP_KEY_RIGHT_CONTROL = 345, + MP_KEY_RIGHT_ALT = 346, + MP_KEY_RIGHT_SUPER = 347, + MP_KEY_MENU = 348; + +static const mp_key_code MP_KEY_MAX = MP_KEY_MENU; + +typedef u8 mp_key_mods; +static const mp_key_mods MP_KEYMOD_NONE = 0x00, + MP_KEYMOD_ALT = 0x01, + MP_KEYMOD_SHIFT = 0x02, + MP_KEYMOD_CTRL = 0x04, + MP_KEYMOD_CMD = 0x08; + +typedef i32 mp_mouse_button; +static const mp_mouse_button MP_MOUSE_LEFT = 0x00, + MP_MOUSE_RIGHT = 0x01, + MP_MOUSE_MIDDLE = 0x02, + MP_MOUSE_EXT1 = 0x03, + MP_MOUSE_EXT2 = 0x04; + +static const u32 MP_KEY_COUNT = MP_KEY_MAX+1, + MP_MOUSE_BUTTON_COUNT = 5; + +typedef struct mp_key_event // keyboard and mouse buttons input +{ + mp_key_action action; + mp_key_code code; + mp_key_mods mods; + char label[8]; + u8 labelLen; + int clickCount; +} mp_key_event; + +typedef struct mp_char_event // character input +{ + utf32 codepoint; + char sequence[8]; + u8 seqLen; +} mp_char_event; + +typedef struct mp_move_event // mouse move/scroll +{ + f32 x; + f32 y; + f32 deltaX; + f32 deltaY; + mp_key_mods mods; +} mp_move_event; + +typedef struct mp_frame_event // window resize / move +{ + mp_rect rect; +} mp_frame_event; + +typedef struct mp_event +{ + //TODO clipboard and path drop + + mp_window window; + mp_event_type type; + + union + { + mp_key_event key; + mp_char_event character; + mp_move_event move; + mp_frame_event frame; + }; + + //TODO(martin): chain externally ? + list_elt list; +} mp_event; + +//-------------------------------------------------------------------- +// app management +//-------------------------------------------------------------------- + +void mp_init(); +void mp_terminate(); + +bool mp_should_quit(); +void mp_do_quit(); +void mp_request_quit(); +void mp_cancel_quit(); + +void mp_set_cursor(mp_mouse_cursor cursor); + +//-------------------------------------------------------------------- +// window management +//-------------------------------------------------------------------- + +//#include"graphics.h" + +bool mp_window_handle_is_null(mp_window window); +mp_window mp_window_null_handle(); + +mp_window mp_window_create(mp_rect contentRect, const char* title, mp_window_style style); +void mp_window_destroy(mp_window window); + +bool mp_window_should_close(mp_window window); +void mp_window_request_close(mp_window window); +void mp_window_cancel_close(mp_window window); + +void* mp_window_native_pointer(mp_window window); + +void mp_window_center(mp_window window); + +bool mp_window_is_hidden(mp_window window); +bool mp_window_is_focused(mp_window window); + +void mp_window_hide(mp_window window); +void mp_window_focus(mp_window window); +void mp_window_send_to_back(mp_window window); +void mp_window_bring_to_front(mp_window window); + +void mp_window_bring_to_front_and_focus(mp_window window); + +mp_rect mp_window_content_rect_for_frame_rect(mp_rect frameRect, mp_window_style style); +mp_rect mp_window_frame_rect_for_content_rect(mp_rect contentRect, mp_window_style style); + +mp_rect mp_window_get_content_rect(mp_window window); +mp_rect mp_window_get_absolute_content_rect(mp_window window); +mp_rect mp_window_get_frame_rect(mp_window window); + +void mp_window_set_content_rect(mp_window window, mp_rect contentRect); +void mp_window_set_frame_rect(mp_window window, mp_rect frameRect); +void mp_window_set_frame_size(mp_window window, int width, int height); +void mp_window_set_content_size(mp_window window, int width, int height); + +//-------------------------------------------------------------------- +// View management +//-------------------------------------------------------------------- + +mp_view mp_view_nil(); +bool mp_view_is_nil(mp_view view); +mp_view mp_view_create(mp_window window, mp_rect frame); +void mp_view_destroy(mp_view view); +void mp_view_set_frame(mp_view view, mp_rect frame); + +/*TODO +mp_view mp_view_bring_to_front(mp_view view); +mp_view mp_view_send_to_back(mp_view view); +*/ +//-------------------------------------------------------------------- +// Main loop throttle +//-------------------------------------------------------------------- + +void mp_set_target_fps(u32 fps); // or use wait vblank? + +//-------------------------------------------------------------------- +// Events handling +//-------------------------------------------------------------------- +void mp_pump_events(f64 timeout); +bool mp_next_event(mp_event* event); + +//-------------------------------------------------------------------- +// Input state polling +//-------------------------------------------------------------------- +bool mp_input_key_down(mp_key_code key); +bool mp_input_key_pressed(mp_key_code key); +bool mp_input_key_released(mp_key_code key); +mp_key_mods mp_input_key_mods(); + +str8 mp_key_to_label(mp_key_code key); +mp_key_code mp_label_to_key(str8 label); + +bool mp_input_mouse_down(mp_mouse_button button); +bool mp_input_mouse_pressed(mp_mouse_button button); +bool mp_input_mouse_released(mp_mouse_button button); +bool mp_input_mouse_clicked(mp_mouse_button button); +bool mp_input_mouse_double_clicked(mp_mouse_button button); + +vec2 mp_input_mouse_position(); +vec2 mp_input_mouse_delta(); +vec2 mp_input_mouse_wheel(); + +str32 mp_input_text_utf32(mem_arena* arena); +str8 mp_input_text_utf8(mem_arena* arena); +//-------------------------------------------------------------------- +// app resources +//-------------------------------------------------------------------- +int mp_app_get_resource_path(const char* name, char** result); +str8 mp_app_get_executable_path(mem_arena* arena); + +//-------------------------------------------------------------------- +// Clipboard +//-------------------------------------------------------------------- +void mp_clipboard_clear(); + +void mp_clipboard_set_string(str8 string); +str8 mp_clipboard_get_string(mem_arena* arena); +str8 mp_clipboard_copy_string(str8 backing); + +bool mp_clipboard_has_tag(const char* tag); +void mp_clipboard_set_data_for_tag(const char* tag, str8 data); +str8 mp_clipboard_get_data_for_tag(mem_arena* arena, const char* tag); + +//-------------------------------------------------------------------- +// native open/save/alert windows +//-------------------------------------------------------------------- + +str8 mp_open_dialog(mem_arena* arena, + const char* title, + const char* defaultPath, + int filterCount, + const char** filters, + bool directory); + +str8 mp_save_dialog(mem_arena* arena, + const char* title, + const char* defaultPath, + int filterCount, + const char** filters); + +int mp_alert_popup(const char* title, + const char* message, + u32 count, + const char** options); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif //__PLATFORM_APP_H_ diff --git a/src/osx_app.h b/src/osx_app.h new file mode 100644 index 0000000..cd98b0b --- /dev/null +++ b/src/osx_app.h @@ -0,0 +1,58 @@ +/************************************************************//** +* +* @file: osx_app.h +* @author: Martin Fouilleul +* @date: 12/02/2021 +* @revision: +* +*****************************************************************/ +#ifndef __OSX_APP_H_ +#define __OSX_APP_H_ + +#import +#import +#include"mp_app.h" +#include"graphics.h" + +struct mp_window_data +{ + list_elt freeListElt; + u32 generation; + + NSWindow* nsWindow; + NSView* nsView; + NSObject* nsWindowDelegate; + + mp_rect contentRect; + mp_rect frameRect; + mp_window_style style; + + bool shouldClose; //TODO could be in status flags + bool hidden; + + mp_view mainView; +}; + +struct mp_view_data +{ + list_elt freeListElt; + u32 generation; + + mp_window window; + NSView* nsView; + mg_surface surface; +}; + +@interface MPNativeWindow : NSWindow +{ + mp_window_data* mpWindow; +} +- (id)initWithMPWindow:(mp_window_data*) window contentRect:(NSRect) rect styleMask:(uint32) style; +@end + +mp_window_data* mp_window_ptr_from_handle(mp_window handle); +mp_view_data* mp_view_ptr_from_handle(mp_view handle); + + + +#endif //__OSX_APP_H_ diff --git a/src/osx_app.mm b/src/osx_app.mm new file mode 100644 index 0000000..dabb0ec --- /dev/null +++ b/src/osx_app.mm @@ -0,0 +1,2401 @@ +//***************************************************************** +// +// $file: osx_app.cpp $ +// $author: Martin Fouilleul $ +// $date: 16/05/2020 $ +// $revision: $ +// $note: (C) 2020 by Martin Fouilleul - all rights reserved $ +// +//***************************************************************** + +#include // malloc/free + +#include"osx_app.h" +#include"lists.h" +#include"ringbuffer.h" +#include"memory.h" +#include"macro_helpers.h" +#include"platform_clock.h" + +#define LOG_SUBSYSTEM "Application" + +//-------------------------------------------------------------------- +// mp window struct and utility functions +//-------------------------------------------------------------------- + +static mp_rect mp_osx_to_user_screen_rect(mp_rect rect) +{ + @autoreleasepool + { + NSRect screenRect = [[NSScreen mainScreen] frame]; + rect.y = screenRect.size.height - rect.y - rect.h; + } + return(rect); +} + +static mp_rect mp_user_to_osx_screen_rect(mp_rect rect) +{ + @autoreleasepool + { + NSRect screenRect = [[NSScreen mainScreen] frame]; + rect.y = screenRect.size.height - rect.y - rect.h; + } + return(rect); +} + +static void mp_window_update_rect_cache(mp_window_data* window) +{ + @autoreleasepool + { + NSRect frame = [window->nsWindow frame]; + window->frameRect = mp_osx_to_user_screen_rect((mp_rect){frame.origin.x, frame.origin.y, frame.size.width, frame.size.height}); + + const NSRect contentRect = [[window->nsWindow contentView] frame]; + + window->contentRect = (mp_rect){ contentRect.origin.x, + contentRect.origin.y, + contentRect.size.width, + contentRect.size.height }; + + window->contentRect.y = window->frameRect.h - window->contentRect.y - window->contentRect.h; + } +} + +static u32 mp_osx_get_window_style_mask(mp_window_style style) +{ + u32 mask = 0; + if(style & MP_WINDOW_STYLE_NO_TITLE) + { + mask = NSWindowStyleMaskBorderless; + } + else + { + mask = NSWindowStyleMaskTitled; + } + + if(!(style & MP_WINDOW_STYLE_FIXED_SIZE)) + { + mask |= NSWindowStyleMaskResizable; + } + if(!(style & MP_WINDOW_STYLE_NO_CLOSE)) + { + mask |= NSWindowStyleMaskClosable; + } + if(!(style & MP_WINDOW_STYLE_NO_MINIFY)) + { + mask |= NSWindowStyleMaskMiniaturizable; + } + return(mask); +} + +//--------------------------------------------------------------- +// App struct and utility functions +//--------------------------------------------------------------- + +const u32 MP_APP_MAX_WINDOWS = 128; +const u32 MP_APP_MAX_VIEWS = 128; + +typedef struct mp_frame_stats +{ + f64 start; + f64 workTime; + f64 remainingTime; + f64 targetFramePeriod; +} mp_frame_stats; + +typedef struct mp_key_utf8 +{ + u8 labelLen; + char label[8]; +} mp_key_utf8; + +typedef struct mp_key_state +{ + u64 lastUpdate; + u32 transitionCounter; + bool down; + bool clicked; + bool doubleClicked; + +} mp_key_state; + +typedef struct mp_keyboard_state +{ + mp_key_state keys[MP_KEY_COUNT]; + mp_key_mods mods; +} mp_keyboard_state; + +typedef struct mp_mouse_state +{ + u64 lastUpdate; + vec2 pos; + vec2 delta; + vec2 wheel; + + union + { + mp_key_state buttons[MP_MOUSE_BUTTON_COUNT]; + struct + { + mp_key_state left; + mp_key_state right; + mp_key_state middle; + mp_key_state ext1; + mp_key_state ext2; + }; + }; +} mp_mouse_state; + +const u32 MP_INPUT_TEXT_BACKING_SIZE = 64; + +typedef struct mp_text_state +{ + u64 lastUpdate; + utf32 backing[MP_INPUT_TEXT_BACKING_SIZE]; + str32 codePoints; +} mp_text_state; + +typedef struct mp_input_state +{ + u64 frameCounter; + mp_keyboard_state keyboard; + mp_mouse_state mouse; + mp_text_state text; +} mp_input_state; + +struct mp_app_data +{ + bool init; + bool shouldQuit; + + mp_input_state inputState; + ringbuffer eventQueue; + + mp_frame_stats frameStats; + + NSTimer* frameTimer; + NSCursor* cursor; + mp_window_data windowPool[MP_APP_MAX_WINDOWS]; + list_info windowFreeList; + + mp_view_data viewPool[MP_APP_MAX_VIEWS]; + list_info viewFreeList; + + + TISInputSourceRef kbLayoutInputSource; + void* kbLayoutUnicodeData; + id kbLayoutListener; + mp_key_utf8 mpKeyToLabel[256]; + + int mpKeysToNative[MP_KEY_MAX]; + int nativeToMPKeys[256]; + +}; + +static mp_app_data __mpAppData = {}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////// + +void mp_init_window_handles() +{ + ListInit(&__mpAppData.windowFreeList); + for(int i=0; i>32; + u32 generation = handle.h & 0xffffffff; + if(index >= MP_APP_MAX_WINDOWS) + { + return(0); + } + mp_window_data* window = &__mpAppData.windowPool[index]; + if(window->generation != generation) + { + return(0); + } + else + { + return(window); + } +} + +mp_window mp_window_handle_from_ptr(mp_window_data* window) +{ + DEBUG_ASSERT( (window - __mpAppData.windowPool) >= 0 + && (window - __mpAppData.windowPool) < MP_APP_MAX_WINDOWS); + + u64 h = ((u64)(window - __mpAppData.windowPool))<<32 + | ((u64)window->generation); + + return((mp_window){h}); +} + +void mp_window_recycle_ptr(mp_window_data* window) +{ + window->generation++; + ListPush(&__mpAppData.windowFreeList, &window->freeListElt); +} + + +void mp_init_view_handles() +{ + ListInit(&__mpAppData.viewFreeList); + for(int i=0; i>32; + u32 generation = handle.h & 0xffffffff; + if(index >= MP_APP_MAX_VIEWS) + { + return(0); + } + mp_view_data* view = &__mpAppData.viewPool[index]; + if(view->generation != generation) + { + return(0); + } + else + { + return(view); + } +} + +mp_view mp_view_handle_from_ptr(mp_view_data* view) +{ + DEBUG_ASSERT( (view - __mpAppData.viewPool) >= 0 + && (view - __mpAppData.viewPool) < MP_APP_MAX_VIEWS); + + u64 h = ((u64)(view - __mpAppData.viewPool))<<32 + | ((u64)view->generation); + + return((mp_view){h}); +} + +void mp_view_recycle_ptr(mp_view_data* view) +{ + view->generation++; + ListPush(&__mpAppData.viewFreeList, &view->freeListElt); +} + +/* +void mp_app_set_process_event_callback(mp_app_process_event_callback callback, void* userData) +{ + DEBUG_ASSERT(callback); + __mpAppData.userData = userData; + __mpAppData.processEvent = callback; +} +*/ +///////////////////////////////////////////////////////////////////////////////////////////////////////// + + +static void mp_init_osx_keys() +{ + memset(__mpAppData.nativeToMPKeys, MP_KEY_UNKNOWN, 256*sizeof(int)); + + __mpAppData.nativeToMPKeys[0x1D] = MP_KEY_0; + __mpAppData.nativeToMPKeys[0x12] = MP_KEY_1; + __mpAppData.nativeToMPKeys[0x13] = MP_KEY_2; + __mpAppData.nativeToMPKeys[0x14] = MP_KEY_3; + __mpAppData.nativeToMPKeys[0x15] = MP_KEY_4; + __mpAppData.nativeToMPKeys[0x17] = MP_KEY_5; + __mpAppData.nativeToMPKeys[0x16] = MP_KEY_6; + __mpAppData.nativeToMPKeys[0x1A] = MP_KEY_7; + __mpAppData.nativeToMPKeys[0x1C] = MP_KEY_8; + __mpAppData.nativeToMPKeys[0x19] = MP_KEY_9; + __mpAppData.nativeToMPKeys[0x00] = MP_KEY_A; + __mpAppData.nativeToMPKeys[0x0B] = MP_KEY_B; + __mpAppData.nativeToMPKeys[0x08] = MP_KEY_C; + __mpAppData.nativeToMPKeys[0x02] = MP_KEY_D; + __mpAppData.nativeToMPKeys[0x0E] = MP_KEY_E; + __mpAppData.nativeToMPKeys[0x03] = MP_KEY_F; + __mpAppData.nativeToMPKeys[0x05] = MP_KEY_G; + __mpAppData.nativeToMPKeys[0x04] = MP_KEY_H; + __mpAppData.nativeToMPKeys[0x22] = MP_KEY_I; + __mpAppData.nativeToMPKeys[0x26] = MP_KEY_J; + __mpAppData.nativeToMPKeys[0x28] = MP_KEY_K; + __mpAppData.nativeToMPKeys[0x25] = MP_KEY_L; + __mpAppData.nativeToMPKeys[0x2E] = MP_KEY_M; + __mpAppData.nativeToMPKeys[0x2D] = MP_KEY_N; + __mpAppData.nativeToMPKeys[0x1F] = MP_KEY_O; + __mpAppData.nativeToMPKeys[0x23] = MP_KEY_P; + __mpAppData.nativeToMPKeys[0x0C] = MP_KEY_Q; + __mpAppData.nativeToMPKeys[0x0F] = MP_KEY_R; + __mpAppData.nativeToMPKeys[0x01] = MP_KEY_S; + __mpAppData.nativeToMPKeys[0x11] = MP_KEY_T; + __mpAppData.nativeToMPKeys[0x20] = MP_KEY_U; + __mpAppData.nativeToMPKeys[0x09] = MP_KEY_V; + __mpAppData.nativeToMPKeys[0x0D] = MP_KEY_W; + __mpAppData.nativeToMPKeys[0x07] = MP_KEY_X; + __mpAppData.nativeToMPKeys[0x10] = MP_KEY_Y; + __mpAppData.nativeToMPKeys[0x06] = MP_KEY_Z; + + __mpAppData.nativeToMPKeys[0x27] = MP_KEY_APOSTROPHE; + __mpAppData.nativeToMPKeys[0x2A] = MP_KEY_BACKSLASH; + __mpAppData.nativeToMPKeys[0x2B] = MP_KEY_COMMA; + __mpAppData.nativeToMPKeys[0x18] = MP_KEY_EQUAL; + __mpAppData.nativeToMPKeys[0x32] = MP_KEY_GRAVE_ACCENT; + __mpAppData.nativeToMPKeys[0x21] = MP_KEY_LEFT_BRACKET; + __mpAppData.nativeToMPKeys[0x1B] = MP_KEY_MINUS; + __mpAppData.nativeToMPKeys[0x2F] = MP_KEY_PERIOD; + __mpAppData.nativeToMPKeys[0x1E] = MP_KEY_RIGHT_BRACKET; + __mpAppData.nativeToMPKeys[0x29] = MP_KEY_SEMICOLON; + __mpAppData.nativeToMPKeys[0x2C] = MP_KEY_SLASH; + __mpAppData.nativeToMPKeys[0x0A] = MP_KEY_WORLD_1; + + __mpAppData.nativeToMPKeys[0x33] = MP_KEY_BACKSPACE; + __mpAppData.nativeToMPKeys[0x39] = MP_KEY_CAPS_LOCK; + __mpAppData.nativeToMPKeys[0x75] = MP_KEY_DELETE; + __mpAppData.nativeToMPKeys[0x7D] = MP_KEY_DOWN; + __mpAppData.nativeToMPKeys[0x77] = MP_KEY_END; + __mpAppData.nativeToMPKeys[0x24] = MP_KEY_ENTER; + __mpAppData.nativeToMPKeys[0x35] = MP_KEY_ESCAPE; + __mpAppData.nativeToMPKeys[0x7A] = MP_KEY_F1; + __mpAppData.nativeToMPKeys[0x78] = MP_KEY_F2; + __mpAppData.nativeToMPKeys[0x63] = MP_KEY_F3; + __mpAppData.nativeToMPKeys[0x76] = MP_KEY_F4; + __mpAppData.nativeToMPKeys[0x60] = MP_KEY_F5; + __mpAppData.nativeToMPKeys[0x61] = MP_KEY_F6; + __mpAppData.nativeToMPKeys[0x62] = MP_KEY_F7; + __mpAppData.nativeToMPKeys[0x64] = MP_KEY_F8; + __mpAppData.nativeToMPKeys[0x65] = MP_KEY_F9; + __mpAppData.nativeToMPKeys[0x6D] = MP_KEY_F10; + __mpAppData.nativeToMPKeys[0x67] = MP_KEY_F11; + __mpAppData.nativeToMPKeys[0x6F] = MP_KEY_F12; + __mpAppData.nativeToMPKeys[0x69] = MP_KEY_F13; + __mpAppData.nativeToMPKeys[0x6B] = MP_KEY_F14; + __mpAppData.nativeToMPKeys[0x71] = MP_KEY_F15; + __mpAppData.nativeToMPKeys[0x6A] = MP_KEY_F16; + __mpAppData.nativeToMPKeys[0x40] = MP_KEY_F17; + __mpAppData.nativeToMPKeys[0x4F] = MP_KEY_F18; + __mpAppData.nativeToMPKeys[0x50] = MP_KEY_F19; + __mpAppData.nativeToMPKeys[0x5A] = MP_KEY_F20; + __mpAppData.nativeToMPKeys[0x73] = MP_KEY_HOME; + __mpAppData.nativeToMPKeys[0x72] = MP_KEY_INSERT; + __mpAppData.nativeToMPKeys[0x7B] = MP_KEY_LEFT; + __mpAppData.nativeToMPKeys[0x3A] = MP_KEY_LEFT_ALT; + __mpAppData.nativeToMPKeys[0x3B] = MP_KEY_LEFT_CONTROL; + __mpAppData.nativeToMPKeys[0x38] = MP_KEY_LEFT_SHIFT; + __mpAppData.nativeToMPKeys[0x37] = MP_KEY_LEFT_SUPER; + __mpAppData.nativeToMPKeys[0x6E] = MP_KEY_MENU; + __mpAppData.nativeToMPKeys[0x47] = MP_KEY_NUM_LOCK; + __mpAppData.nativeToMPKeys[0x79] = MP_KEY_PAGE_DOWN; + __mpAppData.nativeToMPKeys[0x74] = MP_KEY_PAGE_UP; + __mpAppData.nativeToMPKeys[0x7C] = MP_KEY_RIGHT; + __mpAppData.nativeToMPKeys[0x3D] = MP_KEY_RIGHT_ALT; + __mpAppData.nativeToMPKeys[0x3E] = MP_KEY_RIGHT_CONTROL; + __mpAppData.nativeToMPKeys[0x3C] = MP_KEY_RIGHT_SHIFT; + __mpAppData.nativeToMPKeys[0x36] = MP_KEY_RIGHT_SUPER; + __mpAppData.nativeToMPKeys[0x31] = MP_KEY_SPACE; + __mpAppData.nativeToMPKeys[0x30] = MP_KEY_TAB; + __mpAppData.nativeToMPKeys[0x7E] = MP_KEY_UP; + + __mpAppData.nativeToMPKeys[0x52] = MP_KEY_KP_0; + __mpAppData.nativeToMPKeys[0x53] = MP_KEY_KP_1; + __mpAppData.nativeToMPKeys[0x54] = MP_KEY_KP_2; + __mpAppData.nativeToMPKeys[0x55] = MP_KEY_KP_3; + __mpAppData.nativeToMPKeys[0x56] = MP_KEY_KP_4; + __mpAppData.nativeToMPKeys[0x57] = MP_KEY_KP_5; + __mpAppData.nativeToMPKeys[0x58] = MP_KEY_KP_6; + __mpAppData.nativeToMPKeys[0x59] = MP_KEY_KP_7; + __mpAppData.nativeToMPKeys[0x5B] = MP_KEY_KP_8; + __mpAppData.nativeToMPKeys[0x5C] = MP_KEY_KP_9; + __mpAppData.nativeToMPKeys[0x45] = MP_KEY_KP_ADD; + __mpAppData.nativeToMPKeys[0x41] = MP_KEY_KP_DECIMAL; + __mpAppData.nativeToMPKeys[0x4B] = MP_KEY_KP_DIVIDE; + __mpAppData.nativeToMPKeys[0x4C] = MP_KEY_KP_ENTER; + __mpAppData.nativeToMPKeys[0x51] = MP_KEY_KP_EQUAL; + __mpAppData.nativeToMPKeys[0x43] = MP_KEY_KP_MULTIPLY; + __mpAppData.nativeToMPKeys[0x4E] = MP_KEY_KP_SUBTRACT; + + memset(__mpAppData.mpKeysToNative, 0, sizeof(int)*MP_KEY_COUNT); + for(int nativeKey=0; nativeKey<256; nativeKey++) + { + mp_key_code mpKey = __mpAppData.nativeToMPKeys[nativeKey]; + if(mpKey) + { + __mpAppData.mpKeysToNative[mpKey] = nativeKey; + } + } +} + +static int mp_convert_osx_key(unsigned short nsCode) +{ + if(nsCode >= 265) + { + return(MP_KEY_UNKNOWN); + } + else + { + return(__mpAppData.nativeToMPKeys[nsCode]); + } +} + +static mp_key_mods mp_convert_osx_mods(NSUInteger nsFlags) +{ + mp_key_mods mods = MP_KEYMOD_NONE; + if(nsFlags & NSEventModifierFlagShift) + { + mods |= MP_KEYMOD_SHIFT; + } + if(nsFlags & NSEventModifierFlagControl) + { + mods |= MP_KEYMOD_CTRL; + } + if(nsFlags & NSEventModifierFlagOption) + { + mods |= MP_KEYMOD_ALT; + } + if(nsFlags & NSEventModifierFlagCommand) + { + mods |= MP_KEYMOD_CMD; + } + return(mods); +} + +static void mp_update_keyboard_layout() +{ + if(__mpAppData.kbLayoutInputSource) + { + CFRelease(__mpAppData.kbLayoutInputSource); + __mpAppData.kbLayoutInputSource = 0; + __mpAppData.kbLayoutUnicodeData = nil; + } + + __mpAppData.kbLayoutInputSource = TISCopyCurrentKeyboardLayoutInputSource(); + if(!__mpAppData.kbLayoutInputSource) + { + LOG_ERROR("Failed to load keyboard layout input source"); + } + + __mpAppData.kbLayoutUnicodeData = TISGetInputSourceProperty(__mpAppData.kbLayoutInputSource, + kTISPropertyUnicodeKeyLayoutData); + if(!__mpAppData.kbLayoutUnicodeData) + { + LOG_ERROR("Failed to load keyboard layout unicode data"); + } + + memset(__mpAppData.mpKeyToLabel, 0, sizeof(mp_key_utf8)*MP_KEY_COUNT); + + for(int key=0; keylabelLen, keyInfo->label); + return(label); +} + +mp_key_code mp_label_to_key(str8 label) +{ + mp_key_code res = MP_KEY_UNKNOWN; + for(int key=0; keylastUpdate != frameCounter) + { + key->transitionCounter = 0; + key->clicked = false; + key->doubleClicked = false; + key->lastUpdate = frameCounter; + } + + if(key->down != down) + { + key->transitionCounter++; + } + key->down = down; +} + +static void mp_update_mouse_move(f32 x, f32 y, f32 deltaX, f32 deltaY) +{ + u64 frameCounter = __mpAppData.inputState.frameCounter; + mp_mouse_state* mouse = &__mpAppData.inputState.mouse; + if(mouse->lastUpdate != frameCounter) + { + mouse->delta = (vec2){0, 0}; + mouse->wheel = (vec2){0, 0}; + mouse->lastUpdate = frameCounter; + } + mouse->pos = (vec2){x, y}; + mouse->delta.x += deltaX; + mouse->delta.y += deltaY; +} + +static void mp_update_mouse_wheel(f32 deltaX, f32 deltaY) +{ + u64 frameCounter = __mpAppData.inputState.frameCounter; + mp_mouse_state* mouse = &__mpAppData.inputState.mouse; + if(mouse->lastUpdate != frameCounter) + { + mouse->delta = (vec2){0, 0}; + mouse->wheel = (vec2){0, 0}; + mouse->lastUpdate = frameCounter; + } + mouse->wheel.x += deltaX; + mouse->wheel.y += deltaY; +} + +static void mp_update_text(utf32 codepoint) +{ + u64 frameCounter = __mpAppData.inputState.frameCounter; + mp_text_state* text = &__mpAppData.inputState.text; + + if(text->lastUpdate != frameCounter) + { + text->codePoints.len = 0; + } + text->codePoints.ptr = text->backing; + if(text->codePoints.len < MP_INPUT_TEXT_BACKING_SIZE) + { + text->codePoints.ptr[text->codePoints.len] = codepoint; + text->codePoints.len++; + } + else + { + LOG_WARNING("too many input codepoints per frame, dropping input"); + } +} + +static void mp_queue_event(mp_event* event) +{ + if(ringbuffer_write_available(&__mpAppData.eventQueue) < sizeof(mp_event)) + { + LOG_ERROR("event queue full\n"); + } + else + { + u32 written = ringbuffer_write(&__mpAppData.eventQueue, sizeof(mp_event), (u8*)event); + DEBUG_ASSERT(written == sizeof(mp_event)); + } +} + +//--------------------------------------------------------------- +// Application and app delegate +//--------------------------------------------------------------- + +@interface MPApplication : NSApplication +@end + +@implementation MPApplication +-(void)noOpThread:(id)object +{} +@end + +@interface MPAppDelegate : NSObject +@end + +@implementation MPAppDelegate + +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender +{ + //NOTE: We set shouldQuit to true and send a Quit event + // We then return a value to cancel the direct termination because we still + // want to execte the code after mp_event_loop(). If the user didn't set shouldQuit to + // false, mp_event_loop() will exit, and the user can execute any cleanup needed and + // exit the program. + + __mpAppData.shouldQuit = true; + mp_event event = {}; + event.type = MP_EVENT_QUIT; + mp_queue_event(&event); + + return(NSTerminateCancel); +} + +- (void)applicationWillFinishLaunching:(NSNotification *)notification +{@autoreleasepool{ + + //NOTE(martin): add a menu for quit, and a corresponding key equivalent. + // this allows to quit the application when there is no window + // left to catch our Cmd-Q key equivalent + NSMenu* bar = [[NSMenu alloc] init]; + [NSApp setMainMenu:bar]; + + NSMenuItem* appMenuItem = + [bar addItemWithTitle:@"" action:NULL keyEquivalent:@""]; + NSMenu* appMenu = [[NSMenu alloc] init]; + [appMenuItem setSubmenu:appMenu]; + + [appMenu addItemWithTitle: @"Quit" + action: @selector(terminate:) + keyEquivalent: @"q"]; +}} + +- (void)timerElapsed:(NSTimer*)timer +{ + mp_event event = {}; + event.type = MP_EVENT_FRAME; + mp_queue_event(&event); +} + +- (void)applicationDidFinishLaunching:(NSNotification *)notification +{@autoreleasepool{ + //WARN(martin): the order of these calls seem to matter a lot for properly showing the menu bar + // with other orderings, the menu doesn't display before the application is put out of + // focus and on focus again... This is flaky undocumented behaviour, so although it is + // fixed by the current ordering, we expect the problem to show up again in future + // versions of macOS. + + //NOTE(martin): send a dummy event to wake-up the run loop and exit from the run loop. + NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined + location:NSMakePoint(0, 0) + modifierFlags:0 + timestamp:0 + windowNumber:0 + context:nil + subtype:0 + data1:0 + data2:0]; + [NSApp postEvent:event atStart:YES]; + [NSApp stop:nil]; +}} + +@end // @implementation MPAppDelegate + +//--------------------------------------------------------------- +// Custom NSWindow +//--------------------------------------------------------------- + +@implementation MPNativeWindow +- (id)initWithMPWindow:(mp_window_data*) window contentRect:(NSRect) rect styleMask:(uint32) style +{ + mpWindow = window; + return([self initWithContentRect:rect styleMask:style backing:NSBackingStoreBuffered defer:NO]); +} +- (BOOL)canBecomeKeyWindow +{ + return(!(mpWindow->style & MP_WINDOW_STYLE_NO_FOCUS)); +} +@end //@implementation MPNativeWindow + +//--------------------------------------------------------------- +// Custom NSWindow delegate +//--------------------------------------------------------------- + +@interface MPNativeWindowDelegate : NSObject +{ + mp_window_data* mpWindow; +} +- (id)initWithMPWindow:(mp_window_data*) window; +@end + +@implementation MPNativeWindowDelegate + +- (id)initWithMPWindow:(mp_window_data*) window +{ + self = [super init]; + if(self != nil) + { + mpWindow = window; + } + return(self); +} + +- (void)windowDidBecomeKey:(NSNotification*)notification +{ + mp_event event = {}; + event.window = mp_window_handle_from_ptr(mpWindow); + event.type = MP_EVENT_WINDOW_FOCUS; + + mpWindow->hidden = false; + + mp_queue_event(&event); +} + +- (void)windowDidResignKey:(NSNotification*)notification +{ + mp_event event = {}; + event.window = mp_window_handle_from_ptr(mpWindow); + event.type = MP_EVENT_WINDOW_UNFOCUS; + + mp_queue_event(&event); +} + +- (void)windowDidMove:(NSNotification *)notification +{ + const NSRect contentRect = [[mpWindow->nsWindow contentView] frame]; + + mp_window_update_rect_cache(mpWindow); + + mp_event event = {}; + event.window = mp_window_handle_from_ptr(mpWindow); + event.type = MP_EVENT_WINDOW_MOVE; + event.frame.rect.x = contentRect.origin.x; + event.frame.rect.y = contentRect.origin.y; + event.frame.rect.w = contentRect.size.width; + event.frame.rect.h = contentRect.size.height; + + mp_queue_event(&event); +} + +- (void)windowDidResize:(NSNotification *)notification +{ + const NSRect contentRect = [[mpWindow->nsWindow contentView] frame]; + + mp_window_update_rect_cache(mpWindow); + + mp_event event = {}; + event.window = mp_window_handle_from_ptr(mpWindow); + event.type = MP_EVENT_WINDOW_RESIZE; + event.frame.rect.x = contentRect.origin.x; + event.frame.rect.y = contentRect.origin.y; + event.frame.rect.w = contentRect.size.width; + event.frame.rect.h = contentRect.size.height; + + mp_rect viewFrame = {0, 0, contentRect.size.width, contentRect.size.height}; + mp_view_set_frame(mpWindow->mainView, viewFrame); + + mp_queue_event(&event); +} + +- (void)windowWillClose:(NSNotification *)notification +{ + mpWindow->nsWindow = nil; + [mpWindow->nsView release]; + mpWindow->nsView = nil; + [mpWindow->nsWindowDelegate release]; + mpWindow->nsWindowDelegate = nil; + + mp_window_recycle_ptr(mpWindow); +} + +- (BOOL)windowShouldClose:(id)sender +{ + mpWindow->shouldClose = true; + + mp_event event = {}; + event.window = mp_window_handle_from_ptr(mpWindow); + event.type = MP_EVENT_WINDOW_CLOSE; + + mp_queue_event(&event); + + return(mpWindow->shouldClose); +} + +@end //@implementation MPNativeWindowDelegate + +//--------------------------------------------------------------- +// Custom NSView +//--------------------------------------------------------------- + +@interface MPNativeView : NSView +{ + mp_window_data* window; + NSTrackingArea* trackingArea; + NSMutableAttributedString* markedText; +} +- (id)initWithMPWindow:(mp_window_data*) mpWindow; +@end + +@implementation MPNativeView + +- (id)initWithMPWindow:(mp_window_data*) mpWindow +{ + self = [super init]; + if(self != nil) + { + window = mpWindow; + mpWindow->nsView = self; + + NSTrackingAreaOptions trackingOptions = NSTrackingMouseEnteredAndExited + | NSTrackingMouseMoved + | NSTrackingCursorUpdate + | NSTrackingActiveInActiveApp //TODO maybe change that to allow multi-window mouse events... + | NSTrackingEnabledDuringMouseDrag + | NSTrackingInVisibleRect + | NSTrackingAssumeInside ; + + trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:trackingOptions owner:self userInfo:nil]; + [self addTrackingArea:trackingArea]; + markedText = [[NSMutableAttributedString alloc] init]; + } + return(self); +} + +- (void)dealloc +{ + [trackingArea release]; + [markedText release]; + [super dealloc]; +} + +-(void)drawRect:(NSRect)dirtyRect +{ + if(window->style & MP_WINDOW_STYLE_NO_TITLE) + { + [NSGraphicsContext saveGraphicsState]; + NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:[self frame] xRadius:5 yRadius:5]; + [path addClip]; + [[NSColor whiteColor] set]; + NSRectFill([self frame]); + } + + if(window->style & MP_WINDOW_STYLE_NO_TITLE) + { + [NSGraphicsContext restoreGraphicsState]; + [window->nsWindow invalidateShadow]; + } +} + +- (BOOL)acceptsFirstReponder +{ + return(YES); +} + +- (void)cursorUpdate:(NSEvent*)event +{ + if(__mpAppData.cursor) + { + [__mpAppData.cursor set]; + } + else + { + [[NSCursor arrowCursor] set]; + } +} + +- (void)mouseDown:(NSEvent *)nsEvent +{ + mp_event event = {}; + event.window = mp_window_handle_from_ptr(window); + event.type = MP_EVENT_MOUSE_BUTTON; + event.key.action = MP_KEY_PRESS; + event.key.code = MP_MOUSE_LEFT; + event.key.mods = mp_convert_osx_mods([nsEvent modifierFlags]); + event.key.clickCount = [nsEvent clickCount]; + + mp_update_key_state(&__mpAppData.inputState.mouse.buttons[event.key.code], true); + if(event.key.clickCount >= 1) + { + __mpAppData.inputState.mouse.buttons[event.key.code].clicked = true; + } + if(event.key.clickCount >= 2) + { + __mpAppData.inputState.mouse.buttons[event.key.code].doubleClicked = true; + } + + mp_queue_event(&event); + + [window->nsWindow makeFirstResponder:self]; +} + +- (void)mouseUp:(NSEvent*)nsEvent +{ + mp_event event = {}; + event.window = mp_window_handle_from_ptr(window); + event.type = MP_EVENT_MOUSE_BUTTON; + event.key.action = MP_KEY_RELEASE; + event.key.code = MP_MOUSE_LEFT; + event.key.mods = mp_convert_osx_mods([nsEvent modifierFlags]); + event.key.clickCount = [nsEvent clickCount]; + + mp_update_key_state(&__mpAppData.inputState.mouse.buttons[event.key.code], false); + + mp_queue_event(&event); +} + +- (void)rightMouseDown:(NSEvent*)nsEvent +{ + mp_event event = {}; + event.window = mp_window_handle_from_ptr(window); + event.type = MP_EVENT_MOUSE_BUTTON; + event.key.action = MP_KEY_PRESS; + event.key.code = MP_MOUSE_RIGHT; + event.key.mods = mp_convert_osx_mods([nsEvent modifierFlags]); + + mp_update_key_state(&__mpAppData.inputState.mouse.buttons[event.key.code], true); + + mp_queue_event(&event); +} + +- (void)rightMouseUp:(NSEvent*)nsEvent +{ + mp_event event = {}; + event.window = mp_window_handle_from_ptr(window); + event.type = MP_EVENT_MOUSE_BUTTON; + event.key.action = MP_KEY_RELEASE; + event.key.code = MP_MOUSE_RIGHT; + event.key.mods = mp_convert_osx_mods([nsEvent modifierFlags]); + + mp_update_key_state(&__mpAppData.inputState.mouse.buttons[event.key.code], false); + + mp_queue_event(&event); +} + +- (void)otherMouseDown:(NSEvent*)nsEvent +{ + mp_event event = {}; + event.window = mp_window_handle_from_ptr(window); + event.type = MP_EVENT_MOUSE_BUTTON; + event.key.action = MP_KEY_PRESS; + event.key.code = [nsEvent buttonNumber]; + event.key.mods = mp_convert_osx_mods([nsEvent modifierFlags]); + + mp_update_key_state(&__mpAppData.inputState.mouse.buttons[event.key.code], true); + + mp_queue_event(&event); +} + +- (void)otherMouseUp:(NSEvent*)nsEvent +{ + mp_event event = {}; + event.window = mp_window_handle_from_ptr(window); + event.type = MP_EVENT_MOUSE_BUTTON; + event.key.action = MP_KEY_RELEASE; + event.key.code = [nsEvent buttonNumber]; + event.key.mods = mp_convert_osx_mods([nsEvent modifierFlags]); + + mp_update_key_state(&__mpAppData.inputState.mouse.buttons[event.key.code], false); + + mp_queue_event(&event); +} + +- (void)mouseDragged:(NSEvent*)nsEvent +{ + [self mouseMoved:nsEvent]; +} + +- (void)mouseMoved:(NSEvent*)nsEvent +{ + NSPoint p = [self convertPoint:[nsEvent locationInWindow] fromView:nil]; + + NSRect frame = [[window->nsWindow contentView] frame]; + mp_event event = {}; + event.type = MP_EVENT_MOUSE_MOVE; + event.window = mp_window_handle_from_ptr(window); + event.move.x = p.x; + event.move.y = p.y; + event.move.deltaX = [nsEvent deltaX]; + event.move.deltaY = -[nsEvent deltaY]; + event.move.mods = mp_convert_osx_mods([nsEvent modifierFlags]); + + mp_update_mouse_move(p.x, p.y, event.move.deltaX, event.move.deltaY); + + mp_queue_event(&event); +} + +- (void)scrollWheel:(NSEvent*)nsEvent +{ + mp_event event = {}; + event.window = mp_window_handle_from_ptr(window); + event.type = MP_EVENT_MOUSE_WHEEL; + + double factor = [nsEvent hasPreciseScrollingDeltas] ? 0.1 : 1.0; + event.move.x = 0; + event.move.y = 0; + event.move.deltaX = [nsEvent scrollingDeltaX]*factor; + event.move.deltaY = -[nsEvent scrollingDeltaY]*factor; + event.move.mods = mp_convert_osx_mods([nsEvent modifierFlags]); + + mp_update_mouse_wheel(event.move.deltaX, event.move.deltaY); + + mp_queue_event(&event); +} + +- (void)mouseExited:(NSEvent *)nsEvent +{ + mp_event event = {}; + event.window = mp_window_handle_from_ptr(window); + event.type = MP_EVENT_MOUSE_LEAVE; + mp_queue_event(&event); +} + +- (void)mouseEntered:(NSEvent *)nsEvent +{ + mp_event event = {}; + event.window = mp_window_handle_from_ptr(window); + event.type = MP_EVENT_MOUSE_ENTER; + mp_queue_event(&event); +} + + + +- (void)keyDown:(NSEvent*)nsEvent +{ + mp_event event = {}; + event.window = mp_window_handle_from_ptr(window); + event.type = MP_EVENT_KEYBOARD_KEY; + event.key.action = MP_KEY_PRESS; + event.key.code = mp_convert_osx_key([nsEvent keyCode]); + event.key.mods = mp_convert_osx_mods([nsEvent modifierFlags]); + + str8 label = mp_key_to_label(event.key.code); + event.key.labelLen = label.len; + memcpy(event.key.label, label.ptr, label.len); + + mp_update_key_state(&__mpAppData.inputState.keyboard.keys[event.key.code], true); + + mp_queue_event(&event); + + [self interpretKeyEvents:@[nsEvent]]; +} + +- (void)keyUp:(NSEvent*)nsEvent +{ + mp_event event = {}; + event.window = mp_window_handle_from_ptr(window); + event.type = MP_EVENT_KEYBOARD_KEY; + event.key.action = MP_KEY_RELEASE; + event.key.code = mp_convert_osx_key([nsEvent keyCode]); + event.key.mods = mp_convert_osx_mods([nsEvent modifierFlags]); + + mp_update_key_state(&__mpAppData.inputState.keyboard.keys[event.key.code], false); + + mp_queue_event(&event); +} + +- (void) flagsChanged:(NSEvent*)nsEvent +{ + mp_event event = {}; + event.window = mp_window_handle_from_ptr(window); + event.type = MP_EVENT_KEYBOARD_MODS; + event.key.mods = mp_convert_osx_mods([nsEvent modifierFlags]); + + mp_update_key_mods(event.key.mods); + + mp_queue_event(&event); +} + +- (BOOL)performKeyEquivalent:(NSEvent*)nsEvent +{ + if([nsEvent modifierFlags] & NSEventModifierFlagCommand) + { + if([nsEvent charactersIgnoringModifiers] == [NSString stringWithUTF8String:"w"]) + { + [window->nsWindow performClose:self]; + return(YES); + } + else if([nsEvent charactersIgnoringModifiers] == [NSString stringWithUTF8String:"q"]) + { + __mpAppData.shouldQuit = true; + + mp_event event = {}; + event.type = MP_EVENT_QUIT; + + mp_queue_event(&event); + + //[NSApp terminate:self]; + return(YES); + } + } + + return([super performKeyEquivalent:nsEvent]); +} + +- (BOOL)hasMarkedText +{ + return([markedText length] > 0); +} + +static const NSRange kEmptyRange = { NSNotFound, 0 }; + +- (NSRange)markedRange +{ + if([markedText length] > 0) + { + return(NSMakeRange(0, [markedText length] - 1)); + } + else + { + return(kEmptyRange); + } +} + +- (NSRange)selectedRange +{ + return(kEmptyRange); +} + +- (void)setMarkedText:(id)string + selectedRange:(NSRange)selectedRange + replacementRange:(NSRange)replacementRange +{ + [markedText release]; + + if([string isKindOfClass:[NSAttributedString class]]) + { + markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string]; + } + else + { + markedText = [[NSMutableAttributedString alloc] initWithString:string]; + } +} + +- (void)unmarkText +{ + [[markedText mutableString] setString:@""]; +} + +- (NSArray*)validAttributesForMarkedText +{ + return([NSArray array]); +} + +- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range + actualRange:(NSRangePointer)actualRange +{ + return(nil); +} + +- (NSUInteger)characterIndexForPoint:(NSPoint)point +{ + return(0); +} + +- (NSRect)firstRectForCharacterRange:(NSRange)range + actualRange:(NSRangePointer)actualRange +{ + NSRect frame = [window->nsView frame]; + return(NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0)); +} + +- (void)insertText:(id)string replacementRange:(NSRange)replacementRange +{ + NSString* characters; + NSEvent* nsEvent = [NSApp currentEvent]; + mp_key_mods mods = mp_convert_osx_mods([nsEvent modifierFlags]); + + if([string isKindOfClass:[NSAttributedString class]]) + { + characters = [string string]; + } + else + { + characters = (NSString*) string; + } + + NSRange range = NSMakeRange(0, [characters length]); + while (range.length) + { + utf32 codepoint = 0; + + if ([characters getBytes:&codepoint + maxLength:sizeof(codepoint) + usedLength:NULL + encoding:NSUTF32StringEncoding + options:0 + range:range + remainingRange:&range]) + { + if(codepoint >= 0xf700 && codepoint <= 0xf7ff) + { + continue; + } + + mp_event event = {}; + event.window = mp_window_handle_from_ptr(window); + event.type = MP_EVENT_KEYBOARD_CHAR; + event.character.codepoint = codepoint; + + str8 seq = utf8_encode(event.character.sequence, event.character.codepoint); + event.character.seqLen = seq.len; + + mp_update_text(codepoint); + + mp_queue_event(&event); + } + } + [self unmarkText]; +} + +- (void)doCommandBySelector:(SEL)selector +{ +} + +@end //@implementation MPNativeView + +/* +void mp_sleep_nanoseconds(u64 nanoseconds) +{ + timespec rqtp; + rqtp.tv_sec = nanoseconds / 1000000000; + rqtp.tv_nsec = nanoseconds - rqtp.tv_sec * 1000000000; + nanosleep(&rqtp, 0); +} + +static mach_timebase_info_data_t __machTimeBase__ = {1,1}; + +u64 mp_get_elapsed_nanoseconds() +{ + //NOTE(martin): according to the documentation, mach_absolute_time() does not + // increment when the system is asleep + u64 now = mach_absolute_time(); + now *= __machTimeBase__.numer; + now /= __machTimeBase__.denom; + return(now); +} + +f64 mp_get_elapsed_seconds() +{ + return(1.e-9*(f64)mp_get_elapsed_nanoseconds()); +} +*/ + +//*************************************************************** +// public API +//*************************************************************** + +//--------------------------------------------------------------- +// App public API +//--------------------------------------------------------------- + +void mp_init() +{@autoreleasepool { + if(!__mpAppData.init) + { + memset(&__mpAppData, 0, sizeof(__mpAppData)); + + mp_clock_init(); + + LOG_MESSAGE("init keys\n"); + mp_init_osx_keys(); + mp_update_keyboard_layout(); + mp_install_keyboard_layout_listener(); + + LOG_MESSAGE("init handles\n"); + mp_init_window_handles(); + mp_init_view_handles(); + + LOG_MESSAGE("init event queue\n"); + ringbuffer_init(&__mpAppData.eventQueue, 16); + + [MPApplication sharedApplication]; + MPAppDelegate* delegate = [[MPAppDelegate alloc] init]; + [NSApp setDelegate: delegate]; + + [NSThread detachNewThreadSelector:@selector(noOpThread:) + toTarget:NSApp + withObject:nil]; + + __mpAppData.init = true; + + LOG_MESSAGE("run application\n"); + [NSApp run]; + + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + [NSApp activateIgnoringOtherApps:YES]; + + } +}} + +void mp_terminate() +{ + //TODO: proper app data cleanup (eg delegate, etc) + if(__mpAppData.init) + { + __mpAppData = (mp_app_data){0}; + } +} + +bool mp_should_quit() +{ + return(__mpAppData.shouldQuit); +} + +void mp_do_quit() +{ + __mpAppData.shouldQuit = true; +} + +void mp_cancel_quit() +{ + __mpAppData.shouldQuit = false; +} + +void mp_request_quit() +{ + __mpAppData.shouldQuit = true; + mp_event event = {}; + event.type = MP_EVENT_QUIT; + mp_queue_event(&event); +} + +void mp_set_cursor(mp_mouse_cursor cursor) +{ + switch(cursor) + { + case MP_MOUSE_CURSOR_ARROW: + { + __mpAppData.cursor = [NSCursor arrowCursor]; + } break; + case MP_MOUSE_CURSOR_RESIZE_0: + { + __mpAppData.cursor = [[NSCursor class] performSelector:@selector(_windowResizeEastWestCursor)]; + } break; + case MP_MOUSE_CURSOR_RESIZE_90: + { + __mpAppData.cursor = [[NSCursor class] performSelector:@selector(_windowResizeNorthSouthCursor)]; + } break; + case MP_MOUSE_CURSOR_RESIZE_45: + { + __mpAppData.cursor = [[NSCursor class] performSelector:@selector(_windowResizeNorthEastSouthWestCursor)]; + } break; + case MP_MOUSE_CURSOR_RESIZE_135: + { + __mpAppData.cursor = [[NSCursor class] performSelector:@selector(_windowResizeNorthWestSouthEastCursor)]; + } break; + case MP_MOUSE_CURSOR_TEXT: + { + __mpAppData.cursor = [NSCursor IBeamCursor]; + } break; + } + [__mpAppData.cursor set]; +} + +void mp_clipboard_clear() +{@autoreleasepool{ + NSPasteboard* pb = [NSPasteboard generalPasteboard]; + [pb clearContents]; +}} + +void mp_clipboard_set_string(str8 string) +{@autoreleasepool{ + + NSString* nsString = [[NSString alloc] initWithBytes:string.ptr length:string.len encoding:NSUTF8StringEncoding]; + NSPasteboard* pb = [NSPasteboard generalPasteboard]; + [pb writeObjects:[[NSArray alloc] initWithObjects:nsString, nil]]; +}} + +str8 mp_clipboard_copy_string(str8 backing) +{@autoreleasepool{ + //WARN(martin): maxSize includes space for a null terminator + + NSPasteboard* pb = [NSPasteboard generalPasteboard]; + NSString* nsString = [pb stringForType:NSPasteboardTypeString]; + const char* cString = [nsString UTF8String]; + u32 len = minimum(backing.len-1, strlen(cString)); //length without null terminator + strncpy(backing.ptr, cString, backing.len-1); + backing.ptr[len] = '\0'; + + str8 result = str8_slice(backing, 0, len); + return(result); +}} + +str8 mp_clipboard_get_string(mem_arena* arena) +{@autoreleasepool{ + //WARN(martin): maxSize includes space for a null terminator + + NSPasteboard* pb = [NSPasteboard generalPasteboard]; + NSString* nsString = [pb stringForType:NSPasteboardTypeString]; + const char* cString = [nsString UTF8String]; + str8 result = str8_push_cstring(arena, cString); + return(result); +}} + +bool mp_clipboard_has_tag(const char* tag) +{@autoreleasepool{ + + NSString* tagString = [[NSString alloc] initWithUTF8String: tag]; + NSArray* tagArray = [NSArray arrayWithObjects: tagString, nil]; + + NSPasteboard* pb = [NSPasteboard generalPasteboard]; + NSString* available = [pb availableTypeFromArray: tagArray]; + + return(available != nil); +}} + +void mp_clipboard_set_data_for_tag(const char* tag, str8 string) +{@autoreleasepool{ + + NSString* tagString = [[NSString alloc] initWithUTF8String: tag]; + NSArray* tagArray = [NSArray arrayWithObjects: tagString, nil]; + NSData* nsData = [NSData dataWithBytes:string.ptr length:string.len]; + + NSPasteboard* pb = [NSPasteboard generalPasteboard]; + [pb addTypes: tagArray owner:nil]; + [pb setData: nsData forType: tagString]; +}} + +str8 mp_clipboard_get_data_for_tag(mem_arena* arena, const char* tag) +{@autoreleasepool{ + + NSString* tagString = [[NSString alloc] initWithUTF8String: tag]; + + NSPasteboard* pb = [NSPasteboard generalPasteboard]; + NSData* nsData = [pb dataForType: tagString]; + str8 result = str8_push_buffer(arena, [nsData length], (char*)[nsData bytes]); + return(result); +}} + + +//--------------------------------------------------------------- +// Window public API +//--------------------------------------------------------------- + +bool mp_window_handle_is_null(mp_window window) +{ + return(window.h == 0); +} + +mp_window mp_window_null_handle() +{ + return((mp_window){.h = 0}); +} + +/* +//TODO(martin): review include scheme +extern "C" { + mp_graphics_surface mp_metal_surface_create_for_window_ptr(mp_window_data* window); + mp_graphics_surface mp_graphics_surface_null_handle(); + mp_graphics_surface mp_graphics_surface_handle_from_ptr(mp_graphics_surface_data* surface); +} +*/ + +mp_window mp_window_create(mp_rect contentRect, const char* title, mp_window_style style) +{@autoreleasepool{ + mp_window_data* window = mp_window_alloc(); + if(!window) + { + LOG_ERROR("Could not allocate window data\n"); + return((mp_window){0}); + } + + window->style = style; + window->shouldClose = false; + window->hidden = true; + + u32 styleMask = mp_osx_get_window_style_mask(style); + + NSRect screenRect = [[NSScreen mainScreen] frame]; + NSRect rect = NSMakeRect(contentRect.x, + screenRect.size.height - contentRect.y - contentRect.h, + contentRect.w, + contentRect.h); + + window->nsWindow = [[MPNativeWindow alloc] initWithMPWindow: window contentRect:rect styleMask:styleMask]; + window->nsWindowDelegate = [[MPNativeWindowDelegate alloc] initWithMPWindow:window]; + + [window->nsWindow setDelegate:(id)window->nsWindowDelegate]; + [window->nsWindow setTitle:[NSString stringWithUTF8String:title]]; + + if(style & MP_WINDOW_STYLE_NO_TITLE) + { + [window->nsWindow setOpaque:NO]; + [window->nsWindow setBackgroundColor:[NSColor clearColor]]; + [window->nsWindow setHasShadow:YES]; + } + if(style & MP_WINDOW_STYLE_FLOAT) + { + [window->nsWindow setLevel:NSFloatingWindowLevel]; + [window->nsWindow setHidesOnDeactivate:YES]; + } + if(style & MP_WINDOW_STYLE_NO_BUTTONS) + { + [[window->nsWindow standardWindowButton:NSWindowCloseButton] setHidden:YES]; + [[window->nsWindow standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES]; + [[window->nsWindow standardWindowButton:NSWindowZoomButton] setHidden:YES]; + } + + MPNativeView* view = [[MPNativeView alloc] initWithMPWindow:window]; + + [window->nsWindow setContentView:view]; + [window->nsWindow makeFirstResponder:view]; + [window->nsWindow setAcceptsMouseMovedEvents:YES]; + + mp_window_update_rect_cache(window); + + mp_window windowHandle = mp_window_handle_from_ptr(window); + + mp_rect mainViewFrame = {0, 0, contentRect.w, contentRect.h}; + window->mainView = mp_view_create(windowHandle, mainViewFrame); + + return(windowHandle); +}//autoreleasepool +} + +void mp_window_destroy(mp_window window) +{@autoreleasepool{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + [windowData->nsWindow orderOut:nil]; + + [windowData->nsWindow setDelegate:nil]; + [windowData->nsWindowDelegate release]; + windowData->nsWindowDelegate = nil; + + [windowData->nsView release]; + windowData->nsView = nil; + + [windowData->nsWindow close]; //also release the window + + mp_window_recycle_ptr(windowData); + } +} // autoreleasepool +} + +bool mp_window_should_close(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + return(windowData->shouldClose); + } + else + { + return(false); + } +} + +void mp_window_cancel_close(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + windowData->shouldClose = false; + } +} + +void mp_window_request_close(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + [windowData->nsWindow close]; + //NOTE(martin): this will call our window delegate willClose method + } +} + +void* mp_window_native_pointer(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + return((__bridge void*)windowData->nsWindow); + } + else + { + return(0); + } +} + +void mp_window_center(mp_window window) +{@autoreleasepool{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + [windowData->nsWindow center]; + } +}} + +bool mp_window_is_hidden(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + return(windowData->hidden); + } + else + { + return(false); + } +} + +bool mp_window_is_focused(mp_window window) +{@autoreleasepool{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + return([windowData->nsWindow isKeyWindow]); + } + else + { + return(false); + } +}} + +void mp_window_hide(mp_window window) +{@autoreleasepool{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + windowData->hidden = true; + [windowData->nsWindow orderOut:nil]; + } +}} + +void mp_window_focus(mp_window window) +{@autoreleasepool{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + [windowData->nsWindow makeKeyWindow]; + } +}} + +void mp_window_send_to_back(mp_window window) +{@autoreleasepool{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + [windowData->nsWindow orderBack:nil]; + } +}} + +void mp_window_bring_to_front(mp_window window) +{@autoreleasepool{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + windowData->hidden = false; + [windowData->nsWindow orderFront:nil]; + } +}} + +void mp_window_bring_to_front_and_focus(mp_window window) +{ + mp_window_bring_to_front(window); + mp_window_focus(window); +} + + +mp_rect mp_window_content_rect_for_frame_rect(mp_rect frameRect, mp_window_style style) +{@autoreleasepool{ + u32 mask = mp_osx_get_window_style_mask(style); + mp_rect nativeFrame = mp_user_to_osx_screen_rect(frameRect); + NSRect frame = NSMakeRect(nativeFrame.x, nativeFrame.y, nativeFrame.w, nativeFrame.h); + NSRect content = [NSWindow contentRectForFrameRect:frame styleMask:mask]; + mp_rect result = {content.origin.x, content.origin.y, content.size.width, content.size.height}; + result = mp_osx_to_user_screen_rect(result); + return(result); +}} + +mp_rect mp_window_frame_rect_for_content_rect(mp_rect contentRect, mp_window_style style) +{@autoreleasepool{ + uint32 mask = mp_osx_get_window_style_mask(style); + mp_rect nativeContent = mp_user_to_osx_screen_rect(contentRect); + NSRect content = NSMakeRect(nativeContent.x, nativeContent.y, nativeContent.w, nativeContent.h); + NSRect frame = [NSWindow frameRectForContentRect:content styleMask:mask]; + mp_rect result = {frame.origin.x, frame.origin.y, frame.size.width, frame.size.height}; + result = mp_osx_to_user_screen_rect(result); + return(result); +}} + +mp_rect mp_window_get_content_rect(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + return(windowData->contentRect); + } + else + { + return((mp_rect){}); + } +} + +mp_rect mp_window_get_absolute_content_rect(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + mp_rect rect = windowData->contentRect; + rect.x += windowData->frameRect.x; + rect.y += windowData->frameRect.y; + return(rect); + } + else + { + return((mp_rect){}); + } +} + +mp_rect mp_window_get_frame_rect(mp_window window) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + return(windowData->frameRect); + } + else + { + return((mp_rect){}); + } +} + +void mp_window_set_content_rect(mp_window window, mp_rect contentRect) +{@autoreleasepool{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + u32 mask = mp_osx_get_window_style_mask(windowData->style); + + mp_rect nativeRect = mp_user_to_osx_screen_rect(contentRect); + NSRect content = NSMakeRect(nativeRect.x, nativeRect.y, nativeRect.w, nativeRect.h); + NSRect frame = [NSWindow frameRectForContentRect:content styleMask:mask]; + + [windowData->nsWindow setFrame:frame display:YES]; + + mp_window_update_rect_cache(windowData); + } +}} +void mp_window_set_frame_rect(mp_window window, mp_rect frameRect) +{@autoreleasepool{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + mp_rect nativeRect = mp_user_to_osx_screen_rect(frameRect); + NSRect frame = NSMakeRect(nativeRect.x, nativeRect.y, nativeRect.w, nativeRect.h); + [windowData->nsWindow setFrame:frame display:YES]; + + mp_window_update_rect_cache(windowData); + NSRect contentRect = [[windowData->nsWindow contentView] frame]; + } +}} + +void mp_window_set_frame_size(mp_window window, int width, int height) +{ + mp_rect frame = mp_window_get_frame_rect(window); + frame.w = width; + frame.h = height; + mp_window_set_frame_rect(window, frame); +} + +void mp_window_set_content_size(mp_window window, int width, int height) +{ + mp_window_data* windowData = mp_window_ptr_from_handle(window); + if(windowData) + { + mp_rect frame = windowData->frameRect; + mp_rect content = mp_window_content_rect_for_frame_rect(frame, windowData->style); + content.w = width; + content.h = height; + frame = mp_window_frame_rect_for_content_rect(content, windowData->style); + mp_window_set_frame_rect(window, frame); + } +} + +//-------------------------------------------------------------------- +// view management +//-------------------------------------------------------------------- + +mp_view mp_view_nil() +{ + return((mp_view){0}); +} + +bool mp_view_is_nil(mp_view view) +{ + return(!view.h); +} + +mp_view mp_view_create(mp_window windowHandle, mp_rect frame) +{@autoreleasepool{ + mp_window_data* window = mp_window_ptr_from_handle(windowHandle); + if(!window) + { + LOG_ERROR("Can't create view for nil window\n"); + return(mp_view_nil()); + } + + mp_view_data* view = mp_view_alloc(); + if(!view) + { + LOG_ERROR("Could not allocate view data\n"); + return(mp_view_nil()); + } + + view->window = windowHandle; + + NSRect nsFrame = {{frame.x, frame.y}, {frame.w, frame.h}}; + view->nsView = [[NSView alloc] initWithFrame: nsFrame]; + + [[window->nsWindow contentView] addSubview: view->nsView]; + + return(mp_view_handle_from_ptr(view)); +}} + +void mp_view_destroy(mp_view viewHandle) +{@autoreleasepool{ + mp_view_data* view = mp_view_ptr_from_handle(viewHandle); + if(!view) + { + return; + } + + mp_window_data* window = mp_window_ptr_from_handle(view->window); + if(!window) + { + return; + } + + [view->nsView removeFromSuperview]; + + mp_view_recycle_ptr(view); +}} + +void mp_view_set_frame(mp_view viewHandle, mp_rect frame) +{ + mp_view_data* view = mp_view_ptr_from_handle(viewHandle); + if(!view) + { + return; + } + + NSRect nsFrame = {{frame.x, frame.y}, {frame.w, frame.h}}; + view->nsView = [[NSView alloc] initWithFrame: nsFrame]; + + mg_surface_resize(view->surface, frame.w, frame.h); +} + +//-------------------------------------------------------------------- +// Main loop throttle +//-------------------------------------------------------------------- +/* +//////////////////////////////////////////////////////////// +//TODO +//////////////////////////////////////////////////////////// + +void mp_set_target_fps(u32 fps) +{ + __mpAppData.frameStats.targetFramePeriod = 1./(f64)fps; + __mpAppData.frameStats.workTime = 0; + __mpAppData.frameStats.remainingTime = 0; + + if(__mpAppData.frameTimer) + { + [__mpAppData.frameTimer invalidate]; + } + + __mpAppData.frameTimer = [NSTimer timerWithTimeInterval: __mpAppData.frameStats.targetFramePeriod + target: [NSApp delegate] + selector:@selector(timerElapsed:) + userInfo:nil + repeats:YES]; + + [[NSRunLoop currentRunLoop] addTimer:__mpAppData.frameTimer forMode:NSRunLoopCommonModes]; +} + +void mp_begin_frame() +{ + __mpAppData.frameStats.start = mp_get_elapsed_seconds(); + + LOG_DEBUG("workTime = %.6f (%.6f fps), remaining = %.6f\n", + __mpAppData.frameStats.workTime, + 1/__mpAppData.frameStats.workTime, + __mpAppData.frameStats.remainingTime); + +} + +void mp_end_frame() +{ + __mpAppData.frameStats.workTime = mp_get_elapsed_seconds() - __mpAppData.frameStats.start; + __mpAppData.frameStats.remainingTime = __mpAppData.frameStats.targetFramePeriod - __mpAppData.frameStats.workTime; + + while(__mpAppData.frameStats.remainingTime > 100e-9) + { + if(__mpAppData.frameStats.remainingTime > 10e-6) + { + mp_sleep_nanoseconds(__mpAppData.frameStats.remainingTime*0.8*1e9); + } + __mpAppData.frameStats.workTime = mp_get_elapsed_seconds() - __mpAppData.frameStats.start; + __mpAppData.frameStats.remainingTime = __mpAppData.frameStats.targetFramePeriod - __mpAppData.frameStats.workTime; + } +} +*/ + +//-------------------------------------------------------------------- +// Events handling +//-------------------------------------------------------------------- + +void mp_pump_events(f64 timeout) +{ + __mpAppData.inputState.frameCounter++; + + @autoreleasepool + { + bool accumulate = false; + NSDate* date = 0; + if(timeout > 0) + { + date = [NSDate dateWithTimeIntervalSinceNow: (double) timeout]; + } + else if(timeout == 0) + { + date = [NSDate distantPast]; + accumulate = true; + } + else + { + date = [NSDate distantFuture]; + } + + for(;;) + { + NSEvent* event = [NSApp nextEventMatchingMask: NSEventMaskAny + untilDate:date + inMode: NSDefaultRunLoopMode + dequeue: YES]; + + if(event != nil) + { + [NSApp sendEvent:event]; + + if(!accumulate) + { + break; + } + } + else + { + break; + } + } + } +} + +bool mp_next_event(mp_event* event) +{ + //NOTE pop and return event from queue + if(ringbuffer_read_available(&__mpAppData.eventQueue) >= sizeof(mp_event)) + { + u64 read = ringbuffer_read(&__mpAppData.eventQueue, sizeof(mp_event), (u8*)event); + DEBUG_ASSERT(read == sizeof(mp_event)); + return(true); + } + else + { + return(false); + } +} + +//-------------------------------------------------------------------- +// Input state polling +//-------------------------------------------------------------------- + +mp_key_state mp_input_get_key_state(mp_key_code key) +{ + if(key <= MP_KEY_COUNT) + { + return(__mpAppData.inputState.keyboard.keys[key]); + } + else + { + return((mp_key_state){0}); + } +} +mp_key_state mp_input_get_mouse_button_state(mp_mouse_button button) +{ + if(button <= MP_MOUSE_BUTTON_COUNT) + { + return(__mpAppData.inputState.mouse.buttons[button]); + } + else + { + return((mp_key_state){0}); + } +} + +bool mp_input_check_key_transition(mp_key_state* key, bool pressed) +{ + bool res = ( (key->lastUpdate == __mpAppData.inputState.frameCounter) + && key->transitionCounter + &&(key->down == pressed || key->transitionCounter > 1)); + return(res); +} + +bool mp_input_key_down(mp_key_code key) +{ + mp_key_state state = mp_input_get_key_state(key); + return(state.down); +} +bool mp_input_key_pressed(mp_key_code key) +{ + mp_key_state state = mp_input_get_key_state(key); + bool res = mp_input_check_key_transition(&state, true); + return(res); +} + +bool mp_input_key_released(mp_key_code key) +{ + mp_key_state state = mp_input_get_key_state(key); + bool res = mp_input_check_key_transition(&state, false); + return(res); +} + +bool mp_input_mouse_down(mp_mouse_button button) +{ + mp_key_state state = mp_input_get_mouse_button_state(button); + return(state.down); +} + +bool mp_input_mouse_pressed(mp_mouse_button button) +{ + mp_key_state state = mp_input_get_mouse_button_state(button); + bool res = mp_input_check_key_transition(&state, true); + return(res); +} + +bool mp_input_mouse_released(mp_mouse_button button) +{ + mp_key_state state = mp_input_get_mouse_button_state(button); + bool res = mp_input_check_key_transition(&state, false); + return(res); +} + +bool mp_input_mouse_clicked(mp_mouse_button button) +{ + mp_key_state state = mp_input_get_mouse_button_state(button); + return(state.clicked); +} + +bool mp_input_mouse_double_clicked(mp_mouse_button button) +{ + mp_key_state state = mp_input_get_mouse_button_state(button); + return(state.doubleClicked); +} + +mp_key_mods mp_input_key_mods() +{ + return(__mpAppData.inputState.keyboard.mods); +} + +vec2 mp_input_mouse_position() +{ + return(__mpAppData.inputState.mouse.pos); +} + +vec2 mp_input_mouse_delta() +{ + if(__mpAppData.inputState.mouse.lastUpdate == __mpAppData.inputState.frameCounter) + { + return(__mpAppData.inputState.mouse.delta); + } + else + { + return((vec2){0, 0}); + } +} + +vec2 mp_input_mouse_wheel() +{ + if(__mpAppData.inputState.mouse.lastUpdate == __mpAppData.inputState.frameCounter) + { + return(__mpAppData.inputState.mouse.wheel); + } + else + { + return((vec2){0, 0}); + } +} + +str32 mp_input_text_utf32(mem_arena* arena) +{ + str32 res = {0}; + if(__mpAppData.inputState.text.lastUpdate == __mpAppData.inputState.frameCounter) + { + res = str32_push_copy(arena, __mpAppData.inputState.text.codePoints); + } + return(res); +} + +str8 mp_input_text_utf8(mem_arena* arena) +{ + str8 res = {0}; + if(__mpAppData.inputState.text.lastUpdate == __mpAppData.inputState.frameCounter) + { + res = utf8_push_from_codepoints(arena, __mpAppData.inputState.text.codePoints); + } + return(res); +} + +//-------------------------------------------------------------------- +// app resources +//-------------------------------------------------------------------- + +#import +#include + +int mp_app_get_resource_path(const char* name, char** result) +{ + @autoreleasepool + { + NSBundle* mainBundle = [NSBundle mainBundle]; + if(!mainBundle) + { + //NOTE(martin): we assume we are running from the command line in debug mode + char* currentPath = getcwd(0, 0); + char* buffer = (char*)malloc(strlen(currentPath) + strlen(name) + 2); + strcpy(buffer, currentPath); + strcat(buffer, "/"); + strcat(buffer, name); + *result = realpath(buffer, 0); + free(currentPath); + free(buffer); + return(1); + } + else + { + NSString* nsName = [[NSString alloc] initWithUTF8String:name]; + NSString* nsPath = [mainBundle executablePath]; + + const char* utf8Path = [nsPath UTF8String]; + const char* dir = dirname((char*)utf8Path); + char* buffer = (char*)malloc(strlen(dir) + strlen(name) + 2); + strcpy(buffer, dir); + strcat(buffer, "/"); + strcat(buffer, name); + *result = realpath(buffer, 0); + free(buffer); + + return(-1); + } + } +} + +#include +str8 mp_app_get_executable_path(mem_arena* arena) +{@autoreleasepool{ + str8 result = {}; + u32 size = 0; + _NSGetExecutablePath(0, &size); + result.len = size; + result.ptr = mem_arena_alloc_array(arena, char, result.len); + _NSGetExecutablePath(result.ptr, &size); + return(result); +}} + +//-------------------------------------------------------------------- +// system dialogs windows +//-------------------------------------------------------------------- + +str8 mp_open_dialog(mem_arena* arena, + const char* title, + const char* defaultPath, + int filterCount, + const char** filters, + bool directory) +{ + @autoreleasepool + { + NSWindow *keyWindow = [NSApp keyWindow]; + + NSOpenPanel* dialog = [NSOpenPanel openPanel] ; + [dialog setLevel:CGShieldingWindowLevel()]; + + if(filterCount) + { + NSMutableArray * fileTypesArray = [NSMutableArray array]; + for(int i=0; i < filterCount; i++) + { + NSString * filt = [NSString stringWithUTF8String:filters[i]]; + [fileTypesArray addObject:filt]; + } + [dialog setAllowedFileTypes:fileTypesArray]; + } + // Enable options in the dialog. + if(directory) + { + [dialog setCanChooseDirectories:YES]; + } + else + { + [dialog setCanChooseFiles:YES]; + } + + + [dialog setAllowsMultipleSelection:FALSE]; + NSString* nsPath = [[NSString stringWithUTF8String:defaultPath?defaultPath:"~"] stringByExpandingTildeInPath]; + [dialog setDirectoryURL:[NSURL fileURLWithPath:nsPath]]; + + // Display the dialog box. If the OK pressed, + // process the files. + + if( [dialog runModal] == NSModalResponseOK ) + { + // Gets list of all files selected + NSArray *files = [dialog URLs]; + //TODO: Loop through the files and process them. + + const char* result = [[[files objectAtIndex:0] path] UTF8String]; + + str8 path = str8_push_cstring(arena, result); + [keyWindow makeKeyWindow]; + + return(path); + } + else + { + [keyWindow makeKeyWindow]; + return((str8){0, 0}); + } + } +} + + +str8 mp_save_dialog(mem_arena* arena, + const char* title, + const char* defaultPath, + int filterCount, + const char** filters) +{ + @autoreleasepool + { + NSWindow *keyWindow = [NSApp keyWindow]; + + NSSavePanel* dialog = [NSSavePanel savePanel] ; + [dialog setLevel:CGShieldingWindowLevel()]; + + if(filterCount) + { + NSMutableArray * fileTypesArray = [NSMutableArray array]; + for(int i=0; i < filterCount; i++) + { + NSString * filt = [NSString stringWithUTF8String:filters[i]]; + [fileTypesArray addObject:filt]; + } + + // Enable options in the dialog. + [dialog setAllowedFileTypes:fileTypesArray]; + } + NSString* nsPath = [[NSString stringWithUTF8String:defaultPath?defaultPath:"~"] stringByExpandingTildeInPath]; + [dialog setDirectoryURL:[NSURL fileURLWithPath:nsPath]]; + + // Display the dialog box. If the OK pressed, + // process the files. + + if( [dialog runModal] == NSModalResponseOK ) + { + // Gets list of all files selected + NSURL *files = [dialog URL]; + // Loop through the files and process them. + + const char* result = [[files path] UTF8String]; + + str8 path = str8_push_cstring(arena, result); + [keyWindow makeKeyWindow]; + return(path); + } + else + { + [keyWindow makeKeyWindow]; + return((str8){0, 0}); + } + } +} + +int mp_alert_popup(const char* title, + const char* message, + uint32 count, + const char** options) +{ + @autoreleasepool + { + NSWindow *keyWindow = [NSApp keyWindow]; + + NSAlert* alert = [[NSAlert alloc] init]; + NSString* string; + for(int i=count-1;i>=0;i--) + { + string = [[NSString alloc] initWithUTF8String:options[i]]; + [alert addButtonWithTitle:string]; + [string release]; + } + string = [[NSString alloc] initWithUTF8String:message]; + [alert setMessageText:string]; + [string release]; + + [alert setAlertStyle:NSAlertStyleWarning]; + int result = count - ([alert runModal]-NSAlertFirstButtonReturn) - 1; + printf("result = %i, NSAlertFirstButtonReturn = %li\n", result, (long)NSAlertFirstButtonReturn); + [keyWindow makeKeyWindow]; + return(result); + } +} + + +#undef LOG_SUBSYSTEM diff --git a/src/platform/linux_clock.c b/src/platform/linux_clock.c new file mode 100644 index 0000000..d6c1367 --- /dev/null +++ b/src/platform/linux_clock.c @@ -0,0 +1,193 @@ +/************************************************************//** +* +* @file: linux_clock.c +* @author: Martin Fouilleul +* @date: 20/04/2020 +* @revision: +* +*****************************************************************/ + +#include //fabs() +#include +#include // gettimeofday() + +#include +#include + +#include // nanosleep() + +#include"platform_rng.h" +#include"platform_clock.h" + +extern "C" { + +//TODO(martin): measure the actual values of these constants +const f64 SYSTEM_FUZZ = 25e-9, // minimum time to read the clock (s) + SYSTEM_TICK = 25e-9; // minimum step between two clock readings (s) + +static u64 __initialTimestamp__ = 0; +static u64 __initialMonotonicNanoseconds__ = 0; + +static inline u64 LinuxGetMonotonicNanoseconds() +{ + timespec ts; + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); + //WARN(martin): do not multiply ts.tv_sec directly (implicit conversion will overflow) + u64 r = ts.tv_sec; + r *= 1000000000; + r += ts.tv_nsec; + return(r); +} + +static inline u64 LinuxGetUptimeNanoseconds() +{ + timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + //WARN(martin): do not multiply ts.tv_sec directly (implicit conversion will overflow) + u64 r = ts.tv_sec; + r *= 1000000000; + r += ts.tv_nsec; + return(r); +} + + +void ClockSystemInit() +{ + //NOTE(martin): we don't know of a widely supported way of getting the boot time on linux, so + // we fallback to our second best choice, which is taking an initial timestamp and + // the initial monotonic time now + + timeval tv; + gettimeofday(&tv, 0); + __initialMonotonicNanoseconds__ = LinuxGetMonotonicNanoseconds(); + + //NOTE(martin): convert boot date to timestamp + __initialTimestamp__ = (((u64)tv.tv_sec + JAN_1970) << 32) + + (u64)(tv.tv_usec * 1e-6 * TIMESTAMPS_PER_SECOND); + + //TODO(martin): maybe get a state vector for exclusive clock usage ? + RandomSeedFromDevice(); +} + +fx_timestamp ClockGetTimestamp(clock_kind clock) +{ + fx_timestamp ts = {0}; + switch(clock) + { + case SYS_CLOCK_MONOTONIC: + { + //NOTE(martin): compute monotonic offset and add it to bootup timestamp + u64 noff = LinuxGetMonotonicNanoseconds() - __initialMonotonicNanoseconds__; + u64 foff = (u64)(noff * 1e-9 * TIMESTAMPS_PER_SECOND); + ts.ts = __initialTimestamp__ + foff; + } break; + + case SYS_CLOCK_UPTIME: + { + //TODO(martin): maybe we should warn that this date is inconsistent after a sleep ? + //NOTE(martin): compute uptime offset and add it to bootup timestamp + u64 noff = LinuxGetUptimeNanoseconds() - __initialMonotonicNanoseconds__ ; + u64 foff = (u64)(noff * 1e-9 * TIMESTAMPS_PER_SECOND); + ts.ts = __initialTimestamp__ + foff; + } break; + + case SYS_CLOCK_DATE: + { + //NOTE(martin): get system date and convert it to a fixed-point timestamp + timeval tv; + gettimeofday(&tv, 0); + ts.ts = (((u64)tv.tv_sec + JAN_1970) << 32) + + (u64)(tv.tv_usec * 1e-6 * TIMESTAMPS_PER_SECOND); + } break; + } + + //NOTE(martin): add a random fuzz between 0 and 1 times the system fuzz + f64 fuzz = RandomU32()/(f64)(~(0UL)) * SYSTEM_FUZZ; + fx_timediff tdfuzz = TimediffFromSeconds(fuzz); + ts = TimestampAdd(ts, tdfuzz); + //TODO(martin): ensure that we always return a value greater than the last value + + return(ts); +} + + +f64 ClockGetTime(clock_kind clock) +{ + switch(clock) + { + case SYS_CLOCK_MONOTONIC: + { + //NOTE(martin): compute monotonic offset and add it to bootup timestamp + u64 noff = LinuxGetMonotonicNanoseconds(); + return((f64)noff * 1e-9); + } break; + + case SYS_CLOCK_UPTIME: + { + //TODO(martin): maybe we should warn that this date is inconsistent after a sleep ? + //NOTE(martin): compute uptime offset and add it to bootup timestamp + u64 noff = LinuxGetUptimeNanoseconds(); + return((f64)noff * 1e-9); + } break; + + case SYS_CLOCK_DATE: + { + //TODO(martin): maybe warn about precision loss ? + // could also change the epoch since we only promise to return a relative time + //NOTE(martin): get system date and convert it to seconds + timeval tv; + gettimeofday(&tv, 0); + return(((f64)tv.tv_sec + JAN_1970) + ((f64)tv.tv_usec * 1e-6)); + } break; + } + return(0); +} + +void ClockSleepNanoseconds(u64 nanoseconds) +{ + timespec rqtp; + rqtp.tv_sec = nanoseconds / 1000000000; + rqtp.tv_nsec = nanoseconds - rqtp.tv_sec * 1000000000; + nanosleep(&rqtp, 0); +} + + + +//////////////////////////////////////////////////////////////////// +//TODO: update these functions for various clocks besides monotonic +//////////////////////////////////////////////////////////////////// +f64 ClockGetGranularity(clock_kind clock) +{ + u64 minDiff = ~(0ULL); + + const int GRANULARITY_NUM_ITERATION = 100000; + for(int i=0; i //fabs() +#include +#include // gettimeofday() +#include +#include +#include +#include // availability macros + +#include +#include + +#include // nanosleep() + +#include"platform_clock.h" + + +typedef struct timeval timeval; +typedef struct timespec timespec; + +#define LOG_SUBSYSTEM "Platform" + +//TODO(martin): measure the actual values of these constants +const f64 SYSTEM_FUZZ = 25e-9, // minimum time to read the clock (s) + SYSTEM_TICK = 25e-9; // minimum step between two clock readings (s) + +static mach_timebase_info_data_t __machTimeBase__ = {1,1}; +static u64 __initialTimestamp__ = 0; + +static inline u64 OSXGetUptimeNanoseconds() +{ + //NOTE(martin): according to the documentation, mach_absolute_time() does not + // increment when the system is asleep + u64 now = mach_absolute_time(); + now *= __machTimeBase__.numer; + now /= __machTimeBase__.denom; + return(now); +} + +static inline u64 OSXGetMonotonicNanoseconds() +{ + //NOTE(martin): according to the documentation, MP_CLOCK_MONOTONIC increment monotonically + // on systems where MP_CLOCK_MONOTONIC_RAW is present, we may want to use that instead, + // because MP_CLOCK_MONOTONIC seems to be subject to frequency changes ? + + #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200 + #ifndef CLOCK_MONOTONIC_RAW + #error "CLOCK_MONOTONIC_RAW not found. Please verify that is included from the MacOSX SDK rather than /usr/local/include" + #else + return(clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW)); + #endif + #else + //TODO(martin): quick and dirty hack is to fallback to uptime, + // but we should either only support macos version >= 10.12, or find a proper solution + return(OSXGetUptimeNanoseconds()); + #endif +} + +static const f64 CLK_TIMESTAMPS_PER_SECOND = 4294967296.; // 2^32 as a double +static const u64 CLK_JAN_1970 = 2208988800ULL; // seconds from january 1900 to january 1970 + +void mp_clock_init() +{ + mach_timebase_info(&__machTimeBase__); + + //NOTE(martin): get the date of system boot time + timeval tv = {0, 0}; + + int mib[2] = {CTL_KERN, + KERN_BOOTTIME}; + size_t size = sizeof(tv); + + if(sysctl(mib, 2, &tv, &size, 0, 0) == -1) + { + LOG_ERROR("can't read boot time\n"); + } + //NOTE(martin): convert boot date to timestamp + __initialTimestamp__ = (((u64)tv.tv_sec + CLK_JAN_1970) << 32) + + (u64)(tv.tv_usec * 1e-6 * CLK_TIMESTAMPS_PER_SECOND); + + //TODO(martin): maybe get a state vector for exclusive clock usage ? + //RandomSeedFromDevice(); +} + +u64 mp_get_timestamp(mp_clock_kind clock) +{ + u64 ts = 0; + switch(clock) + { + case MP_CLOCK_MONOTONIC: + { + //NOTE(martin): compute monotonic offset and add it to bootup timestamp + u64 noff = OSXGetMonotonicNanoseconds() ; + u64 foff = (u64)(noff * 1e-9 * CLK_TIMESTAMPS_PER_SECOND); + ts = __initialTimestamp__ + foff; + } break; + + case MP_CLOCK_UPTIME: + { + //TODO(martin): maybe we should warn that this date is inconsistent after a sleep ? + //NOTE(martin): compute uptime offset and add it to bootup timestamp + u64 noff = OSXGetUptimeNanoseconds() ; + u64 foff = (u64)(noff * 1e-9 * CLK_TIMESTAMPS_PER_SECOND); + ts = __initialTimestamp__ + foff; + } break; + + case MP_CLOCK_DATE: + { + //NOTE(martin): get system date and convert it to a fixed-point timestamp + timeval tv; + gettimeofday(&tv, 0); + ts = (((u64)tv.tv_sec + CLK_JAN_1970) << 32) + + (u64)(tv.tv_usec * 1e-6 * CLK_TIMESTAMPS_PER_SECOND); + } break; + } + + /* + //NOTE(martin): add a random fuzz between 0 and 1 times the system fuzz + //TODO(martin): ensure that we always return a value greater than the last value + f64 fuzz = RandomU32()/(f64)(~(0UL)) * SYSTEM_FUZZ; + ts_timediff tdfuzz = TimediffFromSeconds(fuzz); + ts = TimestampAdd(ts, tdfuzz); + */ + + return(ts); +} + + +f64 mp_get_time(mp_clock_kind clock) +{ + switch(clock) + { + case MP_CLOCK_MONOTONIC: + { + //NOTE(martin): compute monotonic offset and add it to bootup timestamp + u64 noff = OSXGetMonotonicNanoseconds(); + return((f64)noff * 1e-9); + } break; + + case MP_CLOCK_UPTIME: + { + //TODO(martin): maybe we should warn that this date is inconsistent after a sleep ? + //NOTE(martin): compute uptime offset and add it to bootup timestamp + u64 noff = OSXGetUptimeNanoseconds(); + return((f64)noff * 1e-9); + } break; + + case MP_CLOCK_DATE: + { + //TODO(martin): maybe warn about precision loss ? + // could also change the epoch since we only promise to return a relative time + //NOTE(martin): get system date and convert it to seconds + timeval tv; + gettimeofday(&tv, 0); + return(((f64)tv.tv_sec + CLK_JAN_1970) + ((f64)tv.tv_usec * 1e-6)); + } break; + } +} + +void mp_sleep_nanoseconds(u64 nanoseconds) +{ + timespec rqtp; + rqtp.tv_sec = nanoseconds / 1000000000; + rqtp.tv_nsec = nanoseconds - rqtp.tv_sec * 1000000000; + nanosleep(&rqtp, 0); +} + +#undef LOG_SUBSYSTEM diff --git a/src/platform/platform_base_allocator.h b/src/platform/platform_base_allocator.h new file mode 100644 index 0000000..975879d --- /dev/null +++ b/src/platform/platform_base_allocator.h @@ -0,0 +1,28 @@ +/************************************************************//** +* +* @file: platform_base_allocator.h +* @author: Martin Fouilleul +* @date: 10/09/2021 +* @revision: +* +*****************************************************************/ +#ifndef __PLATFORM_BASE_ALLOCATOR_H_ +#define __PLATFORM_BASE_ALLOCATOR_H_ + +#include"typedefs.h" +#include"memory.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void* mem_base_reserve_mmap(void* context, u64 size); +void mem_base_release_mmap(void* context, void* ptr, u64 size); + +mem_base_allocator* mem_base_allocator_default(); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif //__PLATFORM_BASE_ALLOCATOR_H_ diff --git a/src/platform/platform_clock.h b/src/platform/platform_clock.h new file mode 100644 index 0000000..29d982d --- /dev/null +++ b/src/platform/platform_clock.h @@ -0,0 +1,34 @@ +/************************************************************//** +* +* @file: platform_clock.h +* @author: Martin Fouilleul +* @date: 07/03/2019 +* @revision: +* +*****************************************************************/ +#ifndef __PLATFORM_CLOCK_H_ +#define __PLATFORM_CLOCK_H_ + +#include"typedefs.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +typedef enum { + MP_CLOCK_MONOTONIC, // clock that increment monotonically + MP_CLOCK_UPTIME, // clock that increment monotonically during uptime + MP_CLOCK_DATE // clock that is driven by the platform time +} mp_clock_kind; + +void mp_clock_init(); // initialize the clock subsystem +u64 mp_get_timestamp(mp_clock_kind clock); +f64 mp_get_time(mp_clock_kind clock); +void mp_sleep_nanoseconds(u64 nanoseconds); // sleep for a given number of nanoseconds + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + + +#endif //__PLATFORM_CLOCK_H_ diff --git a/src/platform/platform_rng.h b/src/platform/platform_rng.h new file mode 100644 index 0000000..b4726b6 --- /dev/null +++ b/src/platform/platform_rng.h @@ -0,0 +1,18 @@ +/************************************************************//** +* +* @file: platform_rng.h +* @author: Martin Fouilleul +* @date: 06/03/2020 +* @revision: +* +*****************************************************************/ +#ifndef __PLATFORM_RANDOM_H_ +#define __PLATFORM_RANDOM_H_ + +#include"typedefs.h" + +int RandomSeedFromDevice(); +u32 RandomU32(); +u64 RandomU64(); + +#endif //__PLATFORM_RANDOM_H_ diff --git a/src/platform/platform_socket.h b/src/platform/platform_socket.h new file mode 100644 index 0000000..8585cd0 --- /dev/null +++ b/src/platform/platform_socket.h @@ -0,0 +1,161 @@ +/************************************************************//** +* +* @file: platform_socket.h +* @author: Martin Fouilleul +* @date: 22/03/2019 +* @revision: +* +*****************************************************************/ +#ifndef __PLATFORM_SOCKET_H_ +#define __PLATFORM_SOCKET_H_ + +#include // timeval +#include"typedefs.h" + +#ifdef __cplusplus +extern "C" { +#else +typedef struct timeval timeval; +#endif //__cplusplus + + +//---------------------------------------------------------------------------------- +// Errors +//---------------------------------------------------------------------------------- + +//TODO(martin): extend these error codes +const int SOCK_ERR_OK = 0, + SOCK_ERR_UNKNOWN = -1, + SOCK_ERR_ACCESS = -2, + SOCK_ERR_MEM = -3, + SOCK_ERR_INTR = -4, + SOCK_ERR_USED = -5, + SOCK_ERR_BADF = -6, + SOCK_ERR_ABORT = -7, + SOCK_ERR_NBLOCK = -8; + +int SocketGetLastError(); +const char* SocketGetLastErrorMessage(); + +//---------------------------------------------------------------------------------- +// Addresses +//---------------------------------------------------------------------------------- + +//NOTE(martin): net_ip and net_port are stored in network byte order +// host_ip and host_port are stored in host byte order +typedef uint32 net_ip; +typedef uint16 net_port; +typedef uint32 host_ip; +typedef uint16 host_port; + +typedef struct +{ + net_ip ip; + net_port port; +} socket_address; + +//NOTE(martin): these are in host byte order ! +const host_ip SOCK_IP_LOOPBACK = 0x7f000001; +const host_ip SOCK_IP_ANY = 0; +const host_port SOCK_PORT_ANY = 0; + +net_ip StringToNetIP(const char* addr); +const char* NetIPToString(net_ip ip); + +host_ip StringToHostIP(const char* addr); +const char* HostIPToString(host_ip ip); + +net_ip HostToNetIP(host_ip ip); +net_port HostToNetPort(host_port port); +host_ip NetToHostIP(net_ip ip); +host_port NetToHostPort(net_port port); + + +int SocketGetIFAddresses(int* count, net_ip* ips); +net_ip SocketGetDefaultExternalIP(); + +//---------------------------------------------------------------------------------- +// Socket API +//---------------------------------------------------------------------------------- + +typedef struct platform_socket platform_socket; + +typedef enum { SOCK_UDP, SOCK_TCP } socket_transport; + +const int SOCK_MSG_OOB = 0x01, + SOCK_MSG_PEEK = 0x02, + SOCK_MSG_DONTROUTE = 0x04, + SOCK_MSG_WAITALL = 0x40; + +platform_socket* SocketOpen(socket_transport transport); +int SocketClose(platform_socket* socket); + +int SocketBind(platform_socket* socket, socket_address* addr); +int SocketListen(platform_socket* socket, int backlog); +platform_socket* SocketAccept(platform_socket* socket, socket_address* from); + +int SocketConnect(platform_socket* socket, socket_address* addr); + +int64 SocketReceive(platform_socket* socket, void* buffer, uint64 size, int flags); +int64 SocketReceiveFrom(platform_socket* socket, void* buffer, uint64 size, int flags, socket_address* from); + +int64 SocketSend(platform_socket* socket, void* buffer, uint64 size, int flags); +int64 SocketSendTo(platform_socket* socket, void* buffer, uint64 size, int flags, socket_address* to); + +int SocketGetAddress(platform_socket* socket, socket_address* addr); + +//---------------------------------------------------------------------------------- +// Multiplexing +//---------------------------------------------------------------------------------- +const uint8 SOCK_ACTIVITY_IN = 1<<0, + SOCK_ACTIVITY_OUT = 1<<2, + SOCK_ACTIVITY_ERR = 1<<3; + +typedef struct +{ + platform_socket* sock; + uint8 watch; + uint8 set; +} socket_activity; + +int SocketSelect(uint32 count, socket_activity* set, double timeout); + +//---------------------------------------------------------------------------------- +// Socket Options +//---------------------------------------------------------------------------------- + +int SocketSetReceiveTimeout(platform_socket* socket, timeval* tv); +int SocketSetSendTimeout(platform_socket* socket, timeval* tv); +int SocketSetBroadcast(platform_socket* sock, bool enable); +int SocketSetReuseAddress(platform_socket* sock, bool enable); +int SocketSetReusePort(platform_socket* sock, bool enable); +int SocketSetReceiveTimestamping(platform_socket* socket, bool enable); + +//---------------------------------------------------------------------------------- +// Multicast +//---------------------------------------------------------------------------------- + +int SocketSetMulticastLoop(platform_socket* sock, bool enable); +int SocketJoinMulticastGroup(platform_socket* socket, host_ip group, host_ip interface); +int SocketLeaveMulticastGroup(platform_socket* socket, host_ip group, host_ip interface); + +//---------------------------------------------------------------------------------- +//Ancillary data API +//---------------------------------------------------------------------------------- +typedef struct +{ + u64 messageBufferSize; + char* messageBuffer; + + u64 controlBufferSize; + char* controlBuffer; +} socket_msg; + +int SocketReceiveMessage(platform_socket* socket, socket_msg* msg, socket_address* from); + +#ifdef __cplusplus +} // extern "C" +#endif + + +#endif //__PLATFORM_SOCKET_H_ diff --git a/src/platform/platform_thread.h b/src/platform/platform_thread.h new file mode 100644 index 0000000..0740748 --- /dev/null +++ b/src/platform/platform_thread.h @@ -0,0 +1,88 @@ +/************************************************************//** +* +* @file: platform_thread.h +* @author: Martin Fouilleul +* @date: 21/03/2019 +* @revision: +* +*****************************************************************/ +#ifndef __PLATFORM_THREAD_H_ +#define __PLATFORM_THREAD_H_ + + +#ifdef __cplusplus + #include + #define _Atomic(T) std::atomic +#else + #include +#endif + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +//--------------------------------------------------------------- +// Platform Thread API +//--------------------------------------------------------------- + +typedef struct platform_thread platform_thread; + +typedef void* (*ThreadStartFunction)(void* userPointer); + +platform_thread* ThreadCreate(ThreadStartFunction start, void* userPointer); +platform_thread* ThreadCreateWithName(ThreadStartFunction start, void* userPointer, const char* name); + +const char* ThreadGetName(platform_thread* thread); + +u64 ThreadSelfID(); +u64 ThreadUniqueID(platform_thread* thread); + +int ThreadSignal(platform_thread* thread, int sig); +void ThreadCancel(platform_thread* thread); +int ThreadJoin(platform_thread* thread, void** ret); +int ThreadDetach(platform_thread* thread); + +//--------------------------------------------------------------- +// Platform Mutex API +//--------------------------------------------------------------- + +typedef struct platform_mutex platform_mutex; + +platform_mutex* MutexCreate(); +int MutexDestroy(platform_mutex* mutex); +int MutexLock(platform_mutex* mutex); +int MutexUnlock(platform_mutex* mutex); + + +//--------------------------------------------------------------- +// Lightweight ticket mutex API +//--------------------------------------------------------------- + +typedef struct ticket_spin_mutex +{ + volatile _Atomic(u64) nextTicket; + volatile _Atomic(u64) serving; +} ticket_spin_mutex; + +void TicketSpinMutexInit(ticket_spin_mutex* mutex); +void TicketSpinMutexLock(ticket_spin_mutex* mutex); +void TicketSpinMutexUnlock(ticket_spin_mutex* mutex); + +//--------------------------------------------------------------- +// Platform condition variable API +//--------------------------------------------------------------- + +typedef struct platform_condition platform_condition; + +platform_condition* ConditionCreate(); +int ConditionDestroy(platform_condition* cond); +int ConditionWait(platform_condition* cond, platform_mutex* mutex); +int ConditionTimedWait(platform_condition* cond, platform_mutex* mutex, f64 seconds); +int ConditionSignal(platform_condition* cond); +int ConditionBroadcast(platform_condition* cond); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif //__PLATFORM_THREAD_H_ diff --git a/src/platform/posix_socket.c b/src/platform/posix_socket.c new file mode 100644 index 0000000..81cb29b --- /dev/null +++ b/src/platform/posix_socket.c @@ -0,0 +1,515 @@ +/************************************************************//** +* +* @file: posix_socket.c +* @author: Martin Fouilleul +* @date: 22/03/2019 +* @revision: +* +*****************************************************************/ + +#include // socket() +#include // socaddr_in +#include // inet_addr() +#include // getifaddrs() / freeifaddrs() +#include // close() +#include // strerror() +#include // errno +#include // malloc()/free() + +#include"platform_socket.h" +#include"debug_log.h" + +typedef struct in_addr in_addr; +typedef struct sockaddr_in sockaddr_in; +typedef struct sockaddr sockaddr; +typedef struct msghdr msghdr; +typedef struct cmsghdr cmsghdr; +typedef struct ip_mreq ip_mreq; +typedef struct iovec iovec; + +#define LOG_SUBSYSTEM "Platform" + +net_ip StringToNetIP(const char* addr) +{ + return(inet_addr(addr)); +} +const char* NetIPToString(net_ip ip) +{ + in_addr in; + in.s_addr = ip; + return(inet_ntoa(in)); +} + +host_ip StringToHostIP(const char* addr) +{ + return(NetToHostIP(StringToNetIP(addr))); +} +const char* HostIPToString(host_ip ip) +{ + return(NetIPToString(HostToNetIP(ip))); +} + +net_ip HostToNetIP(uint32 ip) +{ + return(htonl(ip)); +} +net_port HostToNetPort(uint16 port) +{ + return(htons(port)); +} +uint32 NetToHostIP(net_ip ip) +{ + return(ntohl(ip)); +} +uint16 NetToHostPort(net_port port) +{ + return(ntohs(port)); +} + +static int PlatformToSocketFlags(int flags) +{ + int sflags = 0; + if(flags & SOCK_MSG_OOB) + { + sflags |= MSG_OOB; + } + if(flags & SOCK_MSG_PEEK) + { + sflags |= MSG_PEEK; + } + if(flags & SOCK_MSG_DONTROUTE) + { + sflags |= MSG_DONTROUTE; + } + if(flags & SOCK_MSG_WAITALL) + { + sflags |= MSG_WAITALL; + } + + return(sflags); +} + +static int ErrnoToSocketError(int err) +{ + //TODO(martin): extend these error codes + switch(err) + { + case 0: return(SOCK_ERR_OK); + case EACCES: return(SOCK_ERR_ACCESS); + case ENOBUFS: + case ENOMEM: return(SOCK_ERR_MEM); + case EINTR: return(SOCK_ERR_INTR); + case EADDRINUSE: return(SOCK_ERR_USED); + case EBADF: return(SOCK_ERR_BADF); + case ECONNABORTED: return(SOCK_ERR_ABORT); + + case EWOULDBLOCK: + #if EAGAIN != EWOULDBLOCK + case EAGAIN: + #endif + return(SOCK_ERR_NBLOCK); + + default: + return(SOCK_ERR_UNKNOWN); + } +} + +int SocketGetLastError() +{ + return(ErrnoToSocketError(errno)); +} + +const char* SocketGetLastErrorMessage() +{ + return(strerror(errno)); +} + +struct platform_socket +{ + int sd; +}; + +platform_socket* SocketOpen(socket_transport transport) +{ + int sd = 0; + switch(transport) + { + case SOCK_UDP: + sd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + break; + + case SOCK_TCP: + sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + break; + } + if(!sd) + { + return(0); + } + + platform_socket* sock = (platform_socket*)malloc(sizeof(platform_socket)); + if(!sock) + { + close(sd); + return(0); + } + sock->sd = sd; + return(sock); +} + +int SocketClose(platform_socket* sock) +{ + if(!sock) + { + return(-1); + } + int res = close(sock->sd); + free(sock); + return(res); +} + +int SocketBind(platform_socket* sock, socket_address* addr) +{ + sockaddr_in saddr; + saddr.sin_addr.s_addr = addr->ip; + saddr.sin_port = addr->port; + saddr.sin_family = AF_INET; + + return(bind(sock->sd, (sockaddr*)&saddr, sizeof(saddr))); +} + +int SocketListen(platform_socket* sock, int backlog) +{ + return(listen(sock->sd, backlog)); +} +platform_socket* SocketAccept(platform_socket* sock, socket_address* from) +{ + sockaddr_in saddr; + socklen_t saddrSize = sizeof(saddr); + int sd = accept(sock->sd, (sockaddr*)&saddr, &saddrSize); + + from->ip = saddr.sin_addr.s_addr; + from->port = saddr.sin_port; + + if(sd <= 0) + { + return(0); + } + else + { + platform_socket* client = (platform_socket*)malloc(sizeof(platform_socket)); + if(!client) + { + close(sd); + return(0); + } + client->sd = sd; + return(client); + + } +} + +int SocketConnect(platform_socket* sock, socket_address* addr) +{ + sockaddr_in saddr; + saddr.sin_addr.s_addr = addr->ip; + saddr.sin_port = addr->port; + saddr.sin_family = AF_INET; + + return(connect(sock->sd, (sockaddr*)&saddr, sizeof(saddr))); +} + +int64 SocketReceive(platform_socket* sock, void* buffer, uint64 size, int flags) +{ + return(recv(sock->sd, buffer, size, PlatformToSocketFlags(flags))); +} + +int64 SocketReceiveFrom(platform_socket* sock, void* buffer, uint64 size, int flags, socket_address* from) +{ + sockaddr_in saddr; + socklen_t saddrSize = sizeof(saddr); + + int res = recvfrom(sock->sd, buffer, size, PlatformToSocketFlags(flags), (sockaddr*)&saddr, &saddrSize); + + from->ip = saddr.sin_addr.s_addr; + from->port = saddr.sin_port; + return(res); +} + +int64 SocketSend(platform_socket* sock, void* buffer, uint64 size, int flags) +{ + return(send(sock->sd, buffer, size, PlatformToSocketFlags(flags))); +} +int64 SocketSendTo(platform_socket* sock, void* buffer, uint64 size, int flags, socket_address* to) +{ + sockaddr_in saddr; + saddr.sin_addr.s_addr = to->ip; + saddr.sin_port = to->port; + saddr.sin_family = AF_INET; + + return(sendto(sock->sd, buffer, size, PlatformToSocketFlags(flags), (sockaddr*)&saddr, sizeof(saddr))); +} + + +int SocketSetReceiveTimeout(platform_socket* sock, timeval* tv) +{ + DEBUG_ASSERT(sock); + DEBUG_ASSERT(sock->sd); + return(setsockopt(sock->sd, SOL_SOCKET, SO_RCVTIMEO, tv, sizeof(timeval))); +} + +int SocketSetSendTimeout(platform_socket* sock, timeval* tv) +{ + DEBUG_ASSERT(sock); + DEBUG_ASSERT(sock->sd); + return(setsockopt(sock->sd, SOL_SOCKET, SO_SNDTIMEO, tv, sizeof(timeval))); +} + +int SocketSetReceiveTimestamping(platform_socket* socket, bool enable) +{ + int opt = enable ? 1 : 0; + socklen_t len = sizeof(int); + return(setsockopt(socket->sd, SOL_SOCKET, SO_TIMESTAMP, &enable, len)); +} + +int SocketSetBroadcast(platform_socket* sock, bool enable) +{ + DEBUG_ASSERT(sock); + DEBUG_ASSERT(sock->sd); + + int opt = enable ? 1 : 0; + return(setsockopt(sock->sd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(int))); +} + + +int SocketSelect(uint32 count, socket_activity* set, double timeout) +{ + fd_set fdInSet; + fd_set fdOutSet; + fd_set fdErrSet; + FD_ZERO(&fdInSet); + FD_ZERO(&fdOutSet); + FD_ZERO(&fdErrSet); + + int maxSd = -1; + for(int i=0; isock) + { + item->set = 0; + if(item->watch & SOCK_ACTIVITY_IN) + { + FD_SET(item->sock->sd, &fdInSet); + } + if(item->watch & SOCK_ACTIVITY_OUT) + { + FD_SET(item->sock->sd, &fdOutSet); + } + if(item->watch & SOCK_ACTIVITY_ERR) + { + FD_SET(item->sock->sd, &fdErrSet); + } + + if(item->watch && (item->sock->sd > maxSd)) + { + maxSd = item->sock->sd; + } + } + } + + if(maxSd <= 0) + { + return(0); + } + timeval tv; + tv.tv_sec = (time_t)timeout; + tv.tv_usec = (suseconds_t)((timeout - tv.tv_sec)*1000000); + + timeval* ptv = timeout >= 0 ? &tv : 0; + + int activity = select(maxSd+1, &fdInSet, &fdOutSet, &fdErrSet, ptv); + if(activity < 0) + { + return(-1); + } + + int processed = 0; + for(int i=0; i= activity) + { + break; + } + socket_activity* item = &(set[i]); + if(item->sock) + { + if(FD_ISSET(item->sock->sd, &fdInSet)) + { + item->set |= SOCK_ACTIVITY_IN; + } + if(FD_ISSET(item->sock->sd, &fdOutSet)) + { + item->set |= SOCK_ACTIVITY_OUT; + } + if(FD_ISSET(item->sock->sd, &fdErrSet)) + { + item->set |= SOCK_ACTIVITY_ERR; + } + if(item->set) + { + processed++; + } + } + } + return(activity); +} + + +int SocketGetAddress(platform_socket* sock, socket_address* addr) +{ + sockaddr_in saddr; + socklen_t sockLen = sizeof(saddr); + if(getsockname(sock->sd, (sockaddr*)&saddr, &sockLen)) + { + return(-1); + } + addr->ip = saddr.sin_addr.s_addr; + addr->port = saddr.sin_port; + + return(0); +} + + +int SocketGetIFAddresses(int* count, net_ip* ips) +{ + struct ifaddrs* ifaList = 0; + if(getifaddrs(&ifaList)) + { + return(-1); + } + + int maxCount = *count; + int i = 0; + + for(struct ifaddrs* ifa = ifaList; ifa != 0 ; ifa = ifa->ifa_next) + { + if(i >= maxCount) + { + freeifaddrs(ifaList); + *count = i; + return(-1); + } + if(ifa->ifa_addr->sa_family == AF_INET) + { + struct in_addr in = ((sockaddr_in*)ifa->ifa_addr)->sin_addr; + ips[i] = in.s_addr; + i++; + } + } + freeifaddrs(ifaList); + *count = i; + return(0); +} + + +int SocketSetReuseAddress(platform_socket* sock, bool enable) +{ + int reuse = enable ? 1 : 0; + return(setsockopt(sock->sd, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse))); +} + +int SocketSetReusePort(platform_socket* sock, bool enable) +{ + int reuse = enable ? 1 : 0; + return(setsockopt(sock->sd, SOL_SOCKET, SO_REUSEPORT, (char*)&reuse, sizeof(reuse))); +} + +int SocketSetMulticastLoop(platform_socket* sock, bool enable) +{ + int on = enable ? 1 : 0; + return(setsockopt(sock->sd, IPPROTO_IP, IP_MULTICAST_LOOP, (char*)&on, sizeof(on))); +} + +int SocketJoinMulticastGroup(platform_socket* sock, host_ip group, host_ip interface) +{ + ip_mreq mreq; + mreq.imr_multiaddr.s_addr = HostToNetIP(group); + mreq.imr_interface.s_addr = HostToNetIP(interface); + return(setsockopt(sock->sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq))); +} + +int SocketLeaveMulticastGroup(platform_socket* sock, host_ip group, host_ip interface) +{ + ip_mreq mreq; + mreq.imr_multiaddr.s_addr = HostToNetIP(group); + mreq.imr_interface.s_addr = HostToNetIP(interface); + return(setsockopt(sock->sd, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char*)&mreq, sizeof(mreq))); +} + + +net_ip SocketGetDefaultExternalIP() +{ + //NOTE(martin): get the default local ip. This is a dumb way to do this + // 'cause I can't be bothered to think of a better way right now :( + + socket_address addr = {.ip = StringToNetIP("8.8.8.8"), + .port = HostToNetPort(5001)}; + + platform_socket* sock = SocketOpen(SOCK_UDP); + if(!sock) + { + LOG_ERROR("can't create socket"); + return(0); + } + + if(SocketConnect(sock, &addr) != 0) + { + LOG_ERROR("can't connect socket: %s\n", SocketGetLastErrorMessage()); + LOG_WARNING("try loopback interface\n"); + + addr.ip = HostToNetIP(SOCK_IP_LOOPBACK); + if(SocketConnect(sock, &addr) != 0) + { + LOG_ERROR("can't connect socket: %s\n", SocketGetLastErrorMessage()); + SocketClose(sock); + return(0); + } + } + SocketGetAddress(sock, &addr); + SocketClose(sock); + return(addr.ip); +} + +int SocketReceiveMessage(platform_socket* socket, socket_msg* msg, socket_address* from) +{ + sockaddr_in saddr; + + iovec iov; + iov.iov_base = msg->messageBuffer; + iov.iov_len = msg->messageBufferSize; + + msghdr hdr; + hdr.msg_name = &saddr; + hdr.msg_namelen = sizeof(saddr); + hdr.msg_iov = &iov; + hdr.msg_iovlen = 1; + hdr.msg_control = msg->controlBuffer; + hdr.msg_controllen = msg->controlBufferSize; + + int size = recvmsg(socket->sd, &hdr, 0); + if(size <= 0) + { + return(size); + } + + from->ip = saddr.sin_addr.s_addr; + from->port = saddr.sin_port; + + msg->controlBufferSize = hdr.msg_controllen; + msg->messageBufferSize = iov.iov_len; + + return(size); +} + +#undef LOG_SUBSYSTEM diff --git a/src/platform/posix_thread.c b/src/platform/posix_thread.c new file mode 100644 index 0000000..166f514 --- /dev/null +++ b/src/platform/posix_thread.c @@ -0,0 +1,241 @@ +/************************************************************//** +* +* @file: posix_thread.c +* @author: Martin Fouilleul +* @date: 21/03/2019 +* @revision: +* +*****************************************************************/ +#include +#include +#include //needed for pthread_kill() on linux +#include +#include + +#include"platform_thread.h" + +const u32 PLATFORM_THREAD_NAME_MAX_SIZE = 64; // including null terminator + +struct platform_thread +{ + bool valid; + pthread_t pthread; + ThreadStartFunction start; + void* userPointer; + char name[PLATFORM_THREAD_NAME_MAX_SIZE]; +}; + +void* platform_thread_bootstrap(void* data) +{ + platform_thread* thread = (platform_thread*)data; + if(strlen(thread->name)) + { + pthread_setname_np(thread->name); + } + return(thread->start(thread->userPointer)); +} + +platform_thread* ThreadCreateWithName(ThreadStartFunction start, void* userPointer, const char* name) +{ + platform_thread* thread = (platform_thread*)malloc(sizeof(platform_thread)); + if(!thread) + { + return(0); + } + + if(name) + { + char* end = stpncpy(thread->name, name, PLATFORM_THREAD_NAME_MAX_SIZE-1); + *end = '\0'; + } + else + { + thread->name[0] = '\0'; + } + thread->start = start; + thread->userPointer = userPointer; + + if(pthread_create(&thread->pthread, 0, platform_thread_bootstrap, thread) != 0) + { + free(thread); + return(0); + } + else + { + thread->valid = true; + return(thread); + } +} + +platform_thread* ThreadCreate(ThreadStartFunction start, void* userPointer) +{ + return(ThreadCreateWithName(start, userPointer, 0)); +} + +void ThreadCancel(platform_thread* thread) +{ + pthread_cancel(thread->pthread); +} + +const char* ThreadGetName(platform_thread* thread) +{ + return(thread->name); +} + + +u64 ThreadUniqueID(platform_thread* thread) +{ + u64 id; + pthread_threadid_np(thread->pthread, &id); + return(id); +} + +u64 ThreadSelfID() +{ + pthread_t thread = pthread_self(); + u64 id; + pthread_threadid_np(thread, &id); + return(id); +} + +int ThreadSignal(platform_thread* thread, int sig) +{ + return(pthread_kill(thread->pthread, sig)); +} + +int ThreadJoin(platform_thread* thread, void** ret) +{ + if(pthread_join(thread->pthread, ret)) + { + return(-1); + } + free(thread); + return(0); +} + +int ThreadDetach(platform_thread* thread) +{ + if(pthread_detach(thread->pthread)) + { + return(-1); + } + free(thread); + return(0); +} + + +struct platform_mutex +{ + pthread_mutex_t pmutex; +}; + +platform_mutex* MutexCreate() +{ + platform_mutex* mutex = (platform_mutex*)malloc(sizeof(platform_mutex)); + if(!mutex) + { + return(0); + } + if(pthread_mutex_init(&mutex->pmutex, 0) != 0) + { + free(mutex); + return(0); + } + return(mutex); +} +int MutexDestroy(platform_mutex* mutex) +{ + if(pthread_mutex_destroy(&mutex->pmutex) != 0) + { + return(-1); + } + free(mutex); + return(0); +} + +int MutexLock(platform_mutex* mutex) +{ + return(pthread_mutex_lock(&mutex->pmutex)); +} + +int MutexUnlock(platform_mutex* mutex) +{ + return(pthread_mutex_unlock(&mutex->pmutex)); +} + +void TicketSpinMutexInit(ticket_spin_mutex* mutex) +{ + mutex->nextTicket = 0; + mutex->serving = 0; +} + +void TicketSpinMutexLock(ticket_spin_mutex* mutex) +{ + u64 ticket = atomic_fetch_add(&mutex->nextTicket, 1ULL); + while(ticket != mutex->serving); //spin +} + +void TicketSpinMutexUnlock(ticket_spin_mutex* mutex) +{ + atomic_fetch_add(&mutex->serving, 1ULL); +} + +struct platform_condition +{ + pthread_cond_t pcond; +}; + +platform_condition* ConditionCreate() +{ + platform_condition* cond = (platform_condition*)malloc(sizeof(platform_condition)); + if(!cond) + { + return(0); + } + if(pthread_cond_init(&cond->pcond, 0) != 0) + { + free(cond); + return(0); + } + return(cond); +} +int ConditionDestroy(platform_condition* cond) +{ + if(pthread_cond_destroy(&cond->pcond) != 0) + { + return(-1); + } + free(cond); + return(0); +} +int ConditionWait(platform_condition* cond, platform_mutex* mutex) +{ + return(pthread_cond_wait(&cond->pcond, &mutex->pmutex)); +} + +int ConditionTimedWait(platform_condition* cond, platform_mutex* mutex, f64 seconds) +{ + struct timeval tv; + gettimeofday(&tv, 0); + + i64 iSeconds = (i64)seconds; + f64 fracSeconds = seconds - (f64)iSeconds; + + struct timespec ts; + ts.tv_sec = tv.tv_sec + iSeconds; + ts.tv_nsec = tv.tv_usec * 1000 + (i32)(fracSeconds*1e9); + ts.tv_sec += ts.tv_nsec / 1000000000; + ts.tv_nsec = ts.tv_nsec % 1000000000; + + return(pthread_cond_timedwait(&cond->pcond, &mutex->pmutex, &ts)); +} + +int ConditionSignal(platform_condition* cond) +{ + return(pthread_cond_signal(&cond->pcond)); +} + +int ConditionBroadcast(platform_condition* cond) +{ + return(pthread_cond_broadcast(&cond->pcond)); +} diff --git a/src/platform/unix_base_allocator.c b/src/platform/unix_base_allocator.c new file mode 100644 index 0000000..839dae7 --- /dev/null +++ b/src/platform/unix_base_allocator.c @@ -0,0 +1,38 @@ +/************************************************************//** +* +* @file: unix_base_allocator.c +* @author: Martin Fouilleul +* @date: 10/09/2021 +* @revision: +* +*****************************************************************/ +#include +#include"platform_base_allocator.h" + +/*NOTE(martin): + Linux and MacOS don't make a distinction between reserved and committed memory, contrary to Windows +*/ +void mem_base_nop(void* context, void* ptr, u64 size) {} + +void* mem_base_reserve_mmap(void* context, u64 size) +{ + return(mmap(0, size, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, 0, 0)); +} + +void mem_base_release_mmap(void* context, void* ptr, u64 size) +{ + munmap(ptr, size); +} + +mem_base_allocator* mem_base_allocator_default() +{ + static mem_base_allocator base = {}; + if(base.reserve == 0) + { + base.reserve = mem_base_reserve_mmap; + base.commit = mem_base_nop; + base.decommit = mem_base_nop; + base.release = mem_base_release_mmap; + } + return(&base); +} diff --git a/src/platform/unix_rng.c b/src/platform/unix_rng.c new file mode 100644 index 0000000..ba06283 --- /dev/null +++ b/src/platform/unix_rng.c @@ -0,0 +1,60 @@ +/************************************************************//** +* +* @file: unix_rng.c +* @author: Martin Fouilleul +* @date: 06/03/2020 +* @revision: +* +*****************************************************************/ + +#include +#include + +#include"debug_log.h" +#include"typedefs.h" + +#define LOG_SUBSYSTEM "Platform" + +int RandomSeedFromDevice() +{ + FILE* urandom = fopen("/dev/urandom", "r"); + if(!urandom) + { + LOG_ERROR("can't open /dev/urandom\n"); + return(-1); + } + + union + { + u32 u; + char buff[4]; + } seed; + + int size = fread(seed.buff, 1, 4, urandom); + if(size != 4) + { + LOG_ERROR("couldn't read from /dev/urandom\n"); + return(-1); + } + + fclose(urandom); + srandom(seed.u); + return(0); +} + +u32 RandomU32() +{ + u32 u1 = (u32)random(); + u32 u2 = (u32)random(); + return((u1<<1) | (u2 & 0x01)); +} + +u64 RandomU64() +{ + u64 u1 = (u64)random(); + u64 u2 = (u64)random(); + u64 u3 = (u64)random(); + return((u1<<33) | (u2<<2) | (u3 & 0x03)); +} + +#undef LOG_SUBSYSTEM diff --git a/src/stb_image.h b/src/stb_image.h new file mode 100644 index 0000000..81b2fa2 --- /dev/null +++ b/src/stb_image.h @@ -0,0 +1,7440 @@ +/* stb_image - v2.19 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + Full documentation under "DOCUMENTATION" below. +LICENSE + See end of file for license information. +RECENT REVISION HISTORY: + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings + 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + See end of file for full revision history. + ============================ Contributors ========================= + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + Arseny Kapoulkine + John-Mark Allen + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Phil Jordan + Dave Moore Roy Eltham Hayaki Saito Nathan Reed + Won Chun Luke Graham Johan Duparc Nick Verigakis + the Horde3D community Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Laurent Gomila Cort Stratton Sergio Gonzalez github:snagar + Aruelien Pocheville Thibault Reuille Cass Everitt github:Zelex + Ryamond Barbiero Paul Du Bois Engin Manap github:grim210 + Aldo Culquicondor Philipp Wiesemann Dale Weiler github:sammyhw + Oriol Ferrer Mesia Josh Tobin Matthew Gregan github:phprus + Julian Raschke Gregory Mullen Baldur Karlsson github:poppolopoppo + Christian Floisand Kevin Schmidt github:darealshinji + Blazej Dariusz Roszkowski github:Michaelangel007 +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'desired_channels' if desired_channels is non-zero, or +// *channels_in_file otherwise. If desired_channels is non-zero, +// *channels_in_file has the number of components that _would_ have been +// output otherwise. E.g. if you set desired_channels to 4, you will always +// get RGBA output, but you can check *channels_in_file to see if it's trivially +// opaque because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *channels_in_file will be unchanged. The function +// stbi_failure_reason() can be queried for an extremely brief, end-user +// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS +// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy to use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// make more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image now supports loading HDR images in general, and currently +// the Radiance .HDR file format, although the support is provided +// generically. You can still load any file through the existing interface; +// if you attempt to load an HDR file, it will be automatically remapped to +// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// By default we convert iphone-formatted PNGs back to RGB, even though +// they are internally encoded differently. You can disable this conversion +// by by calling stbi_convert_iphone_png_to_rgb(0), in which case +// you will always just get the native iphone "format" through (which +// is BGR stored in RGB). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// + + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// NOT THREADSAFE +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +// assume GCC or Clang on ARM targets +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + fseek((FILE*) user, n, SEEK_CUR); +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +// this is not threadsafe +static const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load = flag_true_if_should_flip; +} + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + if (ri.bits_per_channel != 8) { + STBI_ASSERT(ri.bits_per_channel == 16); + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + if (ri.bits_per_channel != 16) { + STBI_ASSERT(ri.bits_per_channel == 8); + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} + +#if !defined(STBI_NO_HDR) || !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} + +static void stbi__skip(stbi__context *s, int n) +{ + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} + +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} + +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} + +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + return z + (stbi__get16le(s) << 16); +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + + +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0], dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; + default: STBI_ASSERT(0); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} + +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0], dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; + default: STBI_ASSERT(0); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (stbi_uc) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + + sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB + k = stbi_lrot(j->code_buffer, n); + STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & ~sgn); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + diff = t ? stbi__extend_receive(j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc << j->succ_low); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) << shift); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) << shift); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + if (x == 255) { + j->marker = stbi__get8(j->s); + break; + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + } else { + if (!stbi__process_marker(j, m)) return 0; + } + m = stbi__get_marker(j); + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4]; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[288]; + stbi__uint16 value[288]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + if (z->zbuffer >= z->zbuffer_end) return 0; + return *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s == 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + STBI_ASSERT(z->size[b] == s); + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) stbi__fill_bits(a); + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (int) (z->zout - z->zout_start); + limit = old_limit = (int) (z->zout_end - z->zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) + c = stbi__zreceive(a,3)+3; + else { + STBI_ASSERT(c == 18); + c = stbi__zreceive(a,7)+11; + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + STBI_ASSERT(a->num_bits == 0); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[288] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior; + int filter = *raw++; + + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + STBI_ASSERT(img_width_bytes <= x); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else if (depth == 16) { + if (img_n != out_n) { + cur[filter_bytes] = 255; // first pixel top byte + cur[filter_bytes+1] = 255; // first pixel bottom byte + } + raw += filter_bytes; + cur += output_bytes; + prior += output_bytes; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*filter_bytes; + #define STBI__CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; + } + #undef STBI__CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define STBI__CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ + for (k=0; k < filter_bytes; ++k) + switch (filter) { + STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; + } + #undef STBI__CASE + + // the loop above sets the high byte of the pixels' alpha, but for + // 16 bit png files we also need the low byte set. we'll do that here. + if (depth == 16) { + cur = a->out + stride*j; // start at the beginning of the row again + for (i=0; i < x; ++i,cur+=output_bytes) { + cur[filter_bytes+1] = 255; + } + } + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } else if (depth == 16) { + // force the image data from big-endian to platform-native. + // this is done in a separate pass due to the decoding relying + // on the data being untouched, but could probably be done + // per-line during decode if care is taken. + stbi_uc *cur = a->out; + stbi__uint16 *cur16 = (stbi__uint16*)cur; + + for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { + *cur16 = (cur[0] << 8) | cur[1]; + } + } + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load = 0; +static int stbi__de_iphone_flag = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag = flag_true_if_should_convert; +} + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth < 8) + ri->bits_per_channel = 8; + else + ri->bits_per_channel = p->depth; + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) n += 16, z >>= 16; + if (z >= 0x00100) n += 8, z >>= 8; + if (z >= 0x00010) n += 4, z >>= 4; + if (z >= 0x00004) n += 2, z >>= 2; + if (z >= 0x00002) n += 1, z >>= 1; + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v >= 0 && v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; +} stbi__bmp_data; + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - 14 - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - 14 - info.hsz) >> 2; + } + + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - 14 - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i], p1[i] = p2[i], p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h); + g->background = (stbi_uc *) stbi__malloc(4 * g->w * g->h); + g->history = (stbi_uc *) stbi__malloc(g->w * g->h); + if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "tranparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to teh color that was there the previous frame. + memset( g->out, 0x00, 4 * g->w * g->h ); + memset( g->background, 0x00, 4 * g->w * g->h ); // state of the background (starts transparent) + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispoase of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (o == NULL) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + out = (stbi_uc*) STBI_REALLOC( out, layers * stride ); + if (delays) { + *delays = (int*) STBI_REALLOC( *delays, sizeof(int) * layers ); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + stbi__rewind( s ); + if (p == NULL) + return 0; + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) *comp = info.ma ? 4 : 3; + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + (void) stbi__get32be(s); + (void) stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) +// Does not support 16-bit-per-channel + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) + return 0; + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad3sizes_valid(s->img_n, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad3(s->img_n, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + stbi__getn(s, out, s->img_n * s->img_x * s->img_y); + + if (req_comp && req_comp != s->img_n) { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + + if (maxv > 255) + return stbi__err("max value > 255", "PPM image not 8-bit"); + else + return 1; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + return 0; +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/src/stb_truetype.h b/src/stb_truetype.h new file mode 100644 index 0000000..62595a1 --- /dev/null +++ b/src/stb_truetype.h @@ -0,0 +1,5011 @@ +// stb_truetype.h - v1.24 - public domain +// authored from 2009-2020 by Sean Barrett / RAD Game Tools +// +// ======================================================================= +// +// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES +// +// This library does no range checking of the offsets found in the file, +// meaning an attacker can use it to read arbitrary memory. +// +// ======================================================================= +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe +// Cass Everitt Martins Mozeiko github:aloucks +// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam +// Brian Hook Omar Cornut github:vassvik +// Walter van Niftrik Ryan Griege +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. Brian Costabile +// Ken Voskuil (kaesve) +// +// VERSION HISTORY +// +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +// Query the font vertical metrics without having to create a font first. + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. +// Returns 0 if the character codepoint is not defined in the font. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + +typedef struct stbtt_kerningentry +{ + int glyph1; // use stbtt_FindGlyphIndex + int glyph2; + int advance; +} stbtt_kerningentry; + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); +// Retrieves a complete list of all of the kerning pairs provided by the font +// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. +// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of contours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); +// fills svg with the character's SVG data. +// returns data size or 0 if SVG not found. + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +// since most people won't use this, find this table the first time it's needed +static int stbtt__get_svg(stbtt_fontinfo *info) +{ + stbtt_uint32 t; + if (info->svg < 0) { + t = stbtt__find_table(info->data, info->fontstart, "SVG "); + if (t) { + stbtt_uint32 offset = ttULONG(info->data + t + 2); + info->svg = t + offset; + } else { + info->svg = 0; + } + } + return info->svg; +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + info->svg = -1; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours < 0) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) +{ + stbtt_uint8 *data = info->data + info->kern; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + return ttUSHORT(data+10); +} + +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) +{ + stbtt_uint8 *data = info->data + info->kern; + int k, length; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + length = ttUSHORT(data+10); + if (table_length < length) + length = table_length; + + for (k = 0; k < length; k++) + { + table[k].glyph1 = ttUSHORT(data+18+(k*6)); + table[k].glyph2 = ttUSHORT(data+20+(k*6)); + table[k].advance = ttSHORT(data+22+(k*6)); + } + + return length; +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch(coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + } break; + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch(classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + + classDefTable = classDef1ValueArray + 2 * glyphCount; + } break; + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + + classDefTable = classRangeRecords + 6 * classRangeCount; + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } break; + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + STBTT_assert(glyph1class < class1Count); + STBTT_assert(glyph2class < class2Count); + + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { + stbtt_uint8 *class1Records = table + 16; + stbtt_uint8 *class2Records = class1Records + 2 * (glyph1class * class2Count); + stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + break; + }; + } + } + break; + }; + + default: + // TODO: Implement other stuff. + break; + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) +{ + int i; + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); + + int numEntries = ttUSHORT(svg_doc_list); + stbtt_uint8 *svg_docs = svg_doc_list + 2; + + for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) + return svg_doc; + } + return 0; +} + +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) +{ + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc; + + if (info->svg == 0) + return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if (svg_doc != NULL) { + *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); + return ttULONG(svg_doc + 8); + } else { + return 0; + } +} + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) +{ + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + int missing_glyph_added = 0; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, missing_glyph = -1, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + orig[0] = x; + orig[1] = y; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + c*x^2 + b*x + a = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + if (scale == 0) return NULL; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve + float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (verts[i].type == STBTT_vline) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3],px,py,t,it; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/src/ui.c b/src/ui.c new file mode 100644 index 0000000..c717d51 --- /dev/null +++ b/src/ui.c @@ -0,0 +1,1332 @@ +/************************************************************//** +* +* @file: ui.c +* @author: Martin Fouilleul +* @date: 08/08/2022 +* @revision: +* +*****************************************************************/ +#include"memory.h" +#include"hash.h" +#include"platform_clock.h" +#include"ui.h" + +#define LOG_SUBSYSTEM "UI" + +//----------------------------------------------------------------------------- +// context +//----------------------------------------------------------------------------- + +const u32 UI_MAX_INPUT_CHAR_PER_FRAME = 64; + +typedef struct ui_input_text +{ + u8 count; + utf32 codePoints[UI_MAX_INPUT_CHAR_PER_FRAME]; + +} ui_input_text; + +typedef struct ui_stack_elt ui_stack_elt; +struct ui_stack_elt +{ + ui_stack_elt* parent; + union + { + ui_box* box; + ui_size size; + mp_rect clip; + ui_style style; + }; +}; + +const u64 UI_BOX_MAP_BUCKET_COUNT = 1024; + +typedef struct ui_context +{ + bool init; + + f32 width; + f32 height; + + u64 frameCounter; + f64 frameTime; + f64 lastFrameDuration; + + mem_arena frameArena; + mem_pool boxPool; + list_info boxMap[UI_BOX_MAP_BUCKET_COUNT]; + + ui_box* root; + ui_box* overlay; + ui_stack_elt* boxStack; + ui_stack_elt* sizeStack[UI_AXIS_COUNT]; + ui_stack_elt* styleStack[UI_STYLE_SELECTOR_COUNT]; + ui_stack_elt* clipStack; + + u32 z; + ui_box* hovered; + +} ui_context; + +__thread ui_context __uiContext = {0}; + +ui_context* ui_get_context() +{ + return(&__uiContext); +} + +//----------------------------------------------------------------------------- +// stacks +//----------------------------------------------------------------------------- +ui_stack_elt* ui_stack_push(ui_context* ui, ui_stack_elt** stack) +{ + ui_stack_elt* elt = mem_arena_alloc_type(&ui->frameArena, ui_stack_elt); + memset(elt, 0, sizeof(ui_stack_elt)); + elt->parent = *stack; + *stack = elt; + return(elt); +} + +void ui_stack_pop(ui_stack_elt** stack) +{ + if(*stack) + { + *stack = (*stack)->parent; + } + else + { + LOG_ERROR("ui stack underflow\n"); + } +} + +void ui_size_push(ui_axis axis, ui_size_kind kind, f32 value, f32 strictness) +{ + ui_context* ui = ui_get_context(); + ui_stack_elt* elt = ui_stack_push(ui, &ui->sizeStack[axis]); + elt->size = (ui_size){.kind = kind, .value = value, .strictness = strictness}; +} + +void ui_size_pop(ui_axis axis) +{ + ui_context* ui = ui_get_context(); + ui_stack_pop(&ui->sizeStack[axis]); +} + +ui_size ui_size_top(ui_axis axis) +{ + ui_context* ui = ui_get_context(); + ui_size size = {0}; + if(ui->sizeStack[axis]) + { + size = ui->sizeStack[axis]->size; + } + return(size); +} + +ui_stack_elt** ui_style_stack_select(ui_context* ui, ui_style_selector selector) +{ + DEBUG_ASSERT(selector < UI_STYLE_SELECTOR_COUNT); + ui_stack_elt** stack = &ui->styleStack[selector]; + return(stack); +} + +void ui_style_push(ui_style_selector selector, ui_style style) +{ + ui_context* ui = ui_get_context(); + ui_stack_elt** stack = ui_style_stack_select(ui, selector); + ui_stack_elt* elt = ui_stack_push(ui, stack); + elt->style = style; +} + +void ui_style_pop(ui_style_selector selector) +{ + ui_context* ui = ui_get_context(); + ui_stack_elt** stack = ui_style_stack_select(ui, selector); + ui_stack_pop(stack); +} + +ui_style* ui_style_top(ui_style_selector selector) +{ + ui_context* ui = ui_get_context(); + ui_stack_elt* stack = *ui_style_stack_select(ui, selector); + ui_style* style = 0; + if(stack) + { + style = &stack->style; + } + return(style); +} + +mp_rect ui_intersect_rects(mp_rect lhs, mp_rect rhs) +{ + //NOTE(martin): intersect with current clip + f32 x0 = maximum(lhs.x, rhs.x); + f32 y0 = maximum(lhs.y, rhs.y); + f32 x1 = minimum(lhs.x + lhs.w, rhs.x + rhs.w); + f32 y1 = minimum(lhs.y + lhs.h, rhs.y + rhs.h); + mp_rect r = {x0, y0, maximum(0, x1-x0), maximum(0, y1-y0)}; + return(r); +} + +mp_rect ui_clip_top() +{ + mp_rect r = {-FLT_MAX/2, -FLT_MAX/2, FLT_MAX, FLT_MAX}; + ui_context* ui = ui_get_context(); + ui_stack_elt* elt = ui->clipStack; + if(elt) + { + r = elt->clip; + } + return(r); +} + +void ui_clip_push(mp_rect clip) +{ + ui_context* ui = ui_get_context(); + mp_rect current = ui_clip_top(); + ui_stack_elt* elt = ui_stack_push(ui, &ui->clipStack); + elt->clip = ui_intersect_rects(current, clip); +} + +void ui_clip_pop() +{ + ui_context* ui = ui_get_context(); + ui_stack_pop(&ui->clipStack); +} + +ui_box* ui_box_top() +{ + ui_context* ui = ui_get_context(); + ui_stack_elt* elt = ui->boxStack; + ui_box* box = elt ? elt->box : 0; + return(box); +} + +void ui_box_push(ui_box* box) +{ + ui_context* ui = ui_get_context(); + ui_stack_elt* elt = ui_stack_push(ui, &ui->boxStack); + elt->box = box; + if(box->flags & UI_FLAG_CLIP) + { + ui_clip_push(box->rect); + } +} + +void ui_box_pop() +{ + ui_context* ui = ui_get_context(); + ui_box* box = ui_box_top(); + if(box->flags & UI_FLAG_CLIP) + { + ui_clip_pop(); + } + + ui_stack_pop(&ui->boxStack); +} + +//----------------------------------------------------------------------------- +// key hashing and caching +//----------------------------------------------------------------------------- +ui_key ui_key_from_string(str8 string) +{ + ui_context* ui = ui_get_context(); + u64 seed = 0; + ui_box* parent = ui_box_top(); + if(parent) + { + seed = parent->key.hash; + } + + ui_key key = {}; + key.hash = mp_hash_aes_string_seed(string, seed); + return(key); +} + +bool ui_key_equal(ui_key a, ui_key b) +{ + return(a.hash == b.hash); +} + +void ui_box_cache(ui_context* ui, ui_box* box) +{ + u64 index = box->key.hash & (UI_BOX_MAP_BUCKET_COUNT-1); + ListAppend(&(ui->boxMap[index]), &box->bucketElt); +} + +ui_box* ui_box_lookup(ui_context* ui, ui_key key) +{ + u64 index = key.hash & (UI_BOX_MAP_BUCKET_COUNT-1); + + for_each_in_list(&ui->boxMap[index], box, ui_box, bucketElt) + { + if(ui_key_equal(key, box->key)) + { + return(box); + } + } + return(0); +} + +//----------------------------------------------------------------------------- +// ui boxes +//----------------------------------------------------------------------------- + +bool ui_rect_hit(mp_rect r, vec2 p) +{ + return( (p.x > r.x) + &&(p.x < r.x + r.w) + &&(p.y > r.y) + &&(p.y < r.y + r.h)); +} + +bool ui_box_hovering(ui_box* box, vec2 p) +{ + ui_context* ui = ui_get_context(); + + mp_rect clip = ui_clip_top(); + mp_rect rect = ui_intersect_rects(clip, box->rect); + bool hit = ui_rect_hit(rect, p); + bool result = hit && (!ui->hovered || box->z >= ui->hovered->z); + return(result); +} + +vec2 ui_mouse_position() +{ + ui_context* ui = ui_get_context(); + + vec2 mousePos = mp_input_mouse_position(); + mousePos.y = ui->height - mousePos.y; + return(mousePos); +} + +vec2 ui_mouse_delta() +{ + ui_context* ui = ui_get_context(); + vec2 delta = mp_input_mouse_delta(); + delta.y *= -1.; + return(delta); +} + +vec2 ui_mouse_wheel() +{ + ui_context* ui = ui_get_context(); + vec2 delta = mp_input_mouse_wheel(); + delta.y *= -1.; + return(delta); +} + +void ui_box_compute_signals(ui_context* ui, ui_box* box) +{ + ui_sig* sig = mem_arena_alloc_type(&ui->frameArena, ui_sig); + memset(sig, 0, sizeof(ui_sig)); + + if(!box->closed && !box->parentClosed) + { + vec2 mousePos = ui_mouse_position(); + + sig->hovering = ui_box_hovering(box, mousePos); + + if(box->flags & UI_FLAG_CLICKABLE) + { + if(sig->hovering) + { + sig->pressed = mp_input_mouse_pressed(MP_MOUSE_LEFT); + if(sig->pressed) + { + box->dragging = true; + } + + sig->clicked = mp_input_mouse_clicked(MP_MOUSE_LEFT); + sig->doubleClicked = mp_input_mouse_clicked(MP_MOUSE_LEFT); + } + + sig->released = mp_input_mouse_released(MP_MOUSE_LEFT); + if(sig->released) + { + if(box->dragging && sig->hovering) + { + sig->triggered = true; + } + } + + if(!mp_input_mouse_down(MP_MOUSE_LEFT)) + { + box->dragging = false; + } + + sig->dragging = box->dragging; + } + + sig->mouse = (vec2){mousePos.x - box->rect.x, mousePos.y - box->rect.y}; + sig->delta = ui_mouse_delta(); + sig->wheel = ui_mouse_wheel(); + } + box->sig = sig; +} + +ui_box* ui_box_make_str8(str8 string, ui_flags flags) +{ + ui_context* ui = ui_get_context(); + + ui_key key = ui_key_from_string(string); + ui_box* box = ui_box_lookup(ui, key); + + if(!box) + { + box = mem_pool_alloc_type(&ui->boxPool, ui_box); + memset(box, 0, sizeof(ui_box)); + + box->key = key; + ui_box_cache(ui, box); + } + + //NOTE: setup hierarchy + ListInit(&box->children); + box->parent = ui_box_top(); + if(box->parent) + { + ListAppend(&box->parent->children, &box->listElt); + box->parentClosed = box->parent->closed || box->parent->parentClosed; + } + + //NOTE: setup per-frame state + box->frameCounter = ui->frameCounter; + box->string = str8_push_copy(&ui->frameArena, string); + box->flags = flags; + + box->floating[UI_AXIS_X] = false; + box->floating[UI_AXIS_Y] = false; + + box->desiredSize[UI_AXIS_X] = ui_size_top(UI_AXIS_X); + box->desiredSize[UI_AXIS_Y] = ui_size_top(UI_AXIS_Y); + box->layout = box->parent ? box->parent->layout : (ui_layout){0}; + + for(int i=0; istyles[i] = ui_style_top(i); + } + box->styleSelector = UI_STYLE_NORMAL; + + //NOTE: compute input signals + ui_box_compute_signals(ui, box); + + return(box); +} + +ui_box* ui_box_make(const char* cstring, ui_flags flags) +{ + str8 string = str8_from_cstring((char*)cstring); + return(ui_box_make_str8(string, flags)); +} + +ui_box* ui_box_begin_str8(str8 string, ui_flags flags) +{ + ui_context* ui = ui_get_context(); + ui_box* box = ui_box_make_str8(string, flags); + ui_box_push(box); + return(box); +} + +ui_box* ui_box_begin(const char* cstring, ui_flags flags) +{ + str8 string = str8_from_cstring((char*)cstring); + return(ui_box_begin_str8(string, flags)); +} + +ui_box* ui_box_end() +{ + ui_context* ui = ui_get_context(); + ui_box* box = ui_box_top(); + DEBUG_ASSERT(box, "box stack underflow"); + + ui_box_pop(); + return(box); +} + +void ui_box_set_layout(ui_box* box, ui_axis axis, ui_align alignX, ui_align alignY) +{ + box->layout = (ui_layout){axis, {alignX, alignY}}; +} + +void ui_box_set_size(ui_box* box, ui_axis axis, ui_size_kind kind, f32 value, f32 strictness) +{ + box->desiredSize[axis] = (ui_size){kind, value, strictness}; +} + +void ui_box_set_floating(ui_box* box, ui_axis axis, f32 pos) +{ + box->floating[axis] = true; + box->rect.c[axis] = pos; +} + +void ui_box_set_style_selector(ui_box* box, ui_style_selector selector) +{ + box->styleSelector = selector; +} + +void ui_box_set_closed(ui_box* box, bool closed) +{ + box->closed = closed; +} + +bool ui_box_closed(ui_box* box) +{ + return(box->closed); +} + +ui_sig ui_box_sig(ui_box* box) +{ + return(*box->sig); +} + +bool ui_box_hidden(ui_box* box) +{ + return(box->closed || box->parentClosed); +} + +//----------------------------------------------------------------------------- +// Auto-layout +//----------------------------------------------------------------------------- + +void ui_animate_f32(ui_context* ui, f32* value, f32 target, f32 animationTime) +{ + if( animationTime < 1e-6 + || fabs(*value - target) < 0.001) + { + *value = target; + } + else + { + + /*NOTE: + we use the euler approximation for df/dt = alpha(target - f) + the implicit form is f(t) = target*(1-e^(-alpha*t)) for the rising front, + and f(t) = e^(-alpha*t) for the falling front (e.g. classic RC circuit charge/discharge) + + Here we bake alpha = 1/tau = -ln(0.05)/tr, with tr the rise time to 95% of target + */ + f32 alpha = 3/animationTime; + f32 dt = ui->lastFrameDuration; + + *value += (target - *value)*alpha*dt; + } +} + +void ui_animate_color(ui_context* ui, mg_color* color, mg_color target, f32 animationTime) +{ + for(int i=0; i<4; i++) + { + ui_animate_f32(ui, &color->c[i], target.c[i], animationTime); + } +} + +void ui_box_compute_styling(ui_context* ui, ui_box* box) +{ + ui_style* targetStyle = box->styles[box->styleSelector]; + if(!targetStyle) + { + targetStyle = box->styles[UI_STYLE_NORMAL]; + } + DEBUG_ASSERT(targetStyle); + + f32 animationTime = targetStyle->animationTime; + + /* + f32 transitionValue = 0; + + //NOTE: update transition values + if(box->styleSelector == UI_STYLE_ACTIVE) + { + //NOTE: transition from normal or hot to active + if(box->hotTransition) + { + //NOTE: transition from hot to active + baseStyle = box->styles[UI_STYLE_HOT]; + } + else + { + //NOTE: transition from normal to active + baseStyle = box->styles[UI_STYLE_NORMAL]; + } + + box->hotTransition = ui_update_transition(ui, box->activeTransition, 1, animationTime); + box->activeTransition = ui_update_transition(ui, box->activeTransition, 1, animationTime); + transitionValue = box->activeTransition; + } + else if(box->styleSelector == UI_STYLE_HOT) + { + box->activeTransition = ui_update_transition(ui, box->activeTransition, 0, animationTime); + box->hotTransition = ui_update_transition(ui, box->hotTransition, 1, animationTime); + + if(box->activeTransition) + { + //NOTE: transition from active to hot + transitionValue = box->activeTransition; + baseStyle = box->styles[UI_STYLE_ACTIVE]; + } + else + { + //NOTE: transition from normal to hot + transitionValue = box->hotTransition; + baseStyle = box->styles[UI_STYLE_NORMAL]; + } + } + else + { + //NOTE: transition from hot or active to normal + box->activeTransition = 0; + box->hotTransition = ui_update_transition(ui, box->hotTransition, 0, animationTime); + transitionValue = box->hotTransition; + } + */ + + //TODO: interpolate based on transition values + ui_animate_color(ui, &box->computedStyle.backgroundColor, targetStyle->backgroundColor, animationTime); + ui_animate_color(ui, &box->computedStyle.foregroundColor, targetStyle->foregroundColor, animationTime); + ui_animate_color(ui, &box->computedStyle.borderColor, targetStyle->borderColor, animationTime); + ui_animate_color(ui, &box->computedStyle.textColor, targetStyle->textColor, animationTime); + + box->computedStyle.font = targetStyle->font; + ui_animate_f32(ui, &box->computedStyle.fontSize, targetStyle->fontSize, animationTime); + ui_animate_f32(ui, &box->computedStyle.borderSize, targetStyle->borderSize, animationTime); + ui_animate_f32(ui, &box->computedStyle.roundness, targetStyle->roundness, animationTime); +} + +void ui_layout_prepass(ui_context* ui, ui_box* box) +{ + if(ui_box_hidden(box)) + { + return; + } + + //NOTE: compute styling and static sizes + ui_box_compute_styling(ui, box); + + ui_style* style = &box->computedStyle; + + mp_rect textBox = {}; + if( box->desiredSize[UI_AXIS_X].kind == UI_SIZE_TEXT + ||box->desiredSize[UI_AXIS_Y].kind == UI_SIZE_TEXT) + { + textBox = mg_text_bounding_box(style->font, style->fontSize, box->string); + } + + for(int i=0; idesiredSize[i]; + + if(size.kind == UI_SIZE_TEXT) + { + box->rect.c[2+i] = textBox.c[2+i]; + } + else if(size.kind == UI_SIZE_PIXELS) + { + box->rect.c[2+i] = size.value; + } + } + + for_each_in_list(&box->children, child, ui_box, listElt) + { + ui_layout_prepass(ui, child); + } +} + +void ui_layout_upward_dependent_size(ui_context* ui, ui_box* box, int axis) +{ + if(ui_box_hidden(box)) + { + return; + } + + ui_size* size = &box->desiredSize[axis]; + + if(size->kind == UI_SIZE_PARENT_RATIO) + { + ui_box* parent = box->parent; + if( parent + && parent->desiredSize[axis].kind != UI_SIZE_CHILDREN) + { + box->rect.c[2+axis] = parent->rect.c[2+axis] * size->value; + } + //TODO else? + } + + for_each_in_list(&box->children, child, ui_box, listElt) + { + ui_layout_upward_dependent_size(ui, child, axis); + } +} + +void ui_layout_downward_dependent_size(ui_context* ui, ui_box* box, int axis) +{ + f32 sum = 0; + if(box->layout.axis == axis) + { + for_each_in_list(&box->children, child, ui_box, listElt) + { + if(!ui_box_hidden(child)) + { + ui_layout_downward_dependent_size(ui, child, axis); + if(!child->floating[axis]) + { + sum += child->rect.c[2+axis]; + } + } + } + } + else + { + for_each_in_list(&box->children, child, ui_box, listElt) + { + if(!ui_box_hidden(child)) + { + ui_layout_downward_dependent_size(ui, child, axis); + if(!child->floating[axis]) + { + sum = maximum(sum, child->rect.c[2+axis]); + } + } + } + } + + box->childrenSum[axis] = sum; + + ui_size* size = &box->desiredSize[axis]; + if(size->kind == UI_SIZE_CHILDREN) + { + box->rect.c[2+axis] = sum; + } +} + +void ui_layout_solve_conflicts(ui_context* ui, ui_box* box, int axis) +{ + //TODO +} + +void ui_layout_compute_rect(ui_context* ui, ui_box* box, vec2 pos) +{ + if(ui_box_hidden(box)) + { + return; + } + + box->rect.x = pos.x; + box->rect.y = pos.y; + box->z = ui->z; + ui->z++; + + ui_axis layoutAxis = box->layout.axis; + ui_axis secondAxis = (layoutAxis == UI_AXIS_X) ? UI_AXIS_Y : UI_AXIS_X; + ui_align* align = box->layout.align; + + vec2 origin = {box->rect.x - box->scroll.x, + box->rect.y - box->scroll.y}; + vec2 currentPos = origin; + + vec2 contentsSize = {maximum(box->rect.w, box->childrenSum[UI_AXIS_X]), + maximum(box->rect.h, box->childrenSum[UI_AXIS_Y])}; + + for(int i=0; ichildrenSum[i]; + } + } + if(align[layoutAxis] == UI_ALIGN_CENTER) + { + currentPos.c[layoutAxis] += 0.5*(contentsSize.c[layoutAxis] - box->childrenSum[layoutAxis]); + } + + for_each_in_list(&box->children, child, ui_box, listElt) + { + if(align[secondAxis] == UI_ALIGN_CENTER) + { + currentPos.c[secondAxis] = origin.c[secondAxis] + 0.5*(contentsSize.c[secondAxis] - child->rect.c[2+secondAxis]); + } + + vec2 childPos = currentPos; + for(int i=0; ifloating[i]) + { + childPos.c[i] = origin.c[i] + child->rect.c[i]; + } + } + + ui_layout_compute_rect(ui, child, childPos); + + if(!child->floating[layoutAxis]) + { + currentPos.c[layoutAxis] += child->rect.c[2+layoutAxis]; + } + } +} + +void ui_layout_find_next_hovered_recursive(ui_context* ui, ui_box* box, vec2 p) +{ + if(ui_box_hidden(box)) + { + return; + } + + bool hit = ui_rect_hit(box->rect, p); + if(hit && (box->flags & UI_FLAG_BLOCK_MOUSE)) + { + ui->hovered = box; + } + if(hit || !(box->flags & UI_FLAG_CLIP)) + { + for_each_in_list(&box->children, child, ui_box, listElt) + { + ui_layout_find_next_hovered_recursive(ui, child, p); + } + } +} + +void ui_layout_find_next_hovered(ui_context* ui, vec2 p) +{ + ui->hovered = 0; + ui_layout_find_next_hovered_recursive(ui, ui->root, p); +} + +void ui_solve_layout(ui_context* ui) +{ + ui_layout_prepass(ui, ui->root); + + for(int axis=0; axisroot, axis); + ui_layout_downward_dependent_size(ui, ui->root, axis); + ui_layout_solve_conflicts(ui, ui->root, axis); + } + ui_layout_compute_rect(ui, ui->root, (vec2){0, 0}); + + vec2 p = ui_mouse_position(); + ui_layout_find_next_hovered(ui, p); +} + +//----------------------------------------------------------------------------- +// Drawing +//----------------------------------------------------------------------------- + +void ui_rectangle_fill(mg_canvas canvas, mp_rect rect, f32 roundness) +{ + if(roundness) + { + mg_rounded_rectangle_fill(canvas, rect.x, rect.y, rect.w, rect.h, roundness); + } + else + { + mg_rectangle_fill(canvas, rect.x, rect.y, rect.w, rect.h); + } +} + +void ui_rectangle_stroke(mg_canvas canvas, mp_rect rect, f32 roundness) +{ + if(roundness) + { + mg_rounded_rectangle_stroke(canvas, rect.x, rect.y, rect.w, rect.h, roundness); + } + else + { + mg_rectangle_stroke(canvas, rect.x, rect.y, rect.w, rect.h); + } +} + +void ui_draw_box(mg_canvas canvas, ui_box* box) +{ + if(ui_box_hidden(box)) + { + return; + } + + ui_style* style = &box->computedStyle; + + if(box->flags & UI_FLAG_CLIP) + { + mg_clip_push(canvas, box->rect.x, box->rect.y, box->rect.w, box->rect.h); + } + + if(box->flags & UI_FLAG_DRAW_BACKGROUND) + { + mg_set_color(canvas, style->backgroundColor); + ui_rectangle_fill(canvas, box->rect, style->roundness); + } + + for_each_in_list(&box->children, child, ui_box, listElt) + { + ui_draw_box(canvas, child); + } + + if(box->flags & UI_FLAG_DRAW_FOREGROUND) + { + mg_set_color(canvas, style->foregroundColor); + ui_rectangle_fill(canvas, box->rect, style->roundness); + } + + if(box->flags & UI_FLAG_DRAW_TEXT) + { + mp_rect textBox = mg_text_bounding_box(style->font, style->fontSize, box->string); + + f32 x = box->rect.x + 0.5*(box->rect.w - textBox.w); + f32 y = box->rect.y + 0.5*(box->rect.h - textBox.h) - textBox.y; + + mg_set_font(canvas, style->font); + mg_set_font_size(canvas, style->fontSize); + mg_set_color(canvas, style->textColor); + + mg_move_to(canvas, x, y); + mg_text_outlines(canvas, box->string); + mg_fill(canvas); + } + + if(box->flags & UI_FLAG_CLIP) + { + mg_clip_pop(canvas); + } + + if(box->flags & UI_FLAG_DRAW_BORDER) + { + mg_set_width(canvas, style->borderSize); + mg_set_color(canvas, style->borderColor); + ui_rectangle_stroke(canvas, box->rect, style->roundness); + } +} + +void ui_draw(mg_canvas canvas) +{ + ui_context* ui = ui_get_context(); + + //NOTE: draw + mg_mat2x3 transform = {1, 0, 0, + 0, -1, ui->height}; + + bool oldTextFlip = mg_get_text_flip(canvas); + mg_set_text_flip(canvas, true); + + mg_matrix_push(canvas, transform); + ui_draw_box(canvas, ui->root); + mg_matrix_pop(canvas); + + mg_set_text_flip(canvas, oldTextFlip); + + //TODO: restore flip?? +} + +//----------------------------------------------------------------------------- +// frame begin/end +//----------------------------------------------------------------------------- + +void ui_begin_frame(u32 width, u32 height, ui_style defaultStyle) +{ + ui_context* ui = ui_get_context(); + + mem_arena_clear(&ui->frameArena); + + ui->width = width; + ui->height = height; + ui->frameCounter++; + f64 time = mp_get_time(MP_CLOCK_MONOTONIC); + ui->lastFrameDuration = time - ui->frameTime; + ui->frameTime = time; + + ui->boxStack = 0; + ui->sizeStack[UI_AXIS_X] = 0; + ui->sizeStack[UI_AXIS_Y] = 0; + for(int i=0; istyleStack[i] = 0; + } + + ui->clipStack = 0; + ui->z = 0; + + ui_style_push(UI_STYLE_NORMAL, defaultStyle); + + ui_size_push(UI_AXIS_X, UI_SIZE_PIXELS, width, 0); + ui_size_push(UI_AXIS_Y, UI_SIZE_PIXELS, height, 0); + + ui->root = ui_box_begin("_root_", 0); + + ui_box* contents = ui_box_make("_contents_", 0); + ui_box_set_floating(contents, UI_AXIS_X, 0); + ui_box_set_floating(contents, UI_AXIS_Y, 0); + ui_box_set_layout(contents, UI_AXIS_Y, UI_ALIGN_START, UI_ALIGN_START); + + ui->overlay = ui_box_make("_overlay_", 0); + ui_box_set_floating(ui->overlay, UI_AXIS_X, 0); + ui_box_set_floating(ui->overlay, UI_AXIS_Y, 0); + + ui_size_pop(UI_AXIS_X); + ui_size_pop(UI_AXIS_Y); + + ui_box_push(contents); +} + +void ui_end_frame() +{ + ui_context* ui = ui_get_context(); + + ui_box_pop(); + + ui_box* box = ui_box_end(); + DEBUG_ASSERT(box == ui->root, "unbalanced box stack"); + + ui_style_pop(UI_STYLE_NORMAL); + + //NOTE: layout + ui_solve_layout(ui); + + //NOTE: prune unused boxes + for(int i=0; iboxMap[i], box, ui_box, bucketElt) + { + if(box->frameCounter < ui->frameCounter) + { + ListRemove(&ui->boxMap[i], &box->bucketElt); + } + } + } +} + +//----------------------------------------------------------------------------- +// Init / cleanup +//----------------------------------------------------------------------------- +void ui_init() +{ + ui_context* ui = ui_get_context(); + if(!ui->init) + { + memset(ui, 0, sizeof(ui_context)); + mem_arena_init(&ui->frameArena); + mem_pool_init(&ui->boxPool, sizeof(ui_box)); + ui->init = true; + } +} + +void ui_cleanup() +{ + ui_context* ui = ui_get_context(); + mem_arena_release(&ui->frameArena); + mem_pool_release(&ui->boxPool); + ui->init = false; +} + + +//----------------------------------------------------------------------------- +// Basic helpers +//----------------------------------------------------------------------------- + +ui_sig ui_label(const char* label) +{ + ui_flags flags = UI_FLAG_CLIP + | UI_FLAG_DRAW_TEXT; + ui_box* box = ui_box_make(label, flags); + ui_box_set_size(box, UI_AXIS_X, UI_SIZE_TEXT, 0, 0); + ui_box_set_size(box, UI_AXIS_Y, UI_SIZE_TEXT, 0, 0); + + ui_sig sig = ui_box_sig(box); + return(sig); +} + +ui_sig ui_button(const char* label) +{ + ui_flags flags = UI_FLAG_CLICKABLE + | UI_FLAG_CLIP + | UI_FLAG_DRAW_FOREGROUND + | UI_FLAG_DRAW_BORDER + | UI_FLAG_DRAW_TEXT + | UI_FLAG_HOT_ANIMATION + | UI_FLAG_ACTIVE_ANIMATION; + + ui_box* box = ui_box_make(label, flags); + ui_sig sig = ui_box_sig(box); + + if(sig.hovering) + { + ui_box_set_style_selector(box, UI_STYLE_HOT); + if(sig.dragging) + { + ui_box_set_style_selector(box, UI_STYLE_ACTIVE); + } + } + + return(sig); +} + +ui_box* ui_scrollbar(const char* label, f32 thumbRatio, f32* scrollValue) +{ + ui_box* frame = ui_box_begin(label, 0); + { + ui_axis trackAxis = (frame->rect.w > frame->rect.h) ? UI_AXIS_X : UI_AXIS_Y; + ui_axis secondAxis = (trackAxis == UI_AXIS_Y) ? UI_AXIS_X : UI_AXIS_Y; + + f32 roundness = 0.5*frame->rect.c[2+secondAxis]; + f32 animationTime = 0.5; + + ui_style style[UI_STYLE_SELECTOR_COUNT] = { + [UI_STYLE_NORMAL] = {.backgroundColor = {0, 0, 0, 0}, + .foregroundColor = {0, 0, 0, 0}, + .roundness = roundness, + .animationTime = animationTime}, + [UI_STYLE_HOT] = {.backgroundColor = {0, 0, 0, 0.5}, + .foregroundColor = {0, 0, 0, 0.7}, + .roundness = roundness, + .animationTime = animationTime}, + [UI_STYLE_ACTIVE] = {.backgroundColor = {0, 0, 0, 0.5}, + .foregroundColor = {0, 0, 0, 0.7}, + .roundness = roundness, + .animationTime = animationTime}}; + + for(int i=0; irect.c[2+trackAxis] - thumb->rect.c[2+trackAxis]; + f32 delta = thumbSig.delta.c[trackAxis]/trackExtents; + f32 oldValue = *scrollValue; + + *scrollValue += delta; + *scrollValue = Clamp(*scrollValue, 0, 1); + } + + ui_sig trackSig = ui_box_sig(track); + if(trackSig.hovering) + { + ui_box_set_style_selector(track, UI_STYLE_HOT); + ui_box_set_style_selector(thumb, UI_STYLE_HOT); + } + if(thumbSig.dragging) + { + ui_box_set_style_selector(track, UI_STYLE_ACTIVE); + ui_box_set_style_selector(thumb, UI_STYLE_ACTIVE); + } + + } ui_box_end(); + + return(frame); +} + +void ui_panel_begin(const char* name) +{ + ui_flags panelFlags = UI_FLAG_DRAW_BACKGROUND + | UI_FLAG_DRAW_BORDER + | UI_FLAG_CLIP + | UI_FLAG_BLOCK_MOUSE; + ui_box* panel = ui_box_begin(name, panelFlags); + + ui_box* innerView = ui_box_begin(name, 0); +} + +void ui_panel_end() +{ + ui_box* innerView = ui_box_top(); + ui_box_end(); + + ui_box* panel = ui_box_top(); + + f32 contentsW = ClampLowBound(innerView->childrenSum[0], innerView->rect.w); + f32 contentsH = ClampLowBound(innerView->childrenSum[1], innerView->rect.h); + + contentsW = ClampLowBound(contentsW, 1); + contentsH = ClampLowBound(contentsH, 1); + + if(contentsW > innerView->rect.w) + { + f32 thumbRatioX = innerView->rect.w / contentsW; + f32 sliderX = innerView->scroll.x /(contentsW - innerView->rect.w); + + ui_box* scrollBarX = ui_scrollbar("scrollerX", thumbRatioX, &sliderX); + ui_box_set_size(scrollBarX, UI_AXIS_X, UI_SIZE_PARENT_RATIO, 1., 0); + ui_box_set_size(scrollBarX, UI_AXIS_Y, UI_SIZE_PIXELS, 10, 0); + ui_box_set_floating(scrollBarX, UI_AXIS_X, 0); + ui_box_set_floating(scrollBarX, UI_AXIS_Y, panel->rect.h - 12); + + innerView->scroll.x = sliderX * (contentsW - innerView->rect.w); + } + + if(contentsH > innerView->rect.h) + { + f32 thumbRatioY = innerView->rect.h / contentsH; + f32 sliderY = innerView->scroll.y /(contentsH - innerView->rect.h); + + ui_box* scrollBarY = ui_scrollbar("scrollerY", thumbRatioY, &sliderY); + ui_box_set_size(scrollBarY, UI_AXIS_X, UI_SIZE_PIXELS, 10, 0); + ui_box_set_size(scrollBarY, UI_AXIS_Y, UI_SIZE_PARENT_RATIO, 1., 0); + ui_box_set_floating(scrollBarY, UI_AXIS_X, panel->rect.w - 12); + ui_box_set_floating(scrollBarY, UI_AXIS_Y, 0); + + innerView->scroll.y = sliderY * (contentsH - innerView->rect.h); + } + + ui_box_end(); +} + +ui_sig ui_tooltip_begin(const char* name) +{ + ui_context* ui = ui_get_context(); + ui_box_push(ui->overlay); + + vec2 p = ui_mouse_position(); + + ui_flags flags = UI_FLAG_DRAW_BACKGROUND + | UI_FLAG_DRAW_BORDER; + + ui_box* tooltip = ui_box_make(name, flags); + ui_box_set_size(tooltip, UI_AXIS_X, UI_SIZE_CHILDREN, 0, 0); + ui_box_set_size(tooltip, UI_AXIS_Y, UI_SIZE_CHILDREN, 0, 0); + ui_box_set_floating(tooltip, UI_AXIS_X, p.x); + ui_box_set_floating(tooltip, UI_AXIS_Y, p.y); + ui_box_push(tooltip); + + return(ui_box_sig(tooltip)); +} + +void ui_tooltip_end() +{ + ui_box_pop(); // tooltip + ui_box_pop(); // ui->overlay +} + +void ui_box_activate(ui_box* box) +{ + box->active = true; +} + +void ui_box_deactivate(ui_box* box) +{ + box->active = false; +} + +bool ui_box_active(ui_box* box) +{ + return(box->active); +} + +void ui_menu_bar_begin(const char* name) +{ + ui_box* bar = ui_box_begin(name, UI_FLAG_DRAW_BACKGROUND); + ui_box_set_size(bar, UI_AXIS_X, UI_SIZE_PARENT_RATIO, 1., 0); + ui_box_set_size(bar, UI_AXIS_Y, UI_SIZE_CHILDREN, 0, 0); + ui_box_set_layout(bar, UI_AXIS_X, UI_ALIGN_START, UI_ALIGN_START); + + ui_size_push(UI_AXIS_X, UI_SIZE_TEXT, 0, 0); + ui_size_push(UI_AXIS_Y, UI_SIZE_TEXT, 0, 0); + + + ui_sig sig = ui_box_sig(bar); + if(!sig.hovering && mp_input_mouse_released(MP_MOUSE_LEFT)) + { + ui_box_deactivate(bar); + } +} + +void ui_menu_bar_end() +{ + ui_size_pop(UI_AXIS_X); + ui_size_pop(UI_AXIS_Y); + ui_box_end(); // menu bar +} + +void ui_menu_begin(const char* label) +{ + ui_box* button = ui_box_make(label, UI_FLAG_CLICKABLE | UI_FLAG_DRAW_TEXT); + ui_box* bar = button->parent; + + ui_sig sig = ui_box_sig(button); + ui_sig barSig = ui_box_sig(bar); + + ui_context* ui = ui_get_context(); + ui_box_push(ui->overlay); + + ui_flags flags = UI_FLAG_DRAW_BACKGROUND + | UI_FLAG_DRAW_BORDER; + + ui_box* menu = ui_box_make(label, flags); + ui_box_set_size(menu, UI_AXIS_X, UI_SIZE_CHILDREN, 0, 0); + ui_box_set_size(menu, UI_AXIS_Y, UI_SIZE_CHILDREN, 0, 0); + ui_box_set_floating(menu, UI_AXIS_X, button->rect.x); + ui_box_set_floating(menu, UI_AXIS_Y, button->rect.y + button->rect.h); + ui_box_set_layout(menu, UI_AXIS_Y, UI_ALIGN_START, UI_ALIGN_START); + + if(ui_box_active(bar)) + { + if(sig.hovering) + { + ui_box_activate(button); + } + else if(barSig.hovering) + { + ui_box_deactivate(button); + } + } + else + { + ui_box_deactivate(button); + if(sig.pressed) + { + ui_box_activate(bar); + ui_box_activate(button); + } + } + + ui_box_set_closed(menu, !ui_box_active(button)); + ui_box_push(menu); +} + +void ui_menu_end() +{ + ui_box_pop(); // menu + ui_box_pop(); // overlay; +} + + + + +#undef LOG_SUBSYSTEM diff --git a/src/ui.h b/src/ui.h new file mode 100644 index 0000000..a0290c6 --- /dev/null +++ b/src/ui.h @@ -0,0 +1,212 @@ +/************************************************************//** +* +* @file: ui.h +* @author: Martin Fouilleul +* @date: 08/08/2022 +* @revision: +* +*****************************************************************/ +#ifndef __UI_H_ +#define __UI_H_ + +#include"typedefs.h" +#include"lists.h" +#include"graphics.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum +{ + UI_FLAG_CLICKABLE = (1<<0), + UI_FLAG_SCROLLABLE = (1<<1), + UI_FLAG_BLOCK_MOUSE = (1<<2), + UI_FLAG_HOT_ANIMATION = (1<<3), + UI_FLAG_ACTIVE_ANIMATION = (1<<4), + UI_FLAG_CLIP = (1<<5), + UI_FLAG_DRAW_BACKGROUND = (1<<6), + UI_FLAG_DRAW_FOREGROUND = (1<<7), + UI_FLAG_DRAW_BORDER = (1<<8), + UI_FLAG_DRAW_TEXT = (1<<9), + +} ui_flags; + +typedef struct ui_key +{ + u64 hash; +} ui_key; + +typedef enum +{ + UI_AXIS_X, + UI_AXIS_Y, + UI_AXIS_COUNT +} ui_axis; + +typedef enum +{ + UI_ALIGN_START, + UI_ALIGN_END, + UI_ALIGN_CENTER, +} ui_align; + +typedef struct ui_layout +{ + ui_axis axis; + ui_align align[UI_AXIS_COUNT]; + +} ui_layout; + +typedef enum +{ + UI_SIZE_TEXT, + UI_SIZE_PIXELS, + UI_SIZE_CHILDREN, + UI_SIZE_PARENT_RATIO, + +} ui_size_kind; + +typedef struct ui_size +{ + ui_size_kind kind; + f32 value; + f32 strictness; +} ui_size; + +typedef enum { UI_STYLE_NORMAL, + UI_STYLE_HOT, + UI_STYLE_ACTIVE, + UI_STYLE_SELECTOR_COUNT } ui_style_selector; + +typedef struct ui_style +{ + mg_color backgroundColor; + mg_color foregroundColor; + mg_color borderColor; + mg_color textColor; + mg_font font; + f32 fontSize; + f32 borderSize; + f32 roundness; + f32 animationTime; +} ui_style; + +typedef struct ui_box ui_box; + +typedef struct ui_sig +{ + ui_box* box; + + vec2 mouse; + vec2 delta; + vec2 wheel; + + bool pressed; + bool released; + bool triggered; + bool clicked; + bool doubleClicked; + bool rightClicked; + bool dragging; + bool hovering; + +} ui_sig; + +struct ui_box +{ + // hierarchy + list_elt listElt; + list_info children; + ui_box* parent; + + // keying and caching + list_elt bucketElt; + ui_key key; + u64 frameCounter; + + // builder-provided info + ui_flags flags; + str8 string; + + // layout + u32 z; + bool floating[UI_AXIS_COUNT]; + ui_size desiredSize[UI_AXIS_COUNT]; + ui_layout layout; + mp_rect rect; + f32 childrenSum[2]; + + // styling + ui_style* styles[UI_STYLE_SELECTOR_COUNT]; + ui_style computedStyle; + ui_style_selector styleSelector; + + // signals + ui_sig* sig; + + // stateful behaviour + bool closed; + bool parentClosed; + bool dragging; + bool active; + vec2 scroll; + + // animation data + f32 hotTransition; + f32 activeTransition; +}; + +void ui_init(); + +void ui_begin_frame(u32 width, u32 height, ui_style defaultStyle); +void ui_end_frame(); +void ui_draw(mg_canvas canvas); + +ui_box* ui_box_make(const char* string, ui_flags flags); +ui_box* ui_box_begin(const char* string, ui_flags flags); +ui_box* ui_box_make_str8(str8 string, ui_flags flags); +ui_box* ui_box_begin_str8(str8 string, ui_flags flags); +ui_box* ui_box_end(); +#define ui_container(name, flags) defer_loop(ui_box_begin(name, flags), ui_box_end()) + +void ui_box_set_layout(ui_box* box, ui_axis axis, ui_align alignX, ui_align alignY); +void ui_box_set_size(ui_box* box, ui_axis axis, ui_size_kind kind, f32 value, f32 strictness); +void ui_box_set_floating(ui_box* box, ui_axis axis, f32 pos); + +void ui_box_set_style_selector(ui_box* box, ui_style_selector selector); + +ui_sig ui_box_sig(ui_box* box); + +void ui_size_push(ui_axis axis, ui_size_kind kind, f32 value, f32 strictness); +void ui_size_pop(ui_axis axis); +void ui_style_push(ui_style_selector selector, ui_style style); +void ui_style_pop(ui_style_selector selector); + +// Basic helpers +ui_sig ui_label(const char* label); + +ui_sig ui_button(const char* label); +ui_box* ui_scrollbar(const char* label, f32 thumbRatio, f32* scrollValue); + +void ui_panel_begin(const char* name); +void ui_panel_end(); +#define ui_panel(name) defer_loop(ui_panel_begin(name), ui_panel_end()) + +ui_sig ui_tooltip_begin(const char* name); +void ui_tooltip_end(); +#define ui_tooltip(name) defer_loop(ui_tooltip_begin(name), ui_tooltip_end()) + +void ui_menu_bar_begin(const char* label); +void ui_menu_bar_end(); +#define ui_menu_bar(name) defer_loop(ui_menu_bar_begin(name), ui_menu_bar_end()) + +void ui_menu_begin(const char* label); +void ui_menu_end(); +#define ui_menu(name) defer_loop(ui_menu_begin(name), ui_menu_end()) + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif //__UI_H_ diff --git a/src/util/debug_log.c b/src/util/debug_log.c new file mode 100644 index 0000000..ce2caaf --- /dev/null +++ b/src/util/debug_log.c @@ -0,0 +1,123 @@ +/************************************************************//** +* +* @file: debug_log.c +* @author: Martin Fouilleul +* @date: 22/10/2020 +* @revision: +* +*****************************************************************/ +#include +#include +#include"debug_log.h" + +static const char* LOG_HEADINGS[LOG_LEVEL_COUNT] = { + "Error", + "Warning", + "Message", + "Debug"}; + +static const char* LOG_FORMATS[LOG_LEVEL_COUNT] = { + "\e[38;5;9m\e[1m", + "\e[38;5;13m\e[1m", + "\e[38;5;10m\e[1m", + "\e[38;5;14m\e[1m" }; + +static const char* LOG_FORMAT_STOP = "\e[m"; + +const int LOG_SUBSYSTEM_MAX_COUNT = 16; + +typedef struct log_config +{ + FILE* out; + log_level level; + const char* subsystemNames[LOG_SUBSYSTEM_MAX_COUNT]; + log_level subsystemLevels[LOG_SUBSYSTEM_MAX_COUNT]; + +} log_config; + +static log_config __log_config = {.out = 0, + .level = LOG_DEFAULT_LEVEL, + .subsystemNames = {}, + .subsystemLevels = {}}; + +int LogFindSubsystem(const char* subsystem) +{ + for(int i=0; i= 0)? __log_config.subsystemLevels[subsystemIndex] : __log_config.level; + + if(level <= filterLevel) + { + if(!__log_config.out) + { + __log_config.out = LOG_DEFAULT_OUTPUT; + } + fprintf(__log_config.out, + "%s%s:%s [%s] %s() in %s:%i: ", + LOG_FORMATS[level], + LOG_HEADINGS[level], + LOG_FORMAT_STOP, + subsystem, + functionName, + fileName, + line); + + va_list ap; + va_start(ap, msg); + vfprintf(__log_config.out, msg, ap); + va_end(ap); + } +} + +void LogOutput(FILE* output) +{ + __log_config.out = output; +} + +void LogLevel(log_level level) +{ + __log_config.level = level; +} + +void LogFilter(const char* subsystem, log_level level) +{ + int firstNull = -1; + for(int i=0; i +#include"typedefs.h" +#include"macro_helpers.h" + +#ifdef __cplusplus +extern "C" { +#endif +//NOTE(martin): the default logging level can be adjusted by defining LOG_DEFAULT_LEVEL. As the name suggest, it is the default, but it +// can be adjusted at runtime with LogLevel() +#ifndef LOG_DEFAULT_LEVEL + #define LOG_DEFAULT_LEVEL LOG_LEVEL_WARNING +#endif + +//NOTE(martin): the default output can be adjusted by defining LOG_DEFAULT_OUTPUT. It can be adjusted at runtime with LogOutput() +#ifndef LOG_DEFAULT_OUTPUT + #define LOG_DEFAULT_OUTPUT stdout +#endif + +//NOTE(martin): LOG_SUBSYSTEM can be defined in each compilation unit to associate it with a subsystem, like this: +// #define LOG_SUBSYSTEM "name" + +typedef enum { LOG_LEVEL_ERROR, + LOG_LEVEL_WARNING, + LOG_LEVEL_MESSAGE, + LOG_LEVEL_DEBUG, + LOG_LEVEL_COUNT } log_level; + +void LogGeneric(log_level level, + const char* subsystem, + const char* functionName, + const char* fileName, + u32 line, + const char* msg, + ...); + +void LogOutput(FILE* output); +void LogLevel(log_level level); +void LogFilter(const char* subsystem, log_level level); + +#define LOG_GENERIC(level, func, file, line, msg, ...) LogGeneric(level, LOG_SUBSYSTEM, func, file, line, msg, ##__VA_ARGS__ ) + +#define LOG_ERROR(msg, ...) LOG_GENERIC(LOG_LEVEL_ERROR, __FUNCTION__, __FILE__, __LINE__, msg, ##__VA_ARGS__ ) + +//NOTE(martin): warnings, messages, and debug info can be enabled in debug mode by defining LOG_COMPILE_XXX, XXX being the max desired log level +// error logging is always compiled +#if defined(LOG_COMPILE_WARNING) || defined(LOG_COMPILE_MESSAGE) || defined(LOG_COMPILE_DEBUG) + #define LOG_WARNING(msg, ...) LOG_GENERIC(LOG_LEVEL_WARNING, __FUNCTION__, __FILE__, __LINE__, msg, ##__VA_ARGS__ ) + + #if defined(LOG_COMPILE_MESSAGE) || defined(LOG_COMPILE_DEBUG) + #define LOG_MESSAGE(msg, ...) LOG_GENERIC(LOG_LEVEL_MESSAGE, __FUNCTION__, __FILE__, __LINE__, msg, ##__VA_ARGS__ ) + + #if defined(LOG_COMPILE_DEBUG) + #define LOG_DEBUG(msg, ...) LOG_GENERIC(LOG_LEVEL_DEBUG, __FUNCTION__, __FILE__, __LINE__, msg, ##__VA_ARGS__ ) + #else + #define LOG_DEBUG(msg, ...) + #endif + #else + #define LOG_MESSAGE(msg, ...) + #define LOG_DEBUG(msg, ...) + #endif +#else + #define LOG_WARNING(msg, ...) + #define LOG_MESSAGE(msg, ...) + #define LOG_DEBUG(msg, ...) +#endif + +#ifndef NO_ASSERT + #include + #define _ASSERT_(x, msg) assert(x && msg) + #define ASSERT(x, ...) _ASSERT_(x, #__VA_ARGS__) + + #ifdef DEBUG + #define DEBUG_ASSERT(x, ...) ASSERT(x, ##__VA_ARGS__ ) + #else + #define DEBUG_ASSERT(x, ...) + #endif +#else + #define ASSERT(x, ...) + #define DEBUG_ASSERT(x, ...) +#endif + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif //__DEBUG_LOG_H_ diff --git a/src/util/hash.c b/src/util/hash.c new file mode 100644 index 0000000..7e704d8 --- /dev/null +++ b/src/util/hash.c @@ -0,0 +1,124 @@ +/************************************************************//** +* +* @file: hash.cpp +* @author: Martin Fouilleul +* @date: 08/08/2022 +* @revision: +* +*****************************************************************/ +#include +#include"hash.h" + +u64 mp_hash_aes_u64(u64 x) +{ + u8 seed[16] = { + 0xaa, 0x9b, 0xbd, 0xb8, + 0xa1, 0x98, 0xac, 0x3f, + 0x1f, 0x94, 0x07, 0xb3, + 0x8c, 0x27, 0x93, 0x69 }; + + __m128i hash = _mm_set_epi64x(0L, x); + __m128i key = _mm_loadu_si128((__m128i*)seed); + hash = _mm_aesdec_si128(hash, key); + hash = _mm_aesdec_si128(hash, key); + u64 result = _mm_extract_epi64(hash, 0); + + return(result); +} + +u64 mp_hash_aes_u64_x2(u64 x, u64 y) +{ + u8 seed[16] = { + 0xaa, 0x9b, 0xbd, 0xb8, + 0xa1, 0x98, 0xac, 0x3f, + 0x1f, 0x94, 0x07, 0xb3, + 0x8c, 0x27, 0x93, 0x69 }; + + __m128i hash = _mm_set_epi64x(x, y); + __m128i key = _mm_loadu_si128((__m128i*)seed); + hash = _mm_aesdec_si128(hash, key); + hash = _mm_aesdec_si128(hash, key); + u64 result = _mm_extract_epi64(hash, 0); + + return(result); +} + +u64 mp_hash_aes_string(str8 string) +{ + u8 seed[16] = { + 0xaa, 0x9b, 0xbd, 0xb8, + 0xa1, 0x98, 0xac, 0x3f, + 0x1f, 0x94, 0x07, 0xb3, + 0x8c, 0x27, 0x93, 0x69 }; + + __m128i hash = _mm_loadu_si128((__m128i*)seed); + + u64 chunkCount = string.len / 16; + char* at = string.ptr; + + while(chunkCount--) + { + __m128i in = _mm_loadu_si128((__m128i*)at); + at += 16; + + hash = _mm_xor_si128(hash, in); + hash = _mm_aesdec_si128(hash, _mm_setzero_si128()); + hash = _mm_aesdec_si128(hash, _mm_setzero_si128()); + hash = _mm_aesdec_si128(hash, _mm_setzero_si128()); + hash = _mm_aesdec_si128(hash, _mm_setzero_si128()); + } + + u64 restCount = string.len % 16; + char tmp[16]; + memset(tmp, 0, 16); + memmove(tmp, at, restCount); + + __m128i in = _mm_loadu_si128((__m128i*)tmp); + hash = _mm_xor_si128(hash, in); + hash = _mm_aesdec_si128(hash, _mm_setzero_si128()); + hash = _mm_aesdec_si128(hash, _mm_setzero_si128()); + hash = _mm_aesdec_si128(hash, _mm_setzero_si128()); + hash = _mm_aesdec_si128(hash, _mm_setzero_si128()); + + u64 result = _mm_extract_epi64(hash, 0); + return(result); +} + +u64 mp_hash_aes_string_seed(str8 string, u64 seed) +{ + u8 seed16[16]; + memcpy(seed16, &seed, 8); + memcpy(seed16+8, &seed, 8); + + __m128i hash = _mm_loadu_si64(&seed16); + + u64 chunkCount = string.len / 16; + char* at = string.ptr; + + while(chunkCount--) + { + __m128i in = _mm_loadu_si128((__m128i*)at); + at += 16; + + hash = _mm_xor_si128(hash, in); + hash = _mm_aesdec_si128(hash, _mm_setzero_si128()); + hash = _mm_aesdec_si128(hash, _mm_setzero_si128()); + hash = _mm_aesdec_si128(hash, _mm_setzero_si128()); + hash = _mm_aesdec_si128(hash, _mm_setzero_si128()); + } + + u64 restCount = string.len % 16; + char tmp[16]; + memset(tmp, 0, 16); + memmove(tmp, at, restCount); + + __m128i in = _mm_loadu_si128((__m128i*)tmp); + hash = _mm_xor_si128(hash, in); + hash = _mm_aesdec_si128(hash, _mm_setzero_si128()); + hash = _mm_aesdec_si128(hash, _mm_setzero_si128()); + hash = _mm_aesdec_si128(hash, _mm_setzero_si128()); + hash = _mm_aesdec_si128(hash, _mm_setzero_si128()); + + u64 result = _mm_extract_epi64(hash, 0); + return(result); +} diff --git a/src/util/hash.h b/src/util/hash.h new file mode 100644 index 0000000..b482a4c --- /dev/null +++ b/src/util/hash.h @@ -0,0 +1,29 @@ +/************************************************************//** +* +* @file: hash.h +* @author: Martin Fouilleul +* @date: 08/08/2022 +* @revision: +* +*****************************************************************/ +#ifndef __HASH_H_ +#define __HASH_H_ + +#include"typedefs.h" +#include"strings.h" + +#ifdef __cplusplus +extern "C" { +#endif + +u64 mp_hash_aes_u64(u64 x); +u64 mp_hash_aes_u64_x2(u64 x, u64 y); +u64 mp_hash_aes_string(str8 string); +u64 mp_hash_aes_string_seed(str8 string, u64 seed); + +#ifdef __cplusplus +} // extern "C" +#endif + + +#endif //__HASH_H_ diff --git a/src/util/lists.h b/src/util/lists.h new file mode 100644 index 0000000..605089f --- /dev/null +++ b/src/util/lists.h @@ -0,0 +1,390 @@ +/************************************************************//** +* +* @file: lists.h +* @author: Martin Fouilleul +* @date: 22/11/2017 +* @revision: 28/04/2019 : deleted containers which are not used by BLITz +* @brief: Implements generic intrusive linked list and dynamic array +* +****************************************************************/ +#ifndef __CONTAINERS_H_ +#define __CONTAINERS_H_ + +#include"debug_log.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define OFFSET_OF_CONTAINER(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) + +#ifdef __cplusplus +#define CONTAINER_OF(ptr, type, member) ({ \ + const decltype( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - OFFSET_OF_CONTAINER(type,member) );}) +#else +#define CONTAINER_OF(ptr, type, member) ({ \ + const char *__mptr = (char*)(ptr); \ + (type *)(__mptr - OFFSET_OF_CONTAINER(type,member) );}) +#endif + +//------------------------------------------------------------------------- +// Intrusive linked lists +//------------------------------------------------------------------------- + +#define ListEntry(ptr, type, member) \ + CONTAINER_OF(ptr, type, member) + +#define ListNext(elt) (elt)->next +#define ListPrev(elt) (elt)->prev + +#define ListNextEntry(list, elt, type, member) \ + ((elt->member.next != ListEnd(list)) ? ListEntry(elt->member.next, type, member) : 0) + +#define ListPrevEntry(list, elt, type, member) \ + ((elt->member.prev != ListEnd(list)) ? ListEntry(elt->member.prev, type, member) : 0) + +#define ListCheckedEntry(list, type, member) \ + (((list) != 0) ? ListEntry(list, type, member) : 0) + +#define ListFirstEntry(list, type, member) \ + (ListCheckedEntry(ListBegin(list), type, member)) + +#define ListLastEntry(list, type, member) \ + (ListCheckedEntry(ListLast(list), type, member)) + +#define for_each_in_list(list, elt, type, member) \ + for(type* elt = ListCheckedEntry(ListBegin(list), type, member); \ + elt != 0; \ + elt = ListCheckedEntry(elt->member.next, type, member)) \ + +#define for_each_in_list_reverse(list, elt, type, member) \ + for(type* elt = ListCheckedEntry(ListLast(list), type, member); \ + elt != 0; \ + elt = ListCheckedEntry(elt->member.prev, type, member)) \ + +#define for_each_in_list_safe(list, elt, type, member) \ + for(type* elt = ListCheckedEntry(ListBegin(list), type, member), \ + *__tmp = elt ? ListCheckedEntry(elt->member.next, type, member) : 0 ; \ + elt != 0; \ + elt = __tmp, \ + __tmp = elt ? ListCheckedEntry(elt->member.next, type, member) : 0) \ + +#define ListPopEntry(list, type, member) (ListEmpty(list) ? 0 : ListEntry(ListPop(list), type, member)) + +typedef struct list_elt list_elt; +struct list_elt +{ + list_elt* next; + list_elt* prev; +}; + +typedef struct list_info +{ + list_elt* first; + list_elt* last; +} list_info; + +static inline void ListInit(list_info* list) +{ + list->first = list->last = 0; +} + +static inline list_elt* ListBegin(list_info* list) +{ + return(list->first); +} +static inline list_elt* ListEnd(list_info* list) +{ + return(0); +} + +static inline list_elt* ListLast(list_info* list) +{ + return(list->last); +} + +static inline void ListInsert(list_info* list, list_elt* afterElt, list_elt* elt) +{ + elt->prev = afterElt; + elt->next = afterElt->next; + if(afterElt->next) + { + afterElt->next->prev = elt; + } + else + { + list->last = elt; + } + afterElt->next = elt; + + ASSERT(elt->next != elt, "ListInsert(): can't insert an element into itself"); +} + +static inline void ListInsertBefore(list_info* list, list_elt* beforeElt, list_elt* elt) +{ + elt->next = beforeElt; + elt->prev = beforeElt->prev; + + if(beforeElt->prev) + { + beforeElt->prev->next = elt; + } + else + { + list->first = elt; + } + beforeElt->prev = elt; + + ASSERT(elt->next != elt, "ListInsertBefore(): can't insert an element into itself"); +} + +static inline void ListRemove(list_info* list, list_elt* elt) +{ + if(elt->prev) + { + elt->prev->next = elt->next; + } + else + { + DEBUG_ASSERT(list->first == elt); + list->first = elt->next; + } + if(elt->next) + { + elt->next->prev = elt->prev; + } + else + { + DEBUG_ASSERT(list->last == elt); + list->last = elt->prev; + } + elt->prev = elt->next = 0; +} + +static inline void ListPush(list_info* list, list_elt* elt) +{ + elt->next = list->first; + elt->prev = 0; + if(list->first) + { + list->first->prev = elt; + } + else + { + list->last = elt; + } + list->first = elt; +} + +static inline list_elt* ListPop(list_info* list) +{ + list_elt* elt = ListBegin(list); + if(elt != ListEnd(list)) + { + ListRemove(list, elt); + return(elt); + } + else + { + return(0); + } +} + +static inline void ListPushBack(list_info* list, list_elt* elt) +{ + elt->prev = list->last; + elt->next = 0; + if(list->last) + { + list->last->next = elt; + } + else + { + list->first = elt; + } + list->last = elt; +} +#define ListAppend(a, b) ListPushBack(a, b) + + +static inline list_elt* ListPopBack(list_info* list) +{ + list_elt* elt = ListLast(list); + if(elt != ListEnd(list)) + { + ListRemove(list, elt); + return(elt); + } + else + { + return(0); + } +} + +static inline bool ListEmpty(list_info* list) +{ + return(list->first == 0 || list->last == 0); +} + + +//------------------------------------------------------------------------- +// Circular Intrusive linked lists +//------------------------------------------------------------------------- + +#define CListEntry(ptr, type, member) ListEntry(ptr, type, member) +#define CListNext(elt) ListNext(elt) +#define CListPrev(elt) ListPrev(elt) + +#define CListNextEntry(head, elt, type, member) \ + ((elt->member.next != CListEnd(head)) ? CListEntry(elt->member.next, type, member) : 0) + +#define CListPrevEntry(head, elt, type, member) \ + ((elt->member.prev != CListEnd(head)) ? CListEntry(elt->member.prev, type, member) : 0) + +#define CListCheckedEntry(head, info, type, member) \ + ((info != CListEnd(head)) ? CListEntry(info, type, member) : 0) + +#define CListFirstEntry(head, type, member) \ + (CListCheckedEntry(head, CListBegin(head), type, member)) + +#define CListLastEntry(head, type, member) \ + (CListCheckedEntry(head, CListLast(head), type, member)) + +#define for_each_in_clist(list, elt, type, member) \ + for(type* elt = CListEntry(CListBegin(list), type, member); \ + &elt->member != CListEnd(list); \ + elt = CListEntry(elt->member.next, type, member)) \ + + +#define for_each_in_clist_reverse(list, elt, type, member) \ + for(type* elt = CListEntry(CListLast(list), type, member); \ + &elt->member != CListEnd(list); \ + elt = CListEntry(elt->member.prev, type, member)) \ + + +#define for_each_in_clist_safe(list, elt, type, member) \ + for(type* elt = CListEntry(CListBegin(list), type, member), \ + *__tmp = CListEntry(elt->member.next, type, member); \ + &elt->member != CListEnd(list); \ + elt = CListEntry(&__tmp->member, type, member), \ + __tmp = CListEntry(elt->member.next, type, member)) \ + + +#define CListPush(a, b) CListInsert(a, b) +#define CListInsertBefore(a, b) CListAppend(a, b) + +#define CListPopEntry(list, type, member) (CListEmpty(list) ? 0 : CListEntry(CListPop(list), type, member)) + +static inline void CListInit(list_elt* info) +{ + info->next = info->prev = info; +} + +static inline list_elt* CListBegin(list_elt* head) +{ + return(head->next ? head->next : head ); +} +static inline list_elt* CListEnd(list_elt* head) +{ + return(head); +} + +static inline list_elt* CListLast(list_elt* head) +{ + return(head->prev ? head->prev : head); +} + +static inline void CListInsert(list_elt* head, list_elt* elt) +{ + elt->prev = head; + elt->next = head->next; + if(head->next) + { + head->next->prev = elt; + } + else + { + head->prev = elt; + } + head->next = elt; + + ASSERT(elt->next != elt, "CListInsert(): can't insert an element into itself"); +} + +static inline void CListAppend(list_elt* head, list_elt* elt) +{ + CListInsert(head->prev, elt); +} + +static inline void CListCat(list_elt* head, list_elt* list) +{ + if(head->prev) + { + head->prev->next = list->next; + } + if(head->prev && head->prev->next) + { + head->prev->next->prev = head->prev; + } + head->prev = list->prev; + if(head->prev) + { + head->prev->next = head; + } + CListInit(list); +} + +static inline void CListRemove(list_elt* elt) +{ + if(elt->prev) + { + elt->prev->next = elt->next; + } + if(elt->next) + { + elt->next->prev = elt->prev; + } + elt->prev = elt->next = 0; +} + +static inline list_elt* CListPop(list_elt* head) +{ + list_elt* it = CListBegin(head); + if(it != CListEnd(head)) + { + CListRemove(it); + return(it); + } + else + { + return(0); + } + +} + +static inline list_elt* CListPopBack(list_elt* head) +{ + list_elt* it = CListLast(head); + if(it != CListEnd(head)) + { + CListRemove(it); + return(it); + } + else + { + return(0); + } +} + +static inline bool CListEmpty(list_elt* head) +{ + return(head->next == 0 || head->next == head); +} + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif //__CONTAINERS_H_ diff --git a/src/util/macro_helpers.h b/src/util/macro_helpers.h new file mode 100644 index 0000000..a38c0b3 --- /dev/null +++ b/src/util/macro_helpers.h @@ -0,0 +1,129 @@ +/************************************************************//** +* +* @file: macro_helpers.h +* @author: Martin Fouilleul +* @date: 27/03/2020 +* @revision: +* +*****************************************************************/ +#ifndef __MACRO_HELPERS_H_ +#define __MACRO_HELPERS_H_ + +//NOTE(martin): macro concatenation +#define _cat2_(a, b) a##b +#define _cat3_(a, b, c) a##b##c + + +//NOTE(martin): inline, but still generate code +// (eg. use the inline version inside a library, but still exports the function for client code) +//TODO(martin): this is a compiler-specific attribute, recognized by clang and gcc. See if there's a more portable approach +//#define INLINE_GEN __attribute__((used)) static inline + + +//NOTE(martin): typed and array mallocs +#define malloc_type(type) ((type*)malloc(sizeof(type))) +#define malloc_array(type, count) ((type*)malloc(sizeof(type)*count)) + +//NOTE(martin): 'hygienic' templates, to replace macros and avoid multiple evaluation problems. +#ifdef __cplusplus + //NOTE(martin): in C++ we use templates and decltype/declval + // (overloaded functions would be ambiguous because of the + // overload resolution and conversion/promotion rules) + + #include + + template + inline decltype(std::declval()+std::declval()) minimum_safe(Ta a, Tb b) + { + return(a < b ? a : b); + } + + template + inline decltype(std::declval()+std::declval()) maximum_safe(Ta a, Tb b) + { + return(a > b ? a : b); + } + + template + inline T square_safe(T a) {return(a*a);} + + template + inline T cube_safe(T a) {return(a*a*a);} + +#else // (__cplusplus not defined) + + //NOTE(martin): Type generic arithmetic functions helpers + // this macros helps generate variants of a generic 'template' for all arithmetic types. + // the def parameter must be a macro that take a type, and optional arguments + #define tga_generate_variants(def, ...) \ + def(u8, ##__VA_ARGS__) def(i8, ##__VA_ARGS__ ) def(u16, ##__VA_ARGS__) def(i16, ##__VA_ARGS__) \ + def(u32, ##__VA_ARGS__) def(i32, ##__VA_ARGS__) def(u64, ##__VA_ARGS__) def(i64, ##__VA_ARGS__) \ + def(f32, ##__VA_ARGS__) def(f64, ##__VA_ARGS__) + + // This macro generates the name of a typed variant + #define tga_variant_name(name, type) _cat3_(name, _, type) + + // This macro generates a _Generic association between a type and its variant + #define tga_variant_association(type, name) , type: tga_variant_name(name, type) + + // This macros selects the appropriate variant for a 2 parameters functions + #define tga_select_binary(name, a, b) \ + _Generic((a+b) tga_generate_variants(tga_variant_association, name))(a, b) + + // This macros selects the appropriate variant for a 1 parameters functions + #define tga_select_unary(name, a) \ + _Generic((a) tga_generate_variants(tga_variant_association, name))(a) + + //NOTE(martin): type generic templates + #define minimum_def(type) static inline type tga_variant_name(minimum_safe, type)(type a, type b) {return(a < b ? a : b);} + #define maximum_def(type) static inline type tga_variant_name(maximum_safe, type)(type a, type b) {return(a > b ? a : b);} + #define square_def(type) static inline type tga_variant_name(square_safe, type)(type a) {return(a*a);} + #define cube_def(type) static inline type tga_variant_name(cube_safe, type)(type a) {return(a*a*a);} + + //NOTE(martin): instantiante our templates for all arithmetic types + tga_generate_variants(minimum_def) + tga_generate_variants(maximum_def) + tga_generate_variants(square_def) + tga_generate_variants(cube_def) + + //NOTE(martin): select the correct variant according to the argument types + #define minimum_safe(a, b) tga_select_binary(minimum_safe, a, b) + #define maximum_safe(a, b) tga_select_binary(maximum_safe, a, b) + #define square_safe(a) tga_select_unary(square_safe, a) + #define cube_safe(a) tga_select_unary(cube_safe, a) + +#endif // __cplusplus else branch + + +//NOTE(martin): these macros are calling the safe functions defined above, so they don't evaluate their +// arguments twice + +#define minimum(a, b) minimum_safe(a, b) +#define maximum(a, b) maximum_safe(a, b) + +#define ClampLowBound(a, low) (maximum((a), (low))) +#define ClampHighBound(a, high) (minimum((a), (high))) +#define Clamp(a, low, high) (ClampLowBound(ClampHighBound((a), (high)), (low))) + +#define Square(a) square_safe(a) +#define Cube(a) cube_safe(a) + +#define AlignUpOnPow2(x, a) (((x) + (a) - 1) & ~((a)-1)) +#define AlignDownOnPow2(x, a) ((x) & ~((a)-1)) + +static inline u64 next_pow2_u64(u64 x) +{ + x--; + x |= x>>1; + x |= x>>2; + x |= x>>4; + x |= x>>8; + x |= x>>16; + x |= x>>32; + x++; + return(x); +} + +#define defer_loop(begin, end) begin; for(int __i__=0; __i__<1; __i__++, end) + +#endif //__MACRO_HELPERS_H_ diff --git a/src/util/memory.c b/src/util/memory.c new file mode 100644 index 0000000..25b0157 --- /dev/null +++ b/src/util/memory.c @@ -0,0 +1,129 @@ +/************************************************************//** +* +* @file: memory.c +* @author: Martin Fouilleul +* @date: 24/10/2019 +* @revision: +* +*****************************************************************/ +#include // memset + +#include"memory.h" +#include"platform_base_allocator.h" +#include"macro_helpers.h" + +static const u32 MEM_ARENA_DEFAULT_RESERVE_SIZE = 1<<30; +static const u32 MEM_ARENA_COMMIT_ALIGNMENT = 1<<20; + +//-------------------------------------------------------------------------------- +//NOTE(martin): memory arena +//-------------------------------------------------------------------------------- +void mem_arena_init(mem_arena* arena) +{ + mem_arena_init_with_options(arena, &(mem_arena_options){}); +} + +void mem_arena_init_with_options(mem_arena* arena, mem_arena_options* options) +{ + arena->base = options->base ? options->base : mem_base_allocator_default(); + arena->cap = options->reserve ? options->reserve : MEM_ARENA_DEFAULT_RESERVE_SIZE; + + arena->ptr = mem_base_reserve(arena->base, arena->cap); + arena->committed = 0; + arena->offset = 0; +} + +void mem_arena_release(mem_arena* arena) +{ + mem_base_release(arena->base, arena->ptr, arena->cap); + memset(arena, 0, sizeof(mem_arena)); +} + +void* mem_arena_alloc(mem_arena* arena, u64 size) +{ + u64 nextOffset = arena->offset + size; + ASSERT(nextOffset <= arena->cap); + + if(nextOffset > arena->committed) + { + u64 nextCommitted = AlignUpOnPow2(nextOffset, MEM_ARENA_COMMIT_ALIGNMENT); + nextCommitted = ClampHighBound(nextCommitted, arena->cap); + u64 commitSize = nextCommitted - arena->committed; + mem_base_commit(arena->base, arena->ptr + arena->committed, commitSize); + arena->committed = nextCommitted; + } + char* p = arena->ptr + arena->offset; + arena->offset += size; + + return(p); +} + +void mem_arena_clear(mem_arena* arena) +{ + arena->offset = 0; +} + +//-------------------------------------------------------------------------------- +//NOTE(martin): memory pool +//-------------------------------------------------------------------------------- +void mem_pool_init(mem_pool* pool, u64 blockSize) +{ + mem_pool_init_with_options(pool, blockSize, &(mem_pool_options){}); +} +void mem_pool_init_with_options(mem_pool* pool, u64 blockSize, mem_pool_options* options) +{ + mem_arena_init_with_options(&pool->arena, &(mem_arena_options){.base = options->base, .reserve = options->reserve}); + pool->blockSize = ClampLowBound(blockSize, sizeof(list_info)); + ListInit(&pool->freeList); +} + +void mem_pool_release(mem_pool* pool) +{ + mem_arena_release(&pool->arena); + memset(pool, 0, sizeof(mem_pool)); +} + +void* mem_pool_alloc(mem_pool* pool) +{ + if(ListEmpty(&pool->freeList)) + { + return(mem_arena_alloc(&pool->arena, pool->blockSize)); + } + else + { + return(ListPop(&pool->freeList)); + } +} + +void mem_pool_recycle(mem_pool* pool, void* ptr) +{ + ASSERT((((char*)ptr) >= pool->arena.ptr) && (((char*)ptr) < (pool->arena.ptr + pool->arena.offset))); + ListPush(&pool->freeList, (list_elt*)ptr); +} + +void mem_pool_clear(mem_pool* pool) +{ + mem_arena_clear(&pool->arena); + ListInit(&pool->freeList); +} + + +//-------------------------------------------------------------------------------- +//NOTE(martin): per-thread scratch arena +//-------------------------------------------------------------------------------- + +__thread mem_arena __scratchArena = {}; + +mem_arena* mem_scratch() +{ + if(__scratchArena.ptr == 0) + { + mem_arena_init(&__scratchArena); + } + return(&__scratchArena); +} + +void mem_scratch_clear() +{ + mem_arena_clear(mem_scratch()); +} diff --git a/src/util/memory.h b/src/util/memory.h new file mode 100644 index 0000000..4e04e16 --- /dev/null +++ b/src/util/memory.h @@ -0,0 +1,106 @@ +/************************************************************//** +* +* @file: memory.h +* @author: Martin Fouilleul +* @date: 24/10/2019 +* @revision: +* +*****************************************************************/ +#ifndef __MEMORY_H_ +#define __MEMORY_H_ + +#include"typedefs.h" +#include"lists.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//-------------------------------------------------------------------------------- +//NOTE(martin): base allocator +//-------------------------------------------------------------------------------- +typedef void*(*mem_reserve_function)(void* context, u64 size); +typedef void(*mem_modify_function)(void* context, void* ptr, u64 size); + +typedef struct mem_base_allocator +{ + mem_reserve_function reserve; + mem_modify_function commit; + mem_modify_function decommit; + mem_modify_function release; + void* context; + +} mem_base_allocator; + +mem_base_allocator* mem_base_allocator_default(); + +#define mem_base_reserve(base, size) base->reserve(base->context, size) +#define mem_base_commit(base, ptr, size) base->commit(base->context, ptr, size) +#define mem_base_decommit(base, ptr, size) base->decommit(base->context, ptr, size) +#define mem_base_release(base, ptr, size) base->release(base->context, ptr, size) + +//-------------------------------------------------------------------------------- +//NOTE(martin): memory arena +//-------------------------------------------------------------------------------- +typedef struct mem_arena +{ + mem_base_allocator* base; + char* ptr; + u64 offset; + u64 committed; + u64 cap; +} mem_arena; + +typedef struct mem_arena_options +{ + mem_base_allocator* base; + u64 reserve; +} mem_arena_options; + +void mem_arena_init(mem_arena* arena); +void mem_arena_init_with_options(mem_arena* arena, mem_arena_options* options); +void mem_arena_release(mem_arena* arena); + +void* mem_arena_alloc(mem_arena* arena, u64 size); +void mem_arena_clear(mem_arena* arena); + +#define mem_arena_alloc_type(arena, type) ((type*)mem_arena_alloc(arena, sizeof(type))) +#define mem_arena_alloc_array(arena, type, count) ((type*)mem_arena_alloc(arena, sizeof(type)*(count))) + +//-------------------------------------------------------------------------------- +//NOTE(martin): memory pool +//-------------------------------------------------------------------------------- +typedef struct mem_pool +{ + mem_arena arena; + list_info freeList; + u64 blockSize; +} mem_pool; + +typedef struct mem_pool_options +{ + mem_base_allocator* base; + u64 reserve; +} mem_pool_options; + +void mem_pool_init(mem_pool* pool, u64 blockSize); +void mem_pool_init_with_options(mem_pool* pool, u64 blockSize, mem_pool_options* options); +void mem_pool_release(mem_pool* pool); + +void* mem_pool_alloc(mem_pool* pool); +void mem_pool_recycle(mem_pool* pool, void* ptr); +void mem_pool_clear(mem_pool* pool); + +#define mem_pool_alloc_type(arena, type) ((type*)mem_pool_alloc(arena)) + +//-------------------------------------------------------------------------------- +//NOTE(martin): per-thread implicit scratch arena +//-------------------------------------------------------------------------------- +void mem_scratch_clear(); +mem_arena* mem_scratch(); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif //__MEMORY_H_ diff --git a/src/util/ringbuffer.c b/src/util/ringbuffer.c new file mode 100644 index 0000000..6252063 --- /dev/null +++ b/src/util/ringbuffer.c @@ -0,0 +1,94 @@ +/************************************************************//** +* +* @file: ringbuffer.cpp +* @author: Martin Fouilleul +* @date: 31/07/2022 +* @revision: +* +*****************************************************************/ +#include // malloc, free +#include"ringbuffer.h" + +void ringbuffer_init(ringbuffer* ring, u8 capExp) +{ + u32 cap = 1<mask = cap - 1; + ring->readIndex = 0; + ring->writeIndex = 0; + ring->buffer = (u8*)malloc(cap); +} + +void ringbuffer_cleanup(ringbuffer* ring) +{ + free(ring->buffer); +} + +u32 ringbuffer_read_available(ringbuffer* ring) +{ + return((ring->writeIndex - ring->readIndex) & ring->mask); +} + +u32 ringbuffer_write_available(ringbuffer* ring) +{ + //NOTE(martin): we keep one sentinel byte between write index and read index, + // when the buffer is full, to avoid overrunning read index. + // Hence, available write space is size - 1 - available read space. + return(ring->mask - ringbuffer_read_available(ring)); +} + +u32 ringbuffer_write(ringbuffer* ring, u32 size, u8* data) +{ + u32 read = ring->readIndex; + u32 write = ring->writeIndex; + + u32 writeAvailable = ringbuffer_write_available(ring); + if(size > writeAvailable) + { + DEBUG_ASSERT("not enough space available"); + size = writeAvailable; + } + + if(read <= write) + { + u32 copyCount = minimum(size, ring->mask + 1 - write); + memcpy(ring->buffer + write, data, copyCount); + + data += copyCount; + copyCount = size - copyCount; + memcpy(ring->buffer, data, copyCount); + } + else + { + memcpy(ring->buffer + write, data, size); + } + ring->writeIndex = (write + size) & ring->mask; + return(size); +} + +u32 ringbuffer_read(ringbuffer* ring, u32 size, u8* data) +{ + u32 read = ring->readIndex; + u32 write = ring->writeIndex; + + u32 readAvailable = ringbuffer_read_available(ring); + if(size > readAvailable) + { + size = readAvailable; + } + + if(read <= write) + { + memcpy(data, ring->buffer + read, size); + } + else + { + u32 copyCount = minimum(size, ring->mask + 1 - read); + memcpy(data, ring->buffer + read, copyCount); + + data += copyCount; + copyCount = size - copyCount; + memcpy(data, ring->buffer, copyCount); + } + ring->readIndex = (read + size) & ring->mask; + return(size); +} diff --git a/src/util/ringbuffer.h b/src/util/ringbuffer.h new file mode 100644 index 0000000..a35740f --- /dev/null +++ b/src/util/ringbuffer.h @@ -0,0 +1,40 @@ +/************************************************************//** +* +* @file: ringbuffer.h +* @author: Martin Fouilleul +* @date: 31/07/2022 +* @revision: +* +*****************************************************************/ +#ifndef __RINGBUFFER_H_ +#define __RINGBUFFER_H_ + +#include +#include"typedefs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ringbuffer +{ + u32 mask; + _Atomic(u32) readIndex; + _Atomic(u32) writeIndex; + + u8* buffer; + +} ringbuffer; + +void ringbuffer_init(ringbuffer* ring, u8 capExp); +void ringbuffer_cleanup(ringbuffer* ring); +u32 ringbuffer_read_available(ringbuffer* ring); +u32 ringbuffer_write_available(ringbuffer* ring); +u32 ringbuffer_write(ringbuffer* ring, u32 size, u8* data); +u32 ringbuffer_read(ringbuffer* ring, u32 size, u8* data); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif //__RINGBUFFER_H_ diff --git a/src/util/strings.c b/src/util/strings.c new file mode 100644 index 0000000..fa8261d --- /dev/null +++ b/src/util/strings.c @@ -0,0 +1,298 @@ +/************************************************************//** +* +* @file: strings.c +* @author: Martin Fouilleul +* @date: 29/05/2021 +* @revision: +* +*****************************************************************/ +#include // strlen(), strcpy(), etc. +#include // va_list() etc. +#include"debug_log.h" +#include"strings.h" + +#define LOG_SUBSYSTEM "Strings" + +//---------------------------------------------------------------------------------- +// string slices as values +//---------------------------------------------------------------------------------- + +str8 str8_from_buffer(u64 len, char* buffer) +{ + return((str8){.len = len, .ptr = buffer}); +} + +str8 str8_from_cstring(char* str) +{ + return(str8_from_buffer(strlen(str), (char*)str)); +} + +str8 str8_slice(str8 s, u64 start, u64 end) +{ + ASSERT(start <= end && start <= s.len && end <= s.len); + return((str8){.len = end - start, .ptr = s.ptr + start}); +} + +str8 str8_push_buffer(mem_arena* arena, u64 len, char* buffer) +{ + str8 str = {}; + str.len = len; + str.ptr = mem_arena_alloc_array(arena, char, len); + memcpy(str.ptr, buffer, len); + return(str); +} + +str8 str8_push_cstring(mem_arena* arena, const char* str) +{ + return(str8_push_buffer(arena, strlen(str), (char*)str)); +} + +str8 str8_push_copy(mem_arena* arena, str8 s) +{ + return(str8_push_buffer(arena, str8_unbox(s))); +} + +str8 str8_push_slice(mem_arena* arena, str8 s, u64 start, u64 end) +{ + str8 slice = str8_slice(s, start, end); + return(str8_push_copy(arena, slice)); +} + +str8 str8_pushfv(mem_arena* arena, const char* format, va_list args) +{ + //NOTE(martin): + // We first compute the number of characters to write passing a size of 0. + // then we allocate len+1 (since vsnprint always terminates with a '\0'). + // We could call vsprintf since we know the size, but I'm not sure there's a hard + // guarantee that vsprintf and vsnprintf write the same number of characters in all + // and every case, and that would be a difficult bug to spot, so it seems better to + // waste one byte and be safe. + char dummy; + str8 str = {}; + va_list argCopy; + va_copy(argCopy, args); + str.len = vsnprintf(&dummy, 0, format, argCopy); + va_end(argCopy); + + str.ptr = mem_arena_alloc_array(arena, char, str.len + 1); + vsnprintf((char*)str.ptr, str.len + 1, format, args); + return(str); +} + +str8 str8_pushf(mem_arena* arena, const char* format, ...) +{ + va_list args; + va_start(args, format); + str8 str = str8_pushfv(arena, format, args); + va_end(args); + return(str); +} + +int str8_cmp(str8 s1, str8 s2) +{ + int res = strncmp(s1.ptr, s2.ptr, minimum(s1.len, s2.len)); + if(!res) + { + res = (s1.len < s2.len)? -1 : ((s1.len == s2.len)? 0 : 1); + } + return(res); +} + +char* str8_to_cstring(mem_arena* arena, str8 string) +{ + char* cstr = mem_arena_alloc_array(arena, char, string.len+1); + memcpy(cstr, string.ptr, string.len); + cstr[string.len] = '\0'; + return(cstr); +} + +//---------------------------------------------------------------------------------- +// string lists +//---------------------------------------------------------------------------------- + +void str8_list_init(str8_list* list) +{ + ListInit(&list->list); + list->eltCount = 0; + list->len = 0; +} + +void str8_list_push(mem_arena* arena, str8_list* list, str8 str) +{ + str8_elt* elt = mem_arena_alloc_type(arena, str8_elt); + elt->string = str; + ListAppend(&list->list, &elt->listElt); + list->eltCount++; + list->len += str.len; +} + +void str8_list_pushf(mem_arena* arena, str8_list* list, const char* format, ...) +{ + va_list args; + va_start(args, format); + str8 str = str8_pushfv(arena, format, args); + va_end(args); + str8_list_push(arena, list, str); +} + +str8 str8_list_collate(mem_arena* arena, str8_list list, str8 prefix, str8 separator, str8 postfix) +{ + str8 str = {}; + str.len = prefix.len + list.len + list.eltCount*separator.len + postfix.len; + str.ptr = mem_arena_alloc_array(arena, char, str.len); + char* dst = str.ptr; + memcpy(dst, prefix.ptr, prefix.len); + dst += prefix.len; + + str8_elt* elt = ListFirstEntry(&list.list, str8_elt, listElt); + if(elt) + { + memcpy(dst, elt->string.ptr, elt->string.len); + dst += elt->string.len; + elt = ListNextEntry(&list.list, elt, str8_elt, listElt); + } + + for( ; elt != 0; elt = ListNextEntry(&list.list, elt, str8_elt, listElt)) + { + memcpy(dst, separator.ptr, separator.len); + dst += separator.len; + memcpy(dst, elt->string.ptr, elt->string.len); + dst += elt->string.len; + } + memcpy(dst, postfix.ptr, postfix.len); + return(str); +} + +str8 str8_list_join(mem_arena* arena, str8_list list) +{ + str8 empty = {.len = 0, .ptr = 0}; + return(str8_list_collate(arena, list, empty, empty, empty)); +} + +str8_list str8_split(mem_arena* arena, str8 str, str8_list separators) +{ + str8_list list = {}; + ListInit(&list.list); + + char* ptr = str.ptr; + char* end = str.ptr + str.len; + char* subStart = ptr; + for(; ptr < end; ptr++) + { + //NOTE(martin): search all separators and try to match them to the current ptr + str8* foundSep = 0; + for_each_in_list(&separators.list, elt, str8_elt, listElt) + { + str8* separator = &elt->string; + bool equal = true; + for(u64 offset = 0; + (offset < separator->len) && (ptr+offset < end); + offset++) + { + if(separator->ptr[offset] != ptr[offset]) + { + equal = false; + break; + } + } + if(equal) + { + foundSep = separator; + break; + } + } + if(foundSep) + { + //NOTE(martin): we found a separator. If the start of the current substring is != ptr, + // the current substring is not empty and we emit the substring + if(ptr != subStart) + { + str8 sub = str8_from_buffer(ptr-subStart, subStart); + str8_list_push(arena, &list, sub); + } + ptr += foundSep->len - 1; //NOTE(martin): ptr is incremented at the end of the loop + subStart = ptr+1; + } + } + //NOTE(martin): emit the last substring + if(ptr != subStart) + { + str8 sub = str8_from_buffer(ptr-subStart, subStart); + str8_list_push(arena, &list, sub); + } + return(list); +} + +//---------------------------------------------------------------------------------- +// u32 strings +//---------------------------------------------------------------------------------- +str32 str32_from_buffer(u64 len, u32* buffer) +{ + return((str32){.len = len, .ptr = buffer}); +} + +str32 str32_slice(str32 s, u64 start, u64 end) +{ + ASSERT(start <= end && start <= s.len && end <= s.len); + return((str32){.len = end - start, .ptr = s.ptr + start}); +} + +str32 str32_push_buffer(mem_arena* arena, u64 len, u32* buffer) +{ + str32 str = {}; + str.len = len; + str.ptr = mem_arena_alloc_array(arena, u32, len); + memcpy(str.ptr, buffer, len*sizeof(u32)); + return(str); +} + +str32 str32_push_copy(mem_arena* arena, str32 s) +{ + return(str32_push_buffer(arena, s.len, s.ptr)); +} + +str32 str32_push_slice(mem_arena* arena, str32 s, u64 start, u64 end) +{ + str32 slice = str32_slice(s, start, end); + return(str32_push_copy(arena, slice)); +} + +//---------------------------------------------------------------------------------- +// Paths helpers +//---------------------------------------------------------------------------------- +str8 mp_path_directory(str8 fullPath) +{ + i64 lastSlashIndex = -1; + + for(i64 i = fullPath.len-1; i >= 0; i--) + { + if(fullPath.ptr[i] == '/') + { + lastSlashIndex = i; + break; + } + } + str8 directory = str8_slice(fullPath, 0, lastSlashIndex+1); + return(directory); +} + +str8 mp_path_base_name(str8 fullPath) +{ + i64 lastSlashIndex = -1; + + for(i64 i = fullPath.len-1; i >= 0; i--) + { + if(fullPath.ptr[i] == '/') + { + lastSlashIndex = i; + break; + } + } + + str8 basename = str8_slice(fullPath, lastSlashIndex+1, fullPath.len); + return(basename); +} + + + +#undef LOG_SUBSYSTEM diff --git a/src/util/strings.h b/src/util/strings.h new file mode 100644 index 0000000..612efa4 --- /dev/null +++ b/src/util/strings.h @@ -0,0 +1,96 @@ +/************************************************************//** +* +* @file: strings.h +* @author: Martin Fouilleul +* @date: 29/05/2021 +* @revision: +* +*****************************************************************/ +#ifndef __STRINGS_H_ +#define __STRINGS_H_ + +#include"typedefs.h" +#include"lists.h" +#include"memory.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//---------------------------------------------------------------------------------- +// string slices as values +//---------------------------------------------------------------------------------- +typedef struct str8 +{ + u64 len; + char* ptr; +} str8; + +#define str8_lit(s) ((str8){.len = sizeof(s)-1, .ptr = (char*)(s)}) +#define str8_unbox(s) (int)((s).len), ((s).ptr) + +str8 str8_from_buffer(u64 len, char* buffer); +str8 str8_from_cstring(char* str); +str8 str8_slice(str8 s, u64 start, u64 end); + +str8 str8_push_buffer(mem_arena* arena, u64 len, char* buffer); +str8 str8_push_cstring(mem_arena* arena, const char* str); +str8 str8_push_copy(mem_arena* arena, str8 s); +str8 str8_push_slice(mem_arena* arena, str8 s, u64 start, u64 end); + +str8 str8_pushfv(mem_arena* arena, const char* format, va_list args); +str8 str8_pushf(mem_arena* arena, const char* format, ...); + +int str8_cmp(str8 s1, str8 s2); + +char* str8_to_cstring(mem_arena* arena, str8 string); +//---------------------------------------------------------------------------------- +// string lists +//---------------------------------------------------------------------------------- +typedef struct str8_elt +{ + list_elt listElt; + str8 string; +} str8_elt; + +typedef struct str8_list +{ + list_info list; + u64 eltCount; + u64 len; +} str8_list; + +void str8_list_push(mem_arena* arena, str8_list* list, str8 str); +void str8_list_pushf(mem_arena* arena, str8_list* list, const char* format, ...); + +str8 str8_list_join(mem_arena* arena, str8_list list); +str8_list str8_split(mem_arena* arena, str8 str, str8_list separators); + +//---------------------------------------------------------------------------------- +// u32 strings +//---------------------------------------------------------------------------------- +typedef struct str32 +{ + u64 len; + u32* ptr; +} str32; + +str32 str32_from_buffer(u64 len, u32* buffer); +str32 str32_slice(str32 s, u64 start, u64 end); + +str32 str32_push_buffer(mem_arena* arena, u64 len, u32* buffer); +str32 str32_push_copy(mem_arena* arena, str32 s); +str32 str32_push_slice(mem_arena* arena, str32 s, u64 start, u64 end); + +//---------------------------------------------------------------------------------- +// Paths helpers +//---------------------------------------------------------------------------------- +str8 mp_path_directory(str8 fullPath); +str8 mp_path_base_name(str8 fullPath); + +#ifdef __cplusplus +} // extern "C" +#endif + + +#endif //__STRINGS_H_ diff --git a/src/util/typedefs.h b/src/util/typedefs.h new file mode 100644 index 0000000..b0cc630 --- /dev/null +++ b/src/util/typedefs.h @@ -0,0 +1,70 @@ +//***************************************************************** +// +// $file: typedefs.h $ +// $author: Martin Fouilleul $ +// $date: 23/36/2015 $ +// $revision: $ +// $note: (C) 2015 by Martin Fouilleul - all rights reserved $ +// +//***************************************************************** +#ifndef __TYPEDEFS_H_ +#define __TYPEDEFS_H_ + +#include +#include //FLT_MAX/MIN etc... + +#ifndef __cplusplus +#include +#endif //__cplusplus + +typedef uint8_t byte; +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +typedef int8_t i8; +typedef int16_t i16; +typedef int32_t i32; +typedef int64_t i64; + +typedef float f32; +typedef double f64; + +typedef union +{ + struct + { + f32 x; + f32 y; + }; + f32 c[2]; +} vec2; + +typedef union +{ + struct + { + f32 x; + f32 y; + f32 z; + f32 w; + }; + f32 c[4]; +} vec4; + +#define vec4_expand_xyz(v) (v).x, (v).y, (v).z + +typedef union +{ + struct + { + f32 x; + f32 y; + f32 w; + f32 h; + }; + f32 c[4]; +} mp_rect; + +#endif //__TYPEDEFS_H_ diff --git a/src/util/utf8.c b/src/util/utf8.c new file mode 100644 index 0000000..f0136df --- /dev/null +++ b/src/util/utf8.c @@ -0,0 +1,283 @@ +//***************************************************************** +// +// $file: utf8.c $ +// $author: Martin Fouilleul $ +// $date: 05/11/2016 $ +// $revision: $ +// $note: (C) 2016 by Martin Fouilleul - all rights reserved $ +// +//***************************************************************** +#include"utf8.h" +#include + +//----------------------------------------------------------------- +// utf-8 gore +//----------------------------------------------------------------- +const u_int32_t offsetsFromUTF8[6] = { + 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; + +const char trailingBytesForUTF8[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 +}; + +#define utf8_is_start_byte(c) (((c)&0xc0)!=0x80) + +//----------------------------------------------------------------- +//NOTE: getting sizes / offsets / indices +//----------------------------------------------------------------- + +u32 utf8_size_from_leading_char(char leadingChar) +{ + return(trailingBytesForUTF8[(unsigned int)(unsigned char)leadingChar] + 1); +} + +u32 utf8_codepoint_size(utf32 codePoint) +{ + if(codePoint < 0x80) + { + return(1); + } + if(codePoint < 0x800) + { + return(2); + } + if(codePoint < 0x10000) + { + return(3); + } + if(codePoint < 0x110000) + { + return(4); + } + return(0); +} + +u64 utf8_codepoint_count_for_string(str8 string) +{ + u64 byteOffset = 0; + u64 codePointIndex = 0; + for(; + (byteOffset < string.len) && (string.ptr[byteOffset] != 0); + codePointIndex++) + { + utf8_dec decode = utf8_decode_at(string, byteOffset); + byteOffset += decode.size; + } + return(codePointIndex); +} + +u64 utf8_byte_count_for_codepoints(str32 codePoints) +{ + //NOTE(martin): return the exact number of bytes taken by the encoded + // version of codePoints. (ie do not attempt to provision + // for a zero terminator). + u64 byteCount = 0; + for(u64 i=0; i= string.len) + { + res = string.len; + } + else + { + u64 nextOffset = byteOffset + utf8_size_from_leading_char(string.ptr[byteOffset]); + res = minimum(nextOffset, string.len); + } + return(res); +} + +u64 utf8_prev_offset(str8 string, u64 byteOffset) +{ + u64 res = 0; + if(byteOffset > string.len) + { + res = string.len; + } + else if(byteOffset) + { + byteOffset--; + while(byteOffset > 0 && !utf8_is_start_byte(string.ptr[byteOffset])) + { + byteOffset--; + } + res = byteOffset; + } + return(res); +} + +//----------------------------------------------------------------- +//NOTE: encoding / decoding +//----------------------------------------------------------------- + +utf8_dec utf8_decode_at(str8 string, u64 offset) +{ + //NOTE(martin): get the first codepoint in str, and advance index to the + // next utf8 character + //TODO(martin): check for utf-16 surrogate pairs + utf32 cp = 0; + u64 sz = 0; + + if(offset >= string.len || !string.ptr[offset]) + { + cp = 0; + sz = 1; + } + else if( !utf8_is_start_byte(string.ptr[offset])) + { + //NOTE(martin): unexpected continuation or invalid character. + cp = 0xfffd; + sz = 1; + } + else + { + int expectedSize = utf8_size_from_leading_char(string.ptr[offset]); + do + { + /*NOTE(martin): + we shift 6 bits and add the next byte at each round. + at the end we have our utf8 codepoint, added to the shifted versions + of the utf8 leading bits for each encoded byte. These values are + precomputed in offsetsFromUTF8. + */ + unsigned char b = string.ptr[offset]; + cp <<= 6; + cp += b; + offset += 1; + sz++; + + if(b == 0xc0 || b == 0xc1 || b >= 0xc5) + { + //NOTE(martin): invalid byte encountered + break; + } + + } while( offset < string.len + && string.ptr[offset] + && !utf8_is_start_byte(string.ptr[offset]) + && sz < expectedSize); + + if(sz != expectedSize) + { + //NOTE(martin): if we encountered an error, we return the replacement codepoint U+FFFD + cp = 0xfffd; + } + else + { + cp -= offsetsFromUTF8[sz-1]; + + //NOTE(martin): check for invalid codepoints + if(cp > 0x10ffff || (cp >= 0xd800 && cp <= 0xdfff)) + { + cp = 0xfffd; + } + } + } + utf8_dec res = {.codepoint = cp, .size = sz}; + return(res); +} + +utf8_dec utf8_decode(str8 string) +{ + return(utf8_decode_at(string, 0)); +} + +str8 utf8_encode(char* dest, utf32 codePoint) +{ + u64 sz = 0; + if (codePoint < 0x80) + { + dest[0] = (char)codePoint; + sz = 1; + } + else if (codePoint < 0x800) + { + dest[0] = (codePoint>>6) | 0xC0; + dest[1] = (codePoint & 0x3F) | 0x80; + sz = 2; + } + else if (codePoint < 0x10000) + { + dest[0] = (codePoint>>12) | 0xE0; + dest[1] = ((codePoint>>6) & 0x3F) | 0x80; + dest[2] = (codePoint & 0x3F) | 0x80; + sz = 3; + } + else if (codePoint < 0x110000) + { + dest[0] = (codePoint>>18) | 0xF0; + dest[1] = ((codePoint>>12) & 0x3F) | 0x80; + dest[2] = ((codePoint>>6) & 0x3F) | 0x80; + dest[3] = (codePoint & 0x3F) | 0x80; + sz = 4; + } + str8 res = {.len = sz, .ptr = dest}; + return(res); +} + +str32 utf8_to_codepoints(u64 maxCount, utf32* backing, str8 string) +{ + u64 codePointIndex = 0; + u64 byteOffset = 0; + for(; codePointIndex < maxCount && byteOffset < string.len; codePointIndex++) + { + utf8_dec decode = utf8_decode_at(string, byteOffset); + backing[codePointIndex] = decode.codepoint; + byteOffset += decode.size; + } + str32 res = {.len = codePointIndex, .ptr = backing}; + return(res); +} + +str8 utf8_from_codepoints(u64 maxBytes, char* backing, str32 codePoints) +{ + u64 byteOffset = 0; + for(u64 codePointIndex = 0; (codePointIndex < codePoints.len); codePointIndex++) + { + utf32 codePoint = codePoints.ptr[codePointIndex]; + u32 byteCount = utf8_codepoint_size(codePoint); + if(byteOffset + byteCount > maxBytes) + { + break; + } + utf8_encode(backing+byteOffset, codePoint); + byteOffset += byteCount; + } + str8 res = {.len = byteOffset, .ptr = backing}; + return(res); +} + +str32 utf8_push_to_codepoints(mem_arena* arena, str8 string) +{ + u64 count = utf8_codepoint_count_for_string(string); + utf32* backing = mem_arena_alloc_array(arena, utf32, count); + str32 res = utf8_to_codepoints(count, backing, string); + return(res); +} + +str8 utf8_push_from_codepoints(mem_arena* arena, str32 codePoints) +{ + u64 count = utf8_byte_count_for_codepoints(codePoints); + char* backing = mem_arena_alloc_array(arena, char, count); + str8 res = utf8_from_codepoints(count, backing, codePoints); + return(res); +} + +#define UNICODE_RANGE(start, cnt, name) const unicode_range _cat2_(UNICODE_RANGE_, name) = { .firstCodePoint = start, .count = cnt }; +UNICODE_RANGES +#undef UNICODE_RANGE diff --git a/src/util/utf8.h b/src/util/utf8.h new file mode 100644 index 0000000..06fd1c8 --- /dev/null +++ b/src/util/utf8.h @@ -0,0 +1,201 @@ +//***************************************************************** +// +// $file: utf8.h $ +// $author: Martin Fouilleul $ +// $date: 05/11/2016 $ +// $revision: $ +// $note: (C) 2016 by Martin Fouilleul - all rights reserved $ +// +//***************************************************************** +#ifndef __UTF8_H_ +#define __UTF8_H_ + +#include"typedefs.h" +#include"macro_helpers.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef u32 utf32; + +//----------------------------------------------------------------- +//NOTE: getting sizes / offsets / indices +//----------------------------------------------------------------- +u32 utf8_size_from_leading_char(char leadingChar); +u32 utf8_codepoint_size(utf32 codePoint); + +u64 utf8_codepoint_count_for_string(str8 string); +u64 utf8_byte_count_for_codepoints(str32 codePoints); + +u64 utf8_next_offset(str8 string, u64 byteOffset); +u64 utf8_prev_offset(str8 string, u64 byteOffset); + +//----------------------------------------------------------------- +//NOTE: encoding / decoding +//----------------------------------------------------------------- +typedef struct utf8_dec +{ + utf32 codepoint; //NOTE: decoded codepoint + u32 size; //NOTE: size of corresponding utf8 sequence +} utf8_dec; + +utf8_dec utf8_decode(str8 string); //NOTE: decode a single utf8 sequence at start of string +utf8_dec utf8_decode_at(str8 string, u64 offset); //NOTE: decode a single utf8 sequence starting at byte offset +str8 utf8_encode(char* dst, utf32 codePoint); //NOTE: encode codepoint into backing buffer dst + +str32 utf8_to_codepoints(u64 maxCount, utf32* backing, str8 string); +str8 utf8_from_codepoints(u64 maxBytes, char* backing, str32 codePoints); + +str32 utf8_push_to_codepoints(mem_arena* arena, str8 string); +str8 utf8_push_from_codepoints(mem_arena* arena, str32 codePoints); + +//----------------------------------------------------------------- +// utf8 range struct and X-macros for defining utf8 ranges +//----------------------------------------------------------------- + +typedef struct unicode_range +{ + utf32 firstCodePoint; + u32 count; +} unicode_range; + +//NOTE(martin): range declared here are defined in utf8.cpp +// they can be used by prefixing them with UTF8_RANGE_, as in 'UTF8_RANGE_BASIC_LATIN' +#define UNICODE_RANGES \ +UNICODE_RANGE(0x0000, 127, BASIC_LATIN) \ +UNICODE_RANGE(0x0080, 127, C1_CONTROLS_AND_LATIN_1_SUPPLEMENT) \ +UNICODE_RANGE(0x0100, 127, LATIN_EXTENDED_A) \ +UNICODE_RANGE(0x0180, 207, LATIN_EXTENDED_B) \ +UNICODE_RANGE(0x0250, 95, IPA_EXTENSIONS) \ +UNICODE_RANGE(0x02b0, 79, SPACING_MODIFIER_LETTERS) \ +UNICODE_RANGE(0x0300, 111, COMBINING_DIACRITICAL_MARKS) \ +UNICODE_RANGE(0x0370, 143, GREEK_COPTIC) \ +UNICODE_RANGE(0x0400, 255, CYRILLIC) \ +UNICODE_RANGE(0x0500, 47, CYRILLIC_SUPPLEMENT) \ +UNICODE_RANGE(0x0530, 95, ARMENIAN) \ +UNICODE_RANGE(0x0590, 111, HEBREW) \ +UNICODE_RANGE(0x0600, 255, ARABIC) \ +UNICODE_RANGE(0x0700, 79, SYRIAC) \ +UNICODE_RANGE(0x0780, 63, THAANA) \ +UNICODE_RANGE(0x0900, 127, DEVANAGARI) \ +UNICODE_RANGE(0x0980, 127, BENGALI_ASSAMESE) \ +UNICODE_RANGE(0x0a00, 127, GURMUKHI) \ +UNICODE_RANGE(0x0a80, 127, GUJARATI) \ +UNICODE_RANGE(0x0b00, 127, ORIYA) \ +UNICODE_RANGE(0x0b80, 127, TAMIL) \ +UNICODE_RANGE(0x0c00, 127, TELUGU) \ +UNICODE_RANGE(0x0c80, 127, KANNADA) \ +UNICODE_RANGE(0x0d00, 255, MALAYALAM) \ +UNICODE_RANGE(0x0d80, 127, SINHALA) \ +UNICODE_RANGE(0x0e00, 127, THAI) \ +UNICODE_RANGE(0x0e80, 127, LAO) \ +UNICODE_RANGE(0x0f00, 255, TIBETAN) \ +UNICODE_RANGE(0x1000, 159, MYANMAR) \ +UNICODE_RANGE(0x10a0, 95, GEORGIAN) \ +UNICODE_RANGE(0x1100, 255, HANGUL_JAMO) \ +UNICODE_RANGE(0x1200, 383, ETHIOPIC) \ +UNICODE_RANGE(0x13a0, 95, CHEROKEE) \ +UNICODE_RANGE(0x1400, 639, UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS) \ +UNICODE_RANGE(0x1680, 31, OGHAM) \ +UNICODE_RANGE(0x16a0, 95, RUNIC) \ +UNICODE_RANGE(0x1700, 31, TAGALOG) \ +UNICODE_RANGE(0x1720, 31, HANUNOO) \ +UNICODE_RANGE(0x1740, 31, BUHID) \ +UNICODE_RANGE(0x1760, 31, TAGBANWA) \ +UNICODE_RANGE(0x1780, 127, KHMER) \ +UNICODE_RANGE(0x1800, 175, MONGOLIAN) \ +UNICODE_RANGE(0x1900, 79, LIMBU) \ +UNICODE_RANGE(0x1950, 47, TAI_LE) \ +UNICODE_RANGE(0x19e0, 31, KHMER_SYMBOLS) \ +UNICODE_RANGE(0x1d00, 127, PHONETIC_EXTENSIONS) \ +UNICODE_RANGE(0x1e00, 255, LATIN_EXTENDED_ADDITIONAL) \ +UNICODE_RANGE(0x1f00, 255, GREEK_EXTENDED) \ +UNICODE_RANGE(0x2000, 111, GENERAL_PUNCTUATION) \ +UNICODE_RANGE(0x2070, 47, SUPERSCRIPTS_AND_SUBSCRIPTS) \ +UNICODE_RANGE(0x20a0, 47, CURRENCY_SYMBOLS) \ +UNICODE_RANGE(0x20d0, 47, COMBINING_DIACRITICAL_MARKS_FOR_SYMBOLS) \ +UNICODE_RANGE(0x2100, 79, LETTERLIKE_SYMBOLS) \ +UNICODE_RANGE(0x2150, 63, NUMBER_FORMS) \ +UNICODE_RANGE(0x2190, 111, ARROWS) \ +UNICODE_RANGE(0x2200, 255, MATHEMATICAL_OPERATORS) \ +UNICODE_RANGE(0x2300, 255, MISCELLANEOUS_TECHNICAL) \ +UNICODE_RANGE(0x2400, 63, CONTROL_PICTURES) \ +UNICODE_RANGE(0x2440, 31, OPTICAL_CHARACTER_RECOGNITION) \ +UNICODE_RANGE(0x2460, 159, ENCLOSED_ALPHANUMERICS) \ +UNICODE_RANGE(0x2500, 127, BOX_DRAWING) \ +UNICODE_RANGE(0x2580, 31, BLOCK_ELEMENTS) \ +UNICODE_RANGE(0x25a0, 95, GEOMETRIC_SHAPES) \ +UNICODE_RANGE(0x2600, 255, MISCELLANEOUS_SYMBOLS) \ +UNICODE_RANGE(0x2700, 191, DINGBATS) \ +UNICODE_RANGE(0x27c0, 47, MISCELLANEOUS_MATHEMATICAL_SYMBOLS_A) \ +UNICODE_RANGE(0x27f0, 15, SUPPLEMENTAL_ARROWS_A) \ +UNICODE_RANGE(0x2800, 255, BRAILLE_PATTERNS) \ +UNICODE_RANGE(0x2900, 127, SUPPLEMENTAL_ARROWS_B) \ +UNICODE_RANGE(0x2980, 127, MISCELLANEOUS_MATHEMATICAL_SYMBOLS_B) \ +UNICODE_RANGE(0x2a00, 255, SUPPLEMENTAL_MATHEMATICAL_OPERATORS) \ +UNICODE_RANGE(0x2b00, 255, MISCELLANEOUS_SYMBOLS_AND_ARROWS) \ +UNICODE_RANGE(0x2e80, 127, CJK_RADICALS_SUPPLEMENT) \ +UNICODE_RANGE(0x2f00, 223, KANGXI_RADICALS) \ +UNICODE_RANGE(0x2ff0, 15, IDEOGRAPHIC_DESCRIPTION_CHARACTERS) \ +UNICODE_RANGE(0x3000, 63, CJK_SYMBOLS_AND_PUNCTUATION) \ +UNICODE_RANGE(0x3040, 95, HIRAGANA) \ +UNICODE_RANGE(0x30a0, 95, KATAKANA) \ +UNICODE_RANGE(0x3100, 47, BOPOMOFO) \ +UNICODE_RANGE(0x3130, 95, HANGUL_COMPATIBILITY_JAMO) \ +UNICODE_RANGE(0x3190, 15, KANBUN_KUNTEN) \ +UNICODE_RANGE(0x31a0, 31, BOPOMOFO_EXTENDED) \ +UNICODE_RANGE(0x31f0, 15, KATAKANA_PHONETIC_EXTENSIONS) \ +UNICODE_RANGE(0x3200, 255, ENCLOSED_CJK_LETTERS_AND_MONTHS) \ +UNICODE_RANGE(0x3300, 255, CJK_COMPATIBILITY) \ +UNICODE_RANGE(0x3400, 6591, CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A) \ +UNICODE_RANGE(0x4dc0, 63, YIJING_HEXAGRAM_SYMBOLS) \ +UNICODE_RANGE(0x4e00, 20911, CJK_UNIFIED_IDEOGRAPHS) \ +UNICODE_RANGE(0xa000, 1167, YI_SYLLABLES) \ +UNICODE_RANGE(0xa490, 63, YI_RADICALS) \ +UNICODE_RANGE(0xac00, 11183, HANGUL_SYLLABLES) \ +UNICODE_RANGE(0xd800, 1023, HIGH_SURROGATE_AREA) \ +UNICODE_RANGE(0xdc00, 1023, LOW_SURROGATE_AREA) \ +UNICODE_RANGE(0xe000, 6399, PRIVATE_USE_AREA) \ +UNICODE_RANGE(0xf900, 511, CJK_COMPATIBILITY_IDEOGRAPHS) \ +UNICODE_RANGE(0xfb00, 79, ALPHABETIC_PRESENTATION_FORMS) \ +UNICODE_RANGE(0xfb50, 687, ARABIC_PRESENTATION_FORMS_A) \ +UNICODE_RANGE(0xfe00, 15, VARIATION_SELECTORS) \ +UNICODE_RANGE(0xfe20, 15, COMBINING_HALF_MARKS) \ +UNICODE_RANGE(0xfe30, 31, CJK_COMPATIBILITY_FORMS) \ +UNICODE_RANGE(0xfe50, 31, SMALL_FORM_VARIANTS) \ +UNICODE_RANGE(0xfe70, 143, ARABIC_PRESENTATION_FORMS_B) \ +UNICODE_RANGE(0xff00, 239, HALFWIDTH_AND_FULLWIDTH_FORMS) \ +UNICODE_RANGE(0xfff0, 15, SPECIALS) \ +UNICODE_RANGE(0x10000, 127, LINEAR_B_SYLLABARY) \ +UNICODE_RANGE(0x10080, 127, LINEAR_B_IDEOGRAMS) \ +UNICODE_RANGE(0x10100, 63, AEGEAN_NUMBERS) \ +UNICODE_RANGE(0x10300, 47, OLD_ITALIC) \ +UNICODE_RANGE(0x10330, 31, GOTHIC) \ +UNICODE_RANGE(0x10380, 31, UGARITIC) \ +UNICODE_RANGE(0x10400, 79, DESERET) \ +UNICODE_RANGE(0x10450, 47, SHAVIAN) \ +UNICODE_RANGE(0x10480, 47, OSMANYA) \ +UNICODE_RANGE(0x10800, 63, CYPRIOT_SYLLABARY) \ +UNICODE_RANGE(0x1d000, 255, BYZANTINE_MUSICAL_SYMBOLS) \ +UNICODE_RANGE(0x1d100, 255, MUSICAL_SYMBOLS) \ +UNICODE_RANGE(0x1d300, 95, TAI_XUAN_JING_SYMBOLS) \ +UNICODE_RANGE(0x1d400, 1023, MATHEMATICAL_ALPHANUMERIC_SYMBOLS) \ +UNICODE_RANGE(0x20000, 42719, CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B) \ +UNICODE_RANGE(0x2f800, 543, CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT) \ +UNICODE_RANGE(0xe0000, 127, TAGS) \ +UNICODE_RANGE(0xe0100, 239, VARIATION_SELECTORS_SUPPLEMENT) \ +UNICODE_RANGE(0xf0000, 65533, SUPPLEMENTARY_PRIVATE_USE_AREA_A) \ +UNICODE_RANGE(0x100000, 65533, SUPPLEMENTARY_PRIVATE_USE_AREA_B) + + +#define UNICODE_RANGE(start, count, name) \ + extern const unicode_range _cat2_(UNICODE_RANGE_, name); +UNICODE_RANGES +#undef UNICODE_RANGE + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif //__UTF8_H_ diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..af71165 --- /dev/null +++ b/todo.txt @@ -0,0 +1,115 @@ + + +Shortlist +--------- +[x] let pass flat args in ui_size_push() and ui_box_set_size() +[>>] separate style stacks +[>] margins? as part of size, or different styling stack? +[>] animation time stack +[ ] Let build code set target style directly, and animate from current to target +[ ] filter styles stack by tag +[ ] image backgrounds/gradients? +[ ] animating open/close widgets? + + +Canvas Drawing +-------------- +[.] Correctly handle resizing / viewport + [x] associate surfaces with mp_windows + [x] window resize resizes surface. Surface always renders to whole mp_window + [x] Add ability to create sub-views, and create surfaces for these + - window comes with a main view, so we can get a surface for the whole window + [>] Clean native mp_window_data struct (don't need to cache a lot of stuff here) + +[.] Add images bliting + [x] Clean, rename uv vs texUV stuff + [>] Destroy stuff + [>] More unified handle system + [.] Rounded images (sortof) + [ ] path clipped images + +[ ] Add color gradients? +[ ] Make canvas implicit? +[/] Handle based error signaling +[/] Allow polling events in main thread, and updating/rendering in background thread. + +[x] font metrics shouldn't depend on surface, & font + shouldn't really depend on canvas either??? + > meaning we should be able to pass fonts without canvas in ui + > and only pass canvas when drawing at the end... + +UI +-- +[x] Make gui context implicit? +[x] Uniform ui_box struct + cache widgets +[x] Prune unused boxes +[.] Layout boxes + [x] basic two pass layout + [x] Layout from start or end + [>] Add overflow flags to layout, & solve conflicts + +[x] Temporarily push transform and text flip when rendering UI, so that the coord system is y down, origin at top left +[x] Canvas render the same size on a high-dpi surface + > it works with abstract 'pixel' units, which are transformed to pixels in the shader, according to backing store scaling + +[.] Style struct and style stack + [ ] Maybe use individual stack for different style attributes +[x] Pass initial style in ui_begin_frame() + +[.] Draw boxes + [x] use flags to enable/disable drawing each feature + [ ] active/hovered transitions + +[x] Change input state handling a move it to app layer +[x] Compute signals for ui_box +[x] Use ui_size_push() and pass axis instead of ui_width/height_push() +[>] Use value is ui_size as margin when kind == text or == children? + +[ ] Allow animating sizes according to hot/active? + +[ ] Basic helpers + [.] button + [.] slider (or rather, scroll bar) + [.] simple spacers + [ ] have a flag for non-cached stuff + [.] scrolling panel + [ ] Allow/disallow scrolling in x/y + [ ] Scroll with mousewheel + [/] add margins to scrollbars (disallow scrollbars crossing) + +[?] Maybe let builder code handle "active"/"hot" state, since it + depends on the widgets + [ ] On the other hand, this state must be set before layouting, in + particular font/fontSize -> maybe do a pass for static layout, + instead of doing it in box creation... + [ ] this way we can compute styling after user has set active/hot, but before layout + > Maybe just let user set style selector, and provide persistent state bits that can + > be used in any way? (to replace eg active/hot?) or perhaps not needed if we have just + > 'dragging' state + + +[x] Mask mouse outside of parent rects -> maintain clip stack and clip mouse against it +[x] Mask mouse below panels/other widgets +[x] popups and tooltips + [x] allow pushing/popping boxes irrespective of parent/child relation + [x] ui_begin_frame() prepares two containers, user ui goes in the first one + [x] tooltips and menus go to the second one + [x] Add menus +[ ] line editing widget + +Misc +---- +[x] Split metal surface and metal painter (but put them in the same compilation unit?) +[x] Have only one rect struct +[x] Shorten mp_string to str8 + +[ ] Better/Simpler time API + +[/] Frame throttling + [x] For now, we always wait on vblank during mg_surface_present(), regardless of target fps + [ ] Then actually get the correct display interval from the surface's current monitor + [ ] Allow waiting for more display interval than one? (ie allow throttling at 30fps for a 60fps display) + +[/] split osx_app and move all platform independant stuff outside + +[ ] Sort out mg_matrix_push/pop() -> transform vs. set...