Adobe Extend Script Toolkit - UnitValue changes in final IF statement? - javascript

this is my first post on here. I am a beginner at java and extendscript.
I am using this script to find black pixels in a 100x100 grid which it scans line by line. X followed by Y increment.
But when it gets to the end of the first line of 10 it loses the x px unit value, then it loses the y px unit value. it still works but I am wondering if they are numbers or unit values..?
#target photoshop
app.bringToFront();
app.preferences.rulerUnits = Units.PIXELS;
//Removes All Color Samplers
app.activeDocument.colorSamplers.removeAll();
// Show Color Sampler/ I don't know how to do this with extend script - generated in console with script listner
// =======================================================
var idShw = charIDToTypeID( "Shw " );
var desc80 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var list40 = new ActionList();
var ref68 = new ActionReference();
var idLyr = charIDToTypeID( "Lyr " );
var idBckg = charIDToTypeID( "Bckg" );
ref68.putProperty( idLyr, idBckg );
list40.putReference( ref68 );
desc80.putList( idnull, list40 );
executeAction( idShw, desc80, DialogModes.NO );
// =======================================================
//Gets Document Width & Height
var docWidth = activeDocument.width.as('px');
var docHeight = activeDocument.height.as('px');
//Default x Position - as a Unit Value in pixels needed for sampler position
var xPosition = new UnitValue( 0, 'px' );
var yPosition = new UnitValue( 0, 'px');
//End point Var - NOT USED
//var xEndPoint = activeDocument.width.as('px');
//var yEndPoint = activeDocument.height.as('px');
//Comparison Color Variables [Kind of like a crude memory, add more for more comparison, or recognized perception]
var WhiteColor = new RGBColor(); // initial default colour
WhiteColor.red = 255; // rest of the values default to 0
WhiteColor.green = 255;
WhiteColor.blue = 255
var BlackColor = new RGBColor(); // initial default colour
BlackColor.red = 0; // rest of the values default to 0
BlackColor.green = 0;
BlackColor.blue = 0;
//======================================================================
while( xPosition < docWidth &&
yPosition < docHeight){ // If the position of the sampler x is less than the doc width x Height - Stops at end of Doc
//Adds a color sampler
var sample = activeDocument.colorSamplers.add( [ xPosition + .5 , yPosition + .5 ] ); // X, Y position, Y position is set at 0 does not change**
app.backgroundColor=sample.color; //Sets thte background color to the sample color, maybe their could be a better way to store this
var colorB = new RGBColor(); // New color var to store background color
colorB = app.backgroundColor; //Assigns the background color to previous declare var
//Nested IF else statements
//Notes* Alerts Sampled Color - need to store this info, I don't know if I should store this as a RGB value?
// White Pixel detect
if (colorB.rgb.red === WhiteColor.red &&
colorB.rgb.blue === WhiteColor.blue &&
colorB.rgb.green === WhiteColor.green)
{
//alert("White Pixel");
}
// Black Pixel detect
else if (colorB.rgb.red === BlackColor.red &&
colorB.rgb.blue === BlackColor.blue &&
colorB.rgb.green === BlackColor.green)
{
alert("Black Pixel " + "x : " + xPosition + " y : " + yPosition + '\n'
+ "R = " + (colorB.rgb.red) + '\n'
+ "G = "+ (colorB.rgb.green) + '\n'
+ "B = "+ (colorB.rgb.blue));
}
// Other Pixel detect - Error option
else {
alert("Other Color");
}
//Final process
app.activeDocument.colorSamplers.removeAll(); //Removes the sampler so that there arn't to many
xPosition++; //increases the position by one, this is the end of the loop, starts again until while statement is void
//Once it arrives at the end of the line, increase y -value & reset x position
if (xPosition == docWidth) {
xPosition = xPosition - docWidth;
yPosition++;
}
} //bottom bracket of while statement
//======================================================================

Is x && y = z valid javascript?
When running the below code in ESTK it returns undefined.
var x,y;
// x = y = 12; // returns 12
x && y = 13; // returns undefined
$.writeln(x);
It gets even stranger when I comment out the third line and comment in the second. The result is then:
12
After that comment out the second and comment in the third again and the result still is:
12
This stays until I restart ESTK.
Anyway. I never saw a expression like x && y = z in JS. This could be your error.

Related

Photoshop script to crop into square based on active selection

