GDI+ Image::SetPropertyItem not working as expected - c++

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;
}

Related

IDeviceTopology how to activate interfaces of devicetopology.h?

Context: I am trying to activate some of the Core Audio Interfaces of "devicetopology.h" such as IAudioLoudness and IAudioPeakMeter using Component Object Model (COM), I need to get the current volume peak of the system's audio and I believe to have written most of the code necessary to make this work, but I am having trouble when trying to activate these interfaces using IPart::Activate.
Issue: When trying to access the IAudioPeakMeter Interface after the supposed activation, my console application throws an error 'Debug Assertion Failed!' and the description just says 'Expression: p!=0'. In Visual Studio an exception is thrown stating: 'Access violation reading location 0x00000000', and a deeper look inside the memory tells me that the variable audio has a value of 0x00000000 <NULL>
Additional Info: While attempting to fix the issue myself, I rewrote the entire code in a separate environment without the use of the CComPtr (Smart Pointer Class for Interfaces) and it threw me a similar error : 'audio is nullptr'
#include <iostream>
#include <devicetopology.h>
#include <mmdeviceapi.h>
#include "atlbase.h"
#include "Functiondiscoverykeys_devpkey.h"
#include "main.h"
int main()
{
HRESULT hr = S_OK;
CoInitialize(NULL);
CComPtr<IMMDeviceEnumerator> enumerator;
hr = enumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator));
if (SUCCEEDED(hr) == true)
{
CComPtr<IMMDeviceCollection> devices;
hr = enumerator->EnumAudioEndpoints(EDataFlow::eRender, DEVICE_STATEMASK_ALL, &devices);
if (SUCCEEDED(hr) == true)
{
UINT count = 0;
devices->GetCount(&count);
for (int i = 0; i < count; i++)
{
CComPtr<IMMDevice> device;
hr = devices->Item(i, &device);
if (SUCCEEDED(hr) == true)
{
CComPtr<IDeviceTopology> topology;
hr = device->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, (void**)&topology);
if (SUCCEEDED(hr) == true)
{
CComPtr<IConnector> connector;
hr = topology->GetConnector(0, &connector);
if (SUCCEEDED(hr) == true)
{
CComPtr<IConnector> connectedTo;
hr = connector->GetConnectedTo(&connectedTo);
if (SUCCEEDED(hr) == true)
{
CComPtr<IPart> part;
hr = connectedTo->QueryInterface(&part);
if (SUCCEEDED(hr) == true)
{
CComPtr<IAudioPeakMeter> audio;
hr = part->Activate(CLSCTX_INPROC_SERVER, __uuidof(IAudioPeakMeter), (void**)&audio);
UINT channels;
audio->GetChannelCount(&channels); // Causes reading violation
}
}
}
}
}
}
}
}
I am new to programming with COM and haven't been able to figure out how to move forward from this point, I have tried googling the issue and also looking at some examples using other interfaces from DeviceTopology but haven't been able to fix the issue as of yet.
Thanks in advance.

Modify EXIF Data using 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;}

Access MSMQ from C++ (non COM component) code

I am new to the concept of MSMQ(Microsoft Message Queues). I had a glimpse of the sample code from Microsoft's link and am trying to create a simple MSMQ queue. Here is my full code (mostly from Microsoft's link).
#include "stdafx.h"
#include "windows.h"
#include "mq.h"
#pragma comment (lib, "Mqrt.lib")
#include "tchar.h"
#include <stdio.h>
#define BUFLEN = 256;
#include <iostream>
HRESULT CreateMSMQQueue(
LPWSTR wszPathName,
PSECURITY_DESCRIPTOR pSecurityDescriptor,
LPWSTR wszOutFormatName,
DWORD *pdwOutFormatNameLength
)
{
// Define the maximum number of queue properties.
const int NUMBEROFPROPERTIES = 2;
// Define a queue property structure and the structures needed to initialize it.
MQQUEUEPROPS QueueProps;
MQPROPVARIANT aQueuePropVar[NUMBEROFPROPERTIES];
QUEUEPROPID aQueuePropId[NUMBEROFPROPERTIES];
HRESULT aQueueStatus[NUMBEROFPROPERTIES];
HRESULT hr = MQ_OK;
// Validate the input parameters.
if (wszPathName == NULL || wszOutFormatName == NULL || pdwOutFormatNameLength == NULL)
{
return MQ_ERROR_INVALID_PARAMETER;
}
// Set queue properties.
DWORD cPropId = 0;
aQueuePropId[cPropId] = PROPID_Q_PATHNAME;
aQueuePropVar[cPropId].vt = VT_LPWSTR;
aQueuePropVar[cPropId].pwszVal = wszPathName;
cPropId++;
WCHAR wszLabel[MQ_MAX_Q_LABEL_LEN] = L"Test Queue";
aQueuePropId[cPropId] = PROPID_Q_LABEL;
aQueuePropVar[cPropId].vt = VT_LPWSTR;
aQueuePropVar[cPropId].pwszVal = wszLabel;
cPropId++;
// Initialize the MQQUEUEPROPS structure.
QueueProps.cProp = cPropId; // Number of properties
QueueProps.aPropID = aQueuePropId; // IDs of the queue properties
QueueProps.aPropVar = aQueuePropVar; // Values of the queue properties
QueueProps.aStatus = aQueueStatus; // Pointer to the return status
// Call MQCreateQueue to create the queue.
WCHAR wszFormatNameBuffer[256];
DWORD dwFormatNameBufferLength = 256;
hr = MQCreateQueue(pSecurityDescriptor, // Security descriptor
&QueueProps, // Address of queue property structure
wszFormatNameBuffer, // Pointer to format name buffer
&dwFormatNameBufferLength); // Pointer to receive the queue's format name length in Unicode characters not bytes.
// Return the format name if the queue is created successfully.
if (hr == MQ_OK || hr == MQ_INFORMATION_PROPERTY)
{
if (*pdwOutFormatNameLength >= dwFormatNameBufferLength)
{
wcsncpy_s(wszOutFormatName, *pdwOutFormatNameLength - 1, wszFormatNameBuffer, _TRUNCATE);
wszOutFormatName[*pdwOutFormatNameLength - 1] = L'\0';
*pdwOutFormatNameLength = dwFormatNameBufferLength;
}
else
{
wprintf(L"The queue was created, but its format name cannot be returned.\n");
}
}
return hr;
}
int _tmain(int argc, _TCHAR* argv[])
{
std::cout<<"started!";
LPWSTR sam = L".\\myQueue";
LPWSTR out_name = L"Sampleoutput";
DWORD d1 = 60;
DWORD *dd = &d1;
HRESULT hs = CreateMSMQQueue(sam,pt,out_name,dd);
return 0;
}
I didn't change much of the code. When I run this code, I get an "The application was unable to start correctly(0x0000142)" error. By debugging I found out tat its due to an "Memory access error" but there is no hint as to where it occurs. Please help me out!
Is there any format for queue names? Or the output format name ?

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;

