Cで書かれたexif関係のライブラリをC++ Builder CEで使う試み

具体的な手順を示しますが、移植先はVCLかFMXのどちらでも可

前記事で紹介した

ここのexif.cexif.hを移植します。(ま、移植という程ではないですが…..。)同化(assimilate)というべきかな、筆者が付け加えた部分はほぼゼロです。作者と連絡が付いて、許諾が得られれば変更後のソースも提示させてもらいます。

まずは、exif.cの拡張子を.cから.cppに変更します。ファイルエクスプローラーから拡張子を書き換えます、警告が出ますけど無視。exif.cppとexif.hを新規のプロジェクトに追加します。追加した状態(fmxの場合)が下図、

IDEのプロジェクトエクスプローラーでは、

のように見えます。さてと、コンパイルしてみましょう。エラーが出たら、逐一修正すれば良いです。(たぶん)コンパイルオンリーは、Shift-F9ですね。

さっそくエラーが出ました。18個もありますが、同様なエラーが複数回カウントされているだけで、特に大きくソースを変更しなければいけないものではありません。さて、最初のは、

コンパイラ様曰く

[bcc64 エラー] exif.cpp(271): assigning to 'IfdTable *' (aka '_ifdTable *') from incompatible type 'void *'
[bcc64 エラー] exif.cpp(286): assigning to 'IfdTable *' (aka '_ifdTable *') from incompatible type 'void *'
[bcc64 エラー] exif.cpp(294): assigning to 'IfdTable *' (aka '_ifdTable *') from incompatible type 'void *'
[bcc64 エラー] exif.cpp(319): assigning to 'IfdTable *' (aka '_ifdTable *') from incompatible type 'void *'
[bcc64 エラー] exif.cpp(334): assigning to 'IfdTable *' (aka '_ifdTable *') from incompatible type 'void *'

Cではvoidへのポインターをキャスト無しで任意のポインターへ代入できたんですが、C++ではアウトということです。よって、

   ifd_0th = parseIFD(fp, App1Header.tiff.Ifd0thOffset, IFD_0TH);

   ifd_0th = (IfdTable *)parseIFD(fp, App1Header.tiff.Ifd0thOffset, IFD_0TH);

と明示的にキャストすれば通るはずですね。再度Shift-F9します。

同様です。parseIFDが返すものを(IfdTable *)へキャストします。これをエラーが出たところで繰り返します。面倒ならば、IDEの置換機能を使って、

から”すべて置換”しても良いです。

で”はい”を選択ですね。箇所がたかだか数カ所なので、逐一確認するのが吉です。大した手間ではありませんから。置換後Shift-F9。

曰く:

[bcc64 エラー] exif.cpp(586): no matching function for call to 'getTagNodePtrFromIfd'
  exif.cpp(87): candidate function not viable: cannot convert argument of incomplete type 'void *' to 'IfdTable *' (aka '_ifdTable *') for 1st argument
[bcc64 エラー] exif.cpp(590): no matching function for call to 'duplicateTagNode'
  exif.cpp(88): candidate function not viable: cannot convert argument of incomplete type 'void *' to 'TagNode *' (aka '_tagNode *') for 1st argument
[bcc64 エラー] exif.cpp(615): no matching function for call to 'getTagNodePtrFromIfd'
  exif.cpp(87): candidate function not viable: cannot convert argument of incomplete type 'void *' to 'IfdTable *' (aka '_ifdTable *') for 1st argument

最初のものと同根のエラーですね。最初の引数を(IfdTable *)にキャストすれば良さげなので、

            void *targetTag = getTagNodePtrFromIfd(ifdArray[i], tagId);

void *targetTag = getTagNodePtrFromIfd((IfdTable *)ifdArray[i], tagId);

と変更すればおけ。同様にキャストで対処していくと、ついにはノーエラーになります。とりあえずここで”すべて保存”しておくのが大吉です。さて、テストしてみますかね?

提供されたsample_main.cから、

    // parse the JPEG header and create the pointer array of the IFD tables
    ifdArray = createIfdTableArray(av[1], &result);

古典的なコマンドラインプログラムなので、av[1]は入力ファイル名です、FMXでTOpenDialogで選んだファイルだと、

void Parse(String fname)
{
	void **ifdArray;
	TagNodeInfo *tag;
	int i, result;

	ifdArray = createIfdTableArray(AnsiString(fname).c_str(),&result);
	Form1->Label1->Text = IntToStr(result);

	for( i = 0 ; ifdArray[i] != NULL ; i++ ){
		dumpIfdTable(ifdArray[i]);
	}

	Form1->Memo1->Lines->Add("datetime is " + datetime);

}
//---------------------------------------------------------------------------
void __fastcall TForm1::OpenClick(TObject *Sender)
{
	if( OpenDialog1->Execute())
		Parse(OpenDialog1->FileName);

}
//---------------------------------------------------------------------------

