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}