Background image in widget is not displayed correctly

I am trying to display a cropped part of a panoramic-like image in a widget.
The image is located here: https://i.ibb.co/bWGv3Qd/10-10-00-h572.jpg
I found a useful script snippet to crop an image with a canvas inside a webview - so far so good.
If I display the cropped image with QuickLook.present() inside the app, the image is cropped and displayed as desired.
But: if I display the image inside a ListWidget via widget.backgroundImage, two strange things happen:

  • the image is not cropped at all but the original one from the URL stated above is shown
  • the image is not displayed throughout the whole widget background but just in a small upper left corner. The image dimension are set correctly though.

Does anyone has a clue whats going on here?

The image inside the app/inside the widget looks like this:


(I added the image’s width and height as text to prove the dimensions)

As you can see: the image in the app is a cropped version of the upper left part of the full image - which is displayed in the widget.

Code of the script:

let fullSizeImage = await new Request("https://i.ibb.co/bWGv3Qd/10-10-00-h572.jpg").loadImage()
let croppedImage = await processImage(fullSizeImage)
if (config.runsInWidget) {
    const widget = await createWidget(croppedImage)
    Script.setWidget(widget)
} else {
    QuickLook.present(croppedImage)
}
Script.complete()

async function createWidget(croppedImage) {
    let result = new ListWidget();
    result.backgroundImage = croppedImage
    result.addText(croppedImage.size.width.toString());
    result.addText(croppedImage.size.height.toString());
    //result.addImage(croppedImage);
    return result;
}

// This function takes an image and returns a processed image.
async function processImage(image) {

    const imageId = "imageId"
    const canvasId = "canvasId"

    const js = `
  // Set up the canvas.
  const img = document.getElementById("${imageId}");
  const canvas = document.getElementById("${canvasId}");
  const w = 987;
  const h = 465;
  canvas.style.width  = w + "px";
  canvas.style.height = h + "px";
  canvas.width = w;
  canvas.height = h;
  const context = canvas.getContext("2d");
  context.clearRect( 0, 0, w, h );
  //image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight
  context.drawImage( img, 0, 0, w, h, 0,0, w,h );
  
  // Image modifications go here. This desaturates the image.
  //context.globalCompositeOperation = "saturation";
  //context.fillStyle = "hsl(0,0%,50%)";
  //context.fillRect(0, 0, w, h);
  // Return a base64 representation.
  canvas.toDataURL(); 
  `

    // Convert the images and create the HTML.
    let inputData = Data.fromPNG(image).toBase64String()
    let html = `
  <img id="${imageId}" src="data:image/png;base64,${inputData}" />
  <canvas id="${canvasId}" />
  `

    // Make the web view and get its return value.
    let view = new WebView()
    await view.loadHTML(html)
    let returnValue = await view.evaluateJavaScript(js)

    // Remove the data type from the string and convert to data.
    let outputDataString = returnValue.slice(returnValue.indexOf(",") + 1)
    outputData = Data.fromBase64String(outputDataString)

    // Convert to image and return.
    return Image.fromData(outputData)
}

Hi there. I made some edits to your widget that fixed your problem. See the attached code.

A few things to note:

  • When creating widgets I suggest you use presentMedium() (or small or large) to preview your widget. This will actually provide you with a quick look of your actual widget, instead of just the image.
  • I always create my initial widget outside of any functions, as the second thing in my scripts (the first being any variables that I need to declare)
  • I personally like to always name my widget ‘widget’. Once you start getting into stacks, it makes it really easy to remember what is at the full widget level vs on a stack level

Hope this helps!

Code:


// error-variables
const PASS = "✅";
const FAIL = "❌";
const WARN = "⚠️";

////// SCRIPT

let fullSizeImage = await new Request("https://i.ibb.co/bWGv3Qd/10-10-00-h572.jpg").loadImage()

let croppedImage = await processImage(fullSizeImage)

var widget = new ListWidget()
widget.backgroundImage = croppedImage
widget.addText(croppedImage.size.width.toString());
widget.addText(croppedImage.size.height.toString());

if (config.runsInApp) {
    // display-preview-widget
	widget.presentMedium(); // other options are '.presentSmall()' or '.presentLarge()'
    console.log(`icon="${PASS}" action="display-preview-widget" message="widget preview displayed"`)

} else {
    // finalize-widget
    Script.setWidget(widget)
    console.log(`icon="${PASS}" action="finalize-widget" message="widget has been set"`)

}

Script.complete()



////// FUNCTIONS

async function createWidget(croppedImage) {
    var result = new ListWidget();
    result.backgroundImage = croppedImage
	result.addText(croppedImage.size.width.toString());
	result.addText(croppedImage.size.height.toString());
    return result;
}

// This function takes an image and returns a processed image.
async function processImage(image) {

    const imageId = "imageId"
    const canvasId = "canvasId"

    const js = `
  // Set up the canvas.
  const img = document.getElementById("${imageId}");
  const canvas = document.getElementById("${canvasId}");
  const w = 987;
  const h = 465;
  canvas.style.width  = w + "px";
  canvas.style.height = h + "px";
  canvas.width = w;
  canvas.height = h;
  const context = canvas.getContext("2d");
  context.clearRect( 0, 0, w, h );
  //image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight
  context.drawImage( img, 0, 0, w, h, 0,0, w,h );
  
  // Image modifications go here. This desaturates the image.
  //context.globalCompositeOperation = "saturation";
  //context.fillStyle = "hsl(0,0%,50%)";
  //context.fillRect(0, 0, w, h);
  // Return a base64 representation.
  canvas.toDataURL(); 
  `

    // Convert the images and create the HTML.
    let inputData = Data.fromPNG(image).toBase64String()
    let html = `
  <img id="${imageId}" src="data:image/png;base64,${inputData}" />
  <canvas id="${canvasId}" />
  `

    // Make the web view and get its return value.
    let view = new WebView()
    await view.loadHTML(html)
    let returnValue = await view.evaluateJavaScript(js)

    // Remove the data type from the string and convert to data.
    let outputDataString = returnValue.slice(returnValue.indexOf(",") + 1)
    outputData = Data.fromBase64String(outputDataString)

    // Convert to image and return.
    return Image.fromData(outputData)
}

Hi samanthafav,

thanks for your reply! To use presentMedium() for a preview of the widget is a good advice, thanks.

Anyway, I copy-pasted your code and I can see the widget preview in the app with the correct cropped image. But: the widget itself has still the not-cropped-one as shown in my screenshot in the first post. Does this work on your phone? Or did you just tried it with the widget preview inside the app?

regards

Hi @bergbiber, happy to help!

You’re correct, I didn’t test this out in an actual widget, since I assumed presentMedium() would be an adequate substitute. Now I know it’s not for all things.

I swapped out your cropped image with the full sized image as the widget background and the image actually covers the background. So, it leads me to believe that there’s something with the way you’re cropping the image. But I’m not very familiar with using canvas in js.

Here’s an example of a widget script that crops images to us as the background image. This shows an alternative way to crop the image. Might be worth giving that a shot?

Hope this helps!