System designExplainer

RGB24 to RGB32 conversion: why the alpha byte matters for UVC images

RGB24 to RGB32 conversion adds one padding byte per pixel. Get buffer sizes, stride and channel order right, and pick the fastest of three ways to convert.

LumaSpec Vision10 min read Share on LinkedIn

On this page 8 sections
  1. 01RGB24 and RGB32: three bytes of color, one of padding
  2. 02Why your UVC camera delivers RGB24
  3. 03Buffer size and stride: the arithmetic
  4. 04Worked example: a 640 × 480 UVC RGB24 frame
  5. 05Three ways to convert, and when each wins
  6. 06What the fourth byte costs at camera size
  7. 07Checks before you write the converter
  8. 08Questions

A USB grabber hands you a packed RGB24 frame — three bytes per pixel — and your vision library refuses it because it wants four bytes per pixel. The missing byte is not a mystery format: RGB32 is RGB24 plus one padding byte after every pixel. This page gives you the buffer arithmetic, the stride rule that stops sheared images, the channel-order trap that swaps red and blue, and three ways to do the RGB24 to RGB32 conversion, including the one where you never convert at all. It is written for engineers wiring a UVC camera into C++ or a machine-vision library.

RGB24 and RGB32: three bytes of color, one of padding

RGB24 stores 3 bytes per pixel, one each for red, green and blue, and nothing else. RGB32 stores the same 3 color bytes plus a fourth byte after every pixel. That is the whole conversion: one byte per pixel added, 33% more memory, and no change to the colors themselves.

The fourth byte has two names. When a later step blends overlays it is alpha, where 255 conventionally means fully opaque. When nothing reads it, it is padding, and libraries still call the format RGB32 because each pixel is then one aligned 32-bit word: a single read or write per pixel instead of three unaligned byte accesses, and every row starts on a 4-byte boundary. Many pixel-buffer APIs require a buffer that starts at least 4-byte aligned for exactly this reason.

Bytes in a buffer are not pixels until they are decoded with the right layout; the image bytes vs pixel data article covers the same trap for compressed payloads.

Why your UVC camera delivers RGB24

A UVC camera offers the host a menu of media types, compressed and uncompressed, and whatever sits behind your grabber picks one. On Windows, the inbox USB video class driver supports cameras that capture the scene in YUV and transmit the pixel data over USB, with RGB produced by conversion in the driver or the application, as the driver's documentation describes. Most DirectShow-based paths serve uncompressed color as packed RGB24, because the bus has no use for a fourth byte: RGB32 would move 25% more bytes per frame for data that never leaves the camera.

So the format you receive is the framework's choice, not yours. If the menu looks wrong or a format is missing, the no matching media type fix for stream setup on Windows walks the negotiation, and the YUV 4:4:4 format greyed out case shows why some listed formats are unusable in practice. Once packed RGB24 lands in your buffer, the RGB24 to RGB32 conversion happens in your code, once per frame.

Buffer size and stride: the arithmetic

An RGB24 capture buffer is not always width × height × 3. Many capture paths pad each row so the next row starts on a 4-byte boundary, the same convention Windows bitmaps use. The row length including that padding is the stride:

bytes_rgb24_packed = width_px × height_px × 3 stride_bytes = ceil(width_px × 3 / 4) × 4 capture_bytes = stride_bytes × height_px bytes_rgb32 = width_px × height_px × 4

With 3 bytes per pixel, widths divisible by 4 need no padding at all; other widths hide 1 to 3 dead bytes at the end of every row. Read with the packed row length and every row after the first starts a little too early, so the image shears diagonally while its colors stay plausible. Read past the reported buffer and you are out of bounds, which is why the buffer size check before processing belongs in every capture loop.

Ask the grabber for its reported stride instead of deriving it, and keep that value in one variable.

Worked example: a 640 × 480 UVC RGB24 frame

The grabber reports a 640 × 480 RGB24 mode, the classic VGA color format a UVC path typically serves. Inputs: width 640 px and height 480 px from the camera's media type; 3 bytes per pixel because the format is RGB24; 4 bytes per pixel because the library's color image type is RGB32; 4-byte row alignment from the capture path's bitmap convention.

The rule in words: RGB32 is RGB24 plus one byte per pixel, and each source row is padded up to the next multiple of 4 bytes.

The arithmetic:

  • Pixels: 640 × 480 = 307,200 px
  • RGB24 packed: 307,200 px × 3 B/px = 921,600 B, exactly 900 KiB
  • Row check: 640 px × 3 B/px = 1,920 B = 480 × 4 B, so the rows are already aligned and no padding hides in this buffer
  • RGB32 target: 307,200 px × 4 B/px = 1,228,800 B
  • The conversion writes 1,228,800 - 921,600 = 307,200 B more than it reads, one byte per pixel

Sensitivity, with an illustrative 641-px-wide mode: rows of 641 × 3 = 1,923 B pad to a stride of 1,924 B, so the capture buffer holds 1,924 × 480 = 923,520 B against 923,040 B of packed pixels — 480 hidden bytes per frame.

The decision: compare the grabber's buffer size with width × height × 3. Equal means packed rows, and one row-wise expand with a stride of width × 3 works. Larger means padding, at most 3 bytes per row at 4-byte alignment, so use the reported stride. Either way, allocate exactly 1,228,800 B for the library image: hand it a 921,600 B block and the last row reads 307,200 B past the end.

Three ways to convert, and when each wins

Use the library's documented path first. Most machine-vision libraries can wrap a foreign buffer or convert between color layouts, taking the source pointer, width, height, stride and channel order as parameters. That is one tested call instead of your loop, and it is where the image type and the pixel format have to agree: when they do not, you get the doubled colour frame from a Mat type mismatch. The same discipline applies to any buffer you hand across — the raw camera buffer to processing-ready image walkthrough shows the wrap-then-process order, and the conversion hang from buffer ownership case shows why the destination must outlive the call.

Write the expand loop when you own both buffers. It is a byte shuffle, not math:

// Illustrative: expand one packed RGB24 row into RGB32 words.
#include <stdint.h>

uint8_t  src[6] = {10, 20, 30, 40, 50, 60};  // two pixels, B, G, R order
uint32_t dst[2] = {0, 0};                    // one 32-bit word per pixel

for (int p = 0; p < 2; ++p) {
    dst[p] = (uint32_t)src[3 * p]
           | ((uint32_t)src[3 * p + 1] << 8)
           | ((uint32_t)src[3 * p + 2] << 16)
           | 0xFF000000u;                    // fourth byte: opaque
}
// Test: dst[0] == 0xFF1E140A and dst[1] == 0xFF3A3228.

Run it row by row with the stride, never as one flat copy of the whole capture. It is the same one-pass expansion as the packed Bayer 12-bit to 16-bit conversion, and at 640 × 480 it is roughly a millisecond of work on one core.

Skip the conversion at the source. A UVC device rarely lists a 4-byte RGB format. An industry camera on USB3 Vision exposes GenICam pixel formats you select by name, so the buffer can arrive in the layout your library wants, or in raw sensor data you convert once, deterministically, on your side.

For a station you are designing now, change the source — it removes the problem class. For hardware already on the bench, use the library call, and fall back to your own loop only when the library cannot take your stride.

What the fourth byte costs at camera size

The padding byte scales with the pixel count. A 12-megapixel frame holds 12,000,000 px, so RGB24 fills 36,000,000 B and RGB32 fills 48,000,000 B: a third more RAM and bus traffic per frame, 12 MB of extra writes at every conversion.

Bandwidth sets the real limit at that size. RGB32 at 12 MP and 30 fps would move 48,000,000 × 30 = 1.44 GB/s, more than USB 3's roughly 500 MB/s of usable payload. That is why 12-megapixel streaming runs compressed or in raw sensor formats, and the RGB expansion happens once per processed frame on the host, not once per streamed frame.

Area scan cameras12 MP USB3 area scan camera, IMX226, 1/1.7" rolling shutter, 30 fps, 4000 × 3000 resolution (LS-CMR-0166)LS-CMR-0166
Sensor format
1/1.7"
Interface
USB3
Resolution
12 MP
Shutter
Rolling
$560.00View partBuild a system with it

