Modify EXIF Data using C++ - c++

We are building a quad copter robot for a competition, and one of the requirements is that we have to capture photos using the camera that is installed on the quad copter.
I wrote a simple OpenCV program that is able to capture them in .jpg format, but my camera or my program are unable to save "latitude" and "longitude" as EXIF on my images.
I've added to my robot a GPS module that can retrieve GPS data while capturing photos and save them to a text file.
So my main problem is adding these data from the text file to the pictures separately while they are captured.
I tried many libraries like:
easyexif
Exiv2
Phil Harvey
Also I tried them in:
Visual C++ .net 2015
Dev C++ (GCC)
Code Blocks (GCC)
and also I worked on PHP but I just can read and extract EIXFs but I can't write new data on my pictures.
How can I solve this problem?

you can use this code. I used Image::GetPropertyIdList method and Reading and Writing Metadata as a material for this code. unfortunately i didnt find any function in opencv that can manipulate Exif.
#include <windows.h>
#include <gdiplus.h>
#include <stdio.h>
#include <Math.h>
#pragma comment(lib,"gdiplus.lib")
using namespace Gdiplus;
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) //i copy this function from Doc.microsoft.com
{
UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes
ImageCodecInfo* pImageCodecInfo = NULL;
GetImageEncodersSize(&num, &size);
if (size == 0)
return -1; // Failure
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if (pImageCodecInfo == NULL)
return -1; // Failure
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; // Success
}
}
free(pImageCodecInfo);
return -1; }
int main(){
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
Status stat;
CLSID clsid;
Bitmap* bitmap = new Bitmap(L"Source.bmp"); // you can use any Image Format as Source
PropertyItem* propertyItem = new PropertyItem;
// Get the CLSID of the JPEG encoder.
GetEncoderClsid(L"image/jpeg", &clsid);
double dlan = 35.715298; // we supposed that your GPS data is Double if its not skip this step
// convert double to unsigned long array
double coord = dlan;
int sec = (int)round(coord * 3600);
int deg = sec / 3600;
sec = abs(sec % 3600);
int min = sec / 60;
sec %= 60;
unsigned long ulExifCoordFormatLan[6] = { deg, 1, min, 1, sec, 1 };
propertyItem->id = PropertyTagGpsLatitude;
propertyItem->length = sizeof(long) * 2 * 3;
propertyItem->type = PropertyTagTypeRational;
propertyItem->value = ulExifCoordFormatLan;
Status s = bitmap->SetPropertyItem(propertyItem);// saving image to the destination
stat = bitmap->Save(L"Dest.jpg", &clsid, NULL);
if (s == Ok && stat==Ok)
printf("Dest.jpg saved successfully .\n");
delete propertyItem;
delete bitmap;
GdiplusShutdown(gdiplusToken);
return 0;}

Related

No Sound When Playing Back PCM-Decoded Audio

