Bits and Pieces

Insightful articles, step-by-step tutorials, and the latest news on full-stack composable software development

Follow publication

Cut this concept into pieces, this is my last dev blog

Lee Martin
Bits and Pieces
Published in
9 min readMar 12, 2022

--

A brown sticker stating Papa Roach Life Cutter 22nd Anniversary Edition. A roach sits on top.

Life Cutter

A clip from the actual “Life Cutter” live stream

Loading Images

loadImage(url) {
return new Promise((resolve, revoke) => {
// Initialize image
let img = new Image()
// Image loaded
img.onload = () => {
// Resolve
resolve(img)
} // Update image src to url
img.src = url
})
}

Generating Cuts

// Initialize cuts
let cuts = []

// Get source cut sizes
let sCutWidth = image.width / this.cutCols
let sCutHeight = image.height / this.cutRows

// Get destination cut sizes
let dCutWidth = this.photoSize / this.cutCols
let dCutHeight = this.photoSize / this.cutRows
// Loop through rows
for (let row = 0; row < this.cutRows; row += 1) {
// Loop through cols
for (let col = 0; col < this.cutCols; col += 1) {
// Get source cut position
let sCutX = col * sCutWidth
let sCutY = row * sCutHeight

// Render cut
let canvas = this.renderCut(image, sCutX, sCutY, sCutWidth, sCutHeight, 0, 0, dCutWidth, dCutHeight)

// Push cutting
cuts.push({
canvas: canvas,
cut: false,
cutOffset: col * dCutWidth,
cutThreshold: 1 - (row / (this.cutRows))
})
}
}

// Return cuts
return cuts
renderCut(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) {
// Create offline canvas
let canvas = document.createElement('canvas')

// Resize canvas
canvas.height = dHeight
canvas.width = dWidth

// Get context
let context = canvas.getContext('2d')

// Draw image
context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

// Return canvas
return canvas
}

Canvas Initialization

// Base
// ----------

// Get canvas
let canvas = document.getElementById('life-cutter')

// Resize canvas
canvas.height = window.innerHeight
canvas.width = window.innerWidth

// Get context
let context = canvas.getContext('2d')
// Resize
// ----------

// Listen for window resizing
window.addEventListener('resize', () => {
// Resize canvas
canvas.height = window.innerHeight
canvas.width = window.innerWidth
})

Canvas Rendering

// Request animation frame
requestAnimationFrame(animate)

// Clear rect
context.clearRect(0, 0, canvas.width, canvas.height)

// Clip top area
context.save()
context.beginPath()
context.rect(0, 0, canvas.width, canvas.height / 2)
context.clip()

// If a photo exists
if (this.photo) {
// Get image and positions
let { image, x, y } = this.photo

// Draw image
context.drawImage(image, 0, 0, image.width, image.height, x, y, this.photoSize, this.photoSize)

// Draw stroke
context.strokeRect(x, y, this.photoSize, this.photoSize)
}

// Restore clipping
context.restore()
// Clip bottom area
context.save()
context.beginPath()
context.rect(0, canvas.height / 2, canvas.width, canvas.height / 2)
context.clip()

// If pieces exist
if (this.pieces.length) {
// Loop through each piece
this.pieces.forEach((piece, i) => {
// Move piece down
piece.y += piece.v_y

// If piece is below canvas
if (piece.y > canvas.height) {
// Remove piece from array
this.pieces.splice(i, 1)
} else {
// Adjust vertical velocity
piece.v_y = piece.v_y + 0.3
// Adjust rotation
piece.rotation += piece.rotate
// Get center rotation point
let centerX = piece.x + (piece.canvas.width / 2)
let centerY = piece.y + (piece.canvas.height / 2)
// Rotate
context.translate(centerX, centerY)
context.rotate(piece.rotation * Math.PI / 180)
context.translate(-centerX, -centerY)
// Draw piece
context.drawImage(piece.canvas, piece.x, piece.y)
// Draw stroke
context.strokeRect(piece.x, piece.y, piece.canvas.width, piece.canvas.height)
// Reset transform
context.setTransform(1, 0, 0, 1, 0, 0)
}
})
}

// Restore clipping
context.restore()

Adding Photos

// Load image
let image = await this.loadImage(url)

// Initialize photo
this.photo = {
image: image,
cuts: this.generateCuts(image),
x: Math.floor(Math.random() * (window.innerWidth - this.photoSize)),
y: -this.photoSize
}
// Initialize timeline
let tl = gsap.timeline({
onComplete: () => {
// Add next photo
this.addPhoto()
}
})
// Send photo to cutter
tl.to(this.photo, {
duration: 2.5,
ease: 'power4.out',
y: (window.innerHeight / 2) - this.photoSize
})
// Pass photo through cutter
tl.to(this.photo, {
duration: 2,
ease: 'linear',
y: window.innerHeight / 2,
onUpdate: function () {
// Find uncut cuts ready for cutting
let uncuts = photo.cuts.filter(cut => {
return !cut.cut && this.progress() > cut.cutThreshold
})

// Loop through uncut cuts
uncuts.forEach(uncut => {
// Set cut to true
uncut.cut = true

// Cut into pieces
pieces.push({
canvas: uncut.canvas,
x: photo.x + uncut.cutOffset,
y: window.innerHeight / 2,
v_y: Math.random() * 10 - 5,
rotation: 0,
rotate: Math.random() * 4 - 2
})
})
}
})
// Vibrate photo
tl.to(this.photo, {
duration: 0.1,
repeat: 20,
x: `random(${this.photo.x - 10}, ${this.photo.x + 10})`
}, "<")

Thanks

Papa Roach from the “Last Resort” music video. Jacoby stands in center of crowd of fans. Fisheye camera.
Papa Roach in “Last Resort” music video

--

--

Published in Bits and Pieces

Insightful articles, step-by-step tutorials, and the latest news on full-stack composable software development

Written by Lee Martin

Netmaker. Playing the Internet in your favorite band for two decades. Previously Silva Artist Management, SoundCloud, and Songkick.

No responses yet

Write a response