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 & conic sampling algorithms adapted from Jeremy
56- * Behreandt. Additional sampling patterns are original implementations.
57+ * Linear, radial & 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