Using Core Graphics to change an images colorspace profile using AppleScriptObjC

425 views Asked by At

I already have some code that will do most of what I need using NSIMage and NSColorSpace. Unfortunatly I am trying to recreate a colorspace/profile change that happens in Photoshop, and it is a bit more complex than what NSColorSpace can do. You can see that post here: Using ApplescriptObjC to convert color spaces of an image using NSColorSpace and iccProfileData

So what I need help with is either adding in the following from CGColorSpace or recreating certain parts of the script so they work from the start with Core Graphics. The functions that I am looking to accomplish are: CGColorRenderingIntent using kCGRenderingIntentPerceptual kCGColorConversionBlackPointCompensation Plus using dithering as a part of this color space conversion, but I can't seem to find an option for that in the Apple Objective-C documentation. NSColor does have NSColorRenderingIntentPerceptual but it does not seem like there is the BlackPointCompensation under NSColor.

I think I have identified all the parts I need to build this script. I think the script is partway written already. I just need some help gluing the last few bits together.

I believe the script will still need to open the profile into NSData (The file is POSIX file reference to the ICC Profile that I am using)

set theData to current application's NSData's dataWithContentsOfFile:theFile

Now I need to open the image, my hope that this is the same whether using NSColor or CGColor:

set theInput to (choose file with prompt "Choose RGB file")
set theOutput to (choose file name default name "Untitled.jpg")
set theImage to current application's NSImage's alloc()'s initWithContentsOfURL:theInput
set imageRep to theImage's representations()'s objectAtIndex:0

Here is what I see the line of code that I need the most help with. This is actually where the color conversion is happening with NSColorSpace:

set targetSpace to current application's NSColorSpace's alloc's initWithICCProfileData:theData

It seems like I should be using CGColorSpaceCreateICCBased with CGDataProviderRef and then theFile, but I doubt that I can just put those in place of the NSColorSpace and initWithICCProfileData. I also need to graft onto this line, or a new line, the CGColorRenderingIntent using kCGRenderingIntentPerceptual and kCGColorConversionBlackPointCompensation (With dither if that option even exists).

I am not sure if the next two lines need to be updated, but I am pretty sure that the third line can stay the same (or I am really stupid, forgive me).

set theProps to current application's NSDictionary's dictionaryWithObjects:{1.0, true} forKeys:{current application's NSImageCompressionFactor, current application's NSImageProgressive}
set jpegData to bitmapRep's representationUsingType:(current application's NSJPEGFileType) |properties|:theProps
jpegData's writeToURL:theOutput atomically:true

So the input would be an RGB with an generic sRGB profile file and the output would be a CMYK file with a specific CMYK Profile (GRACoL2013_CRPC6.icc to be exact).

1

There are 1 answers

5
CJK On

The input would be an RGB with an generic sRGB profile file and the output would be a CMYK file with a specific CMYK Profile (GRACoL2013_CRPC6.icc)

If this accurately summarises the objective, you ought to be able to do this using Image Events, which is an AppleScriptable faceless program to manipulate images.

Played around with Image Events, but embedding a new colour profile—which ought to be possible—doesn't appear to take, and the original colour profile remains.

So I wrote the AppleScriptObjC equivalent:

use framework "Foundation"
use framework "AppKit"
use scripting additions

property this : a reference to the current application
property nil : a reference to missing value
property _1 : a reference to reference

property NSBitmapImageRep : a reference to NSBitmapImageRep of this
property NSColorSpace : a reference to NSColorSpace of this
property NSData : a reference to NSData of this
property NSImage : a reference to NSImage of this
property NSString : a reference to NSString of this
property NSURL : a reference to NSURL of this

property JPEG : a reference to 3
property PNG : a reference to 4
property NSFileType : {nil, nil, "jpg", "png"}
property options : {NSImageCompressionFactor:0.75, NSImageProgressive:true ¬
    , NSImageColorSyncProfileData:a reference to iccData}

