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

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.

This is because I produced the PNG file from vector graphics, without any antialiasing. JPEG doesn’t “understand” this type of non-continuous-tone images and will introduce its typical artifacts. Thus the JPEG file size of those images will always be larger than the PNG which uses some kind of deflate compression (very effective for this kind of images, and lossless, i.e. no artifacts added).

And, this is true over the whole JPEG quality range, including 100:

JPEG, quality: 5 → file size: 1.2MB

JPEG, quality: 100 → file size: 3.6MB

Original PNG, file size: 325KB; losslessly optimized: 125KB

(File sizes and screenshots for the 600ppi version of the test file.)

But this doesn’t mean JPEG is useless. It works fine for continuous-tone images, typically photos. (Good ratio of perceived quality / file size.)

Not sure what you mean.

Even if you set the JPEG quality to 100 it will still alter the image (see above).

It exists a “JPEG Lossless” but it seems to be a different format than JPEG and not many programs have implemented it. Off hand, GraphicConverter is the only app I know that offers “JPEG Lossless”. But with the example image from above it produces a file size of 20.2MB.

JPEG2000 has a Lossless option. It produces a file size of 1.2MB.

Yep, this works fine! Thank you.

Hello Tom

I reproduce my message with a different layout :slight_smile:
This thought me that : (1) the jpeg stored by default is not the best quality one, it’s compressed with a factor ≃ 0.75
This thought me that : (2) how to store a jpeg with no compression (better said with a factor = 1.0)
This thought me that : (3) the png object is smaller than the jpeg one so it’s useless to try to play with the jpeg one. (in a case like yours)

I guess that it’s more clear this time.
Of course, I knew that getting a jpeg larger than the png is not the general case.
Thank you for giving the explanation of this “surprising” behavior.

theClip's setData:theData forType:"public.png"

Many thanks, Shane. That was the key to getting the data on the clipboard in the same format as the file.

So, here is my script, hopefully solving the problem, which takes into account all of the great stuff posted by @ShaneStanley, @Tom, @ccstone, and others.

If anyone sees any errors/issues, or can offer improvements, please advise.

###Script to Put Image File on Clipboard & Retain Format

(*
===============================================================================
  Put Image File on Clipboard
===============================================================================

VER:   1.0    LAST UPDATE:   2017-05-01

PURPOSE:
  • Select Image File, and Put on Clipboard Retaining Same Format

AUTHOR:    JMichaelTX (with heavy lifting from others)
              • I cobbled this together, and accept responsibility for all errors
              • The meat of this script provided by @ShaneStanley, @ccstone, and @Tom

REQUIRED:
  1.  macOS El Capitan 10.11.6+
  2. use statements for ASObjC shown below
  
METHOD:
  • Get the file format using Spotlight Metadata for kMDItemContentTypeTree
  • Read the file using ASObjC
  • Write the Clipboard (Pasteboard) specifying the above file format

INSTALLATION:   See http://tinyurl.com/install-as

REF:  The following were used in some way in the writing of this script.
  1.  How Do I Copy Image File to Clipboard and Retain Format?
      http://forum.latenightsw.com/t/how-do-i-copy-image-file-to-clipboard-and-retain-format/590
===============================================================================
   
*)

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

--- Primary Type (UTI) is Listed First ---
(*    NOTE:  If you wanted an alternate format, you could search list *)
set kMDKeyStr to "kMDItemContentTypeTree"

set filePath to POSIX path of (choose file with prompt ¬
  "Choose IMAGE file" of type ["public.image"])

set metaDataStr to getMetaData(filePath, kMDKeyStr)
set primaryType to item 1 of metaDataStr

--- READ FILE ---
set {nsFileContents, theError} to current application's NSData's dataWithContentsOfFile:filePath options:0 |error|:(reference)

--- GET REFERENCE TO CLIPBOARD ---
set theClip to current application's NSPasteboard's generalPasteboard()
theClip's clearContents()

--- PUT IMAGE DATA ON CLIPBOARD ---
theClip's setData:nsFileContents forType:primaryType

--- CHECK CLIPBOARD FOR ITEM TYPES ---
set clipList to theClip's (pasteboardItems()'s objectAtIndex:0)'s |types|() as list
set clipRec to the clipboard as record

set msgStr to "Type: " & primaryType
set msgTitleStr to "Image File Put on Clipboard"
display notification msgStr with title msgTitleStr sound name "Hero.aiff"

--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
on getMetaData(pPOSIXPath, pkMDKey)
  (*  VER: 1.0    2017-05-01
  --–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
  PURPOSE:  Get Value(s) of Spotlight Metadata for Given Key
  PARAMETERS:
    • pPOSIXPath    : POSIX path of file
    • pkMDKey        : Spotlight kMD Key Name
  RETURNS:  List of key values (even if only one value)
  
  AUTHOR:  JMichaelTX (heavy lifting by @ccstone)
  REQUIRES:  use framework "Foundation"

  REF:  Script by @ccstone with @ShaneStanley
          Jun 10, 2016
          Getting Individual Metadata Items with ASObjC
          https://lists.apple.com/archives/applescript-users/2016/Jun/msg00068.html
    --–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
    *)
  
  local curApp, nsPath, myNSURL, nsMetaItem, nsMDValue
  
  set curApp to current application
  
  set nsPath to curApp's NSString's stringWithString:pPOSIXPath
  
  --- EXPAND TILDE & SYMLINK (if any exist) ---
  set nsPath to nsPath's stringByResolvingSymlinksInPath()
  
  --- GET THE NSURL, WHETHER OR NOT THE FILE/FOLDER EXISTS ---
  set myNSURL to curApp's |NSURL|'s fileURLWithPath:nsPath
  
  --- GET THE SPOTLIGHT METADATA VALUE ---
  set nsMetaItem to current application's NSMetadataItem's alloc()'s initWithURL:myNSURL
  set nsMDValue to nsMetaItem's valueForAttribute:pkMDKey
  
  return (nsMDValue as list)
  
end getMetaData
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~