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