Separate fill for each shape in Flex graphics - actionscript-3

I am drawing a bunch of circles in 2 horizontal rows across a sprite and would like a gradient applied to each circle individually but flex is applying it to the entire graphics area. What do I need to change to draw each circle with its own gradient?
var s:Sprite = new Sprite();
var g:Graphics = s.graphics;
g.beginBitmapFill(bd, new Matrix(), false, false);
g.drawRect(0, 0, s.width, s.height);
g.endFill();
var m:Matrix = new Matrix();
g.lineStyle(1, 0x888888, 1);
for(var i:int = 0; i < numSpirals; i++) {
g.beginGradientFill(GradientType.LINEAR, [0x666666, 0xFFFFFF], [1, 1], [0, 255], m);
xc = i * spiralWidth + spiralHoleRadius;
yc = bdCenter - (spiralBoxHeight / 2) + (spiralHoleRadius / 2);
g.drawCircle(xc, yc, spiralHoleRadius);
g.endFill();
}
for(i = 0; i < numSpirals; i++) {
g.beginGradientFill(GradientType.LINEAR, [0x666666, 0xFFFFFF], [1, 1], [0, 255], m);
xc = i * spiralWidth + spiralHoleRadius;
yc = bdCenter + (spiralBoxHeight / 2) - (spiralHoleRadius / 2);
g.drawCircle(xc, yc, spiralHoleRadius);
g.endFill();
}
Thanks for your help!
Edit - I forgot this part (may or may not be relevant). Before drawing the circles I drew the BitmapData from an ImageSnapshot of some UI elements onto the sprite as well.
Solution:
I needed to create a new matrix and 'createGradientBox' for each circle individually
m = new Matrix();
m.createGradientBox(spiralHoleRadius * 2, spiralHoleRadius * 2, 0, xc - spiralHoleRadius / 2, yc - spiralHoleRadius / 2);

Following is a simular question on this site, maybe it helps (I cannot comment so I put this in a answer)
AS3: beginGradientFIll() doesn't make me a gradient!

Related

Clip + Arc leads to an unwanted closing of the path, while Clip + Rect shows the expected behavior

Question:
Why does CanvasRenderingContext2D.clip() closes an additional path when applying it to a collection of CanvasRenderingContext2D.arc() sampled along the path of a quadratic curve?
Background
I am trying to create a path of quadratic segments with a longitudinal color split. Based on a comment to the question "Square curve with lengthwise color division" I am trying to accomplish this goal by going through the following steps:
Draw the quadratic path
Sample point on the quadratic curve
Create a clipping region and draw a cycle at each sampled point
let region = new Path2D();
for (j = 0; j < pointsQBez.length; j++) {
region.arc(pointsQBez[j].x, pointsQBez[j].y, 4, 0, 2 * Math.PI );
}
ctx.clip(region)
Split the canvas into two segments based on the curve
Calculate the intersection of the start- and end-segment with the canvas border
Close the path (first clipping region)
Draw a rectangle over the whole canvas (second clipping region)
Fill in the two regions created in step four
Steps 3, 4, and 5 in pictures:
Issue
The pink part in the third image above should have the same thickness as the turquoise.
But for some strange reason, the whole inner part of the curve gets filled in.
Additional observations
This behaviour does not show when using CanvasRenderingContext2D.rect() instead of CanvasRenderingContext2D.arc():
When using CanvasRenderingContext2D.arc(), the inner part of the curve that is filled in is not consistent
Because rect does include a call to closePath() while arc doesn't.
Two ways of working around that:
You can call closePath() after each arc:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const pointsQBez = [];
const cx = 75;
const cy = 75;
const rad = 50;
for(let i = 0; i < 180; i++) {
const a = (Math.PI / 180) * i - Math.PI / 2;
const x = cx + Math.cos(a) * rad;
const y = cy + Math.sin(a) * rad;
pointsQBez.push({ x, y });
}
let region = new Path2D();
for (const {x, y} of pointsQBez) {
region.arc(x, y, 4, 0, 2 * Math.PI);
region.closePath();
}
ctx.clip(region);
ctx.fillStyle = "red";
ctx.fillRect(0, 0, canvas.width, canvas.height);
<canvas></canvas>
Or you can moveTo() the entry point of your arc:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const pointsQBez = [];
const cx = 75;
const cy = 75;
const rad = 50;
for(let i = 0; i < 180; i++) {
const a = (Math.PI / 180) * i - Math.PI / 2;
const x = cx + Math.cos(a) * rad;
const y = cy + Math.sin(a) * rad;
pointsQBez.push({ x, y });
}
let region = new Path2D();
for (const {x, y} of pointsQBez) {
region.moveTo(x + 4, y); // x + arc radius
region.arc(x, y, 4, 0, 2 * Math.PI);
}
ctx.clip(region);
ctx.fillStyle = "red";
ctx.fillRect(0, 0, canvas.width, canvas.height);
<canvas></canvas>

