Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A Scriptable script that creates "invisible" widget backgrounds based on your iOS wallpaper, and then either uses them as a Scriptable widget background or exports to your camera roll.
// This widget was created by Max Zeryck @mzeryck
/*
You can't add commit messages to gists, so I just want to say thanks to everyone who has used, modified,
and enjoyed this script. This version adds support for the iPhone 12 mini, thanks to arealhen for providing
a screenshot, and mintakka for a temporary solution.
*/
// Widgets are unique based on the name of the script.
const filename = Script.name() + ".jpg"
const files = FileManager.local()
const path = files.joinPath(files.documentsDirectory(), filename)
if (config.runsInWidget) {
let widget = new ListWidget()
widget.backgroundImage = files.readImage(path)
// You can your own code here to add additional items to the "invisible" background of the widget.
Script.setWidget(widget)
Script.complete()
/*
* The code below this comment is used to set up the invisible widget.
* ===================================================================
*/
} else {
// Determine if user has taken the screenshot.
var message
message = "Before you start, go to your home screen and enter wiggle mode. Scroll to the empty page on the far right and take a screenshot."
let exitOptions = ["Continue","Exit to Take Screenshot"]
let shouldExit = await generateAlert(message,exitOptions)
if (shouldExit) return
// Get screenshot and determine phone size.
let img = await Photos.fromLibrary()
let height = img.size.height
let phone = phoneSizes()[height]
if (!phone) {
message = "It looks like you selected an image that isn't an iPhone screenshot, or your iPhone is not supported. Try again with a different image."
await generateAlert(message,["OK"])
return
}
// Extra setup needed for 2436-sized phones.
if (height == 2436) {
let cacheName = "mz-phone-type"
let cachePath = files.joinPath(files.libraryDirectory(), cacheName)
// If we already cached the phone size, load it.
if (files.fileExists(cachePath)) {
let typeString = files.readString(cachePath)
phone = phone[typeString]
// Otherwise, prompt the user.
} else {
message = "What type of iPhone do you have?"
let types = ["iPhone 12 mini", "iPhone 11 Pro, XS, or X"]
let typeIndex = await generateAlert(message, types)
let type = (typeIndex == 0) ? "mini" : "x"
phone = phone[type]
files.writeString(cachePath, type)
}
}
// Prompt for widget size and position.
message = "What size of widget are you creating?"
let sizes = ["Small","Medium","Large"]
let size = await generateAlert(message,sizes)
let widgetSize = sizes[size]
message = "What position will it be in?"
message += (height == 1136 ? " (Note that your device only supports two rows of widgets, so the middle and bottom options are the same.)" : "")
// Determine image crop based on phone size.
let crop = { w: "", h: "", x: "", y: "" }
if (widgetSize == "Small") {
crop.w = phone.small
crop.h = phone.small
let positions = ["Top left","Top right","Middle left","Middle right","Bottom left","Bottom right"]
let position = await generateAlert(message,positions)
// Convert the two words into two keys for the phone size dictionary.
let keys = positions[position].toLowerCase().split(' ')
crop.y = phone[keys[0]]
crop.x = phone[keys[1]]
} else if (widgetSize == "Medium") {
crop.w = phone.medium
crop.h = phone.small
// Medium and large widgets have a fixed x-value.
crop.x = phone.left
let positions = ["Top","Middle","Bottom"]
let position = await generateAlert(message,positions)
let key = positions[position].toLowerCase()
crop.y = phone[key]
} else if(widgetSize == "Large") {
crop.w = phone.medium
crop.h = phone.large
crop.x = phone.left
let positions = ["Top","Bottom"]
let position = await generateAlert(message,positions)
// Large widgets at the bottom have the "middle" y-value.
crop.y = position ? phone.middle : phone.top
}
// Crop image and finalize the widget.
let imgCrop = cropImage(img, new Rect(crop.x,crop.y,crop.w,crop.h))
message = "Your widget background is ready. Would you like to use it as this script's background, or export the image for use in a different script or another widget app?"
const exportPhotoOptions = ["Use for this script","Export to Photos","Export to Files"]
const exportPhoto = await generateAlert(message,exportPhotoOptions)
if (exportPhoto == 0) {
files.writeImage(path,imgCrop)
} else if (exportPhoto == 1) {
Photos.save(imgCrop)
} else if (exportPhoto == 2) {
await DocumentPicker.exportImage(imgCrop)
}
Script.complete()
}
// Generate an alert with the provided array of options.
async function generateAlert(message,options) {
let alert = new Alert()
alert.message = message
for (const option of options) {
alert.addAction(option)
}
let response = await alert.presentAlert()
return response
}
// Crop an image into the specified rect.
function cropImage(img,rect) {
let draw = new DrawContext()
draw.size = new Size(rect.width, rect.height)
draw.drawImageAtPoint(img,new Point(-rect.x, -rect.y))
return draw.getImage()
}
// Pixel sizes and positions for widgets on all supported phones.
function phoneSizes() {
let phones = {
// 12 Pro Max
"2778": {
small: 510,
medium: 1092,
large: 1146,
left: 96,
right: 678,
top: 246,
middle: 882,
bottom: 1518
},
// 12 and 12 Pro
"2532": {
small: 474,
medium: 1014,
large: 1062,
left: 78,
right: 618,
top: 231,
middle: 819,
bottom: 1407
},
// 11 Pro Max, XS Max
"2688": {
small: 507,
medium: 1080,
large: 1137,
left: 81,
right: 654,
top: 228,
middle: 858,
bottom: 1488
},
// 11, XR
"1792": {
small: 338,
medium: 720,
large: 758,
left: 54,
right: 436,
top: 160,
middle: 580,
bottom: 1000
},
// 11 Pro, XS, X, 12 mini
"2436": {
x: {
small: 465,
medium: 987,
large: 1035,
left: 69,
right: 591,
top: 213,
middle: 783,
bottom: 1353,
},
mini: {
small: 465,
medium: 987,
large: 1035,
left: 69,
right: 591,
top: 231,
middle: 801,
bottom: 1371,
}
},
// Plus phones
"2208": {
small: 471,
medium: 1044,
large: 1071,
left: 99,
right: 672,
top: 114,
middle: 696,
bottom: 1278
},
// SE2 and 6/6S/7/8
"1334": {
small: 296,
medium: 642,
large: 648,
left: 54,
right: 400,
top: 60,
middle: 412,
bottom: 764
},
// SE1
"1136": {
small: 282,
medium: 584,
large: 622,
left: 30,
right: 332,
top: 59,
middle: 399,
bottom: 399
},
// 11 and XR in Display Zoom mode
"1624": {
small: 310,
medium: 658,
large: 690,
left: 46,
right: 394,
top: 142,
middle: 522,
bottom: 902
},
// Plus in Display Zoom mode
"2001" : {
small: 444,
medium: 963,
large: 972,
left: 81,
right: 600,
top: 90,
middle: 618,
bottom: 1146
},
}
return phones
}
@simonbs

