This repository has been archived by the owner on Mar 30, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 37
/
write_png.c
123 lines (107 loc) · 3.13 KB
/
write_png.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include <assert.h>
#include <png.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include "write_png.h"
static void pack_row32(uint8_t *restrict row_out, const uint32_t *restrict row_in,
size_t width, bool fully_opaque) {
for (size_t x = 0; x < width; x++) {
uint8_t b = (row_in[x] >> 0) & 0xff;
uint8_t g = (row_in[x] >> 8) & 0xff;
uint8_t r = (row_in[x] >> 16) & 0xff;
uint8_t a = (row_in[x] >> 24) & 0xff;
// Unpremultiply pixels, if necessary. In practice, few images
// made by grim will have many pixels with fractional alpha
if (!fully_opaque && (a != 0 && a != 255)) {
uint32_t inv = (0xff << 16) / a;
uint32_t sr = r * inv;
r = sr > (0xff << 16) ? 0xff : (sr >> 16);
uint32_t sg = g * inv;
g = sg > (0xff << 16) ? 0xff : (sg >> 16);
uint32_t sb = b * inv;
b = sb > (0xff << 16) ? 0xff : (sb >> 16);
}
*row_out++ = r;
*row_out++ = g;
*row_out++ = b;
if (!fully_opaque) {
*row_out++ = a;
}
}
}
int write_to_png_stream(pixman_image_t *image, FILE *stream,
int comp_level) {
pixman_format_code_t format = pixman_image_get_format(image);
assert(format == PIXMAN_a8r8g8b8 || format == PIXMAN_x8r8g8b8);
int width = pixman_image_get_width(image);
int height = pixman_image_get_height(image);
int stride = pixman_image_get_stride(image);
const unsigned char *data = (unsigned char *)pixman_image_get_data(image);
bool fully_opaque = true;
if (format == PIXMAN_a8r8g8b8) {
for (int y = 0; y < height; y++) {
const uint32_t *row = (const uint32_t *)(data + y * stride);
for (int x = 0; x < width; x++) {
if ((row[x] >> 24) != 0xff) {
fully_opaque = false;
}
}
}
}
int color_type = fully_opaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGBA;
int bit_depth = 8;
uint8_t *tmp_row = calloc(width, 4);
if (!tmp_row) {
fprintf(stderr, "failed to allocate temp row\n");
return -1;
}
int ret = 0;
png_struct *png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL);
png_info *info = NULL;
if (!png) {
fprintf(stderr, "failed to allocate png struct\n");
ret = -1;
goto cleanup;
}
info = png_create_info_struct(png);
if (!info) {
fprintf(stderr, "failed to allocate png write struct\n");
ret = -1;
goto cleanup;
}
#ifdef PNG_SETJMP_SUPPORTED
if (setjmp(png_jmpbuf(png))) {
fprintf(stderr, "failed to write png\n");
ret = -1;
goto cleanup;
}
#endif
png_init_io(png, stream);
png_set_IHDR(png, info, width, height, bit_depth, color_type,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
png_write_info(png, info);
// If the level is zero (no compression), filtering will be unnecessary
png_set_compression_level(png, comp_level);
if (comp_level == 0) {
png_set_filter(png, 0, PNG_NO_FILTERS);
} else {
png_set_filter(png, 0, PNG_ALL_FILTERS);
}
for (int y = 0; y < height; y++) {
const uint32_t *row = (const uint32_t *)(data + y * stride);
pack_row32(tmp_row, row, width, fully_opaque);
png_write_row(png, tmp_row);
}
png_write_end(png, NULL);
cleanup:
if (info) {
png_destroy_info_struct(png, &info);
}
if (png) {
png_destroy_write_struct(&png, NULL);
}
free(tmp_row);
return ret;
}