Merge remote-tracking branch 'origin/master' into colour

Conflicts:
	ChangeLog
	configure.in
	po/vips7.pot
This commit is contained in:
John Cupitt 2012-10-21 12:24:08 +01:00
commit f8a160de44
19 changed files with 831 additions and 167 deletions

View File

@ -5,9 +5,21 @@
im_LabS2LabQ(), im_LabQ2disp(), im_XYZ2disp(), im_disp2XYZ(),
im_icc_import*(), im_icc_export*(), im_icc_transform*() as classes
- added vips_colour_convert(), replaces all derived conversions
- dzsave can write zoomify and google maps layout as well
- tilecache supports threaded access
- openslide2vips gets underlying tile size from openslide
- embed has 'background' option
- dzsave --layout google has a @background option
2/10/12 started 7.30.4
- remove options from format string in .dzi (thanks Martin)
- vipsCC.pc required the wrong version of vips (thanks Alessandro)
13/9/12 started 7.30.3
- linecache sized itself too large
- fix a compile failure if libtiff was not found (thanks Martin)
- dzsave did not work for images with an odd number of scanlines
(thanks Martin)
4/9/12 started 7.30.2
- sequential stops all threads on error

5
TODO
View File

@ -1,3 +1,8 @@
- get rid of no-threads mode, glib no longer supports this
- argh g_mutex_new() is now deprecated, make our own vips_mutex_new()
compatibility function that uses g_mutex_init() internally, if available
- add mono as a colourspace? also rad?
- something to test if an image is in a supported colourspace?

View File

@ -16,8 +16,8 @@ AC_CONFIG_MACRO_DIR([m4])
# user-visible library versioning
m4_define([vips_major_version], [7])
m4_define([vips_minor_version], [30])
m4_define([vips_micro_version], [3])
m4_define([vips_minor_version], [31])
m4_define([vips_micro_version], [0])
m4_define([vips_version],
[vips_major_version.vips_minor_version.vips_micro_version])
@ -37,8 +37,8 @@ VIPS_VERSION_STRING=$VIPS_VERSION-`date`
# binary interface changes not backwards compatible?: reset age to 0
LIBRARY_CURRENT=33
LIBRARY_REVISION=6
LIBRARY_AGE=2
LIBRARY_REVISION=8
LIBRARY_AGE=4
# patched into include/vips/version.h
AC_SUBST(VIPS_VERSION)

View File

