How to convert UIImage to Juce Image


#1

I'm implementing Inter-App Audio in my iPad app, and I need to pull the host app's icon to display in my interface when it's connected. Looking through Juce's CoreGraphicsImage class I've pieced together the following:

​
    UIImage* uiImage = (UIImage*) deviceManager.getCurrentAudioDevice()->getHostIcon(); // this is a method I added that retreives the UIImage from the host app and returns a void*, hence the cast

    CGImageRef loadedImage = uiImage.CGImage;
    
    if (loadedImage != 0)
    {
        CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo (loadedImage);
        const bool hasAlphaChan = (alphaInfo != kCGImageAlphaNone
                                   && alphaInfo != kCGImageAlphaNoneSkipLast
                                   && alphaInfo != kCGImageAlphaNoneSkipFirst);
        
        int width = (int) CGImageGetWidth (loadedImage);
        int height = (int) CGImageGetHeight (loadedImage);
        
        Image image (NativeImageType().create (Image::ARGB,
                                               width,
                                               height,
                                               hasAlphaChan));
        
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        
        Image::BitmapData bitmap (image, Image::BitmapData::readWrite);
        
        int pixelStride = 4;
        int lineStride = (pixelStride * jmax (1, width) + 3) & ~3;
        
        CGContextRef context = CGBitmapContextCreate(bitmap.data, (size_t) width, (size_t) height, 8, (size_t) lineStride, colorSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big);
        
        CGColorSpaceRelease(colorSpace);
        
        CGContextDrawImage(context, CGRectMake(0, 0, width, height), loadedImage);
        
        CGContextFlush(context);
        
        image.getProperties()->set ("originalImageHadAlpha", hasAlphaChan);
        
        return image;
    }

This retrieves the image, but the coloring is messed up. For example the Garageband icon should look like:

But instead looks like:

Any ideas?


#2

Solved. I made a simplified copy of the CoreGraphicsImage class with only the methods I need, which allowed me to closer mimic the juce_loadWithCoreImage method. Here it is:

​
class CoreGraphicsImageSimple : public ImagePixelData
{
public:
    CoreGraphicsImageSimple (const Image::PixelFormat format, const int w, const int h, const bool clearImage)
    : ImagePixelData (format, w, h), cachedImageRef (0)
    {
        pixelStride = format == Image::RGB ? 3 : ((format == Image::ARGB) ? 4 : 1);
        lineStride = (pixelStride * jmax (1, width) + 3) & ~3;
        
        imageData.allocate ((size_t) (lineStride * jmax (1, height)), clearImage);
        
        CGColorSpaceRef colourSpace = (format == Image::SingleChannel) ? CGColorSpaceCreateDeviceGray()
        : CGColorSpaceCreateDeviceRGB();
        
        context = CGBitmapContextCreate (imageData, (size_t) width, (size_t) height, 8, (size_t) lineStride,
                                         colourSpace, getCGImageFlags (format));
        
        CGColorSpaceRelease (colourSpace);
    }
    
    ~CoreGraphicsImageSimple()
    {
        freeCachedImageRef();
        CGContextRelease (context);
    }
    
    
    
    //==============================================================================
    CGContextRef context;
    CGImageRef cachedImageRef;
    HeapBlock<uint8> imageData;
    int pixelStride, lineStride;
    
private:
    void freeCachedImageRef()
    {
        if (cachedImageRef != 0)
        {
            CGImageRelease (cachedImageRef);
            cachedImageRef = 0;
        }
    }
    
    static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format)
    {
#if JUCE_BIG_ENDIAN
        return format == Image::ARGB ? (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big) : kCGBitmapByteOrderDefault;
#else
        return format == Image::ARGB ? (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little) : kCGBitmapByteOrderDefault;
#endif
    }
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreGraphicsImageSimple)
};

Image HostIconRetreiver::getHostIcon(AudioDeviceManager& deviceManager)
{
    UIImage* uiImage = (UIImage*) deviceManager.getCurrentAudioDevice()->getHostIcon();
    CGImageRef loadedImage = uiImage.CGImage;
    
    if (loadedImage != 0)
    {
        CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo (loadedImage);
        const bool hasAlphaChan = (alphaInfo != kCGImageAlphaNone
                                   && alphaInfo != kCGImageAlphaNoneSkipLast
                                   && alphaInfo != kCGImageAlphaNoneSkipFirst);
        
        Image image (NativeImageType().create (Image::ARGB, // (CoreImage doesn't work with 24-bit images)
                                               (int) CGImageGetWidth (loadedImage),
                                               (int) CGImageGetHeight (loadedImage),
                                               hasAlphaChan));
        
        CoreGraphicsImageSimple* const cgImage = static_cast<CoreGraphicsImageSimple*> (image.getPixelData());
        jassert (cgImage != nullptr); // if USE_COREGRAPHICS_RENDERING is set, the CoreGraphicsImageSimple class should have been used.
        
        CGContextDrawImage (cgImage->context, CGRectMake(0, 0, image.getWidth(), image.getHeight()), loadedImage);
        CGContextFlush (cgImage->context);
        
#if ! JUCE_IOS
        CFRelease (loadedImage);
#endif
        
        // Because it's impossible to create a truly 24-bit CG image, this flag allows a user
        // to find out whether the file they just loaded the image from had an alpha channel or not.
        image.getProperties()->set ("originalImageHadAlpha", hasAlphaChan);
        return image;
    }
    
    return Image::null;
}