This comment has been minimized.

Copy link

@simonbs simonbs commented Sep 27, 2020

I tweaked the script a bit to make it export either to the Files app or Photos. I prefer this over the hard coded path it exported to before. It’s just a personal preference but I’ll share my updated script here for anyone who might be interested.

// This widget was created by Max Zeryck @mzeryck

if (config.runsInWidget) {
  let widget = new ListWidget()
  widget.backgroundImage = files.readImage(path)
  
  // You can your own code here to add additional items to the "invisible" background of the widget.
  
  Script.setWidget(widget)
  Script.complete()

/*
 * The code below this comment is used to set up the invisible widget.
 * ===================================================================
 */
} else {
  
  // Determine if user has taken the screenshot.
  var message
  message = "Before you start, go to your home screen and enter wiggle mode. Scroll to the empty page on the far right and take a screenshot."
  let exitOptions = ["Continue","Exit to Take Screenshot"]
  let shouldExit = await generateAlert(message,exitOptions)
  if (shouldExit) return
  
  // Get screenshot and determine phone size.
  let img = await Photos.fromLibrary()
  let height = img.size.height
  let phone = phoneSizes()[height]
  if (!phone) {
    message = "It looks like you selected an image that isn't an iPhone screenshot, or your iPhone is not supported. Try again with a different image."
    await generateAlert(message,["OK"])
    return
  }
  
  // Prompt for widget size and position.
  message = "What size of widget are you creating?"
  let sizes = ["Small","Medium","Large"]
  let size = await generateAlert(message,sizes)
  let widgetSize = sizes[size]
  
  message = "What position will it be in?"
  message += (height == 1136 ? " (Note that your device only supports two rows of widgets, so the middle and bottom options are the same.)" : "")
  
  // Determine image crop based on phone size.
  let crop = { w: "", h: "", x: "", y: "" }
  if (widgetSize == "Small") {
    crop.w = phone.small
    crop.h = phone.small
    let positions = ["Top left","Top right","Middle left","Middle right","Bottom left","Bottom right"]
    let position = await generateAlert(message,positions)
    
    // Convert the two words into two keys for the phone size dictionary.
    let keys = positions[position].toLowerCase().split(' ')
    crop.y = phone[keys[0]]
    crop.x = phone[keys[1]]
    
  } else if (widgetSize == "Medium") {
    crop.w = phone.medium
    crop.h = phone.small
    
    // Medium and large widgets have a fixed x-value.
    crop.x = phone.left
    let positions = ["Top","Middle","Bottom"]
    let position = await generateAlert(message,positions)
    let key = positions[position].toLowerCase()
    crop.y = phone[key]
    
  } else if(widgetSize == "Large") {
    crop.w = phone.medium
    crop.h = phone.large
    crop.x = phone.left
    let positions = ["Top","Bottom"]
    let position = await generateAlert(message,positions)
    
    // Large widgets at the bottom have the "middle" y-value.
    crop.y = position ? phone.middle : phone.top
  }
  
  // Crop image and finalize the widget.
  let imgCrop = cropImage(img, new Rect(crop.x,crop.y,crop.w,crop.h))
  
  message = "Your widget background is ready. Would you like to use it in a Scriptable widget or export the image?"
  const exportPhotoOptions = ["Export to Files","Export to Photos"]
  const exportPhoto = await generateAlert(message,exportPhotoOptions)
  
  if (exportPhoto) {
    Photos.save(imgCrop)
  } else {
    await DocumentPicker.exportImage(imgCrop)
  }
  
  Script.complete()
}

