Skip to content

Create and update textures from BGRA, RGB, BGR and greyscale pixels with row padding - #460

Merged
matt-edmondson merged 1 commit into
mainfrom
claude/issue-458-e0dcxq
Sep 24, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
claude/issue-458-e0dcxq

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Closes #458. Follow-up to #459.

CreateTexture/UpdateTexture only accepted tightly packed RGBA8, so anything from OpenCV or SkiaSharp had to be swizzled and repacked by the caller first. This PR adds overloads that take the pixels as they sit in memory.

API

public enum PixelLayout { Rgba8, Bgra8, Rgb8, Bgr8, Gray8 }   // ktsu.ImGui.App.Images

ImGuiApp.CreateTexture(ReadOnlySpan<byte> pixels, int width, int height, PixelLayout layout, int rowStride = 0);
ImGuiApp.UpdateTexture(ImGuiAppTextureInfo texture, ReadOnlySpan<byte> pixels, int width, int height, PixelLayout layout, int rowStride = 0);
  • PixelLayout is named for byte order in memory. Bgra8 is SkiaSharp's Bgra8888, OpenCV's CV_8UC4 and a Windows DIB. Bgr8 is OpenCV's default three-channel order. Gray8 covers CV_8UC1. Three-channel and grey sources get opaque alpha.
  • rowStride is bytes per row, for example Mat.Step() or SKBitmap.RowBytes. 0 means tightly packed. The buffer only has to reach the end of the last row's pixels, so an OpenCV region of interest can be passed without copying it out first.
  • Why PixelLayout and not PixelFormat: ImGuiApp.cs and most Silk.NET consumers also import Silk.NET.OpenGL.PixelFormat, so PixelFormat would be ambiguous.
  • The existing RGBA8 overloads are unchanged.

How it works

  • Images/PixelConverter validates the source geometry and converts rows into a pooled RGBA8 buffer on the calling thread. Tightly packed Rgba8 skips conversion and goes through the existing zero-copy path.
  • Invoker.Invoke waits for its body to finish, so the pooled buffer can be handed to the window thread as is. A converted update makes one pass over the pixels and no further copy, on any thread.
  • The update path is now one private core, UpdateTextureFromBuffer. It does the in-place attempt and the recreate fallback inside a single Invoke, and both public overloads share it. Behaviour of the existing overload is unchanged, and its tests still pass as they were.
  • No renderer backend interface changes. Conversion happens before the backend is involved, so OpenGL, Metal and the software renderer need nothing new.

Docs

ImGui.App/README.md now covers the layout overloads and the straight-alpha expectation. Its OpenCvSharp and SkiaSharp snippets pass Mat/SKBitmap memory directly instead of converting first. The API tables in both READMEs and the key-file list in CLAUDE.md are updated.

Tests

  • PixelConverterTests (13): each layout, row padding, an unpadded last row, and validation of stride, length, dimensions and layout.
  • ImGuiAppTests (7 new): converted create, in-place converted update, Rgba8 pass-through, converted recreate on resize, worker-thread converted update reaching the backend on the invoker thread, and argument errors.
  • ImGui.App.Tests 471/471 and ImGui.App.Testing.Tests 95/95 pass. The full solution builds with 0 warnings in Release.

Not in this PR

Sub-rectangle updates and a nearest/linear filter option would need new IRendererBackend members, implemented across OpenGL, Metal and the software renderer. Left out until there's a concrete need.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PkBLq2ZjJSRADscJJf5A5D


Generated by Claude Code

…pixels with row padding

Adds PixelLayout (Rgba8, Bgra8, Rgb8, Bgr8, Gray8) and overloads
CreateTexture(pixels, width, height, layout, rowStride = 0) and
UpdateTexture(texture, pixels, width, height, layout, rowStride = 0), so an
OpenCV Mat, a SkiaSharp bitmap or a Windows DIB can be uploaded as it sits
in memory instead of being swizzled and repacked by the caller.

Conversion to RGBA8 happens on the calling thread into a pooled buffer.
Because Invoker.Invoke waits for its body, that buffer is handed to the
window thread as is, so a converted update makes one pass over the pixels
and no further copy on any thread. Tightly packed RGBA8 skips conversion.

The update path is restructured around one private core that performs the
in-place attempt and the recreate fallback inside a single Invoke, which
both public overloads share.

Refs #458

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PkBLq2ZjJSRADscJJf5A5D
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Provide helper APIs for updating textures from image buffers

2 participants