Files
test2/source/blender/imbuf/intern/png.c
Sergey Sharybin 08cea96ab0 Alpha premul pipeline cleanup
This assumptions are now made:
- Internally float buffers are always linear alpha-premul colors
- Readers should worry about delivering float buffers with that
  assumptions.
- There's an input image setting to say whether it's stored with
  straight/premul alpha on the disk.
- Byte buffers are now assumed have straight alpha, readers should
  deliver straight alpha.

Some implementation details:

- Removed scene's color unpremultiply setting, which was very
  much confusing and was wrong for default settings.
  Now all renderers assumes to deliver premultiplied alpha.

- IMB_buffer_byte_from_float will now linearize alpha when
  converting from buffer.

- Sequencer's effects were changed to assume bytes have got
  straight alpha. Most of effects will work with bytes still,
  however for glow it was more tricky to avoid data loss, so
  there's a commented out glow implementation which converts
  byte buffer to floats first, operates on floats and returns
  bytes back. It's slower and not sure if it should actually
  be used -- who're using glow on alpha anyway?

- Sequencer modifiers should also be working nice with straight
  bytes now.

- GLSL preview will predivide float textures to make nice shading,
  shading with byte textures worked nice (GLSL was assuming straight
  alpha).

- Blender Internal will set alpha=1 to the whole sky. The same
  happens in Cycles and there's no way to avoid this -- sky is
  neither straight nor premul and doesn't fit color pipeline well.

- Straight alpha mode for render result was also eliminated.

- Conversion to correct alpha need to be done before linearizing
  float buffer.

- TIFF will now load and save files with proper alpha mode setting
  in file meta data header.

- Remove Use Alpha from texture mapping and replaced with image
  datablock setting.

  Behaves much more predictable and clear from code point of view
  and solves possible regressions when non-premultiplied images were
  used as textures with ignoring alpha channel.
2012-12-31 13:52:13 +00:00

633 lines
16 KiB
C

