GOEXIF读取和写入EXIF信息

2023-05-16

最新版本的gexif,直接基于gdi+实现了exif信息的读取和写入,代码更清晰。

/*
 * File:	gexif.h
 * Purpose:	cpp EXIF reader
 * 3/2/2017 <jsxyhelu@foxmail.com>
 * 基于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 <jsxyhelu@foxmail.com>
 * 基于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 <ing.davide.pizzolato@libero.it>
 * 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){
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信息失败!");
*/
#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>
#include <stdio.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   HeightWidth;
    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:
    EXIFINFOm_exifinfo;
    char m_szLastError[256];
    Cexif(EXIFINFOinfo = NULL);
    ~Cexif();
    bool DecodeExif(FILEhFile);
protected:
    bool process_EXIF(unsigned char * CharBufunsigned int length);
    void process_COM (const unsigned char * Dataint length);
    void process_SOFn (const unsigned char * Dataint marker);
    int Get16u(void * Short);
    int Get16m(void * Short);
    long Get32s(void * Long);
    unsigned long Get32u(void * Long);
    double ConvertAnyFormat(void * ValuePtrint Format);
    bool ProcessExifDir(unsigned char * DirStartunsigned char * OffsetBaseunsigned ExifLength,
                           EXIFINFO * const pInfounsigned char ** const LastExifRefdP);
    int ExifImageWidth;
    int MotorolaOrder;
    Section_t Sections[MAX_SECTIONS];
    int SectionsRead;
    bool freeinfo;
public:
    int GetEncoderClsid(const WCHARformatCLSIDpClsid);
    Gdiplus::BitmapLoadBitmapFromMemory(const voidmemoryDWORD size);
    Gdiplus::BitmapLoadBitmapFromFile(const TCHARfile_name);
    int WriteExif2Image(CString strFileNameint PropertyTagCString propertyValue);
};
 
#endif
 
/*
 * File:    exif.h
 * Purpose:    cpp EXIF reader
 * 16/Mar/2003 <ing.davide.pizzolato@libero.it>
 * based on jhead-1.8 by Matthias Wandel <mwandel(at)rim(dot)net>
 */
