How to increase size of an image in clipboard?

Hello, all, hope you’re well and keeping safe.

I’ve got an odd problem. I’m reading image into the clipboard via a script like this

tell application “Finder”
set the clipboard to (read theFile as JPEG picture)
end tell

with the goal being to paste it into Photoshop images I’m working with. The quandry I’m having is, sometimes the images are 800 px high but I need them to be 1000 px high so I can post them to social media and fit in with my other layouts. Since Photoshop is getting increasingly hard to script (that’s my perception at least), it’d be easier if I could just increase the size of the image without resorting to ImageMagick and terminal workarounds that will likely slow down my script.

Any ideas on how to upscale an image in the clipboard easily?

If I understand well, the problem wasn’t to resize the clipboard’s contents but to fill the clipboard with a resized picture.
I guess that there is a way to do that without creating a temporary file but at least the script below does the asked job.

use scripting additions
use framework "Foundation"
use framework "AppKit"

on resizedJpegFromPath:imagePath newHeight:theHeight compressFactor:compFactor
	-- Get the contents of the passed picture
	set theImage to current application's NSImage's alloc()'s initWithContentsOfFile:imagePath -- load the file as an NSImage
	-- Extract its original size
	set oldSize to item 1 of ((theImage's |size|) as list)
	set oldHeight to height of oldSize
	if oldHeight ≠ theHeight then -- The contents must be resized
		set oldWidth to width of oldSize
		set theWidth to oldWidth * theHeight / oldHeight
		set newImage to current application's NSImage's alloc()'s initWithSize:(current application's NSMakeSize(theWidth, theHeight)) -- make new blank image
		-- draw from original to new
		newImage's lockFocus()
		theImage's drawInRect:{origin:{x:0, y:0}, |size|:{width:theWidth, height:theHeight}} fromRect:(current application's NSZeroRect) operation:(current application's NSCompositeSourceOver) fraction:1.0
		newImage's unlockFocus()
		set theData to newImage's TIFFRepresentation() -- get bitmap as data
		set newRep to current application's NSBitmapImageRep's imageRepWithData:theData -- make bitmap from data
		set theData to (newRep's representationUsingType:(current application's NSJPEGFileType) |properties|:{NSImageCompressionFactor:compFactor, NSImageProgressive:false}) -- turn the bitmap into JPEG data
		set imagePath to POSIX path of (path to temporary items as string) & "temp.jpg"
		(theData's writeToFile:imagePath atomically:true) -- write it to disk	
	end if
	set the clipboard to (read (imagePath as «class furl») as JPEG picture)
end resizedJpegFromPath:newHeight:compressFactor:

set aJpeg to choose file of type {"public.jpeg"}
my resizedJpegFromPath:(POSIX path of aJpeg) newHeight:800 compressFactor:1

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) dimanche 17 mai 2020 11:16:50

If you copy many images at once, then you can increase the speed further - creating RAM disk and temporary image file in it.
Having the image in the temporary file of RAM disk, you can use for resizing AsObjC (as Yvan Koenig show), or background utility sips, or background application Image Events.

NOTE: you can’t avoid creating file at all, because the clipboard is not application with image processing ability.

(Sentence removed by NG.)

use scripting additions
use framework "Foundation"
use framework "AppKit"

