diff png.c @ 2295:eb45ad9d8a3f

WIP "video" recording in APNG format
author Michael Pavone <pavone@retrodev.com>
date Fri, 10 Feb 2023 23:17:43 -0800
parents 81eebbe6b2e3
children 0111c8344477
line wrap: on
line diff
--- a/png.c	Fri Feb 10 21:37:59 2023 -0800
+++ b/png.c	Fri Feb 10 23:17:43 2023 -0800
@@ -3,12 +3,16 @@
 #include <stdio.h>
 #include <string.h>
 #include "zlib/zlib.h"
+#include "png.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'};
+static const char actl[] = {'a', 'c', 'T', 'L'};
+static const char fctl[] = {'f', 'c', 'T', 'L'};
+static const char fdat[] = {'f', 'd', 'A', 'T'};
 
 enum {
 	COLOR_GRAY,
@@ -56,7 +60,7 @@
 	write_chunk(f, ihdr, chunk, sizeof(chunk));
 }
 
-void save_png24(FILE *f, uint32_t *buffer, uint32_t width, uint32_t height, uint32_t pitch)
+void save_png24_frame(FILE *f, uint32_t *buffer, apng_state *apng, uint32_t width, uint32_t height, uint32_t pitch)
 {
 	uint32_t idat_size = (1 + width*3) * height;
 	uint8_t *idat_buffer = malloc(idat_size);
@@ -76,14 +80,78 @@
 		}
 		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;
+	uint32_t offset = 0;
+	if (apng) {
+		uint8_t chunk[26] = {
+			apng->sequence_number >> 24, apng->sequence_number >> 16,
+			apng->sequence_number >> 8, apng->sequence_number,
+			width >> 24, width >> 16, width >> 8, width,
+			height >> 24, height >> 16, height >> 8, height,
+			0, 0, 0, 0, //x offset
+			0, 0, 0, 0, //y offset
+			apng->delay_num >> 8, apng->delay_num,
+			apng->delay_den >> 8, apng->delay_den,
+			0, 0 //dispose and blend ops
+		};
+		write_chunk(f, fctl, chunk, sizeof(chunk));
+		apng->sequence_number++;
+		apng->num_frames++;
+		if (apng->sequence_number > 1) {
+			offset = sizeof(uint32_t);
+			compress_buffer_size += offset;
+		}
+	}
 	uint8_t *compressed = malloc(compress_buffer_size);
-	compress(compressed, &compress_buffer_size, idat_buffer, idat_size);
+	compress_buffer_size -= offset;
+	compress(compressed + offset, &compress_buffer_size, idat_buffer, idat_size);
 	free(idat_buffer);
-	write_chunk(f, idat, compressed, compress_buffer_size);
+	if (offset) {
+		cur = compressed;
+		*(cur++) = apng->sequence_number >> 24;
+		*(cur++) = apng->sequence_number >> 16;
+		*(cur++) = apng->sequence_number >> 8;
+		*(cur++) = apng->sequence_number;
+		apng->sequence_number++;
+	}
+	write_chunk(f, offset ? fdat : idat, compressed, compress_buffer_size + offset);
+	free(compressed);
+}
+
+apng_state* start_apng(FILE *f, uint32_t width, uint32_t height, float frame_rate)
+{
+	write_header(f, width, height, COLOR_TRUE);
+	apng_state *apng = calloc(1, sizeof(apng_state));
+	uint8_t chunk[] = {
+		0, 0, 0, 0,
+		0, 0, 0, 1
+	};
+	apng->num_frame_offset = ftell(f) + 8;
+	write_chunk(f, actl, chunk, sizeof(chunk));
+	apng->delay_num = 65535.0f / frame_rate;
+	apng->delay_den = frame_rate * apng->delay_num;
+	return apng;
+}
+
+void end_apng(FILE *f, apng_state *apng)
+{
 	write_chunk(f, iend, NULL, 0);
-	free(compressed);
+	fseek(f, apng->num_frame_offset, SEEK_SET);
+	uint8_t bytes[] = {
+		apng->num_frames >> 24, apng->num_frames >> 16, 
+		apng->num_frames >> 8, apng->num_frames
+	};
+	fwrite(bytes, 1, sizeof(bytes), f);
+	fclose(f);
+	free(apng);
+}
+
+void save_png24(FILE *f, uint32_t *buffer, uint32_t width, uint32_t height, uint32_t pitch)
+{
+	write_header(f, width, height, COLOR_TRUE);
+	save_png24_frame(f, buffer, NULL, width, height, pitch);
+	write_chunk(f, iend, NULL, 0);
 }
 
 void save_png(FILE *f, uint32_t *buffer, uint32_t width, uint32_t height, uint32_t pitch)