Encode / Decode PNGs to base64 strings in JXA/JavaScript

Viewed 356

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!

2 Answers

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

Use below code. Read the file to var file from jquery file input element using FileReader in 'readDataAsURL'. Then you will have your png as a string in base64 format.

You may need to split the base64 string with ',' to get the actual data part of the string, which you can include in a JSON and send it to the backend via an API.

var file = $('#fileUpload').prop('files')[0];
var base64data;
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function() {
  base64data = reader.result;
  var dataUrl = base64data.split(",");
};

Usually the base64 string you will get be in this form.

'data:image/png;base64,STREAM_OF_SOME_CHARACTERS...

So the STREAM_OF_SOME_CHARACTERS...(dataUrl) is where actually the image data is in.

Furthermore you can open the image in a HTML page with

<img src=base64data>
Related