Thursday, February 26, 2026
Rendering graphics on Linux with SDL2
Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D. It is used by video playback software, emulators, and popular games including Valve's award winning catalog and many Humble Bundle games.
SDL officially supports Windows, macOS, Linux, iOS, and Android. Support for other platforms may be found in the source code.
Developing on Ubuntu/Debian
sudo apt update
sudo apt install build-essential pkg-config
sudo apt install libsdl2-dev
## common addons
sudo apt install libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev libsdl2-net-dev
## OpenGL
sudo apt install libgl1-mesa-dev
Basic example
//main.c
#include <SDL2/SDL.h> // SDL main header (types, functions, constants)
#include <stdbool.h> // for bool, true, false
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
int main(int argc, char* argv[]) {
// Initialize SDL video subsystem. SDL_Init returns 0 on success.
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
SDL_Log("Unable to initialize SDL: %s", SDL_GetError()); // log SDL error
return 1;
}
// Create an SDL window centered on the screen with the defined size.
SDL_Window* window = SDL_CreateWindow(
"Blank SDL2 Window", // window title
SDL_WINDOWPOS_CENTERED, // x position
SDL_WINDOWPOS_CENTERED, // y position
WINDOW_WIDTH, // width
WINDOW_HEIGHT, // height
SDL_WINDOW_SHOWN // window flags (visible)
);
// Check window creation success.
if (!window) {
SDL_Log("Failed to create window: %s", SDL_GetError());
SDL_Quit(); // clean up SDL subsystems
return 1;
}
// Create a renderer for the window. -1 chooses the first available driver.
// SDL_RENDERER_ACCELERATED requests hardware acceleration.
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!renderer) {
SDL_Log("Failed to create renderer: %s", SDL_GetError());
SDL_DestroyWindow(window); // destroy window before quitting
SDL_Quit();
return 1;
}
bool running = true; // main loop flag
SDL_Event event; // event container
// Main loop: handle events, clear and present the screen, throttle frame rate.
while (running) {
// Process all pending events.
while (SDL_PollEvent(&event)) {
// If window close or any key pressed, exit loop.
if (event.type == SDL_QUIT ||
(event.type == SDL_KEYDOWN)) {
running = false;
}
}
// Set draw color to opaque black (r,g,b,a) and clear the render target.
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
// Present the backbuffer to the screen.
SDL_RenderPresent(renderer);
// Simple frame cap: delay ~16 ms -> ~60 FPS.
SDL_Delay(16);
}
// Clean up resources in reverse creation order.
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
// Print the name of the video driver SDL used (useful for debugging).
printf("Video driver: %s\n", SDL_GetCurrentVideoDriver());
return 0;
}
Build command
gcc -o main main.c -lSDL2
Draw rectangle
// gcc -o main main.c -lSDL2
#include <SDL2/SDL.h>
#include <stdbool.h>
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
// Helper function: draw a filled rectangle using top-left coordinates
void draw_rect(SDL_Renderer* renderer, SDL_Color color, int x, int y, int w, int h) {
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
SDL_Rect rect = { x, y, w, h };
SDL_RenderFillRect(renderer, &rect);
}
// Draw 1px outline + X connecting all corners
void draw_rect_outline(SDL_Renderer* renderer, SDL_Color color, int x, int y, int w, int h) {
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
// Outline: draw 4 lines (top, bottom, left, right) — 1px thick
// Top edge
SDL_RenderDrawLine(renderer, x, y, x + w - 1, y);
// Bottom edge
SDL_RenderDrawLine(renderer, x, y + h - 1, x + w - 1, y + h - 1);
// Left edge
SDL_RenderDrawLine(renderer, x, y, x, y + h - 1);
// Right edge
SDL_RenderDrawLine(renderer, x + w - 1, y, x + w - 1, y + h - 1);
// Diagonal "X": connect opposite corners
SDL_RenderDrawLine(renderer, x, y, x + w - 1, y + h - 1); // top-left to bottom-right
SDL_RenderDrawLine(renderer, x + w - 1, y, x, y + h - 1); // top-right to bottom-left
}
int main(int argc, char* argv[]) {
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
SDL_Log("Unable to initialize SDL: %s", SDL_GetError());
return 1;
}
SDL_Window* window = SDL_CreateWindow(
"Rectangle SDL2 Window",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
WINDOW_WIDTH,
WINDOW_HEIGHT,
SDL_WINDOW_SHOWN
);
if (!window) {
SDL_Log("Failed to create window: %s", SDL_GetError());
SDL_Quit();
return 1;
}
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!renderer) {
SDL_Log("Failed to create renderer: %s", SDL_GetError());
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
bool running = true;
SDL_Event event;
while (running) {
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT || event.type == SDL_KEYDOWN) {
running = false;
}
}
// Clear screen to black
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
// Example: draw a red rectangle at (100, 50) with size 200x100
SDL_Color red = {255, 0, 0, 255};
draw_rect(renderer, red, 100, 50, 200, 100);
// Example: draw a semi-transparent green square
SDL_Color green = {0, 255, 0, 128};
draw_rect(renderer, green, 300, 200, 80, 80);
SDL_RenderPresent(renderer);
SDL_Delay(16); // ~60 FPS
}
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
Saturday, January 10, 2026
Visualize C code
CFLOW
GNU cflow analyzes a collection of C source files and prints a graph, charting control flow within the program.
GRAPHVIZ
Graphviz is open source graph visualization software. Graph visualization is a way of representing structural information as diagrams of abstract graphs and networks. It has important applications in networking, bioinformatics, software engineering, database and web design, machine learning, and in visual interfaces for other technical domains.
Ubuntu/debian install
sudo apt-get install cflow graphviz
Generate "callgraph.dot" (text file that image renderer will use)
#run this in project dir
cflow $(find . -name "*.c" -o -name "*.h") --format=dot > callgraph.dot
Render to PNG
# fdp (force-directed placement)
fdp -Tpng callgraph.dot -o callgraph.png
# circo (circular layout)
circo -Tpng callgraph.dot -o callgraph.png
# neato
neato -Tpng callgraph.dot -o callgraph.png
Result
To get SVG output use:
fdp -Tsvg callgraph.dot -o callgraph.svg
circo -Tsvg callgraph.dot -o callgraph.svg
neato -Tsvg callgraph.dot -o callgraph.svg
Friday, January 9, 2026
Compile C project to single text file for AI analysis.
Simple script that collects all your .h/.c files and adds project path before collected text - and ads them to single text file that you can then give to AI for analysis. Add script to you project root.
#!/usr/bin/env bash
# combine_sources.sh
# Creates a single text file (combined.txt) that contains the contents of every
# .c and .h file in the project, prefixed by a comment showing its path.
# Exit on any error
set -e
# Output file placed in the directory where this script lives (project root)
output="$(dirname "$0")/combined.txt"
> "$output" # truncate/create the file
# Find all .c and .h files under the project root (excluding the script itself)
find "$(dirname "$0")" -type f \( -name "*.c" -o -name "*.h" \) ! -name "$(basename "$0")" |
while IFS= read -r file; do
# Write a header comment with the relative path
echo "# $file" >> "$output"
# Append the file's contents
cat "$file" >> "$output"
# Add a blank line between files for readability
echo "" >> "$output"
done
echo "All source files have been combined into $output"
Saturday, January 3, 2026
stdint cheatsheet
| Category |
Type Name |
Description |
| Exact-width | int8_t | 8-bit signed integer |
uint8_t | 8-bit unsigned integer |
int16_t | 16-bit signed integer |
uint16_t | 16-bit unsigned integer |
int32_t | 32-bit signed integer |
uint32_t | 32-bit unsigned integer |
int64_t | 64-bit signed integer |
uint64_t | 64-bit unsigned integer |
| Minimum-width | int_least8_t | Signed int with at least 8 bits |
uint_least8_t | Unsigned int with at least 8 bits |
int_least16_t | Signed int with at least 16 bits |
uint_least16_t | Unsigned int with at least 16 bits |
int_least32_t | Signed int with at least 32 bits |
uint_least32_t | Unsigned int with at least 32 bits |
int_least64_t | Signed int with at least 64 bits |
uint_least64_t | Unsigned int with at least 64 bits |
| Fastest minimum-width | int_fast8_t | Fastest signed int with at least 8 bits |
uint_fast8_t | Fastest unsigned int with at least 8 bits |
int_fast16_t | Fastest signed int with at least 16 bits |
uint_fast16_t | Fastest unsigned int with at least 16 bits |
int_fast32_t | Fastest signed int with at least 32 bits |
uint_fast32_t | Fastest unsigned int with at least 32 bits |
int_fast64_t | Fastest signed int with at least 64 bits |
uint_fast64_t | Fastest unsigned int with at least 64 bits |
| Pointer-holding | intptr_t | Signed integer capable of holding a void pointer |
uintptr_t | Unsigned integer capable of holding a void pointer |
| Greatest-width | intmax_t | Maximum-width signed integer type |
| uintmax_t | Maximum-width unsigned integer type |
Friday, January 2, 2026
What are those "ARG1 | ARG2" wierd arguments in C?
In C, a bit-mask argument refers to passing an integer value to a function where individual bits (or groups of bits) in that integer represent boolean flags or options. This technique allows you to combine multiple settings into a single argument using bitwise operations.
How It Works
Each option is typically defined as a power of two (i.e., a number with only one bit set), so that each option corresponds to a unique bit position.
#define OPTION_A (1 << 0) // binary: 0001 (1)
#define OPTION_B (1 << 1) // binary: 0010 (2)
#define OPTION_C (1 << 2) // binary: 0100 (4)
#define OPTION_D (1 << 3) // binary: 1000 (8)
Combining Options
You combine options using the bitwise OR (|) operator:
int flags = OPTION_A | OPTION_C; // binary: 0101 (5)
This means both OPTION_A and OPTION_C are enabled.
Checking Options
Inside the function, you test whether a particular option is set using the bitwise AND (&) operator:
#include <stdio.h>
#define VERBOSE (1 << 0) // 1
#define UPPERCASE (1 << 1) // 2
#define DRY_RUN (1 << 2) // 4
void process_text(const char* text, int flags) {
if (flags & DRY_RUN) {
printf("[DRY RUN] Would process: %s\n", text);
return;
}
char buffer[100];
if (flags & UPPERCASE) {
// Simple uppercase conversion (for demo)
for (int i = 0; text[i]; i++) {
buffer[i] = (text[i] >= 'a' && text[i] <= 'z') ? text[i] - 32 : text[i];
}
buffer[strlen(text)] = '\0';
text = buffer;
}
if (flags & VERBOSE) {
printf("Processing text: %s\n", text);
} else {
printf("%s\n", text);
}
}
int main() {
process_text("hello", VERBOSE | UPPERCASE); // Enables both options
process_text("world", DRY_RUN);
return 0;
}
How many flags can you stack?
It depends on the size (in bits) of the integer type you use:
| Type |
Bits |
Max Distinct Flags |
| unsigned char / uint8_t |
8 |
8 |
| unsigned short / uint16_t |
16 |
16 |
| unsigned int / uint32_t |
32 |
32 |
| unsigned long long / uint64_t |
64 |
64 |
Another 32bit example
#include <stdint.h>
#include <stdio.h>
// Mouse buttons
#define LMOUSEBUTTON (1U << 0) // Left mouse button
#define RMOUSEBUTTON (1U << 1) // Right mouse button
#define MMOUSEBUTTON (1U << 2) // Middle mouse button
// Keyboard modifier keys
#define SHIFTKEY (1U << 3)
#define CTRLKEY (1U << 4)
#define ALTKEY (1U << 5)
#define SUPERKEY (1U << 6) // Windows / Command key
// Movement keys (WASD + arrows)
#define W_KEY (1U << 7)
#define A_KEY (1U << 8)
#define S_KEY (1U << 9)
#define D_KEY (1U << 10)
#define UP_ARROW (1U << 11)
#define DOWN_ARROW (1U << 12)
#define LEFT_ARROW (1U << 13)
#define RIGHT_ARROW (1U << 14)
// Action keys
#define SPACE_KEY (1U << 15)
#define ENTER_KEY (1U << 16)
#define ESC_KEY (1U << 17)
#define TAB_KEY (1U << 18)
// Common game keys
#define E_KEY (1U << 19)
#define Q_KEY (1U << 20)
#define R_KEY (1U << 21)
#define F_KEY (1U << 22)
#define MOUSE_WHEEL_UP (1U << 23)
#define MOUSE_WHEEL_DOWN (1U << 24)
// Extra (reserved for future use or custom actions)
#define CUSTOM_1 (1U << 25)
#define CUSTOM_2 (1U << 26)
#define CUSTOM_3 (1U << 27)
#define CUSTOM_4 (1U << 28)
// You can go up to (1U << 31) safely on 32-bit unsigned
// Optional: Combined masks
#define MODIFIERS_MASK (SHIFTKEY | CTRLKEY | ALTKEY | SUPERKEY)
#define MOVEMENT_MASK (W_KEY | A_KEY | S_KEY | D_KEY | \
UP_ARROW | DOWN_ARROW | LEFT_ARROW | RIGHT_ARROW)
// Helper function to check if a specific key/flag is pressed
static inline int is_key_pressed(uint32_t input_state, uint32_t key_flag)
{
return (input_state & key_flag) != 0;
}
// Example: Check if running (W + SHIFT)
static inline int is_running(uint32_t input_state)
{
return is_key_pressed(input_state, W_KEY) && is_key_pressed(input_state, SHIFTKEY);
}
int main(void)
{
// Simulate an input state: Left mouse + Shift + W pressed
uint32_t state = LMOUSEBUTTON | SHIFTKEY | W_KEY;
if (is_key_pressed(state, LMOUSEBUTTON))
{
printf("Left mouse is pressed\n");
}
if (is_running(state))
{
printf("Player is running forward!\n");
}
// Additional example checks
if (is_key_pressed(state, SHIFTKEY))
{
printf("Shift key is held\n");
}
return 0;
}
Notes:
* All values use 1U (unsigned) to avoid undefined behavior with shifts ≥16 on some systems.
* Stays safely within 32 bits (bits 0–28 used above; bits 29–31 are free).
* You can add more (e.g., 1U << 29, 1U << 30, 1U << 31) if needed.
* Avoid 1 << 31 without the U suffix — that’s a signed overflow!
Wednesday, December 31, 2025
Color in programming
24‑bit color uses three 8‑bit channels—red, green, and blue. Allowing 256 intensity levels per channel. This yields 16,777,216 possible colors, often called “true color.”
/* 24‑bit RGB (8 bits per channel, no alpha) */
typedef struct {
unsigned char r; // red 0‑255
unsigned char g; // green 0‑255
unsigned char b; // blue 0‑255
} rgb24_t;
rgb24_t sky = {135, 206, 235}; // light sky blue
32‑bit color adds an extra 8‑bit channel to the 24‑bit RGB format. The fourth channel is typically an alpha (transparency) channel, giving each pixel 256 levels of opacity.
/* 32‑bit RGBA (8 bits per channel, includes alpha) */
typedef struct {
unsigned char r; // red
unsigned char g; // green
unsigned char b; // blue
unsigned char a; // alpha (0 = transparent, 255 = opaque)
} rgba32_t;
rgba32_t pixel = {255, 0, 0, 128}; // semi‑transparent red
OpenGL supports color components with more than 8 bits (256 levels) per channel. Modern OpenGL provides several higher‑precision formats:
| Bits per component |
Typical internal format |
Typical use case |
| 10 bits |
GL_RGB10_A2 |
HDR rendering, packed 10‑bit RGB + 2‑bit alpha |
| 11 bits |
GL_R11F_G11F_B10F |
HDR with floating‑point storage |
| 16 bits (fixed) |
GL_RGB16, GL_RGBA16 |
High‑precision color buffers, scientific visualization |
| 16 bits (float) |
GL_R16F, GL_RG16F, GL_RGB16F, GL_RGBA16F |
HDR rendering, tone‑mapping pipelines |
| 32 bits (float) |
GL_R32F, GL_RG32F, GL_RGB32F, GL_RGBA32F |
Full‑precision HDR, linear color pipelines |
| 64 bits (float) |
GL_R64F, GL_RG64F, GL_RGB64F, GL_RGBA64F |
Very high dynamic range, scientific computing |
Photoshop (most popular graphical editor) supports:
| OpenGL format (bits per component) |
Photoshop equivalent |
10 bits – GL_RGB10_A2 |
Not available; Photoshop does not expose a 10‑bit packed RGB + 2‑bit alpha mode. |
11 bits – GL_R11F_G11F_B10F |
Not available; Photoshop has no 11‑bit floating‑point channel layout. |
16 bits (fixed) – GL_RGB16, GL_RGBA16 |
16‑bit/channel (per‑channel integer) – Photoshop’s “16 Bits/Channel” mode matches these fixed‑point formats. |
16 bits (float) – GL_R16F, GL_RG16F, GL_RGB16F, GL_RGBA16F |
16‑bit/channel (per‑channel floating‑point) – Photoshop’s “16 Bits/Channel (Float)” mode (available in the 16‑bit workflow) corresponds to these floating‑point formats. |
32 bits (float) – GL_R32F, GL_RG32F, GL_RGB32F, GL_RGBA32F |
32‑bit/channel (per‑channel floating‑point) – Photoshop’s “32 Bits/Channel (Float)” mode (used for HDR/EXR files) matches these formats. |
64 bits (float) – GL_R64F, GL_RG64F, GL_RGB64F, GL_RGBA64F |
Not available; Photoshop does not provide a 64‑bit per‑channel mode. |
Modern AMD Ryzen systems (including those with integrated Radeon Graphics, e.g., Ryzen 5000/7000/8000/9000 series APUs, and discrete Radeon GPUs like RX 6000/7000/8000 series) fully support 32-bit floating-point formats such as GL_R32F, GL_RG32F, GL_RGB32F, GL_RGBA32F for textures, renderbuffers, and framebuffers in OpenGL.
These formats are part of the core OpenGL specification since version 3.0 (via ARB_color_buffer_float and related extensions), and AMD's drivers have supported them reliably for many years across Radeon hardware. They are widely used for HDR rendering, deferred shading, compute shaders, and other high-precision pipelines without issues on modern AMD GPUs.
However, 64-bit floating-point formats (double-precision) such as GL_R64F, GL_RG64F, GL_RGB64F, GL_RGBA64F are not supported on modern AMD Ryzen integrated graphics or consumer-grade Radeon GPUs (RDNA/RDNA2/RDNA3/RDNA4 architectures).
These formats require the ARB_gpu_shader_fp64 extension (for double-precision computations) and hardware-level support for double-precision textures/renderbuffers. AMD's consumer GPUs, including Ryzen APUs, lack native double-precision floating-point units at the required performance/level for OpenGL exposure. While some older professional FirePro cards or specific extensions provided limited/emulated support in the past, current Ryzen systems do not expose these 64-bit float formats. Attempting to use them will result in errors (e.g., framebuffer incomplete or unsupported internal format).
In summary:
| 32-bit float formats |
Fully supported and recommended for high-dynamic-range workflows. |
| 64-bit float formats |
Not available; use 32-bit floats instead, or switch to Vulkan/DirectX for potential alternatives on AMD hardware. |
Display Limitations
Physical panel capabilities: Virtually all consumer monitors (including HDR-capable ones) use panels with 8-bit or 10-bit per channel native depth (some premium professional displays reach 12-bit, but rarely higher). No consumer display panel natively supports 32-bit or 64-bit floating-point per channel. The panel physically modulates light in fixed integer steps (typically 256–1024 levels per channel for SDR, up to ~1024 for HDR10).
Output signal limitations: Modern connections (HDMI 2.1, DisplayPort 1.4+) and GPUs support at most 12-bit integer per channel output for HDR (e.g., via HDR10 or Dolby Vision signaling). OpenGL's default framebuffer or swapchain typically outputs in 8-bit or 10-bit integer formats. Even when using floating-point pixel formats for the window surface (via extensions like WGL_ARB_pixel_format_float on Windows), the actual signal sent to the monitor converts to integer bits—usually 10-bit for HDR.
Tone mapping requirement: 32-bit float formats allow unbounded HDR values (>1.0, even thousands for bright lights) with high precision. However, monitors cannot display values outside their brightness range (e.g., typical HDR monitors peak at 400–2000 nits). You must apply tone mapping (and often gamma/sRGB conversion) in a shader before presenting to the default framebuffer. Without this, bright areas clamp or appear blown out, and subtle gradients may band due to final integer output.
HDR-specific constraints: True HDR monitors require 10-bit signaling (often with scRGB or HDR10 metadata). Floating-point rendering excels internally for HDR pipelines (avoiding clamping during lighting calculations), but the final output to an HDR display still reduces to 10-bit integer per channel after tone mapping.
File support
| File Format |
Channels supported |
32‑bit float per channel? |
Typical use case |
Notes / Limitations |
| OpenEXR (.exr) |
R, RG, RGB, RGBA, multi‑channel |
Yes (full support) |
HDR rendering, VFX, film production, photography |
Industry standard for HDR. Supports 16‑bit half‑float & 32‑bit float. Very flexible (layers, metadata). |
| Radiance HDR (.hdr) |
RGB only |
Yes |
HDR photography, environment maps |
Older format, simpler than EXR. Only RGB (no alpha), no metadata layers. |
| TIFF (.tif/.tiff) |
R, RG, RGB, RGBA, multi‑channel |
Yes (with Float32) |
Professional photography, scientific imaging |
Must specify 32‑bit float samples. Not all software reads float TIFF correctly. |
| PNG |
RGB, RGBA |
No (only 8‑bit or 16‑bit integer) |
General web & graphics |
PNG does not support floating‑point. |
| JPEG / JPEG 2000 |
RGB, grayscale |
No |
Photos, web |
Lossy or integer‑only. No float support. |
| DDS (.dds) |
R, RG, RGB, RGBA |
Yes (with R32F, RG32F, RGB32F, RGBA32F) |
Real‑time graphics, games |
DirectDraw Surface. Used by DirectX/OpenGL apps. Good for textures. |
| KTX / KTX2 (.ktx/.ktx2) |
R, RG, RGB, RGBA |
Yes |
Textures for OpenGL/Vulkan |
Modern container for GPU textures. Supports float formats natively. |
| PFM (.pfm) |
RGB, grayscale |
Yes |
Simple HDR images, academic/research |
Very basic format, no compression, no alpha, no metadata. |
| ILM OpenEXR (deep) |
Multi‑layer / deep RGBA |
Yes |
Advanced VFX (deep compositing) |
Extension of OpenEXR for deep images (multiple samples per pixel). |
Neither WebP nor BMP supports 32-bit floating-point per channel (i.e., true GL_R32F, GL_RG32F, GL_RGB32F, or GL_RGBA32F formats with IEEE 754 single-precision floats)
GIMP (current stable versions, e.g., 2.10/3.0 series as of late 2025) and Krita (current versions, e.g., 5.2+) fully support 32-bit floating-point per channel workflows. This corresponds exactly to OpenGL's GL_R32F, GL_RG32F, GL_RGB32F, and GL_RGBA32F formats: single-precision IEEE 754 floating-point data per channel, allowing unbounded HDR values (above 1.0 or even negative) with high dynamic range and precision.
| Software |
Key 32‑bit Float Features |
| GIMP |
- Convert images to 32‑bit floating point via Image → Precision → 32‑bit floating point (Linear or Perceptual gamma).
- Internal processing always 32‑bit float → true HDR editing (exposure, curves, etc. without clipping).
- Import/Export 32‑bit float through OpenEXR (.exr) and TIFF (Float32).
- Ideal for HDR photography, scientific imaging, or high‑precision intermediates (higher RAM usage).
|
| Krita |
- Native 32‑bit float per channel (set when creating or converting documents).
- ~23‑24 bits effective precision in 0‑1 range with extended dynamic range for HDR/scene‑referenced work.
- Optimized for digital painting in HDR (OCIO color management, unbounded blending, values > 1.0).
- Robust OpenEXR (.exr) import/export, including multilayer support – great for VFX/matte‑painting pipelines.
- Also supports 16‑bit half‑float; 32‑bit float offers maximum precision at higher RAM/performance cost.
|
C examples
Here are C language structs that directly match the memory layout and size of OpenGL's 32-bit floating-point texture/internal formats (GL_R32F, GL_RG32F, GL_RGB32F, GL_RGBA32F), along with simple usage examples.
#include <stdint.h>
#include <stdio.h>
// 1 channel: GL_R32F → one 32-bit float
typedef struct {
float r;
} Float1;
// 2 channels: GL_RG32F → two 32-bit floats
typedef struct {
float r;
float g;
} Float2;
// 3 channels: GL_RGB32F → three 32-bit floats
typedef struct {
float r;
float g;
float b;
} Float3;
// 4 channels: GL_RGBA32F → four 32-bit floats
typedef struct {
float r;
float g;
float b;
float a;
} Float4;
Size and Alignment Check
printf("Size of Float1 (GL_R32F): %zu bytes\n", sizeof(Float1)); // 4
printf("Size of Float2 (GL_RG32F): %zu bytes\n", sizeof(Float2)); // 8
printf("Size of Float3 (GL_RGB32F): %zu bytes\n", sizeof(Float3)); // 12
printf("Size of Float4 (GL_RGBA32F): %zu bytes\n", sizeof(Float4)); // 16
Usage Example
// Example 1: Single red channel value (e.g., heightmap or distance field)
Float1 height = { .r = 1234.567f };
printf("Height value: %.3f\n", height.r);
// Example 2: 2D position or normal (x, y)
Float2 position = { .r = -5.5f, .g = 10.0f };
printf("Position: (%.2f, %.2f)\n", position.r, position.g);
// Example 3: HDR color without alpha
Float3 hdr_color = { .r = 10.5f, .g = 2.3f, .b = 0.8f }; // bright sunlight-like
printf("HDR Color: r=%.2f g=%.2f b=%.2f\n",
hdr_color.r, hdr_color.g, hdr_color.b);
// Example 4: Full HDR color with alpha
Float4 hdr_rgba = { .r = 15.0f, .g = 8.0f, .b = 3.0f, .a = 1.0f };
printf("HDR RGBA: r=%.2f g=%.2f b=%.2f a=%.2f\n",
hdr_rgba.r, hdr_rgba.g, hdr_rgba.b, hdr_rgba.a);
// Example 5: Array of pixels (e.g., for uploading to OpenGL texture)
Float4 pixels[4] = {
{ 1.0f, 0.0f, 0.0f, 1.0f }, // red
{ 0.0f, 1.0f, 0.0f, 1.0f }, // green
{ 0.0f, 0.0f, 1.0f, 1.0f }, // blue
{ 10.0f, 10.0f, 10.0f, 1.0f } // very bright white (HDR)
};
// In real OpenGL code, you would upload like this:
// glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 2, 2, 0, GL_RGBA, GL_FLOAT, pixels);
SDL2 Example
// gcc -o float_texture main.c -lSDL2 -lGLEW -lGL
#include <SDL2/SDL.h>
#include <GL/glew.h> // or glad.h if you prefer GLAD
#include <stdio.h>
#include <stdlib.h>
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
#define TEX_WIDTH 4
#define TEX_HEIGHT 4
// Simple vertex shader
const char* vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
void main() {
gl_Position = vec4(aPos, 0.0, 1.0);
TexCoord = aTexCoord;
}
)";
// Fragment shader with basic Reinhard tone mapping
const char* fragmentShaderSource = R"(
#version 330 core
in vec2 TexCoord;
out vec4 FragColor;
uniform sampler2D hdrTexture;
void main() {
vec4 color = texture(hdrTexture, TexCoord);
// Simple Reinhard tone mapping: color / (color + 1.0)
vec3 toned = color.rgb / (color.rgb + vec3(1.0));
// Gamma correction
toned = pow(toned, vec3(1.0/2.2));
FragColor = vec4(toned, color.a);
}
)";
GLuint compileShader(GLenum type, const char* source) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, NULL);
glCompileShader(shader);
GLint success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
char infoLog[512];
glGetShaderInfoLog(shader, 512, NULL, infoLog);
printf("Shader compilation failed:\n%s\n", infoLog);
exit(1);
}
return shader;
}
int main(int argc, char* argv[]) {
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("SDL_Init Error: %s\n", SDL_GetError());
return 1;
}
// Request OpenGL 3.3 core profile
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
// Important: Request floating-point framebuffer support (optional but helpful)
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
SDL_Window* window = SDL_CreateWindow("SDL2 + OpenGL 32-bit Float Texture",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
WINDOW_WIDTH, WINDOW_HEIGHT,
SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
if (!window) {
printf("Window creation failed: %s\n", SDL_GetError());
SDL_Quit();
return 1;
}
SDL_GLContext context = SDL_GL_CreateContext(window);
if (!context) {
printf("OpenGL context creation failed: %s\n", SDL_GetError());
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
// Initialize GLEW (or GLAD)
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK) {
printf("GLEW init failed\n");
return 1;
}
// Compile shaders
GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
GLint success;
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
char infoLog[512];
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
printf("Program linking failed:\n%s\n", infoLog);
return 1;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// Fullscreen quad vertices (position + texcoord)
float quadVertices[] = {
// positions // tex coords
-1.0f, 1.0f, 0.0f, 1.0f,
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 1.0f,
1.0f, -1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f
};
GLuint VAO, VBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
// Create GL_RGBA32F texture (4x4 for demo)
GLuint hdrTexture;
glGenTextures(1, &hdrTexture);
glBindTexture(GL_TEXTURE_2D, hdrTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, TEX_WIDTH, TEX_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Fill with HDR data (values > 1.0!)
float pixels[TEX_WIDTH * TEX_HEIGHT * 4] = {
5.0f, 0.1f, 0.1f, 1.0f, // bright red
0.1f, 4.0f, 0.1f, 1.0f, // bright green
0.1f, 0.1f, 8.0f, 1.0f, // bright blue
10.0f, 10.0f, 10.0f, 1.0f, // very bright white
0.5f, 0.5f, 0.5f, 1.0f, // gray
2.0f, 1.0f, 0.5f, 0.8f, // orange-ish with alpha
0.0f, 0.0f, 0.0f, 0.0f, // transparent black
1.0f, 1.0f, 1.0f, 1.0f, // normal white
// ... repeat or extend as needed
3.0f, 2.0f, 1.0f, 1.0f,
1.0f, 2.0f, 3.0f, 1.0f,
2.0f, 3.0f, 1.0f, 1.0f,
15.0f, 8.0f, 4.0f, 1.0f, // extremely bright
0.2f, 0.3f, 0.8f, 1.0f,
0.8f, 0.2f, 0.4f, 1.0f,
0.4f, 0.6f, 0.9f, 1.0f,
20.0f, 20.0f, 20.0f, 1.0f // blinding white
};
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, TEX_WIDTH, TEX_HEIGHT, GL_RGBA, GL_FLOAT, pixels);
// You can also create other formats:
// GLuint r32f_tex; glTexImage2D(..., GL_R32F, ...);
// GLuint rg32f_tex; glTexImage2D(..., GL_RG32F, ...);
// GLuint rgb32f_tex; glTexImage2D(..., GL_RGB32F, ...);
glUseProgram(shaderProgram);
glUniform1i(glGetUniformLocation(shaderProgram, "hdrTexture"), 0);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
int running = 1;
SDL_Event event;
while (running) {
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) running = 0;
}
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, hdrTexture);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
SDL_GL_SwapWindow(window);
}
// Cleanup
glDeleteTextures(1, &hdrTexture);
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProgram);
SDL_GL_DeleteContext(context);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
Monday, December 29, 2025
Bresenham's line algorithm
Bresenham's line algorithm is a highly efficient computer graphics method for drawing straight lines on pixel-based displays (rasters) by selecting the best pixels to approximate the true line, using only fast integer arithmetic (addition, subtraction, bit shifts) instead of slower floating-point math. It works by calculating an "error" or "decision" parameter at each step to decide whether the next pixel should be at the current Y-level or moved up (or down) by one pixel, efficiently creating the illusion of a continuous line by minimizing error.
// Compile with:
// gcc -std=c99 -O2 -Wall main.c -o main `sdl2-config --cflags --libs`
// gcc -std=c99 -O2 -Wall main.c -o main $(pkg-config --cflags --libs sdl2)
#include <SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define WIDTH 640
#define HEIGHT 480
// Bresenham's line algorithm to draw a line from (x0,y0) to (x1,y1)
void drawLine(Uint32* pixels, int x0, int y0, int x1, int y1, Uint32 color)
{
// Clamp coordinates to window bounds
if (x0 < 0) x0 = 0;
if (x0 >= WIDTH) x0 = WIDTH - 1;
if (y0 < 0) y0 = 0;
if (y0 >= HEIGHT) y0 = HEIGHT - 1;
if (x1 < 0) x1 = 0;
if (x1 >= WIDTH) x1 = WIDTH - 1;
if (y1 < 0) y1 = 0;
if (y1 >= HEIGHT) y1 = HEIGHT - 1;
int dx = abs(x1 - x0);
int dy = abs(y1 - y0);
int sx = (x0 < x1) ? 1 : -1;
int sy = (y0 < y1) ? 1 : -1;
int err = dx - dy;
while (1)
{
if (x0 >= 0 && x0 < WIDTH && y0 >= 0 && y0 < HEIGHT)
pixels[y0 * WIDTH + x0] = color;
if (x0 == x1 && y0 == y1) break;
int e2 = 2 * err;
if (e2 > -dy)
{
err -= dy;
x0 += sx;
}
if (e2 < dx)
{
err += dx;
y0 += sy;
}
}
}
int main(int argc, char* argv[])
{
if (SDL_Init(SDL_INIT_VIDEO) != 0)
{
fprintf(stderr, "SDL_Init Error: %s\n", SDL_GetError());
return 1;
}
SDL_Window* window = SDL_CreateWindow("Simple Paint - Connected Lines",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WIDTH, HEIGHT, SDL_WINDOW_SHOWN);
if (!window)
{
fprintf(stderr, "SDL_CreateWindow Error: %s\n", SDL_GetError());
SDL_Quit();
return 1;
}
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
SDL_Texture* texture = SDL_CreateTexture(renderer,
SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
Uint32* pixels = malloc(WIDTH * HEIGHT * sizeof(Uint32));
if (!pixels)
{
fprintf(stderr, "Failed to allocate pixel buffer\n");
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
// White background (0xFFFFFFFF = ARGB white)
memset(pixels, 0xFF, WIDTH * HEIGHT * sizeof(Uint32));
int quit = 0;
int drawing = 0;
int prevX = 0, prevY = 0;
Uint32 drawColor = 0xFF000000; // Black (ARGB)
while (!quit)
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
quit = 1;
break;
case SDL_MOUSEBUTTONDOWN:
if (event.button.button == SDL_BUTTON_LEFT)
{
drawing = 1;
prevX = event.button.x;
prevY = event.button.y;
// Draw single pixel on click
if (prevX >= 0 && prevX < WIDTH && prevY >= 0 && prevY < HEIGHT)
pixels[prevY * WIDTH + prevX] = drawColor;
}
break;
case SDL_MOUSEBUTTONUP:
if (event.button.button == SDL_BUTTON_LEFT)
drawing = 0;
break;
case SDL_MOUSEMOTION:
if (drawing)
{
int currX = event.motion.x;
int currY = event.motion.y;
// Draw line from previous position to current
drawLine(pixels, prevX, prevY, currX, currY, drawColor);
prevX = currX;
prevY = currY;
}
break;
case SDL_KEYDOWN:
if (event.key.keysym.sym == SDLK_c) // Press 'C' to clear
{
memset(pixels, 0xFF, WIDTH * HEIGHT * sizeof(Uint32));
}
break;
}
}
// Update texture and render
SDL_UpdateTexture(texture, NULL, pixels, WIDTH * sizeof(Uint32));
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
SDL_Delay(16); // ~60 FPS cap
}
free(pixels);
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}