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