How Do I Set Clipboard to Image Only From Clipboard of Image and Text?

asobjc
(Jim Underwood) #1

Continuing the discussion from How Do I Copy Image File to Clipboard and Retain Format?:

Starting with a Clipboard (that was set using the standard Copy UI) which contains BOTH an image and text, how do I re-set the Clipboard to only the image?

I tried to use the scripts in the above link, but just could not make it work.
I’m missing the basic command to read only the image on the Clipboard into an ASObjC variable.

BTW, I do not know what type of image it will be.

Thanks.

1 Like

(Shane Stanley) #2

There might be a simpler way, but I think this should work — it’s a variation on earlier code for preserving the contents of the clipboard.

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"

on fetchPicsOnlyFromClipboard()
	set picTypes to current application's NSArray's arrayWithArray:{current application's NSPasteboardTypeTIFF, current application's NSPasteboardTypePNG, current application's NSPasteboardTypePDF} -- types to fetch
	set aMutableArray to current application's NSMutableArray's array() -- used to store contents
	-- get the pasteboard and then its pasteboard items
	set thePasteboard to current application's NSPasteboard's generalPasteboard()
	set theItems to thePasteboard's pasteboardItems()
	-- loop through pasteboard items
	repeat with pbItem in theItems
		-- make a new pasteboard item to store existing item's stuff
		set newPBItem to current application's NSPasteboardItem's new()
		-- get the types of data stored on the pasteboard item
		set theTypes to pbItem's |types|()
		-- for each type, check if it's a pic, get the corresponding data and store it in the new pasteboard item
		repeat with aType in theTypes
			if (picTypes's containsObject:aType) as boolean then
				set theData to (pbItem's dataForType:aType)'s mutableCopy()
				if theData is not missing value then
					(newPBItem's setData:theData forType:aType)
				end if
			end if
		end repeat
		-- add new pasteboard item to array if it contains anything
		if newPBItem's |types|()'s |count|() > 0 then
			(aMutableArray's addObject:newPBItem)
		end if
	end repeat
	return aMutableArray
end fetchPicsOnlyFromClipboard

-- get pasteboard
set thePasteboard to current application's NSPasteboard's generalPasteboard()
-- get pic contents
set theClip to my fetchPicsOnlyFromClipboard()
-- clear it, then write back filtered contents
thePasteboard's clearContents()
thePasteboard's writeObjects:theClip
0 Likes

(Thomas Tempelmann) #3

Curious. Does Shane’s solution work? I’d expect text+image data in a clipboard be encoded as RTFD or HTML, and that would mean that you can’t simply find the images as separate clipboard items. Instead, you’ll have to parse the html or RTF content to extra the images, which is much more work.

You can view the clipboard contents with this app of mine, for instance: http://files.tempel.org/iClip-public/ShowClipboards.app.zip

0 Likes

(Jim Underwood) #4

Good question, Thomas. No, and it is my fault for not properly inspecting the clipboard I need to process, and for not asking the right question. :wink:

Bingo! That is exactly the actual issue/clipboard I face. When I inspect the clipboard it does NOT have any images included just as image types. All are embedded in RTF.

@ShaneStanley, many thanks for your help, and I appreciate the script. I just asked the wrong question.

Anyone have a solution for extracting images out of RTF on the clipboard?

0 Likes

(Thomas Tempelmann) #5

Here’s some ideas:

  1. The rtfd doc in the clipboard has the images embedded (type: “com.apple.flat-rtfd”). If you save that clipboard data to a .rtfd file, TextEdit won’t be able to open it, though.
  2. If you can paste it into TextEdit (in rich text mode), however, you’ll get the text + imgs properly in the TextEdit document. Now, if you save that, you’ll get a .rtfd folder - if you use “Show Package Contents” in Finder, you’ll find your img file inside.

I believe there are also macOS API calls for doing this, i.e. take a flat rtfd and turn it into one that you can save as a package folder. Once you accomplish that, you can extract the .tiff files from it.

Google for that type reveals some code (not ObjC AppleScript, though):

That part gets the flat rtfd data into an NSAttributedString.
All that’s left to find is a function that will write that out as a .rtfd package.

Looks like this code should do it (again, not yet AppleScripted - I leave that to someone who has some skills at it):

NSFileWrapper *fileWrapper = [imageAttrString fileWrapperFromRange:NSMakeRange(0, [imageAttrString length]) documentAttributes:@{NSDocumentTypeDocumentAttribute: NSRTFDTextDocumentType} error:&error];
[fileWrapper writeToURL:yourFileURL options:NSFileWrapperWritingAtomic originalContentsURL:nil error:&error];
0 Likes

(Shane Stanley) #6

It’s possible — it’s up to the copying app.

I was basing my code on recollections of a previous discussion with @JMichaelTX where he described the clipboard after a copy containing an image and text such that pasting into an image editor resulted in the pic being pasted, whereas pasting into a text editor resulted in the text being pasted. That won’t happen with RTFD or HTML on the clipboard in a single pasteboard item — it can only happen with multiple pasteboard items.

0 Likes

(Shane Stanley) #7

This will get the first image out of RTFD on the clipboard, and puts it alone on the clipboard:

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

set thePasteboard to current application's NSPasteboard's generalPasteboard()
set theData to thePasteboard's dataForType:(current application's NSRTFDPboardType)
if theData is missing value then error "No RTFD on the clipboard"
set {theRTFD, theAtts} to current application's NSAttributedString's alloc()'s initWithRTFD:theData documentAttributes:(reference)
-- make file wrapper, sort of folder in memory
set theWrapper to theRTFD's RTFDFileWrapperFromRange:{0, theRTFD's |length|()} documentAttributes:theAtts
-- subwrappers are like the files in a folder, with keys like file names
set subWrappers to theWrapper's fileWrappers()
set theKeys to subWrappers's allKeys()
-- loop through and find the first non-rtf wrapper, which we'll assume is a pic
repeat with aKey in theKeys
	set theExt to aKey's pathExtension() as text
	if theExt is not "rtf" then
		-- get the dta for the wrapper/pseudo-file
		set picWrapper to (subWrappers's objectForKey:aKey)
		set theData to picWrapper's regularFileContents()
		-- put it on the clipboard
		if theExt = "png" then
			set theType to current application's NSPasteboardTypePNG
		else if theExt = "tif" then
			set theType to current application's NSPasteboardTypeTIFF
		else if theExt = "pdf" then
			set theType to current application's NSPasteboardTypePDF
		else
			-- you're going to have to convert it 
			set theImage to (current application's NSImage's alloc()'s initWithData:theData)
			set theData to theImage()'s TIFFRepresentation()
			set theType to current application's NSPasteboardTypeTIFF
		end if
		thePasteboard's clearContents()
		(thePasteboard's setData:theData forType:theType)
	end if
end repeat
1 Like

(Thomas Tempelmann) #8

Very nice, Shane, thanks!

I can use that as a script in my app iClip, in order to extract images from rich text clips. Users kept asking me for this, and only this question led me to figure out how to do it, and instead of having to hard-code it into iClip, I can now use this script. Awesome.

A (little) possible bug: Images inside my rtfd folders use “.tiff”, not “tif”, as extension. Therefore, your test should be for “tif”, not “tiff”, right?

One more possible improvement would be to not check for extensions but to check whether the UTI conforms to the public.image type: if UTTypeConformsTo(fileType, "public.image).

However, that’s not a ObjC function but a plain C function. Can those be called from AppleScript as well? And if, how does one get the filetype of a file name? One way would be to call
UTTypeCreatePreferredIdentifierForTag("kUTTagClassFilenameExtension",theExt, nil), if that works.

0 Likes

(Shane Stanley) #9

Yes. It’s really only an outline I banged out in a hurry — it needs more work before being used, such as some more error checking.

I’m not sure how you’d get the UTI in the first place without physically saving as a file, which would slow things down a bit.

You can call them, but you can’t deal in CF types, so in practice that rules most of them out. In this case there’s an NSWorkspace alternative, -type:conformsToType:.

Actually, that’s a thought: NSWorkspace has -filenameExtension:isValidForType:.

1 Like

(Jim Underwood) #10

Many thanks, Shane! That works perfectly. :+1:

0 Likes