天天看點

GOEXIF讀取和寫入EXIF資訊

最新版本的gexif,直接基于gdi+實作了exif資訊的讀取和寫入,代碼更清晰。

/*
 * File:	gexif.h
 * Purpose:	cpp EXIF reader
 * 3/2/2017 <[email protected]>
 * 基于GDI+的EXIF讀寫類
 */
//2017年2月3日 jsxyhelu 在原有cexif的基礎上重構
#pragma once


#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <windows.h>
#include <gdiplus.h>
#include <stdio.h>
#pragma comment(lib, "gdiplus.lib")   

class Gexif
{
public:
	Gexif(void);
	~Gexif(void);
	int GetEncoderClsid(const WCHAR* format, CLSID* pClsid);
	Gdiplus::Bitmap* LoadBitmapFromMemory(const void* memory, DWORD size);
	Gdiplus::Bitmap* LoadBitmapFromFile(const TCHAR* file_name);
	int WriteExif2Image(CString strFileName, int PropertyTag, CString propertyValue);
	CString ReadExifFromImage(CString strFileName, int PropertyTag);
	float GetXresoutionFromImage(CString strFileName);
};
      
/*
 * File:	gexif.h
 * Purpose:	cpp EXIF reader
 * 3/2/2017 <[email protected]>
 * 基于GDI+的EXIF讀寫類
 */

#include "stdafx.h"
#include "Gexif.h"
using namespace Gdiplus;

Gexif::Gexif(void)
{
}


Gexif::~Gexif(void)
{
}


int Gexif::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
	return 0;
}

// 從記憶體加載圖檔,失敗傳回NULL
Gdiplus::Bitmap* Gexif::LoadBitmapFromMemory(const void* memory, DWORD size)
{
	Bitmap* bmp = NULL;
	IStream* stream = NULL;
	if (CreateStreamOnHGlobal(NULL, TRUE, &stream) == S_OK)
	{
		ULARGE_INTEGER uli;
		uli.QuadPart = size;
		stream->SetSize(uli);
		if (stream->Write(memory, size, NULL) == S_OK)
			bmp = new Bitmap(stream);
		stream->Release();
	}
	return bmp;
}

