X-Git-Url: http://git.megacz.com/?p=org.ibex.core.git;a=blobdiff_plain;f=src%2Forg%2Fibex%2Fgraphics%2FPath.java;h=7dce44bdda19c68286a7568f3d59ebe6d2d5255f;hp=79fecba5185e8f04eaa9cfe946f02399bf4873d5;hb=HEAD;hpb=4daeeb4119b901d53b44913c86f8af3ce67db925 diff --git a/src/org/ibex/graphics/Path.java b/src/org/ibex/graphics/Path.java index 79fecba..7dce44b 100644 --- a/src/org/ibex/graphics/Path.java +++ b/src/org/ibex/graphics/Path.java @@ -1,201 +1,269 @@ -// FIXME -// Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL] -package org.ibex; +// Copyright 2000-2005 the Contributors, as shown in the revision logs. +// Licensed under the GNU General Public License version 2 ("the License"). +// You may not use this file except in compliance with the License. + +package org.ibex.graphics; import java.util.*; +import org.ibex.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? +/** an abstract path; may contain splines and arcs */ +public class Path { -/* - v1.0 - - textpath - - gradients - - patterns - - clipping/masking - - filters (filtering of a group must be performed AFTER the group is assembled; sep. canvas) + 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 int NUMSTEPS = 10; + private static final float PI = (float)Math.PI; - 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 -*/ + boolean closed = false; + Curve head = null; + Curve tail = null; + protected void add(Curve c) { + if (head==null) { tail=head=c; return; } + c.prev = tail; + tail.next = c; + tail = c; + } -/** Ibex's fully conformant Static SVG Viewer; see SVG spec, section G.7 */ -public final class VectorGraphics { + public void addTo(Mesh m, boolean evenOdd) { + for(Curve c = head; c != null; c = c.next) c.addTo(m); + m.setIn(evenOdd); + } - // Private Constants /////////////////////////////////////////////////////////////////// + abstract class Curve { + Curve next, prev; + float x, y; + float c1x, c1y, c2x, c2y; + public Curve() { } + public abstract void addTo(Mesh ret); + } - private static final int DEFAULT_PATHLEN = 1000; - private static final float PI = (float)Math.PI; + class Line extends Curve { + public void addTo(Mesh ret) { + float rx = next.x; + float ry = next.y; + ret.add(rx,ry); + } + } + class Move extends Curve { + public void addTo(Mesh ret) { + ret.newcontour(); + if (next==null) return; + float rx = next.x; + float ry = next.y; + ret.add(rx, ry); + } + } - // Public entry points ///////////////////////////////////////////////////////////////// + class Arc extends Curve { + public void addTo(Mesh ret) { + System.out.println("ARC!"); + float rx = c1x; + float ry = c1y; + float phi = c2x; + float fa = ((int)c2y) >> 1; + float fs = ((int)c2y) & 1; + float x1 = x; + float y1 = y; + float x2 = next.x; + float y2 = next.y; + + // 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) { - 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])); + //#end + + public long transform(Affine a, boolean forReal) { return transform(a, forReal, true); } + public long transform(Affine a, boolean forReal, boolean widthheight) { + float minx = Integer.MAX_VALUE; float miny = Integer.MAX_VALUE; + float maxx = Integer.MIN_VALUE; float maxy = Integer.MIN_VALUE; + for(Curve c = head; c != null; c = c.next) { + if (c instanceof Arc) { /* FIXME!!! WRONG!!!! */ continue; } + float x = a.multiply_px(c.x, c.y); if (x>maxx) maxx = x; if (xmaxy) maxy = y; if (ymaxx) maxx = c1x; if (c1xmaxy) maxy = c1y; if (c1ymaxx) maxx = c2x; if (c2xmaxy) maxy = c2y; if (c2y> 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; - } - - 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 (tail==null && command!='m') throw new RuntimeException("first command MUST be an 'm', not a " + command); + switch(command) { case 'z': { - int where; - type[numvertices-1] = TYPE_LINETO; - for(where = numvertices - 1; where > 0; where--) - if (type[where - 1] == TYPE_MOVETO) break; - x[numvertices] = x[where]; - y[numvertices] = y[where]; - numvertices++; + Curve c; + for(c = tail.prev; c != null && !(c instanceof Move); c = c.prev); + Line ret = new Line(); + ret.x = c.x; + ret.y = c.y; + add(ret); + Move mov = new Move(); + mov.x = ret.x; + mov.y = ret.y; + add(mov); closed = true; + // FIXME: actually, we should search back to the last 'z' or 'm', not just 'm' break; } case 'm': { - if (numvertices > 0) type[numvertices-1] = TYPE_MOVETO; - x[numvertices] = t.parseFloat() + (relative ? x[numvertices - 1] : 0); - y[numvertices] = t.parseFloat() + (relative ? y[numvertices - 1] : 0); - numvertices++; + // feature: collapse consecutive movetos + Move ret = new Move(); + ret.x = t.parseFloat() + (relative ? tail.y : 0); + ret.y = t.parseFloat() + (relative ? tail.y : 0); + add(ret); break; } case 'l': case 'h': case 'v': { - type[numvertices-1] = TYPE_LINETO; float first = t.parseFloat(), second; - if (command == 'h') { - second = relative ? 0 : y[numvertices - 1]; - } else if (command == 'v') { - second = first; first = relative ? 0 : x[numvertices - 1]; - } else { - second = t.parseFloat(); - } - x[numvertices] = first + (relative ? x[numvertices - 1] : 0); - y[numvertices] = second + (relative ? y[numvertices - 1] : 0); - numvertices++; + if (command == 'h') second = relative ? 0 : tail.y; + else if (command == 'v') { second = first; first = relative ? 0 : tail.x; } + else second = t.parseFloat(); + Line ret = new Line(); + ret.x = first + (relative ? tail.x : 0); + ret.y = second + (relative ? tail.y : 0); + add(ret); break; } case 'a': { - type[numvertices-1] = TYPE_ARCTO; - c1x[numvertices-1] = t.parseFloat() + (relative ? x[numvertices - 1] : 0); - c1y[numvertices-1] = t.parseFloat() + (relative ? y[numvertices - 1] : 0); - c2x[numvertices-1] = (t.parseFloat() / 360) * 2 * PI; - c2y[numvertices-1] = (((int)t.parseFloat()) << 1) | (int)t.parseFloat(); - x[numvertices] = t.parseFloat() + (relative ? x[numvertices - 1] : 0); - y[numvertices] = t.parseFloat() + (relative ? y[numvertices - 1] : 0); - numvertices++; + Arc ret = new Arc(); + ret.c1x = t.parseFloat() + (relative ? tail.x : 0); + ret.c1y = t.parseFloat() + (relative ? tail.y : 0); + ret.c2x = (t.parseFloat() / 360) * 2 * PI; + ret.c2y = t.parseFloat(); + ret.x = t.parseFloat() + (relative ? tail.x : 0); + ret.y = t.parseFloat() + (relative ? tail.y : 0); + add(ret); break; } case 's': case 'c': { - type[numvertices-1] = TYPE_CUBIC; + Bezier ret = new Bezier(); if (command == 'c') { - c1x[numvertices-1] = t.parseFloat() + (relative ? x[numvertices - 1] : 0); - c1y[numvertices-1] = t.parseFloat() + (relative ? y[numvertices - 1] : 0); - } else if (numvertices > 1 && type[numvertices-2] == TYPE_CUBIC) { - c1x[numvertices-1] = 2 * x[numvertices - 1] - c2x[numvertices-2]; - c1y[numvertices-1] = 2 * y[numvertices - 1] - c2y[numvertices-2]; + tail.c1x = t.parseFloat() + (relative ? tail.x : 0); + tail.c1y = t.parseFloat() + (relative ? tail.y : 0); + } else if (head != null && tail instanceof Bezier) { + tail.c1x = 2 * tail.x-((Bezier)tail).c2x; + tail.c1y = 2 * tail.y-((Bezier)tail).c2x; } else { - c1x[numvertices-1] = x[numvertices-1]; - c1y[numvertices-1] = y[numvertices-1]; + tail.c1x = tail.x; + tail.c1y = tail.y; } - c2x[numvertices-1] = t.parseFloat() + (relative ? x[numvertices - 1] : 0); - c2y[numvertices-1] = t.parseFloat() + (relative ? y[numvertices - 1] : 0); - x[numvertices] = t.parseFloat() + (relative ? x[numvertices - 1] : 0); - y[numvertices] = t.parseFloat() + (relative ? y[numvertices - 1] : 0); - numvertices++; + tail.c2x = t.parseFloat() + (relative ? tail.x : 0); + tail.c2y = t.parseFloat() + (relative ? tail.y : 0); + ret.x = t.parseFloat() + (relative ? tail.x : 0); + ret.y = t.parseFloat() + (relative ? tail.y : 0); + add(ret); break; } case 't': case 'q': { - type[numvertices-1] = TYPE_QUADRADIC; + QuadBezier ret = new QuadBezier(); if (command == 'q') { - c1x[numvertices-1] = t.parseFloat() + (relative ? x[numvertices - 1] : 0); - c1y[numvertices-1] = t.parseFloat() + (relative ? y[numvertices - 1] : 0); - } else if (numvertices > 1 && type[numvertices-2] == TYPE_QUADRADIC) { - c1x[numvertices-1] = 2 * x[numvertices - 1] - c1x[numvertices-2]; - c1y[numvertices-1] = 2 * y[numvertices - 1] - c1y[numvertices-2]; + tail.c1x = t.parseFloat() + (relative ? tail.x : 0); + tail.c1y = t.parseFloat() + (relative ? tail.y : 0); + } else if (head != null && tail instanceof QuadBezier) { + tail.c1x = 2 * tail.x-((QuadBezier)tail).c1x; + tail.c1y = 2 * tail.y-((QuadBezier)tail).c1y; } else { - c1x[numvertices-1] = x[numvertices-1]; - c1y[numvertices-1] = y[numvertices-1]; + tail.c1x = tail.x; + tail.c1y = tail.y; } - x[numvertices] = t.parseFloat() + (relative ? x[numvertices - 1] : 0); - y[numvertices] = t.parseFloat() + (relative ? y[numvertices - 1] : 0); - numvertices++; + ret.x = t.parseFloat() + (relative ? tail.x : 0); + ret.y = t.parseFloat() + (relative ? tail.y : 0); + add(ret); break; } 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 - } - } - } - */ - - } - } - - - - - // Rasterized Vector Path ////////////////////////////////////////////////////////////////////////////// - - /** a vector path */ - public static class RasterPath { - - // the vertices of this path - int[] x = new int[DEFAULT_PATHLEN]; - int[] y = new int[DEFAULT_PATHLEN]; - int numvertices = 0; - - /** - * A list of the vertices on this path which *start* an *edge* (rather than a moveto), sorted by increasing y. - * example: x[edges[1]],y[edges[1]] - x[edges[i]+1],y[edges[i]+1] is the second-topmost edge - * note that if x[i],y[i] - x[i+1],y[i+1] is a MOVETO, then no element in edges will be equal to i - */ - int[] edges = new int[DEFAULT_PATHLEN]; - int numedges = 0; - - /** translate a rasterized path */ - public void translate(int dx, int dy) { for(int i=0; i left && y[edges[--j]] > y[edges[right]]); - if (i >= j) break; - s = edges[i]; edges[i] = edges[j]; edges[j] = s; - } - s = edges[right]; edges[right] = edges[i]; edges[i] = s; - return i; - } else { - if (left >= right) return 0; - int p = sort(left, right, true); - sort(left, p - 1, false); - sort(p + 1, right, false); - return 0; - } - } - - /** finds the x value at which the line intercepts the line y=_y */ - private int intercept(int i, float _y, boolean includeTop, boolean includeBottom) { - if (includeTop ? (_y < Math.min(y[i], y[i+1])) : (_y <= Math.min(y[i], y[i+1]))) - return Integer.MIN_VALUE; - if (includeBottom ? (_y > Math.max(y[i], y[i+1])) : (_y >= Math.max(y[i], y[i+1]))) - return Integer.MIN_VALUE; - return (int)Math.round((((float)(x[i + 1] - x[i])) / - ((float)(y[i + 1] - y[i])) ) * ((float)(_y - y[i])) + x[i]); - } - - /** fill the interior of the path */ - public void fill(PixelBuffer buf, Paint paint) { - if (numedges == 0) return; - int y0 = y[edges[0]], y1 = y0; - boolean useEvenOdd = false; - - // we iterate over all endpoints in increasing y-coordinate order - for(int index = 1; index x1) continue; - if (midpoint == x1 && i >= rightSegment) continue; - rightSegment = i; - x1 = midpoint; - } - if (leftSegment == rightSegment || rightSegment == Integer.MAX_VALUE) break; - if (leftSegment != -1) - if ((useEvenOdd && count % 2 != 0) || (!useEvenOdd && count != 0)) - paint.fillTrapezoid(intercept(edges[leftSegment], y0, true, true), - intercept(edges[rightSegment], y0, true, true), y0, - intercept(edges[leftSegment], y1, true, true), - intercept(edges[rightSegment], y1, true, true), y1, - buf); - if (useEvenOdd) count++; - else count += (y[edges[rightSegment]] < y[edges[rightSegment]+1]) ? -1 : 1; - leftSegment = rightSegment; x0 = x1; - } - } - } - - /** stroke the outline of the path */ - public void stroke(PixelBuffer buf, int width, int color) { stroke(buf, width, color, null, 0, 0); } - public void stroke(PixelBuffer buf, int width, int color, String dashArray, int dashOffset, float segLength) { - - if (dashArray == null) { - for(int i=0; i 0) { - float actualLength = 0; - for(int i=0; i _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; - - } - */ -