static void *bg_new_img(void *data) { /* so we can poll for it */ block_usr1_signal(); struct imv_loader *ldr = data; char path[PATH_MAX] = "-"; pthread_mutex_lock(&ldr->lock); int from_stdin = !strncmp(path, ldr->path, 2); if(!from_stdin) { (void)snprintf(path, PATH_MAX, "%s", ldr->path); } pthread_mutex_unlock(&ldr->lock); FREE_IMAGE_FORMAT fmt; if (from_stdin) { pthread_mutex_lock(&ldr->lock); ldr->fi_buffer = FreeImage_OpenMemory(ldr->buffer, ldr->buffer_size); fmt = FreeImage_GetFileTypeFromMemory(ldr->fi_buffer, 0); pthread_mutex_unlock(&ldr->lock); } else { fmt = FreeImage_GetFileType(path, 0); } if(fmt == FIF_UNKNOWN) { if (from_stdin) { pthread_mutex_lock(&ldr->lock); FreeImage_CloseMemory(ldr->fi_buffer); pthread_mutex_unlock(&ldr->lock); } error_occurred(ldr); return NULL; } int num_frames = 1; FIMULTIBITMAP *mbmp = NULL; FIBITMAP *bmp = NULL; int width, height; int raw_frame_time = 100; /* default to 100 */ if(fmt == FIF_GIF) { if(from_stdin) { pthread_mutex_lock(&ldr->lock); mbmp = FreeImage_LoadMultiBitmapFromMemory(FIF_GIF, ldr->fi_buffer, GIF_LOAD256); pthread_mutex_unlock(&ldr->lock); } else { mbmp = FreeImage_OpenMultiBitmap(FIF_GIF, path, /* don't create file */ 0, /* read only */ 1, /* keep in memory */ 1, /* flags */ GIF_LOAD256); } if(!mbmp) { error_occurred(ldr); return NULL; } num_frames = FreeImage_GetPageCount(mbmp); FIBITMAP *frame = FreeImage_LockPage(mbmp, 0); width = FreeImage_GetWidth(frame); height = FreeImage_GetHeight(frame); bmp = FreeImage_ConvertTo32Bits(frame); /* get duration of first frame */ FITAG *tag = NULL; FreeImage_GetMetadata(FIMD_ANIMATION, frame, "FrameTime", &tag); if(FreeImage_GetTagValue(tag)) { raw_frame_time = *(int*)FreeImage_GetTagValue(tag); } FreeImage_UnlockPage(mbmp, frame, 0); } else { /* Future TODO: If we load image line-by-line we could stop loading large * ones before wasting much more time/memory on them. */ int flags = (fmt == FIF_JPEG) ? JPEG_EXIFROTATE : 0; FIBITMAP *image; if(from_stdin) { pthread_mutex_lock(&ldr->lock); image = FreeImage_LoadFromMemory(fmt, ldr->fi_buffer, flags); pthread_mutex_unlock(&ldr->lock); } else { image = FreeImage_Load(fmt, path, flags); } if(!image) { error_occurred(ldr); pthread_mutex_lock(&ldr->lock); FreeImage_CloseMemory(ldr->fi_buffer); ldr->fi_buffer = NULL; pthread_mutex_unlock(&ldr->lock); return NULL; } /* Check for cancellation before we convert pixel format */ if(is_thread_cancelled()) { FreeImage_Unload(image); return NULL; } width = FreeImage_GetWidth(bmp); height = FreeImage_GetHeight(bmp); bmp = FreeImage_ConvertTo32Bits(image); FreeImage_Unload(image); } /* now update the loader */ pthread_mutex_lock(&ldr->lock); /* check for cancellation before finishing */ if(is_thread_cancelled()) { if(mbmp) { FreeImage_CloseMultiBitmap(mbmp, 0); } if(bmp) { FreeImage_Unload(bmp); } pthread_mutex_unlock(&ldr->lock); return NULL; } if(ldr->mbmp) { FreeImage_CloseMultiBitmap(ldr->mbmp, 0); } if(ldr->bmp) { FreeImage_Unload(ldr->bmp); } ldr->mbmp = mbmp; ldr->bmp = bmp; if(ldr->out_bmp) { FreeImage_Unload(ldr->out_bmp); } ldr->out_bmp = FreeImage_Clone(bmp); ldr->out_is_new_image = 1; ldr->width = width; ldr->height = height; ldr->cur_frame = 0; ldr->next_frame = 1; ldr->num_frames = num_frames; ldr->frame_time = (double)raw_frame_time * 0.0001; pthread_mutex_unlock(&ldr->lock); return NULL; }
static void *bg_next_frame(void *data) { struct imv_loader *ldr = data; pthread_mutex_lock(&ldr->lock); int num_frames = ldr->num_frames; if(num_frames < 2) { pthread_mutex_unlock(&ldr->lock); return NULL; } FITAG *tag = NULL; char disposal_method = 0; int frame_time = 0; short top = 0; short left = 0; ldr->cur_frame = ldr->next_frame; ldr->next_frame = (ldr->cur_frame + 1) % ldr->num_frames; FIBITMAP *frame = FreeImage_LockPage(ldr->mbmp, ldr->cur_frame); FIBITMAP *frame32 = FreeImage_ConvertTo32Bits(frame); /* First frame is always going to use the raw frame */ if(ldr->cur_frame > 0) { FreeImage_GetMetadata(FIMD_ANIMATION, frame, "DisposalMethod", &tag); if(FreeImage_GetTagValue(tag)) { disposal_method = *(char*)FreeImage_GetTagValue(tag); } } FreeImage_GetMetadata(FIMD_ANIMATION, frame, "FrameLeft", &tag); if(FreeImage_GetTagValue(tag)) { left = *(short*)FreeImage_GetTagValue(tag); } FreeImage_GetMetadata(FIMD_ANIMATION, frame, "FrameTop", &tag); if(FreeImage_GetTagValue(tag)) { top = *(short*)FreeImage_GetTagValue(tag); } FreeImage_GetMetadata(FIMD_ANIMATION, frame, "FrameTime", &tag); if(FreeImage_GetTagValue(tag)) { frame_time = *(int*)FreeImage_GetTagValue(tag); } /* some gifs don't provide a frame time at all */ if(frame_time == 0) { frame_time = 100; } ldr->frame_time += frame_time * 0.001; FreeImage_UnlockPage(ldr->mbmp, frame, 0); /* If this frame is inset, we need to expand it for compositing */ if(ldr->width != (int)FreeImage_GetWidth(frame32) || ldr->height != (int)FreeImage_GetHeight(frame32)) { FIBITMAP *expanded = FreeImage_Allocate(ldr->width, ldr->height, 32, 0,0,0); FreeImage_Paste(expanded, frame32, left, top, 255); FreeImage_Unload(frame32); frame32 = expanded; } switch(disposal_method) { case 0: /* nothing specified, fall through to compositing */ case 1: /* composite over previous frame */ if(ldr->bmp && ldr->cur_frame > 0) { FIBITMAP *bg_frame = FreeImage_ConvertTo24Bits(ldr->bmp); FreeImage_Unload(ldr->bmp); FIBITMAP *comp = FreeImage_Composite(frame32, 1, NULL, bg_frame); FreeImage_Unload(bg_frame); FreeImage_Unload(frame32); ldr->bmp = comp; } else { /* No previous frame, just render directly */ if(ldr->bmp) { FreeImage_Unload(ldr->bmp); } ldr->bmp = frame32; } break; case 2: /* TODO - set to background, composite over that */ if(ldr->bmp) { FreeImage_Unload(ldr->bmp); } ldr->bmp = frame32; break; case 3: /* TODO - restore to previous content */ if(ldr->bmp) { FreeImage_Unload(ldr->bmp); } ldr->bmp = frame32; break; } if(ldr->out_bmp) { FreeImage_Unload(ldr->out_bmp); } ldr->out_bmp = FreeImage_Clone(ldr->bmp); pthread_mutex_unlock(&ldr->lock); return NULL; }
/** Convert a tag to a C string */ static const char* ConvertAnyTag(FITAG *tag) { char format[MAX_TEXT_EXTENT]; static std::string buffer; DWORD i; if(!tag) return NULL; buffer.erase(); // convert the tag value to a string buffer FREE_IMAGE_MDTYPE tag_type = FreeImage_GetTagType(tag); DWORD tag_count = FreeImage_GetTagCount(tag); switch(tag_type) { case FIDT_BYTE: // N x 8-bit unsigned integer { BYTE *pvalue = (BYTE*)FreeImage_GetTagValue(tag); sprintf(format, "%ld", (LONG) pvalue[0]); buffer += format; for(i = 1; i < tag_count; i++) { sprintf(format, " %ld", (LONG) pvalue[i]); buffer += format; } break; } case FIDT_SHORT: // N x 16-bit unsigned integer { unsigned short *pvalue = (unsigned short *)FreeImage_GetTagValue(tag); sprintf(format, "%hu", pvalue[0]); buffer += format; for(i = 1; i < tag_count; i++) { sprintf(format, " %hu", pvalue[i]); buffer += format; } break; } case FIDT_LONG: // N x 32-bit unsigned integer { DWORD *pvalue = (DWORD *)FreeImage_GetTagValue(tag); sprintf(format, "%lu", pvalue[0]); buffer += format; for(i = 1; i < tag_count; i++) { sprintf(format, " %lu", pvalue[i]); buffer += format; } break; } case FIDT_RATIONAL: // N x 64-bit unsigned fraction { DWORD *pvalue = (DWORD*)FreeImage_GetTagValue(tag); sprintf(format, "%ld/%ld", pvalue[0], pvalue[1]); buffer += format; for(i = 1; i < tag_count; i++) { sprintf(format, " %ld/%ld", pvalue[2*i], pvalue[2*i+1]); buffer += format; } break; } case FIDT_SBYTE: // N x 8-bit signed integer { char *pvalue = (char*)FreeImage_GetTagValue(tag); sprintf(format, "%ld", (LONG) pvalue[0]); buffer += format; for(i = 1; i < tag_count; i++) { sprintf(format, " %ld", (LONG) pvalue[i]); buffer += format; } break; } case FIDT_SSHORT: // N x 16-bit signed integer { short *pvalue = (short *)FreeImage_GetTagValue(tag); sprintf(format, "%hd", pvalue[0]); buffer += format; for(i = 1; i < tag_count; i++) { sprintf(format, " %hd", pvalue[i]); buffer += format; } break; } case FIDT_SLONG: // N x 32-bit signed integer { LONG *pvalue = (LONG *)FreeImage_GetTagValue(tag); sprintf(format, "%ld", pvalue[0]); buffer += format; for(i = 1; i < tag_count; i++) { sprintf(format, " %ld", pvalue[i]); buffer += format; } break; } case FIDT_SRATIONAL:// N x 64-bit signed fraction { LONG *pvalue = (LONG*)FreeImage_GetTagValue(tag); sprintf(format, "%ld/%ld", pvalue[0], pvalue[1]); buffer += format; for(i = 1; i < tag_count; i++) { sprintf(format, " %ld/%ld", pvalue[2*i], pvalue[2*i+1]); buffer += format; } break; } case FIDT_FLOAT: // N x 32-bit IEEE floating point { float *pvalue = (float *)FreeImage_GetTagValue(tag); sprintf(format, "%f", (double) pvalue[0]); buffer += format; for(i = 1; i < tag_count; i++) { sprintf(format, "%f", (double) pvalue[i]); buffer += format; } break; } case FIDT_DOUBLE: // N x 64-bit IEEE floating point { double *pvalue = (double *)FreeImage_GetTagValue(tag); sprintf(format, "%f", pvalue[0]); buffer += format; for(i = 1; i < tag_count; i++) { sprintf(format, "%f", pvalue[i]); buffer += format; } break; } case FIDT_IFD: // N x 32-bit unsigned integer (offset) { DWORD *pvalue = (DWORD *)FreeImage_GetTagValue(tag); sprintf(format, "%X", pvalue[0]); buffer += format; for(i = 1; i < tag_count; i++) { sprintf(format, " %X", pvalue[i]); buffer += format; } break; } case FIDT_PALETTE: // N x 32-bit RGBQUAD { RGBQUAD *pvalue = (RGBQUAD *)FreeImage_GetTagValue(tag); sprintf(format, "(%d,%d,%d,%d)", pvalue[0].rgbRed, pvalue[0].rgbGreen, pvalue[0].rgbBlue, pvalue[0].rgbReserved); buffer += format; for(i = 1; i < tag_count; i++) { sprintf(format, " (%d,%d,%d,%d)", pvalue[i].rgbRed, pvalue[i].rgbGreen, pvalue[i].rgbBlue, pvalue[i].rgbReserved); buffer += format; } break; } case FIDT_LONG8: // N x 64-bit unsigned integer { FIUINT64 *pvalue = (FIUINT64 *)FreeImage_GetTagValue(tag); sprintf(format, "%ld", pvalue[0]); buffer += format; for(i = 1; i < tag_count; i++) { sprintf(format, "%ld", pvalue[i]); buffer += format; } break; } case FIDT_IFD8: // N x 64-bit unsigned integer (offset) { FIUINT64 *pvalue = (FIUINT64 *)FreeImage_GetTagValue(tag); sprintf(format, "%X", pvalue[0]); buffer += format; for(i = 1; i < tag_count; i++) { sprintf(format, "%X", pvalue[i]); buffer += format; } break; } case FIDT_SLONG8: // N x 64-bit signed integer { FIINT64 *pvalue = (FIINT64 *)FreeImage_GetTagValue(tag); sprintf(format, "%ld", pvalue[0]); buffer += format; for(i = 1; i < tag_count; i++) { sprintf(format, "%ld", pvalue[i]); buffer += format; } break; } case FIDT_ASCII: // 8-bit bytes w/ last byte null case FIDT_UNDEFINED:// 8-bit untyped data default: { int max_size = MIN((int)FreeImage_GetTagLength(tag), (int)MAX_TEXT_EXTENT); if(max_size == MAX_TEXT_EXTENT) max_size--; memcpy(format, (char*)FreeImage_GetTagValue(tag), max_size); format[max_size] = '\0'; buffer += format; break; } } return buffer.c_str(); }
/** Convert a Exif GPS tag to a C string */ static const char* ConvertExifGPSTag(FITAG *tag) { char format[MAX_TEXT_EXTENT]; static std::string buffer; if(!tag) return NULL; buffer.erase(); // convert the tag value to a string buffer switch(FreeImage_GetTagID(tag)) { case TAG_GPS_LATITUDE: case TAG_GPS_LONGITUDE: case TAG_GPS_TIME_STAMP: { DWORD *pvalue = (DWORD*)FreeImage_GetTagValue(tag); if(FreeImage_GetTagLength(tag) == 24) { // dd:mm:ss or hh:mm:ss int dd = 0, mm = 0; double ss = 0; // convert to seconds if(pvalue[1]) ss += ((double)pvalue[0] / (double)pvalue[1]) * 3600; if(pvalue[3]) ss += ((double)pvalue[2] / (double)pvalue[3]) * 60; if(pvalue[5]) ss += ((double)pvalue[4] / (double)pvalue[5]); // convert to dd:mm:ss.ss dd = (int)(ss / 3600); mm = (int)(ss / 60) - dd * 60; ss = ss - dd * 3600 - mm * 60; sprintf(format, "%d:%d:%.2f", dd, mm, ss); buffer += format; return buffer.c_str(); } } break; case TAG_GPS_VERSION_ID: case TAG_GPS_LATITUDE_REF: case TAG_GPS_LONGITUDE_REF: case TAG_GPS_ALTITUDE_REF: case TAG_GPS_ALTITUDE: case TAG_GPS_SATELLITES: case TAG_GPS_STATUS: case TAG_GPS_MEASURE_MODE: case TAG_GPS_DOP: case TAG_GPS_SPEED_REF: case TAG_GPS_SPEED: case TAG_GPS_TRACK_REF: case TAG_GPS_TRACK: case TAG_GPS_IMG_DIRECTION_REF: case TAG_GPS_IMG_DIRECTION: case TAG_GPS_MAP_DATUM: case TAG_GPS_DEST_LATITUDE_REF: case TAG_GPS_DEST_LATITUDE: case TAG_GPS_DEST_LONGITUDE_REF: case TAG_GPS_DEST_LONGITUDE: case TAG_GPS_DEST_BEARING_REF: case TAG_GPS_DEST_BEARING: case TAG_GPS_DEST_DISTANCE_REF: case TAG_GPS_DEST_DISTANCE: case TAG_GPS_PROCESSING_METHOD: case TAG_GPS_AREA_INFORMATION: case TAG_GPS_DATE_STAMP: case TAG_GPS_DIFFERENTIAL: break; } return ConvertAnyTag(tag); }
FREE_IMAGE_COLOR_TYPE DLL_CALLCONV FreeImage_GetColorType(FIBITMAP *dib) { RGBQUAD *rgb; const FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(dib); // special bitmap type if(image_type != FIT_BITMAP) { switch(image_type) { case FIT_UINT16: { // 16-bit greyscale TIF can be either FIC_MINISBLACK (the most common case) or FIC_MINISWHITE // you can check this using EXIF_MAIN metadata FITAG *photometricTag = NULL; if(FreeImage_GetMetadata(FIMD_EXIF_MAIN, dib, "PhotometricInterpretation", &photometricTag)) { const short *value = (short*)FreeImage_GetTagValue(photometricTag); // PHOTOMETRIC_MINISWHITE = 0 => min value is white // PHOTOMETRIC_MINISBLACK = 1 => min value is black return (*value == 0) ? FIC_MINISWHITE : FIC_MINISBLACK; } return FIC_MINISBLACK; } break; case FIT_RGB16: case FIT_RGBF: return FIC_RGB; case FIT_RGBA16: case FIT_RGBAF: return FIC_RGBALPHA; } return FIC_MINISBLACK; } // standard image type switch (FreeImage_GetBPP(dib)) { case 1: { rgb = FreeImage_GetPalette(dib); if ((rgb->rgbRed == 0) && (rgb->rgbGreen == 0) && (rgb->rgbBlue == 0)) { rgb++; if ((rgb->rgbRed == 255) && (rgb->rgbGreen == 255) && (rgb->rgbBlue == 255)) { return FIC_MINISBLACK; } } if ((rgb->rgbRed == 255) && (rgb->rgbGreen == 255) && (rgb->rgbBlue == 255)) { rgb++; if ((rgb->rgbRed == 0) && (rgb->rgbGreen == 0) && (rgb->rgbBlue == 0)) { return FIC_MINISWHITE; } } return FIC_PALETTE; } case 4: case 8: // Check if the DIB has a color or a greyscale palette { int ncolors = FreeImage_GetColorsUsed(dib); int minisblack = 1; rgb = FreeImage_GetPalette(dib); for (int i = 0; i < ncolors; i++) { if ((rgb->rgbRed != rgb->rgbGreen) || (rgb->rgbRed != rgb->rgbBlue)) { return FIC_PALETTE; } // The DIB has a color palette if the greyscale isn't a linear ramp // Take care of reversed grey images if (rgb->rgbRed != i) { if ((ncolors-i-1) != rgb->rgbRed) { return FIC_PALETTE; } else { minisblack = 0; } } rgb++; } return minisblack ? FIC_MINISBLACK : FIC_MINISWHITE; } case 16: case 24: return FIC_RGB; case 32: { if (FreeImage_GetICCProfile(dib)->flags & FIICC_COLOR_IS_CMYK) { return FIC_CMYK; } if( FreeImage_HasPixels(dib) ) { // check for fully opaque alpha layer for (unsigned y = 0; y < FreeImage_GetHeight(dib); y++) { rgb = (RGBQUAD *)FreeImage_GetScanLine(dib, y); for (unsigned x = 0; x < FreeImage_GetWidth(dib); x++) { if (rgb[x].rgbReserved != 0xFF) { return FIC_RGBALPHA; } } } return FIC_RGB; } return FIC_RGBALPHA; } default : return FIC_MINISBLACK; } }
/** Convert a Exif tag to a C string */ static const char* ConvertExifTag(FITAG *tag) { char format[MAX_TEXT_EXTENT]; static std::string buffer; if(!tag) return NULL; buffer.erase(); // convert the tag value to a string buffer switch(FreeImage_GetTagID(tag)) { case TAG_ORIENTATION: { unsigned short orientation = *((unsigned short *)FreeImage_GetTagValue(tag)); switch (orientation) { case 1: return "top, left side"; case 2: return "top, right side"; case 3: return "bottom, right side"; case 4: return "bottom, left side"; case 5: return "left side, top"; case 6: return "right side, top"; case 7: return "right side, bottom"; case 8: return "left side, bottom"; default: break; } } break; case TAG_REFERENCE_BLACK_WHITE: { DWORD *pvalue = (DWORD*)FreeImage_GetTagValue(tag); if(FreeImage_GetTagLength(tag) == 48) { // reference black point value and reference white point value (ReferenceBlackWhite) int blackR = 0, whiteR = 0, blackG = 0, whiteG = 0, blackB = 0, whiteB = 0; if(pvalue[1]) blackR = (int)(pvalue[0] / pvalue[1]); if(pvalue[3]) whiteR = (int)(pvalue[2] / pvalue[3]); if(pvalue[5]) blackG = (int)(pvalue[4] / pvalue[5]); if(pvalue[7]) whiteG = (int)(pvalue[6] / pvalue[7]); if(pvalue[9]) blackB = (int)(pvalue[8] / pvalue[9]); if(pvalue[11]) whiteB = (int)(pvalue[10] / pvalue[11]); sprintf(format, "[%d,%d,%d] [%d,%d,%d]", blackR, blackG, blackB, whiteR, whiteG, whiteB); buffer += format; return buffer.c_str(); } } break; case TAG_COLOR_SPACE: { unsigned short colorSpace = *((unsigned short *)FreeImage_GetTagValue(tag)); if (colorSpace == 1) { return "sRGB"; } else if (colorSpace == 65535) { return "Undefined"; } else { return "Unknown"; } } break; case TAG_COMPONENTS_CONFIGURATION: { const char *componentStrings[7] = {"", "Y", "Cb", "Cr", "R", "G", "B"}; BYTE *pvalue = (BYTE*)FreeImage_GetTagValue(tag); for(DWORD i = 0; i < MIN((DWORD)4, FreeImage_GetTagCount(tag)); i++) { int j = pvalue[i]; if(j > 0 && j < 7) buffer += componentStrings[j]; } return buffer.c_str(); } break; case TAG_COMPRESSED_BITS_PER_PIXEL: { FIRational r(tag); buffer = r.toString(); if(buffer == "1") buffer += " bit/pixel"; else buffer += " bits/pixel"; return buffer.c_str(); } break; case TAG_X_RESOLUTION: case TAG_Y_RESOLUTION: case TAG_FOCAL_PLANE_X_RES: case TAG_FOCAL_PLANE_Y_RES: case TAG_BRIGHTNESS_VALUE: case TAG_EXPOSURE_BIAS_VALUE: { FIRational r(tag); buffer = r.toString(); return buffer.c_str(); } break; case TAG_RESOLUTION_UNIT: case TAG_FOCAL_PLANE_UNIT: { unsigned short resolutionUnit = *((unsigned short *)FreeImage_GetTagValue(tag)); switch (resolutionUnit) { case 1: return "(No unit)"; case 2: return "inches"; case 3: return "cm"; default: break; } } break; case TAG_YCBCR_POSITIONING: { unsigned short yCbCrPosition = *((unsigned short *)FreeImage_GetTagValue(tag)); switch (yCbCrPosition) { case 1: return "Center of pixel array"; case 2: return "Datum point"; default: break; } } break; case TAG_EXPOSURE_TIME: { FIRational r(tag); buffer = r.toString(); buffer += " sec"; return buffer.c_str(); } break; case TAG_SHUTTER_SPEED_VALUE: { FIRational r(tag); LONG apexValue = r.longValue(); LONG apexPower = 1 << apexValue; sprintf(format, "1/%d sec", (int)apexPower); buffer += format; return buffer.c_str(); } break; case TAG_APERTURE_VALUE: case TAG_MAX_APERTURE_VALUE: { FIRational r(tag); double apertureApex = r.doubleValue(); double rootTwo = sqrt((double)2); double fStop = pow(rootTwo, apertureApex); sprintf(format, "F%.1f", fStop); buffer += format; return buffer.c_str(); } break; case TAG_FNUMBER: { FIRational r(tag); double fnumber = r.doubleValue(); sprintf(format, "F%.1f", fnumber); buffer += format; return buffer.c_str(); } break; case TAG_FOCAL_LENGTH: { FIRational r(tag); double focalLength = r.doubleValue(); sprintf(format, "%.1f mm", focalLength); buffer += format; return buffer.c_str(); } break; case TAG_FOCAL_LENGTH_IN_35MM_FILM: { unsigned short focalLength = *((unsigned short *)FreeImage_GetTagValue(tag)); sprintf(format, "%hu mm", focalLength); buffer += format; return buffer.c_str(); } break; case TAG_FLASH: { unsigned short flash = *((unsigned short *)FreeImage_GetTagValue(tag)); switch(flash) { case 0x0000: return "Flash did not fire"; case 0x0001: return "Flash fired"; case 0x0005: return "Strobe return light not detected"; case 0x0007: return "Strobe return light detected"; case 0x0009: return "Flash fired, compulsory flash mode"; case 0x000D: return "Flash fired, compulsory flash mode, return light not detected"; case 0x000F: return "Flash fired, compulsory flash mode, return light detected"; case 0x0010: return "Flash did not fire, compulsory flash mode"; case 0x0018: return "Flash did not fire, auto mode"; case 0x0019: return "Flash fired, auto mode"; case 0x001D: return "Flash fired, auto mode, return light not detected"; case 0x001F: return "Flash fired, auto mode, return light detected"; case 0x0020: return "No flash function"; case 0x0041: return "Flash fired, red-eye reduction mode"; case 0x0045: return "Flash fired, red-eye reduction mode, return light not detected"; case 0x0047: return "Flash fired, red-eye reduction mode, return light detected"; case 0x0049: return "Flash fired, compulsory flash mode, red-eye reduction mode"; case 0x004D: return "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected"; case 0x004F: return "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected"; case 0x0059: return "Flash fired, auto mode, red-eye reduction mode"; case 0x005D: return "Flash fired, auto mode, return light not detected, red-eye reduction mode"; case 0x005F: return "Flash fired, auto mode, return light detected, red-eye reduction mode"; default: sprintf(format, "Unknown (%d)", flash); buffer += format; return buffer.c_str(); } } break; case TAG_SCENE_TYPE: { BYTE sceneType = *((BYTE*)FreeImage_GetTagValue(tag)); if (sceneType == 1) { return "Directly photographed image"; } else { sprintf(format, "Unknown (%d)", sceneType); buffer += format; return buffer.c_str(); } } break; case TAG_SUBJECT_DISTANCE: { FIRational r(tag); if(r.getNumerator() == 0xFFFFFFFF) { return "Infinity"; } else if(r.getNumerator() == 0) { return "Distance unknown"; } else { double distance = r.doubleValue(); sprintf(format, "%.3f meters", distance); buffer += format; return buffer.c_str(); } } break; case TAG_METERING_MODE: { unsigned short meteringMode = *((unsigned short *)FreeImage_GetTagValue(tag)); switch (meteringMode) { case 0: return "Unknown"; case 1: return "Average"; case 2: return "Center weighted average"; case 3: return "Spot"; case 4: return "Multi-spot"; case 5: return "Multi-segment"; case 6: return "Partial"; case 255: return "(Other)"; default: return ""; } } break; case TAG_LIGHT_SOURCE: { unsigned short lightSource = *((unsigned short *)FreeImage_GetTagValue(tag)); switch (lightSource) { case 0: return "Unknown"; case 1: return "Daylight"; case 2: return "Fluorescent"; case 3: return "Tungsten (incandescent light)"; case 4: return "Flash"; case 9: return "Fine weather"; case 10: return "Cloudy weather"; case 11: return "Shade"; case 12: return "Daylight fluorescent (D 5700 - 7100K)"; case 13: return "Day white fluorescent (N 4600 - 5400K)"; case 14: return "Cool white fluorescent (W 3900 - 4500K)"; case 15: return "White fluorescent (WW 3200 - 3700K)"; case 17: return "Standard light A"; case 18: return "Standard light B"; case 19: return "Standard light C"; case 20: return "D55"; case 21: return "D65"; case 22: return "D75"; case 23: return "D50"; case 24: return "ISO studio tungsten"; case 255: return "(Other)"; default: return ""; } } break; case TAG_SENSING_METHOD: { unsigned short sensingMethod = *((unsigned short *)FreeImage_GetTagValue(tag)); switch (sensingMethod) { case 1: return "(Not defined)"; case 2: return "One-chip color area sensor"; case 3: return "Two-chip color area sensor"; case 4: return "Three-chip color area sensor"; case 5: return "Color sequential area sensor"; case 7: return "Trilinear sensor"; case 8: return "Color sequential linear sensor"; default: return ""; } } break; case TAG_FILE_SOURCE: { BYTE fileSource = *((BYTE*)FreeImage_GetTagValue(tag)); if (fileSource == 3) { return "Digital Still Camera (DSC)"; } else { sprintf(format, "Unknown (%d)", fileSource); buffer += format; return buffer.c_str(); } } break; case TAG_EXPOSURE_PROGRAM: { unsigned short exposureProgram = *((unsigned short *)FreeImage_GetTagValue(tag)); switch (exposureProgram) { case 1: return "Manual control"; case 2: return "Program normal"; case 3: return "Aperture priority"; case 4: return "Shutter priority"; case 5: return "Program creative (slow program)"; case 6: return "Program action (high-speed program)"; case 7: return "Portrait mode"; case 8: return "Landscape mode"; default: sprintf(format, "Unknown program (%d)", exposureProgram); buffer += format; return buffer.c_str(); } } break; case TAG_CUSTOM_RENDERED: { unsigned short customRendered = *((unsigned short *)FreeImage_GetTagValue(tag)); switch (customRendered) { case 0: return "Normal process"; case 1: return "Custom process"; default: sprintf(format, "Unknown rendering (%d)", customRendered); buffer += format; return buffer.c_str(); } } break; case TAG_EXPOSURE_MODE: { unsigned short exposureMode = *((unsigned short *)FreeImage_GetTagValue(tag)); switch (exposureMode) { case 0: return "Auto exposure"; case 1: return "Manual exposure"; case 2: return "Auto bracket"; default: sprintf(format, "Unknown mode (%d)", exposureMode); buffer += format; return buffer.c_str(); } } break; case TAG_WHITE_BALANCE: { unsigned short whiteBalance = *((unsigned short *)FreeImage_GetTagValue(tag)); switch (whiteBalance) { case 0: return "Auto white balance"; case 1: return "Manual white balance"; default: sprintf(format, "Unknown (%d)", whiteBalance); buffer += format; return buffer.c_str(); } } break; case TAG_SCENE_CAPTURE_TYPE: { unsigned short sceneType = *((unsigned short *)FreeImage_GetTagValue(tag)); switch (sceneType) { case 0: return "Standard"; case 1: return "Landscape"; case 2: return "Portrait"; case 3: return "Night scene"; default: sprintf(format, "Unknown (%d)", sceneType); buffer += format; return buffer.c_str(); } } break; case TAG_GAIN_CONTROL: { unsigned short gainControl = *((unsigned short *)FreeImage_GetTagValue(tag)); switch (gainControl) { case 0: return "None"; case 1: return "Low gain up"; case 2: return "High gain up"; case 3: return "Low gain down"; case 4: return "High gain down"; default: sprintf(format, "Unknown (%d)", gainControl); buffer += format; return buffer.c_str(); } } break; case TAG_CONTRAST: { unsigned short contrast = *((unsigned short *)FreeImage_GetTagValue(tag)); switch (contrast) { case 0: return "Normal"; case 1: return "Soft"; case 2: return "Hard"; default: sprintf(format, "Unknown (%d)", contrast); buffer += format; return buffer.c_str(); } } break; case TAG_SATURATION: { unsigned short saturation = *((unsigned short *)FreeImage_GetTagValue(tag)); switch (saturation) { case 0: return "Normal"; case 1: return "Low saturation"; case 2: return "High saturation"; default: sprintf(format, "Unknown (%d)", saturation); buffer += format; return buffer.c_str(); } } break; case TAG_SHARPNESS: { unsigned short sharpness = *((unsigned short *)FreeImage_GetTagValue(tag)); switch (sharpness) { case 0: return "Normal"; case 1: return "Soft"; case 2: return "Hard"; default: sprintf(format, "Unknown (%d)", sharpness); buffer += format; return buffer.c_str(); } } break; case TAG_SUBJECT_DISTANCE_RANGE: { unsigned short distanceRange = *((unsigned short *)FreeImage_GetTagValue(tag)); switch (distanceRange) { case 0: return "unknown"; case 1: return "Macro"; case 2: return "Close view"; case 3: return "Distant view"; default: sprintf(format, "Unknown (%d)", distanceRange); buffer += format; return buffer.c_str(); } } break; case TAG_ISO_SPEED_RATINGS: { unsigned short isoEquiv = *((unsigned short *)FreeImage_GetTagValue(tag)); if (isoEquiv < 50) { isoEquiv *= 200; } sprintf(format, "%d", isoEquiv); buffer += format; return buffer.c_str(); } break; case TAG_USER_COMMENT: { // first 8 bytes are used to define an ID code // we assume this is an ASCII string const BYTE *userComment = (BYTE*)FreeImage_GetTagValue(tag); for(DWORD i = 8; i < FreeImage_GetTagLength(tag); i++) { buffer += userComment[i]; } buffer += '\0'; return buffer.c_str(); } break; case TAG_COMPRESSION: { WORD compression = *((WORD*)FreeImage_GetTagValue(tag)); switch(compression) { case TAG_COMPRESSION_NONE: sprintf(format, "dump mode (%d)", compression); break; case TAG_COMPRESSION_CCITTRLE: sprintf(format, "CCITT modified Huffman RLE (%d)", compression); break; case TAG_COMPRESSION_CCITTFAX3: sprintf(format, "CCITT Group 3 fax encoding (%d)", compression); break; /* case TAG_COMPRESSION_CCITT_T4: sprintf(format, "CCITT T.4 (TIFF 6 name) (%d)", compression); break; */ case TAG_COMPRESSION_CCITTFAX4: sprintf(format, "CCITT Group 4 fax encoding (%d)", compression); break; /* case TAG_COMPRESSION_CCITT_T6: sprintf(format, "CCITT T.6 (TIFF 6 name) (%d)", compression); break; */ case TAG_COMPRESSION_LZW: sprintf(format, "LZW (%d)", compression); break; case TAG_COMPRESSION_OJPEG: sprintf(format, "!6.0 JPEG (%d)", compression); break; case TAG_COMPRESSION_JPEG: sprintf(format, "JPEG (%d)", compression); break; case TAG_COMPRESSION_NEXT: sprintf(format, "NeXT 2-bit RLE (%d)", compression); break; case TAG_COMPRESSION_CCITTRLEW: sprintf(format, "CCITTRLEW (%d)", compression); break; case TAG_COMPRESSION_PACKBITS: sprintf(format, "PackBits Macintosh RLE (%d)", compression); break; case TAG_COMPRESSION_THUNDERSCAN: sprintf(format, "ThunderScan RLE (%d)", compression); break; case TAG_COMPRESSION_PIXARFILM: sprintf(format, "Pixar companded 10bit LZW (%d)", compression); break; case TAG_COMPRESSION_PIXARLOG: sprintf(format, "Pixar companded 11bit ZIP (%d)", compression); break; case TAG_COMPRESSION_DEFLATE: sprintf(format, "Deflate compression (%d)", compression); break; case TAG_COMPRESSION_ADOBE_DEFLATE: sprintf(format, "Adobe Deflate compression (%d)", compression); break; case TAG_COMPRESSION_DCS: sprintf(format, "Kodak DCS encoding (%d)", compression); break; case TAG_COMPRESSION_JBIG: sprintf(format, "ISO JBIG (%d)", compression); break; case TAG_COMPRESSION_SGILOG: sprintf(format, "SGI Log Luminance RLE (%d)", compression); break; case TAG_COMPRESSION_SGILOG24: sprintf(format, "SGI Log 24-bit packed (%d)", compression); break; case TAG_COMPRESSION_JP2000: sprintf(format, "Leadtools JPEG2000 (%d)", compression); break; case TAG_COMPRESSION_LZMA: sprintf(format, "LZMA2 (%d)", compression); break; default: sprintf(format, "Unknown type (%d)", compression); break; } buffer += format; return buffer.c_str(); } break; } return ConvertAnyTag(tag); }
/** Process a maker note IFD offset Returns the offset and the metadata model for this tag */ static void processMakerNote(FIBITMAP *dib, char *pval, BOOL msb_order, DWORD *subdirOffset, TagLib::MDMODEL *md_model) { FITAG *tagMake = NULL; *subdirOffset = 0; *md_model = TagLib::UNKNOWN; // Determine the camera model and makernote format // WARNING: note that Maker may be NULL sometimes so check its value before using it // (NULL pointer checking is done by FreeImage_strnicmp) FreeImage_GetMetadata(FIMD_EXIF_MAIN, dib, "Make", &tagMake); const char *Maker = (char*)FreeImage_GetTagValue(tagMake); if((memcmp("OLYMP\x00\x01", pval, 7) == 0) || (memcmp("OLYMP\x00\x02", pval, 7) == 0) || (memcmp("EPSON", pval, 5) == 0) || (memcmp("AGFA", pval, 4) == 0)) { // Olympus Type 1 Makernote // Epson and Agfa use Olympus maker note standard, // see: http://www.ozhiker.com/electronics/pjmt/jpeg_info/ *md_model = TagLib::EXIF_MAKERNOTE_OLYMPUSTYPE1; *subdirOffset = 8; } else if(memcmp("OLYMPUS\x00\x49\x49\x03\x00", pval, 12) == 0) { // Olympus Type 2 Makernote // !!! NOT YET SUPPORTED !!! *subdirOffset = 0; *md_model = TagLib::UNKNOWN; } else if(memcmp("Nikon", pval, 5) == 0) { /* There are two scenarios here: * Type 1: * :0000: 4E 69 6B 6F 6E 00 01 00-05 00 02 00 02 00 06 00 Nikon........... * :0010: 00 00 EC 02 00 00 03 00-03 00 01 00 00 00 06 00 ................ * Type 3: * :0000: 4E 69 6B 6F 6E 00 02 00-00 00 4D 4D 00 2A 00 00 Nikon....MM.*... * :0010: 00 08 00 1E 00 01 00 07-00 00 00 04 30 32 30 30 ............0200 */ if (pval[6] == 1) { // Nikon type 1 Makernote *md_model = TagLib::EXIF_MAKERNOTE_NIKONTYPE1; *subdirOffset = 8; } else if (pval[6] == 2) { // Nikon type 3 Makernote *md_model = TagLib::EXIF_MAKERNOTE_NIKONTYPE3; *subdirOffset = 18; } else { // Unsupported makernote data ignored *subdirOffset = 0; *md_model = TagLib::UNKNOWN; } } else if(Maker && (FreeImage_strnicmp("NIKON", Maker, 5) == 0)) { // Nikon type 2 Makernote *md_model = TagLib::EXIF_MAKERNOTE_NIKONTYPE2; *subdirOffset = 0; } else if(Maker && (FreeImage_strnicmp("Canon", Maker, 5) == 0)) { // Canon Makernote *md_model = TagLib::EXIF_MAKERNOTE_CANON; *subdirOffset = 0; } else if(Maker && (FreeImage_strnicmp("Casio", Maker, 5) == 0)) { // Casio Makernote if(memcmp("QVC\x00\x00\x00", pval, 6) == 0) { // Casio Type 2 Makernote *md_model = TagLib::EXIF_MAKERNOTE_CASIOTYPE2; *subdirOffset = 6; } else { // Casio Type 1 Makernote *md_model = TagLib::EXIF_MAKERNOTE_CASIOTYPE1; *subdirOffset = 0; } } else if ((memcmp("FUJIFILM", pval, 8) == 0) || (Maker && (FreeImage_strnicmp("Fujifilm", Maker, 8) == 0))) { // Fujifile Makernote // Fujifilm's Makernote always use Intel order altough the Exif section maybe in Intel order or in Motorola order. // If msb_order == TRUE, the Makernote won't be read: // the value of ifdStart will be 0x0c000000 instead of 0x0000000c and the MakerNote section will be discarded later // in jpeg_read_exif_dir because the IFD is too high *md_model = TagLib::EXIF_MAKERNOTE_FUJIFILM; DWORD ifdStart = (DWORD) ReadUint32(msb_order, pval + 8); *subdirOffset = ifdStart; } else if(memcmp("KYOCERA\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x00\x00\x00", pval, 22) == 0) { *md_model = TagLib::EXIF_MAKERNOTE_KYOCERA; *subdirOffset = 22; } else if(Maker && (FreeImage_strnicmp("Minolta", Maker, 7) == 0)) { // Minolta maker note *md_model = TagLib::EXIF_MAKERNOTE_MINOLTA; *subdirOffset = 0; } else if(memcmp("Panasonic\x00\x00\x00", pval, 12) == 0) { // Panasonic maker note *md_model = TagLib::EXIF_MAKERNOTE_PANASONIC; *subdirOffset = 12; } else if(Maker && (FreeImage_strnicmp("LEICA", Maker, 5) == 0)) { // Leica maker note if(memcmp("LEICA\x00\x00\x00", pval, 8) == 0) { // not yet supported makernote data ignored *subdirOffset = 0; *md_model = TagLib::UNKNOWN; } } else if(Maker && ((FreeImage_strnicmp("Pentax", Maker, 6) == 0) || (FreeImage_strnicmp("Asahi", Maker, 5) == 0))) { // Pentax maker note if(memcmp("AOC\x00", pval, 4) == 0) { // Type 2 Pentax Makernote *md_model = TagLib::EXIF_MAKERNOTE_PENTAX; *subdirOffset = 6; } else { // Type 1 Pentax Makernote *md_model = TagLib::EXIF_MAKERNOTE_ASAHI; *subdirOffset = 0; } } else if((memcmp("SONY CAM\x20\x00\x00\x00", pval, 12) == 0) || (memcmp("SONY DSC\x20\x00\x00\x00", pval, 12) == 0)) { *md_model = TagLib::EXIF_MAKERNOTE_SONY; *subdirOffset = 12; } else if((memcmp("SIGMA\x00\x00\x00", pval, 8) == 0) || (memcmp("FOVEON\x00\x00", pval, 8) == 0)) { FITAG *tagModel = NULL; FreeImage_GetMetadata(FIMD_EXIF_MAIN, dib, "Model", &tagModel); const char *Model = (char*)FreeImage_GetTagValue(tagModel); if(Model && (memcmp("SIGMA SD1\x00", Model, 10) == 0)) { // Sigma SD1 maker note *subdirOffset = 10; *md_model = TagLib::EXIF_MAKERNOTE_SIGMA_SD1; } else { // Sigma / Foveon makernote *subdirOffset = 10; *md_model = TagLib::EXIF_MAKERNOTE_SIGMA_FOVEON; } } }
/** Process a Canon maker note tag. A single Canon tag may contain many other tags within. */ static BOOL processCanonMakerNoteTag(FIBITMAP *dib, FITAG *tag) { char defaultKey[16]; DWORD startIndex = 0; TagLib& s = TagLib::instance(); WORD tag_id = FreeImage_GetTagID(tag); int subTagTypeBase = 0; switch(tag_id) { case TAG_CANON_CAMERA_STATE_0x01: subTagTypeBase = 0xC100; startIndex = 1; break; case TAG_CANON_CAMERA_STATE_0x02: subTagTypeBase = 0xC200; startIndex = 0; break; case TAG_CANON_CAMERA_STATE_0x04: subTagTypeBase = 0xC400; startIndex = 1; break; case TAG_CANON_CAMERA_STATE_0x12: subTagTypeBase = 0x1200; startIndex = 0; break; case TAG_CANON_CAMERA_STATE_0xA0: subTagTypeBase = 0xCA00; startIndex = 1; break; case TAG_CANON_CAMERA_STATE_0xE0: subTagTypeBase = 0xCE00; startIndex = 1; break; default: { // process as a normal tag // get the tag key and description const char *key = s.getTagFieldName(TagLib::EXIF_MAKERNOTE_CANON, tag_id, defaultKey); FreeImage_SetTagKey(tag, key); const char *description = s.getTagDescription(TagLib::EXIF_MAKERNOTE_CANON, tag_id); FreeImage_SetTagDescription(tag, description); // store the tag if(key) { FreeImage_SetMetadata(FIMD_EXIF_MAKERNOTE, dib, key, tag); } return TRUE; } break; } WORD *pvalue = (WORD*)FreeImage_GetTagValue(tag); // create a tag FITAG *canonTag = FreeImage_CreateTag(); if(!canonTag) return FALSE; // we intentionally skip the first array member (if needed) for (DWORD i = startIndex; i < FreeImage_GetTagCount(tag); i++) { tag_id = (WORD)(subTagTypeBase + i); FreeImage_SetTagID(canonTag, tag_id); FreeImage_SetTagType(canonTag, FIDT_SHORT); FreeImage_SetTagCount(canonTag, 1); FreeImage_SetTagLength(canonTag, 2); FreeImage_SetTagValue(canonTag, &pvalue[i]); // get the tag key and description const char *key = s.getTagFieldName(TagLib::EXIF_MAKERNOTE_CANON, tag_id, defaultKey); FreeImage_SetTagKey(canonTag, key); const char *description = s.getTagDescription(TagLib::EXIF_MAKERNOTE_CANON, tag_id); FreeImage_SetTagDescription(canonTag, description); // store the tag if(key) { FreeImage_SetMetadata(FIMD_EXIF_MAKERNOTE, dib, key, canonTag); } } // delete the tag FreeImage_DeleteTag(canonTag); return TRUE; }
/** Write a metadata model as a TIF IFD to a FIMEMORY handle. The entries in the TIF IFD are sorted in ascending order by tag id. The last entry is written as 0 (4 bytes) which means no more IFD to follow. Supported metadata models are <ul> <li>FIMD_EXIF_MAIN <li>FIMD_EXIF_EXIF <li>FIMD_EXIF_GPS <li>FIMD_EXIF_INTEROP </ul> The end of the buffer is filled with 4 bytes equal to 0 (end of IFD offset) @param dib Input FIBITMAP @param md_model Metadata model to write @param hmem Memory handle @return Returns TRUE if successful, FALSE otherwise @see tiff_get_ifd_profile */ static BOOL tiff_write_ifd(FIBITMAP *dib, FREE_IMAGE_MDMODEL md_model, FIMEMORY *hmem) { FITAG *tag = NULL; FIMETADATA *mdhandle = NULL; std::vector<FITAG*> vTagList; TagLib::MDMODEL internal_md_model; DWORD ifd_offset = 0; // WORD-aligned IFD value offset const BYTE empty_byte = 0; // start of the file const long start_of_file = FreeImage_TellMemory(hmem); // get the metadata count unsigned metadata_count = FreeImage_GetMetadataCount(md_model, dib); if(metadata_count == 0) { return FALSE; } TagLib& s = TagLib::instance(); // check for supported metadata models switch(md_model) { case FIMD_EXIF_MAIN: internal_md_model = TagLib::EXIF_MAIN; break; case FIMD_EXIF_EXIF: internal_md_model = TagLib::EXIF_EXIF; break; case FIMD_EXIF_GPS: internal_md_model = TagLib::EXIF_GPS; break; case FIMD_EXIF_INTEROP: internal_md_model = TagLib::EXIF_INTEROP; break; default: return FALSE; } try { // 1) according to the TIFF specifications, // the entries in a TIF IFD must be sorted in ascending order by tag id // store the tags into a vector vTagList.reserve(metadata_count); mdhandle = FreeImage_FindFirstMetadata(md_model, dib, &tag); if(mdhandle) { // parse the tags and store them inside vTagList do { // rewrite the tag id using FreeImage internal database // (in case the tag id is wrong or missing) const char *key = FreeImage_GetTagKey(tag); int tag_id = s.getTagID(internal_md_model, key); if(tag_id != -1) { // this is a known tag, set the tag ID FreeImage_SetTagID(tag, (WORD)tag_id); // record the tag vTagList.push_back(tag); } // else ignore this tag } while(FreeImage_FindNextMetadata(mdhandle, &tag)); FreeImage_FindCloseMetadata(mdhandle); // sort the vector by tag id std::sort(vTagList.begin(), vTagList.end(), PredicateTagIDCompare()); // update the metadata_count metadata_count = (unsigned)vTagList.size(); } else { throw(1); } // 2) prepare the place for each IFD entries. /* An Image File Directory (IFD) consists of a 2-byte count of the number of directory entries (i.e., the number of fields), followed by a sequence of 12-byte field entries, followed by a 4-byte offset of the next IFD (or 0 if none). Do not forget to write the 4 bytes of 0 after the last IFD. */ { // prepare place for 2 bytes for number of entries + 12 bytes for each entry unsigned ifd_size = 2 + 12 * metadata_count; FreeImage_WriteMemory(&empty_byte, 1, ifd_size, hmem); // record the offset used to write values > 4-bytes ifd_offset = FreeImage_TellMemory(hmem); // rewind FreeImage_SeekMemory(hmem, start_of_file, SEEK_SET); } // 3) write each IFD entry in tag id ascending order // number of directory entries WORD nde = (WORD)metadata_count; FreeImage_WriteMemory(&nde, 1, 2, hmem); // for each entry ... for(unsigned i = 0; i < metadata_count; i++) { FITAG *tag = vTagList[i]; // tag id WORD tag_id = FreeImage_GetTagID(tag); FreeImage_WriteMemory(&tag_id, 1, 2, hmem); // tag type (compliant with TIFF specification) WORD tag_type = (WORD)FreeImage_GetTagType(tag); FreeImage_WriteMemory(&tag_type, 1, 2, hmem); // tag count DWORD tag_count = FreeImage_GetTagCount(tag); FreeImage_WriteMemory(&tag_count, 1, 4, hmem); // tag value or offset (results are in BYTE's units) unsigned tag_length = FreeImage_GetTagLength(tag); if(tag_length <= 4) { // 4 bytes or less, write the value (left justified) const BYTE *tag_value = (BYTE*)FreeImage_GetTagValue(tag); FreeImage_WriteMemory(tag_value, 1, tag_length, hmem); for(unsigned k = tag_length; k < 4; k++) { FreeImage_WriteMemory(&empty_byte, 1, 1, hmem); } } else { // write an offset FreeImage_WriteMemory(&ifd_offset, 1, 4, hmem); // write the value long current_position = FreeImage_TellMemory(hmem); FreeImage_SeekMemory(hmem, ifd_offset, SEEK_SET); FreeImage_WriteMemory(FreeImage_GetTagValue(tag), 1, tag_length, hmem); if(tag_length & 1) { // align to the next WORD boundary FreeImage_WriteMemory(&empty_byte, 1, 1, hmem); } // next offset to use ifd_offset = FreeImage_TellMemory(hmem); // rewind FreeImage_SeekMemory(hmem, current_position, SEEK_SET); } } // end-of-IFD or next IFD (0 == none) FreeImage_SeekMemory(hmem, ifd_offset, SEEK_SET); FreeImage_WriteMemory(&empty_byte, 1, 4, hmem); return TRUE; } catch(int) { return FALSE; } }
static BOOL WriteMetadata(png_structp png_ptr, png_infop info_ptr, FIBITMAP *dib) { // XMP keyword const char *g_png_xmp_keyword = "XML:com.adobe.xmp"; FITAG *tag = NULL; FIMETADATA *mdhandle = NULL; BOOL bResult = TRUE; png_text text_metadata; png_time mod_time; // set the 'Comments' metadata as iTXt chuncks mdhandle = FreeImage_FindFirstMetadata(FIMD_COMMENTS, dib, &tag); if(mdhandle) { do { memset(&text_metadata, 0, sizeof(png_text)); text_metadata.compression = 1; // iTXt, none text_metadata.key = (char*)FreeImage_GetTagKey(tag); // keyword, 1-79 character description of "text" text_metadata.text = (char*)FreeImage_GetTagValue(tag); // comment, may be an empty string (ie "") text_metadata.text_length = FreeImage_GetTagLength(tag);// length of the text string text_metadata.itxt_length = FreeImage_GetTagLength(tag);// length of the itxt string text_metadata.lang = 0; // language code, 0-79 characters or a NULL pointer text_metadata.lang_key = 0; // keyword translated UTF-8 string, 0 or more chars or a NULL pointer // set the tag png_set_text(png_ptr, info_ptr, &text_metadata, 1); } while(FreeImage_FindNextMetadata(mdhandle, &tag)); FreeImage_FindCloseMetadata(mdhandle); bResult &= TRUE; } // set the 'XMP' metadata as iTXt chuncks tag = NULL; FreeImage_GetMetadata(FIMD_XMP, dib, g_TagLib_XMPFieldName, &tag); if(tag && FreeImage_GetTagLength(tag)) { memset(&text_metadata, 0, sizeof(png_text)); text_metadata.compression = 1; // iTXt, none text_metadata.key = (char*)g_png_xmp_keyword; // keyword, 1-79 character description of "text" text_metadata.text = (char*)FreeImage_GetTagValue(tag); // comment, may be an empty string (ie "") text_metadata.text_length = FreeImage_GetTagLength(tag);// length of the text string text_metadata.itxt_length = FreeImage_GetTagLength(tag);// length of the itxt string text_metadata.lang = 0; // language code, 0-79 characters or a NULL pointer text_metadata.lang_key = 0; // keyword translated UTF-8 string, 0 or more chars or a NULL pointer // set the tag png_set_text(png_ptr, info_ptr, &text_metadata, 1); bResult &= TRUE; } // set the Exif-TIFF 'DateTime' metadata as a tIME chunk tag = NULL; FreeImage_GetMetadata(FIMD_EXIF_MAIN, dib, "DateTime", &tag); if(tag && FreeImage_GetTagLength(tag)) { int year, month, day, hour, minute, second; const char *value = (char*)FreeImage_GetTagValue(tag); if(sscanf(value, "%4d:%02d:%02d %2d:%02d:%02d", &year, &month, &day, &hour, &minute, &second) == 6) { mod_time.year = (png_uint_16)year; mod_time.month = (png_byte)month; mod_time.day = (png_byte)day; mod_time.hour = (png_byte)hour; mod_time.minute = (png_byte)minute; mod_time.second = (png_byte)second; png_set_tIME (png_ptr, info_ptr, &mod_time); } } return bResult; }
/** Process a Canon maker note tag. A single Canon tag may contain many other tags within. */ static void processCanonMakerNoteTag(FIBITMAP *dib, FITAG *tag) { char defaultKey[16]; DWORD startIndex = 0; TagLib& s = TagLib::instance(); WORD tag_id = FreeImage_GetTagID(tag); if((tag_id == TAG_CANON_CAMERA_STATE_1) || (tag_id == TAG_CANON_CAMERA_STATE_2) || (tag_id == TAG_CANON_CAMERA_STATE_4)) { // this single tag has multiple values within int subTagTypeBase = 0; switch(tag_id) { case TAG_CANON_CAMERA_STATE_1: subTagTypeBase = 0xC100; startIndex = 1; break; case TAG_CANON_CAMERA_STATE_2: subTagTypeBase = 0xC200; startIndex = 0; break; case TAG_CANON_CAMERA_STATE_4: subTagTypeBase = 0xC400; startIndex = 2; break; } WORD *pvalue = (WORD*)FreeImage_GetTagValue(tag); // we intentionally skip the first array member for (DWORD i = startIndex; i < FreeImage_GetTagCount(tag); i++) { // create a tag FITAG *canonTag = FreeImage_CreateTag(); if(!canonTag) return; tag_id = (WORD)(subTagTypeBase + i); FreeImage_SetTagID(canonTag, tag_id); FreeImage_SetTagType(canonTag, FIDT_SHORT); FreeImage_SetTagCount(canonTag, 1); FreeImage_SetTagLength(canonTag, 2); FreeImage_SetTagValue(canonTag, &pvalue[i]); // get the tag key and description const char *key = s.getTagFieldName(TagLib::EXIF_MAKERNOTE_CANON, tag_id, defaultKey); FreeImage_SetTagKey(canonTag, key); const char *description = s.getTagDescription(TagLib::EXIF_MAKERNOTE_CANON, tag_id); FreeImage_SetTagDescription(canonTag, description); // store the tag if(key) { FreeImage_SetMetadata(FIMD_EXIF_MAKERNOTE, dib, key, canonTag); } // delete the tag FreeImage_DeleteTag(canonTag); } } else { // process as a normal tag // get the tag key and description const char *key = s.getTagFieldName(TagLib::EXIF_MAKERNOTE_CANON, tag_id, defaultKey); FreeImage_SetTagKey(tag, key); const char *description = s.getTagDescription(TagLib::EXIF_MAKERNOTE_CANON, tag_id); FreeImage_SetTagDescription(tag, description); // store the tag if(key) { FreeImage_SetMetadata(FIMD_EXIF_MAKERNOTE, dib, key, tag); } } }
static BOOL DLL_CALLCONV Save(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void *data) { WebPMux *mux = NULL; FIMEMORY *hmem = NULL; WebPData webp_image; WebPData output_data = { 0 }; WebPMuxError error_status; int copy_data = 1; // 1 : copy data into the mux, 0 : keep a link to local data if(!dib || !handle || !data) { return FALSE; } try { // get the MUX object mux = (WebPMux*)data; if(!mux) { return FALSE; } // --- prepare image data --- // encode image as a WebP blob hmem = FreeImage_OpenMemory(); if(!hmem || !EncodeImage(hmem, dib, flags)) { throw (1); } // store the blob into the mux BYTE *data = NULL; DWORD data_size = 0; FreeImage_AcquireMemory(hmem, &data, &data_size); webp_image.bytes = data; webp_image.size = data_size; error_status = WebPMuxSetImage(mux, &webp_image, copy_data); // no longer needed since copy_data == 1 FreeImage_CloseMemory(hmem); hmem = NULL; if(error_status != WEBP_MUX_OK) { throw (1); } // --- set metadata --- // set ICC color profile { FIICCPROFILE *iccProfile = FreeImage_GetICCProfile(dib); if (iccProfile->size && iccProfile->data) { WebPData icc_profile; icc_profile.bytes = (uint8_t*)iccProfile->data; icc_profile.size = (size_t)iccProfile->size; error_status = WebPMuxSetChunk(mux, "ICCP", &icc_profile, copy_data); if(error_status != WEBP_MUX_OK) { throw (1); } } } // set XMP metadata { FITAG *tag = NULL; if(FreeImage_GetMetadata(FIMD_XMP, dib, g_TagLib_XMPFieldName, &tag)) { WebPData xmp_profile; xmp_profile.bytes = (uint8_t*)FreeImage_GetTagValue(tag); xmp_profile.size = (size_t)FreeImage_GetTagLength(tag); error_status = WebPMuxSetChunk(mux, "XMP ", &xmp_profile, copy_data); if(error_status != WEBP_MUX_OK) { throw (1); } } } // set Exif metadata { FITAG *tag = NULL; if(FreeImage_GetMetadata(FIMD_EXIF_RAW, dib, g_TagLib_ExifRawFieldName, &tag)) { WebPData exif_profile; exif_profile.bytes = (uint8_t*)FreeImage_GetTagValue(tag); exif_profile.size = (size_t)FreeImage_GetTagLength(tag); error_status = WebPMuxSetChunk(mux, "EXIF", &exif_profile, copy_data); if(error_status != WEBP_MUX_OK) { throw (1); } } } // get data from mux in WebP RIFF format error_status = WebPMuxAssemble(mux, &output_data); if(error_status != WEBP_MUX_OK) { FreeImage_OutputMessageProc(s_format_id, "Failed to create webp output file"); throw (1); } // write the file to the output stream if(io->write_proc((void*)output_data.bytes, 1, (unsigned)output_data.size, handle) != output_data.size) { FreeImage_OutputMessageProc(s_format_id, "Failed to write webp output file"); throw (1); } // free WebP output file WebPDataClear(&output_data); return TRUE; } catch(int) { if(hmem) { FreeImage_CloseMemory(hmem); } WebPDataClear(&output_data); return FALSE; } }
/** Write ICC, XMP, Exif, Exif-GPS, IPTC, descriptive (i.e. Exif-TIFF) metadata */ static ERR WriteMetadata(PKImageEncode *pIE, FIBITMAP *dib) { ERR error_code = 0; // error code as returned by the interface BYTE *profile = NULL; unsigned profile_size = 0; try { // write ICC profile { FIICCPROFILE *iccProfile = FreeImage_GetICCProfile(dib); if(iccProfile->data) { error_code = pIE->SetColorContext(pIE, (U8*)iccProfile->data, iccProfile->size); JXR_CHECK(error_code); } } // write descriptive metadata if(FreeImage_GetMetadataCount(FIMD_EXIF_MAIN, dib)) { error_code = WriteDescriptiveMetadata(pIE, dib); JXR_CHECK(error_code); } // write IPTC metadata if(FreeImage_GetMetadataCount(FIMD_IPTC, dib)) { // create a binary profile if(write_iptc_profile(dib, &profile, &profile_size)) { // write the profile error_code = PKImageEncode_SetIPTCNAAMetadata_WMP(pIE, profile, profile_size); JXR_CHECK(error_code); // release profile free(profile); profile = NULL; } } // write XMP metadata { FITAG *tag_xmp = NULL; if(FreeImage_GetMetadata(FIMD_XMP, dib, g_TagLib_XMPFieldName, &tag_xmp)) { error_code = PKImageEncode_SetXMPMetadata_WMP(pIE, (BYTE*)FreeImage_GetTagValue(tag_xmp), FreeImage_GetTagLength(tag_xmp)); JXR_CHECK(error_code); } } // write Exif metadata { if(tiff_get_ifd_profile(dib, FIMD_EXIF_EXIF, &profile, &profile_size)) { error_code = PKImageEncode_SetEXIFMetadata_WMP(pIE, profile, profile_size); JXR_CHECK(error_code); // release profile free(profile); profile = NULL; } } // write Exif GPS metadata { if(tiff_get_ifd_profile(dib, FIMD_EXIF_GPS, &profile, &profile_size)) { error_code = PKImageEncode_SetGPSInfoMetadata_WMP(pIE, profile, profile_size); JXR_CHECK(error_code); // release profile free(profile); profile = NULL; } } return WMP_errSuccess; } catch(...) { free(profile); return error_code; } }