cosmopolitan/third_party/stb/stb_image_write_png.c

379 lines
12 KiB
C

/* stb_image_write - v1.13 - public domain - http://nothings.org/stb
* writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015
* no warranty implied; use at your own risk
*
* ABOUT:
*
* This file is a library for writing images to stdio or a callback.
*
* The PNG output is not optimal; it is 20-50% larger than the file
* written by a decent optimizing implementation; though providing a
* custom zlib compress function (see STBIW_ZLIB_COMPRESS) can
* mitigate that. This library is designed for source code
* compactness and simplicity, not optimal image file size or
* run-time performance.
*
* USAGE:
*
* There are five functions, one for each image file format:
*
* stbi_write_png
* stbi_write_bmp
* stbi_write_tga
* stbi_write_jpg
* stbi_write_hdr
*
* stbi_flip_vertically_on_write
*
* There are also five equivalent functions that use an arbitrary
* write function. You are expected to open/close your
* file-equivalent before and after calling these:
*
* stbi_write_png_to_func
* stbi_write_bmp_to_func
* stbi_write_tga_to_func
* stbi_write_hdr_to_func
* stbi_write_jpg_to_func
*
* where the callback is:
* void stbi_write_func(void *context, void *data, int size);
*
* You can configure it with these:
* stbi_write_tga_with_rle
* stbi_write_png_compression_level
* stbi_write_force_png_filter
*
* Each function returns 0 on failure and non-0 on success.
*
* The functions create an image file defined by the parameters. The
* image is a rectangle of pixels stored from left-to-right,
* top-to-bottom. Each pixel contains 'comp' channels of data stored
* interleaved with 8-bits per channel, in the following order: 1=Y,
* 2=YA, 3=RGB, 4=RGBA. (Y is monochrome color.) The rectangle is 'w'
* pixels wide and 'h' pixels tall. The *data pointer points to the
* first byte of the top-left-most pixel. For PNG, "stride_in_bytes"
* is the distance in bytes from the first byte of a row of pixels to
* the first byte of the next row of pixels.
*
* PNG creates output files with the same number of components as the
* input. The BMP format expands Y to RGB in the file format and does
* not output alpha.
*
* PNG supports writing rectangles of data even when the bytes
* storing rows of data are not consecutive in memory (e.g.
* sub-rectangles of a larger image), by supplying the stride between
* the beginning of adjacent rows. The other formats do not. (Thus
* you cannot write a native-format BMP through the BMP writer, both
* because it is in BGR order and because it may have padding at the
* end of the line.)
*
* PNG allows you to set the deflate compression level by setting the
* global variable 'stbi_write_png_compression_level' (it defaults to
* 8).
*
* HDR expects linear float data. Since the format is always 32-bit
* rgb(e) data, alpha (if provided) is discarded, and for monochrome
* data it is replicated across all three channels.
*
* TGA supports RLE or non-RLE compressed data. To use
* non-RLE-compressed data, set the global variable
* 'stbi_write_tga_with_rle' to 0.
*
* JPEG does ignore alpha channels in input data; quality is between
* 1 and 100. Higher quality looks better but results in a bigger
* image. JPEG baseline (no JPEG progressive).
*
* CREDITS:
*
*
* Sean Barrett - PNG/BMP/TGA
* Baldur Karlsson - HDR
* Jean-Sebastien Guay - TGA monochrome
* Tim Kelsey - misc enhancements
* Alan Hickman - TGA RLE
* Emmanuel Julien - initial file IO callback implementation
* Jon Olick - original jo_jpeg.cpp code
* Daniel Gibson - integrate JPEG, allow external zlib
* Aarni Koskela - allow choosing PNG filter
*
* bugfixes:
* github:Chribba
* Guillaume Chereau
* github:jry2
* github:romigrou
* Sergio Gonzalez
* Jonas Karlsson
* Filip Wasil
* Thatcher Ulrich
* github:poppolopoppo
* Patrick Boettcher
* github:xeekworx
* Cap Petschulat
* Simon Rodriguez
* Ivan Tikhonov
* github:ignotion
* Adam Schackart
*
* LICENSE
*
* Public Domain (www.unlicense.org)
*/
#include "libc/assert.h"
#include "libc/fmt/conv.h"
#include "libc/limits.h"
#include "libc/mem/mem.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "third_party/stb/stb_image_write.h"
#include "third_party/zlib/zlib.h"
#define STBIW_UCHAR(x) (unsigned char)((x)&0xff)
#define stbiw__wpng4(o, a, b, c, d) \
((o)[0] = STBIW_UCHAR(a), (o)[1] = STBIW_UCHAR(b), (o)[2] = STBIW_UCHAR(c), \
(o)[3] = STBIW_UCHAR(d), (o) += 4)
#define stbiw__wp32(data, v) \
stbiw__wpng4(data, (v) >> 24, (v) >> 16, (v) >> 8, (v));
#define stbiw__wptag(data, s) stbiw__wpng4(data, s[0], s[1], s[2], s[3])
int stbi_write_png_compression_level = 4;
int stbi_write_force_png_filter = -1;
static unsigned char *stbi_zlib_compress(unsigned char *data, int size,
int *out_len, int quality) {
unsigned long newsize;
unsigned char *newdata, *trimdata;
assert(0 <= size && size <= INT_MAX);
if ((newdata = malloc((newsize = compressBound(size)))) &&
compress2(newdata, &newsize, data, size,
stbi_write_png_compression_level) == Z_OK) {
*out_len = newsize;
if ((trimdata = realloc(newdata, newsize))) {
return trimdata;
} else {
return newdata;
}
}
free(newdata);
return NULL;
}
static void stbiw__wpcrc(unsigned char **data, int len) {
unsigned int crc = crc32(0, *data - len - 4, len + 4);
stbiw__wp32(*data, crc);
}
forceinline unsigned char stbiw__paeth(int a, int b, int c) {
int p = a + b - c, pa = abs(p - a), pb = abs(p - b), pc = abs(p - c);
if (pa <= pb && pa <= pc) return STBIW_UCHAR(a);
if (pb <= pc) return STBIW_UCHAR(b);
return STBIW_UCHAR(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) {
const int mapping[] = {0, 1, 2, 3, 4};
const int firstmap[] = {0, 1, 0, 5, 6};
unsigned char *z;
int *mymap, i, type, signed_stride;
mymap = (y != 0) ? mapping : firstmap;
type = mymap[filter_type];
z = pixels +
stride_bytes * (stbi__flip_vertically_on_write ? height - 1 - y : y);
signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes;
if (type == 0) {
memcpy(line_buffer, z, width * n);
return;
}
for (i = 0; i < n; ++i) {
switch (type) {
case 1:
line_buffer[i] = z[i];
break;
case 2:
line_buffer[i] = z[i] - z[i - signed_stride];
break;
case 3:
line_buffer[i] = z[i] - (z[i - signed_stride] >> 1);
break;
case 4:
line_buffer[i] =
(signed char)(z[i] - stbiw__paeth(0, z[i - signed_stride], 0));
break;
case 5:
line_buffer[i] = z[i];
break;
case 6:
line_buffer[i] = z[i];
break;
}
}
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;
}
}
unsigned char *stbi_write_png_to_mem(const unsigned char *pixels,
int stride_bytes, int x, int y, int n,
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;
if (stride_bytes == 0) stride_bytes = x * n;
if (force_filter >= 5) {
force_filter = -1;
}
filt = malloc((x * n + 1) * y);
if (!filt) return 0;
line_buffer = malloc(x * n);
if (!line_buffer) {
free(filt);
return 0;
}
for (j = 0; j < y; ++j) {
int filter_type;
if (force_filter > -1) {
filter_type = force_filter;
stbiw__encode_png_line(pixels, stride_bytes, x, y, j, n, force_filter,
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(pixels, stride_bytes, x, y, j, n, filter_type,
line_buffer);
// Estimate the entropy of the line using this filter; the less, the
// better.
est = 0;
for (i = 0; i < x * n; ++i) {
est += abs((signed char)line_buffer[i]);
}
if (est < best_filter_val) {
best_filter_val = est;
best_filter = filter_type;
}
}
if (filter_type != best_filter) { // If the last iteration already got us
// the best filter, don't redo it
stbiw__encode_png_line(pixels, stride_bytes, x, y, j, n, best_filter,
line_buffer);
filter_type = best_filter;
}
}
// 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;
memmove(filt + j * (x * n + 1) + 1, line_buffer, x * n);
}
free(line_buffer);
zlib = stbi_zlib_compress(filt, y * (x * n + 1), &zlen,
stbi_write_png_compression_level);
free(filt);
if (!zlib) return 0;
// each tag requires 12 bytes of overhead
out = malloc(8 + 12 + 13 + 12 + zlen + 12);
if (!out) return 0;
*out_len = 8 + 12 + 13 + 12 + zlen + 12;
o = out;
memmove(o, sig, 8);
o += 8;
stbiw__wp32(o, 13); // header length
stbiw__wptag(o, "IHDR");
stbiw__wp32(o, x);
stbiw__wp32(o, y);
*o++ = 8;
*o++ = STBIW_UCHAR(ctype[n]);
*o++ = 0;
*o++ = 0;
*o++ = 0;
stbiw__wpcrc(&o, 13);
stbiw__wp32(o, zlen);
stbiw__wptag(o, "IDAT");
memmove(o, zlib, zlen);
o += zlen;
free(zlib);
stbiw__wpcrc(&o, zlen);
stbiw__wp32(o, 0);
stbiw__wptag(o, "IEND");
stbiw__wpcrc(&o, 0);
assert(o == out + *out_len);
return out;
}
int stbi_write_png(const char *filename, int x, int y, int comp,
const void *data, int stride_bytes) {
int len;
FILE *f;
unsigned char *png;
png = stbi_write_png_to_mem(data, stride_bytes, x, y, comp, &len);
if (png == NULL) return 0;
f = fopen(filename, "wb");
if (!f) {
free(png);
return 0;
}
fwrite(png, 1, len, f);
fclose(f);
free(png);
return 1;
}
int stbi_write_png_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;
png = stbi_write_png_to_mem((const unsigned char *)data, stride_bytes, x, y,
comp, &len);
if (png == NULL) return 0;
func(context, png, len);
free(png);
return 1;
}