|
@@ -0,0 +1,2889 @@
|
|
|
+/*
|
|
|
+ * Copyright (C) 2013 KLab Inc.
|
|
|
+ *
|
|
|
+ * Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
+ * you may not use this file except in compliance with the License.
|
|
|
+ * You may obtain a copy of the License at
|
|
|
+ *
|
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+ *
|
|
|
+ * Unless required by applicable law or agreed to in writing, software
|
|
|
+ * distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+ * See the License for the specific language governing permissions and
|
|
|
+ * limitations under the License.
|
|
|
+ */
|
|
|
+
|
|
|
+//
|
|
|
+// Links related to EXIF, TIFF 6.0 and MPO (Multi Picture Object) format
|
|
|
+//
|
|
|
+// https://www.exif.org/Exif2-2.PDF
|
|
|
+// https://www.itu.int/itudoc/itu-t/com16/tiff-fx/docs/tiff6.pdf
|
|
|
+//
|
|
|
+// https://en.wikipedia.org/wiki/JPEG#JPEG_Multi-Picture_Format
|
|
|
+// http://www.cmsoft.com.br/downloads/cmsoft-stereoscopic-picture-editor-converter/3d-picture-gallery/
|
|
|
+// https://dmitrybrant.com/2011/02/08/the-fujifilm-mpo-3d-photo-format
|
|
|
+//
|
|
|
+#ifdef _MSC_VER
|
|
|
+#include <windows.h>
|
|
|
+//#define vsnprintf _vsnprintf
|
|
|
+#endif
|
|
|
+
|
|
|
+#include "exif.h"
|
|
|
+
|
|
|
+#include <stdio.h>
|
|
|
+#include <stddef.h>
|
|
|
+#include <stdlib.h>
|
|
|
+#include <stdarg.h>
|
|
|
+#include <string.h>
|
|
|
+#include <memory.h>
|
|
|
+#include <ctype.h>
|
|
|
+
|
|
|
+#ifdef _MSC_VER
|
|
|
+#ifdef __cplusplus
|
|
|
+extern "C" {
|
|
|
+#endif
|
|
|
+#endif
|
|
|
+
|
|
|
+
|
|
|
+#pragma pack(2)
|
|
|
+
|
|
|
+#define VERSION "1.0.1"
|
|
|
+
|
|
|
+#define APP0_MARKER 0xFFE0
|
|
|
+#define APP1_MARKER 0xFFE1
|
|
|
+#define APP2_MARKER 0xFFE2
|
|
|
+
|
|
|
+// TIFF Header
|
|
|
+typedef struct _tiff_Header {
|
|
|
+ uint16_t byteOrder;
|
|
|
+ uint16_t reserved;
|
|
|
+ unsigned int Ifd0thOffset;
|
|
|
+} TIFF_HEADER;
|
|
|
+
|
|
|
+// APP1 Exif Segment Header
|
|
|
+typedef struct _App_Header {
|
|
|
+ uint16_t marker;
|
|
|
+ uint16_t length;
|
|
|
+ char id[6]; // "Exif\0\0"
|
|
|
+ TIFF_HEADER tiff;
|
|
|
+} APP_HEADER;
|
|
|
+
|
|
|
+// MPF Segment Header
|
|
|
+typedef struct _MPF_Header {
|
|
|
+ uint16_t marker;
|
|
|
+ uint16_t length;
|
|
|
+ char id[4]; // "MPF\0"
|
|
|
+ TIFF_HEADER tiff;
|
|
|
+} MPF_HEADER;
|
|
|
+
|
|
|
+// tag field in IFD
|
|
|
+typedef struct {
|
|
|
+ uint16_t tag;
|
|
|
+ uint16_t type;
|
|
|
+ unsigned int count;
|
|
|
+ unsigned int offset;
|
|
|
+} IFD_TAG;
|
|
|
+
|
|
|
+// tag node - internal use
|
|
|
+typedef struct _tagNode TagNode;
|
|
|
+struct _tagNode {
|
|
|
+ uint16_t tagId;
|
|
|
+ uint16_t type;
|
|
|
+ unsigned int count;
|
|
|
+ unsigned int *numData;
|
|
|
+ uint8_t *byteData;
|
|
|
+ uint16_t error;
|
|
|
+ TagNode *prev;
|
|
|
+ TagNode *next;
|
|
|
+};
|
|
|
+
|
|
|
+// IFD table - internal use
|
|
|
+typedef struct _ifdTable IfdTable;
|
|
|
+struct _ifdTable {
|
|
|
+ EXIF_IFD_TYPE ifdType;
|
|
|
+ uint16_t tagCount;
|
|
|
+ TagNode *tags;
|
|
|
+ unsigned int nextIfdOffset;
|
|
|
+ uint16_t offset;
|
|
|
+ uint16_t length;
|
|
|
+ uint8_t *p;
|
|
|
+};
|
|
|
+
|
|
|
+static int init(FILE*);
|
|
|
+static int systemIsLittleEndian();
|
|
|
+static int dataIsLittleEndian();
|
|
|
+static void freeIfdTable(void*);
|
|
|
+static void *parseIFD(FILE*, unsigned int, unsigned int, EXIF_IFD_TYPE);
|
|
|
+static TagNode *getTagNodePtrFromIfd(IfdTable*, uint16_t);
|
|
|
+static TagNode *duplicateTagNode(TagNode*);
|
|
|
+static void freeTagNode(void*);
|
|
|
+static const char *getTagName(int, uint16_t);
|
|
|
+static int countIfdTableOnIfdTableArray(void **ifdTableArray);
|
|
|
+static IfdTable *getIfdTableFromIfdTableArray(void **ifdTableArray, EXIF_IFD_TYPE ifdType);
|
|
|
+static void *createIfdTable(EXIF_IFD_TYPE IfdType, uint16_t tagCount, unsigned int nextOfs);
|
|
|
+static void *addTagNodeToIfd(void *pIfd, uint16_t tagId, uint16_t type,
|
|
|
+ unsigned int count, unsigned int *numData,uint8_t *byteData);
|
|
|
+static int writeExifSegment(FILE *fp, void **ifdTableArray);
|
|
|
+static int removeTagOnIfd(void *pIfd, uint16_t tagId);
|
|
|
+static int fixLengthAndOffsetInIfdTables(void **ifdTableArray);
|
|
|
+static int setSingleNumDataToTag(TagNode *tag, unsigned int value);
|
|
|
+static int getAppNStartOffset(FILE *fp, uint16_t appMarkerN, const char *App1IDString,
|
|
|
+ size_t App1IDStringLength, int *pDQTOffset);
|
|
|
+static uint16_t swab16(uint16_t us);
|
|
|
+static void PRINTF(char **ms, const char *fmt, ...);
|
|
|
+static void _dumpIfdTable(void *pIfd, char **p);
|
|
|
+
|
|
|
+static int Verbose = 0;
|
|
|
+static int App1StartOffset = -1;
|
|
|
+static int App2StartOffset = -1;
|
|
|
+static int MPFStartOffset = -1;
|
|
|
+static int JpegDQTOffset = -1;
|
|
|
+static APP_HEADER App1Header;
|
|
|
+static APP_HEADER App2Header;
|
|
|
+static MPF_HEADER MPFHeader;
|
|
|
+
|
|
|
+
|
|
|
+// private functions
|
|
|
+
|
|
|
+static int dataIsLittleEndian()
|
|
|
+{
|
|
|
+ return (App1Header.tiff.byteOrder == 0x4949) ? 1 : 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int systemIsLittleEndian()
|
|
|
+{
|
|
|
+ static int i = 1;
|
|
|
+ return (int)(*(char*)&i);
|
|
|
+}
|
|
|
+
|
|
|
+static uint16_t swab16(uint16_t us)
|
|
|
+{
|
|
|
+ return (us << 8) | ((us >> 8) & 0x00FF);
|
|
|
+}
|
|
|
+
|
|
|
+static unsigned int swab32(unsigned int ui)
|
|
|
+{
|
|
|
+ return
|
|
|
+ ((ui << 24) & 0xFF000000) | ((ui << 8) & 0x00FF0000) |
|
|
|
+ ((ui >> 8) & 0x0000FF00) | ((ui >> 24) & 0x000000FF);
|
|
|
+}
|
|
|
+
|
|
|
+static uint16_t fix_short(uint16_t us)
|
|
|
+{
|
|
|
+ return (dataIsLittleEndian() !=
|
|
|
+ systemIsLittleEndian()) ? swab16(us) : us;
|
|
|
+}
|
|
|
+
|
|
|
+static unsigned int fix_int(unsigned int ui)
|
|
|
+{
|
|
|
+ return (dataIsLittleEndian() !=
|
|
|
+ systemIsLittleEndian()) ? swab32(ui) : ui;
|
|
|
+}
|
|
|
+
|
|
|
+// public funtions
|
|
|
+
|
|
|
+/**
|
|
|
+ * setVerbose()
|
|
|
+ *
|
|
|
+ * Verbose output on/off
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] v : 1=on 0=off
|
|
|
+ */
|
|
|
+EXIF_API_EXPORT void exif_setVerbose(int v)
|
|
|
+{
|
|
|
+ Verbose = v;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * removeExifSegmentFromJPEGFile()
|
|
|
+ *
|
|
|
+ * Remove the Exif segment from a JPEG file
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] inJPEGFileName : original JPEG file
|
|
|
+ * [in] outJPGEFileName : output JPEG file
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * 1: OK
|
|
|
+ * 0: the Exif segment is not found
|
|
|
+ * -n: error
|
|
|
+ * EXIF_ERR_READ_FILE
|
|
|
+ * EXIF_ERR_WRITE_FILE
|
|
|
+ * EXIF_ERR_INVALID_JPEG
|
|
|
+ * EXIF_ERR_INVALID_APP1HEADER
|
|
|
+ */
|
|
|
+int exif_removeExifSegmentFromJPEGFile(const char *inJPEGFileName,
|
|
|
+ const char *outJPGEFileName)
|
|
|
+{
|
|
|
+ int ofs;
|
|
|
+ int i, sts = 1;
|
|
|
+ size_t readLen, writeLen;
|
|
|
+ uint8_t buf[8192], *p;
|
|
|
+ FILE *fpr = NULL, *fpw = NULL;
|
|
|
+
|
|
|
+ fpr = fopen(inJPEGFileName, "rb");
|
|
|
+ if (!fpr) {
|
|
|
+ sts = EXIF_ERR_READ_FILE;
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ sts = init(fpr);
|
|
|
+ if (sts <= 0) {
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ fpw = fopen(outJPGEFileName, "wb");
|
|
|
+ if (!fpw) {
|
|
|
+ sts = EXIF_ERR_WRITE_FILE;
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ // copy the data in front of the Exif segment
|
|
|
+ rewind(fpr);
|
|
|
+ p = buf;
|
|
|
+ if (App1StartOffset > sizeof(buf)) {
|
|
|
+ // allocate new buffer if needed
|
|
|
+ p = (uint8_t*)malloc(App1StartOffset);
|
|
|
+ }
|
|
|
+ if (!p) {
|
|
|
+ for (i = 0; i < App1StartOffset; i++) {
|
|
|
+ fread(buf, 1, sizeof(char), fpr);
|
|
|
+ fwrite(buf, 1, sizeof(char), fpw);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (fread(p, 1, App1StartOffset, fpr) < (size_t)App1StartOffset) {
|
|
|
+ sts = EXIF_ERR_READ_FILE;
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ if (fwrite(p, 1, App1StartOffset, fpw) < (size_t)App1StartOffset) {
|
|
|
+ sts = EXIF_ERR_WRITE_FILE;
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ if (p != &buf[0]) {
|
|
|
+ free(p);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // seek to the end of the Exif segment
|
|
|
+ ofs = App1StartOffset + sizeof(App1Header.marker) + App1Header.length;
|
|
|
+ if (fseek(fpr, ofs, SEEK_SET) != 0) {
|
|
|
+ sts = EXIF_ERR_READ_FILE;
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ // read & write
|
|
|
+ for (;;) {
|
|
|
+ readLen = fread(buf, 1, sizeof(buf), fpr);
|
|
|
+ if (readLen <= 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ writeLen = fwrite(buf, 1, readLen, fpw);
|
|
|
+ if (writeLen != readLen) {
|
|
|
+ sts = EXIF_ERR_WRITE_FILE;
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ }
|
|
|
+DONE:
|
|
|
+ if (fpw) {
|
|
|
+ fclose(fpw);
|
|
|
+ }
|
|
|
+ if (fpr) {
|
|
|
+ fclose(fpr);
|
|
|
+ }
|
|
|
+ return sts;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * fillIfdTableArray()
|
|
|
+ *
|
|
|
+ * Parse the JPEG header and fill in the IFD table
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] JPEGFileName : target JPEG file
|
|
|
+ * [out] ifdArray[32] : array of IfdTable pointers
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * n: number of IFD tables
|
|
|
+ * 0: the Exif segment is not found
|
|
|
+ * -n: error
|
|
|
+ * EXIF_ERR_READ_FILE
|
|
|
+ * EXIF_ERR_INVALID_JPEG
|
|
|
+ * EXIF_ERR_INVALID_APP1HEADER
|
|
|
+ * EXIF_ERR_INVALID_IFD
|
|
|
+ */
|
|
|
+int EXIF_API_EXPORT exif_fillIfdTableArray(const char *JPEGFileName, void* ifdArray[32])
|
|
|
+{
|
|
|
+ #define FMT_ERR "critical error in %s IFD\n"
|
|
|
+
|
|
|
+ int sts = 1, ifdCount = 0;
|
|
|
+ unsigned int ifdOffset;
|
|
|
+ FILE *fp = NULL;
|
|
|
+ TagNode *tag;
|
|
|
+ IfdTable *IFD_0th, *IFD_exif, *IFD_gps, *IFD_io, *IFD_1st, *mpf_ifd;
|
|
|
+
|
|
|
+ IFD_0th = IFD_exif = IFD_gps = IFD_io = IFD_1st = NULL;
|
|
|
+ memset(ifdArray, 0, sizeof(void*) * 32);
|
|
|
+
|
|
|
+ fp = fopen(JPEGFileName, "rb");
|
|
|
+ if (!fp) {
|
|
|
+ sts = EXIF_ERR_READ_FILE;
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ sts = init(fp);
|
|
|
+ if (sts <= 0) {
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ if (Verbose) {
|
|
|
+ printf("system: %s-endian\n data: %s-endian\n",
|
|
|
+ systemIsLittleEndian() ? "little" : "big",
|
|
|
+ dataIsLittleEndian() ? "little" : "big");
|
|
|
+ }
|
|
|
+
|
|
|
+ // for 0th IFD
|
|
|
+ IFD_0th = parseIFD(fp, App1StartOffset + offsetof(APP_HEADER, tiff), App1Header.tiff.Ifd0thOffset, IFD_0TH);
|
|
|
+ if (!IFD_0th) {
|
|
|
+ if (Verbose) {
|
|
|
+ printf(FMT_ERR, "0th");
|
|
|
+ }
|
|
|
+ sts = EXIF_ERR_INVALID_IFD;
|
|
|
+ goto DONE; // non-continuable
|
|
|
+ }
|
|
|
+ ifdArray[ifdCount++] = IFD_0th;
|
|
|
+
|
|
|
+ if (MPFStartOffset > 0) {
|
|
|
+ mpf_ifd = parseIFD(fp, MPFStartOffset + offsetof(MPF_HEADER, tiff), MPFHeader.tiff.Ifd0thOffset, IFD_MPF);
|
|
|
+ ifdArray[ifdCount++] = mpf_ifd;
|
|
|
+ }
|
|
|
+
|
|
|
+ // for Exif IFD
|
|
|
+ tag = getTagNodePtrFromIfd(IFD_0th, TAG_ExifIFDPointer);
|
|
|
+ if (tag && !tag->error) {
|
|
|
+ ifdOffset = tag->numData[0];
|
|
|
+ if (ifdOffset != 0) {
|
|
|
+ IFD_exif = parseIFD(fp, App1StartOffset + offsetof(APP_HEADER, tiff), ifdOffset, IFD_EXIF);
|
|
|
+ if (IFD_exif) {
|
|
|
+ ifdArray[ifdCount++] = IFD_exif;
|
|
|
+ // for InteroperabilityIFDPointer IFD
|
|
|
+ tag = getTagNodePtrFromIfd(IFD_exif, TAG_InteroperabilityIFDPointer);
|
|
|
+ if (tag && !tag->error) {
|
|
|
+ ifdOffset = tag->numData[0];
|
|
|
+ if (ifdOffset != 0) {
|
|
|
+ IFD_io = parseIFD(fp, App1StartOffset + offsetof(APP_HEADER, tiff), ifdOffset, IFD_IO);
|
|
|
+ if (IFD_io) {
|
|
|
+ ifdArray[ifdCount++] = IFD_io;
|
|
|
+ } else {
|
|
|
+ if (Verbose) {
|
|
|
+ printf(FMT_ERR, "Interoperability");
|
|
|
+ }
|
|
|
+ sts = EXIF_ERR_INVALID_IFD;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (Verbose) {
|
|
|
+ printf(FMT_ERR, "Exif");
|
|
|
+ }
|
|
|
+ sts = EXIF_ERR_INVALID_IFD;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // for GPS IFD
|
|
|
+ tag = getTagNodePtrFromIfd(IFD_0th, TAG_GPSInfoIFDPointer);
|
|
|
+ if (tag && !tag->error) {
|
|
|
+ ifdOffset = tag->numData[0];
|
|
|
+ if (ifdOffset != 0) {
|
|
|
+ IFD_gps = parseIFD(fp, App1StartOffset + offsetof(APP_HEADER, tiff), ifdOffset, IFD_GPS);
|
|
|
+ if (IFD_gps) {
|
|
|
+ ifdArray[ifdCount++] = IFD_gps;
|
|
|
+ } else {
|
|
|
+ if (Verbose) {
|
|
|
+ printf(FMT_ERR, "GPS");
|
|
|
+ }
|
|
|
+ sts = EXIF_ERR_INVALID_IFD;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // for 1st IFD
|
|
|
+ ifdOffset = IFD_0th->nextIfdOffset;
|
|
|
+ if (Verbose) {
|
|
|
+ printf("1st IFD ifdOffset=%u\n", ifdOffset);
|
|
|
+ }
|
|
|
+ if (ifdOffset != 0) {
|
|
|
+ IFD_1st = parseIFD(fp, App1StartOffset + offsetof(APP_HEADER, tiff), ifdOffset, IFD_1ST);
|
|
|
+ if (IFD_1st) {
|
|
|
+ ifdArray[ifdCount++] = IFD_1st;
|
|
|
+ } else {
|
|
|
+ if (Verbose) {
|
|
|
+ printf(FMT_ERR, "1st");
|
|
|
+ }
|
|
|
+ sts = EXIF_ERR_INVALID_IFD;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+DONE:
|
|
|
+ if (fp) {
|
|
|
+ fclose(fp);
|
|
|
+ }
|
|
|
+ return (sts <= 0) ? sts : ifdCount;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * createIfdTableArray()
|
|
|
+ *
|
|
|
+ * Parse the JPEG header and create the pointer array of the IFD tables
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] JPEGFileName : target JPEG file
|
|
|
+ * [out] result : result status value
|
|
|
+ * n: number of IFD tables
|
|
|
+ * 0: the Exif segment is not found
|
|
|
+ * -n: error
|
|
|
+ * EXIF_ERR_READ_FILE
|
|
|
+ * EXIF_ERR_INVALID_JPEG
|
|
|
+ * EXIF_ERR_INVALID_APP1HEADER
|
|
|
+ * EXIF_ERR_INVALID_IFD
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * NULL: error or no Exif segment
|
|
|
+ * !NULL: pointer array of the IFD tables
|
|
|
+ */
|
|
|
+void EXIF_API_EXPORT ** EXIF_API_CALL exif_createIfdTableArray(const char *JPEGFileName, int *result)
|
|
|
+{
|
|
|
+ void* ifdTable[32];
|
|
|
+ void** ppIfdArray = NULL;
|
|
|
+ int i, count = exif_fillIfdTableArray(JPEGFileName, ifdTable);
|
|
|
+ *result = count;
|
|
|
+ if (count > 0) {
|
|
|
+ // +1 extra NULL element to the array
|
|
|
+ ppIfdArray = (void**)malloc(sizeof(void*)*(count+1));
|
|
|
+ memset(ppIfdArray, 0, sizeof(void*)*(count+1));
|
|
|
+ for (i = 0; ifdTable[i] != NULL; i++) {
|
|
|
+ ppIfdArray[i] = ifdTable[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return ppIfdArray;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * freeIfdTables()
|
|
|
+ *
|
|
|
+ * Free the pointer array of the IFD tables
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] ifdArray : address of the IFD array
|
|
|
+ */
|
|
|
+void EXIF_API_EXPORT exif_freeIfdTables(void* ifdArray[32])
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ for (i = 0; ifdArray[i] != NULL; i++) {
|
|
|
+ freeIfdTable(ifdArray[i]);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * freeIfdTableArray()
|
|
|
+ *
|
|
|
+ * Free the pointer array of the IFD tables
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] ifdArray : address of the IFD array
|
|
|
+ */
|
|
|
+void EXIF_API_EXPORT exif_freeIfdTableArray(void **ifdArray)
|
|
|
+{
|
|
|
+ exif_freeIfdTables(ifdArray);
|
|
|
+ free(ifdArray);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * getIfdType()
|
|
|
+ *
|
|
|
+ * Returns the type of the IFD
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] ifd: target IFD
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * IFD TYPE value
|
|
|
+ */
|
|
|
+EXIF_IFD_TYPE EXIF_API_EXPORT exif_getIfdType(void *pIfd)
|
|
|
+{
|
|
|
+ IfdTable *ifd = (IfdTable*)pIfd;
|
|
|
+ if (!ifd) {
|
|
|
+ return IFD_UNKNOWN;
|
|
|
+ }
|
|
|
+ return ifd->ifdType;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * dumpIfdTable()
|
|
|
+ *
|
|
|
+ * Dump the IFD table
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] ifd: target IFD
|
|
|
+ */
|
|
|
+
|
|
|
+void EXIF_API_EXPORT exif_dumpIfdTable(void *pIfd)
|
|
|
+{
|
|
|
+ _dumpIfdTable(pIfd, NULL);
|
|
|
+}
|
|
|
+
|
|
|
+void EXIF_API_EXPORT exif_getIfdTableDump(void *pIfd, char **pp)
|
|
|
+{
|
|
|
+ if (pp) {
|
|
|
+ *pp = NULL;
|
|
|
+ }
|
|
|
+ _dumpIfdTable(pIfd, pp);
|
|
|
+}
|
|
|
+
|
|
|
+static void _dumpIfdTable(void *pIfd, char **p)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ IfdTable *ifd;
|
|
|
+ TagNode *tag;
|
|
|
+ char tagName[512];
|
|
|
+ int cnt = 0;
|
|
|
+ unsigned int count;
|
|
|
+
|
|
|
+ if (!pIfd) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ ifd = (IfdTable*)pIfd;
|
|
|
+
|
|
|
+ PRINTF(p, "\n{%s IFD}",
|
|
|
+ (ifd->ifdType == IFD_0TH) ? "0TH" :
|
|
|
+ (ifd->ifdType == IFD_1ST) ? "1ST" :
|
|
|
+ (ifd->ifdType == IFD_EXIF) ? "EXIF" :
|
|
|
+ (ifd->ifdType == IFD_GPS) ? "GPS" :
|
|
|
+ (ifd->ifdType == IFD_IO) ? "Interoperability" :
|
|
|
+ (ifd->ifdType == IFD_MPF) ? "MPF" : "");
|
|
|
+
|
|
|
+ if (Verbose) {
|
|
|
+ PRINTF(p, " tags=%u\n", ifd->tagCount);
|
|
|
+ } else {
|
|
|
+ PRINTF(p, "\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ tag = ifd->tags;
|
|
|
+ while (tag) {
|
|
|
+ if (Verbose) {
|
|
|
+ PRINTF(p, "tag[%02d] 0x%04X %s\n",
|
|
|
+ cnt++, tag->tagId, getTagName(ifd->ifdType, tag->tagId));
|
|
|
+ PRINTF(p, "\ttype=%u count=%u ", tag->type, tag->count);
|
|
|
+ PRINTF(p, "val=");
|
|
|
+ } else {
|
|
|
+ strcpy(tagName, getTagName(ifd->ifdType, tag->tagId));
|
|
|
+ PRINTF(p, " - %s: ", (strlen(tagName) > 0) ? tagName : "(unknown)");
|
|
|
+ }
|
|
|
+ if (tag->error) {
|
|
|
+ PRINTF(p, "(error)");
|
|
|
+ } else {
|
|
|
+ switch (tag->type) {
|
|
|
+ case TYPE_BYTE:
|
|
|
+ for (i = 0; i < (int)tag->count; i++) {
|
|
|
+ PRINTF(p, "%u ", (uint8_t)tag->numData[i]);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case TYPE_ASCII:
|
|
|
+ PRINTF(p, "[%s]", (char*)tag->byteData);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case TYPE_SHORT:
|
|
|
+ for (i = 0; i < (int)tag->count; i++) {
|
|
|
+ PRINTF(p, "%hu ", (uint16_t)tag->numData[i]);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case TYPE_LONG:
|
|
|
+ for (i = 0; i < (int)tag->count; i++) {
|
|
|
+ PRINTF(p, "%u ", tag->numData[i]);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case TYPE_RATIONAL:
|
|
|
+ for (i = 0; i < (int)tag->count; i++) {
|
|
|
+ PRINTF(p, "%u/%u ", tag->numData[i*2], tag->numData[i*2+1]);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case TYPE_SBYTE:
|
|
|
+ for (i = 0; i < (int)tag->count; i++) {
|
|
|
+ PRINTF(p, "%d ", (char)tag->numData[i]);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case TYPE_UNDEFINED:
|
|
|
+ count = tag->count;
|
|
|
+ // omit too long data if !Verbose
|
|
|
+ if (count > 16 && !Verbose) {
|
|
|
+ count = 16;
|
|
|
+ }
|
|
|
+ for (i = 0; i < (int)count; i++) {
|
|
|
+ // if character is printable
|
|
|
+ if (isgraph(tag->byteData[i])) {
|
|
|
+ PRINTF(p, "%c ", tag->byteData[i]);
|
|
|
+ } else {
|
|
|
+ PRINTF(p, "0x%02x ", tag->byteData[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (count < tag->count) {
|
|
|
+ PRINTF(p, "(omitted)");
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case TYPE_SSHORT:
|
|
|
+ for (i = 0; i < (int)tag->count; i++) {
|
|
|
+ PRINTF(p, "%hd ", (short)tag->numData[i]);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case TYPE_SLONG:
|
|
|
+ for (i = 0; i < (int)tag->count; i++) {
|
|
|
+ PRINTF(p, "%d ", (int)tag->numData[i]);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case TYPE_SRATIONAL:
|
|
|
+ for (i = 0; i < (int)tag->count; i++) {
|
|
|
+ PRINTF(p, "%d/%d ", (int)tag->numData[i*2], (int)tag->numData[i*2+1]);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ PRINTF(p, "\n");
|
|
|
+
|
|
|
+ tag = tag->next;
|
|
|
+ }
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * dumpIfdTableArray()
|
|
|
+ *
|
|
|
+ * Dump the array of the IFD tables
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] ifdArray : address of the IFD array
|
|
|
+ */
|
|
|
+void EXIF_API_EXPORT exif_dumpIfdTableArray(void **ifdArray)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ if (ifdArray) {
|
|
|
+ for (i = 0; ifdArray[i] != NULL; i++) {
|
|
|
+ exif_dumpIfdTable(ifdArray[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * getTagInfo()
|
|
|
+ *
|
|
|
+ * Get the ExifTagNodeInfo that matches the EXIF_IFD_TYPE & TagId
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] ifdArray : address of the IFD array
|
|
|
+ * [in] ifdType : target IFD TYPE
|
|
|
+ * [in] tagId : target tag ID
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * NULL: tag is not found
|
|
|
+ * !NULL: address of the ExifTagNodeInfo structure
|
|
|
+ */
|
|
|
+ExifTagNodeInfo EXIF_API_EXPORT * exif_getTagInfo(void **ifdArray,
|
|
|
+ EXIF_IFD_TYPE ifdType,
|
|
|
+ uint16_t tagId)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ if (!ifdArray) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ for (i = 0; ifdArray[i] != NULL; i++) {
|
|
|
+ if (exif_getIfdType(ifdArray[i]) == ifdType) {
|
|
|
+ void *targetTag = getTagNodePtrFromIfd(ifdArray[i], tagId);
|
|
|
+ if (!targetTag) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ return (ExifTagNodeInfo*)duplicateTagNode(targetTag);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * getTagInfoFromIfd()
|
|
|
+ *
|
|
|
+ * Get the ExifTagNodeInfo that matches the TagId
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] ifd : target IFD table
|
|
|
+ * [in] tagId : target tag ID
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * NULL: tag is not found
|
|
|
+ * !NULL: address of the ExifTagNodeInfo structure
|
|
|
+ */
|
|
|
+ExifTagNodeInfo EXIF_API_EXPORT * exif_getTagInfoFromIfd(void *ifd,
|
|
|
+ uint16_t tagId)
|
|
|
+{
|
|
|
+ if (!ifd) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ return (ExifTagNodeInfo*)getTagNodePtrFromIfd(ifd, tagId);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * freeTagInfo()
|
|
|
+ *
|
|
|
+ * Free the ExifTagNodeInfo allocated by getTagInfo() or getTagInfoFromIfd()
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] tag : target ExifTagNodeInfo
|
|
|
+ */
|
|
|
+void EXIF_API_EXPORT exif_freeTagInfo(void *tag)
|
|
|
+{
|
|
|
+ freeTagNode(tag);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * queryTagNodeIsExist()
|
|
|
+ *
|
|
|
+ * Query if the specified tag node is exist in the IFD tables
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] ifdTableArray: address of the IFD tables array
|
|
|
+ * [in] ifdType : target IFD type
|
|
|
+ * [in] tagId : target tag ID
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * 0: not exist
|
|
|
+ * 1: exist
|
|
|
+ */
|
|
|
+int EXIF_API_EXPORT exif_queryTagNodeIsExist(void **ifdTableArray,
|
|
|
+ EXIF_IFD_TYPE ifdType,
|
|
|
+ uint16_t tagId)
|
|
|
+{
|
|
|
+ IfdTable *ifd;
|
|
|
+ TagNode *tag;
|
|
|
+ if (!ifdTableArray) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ ifd = getIfdTableFromIfdTableArray(ifdTableArray, ifdType);
|
|
|
+ if (!ifd) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ tag = getTagNodePtrFromIfd(ifd, tagId);
|
|
|
+ if (!tag) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * createTagInfo()
|
|
|
+ *
|
|
|
+ * Create new ExifTagNodeInfo block
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] tagId: id of the tag
|
|
|
+ * [in] type: type of the tag
|
|
|
+ * [in] count: data count of the tag
|
|
|
+ * [out] pResult : error status
|
|
|
+ * 0: OK
|
|
|
+ * -n: error
|
|
|
+ * EXIF_ERR_INVALID_TYPE
|
|
|
+ * EXIF_ERR_INVALID_COUNT
|
|
|
+ * EXIF_ERR_MEMALLOC
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * NULL: error
|
|
|
+ * !NULL: address of the newly created ExifTagNodeInfo
|
|
|
+ */
|
|
|
+ExifTagNodeInfo EXIF_API_EXPORT * exif_createTagInfo(uint16_t tagId,
|
|
|
+ uint16_t type,
|
|
|
+ unsigned int count,
|
|
|
+ int *pResult)
|
|
|
+{
|
|
|
+ TagNode *tag;
|
|
|
+ if (type < TYPE_BYTE || type > TYPE_SRATIONAL) {
|
|
|
+ if (pResult) {
|
|
|
+ *pResult = EXIF_ERR_INVALID_TYPE;
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ if (count <= 0) {
|
|
|
+ if (pResult) {
|
|
|
+ *pResult = EXIF_ERR_INVALID_COUNT;
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ tag = (TagNode*)malloc(sizeof(TagNode));
|
|
|
+ if (!tag) {
|
|
|
+ if (pResult) {
|
|
|
+ *pResult = EXIF_ERR_MEMALLOC;
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ memset(tag, 0, sizeof(TagNode));
|
|
|
+ tag->tagId = tagId;
|
|
|
+ tag->type = type;
|
|
|
+ tag->count = count;
|
|
|
+
|
|
|
+ if (type == TYPE_ASCII || type == TYPE_UNDEFINED) {
|
|
|
+ tag->byteData = (uint8_t*)malloc(count*sizeof(char));
|
|
|
+ }
|
|
|
+ else if (type == TYPE_BYTE ||
|
|
|
+ type == TYPE_SBYTE ||
|
|
|
+ type == TYPE_SHORT ||
|
|
|
+ type == TYPE_LONG ||
|
|
|
+ type == TYPE_SSHORT ||
|
|
|
+ type == TYPE_SLONG) {
|
|
|
+ tag->numData = (unsigned int*)malloc(count*sizeof(int));
|
|
|
+ }
|
|
|
+ else if (type == TYPE_RATIONAL ||
|
|
|
+ type == TYPE_SRATIONAL) {
|
|
|
+ tag->numData = (unsigned int*)malloc(count*sizeof(int)*2);
|
|
|
+ }
|
|
|
+ if (pResult) {
|
|
|
+ *pResult = 0;
|
|
|
+ }
|
|
|
+ return (ExifTagNodeInfo*)tag;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * removeIfdTableFromIfdTableArray()
|
|
|
+ *
|
|
|
+ * Remove the IFD table from the ifdTableArray
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] ifdTableArray: address of the IFD tables array
|
|
|
+ * [in] ifdType : target IFD type
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * n: number of the removed IFD tables
|
|
|
+ */
|
|
|
+int EXIF_API_EXPORT exif_removeIfdTableFromIfdTableArray(void **ifdTableArray, EXIF_IFD_TYPE ifdType)
|
|
|
+{
|
|
|
+ int i, num = 0, ret = 0;
|
|
|
+ if (!ifdTableArray) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ // count IFD tables
|
|
|
+ num = countIfdTableOnIfdTableArray(ifdTableArray);
|
|
|
+ for (;;) { // possibility of multiple entries
|
|
|
+ for (i = 0; i < num; i++) {
|
|
|
+ IfdTable *ifd = ifdTableArray[i];
|
|
|
+ if (ifd->ifdType == ifdType) {
|
|
|
+ freeIfdTable(ifd);
|
|
|
+ ifdTableArray[i] = NULL;
|
|
|
+ ret++;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (i == num) {
|
|
|
+ break; // no more found
|
|
|
+ }
|
|
|
+ // left justify the array
|
|
|
+ memcpy(&ifdTableArray[i], &ifdTableArray[i+1], (num-i) * sizeof(void*));
|
|
|
+ num--;
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * insertIfdTableToIfdTableArray()
|
|
|
+ *
|
|
|
+ * Insert new IFD table to the ifdTableArray
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] ifdTableArray: address of the IFD tables array
|
|
|
+ * [in] ifdType : target IFD type
|
|
|
+ * [out] pResult : error status
|
|
|
+ * 0: OK
|
|
|
+ * -n: error
|
|
|
+ * EXIF_ERR_ALREADY_EXIST
|
|
|
+ * EXIF_ERR_MEMALLOC
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * NULL: error
|
|
|
+ * !NULL: address of the newly created ifdTableArray
|
|
|
+ *
|
|
|
+ * note
|
|
|
+ * This function frees old ifdTableArray if is not NULL.
|
|
|
+ */
|
|
|
+void EXIF_API_EXPORT ** exif_insertIfdTableToIfdTableArray(void **ifdTableArray,
|
|
|
+ EXIF_IFD_TYPE ifdType,
|
|
|
+ int *pResult)
|
|
|
+{
|
|
|
+ void *newIfd;
|
|
|
+ void **newIfdTableArray;
|
|
|
+ int num = 0;
|
|
|
+ if (!ifdTableArray) {
|
|
|
+ num = 0;
|
|
|
+ } else {
|
|
|
+ num = countIfdTableOnIfdTableArray(ifdTableArray);
|
|
|
+ }
|
|
|
+ if (num > 0 && getIfdTableFromIfdTableArray(ifdTableArray, ifdType) != NULL) {
|
|
|
+ if (pResult) {
|
|
|
+ *pResult = EXIF_ERR_ALREADY_EXIST;
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ // create the new IFD table
|
|
|
+ newIfd = createIfdTable(ifdType, 0, 0);
|
|
|
+ if (!newIfd) {
|
|
|
+ if (pResult) {
|
|
|
+ *pResult = EXIF_ERR_MEMALLOC;
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ // copy existing IFD tables to the new array
|
|
|
+ newIfdTableArray = (void**)malloc(sizeof(void*)*(num+2));
|
|
|
+ if (!newIfdTableArray) {
|
|
|
+ if (pResult) {
|
|
|
+ *pResult = EXIF_ERR_MEMALLOC;
|
|
|
+ }
|
|
|
+ free(newIfd);
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ memset(newIfdTableArray, 0, sizeof(void*)*(num+2));
|
|
|
+ if (num > 0) {
|
|
|
+ memcpy(newIfdTableArray, ifdTableArray, num * sizeof(void*));
|
|
|
+ }
|
|
|
+ // add the new IFD table
|
|
|
+ newIfdTableArray[num] = newIfd;
|
|
|
+ if (ifdTableArray) {
|
|
|
+ free(ifdTableArray); // free the old array
|
|
|
+ }
|
|
|
+ if (pResult) {
|
|
|
+ *pResult = 0;
|
|
|
+ }
|
|
|
+ return newIfdTableArray;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * removeTagNodeFromIfdTableArray()
|
|
|
+ *
|
|
|
+ * Remove the specified tag node from the IFD table
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] ifdTableArray: address of the IFD tables array
|
|
|
+ * [in] ifdType : target IFD type
|
|
|
+ * [in] tagId : target tag ID
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * n: number of the removed tags
|
|
|
+ */
|
|
|
+int EXIF_API_EXPORT exif_removeTagNodeFromIfdTableArray(void **ifdTableArray,
|
|
|
+ EXIF_IFD_TYPE ifdType,
|
|
|
+ uint16_t tagId)
|
|
|
+{
|
|
|
+ IfdTable *ifd = getIfdTableFromIfdTableArray(ifdTableArray, ifdType);
|
|
|
+ if (!ifd) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ return removeTagOnIfd(ifd, tagId);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * insertTagNodeToIfdTableArray()
|
|
|
+ *
|
|
|
+ * Insert the specified tag node to the IFD table
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] ifdTableArray: address of the IFD tables array
|
|
|
+ * [in] ifdType : target IFD type
|
|
|
+ * [in] ExifTagNodeInfo: address of the ExifTagNodeInfo
|
|
|
+ *
|
|
|
+ * note
|
|
|
+ * This function uses the copy of the specified tag data.
|
|
|
+ * The caller must free it after this function returns.
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * 0: OK
|
|
|
+ * EXIF_ERR_INVALID_POINTER:
|
|
|
+ * EXIF_ERR_NOT_EXIST:
|
|
|
+ * EXIF_ERR_ALREADY_EXIST:
|
|
|
+ * EXIF_ERR_UNKNOWN:
|
|
|
+ */
|
|
|
+int EXIF_API_EXPORT exif_insertTagNodeToIfdTableArray(void **ifdTableArray,
|
|
|
+ EXIF_IFD_TYPE ifdType,
|
|
|
+ ExifTagNodeInfo *ExifTagNodeInfo)
|
|
|
+{
|
|
|
+ IfdTable *ifd;
|
|
|
+ if (!ifdTableArray) {
|
|
|
+ return EXIF_ERR_INVALID_POINTER;
|
|
|
+ }
|
|
|
+ if (!ExifTagNodeInfo) {
|
|
|
+ return EXIF_ERR_INVALID_POINTER;
|
|
|
+ }
|
|
|
+ ifd = getIfdTableFromIfdTableArray(ifdTableArray, ifdType);
|
|
|
+ if (!ifd) {
|
|
|
+ return EXIF_ERR_NOT_EXIST;
|
|
|
+ }
|
|
|
+ // already exists the same type entry
|
|
|
+ if (getTagNodePtrFromIfd(ifd, ExifTagNodeInfo->tagId) != NULL) {
|
|
|
+ return EXIF_ERR_ALREADY_EXIST;
|
|
|
+ }
|
|
|
+ // add to the IFD table
|
|
|
+ if (!addTagNodeToIfd(ifd,
|
|
|
+ ExifTagNodeInfo->tagId,
|
|
|
+ ExifTagNodeInfo->type,
|
|
|
+ ExifTagNodeInfo->count,
|
|
|
+ ExifTagNodeInfo->numData,
|
|
|
+ ExifTagNodeInfo->byteData)) {
|
|
|
+ return EXIF_ERR_UNKNOWN;
|
|
|
+ }
|
|
|
+ ifd->tagCount++;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * getThumbnailDataOnIfdTableArray()
|
|
|
+ *
|
|
|
+ * Get a copy of the thumbnail data from the 1st IFD table
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] ifdTableArray : address of the IFD tables array
|
|
|
+ * [out] pLength : returns the length of the thumbnail data
|
|
|
+ * [out] pResult : error status
|
|
|
+ * 0: OK
|
|
|
+ * -n: error
|
|
|
+ * EXIF_ERR_INVALID_POINTER
|
|
|
+ * EXIF_ERR_MEMALLOC
|
|
|
+ * EXIF_ERR_NOT_EXIST
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * NULL: error
|
|
|
+ * !NULL: the thumbnail data
|
|
|
+ *
|
|
|
+ * note
|
|
|
+ * This function returns the copy of the thumbnail data.
|
|
|
+ * The caller must free it.
|
|
|
+ */
|
|
|
+uint8_t EXIF_API_EXPORT * exif_getThumbnailDataOnIfdTableArray(void **ifdTableArray,
|
|
|
+ unsigned int *pLength,
|
|
|
+ int *pResult)
|
|
|
+{
|
|
|
+ IfdTable *ifd;
|
|
|
+ TagNode *tag;
|
|
|
+ unsigned int len;
|
|
|
+ uint8_t *retp;
|
|
|
+ if (!ifdTableArray || !pLength) {
|
|
|
+ if (pResult) {
|
|
|
+ *pResult = EXIF_ERR_INVALID_POINTER;
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ ifd = getIfdTableFromIfdTableArray(ifdTableArray, IFD_1ST);
|
|
|
+ if (!ifd || !ifd->p) {
|
|
|
+ if (pResult) {
|
|
|
+ *pResult = EXIF_ERR_NOT_EXIST;
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ tag = getTagNodePtrFromIfd(ifd, TAG_JPEGInterchangeFormatLength);
|
|
|
+ if (!tag || tag->error) {
|
|
|
+ if (pResult) {
|
|
|
+ *pResult = EXIF_ERR_NOT_EXIST;
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ len = tag->numData[0];
|
|
|
+ if (len <= 0) {
|
|
|
+ if (pResult) {
|
|
|
+ *pResult = EXIF_ERR_NOT_EXIST;
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ retp= (uint8_t*)malloc(len);
|
|
|
+ if (!retp) {
|
|
|
+ if (pResult) {
|
|
|
+ *pResult = EXIF_ERR_MEMALLOC;
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ memcpy(retp, ifd->p, len);
|
|
|
+ *pLength = len;
|
|
|
+ if (pResult) {
|
|
|
+ *pResult = 0;
|
|
|
+ }
|
|
|
+ return retp;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * setThumbnailDataOnIfdTableArray()
|
|
|
+ *
|
|
|
+ * Set or update the thumbnail data to the 1st IFD table
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] ifdTableArray : address of the IFD tables array
|
|
|
+ * [in] pData : thumbnail data
|
|
|
+ * [in] length : thumbnail data length
|
|
|
+ *
|
|
|
+ * note
|
|
|
+ * This function creates the copy of the specified data.
|
|
|
+ * The caller must free it after this function returns.
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * 0: OK
|
|
|
+ * -n: error
|
|
|
+ * EXIF_ERR_INVALID_POINTER
|
|
|
+ * EXIF_ERR_MEMALLOC
|
|
|
+ * EXIF_ERR_UNKNOWN
|
|
|
+ */
|
|
|
+int EXIF_API_EXPORT exif_setThumbnailDataOnIfdTableArray(void **ifdTableArray,
|
|
|
+ uint8_t *pData,
|
|
|
+ unsigned int length)
|
|
|
+{
|
|
|
+ IfdTable *ifd;
|
|
|
+ TagNode *tag;
|
|
|
+ unsigned int zero = 0;
|
|
|
+ if (!ifdTableArray || !pData || length <= 0) {
|
|
|
+ return EXIF_ERR_INVALID_POINTER;
|
|
|
+ }
|
|
|
+ ifd = getIfdTableFromIfdTableArray(ifdTableArray, IFD_1ST);
|
|
|
+ if (!ifd) {
|
|
|
+ int count = countIfdTableOnIfdTableArray(ifdTableArray);
|
|
|
+ void* ifd1st = createIfdTable(IFD_1ST, 0, 0);
|
|
|
+ printf("count=%d ifd1st=%p\n", count, ifd1st);
|
|
|
+ ifdTableArray[count] = ifd1st;
|
|
|
+ ifd = getIfdTableFromIfdTableArray(ifdTableArray, IFD_1ST);
|
|
|
+ if (!ifd) {
|
|
|
+ return EXIF_ERR_NOT_EXIST;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (ifd->p) {
|
|
|
+ free(ifd->p);
|
|
|
+ }
|
|
|
+ // set thumbnail length;
|
|
|
+ tag = getTagNodePtrFromIfd(ifd, TAG_JPEGInterchangeFormatLength);
|
|
|
+ if (tag) {
|
|
|
+ setSingleNumDataToTag(tag, length);
|
|
|
+ } else {
|
|
|
+ if (!addTagNodeToIfd(ifd, TAG_JPEGInterchangeFormatLength,
|
|
|
+ TYPE_LONG, 1, &length, NULL)) {
|
|
|
+ return EXIF_ERR_UNKNOWN;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ tag = getTagNodePtrFromIfd(ifd, TAG_JPEGInterchangeFormat);
|
|
|
+ if (tag) {
|
|
|
+ setSingleNumDataToTag(tag, zero);
|
|
|
+ } else {
|
|
|
+ // add thumbnail offset tag if not exist
|
|
|
+ addTagNodeToIfd(ifd, TAG_JPEGInterchangeFormat,
|
|
|
+ TYPE_LONG, 1, &zero, NULL);
|
|
|
+ }
|
|
|
+ ifd->p = (uint8_t*)malloc(length);
|
|
|
+ if (!ifd->p) {
|
|
|
+ return EXIF_ERR_MEMALLOC;
|
|
|
+ }
|
|
|
+ memcpy(ifd->p, pData, length);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * updateExifSegmentInJPEGFile()
|
|
|
+ *
|
|
|
+ * Update the Exif segment in a JPEG file
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] inJPEGFileName : original JPEG file
|
|
|
+ * [in] outJPGEFileName : output JPEG file
|
|
|
+ * [in] ifdTableArray : address of the IFD tables array
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * 1: OK
|
|
|
+ * -n: error
|
|
|
+ * EXIF_ERR_READ_FILE
|
|
|
+ * EXIF_ERR_WRITE_FILE
|
|
|
+ * EXIF_ERR_INVALID_JPEG
|
|
|
+ * EXIF_ERR_INVALID_APP1HEADER
|
|
|
+ * EXIF_ERR_INVALID_POINTER
|
|
|
+ * ERROR_UNKNOWN:
|
|
|
+ */
|
|
|
+int EXIF_API_EXPORT exif_updateExifSegmentInJPEGFile(const char *inJPEGFileName,
|
|
|
+ const char *outJPGEFileName,
|
|
|
+ void **ifdTableArray)
|
|
|
+{
|
|
|
+ int ofs;
|
|
|
+ int i, sts = 1, hasExifSegment;
|
|
|
+ size_t readLen, writeLen;
|
|
|
+ uint8_t buf[8192], *p;
|
|
|
+ FILE *fpr = NULL, *fpw = NULL;
|
|
|
+
|
|
|
+ // refresh the length and offset variables in the IFD table
|
|
|
+ sts = fixLengthAndOffsetInIfdTables(ifdTableArray);
|
|
|
+ if (sts != 0) {
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ fpr = fopen(inJPEGFileName, "rb");
|
|
|
+ if (!fpr) {
|
|
|
+ sts = EXIF_ERR_READ_FILE;
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ sts = init(fpr);
|
|
|
+ if (sts < 0) {
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ if (sts == 0) {
|
|
|
+ hasExifSegment = 0;
|
|
|
+ ofs = JpegDQTOffset;
|
|
|
+ } else {
|
|
|
+ hasExifSegment = 1;
|
|
|
+ ofs = App1StartOffset;
|
|
|
+ }
|
|
|
+ fpw = fopen(outJPGEFileName, "wb");
|
|
|
+ if (!fpw) {
|
|
|
+ sts = EXIF_ERR_WRITE_FILE;
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ // copy the data in front of the Exif segment
|
|
|
+ rewind(fpr);
|
|
|
+ p = buf;
|
|
|
+ if (ofs > sizeof(buf)) {
|
|
|
+ // allocate new buffer if needed
|
|
|
+ p = (uint8_t*)malloc(ofs);
|
|
|
+ }
|
|
|
+ if (!p) {
|
|
|
+ for (i = 0; i < ofs; i++) {
|
|
|
+ fread(buf, 1, sizeof(char), fpr);
|
|
|
+ fwrite(buf, 1, sizeof(char), fpw);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (fread(p, 1, ofs, fpr) < (size_t)ofs) {
|
|
|
+ sts = EXIF_ERR_READ_FILE;
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ if (fwrite(p, 1, ofs, fpw) < (size_t)ofs) {
|
|
|
+ sts = EXIF_ERR_WRITE_FILE;
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ if (p != &buf[0]) {
|
|
|
+ free(p);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // write new Exif segment
|
|
|
+ sts = writeExifSegment(fpw, ifdTableArray);
|
|
|
+ if (sts != 0) {
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ sts = 1;
|
|
|
+ if (hasExifSegment) {
|
|
|
+ // seek to the end of the Exif segment
|
|
|
+ ofs = App1StartOffset + sizeof(App1Header.marker) + App1Header.length;
|
|
|
+ if (fseek(fpr, ofs, SEEK_SET) != 0) {
|
|
|
+ sts = EXIF_ERR_READ_FILE;
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // read & write
|
|
|
+ for (;;) {
|
|
|
+ readLen = fread(buf, 1, sizeof(buf), fpr);
|
|
|
+ if (readLen <= 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ writeLen = fwrite(buf, 1, readLen, fpw);
|
|
|
+ if (writeLen != readLen) {
|
|
|
+ sts = EXIF_ERR_WRITE_FILE;
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ }
|
|
|
+DONE:
|
|
|
+ if (fpw) {
|
|
|
+ fclose(fpw);
|
|
|
+ }
|
|
|
+ if (fpr) {
|
|
|
+ fclose(fpr);
|
|
|
+ }
|
|
|
+ return sts;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * removeAdobeMetadataSegmentFromJPEGFile()
|
|
|
+ *
|
|
|
+ * Remove Adobe's XMP metadata segment from a JPEG file
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] inJPEGFileName : original JPEG file
|
|
|
+ * [in] outJPGEFileName : output JPEG file
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * 1: OK
|
|
|
+ * 0: Adobe's metadata segment is not found
|
|
|
+ * -n: error
|
|
|
+ * EXIF_ERR_READ_FILE
|
|
|
+ * EXIF_ERR_WRITE_FILE
|
|
|
+ * EXIF_ERR_INVALID_JPEG
|
|
|
+ * EXIF_ERR_INVALID_APP1HEADER
|
|
|
+ */
|
|
|
+int EXIF_API_EXPORT exif_removeAdobeMetadataSegmentFromJPEGFile(const char *inJPEGFileName,
|
|
|
+ const char *outJPGEFileName)
|
|
|
+{
|
|
|
+#define ADOBE_METADATA_ID "http://ns.adobe.com/xap/"
|
|
|
+#define ADOBE_METADATA_ID_LEN 24
|
|
|
+
|
|
|
+ typedef struct _SegmentHeader {
|
|
|
+ uint16_t marker;
|
|
|
+ uint16_t length;
|
|
|
+ } SEGMENT_HEADER;
|
|
|
+
|
|
|
+ SEGMENT_HEADER hdr;
|
|
|
+ int i, sts = 1;
|
|
|
+ size_t readLen, writeLen;
|
|
|
+ unsigned int ofs;
|
|
|
+ uint8_t buf[8192], *p;
|
|
|
+ FILE *fpr = NULL, *fpw = NULL;
|
|
|
+
|
|
|
+ fpr = fopen(inJPEGFileName, "rb");
|
|
|
+ if (!fpr) {
|
|
|
+ sts = EXIF_ERR_READ_FILE;
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ sts = getAppNStartOffset(fpr, APP1_MARKER, ADOBE_METADATA_ID, ADOBE_METADATA_ID_LEN, NULL);
|
|
|
+ if (sts <= 0) { // target segment is not exist or something error
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ ofs = sts;
|
|
|
+ sts = 1;
|
|
|
+ fpw = fopen(outJPGEFileName, "wb");
|
|
|
+ if (!fpw) {
|
|
|
+ sts = EXIF_ERR_WRITE_FILE;
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ // copy the data in front of the App1 segment
|
|
|
+ rewind(fpr);
|
|
|
+ p = buf;
|
|
|
+ if (ofs > sizeof(buf)) {
|
|
|
+ // allocate new buffer if needed
|
|
|
+ p = (uint8_t*)malloc(ofs);
|
|
|
+ }
|
|
|
+ if (!p) {
|
|
|
+ for (i = 0; i < (int)ofs; i++) {
|
|
|
+ fread(buf, 1, sizeof(char), fpr);
|
|
|
+ fwrite(buf, 1, sizeof(char), fpw);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (fread(p, 1, ofs, fpr) < (size_t)ofs) {
|
|
|
+ sts = EXIF_ERR_READ_FILE;
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ if (fwrite(p, 1, ofs, fpw) < (size_t)ofs) {
|
|
|
+ sts = EXIF_ERR_WRITE_FILE;
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ if (p != &buf[0]) {
|
|
|
+ free(p);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (fread(&hdr, 1, sizeof(SEGMENT_HEADER), fpr) != sizeof(SEGMENT_HEADER)) {
|
|
|
+ sts = EXIF_ERR_READ_FILE;
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ if (systemIsLittleEndian()) {
|
|
|
+ // the segment length value is always in big-endian order
|
|
|
+ hdr.length = swab16(hdr.length);
|
|
|
+ }
|
|
|
+ // seek to the end of the App1 segment
|
|
|
+ if (fseek(fpr, hdr.length - sizeof(hdr.length), SEEK_CUR) != 0) {
|
|
|
+ sts = EXIF_ERR_READ_FILE;
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ // read & write
|
|
|
+ for (;;) {
|
|
|
+ readLen = fread(buf, 1, sizeof(buf), fpr);
|
|
|
+ if (readLen <= 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ writeLen = fwrite(buf, 1, readLen, fpw);
|
|
|
+ if (writeLen != readLen) {
|
|
|
+ sts = EXIF_ERR_WRITE_FILE;
|
|
|
+ goto DONE;
|
|
|
+ }
|
|
|
+ }
|
|
|
+DONE:
|
|
|
+ if (fpw) {
|
|
|
+ fclose(fpw);
|
|
|
+ }
|
|
|
+ if (fpr) {
|
|
|
+ fclose(fpr);
|
|
|
+ }
|
|
|
+ return sts;
|
|
|
+}
|
|
|
+static int seekToRelativeOffset(FILE *fp, unsigned int baseOff, unsigned int ofs)
|
|
|
+{
|
|
|
+ return fseek(fp, baseOff + ofs, SEEK_SET);
|
|
|
+}
|
|
|
+
|
|
|
+static const char *getTagName(int ifdType, uint16_t tagId)
|
|
|
+{
|
|
|
+ if (ifdType == IFD_0TH || ifdType == IFD_1ST || ifdType == IFD_EXIF) {
|
|
|
+ switch (tagId) {
|
|
|
+ case TAG_ImageWidth: return "ImageWidth";
|
|
|
+ case TAG_ImageLength: return "ImageLength";
|
|
|
+ case TAG_BitsPerSample: return "BitsPerSample";
|
|
|
+ case TAG_Compression: return "Compression";
|
|
|
+ case TAG_PhotometricInterpretation: return "PhotometricInterpretation";
|
|
|
+ case TAG_Orientation: return "Orientation";
|
|
|
+ case TAG_SamplesPerPixel: return "SamplesPerPixel";
|
|
|
+ case TAG_PlanarConfiguration: return "PlanarConfiguration";
|
|
|
+ case TAG_YCbCrSubSampling: return "YCbCrSubSampling";
|
|
|
+ case TAG_YCbCrPositioning: return "YCbCrPositioning";
|
|
|
+ case TAG_XResolution: return "XResolution";
|
|
|
+ case TAG_YResolution: return "YResolution";
|
|
|
+ case TAG_ResolutionUnit: return "ResolutionUnit";
|
|
|
+
|
|
|
+ case TAG_StripOffsets: return "StripOffsets";
|
|
|
+ case TAG_RowsPerStrip: return "RowsPerStrip";
|
|
|
+ case TAG_StripByteCounts: return "StripByteCounts";
|
|
|
+ case TAG_JPEGInterchangeFormat: return "JPEGInterchangeFormat";
|
|
|
+ case TAG_JPEGInterchangeFormatLength: return "JPEGInterchangeFormatLength";
|
|
|
+
|
|
|
+ case TAG_TransferFunction: return "TransferFunction";
|
|
|
+ case TAG_WhitePoint: return "WhitePoint";
|
|
|
+ case TAG_PrimaryChromaticities: return "PrimaryChromaticities";
|
|
|
+ case TAG_YCbCrCoefficients: return "YCbCrCoefficients";
|
|
|
+ case TAG_ReferenceBlackWhite: return "ReferenceBlackWhite";
|
|
|
+
|
|
|
+ case TAG_DateTime: return "DateTime";
|
|
|
+ case TAG_ImageDescription: return "ImageDescription";
|
|
|
+ case TAG_Make: return "Make";
|
|
|
+ case TAG_Model: return "Model";
|
|
|
+ case TAG_Software: return "Software";
|
|
|
+ case TAG_Artist: return "Artist";
|
|
|
+ case TAG_Copyright: return "Copyright";
|
|
|
+ case TAG_ExifIFDPointer: return "ExifIFDPointer";
|
|
|
+ case TAG_GPSInfoIFDPointer: return "GPSInfoIFDPointer";
|
|
|
+ case TAG_InteroperabilityIFDPointer: return "InteroperabilityIFDPointer";
|
|
|
+
|
|
|
+ case TAG_Rating: return "Rating";
|
|
|
+
|
|
|
+ case TAG_ExifVersion: return "ExifVersion";
|
|
|
+ case TAG_FlashPixVersion: return "FlashPixVersion";
|
|
|
+
|
|
|
+ case TAG_ColorSpace: return "ColorSpace";
|
|
|
+
|
|
|
+ case TAG_ComponentsConfiguration: return "ComponentsConfiguration";
|
|
|
+ case TAG_CompressedBitsPerPixel: return "CompressedBitsPerPixel";
|
|
|
+ case TAG_PixelXDimension: return "PixelXDimension";
|
|
|
+ case TAG_PixelYDimension: return "PixelYDimension";
|
|
|
+
|
|
|
+ case TAG_MakerNote: return "MakerNote";
|
|
|
+ case TAG_UserComment: return "UserComment";
|
|
|
+
|
|
|
+ case TAG_RelatedSoundFile: return "RelatedSoundFile";
|
|
|
+
|
|
|
+ case TAG_DateTimeOriginal: return "DateTimeOriginal";
|
|
|
+ case TAG_DateTimeDigitized: return "DateTimeDigitized";
|
|
|
+ case TAG_SubSecTime: return "SubSecTime";
|
|
|
+ case TAG_SubSecTimeOriginal: return "SubSecTimeOriginal";
|
|
|
+ case TAG_SubSecTimeDigitized: return "SubSecTimeDigitized";
|
|
|
+
|
|
|
+ case TAG_ExposureTime: return "ExposureTime";
|
|
|
+ case TAG_FNumber: return "FNumber";
|
|
|
+ case TAG_ExposureProgram: return "ExposureProgram";
|
|
|
+ case TAG_SpectralSensitivity: return "SpectralSensitivity";
|
|
|
+ case TAG_PhotographicSensitivity: return "PhotographicSensitivity";
|
|
|
+ case TAG_OECF: return "OECF";
|
|
|
+ case TAG_SensitivityType: return "SensitivityType";
|
|
|
+ case TAG_StandardOutputSensitivity: return "StandardOutputSensitivity";
|
|
|
+ case TAG_RecommendedExposureIndex: return "RecommendedExposureIndex";
|
|
|
+ case TAG_ISOSpeed: return "ISOSpeed";
|
|
|
+ case TAG_ISOSpeedLatitudeyyy: return "ISOSpeedLatitudeyyy";
|
|
|
+ case TAG_ISOSpeedLatitudezzz: return "ISOSpeedLatitudezzz";
|
|
|
+
|
|
|
+ case TAG_ShutterSpeedValue: return "ShutterSpeedValue";
|
|
|
+ case TAG_ApertureValue: return "ApertureValue";
|
|
|
+ case TAG_BrightnessValue: return "BrightnessValue";
|
|
|
+ case TAG_ExposureBiasValue: return "ExposureBiasValue";
|
|
|
+ case TAG_MaxApertureValue: return "MaxApertureValue";
|
|
|
+ case TAG_SubjectDistance: return "SubjectDistance";
|
|
|
+ case TAG_MeteringMode: return "MeteringMode";
|
|
|
+ case TAG_LightSource: return "LightSource";
|
|
|
+ case TAG_Flash: return "Flash";
|
|
|
+ case TAG_FocalLength: return "FocalLength";
|
|
|
+ case TAG_SubjectArea: return "SubjectArea";
|
|
|
+ case TAG_FlashEnergy: return "FlashEnergy";
|
|
|
+ case TAG_SpatialFrequencyResponse: return "SpatialFrequencyResponse";
|
|
|
+ case TAG_FocalPlaneXResolution: return "FocalPlaneXResolution";
|
|
|
+ case TAG_FocalPlaneYResolution: return "FocalPlaneYResolution";
|
|
|
+ case TAG_FocalPlaneResolutionUnit: return "FocalPlaneResolutionUnit";
|
|
|
+ case TAG_SubjectLocation: return "SubjectLocation";
|
|
|
+ case TAG_ExposureIndex: return "ExposureIndex";
|
|
|
+ case TAG_SensingMethod: return "SensingMethod";
|
|
|
+ case TAG_FileSource: return "FileSource";
|
|
|
+ case TAG_SceneType: return "SceneType";
|
|
|
+ case TAG_CFAPattern: return "CFAPattern";
|
|
|
+
|
|
|
+ case TAG_CustomRendered: return "CustomRendered";
|
|
|
+ case TAG_ExposureMode: return "ExposureMode";
|
|
|
+ case TAG_WhiteBalance: return "WhiteBalance";
|
|
|
+ case TAG_DigitalZoomRatio: return "DigitalZoomRatio";
|
|
|
+ case TAG_FocalLengthIn35mmFormat: return "FocalLengthIn35mmFormat";
|
|
|
+ case TAG_SceneCaptureType: return "SceneCaptureType";
|
|
|
+ case TAG_GainControl: return "GainControl";
|
|
|
+ case TAG_Contrast: return "Contrast";
|
|
|
+ case TAG_Saturation: return "Saturation";
|
|
|
+ case TAG_Sharpness: return "Sharpness";
|
|
|
+ case TAG_DeviceSettingDescription: return "DeviceSettingDescription";
|
|
|
+ case TAG_SubjectDistanceRange: return "SubjectDistanceRange";
|
|
|
+
|
|
|
+ case TAG_ImageUniqueID: return "ImageUniqueID";
|
|
|
+ case TAG_CameraOwnerName: return "CameraOwnerName";
|
|
|
+ case TAG_BodySerialNumber: return "BodySerialNumber";
|
|
|
+ case TAG_LensSpecification: return "LensSpecification";
|
|
|
+ case TAG_LensMake: return "LensMake";
|
|
|
+ case TAG_LensModel: return "LensModel";
|
|
|
+ case TAG_LensSerialNumber: return "LensSerialNumber";
|
|
|
+ case TAG_Gamma: return "Gamma";
|
|
|
+ case TAG_PrintIM: return "PrintIM";
|
|
|
+ case TAG_Padding: return "Padding";
|
|
|
+ default: ;
|
|
|
+ }
|
|
|
+ } else if (ifdType == IFD_GPS) {
|
|
|
+ switch (tagId) {
|
|
|
+ case TAG_GPSVersionID: return "GPSVersionID";
|
|
|
+ case TAG_GPSLatitudeRef: return "GPSLatitudeRef";
|
|
|
+ case TAG_GPSLatitude: return "GPSLatitude";
|
|
|
+ case TAG_GPSLongitudeRef: return "GPSLongitudeRef";
|
|
|
+ case TAG_GPSLongitude: return "GPSLongitude";
|
|
|
+ case TAG_GPSAltitudeRef: return "GPSAltitudeRef";
|
|
|
+ case TAG_GPSAltitude: return "GPSAltitude";
|
|
|
+ case TAG_GPSTimeStamp: return "GPSTimeStamp";
|
|
|
+ case TAG_GPSSatellites: return "GPSSatellites";
|
|
|
+ case TAG_GPSStatus: return "GPSStatus";
|
|
|
+ case TAG_GPSMeasureMode: return "GPSMeasureMode";
|
|
|
+ case TAG_GPSDOP: return "GPSDOP";
|
|
|
+ case TAG_GPSSpeedRef: return "GPSSpeedRef";
|
|
|
+ case TAG_GPSSpeed: return "GPSSpeed";
|
|
|
+ case TAG_GPSTrackRef: return "GPSTrackRef";
|
|
|
+ case TAG_GPSTrack: return "GPSTrack";
|
|
|
+ case TAG_GPSImgDirectionRef: return "GPSImgDirectionRef";
|
|
|
+ case TAG_GPSImgDirection: return "GPSImgDirection";
|
|
|
+ case TAG_GPSMapDatum: return "GPSMapDatum";
|
|
|
+ case TAG_GPSDestLatitudeRef: return "GPSDestLatitudeRef";
|
|
|
+ case TAG_GPSDestLatitude: return "GPSDestLatitude";
|
|
|
+ case TAG_GPSDestLongitudeRef: return "GPSDestLongitudeRef";
|
|
|
+ case TAG_GPSDestLongitude: return "GPSDestLongitude";
|
|
|
+ case TAG_GPSBearingRef: return "GPSBearingRef";
|
|
|
+ case TAG_GPSBearing: return "GPSBearing";
|
|
|
+ case TAG_GPSDestDistanceRef: return "GPSDestDistanceRef";
|
|
|
+ case TAG_GPSDestDistance: return "GPSDestDistance";
|
|
|
+ case TAG_GPSProcessingMethod: return "GPSProcessingMethod";
|
|
|
+ case TAG_GPSAreaInformation: return "GPSAreaInformation";
|
|
|
+ case TAG_GPSDateStamp: return "GPSDateStamp";
|
|
|
+ case TAG_GPSDifferential: return "GPSDifferential";
|
|
|
+ case TAG_GPSHPositioningError: return "GPSHPositioningError";
|
|
|
+ default: ;
|
|
|
+ }
|
|
|
+ } else if (ifdType == IFD_IO) {
|
|
|
+ switch (tagId) {
|
|
|
+ case TAG_InteroperabilityIndex: return "InteroperabilityIndex";
|
|
|
+ case TAG_InteroperabilityVersion: return "InteroperabilityVersion";
|
|
|
+ case TAG_RelatedImageFileFormat: return "RelatedImageFileFormat";
|
|
|
+ case TAG_RelatedImageWidth: return "RelatedImageWidth";
|
|
|
+ case TAG_RelatedImageHeight: return "RelatedImageHeight";
|
|
|
+ default: ;
|
|
|
+ }
|
|
|
+ } else if (ifdType == IFD_MPF) {
|
|
|
+ switch (tagId) {
|
|
|
+ case TAG_MPFVersion: return "MPFVersion";
|
|
|
+ case TAG_NumberOfImage: return "NumberOfImage";
|
|
|
+ case TAG_MPImageList: return "MPImageList";
|
|
|
+ case TAG_ImageUIDList: return "ImageUIDList";
|
|
|
+ case TAG_TotalFrames: return "TotalFrames";
|
|
|
+
|
|
|
+ case TAG_MPIndividualNum: return "MPIndividualNum";
|
|
|
+ case TAG_PanOrientation: return "PanOrientation";
|
|
|
+ case TAG_PanOverlapH: return "PanOverlapH";
|
|
|
+ case TAG_PanOverlapV: return "PanOverlapV";
|
|
|
+ case TAG_BaseViewpointNum: return "BaseViewpointNum";
|
|
|
+ case TAG_ConvergenceAngle: return "ConvergenceAngle";
|
|
|
+ case TAG_BaselineLength: return "BaseLineLength";
|
|
|
+ case TAG_VerticalDivergence: return "VerticalDivergence";
|
|
|
+ case TAG_AxisDistanceX: return "AxisDistanceX";
|
|
|
+ case TAG_AxisDistanceY: return "AxisDistanceY";
|
|
|
+ case TAG_AxisDistanceZ: return "AxisDistanceZ";
|
|
|
+ case TAG_YawAngle: return "YawAngle";
|
|
|
+ case TAG_PitchAngle: return "PitchAngle";
|
|
|
+ case TAG_RollAngle: return "RollAngle";
|
|
|
+ default:;
|
|
|
+ }
|
|
|
+ return "(Unknown)";
|
|
|
+ }
|
|
|
+ return "(Unknown)";
|
|
|
+}
|
|
|
+
|
|
|
+// create the IFD table
|
|
|
+static void *createIfdTable(EXIF_IFD_TYPE IfdType, uint16_t tagCount, unsigned int nextOfs)
|
|
|
+{
|
|
|
+ IfdTable *ifd = (IfdTable*)malloc(sizeof(IfdTable));
|
|
|
+ if (!ifd) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ memset(ifd, 0, sizeof(IfdTable));
|
|
|
+ ifd->ifdType = IfdType;
|
|
|
+ ifd->tagCount = tagCount;
|
|
|
+ ifd->nextIfdOffset = nextOfs;
|
|
|
+ return ifd;
|
|
|
+}
|
|
|
+
|
|
|
+// add the TagNode enrtry to the IFD table
|
|
|
+static void *addTagNodeToIfd(void *pIfd,
|
|
|
+ uint16_t tagId,
|
|
|
+ uint16_t type,
|
|
|
+ unsigned int count,
|
|
|
+ unsigned int *numData,
|
|
|
+ uint8_t *byteData)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ IfdTable *ifd = (IfdTable*)pIfd;
|
|
|
+ TagNode *tag;
|
|
|
+ if (!ifd) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ tag = (TagNode*)malloc(sizeof(TagNode));
|
|
|
+ memset(tag, 0, sizeof(TagNode));
|
|
|
+ tag->tagId = tagId;
|
|
|
+ tag->type = type;
|
|
|
+ tag->count = count;
|
|
|
+
|
|
|
+ if (count > 0) {
|
|
|
+ if (numData != NULL) {
|
|
|
+ int num = count;
|
|
|
+ if (type == TYPE_RATIONAL ||
|
|
|
+ type == TYPE_SRATIONAL) {
|
|
|
+ num *= 2;
|
|
|
+ }
|
|
|
+ tag->numData = (unsigned int*)malloc(sizeof(int)*num);
|
|
|
+ for (i = 0; i < num; i++) {
|
|
|
+ tag->numData[i] = numData[i];
|
|
|
+ }
|
|
|
+ } else if (byteData != NULL) {
|
|
|
+ tag->byteData = (uint8_t*)malloc(count);
|
|
|
+ memcpy(tag->byteData, byteData, count);
|
|
|
+ } else {
|
|
|
+ tag->error = 1;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ tag->error = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ // first tag
|
|
|
+ if (!ifd->tags) {
|
|
|
+ ifd->tags = tag;
|
|
|
+ } else {
|
|
|
+ TagNode *tagWk = ifd->tags;
|
|
|
+ while (tagWk->next) {
|
|
|
+ tagWk = tagWk->next;
|
|
|
+ }
|
|
|
+ tagWk->next = tag;
|
|
|
+ tag->prev = tagWk;
|
|
|
+ }
|
|
|
+
|
|
|
+ return tag;
|
|
|
+}
|
|
|
+
|
|
|
+// create a copy of TagNode
|
|
|
+static TagNode *duplicateTagNode(TagNode *src)
|
|
|
+{
|
|
|
+ TagNode *dup;
|
|
|
+ size_t len;
|
|
|
+ if (!src || src->count <= 0) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ dup = (TagNode*)malloc(sizeof(TagNode));
|
|
|
+ memset(dup, 0, sizeof(TagNode));
|
|
|
+ dup->tagId = src->tagId;
|
|
|
+ dup->type = src->type;
|
|
|
+ dup->count = src->count;
|
|
|
+ dup->error = src->error;
|
|
|
+ if (src->numData) {
|
|
|
+ len = sizeof(int) * src->count;
|
|
|
+ if (src->type == TYPE_RATIONAL ||
|
|
|
+ src->type == TYPE_SRATIONAL) {
|
|
|
+ len *= 2;
|
|
|
+ }
|
|
|
+ dup->numData = (unsigned int*)malloc(len);
|
|
|
+ memcpy(dup->numData, src->numData, len);
|
|
|
+ } else if (src->byteData) {
|
|
|
+ len = sizeof(char) * src->count;
|
|
|
+ dup->byteData = (uint8_t*)malloc(len);
|
|
|
+ memcpy(dup->byteData, src->byteData, len);
|
|
|
+ }
|
|
|
+ return dup;
|
|
|
+}
|
|
|
+
|
|
|
+// free TagNode
|
|
|
+static void freeTagNode(void *pTag)
|
|
|
+{
|
|
|
+ TagNode *tag = (TagNode*)pTag;
|
|
|
+ if (!tag) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (tag->numData) {
|
|
|
+ free(tag->numData);
|
|
|
+ }
|
|
|
+ if (tag->byteData) {
|
|
|
+ free(tag->byteData);
|
|
|
+ }
|
|
|
+ free(tag);
|
|
|
+}
|
|
|
+
|
|
|
+// free entire IFD table
|
|
|
+static void freeIfdTable(void *pIfd)
|
|
|
+{
|
|
|
+ IfdTable *ifd = (IfdTable*)pIfd;
|
|
|
+ TagNode *tag;
|
|
|
+ if (!ifd) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ tag = ifd->tags;
|
|
|
+ if (ifd->p) {
|
|
|
+ free(ifd->p);
|
|
|
+ }
|
|
|
+ free(ifd);
|
|
|
+
|
|
|
+ if (tag) {
|
|
|
+ while (tag->next) {
|
|
|
+ tag = tag->next;
|
|
|
+ }
|
|
|
+ while (tag) {
|
|
|
+ TagNode *tagWk = tag->prev;
|
|
|
+ freeTagNode(tag);
|
|
|
+ tag = tagWk;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
+// search the specified tag's node from the IFD table
|
|
|
+static TagNode *getTagNodePtrFromIfd(IfdTable *ifd, uint16_t tagId)
|
|
|
+{
|
|
|
+ TagNode *tag;
|
|
|
+ if (!ifd) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ tag = ifd->tags;
|
|
|
+ while (tag) {
|
|
|
+ if (tag->tagId == tagId) {
|
|
|
+ return tag;
|
|
|
+ }
|
|
|
+ tag = tag->next;
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+// remove the TagNode entry from the IFD table
|
|
|
+static int removeTagOnIfd(void *pIfd, uint16_t tagId)
|
|
|
+{
|
|
|
+ int num = 0;
|
|
|
+ IfdTable *ifd = (IfdTable*)pIfd;
|
|
|
+ TagNode *tag;
|
|
|
+ if (!ifd) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ for (;;) { // possibility of multiple entries
|
|
|
+ tag = getTagNodePtrFromIfd(ifd, tagId);
|
|
|
+ if (!tag) {
|
|
|
+ break; // no more found
|
|
|
+ }
|
|
|
+ num++;
|
|
|
+ if (tag->prev) {
|
|
|
+ tag->prev->next = tag->next;
|
|
|
+ } else {
|
|
|
+ ifd->tags = tag->next;
|
|
|
+ }
|
|
|
+ if (tag->next) {
|
|
|
+ tag->next->prev = tag->prev;
|
|
|
+ }
|
|
|
+ freeTagNode(tag);
|
|
|
+ ifd->tagCount--;
|
|
|
+ }
|
|
|
+ return num;
|
|
|
+}
|
|
|
+
|
|
|
+// get the IFD table address of the specified type
|
|
|
+static IfdTable *getIfdTableFromIfdTableArray(void **ifdTableArray, EXIF_IFD_TYPE ifdType)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ if (!ifdTableArray) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ for (i = 0; ifdTableArray[i] != NULL; i++) {
|
|
|
+ IfdTable *ifd = ifdTableArray[i];
|
|
|
+ if (ifd->ifdType == ifdType) {
|
|
|
+ return ifd;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+// count IFD tables
|
|
|
+static int countIfdTableOnIfdTableArray(void **ifdTableArray)
|
|
|
+{
|
|
|
+ int i, num = 0;
|
|
|
+ for (i = 0; ifdTableArray[i] != NULL; i++) {
|
|
|
+ num++;
|
|
|
+ }
|
|
|
+ return num;
|
|
|
+}
|
|
|
+
|
|
|
+// set single numeric value to the existing TagNode entry
|
|
|
+static int setSingleNumDataToTag(TagNode *tag, unsigned int value)
|
|
|
+{
|
|
|
+ if (!tag) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ if (tag->type != TYPE_BYTE &&
|
|
|
+ tag->type != TYPE_SHORT &&
|
|
|
+ tag->type != TYPE_LONG &&
|
|
|
+ tag->type != TYPE_SBYTE &&
|
|
|
+ tag->type != TYPE_SSHORT &&
|
|
|
+ tag->type != TYPE_SLONG) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ if (!tag->numData) {
|
|
|
+ tag->numData = (unsigned int*)malloc(sizeof(int));
|
|
|
+ }
|
|
|
+ tag->count = 1;
|
|
|
+ tag->numData[0] = value;
|
|
|
+ tag->error = 0;
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * write the Exif segment to the file
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] fp: the output file pointer
|
|
|
+ * [in] ifdTableArray: address of the IFD tables array
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * 0: OK
|
|
|
+ * EXIF_ERR_WRITE_FILE
|
|
|
+ */
|
|
|
+static int writeExifSegment(FILE *fp, void **ifdTableArray)
|
|
|
+{
|
|
|
+#define IFDMAX 5
|
|
|
+
|
|
|
+ union _packed {
|
|
|
+ unsigned int ui;
|
|
|
+ uint16_t us[2];
|
|
|
+ uint8_t uc[4];
|
|
|
+ };
|
|
|
+
|
|
|
+ IfdTable *ifds[IFDMAX], *ifd0th;
|
|
|
+ TagNode *tag;
|
|
|
+ IFD_TAG tagField;
|
|
|
+ uint16_t num, us;
|
|
|
+ unsigned int ui;
|
|
|
+ int zero = 0;
|
|
|
+ int i, x;
|
|
|
+ unsigned int ofs;
|
|
|
+ union _packed packed;
|
|
|
+ APP_HEADER dupApp1Header = App1Header;
|
|
|
+
|
|
|
+ ifds[0] = getIfdTableFromIfdTableArray(ifdTableArray, IFD_0TH);
|
|
|
+ ifds[1] = getIfdTableFromIfdTableArray(ifdTableArray, IFD_EXIF);
|
|
|
+ ifds[2] = getIfdTableFromIfdTableArray(ifdTableArray, IFD_IO);
|
|
|
+ ifds[3] = getIfdTableFromIfdTableArray(ifdTableArray, IFD_GPS);
|
|
|
+ ifds[4] = getIfdTableFromIfdTableArray(ifdTableArray, IFD_1ST);
|
|
|
+ ifd0th = ifds[0];
|
|
|
+
|
|
|
+ // return if 0th IFD is not exist
|
|
|
+ if (!ifd0th) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ // get total length of the segment
|
|
|
+ us = sizeof(APP_HEADER) - sizeof(short);
|
|
|
+ for (x = 0; x < IFDMAX; x++) {
|
|
|
+ if (ifds[x]) {
|
|
|
+ us = us + ifds[x]->length;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // segment length must be written in big-endian
|
|
|
+ if (systemIsLittleEndian()) {
|
|
|
+ us = swab16(us);
|
|
|
+ }
|
|
|
+ dupApp1Header.length = us;
|
|
|
+ dupApp1Header.tiff.reserved = fix_short(dupApp1Header.tiff.reserved);
|
|
|
+ dupApp1Header.tiff.Ifd0thOffset = fix_int(dupApp1Header.tiff.Ifd0thOffset);
|
|
|
+ // write Exif segment Header
|
|
|
+ if (fwrite(&dupApp1Header, 1, sizeof(APP_HEADER), fp) != sizeof(APP_HEADER)) {
|
|
|
+ return EXIF_ERR_WRITE_FILE;
|
|
|
+ }
|
|
|
+
|
|
|
+ // base offset of the Exif segment
|
|
|
+ ofs = sizeof(TIFF_HEADER);
|
|
|
+ for (x = 0; x < IFDMAX; x++) {
|
|
|
+ IfdTable *ifd = ifds[x];
|
|
|
+ if (ifd == NULL) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ // calculate the start offset to write the tag's data of this IFD
|
|
|
+ ofs += sizeof(short) + // sizeof the tag number area
|
|
|
+ sizeof(IFD_TAG) * ifd->tagCount + // sizeof the tag fields
|
|
|
+ sizeof(int); // sizeof the NextOffset area
|
|
|
+
|
|
|
+ // write actual tag number of the current IFD
|
|
|
+ num = 0;
|
|
|
+ tag = ifd->tags;
|
|
|
+ while (tag) {
|
|
|
+ if (!tag->error) {
|
|
|
+ num++;
|
|
|
+ }
|
|
|
+ tag = tag->next;
|
|
|
+ }
|
|
|
+ us = fix_short(num);
|
|
|
+ if (fwrite(&us, 1, sizeof(short), fp) != sizeof(short)) {
|
|
|
+ return EXIF_ERR_WRITE_FILE;
|
|
|
+ }
|
|
|
+
|
|
|
+ // write the each tag fields
|
|
|
+ tag = ifd->tags;
|
|
|
+ while (tag) {
|
|
|
+ if (tag->error) {
|
|
|
+ tag = tag->next; // ignore
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ tagField.tag = fix_short(tag->tagId);
|
|
|
+ tagField.type = fix_short(tag->type);
|
|
|
+ tagField.count = fix_int(tag->count);
|
|
|
+ packed.ui = 0;
|
|
|
+
|
|
|
+ switch (tag->type) {
|
|
|
+ case TYPE_ASCII:
|
|
|
+ case TYPE_UNDEFINED:
|
|
|
+ if (tag->count <= 4) {
|
|
|
+ for (i = 0; i < (int)tag->count; i++) {
|
|
|
+ packed.uc[i] = tag->byteData[i];
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ packed.ui = fix_int(ofs);
|
|
|
+ ofs += tag->count;
|
|
|
+ if (tag->count % 2 != 0) {
|
|
|
+ ofs++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case TYPE_BYTE:
|
|
|
+ case TYPE_SBYTE:
|
|
|
+ if (tag->count <= 4) {
|
|
|
+ for (i = 0; i < (int)tag->count; i++) {
|
|
|
+ packed.uc[i] = (uint8_t)tag->numData[i];
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ packed.ui = fix_int(ofs);
|
|
|
+ ofs += tag->count;
|
|
|
+ if (tag->count % 2 != 0) {
|
|
|
+ ofs++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case TYPE_SHORT:
|
|
|
+ case TYPE_SSHORT:
|
|
|
+ if (tag->count <= 2) {
|
|
|
+ for (i = 0; i < (int)tag->count; i++) {
|
|
|
+ packed.us[i] = fix_short((uint16_t)tag->numData[i]);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ packed.ui = fix_int(ofs);
|
|
|
+ ofs += tag->count * sizeof(short);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case TYPE_LONG:
|
|
|
+ case TYPE_SLONG:
|
|
|
+ if (tag->count <= 1) {
|
|
|
+ packed.ui = fix_int((unsigned int)tag->numData[0]);
|
|
|
+ } else {
|
|
|
+ packed.ui = fix_int(ofs);
|
|
|
+ ofs += tag->count * sizeof(short);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case TYPE_RATIONAL:
|
|
|
+ case TYPE_SRATIONAL:
|
|
|
+ packed.ui = fix_int(ofs);
|
|
|
+ ofs += tag->count * sizeof(int) * 2;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ tagField.offset = packed.ui;
|
|
|
+ if (fwrite(&tagField, 1, sizeof(tagField), fp) != sizeof(tagField)) {
|
|
|
+ return EXIF_ERR_WRITE_FILE;
|
|
|
+ }
|
|
|
+ tag = tag->next;
|
|
|
+ }
|
|
|
+ ui = fix_int(ifd->nextIfdOffset);
|
|
|
+ if (fwrite(&ui, 1, sizeof(int), fp) != sizeof(int)) {
|
|
|
+ return EXIF_ERR_WRITE_FILE;
|
|
|
+ }
|
|
|
+
|
|
|
+ // write the tag values over 4 bytes
|
|
|
+ tag = ifd->tags;
|
|
|
+ while (tag) {
|
|
|
+ if (tag->error) {
|
|
|
+ tag = tag->next;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ switch (tag->type) {
|
|
|
+ case TYPE_ASCII:
|
|
|
+ case TYPE_UNDEFINED:
|
|
|
+ if (tag->count > 4) {
|
|
|
+ if (fwrite(tag->byteData, 1, tag->count, fp) != tag->count) {
|
|
|
+ return EXIF_ERR_WRITE_FILE;
|
|
|
+ }
|
|
|
+ if (tag->count % 2 != 0) { // for even boundary
|
|
|
+ if (fwrite(&zero, 1, sizeof(char), fp) != sizeof(char)) {
|
|
|
+ return EXIF_ERR_WRITE_FILE;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case TYPE_BYTE:
|
|
|
+ case TYPE_SBYTE:
|
|
|
+ if (tag->count > 4) {
|
|
|
+ for (i = 0; i < (int)tag->count; i++) {
|
|
|
+ uint8_t n = (uint8_t)tag->numData[i];
|
|
|
+ if (fwrite(&n, 1, sizeof(char), fp) != sizeof(char)) {
|
|
|
+ return EXIF_ERR_WRITE_FILE;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ if (tag->count % 2 != 0) {
|
|
|
+ if (fwrite(&zero, 1, sizeof(char), fp) != sizeof(char)) {
|
|
|
+ return EXIF_ERR_WRITE_FILE;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case TYPE_SHORT:
|
|
|
+ case TYPE_SSHORT:
|
|
|
+ if (tag->count > 2) {
|
|
|
+ for (i = 0; i < (int)tag->count; i++) {
|
|
|
+ uint16_t n = fix_short((uint16_t)tag->numData[i]);
|
|
|
+ if (fwrite(&n, 1, sizeof(short), fp) != sizeof(short)) {
|
|
|
+ return EXIF_ERR_WRITE_FILE;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case TYPE_LONG:
|
|
|
+ case TYPE_SLONG:
|
|
|
+ if (tag->count > 1) {
|
|
|
+ for (i = 0; i < (int)tag->count; i++) {
|
|
|
+ unsigned int n = fix_int((unsigned int)tag->numData[i]);
|
|
|
+ if (fwrite(&n, 1, sizeof(int), fp) != sizeof(int)) {
|
|
|
+ return EXIF_ERR_WRITE_FILE;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case TYPE_RATIONAL:
|
|
|
+ case TYPE_SRATIONAL:
|
|
|
+ for (i = 0; i < (int)tag->count*2; i++) {
|
|
|
+ unsigned int n = fix_int((unsigned int)tag->numData[i]);
|
|
|
+ if (fwrite(&n, 1, sizeof(int), fp) != sizeof(int)) {
|
|
|
+ return EXIF_ERR_WRITE_FILE;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ tag = tag->next;
|
|
|
+ }
|
|
|
+ // write the thumbnail data in the 1st IFD
|
|
|
+ if (ifd->ifdType == IFD_1ST && ifd->p != NULL) {
|
|
|
+ tag = getTagNodePtrFromIfd(ifd, TAG_JPEGInterchangeFormatLength);
|
|
|
+ if (tag) {
|
|
|
+ if (tag->numData[0] > 0) {
|
|
|
+ if (fwrite(ifd->p, 1, tag->numData[0], fp) != tag->numData[0]) {
|
|
|
+ return EXIF_ERR_WRITE_FILE;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+// calculate the actual length of the IFD
|
|
|
+static uint16_t calcIfdSize(void *pIfd)
|
|
|
+{
|
|
|
+ unsigned int size, num = 0;
|
|
|
+ TagNode *tag;
|
|
|
+ IfdTable *ifd = (IfdTable*)pIfd;
|
|
|
+ if (!ifd) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ // count the actual tag number
|
|
|
+ tag = ifd->tags;
|
|
|
+ while (tag) {
|
|
|
+ if (!tag->error) {
|
|
|
+ num++;
|
|
|
+ }
|
|
|
+ tag = tag->next;
|
|
|
+ }
|
|
|
+
|
|
|
+ size = sizeof(short) + // sizeof the tag number area
|
|
|
+ sizeof(IFD_TAG) * num + // sizeof tag fields
|
|
|
+ sizeof(int); // sizeof the NextOffset area
|
|
|
+
|
|
|
+ // add thumbnail data length
|
|
|
+ if (ifd->ifdType == IFD_1ST) {
|
|
|
+ if (ifd->p) {
|
|
|
+ tag = getTagNodePtrFromIfd(ifd, TAG_JPEGInterchangeFormatLength);
|
|
|
+ if (tag) {
|
|
|
+ size += tag->numData[0];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ tag = ifd->tags;
|
|
|
+ while (tag) {
|
|
|
+ if (tag->error) {
|
|
|
+ // ignore
|
|
|
+ tag = tag->next;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ switch (tag->type) {
|
|
|
+ case TYPE_ASCII:
|
|
|
+ case TYPE_UNDEFINED:
|
|
|
+ case TYPE_BYTE:
|
|
|
+ case TYPE_SBYTE:
|
|
|
+ if (tag->count > 4) {
|
|
|
+ size += tag->count;
|
|
|
+ if (tag->count % 2 != 0) {
|
|
|
+ // padding for even byte boundary
|
|
|
+ size += 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case TYPE_SHORT:
|
|
|
+ case TYPE_SSHORT:
|
|
|
+ if (tag->count > 2) {
|
|
|
+ size += tag->count * sizeof(short);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case TYPE_LONG:
|
|
|
+ case TYPE_SLONG:
|
|
|
+ if (tag->count > 1) {
|
|
|
+ size += tag->count * sizeof(int);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case TYPE_RATIONAL:
|
|
|
+ case TYPE_SRATIONAL:
|
|
|
+ if (tag->count > 0) {
|
|
|
+ size += tag->count * sizeof(int) * 2;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ tag = tag->next;
|
|
|
+ }
|
|
|
+ return (uint16_t)size;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * refresh the length and offset variables in the IFD tables
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in/out] ifdTableArray: address of the IFD tables array
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * 0: OK
|
|
|
+ * EXIF_ERR_INVALID_POINTER
|
|
|
+ * ERROR_UNKNOWN
|
|
|
+ */
|
|
|
+static int fixLengthAndOffsetInIfdTables(void **ifdTableArray)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ TagNode *tag, *tagwk;
|
|
|
+ uint16_t num;
|
|
|
+ uint16_t ofsBase = sizeof(TIFF_HEADER);
|
|
|
+ unsigned int len, dummy = 0, again = 0;
|
|
|
+ IfdTable *ifd0th, *ifdExif, *ifdIo, *ifdGps, *ifd1st;
|
|
|
+ if (!ifdTableArray) {
|
|
|
+ return EXIF_ERR_INVALID_POINTER;
|
|
|
+ }
|
|
|
+
|
|
|
+AGAIN:
|
|
|
+ // calculate the length of the each IFD tables.
|
|
|
+ for (i = 0; ifdTableArray[i] != NULL; i++) {
|
|
|
+ IfdTable *ifd = ifdTableArray[i];
|
|
|
+ // count the actual tag number
|
|
|
+ tag = ifd->tags;
|
|
|
+ num = 0;
|
|
|
+ while (tag) {
|
|
|
+ // ignore and dispose the error tag
|
|
|
+ if (tag->error) {
|
|
|
+ tagwk = tag->next;
|
|
|
+ if (tag->prev) {
|
|
|
+ tag->prev->next = tag->next;
|
|
|
+ } else {
|
|
|
+ ifd->tags = tag->next;
|
|
|
+ }
|
|
|
+ if (tag->next) {
|
|
|
+ tag->next->prev = tag->prev;
|
|
|
+ }
|
|
|
+ freeTagNode(tag);
|
|
|
+ tag = tagwk;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ num++;
|
|
|
+ tag = tag->next;
|
|
|
+ }
|
|
|
+ ifd->tagCount = num;
|
|
|
+ ifd->length = calcIfdSize(ifd);
|
|
|
+ ifd->nextIfdOffset = 0;
|
|
|
+ }
|
|
|
+ ifd0th = getIfdTableFromIfdTableArray(ifdTableArray, IFD_0TH);
|
|
|
+ ifdExif = getIfdTableFromIfdTableArray(ifdTableArray, IFD_EXIF);
|
|
|
+ ifdIo = getIfdTableFromIfdTableArray(ifdTableArray, IFD_IO);
|
|
|
+ ifdGps = getIfdTableFromIfdTableArray(ifdTableArray, IFD_GPS);
|
|
|
+ ifd1st = getIfdTableFromIfdTableArray(ifdTableArray, IFD_1ST);
|
|
|
+
|
|
|
+ if (!ifd0th) {
|
|
|
+ return 0; // not error
|
|
|
+ }
|
|
|
+ ifd0th->offset = ofsBase;
|
|
|
+
|
|
|
+ // set "NextOffset" variable in the 0th IFD if the 1st IFD is exist
|
|
|
+ if (ifd1st) {
|
|
|
+ ifd0th->nextIfdOffset =
|
|
|
+ ofsBase +
|
|
|
+ ifd0th->length +
|
|
|
+ ((ifdExif)? ifdExif->length : 0) +
|
|
|
+ ((ifdIo)? ifdIo->length : 0) +
|
|
|
+ ((ifdGps)? ifdGps->length : 0);
|
|
|
+ ifd1st->offset = (uint16_t)ifd0th->nextIfdOffset;
|
|
|
+
|
|
|
+ // set the offset value of the thumbnail data
|
|
|
+ if (ifd1st->p) {
|
|
|
+ tag = getTagNodePtrFromIfd(ifd1st, TAG_JPEGInterchangeFormatLength);
|
|
|
+ if (tag) {
|
|
|
+ len = tag->numData[0]; // thumbnail length;
|
|
|
+ tag = getTagNodePtrFromIfd(ifd1st, TAG_JPEGInterchangeFormat);
|
|
|
+ if (tag) {
|
|
|
+ // set the offset value
|
|
|
+ setSingleNumDataToTag(tag, ifd1st->offset + ifd1st->length - len);
|
|
|
+ } else {
|
|
|
+ // create the JPEGInterchangeFormat tag if not exist
|
|
|
+ if (!addTagNodeToIfd(ifd1st, TAG_JPEGInterchangeFormat,
|
|
|
+ TYPE_LONG, 1, &dummy, NULL)) {
|
|
|
+ return EXIF_ERR_UNKNOWN;
|
|
|
+ }
|
|
|
+ again = 1;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ tag = getTagNodePtrFromIfd(ifd1st, TAG_JPEGInterchangeFormat);
|
|
|
+ if (tag) {
|
|
|
+ setSingleNumDataToTag(tag, 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ ifd0th->nextIfdOffset = 0; // 1st IFD is not exist
|
|
|
+ }
|
|
|
+
|
|
|
+ // set "ExifIFDPointer" tag value in the 0th IFD if the Exif IFD is exist
|
|
|
+ if (ifdExif) {
|
|
|
+ tag = getTagNodePtrFromIfd(ifd0th, TAG_ExifIFDPointer);
|
|
|
+ if (tag) {
|
|
|
+ setSingleNumDataToTag(tag, ofsBase + ifd0th->length);
|
|
|
+ ifdExif->offset = (uint16_t)tag->numData[0];
|
|
|
+ } else {
|
|
|
+ // create the tag if not exist
|
|
|
+ if (!addTagNodeToIfd(ifd0th, TAG_ExifIFDPointer, TYPE_LONG, 1,
|
|
|
+ &dummy, NULL)) {
|
|
|
+ return EXIF_ERR_UNKNOWN;
|
|
|
+ }
|
|
|
+ again = 1;
|
|
|
+ }
|
|
|
+ // set "InteroperabilityIFDPointer" tag value in the Exif IFD
|
|
|
+ // if the Interoperability IFD is exist
|
|
|
+ if (ifdIo) {
|
|
|
+ tag = getTagNodePtrFromIfd(ifdExif, TAG_InteroperabilityIFDPointer);
|
|
|
+ if (tag) {
|
|
|
+ setSingleNumDataToTag(tag, ofsBase + ifd0th->length + ifdExif->length);
|
|
|
+ ifdIo->offset = (uint16_t)tag->numData[0];
|
|
|
+ } else {
|
|
|
+ // create the tag if not exist
|
|
|
+ if (!addTagNodeToIfd(ifdExif, TAG_InteroperabilityIFDPointer,
|
|
|
+ TYPE_LONG, 1, &dummy, NULL)) {
|
|
|
+ return EXIF_ERR_UNKNOWN;
|
|
|
+ }
|
|
|
+ again = 1;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ tag = getTagNodePtrFromIfd(ifdExif, TAG_InteroperabilityIFDPointer);
|
|
|
+ if (tag) {
|
|
|
+ setSingleNumDataToTag(tag, 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else { // Exif
|
|
|
+ tag = getTagNodePtrFromIfd(ifd0th, TAG_ExifIFDPointer);
|
|
|
+ if (tag) {
|
|
|
+ setSingleNumDataToTag(tag, 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // set "GPSInfoIFDPointer" tag value in the 0th IFD if the GPS IFD is exist
|
|
|
+ if (ifdGps) {
|
|
|
+ tag = getTagNodePtrFromIfd(ifd0th, TAG_GPSInfoIFDPointer);
|
|
|
+ if (tag) {
|
|
|
+ setSingleNumDataToTag(tag, ofsBase +
|
|
|
+ ifd0th->length +
|
|
|
+ ((ifdExif)? ifdExif->length : 0) +
|
|
|
+ ((ifdIo)? ifdIo->length : 0));
|
|
|
+ ifdGps->offset = (uint16_t)tag->numData[0];
|
|
|
+ } else {
|
|
|
+ // create the tag if not exist
|
|
|
+ if (!addTagNodeToIfd(ifd0th, TAG_GPSInfoIFDPointer, TYPE_LONG,
|
|
|
+ 1, &dummy, NULL)) {
|
|
|
+ return EXIF_ERR_UNKNOWN;
|
|
|
+ }
|
|
|
+ again = 1;
|
|
|
+ }
|
|
|
+ } else { // GPS IFD is not exist
|
|
|
+ tag = getTagNodePtrFromIfd(ifd0th, TAG_GPSInfoIFDPointer);
|
|
|
+ if (tag) {
|
|
|
+ setSingleNumDataToTag(tag, 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // repeat again if needed
|
|
|
+ if (again) {
|
|
|
+ again = 0;
|
|
|
+ goto AGAIN;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Set the data of the IFD to the internal table
|
|
|
+ *
|
|
|
+ * parameters
|
|
|
+ * [in] fp: file pointer of opened file
|
|
|
+ * [in] startOffset : offset of target IFD
|
|
|
+ * [in] ifdType : type of the IFD
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * NULL: critical error occurred
|
|
|
+ * !NULL: the address of the IFD table
|
|
|
+ */
|
|
|
+static void *parseIFD(FILE *fp,
|
|
|
+ unsigned int baseOffset,
|
|
|
+ unsigned int startOffset,
|
|
|
+ EXIF_IFD_TYPE ifdType)
|
|
|
+{
|
|
|
+ void *ifd;
|
|
|
+ uint8_t buf[8192];
|
|
|
+ uint16_t tagCount, us;
|
|
|
+ unsigned int nextOffset = 0;
|
|
|
+ unsigned int *array, val, allocSize;
|
|
|
+ int size, cnt, i;
|
|
|
+ size_t len;
|
|
|
+ int pos;
|
|
|
+
|
|
|
+ // get the count of the tags
|
|
|
+ if (seekToRelativeOffset(fp, baseOffset, startOffset) != 0 ||
|
|
|
+ fread(&tagCount, 1, sizeof(short), fp) < sizeof(short)) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ tagCount = fix_short(tagCount);
|
|
|
+ pos = ftell(fp);
|
|
|
+
|
|
|
+ // in case of the 0th IFD, check the offset of the 1st IFD
|
|
|
+ if (ifdType == IFD_0TH || ifdType == IFD_MPF) {
|
|
|
+ // next IFD's offset is at the tail of the segment
|
|
|
+ if (seekToRelativeOffset(fp, baseOffset,
|
|
|
+ sizeof(TIFF_HEADER) + sizeof(short) + sizeof(IFD_TAG) * tagCount) != 0 ||
|
|
|
+ fread(&nextOffset, 1, sizeof(int), fp) < sizeof(int)) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ nextOffset = fix_int(nextOffset);
|
|
|
+ fseek(fp, pos, SEEK_SET);
|
|
|
+ }
|
|
|
+ // create new IFD table
|
|
|
+ ifd = createIfdTable(ifdType, tagCount, nextOffset);
|
|
|
+
|
|
|
+ // parse all tags
|
|
|
+ for (cnt = 0; cnt < tagCount; cnt++) {
|
|
|
+ IFD_TAG tag;
|
|
|
+ uint8_t data[4];
|
|
|
+ if (fseek(fp, pos, SEEK_SET) != 0 ||
|
|
|
+ fread(&tag, 1, sizeof(tag), fp) < sizeof(tag)) {
|
|
|
+ goto ERR;
|
|
|
+ }
|
|
|
+ memcpy(data, &tag.offset, 4); // keep raw data temporary
|
|
|
+ tag.tag = fix_short(tag.tag);
|
|
|
+ tag.type = fix_short(tag.type);
|
|
|
+ tag.count = fix_int(tag.count);
|
|
|
+ tag.offset = fix_int(tag.offset);
|
|
|
+ pos = ftell(fp);
|
|
|
+
|
|
|
+ //printf("tag=0x%04X type=%u count=%u offset=%u name=[%s]\n",
|
|
|
+ // tag.tag, tag.type, tag.count, tag.offset, getTagName(ifdType, tag.tag));
|
|
|
+
|
|
|
+ if (tag.type == TYPE_ASCII || // ascii = the null-terminated string
|
|
|
+ tag.type == TYPE_UNDEFINED) { // undefined = the chunk data bytes
|
|
|
+ if (tag.count <= 4) {
|
|
|
+ // 4 bytes or less data is placed in the 'offset' area directly
|
|
|
+ addTagNodeToIfd(ifd, tag.tag, tag.type, tag.count, NULL, data);
|
|
|
+ } else {
|
|
|
+ // 5 bytes or more data is placed in the value area of the IFD
|
|
|
+ uint8_t *p = buf;
|
|
|
+ if (tag.count > sizeof(buf)) {
|
|
|
+ // allocate new buffer if needed
|
|
|
+ if (tag.count >= App1Header.length) { // illegal
|
|
|
+ p = NULL;
|
|
|
+ } else {
|
|
|
+ p = (uint8_t*)malloc(tag.count);
|
|
|
+ }
|
|
|
+ if (!p) {
|
|
|
+ // treat as an error
|
|
|
+ addTagNodeToIfd(ifd, tag.tag, tag.type, tag.count, NULL, NULL);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ memset(p, 0, tag.count);
|
|
|
+ }
|
|
|
+ if (seekToRelativeOffset(fp, baseOffset, tag.offset) != 0 ||
|
|
|
+ fread(p, 1, tag.count, fp) < tag.count) {
|
|
|
+ if (p != &buf[0]) {
|
|
|
+ free(p);
|
|
|
+ }
|
|
|
+ addTagNodeToIfd(ifd, tag.tag, tag.type, tag.count, NULL, NULL);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ addTagNodeToIfd(ifd, tag.tag, tag.type, tag.count, NULL, p);
|
|
|
+ if (p != &buf[0]) {
|
|
|
+ free(p);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (tag.type == TYPE_RATIONAL || tag.type == TYPE_SRATIONAL) {
|
|
|
+ unsigned int realCount = tag.count * 2; // need double the space
|
|
|
+ size_t len = realCount * sizeof(int);
|
|
|
+ if (len >= App1Header.length) { // illegal
|
|
|
+ array = NULL;
|
|
|
+ } else {
|
|
|
+ array = (unsigned int*)malloc(len);
|
|
|
+ if (array) {
|
|
|
+ if (seekToRelativeOffset(fp, baseOffset, tag.offset) != 0 ||
|
|
|
+ fread(array, 1, len , fp) < len) {
|
|
|
+ free(array);
|
|
|
+ array = NULL;
|
|
|
+ } else {
|
|
|
+ for (i = 0; i < (int)realCount; i++) {
|
|
|
+ array[i] = fix_int(array[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ addTagNodeToIfd(ifd, tag.tag, tag.type, tag.count, array, NULL);
|
|
|
+ if (array) {
|
|
|
+ free(array);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (tag.type == TYPE_BYTE ||
|
|
|
+ tag.type == TYPE_SHORT ||
|
|
|
+ tag.type == TYPE_LONG ||
|
|
|
+ tag.type == TYPE_SBYTE ||
|
|
|
+ tag.type == TYPE_SSHORT ||
|
|
|
+ tag.type == TYPE_SLONG ) {
|
|
|
+
|
|
|
+ // the single value is always stored in tag.offset area directly
|
|
|
+ // # the data is Left-justified if less than 4 bytes
|
|
|
+ if (tag.count <= 1) {
|
|
|
+ val = tag.offset;
|
|
|
+ if (tag.type == TYPE_BYTE || tag.type == TYPE_SBYTE) {
|
|
|
+ uint8_t uc = data[0];
|
|
|
+ val = uc;
|
|
|
+ } else if (tag.type == TYPE_SHORT || tag.type == TYPE_SSHORT) {
|
|
|
+ memcpy(&us, data, sizeof(short));
|
|
|
+ us = fix_short(us);
|
|
|
+ val = us;
|
|
|
+ }
|
|
|
+ addTagNodeToIfd(ifd, tag.tag, tag.type, tag.count, &val, NULL);
|
|
|
+ }
|
|
|
+ // multiple value
|
|
|
+ else {
|
|
|
+ size = sizeof(int);
|
|
|
+ if (tag.type == TYPE_BYTE || tag.type == TYPE_SBYTE) {
|
|
|
+ size = sizeof(char);
|
|
|
+ } else if (tag.type == TYPE_SHORT || tag.type == TYPE_SSHORT) {
|
|
|
+ size = sizeof(short);
|
|
|
+ }
|
|
|
+ // for the sake of simplicity, using the 4bytes area for
|
|
|
+ // each numeric data type
|
|
|
+ allocSize = sizeof(int) * tag.count;
|
|
|
+ if (allocSize >= App1Header.length) { // illegal
|
|
|
+ array = NULL;
|
|
|
+ } else {
|
|
|
+ array = (unsigned int*)malloc(allocSize);
|
|
|
+ }
|
|
|
+ if (!array) {
|
|
|
+ addTagNodeToIfd(ifd, tag.tag, tag.type, tag.count, NULL, NULL);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ len = size * tag.count;
|
|
|
+ // if the total length of the value is less than or equal to 4bytes,
|
|
|
+ // they have been stored in the tag.offset area
|
|
|
+ if (len <= 4) {
|
|
|
+ if (size == 1) { // byte
|
|
|
+ for (i = 0; i < (int)tag.count; i++) {
|
|
|
+ array[i] = (unsigned int)data[i];
|
|
|
+ }
|
|
|
+ } else if (size == 2) { // short
|
|
|
+ for (i = 0; i < 2; i++) {
|
|
|
+ memcpy(&us, &data[i*2], sizeof(short));
|
|
|
+ us = fix_short(us);
|
|
|
+ array[i] = (unsigned int)us;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (seekToRelativeOffset(fp, baseOffset, tag.offset) != 0 ||
|
|
|
+ fread(buf, 1, len , fp) < len) {
|
|
|
+ addTagNodeToIfd(ifd, tag.tag, tag.type, tag.count, NULL, NULL);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ for (i = 0; i < (int)tag.count; i++) {
|
|
|
+ memcpy(&val, &buf[i*size], size);
|
|
|
+ if (size == sizeof(int)) {
|
|
|
+ val = fix_int(val);
|
|
|
+ } else if (size == sizeof(short)) {
|
|
|
+ val = fix_short((uint16_t)val);
|
|
|
+ }
|
|
|
+ array[i] = (unsigned int)val;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ addTagNodeToIfd(ifd, tag.tag, tag.type, tag.count, array, NULL);
|
|
|
+ free(array);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (ifdType == IFD_1ST) {
|
|
|
+ // get thumbnail data
|
|
|
+ unsigned int thumbnail_ofs = 0, thumbnail_len;
|
|
|
+ IfdTable *ifdTable = (IfdTable*)ifd;
|
|
|
+ TagNode *tag = getTagNodePtrFromIfd(ifd, TAG_JPEGInterchangeFormat);
|
|
|
+ if (tag) {
|
|
|
+ thumbnail_ofs = tag->numData[0];
|
|
|
+ }
|
|
|
+ if (thumbnail_ofs > 0) {
|
|
|
+ tag = getTagNodePtrFromIfd(ifd, TAG_JPEGInterchangeFormatLength);
|
|
|
+ if (tag) {
|
|
|
+ thumbnail_len = tag->numData[0];
|
|
|
+ if (thumbnail_len > 0) {
|
|
|
+ ifdTable->p = (uint8_t*)malloc(thumbnail_len);
|
|
|
+ if (ifdTable->p) {
|
|
|
+ if (seekToRelativeOffset(fp, baseOffset, thumbnail_ofs) == 0) {
|
|
|
+ if (fread(ifdTable->p, 1, thumbnail_len, fp)
|
|
|
+ != thumbnail_len) {
|
|
|
+ free(ifdTable->p);
|
|
|
+ ifdTable->p = NULL;
|
|
|
+ } else {
|
|
|
+ // for test
|
|
|
+ //FILE *fpw = fopen("thumbnail.jpg", "wb");
|
|
|
+ //fwrite(ifdTable->p, 1, thumbnail_len, fpw);
|
|
|
+ //fclose(fpw);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ free(ifdTable->p);
|
|
|
+ ifdTable->p = NULL;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return ifd;
|
|
|
+ERR:
|
|
|
+ if (ifd) {
|
|
|
+ freeIfdTable(ifd);
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+void setDefaultAppNSegmentHeader(APP_HEADER* appHeader, const char* strId, uint16_t marker)
|
|
|
+{
|
|
|
+ memset(&App1Header, 0, sizeof(APP_HEADER));
|
|
|
+ appHeader->marker = systemIsLittleEndian() ? swab16(marker) : marker;
|
|
|
+ appHeader->length = 0;
|
|
|
+ strncpy(appHeader->id, strId, sizeof(appHeader->id));
|
|
|
+ appHeader->tiff.byteOrder = 0x4949; // means little-endian
|
|
|
+ appHeader->tiff.reserved = 0x002A;
|
|
|
+ appHeader->tiff.Ifd0thOffset = 0x00000008;
|
|
|
+}
|
|
|
+
|
|
|
+void setDefaultMPFSegmentHeader(MPF_HEADER* appHeader, const char* strId, uint16_t marker)
|
|
|
+{
|
|
|
+ memset(&App1Header, 0, sizeof(APP_HEADER));
|
|
|
+ appHeader->marker = systemIsLittleEndian() ? swab16(marker) : marker;
|
|
|
+ appHeader->length = 0;
|
|
|
+ strncpy(appHeader->id, strId, sizeof(appHeader->id));
|
|
|
+ appHeader->tiff.byteOrder = 0x4949; // means little-endian
|
|
|
+ appHeader->tiff.reserved = 0x002A;
|
|
|
+ appHeader->tiff.Ifd0thOffset = 0x00000008;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Load the APP1 segment header
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * 1: success
|
|
|
+ * 0: error
|
|
|
+ */
|
|
|
+static int readAppNSegmentHeader(FILE *fp, APP_HEADER* appHeader, size_t startOffset)
|
|
|
+{
|
|
|
+ // read the APP1 header
|
|
|
+ if (fseek(fp, startOffset, SEEK_SET) != 0 ||
|
|
|
+ fread(appHeader, 1, sizeof(APP_HEADER), fp) <
|
|
|
+ sizeof(APP_HEADER)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ if (systemIsLittleEndian()) {
|
|
|
+ // the segment length value is always in big-endian order
|
|
|
+ appHeader->length = swab16(appHeader->length);
|
|
|
+ }
|
|
|
+ // byte-order identifier
|
|
|
+ if (appHeader->tiff.byteOrder != 0x4D4D && // big-endian
|
|
|
+ appHeader->tiff.byteOrder != 0x4949) { // little-endian
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ // TIFF version number (always 0x002A)
|
|
|
+ appHeader->tiff.reserved = fix_short(appHeader->tiff.reserved);
|
|
|
+ if (appHeader->tiff.reserved != 0x002A) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ // offset of the 0TH IFD
|
|
|
+ appHeader->tiff.Ifd0thOffset = fix_int(appHeader->tiff.Ifd0thOffset);
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+* Load the MPF segment header
|
|
|
+*
|
|
|
+* return
|
|
|
+* 1: success
|
|
|
+* 0: error
|
|
|
+*/
|
|
|
+static int readMPFSegmentHeader(FILE *fp, MPF_HEADER* appHeader, size_t startOffset)
|
|
|
+{
|
|
|
+ // read the MPF header
|
|
|
+ if (fseek(fp, startOffset, SEEK_SET) != 0 ||
|
|
|
+ fread(appHeader, 1, sizeof(MPF_HEADER), fp) <
|
|
|
+ sizeof(MPF_HEADER)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ if (systemIsLittleEndian()) {
|
|
|
+ // the segment length value is always in big-endian order
|
|
|
+ appHeader->length = swab16(appHeader->length);
|
|
|
+ }
|
|
|
+ // byte-order identifier
|
|
|
+ if (appHeader->tiff.byteOrder != 0x4D4D && // big-endian
|
|
|
+ appHeader->tiff.byteOrder != 0x4949) { // little-endian
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ // TIFF version number (always 0x002A)
|
|
|
+ appHeader->tiff.reserved = fix_short(appHeader->tiff.reserved);
|
|
|
+ if (appHeader->tiff.reserved != 0x002A) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ // offset of the 0TH IFD
|
|
|
+ appHeader->tiff.Ifd0thOffset = fix_int(appHeader->tiff.Ifd0thOffset);
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+/**
|
|
|
+ * Get the offset of the Exif segment in the current opened JPEG file
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * n: the offset from the beginning of the file
|
|
|
+ * 0: the Exif segment is not found
|
|
|
+ * -n: error
|
|
|
+ */
|
|
|
+#define EXIF_ID_STR "Exif\0"
|
|
|
+#define EXIF_ID_STR_LEN 5
|
|
|
+#define FPXR_ID_STR "FPXR\0"
|
|
|
+#define FPXR_ID_STR_LEN 5
|
|
|
+#define MPF_ID_STR "MPF\0"
|
|
|
+#define MPF_ID_STR_LEN 4
|
|
|
+
|
|
|
+static int getAppNStartOffset(FILE *fp,
|
|
|
+ uint16_t appMarkerN,
|
|
|
+ const char *App1IDString,
|
|
|
+ size_t App1IDStringLength,
|
|
|
+ int *pDQTOffset)
|
|
|
+{
|
|
|
+ int pos;
|
|
|
+ uint8_t buf[64];
|
|
|
+ uint16_t len, marker;
|
|
|
+ uint32_t appn_pos = 0;
|
|
|
+ if (!fp) {
|
|
|
+ return EXIF_ERR_READ_FILE;
|
|
|
+ }
|
|
|
+ rewind(fp);
|
|
|
+
|
|
|
+ // check JPEG SOI Marker (0xFFD8)
|
|
|
+ if (fread(&marker, 1, sizeof(short), fp) < sizeof(short)) {
|
|
|
+ return EXIF_ERR_READ_FILE;
|
|
|
+ }
|
|
|
+ if (systemIsLittleEndian()) {
|
|
|
+ marker = swab16(marker);
|
|
|
+ }
|
|
|
+ if (marker != 0xFFD8) {
|
|
|
+ return EXIF_ERR_INVALID_JPEG;
|
|
|
+ }
|
|
|
+ // check for next 2 bytes
|
|
|
+ if (fread(&marker, 1, sizeof(short), fp) < sizeof(short)) {
|
|
|
+ return EXIF_ERR_READ_FILE;
|
|
|
+ }
|
|
|
+ if (systemIsLittleEndian()) {
|
|
|
+ marker = swab16(marker);
|
|
|
+ }
|
|
|
+ // if DQT marker (0xFFDB) is appeared, the application segment
|
|
|
+ // doesn't exist
|
|
|
+ if (marker == 0xFFDB) {
|
|
|
+ if (pDQTOffset != NULL) {
|
|
|
+ *pDQTOffset = ftell(fp) - sizeof(short);
|
|
|
+ }
|
|
|
+ return 0; // not found the Exif segment
|
|
|
+ }
|
|
|
+
|
|
|
+ pos = ftell(fp);
|
|
|
+ for (;;) {
|
|
|
+ // unexpected value. is not a APP[0-14] marker
|
|
|
+ if (!(marker >= 0xFFE0 && marker <= 0xFFEF)) {
|
|
|
+ // found DQT
|
|
|
+ if (marker == 0xFFDB && pDQTOffset != NULL) {
|
|
|
+ *pDQTOffset = pos - sizeof(short);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // read the length of the segment
|
|
|
+ if (fread(&len, 1, sizeof(short), fp) < sizeof(short)) {
|
|
|
+ return EXIF_ERR_READ_FILE;
|
|
|
+ }
|
|
|
+ if (systemIsLittleEndian()) {
|
|
|
+ len = swab16(len);
|
|
|
+ }
|
|
|
+ // if is not a APPn segment, move to next segment
|
|
|
+ if (marker != appMarkerN) {
|
|
|
+ if (appn_pos != 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (fseek(fp, len - sizeof(short), SEEK_CUR) != 0) {
|
|
|
+ return EXIF_ERR_INVALID_JPEG;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // check if it is the Exif segment
|
|
|
+ size_t bytesread = fread(buf, 1, App1IDStringLength + 4, fp);
|
|
|
+ if (bytesread < App1IDStringLength) {
|
|
|
+ return EXIF_ERR_READ_FILE;
|
|
|
+ }
|
|
|
+ if (memcmp(buf, App1IDString, App1IDStringLength) == 0) {
|
|
|
+ // return the start offset of the Exif segment
|
|
|
+ if (appn_pos == 0) {
|
|
|
+ appn_pos = pos - sizeof(short);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (Verbose) {
|
|
|
+ unsigned char c1 = buf[0];
|
|
|
+ unsigned char c2 = buf[1];
|
|
|
+ unsigned char c3 = buf[2];
|
|
|
+ unsigned char c4 = buf[3];
|
|
|
+ if (c4 < ' ') {
|
|
|
+ c4 = '?';
|
|
|
+ }
|
|
|
+ printf("APP%u %c%c%c%c len=%u\n", appMarkerN - APP0_MARKER, c1, c2, c3, c4, len - 2);
|
|
|
+ }
|
|
|
+ // if is not a Exif segment, move to next segment
|
|
|
+ if (fseek(fp, pos, SEEK_SET) != 0 ||
|
|
|
+ fseek(fp, len, SEEK_CUR) != 0) {
|
|
|
+ return EXIF_ERR_INVALID_JPEG;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // read next marker
|
|
|
+ if (fread(&marker, 1, sizeof(short), fp) < sizeof(short)) {
|
|
|
+ return EXIF_ERR_READ_FILE;
|
|
|
+ }
|
|
|
+ if (systemIsLittleEndian()) {
|
|
|
+ marker = swab16(marker);
|
|
|
+ }
|
|
|
+ pos = ftell(fp);
|
|
|
+ }
|
|
|
+ return appn_pos; // return Exif segment if found
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Initialize
|
|
|
+ *
|
|
|
+ * return
|
|
|
+ * 1: OK
|
|
|
+ * 0: the Exif segment is not found
|
|
|
+ * -n: error
|
|
|
+ */
|
|
|
+static int init(FILE *fp)
|
|
|
+{
|
|
|
+ int sts, dqtOffset = -1;;
|
|
|
+ setDefaultAppNSegmentHeader(&App1Header, "Exif", 0xFFE1);
|
|
|
+ setDefaultAppNSegmentHeader(&App2Header, "FPXR", 0xFFE2);
|
|
|
+ setDefaultMPFSegmentHeader(&MPFHeader, "MPF", 0xFFE2);
|
|
|
+ // get the offset of the Exif segment
|
|
|
+ sts = getAppNStartOffset(fp, APP1_MARKER, EXIF_ID_STR, EXIF_ID_STR_LEN, &dqtOffset);
|
|
|
+ if (sts < 0) { // error
|
|
|
+ return sts;
|
|
|
+ }
|
|
|
+ JpegDQTOffset = dqtOffset;
|
|
|
+ App1StartOffset = sts;
|
|
|
+ if (sts == 0) {
|
|
|
+ return sts;
|
|
|
+ }
|
|
|
+
|
|
|
+ App2StartOffset = getAppNStartOffset(fp, APP2_MARKER, FPXR_ID_STR, FPXR_ID_STR_LEN, NULL);
|
|
|
+
|
|
|
+ MPFStartOffset = getAppNStartOffset(fp, APP2_MARKER, MPF_ID_STR, MPF_ID_STR_LEN, NULL);
|
|
|
+
|
|
|
+ // Load the App1 segment header
|
|
|
+ if (!readAppNSegmentHeader(fp, &App1Header, App1StartOffset)) {
|
|
|
+ return EXIF_ERR_INVALID_APP1HEADER;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (MPFStartOffset > 0) {
|
|
|
+ if (!readMPFSegmentHeader(fp, &MPFHeader, MPFStartOffset)) {
|
|
|
+ return EXIF_ERR_INVALID_APP1HEADER;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+
|
|
|
+static void PRINTF(char **ms, const char *fmt, ...) {
|
|
|
+ char buf[4096];
|
|
|
+ char *p = NULL;
|
|
|
+ int len, cnt;
|
|
|
+ va_list args;
|
|
|
+ va_start(args, fmt);
|
|
|
+ cnt = vsnprintf(buf, sizeof(buf)-1, fmt, args);
|
|
|
+ if (!ms) {
|
|
|
+ printf("%s", buf);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ else if (*ms) {
|
|
|
+ len = (int)(strlen(*ms) + cnt + 1);
|
|
|
+ p = (char*)malloc(len);
|
|
|
+ strcpy(p, *ms);
|
|
|
+ strcat(p, buf);
|
|
|
+ free(*ms);
|
|
|
+ } else {
|
|
|
+ len = cnt + 1;
|
|
|
+ p = (char*)malloc(len);
|
|
|
+ strcpy(p, buf);
|
|
|
+ }
|
|
|
+ *ms = p;
|
|
|
+ va_end(args);
|
|
|
+}
|
|
|
+
|
|
|
+#ifdef _MSC_VER
|
|
|
+#ifdef __cplusplus
|
|
|
+} /* extern "C" */
|
|
|
+#endif
|
|
|
+#endif
|