0

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).

3
  • It is doubtful you will be able to use the CGColorRenderingIntent() from within AppleScriptObjC, because it takes arguments that use C data types, which don't bridge to AppleScript. Commented Jun 23, 2019 at 6:50
  • Thanks CJK for looking into this for me. What would you suggest my options were? Try using the NSColorRenderingIntentPerceptual and see how close I can get to the Photoshop version? How difficult would it be to put together a command line tool that took the image file and color profile as arguments and output the file with the new colorspace based on the profile? Commented Jun 23, 2019 at 16:55
  • You can do this with Image Events. If you use custom profile settings, you won't be able to specify the black point compensation. But if you have a specific ICC profile saved on your machine, I believe you can load it for embedding. Commented Jun 23, 2019 at 21:13

1 Answer 1

0

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.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks so much CJK! I'll give it a try and let you know how it goes. Not sure how important that black point compensation is. I have started to try and build a portable version of ImageMagick to embed in my scripts, IM does everything I need (and a whole lot that I don't need) but it is really unwieldy. I wish there was an official portable version for more than just Windows. But that is neither here nor there. One way or another I will figure out how to get the colorspaces converted and the correct profiles attached to the images!
So I replaced your placeholder paths with real paths on my system, and when I I run the script I get an error at this piece of code: ``` tell the result init() return it end tell end loadICCProfile ``` The variable result is not defined. That is strange because it doesn't even look like Script Debugger see's that as a variable, it looks more like it is being treated like a property based on color coding. Because it is code dealing with the ICC profile, I reserved that with the ColorSync utility, and that had no effect.
@ChrisNorman That is an odd error. The result is a property as the colour-coding suggests: it refers to the output of the previous AppleScript expression (usually the previous line, but in this case the script object directly preceding). The handler executes init() for the script object, then returns that script object as its output. For result not to be defined, the script object isn't created, which doesn't seem possible. And I'm unable to recreate the error. Did you change anything else in the script at all? What if you run it in Script Editor ? macOS version ?
It ran fine under Script Editor. Must be a weird SD bug or something.
I find Script Debugger frustrating at times because it defines its own AppleScript terminology that clashes occasionally with some scripts. That said, the script ran okay for me in SD too, so I don’t know precisely where the issue lies in SD on your system. If you’re saving it as a compiled script (.scpt), it might be related to the data that SD stores in the script alongside properties and script objects, which Script Editor doesn’t (one reason I save all my scripts as .applescript text files).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.