I am reading AAC audio frames which I then decode to PCM with Media Foundation and am trying to play back through WASAPI. Particularly 48000khz 2 channels, 16 bit. I am able to decode the frames, write them to a file full.pcm, and then open and play that PCM file successfully in Audacity. However, my code to play back through the device speakers gives me nothing. The source I am trying to play through is the default source, which is my DAC. I am not getting any bad HRESULTS from any of the WASAPI-related code, so I'm confused. WASAPI is new to me though, so maybe there is something obvious I am missing.
#include "AudioDecoder.h"
#include <vector>
#include <__msvc_chrono.hpp>
#include <string>
#include <fstream>
#include <cassert>
#include <filesystem>
#include <mmdeviceapi.h>
#include <endpointvolume.h>
#include <functiondiscoverykeys.h>
#include <audioclient.h>
int fps_counter = 0;
int frame_index = 0;
IAudioClient* audio_client;
IAudioRenderClient* render_client = nullptr;
int setup_audio_playback()
{
HRESULT hr = S_OK;
IMMDeviceEnumerator* pEnumerator = nullptr;
IMMDevice* pDevice = nullptr;
ATLENSURE_SUCCEEDED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator));
ATLENSURE_SUCCEEDED(pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pDevice));
IPropertyStore* ips;
ATLENSURE_SUCCEEDED(pDevice->OpenPropertyStore(STGM_READ, &ips));
PROPVARIANT varName;
// Initialize container for property value.
PropVariantInit(&varName);
ATLENSURE_SUCCEEDED(ips->GetValue(PKEY_Device_FriendlyName, &varName));
std::wcout << L"Device name: " << varName.pwszVal << std::endl;
ATLENSURE_SUCCEEDED(pDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void**)&audio_client));
WAVEFORMATEX* format;
ATLENSURE_SUCCEEDED(audio_client->GetMixFormat(&format));
ATLENSURE_SUCCEEDED(audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, 10000000, 0, format, NULL));
uint32_t bufferFrameCount;
ATLENSURE_SUCCEEDED(audio_client->GetBufferSize(&bufferFrameCount));
ATLENSURE_SUCCEEDED(audio_client->GetService(__uuidof(IAudioRenderClient), (void**)&render_client));
ATLENSURE_SUCCEEDED(audio_client->Start());
return hr;
}
int main()
{
HRESULT hr = S_OK;
std::ofstream fout_all_frames_pcm;
std::filesystem::remove(std::filesystem::current_path() / "full.pcm");
fout_all_frames_pcm.open("full.pcm", std::ios::binary | std::ios::out);
if (FAILED(hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED)))
return hr;
if (FAILED(hr = MFStartup(MF_VERSION)))
return hr;
setup_audio_playback();
AudioDecoder* ad = new AudioDecoder();
std::vector<uint8_t> data;
while (true)
{
std::chrono::time_point<std::chrono::steady_clock> iteration_time = std::chrono::high_resolution_clock::now();
// Read frame data
std::ifstream fin("Encoded Audio Frames\\frame" + std::to_string(frame_index) + ".aac", std::ios::binary | std::ios::in);
if (fin.fail())
{
//throw std::runtime_error("Invalid file path specified");
break;
}
// Get file length
fin.seekg(0, std::ios::end);
size_t const length = fin.tellg();
fin.seekg(0, std::ios::beg);
if (length > data.size())
{
static size_t constexpr const granularity = 64 << 10;
data.resize((length + (granularity - 1)) & ~(granularity - 1));
assert(length <= data.size());
}
// Copy frame data from file to array;
fin.read(reinterpret_cast<char*>(data.data()), length);
fin.close();
CComPtr<IMFSample> pcm_sample;
while (!ad->decode_sync(data.data(), length, &pcm_sample))
{
if (pcm_sample == nullptr) // This will happen if the color converter isn't able to produce output, so we will continue in that case
continue;
CComPtr<IMFMediaBuffer> buffer;
if (FAILED(hr = pcm_sample->ConvertToContiguousBuffer(&buffer)))
return hr;
unsigned char* datas;
DWORD length;
if (FAILED(hr = buffer->GetCurrentLength(&length)))
return hr;
if (FAILED(hr = buffer->Lock(&datas, nullptr, &length)))
return hr;
fout_all_frames_pcm.write((char*)datas, length);
// Does nothing
//Sleep(120);
// Grab all the available space in the shared buffer.
uint8_t* pData;
ATLENSURE_SUCCEEDED(render_client->GetBuffer(1, &pData));
memcpy(pData, datas, length);
DWORD flags = 0;
ATLENSURE_SUCCEEDED(render_client->ReleaseBuffer(1, flags));
pcm_sample.Release();
}
frame_index++;
}
audio_client->Stop();
return 0;
}
Doing
render_client->GetBuffer(1, ...
will not give you any stable behavior because you are trying to submit data sample by sample. Literally, one PCM sample of your 48000 samples per second. Of course, the code is likely to be broken more than this because you seem to be simply losing most of the data getting much more from decoder and feeding just one sample to the device.
You would want to check this article in the part where the code identifies how many samples the GetBuffer will carry and then loop with filling those buffers accurately until you consume your IMFsample data.
How large those buffers are, those you obtain with GetBuffer? For 10 ms buffers which are pretty typical and 48 kHz sampling rate, you would have 480 samples per buffer. With stereo and 16-bit PCM you have four bytes per sample and so you would be delivering around 2K bytes every GetBuffer/ReleaseBuffer iteration.

BIts Per Sample / Pixel libtiff vs WIC

TIFF *TiffImage;
uint16 photo, bps, spp, fillorder;
uint32 width,height;
unsigned long stripSize;
unsigned long imageOffset, result;
int stripMax, stripCount;
unsigned char *buffer, tempbyte;
unsigned short *buffer16;
unsigned int *buffer32;
unsigned long bufferSize, count;
bool success = true;
int shiftCount = 0;
//read image to InData
const char *InFileName = fileName.c_str();
if((TiffImage = TIFFOpen(InFileName, "r")) == NULL){
ErrMsg("Could not open incoming image\n");
return false;
}
// Check that it is of a type that we support
if(TIFFGetField(TiffImage, TIFFTAG_BITSPERSAMPLE, &bps) == 0) {
ErrMsg("Either undefined or unsupported number of bits per sample\n");
return false;
}
TBitPrecision bitPrecision = (TBitPrecision)bps;
char* imageDesc = NULL;
TIFFGetField(TiffImage, TIFFTAG_IMAGEDESCRIPTION, &imageDesc);
// Get actual bit precision for CP Images
if (bps > 8 && bps <= 16)
{
if (GetCpTiffTag(imageDesc, CP_TIFFTAG_BITPRECISION, (uint32*)&bitPrecision) == true)
{
shiftCount = 16 - bitPrecision;
}
}
In my libtiff implementation I have used 12 bit per pixel image and also 10 bpp
it is very easy to set this info in libtiff
I can't find a similar way to do so in WIC
uint16_t photo, bps, spp, fillorder;
uint32_t width,height;
unsigned long stripSize;
unsigned long imageOffset, result;
int stripMax, stripCount;
unsigned char *buffer, tempbyte;
unsigned short *buffer16;
unsigned int *buffer32;
unsigned long bufferSize, count;
bool success = true;
int shiftCount = 0;
//read image to InData
const char *InFileName = fileName.c_str();
IWICImagingFactory* piFactory = NULL;
// Create WIC factory
HRESULT hr = CoCreateInstance(
CLSID_WICImagingFactory,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&piFactory)
);
// Create a decoder
IWICBitmapDecoder *pIDecoder = NULL;
IWICBitmapFrameDecode *pIDecoderFrame = NULL;
std::wstring ws;
ws.assign(fileName.begin(), fileName.end());
// get temporary LPCWSTR (pretty safe)
LPCWSTR pcwstr = ws.c_str();
hr = piFactory->CreateDecoderFromFilename(
pcwstr, // Image to be decoded
NULL, // Do not prefer a particular vendor
GENERIC_READ, // Desired read access to the file
WICDecodeMetadataCacheOnDemand, // Cache metadata when needed
&pIDecoder // Pointer to the decoder
);
// Retrieve the first bitmap frame.
if (SUCCEEDED(hr))
{
hr = pIDecoder->GetFrame(0, &pIDecoderFrame);
}
else
{
ErrMsg("Could not open incoming image\n");
}
return true;
Am I suppose to use EXIF or XMP? how do I find the TIFFTAG's for WIC?
The DirectXTex library has lots of examples of using WIC from C++.
You need something like:
using Microsoft::WRL::ComPtr;
ComPtr<IWICMetadataQueryReader> metareader;
hr = pIDecoderFrame->GetMetadataQueryReader(metareader.GetAddressOf());
if (SUCCEEDED(hr))
{
PROPVARIANT value;
PropVariantInit(&value);
if (SUCCEEDED(metareader->GetMetadataByName(L"/ifd/{ushort=258}", &value))
&& value.vt == VT_UI2)
{
// BitsPerSample is in value.uiVal
}
PropVariantClear(&value);
}
You should get in the habit of using a smart-pointer like ComPtr rather than using raw interface pointers to keep the ref counts correct.

