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 | |
} |
This comment has been minimized.
This comment has been minimized.
|
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.