001/*
002 * Renderer 4. The MIT License.
003 * Copyright (c) 2022 rlkraft@pnw.edu
004 * See LICENSE for details.
005*/
006
007package renderer.pipeline;
008
009import renderer.scene.*;
010import renderer.scene.primitives.LineSegment;
011import renderer.framebuffer.*;
012import static renderer.pipeline.PipelineLogger.*;
013
014import java.awt.Color;
015
016/**
017   Rasterize a projected {@link LineSegment} into shaded pixels
018   in a {@link FrameBuffer.Viewport} and (optionally) anti-alias
019   and gamma-encode the line at the same time. Also, (optionally)
020   do not rasterize any part of the {@link LineSegment} that is
021   not contained in the {@link Camera}'s view rectangle.
022<p>
023   This pipeline stage takes a {@link LineSegment} whose vertices
024   have been projected into the {@link Camera}'s view plane
025   coordinate system and rasterizes the {@link LineSegment}
026   into shaded, anti-aliased pixels in a {@link FrameBuffer.Viewport}.
027<p>
028   In addition, this rasterizer has the option to "clip" the
029   {@link LineSegment} by not rasterizing into the
030   {@link FrameBuffer.Viewport} any part of the projected
031   {@link LineSegment} that is not within the {@link Camera}'s
032   view rectangle.
033<p>
034   This rasterization algorithm is based on
035<pre>
036     "Fundamentals of Computer Graphics", 3rd Edition,
037      by Peter Shirley, pages 163-165.
038</pre>
039<p>
040   This rasterizer implements a simple version of Xiaolin_Wu's
041   anti-aliasing algorithm. See
042     <a href="https://en.wikipedia.org/wiki/Xiaolin_Wu's_line_algorithm" target="_top">
043              https://en.wikipedia.org/wiki/Xiaolin_Wu's_line_algorithm</a>
044<p>
045   Recall that a {@link FrameBuffer.Viewport} is a two-dimensional array
046   of pixel data. So a viewport has an integer "coordinate system". That
047   is, we locate a pixel in a viewport using two integers, which we think
048   of as row and column. On the other hand, a {@link Camera}'s view rectangle
049   has a two-dimensional real number (not integer) coordinate system. Since
050   a framebuffer's viewport and a camera's view rectangle have such different
051   coordinate systems, rasterizing line segments from a two-dimensional real
052   number coordinate system to an two-dimensional integer grid can be tricky.
053   The "logical pixel-plane" and the "viewport transformation" try to make
054   this rasterization step a bit easier.
055<pre>{@code
056                                 (0,0)
057                                   +-------------------------------------------+
058        y-axis                     |-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
059          |                        |-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
060          |  (+1,+1)               |-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
061    +-----|-----+                  |-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
062    |     |     |                  |-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
063    |     |     |                  |-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
064----------+----------- x-axis      |-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
065    |     |     |                  |-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
066    |     |     |                  |-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
067    +-----|-----+                  |-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
068 (-1,-1)  |                        |-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
069          |                        |-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
070                                   |-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
071 Camera's View Rectangle           +-------------------------------------------+
072  (in the View Plane)                                                      (w-1,h-1)
073                                              FrameBuffer's Viewport
074}</pre>
075<p>
076   The viewport transformation places the logical pixel-plane between the
077   camera's view rectangle and the framebuffer's viewport. The pixel-plane
078   has a real number coordinate system (like the camera's view plane) but
079   is has dimensions more like the dimensions of the framebuffer's viewport.
080<pre>{@code
081                                                    (w+0.5, h+0.5)
082           +----------------------------------------------+
083           | . . . . . . . . . . . . . . . . . . . . . .  |
084           | . . . . . . . . . . . . . . . . . . . . . .  |
085           | . . . . . . . . . . . . . . . . . . . . . .  |
086           | . . . . . . . . . . . . . . . . . . . . . .  |
087           | . . . . . . . . . . . . . . . . . . . . . .  |  The "logical pixels"
088           | . . . . . . . . . . . . . . . . . . . . . .  |  are the points in
089           | . . . . . . . . . . . . . . . . . . . . . .  |  the pixel-plane with
090           | . . . . . . . . . . . . . . . . . . . . . .  |  integer coordinates.
091           | . . . . . . . . . . . . . . . . . . . . . .  |
092           | . . . . . . . . . . . . . . . . . . . . . .  |
093           | . . . . . . . . . . . . . . . . . . . . . .  |
094           | . . . . . . . . . . . . . . . . . . . . . .  |
095           | . . . . . . . . . . . . . . . . . . . . . .  |
096           +----------------------------------------------+
097      (0.5, 0.5)
098                 pixel-plane's "logical viewport"
099                   containing "logical pixels"
100   }</pre>
101<p>
102   Notice that we have two uses of the word "viewport",
103   <ul>
104   <li>The "logical viewport" is a rectangle in the pixel-plane (so
105       its points have real number coordinates). The "logical pixels"
106       are the points in the logical viewport with integer coordinates.
107   <li>The "physical viewport" is part of the {@link FrameBuffer}'s pixel
108       array (so its entries have integer coordinates). The "physical
109       pixels" are the entries in the physical viewport.
110   </ul>
111*/
112public final class Rasterize_Clip_AntiAlias_Line
113{
114   /**
115      Rasterize and (possibly) clip a projected {@link LineSegment} into pixels
116      in the {@link FrameBuffer.Viewport}.
117
118      @param model  {@link Model} that the {@link LineSegment} {@code ls} comes from
119      @param ls     {@link LineSegment} to rasterize into the {@link FrameBuffer.Viewport}
120      @param vp     {@link FrameBuffer.Viewport} to hold rasterized pixels
121   */
122   public static void rasterize(final Model model,
123                                final LineSegment ls,
124                                final FrameBuffer.Viewport vp)
125   {
126      final String     CLIPPED = "Clip: ";
127      final String NOT_CLIPPED = "      ";
128
129      // Get the viewport's background color.
130      final Color bg = vp.bgColorVP;
131
132      // Make local copies of several values.
133      final int w = vp.getWidthVP();
134      final int h = vp.getHeightVP();
135
136      final int vIndex0 = ls.vIndexList.get(0);
137      final int vIndex1 = ls.vIndexList.get(1);
138      final Vertex v0 = model.vertexList.get(vIndex0);
139      final Vertex v1 = model.vertexList.get(vIndex1);
140
141      final int cIndex0 = ls.cIndexList.get(0);
142      final int cIndex1 = ls.cIndexList.get(1);
143      final float[] c0 = model.colorList.get(cIndex0).getRGBComponents(null);
144      final float[] c1 = model.colorList.get(cIndex1).getRGBComponents(null);
145      float r0 = c0[0], g0 = c0[1], b0 = c0[2];
146      float r1 = c1[0], g1 = c1[1], b1 = c1[2];
147
148      // Round each point's coordinates to the nearest logical pixel.
149      double x0 = Math.round(v0.x);
150      double y0 = Math.round(v0.y);
151      double x1 = Math.round(v1.x);
152      double y1 = Math.round(v1.y);
153
154      if (Rasterize.debug)
155      {
156         logMessage(String.format("(x0_pp, y0_pp) = (%9.4f, %9.4f)", x0,y0));
157         logMessage(String.format("(x1_pp, y1_pp) = (%9.4f, %9.4f)", x1,y1));
158      }
159
160      // Round each vertex to the nearest logical pixel. This
161      // makes the algorithm a lot simpler, but it can cause
162      // a slight, but noticeable, shift of the line segment.
163      x0 = Math.round(x0);
164      y0 = Math.round(y0);
165      x1 = Math.round(x1);
166      y1 = Math.round(y1);
167
168      // Rasterize a degenerate line segment (a line segment
169      // that projected onto a single point) as a single pixel.
170      if ( (x0 == x1) && (y0 == y1) )
171      {
172         // We don't know which endpoint of the line segment
173         // is in front, so just pick v0.
174         final int x0_vp = (int)x0 - 1;  // viewport coordinate
175         final int y0_vp = h - (int)y0;  // viewport coordinate
176
177         if (Rasterize.debug)
178         {
179            final String clippedMessage;
180            if ( ! Rasterize.doClipping
181              || (x0_vp >= 0 && x0_vp < w && y0_vp >= 0 && y0_vp < h) ) // clipping test
182            {
183               clippedMessage = NOT_CLIPPED;
184            }
185            else
186            {
187               clippedMessage = CLIPPED;
188            }
189            logPixel(clippedMessage, x0, y0, x0_vp, y0_vp, r0, g0, b0, vp);
190         }
191         // Log the pixel before setting it so that an array out-
192         // of-bounds error will be right after the pixel's address.
193
194         if ( ! Rasterize.doClipping
195           || (x0_vp >= 0 && x0_vp < w && y0_vp >= 0 && y0_vp < h) ) // clipping test
196         {
197            vp.setPixelVP(x0_vp, y0_vp, new Color(r0, g0, b0));
198         }
199         return;
200      }
201
202      // If abs(slope) <= 1, then rasterize this line in
203      // the direction of the x-axis. Otherwise, rasterize
204      // this line segment in the direction of the y-axis.
205      if (Math.abs(y1 - y0) <= Math.abs(x1 - x0)) // if abs(slope) <= 1
206      {
207         if (x1 < x0) // We want to rasterize along the x-axis from left-to-right,
208         {            // so, if necessary, swap (x0, y0) with (x1, y1).
209            final double tempX = x0;
210            final double tempY = y0;
211            x0 = x1;
212            y0 = y1;
213            x1 = tempX;
214            y1 = tempY;
215            // Swap the colors too.
216            final float tempR = r0;
217            final float tempG = g0;
218            final float tempB = b0;
219            r0 = r1;
220            g0 = g1;
221            b0 = b1;
222            r1 = tempR;
223            g1 = tempG;
224            b1 = tempB;
225         }
226
227         // Compute this line segment's slopes.
228         final double      m = (y1 - y0) / (x1 - x0);
229         final double slopeR = (r1 - r0) / (x1 - x0);
230         final double slopeG = (g1 - g0) / (x1 - x0);
231         final double slopeB = (b1 - b0) / (x1 - x0);
232
233         if (Rasterize.debug)
234         {
235            logMessage("Slope m    = " + m);
236            logMessage("Slope mRed = " + slopeR);
237            logMessage("Slope mGrn = " + slopeG);
238            logMessage("Slope mBlu = " + slopeB);
239            logMessage(String.format("(x0_vp, y0_vp) = (%9.4f, %9.4f)", x0-1,h-y0));
240            logMessage(String.format("(x1_vp, y1_vp) = (%9.4f, %9.4f)", x1-1,h-y1));
241         }
242
243         // Rasterize this line segment, along the x-axis, from left-to-right.
244         // In the following loop, as x moves across the logical
245         // horizontal pixels, we compute a y value for each x.
246         double y = y0;
247         for (int x = (int)x0; x <= (int)x1; x += 1, y += m)
248         {
249            // Interpolate this pixel's color between the two endpoint's colors.
250            float r = (float)Math.abs(r0 + slopeR*(x - x0));
251            float g = (float)Math.abs(g0 + slopeG*(x - x0));
252            float b = (float)Math.abs(b0 + slopeB*(x - x0));
253            // We need the Math.abs() because otherwise, we sometimes get -0.0.
254
255            if (Rasterize.doAntiAliasing)
256            {
257               // y must be between two vertical logical-pixel coordinates.
258               // Let y_low and y_hi be the logical-pixel coordinates
259               // that bracket around y.
260               int y_low = (int)Math.floor(y);
261               int y_hi = y_low + 1;
262
263               // Let weight be the distance from y to its floor (when
264               // y is positive, this is the fractional part of y). We
265               // will use weight to determine how much emphasis to place
266               // on each of the two logical-pixels that bracket y.
267               final float weight = (float)(y - y_low);
268
269               // Interpolate colors for the low and high pixels.
270               // The smaller weight is, the closer y is to the lower
271               // pixel, so we give the lower pixel more emphasis when
272               // weight is small.
273               float r_low = (1 - weight) * r + weight * (bg.getRed()  /255.0f);
274               float g_low = (1 - weight) * g + weight * (bg.getGreen()/255.0f);
275               float b_low = (1 - weight) * b + weight * (bg.getBlue() /255.0f);
276               float r_hi  = weight * r + (1 - weight) * (bg.getRed()  /255.0f);
277               float g_hi  = weight * g + (1 - weight) * (bg.getGreen()/255.0f);
278               float b_hi  = weight * b + (1 - weight) * (bg.getBlue() /255.0f);
279/*
280               // You can try replacing the above anti-aliasing code with this
281               // code to see that this simple idea doesn't work here (as it
282               // did in the previous renderer). This code just distributes the
283               // line's color between two adjacent pixels (instead of blending
284               // each pixel's color with the back ground color). This code ends
285               // up having pixels fade to black, instead of fading to the back
286               // ground color.
287               float r_low = (1 - weight) * r;
288               float g_low = (1 - weight) * g;
289               float b_low = (1 - weight) * b;
290               float r_hi  = weight * r;
291               float g_hi  = weight * g;
292               float b_hi  = weight * b;
293*/
294               if (Rasterize.doGamma)
295               {
296                  // Apply gamma-encoding (gamma-compression) to the two colors.
297                  // https://www.scratchapixel.com/lessons/digital-imaging/digital-images
298                  // http://blog.johnnovak.net/2016/09/21/what-every-coder-should-know-about-gamma/
299                  final double gammaInv = 1.0 / Rasterize.GAMMA;
300                  r_low = (float)Math.pow(r_low, gammaInv);
301                  g_low = (float)Math.pow(g_low, gammaInv);
302                  b_low = (float)Math.pow(b_low, gammaInv);
303                  r_hi  = (float)Math.pow(r_hi,  gammaInv);
304                  g_hi  = (float)Math.pow(g_hi,  gammaInv);
305                  b_hi  = (float)Math.pow(b_hi,  gammaInv);
306               }
307
308               final int x_vp     = x - 1;      // viewport coordinate
309               final int y_vp_low = h - y_low;  // viewport coordinate
310               final int y_vp_hi  = h - y_hi;   // viewport coordinate
311
312               if (Rasterize.debug)
313               {
314                  final String clippedMessage;
315                  if ( ! Rasterize.doClipping
316                    || (x_vp >= 0 && x_vp < w && y_vp_hi >= 0 && y_vp_low < h) ) // clipping test
317                  {
318                     clippedMessage = NOT_CLIPPED;
319                  }
320                  else // at least one of the two pixels is clipped off
321                  {
322                     clippedMessage = CLIPPED;
323                  }
324                  logPixelsAA(clippedMessage,
325                              x, y,
326                              x_vp, y_vp_low, y_vp_hi,
327                              r_low, g_low, b_low,
328                              r_hi,  g_hi,  b_hi,
329                              vp);
330               }
331               // Log the pixel before setting it so that an array out-
332               // of-bounds error will be right after the pixel's address.
333
334               // Test each pixel, both the hi and the low one, for clipping.
335               if ( ! Rasterize.doClipping
336                 || (x_vp >= 0 && x_vp < w && y_vp_low >= 0 && y_vp_low < h) ) // clipping test
337               {
338                  vp.setPixelVP(x_vp, y_vp_low, new Color(r_low, g_low, b_low));
339               }
340               if ( ! Rasterize.doClipping
341                 || (x_vp >= 0 && x_vp < w && y_vp_hi >= 0 && y_vp_hi < h) ) // clipping test
342               {
343                  vp.setPixelVP(x_vp, y_vp_hi, new Color(r_hi,  g_hi,  b_hi));
344               }
345            }
346            else // No anti-aliasing.
347            {
348               if (Rasterize.doGamma)
349               {
350                  // Apply gamma-encoding (gamma-compression) to the colors.
351                  // https://www.scratchapixel.com/lessons/digital-imaging/digital-images
352                  // http://blog.johnnovak.net/2016/09/21/what-every-coder-should-know-about-gamma/
353                  final double gammaInv = 1.0 / Rasterize.GAMMA;
354                  r = (float)Math.pow(r, gammaInv);
355                  g = (float)Math.pow(g, gammaInv);
356                  b = (float)Math.pow(b, gammaInv);
357               }
358
359               // The value of y will almost always be between
360               // two vertical logical-pixel coordinates. By rounding
361               // off the value of y, we are choosing the nearest
362               // logical vertical pixel coordinate.
363               final int x_vp = x - 1;                  // viewport coordinate
364               final int y_vp = h - (int)Math.round(y); // viewport coordinate
365
366               if (Rasterize.debug)
367               {
368                  final String clippedMessage;
369                  if ( ! Rasterize.doClipping
370                    || (x_vp >= 0 && x_vp < w && y_vp >= 0 && y_vp < h) ) // clipping test
371                  {
372                     clippedMessage = NOT_CLIPPED;
373                  }
374                  else
375                  {
376                     clippedMessage = CLIPPED;
377                  }
378                  logPixel(clippedMessage, x, y, x_vp, y_vp, r, g, b, vp);
379               }
380               // Log the pixel before setting it so that an array out-
381               // of-bounds error will be right after the pixel's address.
382
383               if ( ! Rasterize.doClipping
384                 || (x_vp >= 0 && x_vp < w && y_vp >= 0 && y_vp < h) ) // clipping test
385               {
386                  vp.setPixelVP(x_vp, y_vp, new Color(r, g, b));
387               }
388            }
389         }// Advance (x,y) to the next pixel. Since delta_x = 1, we need delta_y = m.
390      }
391      else // abs(slope) > 1, so rasterize along the y-axis.
392      {
393         if (y1 < y0) // We want to rasterize along the y-axis from bottom-to-top,
394         {            // so, if necessary, swap (x0, y0) with (x1, y1).
395            final double tempX = x0;
396            final double tempY = y0;
397            x0 = x1;
398            y0 = y1;
399            x1 = tempX;
400            y1 = tempY;
401            // Swap the colors too.
402            final float tempR = r0;
403            final float tempG = g0;
404            final float tempB = b0;
405            r0 = r1;
406            g0 = g1;
407            b0 = b1;
408            r1 = tempR;
409            g1 = tempG;
410            b1 = tempB;
411         }
412
413         // Compute this line segment's slopes.
414         final double      m = (x1 - x0) / (y1 - y0);
415         final double slopeR = (r1 - r0) / (y1 - y0);
416         final double slopeG = (g1 - g0) / (y1 - y0);
417         final double slopeB = (b1 - b0) / (y1 - y0);
418
419         if (Rasterize.debug)
420         {
421            logMessage("Slope m    = " + m + " (so 1/m = " + 1/m + ")");
422            logMessage("Slope mRed = " + slopeR);
423            logMessage("Slope mGrn = " + slopeG);
424            logMessage("Slope mBlu = " + slopeB);
425            logMessage(String.format("(x0_vp, y0_vp) = (%9.4f, %9.4f)", x0-1,h-y0));
426            logMessage(String.format("(x1_vp, y1_vp) = (%9.4f, %9.4f)", x1-1,h-y1));
427         }
428
429         // Rasterize this line segment, along the y-axis, from bottom-to-top.
430         // In the following loop, as y moves across the logical
431         // vertical pixels, we compute a x value for each y.
432         double x = x0;
433         for (int y = (int)y0; y <= (int)y1; x += m, y += 1)
434         {
435            // Interpolate this pixel's color between the two endpoint's colors.
436            float r = (float)Math.abs(r0 + slopeR*(y - y0));
437            float g = (float)Math.abs(g0 + slopeG*(y - y0));
438            float b = (float)Math.abs(b0 + slopeB*(y - y0));
439            // We need the Math.abs() because otherwise, we sometimes get -0.0.
440
441            if (Rasterize.doAntiAliasing)
442            {
443               // x must be between two horizontal logical-pixel coordinates.
444               // Let x_left and x_right be the logical-pixel coordinates
445               // that bracket around x.
446               int x_left = (int)Math.floor(x);
447               int x_right = x_left + 1;
448
449               // Let weight be the distance from x to its floor (when
450               // x is positive, this is the fractional part of x). We
451               // will use weight to determine how much emphasis to place
452               // on each of the two logical-pixels that bracket x.
453               final float weight = (float)(x - x_left);
454
455               // Interpolate colors for the left and right pixels.
456               // The smaller weight is, the closer y is to the left
457               // pixel, so we give the left pixel more emphasis when
458               // weight is small.
459               float r_left  = (1 - weight) * r + weight * (bg.getRed()  /255.0f);
460               float g_left  = (1 - weight) * g + weight * (bg.getGreen()/255.0f);
461               float b_left  = (1 - weight) * b + weight * (bg.getBlue() /255.0f);
462               float r_right = weight * r + (1 - weight) * (bg.getRed()  /255.0f);
463               float g_right = weight * g + (1 - weight) * (bg.getGreen()/255.0f);
464               float b_right = weight * b + (1 - weight) * (bg.getBlue() /255.0f);
465/*
466               // You can try replacing the above anti-aliasing code with this
467               // code to see that this simple idea doesn't work here (as it
468               // did in the previous renderer). This code just distributes the
469               // line's color between two adjacent pixels (instead of blending
470               // each pixel's color with the back ground color). This code ends
471               // up having pixels fade to black, instead of fading to the back
472               // ground color.
473               float r_left  = (1 - weight) * r;
474               float g_left  = (1 - weight) * g;
475               float b_left  = (1 - weight) * b;
476               float r_right = weight * r;
477               float g_right = weight * g;
478               float b_right = weight * b;
479*/
480               if (Rasterize.doGamma)
481               {
482                  // Apply gamma-encoding (gamma-compression) to the two colors.
483                  // https://www.scratchapixel.com/lessons/digital-imaging/digital-images
484                  // http://blog.johnnovak.net/2016/09/21/what-every-coder-should-know-about-gamma/
485                  final double gammaInv = 1.0 / Rasterize.GAMMA;
486                  r_left  = (float)Math.pow(r_left,  gammaInv);
487                  g_left  = (float)Math.pow(g_left,  gammaInv);
488                  b_left  = (float)Math.pow(b_left,  gammaInv);
489                  r_right = (float)Math.pow(r_right, gammaInv);
490                  g_right = (float)Math.pow(g_right, gammaInv);
491                  b_right = (float)Math.pow(b_right, gammaInv);
492               }
493
494               final int x_vp_left  = x_left  - 1;  // viewport coordinate
495               final int x_vp_right = x_right - 1;  // viewport coordinate
496               final int y_vp       = h - y;        // viewport coordinate
497
498               if (Rasterize.debug)
499               {
500                  final String clippedMessage;
501                  if ( ! Rasterize.doClipping
502                    || (x_vp_left >= 0 && x_vp_right < w && y_vp >= 0 && y_vp < h) ) // clipping test
503                  {
504                     clippedMessage = NOT_CLIPPED;
505                  }
506                  else // at least one of the two pixels is clipped off
507                  {
508                     clippedMessage = CLIPPED;
509                  }
510                  logPixelsAA(clippedMessage,
511                              x, y,
512                              x_vp_left, x_vp_right, y_vp,
513                              r_left,  g_left,  b_left,
514                              r_right, g_right, b_right,
515                              vp);
516               }
517               // Log the pixel before setting it so that an array out-
518               // of-bounds error will be right after the pixel's address.
519
520               // Test each pixel, both the left and the right one, for clipping.
521               if ( ! Rasterize.doClipping
522                 || (x_vp_left >= 0 && x_vp_left < w && y_vp >= 0 && y_vp < h) ) // clipping test
523               {
524                  vp.setPixelVP(x_vp_left,  y_vp, new Color(r_left,  g_left,  b_left));
525               }
526               if ( ! Rasterize.doClipping
527                 || (x_vp_right >= 0 && x_vp_right < w && y_vp >= 0 && y_vp < h) ) // clipping test
528               {
529                  vp.setPixelVP(x_vp_right, y_vp, new Color(r_right, g_right, b_right));
530               }
531            }
532            else // No anti-aliasing.
533            {
534               if (Rasterize.doGamma)
535               {
536                  // Apply gamma-encoding (gamma-compression) to the colors.
537                  // https://www.scratchapixel.com/lessons/digital-imaging/digital-images
538                  // http://blog.johnnovak.net/2016/09/21/what-every-coder-should-know-about-gamma/
539                  final double gammaInv = 1.0 / Rasterize.GAMMA;
540                  r = (float)Math.pow(r, gammaInv);
541                  g = (float)Math.pow(g, gammaInv);
542                  b = (float)Math.pow(b, gammaInv);
543               }
544
545               // The value of x will almost always be between
546               // two horizontal logical-pixel coordinates. By rounding
547               // off the value of x, we are choosing the nearest
548               // logical horizontal pixel coordinate.
549               final int x_vp = (int)Math.round(x) - 1; // viewport coordinate
550               final int y_vp = h - y;                  // viewport coordinate
551
552               if (Rasterize.debug)
553               {
554                  final String clippedMessage;
555                  if ( ! Rasterize.doClipping
556                    || (x_vp >= 0 && x_vp < w && y_vp >= 0 && y_vp < h) ) // clipping test
557                  {
558                     clippedMessage = NOT_CLIPPED;
559                  }
560                  else
561                  {
562                     clippedMessage = CLIPPED;
563                  }
564                  logPixel(clippedMessage, x, y, x_vp, y_vp, r, g, b, vp);
565               }
566               // Log the pixel before setting it so that an array out-
567               // of-bounds error will be right after the pixel's address.
568
569               if ( ! Rasterize.doClipping
570                 || (x_vp >= 0 && x_vp < w && y_vp >= 0 && y_vp < h) ) // clipping test
571               {
572                  vp.setPixelVP(x_vp, y_vp, new Color(r, g, b));
573               }
574            }
575         }// Advance (x,y) to the next pixel. Since delta_y = 1, we need delta_x = m.
576      }
577   }
578
579
580
581   // Private default constructor to enforce noninstantiable class.
582   // See Item 4 in "Effective Java", 3rd Ed, Joshua Bloch.
583   private Rasterize_Clip_AntiAlias_Line() {
584      throw new AssertionError();
585   }
586}