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