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