Convert excel column alphabet (e.g. AA) to number (e.g., 25) - javascript

In my grid the column headers are named A,B,C...,AA,AB,AC,...etc like an excel spreadsheet. How can I convert the string to number like: A => 1, B => 2, AA => 27

Try:
var foo = function(val) {
var base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', i, j, result = 0;
for (i = 0, j = val.length - 1; i < val.length; i += 1, j -= 1) {
result += Math.pow(base.length, j) * (base.indexOf(val[i]) + 1);
}
return result;
};
console.log(['A', 'AA', 'AB', 'ZZ'].map(foo)); // [1, 27, 28, 702]

solution 1: best performance and browser compatibility
// convert A to 1, Z to 26, AA to 27
function lettersToNumber(letters){
var chrs = ' ABCDEFGHIJKLMNOPQRSTUVWXYZ', mode = chrs.length - 1, number = 0;
for(var p = 0; p < letters.length; p++){
number = number * mode + chrs.indexOf(letters[p]);
}
return number;
}
solution 2: best performance and compatibility and shorter code (Recommended)
// convert A to 1, Z to 26, AA to 27
function lettersToNumber(letters){
for(var p = 0, n = 0; p < letters.length; p++){
n = letters[p].charCodeAt() - 64 + n * 26;
}
return n;
}
solution 3: short code (es6 arrow function)
// convert A to 1, Z to 26, AA to 27
function lettersToNumber(letters){
return letters.split('').reduce((r, a) => r * 26 + parseInt(a, 36) - 9, 0);
}
test:
['A', 'Z', 'AA', 'AB', 'ZZ','BKTXHSOGHKKE'].map(lettersToNumber);
// [1, 26, 27, 28, 702, 9007199254740991]
lettersToNumber('AAA'); //703

Here's a quick example of the code you should implement.
This will work with any given number of letters.
function letterToNumbers(string) {
string = string.toUpperCase();
var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', sum = 0, i;
for (i = 0; i < string.length; i++) {
sum += Math.pow(letters.length, i) * (letters.indexOf(string.substr(((i + 1) * -1), 1)) + 1);
}
return sum;
}

i just wrote a junk yard f##$ snippet... need to be optimized.. :)
charToNum = function(alpha) {
var index = 0
for(var i = 0, j = 1; i < j; i++, j++) {
if(alpha == numToChar(i)) {
index = i;
j = i;
}
}
console.log(index);
}
numToChar = function(number) {
var numeric = (number - 1) % 26;
var letter = chr(65 + numeric);
var number2 = parseInt((number - 1) / 26);
if (number2 > 0) {
return numToChar(number2) + letter;
} else {
return letter;
}
}
chr = function (codePt) {
if (codePt > 0xFFFF) {
codePt -= 0x10000;
return String.fromCharCode(0xD800 + (codePt >> 10), 0xDC00 + (codePt & 0x3FF));
}
return String.fromCharCode(codePt);
}
charToNum('A') => returns 1 and charToNum('AA') => returns 27;

// Given Column to Number
function colToNumber(str) {
let num = 0
let i = 0
while (i < str.length) {
num = str[i].charCodeAt(0) - 64 + num * 26;
i++;
}
return num;
}
//Given Number to Column name
function numberToCol(num) {
let str = '', q, r;
while (num > 0) {
q = (num-1) / 26;
r = (num-1) % 26
num = Math.floor(q)
str = String.fromCharCode(65 + r) + str;
}
return str;
}

I rewrote Yoshi's answer in a more verbose form that explains better how it works and is easier to port to other languages:
var foo = function(val) {
var base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var baseNumber = base.length;
var runningTotal = 0;
var characterIndex = 0;
var indexExponent = val.length - 1;
while( characterIndex < val.length ){
var digit = val[characterIndex];
var digitValue = base.indexOf(digit) + 1;
runningTotal += Math.pow(baseNumber, indexExponent) * digitValue;
characterIndex += 1
indexExponent -= 1
}
return runningTotal;
};
console.log(['A', 'AA', 'AB', 'ZZ'].map(foo)); // [1, 27, 28, 702]

Public Function ColLet2Num(Letras As String)
'RALONSO MAYO 2017
'A-> 1
'OQ ->407
'XFD->16384
Dim UnChar As String
Dim NAsc As Long
Dim F As Long
Dim Acum As Long
Dim Indice As Long
Letras = UCase(Letras)
Acum = 0
Indice = 0
For F = Len(Letras) - 1 To 0 Step -1
UnChar = Mid(Letras, F + 1, 1)
NAsc = Asc(UnChar) - 64
Acum = Acum + (NAsc * (26 ^ Indice))
Indice = Indice + 1
Next
If Acum > 16384 Then
MsgBox "La celda máxima es la XFD->16384", vbCritical
End If
ColLet2Num = Acum
End Function

