SDLImage.cc 11 KB

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