| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182 |
- // ImGuiTexInspect, a texture inspector widget for dear imgui
- //-------------------------------------------------------------------------
- // [SECTION] INCLUDES
- //-------------------------------------------------------------------------
- #define IMGUI_DEFINE_MATH_OPERATORS
- #include "imgui_tex_inspect.h"
- #include "imgui_tex_inspect_internal.h"
- #include "imgui.h"
- #include "imgui_internal.h"
- #if defined(_MSC_VER)
- #pragma warning(disable : 4996) // 'sprintf' considered unsafe
- #endif
- namespace ImGuiTexInspect
- {
- //-------------------------------------------------------------------------
- // [SECTION] FORWARD DECLARATIONS
- //-------------------------------------------------------------------------
- void UpdateShaderOptions(Inspector *inspector);
- void InspectorDrawCallback(const ImDrawList *parent_list, const ImDrawCmd *cmd);
- bool GetVisibleTexelRegionAndGetData(Inspector *inspector, ImVec2 &texelTL, ImVec2 &texelBR);
- //-------------------------------------------------------------------------
- // [SECTION] GLOBAL STATE
- //-------------------------------------------------------------------------
- // Input mapping structure, default values listed in the comments.
- struct InputMap
- {
- ImGuiMouseButton PanButton; // LMB enables panning when held
- InputMap();
- };
- InputMap::InputMap()
- {
- PanButton = ImGuiMouseButton_Left;
- }
- // Settings configured via SetNextPanelOptions etc.
- struct NextPanelSettings
- {
- InspectorFlags ToSet = 0;
- InspectorFlags ToClear = 0;
- };
- // Main context / configuration structure for imgui_tex_inspect
- struct Context
- {
- InputMap Input; // Input mapping config
- ImGuiStorage Inspectors; // All the inspectors we've seen
- Inspector * CurrentInspector; // Inspector currently being processed
- NextPanelSettings NextPanelOptions; // Options configured for next inspector panel
- float ZoomRate = 1.3f; // How fast mouse wheel affects zoom
- float DefaultPanelHeight = 600; // Height of panel in pixels
- float DefaultInitialPanelWidth = 600; // Only applies when window first appears
- int MaxAnnotations = 1000; // Limit number of texel annotations for performance
- };
- Context *GContext = nullptr;
- //-------------------------------------------------------------------------
- // [SECTION] USER FUNCTIONS
- //-------------------------------------------------------------------------
- void Init()
- {
- // Nothing to do here. But there might be in a later version. So client code should still call it!
- }
- void Shutdown()
- {
- // Nothing to do here. But there might be in a later version. So client code should still call it!
- }
- Context *CreateContext()
- {
- GContext = IM_NEW(Context);
- SetCurrentContext(GContext);
- return GContext;
- }
- void DestroyContext(Context *ctx)
- {
- if (ctx == NULL)
- {
- ctx = GContext;
- }
- if (ctx == GContext)
- {
- GContext = NULL;
- }
- for (ImGuiStorage::ImGuiStoragePair &pair : ctx->Inspectors.Data)
- {
- Inspector *inspector = (Inspector *)pair.val_p;
- if (inspector)
- {
- IM_DELETE(inspector);
- }
- }
- IM_DELETE(ctx);
- }
- void SetCurrentContext(Context *context)
- {
- ImGuiTexInspect::GContext = context;
- }
- void SetNextPanelFlags(InspectorFlags setFlags, InspectorFlags clearFlags)
- {
- SetFlag(GContext->NextPanelOptions.ToSet, setFlags);
- SetFlag(GContext->NextPanelOptions.ToClear, clearFlags);
- }
- bool BeginInspectorPanel(const char *title, ImTextureID texture, ImVec2 textureSize, InspectorFlags flags,
- SizeIncludingBorder sizeIncludingBorder)
- {
- const int borderWidth = 1;
- // Unpack size param. It's in the SizeIncludingBorder structure just to make sure users know what they're requesting
- ImVec2 size = sizeIncludingBorder.Size;
- ImGuiWindow *window = ImGui::GetCurrentWindow();
- Context *ctx = GContext;
- const ImGuiID ID = window->GetID(title);
- const ImGuiIO &IO = ImGui::GetIO();
- // Create or find inspector
- bool justCreated = GetByKey(ctx, ID) == NULL;
- ctx->CurrentInspector = GetOrAddByKey(ctx, ID);
- Inspector *inspector = ctx->CurrentInspector;
- justCreated |= !inspector->Initialized;
- // Cache the basics
- inspector->ID = ID;
- inspector->Texture = texture;
- inspector->TextureSize = textureSize;
- inspector->Initialized = true;
- // Handle incoming flags. We keep special track of the
- // newly set flags because somethings only take effect
- // the first time the flag is set.
- InspectorFlags newlySetFlags = ctx->NextPanelOptions.ToSet;
- if (justCreated)
- {
- SetFlag(newlySetFlags, flags);
- inspector->MaxAnnotatedTexels = ctx->MaxAnnotations;
- }
- SetFlag(inspector->Flags, newlySetFlags);
- ClearFlag(inspector->Flags, ctx->NextPanelOptions.ToClear);
- ClearFlag(newlySetFlags, ctx->NextPanelOptions.ToClear);
- ctx->NextPanelOptions = NextPanelSettings();
- // Calculate panel size
- ImVec2 contentRegionAvail = ImGui::GetContentRegionAvail();
- ImVec2 panelSize;
- // A size value of zero indicates we should use defaults
- if (justCreated)
- {
- panelSize = {size.x == 0 ? ImMax(ctx->DefaultInitialPanelWidth, contentRegionAvail.x) : size.x,
- size.y == 0 ? ctx->DefaultPanelHeight : size.y};
- }
- else
- {
- panelSize = {size.x == 0 ? contentRegionAvail.x : size.x, size.y == 0 ? ctx->DefaultPanelHeight : size.y};
- }
- inspector->PanelSize = panelSize;
- ImVec2 availablePanelSize = panelSize - ImVec2(borderWidth, borderWidth) * 2;
- {
- // Possibly update scale
- float newScale = -1;
- if (HasFlag(newlySetFlags, InspectorFlags_FillVertical))
- {
- newScale = availablePanelSize.y / textureSize.y;
- }
- else if (HasFlag(newlySetFlags, InspectorFlags_FillHorizontal))
- {
- newScale = availablePanelSize.x / textureSize.x;
- }
- else if (justCreated)
- {
- newScale = 1;
- }
- if (newScale != -1)
- {
- inspector->Scale = ImVec2(newScale, newScale);
- SetPanPos(inspector, ImVec2(0.5f, 0.5f));
- }
- }
- RoundPanPos(inspector);
- ImVec2 textureSizePixels = inspector->Scale * textureSize; // Size whole texture would appear on screen
- ImVec2 viewSizeUV = availablePanelSize / textureSizePixels; // Cropped size in terms of UV
- ImVec2 uv0 = inspector->PanPos - viewSizeUV * 0.5;
- ImVec2 uv1 = inspector->PanPos + viewSizeUV * 0.5;
- ImVec2 drawImageOffset{borderWidth, borderWidth};
- ImVec2 viewSize = availablePanelSize;
- if ((inspector->Flags & InspectorFlags_ShowWrap) == 0)
- {
- /* Don't crop the texture to UV [0,1] range. What you see outside this
- * range will depend on API and texture properties */
- if (textureSizePixels.x < availablePanelSize.x)
- {
- // Not big enough to horizontally fill view
- viewSize.x = ImFloor(textureSizePixels.x);
- drawImageOffset.x += ImFloor((availablePanelSize.x - textureSizePixels.x) / 2);
- uv0.x = 0;
- uv1.x = 1;
- viewSizeUV.x = 1;
- inspector->PanPos.x = 0.5f;
- }
- if (textureSizePixels.y < availablePanelSize.y)
- {
- // Not big enough to vertically fill view
- viewSize.y = ImFloor(textureSizePixels.y);
- drawImageOffset.y += ImFloor((availablePanelSize.y - textureSizePixels.y) / 2);
- uv0.y = 0;
- uv1.y = 1;
- viewSizeUV.y = 1;
- inspector->PanPos.y = 0.5;
- }
- }
- if (HasFlag(flags,InspectorFlags_FlipX))
- {
- ImSwap(uv0.x, uv1.x);
- viewSizeUV.x *= -1;
- }
- if (HasFlag(flags,InspectorFlags_FlipY))
- {
- ImSwap(uv0.y, uv1.y);
- viewSizeUV.y *= -1;
- }
- inspector->ViewSize = viewSize;
- inspector->ViewSizeUV = viewSizeUV;
- /* We use mouse scroll to zoom so we don't want scroll to propagate to
- * parent window. For this to happen we must NOT set
- * ImGuiWindowFlags_NoScrollWithMouse. This seems strange but it's the way
- * ImGui works. Also we must ensure the ScrollMax.y is not zero for the
- * child window. */
- if (ImGui::BeginChild(title, panelSize, false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove))
- {
- // See comment above
- ImGui::GetCurrentWindow()->ScrollMax.y = 1.0f;
- // Callback for using our own image shader
- ImGui::GetWindowDrawList()->AddCallback(InspectorDrawCallback, inspector);
- // Keep track of size of area that we draw for borders later
- inspector->PanelTopLeftPixel = ImGui::GetCursorScreenPos();
- ImGui::SetCursorPos(ImGui::GetCursorPos() + drawImageOffset);
- inspector->ViewTopLeftPixel = ImGui::GetCursorScreenPos();
- UpdateShaderOptions(inspector);
- inspector->CachedShaderOptions = inspector->ActiveShaderOptions;
- ImGui::Image(texture, viewSize, uv0, uv1);
- ImGui::GetWindowDrawList()->AddCallback(ImDrawCallback_ResetRenderState, nullptr);
- /* Matrices for going back and forth between texel coordinates in the
- * texture and screen coordinates based on where texture is drawn.
- * Useful for annotations and mouse hover etc. */
- inspector->TexelsToPixels = GetTexelsToPixels(inspector->ViewTopLeftPixel, viewSize, uv0, viewSizeUV, inspector->TextureSize);
- inspector->PixelsToTexels = inspector->TexelsToPixels.Inverse();
- ImVec2 mousePos = ImGui::GetMousePos();
- ImVec2 mousePosTexel = inspector->PixelsToTexels * mousePos;
- ImVec2 mouseUV = mousePosTexel / textureSize;
- mousePosTexel.x = Modulus(mousePosTexel.x, textureSize.x);
- mousePosTexel.y = Modulus(mousePosTexel.y, textureSize.y);
- if (ImGui::IsItemHovered() && (inspector->Flags & ImGuiTexInspect::InspectorFlags_NoTooltip) == 0)
- {
- // Show a tooltip for currently hovered texel
- ImVec2 texelTL;
- ImVec2 texelBR;
- if (GetVisibleTexelRegionAndGetData(inspector, texelTL, texelBR))
- {
- ImVec4 color = GetTexel(&inspector->Buffer, (int)mousePosTexel.x, (int)mousePosTexel.y);
- char buffer[128];
- sprintf(buffer, "UV: (%.5f, %.5f)\nTexel: (%d, %d)", mouseUV.x, mouseUV.y, (int)mousePosTexel.x, (int)mousePosTexel.y);
- ImGui::ColorTooltip(buffer, &color.x, 0);
- }
- }
- bool hovered = ImGui::IsWindowHovered();
- { //DRAGGING
-
- // start drag
- if (!inspector->IsDragging && hovered && IO.MouseClicked[ctx->Input.PanButton])
- {
- inspector->IsDragging = true;
- }
- // carry on dragging
- else if (inspector->IsDragging)
- {
- ImVec2 uvDelta = IO.MouseDelta * viewSizeUV / viewSize;
- inspector->PanPos -= uvDelta;
- RoundPanPos(inspector);
- }
- // end drag
- if (inspector->IsDragging && (IO.MouseReleased[ctx->Input.PanButton] || !IO.MouseDown[ctx->Input.PanButton]))
- {
- inspector->IsDragging = false;
- }
- }
- // ZOOM
- if (hovered && IO.MouseWheel != 0)
- {
- float zoomRate = ctx->ZoomRate;
- float scale = inspector->Scale.y;
- float prevScale = scale;
- bool keepTexelSizeRegular = scale > inspector->MinimumGridSize && !HasFlag(inspector->Flags, InspectorFlags_NoGrid);
- if (IO.MouseWheel > 0)
- {
- scale *= zoomRate;
- if (keepTexelSizeRegular)
- {
- // It looks nicer when all the grid cells are the same size
- // so keep scale integer when zoomed in
- scale = ImCeil(scale);
- }
- }
- else
- {
- scale /= zoomRate;
- if (keepTexelSizeRegular)
- {
- // See comment above. We're doing a floor this time to make
- // sure the scale always changes when scrolling
- scale = ImFloorSigned(scale);
- }
- }
- /* To make it easy to get back to 1:1 size we ensure that we stop
- * here without going straight past it*/
- if ((prevScale < 1 && scale > 1) || (prevScale > 1 && scale < 1))
- {
- scale = 1;
- }
- SetScale(inspector, ImVec2(inspector->PixelAspectRatio * scale, scale));
- SetPanPos(inspector, inspector->PanPos + (mouseUV - inspector->PanPos) * (1 - prevScale / scale));
- }
- return true;
- }
- else
- {
- return false;
- }
- }
- bool BeginInspectorPanel(const char *name, ImTextureID texture, ImVec2 textureSize, InspectorFlags flags)
- {
- return BeginInspectorPanel(name, texture, textureSize, flags, SizeIncludingBorder{{0, 0}});
- }
- bool BeginInspectorPanel(const char *name, ImTextureID texture, ImVec2 textureSize, InspectorFlags flags, SizeExcludingBorder size)
- {
- // Correct the size to include the border, but preserve 0 which has a special meaning
- return BeginInspectorPanel(name, texture, textureSize, flags,
- SizeIncludingBorder{ImVec2{size.size.x == 0 ? 0 : size.size.x + 2,
- size.size.y == 0 ? 0 : size.size.y + 2}});
- }
- void EndInspectorPanel()
- {
- const ImU32 innerBorderColour = 0xFFFFFFFF;
- const ImU32 outerBorderColour = 0xFF888888;
- Inspector *inspector = GContext->CurrentInspector;
- // Draw out border around whole inspector panel
- ImGui::GetWindowDrawList()->AddRect(inspector->PanelTopLeftPixel, inspector->PanelTopLeftPixel + inspector->PanelSize,
- outerBorderColour);
- // Draw innder border around texture. If zoomed in this will completely cover the outer border
- ImGui::GetWindowDrawList()->AddRect(inspector->ViewTopLeftPixel - ImVec2(1, 1),
- inspector->ViewTopLeftPixel + inspector->ViewSize + ImVec2(1, 1), innerBorderColour);
- ImGui::EndChild();
- // We set this back to false every frame in case the texture is dynamic
- if (!HasFlag(inspector->Flags, InspectorFlags_NoAutoReadTexture))
- {
- inspector->HaveCurrentTexelData = false;
- }
- }
- void ReleaseInspectorData(ImGuiID ID)
- {
- Inspector *inspector = GetByKey(GContext, ID);
- if (inspector == NULL)
- return;
- if (inspector->DataBuffer)
- {
- IM_FREE(inspector->DataBuffer);
- inspector->DataBuffer = NULL;
- inspector->DataBufferSize = 0;
- }
- /* In a later version we will remove inspector from the inspector table
- * altogether. For now we reset the whole inspector structure to prevent
- * clients relying on persisted data.
- */
- *inspector = Inspector();
- }
- ImGuiID CurrentInspector_GetID()
- {
- return GContext->CurrentInspector->ID;
- }
- void CurrentInspector_SetColorMatrix(const float (&matrix)[16], const float (&colorOffset)[4])
- {
- Inspector *inspector = GContext->CurrentInspector;
- ShaderOptions *shaderOptions = &inspector->ActiveShaderOptions;
- memcpy(shaderOptions->ColorTransform, matrix, sizeof(matrix));
- memcpy(shaderOptions->ColorOffset, colorOffset, sizeof(colorOffset));
- }
- void CurrentInspector_ResetColorMatrix()
- {
- Inspector *inspector = GContext->CurrentInspector;
- ShaderOptions *shaderOptions = &inspector->ActiveShaderOptions;
- shaderOptions->ResetColorTransform();
- }
- void CurrentInspector_SetAlphaMode(InspectorAlphaMode mode)
- {
- Inspector *inspector = GContext->CurrentInspector;
- ShaderOptions *shaderOptions = &inspector->ActiveShaderOptions;
- inspector->AlphaMode = mode;
- switch (mode)
- {
- case InspectorAlphaMode_Black:
- shaderOptions->BackgroundColor = ImVec4(0, 0, 0, 1);
- shaderOptions->DisableFinalAlpha = 1;
- shaderOptions->PremultiplyAlpha = 1;
- break;
- case InspectorAlphaMode_White:
- shaderOptions->BackgroundColor = ImVec4(1, 1, 1, 1);
- shaderOptions->DisableFinalAlpha = 1;
- shaderOptions->PremultiplyAlpha = 1;
- break;
- case InspectorAlphaMode_ImGui:
- shaderOptions->BackgroundColor = ImVec4(0, 0, 0, 0);
- shaderOptions->DisableFinalAlpha = 0;
- shaderOptions->PremultiplyAlpha = 0;
- break;
- case InspectorAlphaMode_CustomColor:
- shaderOptions->BackgroundColor = inspector->CustomBackgroundColor;
- shaderOptions->DisableFinalAlpha = 1;
- shaderOptions->PremultiplyAlpha = 1;
- break;
- }
- }
- void CurrentInspector_SetFlags(InspectorFlags toSet, InspectorFlags toClear)
- {
- Inspector *inspector = GContext->CurrentInspector;
- SetFlag(inspector->Flags, toSet);
- ClearFlag(inspector->Flags, toClear);
- }
- void CurrentInspector_SetGridColor(ImU32 color)
- {
- Inspector *inspector = GContext->CurrentInspector;
- float alpha = inspector->ActiveShaderOptions.GridColor.w;
- inspector->ActiveShaderOptions.GridColor = ImColor(color);
- inspector->ActiveShaderOptions.GridColor.w = alpha;
- }
- void CurrentInspector_SetMaxAnnotations(int maxAnnotations)
- {
- Inspector *inspector = GContext->CurrentInspector;
- inspector->MaxAnnotatedTexels = maxAnnotations;
- }
- void CurrentInspector_InvalidateTextureCache()
- {
- Inspector *inspector = GContext->CurrentInspector;
- inspector->HaveCurrentTexelData = false;
- }
- void CurrentInspector_SetCustomBackgroundColor(ImVec4 color)
- {
- Inspector *inspector = GContext->CurrentInspector;
- inspector->CustomBackgroundColor = color;
- if (inspector->AlphaMode == InspectorAlphaMode_CustomColor)
- {
- inspector->ActiveShaderOptions.BackgroundColor = color;
- }
- }
- void CurrentInspector_SetCustomBackgroundColor(ImU32 color)
- {
- CurrentInspector_SetCustomBackgroundColor(ImGui::ColorConvertU32ToFloat4(color));
- }
- void DrawColorMatrixEditor()
- {
- const char *colorVectorNames[] = {"R", "G", "B", "A", "1"};
- const char *finalColorVectorNames[] = {"R'", "G'", "B'", "A'"};
- const float dragSpeed = 0.02f;
- Inspector *inspector = GContext->CurrentInspector;
- ShaderOptions *shaderOptions = &inspector->ActiveShaderOptions;
-
- // Left hand side of equation. The final color vector which is the actual drawn color
- TextVector("FinalColorVector", finalColorVectorNames, IM_ARRAYSIZE(finalColorVectorNames));
- ImGui::SameLine();
- ImGui::TextUnformatted("=");
- ImGui::SameLine();
- // Right hand side of the equation: the Matrix. This is the editable part
- ImGui::BeginGroup();
- for (int i = 0; i < 4; ++i)
- {
- ImGui::PushID(i);
- for (int j = 0; j < 4; ++j)
- {
- ImGui::PushID(j);
- ImGui::SetNextItemWidth(50);
- ImGui::DragFloat("##f", &shaderOptions->ColorTransform[j * 4 + i], dragSpeed);
- ImGui::PopID();
- ImGui::SameLine();
- }
- ImGui::SetNextItemWidth(50);
- ImGui::DragFloat("##offset", &shaderOptions->ColorOffset[i], dragSpeed);
- ImGui::PopID();
- }
- ImGui::EndGroup();
- ImGui::SameLine();
- ImGui::TextUnformatted("*");
- ImGui::SameLine();
- // Right hand side of equation. The input vector, the source color of the texel.
- TextVector("ColorVector", colorVectorNames, IM_ARRAYSIZE(colorVectorNames));
- }
- void DrawGridEditor()
- {
- Inspector *inspector = GContext->CurrentInspector;
- ImGui::BeginGroup();
- bool gridEnabled = !HasFlag(inspector->Flags, InspectorFlags_NoGrid);
- if (ImGui::Checkbox("Grid", &gridEnabled))
- {
- if (gridEnabled)
- {
- CurrentInspector_ClearFlags(InspectorFlags_NoGrid);
- }
- else
- {
- CurrentInspector_SetFlags(InspectorFlags_NoGrid);
- }
- }
- if (gridEnabled)
- {
- ImGui::SameLine();
- ImGui::ColorEdit3("Grid Color", (float *)&inspector->ActiveShaderOptions.GridColor, ImGuiColorEditFlags_NoInputs);
- }
- ImGui::EndGroup();
- }
- void DrawColorChannelSelector()
- {
- Inspector *inspector = GContext->CurrentInspector;
- ShaderOptions *shaderOptions = &inspector->ActiveShaderOptions;
- ImGuiStorage *storage = ImGui::GetStateStorage();
- const ImGuiID greyScaleID = ImGui::GetID("greyScale");
- bool greyScale = storage->GetBool(greyScaleID, false);
- bool red = shaderOptions->ColorTransform[0] > 0;
- bool green = shaderOptions->ColorTransform[5] > 0;
- bool blue = shaderOptions->ColorTransform[10] > 0;
- bool changed = false;
- // In greyScale made we draw the red, green, blue checkboxes as disabled
- if (greyScale)
- {
- PushDisabled();
- }
- ImGui::BeginGroup();
- changed |= ImGui::Checkbox("Red", &red);
- changed |= ImGui::Checkbox("Green", &green);
- changed |= ImGui::Checkbox("Blue", &blue);
- ImGui::EndGroup();
- ImGui::SameLine();
- if (greyScale)
- {
- PopDisabled();
- }
- if (changed)
- {
- // Overwrite the color transform matrix with one based on the settings
- shaderOptions->ResetColorTransform();
- shaderOptions->ColorTransform[0] = red ? 1.0f : 0.0f;
- shaderOptions->ColorTransform[5] = green ? 1.0f : 0.0f;
- shaderOptions->ColorTransform[10] = blue ? 1.0f : 0.0f;
- }
- ImGui::BeginGroup();
- if (ImGui::Checkbox("Grey", &greyScale))
- {
- shaderOptions->ResetColorTransform();
- storage->SetBool(greyScaleID, greyScale);
- if (greyScale)
- {
- for (int i = 0; i < 3; i++)
- {
- for (int j = 0; j < 3; j++)
- {
- shaderOptions->ColorTransform[i * 4 + j] = 0.333f;
- }
- }
- }
- }
- ImGui::EndGroup();
- }
- void DrawAlphaModeSelector()
- {
- Inspector *inspector = GContext->CurrentInspector;
- const char *alphaModes[] = {"ImGui Background", "Black", "White", "Custom Color"};
- ImGui::SetNextItemWidth(200);
- InspectorAlphaMode currentAlphaMode = inspector->AlphaMode;
- ImGui::Combo("Alpha Modes", (int *)¤tAlphaMode, alphaModes, IM_ARRAYSIZE(alphaModes));
- CurrentInspector_SetAlphaMode(currentAlphaMode);
- if (inspector->AlphaMode == InspectorAlphaMode_CustomColor)
- {
- ImVec4 backgroundColor = inspector->CustomBackgroundColor;
- if (ImGui::ColorEdit3("Background Color", (float *)&backgroundColor, 0))
- {
- CurrentInspector_SetCustomBackgroundColor(backgroundColor);
- }
- }
- }
- void SetZoomRate(float rate)
- {
- GContext->ZoomRate = rate;
- }
- //-------------------------------------------------------------------------
- // [SECTION] Life Cycle
- //-------------------------------------------------------------------------
- Inspector::~Inspector()
- {
- if (DataBuffer)
- {
- IM_FREE(DataBuffer);
- }
- }
- //-------------------------------------------------------------------------
- // [SECTION] Scaling and Panning
- //-------------------------------------------------------------------------
- void RoundPanPos(Inspector *inspector)
- {
- if ((inspector->Flags & InspectorFlags_ShowWrap) > 0)
- {
- /* PanPos is the point in the center of the current view. Allow the
- * user to pan anywhere as long as the view center is inside the
- * texture.*/
- inspector->PanPos = ImClamp(inspector->PanPos, ImVec2(0, 0), ImVec2(1, 1));
- }
- else
- {
- /* When ShowWrap mode is disabled the limits are a bit more strict. We
- * try to keep it so that the user cannot pan past the edge of the
- * texture at all.*/
- ImVec2 absViewSizeUV = Abs(inspector->ViewSizeUV);
- inspector->PanPos = ImMax(inspector->PanPos - absViewSizeUV / 2, ImVec2(0, 0)) + absViewSizeUV / 2;
- inspector->PanPos = ImMin(inspector->PanPos + absViewSizeUV / 2, ImVec2(1, 1)) - absViewSizeUV / 2;
- }
- /* If inspector->scale is 1 then we should ensure that pixels are aligned
- * with texel centers to get pixel-perfect texture rendering*/
- ImVec2 topLeftSubTexel = inspector->PanPos * inspector->Scale * inspector->TextureSize - inspector->ViewSize * 0.5f;
- if (inspector->Scale.x >= 1)
- {
- topLeftSubTexel.x = Round(topLeftSubTexel.x);
- }
- if (inspector->Scale.y >= 1)
- {
- topLeftSubTexel.y = Round(topLeftSubTexel.y);
- }
- inspector->PanPos = (topLeftSubTexel + inspector->ViewSize * 0.5f) / (inspector->Scale * inspector->TextureSize);
- }
- void SetPanPos(Inspector *inspector, ImVec2 pos)
- {
- inspector->PanPos = pos;
- RoundPanPos(inspector);
- }
- void SetScale(Inspector *inspector, ImVec2 scale)
- {
- scale = ImClamp(scale, inspector->ScaleMin, inspector->ScaleMax);
- inspector->ViewSizeUV *= inspector->Scale / scale;
- inspector->Scale = scale;
-
- // Only force nearest sampling if zoomed in
- inspector->ActiveShaderOptions.ForceNearestSampling =
- (inspector->Scale.x > 1.0f || inspector->Scale.y > 1.0f) && !HasFlag(inspector->Flags, InspectorFlags_NoForceFilterNearest);
- inspector->ActiveShaderOptions.GridWidth = ImVec2(1.0f / inspector->Scale.x, 1.0f / inspector->Scale.y);
- }
- void SetScale(Inspector *inspector, float scaleY)
- {
- SetScale(inspector, ImVec2(scaleY * inspector->PixelAspectRatio, scaleY));
- }
- //-------------------------------------------------------------------------
- // [SECTION] INSPECTOR MAP
- //-------------------------------------------------------------------------
- Inspector *GetByKey(const Context *ctx, ImGuiID key)
- {
- return (Inspector *)ctx->Inspectors.GetVoidPtr(key);
- }
- Inspector *GetOrAddByKey(Context *ctx, ImGuiID key)
- {
- Inspector *inspector = GetByKey(ctx, key);
- if (inspector)
- {
- return inspector;
- }
- else
- {
- inspector = IM_NEW(Inspector);
- ctx->Inspectors.SetVoidPtr(key, inspector);
- return inspector;
- }
- }
- //-------------------------------------------------------------------------
- // [SECTION] TextureConversion class
- //-------------------------------------------------------------------------
- void ShaderOptions::ResetColorTransform()
- {
- memset(ColorTransform, 0, sizeof(ColorTransform));
- for (int i = 0; i < 4; ++i)
- {
- ColorTransform[i * 4 + i] = 1;
- }
- }
- ShaderOptions::ShaderOptions()
- {
- ResetColorTransform();
- memset(ColorOffset, 0, sizeof(ColorOffset));
- }
- //-------------------------------------------------------------------------
- // [SECTION] UI and CONFIG
- //-------------------------------------------------------------------------
- void UpdateShaderOptions(Inspector *inspector)
- {
- if (HasFlag(inspector->Flags, InspectorFlags_NoGrid) == false && inspector->Scale.y > inspector->MinimumGridSize)
- {
- // Enable grid in shader
- inspector->ActiveShaderOptions.GridColor.w = 1;
- SetScale(inspector, Round(inspector->Scale.y));
- }
- else
- {
- // Disable grid in shader
- inspector->ActiveShaderOptions.GridColor.w = 0;
- }
- inspector->ActiveShaderOptions.ForceNearestSampling =
- (inspector->Scale.x > 1.0f || inspector->Scale.y > 1.0f) && !HasFlag(inspector->Flags, InspectorFlags_NoForceFilterNearest);
- }
- // Draws a single column ImGui table with one row for each provided string
- void TextVector(const char *title, const char *const *strings, int stringCount)
- {
- ImGui::BeginGroup();
- ImGui::SetNextItemWidth(50);
- if (ImGui::BeginTable(title, 1, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX))
- {
- for (int i = 0; i < stringCount; ++i)
- {
- ImGui::TableNextRow();
- ImGui::TableSetColumnIndex(0);
- ImGui::SetNextItemWidth(50);
- ImGui::Text("%s", strings[i]);
- }
- ImGui::EndTable();
- }
- ImGui::EndGroup();
- }
- const ImGuiCol disabledUIColorIds[] = {ImGuiCol_FrameBg,
- ImGuiCol_FrameBgActive,
- ImGuiCol_FrameBgHovered,
- ImGuiCol_Text,
- ImGuiCol_CheckMark};
- // Push disabled style for ImGui elements
- void PushDisabled()
- {
- for (ImGuiCol colorId : disabledUIColorIds)
- {
- ImVec4 color = ImGui::GetStyleColorVec4(colorId);
- color = color * ImVec4(0.5f, 0.5f, 0.5f, 0.5f);
- ImGui::PushStyleColor(colorId, color);
- }
- ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
- }
- // Pop disabled style for ImGui elements
- void PopDisabled()
- {
- for (ImGuiCol colorId : disabledUIColorIds)
- {
- (void)colorId;
- ImGui::PopStyleColor();
- }
- ImGui::PopItemFlag();
- }
- //-------------------------------------------------------------------------
- // [SECTION] Rendering & Buffer Management
- //-------------------------------------------------------------------------
- void InspectorDrawCallback(const ImDrawList *parent_list, const ImDrawCmd *cmd)
- {
- // Forward call to API-specific backend
- Inspector *inspector = (Inspector *)cmd->UserCallbackData;
- BackEnd_SetShader(parent_list, cmd, inspector);
- }
- // Calculate a transform to convert from texel coordinates to screen pixel coordinates
- Transform2D GetTexelsToPixels(ImVec2 screenTopLeft, ImVec2 screenViewSize, ImVec2 uvTopLeft, ImVec2 uvViewSize, ImVec2 textureSize)
- {
- ImVec2 uvToPixel = screenViewSize / uvViewSize;
- Transform2D transform;
- transform.Scale = uvToPixel / textureSize;
- transform.Translate.x = screenTopLeft.x - uvTopLeft.x * uvToPixel.x;
- transform.Translate.y = screenTopLeft.y - uvTopLeft.y * uvToPixel.y;
- return transform;
- }
- /* Fills in the AnnotationsDesc structure which provides all necessary
- * information for code which draw annoations. Returns false if no annoations
- * should be drawn. The maxAnnotatedTexels argument provides a way to override
- * the default maxAnnotatedTexels.
- */
- bool GetAnnotationDesc(AnnotationsDesc *ad, ImU64 maxAnnotatedTexels)
- {
- Inspector *inspector = GContext->CurrentInspector;
- if (maxAnnotatedTexels == 0)
- {
- maxAnnotatedTexels = inspector->MaxAnnotatedTexels;
- }
- if (maxAnnotatedTexels != 0)
- {
- /* Check if we would draw too many annotations. This is to avoid poor
- * frame rate when too zoomed out. Increase MaxAnnotatedTexels if you
- * want to draw more annotations. Note that we don't use texelTL &
- * texelBR to get total visible texels as this would cause flickering
- * while panning as the exact number of visible texels changes.
- */
- ImVec2 screenViewSizeTexels = Abs(inspector->PixelsToTexels.Scale) * inspector->ViewSize;
- ImU64 approxVisibleTexelCount = (ImU64)screenViewSizeTexels.x * (ImU64)screenViewSizeTexels.y;
- if (approxVisibleTexelCount > maxAnnotatedTexels)
- {
- return false;
- }
- }
- // texelTL & texelBL will describe the currently visible texel region
- ImVec2 texelTL;
- ImVec2 texelBR;
- if (GetVisibleTexelRegionAndGetData(inspector, texelTL, texelBR))
- {
- ad->Buffer= inspector->Buffer;
- ad->DrawList = ImGui::GetWindowDrawList();
- ad->TexelsToPixels = inspector->TexelsToPixels;
- ad->TexelTopLeft = texelTL;
- ad->TexelViewSize = texelBR - texelTL;
- return true;
- }
- return false;
- }
- /* Calculates currently visible region of texture (which is returned in texelTL
- * and texelBR) then also actually ensure that that data is in memory. Returns
- * false if fetching data failed.
- */
- bool GetVisibleTexelRegionAndGetData(Inspector *inspector, ImVec2 &texelTL, ImVec2 &texelBR)
- {
- /* Figure out which texels correspond to the top left and bottom right
- * corners of the texture view. The plus + ImVec2(1,1) is because we
- * want to draw partially visible texels on the bottom and right edges.
- */
- texelTL = ImFloor(inspector->PixelsToTexels * inspector->ViewTopLeftPixel);
- texelBR = ImFloor(inspector->PixelsToTexels * (inspector->ViewTopLeftPixel + inspector->ViewSize));
- if (texelTL.x > texelBR.x)
- {
- ImSwap(texelTL.x, texelBR.x);
- }
- if (texelTL.y > texelBR.y)
- {
- ImSwap(texelTL.y, texelBR.y);
- }
- /* Add ImVec2(1,1) because we want to draw partially visible texels on the
- * bottom and right edges.*/
- texelBR += ImVec2(1,1);
- texelTL = ImClamp(texelTL, ImVec2(0, 0), inspector->TextureSize);
- texelBR = ImClamp(texelBR, ImVec2(0, 0), inspector->TextureSize);
- if (inspector->HaveCurrentTexelData)
- {
- return true;
- }
- // Now request pixel data for this region from backend
- ImVec2 texelViewSize = texelBR - texelTL;
- if (ImMin(texelViewSize.x, texelViewSize.y) > 0)
- {
- if (BackEnd_GetData(inspector, inspector->Texture, (int)texelTL.x, (int)texelTL.y, (int)texelViewSize.x, (int)texelViewSize.y,
- &inspector->Buffer))
- {
- inspector->HaveCurrentTexelData = true;
- return true;
- }
- }
- return false;
- }
- /* This is a function the backends can use to allocate a buffer for storing
- * texture texel data. The buffer is owned by the inpsector so the backend
- * code doesn't need to worry about freeing it.
- */
- ImU8 *GetBuffer(Inspector *inspector, size_t bytes)
- {
- if (inspector->DataBufferSize < bytes || inspector->DataBuffer == nullptr)
- {
- // We need to allocate a buffer
- if (inspector->DataBuffer)
- {
- IM_FREE(inspector->DataBuffer);
- }
- // Allocate slightly more than we need to avoid reallocating
- // very frequently in the case that size is increasing.
- size_t size = bytes * 5 / 4;
- inspector->DataBuffer = (ImU8 *)IM_ALLOC(size);
- inspector->DataBufferSize = size;
- }
- return inspector->DataBuffer;
- }
- ImVec4 GetTexel(const BufferDesc *bd, int x, int y)
- {
- if (x < bd->StartX || x >= bd->StartX + bd->Width || y < bd->StartY || y >= bd->StartY + bd->Height)
- {
- // Outside the range of data in the buffer.
- return ImVec4();
- }
- // Calculate position in array
- size_t offset = ((size_t)bd->LineStride * (y - bd->StartY) + bd->Stride * (x - bd->StartX));
- if (bd->Data_float)
- {
- const float *texel = bd->Data_float + offset;
- // It's possible our buffer doesn't have all 4 channels so fill gaps in with zeros
- return ImVec4( texel[bd->Red],
- bd->ChannelCount >= 2 ? texel[bd->Green] : 0,
- bd->ChannelCount >= 3 ? texel[bd->Blue] : 0,
- bd->ChannelCount >= 4 ? texel[bd->Alpha] : 0);
- }
- else if (bd->Data_uint8_t)
- {
- const ImU8 *texel = bd->Data_uint8_t + offset;
- // It's possible our buffer doesn't have all 4 channels so fill gaps in with zeros.
- // Also map from [0,255] to [0,1]
- return ImVec4( (float)texel[bd->Red] / 255.0f,
- bd->ChannelCount >= 2 ? (float)texel[bd->Green] / 255.0f : 0,
- bd->ChannelCount >= 3 ? (float)texel[bd->Blue] / 255.0f : 0,
- bd->ChannelCount >= 4 ? (float)texel[bd->Alpha] / 255.0f : 0);
- }
- else
- {
- return ImVec4();
- }
- }
- //-------------------------------------------------------------------------
- // [SECTION] Annotations
- //-------------------------------------------------------------------------
- ValueText::ValueText(Format format)
- {
- /* The ValueText annotation draws a string inside each texel displaying the
- * values of each channel. We now select a format string based on the enum
- * parameter*/
- switch (format)
- {
- case Format::HexString:
- TextFormatString = "#%02X%02X%02X%02X";
- TextColumnCount = 9;
- TextRowCount = 1;
- FormatAsFloats = false;
- break;
- case Format::BytesHex:
- TextFormatString = "R:#%02X\nG:#%02X\nB:#%02X\nA:#%02X";
- TextColumnCount = 5;
- TextRowCount = 4;
- FormatAsFloats = false;
- break;
- case Format::BytesDec:
- TextFormatString = "R:%3d\nG:%3d\nB:%3d\nA:%3d";
- TextColumnCount = 5;
- TextRowCount = 4;
- FormatAsFloats = false;
- break;
- case Format::Floats:
- TextFormatString = "%5.3f\n%5.3f\n%5.3f\n%5.3f";
- TextColumnCount = 5;
- TextRowCount = 4;
- FormatAsFloats = true;
- break;
- }
- }
- void ValueText::DrawAnnotation(ImDrawList *drawList, ImVec2 texel, Transform2D texelsToPixels, ImVec4 value)
- {
- char buffer[64];
- float fontHeight = ImGui::GetFontSize();
- float fontWidth = fontHeight / 2; /* WARNING this is a hack that gets a constant
- * character width from half the height. This work for the default font but
- * won't work on other fonts which may even not be monospace.*/
- // Calculate size of text and check if it fits
- ImVec2 textSize = ImVec2((float)TextColumnCount * fontWidth, (float)TextRowCount * fontHeight);
- if (textSize.x > ImAbs(texelsToPixels.Scale.x) || textSize.y > ImAbs(texelsToPixels.Scale.y))
- {
- // Not enough room in texel to fit the text. Don't draw it.
- return;
- }
- /* Choose black or white text based on how bright the texel. I.e. don't
- * draw black text on a dark background or vice versa. */
- float brightness = (value.x + value.y + value.z) * value.w / 3;
- ImU32 lineColor = brightness > 0.5 ? 0xFF000000 : 0xFFFFFFFF;
- if (FormatAsFloats)
- {
- sprintf(buffer, TextFormatString, value.x, value.y, value.z, value.w);
- }
- else
- {
- /* Map [0,1] to [0,255]. Also clamp it since input data wasn't
- * necessarily in [0,1] range. */
- ImU8 r = (ImU8)Round((ImClamp(value.x, 0.0f, 1.0f)) * 255);
- ImU8 g = (ImU8)Round((ImClamp(value.y, 0.0f, 1.0f)) * 255);
- ImU8 b = (ImU8)Round((ImClamp(value.z, 0.0f, 1.0f)) * 255);
- ImU8 a = (ImU8)Round((ImClamp(value.w, 0.0f, 1.0f)) * 255);
- sprintf(buffer, TextFormatString, r, g, b, a);
- }
- // Add text to drawlist!
- ImVec2 pixelCenter = texelsToPixels * texel;
- drawList->AddText(pixelCenter - textSize * 0.5f, lineColor, buffer);
- }
- Arrow::Arrow(int xVectorIndex, int yVectorIndex, ImVec2 lineScale)
- : VectorIndex_x(xVectorIndex), VectorIndex_y(yVectorIndex), LineScale(lineScale)
- {
- }
- Arrow &Arrow::UsePreset(Preset preset)
- {
- switch (preset)
- {
- default:
- case Preset::NormalMap:
- VectorIndex_x = 0;
- VectorIndex_y = 1;
- LineScale = ImVec2(1, -1);
- ZeroPoint = ImVec2(128.0f / 255, 128.0f / 255);
- break;
- case Preset::NormalizedFloat:
- VectorIndex_x = 0;
- VectorIndex_y = 1;
- LineScale = ImVec2(0.5f, -0.5f);
- ZeroPoint = ImVec2(0, 0);
- break;
- }
- return *this;
- }
- void Arrow::DrawAnnotation(ImDrawList *drawList, ImVec2 texel, Transform2D texelsToPixels, ImVec4 value)
- {
- const float arrowHeadScale = 0.35f;
- const ImU32 lineColor = 0xFFFFFFFF;
- float *vecPtr = &value.x;
- // Draw an arrow!
- ImVec2 lineDir = (ImVec2(vecPtr[VectorIndex_x], vecPtr[VectorIndex_y]) - ZeroPoint) * LineScale;
- ImVec2 lineStart = texel;
- ImVec2 lineEnd = lineStart + lineDir;
- ImVec2 arrowHead1 = ImVec2(lineDir.x - lineDir.y, lineDir.x + lineDir.y) * -arrowHeadScale;
- ImVec2 arrowHead2 = ImVec2(lineDir.x + lineDir.y, -lineDir.x + lineDir.y) * -arrowHeadScale;
- DrawAnnotationLine(drawList, lineStart, lineEnd, texelsToPixels, lineColor);
- DrawAnnotationLine(drawList, lineEnd, lineEnd + arrowHead1, texelsToPixels, lineColor);
- DrawAnnotationLine(drawList, lineEnd, lineEnd + arrowHead2, texelsToPixels, lineColor);
- }
- void DrawAnnotationLine(ImDrawList *drawList, ImVec2 fromTexel, ImVec2 toTexel, Transform2D texelsToPixels, ImU32 color)
- {
- ImVec2 lineFrom = texelsToPixels * fromTexel;
- ImVec2 lineTo = texelsToPixels * toTexel;
- drawList->AddLine(lineFrom, lineTo, color, 1.0f);
- }
- } // namespace ImGuiTexInspect
|