const getColumnName = (columnNumber) => {
let columnName = "";
const alphabets = "abcdefghijklmnopqrstuvwxyz".toUpperCase();
while (columnNumber > 0) {
const rem = columnNumber % 26;
if (rem === 0) {
columnName += "Z";
columnNumber = columnNumber / 26 - 1;
} else {
columnName += alphabets[rem - 1];
columnNumber = Math.floor(columnNumber / 26);
}
}
return columnName.split("").reverse().join("");
};
console.log(getColumnName(27));

A good readability and performance example:
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
// create dict to O(1) access to letter number
const dict = Object.fromEntries(letters.split('').map((it, index) => [it, index + 1]));
function toNumber(col) {
return col
.toUpperCase()
.split('')
.reduce((acc, letter, index) => acc + Math.pow(letters.length, col.length - (index + 1)) * dict[letter], 0);
}

Highly inspired by the different solutions put forth on this page
//fixed the one taken from here
function colToNumber(str: string): number {
var num = 0
for (var i = 0; i < str.length; i++) {
const current_letter = str.charCodeAt(i) - 64
const current_char = str[i]
if (i == str.length - 1) {
num += current_letter
} else {
var current = current_letter * Math.pow(26, str.length - i - 1)
num += current
}
}
return num;
}
//Given Number to Column name (taken from here)
function numberToCol(num: number) {
var str = '', q: number, r: number;
while (num > 0) {
q = (num - 1) / 26;
r = (num - 1) % 26
num = Math.floor(q)
str = String.fromCharCode(65 + r) + str;
}
return str;
}
function test_both() {
const dic = new Map<number, string>()
dic.set(1,"A")
dic.set(10,"J")
dic.set(13,"M")
dic.set(33,"AG")
dic.set(63,"BK")
dic.set(66,"BN")
dic.set(206,"GX")
dic.set(502,"SH")
dic.set(1003,"ALO")
dic.set(100,"CV")
dic.set(10111,"NXW")
dic.set(10001,"NTQ")
dic.set(9002,"MHF")
dic.set(5002,"GJJ")
dic.set(3002,"DKL")
dic.set(16384,"XFD")
for (var key of dic.keys()) {
const expected_a1 = dic.get(key) || ""
//console.log(`${ key }, ${ expected_a1 } `)
var actual = numberToCol(key)
var actual_num = colToNumber(expected_a1)
if (actual.localeCompare(expected_a1) != 0) {
console.error(`key = ${key} == expected=${expected_a1} actual = ${actual} `)
}
if (actual_num != key) {
console.error(`expected = ${expected_a1} key = ${key} == actual = ${actual_num} `)
}
}
}

Related

Recreate Number.toString(nn) in pure javascript

I'm trying convert a number into string using nn base. Basically, trying re-create Number.toString(nn)
Here is the code I have so far which produces incorrect result:
const baseData = "0123456789abcdef";
for(let i = 0; i < 257; i += 8)
{
console.log(i, "expected:", i.toString(16), "| actual:", toBase(i, 16));
}
function toBase(n, radix)
{
let result = "";
const count = ~~(n / radix);
for(let i = 0; i < count; i++)
{
result += baseData[~~(i % radix)];
}
result += baseData[~~(n % radix)];
return result;
}
.as-console-wrapper{top:0;max-height:unset!important;overflow:auto!important;}
There must be some kind of formula for this...
Any suggestions?
const baseData = "0123456789abcdef";
for(let i = 0; i < 257; i += 8)
{
console.log(i, "expected:", i.toString(16), "| actual:", toBase(i, 16));
}
function toBase(n, radix)
{
let result = "";
while(n>0){
result = baseData[n % radix]+result;
n=(n - n % radix)/radix;
}
return result;
}
const baseData = "0123456789abcdef";
for (let i = 0; i < 257; i += 8) {
console.log(i, "expected:", i.toString(16), "| actual:", toBase(i, 16));
}
function toBase(n, radix) {
if (n === 0) return 0
const chars = '0123456789abcdef'
const result = []
while (n > 0) {
const mod = n % radix
result.unshift(chars[mod])
n = Math.floor(n / radix)
}
return result.join('')
}
.as-console-wrapper {
top: 0;
max-height: unset!important;
overflow: auto!important;
}
Just in case someone is looking for the encoding/decoding solution:
const numBase = (() =>
{
//base65
let baseData = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY=+/-";
return {
get base() {return baseData;},
set base(data) {baseData = data;},
to: (num, radix = baseData.length) =>
{
let result = "";
for(; num > 0; num = ~~(num / radix))
result = baseData[num % radix] + result;
return result || "0";
},
from: (text, redix = baseData.length) =>
{
let result = 0;
for(let i = text.length, x = 1; --i >= 0; x *= redix)
result += baseData.indexOf(text[i]) * x;
return result;
}
};
})();
const redix = 65;
for(let i = 0; i < 4097; i += 82)
{
console.log(i, "⟶", numBase.to(i, redix), "⟶", numBase.from(numBase.to(i, redix), redix));
}
.as-console-wrapper{top:0;max-height:unset!important;overflow:auto!important;}