// Generate an alert with the provided array of options.
async function generateAlert(message,options) {
  
  let alert = new Alert()
  alert.message = message
  
  for (const option of options) {
    alert.addAction(option)
  }
  
  let response = await alert.presentAlert()
  return response
}

// Crop an image into the specified rect.
function cropImage(img,rect) {
   
  let draw = new DrawContext()
  draw.size = new Size(rect.width, rect.height)
  
  draw.drawImageAtPoint(img,new Point(-rect.x, -rect.y))  
  return draw.getImage()
}

// Pixel sizes and positions for widgets on all supported phones.
function phoneSizes() {
  let phones = {	
	"2688": {
			"small":  507,
			"medium": 1080,
			"large":  1137,
			"left":  81,
			"right": 654,
			"top":    228,
			"middle": 858,
			"bottom": 1488
	},
	
	"1792": {
			"small":  338,
			"medium": 720,
			"large":  758,
			"left":  54,
			"right": 436,
			"top":    160,
			"middle": 580,
			"bottom": 1000
	},
	
	"2436": {
			"small":  465,
			"medium": 987,
			"large":  1035,
			"left":  69,
			"right": 591,
			"top":    213,
			"middle": 783,
			"bottom": 1353
	},
	
	"2208": {
			"small":  471,
			"medium": 1044,
			"large":  1071,
			"left":  99,
			"right": 672,
			"top":    114,
			"middle": 696,
			"bottom": 1278
	},
	
	"1334": {
			"small":  296,
			"medium": 642,
			"large":  648,
			"left":  54,
			"right": 400,
			"top":    60,
			"middle": 412,
			"bottom": 764
	},
	
	"1136": {
			"small":  282,
			"medium": 584,
			"large":  622,
			"left": 30,
			"right": 332,
			"top":  59,
			"middle": 399,
			"bottom": 399
	}
  }
  return phones
}
@janik6n

