Working with iOS Core types

I am trying to create a thumbnail for an image using the iOS APIs. I already created an image source using the URL for the file containing the original image. I’ll assume that I created the source correctly for now.

When I run the following code, the IllegalStateException is thrown:

private fun createThumbnail(originalImageSource: CGImageSourceRef, originalWidth: Int, originalHeight: Int): CGImageRef {
    val result: CGImageRef

    var options = NSMutableDictionary.alloc()
    try {
        options = options.initWithCapacity(3)
        var size = NSNumber.alloc()
        try {
            // Max length of shortest size hardcoded to 100 pixels at the moment. This will become a parameter.
            size = size.initWithLong((100 / Math.min(originalWidth, originalHeight) * Math.max(originalWidth, originalHeight)).toLong())
            val optionsAsCfMutableDictionary = ObjCRuntime.cast(options, CFMutableDictionaryRef::class.java)
            val sizeAsCfNumber = ObjCRuntime.cast(size, CFNumberRef::class.java)
            CFDictionaryAddValue(optionsAsCfMutableDictionary, kCGImageSourceThumbnailMaxPixelSize(), sizeAsCfNumber)
            CFDictionaryAddValue(optionsAsCfMutableDictionary, kCGImageSourceCreateThumbnailFromImageAlways(), kCFBooleanTrue())
            CFDictionaryAddValue(optionsAsCfMutableDictionary, kCGImageSourceCreateThumbnailFromImageIfAbsent(), kCFBooleanTrue())
            result = CGImageSourceCreateThumbnailAtIndex(originalImageSource, 0, optionsAsCfMutableDictionary) ?: throw IllegalStateException("No thumbnail image")
        } finally {
            size.dealloc()
        }
    } finally {
        options.dealloc()
    }

    return result
}

Are the parameters I pass to CGImageSourceCreateThumbnailAtIndex incorrect? Is my image source incorrect? Is there a way to get the error code or message from CGImageSourceCreateThumbnailAtIndex?

Never mind. The following expression results in 0. 100 used to be a float in the original RoboVM implementation:

100 / Math.min(originalWidth, originalHeight) * Math.max(originalWidth, originalHeight)

Hi Johan,

100 is an integer according to the Java specs, so I guess it was a bug in RoboVM.

Is there a reason why you are accessing the NSMutableDictionary using the Core Foundation methods instead of just converting it when necessary for the CGImageSourceCreateThumbnailAtIndex()?

Also, you should not call dealloc() or release() on Objective-C objects, these will be handled by the Java GC and NatJ automatically. We are thinking of removing these methods from future versions of the bindings so they are not confusing anyone.

There could be cases, where you are allocating a lot of huge native objects in a tight loop, that are not used afterwards, and the Java GC may take too much time to release them (meaning that iOS may kill your app due to high memory usage before that happens), then you can call ObjCRuntime.disposeObject() to force the disposal of the native peers. But this is the exception to the rule and should not be used in most cases.

Best Regards,
Gergely

Hi Gergely,

Thanks for the information. It already made the code a lot cleaner. And even better: I got it working! :smile:

In the RoboVM implementation 100 was actually a Float variable, so the resulting Long would not be zero.

When I use this code, the thumbnail is not created; the exception is thrown. I assume this happens because the keys and/or values are converted incorrectly:

val options = NSMutableDictionary.alloc().initWithCapacity(2) as NSMutableDictionary<String, Any>
options.put(toJavaString(kCGImageSourceThumbnailMaxPixelSize()), 100L)
options.put(toJavaString(kCGImageSourceCreateThumbnailFromImageAlways()), true)
val cfOptions = ObjCRuntime.cast(options, CFDictionaryRef::class.java)
result = CGImageSourceCreateThumbnailAtIndex(originalImageSource, 0, cfOptions) ?: throw IllegalStateException("No thumbnail image")

I got it working with an NSMutableDictionary. Turns out I need to use NSNumber.alloc().initWithBool(true) instead of true, and NSNumber.alloc().initWithLong(longValue) instead of longValue.

Hi, I afraid of removing release mathod.
Assume we have large software canvas and at some point I want to free its memory and forget the object.

How can I do that? GC may never happens and I need to free native memory for other purposes at the specific point in the program.

As I wrote above, you can still use the ObjCRuntime.disposeObject() for this purpose. Calling the release() or dealloc() method as they are now (meaning they simply emit the ObjC message) actually messes up the memory management inside NatJ.

Where can I are exampe of
ObjCRuntime.disposeObject() usage ?