Skip to content

Instantly share code, notes, and snippets.

@jcupitt
Created September 2, 2016 14:32
Show Gist options
  • Save jcupitt/ff6067e5d90e80394181b2357199d933 to your computer and use it in GitHub Desktop.
Save jcupitt/ff6067e5d90e80394181b2357199d933 to your computer and use it in GitHub Desktop.
load/shrink/crop/rotate from a mem buffer to stdout with vips C
/* compile with
*
* gcc -Wall vikas.c `pkg-config vips --cflags --libs`
*/
#include <stdio.h>
#include <string.h>
#include <vips/vips.h>
/* The bounding box we shrink to fit within.
*/
static const int thumbnail_width = 200;
static const int thumbnail_height = 300;
/* Set true to fill the bounding box.
*/
static const gboolean crop_image = FALSE;
/* Set true to autorotate the image.
*/
static const gboolean rotate_image = TRUE;
/* Calculate the shrink factor, taking into account auto-rotate, the fit mode,
* and so on.
*/
static double
calculate_shrink( VipsImage *im )
{
VipsAngle angle = vips_autorot_get_angle( im );
gboolean rotate = angle == VIPS_ANGLE_D90 || angle == VIPS_ANGLE_D270;
int width = rotate_image && rotate ? im->Ysize : im->Xsize;
int height = rotate_image && rotate ? im->Xsize : im->Ysize;
VipsDirection direction;
/* 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.
*
* Add a small amount so when vips_resize() later rounds down, we
* don't round below target.
*/
double horizontal = (double) width / (thumbnail_width + 0.1);
double vertical = (double) height / (thumbnail_height + 0.1);
if( crop_image ) {
if( horizontal < vertical )
direction = VIPS_DIRECTION_HORIZONTAL;
else
direction = VIPS_DIRECTION_VERTICAL;
}
else {
if( horizontal < vertical )
direction = VIPS_DIRECTION_VERTICAL;
else
direction = VIPS_DIRECTION_HORIZONTAL;
}
return( direction == VIPS_DIRECTION_HORIZONTAL ?
horizontal : vertical );
}
/* Find the best jpeg preload shrink.
*/
static int
thumbnail_find_jpegshrink( VipsImage *im )
{
double shrink = calculate_shrink( im );
/* Shrink-on-load is a simple block shrink and will add quite a bit of
* extra sharpness to the image. We want to block shrink to a
* bit above our target, then vips_resize() to the final size.
*
* Leave at least a factor of two for the final resize step.
*/
if( shrink >= 16 )
return( 8 );
else if( shrink >= 8 )
return( 4 );
else if( shrink >= 4 )
return( 2 );
else
return( 1 );
}
/* Open an image, perhaps shrinking a bit as we open.
*/
static int
txm_open( char *contents, size_t length, VipsImage **out )
{
const char *loader;
VipsImage *im;
/* This gives the name of the load operation.
*/
if( !(loader = vips_foreign_find_load_buffer( contents, length )) )
return( -1 );
if( strcmp( loader, "VipsForeignLoadJpegBuffer" ) == 0 ) {
int jpegshrink;
/* This will just read in the header and is quick.
*/
if( !(im = vips_image_new_from_buffer( contents, length,
"", NULL )) )
return( -1 );
jpegshrink = thumbnail_find_jpegshrink( im );
g_object_unref( im );
printf( "loading jpeg with factor %d pre-shrink\n",
jpegshrink );
if( !(im = vips_image_new_from_buffer( contents, length, "",
"access", VIPS_ACCESS_SEQUENTIAL,
"shrink", jpegshrink,
NULL )) )
return( -1 );
}
/* Other formats can do clever things, eg. webp, svg, pdf. Add
* handling for them as well.
*/
else {
if( !(im = vips_image_new_from_buffer( contents, length, "",
"access", VIPS_ACCESS_SEQUENTIAL,
NULL )) )
return( -1 );
}
*out = im;
return( 0 );
}
/* Process an image from memory to a jpg on stdout.
*/
static int
txm_process( VipsImage *request, char *contents, size_t length )
{
/* This makes an array of 10 VipsImage pointers. When @request is
* unreffed, all the non-NULL Pointers in this array will also be
* unreffed.
*/
VipsImage **t = (VipsImage **)
vips_object_local_array( (VipsObject *) request, 10 );
/* The current image as we build the pipeline.
*/
VipsImage *im;
/* The shrink factor we calculate.
*/
double shrink;
/* Our autorot angle.
*/
VipsAngle angle;
/* Load the input image and do any pre-shrinking.
*/
if( txm_open( contents, length, &t[0] ) )
return( -1 );
im = t[0];
/* Shrink as required. This will default to a fast lanczos3 for the
* finishing stage.
*/
shrink = calculate_shrink( im );
if( vips_resize( im, &t[1], 1.0 / shrink, NULL ) )
return( -1 );
im = t[1];
if( crop_image ) {
int left = (im->Xsize - thumbnail_width) / 2;
int top = (im->Ysize - thumbnail_height) / 2;
if( vips_crop( im, &t[2], left, top,
thumbnail_width, thumbnail_height, NULL ) )
return( -1 );
im = t[2];
}
angle = vips_autorot_get_angle( im );
if( rotate_image &&
angle != VIPS_ANGLE_D0 ) {
/* Need to copy to memory before the rotate, since we are
* streaming the input image.
*/
t[3] = vips_image_new_memory();
if( vips_image_write( im, t[3] ) ||
vips_rot( t[3], &t[4], angle, NULL ) )
return( -1 );
im = t[4];
vips_autorot_remove_angle( im );
}
/* There are a LOT of jpeg options you can set to improve quality, see
* the jpegsave docs.
*/
if( vips_jpegsave_mime( im, NULL ) )
return( -1 );
return( 0 );
}
int
main( int argc, char **argv )
{
int i;
if( VIPS_INIT( argv[0] ) )
vips_error_exit( NULL );
for( i = 1; i < argc; i++ ) {
VipsImage *request;
char *contents;
size_t length;
GError *err = NULL;
/* We make an empty image to represent the lifetime of this
* request. All objects are allocated off this, so when we
* unref this at the end of the request, they are all
* unreffed too.
*/
request = vips_image_new();
/* Read the entire file into memory to simulate it arriving
* from S3.
*/
if( !g_file_get_contents( argv[i], &contents, &length, &err ) ) {
vips_g_error( &err );
fprintf( stderr, "fail for file %s\n", argv[i] );
fprintf( stderr, "\t%s\n", vips_error_buffer() );
vips_error_clear();
continue;
}
if( txm_process( request, contents, length ) ) {
fprintf( stderr, "fail for file %s\n", argv[i] );
fprintf( stderr, "\t%s\n", vips_error_buffer() );
vips_error_clear();
}
g_free( contents );
g_object_unref( request );
}
return( 0 );
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment