-
Notifications
You must be signed in to change notification settings - Fork 241
/
AVIWriter.java
564 lines (462 loc) · 20.2 KB
/
AVIWriter.java
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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
/*
* #%L
* BSD implementations of Bio-Formats readers and writers
* %%
* Copyright (C) 2005 - 2016 Open Microscopy Environment:
* - Board of Regents of the University of Wisconsin-Madison
* - Glencoe Software, Inc.
* - University of Dundee
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/
package loci.formats.out;
import java.awt.image.IndexColorModel;
import java.io.IOException;
import java.util.Vector;
import loci.common.RandomAccessInputStream;
import loci.formats.FormatException;
import loci.formats.FormatTools;
import loci.formats.FormatWriter;
import loci.formats.meta.MetadataRetrieve;
/**
* AVIWriter is the file format writer for AVI files.
*
* Much of this writer's code was adapted from Wayne Rasband's
* AVI Movie Writer plugin for ImageJ
* (available at http://rsb.info.nih.gov/ij/).
*/
public class AVIWriter extends FormatWriter {
// -- Constants --
private static final long SAVE_MOVI = 4092;
private static final long SAVE_FILE_SIZE = 4;
// location of length of strf CHUNK - not including the first 8 bytes with
// strf and size. strn follows the end of this CHUNK.
private static final long SAVE_STRF_SIZE = 168;
private static final long SAVE_STRN_POS = SAVE_STRF_SIZE + 1068;
private static final long SAVE_JUNK_SIG = SAVE_STRN_POS + 24;
// location of length of CHUNK with first LIST - not including first 8
// bytes with LIST and size. JUNK follows the end of this CHUNK
private static final long SAVE_LIST1_SIZE = 16;
// location of length of CHUNK with second LIST - not including first 8
// bytes with LIST and size. Note that saveLIST1subSize = saveLIST1Size +
// 76, and that the length size written to saveLIST2Size is 76 less than
// that written to saveLIST1Size. JUNK follows the end of this CHUNK.
private static final long SAVE_LIST1_SUBSIZE = 92;
private static final long FRAME_OFFSET = 48;
private static final long FRAME_OFFSET_2 = 140;
private static final long PADDING_BYTES = 4076 - SAVE_JUNK_SIG;
private static final long SAVE_LIST2_SIZE = 4088;
private static final String DATA_SIGNATURE = "00db";
// -- Fields --
private int planesWritten = 0;
private int bytesPerPixel;
private int xDim, yDim, zDim, tDim, xPad;
private int microSecPerFrame;
private Vector<Long> savedbLength;
private long idx1Pos;
private long endPos;
private long saveidx1Length;
// -- Constructor --
public AVIWriter() { super("Audio Video Interleave", "avi"); }
// -- IFormatWriter API methods --
/**
* @see loci.formats.IFormatWriter#saveBytes(int, byte[], int, int, int, int)
*/
@Override
public void saveBytes(int no, byte[] buf, int x, int y, int w, int h)
throws FormatException, IOException
{
checkParams(no, buf, x, y, w, h);
if (!isFullPlane(x, y, w, h)) {
throw new FormatException(
"AVIWriter does not yet support saving image tiles.");
}
int nChannels = getSamplesPerPixel();
if (!initialized[series][no]) {
initialized[series][no] = true;
}
// Write the data. Each 3-byte triplet in the bitmap array represents the
// relative intensities of blue, green, and red, respectively, for a pixel.
// The color bytes are in reverse order from the Windows convention.
int width = xDim - xPad;
int height = buf.length / (width * bytesPerPixel);
out.seek(idx1Pos);
out.writeBytes(DATA_SIGNATURE);
savedbLength.add(new Long(out.getFilePointer()));
// Write the data length
out.writeInt(bytesPerPixel * xDim * yDim);
int rowPad = xPad * bytesPerPixel;
byte[] rowBuffer = new byte[width * bytesPerPixel + rowPad];
for (int row=height-1; row>=0; row--) {
for (int col=0; col<width; col++) {
int offset = row * width + col;
if (interleaved) offset *= nChannels;
byte r = buf[offset];
if (nChannels > 1) {
byte g = buf[offset + (interleaved ? 1 : width * height)];
byte b = 0;
if (nChannels > 2) {
b = buf[offset + (interleaved ? 2 : 2 * width * height)];
}
rowBuffer[col * bytesPerPixel] = b;
rowBuffer[col * bytesPerPixel + 1] = g;
}
rowBuffer[col * bytesPerPixel + bytesPerPixel - 1] = r;
}
out.write(rowBuffer);
}
planesWritten++;
// Write the idx1 CHUNK
// Write the 'idx1' signature
idx1Pos = out.getFilePointer();
out.seek(SAVE_LIST2_SIZE);
out.writeInt((int) (idx1Pos - (SAVE_LIST2_SIZE + 4)));
out.seek(idx1Pos);
out.writeBytes("idx1");
saveidx1Length = out.getFilePointer();
// Write the length of the idx1 CHUNK not including the idx1 signature
out.writeInt(4 + (planesWritten*16));
for (int z=0; z<planesWritten; z++) {
// In the ckid field write the 4 character code to identify the chunk
// 00db or 00dc
out.writeBytes(DATA_SIGNATURE);
// Write the flags - select AVIIF_KEYFRAME
if (z == 0) out.writeInt(0x10);
else out.writeInt(0x00);
// AVIIF_KEYFRAME 0x00000010L
// The flag indicates key frames in the video sequence.
// Key frames do not need previous video information to be
// decompressed.
// AVIIF_NOTIME 0x00000100L The CHUNK does not influence video timing
// (for example a palette change CHUNK).
// AVIIF_LIST 0x00000001L Marks a LIST CHUNK.
// AVIIF_TWOCC 2L
// AVIIF_COMPUSE 0x0FFF0000L These bits are for compressor use.
out.writeInt((int) (savedbLength.get(z) - 4 - SAVE_MOVI));
// Write the offset (relative to the 'movi' field) to the relevant
// CHUNK. Write the length of the relevant CHUNK. Note that this length
// is also written at savedbLength
out.writeInt(bytesPerPixel*xDim*yDim);
}
endPos = out.getFilePointer();
out.seek(SAVE_FILE_SIZE);
out.writeInt((int) (endPos - (SAVE_FILE_SIZE + 4)));
out.seek(saveidx1Length);
out.writeInt((int) (endPos - (saveidx1Length + 4)));
// write the total number of planes
out.seek(FRAME_OFFSET);
out.writeInt(planesWritten);
out.seek(FRAME_OFFSET_2);
out.writeInt(planesWritten);
}
/* @see loci.formats.IFormatWriter#canDoStacks() */
@Override
public boolean canDoStacks() { return true; }
/* @see loci.formats.IFormatWriter#getPixelTypes(String) */
@Override
public int[] getPixelTypes(String codec) {
return new int[] {FormatTools.UINT8};
}
// -- FormatWriter API methods --
/* @see loci.formats.FormatWriter#close() */
@Override
public void close() throws IOException {
super.close();
planesWritten = 0;
bytesPerPixel = 0;
xDim = yDim = zDim = tDim = xPad = 0;
microSecPerFrame = 0;
savedbLength = null;
idx1Pos = 0;
endPos = 0;
saveidx1Length = 0;
}
/* @see loci.formats.FormatWriter#setId(String) */
@Override
public void setId(String id) throws FormatException, IOException {
super.setId(id);
savedbLength = new Vector<Long>();
if (out.length() > 0) {
RandomAccessInputStream in = new RandomAccessInputStream(currentId);
in.order(true);
in.seek(FRAME_OFFSET);
planesWritten = in.readInt();
in.seek(SAVE_FILE_SIZE);
endPos = in.readInt() + SAVE_FILE_SIZE + 4;
in.seek(SAVE_LIST2_SIZE);
idx1Pos = in.readInt() + SAVE_LIST2_SIZE + 4;
saveidx1Length = idx1Pos + 4;
if (planesWritten > 0) in.seek(saveidx1Length + 4);
for (int z=0; z<planesWritten; z++) {
in.skipBytes(8);
savedbLength.add(in.readInt() + 4 + SAVE_MOVI);
in.skipBytes(4);
}
in.close();
out.seek(idx1Pos);
}
else {
planesWritten = 0;
}
out.order(true);
MetadataRetrieve meta = getMetadataRetrieve();
tDim = meta.getPixelsSizeZ(series).getValue().intValue();
zDim = meta.getPixelsSizeT(series).getValue().intValue();
yDim = meta.getPixelsSizeY(series).getValue().intValue();
xDim = meta.getPixelsSizeX(series).getValue().intValue();
String type = meta.getPixelsType(series).toString();
int pixelType = FormatTools.pixelTypeFromString(type);
bytesPerPixel = FormatTools.getBytesPerPixel(pixelType);
bytesPerPixel *= getSamplesPerPixel();
xPad = 0;
int xMod = xDim % 4;
if (xMod != 0) {
xPad = 4 - xMod;
xDim += xPad;
}
byte[][] lut = null;
if (getColorModel() instanceof IndexColorModel) {
lut = new byte[4][256];
IndexColorModel model = (IndexColorModel) getColorModel();
model.getReds(lut[0]);
model.getGreens(lut[1]);
model.getBlues(lut[2]);
model.getAlphas(lut[3]);
}
if (out.length() == 0) {
out.writeBytes("RIFF"); // signature
// Bytes 4 thru 7 contain the length of the file. This length does
// not include bytes 0 thru 7.
out.writeInt(0); // for now write 0 for size
out.writeBytes("AVI "); // RIFF type
// Write the first LIST chunk, which contains
// information on data decoding
out.writeBytes("LIST"); // CHUNK signature
// Write the length of the LIST CHUNK not including the first 8 bytes
// with LIST and size. Note that the end of the LIST CHUNK is followed
// by JUNK.
out.writeInt((bytesPerPixel == 1) ? 1240 : 216);
out.writeBytes("hdrl"); // CHUNK type
out.writeBytes("avih"); // Write the avih sub-CHUNK
// Write the length of the avih sub-CHUNK (38H) not including the
// the first 8 bytes for avihSignature and the length
out.writeInt(0x38);
// dwMicroSecPerFrame - Write the microseconds per frame
microSecPerFrame = (int) (1.0 / fps * 1.0e6);
out.writeInt(microSecPerFrame);
// Write the maximum data rate of the file in bytes per second
out.writeInt(0); // dwMaxBytesPerSec
out.writeInt(0); // dwReserved1 - set to 0
// dwFlags - just set the bit for AVIF_HASINDEX
out.writeInt(0x10);
// 10H AVIF_HASINDEX: The AVI file has an idx1 chunk containing
// an index at the end of the file. For good performance, all
// AVI files should contain an index.
// 20H AVIF_MUSTUSEINDEX: Index CHUNK, rather than the physical
// ordering of the chunks in the file, must be used to determine the
// order of the frames.
// 100H AVIF_ISINTERLEAVED: Indicates that the AVI file is interleaved.
// This is used to read data from a CD-ROM more efficiently.
// 800H AVIF_TRUSTCKTYPE: USE CKType to find key frames
// 10000H AVIF_WASCAPTUREFILE: The AVI file is used for capturing
// real-time video. Applications should warn the user before
// writing over a file with this fla set because the user
// probably defragmented this file.
// 20000H AVIF_COPYRIGHTED: The AVI file contains copyrighted data
// and software. When, this flag is used, software should not
// permit the data to be duplicated.
// dwTotalFrames - total frame number
out.writeInt(0);
// dwInitialFrames -Initial frame for interleaved files.
// Noninterleaved files should specify 0.
out.writeInt(0);
// dwStreams - number of streams in the file - here 1 video and
// zero audio.
out.writeInt(1);
// dwSuggestedBufferSize - Suggested buffer size for reading the file.
// Generally, this size should be large enough to contain the largest
// chunk in the file.
out.writeInt(0);
// dwWidth - image width in pixels
out.writeInt(xDim - xPad);
out.writeInt(yDim); // dwHeight - height in pixels
// dwReserved[4] - Microsoft says to set the following 4 values to 0.
out.writeInt(0);
out.writeInt(0);
out.writeInt(0);
out.writeInt(0);
// Write the Stream line header CHUNK
out.writeBytes("LIST");
// Write the size of the first LIST subCHUNK not including the first 8
// bytes with LIST and size. Note that saveLIST1subSize = saveLIST1Size
// + 76, and that the length written to saveLIST1subSize is 76 less than
// the length written to saveLIST1Size. The end of the first LIST
// subCHUNK is followed by JUNK.
out.writeInt((bytesPerPixel == 1) ? 1164 : 140);
out.writeBytes("strl"); // Write the chunk type
out.writeBytes("strh"); // Write the strh sub-CHUNK
out.writeInt(56); // Write length of strh sub-CHUNK
// fccType - Write the type of data stream - here vids for video stream
out.writeBytes("vids");
// Write DIB for Microsoft Device Independent Bitmap.
// Note: Unfortunately, at least 3 other four character codes are
// sometimes used for uncompressed AVI videos: 'RGB ', 'RAW ',
// 0x00000000
out.writeBytes("DIB ");
out.writeInt(0); // dwFlags
// 0x00000001 AVISF_DISABLED The stram data should be rendered only when
// explicitly enabled.
// 0x00010000 AVISF_VIDEO_PALCHANGES Indicates that a palette change is
// included in the AVI file. This flag warns the playback software that
// it will need to animate the palette.
// dwPriority - priority of a stream type. For example, in a file with
// multiple audio streams, the one with the highest priority might be
// the default one.
out.writeInt(0);
// dwInitialFrames - Specifies how far audio data is skewed ahead of
// video frames in interleaved files. Typically, this is about 0.75
// seconds. In interleaved files specify the number of frames in the
// file prior to the initial frame of the AVI sequence.
// Noninterleaved files should use zero.
out.writeInt(0);
// rate/scale = samples/second
out.writeInt(1); // dwScale
// dwRate - frame rate for video streams
out.writeInt(fps);
// dwStart - this field is usually set to zero
out.writeInt(0);
// dwLength - playing time of AVI file as defined by scale and rate
// Set equal to the number of frames
out.writeInt(tDim * zDim);
// dwSuggestedBufferSize - Suggested buffer size for reading the stream.
// Typically, this contains a value corresponding to the largest chunk
// in a stream.
out.writeInt(0);
// dwQuality - encoding quality given by an integer between 0 and
// 10,000. If set to -1, drivers use the default quality value.
out.writeInt(-1);
// dwSampleSize #
// 0 if the video frames may or may not vary in size
// If 0, each sample of data(such as a video frame) must be in a
// separate chunk. If nonzero, then multiple samples of data can be
// grouped into a single chunk within the file.
out.writeInt(0);
// rcFrame - Specifies the destination rectangle for a text or video
// stream within the movie rectangle specified by the dwWidth and
// dwHeight members of the AVI main header structure. The rcFrame member
// is typically used in support of multiple video streams. Set this
// rectangle to the coordinates corresponding to the movie rectangle to
// update the whole movie rectangle. Units for this member are pixels.
// The upper-left corner of the destination rectangle is relative to the
// upper-left corner of the movie rectangle.
out.writeShort((short) 0); // left
out.writeShort((short) 0); // top
out.writeShort((short) 0); // right
out.writeShort((short) 0); // bottom
// Write the size of the stream format CHUNK not including the first 8
// bytes for strf and the size. Note that the end of the stream format
// CHUNK is followed by strn.
out.writeBytes("strf"); // Write the stream format chunk
// write the strf CHUNK size
out.writeInt((bytesPerPixel == 1) ? 1068 : 44);
// Applications should use this size to determine which BITMAPINFO
// header structure is being used. This size includes this biSize field.
// biSize- Write header size of BITMAPINFO header structure
out.writeInt(40);
// biWidth - image width in pixels
out.writeInt(xDim);
// biHeight - image height in pixels. If height is positive, the bitmap
// is a bottom up DIB and its origin is in the lower left corner. If
// height is negative, the bitmap is a top-down DIB and its origin is
// the upper left corner. This negative sign feature is supported by the
// Windows Media Player, but it is not supported by PowerPoint.
out.writeInt(yDim);
// biPlanes - number of color planes in which the data is stored
// This must be set to 1.
out.writeShort(1);
int bitsPerPixel = (bytesPerPixel == 3) ? 24 : 8;
// biBitCount - number of bits per pixel #
// 0L for BI_RGB, uncompressed data as bitmap
out.writeShort((short) bitsPerPixel);
out.writeInt(0); // biSizeImage #
out.writeInt(0); // biCompression - compression type
// biXPelsPerMeter - horizontal resolution in pixels
out.writeInt(0);
// biYPelsPerMeter - vertical resolution in pixels per meter
out.writeInt(0);
int nColors = 256;
out.writeInt(nColors);
// biClrImportant - specifies that the first x colors of the color table
// are important to the DIB. If the rest of the colors are not
// available, the image still retains its meaning in an acceptable
// manner. When this field is set to zero, all the colors are important,
// or, rather, their relative importance has not been computed.
out.writeInt(0);
// Write the LUTa.getExtents()[1] color table entries here. They are
// written: blue byte, green byte, red byte, 0 byte
if (bytesPerPixel == 1) {
if (lut != null) {
for (int i=0; i<256; i++) {
out.write(lut[2][i]);
out.write(lut[1][i]);
out.write(lut[0][i]);
out.write(lut[3][i]);
}
}
else {
byte[] lutWrite = new byte[4 * 256];
for (int i=0; i<256; i++) {
lutWrite[4*i] = (byte) i; // blue
lutWrite[4*i+1] = (byte) i; // green
lutWrite[4*i+2] = (byte) i; // red
lutWrite[4*i+3] = 0;
}
out.write(lutWrite);
}
}
out.seek(SAVE_STRF_SIZE);
out.writeInt((int) (SAVE_STRN_POS - (SAVE_STRF_SIZE + 4)));
out.seek(SAVE_STRN_POS);
// Use strn to provide zero terminated text string describing the stream
out.writeBytes("strn");
out.writeInt(16); // Write length of strn sub-CHUNK
out.writeBytes("FileAVI write ");
out.seek(SAVE_LIST1_SIZE);
out.writeInt((int) (SAVE_JUNK_SIG - (SAVE_LIST1_SIZE + 4)));
out.seek(SAVE_LIST1_SUBSIZE);
out.writeInt((int) (SAVE_JUNK_SIG - (SAVE_LIST1_SUBSIZE + 4)));
out.seek(SAVE_JUNK_SIG);
// write a JUNK CHUNK for padding
out.writeBytes("JUNK");
out.writeInt((int) PADDING_BYTES);
for (int i=0; i<PADDING_BYTES/2; i++) {
out.writeShort((short) 0);
}
// Write the second LIST chunk, which contains the actual data
out.writeBytes("LIST");
out.writeInt(4); // For now write 0
out.writeBytes("movi"); // Write CHUNK type 'movi'
idx1Pos = out.getFilePointer();
}
}
}