Related
people of the internet!
I'm making a program that needs to try and find the closest RGB match of lots of pixels, using Jimp.
Say if you had a color that was slightly purple, I would want to change it to the purple that I have in my table, here:
var colors = [
[0,0,255,0],
[1,255,165,0],
[2,0,0,255],
[3,255,192,203],
[4,165,42,42],
[5,255,255,255],
[6,0,0,0],
[7,230,230,350],
[8,255,255,0],
[9,0,0,0],
]
(the first number in one of these arrays can be ignored, the rest are just R, G, and B)
So if I had a red like (255,7,1) I would like to match that to the red in my table, being (255,0,0)
I have tried something, but rather it was very dumb and didn't work, so I will spare you the details.
Can anybody help me? Thanks!
RGB is a 3-dimensional space. Consider a color as a point in 3d space, you should find most closest distance between two points by using 3d pythagoras.
const colors = [
[0,0,255,0],
[1,255,165,0],
[2,0,0,255],
[3,255,192,203],
[4,165,42,42],
[5,255,255,255],
[6,0,0,0],
[7,230,230,350],
[8,255,255,0],
[9,0,0,0],
]
function get_closest_color(colors, [r2, g2, b2]) {
const [[closest_color_id]] = (
colors
.map(([id, r1,g1,b1]) => (
[id, Math.sqrt((r2-r1)**2 + (g2-g1)**2 + (b2-b1)**2)]
))
.sort(([, d1], [, d2]) => d1 - d2)
);
return colors.find(([id]) => id == closest_color_id);
}
const closest_color = get_closest_color(colors, [230, 200,0]);
console.log(closest_color);
If I undestand your question correctly you can do something like this
const colors = [
[0,0,255,0],
[1,255,165,0],
[2,0,0,255],
[3,255,192,203],
[4,165,42,42],
[5,255,255,255],
[6,0,0,0],
[7,230,230,350],
[8,255,255,0],
[9,0,0,0],
]
const findClosestColor = (color, colors) =>
colors.reduce((res, c) => {
const distance = [1,2,3].reduce((sum, i) => sum + Math.abs(c[i] - color[i]), 0)
if(distance > res.distance){
return res;
}
return {
closest: c,
distance
}
}, {closest: null, distance: 9999} ).closest
console.log(findClosestColor([0, 255,7,1], [[1,0,0,200], [2,255,0,0], [3, 128,128,128]]))
Use the function(s) from here to find the color with the smallest E distance
Color difference/similarity% between two values with JS
I have a Light and a Dark theme and all the Colors are set in a Master theme. Currently, if I want to change the theme, I have to manually import the other theme for it to work. I wanted to create an add-on that will allow me to do this with a click of a button (much like in Powerpoint)
I'm more familiar with using Scripts on Google Sheets so I don't know if it's possible to set ACCENT colors in Google Slides. I know how to change fonts, colors of textbox etc but not this.
Is there a way?
Thank you
The Slides API features the property ThemeColorType
To implement a color scheme, you can perform a call to presentations.batchUpdate with a UpdatePagePropertiesRequest.
This lets you define / modify the theme color programamtically and features the following enums (including accents!):
DARK1 Represents the first dark color.
LIGHT1 Represents the first light color.
DARK2 Represents the second dark color.
LIGHT2 Represents the second light color.
ACCENT1 Represents the first accent color.
ACCENT2 Represents the second accent color.
ACCENT3 Represents the third accent color.
ACCENT4 Represents the fourth accent color.
ACCENT5 Represents the fifth accent color.
ACCENT6 Represents the sixth accent color.
HYPERLINK Represents the color to use for hyperlinks.
FOLLOWED_HYPERLINK Represents the color to use for visited hyperlinks.
TEXT1 Represents the first text color.
BACKGROUND1 Represents the first background color.
TEXT2 Represents the second text color.
BACKGROUND2
See also the resource Color Scheme to udnerstand the ensting of the JSON object.
function test(){
setThemeColors('#000000,
#ffffff,#424242,#f2f2f2,#41c4dc,
#34d058,#f5f442,#f55e3b,#9c51b6,
#c9c9c9,#646464,#a3a3a3')
}
function setThemeColors(hexcolors) {
// Convert a hex color string to an array of red, green, and blue values.
function hex2concrete(hexColor) {
var red = parseInt(hexColor.slice(1, 3), 16) / 255;
var green = parseInt(hexColor.slice(3, 5), 16) / 255;
var blue = parseInt(hexColor.slice(5, 7), 16) / 255;
return [red, green, blue];
}
// Split the hexcolors string into an array of hex color strings.
var hexColors = hexcolors.split(',');
// Define the COLOR_SCHEME object.
var COLOR_SCHEME = {
colors: []
};
// Define the theme color types in the correct order.
var colorTypes = 'DARK1,LIGHT1,DARK2,LIGHT2,ACCENT1,ACCENT2,ACCENT3,ACCENT4,ACCENT5,ACCENT6,HYPERLINK,FOLLOWED_HYPERLINK'.split(',');
// Loop over the hex colors and theme color types.
for (var i = 0; i < 12; i++) {
// Skip the iteration if the hex color string is empty.
if (hexColors[i] === '') continue;
// Convert the hex color to an array of red, green, and blue values.
var color = hex2concrete(hexColors[i]);
// Add a color object to the COLOR_SCHEME object.
COLOR_SCHEME.colors.push({
type: colorTypes[i],
color: {
red: color[0],
green: color[1],
blue: color[2]
}
});
}
// Define the requests to update the page properties.
var requests = [{
updatePageProperties: {
objectId: SlidesApp.getActivePresentation().getMasters()[0].getObjectId(),
pageProperties: {
colorScheme: COLOR_SCHEME
},
fields: 'colorScheme.colors'
}
}];
// Execute the requests.
Slides.Presentations.batchUpdate({
requests: requests
}, SlidesApp.getActivePresentation().getId())
}
Or, maybe Simpler
function setThemeColors(hexcolors, masterno){
/* Test setThemeColors(["#000000","#ffffff","#424242","#f2f2f2",
"#41c4dc","#34d058","#f5f442","#f55e3b",
"#9c51b6","#c9c9c9","#646464","#a3a3a3"],
1) */
var clrSc = SlidesApp.getActivePresentation()
.getMasters()[masterno-1].getColorScheme();
clrSc.setConcreteColor(SlidesApp.ThemeColorType.DARK1,hexcolors[0]).
setConcreteColor(SlidesApp.ThemeColorType.LIGHT1,hexcolors[1]).
setConcreteColor(SlidesApp.ThemeColorType.DARK2,hexcolors[2]).
setConcreteColor(SlidesApp.ThemeColorType.LIGHT2,hexcolors[3]).
setConcreteColor(SlidesApp.ThemeColorType.ACCENT1,hexcolors[4]).
setConcreteColor(SlidesApp.ThemeColorType.ACCENT2,hexcolors[5]).
setConcreteColor(SlidesApp.ThemeColorType.ACCENT3,hexcolors[6]).
setConcreteColor(SlidesApp.ThemeColorType.ACCENT4,hexcolors[7]).
setConcreteColor(SlidesApp.ThemeColorType.ACCENT5,hexcolors[8]).
setConcreteColor(SlidesApp.ThemeColorType.ACCENT6,hexcolors[9]).
setConcreteColor(SlidesApp.ThemeColorType.HYPERLINK,hexcolors[10]).
setConcreteColor(SlidesApp.ThemeColorType.FOLLOWED_HYPERLINK,hexcolors[11])
}
On the web is lot of conversion tables between foot height and shoe size. Like here http://www.shoesize.com/men/sizechart/ I would like to ask if there is a math function between those two params. Fuction should looks like:
function getShoeSize(height in cm){
...type magic here...
return shoeSize; //size in EU format
}
I don't think there is an easy mathematical way of doing it (I am sure it is possible, but it would probably be unreadable for the average developer).
What I suggest you can do, and which is easy to build. Is that you make an array of the shoe sizes (example of all sizes can be found on http://www.shoesizingcharts.com/ and many more sites). And use that array to convert cm to euro size.
The array could look something like:
var shoeSizeMap = [
{ cm: 20.8, euroSize: '35' },
{ cm: 21.3, euroSize: '35' },
{ cm: 21.6, euroSize: '35/36' },
... etc
]
And use a function to find it like:
function findEuroSizeByCm(cm) {
var result = shoeSizeMap.find(function(shoeSize) {
return shoeSize.cm == cm;
});
return result ? result.euroSize : 'unknown size';
}
findEuroSizeByCm(20.8); // returns 35
ps. be aware that I typed in the example the numbers as 'floats' you might want to compare as strings if your data-source is supplying strings as well (just change cm: 20.8 to cm: '20.8').
It is clear that dependence is not very smooth:
So you can find closest cm size in array with binary search and check if neighbour size is closer (for example, for 26.5 cm binary search can find tabular value 26 as lesser value, but 26.7 is closer, so 42.5 size would fit better). Or use binary search implementation that finds upper value.
Edit: Due to very small array it is simpler to use linear search. Pseudocode:
idx = 0
while (CmSize[idx] < Foot_len) && (idx < CmSize.Length)
idx++
return EUSize[idx]
I have a simple pdf file, containing the words "Hello world", each in a different colour.
I'm loading the PDF, like this:
PDFJS.getDocument('test.pdf').then( onPDF );
function onPDF( pdf )
{
pdf.getPage( 1 ).then( onPage );
}
function onPage( page )
{
page.getTextContent().then( onText );
}
function onText( text )
{
console.log( JSON.stringify( text ) );
}
And I get a JSON output like this:
{
"items" : [{
"str" : "Hello ",
"dir" : "ltr",
"width" : 29.592,
"height" : 12,
"transform" : [12, 0, 0, 12, 56.8, 774.1],
"fontName" : "g_font_1"
}, {
"str" : "world",
"dir" : "ltr",
"width" : 27.983999999999998,
"height" : 12,
"transform" : [12, 0, 0, 12, 86.5, 774.1],
"fontName" : "g_font_1"
}
],
"styles" : {
"g_font_1" : {
"fontFamily" : "serif",
"ascent" : 0.891,
"descent" : 0.216
}
}
}
However, I've not been able to find a way to determine the colour of each word. When I render it, it renders properly, so I know the information is in there somewhere. Is there somewhere I can access this?
As Respawned alluded to, there is no easy answer that will work in all cases. That being said, here are two approaches which seem to work fairly well. Both having upsides and downsides.
Approach 1
Internally, the getTextContent method uses whats called an EvaluatorPreprocessor to parse the PDF operators, and maintain the graphic state. So what we can do is, implement a custom EvaluatorPreprocessor, overwrite the preprocessCommand method, and use it to add the current text color to the graphic state. Once this is in place, anytime a new text chunk is created, we can add a color attribute, and set it to the current color state.
The downsides to this approach are:
Requires modifying the PDFJS source code. It also depends heavily on
the current implementation of PDFJS, and could break if this is
changed.
It will fail in cases where the text is used as a path to be filled with an image. In some PDF creators (such as Photoshop), the way it creates colored text is, it first creates a clipping path from all the given text characters, and then paints a solid image over the path. So the only way to deduce the fill-color is by reading the pixel values from the image, which would require painting it to a canvas. Even hooking into paintChar wont be of much help here, since the fill color will only emerge at a later time.
The upside is, its fairly robust and works irrespective of the page background. It also does not require rendering anything to canvas, so it can be done entirely in the background thread.
Code
All the modifications are made in the core/evaluator.js file.
First you must define the custom evaluator, after the EvaluatorPreprocessor definition.
var CustomEvaluatorPreprocessor = (function() {
function CustomEvaluatorPreprocessor(stream, xref, stateManager, resources) {
EvaluatorPreprocessor.call(this, stream, xref, stateManager);
this.resources = resources;
this.xref = xref;
// set initial color state
var state = this.stateManager.state;
state.textRenderingMode = TextRenderingMode.FILL;
state.fillColorSpace = ColorSpace.singletons.gray;
state.fillColor = [0,0,0];
}
CustomEvaluatorPreprocessor.prototype = Object.create(EvaluatorPreprocessor.prototype);
CustomEvaluatorPreprocessor.prototype.preprocessCommand = function(fn, args) {
EvaluatorPreprocessor.prototype.preprocessCommand.call(this, fn, args);
var state = this.stateManager.state;
switch(fn) {
case OPS.setFillColorSpace:
state.fillColorSpace = ColorSpace.parse(args[0], this.xref, this.resources);
break;
case OPS.setFillColor:
var cs = state.fillColorSpace;
state.fillColor = cs.getRgb(args, 0);
break;
case OPS.setFillGray:
state.fillColorSpace = ColorSpace.singletons.gray;
state.fillColor = ColorSpace.singletons.gray.getRgb(args, 0);
break;
case OPS.setFillCMYKColor:
state.fillColorSpace = ColorSpace.singletons.cmyk;
state.fillColor = ColorSpace.singletons.cmyk.getRgb(args, 0);
break;
case OPS.setFillRGBColor:
state.fillColorSpace = ColorSpace.singletons.rgb;
state.fillColor = ColorSpace.singletons.rgb.getRgb(args, 0);
break;
}
};
return CustomEvaluatorPreprocessor;
})();
Next, you need to modify the getTextContent method to use the new evaluator:
var preprocessor = new CustomEvaluatorPreprocessor(stream, xref, stateManager, resources);
And lastly, in the newTextChunk method, add a color attribute:
color: stateManager.state.fillColor
Approach 2
Another approach would be to extract the text bounding boxes via getTextContent, render the page, and for each text, get the pixel values which reside within its bounds, and take that to be the fill color.
The downsides to this approach are:
The computed text bounding boxes are not always correct, and in some cases may even be off completely (eg: rotated text). If the bounding box does not cover at least partially the actual text on canvas, then this method will fail. We can recover from complete failures, by checking that the text pixels have a color variance greater than a threshold. The rationale being, if bounding box is completely background, it will have little variance, in which case we can fallback to a default text color (or maybe even the color of k nearest-neighbors).
The method assumes the text is darker than the background. Otherwise, the background could be mistaken as the fill color. This wont be a problem is most cases, as most docs have white backgrounds.
The upside is, its simple, and does not require messing with the PDFJS source-code. Also, it will work in cases where the text is used as a clipping path, and filled with an image. Though this can become hazy when you have complex image fills, in which case, the choice of text color becomes ambiguous.
Demo
http://jsfiddle.net/x2rajt5g/
Sample PDF's to test:
https://www.dropbox.com/s/0t5vtu6qqsdm1d4/color-test.pdf?dl=1
https://www.dropbox.com/s/cq0067u80o79o7x/testTextColour.pdf?dl=1
Code
function parseColors(canvasImgData, texts) {
var data = canvasImgData.data,
width = canvasImgData.width,
height = canvasImgData.height,
defaultColor = [0, 0, 0],
minVariance = 20;
texts.forEach(function (t) {
var left = Math.floor(t.transform[4]),
w = Math.round(t.width),
h = Math.round(t.height),
bottom = Math.round(height - t.transform[5]),
top = bottom - h,
start = (left + (top * width)) * 4,
color = [],
best = Infinity,
stat = new ImageStats();
for (var i, v, row = 0; row < h; row++) {
i = start + (row * width * 4);
for (var col = 0; col < w; col++) {
if ((v = data[i] + data[i + 1] + data[i + 2]) < best) { // the darker the "better"
best = v;
color[0] = data[i];
color[1] = data[i + 1];
color[2] = data[i + 2];
}
stat.addPixel(data[i], data[i+1], data[i+2]);
i += 4;
}
}
var stdDev = stat.getStdDev();
t.color = stdDev < minVariance ? defaultColor : color;
});
}
function ImageStats() {
this.pixelCount = 0;
this.pixels = [];
this.rgb = [];
this.mean = 0;
this.stdDev = 0;
}
ImageStats.prototype = {
addPixel: function (r, g, b) {
if (!this.rgb.length) {
this.rgb[0] = r;
this.rgb[1] = g;
this.rgb[2] = b;
} else {
this.rgb[0] += r;
this.rgb[1] += g;
this.rgb[2] += b;
}
this.pixelCount++;
this.pixels.push([r,g,b]);
},
getStdDev: function() {
var mean = [
this.rgb[0] / this.pixelCount,
this.rgb[1] / this.pixelCount,
this.rgb[2] / this.pixelCount
];
var diff = [0,0,0];
this.pixels.forEach(function(p) {
diff[0] += Math.pow(mean[0] - p[0], 2);
diff[1] += Math.pow(mean[1] - p[1], 2);
diff[2] += Math.pow(mean[2] - p[2], 2);
});
diff[0] = Math.sqrt(diff[0] / this.pixelCount);
diff[1] = Math.sqrt(diff[1] / this.pixelCount);
diff[2] = Math.sqrt(diff[2] / this.pixelCount);
return diff[0] + diff[1] + diff[2];
}
};
This question is actually extremely hard if you want to do it to perfection... or it can be relatively easy if you can live with solutions that work only some of the time.
First of all, realize that getTextContent is intended for searchable text extraction and that's all it's intended to do.
It's been suggested in the comments above that you use page.getOperatorList(), but that's basically re-implementing the whole PDF drawing model in your code... which is basically silly because the largest chunk of PDFJS does exactly that... except not for the purpose of text extraction but for the purpose of rendering to canvas. So what you want to do is to hack canvas.js so that instead of just setting its internal knobs it also does some callbacks to your code. Alas, if you go this way, you won't be able to use stock PDFJS, and I rather doubt that your goal of color extraction will be seen as very useful for PDFJS' main purpose, so your changes are likely not going to get accepted upstream, so you'll likely have to maintain your own fork of PDFJS.
After this dire warning, what you'd need to minimally change are the functions where PDFJS has parsed the PDF color operators and sets its own canvas painting color. That happens around line 1566 (of canvas.js) in function setFillColorN. You'll also need to hook the text render... which is rather a character renderer at canvas.js level, namely CanvasGraphics_paintChar around line 1270. With these two hooked, you'll get a stream of callbacks for color changes interspersed between character drawing sequences. So you can reconstruct the color of character sequences reasonably easy from this.. in the simple color cases.
And now I'm getting to the really ugly part: the fact that PDF has an extremely complex color model. First there are two colors for drawing anything, including text: a fill color and stroke (outline) color. So far not too scary, but the color is an index in a ColorSpace... of which there are several, RGB being only one possibility. Then there's also alpha and compositing modes, so the layers (of various alphas) can result in a different final color depending on the compositing mode. And the PDFJS has not a single place where it accumulates color from layers.. it simply [over]paints them as they come. So if you only extract the fill color changes and ignore alpha, compositing etc.. it will work but not for complex documents.
Hope this helps.
There's no need to patch pdfjs, the transform property gives the x and y, so you can go through the operator list and find the setFillColor op that precedes the text op at that point.
I'm wondering what the best way to approach this problem is...
I have an array of user defined colors that get pushed onto a stack as rgb values. I want to find a second color for each user-defined color. These second colors must be completely unique from the user-defined array, but can be completely random and do not need to have any visual relationship with any of the user colors.
Would generating random rgb values and testing it against the stack be fast, since it is highly unlikely that random numbers would find the same color on the first try. Or would simply starting at 255,254,253 and substracting 1 from each value, then testing it against the stack be better, also very unlikely and less operations. Or any other idea?
Here's some code to start off:
var colors = [];
function getRandUniqColor()
{
var r = getRGBString(),
g = getRGBString(),
b = getRGBString(),
rgb = r + g + b;
if ( colors.indexOf(rgb) == -1 )
{
colors.push(rgb);
return {
r: r,
g: g,
b: b
}
}
else
{
return getRandUniqColor()
}
}
function getRGBString()
{
var num = Math.floor( Math.random() * 255 ).toString();
while (num.length < 3)
{
num = '0' + num;
}
return num;
}
Just call getRandUniqColor(), and you'll get an object literal of RGB values.
Note: If there'll be a lot of colors involved, you should not use a recursive function.
...and here's the fiddle: http://jsfiddle.net/wV6Mw/
I would suggest generating random RGB values (combine three random numbers) and then create a function that measures the color difference between your generated random color and the ones in your data structure. If your generated color is too close to one you already have, generate a new one. You will have to decide how "different" you want the random color to be from what you already have, but a difference of only a few points in R, G an B won't be discernibly different so you should be looking for some minimum separation in at least one channel.
Searching for "calculate rgb color difference" gives you a bunch of references on how to calculate a meaningful difference between two color values. This link from that search has a specific equation and here's a previous question on SO about color differences.
You can also generate complementary colors from what you already have if you just want different colors and don't need/want truly random colors.
You could generate a random color like this in either data form or hex string form:
// generate r,g,b data structure like {r: 255, g: 30, b: 09}
function generateRandomColorData() {
function randomColorVal() {
return(Math.floor(Math.random() * 256));
}
return {r:randomColorVal(), g: randomeColorVal(), b: randomColorVal()};
}
// generate hex string color value like FF4409
function generateRandomColorHex() {
function randomColorVal() {
var val = Math.floor(Math.random() * 256).toString(16);
if (val.length < 2) {
val = "0" + val;
}
return(val);
}
return(randomColorVal() + randomColorVal() + randomColorVal());
}
As others have said, the easiest way would probably be to just create random colors and check for collisions.
If you are filling up the color space a lot (users selecting millions of colors) then another way would be to create a color cube of 256^3 bits, and let "1" represent the used colors. Then you could start from position 0 and use the first "0" as your generated color, the next "0" as the second and so on. The data structure would be just over 2 MB of ram.
But random is probably the way to go.