/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): none yet.
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/imbuf/intern/png.c
* \ingroup imbuf
*
* \todo Save floats as 16 bits per channel, currently readonly.
*/
#include "png.h"
#include "BLI_fileops.h"
#include "BLI_math.h"
#include "MEM_guardedalloc.h"
#include "imbuf.h"
#include "IMB_imbuf_types.h"
#include "IMB_imbuf.h"
#include "IMB_allocimbuf.h"
#include "IMB_metadata.h"
#include "IMB_filetype.h"
#include "IMB_colormanagement.h"
#include "IMB_colormanagement_intern.h"
typedef struct PNGReadStruct {
unsigned char *data;
unsigned int size;
unsigned int seek;
} PNGReadStruct;
static void ReadData(png_structp png_ptr, png_bytep data, png_size_t length);
static void WriteData(png_structp png_ptr, png_bytep data, png_size_t length);
static void Flush(png_structp png_ptr);
int imb_is_a_png(unsigned char *mem)
{
int ret_val = 0;
if (mem) ret_val = !png_sig_cmp(mem, 0, 8);
return(ret_val);
}
static void Flush(png_structp png_ptr)
{
(void)png_ptr;
}
static void WriteData(png_structp png_ptr, png_bytep data, png_size_t length)
{
ImBuf *ibuf = (ImBuf *) png_get_io_ptr(png_ptr);
/* if buffer is to small increase it. */
while (ibuf->encodedsize + length > ibuf->encodedbuffersize) {
imb_enlargeencodedbufferImBuf(ibuf);
}
memcpy(ibuf->encodedbuffer + ibuf->encodedsize, data, length);
ibuf->encodedsize += length;
}
static void ReadData(png_structp png_ptr, png_bytep data, png_size_t length)
{
PNGReadStruct *rs = (PNGReadStruct *) png_get_io_ptr(png_ptr);
if (rs) {
if (length <= rs->size - rs->seek) {
memcpy(data, rs->data + rs->seek, length);
rs->seek += length;
return;
}
}
printf("Reached EOF while decoding PNG\n");
longjmp(png_jmpbuf(png_ptr), 1);
}
int imb_savepng(struct ImBuf *ibuf, const char *name, int flags)
{
png_structp png_ptr;
png_infop info_ptr;
unsigned char *pixels = NULL;
unsigned char *from, *to;
unsigned short *pixels16 = NULL, *to16;
float *from_float, from_straight[4];
png_bytepp row_pointers = NULL;
int i, bytesperpixel, color_type = PNG_COLOR_TYPE_GRAY;
FILE *fp = NULL;
int is_16bit = (ibuf->ftype & PNG_16BIT) && ibuf->rect_float;
/* use the jpeg quality setting for compression */
int compression;
compression = (int)(((float)(ibuf->ftype & 0xff) / 11.1111f));
compression = compression < 0 ? 0 : (compression > 9 ? 9 : compression);
/* for prints */
if (flags & IB_mem)
name = "<memory>";
bytesperpixel = (ibuf->planes + 7) >> 3;
if ((bytesperpixel > 4) || (bytesperpixel == 2)) {
printf("imb_savepng: Cunsupported bytes per pixel: %d for file: '%s'\n", bytesperpixel, name);
return (0);
}
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL);
if (png_ptr == NULL) {
printf("imb_savepng: Cannot png_create_write_struct for file: '%s'\n", name);
return 0;
}
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) {
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
printf("imb_savepng: Cannot png_create_info_struct for file: '%s'\n", name);
return 0;
}
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_write_struct(&png_ptr, &info_ptr);
printf("imb_savepng: Cannot setjmp for file: '%s'\n", name);
return 0;
}
/* copy image data */
if (is_16bit)
pixels16 = MEM_mallocN(ibuf->x * ibuf->y * bytesperpixel * sizeof(unsigned short), "png 16bit pixels");
else
pixels = MEM_mallocN(ibuf->x * ibuf->y * bytesperpixel * sizeof(unsigned char), "png 8bit pixels");
if (pixels == NULL && pixels16 == NULL) {
png_destroy_write_struct(&png_ptr, &info_ptr);
printf("imb_savepng: Cannot allocate pixels array of %dx%d, %d bytes per pixel for file: '%s'\n", ibuf->x, ibuf->y, bytesperpixel, name);
return 0;
}
from = (unsigned char *) ibuf->rect;
to = pixels;
from_float = ibuf->rect_float;
to16 = pixels16;
switch (bytesperpixel) {
case 4:
color_type = PNG_COLOR_TYPE_RGBA;
if (is_16bit) {
for (i = ibuf->x * ibuf->y; i > 0; i--) {
premul_to_straight_v4(from_straight, from_float);
to16[0] = FTOUSHORT(from_straight[0]);
to16[1] = FTOUSHORT(from_straight[1]);
to16[2] = FTOUSHORT(from_straight[2]);
to16[3] = FTOUSHORT(from_straight[3]);
to16 += 4; from_float += 4;
}
}
else {
for (i = ibuf->x * ibuf->y; i > 0; i--) {
to[0] = from[0];
to[1] = from[1];
to[2] = from[2];
to[3] = from[3];
to += 4; from += 4;
}
}
break;
case 3:
color_type = PNG_COLOR_TYPE_RGB;
if (is_16bit) {
for (i = ibuf->x * ibuf->y; i > 0; i--) {
premul_to_straight_v4(from_straight, from_float);
to16[0] = FTOUSHORT(from_straight[0]);
to16[1] = FTOUSHORT(from_straight[1]);
to16[2] = FTOUSHORT(from_straight[2]);
to16 += 3; from_float += 4;
}
}
else {
for (i = ibuf->x * ibuf->y; i > 0; i--) {
to[0] = from[0];
to[1] = from[1];
to[2] = from[2];
to += 3; from += 4;
}
}
break;
case 1:
color_type = PNG_COLOR_TYPE_GRAY;
if (is_16bit) {
for (i = ibuf->x * ibuf->y; i > 0; i--) {
premul_to_straight_v4(from_straight, from_float);
to16[0] = FTOUSHORT(from_straight[0]);
to16++; from_float += 4;
}
}
else {
for (i = ibuf->x * ibuf->y; i > 0; i--) {
to[0] = from[0];
to++; from += 4;
}
}
break;
}
if (flags & IB_mem) {
/* create image in memory */
imb_addencodedbufferImBuf(ibuf);
ibuf->encodedsize = 0;
png_set_write_fn(png_ptr,
(png_voidp) ibuf,
WriteData,
Flush);
}
else {
fp = BLI_fopen(name, "wb");
if (!fp) {
png_destroy_write_struct(&png_ptr, &info_ptr);
if (pixels)
MEM_freeN(pixels);
if (pixels16)
MEM_freeN(pixels16);
printf("imb_savepng: Cannot open file for writing: '%s'\n", name);
return 0;
}
png_init_io(png_ptr, fp);
}
#if 0
png_set_filter(png_ptr, 0,
PNG_FILTER_NONE | PNG_FILTER_VALUE_NONE |
PNG_FILTER_SUB | PNG_FILTER_VALUE_SUB |
PNG_FILTER_UP | PNG_FILTER_VALUE_UP |
PNG_FILTER_AVG | PNG_FILTER_VALUE_AVG |
PNG_FILTER_PAETH | PNG_FILTER_VALUE_PAETH |
PNG_ALL_FILTERS);
#endif
png_set_compression_level(png_ptr, compression);
/* png image settings */
png_set_IHDR(png_ptr,
info_ptr,
ibuf->x,
ibuf->y,
is_16bit ? 16 : 8,
color_type,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
/* image text info */
if (ibuf->metadata) {
png_text *metadata;
ImMetaData *iptr;
int num_text = 0;
iptr = ibuf->metadata;
while (iptr) {
num_text++;
iptr = iptr->next;
}
metadata = MEM_callocN(num_text * sizeof(png_text), "png_metadata");
iptr = ibuf->metadata;
num_text = 0;
while (iptr) {
metadata[num_text].compression = PNG_TEXT_COMPRESSION_NONE;
metadata[num_text].key = iptr->key;
metadata[num_text].text = iptr->value;
num_text++;
iptr = iptr->next;
}
png_set_text(png_ptr, info_ptr, metadata, num_text);
MEM_freeN(metadata);
}
if (ibuf->ppm[0] > 0.0 && ibuf->ppm[1] > 0.0) {
png_set_pHYs(png_ptr, info_ptr, (unsigned int)(ibuf->ppm[0] + 0.5), (unsigned int)(ibuf->ppm[1] + 0.5), PNG_RESOLUTION_METER);
}
/* write the file header information */
png_write_info(png_ptr, info_ptr);
#ifdef __LITTLE_ENDIAN__
png_set_swap(png_ptr);
#endif
/* allocate memory for an array of row-pointers */
row_pointers = (png_bytepp) MEM_mallocN(ibuf->y * sizeof(png_bytep), "row_pointers");
if (row_pointers == NULL) {
printf("imb_savepng: Cannot allocate row-pointers array for file '%s'\n", name);
png_destroy_write_struct(&png_ptr, &info_ptr);
if (pixels)
MEM_freeN(pixels);
if (pixels16)
MEM_freeN(pixels16);
if (fp) {
fclose(fp);
}
return 0;
}
/* set the individual row-pointers to point at the correct offsets */
if (is_16bit) {
for (i = 0; i < ibuf->y; i++) {
row_pointers[ibuf->y - 1 - i] = (png_bytep)
((unsigned short *)pixels16 + (i * ibuf->x) * bytesperpixel);
}
}
else {
for (i = 0; i < ibuf->y; i++) {
row_pointers[ibuf->y - 1 - i] = (png_bytep)
((unsigned char *)pixels + (i * ibuf->x) * bytesperpixel * sizeof(unsigned char));
}
}
/* write out the entire image data in one call */
png_write_image(png_ptr, row_pointers);
/* write the additional chunks to the PNG file (not really needed) */
png_write_end(png_ptr, info_ptr);
/* clean up */
if (pixels)
MEM_freeN(pixels);
if (pixels16)
MEM_freeN(pixels16);
MEM_freeN(row_pointers);
png_destroy_write_struct(&png_ptr, &info_ptr);
if (fp) {
fflush(fp);
fclose(fp);
}
return(1);
}
ImBuf *imb_loadpng(unsigned char *mem, size_t size, int flags, char colorspace[IM_MAX_SPACE])
{
struct ImBuf *ibuf = NULL;
png_structp png_ptr;
png_infop info_ptr;
unsigned char *pixels = NULL;
unsigned short *pixels16 = NULL;
png_bytepp row_pointers = NULL;
png_uint_32 width, height;
int bit_depth, color_type;
PNGReadStruct ps;
unsigned char *from, *to;
unsigned short *from16;
float *to_float;
int i, bytesperpixel;
if (imb_is_a_png(mem) == 0) return(NULL);
/* both 8 and 16 bit PNGs are default to standard byte colorspace */
colorspace_set_default_role(colorspace, IM_MAX_SPACE, COLOR_ROLE_DEFAULT_BYTE);
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL);
if (png_ptr == NULL) {
printf("Cannot png_create_read_struct\n");
return NULL;
}
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) {
png_destroy_read_struct(&png_ptr, (png_infopp)NULL,
(png_infopp)NULL);
printf("Cannot png_create_info_struct\n");
return NULL;
}
ps.size = size; /* XXX, 4gig limit! */
ps.data = mem;
ps.seek = 0;
png_set_read_fn(png_ptr, (void *) &ps, ReadData);
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
if (pixels) MEM_freeN(pixels);
if (pixels16) MEM_freeN(pixels16);
if (row_pointers) MEM_freeN(row_pointers);
if (ibuf) IMB_freeImBuf(ibuf);
return NULL;
}
// png_set_sig_bytes(png_ptr, 8);
png_read_info(png_ptr, info_ptr);
png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth,
&color_type, NULL, NULL, NULL);
bytesperpixel = png_get_channels(png_ptr, info_ptr);
switch (color_type) {
case PNG_COLOR_TYPE_RGB:
case PNG_COLOR_TYPE_RGB_ALPHA:
break;
case PNG_COLOR_TYPE_PALETTE:
png_set_palette_to_rgb(png_ptr);
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
bytesperpixel = 4;
}
else {
bytesperpixel = 3;
}
break;
case PNG_COLOR_TYPE_GRAY:
case PNG_COLOR_TYPE_GRAY_ALPHA:
if (bit_depth < 8) {
png_set_expand(png_ptr);
bit_depth = 8;
}
break;
default:
printf("PNG format not supported\n");
longjmp(png_jmpbuf(png_ptr), 1);
}
ibuf = IMB_allocImBuf(width, height, 8 * bytesperpixel, 0);
if (ibuf) {
ibuf->ftype = PNG;
if (bit_depth == 16)
ibuf->ftype |= PNG_16BIT;
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_pHYs)) {
int unit_type;
png_uint_32 xres, yres;
if (png_get_pHYs(png_ptr, info_ptr, &xres, &yres, &unit_type))
if (unit_type == PNG_RESOLUTION_METER) {
ibuf->ppm[0] = xres;
ibuf->ppm[1] = yres;
}
}
}
else {
printf("Couldn't allocate memory for PNG image\n");
}
if (ibuf && ((flags & IB_test) == 0)) {
if (bit_depth == 16) {
imb_addrectfloatImBuf(ibuf);
png_set_swap(png_ptr);
pixels16 = MEM_mallocN(ibuf->x * ibuf->y * bytesperpixel * sizeof(png_uint_16), "pixels");
if (pixels16 == NULL) {
printf("Cannot allocate pixels array\n");
longjmp(png_jmpbuf(png_ptr), 1);
}
/* allocate memory for an array of row-pointers */
row_pointers = (png_bytepp) MEM_mallocN(ibuf->y * sizeof(png_uint_16p), "row_pointers");
if (row_pointers == NULL) {
printf("Cannot allocate row-pointers array\n");
longjmp(png_jmpbuf(png_ptr), 1);
}
/* set the individual row-pointers to point at the correct offsets */
for (i = 0; i < ibuf->y; i++) {
row_pointers[ibuf->y - 1 - i] = (png_bytep)
((png_uint_16 *)pixels16 + (i * ibuf->x) * bytesperpixel);
}
png_read_image(png_ptr, row_pointers);
/* copy image data */
to_float = ibuf->rect_float;
from16 = pixels16;
switch (bytesperpixel) {
case 4:
for (i = ibuf->x * ibuf->y; i > 0; i--) {
to_float[0] = from16[0] / 65535.0;
to_float[1] = from16[1] / 65535.0;
to_float[2] = from16[2] / 65535.0;
to_float[3] = from16[3] / 65535.0;
to_float += 4; from16 += 4;
}
break;
case 3:
for (i = ibuf->x * ibuf->y; i > 0; i--) {
to_float[0] = from16[0] / 65535.0;
to_float[1] = from16[1] / 65535.0;
to_float[2] = from16[2] / 65535.0;
to_float[3] = 1.0;
to_float += 4; from16 += 3;
}
break;
case 2:
for (i = ibuf->x * ibuf->y; i > 0; i--) {
to_float[0] = to_float[1] = to_float[2] = from16[0] / 65535.0;
to_float[3] = from16[1] / 65535.0;
to_float += 4; from16 += 2;
}
break;
case 1:
for (i = ibuf->x * ibuf->y; i > 0; i--) {
to_float[0] = to_float[1] = to_float[2] = from16[0] / 65535.0;
to_float[3] = 1.0;
to_float += 4; from16++;
}
break;
}
}
else {
imb_addrectImBuf(ibuf);
pixels = MEM_mallocN(ibuf->x * ibuf->y * bytesperpixel * sizeof(unsigned char), "pixels");
if (pixels == NULL) {
printf("Cannot allocate pixels array\n");
longjmp(png_jmpbuf(png_ptr), 1);
}
/* allocate memory for an array of row-pointers */
row_pointers = (png_bytepp) MEM_mallocN(ibuf->y * sizeof(png_bytep), "row_pointers");
if (row_pointers == NULL) {
printf("Cannot allocate row-pointers array\n");
longjmp(png_jmpbuf(png_ptr), 1);
}
/* set the individual row-pointers to point at the correct offsets */
for (i = 0; i < ibuf->y; i++) {
row_pointers[ibuf->y - 1 - i] = (png_bytep)
((unsigned char *)pixels + (i * ibuf->x) * bytesperpixel * sizeof(unsigned char));
}
png_read_image(png_ptr, row_pointers);
/* copy image data */
to = (unsigned char *) ibuf->rect;
from = pixels;
switch (bytesperpixel) {
case 4:
for (i = ibuf->x * ibuf->y; i > 0; i--) {
to[0] = from[0];
to[1] = from[1];
to[2] = from[2];
to[3] = from[3];
to += 4; from += 4;
}
break;
case 3:
for (i = ibuf->x * ibuf->y; i > 0; i--) {
to[0] = from[0];
to[1] = from[1];
to[2] = from[2];
to[3] = 0xff;
to += 4; from += 3;
}
break;
case 2:
for (i = ibuf->x * ibuf->y; i > 0; i--) {
to[0] = to[1] = to[2] = from[0];
to[3] = from[1];
to += 4; from += 2;
}
break;
case 1:
for (i = ibuf->x * ibuf->y; i > 0; i--) {
to[0] = to[1] = to[2] = from[0];
to[3] = 0xff;
to += 4; from++;
}
break;
}
}
if (flags & IB_metadata) {
png_text *text_chunks;
int count = png_get_text(png_ptr, info_ptr, &text_chunks, NULL);
for (i = 0; i < count; i++) {
IMB_metadata_add_field(ibuf, text_chunks[i].key, text_chunks[i].text);
ibuf->flags |= IB_metadata;
}
}
png_read_end(png_ptr, info_ptr);
}
/* clean up */
if (pixels)
MEM_freeN(pixels);
if (pixels16)
MEM_freeN(pixels16);
if (row_pointers)
MEM_freeN(row_pointers);
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
return(ibuf);
}