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