Search code examples
javascriptcocoabase64applescriptjavascript-automation

Encode / Decode PNGs to base64 strings in JXA/JavaScript


I am trying to write a JXA script in Apple Script Editor that converts PNG files to base64 strings, which can then be added to a JSON object.

I cannot seem to find a JXA method that works for doing the base64 encoding /decoding part.

I came across a droplet which was written using Shell Script that outsources the task to openssl and then outputs a .b64 file:

for f in "$@"
do
    openssl base64 -in "$f" -out "$f.b64"
done

So I was thinking of Frankenstein'ing this up to a method that uses evalAS to run inline AppleScript, per the example:

(() => {
    'use strict';

    // evalAS2 :: String -> IO a
    const evalAS2 = s => {
        const a = Application.currentApplication();
        return (a.includeStandardAdditions = true, a)
            .runScript(s);
    };

    return evalAS2(
        'use scripting additions\n\
         for f in' + '\x22' + file + '\x22\n'
         do
         openssl base64 -in "$f" -out "$f.b64"
         done'
    );
})();

And then re-opening the .b64 file in the script, but this all seems rather long-winded and clunky.

I know that it is possible to use Cocoa in JXA scripts, and I see that there are methods for base64 encoding/decoding in Cocoa...

As well as Objective-C:

NSData *imageData = UIImagePNGRepresentation(myImageView.image);
NSString * base64String = [imageData base64EncodedStringWithOptions:0];

The JXA Cookbook has a whole section going over Syntax for Calling ObjC functions, which I am trying to read over. From what I understand, it should look something like:

var image_to_convert = $.NSData.alloc.UIImagePNGRepresentation(image)
var image_as_base64 = $.NSString.alloc.base64EncodedStringWithOptions(image_to_convert) 

But I just am a total noob to this, so it is still difficult for me to understand it all.

In the speculative code above, I am not sure where I would get the image data from?

I am currently trying:

ObjC.import("Cocoa");
var image = $.NSImage.alloc.initWithContentsOfFile(file)
console.log(image);
var image_to_convert = $.NSData.alloc.UIImagePNGRepresentation(image)
var image_as_base64 = $.NSString.alloc.base64EncodedStringWithOptions(image_to_convert)

But it is resulting in the following errors:

$.NSData.alloc.UIImagePNGRepresentation is not a function. (In '$.NSData.alloc.UIImagePNGRepresentation(image)', '$.NSData.alloc.UIImagePNGRepresentation' is undefined)

I am guessing it is because UIImagePNGRepresentation is of the UIKit framework, which is an iOS thing and not OS X?

I came across this post, which suggests this:

NSArray *keys = [NSArray arrayWithObject:@"NSImageCompressionFactor"];
NSArray *objects = [NSArray arrayWithObject:@"1.0"];
NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:objects forKeys:keys];

NSImage *image = [[NSImage alloc] initWithContentsOfFile:[imageField stringValue]];
NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithData:[image TIFFRepresentation]];
NSData *tiff_data = [imageRep representationUsingType:NSPNGFileType properties:dictionary];

NSString *base64 = [tiff_data encodeBase64WithNewlines:NO];

But again, I have no idea how this translates to JXA. I just am determined to get something working.

I was hoping that there was some way of just doing it in plain old JavaScript that will work in a JXA script?

I look forward to any answers and/or pointers that you might be able to provide. Thank you all in advance!


Solution

  • I'm sorry I never worked with JXA but a lot in Objective-C.

    I think You are getting the compile errors, because You are trying to always allocate new Objects.

    I think it should be the simply:

    ObjC.import("Cocoa");
    var imageData = $.NSData.alloc.initWithContentsOfFile(file);
    console.log(imageData);
    var image_as_base64 = imageData.base64EncodedStringWithOptions(0); // Call method of allocated object
    

    0 is a constant for Base64 encodings to just get the base64 String.

    edit:
    var theString = ObjC.unwrap(image_as_base64);
    

    This to make the value visible to JXA