Add PNG loading support

This commit is contained in:
Phireh 2025-05-12 00:57:25 +02:00
commit ea9a94dc08
3 changed files with 158 additions and 22 deletions

172
main.cpp
View file

@ -36,6 +36,9 @@
#include "stb_sprintf.h"
#undef STB_SPRINTF_IMPLEMENTATION
// libpng
#include <png.h>
// POSIX
#include <time.h>
@ -110,6 +113,12 @@ struct selection_t {
int32_t nselected;
};
struct texture_t {
uint32_t gl_id; // OpenGL GLuint ID
uint32_t w;
uint32_t h;
};
const int32_t ARRAY_LIMIT = 400;
charglyph_t glyphmap[128];
@ -121,6 +130,7 @@ glm::mat4 transforms[ARRAY_LIMIT];
int32_t letter_map[ARRAY_LIMIT];
int32_t text_frame_idx;
texture_t pngtex = {};
// error processing
enum err_type {
@ -172,6 +182,65 @@ void scroll_callback([[maybe_unused]]GLFWwindow* window, [[maybe_unused]]double
mouse_scroll = yoffset;
}
int path_callback([[maybe_unused]]ImGuiInputTextCallbackData *data)
{
fprintf(stderr, "Path callback\n");
return 0;
}
int read_png(const char *path)
{
FILE *fd = fopen(path, "rb");
if (!fd) return -1;
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr)
return 4;
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
return 4;
if (setjmp(png_jmpbuf(png_ptr)))
{
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
fclose(fd);
fprintf(stderr, "Error routine\n");
return -1;
}
png_init_io(png_ptr, fd);
png_set_crc_action(png_ptr, PNG_CRC_WARN_USE, PNG_CRC_WARN_USE);
png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_NEVER, NULL, 0);
// TODO: Use low-level interface to read textures in inverted order as an optimization
png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND, NULL);
fprintf(stdout, "File %s read correctly\n", path);
uint32_t row_bytes = png_get_rowbytes(png_ptr, info_ptr);
uint32_t width = png_get_image_width(png_ptr, info_ptr);
uint32_t height = png_get_image_height(png_ptr, info_ptr);
fprintf(stdout, "\tWidth: %d px; Height: %d px; Rowbytes: %d\n", width, height, row_bytes);
uint8_t **row_pointers = png_get_rows(png_ptr, info_ptr);
uint8_t *data = (uint8_t*) calloc(1, row_bytes * height);
for (uint32_t i = 0; i < height; ++i)
memcpy(data + i * row_bytes, row_pointers[height - i - 1], row_bytes);
GLuint tex_id;
glGenTextures(1, &tex_id);
glBindTexture(GL_TEXTURE_2D, tex_id);
GLenum tex_format = GL_RGBA;
glTexImage2D(GL_TEXTURE_2D, 0, tex_format, width, height, 0, tex_format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
pngtex.gl_id = tex_id;
pngtex.w = width;
pngtex.h = height;
return 0;
}
char* load_file(const char *pathname)
{
FILE *text_file = fopen(pathname, "r");
@ -445,6 +514,9 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv)
glGetIntegerv(GL_MINOR_VERSION, &gl_version_minor);
fprintf(stdout, "Using OpenGL %d.%d\n", gl_version_major, gl_version_minor);
fprintf(stdout, "Compiled using libPNG version %s\n", PNG_LIBPNG_VER_STRING);
fprintf(stdout, "Runtime using libPNG version %s\n", png_libpng_ver);
// font library initialization
FT_Library ft;
@ -543,8 +615,11 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv)
double cursor_x;
double cursor_y;
double cursor_x_scaled;
double cursor_y_scaled;
camera_t the_camera;
bool mouse_pressed;
bool right_mouse_pressed;
bool mouse_over_gui;
static bool show_hex_numbers = true;
static selection_t selection;
@ -561,12 +636,19 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv)
double cursor_dy = 0.0;
int hovered_hex = -1;
int32_t window_width, window_height;
int lf, tf, rf, bf;
float content_scale_x, content_scale_y;
int32_t scaled_window_width, scaled_window_height;
{
TIME_BLOCK(window_size_polling);
glfwGetWindowSize(window, &window_width, &window_height);
glViewport(0, 0, window_width, window_height);
glfwGetWindowFrameSize(window, &lf, &tf, &rf, &bf);
glfwGetWindowContentScale(window, &content_scale_x, &content_scale_y);
scaled_window_width = (int)(window_width * content_scale_x);
scaled_window_height = (int)(window_height * content_scale_y);
glViewport(0, 0, scaled_window_width, scaled_window_height);
}
float aspect_ratio = (float)window_width/window_height;
float aspect_ratio = (float)scaled_window_width/scaled_window_height;
{
TIME_BLOCK(input_handling);
@ -595,24 +677,27 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv)
}
mouse_pressed = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS;
right_mouse_pressed = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS;
mouse_over_gui = ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
cursor_dx = cursor_x;
cursor_dy = cursor_y;
cursor_dx = cursor_x * content_scale_x;
cursor_dy = cursor_y * content_scale_y;
glfwGetCursorPos(window, &cursor_x, &cursor_y);
cursor_x_scaled = cursor_x * content_scale_x;
cursor_y_scaled = cursor_y * content_scale_y;
cursor_dx -= cursor_x;
cursor_dy -= cursor_y;
cursor_dx -= cursor_x_scaled;
cursor_dy -= cursor_y_scaled;
// Mouse movement
if (!mouse_over_gui && glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS)
{
fprintf(stdout, "Moving camera by %f %f\n", cursor_dx, cursor_dx);
// mouse delta!
the_camera.position.x += cursor_dx * the_camera.size.x/window_width;
the_camera.position.x += cursor_dx * the_camera.size.x/scaled_window_width;
// The Y coord is inverted
the_camera.position.y -= cursor_dy * the_camera.size.y/window_height;
the_camera.position.y -= cursor_dy * the_camera.size.y/scaled_window_height;
}
if (the_camera.size.x < 0.1)
@ -636,13 +721,13 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv)
view_matrix = glm::mat4(1.0f);
proj_matrix = glm::ortho((the_camera.position.x - the_camera.size.x/2) * aspect_ratio, (the_camera.position.x + the_camera.size.x/2) * aspect_ratio, the_camera.position.y - the_camera.size.y/2, the_camera.position.y + the_camera.size.y/2);
glm::vec4 aux((float)cursor_x, (float)cursor_y, 0.0f, 1.0f);
glm::vec4 aux((float)cursor_x_scaled, (float)cursor_y_scaled, 0.0f, 1.0f);
// move screen coordinates to NDC
aux.x -= window_width/2.0f;
aux.x /= window_width/2.0f;
aux.y -= window_height/2.0f;
aux.y /= -window_height/2.0f;
aux.x -= scaled_window_width/2.0f;
aux.x /= scaled_window_width/2.0f;
aux.y -= scaled_window_height/2.0f;
aux.y /= -scaled_window_height/2.0f;
aux = glm::inverse(proj_matrix) * aux;
cursor_world = glm::vec2(aux.x, aux.y);
@ -662,15 +747,21 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv)
{
select(idx, &selection);
}
else if (right_mouse_pressed)
{
deselect(idx, &selection);
}
if (!is_selected(idx, &selection))
glUniform4f(glGetUniformLocation(hex_program, "hex_color"), 1.0f, 0.0f, 0.0f, 1.0f); // red
else
glUniform4f(glGetUniformLocation(hex_program, "hex_color"), 1.0f, 0.0f, 0.0f, 1.0f);
glUniform4f(glGetUniformLocation(hex_program, "hex_color"), 1.0f, 1.0f, 0.0f, 1.0f); // yellow
}
else
{
if (is_selected(idx, &selection))
{
glUniform4f(glGetUniformLocation(hex_program, "hex_color"), 1.0f, 1.0f, 0.0f, 1.0f);
glUniform4f(glGetUniformLocation(hex_program, "hex_color"), 1.0f, 1.0f, 0.0f, 1.0f); // yellow
}
else
glUniform4f(glGetUniformLocation(hex_program, "hex_color"), grid.hexes[idx].color.x, grid.hexes[idx].color.y, grid.hexes[idx].color.z, grid.hexes[idx].color.w);
@ -691,7 +782,7 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
glm::mat4 text_proj_matrix = glm::ortho(0.0f, (float)window_width, 0.0f, (float)window_height);
glm::mat4 text_proj_matrix = glm::ortho(0.0f, (float)scaled_window_width, 0.0f, (float)scaled_window_height);
glUniformMatrix4fv(glGetUniformLocation(text_program, "projection"), 1, GL_FALSE, &text_proj_matrix[0][0]);
static char debug_text_buf[256];
@ -704,7 +795,7 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv)
glBindVertexArray(textvao);
glBindBuffer(GL_ARRAY_BUFFER, textvbo);
stbsp_snprintf(debug_text_buf, 256, "Cursor position %.0f %.0f screen %.2f %.2f world", cursor_x, cursor_y, cursor_world.x, cursor_world.y);
stbsp_snprintf(debug_text_buf, 256, "Cursor position %.0f %.0f screen %.2f %.2f world", cursor_x_scaled, cursor_y_scaled, cursor_world.x, cursor_world.y);
render_text(debug_text_buf, 25, 25, .5f, glm::vec3(1.0f, 1.0f, 1.0f), true);
glm::mat4 camera_transform = proj_matrix * view_matrix * model_matrix;
@ -714,11 +805,11 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv)
// Get position of hexes in screen
glm::vec4 v = camera_transform * glm::vec4(grid.hexes[i].position.x, grid.hexes[i].position.y, grid.hexes[i].position.z, 1.0);
v.x += 1;
v.x *= window_width/2.0f;
v.x *= scaled_window_width/2.0f;
v.y += 1;
v.y *= window_height/2.0f;
v.y *= scaled_window_height/2.0f;
// Do not draw text outside visible window
if (v.x > window_width || v.x < 0 || v.y > window_height || v.y < 0)
if (v.x > scaled_window_width || v.x < 0 || v.y > scaled_window_height || v.y < 0)
continue;
render_text(&hex_labels[i*label_size], v.x, v.y, .2f + (0.3f / the_camera.size.x), glm::vec3(0.0f, 0.0f, 0.0f), false);
}
@ -740,6 +831,7 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv)
static int grid_rows = grid.rows;
static int grid_columns = grid.columns;
static char image_path[PATH_MAX];
if (grid_rows != grid.rows || grid_columns != grid.columns)
{
@ -756,6 +848,25 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv)
ImGui::SliderInt("Rows", &grid_rows, 1, 100);
ImGui::SliderInt("Columns", &grid_columns, 1, 100);
ImGui::Checkbox("Show hex numbers", &show_hex_numbers);
if (ImGui::CollapsingHeader("Image"))
{
if (ImGui::InputText("Input image", image_path, PATH_MAX, ImGuiInputTextFlags_EnterReturnsTrue, path_callback))
{
// TODO: Remove testing code
fprintf(stdout, "Trying to read file %s\n", image_path);
if (read_png(image_path))
{
fprintf(stdout, "Could not read file %s\n", image_path);
pngtex = {};
}
}
if (pngtex.gl_id)
{
glBindTexture(GL_TEXTURE_2D, pngtex.gl_id);
if (ImGui::CollapsingHeader("Preview"))
ImGui::Image((void*)pngtex.gl_id, ImVec2(pngtex.w, pngtex.h), {0,1}, {1,0});
}
}
if (ImGui::CollapsingHeader("Debug timers"))
{
for (int i = 0; i < ntimers; ++i)
@ -763,6 +874,27 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv)
ImGui::Text("%s: %d nsecs", debug_timers[i].name, debug_timers[i].usec);
}
}
if (ImGui::CollapsingHeader("Debug vars"))
{
ImGui::Text("Window width %d", window_width);
ImGui::Text("Window height %d", window_height);
ImGui::Text("Window borders: left %d top %d right %d bottom %d", lf, tf, rf, bf);
ImGui::Text("Window content scale: %f X %f Y", content_scale_x, content_scale_y);
ImGui::Text("True (scaled) width %d", scaled_window_width);
ImGui::Text("True (scaled) height %d", scaled_window_height);
}
if (ImGui::CollapsingHeader("Debug selection"))
{
ImGui::Text("Selected %d indices", selection.nselected);
std::string aux;
for (int i = 0; i < selection.nselected - 1; ++i)
aux += std::to_string(selection.indices[i]) + ",";
if (selection.nselected)
aux += std::to_string(selection.indices[selection.nselected - 1]);
ImGui::Text(aux.c_str());
}
ImGui::End();
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());