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