diff --git a/ChangeLog b/ChangeLog index 4176ca9b..e495c72f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18,6 +18,7 @@ - add "all" mode to smartcrop - flood fill could stop half-way for some very complex shapes - better handling of unaligned reads in multipage tiffs [petoor] +- add experimental libspng reader - mark old --delete option to vipsthumbnail as deprecated [UweOhse] - png save with a bad ICC profile just gives a warning - add "premultipled" option to vips_affine(), clarified vips_resize() @@ -26,6 +27,7 @@ - thumbnail exploits subifd pyramids - handle all EXIF orientation cases, deprecate vips_autorot_get_angle() [Elad-Laufer] +- load PNGs with libspng, if possible - deprecate heifload autorotate -- it's now always on - revised resize improves accuracy [kleisauke] - add --vips-config flag to show configuration info diff --git a/README.md b/README.md index 03bab492..89ae80a5 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,53 @@ [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/libvips.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=2&q=proj:libvips) [![Coverity Status](https://scan.coverity.com/projects/6503/badge.svg)](https://scan.coverity.com/projects/jcupitt-libvips) +# This branch + +Is for experiemtning with [libspng](https://github.com/randy408/libspng). + +## Notes + +Build libspng: + +``` +cd libspng +meson build --prefix=/home/john/vips --libdir=/home/john/vips/lib \ + --buildtype=release +cd build +ninja +ninja install +``` + +Installs `spng.pc`. + +Sample code: + +https://github.com/randy408/libspng/blob/master/examples/example.c + +libspng benchmark: + +``` +$ time vips avg wtc.png +117.065766 + +real 0m2.972s +user 0m3.376s +sys 0m0.197s +``` + +And for libpng: + +``` +$ time vips avg wtc.png +117.065766 + +real 0m3.816s +user 0m4.177s +sys 0m0.221s +``` + +# Introduction + libvips is a [demand-driven, horizontally threaded](https://github.com/libvips/libvips/wiki/Why-is-libvips-quick) image processing library. Compared to similar diff --git a/configure.ac b/configure.ac index 97a82b50..790b5987 100644 --- a/configure.ac +++ b/configure.ac @@ -1179,6 +1179,21 @@ FIND_GIFLIB( ] ) +# Look for libspng first +AC_ARG_WITH([libspng], + AS_HELP_STRING([--without-libspng], [build without libspng (default: test)])) + +if test x"$with_libspng" != x"no"; then + PKG_CHECK_MODULES(SPNG, spng >= 0.6, + [AC_DEFINE(HAVE_SPNG,1,[define if you have libspng installed.]) + with_libspng=yes + PACKAGES_USED="$PACKAGES_USED spng" + ], + [with_libspng=no + ] + ) +fi + # look for PNG with pkg-config ... fall back to our tester AC_ARG_WITH([png], AS_HELP_STRING([--without-png], [build without libpng (default: test)])) @@ -1302,10 +1317,10 @@ if test x"$LIB_FUZZING_ENGINE" = x; then fi # Gather all up for VIPS_CFLAGS, VIPS_INCLUDES, VIPS_LIBS -VIPS_CFLAGS="$VIPS_CFLAGS $GTHREAD_CFLAGS $GIO_CFLAGS $REQUIRED_CFLAGS $EXPAT_CFLAGS $ZLIB_CFLAGS $PANGOFT2_CFLAGS $GSF_CFLAGS $FFTW_CFLAGS $MAGICK_CFLAGS $JPEG_CFLAGS $PNG_CFLAGS $IMAGEQUANT_CFLAGS $EXIF_CFLAGS $MATIO_CFLAGS $CFITSIO_CFLAGS $LIBWEBP_CFLAGS $LIBWEBPMUX_CFLAGS $GIFLIB_INCLUDES $RSVG_CFLAGS $PDFIUM_INCLUDES $POPPLER_CFLAGS $OPENEXR_CFLAGS $OPENSLIDE_CFLAGS $ORC_CFLAGS $TIFF_CFLAGS $LCMS_CFLAGS $HEIF_CFLAGS" +VIPS_CFLAGS="$VIPS_CFLAGS $GTHREAD_CFLAGS $GIO_CFLAGS $REQUIRED_CFLAGS $EXPAT_CFLAGS $ZLIB_CFLAGS $PANGOFT2_CFLAGS $GSF_CFLAGS $FFTW_CFLAGS $MAGICK_CFLAGS $JPEG_CFLAGS $SPNG_CFLAGS $PNG_CFLAGS $IMAGEQUANT_CFLAGS $EXIF_CFLAGS $MATIO_CFLAGS $CFITSIO_CFLAGS $LIBWEBP_CFLAGS $LIBWEBPMUX_CFLAGS $GIFLIB_INCLUDES $RSVG_CFLAGS $PDFIUM_INCLUDES $POPPLER_CFLAGS $OPENEXR_CFLAGS $OPENSLIDE_CFLAGS $ORC_CFLAGS $TIFF_CFLAGS $LCMS_CFLAGS $HEIF_CFLAGS" VIPS_CFLAGS="$VIPS_DEBUG_FLAGS $VIPS_CFLAGS" VIPS_INCLUDES="$ZLIB_INCLUDES $PNG_INCLUDES $TIFF_INCLUDES $JPEG_INCLUDES $NIFTI_INCLUDES" -VIPS_LIBS="$ZLIB_LIBS $HEIF_LIBS $MAGICK_LIBS $PNG_LIBS $IMAGEQUANT_LIBS $TIFF_LIBS $JPEG_LIBS $GTHREAD_LIBS $GIO_LIBS $REQUIRED_LIBS $EXPAT_LIBS $PANGOFT2_LIBS $GSF_LIBS $FFTW_LIBS $ORC_LIBS $LCMS_LIBS $GIFLIB_LIBS $RSVG_LIBS $NIFTI_LIBS $PDFIUM_LIBS $POPPLER_LIBS $OPENEXR_LIBS $OPENSLIDE_LIBS $CFITSIO_LIBS $LIBWEBP_LIBS $LIBWEBPMUX_LIBS $MATIO_LIBS $EXIF_LIBS -lm" +VIPS_LIBS="$ZLIB_LIBS $HEIF_LIBS $MAGICK_LIBS $SPNG_LIBS $PNG_LIBS $IMAGEQUANT_LIBS $TIFF_LIBS $JPEG_LIBS $GTHREAD_LIBS $GIO_LIBS $REQUIRED_LIBS $EXPAT_LIBS $PANGOFT2_LIBS $GSF_LIBS $FFTW_LIBS $ORC_LIBS $LCMS_LIBS $GIFLIB_LIBS $RSVG_LIBS $NIFTI_LIBS $PDFIUM_LIBS $POPPLER_LIBS $OPENEXR_LIBS $OPENSLIDE_LIBS $CFITSIO_LIBS $LIBWEBP_LIBS $LIBWEBPMUX_LIBS $MATIO_LIBS $EXIF_LIBS -lm" # autoconf hates multi-line AC_SUBST VIPS_CONFIG="native win32: $vips_os_win32, native OS X: $vips_os_darwin, open files in binary mode: $vips_binary_open, enable debug: $enable_debug, enable deprecated library components: $enable_deprecated, enable docs with gtkdoc: $enable_gtk_doc, gobject introspection: $found_introspection, enable radiance support: $with_radiance, enable analyze support: $with_analyze, enable PPM support: $with_ppm, use fftw3 for FFT: $with_fftw, Magick package: $with_magickpackage, Magick API version: $magick_version, load with libMagick: $enable_magickload, save with libMagick: $enable_magicksave, accelerate loops with orc: $with_orc, ICC profile support with lcms: $with_lcms, file import with niftiio: $with_nifti, file import with libheif: $with_heif, file import with OpenEXR: $with_OpenEXR, file import with OpenSlide: $with_openslide, file import with matio: $with_matio, PDF import with PDFium: $with_pdfium, PDF import with poppler-glib: $with_poppler, SVG import with librsvg-2.0: $with_rsvg, zlib: $with_zlib, file import with cfitsio: $with_cfitsio, file import/export with libwebp: $with_libwebp, text rendering with pangoft2: $with_pangoft2, file import/export with libpng: $with_png, support 8bpp PNG quantisation: $with_imagequant, file import/export with libtiff: $with_tiff, file import/export with giflib: $with_giflib, file import/export with libjpeg: $with_jpeg, image pyramid export: $with_gsf, use libexif to load/save JPEG metadata: $with_libexif" @@ -1405,6 +1420,8 @@ file import with cfitsio: $with_cfitsio file import/export with libwebp: $with_libwebp (requires libwebp, libwebpmux, libwebpdemux 0.6.0 or later) text rendering with pangoft2: $with_pangoft2 +file import/export with libspng: $with_libspng + (requires libspng-0.6 or later) file import/export with libpng: $with_png (requires libpng-1.2.9 or later) support 8bpp PNG quantisation: $with_imagequant diff --git a/libvips/foreign/Makefile.am b/libvips/foreign/Makefile.am index d9a68585..759df2e5 100644 --- a/libvips/foreign/Makefile.am +++ b/libvips/foreign/Makefile.am @@ -39,6 +39,7 @@ libforeign_la_SOURCES = \ magickload.c \ magick7load.c \ magicksave.c \ + spngload.c \ pngload.c \ pngsave.c \ vipspng.c \ diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 895c999b..e0367c4d 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -2206,6 +2206,12 @@ vips_foreign_operation_init( void ) vips_foreign_save_png_target_get_type(); #endif /*HAVE_PNG*/ +#ifdef HAVE_SPNG + vips_foreign_load_png_file_get_type(); + vips_foreign_load_png_buffer_get_type(); + vips_foreign_load_png_source_get_type(); +#endif /*HAVE_SPNG*/ + #ifdef HAVE_MATIO vips_foreign_load_mat_get_type(); #endif /*HAVE_MATIO*/ diff --git a/libvips/foreign/pngload.c b/libvips/foreign/pngload.c index 1c4d34b0..8092078e 100644 --- a/libvips/foreign/pngload.c +++ b/libvips/foreign/pngload.c @@ -50,7 +50,7 @@ #include "pforeign.h" -#ifdef HAVE_PNG +#if defined(HAVE_PNG) && !defined(HAVE_SPNG) typedef struct _VipsForeignLoadPng { VipsForeignLoad parent_object; @@ -387,7 +387,7 @@ vips_foreign_load_png_buffer_init( VipsForeignLoadPngBuffer *buffer ) { } -#endif /*HAVE_PNG*/ +#endif /*defined(HAVE_PNG) && !defined(HAVE_SPNG)*/ /** * vips_pngload: diff --git a/libvips/foreign/spngload.c b/libvips/foreign/spngload.c new file mode 100644 index 00000000..ac576e65 --- /dev/null +++ b/libvips/foreign/spngload.c @@ -0,0 +1,807 @@ +/* load PNG with libspng + * + * 1/5/20 + * - from pngload.c + */ + +/* + + This file is part of VIPS. + + VIPS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser 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 + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +/* +#define DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include + +#include +#include +#include + +#include "pforeign.h" + +#ifdef HAVE_SPNG + +#include + +typedef struct _VipsForeignLoadPng { + VipsForeignLoad parent_object; + + /* Set by subclasses. + */ + VipsSource *source; + + spng_ctx *ctx; + struct spng_ihdr ihdr; + enum spng_format fmt; + int bands; + VipsInterpretation interpretation; + VipsBandFormat format; + int y_pos; + +} VipsForeignLoadPng; + +typedef VipsForeignLoadClass VipsForeignLoadPngClass; + +G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadPng, vips_foreign_load_png, + VIPS_TYPE_FOREIGN_LOAD ); + +static void +vips_foreign_load_png_dispose( GObject *gobject ) +{ + VipsForeignLoadPng *png = (VipsForeignLoadPng *) gobject; + + VIPS_FREEF( spng_ctx_free, png->ctx ); + VIPS_UNREF( png->source ); + + G_OBJECT_CLASS( vips_foreign_load_png_parent_class )-> + dispose( gobject ); +} + +static int +vips_foreign_load_png_stream( spng_ctx *ctx, void *user, + void *dest, size_t length ) +{ + VipsSource *source = VIPS_SOURCE( user ); + + gint64 bytes_read; + + bytes_read = vips_source_read( source, dest, length ); + if( bytes_read < 0 ) + return( SPNG_IO_ERROR ); + if( bytes_read < length ) + return( SPNG_IO_EOF); + + return( 0 ); +} + +static VipsForeignFlags +vips_foreign_load_png_get_flags_source( VipsSource *source ) +{ + spng_ctx *ctx; + struct spng_ihdr ihdr; + VipsForeignFlags flags; + + ctx = spng_ctx_new( SPNG_CTX_IGNORE_ADLER32 ); + spng_set_crc_action( ctx, SPNG_CRC_USE, SPNG_CRC_USE ); + spng_set_png_stream( ctx, + vips_foreign_load_png_stream, source ); + if( spng_get_ihdr( ctx, &ihdr ) ) { + spng_ctx_free( ctx ); + return( 0 ); + } + spng_ctx_free( ctx ); + + flags = 0; + if( ihdr.interlace_method != SPNG_INTERLACE_NONE ) + flags |= VIPS_FOREIGN_PARTIAL; + else + flags |= VIPS_FOREIGN_SEQUENTIAL; + + return( flags ); +} + +static VipsForeignFlags +vips_foreign_load_png_get_flags( VipsForeignLoad *load ) +{ + VipsForeignLoadPng *png = (VipsForeignLoadPng *) load; + + return( vips_foreign_load_png_get_flags_source( png->source ) ); +} + +static VipsForeignFlags +vips_foreign_load_png_get_flags_filename( const char *filename ) +{ + VipsSource *source; + VipsForeignFlags flags; + + if( !(source = vips_source_new_from_file( filename )) ) + return( 0 ); + flags = vips_foreign_load_png_get_flags_source( source ); + VIPS_UNREF( source ); + + return( flags ); +} + +/* Set the png text data as metadata on the vips image. These are always + * null-terminated strings. + */ +static void +vips_foreign_load_png_set_text( VipsImage *out, + int i, const char *key, const char *value ) +{ +#ifdef DEBUG + printf( "vips_foreign_load_png_set_text: key %s, value %s\n", + key, value ); +#endif /*DEBUG*/ + + if( strcmp( key, "XML:com.adobe.xmp" ) == 0 ) { + /* Save as an XMP tag. This must be a BLOB, for compatibility + * for things like the XMP blob that the tiff loader adds. + * + * Note that this will remove the null-termination from the + * string. We must carefully reattach this. + */ + vips_image_set_blob_copy( out, + VIPS_META_XMP_NAME, value, strlen( value ) ); + } + else { + char name[256]; + + /* Save as a string comment. Some PNGs have EXIF data as + * text segments, unfortunately. + */ + vips_snprintf( name, 256, "png-comment-%d-%s", i, key ); + + vips_image_set_string( out, name, value ); + } +} + +static void +vips_foreign_load_png_set_header( VipsForeignLoadPng *png, VipsImage *image ) +{ + double xres, yres; + struct spng_iccp iccp; + struct spng_text *text; + struct spng_exif exif; + struct spng_phys phys; + guint32 n_text; + + /* Get resolution. Default to 72 pixels per inch. + */ + xres = (72.0 / 2.54 * 100.0); + yres = (72.0 / 2.54 * 100.0); + if( !spng_get_phys( png->ctx, &phys ) ) { + /* There's phys.units, but it's always 0, meaning pixels per + * metre. + */ + xres = phys.ppu_x / 1000.0; + yres = phys.ppu_y / 1000.0; + } + + vips_image_init_fields( image, + png->ihdr.width, png->ihdr.height, png->bands, + png->format, VIPS_CODING_NONE, png->interpretation, + xres, yres ); + + VIPS_SETSTR( image->filename, + vips_connection_filename( VIPS_CONNECTION( png->source ) ) ); + + if( png->ihdr.interlace_method == SPNG_INTERLACE_NONE ) + /* Sequential mode needs thinstrip to work with things like + * vips_shrink(). + */ + vips_image_pipelinev( image, + VIPS_DEMAND_STYLE_THINSTRIP, NULL ); + else + /* Interlaced images are read via a huge memory buffer. + */ + vips_image_pipelinev( image, VIPS_DEMAND_STYLE_ANY, NULL ); + + if( !spng_get_iccp( png->ctx, &iccp ) ) + vips_image_set_blob_copy( image, + VIPS_META_ICC_NAME, iccp.profile, iccp.profile_len ); + + spng_get_text( png->ctx, NULL, &n_text ); + text = VIPS_ARRAY( VIPS_OBJECT( png ), n_text, struct spng_text ); + if( !spng_get_text( png->ctx, text, &n_text ) ) { + guint32 i; + + for( i = 0; i < n_text; i++ ) + /* .text is always a null-terminated C string. + */ + vips_foreign_load_png_set_text( image, + i, text[i].keyword, text[i].text ); + } + + if( !spng_get_exif( png->ctx, &exif ) ) + vips_image_set_blob_copy( image, VIPS_META_EXIF_NAME, + exif.data, exif.length ); + + /* Attach original palette bit depth, if any, as metadata. + */ + if( png->ihdr.color_type == SPNG_COLOR_TYPE_INDEXED ) + vips_image_set_int( image, + "palette-bit-depth", png->ihdr.bit_depth ); + + /* Let our caller know. These are very expensive to decode. + */ + if( png->ihdr.interlace_method != SPNG_INTERLACE_NONE ) + vips_image_set_int( image, "interlaced", 1 ); +} + +static int +vips_foreign_load_png_header( VipsForeignLoad *load ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); + VipsForeignLoadPng *png = (VipsForeignLoadPng *) load; + + int flags; + int error; + struct spng_trns trns; + + /* In non-fail mode, ignore CRC errors. + */ + flags = 0; + if( !load->fail ) + flags |= SPNG_CTX_IGNORE_ADLER32; + png->ctx = spng_ctx_new( flags ); + if( !load->fail ) + /* Ignore and don't calculate checksums. + */ + spng_set_crc_action( png->ctx, SPNG_CRC_USE, SPNG_CRC_USE ); + + /* Set limits to avoid decompression bombs. Set chunk limits to 60mb + * -- we've seen 50mb XMP blocks in the wild. + * + * No need to test the decoded image size -- the user can do that if + * they wish. + */ + spng_set_image_limits( png->ctx, VIPS_MAX_COORD, VIPS_MAX_COORD ); + spng_set_chunk_limits( png->ctx, 60 * 1024 * 1024, 60 * 1024 * 1024 ); + + if( vips_source_rewind( png->source ) ) + return( -1 ); + spng_set_png_stream( png->ctx, + vips_foreign_load_png_stream, png->source ); + if( (error = spng_get_ihdr( png->ctx, &png->ihdr )) ) { + vips_error( class->nickname, "%s", spng_strerror( error ) ); + return( -1 ); + } + + /* + printf( "width: %d\nheight: %d\nbit depth: %d\ncolor type: %d\n", + png->ihdr.width, png->ihdr.height, + png->ihdr.bit_depth, png->ihdr.color_type ); + printf( "compression method: %d\nfilter method: %d\n" + "interlace method: %d\n", + png->ihdr.compression_method, png->ihdr.filter_method, + png->ihdr.interlace_method ); + */ + + /* Just convert to host-endian if nothing else applies. + */ + png->fmt = SPNG_FMT_PNG; + + switch( png->ihdr.color_type ) { + case SPNG_COLOR_TYPE_INDEXED: + png->bands = 3; + break; + + case SPNG_COLOR_TYPE_GRAYSCALE_ALPHA: + case SPNG_COLOR_TYPE_GRAYSCALE: + png->bands = 1; + break; + + case SPNG_COLOR_TYPE_TRUECOLOR: + case SPNG_COLOR_TYPE_TRUECOLOR_ALPHA: + png->bands = 3; + break; + + default: + vips_error( class->nickname, "%s", _( "unknown color type" ) ); + return( -1 ); + } + + /* Set libvips format and interpretation. + */ + if( png->ihdr.bit_depth > 8 ) { + if( png->bands < 3 ) + png->interpretation = VIPS_INTERPRETATION_GREY16; + else + png->interpretation = VIPS_INTERPRETATION_RGB16; + + png->format = VIPS_FORMAT_USHORT; + } + else { + if( png->bands < 3 ) + png->interpretation = VIPS_INTERPRETATION_B_W; + else + png->interpretation = VIPS_INTERPRETATION_sRGB; + + png->format = VIPS_FORMAT_UCHAR; + } + + /* Expand palette images. + */ + if( png->ihdr.color_type == SPNG_COLOR_TYPE_INDEXED ) + png->fmt = SPNG_FMT_RGB8; + + /* Expand <8 bit images to full bytes. + */ + if( png->ihdr.color_type == SPNG_COLOR_TYPE_GRAYSCALE && + png->ihdr.bit_depth < 8 ) + png->fmt = SPNG_FMT_G8; + + /* Expand transparency. + * + * The _ALPHA types should not have the optional trns chunk (they + * always have a transparent band), see + * https://www.w3.org/TR/2003/REC-PNG-20031110/#11tRNS + */ + if( png->ihdr.color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA || + png->ihdr.color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA ) + png->bands += 1; + else if( !spng_get_trns( png->ctx, &trns ) ) { + png->bands += 1; + + if( png->ihdr.color_type == SPNG_COLOR_TYPE_TRUECOLOR ) { + if( png->ihdr.bit_depth == 16 ) + png->fmt = SPNG_FMT_RGBA16; + else + png->fmt = SPNG_FMT_RGBA8; + } + else if( png->ihdr.color_type == SPNG_COLOR_TYPE_INDEXED ) + png->fmt = SPNG_FMT_RGBA8; + else if( png->ihdr.color_type == SPNG_COLOR_TYPE_GRAYSCALE ) { + if( png->ihdr.bit_depth == 16 ) + png->fmt = SPNG_FMT_GA16; + else + png->fmt = SPNG_FMT_GA8; + } + } + + vips_source_minimise( png->source ); + + vips_foreign_load_png_set_header( png, load->out ); + + return( 0 ); +} + +static int +vips_foreign_load_png_generate( VipsRegion *or, + void *seq, void *a, void *b, gboolean *stop ) +{ + VipsRect *r = &or->valid; + VipsForeignLoad *load = VIPS_FOREIGN_LOAD( a ); + VipsForeignLoadPng *png = (VipsForeignLoadPng *) load; + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( png ); + + int y; + int error; + +#ifdef DEBUG + printf( "vips_foreign_load_png_generate: line %d, %d rows\n", + r->top, r->height ); + printf( "vips_foreign_load_png_generate: y_top = %d\n", png->y_pos ); +#endif /*DEBUG*/ + + /* We're inside a tilecache where tiles are the full image width, so + * this should always be true. + */ + g_assert( r->left == 0 ); + g_assert( r->width == or->im->Xsize ); + g_assert( VIPS_RECT_BOTTOM( r ) <= or->im->Ysize ); + + /* Tiles should always be a strip in height, unless it's the final + * strip. + */ + g_assert( r->height == VIPS_MIN( VIPS__FATSTRIP_HEIGHT, + or->im->Ysize - r->top ) ); + + /* And check that y_pos is correct. It should be, since we are inside + * a vips_sequential(). + */ + if( r->top != png->y_pos ) { + vips_error( class->nickname, + _( "out of order read at line %d" ), png->y_pos ); + return( -1 ); + } + + for( y = 0; y < r->height; y++ ) { + error = spng_decode_row( png->ctx, + VIPS_REGION_ADDR( or, 0, r->top + y ), + VIPS_REGION_SIZEOF_LINE( or ) ); + /* libspng returns EOI when successfully reading the + * final line of input. + */ + if( error != 0 && + error != SPNG_EOI ) { + /* We've failed to read some pixels. Knock this + * operation out of cache. + */ + vips_operation_invalidate( VIPS_OPERATION( png ) ); + +#ifdef DEBUG + printf( "vips_foreign_load_png_generate:\n" ); + printf( " spng_decode_row() failed, line %d\n", + r->top + y ); + printf( " thread %p\n", g_thread_self() ); + printf( " error %s\n", spng_strerror( error ) ); +#endif /*DEBUG*/ + + /* And bail if fail is on. + */ + if( load->fail ) { + vips_error( class->nickname, + "%s", _( "libpng read error" ) ); + return( -1 ); + } + } + + png->y_pos += 1; + } + + return( 0 ); +} + +static int +vips_foreign_load_png_load( VipsForeignLoad *load ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); + VipsForeignLoadPng *png = (VipsForeignLoadPng *) load; + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( load ), 3 ); + + enum spng_decode_flags flags; + int error; + + if( vips_source_decode( png->source ) ) + return( -1 ); + + /* Decode transparency, if available. + */ + flags = SPNG_DECODE_TRNS; + + if( png->ihdr.interlace_method != SPNG_INTERLACE_NONE ) { + /* Arg awful interlaced image. We have to load to a huge mem + * buffer, then copy to out. + */ + t[0] = vips_image_new_memory(); + vips_foreign_load_png_set_header( png, t[0] ); + if( vips_image_write_prepare( t[0] ) ) + return( -1 ); + + if( (error = spng_decode_image( png->ctx, + VIPS_IMAGE_ADDR( t[0], 0, 0 ), + VIPS_IMAGE_SIZEOF_IMAGE( t[0] ), + png->fmt, flags )) ) { + vips_error( class->nickname, + "%s", spng_strerror( error ) ); + return( -1 ); + } + + if( vips_image_write( t[0], load->real ) ) + return( -1 ); + } + else { + t[0] = vips_image_new(); + vips_foreign_load_png_set_header( png, t[0] ); + + /* We can decode these progressively. + */ + flags |= SPNG_DECODE_PROGRESSIVE; + + if( (error = spng_decode_image( png->ctx, NULL, 0, + png->fmt, flags )) ) { + vips_error( class->nickname, + "%s", spng_strerror( error ) ); + return( -1 ); + } + + if( vips_image_generate( t[0], + NULL, vips_foreign_load_png_generate, NULL, + png, NULL ) || + vips_sequential( t[0], &t[1], + "tile_height", VIPS__FATSTRIP_HEIGHT, + NULL ) || + vips_image_write( t[1], load->real ) ) + return( -1 ); + } + + return( 0 ); +} + +static void +vips_foreign_load_png_class_init( VipsForeignLoadPngClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignClass *foreign_class = (VipsForeignClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->dispose = vips_foreign_load_png_dispose; + + object_class->nickname = "pngload_base"; + object_class->description = _( "load png base class" ); + + /* We are fast at is_a(), so high priority. + */ + foreign_class->priority = 200; + + load_class->get_flags_filename = + vips_foreign_load_png_get_flags_filename; + load_class->get_flags = vips_foreign_load_png_get_flags; + load_class->header = vips_foreign_load_png_header; + load_class->load = vips_foreign_load_png_load; + +} + +static void +vips_foreign_load_png_init( VipsForeignLoadPng *png ) +{ +} + +typedef struct _VipsForeignLoadPngSource { + VipsForeignLoadPng parent_object; + + /* Load from a source. + */ + VipsSource *source; + +} VipsForeignLoadPngSource; + +typedef VipsForeignLoadPngClass VipsForeignLoadPngSourceClass; + +G_DEFINE_TYPE( VipsForeignLoadPngSource, vips_foreign_load_png_source, + vips_foreign_load_png_get_type() ); + +static int +vips_foreign_load_png_source_build( VipsObject *object ) +{ + VipsForeignLoadPng *png = (VipsForeignLoadPng *) object; + VipsForeignLoadPngSource *source = (VipsForeignLoadPngSource *) object; + + if( source->source ) { + png->source = source->source; + g_object_ref( png->source ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_load_png_source_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static gboolean +vips_foreign_load_png_source_is_a_source( VipsSource *source ) +{ + static unsigned char signature[8] = { 137, 80, 78, 71, 13, 10, 26, 10 }; + + const unsigned char *p; + + if( (p = vips_source_sniff( source, 8 )) && + memcmp( p, signature, 8 ) == 0 ) + return( TRUE ); + + return( FALSE ); +} + +static void +vips_foreign_load_png_source_class_init( VipsForeignLoadPngSourceClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "pngload_source"; + object_class->description = _( "load png from source" ); + object_class->build = vips_foreign_load_png_source_build; + + load_class->is_a_source = vips_foreign_load_png_source_is_a_source; + + VIPS_ARG_OBJECT( class, "source", 1, + _( "Source" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadPngSource, source ), + VIPS_TYPE_SOURCE ); + +} + +static void +vips_foreign_load_png_source_init( VipsForeignLoadPngSource *source ) +{ +} + +typedef struct _VipsForeignLoadPngFile { + VipsForeignLoadPng parent_object; + + /* Filename for load. + */ + char *filename; + +} VipsForeignLoadPngFile; + +typedef VipsForeignLoadPngClass VipsForeignLoadPngFileClass; + +G_DEFINE_TYPE( VipsForeignLoadPngFile, vips_foreign_load_png_file, + vips_foreign_load_png_get_type() ); + +static int +vips_foreign_load_png_file_build( VipsObject *object ) +{ + VipsForeignLoadPng *png = (VipsForeignLoadPng *) object; + VipsForeignLoadPngFile *file = (VipsForeignLoadPngFile *) object; + + if( file->filename && + !(png->source = vips_source_new_from_file( file->filename )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_png_file_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static gboolean +vips_foreign_load_png_file_is_a( const char *filename ) +{ + VipsSource *source; + gboolean result; + + if( !(source = vips_source_new_from_file( filename )) ) + return( FALSE ); + result = vips_foreign_load_png_source_is_a_source( source ); + VIPS_UNREF( source ); + + return( result ); +} + +const char *vips_foreign_load_png_file_suffs[] = { ".png", NULL }; + +static void +vips_foreign_load_png_file_class_init( VipsForeignLoadPngFileClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignClass *foreign_class = (VipsForeignClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "pngload"; + object_class->description = _( "load png from file" ); + object_class->build = vips_foreign_load_png_file_build; + + foreign_class->suffs = vips_foreign_load_png_file_suffs; + + load_class->is_a = vips_foreign_load_png_file_is_a; + + VIPS_ARG_STRING( class, "filename", 1, + _( "Filename" ), + _( "Filename to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadPngFile, filename ), + NULL ); +} + +static void +vips_foreign_load_png_file_init( VipsForeignLoadPngFile *file ) +{ +} + +typedef struct _VipsForeignLoadPngBuffer { + VipsForeignLoadPng parent_object; + + /* Load from a buffer. + */ + VipsBlob *blob; + +} VipsForeignLoadPngBuffer; + +typedef VipsForeignLoadPngClass VipsForeignLoadPngBufferClass; + +G_DEFINE_TYPE( VipsForeignLoadPngBuffer, vips_foreign_load_png_buffer, + vips_foreign_load_png_get_type() ); + +static int +vips_foreign_load_png_buffer_build( VipsObject *object ) +{ + VipsForeignLoadPng *png = (VipsForeignLoadPng *) object; + VipsForeignLoadPngBuffer *buffer = (VipsForeignLoadPngBuffer *) object; + + if( buffer->blob && + !(png->source = vips_source_new_from_memory( + VIPS_AREA( buffer->blob )->data, + VIPS_AREA( buffer->blob )->length )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_png_buffer_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static gboolean +vips_foreign_load_png_buffer_is_a_buffer( const void *buf, size_t len ) +{ + VipsSource *source; + gboolean result; + + if( !(source = vips_source_new_from_memory( buf, len )) ) + return( FALSE ); + result = vips_foreign_load_png_source_is_a_source( source ); + VIPS_UNREF( source ); + + return( result ); +} + +static void +vips_foreign_load_png_buffer_class_init( VipsForeignLoadPngBufferClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "pngload_buffer"; + object_class->description = _( "load png from buffer" ); + object_class->build = vips_foreign_load_png_buffer_build; + + load_class->is_a_buffer = vips_foreign_load_png_buffer_is_a_buffer; + + VIPS_ARG_BOXED( class, "buffer", 1, + _( "Buffer" ), + _( "Buffer to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadPngBuffer, blob ), + VIPS_TYPE_BLOB ); + +} + +static void +vips_foreign_load_png_buffer_init( VipsForeignLoadPngBuffer *buffer ) +{ +} + +#endif /*HAVE_SPNG*/