//2016年11月25日 jsxyhelu 修改添加版权信息
//2017年1月21日  jsxyhelu 添加写入exif的功能
#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].Datafree(Sections[i].Data);
    if (freeinfodelete 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,lhgot;
        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!");
            return 0;
        }
 
        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");
            return 0;
        }
 
        Sections[SectionsRead].Size = itemlen;
 
        Data = (unsigned char *)malloc(itemlen);
        if (Data == NULL){
            strcpy(m_szLastError,"Could not allocate memory");
            return 0;
        }
        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?");
            return 0;
        }
        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");
                        return 0;
                    }
 
                    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");
                return 0;
 
            case M_COM// Comment section
                if (HaveCom){
                    // Discard this section.
                    free(Sections[--SectionsRead].Data);
                    Sections[SectionsRead].Data=0;
                }else{
                    process_COM(Dataitemlen);
                    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;
                break;
 
            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);
                }else{
                    // Discard this section.
                    free(Sections[--SectionsRead].Data);
                    Sections[SectionsRead].Data=0;
                }
                break;
 
            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(Datamarker);
                break;
            default:
                // Skip any other sections.
                //if (ShowTags) printf("Jpeg section marker 0x%02x size %d\n",marker, itemlen);
                break;
        }
    }
    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");
            return 0;
        }
    }
 
    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.");
            return 0;
        }
    }
 
    /* Check the next two values for correctness. */
    if (Get16u(CharBuf+8) != 0x2a){
        strcpy(m_szLastError,"Invalid Exif start (1)");
        return 0;
    }
 
    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");
        return 0;
    }
 
    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))
        return 0;
 
    /* 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);
    }
 
    return 1;
}
//--------------------------------------------------------------------------
// 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];
    }else{
        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)
{
    if (MotorolaOrder){
        return  ((( char *)Long)[0] << 24) | (((unsigned char *)Long)[1] << 16)
              | (((unsigned char *)Long)[2] << 8 ) | (((unsigned char *)Long)[3] << 0 );
    }else{
        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_exifinfounsigned char ** const LastExifRefdP )
{
    int de;
    int a;
    int NumDirEntries;
    unsigned ThumbnailOffset = 0;
    unsigned ThumbnailSize = 0;
 
    NumDirEntries = Get16u(DirStart);
 
    if ((DirStart+2+NumDirEntries*12) > (OffsetBase+ExifLength)){
        strcpy(m_szLastError,"Illegally sized directory");
        return 0;
    }
 
    for (de=0;de<NumDirEntries;de++){
        int TagFormatComponents;
        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");
            return 0;
        }
 
        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.");
                return 0;
            }
            ValuePtr = OffsetBase+OffsetVal;
        }else{
            /* 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);
                break;
                //jsxyhelu添加的代码
            case TAG_COPYRIGHT:
                strncpy(m_exifinfo->CopyRight, (char*)ValuePtr,MAX_COMMENT-1);
                break;
            case TAG_MODEL:
                strncpy(m_exifinfo->CameraModel, (char*)ValuePtr, 39);
                break;
 
            case TAG_EXIF_VERSION:
                strncpy(m_exifinfo->Version,(char*)ValuePtr, 4);
                break;
 
            case TAG_DATETIME_ORIGINAL:
                strncpy(m_exifinfo->DateTime, (char*)ValuePtr, 19);
                break;
 
            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;
                        }
                    }
                    
                }else{
                    strncpy(m_exifinfo->Comments, (char*)ValuePtr, 199);
                }
                break;
 
            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(ValuePtrFormat);
                break;
 
            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(ValuePtrFormat)*log(2.0)*0.5);
                }
                break;
 
            case TAG_BRIGHTNESS:
                m_exifinfo->Brightness = (float)ConvertAnyFormat(ValuePtrFormat);
                break;
 
            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(ValuePtrFormat);
                break;
 
            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(ValuePtrFormat);
                break;
 
            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(ValuePtrFormat);
                break;
 
            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(ValuePtrFormat)*log(2.0)));
                }
                break;
 
            case TAG_FLASH:
                if ((int)ConvertAnyFormat(ValuePtrFormat) & 7){
                    m_exifinfo->FlashUsed = 1;
                }else{
                    m_exifinfo->FlashUsed = 0;
                }
                break;
 
            case TAG_ORIENTATION:
                m_exifinfo->Orientation = (int)ConvertAnyFormat(ValuePtrFormat);
                if (m_exifinfo->Orientation < 1 || m_exifinfo->Orientation > 8){
                    strcpy(m_szLastError,"Undefined rotation value");
                    m_exifinfo->Orientation = 0;
                }
                break;
 
            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(ValuePtrFormat);
                if (ExifImageWidth < aExifImageWidth = a;
                break;
 
            case TAG_FOCALPLANEXRES:
                m_exifinfo->FocalplaneXRes = (float)ConvertAnyFormat(ValuePtrFormat);
                break;
 
            case TAG_FOCALPLANEYRES:
                m_exifinfo->FocalplaneYRes = (float)ConvertAnyFormat(ValuePtrFormat);
                break;
 
            case TAG_RESOLUTIONUNIT:
                switch((int)ConvertAnyFormat(ValuePtrFormat)){
                    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*/
                }
                break;
 
            case TAG_FOCALPLANEUNITS:
                switch((int)ConvertAnyFormat(ValuePtrFormat)){
                    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*/
                }
                break;
 
                // Remaining cases contributed by: Volker C. Schoech <schoech(at)gmx(dot)de>
 
            case TAG_EXPOSURE_BIAS:
                m_exifinfo->ExposureBias = (floatConvertAnyFormat(ValuePtrFormat);
                break;
 
            case TAG_WHITEBALANCE:
                m_exifinfo->Whitebalance = (int)ConvertAnyFormat(ValuePtrFormat);
                break;
 
            case TAG_METERING_MODE:
                m_exifinfo->MeteringMode = (int)ConvertAnyFormat(ValuePtrFormat);
                break;
 
            case TAG_EXPOSURE_PROGRAM:
                m_exifinfo->ExposureProgram = (int)ConvertAnyFormat(ValuePtrFormat);
                break;
 
            case TAG_ISO_EQUIVALENT:
                m_exifinfo->ISOequivalent = (int)ConvertAnyFormat(ValuePtrFormat);
                if ( m_exifinfo->ISOequivalent < 50 ) m_exifinfo->ISOequivalent *= 200;
                break;
 
            case TAG_COMPRESSION_LEVEL:
                m_exifinfo->CompressionLevel = (int)ConvertAnyFormat(ValuePtrFormat);
                break;
 
            case TAG_XRESOLUTION:
                m_exifinfo->Xresolution = (float)ConvertAnyFormat(ValuePtrFormat);
                break;
            case TAG_YRESOLUTION:
                m_exifinfo->Yresolution = (float)ConvertAnyFormat(ValuePtrFormat);
                break;
 
            case TAG_THUMBNAIL_OFFSET:
                ThumbnailOffset = (unsigned)ConvertAnyFormat(ValuePtrFormat);
                break;
 
            case TAG_THUMBNAIL_LENGTH:
                ThumbnailSize = (unsigned)ConvertAnyFormat(ValuePtrFormat);
                break;
 
        }
 
        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");
                return 0;
            }
            ProcessExifDir(SubdirStartOffsetBaseExifLengthm_exifinfoLastExifRefdP);
            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){
                strcpy(m_szLastError,"Illegal subdirectory link");
                return 0;
            }
            ProcessExifDir(SubdirStartOffsetBaseExifLengthm_exifinfoLastExifRefdP);
        }
    }
 
 
    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;
        }
    }
 
    return 1;
}
/*--------------------------------------------------------------------------
   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;
                }else{
                    Value = (double)Num/Den;
                }
                break;
            }
 
        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;
    int a;
 
    nch = 0;
 
    if (length > MAX_COMMENTlength = 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;
        }else{
            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_precisionnum_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;
    }else{
        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 WCHARformatCLSIDpClsid)
{
    UINT num= 0;
    UINT size= 0;
    ImageCodecInfopImageCodecInfoNULL;
    GetImageEncodersSize(&num, &size);
    if(size== 0)
    {
        return -1;
    }
    pImageCodecInfo= (ImageCodecInfo*)(malloc(size));
    if(pImageCodecInfo== NULL)
    {
        return -1;
    }
    GetImageEncoders(numsizepImageCodecInfo);
    for(UINT j=0; jnum; ++j)
    {
        if(wcscmp(pImageCodecInfo[j].MimeTypeformat)== 0)
        {
            *pClsidpImageCodecInfo[j].Clsid;
            free(pImageCodecInfo);
            return j;
        }
    }
    free(pImageCodecInfo);
    return -1;
}
 
// 从内存加载图片,失败返回NULL
BitmapCexif::LoadBitmapFromMemory(const voidmemoryDWORD size)
{
    Bitmapbmp = NULL;
    IStreamstream = NULL;
    if (CreateStreamOnHGlobal(NULLTRUE, &stream) == S_OK)
    {
        ULARGE_INTEGER uli;
        uli.QuadPart = size;
        stream->SetSize(uli);
        if (stream->Write(memorysizeNULL) == S_OK)
            bmp = new Bitmap(stream);
        stream->Release();
    }
    return bmp;
}
 
// 从文件加载图片,不独占文件,失败返回NULL
Gdiplus::BitmapCexif::LoadBitmapFromFile(const TCHARfile_name)
{
    Bitmapbmp = NULL;
    HANDLE file_handle = CreateFile(file_nameGENERIC_READFILE_SHARE_READNULLOPEN_EXISTINGFILE_ATTRIBUTE_NORMALNULL);
    if (file_handle != INVALID_HANDLE_VALUE)
    {
        DWORD temp = 0;
        DWORD file_size = GetFileSize(file_handle, &temp);
        if (file_size && !temp)  // 不处理大于4G的文件
        {
            // 将图片文件读到内存后,再从内存创建Bitmap
            unsigned charbuffer = new unsigned char[file_size];
            if (ReadFile(file_handlebufferfile_size, &tempNULL))
                bmp = LoadBitmapFromMemory(buffertemp);
            delete [] buffer;
        }
        CloseHandle(file_handle);
    }
    return bmp;
}
 
//写入exif信息到图片中去,目前只支持jpeg格式
//返回 0为正常 -1为异常
//各种tchar wchar cstring的转换,技巧较多.
int Cexif::WriteExif2Image(CString strFileName, int PropertyTagCString pValue)
{
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInputNULL);
    Status stat;
    CLSID  clsid;
    
    charpropertyValue = new char[100];
    strcpy(propertyValue,pValue);
    
    Bitmapbitmap = LoadBitmapFromFile(strFileName);
    PropertyItempropertyItem = 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, &clsidNULL);
 
    if(stat != Ok)
        return -1;
    delete propertyItem;
    delete bitmap;
    GdiplusShutdown(gdiplusToken);
    return 0;
}
 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

GOEXIF读取和写入EXIF信息 的相关文章

  • [Python开发] 使用python读取图片的EXIF

    使用python读取图片的EXIF 方法 使用PIL Image读取图片的EXIF 使用https pypi python org pypi ExifRead 读取图片的EXIF xff0c 得到EXIF标签 xff08 dict类型 xf
  • python读取与保存图片的exif信息

    图片的exif文件格式中保存了很多信息 xff0c 比如GPS经纬度 xff0c 高度 xff0c 焦距等信息 在图片的属性中可以看到这些信息 xff1a 我们可以使用python来进行exif数据的读取和保存 1 首先安装piexif p
  • JPG图像exif和XPM信息中GPS数据姿态数据航向角数据的提取

    JPG图像的编码相关内容太多不在多说了大家随手能查很多资料 今天重点说说图像数据中的GPS信息以及飞机 相机姿态角度数据提取 JPG作为复杂的图像数据很多人都知道存在一个叫做EXIF的数据规范 xff0c 在这个数据规范中 xff0c 包含
  • 设置 Android 照片 EXIF 方向

    我编写了一个以编程方式捕获照片的 Android 活动 我想将图像保存为具有正确 EXIF 方向数据的 JPEG 就像本机 Android 相机应用程序自动执行的那样 这是实际拍照的方法 我删除了 try catch 块 private v
  • 使用 PHP 从 JPG 中删除 EXIF 数据

    有没有办法使用 PHP 从 JPG 中删除 EXIF 数据 我听说过 PEL 但我希望有一种更简单的方法 我正在上传将在线显示的图像 并且希望删除 EXIF 数据 Thanks 编辑 我不 无法安装 ImageMagick Use gd在您
  • 如何获取从 FileProvider 类获取的图像文件的方向?

    背景 针对API 24或更高版本 开发人员需要使用FileProvider 或他们自己的ContentProvider 而不是使用简单的 Uri fromFile 命令 以便让其他应用程序访问该应用程序的文件 问题 我尝试使用以下代码打开相
  • 使用 ImageMagick 检测 EXIF 方向并旋转图像

    佳能数码单反相机似乎可以横向保存照片并使用exif orientation进行旋转 问题 如何使用 imagemagick 使用 exif 方向数据将图像重新保存到预期方向 以便不再需要 exif 数据以正确的方向显示 Use the 自动
  • 调整大小时使用 PIL 保留图像的 exif 数据(创建缩略图)

    当我尝试使用 PIL 调整图像大小 缩略图 时 exif 数据丢失 我必须做什么才能保留缩略图中的 exif 数据 当我搜索相同内容时 得到了一些链接 但似乎没有一个有效 from PIL import Image import Strin
  • 如何在Rails中检索图像的EXIF信息

    我正在使用 Rails 回形针在我的页面中显示图像 我想知道如何检索图像的 EXIF 信息 如尺寸 相机型号 高度 宽度等 有人可以帮我吗 谢谢 你给了吗exifr https github com remvee exifr 宝石尝试一下吗
  • 如何将 exif 元数据写入图像(不是相机胶卷,只是 UIImage 或 JPEG)

    我知道如何使用 ALAssets 保存元数据 但是 我想保存图像 或将其上传到某个地方 且 exif 完好无损 我有 exif 数据作为 NSDictionary 但是我怎样才能将它正确地注入到 UIImage 或者可能是 NSData J
  • 在 .NET 中,如何在不重新压缩 JPEG 的情况下编写 Exif 标头?

    我有一个JPEG http en wikipedia org wiki JPEG我想要设置的图像Exif http en wikipedia org wiki Exchangeable image file format标题 特别是作者 在
  • 在三星 Android 设备上拍摄的图库图像始终为横向,即使以纵向拍摄也是如此

    使用以下代码从图库中选取图像 Intent intent new Intent Intent ACTION GET CONTENT intent setType intent addCategory Intent CATEGORY OPEN
  • 在 C# 中从图像的 EXIF 获取 GPS 数据

    我正在开发一个系统 允许使用 ASP NET C 将图像上传到服务器 我正在处理图像 一切正常 我设法找到一种方法来读取创建日期 EXIF 数据并将其解析为日期时间 这也很好用 我现在正在尝试从 EXIF 读取 GPS 数据 我想捕获纬度和
  • 如何从 mp4 视频中删除或编辑 Exif?

    我用 Samsung Galaxy II 录制了一个全高清视频 当我将其上传到 YouTube 时 我发现它变成了 90 度 就像纵向布局 1080x1920 而不是 1920x1080 我找到了问题的原因 YouTube 正在读取视频元数
  • Python:从图像中删除 Exif 信息

    为了减小网站中使用的图像大小 我将质量降低到 80 85 这在一定程度上大大减小了图像尺寸 为了在不影响质量的情况下进一步减小尺寸 我的朋友指出 来自相机的原始图像有很多称为 Exif 信息的元数据 由于网站中的图像不需要保留此 Exif
  • 小米Exif方向标签错误

    我在用着ExifInterface对于通过检测方向标签从图库中选取的旋转图像 它正在工作 最近我在小米9 SE上测试了应用程序 发现相机拍摄的照片有方向标签8 旋转270 但照片方向是正确的 不需要旋转 为什么方向标签错误 如何找到正确的旋
  • 在 Android 应用程序中编辑/添加 IPTC 元数据

    我看过许多其他类似的问题 但似乎没有一个有准确的答案 我正在开发一个可处理大量图像的 Android 应用程序 我希望通过编辑 IPTC 关键字标签 或其他适当标签 的值来向图像添加信息 我在用元数据提取器 http drewnoakes
  • 可以用js在客户端读取图片的Exif数据吗?

    我有一个小 大 问题 我使用agile uploader上传多个图像 这个组件调整了所有图片的大小 它工作得很好 但是这样做我丢失了exif数据 我可以使用JS在客户端读取exif数据吗 鉴于这不是同一个名称域 是的 有一个新图书馆exif
  • 如何应用 EXIF 定位

    我注意到并不是每个浏览器都应用 EXIF 方向 我的手机上的 Chrome 不应用 EXIF 方向 但 Safari 手机则应用 那么既然它不是标准的 那么如何在 Safari 上应用 EXIF 方向而不应用两次呢 另外我想知道是否可以在客
  • 使用Android Camera API,拍摄照片的方向始终未定义

    我使用相机API 拍摄的照片总是旋转90度 我想旋转它 所以首先我想知道图片的方向 这一点我被卡住了 我总是以两种方式得到未定义的方向 这是代码 Override public void onPictureTaken byte data C

随机推荐

  • Got timeout reading communication packets解决方法

    Got timeout reading communication packets解决方法 http www th7 cn db mysql 201702 225243 shtml Note Aborted connection xxxx
  • java在数字前面自动补零的方法

    将元数据前补零 xff0c 补后的总长度为指定的长度 xff0c 以字符串的形式返回 64 param sourceDate 64 param formatLength 64 return 重组后的数据 public static Stri
  • MariaDB命令详解

    MariaDB命令详解 mysql客户端程序 xff1a 命令行交互式客户端程序 xff1a mysql mysql mysql OPTIONS database mysql help 配置文件的读取次序 xff1a etc mysql m
  • python文本文件操作诗句给上一句输出下一句_[Python] 自动化办公 定制微信每日一句诗...

    转载请注明 xff1a 陈熹 chenx6542 64 foxmail com 简书号 xff1a 半为花间酒 若公众号内转载请联系公众号 xff1a 早起Python 这篇文章能学到的主要内容 xff1a 利用 喵提醒 推送消息至微信 x
  • 我的年终总结,作为研发,在2018年都有哪些进步、收获与成长?

    2018 结束了 部门开会总结了过去的工作与未来的展望 xff0c 也是个不错的机会去回顾 审视 思考自己的 2018 年 玄难说过人与人的差距来自于思考与总结 xff0c 我深深地认同这一点 我也把自己的一部分思考写下来 xff0c 在公
  • Arch无法更新和安装软件

    今天用户yay明来安装htop时 xff0c 一直卡死在以下输出内容出 xff1a db lck is present Waiting 更新软件源也出现以下故障 xff1a sudo pacman Syy sudo ivan 的密码 xff
  • 云主机的硬盘IO性能比较

    测试方式 因为工作等需要 xff0c 手里有一堆云主机 xff0c 前几天忽然想到来测试对比一下各家的IO性能如何 测试方法不严谨 xff0c 仅供参考 测试工具为fio xff0c 测试命令如下 xff08 以sync方式为例 xff09
  • 定制小狼豪(五笔+拼音)输入法

    小狼毫输入法是一个给程序员折腾的输入法 xff0c 可以自由定制 rime是一个输入法框架 xff0c 小狼毫是在windows平台上的名称 相关教程和下载 xff1a https jianguoyun com p DRylhFMQv 3j
  • 10.12 firewalld和netfilter

    2019独角兽企业重金招聘Python工程师标准 gt gt gt Linux防火墙 netfilter selinux临时关闭 setenforce 0selinux永久关闭 vi etc selinux configcentos7之前使
  • 使用 build-simple-cdd 快速定制 Debian 安装盘

    为什么80 的码农都做不了架构师 xff1f gt gt gt 官方推荐了 build simple cdd 来 定制Debian安装盘 sudo apt get y install simple cdd xorriso 创建基础目录和文件
  • PostSharp-5.0.26安装包_KeyGen发布_支持VS2017

    PostSharp 5 0 26安装包 KeyGen发布 支持VS2017 请低调使用 PostSharp安装及注册步骤截图 rar 请把浏览器主页设置为以下地址支持本人 https www duba com un 454974 16968
  • centos7 Firewall防火墙开启80端口

    为什么80 的码农都做不了架构师 xff1f gt gt gt centos7 默认是FirewallD 提供支持网络 防火墙区域 zone 定义网络链接以及接口安全等级的动态防火墙管理工具 xff0c 利用FirewallD开启80端口操
  • 安卓6.0系统权限问题android.permission.WRITE_SETTINGS

    关于 Android permission WRITE SETTINGS 的权限 xff0c 申请 xff0c 判断 精简代码如下 xff1a if Build VERSION SDK INT gt 61 Build VERSION COD
  • js match函数注意

    match函数 String prototype match 参数 regexp 返回 返回包含所有匹配的数组 xff0c 如果匹配失败返回Null 数组第一项是整段字符串的匹配 xff0c 第二项至以后都是捕获匹配 注意 需要注意的是 x
  • VR发展简史

    最初的起源 事实上 xff0c 虚拟现实由来已久 xff0c 其概念最早被提及应该追溯到Aldous Huxley xff08 阿道司 赫胥黎 xff09 1932年推出的长篇小说 美丽新世界 xff0c 这篇小说以26世纪为背景 xff0
  • crontab 每月执行一次怎么写? - Linux系统管理 - ChinaUnix.net -

    crontab 每月执行一次怎么写 xff1f Linux系统管理 ChinaUnix net 0 19 1 bin sh xxx sh 每个月的1号的19点钟运行xxx sh 分钟 小时 日子可以更改 xff0c 后两项为 就是month
  • SparkStreaming结合Kafka使用

    spark自带的example中就有streaming结合kafka使用的案例 xff1a SPARK HOME examples src main scala org apache spark examples streaming Kaf
  • grails一对多双向关联

    前面分享了一些学习grails的心得 xff0c 可是grails的知识还远不止这些 xff0c 这次整理了一点有关grails一对多双向关联关系的知识 我认为这样的关联用的地方太多了 xff0c 这次准备的样例是城市和区域的相关样例 1
  • IAR EWAR 内联汇编 调用外部函数 Error[Og005], Error[Og006]

    How do I call a C function in another module from inline assembler in IAR EWARM I have a bit of assembly in a hard fault
  • GOEXIF读取和写入EXIF信息

    最新版本的gexif xff0c 直接基于gdi 43 实现了exif信息的读取和写入 xff0c 代码更清晰 File gexif h Purpose cpp EXIF reader 3 2 2017 lt jsxyhelu 64 fox