Merge branch 'master' into add-openjpeg

This commit is contained in:
John Cupitt 2021-03-27 15:22:13 +00:00 committed by GitHub
commit 69fcf1b9e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 3166 additions and 1865 deletions

View File

@ -34,6 +34,7 @@
- fix issue thumbnailing RGBA images in linear mode [jjonesrs]
- improve vipsthumbnail profile handling
- fix tiff deflate predictor setting [Adios]
- fix vector path for composite on i386 [kleisauke]
18/12/20 started 8.10.5
- fix potential /0 in animated webp load [lovell]

View File

@ -116,6 +116,13 @@ Debug build:
make
make install
# Built-in loaders
libvips has a number of built-in loaders and savers. You can disable these if
you wish, for example:
./configure --prefix=/Users/john/vips --without-nsgif --without-ppm
# Dependencies
libvips has to have `libglib2.0-dev` and `libexpat1-dev`. Other dependencies
@ -128,14 +135,14 @@ libraries automatically. See `./configure --help` for a set of flags to
control library detection. Packages are generally found with `pkg-config`,
so make sure that is working.
Libraries like giflib and nifti do not use `pkg-config` so libvips will also
Libraries like nifti do not use `pkg-config` so libvips will also
look for them in the default path and in `$prefix`. If you have installed
your own versions of these libraries in a different location, libvips will
not see them. Use switches to libvips configure like:
./configure --prefix=/Users/john/vips \
--with-giflib-includes=/opt/local/include \
--with-giflib-libraries=/opt/local/lib
--with-nifti-includes=/opt/local/include \
--with-nifti-libraries=/opt/local/lib
or perhaps:
@ -151,11 +158,6 @@ The IJG JPEG library. Use the `-turbo` version if you can.
If available, libvips adds support for EXIF metadata in JPEG files.
### giflib
The standard gif loader. If this is not present, vips will try to load gifs
via imagemagick instead.
### librsvg
The usual SVG loader. If this is not present, vips will try to load SVGs

View File

@ -190,7 +190,6 @@ AC_PROG_CC_STDC
AC_PROG_CXX
AC_PROG_INSTALL
AC_PROG_LN_S
AM_WITH_DMALLOC
AC_ARG_WITH([doxygen],
AS_HELP_STRING([--without-doxygen], [build without doxygen (default: test)]))
@ -257,7 +256,7 @@ AM_GLIB_GNU_GETTEXT
# [ax_gcc_version_option=yes],
# [ax_gcc_version_option=no]
# )
AC_MSG_CHECKING([for gcc version])
AC_MSG_CHECKING([for $CC version])
GCC_VERSION=""
version=$($CC -dumpversion)
if test $? = 0; then
@ -311,7 +310,7 @@ AC_TYPE_SIZE_T
# g++/gcc 4.x and 5.x have rather broken vector support ... 5.4.1 seems to
# work, but 5.4.0 fails to even compile
AC_MSG_CHECKING([for gcc with working vector support])
AC_MSG_CHECKING([for $CC with working vector support])
if test x"$GCC_VERSION_MAJOR" != x"4" -a x"$GCC_VERSION_MAJOR" != x"5"; then
AC_MSG_RESULT([yes])
else
@ -324,7 +323,7 @@ if test x"$ax_cv_have_var_attribute_vector_size" = x"yes"; then
AC_MSG_CHECKING([for C++ vector shuffle])
AC_LANG_PUSH([C++])
AC_TRY_COMPILE([
typedef float v4f __attribute__((vector_size(4 * sizeof(float))));
typedef float v4f __attribute__((vector_size(4 * sizeof(float)),aligned(16)));
],[
v4f f; f[3] = 99;
],[
@ -347,7 +346,7 @@ if test x"$have_vector_shuffle" = x"yes"; then
AC_MSG_CHECKING([for C++ vector arithmetic])
AC_LANG_PUSH([C++])
AC_TRY_COMPILE([
typedef float v4f __attribute__((vector_size(4 * sizeof(float))));
typedef float v4f __attribute__((vector_size(4 * sizeof(float)),aligned(16)));
],[
v4f f = {1, 2, 3, 4}; f *= 12.0;
v4f g = {5, 6, 7, 8}; f = g > 0 ? g : -1 * g;
@ -367,7 +366,7 @@ if test x"$have_vector_arith" = x"yes"; then
AC_MSG_CHECKING([for C++ signed constants in vector templates])
AC_LANG_PUSH([C++])
AC_TRY_COMPILE([
typedef float v4f __attribute__((vector_size(4 * sizeof(float))));
typedef float v4f __attribute__((vector_size(4 * sizeof(float)),aligned(16)));
template <typename T>
static void
h( v4f B )
@ -394,7 +393,7 @@ fi
AC_FUNC_MEMCMP
AC_FUNC_MMAP
AC_FUNC_VPRINTF
AC_CHECK_FUNCS([getcwd gettimeofday getwd memset munmap putenv realpath strcasecmp strchr strcspn strdup strerror strrchr strspn vsnprintf realpath mkstemp mktemp random rand sysconf atexit])
AC_CHECK_FUNCS([getcwd gettimeofday getwd memset munmap putenv realpath strcasecmp strchr strcspn strdup strerror strrchr strspn vsnprintf realpath mkstemp mktemp random rand sysconf atexit _aligned_malloc posix_memalign memalign])
AC_CHECK_LIB(m,cbrt,[AC_DEFINE(HAVE_CBRT,1,[have cbrt() in libm.])])
AC_CHECK_LIB(m,hypot,[AC_DEFINE(HAVE_HYPOT,1,[have hypot() in libm.])])
AC_CHECK_LIB(m,atan2,[AC_DEFINE(HAVE_ATAN2,1,[have atan2() in libm.])])
@ -1010,6 +1009,16 @@ VIPS_LIBS="$VIPS_LIBS $MATIO_LIBS"
# not external libraries, but have options to disable them, helps to
# reduce attack surface
AC_ARG_WITH([nsgif],
AS_HELP_STRING([--without-nsgif], [build without nsgif load (default: with)]))
if test x"$with_nsgif" != x"no"; then
AC_DEFINE(HAVE_NSGIF,1,[define to build nsgif load support.])
with_nsgif=yes
fi
AM_CONDITIONAL(ENABLE_NSGIF, [test x"$with_nsgif" = x"yes"])
AC_ARG_WITH([ppm],
AS_HELP_STRING([--without-ppm], [build without ppm (default: with)]))
@ -1133,19 +1142,6 @@ VIPS_CFLAGS="$VIPS_CFLAGS $TIFF_CFLAGS"
VIPS_INCLUDES="$VIPS_INCLUDES $TIFF_INCLUDES"
VIPS_LIBS="$VIPS_LIBS $TIFF_LIBS"
# giflib
FIND_GIFLIB(
[with_giflib="yes (found by search)"
],
[AC_MSG_WARN([giflib not found; disabling direct GIF support])
with_giflib=no
]
)
VIPS_CFLAGS="$VIPS_CFLAGS $GIFLIB_CFLAGS"
VIPS_INCLUDES="$VIPS_INCLUDES $GIFLIB_INCLUDES"
VIPS_LIBS="$VIPS_LIBS $GIFLIB_LIBS"
# Look for libspng first
# 0.6.1 uses "libspng.pc", git master libspng uses "spng.pc"
AC_ARG_WITH([libspng],
@ -1323,6 +1319,7 @@ gobject introspection: $found_introspection, \
RAD load/save: $with_radiance, \
Analyze7 load/save: $with_analyze, \
PPM load/save: $with_ppm, \
GIF load: $with_nsgif, \
generate C++ docs: $with_doxygen, \
use fftw3 for FFT: $with_fftw, \
accelerate loops with orc: $with_orc, \
@ -1336,7 +1333,6 @@ PNG load/save with libpng: $with_png, \
8bpp PNG quantisation: $with_imagequant, \
TIFF load/save with libtiff: $with_tiff, \
image pyramid save: $with_gsf, \
GIF load with giflib: $with_giflib, \
HEIC/AVIF load/save with libheif: $with_heif, \
WebP load/save with libwebp: $with_libwebp, \
PDF load with PDFium: $with_pdfium, \
@ -1381,6 +1377,7 @@ AC_CONFIG_FILES([
libvips/convolution/Makefile
libvips/deprecated/Makefile
libvips/foreign/Makefile
libvips/foreign/libnsgif/Makefile
libvips/freqfilt/Makefile
libvips/histogram/Makefile
libvips/draw/Makefile
@ -1421,6 +1418,7 @@ gobject introspection: $found_introspection
RAD load/save: $with_radiance
Analyze7 load/save: $with_analyze
PPM load/save: $with_ppm
GIF load: $with_nsgif
generate C++ docs: $with_doxygen
## Optional dependencies
@ -1443,7 +1441,6 @@ PNG load/save with libpng: $with_png
TIFF load/save with libtiff: $with_tiff
image pyramid save: $with_gsf
(requires libgsf-1 1.14.26 or later)
GIF load with giflib: $with_giflib
HEIC/AVIF load/save with libheif: $with_heif
WebP load/save with libwebp: $with_libwebp
(requires libwebp, libwebpmux, libwebpdemux 0.6.0 or later)

View File

@ -5,9 +5,10 @@ pkginclude_HEADERS = \
VConnection8.h \
vips8
vips-operators.h:
echo "// headers for vips operations" > vips-operators.h; \
echo -n "// " >> vips-operators.h; \
date >> vips-operators.h; \
echo "// this file is generated automatically, do not edit!" >> vips-operators.h; \
./../../gen-operators.py -g h >> vips-operators.h
vips-operators:
echo "// headers for vips operations" > vips-operators; \
echo "// paste this file into VImage8.h, do not leave in repo" > vips-operators; \
echo -n "// " >> vips-operators; \
date >> vips-operators; \
echo "// this file is generated automatically, do not edit!" >> vips-operators; \
./../../gen-operators.py -g h >> vips-operators

View File

@ -2983,7 +2983,7 @@ static VImage heifload_source( VSource source, VOption *options = 0 );
* - **background** -- Background value, std::vector<double>.
* - **page_height** -- Set page height for multipage save, int.
*
* @param filename Filename to load from.
* @param filename Filename to save to.
* @param options Set of options.
*/
void heifsave( const char *filename, VOption *options = 0 ) const;

View File

@ -9,6 +9,10 @@ else
OPTIONAL_DIST_DIR += deprecated
endif
if ENABLE_NSGIF
OPTIONAL_LIB += foreign/libnsgif/libnsgif.la
endif
SUBDIRS = \
include \
foreign \

View File

@ -1,7 +1,7 @@
noinst_LTLIBRARIES = libcolour.la
SUBDIRS = profiles
noinst_LTLIBRARIES = libcolour.la
libcolour_la_SOURCES = \
profiles.c \
profiles.h \

View File

@ -55,13 +55,17 @@
#include <stdio.h>
#include <string.h>
#if _MSC_VER
#ifdef _MSC_VER
#include <cstdlib>
#else
#include <stdlib.h>
#endif
#include <math.h>
#if defined(HAVE__ALIGNED_MALLOC) || defined(HAVE_MEMALIGN)
#include <malloc.h>
#endif
#include <vips/vips.h>
#include <vips/internal.h>
#include <vips/debug.h>
@ -81,7 +85,7 @@
#ifdef HAVE_VECTOR_ARITH
/* A vector of four floats.
*/
typedef float v4f __attribute__((vector_size(4 * sizeof(float))));
typedef float v4f __attribute__((vector_size(4 * sizeof(float)),aligned(16)));
#endif /*HAVE_VECTOR_ARITH*/
typedef struct _VipsCompositeBase {
@ -130,12 +134,6 @@ typedef struct _VipsCompositeBase {
*/
gboolean skippable;
#ifdef HAVE_VECTOR_ARITH
/* max_band as a vector, for the RGBA case.
*/
v4f max_band_vec;
#endif /*HAVE_VECTOR_ARITH*/
} VipsCompositeBase;
typedef VipsConversionClass VipsCompositeBaseClass;
@ -168,6 +166,14 @@ vips_composite_base_dispose( GObject *gobject )
/* Our sequence value.
*/
typedef struct {
#ifdef HAVE_VECTOR_ARITH
/* max_band as a vector, for the RGBA case. This must be
* defined first to ensure that the member is aligned
* on a 16-byte boundary.
*/
v4f max_band_vec;
#endif /*HAVE_VECTOR_ARITH*/
VipsCompositeBase *composite;
/* Full set of input regions, each made on the corresponding input
@ -196,6 +202,39 @@ typedef struct {
} VipsCompositeSequence;
#ifdef HAVE_VECTOR_ARITH
/* Allocate aligned memory. The return value can be released
* by calling the vips_free_aligned() function, for example:
* VIPS_FREEF( vips_free_aligned, ptr );
*/
static inline void *
vips_alloc_aligned( size_t sz, size_t align )
{
g_assert( !(align & (align - 1)) );
#ifdef HAVE__ALIGNED_MALLOC
return _aligned_malloc( sz, align );
#elif defined(HAVE_POSIX_MEMALIGN)
void *ptr;
if( posix_memalign( &ptr, align, sz ) ) return NULL;
return ptr;
#elif defined(HAVE_MEMALIGN)
return memalign( align, sz );
#else
#error Missing aligned alloc implementation
#endif
}
static inline void
vips_free_aligned( void* ptr )
{
#ifdef HAVE__ALIGNED_MALLOC
_aligned_free( ptr );
#else /*defined(HAVE_POSIX_MEMALIGN) || defined(HAVE_MEMALIGN)*/
free( ptr );
#endif
}
#endif /*HAVE_VECTOR_ARITH*/
static int
vips_composite_stop( void *vseq, void *a, void *b )
{
@ -216,7 +255,11 @@ vips_composite_stop( void *vseq, void *a, void *b )
VIPS_FREE( seq->enabled );
VIPS_FREE( seq->p );
#ifdef HAVE_VECTOR_ARITH
VIPS_FREEF( vips_free_aligned, seq );
#else /*!defined(HAVE_VECTOR_ARITH)*/
VIPS_FREE( seq );
#endif /*HAVE_VECTOR_ARITH*/
return( 0 );
}
@ -230,7 +273,14 @@ vips_composite_start( VipsImage *out, void *a, void *b )
VipsCompositeSequence *seq;
int i, n;
#ifdef HAVE_VECTOR_ARITH
/* Ensure that the memory is aligned on a 16-byte boundary.
*/
if( !(seq = ((VipsCompositeSequence *) vips_alloc_aligned(
sizeof( VipsCompositeSequence ), 16 ))) )
#else /*!defined(HAVE_VECTOR_ARITH)*/
if( !(seq = VIPS_NEW( NULL, VipsCompositeSequence )) )
#endif /*HAVE_VECTOR_ARITH*/
return( NULL );
seq->composite = composite;
@ -280,7 +330,19 @@ vips_composite_start( VipsImage *out, void *a, void *b )
return( NULL );
}
}
#ifdef HAVE_VECTOR_ARITH
/* We need a float version for the vector path.
*/
if( composite->bands == 3 )
seq->max_band_vec = (v4f){
(float) composite->max_band[0],
(float) composite->max_band[1],
(float) composite->max_band[2],
(float) composite->max_band[3]
};
#endif
return( seq );
}
@ -664,9 +726,11 @@ vips_composite_base_blend( VipsCompositeBase *composite,
*/
template <typename T>
static void
vips_composite_base_blend3( VipsCompositeBase *composite,
vips_composite_base_blend3( VipsCompositeSequence *seq,
VipsBlendMode mode, v4f &B, T * restrict p )
{
VipsCompositeBase *composite = seq->composite;
v4f A;
float aA;
float aB;
@ -684,7 +748,7 @@ vips_composite_base_blend3( VipsCompositeBase *composite,
A[2] = p[2];
A[3] = p[3];
A /= composite->max_band_vec;
A /= seq->max_band_vec;
aA = A[3];
aB = B[3];
@ -975,7 +1039,7 @@ vips_combine_pixels3( VipsCompositeSequence *seq, VipsPel *q )
/* Scale the base pixel to 0 - 1.
*/
B /= composite->max_band_vec;
B /= seq->max_band_vec;
aB = B[3];
if( !composite->premultiplied ) {
@ -987,7 +1051,7 @@ vips_combine_pixels3( VipsCompositeSequence *seq, VipsPel *q )
int j = seq->enabled[i];
VipsBlendMode m = n_mode == 1 ? mode[0] : mode[j - 1];
vips_composite_base_blend3<T>( composite, m, B, tp[i] );
vips_composite_base_blend3<T>( seq, m, B, tp[i] );
}
/* Unpremultiply, if necessary.
@ -1006,7 +1070,7 @@ vips_combine_pixels3( VipsCompositeSequence *seq, VipsPel *q )
/* Write back as a full range pixel, clipping to range.
*/
B *= composite->max_band_vec;
B *= seq->max_band_vec;
if( min_T != 0 ||
max_T != 0 ) {
float low = min_T;
@ -1386,14 +1450,6 @@ vips_composite_base_build( VipsObject *object )
return( -1 );
}
#ifdef HAVE_VECTOR_ARITH
/* We need a float version for the vector path.
*/
if( composite->bands == 3 )
for( int b = 0; b <= 3; b++ )
composite->max_band_vec[b] = composite->max_band[b];
#endif /*HAVE_VECTOR_ARITH*/
/* Transform the input images to match in format. We may have
* mixed float and double, for example.
*/

View File

@ -1,3 +1,7 @@
if ENABLE_NSGIF
SUBDIRS = libnsgif
endif
noinst_LTLIBRARIES = libforeign.la
libforeign_la_SOURCES = \
@ -34,10 +38,13 @@ libforeign_la_SOURCES = \
matrixsave.c \
niftiload.c \
niftisave.c \
nsgifload.c \
openexr2vips.c \
openexrload.c \
openslide2vips.c \
openslideload.c \
quantise.c \
pdfload.c \
pdfiumload.c \
pdfload.c \
pforeign.h \
@ -68,7 +75,7 @@ libforeign_la_SOURCES = \
webpload.c \
webpsave.c
EXTRA_DIST =
EXTRA_DIST = libnsgif
AM_CPPFLAGS = -I${top_srcdir}/libvips/include @VIPS_CFLAGS@ @VIPS_INCLUDES@

View File

@ -2194,22 +2194,25 @@ vips_foreign_operation_init( void )
extern GType vips_foreign_load_nifti_source_get_type( void );
extern GType vips_foreign_save_nifti_get_type( void );
extern GType vips_foreign_load_gif_file_get_type( void );
extern GType vips_foreign_load_gif_buffer_get_type( void );
extern GType vips_foreign_load_gif_source_get_type( void );
extern GType vips_foreign_load_nsgif_file_get_type( void );
extern GType vips_foreign_load_nsgif_buffer_get_type( void );
extern GType vips_foreign_load_nsgif_source_get_type( void );
vips_foreign_load_csv_file_get_type();
vips_foreign_load_csv_source_get_type();
vips_foreign_save_csv_file_get_type();
vips_foreign_save_csv_target_get_type();
vips_foreign_load_matrix_file_get_type();
vips_foreign_load_matrix_source_get_type();
vips_foreign_save_matrix_file_get_type();
vips_foreign_save_matrix_target_get_type();
vips_foreign_print_matrix_get_type();
vips_foreign_load_raw_get_type();
vips_foreign_save_raw_get_type();
vips_foreign_save_raw_fd_get_type();
vips_foreign_load_vips_file_get_type();
vips_foreign_load_vips_source_get_type();
vips_foreign_save_vips_file_get_type();
@ -2262,11 +2265,11 @@ vips_foreign_operation_init( void )
vips_foreign_save_jp2k_target_get_type();
#endif /*HAVE_LIBOPENJP2*/
#ifdef HAVE_GIFLIB
vips_foreign_load_gif_file_get_type();
vips_foreign_load_gif_buffer_get_type();
vips_foreign_load_gif_source_get_type();
#endif /*HAVE_GIFLIB*/
#ifdef HAVE_NSGIF
vips_foreign_load_nsgif_file_get_type();
vips_foreign_load_nsgif_buffer_get_type();
vips_foreign_load_nsgif_source_get_type();
#endif /*HAVE_NSGIF*/
#ifdef HAVE_GSF
vips_foreign_save_dz_file_get_type();

File diff suppressed because it is too large Load Diff

View File

@ -321,6 +321,7 @@ vips_foreign_save_heif_build( VipsObject *object )
VipsForeignSave *save = (VipsForeignSave *) object;
VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object;
const char *filename;
struct heif_error error;
struct heif_writer writer;
char *chroma;
@ -335,6 +336,15 @@ vips_foreign_save_heif_build( VipsObject *object )
if( vips_copy( save->ready, &heif->image, NULL ) )
return( -1 );
/* Compression defaults to VIPS_FOREIGN_HEIF_COMPRESSION_AV1 for .avif
* suffix.
*/
filename = vips_connection_filename( VIPS_CONNECTION( heif->target ) );
if( !vips_object_argument_isset( object, "compression" ) &&
filename &&
vips_iscasepostfix( filename, ".avif" ) )
heif->compression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1;
error = heif_context_get_encoder_for_format( heif->ctx,
(enum heif_compression_format) heif->compression,
&heif->encoder );
@ -566,7 +576,7 @@ vips_foreign_save_heif_file_class_init( VipsForeignSaveHeifFileClass *class )
VIPS_ARG_STRING( class, "filename", 1,
_( "Filename" ),
_( "Filename to load from" ),
_( "Filename to save to" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsForeignSaveHeifFile, filename ),
NULL );
@ -722,7 +732,8 @@ vips_foreign_save_heif_target_init( VipsForeignSaveHeifTarget *target )
*
* Set @lossless %TRUE to switch to lossless compression.
*
* Use @compression to set the encoder e.g. HEVC, AVC, AV1
* Use @compression to set the encoder e.g. HEVC, AVC, AV1. It defaults to AV1
* if the target filename ends with ".avif", otherwise HEVC.
*
* Use @speed to control the CPU effort spent improving compression.
* This is currently only applicable to AV1 encoders, defaults to 5.

View File

@ -0,0 +1,15 @@
EXTRA_DIST = \
README-ns \
README.md \
patches \
update.sh \
utils
noinst_LTLIBRARIES = libnsgif.la
libnsgif_la_SOURCES = \
libnsgif.h \
libnsgif.c \
lzw.c \
lzw.h

View File

@ -0,0 +1,36 @@
libnsgif - Decoding GIF files
=============================
The functions provided by this library allow for efficient progressive
GIF decoding. Whilst the initialisation does not ensure that there is
sufficient image data to complete the entire frame, it does ensure
that the information provided is valid. Any subsequent attempts to
decode an initialised GIF are guaranteed to succeed, and any bytes of
the image not present are assumed to be totally transparent.
To begin decoding a GIF, the 'gif' structure must be initialised with
the 'gif_data' and 'buffer_size' set to their initial values. The
'buffer_position' should initially be 0, and will be internally
updated as the decoding commences. The caller should then repeatedly
call gif_initialise() with the structure until the function returns 1,
or no more data is avaliable.
Once the initialisation has begun, the decoder completes the variables
'frame_count' and 'frame_count_partial'. The former being the total
number of frames that have been successfully initialised, and the
latter being the number of frames that a partial amount of data is
available for. This assists the caller in managing the animation
whilst decoding is continuing.
To decode a frame, the caller must use gif_decode_frame() which
updates the current 'frame_image' to reflect the desired frame. The
required 'disposal_method' is also updated to reflect how the frame
should be plotted. The caller must not assume that the current
'frame_image' will be valid between calls if initialisation is still
occuring, and should either always request that the frame is decoded
(no processing will occur if the 'decoded_frame' has not been
invalidated by initialisation) or perform the check itself.
It should be noted that gif_finalise() should always be called, even
if no frames were initialised. Additionally, it is the responsibility
of the caller to free 'gif_data'.

View File

@ -0,0 +1,16 @@
# libnsgif
This is [libnsgif](https://www.netsurf-browser.org/projects/libnsgif/),
but within the libvips build system.
# To update
Run `./update.sh` to update this copy of libnsgif from the upstream repo. It
will also patch libnsgif.c to prevent it modifying the input.
Last updated 28 Feb 2021.
# To do
No attempt made to run tests or build docs. Though the gif loader is tested as
part of the libvips test suite.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,192 @@
/*
* Copyright 2004 Richard Wilson <richard.wilson@netsurf-browser.org>
* Copyright 2008 Sean Fox <dyntryx@gmail.com>
*
* This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
* Licenced under the MIT License,
* http://www.opensource.org/licenses/mit-license.php
*/
/**
* \file
* Interface to progressive animated GIF file decoding.
*/
#ifndef _LIBNSGIF_H_
#define _LIBNSGIF_H_
#include <stdbool.h>
#include <inttypes.h>
/* Error return values */
typedef enum {
GIF_WORKING = 1,
GIF_OK = 0,
GIF_INSUFFICIENT_FRAME_DATA = -1,
GIF_FRAME_DATA_ERROR = -2,
GIF_INSUFFICIENT_DATA = -3,
GIF_DATA_ERROR = -4,
GIF_INSUFFICIENT_MEMORY = -5,
GIF_FRAME_NO_DISPLAY = -6,
GIF_END_OF_FRAME = -7
} gif_result;
/** GIF frame data */
typedef struct gif_frame {
/** whether the frame should be displayed/animated */
bool display;
/** delay (in cs) before animating the frame */
unsigned int frame_delay;
/* Internal members are listed below */
/** offset (in bytes) to the GIF frame data */
unsigned int frame_pointer;
/** whether the frame has previously been used */
bool virgin;
/** whether the frame is totally opaque */
bool opaque;
/** whether a forcable screen redraw is required */
bool redraw_required;
/** how the previous frame should be disposed; affects plotting */
unsigned char disposal_method;
/** whether we acknoledge transparency */
bool transparency;
/** the index designating a transparent pixel */
unsigned char transparency_index;
/** x co-ordinate of redraw rectangle */
unsigned int redraw_x;
/** y co-ordinate of redraw rectangle */
unsigned int redraw_y;
/** width of redraw rectangle */
unsigned int redraw_width;
/** height of redraw rectangle */
unsigned int redraw_height;
} gif_frame;
/* API for Bitmap callbacks */
typedef void* (*gif_bitmap_cb_create)(int width, int height);
typedef void (*gif_bitmap_cb_destroy)(void *bitmap);
typedef unsigned char* (*gif_bitmap_cb_get_buffer)(void *bitmap);
typedef void (*gif_bitmap_cb_set_opaque)(void *bitmap, bool opaque);
typedef bool (*gif_bitmap_cb_test_opaque)(void *bitmap);
typedef void (*gif_bitmap_cb_modified)(void *bitmap);
/** Bitmap callbacks function table */
typedef struct gif_bitmap_callback_vt {
/** Create a bitmap. */
gif_bitmap_cb_create bitmap_create;
/** Free a bitmap. */
gif_bitmap_cb_destroy bitmap_destroy;
/** Return a pointer to the pixel data in a bitmap. */
gif_bitmap_cb_get_buffer bitmap_get_buffer;
/* Members below are optional */
/** Sets whether a bitmap should be plotted opaque. */
gif_bitmap_cb_set_opaque bitmap_set_opaque;
/** Tests whether a bitmap has an opaque alpha channel. */
gif_bitmap_cb_test_opaque bitmap_test_opaque;
/** The bitmap image has changed, so flush any persistant cache. */
gif_bitmap_cb_modified bitmap_modified;
} gif_bitmap_callback_vt;
/** GIF animation data */
typedef struct gif_animation {
/** LZW decode context */
void *lzw_ctx;
/** callbacks for bitmap functions */
gif_bitmap_callback_vt bitmap_callbacks;
/** pointer to GIF data */
unsigned char *gif_data;
/** width of GIF (may increase during decoding) */
unsigned int width;
/** heigth of GIF (may increase during decoding) */
unsigned int height;
/** number of frames decoded */
unsigned int frame_count;
/** number of frames partially decoded */
unsigned int frame_count_partial;
/** decoded frames */
gif_frame *frames;
/** current frame decoded to bitmap */
int decoded_frame;
/** currently decoded image; stored as bitmap from bitmap_create callback */
void *frame_image;
/** number of times to loop animation */
int loop_count;
/* Internal members are listed below */
/** current index into GIF data */
unsigned int buffer_position;
/** total number of bytes of GIF data available */
unsigned int buffer_size;
/** current number of frame holders */
unsigned int frame_holders;
/** index in the colour table for the background colour */
unsigned int background_index;
/** image aspect ratio (ignored) */
unsigned int aspect_ratio;
/** size of colour table (in entries) */
unsigned int colour_table_size;
/** whether the GIF has a global colour table */
bool global_colours;
/** global colour table */
unsigned int *global_colour_table;
/** local colour table */
unsigned int *local_colour_table;
/** previous frame for GIF_FRAME_RESTORE */
void *prev_frame;
/** previous frame index */
int prev_index;
/** previous frame width */
unsigned prev_width;
/** previous frame height */
unsigned prev_height;
} gif_animation;
/**
* Initialises necessary gif_animation members.
*/
void gif_create(gif_animation *gif, gif_bitmap_callback_vt *bitmap_callbacks);
/**
* Initialises any workspace held by the animation and attempts to decode
* any information that hasn't already been decoded.
* If an error occurs, all previously decoded frames are retained.
*
* @return Error return value.
* - GIF_FRAME_DATA_ERROR for GIF frame data error
* - GIF_INSUFFICIENT_FRAME_DATA for insufficient data to process
* any more frames
* - GIF_INSUFFICIENT_MEMORY for memory error
* - GIF_DATA_ERROR for GIF error
* - GIF_INSUFFICIENT_DATA for insufficient data to do anything
* - GIF_OK for successful decoding
* - GIF_WORKING for successful decoding if more frames are expected
*/
gif_result gif_initialise(gif_animation *gif, size_t size, unsigned char *data);
/**
* Decodes a GIF frame.
*
* @return Error return value. If a frame does not contain any image data,
* GIF_OK is returned and gif->current_error is set to
* GIF_FRAME_NO_DISPLAY
* - GIF_FRAME_DATA_ERROR for GIF frame data error
* - GIF_INSUFFICIENT_FRAME_DATA for insufficient data to complete the frame
* - GIF_DATA_ERROR for GIF error (invalid frame header)
* - GIF_INSUFFICIENT_DATA for insufficient data to do anything
* - GIF_INSUFFICIENT_MEMORY for insufficient memory to process
* - GIF_OK for successful decoding
*/
gif_result gif_decode_frame(gif_animation *gif, unsigned int frame);
/**
* Releases any workspace held by a gif
*/
void gif_finalise(gif_animation *gif);
#endif

View File

@ -0,0 +1,377 @@
/*
* This file is part of NetSurf's LibNSGIF, http://www.netsurf-browser.org/
* Licensed under the MIT License,
* http://www.opensource.org/licenses/mit-license.php
*
* Copyright 2017 Michael Drake <michael.drake@codethink.co.uk>
*/
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include "lzw.h"
/**
* \file
* \brief LZW decompression (implementation)
*
* Decoder for GIF LZW data.
*/
/**
* Context for reading LZW data.
*
* LZW data is split over multiple sub-blocks. Each sub-block has a
* byte at the start, which says the sub-block size, and then the data.
* Zero-size sub-blocks have no data, and the biggest sub-block size is
* 255, which means there are 255 bytes of data following the sub-block
* size entry.
*
* Note that an individual LZW code can be split over up to three sub-blocks.
*/
struct lzw_read_ctx {
const uint8_t *data; /**< Pointer to start of input data */
uint32_t data_len; /**< Input data length */
uint32_t data_sb_next; /**< Offset to sub-block size */
const uint8_t *sb_data; /**< Pointer to current sub-block in data */
uint32_t sb_bit; /**< Current bit offset in sub-block */
uint32_t sb_bit_count; /**< Bit count in sub-block */
};
/**
* LZW dictionary entry.
*
* Records in the dictionary are composed of 1 or more entries.
* Entries point to previous entries which can be followed to compose
* the complete record. To compose the record in reverse order, take
* the `last_value` from each entry, and move to the previous entry.
* If the previous_entry's index is < the current clear_code, then it
* is the last entry in the record.
*/
struct lzw_dictionary_entry {
uint8_t last_value; /**< Last value for record ending at entry. */
uint8_t first_value; /**< First value for entry's record. */
uint16_t previous_entry; /**< Offset in dictionary to previous entry. */
};
/**
* LZW decompression context.
*/
struct lzw_ctx {
/** Input reading context */
struct lzw_read_ctx input;
uint32_t previous_code; /**< Code read from input previously. */
uint32_t previous_code_first; /**< First value of previous code. */
uint32_t initial_code_size; /**< Starting LZW code size. */
uint32_t current_code_size; /**< Current LZW code size. */
uint32_t current_code_size_max; /**< Max code value for current size. */
uint32_t clear_code; /**< Special Clear code value */
uint32_t eoi_code; /**< Special End of Information code value */
uint32_t current_entry; /**< Next position in table to fill. */
/** Output value stack. */
uint8_t stack_base[1 << LZW_CODE_MAX];
/** LZW decode dictionary. Generated during decode. */
struct lzw_dictionary_entry table[1 << LZW_CODE_MAX];
};
/* Exported function, documented in lzw.h */
lzw_result lzw_context_create(struct lzw_ctx **ctx)
{
struct lzw_ctx *c = malloc(sizeof(*c));
if (c == NULL) {
return LZW_NO_MEM;
}
*ctx = c;
return LZW_OK;
}
/* Exported function, documented in lzw.h */
void lzw_context_destroy(struct lzw_ctx *ctx)
{
free(ctx);
}
/**
* Advance the context to the next sub-block in the input data.
*
* \param[in] ctx LZW reading context, updated on success.
* \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise.
*/
static lzw_result lzw__block_advance(struct lzw_read_ctx *ctx)
{
uint32_t block_size;
uint32_t next_block_pos = ctx->data_sb_next;
const uint8_t *data_next = ctx->data + next_block_pos;
if (next_block_pos >= ctx->data_len) {
return LZW_NO_DATA;
}
block_size = *data_next;
if ((next_block_pos + block_size) >= ctx->data_len) {
return LZW_NO_DATA;
}
ctx->sb_bit = 0;
ctx->sb_bit_count = block_size * 8;
if (block_size == 0) {
ctx->data_sb_next += 1;
return LZW_OK_EOD;
}
ctx->sb_data = data_next + 1;
ctx->data_sb_next += block_size + 1;
return LZW_OK;
}
/**
* Get the next LZW code of given size from the raw input data.
*
* Reads codes from the input data stream coping with GIF data sub-blocks.
*
* \param[in] ctx LZW reading context, updated.
* \param[in] code_size Size of LZW code to get from data.
* \param[out] code_out Returns an LZW code on success.
* \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise.
*/
static inline lzw_result lzw__next_code(
struct lzw_read_ctx *ctx,
uint8_t code_size,
uint32_t *code_out)
{
uint32_t code = 0;
uint8_t current_bit = ctx->sb_bit & 0x7;
uint8_t byte_advance = (current_bit + code_size) >> 3;
assert(byte_advance <= 2);
if (ctx->sb_bit + code_size <= ctx->sb_bit_count) {
/* Fast path: code fully inside this sub-block */
const uint8_t *data = ctx->sb_data + (ctx->sb_bit >> 3);
switch (byte_advance) {
case 2: code |= data[2] << 16; /* Fall through */
case 1: code |= data[1] << 8; /* Fall through */
case 0: code |= data[0] << 0;
}
ctx->sb_bit += code_size;
} else {
/* Slow path: code spans sub-blocks */
uint8_t byte = 0;
uint8_t bits_remaining_0 = (code_size < (8 - current_bit)) ?
code_size : (8 - current_bit);
uint8_t bits_remaining_1 = code_size - bits_remaining_0;
uint8_t bits_used[3] = {
[0] = bits_remaining_0,
[1] = bits_remaining_1 < 8 ? bits_remaining_1 : 8,
[2] = bits_remaining_1 - 8,
};
while (true) {
const uint8_t *data = ctx->sb_data;
lzw_result res;
/* Get any data from end of this sub-block */
while (byte <= byte_advance &&
ctx->sb_bit < ctx->sb_bit_count) {
code |= data[ctx->sb_bit >> 3] << (byte << 3);
ctx->sb_bit += bits_used[byte];
byte++;
}
/* Check if we have all we need */
if (byte > byte_advance) {
break;
}
/* Move to next sub-block */
res = lzw__block_advance(ctx);
if (res != LZW_OK) {
return res;
}
}
}
*code_out = (code >> current_bit) & ((1 << code_size) - 1);
return LZW_OK;
}
/**
* Clear LZW code dictionary.
*
* \param[in] ctx LZW reading context, updated.
* \param[out] stack_pos_out Returns current stack position.
* \return LZW_OK or error code.
*/
static lzw_result lzw__clear_codes(
struct lzw_ctx *ctx,
const uint8_t ** const stack_pos_out)
{
uint32_t code;
uint8_t *stack_pos;
/* Reset dictionary building context */
ctx->current_code_size = ctx->initial_code_size + 1;
ctx->current_code_size_max = (1 << ctx->current_code_size) - 1;;
ctx->current_entry = (1 << ctx->initial_code_size) + 2;
/* There might be a sequence of clear codes, so process them all */
do {
lzw_result res = lzw__next_code(&ctx->input,
ctx->current_code_size, &code);
if (res != LZW_OK) {
return res;
}
} while (code == ctx->clear_code);
/* The initial code must be from the initial dictionary. */
if (code > ctx->clear_code) {
return LZW_BAD_ICODE;
}
/* Record this initial code as "previous" code, needed during decode. */
ctx->previous_code = code;
ctx->previous_code_first = code;
/* Reset the stack, and add first non-clear code added as first item. */
stack_pos = ctx->stack_base;
*stack_pos++ = code;
*stack_pos_out = stack_pos;
return LZW_OK;
}
/* Exported function, documented in lzw.h */
lzw_result lzw_decode_init(
struct lzw_ctx *ctx,
const uint8_t *compressed_data,
uint32_t compressed_data_len,
uint32_t compressed_data_pos,
uint8_t code_size,
const uint8_t ** const stack_base_out,
const uint8_t ** const stack_pos_out)
{
struct lzw_dictionary_entry *table = ctx->table;
/* Initialise the input reading context */
ctx->input.data = compressed_data;
ctx->input.data_len = compressed_data_len;
ctx->input.data_sb_next = compressed_data_pos;
ctx->input.sb_bit = 0;
ctx->input.sb_bit_count = 0;
/* Initialise the dictionary building context */
ctx->initial_code_size = code_size;
ctx->clear_code = (1 << code_size) + 0;
ctx->eoi_code = (1 << code_size) + 1;
/* Initialise the standard dictionary entries */
for (uint32_t i = 0; i < ctx->clear_code; ++i) {
table[i].first_value = i;
table[i].last_value = i;
}
*stack_base_out = ctx->stack_base;
return lzw__clear_codes(ctx, stack_pos_out);
}
/* Exported function, documented in lzw.h */
lzw_result lzw_decode(struct lzw_ctx *ctx,
const uint8_t ** const stack_pos_out)
{
lzw_result res;
uint32_t code_new;
uint32_t code_out;
uint8_t last_value;
uint8_t *stack_pos = ctx->stack_base;
uint32_t clear_code = ctx->clear_code;
uint32_t current_entry = ctx->current_entry;
struct lzw_dictionary_entry * const table = ctx->table;
/* Get a new code from the input */
res = lzw__next_code(&ctx->input, ctx->current_code_size, &code_new);
if (res != LZW_OK) {
return res;
}
/* Handle the new code */
if (code_new == clear_code) {
/* Got Clear code */
return lzw__clear_codes(ctx, stack_pos_out);
} else if (code_new == ctx->eoi_code) {
/* Got End of Information code */
return LZW_EOI_CODE;
} else if (code_new > current_entry) {
/* Code is invalid */
return LZW_BAD_CODE;
} else if (code_new < current_entry) {
/* Code is in table */
code_out = code_new;
last_value = table[code_new].first_value;
} else {
/* Code not in table */
*stack_pos++ = ctx->previous_code_first;
code_out = ctx->previous_code;
last_value = ctx->previous_code_first;
}
/* Add to the dictionary, only if there's space */
if (current_entry < (1 << LZW_CODE_MAX)) {
struct lzw_dictionary_entry *entry = table + current_entry;
entry->last_value = last_value;
entry->first_value = ctx->previous_code_first;
entry->previous_entry = ctx->previous_code;
ctx->current_entry++;
}
/* Ensure code size is increased, if needed. */
if (current_entry == ctx->current_code_size_max) {
if (ctx->current_code_size < LZW_CODE_MAX) {
ctx->current_code_size++;
ctx->current_code_size_max =
(1 << ctx->current_code_size) - 1;
}
}
/* Store details of this code as "previous code" to the context. */
ctx->previous_code_first = table[code_new].first_value;
ctx->previous_code = code_new;
/* Put rest of data for this code on output stack.
* Note, in the case of "code not in table", the last entry of the
* current code has already been placed on the stack above. */
while (code_out > clear_code) {
struct lzw_dictionary_entry *entry = table + code_out;
*stack_pos++ = entry->last_value;
code_out = entry->previous_entry;
}
*stack_pos++ = table[code_out].last_value;
*stack_pos_out = stack_pos;
return LZW_OK;
}

View File

@ -0,0 +1,105 @@
/*
* This file is part of NetSurf's LibNSGIF, http://www.netsurf-browser.org/
* Licensed under the MIT License,
* http://www.opensource.org/licenses/mit-license.php
*
* Copyright 2017 Michael Drake <michael.drake@codethink.co.uk>
*/
#ifndef LZW_H_
#define LZW_H_
/**
* \file
* \brief LZW decompression (interface)
*
* Decoder for GIF LZW data.
*/
/** Maximum LZW code size in bits */
#define LZW_CODE_MAX 12
/* Declare lzw internal context structure */
struct lzw_ctx;
/** LZW decoding response codes */
typedef enum lzw_result {
LZW_OK, /**< Success */
LZW_OK_EOD, /**< Success; reached zero-length sub-block */
LZW_NO_MEM, /**< Error: Out of memory */
LZW_NO_DATA, /**< Error: Out of data */
LZW_EOI_CODE, /**< Error: End of Information code */
LZW_BAD_ICODE, /**< Error: Bad initial LZW code */
LZW_BAD_CODE, /**< Error: Bad LZW code */
} lzw_result;
/**
* Create an LZW decompression context.
*
* \param[out] ctx Returns an LZW decompression context. Caller owned,
* free with lzw_context_destroy().
* \return LZW_OK on success, or appropriate error code otherwise.
*/
lzw_result lzw_context_create(
struct lzw_ctx **ctx);
/**
* Destroy an LZW decompression context.
*
* \param[in] ctx The LZW decompression context to destroy.
*/
void lzw_context_destroy(
struct lzw_ctx *ctx);
/**
* Initialise an LZW decompression context for decoding.
*
* Caller owns neither `stack_base_out` or `stack_pos_out`.
*
* \param[in] ctx The LZW decompression context to initialise.
* \param[in] compressed_data The compressed data.
* \param[in] compressed_data_len Byte length of compressed data.
* \param[in] compressed_data_pos Start position in data. Must be position
* of a size byte at sub-block start.
* \param[in] code_size The initial LZW code size to use.
* \param[out] stack_base_out Returns base of decompressed data stack.
* \param[out] stack_pos_out Returns current stack position.
* There are `stack_pos_out - stack_base_out`
* current stack entries.
* \return LZW_OK on success, or appropriate error code otherwise.
*/
lzw_result lzw_decode_init(
struct lzw_ctx *ctx,
const uint8_t *compressed_data,
uint32_t compressed_data_len,
uint32_t compressed_data_pos,
uint8_t code_size,
const uint8_t ** const stack_base_out,
const uint8_t ** const stack_pos_out);
/**
* Fill the LZW stack with decompressed data
*
* Ensure anything on the stack is used before calling this, as anything
* on the stack before this call will be trampled.
*
* Caller does not own `stack_pos_out`.
*
* \param[in] ctx LZW reading context, updated.
* \param[out] stack_pos_out Returns current stack position.
* Use with `stack_base_out` value from previous
* lzw_decode_init() call.
* There are `stack_pos_out - stack_base_out`
* current stack entries.
* \return LZW_OK on success, or appropriate error code otherwise.
*/
lzw_result lzw_decode(
struct lzw_ctx *ctx,
const uint8_t ** const stack_pos_out);
#endif

View File

@ -0,0 +1,32 @@
--- libnsgif-orig.c 2021-02-28 14:10:41.818557190 +0000
+++ libnsgif.c 2021-02-28 14:11:55.942285930 +0000
@@ -435,20 +435,15 @@
block_size = gif_data[0] + 1;
/* Check if the frame data runs off the end of the file */
if ((int)(gif_bytes - block_size) < 0) {
- /* Try to recover by signaling the end of the gif.
- * Once we get garbage data, there is no logical way to
- * determine where the next frame is. It's probably
- * better to partially load the gif than not at all.
- */
- if (gif_bytes >= 2) {
- gif_data[0] = 0;
- gif_data[1] = GIF_TRAILER;
- gif_bytes = 1;
- ++gif_data;
- break;
- } else {
- return GIF_INSUFFICIENT_FRAME_DATA;
- }
+ /* jcupitt 15/9/19
+ *
+ * There was code here to set a TRAILER tag. But this
+ * wrote to the input buffer, which will not work for
+ * libvips, where buffers can be mmaped read only files.
+ *
+ * Instead, just signal insufficient frame data.
+ */
+ return GIF_INSUFFICIENT_FRAME_DATA;
} else {
gif_bytes -= block_size;
gif_data += block_size;

View File

@ -0,0 +1,22 @@
#!/bin/bash
# attempt to update our copy of libnsgif from the upstream repo
set -e
git clone git://git.netsurf-browser.org/libnsgif.git
echo copying out source files ...
cp libnsgif/src/libnsgif.c .
cp libnsgif/include/libnsgif.h .
cp libnsgif/src/lzw.[ch] .
cp libnsgif/src/utils/log.h utils
echo applying patches ...
for patch in patches/*.patch; do
patch -p0 <$patch
done
echo cleaning up ...
rm -rf libnsgif

View File

@ -0,0 +1,21 @@
/*
* Copyright 2003 James Bursa <bursa@users.sourceforge.net>
* Copyright 2004 John Tytgat <John.Tytgat@aaug.net>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
* Licenced under the MIT License,
* http://www.opensource.org/licenses/mit-license.php
*/
#include <stdio.h>
#ifndef _LIBNSGIF_LOG_H_
#define _LIBNSGIF_LOG_H_
#ifdef NDEBUG
# define LOG(x) ((void) 0)
#else
# define LOG(x) do { fprintf(stderr, x), fputc('\n', stderr); } while (0)
#endif /* NDEBUG */
#endif /* _LIBNSGIF_LOG_H_ */

909
libvips/foreign/nsgifload.c Normal file
View File

@ -0,0 +1,909 @@
/* load a GIF with libnsgif
*
* 6/10/18
* - from gifload.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 VERBOSE
#define VIPS_DEBUG
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <vips/vips.h>
#include <vips/buf.h>
#include <vips/internal.h>
#include <vips/debug.h>
/* TODO:
*
* - libnsgif does not seem to support comment metadata
*
* - it always loads the entire source file into memory
*
* Notes:
*
* - hard to detect mono images -- local_colour_table in libnsgif is only set
* when we decode a frame, so we can't tell just from init whether any
* frames have colour info
*
* - don't bother detecting alpha -- if we can't detect RGB, alpha won't help
* much
*
*/
#ifdef HAVE_NSGIF
#include <libnsgif/libnsgif.h>
#define VIPS_TYPE_FOREIGN_LOAD_GIF (vips_foreign_load_nsgif_get_type())
#define VIPS_FOREIGN_LOAD_GIF( obj ) \
(G_TYPE_CHECK_INSTANCE_CAST( (obj), \
VIPS_TYPE_FOREIGN_LOAD_GIF, VipsForeignLoadNsgif ))
#define VIPS_FOREIGN_LOAD_GIF_CLASS( klass ) \
(G_TYPE_CHECK_CLASS_CAST( (klass), \
VIPS_TYPE_FOREIGN_LOAD_GIF, VipsForeignLoadNsgifClass))
#define VIPS_IS_FOREIGN_LOAD_GIF( obj ) \
(G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_FOREIGN_LOAD_GIF ))
#define VIPS_IS_FOREIGN_LOAD_GIF_CLASS( klass ) \
(G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_FOREIGN_LOAD_GIF ))
#define VIPS_FOREIGN_LOAD_GIF_GET_CLASS( obj ) \
(G_TYPE_INSTANCE_GET_CLASS( (obj), \
VIPS_TYPE_FOREIGN_LOAD_GIF, VipsForeignLoadNsgifClass ))
typedef struct _VipsForeignLoadNsgif {
VipsForeignLoad parent_object;
/* Load this page (frame number).
*/
int page;
/* Load this many pages.
*/
int n;
/* Load from this source (set by subclasses).
*/
VipsSource *source;
/* The animation created by libnsgif.
*/
gif_animation *anim;
/* The data/size pair we pass to libnsgif.
*/
unsigned char *data;
size_t size;
/* The frame_count, after we have removed undisplayable frames.
*/
int frame_count_displayable;
/* Delays between frames (in milliseconds). Array of length @n.
*/
int *delay;
/* A single centisecond value for compatibility.
*/
int gif_delay;
} VipsForeignLoadNsgif;
typedef VipsForeignLoadClass VipsForeignLoadNsgifClass;
G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadNsgif, vips_foreign_load_nsgif,
VIPS_TYPE_FOREIGN_LOAD );
static const char *
vips_foreign_load_nsgif_errstr( gif_result result )
{
switch( result ) {
case GIF_WORKING:
return( _( "Working" ) );
case GIF_OK:
return( _( "OK" ) );
case GIF_INSUFFICIENT_FRAME_DATA:
return( _( "Insufficient data to complete frame" ) );
case GIF_FRAME_DATA_ERROR:
return( _( "GIF frame data error" ) );
case GIF_INSUFFICIENT_DATA:
return( _( "Insufficient data to do anything" ) );
case GIF_DATA_ERROR:
return( _( "GIF header data error" ) );
case GIF_INSUFFICIENT_MEMORY:
return( _( "Insuficient memory to process" ) );
case GIF_FRAME_NO_DISPLAY:
return( _( "No display" ) );
case GIF_END_OF_FRAME:
return( _( "At end of frame" ) );
default:
return( _( "Unknown error" ) );
}
}
static void
vips_foreign_load_nsgif_error( VipsForeignLoadNsgif *gif, gif_result result )
{
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif );
vips_error( class->nickname, "%s",
vips_foreign_load_nsgif_errstr( result ) );
}
static void
vips_foreign_load_nsgif_dispose( GObject *gobject )
{
VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) gobject;
VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_dispose:\n" );
if( gif->anim ) {
gif_finalise( gif->anim );
VIPS_FREE( gif->anim );
}
VIPS_UNREF( gif->source );
VIPS_FREE( gif->delay );
G_OBJECT_CLASS( vips_foreign_load_nsgif_parent_class )->
dispose( gobject );
}
static VipsForeignFlags
vips_foreign_load_nsgif_get_flags_filename( const char *filename )
{
return( VIPS_FOREIGN_SEQUENTIAL );
}
static VipsForeignFlags
vips_foreign_load_nsgif_get_flags( VipsForeignLoad *load )
{
return( VIPS_FOREIGN_SEQUENTIAL );
}
static gboolean
vips_foreign_load_nsgif_is_a_source( VipsSource *source )
{
const unsigned char *data;
if( (data = vips_source_sniff( source, 4 )) &&
data[0] == 'G' &&
data[1] == 'I' &&
data[2] == 'F' &&
data[3] == '8' )
return( TRUE );
return( FALSE );
}
#ifdef VERBOSE
static void
print_frame( gif_frame *frame )
{
printf( "frame:\n" );
printf( " display = %d\n", frame->display );
printf( " frame_delay = %d\n", frame->frame_delay );
printf( " virgin = %d\n", frame->virgin );
printf( " opaque = %d\n", frame->opaque );
printf( " redraw_required = %d\n", frame->redraw_required );
printf( " disposal_method = %d\n", frame->disposal_method );
printf( " transparency = %d\n", frame->transparency );
printf( " transparency_index = %d\n", frame->transparency_index );
printf( " redraw_x = %d\n", frame->redraw_x );
printf( " redraw_y = %d\n", frame->redraw_y );
printf( " redraw_width = %d\n", frame->redraw_width );
printf( " redraw_height = %d\n", frame->redraw_height );
}
static void
print_animation( gif_animation *anim )
{
int i;
printf( "animation:\n" );
printf( " width = %d\n", anim->width );
printf( " height = %d\n", anim->height );
printf( " frame_count = %d\n", anim->frame_count );
printf( " frame_count_partial = %d\n", anim->frame_count_partial );
printf( " decoded_frame = %d\n", anim->decoded_frame );
printf( " frame_image = %p\n", anim->frame_image );
printf( " loop_count = %d\n", anim->loop_count );
printf( " frame_holders = %d\n", anim->frame_holders );
printf( " background_index = %d\n", anim->background_index );
printf( " colour_table_size = %d\n", anim->colour_table_size );
printf( " global_colours = %d\n", anim->global_colours );
printf( " global_colour_table = %p\n", anim->global_colour_table );
printf( " local_colour_table = %p\n", anim->local_colour_table );
for( i = 0; i < anim->frame_holders; i++ ) {
printf( "%d ", i );
print_frame( &anim->frames[i] );
}
}
#endif /*VERBOSE*/
static int
vips_foreign_load_nsgif_set_header( VipsForeignLoadNsgif *gif,
VipsImage *image )
{
VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_set_header:\n" );
vips_image_init_fields( image,
gif->anim->width, gif->anim->height * gif->n, 4,
VIPS_FORMAT_UCHAR, VIPS_CODING_NONE,
VIPS_INTERPRETATION_sRGB, 1.0, 1.0 );
vips_image_pipelinev( image, VIPS_DEMAND_STYLE_FATSTRIP, NULL );
if( vips_object_argument_isset( VIPS_OBJECT( gif ), "n" ) )
vips_image_set_int( image,
VIPS_META_PAGE_HEIGHT, gif->anim->height );
vips_image_set_int( image, VIPS_META_N_PAGES,
gif->frame_count_displayable );
vips_image_set_int( image, "gif-loop", gif->anim->loop_count );
vips_image_set_array_int( image, "delay", gif->delay, gif->n );
/* The deprecated gif-delay field is in centiseconds.
*/
vips_image_set_int( image, "gif-delay", gif->gif_delay );
return( 0 );
}
/* Scan the GIF as quickly as we can and extract transparency, bands, pages,
* etc.
*
* Don't flag any errors unless we have to: we want to work for corrupt or
* malformed GIFs.
*
* Close as soon as we can to free up the fd.
*/
static int
vips_foreign_load_nsgif_header( VipsForeignLoad *load )
{
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load );
VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) load;
const void *data;
size_t size;
gif_result result;
int i;
VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_header:\n" );
/* We map in the image, then minimise to close any underlying file
* object. This won't unmap.
*/
if( !(data = vips_source_map( gif->source, &size )) )
return( -1 );
vips_source_minimise( gif->source );
result = gif_initialise( gif->anim, size, (void *) data );
VIPS_DEBUG_MSG( "gif_initialise() = %d\n", result );
#ifdef VERBOSE
print_animation( gif->anim );
#endif /*VERBOSE*/
if( result != GIF_OK &&
result != GIF_WORKING &&
result != GIF_INSUFFICIENT_FRAME_DATA ) {
vips_foreign_load_nsgif_error( gif, result );
return( -1 );
}
else if( result == GIF_INSUFFICIENT_FRAME_DATA &&
load->fail ) {
vips_error( class->nickname, "%s", _( "truncated GIF" ) );
return( -1 );
}
/* Many GIFs have dead frames at the end. Remove these from our count.
*/
for( i = gif->anim->frame_count - 1;
i >= 0 && !gif->anim->frames[i].display; i-- )
;
gif->frame_count_displayable = i + 1;
#ifdef VERBOSE
if( gif->frame_count_displayable != gif->anim->frame_count )
printf( "vips_foreign_load_nsgif_open: "
"removed %d undisplayable frames\n",
gif->anim->frame_count - gif->frame_count_displayable );
#endif /*VERBOSE*/
if( !gif->frame_count_displayable ) {
vips_error( class->nickname, "%s", _( "no frames in GIF" ) );
return( -1 );
}
if( gif->n == -1 )
gif->n = gif->frame_count_displayable - gif->page;
if( gif->page < 0 ||
gif->n <= 0 ||
gif->page + gif->n > gif->frame_count_displayable ) {
vips_error( class->nickname, "%s", _( "bad page number" ) );
return( -1 );
}
/* In ms, frame_delay in cs.
*/
VIPS_FREE( gif->delay );
if( !(gif->delay = VIPS_ARRAY( NULL, gif->n, int )) )
return( -1 );
for( i = 0; i < gif->n; i++ )
gif->delay[i] =
10 * gif->anim->frames[gif->page + i].frame_delay;
gif->gif_delay = gif->anim->frames[0].frame_delay;
vips_foreign_load_nsgif_set_header( gif, load->out );
return( 0 );
}
static int
vips_foreign_load_nsgif_generate( VipsRegion *or,
void *seq, void *a, void *b, gboolean *stop )
{
VipsRect *r = &or->valid;
VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) a;
int y;
#ifdef VERBOSE
VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_generate: "
"top = %d, height = %d\n", r->top, r->height );
#endif /*VERBOSE*/
for( y = 0; y < r->height; y++ ) {
/* The page for this output line, and the line number in page.
*/
int page = (r->top + y) / gif->anim->height + gif->page;
int line = (r->top + y) % gif->anim->height;
gif_result result;
VipsPel *p, *q;
g_assert( line >= 0 && line < gif->anim->height );
g_assert( page >= 0 && page < gif->frame_count_displayable );
if( gif->anim->decoded_frame != page ) {
result = gif_decode_frame( gif->anim, page );
VIPS_DEBUG_MSG( " gif_decode_frame(%d) = %d\n",
page, result );
if( result != GIF_OK ) {
vips_foreign_load_nsgif_error( gif, result );
return( -1 );
}
#ifdef VERBOSE
print_animation( gif->anim );
#endif /*VERBOSE*/
}
p = gif->anim->frame_image +
line * gif->anim->width * sizeof( int );
q = VIPS_REGION_ADDR( or, 0, r->top + y );
memcpy( q, p, VIPS_REGION_SIZEOF_LINE( or ) );
}
return( 0 );
}
static int
vips_foreign_load_nsgif_load( VipsForeignLoad *load )
{
VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) load;
VipsImage **t = (VipsImage **)
vips_object_local_array( VIPS_OBJECT( load ), 4 );
VIPS_DEBUG_MSG( "vips_foreign_load_nsgif_load:\n" );
/* Make the output pipeline.
*/
t[0] = vips_image_new();
if( vips_foreign_load_nsgif_set_header( gif, t[0] ) )
return( -1 );
/* Strips 8 pixels high to avoid too many tiny regions.
*/
if( vips_image_generate( t[0],
NULL, vips_foreign_load_nsgif_generate, NULL, gif, 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_nsgif_class_init( VipsForeignLoadNsgifClass *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_nsgif_dispose;
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
object_class->nickname = "gifload_base";
object_class->description = _( "load GIF with libnsgif" );
/* High priority, so that we handle vipsheader etc.
*/
foreign_class->priority = 50;
load_class->get_flags_filename =
vips_foreign_load_nsgif_get_flags_filename;
load_class->get_flags = vips_foreign_load_nsgif_get_flags;
load_class->header = vips_foreign_load_nsgif_header;
load_class->load = vips_foreign_load_nsgif_load;
VIPS_ARG_INT( class, "page", 10,
_( "Page" ),
_( "Load this page from the file" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignLoadNsgif, page ),
0, 100000, 0 );
VIPS_ARG_INT( class, "n", 6,
_( "n" ),
_( "Load this many pages" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignLoadNsgif, n ),
-1, 100000, 1 );
}
static void *
vips_foreign_load_nsgif_bitmap_create( int width, int height )
{
/* ensure a stupidly large bitmap is not created
*/
return calloc( width * height, 4 );
}
static void
vips_foreign_load_nsgif_bitmap_set_opaque( void *bitmap, bool opaque )
{
(void) opaque; /* unused */
(void) bitmap; /* unused */
g_assert( bitmap );
}
static bool
vips_foreign_load_nsgif_bitmap_test_opaque( void *bitmap )
{
(void) bitmap; /* unused */
g_assert( bitmap );
return( false );
}
static unsigned char *
vips_foreign_load_nsgif_bitmap_get_buffer( void *bitmap )
{
g_assert( bitmap );
return( bitmap );
}
static void
vips_foreign_load_nsgif_bitmap_destroy( void *bitmap )
{
g_assert( bitmap );
free( bitmap );
}
static void
vips_foreign_load_nsgif_bitmap_modified( void *bitmap )
{
(void) bitmap; /* unused */
g_assert( bitmap );
return;
}
static gif_bitmap_callback_vt vips_foreign_load_nsgif_bitmap_callbacks = {
vips_foreign_load_nsgif_bitmap_create,
vips_foreign_load_nsgif_bitmap_destroy,
vips_foreign_load_nsgif_bitmap_get_buffer,
vips_foreign_load_nsgif_bitmap_set_opaque,
vips_foreign_load_nsgif_bitmap_test_opaque,
vips_foreign_load_nsgif_bitmap_modified
};
static void
vips_foreign_load_nsgif_init( VipsForeignLoadNsgif *gif )
{
gif->anim = g_new0( gif_animation, 1 );
gif_create( gif->anim, &vips_foreign_load_nsgif_bitmap_callbacks );
gif->n = 1;
}
typedef struct _VipsForeignLoadNsgifFile {
VipsForeignLoadNsgif parent_object;
/* Filename for load.
*/
char *filename;
} VipsForeignLoadNsgifFile;
typedef VipsForeignLoadNsgifClass VipsForeignLoadNsgifFileClass;
G_DEFINE_TYPE( VipsForeignLoadNsgifFile, vips_foreign_load_nsgif_file,
vips_foreign_load_nsgif_get_type() );
static int
vips_foreign_load_gif_file_build( VipsObject *object )
{
VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) object;
VipsForeignLoadNsgifFile *file = (VipsForeignLoadNsgifFile *) object;
if( file->filename )
if( !(gif->source =
vips_source_new_from_file( file->filename )) )
return( -1 );
if( VIPS_OBJECT_CLASS( vips_foreign_load_nsgif_file_parent_class )->
build( object ) )
return( -1 );
return( 0 );
}
static const char *vips_foreign_nsgif_suffs[] = {
".gif",
NULL
};
static gboolean
vips_foreign_load_nsgif_file_is_a( const char *filename )
{
VipsSource *source;
gboolean result;
if( !(source = vips_source_new_from_file( filename )) )
return( FALSE );
result = vips_foreign_load_nsgif_is_a_source( source );
VIPS_UNREF( source );
return( result );
}
static void
vips_foreign_load_nsgif_file_class_init(
VipsForeignLoadNsgifFileClass *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 = "gifload";
object_class->description = _( "load GIF with libnsgif" );
object_class->build = vips_foreign_load_gif_file_build;
foreign_class->suffs = vips_foreign_nsgif_suffs;
load_class->is_a = vips_foreign_load_nsgif_file_is_a;
VIPS_ARG_STRING( class, "filename", 1,
_( "Filename" ),
_( "Filename to load from" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsForeignLoadNsgifFile, filename ),
NULL );
}
static void
vips_foreign_load_nsgif_file_init( VipsForeignLoadNsgifFile *file )
{
}
typedef struct _VipsForeignLoadNsgifBuffer {
VipsForeignLoadNsgif parent_object;
/* Load from a buffer.
*/
VipsArea *blob;
} VipsForeignLoadNsgifBuffer;
typedef VipsForeignLoadNsgifClass VipsForeignLoadNsgifBufferClass;
G_DEFINE_TYPE( VipsForeignLoadNsgifBuffer, vips_foreign_load_nsgif_buffer,
vips_foreign_load_nsgif_get_type() );
static int
vips_foreign_load_nsgif_buffer_build( VipsObject *object )
{
VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) object;
VipsForeignLoadNsgifBuffer *buffer =
(VipsForeignLoadNsgifBuffer *) object;
if( buffer->blob &&
!(gif->source = vips_source_new_from_memory(
buffer->blob->data,
buffer->blob->length )) )
return( -1 );
if( VIPS_OBJECT_CLASS( vips_foreign_load_nsgif_buffer_parent_class )->
build( object ) )
return( -1 );
return( 0 );
}
static gboolean
vips_foreign_load_nsgif_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_nsgif_is_a_source( source );
VIPS_UNREF( source );
return( result );
}
static void
vips_foreign_load_nsgif_buffer_class_init(
VipsForeignLoadNsgifBufferClass *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 = "gifload_buffer";
object_class->description = _( "load GIF with libnsgif" );
object_class->build = vips_foreign_load_nsgif_buffer_build;
load_class->is_a_buffer = vips_foreign_load_nsgif_buffer_is_a_buffer;
VIPS_ARG_BOXED( class, "buffer", 1,
_( "Buffer" ),
_( "Buffer to load from" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsForeignLoadNsgifBuffer, blob ),
VIPS_TYPE_BLOB );
}
static void
vips_foreign_load_nsgif_buffer_init( VipsForeignLoadNsgifBuffer *buffer )
{
}
typedef struct _VipsForeignLoadNsgifSource {
VipsForeignLoadNsgif parent_object;
/* Load from a source.
*/
VipsSource *source;
} VipsForeignLoadNsgifSource;
typedef VipsForeignLoadClass VipsForeignLoadNsgifSourceClass;
G_DEFINE_TYPE( VipsForeignLoadNsgifSource, vips_foreign_load_nsgif_source,
vips_foreign_load_nsgif_get_type() );
static int
vips_foreign_load_nsgif_source_build( VipsObject *object )
{
VipsForeignLoadNsgif *gif = (VipsForeignLoadNsgif *) object;
VipsForeignLoadNsgifSource *source =
(VipsForeignLoadNsgifSource *) object;
if( source->source ) {
gif->source = source->source;
g_object_ref( gif->source );
}
if( VIPS_OBJECT_CLASS( vips_foreign_load_nsgif_source_parent_class )->
build( object ) )
return( -1 );
return( 0 );
}
static void
vips_foreign_load_nsgif_source_class_init(
VipsForeignLoadNsgifSourceClass *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 = "gifload_source";
object_class->description = _( "load gif from source" );
object_class->build = vips_foreign_load_nsgif_source_build;
load_class->is_a_source = vips_foreign_load_nsgif_is_a_source;
VIPS_ARG_OBJECT( class, "source", 1,
_( "Source" ),
_( "Source to load from" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsForeignLoadNsgifSource, source ),
VIPS_TYPE_SOURCE );
}
static void
vips_foreign_load_nsgif_source_init( VipsForeignLoadNsgifSource *source )
{
}
#endif /*HAVE_NSGIF*/
/**
* vips_gifload:
* @filename: file to load
* @out: (out): output image
* @...: %NULL-terminated list of optional named arguments
*
* Optional arguments:
*
* * @page: %gint, page (frame) to read
* * @n: %gint, load this many pages
*
* Read a GIF file into a libvips image.
*
* Use @page to select a page to render, numbering from zero.
*
* Use @n to select the number of pages to render. The default is 1. Pages are
* rendered in a vertical column. Set to -1 to mean "until the end of the
* document". Use vips_grid() to change page layout.
*
* The output image is always RGBA.
*
* See also: vips_image_new_from_file().
*
* Returns: 0 on success, -1 on error.
*/
int
vips_gifload( const char *filename, VipsImage **out, ... )
{
va_list ap;
int result;
va_start( ap, out );
result = vips_call_split( "gifload", ap, filename, out );
va_end( ap );
return( result );
}
/**
* vips_gifload_buffer:
* @buf: (array length=len) (element-type guint8): memory area to load
* @len: (type gsize): size of memory area
* @out: (out): image to write
* @...: %NULL-terminated list of optional named arguments
*
* Optional arguments:
*
* * @page: %gint, page (frame) to read
* * @n: %gint, load this many pages
*
* Read a GIF-formatted memory block into a VIPS image. Exactly as
* vips_gifload(), but read from a memory buffer.
*
* You must not free the buffer while @out is active. The
* #VipsObject::postclose signal on @out is a good place to free.
*
* See also: vips_gifload().
*
* Returns: 0 on success, -1 on error.
*/
int
vips_gifload_buffer( void *buf, size_t len, VipsImage **out, ... )
{
va_list ap;
VipsBlob *blob;
int result;
/* We don't take a copy of the data or free it.
*/
blob = vips_blob_new( NULL, buf, len );
va_start( ap, out );
result = vips_call_split( "gifload_buffer", ap, blob, out );
va_end( ap );
vips_area_unref( VIPS_AREA( blob ) );
return( result );
}
/**
* vips_gifload_source:
* @source: source to load
* @out: (out): image to write
* @...: %NULL-terminated list of optional named arguments
*
* Optional arguments:
*
* * @page: %gint, page (frame) to read
* * @n: %gint, load this many pages
*
* Exactly as vips_gifload(), but read from a source.
*
* See also: vips_gifload().
*
* Returns: 0 on success, -1 on error.
*/
int
vips_gifload_source( VipsSource *source, VipsImage **out, ... )
{
va_list ap;
int result;
va_start( ap, out );
result = vips_call_split( "gifload_source", ap, source, out );
va_end( ap );
return( result );
}

View File

@ -226,7 +226,11 @@ read_header( Read *read, VipsImage *out )
VIPS_FORMAT_FLOAT,
VIPS_CODING_NONE, VIPS_INTERPRETATION_scRGB, 1.0, 1.0 );
if( read->tiles )
vips_image_pipelinev( out, VIPS_DEMAND_STYLE_SMALLTILE, NULL );
/* Even though this is a tiled reader, we hint thinstrip
* since with the cache we are quite happy serving that if
* anything downstream would like it.
*/
vips_image_pipelinev( out, VIPS_DEMAND_STYLE_THINSTRIP, NULL );
else
vips_image_pipelinev( out, VIPS_DEMAND_STYLE_FATSTRIP, NULL );
}
@ -363,7 +367,8 @@ vips__openexr_read( const char *filename, VipsImage *out )
VipsImage *raw;
VipsImage *t;
/* Tile cache: keep enough for two complete rows of tiles.
/* Tile cache: keep enough for two complete rows of tiles,
* plus 50%.
*/
raw = vips_image_new();
vips_object_local( out, raw );
@ -375,14 +380,11 @@ vips__openexr_read( const char *filename, VipsImage *out )
read, NULL ) )
return( -1 );
/* Copy to out, adding a cache. Enough tiles for a complete
* row, plus 50%.
*/
if( vips_tilecache( raw, &t,
"tile_width", read->tile_width,
"tile_height", read->tile_height,
"max_tiles", (int)
(1.5 * (1 + raw->Xsize / read->tile_width)),
(2.5 * (1 + raw->Xsize / read->tile_width)),
NULL ) )
return( -1 );
if( vips_image_write( t, out ) ) {

View File

@ -92,7 +92,7 @@ G_STMT_START { \
(void) g_once( ONCE, FUNC, CLIENT ); \
} G_STMT_END
/* VIPS_RINT() does "bankers rounding", it rounds to the nerarest even integer.
/* VIPS_RINT() does "bankers rounding", it rounds to the nearest even integer.
* For things like image geometry, we want strict nearest int.
*
* If you know it's unsigned, _UINT is a little faster.

View File

@ -565,8 +565,6 @@ vips_source_new_from_options( const char *options )
*
* Loaders should call this in response to the minimise signal on their output
* image.
*
* Returns: 0 on success, or -1 on error.
*/
void
vips_source_minimise( VipsSource *source )

View File

@ -900,6 +900,21 @@ transform_array_int_g_string( const GValue *src_value, GValue *dest_value )
g_value_set_string( dest_value, vips_buf_all( &buf ) );
}
static void
transform_array_int_save_string( const GValue *src_value, GValue *dest_value )
{
GValue intermediate = { 0 };
g_value_init( &intermediate, G_TYPE_STRING );
transform_array_int_g_string( src_value, &intermediate );
vips_value_set_save_string( dest_value,
g_value_get_string( &intermediate ) );
g_value_unset( &intermediate );
}
/* It'd be great to be able to write a generic string->array function, but
* it doesn't seem possible.
*/
@ -951,6 +966,21 @@ transform_g_string_array_int( const GValue *src_value, GValue *dest_value )
g_free( str );
}
static void
transform_save_string_array_int( const GValue *src_value, GValue *dest_value )
{
GValue intermediate = { 0 };
g_value_init( &intermediate, G_TYPE_STRING );
g_value_set_string( &intermediate,
vips_value_get_save_string( src_value ) );
transform_g_string_array_int( &intermediate, dest_value );
g_value_unset( &intermediate );
}
/* We need a arrayint, we have an int, make a one-element array.
*/
static void
@ -1006,6 +1036,10 @@ vips_array_int_get_type( void )
transform_double_array_int );
g_value_register_transform_func( VIPS_TYPE_ARRAY_DOUBLE, type,
transform_array_double_array_int );
g_value_register_transform_func( type, VIPS_TYPE_SAVE_STRING,
transform_array_int_save_string );
g_value_register_transform_func( VIPS_TYPE_SAVE_STRING, type,
transform_save_string_array_int );
}
return( type );

View File

@ -54,10 +54,6 @@
#include "templates.h"
#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif /*WITH_DMALLOC*/
#define VIPS_TYPE_INTERPOLATE_BICUBIC \
(vips_interpolate_bicubic_get_type())
#define VIPS_INTERPOLATE_BICUBIC( obj ) \

View File

@ -700,6 +700,10 @@ vips_thumbnail_build( VipsObject *object )
in = t[12];
}
/* Note the interpretation we will revert to after linear.
*/
input_interpretation = in->Type;
/* In linear mode, we need to transform to a linear space before
* vips_resize().
*/
@ -734,11 +738,6 @@ vips_thumbnail_build( VipsObject *object )
*/
VipsInterpretation interpretation;
/* Note the interpretation we will revert to after
* linear.
*/
input_interpretation = in->Type;
if( in->Bands < 3 )
interpretation = VIPS_INTERPRETATION_GREY16;
else

View File

@ -237,7 +237,6 @@ libvips/foreign/fits.c
libvips/foreign/fitsload.c
libvips/foreign/fitssave.c
libvips/foreign/foreign.c
libvips/foreign/gifload.c
libvips/foreign/heifload.c
libvips/foreign/heifsave.c
libvips/foreign/jpeg2vips.c
@ -254,6 +253,7 @@ libvips/foreign/matrixload.c
libvips/foreign/matrixsave.c
libvips/foreign/niftiload.c
libvips/foreign/niftisave.c
libvips/foreign/nsgifload.c
libvips/foreign/openexr2vips.c
libvips/foreign/openexrload.c
libvips/foreign/openslide2vips.c
@ -285,6 +285,8 @@ libvips/foreign/vipssave.c
libvips/foreign/webp2vips.c
libvips/foreign/webpload.c
libvips/foreign/webpsave.c
libvips/foreign/libnsgif/libnsgif.c
libvips/foreign/libnsgif/lzw.c
libvips/freqfilt/freqfilt.c
libvips/freqfilt/freqmult.c
libvips/freqfilt/fwfft.c
@ -333,6 +335,7 @@ libvips/iofuncs/sinkmemory.c
libvips/iofuncs/sinkscreen.c
libvips/iofuncs/source.c
libvips/iofuncs/sourcecustom.c
libvips/iofuncs/sourceginput.c
libvips/iofuncs/system.c
libvips/iofuncs/target.c
libvips/iofuncs/targetcustom.c

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,5 +1,4 @@
# vim: set fileencoding=utf-8 :
import filecmp
import sys
import os
import shutil
@ -38,7 +37,7 @@ class TestForeign:
cls.cmyk.remove("icc-profile-data")
im = pyvips.Image.new_from_file(GIF_FILE)
cls.onebit = im > 128
cls.onebit = im[1] > 128
@classmethod
def teardown_class(cls):
@ -804,10 +803,10 @@ class TestForeign:
def test_gifload(self):
def gif_valid(im):
a = im(10, 10)
assert_almost_equal_objects(a, [33])
assert_almost_equal_objects(a, [33, 33, 33, 255])
assert im.width == 159
assert im.height == 203
assert im.bands == 1
assert im.bands == 4
self.file_loader("gifload", GIF_FILE, gif_valid)
self.buffer_loader("gifload_buffer", GIF_FILE, gif_valid)
@ -828,37 +827,21 @@ class TestForeign:
x2 = pyvips.Image.new_from_file(GIF_ANIM_FILE, page=1, n=-1)
assert x2.height == 4 * x1.height
animation = pyvips.Image.new_from_file(GIF_ANIM_FILE, n=-1)
filename = temp_filename(self.tempdir, '.png')
animation.write_to_file(filename)
# Uncomment to see output file
# animation.write_to_file('cogs.png')
assert filecmp.cmp(GIF_ANIM_EXPECTED_PNG_FILE, filename, shallow=False)
x1 = pyvips.Image.new_from_file(GIF_ANIM_FILE, n=-1)
x2 = pyvips.Image.new_from_file(GIF_ANIM_EXPECTED_PNG_FILE)
assert (x1 - x2).abs().max() == 0
@skip_if_no("gifload")
def test_gifload_animation_dispose_background(self):
animation = pyvips.Image.new_from_file(GIF_ANIM_DISPOSE_BACKGROUND_FILE, n=-1)
filename = temp_filename(self.tempdir, '.png')
animation.write_to_file(filename)
# Uncomment to see output file
# animation.write_to_file('dispose-background.png')
assert filecmp.cmp(GIF_ANIM_DISPOSE_BACKGROUND_EXPECTED_PNG_FILE, filename, shallow=False)
x1 = pyvips.Image.new_from_file(GIF_ANIM_DISPOSE_BACKGROUND_FILE, n=-1)
x2 = pyvips.Image.new_from_file(GIF_ANIM_DISPOSE_BACKGROUND_EXPECTED_PNG_FILE)
assert (x1 - x2).abs().max() == 0
@skip_if_no("gifload")
def test_gifload_animation_dispose_previous(self):
animation = pyvips.Image.new_from_file(GIF_ANIM_DISPOSE_PREVIOUS_FILE, n=-1)
filename = temp_filename(self.tempdir, '.png')
animation.write_to_file(filename)
# Uncomment to see output file
# animation.write_to_file('dispose-previous.png')
assert filecmp.cmp(GIF_ANIM_DISPOSE_PREVIOUS_EXPECTED_PNG_FILE, filename, shallow=False)
x1 = pyvips.Image.new_from_file(GIF_ANIM_DISPOSE_PREVIOUS_FILE, n=-1)
x2 = pyvips.Image.new_from_file(GIF_ANIM_DISPOSE_PREVIOUS_EXPECTED_PNG_FILE)
assert (x1 - x2).abs().max() == 0
@skip_if_no("svgload")
def test_svgload(self):