これでresultが4になりますから、大丈夫そうですね。printfをMemo1->Lines->Addへ投影する等の苦労の末、中身もおけですのようです。前半で必要なのはDateTimeのみなので、上記のようにdumpIfdTableの途中でHookして文字列を得ればよいだけです。得る側は、

extern AnsiString datetime;

.....

	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 ", (unsigned char)tag->numData[i]);
				}
				break;

			case TYPE_ASCII:
				if( strcmp(tagName,"DateTime") == 0 )
					datetime.sprintf("%s",(char*)tag->byteData);

				PRINTF(p, "[%s]", (char*)tag->byteData);
				break;

			case TYPE_SHORT:
                for (i = 0; i < (int)tag->count; i++) {
                    PRINTF(p, "%hu ", (unsigned short)tag->numData[i]);
				if( strcmp(tagName,"DateTime") == 0 )
					datetime.sprintf("%s",(char*)tag->byteData);

を追加します。あとはsample_main.cを参考にして、

/**
 * sample_removeSensitiveData()
 *
 * remove sensitive Exif data in a JPEG file
 *
 */
int sample_removeGPSData(const char *srcJpgFileName, const char *outJpgFileName)
{
    int sts, result;
    void **ifdTableArray = createIfdTableArray(srcJpgFileName, &result);

    if (!ifdTableArray) {
        printf("createIfdTableArray: ret=%d\n", result);
        return result;
    }

    // remove GPS IFD and 1st IFD if exist
    removeIfdTableFromIfdTableArray(ifdTableArray, IFD_GPS);
    removeIfdTableFromIfdTableArray(ifdTableArray, IFD_1ST);
    
    // remove tags if exist
    /*
    removeTagNodeFromIfdTableArray(ifdTableArray, IFD_0TH, TAG_Make);
    removeTagNodeFromIfdTableArray(ifdTableArray, IFD_0TH, TAG_Model);
    removeTagNodeFromIfdTableArray(ifdTableArray, IFD_0TH, TAG_DateTime);
    removeTagNodeFromIfdTableArray(ifdTableArray, IFD_0TH, TAG_ImageDescription);
    removeTagNodeFromIfdTableArray(ifdTableArray, IFD_0TH, TAG_Software);
    removeTagNodeFromIfdTableArray(ifdTableArray, IFD_0TH, TAG_Artist);
    removeTagNodeFromIfdTableArray(ifdTableArray, IFD_EXIF, TAG_MakerNote);
    removeTagNodeFromIfdTableArray(ifdTableArray, IFD_EXIF, TAG_UserComment);
    removeTagNodeFromIfdTableArray(ifdTableArray, IFD_EXIF, TAG_DateTimeOriginal);
    removeTagNodeFromIfdTableArray(ifdTableArray, IFD_EXIF, TAG_DateTimeDigitized);
    removeTagNodeFromIfdTableArray(ifdTableArray, IFD_EXIF, TAG_SubSecTime);
    removeTagNodeFromIfdTableArray(ifdTableArray, IFD_EXIF, TAG_SubSecTimeOriginal);
    removeTagNodeFromIfdTableArray(ifdTableArray, IFD_EXIF, TAG_SubSecTimeDigitized);
    removeTagNodeFromIfdTableArray(ifdTableArray, IFD_EXIF, TAG_ImageUniqueID);
    removeTagNodeFromIfdTableArray(ifdTableArray, IFD_EXIF, TAG_CameraOwnerName);
    removeTagNodeFromIfdTableArray(ifdTableArray, IFD_EXIF, TAG_BodySerialNumber);
    removeTagNodeFromIfdTableArray(ifdTableArray, IFD_EXIF, TAG_LensMake);
    removeTagNodeFromIfdTableArray(ifdTableArray, IFD_EXIF, TAG_LensModel);
    removeTagNodeFromIfdTableArray(ifdTableArray, IFD_EXIF, TAG_LensSerialNumber);
   */ 
    // update the Exif segment
    sts = updateExifSegmentInJPEGFile(srcJpgFileName, outJpgFileName, ifdTableArray);
    if (sts < 0) {
        printf("updateExifSegmentInJPEGFile: ret=%d\n", sts);
    }
    freeIfdTableArray(ifdTableArray);
    return sts;
}

のようにexif全体を消すのではなく、IFD_GPS,IFD_1STだけ消せばよいわけです。こうしておいて、

 sample_removeGPSData(const char *srcJpgFileName, const char *outJpgFileName)

でおけです。ほぼできましたね。

元のC ProgramのようにExif情報のテーブルをダンプすると上図のようになります。面倒なので同じJPGをダンプしてdiffとかはしません。またstdoutではなくてTMemoにUnicodeStringを出力するのは少し工夫が必要でした。詳細はここでは省略します。

なおcppプログラムとしてまとめる以上の方法ではなく、cの関数をcppから呼び出す方策も可能ですが、修正はこちらの方が楽だと思います。cppのmanglingに対処するやり方ですね。

コメント