This comment has been minimized.

Copy link

@janik6n janik6n commented Sep 27, 2020

Very nice! One issue I noticed on my ancient iPhone 8 Plus is that the image aligns a couple of pixels too much on the left.

@mzeryck

This comment has been minimized.

Copy link
Owner Author

@mzeryck mzeryck commented Sep 27, 2020

@janik6n The Plus was the hardest phone to find screenshots of! I would be happy to fix it if you're willing to take a screenshot like the one linked here - basically, three small widgets in a diagonal pattern (they can be any widgets, so long as they contrast with the background).

@sebi-alt

This comment has been minimized.

Copy link

@sebi-alt sebi-alt commented Sep 27, 2020

Nice one,
I´ve been working on the exact same thing, but as a webpage.
Gotta be faster next time 😉

@janik6n

This comment has been minimized.

@mzeryck

This comment has been minimized.

Copy link
Owner Author

@mzeryck mzeryck commented Sep 28, 2020

@janik6n Huh, I'm getting the same width/height values from that screenshot. Please feel free to message me on Twitter (same @) if you are willing to troubleshoot further -- I'm wondering if the Plus is weird because it's the only iPhone that downsamples the entire display.

@mikecharles

This comment has been minimized.

Copy link

@mikecharles mikecharles commented Sep 29, 2020

I’m also getting an image with a few columns of blank pixels on the fidget side of the image. I can send a screenshot tomorrow

@janik6n

This comment has been minimized.

Copy link

@janik6n janik6n commented Sep 29, 2020

@mzeryck I figured it out. As a user, when setting a wallpaper, I need to set the Perspective zoom OFF. That was messing with the dimensions.

@mikecharles

This comment has been minimized.

Copy link

@mikecharles mikecharles commented Sep 29, 2020

@mzeryck I figured it out. As a user, when setting a wallpaper, I need to set the Perspective zoom OFF. That was messing with the dimensions.

I have perspective zoom off and my image is still a bit shifted ☹️

@alekhat

This comment has been minimized.

Copy link

@alekhat alekhat commented Oct 21, 2020

Hi, I took a screenshot of my empty home page (perspective zoom: off), copied the code into scriptable, and pressed run. It came up with the prompt to add a screenshot and I did. But it says my phone or the image isn’t compatible. ): any help would be appreciated.

Edit: I have an iPhone 8 plus

@mzeryck

This comment has been minimized.

Copy link
Owner Author

@mzeryck mzeryck commented Oct 23, 2020

@alekhat I think the update I just posted should work now - let me know if it doesn't!

@alekhat

This comment has been minimized.

Copy link

@alekhat alekhat commented Oct 23, 2020

@alekhat I think the update I just posted should work now - let me know if it doesn't!

THANK YOU SO MUCH! This is perfect

@tslagle13

This comment has been minimized.

Copy link

@tslagle13 tslagle13 commented Oct 24, 2020

Doesn't work for iPhone 12 Pro. I tried to edit it to work but I don't think I fully understand the script and it broke. Any help getting it to work for iPhone 12 Pro would be awesome! :)

EDIT

I just read your comment at the top and realized you asked for some screenshots. Does this one work? Used a white background and black widgets to give as much contrast as possible.
8F30440C-9CB1-47D3-AA9A-A70B117C53A8

@mzeryck

This comment has been minimized.

