Tuesday, March 18, 2008

ImageIO OpenGL and Radiance .hdr Files

I promised to post my ImageIO code once I had things working, and now that it's working, I'm going to do just that. I just worked through the last bug I had in my texturing code. 

If you're using ImageIO to read Radiance .hdr files, and you're getting garbage, check the type parameter you're passing to glTexImage2D. The Apple documentation concerning ImageIO and OpenGL uses the format GL_UNSIGNED_INT_8_8_8_8_REV. While .hdr files are floating point, its the REV you should take note of.  The byte ordering of each R, G, B component is individually reversed between what ImageIO provides and OpenGL expects. Simply call glPixelStorei(GL_UNPACK_SWAP_BYTES,1) and things should look a bit more normal.

I hope this saves someone else a few minutes. 





Boolean osx_CGImageSupportsFileFormat(std::string format) {
CFArrayRef imageFormats = CGImageSourceCopyTypeIdentifiers();
CFStringRef targetImageFormat = CFStringCreateWithCString( kCFAllocatorDefault, format.c_str(), kCFStringEncodingUTF8 );
CFRange imageFormatsRange = CFRangeMake( 0, CFArrayGetCount( imageFormats ) );
Boolean didContainFormat = CFArrayContainsValue(imageFormats, CFRangeMake(0, CFArrayGetCount(imageFormats)), targetImageFormat);
CFRelease(targetImageFormat);
CFRelease(imageFormats);
return didContainFormat;
}

CGImageRef osx_CGImageCreateFromFile(std::string filename) {
CGImageRef image = 0;
CFStringRef fileString = 0;
CFURLRef fileURL = 0;
CGImageSourceRef imageSource = 0;
CFDictionaryRef imageSourceOptions = 0;
CFStringRef imageSourceKeys[] = { kCGImageSourceShouldAllowFloat, kCGImageSourceShouldCache };
CFBooleanRef imageSourceValues[] = { kCFBooleanTrue, kCFBooleanTrue };

try {
fileString = CFStringCreateWithCString( kCFAllocatorDefault, filename.c_str(), kCFStringEncodingUTF8 );
if (fileString == 0) throw std::runtime_error("CFStringCreateWithCString");

fileURL = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, fileString, kCFURLPOSIXPathStyle, 0);
if (fileURL == 0) throw std::runtime_error("CFURLCreateWithFileSystemPath");

imageSource = CGImageSourceCreateWithURL(fileURL, 0);
if (imageSource == 0) throw std::runtime_error("CGImageSourceCreateWithURL");

imageSourceOptions = CFDictionaryCreate( kCFAllocatorDefault, (const void**)&imageSourceKeys, (const void**)&imageSourceValues, 1, 0, 0 );
if (imageSourceOptions == 0) throw std::runtime_error("CFDictionaryCreate");

image = CGImageSourceCreateImageAtIndex(imageSource, 0, imageSourceOptions);
if (image == 0) throw std::runtime_error("CGImageSourceCreateImageAtIndex");
}
catch(std::runtime_error &problem) {
std::cerr << problem.what() << std::endl;
}

CFRelease(imageSourceOptions);
CFRelease(imageSource);
CFRelease(fileURL);
CFRelease(fileString);

return image;
}

void osx_CGImageCrossCubeToImageCube(CGImageRef source, ImageCube &imageCube) {
size_t x = CGImageGetWidth(source) / 3;
size_t y = CGImageGetHeight(source) / 4;

assert( x == y ); // Images should be square. Could scale to ensure this instead of aborting.

CGRect imageBounds[6] = {0};

imageBounds[0] = CGRectMake(2*x, y, x, y); // +x 1
imageBounds[1] = CGRectMake(0, y, x, y); // -x 0
imageBounds[2] = CGRectMake(x, 2*y, x, y); // -y 2
imageBounds[3] = CGRectMake(x, 0, x, y); // +y 3
imageBounds[4] = CGRectMake(x, y, x, y); // -z 4
imageBounds[5] = CGRectMake(x, 3*y, x, y); // +z 5

imageCube.size = x;
imageCube.bitsPerComponent = CGImageGetBitsPerComponent(source);
imageCube.numComponents = 4;

const size_t bytesPerRow = imageCube.size * imageCube.numComponents * imageCube.bitsPerComponent / 8;

CGImageRef partitions[6] = {0};
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

for (int i = 0; i < 6; ++i) {
imageCube.image[i].resize( imageCube.size * bytesPerRow );
partitions[i] = CGImageCreateWithImageInRect(source, imageBounds[i]);
CGContextRef imageContext = CGBitmapContextCreate( &imageCube.image[i][0],
imageCube.size, imageCube.size,
imageCube.bitsPerComponent,
bytesPerRow,
colorSpace,
kCGImageAlphaNoneSkipLast | kCGBitmapFloatComponents );

CGContextSaveGState(imageContext);

// Up Down and Backwards are flipped in the cube cross format
if ( i == 2 || i == 3 || i == 5 ) {
CGContextTranslateCTM(imageContext, imageCube.size, imageCube.size);
CGContextScaleCTM(imageContext, -1.0f, -1.0f);
}

CGContextDrawImage(imageContext, CGRectMake(0, 0, imageCube.size, imageCube.size), partitions[i]);
CGContextRestoreGState(imageContext);
CGContextRelease(imageContext);
CGImageRelease(partitions[i]);
}

CGColorSpaceRelease(colorSpace);
}

No comments: