i'm writing a little coffeescript/js app that allows user to design icons ( 16x16 pixels or 32X32 pixels ).
The icon is actually a 2 dimensional array with color cells. A cell can have a color or be empty.
I want the user to be able to fill blank cells with a "bucket paint" tool.
It means that
if the user clicks on a blank cell , all the cells that are blank next to the clicked cell with be filled with the choosen color , until it reaches a colored cell
if the user clicks on a colored cell , all the cells that are next to the clicked cell and share the same color will be filled , but not the blank ones nor the colored ones ( with another color ).
The app already allows the user to fill cells one by one with a chosen color , or delete colored cells with a pen tool.
Any suggestions ?
(ps : i'm not using html canvas to draw )
Since this is only 16x16 or 32x32 you can use a recursive solution:
Say your starting point is to change pixel x/y from color A to color B (A or B can be empty).
In pseudo code:
function floodfill(x,y,A,B) {
if ((x<0) || (x>15) || (y<0) || (y>15)) return;
if (get_color(x,y)!=A) return;
set_color(x,y,B);
floodfill(x-1,y-1,A,B);
floodfill(x-1,y,A,B);
floodfill(x-1,y+1,A,B);
floodfill(x,y-1,A,B);
floodfill(x,y+1,A,B);
floodfill(x+1,y-1,A,B);
floodfill(x+1,y,A,B);
floodfill(x+1,y+1,A,B);
}
I'm not entirely sure what type of suggestions you're looking for, but you should take a look at flood fill algorithms.
Here it is on Wikipedia: http://en.wikipedia.org/wiki/Flood_fill
Allright , here is how i solve my problem in coffeescript.
It is an exemple using canvas.
the script should run on any page where there is a canvas element with a id of canvas , i had to create my own stack because of stackoverflow issues.
log = ->
console.log arguments
class Point
constructor:(#x,#y)->
class BucketFiller
MAXITERATION:100000
factor:1
fill : (ctx,pixel, colCible, colRep)->
P = []
max = #MAXITERATION
if #getColorAtPixel(ctx,pixel)!=colCible then return null
P.push(pixel)
while P.length > 0 and max >=0
--max
currentpixel = P.pop()
#fillRect(ctx,currentpixel.x,currentpixel.y,#factor,#factor,colRep)
if #isInCanvas(ctx,currentpixel)
if #getColorAtPixel(ctx,#up(currentpixel)) == colCible then P.push(#up(currentpixel))
if #getColorAtPixel(ctx,#down(currentpixel)) == colCible then P.push(#down(currentpixel))
if #getColorAtPixel(ctx,#right(currentpixel)) == colCible then P.push(#right(currentpixel))
if #getColorAtPixel(ctx,#left(currentpixel)) == colCible then P.push(#left(currentpixel))
return
fillRect:(ctx,x,y,width,height,color)->
ctx.fillStyle = color
ctx.fillRect(x,y,width,height)
return
down :(pixel)->
return {x:pixel.x,y:pixel.y-#factor}
up:(pixel)->
return {x:pixel.x,y:pixel.y+#factor}
right :(pixel)->
return {x:pixel.x+#factor,y:pixel.y}
left :(pixel)->
return {x:pixel.x-#factor,y:pixel.y}
getColorAtPixel:(ctx,pixel)->
try
imageData = ctx.getImageData(pixel.x,pixel.y,1,1)
catch e
return null
return #rgbArrayToCssColorString(imageData.data)
rgbArrayToCssColorString:(array)->
result = "rgb(#{array[0]},#{array[1]},#{array[2]})"
return result
isInCanvas : (ctx,pixel)->
result = ((0 <= pixel.x <= ctx.canvas.width) and (0 <= pixel.y <= ctx.canvas.height))
return result
main=->
buckfiller = new BucketFiller()
log("start")
canvas = document.getElementById("canvas")
ctx = canvas.getContext("2d")
penPosition = new Point(2,10)
fillColor = "rgb(255,0,0)"
colCible = buckfiller.getColorAtPixel(ctx,penPosition)
try
buckfiller.fill(ctx,penPosition,colCible,fillColor)
catch e
log e
window.onload=->
main()
Related
I want to color the bars based on the conditional check.
I want to show the bar color based on the conditional check as follows:
I am displaying the 'expertise level' for the certain time period.
When the expertise column starts from 'Begining' word, they have to show red color for that timeline bar.
When the expertise column starts from 'Advanced' word, they have to show brown color for that timeline bar.
When the expertise column starts from 'Intermediate' word, they have to submit Yellow color for that timeline bar.
Please find the complete demo : https://plnkr.co/edit/XrEgUo9oDJvxAXH03KMA?p=preview
Currently i'm using the below options:
function drawChart() {
var options = {
colors:['red','brown','yellow'],
};
chart.draw(dataTable,options, {
height: (dataTable.getNumberOfRows() * rowHeight) + rowHeight
});
}
Any inputs on how to give timeline chart each milestone with different colors based on the input string .
Whatever string your using to detect what level you are at.
Use:
var totalWords = "foo love bar very much.";
var firstWord =totalWords.replace(/ .*/,'');
To get the first word (intermediate, advanced, etc)
Then use a and if statement to output the color you want
if (firstword == "advanced) {
color = "brown";
} else if(firstword == "intermediate"){
color = "yellow";
}
Firstly define your rows before adding them to the table so that you can iterate over them to construct an array with the corresponding colors prior or rendering the chart.
Then call map on the data array passing this function.
function getRowColor(row) {
let firstWord = row[1].trim().split(' ')[0];
if (firstWord === 'Beginning') return 'red';
if (firstWord === 'Advanced') return 'brown';
if (firstWord === 'Intermediate') return 'yellow';
}
const colors = rows.map(getRowColor);
I've forked your plunker here
I am trying to make the blank or "0" function of ms in a web page.
The goal:
what happens in mine: what should happen:
the red square indicates the button that was clicked, and the green circles indicate its adjacent squares/tiles.
My approach or logic to make this function was:
step1: if the button that was clicked is a 0, reveal its adjacent tiles.
step 2: for every adjacent tile, if THAT tile is 0, reveal THAT TILES' adjacent tiles. and so on until all adjacent tiles of every connected 0 is revealed.
The code of my function: (the parameters are the coordinates of the button that is clicked. e.g.the red square/tile in the picture above has coordinates 3,6)
function RevealNearbyTiles(y,x){
var cordsx; //cordsx and cordsy represent the adjacent tiles of the coordinates (the parameters).
var cordsy;
var coordinates;
for(i=-1; i<2; i++){ //for every adjacent tile:
for(j=-1;j<2;j++){
cordsx = x;
cordsy = y;
if(i === 0 && j === 0){
continue;
}
else{
cordsx += j;
cordsy += i;
//if this ^ offset is within the grid:
if((cordsx >= 0 && cordsx < 10) && (cordsy >= 0 && cordsy < 10)){
//the coordinates of the tile.
coordinates = $("#mstable tr:nth-of-type("+(cordsy+1)+") td:nth-of-type("+(cordsx+1)+") .tiles");
//if it has not been revealed
if(coordinates.parent().attr("data-revealed") === "false"){
//reveal this coordinate.
coordinates.empty().append("<p id='number'>"+coordinates.parent().attr("data-value")+"</p>");
coordinates.parent().attr("data-revealed", "true");
//if this coordinate is 0
if(coordinates.parent().attr("data-value") === " "){
//reveal this coordiantes' nerabytiles
RevealNearbyTiles(cordsy,cordsx);
}
}
}
}
}
}
}
The attribute "data-value" is the number of nearby bombs the tile has.
The attribute "data-revealed" is true if the tile is revealed, or false if it's not. They both work, don't worry too much about them.
The code for every tile button:
$(".tiles").click(function(){
//if this button is clicked, reveal this tile
$(this).empty().append("<p id='number'>"+$(this).parent().attr("data-value")+"</p>");
$(this).parent().attr("data-revealed","true");
//if this tile's value is 0, call the function
if($(this).parent().attr("data-value") === " "){
RevealNearbyTiles($(this).parent().data("index").a,$(this).parent().data("index").b);
}
});
What I think the problem is: The for loop is supposed to run for every adjacent tile of the tile that was clicked, but when it runs the function for the first tile, it forgets about all other adjacent tiles. I need to make it so that the function runs on all adjacent tiles that are 0, and on all of THEIR adjacent tiles that are 0 and so on.
Thanks for your help, its a hard problem to explain =/. I searched many places but could not find an answer. Sorry for the very long and specific problem.
I think the issue is with your two for loops, they use global vars called i and j which are the same for each call to RevealNearbyTiles(). You can fix this with the following code...
for(var i=-1; i<2; i++){ //for every adjacent tile:
for(var j=-1;j<2;j++){
I think what happened was that your algorithm ran until it hit the case where it had revealed all adjacent tiles (e.g. i = 1, j = 1), it then exited up the call chain with those values and exited each loop rather than keep executing them.
I am working on a project using html5 in which I have a canvas on which I am drawing 5 images one after the other with a time spacing of 1 second. I need to be able to write some text on a particular frame dynamically which I am unable to do. Say for example when I enter 3;hello in the text field, when the third frame is drawn onto the canvas, it should display the text hello on it. below is my javascript function for the same.
function overlayText(){
string2 = document.getElementById("myTextArea");
var splitString = (string2.value).split(";");
var temp = splitString[0].split(",");
var frameNumber = splitString[0];
var text = splitString[1];
//var array = text.split("\n");
alert(frameNumber); alert(text);
ctx.font = "16pt Arial";
if(frameNumber!=0 && frameNumber<im1.length) {
for(var i = 0; i < text.length; i++){
//var temp = array[i];
ctx.textAlign="left";
ctx.textBaseline="bottom";
ctx.fillText(im1[frameNumber].text[i], 40, 50); //Seems to be the issue
}
}
else alert("Chill out!!Try entering realistic frame number");
}
I guess I am unable to use the array appropriately. Could someone please help me with this?
Thanks for your help.
You can not draw text under already drawn image. You have to store all texts in some shared variable(array similar to images' URLs i.e.) and draw text right after corresponding drawImage call inside setInterval handler. Unfortunately your fiddle doesn't work.
I have a HTML5 canvas, which is displaying a number of images and a paragraph of text on the page underneath the canvas. I want the text in the paragraph to be updated to display a different element from a JS array depending on which image the user clicks on.
Currently, I have a 'mousedown' function that looks like this:
_mousedown: function(evt) {
this._setUserPosition(evt);
var obj = this.getIntersection(this.getUserPosition());
if(obj && obj.shape) {
var shape = obj.shape;
this.clickStart = true;
shape._handleEvent('mousedown', evt);
isClickOnImage(evt);
var id = shape.id;
selectTip(id);
}
//init stage drag and drop
if(Kinetic.DD && this.attrs.draggable) {
this._initDrag();
}
}
I tried using the line var id = shape.id to update the ID that's being passed to the function, so that it will get the correct element from my 'tips' array, but for some reason, when I view the page in the browser, and click on an image, the text beneath the canvas is not updated. It seems that this function is not updating the 'id' variable to the ID of whichever image has been clicked.
After looking into this, it seems to me that I will want to use a loop inside the 'mousedown' function, that will take the 'id' of the image on which the click has been detected, and loop through my 'sources' array (which is where all of the images have been loaded from the HTML into the JS), checking at each position whether the image stored at that location has the same ID as that of the image that has been clicked on. If it does, the loop should set the text to the text stored at that position of the array, and if not, it should continue looking through the array until it find it. Would this make sense? I tried adding the following code to the 'mousedown' function, but it doesn't change the text as I expected:
var imageCheckArray = 0;
while(imageCheckArray < sources.length){
if(shape.id == sources[imageCheckArray]){
selectTip(imageCheckArray);
} else {
imageCheckArray++;
}
}
Is there something I'm missing from the loop?
The code for the whole function currently looks like this:
_mousedown: function(evt) {
this._setUserPosition(evt);
var obj = this.getIntersection(this.getUserPosition());
if(obj && obj.shape) {
var shape = obj.shape;
this.clickStart = true;
shape._handleEvent('mousedown', evt);
isClickOnImage(evt);
/*This line needs to get the element of the sources array that has been selected,
and then select the element at the same position from the tips array.*/
//var id = null;
var imageCheckArray = 0;
while(imageCheckArray < sources.length){
if(shape.id == sources[imageCheckArray]){
selectTip(imageCheckArray);
} else {
imageCheckArray++;
}
}
//var id =
//selectTip(id);
}
//init stage drag and drop
if(Kinetic.DD && this.attrs.draggable) {
this._initDrag();
}
}
Edit 11/01/2013 # 16:10
The code for selectTip is:
function selectTip(id){
$("#tipsParagraph").text(tips[id]);
}
and I've put a jsFiddle up here: http://jsfiddle.net/cd8G7/ although the 'result' panel is not showing what I actually see when I view the page in my browser- I get the canvas with all of the images displayed, and the paragraph underneath the canvas shows the text from the first element of my 'tips' array.
Edit 23/01/2013 # 13:50
Here's my isClickOnImage function:
function isClickOnImage(event){
var clickX = event.clientX;
var clickY = event.clientY;
//var imageCheckIteration = 0;
while(imageCheckIteration < sources.length){
if((clickX > sources[imageCheckIteration].x && clickX < sources[imageCheckIteration].x + imageWidth) &&
(clickY > sources[imageCheckIteration].y && clickY < sources[imageCheckIteration].y + imageHeight)){
/*This is where I need to print the variable that holds the text I want to display, but I need to display its contents
outside the canvas, in the <p></p> tags below. */
console.log("Click on image detected");
document.getElementById("tipsParagraph").innerHTML = sources[imageCheckIteration].data-tip /*tips[imageCheckIteration]*/;
} else {
document.getElementById("tipsParagraph").innerHTML = "";
}
}
}
What I intended that this function do is, capture the X & Y coordinates of any click on the canvas, and store them in the variables "clickX" and "clickY". Then, I have a variable called "imageCheckIteration" that has been initialised to 0, and while this variable is less than the length of my "sources" array (which is the array where all of the images have been stored), the function should check whether the click was on an area of the canvas that is covered by one of the images in the array.
If it was, then a console log should display the message "click on image detected", and the line
document.getElementById("tipsParagraph").innerHTML = sources[imageCheckIteration].data-tip;
should set the value of the "tipsParagraph" to be the value of the 'data-tip' attribute of whichever image is at the 'imageCheckIteration' position of the 'sources' array. If the click was detected on an area of the canvas that does not have an image displayed, then the value of the "tipsParagraph" should be set to hold nothing.
However, for some reason, when I view the page in the browser, the 'tipsParagraph' displays the text "This is where the text will be displayed", which is its default value, so that's fine. But, when I click on an image, or click anywhere else on the canvas, the text displayed in the 'tipsParagraph' is not updated.
I can't figure out why this is- can someone point me in the right direction? Does it mean that my isClickOnImage(event) function is never being called?
I simplified the way you are getting a reference to an image through the canvas. The trick here is to swap the z-index of the canvas and the image container and grab the reference to the image on the mouse up event. I don't know of a clean way to get elements behind a canvas, hence the workaround.
$('canvas').bind('mousedown', function(e) {
$('section').css('z-index', 4);
});
$('img').bind('mouseup', function(e) {
$('#tipsParagraph').text($(this).attr('id') + ":" + $(this).attr('alt'));
$('section').css('z-index', 2);
});
The second portion here is grabbing some attributes from the image itself and updating the text inside your div.
You can see more of the solution here.
Here is the idea of what I hope final product would do. Say I have two inputs:
<form id="builder"/>
<input type="text" name="background_color" />
<input type="text" name="border_color" />
</form>
Let's say user inputs this Hex color code in "background_color" input: #FF8CA9 (Light pink color). I want to automatically add darker tone of this color Hex code in "border_color" input: say #D63E64 (dark pink color).
So is it possible to 1) Find darker tone of color that was entered in input "background_color" and generate Hex color code of it and 2) automatically place it in input "border_color" without page refresh?
I'm not sure how to explain this in other way, I know it might sound confusing, but if you don't understand some parts please ask me.
Additional info: if this needs to use sort of javascript, a jQuery solution is preferable.
I digged up an old piece of code which does exactly what you want. It might need some tuning.
Run it like this:
add_to_color('#996600', 10);
It will add 10 to every r g and b value. This will make the color lighter. To make it darker use -10.
function add_to_color(hex, addDec)
{
hex = hex.replace('#', '');
rDec = Hex2Dec(hex.substr(0,2));
gDec = Hex2Dec(hex.substr(2,2));
bDec = Hex2Dec(hex.substr(4,2));
if( rDec < -addDec || gDec < -addDec || bDec < -addDec )
{
return '#'+hex;
}
rDec = rDec + addDec;
gDec = gDec + addDec;
bDec = bDec + addDec;
hex = "#"+ Dec2Hex(rDec)+Dec2Hex(gDec)+Dec2Hex(bDec);
return hex;
}
function Hex2Dec(HexVal)
{
HexVal=HexVal.toUpperCase();
var DecVal=0;
var HV1=HexVal.substring(0,1);
DecVal=(HexChars.indexOf(HV1)*16);
HV1=HexVal.substring(1);
DecVal+=HexChars.indexOf(HV1);
return DecVal;
}
// Created by T.N.W.Hynes - (c) 2002 PalandorZone.com ... Use it freely but leave this line intact
// Conversion function for Decimal to Hexadecimal - 255 max
function Dec2Hex(DecVal)
{
DecVal=parseInt(DecVal);
if (DecVal > 255 || DecVal < 0)
{
DecVal=255;
}
var Dig1 = DecVal % 16;
var Dig2 = (DecVal-Dig1) / 16;
var HexVal = HexChars.charAt(Dig2)+HexChars.charAt(Dig1);
return HexVal;
}
You can get brighter or darker color by simply multiplying its RGB values by some value (or converting to HSV and modifying V).
See this question and answers.