Applescript Tutorials, Examples, and Hacks

Talk about Pixelmator Pro, share tips & tricks, tutorials, and other resources.
User avatar

2021-01-02 05:39:23

Love love love that Pixelmator Pro now supports scripting. I’m not an experienced AppleScript writer, but I have programming experience and manage to hack a way through, so take this with a grain of salt.

The official tutorial is helpful but very limited. For my purposes, I mainly missed seeing much about processing multiple files or layers. Some of these examples are partly based on hacks from other threads, thanks to all contributors.

This tutorial is organized around 5 examples:

* Simple user prompting
* Resizing multiple images and export
* Feathering layers using selection refiniment and masking
* Repositioning multiple layers - Including grouped layers (limited)
* Using Automator to hotkey a script

User Interaction

User prompting is limited in AppleScript and we will not cover extensive input validation.

Prompt for an Integer

See the official guide references for more complete treatment. This is just brief for code copying.

We will prompt the user for text (a number), the resolution of the short side of an image, and want it accessible as the variable `userNumber`

With no input validation:
--- Prompt user for short side resolution
set userNumber to (display dialog "Enter short side resolution:" default answer "1080")
Now with a little validation (ensures that casting the text as an integer doesn't result in an error, and reprompts if it does):
	--- Prompt user for short side resolution
	repeat 
		set dialogResult to (display dialog "Enter short side resolution:" default answer "1080")
		try
			set userNumber to (text returned of dialogResult) as integer
			exit repeat
		end try
		display dialog "Needs a valid integer." buttons {"Enter again", "Cancel"} default button 1
	end repeat
Prompt with Buttons

AppleScript allows prompting with upto 3 buttons. Here is a short oneline two button prompt:
set userOrient to (button returned of (display dialog "Select stacking orientation:" buttons {"Vertical", "Horizontal"} default button 1))
Prompts with the title: "Select stacking orientation:", presents buttons labeled "Vertical" and "Horizontal". The variable `userOrient` will contain the label of the button the user pressed. So response will simply look like
if userOrient is "Vertical" then
	-- Actions for vertical orientation ...
else
	-- Actions for horizontal orientation ...
end if
Resizing Multiple Images and Export

Action to perform:

* Prompt to select files
* Prompt to select folder to export to
* Prompt for the resolution to set as the smaller dimension of each file
* Iteratively open and resize each of the selected files and export to the selected folder

Demonstrates:

* File/Folder prompting
* Opening, processing, exporting and closing files
* Thus achieving non-destructive multi file processing
tell application "Pixelmator Pro"
	-- Prompt for resize resolution
	repeat
		set dialogResult to (display dialog "Enter short side resolution:" default answer "1080")
		try
			set userNumber to (text returned of dialogResult) as integer
			exit repeat
		end try
		display dialog "Needs a valid integer." buttons {"Enter again", "Cancel"} default button 1
	end repeat
	-- Open a prompt that lets you pick multiple Pixelmator Pro files to process 
	-- and save references to all those images in the originalImages variable
	set originalImages to choose file with prompt ¬
		"Please select the images to process:" with multiple selections allowed
	-- Open a prompt to choose the location where the files should be exported 
	set exportLocation to choose folder with prompt ¬
		"Please select where you'd like export the images:"
	-- Start a repeat loop that loops over each image
	repeat with a from 1 to number of originalImages
		-- Open the current image in the loop
		set currentImage to open item a of originalImages
		-- Apply resizing
		if width of currentImage < height of currentImage then
			tell currentImage to resize image width userNumber
		else
			tell currentImage to resize image height userNumber
		end if
		-- Export the images to the location chosen previously as Pixelmator Pro files
		export currentImage to file ((exportLocation as text) & ¬
			name of currentImage & "-edited" & ".HEIC") as HEIC with properties {compression factor:90}
		-- Close the current image without saving
		close currentImage without saving
	end repeat
	display notification (number of originalImages as text) & ¬
		" images have been successfully processed." with title "Pixelmator Pro Processing"
end tell
Feathering Layers Using Selection Refiniment and Masking

Action to perform:

* List selected layers (needs to be done because the processing will change the selection)
* Use selection and refinement to feather (fade the edges) of each layer
* Reassign the original selection

Demonstrates:

* Processing all selected layers individually, sequentially
* Restoring selection
tell application "Pixelmator Pro"
	tell its front document
		-- List all selected layers
		set sel_lay to selected layers --alternatively use: (every layer whose selected is true)
		-- Process each layer
		repeat with lay in sel_lay -- don't put selected layers here, it will fail as actions change the selection
			deselect
			unmask lay
			load selection of the lay mode add selection
			refine selection roundness 50.0 softness 60.0 expand -20.0
			mask lay
		end repeat
		set selected layers to sel_lay
	end tell
end tell
Repositioning Multiple Layers

Action to perform: Stack all selected layers in a corner selected by the user

* Prompt user for a corner (top/bottom, left/right) using buttons
* Prompt user for vertical or horizontal stacking (if multiple chosen)
* Prompt user for % stacking offset depending adjacent layer size
* Assemble a list of selected layers, unpacking one level of group layers
* Move all the listed layers into a stack in the selected corner

Demonstrates:

* Button and integer prompting
* Listing selected layers
* Iterating to act on all selected layers
tell application "Pixelmator Pro"
	tell its front document
		-- Prompt for which corner to use
		set dlgY to (display dialog "Select Top/Bottom:" buttons {"Top", "Bottom"} default button 1)
		set dlgX to (display dialog "Select Left/Right:" buttons {"Left", "Right"} default button 1)
		set userLocY to (button returned of dlgY)
		set userLocX to (button returned of dlgX)
		set userLoc to userLocY & userLocX
		
		set x_prop_offset to 0 -- % x position offset between selected layers
		set y_prop_offset to 0 -- % y position offset between selected layers
		
		-- Get selected layers and unpack one level of selected groups
		set sel_lays to selected layers
		set sel_lay to {}
		set counter to 0
		repeat with clay in sel_lays
			if (class of clay) is group layer then
				set lays to layers of clay
			else
				set lays to {clay}
			end if
			repeat with lay in lays
				copy lay to the end of sel_lay
			end repeat
		end repeat
		
		-- Prompt for vertical spacing relative to the height of the first layer
		if (count of sel_lay) is greater than 1 then
			set userOrient to (button returned of (display dialog "Select stacking orientation:" buttons {"Vertical", "Horizontal"} default button 1))
			if userOrient is "Vertical" then
				set spacingPrompt to "Enter offset as % height of adjacent layer:"
			else
				set spacingPrompt to "Enter offset as % width of adjacent layer:"
			end if
			repeat
				set dialogResult to (display dialog spacingPrompt default answer "100")
				try
					set userNumber to (text returned of dialogResult) as integer
					exit repeat
				end try
				display dialog "Needs a valid integer." buttons {"Enter again", "Cancel"} default button 1
			end repeat
			if userOrient is "Vertical" then
				set y_prop_offset to userNumber
			else
				set x_prop_offset to userNumber
			end if
		end if
		
		-- Set the stack
		repeat with i from 1 to count of sel_lay
			set lay to item i of sel_lay
			if i is greater than 1 then
				-- set location of stacked layers realtive to each other
				set prev to item (i - 1) of sel_lay
				set prev_pos to position of prev
				set x_offset to (x_prop_offset * (width of prev)) / 100
				set y_offset to (y_prop_offset * (height of prev)) / 100
				if userLocX is "Left" then
					set xloc to x_offset + (item 1 of prev_pos)
				else
					set xloc to -x_offset + (item 1 of prev_pos)
				end if
				if userLocY is "Top" then
					set yloc to y_offset + (item 2 of prev_pos)
				else
					set yloc to -y_offset + (item 2 of prev_pos)
				end if
				set position of lay to {xloc, yloc}
			else
				-- set location according to user prompt
				if userLoc = "TopLeft" then
					set position of lay to {0, 0}
				else if userLoc = "TopRight" then
					set position of lay to {0 + (width) - (width of lay), 0}
				else if userLoc = "BottomLeft" then
					set position of lay to {0, 0 + (height) - (height of lay)}
				else if userLoc = "BottomRight" then
					set position of lay to {0 + (width) - (width of lay), 0 + (height) - (height of lay)}
				end if
			end if
		end repeat
	end tell
end tell
Using Automator to Hotkey a Script

* Launch Automator
* Create new file, select "Quick Action"
* On the right choose: Workflow recieves "no input" in "Pixelmator Pro"
* On the left, search for "Run AppleScript", drag and drop to the workflow on the middle right
* Replace `(* Your script goes here *)` with your AppleScript
* Save the Automation, e.g. `My Processing Script`
* ( In Pixelmator Pro, the script can be run under "Pixelmator Pro" top menu item and "Services" )
* Launch System Preference, goto "Keyboard"
* Select "Shortcuts" tab
* On the left panel, select "App Shortcuts", click `+` on the bottom of the right panel
* Under Application, select "Pixelmator Pro"
* Under Menu Item, write the name of your script from Automator, e.g. `My Processing Script`
* Under Keyboard Shortcut, assign a keyboard shortcut (be careful of duplicates)
User avatar

2021-01-03 03:14:28

Bonus - Delete all invisible layers with bug workaround

Action to perform: Delete all invisible layers among selected layers
* List all selected layers, individually including layers in 1 level of group layers
* List all layers the are invisible
* Merge all invisible layers, and set properties of the resulting layer
* Delete the merged layer
* (The merging before deletion is to workaround what seem like bugs in the deletion of layers function)
tell application "Pixelmator Pro"
	tell its front document
		-- Unpack one level of selected groups, list them all
		set sel_lays to selected layers
		set sel_lay to {}
		set counter to 0
		repeat with clay in sel_lays
			if (class of clay) is group layer then
				set lays to layers of clay
			else
				set lays to {clay}
			end if
			repeat with lay in lays
				copy lay to the end of sel_lay
			end repeat
		end repeat
		
		-- List all invisible layers
		set invlay to {}
		repeat with i from 1 to count of sel_lay
			set lay to item i of sel_lay
			if visible of lay is false then
				copy lay to end of invlay
			end if
		end repeat
		
		-- Merge all invisible layers and delete the resulting layer
		-- (they can't be just deleted individually, probably because of a bug)
		if (count of invlay) is greater than 0 then
			set mlay to merge layers invlay
			set name of mlay to "Temporary merger of all invisbles"
			set visible of mlay to false
			delete mlay
		end if
	end tell
end tell
User avatar

2021-01-04 05:27:00

Bonus 2 - Scripts to recursively obtain each individual layers in a list of layers including group layers
on GetImageLayersRecursively(lays)
	-- Recursively retrieve the individual layers in a list of layers, delving group layers
	tell application "Pixelmator Pro"
		set laysOut to {}
		repeat with lay in lays
			if class of lay is group layer then
				set lays to my GetImageLayersRecursively(layers of lay)
				set laysOut to laysOut & lays
			else
				set laysOut to laysOut & {lay}
			end if
		end repeat
	end tell
	return my RemoveDuplicateLayers(laysOut)
end GetImageLayersRecursively

on RemoveDuplicateLayers(lays)
	-- Remove duplicate layers in a list of layers
	tell application "Pixelmator Pro"
		set laysOut to {}
		set laysID to {}
		repeat with lay in lays
			if id of lay is not in laysID then
				set laysOut to laysOut & {lay}
				set laysID to laysID & {id of lay}
			end if
		end repeat
	end tell
	return laysOut
end RemoveDuplicateLayers
User avatar

2021-01-04 18:24:11

Ooh, I like your handler(s) for recursively getting all layers in a group! The other stuff looks great too, though I haven't had time to check it out.