Skip to content

Instantly share code, notes, and snippets.

@nyteshade

nyteshade/ViNCEdThemes.js

Last active Jan 5, 2021
Embed
What would you like to do?
ViNCEdThemes
/*
Small sample script designed to use JavaScript, with Chrome, to pull the actual
RGB values out of the sample theme images located in the iTerm2 Color Schemes repo
https://github.com/mbadolato/iTerm2-Color-Schemes
To use, navigate to the page above, copy this entire file to the clipboard, open
the developer console (Ctrl-Shift-i and select console, like Command-Shift-i on
macs) and paste the contents into that window.
For the most part, theme image file names are the theme name in snake case, such
as "3024 Night" being 3024_night. This is important for the next steps
run the `getAndCopy` function with a regular expression matching the image file
name you desire. In the above example, you'll need to enter the following
`getAndCopy(/3024_night/i)`
It should show you the results in the console window, but you will have two seconds
to select the main browser window area if you would like the results output added
into your clipboard; this is due to browser security.
When you're done your clipboard should be full of the contents you desire about the
theme in question.
Note since the locations being queried are pixel location specific, you may assume
that it only works well on themes of 642x357. Most follow this convention, but a
few do not. If you wish to locate the correct relative pixel locations in another
size, create an object with the same keys as `normalSize_642x357` with the appropriate
x and y coordinates for each of the colors you desire from the image. cc stands for
cursor color and is primarily used by ViNCEd. I chose to use the top left background
color from these images for that purpose.
Copyright (c)2021 Brielle Harrison
Free use by anyone for any purpose.
Distribute how you'd like.
$VER: ViNCEdThemes 1.0 (4.1.2021)
*/
/**
* Default coordinates for each color and cursor color (cc) found within the
* standard size example image.
*/
let normalSize_642x357 = {
cc: [10,10],
black: [172,33],
red: [237,33],
green: [299,33],
yellow: [364,33],
blue: [427,33],
magenta: [490,33],
cyan: [550,33],
white: [620,33],
}
let japanesque_631x363 = {
cc: [10,10],
black: [125,33],
red: [185,33],
green: [250,33],
yellow: [310,33],
blue: [375,33],
magenta: [440,33],
cyan: [505,33],
white: [565,33],
}
/**
* This is the main function of this script. It creates a <canvas> tag and a new
* <img> tag from the supplied source and then paints the image into the canvas.
* From this point, it then checks the supplied coordinates and builds out a page
* of text with rgb values for both normal and bright colors as well as specific
* ViNCEd 16-bit color values and formatting. The generated text includes the
* URL to the image that was sampled.
*
* Additionally, if you click into the webpage page itself (and out of the console
* area after the colors have been selected, you will be given instruction to do so)
* within the alloted time, the generated contents will be stored in the clipboard
* of the device you're browsing with.
*
* @param {string|image|regexp} url either a URL pointing to an image or an image
* object that already exists on the page.
* @param {number} brightenBy for bright colors (ansi ranges 90-97 and 100-107),
* we take the supplied color and brighten it by the multiplier supplied. This
* defaults to 20% brighter with a value of 1.2
* @param {object} locations an object with string keys and array values where each
* array is a x,y value of where on the image it should look to find the pixel color
* associated with the named key
*/
function getANSIColors(url, brightenBy = 1.2, locations) {
console.log('Working... (first call might take a moment)\n')
// Used to increase the red, green and blue values by a supplied factor
function brighten(r,g,b,m=1.2) {
r = Math.ceil(Math.min(255, r*m))
g = Math.ceil(Math.min(255, g*m))
b = Math.ceil(Math.min(255, b*m))
return [r, g, b]
}
// Converts a supplied number into a hexadecimal string, ensuring prefixed
// zeroes as needed.
function numHex(s) {
var a = s.toString(16);
if ((a.length % 2) > 0) {
a = "0" + a;
}
return a;
}
// Declare some variables for use
// `done` is used to determine once the image has completed its loading
let done = Deferred()
// `result` is used to allow the rest of the code to run before the final output
// is ready, but allows us to return the promise in question immediately.
let result = Deferred()
// These are the pixel coordinates for each of the various colors. If nothing
// is provided in the locations variable, then a copy of `normalSize_642x357` is
// used instead.
let locs = locations && duplicate(locations) || duplicate(normalSize_642x357)
// Creates a new canvas object used to introspect into the image for pixel colors
let canvas = document.createElement('canvas');
// Creates a new image scoped to this page as far as CORS is concerned. If you
// provide a url on another domain, it needs to be CORS allowed or it will fail
let image = document.createElement('img');
// Check to see if we received a Regular Expression instead of url string or
// image object. If so, try to look on the pages images for a image source that
// matches and use that image object instead.
if (url instanceof RegExp) {
url = Array.from(document.images).filter(i => i.src.match(url))[0] || url
try {
// If we did get an image on the page, try moving to that theme's hash
// on the page. This has a high likelihood of failing but works well enough
document.location.hash = url.
parentElement.
parentElement.
previousElementSibling.
querySelector('a').href.replace(/.*?#(.*)/, '$1')
}
// Ignore any errors here
catch (ignore) { }
}
// Set the crossOrigin to anonymous to help with CORS related issues
image.crossOrigin = 'Anonymous'
// Set our onload handler to resolve the done deferred with the image; this
// will trigger the next bit of code to work.
image.onload = () => { done.resolve(image); }
// Once the `done.promise` is resolved, the following code proceeds with its
// results.
done.promise.then((image) => {
// ViNCEd has 17 properties that need to be generated; one for each of the
// 16 ansi colors (30-37, 90-97) and one for the CURSORCOLOR property.
let vinced = new Array(17)
// In order to show the color in the console output, I need to keep track
// of the actual rgb values as well. Those are stored here as other data
let ovinced = new Array(17)
// This serves as an index pointer to the various ViNCEd values. Since we
// get one from the image and the high color is generated from the low
// color, we need an index to reference where we are in the looping process
let i = 0;
// A generic CSS value used in the console output to restore foreground and
// background colors
let clear = 'background-color: auto; color: auto;'
// An array of strings that will become the final output
let output = []
// Red, green, blue and alpha values for the cursor color
let cursorColor = [0,0,0,0]
// Set the canvas width and height and then paint our image inside it
canvas.width = image.width
canvas.height = image.height
canvas.getContext('2d').drawImage(image, 0, 0, image.width, image.height);
// Write some output to the console
console.log(`// ${image.src}`)
console.log(`// Colors in second parens are brightened by ${brightenBy * 100.0}%\n`)
// Store similar data in our resulting strings list
output.push(`${image.src}`)
output.push(`Colors in second rgb call are brightened by ${brightenBy * 100.0}%`)
output.push('')
// Just a way to not pollute the function scope with more variables as we deal
// with the cursor color portion. Since the number of properties in the location
// object is tied to the numerical index of the ViNCEd values, we need to remove
// the cursor color location from the coordinates object when we are done with
// it.
if (true) {
// let and const are blocked scoped, so r,g,b, and a will not exist outside this
// `if (true)` block
let [r, g, b, a] = canvas.getContext('2d').getImageData(locs.cc[0],locs.cc[1],1,1).data
delete locs.cc
cursorColor = [r, g, b, a]
}
// Loop over the remaining locations
for (let prop in locs) {
// Grab the x and y coordinate
let [x, y] = locs[prop]
// Read the pixel RGBA values
let [r, g, b, a] = canvas.getContext('2d').getImageData(x,y,1,1).data
// Calculate the brighter color from the pixel value (90-97 values)
let [br, bg, bb] = brighten(r, g, b, brightenBy)
// Grab the cursor RGBA values
let [cr, cg, cb, ca] = cursorColor
// Create some CSS used to make the console output prettier and easier to verify at
// a glance. ccss is for the cursor color, ncss is for normal css and bcss is for
// bright css values.
let ccss = `background-color: rgb(${cr},${cg},${cb});`
let ncss = `background-color: rgb(${r},${g},${b});`
let bcss = `background-color: rgb(${br},${bg},${bb});`
// Since black is the first property (hmm, this seems to work fine but could result in
// a bug at some point since iterating over an object's properties is not guaranteed to
// be in order) we only want to insert the CURSORCOLOR once. So we do it when we are
// processing black.
if (prop.match(/^black$/i) && cursorColor.length) {
console.log('%c %c cursor color rgba(%s,%s,%s,%s)', ccss, clear, cr, cg, cb, ca)
vinced[i] = `CURSORCOLOR=NOLOAD,ANSI,0x${numHex(cr*257)},0x${numHex(cg*257)},0x${numHex(cb*257)}`
ovinced[i] = ccss
i++
}
// Show the property we are processing, the RGB and calculated RGB values and also generate
// the appropriate ViNCEd lines. Finally we do both console and output additions here since
// the console gets a nice pretty swatch of color to see and the output text does not.
console.log('%c %c %c %s A:%s rgb(%s,%s,%s) bright rgb(%s,%s,%s)', ncss, bcss, clear, prop, a, r, g, b, br, bg, bb)
output.push(`${prop} Alpha:${a} rgb(${r},${g},${b}) brighter rgb(${br},${bg},${bb})`)
vinced[i] = `COLOR=NOLOAD,ANSI,0x${numHex(r*257)},0x${numHex(g*257)},0x${numHex(b*257)}`
vinced[i + 8] = `COLOR=NOLOAD,ANSI,0x${numHex(br*257)},0x${numHex(bg*257)},0x${numHex(bb*257)}`
ovinced[i] = ncss
ovinced[i + 8] = bcss
i++
}
// Finish up writing the ViNCEd bits to console and append it to the output
console.log('\n// ViNCEd Color Scheme\n')
output.push('')
output.push('ViNCEd Color Scheme')
output.push('')
for (let i = 0; i < 16; i++) {
console.log('%s %c %c', vinced[i], ovinced[i], clear)
}
// Finally resolve the deferred with ViNCEd lines
result.resolve(output.concat(vinced))
})
// If url is now an <img> object, then snag its source and apply it to our image
// otherwise apply the supplied url string. Once the url is assigned, the loading
// will commence which is why it is at the bottom.
if (url instanceof Image) {
image.src = url.src;
}
else {
image.src = url;
}
// Keep a quick record of the call for use by `showAgain` should you choose to
// call it.
getANSIColors.processed[getANSIColors.processed.length] = [url, brightenBy]
// Return the promise immediately that will eventually contain the results of the
// pixel introspections.
return result.promise
}
/// A small history of input parameters for every sampled theme
getANSIColors.processed = []
/// A function that replays the same action as indicated by the historical reference
getANSIColors.showAgain = (index) => getANSIColors(...getANSIColors.processed[index])
/**
* A small helper function to give the user time to focus the github page itself
*
* @param {string} startText text to display at the begining before the countdown
* starts
* @param {number} seconds the number of whole seconds to wait before acting
* @param {function} action a function of work to do once the timer is up
* @param {string} doneText text to display once the action has completed
*/
function countDown(startText, seconds, action, doneText = 'Done!') {
let interval = null
console.log(startText)
interval = setInterval(() => {
if (seconds > 0) {
console.log(`${seconds}...`)
}
else {
try { action(); console.log(doneText) } catch (e) { console.log('Failed!!!') }
clearInterval(interval)
}
seconds--
}, 1000)
}
/**
* This function invokes `getANSIColors` but does so with a timer at the end, giving
* the user time to focus the main DOM so that the results can be copied to the clip
* board.
*
* @param {string|image} url a url string or image object
* @param {number} brightenBy a fraction, defaulting to 1.2, to determine how
* many times brighter (or dimmer) to make the high ansi color values
* @param {object} locations the coordinate positions; if none are supplied, the
* `normalSize_642x357` object is used instead.
*/
function getAndCopy(url, brightenBy = 1.2, locations = null) {
getANSIColors(url, brightenBy, locations).then(output => {
countDown(
'Copying contents to clipboard in 2 seconds, select the main window',
2,
() => { navigator.clipboard.writeText(output.join('\n')) },
'Finished copying to clipboard (hopefully)'
)
})
}
/**
* JavaScript is very pass by reference and this can cause problems. Given we know
* how the coordinate object stores its values, this function creates a copy of the
* values it has so we can destroy or modify the copy rather than the original.
*
* @param {object} object the coordinate object to duplicate
*/
function duplicate(object) {
let newObj = {}
for (prop in object) {
newObj[prop] = Array.from(object[prop])
}
return newObj
}
/**
* Creates an inverted Promise ala jQuery. While I do not use jQuery any longer,
* the deferred functionality they introduced back when promises were new to
* JavaScript have always been useful.
*
* @returns {object} an object with a reference to the promise, resolve and reject
* functions
*/
function Deferred() {
let object = {
promise: null,
resolve: null,
reject: null,
}
object.promise = new Promise((resolve, reject) => {
object.resolve = resolve
object.reject = reject
})
return object
}
@nyteshade

This comment has been minimized.

Copy link
Owner Author

@nyteshade nyteshade commented Jan 5, 2021

image

@nyteshade

This comment has been minimized.

Copy link
Owner Author

@nyteshade nyteshade commented Jan 5, 2021

https://github.com/mbadolato/iTerm2-Color-Schemes/raw/master/screenshots/elemental.png
Colors in second rgb call are brightened by 120%

black Alpha:255 rgb(77,76,64) brighter rgb(93,92,77)
red Alpha:255 rgb(168,58,26) brighter rgb(202,70,32)
green Alpha:255 rgb(89,166,88) brighter rgb(107,200,106)
yellow Alpha:255 rgb(145,130,33) brighter rgb(174,156,40)
blue Alpha:255 rgb(92,145,143) brighter rgb(111,174,172)
magenta Alpha:255 rgb(144,97,64) brighter rgb(173,117,77)
cyan Alpha:255 rgb(73,143,108) brighter rgb(88,172,130)
white Alpha:255 rgb(147,140,135) brighter rgb(177,168,162)

ViNCEd Color Scheme

CURSORCOLOR=NOLOAD,ANSI,0x2d2d,0x2c2c,0x2626
COLOR=NOLOAD,ANSI,0x4d4d,0x4c4c,0x4040
COLOR=NOLOAD,ANSI,0xa8a8,0x3a3a,0x1a1a
COLOR=NOLOAD,ANSI,0x5959,0xa6a6,0x5858
COLOR=NOLOAD,ANSI,0x9191,0x8282,0x2121
COLOR=NOLOAD,ANSI,0x5c5c,0x9191,0x8f8f
COLOR=NOLOAD,ANSI,0x9090,0x6161,0x4040
COLOR=NOLOAD,ANSI,0x4949,0x8f8f,0x6c6c
COLOR=NOLOAD,ANSI,0x9393,0x8c8c,0x8787
COLOR=NOLOAD,ANSI,0x5d5d,0x5c5c,0x4d4d
COLOR=NOLOAD,ANSI,0xcaca,0x4646,0x2020
COLOR=NOLOAD,ANSI,0x6b6b,0xc8c8,0x6a6a
COLOR=NOLOAD,ANSI,0xaeae,0x9c9c,0x2828
COLOR=NOLOAD,ANSI,0x6f6f,0xaeae,0xacac
COLOR=NOLOAD,ANSI,0xadad,0x7575,0x4d4d
COLOR=NOLOAD,ANSI,0x5858,0xacac,0x8282
COLOR=NOLOAD,ANSI,0xb1b1,0xa8a8,0xa2a2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment