mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 14:16:07 -06:00
523 lines
14 KiB
C++
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>();
|
|
}
|
|
|
|
}
|