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}