Java-Gaming.org    
Featured games (81)
games approved by the League of Dukes
Games in Showcase (499)
Games in Android Showcase (118)
games submitted by our members
Games in WIP (566)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  Custom .TIFF writer  (Read 1652 times)
0 Members and 1 Guest are viewing this topic.
Offline Spasi
« Posted 2003-04-02 20:12:44 »

Hi everybody,

NOTE: This code was written to be applied in an LWJGL game and as a result, offers very "specific" functionality. Nonetheless I believe it may have other uses as well. I just hope it's going to help some of you...

Some time ago I implemented a screenshot capture method. I used ImageIO to do the screenshot writting. Of course it wasn't good enough for my needs, as it added both memory overhead (more classes loaded, obligatory use of BufferedImage Tongue, etc.) and a lot of CPU overhead (in my Athlon 700MHz it took 1.5-2 seconds per capture). It did it's job, but I had to use another method to go at least under 1 sec / capture.

I wanted to have a fast solution (1-3 captures per second), and export a cross-platform loss-less image format. From what ImageIO offers (NOTE: I used the new ImageIO extension, that adds more formats), the only formats that satisfy the cross-platform / loss-less part are .PNG and .TIFF (maybe .BMP too? I have never touched a Mac...). I tried both, using ImageIO and although the new extension adds some native code for read/write acceleration, the results were unsatisfactory when it came to speed. I even tried some other formats, with no luck Sad

Finally I decided to implement my own writer. I chose .TIFF, as it is the "most" cross-platform of all the others, it supports uncompressed data (no need for time consuming compression algos), and is easy to write Wink.

So, here it is, for you to use and enjoy! Overall, I got a ten-fold(!) speed increase, grabbing ~5 screenshots / second. It's not a complete .TIFF writer, you can just write 3 bytes/pixel RGB images. If you want other types, feel free to extend it's functionality Roll Eyes.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
/** Screenshot count */
private static int lastCapture = 1;

/** A buffer that holds the inversed screenshot image */
private static byte[] captureData = new byte[Display.WIDTH * Display.HEIGHT * 3];

public static void capture() throws IOException {
      // Get a temp ByteBuffer
     final ByteDataBuffer captureDataBuffer = Memory.getByteDataBuffer(captureData.length);

      // Read Frame Buffer
     Scene.gl.readPixels(0, 0, Display.WIDTH, Display.HEIGHT, GL.RGB, GL.UNSIGNED_BYTE, captureDataBuffer.address);

      int bytesPerRow = Display.WIDTH * 3;
      int lastRow = Display.HEIGHT - 1;

      // Inverse captured image
     ByteBuffer captureBuffer = captureDataBuffer.data;
      for ( int i = 0; i < Display.HEIGHT; i++ )
            captureBuffer.get(captureData, ( lastRow - i ) * bytesPerRow, bytesPerRow);

      // Write screenshot
     TIFFWriter.writeImage(Display.WIDTH, Display.HEIGHT, captureData, new File("screenshot" + lastCapture++ + ".tiff"));

      // Release temp ByteBuffer
     Memory.releaseDataBuffer(captureDataBuffer);
}


