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

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.

I will be more precise.
The original file was "test-1200.png"
I ran the script

[code]# borrowed from : How Do I Copy Image File to Clipboard and Retain Format?

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 posixPath to POSIX path of “Macintosh HD:Users:Important:pour ebay:_X28737ƒYK:##28755.png”
*)
set posixPath to POSIX path of ((path to desktop as text) & “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.jpeg"
set theType to theClip’s (pasteboardItems()'s objectAtIndex:0)'s types() as list
set theTypes to theClip’s types() as list[/code]
I created a new mail and pasted in it.
I sent the message to myself (the joy of having several mail addresses)
I opened the received message.
I entered the small local menu listing the attachments and was proposed to save the file as “PastedGraphic-1.tiff” with its 3.8 Mbytes.

It seems that if the clipboard contain “public.png” and “public.tiff” data, Mail grabs the “public.png” component
but if the clipboard contain “public.jpeg” and “public.tiff” data, Mail grabs the “public.tiff” component.

When I pasted the same clipboard into a Pages document, as the file was a flat one I can’t see the picture file itself but looking in the file with Hexedit I found the string “PastedGraphic-1.jpeg” which is what I wished to get.

Of course, I was able to save the content of the clipboard in a jpeg file but I don’t guess what would help in doing that.

What would be fine would be the ability to remove the “public.tiff” and “NeXT TIFF v4.0 pasteboard type” components from the clipboard exactly as we may drop the style components when we copy a styled piece of text.
I will try to retrieve the script doing that to see if it may be adapted to drop the tiff components.

Then you shouldn’t use public.jpeg as the type – you have to use public.png. When you call setData:forType:, the data and the type must match.

And then you would not be able to copy-and-paste the image into an application that only accepts tiffs. The whole point of the clipboard is to offer a wide range of formats, and let the client application choose which suits it best.

I don’t want to keep always jpeg only but I feel that it may be interesting to paste jpeg in some cases where I know that it would be smaller than png.

With the proposed file I ran :slight_smile:[code]# borrowed from : How Do I Copy Image File to Clipboard and Retain Format?

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 posixPath to POSIX path of “Macintosh HD:Users:Important:pour ebay:_X28737ƒYK:##28755.png”
*)
set posixPath to POSIX path of ((path to desktop as text) & “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"
set theType to theClip’s (pasteboardItems()'s objectAtIndex:0)'s types() as list
log theType (public.png)
set theTypes to theClip’s types() as list
log theTypes (public.png, Apple PNG pasteboard type, public.tiff, NeXT TIFF v4.0 pasteboard type)
clipboard info

The default compfactor applied to jpeg is ≃ 0.75

–> {{«class PNGf», 1 070 884}, {«class 8BPS», 11 066 614}, {GIF picture, 626 510}, {«class jp2 », 4 139 634}, {JPEG picture, 6 359 827}, {TIFF picture, 6.3105946E+8}, {«class BMP », 6.31061382E+8}, {«class TPIC», 7 359 854}}

set theData to theClip’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 compfactor to 1.0
set theData to (newRep’s representationUsingType:(current application’s NSJPEGFileType) |properties|:{NSImageCompressionFactor:compfactor, NSImageProgressive:false})
theClip’s setData:theData forType:"public.jpeg"
set theType to theClip’s (pasteboardItems()'s objectAtIndex:0)'s types() as list
log theType (public.png, public.jpeg)
set theTypes to theClip’s types() as list
log theTypes (public.png, Apple PNG pasteboard type, public.jpeg, CorePasteboardFlavorType 0x4A504547, public.tiff, NeXT TIFF v4.0 pasteboard type)
clipboard info

with compfactor = 1.0

–> {{«class PNGf», 1 070 884}, {JPEG picture, 11 564 428}, {«class 8BPS», 11 066 614}, {GIF picture, 626 510}, {«class jp2 », 4 139 634}, {TIFF picture, 6.3105946E+8}, {«class BMP », 6.31061382E+8}, {«class TPIC», 7 359 854}}

[/code]

This thought me that :
(1) the jpeg stored by default is not the best quality one, it’s compressed with a factor ≃ 0.75
(2) how to store a jpeg with no compression
(3) the png object is smaller than the jpeg one so it’s useless to try to play with the jpeg one.