Skip to content

Instantly share code, notes, and snippets.

@perliedman
Created December 20, 2020 14:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save perliedman/9c70bd016a4e998b1c9eb4a8cc348ea5 to your computer and use it in GitHub Desktop.
Save perliedman/9c70bd016a4e998b1c9eb4a8cc348ea5 to your computer and use it in GitHub Desktop.
function collage(imgUrls, canvas) {
const options = {
margin: 4,
spacing: 2,
borderRadius: 5
}
const ctx = canvas.getContext('2d')
const { width, height } = canvas
const drawWidth = width - 2 * options.margin
const drawHeight = height - 2 * options.margin
const aspectRatio = drawWidth / drawHeight
const sqrt = Math.sqrt(imgUrls.length)
if (Math.abs(Math.round(sqrt) - sqrt) > 1e-3) {
throw new Error(`Images does not form an even rectangle (${sqrt}`)
}
const nPerRow = Math.round(sqrt * aspectRatio)
const imgAreaWidth = (drawWidth + options.margin) / nPerRow
const imgAreaHeight = (drawHeight + options.margin) / nPerRow / aspectRatio
const imgDrawWidth = imgAreaWidth - options.spacing
const imgDrawHeight = imgAreaHeight - options.spacing
const imgAspect = drawWidth / drawHeight
console.log({ width, height, nPerRow, imgDrawWidth, imgDrawHeight})
Promise.all(imgUrls.map(drawImage))
function drawImage(url, i) {
return new Promise((resolve, reject) => {
const image = new Image()
image.src = url
image.onload = () => {
const row = Math.floor(i / nPerRow)
const col = i % nPerRow
const dx = col * imgAreaWidth
const dy = row * imgAreaHeight
const imageAspect = image.width / image.height
let sx, sy, sWidth, sHeight
// const maxDim = Math.max(image.width, image.height)
const aspectsAspect = imgAspect / imageAspect
if (aspectsAspect < 1) {
sWidth = image.width * aspectsAspect
sHeight = image.height
sx = (image.width - sWidth) / 2
sy = 0
} else {
sWidth = image.width
sHeight = image.height / aspectsAspect
sx = 0
sy = (image.height - sHeight) / 2
}
if (Math.abs(imgAspect) - (sWidth/sHeight) > 1e-3) {
throw new Error(`Calculated aspect is ${sWidth/sHeight}`)
}
ctx.save()
ctx.clip(roundRect(dx, dy, imgDrawWidth, imgDrawHeight, options.borderRadius))
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, imgDrawWidth, imgDrawHeight)
ctx.restore()
resolve()
}
image.onerror = reject
})
}
}
/**
* Draws a rounded rectangle using the current state of the canvas.
* If you omit the last three params, it will draw a rectangle
* outline with a 5 pixel border radius
* @param {Number} x The top left x coordinate
* @param {Number} y The top left y coordinate
* @param {Number} width The width of the rectangle
* @param {Number} height The height of the rectangle
* @param {Number} radius The corner radius. Defaults to 5
*/
function roundRect(x, y, width, height, radius) {
const path = new Path2D()
path.moveTo(x + radius, y)
path.lineTo(x + width - radius, y)
path.quadraticCurveTo(x + width, y, x + width, y + radius)
path.lineTo(x + width, y + height - radius)
path.quadraticCurveTo(x + width, y + height, x + width - radius, y + height)
path.lineTo(x + radius, y + height)
path.quadraticCurveTo(x, y + height, x, y + height - radius)
path.lineTo(x, y + radius)
path.quadraticCurveTo(x, y, x + radius, y)
path.closePath()
return path
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment