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