triangulation messing up triangles

I'm trying to triangulate a convex polygon.
On the left are the triangulated polygons, while on the right is the convex polygon without triangulation. (both are wrong) but the one on the right is in the correct position and besides the fact that it's missing the convex part, it's pretty ok...
I create the triangles and add them to an arraylist
float[] vertices = polygonObject.getPolygon().getTransformedVertices();
float[] worldVertices = new float[vertices.length];
for (int i = 0; i < vertices.length; ++i) {
System.out.println(vertices[i]);
worldVertices[i] = vertices[i] / ppt;
}
// Make triangles
Vector<float[]> trianglesVertices = new Vector<float[]>();
EarClippingTriangulator triangulator = new EarClippingTriangulator();
ArrayList<PolygonShape> triangles = new ArrayList<PolygonShape>();
ShortArray pointsCoords = triangulator.computeTriangles(worldVertices);
for (int i = 0; i < pointsCoords.size / 6; i++)
{
trianglesVertices.add(new float[] {
pointsCoords.get(i*6), pointsCoords.get(i*6 + 1),
pointsCoords.get(i*6 + 2), pointsCoords.get(i*6 + 3),
pointsCoords.get(i*6 + 4), pointsCoords.get(i*6 + 5),
});
PolygonShape triangleShape = new PolygonShape();
triangleShape.set(trianglesVertices.get(i));
triangles.add(triangleShape);
}
later I loop that arraylist and create bodies, but somehow libgdx(probably me, not libgdx) totally messes it up.
for(PolygonShape triangle:triangles){
BodyDef bd = new BodyDef();
bd.type = BodyDef.BodyType.StaticBody;
Body body = world.createBody(bd);
body.createFixture(triangle, 1);
bodies.add(body);
triangle.dispose();
}
Here is the shape I am trying to render in libgdx (created in Tiled)
I found another example and it worked!
https://github.com/MobiDevelop/maps-editor/blob/master/maps-basic/src/com/mobidevelop/maps/basic/BasicMapRenderer.java#L151
the way I was creating the triangles from this array was wrong
ShortArray pointsCoords = triangulator.computeTriangles(worldVertices);
here is the code that does create the triangles correctly,
for (int i = 0; i < pointsCoords.size; i += 3) {
int v1 = pointsCoords.get(i) * 2;
int v2 = pointsCoords.get(i + 1) * 2;
int v3 = pointsCoords.get(i + 2) * 2;
PolygonShape triangleShape = new PolygonShape();
triangleShape.set(
new float[]{
worldVertices[v1 + 0],
worldVertices[v1 + 1],
worldVertices[v2 + 0],
worldVertices[v2 + 1],
worldVertices[v3 + 0],
worldVertices[v3 + 1]
}
);
triangles.add(triangleShape);
}

find coordinates to draw square inside circle

