today I got some code to review.
Since the code is going to work on an headless pc the code saves every frame as a seperate RGBa image.
On my Ubuntu install I cannot view theses images, GIMP complains about a broken header. Imagemagick options convert or display also did not show any images.
Here's the code fragment that generates the image:
if (act.doScreenshot || (act.doVideo && buddhabrot_animate.animating))
{
uchar4* tmpBuffer = new uchar4[env.static_env.save.imageW
* env.static_env.save.imageH];
for (int i = 0; i < env.static_env.save.imageW * env.static_env.save.imageH; i++)
{
const unsigned char tmp = tmpBuffer[i].x;
tmpBuffer[i].x = tmpBuffer[i].z;
tmpBuffer[i].z = tmp;
}
char filename[128];
FILE* fp = fopen(filename, "w+b");
BITMAPFILEHEADER bmpFH;
BITMAPINFOHEADER bmpIH;
memset(&bmpFH, 0, sizeof(bmpFH));
memset(&bmpIH, 0, sizeof(bmpIH));
bmpFH.bfType = 19778; //"BM"
bmpFH.bfSize = sizeof(bmpFH) + sizeof(bmpIH) + env.static_env.save.imageW * env.static_env.save.imageH;
bmpFH.bfOffBits = sizeof(bmpFH) + sizeof(bmpIH);
bmpIH.biSize = sizeof(bmpIH);
bmpIH.biWidth = env.static_env.save.imageW;
bmpIH.biHeight = env.static_env.save.imageH;
bmpIH.biPlanes = 1;
bmpIH.biBitCount = 32;
fwrite(&bmpFH, 1, sizeof(bmpFH), fp);
fwrite(&bmpIH, 1, sizeof(bmpIH), fp);
fwrite
(tmpBuffer,
env.static_env.save.imageW * env.static_env.save.imageH,
sizeof(uchar4),
fp);
fclose(fp);
delete[] tmpBuffer;
Is there any way to look at the image?
Or maybe another way to save the images as JPGs?
You don't calculate bmpFH.bfSize correctly, you need to multiply the number of pixels in the image by the size of the pixels (4). For example:
bmpFH.bfSize = sizeof(bmpFH) + sizeof(bmpIH) + env.static_env.save.imageW * env.static_env.save.imageH * sizeof(uchar4);
You should also initialize bmpIH.biCompression to BI_RGB. It'll work anyway because its value happens to be zero, but it's good to be explicit. You also might want to negate the value you're assigning to bmpIH.biHeight as positive height values indicate a bottom up image.
Related
I want my app (which works with RGBA8888 images) to be able to paste images from the Windows clipboard. So it should be able to read images off the clipboard that come from any common raster image apps like Gimp, Photoshop, MSPaint, etc.
From reading up on the clipboard functions, it seems I should be able to call GetClipboardData(CF_DIBV5) to get access to pretty much any bitmap type that's on the Clipboard since Windows automatically converts between that and CF_BITMAP and CF_DIB. But from reading up on the DIB format, I see that there is an immense number of possible combinations of bit depth, RGB order, optional compression, etc. It seems like what I'm doing would be a common task, but I don't see any conversion functions in the Windows API (unless I'm poor at searching), and this seems like something that would take a week to write to support all possible formats. So I'm wondering if I've overlooked something obvious. Or if there is some kind of assumption I can make to simplify this...like if all the popular image apps happen to copy images to the clipboard in uncompressed/unindexed formats.
UPDATE: Here's what I have so far:
HGLOBAL clipboard = GetClipboardData(CF_DIBV5);
exists = clipboard != NULL;
int dataLength = GlobalSize(clipboard);
exists = dataLength != 0;
if (exists) {
LPTSTR lockedClipboard = GlobalLock(clipboard);
exists = lockedClipboard != NULL;
if (exists) {
BITMAPV5HEADER *header = (BITMAPV5HEADER*)lockedClipboard;
LONG width = header->bV5Width;
LONG height = header->bV5Height;
BYTE *bits = header + sizeof(header) + header->bV5ClrUsed * sizeof(RGBQUAD);
//Now what? Need function to convert the bits to something uncompressed.
GlobalUnlock(clipboard);
}
}
UPDATE 2:
To clarify, I need literally uncompressed 32 bit image data (RRGGBBAA) which I can manipulate however I like in a cross-platform app. I have no need to use Windows APIs to draw this image to screen.
I am aware of a 3rd party library called stdb_image.h that can load .bmps, .jpgs, and .pngs into the type of data I need. So if there's a way I can turn the clipboard data into bitmap or png file data without losing alpha, then I'll be in good shape.
The basic strategy I've found is to check if there's a raw PNG on the clipboard and use that first if available. That's the easiest. Some apps, such as GIMP, copy images as PNG to the clipboard.
Then check for CF_DIBV5. The location of the actual bits depends on whether the "compression" is BI_BITFIELDS:
int offset = bitmapV5Header->bV5Size + bitmapV5Header->bV5ClrUsed * (bitmapV5Header->bV5BitCount > 24 ? sizeof(RGBQUAD) : sizeof(RGBTRIPLE));
if (compression == BI_BITFIELDS)
offset += 12; //bit masks follow the header
BYTE *bits = (BYTE*)bitmapV5Header + offset;
If the header says compression is BI_BITFIELDS, then the data is already as I needed it.
If the header says compression is BI_RGB and the bit count is 24 or 32, then I can unpack the bytes. 24 bytes means row size might not land on a DWORD boundary, so you have to watch for that.
Finally, lower bit counts than 24 likely mean indexed color, which I don't have working yet.
Here is example of usage for CF_DIBV5 and CF_DIB. It's best to use CF_DIB as backup option. Note, this code won't work for palette based images (if it is not guaranteed 32bit then see the method further down)
You can use SetDIBitsToDevice to draw directly on HDC, or use SetDIBits
GDI functions don't support alpha transparency (except for a couple of functions like TransparentBlt), in general you have to use libraries such as GDI+ for that.
void foo(HDC hdc)
{
if (!OpenClipboard(NULL))
return;
HANDLE handle = GetClipboardData(CF_DIBV5);
if (handle)
{
BITMAPV5HEADER* header = (BITMAPV5HEADER*)GlobalLock(handle);
if (header)
{
BITMAPINFO bmpinfo;
memcpy(&bmpinfo.bmiHeader, header, sizeof(BITMAPINFOHEADER));
bmpinfo.bmiHeader.biSize = sizeof(BITMAPINFO);
//(use `header` to access other BITMAPV5HEADER information)
int w = bmpinfo.bmiHeader.biWidth;
int h = bmpinfo.bmiHeader.biHeight;
const char* bits = (char*)(header) + header->bV5Size;
//draw using SetDIBitsToDevice
SetDIBitsToDevice(hdc,0,0,w,h,0,0,0,h,bits,&bmpinfo,DIB_RGB_COLORS);
}
}
else
{
handle = GetClipboardData(CF_DIB);
if (handle)
{
BITMAPINFO* bmpinfo = (BITMAPINFO*)GlobalLock(handle);
if (bmpinfo)
{
int w = bmpinfo->bmiHeader.biWidth;
int h = bmpinfo->bmiHeader.biHeight;
const char* bits = (char*)(bmpinfo)+bmpinfo->bmiHeader.biSize;
SetDIBitsToDevice(hdc, 0, 0, w, h, 0, 0, 0, h, bits, bmpinfo, 0);
}
}
}
CloseClipboard();
}
If the original image is palette based, you would have to convert to 32bit. Alternatively you could add BITMAPFILEHEADER to the data (assuming the source is bitmap) then pass to the other library.
This is an example using CreateDIBitmap and GetDIBits to make sure the pixels are in 32bit:
HANDLE handle = GetClipboardData(CF_DIB);
if (handle)
{
BITMAPINFO* bmpinfo = (BITMAPINFO*)GlobalLock(handle);
if (bmpinfo)
{
int offset = (bmpinfo->bmiHeader.biBitCount > 8) ?
0 : sizeof(RGBQUAD) * (1 << bmpinfo->bmiHeader.biBitCount);
const char* bits = (const char*)(bmpinfo)+bmpinfo->bmiHeader.biSize + offset;
HBITMAP hbitmap = CreateDIBitmap(hdc, &bmpinfo->bmiHeader, CBM_INIT, bits, bmpinfo, DIB_RGB_COLORS);
//convert to 32 bits format (if it's not already 32bit)
BITMAP bm;
GetObject(hbitmap, sizeof(bm), &bm);
int w = bm.bmWidth;
int h = bm.bmHeight;
char *bits32 = new char[w*h*4];
BITMAPINFOHEADER bmpInfoHeader = { sizeof(BITMAPINFOHEADER), w, h, 1, 32 };
HDC hdc = GetDC(0);
GetDIBits(hdc, hbitmap, 0, h, bits32, (BITMAPINFO*)&bmpInfoHeader, DIB_RGB_COLORS);
ReleaseDC(0, hdc);
//use bits32 for whatever purpose...
//cleanup
delete[]bits32;
}
}
I am writting a program in C in which i'm reading a bmp image with a 2D array. I am supposed to have as output the original image + its inverse right next to each other.
The code I have here should be doing that but the images print on top of each other. I appreciate the help if anyone knows how I can fix that.
int main(void) {
// Opens file to read the image from
FILE * infile = fopen("inputImage.bmp", "rb");
// Creates file to write the image to after modifications
FILE * outfile = fopen("flip.bmp", "wb");
//Bmp images have headers the next 3 lines of code store the header of the input image
unsigned char headersPart1[2];
int filesize;
unsigned char headersPart2[48];
// This space[] [] creates the array to which to write the input image AND its inverse right next to each other the image file is 160 * 240
// The 3 is for the rgb values.. since its a 2D array we want to use every single //part of the image to work with
unsigned char space[160][3*240];
//The array to which to copy the results to (original image + INVERSE)
unsigned char mirror[160][3*480];
fread(headersPart1,sizeof(char) ,2,infile);
fread(&filesize,sizeof(char) ,4,infile);
fread(headersPart2,sizeof(char) ,48,infile);
fread(space,sizeof(char) ,filesize-54,infile);
// copying what in the original image array (space[][]) into the new //array mirror[][]
for ( int row = 0; row < 240*3 ; row++ ) {
for (int col = 0 ; col < 160; col++) {
char temp = space[col][row];
mirror[col][row] = space[col][row];
//Starts printing the inverse of the original image, starting at the index where the original image will end
mirror[col][720+row]= space[col][719-row];
space[col][row] = temp;
}
}
// Puts everything back into the outfile , once i save and run on GCC this gives me an image and its inverse on top of each other
fwrite(headersPart1,sizeof(char) ,2,outfile);
fwrite(&filesize,sizeof(char) ,4,outfile);
fwrite(headersPart2,sizeof(char) ,48,outfile);
//sends whats in mirror [][] to the outfile
//54 is the size of the header (bmp images))
fwrite(mirror,sizeof(char) ,filesize-54,outfile);
return 0;
}
PS: I'm running this on GCC
The BMP file format you can find here
Your loader is not complete regarding support different versions of BMP, check resolution, color-depth, the presens of palette etc.
But for what you want to use this for, you probably have a fixed input, always the same resolution etc.
But you want the resulting image to be twice as wide. Hence some details in the headers has to change:
"filesize"
"the bitmap width in pixels (signed integer)"
I'd like to compress a planar 4:2:0 YUV buffer to a jpeg image using libturbojpeg in C, but I'm having trouble using the tjCompressFromYUV() function.
This is my code:
#define PADDING 2
tjhandle tjh;
unsigned long buf_size;
unsigned char *jpegBuf = NULL;
unsigned long jpegSize;
int width = 352;
int height = 288;
int quality = 70;
unsigned char *ucp_frame;
int j;
FILE *fp = NULL;
ucp_frame = malloc(width * height * 3 / 2);
if ( NULL == ucp_frame ) {
printf("malloc error ucp_frame\n");
return 0;
}
fp = fopen("planar_352x288.raw", "rb");
if( NULL == fp ) {
printf("fopen error\n");
return 0;
}
j = fread( ucp_frame, 1, width * height * 3 / 2, fp);
if( j != width * height * 3 / 2 ) {
printf("fread error\n");
return 0;
}
fclose(fp);
tjh = tjInitCompress();
if( NULL == tjh ) {
printf("tjInitCompress error '%s'\n", tjGetErrorStr() );
return 0;
}
buf_size = tjBufSizeYUV2( width, PADDING, height, TJSAMP_420);
jpegBuf = tjAlloc(buf_size);
if( tjCompressFromYUV( tjh, ucp_frame, width, PADDING, height,
TJSAMP_420, &jpegBuf, &jpegSize, quality,
TJFLAG_NOREALLOC ) ) {
printf("tjCompressFromYUV error '%s'\n", tjGetErrorStr() );
}
The error string returned by tjGetErrorStr() is "Bogus input colorspace".
I tried linking libturbojpeg versions 1.4.2 and 1.4.90.
Any help wolud be appreciated,
Thanks
Turbojpeg API tjCompressFromYUV allows you such options for jpegBuf:
#param jpegBuf address of a pointer to an image buffer that will receive the
JPEG image. TurboJPEG has the ability to reallocate the JPEG buffer to
accommodate the size of the JPEG image. Thus, you can choose to:
pre-allocate the JPEG buffer with an arbitrary size using #tjAlloc() and
let TurboJPEG grow the buffer as needed,
set *jpegBuf to NULL to tell TurboJPEG to allocate the buffer
for you, or
pre-allocate the buffer to a "worst case" size determined by calling
tjBufSize(). This should ensure that the buffer never has to be
re-allocated (setting #TJFLAG_NOREALLOC guarantees this.)
If you choose option 1, *jpegSize should be set to the size of your
pre-allocated buffer. In any case, unless you have set #TJFLAG_NOREALLOC,
you should always check *jpegBuf upon return from this function, as
it may have changed.
So by using 2-nd option, there is no need to call tjBufSizeYUV2 and tjAlloc, simply have jpegBuf=NULL before calling tjCompressFromYUV and do tjFree after compressing.
Ok it turned out the problem was in the program containing the code I posted, in a smaller test program the tjBufSizeYUV2 call performs as expected.
On a side note it seems that if jpegBuf is pre-allocated before calling tjBufSizeYUV2, the flag argument passed to tjBufSizeYUV2 must contain TJFLAG_NOREALLOC, or else jpegBuf won't be freed even if tjFree(jpegBuf); is later called.
#define PADDING 4
jpegBuf = tjAlloc(width*height*3/2);
The last time i had problem with this was because i swapped WIDTH and HEIGHT in the wrong way. After that i have been told for correct way and i fixed it. However.. today i noticed that the function isn't working as it was expected on some bmp images.
I am rendering the image from a buffer that has all the image's byte data.
And there is the function to render:
void bmp_bdraw (BYTE* BUFF)
{
word WIDTH, HEIGHT, W, H; // word - unsigned short
BYTE R, G, B; // BYTE - unsigned char
(!BUFF || !BUFF[COUNT-1]) ? // debug1
(error("Error in function 'bmp_bdraw'. There is no data to read from.")) : ;
WIDTH = BUFF[18] + BUFF[19] * 256;
HEIGHT = BUFF[22] + BUFF[23] * 256;
ofs = 54;
if(BUFF[0] != 'B' | BUFF[1] != 'M') error // debug2
("Warning: Data identifier error in function 'bmp_bdraw' occurred. Invalid BMP file loaded.");
for(H=HEIGHT-1; H>=0; H--)
{
for(W=0; W<WIDTH; W++)
{
B = sgetc(BUFF); // fgetc-like function but from buff
G = sgetc(BUFF);
R = sgetc(BUFF);
setpen(R, G, B, 0, 1); // sets the color, transparancy and size of the pen
putpixel(W, H); // and puts the pixel at the right location
}
}
if(W != WIDTH || H > 1) // debug3
error("Error in function 'bmp_bdraw'. Rendering failed. The file might be damaged.");
if(real_fps < 11)
error("Too low fps rate."); // debug4
}
If your input data here (BUFF buffer) is correct then
I am sharing my observation with you so you can debug further more.
You are rendering from (H(max),w(0)) to (H(0),w(max)). So in beging everything is good. Image is good but when your H(max) get decrease output image gets bad.
In simple term in bottom part of image output is good when in top part output is bad. It seems like pixel get strinked in top part.
I do not understand how setpen() works. and after processing you should paint that pixel at same position or not?
I think As H(max) decrease with that you should decrease your W value in putpixel(W, H); I think this will remove that inclined vertical line and bad image issue.
I am trying to use the C interface of CoreGraphics & CoreFoundation to save a buffer of 32-bit RGBA data (as a void*) to a PNG file. When I try to finialize the CGImageDestinationRef, the following error message is printed to the console:
libpng error: No IDATs written into file
As far as I can tell, the CGImageRef I'm adding to the CGImageDestinationRef is valid.
Relavent Code:
void saveImage(const char* szImage, void* data, size_t dataSize, size_t width, size_t height)
{
CFStringRef name = CFStringCreateWithCString(NULL, szImage, kCFStringEncodingASCII);
CFURLRef texture_url = CFURLCreateWithFileSystemPath(
NULL,
name,
kCFURLPOSIXPathStyle,
false);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, data, dataSize, NULL);
CGImageRef image = CGImageCreate(width, height, 8, 32, 32 * width, colorSpace,
kCGImageAlphaLast | kCGBitmapByteOrderDefault, dataProvider,
NULL, FALSE, kCGRenderingIntentDefault);
// From Image I/O Programming Guide, "Working with Image Destinations"
float compression = 1.0; // Lossless compression if available.
int orientation = 4; // Origin is at bottom, left.
CFStringRef myKeys[3];
CFTypeRef myValues[3];
CFDictionaryRef myOptions = NULL;
myKeys[0] = kCGImagePropertyOrientation;
myValues[0] = CFNumberCreate(NULL, kCFNumberIntType, &orientation);
myKeys[1] = kCGImagePropertyHasAlpha;
myValues[1] = kCFBooleanTrue;
myKeys[2] = kCGImageDestinationLossyCompressionQuality;
myValues[2] = CFNumberCreate(NULL, kCFNumberFloatType, &compression);
myOptions = CFDictionaryCreate( NULL, (const void **)myKeys, (const void **)myValues, 3,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFStringRef type = CFStringCreateWithCString(NULL, "public.png", kCFStringEncodingASCII);
CGImageDestinationRef dest = CGImageDestinationCreateWithURL(texture_url, type, 1, myOptions);
CGImageDestinationAddImage(dest, image, NULL);
if (!CGImageDestinationFinalize(dest))
{
// ERROR!
}
CFRelease(image);
CFRelease(colorSpace);
CFRelease(dataProvider);
CFRelease(dest);
CFRelease(texture_url);
}
This post is similar, except I'm not using the Objective C interface: Saving a 32 bit RGBA buffer into a .png file (Cocoa OSX)
Answering my own questions:
In addition to the issues pointed out by NSGod, the IDAT issue was an invalid parameter to CGImageCreate(): parameter 5 is bytesPerRow, not bitsPerRow. So 32 * width was incorrect; 4 * width is correct.
Despite what this page of the official documentation lists, UTCoreTypes.h is located in the CoreServices.framework for MacOSX, not MobileCoreServices.framework.
There are numerous issues with your code.
Here it is rewritten how I would do it:
void saveImage(const char* szImage, void* data, size_t dataSize, size_t width, size_t height)
{
CFStringRef name = CFStringCreateWithCString(NULL, szImage, kCFStringEncodingUTF8);
CFURLRef texture_url = CFURLCreateWithFileSystemPath(
NULL,
name,
kCFURLPOSIXPathStyle,
false);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, data,
dataSize, NULL);
CGImageRef image = CGImageCreate(width, height, 8, 32, 32 * width, colorSpace,
kCGImageAlphaLast | kCGBitmapByteOrderDefault,
dataProvider, NULL, FALSE, kCGRenderingIntentDefault);
// From Image I/O Programming Guide, "Working with Image Destinations"
float compression = 1.0; // Lossless compression if available.
int orientation = 4; // Origin is at bottom, left.
CFStringRef myKeys[3];
CFTypeRef myValues[3];
CFDictionaryRef myOptions = NULL;
myKeys[0] = kCGImagePropertyOrientation;
myValues[0] = CFNumberCreate(NULL, kCFNumberIntType, &orientation);
myKeys[1] = kCGImagePropertyHasAlpha;
myValues[1] = kCFBooleanTrue;
myKeys[2] = kCGImageDestinationLossyCompressionQuality;
myValues[2] = CFNumberCreate(NULL, kCFNumberFloatType, &compression);
myOptions = CFDictionaryCreate(NULL, (const void **)myKeys,
(const void **)myValues, 3, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CGImageDestinationRef dest =
CGImageDestinationCreateWithURL(texture_url, kUTTypePNG, 1, NULL);
CGImageDestinationAddImage(dest, image, NULL);
CGImageDestinationSetProperties(dest, myOptions);
if (!CGImageDestinationFinalize(dest))
{
// ERROR!
}
}
First, never use ASCII when dealing with file system paths, use UTF8. Second, you were constructing a dictionary to be used to set the properties of the image, but you were using it with the wrong function. The documentation for CGImageDestinationCreateWithURL() says the following:
CGImageDestinationCreateWithURL
Creates an image destination that writes to a location specified by a
URL.
CGImageDestinationRef CGImageDestinationCreateWithURL (
CFURLRef url,
CFStringRef type,
size_t count,
CFDictionaryRef options
);
Parameters
options - Reserved for future use. Pass NULL.
You were trying to pass a dictionary of properties when you were supposed to pass NULL. (Also, you can simply use the kUTTypePNG Uniform Type Identifier string constant instead of re-creating it). First call CGImageDestinationCreateWithURL(), then call CGImageDestinationAddImage() to add the image, then call CGImageDestinationSetProperties() and pass in the dictionary of properties you created.
[UPDATE]: If after these changes you're still having libpng error: No IDATs written into file issues, try the following: First, make sure that dataProvider is non-NULL-- in other words, make sure the CGDataProviderCreateWithData() function succeeded. Second, if dataProvider is valid, perhaps try changing the options from kCGImageAlphaLast | kCGBitmapByteOrderDefault to simply kCGImageAlphaPremultipliedLast and see if it succeeds.