move exif handling out to exif.c
now used by webpload as well
This commit is contained in:
parent
9fdbaf288d
commit
47222bc1f5
@ -6,6 +6,7 @@
|
||||
- better vipsheader behaviour with complex field types
|
||||
- added vips_image_hasalpha()
|
||||
- added vips_thumbnail() / vips_thumbnail_buffer()
|
||||
- webpload reads icc, xmp, exif metadata
|
||||
|
||||
18/10/16 started 8.4.3
|
||||
- fix error detection in gif_close, thanks aaron42net
|
||||
|
@ -2,6 +2,7 @@ noinst_LTLIBRARIES = libforeign.la
|
||||
|
||||
libforeign_la_SOURCES = \
|
||||
pforeign.h \
|
||||
exif.c \
|
||||
gifload.c \
|
||||
cairo.c \
|
||||
pdfload.c \
|
||||
|
1128
libvips/foreign/exif.c
Normal file
1128
libvips/foreign/exif.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -77,8 +77,6 @@ typedef struct {
|
||||
void vips__new_output_message( j_common_ptr cinfo );
|
||||
void vips__new_error_exit( j_common_ptr cinfo );
|
||||
|
||||
int vips__set_exif_resolution( ExifData *ed, VipsImage *im );
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /*__cplusplus*/
|
||||
|
@ -79,6 +79,8 @@
|
||||
* 07/09/16
|
||||
* - Don't use the exif resolution if x_resolution / y_resolution /
|
||||
* resolution_unit is missing
|
||||
* 7/11/16
|
||||
* - exif handling moved out to exif.c
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -125,20 +127,6 @@
|
||||
#include <string.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
#ifdef HAVE_EXIF
|
||||
#ifdef UNTAGGED_EXIF
|
||||
#include <exif-data.h>
|
||||
#include <exif-loader.h>
|
||||
#include <exif-ifd.h>
|
||||
#include <exif-utils.h>
|
||||
#else /*!UNTAGGED_EXIF*/
|
||||
#include <libexif/exif-data.h>
|
||||
#include <libexif/exif-loader.h>
|
||||
#include <libexif/exif-ifd.h>
|
||||
#include <libexif/exif-utils.h>
|
||||
#endif /*UNTAGGED_EXIF*/
|
||||
#endif /*HAVE_EXIF*/
|
||||
|
||||
#include <vips/vips.h>
|
||||
#include <vips/buf.h>
|
||||
#include <vips/internal.h>
|
||||
@ -176,7 +164,7 @@ typedef struct _ReadJpeg {
|
||||
*/
|
||||
int y_pos;
|
||||
|
||||
/* Use Orientation exif tag to automatically rotate and flip image
|
||||
/* Use orientation tag to automatically rotate and flip image
|
||||
* during load.
|
||||
*/
|
||||
gboolean autorotate;
|
||||
@ -272,424 +260,6 @@ readjpeg_file( ReadJpeg *jpeg, const char *filename )
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
#ifdef HAVE_EXIF
|
||||
#ifdef DEBUG_VERBOSE
|
||||
/* Print exif for debugging ... hacked from exif-0.6.9/actions.c
|
||||
*/
|
||||
static void
|
||||
show_tags( ExifData *data )
|
||||
{
|
||||
int i;
|
||||
unsigned int tag;
|
||||
const char *name;
|
||||
|
||||
printf( "show EXIF tags:\n" );
|
||||
|
||||
for( i = 0; i < EXIF_IFD_COUNT; i++ )
|
||||
printf( "%-7.7s", exif_ifd_get_name( i ) );
|
||||
printf( "\n" );
|
||||
|
||||
for( tag = 0; tag < 0xffff; tag++ ) {
|
||||
name = exif_tag_get_title( tag );
|
||||
if( !name )
|
||||
continue;
|
||||
printf( " 0x%04x %-29.29s", tag, name );
|
||||
for( i = 0; i < EXIF_IFD_COUNT; i++ )
|
||||
if( exif_content_get_entry( data->ifd[i], tag ) )
|
||||
printf( " * " );
|
||||
else
|
||||
printf( " - " );
|
||||
printf( "\n" );
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
show_entry( ExifEntry *entry, void *client )
|
||||
{
|
||||
char exif_text[256];
|
||||
|
||||
printf( "%s", exif_tag_get_title( entry->tag ) );
|
||||
printf( "|" );
|
||||
printf( "%s", exif_entry_get_value( entry, exif_text, 256 ) );
|
||||
printf( "|" );
|
||||
printf( "%s", exif_format_get_name( entry->format ) );
|
||||
printf( "|" );
|
||||
printf( "%d bytes", entry->size );
|
||||
printf( "\n" );
|
||||
}
|
||||
|
||||
static void
|
||||
show_ifd( ExifContent *content, void *client )
|
||||
{
|
||||
int *ifd = (int *) client;
|
||||
|
||||
printf( "- ifd %d\n", *ifd );
|
||||
exif_content_foreach_entry( content, show_entry, client );
|
||||
|
||||
*ifd += 1;
|
||||
}
|
||||
|
||||
void
|
||||
show_values( ExifData *data )
|
||||
{
|
||||
ExifByteOrder order;
|
||||
int ifd;
|
||||
|
||||
order = exif_data_get_byte_order( data );
|
||||
printf( "EXIF tags in '%s' byte order\n",
|
||||
exif_byte_order_get_name( order ) );
|
||||
|
||||
printf( "Title|Value|Format|Size\n" );
|
||||
|
||||
ifd = 0;
|
||||
exif_data_foreach_content( data, show_ifd, &ifd );
|
||||
|
||||
if( data->size )
|
||||
printf( "contains thumbnail of %d bytes\n", data->size );
|
||||
}
|
||||
#endif /*DEBUG_VERBOSE*/
|
||||
#endif /*HAVE_EXIF*/
|
||||
|
||||
#ifdef HAVE_EXIF
|
||||
/* Like exif_data_new_from_data(), but don't default missing fields.
|
||||
*
|
||||
* If we do exif_data_new_from_data(), then missing fields are set to
|
||||
* their default value and we won't know about it.
|
||||
*/
|
||||
static ExifData *
|
||||
vips_exif_load_data_without_fix( void *data, int data_length )
|
||||
{
|
||||
ExifData *ed;
|
||||
|
||||
if( !(ed = exif_data_new()) ) {
|
||||
vips_error( "VipsJpeg", "%s", _( "unable to init exif" ) );
|
||||
return( NULL );
|
||||
}
|
||||
|
||||
exif_data_unset_option( ed, EXIF_DATA_OPTION_FOLLOW_SPECIFICATION );
|
||||
exif_data_load_data( ed, data, data_length );
|
||||
|
||||
return( ed );
|
||||
}
|
||||
|
||||
static int
|
||||
vips_exif_get_int( ExifData *ed,
|
||||
ExifEntry *entry, unsigned long component, int *out )
|
||||
{
|
||||
ExifByteOrder bo = exif_data_get_byte_order( ed );
|
||||
size_t sizeof_component = entry->size / entry->components;
|
||||
size_t offset = component * sizeof_component;
|
||||
|
||||
if( entry->format == EXIF_FORMAT_SHORT )
|
||||
*out = exif_get_short( entry->data + offset, bo );
|
||||
else if( entry->format == EXIF_FORMAT_SSHORT )
|
||||
*out = exif_get_sshort( entry->data + offset, bo );
|
||||
else if( entry->format == EXIF_FORMAT_LONG )
|
||||
/* This won't work for huge values, but who cares.
|
||||
*/
|
||||
*out = (int) exif_get_long( entry->data + offset, bo );
|
||||
else if( entry->format == EXIF_FORMAT_SLONG )
|
||||
*out = exif_get_slong( entry->data + offset, bo );
|
||||
else
|
||||
return( -1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static int
|
||||
vips_exif_get_rational( ExifData *ed,
|
||||
ExifEntry *entry, unsigned long component, ExifRational *out )
|
||||
{
|
||||
if( entry->format == EXIF_FORMAT_RATIONAL ) {
|
||||
ExifByteOrder bo = exif_data_get_byte_order( ed );
|
||||
size_t sizeof_component = entry->size / entry->components;
|
||||
size_t offset = component * sizeof_component;
|
||||
|
||||
*out = exif_get_rational( entry->data + offset, bo );
|
||||
}
|
||||
else
|
||||
return( -1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static int
|
||||
vips_exif_get_srational( ExifData *ed,
|
||||
ExifEntry *entry, unsigned long component, ExifSRational *out )
|
||||
{
|
||||
if( entry->format == EXIF_FORMAT_SRATIONAL ) {
|
||||
ExifByteOrder bo = exif_data_get_byte_order( ed );
|
||||
size_t sizeof_component = entry->size / entry->components;
|
||||
size_t offset = component * sizeof_component;
|
||||
|
||||
*out = exif_get_srational( entry->data + offset, bo );
|
||||
}
|
||||
else
|
||||
return( -1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static int
|
||||
vips_exif_get_double( ExifData *ed,
|
||||
ExifEntry *entry, unsigned long component, double *out )
|
||||
{
|
||||
ExifRational rv;
|
||||
ExifSRational srv;
|
||||
|
||||
if( !vips_exif_get_rational( ed, entry, component, &rv ) )
|
||||
*out = (double) rv.numerator / rv.denominator;
|
||||
else if( !vips_exif_get_srational( ed, entry, component, &srv ) )
|
||||
*out = (double) srv.numerator / srv.denominator;
|
||||
else
|
||||
return( -1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
/* Save an exif value to a string in a way that we can restore. We only bother
|
||||
* for the simple formats (that a client might try to change) though.
|
||||
*
|
||||
* Keep in sync with vips_exif_from_s() in vips2jpeg.
|
||||
*/
|
||||
static void
|
||||
vips_exif_to_s( ExifData *ed, ExifEntry *entry, VipsBuf *buf )
|
||||
{
|
||||
unsigned long i;
|
||||
int iv;
|
||||
ExifRational rv;
|
||||
ExifSRational srv;
|
||||
char txt[256];
|
||||
|
||||
if( entry->format == EXIF_FORMAT_ASCII ) {
|
||||
/* libexif does not null-terminate strings. Copy out and add
|
||||
* the \0 ourselves.
|
||||
*/
|
||||
int len = VIPS_MIN( 254, entry->size );
|
||||
|
||||
memcpy( txt, entry->data, len );
|
||||
txt[len] = '\0';
|
||||
vips_buf_appendf( buf, "%s ", txt );
|
||||
}
|
||||
else if( entry->components < 10 &&
|
||||
!vips_exif_get_int( ed, entry, 0, &iv ) ) {
|
||||
for( i = 0; i < entry->components; i++ ) {
|
||||
vips_exif_get_int( ed, entry, i, &iv );
|
||||
vips_buf_appendf( buf, "%d ", iv );
|
||||
}
|
||||
}
|
||||
else if( entry->components < 10 &&
|
||||
!vips_exif_get_rational( ed, entry, 0, &rv ) ) {
|
||||
for( i = 0; i < entry->components; i++ ) {
|
||||
vips_exif_get_rational( ed, entry, i, &rv );
|
||||
vips_buf_appendf( buf, "%u/%u ",
|
||||
rv.numerator, rv.denominator );
|
||||
}
|
||||
}
|
||||
else if( entry->components < 10 &&
|
||||
!vips_exif_get_srational( ed, entry, 0, &srv ) ) {
|
||||
for( i = 0; i < entry->components; i++ ) {
|
||||
vips_exif_get_srational( ed, entry, i, &srv );
|
||||
vips_buf_appendf( buf, "%d/%d ",
|
||||
srv.numerator, srv.denominator );
|
||||
}
|
||||
}
|
||||
else
|
||||
vips_buf_appendf( buf, "%s ",
|
||||
exif_entry_get_value( entry, txt, 256 ) );
|
||||
|
||||
vips_buf_appendf( buf, "(%s, %s, %lu components, %d bytes)",
|
||||
exif_entry_get_value( entry, txt, 256 ),
|
||||
exif_format_get_name( entry->format ),
|
||||
entry->components,
|
||||
entry->size );
|
||||
}
|
||||
|
||||
typedef struct _VipsExif {
|
||||
VipsImage *image;
|
||||
ExifData *ed;
|
||||
} VipsExif;
|
||||
|
||||
static void
|
||||
attach_exif_entry( ExifEntry *entry, VipsExif *ve )
|
||||
{
|
||||
const char *tag_name;
|
||||
char vips_name_txt[256];
|
||||
VipsBuf vips_name = VIPS_BUF_STATIC( vips_name_txt );
|
||||
char value_txt[256];
|
||||
VipsBuf value = VIPS_BUF_STATIC( value_txt );
|
||||
|
||||
if( !(tag_name = exif_tag_get_name( entry->tag )) )
|
||||
return;
|
||||
|
||||
vips_buf_appendf( &vips_name, "exif-ifd%d-%s",
|
||||
exif_entry_get_ifd( entry ), tag_name );
|
||||
vips_exif_to_s( ve->ed, entry, &value );
|
||||
|
||||
/* Can't do anything sensible with the error return.
|
||||
*/
|
||||
(void) vips_image_set_string( ve->image,
|
||||
vips_buf_all( &vips_name ), vips_buf_all( &value ) );
|
||||
}
|
||||
|
||||
static void
|
||||
attach_exif_content( ExifContent *content, VipsExif *ve )
|
||||
{
|
||||
exif_content_foreach_entry( content,
|
||||
(ExifContentForeachEntryFunc) attach_exif_entry, ve );
|
||||
}
|
||||
|
||||
static int
|
||||
get_entry_double( ExifData *ed, int ifd, ExifTag tag, double *out )
|
||||
{
|
||||
ExifEntry *entry;
|
||||
|
||||
if( !(entry = exif_content_get_entry( ed->ifd[ifd], tag )) ||
|
||||
entry->components != 1 )
|
||||
return( -1 );
|
||||
|
||||
return( vips_exif_get_double( ed, entry, 0, out ) );
|
||||
}
|
||||
|
||||
static int
|
||||
get_entry_int( ExifData *ed, int ifd, ExifTag tag, int *out )
|
||||
{
|
||||
ExifEntry *entry;
|
||||
|
||||
if( !(entry = exif_content_get_entry( ed->ifd[ifd], tag )) ||
|
||||
entry->components != 1 )
|
||||
return( -1 );
|
||||
|
||||
return( vips_exif_get_int( ed, entry, 0, out ) );
|
||||
}
|
||||
|
||||
static int
|
||||
res_from_exif( VipsImage *im, ExifData *ed )
|
||||
{
|
||||
double xres, yres;
|
||||
int unit;
|
||||
|
||||
/* The main image xres/yres are in ifd0. ifd1 has xres/yres of the
|
||||
* image thumbnail, if any.
|
||||
*/
|
||||
if( get_entry_double( ed, 0, EXIF_TAG_X_RESOLUTION, &xres ) ||
|
||||
get_entry_double( ed, 0, EXIF_TAG_Y_RESOLUTION, &yres ) ||
|
||||
get_entry_int( ed, 0, EXIF_TAG_RESOLUTION_UNIT, &unit ) ) {
|
||||
vips_warn( "VipsJpeg",
|
||||
"%s", _( "error reading resolution" ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
printf( "res_from_exif: seen exif tags "
|
||||
"xres = %g, yres = %g, unit = %d\n", xres, yres, unit );
|
||||
#endif /*DEBUG*/
|
||||
|
||||
switch( unit ) {
|
||||
case 1:
|
||||
/* No unit ... just pass the fields straight to vips.
|
||||
*/
|
||||
vips_image_set_string( im,
|
||||
VIPS_META_RESOLUTION_UNIT, "none" );
|
||||
break;
|
||||
|
||||
case 2:
|
||||
/* In inches.
|
||||
*/
|
||||
xres /= 25.4;
|
||||
yres /= 25.4;
|
||||
vips_image_set_string( im,
|
||||
VIPS_META_RESOLUTION_UNIT, "in" );
|
||||
break;
|
||||
|
||||
case 3:
|
||||
/* In cm.
|
||||
*/
|
||||
xres /= 10.0;
|
||||
yres /= 10.0;
|
||||
vips_image_set_string( im,
|
||||
VIPS_META_RESOLUTION_UNIT, "cm" );
|
||||
break;
|
||||
|
||||
default:
|
||||
vips_warn( "VipsJpeg",
|
||||
"%s", _( "unknown EXIF resolution unit" ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
printf( "res_from_exif: seen exif resolution %g, %g p/mm\n",
|
||||
xres, yres );
|
||||
#endif /*DEBUG*/
|
||||
|
||||
im->Xres = xres;
|
||||
im->Yres = yres;
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static int
|
||||
attach_thumbnail( VipsImage *im, ExifData *ed )
|
||||
{
|
||||
if( ed->size > 0 ) {
|
||||
char *thumb_copy;
|
||||
|
||||
thumb_copy = g_malloc( ed->size );
|
||||
memcpy( thumb_copy, ed->data, ed->size );
|
||||
|
||||
vips_image_set_blob( im, "jpeg-thumbnail-data",
|
||||
(VipsCallbackFn) g_free, thumb_copy, ed->size );
|
||||
}
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
#endif /*HAVE_EXIF*/
|
||||
|
||||
static int
|
||||
parse_exif( VipsImage *im, void *data, int data_length )
|
||||
{
|
||||
#ifdef HAVE_EXIF
|
||||
{
|
||||
ExifData *ed;
|
||||
VipsExif ve;
|
||||
|
||||
if( !(ed = vips_exif_load_data_without_fix( data, data_length )) )
|
||||
return( -1 );
|
||||
|
||||
#ifdef DEBUG_VERBOSE
|
||||
show_tags( ed );
|
||||
show_values( ed );
|
||||
#endif /*DEBUG_VERBOSE*/
|
||||
|
||||
/* Look for resolution fields and use them to set the VIPS
|
||||
* xres/yres fields.
|
||||
*
|
||||
* If the fields are missing, write the ones from jfif.
|
||||
*/
|
||||
if( res_from_exif( im, ed ) &&
|
||||
vips__set_exif_resolution( ed, im ) )
|
||||
return( -1 );
|
||||
|
||||
/* Make sure all required fields are there before we attach to vips
|
||||
* metadata.
|
||||
*/
|
||||
exif_data_fix( ed );
|
||||
|
||||
/* Attach informational fields for what we find.
|
||||
*/
|
||||
ve.image = im;
|
||||
ve.ed = ed;
|
||||
exif_data_foreach_content( ed,
|
||||
(ExifDataForeachContentFunc) attach_exif_content, &ve );
|
||||
|
||||
attach_thumbnail( im, ed );
|
||||
exif_data_free( ed );
|
||||
}
|
||||
#endif /*HAVE_EXIF*/
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static int
|
||||
attach_blob( VipsImage *im, const char *field, void *data, int data_length )
|
||||
{
|
||||
@ -718,27 +288,6 @@ attach_blob( VipsImage *im, const char *field, void *data, int data_length )
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static void *
|
||||
read_jpeg_orientation_sub( VipsImage *image,
|
||||
const char *field, GValue *value, void *data )
|
||||
{
|
||||
const char *orientation_str;
|
||||
|
||||
if( vips_isprefix( "exif-", field ) &&
|
||||
vips_ispostfix( field, "-Orientation" ) &&
|
||||
!vips_image_get_string( image, field, &orientation_str ) ) {
|
||||
int orientation;
|
||||
|
||||
orientation = atoi( orientation_str );
|
||||
orientation = VIPS_CLIP( 1, orientation, 8 );
|
||||
vips_image_set_int( image, VIPS_META_ORIENTATION, orientation );
|
||||
|
||||
return( image );
|
||||
}
|
||||
|
||||
return( NULL );
|
||||
}
|
||||
|
||||
/* Number of app2 sections we can capture. Each one can be 64k, so 6400k should
|
||||
* be enough for anyone (haha).
|
||||
*/
|
||||
@ -875,13 +424,10 @@ read_jpeg_header( ReadJpeg *jpeg, VipsImage *out )
|
||||
/* Possible EXIF or XMP data.
|
||||
*/
|
||||
if( p->data_length > 4 &&
|
||||
vips_isprefix( "Exif", (char *) p->data ) ) {
|
||||
if( parse_exif( out,
|
||||
p->data, p->data_length ) ||
|
||||
attach_blob( out, VIPS_META_EXIF_NAME,
|
||||
p->data, p->data_length ) )
|
||||
vips_isprefix( "Exif", (char *) p->data ) &&
|
||||
attach_blob( out, VIPS_META_EXIF_NAME,
|
||||
p->data, p->data_length ) )
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
if( p->data_length > 4 &&
|
||||
vips_isprefix( "http", (char *) p->data ) &&
|
||||
@ -958,11 +504,8 @@ read_jpeg_header( ReadJpeg *jpeg, VipsImage *out )
|
||||
(VipsCallbackFn) vips_free, data, data_length );
|
||||
}
|
||||
|
||||
/* Orientation handling. We look for the first Orientation EXIF tag
|
||||
* (there can be many of them) and use that to set our own
|
||||
* VIPS_META_ORIENTATION.
|
||||
*/
|
||||
(void) vips_image_map( out, read_jpeg_orientation_sub, NULL );
|
||||
if( vips__exif_parse( out ) )
|
||||
return( -1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
@ -229,6 +229,9 @@ int vips__openslide_read( const char *filename, VipsImage *out,
|
||||
int vips__openslide_read_associated( const char *filename, VipsImage *out,
|
||||
const char *associated );
|
||||
|
||||
int vips__exif_parse( VipsImage *image );
|
||||
int vips__exif_update( VipsImage *image );
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /*__cplusplus*/
|
||||
|
@ -80,6 +80,8 @@
|
||||
* - switch to new orientation tag
|
||||
* 9/7/16
|
||||
* - turn off chroma subsample for Q > 90
|
||||
* 7/11/16
|
||||
* - move exif handling out to exif.c
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -124,7 +126,6 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <setjmp.h>
|
||||
#include <math.h>
|
||||
|
||||
@ -133,20 +134,6 @@
|
||||
|
||||
#include "pforeign.h"
|
||||
|
||||
#ifdef HAVE_EXIF
|
||||
#ifdef UNTAGGED_EXIF
|
||||
#include <exif-data.h>
|
||||
#include <exif-loader.h>
|
||||
#include <exif-ifd.h>
|
||||
#include <exif-utils.h>
|
||||
#else /*!UNTAGGED_EXIF*/
|
||||
#include <libexif/exif-data.h>
|
||||
#include <libexif/exif-loader.h>
|
||||
#include <libexif/exif-ifd.h>
|
||||
#include <libexif/exif-utils.h>
|
||||
#endif /*UNTAGGED_EXIF*/
|
||||
#endif /*HAVE_EXIF*/
|
||||
|
||||
#include "jpeg.h"
|
||||
|
||||
/* New output message method - send to VIPS.
|
||||
@ -236,513 +223,6 @@ write_new( VipsImage *in )
|
||||
return( write );
|
||||
}
|
||||
|
||||
#ifdef HAVE_EXIF
|
||||
static void
|
||||
vips_exif_set_int( ExifData *ed,
|
||||
ExifEntry *entry, unsigned long component, void *data )
|
||||
{
|
||||
int value = *((int *) data);
|
||||
|
||||
ExifByteOrder bo;
|
||||
size_t sizeof_component;
|
||||
size_t offset = component;
|
||||
|
||||
if( entry->components <= component ) {
|
||||
VIPS_DEBUG_MSG( "vips_exif_set_int: too few components\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
/* Wait until after the component check to make sure we cant get /0.
|
||||
*/
|
||||
bo = exif_data_get_byte_order( ed );
|
||||
sizeof_component = entry->size / entry->components;
|
||||
offset = component * sizeof_component;
|
||||
|
||||
VIPS_DEBUG_MSG( "vips_exif_set_int: %s = %d\n",
|
||||
exif_tag_get_name( entry->tag ), value );
|
||||
|
||||
if( entry->format == EXIF_FORMAT_SHORT )
|
||||
exif_set_short( entry->data + offset, bo, value );
|
||||
else if( entry->format == EXIF_FORMAT_SSHORT )
|
||||
exif_set_sshort( entry->data + offset, bo, value );
|
||||
else if( entry->format == EXIF_FORMAT_LONG )
|
||||
exif_set_long( entry->data + offset, bo, value );
|
||||
else if( entry->format == EXIF_FORMAT_SLONG )
|
||||
exif_set_slong( entry->data + offset, bo, value );
|
||||
}
|
||||
|
||||
static void
|
||||
vips_exif_double_to_rational( double value, ExifRational *rv )
|
||||
{
|
||||
/* We will usually set factors of 10, so use 1000 as the denominator
|
||||
* and it'll probably be OK.
|
||||
*/
|
||||
rv->numerator = value * 1000;
|
||||
rv->denominator = 1000;
|
||||
}
|
||||
|
||||
static void
|
||||
vips_exif_double_to_srational( double value, ExifSRational *srv )
|
||||
{
|
||||
/* We will usually set factors of 10, so use 1000 as the denominator
|
||||
* and it'll probably be OK.
|
||||
*/
|
||||
srv->numerator = value * 1000;
|
||||
srv->denominator = 1000;
|
||||
}
|
||||
|
||||
/* Parse a char* into an ExifRational. We allow floats as well.
|
||||
*/
|
||||
static void
|
||||
vips_exif_parse_rational( const char *str, ExifRational *rv )
|
||||
{
|
||||
if( sscanf( str, " %u / %u ", &rv->numerator, &rv->denominator ) == 2 )
|
||||
return;
|
||||
vips_exif_double_to_rational( g_ascii_strtod( str, NULL ), rv );
|
||||
}
|
||||
|
||||
/* Parse a char* into an ExifSRational. We allow floats as well.
|
||||
*/
|
||||
static void
|
||||
vips_exif_parse_srational( const char *str, ExifSRational *srv )
|
||||
{
|
||||
if( sscanf( str, " %d / %d ",
|
||||
&srv->numerator, &srv->denominator ) == 2 )
|
||||
return;
|
||||
vips_exif_double_to_srational( g_ascii_strtod( str, NULL ), srv );
|
||||
}
|
||||
|
||||
/* Does both signed and unsigned rationals from a char*.
|
||||
*/
|
||||
static void
|
||||
vips_exif_set_rational( ExifData *ed,
|
||||
ExifEntry *entry, unsigned long component, void *data )
|
||||
{
|
||||
char *value = (char *) data;
|
||||
|
||||
ExifByteOrder bo;
|
||||
size_t sizeof_component;
|
||||
size_t offset;
|
||||
|
||||
if( entry->components <= component ) {
|
||||
VIPS_DEBUG_MSG( "vips_exif_set_rational: "
|
||||
"too few components\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
/* Wait until after the component check to make sure we cant get /0.
|
||||
*/
|
||||
bo = exif_data_get_byte_order( ed );
|
||||
sizeof_component = entry->size / entry->components;
|
||||
offset = component * sizeof_component;
|
||||
|
||||
VIPS_DEBUG_MSG( "vips_exif_set_rational: %s = \"%s\"\n",
|
||||
exif_tag_get_name( entry->tag ), value );
|
||||
|
||||
if( entry->format == EXIF_FORMAT_RATIONAL ) {
|
||||
ExifRational rv;
|
||||
|
||||
vips_exif_parse_rational( value, &rv );
|
||||
|
||||
VIPS_DEBUG_MSG( "vips_exif_set_rational: %u / %u\n",
|
||||
rv.numerator,
|
||||
rv.denominator );
|
||||
|
||||
exif_set_rational( entry->data + offset, bo, rv );
|
||||
}
|
||||
else if( entry->format == EXIF_FORMAT_SRATIONAL ) {
|
||||
ExifSRational srv;
|
||||
|
||||
vips_exif_parse_srational( value, &srv );
|
||||
|
||||
VIPS_DEBUG_MSG( "vips_exif_set_rational: %d / %d\n",
|
||||
srv.numerator, srv.denominator );
|
||||
|
||||
exif_set_srational( entry->data + offset, bo, srv );
|
||||
}
|
||||
}
|
||||
|
||||
/* Does both signed and unsigned rationals from a double*.
|
||||
*
|
||||
* Don't change the exit entry if the value currently there is a good
|
||||
* approximation of the double we are trying to set.
|
||||
*/
|
||||
static void
|
||||
vips_exif_set_double( ExifData *ed,
|
||||
ExifEntry *entry, unsigned long component, void *data )
|
||||
{
|
||||
double value = *((double *) data);
|
||||
|
||||
ExifByteOrder bo;
|
||||
size_t sizeof_component;
|
||||
size_t offset;
|
||||
double old_value;
|
||||
|
||||
if( entry->components <= component ) {
|
||||
VIPS_DEBUG_MSG( "vips_exif_set_double: "
|
||||
"too few components\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
/* Wait until after the component check to make sure we cant get /0.
|
||||
*/
|
||||
bo = exif_data_get_byte_order( ed );
|
||||
sizeof_component = entry->size / entry->components;
|
||||
offset = component * sizeof_component;
|
||||
|
||||
VIPS_DEBUG_MSG( "vips_exif_set_double: %s = %g\n",
|
||||
exif_tag_get_name( entry->tag ), value );
|
||||
|
||||
if( entry->format == EXIF_FORMAT_RATIONAL ) {
|
||||
ExifRational rv;
|
||||
|
||||
rv = exif_get_rational( entry->data + offset, bo );
|
||||
old_value = (double) rv.numerator / rv.denominator;
|
||||
if( VIPS_FABS( old_value - value ) > 0.0001 ) {
|
||||
vips_exif_double_to_rational( value, &rv );
|
||||
|
||||
VIPS_DEBUG_MSG( "vips_exif_set_double: %u / %u\n",
|
||||
rv.numerator,
|
||||
rv.denominator );
|
||||
|
||||
exif_set_rational( entry->data + offset, bo, rv );
|
||||
}
|
||||
}
|
||||
else if( entry->format == EXIF_FORMAT_SRATIONAL ) {
|
||||
ExifSRational srv;
|
||||
|
||||
srv = exif_get_srational( entry->data + offset, bo );
|
||||
old_value = (double) srv.numerator / srv.denominator;
|
||||
if( VIPS_FABS( old_value - value ) > 0.0001 ) {
|
||||
vips_exif_double_to_srational( value, &srv );
|
||||
|
||||
VIPS_DEBUG_MSG( "vips_exif_set_double: %d / %d\n",
|
||||
srv.numerator, srv.denominator );
|
||||
|
||||
exif_set_srational( entry->data + offset, bo, srv );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef void (*write_fn)( ExifData *ed,
|
||||
ExifEntry *entry, unsigned long component, void *data );
|
||||
|
||||
/* Write a tag. Update what's there, or make a new one.
|
||||
*/
|
||||
static void
|
||||
write_tag( ExifData *ed, int ifd, ExifTag tag, write_fn fn, void *data )
|
||||
{
|
||||
ExifEntry *entry;
|
||||
|
||||
if( (entry = exif_content_get_entry( ed->ifd[ifd], tag )) ) {
|
||||
fn( ed, entry, 0, data );
|
||||
}
|
||||
else {
|
||||
entry = exif_entry_new();
|
||||
|
||||
/* tag must be set before calling exif_content_add_entry.
|
||||
*/
|
||||
entry->tag = tag;
|
||||
|
||||
exif_content_add_entry( ed->ifd[ifd], entry );
|
||||
exif_entry_initialize( entry, tag );
|
||||
exif_entry_unref( entry );
|
||||
|
||||
fn( ed, entry, 0, data );
|
||||
}
|
||||
}
|
||||
|
||||
/* This is different, we set the xres/yres from the vips header rather than
|
||||
* from the exif tags on the image metadata.
|
||||
*
|
||||
* This is also called from the jpg reader to fix up bad exif resoltion.
|
||||
*/
|
||||
int
|
||||
vips__set_exif_resolution( ExifData *ed, VipsImage *im )
|
||||
{
|
||||
double xres, yres;
|
||||
const char *p;
|
||||
int unit;
|
||||
|
||||
VIPS_DEBUG_MSG( "vips__set_exif_resolution: vips res of %g, %g\n",
|
||||
im->Xres, im->Yres );
|
||||
|
||||
/* Default to inches, more progs support it.
|
||||
*/
|
||||
unit = 2;
|
||||
if( vips_image_get_typeof( im, VIPS_META_RESOLUTION_UNIT ) &&
|
||||
!vips_image_get_string( im, VIPS_META_RESOLUTION_UNIT, &p ) ) {
|
||||
if( vips_isprefix( "cm", p ) )
|
||||
unit = 3;
|
||||
else if( vips_isprefix( "none", p ) )
|
||||
unit = 1;
|
||||
}
|
||||
|
||||
switch( unit ) {
|
||||
case 1:
|
||||
xres = im->Xres;
|
||||
yres = im->Yres;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
xres = im->Xres * 25.4;
|
||||
yres = im->Yres * 25.4;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
xres = im->Xres * 10.0;
|
||||
yres = im->Yres * 10.0;
|
||||
break;
|
||||
|
||||
default:
|
||||
vips_warn( "VipsJpeg",
|
||||
"%s", _( "unknown EXIF resolution unit" ) );
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
/* Main image xres/yres/unit are in ifd0. ifd1 has the thumbnail
|
||||
* xres/yres/unit.
|
||||
*/
|
||||
write_tag( ed, 0, EXIF_TAG_X_RESOLUTION,
|
||||
vips_exif_set_double, (void *) &xres );
|
||||
write_tag( ed, 0, EXIF_TAG_Y_RESOLUTION,
|
||||
vips_exif_set_double, (void *) &yres );
|
||||
write_tag( ed, 0, EXIF_TAG_RESOLUTION_UNIT,
|
||||
vips_exif_set_int, (void *) &unit );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
/* Exif also tracks image dimensions.
|
||||
*/
|
||||
static int
|
||||
set_exif_dimensions( ExifData *ed, VipsImage *im )
|
||||
{
|
||||
VIPS_DEBUG_MSG( "set_exif_dimensions: vips size of %d, %d\n",
|
||||
im->Xsize, im->Ysize );
|
||||
|
||||
write_tag( ed, 2, EXIF_TAG_PIXEL_X_DIMENSION,
|
||||
vips_exif_set_int, (void *) &im->Xsize );
|
||||
write_tag( ed, 2, EXIF_TAG_PIXEL_Y_DIMENSION,
|
||||
vips_exif_set_int, (void *) &im->Ysize );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
/* And orientation.
|
||||
*/
|
||||
static int
|
||||
set_exif_orientation( ExifData *ed, VipsImage *im )
|
||||
{
|
||||
int orientation;
|
||||
|
||||
/* We set the tag, even if it's been deleted, since it's a required
|
||||
* field.
|
||||
*/
|
||||
if( !vips_image_get_typeof( im, VIPS_META_ORIENTATION ) ||
|
||||
vips_image_get_int( im, VIPS_META_ORIENTATION, &orientation ) )
|
||||
orientation = 1;
|
||||
|
||||
VIPS_DEBUG_MSG( "set_exif_orientation: %d\n", orientation );
|
||||
|
||||
write_tag( ed, 0, EXIF_TAG_ORIENTATION,
|
||||
vips_exif_set_int, (void *) &orientation );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
/* See also vips_exif_to_s() ... keep in sync.
|
||||
*/
|
||||
static void
|
||||
vips_exif_from_s( ExifData *ed, ExifEntry *entry, const char *value )
|
||||
{
|
||||
unsigned long i;
|
||||
const char *p;
|
||||
|
||||
if( entry->format != EXIF_FORMAT_SHORT &&
|
||||
entry->format != EXIF_FORMAT_SSHORT &&
|
||||
entry->format != EXIF_FORMAT_LONG &&
|
||||
entry->format != EXIF_FORMAT_SLONG &&
|
||||
entry->format != EXIF_FORMAT_RATIONAL &&
|
||||
entry->format != EXIF_FORMAT_SRATIONAL )
|
||||
return;
|
||||
if( entry->components >= 10 )
|
||||
return;
|
||||
|
||||
/* Skip any leading spaces.
|
||||
*/
|
||||
p = value;
|
||||
while( *p == ' ' )
|
||||
p += 1;
|
||||
|
||||
for( i = 0; i < entry->components; i++ ) {
|
||||
if( entry->format == EXIF_FORMAT_SHORT ||
|
||||
entry->format == EXIF_FORMAT_SSHORT ||
|
||||
entry->format == EXIF_FORMAT_LONG ||
|
||||
entry->format == EXIF_FORMAT_SLONG ) {
|
||||
int value = atof( p );
|
||||
|
||||
vips_exif_set_int( ed, entry, i, &value );
|
||||
}
|
||||
else if( entry->format == EXIF_FORMAT_RATIONAL ||
|
||||
entry->format == EXIF_FORMAT_SRATIONAL )
|
||||
vips_exif_set_rational( ed, entry, i, (void *) p );
|
||||
|
||||
/* Skip to the next set of spaces, then to the beginning of
|
||||
* the next item.
|
||||
*/
|
||||
while( *p && *p != ' ' )
|
||||
p += 1;
|
||||
while( *p == ' ' )
|
||||
p += 1;
|
||||
if( !*p )
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
vips_exif_set_entry( ExifData *ed, ExifEntry *entry,
|
||||
unsigned long component, void *data )
|
||||
{
|
||||
const char *string = (const char *) data;
|
||||
|
||||
vips_exif_from_s( ed, entry, string );
|
||||
}
|
||||
|
||||
static void *
|
||||
vips_exif_image_field( VipsImage *image,
|
||||
const char *field, GValue *value, void *data )
|
||||
{
|
||||
ExifData *ed = (ExifData *) data;
|
||||
|
||||
const char *string;
|
||||
int ifd;
|
||||
const char *p;
|
||||
ExifTag tag;
|
||||
|
||||
if( !vips_isprefix( "exif-ifd", field ) )
|
||||
return( NULL );
|
||||
|
||||
/* value must be a string.
|
||||
*/
|
||||
if( vips_image_get_string( image, field, &string ) ) {
|
||||
vips_warn( "VipsJpeg", _( "bad exif meta \"%s\"" ), field );
|
||||
return( NULL );
|
||||
}
|
||||
|
||||
p = field + strlen( "exif-ifd" );
|
||||
ifd = atoi( p );
|
||||
|
||||
for( ; isdigit( *p ); p++ )
|
||||
;
|
||||
if( *p != '-' ) {
|
||||
vips_warn( "VipsJpeg", _( "bad exif meta \"%s\"" ), field );
|
||||
return( NULL );
|
||||
}
|
||||
|
||||
if( !(tag = exif_tag_from_name( p + 1 )) ) {
|
||||
vips_warn( "VipsJpeg", _( "bad exif meta \"%s\"" ), field );
|
||||
return( NULL );
|
||||
}
|
||||
|
||||
write_tag( ed, ifd, tag, vips_exif_set_entry, (void *) string );
|
||||
|
||||
return( NULL );
|
||||
}
|
||||
|
||||
typedef struct _VipsExif {
|
||||
VipsImage *image;
|
||||
ExifData *ed;
|
||||
ExifContent *content;
|
||||
GSList *to_remove;
|
||||
} VipsExif;
|
||||
|
||||
static void
|
||||
vips_exif_exif_entry( ExifEntry *entry, VipsExif *ve )
|
||||
{
|
||||
const char *tag_name;
|
||||
char vips_name_txt[256];
|
||||
VipsBuf vips_name = VIPS_BUF_STATIC( vips_name_txt );
|
||||
|
||||
if( !(tag_name = exif_tag_get_name( entry->tag )) )
|
||||
return;
|
||||
|
||||
vips_buf_appendf( &vips_name, "exif-ifd%d-%s",
|
||||
exif_entry_get_ifd( entry ), tag_name );
|
||||
|
||||
/* Does this field exist on the image? If not, schedule it for
|
||||
* removal.
|
||||
*/
|
||||
if( !vips_image_get_typeof( ve->image, vips_buf_all( &vips_name ) ) )
|
||||
ve->to_remove = g_slist_prepend( ve->to_remove, entry );
|
||||
|
||||
/* Orientation is really set from the vips
|
||||
* VIPS_META_ORIENTATION tag. If that's been deleted, we must delete
|
||||
* any matching EXIF tags too.
|
||||
*/
|
||||
if( strcmp( tag_name, "Orientation" ) == 0 &&
|
||||
vips_image_get_typeof( ve->image, VIPS_META_ORIENTATION ) )
|
||||
ve->to_remove = g_slist_prepend( ve->to_remove, entry );
|
||||
}
|
||||
|
||||
static void *
|
||||
vips_exif_exif_remove( ExifEntry *entry, VipsExif *ve )
|
||||
{
|
||||
#ifdef DEBUG
|
||||
{
|
||||
const char *tag_name;
|
||||
char vips_name_txt[256];
|
||||
VipsBuf vips_name = VIPS_BUF_STATIC( vips_name_txt );
|
||||
|
||||
tag_name = exif_tag_get_name( entry->tag );
|
||||
vips_buf_appendf( &vips_name, "exif-ifd%d-%s",
|
||||
exif_entry_get_ifd( entry ), tag_name );
|
||||
|
||||
printf( "vips_exif_exif_remove: %s\n", vips_buf_all( &vips_name ) );
|
||||
}
|
||||
#endif /*DEBUG*/
|
||||
|
||||
exif_content_remove_entry( ve->content, entry );
|
||||
|
||||
return( NULL );
|
||||
}
|
||||
|
||||
static void
|
||||
vips_exif_exif_content( ExifContent *content, VipsExif *ve )
|
||||
{
|
||||
ve->content = content;
|
||||
ve->to_remove = NULL;
|
||||
exif_content_foreach_entry( content,
|
||||
(ExifContentForeachEntryFunc) vips_exif_exif_entry, ve );
|
||||
vips_slist_map2( ve->to_remove,
|
||||
(VipsSListMap2Fn) vips_exif_exif_remove, ve, NULL );
|
||||
VIPS_FREEF( g_slist_free, ve->to_remove );
|
||||
}
|
||||
|
||||
static void
|
||||
vips_exif_update( ExifData *ed, VipsImage *image )
|
||||
{
|
||||
VipsExif ve;
|
||||
|
||||
VIPS_DEBUG_MSG( "vips_exif_update: \n" );
|
||||
|
||||
/* Walk the image and update any stuff that's been changed in image
|
||||
* metadata.
|
||||
*/
|
||||
vips_image_map( image, vips_exif_image_field, ed );
|
||||
|
||||
/* Walk the exif and look for any fields which are NOT in image
|
||||
* metadata. They must have been removed ... remove them from exif as
|
||||
* well.
|
||||
*/
|
||||
ve.image = image;
|
||||
ve.ed = ed;
|
||||
exif_data_foreach_content( ed,
|
||||
(ExifDataForeachContentFunc) vips_exif_exif_content, &ve );
|
||||
}
|
||||
|
||||
#endif /*HAVE_EXIF*/
|
||||
|
||||
static int
|
||||
write_blob( Write *write, const char *field, int app )
|
||||
{
|
||||
@ -782,85 +262,9 @@ write_blob( Write *write, const char *field, int app )
|
||||
static int
|
||||
write_exif( Write *write )
|
||||
{
|
||||
#ifdef HAVE_EXIF
|
||||
unsigned char *data;
|
||||
size_t data_length;
|
||||
unsigned int idl;
|
||||
ExifData *ed;
|
||||
|
||||
/* Either parse from the embedded EXIF, or if there's none, make
|
||||
* some fresh EXIF we can write the resolution to.
|
||||
*/
|
||||
if( vips_image_get_typeof( write->in, VIPS_META_EXIF_NAME ) ) {
|
||||
if( vips_image_get_blob( write->in, VIPS_META_EXIF_NAME,
|
||||
(void *) &data, &data_length ) )
|
||||
return( -1 );
|
||||
|
||||
if( !(ed = exif_data_new_from_data( data, data_length )) )
|
||||
return( -1 );
|
||||
}
|
||||
else {
|
||||
ed = exif_data_new();
|
||||
|
||||
exif_data_set_option( ed,
|
||||
EXIF_DATA_OPTION_FOLLOW_SPECIFICATION );
|
||||
exif_data_set_data_type( ed, EXIF_DATA_TYPE_COMPRESSED );
|
||||
exif_data_set_byte_order( ed, EXIF_BYTE_ORDER_INTEL );
|
||||
|
||||
/* Create the mandatory EXIF fields with default data.
|
||||
*/
|
||||
exif_data_fix( ed );
|
||||
}
|
||||
|
||||
/* Update EXIF tags from the image metadata.
|
||||
*/
|
||||
vips_exif_update( ed, write->in );
|
||||
|
||||
/* Update EXIF resolution from the vips image header.
|
||||
*/
|
||||
if( vips__set_exif_resolution( ed, write->in ) ) {
|
||||
exif_data_free( ed );
|
||||
if( vips__exif_update( write->in ) ||
|
||||
write_blob( write, VIPS_META_EXIF_NAME, JPEG_APP0 + 1 ) )
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
/* Update EXIF image dimensions from the vips image header.
|
||||
*/
|
||||
if( set_exif_dimensions( ed, write->in ) ) {
|
||||
exif_data_free( ed );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
/* Update EXIF orientation from the vips image header.
|
||||
*/
|
||||
if( set_exif_orientation( ed, write->in ) ) {
|
||||
exif_data_free( ed );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
/* Reserialise and write. exif_data_save_data() returns an int for some
|
||||
* reason.
|
||||
*/
|
||||
exif_data_save_data( ed, &data, &idl );
|
||||
if( !idl ) {
|
||||
vips_error( "VipsJpeg", "%s", _( "error saving EXIF" ) );
|
||||
exif_data_free( ed );
|
||||
return( -1 );
|
||||
}
|
||||
data_length = idl;
|
||||
|
||||
#ifdef DEBUG
|
||||
printf( "write_exif: attaching %zd bytes of EXIF\n", data_length );
|
||||
#endif /*DEBUG*/
|
||||
|
||||
exif_data_free( ed );
|
||||
jpeg_write_marker( &write->cinfo, JPEG_APP0 + 1, data, data_length );
|
||||
free( data );
|
||||
#else /*!HAVE_EXIF*/
|
||||
/* No libexif ... just copy the embedded EXIF over.
|
||||
*/
|
||||
if( write_blob( write, VIPS_META_EXIF_NAME, JPEG_APP0 + 1 ) )
|
||||
return( -1 );
|
||||
#endif /*!HAVE_EXIF*/
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
@ -264,7 +264,10 @@ read_header( Read *read, VipsImage *out )
|
||||
|
||||
WebPMuxDelete( mux );
|
||||
|
||||
|
||||
/* We may have read some exif ... parse into the header.
|
||||
*/
|
||||
if( vips__exif_parse( out ) )
|
||||
return( -1 );
|
||||
}
|
||||
#endif /*HAVE_LIBWEBPMUX*/
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user