openMSX
PNG.cc
Go to the documentation of this file.
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 <tuple>
15 #include <png.h>
16 #include <SDL.h>
17 
18 namespace openmsx::PNG {
19 
20 static void handleError(png_structp png_ptr, png_const_charp error_msg)
21 {
22  const 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  const 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 {
75  PNGReadHandle() = default;
77  {
78  if (ptr) {
79  png_destroy_read_struct(&ptr, info ? &info : nullptr, nullptr);
80  }
81  }
82  PNGReadHandle(const PNGReadHandle&) = delete;
84 
85  png_structp ptr = nullptr;
86  png_infop info = nullptr;
87 };
88 
89 static void readData(png_structp ctx, png_bytep area, png_size_t size)
90 {
91  auto* file = reinterpret_cast<File*>(png_get_io_ptr(ctx));
92  file->read(area, size);
93 }
94 
95 SDLSurfacePtr load(const std::string& filename, bool want32bpp)
96 {
97  File file(filename);
98 
99  try {
100  // Create the PNG loading context structure.
101  PNGReadHandle png;
102  png.ptr = png_create_read_struct(
103  PNG_LIBPNG_VER_STRING,
104  const_cast<char*>("decoding"), handleError, handleWarning);
105  if (!png.ptr) {
106  throw MSXException("Failed to allocate main struct");
107  }
108 
109  // Allocate/initialize the memory for image information.
110  png.info = png_create_info_struct(png.ptr);
111  if (!png.info) {
112  throw MSXException("Failed to allocate image info struct");
113  }
114 
115  // Set up the input control.
116  png_set_read_fn(png.ptr, &file, readData);
117 
118  // Read PNG header info.
119  png_read_info(png.ptr, png.info);
120  png_uint_32 width, height;
121  int bit_depth, color_type, interlace_type;
122  png_get_IHDR(png.ptr, png.info, &width, &height, &bit_depth,
123  &color_type, &interlace_type, nullptr, nullptr);
124 
125  // Tell libpng to strip 16 bit/color files down to 8 bits/color.
126  png_set_strip_16(png.ptr);
127 
128  // Extract multiple pixels with bit depths of 1, 2, and 4 from a single
129  // byte into separate bytes (useful for paletted and grayscale images).
130  png_set_packing(png.ptr);
131 
132  // The following enables:
133  // - transformation of grayscale images of less than 8 to 8 bits
134  // - changes paletted images to RGB
135  // - adds a full alpha channel if there is transparency information in a tRNS chunk
136  png_set_expand(png.ptr);
137 
138  if (want32bpp) {
139  png_set_filler(png.ptr, 0xff, PNG_FILLER_AFTER);
140  }
141 
142  // Try to read the PNG directly in the same format as the video
143  // surface format. The supported formats are
144  // RGBA, BGRA, ARGB, ABGR
145  // When the output surface is 16bpp, still produce PNG in BGRA
146  // format because SDL *seems* to be better optimized for this
147  // format (not documented, but I checked SDL-1.2.15 source code).
148  // if (for some reason) the surface is not available yet,
149  // we just skip this
150  bool bgr(true), swapAlpha(false); // default BGRA
151 
152  int displayIndex = 0;
153  SDL_DisplayMode currentMode;
154  if (SDL_GetCurrentDisplayMode(displayIndex, &currentMode) == 0) {
155  int bpp;
156  Uint32 Rmask, Gmask, Bmask, Amask;
157  SDL_PixelFormatEnumToMasks(
158  currentMode.format, &bpp, &Rmask, &Gmask, &Bmask, &Amask);
159  if (bpp >= 24) {
160  if (Rmask == 0x000000FF &&
161  Gmask == 0x0000FF00 &&
162  Bmask == 0x00FF0000) { // RGB(A)
163  bgr = false; swapAlpha = false;
164  } else if (Rmask == 0x00FF0000 &&
165  Gmask == 0x0000FF00 &&
166  Bmask == 0x000000FF) { // BGR(A)
167  bgr = true; swapAlpha = false;
168  } else if (Rmask == 0x0000FF00 &&
169  Gmask == 0x00FF0000 &&
170  Bmask == 0xFF000000) { // ARGB
171  bgr = false; swapAlpha = true;
172  } else if (Rmask == 0xFF000000 &&
173  Gmask == 0x00FF0000 &&
174  Bmask == 0x0000FF00) { // ABGR
175  bgr = true; swapAlpha = true;
176  }
177  }
178  }
179  if (bgr) png_set_bgr (png.ptr);
180  if (swapAlpha) png_set_swap_alpha(png.ptr);
181 
182  // always convert grayscale to RGB
183  // together with all the above conversions, the resulting image will
184  // be either RGB or RGBA with 8 bits per component.
185  png_set_gray_to_rgb(png.ptr);
186 
187  png_read_update_info(png.ptr, png.info);
188 
189  png_get_IHDR(png.ptr, png.info, &width, &height, &bit_depth,
190  &color_type, &interlace_type, nullptr, nullptr);
191 
192  // Allocate the SDL surface to hold the image.
193  constexpr unsigned MAX_SIZE = 2048;
194  if (width > MAX_SIZE) {
195  throw MSXException(
196  "Attempted to create a surface with excessive width: ",
197  width, ", max ", MAX_SIZE);
198  }
199  if (height > MAX_SIZE) {
200  throw MSXException(
201  "Attempted to create a surface with excessive height: ",
202  height, ", max ", MAX_SIZE);
203  }
204  int bpp = png_get_channels(png.ptr, png.info) * 8;
205  assert(bpp == one_of(24, 32));
206  auto [redMask, grnMask, bluMask, alpMask] = [&]()-> std::tuple<Uint32, Uint32, Uint32, Uint32> {
207  if constexpr (OPENMSX_BIGENDIAN) {
208  if (bpp == 32) {
209  if (swapAlpha) {
210  return {0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000};
211  } else {
212  return {0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF};
213  }
214  } else {
215  return {0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000};
216  }
217  } else {
218  if (bpp == 32) {
219  if (swapAlpha) {
220  return {0x0000FF00, 0x00FF0000, 0xFF000000, 0x000000FF};
221  } else {
222  return {0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000};
223  }
224  } else {
225  return {0x000000FF, 0x0000FF00, 0x00FF0000, 0x00000000};
226  }
227  }
228  }();
229  if (bgr) std::swap(redMask, bluMask);
230  SDLSurfacePtr surface(width, height, bpp,
231  redMask, grnMask, bluMask, alpMask);
232 
233  // Create the array of pointers to image data.
234  VLA(png_bytep, row_pointers, height);
235  for (auto row : xrange(height)) {
236  row_pointers[row] = reinterpret_cast<png_bytep>(
237  surface.getLinePtr(row));
238  }
239 
240  // Read the entire image in one go.
241  png_read_image(png.ptr, row_pointers);
242 
243  // In some cases it can't read PNGs created by some popular programs
244  // (ACDSEE), we do not want to process comments, so we omit png_read_end
245  //png_read_end(png.ptr, png.info);
246 
247  return surface;
248  } catch (MSXException& e) {
249  throw MSXException(
250  "Error while loading PNG file \"", filename, "\": ",
251  e.getMessage());
252  }
253 }
254 
255 
256 /* PNG save code by Darren Grant sdl@lokigames.com */
257 /* heavily modified for openMSX by Joost Damad joost@lumatec.be */
258 
260  PNGWriteHandle() = default;
262  {
263  if (ptr) {
264  png_destroy_write_struct(&ptr, info ? &info : nullptr);
265  }
266  }
267  PNGWriteHandle(const PNGWriteHandle&) = delete;
269 
270  png_structp ptr = nullptr;
271  png_infop info = nullptr;
272 };
273 
274 static void writeData(png_structp ctx, png_bytep area, png_size_t size)
275 {
276  auto* file = reinterpret_cast<File*>(png_get_io_ptr(ctx));
277  file->write(area, size);
278 }
279 
280 static void flushData(png_structp ctx)
281 {
282  auto* file = reinterpret_cast<File*>(png_get_io_ptr(ctx));
283  file->flush();
284 }
285 
286 static void IMG_SavePNG_RW(int width, int height, const void** row_pointers,
287  const std::string& filename, bool color)
288 {
289  try {
290  File file(filename, File::TRUNCATE);
291 
292  PNGWriteHandle png;
293  png.ptr = png_create_write_struct(
294  PNG_LIBPNG_VER_STRING,
295  const_cast<char*>("encoding"), handleError, handleWarning);
296  if (!png.ptr) {
297  throw MSXException("Failed to allocate main struct");
298  }
299 
300  // Allocate/initialize the image information data. REQUIRED
301  png.info = png_create_info_struct(png.ptr);
302  if (!png.info) {
303  // Couldn't create image information for PNG file
304  throw MSXException("Failed to allocate image info struct");
305  }
306 
307  // Set up the output control.
308  png_set_write_fn(png.ptr, &file, writeData, flushData);
309 
310  // Mark this image as being generated by openMSX and add creation time.
311  std::string version = Version::full();
312  png_text text[2];
313  text[0].compression = PNG_TEXT_COMPRESSION_NONE;
314  text[0].key = const_cast<char*>("Software");
315  text[0].text = const_cast<char*>(version.c_str());
316  text[1].compression = PNG_TEXT_COMPRESSION_NONE;
317  text[1].key = const_cast<char*>("Creation Time");
318 
319  // A buffer size of 20 characters is large enough till the year
320  // 9999. But the compiler doesn't understand calendars and
321  // warns that the snprintf output could be truncated (e.g.
322  // because the year is -2147483647). To silence this warning
323  // (and also to work around the windows _snprintf stuff) we add
324  // some extra buffer space.
325  static constexpr size_t size = (10 + 1 + 8 + 1) + 44;
326  time_t now = time(nullptr);
327  struct tm* tm = localtime(&now);
328  char timeStr[size];
329  snprintf(timeStr, sizeof(timeStr), "%04d-%02d-%02d %02d:%02d:%02d",
330  1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday,
331  tm->tm_hour, tm->tm_min, tm->tm_sec);
332  text[1].text = timeStr;
333 
334  png_set_text(png.ptr, png.info, text, 2);
335 
336  png_set_IHDR(png.ptr, png.info, width, height, 8,
337  color ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_GRAY,
338  PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
339  PNG_FILTER_TYPE_BASE);
340 
341  // Write the file header information. REQUIRED
342  png_write_info(png.ptr, png.info);
343 
344  // Write out the entire image data in one call.
345  png_write_image(
346  png.ptr,
347  reinterpret_cast<png_bytep*>(const_cast<void**>(row_pointers)));
348  png_write_end(png.ptr, png.info);
349  } catch (MSXException& e) {
350  throw MSXException(
351  "Error while writing PNG file \"", filename, "\": ",
352  e.getMessage());
353  }
354 }
355 
356 static void save(SDL_Surface* image, const std::string& filename)
357 {
358  SDLAllocFormatPtr frmt24(SDL_AllocFormat(
359  OPENMSX_BIGENDIAN ? SDL_PIXELFORMAT_BGR24 : SDL_PIXELFORMAT_RGB24));
360  SDLSurfacePtr surf24(SDL_ConvertSurface(image, frmt24.get(), 0));
361 
362  // Create the array of pointers to image data
363  VLA(const void*, row_pointers, image->h);
364  for (auto i : xrange(image->h)) {
365  row_pointers[i] = surf24.getLinePtr(i);
366  }
367 
368  IMG_SavePNG_RW(image->w, image->h, row_pointers, filename, true);
369 }
370 
371 void save(unsigned width, unsigned height, const void** rowPointers,
372  const PixelFormat& format, const std::string& filename)
373 {
374  // this implementation creates 1 extra copy, can be optimized if required
375  SDLSurfacePtr surface(
376  width, height, format.getBpp(),
377  format.getRmask(), format.getGmask(), format.getBmask(), format.getAmask());
378  for (auto y : xrange(height)) {
379  memcpy(surface.getLinePtr(y),
380  rowPointers[y], width * format.getBytesPerPixel());
381  }
382  save(surface.get(), filename);
383 }
384 
385 void save(unsigned width, unsigned height,
386  const void** rowPointers, const std::string& filename)
387 {
388  IMG_SavePNG_RW(width, height, rowPointers, filename, true);
389 }
390 
391 void saveGrayscale(unsigned width, unsigned height,
392  const void** rowPointers, const std::string& filename)
393 {
394  IMG_SavePNG_RW(width, height, rowPointers, filename, false);
395 }
396 
397 } // namespace openmsx::PNG
std::unique_ptr< SDL_PixelFormat, SDLFreeFormat > SDLAllocFormatPtr
Wrapper around a SDL_Surface.
SDL_Surface * get()
void * getLinePtr(unsigned y)
Definition: one_of.hh:7
void read(void *buffer, size_t num)
Read from file.
Definition: File.cc:93
void write(const void *buffer, size_t num)
Write to file.
Definition: File.cc:98
@ TRUNCATE
Definition: File.hh:20
void flush()
Force a write of all buffered data to disk.
Definition: File.cc:133
const std::string & getMessage() const &
Definition: MSXException.hh:23
static std::string full()
Definition: Version.cc:8
void format(SectorAccessibleDisk &disk, bool dos1)
Format the given disk (= a single partition).
Utility functions to hide the complexity of saving to a PNG file.
Definition: PNG.cc:18
void saveGrayscale(unsigned width, unsigned height, const void **rowPointers, const std::string &filename)
Definition: PNG.cc:391
void save(unsigned width, unsigned height, const void **rowPointers, const std::string &filename)
Definition: PNG.cc:385
SDLSurfacePtr load(const std::string &filename, bool want32bpp)
Load the given PNG file in a SDL_Surface.
Definition: PNG.cc:95
constexpr int MAX_SIZE
Definition: BaseImage.cc:6
constexpr const char *const filename
size_t size(std::string_view utf8)
PNGReadHandle(const PNGReadHandle &)=delete
PNGReadHandle & operator=(const PNGReadHandle &)=delete
PNGWriteHandle & operator=(const PNGWriteHandle &)=delete
PNGWriteHandle(const PNGWriteHandle &)=delete
#define VLA(TYPE, NAME, LENGTH)
Definition: vla.hh:10
constexpr auto xrange(T e)
Definition: xrange.hh:155