Copy link
Owner Author

@mzeryck mzeryck commented Oct 24, 2020

@tslagle13 That is perfect, thank you so much! I just added 12 and 12 Pro support thanks to you, really appreciate it.

@spikeyhair

This comment has been minimized.

Copy link

@spikeyhair spikeyhair commented Nov 4, 2020

I use the latest script on iphone 11 pro max, even I have turned off the perspective zoom and regardless how many images I use to try, there is always a white line at the right side of the widget.

is it a bug in the script or any settings i need to do on widgetsmith?

it only happen for the medium size widget, no problem with the large size widget.

IMG_5449 222

@mzeryck

This comment has been minimized.

Copy link
Owner Author

@mzeryck mzeryck commented Nov 4, 2020

@spikeyhair This is a bug in Widgetsmith. For some reason, horizontal images don't fill the entire widget, leaving a white line at the edge. It works for small and large widgets because they're square. Please feel free to submit feedback to the developer (it's in Widgetsmith > Settings > Contact & Help) about this issue, although it's an app made by just one developer so he may not be able to respond.

@Augustus88

This comment has been minimized.

Copy link

@Augustus88 Augustus88 commented Nov 8, 2020

@ mzeryck

Hey Max, your widget is really good!!! However, I have problems at "weather calendar widget builder” and always get the following hint:

2020-11-08 17:09:55: Error on line 769:51: TypeError: undefined is not an object (evaluating 'weatherDataRaw.current.temp')

Can you help meines me?

I’m looking forward to a feedback.

@idec

This comment has been minimized.

Copy link

@idec idec commented Nov 12, 2020

@mzeryck

Hey there Max, congratulations for such amazing script! We are building a similar feature in one of our apps.

Digging a bit, I'm not sure if you guys support iPhone 12 Pro Max (1284 x 2778), so maybe this is helpful:

// 12 Pro Max
    "2778": {
      small:  510,
      medium: 1092,
      large:  1146,
      left:  96,
      right: 678,
      top:    246,
      middle: 882,
      bottom: 1518
    }

Feel free to correct me if I'm wrong.

Once again awesome job and awesome app @simonbs!

@mzeryck

This comment has been minimized.

Copy link
Owner Author

@mzeryck mzeryck commented Nov 13, 2020

@idec Oh my gosh, thank you so much! My Pro Max arrives later today and I've been anxious to update the script. Really appreciate it!

@StratosHF

This comment has been minimized.

Copy link

@StratosHF StratosHF commented Nov 16, 2020

I just got my iPhone 12 mini, is there going to be an update for this design?

@mzeryck

This comment has been minimized.

Copy link
Owner Author

@mzeryck mzeryck commented Nov 16, 2020

@StratosHF The most recent version of this gist does have 12 mini support! Please let me know if you have any issues with it.

@StratosHF

This comment has been minimized.

Copy link

@StratosHF StratosHF commented Nov 16, 2020

This is my first time ever trying this kind of thing sorry if I’m doing something wrong but I’m getting some weird shifting. Like it needs to be moved up an eighth of an inch.
BDB61CA6-FBF0-47B3-9E29-FDC4101AD42C

@mzeryck

This comment has been minimized.

Copy link
Owner Author

@mzeryck mzeryck commented Nov 16, 2020

@StratosHF When you ran the script, did it prompt you to select your phone type, like this:
IMG_0015

If it didn't, that means your script is on an older version. You'd just need to copy the code from the very top of this page.

@StratosHF

This comment has been minimized.

Copy link

@StratosHF StratosHF commented Nov 16, 2020

That fixed it, not sure how I got the wrong script, but thank you for pointing out my mistake. Works perfectly!

@mzeryck

This comment has been minimized.

Copy link
Owner Author

@mzeryck mzeryck commented Nov 16, 2020

@StratosHF Don't worry about it, I'm glad it works now! :)

@janik6n

This comment has been minimized.

Copy link

@janik6n janik6n commented Nov 16, 2020

