updating jpeg properties in c++ using GDI+ - c++

I am tring to copy properties from one jpeg image to another in c++. As I don't have any library and I am working on windows, I am using GDI+ for this.
My code is as follow
void CopyJpegProperties(string targetImageName,string sourceImageName)
{
CLSID m_clsid;
EncoderParameters m_encoderParameters;
UINT size;
UINT count;
int quality=100;
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR m_gdiplusToken;
// start GDI+
GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
{
// we are processing images inside a block to make sure that all of objects are deleted when GDI is shutdown.
// read source image properties
std::wstring wcSourceImageName=StringTools::StringToWString(sourceImageName);
Gdiplus::Bitmap sourceBitmap(wcSourceImageName.c_str());
sourceBitmap.GetPropertySize(&size, &count);
PropertyItem* pPropBuffer =(PropertyItem*)malloc(size);
sourceBitmap.GetAllPropertyItems(size, count, pPropBuffer);
// write to target image
std::wstring wcTargetImageName=StringTools::StringToWString(targetImageName);
Gdiplus::Bitmap targetBitmap(wcTargetImageName.c_str());
for(int i=0; i<count; i++)
{
targetBitmap.SetPropertyItem(&pPropBuffer[i]);
}
JpegTools::GetEncoderClsid(L"image/jpeg", &m_clsid);
m_encoderParameters.Count = 1;
m_encoderParameters.Parameter[0].Guid = EncoderQuality;
m_encoderParameters.Parameter[0].Type = EncoderParameterValueTypeLong;
m_encoderParameters.Parameter[0].NumberOfValues = 1;
m_encoderParameters.Parameter[0].Value = &quality;
Status stat = targetBitmap.Save(wcTargetImageName.c_str(), &m_clsid, &m_encoderParameters);
if(stat != Ok)
{
throw exception("Error in saving");
}
}
GdiplusShutdown(m_gdiplusToken);
}
This function fails with error code 7 which win32error.
I think the problem is that when I open the target file I can not write to it as it is locked by open function.
How can I fix it?
Is there any better way to read/write image properties in windows?

Related

Creating GDI+ bitmaps in memory and then saving as png

I am new to C++ and been having trouble with writing a function using the GDI+ library to create a new bitmap in memory ( so not opening/reading an existing bitmap); then drawing on the bitmap; before saving it to png. In particular, I am having problems with the bitmap creation and saving code. I am constrained to using codeblocks and I can't use visual studios, even if I wanted to. The code is as follows:
#include "drawImage.h"
#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
#include <stdio.h>
#include <iostream>
using namespace std;
using namespace Gdiplus;
drawImage::drawImage(){}
void drawImage::DrawBitmap(int width, int height){
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
{
//Create a bitmap
Bitmap myBitmap(width, height, PixelFormatCanonical);
Graphics g(&myBitmap);
Pen blackpen(Color(255,0,0,0), 3);
//draw on bitmap
int x1 = 1;
int x2 = 200;
int y1 = 1;
int y2 = 200;
g.DrawLine(&blackpen, x1,y1,x2,y2);
// Save bitmap (as a png)
CLSID pngClsid;
GetEncoderClsid(L"image/png", &pngClsid);
myBitmap.Save(L"C:\\test\\test.png", &pngClsid, NULL);
}
GdiplusShutdown(gdiplusToken);
}
The issues I am having are as follows:
The 'saving' code does not compile and gives the error message "'GetEncoderClsid' was not declared in this scope". However, I got this direct from the Microsoft website here. I don't think this is the proper way of converting to png but I dont know an alternative way?
When the code is compiled and run (by commenting out the saving code), it then crashes on the line "Bitmap *myBitmap = new Bitmap(width, height, PixelFormatCanonical);" and gives an error message saying my executable has stopped working.
I have added the 'gdi32' linker library and also '-lgdiplus' as a linker option. Also, I have used this website to help with the gdi stuff although the section on bitmaps only deals with loading existing bitmaps (not creating new ones in memory)
I am totally lost on what to do, so any help or advice on this matter is much appreciated.
The core issue is passing the wrong pixel format to the Bitmap constructor. PixelFormatCanonical is not one of the supported pixel formats. It's a bit mask used to determine whether a pixel format is canonical (see IsCanonicalPixelFormat). You'll have to use a real pixel format, like the default PixelFormat32bppARGB.
The following code produces the desired output:
First up, a small helper class for GDI+ initialization. This ensures, that the d'tor (i.e. the call to GdiplusShutdown) is executed after all other objects have been destroyed. With respect to order of destruction, it serves the same purpose as the additional scope in the OP. In addition, it also allows for exceptions to be thrown.
#include <windows.h>
#include <gdiplus.h>
using namespace Gdiplus;
#include <stdexcept>
using std::runtime_error;
struct GdiplusInit {
GdiplusInit() {
GdiplusStartupInput inp;
GdiplusStartupOutput outp;
if ( Ok != GdiplusStartup( &token_, &inp, &outp ) )
throw runtime_error( "GdiplusStartup" );
}
~GdiplusInit() {
GdiplusShutdown( token_ );
}
private:
ULONG_PTR token_;
};
This code was taken from the MSDN sample Retrieving the Class Identifier for an Encoder.
int GetEncoderClsid( const WCHAR* format, CLSID* pClsid )
{
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; // Failure
}
Finally, the GDI+ rendering code. It uses objects with automatic storage duration throughout, making it more compact and safer.
void drawImage( int width, int height ) {
GdiplusInit gdiplusinit;
//Create a bitmap
Bitmap myBitmap( width, height, PixelFormat32bppARGB );
Graphics g( &myBitmap );
Pen blackpen( Color( 255, 0, 0, 0 ), 3 );
//draw on bitmap
g.DrawLine( &blackpen, 1, 1, 200, 200 );
// Save bitmap (as a png)
CLSID pngClsid;
int result = GetEncoderClsid( L"image/png", &pngClsid );
if ( result == -1 )
throw runtime_error( "GetEncoderClsid" );
if ( Ok != myBitmap.Save( L"C:\\test\\test.png", &pngClsid, NULL ) )
throw runtime_error( "Bitmap::Save" );
}
int main()
{
drawImage( 200, 200 );
return 0;
}
Note: It looks like GetEncoderClsid shouldn't be required, since those are well-known constants. However, trying to pass the appropriate WIC CLSID (CLSID_WICPngEncoder) to Bitmap::Save only produced a FileNotFound error.
For 1.: GetEncoderClsid is not a library function, it is an example helper defined here. If you want to use it copy the code from the site. (Btw your link explicitly states this.)
For 2.: You need to call GdiplusStartup before creating any GDI+ object or calling any GDI+ function. See its documentation here.
To be more precise about GdiplusShutdown because that seems to be making you new problems: It is required that all GDI+ objects are destroyed before GdiplusShutdown is called. That means that objects with static storage (for example pen) must go out of scope before GdiplusShutdown is called. That is not the case if you call it in DrawBitmap in the way you do now. Either call GdiplusStartup and GdiplusShutdown in main or add additional brackets {} around your code between GdiplusStartup and GdiplusShutdown, because these introduce a new scope and pen would be destroyed when } is reached.
Edit: Nr. 2 was fixed in the question by edit. For the remaining problem and improvement of the code see #IInspectable's answer.

save jpeg from hBitmap to BYTE array [duplicate]

I've got a HBITMAP that I want to save into a JPEG/PNG stream or array of bytes. The problem is that I'm using mingw as my compiler so I can't use CImage.. which would have made my life easier.
I can get the pixels from the bitmap without any problems, but I have no idea how to get access to them in JPEG/PNG-format.
Where do I start?
If you have access DirectX library you may use IStream to convert your image to JPEG
http://msdn.microsoft.com/en-us/library/windows/desktop/aa380034(v=vs.85).aspx
or if you have GDI+ something like this might work
Gdiplus::Bitmap bmp(hbmpImage,(HPALETTE)0);
CLSID pngClsid;
GetEncoderClsid(L"image/png", &pngClsid);
bmp.Save(L"D:\image.png",&pngClsid,NULL);
where GetEncoderLCLsid looks like this:
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
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; // Failure
}
don't forget to initialize GDI+
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
if don't have access to either you may use libjpeg but you need to put all the dependencies packages from the GnuWin32 site. Much faster the code in this page should work, just forget about the boost
libjpeg dying without message
Another option is to use the WIC API (Windows Imaging Component), which gives direct access to image encoders and decoders. I believe GDI+ may use this under the covers.

GDI+ - converting bitmap pixels to JPG format

I have an array of a bitmap pixels. How can I convert them to JPG format and copy to another array? How to convert them back to bitmap from JPG pixels?
Checkout the Encoder CLSID function from here: http://msdn.microsoft.com/en-us/library/windows/desktop/ms533843(v=vs.85).aspx
Modified original code from https://vctipsplusplus.wordpress.com/tag/image-conversion-gdi/:
int main()
{
// Initialize GDI+.
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
CLSID encoderClsid;
Status stat;
Image* image = new Image(L”Bird.bmp”);
// Get the CLSID of the JPEG encoder.
GetEncoderClsid(L”image/jpeg”, &encoderClsid);
stat = image->Save(L”Bird.png”, &encoderClsid, NULL);
if(stat == Ok)
printf(“Bird.png was saved successfully\n”);
else
printf(“Failure: stat = %d\n”, stat);
delete image;
GdiplusShutdown(gdiplusToken);
return 0;
}
Just change image/jpeg to whatever format you want to convert. The details are given on the MSDN link I mentioned above. Of course to work with the pixels, you'll need to convert the JPEG to BMP
You can create a memory stream using CreateStreamOnHGlobal or SHCreateMemStream, then use the GDI+ method Image.Save to save to the stream. Reverse the process to read back in.

