diff --git a/makefile b/makefile index 2d92935..b396943 100644 --- a/makefile +++ b/makefile @@ -1,35 +1,52 @@ -# Compiler and flags -CXX := g++ -CXXFLAGS := -std=c++23 -O3 -march=native -I./imgui -LDFLAGS := -L./imgui -limgui -lstb -lGL -PKG_FLAGS := $(shell pkg-config --cflags --libs glfw3) - # Directories BIN_DIR := ./bin SRC_DIR := ./tests +IMGUI_DIR = ./imgui +OBJ_DIR := $(BIN_DIR)/obj +STB_DIR := ./stb + +# Compiler and flags +CXX := g++ +CXXFLAGS = -std=c++23 -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends -I$(STB_DIR) +#CXXFLAGS += -g -Wall -Wformat +CXXFLAGS += `pkg-config --cflags glfw3` +CFLAGS = $(CXXFLAGS) +LDFLAGS := -L./imgui -limgui -lGL +LINUX_GL_LIBS = -lGL +PKG_FLAGS := $(LINUX_GL_LIBS) `pkg-config --static --cflags --libs glfw3` +CXXFLAGS += $(PKG_FLAGS) # Source files SRC := $(SRC_DIR)/g2chromatic2.cpp -TARGET := $(BIN_DIR)/g2gradc +SRC += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SRC += $(IMGUI_DIR)/backends/imgui_impl_glfw.cpp $(IMGUI_DIR)/backends/imgui_impl_opengl3.cpp +SRC += $(SRC_DIR)/stb_image.cpp +OBJS = $(addprefix $(OBJ_DIR)/, $(addsuffix .o, $(basename $(notdir $(SRC))))) +UNAME_S := $(shell uname -s) +EXE := $(BIN_DIR)/g2gradc -# Default target -all: $(TARGET) +$(shell mkdir -p $(OBJ_DIR)) -# Create binary directory if it doesn't exist -$(BIN_DIR): - @mkdir -p $(BIN_DIR) +$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< -# Build target -$(TARGET): $(SRC) | $(BIN_DIR) - $(CXX) $(CXXFLAGS) -o $@ $< $(LDFLAGS) $(PKG_FLAGS) +$(OBJ_DIR)/%.o: %.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< -# Run the program -run: $(TARGET) - ./$(TARGET) +$(OBJ_DIR)/%.o: $(IMGUI_DIR)/%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +$(OBJ_DIR)/%.o: $(IMGUI_DIR)/backends/%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +$(OBJ_DIR)/%.o: $(STB_DIR)/%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +all: $(EXE) + @echo Build complete for $(ECHO_MESSAGE) + +$(EXE): $(OBJS) + $(CXX) -o $@ $^ $(CXXFLAGS) $(LIBS) -# Clean build artifacts clean: - rm -rf $(BIN_DIR) - -# Phony targets -.PHONY: all run clean \ No newline at end of file + rm -f $(EXE) $(OBJS) \ No newline at end of file diff --git a/tests/g2chromatic2.cpp b/tests/g2chromatic2.cpp index 1485cdf..4f5564f 100644 --- a/tests/g2chromatic2.cpp +++ b/tests/g2chromatic2.cpp @@ -20,6 +20,7 @@ #include #include #include +#include std::mutex m; std::atomic isGenerating{false}; @@ -28,7 +29,15 @@ std::future generationFuture; std::mutex previewMutex; std::atomic updatePreview{false}; frame currentPreviewFrame; -GLuint previewTexture = 0; +GLuint textu = 0; +std::string previewText; + +struct Shared { + std::mutex mutex; + Grid2 grid; + bool hasNewFrame = false; + int currentFrame = 0; +}; struct AnimationConfig { int width = 1024; @@ -69,12 +78,22 @@ void Preview(Grid2& grid) { } } -void livePreview(GLFWwindow* window, Grid2& grid) { - // frame Frame = grid.getGridAsFrame(frame::colormap::RGB); - // int image_width = Frame.getWidth(); - // int image_height = Frame.getHeight(); - // auto data = reinterpret_cast(Frame.getData()); - // uint8_t* image_data = stbi_load_from_memory((const unsigned char*)data, (int)data.size(), &image_width, &image_height, 3, 4); +void livePreview(const Grid2& grid) { + std::lock_guard lock(previewMutex); + + currentPreviewFrame = grid.getGridAsFrame(frame::colormap::RGBA); + + glGenTextures(1, &textu); + glBindTexture(GL_TEXTURE_2D, textu); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + + glBindTexture(GL_TEXTURE_2D, textu); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, currentPreviewFrame.getWidth(), currentPreviewFrame.getHeight(), + 0, GL_RGBA, GL_UNSIGNED_BYTE, currentPreviewFrame.getData().data()); + + updatePreview = true; } std::vector> pickSeeds(Grid2 grid, AnimationConfig config) { @@ -201,10 +220,16 @@ bool exportavi(std::vector frames, AnimationConfig config) { return success; } -void mainLogic(const AnimationConfig& config, Grid2& grid) { +void mainLogic(const AnimationConfig& config, Shared& state) { isGenerating = true; try { - grid = setup(config); + Grid2 grid = setup(config); + { + std:: lock_guard lock(state.mutex); + state.grid = grid; + state.hasNewFrame = true; + state.currentFrame = 0; + } Preview(grid); std::vector> seeds = pickSeeds(grid, config); std::vector frames; @@ -218,16 +243,21 @@ void mainLogic(const AnimationConfig& config, Grid2& grid) { expandPixel(grid,config,seeds); + std::lock_guard lock(state.mutex); + state.grid = grid; + state.hasNewFrame = true; + state.currentFrame = i; + // Print compression info for this frame if (i % 10 == 0 ) { frame bgrframe; std::cout << "Processing frame " << i + 1 << "/" << config.totalFrames << std::endl; bgrframe = grid.getGridAsFrame(frame::colormap::BGR); - bgrframe.printCompressionStats(); frames.push_back(bgrframe); //bgrframe.decompress(); //BMPWriter::saveBMP(std::format("output/grayscalesource.{}.bmp", i), bgrframe); bgrframe.compressFrameLZ78(); + bgrframe.printCompressionStats(); } } exportavi(frames,config); @@ -296,7 +326,7 @@ int main() { //ImGui::SetNextWindowSize(ImVec2(1110,667)); //auto beg = ImGui::Begin("Gradient thing", &window); //if (beg) { - std::cout << "stuff breaks at 223" << std::endl; + // std::cout << "stuff breaks at 223" << std::endl; bool application_not_closed = true; //float main_scale = ImGui_ImplGlfw_GetContentScaleForMonitor(glfwGetPrimaryMonitor()); GLFWwindow* window = glfwCreateWindow((int)(1280), (int)(800), "Chromatic gradient generator thing", nullptr, nullptr); @@ -306,7 +336,7 @@ int main() { glfwSwapInterval(1); //IMGUI_CHECKVERSION(); //this might be more important than I realize. but cant run with it so currently ignoring. ImGui::CreateContext(); - std::cout << "context created" << std::endl; + // std::cout << "context created" << std::endl; ImGuiIO& io = ImGui::GetIO(); (void)io; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls ImGui::StyleColorsDark(); @@ -322,7 +352,7 @@ int main() { ImGui_ImplOpenGL3_Init(glsl_version); - std::cout << "created glfw window" << std::endl; + // std::cout << "created glfw window" << std::endl; // Our state @@ -336,7 +366,10 @@ int main() { static int i4 = 8; std::future mainlogicthread; + Shared state; Grid2 grid; + AnimationConfig config; + previewText = "Please generate"; while (!glfwWindowShouldClose(window)) { glfwPollEvents(); @@ -354,94 +387,90 @@ int main() { ImGui::SliderInt("framecount", &i3, 10, 5000); ImGui::SliderInt("numSeeds", &i4, 0, 10); - // Disable button while generating if (isGenerating) { ImGui::BeginDisabled(); } if (ImGui::Button("Generate Animation")) { - AnimationConfig config = AnimationConfig(i1, i2, i3, f, i4); - mainlogicthread = std::async(std::launch::async, mainLogic, config, std::ref(grid)); + config = AnimationConfig(i1, i2, i3, f, i4); + mainlogicthread = std::async(std::launch::async, mainLogic, config, std::ref(state)); } if (isGenerating) { ImGui::EndDisabled(); - // Show cancel button and progress indicator ImGui::SameLine(); if (ImGui::Button("Cancel")) { cancelGeneration(); } - // Optional: Show a progress indicator - ImGui::Text("Generating..."); + // Check for new frames from the generation thread + bool hasNewFrame = false; + { + std::lock_guard lock(state.mutex); + if (state.hasNewFrame) { + livePreview(state.grid); + state.hasNewFrame = false; + previewText = "Generating... Frame: " + std::to_string(state.currentFrame); + } + } + + ImGui::Text(previewText.c_str()); + + if (textu != 0) { + ImVec2 imageSize = ImVec2(config.width * 0.3f, config.height * 0.3f); // Scale down for preview + ImVec2 uv_min = ImVec2(0.0f, 0.0f); + ImVec2 uv_max = ImVec2(1.0f, 1.0f); + ImGui::Image((void*)(intptr_t)textu, imageSize, uv_min, uv_max); + } else { + ImGui::Text("Generating preview..."); + } + + } else { + ImGui::Text("No preview available"); + ImGui::Text("Start generation to see live preview"); } - //ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); // is this broken or is it just cause I have no refresh buffer in this loop? + //std::cout << "sleeping" << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + //std::cout << "ending" << std::endl; ImGui::End(); } - // ImGui::NewFrame(); - // { - // ImGui::Begin("Live Preview"); - - // if (previewTexture != 0) { - // // Get available space - // ImVec2 availableSize = ImGui::GetContentRegionAvail(); - - // // Maintain aspect ratio (assuming square or config width/height) - // float aspectRatio = static_cast(currentPreviewFrame.getWidth()) / - // static_cast(currentPreviewFrame.getHeight()); - - // ImVec2 imageSize; - // if (availableSize.x / aspectRatio <= availableSize.y) { - // imageSize.x = availableSize.x; - // imageSize.y = availableSize.x / aspectRatio; - // } else { - // imageSize.y = availableSize.y; - // imageSize.x = availableSize.y * aspectRatio; - // } - - // ImGui::Image((ImTextureID)(intptr_t)previewTexture, imageSize); - - // ImGui::Text("Frame: %dx%d", currentPreviewFrame.getWidth(), currentPreviewFrame.getHeight()); - // if (isGenerating) { - // ImGui::TextColored(ImVec4(0, 1, 0, 1), "Generating..."); - // } else { - // ImGui::Text("Ready"); - // } - // } else { - // ImGui::Text("No preview available"); - // ImGui::Text("Start generation to see live preview"); - // } - - // ImGui::End(); - // } - - + + // std::cout << "ending frame" << std::endl; ImGui::Render(); int display_w, display_h; glfwGetFramebufferSize(window, &display_w, &display_h); glViewport(0, 0, display_w, display_h); glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); glClear(GL_COLOR_BUFFER_BIT); + + // std::cout << "rendering" << std::endl; ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); glfwSwapBuffers(window); //mainlogicthread.join(); + + // std::cout << "swapping buffers" << std::endl; } cancelGeneration(); + + // std::cout << "shutting down" << std::endl; ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); + // std::cout << "destroying" << std::endl; glfwDestroyWindow(window); - if (previewTexture != 0) { - glDeleteTextures(1, &previewTexture); - previewTexture = 0; + if (textu != 0) { + glDeleteTextures(1, &textu); + textu = 0; } glfwTerminate(); FunctionTimer::printStats(FunctionTimer::Mode::ENHANCED); + + // std::cout << "printing" << std::endl; return 0; } //I need this: https://raais.github.io/ImStudio/ diff --git a/tests/stb_image.cpp b/tests/stb_image.cpp new file mode 100644 index 0000000..badb3ef --- /dev/null +++ b/tests/stb_image.cpp @@ -0,0 +1,2 @@ +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" \ No newline at end of file diff --git a/util/grid/grid2.hpp b/util/grid/grid2.hpp index 6d49a83..de8750b 100644 --- a/util/grid/grid2.hpp +++ b/util/grid/grid2.hpp @@ -67,7 +67,7 @@ public: return Positions.bucket_count(); } - bool empty() { + bool empty() const { return Positions.empty(); } @@ -194,7 +194,9 @@ protected: //TODO: spatial map SpatialGrid spatialGrid; float spatialCellSize = 2.0f; + public: + bool usable = false; //get position from id Vec2 getPositionID(size_t id) const { Vec2 it = Positions.at(id); @@ -459,6 +461,7 @@ public: shrinkIfNeeded(); updateNeighborMap(); + usable = true; return getAllIDs(); } @@ -620,6 +623,78 @@ public: } } + + + void getGridRegionAsRGBA(const Vec2& minCorner, const Vec2& maxCorner, + int& width, int& height, std::vector& rgbData) const { + TIME_FUNCTION; + // std::cout << "excessdebug g2.483" << std::endl; + // Calculate dimensions + width = static_cast(maxCorner.x - minCorner.x); + height = static_cast(maxCorner.y - minCorner.y); + + if (width <= 0 || height <= 0) { + width = height = 0; + rgbData.clear(); + rgbData.shrink_to_fit(); + return; + } + // std::cout << "excessdebug g2.494" << std::endl; + + // Initialize RGB data (3 bytes per pixel: R, G, B) + std::vector rgbaBuffer(width * height, Vec4(0.0f, 0.0f, 0.0f, 0.0f)); + + // For each position in the grid, find the corresponding pixel + for (const auto& [id, pos] : Positions) { + // std::cout << "excessdebug g2.501." << id << std::endl; + size_t size = Sizes.at(id); + + // Calculate pixel coordinates + int pixelXm = static_cast(pos.x - size/2 - minCorner.x); + int pixelXM = static_cast(pos.x + size/2 - minCorner.x); + int pixelYm = static_cast(pos.y - size/2 - minCorner.y); + int pixelYM = static_cast(pos.y + size/2 - minCorner.y); + + pixelXm = std::max(0, pixelXm); + pixelXM = std::min(width - 1, pixelXM); + pixelYm = std::max(0, pixelYm); + pixelYM = std::min(height - 1, pixelYM); + // std::cout << "excessdebug g2.514." << id << std::endl; + + // Ensure within bounds + if (pixelXM >= minCorner.x && pixelXm < width && pixelYM >= minCorner.y && pixelYm < height) { + // std::cout << "excessdebug g2.518." << id << " - (" << pixelXm << "," << pixelYM << ")" << std::endl; + const Vec4& color = Colors.at(id); + float srcAlpha = color.a; + float invSrcAlpha = 1.0f - srcAlpha; + for (int py = pixelYm; py <= pixelYM; ++py){ + for (int px = pixelXm; px <= pixelXM; ++px){ + // std::cout << "excessdebug g2.524." << id << " - (" << py << "," << px << ")" << std::endl; + int index = (py * width + px); + Vec4 dest = rgbaBuffer[index]; + + dest.r = color.r * srcAlpha + dest.r; // * invSrcAlpha; + dest.g = color.g * srcAlpha + dest.g; // * invSrcAlpha; + dest.b = color.b * srcAlpha + dest.b; // * invSrcAlpha; + dest.a = srcAlpha + dest.a; // * invSrcAlpha; + rgbaBuffer[index] = dest; + } + } + } + } + rgbData.resize(rgbaBuffer.size() * 4); + for (int i = 0; i < rgbaBuffer.size(); ++i) { + const Vec4& color = rgbaBuffer[i]; + int bgrIndex = i * 4; + + // Convert from [0,1] to [0,255] and store as RGB + rgbData[bgrIndex + 0] = static_cast(color.r * 255); + rgbData[bgrIndex + 1] = static_cast(color.g * 255); + rgbData[bgrIndex + 2] = static_cast(color.b * 255); + rgbData[bgrIndex + 2] = static_cast(color.a * 255); + + } + } //get full as rgb/bgr void getGridAsRGB(int& width, int& height, std::vector& rgbData) { @@ -645,6 +720,17 @@ public: resultFrame.setData(rgbData); return resultFrame; } + + frame getGridRegionAsFrameRGBA(const Vec2& minCorner, const Vec2& maxCorner) const { + TIME_FUNCTION; + int width, height; + std::vector rgbData; + getGridRegionAsRGBA(minCorner, maxCorner, width, height, rgbData); + + frame resultFrame(width, height, frame::colormap::RGB); + resultFrame.setData(rgbData); + return resultFrame; + } // Get region as frame (BGR format) frame getGridRegionAsFrameBGR(const Vec2& minCorner, const Vec2& maxCorner) const { @@ -659,7 +745,7 @@ public: } // Get entire grid as frame with specified format - frame getGridAsFrame(frame::colormap format = frame::colormap::RGB) { + frame getGridAsFrame(frame::colormap format = frame::colormap::RGB) const { TIME_FUNCTION; Vec2 minCorner, maxCorner; getBoundingBox(minCorner, maxCorner); @@ -669,6 +755,9 @@ public: case frame::colormap::RGB: Frame = std::move(getGridRegionAsFrameRGB(minCorner, maxCorner)); break; + case frame::colormap::RGBA: + Frame = std::move(getGridRegionAsFrameRGBA(minCorner, maxCorner)); + break; case frame::colormap::BGR: Frame = std::move(getGridRegionAsFrameBGR(minCorner, maxCorner)); break; @@ -708,7 +797,7 @@ public: } //get bounding box - void getBoundingBox(Vec2& minCorner, Vec2& maxCorner) { + void getBoundingBox(Vec2& minCorner, Vec2& maxCorner) const { TIME_FUNCTION; if (Positions.empty()) { minCorner = Vec2(0, 0);