How to compute the starting co-ordinates to draw a square inside a cirle?
Function Draws the circular spectrum .
Now help me to find the starting coordinates to draw the rectangle inside the circle
Gradient.prototype.renderSpectrum = function() {
var radius = this.width / 2;
var toRad = (2 * Math.PI) / 360;
var step = 1 / radius;
this.ctx.clearRect(0, 0, this.width, this.height);
for(var i = 0; i < 360; i += step) {
var rad = i * toRad;
this.ctx.strokeStyle = 'hsl(' + i + ', 100%, 50%)';
this.ctx.beginPath();
this.ctx.moveTo(radius, radius);
this.ctx.lineTo(radius + radius * Math.cos(rad), radius + radius * Math.sin(rad));
this.ctx.stroke();
}
this.ctx.fillStyle = 'rgb(255, 255, 255)';
this.ctx.beginPath();
this.ctx.arc(radius, radius, radius * 0.8, 0, Math.PI * 2, true);
this.ctx.closePath();
return this.ctx.fill();
}
Function to draw the square
Gradient.prototype.renderGradient = function() {
var color, colors, gradient, index, xy, _i, _len, _ref, _ref1;
xy = arguments[0], colors = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
gradient = (_ref = this.ctx).createLinearGradient.apply(_ref, [0, 0].concat(__slice.call(xy)));
gradient.addColorStop(0, (_ref1 = colors.shift()) != null ? _ref1.toString() : void 0);
for (index = _i = 0, _len = colors.length; _i < _len; index = ++_i) {
color = colors[index];
gradient.addColorStop(index + 1 / colors.length, color.toString());
}
this.ctx.fillStyle = gradient;
this.renderSpectrum();
return this.ctx.fillRect(?, ?, this.width * 0.8, this.height * 0.8);
};
To fit a square inside a circle you can use something like this (adopt as needed):
Live example
/**
* ctx - context
* cx/cy - center of circle
* radius - radius of circle
*/
function squareInCircle(ctx, cx, cy, radius) {
var side = Math.sqrt(radius * radius * 2), // calc side length of square
half = side * 0.5; // position offset
ctx.strokeRect(cx - half, cy - half, side, side);
}
Just replace strokeRect() with fillRect().
Which will result in this (circle added for reference):
Adopting it for general usage:
function getSquareInCircle(cx, cy, radius) {
var side = Math.sqrt(radius * radius * 2), // calc side length of square
half = side * 0.5; // position offset
return {
x: cx - half,
y: cy - half,
w: side,
h: side
}
}
Then in your method:
Gradient.prototype.renderGradient = function() {
var color, colors, gradient, index, xy, _i, _len, _ref, _ref1;
xy = arguments[0], colors = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
gradient = (_ref = this.ctx).createLinearGradient.apply(_ref, [0, 0].concat(__slice.call(xy)));
gradient.addColorStop(0, (_ref1 = colors.shift()) != null ? _ref1.toString() : void 0);
for (index = _i = 0, _len = colors.length; _i < _len; index = ++_i) {
color = colors[index];
gradient.addColorStop(index + 1 / colors.length, color.toString());
}
this.ctx.fillStyle = gradient;
this.renderSpectrum();
// supply the proper position/radius here:
var square = getSquareInCircle(centerX, centerY, radius);
return this.ctx.fillRect(square.x, square.y, square.w, square.h);
};
Here is how I computed the co ordinates to draw square inside a circle
1)Get the coordinates of a inner circle at 135 degree
using the formula
x = rad + rad * Math.cos(135 * ( 2 Math.PI / 360);
y = rad - rad * Math.sin(135 * ( 2 Math.PI / 360);
2) then pyhthogoram therom to find the width if the square
width = Math.sqrt(rad * rad / 2);

HTML canvas fill difference on Firefox vs Chrome

Consider the snippet below - available on jsFiddle: http://jsfiddle.net/Xqu5U/2/
ctx = document.getElementById('canvas').getContext('2d');
ctx.beginPath();
ctx.fillStyle = '#FF0000';
for (var i = 0; i < 20; i++) {
ctx.arc(i * 10, 50 + Math.sin(i * 0.5) * 15, 2, 0, Math.PI * 2);
}
ctx.fill();
It works as expected on Google Chrome, but Firefox renders a fill from the last arc to the first one.
How can the snippet above be updated to get Firefox drawing the arcs exactly like Chrome?
I found a way to get it working: http://jsfiddle.net/Xqu5U/3/.
Basically just add ctx.closePath() inside the for loop. Not sure if this is the best solution though...
I know this is old question but for the future reference:
ctx = document.getElementById('canvas').getContext('2d');
ctx.beginPath()
ctx.fillStyle = '#FF0000'
for (var i = 0; i < 20; i++) {
ctx.arc(i * 10, 50 + Math.sin(i * 0.5) * 15, 2, 0, Math.PI * 2)
ctx.closePath()
}
ctx.fill()
It's really old, but someone just pointed me to here.
The problem was a chrome bug (now fixed since an undetermined long time). FF behavior was correct.
arc() method doesn't include a moveTo(x, y) call, but a lineTo one.
So in order to get your desired result, you just have to call ctx.moveTo(nextCX + radius, nextCY) where nextCX and nextCY are the center x and y coordinates of your next arc to be drawn. We add radius to the x position because, when the arc's starting angle is set to 0, the arc is being drawn from 3 o'clock, without this + radius, we would still have an internal lineTo(cx + radius, cy) being added, visible when calling stroke().
var ctx = document.getElementById('canvas').getContext('2d');
ctx.beginPath();
ctx.fillStyle = '#FF0000';
for (var i = 0; i < 20; i++) {
// you need to moveTo manually.
ctx.moveTo(i * 10 + 2, 50 + Math.sin(i * 0.5) * 15)
ctx.arc(i * 10, 50 + Math.sin(i * 0.5) * 15, 2, 0, Math.PI * 2);
}
ctx.fill();
<canvas id="canvas"></canvas>
You don't have really something to do about it...
It's the way both browser's work...
You can always design it with CSS with the right commands to make it the same...
Go to http://w3schools.com and find what you need for doing that :)
Good luck !

Drawing Color Spectrum with Waveform

i've come across this ActionScript sample, which demonstrates drawing of the color spectrum, one line at a time via a loop, using waveforms.
however, the waveform location of each RGB channel create a color spectrum that is missing colors (pure yellow, cyan and magenta) and therefore the spectrum is incomplete.
how can i remedy this problem so that the drawn color spectrum will exhibit all colors?
// Loop through all of the pixels from '0' to the specified width.
for(var i:int = 0; i < nWidth; i++)
{
// Calculate the color percentage based on the current pixel.
nColorPercent = i / nWidth;
// Calculate the radians of the angle to use for rotating color values.
nRadians = (-360 * nColorPercent) * (Math.PI / 180);
// Calculate the RGB channels based on the angle.
nR = Math.cos(nRadians) * 127 + 128 << 16;
nG = Math.cos(nRadians + 2 * Math.PI / 3) * 127 + 128 << 8;
nB = Math.cos(nRadians + 4 * Math.PI / 3) * 127 + 128;
// OR the individual color channels together.
nColor = nR | nG | nB;
}
UPDATED SOLUTION
for anyone interested, below is the solution i wrote to address the above problem. RGB waveforms are not used to create the full color spectrum. also, the code is flexible so you can assign your own size and color variables for the produced sprite. the color variables in this example are red, yellow, green, cyan, blue, magenta, red to produce the complete color spectrum
/*
//SpectrumGradient Object Call
var spectrum:SpectrumGradient = new SpectrumGradient(stage.stageWidth, stage.stageHeight, 0xFF0000, 0xFFFF00, 0x00FF00, 0x00FFFF, 0x0000FF, 0xFF00FF, 0xFF0000);
this.addChild(spectrum);
*/
package
{
import flash.display.BitmapData;
import flash.display.CapsStyle;
import flash.display.GradientType;
import flash.display.LineScaleMode;
import flash.display.Sprite;
import flash.geom.Matrix;
public class SpectrumGradient extends Sprite
{
public function SpectrumGradient(spriteWidth:Number, spriteHeight:Number, ...spriteColors)
{
//Setup spectrum sprite
var spectrum:Sprite = new Sprite();
var spectrumAlphas:Array = new Array();
var spectrumRatios:Array = new Array();
var spectrumPartition:Number = 255 / (spriteColors.length - 1);
for (var pushLoop:int = 0; pushLoop < spriteColors.length; pushLoop++)
{
spectrumAlphas.push(1);
spectrumRatios.push(pushLoop * spectrumPartition);
}
//Create spectrum sprite as evenly distributed linear gradient using supplied spriteColors
var spectrumMatrix:Matrix = new Matrix();
spectrumMatrix.createGradientBox(spriteWidth, spriteHeight);
spectrum.graphics.lineStyle();
spectrum.graphics.beginGradientFill(GradientType.LINEAR, spriteColors, spectrumAlphas, spectrumRatios, spectrumMatrix);
spectrum.graphics.drawRect(0, 0, spriteWidth, 1);
spectrum.graphics.endFill();
//Assign bitmapData to the spectrum sprite
var bitmapData:BitmapData = new BitmapData(spectrum.width, spectrum.height, true, 0);
bitmapData.draw(spectrum);
var pixelColor:Number;
for (var i:int = 0; i < spriteWidth; i++)
{
//Retrieve the color number for each pixel of the spectrum sprite
pixelColor = bitmapData.getPixel(i, 0);
//Create new matrices for the white and black gradient lines
var matrixWhite:Matrix = new Matrix();
matrixWhite.createGradientBox(1, spriteHeight / 2, Math.PI * 0.5, 0, 0);
var matrixBlack = new Matrix();
matrixBlack.createGradientBox(1, spriteHeight / 2, Math.PI * 0.5, 0, spriteHeight / 2);
//Each slice of the sprite is composed of two vertical lines: the first fades from white to the pixelColor, the second fades from pixelColor to black
graphics.lineStyle(1, 0, 1, false, LineScaleMode.NONE, CapsStyle.NONE);
graphics.lineGradientStyle(GradientType.LINEAR, [0xFFFFFF, pixelColor], [100, 100], [0, 255], matrixWhite);
graphics.moveTo(i, 0);
graphics.lineTo(i, spriteHeight / 2);
graphics.lineGradientStyle(GradientType.LINEAR, [pixelColor, 0], [100, 100], [0, 255], matrixBlack);
graphics.moveTo(i, spriteHeight / 2);
graphics.lineTo(i, spriteHeight);
}
}
}
}
you can't have all colors at once. all RGB colors, that's 256 x 256 x 256, so you'd need 4096 x 4096 pixels for showing all of them.
Also, there is no "natural"/sensible way of displaying them all. At least until now, nobody has come up with a 2 dimensional color space that really makes sense. For displaying colors, you'll always have to pick 2. That's why common color choosers either use a hue slider and a lightness/saturation plane or a hue/saturation plane and a lightness slider.
please also note that the first (rectangular) spectrum can be easily drawn with 2 superposed gradients. a horizontal one for the hue, and a vertical (semitransparent) for lightness. its faster and completely smooth (if you zoom you don't see the individual lines).
edit: here's a working example of how this can be achieved with a single gradient, which is preferable for obvious reasons:
package {
import flash.display.*;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Matrix;
public class GradientTest extends Sprite {
public function GradientTest() {
var colors:Array = [0xFF0000, 0xFFFF00, 0x00FF00, 0x00FFFF, 0x0000FF, 0xFF00FF, 0xFF0000];
var part:Number = 0xFF / (colors.length-1);
var ratios:Array = [], alphas:Array = [];
var m:Matrix = new Matrix();
m.createGradientBox(500, 20);
for (var i:int = 0; i < colors.length; i++) {
ratios.push(part * i);
alphas.push(100);
}
this.graphics.beginGradientFill(GradientType.LINEAR, colors, alphas, ratios, m);
this.graphics.drawRect(0, 0, 500, 20);
//just to get the RGB values under the mouse:
var b:BitmapData = new BitmapData(this.width, this.height, true, 0);
b.draw(this);
stage.addEventListener(MouseEvent.MOUSE_MOVE, function (e:Event):void {
if (hitTestPoint(mouseX, mouseY)) {
var s:String = b.getPixel(mouseX, mouseY).toString(16);
while (s.length < 6) s = "0" + s;
trace("#" + s);
}
});
}
}
}
the approach using waveforms is a bit like a hammer in search of a nail. just because bit operations and trigonometry are great tools, doesn't mean you should prefer them to a solution that is much simpler.