AviExporter::AviExporter(const String& fileName, Vec2i size, int fps)
:   m_file  (fileName, File::Create),
    m_size  (size),
    m_frame (size, ImageFormat::R8_G8_B8),
    m_fps   (fps)
{
    FW_ASSERT(size.min() > 0 && fps > 0);
    m_lineBytes  = (m_size.x * 3 + 3) & -4;
    m_frameBytes = m_lineBytes * m_size.y;
    m_numFrames  = 0;
    writeHeader();
}
void FW::exportTiffImage(OutputStream& stream, const Image* image)
{
    // Select format and convert.
    // - RGBA and floats are not well-supported.
    // - PackBits compression does not make sense for RGB data.

    FW_ASSERT(image);
    Vec2i size = image->getSize();
    bool empty = (size.min() <= 0);
    const Image* source = image;
    Image* converted = NULL;

    if (empty ||
        image->getFormat().getID() != ImageFormat::R8_G8_B8 ||
        (int)image->getStride() != size.x * 4)
    {
        size = Vec2i(max(size.x, 1), max(size.y, 1));
        converted = new Image(size, ImageFormat::R8_G8_B8);
        if (empty)
            converted->clear();
        else
            *converted = *image;
        source = converted;
    }

    int numBytes = (int)source->getStride() * size.y;

    // Write.

    stream.writeU8('I');                    // 0x00: endianess tag
    stream.writeU8('I');                    // 0x01: (little-endian)
    stream.writeU16LE(42);                  // 0x02: format identifier
    stream.writeU32LE(0x08);                // 0x04: IFD offset
    stream.writeU16LE(12);                  // 0x08: number of IFD entries

    stream.writeU16LE(256);                 // 0x0A: Tag = ImageWidth
    stream.writeU16LE(4);                   // 0x0C: Type = LONG
    stream.writeU32LE(1);                   // 0x0E: Count
    stream.writeU32LE(size.x);              // 0x12: Value

    stream.writeU16LE(257);                 // 0x16: Tag = ImageLength
    stream.writeU16LE(4);                   // 0x18: Type = LONG
    stream.writeU32LE(1);                   // 0x1A: Count
    stream.writeU32LE(size.y);              // 0x1E: Value

    stream.writeU16LE(258);                 // 0x22: Tag = BitsPerSample
    stream.writeU16LE(3);                   // 0x24: Type = SHORT
    stream.writeU32LE(3);                   // 0x26: Count
    stream.writeU32LE(0x9E);                // 0x2A: Value offset

    stream.writeU16LE(259);                 // 0x2E: Tag = Compression
    stream.writeU16LE(3);                   // 0x30: Type = SHORT
    stream.writeU32LE(1);                   // 0x32: Count
    stream.writeU32LE(1);                   // 0x36: Value = No compression

    stream.writeU16LE(262);                 // 0x3A: Tag = PhotometricInterpretation
    stream.writeU16LE(3);                   // 0x3C: Type = SHORT
    stream.writeU32LE(1);                   // 0x3E: Count
    stream.writeU32LE(2);                   // 0x42: Value = RGB

    stream.writeU16LE(273);                 // 0x46: Tag = StripOffsets
    stream.writeU16LE(4);                   // 0x48: Type = LONG
    stream.writeU32LE(1);                   // 0x4A: Count
    stream.writeU32LE(0xAC);                // 0x4E: Value

    stream.writeU16LE(277);                 // 0x52: Tag = SamplesPerPixel
    stream.writeU16LE(3);                   // 0x54: Type = SHORT
    stream.writeU32LE(1);                   // 0x56: Count
    stream.writeU32LE(3);                   // 0x5A: Value

    stream.writeU16LE(278);                 // 0x5E: Tag = RowsPerStrip
    stream.writeU16LE(4);                   // 0x60: Type = LONG
    stream.writeU32LE(1);                   // 0x62: Count
    stream.writeU32LE(size.y);              // 0x66: Value

    stream.writeU16LE(279);                 // 0x6A: Tag = StripByteCounts
    stream.writeU16LE(4);                   // 0x6C: Type = LONG
    stream.writeU32LE(1);                   // 0x6E: Count
    stream.writeU32LE(numBytes);            // 0x72: Value

    stream.writeU16LE(282);                 // 0x76: Tag = XResolution
    stream.writeU16LE(5);                   // 0x78: Type = RATIONAL
    stream.writeU32LE(1);                   // 0x7A: Count
    stream.writeU32LE(0xA4);                // 0x7E: Value offset

    stream.writeU16LE(283);                 // 0x82: Tag = YResolution
    stream.writeU16LE(5);                   // 0x84: Type = RATIONAL
    stream.writeU32LE(1);                   // 0x86: Count
    stream.writeU32LE(0xA4);                // 0x8A: Value offset

    stream.writeU16LE(296);                 // 0x8E: Tag = ResolutionUnit
    stream.writeU16LE(3);                   // 0x90: Type = SHORT
    stream.writeU32LE(1);                   // 0x92: Count
    stream.writeU32LE(2);                   // 0x96: Value = Inch

    stream.writeU32LE(0);                   // 0x9A: next IFD offset
    stream.writeU16LE(8);                   // 0x9E: BitsPerSample[0]
    stream.writeU16LE(8);                   // 0xA0: BitsPerSample[1]
    stream.writeU16LE(8);                   // 0xA2: BitsPerSample[2]
    stream.writeU32LE(72);                  // 0xA4: Resolution numerator
    stream.writeU32LE(1);                   // 0xA8: Resolution denominator

    stream.write(source->getPtr(), numBytes); // 0xAC: image data

    // Clean up.

    delete converted;
}