Load an image
This program loads a small PNG file and draws it at three sizes. The image has clear corners, so the blue background shows through. You learn where image files go, and the difference between a surface and a texture.
The full program
/* Load a PNG file and draw it at three sizes. */
#define SDL_MAIN_USE_CALLBACKS
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480
static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
static SDL_Texture *ship = NULL;
/* Load an image file into a texture. */
static SDL_Texture *load_texture(const char *path)
{
SDL_Surface *surface = SDL_LoadPNG(path);
SDL_Texture *texture;
if (surface == NULL) {
SDL_Log("SDL_LoadPNG failed: %s", SDL_GetError());
return NULL;
}
texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_DestroySurface(surface);
if (texture == NULL) {
SDL_Log("SDL_CreateTextureFromSurface failed: %s", SDL_GetError());
}
return texture;
}
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
if (!SDL_Init(SDL_INIT_VIDEO)) {
SDL_Log("SDL_Init failed: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
if (!SDL_CreateWindowAndRenderer("Load an image", WINDOW_WIDTH, WINDOW_HEIGHT, 0,
&window, &renderer)) {
SDL_Log("SDL_CreateWindowAndRenderer failed: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
SDL_SetRenderVSync(renderer, 1);
ship = load_texture("assets/ship.png");
if (ship == NULL) {
return SDL_APP_FAILURE;
}
/* Keep hard pixel edges when the image is scaled up. */
SDL_SetTextureScaleMode(ship, SDL_SCALEMODE_NEAREST);
return SDL_APP_CONTINUE;
}
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
if (event->type == SDL_EVENT_QUIT) {
return SDL_APP_SUCCESS;
}
return SDL_APP_CONTINUE;
}
SDL_AppResult SDL_AppIterate(void *appstate)
{
float width, height;
SDL_GetTextureSize(ship, &width, &height);
SDL_FRect small = { 80.0f, 224.0f, width, height };
SDL_FRect medium = { 180.0f, 192.0f, width * 3.0f, height * 3.0f };
SDL_FRect large = { 340.0f, 128.0f, width * 7.0f, height * 7.0f };
SDL_SetRenderDrawColor(renderer, 32, 96, 160, SDL_ALPHA_OPAQUE);
SDL_RenderClear(renderer);
SDL_RenderTexture(renderer, ship, NULL, &small);
SDL_RenderTexture(renderer, ship, NULL, &medium);
SDL_RenderTexture(renderer, ship, NULL, &large);
SDL_RenderPresent(renderer);
return SDL_APP_CONTINUE;
}
void SDL_AppQuit(void *appstate, SDL_AppResult result)
{
SDL_DestroyTexture(ship);
}/* Load a PNG file and draw it at three sizes. */
#include <SDL.h>
#include <SDL_image.h>
#include <stdbool.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480
static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
static SDL_Texture *ship = NULL;
static bool running = true;
/* Load an image file into a texture. */
static SDL_Texture *load_texture(const char *path)
{
SDL_Surface *surface = IMG_Load(path);
SDL_Texture *texture;
if (surface == NULL) {
SDL_Log("IMG_Load failed: %s", IMG_GetError());
return NULL;
}
texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_FreeSurface(surface);
if (texture == NULL) {
SDL_Log("SDL_CreateTextureFromSurface failed: %s", SDL_GetError());
}
return texture;
}
static void frame(void)
{
SDL_Event event;
int width, height;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
running = false;
}
}
SDL_QueryTexture(ship, NULL, NULL, &width, &height);
SDL_FRect small = { 80.0f, 224.0f, (float)width, (float)height };
SDL_FRect medium = { 180.0f, 192.0f, width * 3.0f, height * 3.0f };
SDL_FRect large = { 340.0f, 128.0f, width * 7.0f, height * 7.0f };
SDL_SetRenderDrawColor(renderer, 32, 96, 160, SDL_ALPHA_OPAQUE);
SDL_RenderClear(renderer);
SDL_RenderCopyF(renderer, ship, NULL, &small);
SDL_RenderCopyF(renderer, ship, NULL, &medium);
SDL_RenderCopyF(renderer, ship, NULL, &large);
SDL_RenderPresent(renderer);
}
int main(int argc, char *argv[])
{
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
SDL_Log("SDL_Init failed: %s", SDL_GetError());
return 1;
}
if (SDL_CreateWindowAndRenderer(WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer) != 0) {
SDL_Log("SDL_CreateWindowAndRenderer failed: %s", SDL_GetError());
SDL_Quit();
return 1;
}
SDL_SetWindowTitle(window, "Load an image");
SDL_RenderSetVSync(renderer, 1);
ship = load_texture("assets/ship.png");
if (ship == NULL) {
SDL_Quit();
return 1;
}
/* Keep hard pixel edges when the image is scaled up. */
SDL_SetTextureScaleMode(ship, SDL_ScaleModeNearest);
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop(frame, 0, 1);
#else
while (running) {
frame();
}
#endif
SDL_DestroyTexture(ship);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}Add the image to your project
An asset is a file that your program uses, such as an image, a sound, or a font. Put your assets in a folder named assets, beside main.c:
hello/
CMakeLists.txt
main.c
assets/
ship.pngTo follow this lesson, right-click ship.png and save it in the assets folder.
The path assets/ship.png is relative to the folder that you run the program from. Run the program from the hello folder, with ./build/hello.
The function SDL_LoadPNG is new in SDL 3.4. The CMakeLists.txt file from the setup page asks for that version or later. For other image formats, such as JPEG, you need the separate SDL_image library.
SDL2 can load only BMP files by itself. For PNG files, you need the separate SDL_image library. Install it first:
Linux
On Ubuntu or Debian:
sudo apt install libsdl2-image-devOn Fedora:
sudo dnf install SDL2_image-develOn Arch:
sudo pacman -S sdl2_imagemacOS
brew install sdl2_imageWindows
pacman -S mingw-w64-ucrt-x86_64-SDL2_imageThen replace your CMakeLists.txt file with this one:
cmake_minimum_required(VERSION 3.16)
project(hello C)
find_package(SDL2 REQUIRED CONFIG)
find_package(SDL2_image REQUIRED CONFIG)
add_executable(hello WIN32 main.c)
# Windows needs SDL2main. The other systems do not have this library.
if(TARGET SDL2::SDL2main)
target_link_libraries(hello PRIVATE SDL2::SDL2main)
endif()
target_link_libraries(hello PRIVATE SDL2::SDL2 SDL2_image::SDL2_image)Surfaces and textures
SDL holds images in two forms, and this is the main idea of the lesson.
A surface is an image in main memory. Your C code can read and change its pixels, but it is slow to draw. A texture is an image on the graphics card. It is very fast to draw, and it can scale, but your code cannot easily read its pixels.
So each image follows the same path:
SDL_Surface *surface = SDL_LoadPNG(path); SDL_Surface *surface = IMG_Load(path);This line reads the file into a surface. Next, the program copies the surface to the graphics card:
texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_DestroySurface(surface); texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_FreeSurface(surface);The texture has its own copy of the pixels, so the program frees the surface at once. If you forget this line, the program leaks memory (it keeps memory that it no longer uses).
Load each image one time, at the start. Reading a file in each frame is slow.
Draw the texture
SDL_RenderTexture(renderer, ship, NULL, &small); SDL_RenderCopyF(renderer, ship, NULL, &small);The third argument chooses a part of the image, and NULL means the full image. The last argument is the destination rectangle. Its size sets the size on the screen, so the same texture can draw at any size.
To keep the shape, the program multiplies the width and the height by the same number.
Keep the pixels sharp
SDL_SetTextureScaleMode(ship, SDL_SCALEMODE_NEAREST);By default, SDL3 blurs a texture as it scales. This is good for photographs, but it is bad for pixel art. The nearest mode keeps hard edges.
SDL_SetTextureScaleMode(ship, SDL_ScaleModeNearest);This call asks SDL to keep hard edges as it scales the texture. This is the correct mode for pixel art. Photographs look better in SDL_ScaleModeLinear.
Clean up
You made the texture, so you destroy it. SDL_AppQuit calls SDL_DestroyTexture.
You made the texture, so you destroy it. The end of main calls SDL_DestroyTexture, before it destroys the renderer.
Build for the browser
A browser program cannot read files from your disk. Emscripten must pack them with the program:
emcc main.c -sUSE_SDL=3 --preload-file assets -o index.htmlemcc main.c -sUSE_SDL=2 -sUSE_SDL_IMAGE=2 -sSDL2_IMAGE_FORMATS='["png"]' --preload-file assets -o index.htmlThis command also makes a file named index.data. It holds your assets, and you must publish it with the other files.
Try this
- Change the scale mode, then build and run. Compare the edges of the large image.
- Draw the image at a size with a different shape, such as twice as wide. The image stretches.
- Make your own 32 by 32 PNG in any drawing program, and load it in place of the ship.
- Make the ship follow the mouse. Use the code from the mouse lesson.
What you learned
- Assets go in a folder that your program can find at run time.
- A surface is in main memory, and a texture is on the graphics card.
- Load an image one time, and free the surface after you make the texture.
- The destination rectangle sets the size on the screen.
- A web build must pack its assets with
--preload-file.