-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* chore: reimplemented based on colorthief * refactor: remove unnecessary dependencies * refactor: less is more * perf: add benchmark * docs: update copyright * fix: dependencies * chore(release): 6.0.0-0 * test: use bmp * test: update * perf(benchmark): add missing dependencies * docs(website): add # support in the URL * perf(benchmark): update assets * refactor: easier to read * chore(release): 6.0.0-1 * fix: quantizer bundling * chore(release): 6.0.0-2 * chore: new website * build: remove preinstall * chore(release): 6.0.0-3 * chore: remove submodule * chore: readd website
- Loading branch information
Showing
59 changed files
with
1,749 additions
and
101 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,3 +30,5 @@ coverage | |
# Other | ||
############################ | ||
.node_history | ||
.vercel | ||
website/.next |
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
|
||
# Benchmark | ||
|
||
This is a comparison of various palette-extracting libraries, on the same image. | ||
|
||
## Test 1 | ||
|
||
### Original image | ||
|
||
![original image](./fixtures/image-1.jpg) | ||
|
||
### Palette by `node-vibrant` | ||
|
||
[![node-vibrant](./output/node-vibrant-1.png)](https://iad.microlink.io/09pRn8QBQqzcE10SDzi0Pzr28AJ0wylrt6674C7t_k_epUHqUEK9oS3aO9fUZwMtnP4MM3zRezx8L_DfbgrXOA.png) | ||
|
||
### Palette by `splashy` | ||
|
||
[![splashy](./output/splashy-1.png)](https://iad.microlink.io/eBuqBYZboCokaAgyD53pnAqW7x8wPgI0AYMKSEsZTDUbvFwTBlRoT5xcoq4ooz0YCtyHjnOA_Glt8kbDBhKn7A.png) | ||
|
||
### Palette by `colorthief` | ||
|
||
[![colorthief](./output/colorthief-1.png)](https://iad.microlink.io/oY9RIn21q1TZakZMFfukK-ZhcRcHxritAEcNFRyTR5i9RTzRJ66mMLU_2uU9435ByfCmFMQPWpsbN5rNzAw70Q.png) | ||
|
||
## Test 2 | ||
|
||
### Original image | ||
|
||
![original image](./fixtures/image-2.jpg) | ||
|
||
### Palette by `node-vibrant` | ||
|
||
[![node-vibrant](./output/node-vibrant-2.png)](https://iad.microlink.io/jXMzy3IZpu7t7_hKjZ4JEEEmZcU3llcgxsfs73po0yqd2rhTB2JFbRjSC4umzfEHnpeMVJJNDXzNmdgIr8Z2kg.png) | ||
|
||
### Palette by `splashy` | ||
|
||
[![splashy](./output/splashy-2.png)](https://iad.microlink.io/ZjX6TEkXSpMXPoT2BxMHfc4Eya5qgJr-RJk5Sk2GPabEhT5hmamzYH11GP9BMu0oEYBgpwHpDQLFItns9dgIzg.png) | ||
|
||
### Palette by `colorthief` | ||
|
||
[![colorthief](./output/colorthief-2.png)](https://iad.microlink.io/CHo-y9RRgfVhhYXVTdkT4n93z3uZjY4bk8TZt4MqdowdFfd5-XEVakjbJNJhd2KTWzaqLKw2OgQpiFRfvAk7zQ.png) | ||
|
||
## Test 3 | ||
|
||
### Original image | ||
|
||
![original image](./fixtures/image-3.png) | ||
|
||
### Palette by `node-vibrant` | ||
|
||
[![node-vibrant](./output/node-vibrant-3.png)](https://iad.microlink.io/iuWlt37UxGF8qgBKJEHCbo_Hkdep_v-CRageoXOAPMb9lM7ah18HP1dIpqmnHjWVy_d22NCJOBeAk35A7J2hkw.png) | ||
|
||
### Palette by `splashy` | ||
|
||
[![splashy](./output/splashy-3.png)](https://iad.microlink.io/cLUc7GtkIUTQ0d4sevMnIE1fRRoKDArvb30UwppHN2g5bcvx3Ltzw-F4xdLrd0aw7h490G3XusdtP3t_OwL_cA.png) | ||
|
||
### Palette by `colorthief` | ||
|
||
[![colorthief](./output/colorthief-3.png)](https://iad.microlink.io/SJdrel7u9Cf8_IiV9nWfYtsaEnFEETIY37JEsFJIiVnaX39j93KTmAI45Nu1-lSRqW4NGgBbGw47hbM5j-WwDw.png) | ||
|
||
## Test 4 | ||
|
||
### Original image | ||
|
||
![original image](./fixtures/image-4.png) | ||
|
||
### Palette by `node-vibrant` | ||
|
||
[![node-vibrant](./output/node-vibrant-4.png)](https://iad.microlink.io/HE7HRM1DY45OAWb_wLH7cNxCE55FGUWwvNNgMEJ0dTz4lbSYLf0bT5xiVvdbjEy4GPk3Sy3gz0H-iuK8ZkYBAQ.png) | ||
|
||
### Palette by `splashy` | ||
|
||
[![splashy](./output/splashy-4.png)](https://iad.microlink.io/7OYNVK_p-Gyt_BR2iOAuN3-a55v4B8o2A2ICuCEcy-oh_q7XMzUsyLne0xi-j_IjzWUKRbR2TVRfwdrNQ2AQWA.png) | ||
|
||
### Palette by `colorthief` | ||
|
||
[![colorthief](./output/colorthief-4.png)](https://iad.microlink.io/v0nBKof7Zh9ldqJGy8Pgzqit2lnoPH01HdQBioCwGPsIRNOlZ3Dz9S1DP6lyV8napjhZboVFWeWLuedAtStXWw.png) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { readFile, writeFile } from 'fs/promises' | ||
import { fileURLToPath } from 'node:url' | ||
import colorthief from 'colorthief' | ||
import Vibrant from 'node-vibrant' | ||
import { chain } from 'lodash-es' | ||
import { readdirSync } from 'fs' | ||
import mql from '@microlink/mql' | ||
import path from 'node:path' | ||
|
||
import splashy from '../src/index.js' | ||
|
||
const { toHex } = splashy | ||
|
||
const __dirname = path.dirname(fileURLToPath(import.meta.url)) | ||
|
||
const images = readdirSync(path.resolve(__dirname, 'fixtures')).map(filename => | ||
path.resolve(__dirname, 'fixtures', filename) | ||
) | ||
|
||
const paletteUrl = palette => | ||
`https://splashy-palette.vercel.app/${palette.map(i => encodeURIComponent(i)).join('-')}` | ||
|
||
const screenshotUrl = async palette => { | ||
const url = paletteUrl(palette) | ||
|
||
const { data } = await mql(url, { | ||
screenshot: true, | ||
styles: '#back-button { display: none; }' | ||
}) | ||
|
||
return data.screenshot.url | ||
} | ||
|
||
const download = async url => Buffer.from(await fetch(url).then(res => res.arrayBuffer())) | ||
|
||
async function getPaletteWithNodeVibrant (filepath) { | ||
const vibrantPalette = await Vibrant.from(filepath).getPalette() | ||
return chain(vibrantPalette) | ||
.map() | ||
.sortBy('_population') | ||
.reverse() | ||
.map(value => toHex(value._rgb)) | ||
.value() | ||
} | ||
|
||
async function getPaletteWithSplashy (filepath) { | ||
const buffer = await readFile(filepath) | ||
const splashyPalette = await splashy(buffer) | ||
return chain(splashyPalette) | ||
.map(value => value.replace('#', '')) | ||
.value() | ||
} | ||
|
||
async function getPaletteWithColorthief (filepath) { | ||
const buffer = await readFile(filepath) | ||
const colorthiefPalette = await colorthief.getPalette(buffer) | ||
return colorthiefPalette.map(toHex) | ||
} | ||
|
||
const output = [] | ||
|
||
for (const [index, imagePath] of images.entries()) { | ||
const filepath = path.resolve(__dirname, imagePath) | ||
const basename = path.basename(filepath) | ||
const displayIndex = index + 1 | ||
|
||
output.push(`## Test ${displayIndex}`) | ||
output.push('### Original image') | ||
output.push(`![original image](./fixtures/${basename})`) | ||
|
||
const nodeVibrantPalette = await getPaletteWithNodeVibrant(filepath) | ||
const vibrantScreenshotUrl = await screenshotUrl(nodeVibrantPalette) | ||
await writeFile( | ||
path.resolve(__dirname, 'output', `node-vibrant-${displayIndex}.png`), | ||
await download(vibrantScreenshotUrl) | ||
) | ||
output.push('### Palette by `node-vibrant`') | ||
output.push( | ||
`[![node-vibrant](./output/node-vibrant-${displayIndex}.png)](${vibrantScreenshotUrl})` | ||
) | ||
|
||
const splashyPalette = await getPaletteWithSplashy(filepath) | ||
const splashyLinkScreenshotUrl = await screenshotUrl(splashyPalette) | ||
await writeFile( | ||
path.resolve(__dirname, 'output', `splashy-${displayIndex}.png`), | ||
await download(splashyLinkScreenshotUrl) | ||
) | ||
output.push('### Palette by `splashy`') | ||
output.push(`[![splashy](./output/splashy-${displayIndex}.png)](${splashyLinkScreenshotUrl})`) | ||
|
||
const colorthiefPalette = await getPaletteWithColorthief(filepath) | ||
const colorthiefScreenshotUrl = await screenshotUrl(colorthiefPalette) | ||
await writeFile( | ||
path.resolve(__dirname, 'output', `colorthief-${displayIndex}.png`), | ||
await download(colorthiefScreenshotUrl) | ||
) | ||
output.push('### Palette by `colorthief`') | ||
output.push( | ||
`[![colorthief](./output/colorthief-${displayIndex}.png)](${colorthiefScreenshotUrl})` | ||
) | ||
} | ||
|
||
const readmeContent = ` | ||
# Benchmark | ||
This is a comparison of various palette-extracting libraries, on the same image. | ||
${output.join('\n\n')} | ||
` | ||
|
||
const filepath = path.resolve(__dirname, 'README.md') | ||
await writeFile(filepath, readmeContent) | ||
console.log(`Benchmark results written to ${filepath} ✨`) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"name": "@splashy/benchmark", | ||
"private": true, | ||
"devDependencies": { | ||
"@microlink/mql": "~0.13.12", | ||
"colorthief": "~2.6.0", | ||
"lodash-es": "~4.17.21" | ||
} | ||
} |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,49 @@ | ||
'use strict' | ||
|
||
const { serializeError } = require('serialize-error') | ||
const debug = require('debug-logfmt')('splashy') | ||
const createVibrant = require('./vibrant') | ||
|
||
const toPalette = swatch => | ||
Object.keys(swatch) | ||
.reduce((acc, key) => { | ||
const value = swatch[key] | ||
if (value) { | ||
acc.push({ | ||
popularity: value.getPopulation(), | ||
hex: value.getHex() | ||
}) | ||
} | ||
return acc | ||
}, []) | ||
.sort((a, b) => a.popularity <= b.popularity) | ||
.map(color => color.hex) | ||
|
||
module.exports = async input => { | ||
let swatch | ||
|
||
try { | ||
const vibrant = createVibrant(input) | ||
swatch = await vibrant.getPalette() | ||
} catch (error) { | ||
debug.error(serializeError(error)) | ||
swatch = {} | ||
const quantize = require('@lokesh.dhakar/quantize').default || require('@lokesh.dhakar/quantize') | ||
const ndarray = require('ndarray') | ||
const sharp = require('sharp') | ||
|
||
async function getPixels ({ data, info }) { | ||
return ndarray( | ||
new Uint8Array(data.buffer, data.byteOffset, data.length), | ||
[info.width, info.height, 4], | ||
[4, (4 * info.width) | 0, 1], | ||
0 | ||
) | ||
} | ||
|
||
function createPixelArray (pixels, pixelCount, quality = 10) { | ||
const pixelArray = [] | ||
for (let i = 0, offset; i < pixelCount; i += quality) { | ||
offset = i * 4 | ||
const r = pixels[offset] | ||
const g = pixels[offset + 1] | ||
const b = pixels[offset + 2] | ||
const a = pixels[offset + 3] | ||
const isOpaqueEnough = a >= 125 | ||
const isWhite = r > 250 && g > 250 && b > 250 | ||
if (isOpaqueEnough && !isWhite) pixelArray.push([r, g, b]) | ||
} | ||
|
||
return toPalette(swatch) | ||
return pixelArray | ||
} | ||
|
||
const toHex = ([r, g, b]) => '#' + (b | (g << 8) | (r << 16) | (1 << 24)).toString(16).slice(1) | ||
|
||
module.exports = async function (buffer) { | ||
const raw = await sharp(buffer) | ||
// resizing the image before processing leads to more consistent (and much shorter) processing times. | ||
// .resize(200, 200, { fit: 'inside', withoutEnlargement: true }) | ||
.ensureAlpha() | ||
.raw() | ||
.toBuffer({ resolveWithObject: true }) | ||
|
||
const imgData = await getPixels(raw) | ||
const pixelCount = imgData.shape[0] * imgData.shape[1] | ||
const pixelArray = createPixelArray(imgData.data, pixelCount) | ||
const cmap = quantize(pixelArray, 10) // internal tuning | ||
return cmap.palette().slice(0, 6).map(toHex) | ||
} | ||
|
||
module.exports.toHex = toHex |
Oops, something went wrong.