double ImageProcessor::Rational2Double ( unsigned char *data, int offset, ExifByteOrder order ) const { // Explaination from GPS Correlate 'exif-gps.cpp' v 1.6.1 // What we are trying to do here is convert the three rationals: // dd/v mm/v ss/v // To a decimal // dd.dddddd... // dd/v is easy: result = dd/v. // mm/v is harder: // mm // -- / 60 = result. // v // ss/v is sorta easy. // ss // -- / 3600 = result // v // Each part is added to the final number. double ans; ExifRational er; er = exif_get_rational (data, order); ans = (double)er.numerator / (double)er.denominator; if (offset <= 0) return ans; er = exif_get_rational (data+(1*offset), order); ans = ans + ( ( (double)er.numerator / (double)er.denominator ) / 60.0 ); er = exif_get_rational (data+(2*offset), order); ans = ans + ( ( (double)er.numerator / (double)er.denominator ) / 3600.0 ); return ans; }
static gboolean parse_exif_gps_coordinate (ExifEntry *entry, gdouble *co, ExifByteOrder byte_order) { gsize val_size; ExifRational val; gdouble hour = 0, min = 0, sec = 0; if (G_UNLIKELY (!MAP_EXIF_ENTRY_IS_GPS_RATIONAL (entry))) return FALSE; val_size = exif_format_get_size (EXIF_FORMAT_RATIONAL); val = exif_get_rational (entry->data, byte_order); if (val.denominator != 0) hour = (gdouble) val.numerator / (gdouble) val.denominator; val = exif_get_rational (entry->data + val_size, byte_order); if (val.denominator != 0) min = (gdouble) val.numerator / (gdouble) val.denominator; val = exif_get_rational (entry->data + (2 * val_size), byte_order); if (val.denominator != 0) sec = (gdouble) val.numerator / (gdouble) val.denominator; if (G_LIKELY (co != NULL)) { *co = hour + (min / 60.0) + (sec / 3600.0); } return TRUE; }
static void entry_getdata_aux (lua_State *L, ExifEntry *entry, unsigned int n) { ExifFormat format = entry->format; size_t formatsize = (size_t)exif_format_get_size(format); const unsigned char *ptr = entry->data+formatsize*n; ExifByteOrder order = exif_data_get_byte_order(entry->parent->parent); switch (format) { case EXIF_FORMAT_BYTE: lua_pushinteger(L, (ExifByte)*ptr); break; case EXIF_FORMAT_SBYTE: lua_pushinteger(L, (ExifSByte)*ptr); break; case EXIF_FORMAT_SHORT: lua_pushinteger(L, exif_get_short(ptr, order)); break; case EXIF_FORMAT_SSHORT: lua_pushinteger(L, exif_get_sshort(ptr, order)); break; case EXIF_FORMAT_LONG: lua_pushnumber(L, exif_get_long(ptr, order)); break; case EXIF_FORMAT_SLONG: lua_pushinteger(L, exif_get_slong(ptr, order)); break; case EXIF_FORMAT_RATIONAL: { ExifRational rat = exif_get_rational(ptr, order); pushrational(L, rat.numerator, rat.denominator); break; } case EXIF_FORMAT_SRATIONAL: { ExifSRational rat = exif_get_srational(ptr, order); pushrational(L, rat.numerator, rat.denominator); break; } case EXIF_FORMAT_ASCII: lua_pushinteger(L, *ptr); break; default: lua_pushnil(L); break; } }
static double exifDouble(ExifEntry *entry, ExifByteOrder byte_order) { switch (entry->format) { case EXIF_FORMAT_BYTE: return double(entry->data[0]); case EXIF_FORMAT_SHORT: return double(exif_get_short(entry->data, byte_order)); case EXIF_FORMAT_LONG: return double(exif_get_long(entry->data, byte_order)); case EXIF_FORMAT_RATIONAL: { ExifRational r = exif_get_rational(entry->data, byte_order); return double(r.numerator)/double(r.denominator); } case EXIF_FORMAT_SBYTE: return double(*(signed char *)entry->data); case EXIF_FORMAT_SSHORT: return double(exif_get_sshort(entry->data, byte_order)); case EXIF_FORMAT_SLONG: return double(exif_get_slong(entry->data, byte_order)); case EXIF_FORMAT_SRATIONAL: { ExifSRational r = exif_get_srational(entry->data, byte_order); return double(r.numerator)/double(r.denominator); } case EXIF_FORMAT_FLOAT: return double(((float *)entry->data)[0]); case EXIF_FORMAT_DOUBLE: return ((double *)entry->data)[0]; default: return nan(0); } }
/* 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", vips_exif_entry_get_name( entry ), 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 ); } } }
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 get_entry_rational( ExifData *ed, ExifTag tag, double *out ) { ExifEntry *entry; ExifRational rational; if( !(entry = find_entry( ed, tag )) || entry->format != EXIF_FORMAT_RATIONAL || entry->components != 1 ) return( -1 ); rational = exif_get_rational( entry->data, exif_data_get_byte_order( ed ) ); *out = (double) rational.numerator / rational.denominator; return( 0 ); }
static int getDouble(ExifData *ed, ExifByteOrder bo, ExifTag t, double *d) { ExifEntry * e = exif_data_get_entry(ed, t); if (!e) return 0; char *b = e->data; switch (e->format) { case EXIF_FORMAT_SHORT: *d = (double) exif_get_short(b, bo); return 1; case EXIF_FORMAT_SSHORT: *d = (double) exif_get_sshort(b, bo); return 1; case EXIF_FORMAT_LONG: *d = (double) exif_get_long(b, bo); return 1; case EXIF_FORMAT_SLONG: *d = (double) exif_get_slong(b, bo); return 1; case EXIF_FORMAT_RATIONAL: { ExifRational r = exif_get_rational(b, bo); *d = (double) r.numerator / (double) r.denominator; return 1; } case EXIF_FORMAT_SRATIONAL: { ExifSRational r = exif_get_srational(b, bo); *d = (double) r.numerator / (double) r.denominator; return 1; } default: return 0; } }
void eog_exif_util_set_focal_length_label_text (GtkLabel *label, EogExifData *exif_data) { ExifEntry *entry = NULL, *entry35mm = NULL; ExifByteOrder byte_order; gfloat f_val = 0.0; gchar *fl_text = NULL,*fl35_text = NULL; /* If no ExifData is supplied the label will be * cleared later as fl35_text is NULL. */ if (exif_data != NULL) { entry = exif_data_get_entry (exif_data, EXIF_TAG_FOCAL_LENGTH); entry35mm = exif_data_get_entry (exif_data, EXIF_TAG_FOCAL_LENGTH_IN_35MM_FILM); byte_order = exif_data_get_byte_order (exif_data); } if (entry && G_LIKELY (entry->format == EXIF_FORMAT_RATIONAL)) { ExifRational value; /* Decode value by hand as libexif is not necessarily returning * it in the format we want it to be. */ value = exif_get_rational (entry->data, byte_order); /* Guard against div by zero */ if (G_LIKELY(value.denominator != 0)) f_val = (gfloat)value.numerator/ (gfloat)value.denominator; /* TRANSLATORS: This is the actual focal length used when the image was taken.*/ fl_text = g_strdup_printf (_("%.1f (lens)"), f_val); } if (entry35mm && G_LIKELY (entry35mm->format == EXIF_FORMAT_SHORT)) { ExifShort s_val; s_val = exif_get_short (entry35mm->data, byte_order); /* Print as float to get a similar look as above. */ /* TRANSLATORS: This is the equivalent focal length assuming a 35mm film camera. */ fl35_text = g_strdup_printf(_("%.1f (35mm film)"),(float)s_val); } if (fl_text) { if (fl35_text) { gchar *merged_txt; merged_txt = g_strconcat (fl35_text,", ", fl_text, NULL); gtk_label_set_text (label, merged_txt); g_free (merged_txt); } else { gtk_label_set_text (label, fl_text); } } else { /* This will also clear the label if no ExifData was supplied */ gtk_label_set_text (label, fl35_text); } g_free (fl35_text); g_free (fl_text); }
void imFileFormatJPEG::iReadExifAttrib(unsigned char* data, int data_length, imAttribTable* attrib_table) { ExifData* exif = exif_data_new_from_data(data, data_length); if (!exif) return; void* value = NULL; int c, value_size = 0; ExifByteOrder byte_order = exif_data_get_byte_order(exif); for (int ifd = 0; ifd < EXIF_IFD_COUNT; ifd++) { if (ifd == EXIF_IFD_1 || ifd == EXIF_IFD_INTEROPERABILITY) // Skip thumbnail and interoperability continue; ExifContent *content = exif->ifd[ifd]; if (content && content->count) { for (int j = 0; j < (int)content->count; j++) { ExifEntry *entry = content->entries[j]; int type = 0; const char* name = exif_tag_get_name_in_ifd(entry->tag, (ExifIfd)ifd); if (!name) continue; if (value_size < (int)entry->size) { value = realloc(value, entry->size); value_size = entry->size; } int format_size = exif_format_get_size(entry->format); if (entry->tag == EXIF_TAG_RESOLUTION_UNIT) { int res_unit = (int)exif_get_short (entry->data, byte_order); if (res_unit == 2) attrib_table->Set("ResolutionUnit", IM_BYTE, -1, "DPI"); else if (res_unit == 3) attrib_table->Set("ResolutionUnit", IM_BYTE, -1, "DPC"); continue; } switch (entry->format) { case EXIF_FORMAT_UNDEFINED: case EXIF_FORMAT_ASCII: case EXIF_FORMAT_SBYTE: case EXIF_FORMAT_BYTE: { type = IM_BYTE; imbyte *bvalue = (imbyte*)value; for (c = 0; c < (int)entry->components; c++) bvalue[c] = entry->data[c]; } break; case EXIF_FORMAT_SSHORT: { type = IM_SHORT; short *svalue = (short*)value; for (c = 0; c < (int)entry->components; c++) svalue[c] = exif_get_short(entry->data + format_size * c, byte_order); } break; case EXIF_FORMAT_SHORT: { type = IM_USHORT; imushort *usvalue = (imushort*)value; for (c = 0; c < (int)entry->components; c++) usvalue[c] = exif_get_short(entry->data + format_size * c, byte_order); } break; case EXIF_FORMAT_LONG: { type = IM_INT; int *ivalue = (int*)value; for (c = 0; c < (int)entry->components; c++) ivalue[c] = (int)exif_get_long(entry->data + format_size * c, byte_order); } break; case EXIF_FORMAT_SLONG: { type = IM_INT; int *ivalue = (int*)value; for (c = 0; c < (int)entry->components; c++) ivalue[c] = (int)exif_get_slong(entry->data + format_size * c, byte_order); } break; case EXIF_FORMAT_RATIONAL: { ExifRational v_rat; type = IM_FLOAT; float *fvalue = (float*)value; for (c = 0; c < (int)entry->components; c++) { v_rat = exif_get_rational(entry->data + format_size * c, byte_order); fvalue[c] = (float)v_rat.numerator / (float)v_rat.denominator; } } break; case EXIF_FORMAT_SRATIONAL: { ExifSRational v_srat; type = IM_FLOAT; float *fvalue = (float*)value; for (c = 0; c < (int)entry->components; c++) { v_srat = exif_get_srational(entry->data + format_size * c, byte_order); fvalue[c] = (float)v_srat.numerator / (float)v_srat.denominator; } } break; case EXIF_FORMAT_FLOAT: // defined but unsupported in libEXIF case EXIF_FORMAT_DOUBLE: // defined but unsupported in libEXIF break; } attrib_table->Set(name, type, entry->components, value); } } } if (value) free(value); exif_data_free(exif); }
static void metadataparse_exif_content_foreach_entry_func (ExifEntry * entry, void *user_data) { MEUserData *meudata = (MEUserData *) user_data; GType type = G_TYPE_NONE; ExifByteOrder byte_order; const gchar *tag; /* We need the byte order */ if (!entry || !entry->parent || !entry->parent->parent) return; tag = metadataparse_exif_get_tag_from_exif (entry->tag, &type); byte_order = exif_data_get_byte_order (entry->parent->parent); if (metadataparse_handle_unit_tags (entry, meudata, byte_order)) goto done; if (!tag) goto done; if (type == GST_TYPE_FRACTION) { gint numerator = 0; gint denominator = 1; switch (entry->format) { case EXIF_FORMAT_SRATIONAL: { ExifSRational v_srat; v_srat = exif_get_srational (entry->data, byte_order); if (v_srat.denominator) { numerator = (gint) v_srat.numerator; denominator = (gint) v_srat.denominator; } } break; case EXIF_FORMAT_RATIONAL: { ExifRational v_rat; v_rat = exif_get_rational (entry->data, byte_order); if (v_rat.denominator) { numerator = (gint) v_rat.numerator; denominator = (gint) v_rat.denominator; } if (meudata->resolution_unit == 3) { /* converts from cm to inches */ if (entry->tag == EXIF_TAG_X_RESOLUTION || entry->tag == EXIF_TAG_Y_RESOLUTION) { numerator *= 2; denominator *= 5; } } } break; default: GST_ERROR ("Unexpected Tag Type"); goto done; break; } gst_tag_list_add (meudata->taglist, meudata->mode, tag, numerator, denominator, NULL); } else if (type == GST_TYPE_BUFFER) { GstBuffer *buf = gst_buffer_new_and_alloc (entry->components); memcpy (GST_BUFFER_DATA (buf), entry->data, entry->components); gst_tag_list_add (meudata->taglist, meudata->mode, tag, buf, NULL); gst_buffer_unref (buf); } else { switch (type) { case G_TYPE_STRING: { char buf[2048]; const gchar *str = exif_entry_get_value (entry, buf, sizeof (buf)); GString *value = NULL; if (entry->tag == EXIF_TAG_DATE_TIME_DIGITIZED || entry->tag == EXIF_TAG_DATE_TIME || entry->tag == EXIF_TAG_DATE_TIME_ORIGINAL) { value = g_string_new_len (str, 20); /* 20 is enough memory to hold "YYYY-MM-DDTHH:MM:SS" */ if (metadataparse_exif_convert_to_datetime (value)) { str = value->str; } else { GST_ERROR ("Unexpected date & time format for %s", tag); str = NULL; } } if (str) gst_tag_list_add (meudata->taglist, meudata->mode, tag, str, NULL); if (value) g_string_free (value, TRUE); } break; case G_TYPE_INT: /* fall through */ case G_TYPE_UINT: { gint value; switch (entry->format) { case EXIF_FORMAT_SHORT: value = exif_get_short (entry->data, byte_order); break; case EXIF_FORMAT_LONG: value = exif_get_long (entry->data, byte_order); break; default: GST_ERROR ("Unexpected Exif Tag Type (%s - %s)", tag, exif_format_get_name (entry->format)); goto done; break; } if (entry->tag == EXIF_TAG_CONTRAST || entry->tag == EXIF_TAG_SATURATION) { switch (value) { case 0: break; case 1: value = -67; /* -100-34 /2 */ break; case 2: value = 67; /* 100+34 /2 */ break; default: GST_ERROR ("Unexpected value"); break; } } gst_tag_list_add (meudata->taglist, meudata->mode, tag, value, NULL); } break; case G_TYPE_DOUBLE: { gdouble value = 0.0; if (entry->tag == EXIF_TAG_GPS_LATITUDE || entry->tag == EXIF_TAG_GPS_LONGITUDE) { ExifRational *rt = (ExifRational *) entry->data; /* DDD - degrees */ value = (gdouble) rt->numerator / (gdouble) rt->denominator; GST_DEBUG ("deg: %lu / %lu", (gulong) rt->numerator, (gulong) rt->denominator); rt++; /* MM - minutes and SS - seconds */ GST_DEBUG ("min: %lu / %lu", (gulong) rt->numerator, (gulong) rt->denominator); value += (gdouble) rt->numerator / ((gdouble) rt->denominator * 60.0); rt++; GST_DEBUG ("sec: %lu / %lu", (gulong) rt->numerator, (gulong) rt->denominator); value += (gdouble) rt->numerator / ((gdouble) rt->denominator * 3600.0); /* apply sign */ if (entry->tag == EXIF_TAG_GPS_LATITUDE) { if (((meudata->latitude_ref == 'S') && (value > 0.0)) || ((meudata->latitude_ref == 'N') && (value < 0.0))) { value = -value; } } else { if (((meudata->longitude_ref == 'W') && (value > 0.0)) || ((meudata->longitude_ref == 'E') && (value < 0.0))) { value = -value; } } GST_DEBUG ("long/lat : %lf", value); } if (entry->tag == EXIF_TAG_GPS_ALTITUDE) { ExifRational v_rat = exif_get_rational (entry->data, byte_order); value = (gdouble) v_rat.numerator / (gdouble) v_rat.denominator; if (((meudata->altitude_ref == 1) && (value > 0.0)) || ((meudata->altitude_ref == 0) && (value < 0.0))) { value = -value; } GST_DEBUG ("altitude = %lf", value); } gst_tag_list_add (meudata->taglist, meudata->mode, tag, value, NULL); } break; default: break; } } done: { #ifndef GST_DISABLE_GST_DEBUG char buf[2048]; GST_LOG ("\n Entry %p: %s (%s)\n" " Size, Comps: %d, %d\n" " Value: %s\n" " Title: %s\n" " Description: %s\n", entry, exif_tag_get_name (entry->tag), exif_format_get_name (entry->format), entry->size, (int) (entry->components), exif_entry_get_value (entry, buf, sizeof (buf)), exif_tag_get_title (entry->tag), exif_tag_get_description (entry->tag)); #endif } return; }
static const char * eog_exif_entry_get_value (ExifEntry *e, char *buf, guint n_buf) { ExifByteOrder bo; /* For now we only want to reformat some GPS values */ if (G_LIKELY (exif_entry_get_ifd (e) != EXIF_IFD_GPS)) return exif_entry_get_value (e, buf, n_buf); bo = exif_data_get_byte_order (e->parent->parent); /* Cast to number to avoid warnings about values not in enumeration */ switch ((guint16) e->tag) { case EXIF_TAG_GPS_LATITUDE: case EXIF_TAG_GPS_LONGITUDE: { gsize rational_size; ExifRational r; gfloat h = 0., m = 0.; rational_size = exif_format_get_size (EXIF_FORMAT_RATIONAL); if (G_UNLIKELY (e->components != 3 || e->format != EXIF_FORMAT_RATIONAL)) return exif_entry_get_value (e, buf, n_buf); r = exif_get_rational (e->data, bo); if (r.denominator != 0) h = (gfloat)r.numerator / r.denominator; r = exif_get_rational (e->data + rational_size, bo); if (r.denominator != 0) m = (gfloat)r.numerator / (gfloat)r.denominator; r = exif_get_rational (e->data + (2 * rational_size), bo); if (r.numerator != 0 && r.denominator != 0) { gfloat s; s = (gfloat)r.numerator / (gfloat)r.denominator; g_snprintf (buf, n_buf, "%.0f° %.0f' %.2f\"", h, m, s); } else { g_snprintf (buf, n_buf, "%.0f° %.2f'", h, m); } break; } case EXIF_TAG_GPS_LATITUDE_REF: case EXIF_TAG_GPS_LONGITUDE_REF: { if (G_UNLIKELY (e->components != 2 || e->format != EXIF_FORMAT_ASCII)) return exif_entry_get_value (e, buf, n_buf); switch (e->data[0]) { case 'N': g_snprintf (buf, n_buf, "%s", _("North")); break; case 'E': g_snprintf (buf, n_buf, "%s", _("East")); break; case 'W': g_snprintf (buf, n_buf, "%s", _("West")); break; case 'S': g_snprintf (buf, n_buf, "%s", _("South")); break; default: return exif_entry_get_value (e, buf, n_buf); break; } break; } default: return exif_entry_get_value (e, buf, n_buf); break; } return buf; }
static void foreach_exif_entry( ExifEntry * entry , void * _closure ) { if ( ! entry ) { return; } //......................................................................... // Bail out of types we don't handle switch( entry->format ) { case EXIF_FORMAT_UNDEFINED: case EXIF_FORMAT_FLOAT: case EXIF_FORMAT_DOUBLE: return; default: break; } //......................................................................... unsigned char component_size = exif_format_get_size( entry->format ); ExifIfd ifd = exif_content_get_ifd( entry->parent ); const char * tag_name = exif_tag_get_name_in_ifd( entry->tag , ifd ); if ( ! tag_name || ! entry->data || ! entry->size || ! component_size || ! entry->components ) { return; } //......................................................................... // Add a prefix based on the IFD String name( tag_name ); switch( ifd ) { case EXIF_IFD_0: name = "IMAGE/" + name; break; case EXIF_IFD_1: name = "THUMBNAIL/" + name; break; case EXIF_IFD_EXIF: name = "EXIF/" + name; break; case EXIF_IFD_GPS: name = "GPS/" + name; break; case EXIF_IFD_INTEROPERABILITY: name = "INTEROP/" + name; break; default: return; } ExifClosure * closure = ( ExifClosure * ) _closure; JSON::Object * tags = closure->tags; //......................................................................... // ASCII ones are easy if ( entry->format == EXIF_FORMAT_ASCII ) { (*tags)[ name ] = String( ( const char * ) entry->data , entry->size ); return; } //......................................................................... if ( ( entry->components * component_size ) != entry->size ) { return; } ExifByteOrder byte_order = exif_data_get_byte_order( closure->exif_data ); const unsigned char * data = entry->data; JSON::Array array; for ( unsigned long i = 0; i < entry->components; ++i ) { switch( entry->format ) { case EXIF_FORMAT_BYTE: array.append( JSON::Value( int( * data ) ) ); break; case EXIF_FORMAT_SHORT: array.append( JSON::Value( int( exif_get_short( data , byte_order ) ) ) ); break; case EXIF_FORMAT_LONG: array.append( JSON::Value( int( exif_get_long( data , byte_order ) ) ) ); break; case EXIF_FORMAT_SBYTE: array.append( JSON::Value( int( * ( ( const char * ) data ) ) ) ); break; case EXIF_FORMAT_SSHORT: array.append( JSON::Value( exif_get_sshort( data , byte_order ) ) ); break; case EXIF_FORMAT_SLONG: array.append( JSON::Value( exif_get_slong( data , byte_order ) ) ); break; // TODO: I don't like representing a rational number as a string with a slash, case EXIF_FORMAT_SRATIONAL: { ExifSRational r = exif_get_srational( data , byte_order ); array.append( Util::format("%ld/%ld" , r.numerator , r.denominator ) ); break; } case EXIF_FORMAT_RATIONAL: { ExifRational r = exif_get_rational( data , byte_order ); array.append( Util::format("%lu/%lu" , r.numerator , r.denominator ) ); break; } default: break; } data += component_size; } if ( array.size() == 1 ) { (*tags)[ name ] = array[ 0 ]; } else if ( array.size() > 1 ) { (*tags)[ name ] = array; } }