@ -83,8 +83,9 @@
#ifdef HAVE_LCMS
typedef DWORD cmsUInt32Number;
#define cmsSigRgbData icSigRgbData:
#define cmsSigCmykData icSigCmykData:
/* This doesn't exist in lcms1, just set it to zero.
*/
#define cmsFLAGS_NOCACHE (0)
#endif
/**
@ -191,7 +192,7 @@ vips_icc_build( VipsObject *object )
if( icc->in_profile &&
code->in ) {
switch( cmsGetColorSpace( icc->in_profile ) ) {
case cmsSigRgbData:
case icSigRgbData:
code->input_bands = 3;
code->input_format =
code->in->BandFmt == VIPS_FORMAT_USHORT ?
@ -201,7 +202,7 @@ vips_icc_build( VipsObject *object )
TYPE_RGB_16 : TYPE_RGB_8;
break;
case cmsSigCmykData:
case icSigCmykData:
code->input_bands = 4;
code->input_format =
code->in->BandFmt == VIPS_FORMAT_USHORT ?
@ -211,7 +212,7 @@ vips_icc_build( VipsObject *object )
TYPE_CMYK_16 : TYPE_CMYK_8;
break;
case cmsSigLabData:
case icSigLabData:
code->input_bands = 3;
code->input_format = VIPS_FORMAT_FLOAT;
icc->in_icc_format = TYPE_Lab_16;
@ -265,8 +266,8 @@ vips_icc_build( VipsObject *object )
*/
if( icc->in_profile &&
icc->out_profile &&
cmsGetColorSpace( icc->in_profile ) == cmsSigLabData &&
cmsGetColorSpace( icc->out_profile ) == cmsSigLabData ) {
cmsGetColorSpace( icc->in_profile ) == icSigLabData &&
cmsGetColorSpace( icc->out_profile ) == icSigLabData ) {
vips_error( class->nickname,
"%s", _( "no device profile" ) );
return( -1 );

View File

@ -23,6 +23,8 @@
* - cleanups
* 15/10/11
* - rewrite as a class
* 10/10/12
* - add @background
*/
/*
@ -79,11 +81,16 @@ typedef struct _VipsEmbed {
VipsImage *in;
VipsExtend extend;
VipsArea *background;
int x;
int y;
int width;
int height;
/* Pixel we paint calculated from background.
*/
VipsPel *ink;
/* Geometry calculations.
*/
VipsRect rout; /* Whole output area */
@ -257,6 +264,14 @@ vips_embed_gen( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop )
embed->extend == 0 ? 0 : 255 );
break;
case VIPS_EXTEND_BACKGROUND:
/* Paint the borders a solid value.
*/
for( i = 0; i < 8; i++ )
vips_region_paint_pel( or, &embed->border[i],
embed->ink );
break;
case VIPS_EXTEND_COPY:
/* Extend the borders.
*/
@ -327,6 +342,15 @@ vips_embed_build( VipsObject *object )
if( vips_image_pio_input( embed->in ) )
return( -1 );
if( !(embed->ink = vips__vector_to_ink(
"VipsEmbed", embed->in,
embed->background->data, embed->background->n )) )
return( -1 );
if( !vips_object_get_argument_assigned( object, "extend" ) &&
vips_object_get_argument_assigned( object, "background" ) )
embed->extend = VIPS_EXTEND_BACKGROUND;
switch( embed->extend ) {
case VIPS_EXTEND_REPEAT:
{
@ -398,6 +422,7 @@ vips_embed_build( VipsObject *object )
case VIPS_EXTEND_BLACK:
case VIPS_EXTEND_WHITE:
case VIPS_EXTEND_BACKGROUND:
case VIPS_EXTEND_COPY:
if( vips_image_copy_fields( conversion->out, embed->in ) )
return( -1 );
@ -559,13 +584,22 @@ vips_embed_class_init( VipsEmbedClass *class )
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsEmbed, extend ),
VIPS_TYPE_EXTEND, VIPS_EXTEND_BLACK );
VIPS_ARG_BOXED( class, "background", 12,
_( "Background" ),
_( "Colour for background pixels" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsEmbed, background ),
VIPS_TYPE_ARRAY_DOUBLE );
}
static void
vips_embed_init( VipsEmbed *embed )
{
/* Init our instance fields.
*/
embed->extend = VIPS_EXTEND_BLACK;
embed->background =
vips_area_new_array( G_TYPE_DOUBLE, sizeof( double ), 1 );
((double *) (embed->background->data))[0] = 0;
}
/**
@ -581,6 +615,7 @@ vips_embed_init( VipsEmbed *embed )
* Optional arguments:
*
* @extend: how to generate the edge pixels
* @background: colour for edge pixels
*
* The opposite of vips_extract_area(): embed @in within an image of size
* @width by @height at position @x, @y. @extend

View File

@ -155,9 +155,8 @@ vips_sequential_generate( VipsRegion *or,
* either not read anything yet or fallen through from the stall
* above.
*
* Probably the operation is something like
* extract_area and we should skip the initial part of the image. In
* fact we read to cache.
* Probably the operation is something like extract_area and we should
* skip the initial part of the image. In fact, we read to cache.
*/
if( r->top > sequential->y_pos ) {
VipsRect area;

View File

@ -21,6 +21,8 @@
* - use a hash table instead of a list
* 13/9/12
* - oops, linecache was oversized
* 8/10/12
* - make it optionally threaded
*/
/*
@ -68,13 +70,32 @@
#include "conversion.h"
/* A tile in cache can be in one of three states:
*
* DATA - the tile holds valid pixels
* CALC - some thread somewhere is calculating it
* PEND - some thread somewhere wants it
*/
typedef enum VipsTileState {
VIPS_TILE_STATE_DATA,
VIPS_TILE_STATE_CALC,
VIPS_TILE_STATE_PEND
} VipsTileState;
/* A tile in our cache.
*/
typedef struct _VipsTile {
struct _VipsBlockCache *cache;
VipsTileState state;
VipsRegion *region; /* Region with private mem for data */
/* We count how many threads are relying on this tile. This tile can't
* be flushed if ref_count > 0.
*/
int ref_count;
/* Tile position. Just use left/top to calculate a hash. This is the
* key for the hash table. Don't use region->valid in case the region
* pointer is NULL.
@ -92,10 +113,12 @@ typedef struct _VipsBlockCache {
int tile_height;
int max_tiles;
VipsCacheStrategy strategy;
gboolean threaded;
int time; /* Update ticks for LRU here */
int ntiles; /* Current cache size */
GMutex *lock; /* Lock everything here */
GCond *new_tile; /* A new tile is ready */
GHashTable *tiles; /* Tiles, hashed by coordinates */
} VipsBlockCache;
@ -108,6 +131,10 @@ G_DEFINE_TYPE( VipsBlockCache, vips_block_cache, VIPS_TYPE_CONVERSION );
static void
vips_block_cache_drop_all( VipsBlockCache *cache )
{
/* FIXME this is a disaster if active threads are working on tiles. We
* should have something to block new requests, and only dispose once
* all tiles are unreffed.
*/
g_hash_table_remove_all( cache->tiles );
}
@ -118,6 +145,7 @@ vips_block_cache_dispose( GObject *gobject )
vips_block_cache_drop_all( cache );
VIPS_FREEF( g_mutex_free, cache->lock );
VIPS_FREEF( g_cond_free, cache->new_tile );
G_OBJECT_CLASS( vips_block_cache_parent_class )->dispose( gobject );
}
@ -140,6 +168,10 @@ vips_tile_move( VipsTile *tile, int x, int y )
if( vips_region_buffer( tile->region, &tile->pos ) )
return( -1 );
/* No data yet, but someone must want it.
*/
tile->state = VIPS_TILE_STATE_PEND;
return( 0 );
}
@ -152,6 +184,8 @@ vips_tile_new( VipsBlockCache *cache, int x, int y )
return( NULL );
tile->cache = cache;
tile->state = VIPS_TILE_STATE_PEND;
tile->ref_count = 0;
tile->region = NULL;
tile->time = cache->time;
tile->pos.left = x;
@ -213,6 +247,7 @@ vips_tile_fill( VipsTile *tile, VipsRegion *in )
if( vips_region_prepare_to( in, tile->region,
&tile->pos, tile->pos.left, tile->pos.top ) )
return( -1 );
tile->state = VIPS_TILE_STATE_DATA;
vips_tile_touch( tile );
@ -226,25 +261,31 @@ typedef struct _VipsTileSearch {
int topmost;
} VipsTileSearch;
void
static void
vips_tile_oldest( gpointer key, gpointer value, gpointer user_data )
{
VipsTile *tile = (VipsTile *) value;
VipsTileSearch *search = (VipsTileSearch *) user_data;
if( tile->time < search->oldest ) {
/* Only consider unreffed tiles for recycling.
*/
if( !tile->ref_count &&
tile->time < search->oldest ) {
search->oldest = tile->time;
search->tile = tile;
}
}
void
static void
vips_tile_topmost( gpointer key, gpointer value, gpointer user_data )
{
VipsTile *tile = (VipsTile *) value;
VipsTileSearch *search = (VipsTileSearch *) user_data;
if( tile->pos.top < search->topmost ) {
/* Only consider unreffed tiles for recycling.
*/
if( !tile->ref_count &&
tile->pos.top < search->topmost ) {
search->topmost = tile->pos.top;
search->tile = tile;
}
@ -254,7 +295,7 @@ vips_tile_topmost( gpointer key, gpointer value, gpointer user_data )
* reuse LRU.
*/
static VipsTile *
vips_tile_find( VipsBlockCache *cache, VipsRegion *in, int x, int y )
vips_tile_find( VipsBlockCache *cache, int x, int y )
{
VipsTile *tile;
VipsTileSearch search;
@ -271,8 +312,7 @@ vips_tile_find( VipsBlockCache *cache, VipsRegion *in, int x, int y )
*/
if( cache->max_tiles == -1 ||
cache->ntiles < cache->max_tiles ) {
if( !(tile = vips_tile_new( cache, x, y )) ||
vips_tile_fill( tile, in ) )
if( !(tile = vips_tile_new( cache, x, y )) )
return( NULL );
return( tile );
@ -301,22 +341,44 @@ vips_tile_find( VipsBlockCache *cache, VipsRegion *in, int x, int y )
g_assert( 0 );
}
g_assert( tile );
if( !tile ) {
/* There are no tiles we can reuse -- we have to make another
* for now. They will get culled down again next time around.
*/
if( !(tile = vips_tile_new( cache, x, y )) )
return( NULL );
return( tile );
}
VIPS_DEBUG_MSG( "tilecache: reusing tile %d x %d\n",
tile->pos.left, tile->pos.top );
if( vips_tile_move( tile, x, y ) ||
vips_tile_fill( tile, in ) )
if( vips_tile_move( tile, x, y ) )
return( NULL );
return( tile );
}
static gboolean
vips_tile_unlocked( gpointer key, gpointer value, gpointer user_data )
{
VipsTile *tile = (VipsTile *) value;
return( !tile->ref_count );
}
static void
vips_block_cache_minimise( VipsImage *image, VipsBlockCache *cache )
{
vips_block_cache_drop_all( cache );
/* We can't drop tiles that are in use.
*/
g_mutex_lock( cache->lock );
g_hash_table_foreach_remove( cache->tiles,
vips_tile_unlocked, NULL );
g_mutex_unlock( cache->lock );
}
static int
@ -342,6 +404,7 @@ vips_block_cache_class_init( VipsBlockCacheClass *class )
{
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class );
VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class );
VIPS_DEBUG_MSG( "vips_block_cache_class_init\n" );
@ -353,20 +416,29 @@ vips_block_cache_class_init( VipsBlockCacheClass *class )
vobject_class->description = _( "cache an image" );
vobject_class->build = vips_block_cache_build;
operation_class->flags = VIPS_OPERATION_SEQUENTIAL;
VIPS_ARG_IMAGE( class, "in", 1,
_( "Input" ),
_( "Input image" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsBlockCache, in ) );
VIPS_ARG_INT( class, "tile_height", 3,
VIPS_ARG_INT( class, "tile_height", 4,
_( "Tile height" ),
_( "Tile height in pixels" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsBlockCache, tile_height ),
1, 1000000, 128 );
VIPS_ARG_ENUM( class, "strategy", 3,
VIPS_ARG_BOOL( class, "threaded", 7,
_( "Threaded" ),
_( "Allow threaded access" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsBlockCache, threaded ),
FALSE );
VIPS_ARG_ENUM( class, "strategy", 6,
_( "Strategy" ),
_( "Expected access pattern" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
@ -413,10 +485,12 @@ vips_block_cache_init( VipsBlockCache *cache )
cache->tile_height = 128;
cache->max_tiles = 1000;
cache->strategy = VIPS_CACHE_RANDOM;
cache->threaded = FALSE;
cache->time = 0;
cache->ntiles = 0;
cache->lock = g_mutex_new();
cache->new_tile = g_cond_new();
cache->tiles = g_hash_table_new_full(
(GHashFunc) vips_rect_hash,
(GEqualFunc) vips_rect_equal,
@ -433,6 +507,73 @@ typedef VipsBlockCacheClass VipsTileCacheClass;
G_DEFINE_TYPE( VipsTileCache, vips_tile_cache, VIPS_TYPE_BLOCK_CACHE );
static void
vips_tile_cache_unref( GSList *work )
{
GSList *p;
for( p = work; p; p = p->next ) {
VipsTile *tile = (VipsTile *) p->data;
tile->ref_count -= 1;
}
g_slist_free( work );
}
/* Make a set of work tiles.
*/
static GSList *
vips_tile_cache_ref( VipsBlockCache *cache, VipsRect *r )
{
const int tw = cache->tile_width;
const int th = cache->tile_height;
/* Find top left of tiles we need.
*/
const int xs = (r->left / tw) * tw;
const int ys = (r->top / th) * th;
GSList *work;
VipsTile *tile;
int x, y;
/* Ref all the tiles we will need.
*/
work = NULL;
for( y = ys; y < VIPS_RECT_BOTTOM( r ); y += th )
for( x = xs; x < VIPS_RECT_RIGHT( r ); x += tw ) {
if( !(tile = vips_tile_find( cache, x, y )) ) {
vips_tile_cache_unref( work );
return( NULL );
}
tile->ref_count += 1;
/* We must append, since we want to keep tile ordering
* for sequential sources.
*/
work = g_slist_append( work, tile );
VIPS_DEBUG_MSG( "vips_tile_cache_gen: "
"tile %d, %d (%p)\n", x, y, tile );
}
return( work );
}
static void
vips_tile_paste( VipsTile *tile, VipsRegion *or )
{
VipsRect hit;
/* The part of the tile that we need.
*/
vips_rect_intersectrect( &or->valid, &tile->pos, &hit );
if( !vips_rect_isempty( &hit ) )
vips_region_copy( tile->region, or, &hit, hit.left, hit.top );
}
/* Also called from vips_line_cache_gen(), beware.
*/
static int
@ -440,58 +581,103 @@ vips_tile_cache_gen( VipsRegion *or,
void *seq, void *a, void *b, gboolean *stop )
{
VipsRegion *in = (VipsRegion *) seq;
VipsBlockCache *block_cache = (VipsBlockCache *) b;
const int tw = block_cache->tile_width;
const int th = block_cache->tile_height;
VipsBlockCache *cache = (VipsBlockCache *) b;
VipsRect *r = &or->valid;
/* Find top left of tiles we need.
*/
int xs = (r->left / tw) * tw;
int ys = (r->top / th) * th;
VipsTile *tile;
GSList *work;
GSList *p;
int x, y;
g_mutex_lock( block_cache->lock );
/* If the output region fits within a tile, we could save a copy by
* routing the output region directly to the tile.
*
* However this would mean that tile drop on minimise could then leave
* dangling pointers, if minimise were called on an active pipeline.
*/
g_mutex_lock( cache->lock );
VIPS_DEBUG_MSG( "vips_tile_cache_gen: "
"left = %d, top = %d, width = %d, height = %d\n",
r->left, r->top, r->width, r->height );
for( y = ys; y < VIPS_RECT_BOTTOM( r ); y += th )
for( x = xs; x < VIPS_RECT_RIGHT( r ); x += tw ) {
VipsTile *tile;
VipsRect tarea;
VipsRect hit;
/* Ref all the tiles we will need.
*/
work = vips_tile_cache_ref( cache, r );
if( !(tile = vips_tile_find( block_cache,
in, x, y )) ) {
g_mutex_unlock( block_cache->lock );
return( -1 );
while( work ) {
/* Search for data tiles: easy, we can just paste those in.
*/
for(;;) {
for( p = work; p; p = p->next ) {
tile = (VipsTile *) p->data;
if( tile->state == VIPS_TILE_STATE_DATA )
break;
}
/* The area of the tile.
*/
tarea.left = x;
tarea.top = y;
tarea.width = tw;
tarea.height = th;
if( !p )
break;
/* The part of the tile that we need.
VIPS_DEBUG_MSG( "vips_tile_cache_gen: pasting %p\n",
tile );
vips_tile_paste( tile, or );
/* We're done with this tile.
*/
vips_rect_intersectrect( &tarea, r, &hit );
vips_region_copy( tile->region, or, &hit,
hit.left, hit.top );
work = g_slist_remove( work, tile );
tile->ref_count -= 1;
}
g_mutex_unlock( block_cache->lock );
/* Calculate the first PEND tile we find on the work list. We
* don't calculate all PEND tiles since after the first, more
* DATA tiles might heve been made available by other threads
* and we want to get them out of the way as soon as we can.
*/
for( p = work; p; p = p->next ) {
tile = (VipsTile *) p->data;
if( tile->state == VIPS_TILE_STATE_PEND ) {
tile->state = VIPS_TILE_STATE_CALC;
VIPS_DEBUG_MSG( "vips_tile_cache_gen: "
"calc of %p\n", tile );
/* In threaded mode, we let other threads run
* while we calc this tile. In non-threaded
* mode, we keep the lock and make 'em wait.
*/
if( cache->threaded )
g_mutex_unlock( cache->lock );
if( vips_tile_fill( tile, in ) ) {
tile->state = VIPS_TILE_STATE_PEND;
vips_tile_cache_unref( work );
if( !cache->threaded )
g_mutex_lock( cache->lock );
return( -1 );
}
if( cache->threaded )
g_mutex_lock( cache->lock );
/* Let everyone know there's a new DATA tile.
* They need to all check their work lists.
*/
g_cond_broadcast( cache->new_tile );
break;
}
}
/* There are no PEND or DATA tiles, but there's srill work to
* do. We must block until the CALC tiles we need are done.
*/
if( !p &&
work ) {
VIPS_DEBUG_MSG( "vips_tile_cache_gen: waiting\n" );
g_cond_wait( cache->new_tile, cache->lock );
VIPS_DEBUG_MSG( "vips_tile_cache_gen: awake!\n" );
}
}
g_mutex_unlock( cache->lock );
return( 0 );
}
@ -547,7 +733,7 @@ vips_tile_cache_class_init( VipsTileCacheClass *class )
G_STRUCT_OFFSET( VipsBlockCache, tile_width ),
1, 1000000, 128 );
VIPS_ARG_INT( class, "max_tiles", 3,
VIPS_ARG_INT( class, "max_tiles", 5,
_( "Max tiles" ),
_( "Maximum number of tiles to cache" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
@ -573,6 +759,7 @@ vips_tile_cache_init( VipsTileCache *cache )
* @tile_height: height of tiles in cache
* @max_tiles: maximum number of tiles to cache
* @strategy: hint expected access pattern #VipsCacheStrategy
* @threaded: allow many threads
*
* This operation behaves rather like vips_copy() between images
* @in and @out, except that it keeps a cache of computed pixels.
@ -590,9 +777,9 @@ vips_tile_cache_init( VipsTileCache *cache )
* By default, @tile_width and @tile_height are 128 pixels, and the operation
* will cache up to 1,000 tiles. @strategy defaults to #VIPS_CACHE_RANDOM.
*
* This is a lower-level operation than vips_image_cache() since it does no
* subdivision and it single-threads its callee. It is suitable for caching
* the output of operations like exr2vips() on tiled images.
* Normally, only a single thread at once is allowed to calculate tiles. If
* you set @threaded to %TRUE, vips_tilecache() will allow many threads to
* calculate tiles at once, and share the cache between them.
*
* See also: vips_cache(), vips_linecache().
*
@ -723,6 +910,7 @@ vips_line_cache_init( VipsLineCache *cache )
*
* @strategy: hint expected access pattern #VipsCacheStrategy
* @tile_height: height of tiles in cache
* @threaded: allow many threads
*
* This operation behaves rather like vips_copy() between images
* @in and @out, except that it keeps a cache of computed pixels.
@ -740,9 +928,9 @@ vips_line_cache_init( VipsLineCache *cache )
* @tile_height can be used to set the size of the strips that
* vips_linecache() uses. The default is 1 (a single scanline).
*
* This is a lower-level operation than vips_image_cache() since it does no
* subdivision and it single-threads its callee. It is suitable for caching
* the output of operations like png load.
* Normally, only a single thread at once is allowed to calculate tiles. If
* you set @threaded to %TRUE, vips_tilecache() will allow many threads to
* calculate tiles at once, and share the cache between them.
*
* See also: vips_cache(), vips_tilecache().
*

View File

@ -83,6 +83,7 @@ tiff2vips( const char *name, IMAGE *out, gboolean header_only )
* malloc if all we are doing is looking at fields.
*/
#ifdef HAVE_TIFF
if( !header_only &&
!seq &&
!vips__istifftiled( filename ) &&
@ -91,7 +92,6 @@ tiff2vips( const char *name, IMAGE *out, gboolean header_only )
return( -1 );
}
#ifdef HAVE_TIFF
if( header_only ) {
if( vips__tiff_read_header( filename, out, page ) )
return( -1 );

View File

@ -17,6 +17,15 @@
* - reorganise the directory structure
* - rename to basename and tile_size
* - deprecate tile_width/_height and dirname
* 1/10/12
* - did not write low pyramid layers for images with an odd number of
* scan lines (thanks Martin)
* 2/10/12
* - remove filename options from format string in .dzi (thanks Martin)
* 3/10/12
* - add zoomify and google maps output
* 10/10/12
* - add @background option
*/
/*
@ -46,10 +55,10 @@
*/
/*
*/
#define DEBUG_VERBOSE
#define DEBUG
#define VIPS_DEBUG
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
@ -61,6 +70,15 @@
#include <string.h>
#include <vips/vips.h>
#include <vips/internal.h>
/* Round N down to P boundary.
*/
#define ROUND_DOWN(N,P) ((N) - ((N) % P))
/* Round N up to P boundary.
*/
#define ROUND_UP(N,P) (ROUND_DOWN( (N) + (P) - 1, (P) ))
typedef struct _VipsForeignSaveDz VipsForeignSaveDz;
typedef struct _Layer Layer;
@ -77,6 +95,12 @@ struct _Layer {
int width;
int height;
/* Number of tiles across and down in this layer. Zoomify needs this
* to calculate the directory to put each tile in.
*/
int tiles_across;
int tiles_down;
VipsImage *image; /* The image we build */
/* The top of this strip of tiles, excluding the overlap. Go up from
@ -108,8 +132,14 @@ struct _VipsForeignSaveDz {
char *suffix;
int overlap;
int tile_size;
VipsForeignDzLayout layout;
VipsArea *background;
Layer *layer; /* x2 shrink pyr layer */
/* Count zoomify tiles we write.
*/
int tile_count;
};
typedef VipsForeignSaveClass VipsForeignSaveDzClass;
@ -148,11 +178,15 @@ pyramid_build( VipsForeignSaveDz *dz, Layer *above, int width, int height )
Layer *layer = VIPS_NEW( dz, Layer );
VipsRect strip;
int limit;
layer->dz = dz;
layer->width = width;
layer->height = height;
layer->tiles_across = ROUND_UP( width, dz->tile_size ) / dz->tile_size;
layer->tiles_down = ROUND_UP( height, dz->tile_size ) / dz->tile_size;
layer->image = NULL;
layer->strip = NULL;
layer->copy = NULL;
@ -204,8 +238,16 @@ pyramid_build( VipsForeignSaveDz *dz, Layer *above, int width, int height )
return( NULL );
}
if( width > 1 ||
height > 1 ) {
/* DeepZoom stops at 1x1 pixels, others when the image fits within a
* tile.
*/
if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_DZ )
limit = 1;
else
limit = dz->tile_size;
if( width > limit ||
height > limit ) {
/* Round up, so eg. a 5 pixel wide image becomes 3 a layer
* down.
*/
@ -219,24 +261,34 @@ pyramid_build( VipsForeignSaveDz *dz, Layer *above, int width, int height )
else
layer->n = 0;
#ifdef DEBUG
printf( "pyramid_build:\n" );
printf( "\tn = %d\n", layer->n );
printf( "\twidth = %d, height = %d\n", width, height );
printf( "\tXsize = %d, Ysize = %d\n",
layer->image->Xsize, layer->image->Ysize );
#endif
return( layer );
}
static int
pyramid_mkdir( VipsForeignSaveDz *dz )
{
Layer *layer;
char buf[PATH_MAX];
if( vips_existsf( "%s_files", dz->basename ) ) {
if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_DZ )
vips_snprintf( buf, PATH_MAX, "%s_files", dz->basename );
else
vips_snprintf( buf, PATH_MAX, "%s", dz->basename );
if( vips_existsf( "%s", buf ) ) {
vips_error( "dzsave",
_( "Directory \"%s_files\" exists" ), dz->basename );
_( "Directory \"%s\" exists" ), buf );
return( -1 );
}
if( vips_mkdirf( "%s_files", dz->basename ) )
if( vips_mkdirf( "%s", buf ) )
return( -1 );
for( layer = dz->layer; layer; layer = layer->below )
if( vips_mkdirf( "%s_files/%d", dz->basename, layer->n ) )
return( -1 );
return( 0 );
}
@ -246,15 +298,20 @@ write_dzi( VipsForeignSaveDz *dz )
{
FILE *fp;
char buf[PATH_MAX];
char *p;
vips_snprintf( buf, PATH_MAX, "%s.dzi", dz->basename );
if( !(fp = vips__file_open_write( buf, TRUE )) )
return( -1 );
vips_snprintf( buf, PATH_MAX, "%s", dz->suffix + 1 );
if( (p = (char *) vips__find_rightmost_brackets( buf )) )
*p = '\0';
fprintf( fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" );
fprintf( fp, "<Image "
"xmlns=\"http://schemas.microsoft.com/deepzoom/2008\"\n" );
fprintf( fp, " Format=\"%s\"\n", dz->suffix + 1 );
fprintf( fp, " Format=\"%s\"\n", buf );
fprintf( fp, " Overlap=\"%d\"\n", dz->overlap );
fprintf( fp, " TileSize=\"%d\"\n", dz->tile_size );
fprintf( fp, " >\n" );
@ -269,6 +326,49 @@ write_dzi( VipsForeignSaveDz *dz )
return( 0 );
}
static int
write_properties( VipsForeignSaveDz *dz )
{
FILE *fp;
char buf[PATH_MAX];
vips_snprintf( buf, PATH_MAX, "%s/ImageProperties.xml", dz->basename );
if( !(fp = vips__file_open_write( buf, TRUE )) )
return( -1 );
fprintf( fp, "<IMAGE_PROPERTIES "
"WIDTH=\"%d\" HEIGHT=\"%d\" NUMTILES=\"%d\" "
"NUMIMAGES=\"1\" VERSION=\"1.8\" TILESIZE=\"%d\" />\n",
dz->layer->width,
dz->layer->height,
dz->tile_count,
dz->tile_size );
fclose( fp );
return( 0 );
}
static int
write_blank( VipsForeignSaveDz *dz )
{
char buf[PATH_MAX];
VipsImage *black;
vips_snprintf( buf, PATH_MAX, "%s/blank.png", dz->basename );
if( vips_black( &black, dz->tile_size, dz->tile_size,
"bands", 3,
NULL ) )
return( -1 );
if( vips_image_write_to_file( black, buf ) ) {
g_object_unref( black );
return( -1 );
}
g_object_unref( black );
return( 0 );
}
/* Generate area @target in @to using pixels in @from. VIPS_CODING_LABQ only.
*/
static void
@ -481,6 +581,104 @@ strip_allocate( VipsThreadState *state, void *a, gboolean *stop )
return( 0 );
}
/* Create a tile name in the current layout.
*/
static int
tile_name( Layer *layer, char *buf, int x, int y )
{
VipsForeignSaveDz *dz = layer->dz;
char dirname[PATH_MAX];
char dirname2[PATH_MAX];
Layer *p;
int n;
/* We have to lock or there's a race between exists() and mkdir().
*/
g_mutex_lock( vips__global_lock );
switch( dz->layout ) {
case VIPS_FOREIGN_DZ_LAYOUT_DZ:
vips_snprintf( dirname, PATH_MAX, "%s_files/%d",
dz->basename, layer->n );
vips_snprintf( buf, PATH_MAX, "%s/%d_%d%s",
dirname, x, y, dz->suffix );
if( !vips_existsf( "%s", dirname ) &&
vips_mkdirf( "%s", dirname ) ) {
g_mutex_unlock( vips__global_lock );
return( -1 );
}
break;
case VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY:
/* We need to work out the tile number so we can calculate the
* directory to put this tile in.
*
* Tiles are numbered from 0 for the most-zoomed-out tile.
*/
n = 0;
/* Count all tiles in layers below this one.
*/
for( p = layer->below; p; p = p->below )
n += p->tiles_across * p->tiles_down;
/* And count tiles so far in this layer.
*/
n += y * layer->tiles_across + x;
vips_snprintf( dirname, PATH_MAX, "%s/TileGroup%d",
dz->basename, n / 256 );
vips_snprintf( buf, PATH_MAX, "%s/%d-%d-%d%s",
dirname, layer->n, x, y, dz->suffix );
/* Used at the end in ImageProperties.xml
*/
dz->tile_count += 1;
if( !vips_existsf( "%s", dirname ) &&
vips_mkdirf( "%s", dirname ) ) {
g_mutex_unlock( vips__global_lock );
return( -1 );
}
break;
case VIPS_FOREIGN_DZ_LAYOUT_GOOGLE:
vips_snprintf( dirname, PATH_MAX, "%s/%d",
dz->basename, layer->n );
vips_snprintf( dirname2, PATH_MAX, "%s/%d",
dirname, y );
vips_snprintf( buf, PATH_MAX, "%s/%d%s",
dirname2, x, dz->suffix );
if( !vips_existsf( "%s", dirname ) &&
vips_mkdirf( "%s", dirname ) ) {
g_mutex_unlock( vips__global_lock );
return( -1 );
}
if( !vips_existsf( "%s", dirname2 ) &&
vips_mkdirf( "%s", dirname2 ) ) {
g_mutex_unlock( vips__global_lock );
return( -1 );
}
break;
default:
g_assert( 0 );
g_mutex_unlock( vips__global_lock );
return( -1 );
}
g_mutex_unlock( vips__global_lock );
return( 0 );
}
static int
strip_work( VipsThreadState *state, void *a )
{
@ -488,29 +686,43 @@ strip_work( VipsThreadState *state, void *a )
Layer *layer = strip->layer;
VipsForeignSaveDz *dz = layer->dz;
VipsImage *extr;
char str[1000];
VipsBuf buf = VIPS_BUF_STATIC( str );
VipsImage *x;
char buf[PATH_MAX];
if( tile_name( layer, buf,
state->x / dz->tile_size, state->y / dz->tile_size ) )
return( -1 );
vips_object_sanity( VIPS_OBJECT( strip->image ) );
/* Extract relative to the strip top-left corner.
*/
if( vips_extract_area( state->im, &extr,
if( vips_extract_area( strip->image, &x,
state->pos.left, 0,
state->pos.width, state->pos.height, NULL ) )
return( -1 );
vips_buf_appendf( &buf, "%s_files/%d/%d_%d%s",
dz->basename, layer->n,
state->x / dz->tile_size,
state->y / dz->tile_size,
dz->suffix );
/* Google tiles need to be padded up to tilesize.
*/
if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_GOOGLE ) {
VipsImage *z;
if( vips_image_write_to_file( extr, vips_buf_all( &buf ) ) ) {
g_object_unref( extr );
return( -1 );
if( vips_embed( x, &z, 0, 0, dz->tile_size, dz->tile_size,
"background", dz->background,
NULL ) ) {
g_object_unref( x );
return( -1 );
}
g_object_unref( x );
x = z;
}
g_object_unref( extr );
if( vips_image_write_to_file( x, buf ) ) {
g_object_unref( x );
return( -1 );
}
g_object_unref( x );
return( 0 );
}
@ -522,6 +734,10 @@ strip_save( Layer *layer )
{
Strip strip;
#ifdef DEBUG
printf( "strip_save: n = %d, y = %d\n", layer->n, layer->y );
#endif /*DEBUG*/
strip_init( &strip, layer );
if( vips_threadpool_run( strip.image,
vips_thread_state_new, strip_allocate, strip_work, NULL,
@ -535,9 +751,9 @@ strip_save( Layer *layer )
}
/* A strip has filled, but the rightmost column and the bottom-most row may
* not have if we've rounded the size up!
* not have been if we've rounded the size up.
*
* Fill them, if necessary, by copyping the previous row/column.
* Fill them, if necessary, by copying the previous row/column.
*/
static void
layer_generate_extras( Layer *layer )
@ -729,7 +945,13 @@ pyramid_strip( VipsRegion *region, VipsRect *area, void *a )
VipsForeignSaveDz *dz = (VipsForeignSaveDz *) a;
Layer *layer = dz->layer;
#ifdef DEBUG
printf( "pyramid_strip: strip at %d, height %d\n",
area->top, area->height );
#endif/*DEBUG*/
for(;;) {
VipsRect *to = &layer->strip->valid;
VipsRect target;
/* The bit of strip that needs filling.
@ -737,9 +959,8 @@ pyramid_strip( VipsRegion *region, VipsRect *area, void *a )
target.left = 0;
target.top = layer->write_y;
target.width = layer->image->Xsize;
target.height = layer->strip->valid.height;
vips_rect_intersectrect( &target,
&layer->strip->valid, &target );
target.height = to->height;
vips_rect_intersectrect( &target, to, &target );
/* Clip against what we have available.
*/
@ -761,12 +982,16 @@ pyramid_strip( VipsRegion *region, VipsRect *area, void *a )
layer->write_y += target.height;
/* If we've filled the strip, let it know.
/* If we've filled the strip of the layer below, let it know.
* We can either fill the region, if it's somewhere half-way
* down the image, or, if it's at the bottom, get to the last
* writeable line.
*/
if( layer->write_y ==
VIPS_RECT_BOTTOM( &layer->strip->valid ) &&
strip_arrived( layer ) )
return( -1 );
if( layer->write_y == VIPS_RECT_BOTTOM( to ) ||
layer->write_y == layer->height ) {
if( strip_arrived( layer ) )
return( -1 );
}
}
return( 0 );
@ -778,6 +1003,16 @@ vips_foreign_save_dz_build( VipsObject *object )
VipsForeignSave *save = (VipsForeignSave *) object;
VipsForeignSaveDz *dz = (VipsForeignSaveDz *) object;
/* Google and zoomify default to zero overlap, ".jpg".
*/
if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY ||
dz->layout == VIPS_FOREIGN_DZ_LAYOUT_GOOGLE ) {
if( !vips_object_get_argument_assigned( object, "overlap" ) )
dz->overlap = 0;
if( !vips_object_get_argument_assigned( object, "suffix" ) )
VIPS_SETSTR( dz->suffix, ".jpg" );
}
if( VIPS_OBJECT_CLASS( vips_foreign_save_dz_parent_class )->
build( object ) )
return( -1 );
@ -795,13 +1030,33 @@ vips_foreign_save_dz_build( VipsObject *object )
if( !(dz->layer = pyramid_build( dz,
NULL, save->ready->Xsize, save->ready->Ysize )) )
return( -1 );
if( pyramid_mkdir( dz ) ||
write_dzi( dz ) )
if( pyramid_mkdir( dz ) )
return( -1 );
if( vips_sink_disc( save->ready, pyramid_strip, dz ) )
return( -1 );
switch( dz->layout ) {
case VIPS_FOREIGN_DZ_LAYOUT_DZ:
if( write_dzi( dz ) )
return( -1 );
break;
case VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY:
if( write_properties( dz ) )
return( -1 );
break;
case VIPS_FOREIGN_DZ_LAYOUT_GOOGLE:
if( write_blank( dz ) )
return( -1 );
break;
default:
g_assert( 0 );
return( -1 );
}
return( 0 );
}
@ -854,6 +1109,14 @@ vips_foreign_save_dz_class_init( VipsForeignSaveDzClass *class )
G_STRUCT_OFFSET( VipsForeignSaveDz, basename ),
NULL );
VIPS_ARG_ENUM( class, "layout", 8,
_( "Layout" ),
_( "Directory layout" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignSaveDz, layout ),
VIPS_TYPE_FOREIGN_DZ_LAYOUT,
VIPS_FOREIGN_DZ_LAYOUT_DZ );
VIPS_ARG_STRING( class, "suffix", 9,
_( "suffix" ),
_( "Filename suffix for tiles" ),
@ -875,6 +1138,13 @@ vips_foreign_save_dz_class_init( VipsForeignSaveDzClass *class )
G_STRUCT_OFFSET( VipsForeignSaveDz, tile_size ),
1, 1024, 256 );
VIPS_ARG_BOXED( class, "background", 12,
_( "Background" ),
_( "Colour for background pixels" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignSaveDz, background ),
VIPS_TYPE_ARRAY_DOUBLE );
/* How annoying. We stupidly had these in earlier versions.
*/
@ -905,8 +1175,13 @@ static void
vips_foreign_save_dz_init( VipsForeignSaveDz *dz )
{
VIPS_SETSTR( dz->suffix, ".jpeg" );
dz->layout = VIPS_FOREIGN_DZ_LAYOUT_DZ;
dz->overlap = 1;
dz->tile_size = 256;
dz->tile_count = 0;
dz->background =
vips_area_new_array( G_TYPE_DOUBLE, sizeof( double ), 1 );
((double *) (dz->background->data))[0] = 255;
}
/**
@ -917,23 +1192,31 @@ vips_foreign_save_dz_init( VipsForeignSaveDz *dz )
*
* Optional arguments:
*
* @suffix: suffix for tile tiles (default ".jpg")
* @overlap; set tile overlap (default 1)
* @tile_size; set tile size (default 256)
* @layout; directory layout convention
* @suffix: suffix for tile tiles
* @overlap; set tile overlap
* @tile_size; set tile size
* @background: background colour
*
* Save an image to a deep zoom - style directory tree. A directory called
* Save an image as a set of tiles at various resolutions. By default dzsave
* uses DeepZoom layout -- use @layout to pick other conventions.
*
* In DeepZoom layout a directory called
* "@basename_files" is created to hold the tiles, and an XML file called
* "@basename.dzi" is written with the image metadata,
* "@basename.dzi" is written with the image metadata,
*
* The image is shrunk in a series of x2 reductions until it fits within a
* single pixel. Each layer is written out to a separate subdirectory of
* @dirname_files, with directory "0" holding the smallest, single pixel image.
*
* Each tile is written as a separate file named as "@x_@y@suffix", where @x
* and @y are the tile coordinates, with (0, 0) as the top-left tile.
* In Zoomify and Google layout, a directory called @basename is created to
* hold the tile structure.
*
* You can set @suffix to something like ".jpg[Q=85]" to set the tile write
* options.
*
* In Google layout mode, edge tiles are expanded to @tile_size by @tile_size
* pixels. Normally they are filled with white, but you can set another colour
* with @background.
*
* You can set the size and overlap of tiles with @tile_size and @overlap.
* They default to the correct settings for the selected @layout.
*
* See also: vips_tiffsave().
*

View File

@ -34,6 +34,9 @@
* 20/9/12
* - update openslide_open error handling for 3.3.0 semantics
* - switch from deprecated _layer_ functions
* 11/10/12
* - look for tile-width and tile-height properties
* - use threaded tile cache
*/
/*
@ -79,14 +82,9 @@
#include "openslide2vips.h"
/* We run our own tile cache. The OpenSlide one can't always keep enough for a
* complete lines of pixels.
*/
#define TILE_WIDTH (256)
#define TILE_HEIGHT (256)
typedef struct {
openslide_t *osr;
char *associated;
/* Only valid if associated == NULL.
@ -94,6 +92,11 @@ typedef struct {
int32_t level;
double downsample;
uint32_t bg;
/* Try to get these from openslide properties.
*/
int tile_width;
int tile_height;
} ReadSlide;
int
@ -117,6 +120,7 @@ vips__openslide_isslide( const char *filename )
if( vendor &&
strcmp( vendor, "generic-tiff" ) != 0 )
ok = 1;
openslide_close( osr );
}
@ -173,6 +177,11 @@ readslide_new( const char *filename, VipsImage *out,
rslide->level = level;
rslide->associated = g_strdup( associated );
/* Non-crazy defaults, override below if we can.
*/
rslide->tile_width = 256;
rslide->tile_height = 256;
rslide->osr = openslide_open( filename );
if( rslide->osr == NULL ) {
vips_error( "openslide2vips",
@ -206,12 +215,29 @@ readslide_new( const char *filename, VipsImage *out,
vips_demand_hint( out, VIPS_DEMAND_STYLE_THINSTRIP, NULL );
}
else {
char buf[256];
const char *value;
openslide_get_level_dimensions( rslide->osr,
level, &w, &h );
rslide->downsample = openslide_get_level_downsample(
rslide->osr, level );
vips_image_set_int( out, "slide-level", level );
vips_demand_hint( out, VIPS_DEMAND_STYLE_SMALLTILE, NULL );
/* Try to get tile width/height. An undocumented, experimental
* feature.
*/
vips_snprintf( buf, 256,
"openslide.level[%d].tile-width", level );
if( (value = openslide_get_property_value( rslide->osr, buf )) )
rslide->tile_width = atoi( value );
vips_snprintf( buf, 256,
"openslide.level[%d].tile-height", level );
if( (value = openslide_get_property_value( rslide->osr, buf )) )
rslide->tile_height = atoi( value );
if( value )
VIPS_DEBUG_MSG( "readslide_new: found tile-size\n" );
}
rslide->bg = 0xffffff;
@ -261,7 +287,7 @@ vips__openslide_read_header( const char *filename, VipsImage *out,
static int
vips__openslide_generate( VipsRegion *out,
void *seq, void *_rslide, void *unused, gboolean *stop )
void *_seq, void *_rslide, void *unused, gboolean *stop )
{
ReadSlide *rslide = _rslide;
uint32_t bg = rslide->bg;
@ -275,13 +301,18 @@ vips__openslide_generate( VipsRegion *out,
VIPS_DEBUG_MSG( "vips__openslide_generate: %dx%d @ %dx%d\n",
r->width, r->height, r->left, r->top );
/* We're inside a cache, so requests should always be TILE_WIDTH by
* TILE_HEIGHT pixels and on a tile boundary.
/* We're inside a cache, so requests should always be
* tile_width by tile_height pixels and on a tile boundary.
*/
g_assert( (r->left % TILE_WIDTH) == 0 );
g_assert( (r->height % TILE_HEIGHT) == 0 );
g_assert( r->width <= TILE_WIDTH );
g_assert( r->height <= TILE_HEIGHT );
g_assert( (r->left % rslide->tile_width) == 0 );
g_assert( (r->top % rslide->tile_height) == 0 );
g_assert( r->width <= rslide->tile_width );
g_assert( r->height <= rslide->tile_height );
/* The memory on the region should be contiguous for our ARGB->RGBA
* loop below.
*/
g_assert( VIPS_REGION_LSKIP( out ) == r->width * 4 );
openslide_read_region( rslide->osr,
buf,
@ -298,12 +329,14 @@ vips__openslide_generate( VipsRegion *out,
return( -1 );
}
/* Convert from ARGB to RGBA and undo premultiplication.
/* Convert from ARGB to RGBA and undo premultiplication. Since we are
* inside a cache, we know buf must be continuous.
*/
for( i = 0; i < n; i++ ) {
uint32_t x = buf[i];
uint32_t *p = buf + i;
uint32_t x = *p;
uint8_t a = x >> 24;
VipsPel *out = (VipsPel *) (buf + i);
VipsPel *out = (VipsPel *) p;
if( a != 0 ) {
out[0] = 255 * ((x >> 16) & 255) / a;
@ -334,10 +367,6 @@ vips__openslide_read( const char *filename, VipsImage *out, int level )
VIPS_DEBUG_MSG( "vips__openslide_read: %s %d\n",
filename, level );
/* Tile cache: keep enough for two complete rows of tiles. OpenSlide
* has its own tile cache, but it's not large enough for a complete
* scan line.
*/
raw = vips_image_new();
vips_object_local( out, raw );
@ -352,9 +381,11 @@ vips__openslide_read( const char *filename, VipsImage *out, int level )
* 50%.
*/
if( vips_tilecache( raw, &t,
"tile_width", TILE_WIDTH,
"tile_height", TILE_WIDTH,
"max_tiles", (int) (1.5 * (1 + raw->Xsize / TILE_WIDTH)),
"tile_width", rslide->tile_width,
"tile_height", rslide->tile_height,
"max_tiles",
(int) (1.5 * (1 + raw->Xsize / rslide->tile_width)),
"threaded", TRUE,
NULL ) )
return( -1 );
if( vips_image_write( t, out ) ) {

View File

@ -44,6 +44,7 @@ extern "C" {
* @VIPS_EXTEND_REPEAT: repeat the whole image
* @VIPS_EXTEND_MIRROR: mirror the whole image
* @VIPS_EXTEND_WHITE: extend with white (all bits set) pixels
* @VIPS_EXTEND_BACKGROUND: extend with colour from the @background property
*
* See vips_embed(), vips_conv(), vips_affine() and so on.
*
@ -61,6 +62,8 @@ extern "C" {
* edges
*
* #VIPS_EXTEND_WHITE --- new pixels are white, ie. all bits are set
* #VIPS_EXTEND_BACKGROUND --- colour set from the @background property
*
* We have to specify the exact value of each enum member since we have to
* keep these frozen for back compat with vips7.
@ -68,12 +71,13 @@ extern "C" {
* See also: vips_embed().
*/
typedef enum {
VIPS_EXTEND_BLACK = 0,
VIPS_EXTEND_COPY = 1,
VIPS_EXTEND_REPEAT = 2,
VIPS_EXTEND_MIRROR = 3,
VIPS_EXTEND_WHITE = 4,
VIPS_EXTEND_LAST = 5
VIPS_EXTEND_BLACK,
VIPS_EXTEND_COPY,
VIPS_EXTEND_REPEAT,
VIPS_EXTEND_MIRROR,
VIPS_EXTEND_WHITE,
VIPS_EXTEND_BACKGROUND,
VIPS_EXTEND_LAST
} VipsExtend;
/**

View File

@ -17,6 +17,8 @@ GType vips_foreign_tiff_predictor_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_FOREIGN_TIFF_PREDICTOR (vips_foreign_tiff_predictor_get_type())
GType vips_foreign_tiff_resunit_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_FOREIGN_TIFF_RESUNIT (vips_foreign_tiff_resunit_get_type())
GType vips_foreign_dz_layout_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_FOREIGN_DZ_LAYOUT (vips_foreign_dz_layout_get_type())
/* enumerations from "../../../libvips/include/vips/arithmetic.h" */
GType vips_operation_math_get_type (void) G_GNUC_CONST;
#define VIPS_TYPE_OPERATION_MATH (vips_operation_math_get_type())

View File

@ -424,6 +424,21 @@ int vips_radload( const char *filename, VipsImage **out, ... )
int vips_radsave( VipsImage *in, const char *filename, ... )
__attribute__((sentinel));
/**
* VipsForeignDzLayout:
* @VIPS_FOREIGN_DZ_LAYOUT_DZ: use DeepZoom directory layout
* @VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY: use Zoomify directory layout
* @VIPS_FOREIGN_DZ_LAYOUT_GOOGLE: use Google maps directory layout
*
* Use inches or centimeters as the resolution unit for a tiff file.
*/
typedef enum {
VIPS_FOREIGN_DZ_LAYOUT_DZ,
VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY,
VIPS_FOREIGN_DZ_LAYOUT_GOOGLE,
VIPS_FOREIGN_DZ_LAYOUT_LAST
} VipsForeignDzLayout;
int vips_dzsave( VipsImage *in, const char *basename, ... )
__attribute__((sentinel));

View File

@ -105,6 +105,25 @@ vips_foreign_tiff_resunit_get_type( void )
return( etype );
}
GType
vips_foreign_dz_layout_get_type( void )
{
static GType etype = 0;
if( etype == 0 ) {
static const GEnumValue values[] = {
{VIPS_FOREIGN_DZ_LAYOUT_DZ, "VIPS_FOREIGN_DZ_LAYOUT_DZ", "dz"},
{VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY, "VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY", "zoomify"},
{VIPS_FOREIGN_DZ_LAYOUT_GOOGLE, "VIPS_FOREIGN_DZ_LAYOUT_GOOGLE", "google"},
{VIPS_FOREIGN_DZ_LAYOUT_LAST, "VIPS_FOREIGN_DZ_LAYOUT_LAST", "last"},
{0, NULL, NULL}
};
etype = g_enum_register_static( "VipsForeignDzLayout", values );
}
return( etype );
}
/* enumerations from "../../libvips/include/vips/conversion.h" */
GType
vips_extend_get_type( void )
@ -118,6 +137,7 @@ vips_extend_get_type( void )
{VIPS_EXTEND_REPEAT, "VIPS_EXTEND_REPEAT", "repeat"},
{VIPS_EXTEND_MIRROR, "VIPS_EXTEND_MIRROR", "mirror"},
{VIPS_EXTEND_WHITE, "VIPS_EXTEND_WHITE", "white"},
{VIPS_EXTEND_BACKGROUND, "VIPS_EXTEND_BACKGROUND", "background"},
{VIPS_EXTEND_LAST, "VIPS_EXTEND_LAST", "last"},
{0, NULL, NULL}
};

View File

@ -122,7 +122,7 @@
*/
#define MAX_IMAGES (1000)
/* Make a upstream/downstream link. upstream is one of downstream's inputs.
/* Make an upstream/downstream link. upstream is one of downstream's inputs.
*/
static void
vips__link_make( VipsImage *image_up, VipsImage *image_down )
@ -183,10 +183,10 @@ vips__link_break_all( VipsImage *image )
vips_slist_map2( image->downstream,
(VipsSListMap2Fn) vips__link_break_rev, image, NULL );
g_mutex_unlock( vips__global_lock );
g_assert( !image->upstream );
g_assert( !image->downstream );
g_mutex_unlock( vips__global_lock );
}
typedef struct _LinkMap {
@ -264,6 +264,7 @@ vips__link_map( VipsImage *image, gboolean upstream,
g_mutex_unlock( vips__global_lock );
result = vips_slist_map2( images, fn, a, b );
for( p = images; p; p = p->next )
g_object_unref( p->data );
g_slist_free( images );

View File

@ -465,6 +465,10 @@ vips_image_sanity( VipsObject *object, VipsBuf *buf )
vips_buf_appends( buf, "bad resolution\n" );
}
/* Must lock around inter-image links.
*/
g_mutex_lock( vips__global_lock );
if( vips_slist_map2( image->upstream,
(VipsSListMap2Fn) vips_image_sanity_upstream, image, NULL ) )
vips_buf_appends( buf, "upstream broken\n" );
@ -472,6 +476,8 @@ vips_image_sanity( VipsObject *object, VipsBuf *buf )
(VipsSListMap2Fn) vips_image_sanity_downstream, image, NULL ) )
vips_buf_appends( buf, "downstream broken\n" );
g_mutex_unlock( vips__global_lock );
VIPS_OBJECT_CLASS( vips_image_parent_class )->sanity( object, buf );
}

View File

@ -9,7 +9,11 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?"
"product=glib&keywords=I18N+L10N&component=general\n"
<<<<<<< HEAD
"POT-Creation-Date: 2012-09-08 14:01+0100\n"
=======
"POT-Creation-Date: 2012-10-01 14:17+0100\n"
>>>>>>> origin/master
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -395,7 +399,11 @@ msgstr ""
#: ../libvips/conversion/extract.c:197 ../libvips/conversion/extract.c:356
#: ../libvips/conversion/bandjoin.c:171 ../libvips/conversion/copy.c:321
#: ../libvips/conversion/rot.c:359 ../libvips/conversion/replicate.c:196
<<<<<<< HEAD
#: ../libvips/conversion/tilecache.c:355 ../libvips/conversion/embed.c:523
=======
#: ../libvips/conversion/tilecache.c:357 ../libvips/conversion/embed.c:523
>>>>>>> origin/master
#: ../libvips/conversion/cache.c:100 ../libvips/conversion/recomb.c:203
#: ../libvips/conversion/sequential.c:273 ../libvips/foreign/foreign.c:1413
#: ../libvips/resample/resample.c:89
@ -567,7 +575,11 @@ msgstr ""
#: ../libvips/conversion/cast.c:480 ../libvips/conversion/flatten.c:376
#: ../libvips/conversion/extract.c:198 ../libvips/conversion/extract.c:357
#: ../libvips/conversion/copy.c:322 ../libvips/conversion/rot.c:360
<<<<<<< HEAD
#: ../libvips/conversion/replicate.c:197 ../libvips/conversion/tilecache.c:356
=======
#: ../libvips/conversion/replicate.c:197 ../libvips/conversion/tilecache.c:358
>>>>>>> origin/master
#: ../libvips/conversion/embed.c:524 ../libvips/conversion/cache.c:101
#: ../libvips/conversion/sequential.c:274
msgid "Input image"
@ -1104,6 +1116,7 @@ msgstr ""
msgid "Top edge of sub in main"
msgstr ""
<<<<<<< HEAD
#: ../libvips/conversion/tilecache.c:351 ../libvips/conversion/cache.c:96
msgid "cache an image"
msgstr ""
@ -1151,6 +1164,55 @@ msgid "Maximum number of tiles to cache"
msgstr ""
#: ../libvips/conversion/tilecache.c:699
=======
#: ../libvips/conversion/tilecache.c:353 ../libvips/conversion/cache.c:96
msgid "cache an image"
msgstr ""
#: ../libvips/conversion/tilecache.c:363 ../libvips/conversion/cache.c:113
#: ../libvips/conversion/sequential.c:286 ../libvips/foreign/tiffsave.c:222
#: ../libvips/foreign/dzsave.c:920
msgid "Tile height"
msgstr ""
#: ../libvips/conversion/tilecache.c:364 ../libvips/conversion/cache.c:114
#: ../libvips/conversion/sequential.c:287 ../libvips/foreign/tiffsave.c:223
#: ../libvips/foreign/dzsave.c:921
msgid "Tile height in pixels"
msgstr ""
#: ../libvips/conversion/tilecache.c:370
msgid "Strategy"
msgstr ""
#: ../libvips/conversion/tilecache.c:371
msgid "Expected access pattern"
msgstr ""
#: ../libvips/conversion/tilecache.c:540
msgid "cache an image as a set of tiles"
msgstr ""
#: ../libvips/conversion/tilecache.c:544 ../libvips/conversion/cache.c:106
#: ../libvips/foreign/tiffsave.c:215 ../libvips/foreign/dzsave.c:913
msgid "Tile width"
msgstr ""
#: ../libvips/conversion/tilecache.c:545 ../libvips/conversion/cache.c:107
#: ../libvips/foreign/tiffsave.c:216 ../libvips/foreign/dzsave.c:914
msgid "Tile width in pixels"
msgstr ""
#: ../libvips/conversion/tilecache.c:551 ../libvips/conversion/cache.c:120
msgid "Max tiles"
msgstr ""
#: ../libvips/conversion/tilecache.c:552 ../libvips/conversion/cache.c:121
msgid "Maximum number of tiles to cache"
msgstr ""
#: ../libvips/conversion/tilecache.c:706
>>>>>>> origin/master
msgid "cache an image as a set of lines"
msgstr ""
@ -1527,48 +1589,48 @@ msgstr ""
msgid "Write a bigtiff image"
msgstr ""
#: ../libvips/foreign/dzsave.c:232
#: ../libvips/foreign/dzsave.c:243
#, c-format
msgid "Directory \"%s_files\" exists"
msgstr ""
#: ../libvips/foreign/dzsave.c:788
#: ../libvips/foreign/dzsave.c:812
msgid "overlap must be less than tile width and height"
msgstr ""
#: ../libvips/foreign/dzsave.c:841
#: ../libvips/foreign/dzsave.c:865
msgid "save image to deep zoom format"
msgstr ""
#: ../libvips/foreign/dzsave.c:851 ../libvips/foreign/dzsave.c:882
#: ../libvips/foreign/dzsave.c:875 ../libvips/foreign/dzsave.c:906
msgid "Base name"
msgstr ""
#: ../libvips/foreign/dzsave.c:852 ../libvips/foreign/dzsave.c:883
#: ../libvips/foreign/dzsave.c:876 ../libvips/foreign/dzsave.c:907
msgid "Base name to save to"
msgstr ""
#: ../libvips/foreign/dzsave.c:858
#: ../libvips/foreign/dzsave.c:882
msgid "suffix"
msgstr ""
#: ../libvips/foreign/dzsave.c:859
#: ../libvips/foreign/dzsave.c:883
msgid "Filename suffix for tiles"
msgstr ""
#: ../libvips/foreign/dzsave.c:865
#: ../libvips/foreign/dzsave.c:889
msgid "Overlap"
msgstr ""
#: ../libvips/foreign/dzsave.c:866
#: ../libvips/foreign/dzsave.c:890
msgid "Tile overlap in pixels"
msgstr ""
#: ../libvips/foreign/dzsave.c:872
#: ../libvips/foreign/dzsave.c:896
msgid "Tile size"
msgstr ""
#: ../libvips/foreign/dzsave.c:873
#: ../libvips/foreign/dzsave.c:897
msgid "Tile size in pixels"
msgstr ""

View File

@ -3,7 +3,7 @@ exec_prefix=@exec_prefix@
libdir=@libdir@
includedir=@includedir@
Name: vips-@VIPS_MAJOR_VERSION@.@VIPS_MINOR_VERSION@
Name: vips
Description: Image processing library
Version: @VERSION@
Requires: @PACKAGES_USED@

View File

@ -3,8 +3,8 @@ exec_prefix=@exec_prefix@
libdir=@libdir@
includedir=@includedir@
Name: vipsCC-@VIPS_MAJOR_VERSION@.@VIPS_MINOR_VERSION@
Name: vipsCC
Description: C++ API for vips image processing library
Version: @VERSION@
Requires: vips-@VIPS_MAJOR_VERSION@.@VIPS_MINOR_VERSION@ = @VERSION@
Requires: vips = @VERSION@
Libs: -L${libdir} -lvipsCC