How Do I Copy Image File to Clipboard and Retain Format?

###How Do I Copy Image File to Clipboard and Retain Format?

I want to, using AppleScript, copy an image file (given it’s POSIX path) to the Clipboard and retain the same format that is in the file. So that when I paste it, also using AppleScript, it will paste into a rich text app using the same format.

I’ve done a bit of searching, and I can’t seem to find a bullet-proof script.

I figure ASObjC is the solution, but I don’t know how to write it.

Any help would be greatly appreciated.


**EDIT:  2017-05-01  9:38 PM CT**

Based mostly on the guidance from @ShaneStanley, but also from @ccstone and @Tom, I was able to cobble together what I hope will be the solution for this question/problem.

See my (hopefully final) script at:
http://forum.latenightsw.com/t/how-do-i-copy-image-file-to-clipboard-and-retain-format/590/27

You may not always be able to retain the format exactly, but try this:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use scripting additions

set theImage to current application's NSImage's alloc()'s initWithContentsOfFile:posixPath
if theImage is missing value then error "Can't read image format"
set theClip to current application's NSPasteboard's generalPasteboard()
theClip's clearContents()
theClip's writeObjects:{theImage}
1 Like

Shane, fantastic! It works perfectly! :+1:

From the clipboard created your script, it pasted great into:

  • TextEdit
  • Evernote
  • Word
  • This forum

May I ask one more question?

What would be the paste command in AppleScript (other than the old standby of keystroke) ?

Thanks again. I know you must be busy getting ready for your big trip, so I really appreciate you taking the time to respond to my post.


**Questions if you have time**
If not, they can wait until you get back.

**(1) What format is put on the Clipboard?**

The file I copied was a "PNG", but a Clipboard viewer I have report it as a "TIFF":

<img src="/uploads/default/original/1X/9de7d35b6f8ed8fa2a61138fb3d8ba6d6c3595e1.png" width="288" height="194">

EDIT: 2017-04-28 8:13 PM CT

Maybe the Clipboard viewer doesn’t see everything.
When I add this statement to the script:

set clipRec to the clipboard as record

SD6 reports this:

Obviously many more formats.

So, is SD6 correct?


When I paste the image into SnagIT, and then copy the image from SnagIT, it reports as this:

<img src="/uploads/default/original/1X/f6c7158f814f96d8c8ab20600d32bb7eb18d6113.png" width="383" height="264">

Apparently SnagIT is putting both TIFF and PNG on the Clipboard.

**(2) If the script does put just TIFF on the Clipboard, how do I force it to use the format of the file?**  I almost never want to use the TIFF format.

Thanks again, and don't worry if you don't have time for these questions now.

I updated my above post to include SD6 results.

Hey Shane,

In a similar vein – can you read a PDF – convert to an image – and place the image on the clipboard?

-Chris

You are mis-understanding how the clipboard works.

When you copy, you pass, say, an image or some text to the clipboard. The clipboard then works out what formats it can offer that data in – styled text might be offered in styled and non-styled formats, for example, and images will be offered in multiple formats. So you might pass one format, but what you passed can be offered in others. (Of course apps can have their own proprietary clipboard formats, too, and they can provide the data in more than one format – and more than one clipboard.)

When you paste, typically the receiving application will pass the clipboard a list of formats it can accept, in preference order, and the clipboard then works through them in order – returning the first it finds. The app then asks for the data in the returned format. (There are variations – a text editor might only support NSStrings, so it it doesn’t need to check.)

And a PDF is an image file in the terms of the script above.

Now you can control what gets placed on the clipboard a bit more, but that starts to get complicated because you need to deal with a range of different possibilities. Not something for now.

And you might play with this:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use scripting additions

set theClip to current application's NSPasteboard's generalPasteboard()
theClip's |types|() as list
No, that doubles things up with old style types listed as if they are separate items -- better this:
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use scripting additions

set theClip to current application's NSPasteboard's generalPasteboard()
theClip's (pasteboardItems()'s objectAtIndex:0)'s |types|() as list

This assumes a single pasteboard item, which is mostly the case.

1 Like

Hey Shane,

Thanks. That’s more convenient than getting the Clipboard as record – especially when image data is involved.

I have the same Clipboard Viewer app (from the app-store) that JM has, and I have the very similar one that comes with Xcode-extras – but it’s nice to be able to get clip-types as text within Script Debugger.

-Chris

Hi Shane,

I noticed something weird with your script:

No matter what the input file is (e.g. PNG) it always writes only TIFF data to the clipboard:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use scripting additions

set theClip to current application's NSPasteboard's generalPasteboard()
theClip's clearContents()

set thePath to "/Users/tom/_Tmp ƒ/test-600.png"
--set thePath to "/Users/tom/_Tmp ƒ/test.jpg"

set theImage to current application's NSImage's alloc()'s initWithContentsOfFile:thePath
if theImage is missing value then error "Can't read image format"
theClip's writeObjects:{theImage}

theClip's |types|() as list

# Returns always: {"public.tiff", "NeXT TIFF v4.0 pasteboard type"}
# Returns for big image data: {}

When I paste that clipboard into a mail message I get a LZW-compressed TIFF file of 1.4MB. (The source PNG file has 325KB.)

In Addition, if the amount of image data (not the file size!) is big enough it fails completely. (For example with a 1MB PNG file that contains 630MB of expanded image data.)