GDI+ Image::SetPropertyItem not working as expected

I am trying to use SetPropertyItem to set a Date Taken property to a file (click here for MSDN docs description).
I have tried assigning a newly initialized FILETIME to an input image with no success (or error messages). To ensure that it was not an issue with Date Taken, I also tried following this MSDN example to no avail.
Currently, I am attempting to extract a Date Taken property item from one input file (works fine) and attempting to set it to a different file. This approach does not work either, and the Status code returned is always 0 (Ok).
The code I am using is below. I can only assume I am making a simple mistake or perhaps misunderstanding what SetPropertyItem is supposed to do. I thought that SetPropertyItem changed the metadata value such that it can be viewed through the Windows properties menu, like in this screenshot.
#include <windows.h>
#include <gdiplus.h>
#include <stdio.h>
#include <iostream>
using namespace Gdiplus;
#pragma comment(lib, "gdiplus.lib")
int main()
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
Image* image = new Image(L"FakePhoto.jpg"); // input image
UINT totalBufferSize;
UINT numProperties; // setup the buffer
image->GetPropertySize(&totalBufferSize, &numProperties);
// extract all metadata property items
PropertyItem* pAllItems = (PropertyItem*)malloc(totalBufferSize);
image->GetAllPropertyItems(totalBufferSize, numProperties, pAllItems);
for (UINT j = 0; j < numProperties; ++j)
{ // loop through each property
if (pAllItems[j].id == PropertyTagExifDTOrig)
{ // if it's the Date Taken property
PropertyItem* propItem = new PropertyItem;
Image* newImage = new Image(L"Test2.jpg");
Status status; // second image
propItem->id = PropertyTagExifDTOrig;
propItem->length = pAllItems[j].length;
propItem->type = PropertyTagTypeASCII;
propItem->value = pAllItems[j].value;
// create a new property item with the input photo Date Taken metadata
status = newImage->SetPropertyItem(propItem);
if (status == Ok)
std::cout << "No errors.";
}
}
free(pAllItems);
delete image;
GdiplusShutdown(gdiplusToken);
}
Any help is greatly appreciated. Also, I apologise about any obvious/potential errors. I am still learning the ropes as this is my first time using C++.
You code works fine, but you must save the image back, for example like this
...
newImage->SetPropertyItem(propItem);
CLSID clsid;
GetEncoderClsid(L"image/jpeg", &clsid);
newImage->Save(L"Test2.jpg", &clsid);
...
BOOL GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0;
UINT size = 0;
ImageCodecInfo* info = NULL;
ZeroMemory(pClsid, sizeof(CLSID));
GetImageEncodersSize(&num, &size);
if (size == 0)
return FALSE;
info = (ImageCodecInfo*)(malloc(size));
if (info == NULL)
return FALSE;
GetImageEncoders(num, size, info);
for (UINT j = 0; j < num; ++j)
{
if (!wcscmp(info[j].MimeType, format))
{
*pClsid = info[j].Clsid;
free(info);
return TRUE;
}
}
free(info);
return FALSE;
}

