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