Skip to content

Commit

Permalink
Add write support for 16-bit PNG
Browse files Browse the repository at this point in the history
  • Loading branch information
WearerOfShoes committed May 14, 2019
1 parent 1034f5e commit 7ffb71e
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 17 deletions.
90 changes: 73 additions & 17 deletions stb_image_write.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,22 @@
USAGE:
There are five functions, one for each image file format:
There are six functions, one for each image file format:
int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes);
int stbi_write_png_16(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes);
int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data);
int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data);
int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality);
int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data);
void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically
There are also five equivalent functions that use an arbitrary write function. You are
There are also six equivalent functions that use an arbitrary write function. You are
expected to open/close your file-equivalent before and after calling these:
int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes);
int stbi_write_png_16_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes);
int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data);
int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data);
int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data);
Expand Down Expand Up @@ -178,6 +180,7 @@ extern int stbi_write_force_png_filter;

#ifndef STBI_WRITE_NO_STDIO
STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes);
STBIWDEF int stbi_write_png_16(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes);
STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data);
STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data);
STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data);
Expand All @@ -191,6 +194,7 @@ STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const w
typedef void stbi_write_func(void *context, void *data, int size);

STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes);
STBIWDEF int stbi_write_png_16_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes);
STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data);
STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data);
STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data);
Expand Down Expand Up @@ -345,8 +349,11 @@ static void stbi__end_write_file(stbi__write_context *s)

#endif // !STBI_WRITE_NO_STDIO

typedef unsigned short stbiw_uint16;
typedef int stb_image_write_test_uint16[sizeof(stbiw_uint16)==2 ? 1 : -1];

typedef unsigned int stbiw_uint32;
typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1];
typedef int stb_image_write_test_uint32[sizeof(stbiw_uint32)==4 ? 1 : -1];

static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v)
{
Expand Down Expand Up @@ -1040,7 +1047,7 @@ static unsigned char stbiw__paeth(int a, int b, int c)
}

// @OPTIMIZE: provide an option that always forces left-predict or paeth predict
static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer)
static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, int comp_bytes, signed char *line_buffer)
{
static int mapping[] = { 0,1,2,3,4 };
static int firstmap[] = { 0,1,0,5,6 };
Expand All @@ -1056,7 +1063,7 @@ static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int
}

// first loop isn't optimized since it's just one pixel
for (i = 0; i < n; ++i) {
for (i = 0; i < n*comp_bytes; ++i) {
switch (type) {
case 1: line_buffer[i] = z[i]; break;
case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break;
Expand All @@ -1067,23 +1074,29 @@ static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int
}
}
switch (type) {
case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break;
case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break;
case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break;
case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break;
case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break;
case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break;
case 1: for (i=n*comp_bytes; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n*comp_bytes]; break;
case 2: for (i=n*comp_bytes; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break;
case 3: for (i=n*comp_bytes; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n*comp_bytes] + z[i-signed_stride])>>1); break;
case 4: for (i=n*comp_bytes; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n*comp_bytes], z[i-signed_stride], z[i-signed_stride-n*comp_bytes]); break;
case 5: for (i=n*comp_bytes; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n*comp_bytes]>>1); break;
case 6: for (i=n*comp_bytes; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n*comp_bytes], 0,0); break;
}
}

STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len)
static unsigned char *stbiw__write_png_to_mem_core(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int comp_bytes, int *out_len)
{
int force_filter = stbi_write_force_png_filter;
int ctype[5] = { -1, 0, 4, 2, 6 };
unsigned char sig[8] = { 137,80,78,71,13,10,26,10 };
unsigned char *out,*o, *filt, *zlib;
signed char *line_buffer;
int j,zlen;
int k;

// all calculations in PNG are based on bytes in 16-bit mode,
// so just treated it as a twice-as-wide 8-bit image works in most cases
// exceptions are patches up by sending along comp_bytes
x *= comp_bytes;

if (stride_bytes == 0)
stride_bytes = x * n;
Expand All @@ -1098,11 +1111,11 @@ STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int s
int filter_type;
if (force_filter > -1) {
filter_type = force_filter;
stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer);
stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, comp_bytes, line_buffer);
} else { // Estimate the best filter by running through all of them:
int best_filter = 0, best_filter_val = 0x7fffffff, est, i;
for (filter_type = 0; filter_type < 5; filter_type++) {
stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer);
stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, comp_bytes, line_buffer);

