diff --git a/resources/icons/splashscreen.jpg b/resources/splashscreen/benchy-splashscreen.jpg similarity index 84% rename from resources/icons/splashscreen.jpg rename to resources/splashscreen/benchy-splashscreen.jpg index efd488677..56efc48aa 100644 Binary files a/resources/icons/splashscreen.jpg and b/resources/splashscreen/benchy-splashscreen.jpg differ diff --git a/resources/icons/splashscreen-gcodepreview.jpg b/resources/splashscreen/prusa-gcodepreview.jpg similarity index 87% rename from resources/icons/splashscreen-gcodepreview.jpg rename to resources/splashscreen/prusa-gcodepreview.jpg index 3bae38493..1f4c5fd7f 100644 Binary files a/resources/icons/splashscreen-gcodepreview.jpg and b/resources/splashscreen/prusa-gcodepreview.jpg differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c2938dfaf..f7a8a579a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -25,6 +25,8 @@ if (SLIC3R_GUI) add_subdirectory(imgui) add_subdirectory(hidapi) include_directories(hidapi/include) + add_subdirectory(exif) + include_directories(exif/include) if(WIN32) message(STATUS "WXWIN environment set to: $ENV{WXWIN}") diff --git a/src/exif/CMakeLists.txt b/src/exif/CMakeLists.txt new file mode 100644 index 000000000..bdab4df6c --- /dev/null +++ b/src/exif/CMakeLists.txt @@ -0,0 +1,6 @@ + +cmake_minimum_required(VERSION 2.5) + +include_directories(include) + +add_library(exif STATIC exif.c) diff --git a/src/exif/COPYING b/src/exif/COPYING new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/src/exif/COPYING @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/src/exif/README b/src/exif/README new file mode 100644 index 000000000..e3ac48e3a --- /dev/null +++ b/src/exif/README @@ -0,0 +1,94 @@ +"exif.c" is a simple implementation to access the Exif segment in a JPEG file. +It easily enables you to get the value of the IFD tag field with such code: + + TagNodeInfo *tag = getTagInfo(ifdArray, IFD_EXIF, TAG_DateTimeOriginal); + printf("DateTimeOriginal = [%s]\n", tag->byteData); + + -> DateTimeOriginal = [2013:09:01 09:49:00] + +See "sample_main.c" and "exif.h" for details. + +exif.c only uses standard C library functions. So, it will be usable in many environments. +It has been tested in the following environments. + + - Windows XP 32bit + 32bit Visual C++ + - Windows 7 64bit + 64bit Visual C++ + - Redhat Linux 32bit + 32bit gcc + - Mac OS X 64bit + 64bit gcc + +building with gcc: +gcc -o exif sample_main.c exif.c + +building with Microsoft Visual C++: +cl.exe /o exif sample_main.c exif.c + +The following output is the result of the sample program. +--------------------------------------------------------------------------- +$ exif test.jpg + +[test.jpg] createIfdTableArray: result=4 + +{0TH IFD} + - Make: [Apple] + - Model: [iPod touch] + - Orientation: 1 + - XResolution: 72/1 + - YResolution: 72/1 + - ResolutionUnit: 2 + - Software: [6.1.4] + - DateTime: [2013:09:01 09:49:00] + - YCbCrPositioning: 1 + - ExifIFDPointer: 206 + - GPSInfoIFDPointer: 576 + +{EXIF IFD} + - ExposureTime: 1/30 + - FNumber: 12/5 + - ExposureProgram: 2 + - PhotographicSensitivity: 400 + - ExifVersion: 0 2 2 1 + - DateTimeOriginal: [2013:09:01 09:49:00] + - DateTimeDigitized: [2013:09:01 09:49:00] + - ComponentsConfiguration: 0x01 0x02 0x03 0x00 + - ShutterSpeedValue: 4035/821 + - ApertureValue: 4845/1918 + - BrightnessValue: 2234/1113 + - MeteringMode: 5 + - Flash: 32 + - FocalLength: 77/20 + - FlashPixVersion: 0 1 0 0 + - ColorSpace: 1 + - PixelXDimension: 960 + - PixelYDimension: 720 + - SensingMethod: 2 + - ExposureMode: 0 + - WhiteBalance: 0 + - FocalLengthIn35mmFormat: 32 + - SceneCaptureType: 0 + +{GPS IFD} + - GPSLatitudeRef: [S] + - GPSLatitude: 69/1 17/100 0/1 + - GPSLongitudeRef: [E] + - GPSLongitude: 39/1 35/100 0/1 + - GPSAltitudeRef: 0 + - GPSAltitude: 6151/470 + - GPSTimeStamp: 0/1 48/1 3921/100 + +{1ST IFD} + - Compression: 6 + - XResolution: 72/1 + - YResolution: 72/1 + - ResolutionUnit: 2 + - JPEGInterchangeFormat: 840 + - JPEGInterchangeFormatLength: 8648 + +0th IFD : Model = [iPod touch] +Exif IFD : DateTimeOriginal = [2013:09:01 09:49:00] +GPS IFD : GPSLatitude = 69/1 17/100 0/1 +removeExifSegmentFromJPEGFile: result=1 +--------------------------------------------------------------------------- +http://dsas.blog.klab.org/archives/52123322.html (Japanese only) + +Copyright (C) 2013 KLab Inc. + diff --git a/src/exif/exif.c b/src/exif/exif.c new file mode 100644 index 000000000..ab2e168c8 --- /dev/null +++ b/src/exif/exif.c @@ -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 +//#define vsnprintf _vsnprintf +#endif + +#include "exif.h" + +#include +#include +#include +#include +#include +#include +#include + +#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 diff --git a/src/exif/include/exif.h b/src/exif/include/exif.h new file mode 100644 index 000000000..1984985ba --- /dev/null +++ b/src/exif/include/exif.h @@ -0,0 +1,751 @@ +/* + * 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. + */ + + +#ifndef EXIF_H_ +#define EXIF_H_ + +#ifdef _WIN32 +#define EXIF_API_EXPORT __declspec(dllexport) +#define EXIF_API_CALL +#else +#define EXIF_API_EXPORT /**< API export macro */ +#define EXIF_API_CALL /**< API call macro */ +#endif + +#define EXIF_API_EXPORT_CALL EXIF_API_EXPORT EXIF_API_CALL /**< API export and call macro*/ + +#ifdef __cplusplus +extern "C" { +#endif + + /** + * setVerbose() + * + * Verbose output on/off + * + * parameters + * [in] v : 1=on 0=off + */ +void EXIF_API_EXPORT_CALL exif_setVerbose(int); + +#ifdef _DEBUG +#ifdef _MSC_VER +#define _CRTDBG_MAP_ALLOC +#include +//#define new ::new(_NORMAL_BLOCK, __FILE__, __LINE__) +#endif +#endif +#include + + /** + * Typical Usage: + * + * The following code describes how to get "Model" Tag's data + * from 0th IFD in "test.jpg". + * + * // Parse the JPEG header and create the pointer array of the IFD tables + * int result; + * void **ifdArray = createIfdTableArray("test.jpg", *result); + * switch (result) { + * case 0: + * : + * } + * if (!ifdArray) { + * return; + * } + * + * // Get the TagNodeInfo that matches the IFD_TYPE & TagId + * TagNodeInfo *tag = getTagInfo(ifdArray, IFD_0TH, TAG_Model); + * + * if (tag) { + * if (!tag->error) { + * printf("Model=[%s]\n", tag->byteData); // TYPE_ASCII + * } + * freeTagInfo(tag); + * } + * + * freeIfdTableArray(ifdArray); + * return; + * + */ + + // IFD Type + typedef enum { + IFD_UNKNOWN = 0, + IFD_0TH, + IFD_1ST, + IFD_EXIF, + IFD_GPS, + IFD_IO, + IFD_MPF + } EXIF_IFD_TYPE; + + // Tag Type + typedef enum { + TYPE_BYTE = 1, + TYPE_ASCII, + TYPE_SHORT, + TYPE_LONG, + TYPE_RATIONAL, + TYPE_SBYTE, + TYPE_UNDEFINED, + TYPE_SSHORT, + TYPE_SLONG, + TYPE_SRATIONAL + } EXIF_IFD_TAG_TYPE; + + // Tag info structure + typedef struct _exif_tagNodeInfo ExifTagNodeInfo; + struct _exif_tagNodeInfo { + uint16_t tagId; // tag ID (e.g. TAG_Model = 0x0110) + uint16_t type; // data Type (e.g. TYPE_ASCII = 2) + unsigned int count; // count of the data + unsigned int* numData; // numeric data array + uint8_t* byteData; // byte data array + uint16_t error; // 0: no error 1: parse error + }; + + typedef struct _exif_image_dir_ent + { + uint32_t ImageFlags; + uint32_t ImageLength; + uint32_t ImageStart; + uint16_t Image1EntryNum; + uint16_t Image2EntryNum; + } EXIF_IMAGE_DIR_ENT; + /** + * Note: + * + * [Type = TYPE_BYTE, TYPE_SHORT, TYPE_LONG, + * TYPE_SBYTE, TYPE_SSHORT, TYPE_SLONG] + * 'numData' holds numeric values. + * numData[0] to numData[count-1] is accessible. + * + * [Type = TYPE_RATIONAL, TYPE_SRATIONAL] + * 'numData' holds numeric values. + * numData[0] to numData[count*2-1] is accessible. + * + * [Type = TYPE_ASCII] + * 'byteData' holds ascii string data. + * 'count' means the whole length of string with '\0' terminator. + * + * [Type = TYPE_UNDEFINED] + * 'byteData' holds byte data. + * byteData[0] to byteData[count-1] is accessible. + * + * If the original tag field holds the wrong value, the 'error' flag + * will be set to 1. In such cases, both 'numData' and 'byteData' + * might have set to NULL. So, the flag should be checked first. + */ + + // error status +#define EXIF_ERR_READ_FILE -1 +#define EXIF_ERR_WRITE_FILE -2 +#define EXIF_ERR_INVALID_JPEG -3 +#define EXIF_ERR_INVALID_APP1HEADER -4 +#define EXIF_ERR_INVALID_IFD -5 +#define EXIF_ERR_INVALID_ID -6 +#define EXIF_ERR_INVALID_TYPE -7 +#define EXIF_ERR_INVALID_COUNT -8 +#define EXIF_ERR_INVALID_POINTER -9 +#define EXIF_ERR_NOT_EXIST -10 +#define EXIF_ERR_ALREADY_EXIST -11 +#define EXIF_ERR_UNKNOWN -12 +#define EXIF_ERR_MEMALLOC -13 + +// public funtions + + /** + * 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 + * ERR_READ_FILE + * ERR_WRITE_FILE + * ERR_INVALID_JPEG + * ERR_INVALID_APP1HEADER + */ + int EXIF_API_EXPORT_CALL exif_removeExifSegmentFromJPEGFile(const char* inJPEGFileName, + const char* outJPGEFileName); + + /** + * 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 + * ERR_READ_FILE + * ERR_INVALID_JPEG + * ERR_INVALID_APP1HEADER + * ERR_INVALID_IFD + */ + int EXIF_API_EXPORT_CALL exif_fillIfdTableArray(const char* JPEGFileName, void* ifdArray[32]); + + /** + * 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 + * ERR_READ_FILE + * ERR_INVALID_JPEG + * ERR_INVALID_APP1HEADER + * 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); + + /** + * freeIfdTables() + * + * Free the pointer array of the IFD tables + * + * parameters + * [in] ifdArray : address of the IFD array + */ + void EXIF_API_EXPORT_CALL exif_freeIfdTables(void* ifdArray[32]); + + /** + * freeIfdTableArray() + * + * Free the pointer array of the IFD tables + * + * parameters + * [in] ifdArray : address of the IFD array + */ + void EXIF_API_EXPORT_CALL exif_freeIfdTableArray(void** ifdArray); + + /** + * getIfdType() + * + * Returns the type of the IFD + * + * parameters + * [in] ifd: target IFD + * + * return + * IFD TYPE value + */ + EXIF_IFD_TYPE EXIF_API_EXPORT_CALL exif_getIfdType(void* ifd); + + /** + * dumpIfdTable() + * + * Dump the IFD table + * + * parameters + * [in] ifd: target IFD + */ + void EXIF_API_EXPORT_CALL exif_dumpIfdTable(void* ifd); + + /** + * dumpIfdTableArray() + * + * Dump the array of the IFD tables + * + * parameters + * [in] ifdArray : address of the IFD array + */ + void EXIF_API_EXPORT_CALL exif_dumpIfdTableArray(void** ifdArray); + + /** + * getTagInfo() + * + * Get the TagNodeInfo that matches the 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 TagNodeInfo structure + */ + ExifTagNodeInfo EXIF_API_EXPORT * EXIF_API_CALL exif_getTagInfo(void** ifdArray, + EXIF_IFD_TYPE ifdType, + uint16_t tagId); + + /** + * getTagInfoFromIfd() + * + * Get the TagNodeInfo that matches the TagId + * + * parameters + * [in] ifd : target IFD + * [in] tagId : target tag ID + * + * return + * NULL: tag is not found + * !NULL: address of the TagNodeInfo structure + */ + ExifTagNodeInfo EXIF_API_EXPORT * EXIF_API_CALL exif_getTagInfoFromIfd(void* ifd, uint16_t tagId); + + /** + * freeTagInfo() + * + * Free the TagNodeInfo allocated by getTagInfo() or getTagInfoFromIfd() + * + * parameters + * [in] tag : target TagNodeInfo + */ + void EXIF_API_EXPORT_CALL exif_freeTagInfo(void* 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_CALL exif_queryTagNodeIsExist(void** ifdTableArray, + EXIF_IFD_TYPE ifdType, + uint16_t tagId); + + /** + * createTagInfo() + * + * Create new TagNodeInfo 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 + * ERR_INVALID_TYPE + * ERR_INVALID_COUNT + * ERR_MEMALLOC + * + * return + * NULL: error + * !NULL: address of the newly created TagNodeInfo + */ + ExifTagNodeInfo EXIF_API_EXPORT * EXIF_API_CALL exif_createTagInfo(uint16_t tagId, + uint16_t type, + unsigned int count, + int* pResult); + + /** + * 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_CALL exif_removeIfdTableFromIfdTableArray(void** ifdTableArray, EXIF_IFD_TYPE ifdType); + + /** + * insertIfdTableToIfdTableArray() + * + * Insert new IFD table to the ifdTableArray + * + * parameters + * [in] ifdTableArray: address of the IFD tables array + * [in] ifdType : target IFD type + * [out] pResult : result status + * 0: OK + * -n: error + * ERR_INVALID_POINTER + * ERR_ALREADY_EXIST + * 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_API_CALL exif_insertIfdTableToIfdTableArray(void** ifdTableArray, + EXIF_IFD_TYPE ifdType, + int* pResult); + + /** + * 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_CALL exif_removeTagNodeFromIfdTableArray(void** ifdTableArray, + EXIF_IFD_TYPE ifdType, + uint16_t 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] tagNodeInfo: address of the TagNodeInfo + * + * note + * This function uses the copy of the specified tag data. + * The caller must free it after this function returns. + * + * return + * 0: OK + * ERR_INVALID_POINTER: + * ERR_NOT_EXIST: + * ERR_ALREADY_EXIST: + * ERR_UNKNOWN: + */ + int EXIF_API_EXPORT_CALL exif_insertTagNodeToIfdTableArray(void** ifdTableArray, + EXIF_IFD_TYPE ifdType, + ExifTagNodeInfo* tagNodeInfo); + + /** + * 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 : result status + * 0: OK + * -n: error + * ERR_INVALID_POINTER + * ERR_MEMALLOC + * 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_API_CALL exif_getThumbnailDataOnIfdTableArray(void** ifdTableArray, + unsigned int* pLength, + int* pResult); + + /** + * 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 + * ERR_INVALID_POINTER + * ERR_MEMALLOC + * ERR_UNKNOWN + */ + int EXIF_API_EXPORT_CALL exif_setThumbnailDataOnIfdTableArray(void** ifdTableArray, + uint8_t* pData, + unsigned int length); + + void EXIF_API_EXPORT_CALL exif_getIfdTableDump(void* pIfd, char** pp); + + + /** + * 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 + * ERR_READ_FILE + * ERR_WRITE_FILE + * ERR_INVALID_JPEG + * ERR_INVALID_APP1HEADER + * ERR_INVALID_POINTER + * ERROR_UNKNOWN: + */ + int EXIF_API_EXPORT_CALL exif_updateExifSegmentInJPEGFile(const char* inJPEGFileName, + const char* outJPGEFileName, + void** ifdTableArray); + + void EXIF_API_EXPORT_CALL exif_getIfdTableDump(void* pIfd, char** pp); + + /** + * 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 + * ERR_READ_FILE + * ERR_WRITE_FILE + * ERR_INVALID_JPEG + * ERR_INVALID_APP1HEADER + */ + int EXIF_API_EXPORT_CALL exif_removeAdobeMetadataSegmentFromJPEGFile(const char* inJPEGFileName, + const char* outJPGEFileName); + + // Tag IDs + // 0th IFD, 1st IFD, Exif IFD +#define TAG_ImageWidth 0x0100 +#define TAG_ImageLength 0x0101 +#define TAG_BitsPerSample 0x0102 +#define TAG_Compression 0x0103 +#define TAG_PhotometricInterpretation 0x0106 +#define TAG_Orientation 0x0112 +#define TAG_SamplesPerPixel 0x0115 +#define TAG_PlanarConfiguration 0x011C +#define TAG_YCbCrSubSampling 0x0212 +#define TAG_YCbCrPositioning 0x0213 +#define TAG_XResolution 0x011A +#define TAG_YResolution 0x011B +#define TAG_ResolutionUnit 0x0128 + +#define TAG_StripOffsets 0x0111 +#define TAG_RowsPerStrip 0x0116 +#define TAG_StripByteCounts 0x0117 +#define TAG_JPEGInterchangeFormat 0x0201 +#define TAG_JPEGInterchangeFormatLength 0x0202 + +#define TAG_TransferFunction 0x012D +#define TAG_WhitePoint 0x013E +#define TAG_PrimaryChromaticities 0x013F +#define TAG_YCbCrCoefficients 0x0211 +#define TAG_ReferenceBlackWhite 0x0214 + +#define TAG_DateTime 0x0132 +#define TAG_ImageDescription 0x010E +#define TAG_Make 0x010F +#define TAG_Model 0x0110 +#define TAG_Software 0x0131 +#define TAG_Artist 0x013B +#define TAG_Copyright 0x8298 +#define TAG_ExifIFDPointer 0x8769 +#define TAG_GPSInfoIFDPointer 0x8825 +#define TAG_InteroperabilityIFDPointer 0xA005 + +#define TAG_Rating 0x4746 + +#define TAG_ExifVersion 0x9000 +#define TAG_FlashPixVersion 0xA000 + +#define TAG_ColorSpace 0xA001 + +#define TAG_ComponentsConfiguration 0x9101 +#define TAG_CompressedBitsPerPixel 0x9102 +#define TAG_PixelXDimension 0xA002 +#define TAG_PixelYDimension 0xA003 + +#define TAG_MakerNote 0x927C +#define TAG_UserComment 0x9286 + +#define TAG_RelatedSoundFile 0xA004 + +#define TAG_DateTimeOriginal 0x9003 +#define TAG_DateTimeDigitized 0x9004 +#define TAG_SubSecTime 0x9290 +#define TAG_SubSecTimeOriginal 0x9291 +#define TAG_SubSecTimeDigitized 0x9292 + +#define TAG_ExposureTime 0x829A +#define TAG_FNumber 0x829D +#define TAG_ExposureProgram 0x8822 +#define TAG_SpectralSensitivity 0x8824 +#define TAG_PhotographicSensitivity 0x8827 +#define TAG_OECF 0x8828 +#define TAG_SensitivityType 0x8830 +#define TAG_StandardOutputSensitivity 0x8831 +#define TAG_RecommendedExposureIndex 0x8832 +#define TAG_ISOSpeed 0x8833 +#define TAG_ISOSpeedLatitudeyyy 0x8834 +#define TAG_ISOSpeedLatitudezzz 0x8835 + +#define TAG_ShutterSpeedValue 0x9201 +#define TAG_ApertureValue 0x9202 +#define TAG_BrightnessValue 0x9203 +#define TAG_ExposureBiasValue 0x9204 +#define TAG_MaxApertureValue 0x9205 +#define TAG_SubjectDistance 0x9206 +#define TAG_MeteringMode 0x9207 +#define TAG_LightSource 0x9208 +#define TAG_Flash 0x9209 +#define TAG_FocalLength 0x920A +#define TAG_SubjectArea 0x9214 +#define TAG_FlashEnergy 0xA20B +#define TAG_SpatialFrequencyResponse 0xA20C +#define TAG_FocalPlaneXResolution 0xA20E +#define TAG_FocalPlaneYResolution 0xA20F +#define TAG_FocalPlaneResolutionUnit 0xA210 +#define TAG_SubjectLocation 0xA214 +#define TAG_ExposureIndex 0xA215 +#define TAG_SensingMethod 0xA217 +#define TAG_FileSource 0xA300 +#define TAG_SceneType 0xA301 +#define TAG_CFAPattern 0xA302 + +#define TAG_CustomRendered 0xA401 +#define TAG_ExposureMode 0xA402 +#define TAG_WhiteBalance 0xA403 +#define TAG_DigitalZoomRatio 0xA404 +#define TAG_FocalLengthIn35mmFormat 0xA405 +#define TAG_SceneCaptureType 0xA406 +#define TAG_GainControl 0xA407 +#define TAG_Contrast 0xA408 +#define TAG_Saturation 0xA409 +#define TAG_Sharpness 0xA40A +#define TAG_DeviceSettingDescription 0xA40B +#define TAG_SubjectDistanceRange 0xA40C + +#define TAG_ImageUniqueID 0xA420 +#define TAG_CameraOwnerName 0xA430 +#define TAG_BodySerialNumber 0xA431 +#define TAG_LensSpecification 0xA432 +#define TAG_LensMake 0xA433 +#define TAG_LensModel 0xA434 +#define TAG_LensSerialNumber 0xA435 +#define TAG_Gamma 0xA500 + +#define TAG_PrintIM 0xC4A5 +#define TAG_Padding 0xEA1C + +// GPS IFD +#define TAG_GPSVersionID 0x0000 +#define TAG_GPSLatitudeRef 0x0001 +#define TAG_GPSLatitude 0x0002 +#define TAG_GPSLongitudeRef 0x0003 +#define TAG_GPSLongitude 0x0004 +#define TAG_GPSAltitudeRef 0x0005 +#define TAG_GPSAltitude 0x0006 +#define TAG_GPSTimeStamp 0x0007 +#define TAG_GPSSatellites 0x0008 +#define TAG_GPSStatus 0x0009 +#define TAG_GPSMeasureMode 0x000A +#define TAG_GPSDOP 0x000B +#define TAG_GPSSpeedRef 0x000C +#define TAG_GPSSpeed 0x000D +#define TAG_GPSTrackRef 0x000E +#define TAG_GPSTrack 0x000F +#define TAG_GPSImgDirectionRef 0x0010 +#define TAG_GPSImgDirection 0x0011 +#define TAG_GPSMapDatum 0x0012 +#define TAG_GPSDestLatitudeRef 0x0013 +#define TAG_GPSDestLatitude 0x0014 +#define TAG_GPSDestLongitudeRef 0x0015 +#define TAG_GPSDestLongitude 0x0016 +#define TAG_GPSBearingRef 0x0017 +#define TAG_GPSBearing 0x0018 +#define TAG_GPSDestDistanceRef 0x0019 +#define TAG_GPSDestDistance 0x001A +#define TAG_GPSProcessingMethod 0x001B +#define TAG_GPSAreaInformation 0x001C +#define TAG_GPSDateStamp 0x001D +#define TAG_GPSDifferential 0x001E +#define TAG_GPSHPositioningError 0x001F + +// Interoperability IFD +#define TAG_InteroperabilityIndex 0x0001 +#define TAG_InteroperabilityVersion 0x0002 + +#define TAG_RelatedImageFileFormat 0x1000 +#define TAG_RelatedImageWidth 0x1001 +#define TAG_RelatedImageHeight 0x1002 + +// MPF tags +#define TAG_MPFVersion 0xB000 +#define TAG_NumberOfImage 0xB001 +#define TAG_MPImageList 0xB002 +#define TAG_ImageUIDList 0xB003 +#define TAG_TotalFrames 0xB004 + +#define TAG_MPIndividualNum 0xB101 + +#define TAG_PanOrientation 0xB201 +#define TAG_PanOverlapH 0xB202 +#define TAG_PanOverlapV 0xB203 +#define TAG_BaseViewpointNum 0xB204 +#define TAG_ConvergenceAngle 0xB205 +#define TAG_BaselineLength 0xB206 +#define TAG_VerticalDivergence 0xB207 +#define TAG_AxisDistanceX 0xB208 +#define TAG_AxisDistanceY 0xB209 +#define TAG_AxisDistanceZ 0xB20A +#define TAG_YawAngle 0xB20B +#define TAG_PitchAngle 0xB20C +#define TAG_RollAngle 0xB20D + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif // _EXIF_H_ diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 8b1310e03..28bf8bf27 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -214,6 +214,12 @@ void AppConfig::set_defaults() if (get("show_splash_screen").empty()) set("show_splash_screen", "1"); + if (get("splash_screen_editor").empty()) + set("splash_screen_editor", "benchy-splashscreen.jpg"); + + if (get("splash_screen_gcodeviewer").empty()) + set("splash_screen_gcodeviewer", "prusa-gcodepreview.jpg"); + #if ENABLE_CTRL_M_ON_WINDOWS #ifdef _WIN32 if (get("use_legacy_3DConnexion").empty()) diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 452cb2f68..f8a4a797e 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -257,7 +257,7 @@ target_compile_definitions(libslic3r_gui PRIVATE $<$:SLIC3 encoding_check(libslic3r_gui) -target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui GLEW::GLEW OpenGL::GL OpenGL::GLU hidapi libcurl ${wxWidgets_LIBRARIES}) +target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui GLEW::GLEW OpenGL::GL OpenGL::GLU hidapi exif libcurl ${wxWidgets_LIBRARIES}) if (CMAKE_SYSTEM_NAME STREQUAL "Linux") target_link_libraries(libslic3r_gui ${DBUS_LIBRARIES}) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 1ecc94bd4..537ce7041 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -990,13 +990,23 @@ void Choice::BUILD() { temp->Bind(wxEVT_COMBOBOX_DROPDOWN, [this](wxCommandEvent&) { m_is_dropped = true; }); temp->Bind(wxEVT_COMBOBOX_CLOSEUP, [this](wxCommandEvent&) { m_is_dropped = false; }); - temp->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent&) { on_change_field(); }, temp->GetId()); + temp->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent&) { + //note: on_change_field() is never really called because m_disable_change_event is always true. + // it should be fixed in a better way, but as modifying how m_disable_change_event is set will need exentive testing + // on all platform, I add this stop-gap. If you can remove it and let the splash_screen_editor field working, do it! + if (m_disable_change_event) { + m_disable_change_event = false; + on_change_field(); + m_disable_change_event = true; + }else + on_change_field(); + }, temp->GetId()); if (m_is_editable) { temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { e.Skip(); if (m_opt.type == coStrings) { - on_change_field(); + on_change_field(); return; } diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 9a22ddd5e..c9e9de73f 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -37,6 +37,8 @@ #include #include +#include "exif.h" + #include "libslic3r/Utils.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/I18N.hpp" @@ -97,14 +99,14 @@ class MainFrame; class SplashScreen : public wxSplashScreen { public: - SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxPoint pos = wxDefaultPosition) + SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxPoint pos = wxDefaultPosition, wxString author = "") : wxSplashScreen(bitmap, splashStyle, milliseconds, static_cast(wxGetApp().mainframe), wxID_ANY, wxDefaultPosition, wxDefaultSize, #ifdef __APPLE__ wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR | wxSTAY_ON_TOP #else wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR #endif // !__APPLE__ - ) + ), m_author(author) { wxASSERT(bitmap.IsOk()); @@ -221,8 +223,11 @@ public: int version_height = memDc.GetTextExtent(m_constant_text.version).GetY(); memDc.SetFont(m_constant_text.credits_font); - memDc.DrawLabel(m_constant_text.credits, banner_rect, wxALIGN_BOTTOM | wxALIGN_LEFT); - int credits_height = memDc.GetMultiLineTextExtent(m_constant_text.credits).GetY(); + wxString credit_and_author = m_constant_text.credits; + if (!m_author.empty()) + credit_and_author += "\n\n" + m_author; + memDc.DrawLabel(credit_and_author, banner_rect, wxALIGN_BOTTOM | wxALIGN_LEFT); + int credits_height = memDc.GetMultiLineTextExtent(credit_and_author).GetY(); int text_height = memDc.GetTextExtent("text").GetY(); // calculate position for the dynamic text @@ -235,6 +240,7 @@ private: wxFont m_action_font; int m_action_line_y_position; float m_scale {1.0}; + wxString m_author; struct ConstantText { @@ -257,8 +263,7 @@ private: // credits infornation credits = _L(SLIC3R_INTRO) + "\n\n" + title + " " + _L("is licensed under the") + " " + _L("GNU Affero General Public License, version 3") + "\n\n" + - _L("Contributions by Vojtech Bubnik, Enrico Turri, Durand Remi, Oleksandra Iushchenko, Tamas Meszaros, Lukas Matena, Vojtech Kral, David Kocik and numerous others.") + "\n\n" + - _L("Artwork model by Durand Remi"); + _L("Contributions by Vojtech Bubnik, Enrico Turri, Durand Remi, Oleksandra Iushchenko, Tamas Meszaros, Lukas Matena, Vojtech Kral, David Kocik and numerous others."); title_font = version_font = credits_font = init_font; } @@ -818,8 +823,30 @@ bool GUI_App::on_init_inner() SplashScreen* scrn = nullptr; if (app_config->get("show_splash_screen") == "1") { + wxBitmap bmp; + std::string file_name = is_editor() + ? app_config->get("splash_screen_editor") + : app_config->get("splash_screen_gcodeviewer"); + wxString artist; + if (!file_name.empty() && file_name != (std::string(SLIC3R_APP_NAME) + L(" icon"))) { + wxString splash_screen_path = (boost::filesystem::path(Slic3r::resources_dir()) / "splashscreen" / file_name).string(); // make a bitmap with dark grey banner on the left side - wxBitmap bmp = SplashScreen::MakeBitmap(wxBitmap(from_u8(var(is_editor() ? "splashscreen.jpg" : "splashscreen-gcodepreview.jpg")), wxBITMAP_TYPE_JPEG)); + bmp = SplashScreen::MakeBitmap(wxBitmap(splash_screen_path, wxBITMAP_TYPE_JPEG)); + + + int result; + void** ifdArray = nullptr; + ExifTagNodeInfo* tag; + ifdArray = exif_createIfdTableArray(splash_screen_path.c_str(), &result); + if (result > 0 && ifdArray) { + tag = exif_getTagInfo(ifdArray, IFD_0TH, TAG_Artist); + if (tag) { + if (!tag->error) { + artist = (_L("Artwork model by") + " " + ((char*)tag->byteData)); + } + } + } + } // Detect position (display) to show the splash screen // Now this position is equal to the mainframe position @@ -832,7 +859,7 @@ bool GUI_App::on_init_inner() // create splash screen with updated bmp scrn = new SplashScreen(bmp.IsOk() ? bmp : create_scaled_bitmap( SLIC3R_APP_KEY "_logo", nullptr, 400), - wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos); + wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos, artist); #ifndef __linux__ wxYield(); #endif diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index b66b66995..829257881 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -58,8 +58,10 @@ void PreferencesDialog::build() m_optgroup_general->m_on_change = [this](t_config_option_key opt_key, boost::any value) { if (opt_key == "default_action_on_close_application" || opt_key == "default_action_on_select_preset") m_values[opt_key] = boost::any_cast(value) ? "none" : "discard"; + else if (opt_key == "splash_screen_editor" || opt_key == "splash_screen_gcodeviewer") + m_values[opt_key] = boost::any_cast(value); else - m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; + m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; bool is_editor = wxGetApp().is_editor(); @@ -236,6 +238,27 @@ void PreferencesDialog::build() def.set_default_value(new ConfigOptionBool{ app_config->get("show_splash_screen") == "1" }); option = Option(def, "show_splash_screen"); m_optgroup_general->append_single_option_line(option); + + // splashscreen image + { + ConfigOptionDef def_combobox; + def_combobox.label = L("Splashscreen image"); + def_combobox.type = coStrings; + def_combobox.tooltip = L("Choose the image to use as splashscreen"); + def_combobox.gui_type = "f_enum_open"; + def_combobox.gui_flags = "show_value"; + def_combobox.enum_values.push_back(std::string(SLIC3R_APP_NAME) + L(" icon")); + //get all images in the spashscreen dir + for (const boost::filesystem::directory_entry& dir_entry : boost::filesystem::directory_iterator(boost::filesystem::path(Slic3r::resources_dir()) / "splashscreen")) + if (dir_entry.path().has_extension()&& std::set{ ".jpg", ".JPG", ".jpeg" }.count(dir_entry.path().extension().string()) > 0 ) + def_combobox.enum_values.push_back(dir_entry.path().filename().string()); + std::string current_file_name = app_config->get(is_editor ? "splash_screen_editor" : "splash_screen_gcodeviewer"); + if (std::find(def_combobox.enum_values.begin(), def_combobox.enum_values.end(), current_file_name) == def_combobox.enum_values.end()) + current_file_name = def_combobox.enum_values[0]; + def_combobox.set_default_value(new ConfigOptionStrings{ current_file_name }); + option = Option(def_combobox, is_editor ? "splash_screen_editor" : "splash_screen_gcodeviewer"); + m_optgroup_general->append_single_option_line(option); + } #if ENABLE_CTRL_M_ON_WINDOWS #if defined(_WIN32) || defined(__APPLE__)