Just wanted to confirm that this works nicely on 12 Pro Max! Once again, great work 👏

@arlon8taruc

This comment has been minimized.

Copy link

@arlon8taruc arlon8taruc commented Nov 20, 2020

I got the calendar working but i cant make it to the transparent part? I think im lost!!!

@mzeryck

This comment has been minimized.

Copy link
Owner Author

@mzeryck mzeryck commented Nov 21, 2020

@arlon8taruc When you copy and paste the code into a script, and press Run, it should guide you through the process. At the end, it will make an image. You can use that image as the background for any widget, making that widget appear "transparent". Does that help?

@david-zou

This comment has been minimized.

Copy link

@david-zou david-zou commented Nov 29, 2020

I’ve noticed that the ‘transparent’ widget background created from this script has slightly lower image quality than the actual wallpaper background (edges are noticeably blurrier), possibly due to jpeg compression. Is there currently any way to fix this by preserving the original image quality? FWIW, running it in a 12 Pro Max.

@mzeryck

This comment has been minimized.

Copy link
Owner Author

@mzeryck mzeryck commented Nov 30, 2020

@david-zou If you save the exported image to Photos or Files, you'll see that the script does export at full resolution - it's just that all Scriptable widgets reduce the resolution of background images. I'm not sure why. You can see this if you use one of these backgrounds in a different widget app, they'll appear sharper.

@08ggininder

This comment has been minimized.

Copy link

@08ggininder 08ggininder commented Dec 1, 2020

How to display week language as zh_CN?

@mzeryck

This comment has been minimized.

Copy link
Owner Author

@mzeryck mzeryck commented Dec 2, 2020

@08ggininder I'm guessing you're asking about Weather Cal, which version do you have? There are text settings for all of the hard-coded words on the widget that you can edit.

@yung40oz84

This comment has been minimized.

Copy link

@yung40oz84 yung40oz84 commented Dec 4, 2020

How do I get the scriptable image processor JS file to work with iPhone 12 Pro Max? I like the blue backgrounds and this is just for clear. What would I have to add to the other to make it function with 12 pro Max? Thanks!

@behns13

This comment has been minimized.

Copy link

@behns13 behns13 commented Dec 18, 2020

Is it possible to automate the steps to complete this script (i.e. the jiggle mode, screenshot, widget size, transparency, option)? I’ve dabbled in setting my wallpaper through Automation (changes daily), and I think it’s be really cool to incorporate this into it! I guess your Weather Cal widget would also need to be incorporated for it to work too.

Anyways, just a thought! Great scripts mate! It literally changed the way I use my iPhone on a daily basis!

@mzeryck

This comment has been minimized.

Copy link
Owner Author

@mzeryck mzeryck commented Dec 21, 2020

@yung40oz84 The most recent version should work with the 12 Pro Max!

@behns13 Thanks, I'm glad you enjoy it! With that new calendar automation stuff I am definitely thinking about ways to integrate into Weather Cal - wallpaper changes for Dark Mode have been requested many times, so check the GitHub page hopefully sometime this week while I'm on vacation! :)

@LuKres1

This comment has been minimized.

Copy link

@LuKres1 LuKres1 commented Jan 7, 2021

7500487F-AF59-41B0-9A98-83421AEA963D
I have one problem... How to fix it?

@Duckbilled

This comment has been minimized.

Copy link

@Duckbilled Duckbilled commented Feb 12, 2021

7500487F-AF59-41B0-9A98-83421AEA963D
I have one problem... How to fix it?

I have the same issue, have you managed to fix it?

i think the code in the scriptable gallery just doesn’t really work well :)

@CaractacusPott

This comment has been minimized.

Copy link

@CaractacusPott CaractacusPott commented Mar 23, 2021

@mzeryck this is really an excellent widget. Is there any chance you would consider adding a permissive license such as MIT to it just so other developers could get clarity? Regardless - huge fan of this (wish Apple would've included it out of the box!). Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment