├── .gitignore ├── Makefile ├── readme.md ├── src ├── shaders │ ├── shape_fsh.cpp │ ├── shape_vsh.cpp │ ├── line_vsh.cpp │ └── line_fsh.cpp ├── args_parser.h ├── args_parser.cpp ├── sdf_gl.h ├── parabola.h ├── sdf_atlas.h ├── glyph_painter.h ├── font.h ├── mat2d.h ├── parabola.cpp ├── sdf_gl.cpp ├── float2.h ├── gl_utils.cpp ├── glyph_painter.cpp ├── gl_utils.h ├── sdf_atlas.cpp ├── main.cpp └── font.cpp └── third_party └── stb_image_write.h /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | *.user 3 | *~ 4 | #*# 5 | *.exe 6 | *.o 7 | .cquery 8 | /.cquery_cached_index 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CCPP=g++ 2 | CPPFLAGS=-c -Wall -O2 -std=c++14 3 | CFLAGS=-c -Wall -O2 4 | 5 | LIBS=-lGLEW -lGL -lglfw 6 | LDFLAGS= 7 | DSFLAGS=-DNDEBUG 8 | 9 | SOURCES= \ 10 | src/gl_utils.cpp \ 11 | src/parabola.cpp \ 12 | src/args_parser.cpp \ 13 | src/sdf_gl.cpp \ 14 | src/glyph_painter.cpp \ 15 | src/sdf_atlas.cpp \ 16 | src/font.cpp \ 17 | src/main.cpp 18 | 19 | VPATH=$(dir $(SOURCES)) 20 | 21 | OBJECTS=$(addsuffix .o, $(basename $(SOURCES))) 22 | 23 | BINDIR=./bin/ 24 | BINDEST=$(addprefix $(BINDIR), $(notdir $(OBJECTS))) 25 | 26 | DEPNAMES = $(addsuffix .d, $(basename $(SOURCES))) 27 | DEPS = $(addprefix $(BINDIR), $(notdir $(DEPNAMES))) 28 | 29 | EXECUTABLE=./bin/sdf_atlas 30 | 31 | all: bindir $(EXECUTABLE) 32 | 33 | $(EXECUTABLE): $(BINDEST) 34 | $(CCPP) $(LDFLAGS) $(BINDEST) $(LIBS) -o $@ 35 | 36 | $(BINDIR)%.o:%.cpp 37 | $(CCPP) $(CPPFLAGS) $(DSFLAGS) -MMD $< -o $(addprefix $(BINDIR), $(notdir $@)) 38 | 39 | .PHONY: all bindir clean 40 | 41 | bindir: 42 | test -d $(BINDIR) || mkdir $(BINDIR) 43 | 44 | clean: 45 | rm ./bin/* 46 | 47 | -include $(DEPS) 48 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # SDF font atlas generation tool 2 | 3 | Atlas generation tool for the [font rendering demo](https://github.com/astiopin/webgl_fonts). 4 | 5 | The algorithm described [here](https://astiopin.github.io/2019/01/06/sdf-on-gpu.html). 6 | 7 | Mostly for educational purposes since the algorithm is performant enough for generating font atlases at runtime. 8 | 9 | # Dependencies 10 | 11 | GLFW 12 | 13 | # Usage 14 | 15 | ```sdf_atlas -f font_file.ttf [options] 16 | Options: 17 | -h this help 18 | -o 'filename' output file name (without extension) 19 | -tw 'size' atlas image width in pixels, default 1024 20 | -th 'size' atlas image height in pixels (optional) 21 | -ur 'ranges' unicode ranges 'start1:end1,start:end2,single_codepoint' without spaces, 22 | default: 31:126,0xffff 23 | -bs 'size' SDF distance in pixels, default 16 24 | -rh 'size' row height in pixels (without SDF border), default 96 25 | Example: 26 | sdf_atlas -f Roboto-Regular.ttf -o roboto -tw 2048 -th 2048 -bs 22 -rh 70 -ur 31:126,0xA0:0xFF,0x400:0x4FF,0xFFFF``` 27 | 28 | -------------------------------------------------------------------------------- /src/shaders/shape_fsh.cpp: -------------------------------------------------------------------------------- 1 | const char * const shape_fsh = R"( //" 2 | /* 3 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | 24 | varying vec2 vpar; 25 | 26 | void main() { 27 | float val = float( vpar.x * vpar.x < vpar.y ); 28 | if ( val == 0.0 ) discard; 29 | gl_FragColor = vec4( 1.0 ); 30 | } 31 | 32 | )"; // " 33 | -------------------------------------------------------------------------------- /src/shaders/shape_vsh.cpp: -------------------------------------------------------------------------------- 1 | const char * const shape_vsh = R"( // " 2 | /* 3 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | 24 | uniform mat3 transform_matrix; 25 | 26 | attribute vec2 pos; 27 | attribute vec2 par; 28 | 29 | varying vec2 vpar; 30 | 31 | void main() { 32 | vpar = par; 33 | vec2 tpos = ( transform_matrix * vec3( pos, 1.0 ) ).xy; 34 | gl_Position = vec4( tpos, 0.0, 1.0 ); 35 | } 36 | 37 | )"; // " 38 | -------------------------------------------------------------------------------- /src/args_parser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | struct ArgsParser { 30 | using Command = std::function; 31 | 32 | std::unordered_map commands; 33 | 34 | int argc = 0; 35 | char **argv = nullptr; 36 | char **arg = nullptr; 37 | 38 | std::string word(); 39 | 40 | bool run( int argc, char** argv ); 41 | }; 42 | -------------------------------------------------------------------------------- /src/shaders/line_vsh.cpp: -------------------------------------------------------------------------------- 1 | const char * const line_vsh = R"( // " 2 | /* 3 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | 24 | uniform mat3 transform_matrix; 25 | 26 | attribute vec2 pos; 27 | attribute vec2 par; 28 | attribute vec2 limits; 29 | attribute float scale; 30 | attribute float line_width; 31 | 32 | varying vec2 vpar; 33 | varying vec2 vlimits; 34 | varying float dist_scale; 35 | 36 | void main() { 37 | vpar = par; 38 | vlimits = limits; 39 | dist_scale = scale / line_width; 40 | 41 | vec2 tpos = ( transform_matrix * vec3( pos, 1.0 ) ).xy; 42 | gl_Position = vec4( tpos, 0.0, 1.0 ); 43 | } 44 | 45 | )"; // " 46 | -------------------------------------------------------------------------------- /src/args_parser.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include "args_parser.h" 24 | 25 | #include 26 | 27 | std::string ArgsParser::word() { 28 | std::string res { *arg }; 29 | arg++; 30 | return res; 31 | } 32 | 33 | bool ArgsParser::run( int argc, char** argv ) { 34 | this->argc = argc; 35 | this->argv = argv; 36 | arg = argv + 1; 37 | 38 | while( arg != argv + argc ) { 39 | std::string op = word(); 40 | auto got = commands.find( op ); 41 | 42 | if ( got == commands.end() ) { 43 | std::cerr << "Unknown parameter: " << op << std::endl; 44 | return false; 45 | } 46 | got->second( this ); 47 | } 48 | return true; 49 | } 50 | -------------------------------------------------------------------------------- /src/sdf_gl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | 24 | #pragma once 25 | 26 | #include 27 | #include "float2.h" 28 | #include "gl_utils.h" 29 | 30 | 31 | struct SdfVertex { 32 | F2 pos; // Vertex position 33 | F2 par; // Vertex position in parabola space 34 | F2 limits; // Parabolic segment xstart, xend 35 | float scale; // Parabola scale relative to world 36 | float line_width; // Line width in world space 37 | }; 38 | 39 | 40 | struct GlyphUnf { 41 | UNIFORM_MATRIX( 3, transform_matrix ); 42 | }; 43 | 44 | 45 | struct SdfGl { 46 | 47 | GLuint fill_prog = 0, line_prog = 0; 48 | 49 | GlyphUnf ufill, uline; 50 | 51 | void init(); 52 | 53 | void render_sdf( F2 tex_size, const std::vector &fill_vertices, const std::vector &line_vertices ); 54 | }; 55 | -------------------------------------------------------------------------------- /src/parabola.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include "mat2d.h" 26 | 27 | enum class QbezType { 28 | Parabola, Line, TwoLines 29 | }; 30 | 31 | 32 | // np10 = normalize( p0 - p1 ); 33 | // np12 = normalize( p2 - p1 ); 34 | QbezType qbez_type( F2 np10, F2 np12 ); 35 | 36 | 37 | // Calculates parabola parameters of a quadratic Bezier 38 | 39 | struct Parabola { 40 | Mat2d mat; // Scale of the parabola transform is stored separately 41 | float scale; 42 | float xstart; // Sorted parabola segment endpoints: xstart < xend; 43 | float xend; 44 | 45 | static Parabola from_qbez( const Float2& p0, const Float2& p1, const Float2& p2 ); 46 | 47 | static Parabola from_line( const Float2& p0, const Float2& p2 ); 48 | 49 | Float2 pos( float x ) const; 50 | 51 | Float2 normal( float x ) const; 52 | 53 | Float2 dir( float x ) const; 54 | 55 | Float2 world_to_par( Float2 pos ) const; 56 | 57 | Float2 par_to_world( Float2 pos ) const; 58 | }; 59 | -------------------------------------------------------------------------------- /src/sdf_atlas.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | #pragma once 23 | 24 | #include "glyph_painter.h" 25 | #include 26 | 27 | struct GlyphRect { 28 | uint32_t codepoint = 0; 29 | int glyph_idx = 0; 30 | float x0 = 0.0f, y0 = 0.0f, x1 = 0.0f, y1 = 0.0f; 31 | }; 32 | 33 | struct SdfAtlas { 34 | Font *font = nullptr; 35 | float tex_width = 2048.0f; 36 | float row_height = 96.0f; 37 | float sdf_size = 16.0f; 38 | int glyph_count = 0; 39 | 40 | float posx = 0; 41 | float posy = 0; 42 | int max_height = 0; 43 | 44 | std::vector glyph_rects; 45 | 46 | void init( Font *font, float tex_width, float row_height, float sdf_size ); 47 | 48 | void allocate_codepoint( uint32_t codepoint ); 49 | 50 | void allocate_all_glyphs(); 51 | 52 | void allocate_unicode_range( uint32_t start, uint32_t end ); // end is inclusive 53 | 54 | void draw_glyphs( GlyphPainter& gp ) const; 55 | 56 | std::string json( float tex_height, bool flip_texcoord_y = true ) const; 57 | }; 58 | -------------------------------------------------------------------------------- /src/glyph_painter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include 26 | 27 | #include "float2.h" 28 | #include "sdf_gl.h" 29 | #include "font.h" 30 | 31 | 32 | struct FillPainter { 33 | std::vector vertices; 34 | 35 | F2 fan_pos = F2( 0.0f ); 36 | F2 prev_pos = F2( 0.0f ); 37 | 38 | void move_to( F2 p0 ); 39 | 40 | void line_to( F2 p1 ); 41 | 42 | void qbez_to( F2 p1, F2 p2 ); 43 | 44 | void close(); 45 | }; 46 | 47 | 48 | struct LinePainter { 49 | std::vector vertices; 50 | 51 | F2 start_pos = F2( 0.0f ); 52 | F2 prev_pos; 53 | 54 | void move_to( F2 p0 ); 55 | 56 | void line_to( F2 p1, float line_width ); 57 | 58 | void qbez_to( F2 p1, F2 p2, float line_width ); 59 | 60 | void close( float line_width ); 61 | }; 62 | 63 | 64 | struct GlyphPainter { 65 | 66 | FillPainter fp; 67 | 68 | LinePainter lp; 69 | 70 | void draw_glyph( const Font *font, int glyph_index, F2 pos, float scale, float sdf_size ); 71 | 72 | void clear() { 73 | fp.vertices.clear(); 74 | lp.vertices.clear(); 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /src/font.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include 26 | #include 27 | #include 28 | #include "float2.h" 29 | #include "mat2d.h" 30 | 31 | 32 | struct Glyph { 33 | enum CharType { 34 | Lower = 1, Upper = 2, Punct = 4, Space = 8, Other = 0 35 | } char_type = Other; 36 | 37 | float advance_width = 0.0f; 38 | float left_side_bearing = 0.0f; 39 | 40 | F2 min = F2{ 0.0f }; 41 | F2 max = F2{ 0.0f }; 42 | 43 | int command_start = 0; 44 | int command_count = 0; 45 | 46 | bool is_composite = false; 47 | 48 | int components_start = 0; 49 | int components_count = 0; 50 | }; 51 | 52 | 53 | struct GlyphCommand { 54 | enum Type { 55 | MoveTo, LineTo, BezTo, ClosePath 56 | } type = MoveTo; 57 | 58 | F2 p0 = F2{ 0.0f }; 59 | F2 p1 = F2{ 0.0f }; 60 | }; 61 | 62 | 63 | struct GlyphComponent { 64 | int glyph_idx; 65 | Mat2d transform; 66 | }; 67 | 68 | 69 | struct Font { 70 | // Kerning map: ( left_codepoint << 16 & right_codepoint ) -> kerning advance distance 71 | std::unordered_map kern_map; 72 | 73 | // Glyph map: codepoint -> glyph index 74 | std::unordered_map glyph_map; 75 | 76 | // Codepoint map: glyph index -> codepoint 77 | std::unordered_map> cp_map; 78 | 79 | // Glyph array 80 | std::vector glyphs; 81 | 82 | // Array of glyph display commands 83 | std::vector glyph_commands; 84 | 85 | // Array of composite glyph indices 86 | std::vector glyph_components; 87 | 88 | // Font metrics in em 89 | float em_ascent, em_descent, em_line_gap; 90 | 91 | // Font metrics relative to ascent (ascent == 1.0) 92 | float ascent, descent, line_gap; 93 | 94 | // Glyph maximum bounding box 95 | F2 glyph_min, glyph_max; 96 | 97 | bool load_ttf_file( const char *filename ); 98 | 99 | bool load_ttf_mem( const uint8_t *ttf ); 100 | 101 | // Find glyph index by codepoint 102 | int glyph_idx( uint32_t codepoint ) const { 103 | auto iter = glyph_map.find( codepoint ); 104 | return iter == glyph_map.end() ? -1 : iter->second; 105 | } 106 | 107 | int kern_advance( uint32_t cp1, uint32_t cp2 ); 108 | }; 109 | -------------------------------------------------------------------------------- /src/mat2d.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include "float2.h" 26 | 27 | struct Mat2d { 28 | Float2 mat[3]; 29 | 30 | Mat2d( float a = 0.0f ) { 31 | mat[0][0] = a; mat[0][1] = 0.0f; 32 | mat[1][0] = 0.0f; mat[1][1] = a; 33 | mat[2][0] = 0.0f; mat[2][1] = 0.0f; 34 | } 35 | 36 | 37 | Mat2d( float m00, float m01, 38 | float m10, float m11, 39 | float m20, float m21 ) { 40 | mat[0][0] = m00; mat[0][1] = m01; 41 | mat[1][0] = m10; mat[1][1] = m11; 42 | mat[2][0] = m20; mat[2][1] = m21; 43 | } 44 | 45 | Mat2d( Float2 v0, Float2 v1, Float2 v2 ) { 46 | mat[0] = v0; 47 | mat[1] = v1; 48 | mat[2] = v2; 49 | } 50 | 51 | Float2& operator[]( size_t index ) { 52 | return mat[index]; 53 | } 54 | 55 | const Float2& operator[]( size_t index ) const { 56 | return mat[index]; 57 | } 58 | 59 | float* ptr() { 60 | return mat[0].ptr(); 61 | } 62 | }; 63 | 64 | 65 | inline Mat2d operator*( const Mat2d& a, const Mat2d& b ) { 66 | Mat2d res; 67 | res[0] = a[0] * F2 { b[0][0] } + a[1] * F2 { b[0][1] }; 68 | res[1] = a[0] * F2 { b[1][0] } + a[1] * F2 { b[1][1] }; 69 | res[2] = a[0] * F2 { b[2][0] } + a[1] * F2 { b[2][1] } + a[2]; 70 | 71 | return res; 72 | } 73 | 74 | inline Float2 operator*( const Mat2d& m, const Float2& v ) { 75 | Float2 res; 76 | res = m[0] * F2{ v[0] } + m[1] * F2{ v[1] } + m[2]; 77 | return res; 78 | } 79 | 80 | inline Mat2d operator*( const Mat2d& m, float a ) { 81 | Mat2d res; 82 | F2 a2 { a }; 83 | res[0] = m[0] * a2; 84 | res[1] = m[1] * a2; 85 | res[2] = m[2] * a2; 86 | return res; 87 | } 88 | 89 | inline float det( const Mat2d& m ) { 90 | return m[0][0] * m[1][1] - m[1][0] * m[0][1]; 91 | } 92 | 93 | 94 | inline Mat2d invert( const Mat2d& m ) { 95 | Mat2d res; 96 | 97 | const float invdet = 1.0f / det( m ); 98 | 99 | res[0][0] = invdet * m[1][1]; res[0][1] = invdet * -m[0][1]; 100 | res[1][0] = invdet * -m[1][0]; res[1][1] = invdet * m[0][0]; 101 | 102 | res[2][0] = invdet * ( m[1][0] * m[2][1] - m[2][0] * m[1][1] ); 103 | res[2][1] = invdet * ( m[0][1] * m[2][0] - m[0][0] * m[2][1] ); 104 | 105 | return res; 106 | } 107 | 108 | inline Mat2d screen_matrix( Float2 screen_size ) { 109 | float sw = screen_size.x; 110 | float sh = screen_size.y; 111 | Mat2d scr_matrix( 2.0 / sw, 0, 0, 2.0 / sh, -1.0, -1.0 ); 112 | return scr_matrix; 113 | } 114 | 115 | -------------------------------------------------------------------------------- /src/shaders/line_fsh.cpp: -------------------------------------------------------------------------------- 1 | const char * const line_fsh = R"( // " 2 | /* 3 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | uniform float line_width; 24 | 25 | varying vec2 vpar; 26 | varying vec2 vlimits; 27 | varying float dist_scale; 28 | 29 | 30 | 31 | // Old cubic root finding algorithm. Not used anymore. 32 | 33 | float solve_par_dist_old( vec2 pcoord ) { 34 | float p = 0.5 - pcoord.y; 35 | float q = -0.5 * pcoord.x; 36 | 37 | // Solving x^3 + p*x + q = 0 38 | 39 | float sigx = pcoord.x > 0.0 ? 1.0 : -1.0; 40 | float sq = 27.0*q*q; 41 | float cp = 4.0*p*p*p; 42 | float tp = -p * 0.33333333; 43 | float dist; 44 | 45 | if ( sq >= -cp ) { 46 | // Point below evolute - single root 47 | float rcb = 0.096225; // 1 / ( 2*3^(3/2) ) 48 | float mc = sigx * pow( sqrt( abs( sq + cp ) ) * rcb + 0.5 * abs( q ), 0.33333333 ); 49 | float x0 = tp / mc + mc; 50 | x0 = clamp( x0, vlimits.x, vlimits.y ); 51 | dist = length( vec2( x0, x0*x0 ) - pcoord ); 52 | } else { 53 | // Point above evolute - three roots 54 | 55 | float a2 = abs( sq / cp ); 56 | float a = sqrt( a2 ); 57 | 58 | // Exact solution 59 | //float dacs = 2.0 * cos( acos( a ) / 3.0 ); 60 | // Approximation with cubic 61 | float dacs = a2 * ( 0.01875324 * a - 0.08179158 ) + ( 0.33098754 * a + 1.7320508 ); 62 | 63 | float rsp = sqrt( abs( tp ) ); 64 | float x0 = sigx * rsp * dacs; 65 | 66 | float dx = sigx * sqrt( -0.75 * x0*x0 - p ); 67 | float x1 = -0.5 * x0 - dx; 68 | 69 | //Third root is never the closest 70 | //float x2 = -0.5 * x0 + dx; 71 | 72 | x0 = clamp( x0, vlimits.x, vlimits.y ); 73 | x1 = clamp( x1, vlimits.x, vlimits.y ); 74 | 75 | float d0 = length( vec2( x0, x0*x0 ) - pcoord ); 76 | float d1 = length( vec2( x1, x1*x1 ) - pcoord ); 77 | 78 | dist = min( d0, d1 ); 79 | } 80 | 81 | return dist; 82 | } 83 | 84 | 85 | // Updated root finding algorithm that copes better with degenerate cases (straight lines) 86 | // From "The Low-Rank LDL^T Quartic Solver" by Peter Strobach, 2015 87 | 88 | float solve_par_dist( vec2 pcoord, int iter ) { 89 | float sigx = pcoord.x > 0.0 ? 1.0 : -1.0; 90 | float px = abs( pcoord.x ); 91 | float py = pcoord.y; 92 | float h = 0.5 * px; 93 | float g = 0.5 - py; 94 | float xr = sqrt( 0.5 * px ); 95 | float x0 = g < -h ? sqrt( abs( g ) ) : 96 | g > xr ? h / abs( g ) : 97 | xr; 98 | 99 | for ( int i = 0; i < iter; ++i ) { 100 | float rcx0 = 1.0 / x0; 101 | float pb = h * rcx0 * rcx0; 102 | float pc = -px * rcx0 + g; 103 | x0 = 2.0 * pc / ( -pb - sqrt( abs( pb*pb - 4.0*pc ) ) ); 104 | } 105 | 106 | x0 = sigx * x0; 107 | float dx = sigx * sqrt( -0.75 * x0*x0 - g ); 108 | float x1 = -0.5 * x0 - dx; 109 | 110 | x0 = clamp( x0, vlimits.x, vlimits.y ); 111 | x1 = clamp( x1, vlimits.x, vlimits.y ); 112 | 113 | float d0 = length( vec2( x0, x0*x0 ) - pcoord ); 114 | float d1 = length( vec2( x1, x1*x1 ) - pcoord ); 115 | 116 | float dist = min( d0, d1 ); 117 | return dist; 118 | } 119 | 120 | 121 | void main() { 122 | //float dist = solve_par_dist_old( vpar ); 123 | float dist = solve_par_dist( vpar, 3 ); 124 | float pdist = min( dist * dist_scale, 1.0 ); 125 | 126 | float color = 0.5 - 0.5 * pdist; 127 | 128 | if ( color == 0.0 ) discard; 129 | 130 | gl_FragColor = vec4( color ); 131 | gl_FragDepth = pdist; 132 | } 133 | 134 | 135 | )"; // " 136 | -------------------------------------------------------------------------------- /src/parabola.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include "parabola.h" 24 | 25 | QbezType qbez_type( F2 np10, F2 np12 ) { 26 | float d = dot( np10, np12 ); 27 | float dmax = 1.0 - 1e-6f; 28 | if ( d >= dmax ) return QbezType::TwoLines; 29 | if ( d <= -dmax ) return QbezType::Line; 30 | return QbezType::Parabola; 31 | } 32 | 33 | /* 34 | Parabola Parabola::from_line( const Float2& p0, const Float2& p2 ) { 35 | float precision = 1e-16; 36 | Parabola res; 37 | 38 | Float2 pc = mix( p0, p2, 0.5f ); 39 | Float2 x_axis = normalize( p2 - p0 ); 40 | float ldir = length( p2 - p0 ); 41 | Float2 y_axis = perp_left( x_axis ); 42 | float ylen = ldir * precision; 43 | Float2 vertex = pc + ylen * y_axis; 44 | float xlen = sqrtf( precision ); 45 | 46 | res.xstart = -xlen; 47 | res.xend = xlen; 48 | res.scale = 0.5f * ldir / xlen; 49 | res.mat = Mat2d( x_axis, y_axis, vertex ); 50 | 51 | return res; 52 | } 53 | */ 54 | 55 | Parabola Parabola::from_line( const Float2& p0, const Float2& p1 ) { 56 | float px0 = 100.0f; 57 | float px1 = 100.0f + 1e-3f; 58 | 59 | float py0 = px0 * px0; 60 | float py1 = px1 * px1; 61 | float plen = length( F2{ px0, py0 } - F2{ px1, py1 } ); 62 | float pslope = 2.0f * px0 / sqrtf( 1.0f * 4.0f*px0*px0 ); 63 | float len = length( p1 - p0 ); 64 | F2 ldir = ( p1 - p0 ) / len; 65 | F2 lndir = perp_right( ldir ); 66 | float scale = len / plen; 67 | F2 rc = p0 + ( ldir * ( px1 - px0 ) + lndir * ( py1 - py0 ) ) * pslope * scale; 68 | F2 px = normalize( rc - p0 ); 69 | F2 py = perp_left( px ); 70 | F2 pvertex = p0 - px * scale * px0 - py * scale * py0; 71 | 72 | Parabola res; 73 | 74 | res.mat = Mat2d{ px, py, pvertex }; 75 | res.scale = scale; 76 | res.xstart = px0; 77 | res.xend = px1; 78 | return res; 79 | } 80 | 81 | 82 | 83 | Parabola Parabola::from_qbez( const Float2& p0, const Float2& p1, const Float2& p2 ) { 84 | Parabola res; 85 | 86 | Float2 pc = mix( p0, p2, 0.5f ); 87 | Float2 yaxis = normalize( pc - p1 ); 88 | Float2 xaxis = perp_right( yaxis ); 89 | 90 | Float2 p01 = normalize( p1 - p0 ); 91 | Float2 p12 = normalize( p2 - p1 ); 92 | float cx0 = dot( xaxis, p01 ); 93 | float sx0 = dot( yaxis, p01 ); 94 | float cx2 = dot( xaxis, p12 ); 95 | float sx2 = dot( yaxis, p12 ); 96 | 97 | float x0 = sx0 / cx0 * 0.5; 98 | float x2 = sx2 / cx2 * 0.5; 99 | float y0 = x0*x0; 100 | 101 | float p02x = dot( p2 - p0, xaxis ); 102 | float scale = p02x / ( x2 - x0 ); 103 | 104 | Float2 vertex = p0 - F2( y0 * scale ) * yaxis - F2( x0 * scale ) * xaxis; 105 | 106 | res.scale = scale; 107 | res.mat = Mat2d( xaxis, yaxis, vertex ); 108 | 109 | if ( x0 < x2 ) { 110 | res.xstart = x0; 111 | res.xend = x2; 112 | } else { 113 | res.xstart = x2; 114 | res.xend = x0; 115 | } 116 | 117 | return res; 118 | } 119 | 120 | Float2 Parabola::pos( float x ) const { 121 | return mat[2] + F2( scale * x ) * mat[0] + F2( scale * x * x ) * mat[1]; 122 | } 123 | 124 | Float2 Parabola::normal( float x ) const { 125 | return perp_left( dir( x ) ); 126 | } 127 | 128 | Float2 Parabola::dir( float x ) const { 129 | return normalize( mat[0] + mat[1] * F2( 2.0f * x ) ); 130 | } 131 | 132 | F2 Parabola::world_to_par( F2 pos ) const { 133 | float is = 1.0 / scale; 134 | F2 dpos = pos - mat[2]; 135 | F2 r0 = dpos * mat[0]; 136 | F2 r1 = dpos * mat[1]; 137 | float v0 = is * ( r0.x + r0.y ); 138 | float v1 = is * ( r1.x + r1.y ); 139 | return F2 { v0, v1 }; 140 | } 141 | 142 | F2 Parabola::par_to_world( F2 pos ) const { 143 | return mat[2] + scale * pos.x * mat[0] + scale * pos.y * mat[1]; 144 | } 145 | -------------------------------------------------------------------------------- /src/sdf_gl.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include "sdf_gl.h" 24 | 25 | #include "shaders/shape_vsh.cpp" 26 | #include "shaders/shape_fsh.cpp" 27 | 28 | #include "shaders/line_vsh.cpp" 29 | #include "shaders/line_fsh.cpp" 30 | 31 | 32 | VertexAttrib vattribs[] = { 33 | VertexAttrib( 0, "pos", 2 ), 34 | VertexAttrib( 1, "par", 2 ), 35 | VertexAttrib( 2, "limits", 2 ), 36 | VertexAttrib( 3, "scale", 1 ), 37 | VertexAttrib( 4, "line_width", 1 ) 38 | }; 39 | 40 | constexpr size_t vattribs_count = sizeof( vattribs ) / sizeof( vattribs[0] ); 41 | 42 | void SdfGl::init() { 43 | initVertexAttribs( vattribs, vattribs_count ); 44 | fill_prog = createProgram( "fill", shape_vsh, shape_fsh, vattribs, vattribs_count ); 45 | initUniformStruct( fill_prog, ufill ); 46 | 47 | line_prog = createProgram( "line", line_vsh, line_fsh, vattribs, vattribs_count ); 48 | initUniformStruct( line_prog, uline ); 49 | } 50 | 51 | void SdfGl::render_sdf( F2 tex_size, const std::vector &fill_vertices, const std::vector &line_vertices ) { 52 | 53 | // full screen quad vertices 54 | SdfVertex fs_quad[6] = { 55 | { F2( -1.0, -1.0 ), F2( 0.0f, 1.0f ), F2( 0.0f ), 0.0f, 0.0f }, 56 | { F2( 1.0, -1.0 ), F2( 0.0f, 1.0f ), F2( 0.0f ), 0.0f, 0.0f }, 57 | { F2( 1.0, 1.0 ), F2( 0.0f, 1.0f ), F2( 0.0f ), 0.0f, 0.0f }, 58 | 59 | { F2( -1.0, -1.0 ), F2( 0.0f, 1.0f ), F2( 0.0f ), 0.0f, 0.0f }, 60 | { F2( 1.0, 1.0 ), F2( 0.0f, 1.0f ), F2( 0.0f ), 0.0f, 0.0f }, 61 | { F2( -1.0, 1.0 ), F2( 0.0f, 1.0f ), F2( 0.0f ), 0.0f, 0.0f } 62 | }; 63 | 64 | size_t lcount = line_vertices.size(); 65 | size_t fcount = fill_vertices.size(); 66 | 67 | // screen matrix 68 | float mscreen3[] = { 69 | 2.0f / tex_size.x, 0, 0, 70 | 0, 2.0f / tex_size.y, 0, 71 | -1, -1, 1 }; 72 | 73 | // identity matrix 74 | float mid[] = { 75 | 1, 0, 0, 76 | 0, 1, 0, 77 | 0, 0, 1 78 | }; 79 | 80 | glViewport( 0, 0, tex_size.x, tex_size.y ); 81 | 82 | glBindBuffer( GL_ARRAY_BUFFER, 0 ); 83 | 84 | // Drawing lines with depth test 85 | 86 | if ( line_vertices.size() ) { 87 | 88 | bindAttribs( vattribs, vattribs_count, (size_t) line_vertices.data() ); 89 | 90 | glUseProgram( line_prog ); 91 | uline.transform_matrix.setv( mscreen3 ); 92 | glEnable( GL_DEPTH_TEST ); 93 | glDepthFunc( GL_LEQUAL ); 94 | glDrawArrays( GL_TRIANGLES, 0, lcount ); 95 | glDisable( GL_DEPTH_TEST ); 96 | 97 | } 98 | 99 | // Drawing fills 100 | 101 | if ( fill_vertices.size() ) { 102 | 103 | bindAttribs( vattribs, vattribs_count, (size_t) fill_vertices.data() ); 104 | 105 | glUseProgram( fill_prog ); 106 | ufill.transform_matrix.setv( mscreen3 ); 107 | 108 | glEnable( GL_STENCIL_TEST ); 109 | glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); 110 | 111 | glStencilFunc( GL_ALWAYS, 0, 0xff ); 112 | glStencilOpSeparate( GL_FRONT, GL_KEEP, GL_INCR_WRAP, GL_INCR_WRAP ); 113 | glStencilOpSeparate( GL_BACK, GL_KEEP, GL_DECR_WRAP, GL_DECR_WRAP ); 114 | glDrawArrays( GL_TRIANGLES, 0, fcount ); 115 | 116 | // Drawing full screen quad, inverting colors where stencil == 1 117 | 118 | bindAttribs( vattribs, vattribs_count, (size_t) fs_quad ); 119 | 120 | glEnable( GL_BLEND ); 121 | glBlendEquation( GL_FUNC_ADD ); 122 | glBlendFunc( GL_ONE_MINUS_DST_COLOR, GL_ZERO ); 123 | glStencilFunc( GL_NOTEQUAL, 0, 0xff ); 124 | glStencilOp( GL_ZERO, GL_ZERO, GL_ZERO ); 125 | glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ); 126 | ufill.transform_matrix.setv( mid ); 127 | glDrawArrays( GL_TRIANGLES, 0, 6 ); 128 | 129 | } 130 | 131 | glDisable( GL_BLEND ); 132 | glDisable( GL_STENCIL_TEST ); 133 | 134 | glUseProgram( 0 ); 135 | } 136 | -------------------------------------------------------------------------------- /src/float2.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | #include 25 | 26 | struct Float2 { 27 | 28 | float x, y; 29 | 30 | Float2() {} 31 | 32 | Float2( float a ) : 33 | x(a), y(a) {} 34 | 35 | explicit Float2( float* a ) : 36 | x(a[0]), y(a[1]) {} 37 | 38 | Float2( float x, float y ) : 39 | x(x), y(y) {} 40 | 41 | Float2( const Float2& other ) : 42 | x( other.x ), y( other.y ) {} 43 | 44 | float* ptr() { 45 | return &x; 46 | } 47 | 48 | const float* ptr() const { 49 | return &x; 50 | } 51 | 52 | float& operator[] ( size_t index ) { 53 | return (&x)[index]; 54 | } 55 | 56 | const float& operator[] ( size_t index ) const { 57 | return (&x)[index]; 58 | } 59 | 60 | Float2& operator- () { 61 | x = -x; 62 | y = -y; 63 | return *this; 64 | } 65 | 66 | Float2& operator= ( const Float2& other ) { 67 | x = other.x; 68 | y = other.y; 69 | return *this; 70 | } 71 | 72 | Float2& operator+= ( const Float2& other ) { 73 | x += other.x; 74 | y += other.y; 75 | return *this; 76 | } 77 | 78 | Float2& operator-= ( const Float2& other ) { 79 | x -= other.x; 80 | y -= other.y; 81 | return *this; 82 | } 83 | 84 | Float2& operator*= ( const Float2& other ) { 85 | x *= other.x; 86 | y *= other.y; 87 | return *this; 88 | } 89 | 90 | Float2& operator/= ( const Float2& other ) { 91 | x /= other.x; 92 | y /= other.y; 93 | return *this; 94 | } 95 | }; 96 | 97 | 98 | using F2 = Float2; 99 | 100 | 101 | inline Float2 operator* ( const Float2& lv, const Float2& rv ) { 102 | return Float2( lv.x * rv.x, lv.y * rv.y ); 103 | } 104 | 105 | inline Float2 operator* ( const Float2& lv, float rv ) { 106 | return lv * Float2( rv ); 107 | } 108 | 109 | inline Float2 operator* ( float lv, const Float2& rv ) { 110 | return Float2( lv ) * rv; 111 | } 112 | 113 | 114 | 115 | inline Float2 operator/ ( const Float2& lv, const Float2& rv ) { 116 | return Float2( lv.x / rv.x, lv.y / rv.y ); 117 | } 118 | 119 | inline Float2 operator/ ( const Float2& lv, float rv ) { 120 | return lv / Float2( rv ); 121 | } 122 | 123 | inline Float2 operator/ ( float lv, const Float2& rv ) { 124 | return Float2( lv ) / rv; 125 | } 126 | 127 | 128 | 129 | inline Float2 operator+ ( const Float2& lv, const Float2& rv ) { 130 | return Float2( lv.x + rv.x, lv.y + rv.y ); 131 | } 132 | 133 | inline Float2 operator+ ( const Float2& lv, float rv ) { 134 | return lv + Float2( rv ); 135 | } 136 | 137 | inline Float2 operator+ ( float lv, const Float2& rv ) { 138 | return Float2( lv ) + rv; 139 | } 140 | 141 | 142 | inline Float2 operator- ( const Float2& lv, const Float2& rv ) { 143 | return Float2( lv.x - rv.x, lv.y - rv.y ); 144 | } 145 | 146 | inline Float2 operator- ( const Float2& lv, float rv ) { 147 | return lv - Float2( rv ); 148 | } 149 | 150 | inline Float2 operator- ( float lv, const Float2& rv ) { 151 | return Float2( lv ) - rv; 152 | } 153 | 154 | 155 | inline Float2 min( const Float2& v1, const Float2& v2 ) { 156 | Float2 res; 157 | res.x = v1.x < v2.x ? v1.x : v2.x; 158 | res.y = v1.y < v2.y ? v1.y : v2.y; 159 | return res; 160 | } 161 | 162 | inline Float2 max( const Float2& v1, const Float2& v2 ) { 163 | Float2 res; 164 | res.x = v1.x > v2.x ? v1.x : v2.x; 165 | res.y = v1.y > v2.y ? v1.y : v2.y; 166 | return res; 167 | } 168 | 169 | 170 | inline float sqr_length( const Float2& v ) { 171 | return v.x * v.x + v.y * v.y; 172 | } 173 | 174 | inline float length( const Float2& v ) { 175 | return sqrtf( sqr_length( v ) ); 176 | } 177 | 178 | 179 | inline float dot( const Float2& v1, const Float2& v2 ) { 180 | return v1.x * v2.x + v1.y * v2.y; 181 | } 182 | 183 | 184 | inline float cross( const Float2& v1, const Float2& v2 ) { 185 | return v1.x * v2.y - v1.y * v2.x; 186 | } 187 | 188 | inline Float2 normalize( const Float2& v ) { 189 | return v / length( v ); 190 | } 191 | 192 | inline Float2 mix( const Float2& p0, const Float2& p1, float t ) { 193 | return p0 * ( 1.0f - t ) + p1 * t; 194 | } 195 | 196 | inline Float2 perp_right( const Float2& v ) { 197 | return Float2( v.y, -v.x ); 198 | } 199 | 200 | inline Float2 perp_left( const Float2& v ) { 201 | return Float2( -v.y, v.x ); 202 | } 203 | 204 | inline Float2 clamp( const Float2& v, const Float2& vmin, const Float2& vmax ) { 205 | return max( min( v, vmax ), vmin ); 206 | } 207 | 208 | inline Float2 vpow( const Float2& v, float p ) { 209 | return F2( powf( v.x, p ), powf( v.y, p ) ); 210 | } 211 | -------------------------------------------------------------------------------- /src/gl_utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include "gl_utils.h" 24 | #include 25 | #include 26 | #include 27 | 28 | GLuint createVertexBuffer( GLenum usage, size_t size, const void *data ) { 29 | GLuint id; 30 | glGenBuffers( 1, &id ); 31 | glBindBuffer( GL_ARRAY_BUFFER, id ); 32 | glBufferData( GL_ARRAY_BUFFER, size, data, usage ); 33 | glBindBuffer( GL_ARRAY_BUFFER, 0 ); 34 | return id; 35 | } 36 | 37 | 38 | GLuint compileShader(const char *name, const char *source, ShaderType type) { 39 | GLuint sid = glCreateShader(type); 40 | if(sid == 0) return 0; 41 | 42 | glShaderSource(sid, 1, &source, NULL); 43 | glCompileShader(sid); 44 | 45 | GLint scompiled; 46 | glGetShaderiv(sid, GL_COMPILE_STATUS, &scompiled); 47 | 48 | if(!scompiled) { 49 | GLint infoLen = 0; 50 | glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &infoLen); 51 | if(infoLen > 1) { 52 | char* infoLog = (char*)malloc(sizeof(char) * infoLen); 53 | glGetShaderInfoLog(sid, infoLen, NULL, infoLog); 54 | 55 | switch (type) { 56 | case VertexShader : 57 | fprintf(stderr, "Error compiling vertex shader '%s':\n%s\n", name, infoLog); 58 | assert( false ); 59 | break; 60 | case FragmentShader : 61 | fprintf(stderr, "Error compiling fragment shader '%s':\n%s\n", name, infoLog); 62 | assert( false ); 63 | break; 64 | default: 65 | fprintf(stderr, "Error compiling shader '%s':\n%s\n", name, infoLog); 66 | assert( false ); 67 | break; 68 | } 69 | 70 | free(infoLog); 71 | } 72 | 73 | glDeleteShader(sid); 74 | return false; 75 | } 76 | 77 | return sid; 78 | } 79 | 80 | 81 | bool linkProgram(GLuint program_id) { 82 | GLint linked; 83 | glLinkProgram(program_id); 84 | glGetProgramiv(program_id, GL_LINK_STATUS, &linked); 85 | 86 | if(!linked) { 87 | GLint infoLen = 0; 88 | glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &infoLen); 89 | if(infoLen > 1) { 90 | char* infoLog = (char*)malloc(sizeof(char) * infoLen); 91 | glGetProgramInfoLog(program_id, infoLen, NULL, infoLog); 92 | fprintf(stderr, "Error linking program:\n%s\n", infoLog); 93 | free(infoLog); 94 | } 95 | glDeleteProgram(program_id); 96 | return false; 97 | } 98 | return true; 99 | } 100 | 101 | 102 | 103 | GLuint createProgram(const char* name, const char* vertex_shader, const char* fragment_shader, VertexAttrib *attribs, size_t attrib_count, ProgramAction before_link) { 104 | GLuint vs_id = compileShader(name, vertex_shader, VertexShader); 105 | if (!vs_id) return 0; 106 | 107 | GLuint fs_id = compileShader(name, fragment_shader, FragmentShader); 108 | if (!fs_id) return 0; 109 | 110 | GLuint id = glCreateProgram(); 111 | if (!id) return 0; 112 | 113 | glAttachShader(id, vs_id); 114 | glAttachShader(id, fs_id); 115 | 116 | for (size_t i = 0; i < attrib_count; i++ ) { 117 | glBindAttribLocation(id, attribs[i].location, attribs[i].name); 118 | } 119 | 120 | if ( before_link ) before_link( id ); 121 | 122 | bool linked = linkProgram(id); 123 | if (!linked) return 0; 124 | 125 | return id; 126 | } 127 | 128 | 129 | void deleteProgram( GLuint program ) { 130 | GLuint shaders[16]; 131 | GLsizei count = 0; 132 | glGetAttachedShaders( program, 16, &count, shaders ); 133 | glDeleteProgram( program ); 134 | 135 | for ( GLsizei i = 0; i < count; ++i ) { 136 | glDeleteShader( shaders[i] ); 137 | } 138 | } 139 | 140 | void initUniforms(GLuint program_id, Uniform *uniforms, size_t count) { 141 | for (size_t i = 0; i < count; ++i) { 142 | Uniform* u = uniforms + i; 143 | GLint location = glGetUniformLocation(program_id, u->name); 144 | u->program_id = program_id; 145 | u->location = location; 146 | } 147 | } 148 | 149 | GLuint vertexAttribsStride(VertexAttrib *attribs, size_t attrib_count) { 150 | GLuint size = 0; 151 | for (size_t i = 0; i < attrib_count; ++i) { 152 | size += attribs[i].type.size * attribs[i].size; 153 | } 154 | 155 | return size; 156 | } 157 | 158 | 159 | void initVertexAttribs(VertexAttrib *attribs, size_t attrib_count, GLvoid *offset, GLuint stride) { 160 | GLuint new_stride = stride ? stride : vertexAttribsStride(attribs, attrib_count); 161 | char *voffset = (char*)offset; 162 | 163 | for (size_t i = 0; i < attrib_count; ++i) { 164 | VertexAttrib *va = attribs + i; 165 | va->stride = new_stride; 166 | va->offset = voffset; 167 | voffset += va->size * va->type.size; 168 | } 169 | } 170 | 171 | 172 | void bindAttribs( VertexAttrib *attribs, size_t attrib_count, size_t offset ) { 173 | for (size_t i = 0; i < attrib_count; ++i) { 174 | VertexAttrib *va = attribs + i; 175 | glVertexAttribPointer(va->location, va->size, va->type.gl_type, va->normalize, va->stride, (void*)((size_t)va->offset + offset)); 176 | glEnableVertexAttribArray(va->location); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/glyph_painter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include "glyph_painter.h" 24 | 25 | #include "parabola.h" 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | 33 | static void fill_triangle( F2 p0, F2 p1, F2 p2, std::vector* vertices ) { 34 | SdfVertex v0, v1, v2; 35 | v0 = { p0, F2( 0.0f, 1.0f ), F2( 0.0f ), 0.0f, 0.0f }; 36 | v1 = { p1, F2( 0.0f, 1.0f ), F2( 0.0f ), 0.0f, 0.0f }; 37 | v2 = { p2, F2( 0.0f, 1.0f ), F2( 0.0f ), 0.0f, 0.0f }; 38 | 39 | vertices->push_back( v0 ); 40 | vertices->push_back( v1 ); 41 | vertices->push_back( v2 ); 42 | } 43 | 44 | void FillPainter::move_to( F2 p0 ) { 45 | fan_pos = p0; 46 | prev_pos = p0; 47 | } 48 | 49 | void FillPainter::line_to( F2 p1 ) { 50 | fill_triangle( fan_pos, prev_pos, p1, &vertices ); 51 | 52 | prev_pos = p1; 53 | } 54 | 55 | void FillPainter::qbez_to( F2 p1, F2 p2 ) { 56 | SdfVertex v0, v1, v2, v3, v4, v5; 57 | 58 | fill_triangle( fan_pos, prev_pos, p2, &vertices ); 59 | 60 | v0 = { prev_pos, F2( -1.0f, 1.0f ), F2( 0.0f ), 0.0f, 0.0f }; 61 | v1 = { p1, F2( 0.0f, -1.0f ), F2( 0.0f ), 0.0f, 0.0f }; 62 | v2 = { p2, F2( 1.0f, 1.0f ), F2( 0.0f ), 0.0f, 0.0f }; 63 | 64 | vertices.push_back( v0 ); 65 | vertices.push_back( v1 ); 66 | vertices.push_back( v2 ); 67 | 68 | prev_pos = p2; 69 | } 70 | 71 | void FillPainter::close() { 72 | if ( sqr_length( fan_pos - prev_pos ) < 1e-7 ) return; 73 | line_to( fan_pos ); 74 | } 75 | 76 | void LinePainter::move_to( F2 p0 ) { 77 | prev_pos = p0; 78 | start_pos = p0; 79 | } 80 | 81 | static void set_par_vertex( SdfVertex *v, const Parabola &par ) { 82 | F2 par_pos = par.world_to_par( v->pos ); 83 | v->par = par_pos; 84 | v->limits = F2( par.xstart, par.xend ); 85 | v->scale = par.scale; 86 | } 87 | 88 | static void line_rect( const Parabola &par, F2 vmin, F2 vmax, float line_width, std::vector *vertices ) { 89 | SdfVertex v0, v1, v2, v3; 90 | v0.pos = F2( vmin.x, vmin.y ); 91 | v1.pos = F2( vmax.x, vmin.y ); 92 | v2.pos = F2( vmax.x, vmax.y ); 93 | v3.pos = F2( vmin.x, vmax.y ); 94 | 95 | v0.line_width = line_width; 96 | v1.line_width = line_width; 97 | v2.line_width = line_width; 98 | v3.line_width = line_width; 99 | 100 | set_par_vertex( &v0, par ); 101 | set_par_vertex( &v1, par ); 102 | set_par_vertex( &v2, par ); 103 | set_par_vertex( &v3, par ); 104 | 105 | vertices->push_back( v0 ); 106 | vertices->push_back( v1 ); 107 | vertices->push_back( v2 ); 108 | 109 | vertices->push_back( v0 ); 110 | vertices->push_back( v2 ); 111 | vertices->push_back( v3 ); 112 | } 113 | 114 | void LinePainter::line_to( F2 p1, float line_width ) { 115 | F2 vmin = min( prev_pos, p1 ); 116 | F2 vmax = max( prev_pos, p1 ); 117 | 118 | vmin -= F2( line_width ); 119 | vmax += F2( line_width ); 120 | 121 | Parabola par = Parabola::from_line( prev_pos, p1 ); 122 | line_rect( par, vmin, vmax, line_width, &vertices ); 123 | 124 | prev_pos = p1; 125 | } 126 | 127 | void LinePainter::qbez_to( F2 p1, F2 p2, float line_width ) { 128 | F2 p0 = prev_pos; 129 | 130 | F2 mid01 = F2( 0.5 ) * ( p0 + p1 ); 131 | F2 mid12 = F2( 0.5 ) * ( p1 + p2 ); 132 | 133 | F2 vmin = min( p0, mid01 ); 134 | vmin = min( vmin, mid12 ); 135 | vmin = min( vmin, p2 ); 136 | 137 | F2 vmax = max( p0, mid01 ); 138 | vmax = max( vmax, mid12 ); 139 | vmax = max( vmax, p2 ); 140 | 141 | vmin -= F2( line_width ); 142 | vmax += F2( line_width ); 143 | 144 | F2 v10 = p0 - p1; 145 | F2 v12 = p2 - p1; 146 | F2 np10 = normalize( v10 ); 147 | F2 np12 = normalize( v12 ); 148 | 149 | QbezType qtype = qbez_type( np10, np12 ); 150 | Parabola par; 151 | 152 | switch ( qtype ) { 153 | case QbezType::Parabola: 154 | par = Parabola::from_qbez( p0, p1, p2 ); 155 | line_rect( par, vmin, vmax, line_width, &vertices ); 156 | break; 157 | case QbezType::Line: 158 | par = Parabola::from_line( p0, p2 ); 159 | line_rect( par, vmin, vmax, line_width, &vertices ); 160 | break; 161 | case QbezType::TwoLines: { 162 | float l10 = length( v10 ); 163 | float l12 = length( v12 ); 164 | float qt = l10 / ( l10 + l12 ); 165 | float nqt = 1.0f - qt; 166 | F2 qtop = p0 * ( nqt * nqt ) + p1 * ( 2.0f * nqt * qt ) + p2 * ( qt * qt ); 167 | Parabola par0 = Parabola::from_line( p0, qtop ); 168 | line_rect( par0, vmin, vmax, line_width, &vertices ); 169 | Parabola par1 = Parabola::from_line( qtop, p1 ); 170 | line_rect( par1, vmin, vmax, line_width, &vertices ); 171 | break; 172 | } 173 | } 174 | 175 | prev_pos = p2; 176 | } 177 | 178 | 179 | void LinePainter::close( float line_width ) { 180 | if ( sqr_length( start_pos - prev_pos ) < 1e-7 ) return; 181 | line_to( start_pos, line_width ); 182 | } 183 | 184 | 185 | 186 | void GlyphPainter::draw_glyph( const Font *font, int glyph_index, F2 pos, float scale, float sdf_size ) { 187 | const Glyph& g = font->glyphs[ glyph_index ]; 188 | if ( g.command_count == 0 ) return; 189 | 190 | for ( int ic = g.command_start; ic < g.command_start + g.command_count; ++ic ) { 191 | const GlyphCommand& gc = font->glyph_commands[ ic ]; 192 | F2 p0, p1; 193 | 194 | switch ( gc.type ) { 195 | case GlyphCommand::MoveTo: 196 | p0 = gc.p0 * scale + pos; 197 | fp.move_to( p0 ); 198 | lp.move_to( p0 ); 199 | break; 200 | case GlyphCommand::LineTo: 201 | p0 = gc.p0 * scale + pos; 202 | fp.line_to( p0 ); 203 | lp.line_to( p0, sdf_size ); 204 | break; 205 | case GlyphCommand::BezTo: 206 | p0 = gc.p0 * scale + pos; 207 | p1 = gc.p1 * scale + pos; 208 | fp.qbez_to( p0, p1 ); 209 | lp.qbez_to( p0, p1, sdf_size ); 210 | break; 211 | case GlyphCommand::ClosePath: 212 | fp.close(); 213 | lp.close( sdf_size ); 214 | break; 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/gl_utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include 26 | 27 | enum ShaderType { 28 | VertexShader = GL_VERTEX_SHADER, FragmentShader = GL_FRAGMENT_SHADER 29 | }; 30 | 31 | struct VertexAttribType { 32 | GLuint gl_type; 33 | GLuint size; 34 | }; 35 | 36 | namespace vatypes { 37 | const VertexAttribType gl_byte = { GL_BYTE, 1 }; 38 | const VertexAttribType gl_ubyte = { GL_UNSIGNED_BYTE, 1 }; 39 | const VertexAttribType gl_short = { GL_SHORT, 2 }; 40 | const VertexAttribType gl_ushort = { GL_UNSIGNED_SHORT, 2 }; 41 | const VertexAttribType gl_int = { GL_INT, 4 }; 42 | const VertexAttribType gl_uint = { GL_UNSIGNED_INT, 4}; 43 | const VertexAttribType gl_float = { GL_FLOAT, 4 }; 44 | const VertexAttribType gl_fixed = { GL_FIXED, 4 }; 45 | } 46 | 47 | struct VertexAttrib { 48 | GLuint location; 49 | const char* name; 50 | GLuint size; 51 | VertexAttribType type; 52 | bool normalize; 53 | GLuint stride; 54 | GLvoid *offset; 55 | 56 | VertexAttrib(GLuint location, 57 | const char *name, 58 | GLuint size = 4, 59 | VertexAttribType type = vatypes::gl_float, 60 | bool normalize = false, 61 | GLvoid *offset = nullptr) : 62 | location(location), name(name), size(size), type(type), 63 | normalize(normalize), offset(offset) {} 64 | }; 65 | 66 | struct Uniform { 67 | char* name; 68 | GLuint program_id; 69 | GLint location; 70 | 71 | Uniform(const char *name) : name((char*)name), program_id(0), location(-1) {} 72 | Uniform( GLuint program_id, GLint location ) : 73 | name(nullptr), program_id( program_id ), location( location ) 74 | {} 75 | }; 76 | 77 | #define UNIFORM( type, name ) Uniform##type name { #name }; 78 | 79 | #define UNIFORM_MATRIX( size, name ) UniformMatrix##size name { #name }; 80 | 81 | 82 | using ProgramAction = void(*)( GLuint ); 83 | 84 | GLuint createVertexBuffer( GLenum usage, size_t size, const void *data = nullptr ); 85 | 86 | GLuint compileShader( const char *name, const char *source, ShaderType type = VertexShader ); 87 | 88 | bool linkProgram( GLuint program_id ); 89 | 90 | GLuint createProgram( const char* name, const char* vertex_shader, const char* fragment_shader, VertexAttrib *attribs = nullptr, size_t attrib_count = 0, ProgramAction before_link = 0 ); 91 | 92 | void deleteProgram( GLuint program ); 93 | 94 | void initUniforms( GLuint program_id, Uniform *uniform, size_t count = 1 ); 95 | 96 | template 97 | void initUniformStruct( GLuint program_id, T& unf_struct ) { 98 | initUniforms( program_id, (Uniform*) &unf_struct, sizeof(T) / sizeof( Uniform ) ); 99 | } 100 | 101 | GLuint vertexAttribsStride( VertexAttrib *attribs, size_t attrib_count ); 102 | 103 | void initVertexAttribs( VertexAttrib *attribs, size_t attrib_count, GLvoid *offset = nullptr, GLuint stride = 0 ); 104 | 105 | void bindAttribs( VertexAttrib *attribs, size_t attrib_count, size_t offset = 0 ); 106 | 107 | struct Uniform1i : Uniform { 108 | Uniform1i(const char* name) : Uniform(name) {}; 109 | 110 | void set(int v0) const { 111 | glUniform1i(location, v0); 112 | } 113 | }; 114 | 115 | struct Uniform1f : Uniform { 116 | using Uniform::Uniform; 117 | 118 | void set(float v0) const { 119 | glUniform1f(location, v0); 120 | } 121 | 122 | void setv(const float *v, GLsizei count = 1) const { 123 | glUniform1fv(location, count, v); 124 | } 125 | }; 126 | 127 | struct Uniform2f : Uniform { 128 | using Uniform::Uniform; 129 | 130 | void set(float v0, float v1) const { 131 | glUniform2f(location, v0, v1); 132 | } 133 | 134 | void setv(const float *v, GLsizei count = 1) const { 135 | glUniform2fv(location, count, v); 136 | } 137 | }; 138 | 139 | struct Uniform3f : Uniform { 140 | using Uniform::Uniform; 141 | 142 | void set(float v0, float v1, float v2) const { 143 | glUniform3f(location, v0, v1, v2); 144 | } 145 | 146 | void setv(const float *v, GLsizei count = 1) const { 147 | glUniform3fv(location, count, v); 148 | } 149 | }; 150 | 151 | struct Uniform4f : Uniform { 152 | using Uniform::Uniform; 153 | 154 | void set(float v0, float v1, float v2, float v3) const { 155 | glUniform4f(location, v0, v1, v2, v3); 156 | } 157 | 158 | void setv(float *v, GLsizei count = 1) const { 159 | glUniform4fv(location, count, v); 160 | } 161 | }; 162 | 163 | struct UniformMatrix2 : Uniform { 164 | using Uniform::Uniform; 165 | 166 | void setv(const float *v, GLsizei count = 1, GLboolean transpose = false) const { 167 | glUniformMatrix2fv(location, count, transpose, v); 168 | } 169 | }; 170 | 171 | struct UniformMatrix3 : Uniform { 172 | using Uniform::Uniform; 173 | 174 | void setv(const float *v, GLsizei count = 1, GLboolean transpose = false) const { 175 | glUniformMatrix3fv(location, count, transpose, v); 176 | } 177 | }; 178 | 179 | struct UniformMatrix4 : Uniform { 180 | using Uniform::Uniform; 181 | 182 | void setv(const float *v, GLsizei count = 1, GLboolean transpose = false) const { 183 | glUniformMatrix4fv(location, count, transpose, v); 184 | } 185 | }; 186 | 187 | struct UniformMatrix2x3 : Uniform { 188 | using Uniform::Uniform; 189 | 190 | void setv(const float *v, GLsizei count = 1, GLboolean transpose = false) const { 191 | glUniformMatrix2x3fv(location, count, transpose, v); 192 | } 193 | }; 194 | 195 | struct UniformMatrix3x2 : Uniform { 196 | using Uniform::Uniform; 197 | 198 | void setv(const float *v, GLsizei count = 1, GLboolean transpose = false) const { 199 | glUniformMatrix3x2fv(location, count, transpose, v); 200 | } 201 | }; 202 | 203 | struct UniformMatrix2x4 : Uniform { 204 | using Uniform::Uniform; 205 | 206 | void setv(const float *v, GLsizei count = 1, GLboolean transpose = false) const { 207 | glUniformMatrix2x4fv(location, count, transpose, v); 208 | } 209 | }; 210 | 211 | struct UniformMatrix4x2 : Uniform { 212 | using Uniform::Uniform; 213 | 214 | void setv(const float *v, GLsizei count = 1, GLboolean transpose = false) const { 215 | glUniformMatrix4x2fv(location, count, transpose, v); 216 | } 217 | }; 218 | 219 | struct UniformMatrix3x4 : Uniform { 220 | using Uniform::Uniform; 221 | 222 | void setv(const float *v, GLsizei count = 1, GLboolean transpose = false) const { 223 | glUniformMatrix3x4fv(location, count, transpose, v); 224 | } 225 | }; 226 | 227 | struct UniformMatrix4x3 : Uniform { 228 | using Uniform::Uniform; 229 | 230 | void setv(const float *v, GLsizei count = 1, GLboolean transpose = false) const { 231 | glUniformMatrix4x3fv(location, count, transpose, v); 232 | } 233 | }; 234 | -------------------------------------------------------------------------------- /src/sdf_atlas.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include "sdf_atlas.h" 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | void SdfAtlas::init( Font *font, float tex_width, float row_height, float sdf_size ) { 31 | this->font = font; 32 | 33 | glyph_rects.clear(); 34 | 35 | this->tex_width = tex_width; 36 | this->row_height = row_height; 37 | this->sdf_size = sdf_size; 38 | glyph_count = 0; 39 | posx = 0.0f; 40 | posy = 0.0f; 41 | max_height = row_height + sdf_size * 2.0f; 42 | } 43 | 44 | void SdfAtlas::allocate_codepoint( uint32_t codepoint ) { 45 | int glyph_idx = font->glyph_idx( codepoint ); 46 | if ( glyph_idx == -1 ) return; 47 | if ( glyph_idx == 0 ) return; 48 | const Glyph& g = font->glyphs[ glyph_idx ]; 49 | if ( g.command_count <= 2 ) return; 50 | 51 | float fheight = font->ascent - font->descent; 52 | float scale = row_height / fheight; 53 | float rect_width = ( g.max.x - g.min.x ) * scale + sdf_size * 2.0f; 54 | float row_and_border = row_height + sdf_size * 2.0f; 55 | 56 | if ( ( posx + rect_width ) > tex_width ) { 57 | posx = 0.0f; 58 | 59 | posy = ceil( posy + row_and_border ); 60 | max_height = ceil( posy + row_and_border ); 61 | } 62 | 63 | GlyphRect gr; 64 | gr.codepoint = codepoint; 65 | gr.glyph_idx = glyph_idx; 66 | gr.x0 = posx; 67 | gr.x1 = posx + rect_width; 68 | gr.y0 = posy; 69 | gr.y1 = posy + row_and_border; 70 | 71 | glyph_rects.push_back( gr ); 72 | 73 | posx = ceil( posx + rect_width ); 74 | glyph_count++; 75 | } 76 | 77 | void SdfAtlas::allocate_all_glyphs() { 78 | for ( auto kv : font->glyph_map ) { 79 | allocate_codepoint( kv.first ); 80 | } 81 | } 82 | 83 | void SdfAtlas::allocate_unicode_range( uint32_t start, uint32_t end ) { 84 | for ( uint32_t ucp = start; ucp <= end; ++ucp ) { 85 | allocate_codepoint( ucp ); 86 | } 87 | } 88 | 89 | void SdfAtlas::draw_glyphs( GlyphPainter& gp ) const { 90 | float fheight = font->ascent - font->descent; 91 | float scale = row_height / fheight; 92 | float baseline = -font->descent * scale; 93 | 94 | for ( size_t iglyph = 0; iglyph < glyph_rects.size(); ++iglyph ) { 95 | const GlyphRect& gr = glyph_rects[ iglyph ]; 96 | float left = font->glyphs[ gr.glyph_idx ].left_side_bearing * scale; 97 | F2 glyph_pos = F2 { gr.x0, gr.y0 + baseline } + F2 { sdf_size - left, sdf_size }; 98 | gp.draw_glyph( font, gr.glyph_idx, glyph_pos, scale, sdf_size ); 99 | } 100 | } 101 | 102 | std::string SdfAtlas::json( float tex_height, bool flip_texcoord_y ) const { 103 | float fheight = font->ascent - font->descent; 104 | float scaley = row_height / tex_height / fheight; 105 | float scalex = row_height / tex_width / fheight; 106 | 107 | const Glyph& gspace = font->glyphs[ font->glyph_idx( ' ' ) ]; 108 | const Glyph& gx = font->glyphs[ font->glyph_idx( 'x' ) ]; 109 | const Glyph& gxcap = font->glyphs[ font->glyph_idx( 'X' ) ]; 110 | 111 | std::unordered_set codepoints; 112 | for ( size_t igr = 0; igr < glyph_rects.size(); ++igr ) { 113 | codepoints.insert( glyph_rects[igr].codepoint ); 114 | } 115 | 116 | std::stringstream ss; 117 | ss << "{" << std::endl; 118 | ss << " ix: " << sdf_size / tex_width << ", " << std::endl; 119 | ss << " iy: " << sdf_size / tex_height << ", " << std::endl; 120 | ss << " row_height: " << ( row_height + 2.0f * sdf_size ) / tex_height << ", " << std::endl; 121 | ss << " aspect: " << tex_width / tex_height << ", " << std::endl; 122 | ss << " ascent: " << font->ascent * scaley << ", " << std::endl; 123 | ss << " descent: " << fabsf( font->descent * scaley ) << ", " << std::endl; 124 | ss << " line_gap: " << font->line_gap * scaley << ", " << std::endl; 125 | ss << " cap_height: " << gxcap.max.y * scaley << ", " << std::endl; 126 | ss << " x_height: " << gx.max.y * scaley << ", " << std::endl; 127 | ss << " space_advance: " << gspace.advance_width * scalex << ", " << std::endl << std::endl; 128 | 129 | ss << " chars: { " << std::endl; 130 | 131 | for ( size_t igr = 0; igr < glyph_rects.size(); ++igr ) { 132 | const GlyphRect& gr = glyph_rects[ igr ]; 133 | const Glyph& g = font->glyphs[ gr.glyph_idx ]; 134 | float tcy0 = gr.y0 / tex_height; 135 | float tcy1 = gr.y1 / tex_height; 136 | 137 | if ( flip_texcoord_y ) { 138 | tcy0 = 1.0 - gr.y1 / tex_height; 139 | tcy1 = 1.0 - gr.y0 / tex_height; 140 | } 141 | 142 | char ucp[32]; 143 | snprintf( ucp, 32, " \"\\u%04x\": {", gr.codepoint ); 144 | ss << ucp << std::endl; 145 | ss << " codepoint: " << gr.codepoint << "," << std::endl; 146 | ss << " rect: ["; 147 | ss << gr.x0 / tex_width << ", " << tcy0 << ", "; 148 | ss << gr.x1 / tex_width << ", " << tcy1 << "]," << std::endl; 149 | ss << " bearing_x: " << g.left_side_bearing * scalex << "," << std::endl; 150 | ss << " advance_x: " << g.advance_width * scalex << "," << std::endl; 151 | ss << " flags: " << (int)g.char_type << std::endl; 152 | ss << " }"; 153 | if ( igr != glyph_rects.size() - 1 ) ss << ","; 154 | ss << std::endl; 155 | } 156 | 157 | ss << " }, // end chars" << std::endl; 158 | 159 | ss << " kern: {" << std::endl; 160 | 161 | for ( auto kv : font->kern_map ) { 162 | uint32_t kern_pair = kv.first; 163 | float kern_value = kv.second * scalex; 164 | 165 | int kern_first_idx = ( kern_pair >> 16 ) & 0xffff; 166 | int kern_second_idx = kern_pair & 0xffff; 167 | 168 | auto it1 = font->cp_map.find( kern_first_idx ); 169 | auto it2 = font->cp_map.find( kern_second_idx ); 170 | 171 | if ( it1 == font->cp_map.end() || it2 == font->cp_map.end() ) { 172 | continue; 173 | } 174 | 175 | const std::vector& v1 = it1->second; 176 | const std::vector& v2 = it2->second; 177 | 178 | for ( uint32_t kern_first : v1 ) { 179 | for ( uint32_t kern_second : v2 ) { 180 | bool first_found = codepoints.find( kern_first ) != codepoints.end(); 181 | bool second_found = codepoints.find( kern_second ) != codepoints.end(); 182 | if ( first_found && second_found ) { 183 | char uckern[ 64 ]; 184 | snprintf( uckern, 64, " \"\\u%04x\\u%04x\" : ", kern_first, kern_second ); 185 | ss << uckern << kern_value << "," << std::endl; 186 | } 187 | } 188 | } 189 | } 190 | 191 | ss << " } // end kern" << std::endl; 192 | 193 | ss << "}; // end font" << std::endl; 194 | 195 | return ss.str(); 196 | } 197 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "float2.h" 32 | #include "args_parser.h" 33 | #include "sdf_gl.h" 34 | #include "sdf_atlas.h" 35 | #include "glyph_painter.h" 36 | #include "font.h" 37 | 38 | #define STB_IMAGE_WRITE_IMPLEMENTATION 39 | #include "../third_party/stb_image_write.h" 40 | 41 | ArgsParser args; 42 | SdfGl sdf_gl; 43 | SdfAtlas sdf_atlas; 44 | Font font; 45 | GlyphPainter gp; 46 | 47 | int max_tex_size = 2048; 48 | int width = 1024; 49 | int height = 0; 50 | int row_height = 96; 51 | int border_size = 16; 52 | 53 | std::string filename; 54 | std::string res_filename; 55 | F2 tex_size = F2( 1024, 1024 ); 56 | 57 | 58 | struct UnicodeRange { 59 | uint32_t start; 60 | uint32_t end; 61 | }; 62 | 63 | std::vector unicode_ranges; 64 | 65 | 66 | std::string help = R"(Program for generating signed distance field font atlas. 67 | Given TTF file, generates PNG image and JSON with glyph rectangles and metrics. 68 | Copyright: ©2019 Anton Stiopin, astiopin@gmail.com 69 | License: MIT 70 | Usage: sdf_atlas -f font_file.ttf [options] 71 | Options: 72 | -h this help 73 | -o 'filename' output file name (without extension) 74 | -tw 'size' atlas image width in pixels, default 1024 75 | -th 'size' atlas image height in pixels (optional) 76 | -ur 'ranges' unicode ranges 'start1:end1,start:end2,single_codepoint' without spaces, 77 | default: 31:126,0xffff 78 | -bs 'size' SDF distance in pixels, default 16 79 | -rh 'size' row height in pixels (without SDF border), default 96 80 | Example: 81 | sdf_atlas -f Roboto-Regular.ttf -o roboto -tw 2048 -th 2048 -bs 22 -rh 70 -ur 31:126,0xA0:0xFF,0x400:0x4FF,0xFFFF 82 | )"; 83 | 84 | void show_help( ArgsParser* ) { 85 | std::cout << help; 86 | exit( 0 ); 87 | } 88 | 89 | void read_filename( ArgsParser* ap ) { 90 | filename = ap->word(); 91 | } 92 | 93 | void read_res_filename( ArgsParser* ap ) { 94 | res_filename = ap->word(); 95 | } 96 | 97 | void read_tex_width( ArgsParser *ap ) { 98 | errno = 0; 99 | width = strtol( ap->word().c_str(), nullptr, 0 ); 100 | if ( errno != 0 || width <= 0 ) { 101 | std::cerr << "Error reading texture width." << std::endl; 102 | exit( 1 ); 103 | } 104 | if ( width > max_tex_size ) { 105 | std::cerr << "Maximum texture size is " << max_tex_size << ". Clamping width." << std::endl; 106 | width = max_tex_size; 107 | } 108 | }; 109 | 110 | void read_tex_height( ArgsParser *ap ) { 111 | errno = 0; 112 | height = strtol( ap->word().c_str(), nullptr, 0 ); 113 | if ( errno != 0 || height <= 0 ) { 114 | std::cerr << "Error reading texture height." << std::endl; 115 | exit( 1 ); 116 | } 117 | if ( height > max_tex_size ) { 118 | std::cout << "Height exceeds maximum texture size. Setting to " << max_tex_size << ".\n"; 119 | height = max_tex_size; 120 | } 121 | }; 122 | 123 | void read_row_height( ArgsParser *ap ) { 124 | errno = 0; 125 | row_height = strtol( ap->word().c_str(), nullptr, 0 ); 126 | if ( errno != 0 || row_height <= 4 ) { 127 | std::cerr << "Error reading row height." << std::endl; 128 | exit( 1 ); 129 | } 130 | } 131 | 132 | void read_border_size( ArgsParser *ap ) { 133 | errno = 0; 134 | border_size = strtol( ap->word().c_str(), nullptr, 0 ); 135 | if ( errno != 0 || border_size <= 0 ) { 136 | std::cerr << "Error reading border size." << std::endl; 137 | exit( 1 ); 138 | } 139 | } 140 | 141 | void read_unicode_ranges( ArgsParser *ap ) { 142 | errno = 0; 143 | int range_start = 0; 144 | int range_end = 0; 145 | 146 | std::string nword = ap->word(); 147 | char *pos = const_cast( nword.c_str() ); 148 | 149 | for(;;) { 150 | errno = 0; 151 | char *new_pos = pos; 152 | range_start = strtol( pos, &new_pos, 0 ); 153 | if ( errno != 0 || range_start < 0 ) { 154 | std::cerr << "Error reading unicode ranges" << std::endl; 155 | exit( 1 ); 156 | } 157 | range_end = range_start; 158 | 159 | pos = new_pos; 160 | char lim = *pos++; 161 | 162 | if ( lim == ':' ) { 163 | errno = 0; 164 | range_end = strtol( pos, &new_pos, 0 ); 165 | if ( errno != 0 || range_end < 0 ) { 166 | std::cerr << "Error reading unicode ranges" << std::endl; 167 | exit( 1 ); 168 | } 169 | pos = new_pos; 170 | lim = *pos++; 171 | } 172 | 173 | if ( lim == ',' ) { 174 | unicode_ranges.push_back( UnicodeRange { (uint32_t) range_start, (uint32_t) range_end } ); 175 | continue; 176 | } else if ( lim == 0 ) { 177 | unicode_ranges.push_back( UnicodeRange { (uint32_t) range_start, (uint32_t) range_end } ); 178 | return; 179 | } else { 180 | std::cerr << "Error reading unicode ranges" << std::endl; 181 | exit( 1 ); 182 | } 183 | } 184 | }; 185 | 186 | void render() { 187 | glClearColor( 0.0, 0.0, 0.0, 0.0 ); 188 | glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); 189 | 190 | F2 tex_size = F2( width, height ); 191 | 192 | glViewport( 0, 0, width, height ); 193 | sdf_gl.render_sdf( tex_size, gp.fp.vertices, gp.lp.vertices ); 194 | } 195 | 196 | 197 | 198 | int main( int argc, char* argv[] ) { 199 | if ( argc == 1 ) { 200 | std::cout << help; 201 | exit( 0 ); 202 | } 203 | 204 | if ( !glfwInit() ) { 205 | std::cerr << "GLFW initailization error" << std::endl; 206 | exit( 1 ); 207 | } 208 | 209 | glfwWindowHint( GLFW_VISIBLE, GL_FALSE ); 210 | GLFWwindow *window = glfwCreateWindow( 1, 1, "sdf_atlas", nullptr, nullptr ); 211 | if ( !window ) { 212 | std::cerr << "GLFW error creating window" << std::endl; 213 | glfwTerminate(); 214 | exit( 1 ); 215 | } 216 | 217 | glfwSetWindowSize( window, 640, 480 ); 218 | glfwMakeContextCurrent( window ); 219 | 220 | GLenum err = glewInit(); 221 | if ( err != GLEW_OK ) { 222 | std::cerr << "GLEW init error: " << glewGetErrorString( err ) << std::endl; 223 | exit( 1 ); 224 | } 225 | 226 | // Reading command line parameters 227 | 228 | glGetIntegerv( GL_MAX_RENDERBUFFER_SIZE, &max_tex_size ); 229 | 230 | args.commands["-h"] = show_help; 231 | args.commands["-f"] = read_filename; 232 | args.commands["-o"] = read_res_filename; 233 | args.commands["-tw"] = read_tex_width; 234 | args.commands["-th"] = read_tex_height; 235 | args.commands["-ur"] = read_unicode_ranges; 236 | args.commands["-bs"] = read_border_size; 237 | args.commands["-rh"] = read_row_height; 238 | args.run( argc, argv ); 239 | 240 | if ( filename.empty() ) { 241 | std::cerr << "Input file not specified" << std::endl; 242 | exit( 1 ); 243 | } 244 | 245 | if ( res_filename.empty() ) { 246 | size_t ext_dot = filename.find_last_of( "." ); 247 | if ( ext_dot == std::string::npos ) { 248 | res_filename = filename; 249 | } else { 250 | res_filename = filename.substr( 0, ext_dot ); 251 | } 252 | } 253 | 254 | if ( !font.load_ttf_file( filename.c_str() ) ) { 255 | std::cerr << "Error reading TTF file '" << filename << "' " << std::endl; 256 | exit( 1 ); 257 | } 258 | 259 | // Allocating glyph rects 260 | 261 | sdf_atlas.init( &font, width, row_height, border_size ); 262 | 263 | if ( unicode_ranges.empty() ) { 264 | sdf_atlas.allocate_unicode_range( 0x21, 0x7e ); 265 | sdf_atlas.allocate_unicode_range( 0xffff, 0xffff ); 266 | } else { 267 | for ( const UnicodeRange& ur : unicode_ranges ) { 268 | sdf_atlas.allocate_unicode_range( ur.start, ur.end ); 269 | } 270 | } 271 | 272 | sdf_atlas.draw_glyphs( gp ); 273 | 274 | std::cout << "Allocated " << sdf_atlas.glyph_count << " glyphs" << std::endl; 275 | std::cout << "Atlas maximum height is " << sdf_atlas.max_height << std::endl; 276 | 277 | if ( height == 0 ) { 278 | height = sdf_atlas.max_height; 279 | } 280 | 281 | uint8_t* picbuf = (uint8_t*) malloc( width * height ); 282 | 283 | // GL initialization 284 | 285 | sdf_gl.init(); 286 | 287 | GLuint rbcolor; 288 | glGenRenderbuffers( 1, &rbcolor ); 289 | glBindRenderbuffer( GL_RENDERBUFFER, rbcolor ); 290 | glRenderbufferStorage( GL_RENDERBUFFER, GL_RED, width, height ); 291 | glBindRenderbuffer( GL_RENDERBUFFER, 0 ); 292 | 293 | GLuint rbds; 294 | glGenRenderbuffers( 1, &rbds ); 295 | glBindRenderbuffer( GL_RENDERBUFFER, rbds ); 296 | glRenderbufferStorage( GL_RENDERBUFFER, GL_DEPTH_STENCIL, width, height ); 297 | glBindRenderbuffer( GL_RENDERBUFFER, 0 ); 298 | 299 | GLuint fbo; 300 | glGenFramebuffers( 1, &fbo ); 301 | glBindFramebuffer( GL_FRAMEBUFFER, fbo ); 302 | glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbcolor ); 303 | glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbds ); 304 | 305 | if ( glCheckFramebufferStatus( GL_FRAMEBUFFER ) != GL_FRAMEBUFFER_COMPLETE ) { 306 | std::cerr << "Error creating framebuffer!" << std::endl; 307 | exit( 1 ); 308 | } 309 | 310 | // Rendering glyphs 311 | 312 | glViewport( 0, 0, width, height ); 313 | glClearColor( 0.0, 0.0, 0.0, 0.0 ); 314 | glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); 315 | sdf_gl.render_sdf( F2( width, height ), gp.fp.vertices, gp.lp.vertices ); 316 | 317 | glReadPixels( 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, picbuf ); 318 | 319 | glBindFramebuffer( GL_FRAMEBUFFER, 0 ); 320 | glFinish(); 321 | 322 | // Flipping the picture vertically 323 | 324 | uint8_t *row_swap = (uint8_t*) malloc( width ); 325 | 326 | for ( int iy = 0; iy < height / 2; ++iy ) { 327 | uint8_t* row0 = picbuf + iy * width; 328 | uint8_t* row1 = picbuf + ( height - 1 - iy ) * width; 329 | memcpy( row_swap, row0, width ); 330 | memcpy( row0, row1, width ); 331 | memcpy( row1, row_swap, width ); 332 | } 333 | 334 | free( row_swap ); 335 | 336 | // Saving the picture 337 | 338 | std::string png_filename = res_filename + ".png"; 339 | if ( !stbi_write_png( png_filename.c_str(), width, height, 1, picbuf, width ) ) { 340 | std::cout << "Error writing png file." << std::endl; 341 | exit( 1 ); 342 | } 343 | 344 | free( picbuf ); 345 | 346 | // Saving JSON 347 | 348 | std::string json = sdf_atlas.json( height ); 349 | std::ofstream json_file; 350 | json_file.open( res_filename + ".js" ); 351 | if ( !json_file ) { 352 | std::cout << "Error writing json file." << std::endl; 353 | } 354 | json_file << json; 355 | json_file.close(); 356 | 357 | glfwTerminate(); 358 | 359 | return 0; 360 | } 361 | -------------------------------------------------------------------------------- /src/font.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Anton Stiopin astiopin@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | #include "font.h" 24 | #include 25 | #include 26 | #include 27 | 28 | 29 | // Convert high-endian TTF values to low-endian 30 | // TODO support for high-endian architectures 31 | 32 | inline uint16_t ttf_u16( const uint8_t *p ) { return p[0] * 256 + p[1]; } 33 | inline uint32_t ttf_u32( const uint8_t *p ) { return ( p[0] << 24 ) + ( p[1] << 16 ) + ( p[2] << 8 ) + p[3]; } 34 | 35 | inline int16_t ttf_i16( const uint8_t *p ) { return p[0] * 256 + p[1]; } 36 | inline int32_t ttf_i32( const uint8_t *p ) { return ( p[0] << 24 ) + ( p[1] << 16 ) + ( p[2] << 8 ) + p[3]; } 37 | 38 | 39 | inline bool check_tag( const uint8_t *d, const char *tag ) { 40 | bool match = d[0] == tag[0] && 41 | d[1] == tag[1] && 42 | d[2] == tag[2] && 43 | d[3] == tag[3]; 44 | return match; 45 | } 46 | 47 | 48 | inline bool is_font( const uint8_t *ttf ) { 49 | bool res = check_tag( ttf, "1\0\0\0" ) 50 | || check_tag( ttf, "\0\1\0\0" ) 51 | || check_tag( ttf, "typ1" ) 52 | || check_tag( ttf, "OTTO" ); 53 | return res; 54 | } 55 | 56 | 57 | static const uint8_t* find_table( const uint8_t *ttf, const char *tag ) { 58 | uint32_t num_tables = ttf_u16( ttf + 4 ); 59 | const uint8_t *table = ttf + 12; 60 | 61 | for ( uint32_t itbl = 0; itbl < num_tables; ++itbl ) { 62 | if ( check_tag( table, tag ) ) { 63 | uint32_t offset = ttf_u32( table + 8 ); 64 | return ttf + offset; 65 | } 66 | table += 16; 67 | } 68 | 69 | return nullptr; 70 | } 71 | 72 | 73 | 74 | // Reading mappings from codepoint to glyph index 75 | 76 | static bool fill_cmap( Font& font, const uint8_t *ttf ) { 77 | const uint8_t *cmap = find_table( ttf, "cmap" ); 78 | if ( !cmap ) return false; 79 | 80 | uint32_t num_tables = ttf_u16( cmap + 2 ); 81 | const uint8_t *imap = nullptr; 82 | 83 | for ( size_t itbl = 0; itbl < num_tables; ++itbl ) { 84 | const uint8_t *enc_table = cmap + 4 + 8 * itbl; 85 | uint16_t platform = ttf_u16( enc_table ); 86 | uint16_t encoding = ttf_u16( enc_table + 2 ); 87 | uint32_t offset = ttf_u32( enc_table + 4 ); 88 | 89 | if ( platform == 0 ) { // Unicode 90 | imap = cmap + offset; 91 | break; 92 | } 93 | if ( platform == 3 ) { // MS 94 | if ( encoding == 1 || encoding == 10 ) { 95 | imap = cmap + offset; 96 | break; 97 | } 98 | } 99 | } 100 | if ( !imap ) return false; 101 | 102 | uint16_t format = ttf_u16( imap ); 103 | 104 | if ( format == 0 ) { 105 | const uint8_t *idx_data = imap + 6; 106 | for ( uint32_t i = 1; i < 256; ++i ) { 107 | int idx = (int) ( idx_data[i] ); 108 | font.glyph_map.insert( { i, idx } ); 109 | } 110 | return true; 111 | 112 | } else if ( format == 4 ) { 113 | uint32_t seg_count = ttf_u16( imap + 6 ) >> 1; 114 | const uint8_t *end_code = imap + 7 * 2; 115 | const uint8_t *start_code = end_code + 2 + seg_count * 2; 116 | const uint8_t *offset = imap + 8 * 2 + seg_count * 2 * 3; 117 | const uint8_t *delta = imap + 8 * 2 + seg_count * 2 * 2; 118 | 119 | for ( uint32_t iseg = 0; iseg < seg_count; iseg++ ) { 120 | uint32_t seg_start = ttf_u16( start_code + iseg * 2 ); 121 | uint32_t seg_end = ttf_u16( end_code + iseg * 2 ); 122 | uint32_t seg_offset = ttf_u16( offset + iseg * 2 ); 123 | int32_t seg_delta = ttf_i16( delta + iseg * 2 ); 124 | 125 | if ( seg_offset == 0 ) { 126 | for ( uint32_t cp = seg_start; cp <= seg_end; ++cp ) { 127 | int32_t idx = (uint16_t) ( cp + seg_delta ); 128 | font.glyph_map.insert( { cp, idx } ); 129 | } 130 | } else { 131 | for ( uint32_t cp = seg_start; cp <= seg_end; ++cp ) { 132 | uint32_t item = cp - seg_start; 133 | int32_t idx = ttf_i16( offset + iseg * 2 + seg_offset + item * 2 ); 134 | font.glyph_map.insert( { cp, idx } ); 135 | } 136 | } 137 | } 138 | return true; 139 | 140 | } else if ( format == 6 ) { 141 | uint32_t first = ttf_u16( imap + 6 ); 142 | uint32_t count = ttf_u16( imap + 8 ); 143 | const uint8_t *idx_data = imap + 10; 144 | 145 | for ( uint32_t i = 0; i < count; ++i ) { 146 | uint32_t idx = ttf_u16( idx_data + i * 2 ); 147 | font.glyph_map.insert( { i + first, idx } ); 148 | } 149 | return true; 150 | 151 | } else if ( format == 10 ) { 152 | uint32_t first_char = ttf_u32( imap + 12 ); 153 | uint32_t num_chars = ttf_u32( imap + 16 ); 154 | const uint8_t *idx_data = imap + 20; 155 | 156 | for ( uint32_t i = 0; i < num_chars; ++i ) { 157 | uint32_t idx = ttf_u16( idx_data + i * 2 ); 158 | font.glyph_map.insert( { i + first_char, idx } ); 159 | } 160 | return true; 161 | 162 | } else if ( format == 12 ) { 163 | uint32_t ngroups = ttf_u32( imap + 12 ); 164 | const uint8_t *sm_group = imap + 16; 165 | 166 | for ( uint32_t i = 0; i < ngroups; ++i ) { 167 | uint32_t start_code = ttf_u32( sm_group ); 168 | uint32_t end_code = ttf_u32( sm_group + 4 ); 169 | uint32_t start_idx = ttf_u32( sm_group + 8 ); 170 | 171 | for ( uint32_t icode = start_code; icode <= end_code; ++icode ) { 172 | uint32_t idx = start_idx + icode - start_code; 173 | font.glyph_map.insert( { icode, idx } ); 174 | } 175 | 176 | sm_group += 12; 177 | } 178 | return true; 179 | 180 | } else if ( format == 13 ) { 181 | uint32_t ngroups = ttf_u32( imap + 12 ); 182 | const uint8_t *sm_group = imap + 16; 183 | 184 | for ( uint32_t i = 0; i < ngroups; ++i ) { 185 | uint32_t start_code = ttf_u32( sm_group ); 186 | uint32_t end_code = ttf_u32( sm_group + 4 ); 187 | uint32_t glyph_idx = ttf_u32( sm_group + 8 ); 188 | 189 | for ( uint32_t icode = start_code; icode <= end_code; ++icode ) { 190 | font.glyph_map.insert( { icode, glyph_idx } ); 191 | } 192 | 193 | sm_group += 12; 194 | } 195 | return true; 196 | } 197 | 198 | return false; 199 | } 200 | 201 | 202 | 203 | // Glyph offset in 'glyf' table 204 | 205 | inline int glyph_loc_offset( int glyph_idx, bool is_loc32, const uint8_t *loca ) { 206 | uint32_t off0, off1; 207 | if ( is_loc32 ) { 208 | off0 = ttf_u32( loca + glyph_idx * 4 ); 209 | off1 = ttf_u32( loca + glyph_idx * 4 + 4 ); 210 | } else { 211 | off0 = ttf_u16( loca + glyph_idx * 2 ) * 2; 212 | off1 = ttf_u16( loca + glyph_idx * 2 + 2 ) * 2; 213 | } 214 | if ( off0 == off1 ) return -1; 215 | else return off0; 216 | } 217 | 218 | 219 | 220 | // Display list for simple (non composite) glyph 221 | 222 | static void glyph_shape_simple( Glyph& glyph, std::vector& commands, const uint8_t *glyph_loc, float scale ) { 223 | int num_contours = ttf_i16( glyph_loc ); 224 | 225 | if ( num_contours < 0 ) return; 226 | 227 | // Indices for the last point of each countour 228 | const uint8_t *end_pts = glyph_loc + 10; 229 | // Size of the byte code instructions, skipping this 230 | size_t icount = ttf_u16( end_pts + num_contours * 2 ); 231 | // Number of control points 232 | size_t num_pts = ttf_u16( end_pts + num_contours * 2 - 2 ) + 1; 233 | 234 | const uint8_t *flag_array = end_pts + num_contours * 2 + 2 + icount; 235 | 236 | glyph.command_start = commands.size(); 237 | 238 | const uint8_t *fpos = flag_array; 239 | int fcount = num_pts; 240 | size_t xbytes = 0; 241 | 242 | // Calculating offsets of point coordinate tables 243 | while ( fcount > 0 ) { 244 | uint8_t flag = fpos[0]; 245 | uint8_t frepeat = fpos[1]; 246 | size_t xsize = ( flag & 0x02 ) ? 1 : ( flag & 0x10 ) ? 0 : 2; 247 | 248 | if ( flag & 0x08 ) { 249 | fcount -= frepeat + 1; 250 | fpos += 2; 251 | xbytes += xsize * ( frepeat + 1 ); 252 | } else { 253 | fcount--; 254 | fpos++; 255 | xbytes += xsize; 256 | } 257 | } 258 | 259 | const uint8_t *xcoord = fpos; 260 | const uint8_t *ycoord = xcoord + xbytes; 261 | 262 | // Flag bits: 263 | // 0x01 - on-curve, ~0x01 - off-curve 264 | // Two consecutive off-curve points assume on-curve point between them 265 | // 266 | // 0x02 - x-coord is 8-bit unsigned integer 267 | // 0x10 - positive, ~0x10 - negative 268 | // ~0x02 - x-coord is 16-bit signed integer 269 | // ~0x02 & 0x10 - x-coord equals x-coord of the previous point 270 | // 271 | // 0x04 - y-coord is 8-bit unsigned integer 272 | // 0x20 - positive, ~0x20 - negative 273 | // ~0x04 - y-coord is 16-bit signed integer 274 | // ~0x04 & 0x20 - y-coord equals y-coord of the previous point 275 | // 276 | // 0x08 - repeat flag N times, read next byte for N 277 | 278 | // Current contour point coordinates 279 | F2 cur_pos { 0.0f }; 280 | // Previous contour point coordinates 281 | F2 prev_pos { 0.0f }; 282 | 283 | bool prev_on_curve = true; // previous point was on-curve 284 | bool on_curve = true; // current point is on-curve 285 | 286 | size_t iflag = 0; // next flag index 287 | uint8_t flag = 0; // current flag value 288 | 289 | size_t gc_contour_start_idx = 0; // Index of the first control point of the contour 290 | bool contour_starts_off_curve = false; 291 | bool new_contour = true; // Current command starts new contour 292 | 293 | size_t icontour = 0; // Next contour starting index 294 | 295 | GlyphCommand command; 296 | command.type = GlyphCommand::MoveTo; 297 | command.p0 = F2{ 0.0f }; 298 | command.p1 = F2{ 0.0f }; 299 | 300 | // Filling glyph display list 301 | 302 | for ( size_t ipoint = 0; ipoint < num_pts; ++ipoint ) { 303 | if ( ipoint == iflag ) { 304 | flag = flag_array[0]; 305 | size_t frepeat = flag_array[1]; 306 | 307 | if ( flag & 0x08 ) { 308 | // Repeat flag 309 | iflag = ipoint + frepeat + 1; 310 | flag_array += 2; 311 | } else { 312 | // Do not repeat flag 313 | iflag = ipoint + 1; 314 | flag_array++; 315 | } 316 | } 317 | 318 | prev_on_curve = on_curve; 319 | on_curve = flag & 0x01; 320 | 321 | prev_pos = cur_pos; 322 | 323 | if ( flag & 0x02 ) { 324 | // X-coord is 8 bit value 325 | float dx = xcoord[0]; 326 | cur_pos.x += ( flag & 0x10 ) ? dx : -dx; // X-coord sign 327 | xcoord++; 328 | } else { 329 | if ( !( flag & 0x10 ) ) { 330 | // X-coord is 16 bit value 331 | cur_pos.x += ttf_i16( xcoord ); 332 | xcoord += 2; 333 | } 334 | } 335 | 336 | if ( flag & 0x04 ) { 337 | // Y-coord is 8-bit value 338 | float dy = ycoord[0]; 339 | cur_pos.y += ( flag & 0x20 ) ? dy : -dy; // Y-coord sign 340 | ycoord++; 341 | } else { 342 | if ( !( flag & 0x20 ) ) { 343 | // Y-coord is 16-bit value 344 | cur_pos.y += ttf_i16( ycoord ); 345 | ycoord += 2; 346 | } 347 | } 348 | 349 | if ( new_contour ) { 350 | // Push MoveTo command if starting new contour 351 | contour_starts_off_curve = !on_curve; 352 | gc_contour_start_idx = commands.size(); 353 | command.type = GlyphCommand::MoveTo; 354 | command.p0 = scale * cur_pos; 355 | command.p1 = F2{ 0.0f }; 356 | 357 | commands.push_back( command ); 358 | 359 | icontour = ttf_u16( end_pts ); 360 | end_pts += 2; 361 | new_contour = false; 362 | } else { 363 | if ( on_curve ) { 364 | if ( prev_on_curve ) { 365 | // Normal (non smooth) control point, pushing LineTo 366 | command.p0 = scale * cur_pos; 367 | command.p1 = F2{ 0.0f }; 368 | command.type = GlyphCommand::LineTo; 369 | commands.push_back( command ); 370 | } else { 371 | // Normal control point, pushing BezTo 372 | command.p0 = scale * prev_pos; 373 | command.p1 = scale * cur_pos; 374 | command.type = GlyphCommand::BezTo; 375 | commands.push_back( command ); 376 | } 377 | } else { 378 | if ( !prev_on_curve ) { 379 | // Smooth curve, inserting control point in the middle 380 | F2 mid_cp = 0.5f * ( prev_pos + cur_pos ); 381 | command.p0 = scale * prev_pos; 382 | command.p1 = scale * mid_cp; 383 | command.type = GlyphCommand::BezTo; 384 | commands.push_back( command ); 385 | } 386 | } 387 | } 388 | 389 | // Closing contour 390 | if ( icontour == ipoint && ipoint > 0 ) { 391 | if ( contour_starts_off_curve ) { 392 | if ( on_curve ) { 393 | // Contour starts off-curve, contour start to current point 394 | commands[ gc_contour_start_idx ].p0 = scale * cur_pos; 395 | } else { 396 | // Contour starts and ends off-curve, 397 | // calculating contour starting point, setting first MoveTo P0, 398 | // and closing contour with BezTo 399 | 400 | F2 cpos = scale * cur_pos; 401 | F2 next_cp = commands[ gc_contour_start_idx + 1 ].p0; // First BezTo off-curve CP 402 | F2 pos = 0.5f * ( cpos + next_cp ); // Contour start point 403 | commands[ gc_contour_start_idx ].p0 = pos; 404 | 405 | command.p0 = cpos; 406 | command.p1 = pos; 407 | command.type = GlyphCommand::BezTo; 408 | commands.push_back( command ); 409 | } 410 | } else { 411 | if ( !on_curve ) { 412 | // Contour ends off-curve, closing contour with BezTo to contour starting point 413 | 414 | F2 start_pos = commands[ gc_contour_start_idx ].p0; 415 | 416 | command.p0 = scale * cur_pos; 417 | command.p1 = start_pos; 418 | command.type = GlyphCommand::BezTo; 419 | commands.push_back( command ); 420 | } 421 | } 422 | // Pushing ClosePath command 423 | command.type = GlyphCommand::ClosePath; 424 | command.p0 = F2{ 0.0f }; 425 | command.p1 = F2{ 0.0f }; 426 | commands.push_back( command ); 427 | new_contour = true; 428 | } 429 | } 430 | 431 | glyph.command_count = commands.size() - glyph.command_start; 432 | } 433 | 434 | 435 | // Composite glyphs will have a display list of all their subglyphs combined with transformation applied 436 | 437 | static void glyph_commands_composite( Font& font, int glyph_idx ) { 438 | Glyph &glyph = font.glyphs[ glyph_idx ]; 439 | if ( !glyph.is_composite ) return; 440 | glyph.command_start = font.glyph_commands.size(); 441 | glyph.command_count = 0; 442 | 443 | for ( int icomp = glyph.components_start; icomp < glyph.components_start + glyph.components_count; ++icomp ) { 444 | GlyphComponent& gcomp = font.glyph_components[ icomp ]; 445 | const Glyph& cglyph = font.glyphs[ gcomp.glyph_idx ]; 446 | const Mat2d& tr = gcomp.transform; 447 | 448 | for ( int icommand = cglyph.command_start; icommand < cglyph.command_start + cglyph.command_count; ++icommand ) { 449 | const GlyphCommand& gcommand = font.glyph_commands[ icommand ]; 450 | GlyphCommand new_command; 451 | new_command.type = gcommand.type; 452 | 453 | switch ( gcommand.type ) { 454 | case GlyphCommand::MoveTo: 455 | case GlyphCommand::LineTo: 456 | new_command.p0 = tr * gcommand.p0; 457 | break; 458 | case GlyphCommand::BezTo: 459 | new_command.p0 = tr * gcommand.p0; 460 | new_command.p1 = tr * gcommand.p1; 461 | break; 462 | case GlyphCommand::ClosePath: 463 | break; 464 | } 465 | font.glyph_commands.push_back( new_command ); 466 | } 467 | } 468 | 469 | glyph.command_count = font.glyph_commands.size() - glyph.command_start; 470 | } 471 | 472 | 473 | // Reading glyph display list or subglyphs of a composite glyph. 474 | 475 | static void glyph_shape( Font& font, int glyph_idx, bool is_loc32, const uint8_t *loca, const uint8_t *glyf, float scale ) { 476 | Glyph &glyph = font.glyphs[ glyph_idx ]; 477 | 478 | int glyph_offset = glyph_loc_offset( glyph_idx, is_loc32, loca ); 479 | if ( glyph_offset < 0 ) return; 480 | 481 | const uint8_t *glyph_loc = glyf + glyph_offset; 482 | int num_contours = ttf_i16( glyph_loc ); 483 | 484 | float minx = ttf_i16( glyph_loc + 2 ); 485 | float miny = ttf_i16( glyph_loc + 4 ); 486 | float maxx = ttf_i16( glyph_loc + 6 ); 487 | float maxy = ttf_i16( glyph_loc + 8 ); 488 | 489 | glyph.min = scale * F2{ minx, miny }; 490 | glyph.max = scale * F2{ maxx, maxy }; 491 | 492 | // Simple glyph 493 | if ( num_contours > 0 ) { 494 | glyph_shape_simple( glyph, font.glyph_commands, glyph_loc, scale ); 495 | 496 | // Composite glyph 497 | } else if ( num_contours < 0 ) { 498 | glyph.is_composite = true; 499 | glyph.components_start = font.glyph_components.size(); 500 | 501 | bool next_comp = true; 502 | const uint8_t *pos = glyph_loc + 10; 503 | 504 | while( next_comp ) { 505 | uint16_t flags = ttf_u16( pos ); 506 | uint32_t comp_glyph_idx = ttf_u16( pos + 2 ); 507 | pos += 4; 508 | 509 | Mat2d gtr { 1.0f }; 510 | 511 | // Component position 512 | if ( flags & 2 ) { 513 | if ( flags & 1 ) { 514 | gtr[2][0] = ttf_i16( pos ) * scale; pos += 2; 515 | gtr[2][1] = ttf_i16( pos ) * scale; pos += 2; 516 | } else { 517 | gtr[2][0] = ( (int8_t) *pos ) * scale; pos++; 518 | gtr[2][1] = ( (int8_t) *pos ) * scale; pos++; 519 | } 520 | } else { 521 | assert( false ); 522 | } 523 | 524 | // Component rotation and scale 525 | if ( flags & ( 1 << 3 ) ) { 526 | // Uniform scale 527 | gtr[0][0] = gtr[1][1] = ttf_i16( pos ) / 16384.0f; pos += 2; 528 | } else if ( flags & ( 1 << 6 ) ) { 529 | // XY-scale 530 | gtr[0][0] = ttf_i16( pos ) / 16384.0f; pos += 2; 531 | gtr[1][1] = ttf_i16( pos ) / 16384.0f; pos += 2; 532 | } else if ( flags & ( 1 << 7 ) ) { 533 | // Rotion matrix 534 | gtr[0][0] = ttf_i16( pos ) / 16384.0f; pos += 2; 535 | gtr[0][1] = ttf_i16( pos ) / 16384.0f; pos += 2; 536 | gtr[1][0] = ttf_i16( pos ) / 16384.0f; pos += 2; 537 | gtr[1][1] = ttf_i16( pos ) / 16384.0f; pos += 2; 538 | } 539 | 540 | GlyphComponent gc; 541 | gc.glyph_idx = comp_glyph_idx; 542 | gc.transform = gtr; 543 | font.glyph_components.push_back( gc ); 544 | 545 | // More components? 546 | next_comp = flags & ( 1 << 5 ); 547 | } 548 | glyph.components_count = font.glyph_components.size() - glyph.components_start; 549 | } 550 | } 551 | 552 | 553 | 554 | // Reading kerning table 555 | 556 | static bool fill_kern( Font& font, const uint8_t *ttf, float scale ) { 557 | const uint8_t *kern = find_table( ttf, "kern" ); 558 | if ( !kern ) return false; 559 | 560 | uint16_t num_tables = ttf_u16( kern + 2 ); 561 | const uint8_t *table = nullptr; 562 | const uint8_t *pos = kern + 4; 563 | 564 | for ( size_t itbl = 0; itbl < num_tables; ++itbl ) { 565 | uint16_t length = ttf_u16( pos + 2 ); 566 | uint16_t coverage = ttf_u16( pos + 4 ); 567 | 568 | if ( coverage == 1 ) { 569 | table = pos; 570 | break; 571 | } 572 | pos += length; 573 | } 574 | 575 | if ( !table ) return false; 576 | 577 | uint32_t num_pairs = ttf_u16( table + 6 ); 578 | pos = table + 14; 579 | 580 | for ( uint32_t ipair = 0; ipair < num_pairs; ++ipair ) { 581 | uint32_t left = ttf_u16( pos ); 582 | uint32_t right = ttf_u16( pos + 2 ); 583 | int32_t kern = ttf_i16( pos + 4 ); 584 | uint32_t pair = ( left << 16 ) | right; 585 | font.kern_map.insert( { pair, kern * scale } ); 586 | pos += 6; 587 | } 588 | 589 | return true; 590 | } 591 | 592 | bool Font::load_ttf_file( const char *filename ) { 593 | FILE *f = fopen( filename, "rb" ); 594 | if ( !f ) return false; 595 | 596 | fseek( f, 0, SEEK_END ); 597 | size_t fsize = ftell( f ); 598 | fseek( f, 0, SEEK_SET ); 599 | 600 | uint8_t *ttf = (unsigned char*) malloc( fsize ); 601 | fread( ttf, 1, fsize, f ); 602 | fclose( f ); 603 | 604 | bool res = load_ttf_mem( ttf ); 605 | free( ttf ); 606 | return res; 607 | } 608 | 609 | 610 | bool Font::load_ttf_mem( const uint8_t *ttf ) { 611 | if ( ttf == nullptr ) return false; 612 | if ( !is_font( ttf ) ) return false; 613 | 614 | uint32_t num_glyphs = 0xffff; 615 | 616 | const uint8_t *head = find_table( ttf, "head" ); 617 | if ( !head ) return false; 618 | 619 | uint16_t loc_format = ttf_u16( head + 50 ); 620 | // 0 - 16 bit offset 621 | // 1 - 32 bit offset 622 | // >1 - unsupported 623 | if ( loc_format > 1 ) return false; 624 | bool is_loc32 = loc_format; 625 | 626 | const uint8_t *loca = find_table( ttf, "loca" ); 627 | if ( !loca ) return false; 628 | 629 | const uint8_t *hmtx = find_table( ttf, "hmtx" ); 630 | if ( !hmtx ) return false; 631 | 632 | const uint8_t *glyf = find_table( ttf, "glyf" ); 633 | if ( !glyf ) return false; 634 | 635 | const uint8_t *maxp = find_table( ttf, "maxp" ); 636 | if ( maxp ) num_glyphs = ttf_u16( maxp + 4 ); 637 | 638 | const uint8_t *hhea = find_table( ttf, "hhea" ); 639 | if ( !hhea ) return false; 640 | em_ascent = ttf_i16( hhea + 4 ); 641 | em_descent = ttf_i16( hhea + 6 ); 642 | em_line_gap = ttf_i16( hhea + 8 ); 643 | 644 | uint32_t num_hmtx = ttf_u16( hhea + 34 ); 645 | 646 | float scale = 1.0f / em_ascent; 647 | ascent = 1.0; 648 | descent = em_descent * scale; 649 | line_gap = em_line_gap * scale; 650 | 651 | // Filling glyph idx mappings 652 | if ( !fill_cmap( *this, ttf ) ) return false; 653 | 654 | glyphs = std::vector( num_glyphs, Glyph{} ); 655 | 656 | // These glyphs have both advance with and left side bearing in "hmtx" table 657 | for ( size_t iglyph = 0; iglyph < num_hmtx; ++iglyph ) { 658 | glyphs[ iglyph ].advance_width = ttf_u16( hmtx + iglyph * 4 ) * scale; 659 | glyphs[ iglyph ].left_side_bearing = ttf_i16( hmtx + iglyph * 4 + 2 ) * scale; 660 | } 661 | // Rest of glyphs have left side bearing only 662 | for ( size_t iglyph = 0; iglyph < ( num_glyphs - num_hmtx ); ++iglyph ) { 663 | const uint8_t *pos = hmtx + num_hmtx * 4 + iglyph * 2; 664 | glyphs[ iglyph + num_hmtx ].advance_width = 0.0f; 665 | glyphs[ iglyph + num_hmtx ].left_side_bearing = ttf_i16( pos ); 666 | } 667 | 668 | // Reading glyph display lists while calculating glyph max bounding box 669 | 670 | glyph_min = F2 { 2e38f }; 671 | glyph_max = F2 { -2e38f }; 672 | 673 | // Reading simple glyph display listd and components for composite glyphs 674 | for ( size_t iglyph = 0; iglyph < num_glyphs; ++iglyph ) { 675 | glyph_shape( *this, iglyph, is_loc32, loca, glyf, scale ); 676 | glyph_min = min( glyph_min, glyphs[iglyph].min ); 677 | glyph_max = max( glyph_max, glyphs[iglyph].max ); 678 | } 679 | 680 | // Calculating composite glyph commands 681 | for ( size_t iglyph = 0; iglyph < num_glyphs; ++iglyph ) { 682 | glyph_commands_composite( *this, iglyph ); 683 | } 684 | 685 | // Reading glyph types 686 | for ( std::pair cgpair : glyph_map ) { 687 | uint32_t codepoint = cgpair.first; 688 | int iglyph = cgpair.second; 689 | if ( iglyph < 0 ) continue; 690 | Glyph& g = glyphs[ iglyph ]; 691 | if ( iswlower( codepoint ) ) g.char_type = Glyph::Lower; 692 | if ( iswupper( codepoint ) | iswdigit( codepoint ) ) g.char_type = Glyph::Upper; 693 | if ( iswpunct( codepoint ) ) g.char_type = Glyph::Punct; 694 | if ( iswspace( codepoint ) ) g.char_type = Glyph::Space; 695 | } 696 | 697 | // Filling codepoint map 698 | for ( std::pair cgpair : glyph_map ) { 699 | auto it = cp_map.find( cgpair.second ); 700 | if ( it == cp_map.end() ) { 701 | std::vector v { cgpair.first }; 702 | cp_map.insert( { cgpair.second, std::move( v ) } ); 703 | } else { 704 | it->second.push_back( cgpair.second ); 705 | } 706 | } 707 | 708 | // Some fonts store kerning information in "kern" table, reading it 709 | fill_kern( *this, ttf, scale ); 710 | 711 | // TODO Other fonts store kerning information in "gpos" table 712 | 713 | return true; 714 | } 715 | -------------------------------------------------------------------------------- /third_party/stb_image_write.h: -------------------------------------------------------------------------------- 1 | /* stb_image_write - v1.05 - public domain - http://nothings.org/stb/stb_image_write.h 2 | writes out PNG/BMP/TGA images to C stdio - Sean Barrett 2010-2015 3 | no warranty implied; use at your own risk 4 | 5 | Before #including, 6 | 7 | #define STB_IMAGE_WRITE_IMPLEMENTATION 8 | 9 | in the file that you want to have the implementation. 10 | 11 | Will probably not work correctly with strict-aliasing optimizations. 12 | 13 | ABOUT: 14 | 15 | This header file is a library for writing images to C stdio. It could be 16 | adapted to write to memory or a general streaming interface; let me know. 17 | 18 | The PNG output is not optimal; it is 20-50% larger than the file 19 | written by a decent optimizing implementation. This library is designed 20 | for source code compactness and simplicity, not optimal image file size 21 | or run-time performance. 22 | 23 | BUILDING: 24 | 25 | You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. 26 | You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace 27 | malloc,realloc,free. 28 | You can define STBIW_MEMMOVE() to replace memmove() 29 | 30 | USAGE: 31 | 32 | There are four functions, one for each image file format: 33 | 34 | int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); 35 | int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); 36 | int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); 37 | int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); 38 | 39 | There are also four equivalent functions that use an arbitrary write function. You are 40 | expected to open/close your file-equivalent before and after calling these: 41 | 42 | int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); 43 | int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); 44 | int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); 45 | int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); 46 | 47 | where the callback is: 48 | void stbi_write_func(void *context, void *data, int size); 49 | 50 | You can define STBI_WRITE_NO_STDIO to disable the file variant of these 51 | functions, so the library will not use stdio.h at all. However, this will 52 | also disable HDR writing, because it requires stdio for formatted output. 53 | 54 | Each function returns 0 on failure and non-0 on success. 55 | 56 | The functions create an image file defined by the parameters. The image 57 | is a rectangle of pixels stored from left-to-right, top-to-bottom. 58 | Each pixel contains 'comp' channels of data stored interleaved with 8-bits 59 | per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is 60 | monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. 61 | The *data pointer points to the first byte of the top-left-most pixel. 62 | For PNG, "stride_in_bytes" is the distance in bytes from the first byte of 63 | a row of pixels to the first byte of the next row of pixels. 64 | 65 | PNG creates output files with the same number of components as the input. 66 | The BMP format expands Y to RGB in the file format and does not 67 | output alpha. 68 | 69 | PNG supports writing rectangles of data even when the bytes storing rows of 70 | data are not consecutive in memory (e.g. sub-rectangles of a larger image), 71 | by supplying the stride between the beginning of adjacent rows. The other 72 | formats do not. (Thus you cannot write a native-format BMP through the BMP 73 | writer, both because it is in BGR order and because it may have padding 74 | at the end of the line.) 75 | 76 | HDR expects linear float data. Since the format is always 32-bit rgb(e) 77 | data, alpha (if provided) is discarded, and for monochrome data it is 78 | replicated across all three channels. 79 | 80 | TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed 81 | data, set the global variable 'stbi_write_tga_with_rle' to 0. 82 | 83 | CREDITS: 84 | 85 | PNG/BMP/TGA 86 | Sean Barrett 87 | HDR 88 | Baldur Karlsson 89 | TGA monochrome: 90 | Jean-Sebastien Guay 91 | misc enhancements: 92 | Tim Kelsey 93 | TGA RLE 94 | Alan Hickman 95 | initial file IO callback implementation 96 | Emmanuel Julien 97 | bugfixes: 98 | github:Chribba 99 | Guillaume Chereau 100 | github:jry2 101 | github:romigrou 102 | Sergio Gonzalez 103 | Jonas Karlsson 104 | Filip Wasil 105 | Thatcher Ulrich 106 | github:poppolopoppo 107 | Patrick Boettcher 108 | 109 | LICENSE 110 | 111 | See end of file for license information. 112 | 113 | */ 114 | 115 | #ifndef INCLUDE_STB_IMAGE_WRITE_H 116 | #define INCLUDE_STB_IMAGE_WRITE_H 117 | 118 | #ifdef __cplusplus 119 | extern "C" { 120 | #endif 121 | 122 | #ifdef STB_IMAGE_WRITE_STATIC 123 | #define STBIWDEF static 124 | #else 125 | #define STBIWDEF extern 126 | extern int stbi_write_tga_with_rle; 127 | #endif 128 | 129 | #ifndef STBI_WRITE_NO_STDIO 130 | STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); 131 | STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); 132 | STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); 133 | STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); 134 | #endif 135 | 136 | typedef void stbi_write_func(void *context, void *data, int size); 137 | 138 | STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); 139 | STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); 140 | STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); 141 | STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); 142 | 143 | #ifdef __cplusplus 144 | } 145 | #endif 146 | 147 | #endif//INCLUDE_STB_IMAGE_WRITE_H 148 | 149 | #ifdef STB_IMAGE_WRITE_IMPLEMENTATION 150 | 151 | #ifdef _WIN32 152 | #ifndef _CRT_SECURE_NO_WARNINGS 153 | #define _CRT_SECURE_NO_WARNINGS 154 | #endif 155 | #ifndef _CRT_NONSTDC_NO_DEPRECATE 156 | #define _CRT_NONSTDC_NO_DEPRECATE 157 | #endif 158 | #endif 159 | 160 | #ifndef STBI_WRITE_NO_STDIO 161 | #include 162 | #endif // STBI_WRITE_NO_STDIO 163 | 164 | #include 165 | #include 166 | #include 167 | #include 168 | 169 | #if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) 170 | // ok 171 | #elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) 172 | // ok 173 | #else 174 | #error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." 175 | #endif 176 | 177 | #ifndef STBIW_MALLOC 178 | #define STBIW_MALLOC(sz) malloc(sz) 179 | #define STBIW_REALLOC(p,newsz) realloc(p,newsz) 180 | #define STBIW_FREE(p) free(p) 181 | #endif 182 | 183 | #ifndef STBIW_REALLOC_SIZED 184 | #define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) 185 | #endif 186 | 187 | 188 | #ifndef STBIW_MEMMOVE 189 | #define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) 190 | #endif 191 | 192 | 193 | #ifndef STBIW_ASSERT 194 | #include 195 | #define STBIW_ASSERT(x) assert(x) 196 | #endif 197 | 198 | #define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) 199 | 200 | typedef struct 201 | { 202 | stbi_write_func *func; 203 | void *context; 204 | } stbi__write_context; 205 | 206 | // initialize a callback-based context 207 | static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) 208 | { 209 | s->func = c; 210 | s->context = context; 211 | } 212 | 213 | #ifndef STBI_WRITE_NO_STDIO 214 | 215 | static void stbi__stdio_write(void *context, void *data, int size) 216 | { 217 | fwrite(data,1,size,(FILE*) context); 218 | } 219 | 220 | static int stbi__start_write_file(stbi__write_context *s, const char *filename) 221 | { 222 | FILE *f = fopen(filename, "wb"); 223 | stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); 224 | return f != NULL; 225 | } 226 | 227 | static void stbi__end_write_file(stbi__write_context *s) 228 | { 229 | fclose((FILE *)s->context); 230 | } 231 | 232 | #endif // !STBI_WRITE_NO_STDIO 233 | 234 | typedef unsigned int stbiw_uint32; 235 | typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; 236 | 237 | #ifdef STB_IMAGE_WRITE_STATIC 238 | static int stbi_write_tga_with_rle = 1; 239 | #else 240 | int stbi_write_tga_with_rle = 1; 241 | #endif 242 | 243 | static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) 244 | { 245 | while (*fmt) { 246 | switch (*fmt++) { 247 | case ' ': break; 248 | case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); 249 | s->func(s->context,&x,1); 250 | break; } 251 | case '2': { int x = va_arg(v,int); 252 | unsigned char b[2]; 253 | b[0] = STBIW_UCHAR(x); 254 | b[1] = STBIW_UCHAR(x>>8); 255 | s->func(s->context,b,2); 256 | break; } 257 | case '4': { stbiw_uint32 x = va_arg(v,int); 258 | unsigned char b[4]; 259 | b[0]=STBIW_UCHAR(x); 260 | b[1]=STBIW_UCHAR(x>>8); 261 | b[2]=STBIW_UCHAR(x>>16); 262 | b[3]=STBIW_UCHAR(x>>24); 263 | s->func(s->context,b,4); 264 | break; } 265 | default: 266 | STBIW_ASSERT(0); 267 | return; 268 | } 269 | } 270 | } 271 | 272 | static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) 273 | { 274 | va_list v; 275 | va_start(v, fmt); 276 | stbiw__writefv(s, fmt, v); 277 | va_end(v); 278 | } 279 | 280 | static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) 281 | { 282 | unsigned char arr[3]; 283 | arr[0] = a, arr[1] = b, arr[2] = c; 284 | s->func(s->context, arr, 3); 285 | } 286 | 287 | static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) 288 | { 289 | unsigned char bg[3] = { 255, 0, 255}, px[3]; 290 | int k; 291 | 292 | if (write_alpha < 0) 293 | s->func(s->context, &d[comp - 1], 1); 294 | 295 | switch (comp) { 296 | case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case 297 | case 1: 298 | if (expand_mono) 299 | stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp 300 | else 301 | s->func(s->context, d, 1); // monochrome TGA 302 | break; 303 | case 4: 304 | if (!write_alpha) { 305 | // composite against pink background 306 | for (k = 0; k < 3; ++k) 307 | px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; 308 | stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); 309 | break; 310 | } 311 | /* FALLTHROUGH */ 312 | case 3: 313 | stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); 314 | break; 315 | } 316 | if (write_alpha > 0) 317 | s->func(s->context, &d[comp - 1], 1); 318 | } 319 | 320 | static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) 321 | { 322 | stbiw_uint32 zero = 0; 323 | int i,j, j_end; 324 | 325 | if (y <= 0) 326 | return; 327 | 328 | if (vdir < 0) 329 | j_end = -1, j = y-1; 330 | else 331 | j_end = y, j = 0; 332 | 333 | for (; j != j_end; j += vdir) { 334 | for (i=0; i < x; ++i) { 335 | unsigned char *d = (unsigned char *) data + (j*x+i)*comp; 336 | stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); 337 | } 338 | s->func(s->context, &zero, scanline_pad); 339 | } 340 | } 341 | 342 | static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) 343 | { 344 | if (y < 0 || x < 0) { 345 | return 0; 346 | } else { 347 | va_list v; 348 | va_start(v, fmt); 349 | stbiw__writefv(s, fmt, v); 350 | va_end(v); 351 | stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); 352 | return 1; 353 | } 354 | } 355 | 356 | static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) 357 | { 358 | int pad = (-x*3) & 3; 359 | return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, 360 | "11 4 22 4" "4 44 22 444444", 361 | 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header 362 | 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header 363 | } 364 | 365 | STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) 366 | { 367 | stbi__write_context s; 368 | stbi__start_write_callbacks(&s, func, context); 369 | return stbi_write_bmp_core(&s, x, y, comp, data); 370 | } 371 | 372 | #ifndef STBI_WRITE_NO_STDIO 373 | STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) 374 | { 375 | stbi__write_context s; 376 | if (stbi__start_write_file(&s,filename)) { 377 | int r = stbi_write_bmp_core(&s, x, y, comp, data); 378 | stbi__end_write_file(&s); 379 | return r; 380 | } else 381 | return 0; 382 | } 383 | #endif //!STBI_WRITE_NO_STDIO 384 | 385 | static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) 386 | { 387 | int has_alpha = (comp == 2 || comp == 4); 388 | int colorbytes = has_alpha ? comp-1 : comp; 389 | int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 390 | 391 | if (y < 0 || x < 0) 392 | return 0; 393 | 394 | if (!stbi_write_tga_with_rle) { 395 | return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, 396 | "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); 397 | } else { 398 | int i,j,k; 399 | 400 | stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); 401 | 402 | for (j = y - 1; j >= 0; --j) { 403 | unsigned char *row = (unsigned char *) data + j * x * comp; 404 | int len; 405 | 406 | for (i = 0; i < x; i += len) { 407 | unsigned char *begin = row + i * comp; 408 | int diff = 1; 409 | len = 1; 410 | 411 | if (i < x - 1) { 412 | ++len; 413 | diff = memcmp(begin, row + (i + 1) * comp, comp); 414 | if (diff) { 415 | const unsigned char *prev = begin; 416 | for (k = i + 2; k < x && len < 128; ++k) { 417 | if (memcmp(prev, row + k * comp, comp)) { 418 | prev += comp; 419 | ++len; 420 | } else { 421 | --len; 422 | break; 423 | } 424 | } 425 | } else { 426 | for (k = i + 2; k < x && len < 128; ++k) { 427 | if (!memcmp(begin, row + k * comp, comp)) { 428 | ++len; 429 | } else { 430 | break; 431 | } 432 | } 433 | } 434 | } 435 | 436 | if (diff) { 437 | unsigned char header = STBIW_UCHAR(len - 1); 438 | s->func(s->context, &header, 1); 439 | for (k = 0; k < len; ++k) { 440 | stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); 441 | } 442 | } else { 443 | unsigned char header = STBIW_UCHAR(len - 129); 444 | s->func(s->context, &header, 1); 445 | stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); 446 | } 447 | } 448 | } 449 | } 450 | return 1; 451 | } 452 | 453 | int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) 454 | { 455 | stbi__write_context s; 456 | stbi__start_write_callbacks(&s, func, context); 457 | return stbi_write_tga_core(&s, x, y, comp, (void *) data); 458 | } 459 | 460 | #ifndef STBI_WRITE_NO_STDIO 461 | int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) 462 | { 463 | stbi__write_context s; 464 | if (stbi__start_write_file(&s,filename)) { 465 | int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); 466 | stbi__end_write_file(&s); 467 | return r; 468 | } else 469 | return 0; 470 | } 471 | #endif 472 | 473 | // ************************************************************************************************* 474 | // Radiance RGBE HDR writer 475 | // by Baldur Karlsson 476 | 477 | #define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) 478 | 479 | void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) 480 | { 481 | int exponent; 482 | float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); 483 | 484 | if (maxcomp < 1e-32f) { 485 | rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; 486 | } else { 487 | float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; 488 | 489 | rgbe[0] = (unsigned char)(linear[0] * normalize); 490 | rgbe[1] = (unsigned char)(linear[1] * normalize); 491 | rgbe[2] = (unsigned char)(linear[2] * normalize); 492 | rgbe[3] = (unsigned char)(exponent + 128); 493 | } 494 | } 495 | 496 | void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) 497 | { 498 | unsigned char lengthbyte = STBIW_UCHAR(length+128); 499 | STBIW_ASSERT(length+128 <= 255); 500 | s->func(s->context, &lengthbyte, 1); 501 | s->func(s->context, &databyte, 1); 502 | } 503 | 504 | void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) 505 | { 506 | unsigned char lengthbyte = STBIW_UCHAR(length); 507 | STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code 508 | s->func(s->context, &lengthbyte, 1); 509 | s->func(s->context, data, length); 510 | } 511 | 512 | void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) 513 | { 514 | unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; 515 | unsigned char rgbe[4]; 516 | float linear[3]; 517 | int x; 518 | 519 | scanlineheader[2] = (width&0xff00)>>8; 520 | scanlineheader[3] = (width&0x00ff); 521 | 522 | /* skip RLE for images too small or large */ 523 | if (width < 8 || width >= 32768) { 524 | for (x=0; x < width; x++) { 525 | switch (ncomp) { 526 | case 4: /* fallthrough */ 527 | case 3: linear[2] = scanline[x*ncomp + 2]; 528 | linear[1] = scanline[x*ncomp + 1]; 529 | linear[0] = scanline[x*ncomp + 0]; 530 | break; 531 | default: 532 | linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; 533 | break; 534 | } 535 | stbiw__linear_to_rgbe(rgbe, linear); 536 | s->func(s->context, rgbe, 4); 537 | } 538 | } else { 539 | int c,r; 540 | /* encode into scratch buffer */ 541 | for (x=0; x < width; x++) { 542 | switch(ncomp) { 543 | case 4: /* fallthrough */ 544 | case 3: linear[2] = scanline[x*ncomp + 2]; 545 | linear[1] = scanline[x*ncomp + 1]; 546 | linear[0] = scanline[x*ncomp + 0]; 547 | break; 548 | default: 549 | linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; 550 | break; 551 | } 552 | stbiw__linear_to_rgbe(rgbe, linear); 553 | scratch[x + width*0] = rgbe[0]; 554 | scratch[x + width*1] = rgbe[1]; 555 | scratch[x + width*2] = rgbe[2]; 556 | scratch[x + width*3] = rgbe[3]; 557 | } 558 | 559 | s->func(s->context, scanlineheader, 4); 560 | 561 | /* RLE each component separately */ 562 | for (c=0; c < 4; c++) { 563 | unsigned char *comp = &scratch[width*c]; 564 | 565 | x = 0; 566 | while (x < width) { 567 | // find first run 568 | r = x; 569 | while (r+2 < width) { 570 | if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) 571 | break; 572 | ++r; 573 | } 574 | if (r+2 >= width) 575 | r = width; 576 | // dump up to first run 577 | while (x < r) { 578 | int len = r-x; 579 | if (len > 128) len = 128; 580 | stbiw__write_dump_data(s, len, &comp[x]); 581 | x += len; 582 | } 583 | // if there's a run, output it 584 | if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd 585 | // find next byte after run 586 | while (r < width && comp[r] == comp[x]) 587 | ++r; 588 | // output run up to r 589 | while (x < r) { 590 | int len = r-x; 591 | if (len > 127) len = 127; 592 | stbiw__write_run_data(s, len, comp[x]); 593 | x += len; 594 | } 595 | } 596 | } 597 | } 598 | } 599 | } 600 | 601 | static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) 602 | { 603 | if (y <= 0 || x <= 0 || data == NULL) 604 | return 0; 605 | else { 606 | // Each component is stored separately. Allocate scratch space for full output scanline. 607 | unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); 608 | int i, len; 609 | char buffer[128]; 610 | char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; 611 | s->func(s->context, header, sizeof(header)-1); 612 | 613 | len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); 614 | s->func(s->context, buffer, len); 615 | 616 | for(i=0; i < y; i++) 617 | stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*i*x); 618 | STBIW_FREE(scratch); 619 | return 1; 620 | } 621 | } 622 | 623 | int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) 624 | { 625 | stbi__write_context s; 626 | stbi__start_write_callbacks(&s, func, context); 627 | return stbi_write_hdr_core(&s, x, y, comp, (float *) data); 628 | } 629 | 630 | #ifndef STBI_WRITE_NO_STDIO 631 | int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) 632 | { 633 | stbi__write_context s; 634 | if (stbi__start_write_file(&s,filename)) { 635 | int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); 636 | stbi__end_write_file(&s); 637 | return r; 638 | } else 639 | return 0; 640 | } 641 | #endif // STBI_WRITE_NO_STDIO 642 | 643 | 644 | ////////////////////////////////////////////////////////////////////////////// 645 | // 646 | // PNG writer 647 | // 648 | 649 | // stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() 650 | #define stbiw__sbraw(a) ((int *) (a) - 2) 651 | #define stbiw__sbm(a) stbiw__sbraw(a)[0] 652 | #define stbiw__sbn(a) stbiw__sbraw(a)[1] 653 | 654 | #define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) 655 | #define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) 656 | #define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) 657 | 658 | #define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) 659 | #define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) 660 | #define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) 661 | 662 | static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) 663 | { 664 | int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; 665 | void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); 666 | STBIW_ASSERT(p); 667 | if (p) { 668 | if (!*arr) ((int *) p)[1] = 0; 669 | *arr = (void *) ((int *) p + 2); 670 | stbiw__sbm(*arr) = m; 671 | } 672 | return *arr; 673 | } 674 | 675 | static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) 676 | { 677 | while (*bitcount >= 8) { 678 | stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); 679 | *bitbuffer >>= 8; 680 | *bitcount -= 8; 681 | } 682 | return data; 683 | } 684 | 685 | static int stbiw__zlib_bitrev(int code, int codebits) 686 | { 687 | int res=0; 688 | while (codebits--) { 689 | res = (res << 1) | (code & 1); 690 | code >>= 1; 691 | } 692 | return res; 693 | } 694 | 695 | static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) 696 | { 697 | int i; 698 | for (i=0; i < limit && i < 258; ++i) 699 | if (a[i] != b[i]) break; 700 | return i; 701 | } 702 | 703 | static unsigned int stbiw__zhash(unsigned char *data) 704 | { 705 | stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); 706 | hash ^= hash << 3; 707 | hash += hash >> 5; 708 | hash ^= hash << 4; 709 | hash += hash >> 17; 710 | hash ^= hash << 25; 711 | hash += hash >> 6; 712 | return hash; 713 | } 714 | 715 | #define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) 716 | #define stbiw__zlib_add(code,codebits) \ 717 | (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) 718 | #define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) 719 | // default huffman tables 720 | #define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) 721 | #define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) 722 | #define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) 723 | #define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) 724 | #define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) 725 | #define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) 726 | 727 | #define stbiw__ZHASH 16384 728 | 729 | unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) 730 | { 731 | static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; 732 | static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; 733 | static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; 734 | static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; 735 | unsigned int bitbuf=0; 736 | int i,j, bitcount=0; 737 | unsigned char *out = NULL; 738 | unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(char**)); 739 | if (quality < 5) quality = 5; 740 | 741 | stbiw__sbpush(out, 0x78); // DEFLATE 32K window 742 | stbiw__sbpush(out, 0x5e); // FLEVEL = 1 743 | stbiw__zlib_add(1,1); // BFINAL = 1 744 | stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman 745 | 746 | for (i=0; i < stbiw__ZHASH; ++i) 747 | hash_table[i] = NULL; 748 | 749 | i=0; 750 | while (i < data_len-3) { 751 | // hash next 3 bytes of data to be compressed 752 | int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; 753 | unsigned char *bestloc = 0; 754 | unsigned char **hlist = hash_table[h]; 755 | int n = stbiw__sbcount(hlist); 756 | for (j=0; j < n; ++j) { 757 | if (hlist[j]-data > i-32768) { // if entry lies within window 758 | int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); 759 | if (d >= best) best=d,bestloc=hlist[j]; 760 | } 761 | } 762 | // when hash table entry is too long, delete half the entries 763 | if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { 764 | STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); 765 | stbiw__sbn(hash_table[h]) = quality; 766 | } 767 | stbiw__sbpush(hash_table[h],data+i); 768 | 769 | if (bestloc) { 770 | // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal 771 | h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); 772 | hlist = hash_table[h]; 773 | n = stbiw__sbcount(hlist); 774 | for (j=0; j < n; ++j) { 775 | if (hlist[j]-data > i-32767) { 776 | int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); 777 | if (e > best) { // if next match is better, bail on current match 778 | bestloc = NULL; 779 | break; 780 | } 781 | } 782 | } 783 | } 784 | 785 | if (bestloc) { 786 | int d = (int) (data+i - bestloc); // distance back 787 | STBIW_ASSERT(d <= 32767 && best <= 258); 788 | for (j=0; best > lengthc[j+1]-1; ++j); 789 | stbiw__zlib_huff(j+257); 790 | if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); 791 | for (j=0; d > distc[j+1]-1; ++j); 792 | stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); 793 | if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); 794 | i += best; 795 | } else { 796 | stbiw__zlib_huffb(data[i]); 797 | ++i; 798 | } 799 | } 800 | // write out final bytes 801 | for (;i < data_len; ++i) 802 | stbiw__zlib_huffb(data[i]); 803 | stbiw__zlib_huff(256); // end of block 804 | // pad with 0 bits to byte boundary 805 | while (bitcount) 806 | stbiw__zlib_add(0,1); 807 | 808 | for (i=0; i < stbiw__ZHASH; ++i) 809 | (void) stbiw__sbfree(hash_table[i]); 810 | STBIW_FREE(hash_table); 811 | 812 | { 813 | // compute adler32 on input 814 | unsigned int s1=1, s2=0; 815 | int blocklen = (int) (data_len % 5552); 816 | j=0; 817 | while (j < data_len) { 818 | for (i=0; i < blocklen; ++i) s1 += data[j+i], s2 += s1; 819 | s1 %= 65521, s2 %= 65521; 820 | j += blocklen; 821 | blocklen = 5552; 822 | } 823 | stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); 824 | stbiw__sbpush(out, STBIW_UCHAR(s2)); 825 | stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); 826 | stbiw__sbpush(out, STBIW_UCHAR(s1)); 827 | } 828 | *out_len = stbiw__sbn(out); 829 | // make returned pointer freeable 830 | STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); 831 | return (unsigned char *) stbiw__sbraw(out); 832 | } 833 | 834 | static unsigned int stbiw__crc32(unsigned char *buffer, int len) 835 | { 836 | static unsigned int crc_table[256] = 837 | { 838 | 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 839 | 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 840 | 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 841 | 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 842 | 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 843 | 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 844 | 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 845 | 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 846 | 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 847 | 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 848 | 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 849 | 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 850 | 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 851 | 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 852 | 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 853 | 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 854 | 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 855 | 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 856 | 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 857 | 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 858 | 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 859 | 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 860 | 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 861 | 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 862 | 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 863 | 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 864 | 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 865 | 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 866 | 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 867 | 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 868 | 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 869 | 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D 870 | }; 871 | 872 | unsigned int crc = ~0u; 873 | int i; 874 | for (i=0; i < len; ++i) 875 | crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; 876 | return ~crc; 877 | } 878 | 879 | #define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) 880 | #define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); 881 | #define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) 882 | 883 | static void stbiw__wpcrc(unsigned char **data, int len) 884 | { 885 | unsigned int crc = stbiw__crc32(*data - len - 4, len+4); 886 | stbiw__wp32(*data, crc); 887 | } 888 | 889 | static unsigned char stbiw__paeth(int a, int b, int c) 890 | { 891 | int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); 892 | if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); 893 | if (pb <= pc) return STBIW_UCHAR(b); 894 | return STBIW_UCHAR(c); 895 | } 896 | 897 | // @OPTIMIZE: provide an option that always forces left-predict or paeth predict 898 | unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) 899 | { 900 | int ctype[5] = { -1, 0, 4, 2, 6 }; 901 | unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; 902 | unsigned char *out,*o, *filt, *zlib; 903 | signed char *line_buffer; 904 | int i,j,k,p,zlen; 905 | 906 | if (stride_bytes == 0) 907 | stride_bytes = x * n; 908 | 909 | filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; 910 | line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } 911 | for (j=0; j < y; ++j) { 912 | static int mapping[] = { 0,1,2,3,4 }; 913 | static int firstmap[] = { 0,1,0,5,6 }; 914 | int *mymap = (j != 0) ? mapping : firstmap; 915 | int best = 0, bestval = 0x7fffffff; 916 | for (p=0; p < 2; ++p) { 917 | for (k= p?best:0; k < 5; ++k) { // @TODO: clarity: rewrite this to go 0..5, and 'continue' the unwanted ones during 2nd pass 918 | int type = mymap[k],est=0; 919 | unsigned char *z = pixels + stride_bytes*j; 920 | for (i=0; i < n; ++i) 921 | switch (type) { 922 | case 0: line_buffer[i] = z[i]; break; 923 | case 1: line_buffer[i] = z[i]; break; 924 | case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; 925 | case 3: line_buffer[i] = z[i] - (z[i-stride_bytes]>>1); break; 926 | case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-stride_bytes],0)); break; 927 | case 5: line_buffer[i] = z[i]; break; 928 | case 6: line_buffer[i] = z[i]; break; 929 | } 930 | for (i=n; i < x*n; ++i) { 931 | switch (type) { 932 | case 0: line_buffer[i] = z[i]; break; 933 | case 1: line_buffer[i] = z[i] - z[i-n]; break; 934 | case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; 935 | case 3: line_buffer[i] = z[i] - ((z[i-n] + z[i-stride_bytes])>>1); break; 936 | case 4: line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-stride_bytes], z[i-stride_bytes-n]); break; 937 | case 5: line_buffer[i] = z[i] - (z[i-n]>>1); break; 938 | case 6: line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; 939 | } 940 | } 941 | if (p) break; 942 | for (i=0; i < x*n; ++i) 943 | est += abs((signed char) line_buffer[i]); 944 | if (est < bestval) { bestval = est; best = k; } 945 | } 946 | } 947 | // when we get here, best contains the filter type, and line_buffer contains the data 948 | filt[j*(x*n+1)] = (unsigned char) best; 949 | STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); 950 | } 951 | STBIW_FREE(line_buffer); 952 | zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, 8); // increase 8 to get smaller but use more memory 953 | STBIW_FREE(filt); 954 | if (!zlib) return 0; 955 | 956 | // each tag requires 12 bytes of overhead 957 | out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); 958 | if (!out) return 0; 959 | *out_len = 8 + 12+13 + 12+zlen + 12; 960 | 961 | o=out; 962 | STBIW_MEMMOVE(o,sig,8); o+= 8; 963 | stbiw__wp32(o, 13); // header length 964 | stbiw__wptag(o, "IHDR"); 965 | stbiw__wp32(o, x); 966 | stbiw__wp32(o, y); 967 | *o++ = 8; 968 | *o++ = STBIW_UCHAR(ctype[n]); 969 | *o++ = 0; 970 | *o++ = 0; 971 | *o++ = 0; 972 | stbiw__wpcrc(&o,13); 973 | 974 | stbiw__wp32(o, zlen); 975 | stbiw__wptag(o, "IDAT"); 976 | STBIW_MEMMOVE(o, zlib, zlen); 977 | o += zlen; 978 | STBIW_FREE(zlib); 979 | stbiw__wpcrc(&o, zlen); 980 | 981 | stbiw__wp32(o,0); 982 | stbiw__wptag(o, "IEND"); 983 | stbiw__wpcrc(&o,0); 984 | 985 | STBIW_ASSERT(o == out + *out_len); 986 | 987 | return out; 988 | } 989 | 990 | #ifndef STBI_WRITE_NO_STDIO 991 | STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) 992 | { 993 | FILE *f; 994 | int len; 995 | unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); 996 | if (png == NULL) return 0; 997 | f = fopen(filename, "wb"); 998 | if (!f) { STBIW_FREE(png); return 0; } 999 | fwrite(png, 1, len, f); 1000 | fclose(f); 1001 | STBIW_FREE(png); 1002 | return 1; 1003 | } 1004 | #endif 1005 | 1006 | STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) 1007 | { 1008 | int len; 1009 | unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); 1010 | if (png == NULL) return 0; 1011 | func(context, png, len); 1012 | STBIW_FREE(png); 1013 | return 1; 1014 | } 1015 | 1016 | #endif // STB_IMAGE_WRITE_IMPLEMENTATION 1017 | 1018 | /* Revision history 1019 | 1.04 (2017-03-03) 1020 | monochrome BMP expansion 1021 | 1.03 ??? 1022 | 1.02 (2016-04-02) 1023 | avoid allocating large structures on the stack 1024 | 1.01 (2016-01-16) 1025 | STBIW_REALLOC_SIZED: support allocators with no realloc support 1026 | avoid race-condition in crc initialization 1027 | minor compile issues 1028 | 1.00 (2015-09-14) 1029 | installable file IO function 1030 | 0.99 (2015-09-13) 1031 | warning fixes; TGA rle support 1032 | 0.98 (2015-04-08) 1033 | added STBIW_MALLOC, STBIW_ASSERT etc 1034 | 0.97 (2015-01-18) 1035 | fixed HDR asserts, rewrote HDR rle logic 1036 | 0.96 (2015-01-17) 1037 | add HDR output 1038 | fix monochrome BMP 1039 | 0.95 (2014-08-17) 1040 | add monochrome TGA output 1041 | 0.94 (2014-05-31) 1042 | rename private functions to avoid conflicts with stb_image.h 1043 | 0.93 (2014-05-27) 1044 | warning fixes 1045 | 0.92 (2010-08-01) 1046 | casts to unsigned char to fix warnings 1047 | 0.91 (2010-07-17) 1048 | first public release 1049 | 0.90 first internal release 1050 | */ 1051 | 1052 | /* 1053 | ------------------------------------------------------------------------------ 1054 | This software is available under 2 licenses -- choose whichever you prefer. 1055 | ------------------------------------------------------------------------------ 1056 | ALTERNATIVE A - MIT License 1057 | Copyright (c) 2017 Sean Barrett 1058 | Permission is hereby granted, free of charge, to any person obtaining a copy of 1059 | this software and associated documentation files (the "Software"), to deal in 1060 | the Software without restriction, including without limitation the rights to 1061 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 1062 | of the Software, and to permit persons to whom the Software is furnished to do 1063 | so, subject to the following conditions: 1064 | The above copyright notice and this permission notice shall be included in all 1065 | copies or substantial portions of the Software. 1066 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1067 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1068 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1069 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1070 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1071 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 1072 | SOFTWARE. 1073 | ------------------------------------------------------------------------------ 1074 | ALTERNATIVE B - Public Domain (www.unlicense.org) 1075 | This is free and unencumbered software released into the public domain. 1076 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 1077 | software, either in source code form or as a compiled binary, for any purpose, 1078 | commercial or non-commercial, and by any means. 1079 | In jurisdictions that recognize copyright laws, the author or authors of this 1080 | software dedicate any and all copyright interest in the software to the public 1081 | domain. We make this dedication for the benefit of the public at large and to 1082 | the detriment of our heirs and successors. We intend this dedication to be an 1083 | overt act of relinquishment in perpetuity of all present and future rights to 1084 | this software under copyright law. 1085 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1086 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1087 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1088 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 1089 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 1090 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1091 | ------------------------------------------------------------------------------ 1092 | */ 1093 | --------------------------------------------------------------------------------