/** * Copyright (c) 2006-2015 LOVE Development Team * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. **/ #include "ImageIOHandler.h" #ifdef LOVE_SUPPORT_IMAGEIO // LOVE #include "common/Exception.h" #include "common/math.h" #include "filesystem/File.h" // ImageIO #include #if defined(LOVE_IOS) #include #elif defined(LOVE_MACOSX) #include #endif // STL #include #include using love::thread::Lock; namespace love { namespace image { namespace magpie { static const CFStringRef invalidFormat = CFSTR(""); static CFStringRef getFormatType(ImageData::Format format) { switch (format) { case ImageData::FORMAT_BMP: return kUTTypeBMP; case ImageData::FORMAT_JPG: return kUTTypeJPEG; case ImageData::FORMAT_TGA: return CFSTR("com.truevision.tga-image"); case ImageData::FORMAT_PNG: return kUTTypePNG; default: return invalidFormat; } } ImageIOHandler::ImageIOHandler() : mutex(nullptr) { mutex = love::thread::newMutex(); } ImageIOHandler::~ImageIOHandler() { delete mutex; } bool ImageIOHandler::canDecode(love::filesystem::FileData *data) { CFStringRef cfext = CFStringCreateWithCString(nullptr, data->getExtension().c_str(), kCFStringEncodingUTF8); CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, cfext, nullptr); CFRelease(cfext); if (!UTI) return false; CFArrayRef types = CGImageSourceCopyTypeIdentifiers(); Boolean isdecodable = CFArrayContainsValue(types, CFRangeMake(0, CFArrayGetCount(types)), UTI); CFRelease(UTI); CFRelease(types); return isdecodable; } bool ImageIOHandler::canEncode(ImageData::Format format) { CFStringRef cftype = getFormatType(format); if (cftype == invalidFormat) return false; // Is the format supported for writing on this system? CFArrayRef types = CGImageDestinationCopyTypeIdentifiers(); Boolean iswritable = CFArrayContainsValue(types, CFRangeMake(0, CFArrayGetCount(types)), cftype); CFRelease(types); return iswritable; } ImageIOHandler::DecodedImage ImageIOHandler::decode(love::filesystem::FileData *data) { Lock lock(mutex); DecodedImage img; CFDataRef cfdata = CFDataCreateWithBytesNoCopy(nullptr, (const UInt8 *) data->getData(), data->getSize(), kCFAllocatorNull); CGImageSourceRef source = CGImageSourceCreateWithData(cfdata, nullptr); CGImageRef image = CGImageSourceCreateImageAtIndex(source, 0, nullptr); CFRelease(cfdata); CFRelease(source); if (!image) throw love::Exception("Could not decode image!"); img.width = (int) CGImageGetWidth(image); img.height = (int) CGImageGetHeight(image); CGImageAlphaInfo alphainfo = CGImageGetAlphaInfo(image); size_t bpp = CGImageGetBitsPerPixel(image); // If the bpp doesn't match, we need to draw the image onto a new 32 bpp canvas if (bpp != 32) { // Create a new bitmap canvas CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(nullptr, img.width, img.height, 8, img.width*sizeof(pixel), colorspace, kCGImageAlphaPremultipliedLast); CGColorSpaceRelease(colorspace); if (!context) { CGImageRelease(image); throw love::Exception("Could not decode image: error converting to 32 bpp!"); } // Draw a black background CGContextSetRGBFillColor(context, 0, 0, 0, 1); CGContextFillRect(context, CGRectMake(0, 0, img.width, img.height)); CGContextDrawImage(context, CGRectMake(0, 0, img.width, img.height), image); // Replace the old image with the canvas CGImageRef contextimage = CGBitmapContextCreateImage(context); CGContextRelease(context); CGImageRelease(image); image = contextimage; if (!image) throw love::Exception("Could not decode image: error converting to 32 bpp!"); } cfdata = CGDataProviderCopyData(CGImageGetDataProvider(image)); CGImageRelease(image); img.size = CFDataGetLength(cfdata); try { img.data = new unsigned char[img.size]; } catch (std::bad_alloc &) { CFRelease(cfdata); throw love::Exception("Out of memory."); } CFDataGetBytes(cfdata, CFRangeMake(0, img.size), (UInt8 *) img.data); CFRelease(cfdata); // ImageIO might try to be "helpful" and premultiply the image's alpha for us, // now we have to un-premultiply it ourselves. Thanks... if (alphainfo == kCGImageAlphaPremultipliedLast) { pixel *pixels = (pixel *) img.data; for (int i = 0; i < img.width * img.height; i++) { unsigned char alpha = pixels[i].a; if (alpha > 0 && alpha < 255) { pixels[i].r = ((short) pixels[i].r * 255) / alpha; pixels[i].g = ((short) pixels[i].g * 255) / alpha; pixels[i].b = ((short) pixels[i].b * 255) / alpha; } } } return img; } ImageIOHandler::EncodedImage ImageIOHandler::encode(const DecodedImage &img, ImageData::Format format) { Lock lock(mutex); EncodedImage encodedimage; CFStringRef cftype = getFormatType(format); if (cftype == invalidFormat || !canEncode(format)) throw love::Exception("Could not encode image: format is not supported on this system."); CGDataProviderRef provider = CGDataProviderCreateWithData(nullptr, img.data, img.size, nullptr); CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); CGBitmapInfo bitmapinfo = kCGBitmapByteOrderDefault | kCGImageAlphaLast; CGColorRenderingIntent intent = kCGRenderingIntentDefault; size_t pixsize = sizeof(pixel); // Create a CoreGraphics image using the ImageData CGImageRef image = CGImageCreate(img.width, img.height, 8, 8*pixsize, img.width*pixsize, space, bitmapinfo, provider, nullptr, false, intent); CGColorSpaceRelease(space); CGDataProviderRelease(provider); CFMutableDataRef encodeddata = CFDataCreateMutable(nullptr, 0); if (!encodeddata) { CFRelease(image); throw love::Exception("Could not create image for encoding!"); } // Set up the encoder, using encodeddata as a chunk of memory to store the final result CGImageDestinationRef dest = CGImageDestinationCreateWithData(encodeddata, cftype, 1, nullptr); CGImageDestinationAddImage(dest, image, nullptr); // Encode the CoreGraphics image to the specified format bool success = CGImageDestinationFinalize(dest); CFRelease(image); CFRelease(dest); CFIndex encodedsize = CFDataGetLength(encodeddata); if (!success || encodedsize <= 0) { CFRelease(encodeddata); throw love::Exception("Could not encode image!"); } encodedimage.size = encodedsize; try { encodedimage.data = new unsigned char[encodedsize]; } catch (std::bad_alloc &) { CFRelease(encodeddata); throw love::Exception("Out of memory"); } CFDataGetBytes(encodeddata, CFRangeMake(0, encodedsize), encodedimage.data); CFRelease(encodeddata); return encodedimage; } } // magpie } // image } // love #endif // LOVE_SUPPORT_IMAGEIO