package ij.plugin.filter;
import ij.*;
import ij.gui.*;
import ij.process.*;
import ij.measure.*;
import ij.util.Tools;
import java.awt.*;
public class BackgroundSubtracter implements ExtendedPlugInFilter, DialogListener {
private static double staticRadius = 50; private static boolean staticLightBackground = Prefs.get("bs.background", true);
private static boolean staticSeparateColors; private static boolean staticCreateBackground; private static boolean staticUseParaboloid; private static boolean staticDoPresmooth = true; private double radius = staticRadius;
private boolean lightBackground = staticLightBackground;
private boolean separateColors = staticSeparateColors;
private boolean createBackground = staticCreateBackground;
private boolean useParaboloid = staticUseParaboloid;
private boolean doPresmooth = staticDoPresmooth;
private boolean isRGB; private boolean previewing;
private final static int MAXIMUM = 0, MEAN = 1; private final static int X_DIRECTION = 0, Y_DIRECTION = 1,
DIAGONAL_1A = 2, DIAGONAL_1B = 3, DIAGONAL_2A = 4, DIAGONAL_2B = 5; private final static int DIRECTION_PASSES = 9; private int nPasses = DIRECTION_PASSES;
private int pass;
private int flags = DOES_ALL|FINAL_PROCESSING|KEEP_PREVIEW|PARALLELIZE_STACKS;
private boolean calledAsPlugin;
public int setup(String arg, ImagePlus imp) {
if (arg.equals("final")) {
imp.getProcessor().resetMinAndMax();
return DONE;
} else
return flags;
}
public int showDialog(ImagePlus imp, String command, PlugInFilterRunner pfr) {
isRGB = imp.getProcessor() instanceof ColorProcessor;
calledAsPlugin = true;
String options = Macro.getOptions();
if (options!=null) { Macro.setOptions(options.replaceAll("white", "light"));
radius = 50;
lightBackground = false;
separateColors = false;
createBackground = false;
useParaboloid = false;
doPresmooth = true;
}
GenericDialog gd = new GenericDialog(command);
gd.addNumericField("Rolling ball radius:", radius, 1, 6, "pixels");
gd.addCheckbox("Light background", lightBackground);
if (isRGB) gd.addCheckbox("Separate colors", separateColors);
gd.addCheckbox("Create background (don't subtract)", createBackground);
gd.addCheckbox("Sliding paraboloid", useParaboloid);
gd.addCheckbox("Disable smoothing", !doPresmooth);
gd.addPreviewCheckbox(pfr);
gd.addDialogListener(this);
previewing = true;
gd.addHelp(IJ.URL+"/docs/menus/process.html#background");
gd.showDialog();
previewing = false;
if (gd.wasCanceled()) return DONE;
IJ.register(this.getClass()); if ((imp.getProcessor() instanceof FloatProcessor) && !createBackground)
flags |= SNAPSHOT; if (options==null) { staticRadius = radius;
staticLightBackground = lightBackground;
staticSeparateColors = separateColors;
staticCreateBackground = createBackground;
staticUseParaboloid = useParaboloid;
staticDoPresmooth = doPresmooth;
Prefs.set("bs.background", lightBackground);
}
return IJ.setupDialog(imp, flags); }
public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
radius = gd.getNextNumber();
if (radius <= 0.0001 || gd.invalidNumber())
return false;
lightBackground = gd.getNextBoolean();
if (isRGB) separateColors = gd.getNextBoolean();
createBackground = gd.getNextBoolean();
useParaboloid = gd.getNextBoolean();
doPresmooth = !gd.getNextBoolean();
return true;
}
public void run(ImageProcessor ip) {
if (isRGB && !separateColors)
rollingBallBrightnessBackground((ColorProcessor)ip, radius, createBackground, lightBackground, useParaboloid, doPresmooth, true);
else
rollingBallBackground(ip, radius, createBackground, lightBackground, useParaboloid, doPresmooth, true);
if (previewing && (ip instanceof FloatProcessor || ip instanceof ShortProcessor)) {
ip.resetMinAndMax();
}
}
public void subtractRGBBackround(ColorProcessor ip, int ballRadius) {
rollingBallBrightnessBackground(ip, (double)ballRadius, false, lightBackground, false, true, true);
}
public void subtractBackround(ImageProcessor ip, int ballRadius) {
rollingBallBackground(ip, (double)ballRadius, false, lightBackground, false, true, true);
}
public void rollingBallBrightnessBackground(ColorProcessor ip, double radius, boolean createBackground,
boolean lightBackground, boolean useParaboloid, boolean doPresmooth, boolean correctCorners) {
int width = ip.getWidth();
int height = ip.getHeight();
byte[] H = new byte[width*height];
byte[] S = new byte[width*height];
byte[] B = new byte[width*height];
ip.getHSB(H, S, B);
ByteProcessor bp = new ByteProcessor(width, height, B, null);
rollingBallBackground(bp, radius, createBackground, lightBackground, useParaboloid, doPresmooth, correctCorners);
ip.setHSB(H, S, (byte[])bp.getPixels());
}
public void rollingBallBackground(ImageProcessor ip, double radius, boolean createBackground,
boolean lightBackground, boolean useParaboloid, boolean doPresmooth, boolean correctCorners) {
boolean invertedLut = ip.isInvertedLut();
boolean invert = (invertedLut && !lightBackground) || (!invertedLut && lightBackground);
RollingBall ball = null;
if (!useParaboloid) ball = new RollingBall(radius);
FloatProcessor fp = null;
for (int channelNumber=0; channelNumber<ip.getNChannels(); channelNumber++) {
fp = ip.toFloat(channelNumber, fp);
if ((ip instanceof FloatProcessor) && !calledAsPlugin && !createBackground)
fp.snapshot(); if (useParaboloid)
slidingParaboloidFloatBackground(fp, (float)radius, invert, doPresmooth, correctCorners);
else
rollingBallFloatBackground(fp, (float)radius, invert, doPresmooth, ball);
if (createBackground)
ip.setPixels(channelNumber, fp);
else { float[] bgPixels = (float[])fp.getPixels(); if (ip instanceof FloatProcessor) { float[] snapshotPixels = (float[])fp.getSnapshotPixels(); for (int p=0; p<bgPixels.length; p++)
bgPixels[p] = snapshotPixels[p]-bgPixels[p];
} else if (ip instanceof ShortProcessor) {
float offset = invert ? 65535.5f : 0.5f; short[] pixels = (short[])ip.getPixels();
for (int p=0; p<bgPixels.length; p++) {
float value = (pixels[p]&0xffff) - bgPixels[p] + offset;
if (value<0f) value = 0f;
if (value>65535f) value = 65535f;
pixels[p] = (short)(value);
}
} else if (ip instanceof ByteProcessor) {
float offset = invert ? 255.5f : 0.5f; byte[] pixels = (byte[])ip.getPixels();
for (int p=0; p<bgPixels.length; p++) {
float value = (pixels[p]&0xff) - bgPixels[p] + offset;
if (value<0f) value = 0f;
if (value>255f) value = 255f;
pixels[p] = (byte)(value);
}
} else if (ip instanceof ColorProcessor) {
float offset = invert ? 255.5f : 0.5f;
int[] pixels = (int[])ip.getPixels();
int shift = 16 - 8*channelNumber;
int byteMask = 255<<shift;
int resetMask = 0xffffffff^(255<<shift);
for (int p=0; p<bgPixels.length; p++) {
int pxl = pixels[p];
float value = ((pxl&byteMask)>>shift) - bgPixels[p] + offset;
if (value<0f) value = 0f;
if (value>255f) value = 255f;
pixels[p] = (pxl&resetMask) | ((int)value<<shift);
}
}
}
}
}
void slidingParaboloidFloatBackground(FloatProcessor fp, float radius, boolean invert,
boolean doPresmooth, boolean correctCorners) {
float[] pixels = (float[])fp.getPixels(); int width = fp.getWidth();
int height = fp.getHeight();
float[] cache = new float[Math.max(width, height)]; int[] nextPoint = new int[Math.max(width, height)]; float coeff2 = 0.5f/radius; float coeff2diag = 1.f/radius;
showProgress(0.000001); if (invert)
for (int i=0; i<pixels.length; i++)
pixels[i] = -pixels[i];
float shiftBy = 0;
if (doPresmooth) {
shiftBy = (float)filter3x3(fp, MAXIMUM); showProgress(0.5);
filter3x3(fp, MEAN); pass++;
}
if (correctCorners)
correctCorners(fp, coeff2, cache, nextPoint);
filter1D(fp, X_DIRECTION, coeff2, cache, nextPoint);
filter1D(fp, Y_DIRECTION, coeff2, cache, nextPoint);
filter1D(fp, X_DIRECTION, coeff2, cache, nextPoint); filter1D(fp, DIAGONAL_1A, coeff2diag, cache, nextPoint);
filter1D(fp, DIAGONAL_1B, coeff2diag, cache, nextPoint);
filter1D(fp, DIAGONAL_2A, coeff2diag, cache, nextPoint);
filter1D(fp, DIAGONAL_2B, coeff2diag, cache, nextPoint);
filter1D(fp, DIAGONAL_1A, coeff2diag, cache, nextPoint); filter1D(fp, DIAGONAL_1B, coeff2diag, cache, nextPoint);
if (invert)
for (int i=0; i<pixels.length; i++)
pixels[i] = -(pixels[i] - shiftBy);
else if (doPresmooth)
for (int i=0; i<pixels.length; i++)
pixels[i] -= shiftBy;
}
void filter1D(FloatProcessor fp, int direction, float coeff2, float[] cache, int[] nextPoint) {
float[] pixels = (float[])fp.getPixels(); int width = fp.getWidth();
int height = fp.getHeight();
int startLine = 0; int nLines = 0; int lineInc = 0; int pointInc = 0; int length = 0; switch (direction) {
case X_DIRECTION: nLines = height;
lineInc = width;
pointInc = 1;
length = width;
break;
case Y_DIRECTION: nLines = width;
lineInc = 1;
pointInc = width;
length = height;
break;
case DIAGONAL_1A: nLines = width-2; lineInc = 1;
pointInc = width + 1;
break;
case DIAGONAL_1B: startLine = 1;
nLines = height-2;
lineInc = width;
pointInc = width + 1;
break;
case DIAGONAL_2A: startLine = 2;
nLines = width;
lineInc = 1;
pointInc = width - 1;
break;
case DIAGONAL_2B: startLine = 0;
nLines = height-2;
lineInc = width;
pointInc = width - 1;
break;
}
for (int i=startLine; i<nLines; i++) {
if (i%50==0) {
if (Thread.currentThread().isInterrupted()) return;
showProgress(i/(double)nLines);
}
int startPixel = i*lineInc;
if (direction == DIAGONAL_2B) startPixel += width-1;
switch (direction) {
case DIAGONAL_1A: length = Math.min(height, width-i); break;
case DIAGONAL_1B: length = Math.min(width, height-i); break;
case DIAGONAL_2A: length = Math.min(height, i+1); break;
case DIAGONAL_2B: length = Math.min(width, height-i); break;
}
lineSlideParabola(pixels, startPixel, pointInc, length, coeff2, cache, nextPoint, null);
}
pass++;
}
static float[] lineSlideParabola(float[] pixels, int start, int inc, int length, float coeff2, float[] cache, int[] nextPoint, float[] correctedEdges) {
float minValue = Float.MAX_VALUE;
int lastpoint = 0;
int firstCorner = length-1; int lastCorner = 0; float vPrevious1 = 0f;
float vPrevious2 = 0f;
float curvatureTest = 1.999f*coeff2;
for (int i=0, p=start; i<length; i++, p+=inc) {
float v = pixels[p];
cache[i] = v;
if (v < minValue) minValue = v;
if (i >= 2 && vPrevious1+vPrevious1-vPrevious2-v < curvatureTest) {
nextPoint[lastpoint] = i-1; lastpoint = i-1;
}
vPrevious2 = vPrevious1;
vPrevious1 = v;
}
nextPoint[lastpoint] = length-1;
nextPoint[length-1] = Integer.MAX_VALUE;
int i1 = 0; while (i1<length-1) {
float v1 = cache[i1];
float minSlope = Float.MAX_VALUE;
int i2 = 0; int searchTo = length;
int recalculateLimitNow = 0;
for (int j=nextPoint[i1]; j<searchTo; j=nextPoint[j], recalculateLimitNow++) {
float v2 = cache[j];
float slope = (v2-v1)/(j-i1)+coeff2*(j-i1);
if (slope < minSlope) {
minSlope = slope;
i2 = j;
recalculateLimitNow = -3;
}
if (recalculateLimitNow==0) { double b = 0.5f*minSlope/coeff2;
int maxSearch = i1+(int)(b+Math.sqrt(b*b+(v1-minValue)/coeff2)+1); if (maxSearch < searchTo && maxSearch > 0) searchTo = maxSearch;
}
}
if (i1 == 0) firstCorner = i2;
if (i2 == length-1) lastCorner = i1;
for (int j=i1+1, p=start+j*inc; j<i2; j++, p+=inc)
pixels[p] = v1 + (j-i1)*(minSlope - (j-i1)*coeff2);
i1 = i2; }
if (correctedEdges != null) {
if (4*firstCorner >= length) firstCorner = 0; if (4*(length - 1 - lastCorner) >= length) lastCorner = length - 1;
float v1 = cache[firstCorner];
float v2 = cache[lastCorner];
float slope = (v2-v1)/(lastCorner-firstCorner); float value0 = v1 - slope * firstCorner; float coeff6 = 0; float mid = 0.5f * (lastCorner + firstCorner);
for (int i=(length+2)/3; i<=(2*length)/3; i++) { float dx = (i-mid)*2f/(lastCorner-firstCorner);
float poly6 = dx*dx*dx*dx*dx*dx - 1f; if (cache[i] < value0 + slope*i + coeff6*poly6) {
coeff6 = -(value0 + slope*i - cache[i])/poly6;
}
}
float dx = (firstCorner-mid)*2f/(lastCorner-firstCorner);
correctedEdges[0] = value0 + coeff6*(dx*dx*dx*dx*dx*dx - 1f) + coeff2*firstCorner*firstCorner;
dx = (lastCorner-mid)*2f/(lastCorner-firstCorner);
correctedEdges[1] = value0 + (length-1)*slope + coeff6*(dx*dx*dx*dx*dx*dx - 1f) + coeff2*(length-1-lastCorner)*(length-1-lastCorner);
}
return correctedEdges;
}
void correctCorners(FloatProcessor fp, float coeff2, float[] cache, int[] nextPoint) {
int width = fp.getWidth();
int height = fp.getHeight();
float[] pixels = (float[])fp.getPixels();
float[] corners = new float[4]; float[] correctedEdges = new float[2];
correctedEdges = lineSlideParabola(pixels, 0, 1, width, coeff2, cache, nextPoint, correctedEdges);
corners[0] = correctedEdges[0];
corners[1] = correctedEdges[1];
correctedEdges = lineSlideParabola(pixels, (height-1)*width, 1, width, coeff2, cache, nextPoint, correctedEdges);
corners[2] = correctedEdges[0];
corners[3] = correctedEdges[1];
correctedEdges = lineSlideParabola(pixels, 0, width, height, coeff2, cache, nextPoint, correctedEdges);
corners[0] += correctedEdges[0];
corners[2] += correctedEdges[1];
correctedEdges = lineSlideParabola(pixels, width-1, width, height, coeff2, cache, nextPoint, correctedEdges);
corners[1] += correctedEdges[0];
corners[3] += correctedEdges[1];
int diagLength = Math.min(width,height); float coeff2diag = 2 * coeff2;
correctedEdges = lineSlideParabola(pixels, 0, 1+width, diagLength, coeff2diag, cache, nextPoint, correctedEdges);
corners[0] += correctedEdges[0];
correctedEdges = lineSlideParabola(pixels, width-1, -1+width, diagLength, coeff2diag, cache, nextPoint, correctedEdges);
corners[1] += correctedEdges[0];
correctedEdges = lineSlideParabola(pixels, (height-1)*width, 1-width, diagLength, coeff2diag, cache, nextPoint, correctedEdges);
corners[2] += correctedEdges[0];
correctedEdges = lineSlideParabola(pixels, width*height-1, -1-width, diagLength, coeff2diag, cache, nextPoint, correctedEdges);
corners[3] += correctedEdges[0];
if (pixels[0] > corners[0]/3) pixels[0] = corners[0]/3;
if (pixels[width-1] > corners[1]/3) pixels[width-1] = corners[1]/3;
if (pixels[(height-1)*width] > corners[2]/3) pixels[(height-1)*width] = corners[2]/3;
if (pixels[width*height-1] > corners[3]/3) pixels[width*height-1] = corners[3]/3;
}
void rollingBallFloatBackground(FloatProcessor fp, float radius, boolean invert,
boolean doPresmooth, RollingBall ball) {
float[] pixels = (float[])fp.getPixels(); boolean shrink = ball.shrinkFactor >1;
showProgress(0.0);
if (invert)
for (int i=0; i<pixels.length; i++)
pixels[i] = -pixels[i];
if (doPresmooth)
filter3x3(fp, MEAN);
double[] minmax = Tools.getMinMax(pixels);
if (Thread.currentThread().isInterrupted()) return;
FloatProcessor smallImage = shrink ? shrinkImage(fp, ball.shrinkFactor) : fp;
if (Thread.currentThread().isInterrupted()) return;
rollBall(ball, smallImage);
if (Thread.currentThread().isInterrupted()) return;
showProgress(0.9);
if (shrink)
enlargeImage(smallImage, fp, ball.shrinkFactor);
if (Thread.currentThread().isInterrupted()) return;
if (invert)
for (int i=0; i<pixels.length; i++)
pixels[i] = -pixels[i];
pass++;
}
FloatProcessor shrinkImage(FloatProcessor ip, int shrinkFactor) {
int width = ip.getWidth();
int height = ip.getHeight();
float[] pixels = (float[])ip.getPixels();
int sWidth = (width+shrinkFactor-1)/shrinkFactor;
int sHeight = (height+shrinkFactor-1)/shrinkFactor;
showProgress(0.1);
FloatProcessor smallImage = new FloatProcessor(sWidth, sHeight);
float[] sPixels = (float[])smallImage.getPixels();
float min, thispixel;
for (int ySmall=0; ySmall<sHeight; ySmall++) {
for (int xSmall=0; xSmall<sWidth; xSmall++) {
min = Float.MAX_VALUE;
for (int j=0, y=shrinkFactor*ySmall; j<shrinkFactor&&y<height; j++, y++) {
for (int k=0, x=shrinkFactor*xSmall; k<shrinkFactor&&x<width; k++, x++) {
thispixel = pixels[x+y*width];
if (thispixel<min)
min = thispixel;
}
}
sPixels[xSmall+ySmall*sWidth] = min; }
}
return smallImage;
}
void rollBall(RollingBall ball, FloatProcessor fp) {
float[] pixels = (float[])fp.getPixels(); int width = fp.getWidth();
int height = fp.getHeight();
float[] zBall = ball.data;
int ballWidth = ball.width;
int radius = ballWidth/2;
float[] cache = new float[width*ballWidth];
Thread thread = Thread.currentThread();
long lastTime = System.currentTimeMillis();
for (int y=-radius; y<height+radius; y++) { long time = System.currentTimeMillis();
if (time-lastTime > 100) {
lastTime = time;
if (thread.isInterrupted()) return;
showProgress(0.1+0.8*y/(height+ballWidth));
}
int nextLineToWriteInCache = (y+radius)%ballWidth;
int nextLineToRead = y + radius; if (nextLineToRead<height) {
System.arraycopy(pixels, nextLineToRead*width, cache, nextLineToWriteInCache*width, width);
for (int x=0, p=nextLineToRead*width; x<width; x++,p++)
pixels[p] = -Float.MAX_VALUE; }
int y0 = y-radius; if (y0 < 0) y0 = 0;
int yBall0 = y0-y+radius; int yend = y+radius; if (yend>=height) yend = height-1;
for (int x=-radius; x<width+radius; x++) {
float z = Float.MAX_VALUE; int x0 = x-radius;
if (x0 < 0) x0 = 0;
int xBall0 = x0-x+radius;
int xend = x+radius;
if (xend>=width) xend = width-1;
for (int yp=y0, yBall=yBall0; yp<=yend; yp++,yBall++) { int cachePointer = (yp%ballWidth)*width+x0;
for (int xp=x0, bp=xBall0+yBall*ballWidth; xp<=xend; xp++, cachePointer++, bp++) {
float zReduced = cache[cachePointer] - zBall[bp];
if (z > zReduced) z = zReduced;
}
}
for (int yp=y0, yBall=yBall0; yp<=yend; yp++,yBall++) for (int xp=x0, p=xp+yp*width, bp=xBall0+yBall*ballWidth; xp<=xend; xp++, p++, bp++) {
float zMin = z + zBall[bp];
if (pixels[p] < zMin)
pixels[p] = zMin;
}
}
}
}
void enlargeImage(FloatProcessor smallImage, FloatProcessor fp, int shrinkFactor) {
int width = fp.getWidth();
int height = fp.getHeight();
int smallWidth = smallImage.getWidth();
int smallHeight = smallImage.getHeight();
float[] pixels = (float[])fp.getPixels();
float[] sPixels = (float[])smallImage.getPixels();
int[] xSmallIndices = new int[width]; float[] xWeights = new float[width]; makeInterpolationArrays(xSmallIndices, xWeights, width, smallWidth, shrinkFactor);
int[] ySmallIndices = new int[height];
float[] yWeights = new float[height];
makeInterpolationArrays(ySmallIndices, yWeights, height, smallHeight, shrinkFactor);
float[] line0 = new float[width];
float[] line1 = new float[width];
for (int x=0; x<width; x++) line1[x] = sPixels[xSmallIndices[x]] * xWeights[x] +
sPixels[xSmallIndices[x]+1] * (1f - xWeights[x]);
int ySmallLine0 = -1; for (int y=0; y<height; y++) {
if (ySmallLine0 < ySmallIndices[y]) {
float[] swap = line0; line0 = line1;
line1 = swap; ySmallLine0++;
int sYPointer = (ySmallIndices[y]+1)*smallWidth; for (int x=0; x<width; x++) line1[x] = sPixels[sYPointer+xSmallIndices[x]] * xWeights[x] +
sPixels[sYPointer+xSmallIndices[x]+1] * (1f - xWeights[x]);
}
float weight = yWeights[y];
for (int x=0, p=y*width; x<width; x++,p++)
pixels[p] = line0[x]*weight + line1[x]*(1f - weight);
}
}
void makeInterpolationArrays(int[] smallIndices, float[] weights, int length, int smallLength, int shrinkFactor) {
for (int i=0; i<length; i++) {
int smallIndex = (i - shrinkFactor/2)/shrinkFactor;
if (smallIndex >= smallLength-1) smallIndex = smallLength - 2;
smallIndices[i] = smallIndex;
float distance = (i + 0.5f)/shrinkFactor - (smallIndex + 0.5f); weights[i] = 1f - distance;
}
}
double filter3x3(FloatProcessor fp, int type) {
int width = fp.getWidth();
int height = fp.getHeight();
double shiftBy = 0;
float[] pixels = (float[])fp.getPixels();
for (int y=0; y<height; y++)
shiftBy += filter3(pixels, width, y*width, 1, type);
for (int x=0; x<width; x++)
shiftBy += filter3(pixels, height, x, width, type);
return shiftBy/width/height;
}
double filter3(float[] pixels, int length, int pixel0, int inc, int type) {
double shiftBy = 0;
float v3 = pixels[pixel0]; float v2 = v3; float v1; for (int i=0, p=pixel0; i<length; i++,p+=inc) {
v1 = v2;
v2 = v3;
if (i<length-1) v3 = pixels[p+inc];
if (type == MAXIMUM) {
float max = v1 > v3 ? v1 : v3;
if (v2 > max) max = v2;
shiftBy += max - v2;
pixels[p] = max;
} else
pixels[p] = (v1+v2+v3)*0.33333333f;
}
return shiftBy;
}
public void setNPasses(int nPasses) {
if (isRGB && separateColors)
nPasses *= 3;
if (useParaboloid)
nPasses*= (doPresmooth) ? DIRECTION_PASSES+2 : DIRECTION_PASSES;
this.nPasses = nPasses;
pass = 0;
}
private void showProgress(double percent) {
if (nPasses <= 0) return;
percent = (double)pass/nPasses + percent/nPasses;
IJ.showProgress(percent);
}
}
class RollingBall {
float[] data;
int width;
int shrinkFactor;
RollingBall(double radius) {
int arcTrimPer;
if (radius<=10) {
shrinkFactor = 1;
arcTrimPer = 24; } else if (radius<=30) {
shrinkFactor = 2;
arcTrimPer = 24; } else if (radius<=100) {
shrinkFactor = 4;
arcTrimPer = 32; } else {
shrinkFactor = 8;
arcTrimPer = 40; }
buildRollingBall(radius, arcTrimPer);
}
void buildRollingBall(double ballradius, int arcTrimPer) {
double rsquare; int xtrim; int xval, yval; double smallballradius; int halfWidth;
this.shrinkFactor = shrinkFactor;
smallballradius = ballradius/shrinkFactor;
if (smallballradius<1)
smallballradius = 1;
rsquare = smallballradius*smallballradius;
xtrim = (int)(arcTrimPer*smallballradius)/100; halfWidth = (int)Math.round(smallballradius - xtrim);
width = 2*halfWidth+1;
data = new float[width*width];
for (int y=0, p=0; y<width; y++)
for (int x=0; x<width; x++, p++) {
xval = x - halfWidth;
yval = y - halfWidth;
double temp = rsquare - xval*xval - yval*yval;
data[p] = temp>0. ? (float)(Math.sqrt(temp)) : 0f;
}
}
}