on resizedJpegFromPath:imagePath newHeight:theHeight compressFactor:compFactor
	
	set metaDataStr to getMetaData(imagePath, "kMDItemContentTypeTree")
	set primaryType to item 1 of metaDataStr
	
	-- Get the contents of the passed picture
	set theImage to current application's NSImage's alloc()'s initWithContentsOfFile:imagePath -- load the file as an NSImage
	-- Extract its original size
	set oldSize to item 1 of ((theImage's |size|) as list)
	set oldHeight to height of oldSize
	if oldHeight ≠ theHeight then -- The contents must be resized
		set oldWidth to width of oldSize
		set theWidth to oldWidth * theHeight / oldHeight
		set newImage to current application's NSImage's alloc()'s initWithSize:(current application's NSMakeSize(theWidth, theHeight)) -- make new blank image
		-- draw from original to new
		newImage's lockFocus()
		theImage's drawInRect:{origin:{x:0, y:0}, |size|:{width:theWidth, height:theHeight}} fromRect:(current application's NSZeroRect) operation:(current application's NSCompositeSourceOver) fraction:1.0
		newImage's unlockFocus()
		set theData to newImage's TIFFRepresentation() -- get bitmap as data
		set newRep to current application's NSBitmapImageRep's imageRepWithData:theData -- make bitmap from data
		set nsFileContents to (newRep's representationUsingType:(current application's NSJPEGFileType) |properties|:{NSImageCompressionFactor:compFactor, NSImageProgressive:false}) -- turn the bitmap into JPEG data
	else
		set {nsFileContents, theError} to current application's NSData's dataWithContentsOfFile:filePath options:0 |error|:(reference)
	end if
	
	--- 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
end resizedJpegFromPath:newHeight:compressFactor:

--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

set aJpeg to choose file of type {"public.jpeg"}
my resizedJpegFromPath:(POSIX path of aJpeg) newHeight:800 compressFactor:1

This edited script does the job without creating a temporary file.
In fact it’s quite easy to pass the imageData to the clipboard.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) dimanche 17 mai 2020 14:16:13

Yvan,

There are a couple of issues. One is that your code assumes the files have a resolution of 72dpi. That used to be the case with just about every jpg file, but it’s not any more. When that’s not the case, you’ll end up with the wrong size in pixels.

Also, you really don’t want to put jpeg data on the clipboard – better to put the full tiff data, to avoid another jpeg sampling.

Here’s a version that does those two things. It’s a little bit more complex because it uses bitmap imagereps, rather than images, to get the sizes right.

use scripting additions
use framework "Foundation"
use framework "AppKit"

on resizedJpegFromPath:imagePath newHeight:theHeight
	-- Get the contents of the passed picture
	set theImageRep to current application's NSBitmapImageRep's imageRepWithContentsOfFile:imagePath -- load the file as an NSImage
	set theClip to current application's NSPasteboard's generalPasteboard()
	-- Extract its original size
	set oldHeight to theImageRep's pixelsHigh()
	set oldWidth to theImageRep's pixelsWide()
	set spaceName to theImageRep's colorSpaceName()
	if oldHeight ≠ theHeight then -- The contents must be resized
		set theWidth to oldWidth * theHeight / oldHeight
		set newRep to (current application's NSBitmapImageRep's alloc()'s initWithBitmapDataPlanes:(missing value) pixelsWide:theWidth pixelsHigh:theHeight bitsPerSample:8 samplesPerPixel:4 hasAlpha:yes isPlanar:false colorSpaceName:spaceName bitmapFormat:(current application's NSAlphaFirstBitmapFormat) bytesPerRow:0 bitsPerPixel:32)
		-- store the existing graphics context
		current application's NSGraphicsContext's saveGraphicsState()
		-- set graphics context to new context based on the new bitmapImageRep
		current application's NSGraphicsContext's setCurrentContext:(current application's NSGraphicsContext's graphicsContextWithBitmapImageRep:newRep)
		theImageRep's drawInRect:{origin:{x:0, y:0}, |size|:{width:theWidth, height:theHeight}} fromRect:(current application's NSZeroRect) operation:(current application's NSCompositeSourceOver) fraction:1.0 respectFlipped:false hints:(missing value)
		-- restore state
		current application's NSGraphicsContext's restoreGraphicsState()
		theClip's clearContents()
		theClip's setData:(newRep's TIFFRepresentation()) forType:(current application's NSPasteboardTypeTIFF)
	else
		set theClip to current application's NSPasteboard's generalPasteboard()
		theClip's clearContents()
		theClip's writeObjects:{theImage}
	end if
end resizedJpegFromPath:newHeight:

set anImage to choose file of type {"public.png", "public.tiff", "public.jpeg"}
my resizedJpegFromPath:(POSIX path of anImage) newHeight:800

Thank you Shane.

I’m an ass, you already sent me this kind of code some months ago.

Small question :

is it better to code:

set anImage to choose file of type {"public.png", "public.tiff", "public.jpeg"}

or to code :

set anImage to choose file of type {"public.image"}

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) dimanche 17 mai 2020 16:32:13