How to find the first prime number in range on JS

I have this code that finds all the prime numbers between 2 values, but how do i change it to find only the first one?
e.g. 33 and 147 = 37
let a, b, i, j, primo;
a = window.prompt("Numero minimo: ");
b = window.prompt("Numero maximo: ");
console.log("Numeros primos entre " + a + " e " + b << " é: ");
for (i = a; i <= b; i++) {
if (i == 1 || i == 0)
continue;
primo = 1;
for (j = 2; j < i; ++j) {
if (i % j == 0) {
primo = 0;
break;
}
}
if (primo == 1)
document.write(i," ");
}
You can extend the final if statement with this:
if (primo == 1) {
document.write(i, " ");
break;
}
break allows you to exit the for loop. You can learn more here: https://www.w3schools.com/js/js_break.asp
let a, b, i, j, primo;
a = window.prompt("Numero minimo: ");
b = window.prompt("Numero maximo: ");
console.log("Numeros primos entre " + a + " e " + b << " é: ");
for (i = a; i <= b; i++) {
if (i == 1 || i == 0)
continue;
primo = 1;
for (j = 2; j < i; ++j) {
if (i % j == 0) {
primo = 0;
break;
}
}
if (primo == 1) {
document.write(i, " ");
break;
}
}
That would be the algorithm for find the first prime in a array.
function makeArr(start, end) {
end++
return Array.from({length: (end-start)}, (v, k) => k+start)
}
isPrime = num => {
for(let i = 2, s = Math.sqrt(num); i <= s; i++)
if(num % i === 0) return false;
return num > 1;
}
nums = makeArr(33,147);
r = nums.filter(n => isPrime(n))
console.log(r[0])
Wrapped in a function example
function getFirstPrimeNumber(s, e) {
const start = parseInt(s);
let end = parseInt(e);
end++;
const _a= Array.from({length: (end-start)}, (v, k) => k+start)
const isPrime = num => {
for(let i = 2, s = Math.sqrt(num); i <= s; i++)
if(num % i === 0) return false;
return num > 1;
}
const primes = _a.filter(n => isPrime(n));
return primes[0];
}
const a = window.prompt("Numero minimo: ");
const b = window.prompt("Numero maximo: ");
const r = getFirstPrimeNumber(a,b)
console.log('result', r);

Sum of all odd fibonacci numbers upto n