property NSColorRenderingIntent : {Default:0, AbsoluteColorimetric:1 ¬
    , RelativeColorimetric:2, Perceptual:3, Saturation:4}
--------------------------------------------------------------------------------
# IMPLEMENTATION:
set iccProfile to loadICCProfile("~/Path/To/GRACoL2013_CRPC6.icc")
set image to _NSImage("~/Path/To/SourceImage.jpg")
set path of image to "~/Path/To/OutputImage.jpg" -- omit to overwrite source
set iccData to iccProfile's space's ICCProfileData()

my (write image for iccProfile given properties:contents of options)
--------------------------------------------------------------------------------
# HANDLERS & SCRIPT OBJECTS:
# __NSURL__()
#   Takes a posix +filepath and returns an NSURL object reference
to __NSURL__(filepath)
    local filepath

    try
        NSURL's fileURLWithPath:((NSString's ¬
            stringWithString:filepath)'s ¬
            stringByStandardizingPath())
    on error
        missing value
    end try
end __NSURL__

# new()
#   Instantiates a new NSObject
on new(_nsObject)
    local _nsObject
    _nsObject's alloc()
end new

# _NSImage()
#   Creates a new NSImage instance with image data loaded from the +filepath
on _NSImage(filepath)
    local filepath

    script
        property file : __NSURL__(filepath)
        property data : new(NSImage)
        property kind : JPEG
        property path : nil -- write path (nil = overwrite source)
        property size : nil
        property name extension : NSFileType's item kind

        to init()
            my (data's initWithContentsOfURL:(my file))
        end init

        to lock()
            tell my data to lockFocus()
        end lock

        to unlock()
            tell my data to unlockFocus()
        end unlock
    end script

    tell the result
        init()
        set its size to its data's |size|() as list
        return it
    end tell
end _NSImage

# ICCProfile()
#   Loads a ColorSync profile from the +filepath and creates a new NSColorSpace
#   instance 
to loadICCProfile(fp)
    local fp

    script
        property file : __NSURL__(fp)
        property data : NSData's dataWithContentsOfURL:(my file)
        property space : new(NSColorSpace)
        property mode : NSColorRenderingIntent's Perceptual

        to init()
            (my space)'s initWithICCProfileData:(my data)
        end init
    end script

    tell the result
        init()
        return it
    end tell
end loadICCProfile

# write
#   Writes out the +_NSImage data optionally converting it to a new colorspace
to write _NSImage for ICC : missing value ¬
    given properties:(opt as record) : missing value
    local _NSImage, ICC, kind, path, options


    set ImageRep to new(NSBitmapImageRep)

    _NSImage's lock()

    ImageRep's initWithFocusedViewRect:{{0, 0}, _NSImage's size}
    ImageRep's bitmapImageRepByConvertingToColorSpace:(ICC's space) ¬
        renderingIntent:(ICC's mode)
    result's representationUsingType:(_NSImage's kind) |properties|:opt
    set ImageRep to the result

    _NSImage's unlock()


    set fURL to __NSURL__(_NSImage's path)
    if fURL = missing value then set fURL to NSImage's file
    set ext to _NSImage's name extension
    if fURL's pathExtension() as text ≠ ext then ¬
        fURL's URLByDeletingPathExtension()'s ¬
        URLByAppendingPathExtension:ext

    ImageRep's writeToURL:fURL atomically:yes
    if the result = true then return fURL's |path|() as text
    false
end write
---------------------------------------------------------------------------❮END❯

As you noted, there doesn't appear to be an equivalent Foundation class option for the Core Graphics' kCGColorConversionBlackPointCompensation when converting colour spaces. So I may not have provided you with anything script-wise that you weren't already able to do. What I did observe, however, is that the GRACoL colour profiles cause the AppleScript engine to crash if one tries to utilise them "as is" after obtaining them from the website. For whatever reason, the profile must first be opened in the ColorSync Utility.app, and then saved (Save As...) or exported (Export...). Overwriting the original file is fine, after which AppleScript appears content to use it. This doesn't appear to be an issue with other profiles already saved on the system.