changeset 1532:b505083dcd87

Added png screenshot support
author Michael Pavone <pavone@retrodev.com>
date Sat, 24 Mar 2018 19:40:51 -0700
parents 092675db4f37
children 78b7fc03c7c6
files Makefile png.c png.h render_sdl.c
diffstat 4 files changed, 173 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Sat Mar 24 15:33:44 2018 -0700
+++ b/Makefile	Sat Mar 24 19:40:51 2018 -0700
@@ -136,7 +136,7 @@
 ifdef NOZLIB
 CFLAGS+= -DDISABLE_ZLIB
 else
-MAINOBJS+= $(LIBZOBJS)
+MAINOBJS+= $(LIBZOBJS) png.o
 endif
 
 ifeq ($(CPU),x86_64)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/png.c	Sat Mar 24 19:40:51 2018 -0700
@@ -0,0 +1,138 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "zlib/zlib.h"
+
+static const char png_magic[] = {0x89, 'P', 'N', 'G', '\r', '\n', 0x1A, '\n'};
+static const char ihdr[] = {'I', 'H', 'D', 'R'};
+static const char plte[] = {'P', 'L', 'T', 'E'};
+static const char idat[] = {'I', 'D', 'A', 'T'};
+static const char iend[] = {'I', 'E', 'N', 'D'};
+
+enum {
+	COLOR_TRUE = 2,
+	COLOR_INDEXED
+};
+
+static void write_chunk(FILE *f, const char*id, uint8_t *buffer, uint32_t size)
+{
+	uint8_t tmp[4] = {size >> 24, size >> 16, size >> 8, size};
+	uint8_t warn = 0;
+	warn = warn || (sizeof(tmp) != fwrite(tmp, 1, sizeof(tmp), f));
+	warn = warn || (4 != fwrite(id, 1, 4, f));
+	if (size) {
+		warn = warn || (size != fwrite(buffer, 1, size, f));
+	}
+	
+	uint32_t crc = crc32(0, NULL, 0);
+	crc = crc32(crc, id, 4);
+	if (size) {
+		crc = crc32(crc, buffer, size);
+	}
+	tmp[0] = crc >> 24;
+	tmp[1] = crc >> 16;
+	tmp[2] = crc >> 8;
+	tmp[3] = crc;
+	warn = warn || (sizeof(tmp) != fwrite(tmp, 1, sizeof(tmp), f));
+	if (warn) {
+		fprintf(stderr, "Failure during write of %c%c%c%c chunk\n", id[0], id[1], id[2], id[3]);
+	}
+}
+
+static void write_header(FILE *f, uint32_t width, uint32_t height, uint8_t color_type)
+{
+	uint8_t chunk[13] = {
+		width >> 24, width >> 16, width >> 8, width,
+		height >> 24, height >> 16, height >> 8, height,
+		8, color_type, 0, 0, 0
+	};
+	if (sizeof(png_magic) != fwrite(png_magic, 1, sizeof(png_magic), f)) {
+		fputs("Error writing PNG magic\n", stderr);
+	}
+	write_chunk(f, ihdr, chunk, sizeof(chunk));
+}
+
+void save_png24(FILE *f, uint32_t *buffer, uint32_t width, uint32_t height, uint32_t pitch)
+{
+	uint32_t idat_size = (1 + width*3) * height;
+	uint8_t *idat_buffer = malloc(idat_size);
+	uint32_t *pixel = buffer;
+	uint8_t *cur = idat_buffer;
+	for (uint32_t y = 0; y < height; y++)
+	{
+		//save filter type
+		*(cur++) = 0;
+		uint32_t *start = pixel;
+		for (uint32_t x = 0; x < width; x++, pixel++)
+		{
+			uint32_t value = *pixel;
+			*(cur++) = value >> 16;
+			*(cur++) = value >> 8;
+			*(cur++) = value;
+		}
+		pixel = start + pitch / sizeof(uint32_t);
+	}
+	write_header(f, width, height, COLOR_TRUE);
+	uLongf compress_buffer_size = idat_size + 5 * (idat_size/16383 + 1) + 3;
+	uint8_t *compressed = malloc(compress_buffer_size);
+	compress(compressed, &compress_buffer_size, idat_buffer, idat_size);
+	free(idat_buffer);
+	write_chunk(f, idat, compressed, compress_buffer_size);
+	write_chunk(f, iend, NULL, 0);
+	free(compressed);
+}
+
+void save_png(FILE *f, uint32_t *buffer, uint32_t width, uint32_t height, uint32_t pitch)
+{
+	uint32_t palette[256];
+	uint8_t pal_buffer[256*3];
+	uint32_t num_pal = 0;
+	uint32_t index_size = (1 + width) * height;
+	uint8_t *index_buffer = malloc(index_size);
+	uint8_t *cur = index_buffer;
+	uint32_t *pixel = buffer;
+	for (uint32_t y = 0; y < height; y++)
+	{
+		//save filter type
+		*(cur++) = 0;
+		uint32_t *start = pixel;
+		for (uint32_t x = 0; x < width; x++, pixel++, cur++)
+		{
+			uint32_t value = (*pixel) & 0xFFFFFF;
+			uint32_t i;
+			for (i = 0; i < num_pal; i++)
+			{
+				if (palette[i] == value) {
+					break;
+				}
+			}
+			if (i == num_pal) {
+				if (num_pal == 256) {
+					free(index_buffer);
+					save_png24(f, buffer, width, height, pitch);
+					return;
+				}
+				palette[i] = value;
+				num_pal++;
+			}
+			*cur = i;
+		}
+		pixel = start + pitch / sizeof(uint32_t);
+	}
+	write_header(f, width, height, COLOR_INDEXED);
+	cur = pal_buffer;
+	for (uint32_t i = 0; i < num_pal; i++)
+	{
+		*(cur++) = palette[i] >> 16;
+		*(cur++) = palette[i] >> 8;
+		*(cur++) = palette[i];
+	}
+	write_chunk(f, plte, pal_buffer, num_pal * 3);
+	uLongf compress_buffer_size = index_size + 5 * (index_size/16383 + 1) + 3;
+	uint8_t *compressed = malloc(compress_buffer_size);
+	compress(compressed, &compress_buffer_size, index_buffer, index_size);
+	free(index_buffer);
+	write_chunk(f, idat, compressed, compress_buffer_size);
+	write_chunk(f, iend, NULL, 0);
+	free(compressed);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/png.h	Sat Mar 24 19:40:51 2018 -0700
@@ -0,0 +1,7 @@
+#ifndef PNG_H_
+#define PNG_H_
+
+void save_png24(FILE *f, uint32_t *buffer, uint32_t width, uint32_t height, uint32_t pitch);
+void save_png(FILE *f, uint32_t *buffer, uint32_t width, uint32_t height, uint32_t pitch);
+
+#endif //PNG_H_
--- a/render_sdl.c	Sat Mar 24 15:33:44 2018 -0700
+++ b/render_sdl.c	Sat Mar 24 19:40:51 2018 -0700
@@ -13,6 +13,7 @@
 #include "io.h"
 #include "util.h"
 #include "ppm.h"
+#include "png.h"
 
 #ifndef DISABLE_OPENGL
 #include <GL/glew.h>
@@ -597,9 +598,13 @@
 		: 240;
 	FILE *screenshot_file = NULL;
 	uint32_t shot_height, shot_width;
+	char *ext;
 	if (screenshot_path && which == FRAMEBUFFER_ODD) {
 		screenshot_file = fopen(screenshot_path, "wb");
 		if (screenshot_file) {
+#ifndef DISABLE_ZLIB
+			ext = path_extension(screenshot_path);
+#endif
 			info_message("Saving screenshot to %s\n", screenshot_path);
 		} else {
 			warning("Failed to open screenshot file %s for writing\n", screenshot_path);
@@ -643,7 +648,17 @@
 		
 		if (screenshot_file) {
 			//properly supporting interlaced modes here is non-trivial, so only save the odd field for now
-			save_ppm(screenshot_file, texture_buf, shot_width, shot_height, LINEBUF_SIZE*sizeof(uint32_t));
+#ifndef DISABLE_ZLIB
+			if (!strcasecmp(ext, "png")) {
+				free(ext);
+				save_png(screenshot_file, texture_buf, shot_width, shot_height, LINEBUF_SIZE*sizeof(uint32_t));
+			} else {
+				free(ext);
+#endif
+				save_ppm(screenshot_file, texture_buf, shot_width, shot_height, LINEBUF_SIZE*sizeof(uint32_t));
+#ifndef DISABLE_ZLIB
+			}
+#endif
 		}
 	} else {
 #endif
@@ -670,7 +685,17 @@
 			} else {
 				shot_pitch *= 2;
 			}
-			save_ppm(screenshot_file, locked_pixels, shot_width, shot_height, shot_pitch);
+#ifndef DISABLE_ZLIB
+			if (!strcasecmp(ext, "png")) {
+				free(ext);
+				save_png(screenshot_file, locked_pixels, shot_width, shot_height, shot_pitch);
+			} else {
+				free(ext);
+#endif
+				save_ppm(screenshot_file, locked_pixels, shot_width, shot_height, shot_pitch);
+#ifndef DISABLE_ZLIB
+			}
+#endif
 		}
 		SDL_UnlockTexture(sdl_textures[which]);
 		SDL_Rect src_clip = {