HBITMAP to JPEG /PNG without CImage in C++

I've got a HBITMAP that I want to save into a JPEG/PNG stream or array of bytes. The problem is that I'm using mingw as my compiler so I can't use CImage.. which would have made my life easier.
I can get the pixels from the bitmap without any problems, but I have no idea how to get access to them in JPEG/PNG-format.
Where do I start?
If you have access DirectX library you may use IStream to convert your image to JPEG
http://msdn.microsoft.com/en-us/library/windows/desktop/aa380034(v=vs.85).aspx
or if you have GDI+ something like this might work
Gdiplus::Bitmap bmp(hbmpImage,(HPALETTE)0);
CLSID pngClsid;
GetEncoderClsid(L"image/png", &pngClsid);
bmp.Save(L"D:\image.png",&pngClsid,NULL);
where GetEncoderLCLsid looks like this:
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
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; // Failure
}
don't forget to initialize GDI+
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
if don't have access to either you may use libjpeg but you need to put all the dependencies packages from the GnuWin32 site. Much faster the code in this page should work, just forget about the boost
libjpeg dying without message
Another option is to use the WIC API (Windows Imaging Component), which gives direct access to image encoders and decoders. I believe GDI+ may use this under the covers.

File path from a usb camera

Hello I am using GDI+ to do some image processing. I am having it run from the command line with two arguments. The reason for this is the program is being called from VBA Excel 2007. A Open file dialog is run from VBA and gives the first argument.
The first arguement is the original image to be processed and the second is where to save the image. Everything works just fine when the two arguments come from a drive with a letter, i.e. C:.
It was not working with network folders, i.e. \server\folder. I overcame this by mounting the folder to a drive letter before trying to load the image.
I have a problem now when the incoming image is on a usb camera. The file path of the file on the camera ends up being COMPUTER\Canon\DCIM\image.jpg. Windows is not mounting the camera to a lettered drive so it is not working correctly for me.
Before trying to load the image I add and extra '\' so that they are all double \.
I am not sure at all how to get this to work and have looked all over. Thanks.
int main(int argc, char* argv[])
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
// INITIALIZE GDI+
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
wchar_t tin[200] = L"";
wchar_t in[200] = L"";
wchar_t out[200] = L"";
wchar_t tout[200] = L"";
NETRESOURCE nr;
DWORD dwRetVal;
nr.dwType = RESOURCETYPE_DISK;
nr.lpLocalName = "M:";
nr.lpRemoteName = "\\\\server\\folder";
nr.lpProvider = NULL;
// Map the mugshots folder
dwRetVal = WNetAddConnection2(&nr, NULL, NULL, CONNECT_TEMPORARY);
// Convert to a wchar_t* from command line argument
size_t origsize = strlen(argv[1]) + 1;
mbstowcs( tin, argv[1], origsize);
//Add an extra \ for directory
int j = 0;
for (int i = 0 ; i < int(origsize) ; i++)
{
if(tin[i] == '\\')
{
in[j] = '\\';
j++;
in[j] = '\\';
j++;
}
else
{
in[j] = tin[i];
j++;
}
}
// Convert to a wchar_t* from command line argument
origsize = strlen(argv[2]) + 1;
mbstowcs(tout, argv[2], origsize);
//Add an extra \ for directory
out[0] = 'M';
out[1] = ':';
out[2] = '\\';
out[3] = '\\';
j = 4;
for (int i = 0 ; i < int(origsize) ; i++)
{
if(tout[i] == '\\')
{
out[j] = '\\';
j++;
out[j] = '\\';
j++;
}
else
{
out[j] = tout[i];
j++;
}
}
Bitmap b(in);
Process image
CLSID pngClsid;
GetEncoderClsid(L"image/jpeg", &pngClsid);
image2->Save(out, &pngClsid, NULL);
return 0;
}
Please take a look at the sample: GetImage Sample: Demonstrates the Windows Image Acquisition API:
The sample application has a single command on its File menu, named
From scanner or camera. When a WIA device (or a device emulator) is
attached, the menu item becomes enabled. When the user selects the
menu command, the sample will sequentially display the WIA Device
Selection dialog box, Image Selection dialog box, and Image Transfer
dialog box. The device and image selection dialog boxes are the common
dialog boxes supplied by the system, and the transfer dialog box is
implemented in this sample. Finally, the sample will display the
transferred image(s) in child window(s).
Hope this helps.
You need to look into how shell handles the special paths, a good start is here:
http://msdn.microsoft.com/en-us/library/bb773559%28v=vs.85%29.aspx
For a lot of what you are doing, you should be using PathCanonicalize() or something along these lines.
Unsure if this helps you with your camera, you may need to access image acquisition APIs directly to get files out of some cameras.