379 lines
12 KiB
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;
|
|
}
|