// 從檔案加載圖檔,不獨占檔案,失敗傳回NULL
Gdiplus::Bitmap* Gexif::LoadBitmapFromFile(const TCHAR* file_name)
{
	Bitmap* bmp = NULL;
	HANDLE file_handle = CreateFile(file_name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (file_handle != INVALID_HANDLE_VALUE)
	{
		DWORD temp = 0;
		DWORD file_size = GetFileSize(file_handle, &temp);
		if (file_size && !temp)  // 不處理大于4G的檔案
		{
			// 将圖檔檔案讀到記憶體後,再從記憶體建立Bitmap
			unsigned char* buffer = new unsigned char[file_size];
			if (ReadFile(file_handle, buffer, file_size, &temp, NULL))
				bmp = LoadBitmapFromMemory(buffer, temp);
			delete [] buffer;
		}
		CloseHandle(file_handle);
	}
	return bmp;
}

//寫入exif資訊到圖檔中去,目前隻支援jpeg格式
//傳回 0為正常 -1為異常
//各種tchar wchar cstring的轉換,技巧較多.
int Gexif::WriteExif2Image(CString strFileName, int PropertyTag, CString pValue)
{
	GdiplusStartupInput gdiplusStartupInput;
	ULONG_PTR gdiplusToken;
	GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
	Status stat;
	CLSID  clsid;

	char* propertyValue = (char*)pValue.GetBuffer(pValue.GetLength());
	Bitmap* bitmap = LoadBitmapFromFile(strFileName);
	PropertyItem* propertyItem = new PropertyItem;
	GetEncoderClsid(L"image/jpeg", &clsid);

	propertyItem->id = PropertyTag;//給Tag指派
	propertyItem->length = 255;  
	propertyItem->type = PropertyTagTypeASCII; 
	propertyItem->value = propertyValue;
	bitmap->SetPropertyItem(propertyItem);


	WCHAR wcharbuf[100];
	CStringW strWide = CT2W(strFileName);
	wcscpy(wcharbuf,strWide);
	stat = bitmap->Save(wcharbuf, &clsid, NULL);

	if(stat != Ok)
		return -1;
	delete propertyItem;
	delete bitmap;
	GdiplusShutdown(gdiplusToken);
	return 0;
}

//使用基于GDI+,擷取Exif資訊
//這裡種類很多的,目前隻是實作了copytright
CString Gexif::ReadExifFromImage(CString strFileName, int PropertyTag)
{
	CString strRet;//用于傳回的str

	GdiplusStartupInput gdiplusStartupInput;
	ULONG_PTR gdiplusToken;
	GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
	Status stat;
	CLSID  clsid;

	//打開圖檔
	Bitmap* bitmap = LoadBitmapFromFile(strFileName);
	int icount = bitmap->GetPropertyCount();
	PROPID* propIDs = new PROPID[icount];
	bitmap->GetPropertyIdList(icount,propIDs);
	UINT size=0;
	UINT type = 0;
	Gdiplus::PropertyItem* propertyItem = NULL;
	//定位到這個PropertyTag
	/*
	#define PropertyTagTypeByte        1
	#define PropertyTagTypeASCII       2
	#define PropertyTagTypeShort       3
	#define PropertyTagTypeLong        4
	#define PropertyTagTypeRational    5
	#define PropertyTagTypeUndefined   7
	#define PropertyTagTypeSLONG       9
	#define PropertyTagTypeSRational  10
	*/
	for(UINT j=0;j < icount; ++j)
	{
		 if (PropertyTag == propIDs[j])
		 {
			 size= bitmap->GetPropertyItemSize(propIDs[j]);
			 propertyItem= (Gdiplus::PropertyItem*)malloc(size);
			 bitmap->GetPropertyItem(propIDs[j], size, propertyItem);
			 type = propertyItem->type;
			 char cbuf[255];
			 switch (type)
			 {
			 case  PropertyTagTypeASCII  :
				 strcpy(cbuf,(char*)propertyItem->value);
				 strRet = cbuf;
				 return strRet;
			 default:
				 return _T("empty");
				 break;
			 }
			
		 }
	} 
	
}

//獲得x方向的分辨率
float Gexif::GetXresoutionFromImage(CString strFileName)
{
	GdiplusStartupInput gdiplusStartupInput;
	ULONG_PTR gdiplusToken;
	GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
	Status stat;
	CLSID  clsid;
	Bitmap* bitmap = LoadBitmapFromFile(strFileName);
	float fret = bitmap->GetHorizontalResolution();
	return fret;
}
      

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

能夠讀取和寫入EXIF資訊,使得圖像處理程式在不改變原有圖檔内容、不添加多餘檔案的情況下可以儲存參數資訊,非常有價值。

這方面中英文資料比較少,經過較長時間的研究和內建,我在網絡相關資料的基礎上完成了GOEXIF,實作了MFC上的直接讀取和寫入。

讀取

EXIFINFO m_exifinfo;

FILE* hFile=fopen(FilePathName.c_str(),"rb");

if (hFile){

memset(&m_exifinfo,0,sizeof(EXIFINFO));

Cexif exif(&m_exifinfo);

exif.DecodeExif(hFile);

fclose(hFile);

寫入

Cexif ceif;

sprintf_s(cbuf2,"123");

int iret = ceif.WriteExif2Image(FilePathName.c_str(),PropertyTagCopyright,cbuf2);

if(iret == 0)

AfxMessageBox("寫入EXIF資訊成功!");

else

AfxMessageBox("寫入EXIF資訊失敗!");

已經能夠投入實際使用,但是由于是多種資料融合,代碼還是需要重構。我會結合自己工作來做,如果哪位有興趣繼續開發,這應該是一個很好的基礎。

/*

 * File:    exif.h

 * Purpose:    cpp EXIF reader

 * 16/Mar/2003 <[email protected]>

 * based on jhead-1.8 by Matthias Wandel <mwandel(at)rim(dot)net>

 */

//2016年11月25日 jsxyhelu 修改添加版權資訊

//2017年1月21日  jsxyhelu 添加寫入exif的功能

//使用例子 (基于MFC)

/*讀取

EXIFINFO m_exifinfo;

FILE* hFile=fopen(FilePathName.c_str(),"rb");

if (hFile){

Cexif exif(&m_exifinfo);

Cexif ceif;

int iret = ceif.WriteExif2Image(FilePathName.c_str(),PropertyTagCopyright,cbuf2);

if(iret == 0)

*/

#if !defined(__exif_h)

#define __exif_h

#include <stdlib.h>

#include <memory.h>

#include <string.h>

#include <stdio.h>

#include <math.h>

#include <windows.h>

#include <gdiplus.h>

#pragma comment(lib, "gdiplus.lib")   

#define MAX_COMMENT 65535

#define MAX_SECTIONS 20

typedef struct tag_ExifInfo {

    char  Version      [5];

    char  CameraMake   [32];

    char  CameraModel  [40];

    char  DateTime     [20];

    char  CopyRight    [MAX_COMMENT];

    int   Height, Width;

    int   Orientation;

    int   IsColor;

    int   Process;

    int   FlashUsed;

    float FocalLength;

    float ExposureTime;

    float ApertureFNumber;

    float Distance;

    float CCDWidth;

    float ExposureBias;

    int   Whitebalance;

    int   MeteringMode;

    int   ExposureProgram;

    int   ISOequivalent;

    int   CompressionLevel;

    float FocalplaneXRes;

    float FocalplaneYRes;

    float FocalplaneUnits;

    float Xresolution;

    float Yresolution;

    float ResolutionUnit;

    float Brightness;

    char  Comments[MAX_COMMENT];

    unsigned char * ThumbnailPointer;  /* Pointer at the thumbnail */

    unsigned ThumbnailSize;     /* Size of thumbnail. */

    bool  IsExif;

} EXIFINFO;

//--------------------------------------------------------------------------

// JPEG markers consist of one or more 0xFF unsigned chars, followed by a marker

// code unsigned char (which is not an FF).  Here are the marker codes of interest

// in this program.  (See jdmarker.c for a more complete list.)

#define M_SOF0  0xC0            // Start Of Frame N

#define M_SOF1  0xC1            // N indicates which compression process

#define M_SOF2  0xC2            // Only SOF0-SOF2 are now in common use

#define M_SOF3  0xC3

#define M_SOF5  0xC5            // NB: codes C4 and CC are NOT SOF markers

#define M_SOF6  0xC6

#define M_SOF7  0xC7

#define M_SOF9  0xC9

#define M_SOF10 0xCA

#define M_SOF11 0xCB

#define M_SOF13 0xCD

#define M_SOF14 0xCE

#define M_SOF15 0xCF

#define M_SOI   0xD8            // Start Of Image (beginning of datastream)

#define M_EOI   0xD9            // End Of Image (end of datastream)

#define M_SOS   0xDA            // Start Of Scan (begins compressed data)

#define M_JFIF  0xE0            // Jfif marker

#define M_EXIF  0xE1            // Exif marker

#define M_COM   0xFE            // COMment 

class Cexif

{

typedef struct tag_Section_t{

    unsigned char*    Data;

    int      Type;

    unsigned Size;

} Section_t;

public:

    EXIFINFO* m_exifinfo;

    char m_szLastError[256];

    Cexif(EXIFINFO* info = NULL);

    ~Cexif();

    bool DecodeExif(FILE* hFile);

protected:

    bool process_EXIF(unsigned char * CharBuf, unsigned int length);

    void process_COM (const unsigned char * Data, int length);

    void process_SOFn (const unsigned char * Data, int marker);

    int Get16u(void * Short);

    int Get16m(void * Short);

    long Get32s(void * Long);

    unsigned long Get32u(void * Long);

    double ConvertAnyFormat(void * ValuePtr, int Format);

    bool ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase, unsigned ExifLength,

                           EXIFINFO * const pInfo, unsigned char ** const LastExifRefdP);

    int ExifImageWidth;

    int MotorolaOrder;

    Section_t Sections[MAX_SECTIONS];

    int SectionsRead;

    bool freeinfo;

    int GetEncoderClsid(const WCHAR* format, CLSID* pClsid);

    Gdiplus::Bitmap* LoadBitmapFromMemory(const void* memory, DWORD size);

    Gdiplus::Bitmap* LoadBitmapFromFile(const TCHAR* file_name);

    int WriteExif2Image(CString strFileName, int PropertyTag, CString propertyValue);

};

#endif

#include "stdafx.h"

#include "Exif.h"

using namespace Gdiplus;

////////////////////////////////////////////////////////////////////////////////

Cexif::Cexif(EXIFINFO* info)

    if (info) {

        m_exifinfo = info;

        freeinfo = false;

    } else {

        m_exifinfo = new EXIFINFO;

        memset(m_exifinfo,0,sizeof(EXIFINFO));

        freeinfo = true;

    }

    m_szLastError[0]='\0';

    ExifImageWidth = MotorolaOrder = 0;

    SectionsRead=0;

    memset(&Sections, 0, MAX_SECTIONS * sizeof(Section_t));

}

