ultimatepp/uppsrc/plugin/png/pngupp.cpp
2025-04-07 10:21:39 +02:00

523 lines
14 KiB
C++

#ifdef flagSTATIC_PNG // Forces the use of internal PNG code even if host has libpng.so
#endif // just for mainconfig info
#if defined(flagWIN32) || defined(flagOSX) || defined(flagSTATIC_PNG)
#include <plugin/png/lib/png.h>
#else
#include <png.h>
#endif
#include <Draw/Draw.h>
#include "png.h"
namespace Upp {
#define LLOG(x) // LOG(x)
static void png_read_stream(png_structp png_ptr, png_bytep buffer, png_size_t length)
{
Stream& stream = *reinterpret_cast<Stream *>(png_get_io_ptr(png_ptr));
if(!stream.GetAll(buffer, (int)length))
png_error(png_ptr, "Error reading input file!");
}
static void png_write_stream(png_structp png_ptr, png_bytep buffer, png_size_t length)
{
Stream& stream = *reinterpret_cast<Stream *>(png_get_io_ptr(png_ptr));
stream.Put(buffer, (int)length);
}
static void png_flush_stream(png_structp png_ptr)
{
Stream& stream = *reinterpret_cast<Stream *>(png_get_io_ptr(png_ptr));
stream.Flush();
}
static void png_user_error_fn(png_structp png_ptr, png_const_charp error_msg)
{
LLOG("PNG error: " << error_msg);
longjmp(png_jmpbuf(png_ptr), 1);
}
static void png_user_warning_fn(png_structp png_ptr, png_const_charp warning_msg)
{
LLOG("png warning: " << warning_msg);
}
static Size GetDotSize(Size pixel_size, png_uint_32 x_ppm, png_uint_32 y_ppm, int unit_type)
{
if(unit_type != 1 || !x_ppm || !y_ppm)
return Size(0, 0);
static const double DOTS_PER_METER = 6e5 / 25.4;
return Size(
fround(pixel_size.cx * (DOTS_PER_METER / x_ppm)),
fround(pixel_size.cy * (DOTS_PER_METER / y_ppm)));
}
static void SetDotSize(Size pixel_size, Size dots_size, png_uint_32 &x_ppm, png_uint_32 &y_ppm, int unit_type)
{
if(unit_type != 1 || !pixel_size.cx || !pixel_size.cy || !dots_size.cx || !dots_size.cy) {
x_ppm = y_ppm = 0;
return;
}
static const double DOTS_PER_METER = 6e5 / 25.4;
x_ppm = fround(pixel_size.cx * (DOTS_PER_METER / dots_size.cx));
y_ppm = fround(pixel_size.cy * (DOTS_PER_METER / dots_size.cy));
}
class PNGRaster::Data {
public:
Data();
~Data();
public:
png_structp png_ptr;
png_infop info_ptr;
Info info;
Size size;
RasterFormat fmt;
RGBA palette[256];
int out_bpp;
int row_bytes;
Vector<byte> preimage;
Buffer<png_bytep> row_pointers;
bool preload;
bool loaded;
bool strip16;
int next_row;
int64 soff;
};
PNGRaster::Data::Data()
{
png_ptr = NULL;
info_ptr = NULL;
}
PNGRaster::Data::~Data()
{
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
}
PNGRaster::PNGRaster()
{
}
PNGRaster::~PNGRaster()
{
}
bool PNGRaster::Init()
{
if(!(data->png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
NULL, png_user_error_fn, png_user_warning_fn)))
return false;
if (setjmp(png_jmpbuf(data->png_ptr)))
return false;
if(!(data->info_ptr = png_create_info_struct(data->png_ptr)))
return false;
png_set_read_fn(data->png_ptr, &GetStream(), png_read_stream);
png_read_info(data->png_ptr, data->info_ptr);
return true;
}
bool PNGRaster::Create()
{
data = new Data;
data->soff = GetStream().GetPos();
if(!Init()) {
data.Clear();
return false;
}
if (setjmp(png_jmpbuf(data->png_ptr))) {
data.Clear();
return false;
}
png_uint_32 width, height;
int bit_depth, color_type, interlace_type;
png_get_IHDR(data->png_ptr, data->info_ptr, &width, &height, &bit_depth, &color_type,
&interlace_type, NULL, NULL);
if(height <= 0 || width <= 0)
return false;
data->size.cx = width;
data->size.cy = height;
data->info.bpp = bit_depth;
data->info.colors = (bit_depth < 8 ? 1 << bit_depth : 0);
data->info.hotspot = Point(0, 0);
data->info.kind = IMAGE_OPAQUE;
if (png_get_valid(data->png_ptr, data->info_ptr, PNG_INFO_pHYs)) {
png_uint_32 x_ppm, y_ppm;
int unit_type = 0;
png_get_pHYs(data->png_ptr, data->info_ptr, &x_ppm, &y_ppm, &unit_type);
if (unit_type == PNG_RESOLUTION_METER) // The only available option
data->info.dots = GetDotSize(data->size, x_ppm, y_ppm, unit_type);
}
data->out_bpp = bit_depth;
data->strip16 = (bit_depth > 8);
if(data->strip16) {
png_set_strip_16(data->png_ptr);
data->out_bpp = 8;
}
if(color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
ASSERT(bit_depth >= 8);
data->out_bpp = 24;
png_set_bgr(data->png_ptr);
if(color_type == PNG_COLOR_TYPE_RGB_ALPHA)
data->out_bpp = 32;
}
if(color_type & PNG_COLOR_MASK_ALPHA)
data->info.kind = IMAGE_ALPHA;
png_bytep trans_alpha = 0;
png_color_16p trans_values = 0;
int num_trans = 0;
png_get_tRNS(data->png_ptr, data->info_ptr, &trans_alpha, &num_trans, &trans_values);
// bool has_mask = (num_trans > 0);
// AlphaArray im(width, height, out_bpp, 4, NULL, Vector<Color>(), has_mask ? 8 : 0, 4);
// im.SetDotSize(GetDotSize(im.GetSize(), x_ppm, y_ppm, unit_type));
// ASSERT(im.GetRowBytes() >= png_get_rowbytes(png_ptr, info_ptr));
// if((color_type & PNG_COLOR_MASK_PALETTE) || !(color_type & PNG_COLOR_MASK_COLOR))
Fill(data->palette, RGBAZero(), 256);
if(color_type & PNG_COLOR_MASK_PALETTE) {
png_colorp ppal = 0;
int pal_count = 0;
png_get_PLTE(data->png_ptr, data->info_ptr, &ppal, &pal_count);
pal_count = min(pal_count, 1 << min(bit_depth, 8));
for(int i = 0; i < pal_count; i++) {
png_color c = ppal[i];
RGBA rgba;
rgba.r = c.red;
rgba.g = c.green;
rgba.b = c.blue;
rgba.a = 255;
if(trans_alpha && i < num_trans)
rgba.a = trans_alpha[i];
data->palette[i] = rgba;
}
Premultiply(data->palette, data->palette, pal_count);
if(trans_alpha)
data->info.kind = IMAGE_ALPHA;
}
else if(!(color_type & PNG_COLOR_MASK_COLOR)) { // grayscale
int colors = 1 << min(bit_depth, 8);
for(int i = 0; i < colors; i++) {
int level = i * 255 / (colors - 1);
RGBA rgba;
rgba.r = rgba.g = rgba.b = (byte)level;
rgba.a = 255;
if(trans_alpha && i < num_trans)
rgba.a = trans_alpha[i];
data->palette[i] = rgba;
}
Premultiply(data->palette, data->palette, colors);
}
/*
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
return out;
}
*/
data->row_bytes = (int)png_get_rowbytes(data->png_ptr, data->info_ptr);
data->next_row = 0;
data->preload = (interlace_type != PNG_INTERLACE_NONE);
data->loaded = false;
switch(data->out_bpp) {
case 1: data->fmt.Set1mf(); break;
case 2: data->fmt.Set2mf(); break;
case 4: data->fmt.Set4mf(); break;
case 8: if(color_type & PNG_COLOR_MASK_ALPHA) data->fmt.Set8A(); else data->fmt.Set8(); break;
case 24: data->fmt.Set24le(0xFF0000, 0xFF00, 0xFF); break;
case 32: data->fmt.SetRGBAStraight(); break;
default: NEVER(); return false; // invalid bpp
}
return true;
}
Size PNGRaster::GetSize()
{
return data ? data->size : Size(0, 0);
}
Raster::Info PNGRaster::GetInfo()
{
return data ? data->info : Raster::Info();
}
Raster::Line PNGRaster::GetLine(int line)
{
ASSERT(data && line >= 0 && line < data->size.cy);
byte *scanline = new byte[data->row_bytes];
if(setjmp(png_jmpbuf(data->png_ptr)))
return Raster::Line(scanline, this, true);
if(data->preload) {
delete[] scanline;
if(!data->loaded) {
data->loaded = true;
data->preimage.SetCount(data->row_bytes * data->size.cy);
data->row_pointers.Alloc(data->size.cy);
for(int i = 0; i < data->size.cy; i++)
data->row_pointers[i] = &data->preimage[i * data->row_bytes];
png_read_image(data->png_ptr, data->row_pointers);
}
const byte *rowdata = &data->preimage[data->row_bytes * line];
return Raster::Line(rowdata, this, false);
}
else {
if(line < data->next_row) {
png_destroy_read_struct(&data->png_ptr, &data->info_ptr, NULL);
GetStream().Seek(data->soff);
if(!Init()) {
NEVER();
}
if(data->strip16)
png_set_strip_16(data->png_ptr);
if(data->out_bpp > 8)
png_set_bgr(data->png_ptr);
data->next_row = 0;
}
while(data->next_row < line) {
png_read_row(data->png_ptr, scanline, NULL);
data->next_row++;
}
png_read_row(data->png_ptr, scanline, NULL);
data->next_row++;
return Raster::Line(scanline, this, true);
}
}
const RGBA *PNGRaster::GetPalette()
{
ASSERT(data);
return data->palette;
}
const RasterFormat *PNGRaster::GetFormat()
{
ASSERT(data);
return &data->fmt;
}
class PNGEncoder::Data {
public:
Data(RasterFormat& format);
~Data();
void Start(Stream& stream, Size size, Size dots, int bpp, ImageKind kind, bool interlace, const RGBA *palette);
void WriteLineRaw(const byte *data);
private:
RasterFormat& format;
ImageKind kind;
png_structp png_ptr;
png_infop info_ptr;
bool interlace;
bool do_palette;
bool do_mask;
bool do_alpha;
Vector<byte> imagebuf;
Vector<byte> rowbuf;
Size size;
Size dots;
int rowbytes;
int linebytes;
int passes;
int line;
};
PNGEncoder::Data::Data(RasterFormat& format)
: format(format)
{
png_ptr = NULL;
info_ptr = NULL;
}
PNGEncoder::Data::~Data()
{
png_destroy_write_struct(&png_ptr, &info_ptr);
}
void PNGEncoder::Data::Start(Stream& stream, Size size_, Size dots_, int bpp, ImageKind kind_, bool interlace_,
const RGBA *imgpal)
{
size = size_;
dots = dots_;
kind = kind_;
interlace = interlace_;
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, png_user_error_fn, png_user_warning_fn);
if(!png_ptr) {
stream.SetError();
return;
}
/* Allocate/initialize the image information data. REQUIRED */
info_ptr = png_create_info_struct(png_ptr);
if(!info_ptr) {
stream.SetError();
return;
}
/*
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_write_struct(&png_ptr, &info_ptr);
sptr -> SetError();
return;
}
*/
png_set_write_fn(png_ptr, (void *)&stream, png_write_stream, png_flush_stream);
int color_type, bit_depth;
Vector<png_color> palette;
do_alpha = (kind != IMAGE_OPAQUE && bpp != 1);
do_mask = (do_alpha && kind == IMAGE_MASK);
do_palette = (bpp <= 8);
if(do_palette) {
switch(bpp) {
case 1: format.Set1mf(); break;
case 2: format.Set2mf(); break;
case 4: format.Set4mf(); break;
default: NEVER();
case 8: format.Set8(); break;
}
bit_depth = bpp;
color_type = PNG_COLOR_TYPE_PALETTE;
palette.SetCount(1 << bpp);
for(int i = 0; i < palette.GetCount(); i++) {
png_color& c = palette[i];
c.red = imgpal[i].r;
c.green = imgpal[i].g;
c.blue = imgpal[i].b;
}
rowbytes = (size.cx * bpp + 31) >> 5 << 2;
}
else {
color_type = (do_alpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB);
bit_depth = 8;
if(do_alpha) {
format.Set32leStraight(0xFF << 16, 0xFF << 8, 0xFF, 0xFF << 24);
rowbytes = 4 * size.cx;
}
else {
format.Set24le(0xFF << 16, 0xFF << 8, 0xFF);
rowbytes = (3 * size.cx + 3) & -4;
}
}
png_set_IHDR(png_ptr, info_ptr, size.cx, size.cy, bit_depth, color_type,
interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
if(!palette.IsEmpty())
png_set_PLTE(png_ptr, info_ptr, palette.Begin(), palette.GetCount());
byte transpal = (1 << bpp) - 1;
if(do_mask && do_palette) {
png_color_16 transrgb = { 0, 0, 0 };
png_set_tRNS(png_ptr, info_ptr, &transpal, 1, &transrgb);
}
// png_set_gAMA(png_ptr, info_ptr, gamma);
/* Optionally write comments into the image */
// text_ptr[0].key = "Title";
// text_ptr[0].text = "Mona Lisa";
// text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE;
// #ifdef PNG_iTXt_SUPPORTED
// text_ptr[0].lang = NULL;
// #endif
// png_set_text(png_ptr, info_ptr, text_ptr, 1);
/* other optional chunks like cHRM, bKGD, tRNS, tIME, oFFs, pHYs, */
/* note that if sRGB is present the gAMA and cHRM chunks must be ignored
* on read and must be written in accordance with the sRGB profile */
/* Write the file header information. REQUIRED */
png_uint_32 x_ppm, y_ppm;
SetDotSize(size, dots, x_ppm, y_ppm, PNG_RESOLUTION_METER);
png_set_pHYs(png_ptr, info_ptr, x_ppm, y_ppm, PNG_RESOLUTION_METER);
png_write_info(png_ptr, info_ptr);
png_set_bgr(png_ptr);
passes = png_set_interlace_handling(png_ptr);
rowbuf.SetCount(rowbytes);
if(passes > 1)
imagebuf.SetCount(rowbytes * size.cy);
line = 0;
linebytes = format.GetByteCount(size.cx);
}
void PNGEncoder::Data::WriteLineRaw(const byte *s)
{
byte *dest = (passes > 1 ? &imagebuf[line * rowbytes] : rowbuf.Begin());
memcpy(dest, s, linebytes);
png_write_row(png_ptr, dest);
if(++line >= size.cy) {
for(int p = 1; p < passes; p++)
for(int y = 0; y < size.cy; y++)
png_write_row(png_ptr, &imagebuf[y * rowbytes]);
/* You can write optional chunks like tEXt, zTXt, and tIME at the end
* as well. Shouldn't be necessary in 1.1.0 and up as all the public
* chunks are supported and you can use png_set_unknown_chunks() to
* register unknown chunks into the info structure to be written out.
*/
png_write_end(png_ptr, info_ptr);
}
}
PNGEncoder::PNGEncoder(int bpp_, ImageKind kind_, bool interlace_)
: bpp(bpp_), kind(kind_), interlace(interlace_)
{
}
PNGEncoder::~PNGEncoder()
{
}
int PNGEncoder::GetPaletteCount()
{
return (bpp > 8 ? 0 : (1 << bpp) - (kind == IMAGE_OPAQUE || bpp == 1 ? 0 : 1));
}
void PNGEncoder::Start(Size sz)
{
data = new Data(format);
data->Start(GetStream(), sz, GetDots(), bpp, kind, interlace, bpp > 8 ? NULL : GetPalette());
}
void PNGEncoder::WriteLineRaw(const byte *s)
{
data->WriteLineRaw(s);
}
INITIALIZER(PNGRaster) {
StreamRaster::Register<PNGRaster>();
}
}