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