Cexif::~Cexif()

    for(int i=0;i<MAX_SECTIONS;i++) if(Sections[i].Data) free(Sections[i].Data);

    if (freeinfo) delete m_exifinfo;

bool Cexif::DecodeExif(FILE * hFile)

    int a;

    int HaveCom = 0;

    a = fgetc(hFile);

    if (a != 0xff || fgetc(hFile) != M_SOI){

        return 0;

    for(;;){

        int itemlen;

        int marker = 0;

        int ll,lh, got;

        unsigned char * Data;

        if (SectionsRead >= MAX_SECTIONS){

            strcpy(m_szLastError,"Too many sections in jpg file");

            return 0;

        }

        for (a=0;a<7;a++){

            marker = fgetc(hFile);

            if (marker != 0xff) break;

            if (a >= 6){

                printf("too many padding unsigned chars\n");

                return 0;

            }

        if (marker == 0xff){

            // 0xff is legal padding, but if we get that many, something's wrong.

            strcpy(m_szLastError,"too many padding unsigned chars!");

        Sections[SectionsRead].Type = marker;

        // Read the length of the section.

        lh = fgetc(hFile);

        ll = fgetc(hFile);

        itemlen = (lh << 8) | ll;

        if (itemlen < 2){

            strcpy(m_szLastError,"invalid marker");

        Sections[SectionsRead].Size = itemlen;

        Data = (unsigned char *)malloc(itemlen);

        if (Data == NULL){

            strcpy(m_szLastError,"Could not allocate memory");

        Sections[SectionsRead].Data = Data;

        // Store first two pre-read unsigned chars.

        Data[0] = (unsigned char)lh;

        Data[1] = (unsigned char)ll;

        got = fread(Data+2, 1, itemlen-2,hFile); // Read the whole section.

        if (got != itemlen-2){

            strcpy(m_szLastError,"Premature end of file?");

        SectionsRead += 1;

        switch(marker){

            case M_SOS:   // stop before hitting compressed data 

                // If reading entire image is requested, read the rest of the data.

                /*if (ReadMode & READ_IMAGE){

                    int cp, ep, size;

                    // Determine how much file is left.

                    cp = ftell(infile);

                    fseek(infile, 0, SEEK_END);

                    ep = ftell(infile);

                    fseek(infile, cp, SEEK_SET);

                    size = ep-cp;

                    Data = (uchar *)malloc(size);

                    if (Data == NULL){

                        strcpy(m_szLastError,"could not allocate data for entire image");

                        return 0;

                    }

                    got = fread(Data, 1, size, infile);

                    if (got != size){

                        strcpy(m_szLastError,"could not read the rest of the image");

                    Sections[SectionsRead].Data = Data;

                    Sections[SectionsRead].Size = size;

                    Sections[SectionsRead].Type = PSEUDO_IMAGE_MARKER;

                    SectionsRead ++;

                    HaveAll = 1;

                }*/

                return 1;

            case M_EOI:   // in case it's a tables-only JPEG stream

                printf("No image in jpeg!\n");

            case M_COM: // Comment section

                if (HaveCom){

                    // Discard this section.

                    free(Sections[--SectionsRead].Data);

                    Sections[SectionsRead].Data=0;

                }else{

                    process_COM(Data, itemlen);

                    HaveCom = 1;

                }

                break;

            case M_JFIF:

                // Regular jpegs always have this tag, exif images have the exif

                // marker instead, althogh ACDsee will write images with both markers.

                // this program will re-create this marker on absence of exif marker.

                // hence no need to keep the copy from the file.

                free(Sections[--SectionsRead].Data);

                Sections[SectionsRead].Data=0;

            case M_EXIF:

                // Seen files from some 'U-lead' software with Vivitar scanner

                // that uses marker 31 for non exif stuff.  Thus make sure 

                // it says 'Exif' in the section before treating it as exif.

                if (memcmp(Data+2, "Exif", 4) == 0){

                    m_exifinfo->IsExif = process_EXIF((unsigned char *)Data+2, itemlen);

            case M_SOF0: 

            case M_SOF1: 

            case M_SOF2: 

            case M_SOF3: 

            case M_SOF5: 

            case M_SOF6: 

            case M_SOF7: 

            case M_SOF9: 

            case M_SOF10:

            case M_SOF11:

            case M_SOF13:

            case M_SOF14:

            case M_SOF15:

                process_SOFn(Data, marker);

            default:

                // Skip any other sections.

                //if (ShowTags) printf("Jpeg section marker 0x%02x size %d\n",marker, itemlen);

    return 1;

/*--------------------------------------------------------------------------

   Process a EXIF marker

   Describes all the drivel that most digital cameras include...

--------------------------------------------------------------------------*/

bool Cexif::process_EXIF(unsigned char * CharBuf, unsigned int length)

    m_exifinfo->FlashUsed = 0; 

    /* If it's from a digicam, and it used flash, it says so. */

    m_exifinfo->Comments[0] = '\0';  /* Initial value - null string */

    ExifImageWidth = 0;

    {   /* Check the EXIF header component */

        static const unsigned char ExifHeader[] = "Exif\0\0";

        if (memcmp(CharBuf+0, ExifHeader,6)){

            strcpy(m_szLastError,"Incorrect Exif header");

    if (memcmp(CharBuf+6,"II",2) == 0){

        MotorolaOrder = 0;

    }else{

        if (memcmp(CharBuf+6,"MM",2) == 0){

            MotorolaOrder = 1;

        }else{

            strcpy(m_szLastError,"Invalid Exif alignment marker.");

    /* Check the next two values for correctness. */

    if (Get16u(CharBuf+8) != 0x2a){

        strcpy(m_szLastError,"Invalid Exif start (1)");

    int FirstOffset = Get32u(CharBuf+10);

    if (FirstOffset < 8 || FirstOffset > 16){

        // I used to ensure this was set to 8 (website I used indicated its 8)

        // but PENTAX Optio 230 has it set differently, and uses it as offset. (Sept 11 2002)

        strcpy(m_szLastError,"Suspicious offset of first IFD value");

    unsigned char * LastExifRefd = CharBuf;

    /* First directory starts 16 unsigned chars in.  Offsets start at 8 unsigned chars in. */

    if (!ProcessExifDir(CharBuf+14, CharBuf+6, length-6, m_exifinfo, &LastExifRefd))

    /* This is how far the interesting (non thumbnail) part of the exif went. */

    // int ExifSettingsLength = LastExifRefd - CharBuf;

    /* Compute the CCD width, in milimeters. */

    if (m_exifinfo->FocalplaneXRes != 0){

        m_exifinfo->CCDWidth = (float)(ExifImageWidth * m_exifinfo->FocalplaneUnits / m_exifinfo->FocalplaneXRes);

// Get 16 bits motorola order (always) for jpeg header stuff.

int Cexif::Get16m(void * Short)

    return (((unsigned char *)Short)[0] << 8) | ((unsigned char *)Short)[1];

   Convert a 16 bit unsigned value from file's native unsigned char order

int Cexif::Get16u(void * Short)

    if (MotorolaOrder){

        return (((unsigned char *)Short)[0] << 8) | ((unsigned char *)Short)[1];

        return (((unsigned char *)Short)[1] << 8) | ((unsigned char *)Short)[0];

   Convert a 32 bit signed value from file's native unsigned char order

long Cexif::Get32s(void * Long)

        return  ((( char *)Long)[0] << 24) | (((unsigned char *)Long)[1] << 16)

              | (((unsigned char *)Long)[2] << 8 ) | (((unsigned char *)Long)[3] << 0 );

        return  ((( char *)Long)[3] << 24) | (((unsigned char *)Long)[2] << 16)

              | (((unsigned char *)Long)[1] << 8 ) | (((unsigned char *)Long)[0] << 0 );

   Convert a 32 bit unsigned value from file's native unsigned char order

unsigned long Cexif::Get32u(void * Long)

    return (unsigned long)Get32s(Long) & 0xffffffff;

/* Describes format descriptor */

static const int BytesPerFormat[] = {0,1,1,2,4,8,1,1,2,4,8,4,8};

#define NUM_FORMATS 12

#define FMT_BYTE       1 

#define FMT_STRING     2

#define FMT_USHORT     3

#define FMT_ULONG      4

#define FMT_URATIONAL  5

#define FMT_SBYTE      6

#define FMT_UNDEFINED  7

#define FMT_SSHORT     8

#define FMT_SLONG      9

#define FMT_SRATIONAL 10

#define FMT_SINGLE    11

#define FMT_DOUBLE    12

/* Describes tag values */

#define TAG_EXIF_VERSION      0x9000

#define TAG_EXIF_OFFSET       0x8769

#define TAG_INTEROP_OFFSET    0xa005

//2016年11月25日15:55:46 修改TAG_MAKE

#define TAG_MAKE              0x010F

#define TAG_COPYRIGHT         0x8298

#define TAG_MODEL             0x0110

#define TAG_ORIENTATION       0x0112

#define TAG_XRESOLUTION       0x011A

#define TAG_YRESOLUTION       0x011B

#define TAG_RESOLUTIONUNIT    0x0128

#define TAG_EXPOSURETIME      0x829A

#define TAG_FNUMBER           0x829D

#define TAG_SHUTTERSPEED      0x9201

#define TAG_APERTURE          0x9202

#define TAG_BRIGHTNESS        0x9203

#define TAG_MAXAPERTURE       0x9205

#define TAG_FOCALLENGTH       0x920A

#define TAG_DATETIME_ORIGINAL 0x9003

#define TAG_USERCOMMENT       0x9286

#define TAG_SUBJECT_DISTANCE  0x9206

#define TAG_FLASH             0x9209

#define TAG_FOCALPLANEXRES    0xa20E

#define TAG_FOCALPLANEYRES    0xa20F

#define TAG_FOCALPLANEUNITS   0xa210

#define TAG_EXIF_IMAGEWIDTH   0xA002

#define TAG_EXIF_IMAGELENGTH  0xA003

/* the following is added 05-jan-2001 vcs */

#define TAG_EXPOSURE_BIAS     0x9204

#define TAG_WHITEBALANCE      0x9208

#define TAG_METERING_MODE     0x9207

#define TAG_EXPOSURE_PROGRAM  0x8822

#define TAG_ISO_EQUIVALENT    0x8827

#define TAG_COMPRESSION_LEVEL 0x9102

#define TAG_THUMBNAIL_OFFSET  0x0201

#define TAG_THUMBNAIL_LENGTH  0x0202

   Process one of the nested EXIF directories.

bool Cexif::ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase, unsigned ExifLength,

                           EXIFINFO * const m_exifinfo, unsigned char ** const LastExifRefdP )

    int de;

    int NumDirEntries;

    unsigned ThumbnailOffset = 0;

    unsigned ThumbnailSize = 0;

    NumDirEntries = Get16u(DirStart);

    if ((DirStart+2+NumDirEntries*12) > (OffsetBase+ExifLength)){

        strcpy(m_szLastError,"Illegally sized directory");

    for (de=0;de<NumDirEntries;de++){

        int Tag, Format, Components;

        unsigned char * ValuePtr;

            /* This actually can point to a variety of things; it must be

               cast to other types when used.  But we use it as a unsigned char-by-unsigned char

               cursor, so we declare it as a pointer to a generic unsigned char here.

            */

        int BytesCount;

        unsigned char * DirEntry;

        DirEntry = DirStart+2+12*de;

        Tag = Get16u(DirEntry);

        Format = Get16u(DirEntry+2);

        Components = Get32u(DirEntry+4);

        if ((Format-1) >= NUM_FORMATS) {

            /* (-1) catches illegal zero case as unsigned underflows to positive large */

            strcpy(m_szLastError,"Illegal format code in EXIF dir");

        BytesCount = Components * BytesPerFormat[Format];

        if (BytesCount > 4){

            unsigned OffsetVal;

            OffsetVal = Get32u(DirEntry+8);

            /* If its bigger than 4 unsigned chars, the dir entry contains an offset.*/

            if (OffsetVal+BytesCount > ExifLength){

                /* Bogus pointer offset and / or unsigned charcount value */

                strcpy(m_szLastError,"Illegal pointer offset value in EXIF.");

            ValuePtr = OffsetBase+OffsetVal;

            /* 4 unsigned chars or less and value is in the dir entry itself */

            ValuePtr = DirEntry+8;

        if (*LastExifRefdP < ValuePtr+BytesCount){

            /* Keep track of last unsigned char in the exif header that was

               actually referenced.  That way, we know where the

               discardable thumbnail data begins.

            *LastExifRefdP = ValuePtr+BytesCount;

        /* Extract useful components of tag */

        switch(Tag){

            case TAG_MAKE:

                strncpy(m_exifinfo->CameraMake, (char*)ValuePtr, 31);

                //jsxyhelu添加的代碼

            case TAG_COPYRIGHT:

                strncpy(m_exifinfo->CopyRight, (char*)ValuePtr,MAX_COMMENT-1);

            case TAG_MODEL:

                strncpy(m_exifinfo->CameraModel, (char*)ValuePtr, 39);

            case TAG_EXIF_VERSION:

                strncpy(m_exifinfo->Version,(char*)ValuePtr, 4);

            case TAG_DATETIME_ORIGINAL:

                strncpy(m_exifinfo->DateTime, (char*)ValuePtr, 19);

            case TAG_USERCOMMENT:

                // Olympus has this padded with trailing spaces. Remove these first. 

                for (a=BytesCount;;){

                    a--;

                    if (((char*)ValuePtr)[a] == ' '){

                        ((char*)ValuePtr)[a] = '\0';

                    }else{

                        break;

                    if (a == 0) break;

                /* Copy the comment */

                if (memcmp(ValuePtr, "ASCII",5) == 0){

                    for (a=5;a<10;a++){

                        char c;

                        c = ((char*)ValuePtr)[a];

                        if (c != '\0' && c != ' '){

                            strncpy(m_exifinfo->Comments, (char*)ValuePtr+a, 199);

                            break;

                        }

                    strncpy(m_exifinfo->Comments, (char*)ValuePtr, 199);

            case TAG_FNUMBER:

                /* Simplest way of expressing aperture, so I trust it the most.

                   (overwrite previously computd value if there is one)

                   */

                m_exifinfo->ApertureFNumber = (float)ConvertAnyFormat(ValuePtr, Format);

            case TAG_APERTURE:

            case TAG_MAXAPERTURE:

                /* More relevant info always comes earlier, so only

                 use this field if we don't have appropriate aperture

                 information yet. 

                */

                if (m_exifinfo->ApertureFNumber == 0){

                    m_exifinfo->ApertureFNumber = (float)exp(ConvertAnyFormat(ValuePtr, Format)*log(2.0)*0.5);

            case TAG_BRIGHTNESS:

                m_exifinfo->Brightness = (float)ConvertAnyFormat(ValuePtr, Format);

            case TAG_FOCALLENGTH:

                /* Nice digital cameras actually save the focal length

                   as a function of how farthey are zoomed in. 

                m_exifinfo->FocalLength = (float)ConvertAnyFormat(ValuePtr, Format);

            case TAG_SUBJECT_DISTANCE:

                /* Inidcates the distacne the autofocus camera is focused to.

                   Tends to be less accurate as distance increases.

                m_exifinfo->Distance = (float)ConvertAnyFormat(ValuePtr, Format);

            case TAG_EXPOSURETIME:

                /* Simplest way of expressing exposure time, so I

                   trust it most.  (overwrite previously computd value

                   if there is one) 

                m_exifinfo->ExposureTime = 

                    (float)ConvertAnyFormat(ValuePtr, Format);

            case TAG_SHUTTERSPEED:

                /* More complicated way of expressing exposure time,

                   so only use this value if we don't already have it

                   from somewhere else.  

                if (m_exifinfo->ExposureTime == 0){

                    m_exifinfo->ExposureTime = (float)

                        (1/exp(ConvertAnyFormat(ValuePtr, Format)*log(2.0)));

            case TAG_FLASH:

                if ((int)ConvertAnyFormat(ValuePtr, Format) & 7){

                    m_exifinfo->FlashUsed = 1;

                    m_exifinfo->FlashUsed = 0;

            case TAG_ORIENTATION:

                m_exifinfo->Orientation = (int)ConvertAnyFormat(ValuePtr, Format);

                if (m_exifinfo->Orientation < 1 || m_exifinfo->Orientation > 8){

                    strcpy(m_szLastError,"Undefined rotation value");

                    m_exifinfo->Orientation = 0;

            case TAG_EXIF_IMAGELENGTH:

            case TAG_EXIF_IMAGEWIDTH:

                /* Use largest of height and width to deal with images

                   that have been rotated to portrait format.  

                a = (int)ConvertAnyFormat(ValuePtr, Format);

                if (ExifImageWidth < a) ExifImageWidth = a;

            case TAG_FOCALPLANEXRES:

                m_exifinfo->FocalplaneXRes = (float)ConvertAnyFormat(ValuePtr, Format);

            case TAG_FOCALPLANEYRES:

                m_exifinfo->FocalplaneYRes = (float)ConvertAnyFormat(ValuePtr, Format);

            case TAG_RESOLUTIONUNIT:

                switch((int)ConvertAnyFormat(ValuePtr, Format)){

                    case 1: m_exifinfo->ResolutionUnit = 1.0f; break; /* 1 inch */

                    case 2:    m_exifinfo->ResolutionUnit = 1.0f; break;

                    case 3: m_exifinfo->ResolutionUnit = 0.3937007874f;    break;  /* 1 centimeter*/

                    case 4: m_exifinfo->ResolutionUnit = 0.03937007874f;   break;  /* 1 millimeter*/

                    case 5: m_exifinfo->ResolutionUnit = 0.00003937007874f;  /* 1 micrometer*/

            case TAG_FOCALPLANEUNITS:

                    case 1: m_exifinfo->FocalplaneUnits = 1.0f; break; /* 1 inch */

                    case 2:    m_exifinfo->FocalplaneUnits = 1.0f; break;

                    case 3: m_exifinfo->FocalplaneUnits = 0.3937007874f;    break;  /* 1 centimeter*/

                    case 4: m_exifinfo->FocalplaneUnits = 0.03937007874f;   break;  /* 1 millimeter*/

                    case 5: m_exifinfo->FocalplaneUnits = 0.00003937007874f;  /* 1 micrometer*/

                // Remaining cases contributed by: Volker C. Schoech <schoech(at)gmx(dot)de>

            case TAG_EXPOSURE_BIAS:

                m_exifinfo->ExposureBias = (float) ConvertAnyFormat(ValuePtr, Format);

            case TAG_WHITEBALANCE:

                m_exifinfo->Whitebalance = (int)ConvertAnyFormat(ValuePtr, Format);

            case TAG_METERING_MODE:

                m_exifinfo->MeteringMode = (int)ConvertAnyFormat(ValuePtr, Format);

            case TAG_EXPOSURE_PROGRAM:

                m_exifinfo->ExposureProgram = (int)ConvertAnyFormat(ValuePtr, Format);

            case TAG_ISO_EQUIVALENT:

                m_exifinfo->ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format);

                if ( m_exifinfo->ISOequivalent < 50 ) m_exifinfo->ISOequivalent *= 200;

            case TAG_COMPRESSION_LEVEL:

                m_exifinfo->CompressionLevel = (int)ConvertAnyFormat(ValuePtr, Format);

            case TAG_XRESOLUTION:

                m_exifinfo->Xresolution = (float)ConvertAnyFormat(ValuePtr, Format);

            case TAG_YRESOLUTION:

                m_exifinfo->Yresolution = (float)ConvertAnyFormat(ValuePtr, Format);

            case TAG_THUMBNAIL_OFFSET:

                ThumbnailOffset = (unsigned)ConvertAnyFormat(ValuePtr, Format);

            case TAG_THUMBNAIL_LENGTH:

                ThumbnailSize = (unsigned)ConvertAnyFormat(ValuePtr, Format);

        if (Tag == TAG_EXIF_OFFSET || Tag == TAG_INTEROP_OFFSET){

            unsigned char * SubdirStart;

            SubdirStart = OffsetBase + Get32u(ValuePtr);

            if (SubdirStart < OffsetBase || 

                SubdirStart > OffsetBase+ExifLength){

                strcpy(m_szLastError,"Illegal subdirectory link");

            ProcessExifDir(SubdirStart, OffsetBase, ExifLength, m_exifinfo, LastExifRefdP);

            continue;

    {

        /* In addition to linking to subdirectories via exif tags,

           there's also a potential link to another directory at the end

           of each directory.  This has got to be the result of a

           committee!  

        */

        unsigned char * SubdirStart;

        unsigned Offset;

        Offset = Get16u(DirStart+2+12*NumDirEntries);

        if (Offset){

            SubdirStart = OffsetBase + Offset;

            if (SubdirStart < OffsetBase 

                || SubdirStart > OffsetBase+ExifLength){

    if (ThumbnailSize && ThumbnailOffset){

        if (ThumbnailSize + ThumbnailOffset <= ExifLength){

            /* The thumbnail pointer appears to be valid.  Store it. */

            m_exifinfo->ThumbnailPointer = OffsetBase + ThumbnailOffset;

            m_exifinfo->ThumbnailSize = ThumbnailSize;

   Evaluate number, be it int, rational, or float from directory.

double Cexif::ConvertAnyFormat(void * ValuePtr, int Format)

    double Value;

    Value = 0;

    switch(Format){

        case FMT_SBYTE:     Value = *(signed char *)ValuePtr;  break;

        case FMT_BYTE:      Value = *(unsigned char *)ValuePtr;        break;

        case FMT_USHORT:    Value = Get16u(ValuePtr);          break;

        case FMT_ULONG:     Value = Get32u(ValuePtr);          break;

        case FMT_URATIONAL:

        case FMT_SRATIONAL: 

            {

                int Num,Den;

                Num = Get32s(ValuePtr);

                Den = Get32s(4+(char *)ValuePtr);

                if (Den == 0){

                    Value = 0;

                    Value = (double)Num/Den;

        case FMT_SSHORT:    Value = (signed short)Get16u(ValuePtr);  break;

        case FMT_SLONG:     Value = Get32s(ValuePtr);                break;

        /* Not sure if this is correct (never seen float used in Exif format)

         */

        case FMT_SINGLE:    Value = (double)*(float *)ValuePtr;      break;

        case FMT_DOUBLE:    Value = *(double *)ValuePtr;             break;

    return Value;

void Cexif::process_COM (const unsigned char * Data, int length)

    int ch;

    char Comment[MAX_COMMENT+1];

    int nch;

    nch = 0;

    if (length > MAX_COMMENT) length = MAX_COMMENT; // Truncate if it won't fit in our structure.

    for (a=2;a<length;a++){

        ch = Data[a];

        if (ch == '\r' && Data[a+1] == '\n') continue; // Remove cr followed by lf.

        if ((ch>=0x20) || ch == '\n' || ch == '\t'){

            Comment[nch++] = (char)ch;

            Comment[nch++] = '?';

    Comment[nch] = '\0'; // Null terminate

    //if (ShowTags) printf("COM marker comment: %s\n",Comment);

    strcpy(m_exifinfo->Comments,Comment);

void Cexif::process_SOFn (const unsigned char * Data, int marker)

    int data_precision, num_components;

    data_precision = Data[2];

    m_exifinfo->Height = Get16m((void*)(Data+3));

    m_exifinfo->Width = Get16m((void*)(Data+5));

    num_components = Data[7];

    if (num_components == 3){

        m_exifinfo->IsColor = 1;

        m_exifinfo->IsColor = 0;

    m_exifinfo->Process = marker;

    //if (ShowTags) printf("JPEG image is %uw * %uh, %d color components, %d bits per sample\n",

    //               ImageInfo.Width, ImageInfo.Height, num_components, data_precision);

//#include "stdafx.h"

//exif 寫入函數

int Cexif::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)

    UINT num= 0;

    UINT size= 0;

    ImageCodecInfo* pImageCodecInfo= NULL;

    GetImageEncodersSize(&num, &size);

    if(size== 0)

        return -1;

    pImageCodecInfo= (ImageCodecInfo*)(malloc(size));

    if(pImageCodecInfo== NULL)

    GetImageEncoders(num, size, pImageCodecInfo);

    for(UINT j=0; j< num; ++j)

        if(wcscmp(pImageCodecInfo[j].MimeType, format)== 0)

        {

            *pClsid= pImageCodecInfo[j].Clsid;

            free(pImageCodecInfo);

            return j;

    free(pImageCodecInfo);

    return -1;

// 從記憶體加載圖檔,失敗傳回NULL

Bitmap* Cexif::LoadBitmapFromMemory(const void* memory, DWORD size)

    Bitmap* bmp = NULL;

    IStream* stream = NULL;

    if (CreateStreamOnHGlobal(NULL, TRUE, &stream) == S_OK)

        ULARGE_INTEGER uli;

        uli.QuadPart = size;

        stream->SetSize(uli);

        if (stream->Write(memory, size, NULL) == S_OK)

            bmp = new Bitmap(stream);

        stream->Release();

    return bmp;

// 從檔案加載圖檔,不獨占檔案,失敗傳回NULL

Gdiplus::Bitmap* Cexif::LoadBitmapFromFile(const TCHAR* file_name)

    HANDLE file_handle = CreateFile(file_name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    if (file_handle != INVALID_HANDLE_VALUE)

        DWORD temp = 0;

        DWORD file_size = GetFileSize(file_handle, &temp);

        if (file_size && !temp)  // 不處理大于4G的檔案

            // 将圖檔檔案讀到記憶體後,再從記憶體建立Bitmap

            unsigned char* buffer = new unsigned char[file_size];

            if (ReadFile(file_handle, buffer, file_size, &temp, NULL))

                bmp = LoadBitmapFromMemory(buffer, temp);

            delete [] buffer;

        CloseHandle(file_handle);

//寫入exif資訊到圖檔中去,目前隻支援jpeg格式

//傳回 0為正常 -1為異常

//各種tchar wchar cstring的轉換,技巧較多.

int Cexif::WriteExif2Image(CString strFileName, int PropertyTag, CString pValue)

    GdiplusStartupInput gdiplusStartupInput;

    ULONG_PTR gdiplusToken;

    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    Status stat;

    CLSID  clsid;

    char* propertyValue = new char[100];

    strcpy(propertyValue,pValue);

    Bitmap* bitmap = LoadBitmapFromFile(strFileName);

    PropertyItem* propertyItem = new PropertyItem;

    GetEncoderClsid(L"image/jpeg", &clsid);

    propertyItem->id = PropertyTag;//給Tag指派

    propertyItem->length = 255;  

    propertyItem->type = PropertyTagTypeASCII; 

    propertyItem->value = propertyValue;

    bitmap->SetPropertyItem(propertyItem);

    WCHAR wcharbuf[100];

    CStringW strWide = CT2W(strFileName);

    wcscpy(wcharbuf,strWide);

    stat = bitmap->Save(wcharbuf, &clsid, NULL);

    if(stat != Ok)

    delete propertyItem;

    delete bitmap;

    GdiplusShutdown(gdiplusToken);

    return 0;

目前方向:圖像拼接融合、圖像識别

聯系方式:[email protected]

繼續閱讀