X-Git-Url: http://git.megacz.com/?p=org.ibex.core.git;a=blobdiff_plain;f=src%2Forg%2Fibex%2Fgraphics%2FPath.java;h=71063a215d6ea25bf45ab59793df653ffb3e6f32;hp=79fecba5185e8f04eaa9cfe946f02399bf4873d5;hb=8e190fb0ff508ccf4962bbfbf8295a431805c12b;hpb=4daeeb4119b901d53b44913c86f8af3ce67db925 diff --git a/src/org/ibex/graphics/Path.java b/src/org/ibex/graphics/Path.java index 79fecba..71063a2 100644 --- a/src/org/ibex/graphics/Path.java +++ b/src/org/ibex/graphics/Path.java @@ -1,201 +1,66 @@ // FIXME -// Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL] -package org.ibex; +// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL] +package org.ibex.graphics; import java.util.*; -// FIXME: offer a "subpixel" mode where we pass floats to the Platform and don't do any snapping -// FIXME: fracture when realizing instead of when parsing? - -/* - v1.0 - - textpath - - gradients - - patterns - - clipping/masking - - filters (filtering of a group must be performed AFTER the group is assembled; sep. canvas) - - v1.1 - - bump caps [requires Paint that can fill circles...] [remember to distinguish between closed/unclosed] - - line joins - - mitre (hard) - - bevel (easy) - - bump (easy, but requires 'round' Paint) - - subtree sharing? otherwise the memory consumption might be outrageous... clone="" attribute? - - better clipping - - intersect clip regions (linearity) - - clip on trapezoids, not pixels - - faster gradients and patterns: - - transform each corner of the trapezoid and then interpolate -*/ - -/** Ibex's fully conformant Static SVG Viewer; see SVG spec, section G.7 */ -public final class VectorGraphics { - - // Private Constants /////////////////////////////////////////////////////////////////// +/** an abstract path; may contain splines and arcs */ +public class Path { + public static final float PX_PER_INCH = 72; + public static final float INCHES_PER_CM = (float)0.3937; + public static final float INCHES_PER_MM = INCHES_PER_CM / 10; private static final int DEFAULT_PATHLEN = 1000; private static final float PI = (float)Math.PI; + // the number of vertices on this path + int numvertices = 0; - // Public entry points ///////////////////////////////////////////////////////////////// - - public static VectorPath parseVectorPath(String s) { - if (s == null) return null; - PathTokenizer t = new PathTokenizer(s); - VectorPath ret = new VectorPath(); - char last_command = 'M'; - boolean first = true; - while(t.hasMoreTokens()) { - char command = t.parseCommand(); - if (first && command != 'M') throw new RuntimeException("the first command of a path must be 'M'"); - first = false; - boolean relative = Character.toLowerCase(command) == command; - command = Character.toLowerCase(command); - ret.parseSingleCommandAndArguments(t, command, relative); - last_command = command; - } - return ret; - } - - - // Affine ////////////////////////////////////////////////////////////////////////////// - - /** an affine transform; all operations are destructive */ - public static final class Affine { - - // [ a b e ] - // [ c d f ] - // [ 0 0 1 ] - public float a, b, c, d, e, f; - - Affine(float _a, float _b, float _c, float _d, float _e, float _f) { a = _a; b = _b; c = _c; d = _d; e = _e; f = _f; } - public String toString() { return "[ " + a + ", " + b + ", " + c + ", " + d + ", " + e + ", " + f + " ]"; } - public Affine copy() { return new Affine(a, b, c, d, e, f); } - public static Affine identity() { return new Affine(1, 0, 0, 1, 0, 0); } - public static Affine scale(float sx, float sy) { return new Affine(sx, 0, 0, sy, 0, 0); } - public static Affine shear(float degrees) { - return new Affine(1, 0, (float)Math.tan(degrees * (float)(Math.PI / 180.0)), 1, 0, 0); } - public static Affine translate(float tx, float ty) { return new Affine(1, 0, 0, 1, tx, ty); } - public static Affine flip(boolean horiz, boolean vert) { return new Affine(horiz ? -1 : 1, 0, 0, vert ? -1 : 1, 0, 0); } - public float multiply_px(float x, float y) { return x * a + y * c + e; } - public float multiply_py(float x, float y) { return x * b + y * d + f; } - public boolean equalsIgnoringTranslation(Affine x) { return a == x.a && b == x.b && c == x.c && d == x.d; } - - public boolean equals(Object o) { - if (!(o instanceof Affine)) return false; - Affine x = (Affine)o; - return a == x.a && b == x.b && c == x.c && d == x.d && e == x.e && f == x.f; - } + // the vertices of the path + float[] x = new float[DEFAULT_PATHLEN]; + float[] y = new float[DEFAULT_PATHLEN]; - public static Affine rotate(float degrees) { - float s = (float)Math.sin(degrees * (float)(Math.PI / 180.0)); - float c = (float)Math.cos(degrees * (float)(Math.PI / 180.0)); - return new Affine(c, s, -s, c, 0, 0); - } + // the type of each edge; type[i] is the type of the edge from x[i],y[i] to x[i+1],y[i+1] + byte[] type = new byte[DEFAULT_PATHLEN]; - /** this = this * a */ - public Affine multiply(Affine A) { - float _a = this.a * A.a + this.b * A.c; - float _b = this.a * A.b + this.b * A.d; - float _c = this.c * A.a + this.d * A.c; - float _d = this.c * A.b + this.d * A.d; - float _e = this.e * A.a + this.f * A.c + A.e; - float _f = this.e * A.b + this.f * A.d + A.f; - a = _a; b = _b; c = _c; d = _d; e = _e; f = _f; - return this; - } - - public void invert() { - float det = 1 / (a * d - b * c); - float _a = d * det; - float _b = -1 * b * det; - float _c = -1 * c * det; - float _d = a * det; - float _e = -1 * e * a - f * c; - float _f = -1 * e * b - f * d; - a = _a; b = _b; c = _c; d = _d; e = _e; f = _f; - } - } + // bezier control points + float[] c1x = new float[DEFAULT_PATHLEN]; // or rx (arcto) + float[] c1y = new float[DEFAULT_PATHLEN]; // or ry (arcto) + float[] c2x = new float[DEFAULT_PATHLEN]; // or x-axis-rotation (arcto) + float[] c2y = new float[DEFAULT_PATHLEN]; // or large-arc << 1 | sweep (arcto) + boolean closed = false; - // PathTokenizer ////////////////////////////////////////////////////////////////////////////// + static final byte TYPE_MOVETO = 0; + static final byte TYPE_LINETO = 1; + static final byte TYPE_ARCTO = 2; + static final byte TYPE_CUBIC = 3; + static final byte TYPE_QUADRADIC = 4; - public static Affine parseTransform(String t) { - if (t == null) return null; - t = t.trim(); - Affine ret = VectorGraphics.Affine.identity(); - while (t.length() > 0) { - if (t.startsWith("skewX(")) { - // FIXME - - } else if (t.startsWith("shear(")) { - // FIXME: nonstandard; remove this - ret.multiply(VectorGraphics.Affine.shear(Float.parseFloat(t.substring(t.indexOf('(') + 1, t.indexOf(')'))))); - - } else if (t.startsWith("skewY(")) { - // FIXME - - } else if (t.startsWith("rotate(")) { - String sub = t.substring(t.indexOf('(') + 1, t.indexOf(')')); - if (sub.indexOf(',') != -1) { - float angle = Float.parseFloat(sub.substring(0, sub.indexOf(','))); - sub = sub.substring(sub.indexOf(',') + 1); - float cx = Float.parseFloat(sub.substring(0, sub.indexOf(','))); - sub = sub.substring(sub.indexOf(',') + 1); - float cy = Float.parseFloat(sub); - ret.multiply(VectorGraphics.Affine.translate(cx, cy)); - ret.multiply(VectorGraphics.Affine.rotate(angle)); - ret.multiply(VectorGraphics.Affine.translate(-1 * cx, -1 * cy)); - } else { - ret.multiply(VectorGraphics.Affine.rotate(Float.parseFloat(t.substring(t.indexOf('(') + 1, t.indexOf(')'))))); - } - - } else if (t.startsWith("translate(")) { - String sub = t.substring(t.indexOf('(') + 1, t.indexOf(')')); - if (sub.indexOf(',') > -1) { - ret.multiply(VectorGraphics.Affine.translate(Float.parseFloat(t.substring(t.indexOf('(') + 1, t.indexOf(','))), - Float.parseFloat(t.substring(t.indexOf(',') + 1, t.indexOf(')'))))); - } else { - ret.multiply(VectorGraphics.Affine.translate(Float.parseFloat(t.substring(t.indexOf('(') + 1, t.indexOf(','))), 0)); - } - - } else if (t.startsWith("flip(")) { - String which = t.substring(t.indexOf('(') + 1, t.indexOf(')')); - ret.multiply(VectorGraphics.Affine.flip(which.equals("horizontal"), which.equals("vertical"))); - - } else if (t.startsWith("scale(")) { - String sub = t.substring(t.indexOf('(') + 1, t.indexOf(')')); - if (sub.indexOf(',') > -1) { - ret.multiply(VectorGraphics.Affine.scale(Float.parseFloat(t.substring(t.indexOf('(') + 1, t.indexOf(','))), - Float.parseFloat(t.substring(t.indexOf(',') + 1, t.indexOf(')'))))); - } else { - ret.multiply(VectorGraphics.Affine.scale(Float.parseFloat(t.substring(t.indexOf('(') + 1, t.indexOf(','))), - Float.parseFloat(t.substring(t.indexOf('(') + 1, t.indexOf(','))))); - } - - } else if (t.startsWith("matrix(")) { - // FIXME: is this mapped right? - float d[] = new float[6]; - StringTokenizer st = new StringTokenizer(t, ",", false); - for(int i=0; i<6; i++) - d[i] = Float.parseFloat(st.nextToken()); - ret.multiply(new VectorGraphics.Affine(d[0], d[1], d[2], d[3], d[4], d[5])); - } - t = t.substring(t.indexOf(')') + 1).trim(); - } - return ret; - } - - public static final float PX_PER_INCH = 72; - public static final float INCHES_PER_CM = (float)0.3937; - public static final float INCHES_PER_MM = INCHES_PER_CM / 10; - - public static class PathTokenizer { + public static class Tokenizer { // FIXME: check array bounds exception for improperly terminated string String s; int i = 0; char lastCommand = 'M'; - public PathTokenizer(String s) { this.s = s; } + public Tokenizer(String s) { this.s = s; } + + public static Path parse(String s) { + if (s == null) return null; + Tokenizer t = new Tokenizer(s); + Path ret = new Path(); + char last_command = 'M'; + boolean first = true; + while(t.hasMoreTokens()) { + char command = t.parseCommand(); + if (first && command != 'M') throw new RuntimeException("the first command of a path must be 'M'"); + first = false; + boolean relative = Character.toLowerCase(command) == command; + command = Character.toLowerCase(command); + ret.parseSingleCommandAndArguments(t, command, relative); + last_command = command; + } + return ret; + } + private void consumeWhitespace() { while(i < s.length() && (Character.isWhitespace(s.charAt(i)))) i++; if (i < s.length() && s.charAt(i) == ',') i++; @@ -235,169 +100,139 @@ public final class VectorGraphics { } } + /** Creates a concrete vector path transformed through the given matrix. */ + public Raster realize(Affine a) { + + Raster ret = new Raster(); + int NUMSTEPS = 5; // FIXME + ret.numvertices = 1; + ret.x[0] = (int)Math.round(a.multiply_px(x[0], y[0])); + ret.y[0] = (int)Math.round(a.multiply_py(x[0], y[0])); + + for(int i=1; i> 1; + float fs = ((int)c2y[i]) & 1; + float x1 = x[i]; + float y1 = y[i]; + float x2 = x[i+1]; + float y2 = y[i+1]; + + // F.6.5: given x1,y1,x2,y2,fa,fs, compute cx,cy,theta1,dtheta + float x1_ = (float)Math.cos(phi) * (x1 - x2) / 2 + (float)Math.sin(phi) * (y1 - y2) / 2; + float y1_ = -1 * (float)Math.sin(phi) * (x1 - x2) / 2 + (float)Math.cos(phi) * (y1 - y2) / 2; + float tmp = (float)Math.sqrt((rx * rx * ry * ry - rx * rx * y1_ * y1_ - ry * ry * x1_ * x1_) / + (rx * rx * y1_ * y1_ + ry * ry * x1_ * x1_)); + float cx_ = (fa == fs ? -1 : 1) * tmp * (rx * y1_ / ry); + float cy_ = (fa == fs ? -1 : 1) * -1 * tmp * (ry * x1_ / rx); + float cx = (float)Math.cos(phi) * cx_ - (float)Math.sin(phi) * cy_ + (x1 + x2) / 2; + float cy = (float)Math.sin(phi) * cx_ + (float)Math.cos(phi) * cy_ + (y1 + y2) / 2; + + // F.6.4 Conversion from center to endpoint parameterization + float ux = 1, uy = 0, vx = (x1_ - cx_) / rx, vy = (y1_ - cy_) / ry; + float det = ux * vy - uy * vx; + float theta1 = (det < 0 ? -1 : 1) * + (float)Math.acos((ux * vx + uy * vy) / + ((float)Math.sqrt(ux * ux + uy * uy) * (float)Math.sqrt(vx * vx + vy * vy))); + ux = (x1_ - cx_) / rx; uy = (y1_ - cy_) / ry; + vx = (-1 * x1_ - cx_) / rx; vy = (-1 * y1_ - cy_) / ry; + det = ux * vy - uy * vx; + float dtheta = (det < 0 ? -1 : 1) * + (float)Math.acos((ux * vx + uy * vy) / + ((float)Math.sqrt(ux * ux + uy * uy) * (float)Math.sqrt(vx * vx + vy * vy))); + dtheta = dtheta % (float)(2 * Math.PI); + + if (fs == 0 && dtheta > 0) theta1 -= 2 * PI; + if (fs == 1 && dtheta < 0) theta1 += 2 * PI; + + if (fa == 1 && dtheta < 0) dtheta = 2 * PI + dtheta; + else if (fa == 1 && dtheta > 0) dtheta = -1 * (2 * PI - dtheta); + + // FIXME: integrate F.6.6 + // FIXME: isn't quite ending where it should... + + // F.6.3: Parameterization alternatives + float theta = theta1; + for(int j=0; j> 1; - float fs = ((int)c2y[i]) & 1; - float x1 = x[i]; - float y1 = y[i]; - float x2 = x[i+1]; - float y2 = y[i+1]; - - // F.6.5: given x1,y1,x2,y2,fa,fs, compute cx,cy,theta1,dtheta - float x1_ = (float)Math.cos(phi) * (x1 - x2) / 2 + (float)Math.sin(phi) * (y1 - y2) / 2; - float y1_ = -1 * (float)Math.sin(phi) * (x1 - x2) / 2 + (float)Math.cos(phi) * (y1 - y2) / 2; - float tmp = (float)Math.sqrt((rx * rx * ry * ry - rx * rx * y1_ * y1_ - ry * ry * x1_ * x1_) / - (rx * rx * y1_ * y1_ + ry * ry * x1_ * x1_)); - float cx_ = (fa == fs ? -1 : 1) * tmp * (rx * y1_ / ry); - float cy_ = (fa == fs ? -1 : 1) * -1 * tmp * (ry * x1_ / rx); - float cx = (float)Math.cos(phi) * cx_ - (float)Math.sin(phi) * cy_ + (x1 + x2) / 2; - float cy = (float)Math.sin(phi) * cx_ + (float)Math.cos(phi) * cy_ + (y1 + y2) / 2; - - // F.6.4 Conversion from center to endpoint parameterization - float ux = 1, uy = 0, vx = (x1_ - cx_) / rx, vy = (y1_ - cy_) / ry; - float det = ux * vy - uy * vx; - float theta1 = (det < 0 ? -1 : 1) * - (float)Math.acos((ux * vx + uy * vy) / - ((float)Math.sqrt(ux * ux + uy * uy) * (float)Math.sqrt(vx * vx + vy * vy))); - ux = (x1_ - cx_) / rx; uy = (y1_ - cy_) / ry; - vx = (-1 * x1_ - cx_) / rx; vy = (-1 * y1_ - cy_) / ry; - det = ux * vy - uy * vx; - float dtheta = (det < 0 ? -1 : 1) * - (float)Math.acos((ux * vx + uy * vy) / - ((float)Math.sqrt(ux * ux + uy * uy) * (float)Math.sqrt(vx * vx + vy * vy))); - dtheta = dtheta % (float)(2 * Math.PI); - - if (fs == 0 && dtheta > 0) theta1 -= 2 * PI; - if (fs == 1 && dtheta < 0) theta1 += 2 * PI; - - if (fa == 1 && dtheta < 0) dtheta = 2 * PI + dtheta; - else if (fa == 1 && dtheta > 0) dtheta = -1 * (2 * PI - dtheta); - - // FIXME: integrate F.6.6 - // FIXME: isn't quite ending where it should... - - // F.6.3: Parameterization alternatives - float theta = theta1; - for(int j=0; j 0) ret.sort(0, ret.numedges - 1, false); - return ret; - } + if (ret.numedges > 0) ret.sort(0, ret.numedges - 1, false); + return ret; + } - protected void parseSingleCommandAndArguments(PathTokenizer t, char command, boolean relative) { - if (numvertices == 0 && command != 'm') throw new RuntimeException("first command MUST be an 'm'"); - if (numvertices > x.length - 2) { - float[] new_x = new float[x.length * 2]; System.arraycopy(x, 0, new_x, 0, x.length); x = new_x; - float[] new_y = new float[y.length * 2]; System.arraycopy(y, 0, new_y, 0, y.length); y = new_y; - } - switch(command) { + protected void parseSingleCommandAndArguments(Tokenizer t, char command, boolean relative) { + if (numvertices == 0 && command != 'm') throw new RuntimeException("first command MUST be an 'm'"); + if (numvertices > x.length - 2) { + float[] new_x = new float[x.length * 2]; System.arraycopy(x, 0, new_x, 0, x.length); x = new_x; + float[] new_y = new float[y.length * 2]; System.arraycopy(y, 0, new_y, 0, y.length); y = new_y; + } + switch(command) { case 'z': { int where; type[numvertices-1] = TYPE_LINETO; @@ -486,51 +321,48 @@ public final class VectorGraphics { default: // FIXME - } + } - /* - // invariant: after this loop, no two lines intersect other than at a vertex - // FIXME: cleanup - int index = numvertices - 2; - for(int i=0; i Math.min(x[i+1], x[i]) && _x < Math.max(x[i+1], x[i]) && - _x > Math.min(x[j+1], x[j]) && _x < Math.max(x[j+1], x[j])) { - // FIXME: something's not right in here. See if we can do without fracturing line 'i'. - for(int k = ++numvertices; k>i; k--) { x[k] = x[k - 1]; y[k] = y[k - 1]; } - x[i+1] = _x; - y[i+1] = _y; - x[numvertices] = x[numvertices - 1]; x[numvertices - 1] = _x; - y[numvertices] = y[numvertices - 1]; y[numvertices - 1] = _y; - edges[numedges++] = numvertices - 1; numvertices++; - index++; - break; // actually 'continue' the outermost loop - } - } - } - */ + if (_x > Math.min(x[i+1], x[i]) && _x < Math.max(x[i+1], x[i]) && + _x > Math.min(x[j+1], x[j]) && _x < Math.max(x[j+1], x[j])) { + // FIXME: something's not right in here. See if we can do without fracturing line 'i'. + for(int k = ++numvertices; k>i; k--) { x[k] = x[k - 1]; y[k] = y[k - 1]; } + x[i+1] = _x; + y[i+1] = _y; + x[numvertices] = x[numvertices - 1]; x[numvertices - 1] = _x; + y[numvertices] = y[numvertices - 1]; y[numvertices - 1] = _y; + edges[numedges++] = numvertices - 1; numvertices++; + index++; + break; // actually 'continue' the outermost loop + } + } + } + */ - } } - - // Rasterized Vector Path ////////////////////////////////////////////////////////////////////////////// /** a vector path */ - public static class RasterPath { + public static class Raster { // the vertices of this path int[] x = new int[DEFAULT_PATHLEN]; @@ -658,7 +490,7 @@ public final class VectorGraphics { } ratio = actualLength / segLength; } - PathTokenizer pt = new PathTokenizer(dashArray); + Tokenizer pt = new Tokenizer(dashArray); Vector v = new Vector(); while (pt.hasMoreTokens()) v.addElement(new Float(pt.parseFloat())); float[] dashes = new float[v.size() % 2 == 0 ? v.size() : 2 * v.size()]; @@ -702,113 +534,6 @@ public final class VectorGraphics { return ret; } } - - - // Paint ////////////////////////////////////////////////////////////////////////////// - public static interface Paint { - public abstract void - fillTrapezoid(int tx1, int tx2, int ty1, int tx3, int tx4, int ty2, PixelBuffer buf); - } - - public static class SingleColorPaint implements Paint { - int color; - public SingleColorPaint(int color) { this.color = color; } - public void fillTrapezoid(int x1, int x2, int y1, int x3, int x4, int y2, PixelBuffer buf) { - buf.fillTrapezoid(x1, x2, y1, x3, x4, y2, color); - } - } } - - - - - - - - - - /* - public static abstract class GradientPaint extends Paint { - public GradientPaint(boolean reflect, boolean repeat, Affine gradientTransform, - int[] stop_colors, float[] stop_offsets) { - this.reflect = reflect; this.repeat = repeat; - this.gradientTransform = gradientTransform; - this.stop_colors = stop_colors; - this.stop_offsets = stop_offsets; - } - Affine gradientTransform = Affine.identity(); - boolean useBoundingBox = false; // FIXME not supported - boolean patternUseBoundingBox = false; // FIXME not supported - - // it's invalid for both of these to be true - boolean reflect = false; // FIXME not supported - boolean repeat = false; // FIXME not supported - int[] stop_colors; - float[] stop_offsets; - - public void fillTrapezoid(float tx1, float tx2, float ty1, float tx3, float tx4, float ty2, PixelBuffer buf) { - Affine a = buf.a; - Affine inverse = a.copy().invert(); - float slope1 = (tx3 - tx1) / (ty2 - ty1); - float slope2 = (tx4 - tx2) / (ty2 - ty1); - for(float y=ty1; y _x2) { float _x0 = _x1; _x1 = _x2; _x2 = _x0; } - - for(float x=_x1; x<_x2; x++) { - - float distance = isLinear ? - // length of projection of onto the gradient vector == { \dot {grad \over |grad|}} - (x * (x2 - x1) + y * (y2 - y1)) / (float)Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) : - - // radial form is simple! FIXME, not quite right - (float)Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy)); - - // FIXME: offsets are 0..1, not 0..length(gradient) - int i = 0; for(; i= stop_offsets.length) continue; - - // gradate from offsets[i - 1] to offsets[i] - float percentage = ((distance - stop_offsets[i - 1]) / (stop_offsets[i] - stop_offsets[i - 1])); - - int a = (int)((((stop_colors[i] >> 24) & 0xff) - ((stop_colors[i - 1] >> 24) & 0xff)) * percentage) + - ((stop_colors[i - 1] >> 24) & 0xff); - int r = (int)((((stop_colors[i] >> 16) & 0xff) - ((stop_colors[i - 1] >> 16) & 0xff)) * percentage) + - ((stop_colors[i - 1] >> 16) & 0xff); - int g = (int)((((stop_colors[i] >> 8) & 0xff) - ((stop_colors[i - 1] >> 8) & 0xff)) * percentage) + - ((stop_colors[i - 1] >> 8) & 0xff); - int b = (int)((((stop_colors[i] >> 0) & 0xff) - ((stop_colors[i - 1] >> 0) & 0xff)) * percentage) + - ((stop_colors[i - 1] >> 0) & 0xff); - int argb = (a << 24) | (r << 16) | (g << 8) | b; - buf.drawPoint((int)x, (int)Math.floor(y), argb); - } - } - } - } - - public static class LinearGradientPaint extends GradientPaint { - public LinearGradientPaint(float x1, float y1, float x2, float y2, boolean reflect, boolean repeat, - Affine gradientTransform, int[] stop_colors, float[] stop_offsets) { - super(reflect, repeat, gradientTransform, stop_colors, stop_offsets); - this.x1 = x1; this.x2 = x2; this.y1 = y1; this.y2 = y2; - } - float x1 = 0, y1 = 0, x2 = 300, y2 = 300; - } - - public static class RadialGradientPaint extends GradientPaint { - public RadialGradientPaint(float cx, float cy, float fx, float fy, float r, boolean reflect, boolean repeat, - Affine gradientTransform, int[] stop_colors, float[] stop_offsets) { - super(reflect, repeat, gradientTransform, stop_colors, stop_offsets); - this.cx = cx; this.cy = cy; this.fx = fx; this.fy = fy; this.r = r; - } - - float cx, cy, r, fx, fy; - - } - */ -