1 /* 2 * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 #import "ImageLoader.h" 27 28 #include "jni_utils.h" 29 #include "com_sun_javafx_iio_ios_IosImageLoader.h" 30 31 #include "debug.h" 32 33 // For incremental loading see: 34 //http://developer.apple.com/library/ios/#documentation/GraphicsImaging/Conceptual/ImageIOGuide/imageio_intro/ikpg_intro.html 35 36 37 @implementation ImageLoader 38 39 40 @synthesize buffer; 41 @synthesize width; 42 @synthesize height; 43 @synthesize nComponents; 44 @synthesize colorSpace; 45 @synthesize nImages; 46 @synthesize delayTime; 47 @synthesize loopCount; 48 @synthesize cgImageSource; 49 @synthesize cgImage; 50 51 -(id) init { 52 self = [super init]; 53 if (self) { 54 [self setBuffer : [NSMutableData data]]; 55 } 56 return self; 57 } 58 59 -(void) dealloc { 60 IIOLog(@"ImageLoader::dealloc"); 61 CFRelease(cgImageSource); 62 [buffer release]; 63 [super dealloc]; 64 } 65 66 -(void) addToBuffer : (const void *) bytes 67 length : (int) length { 68 [buffer appendBytes : bytes 69 length : length]; 70 } 71 72 -(BOOL) hasAlpha : (CGImageAlphaInfo) aInfo { 73 return aInfo != kCGImageAlphaNone; 74 } 75 76 -(BOOL) isAlphaPremultiplied : (CGImageAlphaInfo) aInfo { 77 return aInfo == kCGImageAlphaPremultipliedLast || 78 aInfo == kCGImageAlphaPremultipliedFirst; 79 } 80 81 -(int) resolveJavaColorSpace : (CGColorSpaceModel) nativeModel 82 alphaInfo : (CGImageAlphaInfo) aInfo { 83 84 int jColorSpace = -1; 85 86 if (nativeModel == kCGColorSpaceModelMonochrome) { 87 if ([self hasAlpha : aInfo]) { 88 if ([self isAlphaPremultiplied : aInfo]) { 89 jColorSpace = com_sun_javafx_iio_ios_IosImageLoader_GRAY_ALPHA_PRE; 90 } else { 91 jColorSpace = com_sun_javafx_iio_ios_IosImageLoader_GRAY_ALPHA; 92 } 93 } else { 94 jColorSpace = com_sun_javafx_iio_ios_IosImageLoader_GRAY; 95 } 96 } else if (nativeModel == kCGColorSpaceModelIndexed) { 97 if ([self hasAlpha : aInfo]) { 98 if ([self isAlphaPremultiplied : aInfo]) { 99 jColorSpace = com_sun_javafx_iio_ios_IosImageLoader_PALETTE_ALPHA_PRE; 100 } else { 101 jColorSpace = com_sun_javafx_iio_ios_IosImageLoader_PALETTE_ALPHA; 102 } 103 } else { 104 jColorSpace = com_sun_javafx_iio_ios_IosImageLoader_PALETTE; 105 } 106 // NOTE: what about com_sun_javafx_iio_ios_IosImageLoader_PALETTE_TRANS ??? 107 } else if (nativeModel == kCGColorSpaceModelRGB) { 108 if ([self hasAlpha : aInfo]) { 109 if ([self isAlphaPremultiplied : aInfo]) { 110 jColorSpace = com_sun_javafx_iio_ios_IosImageLoader_RGBA_PRE; 111 } else { 112 jColorSpace = com_sun_javafx_iio_ios_IosImageLoader_RGBA; 113 } 114 } else { 115 jColorSpace = com_sun_javafx_iio_ios_IosImageLoader_RGB; 116 } 117 } 118 119 /* 120 121 NOTE: what about the rest of color space models? The Java enum ImageType has no equivalents! 122 123 kCGColorSpaceModelUnknown, 124 kCGColorSpaceModelCMYK, 125 kCGColorSpaceModelLab, 126 kCGColorSpaceModelDeviceN, 127 kCGColorSpaceModelPattern 128 129 */ 130 131 return jColorSpace; 132 } 133 134 -(void) reportImageProperties { 135 IIOLog(@"Image size: [ %d x %d ]", [self width], [self height]); 136 IIOLog(@"Image color space: %d", [self colorSpace]); 137 IIOLog(@"Image number of components: %d", [self nComponents]); 138 IIOLog(@"Image number of images: %d", [self nImages]); 139 IIOLog(@"Image loop count: %d", [self loopCount]); 140 IIOLog(@"Image duration: %d", [self delayTime]); 141 } 142 143 -(void) retrieveDelayTime { 144 145 NSDictionary *dict = (NSDictionary *) CGImageSourceCopyPropertiesAtIndex(cgImageSource, 0, NULL); 146 NSDictionary *gifDict = (NSDictionary *) [dict objectForKey : (id) kCGImagePropertyGIFDictionary]; 147 148 int delay = 100; // 100ms default if no time interval is retrieved 149 int nLoopCount = 1; 150 if (gifDict) { 151 NSNumber *delayValue = [gifDict objectForKey : (id) kCGImagePropertyGIFDelayTime]; 152 delay = (int) ([delayValue doubleValue] * 1000); 153 NSNumber *loopCountValue = [gifDict objectForKey : (id) kCGImagePropertyGIFLoopCount]; 154 nLoopCount = [loopCountValue intValue]; 155 } 156 157 [self setDelayTime : delay]; 158 [self setLoopCount : nLoopCount]; 159 } 160 161 -(CGImageRef) createImageAtIndex : (int) index { 162 163 CGImageRef cgImageRef = CGImageSourceCreateImageAtIndex(cgImageSource, index, NULL); 164 return cgImageRef; 165 } 166 167 -(BOOL) loadFromSource : (CGImageSourceRef) sourceRef 168 JNIEnv : (JNIEnv *) env { 169 170 [self setCgImageSource : sourceRef]; 171 172 size_t imageCount = CGImageSourceGetCount(sourceRef); 173 [self setNImages : (int) imageCount]; 174 175 if (imageCount > 1) { 176 [self retrieveDelayTime]; 177 } 178 179 BOOL success = FALSE; 180 181 CGImageRef firstImage = [self createImageAtIndex : 0]; 182 183 if (firstImage) { 184 [self setCgImage : firstImage]; 185 [self setWidth : (int) CGImageGetWidth(firstImage)]; 186 [self setHeight : (int) CGImageGetHeight(firstImage)]; 187 188 CGColorSpaceRef cgColorSpace = CGImageGetColorSpace(firstImage); 189 CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(firstImage); 190 191 int nComp = (int) CGColorSpaceGetNumberOfComponents(cgColorSpace); 192 if ([self hasAlpha : alphaInfo]) { 193 nComp++; 194 } 195 196 [self setNComponents : nComp]; 197 198 CGColorSpaceModel spaceModel = CGColorSpaceGetModel(cgColorSpace); 199 [self setColorSpace : 200 [self resolveJavaColorSpace : spaceModel 201 alphaInfo : alphaInfo]]; 202 203 [self reportImageProperties]; 204 205 success = TRUE; 206 } else { 207 // NOTE: see what went wrong (RT-27439) 208 //CGImageSourceStatus status = CGImageSourceGetStatus(sourceRef); 209 /*enum CGImageSourceStatus { 210 kCGImageStatusUnexpectedEOF = -5, 211 kCGImageStatusInvalidData = -4, 212 kCGImageStatusUnknownType = -3, 213 kCGImageStatusReadingHeader = -2, 214 kCGImageStatusIncomplete = -1, 215 kCGImageStatusComplete = 0 216 };*/ 217 218 throwException(env, JAVA_IO_IOEXCEPTION, "Unable to decode image"); 219 } 220 221 return success; 222 } 223 224 -(BOOL) loadFromURL : (NSString *) urlString 225 JNIEnv : (JNIEnv *) env { 226 227 NSURL *url = [NSURL URLWithString : urlString]; 228 229 BOOL success = FALSE; 230 231 if ([url isFileURL]) { 232 NSError *error; 233 BOOL isReachable = [url checkResourceIsReachableAndReturnError : &error]; 234 235 if (!isReachable) { 236 NSString *nsErrorMessage = [NSString stringWithFormat : @"%@ (%@), Recovery suggestion: %@", 237 [error localizedFailureReason], 238 [error localizedDescription], 239 [error localizedRecoverySuggestion]]; 240 241 const char *errorMessage = [nsErrorMessage UTF8String]; 242 throwException(env, JAVA_IO_IOEXCEPTION, errorMessage); 243 return FALSE; 244 } 245 } 246 247 CGImageSourceRef sourceRef = CGImageSourceCreateWithURL((CFURLRef) url, NULL); 248 if (sourceRef == NULL) { 249 throwException(env, JAVA_IO_IOEXCEPTION, "Failed to create CGImageSource"); 250 } else { 251 success = [self loadFromSource : sourceRef 252 JNIEnv : env]; 253 } 254 255 return success; 256 } 257 258 -(BOOL) loadFromBuffer : (JNIEnv *) env { 259 260 BOOL success = FALSE; 261 262 CGImageSourceRef sourceRef = CGImageSourceCreateWithData((CFTypeRef) buffer, NULL); 263 264 if (sourceRef == NULL) { 265 throwException(env, JAVA_IO_IOEXCEPTION, "Failed to create CGImageSource"); 266 } else { 267 success = [self loadFromSource : sourceRef 268 JNIEnv : env]; 269 } 270 271 return success; 272 } 273 274 -(BOOL) hasLessThan8BitsPerChannel { 275 return CGImageGetBitsPerPixel([self cgImage]) < 24; 276 } 277 278 /* 279 280 In order to resize an image it is necessary to create a new bitmap context. However, not all 281 color spaces are supported for new bitmap contexts. Therefore all non-alpha images are 282 converted to 283 284 - kCGImageAlphaNoneSkipLast 285 286 and all images with an alpha component are converted to 287 288 - kCGImageAlphaPremultipliedLast 289 290 (Not premultiplied RGB is not supported) 291 292 For the list of all supported pixel formats, see 293 294 https://developer.apple.com/library/ios/#documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_context/dq_context.html 295 296 (Supported Pixel Formats section) 297 298 */ 299 300 -(void) resize : (int) newWidth : (int) newHeight { 301 302 // in case a different size is requested or the original color model uses less than 8 bits per component, create a new bitmap context 303 if ([self width] != newWidth || 304 [self height] != newHeight || 305 [self hasLessThan8BitsPerChannel]) { 306 307 const CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage); 308 309 const CGBitmapInfo bitmapInfo = [self hasAlpha : alphaInfo] ? 310 kCGImageAlphaPremultipliedLast : kCGImageAlphaNoneSkipLast; 311 312 const int nComp = 4; 313 CGColorSpaceRef cgColorSpace = CGColorSpaceCreateDeviceRGB(); 314 CGContextRef cgContext = CGBitmapContextCreate( 315 NULL, 316 newWidth, 317 newHeight, 318 8, 319 nComp * newWidth, 320 cgColorSpace, 321 bitmapInfo); 322 323 CGContextDrawImage(cgContext, 324 CGRectMake(0.0f, 0.0f, 325 (CGFloat) newWidth, 326 (CGFloat) newHeight), 327 cgImage); 328 329 CGImageRef cgImageNew = CGBitmapContextCreateImage(cgContext); 330 331 IIOLog(@"Releasing old cgImage before assigning new one"); 332 CGImageRelease(cgImage); 333 334 [self setCgImage : cgImageNew]; 335 [self setNComponents : nComp]; 336 [self setColorSpace : [self resolveJavaColorSpace : CGColorSpaceGetModel(cgColorSpace) 337 alphaInfo : bitmapInfo]]; 338 339 CGContextRelease(cgContext); 340 341 IIOLog(@"Image was resized to %dx%d", newWidth, newHeight); 342 } 343 } 344 345 -(CFDataRef) copyImagePixels : (CGImageRef) image { 346 return CGDataProviderCopyData(CGImageGetDataProvider(image)); 347 } 348 349 -(jbyteArray) getDecompressedBuffer : (JNIEnv *) env 350 imageIndex : (int) imageIndex { 351 352 jboolean iscopy = FALSE; 353 354 int bufferSize = [self width] * [self height] * [self nComponents]; 355 jbyteArray outBuffer = (*env)->NewByteArray(env, bufferSize); 356 if (outBuffer == NULL) { 357 throwException(env, 358 JAVA_OOM_ERROR, 359 "Cannot initilialize memory buffer for decoded image data"); 360 } else { 361 jbyte *jByteBuffer = (*env)->GetPrimitiveArrayCritical(env, 362 outBuffer, 363 &iscopy); 364 365 CFDataRef cfDataRef = [self copyImagePixels : cgImage]; 366 size_t dataLength = (size_t) CFDataGetLength(cfDataRef); 367 memcpy(jByteBuffer, (UInt8 *) CFDataGetBytePtr(cfDataRef), dataLength); 368 CFRelease(cfDataRef); 369 370 (*env)->ReleasePrimitiveArrayCritical(env, 371 outBuffer, 372 jByteBuffer, 373 JNI_ABORT); 374 375 CGImageRelease(cgImage); 376 377 // prepare the next image of an animated image 378 if (imageIndex + 1 < nImages) { 379 CGImageRef image = [self createImageAtIndex : imageIndex]; 380 [self setCgImage : image]; 381 } 382 383 IIOLog(@"ImageLoader: sent %lu data to Java", dataLength); 384 } 385 386 return outBuffer; 387 } 388 389 @end