blob: 6d2b0fec1d4e051695cd4bd60882387b98d80493 [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
54// Defining the WUFFS_CONFIG__MODULE* macros are optional, but it lets users of
55// release/c/etc.c choose which parts of Wuffs to build. That file contains the
56// entire Wuffs standard library, implementing a variety of codecs and file
57// formats. Without this macro definition, an optimizing compiler or linker may
58// very well discard Wuffs code for unused codecs, but listing the Wuffs
59// modules we use makes that process explicit. Preprocessing means that such
60// code simply isn't compiled.
61#define WUFFS_CONFIG__MODULES
62#define WUFFS_CONFIG__MODULE__ADLER32
63#define WUFFS_CONFIG__MODULE__AUX__BASE
64#define WUFFS_CONFIG__MODULE__AUX__IMAGE
65#define WUFFS_CONFIG__MODULE__BASE
66#define WUFFS_CONFIG__MODULE__BMP
67#define WUFFS_CONFIG__MODULE__CRC32
68#define WUFFS_CONFIG__MODULE__DEFLATE
69#define WUFFS_CONFIG__MODULE__GIF
70#define WUFFS_CONFIG__MODULE__LZW
71#define WUFFS_CONFIG__MODULE__PNG
72#define WUFFS_CONFIG__MODULE__ZLIB
73
74// If building this program in an environment that doesn't easily accommodate
75// relative includes, you can use the script/inline-c-relative-includes.go
76// program to generate a stand-alone C file.
77#include "../../release/c/wuffs-unsupported-snapshot.c"
78
79// ----------------
80
81#include <SDL2/SDL.h>
82#include <SDL2/SDL_image.h>
83
84// --------
85
86class Wuffs_Load_RW_Callbacks : public wuffs_aux::DecodeImageCallbacks {
87 public:
Nigel Taoa1506ad2021-04-02 19:40:11 +110088 Wuffs_Load_RW_Callbacks() : m_surface(NULL) {}
Nigel Tao10850c12021-04-01 00:14:10 +110089
90 ~Wuffs_Load_RW_Callbacks() {
91 if (m_surface) {
92 SDL_UnlockSurface(m_surface);
93 SDL_FreeSurface(m_surface);
94 m_surface = NULL;
95 }
96 }
97
98 SDL_Surface* //
99 TakeSurface() {
100 if (!m_surface) {
101 return NULL;
102 }
103 SDL_UnlockSurface(m_surface);
104 SDL_Surface* ret = m_surface;
105 m_surface = NULL;
106 return ret;
107 }
108
109 private:
110 wuffs_base__pixel_format //
111 SelectPixfmt(const wuffs_base__image_config& image_config) override {
Nigel Taoa1506ad2021-04-02 19:40:11 +1100112 // Regardless of endianness, SDL_PIXELFORMAT_BGRA32 (from a few lines
113 // below) is equivalent to WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL.
114 return wuffs_base__make_pixel_format(
115 WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL);
Nigel Tao10850c12021-04-01 00:14:10 +1100116 }
117
Nigel Tao314d2652021-04-02 23:57:18 +1100118 AllocPixbufResult //
Nigel Tao10850c12021-04-01 00:14:10 +1100119 AllocPixbuf(const wuffs_base__image_config& image_config,
120 bool allow_uninitialized_memory) override {
121 if (m_surface) {
122 SDL_UnlockSurface(m_surface);
123 SDL_FreeSurface(m_surface);
124 m_surface = NULL;
125 }
126 uint32_t w = image_config.pixcfg.width();
127 uint32_t h = image_config.pixcfg.height();
128 if ((w > 0xFFFFFF) || (h > 0xFFFFFF)) {
Nigel Tao314d2652021-04-02 23:57:18 +1100129 return AllocPixbufResult("Wuffs_Load_RW_Callbacks: image is too large");
Nigel Tao10850c12021-04-01 00:14:10 +1100130 }
Nigel Taoa1506ad2021-04-02 19:40:11 +1100131 uint32_t sdl_pixelformat = SDL_PIXELFORMAT_BGRA32;
132
133 // (§) Uncomment this line of code to invert the BGRA/RGBA color order.
134 // This isn't a generally useful feature for an image viewer, but it should
135 // make it obvious, when pressing the TAB key, whether you're using the
136 // Wuffs (inverted) or SDL_image (correct) decoder.
137 //
138 // sdl_pixelformat = SDL_PIXELFORMAT_RGBA32;
139
140 m_surface = SDL_CreateRGBSurfaceWithFormat(
141 0, static_cast<int>(w), static_cast<int>(h), 32, sdl_pixelformat);
Nigel Tao10850c12021-04-01 00:14:10 +1100142 if (!m_surface) {
Nigel Tao314d2652021-04-02 23:57:18 +1100143 return AllocPixbufResult(
Nigel Tao10850c12021-04-01 00:14:10 +1100144 "Wuffs_Load_RW_Callbacks: SDL_CreateRGBSurface failed");
145 }
146 SDL_LockSurface(m_surface);
Nigel Tao314d2652021-04-02 23:57:18 +1100147 wuffs_base__pixel_buffer pixbuf;
148 wuffs_base__status status = pixbuf.set_interleaved(
149 &image_config.pixcfg,
150 wuffs_base__make_table_u8(static_cast<uint8_t*>(m_surface->pixels),
151 m_surface->w * 4, m_surface->h,
152 m_surface->pitch),
153 wuffs_base__empty_slice_u8());
154 if (!status.is_ok()) {
155 SDL_UnlockSurface(m_surface);
156 SDL_FreeSurface(m_surface);
157 m_surface = NULL;
158 return AllocPixbufResult(status.message());
159 }
160 return AllocPixbufResult(wuffs_aux::MemOwner(NULL, &free), pixbuf);
Nigel Tao10850c12021-04-01 00:14:10 +1100161 }
162
Nigel Tao10850c12021-04-01 00:14:10 +1100163 SDL_Surface* m_surface;
164};
165
166// --------
167
168class Wuffs_Load_RW_Input : public wuffs_aux::sync_io::Input {
169 public:
170 Wuffs_Load_RW_Input(SDL_RWops* rw, bool take_ownership_of_rw)
171 : m_rw(rw), m_ownership(take_ownership_of_rw) {}
172
173 ~Wuffs_Load_RW_Input() {
174 if (m_rw && m_ownership) {
175 m_rw->close(m_rw);
176 }
177 }
178
179 private:
180 std::string //
181 CopyIn(wuffs_aux::IOBuffer* dst) override {
182 if (!m_rw) {
183 return "Wuffs_Load_RW_Input: NULL SDL_RWops";
184 } else if (!dst) {
185 return "Wuffs_Load_RW_Input: NULL IOBuffer";
186 } else if (dst->meta.closed) {
187 return "Wuffs_Load_RW_Input: end of file";
188 }
189 dst->compact();
190 if (dst->writer_length() == 0) {
191 return "Wuffs_Load_RW_Input: full IOBuffer";
192 }
193 size_t n = m_rw->read(m_rw, dst->writer_pointer(), 1, dst->writer_length());
194 dst->meta.wi += n;
195 return std::string();
196 }
197
198 SDL_RWops* m_rw;
199 const bool m_ownership;
200};
201
202// --------
203
Nigel Taoa1506ad2021-04-02 19:40:11 +1100204// Wuffs_Load_RW loads the image from the input rw. It is like SDL_image's
205// IMG_Load_RW function but it returns any error in-band (as a std::string)
206// instead of separately (global state accessible via SDL_GetError).
Nigel Tao10850c12021-04-01 00:14:10 +1100207//
208// On success, the SDL_Surface* returned will be non-NULL and the caller owns
209// it. Ownership means that they are responsible for calling SDL_FreeSurface on
210// it when done.
211std::pair<SDL_Surface*, std::string> //
Nigel Taoa1506ad2021-04-02 19:40:11 +1100212Wuffs_Load_RW(SDL_RWops* rw, bool take_ownership_of_rw) {
213 Wuffs_Load_RW_Callbacks callbacks;
Nigel Tao10850c12021-04-01 00:14:10 +1100214 Wuffs_Load_RW_Input input(rw, take_ownership_of_rw);
Nigel Tao10850c12021-04-01 00:14:10 +1100215 wuffs_aux::DecodeImageResult res = wuffs_aux::DecodeImage(callbacks, input);
216 if (!res.error_message.empty()) {
217 return std::make_pair<SDL_Surface*, std::string>(
218 NULL, std::move(res.error_message));
219 }
220 return std::make_pair<SDL_Surface*, std::string>(callbacks.TakeSurface(),
221 std::string());
222}
223
224// ----------------
225
226SDL_Surface* g_image = NULL;
227bool g_load_via_sdl_image = false;
228
229bool //
230draw(SDL_Window* window) {
231 SDL_Surface* ws = SDL_GetWindowSurface(window);
232 SDL_FillRect(ws, NULL, SDL_MapRGB(ws->format, 0x00, 0x00, 0x00));
233 if (g_image) {
234 SDL_BlitSurface(g_image, NULL, ws, NULL);
235 }
236 SDL_UpdateWindowSurface(window);
237 return true;
238}
239
240bool //
Nigel Taoa1506ad2021-04-02 19:40:11 +1100241load_image(const char* filename) {
Nigel Tao10850c12021-04-01 00:14:10 +1100242 if (g_image) {
243 SDL_FreeSurface(g_image);
244 g_image = NULL;
245 }
246
247 SDL_RWops* rw = SDL_RWFromFile(filename, "rb");
248 if (!rw) {
249 fprintf(stderr, "main: SDL_RWFromFile(\"%s\"): %s\n", filename,
250 SDL_GetError());
251 return false;
252 }
253
254 constexpr bool take_ownership_of_rw = true;
255 if (g_load_via_sdl_image) {
256 g_image = IMG_Load_RW(rw, take_ownership_of_rw);
257 if (!g_image) {
258 fprintf(stderr, "main: IMG_Load_RW(\"%s\"): %s\n", filename,
259 SDL_GetError());
260 return false;
261 }
262 } else {
Nigel Taoa1506ad2021-04-02 19:40:11 +1100263 std::pair<SDL_Surface*, std::string> p =
264 Wuffs_Load_RW(rw, take_ownership_of_rw);
Nigel Tao10850c12021-04-01 00:14:10 +1100265 if (!p.second.empty()) {
266 fprintf(stderr, "main: Wuffs_Load_RW(\"%s\"): %s\n", filename,
267 p.second.c_str());
268 return false;
269 }
270 g_image = p.first;
271 }
272
273 return true;
274}
275
276int //
277main(int argc, char** argv) {
278 if (argc != 2) {
279 fprintf(stderr, "usage: %s filename\n", argv[0]);
280 return 1;
281 }
282 if (SDL_Init(SDL_INIT_VIDEO) < 0) {
283 fprintf(stderr, "main: SDL_Init: %s\n", SDL_GetError());
284 return 1;
285 }
286 SDL_Window* window =
287 SDL_CreateWindow("sdl-imageviewer", SDL_WINDOWPOS_UNDEFINED,
288 SDL_WINDOWPOS_UNDEFINED, 1024, 768, SDL_WINDOW_SHOWN);
289 if (!window) {
290 fprintf(stderr, "main: SDL_CreateWindow: %s\n", SDL_GetError());
291 return 1;
292 }
293
Nigel Taoa1506ad2021-04-02 19:40:11 +1100294 if (!load_image(argv[1])) {
Nigel Tao10850c12021-04-01 00:14:10 +1100295 return 1;
296 }
297
298 while (true) {
299 SDL_Event event;
300 if (!SDL_WaitEvent(&event)) {
301 fprintf(stderr, "main: SDL_WaitEvent: %s\n", SDL_GetError());
302 return 1;
303 }
304
305 switch (event.type) {
306 case SDL_QUIT:
307 goto cleanup;
308
309 case SDL_WINDOWEVENT:
310 switch (event.window.event) {
311 case SDL_WINDOWEVENT_EXPOSED:
312 if (!draw(window)) {
313 return 1;
314 }
315 break;
316 }
317 break;
318
319 case SDL_KEYDOWN:
320 switch (event.key.keysym.sym) {
321 case SDLK_ESCAPE:
322 goto cleanup;
323 case SDLK_TAB:
324 g_load_via_sdl_image = !g_load_via_sdl_image;
325 printf("Switched to %s.\n",
326 g_load_via_sdl_image ? "SDL_image" : "Wuffs");
Nigel Taoa1506ad2021-04-02 19:40:11 +1100327 if (!load_image(argv[1]) || !draw(window)) {
Nigel Tao10850c12021-04-01 00:14:10 +1100328 return 1;
329 }
330 break;
331 }
332 break;
333 }
334 }
335
336cleanup:
337 if (g_image) {
338 SDL_FreeSurface(g_image);
339 g_image = NULL;
340 }
341 SDL_DestroyWindow(window);
342 SDL_Quit();
343 return 0;
344}