diff --git a/.gitignore b/.gitignore index dcb7dd9..0f3c454 100644 --- a/.gitignore +++ b/.gitignore @@ -31,5 +31,6 @@ *.out *.app +imgui.ini tview diff --git a/Makefile b/Makefile index fc45bd6..b53f171 100644 --- a/Makefile +++ b/Makefile @@ -24,8 +24,8 @@ UNAME_S := $(shell uname -s) LINUX_GL_LIBS = -lGL CXXFLAGS = -std=c++20 -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends -CXXFLAGS += -DIMGUI_DEFINE_MATH_OPERATORS -Ofast -LIBS = +CXXFLAGS += -DIMGUI_DEFINE_MATH_OPERATORS -O3 -DGL_SILENCE_DEPRECATION +LIBS = -lexiv2 ##--------------------------------------------------------------------- ## OPENGL ES @@ -44,9 +44,9 @@ LIBS = ifeq ($(UNAME_S), Linux) #LINUX ECHO_MESSAGE = "Linux" - LIBS += $(LINUX_GL_LIBS) -ldl `sdl2-config --libs` + LIBS += $(LINUX_GL_LIBS) -ldl -L/usr/lib -lSDL2 - CXXFLAGS += `sdl2-config --cflags` + CXXFLAGS += -I/usr/include/SDL2 -D_REENTRANT CFLAGS = $(CXXFLAGS) endif diff --git a/imgui.ini b/imgui.ini deleted file mode 100644 index 5b3f7e9..0000000 --- a/imgui.ini +++ /dev/null @@ -1,24 +0,0 @@ -[Window][Debug##Default] -Pos=341,6 -Size=400,400 -Collapsed=0 - -[Window][viewport_container] -Size=1101,598 -Collapsed=0 - -[Window][Editor] -Pos=33,52 -Size=1118,634 -Collapsed=0 - -[Window][Main] -Pos=0,0 -Size=1654,1186 -Collapsed=0 - -[Window][Help] -Pos=136,255 -Size=177,114 -Collapsed=0 - diff --git a/lib/backends/tex_inspect_opengl.cpp b/lib/backends/tex_inspect_opengl.cpp index 29570fe..7cfae92 100644 --- a/lib/backends/tex_inspect_opengl.cpp +++ b/lib/backends/tex_inspect_opengl.cpp @@ -77,11 +77,16 @@ using namespace gl; #elif defined(IMGUI_IMPL_OPENGL_LOADER_EPOXY) #include #else +#if defined(__APPLE__) +#include +#include +#else #include #include #include #endif #endif +#endif // Desktop GL 3.2+ has glDrawElementsBaseVertex() which GL ES and WebGL don't have. #if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_2) diff --git a/lib/uuid.cpp b/lib/uuid.cpp deleted file mode 100644 index 248836e..0000000 --- a/lib/uuid.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "uuid.h" - -namespace uuid { -static std::random_device rd; -static std::mt19937 gen(rd()); -static std::uniform_int_distribution<> dis(0, 15); -static std::uniform_int_distribution<> dis2(8, 11); - -std::string generate_uuid_v4() { - std::stringstream ss; - int i; - ss << std::hex; - for (i = 0; i < 8; i++) { - ss << dis(gen); - } - ss << "-"; - for (i = 0; i < 4; i++) { - ss << dis(gen); - } - ss << "-4"; - for (i = 0; i < 3; i++) { - ss << dis(gen); - } - ss << "-"; - ss << dis2(gen); - for (i = 0; i < 3; i++) { - ss << dis(gen); - } - ss << "-"; - for (i = 0; i < 12; i++) { - ss << dis(gen); - }; - return ss.str(); -} -} // namespace uuid diff --git a/lib/uuid.h b/lib/uuid.h deleted file mode 100644 index 0f938d6..0000000 --- a/lib/uuid.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef UUID_H -#define UUID_H - -#include -#include -#include - -namespace uuid { -std::string generate_uuid_v4(); -} - -#endif // UUID_H diff --git a/main.cpp b/main.cpp index e4b06d2..eb1c881 100644 --- a/main.cpp +++ b/main.cpp @@ -1,14 +1,3 @@ -// Dear ImGui: standalone example application for SDL2 + OpenGL -// (SDL is a cross-platform general purpose library for handling windows, -// inputs, OpenGL/Vulkan/Metal graphics context creation, etc.) - -// Learn about Dear ImGui: -// - FAQ https://dearimgui.com/faq -// - Getting Started https://dearimgui.com/getting-started -// - Documentation https://dearimgui.com/docs (same as your local docs/ -// folder). -// - Introduction, links and more at the top of imgui.cpp - #include #include #include @@ -17,6 +6,8 @@ #include "lib/backends/imgui_impl_sdl2.h" #include "lib/imgui.h" #include "lib/imgui_internal.h" +#include +#include #include #include #include @@ -44,29 +35,141 @@ #include "lib/backends/tex_inspect_opengl.h" #include "lib/argparse.hpp" +#include + struct Args : public argparse::Args { std::string &fpath = arg("path to the image"); }; + +struct EXIFData { + std::string CameraMake; + std::string CameraModel; + std::string LensModel; + std::string ImageOrientation; + std::string ImageDimensionX; + std::string ImageDimensiony; + std::string ISO; + std::string ShutterSpeed; + std::string FNumber; + std::string FocalLength; + std::string ExposureBias; + std::string MeteringMode; + std::string Flash; + std::string Time; + std::string TimeTaken; + std::string TimeTakenOffset; + std::string GPSLat; + std::string GPSLatref; + std::string GPSLon; + std::string GPSLonref; +}; + struct Texture { ImTextureID texture; ImVec2 size; + int channels; + EXIFData exif; }; -Texture LoadTexture(const char * path) -{ - const int channelCount = 4; - int imageFileChannelCount; - int width, height; - uint8_t *image = (uint8_t *)stbi_load(path, &width, &height, &imageFileChannelCount, channelCount); - if (image == NULL) - { - fprintf(stderr, "%s\nFailed to open %s\n", stbi_failure_reason(), path); +static uint8_t* image = nullptr; - return {nullptr,{0,0}}; + +EXIFData printExifData(const std::string& imagePath) { + EXIFData d; + d.CameraMake = "NULL"; + try { + // Load the image + Exiv2::Image::UniquePtr img = Exiv2::ImageFactory::open(imagePath); + if (img.get() == 0) { + std::cerr << "Error: Could not open image file " << imagePath << std::endl; + return d; + } + img->readMetadata(); + + // Get the Exif data + Exiv2::ExifData &exifData = img->exifData(); + if (exifData.empty()) { + std::cerr << "No EXIF data found in file " << imagePath << std::endl; + return d; + } + + // Helper function to print EXIF data if available + auto printExifValue = [&exifData](const char* key, std::string *d) { + Exiv2::ExifKey exifKey(key); + Exiv2::ExifData::const_iterator pos = exifData.findKey(exifKey); + if (pos != exifData.end()) { + *d = pos->value().toString(); + } + }; + + // Print common EXIF data + printExifValue("Exif.Image.Make", &d.CameraMake); + printExifValue("Exif.Image.Model", &d.CameraModel); + printExifValue("Exif.Photo.LensModel", &d.LensModel); + + printExifValue("Exif.Image.Orientation", &d.ImageOrientation); + // + printExifValue("Exif.Photo.ExposureTime", &d.ShutterSpeed); + printExifValue("Exif.Photo.FNumber", &d.FNumber); + printExifValue("Exif.Photo.ISOSpeedRatings", &d.ISO); + printExifValue("Exif.Photo.FocalLength", &d.FocalLength); + printExifValue("Exif.Photo.ExposureBiasValue", &d.ExposureBias); + printExifValue("Exif.Photo.MeteringMode", &d.MeteringMode); + printExifValue("Exif.Photo.Flash", &d.Flash); + + printExifValue("Exif.Image.DateTime", &d.Time); + printExifValue("Exif.Photo.DateTimeOriginal", &d.TimeTaken); + printExifValue("Exif.Photo.OffsetTime", &d.TimeTakenOffset); + printExifValue("Exif.Photo.PixelXDimension", &d.ImageDimensionX); + printExifValue("Exif.Photo.PixelYDimension", &d.ImageDimensiony); + + printExifValue("Exif.GPSInfo.GPSLatitudeRef", &d.GPSLatref); + printExifValue("Exif.GPSInfo.GPSLatitude", &d.GPSLat); + printExifValue("Exif.GPSInfo.GPSLongitudeRef", &d.GPSLonref); + printExifValue("Exif.GPSInfo.GPSLongitude", &d.GPSLon); + + +/* +Exif.Image.Make 0x010f Ascii 5 SONY +Exif.Image.Model 0x0110 Ascii 10 ILCE-7RM5 + +Exif.Image.Orientation 0x0112 Short 1 8 +Exif.Photo.PixelXDimension 0xa002 Long 1 9504 +Exif.Photo.PixelYDimension 0xa003 Long 1 6336 + +Exif.Photo.ExposureTime 0x829a Rational 1 1/400 +Exif.Photo.FNumber 0x829d Rational 1 56/10 +Exif.Photo.ISOSpeedRatings 0x8827 Short 1 100 +Exif.Photo.FocalLength 0x920a Rational 1 400/10 +Exif.Photo.ExposureBiasValue 0x9204 SRational 1 -10/10 +Exif.Photo.MeteringMode 0x9207 Short 1 5 +Exif.Photo.Flash 0x9209 Short 1 16 + +Exif.Image.DateTime 0x0132 Ascii 20 2024:06:07 08:58:07 +Exif.Photo.DateTimeOriginal 0x9003 Ascii 20 2024:06:07 08:58:07 +Exif.Photo.OffsetTime 0x9010 Ascii 7 -05:00 + +Exif.GPSInfo.GPSLatitudeRef 0x0001 Ascii 2 N +Exif.GPSInfo.GPSLatitude 0x0002 Rational 3 40/1 43/1 32231/1000 +Exif.GPSInfo.GPSLongitudeRef 0x0003 Ascii 2 W +Exif.GPSInfo.GPSLongitude 0x0004 Rational 3 73/1 59/1 16688/1000 +*/ + + } catch (Exiv2::Error& e) { + std::cerr << "Error: " << e.what() << std::endl; } + return d; +} + + + +Texture ReloadTexture(Texture tin, int width, int height) { + GLuint tid = (GLuint)(uintptr_t)tin.texture; + glDeleteTextures((GLsizei)1, &tid); + GLenum dataFormat = GL_RGBA; GLuint textureHandle; glGenTextures(1, &textureHandle); @@ -78,16 +181,76 @@ Texture LoadTexture(const char * path) Texture t; t.texture = (void*)(uintptr_t)(textureHandle); t.size = ImVec2((float)width,(float)height); + t.exif = tin.exif; + t.channels = tin.channels; + + return t; +} + +void RotateImage(Texture t) { + int height = t.size.y; + int width = t.size.x; + int channels = t.channels; + + const unsigned int sizeBuffer = width * height * channels; + unsigned char *tempBuffer = new unsigned char[sizeBuffer]; + + int nidx = 0; + for (int x = 0; x < width; x++) + { + for (int y = height - 1; y >= 0; y--) + { + int idx = (x + y * width) * channels; + for (int i = 0; i < channels; i++) + tempBuffer[nidx + i] = image[idx + i]; + + nidx = nidx + 4; + } + } + + memcpy(image, tempBuffer, sizeBuffer); + delete[] tempBuffer; +} + +Texture LoadTexture(const char * path) +{ + const int channelCount = 4; + int imageFileChannelCount; + int width, height; + image = (uint8_t *)stbi_load(path, &width, &height, &imageFileChannelCount, channelCount); + + if (image == NULL) + { + fprintf(stderr, "%s\nFailed to open %s\n", stbi_failure_reason(), path); + + return {nullptr,{0,0}}; + } + + auto exif = printExifData(path); + + + + GLenum dataFormat = GL_RGBA; + GLuint textureHandle; + glGenTextures(1, &textureHandle); + glBindTexture(GL_TEXTURE_2D, textureHandle); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, dataFormat, GL_UNSIGNED_BYTE, image); + + Texture t; + t.texture = (void*)(uintptr_t)(textureHandle); + t.size = ImVec2((float)width,(float)height); + t.channels = channelCount; + t.exif = exif; + - stbi_image_free(image); return t; } -static bool init = true; -static bool showHelp = false; -static int mode = 0; -const int maxAnnotatedTexels = 10000; + +const int MAX_ANNOATED_TEXELS = 10000; // Main code int main(int argc, char* argv[]) { @@ -99,8 +262,11 @@ int main(int argc, char* argv[]) { } bool TOOLTIP_ENABLED = false; - bool GRID_ENABLED = true; + bool GRID_ENABLED = false; bool AA_ENABLED = true; + bool SHOW_HELP = false; + bool SHOW_EXIF = false; + int MODE = 0; // Decide GL+GLSL versions #if defined(IMGUI_IMPL_OPENGL_ES2) @@ -154,7 +320,6 @@ int main(int argc, char* argv[]) { // Setup Dear ImGui context IMGUI_CHECKVERSION(); - ImGui::CreateContext(); ImGuiIO &io = ImGui::GetIO(); (void)io; @@ -163,34 +328,27 @@ int main(int argc, char* argv[]) { ImGuiTexInspect::ImplOpenGL3_Init(); // Or DirectX 11 equivalent (check your chosen backend header file) ImGuiTexInspect::Init(); ImGuiTexInspect::CreateContext(); - ImGui::StyleColorsDark(); - ImGuiStyle &style = ImGui::GetStyle(); - ImGui_ImplSDL2_InitForOpenGL(window, gl_context); ImGui_ImplOpenGL3_Init(glsl_version); ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); - Texture t; + auto flags = ImGuiTexInspect::InspectorFlags_FillVertical | ImGuiTexInspect::InspectorFlags_FillHorizontal; - if (init) { - init = false; - try { - auto args = argparse::parse(argc, argv, true); - t = LoadTexture(args.fpath.c_str()); - } catch (const std::runtime_error &e) { - std::cerr << "failed to parse arguments: " << e.what() << std::endl; - return -1; - } + try { + auto args = argparse::parse(argc, argv, true); + t = LoadTexture(args.fpath.c_str()); + } catch (const std::runtime_error &e) { + std::cerr << "failed to parse arguments: " << e.what() << std::endl; + return -1; } // Main loop bool done = false; while (!done) { - auto flags = ImGuiTexInspect::InspectorFlags_FillVertical | ImGuiTexInspect::InspectorFlags_FillHorizontal; // Poll and handle events (inputs, window resize, etc.) SDL_Event event; @@ -217,14 +375,21 @@ int main(int argc, char* argv[]) { AA_ENABLED = !AA_ENABLED; break; case SDL_SCANCODE_H: - showHelp = !showHelp; + SHOW_HELP = !SHOW_HELP; break; case SDL_SCANCODE_D: - mode = (mode + 1) % 5; + MODE = (MODE + 1) % 5; + break; + case SDL_SCANCODE_R: + RotateImage(t); + t = ReloadTexture(t, t.size.y, t.size.x); break; case SDL_SCANCODE_Q: done = true; break; + case SDL_SCANCODE_E: + SHOW_EXIF = !SHOW_EXIF; + break; default: break; } @@ -252,7 +417,7 @@ int main(int argc, char* argv[]) { ImGui::SetNextWindowPos(viewport->Pos); ImGui::SetNextWindowSize(viewport->Size); ImGui::Begin("Main", NULL, - ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize); + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBringToFrontOnFocus); auto wSize = ImGui::GetContentRegionAvail(); if (t.texture != 0) { @@ -286,18 +451,18 @@ int main(int argc, char* argv[]) { CurrentInspector_SetFlags(ImGuiTexInspect::InspectorFlags_NoForceFilterNearest); } - switch(mode) { + switch(MODE) { case 1: - ImGuiTexInspect::DrawAnnotations(ImGuiTexInspect::Arrow().UsePreset(ImGuiTexInspect::Arrow::NormalMap), maxAnnotatedTexels); + ImGuiTexInspect::DrawAnnotations(ImGuiTexInspect::Arrow().UsePreset(ImGuiTexInspect::Arrow::NormalMap), MAX_ANNOATED_TEXELS); break; case 2: - ImGuiTexInspect::DrawAnnotations(ImGuiTexInspect::ValueText(ImGuiTexInspect::ValueText::Format::HexString), maxAnnotatedTexels); + ImGuiTexInspect::DrawAnnotations(ImGuiTexInspect::ValueText(ImGuiTexInspect::ValueText::Format::HexString), MAX_ANNOATED_TEXELS); break; case 3: - ImGuiTexInspect::DrawAnnotations(ImGuiTexInspect::ValueText(ImGuiTexInspect::ValueText::Format::BytesDec), maxAnnotatedTexels); + ImGuiTexInspect::DrawAnnotations(ImGuiTexInspect::ValueText(ImGuiTexInspect::ValueText::Format::BytesDec), MAX_ANNOATED_TEXELS); break; case 4: - ImGuiTexInspect::DrawAnnotations(ImGuiTexInspect::ValueText(ImGuiTexInspect::ValueText::Format::Floats), maxAnnotatedTexels); + ImGuiTexInspect::DrawAnnotations(ImGuiTexInspect::ValueText(ImGuiTexInspect::ValueText::Format::Floats), MAX_ANNOATED_TEXELS); break; default: break; @@ -310,23 +475,154 @@ int main(int argc, char* argv[]) { ImGui::PopStyleVar(); ImGui::PopStyleVar(); - if (showHelp){ + if (SHOW_HELP){ ImGui::OpenPopup("HelpPopup"); - showHelp = !showHelp; + SHOW_HELP = !SHOW_HELP; } + if (ImGui::BeginPopup("HelpPopup")) { ImGui::Text("tview Help"); ImGui::Separator(); + ImGui::Text("r - rotate 90 deg clockwise"); ImGui::Text("g - toggle grid"); ImGui::Text("a - toggle filtering"); ImGui::Text("t - toggle tooltip"); ImGui::Text("d - cycle pixel detail mode"); - ImGui::Text("q - quit"); + ImGui::Separator(); + ImGui::Text("modes:"); + ImGui::Text("\tOff"); + ImGui::Text("\tGradient Arrow"); + ImGui::Text("\tHex Code"); + ImGui::Text("\tRGB Values"); + ImGui::Text("\tFloat Values"); + ImGui::Separator(); ImGui::Text("h - show help popup"); + ImGui::Text("e - toggle EXIF info"); + ImGui::Separator(); + ImGui::Text("q - quit"); ImGui::Separator(); ImGui::Text("click anywhere to continue"); ImGui::EndPopup(); } + if (SHOW_EXIF && t.exif.CameraMake != "NULL") { + ImGuiWindowClass topmost; + topmost.ClassId = ImHashStr("TopMost"); + topmost.ViewportFlagsOverrideSet = ImGuiViewportFlags_TopMost; + ImGui::SetNextWindowClass(&topmost); + ImGui::Begin("EXIF", NULL, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing); + if(ImGui::BeginTable("Hardware", 2)) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("Make"); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%s", t.exif.CameraMake.c_str()); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("Model"); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%s", t.exif.CameraModel.c_str()); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("Lens"); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%s", t.exif.LensModel.c_str()); + + ImGui::EndTable(); + } + + ImGui::Separator(); + + if(ImGui::BeginTable("Photo", 2)) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("Shutter Speed"); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%s", t.exif.ShutterSpeed.c_str()); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("F-Stop"); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%s", t.exif.FNumber.c_str()); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("ISO"); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%s", t.exif.ISO.c_str()); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("Focal Length"); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%s", t.exif.FocalLength.c_str()); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("Exposure Comp"); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%s", t.exif.ExposureBias.c_str()); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("Metering Mode"); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%s", t.exif.MeteringMode.c_str()); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("Flash"); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%s", t.exif.Flash.c_str()); + + ImGui::EndTable(); + } + + ImGui::Separator(); + + if(ImGui::BeginTable("Meta", 2)) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("Date"); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%s %s", t.exif.TimeTaken.c_str(), t.exif.TimeTakenOffset.c_str()); + + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("Dimensions"); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%sx%s", t.exif.ImageDimensionX.c_str(), t.exif.ImageDimensiony.c_str()); + + ImGui::EndTable(); + } + + ImGui::Separator(); + + if(ImGui::BeginTable("Location", 2)) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("Latitude"); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%s %s", t.exif.GPSLat.c_str(), t.exif.GPSLatref.c_str()); + + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("Longitude"); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%s %s", t.exif.GPSLon.c_str(), t.exif.GPSLonref.c_str()); + + ImGui::EndTable(); + } + + ImGui::Separator(); + ImGui::Text("Press e to hide"); + + ImGui::End(); + } } // Rendering @@ -339,6 +635,10 @@ int main(int argc, char* argv[]) { SDL_GL_SwapWindow(window); } + if (image != nullptr) { + stbi_image_free(image); + } + // Cleanup ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplSDL2_Shutdown();