The LS-CMR-0166 streams 4000 × 3000 px at 30 fps over USB3, and its pixel format comes from the camera's documented list rather than from whatever a UVC stack negotiated, so the packed-RGB24 surprise disappears from the design.

Vision controllersHikrobot MV-VC2140-128G40-1T VC2100 Vision Controller, N97, 8GB+128G SSD+ 1THDDMV-VC2140-128G40-1T
LAN ports
3-4 LAN ports
USB 3 ports
2 USB 3 ports
Light outputs
4 built-in channels
Memory
8 GB
$1,679.00View partBuild a system with it

The MV-VC2140-128G40-1T carries 8 GB of memory, 2 USB 3 ports and 4 built-in light channels. A ten-frame RGB32 ring at 12 megapixels takes 10 × 48,000,000 = 480,000,000 B, about 6% of that memory, leaving room for the operating system, your processing and a second camera stream.

The decision: if the inspection needs full-resolution color at high rate, budget the host for RGB32 rings and pick cameras whose documented pixel formats fit your library. If rate matters more than layout, stream raw and convert once.

Checks before you write the converter

  • Compare the grabber's buffer size with width × height × 3. Equal means packed rows; larger means padding of up to 3 bytes per row at 4-byte alignment. It decides the stride before a single pixel moves.
  • Read the stride from the capture API and store it in one variable. Hard-coded row lengths are how diagonal shear ships to production.
  • Fire a solid red test frame through the converter. If the memory order is blue-green-red and you assumed red-green-blue, the image looks normal but red and blue swap, which fails a red-green stop-light check.
  • Set the fourth byte to one fixed value and never read it back. It is data only when a blending step says so.
  • Assert the destination size equals width × height × 4 before the copy. At 640 × 480 that is 1,228,800 B, and a 921,600 B allocation overruns by 307,200 B on the last row.
  • Time the conversion at full rate. 100 fps of VGA RGB32 writes about 123 MB/s, so a slower loop has an overhead problem, not a bandwidth one.

If you are specifying hardware rather than patching a driver path, the camera finder filters area scan cameras by interface, resolution and shutter, and vision controllers are sized to hold deep image rings. Start the system configurator with your frame rate and pixel format; if a conversion still dominates the budget, describe the stream on the contact page. More acquisition plumbing lives on the systems topic page.

Questions

Should the converter write 255 or 0 into the fourth byte?

Write 255 if anything downstream may interpret alpha, because 255 means opaque and a 0 would make blended overlays invisible. Write 0 only when the byte is pure padding and no consumer reads it. Either way, pick one value, set it in the same loop that moves the color bytes, and note the choice where the buffer layout is documented in your code.

Which byte order does RGB24 use in memory?

Whatever the capture API documents, and it is often not red, green, blue. Windows-side bitmap conventions store each pixel as blue, green, red in memory, which is why the example packs B, G, R. Get it wrong and the image still looks plausible but red and blue swap, which a stop-light or cable-color check will catch the hard way.

Can I ask a UVC camera for RGB32 directly?

Usually not. Check the list of media types or pixel formats the device actually exposes rather than the spec sheet: UVC lists normally stop at YUV, MJPEG and packed RGB24, because a transmitted fourth byte would spend 25% more USB bandwidth on data the camera never sends. USB3 Vision cameras list standard GenICam formats by name, including raw sensor modes.

How do I keep the conversion from costing real time?

Preallocate the destination once and reuse it; allocating 1,228,800 B per VGA frame turns a trivial loop into an allocator benchmark. Convert outside the grab callback so acquisition and processing do not serialize, keep both buffers warm in cache, and measure: at 640 × 480 the destination write is small enough that a row-wise loop should never be the bottleneck.

What does a wrong RGB24 to RGB32 copy look like in the image?

Three distinct pictures. Diagonal color smearing means the reader takes 4 bytes per pixel from a 3-byte stream. A clean image shifted a few pixels per row means the stride was taken as the packed row length while the buffer is padded. Colors doubled or tinted with correct geometry means the image type and the pixel format disagree in the library call.