// Estimate the entropy of the line using this filter; the less, the better.
est = 0;
Expand All @@ -1115,10 +1128,19 @@ STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int s
}
}
if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it
stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer);
stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, comp_bytes, line_buffer);
filter_type = best_filter;
}
}
if (comp_bytes == 2) {
// flip endian of 16-bit words in line buffer on LE machines (this is effectively a noop on BE)
unsigned char *line_buf_uint8 = (unsigned char *)line_buffer;
stbiw_uint16 *line_buf_uint16 = (stbiw_uint16 *)line_buffer;
for (k = 0; k < x*n/2; k++) {
stbiw_uint16 swapped = (((stbiw_uint16)line_buf_uint8[k*2]) << 8) | ((stbiw_uint16)line_buf_uint8[k*2+1]);
line_buf_uint16[k] = swapped;
}
}
// when we get here, filter_type contains the filter type, and line_buffer contains the data
filt[j*(x*n+1)] = (unsigned char) filter_type;
STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n);
Expand All @@ -1137,9 +1159,9 @@ STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int s
STBIW_MEMMOVE(o,sig,8); o+= 8;
stbiw__wp32(o, 13); // header length
stbiw__wptag(o, "IHDR");
stbiw__wp32(o, x);
stbiw__wp32(o, x/comp_bytes);
stbiw__wp32(o, y);
*o++ = 8;
*o++ = comp_bytes == 2 ? 16 : 8;
*o++ = STBIW_UCHAR(ctype[n]);
*o++ = 0;
*o++ = 0;
Expand All @@ -1162,6 +1184,17 @@ STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int s
return out;
}

STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len)
{
return stbiw__write_png_to_mem_core(pixels, stride_bytes, x, y, n, 1, out_len);
}

STBIWDEF unsigned char *stbi_write_png_16_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len)
{
return stbiw__write_png_to_mem_core(pixels, stride_bytes, x, y, n, 2, out_len);
}


#ifndef STBI_WRITE_NO_STDIO
STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes)
{
Expand All @@ -1177,6 +1210,20 @@ STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const
STBIW_FREE(png);
return 1;
}
STBIWDEF int stbi_write_png_16(char const *filename, int x, int y, int comp, const void *data, int stride_bytes)
{
FILE *f;
int len;
unsigned char *png = stbi_write_png_16_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len);
if (png == NULL) return 0;

f = stbiw__fopen(filename, "wb");
if (!f) { STBIW_FREE(png); return 0; }
fwrite(png, 1, len, f);
fclose(f);
STBIW_FREE(png);
return 1;
}
#endif

STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes)
Expand All @@ -1188,6 +1235,15 @@ STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x,
STBIW_FREE(png);
return 1;
}
STBIWDEF int stbi_write_png_16_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes)
{
int len;
unsigned char *png = stbi_write_png_16_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len);
if (png == NULL) return 0;
func(context, png, len);
STBIW_FREE(png);
return 1;
}


