blob: f9e1c4184eec93c81db1d445617ad3fedab4e769 [file] [log] [blame]
Nigel Tao10850c12021-04-01 00:14:10 +11001// Copyright 2021 The Wuffs Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// ----------------
16
17/*
18sdl-imageviewer is a simple GUI program for viewing an image. To run:
19
20$CXX sdl-imageviewer.cc -lSDL2 -lSDL2_image && \
21 ./a.out ../../test/data/bricks-color.png; rm -f a.out
22
23for a C++ compiler $CXX, such as clang++ or g++.
24
25The Tab key switches between decoding the image via Wuffs or via SDL2_image.
26There should be no difference unless you uncomment the § line of code below.
27
28The Escape key quits.
29
30----
31
32This program (in Wuffs' example directory) is like example/imageviewer but with
33fewer features. It focuses on showing how to integrate the Wuffs image decoders
34with SDL (as an alternative to the SDL_image extension).
35
36While SDL is cross-platform, this program is not as good as example/imageviewer
37for general use. SDL (which is designed for full-screen games) uses a noticable
38amount of CPU (and therefore power) polling for events even when the program
39isn't otherwise doing anything.
40*/
41
42#include <inttypes.h>
43#include <stdio.h>
44#include <stdlib.h>
45
46// Wuffs ships as a "single file C library" or "header file library" as per
47// https://github.com/nothings/stb/blob/master/docs/stb_howto.txt
48//
49// To use that single file as a "foo.c"-like implementation, instead of a
50// "foo.h"-like header, #define WUFFS_IMPLEMENTATION before #include'ing or
51// compiling it.
52#define WUFFS_IMPLEMENTATION
53
Nigel Tao7f9f37c2021-10-04 12:35:32 +110054// Defining the WUFFS_CONFIG__STATIC_FUNCTIONS macro is optional, but when
55// combined with WUFFS_IMPLEMENTATION, it demonstrates making all of Wuffs'
56// functions have static storage.
57//
58// This can help the compiler ignore or discard unused code, which can produce
59// faster compiles and smaller binaries. Other motivations are discussed in the
60// "ALLOW STATIC IMPLEMENTATION" section of
61// https://raw.githubusercontent.com/nothings/stb/master/docs/stb_howto.txt
62#define WUFFS_CONFIG__STATIC_FUNCTIONS
63
Nigel Tao10850c12021-04-01 00:14:10 +110064// Defining the WUFFS_CONFIG__MODULE* macros are optional, but it lets users of
65// release/c/etc.c choose which parts of Wuffs to build. That file contains the
66// entire Wuffs standard library, implementing a variety of codecs and file
67// formats. Without this macro definition, an optimizing compiler or linker may
68// very well discard Wuffs code for unused codecs, but listing the Wuffs
69// modules we use makes that process explicit. Preprocessing means that such
70// code simply isn't compiled.
71#define WUFFS_CONFIG__MODULES
72#define WUFFS_CONFIG__MODULE__ADLER32
73#define WUFFS_CONFIG__MODULE__AUX__BASE
74#define WUFFS_CONFIG__MODULE__AUX__IMAGE
75#define WUFFS_CONFIG__MODULE__BASE
76#define WUFFS_CONFIG__MODULE__BMP
77#define WUFFS_CONFIG__MODULE__CRC32
78#define WUFFS_CONFIG__MODULE__DEFLATE
79#define WUFFS_CONFIG__MODULE__GIF
80#define WUFFS_CONFIG__MODULE__LZW
81#define WUFFS_CONFIG__MODULE__PNG
82#define WUFFS_CONFIG__MODULE__ZLIB
83
84// If building this program in an environment that doesn't easily accommodate
85// relative includes, you can use the script/inline-c-relative-includes.go
86// program to generate a stand-alone C file.
87#include "../../release/c/wuffs-unsupported-snapshot.c"
88
89// ----------------
90
91#include <SDL2/SDL.h>
92#include <SDL2/SDL_image.h>
Nigel Tao2453a8d2021-10-03 13:53:27 +110093#include <SDL2/SDL_render.h>
Nigel Tao10850c12021-04-01 00:14:10 +110094
95// --------
96
97class Wuffs_Load_RW_Callbacks : public wuffs_aux::DecodeImageCallbacks {
98 public:
Nigel Taoa1506ad2021-04-02 19:40:11 +110099 Wuffs_Load_RW_Callbacks() : m_surface(NULL) {}
Nigel Tao10850c12021-04-01 00:14:10 +1100100
101 ~Wuffs_Load_RW_Callbacks() {
102 if (m_surface) {
103 SDL_UnlockSurface(m_surface);
104 SDL_FreeSurface(m_surface);
105 m_surface = NULL;
106 }
107 }
108
109 SDL_Surface* //
110 TakeSurface() {
111 if (!m_surface) {
112 return NULL;
113 }
114 SDL_UnlockSurface(m_surface);
115 SDL_Surface* ret = m_surface;
116 m_surface = NULL;
117 return ret;
118 }
119
120 private:
121 wuffs_base__pixel_format //
122 SelectPixfmt(const wuffs_base__image_config& image_config) override {
Nigel Taoa1506ad2021-04-02 19:40:11 +1100123 // Regardless of endianness, SDL_PIXELFORMAT_BGRA32 (from a few lines
124 // below) is equivalent to WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL.
125 return wuffs_base__make_pixel_format(
126 WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL);
Nigel Tao10850c12021-04-01 00:14:10 +1100127 }
128
Nigel Tao314d2652021-04-02 23:57:18 +1100129 AllocPixbufResult //
Nigel Tao10850c12021-04-01 00:14:10 +1100130 AllocPixbuf(const wuffs_base__image_config& image_config,
131 bool allow_uninitialized_memory) override {
132 if (m_surface) {
133 SDL_UnlockSurface(m_surface);
134 SDL_FreeSurface(m_surface);
135 m_surface = NULL;
136 }
137 uint32_t w = image_config.pixcfg.width();
138 uint32_t h = image_config.pixcfg.height();
139 if ((w > 0xFFFFFF) || (h > 0xFFFFFF)) {
Nigel Tao314d2652021-04-02 23:57:18 +1100140 return AllocPixbufResult("Wuffs_Load_RW_Callbacks: image is too large");
Nigel Tao10850c12021-04-01 00:14:10 +1100141 }
Nigel Taoa1506ad2021-04-02 19:40:11 +1100142 uint32_t sdl_pixelformat = SDL_PIXELFORMAT_BGRA32;
143
144 // (§) Uncomment this line of code to invert the BGRA/RGBA color order.
145 // This isn't a generally useful feature for an image viewer, but it should
146 // make it obvious, when pressing the TAB key, whether you're using the
147 // Wuffs (inverted) or SDL_image (correct) decoder.
148 //
149 // sdl_pixelformat = SDL_PIXELFORMAT_RGBA32;
150
151 m_surface = SDL_CreateRGBSurfaceWithFormat(
152 0, static_cast<int>(w), static_cast<int>(h), 32, sdl_pixelformat);
Nigel Tao10850c12021-04-01 00:14:10 +1100153 if (!m_surface) {
Nigel Tao314d2652021-04-02 23:57:18 +1100154 return AllocPixbufResult(
Nigel Tao10850c12021-04-01 00:14:10 +1100155 "Wuffs_Load_RW_Callbacks: SDL_CreateRGBSurface failed");
156 }
157 SDL_LockSurface(m_surface);
Nigel Tao314d2652021-04-02 23:57:18 +1100158 wuffs_base__pixel_buffer pixbuf;
159 wuffs_base__status status = pixbuf.set_interleaved(
160 &image_config.pixcfg,
161 wuffs_base__make_table_u8(static_cast<uint8_t*>(m_surface->pixels),
162 m_surface->w * 4, m_surface->h,
163 m_surface->pitch),
164 wuffs_base__empty_slice_u8());
165 if (!status.is_ok()) {
166 SDL_UnlockSurface(m_surface);
167 SDL_FreeSurface(m_surface);
168 m_surface = NULL;
169 return AllocPixbufResult(status.message());
170 }
171 return AllocPixbufResult(wuffs_aux::MemOwner(NULL, &free), pixbuf);
Nigel Tao10850c12021-04-01 00:14:10 +1100172 }
173
Nigel Tao10850c12021-04-01 00:14:10 +1100174 SDL_Surface* m_surface;
175};
176
177// --------
178
179class Wuffs_Load_RW_Input : public wuffs_aux::sync_io::Input {
180 public:
181 Wuffs_Load_RW_Input(SDL_RWops* rw, bool take_ownership_of_rw)
182 : m_rw(rw), m_ownership(take_ownership_of_rw) {}
183
184 ~Wuffs_Load_RW_Input() {
185 if (m_rw && m_ownership) {
186 m_rw->close(m_rw);
187 }
188 }
189
190 private:
191 std::string //
192 CopyIn(wuffs_aux::IOBuffer* dst) override {
193 if (!m_rw) {
194 return "Wuffs_Load_RW_Input: NULL SDL_RWops";
195 } else if (!dst) {
196 return "Wuffs_Load_RW_Input: NULL IOBuffer";
197 } else if (dst->meta.closed) {
198 return "Wuffs_Load_RW_Input: end of file";
199 }
200 dst->compact();
201 if (dst->writer_length() == 0) {
202 return "Wuffs_Load_RW_Input: full IOBuffer";
203 }
204 size_t n = m_rw->read(m_rw, dst->writer_pointer(), 1, dst->writer_length());
205 dst->meta.wi += n;
206 return std::string();
207 }
208
209 SDL_RWops* m_rw;
210 const bool m_ownership;
211};
212
213// --------
214
Nigel Taoa1506ad2021-04-02 19:40:11 +1100215// Wuffs_Load_RW loads the image from the input rw. It is like SDL_image's
216// IMG_Load_RW function but it returns any error in-band (as a std::string)
217// instead of separately (global state accessible via SDL_GetError).
Nigel Tao10850c12021-04-01 00:14:10 +1100218//
219// On success, the SDL_Surface* returned will be non-NULL and the caller owns
220// it. Ownership means that they are responsible for calling SDL_FreeSurface on
221// it when done.
222std::pair<SDL_Surface*, std::string> //
Nigel Taoa1506ad2021-04-02 19:40:11 +1100223Wuffs_Load_RW(SDL_RWops* rw, bool take_ownership_of_rw) {
224 Wuffs_Load_RW_Callbacks callbacks;
Nigel Tao10850c12021-04-01 00:14:10 +1100225 Wuffs_Load_RW_Input input(rw, take_ownership_of_rw);
Nigel Tao10850c12021-04-01 00:14:10 +1100226 wuffs_aux::DecodeImageResult res = wuffs_aux::DecodeImage(callbacks, input);
227 if (!res.error_message.empty()) {
228 return std::make_pair<SDL_Surface*, std::string>(
229 NULL, std::move(res.error_message));
230 }
231 return std::make_pair<SDL_Surface*, std::string>(callbacks.TakeSurface(),
232 std::string());
233}
234
235// ----------------
236
237SDL_Surface* g_image = NULL;
238bool g_load_via_sdl_image = false;
239
240bool //
241draw(SDL_Window* window) {
242 SDL_Surface* ws = SDL_GetWindowSurface(window);
Přemysl Eric Janouch6f39b962021-09-30 12:35:43 +0200243 if (!ws) {
Nigel Tao1cf0df92021-10-03 15:30:04 +1100244 // Use an indirect approach, for exotic window pixel formats (e.g. X.org 10
245 // bits per RGB channel), when we can't get the window surface directly.
246 //
247 // See https://github.com/google/wuffs/issues/51
Přemysl Eric Janouch6f39b962021-09-30 12:35:43 +0200248 SDL_Renderer* r = SDL_CreateRenderer(window, -1, 0);
Nigel Tao1cf0df92021-10-03 15:30:04 +1100249 if (!r) {
250 fprintf(stderr, "main: SDL_CreateRenderer: %s\n", SDL_GetError());
251 return false;
252 }
Přemysl Eric Janouch6f39b962021-09-30 12:35:43 +0200253 SDL_RenderClear(r);
254 if (g_image) {
255 SDL_Texture* texture = SDL_CreateTextureFromSurface(r, g_image);
256 SDL_RenderCopy(r, texture, NULL, &g_image->clip_rect);
257 SDL_DestroyTexture(texture);
258 }
259 SDL_RenderPresent(r);
260 SDL_DestroyRenderer(r);
261 return true;
262 }
Nigel Tao1cf0df92021-10-03 15:30:04 +1100263
264 // Use a direct approach.
Nigel Tao10850c12021-04-01 00:14:10 +1100265 SDL_FillRect(ws, NULL, SDL_MapRGB(ws->format, 0x00, 0x00, 0x00));
266 if (g_image) {
267 SDL_BlitSurface(g_image, NULL, ws, NULL);
268 }
269 SDL_UpdateWindowSurface(window);
270 return true;
271}
272
273bool //
Nigel Taoa1506ad2021-04-02 19:40:11 +1100274load_image(const char* filename) {
Nigel Tao10850c12021-04-01 00:14:10 +1100275 if (g_image) {
276 SDL_FreeSurface(g_image);
277 g_image = NULL;
278 }
279
280 SDL_RWops* rw = SDL_RWFromFile(filename, "rb");
281 if (!rw) {
282 fprintf(stderr, "main: SDL_RWFromFile(\"%s\"): %s\n", filename,
283 SDL_GetError());
284 return false;
285 }
286
287 constexpr bool take_ownership_of_rw = true;
288 if (g_load_via_sdl_image) {
289 g_image = IMG_Load_RW(rw, take_ownership_of_rw);
290 if (!g_image) {
291 fprintf(stderr, "main: IMG_Load_RW(\"%s\"): %s\n", filename,
292 SDL_GetError());
293 return false;
294 }
295 } else {
Nigel Taoa1506ad2021-04-02 19:40:11 +1100296 std::pair<SDL_Surface*, std::string> p =
297 Wuffs_Load_RW(rw, take_ownership_of_rw);
Nigel Tao10850c12021-04-01 00:14:10 +1100298 if (!p.second.empty()) {
299 fprintf(stderr, "main: Wuffs_Load_RW(\"%s\"): %s\n", filename,
300 p.second.c_str());
301 return false;
302 }
303 g_image = p.first;
304 }
305
306 return true;
307}
308
309int //
310main(int argc, char** argv) {
311 if (argc != 2) {
312 fprintf(stderr, "usage: %s filename\n", argv[0]);
313 return 1;
314 }
315 if (SDL_Init(SDL_INIT_VIDEO) < 0) {
316 fprintf(stderr, "main: SDL_Init: %s\n", SDL_GetError());
317 return 1;
318 }
319 SDL_Window* window =
320 SDL_CreateWindow("sdl-imageviewer", SDL_WINDOWPOS_UNDEFINED,
321 SDL_WINDOWPOS_UNDEFINED, 1024, 768, SDL_WINDOW_SHOWN);
322 if (!window) {
323 fprintf(stderr, "main: SDL_CreateWindow: %s\n", SDL_GetError());
324 return 1;
325 }
326
Nigel Taoa1506ad2021-04-02 19:40:11 +1100327 if (!load_image(argv[1])) {
Nigel Tao10850c12021-04-01 00:14:10 +1100328 return 1;
329 }
330
331 while (true) {
332 SDL_Event event;
333 if (!SDL_WaitEvent(&event)) {
334 fprintf(stderr, "main: SDL_WaitEvent: %s\n", SDL_GetError());
335 return 1;
336 }
337
338 switch (event.type) {
339 case SDL_QUIT:
340 goto cleanup;
341
342 case SDL_WINDOWEVENT:
343 switch (event.window.event) {
344 case SDL_WINDOWEVENT_EXPOSED:
345 if (!draw(window)) {
346 return 1;
347 }
348 break;
349 }
350 break;
351
352 case SDL_KEYDOWN:
353 switch (event.key.keysym.sym) {
354 case SDLK_ESCAPE:
355 goto cleanup;
356 case SDLK_TAB:
357 g_load_via_sdl_image = !g_load_via_sdl_image;
358 printf("Switched to %s.\n",
359 g_load_via_sdl_image ? "SDL_image" : "Wuffs");
Nigel Taoa1506ad2021-04-02 19:40:11 +1100360 if (!load_image(argv[1]) || !draw(window)) {
Nigel Tao10850c12021-04-01 00:14:10 +1100361 return 1;
362 }
363 break;
364 }
365 break;
366 }
367 }
368
369cleanup:
370 if (g_image) {
371 SDL_FreeSurface(g_image);
372 g_image = NULL;
373 }
374 SDL_DestroyWindow(window);
375 SDL_Quit();
376 return 0;
377}