WIP: PNG support for Qt-less apps through libpng in lay::PixelBuffer

This commit is contained in:
Matthias Koefferlein 2022-05-07 01:33:16 +02:00
parent 7291a3dc47
commit 067f59ab0a
4 changed files with 453 additions and 1 deletions

View File

@ -72,6 +72,15 @@ equals(HAVE_CURL, "1") {
DEFINES += HAVE_CURL
}
equals(HAVE_PNG, "1") {
!isEmpty(BITS_PATH) {
include($$BITS_PATH/png/png.pri)
} else {
LIBS += -lpng
}
DEFINES += HAVE_PNG
}
equals(HAVE_EXPAT, "1") {
!isEmpty(BITS_PATH) {
include($$BITS_PATH/expat/expat.pri)

View File

@ -22,10 +22,92 @@
#include "layPixelBuffer.h"
#include "tlAssert.h"
#include "tlLog.h"
#if defined(HAVE_PNG)
# include <png.h>
#endif
namespace lay
{
// -----------------------------------------------------------------------------------------------------
// Exceptions
PixelBufferReadError::PixelBufferReadError (const char *msg)
: tl::Exception (tl::to_string (tr ("PNG read error: ")) + std::string (msg))
{
// .. nothing yet ..
}
PixelBufferReadError::PixelBufferReadError (const std::string &msg)
: tl::Exception (tl::to_string (tr ("PNG read error: ")) + msg)
{
// .. nothing yet ..
}
PixelBufferWriteError::PixelBufferWriteError (const char *msg)
: tl::Exception (tl::to_string (tr ("PNG write error: ")) + std::string (msg))
{
// .. nothing yet ..
}
PixelBufferWriteError::PixelBufferWriteError (const std::string &msg)
: tl::Exception (tl::to_string (tr ("PNG write error: ")) + msg)
{
// .. nothing yet ..
}
#if defined(HAVE_PNG)
static void png_read_warn_f (png_structp /*png_ptr*/, png_const_charp error_message)
{
tl::warn << tl::to_string (tr ("Warning reading PNG: ")) << error_message;
}
static void png_read_error_f (png_structp /*png_ptr*/, png_const_charp error_message)
{
throw PixelBufferReadError (error_message);
}
static void png_write_warn_f (png_structp /*png_ptr*/, png_const_charp error_message)
{
tl::warn << tl::to_string (tr ("Warning writing PNG: ")) << error_message;
}
static void png_write_error_f (png_structp /*png_ptr*/, png_const_charp error_message)
{
throw PixelBufferReadError (error_message);
}
static void read_from_stream_f (png_structp png_ptr, png_bytep bytes, size_t length)
{
tl::InputStream *stream = (tl::InputStream *) png_get_io_ptr (png_ptr);
try {
memcpy (bytes, stream->get (length), length);
} catch (tl::Exception &ex) {
png_error (png_ptr, ex.msg ().c_str ());
}
}
static void write_to_stream_f (png_structp png_ptr, png_bytep bytes, size_t length)
{
tl::OutputStream *stream = (tl::OutputStream *) png_get_io_ptr (png_ptr);
try {
stream->put ((const char *) bytes, length);
} catch (tl::Exception &ex) {
png_error (png_ptr, ex.msg ().c_str ());
}
}
static void flush_stream_f (png_structp png_ptr)
{
tl::OutputStream *stream = (tl::OutputStream *) png_get_io_ptr (png_ptr);
stream->flush ();
}
#endif
// -----------------------------------------------------------------------------------------------------
// PixelBuffer implementation
@ -228,6 +310,103 @@ PixelBuffer::diff (const PixelBuffer &other) const
return res;
}
#if defined(HAVE_PNG)
PixelBuffer
PixelBuffer::read_png (tl::InputStream &input)
{
png_structp png_ptr = NULL;
png_infop info_ptr = NULL;
png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, &png_read_error_f, &png_read_warn_f);
tl_assert (png_ptr != NULL);
info_ptr = png_create_info_struct (png_ptr);
tl_assert (info_ptr != NULL);
png_set_read_fn (png_ptr, (void *) &input, &read_from_stream_f);
png_set_bgr (png_ptr); // compatible with lay::color_t
png_read_png (png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
PixelBuffer res (png_get_image_width (png_ptr, info_ptr), png_get_image_height (png_ptr, info_ptr));
unsigned int fmt = png_get_color_type (png_ptr, info_ptr);
unsigned int bd = png_get_bit_depth (png_ptr, info_ptr);
if (fmt == PNG_COLOR_TYPE_RGBA && bd == 8) {
tl_assert (png_get_rowbytes (png_ptr, info_ptr) == res.width () * sizeof (lay::color_t));
png_bytepp row_pointers = png_get_rows (png_ptr, info_ptr);
for (unsigned int i = 0; i < res.height (); ++i) {
memcpy ((void *) res.scan_line (i), (void *) row_pointers [i], sizeof (lay::color_t) * res.width ());
}
} else if (fmt == PNG_COLOR_TYPE_RGB && bd == 8) {
// RGB has 3 bytes per pixel which need to be transformed into RGB32
unsigned int rb = png_get_rowbytes (png_ptr, info_ptr);
tl_assert (rb == res.width () * 3);
png_bytepp row_pointers = png_get_rows (png_ptr, info_ptr);
for (unsigned int i = 0; i < res.height (); ++i) {
lay::color_t *c = res.scan_line (i);
const uint8_t *d = row_pointers [i];
const uint8_t *dd = d + rb;
while (d < dd) {
uint8_t b = *d++;
uint8_t g = *d++;
uint8_t r = *d++;
*c++ = 0xff000000 | ((r << 8 | g) << 8) | b;
}
}
} else {
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
throw PixelBufferReadError (tl::sprintf (tl::to_string (tr ("PNG reader supports 32 bit RGB or RGBA only (file: %s, format is %d, bit depth is %d)")), input.filename (), fmt, bd));
}
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
return res;
}
void
PixelBuffer::write_png (tl::OutputStream &output)
{
png_structp png_ptr = NULL;
png_infop info_ptr = NULL;
png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, &png_write_error_f, &png_write_warn_f);
tl_assert (png_ptr != NULL);
info_ptr = png_create_info_struct (png_ptr);
tl_assert (info_ptr != NULL);
png_set_write_fn (png_ptr, (void *) &output, &write_to_stream_f, &flush_stream_f);
png_set_bgr (png_ptr); // compatible with lay::color_t
unsigned int bd = 8; // bit depth
unsigned int fmt = PNG_COLOR_TYPE_RGBA;
png_set_IHDR (png_ptr, info_ptr, width (), height (), bd, fmt, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_write_info (png_ptr, info_ptr);
for (unsigned int i = 0; i < height (); ++i) {
png_write_row (png_ptr, png_const_bytep (scan_line (i)));
}
png_write_end (png_ptr, info_ptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
}
#endif
// -----------------------------------------------------------------------------------------------------
// BitmapBuffer implementation
@ -382,4 +561,81 @@ BitmapBuffer::to_image_copy () const
}
#endif
#if defined(HAVE_PNG)
BitmapBuffer
BitmapBuffer::read_png (tl::InputStream &input)
{
png_structp png_ptr = NULL;
png_infop info_ptr = NULL;
png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, &png_read_error_f, &png_read_warn_f);
tl_assert (png_ptr != NULL);
info_ptr = png_create_info_struct (png_ptr);
tl_assert (info_ptr != NULL);
png_set_read_fn (png_ptr, (void *) &input, &read_from_stream_f);
png_read_png (png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
BitmapBuffer res (png_get_image_width (png_ptr, info_ptr), png_get_image_height (png_ptr, info_ptr));
unsigned int fmt = png_get_color_type (png_ptr, info_ptr);
unsigned int bd = png_get_bit_depth (png_ptr, info_ptr);
if (fmt == PNG_COLOR_TYPE_GRAY && bd == 1) {
size_t rb = png_get_rowbytes (png_ptr, info_ptr);
tl_assert (rb == (res.width () + 7) / 8);
png_bytepp row_pointers = png_get_rows (png_ptr, info_ptr);
for (unsigned int i = 0; i < res.height (); ++i) {
memcpy ((void *) res.scan_line (i), (void *) row_pointers [i], rb);
}
} else {
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
throw PixelBufferReadError (tl::sprintf (tl::to_string (tr ("PNG bitmap reader supports monochrome files only (file: %s, format is %d, bit depth is %d)")), input.filename (), fmt, bd));
}
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
return res;
}
void
BitmapBuffer::write_png (tl::OutputStream &output)
{
png_structp png_ptr = NULL;
png_infop info_ptr = NULL;
png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, &png_write_error_f, &png_write_warn_f);
tl_assert (png_ptr != NULL);
info_ptr = png_create_info_struct (png_ptr);
tl_assert (info_ptr != NULL);
png_set_write_fn (png_ptr, (void *) &output, &write_to_stream_f, &flush_stream_f);
unsigned int bd = 1; // bit depth
unsigned int fmt = PNG_COLOR_TYPE_GRAY;
png_set_IHDR (png_ptr, info_ptr, width (), height (), bd, fmt, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_write_info (png_ptr, info_ptr);
for (unsigned int i = 0; i < height (); ++i) {
png_write_row (png_ptr, png_const_bytep (scan_line (i)));
}
png_write_end (png_ptr, info_ptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
}
#endif
}

View File

@ -27,6 +27,8 @@
#include "laybasicCommon.h"
#include "layColor.h"
#include "tlCopyOnWrite.h"
#include "tlStream.h"
#include "tlException.h"
#include <string.h>
#include <cstdint>
@ -38,13 +40,34 @@
namespace lay
{
/**
* @brief An exception thrown when a PNG read error occurs
*/
class LAYBASIC_PUBLIC PixelBufferReadError
: public tl::Exception
{
public:
PixelBufferReadError (const char *msg);
PixelBufferReadError (const std::string &msg);
};
/**
* @brief An exception thrown when a PNG write error occurs
*/
class LAYBASIC_PUBLIC PixelBufferWriteError
: public tl::Exception
{
public:
PixelBufferWriteError (const char *msg);
PixelBufferWriteError (const std::string &msg);
};
/**
* @brief An 32bit RGB/RGBA image class
*
* This class substitutes QImage in Qt-less applications.
* It provides 32bit RGBA pixels with the format used by lay::Color.
*/
class LAYBASIC_PUBLIC PixelBuffer
{
public:
@ -183,6 +206,20 @@ public:
QImage to_image_copy () const;
#endif
#if defined(HAVE_PNG)
/**
* @brief Creates a PixelBuffer object from a PNG file
* Throws a PixelBufferReadError if an error occurs.
*/
static PixelBuffer read_png (tl::InputStream &input);
/**
* @brief Writes the PixelBuffer object to a PNG file
* Throws a PixelBufferWriteError if an error occurs.
*/
void write_png (tl::OutputStream &output);
#endif
/**
* @brief Overlays the other image with this one
*
@ -379,6 +416,20 @@ public:
QImage to_image_copy () const;
#endif
#if defined(HAVE_PNG)
/**
* @brief Creates a PixelBuffer object from a PNG file
* Throws a PixelBufferReadError if an error occurs.
*/
static BitmapBuffer read_png (tl::InputStream &input);
/**
* @brief Writes the PixelBuffer object to a PNG file
* Throws a PixelBufferWriteError if an error occurs.
*/
void write_png (tl::OutputStream &output);
#endif
private:
class MonoImageData
{

View File

@ -250,6 +250,95 @@ TEST(2)
#endif
#if defined(HAVE_PNG)
// libpng support
TEST(2b)
{
lay::PixelBuffer img;
std::string in = tl::testsrc () + "/testdata/lay/png1.png"; // ARGB32
tl::info << "PNG file read (libpng) from " << in;
{
tl::InputStream stream (in);
img = lay::PixelBuffer::read_png (stream);
}
std::string tmp = tmp_file ("test.png");
{
tl::OutputStream stream (tmp);
img.write_png (stream);
}
tl::info << "PNG file written to " << tmp;
lay::PixelBuffer img2;
{
tl::InputStream stream (tmp);
img2 = lay::PixelBuffer::read_png (stream);
}
EXPECT_EQ (compare_images (img, img2), true);
std::string tmp2 = tmp_file ("test2.png");
{
tl::OutputStream stream (tmp2);
img2.write_png (stream);
}
tl::info << "PNG file written to " << tmp2;
#if defined (HAVE_QT)
// Qt cross-check
std::string au = tl::testsrc () + "/testdata/lay/au.png";
EXPECT_EQ (compare_images (img2.to_image (), au), true);
#endif
}
TEST(2c)
{
lay::PixelBuffer img;
std::string in = tl::testsrc () + "/testdata/lay/png2.png"; // RGB32
tl::info << "PNG file read (libpng) from " << in;
{
tl::InputStream stream (in);
img = lay::PixelBuffer::read_png (stream);
}
std::string tmp = tmp_file ("test.png");
{
tl::OutputStream stream (tmp);
img.write_png (stream);
}
tl::info << "PNG file written to " << tmp;
lay::PixelBuffer img2;
{
tl::InputStream stream (tmp);
img2 = lay::PixelBuffer::read_png (stream);
}
EXPECT_EQ (compare_images (img, img2), true);
std::string tmp2 = tmp_file ("test2.png");
{
tl::OutputStream stream (tmp2);
img2.write_png (stream);
}
tl::info << "PNG file written to " << tmp2;
#if defined (HAVE_QT)
// Qt cross-check
std::string au = tl::testsrc () + "/testdata/lay/au.png";
EXPECT_EQ (compare_images (img2.to_image (), au), true);
#endif
}
#endif
TEST(3)
{
{
@ -449,3 +538,50 @@ TEST(12)
}
#endif
#if defined(HAVE_PNG)
// libpng support
TEST(12b)
{
lay::BitmapBuffer img;
std::string in = tl::testsrc () + "/testdata/lay/au_mono.png";
tl::info << "PNG file read (libpng) from " << in;
{
tl::InputStream stream (in);
img = lay::BitmapBuffer::read_png (stream);
}
std::string tmp = tmp_file ("test.png");
{
tl::OutputStream stream (tmp);
img.write_png (stream);
}
tl::info << "PNG file written to " << tmp;
lay::BitmapBuffer img2;
{
tl::InputStream stream (tmp);
img2 = lay::BitmapBuffer::read_png (stream);
}
EXPECT_EQ (compare_images (img, img2), true);
std::string tmp2 = tmp_file ("test2.png");
{
tl::OutputStream stream (tmp2);
img2.write_png (stream);
}
tl::info << "PNG file written to " << tmp2;
#if defined (HAVE_QT)
// Qt cross-check
std::string au = tl::testsrc () + "/testdata/lay/au_mono.png";
EXPECT_EQ (compare_images (img2.to_image ().convertToFormat (QImage::Format_Mono), au), true);
#endif
}
#endif