/* ***************************************************************************
Expand Down
53 changes: 53 additions & 0 deletions tests/image_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ void dummy_write(void *context, void *data, int len)
memcpy(dummy, data, len);
}

typedef struct {
unsigned char data[64*1024];
int len;
} data_t;

void write_data(void *context, void *data, int len)
{
data_t *d = (data_t *)context;
assert(d->len + len <= sizeof(d->data));
memcpy(&d->data[d->len], data, len);
d->len += len;
}

extern void image_write_test(void);

int main(int argc, char **argv)
Expand Down Expand Up @@ -168,5 +181,45 @@ int main(int argc, char **argv)
}
printf("Tested %d files.\n", i);
}

// test 16-bit png
{
int i;
char **files = stb_readdir_files("pngsuite/16bit");
for (i=0; i < stb_arr_len(files); ++i) {
int n;
int w2,h2,n2;
char **failed = NULL;
stbi_us *data;
stbi_us *data2;
data_t compressed_png;
compressed_png.len = 0;
printf(".");
//printf("%s\n", files[i]);
data = stbi_load_16(files[i], &w, &h, &n, 0); if (data) ; else stb_arr_push(failed, "&n");
if (data) {
stbi_write_png_16_to_func(write_data,&compressed_png, w, h, n, data, w*n*2);
data2 = stbi_load_16_from_memory(compressed_png.data, compressed_png.len, &w2, &h2, &n2, 0);
if (data2) ; else stb_arr_push(failed, "Couldn't load");

if (memcmp(data, data2, w*h*n*2) != 0) printf("FAILED: %s data differs\n", files[i]);
if (w != w2) printf("FAILED: %s w differs: %d vs %d\n", files[i], w, w2);
if (h != h2) printf("FAILED: %s h differs: %d vs %d\n", files[i], h, h2);
if (n != n2) printf("FAILED: %s n differs: %d vs %d\n", files[i], n, n2);

free(data2);
}
if (failed) {
int j;
printf("FAILED: ");
for (j=0; j < stb_arr_len(failed); ++j)
printf("%s ", failed[j]);
printf(" -- %s\n", files[i]);
}
free(data);
}
printf("Tested %d 16-bit PNG files.\n", i);
}

return 0;
}
7 changes: 7 additions & 0 deletions tests/image_write_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ void image_write_test(void)
// make a RGB version of the template image
// use red on blue to detect R<->B swaps
unsigned char img6x5_rgb[6*5*3];
unsigned short img6x5_rgb_16[6*5*3];
float img6x5_rgbf[6*5*3];
int i;

Expand All @@ -31,12 +32,17 @@ void image_write_test(void)
img6x5_rgb[i*3 + 1] = 0;
img6x5_rgb[i*3 + 2] = on ? 0 : 255;

img6x5_rgb_16[i*3 + 0] = on ? 65535 : 0;
img6x5_rgb_16[i*3 + 1] = 0;
img6x5_rgb_16[i*3 + 2] = on ? 0 : 65535;

img6x5_rgbf[i*3 + 0] = on ? 1.0f : 0.0f;
img6x5_rgbf[i*3 + 1] = 0.0f;
img6x5_rgbf[i*3 + 2] = on ? 0.0f : 1.0f;
}

stbi_write_png("output/wr6x5_regular.png", 6, 5, 3, img6x5_rgb, 6*3);
stbi_write_png_16("output/wr6x5_regular_16.png", 6, 5, 3, img6x5_rgb_16, 2*6*3);
stbi_write_bmp("output/wr6x5_regular.bmp", 6, 5, 3, img6x5_rgb);
stbi_write_tga("output/wr6x5_regular.tga", 6, 5, 3, img6x5_rgb);
stbi_write_jpg("output/wr6x5_regular.jpg", 6, 5, 3, img6x5_rgb, 95);
Expand All @@ -45,6 +51,7 @@ void image_write_test(void)
stbi_flip_vertically_on_write(1);

stbi_write_png("output/wr6x5_flip.png", 6, 5, 3, img6x5_rgb, 6*3);
stbi_write_png_16("output/wr6x5_flip_16.png", 6, 5, 3, img6x5_rgb_16, 2*6*3);
stbi_write_bmp("output/wr6x5_flip.bmp", 6, 5, 3, img6x5_rgb);
stbi_write_tga("output/wr6x5_flip.tga", 6, 5, 3, img6x5_rgb);
stbi_write_jpg("output/wr6x5_flip.jpg", 6, 5, 3, img6x5_rgb, 95);
Expand Down

0 comments on commit 7ffb71e

Please sign in to comment.