Does GDI+ have standard image encoder CLSIDs?

The GDI+ Image::Save method requires a CLSID parameter to specify the encoder to use. The documentation points to some sample code for getting the encoder associated with a particular MIME type, such as image/jpeg or image/png. However I'm balking at the thought of copying a half-page function just to support a 1-line debugging aid where I save an intermediate result out to disk.
Shouldn't there be a list of standard CLSIDs for the standard encoders? Where would I find such a list? I haven't been able to find one by searching Microsoft's include files.
There isn't one. I think they intended the codec list to be extensible and support plugins, but never got around to it. Given that they haven't made any changes to GDI+ in quite some time, they likely won't anytime soon. You could probably get away with generating your own hard coded list based on an enumeration of Gdiplus::GetImageEncoders.
That is:
image/bmp : {557cf400-1a04-11d3-9a73-0000f81ef32e}
image/jpeg : {557cf401-1a04-11d3-9a73-0000f81ef32e}
image/gif : {557cf402-1a04-11d3-9a73-0000f81ef32e}
image/tiff : {557cf405-1a04-11d3-9a73-0000f81ef32e}
image/png : {557cf406-1a04-11d3-9a73-0000f81ef32e}
Here's the function I routinely cut&paste between projects for getting at the CLSID of the encoder. You could modify it to be a table lookup.
#include <windows.h>
#include <gdiplus.h>
#include <string>
#include <vector>
HRESULT GetGdiplusEncoderClsid(const std::wstring& format, GUID* pGuid)
{
HRESULT hr = S_OK;
UINT nEncoders = 0; // number of image encoders
UINT nSize = 0; // size of the image encoder array in bytes
std::vector<BYTE> spData;
Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL;
Gdiplus::Status status;
bool found = false;
if (format.empty() || !pGuid)
{
hr = E_INVALIDARG;
}
if (SUCCEEDED(hr))
{
*pGuid = GUID_NULL;
status = Gdiplus::GetImageEncodersSize(&nEncoders, &nSize);
if ((status != Gdiplus::Ok) || (nSize == 0))
{
hr = E_FAIL;
}
}
if (SUCCEEDED(hr))
{
spData.resize(nSize);
pImageCodecInfo = (Gdiplus::ImageCodecInfo*)&spData.front();
status = Gdiplus::GetImageEncoders(nEncoders, nSize, pImageCodecInfo);
if (status != Gdiplus::Ok)
{
hr = E_FAIL;
}
}
if (SUCCEEDED(hr))
{
for (UINT j = 0; j < nEncoders && !found; j++)
{
if (pImageCodecInfo[j].MimeType == format)
{
*pGuid = pImageCodecInfo[j].Clsid;
found = true;
}
}
hr = found ? S_OK : E_FAIL;
}
return hr;
}
int main()
{
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
GUID guidBmp = {};
GUID guidJpeg = {};
GUID guidGif = {};
GUID guidTiff = {};
GUID guidPng = {};
GetGdiplusEncoderClsid(L"image/bmp", &guidBmp);
GetGdiplusEncoderClsid(L"image/jpeg", &guidJpeg);
GetGdiplusEncoderClsid(L"image/gif", &guidGif);
GetGdiplusEncoderClsid(L"image/tiff", &guidTiff);
GetGdiplusEncoderClsid(L"image/png", &guidPng);
return 0;
}
If you just want to write a PNG, this appears to work:
// image/png : {557cf406-1a04-11d3-9a73-0000f81ef32e}
const CLSID pngEncoderClsId = { 0x557cf406, 0x1a04, 0x11d3,{ 0x9a,0x73,0x00,0x00,0xf8,0x1e,0xf3,0x2e } };
stat = image->Save(L"test.png", &pngEncoderClsId, NULL);
Note the reformatting of the hex values.
From:
How to initialize a constant CLSID
You will probably want to use ImageCodecInfo with GetImageEncodersSize() and GetImageEncoders() I'm not aware of any easier way.
EDIT: If you know specifically what you want and damn all the rest you can get away with doing something like this ...
CLSID pngClsid;
GetEncoderClsid("image/png", &pngClsid);
image.Save("imagename.png", &pngClsid, NULL);