001package framebuffer; 002 003import java.awt.Color; 004import java.io.FileInputStream; 005import java.io.FileOutputStream; 006import java.io.FileNotFoundException; 007import java.io.IOException; 008/** 009 <p> 010 This FrameBuffer represents a two-dimensional array of pixel data. 011 The data is stored as a one dimensional array in row-major order. 012 The first row of data should be displayed as the top row of pixels 013 in the image (or window). 014 </p> 015 016 <p> 017 The "viewport" is a two-dimensional sub array of this framebuffer. 018 The viewport is represented by its upper-left-hand corner and its 019 lower-right-hand corner. 020 </p> 021 022 <p> 023 Framebuffer and viewport coordinates act like Java Graphics 024 coordinates; the positive x direction is to the right and 025 the positive y direction is downward. 026 </p> 027*/ 028public class FrameBuffer 029{ 030 private Color bgColorFB; // the framebuffer's background color 031 private int width; // the framebuffer's width 032 private int height; // the framebuffer's height 033 protected int[] pixel_buffer; // contains each pixel's color data for a rendered frame 034 035 // Coordinates of the current viewport within the framebuffer. 036 private int vp_ul_x; // upper-left-hand corner 037 private int vp_ul_y; 038 private int vp_lr_x; // lower-right-hand corner 039 private int vp_lr_y; 040 private Color bgColorVP; // the viewport's background color 041 042 /** 043 Construct a FrameBuffer with the given dimensions. 044 Initialize the framebuffer to hold all black pixels. 045 046 @param w Width of the FrameBuffer. 047 @param h Height of the FrameBuffer. 048 */ 049 public FrameBuffer(int w, int h) 050 { 051 this.width = w; 052 this.height = h; 053 054 // Set the default viewport (to be the whole framebuffer). 055 vp_ul_x = 0; 056 vp_ul_y = 0; 057 vp_lr_x = width - 1; 058 vp_lr_y = height - 1; 059 060 // Create the pixel buffer. 061 pixel_buffer = new int[width * height]; 062 063 // Initialize the pixel buffer. 064 clearFB(Color.black); 065 this.bgColorFB = Color.black; 066 this.bgColorVP = Color.black; 067 } 068 069 070 /** 071 Construct a FrameBuffer from a PPM image file. 072 This can be used to initialize a FrameBuffer with a background image. 073 074 @param inputFileName Must name a PPM image file with magic number P6. 075 */ 076 public FrameBuffer(String inputFileName) 077 { 078 // Read the pixel data in a PPM file. 079 // http://stackoverflow.com/questions/2693631/read-ppm-file-and-store-it-in-an-array-coded-with-c 080 try 081 { 082 FileInputStream fis = new FileInputStream(inputFileName); 083 084 // Read image format string "P6". 085 String magicNumber = ""; 086 char c = (char)fis.read(); 087 while (c != '\n') 088 { 089 magicNumber += c; 090 c = (char)fis.read(); 091 } 092 if (! magicNumber.trim().startsWith("P6")) 093 { 094 System.err.printf("ERROR! Improper PPM number in file %s\n", inputFileName); 095 System.exit(-1); 096 } 097 098 c = (char)fis.read(); 099 if ( '#' == c ) // read (and discard) IrfanView comment 100 { 101 while (c != '\n') 102 { 103 c = (char)fis.read(); 104 } 105 c = (char)fis.read(); 106 } 107 108 // Read image dimensions. 109 String widthDim = ""; 110 while (c != ' ' && c != '\n') 111 { 112 widthDim += c; 113 c = (char)fis.read(); 114 } 115 116 String heightDim = ""; 117 c = (char)fis.read(); 118 while (c != '\n') 119 { 120 heightDim += c; 121 c = (char)fis.read(); 122 } 123 124 fis.close(); 125 126 this.width = Integer.parseInt(widthDim.trim()); 127 this.height = Integer.parseInt(heightDim.trim()); 128 129 // Set the default viewport (to be the whole framebuffer). 130 vp_ul_x = 0; 131 vp_ul_y = 0; 132 vp_lr_x = this.width - 1; 133 vp_lr_y = this.height - 1; 134 135 // Create the pixel buffer. 136 pixel_buffer = new int[this.width * this.height]; 137 138 // Initialize the pixel buffer. 139 clearFB(Color.black); 140 this.bgColorFB = Color.black; 141 this.bgColorVP = Color.black; 142 143 setViewport(vp_ul_x, vp_ul_y, inputFileName); 144 } 145 catch (IOException e) 146 { 147 System.err.printf("ERROR! Could not read %s\n", inputFileName); 148 e.printStackTrace(System.err); 149 System.exit(-1); 150 } 151 } 152 153 154 /** 155 Get the width of the framebuffer. 156 157 @return this framebuffer's width 158 */ 159 public int getWidthFB() 160 { 161 return this.width; 162 } 163 164 165 /** 166 Get the height of the framebuffer. 167 168 @return this framebuffer's height 169 */ 170 public int getHeightFB() 171 { 172 return this.height; 173 } 174 175 176 /** 177 Get the background color of the framebuffer. 178 179 @return this framebuffer's background color 180 */ 181 public Color getBgColorFB() 182 { 183 return this.bgColorFB; 184 } 185 186 187 /** 188 Clear the framebuffer (set a background color). 189 190 @param c color to clear framebuffer with 191 */ 192 public void clearFB(Color c) 193 { 194 this.bgColorFB = c; 195 this.bgColorVP = c; 196 197 for (int y = 0; y < height; y++) 198 { 199 for (int x = 0; x < width; x++) 200 { 201 setPixelFB(x, y, c); 202 } 203 } 204 } 205 206 207 /** 208 Get the color of the pixel with coordinates 209 (x,y) in the framebuffer. 210 211 @param x horizontal coordinate within the framebuffer 212 @param y vertical coordinate within the framebuffer 213 @return the color of the current pixel at the given pixel coordinates 214 */ 215 public Color getPixelFB(int x, int y) 216 { 217 int index = (y*width + x); 218 try 219 { 220 int rgb = pixel_buffer[index]; 221 return new Color(rgb); 222 } 223 catch(ArrayIndexOutOfBoundsException e) 224 { 225 System.err.println("FrameBuffer: Bad pixel coordinate (" + x + ", " + y +")"); 226 //e.printStackTrace(System.err); 227 return Color.black; 228 } 229 } 230 231 232 /** 233 Set the color of the pixel with coordinates 234 (x,y) in the framebuffer. 235 236 @param x horizontal coordinate within the framebuffer 237 @param y vertical coordinate within the framebuffer 238 @param c color for the pixel at the given pixel coordinates 239 */ 240 public void setPixelFB(int x, int y, Color c) 241 { 242 int index = (y*width + x); 243 try 244 { 245 pixel_buffer[index] = c.getRGB(); 246 } 247 catch(ArrayIndexOutOfBoundsException e) 248 { 249 System.err.println("FrameBuffer: Bad pixel coordinate (" + x + ", " + y +")"); 250 //e.printStackTrace(System.err); 251 } 252 } 253 254 255 /** 256 Set the coordinates, within the FrameBuffer, of the 257 viewport's upper-left-hand corner, width, and height. 258 (Using upper-left-hand corner, width, and height is 259 like Java's Rectangle class and Graphics.drawRect() 260 method.) 261 262 @param vp_ul_x upper left hand x-coordinate of new viewport rectangle 263 @param vp_ul_y upper left hand y-coordinate of new viewport rectangle 264 @param width viewport's width 265 @param height viewport's height 266 */ 267 public void setViewport(int vp_ul_x, int vp_ul_y, int width, int height) 268 { 269 this.vp_ul_x = vp_ul_x; 270 this.vp_ul_y = vp_ul_y; 271 this.vp_lr_x = vp_ul_x + width - 1; 272 this.vp_lr_y = vp_ul_y + height - 1; 273 } 274 275 276 /** 277 Create a Viewport from a FrameBuffer. 278 The size of the viewport will be the size of the source FrameBuffer. 279 280 @param vp_ul_x upper left hand x-coordinate of new viewport rectangle 281 @param vp_ul_y upper left hand y-coordinate of new viewport rectangle 282 @param sourceFB FrameBuffer to use as the source of the pixel data.. 283 */ 284 public void setViewport(int vp_ul_x, int vp_ul_y, FrameBuffer sourceFB) 285 { 286 int vpWidth = sourceFB.width; 287 int vpHeight = sourceFB.height; 288 289 this.vp_ul_x = vp_ul_x; 290 this.vp_ul_y = vp_ul_y; 291 this.vp_lr_x = vp_ul_x + vpWidth - 1; 292 this.vp_lr_y = vp_ul_y + vpHeight - 1; 293 294 // Read pixel data, one pixel at a time, from the source FrameBuffer. 295 for (int y = 0; y < vpHeight; y++) 296 { 297 for (int x = 0; x < vpWidth; x++) 298 { 299 this.setPixelVP(x, y, sourceFB.getPixelFB(x,y)); 300 } 301 } 302 } 303 304 305 /** 306 Create a Viewport from a PPM image file. 307 The size of the viewport will be the size of the image. 308 This can be used to initialize a Viewport with a background image. 309 310 @param vp_ul_x upper left hand x-coordinate of new viewport rectangle 311 @param vp_ul_y upper left hand y-coordinate of new viewport rectangle 312 @param inputFileName Must name a PPM image file with magic number P6. 313 */ 314 public void setViewport(int vp_ul_x, int vp_ul_y, String inputFileName) 315 { 316 // Read the pixel data in a PPM file. 317 // http://stackoverflow.com/questions/2693631/read-ppm-file-and-store-it-in-an-array-coded-with-c 318 try 319 { 320 FileInputStream fis = new FileInputStream(inputFileName); 321 322 // Read image format string "P6". 323 String magicNumber = ""; 324 char c = (char)fis.read(); 325 while (c != '\n') 326 { 327 magicNumber += c; 328 c = (char)fis.read(); 329 } 330 if (! magicNumber.trim().startsWith("P6")) 331 { 332 System.err.printf("ERROR! Improper PPM number in file %s\n", inputFileName); 333 System.exit(-1); 334 } 335 336 c = (char)fis.read(); 337 if ( '#' == c ) // read (and discard) IrfanView comment 338 { 339 while (c != '\n') 340 { 341 c = (char)fis.read(); 342 } 343 c = (char)fis.read(); 344 } 345 346 // Read image dimensions. 347 String widthDim = ""; 348 while (c != ' ' && c != '\n') 349 { 350 widthDim += c; 351 c = (char)fis.read(); 352 } 353 354 String heightDim = ""; 355 c = (char)fis.read(); 356 while (c != '\n') 357 { 358 heightDim += c; 359 c = (char)fis.read(); 360 } 361 362 // Read image rgb dimensions (which we don't use). 363 c = (char)fis.read(); 364 while (c != '\n') 365 { 366 c = (char)fis.read(); 367 } 368 369 int vpWidth = Integer.parseInt(widthDim.trim()); 370 int vpHeight = Integer.parseInt(heightDim.trim()); 371 372 this.vp_ul_x = vp_ul_x; 373 this.vp_ul_y = vp_ul_y; 374 this.vp_lr_x = vp_ul_x + vpWidth - 1; 375 this.vp_lr_y = vp_ul_y + vpHeight - 1; 376 377 // Create a data array. 378 byte[] pixelData = new byte[3]; 379 // Read pixel data, one pixel at a time, from the PPM file. 380 for (int y = 0; y < vpHeight; y++) 381 { 382 for (int x = 0; x < vpWidth; x++) 383 { 384 if ( fis.read(pixelData, 0, 3) != 3 ) 385 { 386 System.err.printf("ERROR! Could not load %s\n", inputFileName); 387 System.exit(-1); 388 } 389 int r = pixelData[0]; 390 int g = pixelData[1]; 391 int b = pixelData[2]; 392 if (r < 0) r = 256+r; // convert from signed byte to unsigned byte 393 if (g < 0) g = 256+g; 394 if (b < 0) b = 256+b; 395 setPixelVP(x, y, new Color(r, g, b)); 396 } 397 } 398 fis.close(); 399 } 400 catch (IOException e) 401 { 402 System.err.printf("ERROR! Could not read %s\n", inputFileName); 403 e.printStackTrace(System.err); 404 System.exit(-1); 405 } 406 } 407 408 409 /** 410 Get the width of the viewport. 411 412 @return width of the current viewport rectangle 413 */ 414 public int getWidthVP() 415 { 416 return vp_lr_x - vp_ul_x + 1; 417 } 418 419 420 /** 421 Get the height of the viewport. 422 423 @return height of the current viewport rectangle 424 */ 425 public int getHeightVP() 426 { 427 return vp_lr_y - vp_ul_y + 1; 428 } 429 430 431 /** 432 Get the upper left hand corner of the viewport. 433 434 @return upper left hand corner of current viewport rectangle 435 */ 436 public java.awt.Point getLocationVP() 437 { 438 return new java.awt.Point(vp_ul_x, vp_ul_y); 439 } 440 441 442 /** 443 Get the background color of the viewport. 444 445 @return background color of current viewport 446 */ 447 public Color getBgColorVP() 448 { 449 return this.bgColorVP; 450 } 451 452 453 /** 454 Clear the viewport (set a background color). 455 456 @param c color to clear current viewport with 457 */ 458 public void clearVP(Color c) 459 { 460 this.bgColorVP = c; 461 462 int wVP = getWidthVP(); 463 int hVP = getHeightVP(); 464 465 for (int y = 0; y < hVP; y++) 466 { 467 for (int x = 0; x < wVP; x++) 468 { 469 setPixelVP(x, y, c); 470 } 471 } 472 } 473 474 475 /** 476 Get the color of the pixel with coordinates 477 (x,y) relative to the current viewport. 478 479 @param x horizontal coordinate within the current viewport 480 @param y vertical coordinate within the current viewport 481 @return the color of the current pixel at the given viewport coordinates 482 */ 483 public Color getPixelVP(int x, int y) 484 { 485 return getPixelFB(vp_ul_x + x, vp_ul_y + y); 486 } 487 488 489 /** 490 Set the color of the pixel with coordinates 491 (x,y) relative to the current viewport. 492 493 @param x horizontal coordinate within the current viewport 494 @param y vertical coordinate within the current viewport 495 @param c color for the pixel at the given viewport coordinates 496 */ 497 public void setPixelVP(int x, int y, Color c) 498 { 499 setPixelFB(vp_ul_x + x, vp_ul_y + y, c); 500 } 501 502 503 /** 504 Convert the viewport into a framebuffer. 505 506 @return FrameBuffer object holding pixel data from the current viewport rectangle 507 */ 508 public FrameBuffer convertVP2FB() 509 { 510 int wVP = this.getWidthVP(); 511 int hVP = this.getHeightVP(); 512 513 FrameBuffer vp_fb = new FrameBuffer( wVP, hVP ); 514 vp_fb.bgColorFB = this.bgColorVP; 515 516 // Copy the current viewport into the new framebuffer's pixel buffer. 517 for (int y = 0; y < hVP; y++) 518 { 519 for (int x = 0; x < wVP; x++) 520 { 521 vp_fb.setPixelFB( x, y, this.getPixelVP(x, y) ); 522 } 523 } 524 525 return vp_fb; 526 } 527 528 529 /** 530 Convert the red plane of the framebuffer into a framebuffer. 531 532 @return FrameBuffer object holding just red pixel data from the framebuffer 533 */ 534 public FrameBuffer convertRed2FB() 535 { 536 FrameBuffer red_fb = new FrameBuffer(this.width, this.height); 537 red_fb.bgColorFB = this.bgColorFB; 538 red_fb.bgColorVP = this.bgColorVP; 539 red_fb.vp_ul_x = this.vp_ul_x; 540 red_fb.vp_ul_y = this.vp_ul_y; 541 red_fb.vp_lr_x = this.vp_lr_x; 542 red_fb.vp_lr_y = this.vp_lr_y; 543 544 // Copy the framebuffer's red values into the new framebuffer's pixel buffer. 545 for (int y = 0; y < this.height; y++) 546 { 547 for (int x = 0; x < this.width; x++) 548 { 549 Color c = new Color(this.getPixelFB(x, y).getRed(), 0, 0); 550 red_fb.setPixelFB(x, y, c); 551 } 552 } 553 554 return red_fb; 555 } 556 557 558 /** 559 Convert the green plane of the framebuffer into a framebuffer. 560 561 @return FrameBuffer object holding just green pixel data from the framebuffer 562 */ 563 public FrameBuffer convertGreen2FB() 564 { 565 FrameBuffer green_fb = new FrameBuffer(this.width, this.height); 566 green_fb.bgColorFB = this.bgColorFB; 567 green_fb.bgColorVP = this.bgColorVP; 568 green_fb.vp_ul_x = this.vp_ul_x; 569 green_fb.vp_ul_y = this.vp_ul_y; 570 green_fb.vp_lr_x = this.vp_lr_x; 571 green_fb.vp_lr_y = this.vp_lr_y; 572 573 // Copy the framebuffer's green values into the new framebuffer's pixel buffer. 574 for (int y = 0; y < this.height; y++) 575 { 576 for (int x = 0; x < this.width; x++) 577 { 578 Color c = new Color(0, this.getPixelFB(x, y).getGreen(), 0); 579 green_fb.setPixelFB(x, y, c); 580 } 581 } 582 583 return green_fb; 584 } 585 586 587 /** 588 Convert the blue plane of the framebuffer into a framebuffer. 589 590 @return FrameBuffer object holding just blue pixel data from the framebuffer 591 */ 592 public FrameBuffer convertBlue2FB() 593 { 594 FrameBuffer blue_fb = new FrameBuffer(this.width, this.height); 595 blue_fb.bgColorFB = this.bgColorFB; 596 blue_fb.bgColorVP = this.bgColorVP; 597 blue_fb.vp_ul_x = this.vp_ul_x; 598 blue_fb.vp_ul_y = this.vp_ul_y; 599 blue_fb.vp_lr_x = this.vp_lr_x; 600 blue_fb.vp_lr_y = this.vp_lr_y; 601 602 // Copy the framebuffer's blue values into the new framebuffer's pixel buffer. 603 for (int y = 0; y < this.height; y++) 604 { 605 for (int x = 0; x < this.width; x++) 606 { 607 Color c = new Color(0, 0, this.getPixelFB(x, y).getBlue()); 608 blue_fb.setPixelFB(x, y, c); 609 } 610 } 611 612 return blue_fb; 613 } 614 615 616 /** 617 Write the framebuffer to the specified file. 618 619 @param filename name of PPM image file to hold framebuffer data 620 */ 621 public void dumpFB2File(String filename) 622 { 623 dumpPixels2File(0, 0, width-1, height-1, filename); 624 } 625 626 627 /** 628 Write the viewport to the specified file. 629 630 @param filename name of PPM image file to hold viewport data 631 */ 632 public void dumpVP2File(String filename) 633 { 634 dumpPixels2File(vp_ul_x, vp_ul_y, vp_lr_x, vp_lr_y, filename); 635 } 636 637 638 /** 639 <p> 640 Write a rectangular sub array of pixels from the framebuffer to the specified file. 641 </p> 642 <p> 643 http://stackoverflow.com/questions/2693631/read-ppm-file-and-store-it-in-an-array-coded-with-c 644 </p> 645 646 @param ul_x upper left hand x-coordinate of pixel data rectangle 647 @param ul_y upper left hand y-coordinate of pixel data rectangle 648 @param lr_x lower right hand x-coordinate of pixel data rectangle 649 @param lr_y lower right hand y-coordinate of pixel data rectangle 650 @param filename name of PPM image file to hold pixel data 651 */ 652 public void dumpPixels2File(int ul_x, int ul_y, int lr_x, int lr_y, String filename) 653 { 654 int p_width = lr_x - ul_x + 1; 655 int p_height = lr_y - ul_y + 1; 656 657 FileOutputStream fos = null; 658 try // open the file 659 { 660 fos = new FileOutputStream(filename); 661 } 662 catch (FileNotFoundException e) 663 { 664 System.err.printf("ERROR! Could not open file %s\n", filename); 665 e.printStackTrace(System.err); 666 System.exit(-1); 667 } 668 //System.err.printf("Created file %s\n", filename); 669 670 try // write data to the file 671 { 672 // write the PPM header information first 673 fos.write( ("P6\n" + p_width + " " + p_height + "\n" + 255 + "\n").getBytes() ); 674 675 // write the pixel data to the file 676 byte[] temp = new byte[p_width*3]; // array to hold one row of data 677 for (int n = 0; n < p_height; n++) 678 { // write one row of pixels at a time, 679 680 // read from the top row of the data buffer 681 // down towards the bottom row 682 for (int i = 0; i < temp.length; i+=3) 683 { 684 int rgb = pixel_buffer[((ul_y+n)*width + ul_x) + i/3]; 685 Color c = new Color(rgb); 686 temp[i + 0] = (byte)(c.getRed()); 687 temp[i + 1] = (byte)(c.getGreen()); 688 temp[i + 2] = (byte)(c.getBlue()); 689 } 690 /* 691 // read from the bottom row of the data buffer 692 // up towards the top row 693 for (int i = 0; i < temp.length; i+=3) 694 { 695 int rgb = pixel_buffer[((lr_y-n)*width + ul_x) + i/3]; 696 Color c = new Color(rgb); 697 temp[i + 0] = (byte)(c.getRed()); 698 temp[i + 1] = (byte)(c.getGreen()); 699 temp[i + 2] = (byte)(c.getBlue()); 700 } 701 */ 702 fos.write(temp); // write one row of data 703 } 704 } 705 catch (IOException e) 706 { 707 System.err.printf("ERROR! Could not write to file %s\n", filename); 708 e.printStackTrace(System.err); 709 System.exit(-1); 710 } 711 712 try 713 { 714 fos.close(); 715 } 716 catch (IOException e) 717 { 718 System.err.printf("ERROR! Could not close file %s\n", filename); 719 e.printStackTrace(System.err); 720 System.exit(-1); 721 } 722 }//dumpPixels2File() 723 724 725 /** 726 A simple test of the framebuffer. 727 It fills the framebuffer with a test pattern. 728 */ 729 public void fbTest() 730 { 731 for (int y = 0; y < this.height; y++) 732 { 733 for (int x = 0; x < this.width; x++) 734 { 735 int gray = (x|y)%255; 736 setPixelFB(x, y, new Color(gray, gray, gray)); 737 } 738 } 739 }//fbTest() 740 741 742 /** 743 A simple test of the viewport. 744 It fills the viewport with a test pattern. 745 */ 746 public void vpTest() 747 { 748 for (int y = 0; y < this.getHeightVP(); y++) 749 { 750 for (int x = 0; x < this.getWidthVP(); x++) 751 { 752 int gray = (x|y)%255; 753 setPixelVP(x, y, new Color(gray, gray, gray)); 754 } 755 } 756 }//vpTest() 757 758 759 /** 760 A main() method for testing the FrameBuffer class. 761 762 @param args command-line arguments to main() method 763 */ 764 public static void main(String[] args) 765 { 766 int w = 512; 767 int h = 512; 768 FrameBuffer fb = new FrameBuffer(w, h); 769 fb.fbTest(); // fill the framebuffer with a test pattern 770 fb.dumpFB2File("test01.ppm"); 771 772 fb.setViewport(64, 64, 192, 320); // 192 by 320 773 fb.clearVP( Color.red ); 774 for (int i = 0; i < 512; i++) 775 fb.setPixelFB(128, i, Color.blue); 776 for (int i = 0; i < 192; i++) 777 fb.setPixelVP(i, i, Color.green); 778 779 fb.dumpFB2File("test02.ppm"); 780 fb.dumpVP2File("test03.ppm"); 781 fb.dumpPixels2File(32, 256-64, 511-64, 255+64, "test04.ppm"); // 416 by 128 782 783 fb.setViewport(80, 80, 160, 160); // 160 by 160 784 fb.vpTest(); // fill the viewport with a test pattern 785 fb.dumpFB2File("test05.ppm"); 786 787 FrameBuffer fb2 = new FrameBuffer("test05.ppm"); 788 fb2.dumpFB2File("test06.ppm"); 789 790 fb.convertRed2FB().dumpFB2File("test07.ppm"); 791 fb.convertGreen2FB().dumpFB2File("test08.ppm"); 792 fb.convertBlue2FB().dumpFB2File("test09.ppm"); 793 fb.convertBlue2FB().convertVP2FB().dumpFB2File("test10.ppm"); 794 795 FrameBuffer fb3 = new FrameBuffer(600, 600); 796 fb3.clearFB(Color.orange); 797 fb3.setViewport(44, 44, "test05.ppm"); 798 fb3.dumpFB2File("test11.ppm"); 799 fb3.setViewport(86, 86, fb3.convertVP2FB()); 800 fb3.dumpFB2File("test12.ppm"); 801 }//main() 802}