Search code examples
iosobjective-creact-nativebase64cgimage

RNImageToBase64 native module crashes for a URL


I am trying to convert a React Native Asset into an Base64 image.

There is a React Native library for that implemented in NativeModules, but it crashes the native iOS app upon executing getBase64String(url, callback).

Example URL sent to RNImageToBase64 (generated by react-native-camera):

/var/mobile/Containers/Data/Application/8BE6DA73-73A6-4E9A-BDF9-431726243667/Documents/F76931EE-E8F6-431F-8FFF-D481DDA8CC6B.jpg

The line that fails:

[library assetForURL:url resultBlock:^(ALAsset *asset) {

The error in Xcode:

libsystem_kernel.dylib`__abort_with_payload:
    0x180640d6c <+0>:  movz   x16, #0x209
    0x180640d70 <+4>:  svc    #0x80
->  0x180640d74 <+8>:  b.lo   0x180640d8c               ; <+32>
    0x180640d78 <+12>: stp    x29, x30, [sp, #-16]!
    0x180640d7c <+16>: mov    x29, sp
    0x180640d80 <+20>: bl     0x1806257b4               ; cerror_nocancel
    0x180640d84 <+24>: mov    sp, x29
    0x180640d88 <+28>: ldp    x29, x30, [sp], #16
    0x180640d8c <+32>: ret    

index.ios.js line that calls NativeModules:

NativeModules.RNImageToBase64.getBase64String(uri, (err, base64) => {
});

RNImageToBase64.m the native module that crashes:

#import "RNImageToBase64.h"
#import <AssetsLibrary/AssetsLibrary.h>
#import <UIKit/UIKit.h>

@implementation RNImageToBase64

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(getBase64String:(NSString *)input callback:(RCTResponseSenderBlock)callback)
{
  NSURL *url = [[NSURL alloc] initWithString:input];
  ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
  [library assetForURL:url resultBlock:^(ALAsset *asset) {
    ALAssetRepresentation *rep = [asset defaultRepresentation];
    CGImageRef imageRef = [rep fullScreenImage];
    NSData *imageData = UIImagePNGRepresentation([UIImage imageWithCGImage:imageRef]);
    NSString *base64Encoded = [imageData base64EncodedStringWithOptions:0];
    callback(@[[NSNull null], base64Encoded]);
  } failureBlock:^(NSError *error) {
    NSLog(@"that didn't work %@", error);
    callback(@[error]);
  }];
}

@end

Solution

  • The problem was that it was trying to access images in the Assets Library.

    My solution consists of a bit modified version of the RNImageToBase64 module that works with the file paths on disk.

    #import "RNImageToBase64.h"
    #import <AssetsLibrary/AssetsLibrary.h>
    #import <UIKit/UIKit.h>
    #import <ImageIO/ImageIO.h>
    
    @implementation RNImageToBase64
    
    RCT_EXPORT_MODULE();
    
    RCT_EXPORT_METHOD(getBase64String:(NSString *)input callback:(RCTResponseSenderBlock)callback)
    {
      CGImageRef image = MyCreateCGImageFromFile(input);
      NSData *imageData = UIImagePNGRepresentation([UIImage imageWithCGImage:image]);
      NSString *base64Encoded = [imageData base64EncodedStringWithOptions:0];
      callback(@[[NSNull null], base64Encoded]);
    }
    
    CGImageRef MyCreateCGImageFromFile (NSString* path)
    {
        // Get the URL for the pathname passed to the function.
        NSURL *url = [NSURL fileURLWithPath:path];
        CGImageRef        myImage = NULL;
        CGImageSourceRef  myImageSource;
        CFDictionaryRef   myOptions = NULL;
        CFStringRef       myKeys[2];
        CFTypeRef         myValues[2];
    
        // Set up options if you want them. The options here are for
        // caching the image in a decoded form and for using floating-point
        // values if the image format supports them.
        myKeys[0] = kCGImageSourceShouldCache;
        myValues[0] = (CFTypeRef)kCFBooleanTrue;
        myKeys[1] = kCGImageSourceShouldAllowFloat;
        myValues[1] = (CFTypeRef)kCFBooleanTrue;
        // Create the dictionary
        myOptions = CFDictionaryCreate(NULL, (const void **) myKeys,
                                       (const void **) myValues, 2,
                                       &kCFTypeDictionaryKeyCallBacks,
                                       & kCFTypeDictionaryValueCallBacks);
        // Create an image source from the URL.
        myImageSource = CGImageSourceCreateWithURL((CFURLRef)url, myOptions);
        CFRelease(myOptions);
        // Make sure the image source exists before continuing
        if (myImageSource == NULL){
            fprintf(stderr, "Image source is NULL.");
            return  NULL;
        }
        // Create an image from the first item in the image source.
        myImage = CGImageSourceCreateImageAtIndex(myImageSource,
                                                  0,
                                                  NULL);
    
        CFRelease(myImageSource);
        // Make sure the image exists before continuing
        if (myImage == NULL){
            fprintf(stderr, "Image not created from image source.");
            return NULL;
        }
    
        return myImage;
    }
    
    @end