gallery/src/photo.c
Stuart Longland 9e2ab341ba AMD64 build fixes and parallelism tweaks
TODO: figure out how to autodetect lib vs lib64.

These changes were made some time ago (can't remember when) but didn't
get committed until now.  (Yes, naughty me, but perhaps I was in a rush.)
2014-04-06 20:09:29 +10:00

551 lines
16 KiB
C

#include <photo.h>
#include <stdlib.h>
#include <string.h>
#include <util.h>
#include <wand/MagickWand.h>
#include <libgen.h>
#include <dprintf.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <jpeg.h>
#include <settings.h>
/* Read photo information in. It takes a gallery and photo filename as
* arguments, and can optionally take a pre-allocated photo_info struct.
*
* Returns a struct on success, or NULL on failure.
*/
struct photo_info* read_photo_info( struct gallery_contents* gallery,
char* filename,
struct photo_info* dest ) {
int free_struct = 1;
if ( gallery == NULL ) return NULL;
if ( filename == NULL ) return NULL;
if ( dest == NULL ) {
dest = (struct photo_info*)
gccalloc( 1, sizeof(struct photo_info) );
dprintf( "read_photo_info: allocated struct at %p\n", dest );
if ( dest == NULL ) return NULL;
} else {
free_struct = 0;
}
dprintf( "read_photo_info: loading photo %s into %p\n",
filename, dest );
/* Determine the full file path */
char* filepath = construct_path( "s/s",
gallery->info->gallery_dir, filename );
if ( filepath == NULL ) {
if ( free_struct ) gcfree( dest );
return NULL;
}
dprintf( "read_photo_info: full path at %s\n", filepath );
/* Read in basic photo metadata */
struct photo_meta* meta =
read_photo_meta( filepath, &dest->meta );
if ( meta == NULL ) {
gcfree( filepath );
if ( free_struct ) gcfree( dest );
return NULL;
}
dprintf( "read_photo_info: metadata loaded to %p\n", meta );
/* Partially initialise the struct */
dest->gc = gallery;
dest->index = (unsigned long)(-1);
dest->previous = NULL;
dest->next = NULL;
dest->annotation = NULL;
dest->description = NULL;
/* Default settings should be set according to system defaults */
dest->default_rotation = DEFAULT_ROTATION;
dest->default_quality = DEFAULT_QUALITY;
if ( meta->aspect_ratio >= PANORAMA_ASPECT_RATIO ) {
dest->default_dims.width = PANORAMA_DEFAULT_WIDTH;
dest->default_dims.height = PANORAMA_DEFAULT_HEIGHT;
} else {
dest->default_dims.width = DEFAULT_WIDTH;
dest->default_dims.height = DEFAULT_HEIGHT;
}
/* Attempt to locate the photo and its neighbours */
size_t i;
for( i = 0; i < gallery->photos->length; i++ ) {
if ( strcmp( filename, gallery->photos->string[i] ) == 0 ) {
dest->index = i;
if ( i > 0 )
dest->previous = strdup(
gallery->photos->string[i-1] );
if ( i < gallery->photos->length-1 )
dest->next = strdup(
gallery->photos->string[i+1] );
break;
}
}
dprintf( "read_photo_info: neighbours found: %s (%p) and %s (%p)\n",
dest->previous, dest->previous, dest->next, dest->next );
/* Attempt to locate the thumbnail image */
dest->thumbnail = resize_photo( meta, 100, 100, 25, 0, &dest->thumbdims );
/* char* thumbnail = construct_path( "s/s/s", gallery->info->gallery_name,
"thumbnails",
filename );
if ( thumbnail != NULL ) {
dest->thumbnail = substitute_ext( thumbnail, ".jpg" );
gcfree( thumbnail );
}*/
dprintf( "read_photo_info: thumbnail at %s (%p)\n",
dest->thumbnail, dest->thumbnail );
/* Attempt to load the annotation. Two possible formats are available:
* (1) "Legacy" annotations.txt database
* (2) New per-file annotations (in <filename>.txt)
*
* A load will be attempted in this order.
*/
char* ann_file = construct_path( "s/s", gallery->info->gallery_dir,
"annotations.txt" );
if ( ann_file != NULL ) {
FILE* annotations = fopen( ann_file, "r" );
if ( annotations != NULL ) {
char buffer[1024];
char* strtok_ptr;
dprintf( "read_photo_info: parsing legacy annotations "
"database in %s\n", ann_file );
/* Read each line in... and split according to tabs */
while( fgets( buffer, 1024, annotations ) ) {
char* file = strtok_r( buffer, "\t", &strtok_ptr );
/* Does it match? */
if ( strcmp( file, filename ) == 0 ) {
/* Yes... grab the next token */
char* ann = strtok_r( NULL, "\t",
&strtok_ptr );
/* If not null, copy it */
if ( ann != NULL )
dest->annotation = strdup(ann);
/* Stop here */
break;
}
}
fclose( annotations );
}
gcfree( ann_file );
}
/* If we still haven't located the annotation yet... try method (2). */
if ( dest->annotation == NULL ) {
/* The newer format uses a file with the same format as
* info.txt, and thus can handle more information and longer
* strings. It'll soon store more than just the annotation,
* but for now, that's it.
*
* The file is the name of the image, with the extension
* substituted with .txt.
*/
ann_file = substitute_ext( filepath, ".txt" );
if ( ann_file != NULL ) {
dprintf( "read_photo_info: legacy read failed, "
"attempting to load annotations from %s\n",
ann_file );
/* Read the file in */
struct txtinfo_chain* annotations =
read_txtinfo_chain( ann_file );
gcfree( ann_file );
if ( annotations != NULL ) {
dest->annotation =
get_txtinfo_chain_val( annotations,
".annotation" );
/* This method can also specify default width,
* height, quality and dimension info
*/
char* str;
str = get_txtinfo_chain_val(
annotations, ".width" );
if ( str != NULL )
dest->default_dims.width = atoi(str);
str = get_txtinfo_chain_val(
annotations, ".height" );
if ( str != NULL )
dest->default_dims.height = atoi(str);
str = get_txtinfo_chain_val(
annotations, ".quality" );
if ( str != NULL )
dest->default_quality = (char)atoi(str);
str = get_txtinfo_chain_val(
annotations, ".rotation" );
if ( str != NULL )
dest->default_rotation = atof(str);
/* Description is a block of HTML
* displayed below the photo */
dest->description =
get_txtinfo_chain_val( annotations,
".description" );
destroy_txtinfo_chain( annotations );
}
}
}
dprintf( "read_photo_info: annotation= %s (%p)\n",
dest->annotation, dest->annotation );
return dest;
}
/* Copy photo information to a new struct.
*
* This can take a pre-allocated struct for destination storage.
*
* Returns the copy on success, NULL on failure.
*/
struct photo_info* copy_photo_info( struct photo_info* src,
struct photo_info* dest ) {
int free_struct = 1;
if ( src == NULL ) return NULL;
if ( dest == NULL ) {
dest = (struct photo_info*)
gcmalloc( sizeof(struct photo_info) );
if ( dest == NULL ) return NULL;
} else {
free_struct = 0;
}
/* Copy gallery pointer */
dest->gc = src->gc;
/* Copy the strings */
if ( src->previous != NULL )
dest->previous = strdup( src->previous );
if ( src->next != NULL )
dest->next = strdup( src->next );
if ( src->thumbnail != NULL )
dest->thumbnail = strdup( src->thumbnail );
if ( src->annotation != NULL )
dest->annotation = strdup( src->annotation );
if ( src->description != NULL )
dest->description = strdup( src->description );
/* Copy the metadata */
copy_photo_meta( &src->meta, &dest->meta );
return dest;
}
/* Destroy photo information struct. */
void destroy_photo_info( struct photo_info* photo, int fields_only ) {
destroy_photo_meta( &photo->meta, 1 );
if ( photo->previous != NULL ) gcfree( photo->previous );
if ( photo->next != NULL ) gcfree( photo->next );
if ( photo->thumbnail != NULL ) gcfree( photo->thumbnail );
if ( photo->annotation != NULL ) gcfree( photo->annotation );
if ( photo->description != NULL ) gcfree( photo->description );
if ( !fields_only ) gcfree( photo );
}
/* Read photo metadata. This takes a full path to an image or movie, and
* returns the dimensions and other critical metadata.
*
* It may optionally take a pre-allocated struct.
*
* Returns the photo metadata obtained, or NULL if it fails.
*/
struct photo_meta* read_photo_meta( const char* filename,
struct photo_meta* dest ) {
int free_struct = 1;
if ( filename == NULL ) return NULL;
if ( dest == NULL ) {
dest = (struct photo_meta*)
gcmalloc( sizeof(struct photo_meta) );
dprintf("read_photo_meta: allocated struct at %p\n", dest);
if ( dest == NULL ) return NULL;
} else {
free_struct = 0;
}
dprintf( "read_photo_meta: reading photo metadata from %s into %p\n",
filename, dest );
/* Gallery name should be the directory containing the photo */
char* dirname = get_dirname( filename );
if ( dirname == NULL ) {
if ( free_struct ) gcfree( dest );
return NULL;
}
char* gallery = get_basename( dirname );
gcfree( dirname );
if ( gallery == NULL ) {
if ( free_struct ) gcfree( dest );
return NULL;
}
dest->gallery_name = gallery;
dest->filepath = strdup( filename );
dest->filename = basename( dest->filepath );
dprintf( "read_photo_meta: gallery: %s, path: %s, name: %s\n",
dest->gallery_name, dest->filepath, dest->filename );
char* extn = rindex( filename, '.' );
/* If the extension is one of these, it's a movie */
if ( (strcasecmp( extn, ".mpg" ) == 0) ||
(strcasecmp( extn, ".mov" ) == 0) ||
(strcasecmp( extn, ".mp4" ) == 0) ||
(strcasecmp( extn, ".avi" ) == 0) ||
(strcasecmp( extn, ".mkv" ) == 0) ||
(strcasecmp( extn, ".ogm" ) == 0) ||
(strcasecmp( extn, ".wmv" ) == 0) ||
(strcasecmp( extn, ".ogg" ) == 0) ) {
/* TODO: Width and height of video! */
dest->is_video = 1;
} else {
dest->is_video = 0;
if ( ( strcasecmp( extn, ".jpg" ) == 0 ) ||
( strcasecmp( extn, ".jpeg" ) == 0 ) ) {
/* We have a JPEG image, use the simpler routine to
* decode it.
*/
read_jpeg_dims( filename, &dest->size );
} else {
MagickWand* wand = NewMagickWand();
if (MagickReadImage( wand, filename ) == MagickFalse) {
/* Failure. Free everything and return */
DestroyMagickWand( wand );
gcfree( gallery );
if ( free_struct ) gcfree( dest );
return NULL;
}
/* Okay, image read in... get the dimensions */
dest->size.width = MagickGetImageWidth( wand );
dest->size.height = MagickGetImageHeight( wand );
DestroyMagickWand( wand );
}
dest->aspect_ratio = (double)dest->size.width /
(double)dest->size.height;
}
dprintf( "read_photo_meta: video: %d, width: %d, height: %d\n",
dest->is_video, dest->size.width, dest->size.height );
return dest;
}
/* Copy photo metadata. Takes an existing photo_meta struct, and may optionally
* take a second as the destination.
*
* Returns the copy on success, NULL on failure.
*/
struct photo_meta* copy_photo_meta( struct photo_meta* src,
struct photo_meta* dest ) {
int free_struct = 1;
if ( src == NULL ) return NULL;
if ( dest == NULL ) {
dest = (struct photo_meta*)
memdup( (void*)src, sizeof(struct photo_meta) );
if ( dest == NULL ) return NULL;
} else {
memcpy( (void*)dest, (void*)src, sizeof( struct photo_meta ) );
free_struct = 0;
}
dest->gallery_name = strdup( src->gallery_name );
dest->filepath = strdup( src->filepath );
dest->filename = basename( src->filepath );
return dest;
}
/* Free photo metadata.
*
* Frees the metadata contained in the specified struct and (if fields_only is
* zero) the struct itself.
*/
void destroy_photo_meta( struct photo_meta* meta, int fields_only ) {
if ( meta->gallery_name != NULL ) gcfree( meta->gallery_name );
if ( meta->filepath != NULL ) gcfree( meta->filepath );
if ( !fields_only ) gcfree( meta );
}
/* Helper function... calculate size of resized photo */
inline void calculate_dims( struct photo_meta* meta,
unsigned int width, unsigned int height,
struct dimensions* dims ) {
if ( (width == 0) && (height == 0) ) {
width = meta->size.width;
height = meta->size.height;
} else if ( width == 0 ) {
width = (unsigned int)((double)height * meta->aspect_ratio);
} else if ( height == 0 ) {
height = (unsigned int)((double)width / meta->aspect_ratio);
} else {
/* We're given both. Scale the photo to fit within this area */
unsigned int s_width = (unsigned int)
((double)height * meta->aspect_ratio);
if ( s_width > width )
height = (unsigned int)
((double)width / meta->aspect_ratio);
else
width = s_width;
}
dims->width = width;
dims->height = height;
/* Check limits...
* These will overwrite the above values if out-of-bounds */
if ( width > MAX_WIDTH )
calculate_dims( meta, MAX_WIDTH, height, dims );
else if ( height > MAX_HEIGHT )
calculate_dims( meta, width, MAX_HEIGHT, dims );
else if ( ( width > MAX_WIDTH ) && ( height > MAX_HEIGHT ) )
calculate_dims( meta, MAX_WIDTH, MAX_HEIGHT, dims );
}
/* Resize a photo. This sizes and scales a photo to the specified dimensions
* and settings. The image is written to disk cache, the filename being
* returned for display in the browser (when generating HTML or JSON).
*
* The width or height may be set to zero, in which case, it sets it to preserve
* the aspect ratio. If both are set to zero, then the image will not be
* resized.
*
* Returns the filename of the resized image in cache, or NULL if this fails.
*/
char* resize_photo( struct photo_meta* dest,
unsigned int width, unsigned int height,
unsigned char quality, double rotation,
struct dimensions* resized_dims ) {
if ( dest == NULL ) return NULL;
dprintf( "resize_photo: fitting %s (from %s) in %dx%d area\n",
dest->filename, dest->gallery_name,
width, height );
struct dimensions my_dims;
calculate_dims( dest, width, height, &my_dims );
width = my_dims.width;
height= my_dims.height;
dprintf( "resize_photo: dimensions %dx%d\n", width, height );
if ( resized_dims != NULL )
memcpy( resized_dims, &my_dims, sizeof(my_dims) );
/* Determine the name of the cache item */
char* cache_file = construct_path( "s/scscicicicfs", CACHEDIR,
dest->gallery_name, '-', dest->filename, '-',
width, 'x', height, '-', quality,
'-', rotation, (quality == 100) ? ".png" : ".jpg" );
dprintf( "resize_photo: cache at %s (%p)\n", cache_file, cache_file );
if ( cache_file == NULL ) return NULL;
char* dir = get_cgidir();
char* cache_path = construct_path( "s/s", dir, cache_file );
//gcfree( dir );
/* Check if the file already exists... if it does, return now */
struct stat cache_stat;
if ( stat( cache_path, &cache_stat ) == 0 ) {
struct stat orig_stat;
if ( stat( dest->filepath, &orig_stat ) == 0 ) {
if ( orig_stat.st_mtime <= cache_stat.st_mtime ) {
/* Recent cache already exists */
gcfree( cache_path );
return cache_file;
}
}
}
#ifndef NO_SEMAPHORE
/*
* Check for semaphore... if it exists, wait for it to disappear.
* For this, we use the open() syscall with O_CREAT|O_EXCL, which
* will fail if the file already exists.
*/
char* sem_file = construct_path( "s/ss", get_cgidir(),
cache_file, ".sem" );
int fd = open( sem_file, O_CREAT|O_EXCL, S_IRUSR|S_IWUSR );
while( fd == -1 ) {
sleep(1);
fd = open( sem_file, O_CREAT|O_EXCL, S_IRUSR|S_IWUSR );
fprintf( stderr, "resize_photo: waiting for semaphore %s (fd: %d)\n",
sem_file, fd );
dprintf( "resize_photo: waiting for semaphore %s (fd: %d)\n",
sem_file, fd );
}
close(fd);
#endif
MagickWand* wand = NewMagickWand();
dprintf( "resize_photo: wand created at %p\n", wand);
if (MagickReadImage( wand, dest->filepath ) == MagickFalse) {
ExceptionType severity;
char* description=MagickGetException(wand,&severity);
dprintf("%s %s %ld %s\n",GetMagickModule(),description);
gcfree( cache_file );
#ifndef NO_SEMAPHORE
/* Get rid of the semaphore */
unlink( sem_file );
gcfree( sem_file );
#endif
return NULL;
}
/* Background colour */
PixelWand* background = NewPixelWand();
PixelSetColor( background, "#ffffff" );
MagickResizeImage( wand, width, height, LanczosFilter, 1.0 );
MagickRotateImage( wand, background, rotation );
/* Since rotating the image changes the aspect ratio, we'll
* update the resized image dimensions here.
*/
if ( ( rotation != 0.0) && ( resized_dims != NULL ) ) {
resized_dims->width = MagickGetImageWidth( wand );
resized_dims->height = MagickGetImageHeight( wand );
}
/* Image quality */
if ( quality < 100 )
MagickSetImageCompressionQuality( wand,
(unsigned long)quality );
MagickSetImageInterlaceScheme( wand, LineInterlace );
if (MagickWriteImage( wand, cache_path ) == MagickFalse) {
ExceptionType severity;
char* description=MagickGetException(wand,&severity);
dprintf("%s %s %ld %s\n",GetMagickModule(),description);
#ifndef NO_SEMAPHORE
/* Get rid of the semaphore */
unlink( sem_file );
gcfree( sem_file );
#endif
gcfree( cache_file );
cache_file = NULL;
}
DestroyMagickWand( wand );
#ifndef NO_SEMAPHORE
/* Get rid of the semaphore */
unlink( sem_file );
gcfree( sem_file );
#endif
gcfree( cache_path );
dprintf( "resize_photo: resized photo at %s (%p)\n", cache_file, cache_file );
return cache_file;
}