syuji-higa
5/1/2019 - 10:25 AM

Node.js - create sprite sheet for JavaScript

Node.js - create sprite sheet for JavaScript

const glob = require('glob')
const mkdirp = require('mkdirp')
const { writeFile } = require('fs')
const { promisify } = require('util')
const { relative, dirname, basename, extname, join } = require('path')
const Spritesmith = require('spritesmith')
const imagemin = require('imagemin')
const pngquant = require('imagemin-pngquant')
const mozjpeg = require('imagemin-mozjpeg')
const gifsicle = require('imagemin-gifsicle')

const plugins = {
  png: pngquant({
    quality: [0.5, 0.6], // 0 ~ 1
    speed: 1
  }),
  jpg: mozjpeg({
    quality: 80 // 0 ~ 100
  }),
  gif: gifsicle({
    optimizationLevel: 3 // 1 ~ 3
  })
}

const options = {
  algorithmOpts: { sort: false }
}

const writeFileAsync = promisify(writeFile)
const globAsync = promisify(glob)
const mkdirpAsync = promisify(mkdirp)

const imgSrcDir = 'src/assets/sprite-sheet'
const imgDistDir = 'src/assets/images'
const imgPathDir = '/assets/images'
const jsDistPath = 'src/assets/js/data/sprite-sheet.js'

;(async () => {
  console.log('[sprite-sheet] start')

  const files = await globAsync(join(imgSrcDir, `**/*.+(png|jpg|gif)`))
  const pathMap = groupBy(files)

  const spritehashs = await Promise.all(
    (() => {
      const _promises = []
      pathMap.forEach((paths, key) => {
        _promises.push(spritesmith(key, paths))
      })
      return _promises
    })()
  )

  const _str = `export const spriteSheet = ${JSON.stringify(spritehashs)};`

  await mkdirpAsync(dirname(jsDistPath))
  await writeFileAsync(jsDistPath, _str)
  console.log(`[sprite-sheet] create ${jsDistPath}`)

  console.log('[sprite-sheet] end')
})()

const spritesmith = (key, paths) => {
  return new Promise((resolve, reject) => {
    Spritesmith.run(
      Object.assign(options, {
        src: paths
      }),
      (err, result) => {
        if (err) return reject(err)

        const { coordinates, properties, image } = result
        const [name, ext] = key.split('--')
        const _fileName = `${name}.${ext}`
        const _dest = join(imgDistDir, _fileName)

        const _data = {
          id: name,
          path: join(imgPathDir, _fileName),
          sheet: {}
        }

        ;(async () => {
          const _buf = await imagemin
            .buffer(new Buffer(image, 'base64'), {
              plugins: [plugins[ext]]
            })
            .catch((err) => {
              if (err) {
                console.log(err)
                return
              }
            })

          await mkdirpAsync(dirname(_dest))

          await writeFileAsync(_dest, _buf)

          console.log(`[sprite-sheet] created image ${_dest}`)

          for (const [path, data] of Object.entries(coordinates)) {
            const _name = basename(path, extname(path))
            const _width = data.width
            const _height = data.height
            const _maxRatio = maxRatio(_width, _height)
            const _minRatio = minRatio(_width, _height)
            _data.sheet[_name] = {
              x: data.x / properties.width,
              y: data.y / properties.height,
              width: _width / properties.width,
              height: _height / properties.height
              widthRatio: _width / _height,
              heightRatio: _height / _width,
              maxRatio: _maxRatio.ratio,
              maxWidthRatio: _maxRatio.width,
              maxHeightRatio: _maxRatio.height,
              minRatio: _minRatio.ratio,
              minWidthRatio: _minRatio.width,
              minHeightRatio: _minRatio.height
            }
          }

          resolve(_data)
        })()
      }
    )
  }).catch((err) => {
    console.error(err)
  })
}

const groupBy = (paths) => {
  return paths.reduce((memo, path) => {
    const _dir = dirname(relative(imgSrcDir, path))
    if (!memo.has(_dir)) {
      memo.set(_dir, [])
    }
    memo.get(_dir).push(path)
    return memo
  }, new Map())
}

const maxRatio = (w, h) => {
  const _maxRatio = Math.max(w / h, h / w)
  return {
    raito: _maxRatio,
    width: w > h ? _maxRatio : 1,
    height: h > w ? _maxRatio : 1
  }
}

const minRatio = (w, h) => {
  const _minRatio = Math.min(w / h, h / w)
  return {
    raito: _minRatio,
    width: w > h ? 1 : _minRatio,
    height: h > w ? 1 : _minRatio
  }
}