I am stuck in summing up to number 10. Can't get it right.
What i am getting is the total sum from 10 digits (after filtering odd only 7). Where should i make <= num ?
function sumFibs(num) {
var fib = [1, 1];
for (var i = 2; i < num; i++) {
var next = fib[i - 1] + fib[i - 2];
var fibi = fib.push(next);
}
return fib.filter(function(a) {
return (a % 2 != 0);
})
.reduce(function(a, z) {
return a + z;
})
}
console.log(sumFibs(10));
Expected output 10, but getting 99
Add a < num to your filter callback test, so you get a % 2 && a < num
function sumFibs(num) {
var fib = [0, 1];
for (var i = 2; i < num; i++) {
var next = fib[i - 1] + fib[i - 2];
var fibi = fib.push(next);
}
return fib.filter(function (a) {
return a % 2 && a < num;
}).reduce(function (a, z) {
return a + z;
}, 0);
}
console.log(sumFibs(0))
console.log(sumFibs(1))
console.log(sumFibs(10))
console.log(sumFibs(9000))
You don't need to use array at all if you need only sum of those numbers
function sumFibs(num) {
if(num <= 1) return 0;
var a = 0, b = 1, sum = a + b;
while(true) {
var next = a + b;
if(next >= num) {
break;
}
if(next % 2) {
sum += next;
}
a = b;
b = next;
}
return sum
}
console.log(sumFibs(0))
console.log(sumFibs(1))
console.log(sumFibs(10))
console.log(sumFibs(9000))
You need to change the loop condition. Loop till the last value of fib is less than num
function sumFibs(num) {
var fib = [1, 1];
for (var i = 2; fib[fib.length - 1] < num; i++) {
var next = fib[i - 1] + fib[i - 2];
fib.push(next);
}
return fib
.filter(x => !(x % 2))
.reduce((ac,a) => ac + a,0)
}
console.log(sumFibs(10));
If you want to retain your code for the most part, you can add an if statement that breaks the loop once your next number goes beyond your num:
function sumFibs(num) {
var fib = [1, 1];
for (var i = 2; i < num; i++) {
var next = fib[i - 1] + fib[i - 2];
if ( next > num ) { // not >= assuming you want to include your num
break;
}
fib.push(next);
}
console.log({fib});
return fib.filter(function(a) {
return (a % 2 != 0);
})
.reduce(function(a, z) {
return a + z;
})
}
console.log(sumFibs(10));
Your code is summing the first N Fibonacci numbers that are odd. You seem to be looking for the first odd Fibonacci numbers that sum to N:
function oddFibsThatAddTo(target)
{
let currentFib = 1;
let lastFib = 1;
let sum = 2;
const outs = [1,1];
while(sum < target)
{
let nextFib = currentFib + lastFib;
if(nextFib % 2 == 1)
{
sum += nextFib;
outs.push(nextFib);
}
lastFib = currentFib;
currentFib = nextFib;
}
if(sum > target)
{
throw 'can\'t find perfect sequence';
}
return outs;
}
console.log(oddFibsThatAddTo(10))
function sumFibs(num) {
var fib=[1,1];
for(var i=2; i<num; i++){
var next=fib[i-1]+fib[i-2];
var fibi=fib.push(next);
}
return fib.filter(function(a){
return(a%2!=0 && a<=num);
})
.reduce(function(a,z){
return a+z;
})
}
console.log(sumFibs(10));

You are given a two-digit integer n. Return the sum of its digits. Example For n = 29, the output should be addTwoDigits(n) = 11

