Merge branch 'master' into add-black-point-compensation

This commit is contained in:
John Cupitt 2021-04-19 18:53:35 +01:00 committed by GitHub
commit 693bc3ccbf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 723 additions and 327 deletions

View File

@ -60,7 +60,8 @@ jobs:
- name: Install macOS dependencies - name: Install macOS dependencies
if: contains(matrix.os, 'macos') if: contains(matrix.os, 'macos')
run: run: |
brew update
brew install brew install
autoconf automake libtool autoconf automake libtool
gtk-doc gobject-introspection gtk-doc gobject-introspection

View File

@ -23,6 +23,7 @@
- add GIF load with libnsgif - add GIF load with libnsgif
- add JPEG2000 load and save - add JPEG2000 load and save
- add black_point_compensation flag for icc transforms - add black_point_compensation flag for icc transforms
- add "rgba" flag to vips_text() to enable full colour text rendering
22/12/20 start 8.10.6 22/12/20 start 8.10.6
- don't seek on bad file descriptors [kleisauke] - don't seek on bad file descriptors [kleisauke]

View File

@ -22,7 +22,7 @@ operations, frequency filtering, colour, resampling,
statistics and others. It supports a large range of [numeric statistics and others. It supports a large range of [numeric
types](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat), types](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat),
from 8-bit int to 128-bit complex. Images can have any number of bands. from 8-bit int to 128-bit complex. Images can have any number of bands.
It supports a good range of image formats, including JPEG, TIFF, PNG, It supports a good range of image formats, including JPEG, JPEG2000, TIFF, PNG,
WebP, HEIC, AVIF, FITS, Matlab, OpenEXR, PDF, SVG, HDR, PPM / PGM / PFM, WebP, HEIC, AVIF, FITS, Matlab, OpenEXR, PDF, SVG, HDR, PPM / PGM / PFM,
CSV, GIF, Analyze, NIfTI, DeepZoom, and OpenSlide. It can also load images CSV, GIF, Analyze, NIfTI, DeepZoom, and OpenSlide. It can also load images
via ImageMagick or GraphicsMagick, letting it work with formats like DICOM. via ImageMagick or GraphicsMagick, letting it work with formats like DICOM.
@ -243,10 +243,10 @@ If you are going to be using libvips with untrusted images, perhaps in a
web server, for example, you should consider the security implications of web server, for example, you should consider the security implications of
enabling a package with such a large attack surface. enabling a package with such a large attack surface.
### pangoft2 ### pangocairo
If available, libvips adds support for text rendering. You need the If available, libvips adds support for text rendering. You need the
package pangoft2 in `pkg-config --list-all`. package pangocairo in `pkg-config --list-all`.
### orc-0.4 ### orc-0.4
@ -274,6 +274,10 @@ If available, vips can load and save NIfTI images.
If available, libvips will directly read (but not write, sadly) If available, libvips will directly read (but not write, sadly)
OpenEXR images. OpenEXR images.
### OpenJPEG
If available, libvips will read and write JPEG2000 images.
### OpenSlide ### OpenSlide
If available, libvips can load OpenSlide-supported virtual slide If available, libvips can load OpenSlide-supported virtual slide

View File

@ -1083,25 +1083,25 @@ fi
VIPS_CFLAGS="$VIPS_CFLAGS $LIBWEBP_CFLAGS" VIPS_CFLAGS="$VIPS_CFLAGS $LIBWEBP_CFLAGS"
VIPS_LIBS="$VIPS_LIBS $LIBWEBP_LIBS" VIPS_LIBS="$VIPS_LIBS $LIBWEBP_LIBS"
# pangoft2 # pangocairo for text rendering
AC_ARG_WITH([pangoft2], AC_ARG_WITH([pangocairo],
AS_HELP_STRING([--without-pangoft2], AS_HELP_STRING([--without-pangocairo],
[build without pangoft2 (default: test)])) [build without pangocairo (default: test)]))
if test x"$with_pangoft2" != x"no"; then if test x"$with_pangocairo" != x"no"; then
PKG_CHECK_MODULES(PANGOFT2, pangoft2, PKG_CHECK_MODULES(PANGOCAIRO, pangocairo,
[AC_DEFINE(HAVE_PANGOFT2,1,[define if you have pangoft2 installed.]) [AC_DEFINE(HAVE_PANGOCAIRO,1,[define if you have pangocairo installed.])
with_pangoft2=yes with_pangocairo=yes
PACKAGES_USED="$PACKAGES_USED pangoft2" PACKAGES_USED="$PACKAGES_USED pangocairo"
], ],
[AC_MSG_WARN([pangoft2 not found; disabling pangoft2 support]) [AC_MSG_WARN([pangocairo not found; disabling pangocairo support])
with_pangoft2=no with_pangocairo=no
] ]
) )
fi fi
VIPS_CFLAGS="$VIPS_CFLAGS $PANGOFT2_CFLAGS" VIPS_CFLAGS="$VIPS_CFLAGS $PANGOCAIRO_CFLAGS"
VIPS_LIBS="$VIPS_LIBS $PANGOFT2_LIBS" VIPS_LIBS="$VIPS_LIBS $PANGOCAIRO_LIBS"
# look for TIFF with pkg-config ... fall back to our tester # look for TIFF with pkg-config ... fall back to our tester
# pkgconfig support for libtiff starts with libtiff-4 # pkgconfig support for libtiff starts with libtiff-4
@ -1325,7 +1325,7 @@ use fftw3 for FFT: $with_fftw, \
accelerate loops with orc: $with_orc, \ accelerate loops with orc: $with_orc, \
ICC profile support with lcms: $with_lcms, \ ICC profile support with lcms: $with_lcms, \
zlib: $with_zlib, \ zlib: $with_zlib, \
text rendering with pangoft2: $with_pangoft2, \ text rendering with pangocairo: $with_pangocairo, \
EXIF metadata support with libexif: $with_libexif, \ EXIF metadata support with libexif: $with_libexif, \
JPEG load/save with libjpeg: $with_jpeg, \ JPEG load/save with libjpeg: $with_jpeg, \
PNG load with libspng: $with_libspng, \ PNG load with libspng: $with_libspng, \
@ -1427,7 +1427,7 @@ accelerate loops with orc: $with_orc
(requires orc-0.4.11 or later) (requires orc-0.4.11 or later)
ICC profile support with lcms: $with_lcms ICC profile support with lcms: $with_lcms
zlib: $with_zlib zlib: $with_zlib
text rendering with pangoft2: $with_pangoft2 text rendering with pangocairo: $with_pangocairo
EXIF metadata support with libexif: $with_libexif EXIF metadata support with libexif: $with_libexif
## File format support ## File format support

View File

@ -32143,6 +32143,64 @@ static VipsProfileFallback vips__profile_fallback_cmyk = {
} }
}; };
static VipsProfileFallback vips__profile_fallback_p3 = {
"p3",
736,
{
0x78, 0xDA, 0x7D, 0xD0, 0x03, 0xA0, 0xE4, 0x30, 0x14, 0x05, 0xD0, 0x57,
0x7C, 0xDB, 0xB6, 0x6D, 0xDB, 0xB6, 0x8D, 0xB1, 0xD1, 0xCE, 0xDA, 0xB6,
0x6D, 0xDB, 0xB6, 0x6D, 0xDB, 0xB6, 0xED, 0x74, 0xED, 0x5B, 0x9D, 0xC6,
0x09, 0x00, 0x7E, 0x49, 0xCC, 0x92, 0x50, 0xB8, 0x21, 0x80, 0x44, 0x4A,
0x2B, 0x8B, 0x33, 0x92, 0x1D, 0x2B, 0xAB, 0xAA, 0x1D, 0x35, 0xAE, 0x00,
0x01, 0xA6, 0xA0, 0x05, 0xFA, 0x60, 0xD7, 0xC4, 0xA2, 0xE4, 0x79, 0x25,
0xE9, 0xA5, 0x80, 0x42, 0x35, 0xB5, 0xA4, 0x58, 0xB4, 0x52, 0x0C, 0xBF,
0xE4, 0xC5, 0x09, 0xC0, 0x98, 0xEF, 0x51, 0x3F, 0x7E, 0x93, 0x94, 0xDD,
0x56, 0x66, 0xB0, 0xF7, 0x52, 0x5A, 0x52, 0x78, 0x42, 0x41, 0x6A, 0xD8,
0x5E, 0xEB, 0x1E, 0x56, 0xF0, 0xFF, 0x68, 0xB1, 0x39, 0x14, 0x0B, 0x7D,
0x1F, 0xA1, 0xA7, 0x9E, 0x25, 0x57, 0xD2, 0x00, 0x98, 0x2E, 0xB2, 0x6E,
0x4B, 0x5A, 0xCE, 0xD8, 0x12, 0xD9, 0x54, 0x89, 0x16, 0x85, 0xEC, 0xCB,
0x98, 0xF7, 0xC5, 0x89, 0x8C, 0x9B, 0xBF, 0xB8, 0x94, 0xB1, 0xB2, 0xB4,
0x38, 0x05, 0x99, 0x8F, 0x9E, 0x56, 0xBC, 0x9F, 0xDC, 0xFC, 0x93, 0xBF,
0xCE, 0xC5, 0x44, 0x8D, 0x2A, 0x0C, 0x41, 0xE5, 0x3F, 0x42, 0x73, 0x5A,
0xD1, 0xCC, 0x37, 0x25, 0x25, 0x10, 0x98, 0x33, 0xF8, 0x52, 0xFA, 0xB4,
0x08, 0x30, 0xF4, 0xC1, 0xCC, 0xF7, 0xFD, 0x28, 0xEB, 0x7A, 0x11, 0x20,
0x6E, 0x23, 0xA3, 0x1F, 0x65, 0xD9, 0xE8, 0x7F, 0x71, 0x04, 0x80, 0xF6,
0xF2, 0x1F, 0x65, 0x9E, 0x91, 0x00, 0x46, 0xDA, 0x00, 0xBB, 0x5A, 0xB1,
0x54, 0xCA, 0x16, 0xF0, 0x25, 0x2B, 0xD0, 0x63, 0x0B, 0x91, 0x50, 0x0A,
0x72, 0xE8, 0x07, 0xB3, 0x60, 0x1B, 0x5C, 0xC6, 0x00, 0x73, 0xC6, 0xD2,
0x31, 0x09, 0x36, 0x1C, 0xDB, 0x8C, 0x3D, 0xC2, 0x1D, 0xF1, 0x32, 0xBC,
0x2F, 0xBE, 0x8D, 0xC0, 0x88, 0x04, 0xA2, 0x0B, 0xB1, 0x83, 0xD4, 0x27,
0xAB, 0xC8, 0x99, 0xE4, 0x6B, 0xB5, 0x5C, 0xB5, 0x69, 0x6A, 0x1F, 0xD4,
0x6B, 0xD5, 0x37, 0x68, 0x38, 0x69, 0xF4, 0xD1, 0x78, 0xA6, 0xC9, 0xD2,
0x3C, 0xAE, 0x95, 0xA5, 0xB5, 0x55, 0x3B, 0x41, 0x7B, 0xA3, 0x4E, 0xB2,
0xCE, 0x6E, 0xDD, 0x52, 0xDD, 0xCB, 0x7A, 0xB4, 0xBE, 0x96, 0xFE, 0x14,
0x83, 0x44, 0x83, 0x4B, 0x86, 0xDD, 0x8D, 0xFC, 0x8C, 0xCE, 0x18, 0xF7,
0x31, 0x89, 0x37, 0x79, 0x61, 0xBA, 0xC4, 0x4C, 0x61, 0x1E, 0x6E, 0xFE,
0xDE, 0x62, 0x8F, 0xE5, 0x68, 0x2B, 0xA9, 0x75, 0xBA, 0x8D, 0xB3, 0x2D,
0x61, 0x7B, 0xD3, 0xEE, 0x90, 0xFD, 0x7A, 0x87, 0x05, 0x8E, 0xD3, 0x9C,
0x26, 0x38, 0x8F, 0x73, 0x99, 0xE8, 0x3A, 0xDD, 0x6D, 0xB1, 0xFB, 0x26,
0x8F, 0xE3, 0x9E, 0xF7, 0xBD, 0xB5, 0x7D, 0x7C, 0x7C, 0x8B, 0xFC, 0xDA,
0xF9, 0xCF, 0x09, 0xB8, 0x10, 0x64, 0x1E, 0x5C, 0x1C, 0x32, 0x2C, 0xF4,
0x74, 0xB8, 0x6B, 0x04, 0x15, 0xB9, 0x23, 0xDA, 0x31, 0xA6, 0x5D, 0xEC,
0xC5, 0xF8, 0xB4, 0x84, 0x45, 0x49, 0x4E, 0xC9, 0x23, 0x52, 0x0D, 0xD3,
0x06, 0x66, 0x18, 0x67, 0x8E, 0xCD, 0xF6, 0xCC, 0x59, 0x9B, 0x57, 0x94,
0xFF, 0xA0, 0x70, 0x60, 0x71, 0x58, 0xC9, 0xA5, 0xB2, 0x41, 0x15, 0x69,
0x55, 0x50, 0xBD, 0xA1, 0xB6, 0x5B, 0x7D, 0x6E, 0xA3, 0x4D, 0xD3, 0x43,
0xD6, 0x4E, 0xCE, 0x0C, 0x5E, 0x6F, 0x81, 0x42, 0xD4, 0x20, 0x29, 0x92,
0x65, 0x2B, 0xD2, 0xA9, 0x6C, 0x55, 0x51, 0xCB, 0xFA, 0xD6, 0x8A, 0xB6,
0xBD, 0xDA, 0xCF, 0xE8, 0xB8, 0xAB, 0xF3, 0xA3, 0x6E, 0xF6, 0x3D, 0x0A,
0x7B, 0xF5, 0xEE, 0xB3, 0xBD, 0xBF, 0xD6, 0xC0, 0xA2, 0xC1, 0x13, 0x87,
0x3E, 0x1D, 0x91, 0x33, 0x6A, 0xEE, 0x58, 0xFD, 0xF1, 0xAD, 0x26, 0xDE,
0x9C, 0x52, 0x33, 0xED, 0xC4, 0xCC, 0xF2, 0xD9, 0x67, 0xE7, 0xF1, 0x16,
0xBC, 0x5C, 0x3C, 0x70, 0x99, 0xF7, 0x8A, 0x7D, 0xAB, 0xE9, 0x75, 0x4E,
0x1B, 0x8E, 0x6F, 0xEE, 0xBF, 0x2D, 0x77, 0xA7, 0xE1, 0xEE, 0x33, 0xFB,
0xE6, 0x1E, 0xEC, 0x78, 0xA4, 0xF6, 0x78, 0xDC, 0x29, 0xD7, 0xB3, 0xC6,
0x17, 0xC8, 0x4B, 0x1F, 0xAE, 0xBE, 0xBB, 0x89, 0xDD, 0xD1, 0xBD, 0x6F,
0xFB, 0x28, 0xF4, 0x69, 0xE1, 0x0B, 0xEA, 0xF5, 0xD8, 0x77, 0x7B, 0x3E,
0x7E, 0xFC, 0x04, 0xD4, 0xCA, 0xD2, 0x46,
}
};
static VipsProfileFallback vips__profile_fallback_sRGB = { static VipsProfileFallback vips__profile_fallback_sRGB = {
"sRGB", "sRGB",
6922, 6922,
@ -32348,6 +32406,7 @@ static VipsProfileFallback vips__profile_fallback_sRGB = {
VipsProfileFallback *vips__profile_fallback_table[] = { VipsProfileFallback *vips__profile_fallback_table[] = {
&vips__profile_fallback_cmyk, &vips__profile_fallback_cmyk,
&vips__profile_fallback_p3,
&vips__profile_fallback_sRGB, &vips__profile_fallback_sRGB,
NULL NULL
}; };

View File

@ -1,3 +1,4 @@
EXTRA_DIST = \ EXTRA_DIST = \
p3.icm \
cmyk.icm \ cmyk.icm \
sRGB.icm sRGB.icm

Binary file not shown.

View File

@ -116,9 +116,9 @@ vips_create_operation_init( void )
extern GType vips_gaussmat_get_type( void ); extern GType vips_gaussmat_get_type( void );
extern GType vips_logmat_get_type( void ); extern GType vips_logmat_get_type( void );
extern GType vips_gaussnoise_get_type( void ); extern GType vips_gaussnoise_get_type( void );
#ifdef HAVE_PANGOFT2 #ifdef HAVE_PANGOCAIRO
extern GType vips_text_get_type( void ); extern GType vips_text_get_type( void );
#endif /*HAVE_PANGOFT2*/ #endif /*HAVE_PANGOCAIRO*/
extern GType vips_xyz_get_type( void ); extern GType vips_xyz_get_type( void );
extern GType vips_eye_get_type( void ); extern GType vips_eye_get_type( void );
extern GType vips_grey_get_type( void ); extern GType vips_grey_get_type( void );
@ -146,9 +146,9 @@ vips_create_operation_init( void )
vips_gaussmat_get_type(); vips_gaussmat_get_type();
vips_logmat_get_type(); vips_logmat_get_type();
vips_gaussnoise_get_type(); vips_gaussnoise_get_type();
#ifdef HAVE_PANGOFT2 #ifdef HAVE_PANGOCAIRO
vips_text_get_type(); vips_text_get_type();
#endif /*HAVE_PANGOFT2*/ #endif /*HAVE_PANGOCAIRO*/
vips_xyz_get_type(); vips_xyz_get_type();
vips_eye_get_type(); vips_eye_get_type();
vips_grey_get_type(); vips_grey_get_type();

View File

@ -32,6 +32,9 @@
* - fitting could occasionally terminate early [levmorozov] * - fitting could occasionally terminate early [levmorozov]
* 16/5/20 [keiviv] * 16/5/20 [keiviv]
* - don't add fontfiles repeatedly * - don't add fontfiles repeatedly
* 12/4/21
* - switch to cairo for text rendering
* - add rgba flag
*/ */
/* /*
@ -74,11 +77,14 @@
#include <string.h> #include <string.h>
#include <vips/vips.h> #include <vips/vips.h>
#include <vips/internal.h>
#ifdef HAVE_PANGOFT2 #ifdef HAVE_PANGOCAIRO
#include <cairo.h>
#include <pango/pango.h> #include <pango/pango.h>
#include <pango/pangoft2.h> #include <pango/pangocairo.h>
#include <fontconfig/fontconfig.h>
#include "pcreate.h" #include "pcreate.h"
@ -94,8 +100,8 @@ typedef struct _VipsText {
gboolean justify; gboolean justify;
int dpi; int dpi;
char *fontfile; char *fontfile;
gboolean rgba;
FT_Bitmap bitmap;
PangoContext *context; PangoContext *context;
PangoLayout *layout; PangoLayout *layout;
@ -129,7 +135,6 @@ vips_text_dispose( GObject *gobject )
VIPS_UNREF( text->layout ); VIPS_UNREF( text->layout );
VIPS_UNREF( text->context ); VIPS_UNREF( text->context );
VIPS_FREE( text->bitmap.buffer );
G_OBJECT_CLASS( vips_text_parent_class )->dispose( gobject ); G_OBJECT_CLASS( vips_text_parent_class )->dispose( gobject );
} }
@ -186,8 +191,8 @@ vips_text_get_extents( VipsText *text, VipsRect *extents )
PangoRectangle ink_rect; PangoRectangle ink_rect;
PangoRectangle logical_rect; PangoRectangle logical_rect;
pango_ft2_font_map_set_resolution( pango_cairo_font_map_set_resolution(
PANGO_FT2_FONT_MAP( vips_text_fontmap ), text->dpi, text->dpi ); PANGO_CAIRO_FONT_MAP( vips_text_fontmap ), text->dpi );
VIPS_UNREF( text->layout ); VIPS_UNREF( text->layout );
if( !(text->layout = text_layout_new( text->context, if( !(text->layout = text_layout_new( text->context,
@ -340,9 +345,12 @@ vips_text_build( VipsObject *object )
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
VipsCreate *create = VIPS_CREATE( object ); VipsCreate *create = VIPS_CREATE( object );
VipsText *text = (VipsText *) object; VipsText *text = (VipsText *) object;
VipsImage **t = (VipsImage **) vips_object_local_array( object, 3 );
VipsRect extents; VipsRect extents;
int y; VipsImage *image;
cairo_surface_t *surface;
cairo_t *cr;
if( VIPS_OBJECT_CLASS( vips_text_parent_class )->build( object ) ) if( VIPS_OBJECT_CLASS( vips_text_parent_class )->build( object ) )
return( -1 ); return( -1 );
@ -356,7 +364,7 @@ vips_text_build( VipsObject *object )
g_mutex_lock( vips_text_lock ); g_mutex_lock( vips_text_lock );
if( !vips_text_fontmap ) if( !vips_text_fontmap )
vips_text_fontmap = pango_ft2_font_map_new(); vips_text_fontmap = pango_cairo_font_map_new();
if( !vips_text_fontfiles ) if( !vips_text_fontfiles )
vips_text_fontfiles = vips_text_fontfiles =
g_hash_table_new( g_str_hash, g_str_equal ); g_hash_table_new( g_str_hash, g_str_equal );
@ -399,40 +407,65 @@ vips_text_build( VipsObject *object )
return( -1 ); return( -1 );
} }
text->bitmap.width = extents.width; /* Set DPI as pixels/mm.
text->bitmap.pitch = (text->bitmap.width + 3) & ~3; */
text->bitmap.rows = extents.height; image = t[0] = vips_image_new_memory();
if( !(text->bitmap.buffer = vips_image_init_fields( image,
VIPS_ARRAY( NULL, extents.width, extents.height, 4,
text->bitmap.pitch * text->bitmap.rows, VipsPel )) ) { VIPS_FORMAT_UCHAR, VIPS_CODING_NONE,
VIPS_INTERPRETATION_sRGB,
text->dpi / 25.4, text->dpi / 25.4 );
vips_image_pipelinev( image, VIPS_DEMAND_STYLE_ANY, NULL );
image->Xoffset = extents.left;
image->Yoffset = extents.top;
if( vips_image_write_prepare( image ) ) {
g_mutex_unlock( vips_text_lock ); g_mutex_unlock( vips_text_lock );
return( -1 ); return( -1 );
} }
text->bitmap.num_grays = 256;
text->bitmap.pixel_mode = ft_pixel_mode_grays;
memset( text->bitmap.buffer, 0x00,
text->bitmap.pitch * text->bitmap.rows );
pango_ft2_render_layout( &text->bitmap, text->layout, surface = cairo_image_surface_create_for_data(
-extents.left, -extents.top ); VIPS_IMAGE_ADDR( image, 0, 0 ),
CAIRO_FORMAT_ARGB32,
image->Xsize, image->Ysize,
VIPS_IMAGE_SIZEOF_LINE( image ) );
cr = cairo_create( surface );
cairo_surface_destroy( surface );
cairo_translate( cr, -extents.left, -extents.top );
pango_cairo_show_layout( cr, text->layout );
cairo_destroy( cr );
g_mutex_unlock( vips_text_lock ); g_mutex_unlock( vips_text_lock );
vips_image_init_fields( create->out, if( text->rgba ) {
text->bitmap.width, text->bitmap.rows, 1, int y;
VIPS_FORMAT_UCHAR, VIPS_CODING_NONE,
VIPS_INTERPRETATION_MULTIBAND,
1.0, 1.0 );
vips_image_pipelinev( create->out,
VIPS_DEMAND_STYLE_ANY, NULL );
create->out->Xoffset = extents.left;
create->out->Yoffset = extents.top;
for( y = 0; y < text->bitmap.rows; y++ ) /* Cairo makes pre-multipled BRGA -- we must byteswap and
if( vips_image_write_line( create->out, y, * unpremultiply.
(VipsPel *) text->bitmap.buffer + */
y * text->bitmap.pitch ) ) for( y = 0; y < image->Ysize; y++ )
vips__premultiplied_bgra2rgba(
(guint32 *)
VIPS_IMAGE_ADDR( image, 0, y ),
image->Xsize );
}
else {
/* We just want the alpha channel.
*/
if( vips_extract_band( image, &t[1], 3, NULL ) ||
vips_copy( t[1], &t[2],
"interpretation", VIPS_INTERPRETATION_MULTIBAND,
NULL ) )
return( -1 ); return( -1 );
image = t[2];
}
if( vips_image_write( image, create->out ) )
return( -1 );
return( 0 ); return( 0 );
} }
@ -534,6 +567,13 @@ vips_text_class_init( VipsTextClass *class )
G_STRUCT_OFFSET( VipsText, fontfile ), G_STRUCT_OFFSET( VipsText, fontfile ),
NULL ); NULL );
VIPS_ARG_BOOL( class, "rgba", 9,
_( "RGBA" ),
_( "Enable RGBA output" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsText, rgba ),
FALSE );
} }
static void static void
@ -541,11 +581,10 @@ vips_text_init( VipsText *text )
{ {
text->align = VIPS_ALIGN_LOW; text->align = VIPS_ALIGN_LOW;
text->dpi = 72; text->dpi = 72;
text->bitmap.buffer = NULL;
VIPS_SETSTR( text->font, "sans 12" ); VIPS_SETSTR( text->font, "sans 12" );
} }
#endif /*HAVE_PANGOFT2*/ #endif /*HAVE_PANGOCAIRO*/
/** /**
* vips_text: * vips_text:
@ -563,17 +602,22 @@ vips_text_init( VipsText *text )
* * @justify: %gboolean, justify lines * * @justify: %gboolean, justify lines
* * @dpi: %gint, render at this resolution * * @dpi: %gint, render at this resolution
* * @autofit_dpi: %gint, read out auto-fitted DPI * * @autofit_dpi: %gint, read out auto-fitted DPI
* * @rgba: %gboolean, enable RGBA output
* * @spacing: %gint, space lines by this in points * * @spacing: %gint, space lines by this in points
* *
* Draw the string @text to an image. @out is a one-band 8-bit * Draw the string @text to an image. @out is normally a one-band 8-bit
* unsigned char image, with 0 for no text and 255 for text. Values between * unsigned char image, with 0 for no text and 255 for text. Values between
* are used for anti-aliasing. * are used for anti-aliasing.
* *
* Set @rgba to enable RGBA output. This is useful for colour emoji rendering,
* or support for pango markup features like `<span
* foreground="red">Red!</span>`.
*
* @text is the text to render as a UTF-8 string. It can contain Pango markup, * @text is the text to render as a UTF-8 string. It can contain Pango markup,
* for example "&lt;i&gt;The&lt;/i&gt;Guardian". * for example `<i>The</i>Guardian`.
* *
* @font is the font to render with, as a fontconfig name. Examples might be * @font is the font to render with, as a fontconfig name. Examples might be
* "sans 12" or perhaps "bitstream charter bold 10". * `sans 12` or perhaps `bitstream charter bold 10`.
* *
* You can specify a font to load with @fontfile. You'll need to also set the * You can specify a font to load with @fontfile. You'll need to also set the
* name of the font with @font. * name of the font with @font.

View File

@ -42,6 +42,9 @@
/** Transparent colour */ /** Transparent colour */
#define GIF_TRANSPARENT_COLOUR 0x00 #define GIF_TRANSPARENT_COLOUR 0x00
/** No transparency */
#define GIF_NO_TRANSPARENCY (0xFFFFFFFFu)
/* GIF Flags */ /* GIF Flags */
#define GIF_FRAME_COMBINE 1 #define GIF_FRAME_COMBINE 1
#define GIF_FRAME_CLEAR 2 #define GIF_FRAME_CLEAR 2
@ -79,34 +82,17 @@ gif_initialise_sprite(gif_animation *gif,
unsigned int width, unsigned int width,
unsigned int height) unsigned int height)
{ {
unsigned int max_width; /* Already allocated? */
unsigned int max_height; if (gif->frame_image) {
struct bitmap *buffer;
/* Check if we've changed */
if ((width <= gif->width) && (height <= gif->height)) {
return GIF_OK; return GIF_OK;
} }
/* Get our maximum values */
max_width = (width > gif->width) ? width : gif->width;
max_height = (height > gif->height) ? height : gif->height;
/* Allocate some more memory */
assert(gif->bitmap_callbacks.bitmap_create); assert(gif->bitmap_callbacks.bitmap_create);
buffer = gif->bitmap_callbacks.bitmap_create(max_width, max_height); gif->frame_image = gif->bitmap_callbacks.bitmap_create(width, height);
if (buffer == NULL) { if (gif->frame_image == NULL) {
return GIF_INSUFFICIENT_MEMORY; return GIF_INSUFFICIENT_MEMORY;
} }
assert(gif->bitmap_callbacks.bitmap_destroy);
gif->bitmap_callbacks.bitmap_destroy(gif->frame_image);
gif->frame_image = buffer;
gif->width = max_width;
gif->height = max_height;
/* Invalidate our currently decoded image */
gif->decoded_frame = GIF_INVALID_FRAME;
return GIF_OK; return GIF_OK;
} }
@ -392,10 +378,12 @@ static gif_result gif_initialise_frame(gif_animation *gif)
gif->frames[frame].redraw_required = ((gif->frames[frame].disposal_method == GIF_FRAME_CLEAR) || gif->frames[frame].redraw_required = ((gif->frames[frame].disposal_method == GIF_FRAME_CLEAR) ||
(gif->frames[frame].disposal_method == GIF_FRAME_RESTORE)); (gif->frames[frame].disposal_method == GIF_FRAME_RESTORE));
/* Boundary checking - shouldn't ever happen except with junk data */ /* Frame size may have grown.
if (gif_initialise_sprite(gif, (offset_x + width), (offset_y + height))) { */
return GIF_INSUFFICIENT_MEMORY; gif->width = (offset_x + width > gif->width) ?
} offset_x + width : gif->width;
gif->height = (offset_y + height > gif->height) ?
offset_y + height : gif->height;
/* Decode the flags */ /* Decode the flags */
flags = gif_data[9]; flags = gif_data[9];
@ -633,6 +621,160 @@ static gif_result gif__recover_previous_frame(const gif_animation *gif)
return GIF_OK; return GIF_OK;
} }
static gif_result
gif__decode_complex(gif_animation *gif,
unsigned int frame,
unsigned int width,
unsigned int height,
unsigned int offset_x,
unsigned int offset_y,
unsigned int interlace,
uint8_t minimum_code_size,
unsigned int *restrict frame_data,
unsigned int *restrict colour_table)
{
unsigned int transparency_index;
uint32_t available = 0;
gif_result ret = GIF_OK;
lzw_result res;
/* Initialise the LZW decoding */
res = lzw_decode_init(gif->lzw_ctx, gif->gif_data,
gif->buffer_size, gif->buffer_position,
minimum_code_size);
if (res != LZW_OK) {
return gif_error_from_lzw(res);
}
transparency_index = gif->frames[frame].transparency ?
gif->frames[frame].transparency_index :
GIF_NO_TRANSPARENCY;
for (unsigned int y = 0; y < height; y++) {
unsigned int x;
unsigned int decode_y;
unsigned int *frame_scanline;
if (interlace) {
decode_y = gif_interlaced_line(height, y) + offset_y;
} else {
decode_y = y + offset_y;
}
frame_scanline = frame_data + offset_x + (decode_y * gif->width);
x = width;
while (x > 0) {
const uint8_t *uncompressed;
unsigned row_available;
if (available == 0) {
if (res != LZW_OK) {
/* Unexpected end of frame, try to recover */
if (res == LZW_OK_EOD) {
ret = GIF_OK;
} else {
ret = gif_error_from_lzw(res);
}
break;
}
res = lzw_decode_continuous(gif->lzw_ctx,
&uncompressed, &available);
}
row_available = x < available ? x : available;
x -= row_available;
available -= row_available;
while (row_available-- > 0) {
register unsigned int colour;
colour = *uncompressed++;
if (colour != transparency_index) {
*frame_scanline = colour_table[colour];
}
frame_scanline++;
}
}
}
return ret;
}
static gif_result
gif__decode_simple(gif_animation *gif,
unsigned int frame,
unsigned int height,
unsigned int offset_y,
uint8_t minimum_code_size,
unsigned int *restrict frame_data,
unsigned int *restrict colour_table)
{
unsigned int transparency_index;
uint32_t pixels = gif->width * height;
uint32_t written = 0;
gif_result ret = GIF_OK;
lzw_result res;
/* Initialise the LZW decoding */
res = lzw_decode_init(gif->lzw_ctx, gif->gif_data,
gif->buffer_size, gif->buffer_position,
minimum_code_size);
if (res != LZW_OK) {
return gif_error_from_lzw(res);
}
transparency_index = gif->frames[frame].transparency ?
gif->frames[frame].transparency_index :
GIF_NO_TRANSPARENCY;
frame_data += (offset_y * gif->width);
while (pixels > 0) {
res = lzw_decode_map_continuous(gif->lzw_ctx,
transparency_index, colour_table,
frame_data, pixels, &written);
pixels -= written;
frame_data += written;
if (res != LZW_OK) {
/* Unexpected end of frame, try to recover */
if (res == LZW_OK_EOD) {
ret = GIF_OK;
} else {
ret = gif_error_from_lzw(res);
}
break;
}
}
if (pixels == 0) {
ret = GIF_OK;
}
return ret;
}
static inline gif_result
gif__decode(gif_animation *gif,
unsigned int frame,
unsigned int width,
unsigned int height,
unsigned int offset_x,
unsigned int offset_y,
unsigned int interlace,
uint8_t minimum_code_size,
unsigned int *restrict frame_data,
unsigned int *restrict colour_table)
{
gif_result ret;
if (interlace == false && width == gif->width && offset_x == 0) {
ret = gif__decode_simple(gif, frame, height, offset_y,
minimum_code_size, frame_data, colour_table);
} else {
ret = gif__decode_complex(gif, frame, width, height,
offset_x, offset_y, interlace,
minimum_code_size, frame_data, colour_table);
}
return ret;
}
/** /**
* decode a gif frame * decode a gif frame
* *
@ -653,11 +795,8 @@ gif_internal_decode_frame(gif_animation *gif,
unsigned int flags, colour_table_size, interlace; unsigned int flags, colour_table_size, interlace;
unsigned int *colour_table; unsigned int *colour_table;
unsigned int *frame_data = 0; // Set to 0 for no warnings unsigned int *frame_data = 0; // Set to 0 for no warnings
unsigned int *frame_scanline;
unsigned int save_buffer_position; unsigned int save_buffer_position;
unsigned int return_value = 0; unsigned int return_value = 0;
unsigned int x, y, decode_y, burst_bytes;
register unsigned char colour;
/* Ensure this frame is supposed to be decoded */ /* Ensure this frame is supposed to be decoded */
if (gif->frames[frame].display == false) { if (gif->frames[frame].display == false) {
@ -739,6 +878,12 @@ gif_internal_decode_frame(gif_animation *gif,
goto gif_decode_frame_exit; goto gif_decode_frame_exit;
} }
/* Make sure we have a buffer to decode to.
*/
if (gif_initialise_sprite(gif, gif->width, gif->height)) {
return GIF_INSUFFICIENT_MEMORY;
}
/* Decode the flags */ /* Decode the flags */
flags = gif_data[9]; flags = gif_data[9];
colour_table_size = 2 << (flags & GIF_COLOUR_TABLE_SIZE_MASK); colour_table_size = 2 << (flags & GIF_COLOUR_TABLE_SIZE_MASK);
@ -805,10 +950,6 @@ gif_internal_decode_frame(gif_animation *gif,
/* If we are clearing the image we just clear, if not decode */ /* If we are clearing the image we just clear, if not decode */
if (!clear_image) { if (!clear_image) {
lzw_result res;
const uint8_t *stack_base;
const uint8_t *stack_pos;
/* Ensure we have enough data for a 1-byte LZW code size + /* Ensure we have enough data for a 1-byte LZW code size +
* 1-byte gif trailer * 1-byte gif trailer
*/ */
@ -872,62 +1013,15 @@ gif_internal_decode_frame(gif_animation *gif,
gif->decoded_frame = frame; gif->decoded_frame = frame;
gif->buffer_position = (gif_data - gif->gif_data) + 1; gif->buffer_position = (gif_data - gif->gif_data) + 1;
/* Initialise the LZW decoding */ return_value = gif__decode(gif, frame, width, height,
res = lzw_decode_init(gif->lzw_ctx, gif->gif_data, offset_x, offset_y, interlace, gif_data[0],
gif->buffer_size, gif->buffer_position, frame_data, colour_table);
gif_data[0], &stack_base, &stack_pos);
if (res != LZW_OK) {
return gif_error_from_lzw(res);
}
/* Decompress the data */
for (y = 0; y < height; y++) {
if (interlace) {
decode_y = gif_interlaced_line(height, y) + offset_y;
} else {
decode_y = y + offset_y;
}
frame_scanline = frame_data + offset_x + (decode_y * gif->width);
/* Rather than decoding pixel by pixel, we try to burst
* out streams of data to remove the need for end-of
* data checks every pixel.
*/
x = width;
while (x > 0) {
burst_bytes = (stack_pos - stack_base);
if (burst_bytes > 0) {
if (burst_bytes > x) {
burst_bytes = x;
}
x -= burst_bytes;
while (burst_bytes-- > 0) {
colour = *--stack_pos;
if (((gif->frames[frame].transparency) &&
(colour != gif->frames[frame].transparency_index)) ||
(!gif->frames[frame].transparency)) {
*frame_scanline = colour_table[colour];
}
frame_scanline++;
}
} else {
res = lzw_decode(gif->lzw_ctx, &stack_pos);
if (res != LZW_OK) {
/* Unexpected end of frame, try to recover */
if (res == LZW_OK_EOD) {
return_value = GIF_OK;
} else {
return_value = gif_error_from_lzw(res);
}
goto gif_decode_frame_exit;
}
}
}
}
} else { } else {
/* Clear our frame */ /* Clear our frame */
if (gif->frames[frame].disposal_method == GIF_FRAME_CLEAR) { if (gif->frames[frame].disposal_method == GIF_FRAME_CLEAR) {
unsigned int y;
for (y = 0; y < height; y++) { for (y = 0; y < height; y++) {
unsigned int *frame_scanline;
frame_scanline = frame_data + offset_x + ((offset_y + y) * gif->width); frame_scanline = frame_data + offset_x + ((offset_y + y) * gif->width);
if (gif->frames[frame].transparency) { if (gif->frames[frame].transparency) {
memset(frame_scanline, memset(frame_scanline,
@ -1115,14 +1209,6 @@ gif_result gif_initialise(gif_animation *gif, size_t size, unsigned char *data)
} }
gif->frame_holders = 1; gif->frame_holders = 1;
/* Initialise the bitmap header */
assert(gif->bitmap_callbacks.bitmap_create);
gif->frame_image = gif->bitmap_callbacks.bitmap_create(gif->width, gif->height);
if (gif->frame_image == NULL) {
gif_finalise(gif);
return GIF_INSUFFICIENT_MEMORY;
}
/* Remember we've done this now */ /* Remember we've done this now */
gif->buffer_position = gif_data - gif->gif_data; gif->buffer_position = gif_data - gif->gif_data;
} }

View File

@ -4,6 +4,7 @@
* http://www.opensource.org/licenses/mit-license.php * http://www.opensource.org/licenses/mit-license.php
* *
* Copyright 2017 Michael Drake <michael.drake@codethink.co.uk> * Copyright 2017 Michael Drake <michael.drake@codethink.co.uk>
* Copyright 2021 Michael Drake <tlsa@netsurf-browser.org>
*/ */
#include <assert.h> #include <assert.h>
@ -20,6 +21,8 @@
* Decoder for GIF LZW data. * Decoder for GIF LZW data.
*/ */
/** Maximum number of lzw table entries. */
#define LZW_TABLE_ENTRY_MAX (1u << LZW_CODE_MAX)
/** /**
* Context for reading LZW data. * Context for reading LZW data.
@ -33,55 +36,63 @@
* Note that an individual LZW code can be split over up to three sub-blocks. * Note that an individual LZW code can be split over up to three sub-blocks.
*/ */
struct lzw_read_ctx { struct lzw_read_ctx {
const uint8_t *data; /**< Pointer to start of input data */ const uint8_t *restrict data; /**< Pointer to start of input data */
uint32_t data_len; /**< Input data length */ uint32_t data_len; /**< Input data length */
uint32_t data_sb_next; /**< Offset to sub-block size */ uint32_t data_sb_next; /**< Offset to sub-block size */
const uint8_t *sb_data; /**< Pointer to current sub-block in data */ const uint8_t *sb_data; /**< Pointer to current sub-block in data */
uint32_t sb_bit; /**< Current bit offset in sub-block */ size_t sb_bit; /**< Current bit offset in sub-block */
uint32_t sb_bit_count; /**< Bit count in sub-block */ uint32_t sb_bit_count; /**< Bit count in sub-block */
}; };
/** /**
* LZW dictionary entry. * LZW table entry.
* *
* Records in the dictionary are composed of 1 or more entries. * Records in the table are composed of 1 or more entries.
* Entries point to previous entries which can be followed to compose * Entries refer to the entry they extend which can be followed to compose
* the complete record. To compose the record in reverse order, take * the complete record. To compose the record in reverse order, take
* the `last_value` from each entry, and move to the previous entry. * the `value` from each entry, and move to the entry it extends.
* If the previous_entry's index is < the current clear_code, then it * If the extended entries index is < the current clear_code, then it
* is the last entry in the record. * is the last entry in the record.
*/ */
struct lzw_dictionary_entry { struct lzw_table_entry {
uint8_t last_value; /**< Last value for record ending at entry. */ uint8_t value; /**< Last value for record ending at entry. */
uint8_t first_value; /**< First value for entry's record. */ uint8_t first; /**< First value in entry's entire record. */
uint16_t previous_entry; /**< Offset in dictionary to previous entry. */ uint16_t count; /**< Count of values in this entry's record. */
uint16_t extends; /**< Offset in table to previous entry. */
}; };
/** /**
* LZW decompression context. * LZW decompression context.
*/ */
struct lzw_ctx { struct lzw_ctx {
/** Input reading context */ struct lzw_read_ctx input; /**< Input reading context */
struct lzw_read_ctx input;
uint32_t previous_code; /**< Code read from input previously. */ uint32_t prev_code; /**< Code read from input previously. */
uint32_t previous_code_first; /**< First value of previous code. */ uint32_t prev_code_first; /**< First value of previous code. */
uint32_t prev_code_count; /**< Total values for previous code. */
uint32_t initial_code_size; /**< Starting LZW code size. */ 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 code_size; /**< Current LZW code size. */
uint32_t code_max; /**< Max code value for current code size. */
uint32_t clear_code; /**< Special Clear code value */ uint32_t clear_code; /**< Special Clear code value */
uint32_t eoi_code; /**< Special End of Information code value */ uint32_t eoi_code; /**< Special End of Information code value */
uint32_t current_entry; /**< Next position in table to fill. */ uint32_t table_size; /**< Next position in table to fill. */
uint32_t output_code; /**< Code that has been partially output. */
uint32_t output_left; /**< Number of values left for output_code. */
uint32_t transparency_idx; /**< Index representing transparency. */
uint32_t *restrict colour_map; /**< Index to pixel colour mapping */
/** Output value stack. */ /** Output value stack. */
uint8_t stack_base[1 << LZW_CODE_MAX]; uint8_t stack_base[LZW_TABLE_ENTRY_MAX];
/** LZW decode dictionary. Generated during decode. */ /** LZW code table. Generated during decode. */
struct lzw_dictionary_entry table[1 << LZW_CODE_MAX]; struct lzw_table_entry table[LZW_TABLE_ENTRY_MAX];
}; };
@ -111,7 +122,7 @@ void lzw_context_destroy(struct lzw_ctx *ctx)
* \param[in] ctx LZW reading context, updated on success. * \param[in] ctx LZW reading context, updated on success.
* \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise. * \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise.
*/ */
static lzw_result lzw__block_advance(struct lzw_read_ctx *ctx) static lzw_result lzw__block_advance(struct lzw_read_ctx *restrict ctx)
{ {
uint32_t block_size; uint32_t block_size;
uint32_t next_block_pos = ctx->data_sb_next; uint32_t next_block_pos = ctx->data_sb_next;
@ -152,31 +163,27 @@ static lzw_result lzw__block_advance(struct lzw_read_ctx *ctx)
* \param[out] code_out Returns an LZW code on success. * \param[out] code_out Returns an LZW code on success.
* \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise. * \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise.
*/ */
static inline lzw_result lzw__next_code( static inline lzw_result lzw__read_code(
struct lzw_read_ctx *ctx, struct lzw_read_ctx *restrict ctx,
uint8_t code_size, uint32_t code_size,
uint32_t *code_out) uint32_t *restrict code_out)
{ {
uint32_t code = 0; uint32_t code = 0;
uint8_t current_bit = ctx->sb_bit & 0x7; uint32_t current_bit = ctx->sb_bit & 0x7;
uint8_t byte_advance = (current_bit + code_size) >> 3;
assert(byte_advance <= 2); if (ctx->sb_bit + 24 <= ctx->sb_bit_count) {
/* Fast path: read three bytes from this sub-block */
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); const uint8_t *data = ctx->sb_data + (ctx->sb_bit >> 3);
switch (byte_advance) { code |= *data++ << 0;
case 2: code |= data[2] << 16; /* Fall through */ code |= *data++ << 8;
case 1: code |= data[1] << 8; /* Fall through */ code |= *data << 16;
case 0: code |= data[0] << 0;
}
ctx->sb_bit += code_size; ctx->sb_bit += code_size;
} else { } else {
/* Slow path: code spans sub-blocks */ /* Slow path: code spans sub-blocks */
uint8_t byte_advance = (current_bit + code_size) >> 3;
uint8_t byte = 0; uint8_t byte = 0;
uint8_t bits_remaining_0 = (code_size < (8 - current_bit)) ? uint8_t bits_remaining_0 = (code_size < (8u - current_bit)) ?
code_size : (8 - current_bit); code_size : (8u - current_bit);
uint8_t bits_remaining_1 = code_size - bits_remaining_0; uint8_t bits_remaining_1 = code_size - bits_remaining_0;
uint8_t bits_used[3] = { uint8_t bits_used[3] = {
[0] = bits_remaining_0, [0] = bits_remaining_0,
@ -184,6 +191,8 @@ static inline lzw_result lzw__next_code(
[2] = bits_remaining_1 - 8, [2] = bits_remaining_1 - 8,
}; };
assert(byte_advance <= 2);
while (true) { while (true) {
const uint8_t *data = ctx->sb_data; const uint8_t *data = ctx->sb_data;
lzw_result res; lzw_result res;
@ -215,48 +224,18 @@ static inline lzw_result lzw__next_code(
/** /**
* Clear LZW code dictionary. * Clear LZW code table.
* *
* \param[in] ctx LZW reading context, updated. * \param[in] ctx LZW reading context, updated.
* \param[out] stack_pos_out Returns current stack position.
* \return LZW_OK or error code. * \return LZW_OK or error code.
*/ */
static lzw_result lzw__clear_codes( static inline void lzw__clear_table(
struct lzw_ctx *ctx, struct lzw_ctx *ctx)
const uint8_t ** const stack_pos_out)
{ {
uint32_t code; /* Reset table building context */
uint8_t *stack_pos; ctx->code_size = ctx->initial_code_size;
ctx->code_max = (1 << ctx->initial_code_size) - 1;
/* Reset dictionary building context */ ctx->table_size = ctx->eoi_code + 1;
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;
} }
@ -266,13 +245,11 @@ lzw_result lzw_decode_init(
const uint8_t *compressed_data, const uint8_t *compressed_data,
uint32_t compressed_data_len, uint32_t compressed_data_len,
uint32_t compressed_data_pos, uint32_t compressed_data_pos,
uint8_t code_size, uint8_t minimum_code_size)
const uint8_t ** const stack_base_out,
const uint8_t ** const stack_pos_out)
{ {
struct lzw_dictionary_entry *table = ctx->table; struct lzw_table_entry *table = ctx->table;
if (code_size >= LZW_CODE_MAX) { if (minimum_code_size >= LZW_CODE_MAX) {
return LZW_BAD_ICODE; return LZW_BAD_ICODE;
} }
@ -284,98 +261,291 @@ lzw_result lzw_decode_init(
ctx->input.sb_bit = 0; ctx->input.sb_bit = 0;
ctx->input.sb_bit_count = 0; ctx->input.sb_bit_count = 0;
/* Initialise the dictionary building context */ /* Initialise the table building context */
ctx->initial_code_size = code_size; ctx->initial_code_size = minimum_code_size + 1;
ctx->clear_code = (1 << code_size) + 0; ctx->clear_code = (1 << minimum_code_size) + 0;
ctx->eoi_code = (1 << code_size) + 1; ctx->eoi_code = (1 << minimum_code_size) + 1;
/* Initialise the standard dictionary entries */ ctx->output_left = 0;
/* Initialise the standard table entries */
for (uint32_t i = 0; i < ctx->clear_code; ++i) { for (uint32_t i = 0; i < ctx->clear_code; ++i) {
table[i].first_value = i; table[i].first = i;
table[i].last_value = i; table[i].value = i;
table[i].count = 1;
} }
*stack_base_out = ctx->stack_base; lzw__clear_table(ctx);
return lzw__clear_codes(ctx, stack_pos_out); ctx->prev_code = ctx->clear_code;
return LZW_OK;
} }
/**
* Create new table entry.
*
* \param[in] ctx LZW reading context, updated.
* \param[in] code Last value code for new table entry.
*/
static inline void lzw__table_add_entry(
struct lzw_ctx *ctx,
uint32_t code)
{
struct lzw_table_entry *entry = &ctx->table[ctx->table_size];
/* Exported function, documented in lzw.h */ entry->value = code;
lzw_result lzw_decode(struct lzw_ctx *ctx, entry->first = ctx->prev_code_first;
const uint8_t ** const stack_pos_out) entry->count = ctx->prev_code_count + 1;
entry->extends = ctx->prev_code;
ctx->table_size++;
}
typedef uint32_t (*lzw_writer_fn)(
struct lzw_ctx *ctx,
void *restrict output,
uint32_t length,
uint32_t used,
uint32_t code,
uint32_t left);
/**
* Get the next LZW code and write its value(s) to output buffer.
*
* \param[in] ctx LZW reading context, updated.
* \param[in] output Array to write output values into.
* \param[in] length Size of output array.
* \param[in] write_pixels Function for writing pixels to output.
* \param[in,out] used Number of values written. Updated on exit.
* \return LZW_OK on success, or appropriate error code otherwise.
*/
static inline lzw_result lzw__decode(struct lzw_ctx *ctx,
void *restrict output,
uint32_t length,
lzw_writer_fn write_pixels,
uint32_t *restrict used)
{ {
lzw_result res; lzw_result res;
uint32_t code_new; uint32_t code;
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 */ /* Get a new code from the input */
res = lzw__next_code(&ctx->input, ctx->current_code_size, &code_new); res = lzw__read_code(&ctx->input, ctx->code_size, &code);
if (res != LZW_OK) { if (res != LZW_OK) {
return res; return res;
} }
/* Handle the new code */ /* Handle the new code */
if (code_new == clear_code) { if (code == ctx->eoi_code) {
/* Got Clear code */
return lzw__clear_codes(ctx, stack_pos_out);
} else if (code_new == ctx->eoi_code) {
/* Got End of Information code */ /* Got End of Information code */
return LZW_EOI_CODE; return LZW_EOI_CODE;
} else if (code_new > current_entry) { } else if (code > ctx->table_size) {
/* Code is invalid */ /* Code is invalid */
return LZW_BAD_CODE; return LZW_BAD_CODE;
} else if (code_new < current_entry) { } else if (code == ctx->clear_code) {
/* Code is in table */ lzw__clear_table(ctx);
code_out = code_new;
last_value = table[code_new].first_value;
} else { } else {
/* Code not in table */ if (ctx->prev_code != ctx->clear_code &&
*stack_pos++ = ctx->previous_code_first; ctx->table_size < LZW_TABLE_ENTRY_MAX) {
code_out = ctx->previous_code; uint32_t size = ctx->table_size;
last_value = ctx->previous_code_first; lzw__table_add_entry(ctx, (code < size) ?
} ctx->table[code].first :
ctx->prev_code_first);
/* Add to the dictionary, only if there's space */ /* Ensure code size is increased, if needed. */
if (current_entry < (1 << LZW_CODE_MAX)) { if (size == ctx->code_max &&
struct lzw_dictionary_entry *entry = table + current_entry; ctx->code_size < LZW_CODE_MAX) {
entry->last_value = last_value; ctx->code_size++;
entry->first_value = ctx->previous_code_first; ctx->code_max = (1 << ctx->code_size) - 1;
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;
} }
*used += write_pixels(ctx, output, length, *used, code,
ctx->table[code].count);
} }
/* Store details of this code as "previous code" to the context. */ /* Store details of this code as "previous code" to the context. */
ctx->previous_code_first = table[code_new].first_value; ctx->prev_code_first = ctx->table[code].first;
ctx->previous_code = code_new; ctx->prev_code_count = ctx->table[code].count;
ctx->prev_code = code;
return LZW_OK;
}
/**
* Write values for this code to the output stack.
*
* If there isn't enough space in the output stack, this function will write
* the as many as it can into the output. If `ctx->output_left > 0` after
* this call, then there is more data for this code left to output. The code
* is stored to the context as `ctx->output_code`.
*
* \param[in] ctx LZW reading context, updated.
* \param[in] output Array to write output values into.
* \param[in] length Size of output array.
* \param[in] used Current position in output array.
* \param[in] code LZW code to output values for.
* \param[in] left Number of values remaining to output for this value.
* \return Number of pixel values written.
*/
static inline uint32_t lzw__write_pixels(struct lzw_ctx *ctx,
void *restrict output,
uint32_t length,
uint32_t used,
uint32_t code,
uint32_t left)
{
uint8_t *restrict output_pos = (uint8_t *)output + used;
const struct lzw_table_entry * const table = ctx->table;
uint32_t space = length - used;
uint32_t count = left;
if (count > space) {
left = count - space;
count = space;
} else {
left = 0;
}
ctx->output_code = code;
ctx->output_left = left;
/* Skip over any values we don't have space for. */
for (unsigned i = left; i != 0; i--) {
const struct lzw_table_entry *entry = table + code;
code = entry->extends;
}
output_pos += count;
for (unsigned i = count; i != 0; i--) {
const struct lzw_table_entry *entry = table + code;
*--output_pos = entry->value;
code = entry->extends;
}
return count;
}
/* Exported function, documented in lzw.h */
lzw_result lzw_decode(struct lzw_ctx *ctx,
const uint8_t *restrict* const restrict data,
uint32_t *restrict used)
{
*used = 0;
*data = ctx->stack_base;
return lzw__decode(ctx, ctx->stack_base, sizeof(ctx->stack_base),
lzw__write_pixels, used);
}
/* Exported function, documented in lzw.h */
lzw_result lzw_decode_continuous(struct lzw_ctx *ctx,
const uint8_t ** const data,
uint32_t *restrict used)
{
*used = 0;
*data = ctx->stack_base;
if (ctx->output_left != 0) {
*used += lzw__write_pixels(ctx,
ctx->stack_base, sizeof(ctx->stack_base), *used,
ctx->output_code, ctx->output_left);
}
while (*used != sizeof(ctx->stack_base)) {
lzw_result res = lzw__decode(ctx,
ctx->stack_base, sizeof(ctx->stack_base),
lzw__write_pixels, used);
if (res != LZW_OK) {
return res;
}
}
return LZW_OK;
}
/**
* Write colour mapped values for this code to the output stack.
*
* If there isn't enough space in the output stack, this function will write
* the as many as it can into the output. If `ctx->output_left > 0` after
* this call, then there is more data for this code left to output. The code
* is stored to the context as `ctx->output_code`.
*
* \param[in] ctx LZW reading context, updated.
* \param[in] output Array to write output values into.
* \param[in] length Size of output array.
* \param[in] used Current position in output array.
* \param[in] code LZW code to output values for.
* \param[in] left Number of values remaining to output for this value.
* \return Number of pixel values written.
*/
static inline uint32_t lzw__write_pixels_map(struct lzw_ctx *ctx,
void *restrict buffer,
uint32_t length,
uint32_t used,
uint32_t code,
uint32_t left)
{
uint32_t *restrict stack_pos = (uint32_t *)buffer + used;
const struct lzw_table_entry * const table = ctx->table;
uint32_t space = length - used;
uint32_t count = left;
if (count > space) {
left = count - space;
count = space;
} else {
left = 0;
}
ctx->output_code = code;
ctx->output_left = left;
for (unsigned i = left; i != 0; i--) {
const struct lzw_table_entry *entry = table + code;
code = entry->extends;
}
stack_pos += count;
for (unsigned i = count; i != 0; i--) {
const struct lzw_table_entry *entry = table + code;
--stack_pos;
if (entry->value != ctx->transparency_idx) {
*stack_pos = ctx->colour_map[entry->value];
}
code = entry->extends;
}
return count;
}
/* Exported function, documented in lzw.h */
lzw_result lzw_decode_map_continuous(struct lzw_ctx *ctx,
uint32_t transparency_idx,
uint32_t *restrict colour_map,
uint32_t *restrict data,
uint32_t length,
uint32_t *restrict used)
{
*used = 0;
ctx->transparency_idx = transparency_idx;
ctx->colour_map = colour_map;
if (ctx->output_left != 0) {
*used += lzw__write_pixels_map(ctx, data, length, *used,
ctx->output_code, ctx->output_left);
}
while (*used != sizeof(ctx->stack_base)) {
lzw_result res = lzw__decode(ctx, data, length,
lzw__write_pixels_map, used);
if (res != LZW_OK) {
return res;
}
}
/* 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; return LZW_OK;
} }

View File

@ -65,11 +65,8 @@ void lzw_context_destroy(
* \param[in] compressed_data_len Byte length of compressed data. * \param[in] compressed_data_len Byte length of compressed data.
* \param[in] compressed_data_pos Start position in data. Must be position * \param[in] compressed_data_pos Start position in data. Must be position
* of a size byte at sub-block start. * of a size byte at sub-block start.
* \param[in] code_size The initial LZW code size to use. * \param[in] minimum_code_size The LZW Minimum Code Size.
* \param[out] stack_base_out Returns base of decompressed data stack. * \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. * \return LZW_OK on success, or appropriate error code otherwise.
*/ */
lzw_result lzw_decode_init( lzw_result lzw_decode_init(
@ -77,29 +74,61 @@ lzw_result lzw_decode_init(
const uint8_t *compressed_data, const uint8_t *compressed_data,
uint32_t compressed_data_len, uint32_t compressed_data_len,
uint32_t compressed_data_pos, uint32_t compressed_data_pos,
uint8_t code_size, uint8_t minimum_code_size);
const uint8_t ** const stack_base_out,
const uint8_t ** const stack_pos_out);
/** /**
* Fill the LZW stack with decompressed data * Read a single LZW code and write into lzw context owned output buffer.
* *
* Ensure anything on the stack is used before calling this, as anything * Ensure anything in output is used before calling this, as anything
* on the stack before this call will be trampled. * on the there before this call will be trampled.
* *
* Caller does not own `stack_pos_out`. * \param[in] ctx LZW reading context, updated.
* * \param[out] data Returns pointer to array of output values.
* \param[in] ctx LZW reading context, updated. * \param[out] used Returns the number of values written to data.
* \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. * \return LZW_OK on success, or appropriate error code otherwise.
*/ */
lzw_result lzw_decode( lzw_result lzw_decode(struct lzw_ctx *ctx,
struct lzw_ctx *ctx, const uint8_t *restrict *const restrict data,
const uint8_t ** const stack_pos_out); uint32_t *restrict used);
/**
* Read input codes until end of lzw context owned output buffer.
*
* Ensure anything in output is used before calling this, as anything
* on the there before this call will be trampled.
*
* \param[in] ctx LZW reading context, updated.
* \param[out] data Returns pointer to array of output values.
* \param[out] used Returns the number of values written to data.
* \return LZW_OK on success, or appropriate error code otherwise.
*/
lzw_result lzw_decode_continuous(struct lzw_ctx *ctx,
const uint8_t ** const data,
uint32_t *restrict used);
/**
* Read LZW codes into client buffer, mapping output to colours.
*
* Ensure anything in output is used before calling this, as anything
* on the there before this call will be trampled.
*
* For transparency to work correctly, the given client buffer must have
* the values from the previous frame. The transparency_idx should be a value
* of 256 or above, if the frame does not have transparency.
*
* \param[in] ctx LZW reading context, updated.
* \param[in] transparency_idx Index representing transparency.
* \param[in] colour_map Index to pixel colour mapping
* \param[in] data Client buffer to fill with colour mapped values.
* \param[in] length Size of output array.
* \param[out] used Returns the number of values written to data.
* \return LZW_OK on success, or appropriate error code otherwise.
*/
lzw_result lzw_decode_map_continuous(struct lzw_ctx *ctx,
uint32_t transparency_idx,
uint32_t *restrict colour_table,
uint32_t *restrict data,
uint32_t length,
uint32_t *restrict used);
#endif #endif

View File

@ -534,17 +534,19 @@ vips_foreign_load_nsgif_class_init( VipsForeignLoadNsgifClass *class )
static void * static void *
vips_foreign_load_nsgif_bitmap_create( int width, int height ) vips_foreign_load_nsgif_bitmap_create( int width, int height )
{ {
/* Check GIF dimensions fit within 16-bit unsigned. /* Enforce max GIF dimensions of 16383 (0x7FFF). This should be enough
* for anyone, and will prevent the worst GIF bombs.
*/ */
if( width <= 0 || if( width <= 0 ||
width > 65535 || width > 16383 ||
height <= 0 || height <= 0 ||
height > 65535 ) { height > 16383 ) {
vips_error( "gifload", vips_error( "gifload",
"%s", _( "dimensions out of range ") ); "%s", _( "bad image dimensions") );
return( NULL ); return( NULL );
} }
return g_malloc0( width * height * 4 );
return g_malloc0( (gsize) width * height * 4 );
} }
static void static void

View File

@ -115,7 +115,7 @@ G_DEFINE_ABSTRACT_TYPE( VipsForeignSaveTiff, vips_foreign_save_tiff,
#define UC VIPS_FORMAT_UCHAR #define UC VIPS_FORMAT_UCHAR
/* Type promotion for save ... just always go to uchar. /* Type promotion for jpeg-in-tiff save ... just always go to uchar.
*/ */
static int bandfmt_jpeg[10] = { static int bandfmt_jpeg[10] = {
/* UC C US S UI I F X D DX */ /* UC C US S UI I F X D DX */

View File

@ -2,7 +2,6 @@ cplusplus/include/vips/VConnection8.h
cplusplus/include/vips/VError8.h cplusplus/include/vips/VError8.h
cplusplus/include/vips/VImage8.h cplusplus/include/vips/VImage8.h
cplusplus/include/vips/VInterpolate8.h cplusplus/include/vips/VInterpolate8.h
cplusplus/include/vips/vips-operators.h
libvips/include/vips/arithmetic.h libvips/include/vips/arithmetic.h
libvips/include/vips/basic.h libvips/include/vips/basic.h
libvips/include/vips/buf.h libvips/include/vips/buf.h