openMSX
PNG.cc
Go to the documentation of this file.
1#include "PNG.hh"
2#include "MSXException.hh"
3#include "File.hh"
4#include "Version.hh"
5#include "endian.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
18namespace openmsx::PNG {
19
20static 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
27static 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/*
36The copyright notice below applies to the original PNG load code, which was
37imported 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
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
89static 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
95SDLSurfacePtr 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 (Endian::BIG) {
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 }
269
270 png_structp ptr = nullptr;
271 png_infop info = nullptr;
272};
273
274static 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
280static void flushData(png_structp ctx)
281{
282 auto* file = reinterpret_cast<File*>(png_get_io_ptr(ctx));
283 file->flush();
284}
285
286static 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
356static void save(SDL_Surface* image, const std::string& filename)
357{
358 SDLAllocFormatPtr frmt24(SDL_AllocFormat(
359 Endian::BIG ? 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
371void 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
385void 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
391void 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::string image
Definition: HDImageCLI.cc:13
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:91
void write(const void *buffer, size_t num)
Write to file.
Definition: File.cc:96
@ TRUNCATE
Definition: File.hh:20
void flush()
Force a write of all buffered data to disk.
Definition: File.cc:131
static std::string full()
Definition: Version.cc:8
constexpr bool BIG
Definition: endian.hh:13
constexpr double e
Definition: Math.hh:18
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
void swap(openmsx::MemBuffer< T > &l, openmsx::MemBuffer< T > &r) noexcept
Definition: MemBuffer.hh:202
size_t size(std::string_view utf8)
PNGReadHandle & operator=(const PNGReadHandle &)=delete
PNGReadHandle(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:133