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