Skip to content

Commit 52b8d96

Browse files
committed
fix subregion rendering for all gradient types
1 parent 1155716 commit 52b8d96

2 files changed

Lines changed: 150 additions & 37 deletions

File tree

src/main/micycle/peasygradients/PeasyGradients.java

Lines changed: 63 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,15 @@
4747
* with multiple color stops and custom center offsets)
4848
* </ul>
4949
*
50-
* <p>By default, renders directly to the Processing sketch. Use
50+
* <p>
51+
* By default, renders directly to the Processing sketch. Use
5152
* {@code .setRenderTarget()} to specify a custom {@code PGraphics} output
52-
* buffer.</p>
53+
* buffer.
54+
* </p>
5355
*
5456
* <p>
55-
* Linear, radial &amp; conic sampling algorithms adapted from Jeremy
56-
* Behreandt. Additional sampling patterns are original implementations.
57+
* Linear, radial &amp; conic sampling algorithms adapted from Jeremy Behreandt.
58+
* Additional sampling patterns are original implementations.
5759
* </p>
5860
*
5961
* @author Michael Carleton
@@ -107,9 +109,13 @@ public final class PeasyGradients {
107109

108110
/**
109111
* Number of horizontal strips the plane is paritioned into for threaded
110-
* rendering
112+
* rendering.
111113
*/
112-
private int renderStrips = (int) Math.max(cpuThreads * 0.75, 1);
114+
private int renderStrips = (int) Math.min(Math.max(cpuThreads * 0.75, 1), 10); // 1..10 strips
115+
116+
void setRenderStrips(int renderStrips) {
117+
this.renderStrips = renderStrips;
118+
}
113119

114120
/**
115121
* Constructs a new PeasyGradients renderer from a running Processing sketch;
@@ -194,22 +200,20 @@ public void setRenderTarget(PImage g) {
194200
* @param height height of region to render gradients into
195201
*/
196202
public void setRenderTarget(PImage g, int offSetX, int offSetY, int width, int height) {
197-
if (offSetX < 0 || offSetY < 0 || (width + offSetX) > g.width || (offSetY + height) > g.height) {
198-
System.err.println("Invalid parameters.");
199-
return;
200-
}
203+
renderOffsetX = Math.max(0, offSetX); // Offset X cannot be less than 0
204+
renderOffsetX = Math.min(renderOffsetX, g.width - 1); // Offset X cannot be beyond (image width - 1)
205+
renderOffsetY = Math.max(0, offSetY); // Offset Y cannot be less than 0
206+
renderOffsetY = Math.min(renderOffsetY, g.height - 1); // Offset Y cannot be beyond (image height - 1)
201207

202-
final int actualWidth = width - offSetX;
203-
final int actualHeight = height - offSetY;
208+
// 2. Constrain Width and Height based on Constrained Offsets:
209+
renderWidth = Math.max(0, width); // Width cannot be negative
210+
renderWidth = Math.min(renderWidth, g.width - renderOffsetX); // Width cannot extend beyond the image's right edge
211+
renderHeight = Math.max(0, height); // Height cannot be negative
212+
renderHeight = Math.min(renderHeight, g.height - renderOffsetY); // Height cannot extend beyond the image's bottom edge
204213

205214
scaleX = g.width / (double) width; // used for correct rendering increment for some gradients
206215
scaleY = g.height / (double) height; // used for correct rendering increment for some gradients
207216

208-
renderWidth = width;
209-
renderHeight = height;
210-
renderOffsetX = offSetX;
211-
renderOffsetY = offSetY;
212-
213217
if (!g.isLoaded()) { // load pixel array if not already done
214218
if (g instanceof PGraphics) {
215219
((PGraphics) g).beginDraw();
@@ -220,7 +224,7 @@ public void setRenderTarget(PImage g, int offSetX, int offSetY, int width, int h
220224

221225
gradientPG = g;
222226

223-
gradientCacheSize = (3 * Math.max(actualWidth, actualHeight));
227+
gradientCacheSize = (3 * Math.max(renderWidth, renderHeight));
224228
gradientCache = new int[gradientCacheSize];
225229
}
226230

@@ -403,7 +407,6 @@ public void linearGradient(Gradient gradient, PVector controlPoint1, PVector con
403407
double odY = controlPoint2.y - controlPoint1.y; // Rise and run of line.
404408
final double odSqInverse = 1 / (odX * odX + odY * odY); // Distance-squared of line.
405409
double opXod = -controlPoint1.x * odX + -controlPoint1.y * odY;
406-
407410
makeThreadPool(gradient, renderStrips, LinearThread.class, odX, odY, odSqInverse, opXod);
408411

409412
gradientPG.updatePixels();
@@ -849,8 +852,8 @@ public void hourglassGradient(Gradient gradient, PVector centerPoint, double ang
849852

850853
/**
851854
* Creates a pool of threads to split the rendering work for the given gradient
852-
* type (each thread works on a portion of the pixels array). This method starts
853-
* the threads and returns when all threads have completed.
855+
* type (each thread works on a horizontal strip portion of the pixels array).
856+
* This method starts the threads and returns when all threads have completed.
854857
*
855858
* @param gradient TODO
856859
* @param partitionsY
@@ -879,14 +882,15 @@ private void makeThreadPool(Gradient gradient, final int partitionsY, final Clas
879882
// division)
880883
int rows = renderHeight / partitionsY;
881884
for (int strip = 0; strip < partitionsY - 1; strip++) {
882-
fullArgs[1] = rows * strip;
883-
fullArgs[2] = rows;
885+
fullArgs[1] = rows * strip; // row vertical offset (y coord to start rendering at)
886+
fullArgs[2] = rows; // row count (height of horizontal strip)
884887
RenderThread thread = constructor.newInstance(fullArgs);
885888
taskList.add(thread);
886889
}
887890

888891
fullArgs[1] = rows * (partitionsY - 1);
889892
fullArgs[2] = renderHeight - rows * (partitionsY - 1);
893+
890894
RenderThread thread = constructor.newInstance(fullArgs);
891895
taskList.add(thread);
892896

@@ -941,7 +945,7 @@ private double interleavedGradientNoise(final int x, final int y) {
941945
}
942946

943947
/**
944-
* Threads operate on a portion of the pixels grid.
948+
* Threads operate on a portion (horizontal strip) of the pixels grid.
945949
*
946950
* RenderThread child classes will implement call(); here the parallel gradient
947951
* rendering work is done.
@@ -963,7 +967,7 @@ private abstract class RenderThread implements Callable<Boolean> {
963967
RenderThread(int rowOffset, int rows) {
964968
this.rowOffset = rowOffset;
965969
this.rows = rows;
966-
pixel = rowOffset * renderWidth;
970+
pixel = (rowOffset + renderOffsetY) * gradientPG.width; // Start at the correct global row, only considering renderOffsetY here.
967971
}
968972
}
969973

@@ -987,19 +991,20 @@ public Boolean call() {
987991
* Usually we'd call Functions.linearProject() to calculate step at each pixel,
988992
* but the function is inlined here to optimise speed.
989993
*/
990-
opXod += rowOffset * odY * scaleY; // offset for thread
994+
opXod += rowOffset * odY * scaleY;
991995
for (int y = rowOffset; y < rowOffset + rows; y++) {
992996
opXod += odY * scaleY;
993-
double xOff = 0; // set partition x offset to correct amount
997+
// Add renderOffsetX only at the start of each row.
998+
pixel += renderOffsetX;
994999
for (int x = 0; x < renderWidth; x++) {
995-
double step = (opXod + xOff) * odSqInverse; // get position of point on 1D gradient and normalise
996-
xOff += odX * scaleX;
1000+
double step = (opXod + x * odX * scaleX) * odSqInverse;
9971001

9981002
int stepInt = clampAndDither(step, x, y);
9991003
gradientPG.pixels[pixel++] = gradientCache[stepInt];
10001004
}
1005+
// After rendering a row, jump to the beginning of the next row.
1006+
pixel += gradientPG.width - (renderWidth + renderOffsetX);
10011007
}
1002-
10031008
return true;
10041009
}
10051010

@@ -1022,6 +1027,7 @@ public Boolean call() {
10221027
for (int y = rowOffset; y < rowOffset + rows; y++) {
10231028
double rise = renderMidpointY - y;
10241029
rise *= rise;
1030+
pixel += renderOffsetX;
10251031
for (int x = 0; x < renderWidth; x++) {
10261032

10271033
double run = renderMidpointX - x;
@@ -1033,6 +1039,7 @@ public Boolean call() {
10331039
int stepInt = clampAndDither(dist, x, y);
10341040
gradientPG.pixels[pixel++] = gradientCache[stepInt];
10351041
}
1042+
pixel += gradientPG.width - (renderWidth + renderOffsetX);
10361043
}
10371044
return true;
10381045
}
@@ -1059,7 +1066,8 @@ public Boolean call() {
10591066

10601067
for (int y = rowOffset; y < rowOffset + rows; y++) {
10611068
rise = renderMidpointY - y;
1062-
for (int x = 0; x < gradientPG.width; x++) { // FULL WIDTH
1069+
pixel += renderOffsetX;
1070+
for (int x = 0; x < renderWidth; x++) { // FULL WIDTH
10631071
run = renderMidpointX - x;
10641072
t = Functions.fastAtan2b(rise, run) + Math.PI - angle; // + PI to align bump with angle
10651073
t *= INV_TWO_PI; // normalise
@@ -1069,6 +1077,7 @@ public Boolean call() {
10691077
gradientPG.pixels[pixel++] = gradientCache[stepInt];
10701078

10711079
}
1080+
pixel += gradientPG.width - (renderWidth + renderOffsetX);
10721081
}
10731082

10741083
return true;
@@ -1107,15 +1116,16 @@ public Boolean call() throws Exception {
11071116
double t;
11081117
double spiralOffset = 0;
11091118
for (int y = rowOffset; y < rowOffset + rows; y++) {
1119+
pixel += renderOffsetX;
11101120
double rise = renderMidpointY - y;
11111121
final double riseSquared = rise * rise;
1112-
for (int x = 0; x < gradientPG.width; x++) { // FULL WIDTH
1122+
for (int x = 0; x < renderWidth; x++) { // FULL WIDTH
11131123

11141124
double run = renderMidpointX - x;
11151125
t = Functions.fastAtan2b(rise, run) - angle; // -PI...PI
11161126
spiralOffset = curviness == 0.5f ? Math.sqrt((riseSquared + run * run) * curveDenominator)
11171127
: FastPow.fastPow((riseSquared + run * run) * curveDenominator, curviness);
1118-
spiralOffset*=curveCount;
1128+
spiralOffset *= curveCount;
11191129
t += spiralOffset;
11201130

11211131
t *= INV_TWO_PI; // normalise
@@ -1125,6 +1135,7 @@ public Boolean call() throws Exception {
11251135

11261136
gradientPG.pixels[pixel++] = gradientCache[stepInt];
11271137
}
1138+
pixel += gradientPG.width - (renderWidth + renderOffsetX);
11281139
}
11291140

11301141
return true;
@@ -1152,6 +1163,7 @@ public Boolean call() {
11521163
double xDist; // x distance between midpoint and a given pixel
11531164

11541165
for (int y = rowOffset; y < rowOffset + rows; y++) {
1166+
pixel += renderOffsetX;
11551167
yDist = (renderMidpointY - y);
11561168
xDist = renderMidpointX;
11571169
for (int x = 0; x < renderWidth; x++) {
@@ -1167,6 +1179,7 @@ public Boolean call() {
11671179
final int stepInt = clampAndDither(dist, x, y);
11681180
gradientPG.pixels[pixel++] = gradientCache[stepInt];
11691181
}
1182+
pixel += gradientPG.width - (renderWidth + renderOffsetX);
11701183
}
11711184

11721185
return true;
@@ -1193,6 +1206,7 @@ private final class CrossThread extends RenderThread {
11931206
public Boolean call() {
11941207

11951208
for (int y = rowOffset; y < rowOffset + rows; y++) {
1209+
pixel += renderOffsetX;
11961210
final double yTranslate = (y - renderMidpointY);
11971211
for (int x = 0; x < renderWidth; x++) {
11981212
final double newXpos = (x - renderMidpointX) * cos - yTranslate * sin + renderMidpointX; // rotate x about midpoint
@@ -1203,6 +1217,7 @@ public Boolean call() {
12031217
final int stepInt = clampAndDither(dist, x, y);
12041218
gradientPG.pixels[pixel++] = gradientCache[stepInt];
12051219
}
1220+
pixel += gradientPG.width - (renderWidth + renderOffsetX);
12061221
}
12071222

12081223
return true;
@@ -1229,6 +1244,7 @@ private final class DiamondThread extends RenderThread {
12291244
public Boolean call() {
12301245

12311246
for (int y = rowOffset; y < rowOffset + rows; y++) {
1247+
pixel += renderOffsetX;
12321248
final double yTranslate = (y - renderMidpointY);
12331249
for (int x = 0; x < renderWidth; x++) {
12341250
final double newXpos = (x - renderMidpointX) * cos - yTranslate * sin + renderMidpointX; // rotate x about midpoint
@@ -1239,6 +1255,7 @@ public Boolean call() {
12391255
final int stepInt = clampAndDither(dist, x, y);
12401256
gradientPG.pixels[pixel++] = gradientCache[stepInt];
12411257
}
1258+
pixel += gradientPG.width - (renderWidth + renderOffsetX);
12421259
}
12431260

12441261
return true;
@@ -1262,8 +1279,9 @@ private class NoiseThread extends RenderThread {
12621279
public Boolean call() {
12631280

12641281
for (int y = rowOffset; y < rowOffset + rows; y++) {
1282+
pixel += renderOffsetX;
12651283
final double yTranslate = (y - centerPoint.y);
1266-
for (int x = 0; x < gradientPG.width; x++) {
1284+
for (int x = 0; x < renderWidth; x++) {
12671285
double newXpos = (x - centerPoint.x) * cos - yTranslate * sin + centerPoint.x; // rotate x about midpoint
12681286
double newYpos = yTranslate * cos + (x - centerPoint.x) * sin + centerPoint.y; // rotate y about midpoint
12691287

@@ -1272,6 +1290,7 @@ public Boolean call() {
12721290
final int stepInt = clampAndDither(step, x, y);
12731291
gradientPG.pixels[pixel++] = gradientCache[stepInt];
12741292
}
1293+
pixel += gradientPG.width - (renderWidth + renderOffsetX);
12751294
}
12761295

12771296
return true;
@@ -1294,8 +1313,9 @@ private final class UniformNoiseThread extends NoiseThread {
12941313
public Boolean call() {
12951314

12961315
for (int y = rowOffset; y < rowOffset + rows; y++) {
1316+
pixel += renderOffsetX;
12971317
final double yTranslate = (y - centerPoint.y);
1298-
for (int x = 0; x < gradientPG.width; x++) {
1318+
for (int x = 0; x < renderWidth; x++) {
12991319
double newXpos = (x - centerPoint.x) * cos - yTranslate * sin + centerPoint.x; // rotate x about midpoint
13001320
double newYpos = yTranslate * cos + (x - centerPoint.x) * sin + centerPoint.y; // rotate y about midpoint
13011321

@@ -1304,6 +1324,7 @@ public Boolean call() {
13041324
final int stepInt = clampAndDither(step, x, y);
13051325
gradientPG.pixels[pixel++] = gradientCache[stepInt];
13061326
}
1327+
pixel += gradientPG.width - (renderWidth + renderOffsetX);
13071328
}
13081329

13091330
return true;
@@ -1330,8 +1351,9 @@ private final class FractalNoiseThread extends RenderThread {
13301351
public Boolean call() {
13311352

13321353
for (int y = rowOffset; y < rowOffset + rows; y++) {
1354+
pixel += renderOffsetX;
13331355
final double yTranslate = (y - centerPoint.y);
1334-
for (int x = 0; x < gradientPG.width; x++) {
1356+
for (int x = 0; x < renderWidth; x++) {
13351357
double newXpos = (x - centerPoint.x) * cos - yTranslate * sin + centerPoint.x; // rotate x about midpoint
13361358
double newYpos = yTranslate * cos + (x - centerPoint.x) * sin + centerPoint.y; // rotate y about midpoint
13371359

@@ -1341,6 +1363,7 @@ public Boolean call() {
13411363
final int stepInt = clampAndDither(step, x, y);
13421364
gradientPG.pixels[pixel++] = gradientCache[stepInt];
13431365
}
1366+
pixel += gradientPG.width - (renderWidth + renderOffsetX);
13441367
}
13451368

13461369
return true;
@@ -1368,6 +1391,7 @@ private final class SpotlightThread extends RenderThread {
13681391
public Boolean call() {
13691392

13701393
for (int y = rowOffset; y < rowOffset + rows; y++) {
1394+
pixel += renderOffsetX;
13711395
final double yTranslate = (y - originPoint.y);
13721396
for (int x = 0; x < renderWidth; x++) {
13731397
double newXpos = (x - originPoint.x) * cos - yTranslate * sin + originPoint.x; // rotate x about midpoint
@@ -1392,6 +1416,7 @@ public Boolean call() {
13921416
int stepInt = clampAndDither(step, x, y);
13931417
gradientPG.pixels[pixel++] = gradientCache[stepInt];
13941418
}
1419+
pixel += gradientPG.width - (renderWidth + renderOffsetX);
13951420
}
13961421

13971422
return true;
@@ -1428,6 +1453,7 @@ public Boolean call() {
14281453
double xDist;
14291454

14301455
for (int y = rowOffset; y < rowOffset + rows; y++) {
1456+
pixel += renderOffsetX;
14311457
yDist = (renderMidpointY - y) * (renderMidpointY - y);
14321458
final double yTranslate = (y - renderMidpointY);
14331459
for (int x = 0; x < renderWidth; x++) {
@@ -1452,6 +1478,7 @@ public Boolean call() {
14521478
final int stepInt = clampAndDither(dist, x, y);
14531479
gradientPG.pixels[pixel++] = gradientCache[stepInt];
14541480
}
1481+
pixel += gradientPG.width - (renderWidth + renderOffsetX);
14551482
}
14561483

14571484
return true;

0 commit comments

Comments
 (0)