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