001/* 002 * FrameBuffer. The MIT License. 003 * Copyright (c) 2022 rlkraft@pnw.edu 004 * See LICENSE for details. 005*/ 006 007package renderer.framebuffer; 008 009import java.awt.Color; 010import java.awt.Dimension; 011import java.io.FileInputStream; 012import java.io.FileOutputStream; 013import java.io.FileNotFoundException; 014import java.io.IOException; 015import java.io.RandomAccessFile; 016import java.nio.ByteBuffer; 017import java.nio.channels.FileChannel; 018import java.awt.image.BufferedImage; 019import javax.imageio.ImageIO; 020 021/** 022 A {@code FrameBuffer} represents a two-dimensional array of pixel data. 023 The pixel data is stored as a one dimensional array in row-major order. 024 The first row of data should be displayed as the top row of pixels 025 in the image. 026<p> 027 A {@link Viewport} is a two-dimensional sub array of a {@code FrameBuffer}. 028<p> 029 A {@code FrameBuffer} has a default {@link Viewport}. The current {@link Viewport} 030 is represented by its upper-left-hand corner and its lower-right-hand 031 corner. 032<p> 033 {@code FrameBuffer} and {@link Viewport} coordinates act like Java 034 {@link java.awt.Graphics2D} coordinates; the positive x direction is 035 to the right and the positive y direction is downward. 036*/ 037public final class FrameBuffer 038{ 039 public final int width; // framebuffer's width (Java calls this a "blank final") 040 public final int height; // framebuffer's height (Java calls this a "blank final") 041 public final int[] pixel_buffer; // contains each pixel's color data for a rendered frame 042 public Color bgColorFB = Color.black; // default background color 043 public final Viewport vp; // default viewport (another "blank final") 044 045 /** 046 Construct a {@code FrameBuffer} with the given dimensions. 047 <p> 048 Initialize the {@code FrameBuffer} to hold all black pixels. 049 <p> 050 The default {@link Viewport} is the whole {@code FrameBuffer}. 051 052 @param w width of the {@code FrameBuffer}. 053 @param h height of the {@code FrameBuffer}. 054 */ 055 public FrameBuffer(final int w, final int h) { 056 this(w, h, Color.black); 057 } 058 059 060 /** 061 Construct a {@code FrameBuffer} with the given dimensions. 062 <p> 063 Initialize the {@code FrameBuffer} to the given {@link Color}. 064 <p> 065 The default {@link Viewport} is the whole {@code FrameBuffer}. 066 067 @param w width of the {@code FrameBuffer}. 068 @param h height of the {@code FrameBuffer}. 069 @param c background {@link Color} for the {@code FrameBuffer} 070 */ 071 public FrameBuffer(final int w, final int h, final Color c) { 072 this.width = w; // fill in the "blank final" 073 this.height = h; // fill in the "blank final" 074 075 // Create the pixel buffer (fill in the "blank final"). 076 this.pixel_buffer = new int[this.width * this.height]; 077 078 // Initialize the pixel buffer. 079 this.bgColorFB = c; 080 clearFB(c); 081 082 // Create the default viewport. 083 this.vp = this.new Viewport(); 084 } 085 086 087 /** 088 Create a {@code FraameBuffer} from the pixel data of another 089 {@code FrameBuffer}. 090 <p> 091 The size of the new {@code FrameBuffer} will be the size of the 092 source {@link FrameBuffer}. 093 <p> 094 The default {@link Viewport} is the whole {@code FrameBuffer}. 095 096 @param sourceFB {@link FrameBuffer} to use as the source of the pixel data 097 */ 098 public FrameBuffer(final FrameBuffer sourceFB) { 099 100 width = sourceFB.width; // fill in the "blank final" 101 height = sourceFB.height; // fill in the "blank final" 102 103 // Create the pixel buffer (fill in the "blank final"). 104 this.pixel_buffer = new int[width * height]; 105 106 // Read pixel data, one pixel at a time, from the source FrameBuffer. 107 for (int y = 0; y < height; ++y) { 108 for (int x = 0; x < width; ++x) { 109 setPixelFB(x, y, sourceFB.getPixelFB(x,y)); 110 } 111 } 112 113 // Create the default viewport. 114 this.vp = this.new Viewport(); 115 } 116 117 118 /** 119 Create a {@code FrameBuffer} from the pixel data of a {@link Viewport}. 120 <p> 121 The size of the new {@code FrameBuffer} will be the size of the 122 source {@code Viewport}. 123 <p> 124 The default {@link Viewport} is the whole {@code FrameBuffer}. 125 126 @param sourceVP {@link Viewport} to use as the source of the pixel data 127 */ 128 public FrameBuffer(final Viewport sourceVP) { 129 130 width = sourceVP.getWidthVP(); // fill in the "blank final" 131 height = sourceVP.getHeightVP(); // fill in the "blank final" 132 133 // Create the pixel buffer (fill in the "blank final"). 134 this.pixel_buffer = new int[width * height]; 135 136 // Read pixel data, one pixel at a time, from the source Viewport. 137 for (int y = 0; y < height; ++y) { 138 for (int x = 0; x < width; ++x) { 139 setPixelFB(x, y, sourceVP.getPixelVP(x,y)); 140 } 141 } 142 143 // Create the default viewport. 144 this.vp = this.new Viewport(); 145 } 146 147 148 /** 149 Construct a {@code FrameBuffer} from a PPM image file. 150 <p> 151 The size of the {@code FrameBuffer} will be the size of the image. 152 <p> 153 The default {@link Viewport} is the whole {@code FrameBuffer}. 154 <p> 155 This can be used to initialize a {@code FrameBuffer} 156 with a background image. 157 158 @param inputFileName must name a PPM image file with magic number P6. 159 */ 160 public FrameBuffer(final String inputFileName) { 161 FileInputStream fis = null; 162 Dimension fbDim = null; 163 try { 164 fis = new FileInputStream(inputFileName); 165 fbDim = getPPMdimensions(inputFileName, fis); 166 } 167 catch (IOException e) { 168 System.err.printf("ERROR! Could not open %s\n", inputFileName); 169 e.printStackTrace(System.err); 170 System.exit(-1); 171 } 172 173 this.width = fbDim.width; // fill in the "blank final" 174 this.height = fbDim.height; // fill in the "blank final" 175 176 // Create the pixel buffer (fill in the "blank final"). 177 this.pixel_buffer = new int[width * height]; 178 179 // Initialize the pixel buffer. 180 try { 181 setPixels(0, 0, width, height, inputFileName, fis); 182 fis.close(); 183 } 184 catch (IOException e) { 185 System.err.printf("ERROR! Could not read %s\n", inputFileName); 186 e.printStackTrace(System.err); 187 System.exit(-1); 188 } 189 190 // Create the default viewport. 191 this.vp = this.new Viewport(); 192 } 193 194 195 /** 196 Get the pixel data's dimensions from a PPM file. 197 198 @param inputFile must name a PPM image file with magic number P6 199 @param fis input stream to the PPM image file 200 @throws IOException if there is a problem with {@code fis} 201 @return a {@link Dimension} object holding the PPM file's width and height 202 */ 203 private Dimension getPPMdimensions(final String inputFile, final FileInputStream fis) 204 throws IOException { 205 // Read the meta data in a PPM file. 206 // http://stackoverflow.com/questions/2693631/read-ppm-file-and-store-it-in-an-array-coded-with-c 207 // Read image format string "P6". 208 String magicNumber = ""; 209 char c = (char)fis.read(); 210 while (c != '\n') { 211 magicNumber += c; 212 c = (char)fis.read(); 213 } 214 if (! magicNumber.trim().startsWith("P6")) { 215 System.err.printf("ERROR! Improper PPM number in file %s\n", inputFile); 216 System.exit(-1); 217 } 218 219 c = (char)fis.read(); 220 if ( '#' == c ) { // read (and discard) IrfanView comment 221 while (c != '\n') { 222 c = (char)fis.read(); 223 } 224 c = (char)fis.read(); 225 } 226 227 // Read image dimensions. 228 String widthDim = ""; 229 while (c != ' ' && c != '\n') { 230 widthDim += c; 231 c = (char)fis.read(); 232 } 233 234 String heightDim = ""; 235 c = (char)fis.read(); 236 while (c != '\n') { 237 heightDim += c; 238 c = (char)fis.read(); 239 } 240 241 final int width = Integer.parseInt(widthDim.trim()); 242 final int height = Integer.parseInt(heightDim.trim()); 243 return new Dimension(width, height); 244 } 245 246 247 /** 248 Initialize a rectangle of pixels from a PPM image file. 249 250 @param rec_ul_x upper left hand x-coordinate of the rectangle of pixels 251 @param rec_ul_y upper left hand y-coordinate of the rectangle of pixels 252 @param width width of the pixel data to read from the PPM file 253 @param height height of the pixel data to read from the PPM file 254 @param inputFile must name a PPM image file with magic number P6 255 @param fis input stream to the PPM image file 256 @throws IOException if there is a problem with {@code fis} 257 */ 258 private void setPixels(final int rec_ul_x, final int rec_ul_y, 259 final int width, final int height, 260 final String inputFile, final FileInputStream fis) 261 throws IOException { 262 // Read the pixel data in a PPM file. 263 // http://stackoverflow.com/questions/2693631/read-ppm-file-and-store-it-in-an-array-coded-with-c 264 // Read image rgb dimensions (which we don't use). 265 char c = (char)fis.read(); 266 while (c != '\n') { 267 c = (char)fis.read(); 268 } 269 270 // Create a small data array. 271 final byte[] pixelData = new byte[3]; 272 273 // Read pixel data, one pixel at a time, from the PPM file. 274 for (int y = 0; y < height; ++y) { 275 for (int x = 0; x < width; ++x) { 276 if ( fis.read(pixelData, 0, 3) != 3 ) { 277 System.err.printf("ERROR! Could not load %s\n", inputFile); 278 System.exit(-1); 279 } 280 int r = pixelData[0]; 281 int g = pixelData[1]; 282 int b = pixelData[2]; 283 if (r < 0) r = 256+r; // convert from signed byte to unsigned byte 284 if (g < 0) g = 256+g; 285 if (b < 0) b = 256+b; 286 setPixelFB(rec_ul_x + x, rec_ul_y + y, new Color(r, g, b)); 287 } 288 } 289 } 290 291 292 /** 293 Get the width of this {@code FrameBuffer}. 294 295 @return width of this {@code FrameBuffer} 296 */ 297 public int getWidthFB() { 298 return width; 299 } 300 301 302 /** 303 Get the height of this {@code FrameBuffer}. 304 305 @return height of this {@code FrameBuffer} 306 */ 307 public int getHeightFB() { 308 return height; 309 } 310 311 312 /** 313 Get this {@code FrameBuffer}'s default {@code Viewport}. 314 315 @return this {@code FrameBuffer}'s default {@code Viewport} 316 */ 317 public Viewport getViewport() { 318 return this.vp; 319 } 320 321 322 /** 323 Set the default {@code Viewport} to be this whole {@code FrameBuffer}. 324 */ 325 public void setViewport() { 326 this.vp.setViewport(0, 0, this.width, this.height); 327 } 328 329 330 /** 331 Set the default {@code Viewport} with the given upper-left-hand corner, 332 width and height within this {@code FrameBuffer}. 333 334 @param vp_ul_x upper left hand x-coordinate of default {@code Viewport} 335 @param vp_ul_y upper left hand y-coordinate of default {@code Viewport} 336 @param width default {@code Viewport}'s width 337 @param height default {@code Viewport}'s height 338 */ 339 public void setViewport(final int vp_ul_x, final int vp_ul_y, 340 final int width, final int height) { 341 this.vp.setViewport(vp_ul_x, vp_ul_y, width, height); 342 } 343 344 345 /** 346 Get the {@code FrameBuffer}'s background color. 347 348 @return the {@code FrameBuffer}'s background {@link Color} 349 */ 350 public Color getBackgroundColorFB() { 351 return bgColorFB; 352 } 353 354 355 /** 356 Set the {@code FrameBuffer}'s background color. 357 <p> 358 NOTE: This method does not clear the pixels of the 359 {@code FrameBuffer} to the given {@link Color}. To 360 actually change all the {@code FrameBuffer}'s pixels 361 to the given {@link Color}, use the {@link clearFB} 362 method. 363 364 @param c {@code FrameBuffer}'s new background {@link Color} 365 */ 366 public void setBackgroundColorFB(final Color c) { 367 bgColorFB = c; 368 } 369 370 371 /** 372 Clear the {@code FrameBuffer} using its background color. 373 */ 374 public void clearFB() { 375 clearFB(bgColorFB); 376 } 377 378 379 /** 380 Clear the {@code FrameBuffer} using the given {@link Color}. 381 382 @param c {@link Color} to clear {@code FrameBuffer} with 383 */ 384 public void clearFB(final Color c) { 385 final int rgb = c.getRGB(); 386 for (int y = 0; y < height; ++y) { 387 for (int x = 0; x < width; ++x) { 388 setPixelFB(x, y, rgb); 389 } 390 } 391 } 392 393 394 /** 395 Get the {@link Color} of the pixel with coordinates 396 {@code (x,y)} in the {@code FrameBuffer}. 397 398 @param x horizontal coordinate within the {@code FrameBuffer} 399 @param y vertical coordinate within the {@code FrameBuffer} 400 @return the {@link Color} of the pixel at the given pixel coordinates 401 */ 402 public Color getPixelFB(final int x, final int y) { 403 final int index = (y*width + x); 404 try { 405 final int rgb = pixel_buffer[index]; 406 return new Color(rgb); 407 } 408 catch(ArrayIndexOutOfBoundsException e) { 409 System.err.println("FrameBuffer: Bad pixel coordinate" 410 + " (" + x + ", " + y +")" 411 + " [w="+width+", h="+height+"]"); 412 //e.printStackTrace(System.err); 413 return Color.black; 414 } 415 } 416 417 418 /** 419 Set the {@link Color} of the pixel with coordinates 420 {@code (x,y)} in the {@code FrameBuffer}. 421 422 @param x horizontal coordinate within the {@code FrameBuffer} 423 @param y vertical coordinate within the {@code FrameBuffer} 424 @param c {@link Color} for the pixel at the given pixel coordinates 425 */ 426 public void setPixelFB(final int x, final int y, final Color c) { 427 final int index = (y*width + x); 428 try { 429 pixel_buffer[index] = c.getRGB(); 430 } 431 catch(ArrayIndexOutOfBoundsException e) { 432 System.err.println("FrameBuffer: Bad pixel coordinate" 433 + " (" + x + ", " + y +")" 434 + " [w="+width+", h="+height+"]"); 435 //e.printStackTrace(System.err); 436 } 437 } 438 439 440 /** 441 Set the combined RGB value of the pixel with coordinates 442 {@code (x,y)} in the {@code FrameBuffer}. 443 444 @param x horizontal coordinate within the {@code FrameBuffer} 445 @param y vertical coordinate within the {@code FrameBuffer} 446 @param c combined RGB value for the pixel at the given pixel coordinates 447 */ 448 public void setPixelFB(final int x, final int y, final int c) { 449 final int index = (y*width + x); 450 try { 451 pixel_buffer[index] = c; 452 } 453 catch(ArrayIndexOutOfBoundsException e) { 454 System.err.println("FrameBuffer: Bad pixel coordinate" 455 + " (" + x + ", " + y +")" 456 + " [w="+width+", h="+height+"]"); 457 //e.printStackTrace(System.err); 458 } 459 } 460 461 462 /** 463 Create a new {@code FrameBuffer} containing the pixel data 464 from just the red plane of this {@code FrameBuffer}. 465 466 @return {@code FrameBuffer} object holding just red pixel data from this {@code FrameBuffer} 467 */ 468 public FrameBuffer convertRed2FB() { 469 final FrameBuffer red_fb = new FrameBuffer(this.width, this.height); 470 red_fb.bgColorFB = this.bgColorFB; 471 472 // Copy the framebuffer's red values into the new framebuffer's pixel buffer. 473 for (int y = 0; y < this.height; ++y) { 474 for (int x = 0; x < this.width; ++x) { 475 final Color c = new Color(this.getPixelFB(x, y).getRed(), 0, 0); 476 red_fb.setPixelFB(x, y, c); 477 } 478 } 479 return red_fb; 480 } 481 482 483 /** 484 Create a new {@code FrameBuffer} containing the pixel data 485 from just the green plane of this {@code FrameBuffer}. 486 487 @return {@code FrameBuffer} object holding just green pixel data from this {@code FrameBuffer} 488 */ 489 public FrameBuffer convertGreen2FB() { 490 final FrameBuffer green_fb = new FrameBuffer(this.width, this.height); 491 green_fb.bgColorFB = this.bgColorFB; 492 493 // Copy the framebuffer's green values into the new framebuffer's pixel buffer. 494 for (int y = 0; y < this.height; ++y) { 495 for (int x = 0; x < this.width; ++x) { 496 final Color c = new Color(0, this.getPixelFB(x, y).getGreen(), 0); 497 green_fb.setPixelFB(x, y, c); 498 } 499 } 500 return green_fb; 501 } 502 503 504 /** 505 Create a new {@code FrameBuffer} containing the pixel data 506 from just the blue plane of this {@code FrameBuffer}. 507 508 @return {@code FrameBuffer} object holding just blue pixel data from this {@code FrameBuffer} 509 */ 510 public FrameBuffer convertBlue2FB() { 511 final FrameBuffer blue_fb = new FrameBuffer(this.width, this.height); 512 blue_fb.bgColorFB = this.bgColorFB; 513 514 // Copy the framebuffer's blue values into the new framebuffer's pixel buffer. 515 for (int y = 0; y < this.height; ++y) { 516 for (int x = 0; x < this.width; ++x) { 517 final Color c = new Color(0, 0, this.getPixelFB(x, y).getBlue()); 518 blue_fb.setPixelFB(x, y, c); 519 } 520 } 521 return blue_fb; 522 } 523 524 525 /** 526 Write this {@code FrameBuffer} to the specified PPM file. 527 <p> 528 <a href="https://en.wikipedia.org/wiki/Netpbm_format" target="_top"> 529 https://en.wikipedia.org/wiki/Netpbm_format</a> 530 531 @param filename name of PPM image file to hold {@code FrameBuffer} data 532 */ 533 public void dumpFB2File(final String filename) { 534 dumpPixels2File(0, 0, width-1, height-1, filename); 535 } 536 537 538 /** 539 Write a rectangular sub array of pixels from this {@code FrameBuffer} 540 to the specified PPM file. 541 <p> 542 <a href="https://en.wikipedia.org/wiki/Netpbm_format#PPM_example" target="_top"> 543 https://en.wikipedia.org/wiki/Netpbm_format#PPM_example</a> 544 <p> 545<a href="http://stackoverflow.com/questions/2693631/read-ppm-file-and-store-it-in-an-array-coded-with-c" target="_top"> 546 http://stackoverflow.com/questions/2693631/read-ppm-file-and-store-it-in-an-array-coded-with-c</a> 547 548 @param ul_x upper left hand x-coordinate of pixel data rectangle 549 @param ul_y upper left hand y-coordinate of pixel data rectangle 550 @param lr_x lower right hand x-coordinate of pixel data rectangle 551 @param lr_y lower right hand y-coordinate of pixel data rectangle 552 @param filename name of PPM image file to hold pixel data 553 */ 554 public void dumpPixels2File(final int ul_x, final int ul_y, 555 final int lr_x, final int lr_y, 556 final String filename) { 557 final int p_width = lr_x - ul_x + 1; 558 final int p_height = lr_y - ul_y + 1; 559 560 try { 561 // Header (meta data) for the PPM file. 562 final byte[] header = ("P6\n" + p_width + " " + p_height + "\n" + 255 + "\n").getBytes(); 563 564 final FileChannel fc = new RandomAccessFile(filename, "rw").getChannel(); 565 final ByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 566 0, 567 header.length + 3 * pixel_buffer.length); 568 569 // Write data to the memory-mapped file. 570 // Write the PPM header information first. 571 for (int i = 0; i < header.length; ++i) { 572 mbb.put(i, header[i]); 573 } 574 // Copy all the pixel data to the memory-mapped file, one row at a time. 575 int index = header.length; 576 for (int n = 0; n < p_height; ++n) { 577 // read data from the top row of the data buffer 578 // down towards the bottom row 579 for (int i = 0; i < p_width * 3; i += 3) { 580 final int rgb = pixel_buffer[((ul_y+n)*width + ul_x) + i/3]; 581 final Color c = new Color(rgb); 582 mbb.put(index + 0, (byte)(c.getRed())); 583 mbb.put(index + 1, (byte)(c.getGreen())); 584 mbb.put(index + 2, (byte)(c.getBlue())); 585 index += 3; 586 } 587 } 588 fc.close(); 589 } 590 catch (IOException e) { 591 System.err.printf("ERROR! Could not write to file %s\n", filename); 592 e.printStackTrace(System.err); 593 System.exit(-1); 594 } 595 } 596 597 598 /** 599 Write this {@code FrameBuffer} to the specified image file 600 using the specified file format. 601 602 @param filename name of the image file to hold framebuffer data 603 @param formatName informal name of the image format 604 */ 605 public void dumpFB2File(final String filename, final String formatName) { 606 dumpPixels2File(0, 0, width-1, height-1, filename, formatName); 607 } 608 609 610 /** 611 Write a rectangular sub array of pixels from this {@code FrameBuffer} 612 to the specified image file using the specified file format. 613 <p> 614 Use the static method {@link ImageIO#getWriterFormatNames} 615 to find out what informal image format names can be used 616 (for example, png, gif, jpg, bmp). 617 618 @param ul_x upper left hand x-coordinate of pixel data rectangle 619 @param ul_y upper left hand y-coordinate of pixel data rectangle 620 @param lr_x lower right hand x-coordinate of pixel data rectangle 621 @param lr_y lower right hand y-coordinate of pixel data rectangle 622 @param filename name of the image file to hold pixel data 623 @param formatName informal name of the image format 624 */ 625 public void dumpPixels2File(final int ul_x, final int ul_y, 626 final int lr_x, final int lr_y, 627 final String filename, 628 final String formatName) { 629 final int p_width = lr_x - ul_x + 1; 630 final int p_height = lr_y - ul_y + 1; 631 632 try { 633 final FileOutputStream fos = new FileOutputStream(filename); 634 //System.err.printf("Created file %s\n", filename); 635 636 final BufferedImage bi = new BufferedImage(p_width, p_height, BufferedImage.TYPE_INT_RGB); 637 for (int n = 0; n < p_height; ++n) { 638 for (int i = 0; i < p_width; ++i) { 639 final int rgb = pixel_buffer[((ul_y+n)*width + ul_x) + i]; 640 bi.setRGB(i, n, rgb); 641 } 642 } 643 ImageIO.write(bi, formatName, fos); 644 fos.close(); 645 } 646 catch (FileNotFoundException e) { 647 System.err.printf("ERROR! Could not open file %s\n", filename); 648 e.printStackTrace(System.err); 649 System.exit(-1); 650 } 651 catch (IOException e) { 652 System.err.printf("ERROR! Could not write to file %s\n", filename); 653 e.printStackTrace(System.err); 654 System.exit(-1); 655 } 656 } 657 658 659 /** 660 For debugging very small {@code FrameBuffer} objects. 661 662 @return a {@link String} representation of this {@code FrameBuffer} 663 */ 664 @Override 665 public String toString() { 666 String result = "FrameBuffer [w=" + width + ", h=" + height + "]\n"; 667 for (int j = 0; j < width; ++j) { 668 result += " r g b |"; 669 } 670 result += "\n"; 671 for (int i = 0; i < height; ++i) { 672 for (int j = 0; j < width; ++j) { 673 final int c = pixel_buffer[(i*width) + j]; 674 final Color color = new Color(c); 675 result += String.format("%3d ", color.getRed()) 676 +String.format("%3d ", color.getGreen()) 677 +String.format("%3d|", color.getBlue()); 678 } 679 result += "\n"; 680 } 681 return result; 682 } 683 684 685 /** 686 A simple test of the {@code FrameBuffer} class. 687 <p> 688 It fills the framebuffer with a test pattern. 689 */ 690 public void fbTestPattern() { 691 for (int y = 0; y < this.height; ++y) { 692 for (int x = 0; x < this.width; ++x) { 693 final int gray = (x|y)%255; 694 setPixelFB(x, y, new Color(gray, gray, gray)); 695 } 696 } 697 } 698 699 700/******************************************************************* 701 The following code is an inner class of FrameBuffer. 702********************************************************************/ 703 704 /** 705 A {@code Viewport} is an inner (non-static nested) class of 706 {@link FrameBuffer}. That means that a {@code Viewport} has 707 access to the pixel data of its "parent" {@link FrameBuffer}. 708 <p> 709 A {@code Viewport} is a two-dimensional sub array of its 710 "parent" {@link FrameBuffer}. A {@code Viewport} is 711 represented by its upper-left-hand corner and its 712 lower-right-hand corner in the {@link FrameBuffer}. 713 <p> 714 When you set a pixel in a {@code Viewport}, you are really 715 setting a pixel in its parent {@link FrameBuffer}. 716 <p> 717 A {@link FrameBuffer} can have multiple {@code Viewport}s. 718 <p> 719 {@code Viewport} coordinates act like Java {@link java.awt.Graphics2D} 720 coordinates; the positive {@code x} direction is to the right and the 721 positive {@code y} direction is downward. 722 */ 723 public class Viewport // inner class (non-static nested class) 724 { 725 // Coordinates of the viewport within the framebuffer. 726 public int vp_ul_x; // upper-left-hand corner 727 public int vp_ul_y; 728 public int vp_lr_x; // lower-right-hand corner 729 public int vp_lr_y; 730 public Color bgColorVP; // the viewport's background color 731 732 733 /** 734 Create a {@code Viewport} that is the whole of its 735 parent {@link FrameBuffer}. The default background 736 {@link Color} is the {@link FrameBuffer}'s background 737 color. (Note: This constructor does not set the pixels 738 of this {@code Viewport}. If you want the pixels of this 739 {@code Viewport} to be cleared to the background color, 740 call the {@link clearVP} method.) 741 */ 742 public Viewport() { 743 this(0, 0, width, height, bgColorFB); 744 } 745 746 747 /** 748 Create a {@code Viewport} that is the whole of its 749 parent {@link FrameBuffer} and with the given 750 background color. (Note: This constructor does not use 751 the background color to set the pixels of this {@code Viewport}. 752 If you want the pixels of this {@code Viewport} to be cleared 753 to the background color, call the {@link clearVP} method.) 754 755 @param c background {@link Color} for the {@code Viewport} 756 */ 757 public Viewport(final Color c) { 758 this(0, 0, width, height, c); 759 } 760 761 762 /** 763 Create a {@code Viewport} with the given upper-left-hand corner, 764 width and height within its parent {@link FrameBuffer}. (Note: This 765 constructor does not set the pixels of this {@code Viewport}. If 766 you want the pixels of this {@code Viewport} to be cleared to 767 the background color, call the {@link clearVP} method.) 768 769 @param vp_ul_x upper left hand x-coordinate of new {@code Viewport} rectangle 770 @param vp_ul_y upper left hand y-coordinate of new {@code Viewport} rectangle 771 @param width {@code Viewport}'s width 772 @param height {@code Viewport}'s height 773 */ 774 public Viewport(final int vp_ul_x, final int vp_ul_y, 775 final int width, final int height) { 776 this(vp_ul_x, vp_ul_y, width, height, bgColorFB); 777 } 778 779 780 /** 781 Create a {@code Viewport} with the given upper-left-hand corner, 782 width and height within its parent {@link FrameBuffer}, and with 783 the given background color. (Note: This constructor does not use 784 the background color to set the pixels of this {@code Viewport}. 785 If you want the pixels of this {@code Viewport} to be cleared to 786 the background color, call the {@link clearVP} method.) 787 <p> 788 (Using upper-left-hand corner, width, and height is 789 like Java's {@link java.awt.Rectangle} class and 790 {@link java.awt.Graphics#drawRect} method.) 791 792 @param vp_ul_x upper left hand x-coordinate of new {@code Viewport} rectangle 793 @param vp_ul_y upper left hand y-coordinate of new {@code Viewport} rectangle 794 @param width {@code Viewport}'s width 795 @param height {@code Viewport}'s height 796 @param c background {@link Color} for the {@code Viewport} 797 */ 798 public Viewport(final int vp_ul_x, final int vp_ul_y, 799 final int width, final int height, 800 final Color c) { 801 this.setViewport(vp_ul_x, vp_ul_y, width, height); 802 this.bgColorVP = c; 803 } 804 805 806 /** 807 Create a {@code Viewport}, within its parent {@link FrameBuffer}, 808 from the pixel data of another {@link FrameBuffer}. 809 <p> 810 The size of the {@code Viewport} will be the size of the 811 source {@link FrameBuffer}. 812 813 @param vp_ul_x upper left hand x-coordinate of new {@code Viewport} rectangle 814 @param vp_ul_y upper left hand y-coordinate of new {@code Viewport} rectangle 815 @param sourceFB {@link FrameBuffer} to use as the source of the pixel data 816 */ 817 public Viewport(final int vp_ul_x, final int vp_ul_y, 818 final FrameBuffer sourceFB) { 819 this(vp_ul_x, vp_ul_y, sourceFB.getWidthFB(), 820 sourceFB.getHeightFB(), 821 sourceFB.getBackgroundColorFB()); 822 823 // Read pixel data, one pixel at a time, from the source FrameBuffer. 824 for (int y = 0; y < sourceFB.getHeightFB(); ++y) { 825 for (int x = 0; x < sourceFB.getWidthFB(); ++x) { 826 this.setPixelVP(x, y, sourceFB.getPixelFB(x,y)); 827 } 828 } 829 } 830 831 832 /** 833 Create a {@code Viewport}, within its parent {@link FrameBuffer}, 834 from the pixel data of a {@code Viewport}. 835 <p> 836 The size of the new {@code Viewport} will be the size of the 837 source {@code Viewport}. 838 <p> 839 This constructor makes the new {@code Viewport} into a copy of the 840 source {@code Viewport}. 841 842 @param vp_ul_x upper left hand x-coordinate of new {@code Viewport} rectangle 843 @param vp_ul_y upper left hand y-coordinate of new {@code Viewport} rectangle 844 @param sourceVP {@link Viewport} to use as the source of the pixel data 845 */ 846 public Viewport(final int vp_ul_x, final int vp_ul_y, 847 final Viewport sourceVP) { 848 this(vp_ul_x, vp_ul_y, sourceVP.getWidthVP(), 849 sourceVP.getHeightVP(), 850 sourceVP.getBackgroundColorVP()); 851 852 // Read pixel data, one pixel at a time, from the source Viewport. 853 for (int y = 0; y < sourceVP.getHeightVP(); ++y) { 854 for (int x = 0; x < sourceVP.getWidthVP(); ++x) { 855 this.setPixelVP(x, y, sourceVP.getPixelVP(x,y)); 856 } 857 } 858 } 859 860 861 /** 862 Create a {@code Viewport}, within its parent {@link FrameBuffer}, 863 from a PPM image file. 864 <p> 865 The size of the {@code Viewport} will be the size of the image. 866 <p> 867 This can be used to initialize a {@code Viewport} with a background image. 868 869 @param vp_ul_x upper left hand x-coordinate of new {@code Viewport} rectangle 870 @param vp_ul_y upper left hand y-coordinate of new {@code Viewport} rectangle 871 @param inputFileName must name a PPM image file with magic number P6. 872 */ 873 public Viewport(final int vp_ul_x, final int vp_ul_y, 874 final String inputFileName) { 875 try { 876 final FileInputStream fis = new FileInputStream(inputFileName); 877 878 final Dimension vpDim = getPPMdimensions(inputFileName, fis); 879 880 this.vp_ul_x = vp_ul_x; 881 this.vp_ul_y = vp_ul_y; 882 this.vp_lr_x = vp_ul_x + vpDim.width - 1; 883 this.vp_lr_y = vp_ul_y + vpDim.height - 1; 884 885 setPixels(vp_ul_x, vp_ul_y, vpDim.width, vpDim.height, inputFileName, fis); 886 887 fis.close(); 888 889 this.bgColorVP = bgColorFB; 890 } 891 catch (IOException e) { 892 System.err.printf("ERROR! Could not read %s\n", inputFileName); 893 e.printStackTrace(System.err); 894 System.exit(-1); 895 } 896 } 897 898 899 /** 900 Mutate this {@code Viewport} into the given upper-left-hand corner, 901 width and height within its parent {@link FrameBuffer}. 902 <p> 903 (Using upper-left-hand corner, width, and height is 904 like Java's {@link java.awt.Rectangle} class and 905 {@link java.awt.Graphics#drawRect} method.) 906 907 @param vp_ul_x new upper left hand x-coordinate of this {@code Viewport} rectangle 908 @param vp_ul_y new upper left hand y-coordinate of this {@code Viewport} rectangle 909 @param width {@code Viewport}'s new width 910 @param height {@code Viewport}'s new height 911 */ 912 public void setViewport(final int vp_ul_x, final int vp_ul_y, 913 final int width, final int height) { 914 this.vp_ul_x = vp_ul_x; 915 this.vp_ul_y = vp_ul_y; 916 this.vp_lr_x = vp_ul_x + width - 1; 917 this.vp_lr_y = vp_ul_y + height - 1; 918 } 919 920 921 /** 922 Return a reference to the {@link FrameBuffer} object that 923 this {@code Viewport} object is nested in. 924 925 @return a reference to the {@link FrameBuffer} object that this {@code Viewport} is part of 926 */ 927 public FrameBuffer getFrameBuffer() 928 { 929 return FrameBuffer.this; 930 } 931 932 933 /** 934 Get the width of this {@code Viewport}. 935 936 @return width of this {@code Viewport} rectangle 937 */ 938 public int getWidthVP() { 939 return vp_lr_x - vp_ul_x + 1; 940 } 941 942 943 /** 944 Get the height of this {@code Viewport}. 945 946 @return height of this {@code Viewport} rectangle 947 */ 948 public int getHeightVP() { 949 return vp_lr_y - vp_ul_y + 1; 950 } 951 952 953 /** 954 Get the {@code Viewport}'s background color. 955 956 @return the {@code Viewport}'s background {@link Color} 957 */ 958 public Color getBackgroundColorVP() { 959 return bgColorVP; 960 } 961 962 963 /** 964 Set the {@code Viewport}'s background color. 965 <p> 966 NOTE: This method does not clear the pixels of the 967 {@code Viewport} to the given {@link Color}. To 968 actually change all the {@code Viewport}'s pixels 969 to the given {@link Color}, use the {@link clearVP} 970 method. 971 972 @param c {@code Viewport}'s new background {@link Color} 973 */ 974 public void setBackgroundColorVP(final Color c) { 975 bgColorVP = c; 976 } 977 978 979 /** 980 Clear this {@code Viewport} using its background color. 981 */ 982 public void clearVP() { 983 clearVP(bgColorVP); 984 } 985 986 987 /** 988 Clear this {@code Viewport} using the given {@link Color}. 989 990 @param c {@link Color} to clear this {@code Viewport} with 991 */ 992 public void clearVP(final Color c) { 993 final int wVP = getWidthVP(); 994 final int hVP = getHeightVP(); 995 final int rgb = c.getRGB(); 996 for (int y = 0; y < hVP; ++y) { 997 for (int x = 0; x < wVP; ++x) { 998 setPixelVP(x, y, rgb); 999 } 1000 } 1001 } 1002 1003 1004 /** 1005 Get the {@link Color} of the pixel with coordinates 1006 {@code (x,y)} relative to this {@code Viewport}. 1007 1008 @param x horizontal coordinate within this {@code Viewport} 1009 @param y vertical coordinate within this {@code Viewport} 1010 @return the {@link Color} of the current pixel at the given {@code Viewport} coordinates 1011 */ 1012 public Color getPixelVP(final int x, final int y) { 1013 return getPixelFB(vp_ul_x + x, vp_ul_y + y); 1014 } 1015 1016 1017 /** 1018 Set the {@link Color} of the pixel with coordinates 1019 {@code (x,y)} relative to this {@code Viewport}. 1020 1021 @param x horizontal coordinate within this {@code Viewport} 1022 @param y vertical coordinate within this {@code Viewport} 1023 @param c {@link Color} for the pixel at the given {@code Viewport} coordinates 1024 */ 1025 public void setPixelVP(final int x, final int y, final Color c) { 1026 setPixelFB(vp_ul_x + x, vp_ul_y + y, c); 1027 } 1028 1029 1030 /** 1031 Set the combined RGB value of the pixel with coordinates 1032 {@code (x,y)} relative to this {@code Viewport}. 1033 1034 @param x horizontal coordinate within this {@code Viewport} 1035 @param y vertical coordinate within this {@code Viewport} 1036 @param c combined RGB value for the pixel at the given {@code Viewport} coordinates 1037 */ 1038 public void setPixelVP(final int x, final int y, final int c) { 1039 setPixelFB(vp_ul_x + x, vp_ul_y + y, c); 1040 } 1041 1042 1043 /** 1044 Create a new {@link FrameBuffer} containing the pixel data 1045 from this {@code Viewport} rectangle. 1046 1047 @return {@code FrameBuffer} object holding pixel data from this {@code Viewport} 1048 */ 1049 public FrameBuffer convertVP2FB() { 1050 final int wVP = this.getWidthVP(); 1051 final int hVP = this.getHeightVP(); 1052 1053 final FrameBuffer vp_fb = new FrameBuffer( wVP, hVP ); 1054 vp_fb.bgColorFB = this.bgColorVP; 1055 1056 // Copy the current viewport into the new framebuffer's pixel buffer. 1057 for (int y = 0; y < hVP; ++y) { 1058 for (int x = 0; x < wVP; ++x) { 1059 vp_fb.setPixelFB( x, y, this.getPixelVP(x, y) ); 1060 } 1061 } 1062 1063 return vp_fb; 1064 } 1065 1066 1067 /** 1068 Write this {@code Viewport} to the specified PPM file. 1069 <p> 1070 <a href="https://en.wikipedia.org/wiki/Netpbm_format" target="_top"> 1071 https://en.wikipedia.org/wiki/Netpbm_format</a> 1072 1073 @param filename name of PPM image file to hold {@code Viewport} data 1074 */ 1075 public void dumpVP2File(final String filename) { 1076 dumpPixels2File(vp_ul_x, vp_ul_y, vp_lr_x, vp_lr_y, filename); 1077 } 1078 1079 1080 /** 1081 Write this {@code Viewport} to the specified image file 1082 using the specified file format. 1083 1084 @param filename name of the image file to hold {@code Viewport} data 1085 @param formatName informal name of the image format 1086 */ 1087 public void dumpVP2File(final String filename, 1088 final String formatName) { 1089 dumpPixels2File(vp_ul_x, vp_ul_y, vp_lr_x, vp_lr_y, 1090 filename, formatName); 1091 } 1092 1093 1094 /** 1095 A simple test of the {@code Viewport}. 1096 <p> 1097 It fills the viewport with a test pattern. 1098 */ 1099 public void vpTestPattern() { 1100 for (int y = 0; y < this.getHeightVP(); ++y) { 1101 for (int x = 0; x < this.getWidthVP(); ++x) { 1102 final int gray = (x|y)%255; 1103 setPixelVP(x, y, new Color(gray, gray, gray)); 1104 } 1105 } 1106 } 1107 }// Viewport 1108 1109 1110/******************************************************************* 1111 The following is a main() method for testing, demonstration, 1112 and documentation purposes. 1113********************************************************************/ 1114 1115 /** 1116 A {@code main()} method for testing the {@code FrameBuffer} class. 1117 1118 @param args array of command-line arguments 1119 */ 1120 public static void main(String[] args) { 1121 final int w = 512; 1122 final int h = 512; 1123 final FrameBuffer fb = new FrameBuffer(w, h); 1124 fb.fbTestPattern(); // fill the framebuffer with a test pattern 1125 fb.dumpFB2File("test01.ppm"); 1126 1127 // Notice the unusual notation for instantiating a new Viewport. 1128 final Viewport vp = fb.new Viewport(64, 64, 192, 320); // 192 by 320 1129 vp.clearVP( Color.red ); 1130 for (int i = 0; i < 512; ++i) 1131 fb.setPixelFB(128, i, Color.blue); // a blue vertical line 1132 for (int i = 0; i < 192; ++i) 1133 vp.setPixelVP(i, i, Color.green); // a green diagonal line 1134 1135 fb.dumpFB2File("test02.ppm"); 1136 vp.dumpVP2File("test03.ppm"); 1137 fb.dumpPixels2File(32, 256-64, 511-64, 255+64, "test04.ppm"); // 416 by 128 1138 1139 final Viewport vp2 = fb.new Viewport(80, 80, 160, 160); // 160 by 160 1140 vp2.vpTestPattern(); // fill the viewport with a test pattern 1141 fb.dumpFB2File("test05.ppm"); 1142 1143 final FrameBuffer fb2 = new FrameBuffer("test05.ppm"); 1144 fb2.dumpFB2File("test06.ppm"); 1145 1146 fb.convertRed2FB().dumpFB2File("test07.ppm"); 1147 fb.convertGreen2FB().dumpFB2File("test08.ppm"); 1148 fb.convertBlue2FB().dumpFB2File("test09.ppm"); 1149 1150 final FrameBuffer fb3 = new FrameBuffer(600, 600); 1151 fb3.clearFB(Color.orange); 1152 final Viewport vp3 = fb3.new Viewport(44, 44, "test05.ppm"); 1153 fb3.dumpFB2File("test10.ppm"); 1154 fb3.new Viewport(86, 86, vp.convertVP2FB()); 1155 fb3.dumpFB2File("test11.ppm"); 1156 fb3.dumpFB2File("test11.png", "png"); 1157 fb3.dumpFB2File("test11.gif", "gif"); 1158 fb3.dumpFB2File("test11.jpg", "jpg"); 1159 fb3.dumpFB2File("test11.bmp", "bmp"); 1160 1161 final FrameBuffer fb4 = new FrameBuffer(1200, 600); 1162 // Create two viewports in one frameBuffer. 1163 final Viewport vp4 = fb4.new Viewport( 0, 0, "test10.ppm"); 1164 final Viewport vp5 = fb4.new Viewport(600, 0, fb3); 1165 // Copy a viewport into a viewport. 1166 final Viewport vp6 = fb4.new Viewport(0, 0, 200, 200); // source 1167 final Viewport vp7 = fb4.new Viewport(1000, 400, vp6); 1168 fb4.dumpFB2File("test12.ppm"); 1169 1170 // list the image file formats supported by the runtime 1171 for (final String s : ImageIO.getWriterFormatNames()) System.out.println(s); 1172 }//main() 1173}//FrameBuffer