|
| 1 | +/* |
| 2 | + * Blending combines a source color 'src', |
| 3 | + * with the pixels already on the screen 'dst', |
| 4 | + * to produce transparency and other visual effects. |
| 5 | + * |
| 6 | + * formula: dst := (a * dst) op (b * src) |
| 7 | + * |
| 8 | + * where: |
| 9 | + * dst: existed pixel on the screen. |
| 10 | + * src: new pixel. |
| 11 | + * a: dst factor. |
| 12 | + * b: src factor. |
| 13 | + * op: blend operation (usually addition). |
| 14 | + * |
| 15 | + * In graphics programming, color and alpha are usually blended separately: |
| 16 | + * dstRGB := (a * srcRGB) op (b * dstRGB) |
| 17 | + * dstA := (c * srcA) op (d * dstA) |
| 18 | + * |
| 19 | + * This example uses SDL_SetTextureBlendMode() to apply blending to textures, |
| 20 | + * and uses SDL_ComposeCustomBlendMode() to create a custom blend mode. |
| 21 | + * |
| 22 | + * You can also use SDL_SetRenderDrawBlendMode() to apply blending to the |
| 23 | + * entire renderer, but it only affects filled rects and lines, not textures. |
| 24 | + * |
| 25 | + * This code is public domain. Feel free to use it for any purpose! |
| 26 | + */ |
| 27 | + |
| 28 | + |
| 29 | +#define SDL_MAIN_USE_CALLBACKS 1 |
| 30 | +#include <SDL3/SDL.h> |
| 31 | +#include <SDL3/SDL_main.h> |
| 32 | + |
| 33 | +#define WINDOW_WIDTH 640 |
| 34 | +#define WINDOW_HEIGHT 480 |
| 35 | + |
| 36 | +/* UI Constants */ |
| 37 | +#define ROWS 2 |
| 38 | +#define COLS 3 |
| 39 | +#define GRID_SIZE ((WINDOW_WIDTH - 1) / 18.0f) |
| 40 | +#define PANEL_SIZE (GRID_SIZE * 4) |
| 41 | +#define ROW_OFFSET ((WINDOW_HEIGHT - ROWS * PANEL_SIZE) / 4) |
| 42 | +#define COL_OFFSET (GRID_SIZE * COLS) |
| 43 | +#define RECT_SIZE 50.0f |
| 44 | +#define RED_OFFSET (GRID_SIZE) |
| 45 | +#define GREEN_OFFSET (RECT_SIZE / 3 + GRID_SIZE) |
| 46 | +#define BLUE_OFFSET (RECT_SIZE * 2 / 3 + GRID_SIZE) |
| 47 | + |
| 48 | +static SDL_FRect panels[ROWS*COLS]; |
| 49 | +static SDL_Window *window = NULL; |
| 50 | +static SDL_Renderer *renderer = NULL; |
| 51 | +static SDL_Texture *red_rect_texture = NULL; |
| 52 | +static SDL_Texture *green_rect_texture = NULL; |
| 53 | +static SDL_Texture *blue_rect_texture = NULL; |
| 54 | +static Uint8 alpha = 255; |
| 55 | +static SDL_BlendMode blend_modes[] = { |
| 56 | + /*The default no blending: dstRGB := srcRGB |
| 57 | + dstA := srcA */ |
| 58 | + SDL_BLENDMODE_NONE, |
| 59 | + |
| 60 | + /* Alpha blending: dstRGB := srcA * srcRGB + (1 - srcA) * dstRGB |
| 61 | + dstA := srcA + (1 - srcA) * dstA */ |
| 62 | + SDL_BLENDMODE_BLEND, |
| 63 | + |
| 64 | + /* Additive blending: dstRGB := srcRGB + dstRGB |
| 65 | + dstA := srcA + dstA */ |
| 66 | + SDL_BLENDMODE_ADD, |
| 67 | + |
| 68 | + /* Modulate blending: dstRGB := srcRGB * dstRGB |
| 69 | + dstA := dstA */ |
| 70 | + SDL_BLENDMODE_MOD, |
| 71 | + |
| 72 | + /* Multiply blending: dstRGB := srcRGB * dstRGB + (1 - srcA) * dstRGB |
| 73 | + dstA := dstA */ |
| 74 | + SDL_BLENDMODE_MUL, |
| 75 | + |
| 76 | + /* Our custom blending 'Screen Blending': dstRGB := 1 - (1 - dstRGB) * (1 - srcRGB) |
| 77 | + dstA := dstA */ |
| 78 | + 0 |
| 79 | +}; |
| 80 | +static const char *blend_mode_names[] = { "NONE", "BLEND", "ADD", "MOD", "MUL", "SCREEN \"CUSTOM\"" }; |
| 81 | + |
| 82 | +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) |
| 83 | +{ |
| 84 | + SDL_Surface *surface = NULL; |
| 85 | + |
| 86 | + SDL_SetAppMetadata("Example Blending", "1.0", "com.example.blending"); |
| 87 | + if (!SDL_Init(SDL_INIT_VIDEO)) { |
| 88 | + SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); |
| 89 | + return SDL_APP_FAILURE; |
| 90 | + } |
| 91 | + if (!SDL_CreateWindowAndRenderer("examples/renderer/blending", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { |
| 92 | + SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); |
| 93 | + return SDL_APP_FAILURE; |
| 94 | + } |
| 95 | + SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); |
| 96 | + |
| 97 | + int row = 0; |
| 98 | + int col = 0; |
| 99 | + for (row = 0; row < ROWS; row++) |
| 100 | + { |
| 101 | + for (col = 0; col < COLS; col++) |
| 102 | + { |
| 103 | + panels[col + row*COLS] = (SDL_FRect){ col*PANEL_SIZE + col*COL_OFFSET, row*PANEL_SIZE + (row+1)*ROW_OFFSET, PANEL_SIZE, PANEL_SIZE }; |
| 104 | + } |
| 105 | + } |
| 106 | + |
| 107 | + /* Create 'screen blend' mode */ |
| 108 | + blend_modes[ROWS*COLS - 1] = SDL_ComposeCustomBlendMode( |
| 109 | + SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR, /* srcRGB factor := (1 - dstRGB) */ |
| 110 | + SDL_BLENDFACTOR_ONE, /* dstRGB factor := 1 */ |
| 111 | + SDL_BLENDOPERATION_ADD, /* RGB operation := + */ |
| 112 | + SDL_BLENDFACTOR_ZERO, /* srcA factor := 0 */ |
| 113 | + SDL_BLENDFACTOR_ONE, /* dstA factor := dstA */ |
| 114 | + SDL_BLENDOPERATION_ADD /* A operation := + */ |
| 115 | + ); |
| 116 | + |
| 117 | + surface = SDL_CreateSurface((int)RECT_SIZE, (int)RECT_SIZE, SDL_PIXELFORMAT_RGBA8888); |
| 118 | + if (!surface) { |
| 119 | + SDL_Log("Couldn't create surface: %s", SDL_GetError()); |
| 120 | + return SDL_APP_FAILURE; |
| 121 | + } |
| 122 | + |
| 123 | + SDL_FillSurfaceRect(surface, NULL, 0xFF0000FF); /* Red */ |
| 124 | + red_rect_texture = SDL_CreateTextureFromSurface(renderer, surface); |
| 125 | + if (!red_rect_texture) { |
| 126 | + SDL_Log("Couldn't create texture: %s", SDL_GetError()); |
| 127 | + return SDL_APP_FAILURE; |
| 128 | + } |
| 129 | + |
| 130 | + SDL_FillSurfaceRect(surface, NULL, 0x00FF00FF); /* Green */ |
| 131 | + green_rect_texture = SDL_CreateTextureFromSurface(renderer, surface); |
| 132 | + if (!green_rect_texture) { |
| 133 | + SDL_Log("Couldn't create texture: %s", SDL_GetError()); |
| 134 | + return SDL_APP_FAILURE; |
| 135 | + } |
| 136 | + |
| 137 | + SDL_FillSurfaceRect(surface, NULL, 0x0000FFFF); /* Blue */ |
| 138 | + blue_rect_texture = SDL_CreateTextureFromSurface(renderer, surface); |
| 139 | + if (!blue_rect_texture) { |
| 140 | + SDL_Log("Couldn't create texture: %s", SDL_GetError()); |
| 141 | + return SDL_APP_FAILURE; |
| 142 | + } |
| 143 | + |
| 144 | + SDL_DestroySurface(surface); |
| 145 | + |
| 146 | + return SDL_APP_CONTINUE; |
| 147 | +} |
| 148 | + |
| 149 | +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) |
| 150 | +{ |
| 151 | + if (event->type == SDL_EVENT_QUIT) { |
| 152 | + return SDL_APP_SUCCESS; |
| 153 | + } |
| 154 | + if (event->type == SDL_EVENT_KEY_DOWN) { |
| 155 | + /* UP arrow increase alpha */ |
| 156 | + if (event->key.key == SDLK_UP && alpha <= 255-8) alpha += 8; |
| 157 | + /* DOWN arrow decrease alpha */ |
| 158 | + if (event->key.key == SDLK_DOWN && alpha >= 8) alpha -= 8; |
| 159 | + } |
| 160 | + return SDL_APP_CONTINUE; |
| 161 | +} |
| 162 | + |
| 163 | +SDL_AppResult SDL_AppIterate(void *appstate) |
| 164 | +{ |
| 165 | + SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); |
| 166 | + SDL_RenderClear(renderer); |
| 167 | + |
| 168 | + int i = 0; |
| 169 | + float x; |
| 170 | + float y; |
| 171 | + /* Render checkerboard panels */ |
| 172 | + for (i = 0; i < ROWS*COLS; i++) |
| 173 | + { |
| 174 | + /* Loop through the panel pixels */ |
| 175 | + for (y = panels[i].y; y < PANEL_SIZE + panels[i].y; y += GRID_SIZE) |
| 176 | + { |
| 177 | + for (x = panels[i].x; x < PANEL_SIZE + panels[i].x; x += GRID_SIZE) |
| 178 | + { |
| 179 | + SDL_FRect grid = { x, y, GRID_SIZE, GRID_SIZE }; |
| 180 | + bool dark = (int)(x/GRID_SIZE + y/GRID_SIZE) % 2; |
| 181 | + |
| 182 | + if (dark) SDL_SetRenderDrawColor(renderer, 70, 70, 70, 255); /* Darker color */ |
| 183 | + else SDL_SetRenderDrawColor(renderer, 110, 110, 110, 255); /* Lighter color */ |
| 184 | + |
| 185 | + SDL_RenderFillRect(renderer, &grid); |
| 186 | + } |
| 187 | + } |
| 188 | + |
| 189 | + /* Label the blend mode */ |
| 190 | + SDL_SetRenderDrawColor(renderer, 255, 255, 255, SDL_ALPHA_OPAQUE); |
| 191 | + SDL_RenderDebugText(renderer, panels[i].x, panels[i].y - 15, blend_mode_names[i]); |
| 192 | + } |
| 193 | + |
| 194 | + /* Render panels */ |
| 195 | + SDL_RenderRects(renderer, panels, ROWS*COLS); |
| 196 | + |
| 197 | + /* Render UI text */ |
| 198 | + SDL_RenderDebugText(renderer, WINDOW_WIDTH - 176, WINDOW_HEIGHT - 20, "UP/DOWN: CHANGE ALPHA"); |
| 199 | + SDL_RenderDebugTextFormat(renderer, 0, WINDOW_HEIGHT - 20, "ALPHA: %d", alpha); |
| 200 | + |
| 201 | + /* Update textures alpha mod */ |
| 202 | + SDL_SetTextureAlphaMod(red_rect_texture, alpha); |
| 203 | + SDL_SetTextureAlphaMod(green_rect_texture, alpha); |
| 204 | + SDL_SetTextureAlphaMod(blue_rect_texture, alpha); |
| 205 | + |
| 206 | + /* Render panels */ |
| 207 | + for (i = 0; i < ROWS*COLS; i++) { |
| 208 | + /* Update rects destination */ |
| 209 | + SDL_FRect red_dst = { panels[i].x + RED_OFFSET, panels[i].y + RED_OFFSET, RECT_SIZE, RECT_SIZE }; |
| 210 | + SDL_FRect green_dst = { panels[i].x + GREEN_OFFSET, panels[i].y + GREEN_OFFSET, RECT_SIZE, RECT_SIZE }; |
| 211 | + SDL_FRect blue_dst = { panels[i].x + BLUE_OFFSET, panels[i].y + BLUE_OFFSET, RECT_SIZE, RECT_SIZE }; |
| 212 | + |
| 213 | + /* Apply the current blend mode */ |
| 214 | + SDL_SetTextureBlendMode(red_rect_texture, blend_modes[i]); |
| 215 | + SDL_SetTextureBlendMode(green_rect_texture, blend_modes[i]); |
| 216 | + SDL_SetTextureBlendMode(blue_rect_texture, blend_modes[i]); |
| 217 | + |
| 218 | + /* Render textures */ |
| 219 | + SDL_RenderTexture(renderer, red_rect_texture, NULL, &red_dst); |
| 220 | + SDL_RenderTexture(renderer, green_rect_texture, NULL, &green_dst); |
| 221 | + SDL_RenderTexture(renderer, blue_rect_texture, NULL, &blue_dst); |
| 222 | + } |
| 223 | + |
| 224 | + SDL_RenderPresent(renderer); |
| 225 | + |
| 226 | + return SDL_APP_CONTINUE; |
| 227 | +} |
| 228 | + |
| 229 | +void SDL_AppQuit(void *appstate, SDL_AppResult result) |
| 230 | +{ |
| 231 | + SDL_DestroyTexture(red_rect_texture); |
| 232 | + SDL_DestroyTexture(green_rect_texture); |
| 233 | + SDL_DestroyTexture(blue_rect_texture); |
| 234 | +} |
0 commit comments