Files
OpenRA/OpenRA.Mods.Cnc/FileFormats/WsaReader.cs
penev92 c4ab7041b8 Updated VideoPlayerWidget to play new IVideo data
Added optional padding to video frames because that's what VideoPlayerWidget expects.
Keeping the option to not use padding for other use-cases like converting frames to PNG.
2022-01-11 18:16:31 +01:00

154 lines
4.1 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2021 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.IO;
using OpenRA.Video;
namespace OpenRA.Mods.Cnc.FileFormats
{
public class WsaReader : IVideo
{
public ushort FrameCount { get; }
public byte Framerate => 1;
public ushort Width { get; }
public ushort Height { get; }
public byte[] CurrentFrameData { get; }
public int CurrentFrameNumber { get; private set; }
public bool HasAudio => false;
public byte[] AudioData => null;
public int AudioChannels => 0;
public int SampleBits => 0;
public int SampleRate => 0;
readonly Stream stream;
readonly uint[] palette;
readonly uint[] frameOffsets;
readonly ushort totalFrameWidth;
byte[] previousFramePaletteIndexData;
byte[] currentFramePaletteIndexData;
public WsaReader(Stream stream, bool useFramePadding)
{
this.stream = stream;
FrameCount = stream.ReadUInt16();
/*var x = */stream.ReadUInt16();
/*var y = */stream.ReadUInt16();
Width = stream.ReadUInt16();
Height = stream.ReadUInt16();
var delta = stream.ReadUInt16() + 37;
var flags = stream.ReadUInt16();
frameOffsets = new uint[FrameCount + 2];
for (var i = 0; i < frameOffsets.Length; i++)
frameOffsets[i] = stream.ReadUInt32();
if (flags == 1)
{
palette = new uint[256];
for (var i = 0; i < palette.Length; i++)
{
var r = (byte)(stream.ReadByte() << 2);
var g = (byte)(stream.ReadByte() << 2);
var b = (byte)(stream.ReadByte() << 2);
// Replicate high bits into the (currently zero) low bits.
r |= (byte)(r >> 6);
g |= (byte)(g >> 6);
b |= (byte)(b >> 6);
palette[i] = (uint)((255 << 24) | (r << 16) | (g << 8) | b);
}
for (var i = 0; i < frameOffsets.Length; i++)
frameOffsets[i] += 768;
}
if (useFramePadding)
{
var frameSize = Exts.NextPowerOf2(Math.Max(Width, Height));
CurrentFrameData = new byte[frameSize * frameSize * 4];
totalFrameWidth = (ushort)frameSize;
}
else
{
CurrentFrameData = new byte[Width * Height * 4];
totalFrameWidth = Width;
}
Reset();
}
public void Reset()
{
CurrentFrameNumber = 0;
previousFramePaletteIndexData = null;
LoadFrame();
}
public void AdvanceFrame()
{
previousFramePaletteIndexData = currentFramePaletteIndexData;
CurrentFrameNumber++;
LoadFrame();
}
void LoadFrame()
{
if (CurrentFrameNumber >= FrameCount)
return;
stream.Seek(frameOffsets[CurrentFrameNumber], SeekOrigin.Begin);
var dataLength = frameOffsets[CurrentFrameNumber + 1] - frameOffsets[CurrentFrameNumber];
var rawData = StreamExts.ReadBytes(stream, (int)dataLength);
var intermediateData = new byte[Width * Height];
// Format80 decompression
LCWCompression.DecodeInto(rawData, intermediateData);
// and Format40 decompression
currentFramePaletteIndexData = new byte[Width * Height];
if (previousFramePaletteIndexData == null)
Array.Clear(currentFramePaletteIndexData, 0, currentFramePaletteIndexData.Length);
else
Array.Copy(previousFramePaletteIndexData, currentFramePaletteIndexData, currentFramePaletteIndexData.Length);
XORDeltaCompression.DecodeInto(intermediateData, currentFramePaletteIndexData, 0);
var c = 0;
var position = 0;
for (var y = 0; y < Height; y++)
{
for (var x = 0; x < Width; x++)
{
var color = palette[currentFramePaletteIndexData[c++]];
CurrentFrameData[position++] = (byte)(color & 0xFF);
CurrentFrameData[position++] = (byte)(color >> 8 & 0xFF);
CurrentFrameData[position++] = (byte)(color >> 16 & 0xFF);
CurrentFrameData[position++] = (byte)(color >> 24 & 0xFF);
}
// Recalculate the position in the byte array to the start of the next pixel row just in case there is padding in the frame.
position = (y + 1) * totalFrameWidth * 4;
}
}
}
}