I'm using the code from this tutorial. I'm also letting the user select the color using <input type="color"> (and I convert that to RGB since it returns a hex value). The fill seems to work just fine if the color is left at the default (set to black on initialization). If the color is changed using the input though, sometimes it ends up freezing. From what I can tell it gets stuck in this loop:
while (pixelStack.length) {...}
There is a section in the loop where it pushes new values to the pixelStack array:
while (y++ < height - 1 && matchStartColor(pixelPos)) {
colorPixel(pixelPos);
if (x > 0) {
if (matchStartColor(pixelPos - 4)) {
if (!reachLeft) {
pixelStack.push([x - 1, y]);
reachLeft = true;
}
} else if (reachLeft) {
reachLeft = false;
}
}
if (x < width - 1) {
if (matchStartColor(pixelPos + 4)) {
if (!reachRight) {
pixelStack.push([x + 1, y]);
reachRight = true;
}
} else if (reachRight) {
reachRight = false;
}
}
pixelPos += width * 4;
}
As I understand this, the only reason it would constantly push to the pixelStack array is if the matchStartColor function never came back false.
function matchStartColor(pixelPos) {
var r = imageData.data[pixelPos];
var g = imageData.data[pixelPos + 1];
var b = imageData.data[pixelPos + 2];
return (r && g && b);
}
But I don't see why this sometimes works and other times not.
Basically the pixelStack array ends up growing continually so it can never exit the loop. I made a jsFiddle with a working example (though it will lock up eventually if you change the color and try to fill). It doesn't seem to fail 100% of the time when the color is changed, but change the color enough and fill and it eventually does (and some colors seem to fail more than others. The pinkish/red in the top left of the color picker window is a bad one for example).
It seems your issue is in your implementation of matchStartColor.
With the following implementation:
function matchStartColor(pixelPos) {
var r = imageData.data[pixelPos];
var g = imageData.data[pixelPos + 1];
var b = imageData.data[pixelPos + 2];
return (r && g && b);
}
This will only ever return false if either r, g or b are 0. When you use black (or any color with a 0 component), this works. If you pick a color where none of the components are 0, this function never returns false.
I think what you actually want to do here is compare the color of the image to the color that you've chosen.
Based on how you use this function, I think this should work:
function matchStartColor(pixelPos) {
var r = imageData.data[pixelPos];
var g = imageData.data[pixelPos + 1];
var b = imageData.data[pixelPos + 2];
return (r !== curColor.r || g !== curColor.g || b !== curColor.b);
}
https://jsfiddle.net/6Uy3U/4/
Related
I've just implemented a topological sort algorithm on my isometric game using this guide: https://mazebert.com/2013/04/18/isometric-depth-sorting/
The issue
Here's a little example (this is just a drawing to illustrate my problem because as we say, a picture is worth a thousand words), what I'm expecting is in left and the result of the topological sorting algorithm is in right
So in the right image, the problem is that the box is drawn BEFORE the character and I'm expecting it to be drawn AFTER like in the left image.
Code of the topological sorting algorithm (Typescript)
private TopologicalSort2() {
// https://mazebert.com/2013/04/18/isometric-depth-sorting/
for(var i = 0; i < this.Stage.children.length; i++) {
var a = this.Stage.children[i];
var behindIndex = 0;
for(var j = 0; j < this.Stage.children.length; j++) {
if(i == j) {
continue;
}
var b = this.Stage.children[j];
if(!a.isoSpritesBehind) {
a.isoSpritesBehind = [];
}
if(!b.isoSpritesBehind) {
b.isoSpritesBehind = [];
}
if(b.posX < a.posX + a.sizeX && b.posY < a.posY + a.sizeY && b.posZ < a.posZ + a.sizeZ) {
a.isoSpritesBehind[behindIndex++] = b;
}
}
a.isoVisitedFlag = 0;
}
var _sortDepth = 0;
for(var i = 0; i < this.Stage.children.length; ++i) {
visitNode(this.Stage.children[i]);
}
function visitNode(n: PIXI.DisplayObject) {
if(n.isoVisitedFlag == 0) {
n.isoVisitedFlag = 1;
if(!n.isoSpritesBehind) {
return;
}
for(var i = 0; i < n.isoSpritesBehind.length; i++) {
if(n.isoSpritesBehind[i] == null) {
break;
} else {
visitNode(n.isoSpritesBehind[i]);
n.isoSpritesBehind[i] = null;
}
}
n.isoDepth = _sortDepth++;
}
}
this.Stage.children.sort((a, b) => {
if(a.isoDepth - b.isoDepth != 0) {
return a.isoDepth - b.isoDepth;
}
return 0;
});
}
Informations
Player:
posX: [the x coordinate of the player]
posY: [the y coordinate of the player]
posZ: 0
sizeX: 1
sizeY: 1
sizeZ: 1
Box:
posX: [the x coordinate of the box]
posY: [the y coordinate of the box]
posZ: 0
sizeX: 3
sizeY: 1
sizeZ: 1
X and Y axis
Do you have any idea of the source of this problem? and maybe how to solve it?
The way to determine whether one object is before the other requires a bit more linear algebra.
First of all, I would suggest to translate the coordinates from the "world" coordinates to the "view" 2D coordinates, i.e. to the rows and columns of the display.
Note also that the original Z coordinate does not influence the sort order (imagine that an object would be lifted up along the Z axis: we can find a sort order where this move would not have any impact). So the above-mentioned translation could assume all points are at Z=0.
Let's take this set-up, but depicted from "above", so when looking along the Z axis down to the game floor:
In the picture there are 7 objects, numbered from 0 to 6. The line of view in the game would be from the bottom-left of this picture. The coordinate system in which I would suggest to translate some points is depicted with the red row/col axis.
The white diagonals in each object link the two points that would be translated and used in the algorithm. The assumption is that when one object is in front of another, their diagonal lines will not intersect. If they would, it would mean that objects are overlapping each other in the game world, which would mean they are like gasses, not solids :) I will assume this is not the case.
One object A could be in front of another object B when in the new coordinate system, the left-most column coordinate of B falls between the two column coordinates of A (or vice versa). There might not really be such an overlap when their Z coordinates differ enough, but we can ignore that, because when there is no overlap we can do no harm in specifying a certain order anyway.
Now, when the coordinates indicate an overlap, the coordinates of diagonals (of A and B) must be compared with some linear algebra formula, which will determine which one is in front of the other.
Here is your adapted function that does that:
topologicalSort() {
// Exit if sorting is a non-operation
if (this.Stage.children.length < 2) return;
// Add two translated coordinates, where each of the resulting
// coordinates has a row (top to bottom) and column
// (left to right) part. They represent a position in the final
// rendered view (the screen).
// The two pairs of coordinates are translations of the
// points (posX + sizeX, Y, 0) and (posX, posY + sizeY, 0).
// Z is ignored (0), since it does not influence the order.
for (let obj of this.Stage.children) {
obj.leftCol = obj.posY - obj.posX - obj.sizeX;
obj.rightCol = obj.posY - obj.posX + obj.sizeY;
obj.leftRow = obj.posY + obj.posX + obj.sizeX;
obj.rightRow = obj.posY + obj.posX + obj.sizeY;
obj.isoSpritesBehind = [];
}
for(let i = 0; i < this.Stage.children.length; i++) {
let a = this.Stage.children[i];
// Only loop over the next objects
for(let j = i + 1; j < this.Stage.children.length; j++) {
let b = this.Stage.children[j];
// Get the two objects in order of left column:
let c = b.leftCol < a.leftCol ? b : a;
let d = b.leftCol < a.leftCol ? a : b;
// See if they overlap in the view (ignoring Z):
if (d.leftCol < c.rightCol) {
// Determine which is behind: some linear algebra
if (d.leftRow <
(d.leftCol - c.leftCol)/(c.rightCol - c.leftCol)
* (c.rightRow - c.leftRow) + c.leftRow) {
// c is in front of d
c.isoSpritesBehind.push(d);
} else { // d is in front of c
d.isoSpritesBehind.push(c);
}
} // in the else-case it does not matter which one comes first
}
}
// This replaces your visitNode function and call:
this.Stage.children.forEach(function getDepth(obj) {
// If depth was already assigned, this node was already visited
if (!obj.isoDepth) {
// Get depths recursively, and retain the maximum of those.
// Add one more to get the depth for the current object
obj.isoDepth = obj.isoSpritesBehind.length
? 1+Math.max(...obj.isoSpritesBehind.map(getDepth))
: 1; // Depth when there is nothing behind it
}
return obj.isoDepth; // Return it for easier recursion
});
// Sort like you did, but in shorter syntax
this.Stage.children.sort((a, b) => a.isoDepth - b.isoDepth);
}
I add a snippet where I completed the class with a minimum of code, enough to make it run and output the final order in terms of object index numbers (as they were originally inserted):
class Game {
constructor() {
this.Stage = { children: [] };
}
addObject(posX, posY, posZ, sizeX, sizeY, sizeZ) {
this.Stage.children.push({posX, posY, posZ, sizeX, sizeY, sizeZ,
id: this.Stage.children.length}); // add a unique id
}
topologicalSort() {
// Exit if sorting is a non-operation
if (this.Stage.children.length < 2) return;
// Add two translated coordinates, where each of the resulting
// coordinates has a row (top to bottom) and column
// (left to right) part. They represent a position in the final
// rendered view (the screen).
// The two pairs of coordinates are translations of the
// points (posX + sizeX, Y, 0) and (posX, posY + sizeY, 0).
// Z is ignored (0), since it does not influence the order.
for (let obj of this.Stage.children) {
obj.leftCol = obj.posY - obj.posX - obj.sizeX;
obj.rightCol = obj.posY - obj.posX + obj.sizeY;
obj.leftRow = obj.posY + obj.posX + obj.sizeX;
obj.rightRow = obj.posY + obj.posX + obj.sizeY;
obj.isoSpritesBehind = [];
}
for(let i = 0; i < this.Stage.children.length; i++) {
let a = this.Stage.children[i];
// Only loop over the next objects
for(let j = i + 1; j < this.Stage.children.length; j++) {
let b = this.Stage.children[j];
// Get the two objects in order of left column:
let c = b.leftCol < a.leftCol ? b : a;
let d = b.leftCol < a.leftCol ? a : b;
// See if they overlap in the view (ignoring Z):
if (d.leftCol < c.rightCol) {
// Determine which is behind: some linear algebra
if (d.leftRow <
(d.leftCol - c.leftCol)/(c.rightCol - c.leftCol)
* (c.rightRow - c.leftRow) + c.leftRow) {
// c is in front of d
c.isoSpritesBehind.push(d);
} else { // d is in front of c
d.isoSpritesBehind.push(c);
}
} // in the else-case it does not matter which one comes first
}
}
// This replaces your visitNode function and call:
this.Stage.children.forEach(function getDepth(obj) {
// If depth was already assigned, this node was already visited
if (!obj.isoDepth) {
// Get depths recursively, and retain the maximum of those.
// Add one more to get the depth for the current object
obj.isoDepth = obj.isoSpritesBehind.length
? 1+Math.max(...obj.isoSpritesBehind.map(getDepth))
: 1; // Depth when there is nothing behind it
}
return obj.isoDepth; // Return it for easier recursion
});
// Sort like you did, but in shorter syntax
this.Stage.children.sort((a, b) => a.isoDepth - b.isoDepth);
}
toString() { // Just print the ids of the children
return JSON.stringify(this.Stage.children.map( x => x.id ));
}
}
const game = new Game();
game.addObject( 2, 2, 0, 1, 1, 1 );
game.addObject( 1, 3, 0, 3, 1, 1 );
game.addObject( 6, 1, 0, 1, 3, 1 );
game.addObject( 9, 3, 0, 1, 1, 1 );
game.addObject( 5, 3, 0, 1, 3, 1 );
game.addObject( 7, 2, 0, 1, 1, 1 );
game.addObject( 8, 2, 0, 3, 1, 1 );
game.topologicalSort();
console.log(game + '');
The objects in the snippet are the same as in the picture with the same numbers. The output order is [0,1,4,2,5,6,3] which is the valid sequence for drawing the objects.
I recently tried to work on a jQuery-Plugin which is used to spread an amount of containers randomly in a container to afterwards be able to animate them in a special way. You can see my attempts here: http://www.manuelmaurer.at/randposplugin.php
The Problem is, that some of them are overlapping and I can't figure out why. At first I thought that the reason for this might be that $.each() is not waiting for one loop to finish before starting the next one but I also tried to solve that using recursive functions - didn't help. I hope someone could give me a little push to figure out where the problem actually is, thanks in advance! You can see the code either on the page itself or just have a look at the following important parts.
This code is used to loop over all the elements. This is not further important, but the function "NoCollision" is trying to figure out, if an element exists in that area. If yes, it returns false, if the space can be used, it returns true. If the space can not be used, some random other coordinates are chosen and it will be tried again.
var Counter2 = 0;
$(FlowContainer).children(':not(:last)').each(function(elem) {
Counter2++;
ElemNow = $(FlowContainer).children().eq(Counter2);
ElemWidth = $(ElemNow).data("animwidth")
ElemHeight = $(ElemNow).data("animheight")
var Tries = 0;
var TryNowX = ElemPrevLeft;
var TryNowY = ElemPrevTop;
while (!NoCollision(TryNowX, TryNowY, ElemWidth, ElemHeight, PositionsArray, Settings.MinSpreadX, Settings.MinSpreadY) && Tries <= Settings.MaxTries) {
if (TryNowY < 15) {
TryNowY += randomIntFromInterval(0, 10);
} else if (TryNowY > (FlowContainer.height() - ElemHeight - 15)) {
TryNowY += randomIntFromInterval(-10, 0);
} else {
TryNowY += randomIntFromInterval(-10, 10);
}
if (TryNowX < 15) {
TryNowX += randomIntFromInterval(0, 10);
} else if (TryNowX > (FlowContainer.width() - ElemWidth - 15)) {
TryNowX += randomIntFromInterval(-10, 0);
} else {
TryNowX += randomIntFromInterval(-10, 10);
}
Tries++;
}
if (Tries == Settings.MaxTries) {
console.log("Warning: Couldn't fit all elements - hiding some.")
$(ElemNow).remove();
} else {
$(ElemNow).css({ top: TryNowY, left: TryNowX });
ElemPrevLeft = TryNowX;
ElemPrevTop = TryNowY;
PositionArray = [TryNowY, TryNowX, ElemHeight, ElemWidth];
PositionsArray[Counter2] = PositionArray;
}
})
The actual check, if the space can be used, takes part in the NoCollision-Function, which you can see in the following code.
function NoCollision(X, Y, W, H, PositionsArray, SpreadX, SpreadY) {
var NoErrors = true;
//Jedes Element im PositionsArray durchgehen und Prüfen
$.each(PositionsArray, function(PositionArray) {
var ArrY = PositionsArray[PositionArray][0];
var ArrX = PositionsArray[PositionArray][1];
var ArrW = PositionsArray[PositionArray][3];
var ArrH = PositionsArray[PositionArray][2];
if ((X < (ArrX - W - SpreadX) || X > (ArrX + ArrW + SpreadX)) && (Y < (ArrY - H - SpreadY) || Y > (ArrY + ArrH + SpreadY))) {
//SHOULD BE OKAY HERE
} else {
NoErrors = false;
}
})
return NoErrors;
}
The array which I am using to save the coordinates of all the already positioned divs looks like this.
PositionsArray[
[Elem1PositionY, Elem1PositionX, Elem1Height, Elem1Width]
[Elem2PositionY, Elem2PositionX, Elem2Height, Elem2Width]
]
My thought was to do it like this. Is there something wrong with the way I'd do it or is there a mistake in my implementation?
I am absolutely thankful for every bit of help! Thanks in advance!
I finally solved the issue - my calculation of the overlapping was a bit wrong, just had to change the && (and) to || (or) - works like charm now.
Found a better solution using a plugin which was referenced on another stackoverflow-thread - works way better now.
http://jsfiddle.net/goldrunt/SeAGU/52/
Line 49 checks for "false" on isOnCircle function before creating the new object. Function is on line 32. When creating more object, the function is passing when it should not pass.
if (isOnCanvas(location) && !isOnCircle(location)) {
console.log(location, isOnCanvas(location), isOnCircle(location));
create(location);
In fact I can't get the collision detection to register true no matter what values are passed to it
(Math.pow((a.x - i.x), 2) + Math.pow((a.y - i.y), 2) <= Math.pow((a.radius + i.radius), 2))
here I've fixed and given more descriptive variable names so you can see what's going on.
EDIT: I've noticed you don't always feed a circle but sometimes a point as A, which does not have a .radius property resulting in NaN, which also screws up your comparison.
function circleTest(a,b) {
var DistanceX = a.x - b.x;
var DistanceY = a.y - b.y;
var DistanceCenter = Math.sqrt(DistanceX * DistanceX + DistanceY * DistanceY);
var CollisionDistance = b.radius;
if (a.radius) CollisionDistance += a.radius
return DistanceCenter <= CollisionDistance;
}
I also noticed a problem in your function called "isOnCircle" where you are using i (a number) as if it were a circle object, with the above function this can be fixed like:
function isOnCircle(a) {
for (var i = 0; i < circles.length; i++) {
if (circleTest(a, circles[i])) return true;
}
return false;
}
Two problems:
i is the numerical index you are using to iterate through the circles array but you are using it as if it was a circle object; you need to use circles[i] to get the circle at each iteration.
a is a point and does not have a radius (in the code below I've left a.radius in just in-case you pass in a circle rather than a point and have ORed it with 0 so you get a valid number).
Defining some additional variables (for clarity) then you can replace the isOnCircle function with this:
function isOnCircle(a) {
var i=0,l=circles.length,x,y,d,c;
for (; i < l; ++i) {
c = circles[i];
x = a.x-c.x;
y = a.y-c.y;
d = (a.radius||0)+c.radius;
if (x*x+y*y <= d*d) {
return true;
}
}
return false;
}
I'm looking for a way to find the average of an unspecified number of colors. I spent a lot of time looking for a way to do this. First I tried converting my colors to CMYK and averaging them, but that didn't provide the result I expected. Then I saw that in several different places, converting the colors to CIE L*a*b* space and then averaging is the preferred way of doing this. So I looked up how to convert RGB colors to LAB space and converted into Javascript the necessary algorithms to make this happen.
Now that I have my colors in LAB space, I thought it would be as simple as finding the average of my colors, so I wrote this function to do the trick:
color.mixRGB = function() {
var cols = Array.prototype.slice.call(arguments),
i = cols.length,
lab = {l: 0, a: 0, b: 0};
while(i--) {
if (typeof cols[i].r === "undefined" && typeof cols[i].g === "undefined" && typeof cols[i] === "undefined") {
console.log("Not enough parameters supplied for color " + i + ".");
return;
}
if(cols[i].r === 0 && cols[i].g === 0 && cols[i].b === 0) {
cols.splice(i, 1);
} else {
cols[i] = color.RGBtoLAB(cols[i]);
lab.l += cols[i].l;
lab.a += cols[i].a;
lab.b += cols[i].b;
}
}
lab.l /= cols.length;
lab.a /= cols.length;
lab.b /= cols.length;
return color.LABtoRGB(lab);
};
If I enter RGB (255, 0, 0) and RGB(0, 0, 255) into the function, I get RGB(202, -59, 136). This color is nothing near what Color Hexa says is the average of those two RGBs, which is RGB (128, 0, 128), a.k.a purple.
I went back over all my code, and so far I've managed to determine that the problem does not lie with any of my conversion algorithms by double- and triple-checking them against Color Hexa and EasyRGB. That means either a) the issue must lie with how I'm averaging the colors or b) I've been misinformed and I shouldn't attempt to mix colors in CIE L*a*b* space.
What exactly am I missing here? Using my current algorithm, why is averaging RGB (255, 0, 0) and RGB (0, 0, 255) not giving me the same results that Color Hexa (or even visual estimation) provides? (here's a fiddle of my problem)
Lets say you have your colors defined by R0, G0, B0 and R1, G1, B1. Then blended/average color will have following RGB values:
RA = (R0+R1)/2;
GA = (G0+G1)/2;
BA = (B0+B1)/2;
Thats it, basically.
A null return means there has been an error.
color.mixRGB = function() {
var cols = Array.prototype.slice.call(arguments),
i = cols.length,
rTotal = 0, gTotal = 0, rTotal = 0, colTotal = 0;
while(i--) {
// NOTE: you had && in your code, I think they should be ||
if (typeof cols[i].r === "undefined" || typeof cols[i].g === "undefined" || typeof cols[i] === "undefined") {
console.log("Not enough parameters supplied for color " + i + ".");
return null;
}
colTotal++;
rTotal += cols[i].r;
gTotal += cols[i].g;
bTotal += cols[i].b;
}
if(colTotal === 0) return null;
// I am not sure what you are trying to return, just build it up with your rgb values
return (new color(Math.round(rTotal / colTotal), Math.round(gTotal / colTotal), Math.round(bTotal / colTotal)));
};
Something that might do something like
<img class="image" ... />
$(".image").get_colors()
I know there are few websites where you can upload your image and it would generate the color for you but I want something to put on my website
Something like this where you see the colors generated from the screenshot and can search by colors. I tried to check the source code but I could not see any reference to a js library.
I need this feature to work with js if possible.
EDIT:
The image would be on the page already; I just need to generate its color, so I don't want the uploading features.
Thanks.
You might be interested in this related question and my answer to another one.
Getting all the colors from an image is simple, at least in a browser that supports the canvas element - the technique is described here. You end up with a CanvasPixelArray (described here), which is essentially an array like [r,g,b,a,r,g,b,a, ...] where r,g,b,a are bytes indicating the red, green, blue, and alpha values of each pixel.
The hard part is identifying or creating a small selection of representative colors, rather than the 10,000 colors that might be in a 100x100 image. This is a pretty complicated problem, with many solutions (overview here). You can see Javascript implementations of two possible algorithms in the clusterfck library and my port of the Leptonica Modified Median Cut algorithm.
I did write just for fun. It is a jquery plugin, if you don't use it you can read it for some pointers. If there is some error during the call to get_colors a array is set in the return value to hold the errors, it returns an array of objects, these objects are a histogram of a image(one item in the array for every selected element).
(function($, window, document, undefined){
var canvas = document.createElement('canvas');
if (canvas && canvas.getContext){
$.fn.get_colors = function(){
var rv = [];
this.each(function(){
var tagname = this.tagName.toLowerCase();
if ((tagname === 'img') || (tagname === 'canvas') || (tagname === 'video')){
//something bad can happend when drawing the image
try{
var w = this.getAttribute('width');
var h = this.getAttribute('height');
canvas.setAttribute('width', w);
canvas.setAttribute('height', h);
var ctxt = canvas.getContext('2d');
if (ctxt){
ctxt.drawImage(this, 0, 0);
var imagedata = ctxt.getImageData(0, 0, w, h);
var data = imagedata.data;
//log('imagedata.width:'+imagedata.width+' imagedata.height:'+imagedata.height+' w:'+w+' h:'+h);
var obj = {};
var color = '';
var r = 0, g = 0, b = 0, a = 0;
var pix = data.length;
for (pix--; pix > 2; pix-=4){
//a = data[pix - 0];
b = data[pix - 1];
g = data[pix - 2];
r = data[pix - 3];
if (r < 16) r = '0' + r.toString(16);
else r = r.toString(16);
if (g < 16) g = '0' + g.toString(16);
else g = g.toString(16);
if (b < 16) b = '0' + b.toString(16);
else b = b.toString(16);
//if (a < 16) a = '0' + r.toString(16);
//else a = a.toString(16);
//color = r + g + b + a;
color = r + g + b;
if (obj[color] > 0) ++obj[color];
else obj[color] = 1;
}
rv.push(obj);
imagedata = data = obj = null;
}
ctxt = null;
} catch(error){
if (!rv.errors){
rv.errors = [];
}
rv.errors.push(error);
}
}
});
return rv;
};
} else{
$.fn.get_colors = function(){
throw new Error('canvas element support required!');
};
}
})(jQuery, this, this.document);
If a document with only one image with 4 pixels(2x2) "#ff0000, #00ff00, #0000ff, #ff0000", if you do $('img').get_colors(); it returns [{"ff0000": 2, "0000ff": 1, "00ff00":1}].
To learn how to use the canvas element you could look at MDN and at the specs in development for details about pixel manipulation.
Edit: commented out a line I was using when debugging.
Have you seen this project on Github?
http://lokeshdhakar.com/projects/color-thief/
It's a javascript solution. (It depends on two additional libraries: jquery, quantize.js).
var colorThief = new ColorThief();
colorThief.getPalette(sourceImage, 8);
getPalette(sourceImage[, colorCount, quality])
Which will return an array, like so: [ [num, num, num], [num, num, num], ... ]