PNG.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. #include "PNG.hh"
  2. #include "MSXException.hh"
  3. #include "File.hh"
  4. #include "build-info.hh"
  5. #include "Version.hh"
  6. #include "one_of.hh"
  7. #include "vla.hh"
  8. #include "cstdiop.hh"
  9. #include <cassert>
  10. #include <cstring>
  11. #include <cstdlib>
  12. #include <ctime>
  13. #include <iostream>
  14. #include <png.h>
  15. #include <SDL.h>
  16. namespace openmsx::PNG {
  17. static void handleError(png_structp png_ptr, png_const_charp error_msg)
  18. {
  19. auto operation = reinterpret_cast<const char*>(
  20. png_get_error_ptr(png_ptr));
  21. throw MSXException("Error while ", operation, " PNG: ", error_msg);
  22. }
  23. static void handleWarning(png_structp png_ptr, png_const_charp warning_msg)
  24. {
  25. auto operation = reinterpret_cast<const char*>(
  26. png_get_error_ptr(png_ptr));
  27. std::cerr << "Warning while " << operation << " PNG: "
  28. << warning_msg << '\n';
  29. }
  30. /*
  31. The copyright notice below applies to the original PNG load code, which was
  32. imported from SDL_image 1.2.10, file "IMG_png.c", function "IMG_LoadPNG_RW".
  33. ===============================================================================
  34. File: SDL_png.c
  35. Purpose: A PNG loader and saver for the SDL library
  36. Revision:
  37. Created by: Philippe Lavoie (2 November 1998)
  38. lavoie@zeus.genie.uottawa.ca
  39. Modified by:
  40. Copyright notice:
  41. Copyright (C) 1998 Philippe Lavoie
  42. This library is free software; you can redistribute it and/or
  43. modify it under the terms of the GNU Library General Public
  44. License as published by the Free Software Foundation; either
  45. version 2 of the License, or (at your option) any later version.
  46. This library is distributed in the hope that it will be useful,
  47. but WITHOUT ANY WARRANTY; without even the implied warranty of
  48. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  49. Library General Public License for more details.
  50. You should have received a copy of the GNU Library General Public
  51. License along with this library; if not, write to the Free
  52. Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  53. Comments: The load and save routine are basically the ones you can find
  54. in the example.c file from the libpng distribution.
  55. Changes:
  56. 1999-05-17: Modified to use the new SDL data sources - Sam Lantinga
  57. 2009-12-29: Modified for use in openMSX - Maarten ter Huurne
  58. and Wouter Vermaelen
  59. ===============================================================================
  60. */
  61. struct PNGReadHandle {
  62. ~PNGReadHandle()
  63. {
  64. if (ptr) {
  65. png_destroy_read_struct(&ptr, info ? &info : nullptr, nullptr);
  66. }
  67. }
  68. png_structp ptr = nullptr;
  69. png_infop info = nullptr;
  70. };
  71. static void readData(png_structp ctx, png_bytep area, png_size_t size)
  72. {
  73. auto file = reinterpret_cast<File*>(png_get_io_ptr(ctx));
  74. file->read(area, size);
  75. }
  76. SDLSurfacePtr load(const std::string& filename, bool want32bpp)
  77. {
  78. File file(filename);
  79. try {
  80. // Create the PNG loading context structure.
  81. PNGReadHandle png;
  82. png.ptr = png_create_read_struct(
  83. PNG_LIBPNG_VER_STRING,
  84. const_cast<char*>("decoding"), handleError, handleWarning);
  85. if (!png.ptr) {
  86. throw MSXException("Failed to allocate main struct");
  87. }
  88. // Allocate/initialize the memory for image information.
  89. png.info = png_create_info_struct(png.ptr);
  90. if (!png.info) {
  91. throw MSXException("Failed to allocate image info struct");
  92. }
  93. // Set up the input control.
  94. png_set_read_fn(png.ptr, &file, readData);
  95. // Read PNG header info.
  96. png_read_info(png.ptr, png.info);
  97. png_uint_32 width, height;
  98. int bit_depth, color_type, interlace_type;
  99. png_get_IHDR(png.ptr, png.info, &width, &height, &bit_depth,
  100. &color_type, &interlace_type, nullptr, nullptr);
  101. // Tell libpng to strip 16 bit/color files down to 8 bits/color.
  102. png_set_strip_16(png.ptr);
  103. // Extract multiple pixels with bit depths of 1, 2, and 4 from a single
  104. // byte into separate bytes (useful for paletted and grayscale images).
  105. png_set_packing(png.ptr);
  106. // The following enables:
  107. // - transformation of grayscale images of less than 8 to 8 bits
  108. // - changes paletted images to RGB
  109. // - adds a full alpha channel if there is transparency information in a tRNS chunk
  110. png_set_expand(png.ptr);
  111. if (want32bpp) {
  112. png_set_filler(png.ptr, 0xff, PNG_FILLER_AFTER);
  113. }
  114. // Try to read the PNG directly in the same format as the video
  115. // surface format. The supported formats are
  116. // RGBA, BGRA, ARGB, ABGR
  117. // When the output surface is 16bpp, still produce PNG in BGRA
  118. // format because SDL *seems* to be better optimized for this
  119. // format (not documented, but I checked SDL-1.2.15 source code).
  120. // if (for some reason) the surface is not available yet,
  121. // we just skip this
  122. bool bgr(true), swapAlpha(false); // default BGRA
  123. int displayIndex = 0;
  124. SDL_DisplayMode currentMode;
  125. if (SDL_GetCurrentDisplayMode(displayIndex, &currentMode) == 0) {
  126. int bpp;
  127. Uint32 Rmask, Gmask, Bmask, Amask;
  128. SDL_PixelFormatEnumToMasks(
  129. currentMode.format, &bpp, &Rmask, &Gmask, &Bmask, &Amask);
  130. if (bpp >= 24) {
  131. if (Rmask == 0x000000FF &&
  132. Gmask == 0x0000FF00 &&
  133. Bmask == 0x00FF0000) { // RGB(A)
  134. bgr = false; swapAlpha = false;
  135. } else if (Rmask == 0x00FF0000 &&
  136. Gmask == 0x0000FF00 &&
  137. Bmask == 0x000000FF) { // BGR(A)
  138. bgr = true; swapAlpha = false;
  139. } else if (Rmask == 0x0000FF00 &&
  140. Gmask == 0x00FF0000 &&
  141. Bmask == 0xFF000000) { // ARGB
  142. bgr = false; swapAlpha = true;
  143. } else if (Rmask == 0xFF000000 &&
  144. Gmask == 0x00FF0000 &&
  145. Bmask == 0x0000FF00) { // ABGR
  146. bgr = true; swapAlpha = true;
  147. }
  148. }
  149. }
  150. if (bgr) png_set_bgr (png.ptr);
  151. if (swapAlpha) png_set_swap_alpha(png.ptr);
  152. // always convert grayscale to RGB
  153. // together with all the above conversions, the resulting image will
  154. // be either RGB or RGBA with 8 bits per component.
  155. png_set_gray_to_rgb(png.ptr);
  156. png_read_update_info(png.ptr, png.info);
  157. png_get_IHDR(png.ptr, png.info, &width, &height, &bit_depth,
  158. &color_type, &interlace_type, nullptr, nullptr);
  159. // Allocate the SDL surface to hold the image.
  160. constexpr unsigned MAX_SIZE = 2048;
  161. if (width > MAX_SIZE) {
  162. throw MSXException(
  163. "Attempted to create a surface with excessive width: ",
  164. width, ", max ", MAX_SIZE);
  165. }
  166. if (height > MAX_SIZE) {
  167. throw MSXException(
  168. "Attempted to create a surface with excessive height: ",
  169. height, ", max ", MAX_SIZE);
  170. }
  171. int bpp = png_get_channels(png.ptr, png.info) * 8;
  172. assert(bpp == one_of(24, 32));
  173. Uint32 redMask, grnMask, bluMask, alpMask;
  174. if (OPENMSX_BIGENDIAN) {
  175. if (bpp == 32) {
  176. if (swapAlpha) {
  177. redMask = 0x00FF0000;
  178. grnMask = 0x0000FF00;
  179. bluMask = 0x000000FF;
  180. alpMask = 0xFF000000;
  181. } else {
  182. redMask = 0xFF000000;
  183. grnMask = 0x00FF0000;
  184. bluMask = 0x0000FF00;
  185. alpMask = 0x000000FF;
  186. }
  187. } else {
  188. redMask = 0x00FF0000;
  189. grnMask = 0x0000FF00;
  190. bluMask = 0x000000FF;
  191. alpMask = 0x00000000;
  192. }
  193. } else {
  194. if (bpp == 32) {
  195. if (swapAlpha) {
  196. redMask = 0x0000FF00;
  197. grnMask = 0x00FF0000;
  198. bluMask = 0xFF000000;
  199. alpMask = 0x000000FF;
  200. } else {
  201. redMask = 0x000000FF;
  202. grnMask = 0x0000FF00;
  203. bluMask = 0x00FF0000;
  204. alpMask = 0xFF000000;
  205. }
  206. } else {
  207. redMask = 0x000000FF;
  208. grnMask = 0x0000FF00;
  209. bluMask = 0x00FF0000;
  210. alpMask = 0x00000000;
  211. }
  212. }
  213. if (bgr) std::swap(redMask, bluMask);
  214. SDLSurfacePtr surface(width, height, bpp,
  215. redMask, grnMask, bluMask, alpMask);
  216. // Create the array of pointers to image data.
  217. VLA(png_bytep, row_pointers, height);
  218. for (png_uint_32 row = 0; row < height; ++row) {
  219. row_pointers[row] = reinterpret_cast<png_bytep>(
  220. surface.getLinePtr(row));
  221. }
  222. // Read the entire image in one go.
  223. png_read_image(png.ptr, row_pointers);
  224. // In some cases it can't read PNG's created by some popular programs
  225. // (ACDSEE), we do not want to process comments, so we omit png_read_end
  226. //png_read_end(png.ptr, png.info);
  227. return surface;
  228. } catch (MSXException& e) {
  229. throw MSXException(
  230. "Error while loading PNG file \"", filename, "\": ",
  231. e.getMessage());
  232. }
  233. }
  234. /* PNG save code by Darren Grant sdl@lokigames.com */
  235. /* heavily modified for openMSX by Joost Damad joost@lumatec.be */
  236. struct PNGWriteHandle {
  237. ~PNGWriteHandle()
  238. {
  239. if (ptr) {
  240. png_destroy_write_struct(&ptr, info ? &info : nullptr);
  241. }
  242. }
  243. png_structp ptr = nullptr;
  244. png_infop info = nullptr;
  245. };
  246. static void writeData(png_structp ctx, png_bytep area, png_size_t size)
  247. {
  248. auto file = reinterpret_cast<File*>(png_get_io_ptr(ctx));
  249. file->write(area, size);
  250. }
  251. static void flushData(png_structp ctx)
  252. {
  253. auto file = reinterpret_cast<File*>(png_get_io_ptr(ctx));
  254. file->flush();
  255. }
  256. static void IMG_SavePNG_RW(int width, int height, const void** row_pointers,
  257. const std::string& filename, bool color)
  258. {
  259. try {
  260. File file(filename, File::TRUNCATE);
  261. PNGWriteHandle png;
  262. png.ptr = png_create_write_struct(
  263. PNG_LIBPNG_VER_STRING,
  264. const_cast<char*>("encoding"), handleError, handleWarning);
  265. if (!png.ptr) {
  266. throw MSXException("Failed to allocate main struct");
  267. }
  268. // Allocate/initialize the image information data. REQUIRED
  269. png.info = png_create_info_struct(png.ptr);
  270. if (!png.info) {
  271. // Couldn't create image information for PNG file
  272. throw MSXException("Failed to allocate image info struct");
  273. }
  274. // Set up the output control.
  275. png_set_write_fn(png.ptr, &file, writeData, flushData);
  276. // Mark this image as being generated by openMSX and add creation time.
  277. std::string version = Version::full();
  278. png_text text[2];
  279. text[0].compression = PNG_TEXT_COMPRESSION_NONE;
  280. text[0].key = const_cast<char*>("Software");
  281. text[0].text = const_cast<char*>(version.c_str());
  282. text[1].compression = PNG_TEXT_COMPRESSION_NONE;
  283. text[1].key = const_cast<char*>("Creation Time");
  284. // A buffer size of 20 characters is large enough till the year
  285. // 9999. But the compiler doesn't understand calendars and
  286. // warns that the snprintf output could be truncated (e.g.
  287. // because the year is -2147483647). To silence this warning
  288. // (and also to work around the windows _snprintf stuff) we add
  289. // some extra buffer space.
  290. static constexpr size_t size = (10 + 1 + 8 + 1) + 44;
  291. time_t now = time(nullptr);
  292. struct tm* tm = localtime(&now);
  293. char timeStr[size];
  294. snprintf(timeStr, sizeof(timeStr), "%04d-%02d-%02d %02d:%02d:%02d",
  295. 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday,
  296. tm->tm_hour, tm->tm_min, tm->tm_sec);
  297. text[1].text = timeStr;
  298. png_set_text(png.ptr, png.info, text, 2);
  299. png_set_IHDR(png.ptr, png.info, width, height, 8,
  300. color ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_GRAY,
  301. PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
  302. PNG_FILTER_TYPE_BASE);
  303. // Write the file header information. REQUIRED
  304. png_write_info(png.ptr, png.info);
  305. // Write out the entire image data in one call.
  306. png_write_image(
  307. png.ptr,
  308. reinterpret_cast<png_bytep*>(const_cast<void**>(row_pointers)));
  309. png_write_end(png.ptr, png.info);
  310. } catch (MSXException& e) {
  311. throw MSXException(
  312. "Error while writing PNG file \"", filename, "\": ",
  313. e.getMessage());
  314. }
  315. }
  316. static void save(SDL_Surface* image, const std::string& filename)
  317. {
  318. SDLAllocFormatPtr frmt24(SDL_AllocFormat(
  319. OPENMSX_BIGENDIAN ? SDL_PIXELFORMAT_BGR24 : SDL_PIXELFORMAT_RGB24));
  320. SDLSurfacePtr surf24(SDL_ConvertSurface(image, frmt24.get(), 0));
  321. // Create the array of pointers to image data
  322. VLA(const void*, row_pointers, image->h);
  323. for (int i = 0; i < image->h; ++i) {
  324. row_pointers[i] = surf24.getLinePtr(i);
  325. }
  326. IMG_SavePNG_RW(image->w, image->h, row_pointers, filename, true);
  327. }
  328. void save(unsigned width, unsigned height, const void** rowPointers,
  329. const PixelFormat& format, const std::string& filename)
  330. {
  331. // this implementation creates 1 extra copy, can be optimized if required
  332. SDLSurfacePtr surface(
  333. width, height, format.getBpp(),
  334. format.getRmask(), format.getGmask(), format.getBmask(), format.getAmask());
  335. for (unsigned y = 0; y < height; ++y) {
  336. memcpy(surface.getLinePtr(y),
  337. rowPointers[y], width * format.getBytesPerPixel());
  338. }
  339. save(surface.get(), filename);
  340. }
  341. void save(unsigned width, unsigned height,
  342. const void** rowPointers, const std::string& filename)
  343. {
  344. IMG_SavePNG_RW(width, height, rowPointers, filename, true);
  345. }
  346. void saveGrayscale(unsigned width, unsigned height,
  347. const void** rowPointers, const std::string& filename)
  348. {
  349. IMG_SavePNG_RW(width, height, rowPointers, filename, false);
  350. }
  351. } // namespace openmsx::PNG