(WebP): ported lossless support (incomplete).

This commit is contained in:
morkt 2016-09-04 04:50:49 +04:00
parent e2a0d76b65
commit bc62faa71a
3 changed files with 135 additions and 16 deletions

View File

@ -74,6 +74,10 @@ namespace GameRes.Formats.Google
m_alpha_plane = new byte[info.Width * info.Height];
Format = PixelFormats.Bgra32;
}
else if (m_info.HasAlpha)
{
Format = PixelFormats.Bgra32;
}
else
{
Format = PixelFormats.Bgr32;
@ -90,13 +94,22 @@ namespace GameRes.Formats.Google
public void Decode ()
{
if (m_info.IsLossless)
throw new NotImplementedException ("Lossless WebP not implemented");
m_input.BaseStream.Position = m_info.DataOffset;
GetHeaders();
EnterCritical();
InitFrame();
ParseFrame();
if (m_info.IsLossless)
{
m_io.opaque = m_output;
var ld = new LosslessDecoder();
ld.Init (m_input, m_info.DataSize, m_io);
if (!ld.DecodeImage())
throw new InvalidFormatException();
}
else
{
GetHeaders();
EnterCritical();
InitFrame();
ParseFrame();
}
}
int ReadInt24 ()

View File

@ -35,6 +35,7 @@ namespace GameRes.Formats.Google
{
public WebPFeature Flags;
public bool IsLossless;
public bool HasAlpha;
public long DataOffset;
public int DataSize;
public long AlphaOffset;
@ -102,12 +103,24 @@ namespace GameRes.Formats.Google
{
if (chunk_size < 10 || 10 != stream.Read (header, 0, 10))
return null;
if (header[3] != 0x9D || header[4] != 1 || header[5] != 0x2A)
return null;
if (0 != (header[0] & 1)) // not a keyframe
return null;
info.Width = LittleEndian.ToUInt16 (header, 6) & 0x3FFFu;
info.Height = LittleEndian.ToUInt16 (header, 8) & 0x3FFFu;
if (info.IsLossless)
{
if (header[0] != 0x2F || (header[4] >> 5) != 0)
return null;
uint wh = LittleEndian.ToUInt32 (header, 1);
info.Width = (wh & 0x3FFFu) + 1;
info.Height = ((wh >> 14) & 0x3FFFu) + 1;
info.HasAlpha = 0 != (header[4] & 0x10);
}
else
{
if (header[3] != 0x9D || header[4] != 1 || header[5] != 0x2A)
return null;
if (0 != (header[0] & 1)) // not a keyframe
return null;
info.Width = LittleEndian.ToUInt16 (header, 6) & 0x3FFFu;
info.Height = LittleEndian.ToUInt16 (header, 8) & 0x3FFFu;
}
}
break;
}

View File

@ -37,6 +37,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
using System;
using System.IO;
using GameRes.Utility;
namespace GameRes.Formats.Google
@ -405,7 +406,7 @@ namespace GameRes.Formats.Google
}
static uint Predictor11(uint left, uint[] data, int top)
{
return Select (data[top], data[left], data[top-1]);
return Select (data[top], left, data[top-1]);
}
static uint Predictor12 (uint left, uint[] data, int top)
{
@ -598,6 +599,13 @@ namespace GameRes.Formats.Google
br_.Init (data, data_i, (uint)data_size);
}
public void Init (BinaryReader input, int length, VP8Io io)
{
io_ = io;
br_.Init (input, (uint)length);
DecodeHeader();
}
public bool Is8bOptimizable ()
{
if (hdr_.color_cache_size_ > 0)
@ -616,6 +624,30 @@ namespace GameRes.Formats.Google
//------------------------------------------------------------------------------
void DecodeHeader ()
{
status_ = VP8StatusCode.Ok;
ReadImageInfo();
state_ = VP8DecodeState.ReadDim;
uint[] data = null;
if (!DecodeImageStream (io_.width, io_.height, true, ref data, false))
throw new InvalidFormatException();
}
void ReadImageInfo ()
{
if (0x2F != br_.ReadBits (8))
throw new InvalidFormatException();
width_ = (int)br_.ReadBits (14) + 1;
height_ = (int)br_.ReadBits (14) + 1;
io_.width = width_;
io_.height = height_;
bool has_alpha = 0 != br_.ReadBits (1);
if (br_.ReadBits (3) != 0)
throw new InvalidFormatException();
}
public bool DecodeImage ()
{
// Initialization.
@ -634,14 +666,67 @@ namespace GameRes.Formats.Google
}
// Decode.
return DecodeImageData (pixels32_, width_, height_, height_, ProcessRows);
return DecodeImageData (pixels32_, width_, height_, height_, (dec, row) => dec.ProcessRows (row));
}
// Processes (transforms, scales & color-converts) the rows decoded after the
// last call.
static void ProcessRows (LosslessDecoder dec, int row)
void ProcessRows (int row)
{
throw new NotImplementedException ("Lossless RGB decoder not implemented");
int rows = width_ * last_row_;
int num_rows = row - last_row_;
if (num_rows <= 0) return; // Nothing to be done.
ApplyInverseTransforms (num_rows, pixels32_, rows);
// Emit output.
int rows_data = argb_cache_;
int in_stride = io_.width * sizeof(uint); // in unit of RGBA
int out_stride = in_stride;
if (SetCropWindow (last_row_, row))
{
int rgba = last_out_row_ * out_stride;
int num_rows_out = EmitRows (pixels32_, rows_data, in_stride,
io_.mb_w, io_.mb_h, io_.opaque, rgba, out_stride);
// Update 'last_out_row_'.
last_out_row_ += num_rows_out;
}
// Update 'last_row_'.
last_row_ = row;
}
int EmitRows (uint[] input, int row_in, int in_stride, int mb_w, int mb_h, byte[] output, int row_out, int out_stride)
{
int lines = mb_h;
while (lines --> 0)
{
Buffer.BlockCopy (input, row_in, output, row_out, mb_w * 4);
row_in += in_stride;
row_out += out_stride;
}
return mb_h; // Num rows out == num rows in.
}
//------------------------------------------------------------------------------
// Cropping.
// Sets io->mb_y, io->mb_h & io->mb_w according to start row, end row and
// crop options. Also updates the input data pointer, so that it points to the
// start of the cropped window.
// Returns true if the crop window is not empty.
bool SetCropWindow (int y_start, int y_end)
{
if (y_end > io_.height)
{
y_end = io_.height; // make sure we don't overflow on last row.
}
if (y_start >= y_end) return false; // Crop window is empty.
io_.mb_y = y_start;
io_.mb_w = io_.width;
io_.mb_h = y_end - y_start;
return true; // Non-empty crop window.
}
//------------------------------------------------------------------------------
@ -1568,6 +1653,14 @@ namespace GameRes.Formats.Google
buf_ = input;
}
public void Init (BinaryReader input, uint length)
{
var buf = input.ReadBytes ((int)length);
if (buf.Length != length)
throw new EndOfStreamException();
Init (buf, 0, length);
}
public void CopyStateTo (LBitReader other)
{
other.val_ = val_;