Whereas, when I use plain old vanilla AppleScript (set the clipboard to … as …), everything works as expected:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use scripting additions

set theClip to current application's NSPasteboard's generalPasteboard()
theClip's clearContents()

set thePath to "/Users/tom/_Tmp ƒ/test-600.png"
--set thePath to "/Users/tom/_Tmp ƒ/test.jpg"

set the clipboard to (read thePath as «class PNGf») # [1]
--set the clipboard to (read thePath as JPEG picture) # [2]

theClip's |types|() as list

# [1] Returns: {"public.png", "Apple PNG pasteboard type", "public.tiff", "NeXT TIFF v4.0 pasteboard type"}
# [2] Reurns: {"public.jpeg", "CorePasteboardFlavorType 0x4A504547", "public.tiff", "NeXT TIFF v4.0 pasteboard type"}

When I paste that clipboard into Mail I get a 325 KB PNG file. (The same as the source.)

And it doesn’t choke on big image data.


Can you reproduce this?

If yes, is there an ASOC way to get the same result as with vanilla AppleScript?


PS:

Here the test file: test-600.png.zip (174.4 KB)
And this is the big one that makes the ASOC script fail: test-1200.png.zip (473.3 KB)

@Tom and @ShaneStanley:

I’m seeing the same thing.
Using Tom’s example file, PNG, 325KB:

  • When I paste into Evernote, Note Size: 962.2 KB
  • When I drag/drop file into Evernote, Note Size: 317.KB
set thePath to "/Users/tom/_Tmp ƒ/test-600.png"
--set thePath to "/Users/tom/_Tmp ƒ/test.jpg"

set the clipboard to (read thePath as «class PNGf») # [1]
--set the clipboard to (read thePath as JPEG picture) # [2]

How would you handle reading any type of image file?
Is there a way to set the class in the read based on the file?

I know how to convert the clipboard’s content into jpeg but I don’t know how to move this jpeg data into the pasteboard.

Here is the handler (borrowed from Shane STANLEY):

on jpegFromClipToPath:thePath compressFactor:compfactor -- 0.0 = max compression, 1.0 = none -- set thePath to POSIX path of thePath set pb to current application's NSPasteboard's generalPasteboard() -- get pasteboard set theData to pb's dataForType:"public.tiff" -- get tiff data off pasteboard if theData = missing value then error "No tiff data found on clipboard" set newRep to current application's NSBitmapImageRep's imageRepWithData:theData set theData to (newRep's representationUsingType:(current application's NSJPEGFileType) |properties|:{NSImageCompressionFactor:compfactor, NSImageProgressive:false}) if (thePath does not end with ".jpg") and (thePath does not end with ".jpeg") then set thePath to my ChangeExtension(thePath, "jpg") as text end if set theResult to (theData's writeToFile:thePath atomically:true) return {(theResult = 1), thePath} end jpegFromClipToPath:compressFactor:

Yes, I would like to know this too. I’m always saving to file and then reading the file to the clipboard. Very clumsy.

Yes, I’m a bit surprised by that. I could understand it adding TIFF, and even preferring TIFF, but I expected it would add PNG too. It may be that it will only do the conversions lazily on larger images.

Yes, there’s no guarantee with the clipboard – as a TIFF that file is 50 megapixels-plus, so it’s obviously drawn the line.

You can just treat it as data. I’m not sure if it works for all formats, but it probably will for the obvious ones:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use scripting additions

set posixPath to "/Users/shane/desktop/test-1200.png"
set {theData, theError} to current application's NSData's dataWithContentsOfFile:posixPath options:0 |error|:(reference)
set theClip to current application's NSPasteboard's generalPasteboard()
theClip's clearContents()
theClip's setData:theData forType:"public.png"
theClip's (pasteboardItems()'s objectAtIndex:0)'s |types|() as list

Something like:

set theClip to current application's NSPasteboard's generalPasteboard()
theClip's clearContents()
theClip's setData:theData forType:"public.jpeg"

In case it’s not obvious, the type in setData:forType: will obviously have to match the file type. And the common types are also defined as constants (for example, NSPasteboardTypePNG).

Hey Shane,

Hmm…

That still puts TIFF data on the Clipboard (in addition to the PNG).

Pasting into Mail you get a PNG.

Pasting into TextEdit and saving as an rtfd file you get a TIFF.

If you copy the same PNG file in the Finder with Cmd-C and paste it into TextEdit (and save) you get a PNG.

You’d think there would be a way to enforce a preferred flavor.

-Chris

In that case presumably the NSURL is on the clipboard, and TextEdit then reads the data from the file. So the clipboard really has no part in the decision.

There is – but for the application, not the clipboard.

Haven’t tried Shane’s code yet, but that would be the same behavior as with vanilla read … as «class PNGf». Nice!

Hello all of you.

When I applied the Shane’s script to the file “test-1200.png” and pasted in TextEdit I got a tiff file in a rtfd document - which is the normal behavior.
When I did the same and pasted in Mail, I got a png file - which is fine.
Then I edited the script so that it put jpeg data in the clipboard.
When I applied the edited script to the “test-1200.png” file and pasted in Mail, although the clipboard’s content was {“public.jpeg”, “CorePasteboardFlavorType 0x4A504547”, “public.tiff”, “NeXT TIFF v4.0 pasteboard type”}, I got a tiff file - which is not what I hoped to get.

Did you re-save the file as a jpeg file – that is, test-1200.jpg? The file type must match.