/* sizealike by expanding in just one dimension and copying the final element. */ static int vips__hist_sizealike_vec( VipsImage **in, VipsImage **out, int n ) { int i; int max_size; g_assert( n >= 1 ); max_size = VIPS_MAX( in[0]->Xsize, in[0]->Ysize ); for( i = 1; i < n; i++ ) max_size = VIPS_MAX( max_size, VIPS_MAX( in[0]->Xsize, in[0]->Ysize ) ); for( i = 0; i < n; i++ ) if( in[i]->Ysize == 1 ) { if( vips_embed( in[i], &out[i], 0, 0, max_size, 1, "extend", VIPS_EXTEND_COPY, NULL ) ) return( -1 ); } else { if( vips_embed( in[i], &out[i], 0, 0, 1, max_size, "extend", VIPS_EXTEND_COPY, NULL ) ) return( -1 ); } return( 0 ); }
static int vips_unary_const_build( VipsObject *object ) { VipsArithmetic *arithmetic = VIPS_ARITHMETIC( object ); VipsUnary *unary = (VipsUnary *) object; VipsUnaryConst *uconst = (VipsUnaryConst *) object; /* If we have a three-element vector we need to bandup the image to * match. */ uconst->n = 1; if( uconst->c ) uconst->n = VIPS_MAX( uconst->n, uconst->c->n ); if( unary->in ) uconst->n = VIPS_MAX( uconst->n, unary->in->Bands ); arithmetic->base_bands = uconst->n; if( unary->in && uconst->c ) { if( vips_check_vector( "VipsRelationalConst", uconst->c->n, unary->in ) ) return( -1 ); } /* Some operations need the vector in the input type (eg. * im_equal_vec() where the output type is always uchar and is useless * for comparisons), some need it in the output type (eg. * im_andimage_vec() where we want to get the double to an int so we * can do bitwise-and without having to cast for each pixel), some * need a fixed type (eg. im_powtra_vec(), where we want to keep it as * double). * * Therefore pass in the desired vector type as a param. */ if( uconst->c ) uconst->c_ready = make_pixel( (VipsObject *) uconst, uconst->n, uconst->const_format, uconst->c->n, (double *) uconst->c->data ); if( VIPS_OBJECT_CLASS( vips_unary_const_parent_class )-> build( object ) ) return( -1 ); return( 0 ); }
int vips__sizealike_vec( VipsImage **in, VipsImage **out, int n ) { int i; int width_max; int height_max; g_assert( n >= 1 ); width_max = in[0]->Xsize; height_max = in[0]->Ysize; for( i = 1; i < n; i++ ) { width_max = VIPS_MAX( width_max, in[i]->Xsize ); height_max = VIPS_MAX( height_max, in[i]->Ysize ); } for( i = 0; i < n; i++ ) if( vips_embed( in[i], &out[i], 0, 0, width_max, height_max, NULL ) ) return( -1 ); return( 0 ); }
/* base_bands is the default minimum. * * Handy for example, if you have VipsLinear with * a 3-element vector of constants and a 1-band input image, you need to cast * the image up to three bands. */ int vips__bandalike_vec( const char *domain, VipsImage **in, VipsImage **out, int n, int base_bands ) { int i; int max_bands; g_assert( n >= 1 ); max_bands = base_bands; for( i = 0; i < n; i++ ) max_bands = VIPS_MAX( max_bands, in[i]->Bands ); for( i = 0; i < n; i++ ) if( vips__bandup( domain, in[i], &out[i], max_bands ) ) return( -1 ); return( 0 ); }
static void write_grow( Write *write, size_t grow_len ) { size_t new_len = write->len + grow_len; if( new_len > write->alloc ) { size_t proposed_alloc = (16 + write->alloc) * 3 / 2; write->alloc = VIPS_MAX( proposed_alloc, new_len ); /* Our result mujst be freedd with g_free(), so it's OK to use * g_realloc(). */ write->buf = g_realloc( write->buf, write->alloc ); VIPS_DEBUG_MSG( "write_buf_grow: grown to %zd bytes\n", write->alloc ); } }
static void write_buf_grow( WriteBuf *wbuf, size_t grow_len ) { size_t new_len = wbuf->len + grow_len; if( new_len > wbuf->alloc ) { size_t proposed_alloc = (16 + wbuf->alloc) * 3 / 2; wbuf->alloc = VIPS_MAX( proposed_alloc, new_len ); /* There's no vips_realloc(), so we call g_realloc() directly. * This is safe, since vips_malloc() / vips_free() are wrappers * over g_malloc() / g_free(). * * FIXME: add vips_realloc(). */ wbuf->buf = g_realloc( wbuf->buf, wbuf->alloc ); VIPS_DEBUG_MSG( "write_buf_grow: grown to %zd bytes\n", wbuf->alloc ); } }
/* Calculate the shrink factors. * * We shrink in two stages: first, a shrink with a block average. This can * only accurately shrink by integer factors. We then do a second shrink with * a supplied interpolator to get the exact size we want. */ static int calculate_shrink( int width, int height, double *residual ) { /* Calculate the horizontal and vertical shrink we'd need to fit the * image to the bounding box, and pick the biggest. * * In crop mode we aim to fill the bounding box, so we must use the * smaller axis. */ double horizontal = (double) width / thumbnail_width; double vertical = (double) height / thumbnail_height; double factor = crop_image ? VIPS_MIN( horizontal, vertical ) : VIPS_MAX( horizontal, vertical ); /* If the shrink factor is <= 1.0, we need to zoom rather than shrink. * Just set the factor to 1 in this case. */ double factor2 = factor < 1.0 ? 1.0 : factor; /* Int component of shrink. */ int shrink = floor( factor2 ); if( residual ) { /* Width after int shrink. */ int iwidth = width / shrink; /* Therefore residual scale factor is. */ *residual = (width / factor) / iwidth; } return( shrink ); }
/* Zoom a VipsRegion. */ static int vips_zoom_gen( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop ) { VipsRegion *ir = (VipsRegion *) seq; VipsZoom *zoom = (VipsZoom *) b; /* Output area we are building. */ const VipsRect *r = &or->valid; const int ri = VIPS_RECT_RIGHT( r ); const int bo = VIPS_RECT_BOTTOM(r); VipsRect s; int left, right, top, bottom; int width, height; /* Area of input we need. We have to round out, as we may have * part-pixels all around the edges. */ left = ROUND_DOWN( r->left, zoom->xfac ); right = ROUND_UP( ri, zoom->xfac ); top = ROUND_DOWN( r->top, zoom->yfac ); bottom = ROUND_UP( bo, zoom->yfac ); width = right - left; height = bottom - top; s.left = left / zoom->xfac; s.top = top / zoom->yfac; s.width = width / zoom->xfac; s.height = height / zoom->yfac; if( vips_region_prepare( ir, &s ) ) return( -1 ); /* Find the part of the output (if any) which uses only whole pels. */ left = ROUND_UP( r->left, zoom->xfac ); right = ROUND_DOWN( ri, zoom->xfac ); top = ROUND_UP( r->top, zoom->yfac ); bottom = ROUND_DOWN( bo, zoom->yfac ); width = right - left; height = bottom - top; /* Stage 1: we just paint the whole pels in the centre of the region. * As we know they are not clipped, we can do it quickly. */ if( width > 0 && height > 0 ) vips_zoom_paint_whole( or, ir, zoom, left, right, top, bottom ); /* Just fractional pixels left. Paint in the top, left, right and * bottom parts. */ if( top - r->top > 0 ) /* Some top pixels. */ vips_zoom_paint_part( or, ir, zoom, r->left, ri, r->top, VIPS_MIN( top, bo ) ); if( left - r->left > 0 && height > 0 ) /* Left pixels. */ vips_zoom_paint_part( or, ir, zoom, r->left, VIPS_MIN( left, ri ), top, bottom ); if( ri - right > 0 && height > 0 ) /* Right pixels. */ vips_zoom_paint_part( or, ir, zoom, VIPS_MAX( right, r->left ), ri, top, bottom ); if( bo - bottom > 0 && height >= 0 ) /* Bottom pixels. */ vips_zoom_paint_part( or, ir, zoom, r->left, ri, VIPS_MAX( bottom, r->top ), bo ); return( 0 ); }
static int vips_resize_build( VipsObject *object ) { VipsResample *resample = VIPS_RESAMPLE( object ); VipsResize *resize = (VipsResize *) object; VipsImage **t = (VipsImage **) vips_object_local_array( object, 7 ); VipsImage *in; int window_size; int int_shrink; int int_shrink_width; double residual; double sigma; if( VIPS_OBJECT_CLASS( vips_resize_parent_class )->build( object ) ) return( -1 ); if( !vips_object_argument_isset( object, "interpolate" ) ) { VipsInterpolate *interpolate; char *nick; if( vips_type_find( "VipsInterpolate", "bicubic" ) ) nick = "bicubic"; else nick = "bilinear"; interpolate = vips_interpolate_new( nick ); g_object_set( object, "interpolate", interpolate, NULL ); VIPS_UNREF( interpolate ); } in = resample->in; window_size = resize->interpolate ? vips_interpolate_get_window_size( resize->interpolate ) : 2; /* If the factor is > 1.0, we need to zoom rather than shrink. * Just set the int part to 1 in this case. */ int_shrink = resize->scale > 1.0 ? 1 : floor( 1.0 / resize->scale ); /* We want to shrink by less for interpolators with larger windows. */ int_shrink = VIPS_MAX( 1, int_shrink / VIPS_MAX( 1, window_size / 2 ) ); /* Size after int shrink. */ int_shrink_width = in->Xsize / int_shrink; /* Therefore residual scale factor is. */ residual = (in->Xsize * resize->scale) / int_shrink_width; /* A copy for enlarge resize. */ if( vips_shrink( in, &t[0], int_shrink, int_shrink, NULL ) ) return( -1 ); in = t[0]; /* We want to make sure we read the image sequentially. * However, the convolution we may be doing later will force us * into SMALLTILE or maybe FATSTRIP mode and that will break * sequentiality. * * So ... read into a cache where tiles are scanlines, and make sure * we keep enough scanlines to be able to serve a line of tiles. * * We use a threaded tilecache to avoid a deadlock: suppose thread1, * evaluating the top block of the output, is delayed, and thread2, * evaluating the second block, gets here first (this can happen on * a heavily-loaded system). * * With an unthreaded tilecache (as we had before), thread2 will get * the cache lock and start evaling the second block of the shrink. * When it reaches the png reader it will stall until the first block * has been used ... but it never will, since thread1 will block on * this cache lock. */ if( int_shrink > 1 ) { int tile_width; int tile_height; int nlines; vips_get_tile_size( in, &tile_width, &tile_height, &nlines ); if( vips_tilecache( in, &t[6], "tile_width", in->Xsize, "tile_height", 10, "max_tiles", 1 + (nlines * 2) / 10, "access", VIPS_ACCESS_SEQUENTIAL, "threaded", TRUE, NULL ) ) return( -1 ); in = t[6]; } /* If the final affine will be doing a large downsample, we can get * nasty aliasing on hard edges. Blur before affine to smooth this out. * * Don't blur for very small shrinks, blur with radius 1 for x1.5 * shrinks, blur radius 2 for x2.5 shrinks and above, etc. */ sigma = ((1.0 / residual) - 0.5) / 1.5; if( residual < 1.0 && sigma > 0.1 ) { if( vips_gaussblur( in, &t[2], sigma, NULL ) ) return( -1 ); in = t[2]; } if( vips_affine( in, &t[3], residual, 0, 0, residual, "interpolate", resize->interpolate, "idx", resize->idx, "idy", resize->idy, NULL ) ) return( -1 ); in = t[3]; /* If we are upsampling, don't sharpen. */ if( int_shrink > 1 ) { t[5] = vips_image_new_matrixv( 3, 3, -1.0, -1.0, -1.0, -1.0, 32.0, -1.0, -1.0, -1.0, -1.0 ); vips_image_set_double( t[5], "scale", 24 ); if( vips_conv( in, &t[4], t[5], NULL ) ) return( -1 ); in = t[4]; } if( vips_image_write( in, resample->out ) ) return( -1 ); return( 0 ); }
static int vips_join_build( VipsObject *object ) { VipsConversion *conversion = VIPS_CONVERSION( object ); VipsJoin *join = (VipsJoin *) object; int x, y; VipsImage *t; if( VIPS_OBJECT_CLASS( vips_join_parent_class )->build( object ) ) return( -1 ); switch( join->direction ) { case VIPS_DIRECTION_HORIZONTAL: x = join->in1->Xsize + join->shim; switch( join->align ) { case VIPS_ALIGN_LOW: y = 0; break; case VIPS_ALIGN_CENTRE: y = join->in1->Ysize / 2 - join->in2->Ysize / 2; break; case VIPS_ALIGN_HIGH: y = join->in1->Ysize - join->in2->Ysize; break; default: g_assert( 0 ); /* Keep -Wall happy. */ return( 0 ); } break; case VIPS_DIRECTION_VERTICAL: y = join->in1->Ysize + join->shim; switch( join->align ) { case VIPS_ALIGN_LOW: x = 0; break; case VIPS_ALIGN_CENTRE: x = join->in1->Xsize / 2 - join->in2->Xsize / 2; break; case VIPS_ALIGN_HIGH: x = join->in1->Xsize - join->in2->Xsize; break; default: g_assert( 0 ); /* Keep -Wall happy. */ return( 0 ); } break; default: g_assert( 0 ); /* Keep -Wall happy. */ return( 0 ); } if( vips_insert( join->in1, join->in2, &t, x, y, "expand", TRUE, "background", join->background, NULL ) ) return( -1 ); if( !join->expand ) { VipsImage *t2; int left, top, width, height; switch( join->direction ) { case VIPS_DIRECTION_HORIZONTAL: left = 0; top = VIPS_MAX( 0, y ) - y; width = t->Xsize; height = VIPS_MIN( join->in1->Ysize, join->in2->Ysize ); break; case VIPS_DIRECTION_VERTICAL: left = VIPS_MAX( 0, x ) - x; top = 0; width = VIPS_MIN( join->in1->Xsize, join->in2->Xsize ); height = t->Ysize; break; default: g_assert( 0 ); /* Keep -Wall happy. */ return( 0 ); } if( vips_extract_area( t, &t2, left, top, width, height, NULL ) ) { g_object_unref( t ); return( -1 ); } g_object_unref( t ); t = t2; } if( vips_image_write( t, conversion->out ) ) { g_object_unref( t ); return( -1 ); } g_object_unref( t ); return( 0 ); }
/* Entropy-style smartcrop. Repeatedly discard low interest areas. This should * be faster for very large images. */ static int vips_smartcrop_entropy( VipsSmartcrop *smartcrop, VipsImage *in, int *left, int *top ) { int max_slice_size; int width; int height; *left = 0; *top = 0; width = in->Xsize; height = in->Ysize; /* How much do we trim by each iteration? Aim for 8 steps in the axis * that needs trimming most. */ max_slice_size = VIPS_MAX( ceil( (width - smartcrop->width) / 8.0 ), ceil( (height - smartcrop->height) / 8.0 ) ); /* Repeatedly take a slice off width and height until we * reach the target. */ while( width > smartcrop->width || height > smartcrop->height ) { const int slice_width = VIPS_MIN( width - smartcrop->width, max_slice_size ); const int slice_height = VIPS_MIN( height - smartcrop->height, max_slice_size ); if( slice_width > 0 ) { double left_score; double right_score; if( vips_smartcrop_score( smartcrop, in, *left, *top, slice_width, height, &left_score ) ) return( -1 ); if( vips_smartcrop_score( smartcrop, in, *left + width - slice_width, *top, slice_width, height, &right_score ) ) return( -1 ); width -= slice_width; if( left_score < right_score ) *left += slice_width; } if( slice_height > 0 ) { double top_score; double bottom_score; if( vips_smartcrop_score( smartcrop, in, *left, *top, width, slice_height, &top_score ) ) return( -1 ); if( vips_smartcrop_score( smartcrop, in, *left, *top + height - slice_height, width, slice_height, &bottom_score ) ) return( -1 ); height -= slice_height; if( top_score < bottom_score ) *top += slice_height; } } return( 0 ); }
static int vips_smartcrop_attention( VipsSmartcrop *smartcrop, VipsImage *in, int *left, int *top ) { /* From smartcrop.js. */ static double skin_vector[] = {-0.78, -0.57, -0.44}; static double ones[] = {1.0, 1.0, 1.0}; VipsImage **t = (VipsImage **) vips_object_local_array( VIPS_OBJECT( smartcrop ), 24 ); double hscale; double vscale; double sigma; double max; int x_pos; int y_pos; /* The size we shrink to gives the precision with which we can place * the crop */ hscale = 32.0 / in->Xsize; vscale = 32.0 / in->Ysize; sigma = VIPS_MAX( sqrt( pow( smartcrop->width * hscale, 2 ) + pow( smartcrop->height * vscale, 2 ) ) / 10, 1.0 ); if ( vips_resize( in, &t[17], hscale, "vscale", vscale, NULL ) ) return( -1 ); /* Simple edge detect. */ if( !(t[21] = vips_image_new_matrixv( 3, 3, 0.0, -1.0, 0.0, -1.0, 4.0, -1.0, 0.0, -1.0, 0.0 )) ) return( -1 ); /* Convert to XYZ and just use the first three bands. */ if( vips_colourspace( t[17], &t[0], VIPS_INTERPRETATION_XYZ, NULL ) || vips_extract_band( t[0], &t[1], 0, "n", 3, NULL ) ) return( -1 ); /* Edge detect on Y. */ if( vips_extract_band( t[1], &t[2], 1, NULL ) || vips_conv( t[2], &t[3], t[21], "precision", VIPS_PRECISION_INTEGER, NULL ) || vips_linear1( t[3], &t[4], 5.0, 0.0, NULL ) || vips_abs( t[4], &t[14], NULL ) ) return( -1 ); /* Look for skin colours. Taken from smartcrop.js. */ if( /* Normalise to magnitude of colour in XYZ. */ pythagoras( smartcrop, t[1], &t[5] ) || vips_divide( t[1], t[5], &t[6], NULL ) || /* Distance from skin point. */ vips_linear( t[6], &t[7], ones, skin_vector, 3, NULL ) || pythagoras( smartcrop, t[7], &t[8] ) || /* Rescale to 100 - 0 score. */ vips_linear1( t[8], &t[9], -100.0, 100.0, NULL ) || /* Ignore dark areas. */ vips_more_const1( t[2], &t[10], 5.0, NULL ) || !(t[11] = vips_image_new_from_image1( t[10], 0.0 )) || vips_ifthenelse( t[10], t[9], t[11], &t[15], NULL ) ) return( -1 ); /* Look for saturated areas. */ if( vips_colourspace( t[1], &t[12], VIPS_INTERPRETATION_LAB, NULL ) || vips_extract_band( t[12], &t[13], 1, NULL ) || vips_ifthenelse( t[10], t[13], t[11], &t[16], NULL ) ) return( -1 ); /* Sum, blur and find maxpos. * * The amount of blur is related to the size of the crop * area: how large an area we want to consider for the scoring * function. */ if( vips_sum( &t[14], &t[18], 3, NULL ) || vips_gaussblur( t[18], &t[19], sigma, NULL ) || vips_max( t[19], &max, "x", &x_pos, "y", &y_pos, NULL ) ) return( -1 ); /* Centre the crop over the max. */ *left = VIPS_CLIP( 0, x_pos / hscale - smartcrop->width / 2, in->Xsize - smartcrop->width ); *top = VIPS_CLIP( 0, y_pos / vscale - smartcrop->height / 2, in->Ysize - smartcrop->height ); return( 0 ); }
/* Build a pyramid. * * width/height is the size of this layer, real_* the subsection of the layer * which is real pixels (as opposed to background). */ static Layer * pyramid_build( VipsForeignSaveDz *dz, Layer *above, int width, int height, VipsRect *real_pixels ) { VipsForeignSave *save = VIPS_FOREIGN_SAVE( dz ); 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->real_pixels = *real_pixels; layer->image = NULL; layer->strip = NULL; layer->copy = NULL; if( !above ) /* Top of pyramid. */ layer->sub = 1; else layer->sub = above->sub * 2; layer->below = NULL; layer->above = above; /* We round the image size up to an even number to make x2 shrink * easy. */ layer->image = vips_image_new(); if( vips_image_pipelinev( layer->image, VIPS_DEMAND_STYLE_ANY, save->ready, NULL ) ) { layer_free( layer ); return( NULL ); } layer->image->Xsize = width + (width & 1); layer->image->Ysize = height + (height & 1); layer->strip = vips_region_new( layer->image ); layer->copy = vips_region_new( layer->image ); /* The regions will get used in the bg thread callback, so make sure * we don't own them. */ vips__region_no_ownership( layer->strip ); vips__region_no_ownership( layer->copy ); /* Build a line of tiles here. Normally strips are height + 2 * * overlap, but the first row is missing the top edge. * * Expand the strip if necessary to make sure we have an even * number of lines. */ layer->y = 0; layer->write_y = 0; strip.left = 0; strip.top = 0; strip.width = layer->image->Xsize; strip.height = dz->tile_size + dz->overlap; if( (strip.height & 1) == 1 ) strip.height += 1; if( vips_region_buffer( layer->strip, &strip ) ) { layer_free( layer ); return( NULL ); } switch( dz->depth ) { case VIPS_FOREIGN_DZ_DEPTH_ONEPIXEL: limit = 1; break; case VIPS_FOREIGN_DZ_DEPTH_ONETILE: limit = dz->tile_size; break; case VIPS_FOREIGN_DZ_DEPTH_ONE: limit = VIPS_MAX( width, height ); break; default: g_assert( 0 ); limit = dz->tile_size; break; } if( width > limit || height > limit ) { /* Round up, so eg. a 5 pixel wide image becomes 3 a layer * down. * * For the rect, round left/top down, round bottom/right up, * so we get all possible pixels. */ VipsRect halfrect; halfrect.left = real_pixels->left / 2; halfrect.top = real_pixels->top / 2; halfrect.width = (VIPS_RECT_RIGHT( real_pixels ) + 1) / 2 - halfrect.left; halfrect.height = (VIPS_RECT_BOTTOM( real_pixels ) + 1) / 2 - halfrect.top; if( !(layer->below = pyramid_build( dz, layer, (width + 1) / 2, (height + 1) / 2, &halfrect )) ) { layer_free( layer ); return( NULL ); } layer->n = layer->below->n + 1; } 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 ); printf( "\treal_pixels.left = %d, real_pixels.top = %d\n", real_pixels->left, real_pixels->top ); printf( "\treal_pixels.width = %d, real_pixels.height = %d\n", real_pixels->width, real_pixels->height ); #endif return( layer ); }