I’m trying to find Java script for photoshop, that will crop to a square based on selection, preserving resolution of the image
Selections are irregular
This is very easy and a perfectly valid question. PhotoShop can crop to selection with a function.
cropToSelection();
// crop to selection
function cropToSelection()
{
executeAction( charIDToTypeID( "Crop" ), new ActionDescriptor(), DialogModes.NO );
}
But if you want the selection bounds coordinates then use:
var lb = get_selection_bounds();
// alerts current selection bounds
alert(lb[0] + ", " + lb[1] + ", " + lb[2] + ", " + lb[3] );
// function GET SELECTION BOUNDS ()
// ----------------------------------------------------------------
function get_selection_bounds()
{
// Only works with a selection
var x = parseFloat(app.activeDocument.selection.bounds[0]);
var y = parseFloat(app.activeDocument.selection.bounds[1]);
var x1 = parseFloat(app.activeDocument.selection.bounds[2]);
var y1 = parseFloat(app.activeDocument.selection.bounds[3]);
// return the results as an array
return [x, y, x1, y1];
}

How to rotate images (360 degree with up and down) in javascript

I try to create a images rotate 360 degree in javascript which is working with left to right perfectly but when I try to move it with bottom to top and top to bottom then it didn't work perfectly I want to create such a demo which show in example
http://www.ajax-zoom.com/examples/example28_clean.php
e(f).mousemove(function(e)
{
if (s == true) dx(e.pageX - this.offsetLeft,e.pageY - this.offsetTop);
else o = e.pageX - this.offsetLeft; f = e.pageY- this.offsetTop;
});
function dx(t,q) {
console.log("t.....x. px.."+t+" -"+ px +"-----q---------y------"+q);
if(f - q > 0.1)
{
f = q;
a="left-top/";
i=43;
r = --r < 1 ? i : r;
e(u).css("background-image", "url(" + a + r + "." + c + ")")
//r = --r < 1 ? i : r;
// e(u).css("background-image", "url(" + a + 73 + "." + c + ")")
}else if (f - q < -0.1) {
f = q;
a="left-top/";
i=43;
r = ++r > i ? 1 : r;
e(u).css("background-image", "url(" + a + r + "." + c + ")")
}
if (o - t > 0.1) {
o = t;
r = --r < 1 ? i : r;
e(u).css("background-image", "url(" + a + r + "." + c + ")")
} else if (o - t < -0.1) {
o = t;
r = ++r > i ? 1 : r;
e(u).css("background-image", "url(" + a + r + "." + c + ")")
}
}
Where : a is path of images folder, r is number of images(1,2,3,4....) and c is .png file
But it is not working perfectly so can Anyone help me...
I think u r pointing out the glitchy movement... U just have to add more images with more perspective
This is one way of doing it by creating a function that converts a view into a Image url. The view has the raw viewing angles and knows nothing about the image URL format or limits. The function createImageURL converts the view to the image URL and applies limits to the view if needed.
An animation function uses the mouse movement to update the view which then calls the URL function to get the current URL. I leave it to you to do the preloading, T
So first Create the vars to hold the current view
const view = {
rotUp : 0,
rotLeftRigh : 0,
speedX : 0.1, // converts from pixels to deg. can reverse with neg val
speedY : 0.1, // converts from pixels to deg
};
Create a function that will take the deg rotate (left right) and the deg rotate up (down) and convert it to the correct image URL.
// returns the url for the image to fit view
function createImageURL(view){
var rotate = view.rotLeftRight;
var rotateUp = view.rotUp;
const rSteps = 24; // number of rotate images
const rStepStringWidth = 3; // width of rotate image index
const upStep = 5; // deg step of rotate up
const maxUp = 90; // max up angle
const minUp = 0; // min up angle
const rotateUpToken = "#UP#"; // token to replace in URL for rotate up
const rotateToken = "#ROT#"; // token to replace in URL for rotate
// URL has token (above two lines) that will be replaced by this function
const url = "http://www.ajax-zoom.com/pic/zoomthumb/N/i/Nike_Air_#UP#_#ROT#_720x480.jpg";
// make rotate fit 0-360 range
rotate = ((rotate % 360) + 360) % 360);
rotate /= 360; // normalize
rotate *= rSteps; // adjust for number of rotation images.
rotate = Math.floor(rotate); // round off value
rotate += 1; // adjust for start index
rotate = "" + rotate; // convert to string
// pad with leading zeros
while(rotate.length < rStepStringWidth) {rotate = "0" + rotate }
// set min max of rotate up;
rotateUp = rotateUp < upMin ? upMin : rotateUp > upMax ? upMax : rotateUp;
view.rotUp = rotateUp; // need to set the view or the movement will
// get stuck at top or bottom
// move rotate up to the nearest valid value
rotateUp = Math.round(rotateUp / upStep) * upStep;
// set min max of rotate again as the rounding may bring it outside
// the min max range;
rotateUp = rotateUp < upMin ? upMin : rotateUp > upMax ? upMax : rotateUp;
url = url.replace(rotateUpToken,rotateUP);
url = url.replace(rotateToken,rotate);
return url;
}
Then in the mouse event you capture the movement of the mouse.
const mouse = {x : 0, y : 0, dx : 0, dy : 0, button : false}
function mouseEvents(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
// as we dont process the mouse events here the movements must be cumulative
mouse.dx += e.movementX;
mouse.dY += e.movementY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
And then finally the animation function.
function update(){
// if there is movement
if(mouse.dx !== 0 || mouse.dy !== 0){
view.rotUp += mouse.dy * view.speedY;
view.rotLeftRight += mouse.dx * view.speedX;
mouse.dx = mouse.dy = 0;
// get the URL
const url = createImageURL(view);
// use that to load or find the image and then display
// it if loaded.
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
he createImageURL could also be used to create a referance to an image in an object.
const URLPart = "http://www.ajax-zoom.com/pic/zoomthumb/N/i/Nike_Air_"
const allImages = {
I_90_001 : (()=>{const i=new Image; i.src=URLPart+"_90_001_720x480.jpg"; return i;})(),
I_90_002 : (()=>{const i=new Image; i.src=URLPart+"_90_002_720x480.jpg"; return i;})(),
I_90_003 : (()=>{const i=new Image; i.src=URLPart+"_90_003_720x480.jpg"; return i;})(),
... and so on Or better yet automate it.
And in the createImageURL use the URL to get the property name for allImages
replacing
const url = "http://www.ajax-zoom.com/pic/zoomthumb/N/i/Nike_Air_#UP#_#ROT#_720x480.jpg";
with
const url = "I_#UP#_#ROT#";
then you can get the image
const currentImage = allImages[createImageURL(view)];
if(currentImage.complete){ // if loaded then
ctx.drawImage(currentImage,0,0); // draw it
}

Algorithm - locating enough space to draw a rectangle given the x and y axis of all other rectangles

Every rectangle has x and y coordinates, width and height.
The total width of the screen is maxWidth and total height is maxHeight.
I have an array containing all the already drawn rectangles.
I am working on an web App where users will be drawing rectangles on the screen using their mouse. For that I am using Javascript to draw on the canvas element.
The challenge is that the rectangles must not intersect at any given point.
I am trying to avoid this kind of case:
or this:
This is how the output I am aiming for should look like:
What I basically need is an Algorithm (preferably in JavaScript) that can help locating enough space to draw a rectangle knowing its axis, height and width.
BM67 Box packing.
This is a method I use to pack rectangles. I made it up myself to create sprite sheets.
How it works.
You maintain two arrays, one holds rectangles of available spaces (space array), and the other rectangles you have placed.
You start by adding to the space array a rectangle that covers the whole area to be filled. This rectangle represents available space.
When you add a rectangle to fit you search the available space rectangles for a rectangle that will fit the new rectangle. If you can not find a rectangle that is bigger or sane size as the one you want to add there is no room.
Once you have found a place to put the rectangle, check all the available space rectangles to see if any of them overlap the new added rectangle. If any overlap you slice it up along the top, bottom, left and right, resulting in up to 4 new space rectangles. There are some optimisation when you do this to keep the number of rectangles down but it will work without the optimisations.
It's not that complicated and reasonably efficient compared to some other methods. It is particularly good when the space starts to run low.
Example
Below is a demo of it filling the canvas with random rectangles. It's on a animation loop to show the process, so is very much slowed down.
Gray boxes are the ones to fit. Red show the current spacer boxes. Each box has a 2 pixel margin. See top of code for demo constants.
Click the canvas to restart.
const boxes = []; // added boxes
const spaceBoxes = []; // free space boxes
const space = 2; // space between boxes
const minW = 4; // min width and height of boxes
const minH = 4;
const maxS = 50; // max width and height
// Demo only
const addCount = 2; // number to add per render cycle
const ctx = canvas.getContext("2d");
canvas.width = canvas.height = 1024;
// create a random integer
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
// itterates an array
const eachOf = (array, cb) => { var i = 0; const len = array.length; while (i < len && cb(array[i], i++, len) !== true ); };
// resets boxes
function start(){
boxes.length = 0;
spaceBoxes.length = 0;
spaceBoxes.push({
x : space, y : space,
w : canvas.width - space * 2,
h : canvas.height - space * 2,
});
}
// creates a random box without a position
function createBox(){
return { w : randI(minW,maxS), h : randI(minH,maxS) }
}
// cuts box to make space for cutter (cutter is a box)
function cutBox(box,cutter){
var b = [];
// cut left
if(cutter.x - box.x - space > minW){
b.push({
x : box.x, y : box.y,
w : cutter.x - box.x - space,
h : box.h,
})
}
// cut top
if(cutter.y - box.y - space > minH){
b.push({
x : box.x, y : box.y,
w : box.w,
h : cutter.y - box.y - space,
})
}
// cut right
if((box.x + box.w) - (cutter.x + cutter.w + space) > space + minW){
b.push({
x : cutter.x + cutter.w + space,
y : box.y,
w : (box.x + box.w) - (cutter.x + cutter.w + space),
h : box.h,
})
}
// cut bottom
if((box.y + box.h) - (cutter.y + cutter.h + space) > space + minH){
b.push({
x : box.x,
y : cutter.y + cutter.h + space,
w : box.w,
h : (box.y + box.h) - (cutter.y + cutter.h + space),
})
}
return b;
}
// get the index of the spacer box that is closest in size to box
function findBestFitBox(box){
var smallest = Infinity;
var boxFound;
eachOf(spaceBoxes,(sbox,index)=>{
if(sbox.w >= box.w && sbox.h >= box.h){
var area = sbox.w * sbox.h;
if(area < smallest){
smallest = area;
boxFound = index;
}
}
})
return boxFound;
}
// returns an array of boxes that are touching box
// removes the boxes from the spacer array
function getTouching(box){
var b = [];
for(var i = 0; i < spaceBoxes.length; i++){
var sbox = spaceBoxes[i];
if(!(sbox.x > box.x + box.w + space || sbox.x + sbox.w < box.x - space ||
sbox.y > box.y + box.h + space || sbox.y + sbox.h < box.y - space )){
b.push(spaceBoxes.splice(i--,1)[0])
}
}
return b;
}
// Adds a space box to the spacer array.
// Check if it is insid, too small, or can be joined to another befor adding.
// will not add if not needed.
function addSpacerBox(box){
var dontAdd = false;
// is to small?
if(box.w < minW || box.h < minH){ return }
// is same or inside another
eachOf(spaceBoxes,sbox=>{
if(box.x >= sbox.x && box.x + box.w <= sbox.x + sbox.w &&
box.y >= sbox.y && box.y + box.h <= sbox.y + sbox.h ){
dontAdd = true;
return true;
}
})
if(!dontAdd){
var join = false;
// check if it can be joinded with another
eachOf(spaceBoxes,sbox=>{
if(box.x === sbox.x && box.w === sbox.w &&
!(box.y > sbox.y + sbox.h || box.y + box.h < sbox.y)){
join = true;
var y = Math.min(sbox.y,box.y);
var h = Math.max(sbox.y + sbox.h,box.y + box.h);
sbox.y = y;
sbox.h = h-y;
return true;
}
if(box.y === sbox.y && box.h === sbox.h &&
!(box.x > sbox.x + sbox.w || box.x + box.w < sbox.x)){
join = true;
var x = Math.min(sbox.x,box.x);
var w = Math.max(sbox.x + sbox.w,box.x + box.w);
sbox.x = x;
sbox.w = w-x;
return true;
}
})
if(!join){ spaceBoxes.push(box) }// add to spacer array
}
}
// Adds a box by finding a space to fit.
function locateSpace(box){
if(boxes.length === 0){ // first box can go in top left
box.x = space;
box.y = space;
boxes.push(box);
var sb = spaceBoxes.pop();
spaceBoxes.push(...cutBox(sb,box));
}else{
var bf = findBestFitBox(box); // get the best fit space
if(bf !== undefined){
var sb = spaceBoxes.splice(bf,1)[0]; // remove the best fit spacer
box.x = sb.x; // use it to position the box
box.y = sb.y;
spaceBoxes.push(...cutBox(sb,box)); // slice the spacer box and add slices back to spacer array
boxes.push(box); // add the box
var tb = getTouching(box); // find all touching spacer boxes
while(tb.length > 0){ // and slice them if needed
eachOf(cutBox(tb.pop(),box),b => addSpacerBox(b));
}
}
}
}
// draws a box array
function drawBoxes(list,col,col1){
eachOf(list,box=>{
if(col1){
ctx.fillStyle = col1;
ctx.fillRect(box.x+ 1,box.y+1,box.w-2,box.h - 2);
}
ctx.fillStyle = col;
ctx.fillRect(box.x,box.y,box.w,1);
ctx.fillRect(box.x,box.y,1,box.h);
ctx.fillRect(box.x+box.w-1,box.y,1,box.h);
ctx.fillRect(box.x,box.y+ box.h-1,box.w,1);
})
}
// Show the process in action
ctx.clearRect(0,0,canvas.width,canvas.height);
var count = 0;
var handle = setTimeout(doIt,10);
start()
function doIt(){
ctx.clearRect(0,0,canvas.width,canvas.height);
for(var i = 0; i < addCount; i++){
var box = createBox();
locateSpace(box);
}
drawBoxes(boxes,"black","#CCC");
drawBoxes(spaceBoxes,"red");
if(count < 1214 && spaceBoxes.length > 0){
count += 1;
handle = setTimeout(doIt,10);
}
}
canvas.onclick = function(){
clearTimeout(handle);
start();
handle = setTimeout(doIt,10);
count = 0;
}
canvas { border : 2px solid black; }
<canvas id="canvas"></canvas>
Update
Improving on the above algorithm.
Turned algorithm into an object
Improved speed by finding better fitting spacer via weighting the fit on the aspect ratio
Added placeBox(box) function that adds a box without checking if it fits. It will be placed at its box.x, box.y coordinates
See code example below on usage.
Example
The example is the same as the above example but have added randomly place boxes before fitting boxes.
Demo displays the boxes and spacer boxes as it goes to show how it works. Click the canvas to restart. Hold [shift] key and click canvas to restart without displaying intermediate results.
Pre placed boxes are blue.
Fitted boxes are gray.
Spacing boxes are red and will overlap.
When holding shift the fitting process is stopped at the first box tat does not fit. The red boxes will show area that are available but unused.
When showing progress the function will keep adding boxes ignoring non fitting boxes until out of room.
const minW = 4; // min width and height of boxes
const minH = 4;
const maxS = 50; // max width and height
const space = 2;
const numberBoxesToPlace = 20; // number of boxes to place befor fitting
const fixedBoxColor = "blue";
// Demo only
const addCount = 2; // number to add per render cycle
const ctx = canvas.getContext("2d");
canvas.width = canvas.height = 1024;
// create a random integer randI(n) return random val 0-n randI(n,m) returns random int n-m, and iterator that can break
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const eachOf = (array, cb) => { var i = 0; const len = array.length; while (i < len && cb(array[i], i++, len) !== true ); };
// creates a random box. If place is true the box also gets a x,y position and is flaged as fixed
function createBox(place){
if(place){
const box = {
w : randI(minW*4,maxS*4),
h : randI(minH*4,maxS*4),
fixed : true,
}
box.x = randI(space, canvas.width - box.w - space * 2);
box.y = randI(space, canvas.height - box.h - space * 2);
return box;
}
return {
w : randI(minW,maxS),
h : randI(minH,maxS),
}
}
//======================================================================
// BoxArea object using BM67 box packing algorithum
// https://stackoverflow.com/questions/45681299/algorithm-locating-enough-space-to-draw-a-rectangle-given-the-x-and-y-axis-of
// Please leave this and the above two lines with any copies of this code.
//======================================================================
//
// usage
// var area = new BoxArea({
// x: ?, // x,y,width height of area
// y: ?,
// width: ?,
// height : ?.
// space : ?, // optional default = 1 sets the spacing between boxes
// minW : ?, // optional default = 0 sets the in width of expected box. Note this is for optimisation you can add smaller but it may fail
// minH : ?, // optional default = 0 sets the in height of expected box. Note this is for optimisation you can add smaller but it may fail
// });
//
// Add a box at a location. Not checked for fit or overlap
// area.placeBox({x : 100, y : 100, w ; 100, h :100});
//
// Tries to fit a box. If the box does not fit returns false
// if(area.fitBox({x : 100, y : 100, w ; 100, h :100})){ // box added
//
// Resets the BoxArea removing all boxes
// area.reset()
//
// To check if the area is full
// area.isFull(); // returns true if there is no room of any more boxes.
//
// You can check if a box can fit at a specific location with
// area.isBoxTouching({x : 100, y : 100, w ; 100, h :100}, area.boxes)){ // box is touching another box
//
// To get a list of spacer boxes. Note this is a copy of the array, changing it will not effect the functionality of BoxArea
// const spacerArray = area.getSpacers();
//
// Use it to get the max min box size that will fit
//
// const maxWidthThatFits = spacerArray.sort((a,b) => b.w - a.w)[0];
// const minHeightThatFits = spacerArray.sort((a,b) => a.h - b.h)[0];
// const minAreaThatFits = spacerArray.sort((a,b) => (a.w * a.h) - (b.w * b.h))[0];
//
// The following properties are available
// area.boxes // an array of boxes that have been added
// x,y,width,height // the area that boxes are fitted to
const BoxArea = (()=>{
const defaultSettings = {
minW : 0, // min expected size of a box
minH : 0,
space : 1, // spacing between boxes
};
const eachOf = (array, cb) => { var i = 0; const len = array.length; while (i < len && cb(array[i], i++, len) !== true ); };
function BoxArea(settings){
settings = Object.assign({},defaultSettings,settings);
this.width = settings.width;
this.height = settings.height;
this.x = settings.x;
this.y = settings.y;
const space = settings.space;
const minW = settings.minW;
const minH = settings.minH;
const boxes = []; // added boxes
const spaceBoxes = [];
this.boxes = boxes;
// cuts box to make space for cutter (cutter is a box)
function cutBox(box,cutter){
var b = [];
// cut left
if(cutter.x - box.x - space >= minW){
b.push({
x : box.x, y : box.y, h : box.h,
w : cutter.x - box.x - space,
});
}
// cut top
if(cutter.y - box.y - space >= minH){
b.push({
x : box.x, y : box.y, w : box.w,
h : cutter.y - box.y - space,
});
}
// cut right
if((box.x + box.w) - (cutter.x + cutter.w + space) >= space + minW){
b.push({
y : box.y, h : box.h,
x : cutter.x + cutter.w + space,
w : (box.x + box.w) - (cutter.x + cutter.w + space),
});
}
// cut bottom
if((box.y + box.h) - (cutter.y + cutter.h + space) >= space + minH){
b.push({
w : box.w, x : box.x,
y : cutter.y + cutter.h + space,
h : (box.y + box.h) - (cutter.y + cutter.h + space),
});
}
return b;
}
// get the index of the spacer box that is closest in size and aspect to box
function findBestFitBox(box, array = spaceBoxes){
var smallest = Infinity;
var boxFound;
var aspect = box.w / box.h;
eachOf(array, (sbox, index) => {
if(sbox.w >= box.w && sbox.h >= box.h){
var area = ( sbox.w * sbox.h) * (1 + Math.abs(aspect - (sbox.w / sbox.h)));
if(area < smallest){
smallest = area;
boxFound = index;
}
}
})
return boxFound;
}
// Exposed helper function
// returns true if box is touching any boxes in array
// else return false
this.isBoxTouching = function(box, array = []){
for(var i = 0; i < array.length; i++){
var sbox = array[i];
if(!(sbox.x > box.x + box.w + space || sbox.x + sbox.w < box.x - space ||
sbox.y > box.y + box.h + space || sbox.y + sbox.h < box.y - space )){
return true;
}
}
return false;
}
// returns an array of boxes that are touching box
// removes the boxes from the array
function getTouching(box, array = spaceBoxes){
var boxes = [];
for(var i = 0; i < array.length; i++){
var sbox = array[i];
if(!(sbox.x > box.x + box.w + space || sbox.x + sbox.w < box.x - space ||
sbox.y > box.y + box.h + space || sbox.y + sbox.h < box.y - space )){
boxes.push(array.splice(i--,1)[0])
}
}
return boxes;
}
// Adds a space box to the spacer array.
// Check if it is inside, too small, or can be joined to another befor adding.
// will not add if not needed.
function addSpacerBox(box, array = spaceBoxes){
var dontAdd = false;
// is box to0 small?
if(box.w < minW || box.h < minH){ return }
// is box same or inside another box
eachOf(array, sbox => {
if(box.x >= sbox.x && box.x + box.w <= sbox.x + sbox.w &&
box.y >= sbox.y && box.y + box.h <= sbox.y + sbox.h ){
dontAdd = true;
return true; // exits eachOf (like a break statement);
}
})
if(!dontAdd){
var join = false;
// check if it can be joined with another
eachOf(array, sbox => {
if(box.x === sbox.x && box.w === sbox.w &&
!(box.y > sbox.y + sbox.h || box.y + box.h < sbox.y)){
join = true;
var y = Math.min(sbox.y,box.y);
var h = Math.max(sbox.y + sbox.h,box.y + box.h);
sbox.y = y;
sbox.h = h-y;
return true; // exits eachOf (like a break statement);
}
if(box.y === sbox.y && box.h === sbox.h &&
!(box.x > sbox.x + sbox.w || box.x + box.w < sbox.x)){
join = true;
var x = Math.min(sbox.x,box.x);
var w = Math.max(sbox.x + sbox.w,box.x + box.w);
sbox.x = x;
sbox.w = w-x;
return true; // exits eachOf (like a break statement);
}
})
if(!join){ array.push(box) }// add to spacer array
}
}
// Adds a box by finding a space to fit.
// returns true if the box has been added
// returns false if there was no room.
this.fitBox = function(box){
if(boxes.length === 0){ // first box can go in top left
box.x = space;
box.y = space;
boxes.push(box);
var sb = spaceBoxes.pop();
spaceBoxes.push(...cutBox(sb,box));
}else{
var bf = findBestFitBox(box); // get the best fit space
if(bf !== undefined){
var sb = spaceBoxes.splice(bf,1)[0]; // remove the best fit spacer
box.x = sb.x; // use it to position the box
box.y = sb.y;
spaceBoxes.push(...cutBox(sb,box)); // slice the spacer box and add slices back to spacer array
boxes.push(box); // add the box
var tb = getTouching(box); // find all touching spacer boxes
while(tb.length > 0){ // and slice them if needed
eachOf(cutBox(tb.pop(),box),b => addSpacerBox(b));
}
} else {
return false;
}
}
return true;
}
// Adds a box at location box.x, box.y
// does not check if it can fit or for overlap.
this.placeBox = function(box){
boxes.push(box); // add the box
var tb = getTouching(box); // find all touching spacer boxes
while(tb.length > 0){ // and slice them if needed
eachOf(cutBox(tb.pop(),box),b => addSpacerBox(b));
}
}
// returns a copy of the spacer array
this.getSpacers = function(){
return [...spaceBoxes];
}
this.isFull = function(){
return spaceBoxes.length === 0;
}
// resets boxes
this.reset = function(){
boxes.length = 0;
spaceBoxes.length = 0;
spaceBoxes.push({
x : this.x + space, y : this.y + space,
w : this.width - space * 2,
h : this.height - space * 2,
});
}
this.reset();
}
return BoxArea;
})();
// draws a box array
function drawBoxes(list,col,col1){
eachOf(list,box=>{
if(col1){
ctx.fillStyle = box.fixed ? fixedBoxColor : col1;
ctx.fillRect(box.x+ 1,box.y+1,box.w-2,box.h - 2);
}
ctx.fillStyle = col;
ctx.fillRect(box.x,box.y,box.w,1);
ctx.fillRect(box.x,box.y,1,box.h);
ctx.fillRect(box.x+box.w-1,box.y,1,box.h);
ctx.fillRect(box.x,box.y+ box.h-1,box.w,1);
})
}
// Show the process in action
ctx.clearRect(0,0,canvas.width,canvas.height);
var count = 0;
var failedCount = 0;
var timeoutHandle;
var addQuick = false;
// create a new box area
const area = new BoxArea({x : 0, y : 0, width : canvas.width, height : canvas.height, space : space, minW : minW, minH : minH});
// fit boxes until a box cant fit or count over count limit
function doIt(){
ctx.clearRect(0,0,canvas.width,canvas.height);
if(addQuick){
while(area.fitBox(createBox()));
count = 2000;
}else{
for(var i = 0; i < addCount; i++){
if(!area.fitBox(createBox())){
failedCount += 1;
break;
}
}
}
drawBoxes(area.boxes,"black","#CCC");
drawBoxes(area.getSpacers(),"red");
if(count < 5214 && !area.isFull()){
count += 1;
timeoutHandle = setTimeout(doIt,10);
}
}
// resets the area places some fixed boxes and starts the fitting cycle.
function start(event){
clearTimeout(timeoutHandle);
area.reset();
failedCount = 0;
for(var i = 0; i < numberBoxesToPlace; i++){
var box = createBox(true); // create a fixed box
if(!area.isBoxTouching(box,area.boxes)){
area.placeBox(box);
}
}
if(event && event.shiftKey){
addQuick = true;
}else{
addQuick = false;
}
timeoutHandle = setTimeout(doIt,10);
count = 0;
}
canvas.onclick = start;
start();
body {font-family : arial;}
canvas { border : 2px solid black; }
.info {position: absolute; z-index : 200; top : 16px; left : 16px; background : rgba(255,255,255,0.75);}
<div class="info">Click canvas to reset. Shift click to add without showing progress.</div>
<canvas id="canvas"></canvas>
Try the following:
iterate through the existing rectangles from top to bottom, based on the top boundary of each existing rectangle
while proceeding in top-to-bottom order, maintain a list of "active rectangles":
adding each succeeding rectangle based on its top boundary as an active rectangle, and
removing active rectangles based on their bottom boundary
(you can do this efficiently by using a priority queue)
also keep track of the gaps between active rectangles:
adding an active rectangle will end all gaps that overlap it, and (assuming it doesn't overlap any existing rectangles) start a new gap on each side
removing an active rectangle will add a new gap (without ending any)
note that multiple active gaps may overlap each other -- you can't count on having exactly one gap between active rectangles!
Check your new rectangle (the one you want to place) against all gaps. Each gap is itself a rectangle; you can place your new rectangle if it fits entirely inside some gap.
This kind of method is called a sweep-line algorithm.
You may have to check whether your current point is inside the area of any of the current rectangles. You can use the following code to test that (stolen from here)
In the array you are having, store the rectangle details in the following way
var point = {x: 1, y: 2};
var rectangle = {x1: 0, x2: 10, y1: 1, y2: 7};
Following will be your function to test whether any given point is inside any given rectangle.
function isPointInsideRectangle(p, r) {
return
p.x > r.x1 &&
p.x < r.x2 &&
p.y > r.y1 &&
p.y < r.y2;
}
I am not sure how you are going to implement this -
On mouse down
Always during drawing (This may be too much of a work).
On mouse up (this will be my preference. You can cancel the drawing if the test did not pass, with possible explanation for the user somewhere in the canvas)
Hope this will get you starting.

How to interact with shapes in Pixijs?

I am trying to get the id of the shape my mouse is currently hovering over.
my shapes are in a container
// creating the layers
gridLayer = new PIXI.DisplayObjectContainer ();
gridLayer.setInteractive(true);
stage.addChild(gridLayer);
and i am creating each shape like this;
function drawHexagon(x,y, size, gap,scale, color, iterI, iterJ, type) {
var shape = new PIXI.Graphics();
// set a fill and line style
shape.beginFill(color);
shape.lineStyle(1, 0xa0a0a0, 1);
size = size-gap;
for (i = 0; i < 7; i++) {
angle = 2 * Math.PI / 6 * (i + 0.5);
var x_i = x + size * Math.cos(angle);
var y_i = y + size * Math.sin(angle);
if (i === 0) {
shape.moveTo(x_i, scale *y_i)
}
else {
shape.lineTo(x_i, scale * y_i)
}
};
shape.endFill();
// calculate and save the axial coordinates
var cX = iterJ - (iterI - (iterI&1)) / 2;
var cZ = iterI;
var cY = -1*(cX+cZ);
shape.hexId = cX + "x" + cY + "y" + cZ + "z";
shape.hexPosX = x;
shape.hexPosY = y;
shape.setInteractive(true);
shape.mouseover = function(mouseData){
console.log("MOUSE OVER " + shape.hexId);
}
shape.click = function(mouseData){
console.log("MOUSE CLICK " + shape.hexId);
}
gridLayer.addChild(shape);
}
However, clicking on any shape or hovering over it is not showing me anything in the console. what am i doing wrong?
i have tried both
shape.setInteractive(true)
and
shape.interactive = true
but neither seems to work for me.
EDIT: i have added a jsfiddle. it doesnt works (i dont know how to link things in jsfiddle) but you can see my entire code in there.
http://jsfiddle.net/9aqHz/1/
For a PIXI.Graphics object to be interactive you need to set a hitArea shape (it can be a Rectangle, Circle or a Polygon):
shape.hitArea = new PIXI.Polygon([
new PIXI.Point(/* first point */),
new PIXI.Point(/* second point */),
new PIXI.Point(/* third point */),
new PIXI.Point(/* fourth point */),
new PIXI.Point(/* fifth point */)
]);
Another approach would be to generate a texture from the shape and use a Sprite, but the hit area would be the entire rectangular bounds of the hexagon:
var texture = shape.generateTexture();
var sprite = new PIXI.Sprite(texture);
sprite.setInteractive(true);
sprite.anchor.set(0.5, 0.5);
Fiddle with this applied to your example
#imcg I updated your code so it workes with Pixi 3.0.8
- sprite.setInteractive(true);
+ shape.interactive = true;
+ shape.buttonMode = true;
- sprite.setInteractive(true)
+ sprite.interactive = true;
+ sprite.buttonMode = true;
http://jsfiddle.net/LP2j8/56/
I will add a bit of info for anyone who is in the same boat i was in;
When you define a shape as a geom, you have to explicitly state a hitarea.
So adding the following code makes it work;
shape.hitArea = new PIXI.Polygon(vertices);
shape.interactive = true;
shape.click = function(mouseData){
console.log("MOUSE CLICK " + shape.hexId);
}
But, when you define a shape as a sprite/texture, you dont need to do this.
in cases of sprites, just setting shape.interactive = true for the sprite is sufficient. You dont need to set the interactive property for the parent object or the stage.

JavaScript Changing BG Color

I made a div with background-color set to rgb(0,0,0); and I want to change it's color on click with javascript. I made a function to do that.
function change(){
var x = 1;
var y = x + 100;
document.getElementById("box").style.backgroundColor = "rgb(" + y + "," + y + "," + y + ")"; }
It works fine but I can change the div's color just once. What I want to do is get div's color value and set it to x and run the function again. So the bg will go like black->grey->white on each click. Depending on the y variable.
I can get the div's value but it'll get it in "rgb(0,0,0);" format. I don't know what to do after getting this. How do I manipulate just integers in rgb(0,0,0); ?
You can store current x value in data attributes:
function change(box) {
var x = +box.getAttribute('data-x'), // +box.dataset.x for modern browsers
y = x + 100;
box.style.backgroundColor = "rgb(" + y + "," + y + "," + y + ")";
box.setAttribute('data-x', y);
}
HTML
<div id="box" onclick="change(this)"></div>
Demo: http://jsfiddle.net/Wuz75/
Instead of trying to analyse the color, since your colors will be static, just make an array of colors, and keep track of the index.
var colors = [
"rgb(0,0,0)",
"rgb(100,100,100)",
"rgb(255,255,255)"
],
c = 0;
Then in your function, use c to get the next color, and then increment, or reset to 0.
function change() {
document.getElementById("box").style.backgroundColor = colors[c];
c = ++c % colors.length;
}
So whenever you run the function, it'll switch between colors in the Array.
Or you can use :
function change(x) {
var el = document.getElementById('color');
var rgb = el.style.backgroundColor.replace(/rgb|\(|\)|\s/g, '').split(',');
if ( rgb == "" ) { rgb = [0,0,0] };
for (var a = 0; a < rgb.length; a++ ) {
rgb[a] = parseInt(rgb[a]) + x;
}
el.style.backgroundColor = 'rgb('+rgb.join(',')+')';
}
Here is a demo : http://jsbin.com/ozepaz/1/edit

Categories