Memory map streaming data in C++

I am collecting data from LiDAR in real time. I created a for loop to iterate over initiating a frame, collecting the data and saving it into an array (distArray). However, I am stuck with the memory mapping part. My array takes 500 integers ==> 2000 bytes of memory .. When I try to map each array element to memory using CopyMemory(), I am getting the following error "Exception thrown at 0x5843335E (vcruntime140d.dll) in file.exe: 0xC0000005: Access violation writing location 0x007E0000." .. Any ideas on how to solve this problem?
In the code below, there are a lot of functions and header calls that are irrelevant to the question, so please don't mind them ..
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <bta.h>
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
#define BUF_SIZE 2000
TCHAR szName[] = TEXT("mapObjFile");
void setTOFParameters(BTA_Handle btaHandle, uint32_t newTint, uint32_t newFPS, uint32_t newFMOD);
static void errorHandling(BTA_Status status);
const int arraySize = 500;
const int numFrames = 500;
int distArray[arraySize];
int main() {
BTA_Status status;
BTA_Config config;
printf("BTAinitConfig()\n\n");
status = BTAinitConfig(&config);
errorHandling(status);
uint8_t udpDataIpAddr[] = { 224, 0, 0, 1 };
config.udpDataIpAddr = udpDataIpAddr;
config.udpDataIpAddrLen = 4;
config.udpDataPort = 10002;
uint8_t tcpDeviceIpAddr[] = { 192, 168, 0, 10 };
config.tcpDeviceIpAddr = tcpDeviceIpAddr;
config.tcpDeviceIpAddrLen = 4;
config.tcpControlPort = 10001;
config.frameQueueMode = BTA_QueueModeDropOldest;
config.frameQueueLength = 500;
config.frameMode = BTA_FrameModeDistAmp;
// OPEN Connection
BTA_Handle btaHandle;
printf("BTAopen()\n\n");
status = BTAopen(&config, &btaHandle);
errorHandling(status);
printf("Service running: %d\n", BTAisRunning(btaHandle));
printf("Connection up: %d\n\n", BTAisConnected(btaHandle));
BTA_DeviceInfo *deviceInfo;
printf("BTAgetDeviceInfo()\n");
status = BTAgetDeviceInfo(btaHandle, &deviceInfo);
errorHandling(status);
printf("Device type: 0x%x\n", deviceInfo->deviceType);
printf("BTAfreeDeviceInfo()\n\n");
BTAfreeDeviceInfo(deviceInfo);
// READ Register
uint32_t regAddr = 0x1004; //MLX75123, Register 0x1000 = I2C_ADDRESS
uint32_t regValue;
status = BTAreadRegister(btaHandle, regAddr, &regValue, 0);
errorHandling(status);
printf("BTAreadRegister : Register 0x%04X has value 0x%04X\n\n", regAddr, regValue);
for (int i = 1; i < numFrames; i++) {
// GET The Frame
printf("Getting distance and amplitude data :\n");
BTA_Frame *frame;
printf("BTAgetFrame()\n");
status = BTAgetFrame(btaHandle, &frame, 300);
errorHandling(status);
BTA_DataFormat dataFormat;
BTA_Unit unit;
uint16_t xRes, yRes;
// Getting the distance data into a buffer and calculating the average amplitude over the entire frame :
uint16_t *distances;
printf("BTAgetDistances()\n");
status = BTAgetDistances(frame, (void**)&distances, &dataFormat, &unit, &xRes, &yRes);
errorHandling(status);
if (dataFormat == BTA_DataFormatUInt16) {
uint32_t distAvg = 0;
for (int y = 0; y < yRes; y++) {
for (int x = 0; x < xRes; x++) {
distAvg += distances[x + y * xRes];
}
}
if (xRes != 0 && yRes != 0) {
distArray[i] = distAvg / xRes / yRes;
printf("The average distance is %d.\n", distArray[i]);
}
}
// FREE The Frame
printf("BTAfreeFrame()\n\n");
status = BTAfreeFrame(&frame);
errorHandling(status);
// ----------------------- Memory Mapping -----------------------
HANDLE hMapFile;
LPCTSTR pBuf;
hMapFile = CreateFileMapping(
INVALID_HANDLE_VALUE, // use paging file
NULL, // default security
PAGE_READWRITE, // read/write access
0, // maximum object size (high-order DWORD)
BUF_SIZE, // maximum object size (low-order DWORD)
szName); // name of mapping object
if (hMapFile == NULL)
{
_tprintf(TEXT("Could not create file mapping object (%d).\n"),
GetLastError());
return 1;
}
pBuf = (LPTSTR)MapViewOfFile(hMapFile, // handle to map object
FILE_MAP_READ, // read/write permission
0,
0,
BUF_SIZE);
if (pBuf == NULL)
{
_tprintf(TEXT("Could not map view of file (%d).\n"),
GetLastError());
CloseHandle(hMapFile);
return 1;
}
CopyMemory((PVOID)pBuf, &distArray, BUF_SIZE);
_getch();
/*UnmapViewOfFile(pBuf);
CloseHandle(hMapFile);*/
}
// CLOSE Connection
printf("BTAclose()\n\n");
status = BTAclose(&btaHandle);
errorHandling(status);
// END Program
printf("Hit <ENTER> to close the window .. ");
fgetc(stdin);
}
After mapping the data to the memory, I will be using the mmap library in Python (https://docs.python.org/3.0/library/mmap.html) to access the data based on the tagname parameter ...
See here: https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/aa366535(v%3Dvs.85)
void CopyMemory(
_In_ PVOID Destination,
_In_ const VOID *Source,
_In_ SIZE_T Length
);
first parameter is the destination, the second is source. But in your code you have:
CopyMemory((PVOID)pBuf, &distArray, BUF_SIZE);
... the other way around. So the exception is because you copy data from distArray to pBuf which is the mapping of read only memory.

Cannot delete GDI+ Image

I am programming a basic image converter to convert an image to a BMP. I clear up the Image at the end to avoid memory leaks. However, when I try to compile it, this error comes up:
type 'class Gdiplus::Image' argument given to 'delete', expected pointer
I have checked multiple websites but when I use their examples, it still comes up with that compiler error. Even Microsoft's examples come up with that error! I saw a website containing a way to delete the Image but I can't remember the link or the way that they deleted the Image.
My code:
#include <windows.h>
#include <gdiplus.h>
using namespace Gdiplus;
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
using namespace Gdiplus; UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes
ImageCodecInfo* pImageCodecInfo = NULL;
GetImageEncodersSize(&num, &size);
if(size == 0)
return -1; // Failure
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if(pImageCodecInfo == NULL)
return -1; // Failure
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; // Success
}
}
free(pImageCodecInfo);
return 0;
}
int main()
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
CLSID bmpClsid;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
Image picture(L"TEST.GIF");
GetEncoderClsid(L"image/bmp", &bmpClsid);
picture.Save(L"Mosaic2.bmp", &bmpClsid, NULL);
delete picture;
GdiplusShutdown(gdiplusToken);
return 0;
}
I will put you in the credits of the program if you give me an answer that works.
Thank you!
Well, delete only works on pointers and your "picture" is an object (unless it is overloaded in some way). Moreover, since it's a local object it should call destructor at the end of main (which should release related memory, including loaded image). But in case memory needs to be released before GdiplusShutdown(gdiplusToken); you can adapt your code to use pointers:
Image *picture = new Image (L"TEST.GIF");
GetEncoderClsid(L"image/bmp", &bmpClsid);
picture->Save(L"Mosaic2.bmp", &bmpClsid, NULL);
delete picture;