More info:
Input/Output Details:
[time limit] 4000ms (js)
[input] integer n
A positive two-digit integer.
Constraints:
10 ≤ n ≤ 99.
[output] integer
The sum of the first and second digits of the input number.
Below is my attempt
function addTwoDigits(n) {
var num = n;
var n = num.toString();
var sum = n[0] + n[1];
return sum;
}
var userInput= prompt('enter a number');
if (userInput>= 10 && userInput<=99) {
return addTwoDigits(userInput);
}
console.log(addTwoDigits(n));
function addTwoDigits (input){
var numString = input.toString()
var numArray = numString.split('')
var digitOne = []
var digitTwo = []
for (var i = 0; i < numArray.length; i++){
digitOne.push(numArray[0])
digitTwo.push(numArray[1])
}
for (var i = 0; i < digitOne.length; i++){
digitOne.pop()
}
for (var i = 0; i < digitTwo.length; i++){
digitTwo.pop()
}
var numOne = parseInt(digitOne)
var numTwo = parseInt(digitTwo)
var sum = numOne + numTwo
return sum;
}
n[0] and n[1] are strings. + is only addition for two numbers, but concatenation for any other case, so "2" + "9" is back at "29" rather than 11 that you hope for. Use parseInt(n[0]) (and similar for n[1]) to turn them into something you can do arithmetic with.
Alternately, you can do this purely numerically, without ever touching strings:
var tens = Math.floor(n / 10);
var ones = n % 10;
var sum = tens + ones;
Here is my answer in Java.
int addTwoDigits(int n) {
String number = String.valueOf(n);
int[] nums = new int [number.length()];
for(int i = 0; i < number.length(); i++){
nums[i] = Character.getNumericValue(number.charAt(i));
}
return nums[0] + nums[1];
}
function solution(n) {
let number = n.toString();
console.log("Here is my number", );
let convertedNum = number.split('');
console.log("Here is my converted number: ", convertedNum.length)
let sum = 0;
for(let i = 0; i < convertedNum.length; i++){
sum += parseInt(convertedNum[i])
}
return sum;
}
Use parseInt
function addTwoDigits(n) {
var num = n;
var n = num.split("");
var sum = parseInt(n[0]) + parseInt(n[1]);
return sum;
}
var userInput = prompt('enter a number');
if (userInput >= 10 && userInput <= 99) {
console.log(addTwoDigits(userInput));
}
function addTwoDigits(n) {
let str = n.toString()
if (n >= 10 && n < 100){
return +str[0] + +str[1]
}
}
function solution(n) {
let a = Math.floor(n/10);
let b = n%10;
return a + b;
}
I used the charAt method and parseInt function to get an answer. You can see it in this code snippet:
function addTwoDigits(n) {
var string = n.toString();
var sum = parseInt(string.charAt(0)) + parseInt(string.charAt(1));
return sum;
}
var userInput = prompt('enter a number');
if (userInput >= 10 && userInput <= 99) {
console.log(addTwoDigits(userInput));
}
function addDigits(source) {
return source
/* convert number to string and split characters to array */
.toString().split('')
/* convert char[] to number[] */
.map(function (elm) { return Number(elm); })
/*
accumilate all numbers in array to value, which is initally zero.
REF: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Examples
*/
.reduce(function (result, current) { return result + current; }, 0)
}
function validate(source) {
if (typeof(source) == "undefined" || source == null) { throw 'value can not ne NULL ...'; }
if ((typeof(source) !== 'number')) { throw 'value has to of NUMBER type ....'; }
if ((source % 1)) { throw 'value has to be an INTEGER ....'; }
if (!(source >= 10 && source <= 99)) { throw 'value is out of range 10-99 ....'; }
}
/* TESTING ......*/
var samples = [null, 'sdsad', 9.9, 9, 10, 99, 100];
samples.forEach(function (elm) {
try {
validate(elm);
console.log(elm + ': ' + addDigits(elm));
} catch (exp) {
console.log(elm + ': ' + exp);
}
});
<
$(document).ready(function(){
var $number = $('input[name=number]'),
$result = $('.result');
$('input[name="calculate"]').on('click',function(){
var total = parseInt($number.val()),
decens = Math.floor(total/10),
units = total % 10;
if (total >9 && total < 100){
$result.html(decens + units);
}
else {
$result.html("Total must be an integer between 10 and 99");
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input name="number" />
<input type="submit" name="calculate" value="Calculate"/>
Result:<span class="result"></span>
Here is a example from me
function solution(n) {
let num = n;
let num2 = n.toString().split("");
let sum = parseInt(num2[0]) + parseInt(num2[1]);
return sum;
}
Follow,
function solution(n) {
let num = n.toString().split('')
return parseInt(num.reduce((acc, cur) => Number(acc) + Number(cur), 0))
}
function solution(n) {
let num = n.toString().split('')
return parseInt(num.reduce((acc, cur) => Number(acc) + Number(cur), 0))
}
document.getElementById("input29").innerHTML = solution(29);
document.getElementById("input48").innerHTML = solution(48);
<!DOCTYPE html>
<html>
<body>
<h2>Example</h2>
Input 29: <p id="input29"></p>
<br>
Input 48: <p id="input48"></p>
</body>
</html>

Try to use quatitize.js but got error message "Uncaught TypeError: Object #<error> has no method 'palette'"

I am learning to use the quantitize.js
but getting the error "Uncaught TypeError: Object # has no method 'palette'". I don't know what is wrong. PLease help. Thanks.
Here is the code:
<div></div>
<script
src="jquery-2.0.3.min.js"></script>
<script>
/*!
* quantize.js Copyright 2008 Nick Rabinowitz.
* Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
*/
// fill out a couple protovis dependencies
/*!
* Block below copied from Protovis: http://mbostock.github.com/protovis/
* Copyright 2010 Stanford Visualization Group
* Licensed under the BSD License: http://www.opensource.org/licenses/bsd-license.php
*/
if (!pv) {
var pv = {
map: function (array, f) {
var o = {};
return f ? array.map(function (d, i) {
o.index = i;
return f.call(o, d);
}) : array.slice();
},
naturalOrder: function (a, b) {
return (a < b) ? -1 : ((a > b) ? 1 : 0);
},
sum: function (array, f) {
var o = {};
return array.reduce(f ? function (p, d, i) {
o.index = i;
return p + f.call(o, d);
} : function (p, d) {
return p + d;
}, 0);
},
max: function (array, f) {
return Math.max.apply(null, f ? pv.map(array, f) : array);
}
};
}
/**
* Basic Javascript port of the MMCQ (modified median cut quantization)
* algorithm from the Leptonica library (http://www.leptonica.com/).
* Returns a color map you can use to map original pixels to the reduced
* palette. Still a work in progress.
*
* #author Nick Rabinowitz
* #example
*/
// array of pixels as [R,G,B] arrays
var myPixels = [
[190, 197, 190],
[202, 204, 200],
[207, 214, 210],
[211, 214, 211],
[205, 207, 207]
// etc
];
var maxColors = 4;
var cmap = MMCQ.quantize(myPixels, maxColors);
var newPalette = cmap.palette();
var newPixels = myPixels.map(function (p) {
return cmap.map(p);
});
// */
var MMCQ = (function () {
// private constants
var sigbits = 5,
rshift = 8 - sigbits,
maxIterations = 1000,
fractByPopulations = 0.75;
// get reduced-space color index for a pixel
function getColorIndex(r, g, b) {
return (r << (2 * sigbits)) + (g << sigbits) + b;
}
// Simple priority queue
function PQueue(comparator) {
var contents = [],
sorted = false;
function sort() {
contents.sort(comparator);
sorted = true;
}
return {
push: function (o) {
contents.push(o);
sorted = false;
},
peek: function (index) {
if (!sorted) sort();
if (index === undefined) index = contents.length - 1;
return contents[index];
},
pop: function () {
if (!sorted) sort();
return contents.pop();
},
size: function () {
return contents.length;
},
map: function (f) {
return contents.map(f);
},
debug: function () {
if (!sorted) sort();
return contents;
}
};
}
// 3d color space box
function VBox(r1, r2, g1, g2, b1, b2, histo) {
var vbox = this;
vbox.r1 = r1;
vbox.r2 = r2;
vbox.g1 = g1;
vbox.g2 = g2;
vbox.b1 = b1;
vbox.b2 = b2;
vbox.histo = histo;
}
VBox.prototype = {
volume: function (force) {
var vbox = this;
if (!vbox._volume || force) {
vbox._volume = ((vbox.r2 - vbox.r1 + 1) * (vbox.g2 - vbox.g1 + 1) * (vbox.b2 - vbox.b1 + 1));
}
return vbox._volume;
},
count: function (force) {
var vbox = this,
histo = vbox.histo;
if (!vbox._count_set || force) {
var npix = 0,
i, j, k;
for (i = vbox.r1; i <= vbox.r2; i++) {
for (j = vbox.g1; j <= vbox.g2; j++) {
for (k = vbox.b1; k <= vbox.b2; k++) {
index = getColorIndex(i, j, k);
npix += (histo[index] || 0);
}
}
}
vbox._count = npix;
vbox._count_set = true;
}
return vbox._count;
},
copy: function () {
var vbox = this;
return new VBox(vbox.r1, vbox.r2, vbox.g1, vbox.g2, vbox.b1, vbox.b2, vbox.histo);
},
avg: function (force) {
var vbox = this,
histo = vbox.histo;
if (!vbox._avg || force) {
var ntot = 0,
mult = 1 << (8 - sigbits),
rsum = 0,
gsum = 0,
bsum = 0,
hval,
i, j, k, histoindex;
for (i = vbox.r1; i <= vbox.r2; i++) {
for (j = vbox.g1; j <= vbox.g2; j++) {
for (k = vbox.b1; k <= vbox.b2; k++) {
histoindex = getColorIndex(i, j, k);
hval = histo[histoindex] || 0;
ntot += hval;
rsum += (hval * (i + 0.5) * mult);
gsum += (hval * (j + 0.5) * mult);
bsum += (hval * (k + 0.5) * mult);
}
}
}
if (ntot) {
vbox._avg = [~~(rsum / ntot), ~~ (gsum / ntot), ~~ (bsum / ntot)];
} else {
// console.log('empty box');
vbox._avg = [~~(mult * (vbox.r1 + vbox.r2 + 1) / 2), ~~ (mult * (vbox.g1 + vbox.g2 + 1) / 2), ~~ (mult * (vbox.b1 + vbox.b2 + 1) / 2)];
}
}
return vbox._avg;
},
contains: function (pixel) {
var vbox = this,
rval = pixel[0] >> rshift;
gval = pixel[1] >> rshift;
bval = pixel[2] >> rshift;
return (rval >= vbox.r1 && rval <= vbox.r2 &&
gval >= vbox.g1 && rval <= vbox.g2 &&
bval >= vbox.b1 && rval <= vbox.b2);
}
};
// Color map
function CMap() {
this.vboxes = new PQueue(function (a, b) {
return pv.naturalOrder(
a.vbox.count() * a.vbox.volume(),
b.vbox.count() * b.vbox.volume()
)
});;
}
CMap.prototype = {
push: function (vbox) {
this.vboxes.push({
vbox: vbox,
color: vbox.avg()
});
},
palette: function () {
return this.vboxes.map(function (vb) {
return vb.color
});
},
size: function () {
return this.vboxes.size();
},
map: function (color) {
var vboxes = this.vboxes;
for (var i = 0; i < vboxes.size(); i++) {
if (vboxes.peek(i).vbox.contains(color)) {
return vboxes.peek(i).color;
}
}
return this.nearest(color);
},
nearest: function (color) {
var vboxes = this.vboxes,
d1, d2, pColor;
for (var i = 0; i < vboxes.size(); i++) {
d2 = Math.sqrt(
Math.pow(color[0] - vboxes.peek(i).color[0], 2) +
Math.pow(color[1] - vboxes.peek(i).color[1], 2) +
Math.pow(color[1] - vboxes.peek(i).color[1], 2)
);
if (d2 < d1 || d1 === undefined) {
d1 = d2;
pColor = vboxes.peek(i).color;
}
}
return pColor;
},
forcebw: function () {
// XXX: won't work yet
var vboxes = this.vboxes;
vboxes.sort(function (a, b) {
return pv.naturalOrder(pv.sum(a.color), pv.sum(b.color))
});
// force darkest color to black if everything < 5
var lowest = vboxes[0].color;
if (lowest[0] < 5 && lowest[1] < 5 && lowest[2] < 5)
vboxes[0].color = [0, 0, 0];
// force lightest color to white if everything > 251
var idx = vboxes.length - 1,
highest = vboxes[idx].color;
if (highest[0] > 251 && highest[1] > 251 && highest[2] > 251)
vboxes[idx].color = [255, 255, 255];
}
};
// histo (1-d array, giving the number of pixels in
// each quantized region of color space), or null on error
function getHisto(pixels) {
var histosize = 1 << (3 * sigbits),
histo = new Array(histosize),
index, rval, gval, bval;
pixels.forEach(function (pixel) {
rval = pixel[0] >> rshift;
gval = pixel[1] >> rshift;
bval = pixel[2] >> rshift;
index = getColorIndex(rval, gval, bval);
histo[index] = (histo[index] || 0) + 1;
});
return histo;
}
function vboxFromPixels(pixels, histo) {
var rmin = 1000000,
rmax = 0,
gmin = 1000000,
gmax = 0,
bmin = 1000000,
bmax = 0,
rval, gval, bval;
// find min/max
pixels.forEach(function (pixel) {
rval = pixel[0] >> rshift;
gval = pixel[1] >> rshift;
bval = pixel[2] >> rshift;
if (rval < rmin) rmin = rval;
else if (rval > rmax) rmax = rval;
if (gval < gmin) gmin = gval;
else if (gval > gmax) gmax = gval;
if (bval < bmin) bmin = bval;
else if (bval > bmax) bmax = bval;
});
return new VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo);
}
function medianCutApply(histo, vbox) {
if (!vbox.count()) return;
var rw = vbox.r2 - vbox.r1 + 1,
gw = vbox.g2 - vbox.g1 + 1,
bw = vbox.b2 - vbox.b1 + 1,
maxw = pv.max([rw, gw, bw]);
// only one pixel, no split
if (vbox.count() == 1) {
return [vbox.copy()]
}
/* Find the partial sum arrays along the selected axis. */
var total = 0,
partialsum = [],
lookaheadsum = [],
i, j, k, sum, index;
if (maxw == rw) {
for (i = vbox.r1; i <= vbox.r2; i++) {
sum = 0;
for (j = vbox.g1; j <= vbox.g2; j++) {
for (k = vbox.b1; k <= vbox.b2; k++) {
index = getColorIndex(i, j, k);
sum += (histo[index] || 0);
}
}
total += sum;
partialsum[i] = total;
}
} else if (maxw == gw) {
for (i = vbox.g1; i <= vbox.g2; i++) {
sum = 0;
for (j = vbox.r1; j <= vbox.r2; j++) {
for (k = vbox.b1; k <= vbox.b2; k++) {
index = getColorIndex(j, i, k);
sum += (histo[index] || 0);
}
}
total += sum;
partialsum[i] = total;
}
} else { /* maxw == bw */
for (i = vbox.b1; i <= vbox.b2; i++) {
sum = 0;
for (j = vbox.r1; j <= vbox.r2; j++) {
for (k = vbox.g1; k <= vbox.g2; k++) {
index = getColorIndex(j, k, i);
sum += (histo[index] || 0);
}
}
total += sum;
partialsum[i] = total;
}
}
partialsum.forEach(function (d, i) {
lookaheadsum[i] = total - d
});
function doCut(color) {
var dim1 = color + '1',
dim2 = color + '2',
left, right, vbox1, vbox2, d2, count2 = 0;
for (i = vbox[dim1]; i <= vbox[dim2]; i++) {
if (partialsum[i] > total / 2) {
vbox1 = vbox.copy();
vbox2 = vbox.copy();
left = i - vbox[dim1];
right = vbox[dim2] - i;
if (left <= right)
d2 = Math.min(vbox[dim2] - 1, ~~ (i + right / 2));
else d2 = Math.max(vbox[dim1], ~~ (i - 1 - left / 2));
// avoid 0-count boxes
while (!partialsum[d2]) d2++;
count2 = lookaheadsum[d2];
while (!count2 && partialsum[d2 - 1]) count2 = lookaheadsum[--d2];
// set dimensions
vbox1[dim2] = d2;
vbox2[dim1] = vbox1[dim2] + 1;
// console.log('vbox counts:', vbox.count(), vbox1.count(), vbox2.count());
return [vbox1, vbox2];
}
}
}
// determine the cut planes
return maxw == rw ? doCut('r') :
maxw == gw ? doCut('g') :
doCut('b');
}
function quantize(pixels, maxcolors) {
// short-circuit
if (!pixels.length || maxcolors < 2 || maxcolors > 256) {
// console.log('wrong number of maxcolors');
return false;
}
// XXX: check color content and convert to grayscale if insufficient
var histo = getHisto(pixels),
histosize = 1 << (3 * sigbits);
// check that we aren't below maxcolors already
var nColors = 0;
histo.forEach(function () {
nColors++
});
if (nColors <= maxcolors) {
// XXX: generate the new colors from the histo and return
}
// get the beginning vbox from the colors
var vbox = vboxFromPixels(pixels, histo),
pq = new PQueue(function (a, b) {
return pv.naturalOrder(a.count(), b.count())
});
pq.push(vbox);
// inner function to do the iteration
function iter(lh, target) {
var ncolors = 1,
niters = 0,
vbox;
while (niters < maxIterations) {
vbox = lh.pop();
if (!vbox.count()) { /* just put it back */
lh.push(vbox);
niters++;
continue;
}
// do the cut
var vboxes = medianCutApply(histo, vbox),
vbox1 = vboxes[0],
vbox2 = vboxes[1];
if (!vbox1) {
// console.log("vbox1 not defined; shouldn't happen!");
return;
}
lh.push(vbox1);
if (vbox2) { /* vbox2 can be null */
lh.push(vbox2);
ncolors++;
}
if (ncolors >= target) return;
if (niters++ > maxIterations) {
// console.log("infinite loop; perhaps too few pixels!");
return;
}
}
}
// first set of colors, sorted by population
iter(pq, fractByPopulations * maxcolors);
// Re-sort by the product of pixel occupancy times the size in color space.
var pq2 = new PQueue(function (a, b) {
return pv.naturalOrder(a.count() * a.volume(), b.count() * b.volume())
});
while (pq.size()) {
pq2.push(pq.pop());
}
// next set - generate the median cuts using the (npix * vol) sorting.
iter(pq2, maxcolors - pq2.size());
// calculate the actual colors
var cmap = new CMap();
while (pq2.size()) {
cmap.push(pq2.pop());
}
return cmap;
}
return {
quantize: quantize
}
})();
</script>
The code is from quantitize.js. Thanks alot.
It seems like the problem is happening when it gets to the function 'quantize'. It seems you aren't passing it the pixels properly, or setting the maxcolors too high or too low.
Try adding some console.logs in it and see if pixels are 0 or max colors are bad.
:
function quantize(pixels, maxcolors) {
// short-circuit
if (!pixels.length || maxcolors < 2 || maxcolors > 256) {
console.log('wrong number of maxcolors');
console.log("pixellength: " + pixels.length);
console.log("maxcolors:" + maxcolors);
return false;
}
console.log(pixels.length);
If you open the link to the current version of the script, you will see that this section of the code is commented out. Also on lines 38 and 39 it is specified that:
* Returns a color map you can use to map original pixels to the reduced
* palette. Still a work in progress.
Therefore, I wouldn't expect it to work, perhaps there are some missing libraries. I suggest you update your code with the latest version at https://gist.github.com/nrabinowitz/1104622.
Also, you can keep your page clean and separate the module. For example create a subdirectory assets, put the quantize.js in there and in your page include:
<script src="/assets/quantize.js" type="text/javascript"></script>

Categories