After the recent forum thread on image height/width and orientation, I decided to put Shane’s script to the test with one portrait and one landscape digital photo. In both cases the script set the photo height to the desired figure. Can’t beat that.

In this case the code is designed to work with bitmap images, so the first example is a way of excluding files like .pdf and .svg, which conform to public.image. So it’s not a matter of better, so much as it depends on the situation.

FWIW, you can get the EXIF data from the imagerep:

set theEXIFData to theImageRep's valueForProperty:(current application's NSImageEXIFData)
if theEXIFData is not missing value then
	set photoOrientation to (theEXIFData's objectForKey:"SceneType")
else
	set photoOrientation to missing value
end if
if photoOrientation is not missing value and (photoOrientation as integer) = 2 then -- portrait

Thanks, of course, for this instructive script. But as I see it, I was right after all. You also use the file initially. As I understand it, OP manually copies images directly from Photoshop document that is not saved on disk. Then he wants to paste the processed images again by hand.
I do not think that the manual Paste command will be active after clearing cliboard contents using a script. I mean it.

And what you do here, with sips and Image Events is pretty available and will take 4-5 code lines.

That’s not what his use of read theFile implies.

I read it the other way around: he reads files on to the clipboard via script and pastes into Photoshop. But it’s not exactly clear.

So, I misunderstood (on the contrary, it reads from a file and wants to paste it in Photoshop). But then again, this is not what the OP hopes for.

He needs to manually paste the contents of the clipboard in Photoshop. Will the Paste menu be active? There is some confusion here.

I don’t know what let you misunderstood what the OP asked for.

He wrote:

So it’s clear that he asked to fill the clipboard from a given JPEG file

This second part clearly state that he want to change the content of the clipboard so that the embedded picture become a 800 pixel heigh one.
This is exactly what Shane’s proposal and mine are achieving.

The only point which is not clear is to know if the resulting contents is supposed to be a jpeg one (what I did) or if it’s allowed to be a tiff one (what Shane did).

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) lundi 18 mai 2020 10:34:55

Thank you Shane.

I asked because here, when I execute:

set anImage to choose file of type {"public.image"}

I’m not allowed to select .pdf files (I own hundreds of them).
For .svg ones I didn’t knew, I never worked with such files.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) lundi 18 mai 2020 10:42:37

My suggestion:


set sourceFile to quoted form of POSIX path of (choose file of type {"public.jpeg"})

set tempFile to POSIX path of ((path to temporary items) as text) & "resizedimage.jpg"
set quotedTempFile to quoted form of tempFile

do shell script "mdls -name kMDItemOrientation " & sourceFile
set photoOrientation to the last word of the result

if photoOrientation is equal to "1" then
	do shell script "sips --resampleWidth 1000 " & sourceFile & " --out " & quotedTempFile
else
	do shell script "sips --resampleHeight 1000 " & sourceFile & " --out " & quotedTempFile
end if

set the clipboard to (read POSIX file tempFile as TIFF picture)

A few thoughts:

  • Perhaps the temp file should be in TIFF or PNG format to avoid image degradation; this conversion is easily done with sips.

  • If the source image file is not indexed, I would use exiftool instead of mdls.

  • If by chance all of the source images are in either portrait or landscape mode, this script could be reduced to perhaps four lines.

  • The temp file is automatically deleted on restart, but it’s probably still a good idea to actually delete this file.