From 3cb144985169da3cea1c21a7b0bfba8d43c28fdb Mon Sep 17 00:00:00 2001 From: hgn Date: Sat, 15 Jan 2022 20:31:24 +0000 Subject: [PATCH] switched to command based rendering --- build.sh | 17 +- fishladder.c | 423 ++++++++++++++---------- fishladder_resources.h | 40 ++- phoboslab/qoi.h | 571 +++++++++++++++++--------------- phoboslab/qoi_old.h | 632 ++++++++++++++++++++++++++++++++++++ sprites_autocombine.h | 15 + texsheet.c | 214 ++++++++++++ textures_combine/donut.png | Bin 0 -> 12795 bytes textures_combine/jack-1.png | Bin 0 -> 6115 bytes textures_combine/jack-2.png | Bin 0 -> 6102 bytes textures_combine/peg.png | Bin 0 -> 4705 bytes vg/vg_tex.h | 7 + 12 files changed, 1471 insertions(+), 448 deletions(-) create mode 100644 phoboslab/qoi_old.h create mode 100644 sprites_autocombine.h create mode 100644 texsheet.c create mode 100644 textures_combine/donut.png create mode 100644 textures_combine/jack-1.png create mode 100644 textures_combine/jack-2.png create mode 100644 textures_combine/peg.png diff --git a/build.sh b/build.sh index aa0d03e..63f64de 100755 --- a/build.sh +++ b/build.sh @@ -65,6 +65,7 @@ if [ "$compile_tools" = true ]; then mkdir tools -p gcc -Wall -Wstrict-aliasing=3 $lib $flags mdlcomp.c gl/glad.c -o tools/mdlcomp $libs $steam_part -Wl,-rpath=./ $defines gcc -Wall -Wstrict-aliasing=3 $lib $flags fontcomp.c gl/glad.c -o tools/fontcomp $libs $steam_part -Wl,-rpath=./ $defines + gcc -Wall -Wstrict-aliasing=3 $lib $flags texsheet.c gl/glad.c -o tools/texsheet $libs $steam_part -Wl,-rpath=./ $defines gcc $lib qoiconv.c -std=c99 -O3 -o tools/qoiconv fi @@ -77,13 +78,15 @@ for f in textures/*.png; ./tools/qoiconv $f ./_temp_textures/"$(basename "$f" .png).qoi" done -#if [ "$compile_models" = true ]; then -# echo "Recompiling models" -# for f in models/*.obj; -# do echo "Compiling $f.."; -# ./tools/mdlcomp $f $f.h -# done -#fi +# Autocombine textures +auto_combine="" +cd textures_combine +for f in *.png; + do echo "[combine] $f"; + auto_combine="$auto_combine $f" +done +../tools/texsheet ../_temp_textures/autocombine.qoi ../sprites_autocombine.h sprites_auto_combine $auto_combine +cd .. # Main build if [ "$do_build" = true ]; then diff --git a/fishladder.c b/fishladder.c index 74f9726..e0cfd67 100644 --- a/fishladder.c +++ b/fishladder.c @@ -218,6 +218,15 @@ static struct world } *data; #pragma pack(pop) + + struct render_cmd + { + struct cell *ptr; + v2i pos; + } + *cmd_buf_tiles, *cmd_buf_specials; + + u32 tile_count, tile_special_count, max_commands; int initialzed; int sim_run, max_runs; @@ -275,7 +284,7 @@ static struct world v2f physics_v; v2f physics_co; } - fishes[16]; + fishes[64]; int num_fishes; @@ -351,7 +360,8 @@ static int world_check_pos_ok( v2i co, int dist ); static int cell_interactive( v2i co ); void vg_update(void); -static void render_tiles( v2i start, v2i end, v4f const regular_colour, v4f const selected_colour ); +static void render_tiles( v4f const regular_colour, v4f const selected_colour ); +static void render_tile_block( v2i start, v2i end, v4f const regular_colour, v4f const selected_colour ); void vg_render(void); void vg_ui(void); @@ -510,6 +520,8 @@ static void map_free(void) { arrfree( world.data ); arrfree( world.io ); + + free( world.cmd_buf_tiles ); world.w = 0; world.h = 0; @@ -1112,6 +1124,10 @@ static int map_load( const char *str, const char *name ) btn->position[1] = world.h -i -2; } + // Allocate buffers for render commands + world.cmd_buf_tiles = malloc( world.w*world.h* sizeof( struct render_cmd ) ); + world.max_commands = world.w*world.h; + return 1; IL_REG_ERROR: @@ -1797,7 +1813,7 @@ static void vg_update(void) { cell->state &= ~FLAG_FLIP_ROTATING; } - if( cell->state & FLAG_IS_TRIGGER ) + if( cell->state & (FLAG_IS_TRIGGER|FLAG_EMITTER) ) cell->state &= ~FLAG_TRIGGERED; } @@ -1984,9 +2000,12 @@ static void vg_update(void) { struct fish *fish = &world.fishes[i]; + // Apply movement if( fish->state == k_fish_state_alive ) - { v2i_add( fish->pos, fish->dir, fish->pos ); + + if( fish->state == k_fish_state_alive || fish->state == k_fish_state_soon_alive ) + { struct cell *cell_current = pcell( fish->pos ); if( cell_current->state & FLAG_IS_TRIGGER ) @@ -1995,7 +2014,8 @@ static void vg_update(void) struct cell *target_peice = &world.data[ cell_current->links[trigger_id] ]; - if( target_peice->state & FLAG_EMITTER ) + // Spawn new marble + if( (target_peice->state & FLAG_EMITTER) && !(target_peice->state & FLAG_TRIGGERED)) { struct fish *fish = &world.fishes[ world.num_fishes ]; lcell( cell_current->links[trigger_id], fish->pos ); @@ -2317,7 +2337,42 @@ static void vg_update(void) } } -static void render_tiles( v2i start, v2i end, v4f const regular_colour, v4f const selected_colour ) +static void render_tile( v2i pos, struct cell *ptr, v4f const regular_colour, v4f const selected_colour ) +{ + int selected = world.selected == pos[1]*world.w + pos[0]; + + int tile_offsets[][2] = + { + {2, 0}, {0, 3}, {0, 2}, {2, 2}, + {1, 0}, {2, 3}, {3, 2}, {1, 3}, + {3, 1}, {0, 1}, {1, 2}, {2, 1}, + {1, 1}, {3, 3}, {2, 1}, {2, 1} + }; + + int uv[2]; + + uv[0] = tile_offsets[ ptr->config ][0]; + uv[1] = tile_offsets[ ptr->config ][1]; + + glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ), + (float)pos[0], + (float)pos[1], + uv[0], + uv[1] + ); + + if( selected ) + { + glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, selected_colour ); + draw_mesh( 0, 2 ); + glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour ); + } + else + draw_mesh( 0, 2 ); +} + +// Renders specific chunk of tiles +static void render_tile_block( v2i start, v2i end, v4f const regular_colour, v4f const selected_colour ) { v2i full_start = { 0,0 }; v2i full_end = { world.w, world.h }; @@ -2327,41 +2382,44 @@ static void render_tiles( v2i start, v2i end, v4f const regular_colour, v4f cons start = full_start; end = full_end; } - - glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour ); for( int y = start[1]; y < end[1]; y ++ ) { for( int x = start[0]; x < end[0]; x ++ ) { - struct cell *cell = pcell((v2i){x,y}); - int selected = world.selected == y*world.w + x; - - int tile_offsets[][2] = - { - {2, 0}, {0, 3}, {0, 2}, {2, 2}, - {1, 0}, {2, 3}, {3, 2}, {1, 3}, - {3, 1}, {0, 1}, {1, 2}, {2, 1}, - {1, 1}, {3, 3}, {2, 1}, {2, 1} - }; - - int uv[2] = { 3, 0 }; - + v2i pos = { x, y }; + struct cell *cell = pcell( pos ); + if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT|FLAG_EMITTER) ) - { - uv[0] = tile_offsets[ cell->config ][0]; - uv[1] = tile_offsets[ cell->config ][1]; - } else continue; - - glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ), (float)x, (float)y, uv[0], uv[1] ); - if( selected ) - { - glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, selected_colour ); - draw_mesh( 0, 2 ); - glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour ); - } - else - draw_mesh( 0, 2 ); + render_tile( pos, cell, regular_colour, selected_colour ); + } + } +} + +// Renders all tiles in the command list +static void render_tiles( v4f const regular_colour, v4f const selected_colour ) +{ + glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour ); + + struct render_list + { + struct render_cmd *arr; + u32 count; + } + render_lists[] = { + { world.cmd_buf_tiles, world.tile_count }, + { world.cmd_buf_specials, world.tile_special_count } + }; + + for( int i = 0; i < vg_list_size( render_lists ); i ++ ) + { + struct render_list *list = &render_lists[i]; + for( int j = 0; j < list->count; j ++ ) + { + struct render_cmd *cmd = &list->arr[j]; + struct cell *cell = cmd->ptr; + + render_tile( cmd->pos, cell, regular_colour, selected_colour ); } } } @@ -2501,6 +2559,34 @@ void vg_render(void) if( !world.initialzed ) return; + + // Extract render commands + world.tile_count = 0; + world.tile_special_count = 0; + + for( int y = 1; y < world.h-1; y ++ ) + { + for( int x = 1; x < world.w-1; x ++ ) + { + struct cell *cell = pcell((v2i){x,y}); + + if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT|FLAG_EMITTER) ) + { + struct render_cmd *cmd; + + if( cell->config == k_cell_type_split || (cell->state & FLAG_EMITTER ) ) + cmd = &world.cmd_buf_tiles[ world.max_commands - (++ world.tile_special_count) ]; + else + cmd = &world.cmd_buf_tiles[ world.tile_count ++ ]; + + cmd->pos[0] = x; + cmd->pos[1] = y; + cmd->ptr = cell; + } + } + } + + world.cmd_buf_specials = &world.cmd_buf_tiles[ world.max_commands - world.tile_special_count ]; // BACKGROUND // ======================================================================================================== @@ -2547,7 +2633,7 @@ void vg_render(void) vg_tex2d_bind( &tex_wood, 1 ); glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 ); - render_tiles( NULL, NULL, colour_default, colour_default ); + render_tiles( colour_default, colour_default ); // MARBLES // ======================================================================================================== @@ -2607,51 +2693,46 @@ void vg_render(void) glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 ); glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 1.0f ); - render_tiles( NULL, NULL, colour_default, colour_selected ); + render_tiles( colour_default, colour_selected ); // Draw splitters - for( int y = 2; y < world.h-2; y ++ ) + for( int i = 0; i < world.tile_special_count; i ++ ) { - for( int x = 2; x < world.w-2; x ++ ) - { - struct cell *cell = pcell((v2i){x,y}); + struct render_cmd *cmd = &world.cmd_buf_specials[i]; + struct cell *cell = cmd->ptr; - if( cell->state & FLAG_CANAL ) + if( cell->config == k_cell_type_split ) + { + float rotation = cell->state & FLAG_FLIP_FLOP? vg_rad( -45.0f ): vg_rad( 45.0f ); + + if( cell->state & FLAG_FLIP_ROTATING ) { - if( cell->config == k_cell_type_split ) + if( (world.frame_lerp > curve_7_linear_section) ) { - float rotation = cell->state & FLAG_FLIP_FLOP? vg_rad( -45.0f ): vg_rad( 45.0f ); - - if( cell->state & FLAG_FLIP_ROTATING ) + float const rotation_speed = 0.4f; + if( (world.frame_lerp < 1.0f-rotation_speed) ) { - if( (world.frame_lerp > curve_7_linear_section) ) - { - float const rotation_speed = 0.4f; - if( (world.frame_lerp < 1.0f-rotation_speed) ) - { - float t = world.frame_lerp - curve_7_linear_section; - t *= -2.0f * (1.0f/(1.0f-(curve_7_linear_section+rotation_speed))); - t += 1.0f; - - rotation *= t; - } - else - rotation *= -1.0f; - } + float t = world.frame_lerp - curve_7_linear_section; + t *= -2.0f * (1.0f/(1.0f-(curve_7_linear_section+rotation_speed))); + t += 1.0f; + + rotation *= t; } - - m2x2_create_rotation( subtransform, rotation ); - - glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform ); - glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ), - (float)x, - (float)y + 0.125f, - cell->state & FLAG_TARGETED? 3.0f: 0.0f, - 0.0f - ); - draw_mesh( 0, 2 ); + else + rotation *= -1.0f; } } + + m2x2_create_rotation( subtransform, rotation ); + + glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform ); + glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ), + (float)cmd->pos[0], + (float)cmd->pos[1] + 0.125f, + cell->state & FLAG_TARGETED? 3.0f: 0.0f, + 0.0f + ); + draw_mesh( 0, 2 ); } } @@ -2670,7 +2751,7 @@ void vg_render(void) glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform ); glUniform2fv( SHADER_UNIFORM( shader_tile_main, "uMousePos" ), 1, world.tile_pos ); - render_tiles( new_begin, new_end, colour_default, colour_default ); + render_tile_block( new_begin, new_end, colour_default, colour_default ); world.data[ world.selected ].state ^= FLAG_CANAL; map_reclassify( new_begin, new_end, 0 ); @@ -2798,54 +2879,53 @@ void vg_render(void) float rp_xa = rp_x1*expf(1.0f-rp_x1)* 0.36f; float rp_x2 = 1.0f-rp_xa; - for( int y = 2; y < world.h-2; y ++ ) + for( int i = 0; i < world.tile_special_count; i ++ ) { - for( int x = 2; x < world.w-2; x ++ ) - { - struct cell *cell = pcell((v2i){x,y}); + struct render_cmd *cmd = &world.cmd_buf_specials[i]; + struct cell *cell = cmd->ptr; - if( cell->state & FLAG_CANAL ) + if( cell->state & FLAG_TARGETED ) + { + for( int j = 0; j < 2; j ++ ) { - if( cell->state & FLAG_IS_TRIGGER ) - { - int trigger_id = cell->links[0]?0:1; - struct cell *other_cell = &world.data[ cell->links[ trigger_id ]]; - - struct cell_description *desc = &cell_descriptions[ cell->config ]; - - int x2 = cell->links[trigger_id] % world.w; - int y2 = (cell->links[trigger_id] - x2) / world.w; - - v2f startpoint; - v2f endpoint; - - startpoint[0] = (float)x2 + (trigger_id? 0.75f: 0.25f); - startpoint[1] = (float)y2 + 0.25f; - - endpoint[0] = x; - endpoint[1] = y; - - v2_add( desc->trigger_pos, endpoint, endpoint ); - - if( other_cell->state & FLAG_EMITTER ) - { - v4f wire_colour; - colour_code_v3( other_cell->cc, wire_colour ); - wire_colour[3] = 1.0f; + if( !cell->links[j] ) + continue; - glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, wire_colour ); - } - else - { - glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, trigger_id? wire_right_colour: wire_left_colour ); - } + struct cell *other_cell = &world.data[ cell->links[ j ]]; + struct cell_description *desc = &cell_descriptions[ other_cell->config ]; + + int x2 = cell->links[j] % world.w; + int y2 = (cell->links[j] - x2) / world.w; + + v2f startpoint; + v2f endpoint; + + endpoint[0] = (float)cmd->pos[0] + (j? 0.75f: 0.25f); + endpoint[1] = (float)cmd->pos[1] + 0.25f; + + startpoint[0] = x2; + startpoint[1] = y2; + + v2_add( desc->trigger_pos, startpoint, startpoint ); + + if( cmd->ptr->state & FLAG_EMITTER ) + { + v4f wire_colour; + colour_code_v3( other_cell->cc, wire_colour ); + wire_colour[3] = 1.0f; - glUniform1f( SHADER_UNIFORM( shader_wire, "uCurve" ), cell->state & FLAG_TRIGGERED? rp_x2 * 0.4f: 0.4f ); - glUniform1f( SHADER_UNIFORM( shader_wire, "uGlow" ), cell->state & FLAG_TRIGGERED? rp_xa: 0.0f ); - glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), startpoint[0], startpoint[1], 0.18f ); - glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), endpoint[0], endpoint[1], 0.18f ); - glDrawElements( GL_TRIANGLES, world.wire.em, GL_UNSIGNED_SHORT, (void*)(0) ); + glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, wire_colour ); } + else + { + glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, j? wire_right_colour: wire_left_colour ); + } + + glUniform1f( SHADER_UNIFORM( shader_wire, "uCurve" ), other_cell->state & FLAG_TRIGGERED? rp_x2 * 0.4f: 0.4f ); + glUniform1f( SHADER_UNIFORM( shader_wire, "uGlow" ), other_cell->state & FLAG_TRIGGERED? rp_xa: 0.0f ); + glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), startpoint[0], startpoint[1], 0.18f ); + glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), endpoint[0], endpoint[1], 0.18f ); + glDrawElements( GL_TRIANGLES, world.wire.em, GL_UNSIGNED_SHORT, (void*)(0) ); } } } @@ -2857,81 +2937,78 @@ void vg_render(void) glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv ); use_mesh( &world.shapes ); - for( int y = 2; y < world.h-2; y ++ ) + for( int i = 0; i < world.tile_special_count; i ++ ) { - for( int x = 2; x < world.w-2; x ++ ) - { - struct cell *cell = pcell((v2i){x,y}); + struct render_cmd *cmd = &world.cmd_buf_specials[i]; + struct cell *cell = cmd->ptr; - if( cell->state & FLAG_CANAL ) + if( cell->state & FLAG_TARGETED ) + { + for( int j = 0; j < 2; j ++ ) { - if( cell->state & FLAG_IS_TRIGGER ) - { - int trigger_id = cell->links[0]?0:1; - struct cell *other_cell = &world.data[ cell->links[ trigger_id ]]; - - struct cell_description *desc = &cell_descriptions[ cell->config ]; + if( !cell->links[j] ) + continue; + + struct cell *other_cell = &world.data[ cell->links[ j ]]; - int x2 = cell->links[trigger_id] % world.w; - int y2 = (cell->links[trigger_id] - x2) / world.w; - - v2f pts[2]; - - pts[0][0] = (float)x2 + (trigger_id? 0.75f: 0.25f); - pts[0][1] = (float)y2 + 0.25f; - - pts[1][0] = x; - pts[1][1] = y; - - v2_add( desc->trigger_pos, pts[1], pts[1] ); - - if( other_cell->state & FLAG_EMITTER ) - { - v4f wire_colour; - colour_code_v3( other_cell->cc, wire_colour ); + struct cell_description *desc = &cell_descriptions[ other_cell->config ]; + + int x2 = cell->links[j] % world.w; + int y2 = (cell->links[j] - x2) / world.w; + + v2f pts[2]; + + pts[0][0] = (float)cmd->pos[0] + (j? 0.75f: 0.25f); + pts[0][1] = (float)cmd->pos[1] + 0.25f; + + pts[1][0] = x2; + pts[1][1] = y2; + + v2_add( desc->trigger_pos, pts[1], pts[1] ); + + if( other_cell->state & FLAG_EMITTER ) + { + v4f wire_colour; + colour_code_v3( other_cell->cc, wire_colour ); - v3_muls( wire_colour, 0.8f, wire_colour ); - wire_colour[3] = 1.0f; + v3_muls( wire_colour, 0.8f, wire_colour ); + wire_colour[3] = 1.0f; - glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, wire_colour ); - } - else - { - glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, trigger_id? wire_right_colour: wire_left_colour ); - } - //glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), - // 1, trigger_id? wire_right_colour: wire_left_colour ); - - for( int i = 0; i < 2; i ++ ) - { - glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), - pts[i][0], - pts[i][1], - 0.08f - ); - draw_mesh( filled_start, filled_count ); - } + glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, wire_colour ); + } + else + { + glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, j? wire_right_colour: wire_left_colour ); + } + + for( int i = 0; i < 2; i ++ ) + { + glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), + pts[i][0], + pts[i][1], + 0.08f + ); + draw_mesh( filled_start, filled_count ); } } } } - + // SUB SPLITTER DIRECTION // ======================================================================================================== - + // TODO: Make this sprites probably + glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.9f, 0.35f, 0.1f, 0.75f ); - for( int y = 2; y < world.h-2; y ++ ) + for( int i = 0; i < world.tile_special_count; i ++ ) { - for( int x = 2; x < world.w-2; x ++ ) - { - struct cell *cell = pcell((v2i){x,y}); + struct render_cmd *cmd = &world.cmd_buf_specials[i]; + struct cell *cell = cmd->ptr; - if( cell->state & FLAG_CANAL && cell->state & FLAG_TARGETED && cell->config == k_cell_type_split ) - { - glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), x, y, 1.0f ); - draw_mesh( cell->state & FLAG_FLIP_FLOP? 5: 4, 1 ); - } + if( cell->state & FLAG_TARGETED && cell->config == k_cell_type_split ) + { + glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), cmd->pos[0], cmd->pos[1], 1.0f ); + draw_mesh( cell->state & FLAG_FLIP_FLOP? 5: 4, 1 ); } } diff --git a/fishladder_resources.h b/fishladder_resources.h index 82efc47..7b12859 100644 --- a/fishladder_resources.h +++ b/fishladder_resources.h @@ -124,8 +124,11 @@ vg_tex2d tex_ball_noise = { .path = "textures/bnoise.qoi" }; vg_tex2d tex_monofur = { .path = "textures/ascii.qoi", .flags = VG_TEXTURE_NO_MIP }; vg_tex2d tex_unkown = { .path = "textures/unkown.qoi" }; vg_tex2d tex_buttons = { .path = "textures/buttons.qoi" }; +vg_tex2d tex_sprites = { .path = "textures/autocombine.qoi" }; -vg_tex2d *texture_list[] = { &tex_tile_detail, &tex_tile_data, &tex_wood, &tex_ball_noise, &tex_monofur, &tex_unkown, &tex_buttons, &tex_ubuntu }; +vg_tex2d *texture_list[] = { &tex_tile_detail, &tex_tile_data, &tex_wood, &tex_ball_noise, &tex_monofur, &tex_unkown, &tex_buttons, &tex_ubuntu, &tex_sprites }; + +#include "sprites_autocombine.h" // AUDIO // =========================================================================================================== @@ -600,7 +603,6 @@ SHADER_DEFINE( shader_sdf, "out vec4 FragColor;" "" "in vec2 aTexCoords;" - "in vec4 aColour;" "" "void main()" "{" @@ -612,6 +614,40 @@ SHADER_DEFINE( shader_sdf, UNIFORMS({ "uPv", "uTexGlyphs", "uColour" }) ) +SHADER_DEFINE( shader_sprite, + + // VERTEX + "layout (location=0) in vec2 a_co;" // quad mesh + "layout (location=1) in vec4 ins_uv;" // instanced data (uv) + "layout (location=2) in vec3 ins_pos;" // position + scale + "" + "uniform mat3 uPv;" + "" + "out vec2 aTexCoords;" + "" + "void main()" + "{" + "vec2 vertex_world = ins_uv.zw * a_co * ins_pos.z + ins_pos.xy;" + "gl_Position = vec4( uPv * vec3( vertex_world, 1.0 ), 1.0 );" + "aTexCoords = ins_uv.xy + (a_co+0.5)*ins_uv.zw;" + "}", + + // FRAGMENT + "uniform sampler2D uTexMain;" + "uniform vec4 uColour;" + "out vec4 FragColor;" + "" + "in vec2 aTexCoords;" + "" + "void main()" + "{" + "vec4 glyph = texture( uTexGlyphs, aTexCoords );" + "FragColor = glyph;" + "}" + , + UNIFORMS({ "uPv", "uTexMain", "uColour" }) +) + void vg_register(void) { SHADER_INIT( shader_tile_colour ); diff --git a/phoboslab/qoi.h b/phoboslab/qoi.h index d70e2e8..988f9ed 100644 --- a/phoboslab/qoi.h +++ b/phoboslab/qoi.h @@ -28,12 +28,9 @@ SOFTWARE. -- About -QOI encodes and decodes images in a lossless format. An encoded QOI image is -usually around 10--30% larger than a decently optimized PNG image. - -QOI outperforms simpler PNG encoders in compression ratio and performance. QOI -images are typically 20% smaller than PNGs written with stbi_image. Encoding is -25-50x faster and decoding is 3-4x faster than stbi_image or libpng. +QOI encodes and decodes images in a lossless format. Compared to stb_image and +stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and +20% better compression. -- Synopsis @@ -48,7 +45,7 @@ images are typically 20% smaller than PNGs written with stbi_image. Encoding is // the input pixel data. qoi_write("image_new.qoi", rgba_pixels, &(qoi_desc){ .width = 1920, - .height = 1080, + .height = 1080, .channels = 4, .colorspace = QOI_SRGB }); @@ -77,143 +74,162 @@ QOI_NO_STDIO before including this library. This library uses malloc() and free(). To supply your own malloc implementation you can define QOI_MALLOC and QOI_FREE before including this library. +This library uses memset() to zero-initialize the index. To supply your own +implementation you can define QOI_ZEROARR before including this library. + -- Data Format -A QOI file has a 14 byte header, followed by any number of data "chunks". +A QOI file has a 14 byte header, followed by any number of data "chunks" and an +8-byte end marker. struct qoi_header_t { char magic[4]; // magic bytes "qoif" uint32_t width; // image width in pixels (BE) uint32_t height; // image height in pixels (BE) - uint8_t channels; // must be 3 (RGB) or 4 (RGBA) - uint8_t colorspace; // a bitmap 0000rgba where - // - a zero bit indicates sRGBA, - // - a one bit indicates linear (user interpreted) - // colorspace for each channel + uint8_t channels; // 3 = RGB, 4 = RGBA + uint8_t colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear }; -The decoder and encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous -pixel value. Pixels are either encoded as +Images are encoded row by row, left to right, top to bottom. The decoder and +encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An +image is complete when all pixels specified by width * height have been covered. + +Pixels are encoded as - a run of the previous pixel - - an index into a previously seen pixel - - a difference to the previous pixel value in r,g,b,a - - full r,g,b,a values + - an index into an array of previously seen pixels + - a difference to the previous pixel value in r,g,b + - full r,g,b or r,g,b,a values + +The color channels are assumed to not be premultiplied with the alpha channel +("un-premultiplied alpha"). + +A running array[64] (zero-initialized) of previously seen pixel values is +maintained by the encoder and decoder. Each pixel that is seen by the encoder +and decoder is put into this array at the position formed by a hash function of +the color value. In the encoder, if the pixel value at the index matches the +current pixel, this index position is written to the stream as QOI_OP_INDEX. +The hash function for the index is: + + index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64 + +Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The +bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All +values encoded in these data bits have the most significant bit on the left. + +The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the +presence of an 8-bit tag first. -A running array[64] of previously seen pixel values is maintained by the encoder -and decoder. Each pixel that is seen by the encoder and decoder is put into this -array at the position (r^g^b^a) % 64. In the encoder, if the pixel value at this -index matches the current pixel, this index position is written to the stream. +The byte stream's end is marked with 7 0x00 bytes followed a single 0x01 byte. -Each chunk starts with a 2, 3 or 4 bit tag, followed by a number of data bits. -The bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. -All values encoded in these data bits have the most significant bit (MSB) on the -left. The possible chunks are: - - QOI_INDEX ------------- + +.- QOI_OP_INDEX ----------. | Byte[0] | | 7 6 5 4 3 2 1 0 | |-------+-----------------| | 0 0 | index | - +`-------------------------` 2-bit tag b00 6-bit index into the color index array: 0..63 - - - QOI_RUN_8 ------------- -| Byte[0] | -| 7 6 5 4 3 2 1 0 | -|----------+--------------| -| 0 1 0 | run | - -3-bit tag b010 -5-bit run-length repeating the previous pixel: 1..32 +A valid encoder must not issue 2 or more consecutive QOI_OP_INDEX chunks to the +same index. QOI_OP_RUN should be used instead. - - QOI_RUN_16 -------------------------------------- -| Byte[0] | Byte[1] | -| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | -|----------+----------------------------------------| -| 0 1 1 | run | - -3-bit tag b011 -13-bit run-length repeating the previous pixel: 33..8224 - - - - QOI_DIFF_8 ------------ +.- QOI_OP_DIFF -----------. | Byte[0] | | 7 6 5 4 3 2 1 0 | |-------+-----+-----+-----| -| 1 0 | dr | db | bg | - -2-bit tag b10 +| 0 1 | dr | dg | db | +`-------------------------` +2-bit tag b01 2-bit red channel difference from the previous pixel between -2..1 2-bit green channel difference from the previous pixel between -2..1 2-bit blue channel difference from the previous pixel between -2..1 -The difference to the current channel values are using a wraparound operation, +The difference to the current channel values are using a wraparound operation, so "1 - 2" will result in 255, while "255 + 1" will result in 0. +Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as +0 (b00). 1 is stored as 3 (b11). + +The alpha value remains unchanged from the previous pixel. - - QOI_DIFF_16 ------------------------------------- + +.- QOI_OP_LUMA -------------------------------------. | Byte[0] | Byte[1] | | 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | -|----------+--------------|------------ +-----------| -| 1 1 0 | red diff | green diff | blue diff | +|-------+-----------------+-------------+-----------| +| 1 0 | green diff | dr - dg | db - dg | +`---------------------------------------------------` +2-bit tag b10 +6-bit green channel difference from the previous pixel -32..31 +4-bit red channel difference minus green channel difference -8..7 +4-bit blue channel difference minus green channel difference -8..7 -3-bit tag b110 -5-bit red channel difference from the previous pixel between -16..15 -4-bit green channel difference from the previous pixel between -8..7 -4-bit blue channel difference from the previous pixel between -8..7 +The green channel is used to indicate the general direction of change and is +encoded in 6 bits. The red and blue channels (dr and db) base their diffs off +of the green channel difference and are encoded in 4 bits. I.e.: + dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g) + db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g) -The difference to the current channel values are using a wraparound operation, +The difference to the current channel values are using a wraparound operation, so "10 - 13" will result in 253, while "250 + 7" will result in 1. +Values are stored as unsigned integers with a bias of 32 for the green channel +and a bias of 8 for the red and blue channel. - - QOI_DIFF_24 --------------------------------------------------------------- -| Byte[0] | Byte[1] | Byte[2] | -| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | -|-------------+----------------+--------------+----------------+--------------| -| 1 1 1 0 | red diff | green diff | blue diff | alpha diff | +The alpha value remains unchanged from the previous pixel. -4-bit tag b1110 -5-bit red channel difference from the previous pixel between -16..15 -5-bit green channel difference from the previous pixel between -16..15 -5-bit blue channel difference from the previous pixel between -16..15 -5-bit alpha channel difference from the previous pixel between -16..15 -The difference to the current channel values are using a wraparound operation, -so "10 - 13" will result in 253, while "250 + 7" will result in 1. - - - - QOI_COLOR ------------- +.- QOI_OP_RUN ------------. | Byte[0] | | 7 6 5 4 3 2 1 0 | -|-------------+--+--+--+--| -| 1 1 1 1 |hr|hg|hb|ha| - -4-bit tag b1111 -1-bit red byte follows -1-bit green byte follows -1-bit blue byte follows -1-bit alpha byte follows - -For each set bit hr, hg, hb and ha another byte follows in this order. If such a -byte follows, it will replace the current color channel value with the value of -this byte. - - -The byte stream is padded at the end with 4 zero bytes. Size the longest chunk -we can encounter is 5 bytes (QOI_COLOR with RGBA set), with this padding we just -have to check for an overrun once per decode loop iteration. +|-------+-----------------| +| 1 1 | run | +`-------------------------` +2-bit tag b11 +6-bit run-length repeating the previous pixel: 1..62 + +The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64 +(b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and +QOI_OP_RGBA tags. + + +.- QOI_OP_RGB ------------------------------------------. +| Byte[0] | Byte[1] | Byte[2] | Byte[3] | +| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | +|-------------------------+---------+---------+---------| +| 1 1 1 1 1 1 1 0 | red | green | blue | +`-------------------------------------------------------` +8-bit tag b11111110 +8-bit red channel value +8-bit green channel value +8-bit blue channel value + +The alpha value remains unchanged from the previous pixel. + + +.- QOI_OP_RGBA ---------------------------------------------------. +| Byte[0] | Byte[1] | Byte[2] | Byte[3] | Byte[4] | +| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | +|-------------------------+---------+---------+---------+---------| +| 1 1 1 1 1 1 1 1 | red | green | blue | alpha | +`-----------------------------------------------------------------` +8-bit tag b11111111 +8-bit red channel value +8-bit green channel value +8-bit blue channel value +8-bit alpha channel value */ -// ----------------------------------------------------------------------------- -// Header - Public functions +/* ----------------------------------------------------------------------------- +Header - Public functions */ #ifndef QOI_H #define QOI_H @@ -222,19 +238,20 @@ have to check for an overrun once per decode loop iteration. extern "C" { #endif -// A pointer to qoi_desc struct has to be supplied to all of qoi's functions. It -// describes either the input format (for qoi_write, qoi_encode), or is filled -// with the description read from the file header (for qoi_read, qoi_decode). +/* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions. +It describes either the input format (for qoi_write and qoi_encode), or is +filled with the description read from the file header (for qoi_read and +qoi_decode). -// The colorspace in this qoi_desc is a bitmap with 0000rgba where a 0-bit -// indicates sRGB and a 1-bit indicates linear colorspace for each channel. You -// may use one of the predefined constants: QOI_SRGB, QOI_SRGB_LINEAR_ALPHA or -// QOI_LINEAR. The colorspace is purely informative. It will be saved to the -// file header, but does not affect en-/decoding in any way. +The colorspace in this qoi_desc is an enum where + 0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel + 1 = all channels are linear +You may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely +informative. It will be saved to the file header, but does not affect +how chunks are en-/decoded. */ -#define QOI_SRGB 0x00 -#define QOI_SRGB_LINEAR_ALPHA 0x01 -#define QOI_LINEAR 0x0f +#define QOI_SRGB 0 +#define QOI_LINEAR 1 typedef struct { unsigned int width; @@ -245,49 +262,49 @@ typedef struct { #ifndef QOI_NO_STDIO -// Encode raw RGB or RGBA pixels into a QOI image and write it to the file -// system. The qoi_desc struct must be filled with the image width, height, -// number of channels (3 = RGB, 4 = RGBA) and the colorspace. +/* Encode raw RGB or RGBA pixels into a QOI image and write it to the file +system. The qoi_desc struct must be filled with the image width, height, +number of channels (3 = RGB, 4 = RGBA) and the colorspace. -// The function returns 0 on failure (invalid parameters, or fopen or malloc -// failed) or the number of bytes written on success. +The function returns 0 on failure (invalid parameters, or fopen or malloc +failed) or the number of bytes written on success. */ int qoi_write(const char *filename, const void *data, const qoi_desc *desc); -// Read and decode a QOI image from the file system. If channels is 0, the -// number of channels from the file header is used. If channels is 3 or 4 the -// output format will be forced into this number of channels. +/* Read and decode a QOI image from the file system. If channels is 0, the +number of channels from the file header is used. If channels is 3 or 4 the +output format will be forced into this number of channels. -// The function either returns NULL on failure (invalid data, or malloc or fopen -// failed) or a pointer to the decoded pixels. On success, the qoi_desc struct -// will be filled with the description from the file header. +The function either returns NULL on failure (invalid data, or malloc or fopen +failed) or a pointer to the decoded pixels. On success, the qoi_desc struct +will be filled with the description from the file header. -// The returned pixel data should be free()d after use. +The returned pixel data should be free()d after use. */ void *qoi_read(const char *filename, qoi_desc *desc, int channels); -#endif // QOI_NO_STDIO +#endif /* QOI_NO_STDIO */ -// Encode raw RGB or RGBA pixels into a QOI image in memory. +/* Encode raw RGB or RGBA pixels into a QOI image in memory. -// The function either returns NULL on failure (invalid parameters or malloc -// failed) or a pointer to the encoded data on success. On success the out_len -// is set to the size in bytes of the encoded data. +The function either returns NULL on failure (invalid parameters or malloc +failed) or a pointer to the encoded data on success. On success the out_len +is set to the size in bytes of the encoded data. -// The returned qoi data should be free()d after use. +The returned qoi data should be free()d after use. */ void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len); -// Decode a QOI image from memory. +/* Decode a QOI image from memory. -// The function either returns NULL on failure (invalid parameters or malloc -// failed) or a pointer to the decoded pixels. On success, the qoi_desc struct -// is filled with the description from the file header. +The function either returns NULL on failure (invalid parameters or malloc +failed) or a pointer to the decoded pixels. On success, the qoi_desc struct +is filled with the description from the file header. -// The returned pixel data should be free()d after use. +The returned pixel data should be free()d after use. */ void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels); @@ -295,75 +312,91 @@ void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels); #ifdef __cplusplus } #endif -#endif // QOI_H +#endif /* QOI_H */ -// ----------------------------------------------------------------------------- -// Implementation +/* ----------------------------------------------------------------------------- +Implementation */ #ifdef QOI_IMPLEMENTATION #include +#include #ifndef QOI_MALLOC #define QOI_MALLOC(sz) malloc(sz) #define QOI_FREE(p) free(p) #endif +#ifndef QOI_ZEROARR + #define QOI_ZEROARR(a) memset((a),0,sizeof(a)) +#endif -#define QOI_INDEX 0x00 // 00xxxxxx -#define QOI_RUN_8 0x40 // 010xxxxx -#define QOI_RUN_16 0x60 // 011xxxxx -#define QOI_DIFF_8 0x80 // 10xxxxxx -#define QOI_DIFF_16 0xc0 // 110xxxxx -#define QOI_DIFF_24 0xe0 // 1110xxxx -#define QOI_COLOR 0xf0 // 1111xxxx +#define QOI_OP_INDEX 0x00 /* 00xxxxxx */ +#define QOI_OP_DIFF 0x40 /* 01xxxxxx */ +#define QOI_OP_LUMA 0x80 /* 10xxxxxx */ +#define QOI_OP_RUN 0xc0 /* 11xxxxxx */ +#define QOI_OP_RGB 0xfe /* 11111110 */ +#define QOI_OP_RGBA 0xff /* 11111111 */ -#define QOI_MASK_2 0xc0 // 11000000 -#define QOI_MASK_3 0xe0 // 11100000 -#define QOI_MASK_4 0xf0 // 11110000 +#define QOI_MASK_2 0xc0 /* 11000000 */ -#define QOI_COLOR_HASH(C) (C.rgba.r ^ C.rgba.g ^ C.rgba.b ^ C.rgba.a) +#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11) #define QOI_MAGIC \ (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \ ((unsigned int)'i') << 8 | ((unsigned int)'f')) #define QOI_HEADER_SIZE 14 -#define QOI_PADDING 4 + +/* 2GB is the max file size that this implementation can safely handle. We guard +against anything larger than that, assuming the worst case with 5 bytes per +pixel, rounded down to a nice clean value. 400 million pixels ought to be +enough for anybody. */ +#define QOI_PIXELS_MAX ((unsigned int)400000000) typedef union { struct { unsigned char r, g, b, a; } rgba; unsigned int v; } qoi_rgba_t; -void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) { +static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1}; + +static void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) { bytes[(*p)++] = (0xff000000 & v) >> 24; bytes[(*p)++] = (0x00ff0000 & v) >> 16; bytes[(*p)++] = (0x0000ff00 & v) >> 8; bytes[(*p)++] = (0x000000ff & v); } -unsigned int qoi_read_32(const unsigned char *bytes, int *p) { +static unsigned int qoi_read_32(const unsigned char *bytes, int *p) { unsigned int a = bytes[(*p)++]; unsigned int b = bytes[(*p)++]; unsigned int c = bytes[(*p)++]; unsigned int d = bytes[(*p)++]; - return (a << 24) | (b << 16) | (c << 8) | d; + return a << 24 | b << 16 | c << 8 | d; } void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) { + int i, max_size, p, run; + int px_len, px_end, px_pos, channels; + unsigned char *bytes; + const unsigned char *pixels; + qoi_rgba_t index[64]; + qoi_rgba_t px, px_prev; + if ( data == NULL || out_len == NULL || desc == NULL || desc->width == 0 || desc->height == 0 || desc->channels < 3 || desc->channels > 4 || - (desc->colorspace & 0xf0) != 0 + desc->colorspace > 1 || + desc->height >= QOI_PIXELS_MAX / desc->width ) { return NULL; } - int max_size = - desc->width * desc->height * (desc->channels + 1) + - QOI_HEADER_SIZE + QOI_PADDING; + max_size = + desc->width * desc->height * (desc->channels + 1) + + QOI_HEADER_SIZE + sizeof(qoi_padding); - int p = 0; - unsigned char *bytes = QOI_MALLOC(max_size); + p = 0; + bytes = (unsigned char *) QOI_MALLOC(max_size); if (!bytes) { return NULL; } @@ -375,105 +408,98 @@ void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) { bytes[p++] = desc->colorspace; - const unsigned char *pixels = (const unsigned char *)data; + pixels = (const unsigned char *)data; + + QOI_ZEROARR(index); - qoi_rgba_t index[64] = {0}; + run = 0; + px_prev.rgba.r = 0; + px_prev.rgba.g = 0; + px_prev.rgba.b = 0; + px_prev.rgba.a = 255; + px = px_prev; - int run = 0; - qoi_rgba_t px_prev = {.rgba = {.r = 0, .g = 0, .b = 0, .a = 255}}; - qoi_rgba_t px = px_prev; - - int px_len = desc->width * desc->height * desc->channels; - int px_end = px_len - desc->channels; - int channels = desc->channels; + px_len = desc->width * desc->height * desc->channels; + px_end = px_len - desc->channels; + channels = desc->channels; - for (int px_pos = 0; px_pos < px_len; px_pos += channels) { + for (px_pos = 0; px_pos < px_len; px_pos += channels) { if (channels == 4) { px = *(qoi_rgba_t *)(pixels + px_pos); } else { - px.rgba.r = pixels[px_pos]; - px.rgba.g = pixels[px_pos+1]; - px.rgba.b = pixels[px_pos+2]; + px.rgba.r = pixels[px_pos + 0]; + px.rgba.g = pixels[px_pos + 1]; + px.rgba.b = pixels[px_pos + 2]; } if (px.v == px_prev.v) { run++; + if (run == 62 || px_pos == px_end) { + bytes[p++] = QOI_OP_RUN | (run - 1); + run = 0; + } } + else { + int index_pos; - if ( - run > 0 && - (run == 0x2020 || px.v != px_prev.v || px_pos == px_end) - ) { - if (run < 33) { - run -= 1; - bytes[p++] = QOI_RUN_8 | run; + if (run > 0) { + bytes[p++] = QOI_OP_RUN | (run - 1); + run = 0; } - else { - run -= 33; - bytes[p++] = QOI_RUN_16 | run >> 8; - bytes[p++] = run; - } - run = 0; - } - if (px.v != px_prev.v) { - int index_pos = QOI_COLOR_HASH(px) % 64; + index_pos = QOI_COLOR_HASH(px) % 64; if (index[index_pos].v == px.v) { - bytes[p++] = QOI_INDEX | index_pos; + bytes[p++] = QOI_OP_INDEX | index_pos; } else { index[index_pos] = px; - int vr = px.rgba.r - px_prev.rgba.r; - int vg = px.rgba.g - px_prev.rgba.g; - int vb = px.rgba.b - px_prev.rgba.b; - int va = px.rgba.a - px_prev.rgba.a; - - if ( - vr > -17 && vr < 16 && - vg > -17 && vg < 16 && - vb > -17 && vb < 16 && - va > -17 && va < 16 - ) { + if (px.rgba.a == px_prev.rgba.a) { + signed char vr = px.rgba.r - px_prev.rgba.r; + signed char vg = px.rgba.g - px_prev.rgba.g; + signed char vb = px.rgba.b - px_prev.rgba.b; + + signed char vg_r = vr - vg; + signed char vg_b = vb - vg; + if ( - va == 0 && vr > -3 && vr < 2 && - vg > -3 && vg < 2 && + vg > -3 && vg < 2 && vb > -3 && vb < 2 ) { - bytes[p++] = QOI_DIFF_8 | ((vr + 2) << 4) | (vg + 2) << 2 | (vb + 2); + bytes[p++] = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2); } else if ( - va == 0 && - vr > -17 && vr < 16 && - vg > -9 && vg < 8 && - vb > -9 && vb < 8 + vg_r > -9 && vg_r < 8 && + vg > -33 && vg < 32 && + vg_b > -9 && vg_b < 8 ) { - bytes[p++] = QOI_DIFF_16 | (vr + 16); - bytes[p++] = (vg + 8) << 4 | (vb + 8); + bytes[p++] = QOI_OP_LUMA | (vg + 32); + bytes[p++] = (vg_r + 8) << 4 | (vg_b + 8); } else { - bytes[p++] = QOI_DIFF_24 | (vr + 16) >> 1; - bytes[p++] = (vr + 16) << 7 | (vg + 16) << 2 | (vb + 16) >> 3; - bytes[p++] = (vb + 16) << 5 | (va + 16); + bytes[p++] = QOI_OP_RGB; + bytes[p++] = px.rgba.r; + bytes[p++] = px.rgba.g; + bytes[p++] = px.rgba.b; } } else { - bytes[p++] = QOI_COLOR | (vr ? 8 : 0) | (vg ? 4 : 0) | (vb ? 2 : 0) | (va ? 1 : 0); - if (vr) { bytes[p++] = px.rgba.r; } - if (vg) { bytes[p++] = px.rgba.g; } - if (vb) { bytes[p++] = px.rgba.b; } - if (va) { bytes[p++] = px.rgba.a; } + bytes[p++] = QOI_OP_RGBA; + bytes[p++] = px.rgba.r; + bytes[p++] = px.rgba.g; + bytes[p++] = px.rgba.b; + bytes[p++] = px.rgba.a; } } } px_prev = px; } - for (int i = 0; i < QOI_PADDING; i++) { - bytes[p++] = 0; + for (i = 0; i < (int)sizeof(qoi_padding); i++) { + bytes[p++] = qoi_padding[i]; } *out_len = p; @@ -481,27 +507,36 @@ void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) { } void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) { + const unsigned char *bytes; + unsigned int header_magic; + unsigned char *pixels; + qoi_rgba_t index[64]; + qoi_rgba_t px; + int px_len, chunks_len, px_pos; + int p = 0, run = 0; + if ( data == NULL || desc == NULL || (channels != 0 && channels != 3 && channels != 4) || - size < QOI_HEADER_SIZE + QOI_PADDING + size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding) ) { return NULL; } - const unsigned char *bytes = (const unsigned char *)data; - int p = 0; + bytes = (const unsigned char *)data; - unsigned int header_magic = qoi_read_32(bytes, &p); + header_magic = qoi_read_32(bytes, &p); desc->width = qoi_read_32(bytes, &p); desc->height = qoi_read_32(bytes, &p); desc->channels = bytes[p++]; desc->colorspace = bytes[p++]; if ( - desc->width == 0 || desc->height == 0 || + desc->width == 0 || desc->height == 0 || desc->channels < 3 || desc->channels > 4 || - header_magic != QOI_MAGIC + desc->colorspace > 1 || + header_magic != QOI_MAGIC || + desc->height >= QOI_PIXELS_MAX / desc->width ) { return NULL; } @@ -510,70 +545,66 @@ void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) { channels = desc->channels; } - int px_len = desc->width * desc->height * channels; - unsigned char *pixels = QOI_MALLOC(px_len); + px_len = desc->width * desc->height * channels; + pixels = (unsigned char *) QOI_MALLOC(px_len); if (!pixels) { return NULL; } - qoi_rgba_t px = {.rgba = {.r = 0, .g = 0, .b = 0, .a = 255}}; - qoi_rgba_t index[64] = {0}; + QOI_ZEROARR(index); + px.rgba.r = 0; + px.rgba.g = 0; + px.rgba.b = 0; + px.rgba.a = 255; - int run = 0; - int chunks_len = size - QOI_PADDING; - for (int px_pos = 0; px_pos < px_len; px_pos += channels) { + chunks_len = size - (int)sizeof(qoi_padding); + for (px_pos = 0; px_pos < px_len; px_pos += channels) { if (run > 0) { run--; } else if (p < chunks_len) { int b1 = bytes[p++]; - if ((b1 & QOI_MASK_2) == QOI_INDEX) { - px = index[b1 ^ QOI_INDEX]; + if (b1 == QOI_OP_RGB) { + px.rgba.r = bytes[p++]; + px.rgba.g = bytes[p++]; + px.rgba.b = bytes[p++]; } - else if ((b1 & QOI_MASK_3) == QOI_RUN_8) { - run = (b1 & 0x1f); + else if (b1 == QOI_OP_RGBA) { + px.rgba.r = bytes[p++]; + px.rgba.g = bytes[p++]; + px.rgba.b = bytes[p++]; + px.rgba.a = bytes[p++]; } - else if ((b1 & QOI_MASK_3) == QOI_RUN_16) { - int b2 = bytes[p++]; - run = (((b1 & 0x1f) << 8) | (b2)) + 32; + else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) { + px = index[b1]; } - else if ((b1 & QOI_MASK_2) == QOI_DIFF_8) { + else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) { px.rgba.r += ((b1 >> 4) & 0x03) - 2; px.rgba.g += ((b1 >> 2) & 0x03) - 2; px.rgba.b += ( b1 & 0x03) - 2; } - else if ((b1 & QOI_MASK_3) == QOI_DIFF_16) { - int b2 = bytes[p++]; - px.rgba.r += (b1 & 0x1f) - 16; - px.rgba.g += (b2 >> 4) - 8; - px.rgba.b += (b2 & 0x0f) - 8; - } - else if ((b1 & QOI_MASK_4) == QOI_DIFF_24) { + else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) { int b2 = bytes[p++]; - int b3 = bytes[p++]; - px.rgba.r += (((b1 & 0x0f) << 1) | (b2 >> 7)) - 16; - px.rgba.g += ((b2 & 0x7c) >> 2) - 16; - px.rgba.b += (((b2 & 0x03) << 3) | ((b3 & 0xe0) >> 5)) - 16; - px.rgba.a += (b3 & 0x1f) - 16; + int vg = (b1 & 0x3f) - 32; + px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f); + px.rgba.g += vg; + px.rgba.b += vg - 8 + (b2 & 0x0f); } - else if ((b1 & QOI_MASK_4) == QOI_COLOR) { - if (b1 & 8) { px.rgba.r = bytes[p++]; } - if (b1 & 4) { px.rgba.g = bytes[p++]; } - if (b1 & 2) { px.rgba.b = bytes[p++]; } - if (b1 & 1) { px.rgba.a = bytes[p++]; } + else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) { + run = (b1 & 0x3f); } index[QOI_COLOR_HASH(px) % 64] = px; } - if (channels == 4) { + if (channels == 4) { *(qoi_rgba_t*)(pixels + px_pos) = px; } else { - pixels[px_pos] = px.rgba.r; - pixels[px_pos+1] = px.rgba.g; - pixels[px_pos+2] = px.rgba.b; + pixels[px_pos + 0] = px.rgba.r; + pixels[px_pos + 1] = px.rgba.g; + pixels[px_pos + 2] = px.rgba.b; } } @@ -585,48 +616,56 @@ void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) { int qoi_write(const char *filename, const void *data, const qoi_desc *desc) { FILE *f = fopen(filename, "wb"); + int size; + void *encoded; + if (!f) { return 0; } - int size; - void *encoded = qoi_encode(data, desc, &size); + encoded = qoi_encode(data, desc, &size); if (!encoded) { fclose(f); return 0; - } - + } + fwrite(encoded, 1, size, f); fclose(f); - + QOI_FREE(encoded); return size; } void *qoi_read(const char *filename, qoi_desc *desc, int channels) { FILE *f = fopen(filename, "rb"); + int size, bytes_read; + void *pixels, *data; + if (!f) { return NULL; } fseek(f, 0, SEEK_END); - int size = ftell(f); + size = ftell(f); + if (size <= 0) { + fclose(f); + return NULL; + } fseek(f, 0, SEEK_SET); - void *data = QOI_MALLOC(size); + data = QOI_MALLOC(size); if (!data) { fclose(f); return NULL; } - int bytes_read = fread(data, 1, size, f); + bytes_read = fread(data, 1, size, f); fclose(f); - void *pixels = qoi_decode(data, bytes_read, desc, channels); + pixels = qoi_decode(data, bytes_read, desc, channels); QOI_FREE(data); return pixels; } -#endif // QOI_NO_STDIO -#endif // QOI_IMPLEMENTATION - +#endif /* QOI_NO_STDIO */ +#endif /* QOI_IMPLEMENTATION */ diff --git a/phoboslab/qoi_old.h b/phoboslab/qoi_old.h new file mode 100644 index 0000000..d70e2e8 --- /dev/null +++ b/phoboslab/qoi_old.h @@ -0,0 +1,632 @@ +/* + +QOI - The "Quite OK Image" format for fast, lossless image compression + +Dominic Szablewski - https://phoboslab.org + + +-- LICENSE: The MIT License(MIT) + +Copyright(c) 2021 Dominic Szablewski + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files(the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions : +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +-- About + +QOI encodes and decodes images in a lossless format. An encoded QOI image is +usually around 10--30% larger than a decently optimized PNG image. + +QOI outperforms simpler PNG encoders in compression ratio and performance. QOI +images are typically 20% smaller than PNGs written with stbi_image. Encoding is +25-50x faster and decoding is 3-4x faster than stbi_image or libpng. + + +-- Synopsis + +// Define `QOI_IMPLEMENTATION` in *one* C/C++ file before including this +// library to create the implementation. + +#define QOI_IMPLEMENTATION +#include "qoi.h" + +// Encode and store an RGBA buffer to the file system. The qoi_desc describes +// the input pixel data. +qoi_write("image_new.qoi", rgba_pixels, &(qoi_desc){ + .width = 1920, + .height = 1080, + .channels = 4, + .colorspace = QOI_SRGB +}); + +// Load and decode a QOI image from the file system into a 32bbp RGBA buffer. +// The qoi_desc struct will be filled with the width, height, number of channels +// and colorspace read from the file header. +qoi_desc desc; +void *rgba_pixels = qoi_read("image.qoi", &desc, 4); + + + +-- Documentation + +This library provides the following functions; +- qoi_read -- read and decode a QOI file +- qoi_decode -- decode the raw bytes of a QOI image from memory +- qoi_write -- encode and write a QOI file +- qoi_encode -- encode an rgba buffer into a QOI image in memory + +See the function declaration below for the signature and more information. + +If you don't want/need the qoi_read and qoi_write functions, you can define +QOI_NO_STDIO before including this library. + +This library uses malloc() and free(). To supply your own malloc implementation +you can define QOI_MALLOC and QOI_FREE before including this library. + + +-- Data Format + +A QOI file has a 14 byte header, followed by any number of data "chunks". + +struct qoi_header_t { + char magic[4]; // magic bytes "qoif" + uint32_t width; // image width in pixels (BE) + uint32_t height; // image height in pixels (BE) + uint8_t channels; // must be 3 (RGB) or 4 (RGBA) + uint8_t colorspace; // a bitmap 0000rgba where + // - a zero bit indicates sRGBA, + // - a one bit indicates linear (user interpreted) + // colorspace for each channel +}; + +The decoder and encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous +pixel value. Pixels are either encoded as + - a run of the previous pixel + - an index into a previously seen pixel + - a difference to the previous pixel value in r,g,b,a + - full r,g,b,a values + +A running array[64] of previously seen pixel values is maintained by the encoder +and decoder. Each pixel that is seen by the encoder and decoder is put into this +array at the position (r^g^b^a) % 64. In the encoder, if the pixel value at this +index matches the current pixel, this index position is written to the stream. + +Each chunk starts with a 2, 3 or 4 bit tag, followed by a number of data bits. +The bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. +All values encoded in these data bits have the most significant bit (MSB) on the +left. + +The possible chunks are: + + - QOI_INDEX ------------- +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------+-----------------| +| 0 0 | index | + +2-bit tag b00 +6-bit index into the color index array: 0..63 + + + - QOI_RUN_8 ------------- +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|----------+--------------| +| 0 1 0 | run | + +3-bit tag b010 +5-bit run-length repeating the previous pixel: 1..32 + + + - QOI_RUN_16 -------------------------------------- +| Byte[0] | Byte[1] | +| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | +|----------+----------------------------------------| +| 0 1 1 | run | + +3-bit tag b011 +13-bit run-length repeating the previous pixel: 33..8224 + + + - QOI_DIFF_8 ------------ +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------+-----+-----+-----| +| 1 0 | dr | db | bg | + +2-bit tag b10 +2-bit red channel difference from the previous pixel between -2..1 +2-bit green channel difference from the previous pixel between -2..1 +2-bit blue channel difference from the previous pixel between -2..1 + +The difference to the current channel values are using a wraparound operation, +so "1 - 2" will result in 255, while "255 + 1" will result in 0. + + + - QOI_DIFF_16 ------------------------------------- +| Byte[0] | Byte[1] | +| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | +|----------+--------------|------------ +-----------| +| 1 1 0 | red diff | green diff | blue diff | + +3-bit tag b110 +5-bit red channel difference from the previous pixel between -16..15 +4-bit green channel difference from the previous pixel between -8..7 +4-bit blue channel difference from the previous pixel between -8..7 + +The difference to the current channel values are using a wraparound operation, +so "10 - 13" will result in 253, while "250 + 7" will result in 1. + + + - QOI_DIFF_24 --------------------------------------------------------------- +| Byte[0] | Byte[1] | Byte[2] | +| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | +|-------------+----------------+--------------+----------------+--------------| +| 1 1 1 0 | red diff | green diff | blue diff | alpha diff | + +4-bit tag b1110 +5-bit red channel difference from the previous pixel between -16..15 +5-bit green channel difference from the previous pixel between -16..15 +5-bit blue channel difference from the previous pixel between -16..15 +5-bit alpha channel difference from the previous pixel between -16..15 + +The difference to the current channel values are using a wraparound operation, +so "10 - 13" will result in 253, while "250 + 7" will result in 1. + + + - QOI_COLOR ------------- +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------------+--+--+--+--| +| 1 1 1 1 |hr|hg|hb|ha| + +4-bit tag b1111 +1-bit red byte follows +1-bit green byte follows +1-bit blue byte follows +1-bit alpha byte follows + +For each set bit hr, hg, hb and ha another byte follows in this order. If such a +byte follows, it will replace the current color channel value with the value of +this byte. + + +The byte stream is padded at the end with 4 zero bytes. Size the longest chunk +we can encounter is 5 bytes (QOI_COLOR with RGBA set), with this padding we just +have to check for an overrun once per decode loop iteration. + +*/ + + +// ----------------------------------------------------------------------------- +// Header - Public functions + +#ifndef QOI_H +#define QOI_H + +#ifdef __cplusplus +extern "C" { +#endif + +// A pointer to qoi_desc struct has to be supplied to all of qoi's functions. It +// describes either the input format (for qoi_write, qoi_encode), or is filled +// with the description read from the file header (for qoi_read, qoi_decode). + +// The colorspace in this qoi_desc is a bitmap with 0000rgba where a 0-bit +// indicates sRGB and a 1-bit indicates linear colorspace for each channel. You +// may use one of the predefined constants: QOI_SRGB, QOI_SRGB_LINEAR_ALPHA or +// QOI_LINEAR. The colorspace is purely informative. It will be saved to the +// file header, but does not affect en-/decoding in any way. + +#define QOI_SRGB 0x00 +#define QOI_SRGB_LINEAR_ALPHA 0x01 +#define QOI_LINEAR 0x0f + +typedef struct { + unsigned int width; + unsigned int height; + unsigned char channels; + unsigned char colorspace; +} qoi_desc; + +#ifndef QOI_NO_STDIO + +// Encode raw RGB or RGBA pixels into a QOI image and write it to the file +// system. The qoi_desc struct must be filled with the image width, height, +// number of channels (3 = RGB, 4 = RGBA) and the colorspace. + +// The function returns 0 on failure (invalid parameters, or fopen or malloc +// failed) or the number of bytes written on success. + +int qoi_write(const char *filename, const void *data, const qoi_desc *desc); + + +// Read and decode a QOI image from the file system. If channels is 0, the +// number of channels from the file header is used. If channels is 3 or 4 the +// output format will be forced into this number of channels. + +// The function either returns NULL on failure (invalid data, or malloc or fopen +// failed) or a pointer to the decoded pixels. On success, the qoi_desc struct +// will be filled with the description from the file header. + +// The returned pixel data should be free()d after use. + +void *qoi_read(const char *filename, qoi_desc *desc, int channels); + +#endif // QOI_NO_STDIO + + +// Encode raw RGB or RGBA pixels into a QOI image in memory. + +// The function either returns NULL on failure (invalid parameters or malloc +// failed) or a pointer to the encoded data on success. On success the out_len +// is set to the size in bytes of the encoded data. + +// The returned qoi data should be free()d after use. + +void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len); + + +// Decode a QOI image from memory. + +// The function either returns NULL on failure (invalid parameters or malloc +// failed) or a pointer to the decoded pixels. On success, the qoi_desc struct +// is filled with the description from the file header. + +// The returned pixel data should be free()d after use. + +void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels); + + +#ifdef __cplusplus +} +#endif +#endif // QOI_H + + +// ----------------------------------------------------------------------------- +// Implementation + +#ifdef QOI_IMPLEMENTATION +#include + +#ifndef QOI_MALLOC + #define QOI_MALLOC(sz) malloc(sz) + #define QOI_FREE(p) free(p) +#endif + +#define QOI_INDEX 0x00 // 00xxxxxx +#define QOI_RUN_8 0x40 // 010xxxxx +#define QOI_RUN_16 0x60 // 011xxxxx +#define QOI_DIFF_8 0x80 // 10xxxxxx +#define QOI_DIFF_16 0xc0 // 110xxxxx +#define QOI_DIFF_24 0xe0 // 1110xxxx +#define QOI_COLOR 0xf0 // 1111xxxx + +#define QOI_MASK_2 0xc0 // 11000000 +#define QOI_MASK_3 0xe0 // 11100000 +#define QOI_MASK_4 0xf0 // 11110000 + +#define QOI_COLOR_HASH(C) (C.rgba.r ^ C.rgba.g ^ C.rgba.b ^ C.rgba.a) +#define QOI_MAGIC \ + (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \ + ((unsigned int)'i') << 8 | ((unsigned int)'f')) +#define QOI_HEADER_SIZE 14 +#define QOI_PADDING 4 + +typedef union { + struct { unsigned char r, g, b, a; } rgba; + unsigned int v; +} qoi_rgba_t; + +void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) { + bytes[(*p)++] = (0xff000000 & v) >> 24; + bytes[(*p)++] = (0x00ff0000 & v) >> 16; + bytes[(*p)++] = (0x0000ff00 & v) >> 8; + bytes[(*p)++] = (0x000000ff & v); +} + +unsigned int qoi_read_32(const unsigned char *bytes, int *p) { + unsigned int a = bytes[(*p)++]; + unsigned int b = bytes[(*p)++]; + unsigned int c = bytes[(*p)++]; + unsigned int d = bytes[(*p)++]; + return (a << 24) | (b << 16) | (c << 8) | d; +} + +void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) { + if ( + data == NULL || out_len == NULL || desc == NULL || + desc->width == 0 || desc->height == 0 || + desc->channels < 3 || desc->channels > 4 || + (desc->colorspace & 0xf0) != 0 + ) { + return NULL; + } + + int max_size = + desc->width * desc->height * (desc->channels + 1) + + QOI_HEADER_SIZE + QOI_PADDING; + + int p = 0; + unsigned char *bytes = QOI_MALLOC(max_size); + if (!bytes) { + return NULL; + } + + qoi_write_32(bytes, &p, QOI_MAGIC); + qoi_write_32(bytes, &p, desc->width); + qoi_write_32(bytes, &p, desc->height); + bytes[p++] = desc->channels; + bytes[p++] = desc->colorspace; + + + const unsigned char *pixels = (const unsigned char *)data; + + qoi_rgba_t index[64] = {0}; + + int run = 0; + qoi_rgba_t px_prev = {.rgba = {.r = 0, .g = 0, .b = 0, .a = 255}}; + qoi_rgba_t px = px_prev; + + int px_len = desc->width * desc->height * desc->channels; + int px_end = px_len - desc->channels; + int channels = desc->channels; + + for (int px_pos = 0; px_pos < px_len; px_pos += channels) { + if (channels == 4) { + px = *(qoi_rgba_t *)(pixels + px_pos); + } + else { + px.rgba.r = pixels[px_pos]; + px.rgba.g = pixels[px_pos+1]; + px.rgba.b = pixels[px_pos+2]; + } + + if (px.v == px_prev.v) { + run++; + } + + if ( + run > 0 && + (run == 0x2020 || px.v != px_prev.v || px_pos == px_end) + ) { + if (run < 33) { + run -= 1; + bytes[p++] = QOI_RUN_8 | run; + } + else { + run -= 33; + bytes[p++] = QOI_RUN_16 | run >> 8; + bytes[p++] = run; + } + run = 0; + } + + if (px.v != px_prev.v) { + int index_pos = QOI_COLOR_HASH(px) % 64; + + if (index[index_pos].v == px.v) { + bytes[p++] = QOI_INDEX | index_pos; + } + else { + index[index_pos] = px; + + int vr = px.rgba.r - px_prev.rgba.r; + int vg = px.rgba.g - px_prev.rgba.g; + int vb = px.rgba.b - px_prev.rgba.b; + int va = px.rgba.a - px_prev.rgba.a; + + if ( + vr > -17 && vr < 16 && + vg > -17 && vg < 16 && + vb > -17 && vb < 16 && + va > -17 && va < 16 + ) { + if ( + va == 0 && + vr > -3 && vr < 2 && + vg > -3 && vg < 2 && + vb > -3 && vb < 2 + ) { + bytes[p++] = QOI_DIFF_8 | ((vr + 2) << 4) | (vg + 2) << 2 | (vb + 2); + } + else if ( + va == 0 && + vr > -17 && vr < 16 && + vg > -9 && vg < 8 && + vb > -9 && vb < 8 + ) { + bytes[p++] = QOI_DIFF_16 | (vr + 16); + bytes[p++] = (vg + 8) << 4 | (vb + 8); + } + else { + bytes[p++] = QOI_DIFF_24 | (vr + 16) >> 1; + bytes[p++] = (vr + 16) << 7 | (vg + 16) << 2 | (vb + 16) >> 3; + bytes[p++] = (vb + 16) << 5 | (va + 16); + } + } + else { + bytes[p++] = QOI_COLOR | (vr ? 8 : 0) | (vg ? 4 : 0) | (vb ? 2 : 0) | (va ? 1 : 0); + if (vr) { bytes[p++] = px.rgba.r; } + if (vg) { bytes[p++] = px.rgba.g; } + if (vb) { bytes[p++] = px.rgba.b; } + if (va) { bytes[p++] = px.rgba.a; } + } + } + } + px_prev = px; + } + + for (int i = 0; i < QOI_PADDING; i++) { + bytes[p++] = 0; + } + + *out_len = p; + return bytes; +} + +void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) { + if ( + data == NULL || desc == NULL || + (channels != 0 && channels != 3 && channels != 4) || + size < QOI_HEADER_SIZE + QOI_PADDING + ) { + return NULL; + } + + const unsigned char *bytes = (const unsigned char *)data; + int p = 0; + + unsigned int header_magic = qoi_read_32(bytes, &p); + desc->width = qoi_read_32(bytes, &p); + desc->height = qoi_read_32(bytes, &p); + desc->channels = bytes[p++]; + desc->colorspace = bytes[p++]; + + if ( + desc->width == 0 || desc->height == 0 || + desc->channels < 3 || desc->channels > 4 || + header_magic != QOI_MAGIC + ) { + return NULL; + } + + if (channels == 0) { + channels = desc->channels; + } + + int px_len = desc->width * desc->height * channels; + unsigned char *pixels = QOI_MALLOC(px_len); + if (!pixels) { + return NULL; + } + + qoi_rgba_t px = {.rgba = {.r = 0, .g = 0, .b = 0, .a = 255}}; + qoi_rgba_t index[64] = {0}; + + int run = 0; + int chunks_len = size - QOI_PADDING; + for (int px_pos = 0; px_pos < px_len; px_pos += channels) { + if (run > 0) { + run--; + } + else if (p < chunks_len) { + int b1 = bytes[p++]; + + if ((b1 & QOI_MASK_2) == QOI_INDEX) { + px = index[b1 ^ QOI_INDEX]; + } + else if ((b1 & QOI_MASK_3) == QOI_RUN_8) { + run = (b1 & 0x1f); + } + else if ((b1 & QOI_MASK_3) == QOI_RUN_16) { + int b2 = bytes[p++]; + run = (((b1 & 0x1f) << 8) | (b2)) + 32; + } + else if ((b1 & QOI_MASK_2) == QOI_DIFF_8) { + px.rgba.r += ((b1 >> 4) & 0x03) - 2; + px.rgba.g += ((b1 >> 2) & 0x03) - 2; + px.rgba.b += ( b1 & 0x03) - 2; + } + else if ((b1 & QOI_MASK_3) == QOI_DIFF_16) { + int b2 = bytes[p++]; + px.rgba.r += (b1 & 0x1f) - 16; + px.rgba.g += (b2 >> 4) - 8; + px.rgba.b += (b2 & 0x0f) - 8; + } + else if ((b1 & QOI_MASK_4) == QOI_DIFF_24) { + int b2 = bytes[p++]; + int b3 = bytes[p++]; + px.rgba.r += (((b1 & 0x0f) << 1) | (b2 >> 7)) - 16; + px.rgba.g += ((b2 & 0x7c) >> 2) - 16; + px.rgba.b += (((b2 & 0x03) << 3) | ((b3 & 0xe0) >> 5)) - 16; + px.rgba.a += (b3 & 0x1f) - 16; + } + else if ((b1 & QOI_MASK_4) == QOI_COLOR) { + if (b1 & 8) { px.rgba.r = bytes[p++]; } + if (b1 & 4) { px.rgba.g = bytes[p++]; } + if (b1 & 2) { px.rgba.b = bytes[p++]; } + if (b1 & 1) { px.rgba.a = bytes[p++]; } + } + + index[QOI_COLOR_HASH(px) % 64] = px; + } + + if (channels == 4) { + *(qoi_rgba_t*)(pixels + px_pos) = px; + } + else { + pixels[px_pos] = px.rgba.r; + pixels[px_pos+1] = px.rgba.g; + pixels[px_pos+2] = px.rgba.b; + } + } + + return pixels; +} + +#ifndef QOI_NO_STDIO +#include + +int qoi_write(const char *filename, const void *data, const qoi_desc *desc) { + FILE *f = fopen(filename, "wb"); + if (!f) { + return 0; + } + + int size; + void *encoded = qoi_encode(data, desc, &size); + if (!encoded) { + fclose(f); + return 0; + } + + fwrite(encoded, 1, size, f); + fclose(f); + + QOI_FREE(encoded); + return size; +} + +void *qoi_read(const char *filename, qoi_desc *desc, int channels) { + FILE *f = fopen(filename, "rb"); + if (!f) { + return NULL; + } + + fseek(f, 0, SEEK_END); + int size = ftell(f); + fseek(f, 0, SEEK_SET); + + void *data = QOI_MALLOC(size); + if (!data) { + fclose(f); + return NULL; + } + + int bytes_read = fread(data, 1, size, f); + fclose(f); + + void *pixels = qoi_decode(data, bytes_read, desc, channels); + QOI_FREE(data); + return pixels; +} + +#endif // QOI_NO_STDIO +#endif // QOI_IMPLEMENTATION + diff --git a/sprites_autocombine.h b/sprites_autocombine.h new file mode 100644 index 0000000..2808eb0 --- /dev/null +++ b/sprites_autocombine.h @@ -0,0 +1,15 @@ +static enum sprites_auto_combine_index +{ + k_sprite_donut, + k_sprite_jack_1, + k_sprite_jack_2, + k_sprite_peg, +}; + +static struct vg_sprite sprites_auto_combine[] = +{ + { 0, 0, 8192, 8192 }, + { 8192, 0, 4096, 4096 }, + { 12288, 0, 4096, 4096 }, + { 16384, 0, 4096, 4096 }, +}; \ No newline at end of file diff --git a/texsheet.c b/texsheet.c new file mode 100644 index 0000000..f420866 --- /dev/null +++ b/texsheet.c @@ -0,0 +1,214 @@ +// Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved + +#define VG_TOOLS +#include "vg/vg.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "stb/stb_image.h" + +struct image_src +{ + int x,y,ch; + + u8 *data; +}; + +int image_sort( const void* a, const void* b) +{ + struct image_src *p_a = (struct image_src *)a; + struct image_src *p_b = (struct image_src *)b; + + if( p_a->x == p_b->x ) + return 0; + else if ( p_a->x < p_b->x ) + return 1; + else + return -1; +} + +int main( int argc, const char *argv[] ) +{ + struct image_src *source_images = malloc( sizeof( struct image_src ) * argc ); + + u32 num_images = 0; + + if( argc < 4 ) + { + vg_error( "Missing output file paths\n" ); + return 0; + } + + // Open header handle + // ------------------ + FILE *fp = fopen( argv[2], "w" ); + if( !fp ) + { + vg_error( "Could not open file for writing\n" ); + return 0; + } + + fprintf( fp, "static enum %s_index\n{\n", argv[3] ); + + // Load images + // ----------- + stbi_set_flip_vertically_on_load(1); + + for( int i = 4; i < argc; i ++ ) + { + struct image_src *src = &source_images[ num_images ]; + src->data = (u8 *)stbi_load( argv[i], &src->x, &src->y, &src->ch, 4 ); + + char name[ 256 ]; + int j = 0; int ext = 0; + for( ; j < vg_list_size( name )-1; j ++ ) + { + if( argv[i][j] ) + { + name[j] = argv[i][j]; + + if( name[j] == '.' ) + ext = j; + + if( name[j] == '.' || name[j] == '-' ) + name[j] = '_'; + } + else + break; + } + + if( ext ) + name[ext] = 0x00; + else + name[j] = 0x00; + + fprintf( fp, "\tk_sprite_%s,\n", name ); + + if( src->data ) + { + if( src->x != src->y ) + { + vg_error( "Non-square images are currently not supported ('%s')\n", argv[i] ); + free( src->data ); + } + else + num_images ++; + } + else + vg_error( "Could not decode '%s'\n", argv[i] ); + } + + fprintf( fp, "};\n\n" ); + + // Sort by size + // ------------ + qsort( source_images, num_images, sizeof(struct image_src), image_sort ); + + // Process images + // -------------- + fprintf( fp, "static struct vg_sprite %s[] = \n{\n", argv[3] ); + + u8 *dest = (u8 *)malloc( 1024*1024*4 ); + + // Clear (temp) + for( int i = 0; i < 1024*1024; i ++ ) + { + dest[ i*4 + 0 ] = 0; + dest[ i*4 + 1 ] = 0; + dest[ i*4 + 2 ] = 128; + dest[ i*4 + 3 ] = 255; + } + + struct region + { + v2i p0; + v2i p1; + } + region_stack[ 32 ] = + { + { + .p0 = { 0, 0 }, + .p1 = { 1024, 1024 } + } + }; + int stack_h = 0; + + int sf = 64; + + for( int i = 0; i < num_images; i ++ ) + { + struct image_src *psrc = &source_images[ i ]; + + // Region checks + while( 1 ) + { + struct region *pregion = ®ion_stack[ stack_h ]; + + if( (pregion->p0[ 0 ] + psrc->x <= pregion->p1[0]) && (pregion->p0[ 1 ] + psrc->y <= pregion->p1[1]) ) + { + // Passed, add image and create subdivisions + fprintf( fp, "\t{ %hu, %hu, %hu, %hu },\n", + (u16)(sf * pregion->p0[0]), + (u16)(sf * pregion->p0[1]), + (u16)(sf * psrc->x), + (u16)(sf * psrc->y) + ); + + // Write image + for( int y = 0; y < psrc->y; y ++ ) + { + int px = pregion->p0[0]; + int py = pregion->p0[1] + y; + + memcpy( &dest[ (py*1024+px) * 4 ], &psrc->data[ y*psrc->x*4 ], psrc->x*4 ); + } + + // Subdivisions + stack_h ++; + struct region *new_region = ®ion_stack[ stack_h ]; + + new_region->p0[0] = pregion->p0[0] + psrc->x; + new_region->p0[1] = pregion->p0[1]; + new_region->p1[0] = pregion->p1[0]; + new_region->p1[1] = pregion->p0[1] + psrc->y; + + pregion->p0[ 1 ] += psrc->y; + break; + } + else + { + // Failed, loop up to next region if can + if( stack_h == 0 ) + { + vg_error( "Could not fit image %d. Pack failed\n", i ); + + goto IL_END_ERR; + } + else + stack_h --; + } + } + } + +IL_END_ERR: + fprintf( fp, "};" ); + fclose( fp ); + + // Write output + // ------------ + qoi_write( argv[1], dest, &(qoi_desc){ + .width = 1024, + .height = 1024, + .channels = 4, + .colorspace = QOI_SRGB + }); + + // Free + // ---- + for( int i = 0; i < num_images; i ++ ) + free( source_images[ i ].data ); + free( dest ); + free( source_images ); + + vg_success( "Processed %u images\n", num_images ); + +} diff --git a/textures_combine/donut.png b/textures_combine/donut.png new file mode 100644 index 0000000000000000000000000000000000000000..a585c5ebb2721a5b510105c793a563f8cef4bc5c GIT binary patch literal 12795 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 literal 0 HcmV?d00001 diff --git a/textures_combine/jack-2.png b/textures_combine/jack-2.png new file mode 100644 index 0000000000000000000000000000000000000000..92488da62865fa671db2845c776c0c4ff7bd7782 GIT binary patch literal 6102 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 literal 0 HcmV?d00001 diff --git a/textures_combine/peg.png b/textures_combine/peg.png new file mode 100644 index 0000000000000000000000000000000000000000..83b366b374f16d68dbec866f5adc6f878fe37f24 GIT binary patch literal 4705 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