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