For the last in this initial series of examples of what you can do with SinterPixels, we’ll make an animation of drawing this stained glass window from the Grande Museo del Duomo in Milan.

We start with a picture of the stained glass, then set the alpha of every pixel to 0, so it starts off looking like a blank canvas. Then, we’ll progressively set the alpha of each pixel higher to reveal the image.

We’ll start with just three pixels, gradually making them opaque. When a pixel becomes opaque, it “activates” its neighbors. The rate at which a pixel becomes opaque is proportional to its “darkness”. This leads to the effect where the dark bands of the window appear first, and the lighter interiors of each pane become opaque more slowly.

To manage the list of activated pixels, the demo uses JapaScript in stead of AppleScript, as JavaScript has built in Array and Map types, which AppleScript doesn’t have built in (although, similar capabilities are accessible using the AppleScript ObjectiveC feature, with the NSMutableArray and NSMutableDictionary types).

Most of the script is just looping through the ‘growing pixels’ list, gradually setting the alpha of those pixels to larger values.

The script in its entirety is below: The prerequisite is to load an image and set the name to “reference”, so that the script can refer to it. And, before starting the script, set all the alpha components to zero with this AppleScript:

tell application "SinterPixels"
	set alpha component of every pixel of document "reference" to 0.0
end tell
const SinterPixels = Application('SinterPixels');

let refDoc = SinterPixels.documents("reference")
let scriptFinished = false 
let docWidth = refDoc.width() 
let docHeight = refDoc.height()
let darknessDict = new Map(); 
let growthRateDict = new Map();   
let activationDict = new Map();  
let alphaDict = new Map(); 

class pixelCoord {
    constructor(x, y) {
        this.x = x;
        this.y = y; 
    }
}
	
function getDarknessFromColor(refColor) {
	let re = refColor.redComponent
	let gr = refColor.greenComponent
	let bl = refColor.blueComponent
    return (1.0 - ((re + gr + bl) / 3.0))
}

function arrayIndexFor(pix) {
	let ai = pix.x + pix.y * docWidth 
	return ai
}

function setDarkness(pix, drk) {
	let arrayIndex = arrayIndexFor(pix)
	darknessDict.set(arrayIndex, drk) 
}

function getDarkness(pix) {
	let arrayIndex = arrayIndexFor(pix)
	let drk = darknessDict.get(arrayIndex)
	return drk 
}

function setGrowthRate(pix, gr) {
	let arrayIndex = arrayIndexFor(pix)
	growthRateDict.set(arrayIndex, gr)
}

function getGrowthRate(pix) {
	let arrayIndex = arrayIndexFor(pix)
	let gr = growthRateDict.get(arrayIndex)
	return gr  
}

function setActivationVal(pix, av) {
	let arrayIndex = arrayIndexFor(pix)
	activationDict.set(arrayIndex, av)
}

function getActivationVal(pix)  {
	let arrayIndex= arrayIndexFor(pix)	
	let av = activationDict.get(arrayIndex)	
	return av  
}

function canBeActivated(pix) {
	let av = getActivationVal(pix)
	let canBe = av == 0
	return canBe
}

function isActivated(pix) {
	let av = getActivationVal(pix)
	let isAct = av == 1
	return isAct
}

function setActivated(pix) {
	setActivationVal(pix, 1) 
}

function markPixelDone(pix) {
	setActivationVal(pix, 2)
}

function isPixelDone(pix) {
	let av = getActivationVal(pix)
	let isDone = av == 2
	return isDone
}

function setAlphaDictOnly(pix, newAlpha) {
	let arrayIndex = arrayIndexFor(pix)
	alphaDict.set(arrayIndex, newAlpha)
}

function setPixAlpha(pix, newAlpha) {
	let newAlf = newAlpha
	if (newAlpha > 1.0) {
		 newAlf = 1.0
	}
	let arrayIndex = arrayIndexFor(pix)
	alphaDict.set(arrayIndex, newAlf)
	refDoc.rows[pix.y].pixels[pix.x].alphaComponent = newAlpha
	return newAlf == 1.0
}

function getPixAlpha(pix) {
	let arrayIndex = arrayIndexFor(pix)
	let alf = alphaDict.get(arrayIndex)
	return alf  
}

function addRate(pix, rate) {
	let oldRate = getGrowthRate(pix)  
	let newGrowthRate = oldRate + rate
	setGrowthRate(pix, newGrowthRate)
}

function activatePixel(pix, rate) {
	let av = getActivationVal(pix)
	if (av == 1) {
		 addRate(pix, rate)
		return false
	} else if (av == 0) {
		let darkness = getDarkness(pix)
		 setActivated(pix)
		 addRate(pix, rate + darkness)
		return true
	}
}

function activatePixels(activationList) {
	for (const activationListItem in activationList) {
		let pix = activationListItem[0]
		let rate =  activationListItem[1]
		let darkness = getDarkness(pix)
		 activatePixel(pix, rate + darkness)
	}
}

function initializePixels() {
	let pixArray = new Array();
	let pixa = new pixelCoord(20,docHeight - 2)
	let halfx = Math.trunc(docWidth / 2)
	let pixb = new pixelCoord(halfx, docHeight - 2)
	let pixc = new pixelCoord(docWidth - 20, docHeight - 2)
	pixArray.push(pixa)
 	pixArray.push(pixb)
 	pixArray.push(pixc)
    activatePixel(pixa, 0.5)
     activatePixel(pixb, 0.5)
     activatePixel(pixc, 0.5)
	return pixArray
}

function growPixel(pix) {
	if (pixExists(pix) == false) {
	    return false
	}
	let gr = getGrowthRate(pix)
	let pa = getPixAlpha(pix)
	let newAlpha = pa + gr 
	let setPixResult = setPixAlpha(pix, newAlpha)
	return setPixResult
}

function pixExists(pix) {
	let xc = pix.x
	let yc = pix.y
	return (xc >= 0) && (yc >= 0) && (xc < (docWidth)) && (yc < (docHeight))
}

function addCoords(pix, dx, dy) {
	let newx = pix.x + dx
	let newy = pix.y + dy
	return [newx, newy]
}

function activateNeighbors(pix) {
	let darkness = getDarkness(pix)

	let newlyActive = new Array()
	let nnDeltas = [[1, 0], [0, 1], [-1, 0], [0, -1]]
	let diagonalDeltas = [[1, 1], [-1, 1], [-1, -1], [1, -1]] 
	let nndCount = nnDeltas.length

	for (let nndi = 0; nndi<nndCount ; ++ nndi) {
	   let nnd = nnDeltas[nndi]
		let newCoords = addCoords(pix, nnd[0], nnd[1])
		let newPix = new pixelCoord(newCoords[0], newCoords[1])
		if (pixExists(newPix)) {
			let activated = activatePixel(newPix, darkness * darkness * 0.08)
			if (activated) { 
				  newlyActive.push(newPix)
			}
		}
	}
	let ddCount = diagonalDeltas.length
	for (let ddi = 0; ddi <ddCount ; ++ ddi) {
	    let dd = diagonalDeltas[ddi]
		let diagCoords = addCoords(pix, dd[0], dd[1])
		let diagPix = new pixelCoord(diagCoords[0], diagCoords[1])
		if (pixExists(diagPix)) {
			let activated = activatePixel(diagPix, darkness * darkness * 0.05)
			if (activated) {
				 newlyActive.push(diagPix)
			}
		}
	}
	return newlyActive
} 

function initRefDoc() {
  for (let y = 0; y < docHeight; ++y) {
     for (let x = 0; x < docWidth; ++x) {
        refDoc.rows[y].pixels[x].alphaComponent = 0.0
     }
   }
}

function initData() {  
   for (let yc = 0; yc < docHeight; ++yc) {
     let rowRefColors = refDoc.rows[yc].pixels.color() ;
        for (let xc = 0; xc < docWidth; ++xc) {
			let darkness = getDarknessFromColor(rowRefColors[xc])		
	 	    let pix = new pixelCoord(xc, yc)
			 setDarkness(pix, darkness)
			 setGrowthRate(pix, 0.0)
			 setAlphaDictOnly(pix, 0.0)
			 setActivationVal(pix, 0)
      }
   }
}
 
function run () {
	initData()
	let growingPixels = initializePixels()
	refDoc.filming.start()
    while (scriptFinished == false) { 
		let donePixels = new Array()
		let nextGrowingPixels = new Array()
		let gpCount = growingPixels.length
		for (let gpn = 0; gpn<gpCount; ++gpn) {
		    let gp = growingPixels[gpn]
			let gpDone = growPixel(gp)
			
			if (gpDone) {
				 donePixels.push(gp) 
			} else {
				nextGrowingPixels.push(gp)
			}
		}
		let dpCount = donePixels.length
		for (let dpn = 0; dpn < dpCount; ++dpn ) {
		    let dp = donePixels[dpn]
			let activatedPixels = activateNeighbors(dp)
			let anCount = activatedPixels.length
			for (let ani = 0 ; ani < anCount; ++ani) {
			    let ap = activatedPixels[ani]
			}
			if (activatedPixels.length > 0) {
			    nextGrowingPixels.push(...activatedPixels)
			}
			markPixelDone(dp)
		}
		refDoc.recordMovieFrame()
		growingPixels = nextGrowingPixels
		scriptFinished = growingPixels.length == 0
	}
	refDoc.filming.stop({filename: "Fin.mov"})
}