利用 JS 進行圖片處理並生成對應粒子圖

Yzz發表於2019-01-31

平時需要實現幾個的動效來改善無聊的中後臺業務帶來的負面情緒。

概述:利用 JS 以及 Three.js 對下圖進行處理

利用 JS 進行圖片處理並生成對應粒子圖

來生成對應的粒子圖,例項程式碼

利用 JS 進行圖片處理並生成對應粒子圖

主要分為以下幾個步驟

1. 獲取對應影象資訊

首先讀取圖片,可以利用 document.images 獲取頁面中 img 的資訊。

再將 img 繪製到 canvas 畫布,利用 getImageData 獲取影象的畫素資訊,具體如下 getImgData

const getImgData = img => {
    // 寬、高
    const { width, height } = img
    const canvas = document.createElement('canvas')
    const ctx = canvas.getContext('2d')
    // 畫素數
    const numPixels = width * height

    canvas.width = width
    canvas.height = height
    ctx.scale(1, -1)
    ctx.drawImage(img, 0, 0, width, height * -1)
    // 用來描述canvas區域隱含的畫素資料
    const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height)

    return {
        width,
        numPixels,
        originalColors: Float32Array.from(imgData.data),
    }
}
複製程式碼

imgData.data 包含著圖片的畫素資料的陣列,即 RGBA 值:

  • R - 紅色 (0-255);
  • G - 綠色 (0-255);
  • B - 藍色 (0-255);
  • A - alpha 通道 (0-255; 0 是透明的,255 是完全可見的)。

imgData.data 排列順序是每個畫素點的 [R, G, B, A, R, G, B, A, ...] ,如圖

利用 JS 進行圖片處理並生成對應粒子圖

2. 影象處理

上一步中獲取到相關影象的畫素資訊,然後處理四個通道的資訊 originalColors

由於影象的背景為黑色,也就是說可以利用R 通道的資料來進行閾值分割

設定一個閾值 threshold ,如果滿足 originalColors[i * 4 + 0] > threshold 的條件,則統計該畫素點可見,然後遍歷 originalColors 得到可見畫素點的位置的座標。

const getParticleData = (img, threshold) => {
    const { width, numPixels, originalColors } = getImgData(img)
    let numVisible = 0
    // 統計大於閾值的畫素點
    for (let i = 0; i < numPixels; i++) {
        if (originalColors[i * 4 + 0] > threshold) numVisible++
    }
    
    const offsets = new Float32Array(numVisible * 3)
    // 獲取畫素點的位置
    for (let i = 0, j = 0; i < numPixels; i++) {
        if (originalColors[i * 4 + 0] > threshold) {
            // 獲取 x 方向的座標
            offsets[j * 3 + 0] = i % width
            // 獲取 y 方向的座標
            offsets[j * 3 + 1] = Math.floor(i / width)

            j++
        }
    }

    return {
        offsets
    }
}
複製程式碼

3. 生成粒子圖

這一步比較簡單,就是利用得到的畫素位置來生成對應的粒子圖,首先初始化場景、相機等要素

// init webGL
const scene = new THREE.Scene()
const group = new THREE.Group()
scene.add(group)
const camera = new THREE.PerspectiveCamera(
    50,
    window.innerWidth / window.innerHeight,
    10,
    10000
)
camera.position.z = 300
const fovHeight = 2 * Math.tan(camera.fov * Math.PI / 180 / 2) * camera.position.z
const renderer = new THREE.WebGLRenderer({
    canvas: document.getElementById('canvas'),
    antialias: true,
    alpha: true,
})
renderer.setClearColor(0x000000, 1)
複製程式碼

然後,在 offsets 的位置資料上,生成對應的粒子

設定每個粒子的 material ,然後利用 TweenMax.to 使得粒子過渡至對應位置。

const textureLoader = new THREE.TextureLoader()
const map = textureLoader.load('./assets/images/circle.png')
const material = new THREE.SpriteMaterial({
    map,
    color: 0xffffff,
    fog: true
})
const positions = offsets

for (let index = 0; index < positions.length; index += 2) {
    const particleMaterial = material
    const particle = new THREE.Sprite(particleMaterial)
    // 粒子目標位置
    const targetX = positions[index]
    const targetY = positions[index + 1]
    const targetZ = positions[index + 2]
    
    if (targetX && targetY) {
        // 粒子的初始位置
        particle.position.x = 0
        particle.position.y = 0
        particle.position.z = 0
        // 粒子從初始位置過渡到目標位置
        TweenMax.to(particle.position, 1, {
            x: targetX,
            y: targetY,
            z: targetZ,
            delay: Math.random() * 0.1
        })
        
        group.add(particle)
    }
}
複製程式碼

4. 總結

簡單的閾值分割結合粒子特效對影象進行處理,如果需要其他特效,可以處理粒子的 material,例如 RawShaderMaterial ,可以實現如下效果

利用 JS 進行圖片處理並生成對應粒子圖

相關文章