Memory.getByteDataBuffer() is just a method that grabs a ByteBuffer from another huge ByteBuffer. Saves a lot of overhead ( Cas, thanks for your precious tips ;-). Always helpful. ). Memory.releaseDataBuffer() releases the allocated memory. I strongly suggest you go implement sth like this (for those who haven't yet). Otherwise, all you have to do is provide a direct ByteBuffer to put the pixel data from the Frame Buffer.

After grabbing the pixels, the image rows get inverted (0,0 coordinate bottom-left Shocked? What were they thinking?? Just kidding, I've gotten used to it...) and then we give the data to the TIFFWriter.

NOTE: I've used Display.WIDTH and Display.HEIGHT. This is the screen resolution. Modify according to your design.

Look at the next post for TIFFWriter implementation and comments...
Offline Spasi
« Reply #1 - Posted 2003-04-02 20:14:41 »

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
100  
101  
102  
103  
104  
105  
106  
107  
108  
109  
110  
111  
112  
113  
114  
115  
116  
117  
118  
119  
120  
121  
122  
123  
124  
125  
126  
127  
128  
129  
130  
131  
132  
133  
134  
135  
136  
137  
138  
139  
140  
141  
142  
143  
144  
145  
146  
147  
148  
149  
150  
151  
152  
153  
154  
155  
156  
157  
158  
159  
160  
161  
162  
163  
164  
165  
166  
167  
168  
169  
170  
171  
172  
173  
174  
175  
176  
177  
178  
179  
180  
181  
182  
183  
184  
185  
186  
187  
188  
189  
190  
191  
192  
193  
194  
195  
196  
197  
198  
199  
200  
201  
202  
203  
204  
205  
206  
207  
208  
209  
210  
211  
212  
213  
214  
215  
216  
217  
218  
219  
220  
221  
222  
223  
224  
225  
226  
227  
228  
229  
230  
231  
232  
233  
234  
235  
236  
237  
238  
239  
240  
241  
242  
243  
244  
245  
246  
247  
248  
249  
250  
251  
252  
253  
254  
255  
256  
257  
258  
259  
260  
261  
262  
263  
264  
265  
import java.nio.*;
import java.io.*;
import java.util.Calendar;

/**
 * TIFFWriter implements .TIFF image writting.
 * The only image type it supports is uncompressed RGB.
 *
 * @author Tsakpinis Ioannis <b>spasi@hiphop.gr</b>
 */

public final class TIFFWriter {

      // ------------------- TIFF CONSTANTS -------------------
     //private final static short BYTE = 1;
     private final static short ASCII = 2;
      private final static short SHORT = 3;
      private final static short LONG = 4;
      private final static short RATIONAL = 5;

      private final static short HEADER_TIFF_MAGIC_NUMBER = 42;

      // ------------------- HEADER -------------------
     private final static byte[] HEADER_BYTEORDER = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN ? new byte[]{'I', 'I'} : new byte[]{'M', 'M'};
      private final static int HEADER_FIRST_IFD_OFFSET = 8;

      // ------------------- IMAGE FILE DIRECTORY -------------------

      private final static short IFD_ENTRY_COUNT = 16;

      // IFD ENTRIES FOR FULL-RGB IMAGES
     private final static short IFD_NEW_SUBFILE_TYPE = 254;
      private final static short IFD_IMAGE_WIDTH = 256;
      private final static short IFD_IMAGE_LENGTH = 257;
      private final static short IFD_BITS_PER_SAMPLE = 258;
      private final static short IFD_COMPRESSION = 259;
      private final static short IFD_PHOTOMETRIC_INTERPOLATION = 262;
      private final static short IFD_STRIP_OFFSETS = 273;
      private final static short IFD_SAMPLES_PER_PIXEL = 277;
      private final static short IFD_ROWS_PER_STRIP = 278;
      private final static short IFD_STRIP_BYTE_COUNTS = 279;
      private final static short IFD_X_RESOLUTION = 282;
      private final static short IFD_Y_RESOLUTION = 283;
      private final static short IFD_RESOLUTION_UNIT = 296;

      // OPTIONAL IFD ENTRIES
     private final static short IFD_SOFTWARE = 305;
      private final static short IFD_DATE_TIME = 306;
      private final static short IFD_IMAGE_DESCRIPTION = 270;

      private final static byte[] SOFTWARE = "Put your software name here".getBytes();
      private final static byte[] IMAGE_DESCRIPTION = "Put an image description here".getBytes();

      private final static int HEADER_SIZE = ( 2 * 2 ) + ( 4 * 1 );
      private final static int IFD_SIZE = 2 + 12 * IFD_ENTRY_COUNT + 4;
      private final static int LONG_VARIABLES_SIZE = 6 + 8 + 8 + 20 + SOFTWARE.length + 1 + IMAGE_DESCRIPTION.length + 1;

      // IFD OFFSETS
     private final static int BITS_PER_SAMPLE_OFFSET = 206;
      private final static int STRIPS_OFFSETS = 248 + SOFTWARE.length + 1 + IMAGE_DESCRIPTION.length + 1;
      private final static int X_RESOLUTION_OFFSET = 212;
      private final static int Y_RESOLUTION_OFFSET = 220;
      private final static int SOFTWARE_OFFSET = 248;
      private final static int IMAGE_DESCRIPTION_OFFSET = 248 + SOFTWARE.length + 1;

      // IFD VARIABLES' OFFSETS
     private final static int IMAGE_WIDTH_OFFSET = 30;
      private final static int IMAGE_HEIGHT_OFFSET = 42;
      private final static int ROWS_PER_STRIP_OFFSET = 114;
      private final static int STRIP_BYTE_COUNTS_OFFSET = 126;
      private final static int DATE_TIME_OFFSET = 228;

      /** This ByteBuffer will hold the TIFF header, the IFD and some of the other metadata (>4 bytes values) */
      private final static ByteBuffer metadata = ByteBuffer.allocate(HEADER_SIZE + IFD_SIZE + LONG_VARIABLES_SIZE).order(ByteOrder.nativeOrder());

      /** A Calendar instance that gets update on every capture */
      private final static Calendar calendar = Calendar.getInstance();

      /** A buffer to hold the formatted current date & time */
      private final static byte[] dateTime = new byte[20];

      static {
            // INITIALISE METADATA BUFFER
           metadata.put(HEADER_BYTEORDER);
            metadata.putShort(HEADER_TIFF_MAGIC_NUMBER);
            metadata.putInt(HEADER_FIRST_IFD_OFFSET);

            metadata.putShort(IFD_ENTRY_COUNT);

            metadata.putShort(IFD_NEW_SUBFILE_TYPE);
            metadata.putShort(LONG);
            metadata.putInt(1);
            metadata.putInt(0);

            metadata.putShort(IFD_IMAGE_WIDTH);
            metadata.putShort(LONG);
            metadata.putInt(1);
            metadata.putInt(0); // PUT IMAGE WIDTH HERE

            metadata.putShort(IFD_IMAGE_LENGTH);
            metadata.putShort(LONG);
            metadata.putInt(1);
            metadata.putInt(0); // PUT IMAGE LENGTH HERE

            metadata.putShort(IFD_BITS_PER_SAMPLE);
            metadata.putShort(SHORT);
            metadata.putInt(3);
            metadata.putInt(BITS_PER_SAMPLE_OFFSET);

            metadata.putShort(IFD_COMPRESSION);
            metadata.putShort(SHORT);
            metadata.putInt(1);
            metadata.putInt(1);

            metadata.putShort(IFD_PHOTOMETRIC_INTERPOLATION);
            metadata.putShort(SHORT);
            metadata.putInt(1);
            metadata.putInt(2);

            metadata.putShort(IFD_STRIP_OFFSETS);
            metadata.putShort(LONG);
            metadata.putInt(1);
            metadata.putInt(STRIPS_OFFSETS);

            metadata.putShort(IFD_SAMPLES_PER_PIXEL);
            metadata.putShort(SHORT);
            metadata.putInt(1);
            metadata.putInt(3);

            metadata.putShort(IFD_ROWS_PER_STRIP);
            metadata.putShort(SHORT);
            metadata.putInt(1);
            metadata.putInt(0); // PUT ROWS PER STRIP HERE

            metadata.putShort(IFD_STRIP_BYTE_COUNTS);
            metadata.putShort(LONG);
            metadata.putInt(1);
            metadata.putInt(0); // PUT STRIP BYTE COUNTS HERE

            metadata.putShort(IFD_RESOLUTION_UNIT);
            metadata.putShort(SHORT);
            metadata.putInt(1);
            metadata.putInt(1);

            metadata.putShort(IFD_X_RESOLUTION);
            metadata.putShort(RATIONAL);
            metadata.putInt(1);
            metadata.putInt(X_RESOLUTION_OFFSET);

            metadata.putShort(IFD_Y_RESOLUTION);
            metadata.putShort(RATIONAL);
            metadata.putInt(1);
            metadata.putInt(Y_RESOLUTION_OFFSET);

            metadata.putShort(IFD_DATE_TIME);
            metadata.putShort(ASCII);
            metadata.putInt(20);
            metadata.putInt(DATE_TIME_OFFSET);

            metadata.putShort(IFD_SOFTWARE);
            metadata.putShort(ASCII);
            metadata.putInt(SOFTWARE.length + 1);
            metadata.putInt(SOFTWARE_OFFSET);

            metadata.putShort(IFD_IMAGE_DESCRIPTION);
            metadata.putShort(ASCII);
            metadata.putInt(IMAGE_DESCRIPTION.length + 1);
            metadata.putInt(IMAGE_DESCRIPTION_OFFSET);

            // We have only one IFD
           metadata.putInt(0);

            // Bits per Sample
           metadata.putShort((short)8).putShort((short)8).putShort((short)8);

            // X,Y Resolution
           metadata.putLong(1).putLong(1);

            // Skip DATE-TIME
           metadata.position(metadata.position() + 20);

            metadata.put(SOFTWARE);
            metadata.put((byte)0);
            metadata.put(IMAGE_DESCRIPTION);
            metadata.put((byte)0);
      }

      private TIFFWriter() {
      }

      /**
       * Writes an image to a TIFF image format file.
       * @param w the image width
       * @param h the image height
       * @param pixels the image pixel data
       * @param output the file to write the image to
       * @throws IOException If an IO error occurs
       */

      public final static void writeImage(int w, int h, byte[] pixels, File output) throws IOException {
            FileOutputStream fos = new FileOutputStream(output);

            metadata.putInt(IMAGE_WIDTH_OFFSET, w);
            metadata.putInt(IMAGE_HEIGHT_OFFSET, h);
            metadata.putInt(ROWS_PER_STRIP_OFFSET, h);
            metadata.putInt(STRIP_BYTE_COUNTS_OFFSET, w * h * 3);

            updateTime();
            metadata.position(DATE_TIME_OFFSET);
            metadata.put(dateTime);

            fos.write(metadata.array());
            fos.write(pixels);

            fos.close();
      }

      /**
       * Updates the calendar and puts the current
       * date and time to a buffer, specially formatted
       * to the TIFF Date & Time format.
       * Example: "2003:04:02 18:30:15\0"
       */

      private final static void updateTime() {
            // Reset current date and time
           for ( int i = 0; i < dateTime.length; i++ )
                  dateTime[i] = 0;

            calendar.setTimeInMillis(System.currentTimeMillis());

            int value = calendar.get(Calendar.YEAR);
            dateTime[0] = (byte)( '0' + ( value / 1000 ) );
            dateTime[1] = (byte)( '0' + ( ( value % 1000 ) / 100 ) );
            dateTime[2] = (byte)( '0' + ( ( value % 100 ) / 10 ) );
            dateTime[3] = (byte)( '0' + ( value % 10 ) );

            dateTime[4] = ':';

            value = calendar.get(Calendar.MONTH);
            dateTime[5] = (byte)( '0' + ( value / 10 ) );
            dateTime[6] = (byte)( '0' + ( value % 10 ) );

            dateTime[7] = ':';

            value = calendar.get(Calendar.DAY_OF_MONTH);
            dateTime[8] = (byte)( '0' + ( value / 10 ) );
            dateTime[9] = (byte)( '0' + ( value % 10 ) );

            dateTime[10] = ' ';

            value = calendar.get(Calendar.HOUR_OF_DAY);
            dateTime[11] = (byte)( '0' + ( value / 10 ) );
            dateTime[12] = (byte)( '0' + ( value % 10 ) );

            dateTime[13] = ':';

            value = calendar.get(Calendar.MINUTE);
            dateTime[14] = (byte)( '0' + ( value / 10 ) );
            dateTime[15] = (byte)( '0' + ( value % 10 ) );

            dateTime[16] = ':';

            value = calendar.get(Calendar.MINUTE);
            dateTime[17] = (byte)( '0' + ( value / 10 ) );
            dateTime[18] = (byte)( '0' + ( value % 10 ) );
      }
}


This code writes the .TIFF image according to the specification. It puts 3 more entries to the image meta-data, although not necessary at all, just for fun. These are SOFTWARE, which is the name of the program used to write the image, IMAGE DESCRIPTION, which is a description of the image content and DATE_TIME, which is the date and time of the image writting. You can put anything you like as SOFTWARE and IMAGE DESCRIPTION. The updateTime method gets the current date and time from a Calendar and formats the result as the .TIFF specification dictates (don't modify). If anyone wants to modify this code, I strongly urge him/her to download the spec, as I've hard-coded many byte offsets that will mess up if something gets changed. As I said in my previous post, this is a very "specific" implementation...

Please let me know if it was of any use to you. And I'd appreciate any optimization suggestions...
Pages: [1]
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

Pippogeek (35 views)
2014-09-24 16:13:29

Pippogeek (28 views)
2014-09-24 16:12:22

Pippogeek (17 views)
2014-09-24 16:12:06

Grunnt (40 views)
2014-09-23 14:38:19

radar3301 (23 views)
2014-09-21 23:33:17

BurntPizza (58 views)
2014-09-21 02:42:18

BurntPizza (29 views)
2014-09-21 01:30:30

moogie (34 views)
2014-09-21 00:26:15

UprightPath (47 views)
2014-09-20 20:14:06

BurntPizza (51 views)
2014-09-19 03:14:18
List of Learning Resources
by Longor1996
2014-08-16 10:40:00

List of Learning Resources
by SilverTiger
2014-08-05 19:33:27

Resources for WIP games
by CogWheelz
2014-08-01 16:20:17

Resources for WIP games
by CogWheelz
2014-08-01 16:19:50

List of Learning Resources
by SilverTiger
2014-07-31 16:29:50

List of Learning Resources
by SilverTiger
2014-07-31 16:26:06

List of Learning Resources
by SilverTiger
2014-07-31 11:54:12

HotSpot Options
by dleskov
2014-07-08 01:59:08
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!