About time to post a new version of my trusty Screenshot Prettifier script:
IM-Screenshot-Prettifier-0.9.9.applescript.zip (9.3 KB)
As with the old version, it can be used standalone (Script menu) or from within KM. Target is the Finder selection (PNG).
Noteworthy changes:
- Using oxipng now instead of advpng for optimization (compression).
- Independent of the action, PPI resolution is now always set to 144 (no pixel change).
- Pre- and postprocessing (PPI metadata & final optimization) as standalone option.
- Option for faster (lower) compression. (Affects optimization, not quantization.)
- Fixed a couple of things and tweaked parameters.
- Added path detection, since Homebrew installs to a different path on ARM Macs (see also the Edit notes for v0.9.9 at the end of the post).
So the dependencies of this version are:
They can all be installed with Homebrew: brew install imagemagick
, brew install pngquant
, brew install oxipng
The standardization to 144ppi is done with sips, which comes with macOS. sips writes the ppi metadata to the pHYs chunk of the PNG.
The pHYs chunk must be preserved during optimization, which – to my knowledge – is not possible with advpng. Hence the switch to oxipng. (zopfli also can preserve the chunk, but oxipng turned out to be significantly faster at a comparable compression level. Besides that, it is actively developed.)
A note on optimization/quantization:
- Optimization (via oxipng) uses lossless compression and does not affect image quality in any way. The script optimizes the PNG automatically at the end.
- Quantization (via pngquant) is significantly more effective, but also reduces image quality, e.g. by reducing the number of colors. Quantization must be selected in the Options dialog and is not done automatically.
The script content (without header):
# Version: 0.9.9 (15)
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
# To leave ppi unchanged, set `ppi` to ""
set theDefaults to current application's NSUserDefaults's alloc()'s initWithSuiteName:"net.dflect.IMScreenshotPrettifier"
# Path: The script should detect the ususal Homebrew path austomatically. If you get a path alert, please set the `binPath` default in the next line manually to your path(s). Example: "export PATH=/xxx/yyy/bin:$PATH;" (single path) or "export PATH=/xxx/yyy/bin:/xxx/zzz/bin:$PATH;" (multiple paths).
theDefaults's registerDefaults:{binPath:"", ppi:"144", options:"", shadowValue:"80x3+2+2", polaroidValue:"-.5", usmValueForResized:".8x.8+1+0.55", usmValueForUnchangedSize:".8x.8+1+0.5", usmValueForPolaroid:".8x.8+1+0.35", polaroidCaption:"-gravity center -font SF-UI-TextI -pointsize 20 -fill maroon -set caption 'CAPTION TEXT HERE'", option1:"", option2:"", compression:"-o max", onlyPPP:false, nonPNGs:0, helpMsg:"You can choose one item from each group.
Polaroid applies USM by default, so do not combine it with USM.
Choose 'Polaroid…' to set the angle. If you press 'Save & Run', a Polaroid action will be executed with that angle. If you press 'Save & Relaunch', the script relaunches and you can run the Polaroid w/Caption action with that angle value.
Values you set in dialogs apply to the current run and are saved as your new default also for the corresponding dialog-less actions.
Select 'Pre- & Postprocessing Only' to only set the PPI metadata and apply lossless compression. (Automatically included with any other action.)"}
# Good values for Unsharp Mask: [radius x sigma + amount + threshold]
# usmValueForResized:".8x.8+1+0.55"
# usmValueForUnchangedSize:".8x.8+1+0.5"
# usmValueForPolaroid:".8x.8+1+0.35"
set binPath to binPath of theDefaults as text
set ppi to ppi of theDefaults as text
set options to options of theDefaults as list
set shadowValue to shadowValue of theDefaults as text
set polaroidValue to polaroidValue of theDefaults as text
set usmValueForResized to usmValueForResized of theDefaults as text
set usmValueForUnchangedSize to usmValueForUnchangedSize of theDefaults as text
# I have added an “unsharp” to the Polaroid action by default because it seems to use “resize” to shrink the image (“resize” is more blurry than “scale”)
set usmValueForPolaroid to usmValueForPolaroid of theDefaults as text
set polaroidCaption to polaroidCaption of theDefaults as text
set option1 to option1 of theDefaults as text
set option2 to option2 of theDefaults as text
set compression to compression of theDefaults as text
set onlyPPP to onlyPPP of theDefaults as boolean
set nonPNGs to nonPNGs of theDefaults as integer
set helpMsg to helpMsg of theDefaults as text
global binPath, outName
# Homebrew path is different on ARM Macs
# Naively we assume that all required binaries are in the same path as `convert`
if binPath is "" then
tell application "System Events"
if exists file "/opt/homebrew/bin/convert" then -- ARM
set binPath to "export PATH=/opt/homebrew/bin:$PATH;"
else if exists file "/usr/local/bin/convert" then -- X86
set binPath to "export PATH=/usr/local/bin:$PATH;"
else
display alert "ImageMagick not found! Expected to find Imagemagick's `convert` in \"/opt/homebrew/bin\" (ARM) or \"/usr/local/bin\" (X86), but nothing was found." & linefeed & linefeed & "Please set your path(s) manually in the script; see the comment around line 33." as warning
error number -128
end if
end tell
set binPath of theDefaults to binPath
end if
# Collect files
tell application "Finder"
set trgImages to selection as alias list
repeat while (count of trgImages) < 1
display alert "Please select one or more image files (PNG) in the Finder." as warning buttons {"Cancel", "Continue"}
if the result's button returned is "Cancel" then error number -128
set trgImages to selection as alias list
end repeat
end tell
# Get options. We can choose one of the first group + Quantize.
set prevOptions to options
tell application (path to frontmost application as text)
set options to choose from list {"Drop Shadow…", "Drop Shadow", "Polaroid…", "Polaroid", "Polaroid w/Caption…", "Torn Edges", "Torn Edges +Shadow", "Thick", "--------------", "Quantize", "--------------", "Resize to 50 %", "Resize to 70 %", "Resize to 75 %", "Resize to 80 %", "Resize to 85 %", "Resize to 90 %", "Shrink to 300 px Width", "Shrink to 350 px Width", "Shrink to 400 px Width", "USM for Original Size", "--------------", "Faster Compression", "--------------", "Pre- & Postprocessing Only", "Help"} with title "Actions & Options" with prompt "⌘-Click for multiple selection (up to 1 per group)." OK button name "OK" cancel button name "Cancel" default items options multiple selections allowed yes empty selection allowed no
if options is false then error number -128
end tell
# Customization for some options
if options contains "Drop Shadow…" then
set shadowValue to the text returned of (display dialog "Set new shadow parameters (default: `80x3+2+2`):" default answer shadowValue buttons {"Cancel", "Save & Run"} default button "Save & Run")
set shadowValue of theDefaults to shadowValue
set item 1 of options to "Drop Shadow"
else if options contains "Polaroid…" then
set theResult to (display dialog "Set new polaroid angle (default `-.5`):" default answer polaroidValue buttons {"Cancel", "Save & Relaunch", "Save & Run"} default button "Save & Run")
set polaroidValue to theResult's text returned
set polaroidValue of theDefaults to polaroidValue
if the theResult's button returned is "Save & Relaunch" then
set item 1 of options to "Polaroid w/Caption…"
set options of theDefaults to options
run me
return
else
set item 1 of options to "Polaroid"
end if
end if
# Set options
if options contains "Drop Shadow" then
# http://www.imagemagick.org/Usage/blur/#shadow
set option1 to "\\( +clone -background black -shadow" & space & shadowValue & space & "\\) +swap -background none -layers merge +repage"
else if options contains "Polaroid" then
# http://www.imagemagick.org/Usage/transform/#polaroid
# Nice colors: *OldLace, linen, *WhiteSmoke, moccasin, AntiqueWhite, PapayaWhip
set option1 to "-bordercolor WhiteSmoke -background gray30 -polaroid" & space & polaroidValue & space & "-unsharp" & space & usmValueForPolaroid
else if options contains "Polaroid w/Caption…" then
set polaroidCaption to the text returned of (display dialog "Set caption:" default answer polaroidCaption)
set polaroidCaption of theDefaults to polaroidCaption
set option1 to "-bordercolor GhostWhite -background gray30" & space & polaroidCaption & space & "-polaroid" & space & polaroidValue & space & "-unsharp" & space & usmValueForPolaroid
else if options contains "Torn Edges" then
# http://www.imagemagick.org/Usage/thumbnails/#torn
set option1 to "\\( +clone -alpha extract -virtual-pixel black -spread 10 -blur 0x3 -threshold 50% -spread 1 -blur 0x.7 \\) -alpha off -compose Copy_Opacity -composite"
else if options contains "Torn Edges +Shadow" then
set option1 to "\\( +clone -alpha extract -virtual-pixel black -spread 10 -blur 0x3 -threshold 50% -spread 1 -blur 0x.7 \\) -alpha off -compose Copy_Opacity -composite - | convert - \\( +clone -background black -shadow 50x2+2+2 \\) +swap -background none -layers merge +repage"
else if options contains "Thick" then
# http://www.imagemagick.org/Usage/thumbnails/#thickness
set option1 to "-alpha set \\( +clone -fill LightSteelBlue3 -colorize 100% -repage +0+1 \\) \\( +clone -repage +1+2 \\) \\( +clone -repage +1+3 \\) \\( +clone -repage +2+4 \\) \\( +clone -repage +2+5 \\) \\( +clone -repage +3+6 \\) \\( +clone -repage +3+7 \\) \\( +clone -repage +4+8 \\) \\( +clone -repage +4+9 \\) -background none -compose DstOver -mosaic"
end if
set lastItem to -1
if item lastItem of options is "Help" then
set options to prevOptions
set theResult to display alert "Hints:" message helpMsg buttons {"Cancel", "Continue"} default button "Continue" cancel button "Cancel"
delay 0.5
run me
return
else if item lastItem of options is "Pre- & Postprocessing Only" then
set onlyPPP to true
if (count of options) is 2 and item -2 of options is "Faster Compression" then
# oxipng: our default is `-o max`; the empty string sets it to oxi's default (2 of 6)
# `-q` if using zopfli
set compression to ""
end if
else
# These options are combinable with the first option (option1); should always be the last item in the list
if item lastItem of options is "Faster Compression" then
# oxipng: our default is `-o max`; the empty string sets it to oxi's default (2 of 6)
# `-q` if using zopfli
set compression to ""
set lastItem to -2
end if
tell item lastItem of options
if it contains "%" then
set scaleValue to word -1
set option2 to "-scale" & space & scaleValue & "%" & space & "-unsharp" & space & usmValueForResized
else if it contains "width" then
set scaleValue to word -3
set option2 to "-scale" & space & scaleValue & "\\>" & space & "-unsharp" & space & usmValueForResized
else if it contains "USM" then
set option2 to "-unsharp" & space & usmValueForUnchangedSize
end if
end tell
end if
set options of theDefaults to options
# Main part
repeat with image in trgImages
set image to POSIX path of image
set {head, extension} to splitPath(image)
# Most of the stuff runs only on pngs
if extension is "png" then
preprocess(image, ppi)
if onlyPPP then
postprocess(image, compression)
else
if item 1 of options is "Quantize" and (count of options) is 1 then
# For convenience, quantization only
quantize(image, extension, head)
else if options contains "Quantize" then
# One IM command + quantization (as stdin/stdout combo)
combo(image, extension, head, option1, option2)
else
# Only IM, without quantization
prettify(image, extension, head, option1, option2)
end if
postprocess(outName, compression)
end if
else
set nonPNGs to nonPNGs + 1
end if
end repeat
display notification (((count of trgImages) - nonPNGs) as text) & " image(s) processed." with title "Screenshot Prettifier" sound name "Submarine"
if nonPNGs > 0 then
display alert (nonPNGs as text) & " non-PNG files found. Only PNG files have been processed!" as warning buttons "OK"
end if
### Handlers #######################################################
# Get path components
on splitPath(path)
set saveTID to AppleScript's text item delimiters
set AppleScript's text item delimiters to {"."}
set extension to text item -1 of path
set head to (text items 1 through -2 of path) as text
set AppleScript's text item delimiters to saveTID
return {head, extension}
end splitPath
# A pure IM command
on prettify(image, extension, head, option1, option2)
set outName to head & "-pty." & extension
do shell script binPath & space & "convert" & space & quoted form of image & space & ¬
option1 & space & option2 & space & quoted form of outName
end prettify
# Pure Pngquant
on quantize(image, extension, head)
set outName to head & "-fs8." & extension
do shell script binPath & space & "pngquant --floyd --skip-if-larger --force --speed=1 --output" & ¬
space & quoted form of outName & space & quoted form of image
end quantize
# Both combined (preferable, since no intermediate file writing)
on combo(image, extension, head, option1, option2)
set outName to head & "-pty-fs8." & extension
do shell script binPath & space & "convert" & space & quoted form of image & space & ¬
option1 & space & option2 & space & "- | pngquant - --floyd --force --speed=1 --output" & ¬
space & quoted form of outName
end combo
on preprocess(input, ppi)
if ppi is not "" then
do shell script "sips -s dpiHeight" & space & ppi & space & "-s dpiWidth" & space & ppi & space & quoted form of input
end if
end preprocess
on postprocess(input, compression)
# zopfli: need to keep pHYs chunk for ppi metadata; use `-q` for fast mode.
--set {head, extension} to splitPath(input)
--set outName to head & "-z." & extension
-- do shell script binPath & space & "zopflipng --keepchunks=pHYs -y -q" & space & quoted form of input & space & quoted form of input
# oxipng seems better currently; `-s` preserves the pHYs chunk:
do shell script binPath & space & "oxipng" & space & compression & space & "-a -s" & space & quoted form of input
end postprocess
Edit 2024-03-18:
Version 0.9.9:
- Added path detection for
convert
. This should make it compatible out of the box with both ARM and X86 Homebrew paths. - Path detection is only done if no path is set (usually the first run); if your bin path changes afterwards, delete the script's preferences file in ~/Library/Preferences and run it again (or set the path manually in the script, if your binaries are not in one of the two Homebrew standard paths; see comment around line 33 in the script).