Skip to content

Instantly share code, notes, and snippets.

@iahu
Created Sep 9, 2021
Embed
What would you like to do?
获取图片主题色
import fs from 'fs'
import glob from 'glob'
import cliProgress from 'cli-progress'
import getThemeColor from './main'
import ProHub from './prohub'
const [path] = process.argv.slice(2)
const bar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic)
glob(path, function (err, matches) {
const total = matches.length
console.log(`共 ${total} 个文件`)
if (total > 0) {
bar.start(total, 0)
}
const tasks = [] as (() => Promise<any>)[]
const result = new Map()
const error = {} as Record<string, any>
matches.forEach(path => {
const task = () =>
getThemeColor(path)
.then(themeColor => {
if (themeColor) {
const { r, g, b, a } = themeColor
result.set(path.split(/[\\/]/g).pop(), `rgba(${r}, ${g}, ${b}, ${a})`)
} else {
result.set(path, '')
}
bar.update(result.size)
if (result.size === total) {
bar.stop()
try {
fs.writeFileSync('./output.json', JSON.stringify(Object.fromEntries(result)))
fs.writeFileSync('./error.json', JSON.stringify(error))
console.log('完成!')
} catch (e) {
return e
}
}
})
.catch(err => {
console.error(err)
error[path] = err.toString()
})
tasks.push(task)
})
new ProHub(tasks, 10).start()
})
import ffmpeg from 'fluent-ffmpeg'
import Jimp from 'jimp'
function resolve(path: string): Promise<string> {
const isWebp = path.endsWith('.webp')
const tempPath = `${path}.png`
if (isWebp) {
return new Promise((resolve, reject) => {
ffmpeg()
.input(path)
.output(tempPath)
.on('error', reject)
.on('end', () => resolve(tempPath))
.run()
})
}
return Promise.resolve(path)
}
const getSamples = (img: Jimp) => {
const w = img.getWidth()
const h = img.getHeight()
return [
img.getPixelColor(0, 0),
img.getPixelColor(0, w),
img.getPixelColor(h / 2, w / 2),
img.getPixelColor(0, w),
img.getPixelColor(h, w),
]
}
const getAvgColor = (simples: number[]) => {
const rgba = simples.reduce(
(acc, item) => {
const { r, g, b, a } = Jimp.intToRGBA(item)
acc.r += r
acc.g += g
acc.b += b
acc.a += a
return acc
},
{ r: 0, g: 0, b: 0, a: 0 }
)
return {
r: rgba.r / 5,
g: rgba.g / 5,
b: rgba.b / 5,
a: rgba.a / 5,
}
}
export default function getThemeColor(path: string) {
return resolve(path)
.then(Jimp.read)
.then(img => {
const w = img.getWidth()
const h = img.getHeight()
const max = Math.max(w, h)
const f = 40 / max
return img.scale(f)
})
.then(img => img.gaussian(20))
.then(img => getSamples(img))
.then(getAvgColor)
}
import EventEmitter from 'events'
type PromiseJob<T = unknown> = () => Promise<T>
class ProHub<T = unknown> extends EventEmitter {
private jobCount: number
private eventJobs = [] as PromiseJob<T>[]
private hubSize: number
runningJobs = [] as PromiseJob<T>[]
constructor(jobs: PromiseJob<T>[], hubSize: number) {
super()
this.jobCount = jobs.length
this.eventJobs = jobs.map(this.jobMapping)
this.hubSize = hubSize
}
private jobMapping = (job: PromiseJob<T>, index?: number) => {
return () => {
const eventJobs = this.eventJobs
const runningJobs = this.runningJobs
return job().then(res => {
const idx = runningJobs.findIndex(job)
const next = eventJobs.shift()
if (next) {
runningJobs[idx] = next
} else {
runningJobs.splice(idx, 1)
}
const data = { value: res, index, next: next?.(), done: !next }
this.emit('shift', data)
if (runningJobs.length === 0) {
this.emit('done', data)
}
return res
})
}
}
async start(): Promise<void> {
const eventJobs = this.eventJobs
this.runningJobs = eventJobs.splice(0, this.hubSize)
const runningJobs = this.runningJobs
await Promise.all(runningJobs.map(j => j()))
}
push(job: PromiseJob<T>): number {
this.jobCount += 1
return this.eventJobs.push(this.jobMapping(job, this.jobCount))
}
pop(): PromiseJob<T> | undefined {
this.jobCount -= 1
return this.eventJobs.pop()
}
}
export default ProHub
@iahu

This comment has been minimized.

Copy link
Owner Author

@iahu iahu commented Sep 9, 2021

过程说明:

  1. 将图片缩小到 40px * 40px 左右
  2. 对缩小的图片运用高斯模糊,使得小图像素糊成一团,基本上只有一个主色调
  3. 取四个角与中心点,求其平均值,产出一个较合理的主题色值
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment