Created
September 9, 2021 06:29
-
-
Save iahu/b400772868e51a02072c4fe28eb31590 to your computer and use it in GitHub Desktop.
获取图片主题色
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
过程说明: