diff --git a/ArcFormats/WebP/Decoder.cs b/ArcFormats/WebP/Decoder.cs index ab8dcdae..ba47fd76 100644 --- a/ArcFormats/WebP/Decoder.cs +++ b/ArcFormats/WebP/Decoder.cs @@ -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 () diff --git a/ArcFormats/WebP/ImageWEBP.cs b/ArcFormats/WebP/ImageWEBP.cs index a2d07174..a18f16fe 100644 --- a/ArcFormats/WebP/ImageWEBP.cs +++ b/ArcFormats/WebP/ImageWEBP.cs @@ -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; } diff --git a/ArcFormats/WebP/Lossless.cs b/ArcFormats/WebP/Lossless.cs index e2d52a42..3cfdc843 100644 --- a/ArcFormats/WebP/Lossless.cs +++ b/ArcFormats/WebP/Lossless.cs @@ -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_;