openMSX
SDLImage.cc
Go to the documentation of this file.
1#include "SDLImage.hh"
2#include "PNG.hh"
3#include "SDLOutputSurface.hh"
4#include "checked_cast.hh"
5#include "ranges.hh"
6#include "xrange.hh"
7#include <array>
8#include <cassert>
9#include <cstdlib>
10#include <cmath>
11#include <SDL.h>
12
13using namespace gl;
14
15namespace openmsx {
16
17static SDLSurfacePtr getTempSurface(ivec2 size_)
18{
19 int displayIndex = 0;
20 SDL_DisplayMode currentMode;
21 if (SDL_GetCurrentDisplayMode(displayIndex, &currentMode) != 0) {
22 // Error. Can this happen? Anything we can do?
23 assert(false);
24 }
25 int bpp;
26 Uint32 rmask, gmask, bmask, amask;
27 SDL_PixelFormatEnumToMasks(
28 currentMode.format, &bpp, &rmask, &gmask, &bmask, &amask);
29 if (bpp == 32) {
30 if (amask == 0) {
31 amask = ~(rmask | gmask | bmask);
32 }
33 } else { // TODO should we also check {R,G,B}_loss == 0?
34 // Use ARGB8888 as a fallback
35 amask = 0xff000000;
36 rmask = 0x00ff0000;
37 gmask = 0x0000ff00;
38 bmask = 0x000000ff;
39 }
40
41 return {unsigned(abs(size_[0])), unsigned(abs(size_[1])), 32,
42 rmask, gmask, bmask, amask};
43}
44
45// Helper functions to draw a gradient
46// Extract R,G,B,A components to 8.16 bit fixed point.
47// Note the order R,G,B,A is arbitrary, the actual pixel value may have the
48// components in a different order.
50 uint32_t r, g, b, a;
51};
52static constexpr UnpackedRGBA unpackRGBA(uint32_t rgba)
53{
54 uint32_t r = (((rgba >> 24) & 0xFF) << 16) + 0x8000;
55 uint32_t g = (((rgba >> 16) & 0xFF) << 16) + 0x8000;
56 uint32_t b = (((rgba >> 8) & 0xFF) << 16) + 0x8000;
57 uint32_t a = (((rgba >> 0) & 0xFF) << 16) + 0x8000;
58 return {r, g, b, a};
59}
60// Setup outer loop (vertical) interpolation parameters.
61// For each component there is a pair of (initial,delta) values. These values
62// are 8.16 bit fixed point, delta is signed.
64 uint32_t r0, g0, b0, a0;
65 int32_t dr, dg, db, da;
66};
67static constexpr Interp1Result setupInterp1(uint32_t rgba0, uint32_t rgba1, unsigned length)
68{
69 auto [r0, g0, b0, a0] = unpackRGBA(rgba0);
70 if (length == 1) {
71 return {r0, g0, b0, a0,
72 0, 0, 0, 0};
73 } else {
74 auto [r1, g1, b1, a1] = unpackRGBA(rgba1);
75 int32_t dr = int32_t(r1 - r0) / int32_t(length - 1);
76 int32_t dg = int32_t(g1 - g0) / int32_t(length - 1);
77 int32_t db = int32_t(b1 - b0) / int32_t(length - 1);
78 int32_t da = int32_t(a1 - a0) / int32_t(length - 1);
79 return {r0, g0, b0, a0,
80 dr, dg, db, da};
81 }
82}
83// Setup inner loop (horizontal) interpolation parameters.
84// - Like above we also output a pair of (initial,delta) values for each
85// component. But we pack two components in one 32-bit value. This leaves only
86// 16 bits per component, so now the values are 8.8 bit fixed point.
87// - To avoid carry/borrow from the lower to the upper pack, we make the lower
88// component always a positive number and output a boolean to indicate whether
89// we should add or subtract the delta from the initial value.
90// - The 8.8 fixed point calculations in the inner loop are less accurate than
91// the 8.16 calculations in the outer loop. This could result in not 100%
92// accurate gradients. Though only on very wide images and the error is
93// so small that it will hardly be visible (if at all).
94// - Packing 2 components in one value is not beneficial in the outer loop
95// because in this routine we need the individual components of the values
96// that are calculated by setupInterp1(). (It would also make the code even
97// more complex).
99 uint32_t rb, ga;
100 uint32_t drb, dga;
102};
103static constexpr Interp2Result setupInterp2(
104 uint32_t r0, uint32_t g0, uint32_t b0, uint32_t a0,
105 uint32_t r1, uint32_t g1, uint32_t b1, uint32_t a1,
106 unsigned length)
107{
108 // Pack the initial values for the components R,B and G,A into
109 // a vector-type: two 8.16 scalars -> one [8.8 ; 8.8] vector
110 uint32_t rb = ((r0 << 8) & 0xffff0000) |
111 ((b0 >> 8) & 0x0000ffff);
112 uint32_t ga = ((g0 << 8) & 0xffff0000) |
113 ((a0 >> 8) & 0x0000ffff);
114 if (length == 1) {
115 return {rb, ga, 0, 0, false, false};
116 } else {
117 // calculate delta values
118 bool subRB = false;
119 bool subGA = false;
120 int32_t dr = int32_t(r1 - r0) / int32_t(length - 1);
121 int32_t dg = int32_t(g1 - g0) / int32_t(length - 1);
122 int32_t db = int32_t(b1 - b0) / int32_t(length - 1);
123 int32_t da = int32_t(a1 - a0) / int32_t(length - 1);
124 if (db < 0) { // make sure db is positive
125 dr = -dr;
126 db = -db;
127 subRB = true;
128 }
129 if (da < 0) { // make sure da is positive
130 dg = -dg;
131 da = -da;
132 subGA = true;
133 }
134 // also pack two 8.16 delta values in one [8.8 ; 8.8] vector
135 uint32_t drb = ((uint32_t(dr) << 8) & 0xffff0000) |
136 ((uint32_t(db) >> 8) & 0x0000ffff);
137 uint32_t dga = ((uint32_t(dg) << 8) & 0xffff0000) |
138 ((uint32_t(da) >> 8) & 0x0000ffff);
139 return {rb, ga, drb, dga, subRB, subGA};
140 }
141}
142// Pack two [8.8 ; 8.8] vectors into one pixel.
143static constexpr uint32_t packRGBA(uint32_t rb, uint32_t ga)
144{
145 return (rb & 0xff00ff00) | ((ga & 0xff00ff00) >> 8);
146}
147
148// Draw a gradient on the given surface. This is a bilinear interpolation
149// between 4 RGBA colors. One color for each corner, in this order:
150// 0 -- 1
151// | |
152// 2 -- 3
153static constexpr void gradient(std::span<const uint32_t, 4> rgba, SDL_Surface& surface, int borderSize)
154{
155 int width = surface.w - 2 * borderSize;
156 int height = surface.h - 2 * borderSize;
157 if ((width <= 0) || (height <= 0)) return;
158
159 auto [r0, g0, b0, a0, dr02, dg02, db02, da02] = setupInterp1(rgba[0], rgba[2], height);
160 auto [r1, g1, b1, a1, dr13, dg13, db13, da13] = setupInterp1(rgba[1], rgba[3], height);
161
162 auto* buffer = static_cast<uint32_t*>(surface.pixels);
163 buffer += borderSize;
164 buffer += borderSize * (surface.pitch / sizeof(uint32_t));
165 repeat(height, [&, r0=r0, g0=g0, b0=b0, a0=a0, dr02=dr02, dg02=dg02, db02=db02, da02=da02,
166 r1=r1, g1=g1, b1=b1, a1=a1, dr13=dr13, dg13=dg13, db13=db13, da13=da13] () mutable {
167 auto [rb, ga, drb, dga, subRB, subGA] = setupInterp2(r0, g0, b0, a0, r1, g1, b1, a1, width);
168
169 // Depending on the subRB/subGA booleans, we need to add or
170 // subtract the delta to/from the initial value. There are
171 // 2 booleans so 4 combinations:
172 if (!subRB) {
173 if (!subGA) {
174 for (auto x : xrange(width)) {
175 buffer[x] = packRGBA(rb, ga);
176 rb += drb; ga += dga;
177 }
178 } else {
179 for (auto x : xrange(width)) {
180 buffer[x] = packRGBA(rb, ga);
181 rb += drb; ga -= dga;
182 }
183 }
184 } else {
185 if (!subGA) {
186 for (auto x : xrange(width)) {
187 buffer[x] = packRGBA(rb, ga);
188 rb -= drb; ga += dga;
189 }
190 } else {
191 for (auto x : xrange(width)) {
192 buffer[x] = packRGBA(rb, ga);
193 rb -= drb; ga -= dga;
194 }
195 }
196 }
197
198 r0 += dr02; g0 += dg02; b0 += db02; a0 += da02;
199 r1 += dr13; g1 += dg13; b1 += db13; a1 += da13;
200 buffer += (surface.pitch / sizeof(uint32_t));
201 });
202}
203
204// class SDLImage
205
206SDLImage::SDLImage(OutputSurface& output, const std::string& filename)
207 : texture(loadImage(output, filename))
208 , flipX(false), flipY(false)
209{
210}
211
212// TODO get rid of this constructor
213// instead allow to draw the same SDLImage to different sizes
214SDLImage::SDLImage(OutputSurface& output, const std::string& filename, float scaleFactor)
215 : texture(loadImage(output, filename))
216 , flipX(scaleFactor < 0.0f), flipY(scaleFactor < 0.0f)
217{
218 size = trunc(vec2(size) * std::abs(scaleFactor)); // scale image size
219}
220
221// TODO get rid of this constructor, see above
222SDLImage::SDLImage(OutputSurface& output, const std::string& filename, ivec2 size_)
223 : texture(loadImage(output, filename))
224 , flipX(size_[0] < 0), flipY(size_[1] < 0)
225{
226 size = size_; // replace image size
227}
228
229SDLImage::SDLImage(OutputSurface& output, ivec2 size_, uint32_t rgba)
230 : flipX(size_[0] < 0), flipY(size_[1] < 0)
231{
232 initSolid(output, size_, rgba, 0, 0); // no border
233}
234
235
236SDLImage::SDLImage(OutputSurface& output, ivec2 size_, std::span<const uint32_t, 4> rgba,
237 int borderSize, uint32_t borderRGBA)
238 : flipX(size_[0] < 0), flipY(size_[1] < 0)
239{
240 if ((rgba[0] == rgba[1]) &&
241 (rgba[0] == rgba[2]) &&
242 (rgba[0] == rgba[3])) {
243 initSolid (output, size_, rgba[0], borderSize, borderRGBA);
244 } else {
245 initGradient(output, size_, rgba, borderSize, borderRGBA);
246 }
247}
248
250 : texture(toTexture(output, *image))
251 , flipX(false), flipY(false)
252{
253}
254
255SDLTexturePtr SDLImage::toTexture(OutputSurface& output, SDL_Surface& surface)
256{
257 SDLTexturePtr result(SDL_CreateTextureFromSurface(
258 checked_cast<SDLOutputSurface&>(output).getSDLRenderer(), &surface));
259 SDL_SetTextureBlendMode(result.get(), SDL_BLENDMODE_BLEND);
260 SDL_QueryTexture(result.get(), nullptr, nullptr, &size[0], &size[1]);
261 return result;
262}
263
264SDLTexturePtr SDLImage::loadImage(OutputSurface& output, const std::string& filename)
265{
266 bool want32bpp = true;
267 return toTexture(output, *PNG::load(filename, want32bpp));
268}
269
270
271static uint32_t convertColor(const SDL_PixelFormat& format, uint32_t rgba)
272{
273 return SDL_MapRGBA(
274 &format,
275 narrow_cast<Uint8>((rgba >> 24) & 0xff),
276 narrow_cast<Uint8>((rgba >> 16) & 0xff),
277 narrow_cast<Uint8>((rgba >> 8) & 0xff),
278 narrow_cast<Uint8>((rgba >> 0) & 0xff));
279}
280
281static void drawBorder(SDL_Surface& image, int size, uint32_t rgba)
282{
283 if (size <= 0) return;
284
285 uint32_t color = convertColor(*image.format, rgba);
286 bool onlyBorder = ((2 * size) >= image.w) ||
287 ((2 * size) >= image.h);
288 if (onlyBorder) {
289 SDL_FillRect(&image, nullptr, color);
290 } else {
291 // +--------------------+
292 // | 1 |
293 // +---+------------+---+
294 // | | | |
295 // | 3 | | 4 |
296 // | | | |
297 // +---+------------+---+
298 // | 2 |
299 // +--------------------+
300 SDL_Rect rect;
301 rect.x = 0;
302 rect.y = 0;
303 rect.w = image.w;
304 rect.h = size;
305 SDL_FillRect(&image, &rect, color); // 1
306
307 rect.y = image.h - size;
308 SDL_FillRect(&image, &rect, color); // 2
309
310 rect.y = size;
311 rect.w = size;
312 rect.h = image.h - 2 * size;
313 SDL_FillRect(&image, &rect, color); // 3
314
315 rect.x = image.w - size;
316 SDL_FillRect(&image, &rect, color); // 4
317 }
318}
319
320void SDLImage::initSolid(OutputSurface& output, ivec2 size_, uint32_t rgba,
321 int borderSize, uint32_t borderRGBA)
322{
323 checkSize(size_);
324 if ((size_[0] == 0) || (size_[1] == 0)) {
325 // SDL_FillRect crashes on zero-width surfaces, so check for it
326 return;
327 }
328
329 SDLSurfacePtr tmp32 = getTempSurface(size_);
330
331 // draw interior
332 SDL_FillRect(tmp32.get(), nullptr, convertColor(*tmp32->format, rgba));
333
334 drawBorder(*tmp32, borderSize, borderRGBA);
335
336 texture = toTexture(output, *tmp32);
337}
338
339void SDLImage::initGradient(OutputSurface& output, ivec2 size_, std::span<const uint32_t, 4> rgba_,
340 int borderSize, uint32_t borderRGBA)
341{
342 checkSize(size_);
343 if ((size_[0] == 0) || (size_[1] == 0)) {
344 return;
345 }
346
347 std::array<uint32_t, 4> rgba;
348 ranges::copy(rgba_, rgba);
349
350 if (flipX) {
351 std::swap(rgba[0], rgba[1]);
352 std::swap(rgba[2], rgba[3]);
353 }
354 if (flipY) {
355 std::swap(rgba[0], rgba[2]);
356 std::swap(rgba[1], rgba[3]);
357 }
358
359 SDLSurfacePtr tmp32 = getTempSurface(size_);
360 for (auto& c : rgba) {
361 c = convertColor(*tmp32->format, c);
362 }
363 gradient(rgba, *tmp32, borderSize);
364 drawBorder(*tmp32, borderSize, borderRGBA);
365
366 texture = toTexture(output, *tmp32);
367}
368
369void SDLImage::draw(OutputSurface& output, gl::ivec2 pos, uint8_t r, uint8_t g, uint8_t b, uint8_t alpha)
370{
371 assert(r == 255); (void)r; // SDL2 supports this now, but do we need it?
372 assert(g == 255); (void)g;
373 assert(b == 255); (void)b;
374
375 if (!texture) return;
376
377 auto [x, y] = pos;
378 auto [w, h] = size;
379 if (flipX) x -= w;
380 if (flipY) y -= h;
381
382 auto* renderer = checked_cast<SDLOutputSurface&>(output).getSDLRenderer();
383 SDL_SetTextureAlphaMod(texture.get(), alpha);
384 SDL_Rect dst = {x, y, w, h};
385 SDL_RenderCopy(renderer, texture.get(), nullptr, &dst);
386}
387
388} // namespace openmsx
std::string image
Definition: HDImageCLI.cc:13
std::unique_ptr< SDL_Texture, SDLDestroyTexture > SDLTexturePtr
int g
Wrapper around a SDL_Surface.
SDL_Surface * get()
gl::ivec2 size
Definition: BaseImage.hh:31
static void checkSize(gl::ivec2 size)
Performs a sanity check on image size.
Definition: BaseImage.cc:8
A frame buffer where pixels can be written to.
SDLImage(OutputSurface &output, const std::string &filename)
Definition: SDLImage.cc:206
void draw(OutputSurface &output, gl::ivec2 pos, uint8_t r, uint8_t g, uint8_t b, uint8_t alpha) override
Definition: SDLImage.cc:369
constexpr T abs(T t)
Definition: cstd.hh:17
Definition: gl_mat.hh:23
constexpr vecN< N, int > trunc(const vecN< N, T > &x)
Definition: gl_vec.hh:376
vecN< 2, float > vec2
Definition: gl_vec.hh:150
T length(const vecN< N, T > &x)
Definition: gl_vec.hh:341
SDLSurfacePtr load(const std::string &filename, bool want32bpp)
Load the given PNG file in a SDL_Surface.
Definition: PNG.cc:98
This file implemented 3 utility functions:
Definition: Autofire.cc:9
auto copy(InputRange &&range, OutputIter out)
Definition: ranges.hh:232
void swap(openmsx::MemBuffer< T > &l, openmsx::MemBuffer< T > &r) noexcept
Definition: MemBuffer.hh:202
size_t size(std::string_view utf8)
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.
Definition: xrange.hh:147
constexpr auto xrange(T e)
Definition: xrange.hh:132