Files
imgui/misc/cpp/imgui_cpu_validate.cpp
T
2026-03-15 12:33:32 +01:00

386 lines
14 KiB
C++

// dear imgui: imgui_impl_cpu format validation utility
// Build manually with imgui core sources + backends/imgui_impl_cpu.cpp.
#include "imgui.h"
#include "imgui_impl_cpu.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct ValidationBuffer
{
ImVector<unsigned char> Storage;
ImVector<ImU32> Palette;
ImGui_ImplCPU_PixelFormat Format;
ImGui_ImplCPU_Framebuffer Framebuffer;
ValidationBuffer() { memset((void*)this, 0, sizeof(*this)); }
};
static inline unsigned char Validation_ExpandBits(ImU32 value, int bits)
{
if (bits <= 0)
return 0;
if (bits >= 8)
return (unsigned char)value;
ImU32 max_value = (1u << bits) - 1u;
return (unsigned char)((value * 255u + (max_value >> 1)) / max_value);
}
static inline ImU32 Validation_CompressBits(unsigned char value, int bits)
{
if (bits <= 0)
return 0;
if (bits >= 8)
return value;
ImU32 max_value = (1u << bits) - 1u;
return (ImU32)((value * max_value + 127u) / 255u);
}
static inline void Validation_UnpackCanonicalRGBA(ImU32 pixel, unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a)
{
*r = (unsigned char)((pixel >> IM_COL32_R_SHIFT) & 0xFF);
*g = (unsigned char)((pixel >> IM_COL32_G_SHIFT) & 0xFF);
*b = (unsigned char)((pixel >> IM_COL32_B_SHIFT) & 0xFF);
*a = (unsigned char)((pixel >> IM_COL32_A_SHIFT) & 0xFF);
}
static inline ImU32 Validation_ReadRawPixel(const ValidationBuffer& buffer, int x, int y)
{
const unsigned char* p = buffer.Framebuffer.Pixels + (size_t)y * (size_t)buffer.Framebuffer.Pitch + (size_t)x * (size_t)buffer.Format.BytesPerPixel;
switch (buffer.Format.BytesPerPixel)
{
case 1:
return *p;
case 2:
{
ImU16 pixel = 0;
memcpy(&pixel, p, sizeof(pixel));
return pixel;
}
case 3:
#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
return (ImU32)((p[0] << 16) | (p[1] << 8) | p[2]);
#else
return (ImU32)(p[0] | (p[1] << 8) | (p[2] << 16));
#endif
case 4:
{
ImU32 pixel = 0;
memcpy(&pixel, p, sizeof(pixel));
return pixel;
}
default:
return 0;
}
}
static inline void Validation_WriteRawPixel(ValidationBuffer* buffer, int x, int y, ImU32 pixel)
{
unsigned char* p = buffer->Framebuffer.Pixels + (size_t)y * (size_t)buffer->Framebuffer.Pitch + (size_t)x * (size_t)buffer->Format.BytesPerPixel;
switch (buffer->Format.BytesPerPixel)
{
case 1:
*p = (unsigned char)pixel;
return;
case 2:
{
ImU16 v = (ImU16)pixel;
memcpy(p, &v, sizeof(v));
return;
}
case 3:
#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
p[0] = (unsigned char)((pixel >> 16) & 0xFF);
p[1] = (unsigned char)((pixel >> 8) & 0xFF);
p[2] = (unsigned char)(pixel & 0xFF);
#else
p[0] = (unsigned char)(pixel & 0xFF);
p[1] = (unsigned char)((pixel >> 8) & 0xFF);
p[2] = (unsigned char)((pixel >> 16) & 0xFF);
#endif
return;
case 4:
memcpy(p, &pixel, sizeof(pixel));
return;
default:
return;
}
}
static ImU32 Validation_MapNearestPaletteColor(const ImGui_ImplCPU_PixelFormat* format, unsigned char r, unsigned char g, unsigned char b, unsigned char a)
{
unsigned int best_distance = 0xFFFFFFFFu;
int best_index = 0;
for (int i = 0; i < format->PaletteSize; i++)
{
unsigned char pr, pg, pb, pa;
Validation_UnpackCanonicalRGBA(format->Palette[i], &pr, &pg, &pb, &pa);
int dr = (int)pr - (int)r;
int dg = (int)pg - (int)g;
int db = (int)pb - (int)b;
int da = (int)pa - (int)a;
unsigned int distance = (unsigned int)(dr * dr + dg * dg + db * db + da * da);
if (distance < best_distance)
{
best_distance = distance;
best_index = i;
if (distance == 0)
break;
}
}
return (ImU32)best_index;
}
static ImU32 Validation_PackPixel(const ValidationBuffer& buffer, unsigned char r, unsigned char g, unsigned char b, unsigned char a)
{
if (buffer.Format.Palette != nullptr && buffer.Format.PaletteSize > 0)
{
if (buffer.Format.MapColorFn != nullptr)
return buffer.Format.MapColorFn(&buffer.Format, r, g, b, a);
return Validation_MapNearestPaletteColor(&buffer.Format, r, g, b, a);
}
ImU32 pixel = 0;
if (buffer.Format.Rmask != 0)
pixel |= (Validation_CompressBits(r, 8 - buffer.Format.Rloss) << buffer.Format.Rshift) & buffer.Format.Rmask;
if (buffer.Format.Gmask != 0)
pixel |= (Validation_CompressBits(g, 8 - buffer.Format.Gloss) << buffer.Format.Gshift) & buffer.Format.Gmask;
if (buffer.Format.Bmask != 0)
pixel |= (Validation_CompressBits(b, 8 - buffer.Format.Bloss) << buffer.Format.Bshift) & buffer.Format.Bmask;
if (buffer.Format.Amask != 0)
pixel |= (Validation_CompressBits(a, 8 - buffer.Format.Aloss) << buffer.Format.Ashift) & buffer.Format.Amask;
return pixel;
}
static void Validation_DecodePixel(const ValidationBuffer& buffer, int x, int y, unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a)
{
ImU32 pixel = Validation_ReadRawPixel(buffer, x, y);
if (buffer.Format.Palette != nullptr && buffer.Format.PaletteSize > 0)
{
if (pixel >= (ImU32)buffer.Format.PaletteSize)
{
*r = *g = *b = *a = 0;
return;
}
Validation_UnpackCanonicalRGBA(buffer.Format.Palette[pixel], r, g, b, a);
return;
}
*r = (buffer.Format.Rmask != 0) ? Validation_ExpandBits((pixel & buffer.Format.Rmask) >> buffer.Format.Rshift, 8 - buffer.Format.Rloss) : 0;
*g = (buffer.Format.Gmask != 0) ? Validation_ExpandBits((pixel & buffer.Format.Gmask) >> buffer.Format.Gshift, 8 - buffer.Format.Gloss) : 0;
*b = (buffer.Format.Bmask != 0) ? Validation_ExpandBits((pixel & buffer.Format.Bmask) >> buffer.Format.Bshift, 8 - buffer.Format.Bloss) : 0;
*a = (buffer.Format.Amask != 0) ? Validation_ExpandBits((pixel & buffer.Format.Amask) >> buffer.Format.Ashift, 8 - buffer.Format.Aloss) : 255;
}
static bool Validation_InitBuffer(ValidationBuffer* buffer, int width, int height, const ImGui_ImplCPU_PixelFormat* format)
{
buffer->Format = *format;
buffer->Framebuffer.Width = width;
buffer->Framebuffer.Height = height;
buffer->Framebuffer.Pitch = width * buffer->Format.BytesPerPixel;
buffer->Storage.resize(height * buffer->Framebuffer.Pitch);
buffer->Framebuffer.Pixels = buffer->Storage.Data;
buffer->Framebuffer.Format = &buffer->Format;
return buffer->Framebuffer.Pixels != nullptr;
}
static bool Validation_InitIndexedBuffer(ValidationBuffer* buffer, int width, int height)
{
buffer->Palette.clear();
buffer->Palette.push_back(IM_COL32(114, 140, 153, 255));
buffer->Palette.push_back(IM_COL32(255, 0, 0, 255));
buffer->Palette.push_back(IM_COL32(0, 255, 0, 255));
buffer->Palette.push_back(IM_COL32(0, 0, 255, 255));
buffer->Palette.push_back(IM_COL32(255, 255, 255, 255));
buffer->Palette.push_back(IM_COL32(255, 255, 0, 255));
memset(&buffer->Format, 0, sizeof(buffer->Format));
buffer->Format.BitsPerPixel = 8;
buffer->Format.BytesPerPixel = 1;
buffer->Format.Palette = buffer->Palette.Data;
buffer->Format.PaletteSize = buffer->Palette.Size;
buffer->Format.MapColorFn = Validation_MapNearestPaletteColor;
return Validation_InitBuffer(buffer, width, height, &buffer->Format);
}
static void Validation_ClearBuffer(ValidationBuffer* buffer, unsigned char r, unsigned char g, unsigned char b, unsigned char a)
{
ImU32 pixel = Validation_PackPixel(*buffer, r, g, b, a);
for (int y = 0; y < buffer->Framebuffer.Height; y++)
for (int x = 0; x < buffer->Framebuffer.Width; x++)
Validation_WriteRawPixel(buffer, x, y, pixel);
}
static bool Validation_CompareBuffers(const char* name, const ValidationBuffer& reference, const ValidationBuffer& candidate, int tolerance_rgb, int tolerance_alpha, bool ignore_alpha)
{
for (int y = 0; y < reference.Framebuffer.Height; y++)
for (int x = 0; x < reference.Framebuffer.Width; x++)
{
unsigned char rr, rg, rb, ra;
unsigned char cr, cg, cb, ca;
Validation_DecodePixel(reference, x, y, &rr, &rg, &rb, &ra);
Validation_DecodePixel(candidate, x, y, &cr, &cg, &cb, &ca);
if (abs((int)rr - (int)cr) > tolerance_rgb
|| abs((int)rg - (int)cg) > tolerance_rgb
|| abs((int)rb - (int)cb) > tolerance_rgb
|| (!ignore_alpha && abs((int)ra - (int)ca) > tolerance_alpha))
{
printf("Validation failed for %s at (%d, %d): ref=(%u,%u,%u,%u) got=(%u,%u,%u,%u)\n",
name, x, y, rr, rg, rb, ra, cr, cg, cb, ca);
return false;
}
}
return true;
}
static void Validation_FillBGRAImage(ImVector<unsigned char>* storage, ImGui_ImplCPU_Texture* texture)
{
const int width = 48;
const int height = 32;
storage->resize(width * height * 4);
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
{
unsigned char r = (unsigned char)(32 + x * 4);
unsigned char g = (unsigned char)(32 + y * 6);
unsigned char b = (unsigned char)(255 - x * 4);
int offset = (y * width + x) * 4;
(*storage)[offset + 0] = b;
(*storage)[offset + 1] = g;
(*storage)[offset + 2] = r;
(*storage)[offset + 3] = 255;
}
texture->Pixels = storage->Data;
texture->Width = width;
texture->Height = height;
texture->Pitch = width * 4;
texture->Format = ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_BGRA8888);
}
static void Validation_BuildMainScene(const ImGui_ImplCPU_Texture* image_texture)
{
ImDrawList* bg = ImGui::GetBackgroundDrawList();
bg->AddRectFilled(ImVec2(8.0f, 8.0f), ImVec2(40.0f, 40.0f), IM_COL32(255, 0, 0, 255));
bg->AddRectFilled(ImVec2(472.0f, 280.0f), ImVec2(504.0f, 312.0f), IM_COL32(0, 255, 0, 255));
ImGui::SetNextWindowPos(ImVec2(32.0f, 24.0f), ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(320.0f, 180.0f), ImGuiCond_Always);
ImGui::Begin("Validation", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
ImGui::Text("CPU format validation");
ImGui::Text("Top-left marker is red. Bottom-right is green.");
ImGui::ColorButton("##color", ImVec4(0.2f, 0.6f, 0.9f, 1.0f), 0, ImVec2(48.0f, 24.0f));
ImGui::SameLine();
ImGui::Image((ImTextureID)(intptr_t)image_texture, ImVec2((float)image_texture->Width, (float)image_texture->Height));
ImGui::ProgressBar(0.65f, ImVec2(240.0f, 0.0f));
ImGui::End();
}
static void Validation_BuildPaletteScene()
{
ImDrawList* bg = ImGui::GetBackgroundDrawList();
bg->AddRectFilled(ImVec2(8.0f, 8.0f), ImVec2(40.0f, 40.0f), IM_COL32(255, 0, 0, 255));
bg->AddRectFilled(ImVec2(472.0f, 280.0f), ImVec2(504.0f, 312.0f), IM_COL32(0, 255, 0, 255));
bg->AddRectFilled(ImVec2(160.0f, 32.0f), ImVec2(320.0f, 96.0f), IM_COL32(0, 0, 255, 255));
bg->AddRectFilled(ImVec2(56.0f, 232.0f), ImVec2(168.0f, 280.0f), IM_COL32(255, 255, 0, 255));
bg->AddRectFilled(ImVec2(360.0f, 64.0f), ImVec2(456.0f, 112.0f), IM_COL32(255, 255, 255, 255));
}
static bool Validation_RenderInto(ValidationBuffer* buffer, ImDrawData* draw_data)
{
Validation_ClearBuffer(buffer, 114, 140, 153, 255);
ImGui_ImplCPU_RenderDrawData(draw_data, &buffer->Framebuffer);
return true;
}
int main(int, char**)
{
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.DisplaySize = ImVec2(512.0f, 320.0f);
io.DeltaTime = 1.0f / 60.0f;
io.Fonts->TexDesiredFormat = ImTextureFormat_Alpha8;
ImGui::StyleColorsDark();
ImGui_ImplCPU_Init();
ImVector<unsigned char> image_storage;
ImGui_ImplCPU_Texture image_texture;
memset(&image_texture, 0, sizeof(image_texture));
Validation_FillBGRAImage(&image_storage, &image_texture);
ValidationBuffer reference;
Validation_InitBuffer(&reference, 512, 320, ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_RGBA8888));
const ImGui_ImplCPU_BuiltinFormat builtin_formats[] =
{
ImGui_ImplCPU_BuiltinFormat_RGBA8888,
ImGui_ImplCPU_BuiltinFormat_BGRA8888,
ImGui_ImplCPU_BuiltinFormat_ARGB8888,
ImGui_ImplCPU_BuiltinFormat_ABGR8888,
ImGui_ImplCPU_BuiltinFormat_RGB565,
ImGui_ImplCPU_BuiltinFormat_ARGB1555,
ImGui_ImplCPU_BuiltinFormat_RGBA4444,
ImGui_ImplCPU_BuiltinFormat_RGB888,
ImGui_ImplCPU_BuiltinFormat_BGR888
};
const char* builtin_names[] =
{
"RGBA8888",
"BGRA8888",
"ARGB8888",
"ABGR8888",
"RGB565",
"ARGB1555",
"RGBA4444",
"RGB888",
"BGR888"
};
const int builtin_tolerance[] =
{
0, 0, 0, 0, 8, 8, 17, 0, 0
};
const bool builtin_ignore_alpha[] =
{
false, false, false, false, true, false, false, true, true
};
ImGui_ImplCPU_NewFrame();
ImGui::NewFrame();
Validation_BuildMainScene(&image_texture);
ImGui::Render();
if (!Validation_RenderInto(&reference, ImGui::GetDrawData()))
return 1;
for (int i = 0; i < IM_ARRAYSIZE(builtin_formats); i++)
{
ValidationBuffer candidate;
Validation_InitBuffer(&candidate, 512, 320, ImGui_ImplCPU_GetBuiltinPixelFormat(builtin_formats[i]));
Validation_RenderInto(&candidate, ImGui::GetDrawData());
if (!Validation_CompareBuffers(builtin_names[i], reference, candidate, builtin_tolerance[i], builtin_tolerance[i], builtin_ignore_alpha[i]))
return 1;
}
ValidationBuffer palette_reference;
Validation_InitBuffer(&palette_reference, 512, 320, ImGui_ImplCPU_GetBuiltinPixelFormat(ImGui_ImplCPU_BuiltinFormat_RGBA8888));
ValidationBuffer palette_candidate;
Validation_InitIndexedBuffer(&palette_candidate, 512, 320);
ImGui_ImplCPU_NewFrame();
ImGui::NewFrame();
Validation_BuildPaletteScene();
ImGui::Render();
Validation_RenderInto(&palette_reference, ImGui::GetDrawData());
Validation_RenderInto(&palette_candidate, ImGui::GetDrawData());
if (!Validation_CompareBuffers("Indexed8", palette_reference, palette_candidate, 0, 0, false))
return 1;
ImGui_ImplCPU_Shutdown();
ImGui::DestroyContext();
printf("imgui_impl_cpu validation passed.\n");
return 0;
}