// 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 #include #include struct ValidationBuffer { ImVector Storage; ImVector 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* 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 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; }